From 3f2bc4d6eb5a4fada842462ba22bb6bbb41d00c7 Mon Sep 17 00:00:00 2001 From: Bryan Huntsman Date: Tue, 16 Aug 2011 17:27:22 -0700 Subject: [PATCH] Initial Contribution msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142 Signed-off-by: Bryan Huntsman --- AndroidKernel.mk | 64 + Documentation/DocBook/debugobjects.tmpl | 50 + Documentation/arm/msm/boot.txt | 23 + .../arm/msm/emulate_domain_manager.txt | 254 + Documentation/arm/msm/gpiomux.txt | 170 +- Documentation/arm/msm/kgsl-sysfs.txt | 98 + Documentation/arm/msm/msm_rng-driver.txt | 75 + Documentation/arm/msm/pil.txt | 267 + Documentation/arm/msm/rpm.txt | 157 + Documentation/arm/msm/tsif.txt | 231 + Documentation/crypto/msm/qce.txt | 228 + Documentation/crypto/msm/qce40.txt | 241 + Documentation/crypto/msm/qcedev.txt | 232 + Documentation/crypto/msm/qcrypto.txt | 144 + Documentation/csdio.txt | 189 + Documentation/kernel-parameters.txt | 4 + Documentation/networking/ip-sysctl.txt | 8 +- Documentation/networking/qfec.txt | 309 + Documentation/sysctl/kernel.txt | 14 + Documentation/tzcom.txt | 181 + Documentation/usb/ehset_compliance.txt | 69 + Documentation/usb/gadget_rmnet.txt | 222 + Documentation/usb/gadget_sdio.txt | 32 + Documentation/usb/gadget_smd.txt | 27 + Documentation/usb/msm_otg.txt | 382 + Makefile | 6 +- arch/Kconfig | 7 + arch/arm/Kconfig | 91 +- arch/arm/Makefile | 1 + arch/arm/boot/compressed/head.S | 8 + arch/arm/common/Kconfig | 1 + arch/arm/common/cpaccess.c | 253 + arch/arm/common/gic.c | 168 + arch/arm/configs/apq8064_defconfig | 134 + arch/arm/configs/msm7627-perf_defconfig | 298 + arch/arm/configs/msm7627_defconfig | 299 + arch/arm/configs/msm7627a-perf_defconfig | 315 + arch/arm/configs/msm7627a_defconfig | 308 + arch/arm/configs/msm7630-perf_defconfig | 369 + arch/arm/configs/msm7630_defconfig | 358 + arch/arm/configs/msm8660-perf_defconfig | 427 + arch/arm/configs/msm8660_defconfig | 425 + arch/arm/configs/msm8960_defconfig | 365 + arch/arm/include/asm/cacheflush.h | 21 + arch/arm/include/asm/cputype.h | 6 + arch/arm/include/asm/delay.h | 5 +- arch/arm/include/asm/dma-mapping.h | 92 + arch/arm/include/asm/domain.h | 20 +- arch/arm/include/asm/hardware/cache-l2x0.h | 5 + arch/arm/include/asm/hardware/gic.h | 2 + arch/arm/include/asm/io.h | 2 + arch/arm/include/asm/mach/flash.h | 2 + arch/arm/include/asm/mach/map.h | 4 + arch/arm/include/asm/mach/mmc.h | 120 + arch/arm/include/asm/memory.h | 7 + arch/arm/include/asm/mutex.h | 8 + arch/arm/include/asm/page.h | 5 + arch/arm/include/asm/perf_event.h | 3 + arch/arm/include/asm/perftypes.h | 33 + arch/arm/include/asm/pgtable.h | 25 +- arch/arm/include/asm/pmu.h | 1 + arch/arm/include/asm/processor.h | 2 + arch/arm/include/asm/remote_spinlock.h | 18 + arch/arm/include/asm/setup.h | 21 + arch/arm/include/asm/system.h | 4 +- arch/arm/include/asm/tlbflush.h | 6 +- arch/arm/include/asm/vfp.h | 7 +- arch/arm/kernel/armksyms.c | 4 - arch/arm/kernel/entry-armv.S | 7 + arch/arm/kernel/head.S | 11 +- arch/arm/kernel/hw_breakpoint.c | 22 +- arch/arm/kernel/irq.c | 4 + arch/arm/kernel/machine_kexec.c | 3 +- arch/arm/kernel/perf_event.c | 36 +- arch/arm/kernel/perf_event_msm.c | 710 ++ arch/arm/kernel/perf_event_msm_krait.c | 399 + arch/arm/kernel/perf_event_msm_krait_l2.c | 656 ++ arch/arm/kernel/perf_event_msm_l2.c | 981 ++ arch/arm/kernel/pmu.c | 31 +- arch/arm/kernel/setup.c | 121 + arch/arm/kernel/smp.c | 5 + arch/arm/kernel/smp_twd.c | 3 +- arch/arm/kernel/traps.c | 4 + arch/arm/lib/delay.c | 90 + arch/arm/lib/lib1funcs.S | 2 +- arch/arm/mach-msm/Kconfig | 1513 ++- arch/arm/mach-msm/Makefile | 259 +- arch/arm/mach-msm/Makefile.boot | 47 +- arch/arm/mach-msm/acpuclock-7x30.c | 502 + arch/arm/mach-msm/acpuclock-8960.c | 969 ++ arch/arm/mach-msm/acpuclock-8x50.c | 723 ++ arch/arm/mach-msm/acpuclock-8x60.c | 965 ++ arch/arm/mach-msm/acpuclock-arm11.c | 5 +- arch/arm/mach-msm/acpuclock-fsm9xxx.c | 42 + arch/arm/mach-msm/acpuclock.c | 1046 ++ arch/arm/mach-msm/acpuclock.h | 24 +- arch/arm/mach-msm/arch-init-scorpion.S | 483 + arch/arm/mach-msm/avs.c | 280 + arch/arm/mach-msm/avs.h | 69 + arch/arm/mach-msm/avs_hw.S | 127 + arch/arm/mach-msm/bam_dmux.c | 784 ++ arch/arm/mach-msm/bms-batterydata.c | 93 + arch/arm/mach-msm/board-apq8064.c | 101 + arch/arm/mach-msm/board-fsm9xxx.c | 854 ++ arch/arm/mach-msm/board-halibut-keypad.c | 177 + arch/arm/mach-msm/board-halibut-panel.c | 73 + arch/arm/mach-msm/board-halibut.h | 20 + arch/arm/mach-msm/board-mahimahi-audio.c | 283 + arch/arm/mach-msm/board-mahimahi-flashlight.c | 279 + arch/arm/mach-msm/board-mahimahi-flashlight.h | 20 + arch/arm/mach-msm/board-mahimahi-keypad.c | 265 + arch/arm/mach-msm/board-mahimahi-microp.c | 2261 ++++ arch/arm/mach-msm/board-mahimahi-mmc.c | 454 + arch/arm/mach-msm/board-mahimahi-panel.c | 998 ++ arch/arm/mach-msm/board-mahimahi-rfkill.c | 122 + arch/arm/mach-msm/board-mahimahi-smb329.c | 177 + arch/arm/mach-msm/board-mahimahi-smb329.h | 32 + arch/arm/mach-msm/board-mahimahi-tpa2018d1.c | 368 + arch/arm/mach-msm/board-mahimahi-tpa2018d1.h | 35 + arch/arm/mach-msm/board-mahimahi-wifi.c | 146 + arch/arm/mach-msm/board-mahimahi.h | 175 + arch/arm/mach-msm/board-msm7x27.c | 2056 +++- arch/arm/mach-msm/board-msm7x27a.c | 3249 ++++++ arch/arm/mach-msm/board-msm7x30.c | 7269 +++++++++++- arch/arm/mach-msm/board-msm8960-regulator.c | 395 + arch/arm/mach-msm/board-msm8960.c | 3330 +++++- arch/arm/mach-msm/board-msm8960.h | 42 + arch/arm/mach-msm/board-msm8x60-vcm.c | 168 + arch/arm/mach-msm/board-msm8x60.c | 9855 ++++++++++++++++- arch/arm/mach-msm/board-qsd8x50.c | 2552 ++++- arch/arm/mach-msm/board-sapphire-gpio.c | 326 + arch/arm/mach-msm/board-sapphire-h2w.c | 545 + arch/arm/mach-msm/board-sapphire-keypad.c | 132 + arch/arm/mach-msm/board-sapphire-mmc.c | 486 + arch/arm/mach-msm/board-sapphire-panel.c | 1272 +++ arch/arm/mach-msm/board-sapphire-rfkill.c | 105 + arch/arm/mach-msm/board-sapphire-wifi.c | 74 + arch/arm/mach-msm/board-sapphire.h | 224 + arch/arm/mach-msm/board-swordfish-keypad.c | 177 + arch/arm/mach-msm/board-swordfish-mmc.c | 263 + arch/arm/mach-msm/board-swordfish-panel.c | 116 + arch/arm/mach-msm/board-swordfish.c | 366 + arch/arm/mach-msm/board-swordfish.h | 48 + arch/arm/mach-msm/board-trout-keypad.c | 345 + arch/arm/mach-msm/board-trout-rfkill.c | 101 + arch/arm/mach-msm/board-trout-wifi.c | 74 + arch/arm/mach-msm/btpintest.c | 234 + arch/arm/mach-msm/clock-7x30.c | 2994 +++++ arch/arm/mach-msm/clock-8960.c | 4134 +++++++ arch/arm/mach-msm/clock-8x60.c | 3822 +++++++ arch/arm/mach-msm/clock-debug.c | 126 +- arch/arm/mach-msm/clock-dss-8960.c | 328 + arch/arm/mach-msm/clock-dss-8960.h | 21 + arch/arm/mach-msm/clock-dummy.c | 70 + arch/arm/mach-msm/clock-fsm9xxx.c | 45 + arch/arm/mach-msm/clock-local.c | 978 ++ arch/arm/mach-msm/clock-local.h | 323 + arch/arm/mach-msm/clock-pcom-lookup.c | 385 + arch/arm/mach-msm/clock-pcom.c | 90 +- arch/arm/mach-msm/clock-pcom.h | 54 +- arch/arm/mach-msm/clock-rpm.c | 198 + arch/arm/mach-msm/clock-rpm.h | 66 + arch/arm/mach-msm/clock-voter.c | 187 + arch/arm/mach-msm/clock-voter.h | 43 + arch/arm/mach-msm/clock.c | 171 +- arch/arm/mach-msm/clock.h | 82 +- arch/arm/mach-msm/cp14.S | 669 ++ arch/arm/mach-msm/cp14.h | 19 + arch/arm/mach-msm/cpufreq.c | 281 + arch/arm/mach-msm/cpuidle.c | 151 + arch/arm/mach-msm/cpuidle.h | 60 + arch/arm/mach-msm/dal.c | 1326 +++ arch/arm/mach-msm/dal_axi.c | 102 + arch/arm/mach-msm/dal_remotetest.c | 410 + arch/arm/mach-msm/dal_remotetest.h | 172 + arch/arm/mach-msm/devices-8064.c | 565 + arch/arm/mach-msm/devices-8960.c | 2052 ++++ arch/arm/mach-msm/devices-fsm9xxx.c | 371 + arch/arm/mach-msm/devices-iommu.c | 5 - arch/arm/mach-msm/devices-msm7x00.c | 193 +- arch/arm/mach-msm/devices-msm7x01a.c | 764 ++ arch/arm/mach-msm/devices-msm7x25.c | 941 ++ arch/arm/mach-msm/devices-msm7x27.c | 841 ++ arch/arm/mach-msm/devices-msm7x27a.c | 699 ++ arch/arm/mach-msm/devices-msm7x2xa.h | 22 + arch/arm/mach-msm/devices-msm7x30.c | 1167 +- arch/arm/mach-msm/devices-msm8960.c | 85 - arch/arm/mach-msm/devices-msm8x60.c | 2339 ++++ arch/arm/mach-msm/devices-msm8x60.h | 57 + arch/arm/mach-msm/devices-qsd8x50.c | 818 +- arch/arm/mach-msm/devices.h | 130 +- arch/arm/mach-msm/devices_htc.c | 450 + arch/arm/mach-msm/dfe-fsm9xxx.c | 436 + arch/arm/mach-msm/dma.c | 719 +- arch/arm/mach-msm/dma_test.c | 360 + arch/arm/mach-msm/etm.c | 1001 ++ arch/arm/mach-msm/fiq.h | 21 + arch/arm/mach-msm/fiq_glue.S | 112 + arch/arm/mach-msm/fish_battery.c | 145 + arch/arm/mach-msm/footswitch-8x60.c | 599 + arch/arm/mach-msm/footswitch-pcom.c | 338 + arch/arm/mach-msm/footswitch.h | 52 + arch/arm/mach-msm/gpio-fsm9xxx.c | 282 + arch/arm/mach-msm/gpio-v2.c | 467 +- arch/arm/mach-msm/gpio.c | 344 +- arch/arm/mach-msm/gpio.h | 28 + arch/arm/mach-msm/gpio_hw.h | 40 + arch/arm/mach-msm/gpiomux-7x27.c | 20 + arch/arm/mach-msm/gpiomux-7x30.c | 20 + arch/arm/mach-msm/gpiomux-8x50.c | 45 +- arch/arm/mach-msm/gpiomux-8x60.c | 1703 ++- arch/arm/mach-msm/gpiomux-8x60.h | 21 + arch/arm/mach-msm/gpiomux-v1.c | 32 +- arch/arm/mach-msm/gpiomux-v1.h | 5 - arch/arm/mach-msm/gpiomux-v2.c | 25 +- arch/arm/mach-msm/gpiomux-v2.h | 5 - arch/arm/mach-msm/gpiomux.c | 126 +- arch/arm/mach-msm/gpiomux.h | 159 +- arch/arm/mach-msm/headsmp.S | 31 +- arch/arm/mach-msm/htc_35mm_jack.c | 397 + arch/arm/mach-msm/htc_acoustic.c | 239 + arch/arm/mach-msm/htc_acoustic_qsd.c | 315 + arch/arm/mach-msm/htc_akm_cal.c | 64 + arch/arm/mach-msm/htc_battery.c | 771 ++ arch/arm/mach-msm/htc_headset.c | 1246 +++ arch/arm/mach-msm/htc_power_supply.c | 616 ++ arch/arm/mach-msm/htc_pwrsink.c | 281 + arch/arm/mach-msm/htc_wifi_nvs.c | 55 + arch/arm/mach-msm/hw3d.c | 407 + arch/arm/mach-msm/idle-v6.S | 194 + arch/arm/mach-msm/idle-v7.S | 252 + arch/arm/mach-msm/idle.S | 90 +- arch/arm/mach-msm/idle.h | 27 + arch/arm/mach-msm/idle_stats.c | 545 + arch/arm/mach-msm/idle_stats.h | 52 + .../mach-msm/include/mach/audio_dma_msm8k.h | 216 + arch/arm/mach-msm/include/mach/bam_dmux.h | 40 + arch/arm/mach-msm/include/mach/barriers.h | 20 + arch/arm/mach-msm/include/mach/bcm_bt_lpm.h | 36 + arch/arm/mach-msm/include/mach/board.h | 372 +- arch/arm/mach-msm/include/mach/board_htc.h | 78 + arch/arm/mach-msm/include/mach/camera.h | 631 ++ arch/arm/mach-msm/include/mach/cpu.h | 5 - arch/arm/mach-msm/include/mach/dal.h | 150 + arch/arm/mach-msm/include/mach/dal_axi.h | 21 + arch/arm/mach-msm/include/mach/debug-macro.S | 48 +- arch/arm/mach-msm/include/mach/debug_mm.h | 31 + arch/arm/mach-msm/include/mach/dma-fsm9xxx.h | 62 + arch/arm/mach-msm/include/mach/dma.h | 221 +- arch/arm/mach-msm/include/mach/dma_test.h | 52 + .../mach-msm/include/mach/entry-macro-qgic.S | 10 +- arch/arm/mach-msm/include/mach/entry-macro.S | 15 +- arch/arm/mach-msm/include/mach/fiq.h | 33 + arch/arm/mach-msm/include/mach/gpio-tlmm-v1.h | 72 + arch/arm/mach-msm/include/mach/gpio-v1.h | 171 + arch/arm/mach-msm/include/mach/gpio.h | 225 +- .../arm/mach-msm/include/mach/htc_35mm_jack.h | 31 + .../mach-msm/include/mach/htc_acoustic_qsd.h | 29 + arch/arm/mach-msm/include/mach/htc_headset.h | 173 + arch/arm/mach-msm/include/mach/htc_pwrsink.h | 87 + arch/arm/mach-msm/include/mach/io.h | 9 +- arch/arm/mach-msm/include/mach/iommu.h | 6 +- .../arm/mach-msm/include/mach/iommu_domains.h | 60 + .../arm/mach-msm/include/mach/iommu_hw-8xxx.h | 20 +- arch/arm/mach-msm/include/mach/irqs-7x00.h | 4 +- arch/arm/mach-msm/include/mach/irqs-7x30.h | 17 +- arch/arm/mach-msm/include/mach/irqs-7xxx.h | 89 + arch/arm/mach-msm/include/mach/irqs-8064.h | 323 + arch/arm/mach-msm/include/mach/irqs-8960.h | 91 +- arch/arm/mach-msm/include/mach/irqs-8x50.h | 5 +- arch/arm/mach-msm/include/mach/irqs-8x60.h | 95 +- arch/arm/mach-msm/include/mach/irqs-fsm9xxx.h | 98 + arch/arm/mach-msm/include/mach/irqs.h | 27 +- arch/arm/mach-msm/include/mach/mdm.h | 34 + arch/arm/mach-msm/include/mach/memory.h | 88 +- arch/arm/mach-msm/include/mach/mpp.h | 276 + .../include/mach/msm-krait-l2-accessors.h | 36 + arch/arm/mach-msm/include/mach/msm72k_otg.h | 187 + arch/arm/mach-msm/include/mach/msm_adsp.h | 103 + .../arm/mach-msm/include/mach/msm_audio_aac.h | 71 + arch/arm/mach-msm/include/mach/msm_battery.h | 29 + arch/arm/mach-msm/include/mach/msm_bus.h | 113 + .../arm/mach-msm/include/mach/msm_bus_board.h | 271 + arch/arm/mach-msm/include/mach/msm_dsps.h | 92 + .../mach-msm/include/mach/msm_fast_timer.h | 19 + arch/arm/mach-msm/include/mach/msm_fb.h | 51 +- .../mach-msm/include/mach/msm_hdmi_audio.h | 18 + arch/arm/mach-msm/include/mach/msm_hsusb.h | 182 + arch/arm/mach-msm/include/mach/msm_hsusb_hw.h | 284 + arch/arm/mach-msm/include/mach/msm_i2ckbd.h | 27 + .../mach-msm/include/mach/msm_iomap-7x00.h | 49 +- .../mach-msm/include/mach/msm_iomap-7x30.h | 43 +- .../mach-msm/include/mach/msm_iomap-7xxx.h | 104 + .../mach-msm/include/mach/msm_iomap-8064.h | 47 + .../mach-msm/include/mach/msm_iomap-8960.h | 84 +- .../mach-msm/include/mach/msm_iomap-8x50.h | 73 +- .../mach-msm/include/mach/msm_iomap-8x60.h | 103 +- .../mach-msm/include/mach/msm_iomap-fsm9xxx.h | 112 + arch/arm/mach-msm/include/mach/msm_iomap.h | 48 +- arch/arm/mach-msm/include/mach/msm_memtypes.h | 55 + .../mach-msm/include/mach/msm_migrate_pages.h | 17 + arch/arm/mach-msm/include/mach/msm_otg.h | 78 + .../mach-msm/include/mach/msm_qdsp6_audio.h | 120 + .../mach-msm/include/mach/msm_qdsp6_audiov2.h | 87 + arch/arm/mach-msm/include/mach/msm_reqs.h | 87 + .../mach-msm/include/mach/msm_rotator_imem.h | 28 + .../arm/mach-msm/include/mach/msm_rpcrouter.h | 376 + .../include/mach/msm_serial_debugger.h | 26 + .../arm/mach-msm/include/mach/msm_serial_hs.h | 35 + .../include/mach/msm_serial_hs_lite.h | 24 + .../mach-msm/include/mach/msm_serial_pdata.h | 27 + arch/arm/mach-msm/include/mach/msm_smd.h | 144 +- arch/arm/mach-msm/include/mach/msm_smsm.h | 219 + arch/arm/mach-msm/include/mach/msm_spi.h | 24 + arch/arm/mach-msm/include/mach/msm_sps.h | 25 + .../mach-msm/include/mach/msm_subsystem_map.h | 61 + arch/arm/mach-msm/include/mach/msm_touch.h | 26 + arch/arm/mach-msm/include/mach/msm_touchpad.h | 22 + arch/arm/mach-msm/include/mach/msm_tsif.h | 26 + arch/arm/mach-msm/include/mach/msm_xo.h | 57 + .../mach-msm/include/mach/oem_rapi_client.h | 76 + .../mach-msm/include/mach/peripheral-loader.h | 27 + arch/arm/mach-msm/include/mach/pmic.h | 748 ++ .../include/mach/qdsp5/qdsp5audplaycmdi.h | 129 + .../include/mach/qdsp5/qdsp5audplaymsg.h | 84 + .../include/mach/qdsp5/qdsp5audppcmdi.h | 1004 ++ .../include/mach/qdsp5/qdsp5audppmsg.h | 321 + .../include/mach/qdsp5/qdsp5audpreproc.h | 33 + .../include/mach/qdsp5/qdsp5audpreproccmdi.h | 256 + .../include/mach/qdsp5/qdsp5audpreprocmsg.h | 85 + .../include/mach/qdsp5/qdsp5audreccmdi.h | 401 + .../include/mach/qdsp5/qdsp5audrecmsg.h | 223 + .../include/mach/qdsp5/qdsp5jpegcmdi.h | 377 + .../include/mach/qdsp5/qdsp5jpegmsg.h | 177 + .../include/mach/qdsp5/qdsp5lpmcmdi.h | 82 + .../mach-msm/include/mach/qdsp5/qdsp5lpmmsg.h | 80 + .../include/mach/qdsp5/qdsp5rmtcmdi.h | 55 + .../mach-msm/include/mach/qdsp5/qdsp5rmtmsg.h | 55 + .../include/mach/qdsp5/qdsp5vdeccmdi.h | 189 + .../include/mach/qdsp5/qdsp5vdecmsg.h | 107 + .../include/mach/qdsp5/qdsp5venccmdi.h | 231 + .../include/mach/qdsp5/qdsp5vfecmdi.h | 910 ++ .../mach-msm/include/mach/qdsp5/qdsp5vfemsg.h | 290 + .../mach-msm/include/mach/qdsp5/snd_adie.h | 86 + .../include/mach/qdsp5v2/acdb_commands.h | 303 + .../include/mach/qdsp5v2/adie_marimba.h | 93 + arch/arm/mach-msm/include/mach/qdsp5v2/afe.h | 52 + .../include/mach/qdsp5v2/audio_acdb_def.h | 51 + .../include/mach/qdsp5v2/audio_acdbi.h | 303 + .../mach-msm/include/mach/qdsp5v2/audio_def.h | 35 + .../include/mach/qdsp5v2/audio_dev_ctl.h | 206 + .../include/mach/qdsp5v2/audio_interct.h | 29 + .../arm/mach-msm/include/mach/qdsp5v2/audpp.h | 129 + .../include/mach/qdsp5v2/audpreproc.h | 96 + .../mach-msm/include/mach/qdsp5v2/aux_pcm.h | 55 + .../include/mach/qdsp5v2/codec_utils.h | 136 + arch/arm/mach-msm/include/mach/qdsp5v2/lpa.h | 36 + .../mach-msm/include/mach/qdsp5v2/lpa_hw.h | 236 + .../include/mach/qdsp5v2/marimba_profile.h | 3201 ++++++ arch/arm/mach-msm/include/mach/qdsp5v2/mi2s.h | 50 + .../mach-msm/include/mach/qdsp5v2/mp3_funcs.h | 20 + .../mach-msm/include/mach/qdsp5v2/msm_lpa.h | 31 + .../mach-msm/include/mach/qdsp5v2/pcm_funcs.h | 19 + .../include/mach/qdsp5v2/qdsp5afecmdi.h | 125 + .../include/mach/qdsp5v2/qdsp5afemsg.h | 31 + .../include/mach/qdsp5v2/qdsp5audplaycmdi.h | 145 + .../include/mach/qdsp5v2/qdsp5audplaymsg.h | 74 + .../include/mach/qdsp5v2/qdsp5audppcmdi.h | 1088 ++ .../include/mach/qdsp5v2/qdsp5audppmsg.h | 311 + .../mach/qdsp5v2/qdsp5audpreproccmdi.h | 519 + .../include/mach/qdsp5v2/qdsp5audpreprocmsg.h | 127 + .../include/mach/qdsp5v2/qdsp5audreccmdi.h | 122 + .../include/mach/qdsp5v2/qdsp5audrecmsg.h | 115 + .../include/mach/qdsp5v2/snddev_ecodec.h | 29 + .../include/mach/qdsp5v2/snddev_icodec.h | 41 + .../include/mach/qdsp5v2/snddev_mi2s.h | 36 + .../include/mach/qdsp5v2/snddev_virtual.h | 23 + .../arm/mach-msm/include/mach/qdsp5v2/voice.h | 117 + arch/arm/mach-msm/include/mach/qdsp6v2/apr.h | 151 + .../mach-msm/include/mach/qdsp6v2/apr_tal.h | 55 + .../include/mach/qdsp6v2/audio_acdb.h | 57 + .../include/mach/qdsp6v2/audio_dev_ctl.h | 216 + .../mach-msm/include/mach/qdsp6v2/dsp_debug.h | 22 + .../mach-msm/include/mach/qdsp6v2/q6voice.h | 752 ++ arch/arm/mach-msm/include/mach/qdsp6v2/rtac.h | 46 + .../mach-msm/include/mach/remote_spinlock.h | 235 + arch/arm/mach-msm/include/mach/restart.h | 27 + arch/arm/mach-msm/include/mach/rpc_hsusb.h | 99 + arch/arm/mach-msm/include/mach/rpc_pmapp.h | 74 + .../include/mach/rpc_server_handset.h | 24 + arch/arm/mach-msm/include/mach/rpm-8660.h | 460 + arch/arm/mach-msm/include/mach/rpm-8960.h | 423 + .../include/mach/rpm-regulator-8660.h | 176 + .../include/mach/rpm-regulator-8960.h | 284 + .../arm/mach-msm/include/mach/rpm-regulator.h | 59 + arch/arm/mach-msm/include/mach/rpm.h | 135 + arch/arm/mach-msm/include/mach/scm-io.h | 29 + arch/arm/mach-msm/include/mach/scm.h | 28 + arch/arm/mach-msm/include/mach/sdio_al.h | 114 + arch/arm/mach-msm/include/mach/sdio_cmux.h | 97 + arch/arm/mach-msm/include/mach/sdio_dmux.h | 46 + arch/arm/mach-msm/include/mach/sdio_smem.h | 32 + arch/arm/mach-msm/include/mach/sdio_tty.h | 48 + arch/arm/mach-msm/include/mach/sirc-fsm9xxx.h | 84 + arch/arm/mach-msm/include/mach/sirc.h | 19 +- arch/arm/mach-msm/include/mach/smem_log.h | 230 + arch/arm/mach-msm/include/mach/smp.h | 24 + arch/arm/mach-msm/include/mach/socinfo.h | 202 + arch/arm/mach-msm/include/mach/sps.h | 1136 ++ .../mach-msm/include/mach/subsystem_notif.h | 80 + .../mach-msm/include/mach/subsystem_restart.h | 72 + arch/arm/mach-msm/include/mach/system.h | 7 + arch/arm/mach-msm/include/mach/timex.h | 4 + arch/arm/mach-msm/include/mach/tpm_st_i2c.h | 25 + arch/arm/mach-msm/include/mach/uncompress.h | 57 +- .../include/mach/usb_gadget_fserial.h | 35 + arch/arm/mach-msm/include/mach/usbdiag.h | 58 + arch/arm/mach-msm/include/mach/vmalloc.h | 7 +- arch/arm/mach-msm/io.c | 183 +- arch/arm/mach-msm/iommu.c | 103 +- arch/arm/mach-msm/iommu_dev.c | 22 +- arch/arm/mach-msm/iommu_domains.c | 273 + arch/arm/mach-msm/ipc_router.c | 2169 ++++ arch/arm/mach-msm/ipc_router.h | 204 + arch/arm/mach-msm/ipc_router_smd_xprt.c | 272 + arch/arm/mach-msm/ipc_socket.c | 560 + arch/arm/mach-msm/irq-vic.c | 360 +- arch/arm/mach-msm/irq.c | 510 +- arch/arm/mach-msm/irq.h | 28 + arch/arm/mach-msm/jtag-v7.S | 123 + arch/arm/mach-msm/keypad-surf-ffa.c | 279 + arch/arm/mach-msm/mdm.c | 464 + arch/arm/mach-msm/memory.c | 403 + arch/arm/mach-msm/memory_topology.c | 35 + arch/arm/mach-msm/mkrpcsym.pl | 162 + arch/arm/mach-msm/modem-8960.c | 227 + arch/arm/mach-msm/modem_notifier.c | 213 + arch/arm/mach-msm/modem_notifier.h | 34 + arch/arm/mach-msm/mpm.c | 518 + arch/arm/mach-msm/mpm.h | 77 + arch/arm/mach-msm/mpp.c | 96 + arch/arm/mach-msm/msm-keypad-devices.h | 22 + arch/arm/mach-msm/msm-krait-l2-accessors.c | 64 + arch/arm/mach-msm/msm_bus/Makefile | 7 + arch/arm/mach-msm/msm_bus/msm_bus_arb.c | 655 ++ .../arm/mach-msm/msm_bus/msm_bus_board_8660.c | 908 ++ .../arm/mach-msm/msm_bus/msm_bus_board_8960.c | 931 ++ arch/arm/mach-msm/msm_bus/msm_bus_config.c | 75 + arch/arm/mach-msm/msm_bus/msm_bus_core.c | 114 + arch/arm/mach-msm/msm_bus/msm_bus_core.h | 191 + arch/arm/mach-msm/msm_bus/msm_bus_dbg.c | 709 ++ arch/arm/mach-msm/msm_bus/msm_bus_fabric.c | 758 ++ arch/arm/mach-msm/msm_bus/msm_bus_rpm.c | 326 + arch/arm/mach-msm/msm_dsps.c | 732 ++ arch/arm/mach-msm/msm_fault_handlers.c | 84 + arch/arm/mach-msm/msm_kexec.c | 30 + arch/arm/mach-msm/msm_pm_qos.c | 69 + arch/arm/mach-msm/msm_pm_qos.h | 29 + arch/arm/mach-msm/msm_reqs.c | 125 + arch/arm/mach-msm/msm_rq_stats.c | 264 + arch/arm/mach-msm/msm_show_resume_irq.c | 22 + arch/arm/mach-msm/msm_vibrator.c | 161 + arch/arm/mach-msm/msm_watchdog.c | 340 + arch/arm/mach-msm/msm_watchdog.h | 22 + arch/arm/mach-msm/msm_xo.c | 301 + arch/arm/mach-msm/nand_partitions.c | 187 + arch/arm/mach-msm/no-pm.c | 40 + arch/arm/mach-msm/nohlt.c | 40 + arch/arm/mach-msm/oem_rapi_client.c | 355 + arch/arm/mach-msm/peripheral-loader.c | 439 + arch/arm/mach-msm/peripheral-loader.h | 38 + arch/arm/mach-msm/peripheral-reset-8960.c | 653 ++ arch/arm/mach-msm/peripheral-reset.c | 694 ++ arch/arm/mach-msm/ping_apps_server.c | 605 + arch/arm/mach-msm/ping_mdm_rpc_client.c | 812 ++ arch/arm/mach-msm/platsmp.c | 225 +- arch/arm/mach-msm/pm-8x60.c | 1222 ++ arch/arm/mach-msm/pm.c | 919 ++ arch/arm/mach-msm/pm.h | 67 + arch/arm/mach-msm/pm2.c | 1894 ++++ arch/arm/mach-msm/pmic.c | 1286 +++ arch/arm/mach-msm/pmic.h | 295 + arch/arm/mach-msm/pmic8058-gpio.c | 705 ++ arch/arm/mach-msm/pmic8058-mpp.c | 258 + arch/arm/mach-msm/pmic_debugfs.c | 1156 ++ arch/arm/mach-msm/pmu.c | 58 + arch/arm/mach-msm/proc_comm.c | 142 +- arch/arm/mach-msm/proc_comm.h | 123 +- arch/arm/mach-msm/proc_comm_test.c | 125 + arch/arm/mach-msm/qdsp5/Makefile | 17 + arch/arm/mach-msm/qdsp5/adsp.c | 1397 +++ arch/arm/mach-msm/qdsp5/adsp.h | 352 + arch/arm/mach-msm/qdsp5/adsp_6210.c | 283 + arch/arm/mach-msm/qdsp5/adsp_6220.c | 284 + arch/arm/mach-msm/qdsp5/adsp_6225.c | 328 + arch/arm/mach-msm/qdsp5/adsp_driver.c | 667 ++ arch/arm/mach-msm/qdsp5/adsp_info.c | 144 + .../mach-msm/qdsp5/adsp_jpeg_patch_event.c | 39 + .../arm/mach-msm/qdsp5/adsp_jpeg_verify_cmd.c | 201 + arch/arm/mach-msm/qdsp5/adsp_lpm_verify_cmd.c | 66 + arch/arm/mach-msm/qdsp5/adsp_rm.c | 192 + .../arm/mach-msm/qdsp5/adsp_vfe_patch_event.c | 54 + arch/arm/mach-msm/qdsp5/adsp_vfe_verify_cmd.c | 244 + .../mach-msm/qdsp5/adsp_video_verify_cmd.c | 233 + .../mach-msm/qdsp5/adsp_videoenc_verify_cmd.c | 235 + arch/arm/mach-msm/qdsp5/audio_aac.c | 1899 ++++ arch/arm/mach-msm/qdsp5/audio_amrnb.c | 1609 +++ arch/arm/mach-msm/qdsp5/audio_amrnb_in.c | 669 ++ arch/arm/mach-msm/qdsp5/audio_amrwb.c | 1680 +++ arch/arm/mach-msm/qdsp5/audio_evrc.c | 1603 +++ arch/arm/mach-msm/qdsp5/audio_evrc_in.c | 1375 +++ arch/arm/mach-msm/qdsp5/audio_fm.c | 169 + arch/arm/mach-msm/qdsp5/audio_in.c | 996 ++ arch/arm/mach-msm/qdsp5/audio_mp3.c | 2337 ++++ arch/arm/mach-msm/qdsp5/audio_out.c | 1020 ++ arch/arm/mach-msm/qdsp5/audio_pcm.c | 1692 +++ arch/arm/mach-msm/qdsp5/audio_pcm_in.c | 950 ++ arch/arm/mach-msm/qdsp5/audio_qcelp.c | 1602 +++ arch/arm/mach-msm/qdsp5/audio_qcelp_in.c | 1380 +++ arch/arm/mach-msm/qdsp5/audio_voicememo.c | 960 ++ arch/arm/mach-msm/qdsp5/audio_wma.c | 1760 +++ arch/arm/mach-msm/qdsp5/audio_wmapro.c | 1748 +++ arch/arm/mach-msm/qdsp5/audmgr.c | 365 + arch/arm/mach-msm/qdsp5/audmgr.h | 266 + arch/arm/mach-msm/qdsp5/audmgr_new.h | 213 + arch/arm/mach-msm/qdsp5/audpp.c | 873 ++ arch/arm/mach-msm/qdsp5/audpreproc.c | 169 + arch/arm/mach-msm/qdsp5/audrec.c | 272 + arch/arm/mach-msm/qdsp5/evlog.h | 125 + arch/arm/mach-msm/qdsp5/snd.c | 675 ++ arch/arm/mach-msm/qdsp5/snd_adie.c | 480 + arch/arm/mach-msm/qdsp5v2/Makefile | 22 + arch/arm/mach-msm/qdsp5v2/adsp.c | 1229 ++ arch/arm/mach-msm/qdsp5v2/adsp.h | 336 + arch/arm/mach-msm/qdsp5v2/adsp_driver.c | 655 ++ arch/arm/mach-msm/qdsp5v2/adsp_info.c | 121 + arch/arm/mach-msm/qdsp5v2/afe.c | 534 + arch/arm/mach-msm/qdsp5v2/audio_a2dp_in.c | 977 ++ arch/arm/mach-msm/qdsp5v2/audio_aac.c | 2025 ++++ arch/arm/mach-msm/qdsp5v2/audio_aac_in.c | 1465 +++ arch/arm/mach-msm/qdsp5v2/audio_acdb.c | 3407 ++++++ arch/arm/mach-msm/qdsp5v2/audio_adpcm.c | 1741 +++ arch/arm/mach-msm/qdsp5v2/audio_amrnb.c | 1633 +++ arch/arm/mach-msm/qdsp5v2/audio_amrnb_in.c | 887 ++ arch/arm/mach-msm/qdsp5v2/audio_amrwb.c | 1712 +++ arch/arm/mach-msm/qdsp5v2/audio_dev_ctl.c | 1328 +++ arch/arm/mach-msm/qdsp5v2/audio_evrc.c | 1624 +++ arch/arm/mach-msm/qdsp5v2/audio_evrc_in.c | 1490 +++ arch/arm/mach-msm/qdsp5v2/audio_fm.c | 358 + arch/arm/mach-msm/qdsp5v2/audio_interct.c | 124 + arch/arm/mach-msm/qdsp5v2/audio_lpa.c | 1700 +++ arch/arm/mach-msm/qdsp5v2/audio_mp3.c | 2505 +++++ arch/arm/mach-msm/qdsp5v2/audio_mvs.c | 1748 +++ arch/arm/mach-msm/qdsp5v2/audio_out.c | 722 ++ arch/arm/mach-msm/qdsp5v2/audio_pcm.c | 1697 +++ arch/arm/mach-msm/qdsp5v2/audio_pcm_in.c | 940 ++ arch/arm/mach-msm/qdsp5v2/audio_qcelp.c | 1625 +++ arch/arm/mach-msm/qdsp5v2/audio_qcelp_in.c | 1497 +++ arch/arm/mach-msm/qdsp5v2/audio_wma.c | 1785 +++ arch/arm/mach-msm/qdsp5v2/audio_wmapro.c | 1801 +++ arch/arm/mach-msm/qdsp5v2/audpp.c | 1133 ++ arch/arm/mach-msm/qdsp5v2/audpreproc.c | 523 + arch/arm/mach-msm/qdsp5v2/aux_pcm.c | 280 + arch/arm/mach-msm/qdsp5v2/lpa.c | 608 + arch/arm/mach-msm/qdsp5v2/mi2s.c | 885 ++ arch/arm/mach-msm/qdsp5v2/mp3_funcs.c | 45 + arch/arm/mach-msm/qdsp5v2/pcm_funcs.c | 47 + .../mach-msm/qdsp5v2/snddev_data_marimba.c | 1537 +++ .../mach-msm/qdsp5v2/snddev_data_timpani.c | 1006 ++ arch/arm/mach-msm/qdsp5v2/snddev_ecodec.c | 484 + arch/arm/mach-msm/qdsp5v2/snddev_icodec.c | 1211 ++ arch/arm/mach-msm/qdsp5v2/snddev_mi2s.c | 405 + arch/arm/mach-msm/qdsp5v2/snddev_virtual.c | 122 + .../mach-msm/qdsp5v2/timpani_profile_7x30.h | 622 ++ arch/arm/mach-msm/qdsp5v2/voice.c | 748 ++ arch/arm/mach-msm/qdsp6/Makefile | 19 + arch/arm/mach-msm/qdsp6/aac_in.c | 470 + arch/arm/mach-msm/qdsp6/amrnb_in.c | 277 + arch/arm/mach-msm/qdsp6/analog_audio.c | 94 + arch/arm/mach-msm/qdsp6/audio_ctl.c | 179 + arch/arm/mach-msm/qdsp6/audiov2/Makefile | 12 + arch/arm/mach-msm/qdsp6/audiov2/aac_in.c | 266 + arch/arm/mach-msm/qdsp6/audiov2/amrnb_in.c | 237 + .../arm/mach-msm/qdsp6/audiov2/analog_audio.c | 85 + arch/arm/mach-msm/qdsp6/audiov2/audio_ctl.c | 140 + arch/arm/mach-msm/qdsp6/audiov2/dal_acdb.h | 71 + arch/arm/mach-msm/qdsp6/audiov2/dal_adie.h | 89 + arch/arm/mach-msm/qdsp6/audiov2/dal_audio.h | 546 + .../mach-msm/qdsp6/audiov2/dal_audio_format.h | 284 + arch/arm/mach-msm/qdsp6/audiov2/dal_voice.h | 69 + arch/arm/mach-msm/qdsp6/audiov2/evrc_in.c | 250 + arch/arm/mach-msm/qdsp6/audiov2/mp3.c | 205 + arch/arm/mach-msm/qdsp6/audiov2/pcm_in.c | 208 + arch/arm/mach-msm/qdsp6/audiov2/pcm_out.c | 196 + arch/arm/mach-msm/qdsp6/audiov2/q6audio.c | 1312 +++ .../mach-msm/qdsp6/audiov2/q6audio_devices.h | 276 + arch/arm/mach-msm/qdsp6/audiov2/qcelp_in.c | 255 + arch/arm/mach-msm/qdsp6/audiov2/routing.c | 73 + arch/arm/mach-msm/qdsp6/audiov2/voice.c | 188 + arch/arm/mach-msm/qdsp6/auxpcm_lb_in.c | 190 + arch/arm/mach-msm/qdsp6/auxpcm_lb_out.c | 191 + arch/arm/mach-msm/qdsp6/dal.c | 727 ++ arch/arm/mach-msm/qdsp6/dal.h | 96 + arch/arm/mach-msm/qdsp6/dal_acdb.h | 69 + arch/arm/mach-msm/qdsp6/dal_adie.h | 104 + arch/arm/mach-msm/qdsp6/dal_audio.h | 604 + arch/arm/mach-msm/qdsp6/dal_audio_format.h | 270 + arch/arm/mach-msm/qdsp6/dsp_debug.c | 179 + arch/arm/mach-msm/qdsp6/dtmf.c | 126 + arch/arm/mach-msm/qdsp6/evrc_in.c | 468 + arch/arm/mach-msm/qdsp6/mp3.c | 249 + arch/arm/mach-msm/qdsp6/msm_q6vdec.c | 1505 +++ arch/arm/mach-msm/qdsp6/msm_q6venc.c | 1195 ++ arch/arm/mach-msm/qdsp6/pcm_in.c | 263 + arch/arm/mach-msm/qdsp6/pcm_out.c | 276 + arch/arm/mach-msm/qdsp6/q6audio.c | 2155 ++++ arch/arm/mach-msm/qdsp6/q6audio_devices.h | 334 + arch/arm/mach-msm/qdsp6/qcelp_in.c | 475 + arch/arm/mach-msm/qdsp6/routing.c | 78 + arch/arm/mach-msm/qdsp6v2/Makefile | 17 + arch/arm/mach-msm/qdsp6v2/aac_in.c | 430 + arch/arm/mach-msm/qdsp6v2/amrnb_in.c | 325 + arch/arm/mach-msm/qdsp6v2/apr.c | 673 ++ arch/arm/mach-msm/qdsp6v2/apr_tal.c | 279 + arch/arm/mach-msm/qdsp6v2/audio_aac.c | 1674 +++ arch/arm/mach-msm/qdsp6v2/audio_acdb.c | 905 ++ arch/arm/mach-msm/qdsp6v2/audio_dev_ctl.c | 1701 +++ arch/arm/mach-msm/qdsp6v2/audio_lpa.c | 1420 +++ arch/arm/mach-msm/qdsp6v2/audio_lpa.h | 112 + arch/arm/mach-msm/qdsp6v2/audio_multi_aac.c | 1673 +++ arch/arm/mach-msm/qdsp6v2/audio_mvs.c | 992 ++ arch/arm/mach-msm/qdsp6v2/audio_utils.c | 639 ++ arch/arm/mach-msm/qdsp6v2/audio_utils.h | 104 + arch/arm/mach-msm/qdsp6v2/audio_wma.c | 1585 +++ arch/arm/mach-msm/qdsp6v2/audio_wmapro.c | 1644 +++ .../mach-msm/qdsp6v2/board-msm8x60-audio.c | 2507 +++++ arch/arm/mach-msm/qdsp6v2/dsp_debug.c | 224 + arch/arm/mach-msm/qdsp6v2/evrc_in.c | 332 + arch/arm/mach-msm/qdsp6v2/fm.c | 262 + arch/arm/mach-msm/qdsp6v2/lpa_if_hdmi.c | 381 + arch/arm/mach-msm/qdsp6v2/msm_qdsp6_audio.h | 52 + arch/arm/mach-msm/qdsp6v2/pcm_in.c | 462 + arch/arm/mach-msm/qdsp6v2/pcm_out.c | 470 + arch/arm/mach-msm/qdsp6v2/q6core.c | 409 + arch/arm/mach-msm/qdsp6v2/q6core.h | 52 + arch/arm/mach-msm/qdsp6v2/q6voice.c | 2817 +++++ arch/arm/mach-msm/qdsp6v2/qcelp_in.c | 329 + arch/arm/mach-msm/qdsp6v2/rtac.c | 1032 ++ arch/arm/mach-msm/qdsp6v2/snddev_ecodec.c | 382 + arch/arm/mach-msm/qdsp6v2/snddev_ecodec.h | 26 + arch/arm/mach-msm/qdsp6v2/snddev_hdmi.c | 198 + arch/arm/mach-msm/qdsp6v2/snddev_hdmi.h | 25 + arch/arm/mach-msm/qdsp6v2/snddev_icodec.c | 1087 ++ arch/arm/mach-msm/qdsp6v2/snddev_icodec.h | 35 + arch/arm/mach-msm/qdsp6v2/snddev_mi2s.c | 462 + arch/arm/mach-msm/qdsp6v2/snddev_mi2s.h | 30 + arch/arm/mach-msm/qdsp6v2/snddev_virtual.c | 172 + arch/arm/mach-msm/qdsp6v2/snddev_virtual.h | 20 + .../mach-msm/qdsp6v2/timpani_profile_8x60.h | 3100 ++++++ arch/arm/mach-msm/ramdump.c | 262 + arch/arm/mach-msm/ramdump.h | 25 + arch/arm/mach-msm/remote_spinlock.c | 128 + arch/arm/mach-msm/reset_modem.c | 183 + arch/arm/mach-msm/restart-fsm9xxx.c | 42 + arch/arm/mach-msm/restart.c | 196 + arch/arm/mach-msm/rfic-fsm9xxx.c | 400 + arch/arm/mach-msm/rmt_storage_client.c | 1753 +++ arch/arm/mach-msm/rpc_dog_keepalive.c | 240 + arch/arm/mach-msm/rpc_fsusb.c | 243 + arch/arm/mach-msm/rpc_hsusb.c | 659 ++ arch/arm/mach-msm/rpc_pmapp.c | 560 + arch/arm/mach-msm/rpc_server_dog_keepalive.c | 77 + arch/arm/mach-msm/rpc_server_handset.c | 707 ++ arch/arm/mach-msm/rpc_server_time_remote.c | 168 + arch/arm/mach-msm/rpc_server_time_remote.h | 21 + arch/arm/mach-msm/rpcrouter_sdio_xprt.c | 656 ++ arch/arm/mach-msm/rpcrouter_smd_xprt.c | 331 + arch/arm/mach-msm/rpm-regulator-8960.c | 1663 +++ arch/arm/mach-msm/rpm-regulator.c | 1667 +++ arch/arm/mach-msm/rpm.c | 831 ++ arch/arm/mach-msm/rpm_log.c | 363 + arch/arm/mach-msm/rpm_log.h | 34 + arch/arm/mach-msm/rpm_resources.c | 1014 ++ arch/arm/mach-msm/rpm_resources.h | 124 + arch/arm/mach-msm/rpm_stats.c | 247 + arch/arm/mach-msm/rpm_stats.h | 23 + arch/arm/mach-msm/saw-regulator.c | 236 + arch/arm/mach-msm/scm-boot.c | 12 +- arch/arm/mach-msm/scm-boot.h | 2 +- arch/arm/mach-msm/scm-io.c | 86 + arch/arm/mach-msm/scm.c | 54 +- arch/arm/mach-msm/sdio_al.c | 3526 ++++++ arch/arm/mach-msm/sdio_al_dloader.c | 2540 +++++ arch/arm/mach-msm/sdio_al_private.h | 255 + arch/arm/mach-msm/sdio_al_test.c | 2199 ++++ arch/arm/mach-msm/sdio_cmux.c | 866 ++ arch/arm/mach-msm/sdio_ctl.c | 507 + arch/arm/mach-msm/sdio_dmux.c | 906 ++ arch/arm/mach-msm/sdio_smem.c | 149 + arch/arm/mach-msm/sdio_tty.c | 588 + arch/arm/mach-msm/sdio_tty_ciq.c | 134 + arch/arm/mach-msm/sirc-fsm9xxx.c | 166 + arch/arm/mach-msm/sirc.c | 93 +- arch/arm/mach-msm/sirc.h | 36 + arch/arm/mach-msm/smd.c | 2026 +++- arch/arm/mach-msm/smd_debug.c | 593 +- arch/arm/mach-msm/smd_nmea.c | 205 + arch/arm/mach-msm/smd_pkt.c | 864 ++ arch/arm/mach-msm/smd_private.h | 380 +- arch/arm/mach-msm/smd_qmi.c | 848 ++ arch/arm/mach-msm/smd_rpc_sym | 156 + arch/arm/mach-msm/smd_rpc_sym.h | 37 + arch/arm/mach-msm/smd_rpcrouter.c | 2508 +++++ arch/arm/mach-msm/smd_rpcrouter.h | 266 + arch/arm/mach-msm/smd_rpcrouter_clients.c | 916 ++ arch/arm/mach-msm/smd_rpcrouter_device.c | 476 + arch/arm/mach-msm/smd_rpcrouter_servers.c | 618 ++ arch/arm/mach-msm/smd_rpcrouter_xdr.c | 415 + arch/arm/mach-msm/smd_tty.c | 590 + arch/arm/mach-msm/smem_log.c | 1961 ++++ arch/arm/mach-msm/socinfo.c | 666 ++ arch/arm/mach-msm/spm-v2.c | 318 + arch/arm/mach-msm/spm.c | 303 + arch/arm/mach-msm/spm.h | 153 + arch/arm/mach-msm/spm_devices.c | 168 + arch/arm/mach-msm/spm_driver.h | 36 + arch/arm/mach-msm/subsystem-fatal-8x60.c | 416 + arch/arm/mach-msm/subsystem_map.c | 521 + arch/arm/mach-msm/subsystem_notif.c | 222 + arch/arm/mach-msm/subsystem_restart.c | 523 + arch/arm/mach-msm/timer.c | 1076 +- arch/arm/mach-msm/timer.h | 23 + arch/arm/mach-msm/vreg.c | 29 +- arch/arm/mm/Kconfig | 45 +- arch/arm/mm/Makefile | 3 + arch/arm/mm/cache-fa.S | 6 +- arch/arm/mm/cache-l2x0.c | 135 +- arch/arm/mm/cache-v3.S | 29 +- arch/arm/mm/cache-v4.S | 29 +- arch/arm/mm/cache-v4wb.S | 6 +- arch/arm/mm/cache-v4wt.S | 15 +- arch/arm/mm/cache-v6.S | 6 +- arch/arm/mm/cache-v7.S | 6 +- arch/arm/mm/dma-mapping.c | 7 +- arch/arm/mm/emulate_domain_manager-v7.c | 345 + arch/arm/mm/fault.c | 63 +- arch/arm/mm/init.c | 90 + arch/arm/mm/ioremap.c | 8 +- arch/arm/mm/mm.h | 3 + arch/arm/mm/mmu.c | 44 +- arch/arm/mm/proc-arm1026.S | 6 +- arch/arm/mm/proc-v7.S | 49 +- arch/arm/mm/tlb-v7.S | 12 + arch/arm/mm/vcm.c | 1830 +++ arch/arm/mm/vcm_alloc.c | 557 + arch/arm/mm/vcm_mm.c | 253 + arch/arm/oprofile/common.c | 6 + arch/arm/perfmon/Makefile | 6 + arch/arm/perfmon/cp15_registers.h | 94 + arch/arm/perfmon/l2_cp15_registers.h | 88 + arch/arm/perfmon/mcrmrc.h | 86 + arch/arm/perfmon/per-axi.c | 759 ++ arch/arm/perfmon/per-axi.h | 76 + arch/arm/perfmon/per-process-perf.c | 1251 +++ arch/arm/perfmon/per.c | 59 + arch/arm/perfmon/perf-function-hooks.c | 81 + arch/arm/perfmon/perf-smp.c | 751 ++ arch/arm/perfmon/perf-v7.c | 1009 ++ arch/arm/perfmon/perf.h | 86 + arch/arm/tools/mach-types | 11 +- arch/arm/vfp/vfpmodule.c | 58 +- arch/x86/kernel/cpu/amd.c | 2 +- arch/x86/xen/mmu.c | 4 +- chromeos/changelog | 13 + chromeos/config/armel/config.common.armel | 209 + .../config/armel/config.flavour.chromeos-arm | 482 + .../armel/config.flavour.chromeos-tegra2 | 372 + .../armel/config.flavour.chromeos-voguev210 | 450 + chromeos/config/config.common.chromeos | 532 + chromeos/config/i386/config.common.i386 | 2195 ++++ .../i386/config.flavour.chromeos-intel-menlow | 9 + .../config/i386/config.flavour.chromium-i386 | 4 + chromeos/config/x86_64/config.common.x86_64 | 2160 ++++ .../config.flavour.chromeos-intel-pineview | 3 + chromeos/scripts/allconfigs | 28 + chromeos/scripts/compat_wireless_config | 120 + chromeos/scripts/kernelconfig | 123 + chromeos/scripts/prepareconfig | 21 + chromeos/scripts/splitconfig | 49 + drivers/Kconfig | 2 + drivers/Makefile | 2 + drivers/base/memory.c | 172 + drivers/bluetooth/Kconfig | 37 + drivers/bluetooth/Makefile | 5 + drivers/bluetooth/ath3k.c | 6 - drivers/bluetooth/bluecard_cs.c | 2 +- drivers/bluetooth/bluesleep.c | 757 ++ drivers/bluetooth/bluetooth-power.c | 138 + drivers/bluetooth/bt3c_cs.c | 2 +- drivers/bluetooth/btmrvl_debugfs.c | 12 - drivers/bluetooth/btuart_cs.c | 2 +- drivers/bluetooth/btusb.c | 17 +- drivers/bluetooth/dtl1_cs.c | 2 +- drivers/bluetooth/hci_ibs.c | 820 ++ drivers/bluetooth/hci_ldisc.c | 25 +- drivers/bluetooth/hci_ll.c | 2 +- drivers/bluetooth/hci_smd.c | 305 + drivers/bluetooth/hci_uart.h | 15 +- drivers/char/Kconfig | 43 + drivers/char/Makefile | 4 +- drivers/char/csdio.c | 1074 ++ drivers/char/dcc_tty.c | 3 +- drivers/char/diag/Kconfig | 31 + drivers/char/diag/Makefile | 3 + drivers/char/diag/diagchar.h | 222 + drivers/char/diag/diagchar_core.c | 982 ++ drivers/char/diag/diagchar_hdlc.c | 223 + drivers/char/diag/diagchar_hdlc.h | 60 + drivers/char/diag/diagfwd.c | 1384 +++ drivers/char/diag/diagfwd.h | 38 + drivers/char/diag/diagfwd_cntl.c | 226 + drivers/char/diag/diagfwd_cntl.h | 38 + drivers/char/diag/diagfwd_sdio.c | 261 + drivers/char/diag/diagfwd_sdio.h | 27 + drivers/char/diag/diagmem.c | 145 + drivers/char/diag/diagmem.h | 22 + drivers/char/hw_random/Kconfig | 13 + drivers/char/hw_random/Makefile | 1 + drivers/char/hw_random/msm_rng.c | 232 + drivers/char/msm_rotator.c | 1523 +++ drivers/char/msm_smd_pkt.c | 5 - drivers/char/tpm/Kconfig | 14 + drivers/char/tpm/Makefile | 2 + drivers/char/tpm/tpm_st_i2c.c | 361 + drivers/char/tpm/tpmd_dev/Makefile | 4 + drivers/char/tpm/tpmd_dev/config.h | 32 + drivers/char/tpm/tpmd_dev/tpmd_dev.c | 272 + drivers/char/tty_io.c | 3154 ++++++ drivers/cpufreq/cpufreq.c | 41 +- drivers/cpufreq/cpufreq_ondemand.c | 126 + drivers/cpufreq/cpufreq_stats.c | 1 + drivers/crypto/Kconfig | 42 + drivers/crypto/Makefile | 1 + drivers/crypto/msm/Makefile | 8 + drivers/crypto/msm/inc/qce.h | 160 + drivers/crypto/msm/inc/qce_ota.h | 31 + drivers/crypto/msm/inc/qcedev.h | 267 + drivers/crypto/msm/inc/qcryptohw_30.h | 308 + drivers/crypto/msm/inc/qcryptohw_40.h | 316 + drivers/crypto/msm/ota_crypto.c | 731 ++ drivers/crypto/msm/qce.c | 2607 +++++ drivers/crypto/msm/qce40.c | 2038 ++++ drivers/crypto/msm/qcedev.c | 2095 ++++ drivers/crypto/msm/qcrypto.c | 3274 ++++++ drivers/gpio/Kconfig | 34 + drivers/gpio/Makefile | 4 + drivers/gpio/gpio-pm8xxx-rpc.c | 241 + drivers/gpio/pm8xxx-gpio.c | 458 + drivers/gpio/pm8xxx-mpp.c | 334 + drivers/gpio/pmic8901-mpp.c | 231 + drivers/gpio/sx150x.c | 19 +- drivers/gpu/Makefile | 1 + drivers/gpu/msm/Kconfig | 105 + drivers/gpu/msm/Makefile | 31 + drivers/gpu/msm/a200_reg.h | 408 + drivers/gpu/msm/a220_reg.h | 24 + drivers/gpu/msm/adreno.c | 1329 +++ drivers/gpu/msm/adreno.h | 116 + drivers/gpu/msm/adreno_debugfs.c | 451 + drivers/gpu/msm/adreno_debugfs.h | 40 + drivers/gpu/msm/adreno_drawctxt.c | 1645 +++ drivers/gpu/msm/adreno_drawctxt.h | 97 + drivers/gpu/msm/adreno_pm4types.h | 187 + drivers/gpu/msm/adreno_postmortem.c | 846 ++ drivers/gpu/msm/adreno_postmortem.h | 21 + drivers/gpu/msm/adreno_ringbuffer.c | 929 ++ drivers/gpu/msm/adreno_ringbuffer.h | 156 + drivers/gpu/msm/kgsl.c | 2119 ++++ drivers/gpu/msm/kgsl.h | 220 + drivers/gpu/msm/kgsl_cffdump.c | 711 ++ drivers/gpu/msm/kgsl_cffdump.h | 57 + drivers/gpu/msm/kgsl_debugfs.c | 82 + drivers/gpu/msm/kgsl_debugfs.h | 37 + drivers/gpu/msm/kgsl_device.h | 289 + drivers/gpu/msm/kgsl_drm.c | 1690 +++ drivers/gpu/msm/kgsl_log.h | 102 + drivers/gpu/msm/kgsl_mmu.c | 1151 ++ drivers/gpu/msm/kgsl_mmu.h | 273 + drivers/gpu/msm/kgsl_pwrctrl.c | 643 ++ drivers/gpu/msm/kgsl_pwrctrl.h | 76 + drivers/gpu/msm/kgsl_pwrscale.c | 327 + drivers/gpu/msm/kgsl_pwrscale.h | 75 + drivers/gpu/msm/kgsl_pwrscale_trustzone.c | 197 + drivers/gpu/msm/kgsl_sharedmem.c | 639 ++ drivers/gpu/msm/kgsl_sharedmem.h | 114 + drivers/gpu/msm/z180.c | 938 ++ drivers/gpu/msm/z180.h | 35 + drivers/gpu/msm/z180_reg.h | 53 + drivers/hwmon/Kconfig | 21 + drivers/hwmon/Makefile | 2 + drivers/hwmon/m_adcproc.c | 469 + drivers/hwmon/msm_adc.c | 1538 +++ drivers/hwmon/wpce775x.c | 167 + drivers/i2c/busses/Kconfig | 26 + drivers/i2c/busses/Makefile | 3 + drivers/i2c/busses/i2c-msm.c | 797 ++ drivers/i2c/busses/i2c-qup.c | 1305 +++ drivers/i2c/busses/i2c-ssbi.c | 516 + drivers/input/evdev.c | 1 + drivers/input/joystick/Kconfig | 11 + drivers/input/joystick/Makefile | 2 +- .../input/joystick/tdisc_vtd518_shinetsu.c | 528 + drivers/input/keyboard/Kconfig | 66 + drivers/input/keyboard/Makefile | 3 + drivers/input/keyboard/matrix_keypad.c | 7 +- drivers/input/keyboard/pmic8058-keypad.c | 948 ++ drivers/input/keyboard/pmic8xxx-keypad.c | 16 +- drivers/input/keyboard/qci_kbd.c | 721 ++ drivers/input/misc/Kconfig | 51 + drivers/input/misc/Makefile | 5 + drivers/input/misc/bma150.c | 791 ++ drivers/input/misc/gpio_matrix.c | 9 +- drivers/input/misc/isa1200-ff-memless.c | 449 + drivers/input/misc/pmic8058-othc.c | 1199 ++ drivers/input/misc/pmic8058-pwrkey.c | 375 + drivers/input/misc/pmic8058-vib-memless.c | 282 + drivers/input/misc/pmic8xxx-pwrkey.c | 8 +- drivers/input/mouse/Kconfig | 9 + drivers/input/mouse/qci_touchpad.c | 309 + drivers/input/touchscreen/Kconfig | 73 + drivers/input/touchscreen/Makefile | 7 + drivers/input/touchscreen/atmel_maxtouch.c | 2338 ++++ drivers/input/touchscreen/cy8c_tmg_ts.c | 467 + drivers/input/touchscreen/cy8c_ts.c | 783 ++ drivers/input/touchscreen/cyttsp-i2c.c | 3040 +++++ drivers/input/touchscreen/cyttsp_fw.h | 4307 +++++++ drivers/input/touchscreen/msm_touch.c | 317 + drivers/input/touchscreen/msm_ts.c | 513 + drivers/input/touchscreen/tsc2007.c | 116 +- drivers/leds/Kconfig | 55 + drivers/leds/Makefile | 6 + drivers/leds/led-class.c | 63 +- drivers/leds/leds-cpld.c | 405 + drivers/leds/leds-msm-pdm.c | 233 + drivers/leds/leds-msm-pmic.c | 105 + drivers/leds/leds-pm8xxx.c | 339 + drivers/leds/leds-pmic8058.c | 434 + drivers/leds/leds-qci-backlight.c | 71 + drivers/leds/leds.h | 23 +- drivers/media/radio/Kconfig | 34 + drivers/media/radio/Makefile | 3 + drivers/media/radio/radio-iris-transport.c | 165 + drivers/media/radio/radio-iris.c | 2220 ++++ drivers/media/radio/radio-tavarua.c | 3755 +++++++ drivers/media/video/Kconfig | 25 + drivers/media/video/Makefile | 5 +- drivers/media/video/msm/Kconfig | 194 + drivers/media/video/msm/Makefile | 46 + drivers/media/video/msm/flash.c | 563 + drivers/media/video/msm/imx072.c | 1164 ++ drivers/media/video/msm/imx072.h | 79 + drivers/media/video/msm/imx072_reg.c | 153 + drivers/media/video/msm/imx074.c | 1414 +++ drivers/media/video/msm/imx074.h | 118 + drivers/media/video/msm/imx074_reg.c | 111 + drivers/media/video/msm/imx074_v4l2.c | 1476 +++ drivers/media/video/msm/msm.c | 2080 ++++ drivers/media/video/msm/msm.h | 372 + drivers/media/video/msm/msm_axi_qos.c | 47 + drivers/media/video/msm/msm_camera.c | 4049 +++++++ drivers/media/video/msm/msm_gemini_common.h | 39 + drivers/media/video/msm/msm_gemini_core.c | 249 + drivers/media/video/msm/msm_gemini_core.h | 35 + drivers/media/video/msm/msm_gemini_dev.c | 208 + drivers/media/video/msm/msm_gemini_hw.c | 525 + drivers/media/video/msm/msm_gemini_hw.h | 101 + drivers/media/video/msm/msm_gemini_hw_reg.h | 176 + drivers/media/video/msm/msm_gemini_platform.c | 154 + drivers/media/video/msm/msm_gemini_platform.h | 34 + drivers/media/video/msm/msm_gemini_sync.c | 839 ++ drivers/media/video/msm/msm_gemini_sync.h | 77 + drivers/media/video/msm/msm_io7x.c | 318 + drivers/media/video/msm/msm_io8x.c | 331 + drivers/media/video/msm/msm_io_7x27a.c | 612 + drivers/media/video/msm/msm_io_8960.c | 1359 +++ drivers/media/video/msm/msm_io_8x60.c | 900 ++ drivers/media/video/msm/msm_io_vfe31.c | 924 ++ drivers/media/video/msm/msm_isp.c | 814 ++ drivers/media/video/msm/msm_ispif.c | 397 + drivers/media/video/msm/msm_ispif.h | 49 + drivers/media/video/msm/msm_mctl.c | 838 ++ drivers/media/video/msm/msm_mem.c | 403 + drivers/media/video/msm/msm_sensor.c | 814 ++ drivers/media/video/msm/msm_sensor.h | 246 + drivers/media/video/msm/msm_vfe31.c | 3729 +++++++ drivers/media/video/msm/msm_vfe31.h | 1113 ++ drivers/media/video/msm/msm_vfe32.c | 3379 ++++++ drivers/media/video/msm/msm_vfe32.h | 1104 ++ drivers/media/video/msm/msm_vfe7x.c | 783 ++ drivers/media/video/msm/msm_vfe7x.h | 265 + drivers/media/video/msm/msm_vfe7x27a.c | 742 ++ drivers/media/video/msm/msm_vfe7x27a.h | 300 + drivers/media/video/msm/msm_vfe8x.c | 842 ++ drivers/media/video/msm/msm_vfe8x.h | 909 ++ drivers/media/video/msm/msm_vfe8x_proc.c | 3888 +++++++ drivers/media/video/msm/msm_vfe8x_proc.h | 1563 +++ drivers/media/video/msm/msm_vpe1.c | 1446 +++ drivers/media/video/msm/msm_vpe1.h | 253 + drivers/media/video/msm/mt9d112.c | 845 ++ drivers/media/video/msm/mt9d112.h | 47 + drivers/media/video/msm/mt9d112_reg.c | 319 + drivers/media/video/msm/mt9d113.c | 664 ++ drivers/media/video/msm/mt9d113.h | 66 + drivers/media/video/msm/mt9d113_reg.c | 455 + drivers/media/video/msm/mt9e013.c | 1140 ++ drivers/media/video/msm/mt9e013.h | 174 + drivers/media/video/msm/mt9e013_reg.c | 234 + drivers/media/video/msm/mt9p012.h | 59 + drivers/media/video/msm/mt9p012_bam.c | 1426 +++ drivers/media/video/msm/mt9p012_fox.c | 1345 +++ drivers/media/video/msm/mt9p012_km.c | 1295 +++ drivers/media/video/msm/mt9p012_km.h | 61 + drivers/media/video/msm/mt9p012_km_reg.c | 375 + drivers/media/video/msm/mt9p012_reg.c | 263 + drivers/media/video/msm/mt9t013.c | 1503 +++ drivers/media/video/msm/mt9t013.h | 59 + drivers/media/video/msm/mt9t013_reg.c | 275 + drivers/media/video/msm/ov2720.c | 598 + drivers/media/video/msm/ov2720.h | 16 + drivers/media/video/msm/ov2720_reg.c | 123 + drivers/media/video/msm/ov7692.c | 595 + drivers/media/video/msm/ov7692.h | 50 + drivers/media/video/msm/ov9726.c | 792 ++ drivers/media/video/msm/ov9726.h | 39 + drivers/media/video/msm/ov9726_reg.c | 101 + drivers/media/video/msm/qs_s5k4e1.c | 1663 +++ drivers/media/video/msm/qs_s5k4e1.h | 89 + drivers/media/video/msm/qs_s5k4e1_reg.c | 799 ++ drivers/media/video/msm/s5k3e2fx.c | 1386 +++ drivers/media/video/msm/s5k3e2fx.h | 18 + drivers/media/video/msm/s5k4e1.c | 1086 ++ drivers/media/video/msm/s5k4e1.h | 94 + drivers/media/video/msm/s5k4e1_reg.c | 169 + drivers/media/video/msm/sn12m0pz.c | 1850 ++++ drivers/media/video/msm/sn12m0pz.h | 138 + drivers/media/video/msm/sn12m0pz_reg.c | 213 + drivers/media/video/msm/vb6801.c | 1616 +++ drivers/media/video/msm/vb6801.h | 66 + drivers/media/video/msm/vx6953.c | 3666 ++++++ drivers/media/video/msm/vx6953.h | 136 + drivers/media/video/msm/vx6953_reg.c | 135 + drivers/media/video/msm/vx6953_reg_v4l2.c | 135 + drivers/media/video/msm/vx6953_v4l2.c | 4149 +++++++ drivers/media/video/msm/vx6953_v4l2.h | 136 + drivers/media/video/videobuf-core.c | 4 +- drivers/media/video/videobuf-msm-mem.c | 394 + drivers/mfd/Kconfig | 117 + drivers/mfd/Makefile | 25 + drivers/mfd/marimba-codec.c | 963 ++ drivers/mfd/marimba-core.c | 699 ++ drivers/mfd/marimba-tsadc.c | 696 ++ drivers/mfd/msm-adie-codec.c | 196 + drivers/mfd/msmproc_adc.c | 468 + drivers/mfd/pm8921-adc.c | 1022 ++ drivers/mfd/pm8921-core.c | 491 +- drivers/mfd/pm8xxx-batt-alarm.c | 805 ++ drivers/mfd/pm8xxx-debug.c | 214 + drivers/mfd/pm8xxx-irq.c | 10 +- drivers/mfd/pm8xxx-misc.c | 310 + drivers/mfd/pm8xxx-pwm.c | 1082 ++ drivers/mfd/pmic8058.c | 1308 +++ drivers/mfd/pmic8901.c | 953 ++ drivers/mfd/timpani-codec.c | 3645 ++++++ drivers/mfd/tps65023.c | 122 + drivers/mfd/wcd9310-core.c | 734 ++ drivers/mfd/wcd9310-irq.c | 197 + drivers/misc/Kconfig | 118 + drivers/misc/Makefile | 14 + drivers/misc/isa1200.c | 440 + drivers/misc/msm_migrate_pages.c | 80 + drivers/misc/pmem.c | 2264 +++- drivers/misc/pmic8058-batt-alarm.c | 753 ++ drivers/misc/pmic8058-misc.c | 335 + drivers/misc/pmic8058-nfc.c | 322 + drivers/misc/pmic8058-pwm.c | 926 ++ drivers/misc/pmic8058-upl.c | 363 + drivers/misc/pmic8058-vibrator.c | 307 + drivers/misc/pmic8058-xoadc.c | 770 ++ drivers/misc/qfp_fuse.c | 410 + drivers/misc/tsif.c | 1564 +++ drivers/misc/tsif_chrdev.c | 225 + drivers/misc/tzcom.c | 910 ++ drivers/misc/tzcomi.h | 112 + drivers/mmc/Kconfig | 8 + drivers/mmc/card/block.c | 2 +- drivers/mmc/card/queue.c | 31 +- drivers/mmc/core/core.c | 39 +- drivers/mmc/core/host.c | 79 +- drivers/mmc/core/mmc.c | 6 + drivers/mmc/core/mmc_ops.c | 4 + drivers/mmc/core/sd.c | 3 + drivers/mmc/core/sdio.c | 34 +- drivers/mmc/core/sdio_cis.c | 12 +- drivers/mmc/host/Kconfig | 142 + drivers/mmc/host/Makefile | 3 + drivers/mmc/host/msm_sdcc.c | 4060 +++++-- drivers/mmc/host/msm_sdcc.h | 179 +- drivers/mmc/host/msm_sdcc_dml.c | 303 + drivers/mmc/host/msm_sdcc_dml.h | 105 + drivers/mmc/host/sdhci.c | 16 + drivers/mtd/devices/Kconfig | 10 + drivers/mtd/devices/Makefile | 1 + drivers/mtd/devices/msm_nand.c | 7143 ++++++++++++ drivers/mtd/devices/msm_nand.h | 195 + drivers/mtd/mtd_blkdevs.c | 7 + drivers/mtd/tests/Makefile | 1 + drivers/mtd/tests/mtd_erasepart.c | 174 + drivers/net/Kconfig | 41 + drivers/net/Makefile | 6 + drivers/net/ks8851.c | 33 +- drivers/net/msm_rmnet.c | 849 ++ drivers/net/msm_rmnet_bam.c | 653 ++ drivers/net/msm_rmnet_sdio.c | 704 ++ drivers/net/qfec.c | 2521 +++++ drivers/net/qfec.h | 793 ++ drivers/net/smc91x.h | 14 + drivers/net/smsc911x.c | 26 +- drivers/net/smsc911x.h | 2 +- drivers/net/wireless/Kconfig | 20 + drivers/net/wireless/Makefile | 3 + drivers/net/wireless/ath/ath9k/hw.c | 11 + drivers/net/wireless/bcm4329/bcmspibrcm.c | 1726 +++ drivers/net/wireless/libra/Makefile | 14 + drivers/net/wireless/libra/libra_sdioif.c | 481 + .../net/wireless/libra/qcomwlan7x27a_pwrif.c | 172 + drivers/net/wireless/libra/qcomwlan_pwrif.c | 256 + drivers/net/wireless/wcnss/Makefile | 6 + drivers/net/wireless/wcnss/qcomwlan_secif.c | 62 + drivers/net/wireless/wcnss/wcnss_riva.c | 314 + drivers/net/wireless/wcnss/wcnss_riva.h | 31 + drivers/net/wireless/wcnss/wcnss_wlan.c | 339 + drivers/platform/Kconfig | 3 + drivers/platform/Makefile | 1 + drivers/platform/msm/Kconfig | 36 + drivers/platform/msm/Makefile | 5 + drivers/platform/msm/sps/Makefile | 2 + drivers/platform/msm/sps/bam.c | 755 ++ drivers/platform/msm/sps/bam.h | 394 + drivers/platform/msm/sps/sps.c | 1502 +++ drivers/platform/msm/sps/sps_bam.c | 1910 ++++ drivers/platform/msm/sps/sps_bam.h | 547 + drivers/platform/msm/sps/sps_core.h | 107 + drivers/platform/msm/sps/sps_dma.c | 896 ++ drivers/platform/msm/sps/sps_map.c | 137 + drivers/platform/msm/sps/sps_map.h | 46 + drivers/platform/msm/sps/sps_mem.c | 156 + drivers/platform/msm/sps/sps_rm.c | 807 ++ drivers/platform/msm/sps/spsi.h | 312 + drivers/platform/msm/ssbi.c | 396 + drivers/power/Kconfig | 102 + drivers/power/Makefile | 11 + drivers/power/bq27520_fuelgauger.c | 960 ++ drivers/power/bq27541_fuelgauger.c | 623 ++ drivers/power/isl9519q.c | 517 + drivers/power/msm_battery.c | 1592 +++ drivers/power/msm_charger.c | 1250 +++ drivers/power/pm8058_usb_fix.c | 357 + drivers/power/pm8921-bms.c | 1178 ++ drivers/power/pm8921-charger.c | 1560 +++ drivers/power/pmic8058-charger.c | 1954 ++++ drivers/power/qci_battery.c | 662 ++ drivers/power/qci_battery.h | 121 + drivers/power/smb137b.c | 857 ++ drivers/regulator/Kconfig | 42 + drivers/regulator/Makefile | 5 + drivers/regulator/core.c | 514 +- drivers/regulator/gpio-regulator.c | 221 + drivers/regulator/pm8058-xo.c | 215 + drivers/regulator/pm8921-regulator.c | 3274 ++++++ drivers/regulator/pmic8058-regulator.c | 1769 +++ drivers/regulator/pmic8901-regulator.c | 1097 ++ drivers/rtc/Kconfig | 84 + drivers/rtc/Makefile | 4 + drivers/rtc/alarm.c | 24 + drivers/rtc/hctosys.c | 2 +- drivers/rtc/rtc-msm.c | 819 ++ drivers/rtc/rtc-msm7x00a.c | 280 + drivers/rtc/rtc-pm8058.c | 563 + drivers/rtc/rtc-pm8xxx.c | 569 + drivers/slimbus/Kconfig | 19 + drivers/slimbus/Makefile | 5 + drivers/slimbus/slim-msm-ctrl.c | 1769 +++ drivers/slimbus/slimbus.c | 2625 +++++ drivers/spi/Kconfig | 25 + drivers/spi/Makefile | 2 + drivers/spi/spi_qsd.c | 2539 +++++ drivers/spi/spidev.c | 91 +- drivers/staging/Kconfig | 2 - .../os/linux/include/athendpack_linux.h | 0 .../os/linux/include/athstartpack_linux.h | 0 drivers/staging/dream/Kconfig | 13 + drivers/staging/dream/Makefile | 5 + drivers/staging/gobi/Kconfig | 5 + drivers/staging/gobi/QCUSBNet2k/Makefile | 2 + drivers/staging/gobi/QCUSBNet2k/QCUSBNet.c | 1227 ++ drivers/staging/gobi/QCUSBNet2k/QMI.c | 954 ++ drivers/staging/gobi/QCUSBNet2k/QMI.h | 251 + drivers/staging/gobi/QCUSBNet2k/QMIDevice.c | 3129 ++++++ drivers/staging/gobi/QCUSBNet2k/QMIDevice.h | 297 + drivers/staging/gobi/QCUSBNet2k/Structs.h | 318 + drivers/staging/msm/ebi2_l2f.c | 5 - drivers/staging/msm/ebi2_lcd.c | 5 - drivers/staging/msm/ebi2_tmd20.c | 5 - drivers/staging/msm/hdmi_sii9022.c | 5 - drivers/staging/msm/lcdc.c | 5 - drivers/staging/msm/lcdc_external.c | 5 - drivers/staging/msm/lcdc_gordon.c | 5 - drivers/staging/msm/lcdc_panel.c | 5 - drivers/staging/msm/lcdc_prism.c | 5 - drivers/staging/msm/lcdc_sharp_wvga_pt.c | 5 - drivers/staging/msm/lcdc_st15.c | 5 - drivers/staging/msm/lcdc_st1_wxga.c | 49 + drivers/staging/msm/lcdc_toshiba_wvga_pt.c | 5 - drivers/staging/msm/lcdc_wxga.c | 51 + drivers/staging/msm/mddi.c | 5 - drivers/staging/msm/mddi_ext.c | 5 - drivers/staging/msm/mddi_ext_lcd.c | 5 - drivers/staging/msm/mddi_prism.c | 5 - drivers/staging/msm/mddi_sharp.c | 5 - drivers/staging/msm/mddi_toshiba.c | 5 - drivers/staging/msm/mddi_toshiba_vga.c | 5 - drivers/staging/msm/mddi_toshiba_wvga.c | 58 + drivers/staging/msm/mddi_toshiba_wvga_pt.c | 5 - drivers/staging/msm/mddihost.c | 5 - drivers/staging/msm/mddihost_e.c | 5 - drivers/staging/msm/mddihosti.c | 5 - drivers/staging/msm/mdp.c | 5 - drivers/staging/msm/mdp4_debugfs.c | 5 - drivers/staging/msm/mdp4_overlay.c | 5 - drivers/staging/msm/mdp4_overlay_lcdc.c | 5 - drivers/staging/msm/mdp4_overlay_mddi.c | 5 - drivers/staging/msm/mdp4_util.c | 5 - drivers/staging/msm/mdp_cursor.c | 5 - drivers/staging/msm/mdp_dma.c | 5 - drivers/staging/msm/mdp_dma_lcdc.c | 5 - drivers/staging/msm/mdp_dma_s.c | 5 - drivers/staging/msm/mdp_dma_tv.c | 5 - drivers/staging/msm/mdp_hw_init.c | 5 - drivers/staging/msm/mdp_ppp_dq.c | 5 - drivers/staging/msm/mdp_ppp_v20.c | 5 - drivers/staging/msm/mdp_ppp_v31.c | 5 - drivers/staging/msm/mdp_vsync.c | 5 - drivers/staging/msm/memory_ll.h | 4 +- drivers/staging/msm/msm_fb_bl.c | 5 - drivers/staging/msm/msm_fb_panel.c | 5 - drivers/staging/msm/staging-devices.c | 56 +- drivers/staging/msm/tv_ntsc.c | 5 - drivers/staging/msm/tv_pal.c | 5 - drivers/staging/msm/tvenc.c | 5 - drivers/thermal/Kconfig | 59 + drivers/thermal/Makefile | 5 + drivers/thermal/msm_popmem-tm.c | 284 + drivers/thermal/msm_tsens.c | 618 ++ drivers/thermal/pm8xxx-tm.c | 648 ++ drivers/thermal/pmic8058-tm.c | 509 + drivers/thermal/pmic8901-tm.c | 594 + drivers/thermal/thermal_sys.c | 153 +- drivers/tty/hvc/hvc_dcc.c | 5 - drivers/tty/serial/Kconfig | 38 + drivers/tty/serial/Makefile | 2 + drivers/tty/serial/msm_serial.c | 713 +- drivers/tty/serial/msm_serial.h | 59 +- drivers/tty/serial/msm_serial_debugger.c | 421 + drivers/tty/serial/msm_serial_hs.c | 1465 +-- drivers/tty/serial/msm_serial_hs_hwreg.h | 179 + drivers/tty/serial/msm_serial_hs_lite.c | 1216 ++ drivers/usb/Kconfig | 2 + drivers/usb/Makefile | 3 +- drivers/usb/core/driver.c | 38 + drivers/usb/core/hcd.c | 8 + drivers/usb/core/hub.c | 441 +- drivers/usb/core/message.c | 7 +- drivers/usb/core/notify.c | 6 + drivers/usb/core/otg_whitelist.h | 25 +- drivers/usb/core/usb.h | 5 + drivers/usb/function/Kconfig | 163 + drivers/usb/function/Makefile | 13 + drivers/usb/function/adb.c | 624 ++ drivers/usb/function/diag.c | 567 + drivers/usb/function/ether.c | 327 + drivers/usb/function/ether_cdc_ecm.c | 1337 +++ drivers/usb/function/loopback.c | 128 + drivers/usb/function/mass_storage.c | 3009 +++++ drivers/usb/function/msm_hsusb.c | 3948 +++++++ drivers/usb/function/msm_hsusb_hw.h | 163 + drivers/usb/function/msm_otg.c | 368 + drivers/usb/function/null.c | 118 + drivers/usb/function/rmnet.c | 1086 ++ drivers/usb/function/serial.c | 2252 ++++ drivers/usb/function/ums.c | 469 + drivers/usb/function/usb_function.h | 187 + drivers/usb/function/zero.c | 120 + drivers/usb/gadget/Kconfig | 98 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/android.c | 91 +- drivers/usb/gadget/ci13xxx_msm.c | 10 +- drivers/usb/gadget/ci13xxx_udc.c | 135 +- drivers/usb/gadget/ci13xxx_udc.h | 5 + drivers/usb/gadget/f_acm.c | 250 +- drivers/usb/gadget/f_diag.c | 752 ++ drivers/usb/gadget/f_diag.h | 24 + drivers/usb/gadget/f_mass_storage.c | 139 +- drivers/usb/gadget/f_rmnet.c | 819 ++ drivers/usb/gadget/f_rmnet.h | 19 + drivers/usb/gadget/f_rmnet_sdio.c | 1314 +++ drivers/usb/gadget/f_rmnet_smd.c | 1333 +++ drivers/usb/gadget/f_rmnet_smd_sdio.c | 1995 ++++ drivers/usb/gadget/f_serial.c | 700 +- drivers/usb/gadget/gadget_chips.h | 8 + drivers/usb/gadget/msm72k_udc.c | 2653 +++++ drivers/usb/gadget/qcom_maemo.c | 304 + drivers/usb/gadget/storage_common.c | 9 +- drivers/usb/gadget/u_bam.c | 812 ++ drivers/usb/gadget/u_rmnet.h | 61 + drivers/usb/gadget/u_rmnet_ctrl_smd.c | 652 ++ drivers/usb/gadget/u_sdio.c | 1097 ++ drivers/usb/gadget/u_serial.c | 359 +- drivers/usb/gadget/u_serial.h | 20 + drivers/usb/gadget/u_smd.c | 887 ++ drivers/usb/host/Kconfig | 38 + drivers/usb/host/Makefile | 1 + drivers/usb/host/ehci-dbg.c | 4 +- drivers/usb/host/ehci-hcd.c | 11 +- drivers/usb/host/ehci-hub.c | 178 +- drivers/usb/host/ehci-msm.c | 7 + drivers/usb/host/ehci-msm72k.c | 823 ++ drivers/usb/host/ehci-q.c | 103 + drivers/usb/host/ehci.h | 2 + drivers/usb/host/pehci/Makefile | 6 + drivers/usb/host/pehci/hal/Makefile | 6 + drivers/usb/host/pehci/hal/hal_intf.h | 313 + drivers/usb/host/pehci/hal/hal_msm.c | 748 ++ drivers/usb/host/pehci/hal/hal_msm.h | 85 + drivers/usb/host/pehci/hal/isp1763.h | 227 + drivers/usb/host/pehci/host/Makefile | 6 + drivers/usb/host/pehci/host/itdptd.c | 2156 ++++ drivers/usb/host/pehci/host/mem.c | 355 + drivers/usb/host/pehci/host/otg.c | 189 + drivers/usb/host/pehci/host/pehci.c | 6567 +++++++++++ drivers/usb/host/pehci/host/pehci.h | 752 ++ drivers/usb/host/pehci/host/qtdptd.c | 1315 +++ drivers/usb/misc/Kconfig | 15 + drivers/usb/misc/Makefile | 1 + drivers/usb/misc/ehset.c | 147 + drivers/usb/musb/Kconfig | 6 +- drivers/usb/otg/Kconfig | 37 + drivers/usb/otg/Makefile | 1 + drivers/usb/otg/msm72k_otg.c | 2957 +++++ drivers/usb/otg/msm_otg.c | 586 +- drivers/usb/otg/otg.c | 15 + drivers/usb/otg/otg_id.c | 2 +- drivers/video/Kconfig | 11 +- drivers/video/fbmem.c | 3 - drivers/video/msm/Kconfig | 661 ++ drivers/video/msm/Makefile | 159 +- drivers/video/msm/adv7520.c | 1005 ++ drivers/video/msm/ebi2_l2f.c | 566 + drivers/video/msm/ebi2_lcd.c | 268 + drivers/video/msm/ebi2_tmd20.c | 1120 ++ drivers/video/msm/external_common.c | 1229 ++ drivers/video/msm/external_common.h | 251 + drivers/video/msm/hdmi_msm.c | 3731 +++++++ drivers/video/msm/hdmi_msm.h | 84 + drivers/video/msm/hdmi_sii9022.c | 245 + drivers/video/msm/lcdc.c | 294 + drivers/video/msm/lcdc_auo_wvga.c | 413 + drivers/video/msm/lcdc_chimei_wxga.c | 233 + drivers/video/msm/lcdc_external.c | 51 + drivers/video/msm/lcdc_gordon.c | 443 + drivers/video/msm/lcdc_panel.c | 84 + drivers/video/msm/lcdc_prism.c | 61 + drivers/video/msm/lcdc_samsung_oled_pt.c | 590 + drivers/video/msm/lcdc_samsung_wsvga.c | 272 + drivers/video/msm/lcdc_sharp_wvga_pt.c | 414 + drivers/video/msm/lcdc_st15.c | 413 + drivers/video/msm/lcdc_toshiba_fwvga_pt.c | 471 + drivers/video/msm/lcdc_toshiba_wvga_pt.c | 519 + drivers/video/msm/lcdc_wxga.c | 53 + drivers/video/msm/logo.c | 97 + drivers/video/msm/mddi.c | 1168 +- drivers/video/msm/mddi_client_dummy.c | 1 - drivers/video/msm/mddi_client_nt35399.c | 1 - drivers/video/msm/mddi_client_toshiba.c | 61 +- drivers/video/msm/mddi_ext.c | 363 + drivers/video/msm/mddi_ext_lcd.c | 90 + drivers/video/msm/mddi_hw.h | 9 + drivers/video/msm/mddi_orise.c | 127 + drivers/video/msm/mddi_prism.c | 111 + drivers/video/msm/mddi_quickvx.c | 718 ++ drivers/video/msm/mddi_sharp.c | 900 ++ drivers/video/msm/mddi_toshiba.c | 1753 +++ drivers/video/msm/mddi_toshiba.h | 37 + drivers/video/msm/mddi_toshiba_vga.c | 133 + drivers/video/msm/mddi_toshiba_wvga.c | 60 + drivers/video/msm/mddi_toshiba_wvga_pt.c | 68 + drivers/video/msm/mddihost.c | 626 ++ drivers/video/msm/mddihost.h | 231 + drivers/video/msm/mddihost_e.c | 59 + drivers/video/msm/mddihosti.c | 2304 ++++ drivers/video/msm/mddihosti.h | 552 + drivers/video/msm/mdp.c | 1904 +++- drivers/video/msm/mdp.h | 727 ++ drivers/video/msm/mdp4.h | 565 + drivers/video/msm/mdp4_dtv.c | 329 + drivers/video/msm/mdp4_overlay.c | 2290 ++++ drivers/video/msm/mdp4_overlay_atv.c | 189 + drivers/video/msm/mdp4_overlay_dsi_cmd.c | 672 ++ drivers/video/msm/mdp4_overlay_dsi_video.c | 404 + drivers/video/msm/mdp4_overlay_dtv.c | 404 + drivers/video/msm/mdp4_overlay_lcdc.c | 395 + drivers/video/msm/mdp4_overlay_mddi.c | 612 + drivers/video/msm/mdp4_util.c | 2101 ++++ drivers/video/msm/mdp_csc_table.h | 163 +- drivers/video/msm/mdp_cursor.c | 264 + drivers/video/msm/mdp_debugfs.c | 1289 +++ drivers/video/msm/mdp_dma.c | 602 + drivers/video/msm/mdp_dma_dsi_video.c | 270 + drivers/video/msm/mdp_dma_lcdc.c | 368 + drivers/video/msm/mdp_dma_s.c | 166 + drivers/video/msm/mdp_dma_tv.c | 138 + drivers/video/msm/mdp_hw.h | 249 +- drivers/video/msm/mdp_hw40.c | 94 + drivers/video/msm/mdp_hw_init.c | 716 ++ drivers/video/msm/mdp_lcdc.c | 432 + drivers/video/msm/mdp_ppp.c | 2047 +++- drivers/video/msm/mdp_ppp.h | 82 + drivers/video/msm/mdp_ppp22.c | 1091 ++ drivers/video/msm/mdp_ppp31.c | 332 + drivers/video/msm/mdp_ppp_v20.c | 2483 +++++ drivers/video/msm/mdp_ppp_v31.c | 844 ++ drivers/video/msm/mdp_vsync.c | 488 + drivers/video/msm/mipi_dsi.c | 548 + drivers/video/msm/mipi_dsi.h | 285 + drivers/video/msm/mipi_dsi_host.c | 1248 +++ drivers/video/msm/mipi_novatek.c | 463 + drivers/video/msm/mipi_novatek.h | 22 + drivers/video/msm/mipi_novatek_cmd_qhd_pt.c | 99 + drivers/video/msm/mipi_novatek_video_qhd_pt.c | 98 + drivers/video/msm/mipi_renesas.c | 1231 ++ drivers/video/msm/mipi_renesas.h | 21 + drivers/video/msm/mipi_renesas_cmd_fwvga_pt.c | 162 + .../video/msm/mipi_renesas_video_fwvga_pt.c | 165 + drivers/video/msm/mipi_simulator.c | 162 + drivers/video/msm/mipi_simulator.h | 19 + drivers/video/msm/mipi_simulator_video.c | 88 + drivers/video/msm/mipi_toshiba.c | 292 + drivers/video/msm/mipi_toshiba.h | 21 + .../video/msm/mipi_toshiba_video_wsvga_pt.c | 106 + .../video/msm/mipi_toshiba_video_wvga_pt.c | 108 + drivers/video/msm/msm_dss_io_7x27a.c | 390 + drivers/video/msm/msm_dss_io_8960.c | 715 ++ drivers/video/msm/msm_dss_io_8x60.c | 613 + drivers/video/msm/msm_fb.c | 3391 +++++- drivers/video/msm/msm_fb.h | 171 + drivers/video/msm/msm_fb_bl.c | 75 + drivers/video/msm/msm_fb_def.h | 204 + drivers/video/msm/msm_fb_panel.c | 141 + drivers/video/msm/msm_fb_panel.h | 199 + drivers/video/msm/tvenc.c | 521 + drivers/video/msm/tvenc.h | 129 + drivers/video/msm/tvout_msm.c | 648 ++ drivers/video/msm/vidc/1080p/ddl/vcd_ddl.c | 634 ++ drivers/video/msm/vidc/1080p/ddl/vcd_ddl.h | 446 + .../video/msm/vidc/1080p/ddl/vcd_ddl_api.h | 110 + .../video/msm/vidc/1080p/ddl/vcd_ddl_core.h | 134 + .../video/msm/vidc/1080p/ddl/vcd_ddl_errors.c | 755 ++ .../video/msm/vidc/1080p/ddl/vcd_ddl_helper.c | 959 ++ .../1080p/ddl/vcd_ddl_interrupt_handler.c | 1638 +++ .../msm/vidc/1080p/ddl/vcd_ddl_metadata.c | 505 + .../msm/vidc/1080p/ddl/vcd_ddl_metadata.h | 66 + .../msm/vidc/1080p/ddl/vcd_ddl_properties.c | 1904 ++++ .../msm/vidc/1080p/ddl/vcd_ddl_shared_mem.c | 678 ++ .../msm/vidc/1080p/ddl/vcd_ddl_shared_mem.h | 157 + .../video/msm/vidc/1080p/ddl/vcd_ddl_utils.c | 310 + .../video/msm/vidc/1080p/ddl/vcd_ddl_utils.h | 73 + .../video/msm/vidc/1080p/ddl/vcd_ddl_vidc.c | 925 ++ drivers/video/msm/vidc/1080p/ddl/vidc.c | 1007 ++ drivers/video/msm/vidc/1080p/ddl/vidc.h | 544 + drivers/video/msm/vidc/1080p/ddl/vidc_hwio.h | 115 + .../video/msm/vidc/1080p/ddl/vidc_hwio_reg.h | 4544 ++++++++ .../video/msm/vidc/1080p/ddl/vidc_pix_cache.c | 349 + .../video/msm/vidc/1080p/ddl/vidc_pix_cache.h | 87 + .../1080p/resource_tracker/vcd_res_tracker.c | 508 + .../1080p/resource_tracker/vcd_res_tracker.h | 69 + .../resource_tracker/vcd_res_tracker_api.h | 37 + drivers/video/msm/vidc/720p/ddl/vcd_ddl.c | 629 ++ drivers/video/msm/vidc/720p/ddl/vcd_ddl.h | 282 + drivers/video/msm/vidc/720p/ddl/vcd_ddl_api.h | 53 + .../video/msm/vidc/720p/ddl/vcd_ddl_core.h | 99 + .../video/msm/vidc/720p/ddl/vcd_ddl_errors.c | 595 + .../msm/vidc/720p/ddl/vcd_ddl_firmware.c | 352 + .../msm/vidc/720p/ddl/vcd_ddl_firmware.h | 53 + drivers/video/msm/vidc/720p/ddl/vcd_ddl_hal.c | 944 ++ .../video/msm/vidc/720p/ddl/vcd_ddl_helper.c | 286 + .../vidc/720p/ddl/vcd_ddl_internal_property.h | 75 + .../vidc/720p/ddl/vcd_ddl_interrupt_handler.c | 1122 ++ .../msm/vidc/720p/ddl/vcd_ddl_metadata.c | 580 + .../msm/vidc/720p/ddl/vcd_ddl_metadata.h | 79 + .../msm/vidc/720p/ddl/vcd_ddl_properties.c | 1919 ++++ .../video/msm/vidc/720p/ddl/vcd_ddl_utils.c | 223 + .../video/msm/vidc/720p/ddl/vcd_ddl_utils.h | 60 + drivers/video/msm/vidc/720p/ddl/vidc.c | 801 ++ drivers/video/msm/vidc/720p/ddl/vidc.h | 2704 +++++ .../720p/resource_tracker/vcd_res_tracker.c | 722 ++ .../720p/resource_tracker/vcd_res_tracker.h | 56 + .../resource_tracker/vcd_res_tracker_api.h | 30 + drivers/video/msm/vidc/Kconfig | 39 + drivers/video/msm/vidc/Makefile | 62 + drivers/video/msm/vidc/common/dec/vdec.c | 1904 ++++ .../video/msm/vidc/common/dec/vdec_internal.h | 43 + drivers/video/msm/vidc/common/enc/venc.c | 1549 +++ .../video/msm/vidc/common/enc/venc_internal.c | 1784 +++ .../video/msm/vidc/common/enc/venc_internal.h | 151 + .../video/msm/vidc/common/init/vidc_init.c | 620 ++ .../video/msm/vidc/common/init/vidc_init.h | 76 + .../msm/vidc/common/init/vidc_init_internal.h | 41 + drivers/video/msm/vidc/common/vcd/vcd.h | 393 + drivers/video/msm/vidc/common/vcd/vcd_api.c | 881 ++ drivers/video/msm/vidc/common/vcd/vcd_api.h | 140 + .../video/msm/vidc/common/vcd/vcd_client_sm.c | 1827 +++ .../video/msm/vidc/common/vcd/vcd_client_sm.h | 110 + drivers/video/msm/vidc/common/vcd/vcd_core.h | 220 + .../video/msm/vidc/common/vcd/vcd_device_sm.c | 1203 ++ .../video/msm/vidc/common/vcd/vcd_device_sm.h | 96 + .../video/msm/vidc/common/vcd/vcd_power_sm.c | 351 + .../video/msm/vidc/common/vcd/vcd_power_sm.h | 43 + .../video/msm/vidc/common/vcd/vcd_property.h | 342 + .../video/msm/vidc/common/vcd/vcd_scheduler.c | 286 + .../video/msm/vidc/common/vcd/vcd_status.h | 60 + drivers/video/msm/vidc/common/vcd/vcd_sub.c | 3057 +++++ drivers/video/msm/vidc/common/vcd/vcd_util.c | 106 + drivers/video/msm/vidc/common/vcd/vcd_util.h | 52 + drivers/video/msm/vidc/common/vcd/vidc_type.h | 30 + fs/btrfs/ctree.h | 2 + fs/compat_ioctl.c | 1 + fs/fat/Makefile | 1 + fs/yaffs2/yaffs_vfs.c | 24 +- include/Kbuild | 1 + include/drm/Kbuild | 1 + include/drm/drm.h | 2 +- include/drm/kgsl_drm.h | 192 + include/linux/Kbuild | 22 + include/linux/adv7520.h | 24 + include/linux/android_alarm.h | 1 + include/linux/android_pmem.h | 148 +- include/linux/ashmem.h | 5 + include/linux/atmel_maxtouch.h | 317 + include/linux/bitmap.h | 24 +- include/linux/bma150.h | 31 + include/linux/completion.h | 1 + include/linux/cpufreq.h | 2 + include/linux/csdio.h | 37 + include/linux/cyttsp.h | 679 ++ include/linux/debugobjects.h | 6 + include/linux/delay.h | 5 + include/linux/diagchar.h | 589 + include/linux/fsm_dfe_hh.h | 85 + include/linux/fsm_rfic_ftr.h | 74 + include/linux/genalloc.h | 51 +- include/linux/gpio-pm8xxx-rpc.h | 35 + include/linux/gpio_event.h | 6 +- include/linux/i2c/bq27520.h | 26 + include/linux/i2c/isa1200.h | 46 + include/linux/i2c/isl9519.h | 38 + include/linux/i2c/smb137b.h | 33 + include/linux/i2c/sx150x.h | 5 - include/linux/i2c/tsc2007.h | 6 + include/linux/if_arp.h | 1 + include/linux/input.h | 2 +- include/linux/input/cy8c_ts.h | 65 + include/linux/input/kp_flip_switch.h | 25 + include/linux/input/matrix_keypad.h | 10 +- include/linux/input/msm_ts.h | 51 + include/linux/input/pmic8058-keypad.h | 48 + include/linux/input/qci_kbd.h | 29 + include/linux/input/tdisc_shinetsu.h | 42 + include/linux/interrupt.h | 3 +- include/linux/iommu.h | 3 + include/linux/ioport.h | 2 + include/linux/irq.h | 4 + include/linux/kexec.h | 1 + include/linux/leds-pm8xxx.h | 37 + include/linux/leds-pmic8058.h | 40 + include/linux/libra_sdioif.h | 77 + include/linux/m_adcproc.h | 30 + include/linux/memory_alloc.h | 53 + include/linux/memory_hotplug.h | 13 + include/linux/mfd/Kbuild | 2 + include/linux/mfd/marimba-codec.h | 52 + include/linux/mfd/marimba-tsadc.h | 29 + include/linux/mfd/marimba.h | 191 + include/linux/mfd/msm-adie-codec.h | 146 + include/linux/mfd/pm8921-adc.h | 582 + include/linux/mfd/pm8xxx/batt-alarm.h | 200 + include/linux/mfd/pm8xxx/core.h | 62 +- include/linux/mfd/pm8xxx/gpio.h | 133 + include/linux/mfd/pm8xxx/misc.h | 51 + include/linux/mfd/pm8xxx/mpp.h | 242 + include/linux/mfd/pm8xxx/pm8921-bms.h | 124 + include/linux/mfd/pm8xxx/pm8921-charger.h | 68 + include/linux/mfd/pm8xxx/pm8921.h | 99 + include/linux/mfd/pm8xxx/pwm.h | 88 + include/linux/mfd/pm8xxx/rtc.h | 25 + include/linux/mfd/pm8xxx/tm.h | 41 + include/linux/mfd/pmic8058.h | 238 + include/linux/mfd/pmic8901.h | 72 + include/linux/mfd/timpani-audio.h | 5016 +++++++++ include/linux/mfd/tps65023.h | 32 + include/linux/mfd/wcd9310/core.h | 115 + include/linux/mfd/wcd9310/pdata.h | 27 + include/linux/mfd/wcd9310/registers.h | 1013 ++ include/linux/mmc/host.h | 23 +- include/linux/mmc/mmc.h | 1 + include/linux/mmc/sdio.h | 1 + include/linux/mmzone.h | 9 + include/linux/mod_devicetable.h | 9 + include/linux/msm-charger.h | 136 + include/linux/msm_adc.h | 347 + include/linux/msm_adsp.h | 78 + include/linux/msm_audio.h | 353 + include/linux/msm_audio_aac.h | 71 + include/linux/msm_audio_acdb.h | 73 + include/linux/msm_audio_amrnb.h | 51 + include/linux/msm_audio_mvs.h | 111 + include/linux/msm_audio_qcp.h | 50 + include/linux/msm_audio_sbc.h | 49 + include/linux/msm_audio_voicememo.h | 79 + include/linux/msm_audio_wma.h | 33 + include/linux/msm_audio_wmapro.h | 22 + include/linux/msm_charm.h | 19 + include/linux/msm_dsps.h | 28 + include/linux/msm_ipc.h | 72 + include/linux/msm_kgsl.h | 408 + include/linux/msm_mdp.h | 249 +- include/linux/msm_q6vdec.h | 277 + include/linux/msm_q6venc.h | 303 + include/linux/msm_rmnet.h | 54 + include/linux/msm_rotator.h | 57 + include/linux/msm_rpcrouter.h | 50 + include/linux/msm_smd_pkt.h | 23 + include/linux/msm_ssbi.h | 50 + include/linux/msm_vidc_dec.h | 547 + include/linux/msm_vidc_enc.h | 594 + include/linux/ofn_atlab.h | 46 + include/linux/perf_event.h | 1 + include/linux/pkt_sched.h | 1 + .../linux/platform_data/qcom_crypto_device.h | 23 + include/linux/platform_data/usb_rmnet.h | 23 + include/linux/pmic8058-batt-alarm.h | 124 + include/linux/pmic8058-charger.h | 29 + include/linux/pmic8058-misc.h | 58 + include/linux/pmic8058-nfc.h | 77 + include/linux/pmic8058-othc.h | 146 + include/linux/pmic8058-pwm.h | 88 + include/linux/pmic8058-pwrkey.h | 30 + include/linux/pmic8058-upl.h | 63 + include/linux/pmic8058-vibrator.h | 23 + include/linux/pmic8058-xoadc.h | 84 + include/linux/pwm.h | 12 +- include/linux/qcomwlan7x27a_pwrif.h | 20 + include/linux/qcomwlan_pwrif.h | 39 + include/linux/qcomwlan_secif.h | 33 + include/linux/qcota.h | 165 + include/linux/qfp_fuse.h | 41 + include/linux/regulator/consumer.h | 5 + include/linux/regulator/gpio-regulator.h | 37 + include/linux/regulator/machine.h | 5 + include/linux/regulator/pm8058-xo.h | 30 + include/linux/regulator/pm8921-regulator.h | 155 + include/linux/regulator/pmic8058-regulator.h | 88 + include/linux/regulator/pmic8901-regulator.h | 79 + include/linux/remote_spinlock.h | 116 + include/linux/rmt_storage_client.h | 83 + include/linux/rtc-msm.h | 32 + include/linux/rtc/rtc-pm8058.h | 21 + include/linux/slimbus/slimbus.h | 934 ++ include/linux/smsc911x.h | 11 +- include/linux/sysctl.h | 1 + include/linux/thermal.h | 12 + include/linux/tsif_api.h | 207 + include/linux/tzcom.h | 101 + include/linux/usb.h | 31 + include/linux/usb/android.h | 24 + include/linux/usb/android_composite.h | 97 + include/linux/usb/ch9.h | 4 + include/linux/usb/f_mtp.h | 7 + include/linux/usb/gadget.h | 1 + include/linux/usb/msm_hsusb.h | 55 +- include/linux/usb/msm_hsusb_hw.h | 6 + include/linux/usb/otg.h | 36 + include/linux/vcm.h | 652 ++ include/linux/vcm_alloc.h | 63 + include/linux/vcm_mm.h | 105 + include/linux/vcm_types.h | 355 + include/linux/wait.h | 87 + include/linux/wcnss_wlan.h | 31 + include/linux/workqueue.h | 2 + include/linux/wpce775x.h | 30 + include/media/Kbuild | 4 + include/media/msm_camera.h | 798 ++ include/media/msm_gemini.h | 114 + include/media/radio-iris.h | 558 + include/media/tavarua.h | 417 + include/media/videobuf-msm-mem.h | 46 + include/mtd/mtd-abi.h | 2 +- include/net/bluetooth/amp.h | 296 + include/net/bluetooth/bluetooth.h | 92 +- include/net/bluetooth/hci.h | 301 +- include/net/bluetooth/hci_core.h | 236 +- include/net/bluetooth/l2cap.h | 586 +- include/net/bluetooth/mgmt.h | 20 +- include/net/bluetooth/rfcomm.h | 9 +- include/sound/apr_audio.h | 1061 ++ include/sound/dai.h | 48 + include/sound/pcm.h | 4 +- include/sound/q6adm.h | 49 + include/sound/q6afe.h | 67 + include/sound/q6asm.h | 254 + include/sound/soc.h | 5 + init/Kconfig | 1 + kernel/cgroup_freezer.c | 8 + kernel/cpuset.c | 7 + kernel/irq/manage.c | 39 + kernel/irq/pm.c | 2 +- kernel/pm_qos_params.c | 4 +- kernel/power/consoleearlysuspend.c | 5 - kernel/power/suspend.c | 2 - kernel/printk.c | 17 + kernel/resource.c | 18 + kernel/sched.c | 50 +- kernel/sysctl.c | 13 + kernel/sysctl_binary.c | 1 + kernel/workqueue.c | 26 + lib/Makefile | 2 +- lib/bitmap.c | 22 +- lib/debugobjects.c | 33 + lib/genalloc.c | 173 +- lib/memory_alloc.c | 328 + mm/ashmem.c | 138 + mm/memory_hotplug.c | 85 +- mm/sparse.c | 4 +- net/bluetooth/Kconfig | 5 +- net/bluetooth/Makefile | 2 +- net/bluetooth/af_bluetooth.c | 36 +- net/bluetooth/amp.c | 2035 ++++ net/bluetooth/bnep/bnep.h | 1 - net/bluetooth/bnep/core.c | 18 +- net/bluetooth/cmtp/capi.c | 3 +- net/bluetooth/cmtp/core.c | 8 +- net/bluetooth/hci_conn.c | 392 +- net/bluetooth/hci_core.c | 491 +- net/bluetooth/hci_event.c | 939 +- net/bluetooth/hci_sock.c | 75 +- net/bluetooth/hci_sysfs.c | 31 - net/bluetooth/hidp/core.c | 265 +- net/bluetooth/hidp/hidp.h | 16 - net/bluetooth/l2cap_core.c | 8010 ++++++++++---- net/bluetooth/l2cap_sock.c | 811 +- net/bluetooth/lib.c | 23 +- net/bluetooth/mgmt.c | 324 +- net/bluetooth/rfcomm/core.c | 45 +- net/bluetooth/rfcomm/sock.c | 39 +- net/bluetooth/sco.c | 48 +- net/bluetooth/smp.c | 58 +- net/ipv4/netfilter/Makefile | 1 - net/ipv4/tcp.c | 4 +- net/ipv4/tcp_ipv4.c | 43 + net/ipv6/addrconf.c | 11 + net/ipv6/ip6_output.c | 5 +- net/sched/sch_prio.c | 10 + scripts/Makefile.modpost | 1 + scripts/build-all.py | 266 + scripts/checkpatch.pl | 232 +- scripts/gcc-wrapper.py | 117 + scripts/mod/modpost.c | 29 +- sound/core/pcm_lib.c | 3 +- sound/core/pcm_native.c | 3 +- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/atmel/atmel-pcm.c | 5 +- sound/soc/blackfin/bf5xx-ac97-pcm.c | 5 +- sound/soc/blackfin/bf5xx-i2s-pcm.c | 5 +- sound/soc/blackfin/bf5xx-tdm-pcm.c | 5 +- sound/soc/codecs/Kconfig | 7 + sound/soc/codecs/Makefile | 6 +- sound/soc/codecs/msm_stub.c | 79 + sound/soc/codecs/timpani.c | 482 + sound/soc/codecs/timpani.h | 15 + sound/soc/codecs/wcd9310-tables.c | 1024 ++ sound/soc/codecs/wcd9310.c | 1598 +++ sound/soc/codecs/wcd9310.h | 48 + sound/soc/imx/imx-pcm-fiq.c | 3 +- sound/soc/jz4740/jz4740-pcm.c | 5 +- sound/soc/kirkwood/kirkwood-dma.c | 5 +- sound/soc/msm/Kconfig | 134 + sound/soc/msm/Makefile | 71 + sound/soc/msm/msm-dai-fe.c | 210 + sound/soc/msm/msm-dai-q6.c | 555 + sound/soc/msm/msm-dai.c | 154 + sound/soc/msm/msm-mvs.c | 936 ++ sound/soc/msm/msm-pcm-hostless.c | 61 + sound/soc/msm/msm-pcm-lpa.c | 464 + sound/soc/msm/msm-pcm-q6.c | 689 ++ sound/soc/msm/msm-pcm-q6.h | 81 + sound/soc/msm/msm-pcm-routing.c | 688 ++ sound/soc/msm/msm-pcm-routing.h | 81 + sound/soc/msm/msm-pcm-voice.c | 319 + sound/soc/msm/msm-pcm-voice.h | 41 + sound/soc/msm/msm-pcm-voip.c | 721 ++ sound/soc/msm/msm-pcm.c | 645 ++ sound/soc/msm/msm-pcm.h | 200 + sound/soc/msm/msm-voip.c | 610 + sound/soc/msm/msm7201.c | 329 + sound/soc/msm/msm7k-pcm.c | 600 + sound/soc/msm/msm7kv2-dai.c | 149 + sound/soc/msm/msm7kv2-dsp.c | 633 ++ sound/soc/msm/msm7kv2-pcm.c | 774 ++ sound/soc/msm/msm7kv2-pcm.h | 207 + sound/soc/msm/msm7x30.c | 1004 ++ sound/soc/msm/msm8660-dma.c | 432 + sound/soc/msm/msm8660-i2s.c | 139 + sound/soc/msm/msm8660-pcm.c | 369 + sound/soc/msm/msm8660-pcm.h | 45 + sound/soc/msm/msm8660.c | 342 + sound/soc/msm/msm8960.c | 673 ++ sound/soc/msm/msm8x60-dai.c | 148 + sound/soc/msm/msm8x60-pcm.c | 806 ++ sound/soc/msm/msm8x60-pcm.h | 92 + sound/soc/msm/msm8x60.c | 1108 ++ sound/soc/msm/msm_audio_mvs.h | 368 + sound/soc/msm/mvs-dai.c | 141 + sound/soc/msm/qdsp6/Makefile | 1 + sound/soc/msm/qdsp6/q6adm.c | 804 ++ sound/soc/msm/qdsp6/q6afe.c | 868 ++ sound/soc/msm/qdsp6/q6asm.c | 2600 +++++ sound/soc/msm/qdsp6/q6voice.c | 2089 ++++ sound/soc/msm/qdsp6/q6voice.h | 686 ++ sound/soc/samsung/dma.c | 5 +- sound/soc/sh/siu_pcm.c | 3 +- sound/soc/soc-dapm.c | 7 +- sound/soc/soc-dsp.c | 9 +- 1851 files changed, 780209 insertions(+), 12975 deletions(-) create mode 100644 AndroidKernel.mk create mode 100644 Documentation/arm/msm/boot.txt create mode 100644 Documentation/arm/msm/emulate_domain_manager.txt create mode 100644 Documentation/arm/msm/kgsl-sysfs.txt create mode 100644 Documentation/arm/msm/msm_rng-driver.txt create mode 100644 Documentation/arm/msm/pil.txt create mode 100644 Documentation/arm/msm/rpm.txt create mode 100644 Documentation/arm/msm/tsif.txt create mode 100644 Documentation/crypto/msm/qce.txt create mode 100644 Documentation/crypto/msm/qce40.txt create mode 100644 Documentation/crypto/msm/qcedev.txt create mode 100644 Documentation/crypto/msm/qcrypto.txt create mode 100644 Documentation/csdio.txt create mode 100644 Documentation/networking/qfec.txt create mode 100644 Documentation/tzcom.txt create mode 100644 Documentation/usb/ehset_compliance.txt create mode 100644 Documentation/usb/gadget_rmnet.txt create mode 100644 Documentation/usb/gadget_sdio.txt create mode 100644 Documentation/usb/gadget_smd.txt create mode 100644 Documentation/usb/msm_otg.txt create mode 100644 arch/arm/common/cpaccess.c create mode 100644 arch/arm/configs/apq8064_defconfig create mode 100644 arch/arm/configs/msm7627-perf_defconfig create mode 100644 arch/arm/configs/msm7627_defconfig create mode 100644 arch/arm/configs/msm7627a-perf_defconfig create mode 100644 arch/arm/configs/msm7627a_defconfig create mode 100644 arch/arm/configs/msm7630-perf_defconfig create mode 100644 arch/arm/configs/msm7630_defconfig create mode 100644 arch/arm/configs/msm8660-perf_defconfig create mode 100644 arch/arm/configs/msm8660_defconfig create mode 100644 arch/arm/configs/msm8960_defconfig create mode 100644 arch/arm/include/asm/perftypes.h create mode 100644 arch/arm/include/asm/remote_spinlock.h create mode 100644 arch/arm/kernel/perf_event_msm.c create mode 100644 arch/arm/kernel/perf_event_msm_krait.c create mode 100644 arch/arm/kernel/perf_event_msm_krait_l2.c create mode 100644 arch/arm/kernel/perf_event_msm_l2.c create mode 100644 arch/arm/lib/delay.c create mode 100644 arch/arm/mach-msm/acpuclock-7x30.c create mode 100644 arch/arm/mach-msm/acpuclock-8960.c create mode 100644 arch/arm/mach-msm/acpuclock-8x50.c create mode 100644 arch/arm/mach-msm/acpuclock-8x60.c create mode 100644 arch/arm/mach-msm/acpuclock-fsm9xxx.c create mode 100644 arch/arm/mach-msm/acpuclock.c create mode 100644 arch/arm/mach-msm/arch-init-scorpion.S create mode 100644 arch/arm/mach-msm/avs.c create mode 100644 arch/arm/mach-msm/avs.h create mode 100644 arch/arm/mach-msm/avs_hw.S create mode 100644 arch/arm/mach-msm/bam_dmux.c create mode 100644 arch/arm/mach-msm/bms-batterydata.c create mode 100644 arch/arm/mach-msm/board-apq8064.c create mode 100644 arch/arm/mach-msm/board-fsm9xxx.c create mode 100644 arch/arm/mach-msm/board-halibut-keypad.c create mode 100644 arch/arm/mach-msm/board-halibut-panel.c create mode 100644 arch/arm/mach-msm/board-halibut.h create mode 100644 arch/arm/mach-msm/board-mahimahi-audio.c create mode 100644 arch/arm/mach-msm/board-mahimahi-flashlight.c create mode 100644 arch/arm/mach-msm/board-mahimahi-flashlight.h create mode 100644 arch/arm/mach-msm/board-mahimahi-keypad.c create mode 100644 arch/arm/mach-msm/board-mahimahi-microp.c create mode 100644 arch/arm/mach-msm/board-mahimahi-mmc.c create mode 100644 arch/arm/mach-msm/board-mahimahi-panel.c create mode 100644 arch/arm/mach-msm/board-mahimahi-rfkill.c create mode 100755 arch/arm/mach-msm/board-mahimahi-smb329.c create mode 100644 arch/arm/mach-msm/board-mahimahi-smb329.h create mode 100644 arch/arm/mach-msm/board-mahimahi-tpa2018d1.c create mode 100644 arch/arm/mach-msm/board-mahimahi-tpa2018d1.h create mode 100644 arch/arm/mach-msm/board-mahimahi-wifi.c create mode 100644 arch/arm/mach-msm/board-mahimahi.h create mode 100644 arch/arm/mach-msm/board-msm7x27a.c create mode 100644 arch/arm/mach-msm/board-msm8960-regulator.c create mode 100644 arch/arm/mach-msm/board-msm8960.h create mode 100644 arch/arm/mach-msm/board-msm8x60-vcm.c create mode 100644 arch/arm/mach-msm/board-sapphire-gpio.c create mode 100644 arch/arm/mach-msm/board-sapphire-h2w.c create mode 100755 arch/arm/mach-msm/board-sapphire-keypad.c create mode 100755 arch/arm/mach-msm/board-sapphire-mmc.c create mode 100644 arch/arm/mach-msm/board-sapphire-panel.c create mode 100644 arch/arm/mach-msm/board-sapphire-rfkill.c create mode 100644 arch/arm/mach-msm/board-sapphire-wifi.c create mode 100644 arch/arm/mach-msm/board-sapphire.h create mode 100644 arch/arm/mach-msm/board-swordfish-keypad.c create mode 100644 arch/arm/mach-msm/board-swordfish-mmc.c create mode 100644 arch/arm/mach-msm/board-swordfish-panel.c create mode 100644 arch/arm/mach-msm/board-swordfish.c create mode 100644 arch/arm/mach-msm/board-swordfish.h create mode 100644 arch/arm/mach-msm/board-trout-keypad.c create mode 100644 arch/arm/mach-msm/board-trout-rfkill.c create mode 100644 arch/arm/mach-msm/board-trout-wifi.c create mode 100644 arch/arm/mach-msm/btpintest.c create mode 100644 arch/arm/mach-msm/clock-7x30.c create mode 100644 arch/arm/mach-msm/clock-8960.c create mode 100644 arch/arm/mach-msm/clock-8x60.c create mode 100644 arch/arm/mach-msm/clock-dss-8960.c create mode 100644 arch/arm/mach-msm/clock-dss-8960.h create mode 100644 arch/arm/mach-msm/clock-dummy.c create mode 100644 arch/arm/mach-msm/clock-fsm9xxx.c create mode 100644 arch/arm/mach-msm/clock-local.c create mode 100644 arch/arm/mach-msm/clock-local.h create mode 100644 arch/arm/mach-msm/clock-pcom-lookup.c create mode 100644 arch/arm/mach-msm/clock-rpm.c create mode 100644 arch/arm/mach-msm/clock-rpm.h create mode 100644 arch/arm/mach-msm/clock-voter.c create mode 100644 arch/arm/mach-msm/clock-voter.h create mode 100644 arch/arm/mach-msm/cp14.S create mode 100644 arch/arm/mach-msm/cp14.h create mode 100644 arch/arm/mach-msm/cpufreq.c create mode 100644 arch/arm/mach-msm/cpuidle.c create mode 100644 arch/arm/mach-msm/cpuidle.h create mode 100644 arch/arm/mach-msm/dal.c create mode 100644 arch/arm/mach-msm/dal_axi.c create mode 100644 arch/arm/mach-msm/dal_remotetest.c create mode 100644 arch/arm/mach-msm/dal_remotetest.h create mode 100644 arch/arm/mach-msm/devices-8064.c create mode 100644 arch/arm/mach-msm/devices-8960.c create mode 100644 arch/arm/mach-msm/devices-fsm9xxx.c create mode 100644 arch/arm/mach-msm/devices-msm7x01a.c create mode 100644 arch/arm/mach-msm/devices-msm7x25.c create mode 100644 arch/arm/mach-msm/devices-msm7x27.c create mode 100644 arch/arm/mach-msm/devices-msm7x27a.c create mode 100644 arch/arm/mach-msm/devices-msm7x2xa.h delete mode 100644 arch/arm/mach-msm/devices-msm8960.c create mode 100644 arch/arm/mach-msm/devices-msm8x60.c create mode 100644 arch/arm/mach-msm/devices-msm8x60.h create mode 100644 arch/arm/mach-msm/devices_htc.c create mode 100644 arch/arm/mach-msm/dfe-fsm9xxx.c create mode 100644 arch/arm/mach-msm/dma_test.c create mode 100644 arch/arm/mach-msm/etm.c create mode 100644 arch/arm/mach-msm/fiq.h create mode 100644 arch/arm/mach-msm/fiq_glue.S create mode 100644 arch/arm/mach-msm/fish_battery.c create mode 100644 arch/arm/mach-msm/footswitch-8x60.c create mode 100644 arch/arm/mach-msm/footswitch-pcom.c create mode 100644 arch/arm/mach-msm/footswitch.h create mode 100644 arch/arm/mach-msm/gpio-fsm9xxx.c create mode 100644 arch/arm/mach-msm/gpio.h create mode 100644 arch/arm/mach-msm/gpiomux-7x27.c create mode 100644 arch/arm/mach-msm/gpiomux-7x30.c create mode 100644 arch/arm/mach-msm/gpiomux-8x60.h create mode 100644 arch/arm/mach-msm/htc_35mm_jack.c create mode 100644 arch/arm/mach-msm/htc_acoustic.c create mode 100644 arch/arm/mach-msm/htc_acoustic_qsd.c create mode 100644 arch/arm/mach-msm/htc_akm_cal.c create mode 100644 arch/arm/mach-msm/htc_battery.c create mode 100644 arch/arm/mach-msm/htc_headset.c create mode 100644 arch/arm/mach-msm/htc_power_supply.c create mode 100644 arch/arm/mach-msm/htc_pwrsink.c create mode 100644 arch/arm/mach-msm/htc_wifi_nvs.c create mode 100644 arch/arm/mach-msm/hw3d.c create mode 100644 arch/arm/mach-msm/idle-v6.S create mode 100644 arch/arm/mach-msm/idle-v7.S create mode 100644 arch/arm/mach-msm/idle.h create mode 100644 arch/arm/mach-msm/idle_stats.c create mode 100644 arch/arm/mach-msm/idle_stats.h create mode 100644 arch/arm/mach-msm/include/mach/audio_dma_msm8k.h create mode 100644 arch/arm/mach-msm/include/mach/bam_dmux.h create mode 100644 arch/arm/mach-msm/include/mach/barriers.h create mode 100644 arch/arm/mach-msm/include/mach/bcm_bt_lpm.h create mode 100644 arch/arm/mach-msm/include/mach/board_htc.h create mode 100644 arch/arm/mach-msm/include/mach/camera.h create mode 100644 arch/arm/mach-msm/include/mach/dal.h create mode 100644 arch/arm/mach-msm/include/mach/dal_axi.h create mode 100644 arch/arm/mach-msm/include/mach/debug_mm.h create mode 100644 arch/arm/mach-msm/include/mach/dma-fsm9xxx.h create mode 100644 arch/arm/mach-msm/include/mach/dma_test.h create mode 100644 arch/arm/mach-msm/include/mach/fiq.h create mode 100644 arch/arm/mach-msm/include/mach/gpio-tlmm-v1.h create mode 100644 arch/arm/mach-msm/include/mach/gpio-v1.h create mode 100644 arch/arm/mach-msm/include/mach/htc_35mm_jack.h create mode 100644 arch/arm/mach-msm/include/mach/htc_acoustic_qsd.h create mode 100644 arch/arm/mach-msm/include/mach/htc_headset.h create mode 100644 arch/arm/mach-msm/include/mach/htc_pwrsink.h create mode 100644 arch/arm/mach-msm/include/mach/iommu_domains.h create mode 100644 arch/arm/mach-msm/include/mach/irqs-7xxx.h create mode 100644 arch/arm/mach-msm/include/mach/irqs-8064.h create mode 100644 arch/arm/mach-msm/include/mach/irqs-fsm9xxx.h create mode 100644 arch/arm/mach-msm/include/mach/mdm.h create mode 100644 arch/arm/mach-msm/include/mach/mpp.h create mode 100644 arch/arm/mach-msm/include/mach/msm-krait-l2-accessors.h create mode 100644 arch/arm/mach-msm/include/mach/msm72k_otg.h create mode 100644 arch/arm/mach-msm/include/mach/msm_adsp.h create mode 100644 arch/arm/mach-msm/include/mach/msm_audio_aac.h create mode 100644 arch/arm/mach-msm/include/mach/msm_battery.h create mode 100644 arch/arm/mach-msm/include/mach/msm_bus.h create mode 100644 arch/arm/mach-msm/include/mach/msm_bus_board.h create mode 100644 arch/arm/mach-msm/include/mach/msm_dsps.h create mode 100644 arch/arm/mach-msm/include/mach/msm_fast_timer.h create mode 100644 arch/arm/mach-msm/include/mach/msm_hdmi_audio.h create mode 100644 arch/arm/mach-msm/include/mach/msm_hsusb.h create mode 100644 arch/arm/mach-msm/include/mach/msm_hsusb_hw.h create mode 100644 arch/arm/mach-msm/include/mach/msm_i2ckbd.h create mode 100644 arch/arm/mach-msm/include/mach/msm_iomap-7xxx.h create mode 100644 arch/arm/mach-msm/include/mach/msm_iomap-8064.h create mode 100644 arch/arm/mach-msm/include/mach/msm_iomap-fsm9xxx.h create mode 100644 arch/arm/mach-msm/include/mach/msm_memtypes.h create mode 100644 arch/arm/mach-msm/include/mach/msm_migrate_pages.h create mode 100644 arch/arm/mach-msm/include/mach/msm_otg.h create mode 100644 arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h create mode 100644 arch/arm/mach-msm/include/mach/msm_qdsp6_audiov2.h create mode 100644 arch/arm/mach-msm/include/mach/msm_reqs.h create mode 100644 arch/arm/mach-msm/include/mach/msm_rotator_imem.h create mode 100644 arch/arm/mach-msm/include/mach/msm_rpcrouter.h create mode 100644 arch/arm/mach-msm/include/mach/msm_serial_debugger.h create mode 100644 arch/arm/mach-msm/include/mach/msm_serial_hs.h create mode 100644 arch/arm/mach-msm/include/mach/msm_serial_hs_lite.h create mode 100644 arch/arm/mach-msm/include/mach/msm_serial_pdata.h create mode 100644 arch/arm/mach-msm/include/mach/msm_smsm.h create mode 100644 arch/arm/mach-msm/include/mach/msm_spi.h create mode 100644 arch/arm/mach-msm/include/mach/msm_sps.h create mode 100644 arch/arm/mach-msm/include/mach/msm_subsystem_map.h create mode 100644 arch/arm/mach-msm/include/mach/msm_touch.h create mode 100644 arch/arm/mach-msm/include/mach/msm_touchpad.h create mode 100644 arch/arm/mach-msm/include/mach/msm_tsif.h create mode 100644 arch/arm/mach-msm/include/mach/msm_xo.h create mode 100644 arch/arm/mach-msm/include/mach/oem_rapi_client.h create mode 100644 arch/arm/mach-msm/include/mach/peripheral-loader.h create mode 100644 arch/arm/mach-msm/include/mach/pmic.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5audplaycmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5audplaymsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5audppcmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5audppmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreproc.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreproccmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreprocmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5audreccmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5audrecmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5jpegcmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5jpegmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5lpmcmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5lpmmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5rmtcmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5rmtmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5vdeccmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5vdecmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5venccmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5vfecmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/qdsp5vfemsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5/snd_adie.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/acdb_commands.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/adie_marimba.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/afe.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/audio_acdb_def.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/audio_acdbi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/audio_def.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/audio_dev_ctl.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/audio_interct.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/audpp.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/audpreproc.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/aux_pcm.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/codec_utils.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/lpa.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/lpa_hw.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/marimba_profile.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/mi2s.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/mp3_funcs.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/msm_lpa.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/pcm_funcs.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5afecmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5afemsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audplaycmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audplaymsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audppcmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audppmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audpreproccmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audpreprocmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audreccmdi.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audrecmsg.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/snddev_ecodec.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/snddev_icodec.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/snddev_mi2s.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/snddev_virtual.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp5v2/voice.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp6v2/apr.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp6v2/apr_tal.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp6v2/audio_acdb.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp6v2/audio_dev_ctl.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp6v2/dsp_debug.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp6v2/q6voice.h create mode 100644 arch/arm/mach-msm/include/mach/qdsp6v2/rtac.h create mode 100644 arch/arm/mach-msm/include/mach/remote_spinlock.h create mode 100644 arch/arm/mach-msm/include/mach/restart.h create mode 100644 arch/arm/mach-msm/include/mach/rpc_hsusb.h create mode 100644 arch/arm/mach-msm/include/mach/rpc_pmapp.h create mode 100644 arch/arm/mach-msm/include/mach/rpc_server_handset.h create mode 100644 arch/arm/mach-msm/include/mach/rpm-8660.h create mode 100644 arch/arm/mach-msm/include/mach/rpm-8960.h create mode 100644 arch/arm/mach-msm/include/mach/rpm-regulator-8660.h create mode 100644 arch/arm/mach-msm/include/mach/rpm-regulator-8960.h create mode 100644 arch/arm/mach-msm/include/mach/rpm-regulator.h create mode 100644 arch/arm/mach-msm/include/mach/rpm.h create mode 100644 arch/arm/mach-msm/include/mach/scm-io.h create mode 100644 arch/arm/mach-msm/include/mach/scm.h create mode 100644 arch/arm/mach-msm/include/mach/sdio_al.h create mode 100644 arch/arm/mach-msm/include/mach/sdio_cmux.h create mode 100644 arch/arm/mach-msm/include/mach/sdio_dmux.h create mode 100644 arch/arm/mach-msm/include/mach/sdio_smem.h create mode 100644 arch/arm/mach-msm/include/mach/sdio_tty.h create mode 100644 arch/arm/mach-msm/include/mach/sirc-fsm9xxx.h create mode 100644 arch/arm/mach-msm/include/mach/smem_log.h create mode 100644 arch/arm/mach-msm/include/mach/smp.h create mode 100644 arch/arm/mach-msm/include/mach/socinfo.h create mode 100644 arch/arm/mach-msm/include/mach/sps.h create mode 100644 arch/arm/mach-msm/include/mach/subsystem_notif.h create mode 100644 arch/arm/mach-msm/include/mach/subsystem_restart.h create mode 100644 arch/arm/mach-msm/include/mach/tpm_st_i2c.h create mode 100644 arch/arm/mach-msm/include/mach/usb_gadget_fserial.h create mode 100644 arch/arm/mach-msm/include/mach/usbdiag.h create mode 100644 arch/arm/mach-msm/iommu_domains.c create mode 100644 arch/arm/mach-msm/ipc_router.c create mode 100644 arch/arm/mach-msm/ipc_router.h create mode 100644 arch/arm/mach-msm/ipc_router_smd_xprt.c create mode 100644 arch/arm/mach-msm/ipc_socket.c create mode 100644 arch/arm/mach-msm/irq.h create mode 100644 arch/arm/mach-msm/jtag-v7.S create mode 100644 arch/arm/mach-msm/keypad-surf-ffa.c create mode 100644 arch/arm/mach-msm/mdm.c create mode 100644 arch/arm/mach-msm/memory.c create mode 100644 arch/arm/mach-msm/memory_topology.c create mode 100644 arch/arm/mach-msm/mkrpcsym.pl create mode 100644 arch/arm/mach-msm/modem-8960.c create mode 100644 arch/arm/mach-msm/modem_notifier.c create mode 100644 arch/arm/mach-msm/modem_notifier.h create mode 100644 arch/arm/mach-msm/mpm.c create mode 100644 arch/arm/mach-msm/mpm.h create mode 100644 arch/arm/mach-msm/mpp.c create mode 100644 arch/arm/mach-msm/msm-keypad-devices.h create mode 100644 arch/arm/mach-msm/msm-krait-l2-accessors.c create mode 100644 arch/arm/mach-msm/msm_bus/Makefile create mode 100644 arch/arm/mach-msm/msm_bus/msm_bus_arb.c create mode 100644 arch/arm/mach-msm/msm_bus/msm_bus_board_8660.c create mode 100644 arch/arm/mach-msm/msm_bus/msm_bus_board_8960.c create mode 100644 arch/arm/mach-msm/msm_bus/msm_bus_config.c create mode 100644 arch/arm/mach-msm/msm_bus/msm_bus_core.c create mode 100644 arch/arm/mach-msm/msm_bus/msm_bus_core.h create mode 100644 arch/arm/mach-msm/msm_bus/msm_bus_dbg.c create mode 100644 arch/arm/mach-msm/msm_bus/msm_bus_fabric.c create mode 100644 arch/arm/mach-msm/msm_bus/msm_bus_rpm.c create mode 100644 arch/arm/mach-msm/msm_dsps.c create mode 100644 arch/arm/mach-msm/msm_fault_handlers.c create mode 100644 arch/arm/mach-msm/msm_kexec.c create mode 100644 arch/arm/mach-msm/msm_pm_qos.c create mode 100644 arch/arm/mach-msm/msm_pm_qos.h create mode 100644 arch/arm/mach-msm/msm_reqs.c create mode 100644 arch/arm/mach-msm/msm_rq_stats.c create mode 100644 arch/arm/mach-msm/msm_show_resume_irq.c create mode 100644 arch/arm/mach-msm/msm_vibrator.c create mode 100644 arch/arm/mach-msm/msm_watchdog.c create mode 100644 arch/arm/mach-msm/msm_watchdog.h create mode 100644 arch/arm/mach-msm/msm_xo.c create mode 100644 arch/arm/mach-msm/nand_partitions.c create mode 100644 arch/arm/mach-msm/no-pm.c create mode 100644 arch/arm/mach-msm/nohlt.c create mode 100644 arch/arm/mach-msm/oem_rapi_client.c create mode 100644 arch/arm/mach-msm/peripheral-loader.c create mode 100644 arch/arm/mach-msm/peripheral-loader.h create mode 100644 arch/arm/mach-msm/peripheral-reset-8960.c create mode 100644 arch/arm/mach-msm/peripheral-reset.c create mode 100644 arch/arm/mach-msm/ping_apps_server.c create mode 100644 arch/arm/mach-msm/ping_mdm_rpc_client.c create mode 100644 arch/arm/mach-msm/pm-8x60.c create mode 100644 arch/arm/mach-msm/pm.c create mode 100644 arch/arm/mach-msm/pm.h create mode 100644 arch/arm/mach-msm/pm2.c create mode 100644 arch/arm/mach-msm/pmic.c create mode 100644 arch/arm/mach-msm/pmic.h create mode 100644 arch/arm/mach-msm/pmic8058-gpio.c create mode 100644 arch/arm/mach-msm/pmic8058-mpp.c create mode 100644 arch/arm/mach-msm/pmic_debugfs.c create mode 100644 arch/arm/mach-msm/pmu.c create mode 100644 arch/arm/mach-msm/proc_comm_test.c create mode 100644 arch/arm/mach-msm/qdsp5/Makefile create mode 100644 arch/arm/mach-msm/qdsp5/adsp.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp.h create mode 100644 arch/arm/mach-msm/qdsp5/adsp_6210.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_6220.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_6225.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_driver.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_info.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_jpeg_patch_event.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_jpeg_verify_cmd.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_lpm_verify_cmd.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_rm.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_vfe_patch_event.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_vfe_verify_cmd.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_video_verify_cmd.c create mode 100644 arch/arm/mach-msm/qdsp5/adsp_videoenc_verify_cmd.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_aac.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_amrnb.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_amrnb_in.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_amrwb.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_evrc.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_evrc_in.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_fm.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_in.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_mp3.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_out.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_pcm.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_pcm_in.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_qcelp.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_qcelp_in.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_voicememo.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_wma.c create mode 100644 arch/arm/mach-msm/qdsp5/audio_wmapro.c create mode 100644 arch/arm/mach-msm/qdsp5/audmgr.c create mode 100644 arch/arm/mach-msm/qdsp5/audmgr.h create mode 100644 arch/arm/mach-msm/qdsp5/audmgr_new.h create mode 100644 arch/arm/mach-msm/qdsp5/audpp.c create mode 100644 arch/arm/mach-msm/qdsp5/audpreproc.c create mode 100644 arch/arm/mach-msm/qdsp5/audrec.c create mode 100644 arch/arm/mach-msm/qdsp5/evlog.h create mode 100644 arch/arm/mach-msm/qdsp5/snd.c create mode 100644 arch/arm/mach-msm/qdsp5/snd_adie.c create mode 100755 arch/arm/mach-msm/qdsp5v2/Makefile create mode 100644 arch/arm/mach-msm/qdsp5v2/adsp.c create mode 100644 arch/arm/mach-msm/qdsp5v2/adsp.h create mode 100644 arch/arm/mach-msm/qdsp5v2/adsp_driver.c create mode 100644 arch/arm/mach-msm/qdsp5v2/adsp_info.c create mode 100644 arch/arm/mach-msm/qdsp5v2/afe.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_a2dp_in.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_aac.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_aac_in.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_acdb.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_adpcm.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_amrnb.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_amrnb_in.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_amrwb.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_dev_ctl.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_evrc.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_evrc_in.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_fm.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_interct.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_lpa.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_mp3.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_mvs.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_out.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_pcm.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_pcm_in.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_qcelp.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_qcelp_in.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_wma.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audio_wmapro.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audpp.c create mode 100644 arch/arm/mach-msm/qdsp5v2/audpreproc.c create mode 100644 arch/arm/mach-msm/qdsp5v2/aux_pcm.c create mode 100644 arch/arm/mach-msm/qdsp5v2/lpa.c create mode 100644 arch/arm/mach-msm/qdsp5v2/mi2s.c create mode 100644 arch/arm/mach-msm/qdsp5v2/mp3_funcs.c create mode 100644 arch/arm/mach-msm/qdsp5v2/pcm_funcs.c create mode 100644 arch/arm/mach-msm/qdsp5v2/snddev_data_marimba.c create mode 100644 arch/arm/mach-msm/qdsp5v2/snddev_data_timpani.c create mode 100644 arch/arm/mach-msm/qdsp5v2/snddev_ecodec.c create mode 100644 arch/arm/mach-msm/qdsp5v2/snddev_icodec.c create mode 100644 arch/arm/mach-msm/qdsp5v2/snddev_mi2s.c create mode 100644 arch/arm/mach-msm/qdsp5v2/snddev_virtual.c create mode 100644 arch/arm/mach-msm/qdsp5v2/timpani_profile_7x30.h create mode 100644 arch/arm/mach-msm/qdsp5v2/voice.c create mode 100644 arch/arm/mach-msm/qdsp6/Makefile create mode 100644 arch/arm/mach-msm/qdsp6/aac_in.c create mode 100644 arch/arm/mach-msm/qdsp6/amrnb_in.c create mode 100644 arch/arm/mach-msm/qdsp6/analog_audio.c create mode 100644 arch/arm/mach-msm/qdsp6/audio_ctl.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/Makefile create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/aac_in.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/amrnb_in.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/analog_audio.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/audio_ctl.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/dal_acdb.h create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/dal_adie.h create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/dal_audio.h create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/dal_audio_format.h create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/dal_voice.h create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/evrc_in.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/mp3.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/pcm_in.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/pcm_out.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/q6audio.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/q6audio_devices.h create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/qcelp_in.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/routing.c create mode 100644 arch/arm/mach-msm/qdsp6/audiov2/voice.c create mode 100644 arch/arm/mach-msm/qdsp6/auxpcm_lb_in.c create mode 100644 arch/arm/mach-msm/qdsp6/auxpcm_lb_out.c create mode 100644 arch/arm/mach-msm/qdsp6/dal.c create mode 100644 arch/arm/mach-msm/qdsp6/dal.h create mode 100644 arch/arm/mach-msm/qdsp6/dal_acdb.h create mode 100644 arch/arm/mach-msm/qdsp6/dal_adie.h create mode 100644 arch/arm/mach-msm/qdsp6/dal_audio.h create mode 100644 arch/arm/mach-msm/qdsp6/dal_audio_format.h create mode 100644 arch/arm/mach-msm/qdsp6/dsp_debug.c create mode 100644 arch/arm/mach-msm/qdsp6/dtmf.c create mode 100644 arch/arm/mach-msm/qdsp6/evrc_in.c create mode 100644 arch/arm/mach-msm/qdsp6/mp3.c create mode 100644 arch/arm/mach-msm/qdsp6/msm_q6vdec.c create mode 100644 arch/arm/mach-msm/qdsp6/msm_q6venc.c create mode 100644 arch/arm/mach-msm/qdsp6/pcm_in.c create mode 100644 arch/arm/mach-msm/qdsp6/pcm_out.c create mode 100644 arch/arm/mach-msm/qdsp6/q6audio.c create mode 100644 arch/arm/mach-msm/qdsp6/q6audio_devices.h create mode 100644 arch/arm/mach-msm/qdsp6/qcelp_in.c create mode 100644 arch/arm/mach-msm/qdsp6/routing.c create mode 100644 arch/arm/mach-msm/qdsp6v2/Makefile create mode 100644 arch/arm/mach-msm/qdsp6v2/aac_in.c create mode 100644 arch/arm/mach-msm/qdsp6v2/amrnb_in.c create mode 100644 arch/arm/mach-msm/qdsp6v2/apr.c create mode 100644 arch/arm/mach-msm/qdsp6v2/apr_tal.c create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_aac.c create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_acdb.c create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_dev_ctl.c create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_lpa.c create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_lpa.h create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_multi_aac.c create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_mvs.c create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_utils.c create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_utils.h create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_wma.c create mode 100644 arch/arm/mach-msm/qdsp6v2/audio_wmapro.c create mode 100644 arch/arm/mach-msm/qdsp6v2/board-msm8x60-audio.c create mode 100644 arch/arm/mach-msm/qdsp6v2/dsp_debug.c create mode 100644 arch/arm/mach-msm/qdsp6v2/evrc_in.c create mode 100644 arch/arm/mach-msm/qdsp6v2/fm.c create mode 100644 arch/arm/mach-msm/qdsp6v2/lpa_if_hdmi.c create mode 100644 arch/arm/mach-msm/qdsp6v2/msm_qdsp6_audio.h create mode 100644 arch/arm/mach-msm/qdsp6v2/pcm_in.c create mode 100644 arch/arm/mach-msm/qdsp6v2/pcm_out.c create mode 100644 arch/arm/mach-msm/qdsp6v2/q6core.c create mode 100644 arch/arm/mach-msm/qdsp6v2/q6core.h create mode 100644 arch/arm/mach-msm/qdsp6v2/q6voice.c create mode 100644 arch/arm/mach-msm/qdsp6v2/qcelp_in.c create mode 100644 arch/arm/mach-msm/qdsp6v2/rtac.c create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_ecodec.c create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_ecodec.h create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_hdmi.c create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_hdmi.h create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_icodec.c create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_icodec.h create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_mi2s.c create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_mi2s.h create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_virtual.c create mode 100644 arch/arm/mach-msm/qdsp6v2/snddev_virtual.h create mode 100644 arch/arm/mach-msm/qdsp6v2/timpani_profile_8x60.h create mode 100644 arch/arm/mach-msm/ramdump.c create mode 100644 arch/arm/mach-msm/ramdump.h create mode 100644 arch/arm/mach-msm/remote_spinlock.c create mode 100644 arch/arm/mach-msm/reset_modem.c create mode 100644 arch/arm/mach-msm/restart-fsm9xxx.c create mode 100644 arch/arm/mach-msm/restart.c create mode 100644 arch/arm/mach-msm/rfic-fsm9xxx.c create mode 100644 arch/arm/mach-msm/rmt_storage_client.c create mode 100644 arch/arm/mach-msm/rpc_dog_keepalive.c create mode 100644 arch/arm/mach-msm/rpc_fsusb.c create mode 100644 arch/arm/mach-msm/rpc_hsusb.c create mode 100644 arch/arm/mach-msm/rpc_pmapp.c create mode 100644 arch/arm/mach-msm/rpc_server_dog_keepalive.c create mode 100644 arch/arm/mach-msm/rpc_server_handset.c create mode 100644 arch/arm/mach-msm/rpc_server_time_remote.c create mode 100644 arch/arm/mach-msm/rpc_server_time_remote.h create mode 100644 arch/arm/mach-msm/rpcrouter_sdio_xprt.c create mode 100644 arch/arm/mach-msm/rpcrouter_smd_xprt.c create mode 100644 arch/arm/mach-msm/rpm-regulator-8960.c create mode 100644 arch/arm/mach-msm/rpm-regulator.c create mode 100644 arch/arm/mach-msm/rpm.c create mode 100644 arch/arm/mach-msm/rpm_log.c create mode 100644 arch/arm/mach-msm/rpm_log.h create mode 100644 arch/arm/mach-msm/rpm_resources.c create mode 100644 arch/arm/mach-msm/rpm_resources.h create mode 100644 arch/arm/mach-msm/rpm_stats.c create mode 100644 arch/arm/mach-msm/rpm_stats.h create mode 100644 arch/arm/mach-msm/saw-regulator.c create mode 100644 arch/arm/mach-msm/scm-io.c create mode 100644 arch/arm/mach-msm/sdio_al.c create mode 100644 arch/arm/mach-msm/sdio_al_dloader.c create mode 100644 arch/arm/mach-msm/sdio_al_private.h create mode 100644 arch/arm/mach-msm/sdio_al_test.c create mode 100644 arch/arm/mach-msm/sdio_cmux.c create mode 100644 arch/arm/mach-msm/sdio_ctl.c create mode 100644 arch/arm/mach-msm/sdio_dmux.c create mode 100644 arch/arm/mach-msm/sdio_smem.c create mode 100644 arch/arm/mach-msm/sdio_tty.c create mode 100644 arch/arm/mach-msm/sdio_tty_ciq.c create mode 100644 arch/arm/mach-msm/sirc-fsm9xxx.c create mode 100644 arch/arm/mach-msm/sirc.h create mode 100644 arch/arm/mach-msm/smd_nmea.c create mode 100644 arch/arm/mach-msm/smd_pkt.c create mode 100644 arch/arm/mach-msm/smd_qmi.c create mode 100755 arch/arm/mach-msm/smd_rpc_sym create mode 100644 arch/arm/mach-msm/smd_rpc_sym.h create mode 100644 arch/arm/mach-msm/smd_rpcrouter.c create mode 100644 arch/arm/mach-msm/smd_rpcrouter.h create mode 100644 arch/arm/mach-msm/smd_rpcrouter_clients.c create mode 100644 arch/arm/mach-msm/smd_rpcrouter_device.c create mode 100644 arch/arm/mach-msm/smd_rpcrouter_servers.c create mode 100644 arch/arm/mach-msm/smd_rpcrouter_xdr.c create mode 100644 arch/arm/mach-msm/smd_tty.c create mode 100644 arch/arm/mach-msm/smem_log.c create mode 100644 arch/arm/mach-msm/socinfo.c create mode 100644 arch/arm/mach-msm/spm-v2.c create mode 100644 arch/arm/mach-msm/spm.c create mode 100644 arch/arm/mach-msm/spm.h create mode 100644 arch/arm/mach-msm/spm_devices.c create mode 100644 arch/arm/mach-msm/spm_driver.h create mode 100644 arch/arm/mach-msm/subsystem-fatal-8x60.c create mode 100644 arch/arm/mach-msm/subsystem_map.c create mode 100644 arch/arm/mach-msm/subsystem_notif.c create mode 100644 arch/arm/mach-msm/subsystem_restart.c create mode 100644 arch/arm/mach-msm/timer.h create mode 100644 arch/arm/mm/emulate_domain_manager-v7.c create mode 100644 arch/arm/mm/vcm.c create mode 100644 arch/arm/mm/vcm_alloc.c create mode 100644 arch/arm/mm/vcm_mm.c create mode 100644 arch/arm/perfmon/Makefile create mode 100644 arch/arm/perfmon/cp15_registers.h create mode 100644 arch/arm/perfmon/l2_cp15_registers.h create mode 100644 arch/arm/perfmon/mcrmrc.h create mode 100644 arch/arm/perfmon/per-axi.c create mode 100644 arch/arm/perfmon/per-axi.h create mode 100644 arch/arm/perfmon/per-process-perf.c create mode 100644 arch/arm/perfmon/per.c create mode 100644 arch/arm/perfmon/perf-function-hooks.c create mode 100644 arch/arm/perfmon/perf-smp.c create mode 100644 arch/arm/perfmon/perf-v7.c create mode 100644 arch/arm/perfmon/perf.h create mode 100644 chromeos/changelog create mode 100644 chromeos/config/armel/config.common.armel create mode 100644 chromeos/config/armel/config.flavour.chromeos-arm create mode 100644 chromeos/config/armel/config.flavour.chromeos-tegra2 create mode 100644 chromeos/config/armel/config.flavour.chromeos-voguev210 create mode 100644 chromeos/config/config.common.chromeos create mode 100644 chromeos/config/i386/config.common.i386 create mode 100644 chromeos/config/i386/config.flavour.chromeos-intel-menlow create mode 100644 chromeos/config/i386/config.flavour.chromium-i386 create mode 100644 chromeos/config/x86_64/config.common.x86_64 create mode 100644 chromeos/config/x86_64/config.flavour.chromeos-intel-pineview create mode 100755 chromeos/scripts/allconfigs create mode 100755 chromeos/scripts/compat_wireless_config create mode 100755 chromeos/scripts/kernelconfig create mode 100755 chromeos/scripts/prepareconfig create mode 100755 chromeos/scripts/splitconfig create mode 100644 drivers/bluetooth/bluesleep.c create mode 100644 drivers/bluetooth/bluetooth-power.c create mode 100644 drivers/bluetooth/hci_ibs.c create mode 100644 drivers/bluetooth/hci_smd.c create mode 100644 drivers/char/csdio.c create mode 100644 drivers/char/diag/Kconfig create mode 100644 drivers/char/diag/Makefile create mode 100644 drivers/char/diag/diagchar.h create mode 100644 drivers/char/diag/diagchar_core.c create mode 100644 drivers/char/diag/diagchar_hdlc.c create mode 100644 drivers/char/diag/diagchar_hdlc.h create mode 100644 drivers/char/diag/diagfwd.c create mode 100644 drivers/char/diag/diagfwd.h create mode 100644 drivers/char/diag/diagfwd_cntl.c create mode 100644 drivers/char/diag/diagfwd_cntl.h create mode 100644 drivers/char/diag/diagfwd_sdio.c create mode 100644 drivers/char/diag/diagfwd_sdio.h create mode 100644 drivers/char/diag/diagmem.c create mode 100644 drivers/char/diag/diagmem.h create mode 100644 drivers/char/hw_random/msm_rng.c create mode 100644 drivers/char/msm_rotator.c create mode 100644 drivers/char/tpm/tpm_st_i2c.c create mode 100644 drivers/char/tpm/tpmd_dev/Makefile create mode 100644 drivers/char/tpm/tpmd_dev/config.h create mode 100644 drivers/char/tpm/tpmd_dev/tpmd_dev.c create mode 100644 drivers/char/tty_io.c create mode 100644 drivers/crypto/msm/Makefile create mode 100644 drivers/crypto/msm/inc/qce.h create mode 100644 drivers/crypto/msm/inc/qce_ota.h create mode 100644 drivers/crypto/msm/inc/qcedev.h create mode 100644 drivers/crypto/msm/inc/qcryptohw_30.h create mode 100644 drivers/crypto/msm/inc/qcryptohw_40.h create mode 100644 drivers/crypto/msm/ota_crypto.c create mode 100644 drivers/crypto/msm/qce.c create mode 100644 drivers/crypto/msm/qce40.c create mode 100644 drivers/crypto/msm/qcedev.c create mode 100644 drivers/crypto/msm/qcrypto.c create mode 100644 drivers/gpio/gpio-pm8xxx-rpc.c create mode 100644 drivers/gpio/pm8xxx-gpio.c create mode 100644 drivers/gpio/pm8xxx-mpp.c create mode 100644 drivers/gpio/pmic8901-mpp.c create mode 100644 drivers/gpu/msm/Kconfig create mode 100644 drivers/gpu/msm/Makefile create mode 100644 drivers/gpu/msm/a200_reg.h create mode 100644 drivers/gpu/msm/a220_reg.h create mode 100644 drivers/gpu/msm/adreno.c create mode 100644 drivers/gpu/msm/adreno.h create mode 100644 drivers/gpu/msm/adreno_debugfs.c create mode 100644 drivers/gpu/msm/adreno_debugfs.h create mode 100644 drivers/gpu/msm/adreno_drawctxt.c create mode 100644 drivers/gpu/msm/adreno_drawctxt.h create mode 100644 drivers/gpu/msm/adreno_pm4types.h create mode 100644 drivers/gpu/msm/adreno_postmortem.c create mode 100644 drivers/gpu/msm/adreno_postmortem.h create mode 100644 drivers/gpu/msm/adreno_ringbuffer.c create mode 100644 drivers/gpu/msm/adreno_ringbuffer.h create mode 100644 drivers/gpu/msm/kgsl.c create mode 100644 drivers/gpu/msm/kgsl.h create mode 100644 drivers/gpu/msm/kgsl_cffdump.c create mode 100644 drivers/gpu/msm/kgsl_cffdump.h create mode 100644 drivers/gpu/msm/kgsl_debugfs.c create mode 100644 drivers/gpu/msm/kgsl_debugfs.h create mode 100644 drivers/gpu/msm/kgsl_device.h create mode 100644 drivers/gpu/msm/kgsl_drm.c create mode 100644 drivers/gpu/msm/kgsl_log.h create mode 100644 drivers/gpu/msm/kgsl_mmu.c create mode 100644 drivers/gpu/msm/kgsl_mmu.h create mode 100644 drivers/gpu/msm/kgsl_pwrctrl.c create mode 100644 drivers/gpu/msm/kgsl_pwrctrl.h create mode 100644 drivers/gpu/msm/kgsl_pwrscale.c create mode 100644 drivers/gpu/msm/kgsl_pwrscale.h create mode 100644 drivers/gpu/msm/kgsl_pwrscale_trustzone.c create mode 100644 drivers/gpu/msm/kgsl_sharedmem.c create mode 100644 drivers/gpu/msm/kgsl_sharedmem.h create mode 100644 drivers/gpu/msm/z180.c create mode 100644 drivers/gpu/msm/z180.h create mode 100644 drivers/gpu/msm/z180_reg.h create mode 100644 drivers/hwmon/m_adcproc.c create mode 100644 drivers/hwmon/msm_adc.c create mode 100644 drivers/hwmon/wpce775x.c create mode 100644 drivers/i2c/busses/i2c-msm.c create mode 100644 drivers/i2c/busses/i2c-qup.c create mode 100644 drivers/i2c/busses/i2c-ssbi.c create mode 100644 drivers/input/joystick/tdisc_vtd518_shinetsu.c create mode 100644 drivers/input/keyboard/pmic8058-keypad.c create mode 100644 drivers/input/keyboard/qci_kbd.c create mode 100644 drivers/input/misc/bma150.c create mode 100644 drivers/input/misc/isa1200-ff-memless.c create mode 100644 drivers/input/misc/pmic8058-othc.c create mode 100644 drivers/input/misc/pmic8058-pwrkey.c create mode 100644 drivers/input/misc/pmic8058-vib-memless.c create mode 100644 drivers/input/mouse/qci_touchpad.c create mode 100644 drivers/input/touchscreen/atmel_maxtouch.c create mode 100644 drivers/input/touchscreen/cy8c_tmg_ts.c create mode 100644 drivers/input/touchscreen/cy8c_ts.c create mode 100644 drivers/input/touchscreen/cyttsp-i2c.c create mode 100755 drivers/input/touchscreen/cyttsp_fw.h create mode 100644 drivers/input/touchscreen/msm_touch.c create mode 100644 drivers/input/touchscreen/msm_ts.c create mode 100644 drivers/leds/leds-cpld.c create mode 100644 drivers/leds/leds-msm-pdm.c create mode 100644 drivers/leds/leds-msm-pmic.c create mode 100644 drivers/leds/leds-pm8xxx.c create mode 100644 drivers/leds/leds-pmic8058.c create mode 100644 drivers/leds/leds-qci-backlight.c create mode 100644 drivers/media/radio/radio-iris-transport.c create mode 100644 drivers/media/radio/radio-iris.c create mode 100644 drivers/media/radio/radio-tavarua.c create mode 100644 drivers/media/video/msm/Kconfig create mode 100644 drivers/media/video/msm/Makefile create mode 100644 drivers/media/video/msm/flash.c create mode 100644 drivers/media/video/msm/imx072.c create mode 100644 drivers/media/video/msm/imx072.h create mode 100644 drivers/media/video/msm/imx072_reg.c create mode 100644 drivers/media/video/msm/imx074.c create mode 100644 drivers/media/video/msm/imx074.h create mode 100644 drivers/media/video/msm/imx074_reg.c create mode 100644 drivers/media/video/msm/imx074_v4l2.c create mode 100644 drivers/media/video/msm/msm.c create mode 100644 drivers/media/video/msm/msm.h create mode 100644 drivers/media/video/msm/msm_axi_qos.c create mode 100644 drivers/media/video/msm/msm_camera.c create mode 100644 drivers/media/video/msm/msm_gemini_common.h create mode 100644 drivers/media/video/msm/msm_gemini_core.c create mode 100644 drivers/media/video/msm/msm_gemini_core.h create mode 100644 drivers/media/video/msm/msm_gemini_dev.c create mode 100644 drivers/media/video/msm/msm_gemini_hw.c create mode 100644 drivers/media/video/msm/msm_gemini_hw.h create mode 100644 drivers/media/video/msm/msm_gemini_hw_reg.h create mode 100644 drivers/media/video/msm/msm_gemini_platform.c create mode 100644 drivers/media/video/msm/msm_gemini_platform.h create mode 100644 drivers/media/video/msm/msm_gemini_sync.c create mode 100644 drivers/media/video/msm/msm_gemini_sync.h create mode 100644 drivers/media/video/msm/msm_io7x.c create mode 100644 drivers/media/video/msm/msm_io8x.c create mode 100644 drivers/media/video/msm/msm_io_7x27a.c create mode 100644 drivers/media/video/msm/msm_io_8960.c create mode 100644 drivers/media/video/msm/msm_io_8x60.c create mode 100644 drivers/media/video/msm/msm_io_vfe31.c create mode 100644 drivers/media/video/msm/msm_isp.c create mode 100644 drivers/media/video/msm/msm_ispif.c create mode 100644 drivers/media/video/msm/msm_ispif.h create mode 100644 drivers/media/video/msm/msm_mctl.c create mode 100644 drivers/media/video/msm/msm_mem.c create mode 100644 drivers/media/video/msm/msm_sensor.c create mode 100644 drivers/media/video/msm/msm_sensor.h create mode 100644 drivers/media/video/msm/msm_vfe31.c create mode 100644 drivers/media/video/msm/msm_vfe31.h create mode 100644 drivers/media/video/msm/msm_vfe32.c create mode 100644 drivers/media/video/msm/msm_vfe32.h create mode 100644 drivers/media/video/msm/msm_vfe7x.c create mode 100644 drivers/media/video/msm/msm_vfe7x.h create mode 100644 drivers/media/video/msm/msm_vfe7x27a.c create mode 100644 drivers/media/video/msm/msm_vfe7x27a.h create mode 100644 drivers/media/video/msm/msm_vfe8x.c create mode 100644 drivers/media/video/msm/msm_vfe8x.h create mode 100644 drivers/media/video/msm/msm_vfe8x_proc.c create mode 100644 drivers/media/video/msm/msm_vfe8x_proc.h create mode 100644 drivers/media/video/msm/msm_vpe1.c create mode 100644 drivers/media/video/msm/msm_vpe1.h create mode 100644 drivers/media/video/msm/mt9d112.c create mode 100644 drivers/media/video/msm/mt9d112.h create mode 100644 drivers/media/video/msm/mt9d112_reg.c create mode 100644 drivers/media/video/msm/mt9d113.c create mode 100644 drivers/media/video/msm/mt9d113.h create mode 100644 drivers/media/video/msm/mt9d113_reg.c create mode 100644 drivers/media/video/msm/mt9e013.c create mode 100644 drivers/media/video/msm/mt9e013.h create mode 100644 drivers/media/video/msm/mt9e013_reg.c create mode 100644 drivers/media/video/msm/mt9p012.h create mode 100644 drivers/media/video/msm/mt9p012_bam.c create mode 100644 drivers/media/video/msm/mt9p012_fox.c create mode 100644 drivers/media/video/msm/mt9p012_km.c create mode 100644 drivers/media/video/msm/mt9p012_km.h create mode 100644 drivers/media/video/msm/mt9p012_km_reg.c create mode 100644 drivers/media/video/msm/mt9p012_reg.c create mode 100644 drivers/media/video/msm/mt9t013.c create mode 100644 drivers/media/video/msm/mt9t013.h create mode 100644 drivers/media/video/msm/mt9t013_reg.c create mode 100644 drivers/media/video/msm/ov2720.c create mode 100644 drivers/media/video/msm/ov2720.h create mode 100644 drivers/media/video/msm/ov2720_reg.c create mode 100644 drivers/media/video/msm/ov7692.c create mode 100644 drivers/media/video/msm/ov7692.h create mode 100644 drivers/media/video/msm/ov9726.c create mode 100644 drivers/media/video/msm/ov9726.h create mode 100644 drivers/media/video/msm/ov9726_reg.c create mode 100644 drivers/media/video/msm/qs_s5k4e1.c create mode 100644 drivers/media/video/msm/qs_s5k4e1.h create mode 100644 drivers/media/video/msm/qs_s5k4e1_reg.c create mode 100644 drivers/media/video/msm/s5k3e2fx.c create mode 100644 drivers/media/video/msm/s5k3e2fx.h create mode 100644 drivers/media/video/msm/s5k4e1.c create mode 100644 drivers/media/video/msm/s5k4e1.h create mode 100644 drivers/media/video/msm/s5k4e1_reg.c create mode 100644 drivers/media/video/msm/sn12m0pz.c create mode 100644 drivers/media/video/msm/sn12m0pz.h create mode 100644 drivers/media/video/msm/sn12m0pz_reg.c create mode 100644 drivers/media/video/msm/vb6801.c create mode 100644 drivers/media/video/msm/vb6801.h create mode 100644 drivers/media/video/msm/vx6953.c create mode 100644 drivers/media/video/msm/vx6953.h create mode 100644 drivers/media/video/msm/vx6953_reg.c create mode 100644 drivers/media/video/msm/vx6953_reg_v4l2.c create mode 100644 drivers/media/video/msm/vx6953_v4l2.c create mode 100644 drivers/media/video/msm/vx6953_v4l2.h create mode 100644 drivers/media/video/videobuf-msm-mem.c create mode 100644 drivers/mfd/marimba-codec.c create mode 100644 drivers/mfd/marimba-core.c create mode 100644 drivers/mfd/marimba-tsadc.c create mode 100644 drivers/mfd/msm-adie-codec.c create mode 100644 drivers/mfd/msmproc_adc.c create mode 100644 drivers/mfd/pm8921-adc.c create mode 100644 drivers/mfd/pm8xxx-batt-alarm.c create mode 100644 drivers/mfd/pm8xxx-debug.c create mode 100644 drivers/mfd/pm8xxx-misc.c create mode 100644 drivers/mfd/pm8xxx-pwm.c create mode 100644 drivers/mfd/pmic8058.c create mode 100644 drivers/mfd/pmic8901.c create mode 100644 drivers/mfd/timpani-codec.c create mode 100644 drivers/mfd/tps65023.c create mode 100644 drivers/mfd/wcd9310-core.c create mode 100644 drivers/mfd/wcd9310-irq.c create mode 100644 drivers/misc/isa1200.c create mode 100644 drivers/misc/msm_migrate_pages.c create mode 100644 drivers/misc/pmic8058-batt-alarm.c create mode 100644 drivers/misc/pmic8058-misc.c create mode 100644 drivers/misc/pmic8058-nfc.c create mode 100644 drivers/misc/pmic8058-pwm.c create mode 100644 drivers/misc/pmic8058-upl.c create mode 100644 drivers/misc/pmic8058-vibrator.c create mode 100644 drivers/misc/pmic8058-xoadc.c create mode 100644 drivers/misc/qfp_fuse.c create mode 100644 drivers/misc/tsif.c create mode 100644 drivers/misc/tsif_chrdev.c create mode 100644 drivers/misc/tzcom.c create mode 100644 drivers/misc/tzcomi.h create mode 100644 drivers/mmc/host/msm_sdcc_dml.c create mode 100644 drivers/mmc/host/msm_sdcc_dml.h create mode 100644 drivers/mtd/devices/msm_nand.c create mode 100644 drivers/mtd/devices/msm_nand.h create mode 100644 drivers/mtd/tests/mtd_erasepart.c create mode 100644 drivers/net/msm_rmnet.c create mode 100644 drivers/net/msm_rmnet_bam.c create mode 100644 drivers/net/msm_rmnet_sdio.c create mode 100644 drivers/net/qfec.c create mode 100644 drivers/net/qfec.h create mode 100644 drivers/net/wireless/bcm4329/bcmspibrcm.c create mode 100644 drivers/net/wireless/libra/Makefile create mode 100644 drivers/net/wireless/libra/libra_sdioif.c create mode 100644 drivers/net/wireless/libra/qcomwlan7x27a_pwrif.c create mode 100644 drivers/net/wireless/libra/qcomwlan_pwrif.c create mode 100644 drivers/net/wireless/wcnss/Makefile create mode 100644 drivers/net/wireless/wcnss/qcomwlan_secif.c create mode 100644 drivers/net/wireless/wcnss/wcnss_riva.c create mode 100644 drivers/net/wireless/wcnss/wcnss_riva.h create mode 100644 drivers/net/wireless/wcnss/wcnss_wlan.c create mode 100644 drivers/platform/msm/Kconfig create mode 100644 drivers/platform/msm/Makefile create mode 100755 drivers/platform/msm/sps/Makefile create mode 100644 drivers/platform/msm/sps/bam.c create mode 100644 drivers/platform/msm/sps/bam.h create mode 100644 drivers/platform/msm/sps/sps.c create mode 100644 drivers/platform/msm/sps/sps_bam.c create mode 100644 drivers/platform/msm/sps/sps_bam.h create mode 100644 drivers/platform/msm/sps/sps_core.h create mode 100644 drivers/platform/msm/sps/sps_dma.c create mode 100644 drivers/platform/msm/sps/sps_map.c create mode 100644 drivers/platform/msm/sps/sps_map.h create mode 100644 drivers/platform/msm/sps/sps_mem.c create mode 100644 drivers/platform/msm/sps/sps_rm.c create mode 100644 drivers/platform/msm/sps/spsi.h create mode 100644 drivers/platform/msm/ssbi.c create mode 100644 drivers/power/bq27520_fuelgauger.c create mode 100644 drivers/power/bq27541_fuelgauger.c create mode 100644 drivers/power/isl9519q.c create mode 100644 drivers/power/msm_battery.c create mode 100644 drivers/power/msm_charger.c create mode 100644 drivers/power/pm8058_usb_fix.c create mode 100644 drivers/power/pm8921-bms.c create mode 100644 drivers/power/pm8921-charger.c create mode 100644 drivers/power/pmic8058-charger.c create mode 100644 drivers/power/qci_battery.c create mode 100644 drivers/power/qci_battery.h create mode 100644 drivers/power/smb137b.c create mode 100644 drivers/regulator/gpio-regulator.c create mode 100644 drivers/regulator/pm8058-xo.c create mode 100644 drivers/regulator/pm8921-regulator.c create mode 100644 drivers/regulator/pmic8058-regulator.c create mode 100644 drivers/regulator/pmic8901-regulator.c create mode 100644 drivers/rtc/rtc-msm.c create mode 100644 drivers/rtc/rtc-msm7x00a.c create mode 100644 drivers/rtc/rtc-pm8058.c create mode 100644 drivers/rtc/rtc-pm8xxx.c create mode 100644 drivers/slimbus/Kconfig create mode 100644 drivers/slimbus/Makefile create mode 100644 drivers/slimbus/slim-msm-ctrl.c create mode 100644 drivers/slimbus/slimbus.c create mode 100644 drivers/spi/spi_qsd.c create mode 100644 drivers/staging/ath6kl/os/linux/include/athendpack_linux.h create mode 100644 drivers/staging/ath6kl/os/linux/include/athstartpack_linux.h create mode 100644 drivers/staging/dream/Kconfig create mode 100644 drivers/staging/dream/Makefile create mode 100644 drivers/staging/gobi/Kconfig create mode 100755 drivers/staging/gobi/QCUSBNet2k/Makefile create mode 100644 drivers/staging/gobi/QCUSBNet2k/QCUSBNet.c create mode 100644 drivers/staging/gobi/QCUSBNet2k/QMI.c create mode 100644 drivers/staging/gobi/QCUSBNet2k/QMI.h create mode 100644 drivers/staging/gobi/QCUSBNet2k/QMIDevice.c create mode 100644 drivers/staging/gobi/QCUSBNet2k/QMIDevice.h create mode 100644 drivers/staging/gobi/QCUSBNet2k/Structs.h create mode 100644 drivers/staging/msm/lcdc_st1_wxga.c create mode 100644 drivers/staging/msm/lcdc_wxga.c create mode 100644 drivers/staging/msm/mddi_toshiba_wvga.c create mode 100644 drivers/thermal/msm_popmem-tm.c create mode 100644 drivers/thermal/msm_tsens.c create mode 100644 drivers/thermal/pm8xxx-tm.c create mode 100644 drivers/thermal/pmic8058-tm.c create mode 100644 drivers/thermal/pmic8901-tm.c create mode 100644 drivers/tty/serial/msm_serial_debugger.c create mode 100644 drivers/tty/serial/msm_serial_hs_hwreg.h create mode 100644 drivers/tty/serial/msm_serial_hs_lite.c create mode 100644 drivers/usb/function/Kconfig create mode 100644 drivers/usb/function/Makefile create mode 100644 drivers/usb/function/adb.c create mode 100644 drivers/usb/function/diag.c create mode 100644 drivers/usb/function/ether.c create mode 100644 drivers/usb/function/ether_cdc_ecm.c create mode 100644 drivers/usb/function/loopback.c create mode 100644 drivers/usb/function/mass_storage.c create mode 100644 drivers/usb/function/msm_hsusb.c create mode 100644 drivers/usb/function/msm_hsusb_hw.h create mode 100644 drivers/usb/function/msm_otg.c create mode 100644 drivers/usb/function/null.c create mode 100644 drivers/usb/function/rmnet.c create mode 100644 drivers/usb/function/serial.c create mode 100644 drivers/usb/function/ums.c create mode 100644 drivers/usb/function/usb_function.h create mode 100644 drivers/usb/function/zero.c create mode 100644 drivers/usb/gadget/f_diag.c create mode 100644 drivers/usb/gadget/f_diag.h create mode 100644 drivers/usb/gadget/f_rmnet.c create mode 100644 drivers/usb/gadget/f_rmnet.h create mode 100644 drivers/usb/gadget/f_rmnet_sdio.c create mode 100644 drivers/usb/gadget/f_rmnet_smd.c create mode 100644 drivers/usb/gadget/f_rmnet_smd_sdio.c create mode 100644 drivers/usb/gadget/msm72k_udc.c create mode 100644 drivers/usb/gadget/qcom_maemo.c create mode 100644 drivers/usb/gadget/u_bam.c create mode 100644 drivers/usb/gadget/u_rmnet.h create mode 100644 drivers/usb/gadget/u_rmnet_ctrl_smd.c create mode 100644 drivers/usb/gadget/u_sdio.c create mode 100644 drivers/usb/gadget/u_smd.c create mode 100644 drivers/usb/host/ehci-msm72k.c create mode 100644 drivers/usb/host/pehci/Makefile create mode 100644 drivers/usb/host/pehci/hal/Makefile create mode 100644 drivers/usb/host/pehci/hal/hal_intf.h create mode 100644 drivers/usb/host/pehci/hal/hal_msm.c create mode 100644 drivers/usb/host/pehci/hal/hal_msm.h create mode 100644 drivers/usb/host/pehci/hal/isp1763.h create mode 100644 drivers/usb/host/pehci/host/Makefile create mode 100644 drivers/usb/host/pehci/host/itdptd.c create mode 100644 drivers/usb/host/pehci/host/mem.c create mode 100755 drivers/usb/host/pehci/host/otg.c create mode 100644 drivers/usb/host/pehci/host/pehci.c create mode 100644 drivers/usb/host/pehci/host/pehci.h create mode 100644 drivers/usb/host/pehci/host/qtdptd.c create mode 100644 drivers/usb/misc/ehset.c create mode 100644 drivers/usb/otg/msm72k_otg.c create mode 100644 drivers/video/msm/Kconfig create mode 100644 drivers/video/msm/adv7520.c create mode 100644 drivers/video/msm/ebi2_l2f.c create mode 100644 drivers/video/msm/ebi2_lcd.c create mode 100644 drivers/video/msm/ebi2_tmd20.c create mode 100644 drivers/video/msm/external_common.c create mode 100644 drivers/video/msm/external_common.h create mode 100644 drivers/video/msm/hdmi_msm.c create mode 100644 drivers/video/msm/hdmi_msm.h create mode 100644 drivers/video/msm/hdmi_sii9022.c create mode 100644 drivers/video/msm/lcdc.c create mode 100644 drivers/video/msm/lcdc_auo_wvga.c create mode 100644 drivers/video/msm/lcdc_chimei_wxga.c create mode 100644 drivers/video/msm/lcdc_external.c create mode 100644 drivers/video/msm/lcdc_gordon.c create mode 100644 drivers/video/msm/lcdc_panel.c create mode 100644 drivers/video/msm/lcdc_prism.c create mode 100644 drivers/video/msm/lcdc_samsung_oled_pt.c create mode 100644 drivers/video/msm/lcdc_samsung_wsvga.c create mode 100644 drivers/video/msm/lcdc_sharp_wvga_pt.c create mode 100644 drivers/video/msm/lcdc_st15.c create mode 100644 drivers/video/msm/lcdc_toshiba_fwvga_pt.c create mode 100644 drivers/video/msm/lcdc_toshiba_wvga_pt.c create mode 100644 drivers/video/msm/lcdc_wxga.c create mode 100644 drivers/video/msm/logo.c create mode 100644 drivers/video/msm/mddi_ext.c create mode 100644 drivers/video/msm/mddi_ext_lcd.c create mode 100644 drivers/video/msm/mddi_orise.c create mode 100644 drivers/video/msm/mddi_prism.c create mode 100644 drivers/video/msm/mddi_quickvx.c create mode 100644 drivers/video/msm/mddi_sharp.c create mode 100644 drivers/video/msm/mddi_toshiba.c create mode 100644 drivers/video/msm/mddi_toshiba.h create mode 100644 drivers/video/msm/mddi_toshiba_vga.c create mode 100644 drivers/video/msm/mddi_toshiba_wvga.c create mode 100644 drivers/video/msm/mddi_toshiba_wvga_pt.c create mode 100644 drivers/video/msm/mddihost.c create mode 100644 drivers/video/msm/mddihost.h create mode 100644 drivers/video/msm/mddihost_e.c create mode 100644 drivers/video/msm/mddihosti.c create mode 100644 drivers/video/msm/mddihosti.h create mode 100644 drivers/video/msm/mdp.h create mode 100644 drivers/video/msm/mdp4.h create mode 100644 drivers/video/msm/mdp4_dtv.c create mode 100644 drivers/video/msm/mdp4_overlay.c create mode 100644 drivers/video/msm/mdp4_overlay_atv.c create mode 100644 drivers/video/msm/mdp4_overlay_dsi_cmd.c create mode 100644 drivers/video/msm/mdp4_overlay_dsi_video.c create mode 100644 drivers/video/msm/mdp4_overlay_dtv.c create mode 100644 drivers/video/msm/mdp4_overlay_lcdc.c create mode 100644 drivers/video/msm/mdp4_overlay_mddi.c create mode 100644 drivers/video/msm/mdp4_util.c create mode 100644 drivers/video/msm/mdp_cursor.c create mode 100644 drivers/video/msm/mdp_debugfs.c create mode 100644 drivers/video/msm/mdp_dma.c create mode 100644 drivers/video/msm/mdp_dma_dsi_video.c create mode 100644 drivers/video/msm/mdp_dma_lcdc.c create mode 100644 drivers/video/msm/mdp_dma_s.c create mode 100644 drivers/video/msm/mdp_dma_tv.c create mode 100644 drivers/video/msm/mdp_hw40.c create mode 100644 drivers/video/msm/mdp_hw_init.c create mode 100644 drivers/video/msm/mdp_lcdc.c create mode 100644 drivers/video/msm/mdp_ppp.h create mode 100644 drivers/video/msm/mdp_ppp22.c create mode 100644 drivers/video/msm/mdp_ppp31.c create mode 100644 drivers/video/msm/mdp_ppp_v20.c create mode 100644 drivers/video/msm/mdp_ppp_v31.c create mode 100644 drivers/video/msm/mdp_vsync.c create mode 100644 drivers/video/msm/mipi_dsi.c create mode 100644 drivers/video/msm/mipi_dsi.h create mode 100644 drivers/video/msm/mipi_dsi_host.c create mode 100644 drivers/video/msm/mipi_novatek.c create mode 100644 drivers/video/msm/mipi_novatek.h create mode 100644 drivers/video/msm/mipi_novatek_cmd_qhd_pt.c create mode 100644 drivers/video/msm/mipi_novatek_video_qhd_pt.c create mode 100644 drivers/video/msm/mipi_renesas.c create mode 100644 drivers/video/msm/mipi_renesas.h create mode 100644 drivers/video/msm/mipi_renesas_cmd_fwvga_pt.c create mode 100644 drivers/video/msm/mipi_renesas_video_fwvga_pt.c create mode 100644 drivers/video/msm/mipi_simulator.c create mode 100644 drivers/video/msm/mipi_simulator.h create mode 100644 drivers/video/msm/mipi_simulator_video.c create mode 100644 drivers/video/msm/mipi_toshiba.c create mode 100644 drivers/video/msm/mipi_toshiba.h create mode 100644 drivers/video/msm/mipi_toshiba_video_wsvga_pt.c create mode 100644 drivers/video/msm/mipi_toshiba_video_wvga_pt.c create mode 100644 drivers/video/msm/msm_dss_io_7x27a.c create mode 100644 drivers/video/msm/msm_dss_io_8960.c create mode 100644 drivers/video/msm/msm_dss_io_8x60.c create mode 100644 drivers/video/msm/msm_fb.h create mode 100644 drivers/video/msm/msm_fb_bl.c create mode 100644 drivers/video/msm/msm_fb_def.h create mode 100644 drivers/video/msm/msm_fb_panel.c create mode 100644 drivers/video/msm/msm_fb_panel.h create mode 100644 drivers/video/msm/tvenc.c create mode 100644 drivers/video/msm/tvenc.h create mode 100644 drivers/video/msm/tvout_msm.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl.h create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_api.h create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_core.h create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_errors.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_helper.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_interrupt_handler.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_metadata.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_metadata.h create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_properties.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.h create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.h create mode 100644 drivers/video/msm/vidc/1080p/ddl/vcd_ddl_vidc.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vidc.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vidc.h create mode 100644 drivers/video/msm/vidc/1080p/ddl/vidc_hwio.h create mode 100644 drivers/video/msm/vidc/1080p/ddl/vidc_hwio_reg.h create mode 100644 drivers/video/msm/vidc/1080p/ddl/vidc_pix_cache.c create mode 100644 drivers/video/msm/vidc/1080p/ddl/vidc_pix_cache.h create mode 100644 drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.c create mode 100644 drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.h create mode 100644 drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker_api.h create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl.h create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_api.h create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_core.h create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_errors.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.h create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_hal.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_helper.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_internal_property.h create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_interrupt_handler.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_metadata.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_metadata.h create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_properties.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_utils.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vcd_ddl_utils.h create mode 100644 drivers/video/msm/vidc/720p/ddl/vidc.c create mode 100644 drivers/video/msm/vidc/720p/ddl/vidc.h create mode 100644 drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.c create mode 100644 drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.h create mode 100644 drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker_api.h create mode 100644 drivers/video/msm/vidc/Kconfig create mode 100644 drivers/video/msm/vidc/Makefile create mode 100644 drivers/video/msm/vidc/common/dec/vdec.c create mode 100644 drivers/video/msm/vidc/common/dec/vdec_internal.h create mode 100644 drivers/video/msm/vidc/common/enc/venc.c create mode 100644 drivers/video/msm/vidc/common/enc/venc_internal.c create mode 100644 drivers/video/msm/vidc/common/enc/venc_internal.h create mode 100644 drivers/video/msm/vidc/common/init/vidc_init.c create mode 100644 drivers/video/msm/vidc/common/init/vidc_init.h create mode 100644 drivers/video/msm/vidc/common/init/vidc_init_internal.h create mode 100644 drivers/video/msm/vidc/common/vcd/vcd.h create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_api.c create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_api.h create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_client_sm.c create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_client_sm.h create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_core.h create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_device_sm.c create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_device_sm.h create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_power_sm.c create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_power_sm.h create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_property.h create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_scheduler.c create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_status.h create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_sub.c create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_util.c create mode 100644 drivers/video/msm/vidc/common/vcd/vcd_util.h create mode 100644 drivers/video/msm/vidc/common/vcd/vidc_type.h create mode 100644 include/drm/kgsl_drm.h create mode 100644 include/linux/adv7520.h create mode 100644 include/linux/atmel_maxtouch.h create mode 100644 include/linux/bma150.h create mode 100644 include/linux/csdio.h create mode 100644 include/linux/cyttsp.h create mode 100644 include/linux/diagchar.h create mode 100644 include/linux/fsm_dfe_hh.h create mode 100644 include/linux/fsm_rfic_ftr.h create mode 100644 include/linux/gpio-pm8xxx-rpc.h create mode 100644 include/linux/i2c/bq27520.h create mode 100644 include/linux/i2c/isa1200.h create mode 100644 include/linux/i2c/isl9519.h create mode 100644 include/linux/i2c/smb137b.h create mode 100644 include/linux/input/cy8c_ts.h create mode 100644 include/linux/input/kp_flip_switch.h create mode 100644 include/linux/input/msm_ts.h create mode 100644 include/linux/input/pmic8058-keypad.h create mode 100644 include/linux/input/qci_kbd.h create mode 100644 include/linux/input/tdisc_shinetsu.h create mode 100644 include/linux/leds-pm8xxx.h create mode 100644 include/linux/leds-pmic8058.h create mode 100644 include/linux/libra_sdioif.h create mode 100644 include/linux/m_adcproc.h create mode 100644 include/linux/memory_alloc.h create mode 100644 include/linux/mfd/Kbuild create mode 100644 include/linux/mfd/marimba-codec.h create mode 100644 include/linux/mfd/marimba-tsadc.h create mode 100644 include/linux/mfd/marimba.h create mode 100644 include/linux/mfd/msm-adie-codec.h create mode 100644 include/linux/mfd/pm8921-adc.h create mode 100644 include/linux/mfd/pm8xxx/batt-alarm.h create mode 100644 include/linux/mfd/pm8xxx/gpio.h create mode 100644 include/linux/mfd/pm8xxx/misc.h create mode 100644 include/linux/mfd/pm8xxx/mpp.h create mode 100644 include/linux/mfd/pm8xxx/pm8921-bms.h create mode 100644 include/linux/mfd/pm8xxx/pm8921-charger.h create mode 100644 include/linux/mfd/pm8xxx/pwm.h create mode 100644 include/linux/mfd/pm8xxx/rtc.h create mode 100644 include/linux/mfd/pm8xxx/tm.h create mode 100644 include/linux/mfd/pmic8058.h create mode 100644 include/linux/mfd/pmic8901.h create mode 100644 include/linux/mfd/timpani-audio.h create mode 100644 include/linux/mfd/tps65023.h create mode 100644 include/linux/mfd/wcd9310/core.h create mode 100644 include/linux/mfd/wcd9310/pdata.h create mode 100644 include/linux/mfd/wcd9310/registers.h create mode 100644 include/linux/msm-charger.h create mode 100644 include/linux/msm_adc.h create mode 100644 include/linux/msm_adsp.h create mode 100644 include/linux/msm_audio.h create mode 100644 include/linux/msm_audio_aac.h create mode 100644 include/linux/msm_audio_acdb.h create mode 100644 include/linux/msm_audio_amrnb.h create mode 100644 include/linux/msm_audio_mvs.h create mode 100644 include/linux/msm_audio_qcp.h create mode 100644 include/linux/msm_audio_sbc.h create mode 100644 include/linux/msm_audio_voicememo.h create mode 100644 include/linux/msm_audio_wma.h create mode 100644 include/linux/msm_audio_wmapro.h create mode 100644 include/linux/msm_charm.h create mode 100644 include/linux/msm_dsps.h create mode 100644 include/linux/msm_ipc.h create mode 100644 include/linux/msm_kgsl.h create mode 100644 include/linux/msm_q6vdec.h create mode 100644 include/linux/msm_q6venc.h create mode 100644 include/linux/msm_rmnet.h create mode 100644 include/linux/msm_rotator.h create mode 100644 include/linux/msm_rpcrouter.h create mode 100644 include/linux/msm_smd_pkt.h create mode 100644 include/linux/msm_ssbi.h create mode 100644 include/linux/msm_vidc_dec.h create mode 100644 include/linux/msm_vidc_enc.h create mode 100644 include/linux/ofn_atlab.h create mode 100644 include/linux/platform_data/qcom_crypto_device.h create mode 100644 include/linux/platform_data/usb_rmnet.h create mode 100644 include/linux/pmic8058-batt-alarm.h create mode 100644 include/linux/pmic8058-charger.h create mode 100644 include/linux/pmic8058-misc.h create mode 100644 include/linux/pmic8058-nfc.h create mode 100644 include/linux/pmic8058-othc.h create mode 100644 include/linux/pmic8058-pwm.h create mode 100644 include/linux/pmic8058-pwrkey.h create mode 100644 include/linux/pmic8058-upl.h create mode 100644 include/linux/pmic8058-vibrator.h create mode 100644 include/linux/pmic8058-xoadc.h create mode 100644 include/linux/qcomwlan7x27a_pwrif.h create mode 100644 include/linux/qcomwlan_pwrif.h create mode 100644 include/linux/qcomwlan_secif.h create mode 100644 include/linux/qcota.h create mode 100644 include/linux/qfp_fuse.h create mode 100644 include/linux/regulator/gpio-regulator.h create mode 100644 include/linux/regulator/pm8058-xo.h create mode 100644 include/linux/regulator/pm8921-regulator.h create mode 100644 include/linux/regulator/pmic8058-regulator.h create mode 100644 include/linux/regulator/pmic8901-regulator.h create mode 100644 include/linux/remote_spinlock.h create mode 100644 include/linux/rmt_storage_client.h create mode 100644 include/linux/rtc-msm.h create mode 100644 include/linux/rtc/rtc-pm8058.h create mode 100644 include/linux/slimbus/slimbus.h create mode 100644 include/linux/tsif_api.h create mode 100644 include/linux/tzcom.h create mode 100644 include/linux/usb/android.h create mode 100644 include/linux/usb/android_composite.h create mode 100644 include/linux/vcm.h create mode 100644 include/linux/vcm_alloc.h create mode 100644 include/linux/vcm_mm.h create mode 100644 include/linux/vcm_types.h create mode 100644 include/linux/wcnss_wlan.h create mode 100644 include/linux/wpce775x.h create mode 100644 include/media/Kbuild create mode 100644 include/media/msm_camera.h create mode 100644 include/media/msm_gemini.h create mode 100644 include/media/radio-iris.h create mode 100644 include/media/tavarua.h create mode 100644 include/media/videobuf-msm-mem.h create mode 100644 include/net/bluetooth/amp.h create mode 100644 include/sound/apr_audio.h create mode 100644 include/sound/dai.h create mode 100644 include/sound/q6adm.h create mode 100644 include/sound/q6afe.h create mode 100644 include/sound/q6asm.h create mode 100644 lib/memory_alloc.c create mode 100644 net/bluetooth/amp.c create mode 100755 scripts/build-all.py create mode 100755 scripts/gcc-wrapper.py create mode 100644 sound/soc/codecs/msm_stub.c create mode 100644 sound/soc/codecs/timpani.c create mode 100644 sound/soc/codecs/timpani.h create mode 100644 sound/soc/codecs/wcd9310-tables.c create mode 100644 sound/soc/codecs/wcd9310.c create mode 100644 sound/soc/codecs/wcd9310.h create mode 100644 sound/soc/msm/Kconfig create mode 100644 sound/soc/msm/Makefile create mode 100644 sound/soc/msm/msm-dai-fe.c create mode 100644 sound/soc/msm/msm-dai-q6.c create mode 100644 sound/soc/msm/msm-dai.c create mode 100644 sound/soc/msm/msm-mvs.c create mode 100644 sound/soc/msm/msm-pcm-hostless.c create mode 100644 sound/soc/msm/msm-pcm-lpa.c create mode 100644 sound/soc/msm/msm-pcm-q6.c create mode 100644 sound/soc/msm/msm-pcm-q6.h create mode 100644 sound/soc/msm/msm-pcm-routing.c create mode 100644 sound/soc/msm/msm-pcm-routing.h create mode 100644 sound/soc/msm/msm-pcm-voice.c create mode 100644 sound/soc/msm/msm-pcm-voice.h create mode 100644 sound/soc/msm/msm-pcm-voip.c create mode 100644 sound/soc/msm/msm-pcm.c create mode 100644 sound/soc/msm/msm-pcm.h create mode 100644 sound/soc/msm/msm-voip.c create mode 100644 sound/soc/msm/msm7201.c create mode 100644 sound/soc/msm/msm7k-pcm.c create mode 100644 sound/soc/msm/msm7kv2-dai.c create mode 100644 sound/soc/msm/msm7kv2-dsp.c create mode 100644 sound/soc/msm/msm7kv2-pcm.c create mode 100644 sound/soc/msm/msm7kv2-pcm.h create mode 100644 sound/soc/msm/msm7x30.c create mode 100644 sound/soc/msm/msm8660-dma.c create mode 100644 sound/soc/msm/msm8660-i2s.c create mode 100644 sound/soc/msm/msm8660-pcm.c create mode 100644 sound/soc/msm/msm8660-pcm.h create mode 100644 sound/soc/msm/msm8660.c create mode 100644 sound/soc/msm/msm8960.c create mode 100644 sound/soc/msm/msm8x60-dai.c create mode 100644 sound/soc/msm/msm8x60-pcm.c create mode 100644 sound/soc/msm/msm8x60-pcm.h create mode 100644 sound/soc/msm/msm8x60.c create mode 100644 sound/soc/msm/msm_audio_mvs.h create mode 100644 sound/soc/msm/mvs-dai.c create mode 100644 sound/soc/msm/qdsp6/Makefile create mode 100644 sound/soc/msm/qdsp6/q6adm.c create mode 100644 sound/soc/msm/qdsp6/q6afe.c create mode 100644 sound/soc/msm/qdsp6/q6asm.c create mode 100644 sound/soc/msm/qdsp6/q6voice.c create mode 100644 sound/soc/msm/qdsp6/q6voice.h diff --git a/AndroidKernel.mk b/AndroidKernel.mk new file mode 100644 index 00000000000..dcb81d72d65 --- /dev/null +++ b/AndroidKernel.mk @@ -0,0 +1,64 @@ +#Android makefile to build kernel as a part of Android Build + +ifeq ($(TARGET_PREBUILT_KERNEL),) + +KERNEL_OUT := $(TARGET_OUT_INTERMEDIATES)/KERNEL_OBJ +KERNEL_CONFIG := $(KERNEL_OUT)/.config +TARGET_PREBUILT_INT_KERNEL := $(KERNEL_OUT)/arch/arm/boot/zImage +KERNEL_HEADERS_INSTALL := $(KERNEL_OUT)/usr +KERNEL_MODULES_INSTALL := system +KERNEL_MODULES_OUT := $(TARGET_OUT)/lib/modules + +ifeq ($(TARGET_USES_UNCOMPRESSED_KERNEL),true) +$(info Using uncompressed kernel) +TARGET_PREBUILT_KERNEL := $(KERNEL_OUT)/piggy +else +TARGET_PREBUILT_KERNEL := $(TARGET_PREBUILT_INT_KERNEL) +endif + +define mv-modules +mdpath=`find $(KERNEL_MODULES_OUT) -type f -name modules.dep`;\ +if [ "$$mdpath" != "" ];then\ +mpath=`dirname $$mdpath`;\ +ko=`find $$mpath/kernel -type f -name *.ko`;\ +for i in $$ko; do mv $$i $(KERNEL_MODULES_OUT)/; done;\ +fi +endef + +define clean-module-folder +mdpath=`find $(KERNEL_MODULES_OUT) -type f -name modules.dep`;\ +if [ "$$mdpath" != "" ];then\ +mpath=`dirname $$mdpath`; rm -rf $$mpath;\ +fi +endef + +$(KERNEL_OUT): + mkdir -p $(KERNEL_OUT) + +$(KERNEL_CONFIG): $(KERNEL_OUT) + $(MAKE) -C kernel O=../$(KERNEL_OUT) ARCH=arm CROSS_COMPILE=arm-eabi- $(KERNEL_DEFCONFIG) + +$(KERNEL_OUT)/piggy : $(TARGET_PREBUILT_INT_KERNEL) + $(hide) gunzip -c $(KERNEL_OUT)/arch/arm/boot/compressed/piggy.gzip > $(KERNEL_OUT)/piggy + +$(TARGET_PREBUILT_INT_KERNEL): $(KERNEL_OUT) $(KERNEL_CONFIG) $(KERNEL_HEADERS_INSTALL) + $(MAKE) -C kernel O=../$(KERNEL_OUT) ARCH=arm CROSS_COMPILE=arm-eabi- + $(MAKE) -C kernel O=../$(KERNEL_OUT) ARCH=arm CROSS_COMPILE=arm-eabi- modules + $(MAKE) -C kernel O=../$(KERNEL_OUT) INSTALL_MOD_PATH=../../$(KERNEL_MODULES_INSTALL) ARCH=arm CROSS_COMPILE=arm-eabi- modules_install + $(mv-modules) + $(clean-module-folder) + +$(KERNEL_HEADERS_INSTALL): $(KERNEL_OUT) $(KERNEL_CONFIG) + $(MAKE) -C kernel O=../$(KERNEL_OUT) ARCH=arm CROSS_COMPILE=arm-eabi- headers_install + +kerneltags: $(KERNEL_OUT) $(KERNEL_CONFIG) + $(MAKE) -C kernel O=../$(KERNEL_OUT) ARCH=arm CROSS_COMPILE=arm-eabi- tags + +kernelconfig: $(KERNEL_OUT) $(KERNEL_CONFIG) + env KCONFIG_NOTIMESTAMP=true \ + $(MAKE) -C kernel O=../$(KERNEL_OUT) ARCH=arm CROSS_COMPILE=arm-eabi- menuconfig + env KCONFIG_NOTIMESTAMP=true \ + $(MAKE) -C kernel O=../$(KERNEL_OUT) ARCH=arm CROSS_COMPILE=arm-eabi- savedefconfig + cp $(KERNEL_OUT)/defconfig kernel/arch/arm/configs/$(KERNEL_DEFCONFIG) + +endif diff --git a/Documentation/DocBook/debugobjects.tmpl b/Documentation/DocBook/debugobjects.tmpl index 08ff908aa7a..24979f691e3 100644 --- a/Documentation/DocBook/debugobjects.tmpl +++ b/Documentation/DocBook/debugobjects.tmpl @@ -96,6 +96,7 @@ debug_object_deactivate debug_object_destroy debug_object_free + debug_object_assert_init Each of these functions takes the address of the real object and a pointer to the object type specific debug description @@ -273,6 +274,26 @@ debug checks. + + + debug_object_assert_init + + This function is called to assert that an object has been + initialized. + + + When the real object is not tracked by debugobjects, it calls + fixup_assert_init of the object type description structure + provided by the caller, with the hardcoded object state + ODEBUG_NOT_AVAILABLE. The fixup function can correct the problem + by calling debug_object_init and other specific initializing + functions. + + + When the real object is already tracked by debugobjects it is + ignored. + + Fixup functions @@ -381,6 +402,35 @@ statistics. + + fixup_assert_init + + This function is called from the debug code whenever a problem + in debug_object_assert_init is detected. + + + Called from debug_object_assert_init() with a hardcoded state + ODEBUG_STATE_NOTAVAILABLE when the object is not found in the + debug bucket. + + + The function returns 1 when the fixup was successful, + otherwise 0. The return value is used to update the + statistics. + + + Note, this function should make sure debug_object_init() is + called before returning. + + + The handling of statically initialized objects is a special + case. The fixup function should check if this is a legitimate + case of a statically initialized object or not. In this case only + debug_object_init() should be called to make the object known to + the tracker. Then the function should return 0 because this is not + a real fixup. + + Known Bugs And Assumptions diff --git a/Documentation/arm/msm/boot.txt b/Documentation/arm/msm/boot.txt new file mode 100644 index 00000000000..1a41cd53202 --- /dev/null +++ b/Documentation/arm/msm/boot.txt @@ -0,0 +1,23 @@ +Introduction +============= +The power management integrated circuit (PMIC) records the reason the +Application processor was powered on in Shared Memory. +The hardware and software used is the shared memory interface. This document +is not for the purpose of describing this interface, but to identify the +possible values for this data item. + +Description +=========== +Shared memory item (SMEM_POWER_ON_STATUS_INFO) is read to get access to +this data. The table below identifies the possible values stored. + +power_on_status values set by the PMIC for power on event: +---------------------------------------------------------- +0x01 -- keyboard power on +0x02 -- RTC alarm +0x04 -- cable power on +0x08 -- SMPL +0x10 -- Watch Dog timeout +0x20 -- USB charger +0x40 -- Wall charger +0xFF -- error reading power_on_status value diff --git a/Documentation/arm/msm/emulate_domain_manager.txt b/Documentation/arm/msm/emulate_domain_manager.txt new file mode 100644 index 00000000000..b0d007ed4c8 --- /dev/null +++ b/Documentation/arm/msm/emulate_domain_manager.txt @@ -0,0 +1,254 @@ +Introduction +============ + +8x50 chipset requires the ability to disable HW domain manager function. + +The ARM MMU architecture has a feature known as domain manager mode. +Briefly each page table, section, or supersection is assigned a domain. +Each domain can be globally configured to NoAccess, Client, or Manager +mode. These global configurations allow the access permissions of the +entire domain to be changed simultaneously. + +The domain manger emulation is required to fix a HW problem on the 8x50 +chipset. The problem is simple to repair except when domain manager mode +is enabled. The emulation allows the problem to be completely resolved. + + +Hardware description +==================== + +When domain manager mode is enabled on a specific domain, the MMU +hardware ignores the access permission bits and the execute never bit. All +accesses, to memory in the domain, are granted full read, write, +execute permissions. + +The mode of each domain is controlled by a field in the cp15 dacr register. +Each domain can be globally configured to NoAccess, Client, or Manager mode. + +See: ARMv7 Architecture Reference Manual + + +Software description +==================== + +In order to disable domain manager mode the equivalent HW functionality must +be emulated in SW. Any attempts to enable domain manager mode, must be +intercepted. + +Because domain manager mode is not enabled, permissions for the +associated domain will remain restricted. Permission faults will be generated. +The permission faults will be intercepted. The faulted pages/sections will +be modified to grant full access and execute permissions. + +The modified page tables must be restored when exiting domain manager mode. + + +Design +====== + +Design Goals: + +Disable Domain Manager Mode +Exact SW emulation of Domain Manager Mode +Minimal Kernel changes +Minimal Security Risk + +Design Decisions: + +Detect kernel page table modifications on restore +Direct ARMv7 HW MMU table manipulation +Restore emulation modified MMU entries on context switch +No need to restore MMU entries for MMU entry copy operations +Invalidate TLB entries on modification +Store Domain Manager bits in memory +8 entry MMU entry cache +Use spin_lock_irqsave to protect domain manipulation +Assume no split MMU table + +Design Discussion: + +Detect kernel page table modifications on restore - +When restoring original page/section permission faults, the submitted design +verifies the MMU entry has not been modified. The kernel modifies MMU +entries for the following purposes : create a memory mapping, release a +memory mapping, add permissions during a permission fault, and map a page +during a translation fault. The submitted design works with the listed +scenarios. The translation fault and permission faults simply do not happen on +relevant entries (valid entries with full access permissions). The alternative +would be to hook every MMU table modification. The alternative greatly +increases complexity and code maintenance issues. + +Direct ARMv7 HW MMU table manipulation - +The natural choice would be to use the kernel provided mechanism to manipulate +MMU page table entries. The ARM MMU interface is described in pgtable.h. +This interface is complicated by the Linux implementation. The level 1 pgd +entries are treated and manipulated as entry pairs. The level 2 entries are +shadowed and cloned. The compromise was chosen to actually use the ARMv7 HW +registers to walk and modify the MMU table entries. The choice limits the +usage of this implementation to ARMv7 and similar ARM MMU architectures. Since +this implementation is targeted at fixing an issue in 8x50 ARMv7, the choice is +logical. The HW manipulation is in distinct low level functions. These could +easily be replaced or generalized to support other architectures as necessary. + +Restore emulation modified MMU entries on context switch - +This additional hook was added to minimize performance impact. By guaranteeing +the ASID will not change during the emulation, the emulation may invalidate each +entry by MVA & ASID. Only the affected page table entries will be removed from +the TLB cache. The performance cost of the invalidate on context switch is near +zero. Typically on context switch the domain mode would also change, forcing a +complete restore of all modified MMU entries. The alternative would be to +invalidate the entire TLB every time a table entry is restored. + +No need to restore MMU entries for copy operations - +Operations which copy MMU entries are relatively rare in the kernel. Because +we modify the level 2 pte entries directly in hardware, the Linux shadow copies +are left untouched. The kernel treats the shadow copies as the primary pte +entry. Any pte copy operations would be unaffected by the HW modification. +On translation section fault, pgd entries are copied from the kernel master +page table to the current thread page table. Since we restore MMU entries on +context switch, we guarantee the master table will not contain modifications, +while faulting on a process local entry. Other read, modify write operations +occur during permission fault handling. Since we open permission on modified +entries, these do not need to be restored, because we guarantee these +permission fault operations will not happen. + +Invalidate TLB entries on modification - +No real choice here. This is more of a design requirement. On permission +fault, the MMU entry with restricted permissions will be in the TLB. To open +access permissions, the TLB entry must be invalidated. Otherwise the access +will permission fault again. Upon restoring original MMU entries, the TLB +must be invalidated to restrict memory access. + +Store Domain Manager bits in memory - +There was only one alternative here. 2.6.29 kernel only uses 3 of 16 +possible domains. Additional bits in dacr could be used to store the +manager bits. This would allow faster access to the manager bits. +Overall this would reduce any performance impact. The performance +needs did not seem to justify the added weirdness. + +8 entry MMU entry cache- +The size of the modified MMU entry cache is somewhat arbitrary. The thought +process is that typically, a thread is using two pointers to perform a copy +operation. In this case only 2 entries would be required. One could imagine +a more complicated operation, a masked copy for instance, which would require +more pointers. 8 pointer seemed to be large enough to minimize risk of +permission fault thrashing. The disadvantage of a larger cache would simply +be a longer list of entries to restore. + +Use spin_lock_irqsave to protect domain manipulation - +The obvious choice. + +Assume no split MMU table - +This same assumption is documented in cpu_v7_switch_mm. + + +Power Management +================ + +Not affected. + + +SMP/multi-core +============== + +SMP/multicore not supported. This is intended as a 8x50 workaround. + + +Security +======== + +MMU page/section permissions must be manipulated correctly to emulate domain +manager mode. If page permission are left in full access mode, any process +can read associated memory. + + +Performance +=========== + +Performance should be impacted only minimally. When emulating domain manager +mode, there is overhead added to MMU table/context switches, set_domain() +calls, data aborts, and prefetch aborts. + +Normally the kernel operates with domain != DOMAIN_MANAGER. In this case the +overhead is minimal. An additional check is required to see if domain manager +mode is on. This minimal code is added to each of emulation entry points : +set, data abort, prefetch abort, and MMU table/context switch. + +Initial accesses to a MMU protected page/section will generate a permission +fault. The page will be manipulated to grant full access permissions and +the access will be retried. This will typically require 2-3 page table +walks. + +On a context switch, all modified MMU entries will be restored. On thread +resume, additional accesses will be treated as initial accesses. + + +Interface +========= + +The emulation does not have clients. It is hooked to the kernel through a +small list of functions. + +void emulate_domain_manager_set(u32 domain); +int emulate_domain_manager_data_abort(u32 dfsr, u32 dfar); +int emulate_domain_manager_prefetch_abort(u32 ifsr, u32 ifar); +void emulate_domain_manager_switch_mm( + unsigned long pgd_phys, + struct mm_struct *mm, + void (*switch_mm)(unsigned long pgd_phys, struct mm_struct *)); + +emulate_domain_manager_set() is the set_domain handler. This replaces the +direct manipulation of CP15 dacr with a function call. This allows emulation +to prevent setting dacr manager bits. It also allows emulation to restore +page/section permissions when domain manger is disabled. + +emulate_domain_manager_data_abort() handles data aborts caused by domain +not being set in HW, and handles section/page manipulation. + +emulate_domain_manager_prefetch_abort() is the similar prefetch abort handler. + +emulate_domain_manager_switch_mm() handles MMU table and context switches. +This notifies the emulation that the MMU context is changing. Allowing the +emulation to restore page table entry permission before switching contexts. + + +Config options +============== + +This option is enable/disable by the EMULATE_DOMAIN_MANAGER_V7 option. + + +Dependencies +============ + +Implementation is for ARMv7, MMU, and !SMP. Targets solving issue for 8x50 +chipset. + + +User space utilities +==================== + +None + + +Other +===== + +Code is implemented in kernel/arch/arm/mm. + + +arch/arm/mm/emulate_domain_manager.c contains comments. No additional public +documentation available or planned. + + +Known issues +============ + +No intent to support SMP or non ARMv7 architectures + + +To do +===== + +None + diff --git a/Documentation/arm/msm/gpiomux.txt b/Documentation/arm/msm/gpiomux.txt index 67a81620adf..aaf0793be07 100644 --- a/Documentation/arm/msm/gpiomux.txt +++ b/Documentation/arm/msm/gpiomux.txt @@ -2,112 +2,79 @@ This document provides an overview of the msm_gpiomux interface, which is used to provide gpio pin multiplexing and configuration on mach-msm targets. -History -======= - -The first-generation API for gpio configuration & multiplexing on msm -is the function gpio_tlmm_config(). This function has a few notable -shortcomings, which led to its deprecation and replacement by gpiomux: - -The 'disable' parameter: Setting the second parameter to -gpio_tlmm_config to GPIO_CFG_DISABLE tells the peripheral -processor in charge of the subsystem to perform a look-up into a -low-power table and apply the low-power/sleep setting for the pin. -As the msm family evolved this became problematic. Not all pins -have sleep settings, not all peripheral processors will accept requests -to apply said sleep settings, and not all msm targets have their gpio -subsystems managed by a peripheral processor. In order to get consistent -behavior on all targets, drivers are forced to ignore this parameter, -rendering it useless. - -The 'direction' flag: for all mux-settings other than raw-gpio (0), -the output-enable bit of a gpio is hard-wired to a known -input (usually VDD or ground). For those settings, the direction flag -is meaningless at best, and deceptive at worst. In addition, using the -direction flag to change output-enable (OE) directly can cause trouble in -gpiolib, which has no visibility into gpio direction changes made -in this way. Direction control in gpio mode should be made through gpiolib. - -Key Features of gpiomux -======================= - -- A consistent interface across all generations of msm. Drivers can expect -the same results on every target. -- gpiomux plays nicely with gpiolib. Functions that should belong to gpiolib -are left to gpiolib and not duplicated here. gpiomux is written with the -intent that gpio_chips will call gpiomux reference-counting methods -from their request() and free() hooks, providing full integration. -- Tabular configuration. Instead of having to call gpio_tlmm_config -hundreds of times, gpio configuration is placed in a single table. -- Per-gpio sleep. Each gpio is individually reference counted, allowing only -those lines which are in use to be put in high-power states. -- 0 means 'do nothing': all flags are designed so that the default memset-zero -equates to a sensible default of 'no configuration', preventing users -from having to provide hundreds of 'no-op' configs for unused or -unwanted lines. - Usage ===== -To use gpiomux, provide configuration information for relevant gpio lines -in the msm_gpiomux_configs table. Since a 0 equates to "unconfigured", -only those lines to be managed by gpiomux need to be specified. Here -is a completely fictional example: - -struct msm_gpiomux_config msm_gpiomux_configs[GPIOMUX_NGPIOS] = { - [12] = { - .active = GPIOMUX_VALID | GPIOMUX_DRV_8MA | GPIOMUX_FUNC_1, - .suspended = GPIOMUX_VALID | GPIOMUX_PULL_DOWN, - }, - [34] = { - .suspended = GPIOMUX_VALID | GPIOMUX_PULL_DOWN, +To use gpiomux, do the following before the msmgpio gpiochips probe: + +- Call msm_gpiomux_init to allocate needed resources. +- Install one or more sets of gpiomux configuration data via + msm_gpiomux_install and/or msm_gpiomux_write. + +Failing to finish these steps before the probe of msmgpio can result in calls +from msmgpio to gpiomux to try and activate lines which have not yet +been configured. + +A basic gpiomux setting is described by a gpiomux_setting structure. +A gpiomux configuration is a group of those settings (one for each power +state of the board) paired with a specific gpio, like so: + +struct msm_gpiomux_config gpio123_config __initdata = { + .gpio = 123, + .settings = { + [GPIOMUX_ACTIVE] = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, + .dir = GPIOMUX_OUT_HIGH, + }, + [GPIOMUX_SUSPENDED] = { + .func = GPIOMUX_FUNC_3, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_DOWN, + }, }, }; -To indicate that a gpio is in use, call msm_gpiomux_get() to increase -its reference count. To decrease the reference count, call msm_gpiomux_put(). - The effect of this configuration is as follows: -When the system boots, gpios 12 and 34 will be initialized with their -'suspended' configurations. All other gpios, which were left unconfigured, -will not be touched. - -When msm_gpiomux_get() is called on gpio 12 to raise its reference count -above 0, its active configuration will be applied. Since no other gpio -line has a valid active configuration, msm_gpiomux_get() will have no -effect on any other line. - -When msm_gpiomux_put() is called on gpio 12 or 34 to drop their reference -count to 0, their suspended configurations will be applied. -Since no other gpio line has a valid suspended configuration, no other -gpio line will be effected by msm_gpiomux_put(). Since gpio 34 has no valid -active configuration, this is effectively a no-op for gpio 34 as well, -with one small caveat, see the section "About Output-Enable Settings". - -All of the GPIOMUX_VALID flags may seem like unnecessary overhead, but -they address some important issues. As unused entries (all those -except 12 and 34) are zero-filled, gpiomux needs a way to distinguish -the used fields from the unused. In addition, the all-zero pattern -is a valid configuration! Therefore, gpiomux defines an additional bit -which is used to indicate when a field is used. This has the pleasant -side-effect of allowing calls to msm_gpiomux_write to use '0' to indicate -that a value should not be changed: - - msm_gpiomux_write(0, GPIOMUX_VALID, 0); - -replaces the active configuration of gpio 0 with an all-zero configuration, -but leaves the suspended configuration as it was. +- When the system boots, gpio 123 will be put into the SUSPENDED setting. +- When the reference count for gpio 123 rises above 0, the ACTIVE setting + will be applied. +- When the reference count falls back to 0, the SUSPENDED setting will be + reapplied. + +The reference count rises when msm_gpiomux_get() is called and falls +when msm_gpiomux_put() is called. msmgpio has hooks to these functions +in its gpiolib implementation. This means that when you call gpio_request() +on an msmgpio, msm_gpiomux_get() is automatically called on your behalf. +Similarly, when you call gpio_free(), msm_gpiomux_put() is called for you. +This allows generic drivers to obtain low-level management of msmgpio lines +without having to be aware of the gpiomux layer. + +Note that the .dir field is ignored if .func != GPIOMUX_FUNC_GPIO, since +software control of gpios is allowed only in GPIO mode. By selecting any +other .func, you assign the gpio to another piece of hardware and lose +control of it from gpiolib. You can still reserve such gpios with gpio_request +to prevent other modules from using them while they're in such a state, +but other gpiolib functions will not behave as you expect if .func != GPIO. + +If a configuration is omitted, nothing will happen at the relevant transitions. +This allows for the creation of 'static configurations' which do not +change as the line is requested and freed. Static Configurations ===================== To install a static configuration, which is applied at boot and does not change after that, install a configuration with a suspended component -but no active component, as in the previous example: +but no active component: - [34] = { - .suspended = GPIOMUX_VALID | GPIOMUX_PULL_DOWN, + .gpio = ..., + .settings = { + [GPIOMUX_SUSPENDED] = { + ... + }, }, The suspended setting is applied during boot, and the lack of any valid @@ -153,24 +120,3 @@ This provides important functionality: This mechanism allows for "auto-request" of gpiomux lines via gpiolib when it is suitable. Drivers wishing more exact control are, of course, free to also use msm_gpiomux_set and msm_gpiomux_get. - -About Output-Enable Settings -============================ - -Some msm targets do not have the ability to query the current gpio -configuration setting. This means that changes made to the output-enable -(OE) bit by gpiolib cannot be consistently detected and preserved by gpiomux. -Therefore, when gpiomux applies a configuration setting, any direction -settings which may have been applied by gpiolib are lost and the default -input settings are re-applied. - -For this reason, drivers should not assume that gpio direction settings -continue to hold if they free and then re-request a gpio. This seems like -common sense - after all, anybody could have obtained the line in the -meantime - but it needs saying. - -This also means that calls to msm_gpiomux_write will reset the OE bit, -which means that if the gpio line is held by a client of gpiolib and -msm_gpiomux_write is called, the direction setting has been lost and -gpiolib's internal state has been broken. -Release gpio lines before reconfiguring them. diff --git a/Documentation/arm/msm/kgsl-sysfs.txt b/Documentation/arm/msm/kgsl-sysfs.txt new file mode 100644 index 00000000000..c572312c437 --- /dev/null +++ b/Documentation/arm/msm/kgsl-sysfs.txt @@ -0,0 +1,98 @@ +This document lists details for the device specific sysfs attributes +created by the KGSL GPU driver. + +- /sys/devices/platform/kgsl/vmalloc + The total amount of vmalloc()ed memory currently allocated by the driver + (in bytes) + +- /sys/devices/platform/kgsl/vmalloc_max + The maximum amount of vmalloc()ed memory allocated at any one + time by the driver since the system was booted (in bytes) + +- /sys/devices/platform/kgsl/coherent + The total amount of coherent DMA memory currently allocated by the driver + (in bytes) + +- /sys/devices/platform/kgsl/coherent_max + The maximum amount of coherent DMA memory allocated at any one + time by the driver since the system was booted (in bytes) + + +- /sys/devices/platform/kgsl/histogram + A histogram of the sizes of vmalloc allocations by the driver + since the system was booted. The allocations are grouped by the order + of the allocation size in pages. For example, order 0 are 1 page + allocations, order 1 are 2 page allocations, order 2 are 4 page allocations, + and so forth, up to order 16 (32768) pages. + +- /sys/devices/platform/kgsl/proc + This directory contains individual entries for each active rendering + process. Rendering instances are created for each unique process that + opens the GPU devices, and are named for the id of the creating process. + In the driver, memory allocations are owned by the process that allocates + them, and outstanding memory is garbage collected when the process closes + the device. + + - /sys/devices/platform/kgsl/proc/NN/vmalloc + The total amount of vmalloc memory currently allocated by the process + (in bytes) + + - /sys/devices/platform/kgsl/proc/NN/vmalloc_max + The maximum amount of vmalloc memory allocated at any one + time by the process since it was created (in bytes) + + - /sys/devices/platform/kgsl/proc/NN/exmem + The total amount of external memory devices currently mapped by the process + (in bytes). This includes PMEM, ASHMEM and external memory pointers from + userspace. + + - /sys/devices/platform/kgsl/proc/NN/exmem_max + The maximum amount of external memory devices allocated at any one + time by the process since it was created (in bytes). This includes PMEM, + ASHMEM and external memory pointers from userspace. + + - /sys/devices/platform/kgsl/proc/NN/flushes + The total number of cache flushes performed by this process since it + was created. + +- /sys/devices/platform/kgsl/pagetables + This directory contains individual entries for each active pagetable. + There will always be a global pagetable with ID 0. If per-process + pagetables are not enabled, pagetable ID 0 will also be the default + pagetable for all processes. If per-process pagetables are enabled, + there will be an entry for each pagetable, named after the ID of the + process that created it. + + - /sys/devices/platform/kgsl/pagetables/NN/entries + The number of concurrent entries mapped in the GPU MMU. + + - /sys/devices/platform/kgsl/pagetables/NN/mapped + The number of bytes currently mapped in the GPU MMU. + + - /sys/devices/platform/kgsl/pagetables/NN/va_range + The virtual address size of the MMU pagetable (in bytes). + + - /sys/devices/platform/kgsl/pagetables/NN/max_mapped + The maximum number of bytes concurrently mapped in the GPU MMU since + the pagetable was created. + + - /sys/devices/platform/kgsl/pagetables/NN/max_entries + The maximum number of entries concurrently mapped in the GPU MMU since + the pagetable was created. + +- /sys/devices/platform/kgsl/msm_kgsl/ + Each individual GPU device (2D or 3D) will have its own device node in + this directory. All platforms will have kgsl-3d0 (3D device), some + devices may have 1 2D device (kgsl-2d0) and others might add a second 2D + device (kgsl-2d1). + + - /sys/devices/platform/kgsl/msm_kgsl/kgsl-XXX/pwrnap + Controls the system ability to nap (lightly sleep between frames). 1 + indicates napping is enabled, 0 indicates it is disabled. Write a 1 or + a 0 to the file to control napping. + + - /sys/devices/platform/kgsl/msm_kgsl/kgsl-XXX/gpuclk + Shows the last active requested speed of the GPU clock in HZ, does not + actually measure the current clock rate. Write a clock speed to the file + corresponding to a supported platform power level to change to that power + level. The bandwidth vote will also be adjusted. diff --git a/Documentation/arm/msm/msm_rng-driver.txt b/Documentation/arm/msm/msm_rng-driver.txt new file mode 100644 index 00000000000..3e7d1e9f460 --- /dev/null +++ b/Documentation/arm/msm/msm_rng-driver.txt @@ -0,0 +1,75 @@ +Introduction: +============= + +The msm_rng device driver handles random number generation +using hardware present in MSM chipsets. + +Hardware description: +===================== + +The supported hardware is a macro block within a system-on-a-chip (SoC). +The hardware is pseudo random number generator (PRNG) with four oscillators +setup with a linear feedback shift register (LFSR). +The hardware must be initially configured once for normal operation and +a 32bit FIFO is read to obtain hardware generated pseudo random numbers. +Currently the driver configures the hardware registers during initialization +and the future plan is to have the boot loader configure these registers and +write lock them so only host OS can read them and the driver writes will be +ignored. + +Software description +==================== + +The driver is based on the platform_driver model. It registers an entry, +exit and probe functions. Once the probe function is called, the driver +registers a callback function with the hwrng (Hardware Random Number Generator) +subsystem that is called when the hardware device (i.e. /dev/hw_random) is +requesting random data from this device. +Once the callback is issued from the hwrng subsystem, the driver checks to +make sure the hardware has random data available and determines the maximum +data it can return and returns that much data back. + +Power Management +================ + +Initially, no services are provided in the area of power management. + +SMP/multi-core +============== + +The locking mechanism for the hwrng operations is taken care of by the hwrng +framework. There are no SMP situations within the driver that need addressing. + +Driver parameters +================= + +This driver is built and statically linked into the kernel; therefore, +there are no module parameters supported by this driver. + +There are no kernel command line parameters supported by this driver. + +Config options +============== + +This driver is enabled by the kernel config option CONFIG_HW_RANDOM_MSM. +The option CONFIG_HW_RANDOM_MSM depends on HW_RANDOM && ARCH_MSM. + +Dependencies: +============= + +This driver depends on the HW_RANDOM subsystem to register with and get +callbacks to request random data. + +User space utilities: +===================== + +The driver alone does not feed random numbers into kernel but just provides a +method to get random numbers to a known device (i.e. /dev/hw_random). A user- +space utility is required to monitor the /dev/random device entropy pool and +feed it from the /dev/hw_random device. This application also must perform some +sort of sanity checking on the returned data to make sure the data is not all +the same. + +There is currently a GPL v2 tool called rng-tools that has a daemon called, +"rngd" that performs this functionality. There is also a test tool in this +package that tests the whole random subsystem. diff --git a/Documentation/arm/msm/pil.txt b/Documentation/arm/msm/pil.txt new file mode 100644 index 00000000000..5b0b527a6aa --- /dev/null +++ b/Documentation/arm/msm/pil.txt @@ -0,0 +1,267 @@ +Introduction +============ + +The PIL (Peripheral Image Loader) driver loads peripheral images into memory +and interfaces with the Peripheral Authentication Service (PAS) to +authenticate and reset peripherals embedded in the SoC. + +The PAS could either be running under secure mode in the application +processor (secure boot support) or be running as a non-secure kernel driver +(non-secure boot support). + +The PIL driver also does housekeeping to handle cases where more than one +client driver is using the same peripheral. + +Some examples of peripherals are modem, DSP and sensors. + +Hardware description +==================== + +The memory used by the peripherals for code and data storage will be +accessible as normal memory to the application processor. + +The non-secure code (Linux kernel) will have read/write permissions to the +peripheral memory by default. + +The PAS will have access to a MPU (memory protection unit) that can lock away +the pages of memory from the Linux kernel. It will also have access to +registers that can reset each peripheral. + +Software description +==================== + +The PAS provides the following three APIs: + +* Init image - Takes as input the peripheral id and firmware metadata and + returns a status indicating the authenticity of the firmware metadata. The + firmware metadata consists of a standard ELF32 header followed by a program + header table and an optional blob of data used to authenticate the metadata + and the rest of the firmware. + +* Verify segment - Takes as input the firmware segment id and the length of + the segment. Authenticates whatever amount (specified by the "length" + parameter) of the firmware segment that has been loaded and removes + non-secure mode read/write permissions for the pages belonging to the + firmware segment. Allows multiple calls for the same firmware segment to + allow partial loading and authentication. + +* Auth and Reset - Verifies all the necessary firmware segments have been + loaded and authenticated and then resets the peripheral. + +The user space is expected to provide the firmware metadata and firmware +segments as separate files on persistent storage. See "Interface" section for +further details. + +The PIL driver will use the request_firmware API provided by the Linux kernel +to read the firmware and firmware metadata from persistent storage. + +When a client driver requests for a peripheral to be enabled, the PIL driver +increments the reference count for that peripheral, loads the firmware +metadata and calls the PAS Init Image API that initializes the authentication +state machine using the firmware metadata. + +If the initialization succeeds, the PIL driver loads the appropriate firmware +segments into their respective memory locations and call the PAS Verify +segment API on each of the loaded segments to authenticate and lock it. + +After all the firmware segments have been successfully loaded and +authenticated, the PAS Auth and Reset API is called to reset the peripheral +and initiate its boot sequence. + +A peripheral enable request to the PIL driver will block until it succeeds +(or fails) to initiate the peripheral boot sequence but will NOT block until +the peripheral is ready. It is not possible to block until a peripheral is +ready since the semantics of "ready" is subjective to the caller. + +The PIL driver will maintain a reference count for each of the peripherals. +So, if a peripheral is already under use and another client driver requests +for the peripheral to be enabled, the PIL driver will immediately return a +value to indicate success. + +When all the client drivers of a particular peripheral no longer need the +peripheral and the reference count reaches zero, the PIL driver can cleanly +shut down the peripheral. Since a lot of drivers in their current state can't +handle a peripheral restart, the PIL driver will never let the reference +count go back to zero. + +All information about a peripheral, like firmware filenames, peripheral ID +passed to PAS, etc, will be hard coded in the PIL driver. + +All the PIL APIs will execute in the context of the caller. This includes +calls from the PIL driver to the PAS driver. The PAS driver might decide to +switch into secure mode from a separate workqueue or in the same context as +the caller, but that shouldn't have any implications for the PIL API callers +since all the PIL APIs are blocking calls. + +Dependencies: +------------- +* Firmware class (CONFIG_FW_LOADER) for using the request_firmware API to + load firmware from persistent storage. +* PAS to authenticate firmware and bring a peripheral out of reset. + +Error cases: +------------ +The PIL driver could fail to enable a peripheral for several reasons like not +having enough memory to load firmware and metadata, being unable to +communicate with the PAS, the PAS returning with an error, etc. For all +possible error cases, the PIL driver does not perform any retries and returns +an appropriate error code. The client drivers should always check for success +before trying to access the peripheral. + +Design +====== + +Design goals: +------------- +* The PIL driver must be agnostic to the actual format and method used to + authenticate the firmware. +* Allow for future expansion to support demand loading of parts of firmware + for each peripheral. +* Move most of the work into the preprocessing/building stage of the firmware. +* Provide an API to the client drivers that absolves them from having to know + the structure or names of the firmware in persistent storage. +* Handle multiple client drivers wanting to enable the same peripheral. + + +Design reasons: +--------------- +The user space is expected to provide the firmware metadata and segments as +separate files for the following reasons: +* Don't need to load the whole ELF file if the authentication info is + invalid. +* Works better during low memory conditions since the amount of memory used + at any given instant when loading one segment at a time is smaller than + loading the whole ELF file. +* Since an ELF segment in memory can be much bigger than on file, having a + flat binary would waste a lot of space due to zero-fills. +* Allows for future enhancements to the loading procedure. + +Design tradeoffs: +----------------- +* With appropriate changes to the request_firmware API, the firmware blobs + could be directly loaded into the right memory location. But due to the + additional work and community approval that would be needed for modifying + the request_firmware API, we load the firmware blobs into kernel memory and + then copy them into the appropriate locations. + +Alternate designs: +------------------ +One of the alternate designs that were considered required the firmware to be +a flat binary. Although this design would simplify the PIL driver, it would +result in the waste of a lot of persistent storage space (due to large +zero-fills), prevent demand loading of segments in the future and use a lot +more memory while loading the firmware. + +Software layering: +------------------ +The peripheral authentication, reset and shutdown implementation is factored +away into a Peripheral Authentication Service driver to allow the PIL driver +to be agnostic of secure vs. non-secure boot and the mechanisms needed for +communicating with any code that might be running in secure mode. + +Power Management +================ + +Some of the peripherals might support being turned off when not in use. +Support for this might be disabled in the initial implementation of the PIL +driver since many of the existing drivers can not handle peripheral restart. + +SMP/multi-core +============== + +Will use mutexes to protected data that might be shared (reference count, +etc). + +Security +======== + +The PIL driver must validate the physical memory addresses specified in the +ELF and program header table before loading firmware segments to make sure +it's not overwriting any memory used by the kernel and possibly PMEM regions +(if it can be done without being an ugly hack). The PIL driver might need to +maintain a white list or black list of physical memory address ranges to +perform the address validation. + +Performance +=========== + +As mentioned in the design section, the loading of firmware segments is not +optimal and has room for improvement. + +Interface +========= + +In kernel APIs: +void * pil_get(char *peripheral_name) + - Enables (if not already enabled) a peripheral and returns a handle + that can be used to disable the peripheral at a later time. If + peripheral can't be enabled successfully, then returns an error + (use IS_ERR) indicating the reason. + +void pil_put(void *peripheral_handle) + - Inform PIL that this client no longer needs the peripheral to be + active. Does not necessarily mean that the peripheral would be + disabled or powered off. + + +User space APIs: +All firmware must be located in the path that is expected by the hotplug (or +compatible) daemon. A hotplug (or compatible) daemon should be running and be +able to handle events from the kernel requesting for a firmware file. + +The basename of the firmware files will depend on the peripheral. For a given +peripheral, the metadata filename should end with a ".mdt" and the firmware +segment files should end with ".bXX" where XX denotes the index of the +firmware segment starting from 0. + +Android hotplug compatible daemon expects the firmware files to be under +/etc/firmware. + +Driver parameters +================= + +No module or kernel command line parameters supported. + +Config options +============== + +This driver is enabled using the MSM_PIL kernel config option and will +depend on the CONFIG_FW_LOADER being available. + +Dependencies +============ + +Depends on firmware class module for the request_firmware API. + +Interacts with the PAS to authenticate the firmware and to initiate the boot +sequence of a peripheral. + +Doesn't communicate with other processors since the secure code, if any, will +be running on the application processor cores. + +User space utilities +==================== + +None. + +Other +===== + +The firmware_class driver might be changed in the future to directly load the +firmware into memory locations provided by the caller of request_firmware(). + +Known issues +============ + +Since support for cleanly shutting down peripherals is yet to be added, the +reference count of peripherals will never be allowed to go to zero once it +becomes non-zero. + +To do +===== + +* Add support for turning off peripherals when they are not in use. +* Modify request_firmware() to directly copy firmware blobs into the + appropriate memory locations. +* Add support for demand loading of firmware segments. +* Add support for forced peripheral restarts. diff --git a/Documentation/arm/msm/rpm.txt b/Documentation/arm/msm/rpm.txt new file mode 100644 index 00000000000..9c9511fb030 --- /dev/null +++ b/Documentation/arm/msm/rpm.txt @@ -0,0 +1,157 @@ +Introduction +============ + +Resource Power Manager (RPM) + +RPM is a dedicated hardware engine for managing shared SoC resources, +which includes buses, clocks, power rails, etc. The goal of RPM is +to achieve the maximum power savings while satisfying the SoC's +operational and performance requirements. RPM accepts resource +requests from multiple RPM masters. It arbitrates and aggregates the +requests, and configures the shared resources. The RPM masters are +the application processor, the modem processor, as well as some +hardware accelerators. + +The RPM driver provides an API for interacting with RPM. Kernel code +calls the RPM driver to request RPM-managed, shared resources. +Kernel code can also register with the driver for RPM notifications, +which are sent when the status of shared resources change. + +Hardware description +==================== + +RPM exposes a separate region of registers to each of the RPM masters. +In general, each register represents some shared resource(s). At a +very basic level, a master requests resources by writing to the +registers, then generating an interrupt to RPM. RPM processes the +request, writes acknowledgement to the registers, then generates an +interrupt to the master. + +In addition to the master-specific regions, RPM also exposes a shared +region that contains the current status of the shared resources. Only +RPM can write to the status region, but every master can read from it. + +RPM contains internal logics that aggregate and arbitrate among +requests from the various RPM masters. It interfaces with the PMIC, +the bus arbitration block, and the clock controller block in order to +configure the shared resources. + +Software description +==================== + +The RPM driver encapsulates the low level RPM interactions, which +rely on reading/writing registers and generating/processing +interrupts, and provides a higher level synchronuous set/clear/get +interface. Most functions take an array of id-value pairs. +The ids identify the RPM registers which would correspond to some +RPM resources, the values specify the new resource values. + +The RPM driver synchronizes accesses to RPM. It protects against +simultaneous accesses from multiple tasks, on SMP cores, in task +contexts, and in atomic contexts. + +Design +====== + +Design goals: +- Encapsulate low level RPM interactions. +- Provide a synchronuous set/clear/get interface. +- Synchronize simultaneous software accesses to RPM. + +Power Management +================ + +RPM is part of the power management architecture for MSM 8660. RPM +manages shared system resources to lower system power. + +SMP/multi-core +============== + +The RPM driver uses mutex to synchronize client accesses among tasks. +It uses spinlocks to synchronize accesses from atomic contexts and +SMP cores. + +Security +======== + +None. + +Performance +=========== + +None. + +Interface +========= + +msm_rpm_get_status(): +The function reads the shared status region and returns the current +resource values, which are the arbitrated/aggregated results across +all RPM masters. + +msm_rpm_set(): +The function makes a resource request to RPM. + +msm_rpm_set_noirq(): +The function is similar to msm_rpm_set() except that it must be +called with interrupts masked. If possible, use msm_rpm_set() +instead, to maximize CPU throughput. + +msm_rpm_clear(): +The function makes a resource request to RPM to clear resource values. +Once the values are cleared, the resources revert back to their default +values for this RPM master. RPM internally uses the default values as +the requests from this RPM master when arbitrating and aggregating with +requests from other RPM masters. + +msm_rpm_clear_noirq(): +The function is similar to msm_rpm_clear() except that it must be +called with interrupts masked. If possible, use msm_rpm_clear() +instead, to maximize CPU throughput. + +msm_rpm_register_notification(): +The function registers for RPM notification. When the specified +resources change their status on RPM, RPM sends out notifications +and the driver will "up" the semaphore in struct +msm_rpm_notification. + +msm_rpm_unregister_notification(): +The function unregisters a notification. + +msm_rpm_init(): +The function initializes the RPM driver with platform specific data. + +Driver parameters +================= + +None. + +Config options +============== + +MSM_RPM + +Dependencies +============ + +None. + +User space utilities +==================== + +None. + +Other +===== + +None. + +Known issues +============ + +None. + +To do +===== + +None. diff --git a/Documentation/arm/msm/tsif.txt b/Documentation/arm/msm/tsif.txt new file mode 100644 index 00000000000..9f6827c38b5 --- /dev/null +++ b/Documentation/arm/msm/tsif.txt @@ -0,0 +1,231 @@ +TSIF driver serves piece of hardware found in Qualcomm MSM's. +It deals with Digital Mobile Broadcast. + +If you are dealing with Qualcomm MSM that have relevant piece of hardware, +read on. + +There are various Digital Mobile Broadcast (DMB) systems developed to receive +audio and/or television broadcast programs by Mobile Station Modem (MSM). +(in simplified words - cellular phone) + +All of these systems have similar architecture. They use radio link which +is different from primary handset link and hence use the additional antenna. +RF signal from the broadcast tuner goes to de-modulator. +Regardless of actual tuner and de-modulator, all systems present +ITU-T H.222.0 (also known as MPEG2) Transport Stream (HTS) +to the Mobile Station Modem (MSM). + +TSIF stands for Transport Stream Interface; +this is hardware block in MSM that receives HTS signal from the de-modulator. + +TSIF use serial interface with de-modulator; +it buffers data received in internal registers. +TSIF support data copying from its internal registers to the RAM +with the Data Mover (DM). + +TSIF driver prevent MSM from sleeping while TSIF hardware is active. +To achieve this, driver holds wake lock. + +For access to TSIF data, TSIF driver provides kernel API +that may be used by another kernel module. As example for API usage, +simple TSIF chardev adapter provided. It provides character device +/dev/tsif0. This device may be opened by single process at a time. +When read, it provides TS stream. + +Quick start: + +### copy modules to the target +adb push msm_tsif.ko /data/local/tmp/ +adb push tsif_chrdev.ko /data/local/tmp/ +### Load modules on the target: +adb shell mount -t debugfs debugfs /sys/kernel/debug +adb shell insmod /data/local/tmp/msm_tsif.ko +adb shell insmod /data/local/tmp/tsif_chrdev.ko +### Run capture: +adb shell cat /dev/tsif0 > /data/local/tmp/tsif.dump + + +# tests: +adb shell mount -t debugfs debugfs /sys/kernel/debug +adb shell rmmod tsif_chrdev +adb shell rmmod msm_tsif +adb shell insmod /data/local/tmp/msm_tsif.ko +adb shell insmod /data/local/tmp/tsif_chrdev.ko +adb shell 'echo 60 > /sys/devices/platform/msm_tsif.0/time_limit' +adb shell 'echo "16 * 8" > /sys/devices/platform/msm_tsif.0/buf_config' + +# separate xterm: +watch adb shell cat /sys/devices/platform/msm_tsif.0/stats +# separate xterm: +watch adb shell cat /sys/kernel/debug/msm_tsif.0/dma +# separate xterm: +adb shell dd if=/dev/tsif0 of=/dev/null + +Mode of operation + +TSIF hardware have 2 modes of operation: mode1 and mode 2. +They differ in serial interface signals. Mode used should match demodulator +chip interface. + +In addition to these 2 modes of operation, TSIF driver have pseudo-mode 3 +that means "debug mode" where all operation controlled through debug interfaces. +Client configure TSIF mode of operation using tsif_set_mode(). +Alternatively, mode of operation may be configured using device attribute file: +echo 1 > /sys/devices/platform/msm_tsif.0/mode + +Time limit + +TSIF driver maintains time limit value. Its value corresponds +to the TSIF_TIME_LIMIT register in TSIF hardware. +Value in ticks of tsif_ref_clk. If time between the end of previous +packet and end of current one exceeds this value, timeout status reported +for the current TSIF packet. +Client configure TSIF time limit using tsif_set_time_limit(). +Alternatively, time limit may be configured using device attribute file: +echo 100 > /sys/devices/platform/msm_tsif.0/time_limit + +TSIF packet format + +TSIF driver uses 192 byte packets; where first 188 packets is HTS packet; +last 4 bytes consists of : +3 bytes TTS in bytes 188..190; and status byte ib byte 191. + +Status byte contains the following fields: +Bit Name Comment +0 Valid Always set to 1 to indicate valid HTS packet and status. + If set to 0, this packet is not valid and should be ignored +1 First packet When set, indicates 1-st packet of a new stream or + 1-st valid packet after one or more packets were lost. +2 Overflow When set, indicates overflow condition in TSIF hardware; + one or more packets were lost. Current packet is valid. +3 Error Indicates the tsif_error signal status +4 Null Indicates the tsif_null signal status +5 Reserved Don't care +6 Timeout Indicates the 1-st packet after timeout. + First packet flag will also be set. + +Debug facilities + +TSIF driver provides extensive debugging facilities to assist debug both +TSIF input and TSIF client interfaces. 2 mechanisms used: + +Device attribute, accessible through usual /sys hierarchy under +/sys/devices/platform/msm_tsif.0, provides status and statistics information. + +Debugfs exposes more hardware and software details. In order to use debugfs, +one need to mount it: + +adb shell mount -t debugfs debugfs /sys/kernel/debug + +When debugfs mounted, TSIF entries may be found under +/sys/kernel/debug/msm_tsif.0 + +Register access + +All TSIF hardware registers accessible through debugfs. +$ adb shell ls -l /sys/kernel/debug/msm_tsif.0 +-r--r--r-- root root 0 1980-01-07 16:15 dma +--w------- root root 0 1980-01-07 16:15 action +-r--r--r-- root root 0 1980-01-07 16:15 gpios +-r-------- root root 0 1980-01-07 16:15 data_port +-r--r--r-- root root 0 1980-01-07 16:15 test_current +-rw-r--r-- root root 0 1980-01-07 16:15 test_export +--w------- root root 0 1980-01-07 16:15 test_reset +-rw-r--r-- root root 0 1980-01-07 16:15 test_mode +-rw-r--r-- root root 0 1980-01-07 16:15 test_ctl +-rw-r--r-- root root 0 1980-01-07 16:15 lpbk_data +-rw-r--r-- root root 0 1980-01-07 16:15 lpbk_flags +-rw-r--r-- root root 0 1980-01-07 16:15 clk_ref +-rw-r--r-- root root 0 1980-01-07 16:15 time_limit +-rw-r--r-- root root 0 1980-01-07 16:15 sts_ctl + +TSIF clocks are off when TSIF is not running. +To control TSIF through low level register access, it should be set to the +mode 3 ("debug"); in addition, TSIF start/stop actions may be executed using +debugfs action file: + +adb shell 'echo open > /sys/kernel/debug/msm_tsif.0/action' + +Possible actions are "open" and "close". + +DMA activity + +DMA activity may be queried using debugfs dma file: + +$ adb shell cat /sys/kernel/debug/msm_tsif.0/dma +ri 16 | wi 24 | dmwi 40 | [ 24]{ 32} [ 32]{ 40} + +This file provides ri/wi/dmwi indexes +(dmwi is for Data Mover write index - index for first location where +next DMA may be scheduled); +and 2 Data Mover transfer tasks, each in [wi] {next_wi} format. +Here, wi is index DMA is scheduled for; next_wi is where driver's +wi will be set after DMA completion. + +Driver status + +Driver status available through stats device attribute: + +$ adb shell cat /sys/devices/platform/msm_tsif.0/stats +Device msm_tsif.0 +Mode = 1 +Time limit = 60 +State running +Client = bf036f68 +Pkt/Buf = 64 +Pkt/chunk = 8 +--statistics-- +Rx chunks = 3288898 +Overflow = 4606 +Lost sync = 0 +Timeout = 1 +DMA error = 0 +Soft drop = 0 +IFI = 48 +--debug-- +GLBL_CLK_ENA = 0x637dfe23 +ROW_RESET = 0x000008c1 +CLK_HALT_STATEB = 0xde6d80ff +TV_NS_REG = 0xf8e00b44 +TSIF_NS_REG = 0x00000b40 + +GPIO + +Current GPIO values may be read using debugfs gpio file: +$ adb shell cat /sys/kernel/debug/msm_tsif.0/gpios + tsif_clk: 0 + tsif_en: 0 + tsif_data: 0 + tsif_sync: 0 + +In normal regime, signals changed too fast for this facility to provide +change by change log; it should be seen as random time capture. +When debugging TSIF input connectivity, it may be helpful to run + +watch -d adb shell cat /sys/kernel/debug/msm_tsif.0/gpios + +to see if input ever changes. If nothing changes at all; it is indication +for mis-configured input. + +Another tip: in case of wire connection between components, one may connect +TSIF input pin to logical 1 instead of actual signal source, +to verify this is the pin required. + +Inter frame interval + +To estimate incoming bit rate, TSIF driver measure average time interval +between packets. Interval measured in tsif_ref_clk ticks. Actually, TSIF +gets TTS from 1-st and last packets in chunk and use this time to calculate +inter frame interval. +Inter frame interval available as part of device statistics. + +Tip: to measure tsif_ref_clk frequency, this approach may be used: + +adb shell cat /sys/kernel/debug/msm_tsif.0/clk_ref; sleep 10; adb shell cat /sys/kernel/debug/msm_tsif.0/clk_ref +0x8db70ec8 +0x8dc6974b + +Then, calculate (0x8dc6974b - 0x8db70ec8)/10 that is 101798.7 Hz + + + diff --git a/Documentation/crypto/msm/qce.txt b/Documentation/crypto/msm/qce.txt new file mode 100644 index 00000000000..18435d170e1 --- /dev/null +++ b/Documentation/crypto/msm/qce.txt @@ -0,0 +1,228 @@ +Introduction: +============= + +The Qualcomm crypto engine (qce) driver is a module that +provides common services for accessing the Qualcomm crypto device. +Currently, the two main clients of qce are +-qcrypto driver (module provided for accessing CE HW by kernel space apps) +-qcedev driver (module provided for accessing CE HW by user space apps) + + +The crypto engine (qce) driver is a client to the DMA driver for the Qualcomm +DMA device - Application Data Mover (ADM). ADM is used to provide the DMA +transfer capability between Qualcomm crypto device hardware and DDR memory +for crypto operations. + + Figure 1. + --------- + + Linux kernel + (ex:IPSec)<--*Qualcomm crypto driver----+ + (qcrypto) | + (for kernel space app) | + | + +-->| + | + | *qce <----> Qualcomm + | driver ADM driver <---> ADM HW + +-->| | | + | | | + | | | + | | | + Linux kernel | | | + misc device <--- *QCEDEV Driver-------+ | | + interface (qcedev) (Reg interface) (DMA interface) + (for user space app) \ / + \ / + \ / + \ / + \ / + \ / + \ / + Qualcomm crypto CE3 HW + + + The entities marked with (*) in the Figure 1, are the software components of + the Linux Qualcomm crypto modules. + +=============== +IMPORTANT NOTE: +=============== +(1) The CE hardware can be accessed either from user space OR kernel space, + at one time. Both user space and kernel space clients cannot access the + qce driver (and the CE hardware) at the same time. + - If your device has user space apps that needs to access the crypto + hardware, make sure to have the qcrypto module disabled/unloaded. + This will result in the kernel space apps to use the registered + software implementation of the crypto algorithms. + - If your device has kernel space apps that needs to access the + crypto hardware, make sure to have qcedev module disabled/unloaded + and implement your user space application to use the software + implemenation (ex: openssl/crypto) of the crypto algorithms. + +(2) If your device has Playready(Windows Media DRM) application enabled and + uses the qcedev module to access the crypto hardware accelarator, + please be informed that for performance reasons, the CE hardware will need + to be dedicated to playready application. Any other user space application + should be implemented to use the software implemenation (ex: openssl/crypto) + of the crypto algorithms. + + +Hardware description: +===================== + +Qualcomm Crypto HW device family provides a series of algorithms implemented +in the device hardware. + +Crypto 2 hardware provides hashing - SHA-1, SHA-256, ciphering - DES, 3DES, AES +algorithms, and concurrent operations of hashing, and ciphering. + +In addition to those functions provided by Crypto 2 HW, Crypto 3 HW provides +fast AES algorithms. + +In addition to those functions provided by Crypto 3 HW, Crypto 3E provides +HMAC-SHA1 hashing algorithm, and Over The Air (OTA) f8/f9 algorithms as +defined by the 3GPP forum. + + +Software description +==================== + +The crypto device is defined as a platform device. The driver is +independent of the platform. The driver supports multiple instances of +crypto HW. +All the platform specific parameters are defined in the board init +file, eg. arch/arm/mach-msm/board-msm7x30.c for MSM7x30. + +The qce driver provide the common services of HW crypto +access to the two drivers as listed above (qcedev, qcrypto. It sets up +the crypto HW device for the operation, then it requests ADM driver for +the DMA of the crypto operation. + +Two ADM channels and two command lists (one command list for each +channel) are involved in an operation. + +The setting up of the command lists and the procedure of the operation +of the crypto device are described in the following sections. + +The command list for the first DMA channel is set up as follows: + + 1st command of the list is for the DMA transfer from DDR memory to the + crypto device to input data to crypto device. The dst crci of the command + is set for crci-in for this crypto device. + + 2nd command is for the DMA tansfer is from crypto device to DDR memory for + the authentication result. The src crci is set as crci-hash-done of the + crypto device. If authentication is not required in the operation, + the 2nd command is not used. + +The command list for the second DMA channel is set up as follows: + + One command to DMA data from crypto device to DDR memory for encryption or + decryption output from crypto device. + +To accomplish ciphering and authentication concurrent operations, the driver +performs the following steps: + (a). set up HW crypto device + (b). hit the crypto go register. + (c). issue the DMA command of first channel to the ADM driver, + (d). issue the DMA command of 2nd channel to the ADM driver. + +SHA1/SHA256 is an authentication/integrity hash algorithm. To accomplish +hash operation (or any authentication only algorithm), 2nd DMA channel is +not required. Only steps (a) to (c) are performed. + +At the completion of the DMA operation (for (c) and (d)) ADM driver +invokes the callback registered to the DMA driver. This signifies the end of +the DMA operation(s). The driver reads the status and other information from +the CE hardware register and then invokes the callback to the qce driver client. +This signal the completion and the results of the DMA along with the status of +the CE hardware to the qce driver client. This completes a crypto operation. + +In the qce driver initialization, memory for the two command lists, descriptor +lists for each crypto device are allocated out of coherent memory, using Linux +DMA API. The driver pre-configures most of the two ADM command lists +in the initialization. During each crypto operation, minimal set up is required. +src_dscr or/and dst_dscr descriptor list of the ADM command are populated +from the information obtained from the corresponding data structure. eg: for +AEAD request, the following data structure provides the information: + + struct aead_request *req + ...... + req->assoc + req->src + req->dst + +The DMA address of a scatter list will be retrieved and set up in the +descriptor list of an ADM command. + +Power Management +================ + none + + +Interface: +========== + +The interface is defined in kernel/drivers/crypto/msm/inc/qce.h + +The clients qcrypto, qcedev drivers are the clients using +the interfaces. + +The following services are provided by the qce driver - + + qce_open(), qce_close(), qce_ablk_cipher_req(), + qce_hw_support(), qce_process_sha_req() + + qce_open() is the first request from the client, ex. Qualcomm crypto + driver (qcedev, qcrypto), to open a crypto engine. It is normally + called at the probe function of the client for a device. During the + probe, + - ADM command list structure will be set up + - Crypto device will be initialized. + - Resource associated with the crypto engine is retrieved by doing + platform_get_resource() or platform_get_resource_byname(). + + The resources for a device are + - crci-in, crci-out, crci-hash-done + - two DMA channel IDs, one for encryption and decryption input, one for + output. + - base address of the HW crypto device. + + qce_close() is the last request from the client. Normally, it is + called from the remove function of the client. + + qce_hw_support() allows the client to query what is supported + by the crypto engine hardware. + + qce_ablk_cipher_req() provides ciphering service to the client. + qce_process_sha_req() provide hashing service to the client. + qce_aead_req() provide aead service to the client. + +Module parameters: +================== + +The following module parameters are defined in the board init file. +-CE hardware nase register address +-Data mover channel used for transfer to/from CE hardware +These parameters differ in each platform. + + +Dependencies: +============= + +Existing DMA driver. +The transfers are DMA'ed between the crypto hardware and DDR memory via the +data mover, ADM. The data transfers are set up to use the existing dma driver. + +User space utilities: +===================== + n/a + +Known issues: +============= + n/a + +To do: +====== + n/a diff --git a/Documentation/crypto/msm/qce40.txt b/Documentation/crypto/msm/qce40.txt new file mode 100644 index 00000000000..e99f7d7ef6c --- /dev/null +++ b/Documentation/crypto/msm/qce40.txt @@ -0,0 +1,241 @@ +Introduction: +============= + +The Qualcomm crypto engine (qce40) driver is a module that +provides common services for accessing the Qualcomm crypto device. +Currently, the two main clients of qce40 are +-qcrypto driver (module provided for accessing CE HW by kernel space apps) +-qcedev driver (module provided for accessing CE HW by user space apps) +This module provides the same interface to the clients as does qce.c and is +based off qce.c. Following are the updates from qce.c +- Add support for AES XTS mode +- Add support for CMAC mode +- Add support for AES CCM mode +- Add support for SHA1/SHA256 HMAC +- Read HASH/MAC information directly from CE hardware registers instead of + using datamover. + +The crypto engine (qce40) module is a client to the DMA driver for the Qualcomm +DMA device - Application Data Mover (ADM). ADM is used to provide the DMA +transfer capability between Qualcomm crypto device hardware and DDR memory +for crypto operations. + + Figure 1. + --------- + + Linux kernel + (ex:IPSec)<--*Qualcomm crypto driver----+ + (qcrypto) | + (for kernel space app) | + | + +-->| + | + | *qce40 <----> Qualcomm + | driver ADM driver <---> ADM HW + +-->| | | + | | | + | | | + | | | + Linux kernel | | | + misc device <--- *QCEDEV Driver-------+ | | + interface (qcedev) (Reg interface) (DMA interface) + (for user space app) \ / + \ / + \ / + \ / + \ / + \ / + \ / + Qualcomm crypto CE3 HW + + + The entities marked with (*) in the Figure 1, are the software components of + the Linux Qualcomm crypto modules. + +=============== +IMPORTANT NOTE: +=============== +(1) The CE hardware can be accessed either from user space OR kernel space, + at one time. Both user space and kernel space clients cannot access the + qce driver (and the CE hardware) at the same time. + - If your device has user space apps that needs to access the crypto + hardware, make sure to have the qcrypto module disabled/unloaded. + This will result in the kernel space apps to use the registered + software implementation of the crypto algorithms. + - If your device has kernel space apps that needs to access the + crypto hardware, make sure to have qcedev module disabled/unloaded + and implement your user space application to use the software + implemenation (ex: openssl/crypto) of the crypto algorithms. + +(2) If your device has Playready(Windows Media DRM) application enabled and + uses the qcedev module to access the crypto hardware accelarator, + please be informed that for performance reasons, the CE hardware will need + to be dedicated to playready application. Any other user space application + should be implemented to use the software implemenation (ex: openssl/crypto) + of the crypto algorithms. + + +Hardware description: +===================== + +Qualcomm Crypto HW device family provides a series of algorithms implemented +in the device hardware. + +Crypto 2 hardware provides hashing - SHA-1, SHA-256, ciphering - DES, 3DES, AES +algorithms, and concurrent operations of hashing and ciphering. + +In addition to those functions provided by Crypto 2 HW, Crypto 3 HW provides +fast AES algorithms. + +In addition to those functions provided by Crypto 3 HW, Crypto 3E provides +HMAC-SHA1 hashing algorithm, and Over The Air (OTA) f8/f9 algorithms as +defined by the 3GPP forum. + + +Software description +==================== + +The crypto device is defined as a platform device. The driver is +independent of the platform. The driver supports multiple instances of +crypto HW. +All the platform specific parameters are defined in the board init +file, eg. arch/arm/mach-msm/board-msm8960.c for MSM8960. + +The qce40 driver provide the common services of HW crypto +access to the two drivers as listed above (qcedev, qcrypto. It sets up +the crypto HW device for the operation, then it requests ADM driver for +the DMA of the crypto operation. + +Two ADM channels and two command lists (one command list for each +channel) are involved in an operation. + +The setting up of the command lists and the procedure of the operation +of the crypto device are described in the following sections. + +The command lists contains a single command. For the first DMA channel it +is set up as follows: + + The command is for the DMA transfer from DDR memory to the + crypto device to input data to crypto device. The dst crci of the command + is set for crci-in for this crypto device. + +The command list for the second DMA channel is set up as follows: + + One command to DMA data from crypto device to DDR memory for encryption or + decryption output from crypto device. + +To accomplish ciphering and authentication concurrent operations, the driver +performs the following steps: + (a). set up HW crypto device + (b). hit the crypto go register. + (c). issue the DMA command of first channel to the ADM driver, + (d). issue the DMA command of 2nd channel to the ADM driver. + +SHA1/SHA256 is an authentication/integrity hash algorithm. To accomplish +hash operation (or any authentication only algorithm), 2nd DMA channel is +not required. Only steps (a) to (c) are performed. + +At the completion of the DMA operation (for (c) and (d)) ADM driver +invokes the callback registered to the DMA driver. This signifies the end of +the DMA operation(s). The driver reads the status and other information from +the CE hardware register. For HASH functions (SHA1/SHA256, HMAC, CMAC and +CCM) were the MAC/HASH information is read off hardware registers. + +[ NOTE: This is different from what is done in the qce module that support +CE3.x hardware. In CE4.0 there is not CRCI_HASH and hence we cannot rely +on the data mover to populate the HMAC/SHA information. This information +is acquired fromte h ahrdware by reading directly from some registers that +hold this information ] + +The driver than nvokes the callback to the qce driver client. +This signal the completion and the results of the DMA along with the status of +the CE hardware to the qce40 driver client. This completes a crypto operation. + +In the qce40 driver initialization, memory for the two command lists, descriptor +lists for each crypto device are allocated out of coherent memory, using Linux +DMA API. The driver pre-configures most of the two ADM command lists +in the initialization. During each crypto operation, minimal set up is required. +src_dscr or/and dst_dscr descriptor list of the ADM command are populated +from the information obtained from the corresponding data structure. eg: for +AEAD request, the following data structure provides the information: + + struct aead_request *req + ...... + req->assoc + req->src + req->dst + +The DMA address of a scatter list will be retrieved and set up in the +descriptor list of an ADM command. + +Power Management +================ + none + + +Interface: +========== + +The interface is defined in kernel/drivers/crypto/msm/inc/qce.h + +The clients qcrypto, qcedev drivers are the clients using +the interfaces. + +The following services are provided by the qce driver - + + qce_open(), qce_close(), qce_ablk_cipher_req(), + qce_hw_support(), qce_process_sha_req() + + qce_open() is the first request from the client, ex. Qualcomm crypto + driver (qcedev, qcrypto), to open a crypto engine. It is normally + called at the probe function of the client for a device. During the + probe, + - ADM command list structure will be set up + - Crypto device will be initialized. + - Resource associated with the crypto engine is retrieved by doing + platform_get_resource() or platform_get_resource_byname(). + + The resources for a device are + - crci-in, crci-out, crci-hash-done + - two DMA channel IDs, one for encryption and decryption input, one for + output. + - base address of the HW crypto device. + + qce_close() is the last request from the client. Normally, it is + called from the remove function of the client. + + qce_hw_support() allows the client to query what is supported + by the crypto engine hardware. + + qce_ablk_cipher_req() provides ciphering service to the client. + qce_process_sha_req() provides hashing service to the client. + qce_aead_req() provides aead service to the client. + + +Module parameters: +================== + +The following module parameters are defined in the board init file. +-CE hardware base register address +-Data mover channel used for transfer to/from CE hardware +These parameters differ in each platform. + + +Dependencies: +============= + +Existing DMA driver. +The transfers are DMA'ed between the crypto hardware and DDR memory via the +data mover, ADM. The data transfers are set up to use the existing dma driver. + +User space utilities: +===================== + n/a + +Known issues: +============= + n/a + +To do: +====== + n/a diff --git a/Documentation/crypto/msm/qcedev.txt b/Documentation/crypto/msm/qcedev.txt new file mode 100644 index 00000000000..fde69bbed7c --- /dev/null +++ b/Documentation/crypto/msm/qcedev.txt @@ -0,0 +1,232 @@ +Introduction: +============= + +This driver provides IOCTLS for user space application to access crypto +engine hardware for the qcedev crypto services. The driver supports the +following crypto algorithms +- AES-128, AES-256 (ECB, CBC and CTR mode) +- AES-192, (ECB, CBC and CTR mode) + (support exists on platform supporting CE 3.x hardware) +- SHA1/SHA256 +- AES-128, AES-256 (XTS), AES CMAC, SHA1/SHA256 HMAC + (support exists on platform supporting CE 4.x hardware) + +Hardware description: +===================== +Crypto 3E provides cipher and hash algorithms as defined in the +3GPP forum specifications. + + +Software description +==================== + +The driver is a Linux platform device driver. For an msm target, +there can be multiple crypto devices assigned for QCEDEV. + +The driver is a misc device driver as well. +The following operations are registered in the driver, +-qcedev_ioctl() +-qcedev_open() +-qcedev_release() + +The following IOCTLS are available to the user space application(s)- + + Cipher IOCTLs: + -------------- + QCEDEV_IOCTL_ENC_REQ is for encrypting data. + QCEDEV_IOCTL_DEC_REQ is for decrypting data. + + Hashing/HMAC IOCTLs + ------------------- + + QCEDEV_IOCTL_SHA_INIT_REQ is for initializing a hash/hmac request. + QCEDEV_IOCTL_SHA_UPDATE_REQ is for updating hash/hmac. + QCEDEV_IOCTL_SHA_FINAL_REQ is for ending the hash/mac request. + QCEDEV_IOCTL_GET_SHA_REQ is for retrieving the hash/hmac for data + packet of known size. + QCEDEV_IOCTL_GET_CMAC_REQ is for retrieving the MAC (using AES CMAC + algorithm) for data packet of known size. + +The requests are synchronous. The driver will put the process to +sleep, waiting for the completion of the requests using wait_for_completion(). + +Since the requests are coming out of user space application, before giving +the requests to the low level qce driver, the ioctl requests and the +associated input/output buffer will have to be safe checked, and copied +to/from kernel space. + +The extra copying of requests/buffer can affect the performance. The issue +with copying the data buffer is resolved by having the client use PMEM +allocated buffers. + +NOTE: Using memory allocated via PMEM is supported only for in place + operations where source and destination buffers point to the same + location. Support for different source and destination buffers + is not supported currently. + Furthermore, when using PMEM, and in AES CTR mode, when issuing an + encryption or decryption request, a non-zero byteoffset is not + supported. + +The design of the driver is to allow multiple open, and multiple requests +to be issued from application(s). Therefore, the driver will internally queue +the requests, and serialize the requests to the low level qce (or qce40) driver. + +On an IOCTL request from an application, if there is no outstanding +request, a the driver will issue a "qce" request, otherwise, +the request is queued in the driver queue. The process is suspended +waiting for completion. + +On completion of a request by the low level qce driver, the internal +tasklet (done_tasklet) is scheduled. The sole purpose of done_tasklet is +to call the completion of the current active request (complete()), and +issue more requests to the qce, if any. +When the process wakes up from wait_for_completion(), it will collect the +return code, and return the ioctl. + +A spin lock is used to protect the critical section of internal queue to +be accessed from multiple tasks, SMP, and completion callback +from qce. + +The driver maintains a set of statistics using debug fs. The files are +in /debug/qcedev/stats1, /debug/qcedev/stats2, /debug/qcedev/stats3; +one for each instance of device. Reading the file associated with +a device will retrieve the driver statistics for that device. +Any write to the file will clear the statistics. + + +Power Management +================ +n/a + + +Interface: +========== + +Linux user space applications will need to open a handle +(file desrciptor) to the qcedev device. This is achieved by doing +the following to retrieve a file desrciptor to the device. + + fd = open("/dev/qce", O_RDWR); + .. + ioctl(fd, ...); + +Once a valid fd is retrieved, user can call the following ioctls with +the fd as the first parameter and a pointer to an appropriate data +structure, qcedev_cipher_op_req or qcedev_sha_op_req (depending on +cipher/hash functionality) as the second parameter. + +The following IOCTLS are available to the user space application(s)- + + Cipher IOCTLs: + -------------- + QCEDEV_IOCTL_ENC_REQ is for encrypting data. + QCEDEV_IOCTL_DEC_REQ is for decrypting data. + + The caller of the IOCTL passes a pointer to the structure shown + below, as the second parameter. + + struct qcedev_cipher_op_req { + int use_pmem; + union{ + struct qcedev_pmem_info pmem; + struct qcedev_vbuf_info vbuf; + }; + uint32_t entries; + uint32_t data_len; + uint8_t in_place_op; + uint8_t enckey[QCEDEV_MAX_KEY_SIZE]; + uint32_t encklen; + uint8_t iv[QCEDEV_MAX_IV_SIZE]; + uint32_t ivlen; + uint32_t byteoffset; + enum qcedev_cipher_alg_enum alg; + enum qcedev_cipher_mode_enum mode; + enum qcedev_oper_enum op; + }; + + Hashing/HMAC IOCTLs + ------------------- + + QCEDEV_IOCTL_SHA_INIT_REQ is for initializing a hash/hmac request. + QCEDEV_IOCTL_SHA_UPDATE_REQ is for updating hash/hmac. + QCEDEV_IOCTL_SHA_FINAL_REQ is for ending the hash/mac request. + QCEDEV_IOCTL_GET_SHA_REQ is for retrieving the hash/hmac for data + packet of known size. + QCEDEV_IOCTL_GET_CMAC_REQ is for retrieving the MAC (using AES CMAC + algorithm) for data packet of known size. + + The caller of the IOCTL passes a pointer to the structure shown + below, as the second parameter. + + struct qcedev_sha_op_req { + struct buf_info data[QCEDEV_MAX_BUFFERS]; + uint32_t entries; + uint32_t data_len; + uint8_t digest[QCEDEV_MAX_SHA_DIGEST]; + uint32_t diglen; + uint8_t *authkey; + uint32_t authklen; + enum qcedev_sha_alg_enum alg; + struct qcedev_sha_ctxt ctxt; + }; + +The IOCTLs and associated request data structures are defined in + kernel/drivers/crypto/msm/inc/qcedev.h.. + + +Module parameters: +================== + +The following module parameters are defined in the board init file. +-CE hardware nase register address +-Data mover channel used for transfer to/from CE hardware +These parameters differ in each platform. + + + +Dependencies: +============= +qce driver. Please see Documentation/arm/msm/qce.txt. + + +User space utilities: +===================== + +none + +Known issues: +============= + +none. + + +To do: +====== + Enhance Cipher functionality: + (1) Add support for handling > 32KB for ciphering functionality when + - operation is not an "in place" operation (source != destination). + (when using PMEM allocated memory) + +Limitations: +============ + (1) In case of cipher functionality, Driver does not support + a combination of different memory sources for source/destination. + In other words, memory pointed to by src and dst, + must BOTH (src/dst) be "pmem" or BOTH(src/dst) be "vbuf". + + (2) In case of hash functionality, driver does not support handling data + buffers allocated via PMEM. + + (3) Do not load this driver if your device already has kernel space apps + that need to access the crypto hardware. + Make sure to have qcedev module disabled/unloaded and implement your user + space application to use the software implemenation (ex: openssl/crypto) + of the crypto algorithms. + (NOTE: Please refer to details on the limitations listed in qce.txt) + + (4) If your device has Playready (Windows Media DRM) application enabled + and uses the qcedev module to access the crypto hardware accelarator, + please be informed that for performance reasons, the CE hardware will + need to be dedicated to playready application. Any other user space + application should be implemented to use the software implemenation + (ex: openssl/crypto) of the crypto algorithms. diff --git a/Documentation/crypto/msm/qcrypto.txt b/Documentation/crypto/msm/qcrypto.txt new file mode 100644 index 00000000000..81aa1941e15 --- /dev/null +++ b/Documentation/crypto/msm/qcrypto.txt @@ -0,0 +1,144 @@ +Introduction: +============= + +Qualcomm Crypto (qcrypto) driver is a Linux crypto driver which interfaces +with the Linux kernel crypto API layer to provide the HW crypto functions. +This driver is accessed by kernel space apps via the kernel crypto API layer. +At present there is no means for user space apps to access this module. + +Hardware description: +===================== + +Qualcomm Crypto HW device family provides a series of algorithms implemented +in the device. + +Crypto 2 hardware provides hashing - SHA-1, SHA-256, ciphering - DES, 3DES, AES +algorithms, and concurrent operations of hashing, and ciphering. + +In addition to those functions provided by Crypto 2 HW, Crypto 3 provides fast +AES algorithms. + +In addition to those functions provided by Crypto 3 HW, Crypto 3E provides +HMAC-SHA1 hashing algorithm. + +In addition to those functions provided by Crypto 3 HW, Crypto 4.0 provides +HMAC-SHA1/SHA256, AES CBC-MAC hashing algorithm and AES XTS/CCM cipher +algorithms. + + +Software description +==================== + +The module init function (_qcrypto_init()), does a platform_register(), +to register the driver. As the result, the driver probe function, +_qcrypto_probe(), will be invoked for each registered device. + +In the probe function, driver opens the low level CE (qce_open), and +registers the supported algorithms to the kernel crypto API layer. +Currently, qcrypto supports the following algorithms. + + ablkcipher - + cbc(aes),ecb(aes),ctr(aes) + ahash - + sha1, sha256 + aead - + authenc(hmac(sha1),cbc(aes)) + + The hmac(sha1), hmac(sha256, authenc(hmac(sha1),cbc(aes)), ccm(aes) + and xts(aes) algorithms are registered for some platforms that + support these in the CE hardware + +The HW device can support various algorithms. However, the most important +algorithms to gain the performance using a HW crypto accelerator are +AEAD, and ABLKCIPHER. + +AEAD stands for "authentication encryption with association data". +ABLKCIPHER stands of "asynchronous block cipher". + +The AEAD structure is described in the following header file + LINUX/opensource/kernel/include/crypto/aead.h + +The design of the driver is to allow multiple requests +issued from kernel client SW (eg IPSec). +Therefore, the driver will have to internally queue the requests, and +serialize the requests to the low level qce driver. + +When a request is received from the client, if there is no outstanding +request, a qce (or qce40) request is issued, otherwise, the request is +queued in the driver queue. + +On completion of a request, the qce (or qce40) invokes the registered +callback from the qcrypto. The internal tasklet (done_tasklet) is scheduled +in this callback function. The sole purpose of done_tasklet is +to call the completion of the current active request, and +issue more requests to the qce (or qce40), if any exists. + +A spin lock is used to protect the critical section of internal queue to +be accessed from multiple tasks, SMP, and completion callback +from qce. + +The driver maintains a set of statistics using debug fs. The files are +in /debug/qcrypto/stats1, /debug/qcrypto/stats2, /debug/qcrypto/stats3; +one for each instance of device. Reading the file associated with +a device will retrieve the driver statistics for that device. +Any write to the file will clear the statistics. + +Test vectors for authenc(hmac(sha1),cbc(aes)) algorithm are +developed offline, and imported to crypto/testmgr.c, and crypto/testmgr.h. + + +Power Management +================ + none + + +Interface: +========== +The kernel interface is defined in + LINUX/opensource/kernel/include/linux/crypto.h. + + +Module parameters: +================== + +All the platform specific parameters are defined in the board init +file, eg. arch/arm/mach-msm/board-mssm7x30.c for msm7x30. + +Dependencies: +============= +qce driver. + + +User space utilities: +===================== + n/a + +Known issues: +============= + n/a + +To do: +====== + Add Hashing algorithms. + + +Limitations: +=============== +(1) Each packet transfer size (for cipher and hash) is limited to maximum of + 32KB. This is a limitation in the crypto engine hardware. Client will + have to break packets larger than 32KB into multiple requests of smaller + size data packets. + +(2) Do not load this driver if your device has user space apps that needs to + access the crypto hardware. Please make sure to have the qcrypto module + disabled/unloaded. + Not having the driver loaded, will result in the kernel space apps to use + the registered software implementation of the crypto algorithms. + +(3) If your device has Playready application enabled and uses the qcedev module + to access the crypto hardware accelarator, please be informed that for + performance reasons, the CE hardware will need to be dedicated to playready + application. Any other user space or kernel application should be implemented + to use the software implemenation of the crypto algorithms. + + (NOTE: Please refer to details on the limitations listed in qce/40.txt) diff --git a/Documentation/csdio.txt b/Documentation/csdio.txt new file mode 100644 index 00000000000..22d5e35bc0f --- /dev/null +++ b/Documentation/csdio.txt @@ -0,0 +1,189 @@ +Introduction +============ +The Char SDIO Device Driver is an interface which exposes an SDIO +card/function from kernel space as a char device in user space. + +The driver doesn't interact with any HW directly. It relies on SDIO +card/function interface provided as a part of Linux kernel. + +Hardware description +==================== +Each SDIO device/card contains an SDIO client HW block. +The host interacts with the device by sending byte sequences called +command (CMD). Some commands can be followed by data blocks. The +device sends back a byte sequence called response (R) and a data +block if required. CMD3, CMD5 and CMD7 are used to initialize the +device. CMD52 and CMD53 are used to access the device. Command +format and properties are defined by SDIO Specification document +published by SD Association: + http://www.sdcard.org/developers/tech/sdio/. + +CMD52 and CMD53 can access up to 8 address spaces called Functions. +Function 0 contains system information predefined by SD/SDIO +standard and Functions 1-7 are defined by the SDIO device +manufacturer. + +An SDIO device/card can send an interrupt to SDIO host. This +interrupt is intercepted and handled by SDIO host. + +Software description +==================== +Linux provides a framework for handling SDIO devices. It implements +kind of plug-and-play model in which the Linux SDIO Host Driver is +responsible for initializing an SDIO device upon insertion. It also +reads device/card identification information and enumerates functions +provided by the device and then looks up in the list of +preregistered user SDIO drivers for a suitable one. + +During its lifecycle the user SDIO driver interacts with the Linux +SDIO Host Driver in order to send/receive information to/from SDIO +device/card. The user SDIO driver doesn't work with CMD52/CMD53 +directly. Instead it uses an abstraction provided by the Linux SDIO +Host Driver. + +The Linux SDIO Host Driver is also in charge of handling SDIO +interrupts. User SDIO driver can register its own callback in SDIO +Host Driver and get a notification about interrupt event. + +The Char SDIO Device Driver follows the design guidelines mentioned +above. It provides the following functionality: + + - Register itself in the user SDIO drivers list; + - Handle Probe event upon insertion of supported card/device; + - Creates and maintains a char device driver for each SDIO Function + found in the card/device; + - Translates read/write/ioctl calls to appropriate SDIO call + sequences; + +In order to handle general SDIO configuration functionality and +Function 0 the Char SDIO Device Driver provides additional +simplified char device driver. + +The Manufacturer and Device IDs of handled SDIO device should be +provided as parameters for kernel module or as configuration +parameters in case of statically linked driver. + +Design +====== +The main goal of the Char SDIO Device Driver is to expose an SDIO +card/device from kernel space to user space as a char device driver. +The driver should be generic and simple as far as possible. + +The biggest design tradeoff is maintaining a balance between the +system call overhead required to initiate an SDIO transaction from +user space and overall SDIO communication performance. But luckily, +because of nature of SDIO protocol, this overhead is negligible +comparing to time required to execute SDIO transaction itself. So, +each CMD52 (read or write) consists from single ioctl system call. +And each CMD53 invokes single ioctl system call followed by read or +write system call. + +The Char SDIO Device Driver registers its own class of the devices +called 'csdio'. This class will serve as a common roof for all SDIO +devices served by different instances of the Char SDIO Device Driver. +Additional benefit from maintaining its own class is the driver +ability to overwrite default permissions of the dev nodes created by +the driver. + +Power Management +================ +None + +SMP/multi-core +============== +The driver does not anticipate any issues related to multi-core +since it is expected to run on one core only. + +Security +======== +None + +Performance +=========== +None + +Interface +========= +The Char SDIO Device Driver has two char device interfaces: + - Control Interface; + - Function Interface. + +Char SDIO Device Driver Control Interface consists of: + - open() - device node is /dev/csdio0; + - close() + - ioctl() - the following options are available: + - CSDIO_IOC_ENABLE_HIGHSPEED_MODE; + - CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS; + - CSDIO_IOC_ENABLE_ISR; + - CSDIO_IOC_DISABLE_ISR. + +Char SDIO Device Driver Function Interface consists of: + - open() - device node is /dev/csdiofX, where X is Function Id; + - close() + - read() - send CMD53 read; + - write() - send CMD53 write; + - ioctl() - the following options are available: + - CSDIO_IOC_SET_OP_CODE - 0 fixed adrress, 1 autoincrement. + - CSDIO_IOC_FUNCTION_SET_BLOCK_SIZE; + - CSDIO_IOC_SET_BLOCK_MODE - 0 byte mode, 1 block mode; + - CSDIO_IOC_CMD52 - execute CMD52, receives the + following structure as a parameter: + struct csdio_cmd52_ctrl_t { + uint32_t m_write; // 0 - read, 1 -write + uint32_t m_address; + uint32_t m_data; // data to write or read data + uint32_t m_ret; // command execution status + }__attribute__ ((packed)); + - CSDIO_IOC_CMD53 - setup CMD53 data transfer, receives the + following structure as a parameter: + struct csdio_cmd53_ctrl_t { + uint32_t m_block_mode; + uint32_t m_op_code; + uint32_t m_address; + }__attribute__ ((packed)); + - CSDIO_IOC_CONNECT_ISR; + - CSDIO_IOC_DISCONNECT_ISR; + - CSDIO_IOC_GET_VDD; + - CSDIO_IOC_SET_VDD. + +Additionally, user space application can use fcntl system call with +parameters F_SETOWN and F_SETFL in order to set an asynchronous +callback for SDIO interrupt. + +Driver parameters +================= +If the driver is compiled as a kernel module, the following +parameters can be used in order to provide Manufacturer and Device IDs +upon module download: + - csdio_vendor_id; + - csdio_device_id. +If the driver is intended to work with specific SDIO host the +host_name parameter should be added followed by the name of the MMC +host platform device. + +Config options +============== +These are the kernel configuration options: + - CONFIG_CSDIO_VENDOR_ID; + - CONFIG_CSDIO_DEVICE_ID. + +Dependencies +============ +The Char SDIO Device Driver depends on Linux SDIO Host Driver. + +User space utilities +==================== +None + +Other +===== +None + +Known issues +============ +None + +To do +===== +Provide mechanism to support a number of SDIO devices simultaneously +connected to different SDIO hosts. diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index aa47be71df4..729e1466d77 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -2349,6 +2349,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted. 1: Fast pin select (default) 2: ATC IRMode + snddev_icodec.msm_codec_i2s_slave_mode= [ARM-MSM] + 1, codec is I2S master + 0, MSM is I2S master (default) + softlockup_panic= [KNL] Should the soft-lockup detector generate panics. Format: diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt index bfe924217f2..d6114ed8aea 100644 --- a/Documentation/networking/ip-sysctl.txt +++ b/Documentation/networking/ip-sysctl.txt @@ -1043,9 +1043,15 @@ conf/all/forwarding - BOOLEAN This referred to as global forwarding. -proxy_ndp - BOOLEAN +proxy_ndp - INTEGER Do proxy ndp. + Possible values are: + 0 Proxy NDP is disabled + 1 Proxy NDP is enabled + 2 NDP packets are sent to userspace, where a userspace proxy + can be implemented + conf/interface/*: Change special settings per interface. diff --git a/Documentation/networking/qfec.txt b/Documentation/networking/qfec.txt new file mode 100644 index 00000000000..182043f6b51 --- /dev/null +++ b/Documentation/networking/qfec.txt @@ -0,0 +1,309 @@ +Driver name: Qualcomm FSM9xxx Ethernet Driver + +Supported hardware: FSM9xxx Ethernet Controller + +Maintainer(s): +Author(s): + + +Introduction: +============= + +The FSM9xxx Ethernet controller is register based with separate TX and RX DMA +engines supporting scatter/gather and support 1EEE-1588 timestamping. +MII, RevMII and RgMII interfaces are support. RgMII support 1G. + +The driver supports gather but not scatter, uses the controller DMA engines, +and timestamping. + + +Hardware description: +===================== + +The Ethernet Controller is a memory mapped register device with two +internal DMA engines for TX and RX path processing using separate +buffer-descriptors (BD) allocated from non-cached main memory for the TX +and RX paths. These BDs support scatter-gather but are only used to +transfer single max sized Ethernet frames. The BDs are sequentially +accessed as a ring, with an end-of-ring bit set in the last BD. Ownership +bits control access by hardware and software to individual BDs. + +An additional 4 words of space can be configured and is allocated between +each BD to store additional information about the sk_buff associated with it. +The driver software uses 2 ring structures and local functions to manage +them to keep in sync with the hardware the BDs . The number of BDs is +determined from the space allocated for them (PAGE_SIZE). The ratio of RX +to TX BD is set by a #define. + +Interrupts are used to service and replenish pre-allocated sk_buff for each +RX BD. TX frames are allocated to a TX BD and transmitted frames are +freed within the xmit() invoked to send the frame. No TX interrupts are +processed since sk_buffs are freed in the xmit(). + +Three PHY interfaces are supported: MII, RevMII and RgMII. The selected +interface is determined from the resource structure (to be completed) and +programmed into a register prior to resetting the Ethernet controller. + +Separate PLLs are managed to provide MAC/PHY clocks in RevMii and RgMii +modes, and a 25mHz clock timestamping. + + + +Software description +==================== + +Structures + +struct qfec_buf_desc { + uint32_t status; + uint32_t ctl; + void *p_buf; + void *next; +}; + +struct buf_desc { + struct qfec_buf_desc desc; /* must be first */ + + struct sk_buff *skb; + void *buf_virt_addr; + void *buf_phys_addr; + uint32_t last_bd_flag; +}; + +struct ring { + int head; + int tail; + int n_free; + int len; +}; + +struct qfec_priv { + struct net_device *net_dev; + struct net_device_stats stats; /* req statistics */ + + struct device dev; + + spinlock_t hw_lock; + + unsigned int state; /* driver state */ + + void *bd_base; /* addr buf-desc */ + dma_addr_t tbd_dma; /* dma/phy-addr buf-desc */ + dma_addr_t rbd_dma; /* dma/phy-addr buf-desc */ + + struct resource *mac_res; + void *mac_base; /* mac (virt) base address */ + + struct resource *clk_res; + void *clk_base; /* clk (virt) base address */ + + unsigned int n_tbd; /* # of TX buf-desc */ + struct ring ring_tbd; /* TX ring */ + struct buf_desc *p_tbd; /* # TX buf-desc */ + + unsigned int n_rbd; /* # of RX buf-desc */ + struct ring ring_rbd; /* RX ring */ + struct buf_desc *p_rbd; /* # RX buf-desc */ + + unsigned long cntr[cntr_last]; /* activity counters */ + + struct mii_if_info mii; + + int mdio_clk; /* phy mdio clock rate */ + int phy_id; /* default PHY addr (0) */ + struct timer_list phy_tmr; /* monitor PHY state */ +}; + + + +Initialization is divided between probe() and open() such that the +net_device is allocated, the address space is mapped for register access, +and procfs files created in probe(). BD memory is allocated and +initialized along with interrupts and timers in open(). BD is not +de-allocated in close() allowing it to be debugged after the interface is +ifconfig down'd. This approach is intended to aid with debugging by +allowing configuring the interface down and up may clear some early usage +problems + +Phy link state changes are monitored using a timer using some existing +functions from the mii library, but also with local functions intended to +support RGMII in the future. + +A variety of information is accessible through procFs. Counters are used +to track various driver events, these include abnormal and error +interrupts. Hardware counters of various frame statistics (e.g. types and +sizes of TX and RX frames) are available. Hardware registers and up to the +50 TX and RX BDs can be can be displayed. A table of procfs filenames and +functions are used to create and delete the procfs entries as needed. + +Probe() + +Allocate and initialize the net_device structure with resource information +specifying the Ethernet controller, clock control and MAC address memory +regions. Set netdev_ops to a statically defined sub-structure supporting +the device. + +Open() + +Use qfec_mem_alloc() to allocate space for the buffer-descriptors (BD). +TX BDs are initialized by clearing the ownership bit of each. Each RX BD +is initialized using qfec_rbd_init(). Qfec_rbd_init() pre-allocates an +sk_buff, saving the addresses of both the sk_buff and its data buffer in the +additional BD space, setting the BD buf pointer to the physical address of +the sk_buff data, and finally setting the ownership bit. + +Once the BDs are initialized, interface selected register is set to the +appropriate PHY interface configuration, and the Ethernet controller is +reset and its registers initialized, including the starting addresses of +the TX and RX BDs. + +The PHY monitor state is initialized and the timer initialized and started. + +Finally, the interrupt for the Ethernet controller is initialized. + + Note - Interrupts from both from the external PHY and internal RevMii + PHY, are available, but neither is used in preference to the + timer. + + +Interrupt Processing + +Besides recognizing abnormal error interrupts, RX, TX and GMAC interrupts +are recognized, although TX and GMAC interrupts are ignored but cleared and +counted. (The gmac interrupt can be ignored but must be disabled). + +RX interrupts invoke a handler to process the received frame, send it +to the stack and re-allocate a replacement sk_bufff for the buffer- +descriptor. + + +Receive Processing + +The RX buffer descriptors are initialized by _open() using qfec_rbd_init() +which pre-allocated an sk_buff, saving its address and the physical address +of its data in the additional BD space, as well as writing the physical +address to the BD pbuf entry read by HW. The size of the buffer and +other control information are written to the BD, as well as setting the +ownership bit. + +A received frame generates an interrupt invoking qfec_rx_int(). It +repeatedly checks the ownership the next available BD, and passing the +sk_buff containing the received frame to the stack via netif_rx(). + +Once all received frames are processed, it repeatedly calls qfec_rbd_init() +to allocate a new sk_buff with each available BD. + + +Transmit Processing + +Frames are transmitted through the start_xmit callback function. +qfec_tx_replenish() is immediately called to free sk_buffs from BD +that have been transmitted, before checking is a BD is available. +The sk_buff address is stored in the additional BD space and the +physical address of its data is store in the pbuf BD entry used +by the HW. The TX poll-demand register is accessed, causing the +HW to recheck the current BD and process it. + +While the TX interrupt could be processed to free sk_buffs as BD +are processed, they are ignored since the sk_buffs will be freed +with each call to _xmit(). + +procfs + +debug files are available to display the controller registers, +frame counters from the controller, driver activity counters, and +the first 50 entries of the RX and TX buffer descriptors. + + +Callbacks + +In addition to the functions described above, the following functions +are used to support their correspondingly named device operations: + + qfec_stop + qfec_do_ioctl + qfec_tx_timeout + qfec_set_mac_address + qfec_get_stats + qfec_set_config + + eth_change_mtu + eth_validate_addr + + +Power Management +================ +None + + +Interface: +========== + +- Module-init/exit +- standard network interface functions + + +Module parameters: +================== + +static struct resource qfec_resources [] = { + [0] = { + .start = QFEC_MAC_BASE, + .end = QFEC_MAC_BASE + QFEC_MAC_SIZE, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = QFEC_MAC_IRQ, + .end = QFEC_MAC_IRQ, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = QFEC_CLK_BASE, + .end = QFEC_CLK_BASE + QFEC_CLK_SIZE, + .flags = IORESOURCE_IO, + }, + [3] = { + .start = QFEC_MAC_FUSE_BASE, + .end = QFEC_MAC_FUSE_BASE + QFEC_MAC_FUSE_SIZE, + .flags = IORESOURCE_DMA, + }, +}; + +static struct platform_device qfec_device = { + .name = "qfec", + .id = 0, + .num_resources = ARRAY_SIZE(qfec_resources), + .resource = qfec_resources, +}; + + +Resource entries exist for three address regions and one interrupt. The +interrupt is identified as IORESOURCE_IRQ, the controller registers as +OPRESOURCE_MEM, the clock control registers as IORESOURCE_IO, and the +MAC address fuses as IORESOURCE_DMA. + + +Dependencies: +============= +None + + +User space utilities: +===================== + +See procfs descriptions + + +Known issues: +============= + +- replace procfs w/ debugfs + + +To do: +====== + +- specify interface (MII/RevMII/RgMii) in resource structure +- RevMii support untested +- RgMii (10/100/1000) +- generic timestamp support diff --git a/Documentation/sysctl/kernel.txt b/Documentation/sysctl/kernel.txt index 5e7cb39ad19..6d7ca5695b0 100644 --- a/Documentation/sysctl/kernel.txt +++ b/Documentation/sysctl/kernel.txt @@ -21,6 +21,7 @@ show up in /proc/sys/kernel: - acct - bootloader_type [ X86 only ] - bootloader_version [ X86 only ] +- boot_reason [ ARM only ] - callhome [ S390 only ] - auto_msgmni - core_pattern @@ -126,6 +127,19 @@ Documentation/x86/boot.txt for additional information. ============================================================== +boot_reason: + +ARM -- reason for device boot + +A single bit will be set in the unsigned integer value to identify the +reason the device was booted / powered on. The value will be zero if this +feature is not supported on the ARM device being booted. + +See the power-on-status field definitions in +Documentation/arm/msm/boot.txt for Qualcomm's family of devices. + +============================================================== + callhome: Controls the kernel's callhome behavior in case of a kernel panic. diff --git a/Documentation/tzcom.txt b/Documentation/tzcom.txt new file mode 100644 index 00000000000..7472deeede6 --- /dev/null +++ b/Documentation/tzcom.txt @@ -0,0 +1,181 @@ +Introduction +============ + +The tzcom (TrustZone Communicator) device driver provides IOCTLs for userspace +to communicate with TrustZone Operating Environment (TZBSP) using Secure +Channel Manager (SCM) interface. It also provides a way for TZBSP to utilize +services in HLOS. + +Hardware description +==================== + +The hardware interaction is specified in Secure Channel Manager for TZBSP design +document. This driver exercises the SCM interface (scm_call). + +Software description +==================== + +This driver is a character device driver and following operations are registered: +- tzcom_open() +- tzcom_release() +- tzcom_ioctl() + + +This driver provides following IOCTL methods: + TZCOM_IOCTL_REGISTER_SERVICE_REQ - to register HLOS service + TZCOM_IOCTL_UNREGISTER_SERVICE_REQ - to unregister HLOS service + TZCOM_IOCTL_SEND_CMD_REQ - send a command to a service + TZCOM_IOCTL_READ_NEXT_CMD_REQ - wait for a cmd from TZBSP to use HLOS service + TZCOM_IOCTL_CONTINUE_CMD_REQ - continue the last incomplete cmd on TZBSP + +TZCOM_IOCTL_REGISTER_SERVICE_REQ sequence diagram: + + +--------------+ +---------------+ + | USERSPACE | | TZCOM | + +------+-------+ +-------+-------+ + | REGISTER_SERVICE | + |----------------->| ___ + | |,-' ``. + | + verify &`. + | | add | + | | service | + | | to a list| + | registered |<-.._,,,,/ + |<-----------------| + | | + +TZCOM_IOCTL_READ_NEXT_CMD_REQ, TZCOM_IOCTL_SEND_CMD_REQ and +TZCOM_IOCTL_CONTINUE_CMD_REQ sequence: + + +--------------+ +---------------+ +-------------+ +----------------+ + | USERSPACE | | TZCOM | | SCM | | TZBSP | + +---+--+-------+ +-------+-------+ +------+------+ +-------+--------+ + | | READ_NEXT_CMD | | | + +--|----------------->| | | + | | |.--------. | | + | | || BLOCKED| | | + | | |`--------' | | + | | | | | + | | | | | + | | SEND_CMD | | | + | +----------------->| | | + | | | scm_call | | + | | +---------------->| SEND_CMD | + | | | +---------------->| + | | | | cmd incomplete | + | | | scm_call returns|<----------------+ + | | |<----------------+ | + | | | | | + | | |,-'''-. | | + | | + READ `. | | + | | | NEXT | | | + | | | CMD / | | + | | READ_NEXT_CMD ret|<.____,' | | + |<-|------------------+ | | + ,---. | | | | | + / \ | | | | | +/perform\| | | | | + received) | | | | +\command/| | | | | + \ / | | | | | + `---' | | | | | + | | | | | + | | CONTINUE_CMD | | | + +--|----------------->| | | + | | returns | _,... | | + | | immediately |' `. | | + | | | fill in`. | | + | | | incomplete | | + | | | cmd ; | | + | |<-...---' | | + | | scm_call | | + | +---------------->| SEND_CMD | + | | +---------------->| + | | | cmd complete | + | | scm_call returns|<----------------+ + |SEND_CMD return |<----------------+ | + |<-----------------+ | | + | | | | + + + +There are three shared buffers between TZCOM driver and TZBSP. +1) For command and response buffers for SEND_CMD requests +2) For commands originated from TZBSP and their corresponding responses +3) For debug service + +When calling IOCTL_SEND_CMD_REQ from userspace, command request and response +buffers are initialized and provided in the IOCTL arguments. Where request and +response buffers will be passed as an arguments to the smc_call method. + +The requests are synchronous. The driver will put the process to sleep, +waiting for the completion of the requests using wait_for_completion(). + +This driver uses kmalloc for shared buffer pools which get initialized at driver +initialization. There are three buffers each 20 KB. If any of the buffers fail +to initialize then driver will fail to load. Assumption is the allocated +memory for buffers is contiguous. + + +Design +====== + +The goal of this driver is to provide a communication API for the userspace +application to execute services in TrustZone as well as TrustZone operating +environment to access services in HLOS. + +Currently TZ->HLOS communication happens from a blocking call to READ_NEXT_CMD +that is initiated from the userspace and on receiving a command request from TZ +service, command is placed on a queue to unblock READ_NEXT_CMD call. This could +have been solved by using a callback, but the practice of invoking callbacks in +userspace from kernel is discouraged. + +Power Management +================ + +n/a + +SMP/multi-core +============== + +TZCOM allows multiple services being registered from HLOS and multiple processes +or threads can call IOCTL_READ_NEXT_MSG. These services will block until new +data arrives on the shared buffer (buffer #2 as mentioned in Software +Description). This is achieved using wait queues. + +Security +======== + +Please refer to Security Channel Manager design document. + +Performance +=========== + +Every scm_call is a context switch between non-trusted and trusted operating +environment. There are no performance related matrix for scm_call available as +of now. + +Interface +========= + +This driver will have a /dev/tzcom node and following IOCTL calls can be made. + +Userspace API (ioctl calls): + TZCOM_IOCTL_REGISTER_SERVICE_REQ - to register HLOS service + TZCOM_IOCTL_UNREGISTER_SERVICE_REQ - to unregister HLOS service + TZCOM_IOCTL_SEND_CMD_REQ - send a command to a service + TZCOM_IOCTL_READ_NEXT_CMD_REQ - wait for a cmd from TZBSP to use HLOS service + TZCOM_IOCTL_CONTINUE_CMD_REQ - continue the last incomplete cmd on TZBSP + + +Dependencies +============ + +This driver interacts with Trustzone operating environment, thus depends on +the TZBSP supported architecture. + + +To do +===== + +TBD diff --git a/Documentation/usb/ehset_compliance.txt b/Documentation/usb/ehset_compliance.txt new file mode 100644 index 00000000000..3f00cf0ea9e --- /dev/null +++ b/Documentation/usb/ehset_compliance.txt @@ -0,0 +1,69 @@ +Introduction +============ + +A USB high speed host must pass electrical compliance tests defined +by the USB-IF. These compliance tests require the host controller to +support various test modes defined by the USB 2.0 specification. + +USB-IF defines a standard method to initiate the test modes on an +embedded host controller by using a test fixture. During enumeration +by the USB host, this test fixture provides a Vendor-Id/Product-Id +(or VID/PID) pair which is used by the host to initiate a particular +test mode as each VID/PID pair corresponds to a unique test mode. + +Hardware description +==================== + +The driver doesn't require any new hardware and is like any other +USB host class driver. It gets notified when a Test Fixture device is +connected to the host. + +The test modes that can be initiated are specific to the high speed +hosts controllers only. + +Software description +==================== + +This EHSET (or Embedded High Speed Electrical Test) driver registers +itself with USB core as the preferred driver for the Test Fixture +device. During registration it provides the list of the various +VID/PID pairs which the Test Fixture may present during enumeration. +The VID is always 0x1A0A, and the PIDs presented by the Test Fixture +correspond to the following test modes: + +__________________________________________________ + PID Test Mode +-------------------------------------------------- + 0x0101 TEST_SE0_NAK + 0x0102 TEST_J + 0x0103 TEST_K + 0x0104 TEST_PACKET + 0x0106 HS_HOST_PORT_SUSPEND_RESUME + 0x0107 SINGLE_STEP_GET_DEV_DESC + 0x0108 SINGLE_STEP_SET_FEATURE +-------------------------------------------------- + +The control flow is as follows: + +1. USB core notifies the ehset driver when a device (Test Fixture) is +attached to the host having the VID/PID pair as one of the specified +above. + +2. EHSET driver checks the PID which the Test Fixture presented during +enumeration and then initiates the corresponding test mode. + + +Dependencies +============ + +The driver depends on the USB EHCI Host support. + +Other +===== + +The driver's code shall be added as a new file in the +/kernel/drivers/usb/misc directory. + +Embedded High-speed Electrical Test Procedure document is available +at: +http://www.usb.org/developers/onthego/EHSET_v1.01.pdf diff --git a/Documentation/usb/gadget_rmnet.txt b/Documentation/usb/gadget_rmnet.txt new file mode 100644 index 00000000000..2bf05a1b131 --- /dev/null +++ b/Documentation/usb/gadget_rmnet.txt @@ -0,0 +1,222 @@ +Introduction +============ + +QUALCOMM MSM Interface (QMI) defines the interface between MSM and +attached Terminal Equipment (TE). RmNet interface is a new logical +device in QMI framework for data services. RmNet in accordance with +QMI architecture defines channels for control and data transfers and +for example it uses Data I/O channel for IP data transfer and control +I/O channel for QMI messaging (functionality similar to AT commands). +RmNet may be used in place of legacy USB modem interface. + +Tethered networking is one of the function from MSM which can also be +supported using QMI protocol. There are other standard protocols exists +such as CDC-ECM and Windows proprietary RNDIS. On the host-side system, +the gadget rmnet device looks like a ethernet adapter. + +Hardware description +==================== + +QMI is a messaging protocol to expose various functionalities of MSM +and one of the functionality to be tethered networking which is being +exposed over QMI using RmNet protocol. This usb gadget has one bulk-in, +one bulk-out and one interrupt-in endpoint. + +Design: +======= +RmNet function driver design follows two approaches: + +Approach 1: +----------- +Single function driver is used to communicate with +Modem(both for data and control). Most of the initial +MSM targets are following this approach. + +The main disadvantage with this approach is there are +multiple RmNet drivers for any change in DATA and Control +Layer. There is no re-use in the code. + +Approach 2: +----------- +RmNet driver is divided into 3 components + +1. USB: +This component has the functionality to deal with composite layer. +Allocates Interfaces, Endpoints, listens to connect/disconnect +interrupts and gives connect/disconnect notifications to DATA and +CONTROL modules. + +2. Data: +This component talks to modem to transfer IP data. Usually DATA +and CONTROL go over same channel. However, to achieve higher +data rates new transport channel for DATA may be used. + +3. Control: +This component talks to modem to transfer rmnet control data. + +Software description +==================== +The RmNet suports following data and control transports: +as follows: + 1. SMD Interface + 2. SDIO Interface + 3. BAM Interface + 4. SMD Control Interface + +SMD interface uses the Shared memory for the RmNet driver to communicate +with the MSM modem processor. +SDIO interface acts as a link for communication of RmNet driver with the +MDM modem processor. + +USB INTERACTION: +---------------- + +The RmNet function driver binds with the USB using the struct usb_function. +The function is added using the usb_function_add(). +Control Transfers: The RmNet handles two Class-specific control +transfers: SEND_ENCAPSULATED_COMMAND and GET_ENCAPSULATED_RESPONSE. +The asynchronous protocol QMI which consists of the QMI requests/responses +is used for handling the transfers between the RmNet and the Host where the +host sends a new QMI request before receiving the response for the current +QMI request. + +Control & Data flow: +1. Host issues a SEND_ENCAPSULATED command to send a QMI request. +SMD: If the SMD control channel has enough room to accomodate a QMI request, +it is written into the SMD buffer. Otherwise, append/add that request to +qmi_request queue. A tasklet is scheduled to drain all QMI requests in +qmi_request queue. +SDIO: Add each request in the qmi_request queue and is processed until +the queue is empty. + +2. Append/Add QMI response from modem to qmi_response queue. +A notification on an interrupt end point is used to communicate the QMI +response to host. + +3. Host issues a GET_ENCAPSULATED command to retrieve the QMI response. +The response from qmi_response queue will be sent to the host. + +4. After the connection is fully established data can be sent to +bulk-out endpoint and data can be received from bulk-in endpoint. + +5. Host can send QMI requests even after the connection is established. + +RmNet gadget driver is completely unaware of QMI/IP protocol. It just +acts as a bridge between the modem and the PC. + +All the request/response queues in the driver can be accessed either +from tasklet/workqueue or from interrupt context (either usb or smd/sdio +interrupt handler). Hence a spinlock is used to protect all data/control req +lists. + + +SMD Interface: +-------------- + +1. Each QMI request/response can at most be 2048 bytes. Eight 2KB buffers +are allocated using kmalloc for storing maximum of 8 requests/responses. + +2. Four 2KB buffers are allocated using kmalloc for data transfers on +each bulk endpoint. + +Data structures: +struct qmi_buf - Buffer to handle QMI requests & responses +struct rmnet_smd_info - Control & Data SMD channel private data +struct rmnet_dev - Endpoint and driver specific data + +Workqueues: +rmnet_connect_work - Called on device connection. + Opens SMD channels; enables endpoints +rmnet_disconnect_work - Called on device disconnection. + Closes SMD channels. + +Tasklets: +rmnet_control_rx_tlet +rmnet_control_tx_tlet - Control transfer data reception and transmission + handler + +rmnet_data_rx_tlet +rmnet_data_tx_tlet - Data transfer data reception and transmission handler + + +SMD control interface +---------------------- +This function driver implements exchnage of control informtion with +modem over SMD. Uses smd_read/write commands to read or write rmnet +ctrl packets. Exposes a call back function to usb component to write +control packet and at the same time call a call back usb component +callback to send data to usb host. + +Data structures and Interfaces are very similar to control interfaces +explained in "SMD Interface" + +BAM MUX interface +------------------ +BAM Mux interface is very similar to SDIO MUX interface. However there +are differences in the way BAM and SDIO operate but all of the details +are masked by MUX Driver. + +Refer to the SDIO interfaces for more information on data structures + +SDIO Interface: +--------------- + +1. Each QMI request/response buffer is allocated depending on the size +of data to be transmitted for the request/response. + +2. A 2KB network buffer is allocated for data transfer on bulk-out +endpoint. The SDIO allocates the required buffer for data transfers +on an bulk-in endpoint. + +Data structures: +struct rmnet_sdio_qmi_buf - Buffer to handle QMI requests/responses. +struct rmnet_dev - Endpoint and driver specific data + +Workqueues: +rmnet_connect_work - Device connection handler. Opens SDIO + channels; enables and allocate buffer for + endpoints +rmnet_disconnect_work - Device disconnection handler. Closes + SDIO channels; Frees allocated buffers. +rmnet_control_rx_work - Control data reception handler. +rmnet_data_rx_work - Network data reception handler. + + +Two SMD/SDIO channels (control and data) are used as communication channels +between Modem and Apps processor. The driver opens the SMD/SDIO channels +on USB device connection. Data is either read from/written to the channels +as one complete packet. + +SMD/SDIO provides a notification whenever the Modem processor completes +read/write of a packet. Based on these SMD/SDIO notifications all the +pending read/write requests will be handled. Tasklets(SMD)/Workqueues(SDIO) +are used to get the requests done. + +There is another variant of rmnet driver called rmnet_smd_sdio which is used +on some boards. This driver allows the transport (SMD/SDIO) to be chosen +at runtime. This is required because either MDM processor or MODEM processor +is only active at a time for data transfers. As SMD and SDIO interfaces +are different, different endpoint completion handlers are used. This driver +leverage the existing rmnet over smd and rmnet over sdio drivers. The control +messages (QMI) always routed over SDIO. After the control messages exchange, +user space will come to know about the available data transport (SMD/SDIO). +User space notify the same to driver and the corresponding transport is +activated. It is assumed that transport will not change while a USB cable +is connected. + +Rmnet over SMD and rmnet over SDIO doesn't expose any of its interfaces to +either kernelspace or userspace. But rmnet over smd/sdio expose a sysfs +interface for userspace to notify the available transport to driver. + +The sysfs file can be found at +/sys/class/usb_composite/rmnet_smd_sdio/transport + +The below command activates the SMD transport +echo 0 > /sys/class/usb_composite/rmnet_smd_sdio/transport + +The below command activates the SDIO transport +echo 1 > /sys/class/usb_composite/rmnet_smd_sdio/transport + +-EINVAL is returned if a write is attempted to transport when a USB cable +is not connected. + diff --git a/Documentation/usb/gadget_sdio.txt b/Documentation/usb/gadget_sdio.txt new file mode 100644 index 00000000000..d0f1d639e9a --- /dev/null +++ b/Documentation/usb/gadget_sdio.txt @@ -0,0 +1,32 @@ +Introduction +============ + +Gadget serial driver is divided into two parts. +1. f_serial.c : Interacts with USB Gadget Layer +2. u_serial.c : Interacts with TTY Layer + +Gadget sdio driver adds capability to interact with SDIO Layer in +case modem device is inter-connected with sdio interface. + +S/W Description +=============== +Gadget SDIO driver is a simple bridge driver between usb serial +gadget and sdio abstraction layer. It registers with sdio +abstraction layer with read/write call backs and provides +USB connect/disconnect call backs usb gadget serial driver. + + +S/W Control Flow: +================= +Driver maintains two sdio channels, one for data and one for control. +Data pipe is dedicated sdio channel where as control is a muxed channel. +Incase of sdio control pipe, driver registers for control information +chagnes from modem side. Whenever new information is available, it +would queue a interrupt endpoint w/ new info. Laptop can also send the +DTR and RTS information as part of SET Encapsulated command. + +Data pipe of sdio channel also has notification mechanism to indicate +the READ/WRITE availability. When READ is available on SDIO pipe, +driver would read the data and hands it over to USB In ept and when +it receives the data from USB driver would queue the same to SDIO +channel(if write buffers are available). diff --git a/Documentation/usb/gadget_smd.txt b/Documentation/usb/gadget_smd.txt new file mode 100644 index 00000000000..18b93d8ff31 --- /dev/null +++ b/Documentation/usb/gadget_smd.txt @@ -0,0 +1,27 @@ +Introduction +============ + +Gadget serial driver is divided into two parts. +1. f_serial.c : Interacts with USB Gadget Layer +2. u_serial.c : Interacts with TTY Layer + +Gadget smd driver adds capability to interact with smd layer in +case modem device is inter-connected with smd interface. + +S/W Description +=============== +Gadget smd driver is a simple bridge driver between usb serial +gadget and smd abstraction layer. It registers with smd +abstraction layer with notification call back and provides +USB connect/disconnect call backs usb gadget serial driver. + + +S/W Control Flow: +================= +USB SMD driver registers w/ SMD driver and provides notification +call back. SMD Driver calls this call back whenever DATA is available +to read, buffer is available to write or modem control signals changed. +Upon receiving notification from SMD driver, USB driver appropriately +schedules read/write works. In case of control singals, USB driver +notifies gadget component with changed control information. + diff --git a/Documentation/usb/msm_otg.txt b/Documentation/usb/msm_otg.txt new file mode 100644 index 00000000000..29b6f1a635c --- /dev/null +++ b/Documentation/usb/msm_otg.txt @@ -0,0 +1,382 @@ +Introduction +============ +This driver implements Session Request Protocol (SRP) and Host negotiation +Protocol (HNP) described in On-The-Go (OTG) Supplement to the USB 2.0 +Specification. It also provides support for Accessory Charger Adapter (ACA) +defined in the Battery Charging Specification 1.1. + +These protocols provide a means for USB host devices to intelligently manage +power on VBUS and USB peripheral devices to become the host when paired with +another OTG device. + +Hardware description +==================== +USB hardware found in Qualcomm chipsets like MSM7x27, MSM7x30, QSD8x50 and +MSM8660 is compliant to USB 2.0 high speed On-The-Go protocol. +The transceiver, aka PHY is integrated on the chip and ULPI interface is used for +communication. + +USB hardware interfaces to the system memory via AHB BUS. DMA engine is included +to move all of the data to be transferred over the USB between USB core and +system memory. Device controller can support 16 endpoints of all types +(control/bulk/interrupt /isochronous) defined in USB 2.0 specification. The +host controller is compliant to EHCI specification. Directly connected USB 1.1 +Full/Low speed devices are supported without a companion controller by having +inbuilt Transaction Translator (TT). + +USB_HS_CLK, USB_HS_PCLK and USB_HS_CCLK are required for USB operation. +Phy feeds 60MHZ HS_CLK to link when ULPI interface is used. This clock needs to +be turned on only while resetting the link. HS_PCLK (Pbus clock) is required to +move data to/from hardware FIFO. This clock may not be required on targets like +MSM8660 where USB is part of smart peripheral subsystem. AXI bus frequency needs +to be kept at maximum value while USB data transfers are happening. HS_CCLK +(core clock) is introduced in MSM7x30 to get rid of dependency on AXI bus +frequency. + +The same irq line is shared across OTG, Device controller and Host controller +drivers. Phy is integrated on the chip and no gpios are required to connect link +and PHY. + +Phy can monitor VBUS and ID lines while operating in low power mode (LPM). But +leaving comparators ON in LPM increases power consumption. Hence VBUS line is +routed to PMIC hardware which can generate interrupt (when accessed by Apps +processor) or send RPC callback. This is also useful when an External LDO to +power up 3.3V of PHY is not installed. An internal LDO is turned upon +receiving notification from PMIC. Id line is not routed to PMIC. Hence OTG mode +can not be supported with this configuration and External LDO must be present. + +Hardware can generate interrupt when voltage on VBUS line is reached +above/below A-VBUS Valid and B-Session Valid threshold values defined in OTG +specification. Interrupt is generated when Id line is grounded i.e Micro-A +cable is connected. + +The following hardware features help in meeting the SRP and HNP protocol +timings. + +Hardware Assist Data-pulse (HADP): +--------------------------------- +When software programs HADP, Hardware start a data pulse of approximately 7ms +in duration and then automatically ceases the data pulsing. This automation +relieves software from controlling the data-pulse duration. This assist will +ensure data pulsing meets the OTG requirement of > 5ms and < 10ms. + +Hardware Assist Auto-Reset (HAAR): +--------------------------------- +When software programs HAAR, Hardware will automatically start a reset after +a connect event. This shortcuts the normal process where software is notified +of the connect event and starts the reset. Software will still receive +notification of the connect event but should not write the reset bit when the +HAAR is set. Software will be notified again after the reset is complete via +the enable change bit in the PORTSC register which cause a port change +interrupt. + +Hardware Assist B-Disconnect to A-Connect (HABA): +------------------------------------------------ +During Host negotiation Protocol(HNP), A-Device must enable pull-up on D+ as +soon as possible after detecting disconnect from B-device. + +When Software programs HABA, the Host Controller port is in suspend mode, and +the B-device disconnects, then this hardware assist begins. +1. Reset the OTG core +2. Write the OTG core into device mode. +3. Write the device run bit to a '1' and enable necessary interrupts including: + * USB Reset Enable (URE) : enables interrupt on usb bus reset to device + * Sleep Enable (SLE) : enables interrupt on device suspend + * Port Change Detect Enable (PCE) : enables interrupt on device connect + +When software has enabled this hardware assist, it must not interfere during the +transition and should not write any register in the core until it gets an +interrupt from the device controller signifying that a reset interrupt has +occurred or at least first verify that the core has entered device mode. + +The following hardware feature helps in supporting Accessory Charger Adapter: + +PHY Support for ID_A/B/C: +------------------------ +Accessory Charger Adapter has three ports to attach an OTG, charger and A or +B-device. So, based on what all device are attached to the ACA, it outputs a +state on the ID pin (i.e GROUND, ID_A, ID_B, ID_C, FLOAT). +USB PHY has support for these ID states. Once software enables this support, +PHY sets corresponding bit in its INTS register based on any changes in the +ID state. + +Software description +==================== + +This driver provides OTG functionality when Device controller driver (DCD) and +Host controller driver (HCD) are enabled. It is enabled even when one of the DCD +or HCD is enabled to use PHY initialization, clock management, register memory +mapping, low power mode (LPM) functionalities. + +Session Request Protocol (SRP): A-device may turn off power on VBUS upon user +request or A_WAIT_BCON timeout. SRP detection interrupt is enabled and +hardware is put into LPM. If Data pulse is detected, A-device starts a new +session by applying power on VBUS. Hardware Auto Assist Data pulse feature is +used to program Data pulse +When acting as a B-device, if SRP initial conditions (SE0 condition for +TB_SE0_SRP min and previous session was ended before TB_SSEND_SRP) are met, SRP +is initiated upon user request. Hardware Auto Assist Data pulse feature is +used to program Data pulse. If remote device does not turn on VBUS before +TB_SRP_FAIL, an error is reported to user space. + +Host Negotiation Protocol (HNP): A-device periodically polls B-device to check +host request status. When B-device returns true, A-device shall enable HNP and +suspend the bus with in THOST_REQ_SUSP. HNP polling is implemented in USB core +software. HCD registers a start_hnp callback method with EHCI framework. This +method is called after suspending the port if HNP is enabled. HCD notifies OTG +that B-device is suspended. A_AIDL_BDIS timer is kicked and waits for B-device +disconnection. If B-device does not signal disconnect within TA_AIDL_BDIS +timeout, session is closed by powering down VBUS. Otherwise A-device stops HCD +and starts DCD to enable pull-up. A-device again resumes host role if it had +observed bus idle for TA_BIDL_ADIS time. +B-device signals host_request true upon user request. DCD notifies OTG that +HNP is enabled and bus is idle. OTG driver disable pull-up by stopping DCD and +kick B_ASE0_BRST timer. If A-device does not signal connect with in +TB_ASE0_BRST, B-device resumes in peripheral role. Otherwise B-device assert +the bus reset and enumerate the A-device. + +MSM chipsets which have 45nm integrated PHY supports Attach Detection Protocol. +(A protocol which enables an OTG device to detect when a remote device has been +attached or detached without supplying VBUS). ADP support needs to be +implemented to efficiently supply/request power on VBUS. Leakage currents (i.e +VBUS applied but no peripheral is connected) are very less on MSM hardware. So +VBUS can be applied when Id becomes false. ADP may be never implemented in +this driver due to this reason. + +The state machine is implemented as described in the OTG specification. +A few exceptions are mentioned below: + +1. Host session request i.e a_bus_request input variable is automatically +asserted when Id becomes false and SRP is detected. +It is de-asserted When HCD suspends the bus and asserted again in case of +remote device remote wakeup. +2. Output variables like drv_vbus, loc_conn, loc_sof, adp_prb are not +maintained in the state machine as they serve no purpose. +3. Bus power down request i.e a_bus_drop is cleared when Micro-A cable is +connected so that non OTG device can be detected when Micro-A cable is +connected next time. +4. Input variables that determine SRP initial condition status b_se0_srp and +b_ssend_srp are not maintained in state machine processing. When a session is +ended i.e VBUS falls below B-Session Valid threshold, time stamp is taken and +is compared against the current time at the time of SRP initiation. + + +Controller gives interrupt for every 1 msec if 1MSIE (1 Msec interrupt enable) +is set. Timers used in OTG state machine can be implementing using 1MSEC +timer as a source. But hrtimer on MSM hardware can give at least 1/32KHZ +precision. So hrtimer framework is used to implement OTG timers. No two OTG +timers run in parallel. Hence one hrtimer is used for all OTG timers. + +OTG state machine processing can not be done in atomic context. Hence a worker +thread is created for processing the state machine events. A separate worker +thread is created instead of using default worker thread to meet OTG +specification timings. + +OTG supplement Revision 2.0 has made HNP timings less stringent compared to +Revision 1.3. TA_BDIS_ACON (The time with in A-Device should enable pull-up +upon B-device signals disconnect) has been changed to 150 msec from 3 msec. +DCD can be easily activated within 150 msec. Hence HABA is not used. +TB_ACON_BSE0 (The time with in B-device should reset the A-device) has been +changed to 150 msec from 1 sec. Host software would easily meet this timing +given that de-bounce delays and root hub port power stabilization delays are +not required during HNP. + +Accessory Charger Adapter (ACA): To support ACA there must be support in the +USB hardware (Controller and PHY) for the ID_A/B/C states. It should be able +to interrupt software for any ID state changes. On receiving this interrupt, +interrupt handler checks the current ID state and invokes OTG state machine +for further handling. Even if the USB Controller doesn't support these ID_A/B/C +states, driver can still detect the ID state transitions by depending on USB +PHY if the PHY supports these ID states. For this scenario, driver relies +on polling of PHY register to determine the ID state changes as long as an +ACA is attached to the system. This polling is implemented by using a timer +running at a frequency of 1 sec. This timer checks for the current ID state +and on detecting any change it invokes OTG state machine for further handling. + +Following are the actions performed by the driver as per the ID state: +* ID_FLOAT: Configure device to act as peripheral and allow charging if VBUS + is present, else move it to LPM (low power mode). +* ID_GROUND: Configure device to act as host and supply VBUS. +* ID_A: Configure device to act as host and don't supply VBUS. In this state + the device can charge as well. +* ID_B: Keep the device in IDLE state and allow charging. +* ID_C: Configure device to act as peripheral and allow charging. + +Design +====== + +The following goals are kept in mind while designing OTG state machine. + +1. Avoid User intervention when operating as a standard Host or standard +peripheral +2. Support host only and peripheral only modes +3. Put Hardware in LPM when ever possible +4. Pass OTG compliance tests +5. Report notification/error messages to user space +6. With ACA, allow charging in Host mode as well +7. Disable LPM as long as ID state polling is happening + +Power Management +================ + +System suspend is negated by acquiring wakelock while processing OTG state +machine, or while polling for the PHY ID state in case of ACA. +Wakelock is released: +1. After activating the DCD/HCD. It is the responsibility of DCD/HCD to +acquire wakelock if required. +2. After putting hardware in LPM. +3. No state machine events and timers are pending. This would cover scenarios +mentioned in (1) and (2). +4. After driver stops polling for ID state in case of ACA. + +Wake lock is re-acquired when state machine work is scheduled, which can +happen from interrupt (exiting LPM), sysfs entries (initiate SRP, clear +error, bus drop, etc), or from ID state polling routine. + +OTG driver provides set_suspend method for DCD/HCD to put hardware in LPM. DCD +can use this method upon bus suspend. HCD can use this method upon suspending +the root hub. + +LPM entering procedure: +1. Clear PHY interrupt latch registers. +2. Enable PHY comparators to detect Id, B-Session Valid interrupts while hardware +is in LPM. +3. Turn off PLL block on the PHY to achieve maximum power savings. +4. Put PHY in suspend mode by setting PHCD bit in PORTSC register. +5. Enable asynchronous interrupt so that PHY can generate interrupt when +clocks are disabled. +6. Disable all USB clocks. + +LPM exit procedure: +1. Enable USB clocks. +2. Disable asynchronous interrupt. +3. Put PHY out of suspend mode. This is not required when LPM is exited due to +hardware activity i.e asynchronous interrupt. + +SMP/multi-core +============== + +OTG state machine inputs like bus request, bus drop, srp_detect etc accessed +from interrupt context, and multiple process contexts. Hence atomic bit ops are +used. ulpi_read and ulpi_write functions can now be accessed from multiple +context, hence, these are protected using a spin_lock. + +Interface +========= +This driver provides the following methods to DCD and HCD. + +set_peripheral: DCD use this methods to register/unregister USB gadget. + +set_host: HCD use this method to register/unregister USB bus. Unlike gadget +framework, there are no standard methods to start/stop HCD. Hence start_host +method is introduced and must be initialized by HCD prior to registration. + +set_clk: HCD and DCD use this method to turn on/off USB_HS_CLK clk which is +required only while resetting the controller. + +start_srp: DCD use this method to initiate Session Request Protocol (SRP). +SRP may be initiated when function drivers use remote wakeup facility, when +B-Device wishes to become host. OTG driver programs Data-Pulsing if initial +condition of SRP are met. Otherwise proper error code is returned. + +set_suspend: DCD call this method when controller generates suspend +interrupt or generates reset/port change interrupt before HNP and during HNP. +If device is in B_PERIPHERAL state, HNP is initiated if host had enabled it. +If device is in A_PERIPHERAL state, A_BIDL_ADIS timer is kicked in case of +suspend interrupt. If this timer expires, A-device take back it's host role +and continue previous session. This timer is deleted in case of +reset/port change interrupts. +HCD call this method after suspending the root hub. Hardware is put into LPM. + +start_hnp: A-device needs to enable pull-up on D+ within TA_BIDL_ADIS after +suspending the bus i.e putting port in suspend state. EHCI stack can use this +method to notify OTG right after suspending the OTG port. OTG driver schedule +a work to stop host and enable pull-up on D+. + +send_event: USB core, DCD and HCD can use otg_send_event() API to send OTG +notification/error messages to user space. send_event method defined in +otg_transceiver is invoked by otg_send_event() API. An uevent is sent +with SUBSYSTEM=platform, MODULE=msm_otg and EVENT=, where event could +be one of the following events. + +OTG_EVENT_DEV_CONN_TMOUT: Device connection timeout or device not responding. +OTG_EVENT_NO_RESP_FOR_HNP_ENABLE: Device is not responding to B_HNP_ENABLE + feature request. +OTG_EVENT_HUB_NOT_SUPPORTED: Host does not support HUB class peripherals. +OTG_EVENT_DEV_NOT_SUPPORTED: Host does not support the attached peripheral. +OTG_EVENT_HNP_FAILED: HNP failed due to not meeting protocol timings. +OTG_EVENT_NO_RESP_FOR_SRP: No Response for B-device SRP request. + +set_power: DCD can use otg_set_power() API to specify about the current that +can be withdrawn from the VBUS for charging. Based on the current OTG state +and whether ACA is attached or not, OTG driver makes a decision about the +charging current and calls the charging APIs. + +The following sysfs nodes are provided at /sys/devices/platform/msm_otg + +pwr_down: This node can be used to control A-device session. a_bus_drop and +a_bus_req state machine input variables are altered to start/stop session. +Write to this node is invalid when device operating as a B-device. + +start_srp: This node can be used for requesting session. If all initial +conditions of SRP are met, SRP is initiated. Write to this node is invalid +when device operating as an A-device. + +clr_err: This node can be used to clear over-current condition. Write to this +node is invalid when operating as an B-device. Error condition is +automatically cleared when Id becomes false. + +The following sysfs nodes are provided at /sys/devices/platform/msm_hsusb/otg + +host_request: This node can be used for requesting host role. A-device shall +select a_hnp_support feature prior to configuration and poll B-device for host +request. When '1' is written to this node, host request is asserted. This node +can also be used for taking host role when A-device operating as a peripheral. + +hnp_avail: User space can check this node before requesting the host role. +Gadget controller driver asserts its internal variable hnp_avail when HNP +polling request is send by the Host. + +Dependencies +============ + +If USB clocks are controlled by modem processor, proc_comm interface is used +to turn on/off clocks. + +If VBUS power is controlled by modem processor, RPC interface is used to turn +on/off VBUS power. + +Config options +============== + +CONFIG_USB_MSM_ACA: Enable support for Accessory Charger Adapter (ACA) +CONFIG_ENABLE_MSM_OTG_A_WAIT_BCON_TIMEOUT: Enable A_WAIT_BCON timeout. VBUS +will be turned off and SRP detection is enabled upon this timeout. If this +config is not selected, VBUS will not be turned off when Mini/Micro-A cable +is connected. But hardware is put into LPM. + +Other +===== +On-The-Go and Embedded Host Supplement to the USB Revision 2.0 Specification +(Revision 2.0) found at http://www.usb.org/developers/onthego + +Known issues +============ +Phy instability issues are observed when vbus_valid interrupt is enabled. +Hence a_vbus_vld state machine variable is explicitly asserted after +a_wait_vrise timer expiration. + +Spurious interrupt is seen when trying to put PHY in Low Power Mode with +ID_A/B/C interrupts enabled in the PHY. As a result of which PHY doesn't stay +in LPM. Hence, ID_A/B/C interrupts are disabled before entering LPM, and +enabled while returning. + +To do +===== + +Verify SRP detection on all targets. + +Phy instability issues are observed when A-Vbus Valid interrupt is enabled. +But without this interrupt over current condition can not be determined. Root +cause analysis for PHY instability issue and alternative methods like PMIC +interrupt are being pursued. diff --git a/Makefile b/Makefile index f124b185b8e..800a54bc85a 100644 --- a/Makefile +++ b/Makefile @@ -330,7 +330,7 @@ include $(srctree)/scripts/Kbuild.include AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld -CC = $(CROSS_COMPILE)gcc +REAL_CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm @@ -345,6 +345,10 @@ KALLSYMS = scripts/kallsyms PERL = perl CHECK = sparse +# Use the wrapper for the compiler. This wrapper scans for new +# warnings and causes the build to stop upon encountering them. +CC = $(srctree)/scripts/gcc-wrapper.py $(REAL_CC) + CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ -Wbitwise -Wno-return-void $(CF) CFLAGS_MODULE = diff --git a/arch/Kconfig b/arch/Kconfig index 26b0e2397a5..6ea895daaa5 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -148,6 +148,13 @@ config HAVE_HW_BREAKPOINT bool depends on PERF_EVENTS +config HAVE_HW_BRKPT_RESERVED_RW_ACCESS + bool + depends on HAVE_HW_BREAKPOINT + help + Some of the hardware might not have r/w access beyond a certain number + of breakpoint register access. + config HAVE_MIXED_BREAKPOINTS_REGS bool depends on HAVE_HW_BREAKPOINT diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 81f0b93a0cb..f150c181fe9 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -62,7 +62,7 @@ config GENERIC_CLOCKEVENTS config GENERIC_CLOCKEVENTS_BROADCAST bool depends on GENERIC_CLOCKEVENTS - default y if SMP + default y if SMP && !LOCAL_TIMERS config KTIME_SCALAR bool @@ -627,6 +627,10 @@ config ARCH_MSM select GENERIC_CLOCKEVENTS select ARCH_REQUIRE_GPIOLIB select CLKDEV_LOOKUP + select ARCH_HAS_CPUFREQ + select GENERIC_GPIO + select GENERIC_TIME + select GENERIC_ALLOCATOR help Support for Qualcomm MSM/QSD based systems. This runs on the apps processor of the MSM/QSD and depends on a shared memory @@ -1033,6 +1037,19 @@ config ARM_TIMER_SP804 source arch/arm/mm/Kconfig +config DONT_RESERVE_FROM_MOVABLE_ZONE + def_bool y + depends on MEMORY_HOTPLUG + +config RESERVE_FIRST_PAGE + bool + default n + help + Reserve the first page at PHYS_OFFSET. The first + physical page is used by many platforms for warm + boot operations. Reserve this page so that it is + not allocated by the kernel. + config IWMMXT bool "Enable iWMMXt support" depends on CPU_XSCALE || CPU_XSC3 || CPU_MOHAWK || CPU_PJ4 @@ -1234,6 +1251,29 @@ config ARM_ERRATA_754327 This workaround defines cpu_relax() as smp_mb(), preventing correctly written polling loops from denying visibility of updates to memory. +config PL310_ERRATA_727915 + bool "Background Clean & Invalidate by Way operation can cause data corruption" + depends on CACHE_L2X0 + help + PL310 implements the Clean & Invalidate by Way L2 cache maintenance + operation (offset 0x7FC). This operation runs in background so that + PL310 can handle normal accesses while it is in progress. Under very + rare circumstances, due to this erratum, write data can be lost when + PL310 treats a cacheable write transaction during a Clean & + Invalidate by Way operation. + +config KSAPI + tristate "KSAPI support (EXPERIMENTAL)" + depends on ARCH_MSM_SCORPION || ARCH_MSM_KRAIT + default n + help + KSAPI: Performance monitoring tool for linux. + KSAPI records performance statistics for Snapdragon linux platform. + It uses the /proc FS as a means to exchange configuration data and + counter statistics. It can monitor the counter statistics for + Scorpion processor supported hardware performance counters on a per + thread basis or AXI counters on an overall system basis. + endmenu source "arch/arm/common/Kconfig" @@ -1311,9 +1351,9 @@ config SMP depends on REALVIEW_EB_ARM11MP || REALVIEW_EB_A9MP || \ MACH_REALVIEW_PB11MP || MACH_REALVIEW_PBX || ARCH_OMAP4 || \ ARCH_EXYNOS4 || ARCH_TEGRA || ARCH_U8500 || ARCH_VEXPRESS_CA9X4 || \ - ARCH_MSM_SCORPIONMP || ARCH_SHMOBILE + MSM_SMP || ARCH_SHMOBILE select USE_GENERIC_SMP_HELPERS - select HAVE_ARM_SCU if !ARCH_MSM_SCORPIONMP + select HAVE_ARM_SCU if !MSM_SMP help This enables support for systems with more than one CPU. If you have a system with only one CPU, like most personal computers, say N. If @@ -1395,9 +1435,11 @@ config HOTPLUG_CPU config LOCAL_TIMERS bool "Use local timer interrupts" - depends on SMP + depends on SMP && (REALVIEW_EB_ARM11MP || MACH_REALVIEW_PB11MP || \ + REALVIEW_EB_A9MP || MACH_REALVIEW_PBX || ARCH_OMAP4 || ARCH_U8500 || \ + MSM_SMP) default y - select HAVE_ARM_TWD if (!ARCH_MSM_SCORPIONMP && !EXYNOS4_MCT) + select HAVE_ARM_TWD if (!MSM_SMP && !EXYNOS4_MCT) help Enable support for local timers on SMP platforms, rather then the legacy IPI broadcast method. Local timers allows the system @@ -1537,8 +1579,26 @@ config HW_PERF_EVENTS Enable hardware performance counter support for perf events. If disabled, perf events will use software events only. +config VMALLOC_RESERVE + hex "Reserved vmalloc space" + default 0x08000000 + depends on MMU + help + Reserved vmalloc space if not specified on the kernel commandline. + source "mm/Kconfig" +config ARCH_MEMORY_PROBE + def_bool n + depends on MEMORY_HOTPLUG + +config ARCH_MEMORY_REMOVE + def_bool n + depends on MEMORY_HOTPLUG + +config DONT_RESERVE_FROM_MOVABLE_ZONE + def_bool n + config FORCE_MAX_ZONEORDER int "Maximum zone order" if ARCH_SHMOBILE range 11 64 if ARCH_SHMOBILE @@ -1680,6 +1740,17 @@ config ARM_FLUSH_CONSOLE_ON_RESTART released if it failed to be acquired, which will cause all the pending messages to be flushed. +config CP_ACCESS + tristate "CP register access tool" + default m + help + Provide support for Coprocessor register access using /sys + interface. Read and write to CP registers from userspace + through sysfs interface. A sys file (cp_rw) will be created under + /sys/devices/system/cpaccess/cpaccess0. + + If unsure, say N. + endmenu menu "Boot options" @@ -1940,6 +2011,14 @@ source "drivers/cpuidle/Kconfig" endmenu +config CPU_FREQ_MSM + bool + depends on CPU_FREQ && ARCH_MSM + default y + help + This enables the CPUFreq driver for Qualcomm CPUs. + If in doubt, say Y. + menu "Floating point emulation" comment "At least one emulation must be selected" @@ -2029,7 +2108,7 @@ menu "Power management options" source "kernel/power/Kconfig" config ARCH_SUSPEND_POSSIBLE - depends on !ARCH_S5P64X0 && !ARCH_S5PC100 + depends on !ARCH_S5P64X0 && !ARCH_S5PC100 && !ARCH_FSM9XXX depends on CPU_ARM920T || CPU_ARM926T || CPU_SA1100 || \ CPU_V6 || CPU_V6K || CPU_V7 || CPU_XSC3 || CPU_XSCALE def_bool y diff --git a/arch/arm/Makefile b/arch/arm/Makefile index f5b2b390c8f..96981603367 100644 --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -255,6 +255,7 @@ core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/ core-y += $(machdirs) $(platdirs) drivers-$(CONFIG_OPROFILE) += arch/arm/oprofile/ +core-y += arch/arm/perfmon/ libs-y := arch/arm/lib/ $(libs-y) diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S index e58603b0006..25914b0aca6 100644 --- a/arch/arm/boot/compressed/head.S +++ b/arch/arm/boot/compressed/head.S @@ -27,6 +27,14 @@ .macro writeb, ch, rb mcr p14, 0, \ch, c0, c5, 0 .endm +#elif defined(CONFIG_CPU_V7) + .macro loadsp, rb, tmp + .endm + .macro writeb, ch, rb +wait: mrc p14, 0, pc, c0, c1, 0 + bcs wait + mcr p14, 0, \ch, c0, c5, 0 + .endm #elif defined(CONFIG_CPU_XSCALE) .macro loadsp, rb, tmp .endm diff --git a/arch/arm/common/Kconfig b/arch/arm/common/Kconfig index cf82a884a5c..66ed0c384b2 100644 --- a/arch/arm/common/Kconfig +++ b/arch/arm/common/Kconfig @@ -1,5 +1,6 @@ config ARM_GIC bool + select MSM_SHOW_RESUME_IRQ config ARM_VIC bool diff --git a/arch/arm/common/cpaccess.c b/arch/arm/common/cpaccess.c new file mode 100644 index 00000000000..241e3395392 --- /dev/null +++ b/arch/arm/common/cpaccess.c @@ -0,0 +1,253 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * CP parameters + */ +struct cp_params { + unsigned long cp; + unsigned long op1; + unsigned long op2; + unsigned long crn; + unsigned long crm; + unsigned long write_value; + char rw; +}; + +static struct semaphore cp_sem; +static int cpu; + +static DEFINE_PER_CPU(struct cp_params, cp_param) + = { 15, 0, 0, 0, 0, 0, 'r' }; + +static struct sysdev_class cpaccess_sysclass = { + .name = "cpaccess", +}; + +/* + * get_asm_value - Dummy fuction + * @write_val: Write value incase of a CP register write operation. + * + * This function is just a placeholder. The first 2 instructions + * will be inserted to perform MRC/MCR instruction and a return. + * See do_cpregister_rw function. Value passed to function is + * accessed from r0 register. + */ +static noinline unsigned long cpaccess_dummy(unsigned long write_val) +{ + asm("mrc p15, 0, r0, c0, c0, 0\n\t"); + asm("bx lr\n\t"); + return 0xBEEF; +} __attribute__((aligned(32))) + +/* + * get_asm_value - Read/Write CP registers + * @ret: Pointer to return value in case of CP register + * read op. + * + */ +static void get_asm_value(void *ret) +{ + *(unsigned long *)ret = + cpaccess_dummy(per_cpu(cp_param.write_value, cpu)); +} + +/* + * dp_cpregister_rw - Read/Write CP registers + * @write: 1 for Write and 0 for Read operation + * + * Returns value read from CP register + */ +static unsigned long do_cpregister_rw(int write) +{ + unsigned long opcode, ret, *p_opcode; + + /* + * Mask the crn, crm, op1, op2 and cp values so they do not + * interfer with other fields of the op code. + */ + per_cpu(cp_param.cp, cpu) &= 0xF; + per_cpu(cp_param.crn, cpu) &= 0xF; + per_cpu(cp_param.crm, cpu) &= 0xF; + per_cpu(cp_param.op1, cpu) &= 0x7; + per_cpu(cp_param.op2, cpu) &= 0x7; + + /* + * Base MRC opcode for MIDR is EE100010, + * MCR is 0xEE000010 + */ + opcode = (write == 1 ? 0xEE000010 : 0xEE100010); + opcode |= (per_cpu(cp_param.crn, cpu)<<16) | + (per_cpu(cp_param.crm, cpu)<<0) | + (per_cpu(cp_param.op1, cpu)<<21) | + (per_cpu(cp_param.op2, cpu)<<5) | + (per_cpu(cp_param.cp, cpu) << 8); + + /* + * Grab address of the Dummy function, insert MRC/MCR + * instruction and a return instruction ("bx lr"). Do + * a D cache clean and I cache invalidate after inserting + * new code. + */ + p_opcode = (unsigned long *)&cpaccess_dummy; + *p_opcode++ = opcode; + *p_opcode-- = 0xE12FFF1E; + __cpuc_coherent_kern_range((unsigned long)p_opcode, + ((unsigned long)p_opcode + (sizeof(long) * 2))); + +#ifdef CONFIG_SMP + /* + * Use smp_call_function_single to do CPU core specific + * get_asm_value function call. + */ + if (smp_call_function_single(cpu, get_asm_value, &ret, 1)) + printk(KERN_ERR "Error cpaccess smp call single\n"); +#else + get_asm_value(&ret); +#endif + + return ret; +} + +/* + * cp_register_write_sysfs - sysfs interface for writing to + * CP register + * @dev: sys device + * @attr: device attribute + * @buf: write value + * @cnt: not used + * + */ +static ssize_t cp_register_write_sysfs(struct sys_device *dev, + struct sysdev_attribute *attr, const char *buf, size_t cnt) +{ + unsigned long op1, op2, crn, crm, cp = 15, write_value, ret; + char rw; + if (down_timeout(&cp_sem, 6000)) + return -ERESTARTSYS; + + sscanf(buf, "%lu:%lu:%lu:%lu:%lu:%c:%lx:%d", &cp, &op1, &crn, + &crm, &op2, &rw, &write_value, &cpu); + per_cpu(cp_param.cp, cpu) = cp; + per_cpu(cp_param.op1, cpu) = op1; + per_cpu(cp_param.crn, cpu) = crn; + per_cpu(cp_param.crm, cpu) = crm; + per_cpu(cp_param.op2, cpu) = op2; + per_cpu(cp_param.rw, cpu) = rw; + per_cpu(cp_param.write_value, cpu) = write_value; + + if (per_cpu(cp_param.rw, cpu) == 'w') { + do_cpregister_rw(1); + ret = cnt; + } + + if ((per_cpu(cp_param.rw, cpu) != 'w') && + (per_cpu(cp_param.rw, cpu) != 'r')) { + ret = -1; + printk(KERN_INFO "Wrong Entry for 'r' or 'w'. \ + Use cp:op1:crn:crm:op2:r/w:write_value.\n"); + } + + return cnt; +} + +/* + * cp_register_read_sysfs - sysfs interface for reading CP registers + * @dev: sys device + * @attr: device attribute + * @buf: write value + * + * Code to read in the CPxx crn, crm, op1, op2 variables, or into + * the base MRC opcode, store to executable memory, clean/invalidate + * caches and then execute the new instruction and provide the + * result to the caller. + */ +static ssize_t cp_register_read_sysfs(struct sys_device *dev, + struct sysdev_attribute *attr, char *buf) +{ + int ret; + ret = sprintf(buf, "%lx\n", do_cpregister_rw(0)); + + if (cp_sem.count <= 0) + up(&cp_sem); + + return ret; +} + +/* + * Setup sysfs files + */ +SYSDEV_ATTR(cp_rw, 0644, cp_register_read_sysfs, + cp_register_write_sysfs); + +static struct sys_device device_cpaccess = { + .id = 0, + .cls = &cpaccess_sysclass, +}; + +/* + * init_cpaccess_sysfs - initialize sys devices + */ +static int __init init_cpaccess_sysfs(void) +{ + int error = sysdev_class_register(&cpaccess_sysclass); + + if (!error) + error = sysdev_register(&device_cpaccess); + else + printk(KERN_ERR "Error initializing cpaccess \ + interface\n"); + + if (!error) + error = sysdev_create_file(&device_cpaccess, + &attr_cp_rw); + else { + printk(KERN_ERR "Error initializing cpaccess \ + interface\n"); + sysdev_unregister(&device_cpaccess); + sysdev_class_unregister(&cpaccess_sysclass); + } + + sema_init(&cp_sem, 1); + + return error; +} + +static void __exit exit_cpaccess_sysfs(void) +{ + sysdev_remove_file(&device_cpaccess, &attr_cp_rw); + sysdev_unregister(&device_cpaccess); + sysdev_class_unregister(&cpaccess_sysclass); +} + +module_init(init_cpaccess_sysfs); +module_exit(exit_cpaccess_sysfs); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/common/gic.c b/arch/arm/common/gic.c index 4ddd0a6ac7f..8996f06a76a 100644 --- a/arch/arm/common/gic.c +++ b/arch/arm/common/gic.c @@ -28,10 +28,12 @@ #include #include #include +#include #include #include #include +#include static DEFINE_SPINLOCK(irq_controller_lock); @@ -42,6 +44,11 @@ struct gic_chip_data { unsigned int irq_offset; void __iomem *dist_base; void __iomem *cpu_base; + unsigned int max_irq; +#ifdef CONFIG_PM + unsigned int wakeup_irqs[32]; + unsigned int enabled_irqs[32]; +#endif }; /* @@ -55,6 +62,7 @@ struct irq_chip gic_arch_extn = { .irq_retrigger = NULL, .irq_set_type = NULL, .irq_set_wake = NULL, + .irq_disable = NULL, }; #ifndef MAX_GIC_NR @@ -93,6 +101,7 @@ static void gic_mask_irq(struct irq_data *d) if (gic_arch_extn.irq_mask) gic_arch_extn.irq_mask(d); spin_unlock(&irq_controller_lock); + } static void gic_unmask_irq(struct irq_data *d) @@ -106,6 +115,104 @@ static void gic_unmask_irq(struct irq_data *d) spin_unlock(&irq_controller_lock); } +static void gic_disable_irq(struct irq_data *d) +{ + if (gic_arch_extn.irq_disable) + gic_arch_extn.irq_disable(d); +} + +#ifdef CONFIG_PM +static int gic_suspend_one(struct gic_chip_data *gic) +{ + unsigned int i; + void __iomem *base = gic->dist_base; + + for (i = 0; i * 32 < gic->max_irq; i++) { + gic->enabled_irqs[i] + = readl_relaxed(base + GIC_DIST_ENABLE_SET + i * 4); + /* disable all of them */ + writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4); + /* enable the wakeup set */ + writel_relaxed(gic->wakeup_irqs[i], + base + GIC_DIST_ENABLE_SET + i * 4); + } + mb(); + return 0; +} + +static int gic_suspend(void) +{ + int i; + for (i = 0; i < MAX_GIC_NR; i++) + gic_suspend_one(&gic_data[i]); + return 0; +} + +extern int msm_show_resume_irq_mask; + +static void gic_show_resume_irq(struct gic_chip_data *gic) +{ + unsigned int i; + u32 enabled; + unsigned long pending[32]; + void __iomem *base = gic->dist_base; + + if (!msm_show_resume_irq_mask) + return; + + spin_lock(&irq_controller_lock); + for (i = 0; i * 32 < gic->max_irq; i++) { + enabled = readl_relaxed(base + GIC_DIST_ENABLE_CLEAR + i * 4); + pending[i] = readl_relaxed(base + GIC_DIST_PENDING_SET + i * 4); + pending[i] &= enabled; + } + spin_unlock(&irq_controller_lock); + + for (i = find_first_bit(pending, gic->max_irq); + i < gic->max_irq; + i = find_next_bit(pending, gic->max_irq, i+1)) { + pr_warning("%s: %d triggered", __func__, + i + gic->irq_offset); + } +} + +static void gic_resume_one(struct gic_chip_data *gic) +{ + unsigned int i; + void __iomem *base = gic->dist_base; + + gic_show_resume_irq(gic); + for (i = 0; i * 32 < gic->max_irq; i++) { + /* disable all of them */ + writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4); + /* enable the enabled set */ + writel_relaxed(gic->enabled_irqs[i], + base + GIC_DIST_ENABLE_SET + i * 4); + } + mb(); +} + +static void gic_resume(void) +{ + int i; + for (i = 0; i < MAX_GIC_NR; i++) + gic_resume_one(&gic_data[i]); +} + +static struct syscore_ops gic_syscore_ops = { + .suspend = gic_suspend, + .resume = gic_resume, +}; + +static int __init gic_init_sys(void) +{ + register_syscore_ops(&gic_syscore_ops); + return 0; +} +arch_initcall(gic_init_sys); + +#endif + static void gic_eoi_irq(struct irq_data *d) { if (gic_arch_extn.irq_eoi) { @@ -202,6 +309,20 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val, static int gic_set_wake(struct irq_data *d, unsigned int on) { int ret = -ENXIO; + unsigned int reg_offset, bit_offset; + unsigned int gicirq = gic_irq(d); + struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d); + + /* per-cpu interrupts cannot be wakeup interrupts */ + WARN_ON(gicirq < 32); + + reg_offset = gicirq / 32; + bit_offset = gicirq % 32; + + if (on) + gic_data->wakeup_irqs[reg_offset] |= 1 << bit_offset; + else + gic_data->wakeup_irqs[reg_offset] &= ~(1 << bit_offset); if (gic_arch_extn.irq_set_wake) ret = gic_arch_extn.irq_set_wake(d, on); @@ -250,6 +371,7 @@ static struct irq_chip gic_chip = { #ifdef CONFIG_SMP .irq_set_affinity = gic_set_affinity, #endif + .irq_disable = gic_disable_irq, .irq_set_wake = gic_set_wake, }; @@ -324,7 +446,10 @@ static void __init gic_dist_init(struct gic_chip_data *gic, set_irq_flags(i, IRQF_VALID | IRQF_PROBE); } + gic->max_irq = gic_irqs; + writel_relaxed(1, base + GIC_DIST_CTRL); + mb(); } static void __cpuinit gic_cpu_init(struct gic_chip_data *gic) @@ -348,6 +473,7 @@ static void __cpuinit gic_cpu_init(struct gic_chip_data *gic) writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); writel_relaxed(1, base + GIC_CPU_CTRL); + mb(); } void __init gic_init(unsigned int gic_nr, unsigned int irq_start, @@ -399,5 +525,47 @@ void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) /* this always happens on GIC0 */ writel_relaxed(map << 16 | irq, gic_data[0].dist_base + GIC_DIST_SOFTINT); + mb(); } #endif + +/* before calling this function the interrupts should be disabled + * and the irq must be disabled at gic to avoid spurious interrupts */ +bool gic_is_spi_pending(unsigned int irq) +{ + struct irq_data *d = irq_get_irq_data(irq); + struct gic_chip_data *gic_data = &gic_data[0]; + u32 mask, val; + + WARN_ON(!irqs_disabled()); + spin_lock(&irq_controller_lock); + mask = 1 << (gic_irq(d) % 32); + val = readl(gic_dist_base(d) + + GIC_DIST_ENABLE_SET + (gic_irq(d) / 32) * 4); + /* warn if the interrupt is enabled */ + WARN_ON(val & mask); + val = readl(gic_dist_base(d) + + GIC_DIST_PENDING_SET + (gic_irq(d) / 32) * 4); + spin_unlock(&irq_controller_lock); + return (bool) (val & mask); +} + +/* before calling this function the interrupts should be disabled + * and the irq must be disabled at gic to avoid spurious interrupts */ +void gic_clear_spi_pending(unsigned int irq) +{ + struct gic_chip_data *gic_data = &gic_data[0]; + struct irq_data *d = irq_get_irq_data(irq); + + u32 mask, val; + WARN_ON(!irqs_disabled()); + spin_lock(&irq_controller_lock); + mask = 1 << (gic_irq(d) % 32); + val = readl(gic_dist_base(d) + + GIC_DIST_ENABLE_SET + (gic_irq(d) / 32) * 4); + /* warn if the interrupt is enabled */ + WARN_ON(val & mask); + writel(mask, gic_dist_base(d) + + GIC_DIST_PENDING_CLEAR + (gic_irq(d) / 32) * 4); + spin_unlock(&irq_controller_lock); +} diff --git a/arch/arm/configs/apq8064_defconfig b/arch/arm/configs/apq8064_defconfig new file mode 100644 index 00000000000..e69865366dd --- /dev/null +++ b/arch/arm/configs/apq8064_defconfig @@ -0,0 +1,134 @@ +CONFIG_EXPERIMENTAL=y +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=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_FAIR_GROUP_SCHED is not set +CONFIG_RT_GROUP_SCHED=y +CONFIG_NAMESPACES=y +# CONFIG_UTS_NS is not set +# CONFIG_IPC_NS is not set +# CONFIG_USER_NS is not set +# CONFIG_PID_NS is not set +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +CONFIG_EMBEDDED=y +CONFIG_KALLSYMS_ALL=y +CONFIG_ASHMEM=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=m +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_APQ8064=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_CPU_HAS_L2_PMU=y +# CONFIG_MSM_JTAG_V7 is not set +# CONFIG_MSM_FIQ_SUPPORT is not set +# CONFIG_MSM_PROC_COMM is not set +# CONFIG_MSM_DALRPC is not set +# CONFIG_MSM_HW3D is not set +CONFIG_MSM_DIRECT_SCLK_ACCESS=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +# CONFIG_SMP_ON_UP is not set +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0x19000000 +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_MISC_DEVICES=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +CONFIG_INPUT_JOYSTICK=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +CONFIG_SERIAL_MSM=y +# CONFIG_SERIAL_MSM_CLOCK_CONTROL is not set +CONFIG_SERIAL_MSM_HSL=y +CONFIG_SERIAL_MSM_HSL_CONSOLE=y +CONFIG_SERIAL_MSM_HS=y +CONFIG_HW_RANDOM=y +CONFIG_DCC_TTY=y +CONFIG_GPIOLIB=y +# CONFIG_HWMON is not set +# CONFIG_MFD_SUPPORT is not set +# CONFIG_HID_SUPPORT is not set +# CONFIG_USB_SUPPORT is not set +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +# CONFIG_LEDS_MSM_PMIC is not set +CONFIG_STAGING=y +# CONFIG_STAGING_EXCLUDE_BUILD is not set +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_MSM_SSBI=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_PARTITION_ADVANCED=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_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_MEMORY_INIT=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_DEBUG_ERRORS=y +CONFIG_KEYS=y +CONFIG_CRYPTO_AUTHENC=y +CONFIG_CRYPTO_CBC=y +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_MD4=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_SHA1=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_ARC4=y +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_DEFLATE=y +# CONFIG_CRYPTO_HW is not set +CONFIG_CRC_CCITT=y +CONFIG_LIBCRC32C=y diff --git a/arch/arm/configs/msm7627-perf_defconfig b/arch/arm/configs/msm7627-perf_defconfig new file mode 100644 index 00000000000..63bbd30d1fe --- /dev/null +++ b/arch/arm/configs/msm7627-perf_defconfig @@ -0,0 +1,298 @@ +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="-perf" +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=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_KALLSYMS_ALL=y +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=m +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM7X27=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_MSM7X00A_USE_DG_TIMER=y +# CONFIG_MSM_FIQ_SUPPORT is not set +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG4=y +CONFIG_MSM_ONCRPCROUTER=y +CONFIG_MSM_RMT_STORAGE_CLIENT=y +# CONFIG_MSM_HW3D is not set +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="init=/sbin/init root=/dev/ram rw initrd=0x11000000,16M console=ttyDCC0 mem=88M" +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT_DETAILS=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_VFP=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_WAKELOCK=y +CONFIG_PM_RUNTIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_INET_DIAG is not set +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=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_IPV6_SUBTREES=y +CONFIG_NETFILTER=y +CONFIG_NETFILTER_NETLINK_LOG=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_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=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_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_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_CLS_U32=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_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_BCSP=y +CONFIG_BT_HCIUART_IBS=y +CONFIG_BT_MSM_SLEEP=y +CONFIG_MSM_BT_POWER=y +# CONFIG_WIRELESS_EXT_SYSFS is not set +CONFIG_RFKILL=y +# CONFIG_RFKILL_PM is not set +CONFIG_MTD=y +CONFIG_MTD_TESTS=m +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=8 +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_MISC_DEVICES=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +CONFIG_LIBRA_SDIOIF=m +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_MSM_LEGACY=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_KEYCHORD=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_GPIO=y +# CONFIG_SERIO is not set +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_MSM_HS=y +# CONFIG_HW_RANDOM is not set +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_SUPPLY=y +CONFIG_BATTERY_MSM=y +# CONFIG_HWMON is not set +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEO_HELPER_CHIPS_AUTO=y +CONFIG_MSM_KGSL=y +CONFIG_VIDEO_OUTPUT_CONTROL=y +CONFIG_FB=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_TRIPLE_BUFFER=y +CONFIG_FB_MSM_MDP30=y +CONFIG_FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_SOUND=y +CONFIG_SND=y +# CONFIG_SND_DRIVERS is not set +# CONFIG_SND_ARM is not set +CONFIG_SND_SOC=y +CONFIG_SND_MSM_SOC=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +CONFIG_USB_EHCI_MSM_72K=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_MSM_72K=y +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_BLOCK_MINORS=32 +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_MMC_MSM=y +CONFIG_MMC_MSM_SDIO_SUPPORT=y +CONFIG_MMC_MSM_SDC1_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED=y +CONFIG_SWITCH=y +CONFIG_SWITCH_GPIO=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DEBUG=y +CONFIG_STAGING=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_LOGGER=y +CONFIG_ANDROID_RAM_CONSOLE=y +CONFIG_ANDROID_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_DISABLE_TAGS_ECC=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +CONFIG_PARTITION_ADVANCED=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_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_INFO=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRC_CCITT=y diff --git a/arch/arm/configs/msm7627_defconfig b/arch/arm/configs/msm7627_defconfig new file mode 100644 index 00000000000..0f9f76f01fb --- /dev/null +++ b/arch/arm/configs/msm7627_defconfig @@ -0,0 +1,299 @@ +CONFIG_EXPERIMENTAL=y +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=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_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=m +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM7X27=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_MSM7X00A_USE_DG_TIMER=y +# CONFIG_MSM_FIQ_SUPPORT is not set +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG4=y +CONFIG_MSM_ONCRPCROUTER=y +CONFIG_MSM_RMT_STORAGE_CLIENT=y +# CONFIG_MSM_HW3D is not set +CONFIG_STRICT_MEMORY_RWX=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="init=/sbin/init root=/dev/ram rw initrd=0x11000000,16M console=ttyDCC0 mem=88M" +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT_DETAILS=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_VFP=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_WAKELOCK=y +CONFIG_PM_RUNTIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_INET_DIAG is not set +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=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_IPV6_SUBTREES=y +CONFIG_NETFILTER=y +CONFIG_NETFILTER_NETLINK_LOG=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_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=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_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_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_CLS_U32=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_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_BCSP=y +CONFIG_BT_HCIUART_IBS=y +CONFIG_BT_MSM_SLEEP=y +CONFIG_MSM_BT_POWER=y +# CONFIG_WIRELESS_EXT_SYSFS is not set +CONFIG_RFKILL=y +# CONFIG_RFKILL_PM is not set +CONFIG_MTD=y +CONFIG_MTD_TESTS=m +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=8 +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_MISC_DEVICES=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +CONFIG_LIBRA_SDIOIF=m +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_MSM_LEGACY=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_KEYCHORD=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_GPIO=y +# CONFIG_SERIO is not set +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_MSM_HS=y +# CONFIG_HW_RANDOM is not set +CONFIG_DCC_TTY=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_SUPPLY=y +CONFIG_BATTERY_MSM=y +# CONFIG_HWMON is not set +CONFIG_MSM_KGSL=y +CONFIG_VIDEO_OUTPUT_CONTROL=y +CONFIG_FB=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_TRIPLE_BUFFER=y +CONFIG_FB_MSM_MDP30=y +CONFIG_FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_SOUND=y +CONFIG_SND=y +# CONFIG_SND_DRIVERS is not set +# CONFIG_SND_ARM is not set +CONFIG_SND_SOC=y +CONFIG_SND_MSM_SOC=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +CONFIG_USB_EHCI_MSM_72K=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_MSM_72K=y +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_BLOCK_MINORS=32 +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_MMC_MSM=y +CONFIG_MMC_MSM_SDIO_SUPPORT=y +CONFIG_MMC_MSM_SDC1_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED=y +CONFIG_SWITCH=y +CONFIG_SWITCH_GPIO=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DEBUG=y +CONFIG_STAGING=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_LOGGER=y +CONFIG_ANDROID_RAM_CONSOLE=y +CONFIG_ANDROID_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_DISABLE_TAGS_ECC=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +CONFIG_PARTITION_ADVANCED=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_TIMER_STATS=y +CONFIG_DEBUG_MUTEXES=y +CONFIG_DEBUG_SPINLOCK_SLEEP=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_LATENCYTOP=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRC_CCITT=y diff --git a/arch/arm/configs/msm7627a-perf_defconfig b/arch/arm/configs/msm7627a-perf_defconfig new file mode 100644 index 00000000000..cef8e5422d6 --- /dev/null +++ b/arch/arm/configs/msm7627a-perf_defconfig @@ -0,0 +1,315 @@ +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="$(KERNEL_LOCAL_VERSION)-perf" +# CONFIG_LOCALVERSION_AUTO is not set +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_RESOURCE_COUNTERS=y +CONFIG_CGROUP_SCHED=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_KALLSYMS_ALL=y +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=m +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM7X27=y +CONFIG_MSM_SOC_REV_A=y +# CONFIG_MACH_MSM7X27_SURF is not set +# CONFIG_MACH_MSM7X27_FFA is not set +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_MSM7X00A_USE_DG_TIMER=y +# CONFIG_MSM_JTAG_V7 is not set +# CONFIG_MSM_FIQ_SUPPORT is not set +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG4=y +# CONFIG_MSM_SMD_DEBUG is not set +# CONFIG_MSM_RESET_MODEM is not set +# CONFIG_MSM_SMD_NMEA is not set +# CONFIG_MSM_SMD_QMI is not set +CONFIG_MSM_ONCRPCROUTER=y +# CONFIG_MSM_RPCSERVER_TIME_REMOTE is not set +CONFIG_MSM_RMT_STORAGE_CLIENT=y +# CONFIG_MSM_HW3D is not set +CONFIG_MSM7X27A_AUDIO=y +CONFIG_MSM_DMA_TEST=y +CONFIG_BT_MSM_PINTEST=y +CONFIG_MSM_RPC_VIBRATOR=y +CONFIG_PM8XXX_RPC_VIBRATOR=y +CONFIG_ARM_THUMBEE=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +# CONFIG_CP_ACCESS is not set +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="init=/sbin/init root=/dev/ram rw initrd=0x11000000,16M console=ttyDCC0 mem=88M" +CONFIG_CPU_FREQ=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_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_WAKELOCK=y +CONFIG_PM_RUNTIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=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_IPV6_SUBTREES=y +CONFIG_NETFILTER=y +CONFIG_NETFILTER_NETLINK_LOG=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_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=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_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_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=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_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_IBS=y +CONFIG_BT_MSM_SLEEP=y +CONFIG_RFKILL=y +CONFIG_MTD=y +CONFIG_MTD_TESTS=m +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +# CONFIG_BLK_DEV is not set +CONFIG_MISC_DEVICES=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_NETDEVICES=y +CONFIG_NET_ETHERNET=y +CONFIG_SMC91X=y +CONFIG_SMSC911X=y +# CONFIG_NETDEV_10000 is not set +CONFIG_LIBRA_SDIOIF=m +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ATMEL_MAXTOUCH=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_GPIO=y +# CONFIG_SERIO is not set +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_CONSOLE=y +CONFIG_SERIAL_MSM_HS=y +# CONFIG_SERIAL_MSM_CLOCK_CONTROL is not set +CONFIG_DIAG_CHAR=y +# CONFIG_HW_RANDOM is not set +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +# CONFIG_I2C_MSM is not set +CONFIG_I2C_QUP=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_SX150X=y +CONFIG_POWER_SUPPLY=y +CONFIG_BATTERY_MSM=y +# CONFIG_HWMON is not set +CONFIG_MARIMBA_CORE=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MT9T013 is not set +# CONFIG_MT9D112 is not set +CONFIG_WEBCAM_OV9726=y +# CONFIG_MT9P012 is not set +# CONFIG_MT9P012_KM is not set +CONFIG_MT9E013=y +# CONFIG_S5K3E2FX is not set +CONFIG_S5K4E1=y +CONFIG_MSM_CAMERA_FLASH_SC628A=y +CONFIG_IMX072=y +CONFIG_RADIO_TAVARUA=y +CONFIG_MSM_KGSL=y +CONFIG_FB=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_TRIPLE_BUFFER=y +CONFIG_FB_MSM_MDP30=y +CONFIG_FB_MSM_MDP303=y +CONFIG_FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT_PANEL=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SOC=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +CONFIG_USB_EHCI_MSM_72K=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_MSM_72K=y +CONFIG_USB_G_ANDROID=y +CONFIG_RMNET_SMD_CTL_CHANNEL="DATA40_CNTL" +CONFIG_RMNET_SMD_DATA_CHANNEL="DATA40" +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_BLOCK_MINORS=32 +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_MMC_TEST=m +CONFIG_MMC_MSM=y +CONFIG_MMC_MSM_SDIO_SUPPORT=y +CONFIG_MMC_MSM_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT=y +CONFIG_LEDS_MSM_PDM=y +CONFIG_SWITCH=y +CONFIG_SWITCH_GPIO=y +CONFIG_RTC_CLASS=y +CONFIG_STAGING=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_LOGGER=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_DISABLE_TAGS_ECC=y +CONFIG_PARTITION_ADVANCED=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_DEBUG_SHIRQ=y +# CONFIG_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +CONFIG_DEBUG_SPINLOCK=y +CONFIG_DEBUG_MUTEXES=y +CONFIG_DEBUG_SPINLOCK_SLEEP=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +# CONFIG_FTRACE is not set +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_DEBUG_LL=y +CONFIG_EARLY_PRINTK=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRC_CCITT=y diff --git a/arch/arm/configs/msm7627a_defconfig b/arch/arm/configs/msm7627a_defconfig new file mode 100644 index 00000000000..d2dde46652d --- /dev/null +++ b/arch/arm/configs/msm7627a_defconfig @@ -0,0 +1,308 @@ +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="$(KERNEL_LOCAL_VERSION)" +# CONFIG_LOCALVERSION_AUTO is not set +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_RESOURCE_COUNTERS=y +CONFIG_CGROUP_SCHED=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=m +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM7X27=y +CONFIG_MSM_SOC_REV_A=y +# CONFIG_MACH_MSM7X27_SURF is not set +# CONFIG_MACH_MSM7X27_FFA is not set +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_MSM7X00A_USE_DG_TIMER=y +# CONFIG_MSM_JTAG_V7 is not set +# CONFIG_MSM_FIQ_SUPPORT is not set +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG4=y +# CONFIG_MSM_SMD_DEBUG is not set +# CONFIG_MSM_RESET_MODEM is not set +# CONFIG_MSM_SMD_NMEA is not set +# CONFIG_MSM_SMD_QMI is not set +CONFIG_MSM_ONCRPCROUTER=y +# CONFIG_MSM_RPCSERVER_TIME_REMOTE is not set +CONFIG_MSM_RMT_STORAGE_CLIENT=y +# CONFIG_MSM_HW3D is not set +CONFIG_MSM7X27A_AUDIO=y +CONFIG_MSM_DMA_TEST=y +CONFIG_BT_MSM_PINTEST=y +CONFIG_MSM_RPC_VIBRATOR=y +CONFIG_PM8XXX_RPC_VIBRATOR=y +CONFIG_ARM_THUMBEE=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +# CONFIG_CP_ACCESS is not set +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="init=/sbin/init root=/dev/ram rw initrd=0x11000000,16M console=ttyDCC0 mem=88M" +CONFIG_CPU_FREQ=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_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_WAKELOCK=y +CONFIG_PM_RUNTIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=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_IPV6_SUBTREES=y +CONFIG_NETFILTER=y +CONFIG_NETFILTER_NETLINK_LOG=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_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=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_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_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=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_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_IBS=y +CONFIG_BT_MSM_SLEEP=y +CONFIG_RFKILL=y +CONFIG_MTD=y +CONFIG_MTD_TESTS=m +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +# CONFIG_BLK_DEV is not set +CONFIG_MISC_DEVICES=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_NETDEVICES=y +CONFIG_NET_ETHERNET=y +CONFIG_SMC91X=y +CONFIG_SMSC911X=y +# CONFIG_NETDEV_10000 is not set +CONFIG_LIBRA_SDIOIF=m +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +# CONFIG_INPUT_KEYBOARD is not set +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ATMEL_MAXTOUCH=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_GPIO=y +# CONFIG_SERIO is not set +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_CONSOLE=y +CONFIG_SERIAL_MSM_HS=y +# CONFIG_SERIAL_MSM_CLOCK_CONTROL is not set +# CONFIG_HW_RANDOM is not set +CONFIG_DCC_TTY=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +# CONFIG_I2C_MSM is not set +CONFIG_I2C_QUP=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_SX150X=y +CONFIG_POWER_SUPPLY=y +CONFIG_BATTERY_MSM=y +# CONFIG_HWMON is not set +CONFIG_MARIMBA_CORE=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MT9T013 is not set +# CONFIG_MT9D112 is not set +CONFIG_WEBCAM_OV9726=y +# CONFIG_MT9P012 is not set +# CONFIG_MT9P012_KM is not set +CONFIG_MT9E013=y +# CONFIG_S5K3E2FX is not set +CONFIG_S5K4E1=y +CONFIG_MSM_CAMERA_FLASH_SC628A=y +CONFIG_IMX072=y +CONFIG_RADIO_TAVARUA=y +CONFIG_MSM_KGSL=y +CONFIG_FB=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_TRIPLE_BUFFER=y +CONFIG_FB_MSM_MDP30=y +CONFIG_FB_MSM_MDP303=y +CONFIG_FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT_PANEL=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_MSM_72K=y +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_BLOCK_MINORS=32 +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_MMC_TEST=m +CONFIG_MMC_MSM=y +CONFIG_MMC_MSM_SDIO_SUPPORT=y +CONFIG_MMC_MSM_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT=y +CONFIG_LEDS_MSM_PDM=y +CONFIG_SWITCH=y +CONFIG_SWITCH_GPIO=y +CONFIG_RTC_CLASS=y +CONFIG_STAGING=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_LOGGER=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_DISABLE_TAGS_ECC=y +CONFIG_PARTITION_ADVANCED=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_DEBUG_SHIRQ=y +CONFIG_DETECT_HUNG_TASK=y +CONFIG_TIMER_STATS=y +CONFIG_DEBUG_SPINLOCK=y +CONFIG_DEBUG_MUTEXES=y +CONFIG_DEBUG_SPINLOCK_SLEEP=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_LATENCYTOP=y +# CONFIG_FTRACE is not set +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_DEBUG_LL=y +CONFIG_EARLY_PRINTK=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRC_CCITT=y diff --git a/arch/arm/configs/msm7630-perf_defconfig b/arch/arm/configs/msm7630-perf_defconfig new file mode 100644 index 00000000000..a1cd480c48b --- /dev/null +++ b/arch/arm/configs/msm7630-perf_defconfig @@ -0,0 +1,369 @@ +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="-perf" +# CONFIG_LOCALVERSION_AUTO is not set +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=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_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=m +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM7X30=y +# CONFIG_MSM_STACKED_MEMORY is not set +# CONFIG_MSM_JTAG_V7 is not set +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG3=y +CONFIG_MSM_SDIO_DMUX=y +CONFIG_MSM_SDIO_CMUX=y +CONFIG_MSM_SDIO_CTL=y +CONFIG_MSM_ONCRPCROUTER=y +CONFIG_MSM_RPC_WATCHDOG=y +CONFIG_MSM_RMT_STORAGE_CLIENT=y +# CONFIG_MSM_HW3D is not set +# CONFIG_QSD_AUDIO is not set +CONFIG_MSM_MEMORY_LOW_POWER_MODE=y +CONFIG_MSM_MEMORY_LOW_POWER_MODE_IDLE_RETENTION=y +CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_DEEP_POWER_DOWN=y +CONFIG_MSM_IDLE_WAIT_ON_MODEM=2000 +CONFIG_MSM_STANDALONE_POWER_COLLAPSE=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="init=/sbin/init root=/dev/ram rw initrd=0x11000000,16M console=ttyDCC0 mem=88M ip=dhcp" +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_WAKELOCK=y +CONFIG_PM_RUNTIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_INET_DIAG is not set +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=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_IPV6_SUBTREES=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_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_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_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_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=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_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_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_TBF=y +CONFIG_NET_SCH_DSMARK=m +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_TCINDEX=y +CONFIG_NET_CLS_FW=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_EMATCH=y +CONFIG_NET_CLS_ACT=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_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_IBS=y +# CONFIG_WIRELESS_EXT_SYSFS is not set +CONFIG_RFKILL=y +# CONFIG_RFKILL_PM is not set +CONFIG_MTD=y +CONFIG_MTD_TESTS=m +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=8 +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_MISC_DEVICES=y +CONFIG_HAPTIC_ISA1200=y +CONFIG_PMIC8058_UPL=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +CONFIG_NET_ETHERNET=y +CONFIG_SMC91X=y +CONFIG_SMSC911X=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +CONFIG_LIBRA_SDIOIF=m +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_MSM_RMNET_SDIO=y +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_MSM=y +CONFIG_TOUCHSCREEN_TSC2007=y +CONFIG_TOUCHSCREEN_CY8C_TS=y +CONFIG_TOUCHSCREEN_CYTTSP_I2C=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_GPIO=y +CONFIG_BOSCH_BMA150=y +# CONFIG_SERIO is not set +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_HS=y +CONFIG_DIAG_CHAR=y +# CONFIG_HW_RANDOM is not set +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_QUP=y +CONFIG_I2C_SSBI=y +CONFIG_SPI=y +CONFIG_SPI_QSD=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_SUPPLY=y +CONFIG_BATTERY_MSM=y +CONFIG_SENSORS_MSM_ADC=y +CONFIG_THERMAL=y +CONFIG_THERMAL_MSM_POPMEM=y +CONFIG_MARIMBA_CORE=y +CONFIG_MARIMBA_CODEC=y +CONFIG_TIMPANI_CODEC=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEO_HELPER_CHIPS_AUTO=y +CONFIG_WEBCAM_OV9726=y +CONFIG_MT9E013=y +CONFIG_MSM_GEMINI=y +CONFIG_RADIO_TAVARUA=y +CONFIG_MSM_KGSL=y +CONFIG_VIDEO_OUTPUT_CONTROL=y +CONFIG_FB=y +CONFIG_FB_MODE_HELPERS=y +CONFIG_FB_TILEBLITTING=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_LOGO=y +CONFIG_FB_MSM_TRIPLE_BUFFER=y +CONFIG_FB_MSM_MDP40=y +CONFIG_FB_MSM_OVERLAY=y +CONFIG_FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM=y +CONFIG_FB_MSM_HDMI_ADV7520_PANEL=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_GENERIC is not set +CONFIG_SOUND=y +CONFIG_SND=y +# CONFIG_SND_DRIVERS is not set +# CONFIG_SND_ARM is not set +# CONFIG_SND_SPI is not set +CONFIG_SND_SOC=y +CONFIG_SND_MSM7KV2_SOC=y +CONFIG_SND_MVS_SOC=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +CONFIG_USB_EHCI_MSM_72K=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_MSM_72K=y +CONFIG_USB_G_ANDROID=y +CONFIG_RMNET_SMD_CTL_CHANNEL="DATA40_CNTL" +CONFIG_RMNET_SMD_DATA_CHANNEL="DATA40" +CONFIG_RMNET_SDIO_SMD_DATA_CHANNEL="" +CONFIG_USB_MSM_ACA=y +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_BLOCK_MINORS=32 +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_MMC_MSM=y +CONFIG_MMC_MSM_SDIO_SUPPORT=y +CONFIG_MMC_MSM_CARD_HW_DETECTION=y +# CONFIG_MMC_MSM_SDC1_SUPPORT is not set +CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC4_SUPPORT=y +# CONFIG_LEDS_MSM_PMIC is not set +CONFIG_LEDS_PMIC8058=y +CONFIG_SWITCH=y +CONFIG_SWITCH_GPIO=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DEBUG=y +CONFIG_STAGING=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_LOGGER=y +CONFIG_ANDROID_RAM_CONSOLE=y +CONFIG_ANDROID_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_DISABLE_TAGS_ECC=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +CONFIG_PARTITION_ADVANCED=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_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_INFO=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_DEV_QCRYPTO=m +CONFIG_CRYPTO_DEV_QCE=m +CONFIG_CRYPTO_DEV_QCEDEV=m +CONFIG_CRC_CCITT=y diff --git a/arch/arm/configs/msm7630_defconfig b/arch/arm/configs/msm7630_defconfig new file mode 100644 index 00000000000..54f1a44e24c --- /dev/null +++ b/arch/arm/configs/msm7630_defconfig @@ -0,0 +1,358 @@ +CONFIG_EXPERIMENTAL=y +# CONFIG_LOCALVERSION_AUTO is not set +# CONFIG_SWAP is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=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_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_SLAB=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=m +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_IOSCHED_DEADLINE is not set +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM7X30=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG3=y +CONFIG_MSM_SDIO_DMUX=y +CONFIG_MSM_SDIO_CMUX=y +CONFIG_MSM_SDIO_CTL=y +CONFIG_MSM_ONCRPCROUTER=y +CONFIG_MSM_RPC_WATCHDOG=y +CONFIG_MSM_RMT_STORAGE_CLIENT=y +# CONFIG_MSM_HW3D is not set +# CONFIG_QSD_AUDIO is not set +CONFIG_MSM_MEMORY_LOW_POWER_MODE=y +CONFIG_MSM_MEMORY_LOW_POWER_MODE_IDLE_RETENTION=y +CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_DEEP_POWER_DOWN=y +CONFIG_MSM_IDLE_WAIT_ON_MODEM=2000 +CONFIG_STRICT_MEMORY_RWX=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_CMDLINE="init=/sbin/init root=/dev/ram rw initrd=0x11000000,16M console=ttyDCC0 mem=88M ip=dhcp" +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_WAKELOCK=y +CONFIG_PM_RUNTIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_INET_DIAG is not set +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=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_IPV6_SUBTREES=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_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_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_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_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=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_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_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_TBF=y +CONFIG_NET_SCH_DSMARK=m +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_TCINDEX=y +CONFIG_NET_CLS_FW=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_EMATCH=y +CONFIG_NET_CLS_ACT=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_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_IBS=y +# CONFIG_WIRELESS_EXT_SYSFS is not set +CONFIG_RFKILL=y +# CONFIG_RFKILL_PM is not set +CONFIG_MTD=y +CONFIG_MTD_TESTS=m +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_CHAR=y +CONFIG_MTD_BLOCK=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=8 +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_MISC_DEVICES=y +CONFIG_HAPTIC_ISA1200=y +CONFIG_PMIC8058_UPL=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +CONFIG_NET_ETHERNET=y +CONFIG_SMC91X=y +CONFIG_SMSC911X=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +CONFIG_LIBRA_SDIOIF=m +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_MSM_RMNET_SDIO=y +# CONFIG_INPUT_MOUSEDEV is not set +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_INPUT_MOUSE is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_MSM=y +CONFIG_TOUCHSCREEN_TSC2007=y +CONFIG_TOUCHSCREEN_CY8C_TS=y +CONFIG_TOUCHSCREEN_CYTTSP_I2C=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_GPIO=y +CONFIG_BOSCH_BMA150=y +# CONFIG_SERIO is not set +# CONFIG_LEGACY_PTYS is not set +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_HS=y +# CONFIG_HW_RANDOM is not set +CONFIG_DCC_TTY=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_QUP=y +CONFIG_I2C_SSBI=y +CONFIG_SPI=y +CONFIG_SPI_QSD=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_POWER_SUPPLY=y +CONFIG_BATTERY_MSM=y +CONFIG_SENSORS_MSM_ADC=y +CONFIG_THERMAL=y +CONFIG_THERMAL_MSM_POPMEM=y +CONFIG_MARIMBA_CORE=y +CONFIG_MARIMBA_CODEC=y +CONFIG_TIMPANI_CODEC=y +CONFIG_MSM_KGSL=y +CONFIG_VIDEO_OUTPUT_CONTROL=y +CONFIG_FB=y +CONFIG_FB_MODE_HELPERS=y +CONFIG_FB_TILEBLITTING=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_LOGO=y +CONFIG_FB_MSM_TRIPLE_BUFFER=y +CONFIG_FB_MSM_MDP40=y +CONFIG_FB_MSM_OVERLAY=y +CONFIG_FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM=y +CONFIG_FB_MSM_HDMI_ADV7520_PANEL=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_GENERIC is not set +CONFIG_SOUND=y +CONFIG_SND=y +# CONFIG_SND_DRIVERS is not set +# CONFIG_SND_ARM is not set +# CONFIG_SND_SPI is not set +CONFIG_SND_SOC=y +CONFIG_SND_MSM7KV2_SOC=y +CONFIG_SND_MVS_SOC=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_MSM_72K=y +CONFIG_USB_MSM_ACA=y +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_BLOCK_MINORS=32 +# CONFIG_MMC_BLOCK_BOUNCE is not set +CONFIG_MMC_MSM=y +CONFIG_MMC_MSM_SDIO_SUPPORT=y +CONFIG_MMC_MSM_CARD_HW_DETECTION=y +# CONFIG_MMC_MSM_SDC1_SUPPORT is not set +CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC4_SUPPORT=y +CONFIG_SWITCH=y +CONFIG_SWITCH_GPIO=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DEBUG=y +CONFIG_STAGING=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_LOGGER=y +CONFIG_ANDROID_RAM_CONSOLE=y +CONFIG_ANDROID_TIMED_GPIO=y +CONFIG_ANDROID_LOW_MEMORY_KILLER=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_YAFFS_FS=y +CONFIG_YAFFS_DISABLE_TAGS_ECC=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +CONFIG_PARTITION_ADVANCED=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_SCHEDSTATS=y +CONFIG_TIMER_STATS=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_PROVE_LOCKING=y +CONFIG_DEBUG_SPINLOCK_SLEEP=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_DEV_QCRYPTO=m +CONFIG_CRYPTO_DEV_QCE=m +CONFIG_CRYPTO_DEV_QCEDEV=m +CONFIG_CRC_CCITT=y diff --git a/arch/arm/configs/msm8660-perf_defconfig b/arch/arm/configs/msm8660-perf_defconfig new file mode 100644 index 00000000000..3423b729ba9 --- /dev/null +++ b/arch/arm/configs/msm8660-perf_defconfig @@ -0,0 +1,427 @@ +CONFIG_EXPERIMENTAL=y +CONFIG_LOCALVERSION="-perf" +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_RESOURCE_COUNTERS=y +CONFIG_CGROUP_SCHED=y +CONFIG_RT_GROUP_SCHED=y +CONFIG_NAMESPACES=y +# CONFIG_UTS_NS is not set +# CONFIG_IPC_NS is not set +# CONFIG_USER_NS is not set +# CONFIG_PID_NS is not set +# CONFIG_NET_NS is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +# CONFIG_SLUB_DEBUG is not set +CONFIG_PROFILING=y +CONFIG_OPROFILE=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_DEFAULT_DEADLINE=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM8X60=y +CONFIG_MACH_MSM8X60_RUMI3=y +CONFIG_MACH_MSM8X60_SIM=y +CONFIG_MACH_MSM8X60_SURF=y +CONFIG_MACH_MSM8X60_FFA=y +CONFIG_MACH_MSM8X60_FLUID=y +CONFIG_MACH_MSM8X60_FUSION=y +CONFIG_MACH_MSM8X60_FUSN_FFA=y +CONFIG_MSM7X00A_USE_DG_TIMER=y +CONFIG_MSM7X00A_SLEEP_MODE_POWER_COLLAPSE=y +CONFIG_MSM7X00A_IDLE_SLEEP_WAIT_FOR_INTERRUPT=y +# CONFIG_MSM_JTAG_V7 is not set +# CONFIG_MSM_FIQ_SUPPORT is not set +# CONFIG_MSM_PROC_COMM is not set +CONFIG_MSM_SMD=y +CONFIG_MSM_SDIO_DMUX=y +# CONFIG_MSM_RESET_MODEM is not set +# CONFIG_MSM_SMD_NMEA is not set +CONFIG_MSM_SDIO_TTY=y +# CONFIG_MSM_SMD_QMI is not set +CONFIG_MSM_SDIO_CMUX=y +CONFIG_MSM_DSPS=y +CONFIG_MSM_SDIO_CTL=y +CONFIG_MSM_ONCRPCROUTER=y +# CONFIG_MSM_RPCSERVER_TIME_REMOTE is not set +# CONFIG_MSM_RPCSERVER_WATCHDOG is not set +# CONFIG_MSM_RPCSERVER_HANDSET is not set +CONFIG_MSM_RMT_STORAGE_CLIENT=y +CONFIG_MSM_SDIO_SMEM=y +# CONFIG_MSM_HW3D is not set +CONFIG_MSM_SECURE_PIL=y +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_RPM_LOG=y +CONFIG_MSM_RPM_STATS_LOG=y +CONFIG_MSM_WATCHDOG=y +CONFIG_MSM_DLOAD_MODE=y +CONFIG_MSM_ETM=y +CONFIG_MSM_SLEEP_STATS=y +CONFIG_MSM_GSBI9_UART=y +CONFIG_STRICT_MEMORY_RWX=y +CONFIG_KSAPI=m +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +CONFIG_NR_CPUS=2 +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0x19000000 +CONFIG_CP_ACCESS=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_IDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_WAKELOCK=y +CONFIG_PM_RUNTIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_INET_DIAG is not set +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=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_IPV6_SUBTREES=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_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_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=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_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_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_TBF=y +CONFIG_NET_SCH_DSMARK=m +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_TCINDEX=y +CONFIG_NET_CLS_FW=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=y +CONFIG_NET_EMATCH_NBYTE=y +CONFIG_NET_EMATCH_U32=y +CONFIG_NET_EMATCH_META=y +CONFIG_NET_EMATCH_TEXT=y +CONFIG_NET_CLS_ACT=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_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_IBS=y +# CONFIG_WIRELESS_EXT_SYSFS is not set +CONFIG_RFKILL=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_MISC_DEVICES=y +CONFIG_UID_STAT=y +CONFIG_TSIF=m +CONFIG_TSIF_CHRDEV=m +CONFIG_HAPTIC_ISA1200=y +CONFIG_PMIC8058_VIBRATOR=y +CONFIG_PMIC8058_UPL=y +CONFIG_PMIC8058_XOADC=y +CONFIG_PMIC8058_MISC=y +CONFIG_TZCOM=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_DM_UEVENT=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +CONFIG_NET_ETHERNET=y +CONFIG_SMC91X=y +CONFIG_SMC911X=y +CONFIG_SMSC911X=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +CONFIG_LIBRA_SDIOIF=m +CONFIG_PPP=y +CONFIG_PPP_ASYNC=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_BSDCOMP=y +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_MSM_RMNET_SDIO=y +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +CONFIG_INPUT_KEYRESET=y +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_MATRIX=y +CONFIG_INPUT_JOYSTICK=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ATMEL_MAXTOUCH=y +CONFIG_TOUCHSCREEN_CY8C_TS=y +CONFIG_TOUCHSCREEN_CYTTSP_I2C=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +CONFIG_PMIC8058_PWRKEY=y +CONFIG_PMIC8058_OTHC=y +CONFIG_SERIAL_MSM_HS=y +CONFIG_SERIAL_MSM_HSL=y +CONFIG_SERIAL_MSM_HSL_CONSOLE=y +CONFIG_DIAG_CHAR=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_MSM=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +# CONFIG_I2C_MSM is not set +CONFIG_I2C_QUP=y +CONFIG_I2C_SSBI=y +CONFIG_SPI=y +CONFIG_SPI_QUP=y +CONFIG_SPI_SPIDEV=m +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_SX150X=y +CONFIG_POWER_SUPPLY=y +# CONFIG_BATTERY_MSM is not set +CONFIG_BATTERY_MSM8X60=y +CONFIG_PM8058_CHARGER=y +CONFIG_ISL9519_CHARGER=y +CONFIG_SMB137B_CHARGER=y +CONFIG_BATTERY_BQ27520=y +CONFIG_BATTERY_BQ27541=y +CONFIG_SENSORS_MSM_ADC=y +CONFIG_THERMAL=y +CONFIG_THERMAL_HWMON=y +CONFIG_THERMAL_PM8901=y +CONFIG_THERMAL_PM8058=y +CONFIG_THERMAL_TSENS=y +CONFIG_MARIMBA_CORE=y +CONFIG_TIMPANI_CODEC=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_USB_VIDEO_CLASS=y +CONFIG_WEBCAM_OV9726=y +CONFIG_MT9E013=y +CONFIG_MSM_GEMINI=y +CONFIG_RADIO_TAVARUA=y +CONFIG_MSM_KGSL=y +CONFIG_KGSL_PER_PROCESS_PAGE_TABLE=y +CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES=y +CONFIG_VIDEO_OUTPUT_CONTROL=y +CONFIG_FB=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_TRIPLE_BUFFER=y +CONFIG_FB_MSM_MDP40=y +CONFIG_FB_MSM_OVERLAY=y +CONFIG_FB_MSM_OVERLAY_WRITEBACK=y +CONFIG_FB_MSM_LCDC_PANEL_AUTO_DETECT=y +CONFIG_FB_MSM_HDMI_MSM_PANEL=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_HID_APPLE=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +CONFIG_USB_EHCI_MSM_72K=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_MSM_72K=y +CONFIG_USB_G_ANDROID=y +CONFIG_RMNET_SMD_CTL_CHANNEL="DATA40_CNTL" +CONFIG_RMNET_SMD_DATA_CHANNEL="DATA40" +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_EMBEDDED_SDIO=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_MSM=y +CONFIG_MMC_MSM_SDIO_SUPPORT=y +CONFIG_MMC_MSM_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SDC1_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC4_SUPPORT=y +CONFIG_MMC_MSM_SDC4_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC5_SUPPORT=y +CONFIG_MMC_MSM_SDC5_DUMMY52_REQUIRED=y +CONFIG_LEDS_GPIO=y +# CONFIG_LEDS_MSM_PMIC is not set +CONFIG_LEDS_PMIC8058=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_SLEEP=y +CONFIG_SWITCH=y +CONFIG_SWITCH_GPIO=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_DRV_MSM is not set +CONFIG_RTC_PM8058=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_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_CIFS=y +CONFIG_PARTITION_ADVANCED=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_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +# CONFIG_DEBUG_PREEMPT is not set +# CONFIG_STACKTRACE is not set +CONFIG_DEBUG_INFO=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_TWOFISH=y diff --git a/arch/arm/configs/msm8660_defconfig b/arch/arm/configs/msm8660_defconfig new file mode 100644 index 00000000000..7b334617ac8 --- /dev/null +++ b/arch/arm/configs/msm8660_defconfig @@ -0,0 +1,425 @@ +CONFIG_EXPERIMENTAL=y +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=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_NAMESPACES=y +# CONFIG_UTS_NS is not set +# CONFIG_IPC_NS is not set +# CONFIG_USER_NS is not set +# CONFIG_PID_NS is not set +# CONFIG_NET_NS is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=m +CONFIG_KPROBES=y +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_DEFAULT_DEADLINE=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM8X60=y +CONFIG_MACH_MSM8X60_RUMI3=y +CONFIG_MACH_MSM8X60_SIM=y +CONFIG_MACH_MSM8X60_SURF=y +CONFIG_MACH_MSM8X60_FFA=y +CONFIG_MACH_MSM8X60_FLUID=y +CONFIG_MSM7X00A_USE_DG_TIMER=y +CONFIG_MSM7X00A_SLEEP_MODE_POWER_COLLAPSE=y +CONFIG_MSM7X00A_IDLE_SLEEP_WAIT_FOR_INTERRUPT=y +# CONFIG_MSM_FIQ_SUPPORT is not set +# CONFIG_MSM_PROC_COMM is not set +CONFIG_MSM_SMD=y +# CONFIG_MSM_RESET_MODEM is not set +# CONFIG_MSM_SMD_NMEA is not set +# CONFIG_MSM_SMD_QMI is not set +CONFIG_MSM_DSPS=y +CONFIG_MSM_ONCRPCROUTER=y +# CONFIG_MSM_RPCSERVER_TIME_REMOTE is not set +# CONFIG_MSM_RPCSERVER_WATCHDOG is not set +# CONFIG_MSM_RPCSERVER_HANDSET is not set +CONFIG_MSM_RMT_STORAGE_CLIENT=y +# CONFIG_MSM_HW3D is not set +CONFIG_MSM_SECURE_PIL=y +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_RPM_LOG=y +CONFIG_MSM_RPM_STATS_LOG=y +CONFIG_MSM_WATCHDOG=y +CONFIG_MSM_DLOAD_MODE=y +CONFIG_MSM_ETM=y +CONFIG_MSM_SLEEP_STATS=y +CONFIG_MSM_GSBI9_UART=y +CONFIG_STRICT_MEMORY_RWX=y +CONFIG_KSAPI=m +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +CONFIG_NR_CPUS=2 +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0x10000000 +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_IDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_WAKELOCK=y +CONFIG_PM_RUNTIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_INET_DIAG is not set +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=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_IPV6_SUBTREES=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_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_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=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_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_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_SCH_PRIO=y +CONFIG_NET_SCH_SFQ=y +CONFIG_NET_SCH_TBF=y +CONFIG_NET_SCH_DSMARK=m +CONFIG_NET_SCH_INGRESS=y +CONFIG_NET_CLS_BASIC=y +CONFIG_NET_CLS_TCINDEX=y +CONFIG_NET_CLS_FW=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=y +CONFIG_NET_EMATCH_NBYTE=y +CONFIG_NET_EMATCH_U32=y +CONFIG_NET_EMATCH_META=y +CONFIG_NET_EMATCH_TEXT=y +CONFIG_NET_CLS_ACT=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_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_IBS=y +# CONFIG_WIRELESS_EXT_SYSFS is not set +CONFIG_RFKILL=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_MISC_DEVICES=y +CONFIG_KERNEL_DEBUGGER_CORE=y +CONFIG_UID_STAT=y +CONFIG_TSIF=m +CONFIG_TSIF_CHRDEV=m +CONFIG_HAPTIC_ISA1200=y +CONFIG_PMIC8058_VIBRATOR=y +CONFIG_PMIC8058_UPL=y +CONFIG_PMIC8058_XOADC=y +CONFIG_PMIC8058_MISC=y +CONFIG_TZCOM=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=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_DUMMY=y +CONFIG_NET_ETHERNET=y +CONFIG_SMC91X=y +CONFIG_SMC911X=y +CONFIG_SMSC911X=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +CONFIG_LIBRA_SDIOIF=m +CONFIG_PPP=y +CONFIG_PPP_ASYNC=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_BSDCOMP=y +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +CONFIG_INPUT_KEYRESET=y +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_MATRIX=y +CONFIG_INPUT_JOYSTICK=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ATMEL_MAXTOUCH=y +CONFIG_TOUCHSCREEN_CY8C_TS=y +CONFIG_TOUCHSCREEN_CYTTSP_I2C=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +CONFIG_PMIC8058_PWRKEY=y +CONFIG_PMIC8058_OTHC=y +CONFIG_SERIAL_MSM_HS=y +CONFIG_SERIAL_MSM_HSL=y +CONFIG_SERIAL_MSM_HSL_CONSOLE=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_MSM=y +CONFIG_DCC_TTY=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +# CONFIG_I2C_MSM is not set +CONFIG_I2C_QUP=y +CONFIG_I2C_SSBI=y +CONFIG_SPI=y +CONFIG_SPI_QUP=y +CONFIG_SPI_SPIDEV=m +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_SX150X=y +CONFIG_POWER_SUPPLY=y +# CONFIG_BATTERY_MSM is not set +CONFIG_BATTERY_MSM8X60=y +CONFIG_PM8058_CHARGER=y +CONFIG_ISL9519_CHARGER=y +CONFIG_SMB137B_CHARGER=y +CONFIG_BATTERY_BQ27520=y +CONFIG_BATTERY_BQ27541=y +CONFIG_SENSORS_MSM_ADC=y +CONFIG_THERMAL=y +CONFIG_THERMAL_PM8901=y +CONFIG_THERMAL_PM8058=y +CONFIG_THERMAL_TSENS=y +CONFIG_MARIMBA_CORE=y +CONFIG_TIMPANI_CODEC=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_USB_VIDEO_CLASS=y +CONFIG_WEBCAM_OV9726=y +CONFIG_MT9E013=y +CONFIG_MSM_GEMINI=y +CONFIG_RADIO_TAVARUA=y +CONFIG_MSM_KGSL=y +CONFIG_KGSL_PER_PROCESS_PAGE_TABLE=y +CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES=y +CONFIG_VIDEO_OUTPUT_CONTROL=y +CONFIG_FB=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_TRIPLE_BUFFER=y +CONFIG_FB_MSM_MDP40=y +CONFIG_FB_MSM_OVERLAY=y +CONFIG_FB_MSM_OVERLAY_WRITEBACK=y +CONFIG_FB_MSM_LCDC_PANEL_AUTO_DETECT=y +CONFIG_FB_MSM_HDMI_MSM_PANEL=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_MSM_8x60_VOIP=y +CONFIG_HID_APPLE=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_MSM_72K=y +CONFIG_USB_MSM_ACA=y +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_EMBEDDED_SDIO=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_MSM=y +CONFIG_MMC_MSM_SDIO_SUPPORT=y +CONFIG_MMC_MSM_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SDC1_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT=y +CONFIG_MMC_MSM_SDC4_SUPPORT=y +CONFIG_MMC_MSM_SDC4_DUMMY52_REQUIRED=y +CONFIG_MMC_MSM_SDC5_SUPPORT=y +CONFIG_MMC_MSM_SDC5_DUMMY52_REQUIRED=y +CONFIG_LEDS_GPIO=y +# CONFIG_LEDS_MSM_PMIC is not set +CONFIG_LEDS_PMIC8058=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_SLEEP=y +CONFIG_SWITCH=y +CONFIG_SWITCH_GPIO=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_DRV_MSM is not set +CONFIG_RTC_PM8058=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_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_CIFS=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_ISO8859_1=y +CONFIG_PRINTK_TIME=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_KERNEL=y +CONFIG_SCHEDSTATS=y +CONFIG_TIMER_STATS=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_PROVE_LOCKING=y +CONFIG_DEBUG_SPINLOCK_SLEEP=y +CONFIG_DEBUG_STACK_USAGE=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_VM=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DEBUG_SG=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_DEBUG_LL=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_DEV_QCRYPTO=m +CONFIG_CRYPTO_DEV_QCE=m +CONFIG_CRYPTO_DEV_QCEDEV=m diff --git a/arch/arm/configs/msm8960_defconfig b/arch/arm/configs/msm8960_defconfig new file mode 100644 index 00000000000..73d9f9d1ca5 --- /dev/null +++ b/arch/arm/configs/msm8960_defconfig @@ -0,0 +1,365 @@ +CONFIG_EXPERIMENTAL=y +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_SYSVIPC=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=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_FAIR_GROUP_SCHED is not set +CONFIG_RT_GROUP_SCHED=y +CONFIG_NAMESPACES=y +# CONFIG_UTS_NS is not set +# CONFIG_IPC_NS is not set +# CONFIG_USER_NS is not set +# CONFIG_PID_NS is not set +# CONFIG_NET_NS is not set +CONFIG_BLK_DEV_INITRD=y +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +CONFIG_PANIC_TIMEOUT=5 +CONFIG_KALLSYMS_ALL=y +CONFIG_ASHMEM=y +CONFIG_EMBEDDED=y +CONFIG_PROFILING=y +CONFIG_OPROFILE=m +CONFIG_MODULES=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM8960=y +CONFIG_MACH_MSM8960_SIM=y +CONFIG_MACH_MSM8960_RUMI3=y +CONFIG_MACH_MSM8960_CDP=y +CONFIG_MACH_MSM8960_MTP=y +CONFIG_MACH_MSM8960_FLUID=y +# CONFIG_MSM_STACKED_MEMORY is not set +CONFIG_KERNEL_PMEM_EBI_REGION=y +# CONFIG_MSM_JTAG_V7 is not set +# CONFIG_MSM_FIQ_SUPPORT is not set +# CONFIG_MSM_PROC_COMM is not set +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_PKG4=y +CONFIG_MSM_IPC_ROUTER_SMD_XPRT=y +CONFIG_MSM_BAM_DMUX=y +CONFIG_MSM_DSPS=y +CONFIG_MSM_IPC_ROUTER=y +# CONFIG_MSM_HW3D is not set +CONFIG_MSM_SUBSYSTEM_RESTART=y +CONFIG_MSM_MODEM_8960=y +CONFIG_MSM_WATCHDOG=y +CONFIG_MSM_DLOAD_MODE=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_SMP=y +# CONFIG_SMP_ON_UP is not set +CONFIG_NR_CPUS=2 +CONFIG_PREEMPT=y +CONFIG_AEABI=y +CONFIG_HIGHMEM=y +CONFIG_VMALLOC_RESERVE=0x19000000 +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_IDLE=y +CONFIG_VFP=y +CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_WAKELOCK=y +CONFIG_PM_RUNTIME=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_UNIX=y +CONFIG_INET=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_LRO is not set +# CONFIG_INET_DIAG is not set +CONFIG_IPV6=y +CONFIG_IPV6_PRIVACY=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +CONFIG_INET6_IPCOMP=y +CONFIG_IPV6_MIP6=y +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_NETFILTER=y +CONFIG_NETFILTER_NETLINK_LOG=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_XT_TARGET_CLASSIFY=y +CONFIG_NETFILTER_XT_TARGET_CONNMARK=y +CONFIG_NETFILTER_XT_TARGET_MARK=y +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y +CONFIG_NETFILTER_XT_MATCH_COMMENT=y +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y +CONFIG_NETFILTER_XT_MATCH_CONNMARK=y +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y +CONFIG_NETFILTER_XT_MATCH_HELPER=y +CONFIG_NETFILTER_XT_MATCH_IPRANGE=y +CONFIG_NETFILTER_XT_MATCH_LENGTH=y +CONFIG_NETFILTER_XT_MATCH_LIMIT=y +CONFIG_NETFILTER_XT_MATCH_MAC=y +CONFIG_NETFILTER_XT_MATCH_MARK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_OWNER=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y +CONFIG_NETFILTER_XT_MATCH_QUOTA=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_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_ARPTABLES=y +CONFIG_IP_NF_ARPFILTER=y +CONFIG_IP_NF_ARP_MANGLE=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_HTB=y +CONFIG_NET_CLS_U32=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_FLOW=y +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=y +CONFIG_NET_EMATCH_NBYTE=y +CONFIG_NET_EMATCH_U32=y +CONFIG_NET_EMATCH_META=y +CONFIG_NET_EMATCH_TEXT=y +CONFIG_NET_CLS_ACT=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_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCISMD=m +CONFIG_RFKILL=y +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_MISC_DEVICES=y +CONFIG_SCSI=y +CONFIG_SCSI_TGT=y +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=y +CONFIG_SCSI_MULTI_LUN=y +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_NETDEVICES=y +CONFIG_DUMMY=y +CONFIG_NET_ETHERNET=y +CONFIG_SMC91X=y +CONFIG_SMC911X=y +CONFIG_SMSC911X=y +CONFIG_KS8851=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +CONFIG_WCNSS_WLAN=m +CONFIG_SLIP=y +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_MODE_SLIP6=y +# CONFIG_MSM_RMNET is not set +CONFIG_MSM_RMNET_BAM=y +CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_EVBUG=m +CONFIG_KEYBOARD_PMIC8XXX=y +CONFIG_INPUT_JOYSTICK=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_CYTTSP_I2C=y +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=y +CONFIG_INPUT_PMIC8XXX_PWRKEY=y +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_HS=y +# CONFIG_SERIAL_MSM_CLOCK_CONTROL is not set +CONFIG_SERIAL_MSM_HSL=y +CONFIG_SERIAL_MSM_HSL_CONSOLE=y +CONFIG_DIAG_CHAR=y +CONFIG_HW_RANDOM=y +CONFIG_DCC_TTY=y +CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y +# CONFIG_I2C_MSM is not set +CONFIG_I2C_QUP=y +CONFIG_SPI=y +CONFIG_SPI_QUP=y +CONFIG_SPI_SPIDEV=m +CONFIG_SLIMBUS=y +CONFIG_SLIMBUS_MSM_CTRL=y +CONFIG_DEBUG_GPIO=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_SX150X=y +CONFIG_POWER_SUPPLY=y +# CONFIG_BATTERY_MSM is not set +CONFIG_PM8921_CHARGER=y +CONFIG_PM8921_BMS=y +# CONFIG_HWMON is not set +CONFIG_THERMAL=y +CONFIG_THERMAL_PM8XXX=y +CONFIG_MFD_PM8921_CORE=y +CONFIG_MFD_PM8921_ADC=y +CONFIG_MFD_PM8XXX_BATT_ALARM=y +CONFIG_WCD9310_CODEC=y +CONFIG_REGULATOR_GPIO=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEO_HELPER_CHIPS_AUTO=y +CONFIG_USB_VIDEO_CLASS=y +CONFIG_MSM_CAMERA_V4L2=y +CONFIG_MSM_GEMINI=y +CONFIG_RADIO_IRIS=y +CONFIG_RADIO_IRIS_TRANSPORT=m +CONFIG_MSM_KGSL=y +CONFIG_KGSL_PER_PROCESS_PAGE_TABLE=y +CONFIG_FB=y +CONFIG_FB_MSM=y +# CONFIG_FB_MSM_BACKLIGHT is not set +CONFIG_FB_MSM_MDP40=y +CONFIG_FB_MSM_OVERLAY=y +CONFIG_FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT_PANEL=y +CONFIG_FB_MSM_HDMI_MSM_PANEL=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_DYNAMIC_MINORS=y +# CONFIG_SND_ARM is not set +# CONFIG_SND_SPI is not set +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_QDSP6=y +CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_SUSPEND=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_EHSET=y +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_STORAGE_DEBUG=y +CONFIG_USB_STORAGE_DATAFAB=y +CONFIG_USB_STORAGE_FREECOM=y +CONFIG_USB_STORAGE_ISD200=y +CONFIG_USB_STORAGE_USBAT=y +CONFIG_USB_STORAGE_SDDR09=y +CONFIG_USB_STORAGE_SDDR55=y +CONFIG_USB_STORAGE_JUMPSHOT=y +CONFIG_USB_STORAGE_ALAUDA=y +CONFIG_USB_STORAGE_ONETOUCH=y +CONFIG_USB_STORAGE_KARMA=y +CONFIG_USB_STORAGE_CYPRESS_ATACB=y +CONFIG_USB_EHSET_TEST_FIXTURE=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DEBUG_FILES=y +CONFIG_USB_GADGET_CI13XXX_MSM=y +CONFIG_USB_G_ANDROID=y +CONFIG_USB_ANDROID_RMNET_CTRL_SMD=y +CONFIG_MMC=y +CONFIG_MMC_PERF_PROFILING=y +CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_PARANOID_SD_INIT=y +CONFIG_MMC_BLOCK_MINORS=32 +CONFIG_MMC_MSM=y +CONFIG_MMC_MSM_CARD_HW_DETECTION=y +CONFIG_MMC_MSM_SDC1_8_BIT_SUPPORT=y +# CONFIG_MMC_MSM_SDC2_SUPPORT is not set +CONFIG_MMC_MSM_SDC3_SUPPORT=y +CONFIG_MMC_MSM_SPS_SUPPORT=y +CONFIG_LEDS_PM8XXX=y +# CONFIG_LEDS_MSM_PMIC is not set +CONFIG_SWITCH=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_DRV_MSM is not set +CONFIG_RTC_DRV_PM8XXX=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_MSM_SSBI=y +CONFIG_SPS=y +CONFIG_SPS_SUPPORT_BAMDMA=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT4_FS=y +CONFIG_VFAT_FS=y +CONFIG_TMPFS=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_CIFS=y +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_SCHED_DEBUG is not set +CONFIG_TIMER_STATS=y +# CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_TWOFISH=y +CONFIG_CRYPTO_DEV_QCRYPTO=m +CONFIG_CRYPTO_DEV_QCE=m +CONFIG_CRYPTO_DEV_QCEDEV=m +CONFIG_CRC_CCITT=y diff --git a/arch/arm/include/asm/cacheflush.h b/arch/arm/include/asm/cacheflush.h index 1252a2675ca..584fe0b6fcd 100644 --- a/arch/arm/include/asm/cacheflush.h +++ b/arch/arm/include/asm/cacheflush.h @@ -87,6 +87,21 @@ * DMA Cache Coherency * =================== * + * dma_inv_range(start, end) + * + * Invalidate (discard) the specified virtual address range. + * May not write back any entries. If 'start' or 'end' + * are not cache line aligned, those lines must be written + * back. + * - start - virtual start address + * - end - virtual end address + * + * dma_clean_range(start, end) + * + * Clean (write back) the specified virtual address range. + * - start - virtual start address + * - end - virtual end address + * * dma_flush_range(start, end) * * Clean and invalidate the specified virtual address range. @@ -107,6 +122,8 @@ struct cpu_cache_fns { void (*dma_map_area)(const void *, size_t, int); void (*dma_unmap_area)(const void *, size_t, int); + void (*dma_inv_range)(const void *, const void *); + void (*dma_clean_range)(const void *, const void *); void (*dma_flush_range)(const void *, const void *); }; @@ -133,6 +150,8 @@ extern struct cpu_cache_fns cpu_cache; */ #define dmac_map_area cpu_cache.dma_map_area #define dmac_unmap_area cpu_cache.dma_unmap_area +#define dmac_inv_range cpu_cache.dma_inv_range +#define dmac_clean_range cpu_cache.dma_clean_range #define dmac_flush_range cpu_cache.dma_flush_range #else @@ -153,6 +172,8 @@ extern void __cpuc_flush_dcache_area(void *, size_t); */ extern void dmac_map_area(const void *, size_t, int); extern void dmac_unmap_area(const void *, size_t, int); +extern void dmac_inv_range(const void *, const void *); +extern void dmac_clean_range(const void *, const void *); extern void dmac_flush_range(const void *, const void *); #endif diff --git a/arch/arm/include/asm/cputype.h b/arch/arm/include/asm/cputype.h index cd4458f6417..cb47d28cbe1 100644 --- a/arch/arm/include/asm/cputype.h +++ b/arch/arm/include/asm/cputype.h @@ -8,6 +8,7 @@ #define CPUID_CACHETYPE 1 #define CPUID_TCM 2 #define CPUID_TLBTYPE 3 +#define CPUID_MPIDR 5 #define CPUID_EXT_PFR0 "c1, 0" #define CPUID_EXT_PFR1 "c1, 1" @@ -70,6 +71,11 @@ static inline unsigned int __attribute_const__ read_cpuid_tcmstatus(void) return read_cpuid(CPUID_TCM); } +static inline unsigned int __attribute_const__ read_cpuid_mpidr(void) +{ + return read_cpuid(CPUID_MPIDR); +} + /* * Intel's XScale3 core supports some v6 features (supersections, L2) * but advertises itself as v5 as it does not support the v6 ISA. For diff --git a/arch/arm/include/asm/delay.h b/arch/arm/include/asm/delay.h index b2deda18154..5c6b9a3c5df 100644 --- a/arch/arm/include/asm/delay.h +++ b/arch/arm/include/asm/delay.h @@ -8,7 +8,7 @@ #include /* HZ */ -extern void __delay(int loops); +extern void __delay(unsigned long loops); /* * This function intentionally does not exist; if you see references to @@ -40,5 +40,8 @@ extern void __const_udelay(unsigned long); __const_udelay((n) * ((2199023U*HZ)>>11))) : \ __udelay(n)) +extern void set_delay_fn(void (*fn)(unsigned long)); +extern void read_current_timer_delay_loop(unsigned long loops); + #endif /* defined(_ARM_DELAY_H) */ diff --git a/arch/arm/include/asm/dma-mapping.h b/arch/arm/include/asm/dma-mapping.h index 4fff837363e..6f48921b08d 100644 --- a/arch/arm/include/asm/dma-mapping.h +++ b/arch/arm/include/asm/dma-mapping.h @@ -172,6 +172,46 @@ static inline void dma_free_noncoherent(struct device *dev, size_t size, { } + +/* + * dma_coherent_pre_ops - barrier functions for coherent memory before DMA. + * A barrier is required to ensure memory operations are complete before the + * initiation of a DMA xfer. + * If the coherent memory is Strongly Ordered + * - pre ARMv7 and 8x50 guarantees ordering wrt other mem accesses + * - ARMv7 guarantees ordering only within a 1KB block, so we need a barrier + * If coherent memory is normal then we need a barrier to prevent + * reordering + */ +static inline void dma_coherent_pre_ops(void) +{ +#if COHERENT_IS_NORMAL == 1 + dmb(); +#else + if (arch_is_coherent()) + dmb(); + else + barrier(); +#endif +} +/* + * dma_post_coherent_ops - barrier functions for coherent memory after DMA. + * If the coherent memory is Strongly Ordered we dont need a barrier since + * there are no speculative fetches to Strongly Ordered memory. + * If coherent memory is normal then we need a barrier to prevent reordering + */ +static inline void dma_coherent_post_ops(void) +{ +#if COHERENT_IS_NORMAL == 1 + dmb(); +#else + if (arch_is_coherent()) + dmb(); + else + barrier(); +#endif +} + /** * dma_alloc_coherent - allocate consistent memory for DMA * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices @@ -385,6 +425,58 @@ static inline dma_addr_t dma_map_single(struct device *dev, void *cpu_addr, return addr; } +/** + * dma_cache_pre_ops - clean or invalidate cache before dma transfer is + * initiated and perform a barrier operation. + * @virtual_addr: A kernel logical or kernel virtual address + * @size: size of buffer to map + * @dir: DMA transfer direction + * + * Ensure that any data held in the cache is appropriately discarded + * or written back. + * + */ +static inline void dma_cache_pre_ops(void *virtual_addr, + size_t size, enum dma_data_direction dir) +{ + extern void ___dma_single_cpu_to_dev(const void *, size_t, + enum dma_data_direction); + + BUG_ON(!valid_dma_direction(dir)); + + if (!arch_is_coherent()) + ___dma_single_cpu_to_dev(virtual_addr, size, dir); +} + +/** + * dma_cache_post_ops - clean or invalidate cache after dma transfer is + * initiated and perform a barrier operation. + * @virtual_addr: A kernel logical or kernel virtual address + * @size: size of buffer to map + * @dir: DMA transfer direction + * + * Ensure that any data held in the cache is appropriately discarded + * or written back. + * + */ +static inline void dma_cache_post_ops(void *virtual_addr, + size_t size, enum dma_data_direction dir) +{ + extern void ___dma_single_cpu_to_dev(const void *, size_t, + enum dma_data_direction); + + BUG_ON(!valid_dma_direction(dir)); + + if (arch_has_speculative_dfetch() && !arch_is_coherent() + && dir != DMA_TO_DEVICE) + /* + * Treat DMA_BIDIRECTIONAL and DMA_FROM_DEVICE + * identically: invalidate + */ + ___dma_single_cpu_to_dev(virtual_addr, + size, DMA_FROM_DEVICE); +} + /** * dma_map_page - map a portion of a page for streaming DMA * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices diff --git a/arch/arm/include/asm/domain.h b/arch/arm/include/asm/domain.h index af18ceaacf5..0854849e4d9 100644 --- a/arch/arm/include/asm/domain.h +++ b/arch/arm/include/asm/domain.h @@ -2,6 +2,7 @@ * arch/arm/include/asm/domain.h * * Copyright (C) 1999 Russell King. + * Copyright (c) 2009, Code Aurora Forum. 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 @@ -27,8 +28,13 @@ * * 36-bit addressing and supersections are only available on * CPUs based on ARMv6+ or the Intel XSC3 core. + * + * We cannot use domain 0 for the kernel on QSD8x50 since the kernel domain + * is set to manager mode when set_fs(KERNEL_DS) is called. Setting domain 0 + * to manager mode will disable the workaround for a cpu bug that can cause an + * invalid fault status and/or tlb corruption (CONFIG_VERIFY_PERMISSION_FAULT). */ -#ifndef CONFIG_IO_36 +#if !defined(CONFIG_IO_36) && !defined(CONFIG_VERIFY_PERMISSION_FAULT) #define DOMAIN_KERNEL 0 #define DOMAIN_TABLE 0 #define DOMAIN_USER 1 @@ -56,6 +62,17 @@ #ifndef __ASSEMBLY__ #ifdef CONFIG_CPU_USE_DOMAINS +#ifdef CONFIG_EMULATE_DOMAIN_MANAGER_V7 +void emulate_domain_manager_set(u32 domain); +int emulate_domain_manager_data_abort(u32 dfsr, u32 dfar); +int emulate_domain_manager_prefetch_abort(u32 ifsr, u32 ifar); +void emulate_domain_manager_switch_mm( + unsigned long pgd_phys, + struct mm_struct *mm, + void (*switch_mm)(unsigned long pgd_phys, struct mm_struct *)); + +#define set_domain(x) emulate_domain_manager_set(x) +#else #define set_domain(x) \ do { \ __asm__ __volatile__( \ @@ -63,6 +80,7 @@ : : "r" (x)); \ isb(); \ } while (0) +#endif #define modify_domain(dom,type) \ do { \ diff --git a/arch/arm/include/asm/hardware/cache-l2x0.h b/arch/arm/include/asm/hardware/cache-l2x0.h index 16bd4803158..1fc2f49a954 100644 --- a/arch/arm/include/asm/hardware/cache-l2x0.h +++ b/arch/arm/include/asm/hardware/cache-l2x0.h @@ -59,6 +59,7 @@ /* Registers shifts and masks */ #define L2X0_CACHE_ID_PART_MASK (0xf << 6) #define L2X0_CACHE_ID_PART_L210 (1 << 6) +#define L2X0_CACHE_ID_PART_L220 (2 << 6) #define L2X0_CACHE_ID_PART_L310 (3 << 6) #define L2X0_AUX_CTRL_MASK 0xc0000fff @@ -71,9 +72,13 @@ #define L2X0_AUX_CTRL_DATA_PREFETCH_SHIFT 28 #define L2X0_AUX_CTRL_INSTR_PREFETCH_SHIFT 29 #define L2X0_AUX_CTRL_EARLY_BRESP_SHIFT 30 +#define L2X0_AUX_CTRL_EVNT_MON_BUS_EN_SHIFT 20 #ifndef __ASSEMBLY__ extern void __init l2x0_init(void __iomem *base, __u32 aux_val, __u32 aux_mask); +extern void l2x0_suspend(void); +extern void l2x0_resume(int collapsed); +extern void l2x0_cache_sync(void); #endif #endif diff --git a/arch/arm/include/asm/hardware/gic.h b/arch/arm/include/asm/hardware/gic.h index 0691f9dcc50..27adea340a5 100644 --- a/arch/arm/include/asm/hardware/gic.h +++ b/arch/arm/include/asm/hardware/gic.h @@ -41,6 +41,8 @@ void gic_secondary_init(unsigned int); void gic_cascade_irq(unsigned int gic_nr, unsigned int irq); void gic_raise_softirq(const struct cpumask *mask, unsigned int irq); void gic_enable_ppi(unsigned int); +bool gic_is_spi_pending(unsigned int irq); +void gic_clear_spi_pending(unsigned int irq); #endif #endif diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index d66605dea55..761c29e01ee 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -248,6 +248,8 @@ extern void _memset_io(volatile void __iomem *, int, size_t); #define ioremap(cookie,size) __arch_ioremap((cookie), (size), MT_DEVICE) #define ioremap_nocache(cookie,size) __arch_ioremap((cookie), (size), MT_DEVICE) +#define ioremap_strongly_ordered(cookie, size) __arch_ioremap(cookie, size, \ + MT_DEVICE_STRONGLY_ORDERED) #define ioremap_cached(cookie,size) __arch_ioremap((cookie), (size), MT_DEVICE_CACHED) #define ioremap_wc(cookie,size) __arch_ioremap((cookie), (size), MT_DEVICE_WC) #define iounmap __arch_iounmap diff --git a/arch/arm/include/asm/mach/flash.h b/arch/arm/include/asm/mach/flash.h index 4ca69fe2c85..36938ea24a3 100644 --- a/arch/arm/include/asm/mach/flash.h +++ b/arch/arm/include/asm/mach/flash.h @@ -17,6 +17,7 @@ struct mtd_info; * map_name: the map probe function name * name: flash device name (eg, as used with mtdparts=) * width: width of mapped device + * interleave: interleave mode feature support * init: method called at driver/device initialisation * exit: method called at driver/device removal * set_vpp: method called to enable or disable VPP @@ -28,6 +29,7 @@ struct flash_platform_data { const char *map_name; const char *name; unsigned int width; + unsigned int interleave; int (*init)(void); void (*exit)(void); void (*set_vpp)(int on); diff --git a/arch/arm/include/asm/mach/map.h b/arch/arm/include/asm/mach/map.h index d2fedb5aeb1..02fa7534bcb 100644 --- a/arch/arm/include/asm/mach/map.h +++ b/arch/arm/include/asm/mach/map.h @@ -29,6 +29,10 @@ struct map_desc { #define MT_MEMORY_NONCACHED 11 #define MT_MEMORY_DTCM 12 #define MT_MEMORY_ITCM 13 +#define MT_DEVICE_STRONGLY_ORDERED 14 +#define MT_MEMORY_R 15 +#define MT_MEMORY_RW 16 +#define MT_MEMORY_RX 17 #ifdef CONFIG_MMU extern void iotable_init(struct map_desc *, int); diff --git a/arch/arm/include/asm/mach/mmc.h b/arch/arm/include/asm/mach/mmc.h index bca864ac945..d439160a51c 100644 --- a/arch/arm/include/asm/mach/mmc.h +++ b/arch/arm/include/asm/mach/mmc.h @@ -7,6 +7,12 @@ #include #include #include +#include + +#define SDC_DAT1_DISABLE 0 +#define SDC_DAT1_ENABLE 1 +#define SDC_DAT1_ENWAKE 2 +#define SDC_DAT1_DISWAKE 3 struct embedded_sdio_data { struct sdio_cis cis; @@ -15,6 +21,91 @@ struct embedded_sdio_data { int num_funcs; }; +/* This structure keeps information per regulator */ +struct msm_mmc_reg_data { + /* voltage regulator handle */ + struct regulator *reg; + /* regulator name */ + const char *name; + /* voltage level to be set */ + unsigned int level; + /* Load values for low power and high power mode */ + unsigned int lpm_uA; + unsigned int hpm_uA; + /* + * is set voltage supported for this regulator? + * false => set voltage is not supported + * true => set voltage is supported + */ + bool set_voltage_sup; + /* is this regulator enabled? */ + bool is_enabled; + /* is this regulator needs to be always on? */ + bool always_on; + /* is low power mode setting required for this regulator? */ + bool lpm_sup; +}; + +/* + * This structure keeps information for all the + * regulators required for a SDCC slot. + */ +struct msm_mmc_slot_reg_data { + struct msm_mmc_reg_data *vdd_data; /* keeps VDD/VCC regulator info */ + struct msm_mmc_reg_data *vccq_data; /* keeps VCCQ regulator info */ + struct msm_mmc_reg_data *vddp_data; /* keeps VDD Pad regulator info */ +}; + +struct msm_mmc_gpio { + u32 no; + const char *name; + bool is_always_on; + bool is_enabled; +}; + +struct msm_mmc_gpio_data { + struct msm_mmc_gpio *gpio; + u8 size; +}; + +struct msm_mmc_pad_pull { + enum msm_tlmm_pull_tgt no; + u32 val; +}; + +struct msm_mmc_pad_pull_data { + struct msm_mmc_pad_pull *on; + struct msm_mmc_pad_pull *off; + u8 size; +}; + +struct msm_mmc_pad_drv { + enum msm_tlmm_hdrive_tgt no; + u32 val; +}; + +struct msm_mmc_pad_drv_data { + struct msm_mmc_pad_drv *on; + struct msm_mmc_pad_drv *off; + u8 size; +}; + +struct msm_mmc_pad_data { + struct msm_mmc_pad_pull_data *pull; + struct msm_mmc_pad_drv_data *drv; +}; + +struct msm_mmc_pin_data { + /* + * = 1 if controller pins are using gpios + * = 0 if controller has dedicated MSM pads + */ + u8 is_gpio; + u8 cfg_sts; + struct msm_mmc_gpio_data *gpio_data; + struct msm_mmc_pad_data *pad_data; +}; + struct mmc_platform_data { unsigned int ocr_mask; /* available voltages */ int built_in; /* built-in device flag */ @@ -23,6 +114,35 @@ struct mmc_platform_data { unsigned int (*status)(struct device *); struct embedded_sdio_data *embedded_sdio; int (*register_status_notify)(void (*callback)(int card_present, void *dev_id), void *dev_id); + /* + * XPC controls the maximum current in the + * default speed mode of SDXC card. + */ + unsigned int xpc_cap; + /* Supported UHS-I Modes */ + unsigned int uhs_caps; + void (*sdio_lpm_gpio_setup)(struct device *, unsigned int); + unsigned int status_irq; + unsigned int status_gpio; + unsigned int sdiowakeup_irq; + unsigned long irq_flags; + unsigned long mmc_bus_width; + int (*wpswitch) (struct device *); + int dummy52_required; + unsigned int msmsdcc_fmin; + unsigned int msmsdcc_fmid; + unsigned int msmsdcc_fmax; + bool nonremovable; + bool pclk_src_dfab; + int (*cfg_mpm_sdiowakeup)(struct device *, unsigned); + bool sdcc_v4_sup; + unsigned int wpswitch_gpio; + unsigned char wpswitch_polarity; + struct msm_mmc_slot_reg_data *vreg_data; + int is_sdio_al_client; + unsigned int *sup_clk_table; + unsigned char sup_clk_cnt; + struct msm_mmc_pin_data *pin_data; }; #endif diff --git a/arch/arm/include/asm/memory.h b/arch/arm/include/asm/memory.h index af44a8fb348..9ecd2dc0b73 100644 --- a/arch/arm/include/asm/memory.h +++ b/arch/arm/include/asm/memory.h @@ -299,6 +299,13 @@ static inline __deprecated void *bus_to_virt(unsigned long x) #define arch_is_coherent() 0 #endif +/* + * Set if the architecture speculatively fetches data into cache. + */ +#ifndef arch_has_speculative_dfetch +#define arch_has_speculative_dfetch() 0 +#endif + #endif #include diff --git a/arch/arm/include/asm/mutex.h b/arch/arm/include/asm/mutex.h index 93226cf23ae..fd3f17ef94a 100644 --- a/arch/arm/include/asm/mutex.h +++ b/arch/arm/include/asm/mutex.h @@ -41,6 +41,8 @@ __mutex_fastpath_lock(atomic_t *count, void (*fail_fn)(atomic_t *)) __res |= __ex_flag; if (unlikely(__res != 0)) fail_fn(count); + else + smp_rmb(); } static inline int @@ -61,6 +63,9 @@ __mutex_fastpath_lock_retval(atomic_t *count, int (*fail_fn)(atomic_t *)) __res |= __ex_flag; if (unlikely(__res != 0)) __res = fail_fn(count); + else + smp_rmb(); + return __res; } @@ -74,6 +79,7 @@ __mutex_fastpath_unlock(atomic_t *count, void (*fail_fn)(atomic_t *)) { int __ex_flag, __res, __orig; + smp_wmb(); __asm__ ( "ldrex %0, [%3] \n\t" @@ -119,6 +125,8 @@ __mutex_fastpath_trylock(atomic_t *count, int (*fail_fn)(atomic_t *)) : "=&r" (__orig), "=&r" (__res), "=&r" (__ex_flag) : "r" (&count->counter) : "cc", "memory" ); + if (__orig) + smp_rmb(); return __orig; } diff --git a/arch/arm/include/asm/page.h b/arch/arm/include/asm/page.h index ac75d084888..ac118163c51 100644 --- a/arch/arm/include/asm/page.h +++ b/arch/arm/include/asm/page.h @@ -201,6 +201,11 @@ typedef struct page *pgtable_t; extern int pfn_valid(unsigned long); #endif +#ifdef CONFIG_MEMORY_HOTPLUG_SPARSE +extern int _early_pfn_valid(unsigned long); +#define early_pfn_valid(pfn) (_early_pfn_valid(pfn)) +#endif + #include #endif /* !__ASSEMBLY__ */ diff --git a/arch/arm/include/asm/perf_event.h b/arch/arm/include/asm/perf_event.h index c4aa4e8c6af..0e1fd190105 100644 --- a/arch/arm/include/asm/perf_event.h +++ b/arch/arm/include/asm/perf_event.h @@ -24,6 +24,9 @@ enum arm_perf_pmu_ids { ARM_PERF_PMU_ID_V6MP, ARM_PERF_PMU_ID_CA8, ARM_PERF_PMU_ID_CA9, + ARM_PERF_PMU_ID_SCORPION, + ARM_PERF_PMU_ID_SCORPIONMP, + ARM_PERF_PMU_ID_KRAIT, ARM_NUM_PMU_IDS, }; diff --git a/arch/arm/include/asm/perftypes.h b/arch/arm/include/asm/perftypes.h new file mode 100644 index 00000000000..8d21dcd121c --- /dev/null +++ b/arch/arm/include/asm/perftypes.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* +** perftypes.h +** DESCRIPTION +** ksapi.ko function hooks header file +*/ + +#ifndef __PERFTYPES_H__ +#define __PERFTYPES_H__ + +typedef void (*VPVF)(void); +typedef void (*VPULF)(unsigned long); +typedef void (*VPULULF)(unsigned long, unsigned long); + +extern VPVF pp_interrupt_out_ptr; +extern VPVF pp_interrupt_in_ptr; +extern VPULF pp_process_remove_ptr; +extern void perf_mon_interrupt_in(void); +extern void perf_mon_interrupt_out(void); + +#endif diff --git a/arch/arm/include/asm/pgtable.h b/arch/arm/include/asm/pgtable.h index 5750704e027..cbb2f45f84d 100644 --- a/arch/arm/include/asm/pgtable.h +++ b/arch/arm/include/asm/pgtable.h @@ -22,7 +22,9 @@ #include #include +#include #include +#include /* * Just any arbitrary offset to the start of the vmalloc VM area: the @@ -232,16 +234,30 @@ extern pgprot_t pgprot_kernel; #define pgprot_writecombine(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE) +#define pgprot_device(prot) \ + __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_DEV_NONSHARED) + +#define pgprot_writethroughcache(prot) \ + __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_WRITETHROUGH) + +#define pgprot_writebackcache(prot) \ + __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_WRITEBACK) + +#define pgprot_writebackwacache(prot) \ + __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_WRITEALLOC) + #ifdef CONFIG_ARM_DMA_MEM_BUFFERABLE #define pgprot_dmacoherent(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE | L_PTE_XN) #define __HAVE_PHYS_MEM_ACCESS_PROT +#define COHERENT_IS_NORMAL 1 struct file; extern pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn, unsigned long size, pgprot_t vma_prot); #else #define pgprot_dmacoherent(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED | L_PTE_XN) +#define COHERENT_IS_NORMAL 0 #endif #endif /* __ASSEMBLY__ */ @@ -469,8 +485,15 @@ static inline pte_t pte_modify(pte_t pte, pgprot_t newprot) * remap a physical page `pfn' of size `size' with page protection `prot' * into virtual address `from' */ +#ifndef HAS_ARCH_IO_REMAP_PFN_RANGE #define io_remap_pfn_range(vma,from,pfn,size,prot) \ - remap_pfn_range(vma, from, pfn, size, prot) + remap_pfn_range(vma,from,pfn,size,prot) +#else +extern int arch_io_remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot); +#define io_remap_pfn_range(vma,from,pfn,size,prot) \ + arch_io_remap_pfn_range(vma,from,pfn,size,prot) +#endif + #define pgtable_cache_init() do { } while (0) diff --git a/arch/arm/include/asm/pmu.h b/arch/arm/include/asm/pmu.h index 7544ce6b481..48d148dc7da 100644 --- a/arch/arm/include/asm/pmu.h +++ b/arch/arm/include/asm/pmu.h @@ -16,6 +16,7 @@ enum arm_pmu_type { ARM_PMU_DEVICE_CPU = 0, + ARM_PMU_DEVICE_L2 = 1, ARM_NUM_PMU_DEVICES, }; diff --git a/arch/arm/include/asm/processor.h b/arch/arm/include/asm/processor.h index b2d9df5667a..12bc94d26f1 100644 --- a/arch/arm/include/asm/processor.h +++ b/arch/arm/include/asm/processor.h @@ -29,6 +29,8 @@ #define STACK_TOP_MAX TASK_SIZE #endif +extern unsigned int boot_reason; + struct debug_info { #ifdef CONFIG_HAVE_HW_BREAKPOINT struct perf_event *hbp[ARM_MAX_HBP_SLOTS]; diff --git a/arch/arm/include/asm/remote_spinlock.h b/arch/arm/include/asm/remote_spinlock.h new file mode 100644 index 00000000000..702b6696131 --- /dev/null +++ b/arch/arm/include/asm/remote_spinlock.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ASM_REMOTE_SPINLOCK_H +#define __ASM_REMOTE_SPINLOCK_H + +#include + +#endif /* __ASM_REMOTE_SPINLOCK_H */ diff --git a/arch/arm/include/asm/setup.h b/arch/arm/include/asm/setup.h index ee2ad8ae07a..950665dfcc6 100644 --- a/arch/arm/include/asm/setup.h +++ b/arch/arm/include/asm/setup.h @@ -37,6 +37,15 @@ struct tag_core { /* it is allowed to have multiple ATAG_MEM nodes */ #define ATAG_MEM 0x54410002 +/* it is allowed to have multiple ATAG_MEM_RESERVED nodes */ +/* these indicate places where hotpluggable memory is present */ +/* which are not active during boot */ +#define ATAG_MEM_RESERVED 0x5441000A +/* it is allowed to have multiple ATAG_MEM_LOW_POWER nodes */ +/* these indicate memory which can be put in a low power state */ +#define ATAG_MEM_LOW_POWER 0x5441000B +/* these indicate memory which can be reclaimed from OSBL memory after bootup */ +#define ATAG_MEM_OSBL 0x5441000C struct tag_mem32 { __u32 size; @@ -221,6 +230,18 @@ extern int arm_add_memory(phys_addr_t start, unsigned long size); extern void early_print(const char *str, ...); extern void dump_machine_table(void); +/* + * Early command line parameters. + */ +struct early_params { + const char *arg; + void (*fn)(char **p); +}; + +#define __early_param(name,fn) \ +static struct early_params __early_##fn __used \ +__attribute__((__section__(".early_param.init"))) = { name, fn } + #endif /* __KERNEL__ */ #endif diff --git a/arch/arm/include/asm/system.h b/arch/arm/include/asm/system.h index 832888d0c20..d3808b5eeed 100644 --- a/arch/arm/include/asm/system.h +++ b/arch/arm/include/asm/system.h @@ -69,6 +69,8 @@ #define __exception_irq_entry __exception #endif +void cpu_idle_wait(void); + struct thread_info; struct task_struct; @@ -141,7 +143,7 @@ extern unsigned int user_debug; #define dsb() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 4" \ : : "r" (0) : "memory") #define dmb() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" \ - : : "r" (0) : "memory") + : : "r" (0) : "memory") #elif defined(CONFIG_CPU_FA526) #define isb() __asm__ __volatile__ ("mcr p15, 0, %0, c7, c5, 4" \ : : "r" (0) : "memory") diff --git a/arch/arm/include/asm/tlbflush.h b/arch/arm/include/asm/tlbflush.h index d2005de383b..ab2a488ae77 100644 --- a/arch/arm/include/asm/tlbflush.h +++ b/arch/arm/include/asm/tlbflush.h @@ -433,7 +433,7 @@ local_flush_tlb_page(struct vm_area_struct *vma, unsigned long uaddr) if (tlb_flag(TLB_V6_I_PAGE)) asm("mcr p15, 0, %0, c8, c5, 1" : : "r" (uaddr) : "cc"); if (tlb_flag(TLB_V7_UIS_PAGE)) -#ifdef CONFIG_ARM_ERRATA_720789 +#if defined(CONFIG_ARM_ERRATA_720789) || defined(CONFIG_ARCH_MSM8X60) asm("mcr p15, 0, %0, c8, c3, 3" : : "r" (uaddr & PAGE_MASK) : "cc"); #else asm("mcr p15, 0, %0, c8, c3, 1" : : "r" (uaddr) : "cc"); @@ -480,7 +480,11 @@ static inline void local_flush_tlb_kernel_page(unsigned long kaddr) if (tlb_flag(TLB_V6_I_PAGE)) asm("mcr p15, 0, %0, c8, c5, 1" : : "r" (kaddr) : "cc"); if (tlb_flag(TLB_V7_UIS_PAGE)) +#ifdef CONFIG_ARCH_MSM8X60 + asm("mcr p15, 0, %0, c8, c3, 3" : : "r" (kaddr) : "cc"); +#else asm("mcr p15, 0, %0, c8, c3, 1" : : "r" (kaddr) : "cc"); +#endif if (tlb_flag(TLB_BTB)) { /* flush the branch target cache */ diff --git a/arch/arm/include/asm/vfp.h b/arch/arm/include/asm/vfp.h index f4ab34fd4f7..cb3ea0845ff 100644 --- a/arch/arm/include/asm/vfp.h +++ b/arch/arm/include/asm/vfp.h @@ -21,7 +21,7 @@ #define FPSID_FORMAT_MASK (0x3 << FPSID_FORMAT_BIT) #define FPSID_NODOUBLE (1<<20) #define FPSID_ARCH_BIT (16) -#define FPSID_ARCH_MASK (0xF << FPSID_ARCH_BIT) +#define FPSID_ARCH_MASK (0x7F << FPSID_ARCH_BIT) #define FPSID_PART_BIT (8) #define FPSID_PART_MASK (0xFF << FPSID_PART_BIT) #define FPSID_VARIANT_BIT (4) @@ -82,3 +82,8 @@ #define VFPOPDESC_UNUSED_BIT (24) #define VFPOPDESC_UNUSED_MASK (0xFF << VFPOPDESC_UNUSED_BIT) #define VFPOPDESC_OPDESC_MASK (~(VFPOPDESC_LENGTH_MASK | VFPOPDESC_UNUSED_MASK)) + +#ifndef __ASSEMBLY__ +int vfp_flush_context(void); +void vfp_reinit(void); +#endif diff --git a/arch/arm/kernel/armksyms.c b/arch/arm/kernel/armksyms.c index acca35aebe2..263eaaf3f26 100644 --- a/arch/arm/kernel/armksyms.c +++ b/arch/arm/kernel/armksyms.c @@ -52,10 +52,6 @@ extern void fpundefinstr(void); EXPORT_SYMBOL(__backtrace); - /* platform dependent support */ -EXPORT_SYMBOL(__udelay); -EXPORT_SYMBOL(__const_udelay); - /* networking */ EXPORT_SYMBOL(csum_partial); EXPORT_SYMBOL(csum_partial_copy_from_user); diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 2cd00764016..af0a86c2688 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -734,7 +734,14 @@ ENTRY(__switch_to) ldr r7, [r7, #TSK_STACK_CANARY] #endif #ifdef CONFIG_CPU_USE_DOMAINS +#ifdef CONFIG_EMULATE_DOMAIN_MANAGER_V7 + stmdb r13!, {r0-r3, lr} + mov r0, r6 + bl emulate_domain_manager_set + ldmia r13!, {r0-r3, lr} +#else mcr p15, 0, r6, c3, c0, 0 @ Set domain register +#endif #endif mov r5, r0 add r4, r2, #TI_CPU_SAVE diff --git a/arch/arm/kernel/head.S b/arch/arm/kernel/head.S index 278c1b0ebb2..f379b0411fc 100644 --- a/arch/arm/kernel/head.S +++ b/arch/arm/kernel/head.S @@ -362,10 +362,17 @@ __enable_mmu: #ifdef CONFIG_CPU_ICACHE_DISABLE bic r0, r0, #CR_I #endif - mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \ +#ifdef CONFIG_EMULATE_DOMAIN_MANAGER_V7 + mov r5, #(domain_val(DOMAIN_USER, DOMAIN_CLIENT) | \ + domain_val(DOMAIN_KERNEL, DOMAIN_CLIENT) | \ + domain_val(DOMAIN_TABLE, DOMAIN_CLIENT) | \ + domain_val(DOMAIN_IO, DOMAIN_CLIENT)) +#else + mov r5, #(domain_val(DOMAIN_USER, DOMAIN_CLIENT) | \ domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \ - domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \ + domain_val(DOMAIN_TABLE, DOMAIN_CLIENT) | \ domain_val(DOMAIN_IO, DOMAIN_CLIENT)) +#endif mcr p15, 0, r5, c3, c0, 0 @ load domain access register mcr p15, 0, r4, c2, c0, 0 @ load page table pointer b __turn_mmu_on diff --git a/arch/arm/kernel/hw_breakpoint.c b/arch/arm/kernel/hw_breakpoint.c index 87acc25d7a3..951eb8d334d 100644 --- a/arch/arm/kernel/hw_breakpoint.c +++ b/arch/arm/kernel/hw_breakpoint.c @@ -833,6 +833,18 @@ static int hw_breakpoint_pending(unsigned long addr, unsigned int fsr, return ret; } +static void reset_brps_reserved_reg(int n) +{ + int i; + + /* we must also reset any reserved registers. */ + for (i = 0; i < n; ++i) { + write_wb_reg(ARM_BASE_BCR + i, 0UL); + write_wb_reg(ARM_BASE_BVR + i, 0UL); + } + +} + /* * One-time initialisation. */ @@ -880,11 +892,11 @@ static void reset_ctrl_regs(void *info) if (enable_monitor_mode()) return; - /* We must also reset any reserved registers. */ - for (i = 0; i < core_num_brps + core_num_reserved_brps; ++i) { - write_wb_reg(ARM_BASE_BCR + i, 0UL); - write_wb_reg(ARM_BASE_BVR + i, 0UL); - } +#ifdef CONFIG_HAVE_HW_BRKPT_RESERVED_RW_ACCESS + reset_brps_reserved_reg(core_num_brps); +#else + reset_brps_reserved_reg(core_num_brps + core_num_reserved_brps); +#endif for (i = 0; i < core_num_wrps; ++i) { write_wb_reg(ARM_BASE_WCR + i, 0UL); diff --git a/arch/arm/kernel/irq.c b/arch/arm/kernel/irq.c index 83bbad03fcc..40603864994 100644 --- a/arch/arm/kernel/irq.c +++ b/arch/arm/kernel/irq.c @@ -42,6 +42,8 @@ #include #include +#include + /* * No architecture-specific irq_finish function defined in arm/arch/irqs.h. */ @@ -76,6 +78,7 @@ asm_do_IRQ(unsigned int irq, struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); + perf_mon_interrupt_in(); irq_enter(); /* @@ -95,6 +98,7 @@ asm_do_IRQ(unsigned int irq, struct pt_regs *regs) irq_exit(); set_irq_regs(old_regs); + perf_mon_interrupt_out(); } void set_irq_flags(unsigned int irq, unsigned int iflags) diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c index e59bbd496c3..601ef744500 100644 --- a/arch/arm/kernel/machine_kexec.c +++ b/arch/arm/kernel/machine_kexec.c @@ -86,6 +86,7 @@ void machine_kexec(struct kimage *image) unsigned long reboot_code_buffer_phys; void *reboot_code_buffer; + arch_kexec(); page_list = image->head & PAGE_MASK; @@ -120,5 +121,5 @@ void machine_kexec(struct kimage *image) cpu_proc_fin(); outer_inv_all(); flush_cache_all(); - cpu_reset(reboot_code_buffer_phys); + __virt_to_phys(cpu_reset)(reboot_code_buffer_phys); } diff --git a/arch/arm/kernel/perf_event.c b/arch/arm/kernel/perf_event.c index 2b5b1421596..c9982829f21 100644 --- a/arch/arm/kernel/perf_event.c +++ b/arch/arm/kernel/perf_event.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -71,6 +72,10 @@ struct arm_pmu { enum arm_perf_pmu_ids id; const char *name; irqreturn_t (*handle_irq)(int irq_num, void *dev); +#ifdef CONFIG_SMP + void (*secondary_enable)(unsigned int irq); + void (*secondary_disable)(unsigned int irq); +#endif void (*enable)(struct hw_perf_event *evt, int idx); void (*disable)(struct hw_perf_event *evt, int idx); int (*get_event_idx)(struct cpu_hw_events *cpuc, @@ -426,6 +431,10 @@ armpmu_reserve_hardware(void) pr_warning("unable to request IRQ%d for ARM perf " "counters\n", irq); break; +#ifdef CONFIG_SMP + } else if (armpmu->secondary_enable) { + armpmu->secondary_enable(irq); +#endif } } @@ -449,8 +458,13 @@ armpmu_release_hardware(void) for (i = pmu_device->num_resources - 1; i >= 0; --i) { irq = platform_get_irq(pmu_device, i); - if (irq >= 0) + if (irq >= 0) { free_irq(irq, NULL); +#ifdef CONFIG_SMP + if (armpmu->secondary_disable) + armpmu->secondary_disable(irq); +#endif + } } armpmu->stop(); @@ -624,6 +638,10 @@ static struct pmu pmu = { #include "perf_event_xscale.c" #include "perf_event_v6.c" #include "perf_event_v7.c" +#include "perf_event_msm.c" +#include "perf_event_msm_l2.c" +#include "perf_event_msm_krait.c" +#include "perf_event_msm_krait_l2.c" /* * Ensure the PMU has sane values out of reset. @@ -674,6 +692,22 @@ init_hw_perf_events(void) armpmu = xscale2pmu_init(); break; } + /* Qualcomm CPUs */ + } else if (0x51 == implementor) { + switch (part_number) { + case 0x00F0: /* 8x50 & 7x30*/ + armpmu = armv7_scorpion_pmu_init(); + break; + case 0x02D0: /* 8x60 */ + armpmu = armv7_scorpionmp_pmu_init(); + scorpionmp_l2_pmu_init(); + break; + case 0x0490: /* 8960 sim */ + case 0x04D0: /* 8960 */ + armpmu = armv7_krait_pmu_init(); + krait_l2_pmu_init(); + break; + } } if (armpmu) { diff --git a/arch/arm/kernel/perf_event_msm.c b/arch/arm/kernel/perf_event_msm.c new file mode 100644 index 00000000000..ae8bfcbf7f0 --- /dev/null +++ b/arch/arm/kernel/perf_event_msm.c @@ -0,0 +1,710 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "../vfp/vfpinstr.h" + +#ifdef CONFIG_CPU_V7 +enum scorpion_perf_common { + SCORPION_EVT_START_IDX = 0x4c, + SCORPION_ICACHE_EXPL_INV = 0x4c, + SCORPION_ICACHE_MISS = 0x4d, + SCORPION_ICACHE_ACCESS = 0x4e, + SCORPION_ICACHE_CACHEREQ_L2 = 0x4f, + SCORPION_ICACHE_NOCACHE_L2 = 0x50, + SCORPION_HIQUP_NOPED = 0x51, + SCORPION_DATA_ABORT = 0x52, + SCORPION_IRQ = 0x53, + SCORPION_FIQ = 0x54, + SCORPION_ALL_EXCPT = 0x55, + SCORPION_UNDEF = 0x56, + SCORPION_SVC = 0x57, + SCORPION_SMC = 0x58, + SCORPION_PREFETCH_ABORT = 0x59, + SCORPION_INDEX_CHECK = 0x5a, + SCORPION_NULL_CHECK = 0x5b, + SCORPION_ICIMVAU_IMPL_ICIALLU = 0x5c, + SCORPION_NONICIALLU_BTAC_INV = 0x5d, + SCORPION_IMPL_ICIALLU = 0x5e, + SCORPION_EXPL_ICIALLU = 0x5f, + SCORPION_SPIPE_ONLY_CYCLES = 0x60, + SCORPION_XPIPE_ONLY_CYCLES = 0x61, + SCORPION_DUAL_CYCLES = 0x62, + SCORPION_DISPATCH_ANY_CYCLES = 0x63, + SCORPION_FIFO_FULLBLK_CMT = 0x64, + SCORPION_FAIL_COND_INST = 0x65, + SCORPION_PASS_COND_INST = 0x66, + SCORPION_ALLOW_VU_CLK = 0x67, + SCORPION_VU_IDLE = 0x68, + SCORPION_ALLOW_L2_CLK = 0x69, + SCORPION_L2_IDLE = 0x6a, + SCORPION_DTLB_IMPL_INV_SCTLR_DACR = 0x6b, + SCORPION_DTLB_EXPL_INV = 0x6c, + SCORPION_DTLB_MISS = 0x6d, + SCORPION_DTLB_ACCESS = 0x6e, + SCORPION_ITLB_MISS = 0x6f, + SCORPION_ITLB_IMPL_INV = 0x70, + SCORPION_ITLB_EXPL_INV = 0x71, + SCORPION_UTLB_D_MISS = 0x72, + SCORPION_UTLB_D_ACCESS = 0x73, + SCORPION_UTLB_I_MISS = 0x74, + SCORPION_UTLB_I_ACCESS = 0x75, + SCORPION_UTLB_INV_ASID = 0x76, + SCORPION_UTLB_INV_MVA = 0x77, + SCORPION_UTLB_INV_ALL = 0x78, + SCORPION_S2_HOLD_RDQ_UNAVAIL = 0x79, + SCORPION_S2_HOLD = 0x7a, + SCORPION_S2_HOLD_DEV_OP = 0x7b, + SCORPION_S2_HOLD_ORDER = 0x7c, + SCORPION_S2_HOLD_BARRIER = 0x7d, + SCORPION_VIU_DUAL_CYCLE = 0x7e, + SCORPION_VIU_SINGLE_CYCLE = 0x7f, + SCORPION_VX_PIPE_WAR_STALL_CYCLES = 0x80, + SCORPION_VX_PIPE_WAW_STALL_CYCLES = 0x81, + SCORPION_VX_PIPE_RAW_STALL_CYCLES = 0x82, + SCORPION_VX_PIPE_LOAD_USE_STALL = 0x83, + SCORPION_VS_PIPE_WAR_STALL_CYCLES = 0x84, + SCORPION_VS_PIPE_WAW_STALL_CYCLES = 0x85, + SCORPION_VS_PIPE_RAW_STALL_CYCLES = 0x86, + SCORPION_EXCEPTIONS_INV_OPERATION = 0x87, + SCORPION_EXCEPTIONS_DIV_BY_ZERO = 0x88, + SCORPION_COND_INST_FAIL_VX_PIPE = 0x89, + SCORPION_COND_INST_FAIL_VS_PIPE = 0x8a, + SCORPION_EXCEPTIONS_OVERFLOW = 0x8b, + SCORPION_EXCEPTIONS_UNDERFLOW = 0x8c, + SCORPION_EXCEPTIONS_DENORM = 0x8d, +}; + +enum scorpion_perf_smp { + SCORPIONMP_NUM_BARRIERS = 0x8e, + SCORPIONMP_BARRIER_CYCLES = 0x8f, +}; + +enum scorpion_perf_up { + SCORPION_BANK_AB_HIT = 0x8e, + SCORPION_BANK_AB_ACCESS = 0x8f, + SCORPION_BANK_CD_HIT = 0x90, + SCORPION_BANK_CD_ACCESS = 0x91, + SCORPION_BANK_AB_DSIDE_HIT = 0x92, + SCORPION_BANK_AB_DSIDE_ACCESS = 0x93, + SCORPION_BANK_CD_DSIDE_HIT = 0x94, + SCORPION_BANK_CD_DSIDE_ACCESS = 0x95, + SCORPION_BANK_AB_ISIDE_HIT = 0x96, + SCORPION_BANK_AB_ISIDE_ACCESS = 0x97, + SCORPION_BANK_CD_ISIDE_HIT = 0x98, + SCORPION_BANK_CD_ISIDE_ACCESS = 0x99, + SCORPION_ISIDE_RD_WAIT = 0x9a, + SCORPION_DSIDE_RD_WAIT = 0x9b, + SCORPION_BANK_BYPASS_WRITE = 0x9c, + SCORPION_BANK_AB_NON_CASTOUT = 0x9d, + SCORPION_BANK_AB_L2_CASTOUT = 0x9e, + SCORPION_BANK_CD_NON_CASTOUT = 0x9f, + SCORPION_BANK_CD_L2_CASTOUT = 0xa0, +}; + +static const unsigned armv7_scorpion_perf_map[PERF_COUNT_HW_MAX] = { + [PERF_COUNT_HW_CPU_CYCLES] = ARMV7_PERFCTR_CPU_CYCLES, + [PERF_COUNT_HW_INSTRUCTIONS] = ARMV7_PERFCTR_INSTR_EXECUTED, + [PERF_COUNT_HW_CACHE_REFERENCES] = HW_OP_UNSUPPORTED, + [PERF_COUNT_HW_CACHE_MISSES] = HW_OP_UNSUPPORTED, + [PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = ARMV7_PERFCTR_PC_WRITE, + [PERF_COUNT_HW_BRANCH_MISSES] = ARMV7_PERFCTR_PC_BRANCH_MIS_PRED, + [PERF_COUNT_HW_BUS_CYCLES] = ARMV7_PERFCTR_CLOCK_CYCLES, +}; + +static const unsigned armv7_scorpion_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + [C(L1D)] = { + /* + * The performance counters don't differentiate between read + * and write accesses/misses so this isn't strictly correct, + * but it's the best we can do. Writes and reads get + * combined. + */ + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = ARMV7_PERFCTR_DCACHE_ACCESS, + [C(RESULT_MISS)] = ARMV7_PERFCTR_DCACHE_REFILL, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = ARMV7_PERFCTR_DCACHE_ACCESS, + [C(RESULT_MISS)] = ARMV7_PERFCTR_DCACHE_REFILL, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(L1I)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = SCORPION_ICACHE_ACCESS, + [C(RESULT_MISS)] = SCORPION_ICACHE_MISS, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = SCORPION_ICACHE_ACCESS, + [C(RESULT_MISS)] = SCORPION_ICACHE_MISS, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(LL)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(DTLB)] = { + /* + * Only ITLB misses and DTLB refills are supported. + * If users want the DTLB refills misses a raw counter + * must be used. + */ + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = SCORPION_DTLB_ACCESS, + [C(RESULT_MISS)] = SCORPION_DTLB_MISS, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = SCORPION_DTLB_ACCESS, + [C(RESULT_MISS)] = SCORPION_DTLB_MISS, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(ITLB)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = SCORPION_ITLB_MISS, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = SCORPION_ITLB_MISS, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(BPU)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] + = ARMV7_PERFCTR_PC_BRANCH_MIS_USED, + [C(RESULT_MISS)] + = ARMV7_PERFCTR_PC_BRANCH_MIS_USED, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] + = ARMV7_PERFCTR_PC_BRANCH_MIS_USED, + [C(RESULT_MISS)] + = ARMV7_PERFCTR_PC_BRANCH_MIS_USED, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, +}; + +struct scorpion_evt { + /* + * The scorpion_evt_type field corresponds to the actual Scorpion + * event codes. These map many-to-one to the armv7 defined codes + */ + u32 scorpion_evt_type; + + /* + * The group_setval field corresponds to the value that the group + * register needs to be set to. This value is deduced from the row + * and column that the event belongs to in the event table + */ + u32 group_setval; + + /* + * The groupcode corresponds to the group that the event belongs to. + * Scorpion has 5 groups of events LPM0, LPM1, LPM2, L2LPM and VLPM + * going from 0 to 4 in terms of the codes used + */ + u8 groupcode; + + /* + * The armv7_evt_type field corresponds to the armv7 defined event + * code that the Scorpion events map to + */ + u32 armv7_evt_type; +}; + +static const struct scorpion_evt scorpion_event[] = { + {SCORPION_ICACHE_EXPL_INV, 0x80000500, 0, 0x4d}, + {SCORPION_ICACHE_MISS, 0x80050000, 0, 0x4e}, + {SCORPION_ICACHE_ACCESS, 0x85000000, 0, 0x4f}, + {SCORPION_ICACHE_CACHEREQ_L2, 0x86000000, 0, 0x4f}, + {SCORPION_ICACHE_NOCACHE_L2, 0x87000000, 0, 0x4f}, + {SCORPION_HIQUP_NOPED, 0x80080000, 0, 0x4e}, + {SCORPION_DATA_ABORT, 0x8000000a, 0, 0x4c}, + {SCORPION_IRQ, 0x80000a00, 0, 0x4d}, + {SCORPION_FIQ, 0x800a0000, 0, 0x4e}, + {SCORPION_ALL_EXCPT, 0x8a000000, 0, 0x4f}, + {SCORPION_UNDEF, 0x8000000b, 0, 0x4c}, + {SCORPION_SVC, 0x80000b00, 0, 0x4d}, + {SCORPION_SMC, 0x800b0000, 0, 0x4e}, + {SCORPION_PREFETCH_ABORT, 0x8b000000, 0, 0x4f}, + {SCORPION_INDEX_CHECK, 0x8000000c, 0, 0x4c}, + {SCORPION_NULL_CHECK, 0x80000c00, 0, 0x4d}, + {SCORPION_ICIMVAU_IMPL_ICIALLU, 0x8000000d, 0, 0x4c}, + {SCORPION_NONICIALLU_BTAC_INV, 0x80000d00, 0, 0x4d}, + {SCORPION_IMPL_ICIALLU, 0x800d0000, 0, 0x4e}, + {SCORPION_EXPL_ICIALLU, 0x8d000000, 0, 0x4f}, + + {SCORPION_SPIPE_ONLY_CYCLES, 0x80000600, 1, 0x51}, + {SCORPION_XPIPE_ONLY_CYCLES, 0x80060000, 1, 0x52}, + {SCORPION_DUAL_CYCLES, 0x86000000, 1, 0x53}, + {SCORPION_DISPATCH_ANY_CYCLES, 0x89000000, 1, 0x53}, + {SCORPION_FIFO_FULLBLK_CMT, 0x8000000d, 1, 0x50}, + {SCORPION_FAIL_COND_INST, 0x800d0000, 1, 0x52}, + {SCORPION_PASS_COND_INST, 0x8d000000, 1, 0x53}, + {SCORPION_ALLOW_VU_CLK, 0x8000000e, 1, 0x50}, + {SCORPION_VU_IDLE, 0x80000e00, 1, 0x51}, + {SCORPION_ALLOW_L2_CLK, 0x800e0000, 1, 0x52}, + {SCORPION_L2_IDLE, 0x8e000000, 1, 0x53}, + + {SCORPION_DTLB_IMPL_INV_SCTLR_DACR, 0x80000001, 2, 0x54}, + {SCORPION_DTLB_EXPL_INV, 0x80000100, 2, 0x55}, + {SCORPION_DTLB_MISS, 0x80010000, 2, 0x56}, + {SCORPION_DTLB_ACCESS, 0x81000000, 2, 0x57}, + {SCORPION_ITLB_MISS, 0x80000200, 2, 0x55}, + {SCORPION_ITLB_IMPL_INV, 0x80020000, 2, 0x56}, + {SCORPION_ITLB_EXPL_INV, 0x82000000, 2, 0x57}, + {SCORPION_UTLB_D_MISS, 0x80000003, 2, 0x54}, + {SCORPION_UTLB_D_ACCESS, 0x80000300, 2, 0x55}, + {SCORPION_UTLB_I_MISS, 0x80030000, 2, 0x56}, + {SCORPION_UTLB_I_ACCESS, 0x83000000, 2, 0x57}, + {SCORPION_UTLB_INV_ASID, 0x80000400, 2, 0x55}, + {SCORPION_UTLB_INV_MVA, 0x80040000, 2, 0x56}, + {SCORPION_UTLB_INV_ALL, 0x84000000, 2, 0x57}, + {SCORPION_S2_HOLD_RDQ_UNAVAIL, 0x80000800, 2, 0x55}, + {SCORPION_S2_HOLD, 0x88000000, 2, 0x57}, + {SCORPION_S2_HOLD_DEV_OP, 0x80000900, 2, 0x55}, + {SCORPION_S2_HOLD_ORDER, 0x80090000, 2, 0x56}, + {SCORPION_S2_HOLD_BARRIER, 0x89000000, 2, 0x57}, + + {SCORPION_VIU_DUAL_CYCLE, 0x80000001, 4, 0x5c}, + {SCORPION_VIU_SINGLE_CYCLE, 0x80000100, 4, 0x5d}, + {SCORPION_VX_PIPE_WAR_STALL_CYCLES, 0x80000005, 4, 0x5c}, + {SCORPION_VX_PIPE_WAW_STALL_CYCLES, 0x80000500, 4, 0x5d}, + {SCORPION_VX_PIPE_RAW_STALL_CYCLES, 0x80050000, 4, 0x5e}, + {SCORPION_VX_PIPE_LOAD_USE_STALL, 0x80000007, 4, 0x5c}, + {SCORPION_VS_PIPE_WAR_STALL_CYCLES, 0x80000008, 4, 0x5c}, + {SCORPION_VS_PIPE_WAW_STALL_CYCLES, 0x80000800, 4, 0x5d}, + {SCORPION_VS_PIPE_RAW_STALL_CYCLES, 0x80080000, 4, 0x5e}, + {SCORPION_EXCEPTIONS_INV_OPERATION, 0x8000000b, 4, 0x5c}, + {SCORPION_EXCEPTIONS_DIV_BY_ZERO, 0x80000b00, 4, 0x5d}, + {SCORPION_COND_INST_FAIL_VX_PIPE, 0x800b0000, 4, 0x5e}, + {SCORPION_COND_INST_FAIL_VS_PIPE, 0x8b000000, 4, 0x5f}, + {SCORPION_EXCEPTIONS_OVERFLOW, 0x8000000c, 4, 0x5c}, + {SCORPION_EXCEPTIONS_UNDERFLOW, 0x80000c00, 4, 0x5d}, + {SCORPION_EXCEPTIONS_DENORM, 0x8c000000, 4, 0x5f}, + +#ifdef CONFIG_MSM_SMP + {SCORPIONMP_NUM_BARRIERS, 0x80000e00, 3, 0x59}, + {SCORPIONMP_BARRIER_CYCLES, 0x800e0000, 3, 0x5a}, +#else + {SCORPION_BANK_AB_HIT, 0x80000001, 3, 0x58}, + {SCORPION_BANK_AB_ACCESS, 0x80000100, 3, 0x59}, + {SCORPION_BANK_CD_HIT, 0x80010000, 3, 0x5a}, + {SCORPION_BANK_CD_ACCESS, 0x81000000, 3, 0x5b}, + {SCORPION_BANK_AB_DSIDE_HIT, 0x80000002, 3, 0x58}, + {SCORPION_BANK_AB_DSIDE_ACCESS, 0x80000200, 3, 0x59}, + {SCORPION_BANK_CD_DSIDE_HIT, 0x80020000, 3, 0x5a}, + {SCORPION_BANK_CD_DSIDE_ACCESS, 0x82000000, 3, 0x5b}, + {SCORPION_BANK_AB_ISIDE_HIT, 0x80000003, 3, 0x58}, + {SCORPION_BANK_AB_ISIDE_ACCESS, 0x80000300, 3, 0x59}, + {SCORPION_BANK_CD_ISIDE_HIT, 0x80030000, 3, 0x5a}, + {SCORPION_BANK_CD_ISIDE_ACCESS, 0x83000000, 3, 0x5b}, + {SCORPION_ISIDE_RD_WAIT, 0x80000009, 3, 0x58}, + {SCORPION_DSIDE_RD_WAIT, 0x80090000, 3, 0x5a}, + {SCORPION_BANK_BYPASS_WRITE, 0x8000000a, 3, 0x58}, + {SCORPION_BANK_AB_NON_CASTOUT, 0x8000000c, 3, 0x58}, + {SCORPION_BANK_AB_L2_CASTOUT, 0x80000c00, 3, 0x59}, + {SCORPION_BANK_CD_NON_CASTOUT, 0x800c0000, 3, 0x5a}, + {SCORPION_BANK_CD_L2_CASTOUT, 0x8c000000, 3, 0x5b}, +#endif +}; + +static unsigned int get_scorpion_evtinfo(unsigned int scorpion_evt_type, + struct scorpion_evt *evtinfo) +{ + u32 idx; + + if (scorpion_evt_type < SCORPION_EVT_START_IDX || scorpion_evt_type >= + (ARRAY_SIZE(scorpion_event) + SCORPION_EVT_START_IDX)) + return -EINVAL; + idx = scorpion_evt_type - SCORPION_EVT_START_IDX; + if (scorpion_event[idx].scorpion_evt_type == scorpion_evt_type) { + evtinfo->group_setval = scorpion_event[idx].group_setval; + evtinfo->groupcode = scorpion_event[idx].groupcode; + evtinfo->armv7_evt_type = scorpion_event[idx].armv7_evt_type; + return scorpion_event[idx].armv7_evt_type; + } + return -EINVAL; +} + +static u32 scorpion_read_lpm0(void) +{ + u32 val; + + asm volatile("mrc p15, 0, %0, c15, c0, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_lpm0(u32 val) +{ + asm volatile("mcr p15, 0, %0, c15, c0, 0" : : "r" (val)); +} + +static u32 scorpion_read_lpm1(void) +{ + u32 val; + + asm volatile("mrc p15, 1, %0, c15, c0, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_lpm1(u32 val) +{ + asm volatile("mcr p15, 1, %0, c15, c0, 0" : : "r" (val)); +} + +static u32 scorpion_read_lpm2(void) +{ + u32 val; + + asm volatile("mrc p15, 2, %0, c15, c0, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_lpm2(u32 val) +{ + asm volatile("mcr p15, 2, %0, c15, c0, 0" : : "r" (val)); +} + +static u32 scorpion_read_l2lpm(void) +{ + u32 val; + + asm volatile("mrc p15, 3, %0, c15, c2, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_l2lpm(u32 val) +{ + asm volatile("mcr p15, 3, %0, c15, c2, 0" : : "r" (val)); +} + +static u32 scorpion_read_vlpm(void) +{ + u32 val; + + asm volatile("mrc p10, 7, %0, c11, c0, 0" : "=r" (val)); + return val; +} + +static void scorpion_write_vlpm(u32 val) +{ + asm volatile("mcr p10, 7, %0, c11, c0, 0" : : "r" (val)); +} + +/* + * The Scorpion processor supports performance monitoring for Venum unit. + * In order to access the performance monitor registers corresponding to + * VFP, CPACR and FPEXC registers need to be set up beforehand. + * Also, they need to be recovered once the access is done. + * This is the reason for having pre and post functions + */ + +static DEFINE_PER_CPU(u32, venum_orig_val); +static DEFINE_PER_CPU(u32, fp_orig_val); + +static void scorpion_pre_vlpm(void) +{ + u32 venum_new_val; + u32 fp_new_val; + + /* CPACR Enable CP10 access*/ + venum_orig_val = get_copro_access(); + venum_new_val = venum_orig_val | CPACC_SVC(10); + set_copro_access(venum_new_val); + /* Enable FPEXC */ + fp_orig_val = fmrx(FPEXC); + fp_new_val = fp_orig_val | FPEXC_EN; + fmxr(FPEXC, fp_new_val); +} + +static void scorpion_post_vlpm(void) +{ + /* Restore FPEXC*/ + fmxr(FPEXC, fp_orig_val); + isb(); + /* Restore CPACR*/ + set_copro_access(venum_orig_val); +} + +struct scorpion_access_funcs { + u32 (*read) (void); + void (*write) (u32); + void (*pre) (void); + void (*post) (void); +}; + +/* + * The scorpion_functions array is used to set up the event register codes + * based on the group to which an event belongs to. + * Having the following array modularizes the code for doing that. + */ +struct scorpion_access_funcs scorpion_functions[] = { + {scorpion_read_lpm0, scorpion_write_lpm0, NULL, NULL}, + {scorpion_read_lpm1, scorpion_write_lpm1, NULL, NULL}, + {scorpion_read_lpm2, scorpion_write_lpm2, NULL, NULL}, + {scorpion_read_l2lpm, scorpion_write_l2lpm, NULL, NULL}, + {scorpion_read_vlpm, scorpion_write_vlpm, scorpion_pre_vlpm, + scorpion_post_vlpm}, +}; + +static inline u32 scorpion_get_columnmask(u32 evt_code) +{ + const u32 columnmasks[] = {0xffffff00, 0xffff00ff, 0xff00ffff, + 0x80ffffff}; + + return columnmasks[evt_code & 0x3]; +} + +static void scorpion_evt_setup(u32 gr, u32 setval, u32 evt_code) +{ + u32 val; + + if (scorpion_functions[gr].pre) + scorpion_functions[gr].pre(); + val = scorpion_get_columnmask(evt_code) & scorpion_functions[gr].read(); + val = val | setval; + scorpion_functions[gr].write(val); + if (scorpion_functions[gr].post) + scorpion_functions[gr].post(); +} + +static void scorpion_clear_pmuregs(void) +{ + unsigned long flags; + + scorpion_write_lpm0(0); + scorpion_write_lpm1(0); + scorpion_write_lpm2(0); + scorpion_write_l2lpm(0); + raw_spin_lock_irqsave(&pmu_lock, flags); + scorpion_pre_vlpm(); + scorpion_write_vlpm(0); + scorpion_post_vlpm(); + raw_spin_unlock_irqrestore(&pmu_lock, flags); +} + +static void scorpion_clearpmu(u32 grp, u32 val, u32 evt_code) +{ + u32 orig_pmuval, new_pmuval; + + if (scorpion_functions[grp].pre) + scorpion_functions[grp].pre(); + orig_pmuval = scorpion_functions[grp].read(); + val = val & ~scorpion_get_columnmask(evt_code); + new_pmuval = orig_pmuval & ~val; + scorpion_functions[grp].write(new_pmuval); + if (scorpion_functions[grp].post) + scorpion_functions[grp].post(); +} + +static void scorpion_pmu_disable_event(struct hw_perf_event *hwc, int idx) +{ + unsigned long flags; + u32 val = 0; + u32 gr; + unsigned long event; + struct scorpion_evt evtinfo; + + /* Disable counter and interrupt */ + raw_spin_lock_irqsave(&pmu_lock, flags); + + /* Disable counter */ + armv7_pmnc_disable_counter(idx); + + /* + * Clear lpm code (if destined for PMNx counters) + * We don't need to set the event if it's a cycle count + */ + if (idx != ARMV7_CYCLE_COUNTER) { + val = hwc->config_base; + val &= ARMV7_EVTSEL_MASK; + if (val > 0x40) { + event = get_scorpion_evtinfo(val, &evtinfo); + if (event == -EINVAL) + goto scorpion_dis_out; + val = evtinfo.group_setval; + gr = evtinfo.groupcode; + scorpion_clearpmu(gr, val, evtinfo.armv7_evt_type); + } + } + /* Disable interrupt for this counter */ + armv7_pmnc_disable_intens(idx); + +scorpion_dis_out: + raw_spin_unlock_irqrestore(&pmu_lock, flags); +} + +static void scorpion_pmu_enable_event(struct hw_perf_event *hwc, int idx) +{ + unsigned long flags; + u32 val = 0; + u32 gr; + unsigned long event; + struct scorpion_evt evtinfo; + + /* + * Enable counter and interrupt, and set the counter to count + * the event that we're interested in. + */ + raw_spin_lock_irqsave(&pmu_lock, flags); + + /* Disable counter */ + armv7_pmnc_disable_counter(idx); + + /* + * Set event (if destined for PMNx counters) + * We don't need to set the event if it's a cycle count + */ + if (idx != ARMV7_CYCLE_COUNTER) { + val = hwc->config_base; + val &= ARMV7_EVTSEL_MASK; + if (val < 0x40) { + armv7_pmnc_write_evtsel(idx, hwc->config_base); + } else { + event = get_scorpion_evtinfo(val, &evtinfo); + + if (event == -EINVAL) + goto scorpion_out; + /* + * Set event (if destined for PMNx counters) + * We don't need to set the event if it's a cycle count + */ + armv7_pmnc_write_evtsel(idx, event); + val = 0x0; + asm volatile("mcr p15, 0, %0, c9, c15, 0" : : + "r" (val)); + val = evtinfo.group_setval; + gr = evtinfo.groupcode; + scorpion_evt_setup(gr, val, evtinfo.armv7_evt_type); + } + } + + /* Enable interrupt for this counter */ + armv7_pmnc_enable_intens(idx); + + /* Enable counter */ + armv7_pmnc_enable_counter(idx); + +scorpion_out: + raw_spin_unlock_irqrestore(&pmu_lock, flags); +} + +#ifdef CONFIG_SMP +static void scorpion_secondary_enable_callback(void *info) +{ + int irq = *(unsigned int *)info; + + if (irq_get_chip(irq)->irq_unmask) + irq_get_chip(irq)->irq_unmask(irq_get_irq_data(irq)); +} +static void scorpion_secondary_disable_callback(void *info) +{ + int irq = *(unsigned int *)info; + + if (irq_get_chip(irq)->irq_mask) + irq_get_chip(irq)->irq_mask(irq_get_irq_data(irq)); +} + +static void scorpion_secondary_enable(unsigned int irq) +{ + smp_call_function(scorpion_secondary_enable_callback, &irq, 1); +} + +static void scorpion_secondary_disable(unsigned int irq) +{ + smp_call_function(scorpion_secondary_disable_callback, &irq, 1); +} +#endif + +static struct arm_pmu scorpion_pmu = { + .handle_irq = armv7pmu_handle_irq, +#ifdef CONFIG_SMP + .secondary_enable = scorpion_secondary_enable, + .secondary_disable = scorpion_secondary_disable, +#endif + .enable = scorpion_pmu_enable_event, + .disable = scorpion_pmu_disable_event, + .read_counter = armv7pmu_read_counter, + .write_counter = armv7pmu_write_counter, + .raw_event_mask = 0xFF, + .get_event_idx = armv7pmu_get_event_idx, + .start = armv7pmu_start, + .stop = armv7pmu_stop, + .max_period = (1LLU << 32) - 1, +}; + +static const struct arm_pmu *__init armv7_scorpion_pmu_init(void) +{ + scorpion_pmu.id = ARM_PERF_PMU_ID_SCORPION; + scorpion_pmu.name = "ARMv7 Scorpion"; + scorpion_pmu.cache_map = &armv7_scorpion_perf_cache_map; + scorpion_pmu.event_map = &armv7_scorpion_perf_map; + scorpion_pmu.num_events = armv7_read_num_pmnc_events(); + scorpion_clear_pmuregs(); + return &scorpion_pmu; +} + +static const struct arm_pmu *__init armv7_scorpionmp_pmu_init(void) +{ + scorpion_pmu.id = ARM_PERF_PMU_ID_SCORPIONMP; + scorpion_pmu.name = "ARMv7 Scorpion-MP"; + scorpion_pmu.cache_map = &armv7_scorpion_perf_cache_map; + scorpion_pmu.event_map = &armv7_scorpion_perf_map; + scorpion_pmu.num_events = armv7_read_num_pmnc_events(); + scorpion_clear_pmuregs(); + return &scorpion_pmu; +} +#else +static const struct arm_pmu *__init armv7_scorpion_pmu_init(void) +{ + return NULL; +} +static const struct arm_pmu *__init armv7_scorpionmp_pmu_init(void) +{ + return NULL; +} +#endif /* CONFIG_CPU_V7 */ diff --git a/arch/arm/kernel/perf_event_msm_krait.c b/arch/arm/kernel/perf_event_msm_krait.c new file mode 100644 index 00000000000..cb94d646aa2 --- /dev/null +++ b/arch/arm/kernel/perf_event_msm_krait.c @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#ifdef CONFIG_CPU_V7 +#define KRAIT_EVT_PREFIX 1 +#define KRAIT_MAX_L1_REG 2 +/* + event encoding: prccg + p = prefix (1 for Krait L1) + r = register + cc = code + g = group +*/ +#define KRAIT_L1_ICACHE_MISS 0x10010 +#define KRAIT_L1_ICACHE_ACCESS 0x10011 +#define KRAIT_DTLB_ACCESS 0x121B2 +#define KRAIT_ITLB_ACCESS 0x121C0 + +u32 evt_type_base[] = {0x4c, 0x50, 0x54}; + +static const unsigned armv7_krait_perf_map[PERF_COUNT_HW_MAX] = { + [PERF_COUNT_HW_CPU_CYCLES] = ARMV7_PERFCTR_CPU_CYCLES, + [PERF_COUNT_HW_INSTRUCTIONS] = ARMV7_PERFCTR_INSTR_EXECUTED, + [PERF_COUNT_HW_CACHE_REFERENCES] = HW_OP_UNSUPPORTED, + [PERF_COUNT_HW_CACHE_MISSES] = HW_OP_UNSUPPORTED, + [PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = ARMV7_PERFCTR_PC_WRITE, + [PERF_COUNT_HW_BRANCH_MISSES] = ARMV7_PERFCTR_PC_BRANCH_MIS_PRED, + [PERF_COUNT_HW_BUS_CYCLES] = ARMV7_PERFCTR_CLOCK_CYCLES, +}; + +static const unsigned armv7_krait_perf_cache_map[PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + [C(L1D)] = { + /* + * The performance counters don't differentiate between read + * and write accesses/misses so this isn't strictly correct, + * but it's the best we can do. Writes and reads get + * combined. + */ + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = ARMV7_PERFCTR_DCACHE_ACCESS, + [C(RESULT_MISS)] = ARMV7_PERFCTR_DCACHE_REFILL, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = ARMV7_PERFCTR_DCACHE_ACCESS, + [C(RESULT_MISS)] = ARMV7_PERFCTR_DCACHE_REFILL, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(L1I)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = KRAIT_L1_ICACHE_ACCESS, + [C(RESULT_MISS)] = KRAIT_L1_ICACHE_MISS, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = KRAIT_L1_ICACHE_ACCESS, + [C(RESULT_MISS)] = KRAIT_L1_ICACHE_MISS, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(LL)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(DTLB)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = KRAIT_DTLB_ACCESS, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = KRAIT_DTLB_ACCESS, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(ITLB)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = KRAIT_ITLB_ACCESS, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = KRAIT_ITLB_ACCESS, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, + [C(BPU)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] + = ARMV7_PERFCTR_PC_BRANCH_MIS_USED, + [C(RESULT_MISS)] + = ARMV7_PERFCTR_PC_BRANCH_MIS_USED, + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] + = ARMV7_PERFCTR_PC_BRANCH_MIS_USED, + [C(RESULT_MISS)] + = ARMV7_PERFCTR_PC_BRANCH_MIS_USED, + }, + [C(OP_PREFETCH)] = { + [C(RESULT_ACCESS)] = CACHE_OP_UNSUPPORTED, + [C(RESULT_MISS)] = CACHE_OP_UNSUPPORTED, + }, + }, +}; + +struct krait_evt { + /* + * The group_setval field corresponds to the value that the group + * register needs to be set to. This value is calculated from the row + * and column that the event belongs to in the event table + */ + u32 group_setval; + + /* + * The groupcode corresponds to the group that the event belongs to. + * Krait has 3 groups of events PMRESR0, 1, 2 + * going from 0 to 2 in terms of the codes used + */ + u8 groupcode; + + /* + * The armv7_evt_type field corresponds to the armv7 defined event + * code that the Krait events map to + */ + u32 armv7_evt_type; +}; + +static unsigned int get_krait_evtinfo(unsigned int krait_evt_type, + struct krait_evt *evtinfo) +{ + u8 prefix; + u8 reg; + u8 code; + u8 group; + + prefix = (krait_evt_type & 0xF0000) >> 16; + reg = (krait_evt_type & 0x0F000) >> 12; + code = (krait_evt_type & 0x00FF0) >> 4; + group = krait_evt_type & 0x0000F; + + if ((prefix != KRAIT_EVT_PREFIX) || (group > 3) || + (reg > KRAIT_MAX_L1_REG)) + return -EINVAL; + + evtinfo->group_setval = 0x80000000 | (code << (group * 8)); + evtinfo->groupcode = reg; + evtinfo->armv7_evt_type = evt_type_base[reg] | group; + + return evtinfo->armv7_evt_type; +} + +static u32 krait_read_pmresr0(void) +{ + u32 val; + + asm volatile("mrc p15, 1, %0, c9, c15, 0" : "=r" (val)); + return val; +} + +static void krait_write_pmresr0(u32 val) +{ + asm volatile("mcr p15, 1, %0, c9, c15, 0" : : "r" (val)); +} + +static u32 krait_read_pmresr1(void) +{ + u32 val; + + asm volatile("mrc p15, 1, %0, c9, c15, 1" : "=r" (val)); + return val; +} + +static void krait_write_pmresr1(u32 val) +{ + asm volatile("mcr p15, 1, %0, c9, c15, 1" : : "r" (val)); +} + +static u32 krait_read_pmresr2(void) +{ + u32 val; + + asm volatile("mrc p15, 1, %0, c9, c15, 2" : "=r" (val)); + return val; +} + +static void krait_write_pmresr2(u32 val) +{ + asm volatile("mcr p15, 1, %0, c9, c15, 2" : : "r" (val)); +} + +struct krait_access_funcs { + u32 (*read) (void); + void (*write) (u32); +}; + +/* + * The krait_functions array is used to set up the event register codes + * based on the group to which an event belongs. + * Having the following array modularizes the code for doing that. + */ +struct krait_access_funcs krait_functions[] = { + {krait_read_pmresr0, krait_write_pmresr0}, + {krait_read_pmresr1, krait_write_pmresr1}, + {krait_read_pmresr2, krait_write_pmresr2}, +}; + +static inline u32 krait_get_columnmask(u32 evt_code) +{ + const u32 columnmasks[] = {0xffffff00, 0xffff00ff, 0xff00ffff, + 0x80ffffff}; + + return columnmasks[evt_code & 0x3]; +} + +static void krait_evt_setup(u32 gr, u32 setval, u32 evt_code) +{ + u32 val; + + val = krait_get_columnmask(evt_code) & krait_functions[gr].read(); + val = val | setval; + krait_functions[gr].write(val); +} + +static void krait_clear_pmuregs(void) +{ + krait_write_pmresr0(0); + krait_write_pmresr1(0); + krait_write_pmresr2(0); +} + +static void krait_clearpmu(u32 grp, u32 val, u32 evt_code) +{ + u32 new_pmuval; + + new_pmuval = krait_functions[grp].read() & + krait_get_columnmask(evt_code); + krait_functions[grp].write(new_pmuval); +} + +static void krait_pmu_disable_event(struct hw_perf_event *hwc, int idx) +{ + unsigned long flags; + u32 val = 0; + u32 gr; + unsigned long event; + struct krait_evt evtinfo; + + /* Disable counter and interrupt */ + raw_spin_lock_irqsave(&pmu_lock, flags); + + /* Disable counter */ + armv7_pmnc_disable_counter(idx); + + /* + * Clear pmresr code (if destined for PMNx counters) + * We don't need to set the event if it's a cycle count + */ + if (idx != ARMV7_CYCLE_COUNTER) { + val = hwc->config_base; + if (val > 0x40) { + event = get_krait_evtinfo(val, &evtinfo); + if (event == -EINVAL) + goto krait_dis_out; + val = evtinfo.group_setval; + gr = evtinfo.groupcode; + krait_clearpmu(gr, val, evtinfo.armv7_evt_type); + } + } + /* Disable interrupt for this counter */ + armv7_pmnc_disable_intens(idx); + +krait_dis_out: + raw_spin_unlock_irqrestore(&pmu_lock, flags); +} + +static void krait_pmu_enable_event(struct hw_perf_event *hwc, int idx) +{ + unsigned long flags; + u32 val = 0; + u32 gr; + unsigned long event; + struct krait_evt evtinfo; + + /* + * Enable counter and interrupt, and set the counter to count + * the event that we're interested in. + */ + raw_spin_lock_irqsave(&pmu_lock, flags); + + /* Disable counter */ + armv7_pmnc_disable_counter(idx); + + /* + * Set event (if destined for PMNx counters) + * We don't need to set the event if it's a cycle count + */ + if (idx != ARMV7_CYCLE_COUNTER) { + val = hwc->config_base; + if (val < 0x40) { + armv7_pmnc_write_evtsel(idx, hwc->config_base); + } else { + event = get_krait_evtinfo(val, &evtinfo); + + if (event == -EINVAL) + goto krait_out; + /* + * Set event (if destined for PMNx counters) + * We don't need to set the event if it's a cycle count + */ + armv7_pmnc_write_evtsel(idx, event); + val = 0x0; + asm volatile("mcr p15, 0, %0, c9, c15, 0" : : + "r" (val)); + val = evtinfo.group_setval; + gr = evtinfo.groupcode; + krait_evt_setup(gr, val, evtinfo.armv7_evt_type); + } + } + + /* Enable interrupt for this counter */ + armv7_pmnc_enable_intens(idx); + + /* Enable counter */ + armv7_pmnc_enable_counter(idx); + +krait_out: + raw_spin_unlock_irqrestore(&pmu_lock, flags); +} + +static struct arm_pmu krait_pmu = { + .handle_irq = armv7pmu_handle_irq, +#ifdef CONFIG_ARCH_MSM_SMP + .secondary_enable = scorpion_secondary_enable, + .secondary_disable = scorpion_secondary_disable, +#endif + .enable = krait_pmu_enable_event, + .disable = krait_pmu_disable_event, + .read_counter = armv7pmu_read_counter, + .write_counter = armv7pmu_write_counter, + .raw_event_mask = 0xFFFFF, + .get_event_idx = armv7pmu_get_event_idx, + .start = armv7pmu_start, + .stop = armv7pmu_stop, + .max_period = (1LLU << 32) - 1, +}; + +static const struct arm_pmu *__init armv7_krait_pmu_init(void) +{ + krait_pmu.id = ARM_PERF_PMU_ID_KRAIT; + krait_pmu.name = "ARMv7 Krait"; + krait_pmu.cache_map = &armv7_krait_perf_cache_map; + krait_pmu.event_map = &armv7_krait_perf_map; + krait_pmu.num_events = armv7_read_num_pmnc_events(); + krait_clear_pmuregs(); + return &krait_pmu; +} + +#else +static const struct arm_pmu *__init armv7_krait_pmu_init(void) +{ + return NULL; +} +#endif /* CONFIG_CPU_V7 */ diff --git a/arch/arm/kernel/perf_event_msm_krait_l2.c b/arch/arm/kernel/perf_event_msm_krait_l2.c new file mode 100644 index 00000000000..7cb4ee78f4b --- /dev/null +++ b/arch/arm/kernel/perf_event_msm_krait_l2.c @@ -0,0 +1,656 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifdef CONFIG_CPU_HAS_L2_PMU + +#include + +#include + +#define MAX_L2_PERIOD ((1ULL << 32) - 1) +#define MAX_KRAIT_L2_CTRS 5 + +#define L2PMCCNTR 0x409 +#define L2PMCCNTCR 0x408 +#define L2PMCCNTSR 0x40A +#define L2CYCLE_CTR_BIT 31 +#define L2CYCLE_CTR_EVENT_IDX 4 +#define L2CYCLE_CTR_RAW_CODE 0xff + +#define L2PMOVSR 0x406 + +#define L2PMCR 0x400 +#define L2PMCR_RESET_ALL 0x6 +#define L2PMCR_GLOBAL_ENABLE 0x1 +#define L2PMCR_GLOBAL_DISABLE 0x0 + +#define L2PMCNTENSET 0x403 +#define L2PMCNTENCLR 0x402 + +#define L2PMINTENSET 0x405 +#define L2PMINTENCLR 0x404 + +#define IA_L2PMXEVCNTCR_BASE 0x420 +#define IA_L2PMXEVTYPER_BASE 0x424 +#define IA_L2PMRESX_BASE 0x410 +#define IA_L2PMXEVFILTER_BASE 0x423 +#define IA_L2PMXEVCNTR_BASE 0x421 + +/* event format is -e rsRCCG See get_event_desc() */ + +#define EVENT_REG_MASK 0xf000 +#define EVENT_GROUPSEL_MASK 0x000f +#define EVENT_GROUPCODE_MASK 0x0ff0 +#define EVENT_REG_SHIFT 12 +#define EVENT_GROUPCODE_SHIFT 4 + +#define RESRX_VALUE_EN 0x80000000 + +static struct platform_device *l2_pmu_device; + +struct hw_krait_l2_pmu { + struct perf_event *events[MAX_KRAIT_L2_CTRS]; + unsigned long active_mask[BITS_TO_LONGS(MAX_KRAIT_L2_CTRS)]; + raw_spinlock_t lock; +}; + +struct hw_krait_l2_pmu hw_krait_l2_pmu; + +struct event_desc { + int event_groupsel; + int event_reg; + int event_group_code; +}; + +void get_event_desc(u64 config, struct event_desc *evdesc) +{ + /* L2PMEVCNTRX */ + evdesc->event_reg = (config & EVENT_REG_MASK) >> EVENT_REG_SHIFT; + /* Group code (row ) */ + evdesc->event_group_code = + (config & EVENT_GROUPCODE_MASK) >> EVENT_GROUPCODE_SHIFT; + /* Group sel (col) */ + evdesc->event_groupsel = (config & EVENT_GROUPSEL_MASK); + + pr_debug("%s: reg: %x, group_code: %x, groupsel: %x\n", __func__, + evdesc->event_reg, evdesc->event_group_code, + evdesc->event_groupsel); +} + +static void set_evcntcr(int ctr) +{ + u32 evtcr_reg = (ctr * 16) + IA_L2PMXEVCNTCR_BASE; + + set_l2_indirect_reg(evtcr_reg, 0x0); +} + +static void set_evtyper(int event_groupsel, int event_reg, int ctr) +{ + u32 evtype_reg = (ctr * 16) + IA_L2PMXEVTYPER_BASE; + u32 evtype_val = event_groupsel + (4 * event_reg); + + set_l2_indirect_reg(evtype_reg, evtype_val); +} + +static void set_evres(int event_groupsel, int event_reg, int event_group_code) +{ + u32 group_reg = event_reg + IA_L2PMRESX_BASE; + u32 group_val = + RESRX_VALUE_EN | (event_group_code << (8 * event_groupsel)); + u32 resr_val; + u32 group_byte = 0xff; + u32 group_mask = ~(group_byte << (8 * event_groupsel)); + + resr_val = get_l2_indirect_reg(group_reg); + resr_val &= group_mask; + resr_val |= group_val; + + set_l2_indirect_reg(group_reg, resr_val); +} + +static void set_evfilter(int ctr) +{ + u32 filter_reg = (ctr * 16) + IA_L2PMXEVFILTER_BASE; + u32 filter_val = 0x000f0030 | 1 << smp_processor_id(); + + set_l2_indirect_reg(filter_reg, filter_val); +} + +static void enable_intenset(u32 idx) +{ + if (idx == L2CYCLE_CTR_EVENT_IDX) + set_l2_indirect_reg(L2PMINTENSET, 1 << L2CYCLE_CTR_BIT); + else + set_l2_indirect_reg(L2PMINTENSET, 1 << idx); +} + +static void disable_intenclr(u32 idx) +{ + if (idx == L2CYCLE_CTR_EVENT_IDX) + set_l2_indirect_reg(L2PMINTENCLR, 1 << L2CYCLE_CTR_BIT); + else + set_l2_indirect_reg(L2PMINTENCLR, 1 << idx); +} + +static void enable_counter(u32 idx) +{ + if (idx == L2CYCLE_CTR_EVENT_IDX) + set_l2_indirect_reg(L2PMCNTENSET, 1 << L2CYCLE_CTR_BIT); + else + set_l2_indirect_reg(L2PMCNTENSET, 1 << idx); +} + +static void disable_counter(u32 idx) +{ + if (idx == L2CYCLE_CTR_EVENT_IDX) + set_l2_indirect_reg(L2PMCNTENCLR, 1 << L2CYCLE_CTR_BIT); + else + set_l2_indirect_reg(L2PMCNTENCLR, 1 << idx); +} + +static u64 read_counter(u32 idx) +{ + u32 val; + u32 counter_reg = (idx * 16) + IA_L2PMXEVCNTR_BASE; + + if (idx == L2CYCLE_CTR_EVENT_IDX) + val = get_l2_indirect_reg(L2PMCCNTR); + else + val = get_l2_indirect_reg(counter_reg); + + return val; +} + +static void write_counter(u32 idx, u32 val) +{ + u32 counter_reg = (idx * 16) + IA_L2PMXEVCNTR_BASE; + + if (idx == L2CYCLE_CTR_EVENT_IDX) + set_l2_indirect_reg(L2PMCCNTR, val); + else + set_l2_indirect_reg(counter_reg, val); +} + +static int +pmu_event_set_period(struct perf_event *event, + struct hw_perf_event *hwc, int idx) +{ + s64 left = local64_read(&hwc->period_left); + s64 period = hwc->sample_period; + int ret = 0; + + if (unlikely(left <= -period)) { + left = period; + local64_set(&hwc->period_left, left); + hwc->last_period = period; + ret = 1; + } + + if (unlikely(left <= 0)) { + left += period; + local64_set(&hwc->period_left, left); + hwc->last_period = period; + ret = 1; + } + + if (left > (s64) MAX_L2_PERIOD) + left = MAX_L2_PERIOD; + + local64_set(&hwc->prev_count, (u64)-left); + + write_counter(idx, (u64) (-left) & 0xffffffff); + + perf_event_update_userpage(event); + + return ret; +} + +static u64 +pmu_event_update(struct perf_event *event, struct hw_perf_event *hwc, int idx, + int overflow) +{ + u64 prev_raw_count, new_raw_count; + u64 delta; + +again: + prev_raw_count = local64_read(&hwc->prev_count); + new_raw_count = read_counter(idx); + + if (local64_cmpxchg(&hwc->prev_count, prev_raw_count, + new_raw_count) != prev_raw_count) + goto again; + + new_raw_count &= MAX_L2_PERIOD; + prev_raw_count &= MAX_L2_PERIOD; + + if (overflow) + delta = MAX_L2_PERIOD - prev_raw_count + new_raw_count; + else + delta = new_raw_count - prev_raw_count; + + local64_add(delta, &event->count); + local64_sub(delta, &hwc->period_left); + + pr_debug("%s: new: %lld, prev: %lld, event: %ld count: %lld\n", + __func__, new_raw_count, prev_raw_count, + hwc->config_base, local64_read(&event->count)); + + return new_raw_count; +} + +static void krait_l2_read(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + + pmu_event_update(event, hwc, hwc->idx, 0); +} + +static void krait_l2_stop_counter(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + + if (!(hwc->state & PERF_HES_STOPPED)) { + disable_intenclr(idx); + disable_counter(idx); + + pmu_event_update(event, hwc, idx, 0); + hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE; + } + + pr_debug("%s: event: %ld ctr: %d stopped\n", __func__, hwc->config_base, + idx); +} + +static void krait_l2_start_counter(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + struct event_desc evdesc; + + if (flags & PERF_EF_RELOAD) + WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE)); + + hwc->state = 0; + + pmu_event_set_period(event, hwc, idx); + + if (hwc->config_base == L2CYCLE_CTR_RAW_CODE) + goto out; + + set_evcntcr(idx); + + memset(&evdesc, 0, sizeof(evdesc)); + + get_event_desc(hwc->config_base, &evdesc); + + set_evtyper(evdesc.event_groupsel, evdesc.event_reg, idx); + + set_evres(evdesc.event_groupsel, evdesc.event_reg, + evdesc.event_group_code); + + set_evfilter(idx); + +out: + enable_intenset(idx); + enable_counter(idx); + + pr_debug + ("%s: ctr: %d group: %ld group_code: %lld started from cpu:%d\n", + __func__, idx, hwc->config_base, hwc->config, smp_processor_id()); +} + +static void krait_l2_del_event(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + unsigned long iflags; + + raw_spin_lock_irqsave(&hw_krait_l2_pmu.lock, iflags); + + clear_bit(idx, (long unsigned int *)(&hw_krait_l2_pmu.active_mask)); + + krait_l2_stop_counter(event, PERF_EF_UPDATE); + hw_krait_l2_pmu.events[idx] = NULL; + hwc->idx = -1; + + raw_spin_unlock_irqrestore(&hw_krait_l2_pmu.lock, iflags); + + pr_debug("%s: event: %ld deleted\n", __func__, hwc->config_base); + + perf_event_update_userpage(event); +} + +static int krait_l2_add_event(struct perf_event *event, int flags) +{ + int ctr = 0; + struct hw_perf_event *hwc = &event->hw; + unsigned long iflags; + int err = 0; + + perf_pmu_disable(event->pmu); + + raw_spin_lock_irqsave(&hw_krait_l2_pmu.lock, iflags); + + /* Cycle counter has a resrvd index */ + if (hwc->config_base == L2CYCLE_CTR_RAW_CODE) { + if (hw_krait_l2_pmu.events[L2CYCLE_CTR_EVENT_IDX]) { + pr_err("%s: Stale cycle ctr event ptr !\n", __func__); + err = -EINVAL; + goto out; + } + hwc->idx = L2CYCLE_CTR_EVENT_IDX; + hw_krait_l2_pmu.events[L2CYCLE_CTR_EVENT_IDX] = event; + set_bit(L2CYCLE_CTR_EVENT_IDX, + (long unsigned int *)&hw_krait_l2_pmu.active_mask); + goto skip_ctr_loop; + } + + for (ctr = 0; ctr < MAX_KRAIT_L2_CTRS - 1; ctr++) { + if (!hw_krait_l2_pmu.events[ctr]) { + hwc->idx = ctr; + hw_krait_l2_pmu.events[ctr] = event; + set_bit(ctr, + (long unsigned int *) + &hw_krait_l2_pmu.active_mask); + break; + } + } + + if (hwc->idx < 0) { + err = -ENOSPC; + pr_err("%s: No space for event: %llx!!\n", __func__, + event->attr.config); + goto out; + } + +skip_ctr_loop: + + disable_counter(hwc->idx); + + hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE; + + if (flags & PERF_EF_START) + krait_l2_start_counter(event, PERF_EF_RELOAD); + + perf_event_update_userpage(event); + + pr_debug("%s: event: %ld, ctr: %d added from cpu:%d\n", + __func__, hwc->config_base, hwc->idx, smp_processor_id()); +out: + raw_spin_unlock_irqrestore(&hw_krait_l2_pmu.lock, iflags); + + /* Resume the PMU even if this event could not be added */ + perf_pmu_enable(event->pmu); + + return err; +} + +static void krait_l2_pmu_enable(struct pmu *pmu) +{ + isb(); + set_l2_indirect_reg(L2PMCR, L2PMCR_GLOBAL_ENABLE); +} + +static void krait_l2_pmu_disable(struct pmu *pmu) +{ + set_l2_indirect_reg(L2PMCR, L2PMCR_GLOBAL_DISABLE); + isb(); +} + +u32 get_reset_pmovsr(void) +{ + int val; + + val = get_l2_indirect_reg(L2PMOVSR); + /* reset it */ + val &= 0xffffffff; + set_l2_indirect_reg(L2PMOVSR, val); + + return val; +} + +static irqreturn_t krait_l2_handle_irq(int irq_num, void *dev) +{ + unsigned long pmovsr; + struct perf_sample_data data; + struct pt_regs *regs; + struct perf_event *event; + struct hw_perf_event *hwc; + int bitp; + int idx = 0; + + pmovsr = get_reset_pmovsr(); + + if (!(pmovsr & 0xffffffff)) + return IRQ_NONE; + + regs = get_irq_regs(); + + perf_sample_data_init(&data, 0); + + raw_spin_lock(&hw_krait_l2_pmu.lock); + + while (pmovsr) { + bitp = __ffs(pmovsr); + + if (bitp == L2CYCLE_CTR_BIT) + idx = L2CYCLE_CTR_EVENT_IDX; + else + idx = bitp; + + event = hw_krait_l2_pmu.events[idx]; + + if (!event) + goto next; + + if (!test_bit(idx, hw_krait_l2_pmu.active_mask)) + goto next; + + hwc = &event->hw; + pmu_event_update(event, hwc, idx, 1); + data.period = event->hw.last_period; + + if (!pmu_event_set_period(event, hwc, idx)) + goto next; + + if (perf_event_overflow(event, 0, &data, regs)) + disable_counter(hwc->idx); +next: + pmovsr &= (pmovsr - 1); + } + + raw_spin_unlock(&hw_krait_l2_pmu.lock); + + irq_work_run(); + + return IRQ_HANDLED; +} + +static atomic_t active_l2_events = ATOMIC_INIT(0); +static DEFINE_MUTEX(krait_pmu_reserve_mutex); + +static int pmu_reserve_hardware(void) +{ + int i, err = -ENODEV, irq; + + l2_pmu_device = reserve_pmu(ARM_PMU_DEVICE_L2); + + if (IS_ERR(l2_pmu_device)) { + pr_warning("unable to reserve pmu\n"); + return PTR_ERR(l2_pmu_device); + } + + if (l2_pmu_device->num_resources < 1) { + pr_err("no irqs for PMUs defined\n"); + return -ENODEV; + } + + if (strncmp(l2_pmu_device->name, "l2-arm-pmu", 6)) { + pr_err("Incorrect pdev reserved !\n"); + return -EINVAL; + } + + for (i = 0; i < l2_pmu_device->num_resources; ++i) { + irq = platform_get_irq(l2_pmu_device, i); + if (irq < 0) + continue; + + err = request_irq(irq, krait_l2_handle_irq, + IRQF_DISABLED | IRQF_NOBALANCING, + "krait-l2-pmu", NULL); + if (err) { + pr_warning("unable to request IRQ%d for Krait L2 perf " + "counters\n", irq); + break; + } + + irq_get_chip(irq)->irq_unmask(irq_get_irq_data(irq)); + } + + if (err) { + for (i = i - 1; i >= 0; --i) { + irq = platform_get_irq(l2_pmu_device, i); + if (irq >= 0) + free_irq(irq, NULL); + } + release_pmu(l2_pmu_device); + l2_pmu_device = NULL; + } + + return err; +} + +static void pmu_release_hardware(void) +{ + int i, irq; + + for (i = l2_pmu_device->num_resources - 1; i >= 0; --i) { + irq = platform_get_irq(l2_pmu_device, i); + if (irq >= 0) + free_irq(irq, NULL); + } + + krait_l2_pmu_disable(NULL); + + release_pmu(l2_pmu_device); + l2_pmu_device = NULL; +} + +static void pmu_perf_event_destroy(struct perf_event *event) +{ + if (atomic_dec_and_mutex_lock + (&active_l2_events, &krait_pmu_reserve_mutex)) { + pmu_release_hardware(); + mutex_unlock(&krait_pmu_reserve_mutex); + } +} + +static int krait_l2_event_init(struct perf_event *event) +{ + int err = 0; + struct hw_perf_event *hwc = &event->hw; + int status = 0; + + switch (event->attr.type) { + case PERF_TYPE_SHARED: + break; + + default: + return -ENOENT; + } + + hwc->idx = -1; + + event->destroy = pmu_perf_event_destroy; + + if (!atomic_inc_not_zero(&active_l2_events)) { + /* 0 active events */ + mutex_lock(&krait_pmu_reserve_mutex); + err = pmu_reserve_hardware(); + mutex_unlock(&krait_pmu_reserve_mutex); + if (!err) + atomic_inc(&active_l2_events); + else + return err; + } else { + if (atomic_read(&active_l2_events) > (MAX_KRAIT_L2_CTRS - 1)) { + pr_err("%s: No space left on PMU for event: %llx\n", + __func__, event->attr.config); + atomic_dec(&active_l2_events); + return -ENOSPC; + } + } + + hwc->config_base = event->attr.config; + hwc->config = 0; + hwc->event_base = 0; + + /* Only one CPU can control the cycle counter */ + if (hwc->config_base == L2CYCLE_CTR_RAW_CODE) { + /* Check if its already running */ + status = get_l2_indirect_reg(L2PMCCNTSR); + if (status == 0x2) { + err = -ENOSPC; + goto out; + } + } + + if (!hwc->sample_period) { + hwc->sample_period = MAX_L2_PERIOD; + hwc->last_period = hwc->sample_period; + local64_set(&hwc->period_left, hwc->sample_period); + } + + pr_debug("%s: event: %lld init'd\n", __func__, event->attr.config); + +out: + if (err < 0) + pmu_perf_event_destroy(event); + + return err; +} + +static struct pmu krait_l2_pmu = { + .pmu_enable = krait_l2_pmu_enable, + .pmu_disable = krait_l2_pmu_disable, + .event_init = krait_l2_event_init, + .add = krait_l2_add_event, + .del = krait_l2_del_event, + .start = krait_l2_start_counter, + .stop = krait_l2_stop_counter, + .read = krait_l2_read, +}; + +static const struct arm_pmu *__init krait_l2_pmu_init(void) +{ + /* Register our own PMU here */ + perf_pmu_register(&krait_l2_pmu, "Krait L2", PERF_TYPE_SHARED); + + memset(&hw_krait_l2_pmu, 0, sizeof(hw_krait_l2_pmu)); + + /* Reset all ctrs */ + set_l2_indirect_reg(L2PMCR, L2PMCR_RESET_ALL); + + /* Avoid spurious interrupt if any */ + get_reset_pmovsr(); + + /* Don't return an arm_pmu here */ + return NULL; +} +#else + +static const struct arm_pmu *__init krait_l2_pmu_init(void) +{ + return NULL; +} +#endif diff --git a/arch/arm/kernel/perf_event_msm_l2.c b/arch/arm/kernel/perf_event_msm_l2.c new file mode 100644 index 00000000000..8678b0ab073 --- /dev/null +++ b/arch/arm/kernel/perf_event_msm_l2.c @@ -0,0 +1,981 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifdef CONFIG_CPU_HAS_L2_PMU + +#include + +#define MAX_BB_L2_PERIOD ((1ULL << 32) - 1) +#define MAX_BB_L2_CTRS 5 +#define BB_L2CYCLE_CTR_BIT 31 +#define BB_L2CYCLE_CTR_EVENT_IDX 4 +#define BB_L2CYCLE_CTR_RAW_CODE 0xff +#define SCORPIONL2_PMNC_E (1 << 0) /* Enable all counters */ + +/* + * Lock to protect r/m/w sequences to the L2 PMU. + */ +DEFINE_RAW_SPINLOCK(bb_l2_pmu_lock); + +static struct platform_device *bb_l2_pmu_device; + +struct hw_bb_l2_pmu { + struct perf_event *events[MAX_BB_L2_CTRS]; + unsigned long active_mask[BITS_TO_LONGS(MAX_BB_L2_CTRS)]; + raw_spinlock_t lock; +}; + +struct hw_bb_l2_pmu hw_bb_l2_pmu; + +struct bb_l2_scorp_evt { + u32 evt_type; + u32 val; + u8 grp; + u32 evt_type_act; +}; + +enum scorpion_perf_types { + SCORPIONL2_TOTAL_BANK_REQ = 0x90, + SCORPIONL2_DSIDE_READ = 0x91, + SCORPIONL2_DSIDE_WRITE = 0x92, + SCORPIONL2_ISIDE_READ = 0x93, + SCORPIONL2_L2CACHE_ISIDE_READ = 0x94, + SCORPIONL2_L2CACHE_BANK_REQ = 0x95, + SCORPIONL2_L2CACHE_DSIDE_READ = 0x96, + SCORPIONL2_L2CACHE_DSIDE_WRITE = 0x97, + SCORPIONL2_L2NOCACHE_DSIDE_WRITE = 0x98, + SCORPIONL2_L2NOCACHE_ISIDE_READ = 0x99, + SCORPIONL2_L2NOCACHE_TOTAL_REQ = 0x9a, + SCORPIONL2_L2NOCACHE_DSIDE_READ = 0x9b, + SCORPIONL2_DSIDE_READ_NOL1 = 0x9c, + SCORPIONL2_L2CACHE_WRITETHROUGH = 0x9d, + SCORPIONL2_BARRIERS = 0x9e, + SCORPIONL2_HARDWARE_TABLE_WALKS = 0x9f, + SCORPIONL2_MVA_POC = 0xa0, + SCORPIONL2_L2CACHE_HW_TABLE_WALKS = 0xa1, + SCORPIONL2_SETWAY_CACHE_OPS = 0xa2, + SCORPIONL2_DSIDE_WRITE_HITS = 0xa3, + SCORPIONL2_ISIDE_READ_HITS = 0xa4, + SCORPIONL2_CACHE_DSIDE_READ_NOL1 = 0xa5, + SCORPIONL2_TOTAL_CACHE_HITS = 0xa6, + SCORPIONL2_CACHE_MATCH_MISS = 0xa7, + SCORPIONL2_DREAD_HIT_L1_DATA = 0xa8, + SCORPIONL2_L2LINE_LOCKED = 0xa9, + SCORPIONL2_HW_TABLE_WALK_HIT = 0xaa, + SCORPIONL2_CACHE_MVA_POC = 0xab, + SCORPIONL2_L2ALLOC_DWRITE_MISS = 0xac, + SCORPIONL2_CORRECTED_TAG_ARRAY = 0xad, + SCORPIONL2_CORRECTED_DATA_ARRAY = 0xae, + SCORPIONL2_CORRECTED_REPLACEMENT_ARRAY = 0xaf, + SCORPIONL2_PMBUS_MPAAF = 0xb0, + SCORPIONL2_PMBUS_MPWDAF = 0xb1, + SCORPIONL2_PMBUS_MPBRT = 0xb2, + SCORPIONL2_CPU0_GRANT = 0xb3, + SCORPIONL2_CPU1_GRANT = 0xb4, + SCORPIONL2_CPU0_NOGRANT = 0xb5, + SCORPIONL2_CPU1_NOGRANT = 0xb6, + SCORPIONL2_CPU0_LOSING_ARB = 0xb7, + SCORPIONL2_CPU1_LOSING_ARB = 0xb8, + SCORPIONL2_SLAVEPORT_NOGRANT = 0xb9, + SCORPIONL2_SLAVEPORT_BPQ_FULL = 0xba, + SCORPIONL2_SLAVEPORT_LOSING_ARB = 0xbb, + SCORPIONL2_SLAVEPORT_GRANT = 0xbc, + SCORPIONL2_SLAVEPORT_GRANTLOCK = 0xbd, + SCORPIONL2_L2EM_STREX_PASS = 0xbe, + SCORPIONL2_L2EM_STREX_FAIL = 0xbf, + SCORPIONL2_LDREX_RESERVE_L2EM = 0xc0, + SCORPIONL2_SLAVEPORT_LDREX = 0xc1, + SCORPIONL2_CPU0_L2EM_CLEARED = 0xc2, + SCORPIONL2_CPU1_L2EM_CLEARED = 0xc3, + SCORPIONL2_SLAVEPORT_L2EM_CLEARED = 0xc4, + SCORPIONL2_CPU0_CLAMPED = 0xc5, + SCORPIONL2_CPU1_CLAMPED = 0xc6, + SCORPIONL2_CPU0_WAIT = 0xc7, + SCORPIONL2_CPU1_WAIT = 0xc8, + SCORPIONL2_CPU0_NONAMBAS_WAIT = 0xc9, + SCORPIONL2_CPU1_NONAMBAS_WAIT = 0xca, + SCORPIONL2_CPU0_DSB_WAIT = 0xcb, + SCORPIONL2_CPU1_DSB_WAIT = 0xcc, + SCORPIONL2_AXI_READ = 0xcd, + SCORPIONL2_AXI_WRITE = 0xce, + + SCORPIONL2_1BEAT_WRITE = 0xcf, + SCORPIONL2_2BEAT_WRITE = 0xd0, + SCORPIONL2_4BEAT_WRITE = 0xd1, + SCORPIONL2_8BEAT_WRITE = 0xd2, + SCORPIONL2_12BEAT_WRITE = 0xd3, + SCORPIONL2_16BEAT_WRITE = 0xd4, + SCORPIONL2_1BEAT_DSIDE_READ = 0xd5, + SCORPIONL2_2BEAT_DSIDE_READ = 0xd6, + SCORPIONL2_4BEAT_DSIDE_READ = 0xd7, + SCORPIONL2_8BEAT_DSIDE_READ = 0xd8, + SCORPIONL2_CSYS_READ_1BEAT = 0xd9, + SCORPIONL2_CSYS_READ_2BEAT = 0xda, + SCORPIONL2_CSYS_READ_4BEAT = 0xdb, + SCORPIONL2_CSYS_READ_8BEAT = 0xdc, + SCORPIONL2_4BEAT_IFETCH_READ = 0xdd, + SCORPIONL2_8BEAT_IFETCH_READ = 0xde, + SCORPIONL2_CSYS_WRITE_1BEAT = 0xdf, + SCORPIONL2_CSYS_WRITE_2BEAT = 0xe0, + SCORPIONL2_AXI_READ_DATA_BEAT = 0xe1, + SCORPIONL2_AXI_WRITE_EVT1 = 0xe2, + SCORPIONL2_AXI_WRITE_EVT2 = 0xe3, + SCORPIONL2_LDREX_REQ = 0xe4, + SCORPIONL2_STREX_PASS = 0xe5, + SCORPIONL2_STREX_FAIL = 0xe6, + SCORPIONL2_CPREAD = 0xe7, + SCORPIONL2_CPWRITE = 0xe8, + SCORPIONL2_BARRIER_REQ = 0xe9, + SCORPIONL2_AXI_READ_SLVPORT = 0xea, + SCORPIONL2_AXI_WRITE_SLVPORT = 0xeb, + SCORPIONL2_AXI_READ_SLVPORT_DATABEAT = 0xec, + SCORPIONL2_AXI_WRITE_SLVPORT_DATABEAT = 0xed, + SCORPIONL2_SNOOPKILL_PREFILTER = 0xee, + SCORPIONL2_SNOOPKILL_FILTEROUT = 0xef, + SCORPIONL2_SNOOPED_IC = 0xf0, + SCORPIONL2_SNOOPED_BP = 0xf1, + SCORPIONL2_SNOOPED_BARRIERS = 0xf2, + SCORPIONL2_SNOOPED_TLB = 0xf3, + BB_L2_MAX_EVT, +}; + +static const struct bb_l2_scorp_evt sc_evt[] = { + {SCORPIONL2_TOTAL_BANK_REQ, 0x80000001, 0, 0x00}, + {SCORPIONL2_DSIDE_READ, 0x80000100, 0, 0x01}, + {SCORPIONL2_DSIDE_WRITE, 0x80010000, 0, 0x02}, + {SCORPIONL2_ISIDE_READ, 0x81000000, 0, 0x03}, + {SCORPIONL2_L2CACHE_ISIDE_READ, 0x80000002, 0, 0x00}, + {SCORPIONL2_L2CACHE_BANK_REQ, 0x80000200, 0, 0x01}, + {SCORPIONL2_L2CACHE_DSIDE_READ, 0x80020000, 0, 0x02}, + {SCORPIONL2_L2CACHE_DSIDE_WRITE, 0x82000000, 0, 0x03}, + {SCORPIONL2_L2NOCACHE_DSIDE_WRITE, 0x80000003, 0, 0x00}, + {SCORPIONL2_L2NOCACHE_ISIDE_READ, 0x80000300, 0, 0x01}, + {SCORPIONL2_L2NOCACHE_TOTAL_REQ, 0x80030000, 0, 0x02}, + {SCORPIONL2_L2NOCACHE_DSIDE_READ, 0x83000000, 0, 0x03}, + {SCORPIONL2_DSIDE_READ_NOL1, 0x80000004, 0, 0x00}, + {SCORPIONL2_L2CACHE_WRITETHROUGH, 0x80000400, 0, 0x01}, + {SCORPIONL2_BARRIERS, 0x84000000, 0, 0x03}, + {SCORPIONL2_HARDWARE_TABLE_WALKS, 0x80000005, 0, 0x00}, + {SCORPIONL2_MVA_POC, 0x80000500, 0, 0x01}, + {SCORPIONL2_L2CACHE_HW_TABLE_WALKS, 0x80050000, 0, 0x02}, + {SCORPIONL2_SETWAY_CACHE_OPS, 0x85000000, 0, 0x03}, + {SCORPIONL2_DSIDE_WRITE_HITS, 0x80000006, 0, 0x00}, + {SCORPIONL2_ISIDE_READ_HITS, 0x80000600, 0, 0x01}, + {SCORPIONL2_CACHE_DSIDE_READ_NOL1, 0x80060000, 0, 0x02}, + {SCORPIONL2_TOTAL_CACHE_HITS, 0x86000000, 0, 0x03}, + {SCORPIONL2_CACHE_MATCH_MISS, 0x80000007, 0, 0x00}, + {SCORPIONL2_DREAD_HIT_L1_DATA, 0x87000000, 0, 0x03}, + {SCORPIONL2_L2LINE_LOCKED, 0x80000008, 0, 0x00}, + {SCORPIONL2_HW_TABLE_WALK_HIT, 0x80000800, 0, 0x01}, + {SCORPIONL2_CACHE_MVA_POC, 0x80080000, 0, 0x02}, + {SCORPIONL2_L2ALLOC_DWRITE_MISS, 0x88000000, 0, 0x03}, + {SCORPIONL2_CORRECTED_TAG_ARRAY, 0x80001A00, 0, 0x01}, + {SCORPIONL2_CORRECTED_DATA_ARRAY, 0x801A0000, 0, 0x02}, + {SCORPIONL2_CORRECTED_REPLACEMENT_ARRAY, 0x9A000000, 0, 0x03}, + {SCORPIONL2_PMBUS_MPAAF, 0x80001C00, 0, 0x01}, + {SCORPIONL2_PMBUS_MPWDAF, 0x801C0000, 0, 0x02}, + {SCORPIONL2_PMBUS_MPBRT, 0x9C000000, 0, 0x03}, + + {SCORPIONL2_CPU0_GRANT, 0x80000001, 1, 0x04}, + {SCORPIONL2_CPU1_GRANT, 0x80000100, 1, 0x05}, + {SCORPIONL2_CPU0_NOGRANT, 0x80020000, 1, 0x06}, + {SCORPIONL2_CPU1_NOGRANT, 0x82000000, 1, 0x07}, + {SCORPIONL2_CPU0_LOSING_ARB, 0x80040000, 1, 0x06}, + {SCORPIONL2_CPU1_LOSING_ARB, 0x84000000, 1, 0x07}, + {SCORPIONL2_SLAVEPORT_NOGRANT, 0x80000007, 1, 0x04}, + {SCORPIONL2_SLAVEPORT_BPQ_FULL, 0x80000700, 1, 0x05}, + {SCORPIONL2_SLAVEPORT_LOSING_ARB, 0x80070000, 1, 0x06}, + {SCORPIONL2_SLAVEPORT_GRANT, 0x87000000, 1, 0x07}, + {SCORPIONL2_SLAVEPORT_GRANTLOCK, 0x80000008, 1, 0x04}, + {SCORPIONL2_L2EM_STREX_PASS, 0x80000009, 1, 0x04}, + {SCORPIONL2_L2EM_STREX_FAIL, 0x80000900, 1, 0x05}, + {SCORPIONL2_LDREX_RESERVE_L2EM, 0x80090000, 1, 0x06}, + {SCORPIONL2_SLAVEPORT_LDREX, 0x89000000, 1, 0x07}, + {SCORPIONL2_CPU0_L2EM_CLEARED, 0x800A0000, 1, 0x06}, + {SCORPIONL2_CPU1_L2EM_CLEARED, 0x8A000000, 1, 0x07}, + {SCORPIONL2_SLAVEPORT_L2EM_CLEARED, 0x80000B00, 1, 0x05}, + {SCORPIONL2_CPU0_CLAMPED, 0x8000000E, 1, 0x04}, + {SCORPIONL2_CPU1_CLAMPED, 0x80000E00, 1, 0x05}, + {SCORPIONL2_CPU0_WAIT, 0x800F0000, 1, 0x06}, + {SCORPIONL2_CPU1_WAIT, 0x8F000000, 1, 0x07}, + {SCORPIONL2_CPU0_NONAMBAS_WAIT, 0x80000010, 1, 0x04}, + {SCORPIONL2_CPU1_NONAMBAS_WAIT, 0x80001000, 1, 0x05}, + {SCORPIONL2_CPU0_DSB_WAIT, 0x80000014, 1, 0x04}, + {SCORPIONL2_CPU1_DSB_WAIT, 0x80001400, 1, 0x05}, + + {SCORPIONL2_AXI_READ, 0x80000001, 2, 0x08}, + {SCORPIONL2_AXI_WRITE, 0x80000100, 2, 0x09}, + {SCORPIONL2_1BEAT_WRITE, 0x80010000, 2, 0x0a}, + {SCORPIONL2_2BEAT_WRITE, 0x80010000, 2, 0x0b}, + {SCORPIONL2_4BEAT_WRITE, 0x80000002, 2, 0x08}, + {SCORPIONL2_8BEAT_WRITE, 0x80000200, 2, 0x09}, + {SCORPIONL2_12BEAT_WRITE, 0x80020000, 2, 0x0a}, + {SCORPIONL2_16BEAT_WRITE, 0x82000000, 2, 0x0b}, + {SCORPIONL2_1BEAT_DSIDE_READ, 0x80000003, 2, 0x08}, + {SCORPIONL2_2BEAT_DSIDE_READ, 0x80000300, 2, 0x09}, + {SCORPIONL2_4BEAT_DSIDE_READ, 0x80030000, 2, 0x0a}, + {SCORPIONL2_8BEAT_DSIDE_READ, 0x83000000, 2, 0x0b}, + {SCORPIONL2_CSYS_READ_1BEAT, 0x80000004, 2, 0x08}, + {SCORPIONL2_CSYS_READ_2BEAT, 0x80000400, 2, 0x09}, + {SCORPIONL2_CSYS_READ_4BEAT, 0x80040000, 2, 0x0a}, + {SCORPIONL2_CSYS_READ_8BEAT, 0x84000000, 2, 0x0b}, + {SCORPIONL2_4BEAT_IFETCH_READ, 0x80000005, 2, 0x08}, + {SCORPIONL2_8BEAT_IFETCH_READ, 0x80000500, 2, 0x09}, + {SCORPIONL2_CSYS_WRITE_1BEAT, 0x80050000, 2, 0x0a}, + {SCORPIONL2_CSYS_WRITE_2BEAT, 0x85000000, 2, 0x0b}, + {SCORPIONL2_AXI_READ_DATA_BEAT, 0x80000600, 2, 0x09}, + {SCORPIONL2_AXI_WRITE_EVT1, 0x80060000, 2, 0x0a}, + {SCORPIONL2_AXI_WRITE_EVT2, 0x86000000, 2, 0x0b}, + {SCORPIONL2_LDREX_REQ, 0x80000007, 2, 0x08}, + {SCORPIONL2_STREX_PASS, 0x80000700, 2, 0x09}, + {SCORPIONL2_STREX_FAIL, 0x80070000, 2, 0x0a}, + {SCORPIONL2_CPREAD, 0x80000008, 2, 0x08}, + {SCORPIONL2_CPWRITE, 0x80000800, 2, 0x09}, + {SCORPIONL2_BARRIER_REQ, 0x88000000, 2, 0x0b}, + + {SCORPIONL2_AXI_READ_SLVPORT, 0x80000001, 3, 0x0c}, + {SCORPIONL2_AXI_WRITE_SLVPORT, 0x80000100, 3, 0x0d}, + {SCORPIONL2_AXI_READ_SLVPORT_DATABEAT, 0x80010000, 3, 0x0e}, + {SCORPIONL2_AXI_WRITE_SLVPORT_DATABEAT, 0x81000000, 3, 0x0f}, + + {SCORPIONL2_SNOOPKILL_PREFILTER, 0x80000001, 4, 0x10}, + {SCORPIONL2_SNOOPKILL_FILTEROUT, 0x80000100, 4, 0x11}, + {SCORPIONL2_SNOOPED_IC, 0x80000002, 4, 0x10}, + {SCORPIONL2_SNOOPED_BP, 0x80000200, 4, 0x11}, + {SCORPIONL2_SNOOPED_BARRIERS, 0x80020000, 4, 0x12}, + {SCORPIONL2_SNOOPED_TLB, 0x82000000, 4, 0x13}, +}; + +static u32 bb_l2_read_l2pm0(void) +{ + u32 val; + asm volatile ("mrc p15, 3, %0, c15, c7, 0" : "=r" (val)); + return val; +} + +static void bb_l2_write_l2pm0(u32 val) +{ + asm volatile ("mcr p15, 3, %0, c15, c7, 0" : : "r" (val)); +} + +static u32 bb_l2_read_l2pm1(void) +{ + u32 val; + asm volatile ("mrc p15, 3, %0, c15, c7, 1" : "=r" (val)); + return val; +} + +static void bb_l2_write_l2pm1(u32 val) +{ + asm volatile ("mcr p15, 3, %0, c15, c7, 1" : : "r" (val)); +} + +static u32 bb_l2_read_l2pm2(void) +{ + u32 val; + asm volatile ("mrc p15, 3, %0, c15, c7, 2" : "=r" (val)); + return val; +} + +static void bb_l2_write_l2pm2(u32 val) +{ + asm volatile ("mcr p15, 3, %0, c15, c7, 2" : : "r" (val)); +} + +static u32 bb_l2_read_l2pm3(void) +{ + u32 val; + asm volatile ("mrc p15, 3, %0, c15, c7, 3" : "=r" (val)); + return val; +} + +static void bb_l2_write_l2pm3(u32 val) +{ + asm volatile ("mcr p15, 3, %0, c15, c7, 3" : : "r" (val)); +} + +static u32 bb_l2_read_l2pm4(void) +{ + u32 val; + asm volatile ("mrc p15, 3, %0, c15, c7, 4" : "=r" (val)); + return val; +} + +static void bb_l2_write_l2pm4(u32 val) +{ + asm volatile ("mcr p15, 3, %0, c15, c7, 4" : : "r" (val)); +} + +struct bb_scorpion_access_funcs { + u32(*read) (void); + void (*write) (u32); + void (*pre) (void); + void (*post) (void); +}; + +struct bb_scorpion_access_funcs bb_l2_func[] = { + {bb_l2_read_l2pm0, bb_l2_write_l2pm0, NULL, NULL}, + {bb_l2_read_l2pm1, bb_l2_write_l2pm1, NULL, NULL}, + {bb_l2_read_l2pm2, bb_l2_write_l2pm2, NULL, NULL}, + {bb_l2_read_l2pm3, bb_l2_write_l2pm3, NULL, NULL}, + {bb_l2_read_l2pm4, bb_l2_write_l2pm4, NULL, NULL}, +}; + +#define COLMN0MASK 0x000000ff +#define COLMN1MASK 0x0000ff00 +#define COLMN2MASK 0x00ff0000 + +static u32 bb_l2_get_columnmask(u32 setval) +{ + if (setval & COLMN0MASK) + return 0xffffff00; + else if (setval & COLMN1MASK) + return 0xffff00ff; + else if (setval & COLMN2MASK) + return 0xff00ffff; + else + return 0x80ffffff; +} + +static void bb_l2_evt_setup(u32 gr, u32 setval) +{ + u32 val; + if (bb_l2_func[gr].pre) + bb_l2_func[gr].pre(); + val = bb_l2_get_columnmask(setval) & bb_l2_func[gr].read(); + val = val | setval; + bb_l2_func[gr].write(val); + if (bb_l2_func[gr].post) + bb_l2_func[gr].post(); +} + +#define BB_L2_EVT_START_IDX 0x90 +#define BB_L2_INV_EVTYPE 0 + +static unsigned int get_bb_l2_evtinfo(unsigned int evt_type, + struct bb_l2_scorp_evt *evtinfo) +{ + u32 idx; + if (evt_type < BB_L2_EVT_START_IDX || evt_type >= BB_L2_MAX_EVT) + return BB_L2_INV_EVTYPE; + idx = evt_type - BB_L2_EVT_START_IDX; + if (sc_evt[idx].evt_type == evt_type) { + evtinfo->val = sc_evt[idx].val; + evtinfo->grp = sc_evt[idx].grp; + evtinfo->evt_type_act = sc_evt[idx].evt_type_act; + return sc_evt[idx].evt_type_act; + } + return BB_L2_INV_EVTYPE; +} + +static inline void bb_l2_pmnc_write(unsigned long val) +{ + val &= 0xff; + asm volatile ("mcr p15, 3, %0, c15, c4, 0" : : "r" (val)); +} + +static inline unsigned long bb_l2_pmnc_read(void) +{ + u32 val; + asm volatile ("mrc p15, 3, %0, c15, c4, 0" : "=r" (val)); + return val; +} + +static void bb_l2_set_evcntcr(void) +{ + u32 val = 0x0; + asm volatile ("mcr p15, 3, %0, c15, c6, 4" : : "r" (val)); +} + +static inline void bb_l2_set_evtyper(int ctr, int val) +{ + /* select ctr */ + asm volatile ("mcr p15, 3, %0, c15, c6, 0" : : "r" (ctr)); + + /* write into EVTYPER */ + asm volatile ("mcr p15, 3, %0, c15, c6, 7" : : "r" (val)); +} + +static void bb_l2_set_evfilter(void) +{ + u32 filter_val = 0x000f0030 | 1 << smp_processor_id(); + + asm volatile ("mcr p15, 3, %0, c15, c6, 3" : : "r" (filter_val)); +} + +static void bb_l2_enable_intenset(u32 idx) +{ + if (idx == BB_L2CYCLE_CTR_EVENT_IDX) { + asm volatile ("mcr p15, 3, %0, c15, c5, 1" : : "r" + (1 << BB_L2CYCLE_CTR_BIT)); + } else { + asm volatile ("mcr p15, 3, %0, c15, c5, 1" : : "r" (1 << idx)); + } +} + +static void bb_l2_disable_intenclr(u32 idx) +{ + if (idx == BB_L2CYCLE_CTR_EVENT_IDX) { + asm volatile ("mcr p15, 3, %0, c15, c5, 0" : : "r" + (1 << BB_L2CYCLE_CTR_BIT)); + } else { + asm volatile ("mcr p15, 3, %0, c15, c5, 0" : : "r" (1 << idx)); + } +} + +static void bb_l2_enable_counter(u32 idx) +{ + if (idx == BB_L2CYCLE_CTR_EVENT_IDX) { + asm volatile ("mcr p15, 3, %0, c15, c4, 3" : : "r" + (1 << BB_L2CYCLE_CTR_BIT)); + } else { + asm volatile ("mcr p15, 3, %0, c15, c4, 3" : : "r" (1 << idx)); + } +} + +static void bb_l2_disable_counter(u32 idx) +{ + if (idx == BB_L2CYCLE_CTR_EVENT_IDX) { + asm volatile ("mcr p15, 3, %0, c15, c4, 2" : : "r" + (1 << BB_L2CYCLE_CTR_BIT)); + + } else { + asm volatile ("mcr p15, 3, %0, c15, c4, 2" : : "r" (1 << idx)); + } +} + +static u64 bb_l2_read_counter(u32 idx) +{ + u32 val; + unsigned long flags; + + if (idx == BB_L2CYCLE_CTR_EVENT_IDX) { + asm volatile ("mrc p15, 3, %0, c15, c4, 5" : "=r" (val)); + } else { + raw_spin_lock_irqsave(&bb_l2_pmu_lock, flags); + asm volatile ("mcr p15, 3, %0, c15, c6, 0" : : "r" (idx)); + + /* read val from counter */ + asm volatile ("mrc p15, 3, %0, c15, c6, 5" : "=r" (val)); + raw_spin_unlock_irqrestore(&bb_l2_pmu_lock, flags); + } + + return val; +} + +static void bb_l2_write_counter(u32 idx, u32 val) +{ + unsigned long flags; + + if (idx == BB_L2CYCLE_CTR_EVENT_IDX) { + asm volatile ("mcr p15, 3, %0, c15, c4, 5" : : "r" (val)); + } else { + raw_spin_lock_irqsave(&bb_l2_pmu_lock, flags); + /* select counter */ + asm volatile ("mcr p15, 3, %0, c15, c6, 0" : : "r" (idx)); + + /* write val into counter */ + asm volatile ("mcr p15, 3, %0, c15, c6, 5" : : "r" (val)); + raw_spin_unlock_irqrestore(&bb_l2_pmu_lock, flags); + } +} + +static int +bb_pmu_event_set_period(struct perf_event *event, + struct hw_perf_event *hwc, int idx) +{ + s64 left = local64_read(&hwc->period_left); + s64 period = hwc->sample_period; + int ret = 0; + + if (unlikely(left <= -period)) { + left = period; + local64_set(&hwc->period_left, left); + hwc->last_period = period; + ret = 1; + } + + if (unlikely(left <= 0)) { + left += period; + local64_set(&hwc->period_left, left); + hwc->last_period = period; + ret = 1; + } + + if (left > (s64) MAX_BB_L2_PERIOD) + left = MAX_BB_L2_PERIOD; + + local64_set(&hwc->prev_count, (u64)-left); + + bb_l2_write_counter(idx, (u64) (-left) & 0xffffffff); + + perf_event_update_userpage(event); + + return ret; +} + +static u64 +bb_pmu_event_update(struct perf_event *event, struct hw_perf_event *hwc, + int idx, int overflow) +{ + u64 prev_raw_count, new_raw_count; + u64 delta; + +again: + prev_raw_count = local64_read(&hwc->prev_count); + new_raw_count = bb_l2_read_counter(idx); + + if (local64_cmpxchg(&hwc->prev_count, prev_raw_count, + new_raw_count) != prev_raw_count) + goto again; + + new_raw_count &= MAX_BB_L2_PERIOD; + prev_raw_count &= MAX_BB_L2_PERIOD; + + if (overflow) { + delta = MAX_BB_L2_PERIOD - prev_raw_count + new_raw_count; + pr_err("%s: delta: %lld\n", __func__, delta); + } else + delta = new_raw_count - prev_raw_count; + + local64_add(delta, &event->count); + local64_sub(delta, &hwc->period_left); + + pr_debug("%s: new: %lld, prev: %lld, event: %ld count: %lld\n", + __func__, new_raw_count, prev_raw_count, + hwc->config_base, local64_read(&event->count)); + + return new_raw_count; +} + +static void bb_l2_read(struct perf_event *event) +{ + struct hw_perf_event *hwc = &event->hw; + + bb_pmu_event_update(event, hwc, hwc->idx, 0); +} + +static void bb_l2_stop_counter(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + + if (!(hwc->state & PERF_HES_STOPPED)) { + bb_l2_disable_intenclr(idx); + bb_l2_disable_counter(idx); + + bb_pmu_event_update(event, hwc, idx, 0); + hwc->state |= PERF_HES_STOPPED | PERF_HES_UPTODATE; + } + + pr_debug("%s: event: %ld ctr: %d stopped\n", __func__, hwc->config_base, + idx); +} + +static void bb_l2_start_counter(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + struct bb_l2_scorp_evt evtinfo; + int evtype = hwc->config_base; + int ev_typer; + unsigned long iflags; + int cpu_id = smp_processor_id(); + + if (flags & PERF_EF_RELOAD) + WARN_ON_ONCE(!(hwc->state & PERF_HES_UPTODATE)); + + hwc->state = 0; + + bb_pmu_event_set_period(event, hwc, idx); + + if (hwc->config_base == BB_L2CYCLE_CTR_RAW_CODE) + goto out; + + memset(&evtinfo, 0, sizeof(evtinfo)); + + ev_typer = get_bb_l2_evtinfo(evtype, &evtinfo); + + raw_spin_lock_irqsave(&bb_l2_pmu_lock, iflags); + + bb_l2_set_evtyper(idx, ev_typer); + + bb_l2_set_evcntcr(); + + bb_l2_set_evfilter(); + + bb_l2_evt_setup(evtinfo.grp, evtinfo.val); + + raw_spin_unlock_irqrestore(&bb_l2_pmu_lock, iflags); + +out: + + bb_l2_enable_intenset(idx); + + bb_l2_enable_counter(idx); + + pr_debug("%s: idx: %d, event: %d, val: %x, cpu: %d\n", + __func__, idx, evtype, evtinfo.val, cpu_id); +} + +static void bb_l2_del_event(struct perf_event *event, int flags) +{ + struct hw_perf_event *hwc = &event->hw; + int idx = hwc->idx; + unsigned long iflags; + + raw_spin_lock_irqsave(&hw_bb_l2_pmu.lock, iflags); + + clear_bit(idx, (long unsigned int *)(&hw_bb_l2_pmu.active_mask)); + + bb_l2_stop_counter(event, PERF_EF_UPDATE); + hw_bb_l2_pmu.events[idx] = NULL; + hwc->idx = -1; + + raw_spin_unlock_irqrestore(&hw_bb_l2_pmu.lock, iflags); + + pr_debug("%s: event: %ld deleted\n", __func__, hwc->config_base); + + perf_event_update_userpage(event); +} + +static int bb_l2_add_event(struct perf_event *event, int flags) +{ + int ctr = 0; + struct hw_perf_event *hwc = &event->hw; + unsigned long iflags; + int err = 0; + + perf_pmu_disable(event->pmu); + + raw_spin_lock_irqsave(&hw_bb_l2_pmu.lock, iflags); + + /* Cycle counter has a resrvd index */ + if (hwc->config_base == BB_L2CYCLE_CTR_RAW_CODE) { + if (hw_bb_l2_pmu.events[BB_L2CYCLE_CTR_EVENT_IDX]) { + pr_err("%s: Stale cycle ctr event ptr !\n", __func__); + err = -EINVAL; + goto out; + } + hwc->idx = BB_L2CYCLE_CTR_EVENT_IDX; + hw_bb_l2_pmu.events[BB_L2CYCLE_CTR_EVENT_IDX] = event; + set_bit(BB_L2CYCLE_CTR_EVENT_IDX, + (long unsigned int *)&hw_bb_l2_pmu.active_mask); + goto skip_ctr_loop; + } + + for (ctr = 0; ctr < MAX_BB_L2_CTRS - 1; ctr++) { + if (!hw_bb_l2_pmu.events[ctr]) { + hwc->idx = ctr; + hw_bb_l2_pmu.events[ctr] = event; + set_bit(ctr, (long unsigned int *) + &hw_bb_l2_pmu.active_mask); + break; + } + } + + if (hwc->idx < 0) { + err = -ENOSPC; + pr_err("%s: No space for event: %llx!!\n", __func__, + event->attr.config); + goto out; + } + +skip_ctr_loop: + + bb_l2_disable_counter(hwc->idx); + + hwc->state = PERF_HES_STOPPED | PERF_HES_UPTODATE; + + if (flags & PERF_EF_START) + bb_l2_start_counter(event, PERF_EF_RELOAD); + + perf_event_update_userpage(event); + + pr_debug("%s: event: %ld, ctr: %d added from cpu:%d\n", + __func__, hwc->config_base, hwc->idx, smp_processor_id()); +out: + raw_spin_unlock_irqrestore(&hw_bb_l2_pmu.lock, iflags); + + /* Resume the PMU even if this event could not be added */ + perf_pmu_enable(event->pmu); + + return err; +} + +static void bb_l2_pmu_enable(struct pmu *pmu) +{ + unsigned long flags; + isb(); + raw_spin_lock_irqsave(&bb_l2_pmu_lock, flags); + /* Enable all counters */ + bb_l2_pmnc_write(bb_l2_pmnc_read() | SCORPIONL2_PMNC_E); + raw_spin_unlock_irqrestore(&bb_l2_pmu_lock, flags); +} + +static void bb_l2_pmu_disable(struct pmu *pmu) +{ + unsigned long flags; + raw_spin_lock_irqsave(&bb_l2_pmu_lock, flags); + /* Disable all counters */ + bb_l2_pmnc_write(bb_l2_pmnc_read() & ~SCORPIONL2_PMNC_E); + raw_spin_unlock_irqrestore(&bb_l2_pmu_lock, flags); + isb(); +} + +static inline u32 bb_l2_get_reset_pmovsr(void) +{ + u32 val; + + /* Read */ + asm volatile ("mrc p15, 3, %0, c15, c4, 1" : "=r" (val)); + + /* Write to clear flags */ + val &= 0xffffffff; + asm volatile ("mcr p15, 3, %0, c15, c4, 1" : : "r" (val)); + + return val; +} + +static irqreturn_t bb_l2_handle_irq(int irq_num, void *dev) +{ + unsigned long pmovsr; + struct perf_sample_data data; + struct pt_regs *regs; + struct perf_event *event; + struct hw_perf_event *hwc; + int bitp; + int idx = 0; + + pmovsr = bb_l2_get_reset_pmovsr(); + + if (!(pmovsr & 0xffffffff)) + return IRQ_NONE; + + regs = get_irq_regs(); + + perf_sample_data_init(&data, 0); + + raw_spin_lock(&hw_bb_l2_pmu.lock); + + while (pmovsr) { + bitp = __ffs(pmovsr); + + if (bitp == BB_L2CYCLE_CTR_BIT) + idx = BB_L2CYCLE_CTR_EVENT_IDX; + else + idx = bitp; + + event = hw_bb_l2_pmu.events[idx]; + + if (!event) + goto next; + + if (!test_bit(idx, hw_bb_l2_pmu.active_mask)) + goto next; + + hwc = &event->hw; + bb_pmu_event_update(event, hwc, idx, 1); + data.period = event->hw.last_period; + + if (!bb_pmu_event_set_period(event, hwc, idx)) + goto next; + + if (perf_event_overflow(event, 0, &data, regs)) + bb_l2_disable_counter(hwc->idx); +next: + pmovsr &= (pmovsr - 1); + } + + raw_spin_unlock(&hw_bb_l2_pmu.lock); + + irq_work_run(); + + return IRQ_HANDLED; +} + +static atomic_t active_bb_l2_events = ATOMIC_INIT(0); +static DEFINE_MUTEX(bb_pmu_reserve_mutex); + +static int bb_pmu_reserve_hardware(void) +{ + int i, err = -ENODEV, irq; + + bb_l2_pmu_device = reserve_pmu(ARM_PMU_DEVICE_L2); + + if (IS_ERR(bb_l2_pmu_device)) { + pr_warning("unable to reserve pmu\n"); + return PTR_ERR(bb_l2_pmu_device); + } + + if (bb_l2_pmu_device->num_resources < 1) { + pr_err("no irqs for PMUs defined\n"); + return -ENODEV; + } + + if (strncmp(bb_l2_pmu_device->name, "l2-arm-pmu", 6)) { + pr_err("Incorrect pdev reserved !\n"); + return -EINVAL; + } + + for (i = 0; i < bb_l2_pmu_device->num_resources; ++i) { + irq = platform_get_irq(bb_l2_pmu_device, i); + if (irq < 0) + continue; + + err = request_irq(irq, bb_l2_handle_irq, + IRQF_DISABLED | IRQF_NOBALANCING, + "bb-l2-pmu", NULL); + if (err) { + pr_warning("unable to request IRQ%d for Krait L2 perf " + "counters\n", irq); + break; + } + + irq_get_chip(irq)->irq_unmask(irq_get_irq_data(irq)); + } + + if (err) { + for (i = i - 1; i >= 0; --i) { + irq = platform_get_irq(bb_l2_pmu_device, i); + if (irq >= 0) + free_irq(irq, NULL); + } + release_pmu(bb_l2_pmu_device); + bb_l2_pmu_device = NULL; + } + + return err; +} + +static void bb_pmu_release_hardware(void) +{ + int i, irq; + + for (i = bb_l2_pmu_device->num_resources - 1; i >= 0; --i) { + irq = platform_get_irq(bb_l2_pmu_device, i); + if (irq >= 0) + free_irq(irq, NULL); + } + + bb_l2_pmu_disable(NULL); + + release_pmu(bb_l2_pmu_device); + bb_l2_pmu_device = NULL; +} + +static void bb_pmu_perf_event_destroy(struct perf_event *event) +{ + if (atomic_dec_and_mutex_lock + (&active_bb_l2_events, &bb_pmu_reserve_mutex)) { + bb_pmu_release_hardware(); + mutex_unlock(&bb_pmu_reserve_mutex); + } +} + +static int bb_l2_event_init(struct perf_event *event) +{ + int err = 0; + struct hw_perf_event *hwc = &event->hw; + int status = 0; + + switch (event->attr.type) { + case PERF_TYPE_SHARED: + break; + + default: + return -ENOENT; + } + + hwc->idx = -1; + + event->destroy = bb_pmu_perf_event_destroy; + + if (!atomic_inc_not_zero(&active_bb_l2_events)) { + /* 0 active events */ + mutex_lock(&bb_pmu_reserve_mutex); + err = bb_pmu_reserve_hardware(); + mutex_unlock(&bb_pmu_reserve_mutex); + if (!err) + atomic_inc(&active_bb_l2_events); + else + return err; + } else { + if (atomic_read(&active_bb_l2_events) > (MAX_BB_L2_CTRS - 1)) { + pr_err("%s: No space left on PMU for event: %llx\n", + __func__, event->attr.config); + atomic_dec(&active_bb_l2_events); + return -ENOSPC; + } + } + + hwc->config_base = event->attr.config & 0xff; + hwc->config = 0; + hwc->event_base = 0; + + /* Only one CPU can control the cycle counter */ + if (hwc->config_base == BB_L2CYCLE_CTR_RAW_CODE) { + /* Check if its already running */ + asm volatile ("mrc p15, 3, %0, c15, c4, 6" : "=r" (status)); + if (status == 0x2) { + err = -ENOSPC; + goto out; + } + } + + if (!hwc->sample_period) { + hwc->sample_period = MAX_BB_L2_PERIOD; + hwc->last_period = hwc->sample_period; + local64_set(&hwc->period_left, hwc->sample_period); + } + + pr_debug("%s: event: %lld init'd\n", __func__, event->attr.config); + +out: + if (err < 0) + bb_pmu_perf_event_destroy(event); + + return err; +} + +static struct pmu bb_l2_pmu = { + .pmu_enable = bb_l2_pmu_enable, + .pmu_disable = bb_l2_pmu_disable, + .event_init = bb_l2_event_init, + .add = bb_l2_add_event, + .del = bb_l2_del_event, + .start = bb_l2_start_counter, + .stop = bb_l2_stop_counter, + .read = bb_l2_read, +}; + +static const struct arm_pmu *__init scorpionmp_l2_pmu_init(void) +{ + /* Register our own PMU here */ + perf_pmu_register(&bb_l2_pmu, "BB L2", PERF_TYPE_SHARED); + + memset(&hw_bb_l2_pmu, 0, sizeof(hw_bb_l2_pmu)); + + /* Avoid spurious interrupts at startup */ + bb_l2_get_reset_pmovsr(); + + /* Don't return an arm_pmu here */ + return NULL; +} +#else + +static const struct arm_pmu *__init scorpionmp_l2_pmu_init(void) +{ + return NULL; +} + +#endif diff --git a/arch/arm/kernel/pmu.c b/arch/arm/kernel/pmu.c index 2c79eec1926..a4688e8e8f6 100644 --- a/arch/arm/kernel/pmu.c +++ b/arch/arm/kernel/pmu.c @@ -45,16 +45,41 @@ static int __devinit pmu_device_probe(struct platform_device *pdev) return 0; } -static struct platform_driver pmu_driver = { +static struct platform_driver cpu_pmu_driver = { .driver = { - .name = "arm-pmu", + .name = "cpu-arm-pmu", }, .probe = pmu_device_probe, }; +static struct platform_driver l2_pmu_driver = { + .driver = { + .name = "l2-arm-pmu", + }, + .probe = pmu_device_probe, +}; + +static struct platform_driver *pmu_drivers[] __initdata = { + &cpu_pmu_driver, + &l2_pmu_driver, +}; + static int __init register_pmu_driver(void) { - return platform_driver_register(&pmu_driver); + int i; + int err; + + for (i = 0; i < ARM_NUM_PMU_DEVICES; i++) { + err = platform_driver_register(pmu_drivers[i]); + if (err) { + pr_err("%s: failed to register id:%d\n", __func__, i); + while (--i >= 0) + platform_driver_unregister(pmu_drivers[i]); + break; + } + } + + return err; } device_initcall(register_pmu_driver); diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index acbb447ac6b..6b1ee832309 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -29,6 +29,9 @@ #include #include #include +#ifdef CONFIG_MEMORY_HOTPLUG +#include +#endif #include #include @@ -97,6 +100,8 @@ EXPORT_SYMBOL(system_serial_high); unsigned int elf_hwcap __read_mostly; EXPORT_SYMBOL(elf_hwcap); +unsigned int boot_reason; +EXPORT_SYMBOL(boot_reason); #ifdef MULTI_CPU struct processor processor __read_mostly; @@ -517,6 +522,62 @@ static int __init early_mem(char *p) } early_param("mem", early_mem); +#ifdef CONFIG_MEMORY_HOTPLUG +static void __init early_mem_reserved(char **p) +{ + unsigned int start; + unsigned int size; + unsigned int end; + unsigned int h_end; + + start = PHYS_OFFSET; + size = memparse(*p, p); + if (**p == '@') + start = memparse(*p + 1, p); + + if (movable_reserved_start) { + end = start + size; + h_end = movable_reserved_start + movable_reserved_size; + end = max(end, h_end); + movable_reserved_start = min(movable_reserved_start, + (unsigned long)start); + movable_reserved_size = end - movable_reserved_start; + } else { + movable_reserved_start = start; + movable_reserved_size = size; + } +} +__early_param("mem_reserved=", early_mem_reserved); + +static void __init early_mem_low_power(char **p) +{ + unsigned int start; + unsigned int size; + unsigned int end; + unsigned int h_end; + + start = PHYS_OFFSET; + size = memparse(*p, p); + if (**p == '@') + start = memparse(*p + 1, p); + + if (low_power_memory_start) { + end = start + size; + h_end = low_power_memory_start + low_power_memory_size; + end = max(end, h_end); + low_power_memory_start = min(low_power_memory_start, + (unsigned long)start); + low_power_memory_size = end - low_power_memory_start; + } else { + low_power_memory_start = start; + low_power_memory_size = size; + } + + arm_add_memory(start, size); +} +__early_param("mem_low_power=", early_mem_low_power); +#endif + static void __init setup_ramdisk(int doload, int prompt, int image_start, unsigned int rd_sz) { @@ -606,6 +667,66 @@ static int __init parse_tag_mem32(const struct tag *tag) __tagtable(ATAG_MEM, parse_tag_mem32); +#ifdef CONFIG_MEMORY_HOTPLUG +static int __init parse_tag_mem32_reserved(const struct tag *tag) +{ + unsigned int start; + unsigned int size; + unsigned int end; + unsigned int h_end; + + start = tag->u.mem.start; + size = tag->u.mem.size; + + if (movable_reserved_start) { + end = start + size; + h_end = movable_reserved_start + movable_reserved_size; + end = max(end, h_end); + movable_reserved_start = min(movable_reserved_start, + (unsigned long)start); + movable_reserved_size = end - movable_reserved_start; + } else { + movable_reserved_start = tag->u.mem.start; + movable_reserved_size = tag->u.mem.size; + } + printk(KERN_ALERT "reserved %lx at %lx for hotplug\n", + movable_reserved_size, movable_reserved_start); + + return 0; +} + +__tagtable(ATAG_MEM_RESERVED, parse_tag_mem32_reserved); + +static int __init parse_tag_mem32_low_power(const struct tag *tag) +{ + unsigned int start; + unsigned int size; + unsigned int end; + unsigned int h_end; + + start = tag->u.mem.start; + size = tag->u.mem.size; + + if (low_power_memory_start) { + end = start + size; + h_end = low_power_memory_start + low_power_memory_size; + end = max(end, h_end); + low_power_memory_start = min(low_power_memory_start, + (unsigned long)start); + low_power_memory_size = end - low_power_memory_start; + } else { + low_power_memory_start = tag->u.mem.start; + low_power_memory_size = tag->u.mem.size; + } + printk(KERN_ALERT "low power memory %lx at %lx\n", + low_power_memory_size, low_power_memory_start); + + return arm_add_memory(tag->u.mem.start, tag->u.mem.size); +} + +__tagtable(ATAG_MEM_LOW_POWER, parse_tag_mem32_low_power); +#endif + #if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE) struct screen_info screen_info = { .orig_video_lines = 30, diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index e7f92a4321f..1b2887b3bb8 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -597,6 +597,11 @@ asmlinkage void __exception_irq_entry do_IPI(int ipinr, struct pt_regs *regs) void smp_send_reschedule(int cpu) { + if (unlikely(cpu_is_offline(cpu))) { + pr_warn("%s: attempt to send resched-IPI to an offline cpu.\n", + __func__); + return; + } smp_cross_call(cpumask_of(cpu), IPI_RESCHEDULE); } diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 2c277d40cee..f62743c6088 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -127,8 +127,7 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) twd_calibrate_rate(); clk->name = "local_timer"; - clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | - CLOCK_EVT_FEAT_C3STOP; + clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; clk->rating = 350; clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index 56b2715355b..aaca029f295 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -453,6 +453,10 @@ do_cache_op(unsigned long start, unsigned long end, int flags) up_read(&mm->mmap_sem); flush_cache_user_range(start, end); + +#ifdef CONFIG_ARCH_MSM7X27 + mb(); +#endif return; } up_read(&mm->mmap_sem); diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c new file mode 100644 index 00000000000..59fdf64b122 --- /dev/null +++ b/arch/arm/lib/delay.c @@ -0,0 +1,90 @@ +/* + * Originally from linux/arch/arm/lib/delay.S + * + * Copyright (C) 1995, 1996 Russell King + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * Copyright (C) 1993 Linus Torvalds + * Copyright (C) 1997 Martin Mares + * Copyright (C) 2005-2006 Atmel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include + +/* + * Oh, if only we had a cycle counter... + */ +void delay_loop(unsigned long loops) +{ + asm volatile( + "1: subs %0, %0, #1 \n" + " bhi 1b \n" + : /* No output */ + : "r" (loops) + ); +} + +#ifdef ARCH_HAS_READ_CURRENT_TIMER +/* + * Assuming read_current_timer() is monotonically increasing + * across calls. + */ +void read_current_timer_delay_loop(unsigned long loops) +{ + unsigned long bclock, now; + + read_current_timer(&bclock); + do { + read_current_timer(&now); + } while ((now - bclock) < loops); +} +#endif + +static void (*delay_fn)(unsigned long) = delay_loop; + +void set_delay_fn(void (*fn)(unsigned long)) +{ + delay_fn = fn; +} + +/* + * loops = usecs * HZ * loops_per_jiffy / 1000000 + */ +void __delay(unsigned long loops) +{ + delay_fn(loops); +} +EXPORT_SYMBOL(__delay); + +/* + * 0 <= xloops <= 0x7fffff06 + * loops_per_jiffy <= 0x01ffffff (max. 3355 bogomips) + */ +void __const_udelay(unsigned long xloops) +{ + unsigned long lpj; + unsigned long loops; + + xloops >>= 14; /* max = 0x01ffffff */ + lpj = loops_per_jiffy >> 10; /* max = 0x0001ffff */ + loops = lpj * xloops; /* max = 0x00007fff */ + loops >>= 6; /* max = 2^32-1 */ + + if (loops) + __delay(loops); +} +EXPORT_SYMBOL(__const_udelay); + +/* + * usecs <= 2000 + * HZ <= 1000 + */ +void __udelay(unsigned long usecs) +{ + __const_udelay(usecs * ((2199023*HZ)>>11)); +} +EXPORT_SYMBOL(__udelay); diff --git a/arch/arm/lib/lib1funcs.S b/arch/arm/lib/lib1funcs.S index c562f649734..63b75df56d2 100644 --- a/arch/arm/lib/lib1funcs.S +++ b/arch/arm/lib/lib1funcs.S @@ -351,7 +351,7 @@ ENDPROC(__aeabi_idivmod) #endif -Ldiv0: +ENTRY(Ldiv0) UNWIND(.fnstart) UNWIND(.pad #4) UNWIND(.save {lr}) diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig index 1516896e8d1..0685c96923d 100644 --- a/arch/arm/mach-msm/Kconfig +++ b/arch/arm/mach-msm/Kconfig @@ -1,139 +1,440 @@ if ARCH_MSM choice - prompt "Qualcomm MSM SoC Type" + prompt "MSM SoC Type" default ARCH_MSM7X00A -config ARCH_MSM7X00A +config ARCH_MSM7X01A bool "MSM7x00A / MSM7x01A" - select MACH_TROUT if !MACH_HALIBUT select ARCH_MSM_ARM11 - select MSM_SMD - select MSM_SMD_PKG3 + select MSM_VIC + select CPU_V6 + select MSM_REMOTE_SPINLOCK_SWP + +config ARCH_MSM7X25 + bool "MSM7x25" + select ARCH_MSM_ARM11 + select MSM_VIC select CPU_V6 - select MSM_PROC_COMM - select HAS_MSM_DEBUG_UART_PHYS + select MSM_REMOTE_SPINLOCK_SWP + +config ARCH_MSM7X27 + bool "MSM7x27" + select ARCH_MSM_ARM11 if MSM_SOC_REV_NONE + select ARCH_HAS_BARRIERS if MSM_SOC_REV_NONE + select ARCH_MSM_CORTEX_A5 if MSM_SOC_REV_A + select MSM_VIC + select CPU_V6 if MSM_SOC_REV_NONE + select CPU_V7 if MSM_SOC_REV_A + select MSM_REMOTE_SPINLOCK_SWP if MSM_SOC_REV_NONE + select MSM_GPIOMUX + select REGULATOR config ARCH_MSM7X30 bool "MSM7x30" - select MACH_MSM7X30_SURF # if ! select ARCH_MSM_SCORPION - select MSM_SMD select MSM_VIC select CPU_V7 + select MSM_REMOTE_SPINLOCK_DEKKERS + select ARCH_SPARSEMEM_ENABLE + select MEMORY_HOTPLUG + select MEMORY_HOTREMOVE + select MIGRATION + select ARCH_MEMORY_PROBE + select ARCH_MEMORY_REMOVE + select DONT_RESERVE_FROM_MOVABLE_ZONE select MSM_GPIOMUX - select MSM_PROC_COMM - select HAS_MSM_DEBUG_UART_PHYS + select RESERVE_FIRST_PAGE + select MSM_DALRPC + select MSM_SPM_V1 + select REGULATOR config ARCH_QSD8X50 bool "QSD8X50" - select MACH_QSD8X50_SURF if !MACH_QSD8X50A_ST1_5 select ARCH_MSM_SCORPION - select MSM_SMD select MSM_VIC select CPU_V7 + select MSM_REMOTE_SPINLOCK_LDREX + select CPU_USE_DOMAINS + select EMULATE_DOMAIN_MANAGER_V7 select MSM_GPIOMUX - select MSM_PROC_COMM - select HAS_MSM_DEBUG_UART_PHYS + select MSM_DALRPC config ARCH_MSM8X60 bool "MSM8X60" - select MACH_MSM8X60_SURF if (!MACH_MSM8X60_RUMI3 && !MACH_MSM8X60_SIM \ - && !MACH_MSM8X60_FFA) select ARCH_MSM_SCORPIONMP + select SMP_PARALLEL_START if SMP select ARM_GIC select CPU_V7 + select MSM_REMOTE_SPINLOCK_LDREX + select ARCH_REQUIRE_GPIOLIB + select MSM_ADM3 + select REGULATOR select MSM_V2_TLMM + select MSM_PIL + select MSM_SCM + select ARCH_HAS_CPU_IDLE_WAIT + select MSM_DIRECT_SCLK_ACCESS + select MSM_RPM + select MSM_XO select MSM_GPIOMUX - select MSM_SCM if SMP + select MSM_BUS_SCALING + select MSM_SECURE_IO + select MSM_DALRPC + select MSM_QDSP6_APR + select MSM_NATIVE_RESTART + select ARCH_INLINE_SPIN_TRYLOCK + select ARCH_INLINE_SPIN_TRYLOCK_BH + select ARCH_INLINE_SPIN_LOCK + select ARCH_INLINE_SPIN_LOCK_BH + select ARCH_INLINE_SPIN_LOCK_IRQ + select ARCH_INLINE_SPIN_LOCK_IRQSAVE + select ARCH_INLINE_SPIN_UNLOCK + select ARCH_INLINE_SPIN_UNLOCK_BH + select ARCH_INLINE_SPIN_UNLOCK_IRQ + select ARCH_INLINE_SPIN_UNLOCK_IRQRESTORE + select ARCH_INLINE_READ_TRYLOCK + select ARCH_INLINE_READ_LOCK + select ARCH_INLINE_READ_LOCK_BH + select ARCH_INLINE_READ_LOCK_IRQ + select ARCH_INLINE_READ_LOCK_IRQSAVE + select ARCH_INLINE_READ_UNLOCK + select ARCH_INLINE_READ_UNLOCK_BH + select ARCH_INLINE_READ_UNLOCK_IRQ + select ARCH_INLINE_READ_UNLOCK_IRQRESTORE + select ARCH_INLINE_WRITE_TRYLOCK + select ARCH_INLINE_WRITE_LOCK + select ARCH_INLINE_WRITE_LOCK_BH + select ARCH_INLINE_WRITE_LOCK_IRQ + select ARCH_INLINE_WRITE_LOCK_IRQSAVE + select ARCH_INLINE_WRITE_UNLOCK + select ARCH_INLINE_WRITE_UNLOCK_BH + select ARCH_INLINE_WRITE_UNLOCK_IRQRESTORE + select CPU_HAS_L2_PMU + select MSM_SPM_V1 config ARCH_MSM8960 bool "MSM8960" - select ARCH_MSM_SCORPIONMP - select MACH_MSM8960_SIM if (!MACH_MSM8960_RUMI3) + select ARCH_MSM_KRAITMP select ARM_GIC select CPU_V7 select MSM_V2_TLMM select MSM_GPIOMUX select MSM_SCM if SMP + select MSM_DIRECT_SCLK_ACCESS + select REGULATOR + select MSM_RPM + select MSM_XO + select MSM_QDSP6_APR + select MSM_PIL + select MSM_AUDIO_QDSP6 if SND_SOC + select CPU_HAS_L2_PMU + select MSM_SPM_V2 + select MSM_L2_SPM + select MSM_NATIVE_RESTART + +config ARCH_APQ8064 + bool "APQ8064" + select ARCH_MSM_KRAITMP + select MACH_APQ8064_SIM + select MSM_V2_TLMM + select ARM_GIC + select CPU_V7 + select MSM_SCM if SMP + +config ARCH_FSM9XXX + bool "FSM9XXX" + select ARCH_MSM_SCORPION + select MSM_VIC + select CPU_V7 + select MSM_REMOTE_SPINLOCK_LDREX + select FSM9XXX_TLMM endchoice +choice + prompt "MSM SoC Revision" + default MSM_SOC_REV_NONE +config MSM_SOC_REV_NONE + bool "N/A" + select EMULATE_DOMAIN_MANAGER_V7 if ARCH_QSD8X50 + select VERIFY_PERMISSION_FAULT if ARCH_QSD8X50 config MSM_SOC_REV_A + bool "Rev. A" + select ARCH_MSM7X27A if ARCH_MSM7X27 +endchoice + +config ARCH_MSM_ARM11 + bool + +config ARCH_MSM_SCORPION + bool + +config ARCH_MSM_KRAIT + bool + +config MSM_SMP bool + config ARCH_MSM_SCORPIONMP + select ARCH_MSM_SCORPION + select MSM_SMP bool -config ARCH_MSM_ARM11 +config ARCH_MSM_KRAITMP + select ARCH_MSM_KRAIT + select MSM_SMP bool -config ARCH_MSM_SCORPION + +config ARCH_MSM_CORTEX_A5 bool + select HAVE_HW_BRKPT_RESERVED_RW_ACCESS -config HAS_MSM_DEBUG_UART_PHYS +config ARCH_MSM7X27A bool + select MSM_DALRPC config MSM_VIC bool -menu "Qualcomm MSM Board Type" +config MSM_RPM + bool "Resource Power Manager" + select MSM_MPM + +config MSM_MPM + bool "Modem Power Manager" + +config MSM_XO + bool + +config MSM_REMOTE_SPINLOCK_DEKKERS + bool +config MSM_REMOTE_SPINLOCK_SWP + bool +config MSM_REMOTE_SPINLOCK_LDREX + bool +config MSM_ADM3 + bool + +menu "MSM Board Selection" config MACH_HALIBUT - depends on ARCH_MSM - depends on ARCH_MSM7X00A + depends on ARCH_MSM7X01A + depends on MSM_STACKED_MEMORY + default y bool "Halibut Board (QCT SURF7201A)" help Support for the Qualcomm SURF7201A eval board. +config MACH_MSM7201A_SURF + depends on ARCH_MSM7X01A + depends on MSM_STACKED_MEMORY + default y + bool "MSM7201A SURF" + help + Support for the Qualcomm MSM7201A SURF eval board. + +config MACH_MSM7201A_FFA + depends on ARCH_MSM7X01A + depends on MSM_STACKED_MEMORY + default y + bool "MSM7201A FFA" + help + Support for the Qualcomm MSM7201A FFA eval board. + config MACH_TROUT - depends on ARCH_MSM - depends on ARCH_MSM7X00A - bool "HTC Dream (aka trout)" + depends on ARCH_MSM7X01A + depends on MSM_STACKED_MEMORY + default y + bool "Trout" + +config MACH_MSM7X27_SURF + depends on ARCH_MSM7X27 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x27 SURF" help - Support for the HTC Dream, T-Mobile G1, Android ADP1 devices. + Support for the Qualcomm MSM7x27 SURF eval board. -config MACH_MSM7X30_SURF - depends on ARCH_MSM7X30 - bool "MSM7x30 SURF" +config MACH_MSM7X27_FFA + depends on ARCH_MSM7X27 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x27 FFA" help - Support for the Qualcomm MSM7x30 SURF eval board. + Support for the Qualcomm MSM7x27 FFA eval board. + +config MACH_MSM7X27A_RUMI3 + depends on ARCH_MSM7X27A + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x27A RUMI3" + help + Support for the Qualcomm MSM7x27A RUMI3 Emulation Platform. + +config MACH_MSM7X27A_SURF + depends on ARCH_MSM7X27A + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x27A SURF" + help + Support for the Qualcomm MSM7x27A SURF. + +config MACH_MSM7X27A_FFA + depends on ARCH_MSM7X27A + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x27A FFA" + help + Support for the Qualcomm MSM7x27A FFA. + +config MACH_MSM7X30_SURF + depends on ARCH_MSM7X30 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x30 SURF" + help + Support for the Qualcomm MSM7x30 SURF eval board. + +config MACH_MSM7X30_FFA + depends on ARCH_MSM7X30 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x30 FFA" + help + Support for the Qualcomm MSM7x30 FFA eval board. + +config MACH_MSM7X30_FLUID + depends on ARCH_MSM7X30 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x30 FLUID" + help + Support for the Qualcomm MSM7x30 FLUID eval board. + +config MACH_SAPPHIRE + depends on ARCH_MSM7X01A + default n + bool "Sapphire" config MACH_QSD8X50_SURF depends on ARCH_QSD8X50 + depends on MSM_SOC_REV_NONE + depends on MSM_STACKED_MEMORY + default y bool "QSD8x50 SURF" help Support for the Qualcomm QSD8x50 SURF eval board. -config MACH_QSD8X50A_ST1_5 +config MACH_QSD8X50_FFA depends on ARCH_QSD8X50 - select MSM_SOC_REV_A - bool "QSD8x50A ST1.5" + depends on MSM_SOC_REV_NONE + depends on MSM_STACKED_MEMORY + default y + bool "QSD8x50 FFA" help - Support for the Qualcomm ST1.5. + Support for the Qualcomm QSD8x50 FFA eval board. + +config MACH_MSM7X25_SURF + depends on ARCH_MSM7X25 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x25 SURF" + help + Support for the Qualcomm MSM7x25 SURF eval board. + +config MACH_MSM7X25_FFA + depends on ARCH_MSM7X25 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM7x25 FFA" + help + Support for the Qualcomm MSM7x25 FFA eval board. + +config MACH_MSM8X55_SURF + depends on ARCH_MSM7X30 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8X55 SURF" + help + Support for the Qualcomm MSM8x55 SURF eval board. + +config MACH_MSM8X55_FFA + depends on ARCH_MSM7X30 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8X55 FFA" + help + Support for the Qualcomm MSM8x55 FFA eval board. + +config MACH_MSM8X55_SVLTE_FFA + depends on ARCH_MSM7X30 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8X55 SVLTE FFA" + help + Support for the Qualcomm MSM8x55 SVLTE FFA eval board. + +config MACH_MSM8X55_SVLTE_SURF + depends on ARCH_MSM7X30 + depends on !MSM_STACKED_MEMORY + default y + bool "MSM8X55 SVLTE SURF" + help + Support for the Qualcomm MSM8x55 SVLTE SURF eval board. config MACH_MSM8X60_RUMI3 depends on ARCH_MSM8X60 + default n bool "MSM8x60 RUMI3" help Support for the Qualcomm MSM8x60 RUMI3 emulator. -config MACH_MSM8X60_SURF - depends on ARCH_MSM8X60 - bool "MSM8x60 SURF" - help - Support for the Qualcomm MSM8x60 SURF eval board. - config MACH_MSM8X60_SIM depends on ARCH_MSM8X60 + default n bool "MSM8x60 Simulator" help Support for the Qualcomm MSM8x60 simulator. +config MACH_MSM8X60_SURF + depends on ARCH_MSM8X60 + default n + bool "MSM8x60 SURF" + help + Support for the Qualcomm MSM8x60 SURF eval board. + config MACH_MSM8X60_FFA depends on ARCH_MSM8X60 + default n bool "MSM8x60 FFA" help Support for the Qualcomm MSM8x60 FFA eval board. +config MACH_MSM8X60_FLUID + depends on ARCH_MSM8X60 + default n + bool "MSM8x60 FLUID" + help + Support for the Qualcomm MSM8x60 FLUID platform. The FLUID is an + 8x60 target which has a form factor that is much closer to that + of a phone than other targets. It also has a new display and + touchscreen controller. + +config MACH_MSM8X60_FUSION + depends on ARCH_MSM8X60 + default n + bool "MSM8x60 FUSION" + help + Support for the Qualcomm MSM8x60 Fusion SURF device. + +config MACH_MSM8X60_FUSN_FFA + depends on ARCH_MSM8X60 + default n + bool "MSM8x60 FUSN FFA" + help + Support for the Qualcomm MSM8x60 Fusion FFA device. + config MACH_MSM8960_SIM depends on ARCH_MSM8960 bool "MSM8960 Simulator" @@ -146,23 +447,110 @@ config MACH_MSM8960_RUMI3 help Support for the Qualcomm MSM8960 RUMI3 emulator. +config MACH_MSM8960_CDP + depends on ARCH_MSM8960 + bool "MSM8960 CDP" + help + Support for the Qualcomm MSM8960 CDP device. + +config MACH_MSM8960_MTP + depends on ARCH_MSM8960 + bool "MSM8960 MTP" + help + Support for the Qualcomm MSM8960 MTP device. + +config MACH_MSM8960_FLUID + depends on ARCH_MSM8960 + bool "MSM8960 FLUID" + help + Support for the Qualcomm MSM8960 FLUID device. + +config MSM_USE_TSIF1 + depends on ARCH_MSM8X60 + bool "MSM8x60 use TSIF1" + help + Selects TSIF1 core to be used rather than TSIF0. + The two TSIF cores share the same DM configuration + so they cannot be used simultaneously. + +config MACH_APQ8064_SIM + depends on ARCH_APQ8064 + bool "APQ8064 Simulator" + help + Support for the Qualcomm APQ8064 simulator. + +config MACH_FSM9XXX_SURF + depends on ARCH_FSM9XXX + depends on !MSM_STACKED_MEMORY + default y + bool "FSM9XXX SURF" + help + Support for the Qualcomm FSM9xxx femtocell + chipset based SURF evaluation board and + FFA board. + endmenu -config MSM_IOMMU - bool "MSM IOMMU Support" - depends on ARCH_MSM8X60 || ARCH_MSM8960 - select IOMMU_API - default n +config MSM_STACKED_MEMORY + bool "Stacked Memory" + default y help - Support for the IOMMUs found on certain Qualcomm SOCs. - These IOMMUs allow virtualization of the address space used by most - cores within the multimedia subsystem. + This option is used to indicate the presence of on-die stacked + memory. When present this memory bank is used for a high speed + shared memory interface. When not present regular RAM is used. - If unsure, say N here. +config PHYS_OFFSET + hex + default "0x80200000" if ARCH_APQ8064 + default "0x80200000" if ARCH_MSM8960 + default "0x10000000" if ARCH_FSM9XXX + default "0x00200000" if !MSM_STACKED_MEMORY + default "0x00000000" if ARCH_QSD8X50 && MSM_SOC_REV_A + default "0x20000000" if ARCH_QSD8X50 + default "0x40200000" if ARCH_MSM8X60 + default "0x10000000" -config IOMMU_PGTABLES_L2 - def_bool y - depends on MSM_IOMMU && MMU && SMP && CPU_DCACHE_DISABLE=n +config KERNEL_PMEM_EBI_REGION + bool "Enable in-kernel PMEM region for EBI" + default y if ARCH_MSM8X60 + depends on ANDROID_PMEM && (ARCH_MSM8X60 || ARCH_MSM8960) + help + Enable the in-kernel PMEM allocator to use EBI memory. + +config KERNEL_PMEM_SMI_REGION + bool "Enable in-kernel PMEM region for SMI" + default y if ARCH_MSM8X60 + depends on ANDROID_PMEM && ((ARCH_QSD8X50 && !PMEM_GPU0) || (ARCH_MSM8X60 && !VCM)) + help + Enable the in-kernel PMEM allocator to use SMI memory. + +config PMEM_GPU0 + bool "Enable PMEM GPU0 region" + default y + depends on ARCH_QSD8X50 && ANDROID_PMEM + help + Enable the PMEM GPU0 device on SMI Memory. + +config MSM_AMSS_VERSION + int + default 6210 if MSM_AMSS_VERSION_6210 + default 6220 if MSM_AMSS_VERSION_6220 + default 6225 if MSM_AMSS_VERSION_6225 + +choice + prompt "AMSS modem firmware version" + + default MSM_AMSS_VERSION_6225 + + config MSM_AMSS_VERSION_6210 + bool "6.2.10" + + config MSM_AMSS_VERSION_6220 + bool "6.2.20" + + config MSM_AMSS_VERSION_6225 + bool "6.2.20 + New ADSP" +endchoice config MSM_DEBUG_UART int @@ -170,7 +558,6 @@ config MSM_DEBUG_UART default 2 if MSM_DEBUG_UART2 default 3 if MSM_DEBUG_UART3 -if HAS_MSM_DEBUG_UART_PHYS choice prompt "Debug UART" @@ -188,26 +575,1024 @@ choice config MSM_DEBUG_UART3 bool "UART3" endchoice -endif -config MSM_SMD_PKG3 - bool +choice + prompt "Default Timer" + default MSM7X00A_USE_GP_TIMER + + config MSM7X00A_USE_GP_TIMER + bool "GP Timer" + help + Low resolution timer that allows power collapse from idle. + + config MSM7X00A_USE_DG_TIMER + bool "DG Timer" + help + High resolution timer. +endchoice + +choice + prompt "Suspend sleep mode" + default MSM7X00A_SLEEP_MODE_POWER_COLLAPSE_SUSPEND + help + Allows overriding the sleep mode used. Leave at power + collapse suspend unless the arm9 image has problems. + + config MSM7X00A_SLEEP_MODE_POWER_COLLAPSE_SUSPEND + bool "Power collapse suspend" + help + Lowest sleep state. Returns through reset vector. + + config MSM7X00A_SLEEP_MODE_POWER_COLLAPSE + bool "Power collapse" + help + Sleep state that returns through reset vector. + + config MSM7X00A_SLEEP_MODE_APPS_SLEEP + bool "Apps Sleep" + + config MSM7X00A_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT + bool "Ramp down cpu clock and wait for interrupt" + + config MSM7X00A_SLEEP_WAIT_FOR_INTERRUPT + bool "Wait for interrupt" +endchoice + +config MSM7X00A_SLEEP_MODE + int + default 0 if MSM7X00A_SLEEP_MODE_POWER_COLLAPSE_SUSPEND + default 1 if MSM7X00A_SLEEP_MODE_POWER_COLLAPSE + default 2 if MSM7X00A_SLEEP_MODE_APPS_SLEEP + default 3 if MSM7X00A_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT + default 4 if MSM7X00A_SLEEP_WAIT_FOR_INTERRUPT + +choice + prompt "Idle sleep mode" + default MSM7X00A_IDLE_SLEEP_MODE_POWER_COLLAPSE + help + Allows overriding the sleep mode used from idle. Leave at power + collapse suspend unless the arm9 image has problems. + + config MSM7X00A_IDLE_SLEEP_MODE_POWER_COLLAPSE_SUSPEND + bool "Power collapse suspend" + help + Lowest sleep state. Returns through reset vector. + + config MSM7X00A_IDLE_SLEEP_MODE_POWER_COLLAPSE + bool "Power collapse" + help + Sleep state that returns through reset vector. + + config MSM7X00A_IDLE_SLEEP_MODE_APPS_SLEEP + bool "Apps Sleep" + + config MSM7X00A_IDLE_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT + bool "Ramp down cpu clock and wait for interrupt" + + config MSM7X00A_IDLE_SLEEP_WAIT_FOR_INTERRUPT + bool "Wait for interrupt" +endchoice + +config MSM7X00A_IDLE_SLEEP_MODE + int + default 0 if MSM7X00A_IDLE_SLEEP_MODE_POWER_COLLAPSE_SUSPEND + default 1 if MSM7X00A_IDLE_SLEEP_MODE_POWER_COLLAPSE + default 2 if MSM7X00A_IDLE_SLEEP_MODE_APPS_SLEEP + default 3 if MSM7X00A_IDLE_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT + default 4 if MSM7X00A_IDLE_SLEEP_WAIT_FOR_INTERRUPT + +config MSM7X00A_IDLE_SLEEP_MIN_TIME + int "Minimum idle time before sleep" + default 20000000 + help + Minimum idle time in nanoseconds before entering low power mode. + +config MSM7X00A_IDLE_SPIN_TIME + int "Idle spin time before cpu ramp down" + default 80000 + help + Spin time in nanoseconds before ramping down cpu clock and entering + any low power state. + +menuconfig MSM_IDLE_STATS + bool "Collect idle statistics" + default y + help + Collect idle statistics and export them in proc/msm_pm_stats. + +if MSM_IDLE_STATS + +config MSM_IDLE_STATS_FIRST_BUCKET + int "First bucket time" + default 62500 + help + Upper time limit in nanoseconds of first bucket. + +config MSM_IDLE_STATS_BUCKET_SHIFT + int "Bucket shift" + default 2 + +config MSM_IDLE_STATS_BUCKET_COUNT + int "Bucket count" + default 10 + +config MSM_SUSPEND_STATS_FIRST_BUCKET + int "First bucket time for suspend" + default 1000000000 + help + Upper time limit in nanoseconds of first bucket of the + histogram. This is for collecting statistics on suspend. + +endif # MSM_IDLE_STATS + +config CPU_HAS_L2_PMU + bool "L2CC PMU Support" + help + Select this if the L2 cache controller has a Performance Monitoring Unit. + +config MSM_JTAG_V7 + depends on CPU_V7 + default y if DEBUG_KERNEL + bool "JTAG debug support" + help + Add additional support for JTAG kernel debugging. + +config HTC_HEADSET + tristate "HTC 2 Wire detection driver" + default n + help + Provides support for detecting HTC 2 wire devices, such as wired + headset, on the trout platform. Can be used with the msm serial + debugger, but not with serial console. + +config TROUT_BATTCHG + depends on MACH_TROUT && POWER_SUPPLY + default y + bool "Trout battery / charger driver" + +config HTC_PWRSINK + depends on MSM_SMD + default n + bool "HTC Power Sink Driver" + +config QSD_SVS + bool "QSD Static Voltage Scaling" + depends on (MACH_QSD8X50_SURF || MACH_QSD8X50_FFA) + default y + select TPS65023 + help + Enables static voltage scaling using the TPS65023 PMIC. + +config QSD_PMIC_DEFAULT_DCDC1 + int "PMIC default output voltage" + depends on (MACH_QSD8X50_SURF || MACH_QSD8X50_FFA) + default 1250 + help + This is the PMIC voltage at Linux kernel boot. + +config MSM_FIQ_SUPPORT + default y + bool "Enable installation of an FIQ handler." + +config MSM_SERIAL_DEBUGGER + select MSM_FIQ_SUPPORT + select KERNEL_DEBUGGER_CORE + default n + bool "FIQ Mode Serial Debugger" + help + The FIQ serial debugger can accept commands even when the + kernel is unresponsive due to being stuck with interrupts + disabled. Depends on the kernel debugger core in drivers/misc. + +config MSM_SERIAL_DEBUGGER_CONSOLE + depends on MSM_SERIAL_DEBUGGER + default n + bool "Console on FIQ Serial Debugger port" + help + Enables a console so that printk messages are displayed on + the debugger serial port as the occur. config MSM_PROC_COMM - bool + default y + bool "Proc-Comm RPC Interface" + help + Enables a lightweight communications interface to the + baseband processor. config MSM_SMD - bool + bool "MSM Shared Memory Driver (SMD)" + help + Support for the shared memory interface between the apps + processor and the baseband processor. Provides access to + the "shared heap", as well as virtual serial channels + used to communicate with various services on the baseband + processor. + +choice + prompt "MSM Shared memory interface version" + depends on MSM_SMD + default MSM_SMD_PKG3 if ARCH_MSM_ARM11 + default MSM_SMD_PKG4 if ARCH_MSM_SCORPION + + config MSM_SMD_PKG3 + bool + prompt "Package 3" + + config MSM_SMD_PKG4 + bool + prompt "Package 4" +endchoice + +config MSM_IPC_ROUTER_SMD_XPRT + depends on MSM_SMD + default n + bool "MSM SMD XPRT Layer" + help + SMD Transport Layer for IPC Router + +config MSM_RPC_SDIO_XPRT + depends on MSM_SDIO_AL + default y + bool "MSM SDIO XPRT Layer" + help + SDIO Transport Layer for RPC Rouer + +config MSM_RPC_SDIO_DEBUG + depends on MSM_RPC_SDIO_XPRT + default y + bool "MSM SDIO XPRT debug support" + help + Support for debugging SDIO XPRT + +config MSM_SMD_DEBUG + depends on MSM_SMD + default y + bool "MSM SMD debug support" + help + Support for debugging the SMD for communication + between the ARM9 and ARM11 + +config MSM_SDIO_AL + depends on ((ARCH_MSM7X30 || MACH_MSM8X60_FUSN_FFA || MACH_TYPE_MSM8X60_FUSION) && HAS_WAKELOCK) + default y + tristate "SDIO-Abstraction-Layer" + help + Support MSM<->MDM Communication over SDIO bus. + MDM SDIO-Client should have pipes support. + +config MSM_SDIO_DMUX + bool "SDIO Data Mux Driver" + depends on MSM_SDIO_AL + default n + help + Support Muxed Data Channels over SDIO interface. + +config MSM_BAM_DMUX + bool "BAM Data Mux Driver" + depends on SPS + default n + help + Support Muxed Data Channels over BAM interface. + BAM has a limited number of pipes. This driver + provides a means to support more logical channels + via muxing than BAM could without muxing. + +config MSM_N_WAY_SMD + depends on (MSM_SMD && (ARCH_MSM_SCORPION || ARCH_MSM8960 || ARCH_MSM7X27 || ARCH_MSM7X25)) + default y + bool "MSM N-WAY SMD support" + help + Supports APPS-QDSP SMD communication along with + normal APPS-MODEM SMD communication. + +config MSM_N_WAY_SMSM + depends on (MSM_SMD && (ARCH_MSM_SCORPION || ARCH_MSM8960 || ARCH_MSM7X27 || ARCH_MSM7X25)) + default y + bool "MSM N-WAY SMSM support" + help + Supports APPS-QDSP SMSM communication along with + normal APPS-MODEM SMSM communication. + +config MSM_RESET_MODEM + tristate "Reset Modem Driver" + depends on MSM_SMD + default m + help + Allows the user to reset the modem through a device node. + +config MSM_SMD_LOGGING + depends on MSM_SMD + default y + bool "MSM Shared Memory Logger" + help + This option exposes the shared memory logger at /dev/smem_log + and a debugfs node named smem_log. + + If in doubt, say yes. + +config MSM_SMD_NMEA + bool "NMEA GPS Driver" + depends on MSM_SMD + default y + help + Enable this to support the NMEA GPS device. + + If in doubt, say yes. + +config MSM_SDIO_TTY + bool "SDIO TTY Driver" + depends on MSM_SDIO_AL + default n + help + Provides a TTY driver SDIO TTY + This driver can be used by user space + applications for passing data through the + SDIO interface. + +config MSM_SMD_TTY + bool "SMD TTY Driver" + depends on MSM_SMD + default y + help + Provides TTY interfaces to interact with the modem. + + If in doubt, say yes. + +config MSM_SMD_QMI + bool "SMD QMI Driver" + depends on MSM_SMD + default y + help + Manages network data connections. + + If in doubt, say yes. + +config MSM_SMD_PKT + bool "SMD Packet Driver" + depends on MSM_SMD + default y + help + Provides a binary SMD non-muxed packet port interface. + + If in doubt, say yes. + +config MSM_SDIO_CMUX + bool "SDIO CMUX Driver" + depends on MSM_SDIO_AL + default n + help + Provides a Muxed port interface over SDIO QMI + +config MSM_DSPS + bool "Sensors DSPS driver" + depends on (MSM_PIL && (ARCH_MSM8X60 || ARCH_MSM8960)) + default n + help + Provides user-space interface to the sensors manager + to turn on/off the Sensors Processor system clocks. + It is the DSPS responsibility to turn on/off the sensors + themself. + The number of clocks and their name may vary between targets. + It also triggers the PIL to load the DSPS firmware. + +config MSM_SDIO_CTL + bool "SDIO CTL Driver" + depends on MSM_SDIO_CMUX + default n + help + Provides a binary SDIO control port interface. + +config MSM_ONCRPCROUTER + depends on MSM_SMD + default n + bool "MSM ONCRPC router support" + help + Support for the MSM ONCRPC router for communication between + the ARM9 and ARM11 + +config MSM_IPC_ROUTER + depends on MSM_IPC_ROUTER_SMD_XPRT + default n + bool "MSM IPC Router support" + help + Support for the MSM IPC Router for communication between + the APPs and the MODEM + +config MSM_ONCRPCROUTER_DEBUG + depends on MSM_ONCRPCROUTER + default y + bool "MSM debug ONCRPC router support" + help + Support for debugging the ONCRPC router for communication + between the ARM9 and ARM11 + +config MSM_RPC_LOOPBACK_XPRT + depends on MSM_ONCRPCROUTER + default n + bool "MSM RPC local routing support" + help + Support for routing RPC messages between APPS clients + and APPS servers. Helps in testing APPS RPC framework. + +config MSM_RPCSERVER_TIME_REMOTE + depends on MSM_ONCRPCROUTER && RTC_HCTOSYS + default y + bool "Time remote RPC server" + help + The time remote server receives notification of time bases and + reports these events to registered callback functions. + +config MSM_RPCSERVER_WATCHDOG + depends on MSM_ONCRPCROUTER + default y + bool "Watchdog RPC server" + help + The dog_keepalive server handles watchdog events. + +config MSM_RPC_WATCHDOG + depends on MSM_ONCRPCROUTER + default n + bool "Watchdog RPC client" + help + The dog_keepalive client module. + +config MSM_RPC_PING + depends on MSM_ONCRPCROUTER && DEBUG_FS + default m + bool "MSM rpc ping" + help + Implements MSM rpc ping test module. + +config MSM_RPC_PROC_COMM_TEST + depends on DEBUG_FS && MSM_PROC_COMM + default m + bool "MSM rpc proc comm test" + help + Implements MSM rpc proc comm test module. + +config MSM_RPC_OEM_RAPI + depends on MSM_ONCRPCROUTER + default m + bool "MSM oem rapi" + help + Implements MSM oem rapi client module. + +config MSM_RPCSERVER_HANDSET + depends on MSM_ONCRPCROUTER + default y + bool "Handset events RPC server" + help + Support for receiving handset events like headset detect, + headset switch and clamshell state. + +config MSM_RMT_STORAGE_CLIENT + depends on (ARCH_MSM && MSM_ONCRPCROUTER) + default n + bool "Remote Storage RPC client" + help + Provide RPC mechanism for remote processors to access storage + device on apps processor. + +config MSM_RMT_STORAGE_CLIENT_STATS + depends on (MSM_RMT_STORAGE_CLIENT && DEBUG_FS) + default n + bool "Remote storage RPC client performance statistics" + help + Collects performance statistics and shows this information + through a debugfs file rmt_storage_stats. + +config MSM_SDIO_SMEM + depends on MSM_SDIO_AL + default n + bool "SDIO SMEM for remote storage" + help + Copies data from remote MDM9K memory to local MSM8x60 + memory. Used by remote storage client to shadow + MDM9K filesystem. + +config MSM_DALRPC + bool "DAL RPC support" + depends on ARCH_MSM_SCORPION || ARCH_MSM_KRAIT + default y + help + Supports RPC calls to DAL devices on remote processor cores. + +config MSM_DALRPC_TEST + tristate "DAL RPC test module" + depends on (MSM_DALRPC && DEBUG_FS) + default m + help + Exercises DAL RPC calls to QDSP6. + +if CPU_FREQ_MSM + +config MSM_CPU_FREQ_SET_MIN_MAX + bool "Set Min/Max CPU frequencies." + default n + help + Allow setting min and max CPU frequencies. Sysfs can be used + to override these values. + +config MSM_CPU_FREQ_MAX + int "Max CPU Frequency" + depends on MSM_CPU_FREQ_SET_MIN_MAX + default 384000 + +config MSM_CPU_FREQ_MIN + int "Min CPU Frequency" + depends on MSM_CPU_FREQ_SET_MIN_MAX + default 245760 + +endif # CPU_FREQ_MSM + +config MSM_CPU_AVS + bool "Enable software controlled Adaptive Voltage Scaling (AVS)" + depends on (ARCH_MSM_SCORPION && QSD_SVS) + depends on ARCH_QSD8X50 + default n + select MSM_AVS_HW + help + This enables the s/w control of Adaptive Voltage Scaling feature + in Qualcomm ARMv7 CPUs. It adjusts the voltage for each frequency + based on feedback from three ring oscillators in the CPU. + +config MSM_AVS_HW + bool "Enable Adaptive Voltage Scaling (AVS)" + default n + help + Enable AVS hardware to fine tune voltage at each frequency. The + AVS hardware blocks associated with each Qualcomm ARMv7 cores can + fine tune the voltages based on the feedback from the ring + oscillators. + +config MSM_HW3D + tristate "MSM Hardware 3D Register Driver" + depends on ANDROID_PMEM + default y + help + Provides access to registers needed by the userspace OpenGL|ES + library. + +config MSM_ADSP + depends on (ARCH_MSM7X01A || ARCH_MSM7X25 || ARCH_MSM7X27) + tristate "MSM ADSP driver" + depends on ANDROID_PMEM + default y + help + Provides access to registers needed by the userspace aDSP library. + +config ADSP_RPC_VER + hex + default 0x30002 if (ARCH_MSM7X27 || (ARCH_MSM7X25 && AMSS_7X25_VERSION_2009)) + default 0x30001 if (ARCH_MSM7X01A || (ARCH_MSM7X25 && AMSS_7X25_VERSION_2008)) + depends on MSM_ADSP + help + Select proper ADSP RPC version +choice + prompt "ADSP RPC version" + + default AMSS_7X25_VERSION_2009 + + config AMSS_7X25_VERSION_2009 + bool "2.0.09" + + config AMSS_7X25_VERSION_2008 + bool "2.0.08" +endchoice + +config MSM7KV2_AUDIO + bool "MSM7K v2 audio" + depends on (ARCH_MSM7X30 && ANDROID_PMEM) + default y + help + Enables QDSP5V2-based audio drivers for audio playbacks and + voice call. + +config MSM_ADSP_REPORT_EVENTS + bool "Report modem events from the DSP" + default y + depends on (MSM_ADSP || MSM7KV2_AUDIO) + help + Normally, only messages from the aDSP are reported to userspace. + With this option, we report events from the aDSP as well. + +config MSM_QDSP6 + tristate "QDSP6 support" + depends on ARCH_QSD8X50 && ANDROID_PMEM + default y + help + Enable support for qdsp6. This provides audio and video functionality. + +config MSM8X60_AUDIO + tristate "MSM8X60 audio support" + depends on ARCH_MSM8X60 && ANDROID_PMEM + default y + help + Enable support for qdsp6v2. This provides audio functionality. + +config MSM8X60_FTM_AUDIO_DEVICES + bool "MSM8X60 audio factory test mode support" + depends on MSM8X60_AUDIO + help + Enable support audio factory test mode devices. This is used + in a production line environment. + +config MSM8X60_RTAC + tristate "MSM8X60 real-time audio calibration support" + depends on MSM8X60_AUDIO + default n + help + Enable support for rtac in qdsp6v2. This enables calibration during + audio operation + +config MSM7X27A_AUDIO + bool "MSM7X27A audio support" + depends on ARCH_MSM7X27A && MSM_ADSP + default n + help + Enable support for 7x27a. This provides audio functionality. + +config MSM_VREG_SWITCH_INVERTED + bool "Reverse vreg switch polarity" + default n + help + Reverses the enable and disable for vreg switch. + +config MSM_DMA_TEST + tristate "MSM DMA test module" + default m + help + Intended to be compiled as a module. Provides a device node + and ioctls for testing the MSM dma system. + +config WIFI_CONTROL_FUNC + bool "Enable WiFi control function abstraction" + help + Enables Power/Reset/Carddetect function abstraction + +config WIFI_MEM_PREALLOC + depends on WIFI_CONTROL_FUNC + bool "Preallocate memory for WiFi buffers" + help + Preallocates memory buffers for WiFi driver + +config QSD_AUDIO + bool "QSD audio" + depends on ARCH_MSM_SCORPION && MSM_DALRPC && ANDROID_PMEM && !MSM_SMP + default y + help + Provides PCM, MP3, and AAC audio playback. + +config AUDIO_AAC_PLUS + depends on (MSM_ADSP || QSD_AUDIO || MSM7KV2_AUDIO) + bool "AAC+ Audio" + default y + help + Provides AAC+ decoding + +config AUDIO_ENHANCED_AAC_PLUS + depends on AUDIO_AAC_PLUS + bool "Enhanced AAC+ Audio" + default y + help + Provides Enhanced AAC+ decoding + +config SURF_FFA_GPIO_KEYPAD + bool "MSM SURF/FFA GPIO keypad" + depends on INPUT_GPIO = "y" + default y + help + Select if the GPIO keypad is attached. + +config MSM_SLEEP_TIME_OVERRIDE + bool "Allow overriding suspend/sleep time with PM module parameter" + default y + help + Enable the module parameter sleep_time_override. Specified + in units of seconds, it overwrites the normal sleep time of + suspend. The feature is required for automated power management + testing. + +config MSM_MEMORY_LOW_POWER_MODE + bool "Control the low power modes of memory" + default n + help + The application processor controls whether memory should enter + which low power mode. + +choice + prompt "Default Memory Low Power Mode during Idle" + depends on MSM_MEMORY_LOW_POWER_MODE + default MSM_MEMORY_LOW_POWER_MODE_IDLE_ACTIVE + help + Selects the default low power mode of the memory during idle + sleep. + + config MSM_MEMORY_LOW_POWER_MODE_IDLE_ACTIVE + bool "Memory active" + + config MSM_MEMORY_LOW_POWER_MODE_IDLE_RETENTION + bool "Memory in retention" + + config MSM_MEMORY_LOW_POWER_MODE_IDLE_DEEP_POWER_DOWN + bool "Memory in deep power down" +endchoice + +choice + prompt "Default Memory Low Power Mode during Suspend" + depends on MSM_MEMORY_LOW_POWER_MODE + default MSM_MEMORY_LOW_POWER_MODE_SUSPEND_ACTIVE + help + Selects the default low power mode of the memory during suspend + sleep. + + config MSM_MEMORY_LOW_POWER_MODE_SUSPEND_ACTIVE + bool "Memory active" + + config MSM_MEMORY_LOW_POWER_MODE_SUSPEND_RETENTION + bool "Memory in retention" + + config MSM_MEMORY_LOW_POWER_MODE_SUSPEND_DEEP_POWER_DOWN + bool "Memory in deep power down" +endchoice + +choice + prompt "Power management timeout action" + default MSM_PM_TIMEOUT_HALT + help + Selects the Application Processor's action when Power Management + times out waiting for Modem's handshake. + + config MSM_PM_TIMEOUT_HALT + bool "Halt the Application Processor" + + config MSM_PM_TIMEOUT_RESET_MODEM + bool "Reset the Modem Processor" + + config MSM_PM_TIMEOUT_RESET_CHIP + bool "Reset the entire chip" +endchoice + +config MSM_IDLE_WAIT_ON_MODEM + int "Wait for Modem to become ready for idle power collapse" + default 0 + help + If Modem is not ready to handle Application Processor's request + for idle power collapse, wait the number of microseconds in case + Modem becomes ready soon. + +config MSM_PIL + bool "Peripheral image loading" + select FW_LOADER + depends on (ARCH_MSM8X60 || ARCH_MSM8960) + default n + help + Some peripherals need to be loaded into memory before they can be + brought out of reset. + + Say yes to support these devices. + +config MSM_SECURE_PIL + bool "Secure Peripheral image loading" + depends on MSM_PIL + default n + +config MSM_SCM + bool "Secure Channel Manager (SCM) support" + default n + +config MSM_SUBSYSTEM_RESTART + bool "MSM Subsystem Restart Driver" + depends on (ARCH_MSM8X60 || ARCH_MSM8960) + default n + help + This option enables the MSM subsystem restart driver, which provides + a framework to handle subsystem crashes. + +config MSM_MODEM_8960 + bool "MSM 8960 Modem driver" + depends on (ARCH_MSM8960) + help + This option enables the modem driver for the MSM8960, which monitors + modem hardware watchdog interrupt lines and plugs into the subsystem + restart and PIL drivers. + +config SCORPION_Uni_45nm_BUG + bool "Scorpion Uni 45nm(SC45U): Workaround for ICIMVAU and BPIMVA" + depends on ARCH_MSM7X30 || (ARCH_QSD8X50 && MSM_SOC_REV_A) + default y + help + Invalidating the Instruction Cache by Modified Virtual Address to PoU and + invalidating the Branch Predictor Array by Modified Virtual Address can + create invalid entries in the TLB with the wrong ASID values on Scorpion + Uniprocessor 45nm (SC45U) cores. This option enables the recommended software + workaround for Scorpion Uniprocessor 45nm cores. + + This bug is not applicable to any ScorpionMP or Scorpion Uni 65nm(SC65U) cores. + +config MSM_RPM_LOG + tristate "MSM Resource Power Manager Log Driver" + depends on DEBUG_FS + depends on ARCH_MSM8X60 + default n + help + This option enables a driver which can read from a circular buffer + of messages produced by the RPM. These messages provide diagnostic + information about RPM operation. The driver outputs the messages + via a debugfs node. + +config MSM_RPM_STATS_LOG + tristate "MSM Resource Power Manager Stat Driver" + depends on DEBUG_FS + depends on ARCH_MSM8X60 + default n + help + This option enables a driver which reads RPM messages from a shared + memory location. These messages provide statistical information about + the low power modes that RPM enters. The drivers outputs the message + via a debugfs node. + +config MSM_IOMMU + bool "MSM IOMMU Support" + depends on ARCH_MSM8X60 || ARCH_MSM8960 + select IOMMU_API + default n + help + Support for the IOMMUs found on certain Qualcomm SOCs. + These IOMMUs allow virtualization of the address space used by most + cores within the multimedia subsystem. + + If unsure, say N here. + +config IOMMU_PGTABLES_L2 + bool "Allow SMMU page tables in the L2 cache (Experimental)" + depends on MSM_IOMMU=y + depends on MMU + depends on CPU_DCACHE_DISABLE=n + depends on SMP + default y + help + Improves TLB miss latency at the expense of potential L2 pollution. + However, with large multimedia buffers, the TLB should mostly contain + section mappings and TLB misses should be quite infrequent. + Most people can probably say Y here. + +config MSM_DIRECT_SCLK_ACCESS + bool "Direct access to the SCLK timer" + default n + +config IOMMU_API + bool config MSM_GPIOMUX bool config MSM_V2_TLMM + bool + +config FSM9XXX_TLMM bool -config IOMMU_API +config MSM_SECURE_IO bool -config MSM_SCM +config MSM_NATIVE_RESTART bool + +config MSM_BUS_SCALING + bool "Bus scaling driver" + default n + +config MSM_WATCHDOG + bool "MSM Watchdog Support" + depends on ARCH_MSM8X60 || ARCH_MSM8960 + help + This enables the watchdog as is present on 8x60. Currently we use + core 0's watchdog, and reset the entire SoC if it times out. It does + not run during the bootup process, so it will not catch any early + lockups. + +config MSM_DLOAD_MODE + bool "Enable download mode on crashes" + depends on ARCH_MSM8X60 || ARCH_MSM8960 + default n + help + This makes the SoC enter download mode when it resets + due to a kernel panic. Note that this doesn't by itself + make the kernel reboot on a kernel panic - that must be + enabled via another mechanism. + +config MSM_ETM + tristate "Enable MSM ETM and ETB" + depends on ARCH_MSM8X60 + help + This enables embedded trace collection on Qualcomm v7 CPUs. + +config MSM_SLEEP_STATS + bool "Enable exporting of MSM sleep stats to userspace" + depends on CPU_IDLE + default n + +config MSM_STANDALONE_POWER_COLLAPSE + bool "Enable standalone power collapse" + default n + +config MSM_GSBI9_UART + bool "Enable GSBI9 UART device" + default n + help + This enables GSBI9 configured into UART. + +config MSM_SHARED_GPIO_FOR_UART2DM + bool "Use shared GPIOs into UART mode" + depends on (ARCH_MSM7X27A && !MMC_MSM_SDC3_8_BIT_SUPPORT && !MMC_MSM_SDC4_SUPPORT) + help + This option configures GPIO muxed with SDC4/MMC3 + 8-bit mode into UART mode. It is used for serial + console on UART2DM. Say Y if you want to have + serial console on UART2DM. + +config MSM_SHOW_RESUME_IRQ + bool "Enable logging of interrupts that could have caused resume" + depends on (ARCH_MSM8X60 || ARCH_MSM8960) + default n + help + This option logs wake up interrupts that have triggered just before + the resume loop unrolls. Say Y if you want to debug why the system + resumed. + +config BT_MSM_PINTEST + tristate "MSM Bluetooth Pin Connectivity Test" + depends on ((ARCH_MSM8X60 || ARCH_MSM7X27A) && DEBUG_FS) + default n + help + Bluetooth MSM Pin Connectivity test module. + This driver provides support for verifying the MSM to BT pin + connectivity. + +config MSM_FAKE_BATTERY + depends on POWER_SUPPLY + default n + bool "MSM Fake Battery" + help + Enables MSM fake battery driver. + +config MSM_QDSP6_APR + bool "Audio QDSP6 APR support" + depends on MSM_SMD + default n + help + Enable APR IPC protocol support between + application processor and QDSP6. APR is + used by audio driver to configure QDSP6's + ASM, ADM and AFE. + + +config MSM_AUDIO_QDSP6 + bool "QDSP6 HW Audio support" + select SND_SOC_MSM_QDSP6_INTF + default n + help + Enable HW audio support in QDSP6. + QDSP6 can support HW encoder & decoder and audio processing + +config MSM_RPC_VIBRATOR + bool "RPC based MSM Vibrator Support" + depends on MSM_ONCRPCROUTER + help + Enable the vibrator support on MSM over RPC. The vibrator + is connected on the PMIC. Say Y if you want to enable this + feature. + +config PM8XXX_RPC_VIBRATOR + bool "RPC based Vibrator on PM8xxx PMICs" + depends on MSM_RPC_VIBRATOR + help + Enable the vibrator support on MSM over RPC. The vibrator + is connected on the PM8XXX PMIC. Say Y if you want to enable + this feature. + +config MSM_SPM_V1 + bool "Driver support for SPM Version 1" + help + Enables the support for Version 1 of the SPM driver. SPM hardware is + used to manage the processor power during sleep. The driver allows + configuring SPM to allow different power modes. + +config MSM_SPM_V2 + bool "Driver support for SPM Version 2" + help + Enables the support for Version 2 of the SPM driver. SPM hardware is + used to manage the processor power during sleep. The driver allows + configuring SPM to allow different power modes. + +config MSM_L2_SPM + bool "SPM support for L2 cache" + depends on MSM_SPM_V2 + help + Enable SPM driver support for L2 cache. Some MSM chipsets allow + control of L2 cache low power mode with a Subsystem Power manager. + Enabling this driver allows configuring L2 SPM for low power modes + on supported chipsets. + endif diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile index 9519fd28a02..e265f8780af 100644 --- a/arch/arm/mach-msm/Makefile +++ b/arch/arm/mach-msm/Makefile @@ -1,39 +1,252 @@ -obj-y += io.o idle.o timer.o -obj-y += clock.o -obj-$(CONFIG_DEBUG_FS) += clock-debug.o +obj-y += io.o dma.o memory.o timer.o +obj-y += clock.o clock-voter.o clock-dummy.o +obj-y += modem_notifier.o subsystem_map.o +obj-$(CONFIG_CPU_FREQ_MSM) += cpufreq.o +obj-$(CONFIG_DEBUG_FS) += nohlt.o clock-debug.o +obj-$(CONFIG_KEXEC) += msm_kexec.o + +obj-$(CONFIG_MSM_PROC_COMM) += proc_comm.o +ifndef CONFIG_ARCH_MSM8X60 + obj-$(CONFIG_MSM_PROC_COMM) += clock-pcom.o + obj-$(CONFIG_MSM_PROC_COMM) += vreg.o mpp.o + ifdef CONFIG_MSM_PROC_COMM +ifndef CONFIG_ARCH_FSM9XXX + obj-$(CONFIG_REGULATOR) += footswitch-pcom.o +endif + obj-$(CONFIG_DEBUG_FS) += pmic_debugfs.o + endif +endif + +obj-$(CONFIG_ARCH_MSM_ARM11) += acpuclock.o +obj-$(CONFIG_ARCH_MSM_SCORPION) += pmu.o +obj-$(CONFIG_ARCH_MSM_CORTEX_A5) += acpuclock.o +obj-$(CONFIG_ARCH_MSM_KRAIT) += msm-krait-l2-accessors.o pmu.o + +ifndef CONFIG_MSM_SMP +obj-$(CONFIG_ARCH_MSM_SCORPION) += msm_fault_handlers.o +endif obj-$(CONFIG_MSM_VIC) += irq-vic.o -obj-$(CONFIG_MSM_IOMMU) += iommu.o iommu_dev.o devices-iommu.o -obj-$(CONFIG_ARCH_MSM7X00A) += dma.o irq.o acpuclock-arm11.o -obj-$(CONFIG_ARCH_MSM7X30) += dma.o -obj-$(CONFIG_ARCH_QSD8X50) += dma.o sirc.o +ifdef CONFIG_ARCH_QSD8X50 + obj-$(CONFIG_MSM_SOC_REV_NONE) += acpuclock-8x50.o +endif + +obj-$(CONFIG_SMP) += headsmp.o platsmp.o + +obj-$(CONFIG_MSM_CPU_AVS) += avs.o +obj-$(CONFIG_MSM_AVS_HW) += avs_hw.o +obj-$(CONFIG_CPU_V6) += idle-v6.o +obj-$(CONFIG_CPU_V7) += idle-v7.o +obj-$(CONFIG_MSM_JTAG_V7) += jtag-v7.o -obj-$(CONFIG_MSM_PROC_COMM) += proc_comm.o clock-pcom.o vreg.o +msm-etm-objs := cp14.o etm.o +obj-$(CONFIG_MSM_ETM) += msm-etm.o + +quiet_cmd_mkrpcsym = MKCAP $@ + cmd_mkrpcsym = $(PERL) $(srctree)/$(src)/mkrpcsym.pl $< $@ + +target += smd_rpc_sym.c +$(obj)/smd_rpc_sym.c: $(src)/smd_rpc_sym $(src)/mkrpcsym.pl + $(call if_changed,mkrpcsym) -obj-$(CONFIG_MSM_SMD) += smd.o smd_debug.o -obj-$(CONFIG_MSM_SMD) += last_radio_log.o obj-$(CONFIG_MSM_SCM) += scm.o scm-boot.o +obj-$(CONFIG_MSM_SECURE_IO) += scm-io.o +obj-$(CONFIG_MSM_PIL) += peripheral-loader.o +ifdef CONFIG_MSM_PIL +obj-$(CONFIG_ARCH_MSM8X60) += peripheral-reset.o +obj-$(CONFIG_ARCH_MSM8960) += peripheral-reset-8960.o +endif +obj-$(CONFIG_ARCH_QSD8X50) += sirc.o +obj-$(CONFIG_ARCH_FSM9XXX) += sirc-fsm9xxx.o +obj-$(CONFIG_MSM_FIQ_SUPPORT) += fiq_glue.o +obj-$(CONFIG_MACH_TROUT) += board-trout-rfkill.o +obj-$(CONFIG_MSM_SDIO_AL) += sdio_al.o +obj-$(CONFIG_MSM_SDIO_AL) += sdio_al_test.o +obj-$(CONFIG_MSM_SDIO_AL) += sdio_al_dloader.o +obj-$(CONFIG_MSM_SDIO_DMUX) += sdio_dmux.o +obj-$(CONFIG_MSM_BAM_DMUX) += bam_dmux.o +obj-$(CONFIG_MSM_SMD_LOGGING) += smem_log.o +obj-$(CONFIG_MSM_SMD) += smd.o smd_debug.o remote_spinlock.o + obj-y += socinfo.o +ifndef CONFIG_ARCH_MSM8960 +ifndef CONFIG_ARCH_MSM8X60 + obj-$(CONFIG_MSM_SMD) += pmic.o + obj-$(CONFIG_MSM_SMD) += nand_partitions.o + obj-$(CONFIG_MSM_ONCRPCROUTER) += rpc_hsusb.o rpc_pmapp.o rpc_fsusb.o +endif +endif +obj-$(CONFIG_MSM_SDIO_TTY) += sdio_tty.o sdio_tty_ciq.o +obj-$(CONFIG_MSM_SMD_TTY) += smd_tty.o +obj-$(CONFIG_MSM_SMD_QMI) += smd_qmi.o +obj-$(CONFIG_MSM_SMD_PKT) += smd_pkt.o +obj-$(CONFIG_MSM_SDIO_CMUX) += sdio_cmux.o +obj-$(CONFIG_MSM_DSPS) += msm_dsps.o +obj-$(CONFIG_MSM_SDIO_CTL) += sdio_ctl.o +obj-$(CONFIG_MSM_SMD_NMEA) += smd_nmea.o +obj-$(CONFIG_MSM_RESET_MODEM) += reset_modem.o +obj-$(CONFIG_MSM_IPC_ROUTER_SMD_XPRT) += ipc_router_smd_xprt.o +obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter.o +obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter_device.o +obj-$(CONFIG_MSM_IPC_ROUTER) += ipc_router.o +obj-$(CONFIG_MSM_IPC_ROUTER)+= ipc_socket.o +obj-$(CONFIG_DEBUG_FS) += smd_rpc_sym.o +obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter_servers.o +obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter_clients.o +obj-$(CONFIG_MSM_ONCRPCROUTER) += smd_rpcrouter_xdr.o +obj-$(CONFIG_MSM_ONCRPCROUTER) += rpcrouter_smd_xprt.o +obj-$(CONFIG_MSM_RPC_SDIO_XPRT) += rpcrouter_sdio_xprt.o +obj-$(CONFIG_MSM_RPC_PING) += ping_mdm_rpc_client.o +obj-$(CONFIG_MSM_RPC_PROC_COMM_TEST) += proc_comm_test.o +obj-$(CONFIG_MSM_RPC_PING) += ping_mdm_rpc_client.o ping_apps_server.o +obj-$(CONFIG_MSM_RPC_OEM_RAPI) += oem_rapi_client.o +obj-$(CONFIG_MSM_RPC_WATCHDOG) += rpc_dog_keepalive.o +obj-$(CONFIG_MSM_RPCSERVER_WATCHDOG) += rpc_server_dog_keepalive.o +obj-$(CONFIG_MSM_RPCSERVER_TIME_REMOTE) += rpc_server_time_remote.o +obj-$(CONFIG_MSM_DALRPC) += dal.o +obj-$(CONFIG_MSM_DALRPC_TEST) += dal_remotetest.o +obj-$(CONFIG_ARCH_MSM7X30) += dal_axi.o +obj-$(CONFIG_ARCH_MSM7X27A) += dal_axi.o +obj-$(CONFIG_MSM_ADSP) += qdsp5/ +obj-$(CONFIG_MSM7KV2_AUDIO) += qdsp5v2/ +obj-$(CONFIG_MSM_RPCSERVER_HANDSET) += rpc_server_handset.o +obj-$(CONFIG_MSM_QDSP6) += qdsp6/ +obj-$(CONFIG_MSM8X60_AUDIO) += qdsp6v2/ +obj-$(CONFIG_MSM_AUDIO_QDSP6) += qdsp6v2/ +obj-$(CONFIG_MSM_HW3D) += hw3d.o +ifdef CONFIG_PM + obj-$(CONFIG_ARCH_MSM8960) += pm-8x60.o + obj-$(CONFIG_ARCH_MSM8X60) += pm-8x60.o + obj-$(CONFIG_ARCH_QSD8X50) += pm2.o + obj-$(CONFIG_ARCH_MSM7X30) += pm2.o + obj-$(CONFIG_ARCH_MSM7X27) += pm2.o + obj-$(CONFIG_ARCH_MSM7X27A) += pm2.o + obj-$(CONFIG_ARCH_MSM7X25) += pm.o + obj-$(CONFIG_ARCH_MSM7X01A) += pm.o +else + obj-y += no-pm.o +endif -obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o -obj-$(CONFIG_SMP) += headsmp.o platsmp.o +obj-$(CONFIG_MSM_SPM_V1) += spm.o +obj-$(CONFIG_MSM_SPM_V2) += spm-v2.o spm_devices.o + +obj-$(CONFIG_MSM_DMA_TEST) += dma_test.o +obj-$(CONFIG_SURF_FFA_GPIO_KEYPAD) += keypad-surf-ffa.o -obj-$(CONFIG_MACH_TROUT) += board-trout.o board-trout-gpio.o board-trout-mmc.o devices-msm7x00.o -obj-$(CONFIG_MACH_TROUT) += board-trout.o board-trout-gpio.o board-trout-mmc.o board-trout-panel.o devices-msm7x00.o -obj-$(CONFIG_MACH_HALIBUT) += board-halibut.o devices-msm7x00.o -obj-$(CONFIG_ARCH_MSM7X30) += board-msm7x30.o devices-msm7x30.o -obj-$(CONFIG_ARCH_QSD8X50) += board-qsd8x50.o devices-qsd8x50.o -obj-$(CONFIG_ARCH_MSM8X60) += board-msm8x60.o -obj-$(CONFIG_ARCH_MSM8960) += board-msm8960.o devices-msm8960.o +obj-$(CONFIG_ARCH_MSM7X01A) += board-halibut.o devices-msm7x01a.o clock-pcom-lookup.o +obj-$(CONFIG_MACH_TROUT) += board-trout.o board-trout-gpio.o +obj-$(CONFIG_MACH_TROUT) += board-trout-keypad.o board-trout-panel.o +obj-$(CONFIG_MACH_TROUT) += htc_akm_cal.o htc_wifi_nvs.o htc_acoustic.o +obj-$(CONFIG_MACH_TROUT) += board-trout-mmc.o board-trout-wifi.o +obj-$(CONFIG_ARCH_QSD8X50) += devices-qsd8x50.o clock-pcom-lookup.o +obj-$(CONFIG_MACH_QSD8X50_SURF) += board-qsd8x50.o +obj-$(CONFIG_MACH_QSD8X50_FFA) += board-qsd8x50.o +obj-$(CONFIG_ARCH_MSM8X60) += devices-msm8x60.o clock-local.o clock-8x60.o acpuclock-8x60.o +obj-$(CONFIG_ARCH_MSM8X60) += clock-rpm.o +obj-$(CONFIG_ARCH_MSM8X60) += saw-regulator.o +obj-$(CONFIG_ARCH_MSM8X60) += rpm-regulator.o +obj-$(CONFIG_ARCH_MSM8X60) += footswitch-8x60.o -obj-$(CONFIG_ARCH_MSM7X30) += gpiomux-v1.o gpiomux.o +ifdef CONFIG_MSM_SUBSYSTEM_RESTART + obj-y += subsystem_notif.o + obj-y += subsystem_restart.o + obj-y += ramdump.o + obj-$(CONFIG_ARCH_MSM8X60) += subsystem-fatal-8x60.o +endif +obj-$(CONFIG_MSM_MODEM_8960) += modem-8960.o + +ifdef CONFIG_CPU_IDLE + obj-$(CONFIG_ARCH_MSM8960) += cpuidle.o + obj-$(CONFIG_ARCH_MSM8X60) += cpuidle.o +endif + +obj-$(CONFIG_ARCH_FSM9XXX) += devices-fsm9xxx.o +obj-$(CONFIG_ARCH_FSM9XXX) += clock-fsm9xxx.o clock-local.o acpuclock-fsm9xxx.o +obj-$(CONFIG_ARCH_FSM9XXX) += dfe-fsm9xxx.o rfic-fsm9xxx.o +obj-$(CONFIG_ARCH_FSM9XXX) += restart-fsm9xxx.o + +obj-$(CONFIG_MSM_WATCHDOG) += msm_watchdog.o +obj-$(CONFIG_MACH_MSM8X60_RUMI3) += board-msm8x60.o +obj-$(CONFIG_MACH_MSM8X60_SIM) += board-msm8x60.o +obj-$(CONFIG_MACH_MSM8X60_SURF) += board-msm8x60.o +obj-$(CONFIG_MACH_MSM8X60_FFA) += board-msm8x60.o +obj-$(CONFIG_MACH_MSM8X60_FLUID) += board-msm8x60.o +obj-$(CONFIG_MACH_TYPE_MSM8X60_FUSION) += board-msm8x60.o mdm.o +obj-$(CONFIG_MACH_MSM8X60_FUSN_FFA) += board-msm8x60.o mdm.o +obj-$(CONFIG_TROUT_H2W) += board-trout-h2w.o +obj-$(CONFIG_TROUT_BATTCHG) += htc_battery.o +obj-$(CONFIG_TROUT_PWRSINK) += htc_pwrsink.o +obj-$(CONFIG_ARCH_MSM7X27) += clock-pcom-lookup.o +obj-$(CONFIG_MACH_MSM7X27_SURF) += board-msm7x27.o devices-msm7x27.o +obj-$(CONFIG_MACH_MSM7X27_FFA) += board-msm7x27.o devices-msm7x27.o +obj-$(CONFIG_ARCH_MSM7X27A) += clock-pcom-lookup.o +obj-$(CONFIG_MACH_MSM7X27A_RUMI3) += board-msm7x27a.o devices-msm7x27a.o +obj-$(CONFIG_MACH_MSM7X27A_SURF) += board-msm7x27a.o devices-msm7x27a.o +obj-$(CONFIG_MACH_MSM7X27A_FFA) += board-msm7x27a.o devices-msm7x27a.o +obj-$(CONFIG_ARCH_MSM7X30) += board-msm7x30.o devices-msm7x30.o memory_topology.o +obj-$(CONFIG_ARCH_MSM7X30) += clock-local.o clock-7x30.o acpuclock-7x30.o +obj-$(CONFIG_MACH_MSM7X25_SURF) += board-msm7x27.o devices-msm7x25.o +obj-$(CONFIG_MACH_MSM7X25_FFA) += board-msm7x27.o devices-msm7x25.o +obj-$(CONFIG_ARCH_MSM8960) += clock-local.o clock-dss-8960.o clock-8960.o clock-rpm.o +obj-$(CONFIG_ARCH_MSM8960) += footswitch-8x60.o +obj-$(CONFIG_ARCH_MSM8960) += acpuclock-8960.o +obj-$(CONFIG_ARCH_MSM8960) += saw-regulator.o rpm-regulator-8960.o +obj-$(CONFIG_MACH_MSM8960_SIM) += board-msm8960.o devices-8960.o board-msm8960-regulator.o +obj-$(CONFIG_MACH_MSM8960_RUMI3) += board-msm8960.o devices-8960.o board-msm8960-regulator.o +obj-$(CONFIG_MACH_MSM8960_CDP) += board-msm8960.o devices-8960.o board-msm8960-regulator.o +obj-$(CONFIG_MACH_MSM8960_MTP) += board-msm8960.o devices-8960.o board-msm8960-regulator.o +obj-$(CONFIG_MACH_MSM8960_FLUID) += board-msm8960.o devices-8960.o board-msm8960-regulator.o +obj-$(CONFIG_ARCH_MSM8960) += bms-batterydata.o +obj-$(CONFIG_ARCH_APQ8064) += board-apq8064.o devices-8064.o + +obj-$(CONFIG_MACH_SAPPHIRE) += board-sapphire.o board-sapphire-gpio.o +obj-$(CONFIG_MACH_SAPPHIRE) += board-sapphire-keypad.o board-sapphire-panel.o +obj-$(CONFIG_MACH_SAPPHIRE) += board-sapphire-mmc.o board-sapphire-wifi.o +obj-$(CONFIG_MACH_SAPPHIRE) += board-sapphire-rfkill.o msm_vibrator.o + +CFLAGS_msm_vibrator.o += -Idrivers/staging/android + +obj-$(CONFIG_ARCH_FSM9XXX) += board-fsm9xxx.o + +obj-$(CONFIG_TROUT_BATTCHG) += htc_battery.o + +obj-$(CONFIG_HTC_PWRSINK) += htc_pwrsink.o +obj-$(CONFIG_HTC_HEADSET) += htc_headset.o +obj-$(CONFIG_MSM_RMT_STORAGE_CLIENT) += rmt_storage_client.o +obj-$(CONFIG_MSM_SDIO_SMEM) += sdio_smem.o +obj-$(CONFIG_PMIC8058) += pmic8058-gpio.o pmic8058-mpp.o +obj-$(CONFIG_MSM_RPM) += rpm.o rpm_resources.o +obj-$(CONFIG_MSM_MPM) += mpm.o +obj-$(CONFIG_MSM_RPM_STATS_LOG) += rpm_stats.o +obj-$(CONFIG_MSM_RPM_LOG) += rpm_log.o +obj-$(CONFIG_MSM_XO) += msm_xo.o +obj-$(CONFIG_MSM_BUS_SCALING) += msm_bus/ + +obj-$(CONFIG_MSM_IOMMU) += iommu.o iommu_dev.o devices-iommu.o iommu_domains.o + +ifdef CONFIG_VCM +obj-$(CONFIG_ARCH_MSM8X60) += board-msm8x60-vcm.o +endif + +obj-$(CONFIG_ARCH_MSM7X27) += gpiomux-7x27.o gpiomux-v1.o gpiomux.o +obj-$(CONFIG_ARCH_MSM7X30) += gpiomux-7x30.o gpiomux-v1.o gpiomux.o obj-$(CONFIG_ARCH_QSD8X50) += gpiomux-8x50.o gpiomux-v1.o gpiomux.o obj-$(CONFIG_ARCH_MSM8X60) += gpiomux-8x60.o gpiomux-v2.o gpiomux.o +obj-$(CONFIG_ARCH_MSM8960) += gpiomux-v2.o gpiomux.o + +ifdef CONFIG_FSM9XXX_TLMM +obj-y += gpio-fsm9xxx.o +else ifdef CONFIG_MSM_V2_TLMM -ifndef CONFIG_ARCH_MSM8960 -# TODO: TLMM Mapping issues need to be resolved obj-y += gpio-v2.o -endif else obj-y += gpio.o endif +endif + +obj-$(CONFIG_MSM_SLEEP_STATS) += msm_rq_stats.o idle_stats.o +obj-$(CONFIG_MSM_SHOW_RESUME_IRQ) += msm_show_resume_irq.o +obj-$(CONFIG_BT_MSM_PINTEST) += btpintest.o +obj-$(CONFIG_MSM_FAKE_BATTERY) += fish_battery.o +obj-$(CONFIG_MSM_RPC_VIBRATOR) += msm_vibrator.o +obj-$(CONFIG_MSM_NATIVE_RESTART) += restart.o diff --git a/arch/arm/mach-msm/Makefile.boot b/arch/arm/mach-msm/Makefile.boot index 24dfbf8c07c..1bbaeaf6a09 100644 --- a/arch/arm/mach-msm/Makefile.boot +++ b/arch/arm/mach-msm/Makefile.boot @@ -1,3 +1,44 @@ - zreladdr-y := 0x10008000 -params_phys-y := 0x10000100 -initrd_phys-y := 0x10800000 +# MSM7x01A + zreladdr-$(CONFIG_ARCH_MSM7X01A) := 0x10008000 +params_phys-$(CONFIG_ARCH_MSM7X01A) := 0x10000100 +initrd_phys-$(CONFIG_ARCH_MSM7X01A) := 0x10800000 + +# MSM7x25 + zreladdr-$(CONFIG_ARCH_MSM7X25) := 0x00208000 +params_phys-$(CONFIG_ARCH_MSM7X25) := 0x00200100 +initrd_phys-$(CONFIG_ARCH_MSM7X25) := 0x0A000000 + +# MSM7x27 + zreladdr-$(CONFIG_ARCH_MSM7X27) := 0x00208000 +params_phys-$(CONFIG_ARCH_MSM7X27) := 0x00200100 +initrd_phys-$(CONFIG_ARCH_MSM7X27) := 0x0A000000 + +# MSM7x27A + zreladdr-$(CONFIG_ARCH_MSM7X27A) := 0x00208000 +params_phys-$(CONFIG_ARCH_MSM7X27A) := 0x00200100 + +# MSM7x30 + zreladdr-$(CONFIG_ARCH_MSM7X30) := 0x00208000 +params_phys-$(CONFIG_ARCH_MSM7X30) := 0x00200100 +initrd_phys-$(CONFIG_ARCH_MSM7X30) := 0x01200000 + +ifeq ($(CONFIG_MSM_SOC_REV_A),y) +# QSD8x50 + zreladdr-$(CONFIG_ARCH_QSD8X50) := 0x20008000 +params_phys-$(CONFIG_ARCH_QSD8X50) := 0x20000100 +initrd_phys-$(CONFIG_ARCH_QSD8X50) := 0x24000000 +endif + +# MSM8x60 + zreladdr-$(CONFIG_ARCH_MSM8X60) := 0x40208000 + +# MSM8960 + zreladdr-$(CONFIG_ARCH_MSM8960) := 0x80208000 + +# APQ8064 + zreladdr-$(CONFIG_ARCH_APQ8064) := 0x80208000 + +# FSM9XXX + zreladdr-$(CONFIG_ARCH_FSM9XXX) := 0x10008000 +params_phys-$(CONFIG_ARCH_FSM9XXX) := 0x10000100 +initrd_phys-$(CONFIG_ARCH_FSM9XXX) := 0x12000000 diff --git a/arch/arm/mach-msm/acpuclock-7x30.c b/arch/arm/mach-msm/acpuclock-7x30.c new file mode 100644 index 00000000000..2180a8daf4b --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-7x30.c @@ -0,0 +1,502 @@ +/* + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smd_private.h" +#include "clock.h" +#include "acpuclock.h" +#include "spm.h" + +#define SCSS_CLK_CTL_ADDR (MSM_ACC_BASE + 0x04) +#define SCSS_CLK_SEL_ADDR (MSM_ACC_BASE + 0x08) + +#define PLL2_L_VAL_ADDR (MSM_CLK_CTL_BASE + 0x33C) +#define PLL2_M_VAL_ADDR (MSM_CLK_CTL_BASE + 0x340) +#define PLL2_N_VAL_ADDR (MSM_CLK_CTL_BASE + 0x344) +#define PLL2_CONFIG_ADDR (MSM_CLK_CTL_BASE + 0x34C) + +#define VREF_SEL 1 /* 0: 0.625V (50mV step), 1: 0.3125V (25mV step). */ +#define V_STEP (25 * (2 - VREF_SEL)) /* Minimum voltage step size. */ +#define VREG_DATA (VREG_CONFIG | (VREF_SEL << 5)) +#define VREG_CONFIG (BIT(7) | BIT(6)) /* Enable VREG, pull-down if disabled. */ +/* Cause a compile error if the voltage is not a multiple of the step size. */ +#define MV(mv) ((mv) / (!((mv) % V_STEP))) +/* mv = (750mV + (raw * 25mV)) * (2 - VREF_SEL) */ +#define VDD_RAW(mv) (((MV(mv) / V_STEP) - 30) | VREG_DATA) + +#define MAX_AXI_KHZ 192000 + +struct clock_state { + struct clkctl_acpu_speed *current_speed; + struct mutex lock; + uint32_t acpu_switch_time_us; + uint32_t vdd_switch_time_us; + struct clk *ebi1_clk; +}; + +struct pll { + unsigned int l; + unsigned int m; + unsigned int n; + unsigned int pre_div; +}; + +struct clkctl_acpu_speed { + unsigned int use_for_scaling; + unsigned int acpu_clk_khz; + int src; + unsigned int acpu_src_sel; + unsigned int acpu_src_div; + unsigned int axi_clk_hz; + unsigned int vdd_mv; + unsigned int vdd_raw; + struct pll *pll_rate; + unsigned long lpj; /* loops_per_jiffy */ +}; + +static struct clock_state drv_state = { 0 }; + +/* Switch to this when reprogramming PLL2 */ +static struct clkctl_acpu_speed *backup_s; + +static struct pll pll2_tbl[] = { + { 42, 0, 1, 0 }, /* 806 MHz */ + { 53, 1, 3, 0 }, /* 1024 MHz */ + { 125, 0, 1, 1 }, /* 1200 MHz */ + { 73, 0, 1, 0 }, /* 1401 MHz */ +}; + +/* Use negative numbers for sources that can't be enabled/disabled */ + +enum acpuclk_source { + LPXO = -2, + AXI = -1, + PLL_0 = 0, + PLL_1, + PLL_2, + PLL_3, + MAX_SOURCE +}; + +static struct clk *acpuclk_sources[MAX_SOURCE]; + +/* + * Each ACPU frequency has a certain minimum MSMC1 voltage requirement + * that is implicitly met by voting for a specific minimum AXI frequency. + * Do NOT change the AXI frequency unless you are _absoulutely_ sure you + * know all the h/w requirements. + */ +static struct clkctl_acpu_speed acpu_freq_tbl[] = { + { 0, 24576, LPXO, 0, 0, 30720000, 900, VDD_RAW(900) }, + { 0, 61440, PLL_3, 5, 11, 61440000, 900, VDD_RAW(900) }, + { 1, 122880, PLL_3, 5, 5, 61440000, 900, VDD_RAW(900) }, + { 0, 184320, PLL_3, 5, 4, 61440000, 900, VDD_RAW(900) }, + { 0, MAX_AXI_KHZ, AXI, 1, 0, 61440000, 900, VDD_RAW(900) }, + { 1, 245760, PLL_3, 5, 2, 61440000, 900, VDD_RAW(900) }, + { 1, 368640, PLL_3, 5, 1, 122800000, 900, VDD_RAW(900) }, + /* AXI has MSMC1 implications. See above. */ + { 1, 768000, PLL_1, 2, 0, 153600000, 1050, VDD_RAW(1050) }, + /* + * AXI has MSMC1 implications. See above. + */ + { 1, 806400, PLL_2, 3, 0, UINT_MAX, 1100, VDD_RAW(1100), &pll2_tbl[0]}, + { 1, 1024000, PLL_2, 3, 0, UINT_MAX, 1200, VDD_RAW(1200), &pll2_tbl[1]}, + { 1, 1200000, PLL_2, 3, 0, UINT_MAX, 1200, VDD_RAW(1200), &pll2_tbl[2]}, + { 1, 1401600, PLL_2, 3, 0, UINT_MAX, 1250, VDD_RAW(1250), &pll2_tbl[3]}, + { 0 } +}; + +#define POWER_COLLAPSE_KHZ MAX_AXI_KHZ +unsigned long acpuclk_power_collapse(void) +{ + int ret = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), POWER_COLLAPSE_KHZ, SETRATE_PC); + return ret; +} + +#define WAIT_FOR_IRQ_KHZ MAX_AXI_KHZ +unsigned long acpuclk_wait_for_irq(void) +{ + int ret = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), WAIT_FOR_IRQ_KHZ, SETRATE_SWFI); + return ret; +} + +static int acpuclk_set_acpu_vdd(struct clkctl_acpu_speed *s) +{ + int ret = msm_spm_set_vdd(0, s->vdd_raw); + if (ret) + return ret; + + /* Wait for voltage to stabilize. */ + udelay(drv_state.vdd_switch_time_us); + return 0; +} + +/* Assumes PLL2 is off and the acpuclock isn't sourced from PLL2 */ +static void acpuclk_config_pll2(struct pll *pll) +{ + uint32_t config = readl_relaxed(PLL2_CONFIG_ADDR); + + /* Make sure write to disable PLL_2 has completed + * before reconfiguring that PLL. */ + mb(); + writel_relaxed(pll->l, PLL2_L_VAL_ADDR); + writel_relaxed(pll->m, PLL2_M_VAL_ADDR); + writel_relaxed(pll->n, PLL2_N_VAL_ADDR); + if (pll->pre_div) + config |= BIT(15); + else + config &= ~BIT(15); + writel_relaxed(config, PLL2_CONFIG_ADDR); + /* Make sure PLL is programmed before returning. */ + mb(); +} + +/* Set clock source and divider given a clock speed */ +static void acpuclk_set_src(const struct clkctl_acpu_speed *s) +{ + uint32_t reg_clksel, reg_clkctl, src_sel; + + reg_clksel = readl_relaxed(SCSS_CLK_SEL_ADDR); + + /* CLK_SEL_SRC1NO */ + src_sel = reg_clksel & 1; + + /* Program clock source and divider. */ + reg_clkctl = readl_relaxed(SCSS_CLK_CTL_ADDR); + reg_clkctl &= ~(0xFF << (8 * src_sel)); + reg_clkctl |= s->acpu_src_sel << (4 + 8 * src_sel); + reg_clkctl |= s->acpu_src_div << (0 + 8 * src_sel); + writel_relaxed(reg_clkctl, SCSS_CLK_CTL_ADDR); + + /* Toggle clock source. */ + reg_clksel ^= 1; + + /* Program clock source selection. */ + writel_relaxed(reg_clksel, SCSS_CLK_SEL_ADDR); + + /* Make sure switch to new source is complete. */ + mb(); +} + +int acpuclk_set_rate(int cpu, unsigned long rate, enum setrate_reason reason) +{ + struct clkctl_acpu_speed *tgt_s, *strt_s; + int res, rc = 0; + + if (reason == SETRATE_CPUFREQ) + mutex_lock(&drv_state.lock); + + strt_s = drv_state.current_speed; + + if (rate == strt_s->acpu_clk_khz) + goto out; + + for (tgt_s = acpu_freq_tbl; tgt_s->acpu_clk_khz != 0; tgt_s++) { + if (tgt_s->acpu_clk_khz == rate) + break; + } + if (tgt_s->acpu_clk_khz == 0) { + rc = -EINVAL; + goto out; + } + + if (reason == SETRATE_CPUFREQ) { + /* Increase VDD if needed. */ + if (tgt_s->vdd_mv > strt_s->vdd_mv) { + rc = acpuclk_set_acpu_vdd(tgt_s); + if (rc < 0) { + pr_err("ACPU VDD increase to %d mV failed " + "(%d)\n", tgt_s->vdd_mv, rc); + goto out; + } + } + } + + pr_debug("Switching from ACPU rate %u KHz -> %u KHz\n", + strt_s->acpu_clk_khz, tgt_s->acpu_clk_khz); + + /* Increase the AXI bus frequency if needed. This must be done before + * increasing the ACPU frequency, since voting for high AXI rates + * implicitly takes care of increasing the MSMC1 voltage, as needed. */ + if (tgt_s->axi_clk_hz > strt_s->axi_clk_hz) { + rc = clk_set_min_rate(drv_state.ebi1_clk, + tgt_s->axi_clk_hz); + if (rc < 0) { + pr_err("Setting AXI min rate failed (%d)\n", rc); + goto out; + } + } + + /* Move off of PLL2 if we're reprogramming it */ + if (tgt_s->src == PLL_2 && strt_s->src == PLL_2) { + clk_enable(acpuclk_sources[backup_s->src]); + acpuclk_set_src(backup_s); + clk_disable(acpuclk_sources[strt_s->src]); + } + + /* Reconfigure PLL2 if we're moving to it */ + if (tgt_s->src == PLL_2) + acpuclk_config_pll2(tgt_s->pll_rate); + + /* Make sure target PLL is on. */ + if ((strt_s->src != tgt_s->src && tgt_s->src >= 0) || + (tgt_s->src == PLL_2 && strt_s->src == PLL_2)) { + pr_debug("Enabling PLL %d\n", tgt_s->src); + clk_enable(acpuclk_sources[tgt_s->src]); + } + + /* Perform the frequency switch */ + acpuclk_set_src(tgt_s); + drv_state.current_speed = tgt_s; + loops_per_jiffy = tgt_s->lpj; + + if (tgt_s->src == PLL_2 && strt_s->src == PLL_2) + clk_disable(acpuclk_sources[backup_s->src]); + + /* Nothing else to do for SWFI. */ + if (reason == SETRATE_SWFI) + goto out; + + /* Turn off previous PLL if not used. */ + if (strt_s->src != tgt_s->src && strt_s->src >= 0) { + pr_debug("Disabling PLL %d\n", strt_s->src); + clk_disable(acpuclk_sources[strt_s->src]); + } + + /* Decrease the AXI bus frequency if we can. */ + if (tgt_s->axi_clk_hz < strt_s->axi_clk_hz) { + res = clk_set_min_rate(drv_state.ebi1_clk, + tgt_s->axi_clk_hz); + if (res < 0) + pr_warning("Setting AXI min rate failed (%d)\n", res); + } + + /* Nothing else to do for power collapse. */ + if (reason == SETRATE_PC) + goto out; + + /* Drop VDD level if we can. */ + if (tgt_s->vdd_mv < strt_s->vdd_mv) { + res = acpuclk_set_acpu_vdd(tgt_s); + if (res) + pr_warning("ACPU VDD decrease to %d mV failed (%d)\n", + tgt_s->vdd_mv, res); + } + + pr_debug("ACPU speed change complete\n"); +out: + if (reason == SETRATE_CPUFREQ) + mutex_unlock(&drv_state.lock); + + return rc; +} + +unsigned long acpuclk_get_rate(int cpu) +{ + WARN_ONCE(drv_state.current_speed == NULL, + "acpuclk_get_rate: not initialized\n"); + if (drv_state.current_speed) + return drv_state.current_speed->acpu_clk_khz; + else + return 0; +} + +uint32_t acpuclk_get_switch_time(void) +{ + return drv_state.acpu_switch_time_us; +} + +/*---------------------------------------------------------------------------- + * Clock driver initialization + *---------------------------------------------------------------------------*/ + +static void __init acpuclk_init(void) +{ + struct clkctl_acpu_speed *s; + uint32_t div, sel, src_num; + uint32_t reg_clksel, reg_clkctl; + int res; + u8 pll2_l = readl_relaxed(PLL2_L_VAL_ADDR) & 0xFF; + + drv_state.ebi1_clk = clk_get(NULL, "ebi1_clk"); + BUG_ON(IS_ERR(drv_state.ebi1_clk)); + + reg_clksel = readl_relaxed(SCSS_CLK_SEL_ADDR); + + /* Determine the ACPU clock rate. */ + switch ((reg_clksel >> 1) & 0x3) { + case 0: /* Running off the output of the raw clock source mux. */ + reg_clkctl = readl_relaxed(SCSS_CLK_CTL_ADDR); + src_num = reg_clksel & 0x1; + sel = (reg_clkctl >> (12 - (8 * src_num))) & 0x7; + div = (reg_clkctl >> (8 - (8 * src_num))) & 0xF; + + /* Check frequency table for matching sel/div pair. */ + for (s = acpu_freq_tbl; s->acpu_clk_khz != 0; s++) { + if (s->acpu_src_sel == sel && s->acpu_src_div == div) + break; + } + if (s->acpu_clk_khz == 0) { + pr_err("Error - ACPU clock reports invalid speed\n"); + return; + } + break; + case 2: /* Running off of the SCPLL selected through the core mux. */ + /* Switch to run off of the SCPLL selected through the raw + * clock source mux. */ + for (s = acpu_freq_tbl; s->acpu_clk_khz != 0 + && s->src != PLL_2 && s->acpu_src_div == 0; s++) + ; + if (s->acpu_clk_khz != 0) { + /* Program raw clock source mux. */ + acpuclk_set_src(s); + + /* Switch to raw clock source input of the core mux. */ + reg_clksel = readl_relaxed(SCSS_CLK_SEL_ADDR); + reg_clksel &= ~(0x3 << 1); + writel_relaxed(reg_clksel, SCSS_CLK_SEL_ADDR); + break; + } + /* else fall through */ + default: + pr_err("Error - ACPU clock reports invalid source\n"); + return; + } + + /* Look at PLL2's L val to determine what speed PLL2 is running at */ + if (s->src == PLL_2) + for ( ; s->acpu_clk_khz; s++) + if (s->pll_rate && s->pll_rate->l == pll2_l) + break; + + /* Set initial ACPU VDD. */ + acpuclk_set_acpu_vdd(s); + + drv_state.current_speed = s; + + /* Initialize current PLL's reference count. */ + if (s->src >= 0) + clk_enable(acpuclk_sources[s->src]); + + res = clk_set_min_rate(drv_state.ebi1_clk, s->axi_clk_hz); + if (res < 0) + pr_warning("Setting AXI min rate failed!\n"); + + pr_info("ACPU running at %d KHz\n", s->acpu_clk_khz); + + return; +} + +/* Initalize the lpj field in the acpu_freq_tbl. */ +static void __init lpj_init(void) +{ + int i; + const struct clkctl_acpu_speed *base_clk = drv_state.current_speed; + + for (i = 0; acpu_freq_tbl[i].acpu_clk_khz; i++) { + acpu_freq_tbl[i].lpj = cpufreq_scale(loops_per_jiffy, + base_clk->acpu_clk_khz, + acpu_freq_tbl[i].acpu_clk_khz); + } +} + +#ifdef CONFIG_CPU_FREQ_MSM +static struct cpufreq_frequency_table cpufreq_tbl[ARRAY_SIZE(acpu_freq_tbl)]; + +static void setup_cpufreq_table(void) +{ + unsigned i = 0; + const struct clkctl_acpu_speed *speed; + + for (speed = acpu_freq_tbl; speed->acpu_clk_khz; speed++) + if (speed->use_for_scaling) { + cpufreq_tbl[i].index = i; + cpufreq_tbl[i].frequency = speed->acpu_clk_khz; + i++; + } + cpufreq_tbl[i].frequency = CPUFREQ_TABLE_END; + + cpufreq_frequency_table_get_attr(cpufreq_tbl, smp_processor_id()); +} +#else +static inline void setup_cpufreq_table(void) { } +#endif + +/* + * Truncate the frequency table at the current PLL2 rate and determine the + * backup PLL to use when scaling PLL2. + */ +void __init pll2_fixup(void) +{ + struct clkctl_acpu_speed *speed = acpu_freq_tbl; + u8 pll2_l = readl_relaxed(PLL2_L_VAL_ADDR) & 0xFF; + + for ( ; speed->acpu_clk_khz; speed++) { + if (speed->src != PLL_2) + backup_s = speed; + if (speed->pll_rate && speed->pll_rate->l == pll2_l) { + speed++; + speed->acpu_clk_khz = 0; + return; + } + } + + pr_err("Unknown PLL2 lval %d\n", pll2_l); + BUG(); +} + +#define RPM_BYPASS_MASK (1 << 3) +#define PMIC_MODE_MASK (1 << 4) + +static void __init populate_plls(void) +{ + acpuclk_sources[PLL_1] = clk_get_sys("acpu", "pll1_clk"); + BUG_ON(IS_ERR(acpuclk_sources[PLL_1])); + acpuclk_sources[PLL_2] = clk_get_sys("acpu", "pll2_clk"); + BUG_ON(IS_ERR(acpuclk_sources[PLL_2])); + acpuclk_sources[PLL_3] = clk_get_sys("acpu", "pll3_clk"); + BUG_ON(IS_ERR(acpuclk_sources[PLL_3])); +} + +void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata) +{ + pr_info("acpu_clock_init()\n"); + + mutex_init(&drv_state.lock); + drv_state.acpu_switch_time_us = clkdata->acpu_switch_time_us; + drv_state.vdd_switch_time_us = clkdata->vdd_switch_time_us; + pll2_fixup(); + populate_plls(); + acpuclk_init(); + lpj_init(); + setup_cpufreq_table(); +} diff --git a/arch/arm/mach-msm/acpuclock-8960.c b/arch/arm/mach-msm/acpuclock-8960.c new file mode 100644 index 00000000000..dab55fc4bac --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-8960.c @@ -0,0 +1,969 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "acpuclock.h" + +/* + * Source IDs. + * These must be negative to not overlap with the source IDs + * used by the 8x60 local clock driver. + */ +#define PLL_8 0 +#define HFPLL -1 +#define QSB -2 + +/* Mux source selects. */ +#define PRI_SRC_SEL_SEC_SRC 0 +#define PRI_SRC_SEL_HFPLL 1 +#define PRI_SRC_SEL_HFPLL_DIV2 2 +#define SEC_SRC_SEL_QSB 0 + +/* HFPLL registers offsets. */ +#define HFPLL_MODE 0x00 +#define HFPLL_CONFIG_CTL 0x04 +#define HFPLL_L_VAL 0x08 +#define HFPLL_M_VAL 0x0C +#define HFPLL_N_VAL 0x10 +#define HFPLL_DROOP_CTL 0x14 + +/* CP15 L2 indirect addresses. */ +#define L2CPMR_IADDR 0x500 +#define L2CPUCPMR_IADDR 0x501 + +#define STBY_KHZ 1 + +#define HFPLL_NOMINAL_VDD 1050000 +#define HFPLL_LOW_VDD 1050000 +#define HFPLL_LOW_VDD_PLL_L_MAX 0x28 + +#define SECCLKAGD BIT(4) + +enum scalables { + CPU0 = 0, + CPU1, + L2, + NUM_SCALABLES +}; + +enum vregs { + VREG_CORE, + VREG_MEM, + VREG_DIG, + NUM_VREG +}; + +struct vreg { + const char name[15]; + const unsigned int max_vdd; + const int rpm_vreg_voter; + const int rpm_vreg_id; + struct regulator *reg; + unsigned int cur_vdd; +}; + +struct core_speed { + unsigned int khz; + int src; + unsigned int pri_src_sel; + unsigned int sec_src_sel; + unsigned int pll_l_val; +}; + +struct l2_level { + struct core_speed speed; + unsigned int vdd_dig; + unsigned int vdd_mem; + unsigned int bw_level; +}; + +struct acpu_level { + unsigned int use_for_scaling; + struct core_speed speed; + struct l2_level *l2_level; + unsigned int vdd_core; +}; + +struct scalable { + void * __iomem const hfpll_base; + void * __iomem const aux_clk_sel; + const uint32_t l2cpmr_iaddr; + struct core_speed *current_speed; + struct l2_level *l2_vote; + struct vreg vreg[NUM_VREG]; + bool first_set_call; +}; + +static struct scalable scalable[] = { + [CPU0] = { + .hfpll_base = MSM_HFPLL_BASE + 0x200, + .aux_clk_sel = MSM_ACC0_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait0", 1150000 }, + .vreg[VREG_MEM] = { "krait0_mem", 1150000, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8921_L24 }, + .vreg[VREG_DIG] = { "krait0_dig", 1150000, + RPM_VREG_VOTER1, + RPM_VREG_ID_PM8921_S3 }, + }, + [CPU1] = { + .hfpll_base = MSM_HFPLL_BASE + 0x300, + .aux_clk_sel = MSM_ACC1_BASE + 0x014, + .l2cpmr_iaddr = L2CPUCPMR_IADDR, + .vreg[VREG_CORE] = { "krait1", 1150000 }, + .vreg[VREG_MEM] = { "krait0_mem", 1150000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8921_L24 }, + .vreg[VREG_DIG] = { "krait0_dig", 1150000, + RPM_VREG_VOTER2, + RPM_VREG_ID_PM8921_S3 }, + }, + [L2] = { + .hfpll_base = MSM_HFPLL_BASE + 0x400, + .aux_clk_sel = MSM_APCS_GCC_BASE + 0x028, + .l2cpmr_iaddr = L2CPMR_IADDR, + }, +}; + +struct mutex driver_lock; +static spinlock_t l2_lock; + +/* Instantaneous bandwidth requests in MB/s. */ +#define BW_MBPS(_bw) \ + { \ + .vectors = (struct msm_bus_vectors[]){ \ + {\ + .src = MSM_BUS_MASTER_AMPSS_M0, \ + .dst = MSM_BUS_SLAVE_EBI_CH0, \ + .ib = (_bw) * 1000000UL, \ + .ab = (_bw) * 100000UL, \ + }, \ + { \ + .src = MSM_BUS_MASTER_AMPSS_M1, \ + .dst = MSM_BUS_SLAVE_EBI_CH0, \ + .ib = (_bw) * 1000000UL, \ + .ab = (_bw) * 100000UL, \ + }, \ + }, \ + .num_paths = 2, \ + } +static struct msm_bus_paths bw_level_tbl[] = { + [0] = BW_MBPS(616), /* At least 77 MHz on bus. */ + [1] = BW_MBPS(1024), /* At least 128 MHz on bus. */ + [2] = BW_MBPS(1536), /* At least 192 MHz on bus. */ + [3] = BW_MBPS(2048), /* At least 256 MHz on bus. */ + [4] = BW_MBPS(3080), /* At least 385 MHz on bus. */ + [5] = BW_MBPS(3968), /* At least 496 MHz on bus. */ +}; + +static struct msm_bus_scale_pdata bus_client_pdata = { + .usecase = bw_level_tbl, + .num_usecases = ARRAY_SIZE(bw_level_tbl), + .active_only = 1, + .name = "acpuclock", +}; + +static uint32_t bus_perf_client; + +/* TODO: Update vdd_dig and vdd_mem when voltage data is available. */ +#define L2(x) (&l2_freq_tbl[(x)]) +#define L2_BOOT_IDX 11 +static struct l2_level l2_freq_tbl[] = { + [0] = { {STBY_KHZ, QSB, 0, 0, 0x00 }, 1050000, 1050000, 0 }, + [1] = { { 384000, PLL_8, 0, 2, 0x00 }, 1050000, 1050000, 0 }, + [2] = { { 432000, HFPLL, 2, 0, 0x20 }, 1050000, 1050000, 1 }, + [3] = { { 486000, HFPLL, 2, 0, 0x24 }, 1050000, 1050000, 1 }, + [4] = { { 540000, HFPLL, 2, 0, 0x28 }, 1050000, 1050000, 1 }, + [5] = { { 594000, HFPLL, 1, 0, 0x16 }, 1050000, 1050000, 2 }, + [6] = { { 648000, HFPLL, 1, 0, 0x18 }, 1050000, 1050000, 2 }, + [7] = { { 702000, HFPLL, 1, 0, 0x1A }, 1050000, 1050000, 2 }, + [8] = { { 756000, HFPLL, 1, 0, 0x1C }, 1150000, 1150000, 3 }, + [9] = { { 810000, HFPLL, 1, 0, 0x1E }, 1150000, 1150000, 3 }, + [10] = { { 864000, HFPLL, 1, 0, 0x20 }, 1150000, 1150000, 3 }, + [11] = { { 918000, HFPLL, 1, 0, 0x22 }, 1150000, 1150000, 3 }, + [12] = { { 972000, HFPLL, 1, 0, 0x24 }, 1150000, 1150000, 3 }, + [13] = { { 1026000, HFPLL, 1, 0, 0x26 }, 1150000, 1150000, 3 }, + [14] = { { 1080000, HFPLL, 1, 0, 0x28 }, 1150000, 1150000, 4 }, + [15] = { { 1134000, HFPLL, 1, 0, 0x2A }, 1150000, 1150000, 4 }, + [16] = { { 1188000, HFPLL, 1, 0, 0x2C }, 1150000, 1150000, 4 }, + [17] = { { 1242000, HFPLL, 1, 0, 0x2E }, 1150000, 1150000, 4 }, + [18] = { { 1296000, HFPLL, 1, 0, 0x30 }, 1150000, 1150000, 4 }, + [19] = { { 1350000, HFPLL, 1, 0, 0x32 }, 1150000, 1150000, 4 }, + [20] = { { 1404000, HFPLL, 1, 0, 0x34 }, 1150000, 1150000, 4 }, + [21] = { { 1458000, HFPLL, 1, 0, 0x36 }, 1150000, 1150000, 5 }, + [22] = { { 1512000, HFPLL, 1, 0, 0x38 }, 1150000, 1150000, 5 }, + [23] = { { 1566000, HFPLL, 1, 0, 0x3A }, 1150000, 1150000, 5 }, + [24] = { { 1620000, HFPLL, 1, 0, 0x3C }, 1150000, 1150000, 5 }, + [25] = { { 1674000, HFPLL, 1, 0, 0x3E }, 1150000, 1150000, 5 }, +}; + +/* TODO: Update core voltages when data is available. */ +#define CPU_BOOT_IDX 11 +static struct acpu_level acpu_freq_tbl[] = { + { 0, {STBY_KHZ, QSB, 0, 0, 0x00 }, L2(0), 1050000 }, + { 1, { 384000, PLL_8, 0, 2, 0x00 }, L2(1), 1050000 }, + { 1, { 432000, HFPLL, 2, 0, 0x20 }, L2(2), 1050000 }, + { 1, { 486000, HFPLL, 2, 0, 0x24 }, L2(3), 1050000 }, + { 1, { 540000, HFPLL, 2, 0, 0x28 }, L2(4), 1050000 }, + { 1, { 594000, HFPLL, 1, 0, 0x16 }, L2(5), 1050000 }, + { 1, { 648000, HFPLL, 1, 0, 0x18 }, L2(6), 1050000 }, + { 1, { 702000, HFPLL, 1, 0, 0x1A }, L2(7), 1050000 }, + { 1, { 756000, HFPLL, 1, 0, 0x1C }, L2(8), 1150000 }, + { 1, { 810000, HFPLL, 1, 0, 0x1E }, L2(9), 1150000 }, + { 1, { 864000, HFPLL, 1, 0, 0x20 }, L2(10), 1150000 }, + { 1, { 918000, HFPLL, 1, 0, 0x22 }, L2(11), 1150000 }, + { 0, { 0 } } +}; + +unsigned long acpuclk_get_rate(int cpu) +{ + return scalable[cpu].current_speed->khz; +} + +uint32_t acpuclk_get_switch_time(void) +{ + return 0; +} + +unsigned long acpuclk_power_collapse(void) +{ + int ret = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), STBY_KHZ, SETRATE_PC); + return ret; +} + +unsigned long acpuclk_wait_for_irq(void) +{ + int ret = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), STBY_KHZ, SETRATE_SWFI); + return ret; +} + +/* Read an 'indirectly' addressed L2 CP15 register. */ +static uint32_t readl_cp15_l2ind(uint32_t addr) +{ + uint32_t regval; + + /* + * TODO: CP15 registers are not emulated on RUMI3. + * Remove this check if/when they are. + */ + if (machine_is_msm8960_rumi3()) + return 0; + + asm volatile ("mcr p15, 3, %[l2cpsler], c15, c0, 6\n\t" + "mrc p15, 3, %[l2cpdr], c15, c0, 7\n\t" + : [l2cpdr]"=r" (regval) + : [l2cpsler]"r" (addr) + : "cc" + ); + return regval; +} + +/* Write an 'indirectly' addressed L2 CP15 register. */ +static void writel_cp15_l2ind(uint32_t regval, uint32_t addr) +{ + /* + * TODO: CP15 registers are not emulated on RUMI3. + * Remove this check if/when they are. + */ + if (machine_is_msm8960_rumi3()) + return; + + mb(); + asm volatile ("mcr p15, 3, %[l2cpsler], c15, c0, 6\n\t" + "mcr p15, 3, %[l2cpdr], c15, c0, 7\n\t" + : + : [l2cpsler]"r" (addr), [l2cpdr]"r" (regval) + : "cc" + ); + isb(); +} + +/* Get the selected source on primary MUX. */ +static int get_pri_clk_src(struct scalable *sc) +{ + uint32_t regval; + + regval = readl_cp15_l2ind(sc->l2cpmr_iaddr); + return regval & 0x3; +} + +/* Set the selected source on primary MUX. */ +static void set_pri_clk_src(struct scalable *sc, uint32_t pri_src_sel) +{ + uint32_t regval; + + regval = readl_cp15_l2ind(sc->l2cpmr_iaddr); + regval &= ~0x3; + regval |= (pri_src_sel & 0x3); + writel_cp15_l2ind(regval, sc->l2cpmr_iaddr); + /* Wait for switch to complete. */ + mb(); + udelay(1); +} + +/* Get the selected source on secondary MUX. */ +static int get_sec_clk_src(struct scalable *sc) +{ + uint32_t regval; + + regval = readl_cp15_l2ind(sc->l2cpmr_iaddr); + return (regval >> 2) & 0x3; +} + +/* Set the selected source on secondary MUX. */ +static void set_sec_clk_src(struct scalable *sc, uint32_t sec_src_sel) +{ + uint32_t regval; + + /* Disable secondary source clock gating during switch. */ + regval = readl_cp15_l2ind(sc->l2cpmr_iaddr); + regval |= SECCLKAGD; + writel_cp15_l2ind(regval, sc->l2cpmr_iaddr); + + /* Program the MUX. */ + regval &= ~(0x3 << 2); + regval |= ((sec_src_sel & 0x3) << 2); + writel_cp15_l2ind(regval, sc->l2cpmr_iaddr); + + /* Wait for switch to complete. */ + mb(); + udelay(1); + + /* Re-enable secondary source clock gating. */ + regval &= ~SECCLKAGD; + writel_cp15_l2ind(regval, sc->l2cpmr_iaddr); +} + +/* Enable an already-configured HFPLL. */ +static void hfpll_enable(struct scalable *sc) +{ + /* Disable PLL bypass mode. */ + writel_relaxed(0x2, sc->hfpll_base + HFPLL_MODE); + + /* + * H/W requires a 5us delay between disabling the bypass and + * de-asserting the reset. Delay 10us just to be safe. + */ + mb(); + udelay(10); + + /* De-assert active-low PLL reset. */ + writel_relaxed(0x6, sc->hfpll_base + HFPLL_MODE); + + /* Wait for PLL to lock. */ + mb(); + udelay(60); + + /* Enable PLL output. */ + writel_relaxed(0x7, sc->hfpll_base + HFPLL_MODE); +} + +/* Disable a HFPLL for power-savings or while its being reprogrammed. */ +static void hfpll_disable(struct scalable *sc) +{ + /* + * Disable the PLL output, disable test mode, enable + * the bypass mode, and assert the reset. + */ + writel_relaxed(0, sc->hfpll_base + HFPLL_MODE); +} + +/* Program the HFPLL rate. Assumes HFPLL is already disabled. */ +static void hfpll_set_rate(struct scalable *sc, struct core_speed *tgt_s) +{ + writel_relaxed(tgt_s->pll_l_val, sc->hfpll_base + HFPLL_L_VAL); +} + +/* Return the L2 speed that should be applied. */ +static struct l2_level *compute_l2_level(struct scalable *sc, + struct l2_level *vote_l) +{ + struct l2_level *new_l; + int cpu; + + /* Bounds check. */ + BUG_ON(vote_l >= (l2_freq_tbl + ARRAY_SIZE(l2_freq_tbl))); + + /* Find max L2 speed vote. */ + sc->l2_vote = vote_l; + new_l = l2_freq_tbl; + for_each_present_cpu(cpu) + new_l = max(new_l, scalable[cpu].l2_vote); + + return new_l; +} + +/* Update the bus bandwidth request. */ +static void set_bus_bw(unsigned int bw) +{ + int ret; + + /* Bounds check. */ + if (bw >= ARRAY_SIZE(bw_level_tbl)) { + pr_err("invalid bandwidth request (%d)\n", bw); + return; + } + + /* Update bandwidth if request has changed. This may sleep. */ + ret = msm_bus_scale_client_update_request(bus_perf_client, bw); + if (ret) + pr_err("bandwidth request failed (%d)\n", ret); +} + +/* Set the CPU or L2 clock speed. */ +static void set_speed(struct scalable *sc, struct core_speed *tgt_s, + enum setrate_reason reason) +{ + struct core_speed *strt_s = sc->current_speed; + + if (tgt_s == strt_s) + return; + + if (strt_s->src == HFPLL && tgt_s->src == HFPLL) { + /* Move CPU to QSB source. */ + /* + * TODO: If using QSB here requires elevating voltages, + * consider using PLL8 instead. + */ + set_sec_clk_src(sc, SEC_SRC_SEL_QSB); + set_pri_clk_src(sc, PRI_SRC_SEL_SEC_SRC); + + /* Program CPU HFPLL. */ + hfpll_disable(sc); + hfpll_set_rate(sc, tgt_s); + hfpll_enable(sc); + + /* Move CPU to HFPLL source. */ + set_pri_clk_src(sc, tgt_s->pri_src_sel); + } else if (strt_s->src == HFPLL && tgt_s->src != HFPLL) { + /* TODO: Enable source. */ + /* + * If responding to CPU_DEAD we must be running on another + * CPU. Therefore, we can't access the downed CPU's CP15 + * clock MUX registers from here and can't change clock sources. + * Just turn off the PLL- since the CPU is down already, halting + * its clock should be safe. + */ + if (reason != SETRATE_HOTPLUG || sc == &scalable[L2]) { + set_sec_clk_src(sc, tgt_s->sec_src_sel); + set_pri_clk_src(sc, tgt_s->pri_src_sel); + } + hfpll_disable(sc); + } else if (strt_s->src != HFPLL && tgt_s->src == HFPLL) { + hfpll_set_rate(sc, tgt_s); + hfpll_enable(sc); + /* + * If responding to CPU_UP_PREPARE, we can't change CP15 + * registers for the CPU that's coming up since we're not + * running on that CPU. That's okay though, since the MUX + * source was not changed on the way down, either. + */ + if (reason != SETRATE_HOTPLUG || sc == &scalable[L2]) + set_pri_clk_src(sc, tgt_s->pri_src_sel); + /* TODO: Disable source. */ + } else { + /* TODO: Enable source. */ + if (reason != SETRATE_HOTPLUG || sc == &scalable[L2]) + set_sec_clk_src(sc, tgt_s->sec_src_sel); + /* TODO: Disable source. */ + } + + sc->current_speed = tgt_s; +} + +/* Apply any per-cpu voltage increases. */ +static int increase_vdd(int cpu, unsigned int vdd_core, unsigned int vdd_mem, + unsigned int vdd_dig, enum setrate_reason reason) +{ + struct scalable *sc = &scalable[cpu]; + int rc; + + /* + * Increase vdd_mem active-set before vdd_dig and vdd_core. + * vdd_mem should be >= both vdd_core and vdd_dig. + */ + if (vdd_mem > sc->vreg[VREG_MEM].cur_vdd) { + rc = rpm_vreg_set_voltage(sc->vreg[VREG_MEM].rpm_vreg_id, + sc->vreg[VREG_MEM].rpm_vreg_voter, vdd_mem, + sc->vreg[VREG_MEM].max_vdd, 0); + if (rc) { + pr_err("%s: vdd_mem (cpu%d) increase failed (%d)\n", + __func__, cpu, rc); + return rc; + } + sc->vreg[VREG_MEM].cur_vdd = vdd_mem; + } + + /* Increase vdd_dig active-set vote. */ + if (vdd_dig > sc->vreg[VREG_DIG].cur_vdd) { + rc = rpm_vreg_set_voltage(sc->vreg[VREG_DIG].rpm_vreg_id, + sc->vreg[VREG_DIG].rpm_vreg_voter, vdd_dig, + sc->vreg[VREG_DIG].max_vdd, 0); + if (rc) { + pr_err("%s: vdd_dig (cpu%d) increase failed (%d)\n", + __func__, cpu, rc); + return rc; + } + sc->vreg[VREG_DIG].cur_vdd = vdd_dig; + } + + /* + * Update per-CPU core voltage. Don't do this for the hotplug path for + * which it should already be correct. Attempting to set it is bad + * because we don't know what CPU we are running on at this point, but + * the CPU regulator API requires we call it from the affected CPU. + */ + if (vdd_core > sc->vreg[VREG_CORE].cur_vdd + && reason != SETRATE_HOTPLUG) { + rc = regulator_set_voltage(sc->vreg[VREG_CORE].reg, vdd_core, + sc->vreg[VREG_CORE].max_vdd); + if (rc) { + pr_err("%s: vdd_core (cpu%d) increase failed (%d)\n", + __func__, cpu, rc); + return rc; + } + sc->vreg[VREG_CORE].cur_vdd = vdd_core; + } + + return rc; +} + +/* Apply any per-cpu voltage decreases. */ +static void decrease_vdd(int cpu, unsigned int vdd_core, unsigned int vdd_mem, + unsigned int vdd_dig, enum setrate_reason reason) +{ + struct scalable *sc = &scalable[cpu]; + int ret; + + /* + * Update per-CPU core voltage. This must be called on the CPU + * that's being affected. Don't do this in the hotplug remove path, + * where the rail is off and we're executing on the other CPU. + */ + if (vdd_core < sc->vreg[VREG_CORE].cur_vdd + && reason != SETRATE_HOTPLUG) { + ret = regulator_set_voltage(sc->vreg[VREG_CORE].reg, vdd_core, + sc->vreg[VREG_CORE].max_vdd); + if (ret) { + pr_err("%s: vdd_core (cpu%d) decrease failed (%d)\n", + __func__, cpu, ret); + return; + } + sc->vreg[VREG_CORE].cur_vdd = vdd_core; + } + + /* Decrease vdd_dig active-set vote. */ + if (vdd_dig < sc->vreg[VREG_DIG].cur_vdd) { + ret = rpm_vreg_set_voltage(sc->vreg[VREG_DIG].rpm_vreg_id, + sc->vreg[VREG_DIG].rpm_vreg_voter, vdd_dig, + sc->vreg[VREG_DIG].max_vdd, 0); + if (ret) { + pr_err("%s: vdd_dig (cpu%d) decrease failed (%d)\n", + __func__, cpu, ret); + return; + } + sc->vreg[VREG_DIG].cur_vdd = vdd_dig; + } + + /* + * Decrease vdd_mem active-set after vdd_dig and vdd_core. + * vdd_mem should be >= both vdd_core and vdd_dig. + */ + if (vdd_mem < sc->vreg[VREG_MEM].cur_vdd) { + ret = rpm_vreg_set_voltage(sc->vreg[VREG_MEM].rpm_vreg_id, + sc->vreg[VREG_MEM].rpm_vreg_voter, vdd_mem, + sc->vreg[VREG_MEM].max_vdd, 0); + if (ret) { + pr_err("%s: vdd_mem (cpu%d) decrease failed (%d)\n", + __func__, cpu, ret); + return; + } + sc->vreg[VREG_MEM].cur_vdd = vdd_mem; + } +} + +static unsigned int calculate_vdd_mem(struct acpu_level *tgt) +{ + return max(tgt->vdd_core, tgt->l2_level->vdd_mem); +} + +static unsigned int calculate_vdd_dig(struct acpu_level *tgt) +{ + unsigned int pll_vdd_dig; + + if (tgt->l2_level->speed.pll_l_val > HFPLL_LOW_VDD_PLL_L_MAX) + pll_vdd_dig = HFPLL_NOMINAL_VDD; + else + pll_vdd_dig = HFPLL_LOW_VDD; + + return max(tgt->l2_level->vdd_dig, pll_vdd_dig); +} + +static unsigned int calculate_vdd_core(struct acpu_level *tgt) +{ + unsigned int pll_vdd_core; + + if (tgt->speed.pll_l_val > HFPLL_LOW_VDD_PLL_L_MAX) + pll_vdd_core = HFPLL_NOMINAL_VDD; + else + pll_vdd_core = HFPLL_LOW_VDD; + + return max(tgt->vdd_core, pll_vdd_core); +} + +/* Set the CPU's clock rate and adjust the L2 rate, if appropriate. */ +int acpuclk_set_rate(int cpu, unsigned long rate, enum setrate_reason reason) +{ + struct core_speed *strt_acpu_s, *tgt_acpu_s; + struct l2_level *tgt_l2_l; + struct acpu_level *tgt; + unsigned int vdd_mem, vdd_dig, vdd_core; + unsigned long flags; + int rc = 0; + + if (cpu > num_possible_cpus()) { + rc = -EINVAL; + goto out; + } + + if (reason == SETRATE_CPUFREQ || reason == SETRATE_HOTPLUG) + mutex_lock(&driver_lock); + + strt_acpu_s = scalable[cpu].current_speed; + + /* Return early if rate didn't change. */ + if (rate == strt_acpu_s->khz && scalable[cpu].first_set_call == false) + goto out; + + /* Find target frequency. */ + for (tgt = acpu_freq_tbl; tgt->speed.khz != 0; tgt++) { + if (tgt->speed.khz == rate) { + tgt_acpu_s = &tgt->speed; + break; + } + } + if (tgt->speed.khz == 0) { + rc = -EINVAL; + goto out; + } + + /* Calculate voltage requirements for the current CPU. */ + vdd_mem = calculate_vdd_mem(tgt); + vdd_dig = calculate_vdd_dig(tgt); + vdd_core = calculate_vdd_core(tgt); + + /* Increase VDD levels if needed. */ + if (reason == SETRATE_CPUFREQ || reason == SETRATE_HOTPLUG) { + rc = increase_vdd(cpu, vdd_core, vdd_mem, vdd_dig, reason); + if (rc) + goto out; + } + + pr_debug("Switching from ACPU%d rate %u KHz -> %u KHz\n", + cpu, strt_acpu_s->khz, tgt_acpu_s->khz); + + /* Set the CPU speed. */ + set_speed(&scalable[cpu], tgt_acpu_s, reason); + + /* + * Update the L2 vote and apply the rate change. A spinlock is + * necessary to ensure L2 rate is calulated and set atomically, + * even if acpuclk_set_rate() is called from an atomic context + * and the driver_lock mutex is not acquired. + */ + spin_lock_irqsave(&l2_lock, flags); + tgt_l2_l = compute_l2_level(&scalable[cpu], tgt->l2_level); + set_speed(&scalable[L2], &tgt_l2_l->speed, reason); + spin_unlock_irqrestore(&l2_lock, flags); + + /* Nothing else to do for power collapse or SWFI. */ + if (reason == SETRATE_PC || reason == SETRATE_SWFI) + goto out; + + /* Update bus bandwith request. */ + set_bus_bw(tgt_l2_l->bw_level); + + /* Drop VDD levels if we can. */ + decrease_vdd(cpu, vdd_core, vdd_mem, vdd_dig, reason); + + scalable[cpu].first_set_call = false; + pr_debug("ACPU%d speed change complete\n", cpu); + +out: + if (reason == SETRATE_CPUFREQ || reason == SETRATE_HOTPLUG) + mutex_unlock(&driver_lock); + return rc; +} + +/* Initialize a HFPLL at a given rate and enable it. */ +static void __init hfpll_init(struct scalable *sc, struct core_speed *tgt_s) +{ + pr_debug("Initializing HFPLL%d\n", sc - scalable); + + /* Disable the PLL for re-programming. */ + hfpll_disable(sc); + + /* Configure PLL parameters for integer mode. */ + writel_relaxed(0x7845C665, sc->hfpll_base + HFPLL_CONFIG_CTL); + writel_relaxed(0, sc->hfpll_base + HFPLL_M_VAL); + writel_relaxed(1, sc->hfpll_base + HFPLL_N_VAL); + + /* Program droop controller. */ + writel_relaxed(0x0108C000, sc->hfpll_base + HFPLL_DROOP_CTL); + + /* Set an initial rate and enable the PLL. */ + hfpll_set_rate(sc, tgt_s); + hfpll_enable(sc); +} + +/* Voltage regulator initialization. */ +static void __init regulator_init(void) +{ + int cpu, ret; + struct scalable *sc; + + for_each_possible_cpu(cpu) { + sc = &scalable[cpu]; + sc->vreg[VREG_CORE].reg = regulator_get(NULL, + sc->vreg[VREG_CORE].name); + if (IS_ERR(sc->vreg[VREG_CORE].reg)) { + pr_err("regulator_get(%s) failed (%ld)\n", + sc->vreg[VREG_CORE].name, + PTR_ERR(sc->vreg[VREG_CORE].reg)); + BUG(); + } + + ret = regulator_set_voltage(sc->vreg[VREG_CORE].reg, + sc->vreg[VREG_CORE].max_vdd, + sc->vreg[VREG_CORE].max_vdd); + if (ret) + pr_err("regulator_set_voltage(%s) failed" + " (%d)\n", sc->vreg[VREG_CORE].name, ret); + + ret = regulator_enable(sc->vreg[VREG_CORE].reg); + if (ret) + pr_err("regulator_enable(%s) failed (%d)\n", + sc->vreg[VREG_CORE].name, ret); + } +} + +#define INIT_QSB_ID 0 +#define INIT_HFPLL_ID 1 +/* Set initial rate for a given core. */ +static void __init init_clock_sources(struct scalable *sc, + struct core_speed *tgt_s) +{ + uint32_t pri_src, regval; + + /* + * If the HFPLL is in use, program AUX source for QSB, switch to it, + * re-initialize the HFPLL, and switch back to the HFPLL. Otherwise, + * the HFPLL is not in use, so we can switch directly to it. + */ + pri_src = get_pri_clk_src(scalable); + if (pri_src == PRI_SRC_SEL_HFPLL || pri_src == PRI_SRC_SEL_HFPLL_DIV2) { + set_sec_clk_src(sc, SEC_SRC_SEL_QSB); + set_pri_clk_src(sc, PRI_SRC_SEL_SEC_SRC); + } + hfpll_init(sc, tgt_s); + + /* Set PRI_SRC_SEL_HFPLL_DIV2 divider to div-2. */ + regval = readl_cp15_l2ind(sc->l2cpmr_iaddr); + regval &= ~(0x3 << 6); + writel_cp15_l2ind(regval, sc->l2cpmr_iaddr); + + /* Select PLL8 as AUX source input to the secondary MUX. */ + writel_relaxed(0x3, sc->aux_clk_sel); + + set_pri_clk_src(sc, tgt_s->pri_src_sel); + sc->current_speed = tgt_s; + + /* + * Set this flag so that the first call to acpuclk_set_rate() can drop + * voltages and set initial bus bandwidth requests. + */ + sc->first_set_call = true; +} + +/* Perform CPU0-specific setup. */ +int __init msm_acpu_clock_early_init(void) +{ + init_clock_sources(&scalable[L2], &l2_freq_tbl[L2_BOOT_IDX].speed); + init_clock_sources(&scalable[CPU0], &acpu_freq_tbl[CPU_BOOT_IDX].speed); + scalable[CPU0].l2_vote = &l2_freq_tbl[L2_BOOT_IDX]; + + return 0; +} +early_initcall(msm_acpu_clock_early_init); + +/* Perform CPU1-specific setup. */ +void __cpuinit acpuclock_secondary_init(void) +{ + static bool warm_boot; + + if (warm_boot) + return; + + init_clock_sources(&scalable[CPU1], &acpu_freq_tbl[CPU_BOOT_IDX].speed); + scalable[CPU1].l2_vote = &l2_freq_tbl[L2_BOOT_IDX]; + + /* Secondary CPU has booted, don't repeat for subsequent warm boots. */ + warm_boot = true; +} + +/* Register with bus driver. */ +static void __init bus_init(void) +{ + int ret; + + bus_perf_client = msm_bus_scale_register_client(&bus_client_pdata); + if (!bus_perf_client) { + pr_err("unable to register bus client\n"); + BUG(); + } + + ret = msm_bus_scale_client_update_request(bus_perf_client, + (ARRAY_SIZE(bw_level_tbl)-1)); + if (ret) + pr_err("initial bandwidth request failed (%d)\n", ret); +} + +#ifdef CONFIG_CPU_FREQ_MSM +static struct cpufreq_frequency_table freq_table[NR_CPUS][30]; + +static void __init cpufreq_table_init(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + int i, freq_cnt = 0; + /* Construct the freq_table tables from acpu_freq_tbl. */ + for (i = 0; acpu_freq_tbl[i].speed.khz != 0 + && freq_cnt < ARRAY_SIZE(*freq_table); i++) { + if (acpu_freq_tbl[i].use_for_scaling) { + freq_table[cpu][freq_cnt].index = freq_cnt; + freq_table[cpu][freq_cnt].frequency + = acpu_freq_tbl[i].speed.khz; + freq_cnt++; + } + } + /* freq_table not big enough to store all usable freqs. */ + BUG_ON(acpu_freq_tbl[i].speed.khz != 0); + + freq_table[cpu][freq_cnt].index = freq_cnt; + freq_table[cpu][freq_cnt].frequency = CPUFREQ_TABLE_END; + + pr_info("CPU%d: %d scaling frequencies supported.\n", + cpu, freq_cnt); + + /* Register table with CPUFreq. */ + cpufreq_frequency_table_get_attr(freq_table[cpu], cpu); + } +} +#else +static void __init cpufreq_table_init(void) {} +#endif + +#define HOT_UNPLUG_KHZ STBY_KHZ +static int __cpuinit acpuclock_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + static int prev_khz[NR_CPUS]; + static int prev_pri_src[NR_CPUS]; + static int prev_sec_src[NR_CPUS]; + int cpu = (int)hcpu; + uint32_t soc_platform_version = socinfo_get_platform_version(); + + switch (action) { + case CPU_DYING: + case CPU_DYING_FROZEN: + /* + * 8960 HW versions < 2.1 must set their primary and secondary + * mux source selections to QSB before L2 power collapse and + * restore it after. + */ + if (SOCINFO_VERSION_MAJOR(soc_platform_version) < 2 || + (SOCINFO_VERSION_MAJOR(soc_platform_version) == 2 && + SOCINFO_VERSION_MINOR(soc_platform_version) < 1)) { + prev_sec_src[cpu] = get_sec_clk_src(&scalable[cpu]); + prev_pri_src[cpu] = get_pri_clk_src(&scalable[cpu]); + set_sec_clk_src(&scalable[cpu], SEC_SRC_SEL_QSB); + set_pri_clk_src(&scalable[cpu], PRI_SRC_SEL_SEC_SRC); + } + break; + case CPU_DEAD: + case CPU_DEAD_FROZEN: + prev_khz[cpu] = acpuclk_get_rate(cpu); + /* Fall through. */ + case CPU_UP_CANCELED: + case CPU_UP_CANCELED_FROZEN: + acpuclk_set_rate(cpu, HOT_UNPLUG_KHZ, SETRATE_HOTPLUG); + break; + case CPU_UP_PREPARE: + case CPU_UP_PREPARE_FROZEN: + if (WARN_ON(!prev_khz[cpu])) + prev_khz[cpu] = acpu_freq_tbl->speed.khz; + acpuclk_set_rate(cpu, prev_khz[cpu], SETRATE_HOTPLUG); + break; + case CPU_STARTING: + case CPU_STARTING_FROZEN: + if (SOCINFO_VERSION_MAJOR(soc_platform_version) < 2 || + (SOCINFO_VERSION_MAJOR(soc_platform_version) == 2 && + SOCINFO_VERSION_MINOR(soc_platform_version) < 1)) { + set_sec_clk_src(&scalable[cpu], prev_sec_src[cpu]); + set_pri_clk_src(&scalable[cpu], prev_pri_src[cpu]); + } + break; + default: + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block __cpuinitdata acpuclock_cpu_notifier = { + .notifier_call = acpuclock_cpu_callback, +}; + +void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata) +{ + mutex_init(&driver_lock); + regulator_init(); + bus_init(); + cpufreq_table_init(); + register_hotcpu_notifier(&acpuclock_cpu_notifier); +} diff --git a/arch/arm/mach-msm/acpuclock-8x50.c b/arch/arm/mach-msm/acpuclock-8x50.c new file mode 100644 index 00000000000..1b2416a492c --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-8x50.c @@ -0,0 +1,723 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "acpuclock.h" +#include "avs.h" + +#define SHOT_SWITCH 4 +#define HOP_SWITCH 5 +#define SIMPLE_SLEW 6 +#define COMPLEX_SLEW 7 + +#define SPSS_CLK_CNTL_ADDR (MSM_CSR_BASE + 0x100) +#define SPSS_CLK_SEL_ADDR (MSM_CSR_BASE + 0x104) + +/* Scorpion PLL registers */ +#define SCPLL_CTL_ADDR (MSM_SCPLL_BASE + 0x4) +#define SCPLL_STATUS_ADDR (MSM_SCPLL_BASE + 0x18) +#define SCPLL_FSM_CTL_EXT_ADDR (MSM_SCPLL_BASE + 0x10) + +enum { + ACPU_PLL_TCXO = -1, + ACPU_PLL_0 = 0, + ACPU_PLL_1, + ACPU_PLL_2, + ACPU_PLL_3, + ACPU_PLL_END, +}; + +struct clkctl_acpu_speed { + unsigned int use_for_scaling; + unsigned int acpuclk_khz; + int pll; + unsigned int acpuclk_src_sel; + unsigned int acpuclk_src_div; + unsigned int ahbclk_khz; + unsigned int ahbclk_div; + unsigned int axiclk_khz; + unsigned int sc_core_src_sel_mask; + unsigned int sc_l_value; + int vdd; + unsigned long lpj; /* loops_per_jiffy */ +}; + +struct clkctl_acpu_speed acpu_freq_tbl_998[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 0, 0, 14000, 0, 0, 1000}, + { 0, 128000, ACPU_PLL_1, 1, 5, 0, 0, 14000, 2, 0, 1000}, + { 1, 245760, ACPU_PLL_0, 4, 0, 0, 0, 29000, 0, 0, 1000}, + /* Update AXI_S and PLL0_S macros if above row numbers change. */ + { 1, 384000, ACPU_PLL_3, 0, 0, 0, 0, 58000, 1, 0xA, 1000}, + { 0, 422400, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xB, 1000}, + { 0, 460800, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xC, 1000}, + { 0, 499200, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xD, 1050}, + { 0, 537600, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xE, 1050}, + { 1, 576000, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xF, 1050}, + { 0, 614400, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0x10, 1075}, + { 0, 652800, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0x11, 1100}, + { 0, 691200, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0x12, 1125}, + { 0, 729600, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0x13, 1150}, + { 1, 768000, ACPU_PLL_3, 0, 0, 0, 0, 128000, 1, 0x14, 1150}, + { 0, 806400, ACPU_PLL_3, 0, 0, 0, 0, 128000, 1, 0x15, 1175}, + { 0, 844800, ACPU_PLL_3, 0, 0, 0, 0, 128000, 1, 0x16, 1225}, + { 0, 883200, ACPU_PLL_3, 0, 0, 0, 0, 128000, 1, 0x17, 1250}, + { 0, 921600, ACPU_PLL_3, 0, 0, 0, 0, 128000, 1, 0x18, 1300}, + { 0, 960000, ACPU_PLL_3, 0, 0, 0, 0, 128000, 1, 0x19, 1300}, + { 1, 998400, ACPU_PLL_3, 0, 0, 0, 0, 128000, 1, 0x1A, 1300}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, +}; + +struct clkctl_acpu_speed acpu_freq_tbl_768[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 0, 0, 14000, 0, 0, 1000}, + { 0, 128000, ACPU_PLL_1, 1, 5, 0, 0, 14000, 2, 0, 1000}, + { 1, 245760, ACPU_PLL_0, 4, 0, 0, 0, 29000, 0, 0, 1000}, + /* Update AXI_S and PLL0_S macros if above row numbers change. */ + { 1, 384000, ACPU_PLL_3, 0, 0, 0, 0, 58000, 1, 0xA, 1075}, + { 0, 422400, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xB, 1100}, + { 0, 460800, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xC, 1125}, + { 0, 499200, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xD, 1150}, + { 0, 537600, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xE, 1150}, + { 1, 576000, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0xF, 1150}, + { 0, 614400, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0x10, 1175}, + { 0, 652800, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0x11, 1200}, + { 0, 691200, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0x12, 1225}, + { 0, 729600, ACPU_PLL_3, 0, 0, 0, 0, 117000, 1, 0x13, 1250}, + { 1, 768000, ACPU_PLL_3, 0, 0, 0, 0, 128000, 1, 0x14, 1250}, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, +}; + +static struct clkctl_acpu_speed *acpu_freq_tbl = acpu_freq_tbl_998; +#define AXI_S (&acpu_freq_tbl[1]) +#define PLL0_S (&acpu_freq_tbl[2]) + +/* Use 128MHz for PC since ACPU will auto-switch to AXI (128MHz) before + * coming back up. This allows detection of return-from-PC, since 128MHz + * is only used for power collapse. */ +#define POWER_COLLAPSE_KHZ (AXI_S->acpuclk_khz) +/* Use 245MHz (not 128MHz) for SWFI to avoid unnecessary steps between + * 128MHz<->245MHz. Jumping to high frequencies from 128MHz directly + * is not allowed. */ +#define WAIT_FOR_IRQ_KHZ (PLL0_S->acpuclk_khz) + +#ifdef CONFIG_CPU_FREQ_MSM +static struct cpufreq_frequency_table freq_table[20]; + +static void __init cpufreq_table_init(void) +{ + unsigned int i; + unsigned int freq_cnt = 0; + + /* Construct the freq_table table from acpu_freq_tbl since the + * freq_table values need to match frequencies specified in + * acpu_freq_tbl and acpu_freq_tbl needs to be fixed up during init. + */ + for (i = 0; acpu_freq_tbl[i].acpuclk_khz != 0 + && freq_cnt < ARRAY_SIZE(freq_table)-1; i++) { + if (acpu_freq_tbl[i].use_for_scaling) { + freq_table[freq_cnt].index = freq_cnt; + freq_table[freq_cnt].frequency + = acpu_freq_tbl[i].acpuclk_khz; + freq_cnt++; + } + } + + /* freq_table not big enough to store all usable freqs. */ + BUG_ON(acpu_freq_tbl[i].acpuclk_khz != 0); + + freq_table[freq_cnt].index = freq_cnt; + freq_table[freq_cnt].frequency = CPUFREQ_TABLE_END; + + pr_info("%d scaling frequencies supported.\n", freq_cnt); +} +#endif + +struct clock_state { + struct clkctl_acpu_speed *current_speed; + struct mutex lock; + uint32_t acpu_switch_time_us; + uint32_t max_speed_delta_khz; + uint32_t vdd_switch_time_us; + unsigned int max_vdd; + struct clk *ebi1_clk; + int (*acpu_set_vdd) (int mvolts); +}; + +static struct clock_state drv_state = { 0 }; + +static void scpll_set_freq(uint32_t lval, unsigned freq_switch) +{ + uint32_t regval; + + if (lval > 33) + lval = 33; + if (lval < 10) + lval = 10; + + /* wait for any calibrations or frequency switches to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x3) + ; + + /* write the new L val and switch mode */ + regval = readl(SCPLL_FSM_CTL_EXT_ADDR); + regval &= ~(0x3f << 3); + regval |= (lval << 3); + if (freq_switch == SIMPLE_SLEW) + regval |= (0x1 << 9); + + regval &= ~(0x3 << 0); + regval |= (freq_switch << 0); + writel(regval, SCPLL_FSM_CTL_EXT_ADDR); + + dmb(); + + /* put in normal mode */ + regval = readl(SCPLL_CTL_ADDR); + regval |= 0x7; + writel(regval, SCPLL_CTL_ADDR); + + dmb(); + + /* wait for frequency switch to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; + + /* status bit seems to clear early, using + * 100us to handle the worst case. */ + udelay(100); +} + +static void scpll_apps_enable(bool state) +{ + uint32_t regval; + + if (state) + pr_debug("Enabling PLL 3\n"); + else + pr_debug("Disabling PLL 3\n"); + + /* Wait for any frequency switches to finish. */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; + + /* put the pll in standby mode */ + regval = readl(SCPLL_CTL_ADDR); + regval &= ~(0x7); + regval |= (0x2); + writel(regval, SCPLL_CTL_ADDR); + + dmb(); + + if (state) { + /* put the pll in normal mode */ + regval = readl(SCPLL_CTL_ADDR); + regval |= (0x7); + writel(regval, SCPLL_CTL_ADDR); + udelay(200); + } else { + /* put the pll in power down mode */ + regval = readl(SCPLL_CTL_ADDR); + regval &= ~(0x7); + writel(regval, SCPLL_CTL_ADDR); + } + udelay(drv_state.vdd_switch_time_us); + + if (state) + pr_debug("PLL 3 Enabled\n"); + else + pr_debug("PLL 3 Disabled\n"); +} + +static void scpll_init(void) +{ + uint32_t regval; +#define L_VAL_384MHZ 0xA +#define L_VAL_768MHZ 0x14 + + pr_debug("Initializing PLL 3\n"); + + /* power down scpll */ + writel(0x0, SCPLL_CTL_ADDR); + + dmb(); + + /* set bypassnl, put into standby */ + writel(0x00400002, SCPLL_CTL_ADDR); + + /* set bypassnl, reset_n, full calibration */ + writel(0x00600004, SCPLL_CTL_ADDR); + + /* Ensure register write to initiate calibration has taken + effect before reading status flag */ + dmb(); + + /* wait for cal_all_done */ + while (readl(SCPLL_STATUS_ADDR) & 0x2) + ; + + /* Start: Set of experimentally derived steps + * to work around a h/w bug. */ + + /* Put the pll in normal mode */ + scpll_apps_enable(1); + + /* SHOT switch to 384 MHz */ + regval = readl(SCPLL_FSM_CTL_EXT_ADDR); + regval &= ~(0x3f << 3); + regval |= (L_VAL_384MHZ << 3); + + regval &= ~0x7; + regval |= SHOT_SWITCH; + writel(regval, SCPLL_FSM_CTL_EXT_ADDR); + + /* Trigger the freq switch by putting pll in normal mode. */ + regval = readl(SCPLL_CTL_ADDR); + regval |= (0x7); + writel(regval, SCPLL_CTL_ADDR); + + /* Wait for frequency switch to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; + + /* Status bit seems to clear early, using + * 800 microseconds for the worst case. */ + udelay(800); + + /* HOP switch to 768 MHz. */ + regval = readl(SCPLL_FSM_CTL_EXT_ADDR); + regval &= ~(0x3f << 3); + regval |= (L_VAL_768MHZ << 3); + + regval &= ~0x7; + regval |= HOP_SWITCH; + writel(regval, SCPLL_FSM_CTL_EXT_ADDR); + + /* Trigger the freq switch by putting pll in normal mode. */ + regval = readl(SCPLL_CTL_ADDR); + regval |= (0x7); + writel(regval, SCPLL_CTL_ADDR); + + /* Wait for frequency switch to finish */ + while (readl(SCPLL_STATUS_ADDR) & 0x1) + ; + + /* Status bit seems to clear early, using + * 100 microseconds for the worst case. */ + udelay(100); + + /* End: Work around for h/w bug */ + + /* Power down scpll */ + scpll_apps_enable(0); +} + +static void config_pll(struct clkctl_acpu_speed *s) +{ + uint32_t regval; + + if (s->pll == ACPU_PLL_3) + scpll_set_freq(s->sc_l_value, HOP_SWITCH); + /* Configure the PLL divider mux if we plan to use it. */ + else if (s->sc_core_src_sel_mask == 0) { + /* get the current clock source selection */ + regval = readl(SPSS_CLK_SEL_ADDR) & 0x1; + + /* configure the other clock source, then switch to it, + * using the glitch free mux */ + switch (regval) { + case 0x0: + regval = readl(SPSS_CLK_CNTL_ADDR); + regval &= ~(0x7 << 4 | 0xf); + regval |= (s->acpuclk_src_sel << 4); + regval |= (s->acpuclk_src_div << 0); + writel(regval, SPSS_CLK_CNTL_ADDR); + + regval = readl(SPSS_CLK_SEL_ADDR); + regval |= 0x1; + writel(regval, SPSS_CLK_SEL_ADDR); + break; + + case 0x1: + regval = readl(SPSS_CLK_CNTL_ADDR); + regval &= ~(0x7 << 12 | 0xf << 8); + regval |= (s->acpuclk_src_sel << 12); + regval |= (s->acpuclk_src_div << 8); + writel(regval, SPSS_CLK_CNTL_ADDR); + + regval = readl(SPSS_CLK_SEL_ADDR); + regval &= ~0x1; + writel(regval, SPSS_CLK_SEL_ADDR); + break; + } + dmb(); + } + + regval = readl(SPSS_CLK_SEL_ADDR); + regval &= ~(0x3 << 1); + regval |= (s->sc_core_src_sel_mask << 1); + writel(regval, SPSS_CLK_SEL_ADDR); +} + +static int acpuclk_set_vdd_level(int vdd) +{ + if (drv_state.acpu_set_vdd) { + pr_debug("Switching VDD to %d mV\n", vdd); + return drv_state.acpu_set_vdd(vdd); + } else { + /* Assume that the PMIC supports scaling the processor + * to its maximum frequency at its default voltage. + */ + return 0; + } +} + +int acpuclk_set_rate(int cpu, unsigned long rate, enum setrate_reason reason) +{ + struct clkctl_acpu_speed *tgt_s, *strt_s; + int res, rc = 0; + int freq_index = 0; + + if (reason == SETRATE_CPUFREQ) + mutex_lock(&drv_state.lock); + + strt_s = drv_state.current_speed; + + if (rate == strt_s->acpuclk_khz) + goto out; + + for (tgt_s = acpu_freq_tbl; tgt_s->acpuclk_khz != 0; tgt_s++) { + if (tgt_s->acpuclk_khz == rate) + break; + freq_index++; + } + + if (tgt_s->acpuclk_khz == 0) { + rc = -EINVAL; + goto out; + } + + if (reason == SETRATE_CPUFREQ) { +#ifdef CONFIG_MSM_CPU_AVS + /* Notify avs before changing frequency */ + rc = avs_adjust_freq(freq_index, 1); + if (rc) { + pr_err("Unable to increase ACPU vdd (%d)\n", rc); + goto out; + } +#endif + /* Increase VDD if needed. */ + if (tgt_s->vdd > strt_s->vdd) { + rc = acpuclk_set_vdd_level(tgt_s->vdd); + if (rc) { + pr_err("Unable to increase ACPU vdd (%d)\n", + rc); + goto out; + } + } + } else if (reason == SETRATE_PC + && rate != POWER_COLLAPSE_KHZ) { + /* Returning from PC. ACPU is running on AXI source. + * Step up to PLL0 before ramping up higher. */ + config_pll(PLL0_S); + } + + pr_debug("Switching from ACPU rate %u KHz -> %u KHz\n", + strt_s->acpuclk_khz, tgt_s->acpuclk_khz); + + if (strt_s->pll != ACPU_PLL_3 && tgt_s->pll != ACPU_PLL_3) { + config_pll(tgt_s); + } else if (strt_s->pll != ACPU_PLL_3 && tgt_s->pll == ACPU_PLL_3) { + scpll_apps_enable(1); + config_pll(tgt_s); + } else if (strt_s->pll == ACPU_PLL_3 && tgt_s->pll != ACPU_PLL_3) { + config_pll(tgt_s); + scpll_apps_enable(0); + } else { + /* Temporarily switch to PLL0 while reconfiguring PLL3. */ + config_pll(PLL0_S); + config_pll(tgt_s); + } + + /* Update the driver state with the new clock freq */ + drv_state.current_speed = tgt_s; + + /* Re-adjust lpj for the new clock speed. */ + loops_per_jiffy = tgt_s->lpj; + + /* Nothing else to do for SWFI. */ + if (reason == SETRATE_SWFI) + goto out; + + if (strt_s->axiclk_khz != tgt_s->axiclk_khz) { + res = clk_set_rate(drv_state.ebi1_clk, + tgt_s->axiclk_khz * 1000); + if (res < 0) + pr_warning("Setting AXI min rate failed (%d)\n", res); + } + + /* Nothing else to do for power collapse */ + if (reason == SETRATE_PC) + goto out; + +#ifdef CONFIG_MSM_CPU_AVS + /* notify avs after changing frequency */ + res = avs_adjust_freq(freq_index, 0); + if (res) + pr_warning("Unable to drop ACPU vdd (%d)\n", res); +#endif + + /* Drop VDD level if we can. */ + if (tgt_s->vdd < strt_s->vdd) { + res = acpuclk_set_vdd_level(tgt_s->vdd); + if (res) + pr_warning("Unable to drop ACPU vdd (%d)\n", res); + } + + pr_debug("ACPU speed change complete\n"); +out: + if (reason == SETRATE_CPUFREQ) + mutex_unlock(&drv_state.lock); + return rc; +} + +static void __init acpuclk_init(void) +{ + struct clkctl_acpu_speed *speed; + uint32_t div, sel, regval; + int res; + + /* Determine the source of the Scorpion clock. */ + regval = readl(SPSS_CLK_SEL_ADDR); + switch ((regval & 0x6) >> 1) { + case 0: /* raw source clock */ + case 3: /* low jitter PLL1 (768Mhz) */ + if (regval & 0x1) { + sel = ((readl(SPSS_CLK_CNTL_ADDR) >> 4) & 0x7); + div = ((readl(SPSS_CLK_CNTL_ADDR) >> 0) & 0xf); + } else { + sel = ((readl(SPSS_CLK_CNTL_ADDR) >> 12) & 0x7); + div = ((readl(SPSS_CLK_CNTL_ADDR) >> 8) & 0xf); + } + + /* Find the matching clock rate. */ + for (speed = acpu_freq_tbl; speed->acpuclk_khz != 0; speed++) { + if (speed->acpuclk_src_sel == sel && + speed->acpuclk_src_div == div) + break; + } + break; + + case 1: /* unbuffered scorpion pll (384Mhz to 998.4Mhz) */ + sel = ((readl(SCPLL_FSM_CTL_EXT_ADDR) >> 3) & 0x3f); + + /* Find the matching clock rate. */ + for (speed = acpu_freq_tbl; speed->acpuclk_khz != 0; speed++) { + if (speed->sc_l_value == sel && + speed->sc_core_src_sel_mask == 1) + break; + } + break; + + case 2: /* AXI bus clock (128Mhz) */ + speed = AXI_S; + break; + default: + BUG(); + } + + /* Initialize scpll only if it wasn't already initialized by the boot + * loader. If the CPU is already running on scpll, then the scpll was + * initialized by the boot loader. */ + if (speed->pll != ACPU_PLL_3) + scpll_init(); + + if (speed->acpuclk_khz == 0) { + pr_err("Error - ACPU clock reports invalid speed\n"); + return; + } + + drv_state.current_speed = speed; + res = clk_set_rate(drv_state.ebi1_clk, speed->axiclk_khz * 1000); + if (res < 0) + pr_warning("Setting AXI min rate failed (%d)\n", res); + res = clk_enable(drv_state.ebi1_clk); + if (res < 0) + pr_warning("Enabling AXI clock failed (%d)\n", res); + + pr_info("ACPU running at %d KHz\n", speed->acpuclk_khz); +} + +unsigned long acpuclk_get_rate(int cpu) +{ + return drv_state.current_speed->acpuclk_khz; +} + +uint32_t acpuclk_get_switch_time(void) +{ + return drv_state.acpu_switch_time_us; +} + +unsigned long acpuclk_power_collapse(void) +{ + int ret = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), POWER_COLLAPSE_KHZ, SETRATE_PC); + return ret; +} + +unsigned long acpuclk_wait_for_irq(void) +{ + int ret = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), WAIT_FOR_IRQ_KHZ, SETRATE_SWFI); + return ret; +} + +/* Spare register populated with efuse data on max ACPU freq. */ +#define CT_CSR_PHYS 0xA8700000 +#define TCSR_SPARE2_ADDR (ct_csr_base + 0x60) + +#define PLL0_M_VAL_ADDR (MSM_CLK_CTL_BASE + 0x308) + +static void __init acpu_freq_tbl_fixup(void) +{ + void __iomem *ct_csr_base; + uint32_t tcsr_spare2, pll0_m_val; + unsigned int max_acpu_khz; + unsigned int i; + + ct_csr_base = ioremap(CT_CSR_PHYS, PAGE_SIZE); + BUG_ON(ct_csr_base == NULL); + + tcsr_spare2 = readl(TCSR_SPARE2_ADDR); + + /* Check if the register is supported and meaningful. */ + if ((tcsr_spare2 & 0xF000) != 0xA000) { + pr_info("Efuse data on Max ACPU freq not present.\n"); + goto skip_efuse_fixup; + } + + switch (tcsr_spare2 & 0xF0) { + case 0x70: + acpu_freq_tbl = acpu_freq_tbl_768; + max_acpu_khz = 768000; + break; + case 0x30: + case 0x00: + max_acpu_khz = 998400; + break; + case 0x10: + max_acpu_khz = 1267200; + break; + default: + pr_warning("Invalid efuse data (%x) on Max ACPU freq!\n", + tcsr_spare2); + goto skip_efuse_fixup; + } + + pr_info("Max ACPU freq from efuse data is %d KHz\n", max_acpu_khz); + + for (i = 0; acpu_freq_tbl[i].acpuclk_khz != 0; i++) { + if (acpu_freq_tbl[i].acpuclk_khz > max_acpu_khz) { + acpu_freq_tbl[i].acpuclk_khz = 0; + break; + } + } + +skip_efuse_fixup: + iounmap(ct_csr_base); + BUG_ON(drv_state.max_vdd == 0); + + /* pll0_m_val will be 36 when PLL0 is run at 235MHz + * instead of the usual 245MHz. */ + pll0_m_val = readl(PLL0_M_VAL_ADDR) & 0x7FFFF; + if (pll0_m_val == 36) + PLL0_S->acpuclk_khz = 235930; + + for (i = 0; acpu_freq_tbl[i].acpuclk_khz != 0; i++) { + if (acpu_freq_tbl[i].vdd > drv_state.max_vdd) { + acpu_freq_tbl[i].acpuclk_khz = 0; + break; + } + } +} + +/* Initalize the lpj field in the acpu_freq_tbl. */ +static void __init lpj_init(void) +{ + int i; + const struct clkctl_acpu_speed *base_clk = drv_state.current_speed; + for (i = 0; acpu_freq_tbl[i].acpuclk_khz; i++) { + acpu_freq_tbl[i].lpj = cpufreq_scale(loops_per_jiffy, + base_clk->acpuclk_khz, + acpu_freq_tbl[i].acpuclk_khz); + } +} + +#ifdef CONFIG_MSM_CPU_AVS +static int __init acpu_avs_init(int (*set_vdd) (int), int khz) +{ + int i; + int freq_count = 0; + int freq_index = -1; + + for (i = 0; acpu_freq_tbl[i].acpuclk_khz; i++) { + freq_count++; + if (acpu_freq_tbl[i].acpuclk_khz == khz) + freq_index = i; + } + + return avs_init(set_vdd, freq_count, freq_index); +} +#endif + +void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata) +{ + mutex_init(&drv_state.lock); + drv_state.acpu_switch_time_us = clkdata->acpu_switch_time_us; + drv_state.max_speed_delta_khz = clkdata->max_speed_delta_khz; + drv_state.vdd_switch_time_us = clkdata->vdd_switch_time_us; + drv_state.max_vdd = clkdata->max_vdd; + drv_state.acpu_set_vdd = clkdata->acpu_set_vdd; + + drv_state.ebi1_clk = clk_get(NULL, "ebi1_acpu_clk"); + BUG_ON(IS_ERR(drv_state.ebi1_clk)); + + acpu_freq_tbl_fixup(); + acpuclk_init(); + lpj_init(); + /* Set a lower bound for ACPU rate for boot. This limits the + * maximum frequency hop caused by the first CPUFREQ switch. */ + if (drv_state.current_speed->acpuclk_khz < PLL0_S->acpuclk_khz) + acpuclk_set_rate(0, PLL0_S->acpuclk_khz, SETRATE_CPUFREQ); + +#ifdef CONFIG_CPU_FREQ_MSM + cpufreq_table_init(); + cpufreq_frequency_table_get_attr(freq_table, smp_processor_id()); +#endif +#ifdef CONFIG_MSM_CPU_AVS + if (!acpu_avs_init(drv_state.acpu_set_vdd, + drv_state.current_speed->acpuclk_khz)) { + /* avs init successful. avs will handle voltage changes */ + drv_state.acpu_set_vdd = NULL; + } +#endif +} diff --git a/arch/arm/mach-msm/acpuclock-8x60.c b/arch/arm/mach-msm/acpuclock-8x60.c new file mode 100644 index 00000000000..d840865c70e --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-8x60.c @@ -0,0 +1,965 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "acpuclock.h" +#include "avs.h" + +/* Frequency switch modes. */ +#define SHOT_SWITCH 4 +#define HOP_SWITCH 5 +#define SIMPLE_SLEW 6 +#define COMPLEX_SLEW 7 + +/* PLL calibration limits. + * The PLL hardware is capable of 384MHz to 1536MHz. The L_VALs + * used for calibration should respect these limits. */ +#define L_VAL_SCPLL_CAL_MIN 0x08 /* = 432 MHz with 27MHz source */ +#define L_VAL_SCPLL_CAL_MAX 0x1C /* = 1512 MHz with 27MHz source */ + +#define MAX_VDD_SC 1250000 /* uV */ +#define MAX_VDD_MEM 1250000 /* uV */ +#define MAX_VDD_DIG 1200000 /* uV */ +#define MAX_AXI 310500 /* KHz */ +#define SCPLL_LOW_VDD_FMAX 594000 /* KHz */ +#define SCPLL_LOW_VDD 1000000 /* uV */ +#define SCPLL_NOMINAL_VDD 1100000 /* uV */ + +/* SCPLL Modes. */ +#define SCPLL_POWER_DOWN 0 +#define SCPLL_BYPASS 1 +#define SCPLL_STANDBY 2 +#define SCPLL_FULL_CAL 4 +#define SCPLL_HALF_CAL 5 +#define SCPLL_STEP_CAL 6 +#define SCPLL_NORMAL 7 + +#define SCPLL_DEBUG_NONE 0 +#define SCPLL_DEBUG_FULL 3 + +/* SCPLL registers offsets. */ +#define SCPLL_DEBUG_OFFSET 0x0 +#define SCPLL_CTL_OFFSET 0x4 +#define SCPLL_CAL_OFFSET 0x8 +#define SCPLL_STATUS_OFFSET 0x10 +#define SCPLL_CFG_OFFSET 0x1C +#define SCPLL_FSM_CTL_EXT_OFFSET 0x24 +#define SCPLL_LUT_A_HW_MAX (0x38 + ((L_VAL_SCPLL_CAL_MAX / 4) * 4)) + +/* Clock registers. */ +#define SPSS0_CLK_CTL_ADDR (MSM_ACC0_BASE + 0x04) +#define SPSS0_CLK_SEL_ADDR (MSM_ACC0_BASE + 0x08) +#define SPSS1_CLK_CTL_ADDR (MSM_ACC1_BASE + 0x04) +#define SPSS1_CLK_SEL_ADDR (MSM_ACC1_BASE + 0x08) +#define SPSS_L2_CLK_SEL_ADDR (MSM_GCC_BASE + 0x38) + +/* PTE EFUSE register. */ +#define QFPROM_PTE_EFUSE_ADDR (MSM_QFPROM_BASE + 0x00C0) + +static const void * const clk_ctl_addr[] = {SPSS0_CLK_CTL_ADDR, + SPSS1_CLK_CTL_ADDR}; +static const void * const clk_sel_addr[] = {SPSS0_CLK_SEL_ADDR, + SPSS1_CLK_SEL_ADDR, SPSS_L2_CLK_SEL_ADDR}; + +static const int rpm_vreg_voter[] = { RPM_VREG_VOTER1, RPM_VREG_VOTER2 }; +static struct regulator *regulator_sc[NR_CPUS]; + +enum scplls { + CPU0 = 0, + CPU1, + L2, +}; + +static const void * const sc_pll_base[] = { + [CPU0] = MSM_SCPLL_BASE + 0x200, + [CPU1] = MSM_SCPLL_BASE + 0x300, + [L2] = MSM_SCPLL_BASE + 0x400, +}; + +enum sc_src { + ACPU_AFAB, + ACPU_PLL_8, + ACPU_SCPLL, +}; + +static struct clock_state { + struct clkctl_acpu_speed *current_speed[NR_CPUS]; + struct clkctl_l2_speed *current_l2_speed; + spinlock_t l2_lock; + struct mutex lock; + uint32_t acpu_switch_time_us; + uint32_t vdd_switch_time_us; + uint32_t max_speed_delta_khz; +} drv_state; + +struct clkctl_l2_speed { + unsigned int khz; + unsigned int src_sel; + unsigned int l_val; + unsigned int vdd_dig; + unsigned int vdd_mem; + unsigned int bw_level; +}; + +static struct clkctl_l2_speed *l2_vote[NR_CPUS]; + +struct clkctl_acpu_speed { + unsigned int use_for_scaling[2]; /* One for each CPU. */ + unsigned int acpuclk_khz; + int pll; + unsigned int acpuclk_src_sel; + unsigned int acpuclk_src_div; + unsigned int core_src_sel; + unsigned int l_val; + struct clkctl_l2_speed *l2_level; + unsigned int vdd_sc; + unsigned int avsdscr_setting; +}; + +/* Instantaneous bandwidth requests in MB/s. */ +#define BW_MBPS(_bw) \ + { \ + .vectors = &(struct msm_bus_vectors){ \ + .src = MSM_BUS_MASTER_AMPSS_M0, \ + .dst = MSM_BUS_SLAVE_EBI_CH0, \ + .ib = (_bw) * 1000000UL, \ + .ab = 0, \ + }, \ + .num_paths = 1, \ + } +static struct msm_bus_paths bw_level_tbl[] = { + [0] = BW_MBPS(824), /* At least 103 MHz on bus. */ + [1] = BW_MBPS(1336), /* At least 167 MHz on bus. */ + [2] = BW_MBPS(2008), /* At least 251 MHz on bus. */ + [3] = BW_MBPS(2480), /* At least 310 MHz on bus. */ +}; + +static struct msm_bus_scale_pdata bus_client_pdata = { + .usecase = bw_level_tbl, + .num_usecases = ARRAY_SIZE(bw_level_tbl), + .active_only = 1, + .name = "acpuclock", +}; + +static uint32_t bus_perf_client; + +/* L2 frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_l2_speed l2_freq_tbl_v2[] = { + [0] = { MAX_AXI, 0, 0, 1000000, 1100000, 0}, + [1] = { 432000, 1, 0x08, 1000000, 1100000, 0}, + [2] = { 486000, 1, 0x09, 1000000, 1100000, 0}, + [3] = { 540000, 1, 0x0A, 1000000, 1100000, 0}, + [4] = { 594000, 1, 0x0B, 1000000, 1100000, 0}, + [5] = { 648000, 1, 0x0C, 1000000, 1100000, 1}, + [6] = { 702000, 1, 0x0D, 1100000, 1100000, 1}, + [7] = { 756000, 1, 0x0E, 1100000, 1100000, 1}, + [8] = { 810000, 1, 0x0F, 1100000, 1100000, 1}, + [9] = { 864000, 1, 0x10, 1100000, 1100000, 1}, + [10] = { 918000, 1, 0x11, 1100000, 1100000, 2}, + [11] = { 972000, 1, 0x12, 1100000, 1100000, 2}, + [12] = {1026000, 1, 0x13, 1100000, 1100000, 2}, + [13] = {1080000, 1, 0x14, 1100000, 1200000, 2}, + [14] = {1134000, 1, 0x15, 1100000, 1200000, 2}, + [15] = {1188000, 1, 0x16, 1200000, 1200000, 3}, + [16] = {1242000, 1, 0x17, 1200000, 1212500, 3}, + [17] = {1296000, 1, 0x18, 1200000, 1225000, 3}, + [18] = {1350000, 1, 0x19, 1200000, 1225000, 3}, + [19] = {1404000, 1, 0x1A, 1200000, 1250000, 3}, +}; + +#define L2(x) (&l2_freq_tbl_v2[(x)]) +/* SCPLL frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_acpu_speed acpu_freq_tbl_1188mhz[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 812500, 0x03006000}, + /* MAX_AXI row is used to source CPU cores and L2 from the AFAB clock. */ + { {0, 0}, MAX_AXI, ACPU_AFAB, 1, 0, 0, 0, L2(0), 875000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 875000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 887500, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 912500, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 925000, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 937500, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 950000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 975000, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 1000000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 1012500, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 1037500, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 1062500, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 1087500, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 1125000, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 1137500, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 1162500, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 1187500, 0x03006000}, + { {0, 0}, 0 }, +}; + +/* SCPLL frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_acpu_speed acpu_freq_tbl_slow[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 812500, 0x03006000}, + /* MAX_AXI row is used to source CPU cores and L2 from the AFAB clock. */ + { {0, 0}, MAX_AXI, ACPU_AFAB, 1, 0, 0, 0, L2(0), 875000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 875000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 887500, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 912500, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 925000, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 937500, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 950000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 975000, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 1000000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 1012500, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 1037500, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 1062500, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 1087500, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 1100000, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 1112500, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 1125000, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 1137500, 0x03006000}, + { {1, 1}, 1242000, ACPU_SCPLL, 0, 0, 1, 0x17, L2(16), 1150000, 0x03006000}, + { {1, 1}, 1296000, ACPU_SCPLL, 0, 0, 1, 0x18, L2(17), 1175000, 0x03006000}, + { {1, 1}, 1350000, ACPU_SCPLL, 0, 0, 1, 0x19, L2(18), 1200000, 0x03006000}, + { {1, 1}, 1404000, ACPU_SCPLL, 0, 0, 1, 0x1A, L2(19), 1225000, 0x03006000}, + { {1, 1}, 1458000, ACPU_SCPLL, 0, 0, 1, 0x1B, L2(19), 1237500, 0x03006000}, + { {1, 1}, 1512000, ACPU_SCPLL, 0, 0, 1, 0x1C, L2(19), 1250000, 0x03006000}, + { {0, 0}, 0 }, +}; + +/* SCPLL frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_acpu_speed acpu_freq_tbl_nom[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 812500, 0x03006000}, + /* MAX_AXI row is used to source CPU cores and L2 from the AFAB clock. */ + { {0, 0}, MAX_AXI, ACPU_AFAB, 1, 0, 0, 0, L2(0), 875000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 875000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 887500, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 912500, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 925000, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 937500, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 950000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 975000, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 1000000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 1012500, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 1037500, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 1062500, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 1062500, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 1075000, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 1087500, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 1100000, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 1112500, 0x03006000}, + { {1, 1}, 1242000, ACPU_SCPLL, 0, 0, 1, 0x17, L2(16), 1125000, 0x03006000}, + { {1, 1}, 1296000, ACPU_SCPLL, 0, 0, 1, 0x18, L2(17), 1150000, 0x03006000}, + { {1, 1}, 1350000, ACPU_SCPLL, 0, 0, 1, 0x19, L2(18), 1175000, 0x03006000}, + { {1, 1}, 1404000, ACPU_SCPLL, 0, 0, 1, 0x1A, L2(19), 1200000, 0x03006000}, + { {1, 1}, 1458000, ACPU_SCPLL, 0, 0, 1, 0x1B, L2(19), 1212500, 0x03006000}, + { {1, 1}, 1512000, ACPU_SCPLL, 0, 0, 1, 0x1C, L2(19), 1225000, 0x03006000}, + { {0, 0}, 0 }, +}; + +/* SCPLL frequencies = 2 * 27 MHz * L_VAL */ +static struct clkctl_acpu_speed acpu_freq_tbl_fast[] = { + { {1, 1}, 192000, ACPU_PLL_8, 3, 1, 0, 0, L2(1), 812500, 0x03006000}, + /* MAX_AXI row is used to source CPU cores and L2 from the AFAB clock. */ + { {0, 0}, MAX_AXI, ACPU_AFAB, 1, 0, 0, 0, L2(0), 875000, 0x03006000}, + { {1, 1}, 384000, ACPU_PLL_8, 3, 0, 0, 0, L2(1), 875000, 0x03006000}, + { {1, 1}, 432000, ACPU_SCPLL, 0, 0, 1, 0x08, L2(1), 887500, 0x03006000}, + { {1, 1}, 486000, ACPU_SCPLL, 0, 0, 1, 0x09, L2(2), 912500, 0x03006000}, + { {1, 1}, 540000, ACPU_SCPLL, 0, 0, 1, 0x0A, L2(3), 925000, 0x03006000}, + { {1, 1}, 594000, ACPU_SCPLL, 0, 0, 1, 0x0B, L2(4), 937500, 0x03006000}, + { {1, 1}, 648000, ACPU_SCPLL, 0, 0, 1, 0x0C, L2(5), 950000, 0x03006000}, + { {1, 1}, 702000, ACPU_SCPLL, 0, 0, 1, 0x0D, L2(6), 975000, 0x03006000}, + { {1, 1}, 756000, ACPU_SCPLL, 0, 0, 1, 0x0E, L2(7), 1000000, 0x03006000}, + { {1, 1}, 810000, ACPU_SCPLL, 0, 0, 1, 0x0F, L2(8), 1012500, 0x03006000}, + { {1, 1}, 864000, ACPU_SCPLL, 0, 0, 1, 0x10, L2(9), 1037500, 0x03006000}, + { {1, 1}, 918000, ACPU_SCPLL, 0, 0, 1, 0x11, L2(10), 1037500, 0x03006000}, + { {1, 1}, 972000, ACPU_SCPLL, 0, 0, 1, 0x12, L2(11), 1037500, 0x03006000}, + { {1, 1}, 1026000, ACPU_SCPLL, 0, 0, 1, 0x13, L2(12), 1050000, 0x03006000}, + { {1, 1}, 1080000, ACPU_SCPLL, 0, 0, 1, 0x14, L2(13), 1062500, 0x03006000}, + { {1, 1}, 1134000, ACPU_SCPLL, 0, 0, 1, 0x15, L2(14), 1075000, 0x03006000}, + { {1, 1}, 1188000, ACPU_SCPLL, 0, 0, 1, 0x16, L2(15), 1087500, 0x03006000}, + { {1, 1}, 1242000, ACPU_SCPLL, 0, 0, 1, 0x17, L2(16), 1100000, 0x03006000}, + { {1, 1}, 1296000, ACPU_SCPLL, 0, 0, 1, 0x18, L2(17), 1125000, 0x03006000}, + { {1, 1}, 1350000, ACPU_SCPLL, 0, 0, 1, 0x19, L2(18), 1150000, 0x03006000}, + { {1, 1}, 1404000, ACPU_SCPLL, 0, 0, 1, 0x1A, L2(19), 1175000, 0x03006000}, + { {1, 1}, 1458000, ACPU_SCPLL, 0, 0, 1, 0x1B, L2(19), 1187500, 0x03006000}, + { {1, 1}, 1512000, ACPU_SCPLL, 0, 0, 1, 0x1C, L2(19), 1200000, 0x03006000}, + { {0, 0}, 0 }, +}; + + +/* acpu_freq_tbl row to use when reconfiguring SC/L2 PLLs. */ +#define CAL_IDX 1 + +static struct clkctl_acpu_speed *acpu_freq_tbl; +static struct clkctl_l2_speed *l2_freq_tbl = l2_freq_tbl_v2; +static unsigned int l2_freq_tbl_size = ARRAY_SIZE(l2_freq_tbl_v2); + +unsigned long acpuclk_get_rate(int cpu) +{ + return drv_state.current_speed[cpu]->acpuclk_khz; +} + +uint32_t acpuclk_get_switch_time(void) +{ + return drv_state.acpu_switch_time_us; +} + +#define POWER_COLLAPSE_KHZ MAX_AXI +unsigned long acpuclk_power_collapse(void) +{ + int ret = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), POWER_COLLAPSE_KHZ, SETRATE_PC); + return ret; +} + +#define WAIT_FOR_IRQ_KHZ MAX_AXI +unsigned long acpuclk_wait_for_irq(void) +{ + int ret = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), WAIT_FOR_IRQ_KHZ, SETRATE_SWFI); + return ret; +} + +static void select_core_source(unsigned int id, unsigned int src) +{ + uint32_t regval; + int shift; + + shift = (id == L2) ? 0 : 1; + regval = readl_relaxed(clk_sel_addr[id]); + regval &= ~(0x3 << shift); + regval |= (src << shift); + writel_relaxed(regval, clk_sel_addr[id]); +} + +static void select_clk_source_div(unsigned int id, struct clkctl_acpu_speed *s) +{ + uint32_t reg_clksel, reg_clkctl, src_sel; + + /* Configure the PLL divider mux if we plan to use it. */ + if (s->core_src_sel == 0) { + + reg_clksel = readl_relaxed(clk_sel_addr[id]); + + /* CLK_SEL_SRC1N0 (bank) bit. */ + src_sel = reg_clksel & 1; + + /* Program clock source and divider. */ + reg_clkctl = readl_relaxed(clk_ctl_addr[id]); + reg_clkctl &= ~(0xFF << (8 * src_sel)); + reg_clkctl |= s->acpuclk_src_sel << (4 + 8 * src_sel); + reg_clkctl |= s->acpuclk_src_div << (0 + 8 * src_sel); + writel_relaxed(reg_clkctl, clk_ctl_addr[id]); + + /* Toggle clock source. */ + reg_clksel ^= 1; + + /* Program clock source selection. */ + writel_relaxed(reg_clksel, clk_sel_addr[id]); + } +} + +static void scpll_enable(int sc_pll, uint32_t l_val) +{ + uint32_t regval; + + /* Power-up SCPLL into standby mode. */ + writel_relaxed(SCPLL_STANDBY, sc_pll_base[sc_pll] + SCPLL_CTL_OFFSET); + mb(); + udelay(10); + + /* Shot-switch to target frequency. */ + regval = (l_val << 3) | SHOT_SWITCH; + writel_relaxed(regval, sc_pll_base[sc_pll] + SCPLL_FSM_CTL_EXT_OFFSET); + writel_relaxed(SCPLL_NORMAL, sc_pll_base[sc_pll] + SCPLL_CTL_OFFSET); + mb(); + udelay(20); +} + +static void scpll_disable(int sc_pll) +{ + /* Power down SCPLL. */ + writel_relaxed(SCPLL_POWER_DOWN, + sc_pll_base[sc_pll] + SCPLL_CTL_OFFSET); +} + +static void scpll_change_freq(int sc_pll, uint32_t l_val) +{ + uint32_t regval; + const void *base_addr = sc_pll_base[sc_pll]; + + /* Complex-slew switch to target frequency. */ + regval = (l_val << 3) | COMPLEX_SLEW; + writel_relaxed(regval, base_addr + SCPLL_FSM_CTL_EXT_OFFSET); + writel_relaxed(SCPLL_NORMAL, base_addr + SCPLL_CTL_OFFSET); + + /* Wait for frequency switch to start. */ + while (((readl_relaxed(base_addr + SCPLL_CTL_OFFSET) >> 3) & 0x3F) + != l_val) + cpu_relax(); + /* Wait for frequency switch to finish. */ + while (readl_relaxed(base_addr + SCPLL_STATUS_OFFSET) & 0x1) + cpu_relax(); +} + +/* Vote for the L2 speed and return the speed that should be applied. */ +static struct clkctl_l2_speed *compute_l2_speed(unsigned int voting_cpu, + struct clkctl_l2_speed *tgt_s) +{ + struct clkctl_l2_speed *new_s; + int cpu; + + /* Bounds check. */ + BUG_ON(tgt_s >= (l2_freq_tbl + l2_freq_tbl_size)); + + /* Find max L2 speed vote. */ + l2_vote[voting_cpu] = tgt_s; + new_s = l2_freq_tbl; + for_each_present_cpu(cpu) + new_s = max(new_s, l2_vote[cpu]); + + return new_s; +} + +/* Set the L2's clock speed. */ +static void set_l2_speed(struct clkctl_l2_speed *tgt_s) +{ + if (tgt_s == drv_state.current_l2_speed) + return; + + if (drv_state.current_l2_speed->src_sel == 1 + && tgt_s->src_sel == 1) + scpll_change_freq(L2, tgt_s->l_val); + else { + if (tgt_s->src_sel == 1) { + scpll_enable(L2, tgt_s->l_val); + mb(); + select_core_source(L2, tgt_s->src_sel); + } else { + select_core_source(L2, tgt_s->src_sel); + mb(); + scpll_disable(L2); + } + } + drv_state.current_l2_speed = tgt_s; +} + +/* Update the bus bandwidth request. */ +static void set_bus_bw(unsigned int bw) +{ + int ret; + + /* Bounds check. */ + if (bw >= ARRAY_SIZE(bw_level_tbl)) { + pr_err("%s: invalid bandwidth request (%d)\n", __func__, bw); + return; + } + + /* Update bandwidth if requst has changed. This may sleep. */ + ret = msm_bus_scale_client_update_request(bus_perf_client, bw); + if (ret) + pr_err("%s: bandwidth request failed (%d)\n", __func__, ret); + + return; +} + +/* Apply any per-cpu voltage increases. */ +static int increase_vdd(int cpu, unsigned int vdd_sc, unsigned int vdd_mem, + unsigned int vdd_dig, enum setrate_reason reason) +{ + int rc = 0; + + /* Increase vdd_mem active-set before vdd_dig and vdd_sc. + * vdd_mem should be >= both vdd_sc and vdd_dig. */ + rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8058_S0, rpm_vreg_voter[cpu], + vdd_mem, MAX_VDD_MEM, 0); + if (rc) { + pr_err("%s: vdd_mem (cpu%d) increase failed (%d)\n", + __func__, cpu, rc); + return rc; + } + + /* Increase vdd_dig active-set vote. */ + rc = rpm_vreg_set_voltage(RPM_VREG_ID_PM8058_S1, rpm_vreg_voter[cpu], + vdd_dig, MAX_VDD_DIG, 0); + if (rc) { + pr_err("%s: vdd_dig (cpu%d) increase failed (%d)\n", + __func__, cpu, rc); + return rc; + } + + /* Don't update the Scorpion voltage in the hotplug path. It should + * already be correct. Attempting to set it is bad because we don't + * know what CPU we are running on at this point, but the Scorpion + * regulator API requires we call it from the affected CPU. */ + if (reason == SETRATE_HOTPLUG) + return rc; + + /* Update per-core Scorpion voltage. */ + rc = regulator_set_voltage(regulator_sc[cpu], vdd_sc, MAX_VDD_SC); + if (rc) { + pr_err("%s: vdd_sc (cpu%d) increase failed (%d)\n", + __func__, cpu, rc); + return rc; + } + + return rc; +} + +/* Apply any per-cpu voltage decreases. */ +static void decrease_vdd(int cpu, unsigned int vdd_sc, unsigned int vdd_mem, + unsigned int vdd_dig, enum setrate_reason reason) +{ + int ret; + + /* Update per-core Scorpion voltage. This must be called on the CPU + * that's being affected. Don't do this in the hotplug remove path, + * where the rail is off and we're executing on the other CPU. */ + if (reason != SETRATE_HOTPLUG) { + ret = regulator_set_voltage(regulator_sc[cpu], vdd_sc, + MAX_VDD_SC); + if (ret) { + pr_err("%s: vdd_sc (cpu%d) decrease failed (%d)\n", + __func__, cpu, ret); + return; + } + } + + /* Decrease vdd_dig active-set vote. */ + ret = rpm_vreg_set_voltage(RPM_VREG_ID_PM8058_S1, rpm_vreg_voter[cpu], + vdd_dig, MAX_VDD_DIG, 0); + if (ret) { + pr_err("%s: vdd_dig (cpu%d) decrease failed (%d)\n", + __func__, cpu, ret); + return; + } + + /* Decrease vdd_mem active-set after vdd_dig and vdd_sc. + * vdd_mem should be >= both vdd_sc and vdd_dig. */ + ret = rpm_vreg_set_voltage(RPM_VREG_ID_PM8058_S0, rpm_vreg_voter[cpu], + vdd_mem, MAX_VDD_MEM, 0); + if (ret) { + pr_err("%s: vdd_mem (cpu%d) decrease failed (%d)\n", + __func__, cpu, ret); + return; + } +} + +static void switch_sc_speed(int cpu, struct clkctl_acpu_speed *tgt_s) +{ + struct clkctl_acpu_speed *strt_s = drv_state.current_speed[cpu]; + + if (strt_s->pll != ACPU_SCPLL && tgt_s->pll != ACPU_SCPLL) { + select_clk_source_div(cpu, tgt_s); + /* Select core source because target may be AFAB. */ + select_core_source(cpu, tgt_s->core_src_sel); + } else if (strt_s->pll != ACPU_SCPLL && tgt_s->pll == ACPU_SCPLL) { + scpll_enable(cpu, tgt_s->l_val); + mb(); + select_core_source(cpu, tgt_s->core_src_sel); + } else if (strt_s->pll == ACPU_SCPLL && tgt_s->pll != ACPU_SCPLL) { + select_clk_source_div(cpu, tgt_s); + select_core_source(cpu, tgt_s->core_src_sel); + /* Core source switch must complete before disabling SCPLL. */ + mb(); + udelay(1); + scpll_disable(cpu); + } else + scpll_change_freq(cpu, tgt_s->l_val); + + /* Update the driver state with the new clock freq */ + drv_state.current_speed[cpu] = tgt_s; +} + +int acpuclk_set_rate(int cpu, unsigned long rate, enum setrate_reason reason) +{ + struct clkctl_acpu_speed *tgt_s, *strt_s; + struct clkctl_l2_speed *tgt_l2; + unsigned int vdd_mem, vdd_dig, pll_vdd_dig; + unsigned long flags; + int rc = 0; + + if (cpu > num_possible_cpus()) { + rc = -EINVAL; + goto out; + } + + if (reason == SETRATE_CPUFREQ || reason == SETRATE_HOTPLUG) + mutex_lock(&drv_state.lock); + + strt_s = drv_state.current_speed[cpu]; + + /* Return early if rate didn't change. */ + if (rate == strt_s->acpuclk_khz) + goto out; + + /* Find target frequency. */ + for (tgt_s = acpu_freq_tbl; tgt_s->acpuclk_khz != 0; tgt_s++) + if (tgt_s->acpuclk_khz == rate) + break; + if (tgt_s->acpuclk_khz == 0) { + rc = -EINVAL; + goto out; + } + + /* AVS needs SAW_VCTL to be intitialized correctly, before enable, + * and is not initialized at acpuclk_init(). + */ + if (reason == SETRATE_CPUFREQ) + AVS_DISABLE(cpu); + + /* Calculate vdd_mem and vdd_dig requirements. + * vdd_mem must be >= vdd_sc */ + vdd_mem = max(tgt_s->vdd_sc, tgt_s->l2_level->vdd_mem); + /* Factor-in PLL vdd_dig requirements. */ + if ((tgt_s->l2_level->khz > SCPLL_LOW_VDD_FMAX) || + (tgt_s->pll == ACPU_SCPLL + && tgt_s->acpuclk_khz > SCPLL_LOW_VDD_FMAX)) + pll_vdd_dig = SCPLL_NOMINAL_VDD; + else + pll_vdd_dig = SCPLL_LOW_VDD; + vdd_dig = max(tgt_s->l2_level->vdd_dig, pll_vdd_dig); + + /* Increase VDD levels if needed. */ + if ((reason == SETRATE_CPUFREQ || reason == SETRATE_HOTPLUG + || reason == SETRATE_INIT) + && (tgt_s->acpuclk_khz > strt_s->acpuclk_khz)) { + rc = increase_vdd(cpu, tgt_s->vdd_sc, vdd_mem, vdd_dig, reason); + if (rc) + goto out; + } + + pr_debug("Switching from ACPU%d rate %u KHz -> %u KHz\n", + cpu, strt_s->acpuclk_khz, tgt_s->acpuclk_khz); + + /* Switch CPU speed. */ + switch_sc_speed(cpu, tgt_s); + + /* Update the L2 vote and apply the rate change. */ + spin_lock_irqsave(&drv_state.l2_lock, flags); + tgt_l2 = compute_l2_speed(cpu, tgt_s->l2_level); + set_l2_speed(tgt_l2); + spin_unlock_irqrestore(&drv_state.l2_lock, flags); + + /* Nothing else to do for SWFI. */ + if (reason == SETRATE_SWFI) + goto out; + + /* Nothing else to do for power collapse. */ + if (reason == SETRATE_PC) + goto out; + + /* Update bus bandwith request. */ + set_bus_bw(tgt_l2->bw_level); + + /* Drop VDD levels if we can. */ + if (tgt_s->acpuclk_khz < strt_s->acpuclk_khz) + decrease_vdd(cpu, tgt_s->vdd_sc, vdd_mem, vdd_dig, reason); + + pr_debug("ACPU%d speed change complete\n", cpu); + + /* Re-enable AVS */ + if (reason == SETRATE_CPUFREQ) + AVS_ENABLE(cpu, tgt_s->avsdscr_setting); + +out: + if (reason == SETRATE_CPUFREQ || reason == SETRATE_HOTPLUG) + mutex_unlock(&drv_state.lock); + return rc; +} + +static void __init scpll_init(int sc_pll) +{ + uint32_t regval; + + pr_debug("Initializing SCPLL%d\n", sc_pll); + + /* Clear calibration LUT registers containing max frequency entry. + * LUT registers are only writeable in debug mode. */ + writel_relaxed(SCPLL_DEBUG_FULL, + sc_pll_base[sc_pll] + SCPLL_DEBUG_OFFSET); + writel_relaxed(0x0, sc_pll_base[sc_pll] + SCPLL_LUT_A_HW_MAX); + writel_relaxed(SCPLL_DEBUG_NONE, + sc_pll_base[sc_pll] + SCPLL_DEBUG_OFFSET); + + /* Power-up SCPLL into standby mode. */ + writel_relaxed(SCPLL_STANDBY, sc_pll_base[sc_pll] + SCPLL_CTL_OFFSET); + mb(); + udelay(10); + + /* Calibrate the SCPLL to the maximum range supported by the h/w. We + * might not use the full range of calibrated frequencies, but this + * simplifies changes required for future increases in max CPU freq. + */ + regval = (L_VAL_SCPLL_CAL_MAX << 24) | (L_VAL_SCPLL_CAL_MIN << 16); + writel_relaxed(regval, sc_pll_base[sc_pll] + SCPLL_CAL_OFFSET); + + /* Start calibration */ + writel_relaxed(SCPLL_FULL_CAL, sc_pll_base[sc_pll] + SCPLL_CTL_OFFSET); + + /* Wait for proof that calibration has started before checking the + * 'calibration done' bit in the status register. Waiting for the + * LUT register we cleared to contain data accomplishes this. + * This is required since the 'calibration done' bit takes time to + * transition from 'done' to 'not done' when starting a calibration. + */ + while (readl_relaxed(sc_pll_base[sc_pll] + SCPLL_LUT_A_HW_MAX) == 0) + cpu_relax(); + + /* Wait for calibration to complete. */ + while (readl_relaxed(sc_pll_base[sc_pll] + SCPLL_STATUS_OFFSET) & 0x2) + cpu_relax(); + + /* Power-down SCPLL. */ + scpll_disable(sc_pll); +} + +/* Force ACPU core and L2 cache clocks to rates that don't require SCPLLs. */ +static void __init unselect_scplls(void) +{ + int cpu; + + /* Ensure CAL_IDX frequency uses AFAB sources for CPU cores and L2. */ + BUG_ON(acpu_freq_tbl[CAL_IDX].core_src_sel != 0); + BUG_ON(acpu_freq_tbl[CAL_IDX].l2_level->src_sel != 0); + + for_each_possible_cpu(cpu) { + select_clk_source_div(cpu, &acpu_freq_tbl[CAL_IDX]); + select_core_source(cpu, acpu_freq_tbl[CAL_IDX].core_src_sel); + drv_state.current_speed[cpu] = &acpu_freq_tbl[CAL_IDX]; + l2_vote[cpu] = acpu_freq_tbl[CAL_IDX].l2_level; + } + + select_core_source(L2, acpu_freq_tbl[CAL_IDX].l2_level->src_sel); + drv_state.current_l2_speed = acpu_freq_tbl[CAL_IDX].l2_level; +} + +/* Ensure SCPLLs use the 27MHz PXO. */ +static void __init scpll_set_refs(void) +{ + int cpu; + uint32_t regval; + + /* Bit 4 = 0:PXO, 1:MXO. */ + for_each_possible_cpu(cpu) { + regval = readl_relaxed(sc_pll_base[cpu] + SCPLL_CFG_OFFSET); + regval &= ~BIT(4); + writel_relaxed(regval, sc_pll_base[cpu] + SCPLL_CFG_OFFSET); + } + regval = readl_relaxed(sc_pll_base[L2] + SCPLL_CFG_OFFSET); + regval &= ~BIT(4); + writel_relaxed(regval, sc_pll_base[L2] + SCPLL_CFG_OFFSET); +} + +/* Voltage regulator initialization. */ +static void __init regulator_init(void) +{ + struct clkctl_acpu_speed **freq = drv_state.current_speed; + const char *regulator_sc_name[] = {"8901_s0", "8901_s1"}; + int cpu, ret; + + for_each_possible_cpu(cpu) { + /* VDD_SC0, VDD_SC1 */ + regulator_sc[cpu] = regulator_get(NULL, regulator_sc_name[cpu]); + if (IS_ERR(regulator_sc[cpu])) + goto err; + ret = regulator_set_voltage(regulator_sc[cpu], + freq[cpu]->vdd_sc, MAX_VDD_SC); + if (ret) + goto err; + ret = regulator_enable(regulator_sc[cpu]); + if (ret) + goto err; + } + + return; + +err: + pr_err("%s: Failed to initialize voltage regulators\n", __func__); + BUG(); +} + +/* Register with bus driver. */ +static void __init bus_init(void) +{ + bus_perf_client = msm_bus_scale_register_client(&bus_client_pdata); + if (!bus_perf_client) { + pr_err("%s: unable register bus client\n", __func__); + BUG(); + } +} + +#ifdef CONFIG_CPU_FREQ_MSM +static struct cpufreq_frequency_table freq_table[NR_CPUS][30]; + +static void __init cpufreq_table_init(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + int i, freq_cnt = 0; + /* Construct the freq_table tables from acpu_freq_tbl. */ + for (i = 0; acpu_freq_tbl[i].acpuclk_khz != 0 + && freq_cnt < ARRAY_SIZE(*freq_table); i++) { + if (acpu_freq_tbl[i].use_for_scaling[cpu]) { + freq_table[cpu][freq_cnt].index = freq_cnt; + freq_table[cpu][freq_cnt].frequency + = acpu_freq_tbl[i].acpuclk_khz; + freq_cnt++; + } + } + /* freq_table not big enough to store all usable freqs. */ + BUG_ON(acpu_freq_tbl[i].acpuclk_khz != 0); + + freq_table[cpu][freq_cnt].index = freq_cnt; + freq_table[cpu][freq_cnt].frequency = CPUFREQ_TABLE_END; + + pr_info("CPU%d: %d scaling frequencies supported.\n", + cpu, freq_cnt); + + /* Register table with CPUFreq. */ + cpufreq_frequency_table_get_attr(freq_table[cpu], cpu); + } +} +#else +static void __init cpufreq_table_init(void) {} +#endif + +#define HOT_UNPLUG_KHZ MAX_AXI +static int __cpuinit acpuclock_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + static int prev_khz[NR_CPUS]; + int cpu = (int)hcpu; + + switch (action) { + case CPU_DEAD: + case CPU_DEAD_FROZEN: + prev_khz[cpu] = acpuclk_get_rate(cpu); + /* Fall through. */ + case CPU_UP_CANCELED: + case CPU_UP_CANCELED_FROZEN: + acpuclk_set_rate(cpu, HOT_UNPLUG_KHZ, SETRATE_HOTPLUG); + break; + case CPU_UP_PREPARE: + case CPU_UP_PREPARE_FROZEN: + if (WARN_ON(!prev_khz[cpu])) + prev_khz[cpu] = acpu_freq_tbl->acpuclk_khz; + acpuclk_set_rate(cpu, prev_khz[cpu], SETRATE_HOTPLUG); + break; + default: + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block __cpuinitdata acpuclock_cpu_notifier = { + .notifier_call = acpuclock_cpu_callback, +}; + +static unsigned int __init select_freq_plan(void) +{ + uint32_t pte_efuse, speed_bin, pvs, max_khz; + struct clkctl_acpu_speed *f; + + pte_efuse = readl_relaxed(QFPROM_PTE_EFUSE_ADDR); + + speed_bin = pte_efuse & 0xF; + if (speed_bin == 0xF) + speed_bin = (pte_efuse >> 4) & 0xF; + + if (speed_bin == 0x1) { + max_khz = 1512000; + pvs = (pte_efuse >> 10) & 0x7; + if (pvs == 0x7) + pvs = (pte_efuse >> 13) & 0x7; + + switch (pvs) { + case 0x0: + case 0x7: + acpu_freq_tbl = acpu_freq_tbl_slow; + pr_info("ACPU PVS: Slow\n"); + break; + case 0x1: + acpu_freq_tbl = acpu_freq_tbl_nom; + pr_info("ACPU PVS: Nominal\n"); + break; + case 0x3: + acpu_freq_tbl = acpu_freq_tbl_fast; + pr_info("ACPU PVS: Fast\n"); + break; + default: + acpu_freq_tbl = acpu_freq_tbl_slow; + pr_warn("ACPU PVS: Unknown. Defaulting to slow.\n"); + break; + } + } else { + max_khz = 1188000; + acpu_freq_tbl = acpu_freq_tbl_1188mhz; + } + + /* Truncate the table based to max_khz. */ + for (f = acpu_freq_tbl; f->acpuclk_khz != 0; f++) { + if (f->acpuclk_khz > max_khz) { + f->acpuclk_khz = 0; + break; + } + } + f--; + pr_info("Max ACPU freq: %u KHz\n", f->acpuclk_khz); + + return f->acpuclk_khz; +} + +void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata) +{ + unsigned int max_cpu_khz; + int cpu; + + mutex_init(&drv_state.lock); + spin_lock_init(&drv_state.l2_lock); + drv_state.acpu_switch_time_us = clkdata->acpu_switch_time_us; + drv_state.vdd_switch_time_us = clkdata->vdd_switch_time_us; + + /* Configure hardware. */ + max_cpu_khz = select_freq_plan(); + unselect_scplls(); + scpll_set_refs(); + for_each_possible_cpu(cpu) + scpll_init(cpu); + scpll_init(L2); + regulator_init(); + bus_init(); + + /* Improve boot time by ramping up CPUs immediately. */ + for_each_online_cpu(cpu) + acpuclk_set_rate(cpu, max_cpu_khz, SETRATE_INIT); + + cpufreq_table_init(); + register_hotcpu_notifier(&acpuclock_cpu_notifier); +} diff --git a/arch/arm/mach-msm/acpuclock-arm11.c b/arch/arm/mach-msm/acpuclock-arm11.c index 805d4ee53f7..dd1e6e5b3c5 100644 --- a/arch/arm/mach-msm/acpuclock-arm11.c +++ b/arch/arm/mach-msm/acpuclock-arm11.c @@ -17,6 +17,7 @@ * */ +#include #include #include #include @@ -97,7 +98,7 @@ struct clkctl_acpu_speed { /* * ACPU speed table. Complete table is shown but certain speeds are commented - * out to optimized speed switching. Initialize loops_per_jiffy to 0. + * out to optimized speed switching. Initalize loops_per_jiffy to 0. * * Table stepping up/down is optimized for 256mhz jumps while staying on the * same PLL. @@ -493,7 +494,7 @@ uint32_t acpuclk_get_switch_time(void) * Clock driver initialization *---------------------------------------------------------------------------*/ -/* Initialize the lpj field in the acpu_freq_tbl. */ +/* Initalize the lpj field in the acpu_freq_tbl. */ static void __init lpj_init(void) { int i; diff --git a/arch/arm/mach-msm/acpuclock-fsm9xxx.c b/arch/arm/mach-msm/acpuclock-fsm9xxx.c new file mode 100644 index 00000000000..b12eb9e2984 --- /dev/null +++ b/arch/arm/mach-msm/acpuclock-fsm9xxx.c @@ -0,0 +1,42 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include + +#include "acpuclock.h" + +/* Registers */ +#define PLL1_CTL_ADDR (MSM_CLK_CTL_BASE + 0x604) + +unsigned long acpuclk_get_rate(int cpu) +{ + unsigned int pll1_ctl; + unsigned int pll1_l, pll1_div2; + unsigned int pll1_khz; + + pll1_ctl = readl_relaxed(PLL1_CTL_ADDR); + pll1_l = ((pll1_ctl >> 3) & 0x3f) * 2; + pll1_div2 = pll1_ctl & 0x20000; + pll1_khz = 19200 * pll1_l; + if (pll1_div2) + pll1_khz >>= 1; + + return pll1_khz; +} + +void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata) +{ + pr_info("ACPU running at %lu KHz\n", acpuclk_get_rate(0)); +} diff --git a/arch/arm/mach-msm/acpuclock.c b/arch/arm/mach-msm/acpuclock.c new file mode 100644 index 00000000000..c6fba577b67 --- /dev/null +++ b/arch/arm/mach-msm/acpuclock.c @@ -0,0 +1,1046 @@ +/* arch/arm/mach-msm/acpuclock.c + * + * MSM architecture clock driver + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2011, Code Aurora Forum. All rights reserved. + * Author: San Mehat + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "proc_comm.h" +#include "smd_private.h" +#include "acpuclock.h" + +#define A11S_CLK_CNTL_ADDR (MSM_CSR_BASE + 0x100) +#define A11S_CLK_SEL_ADDR (MSM_CSR_BASE + 0x104) +#define A11S_VDD_SVS_PLEVEL_ADDR (MSM_CSR_BASE + 0x124) +#define PLLn_MODE(n) (MSM_CLK_CTL_BASE + 0x300 + 28 * (n)) +#define PLLn_L_VAL(n) (MSM_CLK_CTL_BASE + 0x304 + 28 * (n)) + +#define PLL4_MODE (MSM_CLK_CTL_BASE + 0x374) +#define PLL4_L_VAL (MSM_CLK_CTL_BASE + 0x378) + +/* Max CPU frequency allowed by hardware while in standby waiting for an irq. */ +#define MAX_WAIT_FOR_IRQ_KHZ 128000 + +enum { + ACPU_PLL_TCXO = -1, + ACPU_PLL_0 = 0, + ACPU_PLL_1, + ACPU_PLL_2, + ACPU_PLL_3, + ACPU_PLL_4, + ACPU_PLL_END, +}; + +static const struct pll { + void __iomem *mod_reg; + const uint32_t l_val_mask; +} soc_pll[ACPU_PLL_END] = { + [ACPU_PLL_0] = {PLLn_MODE(ACPU_PLL_0), 0x3f}, + [ACPU_PLL_1] = {PLLn_MODE(ACPU_PLL_1), 0x3f}, + [ACPU_PLL_2] = {PLLn_MODE(ACPU_PLL_2), 0x3f}, + [ACPU_PLL_3] = {PLLn_MODE(ACPU_PLL_3), 0x3f}, + [ACPU_PLL_4] = {PLL4_MODE, 0x3ff}, +}; + +struct clock_state { + struct clkctl_acpu_speed *current_speed; + struct mutex lock; + uint32_t acpu_switch_time_us; + uint32_t max_speed_delta_khz; + uint32_t vdd_switch_time_us; + unsigned long max_axi_khz; + unsigned long wait_for_irq_khz; + struct clk *ebi1_clk; +}; + +#define PLL_BASE 7 + +struct shared_pll_control { + uint32_t version; + struct { + /* Denotes if the PLL is ON. Technically, this can be read + * directly from the PLL registers, but this feild is here, + * so let's use it. + */ + uint32_t on; + /* One bit for each processor core. The application processor + * is allocated bit position 1. All other bits should be + * considered as votes from other processors. + */ + uint32_t votes; + } pll[PLL_BASE + ACPU_PLL_END]; +}; + +struct clkctl_acpu_speed { + unsigned int use_for_scaling; + unsigned int a11clk_khz; + int pll; + unsigned int a11clk_src_sel; + unsigned int a11clk_src_div; + unsigned int ahbclk_khz; + unsigned int ahbclk_div; + int vdd; + unsigned int axiclk_khz; + unsigned long lpj; /* loops_per_jiffy */ + /* Pointers in acpu_freq_tbl[] for max up/down steppings. */ + struct clkctl_acpu_speed *down[ACPU_PLL_END]; + struct clkctl_acpu_speed *up[ACPU_PLL_END]; +}; + +static remote_spinlock_t pll_lock; +static struct shared_pll_control *pll_control; +static struct clock_state drv_state = { 0 }; +static struct clkctl_acpu_speed *acpu_freq_tbl; + +static void __init acpuclk_init(void); + +/* + * ACPU freq tables used for different PLLs frequency combinations. The + * correct table is selected during init. + * + * Table stepping up/down entries are calculated during boot to choose the + * largest frequency jump that's less than max_speed_delta_khz on each PLL. + */ + +/* 7x01/7x25 normal with GSM capable modem */ +static struct clkctl_acpu_speed pll0_245_pll1_768_pll2_1056_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 30720 }, + { 1, 122880, ACPU_PLL_0, 4, 1, 61440, 1, 3, 61440 }, + { 0, 128000, ACPU_PLL_1, 1, 5, 64000, 1, 3, 61440 }, + { 0, 176000, ACPU_PLL_2, 2, 5, 88000, 1, 3, 61440 }, + { 1, 245760, ACPU_PLL_0, 4, 0, 81920, 2, 4, 61440 }, + { 1, 256000, ACPU_PLL_1, 1, 2, 128000, 1, 5, 128000 }, + { 0, 352000, ACPU_PLL_2, 2, 2, 88000, 3, 5, 128000 }, + { 1, 384000, ACPU_PLL_1, 1, 1, 128000, 2, 6, 128000 }, + { 1, 528000, ACPU_PLL_2, 2, 1, 132000, 3, 7, 128000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x01/7x25 normal with CDMA-only modem */ +static struct clkctl_acpu_speed pll0_196_pll1_768_pll2_1056_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 24576 }, + { 1, 98304, ACPU_PLL_0, 4, 1, 49152, 1, 3, 24576 }, + { 0, 128000, ACPU_PLL_1, 1, 5, 64000, 1, 3, 24576 }, + { 0, 176000, ACPU_PLL_2, 2, 5, 88000, 1, 3, 24576 }, + { 1, 196608, ACPU_PLL_0, 4, 0, 65536, 2, 4, 24576 }, + { 1, 256000, ACPU_PLL_1, 1, 2, 128000, 1, 5, 128000 }, + { 0, 352000, ACPU_PLL_2, 2, 2, 88000, 3, 5, 128000 }, + { 1, 384000, ACPU_PLL_1, 1, 1, 128000, 2, 6, 128000 }, + { 1, 528000, ACPU_PLL_2, 2, 1, 132000, 3, 7, 128000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x01/7x25 turbo with GSM capable modem */ +static struct clkctl_acpu_speed pll0_245_pll1_960_pll2_1056_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 30720 }, + { 0, 120000, ACPU_PLL_1, 1, 7, 60000, 1, 3, 61440 }, + { 1, 122880, ACPU_PLL_0, 4, 1, 61440, 1, 3, 61440 }, + { 0, 176000, ACPU_PLL_2, 2, 5, 88000, 1, 3, 61440 }, + { 1, 245760, ACPU_PLL_0, 4, 0, 81920, 2, 4, 61440 }, + { 1, 320000, ACPU_PLL_1, 1, 2, 107000, 2, 5, 120000 }, + { 0, 352000, ACPU_PLL_2, 2, 2, 88000, 3, 5, 120000 }, + { 1, 480000, ACPU_PLL_1, 1, 1, 120000, 3, 6, 120000 }, + { 1, 528000, ACPU_PLL_2, 2, 1, 132000, 3, 7, 122880 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x01/7x25 turbo with CDMA-only modem */ +static struct clkctl_acpu_speed pll0_196_pll1_960_pll2_1056_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 24576 }, + { 1, 98304, ACPU_PLL_0, 4, 1, 49152, 1, 3, 24576 }, + { 0, 120000, ACPU_PLL_1, 1, 7, 60000, 1, 3, 24576 }, + { 0, 176000, ACPU_PLL_2, 2, 5, 88000, 1, 3, 24576 }, + { 1, 196608, ACPU_PLL_0, 4, 0, 65536, 2, 4, 24576 }, + { 1, 320000, ACPU_PLL_1, 1, 2, 107000, 2, 5, 120000 }, + { 0, 352000, ACPU_PLL_2, 2, 2, 88000, 3, 5, 120000 }, + { 1, 480000, ACPU_PLL_1, 1, 1, 120000, 3, 6, 120000 }, + { 1, 528000, ACPU_PLL_2, 2, 1, 132000, 3, 7, 120000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x27 normal with GSM capable modem */ +static struct clkctl_acpu_speed pll0_245_pll1_960_pll2_1200_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 30720 }, + { 0, 120000, ACPU_PLL_1, 1, 7, 60000, 1, 3, 61440 }, + { 1, 122880, ACPU_PLL_0, 4, 1, 61440, 1, 3, 61440 }, + { 0, 200000, ACPU_PLL_2, 2, 5, 66667, 2, 4, 61440 }, + { 1, 245760, ACPU_PLL_0, 4, 0, 122880, 1, 4, 61440 }, + { 1, 320000, ACPU_PLL_1, 1, 2, 160000, 1, 5, 122880 }, + { 0, 400000, ACPU_PLL_2, 2, 2, 133333, 2, 5, 122880 }, + { 1, 480000, ACPU_PLL_1, 1, 1, 160000, 2, 6, 122880 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 200000, 2, 7, 122880 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x27 normal with CDMA-only modem */ +static struct clkctl_acpu_speed pll0_196_pll1_960_pll2_1200_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 24576 }, + { 1, 98304, ACPU_PLL_0, 4, 1, 98304, 0, 3, 49152 }, + { 0, 120000, ACPU_PLL_1, 1, 7, 60000, 1, 3, 49152 }, + { 1, 196608, ACPU_PLL_0, 4, 0, 65536, 2, 4, 98304 }, + { 0, 200000, ACPU_PLL_2, 2, 5, 66667, 2, 4, 98304 }, + { 1, 320000, ACPU_PLL_1, 1, 2, 160000, 1, 5, 120000 }, + { 0, 400000, ACPU_PLL_2, 2, 2, 133333, 2, 5, 120000 }, + { 1, 480000, ACPU_PLL_1, 1, 1, 160000, 2, 6, 120000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 200000, 2, 7, 120000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x27 normal with GSM capable modem - PLL0 and PLL1 swapped */ +static struct clkctl_acpu_speed pll0_960_pll1_245_pll2_1200_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 30720 }, + { 0, 120000, ACPU_PLL_0, 4, 7, 60000, 1, 3, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 1, 61440, 1, 3, 61440 }, + { 0, 200000, ACPU_PLL_2, 2, 5, 66667, 2, 4, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 0, 122880, 1, 4, 61440 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 160000, 1, 5, 122880 }, + { 0, 400000, ACPU_PLL_2, 2, 2, 133333, 2, 5, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 6, 122880 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 200000, 2, 7, 122880 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x27 normal with CDMA-only modem - PLL0 and PLL1 swapped */ +static struct clkctl_acpu_speed pll0_960_pll1_196_pll2_1200_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 24576 }, + { 1, 98304, ACPU_PLL_1, 1, 1, 98304, 0, 3, 49152 }, + { 0, 120000, ACPU_PLL_0, 4, 7, 60000, 1, 3, 49152 }, + { 1, 196608, ACPU_PLL_1, 1, 0, 65536, 2, 4, 98304 }, + { 0, 200000, ACPU_PLL_2, 2, 5, 66667, 2, 4, 98304 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 160000, 1, 5, 120000 }, + { 0, 400000, ACPU_PLL_2, 2, 2, 133333, 2, 5, 120000 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 6, 120000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 200000, 2, 7, 120000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x27 normal with GSM capable modem - PLL0 and PLL1 swapped and pll2 @ 800 */ +static struct clkctl_acpu_speed pll0_960_pll1_245_pll2_800_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 30720 }, + { 0, 120000, ACPU_PLL_0, 4, 7, 60000, 1, 3, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 1, 61440, 1, 3, 61440 }, + { 0, 200000, ACPU_PLL_2, 2, 3, 66667, 2, 4, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 0, 122880, 1, 4, 61440 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 160000, 1, 5, 122880 }, + { 0, 400000, ACPU_PLL_2, 2, 1, 133333, 2, 5, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 6, 122880 }, + { 1, 800000, ACPU_PLL_2, 2, 0, 200000, 3, 7, 122880 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x27 normal with CDMA-only modem - PLL0 and PLL1 swapped and pll2 @ 800 */ +static struct clkctl_acpu_speed pll0_960_pll1_196_pll2_800_pll4_0[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 24576 }, + { 1, 98304, ACPU_PLL_1, 1, 1, 98304, 0, 3, 49152 }, + { 0, 120000, ACPU_PLL_0, 4, 7, 60000, 1, 3, 49152 }, + { 1, 196608, ACPU_PLL_1, 1, 0, 65536, 2, 4, 98304 }, + { 0, 200000, ACPU_PLL_2, 2, 3, 66667, 2, 4, 98304 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 160000, 1, 5, 120000 }, + { 0, 400000, ACPU_PLL_2, 2, 1, 133333, 2, 5, 120000 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 6, 120000 }, + { 1, 800000, ACPU_PLL_2, 2, 0, 200000, 3, 7, 120000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x27a pll2 at 1200mhz with GSM capable modem */ +static struct clkctl_acpu_speed pll0_960_pll1_245_pll2_1200_pll4_800[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 30720 }, + { 0, 61440, ACPU_PLL_1, 1, 3, 61440, 0, 1, 61440 }, + { 1, 122880, ACPU_PLL_1, 1, 1, 61440, 1, 2, 61440 }, + { 1, 245760, ACPU_PLL_1, 1, 0, 122880, 1, 3, 61440 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 150000, 1, 4, 150000 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 160000, 1, 4, 122880 }, + { 0, 400000, ACPU_PLL_4, 6, 1, 133333, 2, 4, 122880 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 5, 122880 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 200000, 2, 6, 200000 }, + { 1, 800000, ACPU_PLL_4, 6, 0, 200000, 3, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +/* 7x27a pll2 at 1200mhz with CDMA only modem */ +static struct clkctl_acpu_speed pll0_960_pll1_196_pll2_1200_pll4_800[] = { + { 0, 19200, ACPU_PLL_TCXO, 0, 0, 19200, 0, 0, 24576 }, + { 0, 65536, ACPU_PLL_1, 1, 3, 65536, 0, 1, 49152 }, + { 1, 98304, ACPU_PLL_1, 1, 1, 49152, 1, 2, 49152 }, + { 1, 196608, ACPU_PLL_1, 1, 0, 98304, 1, 3, 98304 }, + { 0, 300000, ACPU_PLL_2, 2, 3, 150000, 1, 4, 150000 }, + { 1, 320000, ACPU_PLL_0, 4, 2, 160000, 1, 4, 160000 }, + { 0, 400000, ACPU_PLL_4, 6, 1, 200000, 1, 4, 160000 }, + { 1, 480000, ACPU_PLL_0, 4, 1, 160000, 2, 5, 160000 }, + { 1, 600000, ACPU_PLL_2, 2, 1, 200000, 2, 6, 200000 }, + { 1, 800000, ACPU_PLL_4, 6, 0, 200000, 3, 7, 200000 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0}, {0, 0, 0, 0} } +}; + +#define PLL_0_MHZ 0 +#define PLL_196_MHZ 10 +#define PLL_245_MHZ 12 +#define PLL_491_MHZ 25 +#define PLL_768_MHZ 40 +#define PLL_800_MHZ 41 +#define PLL_960_MHZ 50 +#define PLL_1056_MHZ 55 +#define PLL_1200_MHZ 62 + +#define PLL_CONFIG(m0, m1, m2, m4) { \ + PLL_##m0##_MHZ, PLL_##m1##_MHZ, PLL_##m2##_MHZ, PLL_##m4##_MHZ, \ + pll0_##m0##_pll1_##m1##_pll2_##m2##_pll4_##m4 \ +} + +struct pll_freq_tbl_map { + unsigned int pll0_l; + unsigned int pll1_l; + unsigned int pll2_l; + unsigned int pll4_l; + struct clkctl_acpu_speed *tbl; +}; + +static struct pll_freq_tbl_map acpu_freq_tbl_list[] = { + PLL_CONFIG(196, 768, 1056, 0), + PLL_CONFIG(245, 768, 1056, 0), + PLL_CONFIG(196, 960, 1056, 0), + PLL_CONFIG(245, 960, 1056, 0), + PLL_CONFIG(196, 960, 1200, 0), + PLL_CONFIG(245, 960, 1200, 0), + PLL_CONFIG(960, 196, 1200, 0), + PLL_CONFIG(960, 245, 1200, 0), + PLL_CONFIG(960, 196, 800, 0), + PLL_CONFIG(960, 245, 800, 0), + PLL_CONFIG(960, 245, 1200, 800), + PLL_CONFIG(960, 196, 1200, 800), + { 0, 0, 0, 0, 0 } +}; + +#ifdef CONFIG_CPU_FREQ_MSM +static struct cpufreq_frequency_table freq_table[20]; + +static void __init cpufreq_table_init(void) +{ + unsigned int i; + unsigned int freq_cnt = 0; + + /* Construct the freq_table table from acpu_freq_tbl since the + * freq_table values need to match frequencies specified in + * acpu_freq_tbl and acpu_freq_tbl needs to be fixed up during init. + */ + for (i = 0; acpu_freq_tbl[i].a11clk_khz != 0 + && freq_cnt < ARRAY_SIZE(freq_table)-1; i++) { + if (acpu_freq_tbl[i].use_for_scaling) { + freq_table[freq_cnt].index = freq_cnt; + freq_table[freq_cnt].frequency + = acpu_freq_tbl[i].a11clk_khz; + freq_cnt++; + } + } + + /* freq_table not big enough to store all usable freqs. */ + BUG_ON(acpu_freq_tbl[i].a11clk_khz != 0); + + freq_table[freq_cnt].index = freq_cnt; + freq_table[freq_cnt].frequency = CPUFREQ_TABLE_END; + + pr_info("%d scaling frequencies supported.\n", freq_cnt); +} +#endif + +static void pll_enable(void __iomem *addr, unsigned on) +{ + if (on) { + writel_relaxed(2, addr); + mb(); + udelay(5); + writel_relaxed(6, addr); + mb(); + udelay(50); + writel_relaxed(7, addr); + } else { + writel_relaxed(0, addr); + } +} + +static int pc_pll_request(unsigned id, unsigned on) +{ + int res = 0; + on = !!on; + + if (on) + pr_debug("Enabling PLL %d\n", id); + else + pr_debug("Disabling PLL %d\n", id); + + if (id >= ACPU_PLL_END) + return -EINVAL; + + if (pll_control) { + remote_spin_lock(&pll_lock); + if (on) { + pll_control->pll[PLL_BASE + id].votes |= 2; + if (!pll_control->pll[PLL_BASE + id].on) { + pll_enable(soc_pll[id].mod_reg, 1); + pll_control->pll[PLL_BASE + id].on = 1; + } + } else { + pll_control->pll[PLL_BASE + id].votes &= ~2; + if (pll_control->pll[PLL_BASE + id].on + && !pll_control->pll[PLL_BASE + id].votes) { + pll_enable(soc_pll[id].mod_reg, 0); + pll_control->pll[PLL_BASE + id].on = 0; + } + } + remote_spin_unlock(&pll_lock); + } else { + res = msm_proc_comm(PCOM_CLKCTL_RPC_PLL_REQUEST, &id, &on); + if (res < 0) + return res; + else if ((int) id < 0) + return -EINVAL; + } + + if (on) + pr_debug("PLL enabled\n"); + else + pr_debug("PLL disabled\n"); + + return res; +} + + +/*---------------------------------------------------------------------------- + * ARM11 'owned' clock control + *---------------------------------------------------------------------------*/ + +#define POWER_COLLAPSE_KHZ 19200 +unsigned long acpuclk_power_collapse(void) +{ + int ret = acpuclk_get_rate(smp_processor_id()); + acpuclk_set_rate(smp_processor_id(), POWER_COLLAPSE_KHZ, SETRATE_PC); + return ret; +} + +unsigned long acpuclk_wait_for_irq(void) +{ + int rate = acpuclk_get_rate(smp_processor_id()); + if (rate > MAX_WAIT_FOR_IRQ_KHZ) + acpuclk_set_rate(smp_processor_id(), drv_state.wait_for_irq_khz, + SETRATE_SWFI); + return rate; +} + +static int acpuclk_set_vdd_level(int vdd) +{ + uint32_t current_vdd; + + /* + * NOTE: v1.0 of 7x27a/7x25a chip doesn't have working + * VDD switching support. + */ + if ((cpu_is_msm7x27a() || cpu_is_msm7x25a()) && + (SOCINFO_VERSION_MINOR(socinfo_get_version()) < 1)) + return 0; + + current_vdd = readl_relaxed(A11S_VDD_SVS_PLEVEL_ADDR) & 0x07; + + pr_debug("Switching VDD from %u mV -> %d mV\n", + current_vdd, vdd); + + writel_relaxed((1 << 7) | (vdd << 3), A11S_VDD_SVS_PLEVEL_ADDR); + mb(); + udelay(drv_state.vdd_switch_time_us); + if ((readl_relaxed(A11S_VDD_SVS_PLEVEL_ADDR) & 0x7) != vdd) { + pr_err("VDD set failed\n"); + return -EIO; + } + + pr_debug("VDD switched\n"); + + return 0; +} + +/* Set proper dividers for the given clock speed. */ +static void acpuclk_set_div(const struct clkctl_acpu_speed *hunt_s) +{ + uint32_t reg_clkctl, reg_clksel, clk_div, src_sel; + + reg_clksel = readl_relaxed(A11S_CLK_SEL_ADDR); + + /* AHB_CLK_DIV */ + clk_div = (reg_clksel >> 1) & 0x03; + /* CLK_SEL_SRC1NO */ + src_sel = reg_clksel & 1; + + /* + * If the new clock divider is higher than the previous, then + * program the divider before switching the clock + */ + if (hunt_s->ahbclk_div > clk_div) { + reg_clksel &= ~(0x3 << 1); + reg_clksel |= (hunt_s->ahbclk_div << 1); + writel_relaxed(reg_clksel, A11S_CLK_SEL_ADDR); + } + + /* Program clock source and divider */ + reg_clkctl = readl_relaxed(A11S_CLK_CNTL_ADDR); + reg_clkctl &= ~(0xFF << (8 * src_sel)); + reg_clkctl |= hunt_s->a11clk_src_sel << (4 + 8 * src_sel); + reg_clkctl |= hunt_s->a11clk_src_div << (0 + 8 * src_sel); + writel_relaxed(reg_clkctl, A11S_CLK_CNTL_ADDR); + + /* Program clock source selection */ + reg_clksel ^= 1; + writel_relaxed(reg_clksel, A11S_CLK_SEL_ADDR); + + /* + * If the new clock divider is lower than the previous, then + * program the divider after switching the clock + */ + if (hunt_s->ahbclk_div < clk_div) { + reg_clksel &= ~(0x3 << 1); + reg_clksel |= (hunt_s->ahbclk_div << 1); + writel_relaxed(reg_clksel, A11S_CLK_SEL_ADDR); + } +} + +int acpuclk_set_rate(int cpu, unsigned long rate, enum setrate_reason reason) +{ + uint32_t reg_clkctl; + struct clkctl_acpu_speed *cur_s, *tgt_s, *strt_s; + int res, rc = 0; + unsigned int plls_enabled = 0, pll; + + if (reason == SETRATE_CPUFREQ) + mutex_lock(&drv_state.lock); + + strt_s = cur_s = drv_state.current_speed; + + WARN_ONCE(cur_s == NULL, "acpuclk_set_rate: not initialized\n"); + if (cur_s == NULL) { + rc = -ENOENT; + goto out; + } + + if (rate == cur_s->a11clk_khz) + goto out; + + for (tgt_s = acpu_freq_tbl; tgt_s->a11clk_khz != 0; tgt_s++) { + if (tgt_s->a11clk_khz == rate) + break; + } + + if (tgt_s->a11clk_khz == 0) { + rc = -EINVAL; + goto out; + } + + /* Choose the highest speed at or below 'rate' with same PLL. */ + if (reason != SETRATE_CPUFREQ + && tgt_s->a11clk_khz < cur_s->a11clk_khz) { + while (tgt_s->pll != ACPU_PLL_TCXO && tgt_s->pll != cur_s->pll) + tgt_s--; + } + + if (strt_s->pll != ACPU_PLL_TCXO) + plls_enabled |= 1 << strt_s->pll; + + if (reason == SETRATE_CPUFREQ) { + if (strt_s->pll != tgt_s->pll && tgt_s->pll != ACPU_PLL_TCXO) { + rc = pc_pll_request(tgt_s->pll, 1); + if (rc < 0) { + pr_err("PLL%d enable failed (%d)\n", + tgt_s->pll, rc); + goto out; + } + plls_enabled |= 1 << tgt_s->pll; + } + } + /* Need to do this when coming out of power collapse since some modem + * firmwares reset the VDD when the application processor enters power + * collapse. */ + if (reason == SETRATE_CPUFREQ || reason == SETRATE_PC) { + /* Increase VDD if needed. */ + if (tgt_s->vdd > cur_s->vdd) { + rc = acpuclk_set_vdd_level(tgt_s->vdd); + if (rc < 0) { + pr_err("Unable to switch ACPU vdd (%d)\n", rc); + goto out; + } + } + } + + /* Set wait states for CPU inbetween frequency changes */ + reg_clkctl = readl_relaxed(A11S_CLK_CNTL_ADDR); + reg_clkctl |= (100 << 16); /* set WT_ST_CNT */ + writel_relaxed(reg_clkctl, A11S_CLK_CNTL_ADDR); + + pr_debug("Switching from ACPU rate %u KHz -> %u KHz\n", + strt_s->a11clk_khz, tgt_s->a11clk_khz); + + while (cur_s != tgt_s) { + /* + * Always jump to target freq if within 256mhz, regulardless of + * PLL. If differnece is greater, use the predefinied + * steppings in the table. + */ + int d = abs((int)(cur_s->a11clk_khz - tgt_s->a11clk_khz)); + if (d > drv_state.max_speed_delta_khz) { + + if (tgt_s->a11clk_khz > cur_s->a11clk_khz) { + /* Step up: jump to target PLL as early as + * possible so indexing using TCXO (up[-1]) + * never occurs. */ + if (likely(cur_s->up[tgt_s->pll])) + cur_s = cur_s->up[tgt_s->pll]; + else + cur_s = cur_s->up[cur_s->pll]; + } else { + /* Step down: stay on current PLL as long as + * possible so indexing using TCXO (down[-1]) + * never occurs. */ + if (likely(cur_s->down[cur_s->pll])) + cur_s = cur_s->down[cur_s->pll]; + else + cur_s = cur_s->down[tgt_s->pll]; + } + + if (cur_s == NULL) { /* This should not happen. */ + pr_err("No stepping frequencies found. " + "strt_s:%u tgt_s:%u\n", + strt_s->a11clk_khz, tgt_s->a11clk_khz); + rc = -EINVAL; + goto out; + } + + } else { + cur_s = tgt_s; + } + + pr_debug("STEP khz = %u, pll = %d\n", + cur_s->a11clk_khz, cur_s->pll); + + if (cur_s->pll != ACPU_PLL_TCXO + && !(plls_enabled & (1 << cur_s->pll))) { + rc = pc_pll_request(cur_s->pll, 1); + if (rc < 0) { + pr_err("PLL%d enable failed (%d)\n", + cur_s->pll, rc); + goto out; + } + plls_enabled |= 1 << cur_s->pll; + } + + acpuclk_set_div(cur_s); + drv_state.current_speed = cur_s; + /* Re-adjust lpj for the new clock speed. */ + loops_per_jiffy = cur_s->lpj; + mb(); + udelay(drv_state.acpu_switch_time_us); + } + + /* Nothing else to do for SWFI. */ + if (reason == SETRATE_SWFI) + goto out; + + /* Change the AXI bus frequency if we can. */ + if (strt_s->axiclk_khz != tgt_s->axiclk_khz) { + res = clk_set_rate(drv_state.ebi1_clk, + tgt_s->axiclk_khz * 1000); + if (res < 0) + pr_warning("Setting AXI min rate failed (%d)\n", res); + } + + /* Disable PLLs we are not using anymore. */ + if (tgt_s->pll != ACPU_PLL_TCXO) + plls_enabled &= ~(1 << tgt_s->pll); + for (pll = ACPU_PLL_0; pll < ACPU_PLL_END; pll++) + if (plls_enabled & (1 << pll)) { + res = pc_pll_request(pll, 0); + if (res < 0) + pr_warning("PLL%d disable failed (%d)\n", + pll, res); + } + + /* Nothing else to do for power collapse. */ + if (reason == SETRATE_PC) + goto out; + + /* Drop VDD level if we can. */ + if (tgt_s->vdd < strt_s->vdd) { + res = acpuclk_set_vdd_level(tgt_s->vdd); + if (res < 0) + pr_warning("Unable to drop ACPU vdd (%d)\n", res); + } + + pr_debug("ACPU speed change complete\n"); +out: + if (reason == SETRATE_CPUFREQ) + mutex_unlock(&drv_state.lock); + return rc; +} + +static void __init acpuclk_init(void) +{ + struct clkctl_acpu_speed *speed; + uint32_t div, sel; + int res; + + /* + * Determine the rate of ACPU clock + */ + + if (!(readl_relaxed(A11S_CLK_SEL_ADDR) & 0x01)) { /* CLK_SEL_SRC1N0 */ + /* CLK_SRC0_SEL */ + sel = (readl_relaxed(A11S_CLK_CNTL_ADDR) >> 12) & 0x7; + /* CLK_SRC0_DIV */ + div = (readl_relaxed(A11S_CLK_CNTL_ADDR) >> 8) & 0x0f; + } else { + /* CLK_SRC1_SEL */ + sel = (readl_relaxed(A11S_CLK_CNTL_ADDR) >> 4) & 0x07; + /* CLK_SRC1_DIV */ + div = readl_relaxed(A11S_CLK_CNTL_ADDR) & 0x0f; + } + + /* Accomodate bootloaders that might not be implementing the + * workaround for the h/w bug in 7x25. */ + if (cpu_is_msm7x25() && sel == 2) + sel = 3; + + for (speed = acpu_freq_tbl; speed->a11clk_khz != 0; speed++) { + if (speed->a11clk_src_sel == sel + && (speed->a11clk_src_div == div)) + break; + } + if (speed->a11clk_khz == 0) { + pr_err("Error - ACPU clock reports invalid speed\n"); + return; + } + + drv_state.current_speed = speed; + if (speed->pll != ACPU_PLL_TCXO) + if (pc_pll_request(speed->pll, 1)) + pr_warning("Failed to vote for boot PLL\n"); + + res = clk_set_rate(drv_state.ebi1_clk, speed->axiclk_khz * 1000); + if (res < 0) + pr_warning("Setting AXI min rate failed (%d)\n", res); + res = clk_enable(drv_state.ebi1_clk); + if (res < 0) + pr_warning("Enabling AXI clock failed (%d)\n", res); + + pr_info("ACPU running at %d KHz\n", speed->a11clk_khz); +} + +unsigned long acpuclk_get_rate(int cpu) +{ + WARN_ONCE(drv_state.current_speed == NULL, + "acpuclk_get_rate: not initialized\n"); + if (drv_state.current_speed) + return drv_state.current_speed->a11clk_khz; + else + return 0; +} + +uint32_t acpuclk_get_switch_time(void) +{ + return drv_state.acpu_switch_time_us; +} + +/*---------------------------------------------------------------------------- + * Clock driver initialization + *---------------------------------------------------------------------------*/ + +#define DIV2REG(n) ((n)-1) +#define REG2DIV(n) ((n)+1) +#define SLOWER_BY(div, factor) div = DIV2REG(REG2DIV(div) * factor) + +static void __init acpu_freq_tbl_fixup(void) +{ + unsigned long pll0_l, pll1_l, pll2_l, pll4_l; + int axi_160mhz = 0, axi_200mhz = 0; + struct pll_freq_tbl_map *lst; + struct clkctl_acpu_speed *t; + unsigned int pll0_needs_fixup = 0; + + /* Wait for the PLLs to be initialized and then read their frequency. + */ + do { + pll0_l = readl_relaxed(PLLn_L_VAL(0)) & + soc_pll[ACPU_PLL_0].l_val_mask; + cpu_relax(); + udelay(50); + } while (pll0_l == 0); + do { + pll1_l = readl_relaxed(PLLn_L_VAL(1)) & + soc_pll[ACPU_PLL_1].l_val_mask; + cpu_relax(); + udelay(50); + } while (pll1_l == 0); + do { + pll2_l = readl_relaxed(PLLn_L_VAL(2)) & + soc_pll[ACPU_PLL_2].l_val_mask; + cpu_relax(); + udelay(50); + } while (pll2_l == 0); + + pr_info("L val: PLL0: %d, PLL1: %d, PLL2: %d\n", + (int)pll0_l, (int)pll1_l, (int)pll2_l); + + if (!cpu_is_msm7x27() && !cpu_is_msm7x25a()) { + do { + pll4_l = readl_relaxed(PLL4_L_VAL) & + soc_pll[ACPU_PLL_4].l_val_mask; + cpu_relax(); + udelay(50); + } while (pll4_l == 0); + pr_info("L val: PLL4: %d\n", (int)pll4_l); + } else { + pll4_l = 0; + } + + /* Some configurations run PLL0 twice as fast. Instead of having + * separate tables for this case, we simply fix up the ACPU clock + * source divider since it's a simple fix up. + */ + if (pll0_l == PLL_491_MHZ) { + pll0_l = PLL_245_MHZ; + pll0_needs_fixup = 1; + } + + /* Select the right table to use. */ + for (lst = acpu_freq_tbl_list; lst->tbl != 0; lst++) { + if (lst->pll0_l == pll0_l && lst->pll1_l == pll1_l + && lst->pll2_l == pll2_l + && lst->pll4_l == pll4_l) { + acpu_freq_tbl = lst->tbl; + break; + } + } + + if (acpu_freq_tbl == NULL) { + pr_crit("Unknown PLL configuration!\n"); + BUG(); + } + + /* Fix up PLL0 source divider if necessary. Also, fix up the AXI to + * the max that's supported by the board (RAM used in board). + */ + axi_160mhz = (pll0_l == PLL_960_MHZ || pll1_l == PLL_960_MHZ); + axi_200mhz = (pll2_l == PLL_1200_MHZ || pll2_l == PLL_800_MHZ); + for (t = &acpu_freq_tbl[0]; t->a11clk_khz != 0; t++) { + + if (pll0_needs_fixup && t->pll == ACPU_PLL_0) + SLOWER_BY(t->a11clk_src_div, 2); + if (axi_160mhz && drv_state.max_axi_khz >= 160000 + && t->ahbclk_khz > 128000) + t->axiclk_khz = 160000; + if (axi_200mhz && drv_state.max_axi_khz >= 200000 + && t->ahbclk_khz > 160000) + t->axiclk_khz = 200000; + } + + t--; + drv_state.max_axi_khz = t->axiclk_khz; + + /* The default 7x27 ACPU clock plan supports running the AXI bus at + * 200 MHz. So we don't classify it as Turbo mode. + */ + if (cpu_is_msm7x27()) + return; + + if (!axi_160mhz) + pr_info("Turbo mode not supported.\n"); + else if (t->axiclk_khz == 160000) + pr_info("Turbo mode supported and enabled.\n"); + else + pr_info("Turbo mode supported but not enabled.\n"); +} + +/* + * Hardware requires the CPU to be dropped to less than MAX_WAIT_FOR_IRQ_KHZ + * before entering a wait for irq low-power mode. Find a suitable rate. + */ +static unsigned long __init find_wait_for_irq_khz(void) +{ + unsigned long found_khz = 0; + int i; + + for (i = 0; acpu_freq_tbl[i].a11clk_khz && + acpu_freq_tbl[i].a11clk_khz <= MAX_WAIT_FOR_IRQ_KHZ; i++) + found_khz = acpu_freq_tbl[i].a11clk_khz; + + return found_khz; +} + +/* Initalize the lpj field in the acpu_freq_tbl. */ +static void __init lpj_init(void) +{ + int i; + const struct clkctl_acpu_speed *base_clk = drv_state.current_speed; + for (i = 0; acpu_freq_tbl[i].a11clk_khz; i++) { + acpu_freq_tbl[i].lpj = cpufreq_scale(loops_per_jiffy, + base_clk->a11clk_khz, + acpu_freq_tbl[i].a11clk_khz); + } +} + +static void __init precompute_stepping(void) +{ + int i, step_idx; + +#define cur_freq acpu_freq_tbl[i].a11clk_khz +#define step_freq acpu_freq_tbl[step_idx].a11clk_khz +#define cur_pll acpu_freq_tbl[i].pll +#define step_pll acpu_freq_tbl[step_idx].pll + + for (i = 0; acpu_freq_tbl[i].a11clk_khz; i++) { + + /* Calculate max "up" step for each destination PLL */ + step_idx = i + 1; + while (step_freq && (step_freq - cur_freq) + <= drv_state.max_speed_delta_khz) { + acpu_freq_tbl[i].up[step_pll] = + &acpu_freq_tbl[step_idx]; + step_idx++; + } + if (step_idx == (i + 1) && step_freq) { + pr_crit("Delta between freqs %u KHz and %u KHz is" + " too high!\n", cur_freq, step_freq); + BUG(); + } + + /* Calculate max "down" step for each destination PLL */ + step_idx = i - 1; + while (step_idx >= 0 && (cur_freq - step_freq) + <= drv_state.max_speed_delta_khz) { + acpu_freq_tbl[i].down[step_pll] = + &acpu_freq_tbl[step_idx]; + step_idx--; + } + if (step_idx == (i - 1) && i > 0) { + pr_crit("Delta between freqs %u KHz and %u KHz is" + " too high!\n", cur_freq, step_freq); + BUG(); + } + } +} + +static void __init print_acpu_freq_tbl(void) +{ + struct clkctl_acpu_speed *t; + short down_idx[ACPU_PLL_END]; + short up_idx[ACPU_PLL_END]; + int i, j; + +#define FREQ_IDX(freq_ptr) (freq_ptr - acpu_freq_tbl) + pr_info("Id CPU-KHz PLL DIV AHB-KHz ADIV AXI-KHz " + "D0 D1 D2 D4 U0 U1 U2 U4\n"); + + t = &acpu_freq_tbl[0]; + for (i = 0; t->a11clk_khz != 0; i++) { + + for (j = 0; j < ACPU_PLL_END; j++) { + down_idx[j] = t->down[j] ? FREQ_IDX(t->down[j]) : -1; + up_idx[j] = t->up[j] ? FREQ_IDX(t->up[j]) : -1; + } + + pr_info("%2d %7d %3d %3d %7d %4d %7d " + "%2d %2d %2d %2d %2d %2d %2d %2d\n", + i, t->a11clk_khz, t->pll, t->a11clk_src_div + 1, + t->ahbclk_khz, t->ahbclk_div + 1, t->axiclk_khz, + down_idx[0], down_idx[1], down_idx[2], down_idx[4], + up_idx[0], up_idx[1], up_idx[2], up_idx[4]); + + t++; + } +} + +static void msm7x25_acpu_pll_hw_bug_fix(void) +{ + unsigned int n; + + /* The 7625 has a hardware bug and in order to select PLL2 we + * must program PLL3. Use the same table, and just fix up the + * numbers on this target. */ + for (n = 0; acpu_freq_tbl[n].a11clk_khz != 0; n++) + if (acpu_freq_tbl[n].pll == ACPU_PLL_2) + acpu_freq_tbl[n].a11clk_src_sel = 3; +} + +static void shared_pll_control_init(void) +{ +#define PLL_REMOTE_SPINLOCK_ID "S:7" + unsigned smem_size; + remote_spin_lock_init(&pll_lock, PLL_REMOTE_SPINLOCK_ID); + pll_control = smem_get_entry(SMEM_CLKREGIM_SOURCES, &smem_size); + + if (!pll_control) + pr_warning("Can't find shared PLL control data structure!\n"); + /* There might be more PLLs than what the application processor knows + * about. But the index used for each PLL is guaranteed to remain the + * same. */ + else if (smem_size < sizeof(struct shared_pll_control)) + pr_warning("Shared PLL control data structure too small!\n"); + else if (pll_control->version != 0xCCEE0001) + pr_warning("Shared PLL control version mismatch!\n"); + else { + pr_info("Shared PLL control available.\n"); + return; + } + + pll_control = NULL; + pr_warning("Falling back to proc_comm PLL control.\n"); +} + +void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *clkdata) +{ + pr_info("acpu_clock_init()\n"); + + drv_state.ebi1_clk = clk_get(NULL, "ebi1_acpu_clk"); + BUG_ON(IS_ERR(drv_state.ebi1_clk)); + + mutex_init(&drv_state.lock); + shared_pll_control_init(); + drv_state.acpu_switch_time_us = clkdata->acpu_switch_time_us; + drv_state.max_speed_delta_khz = clkdata->max_speed_delta_khz; + drv_state.vdd_switch_time_us = clkdata->vdd_switch_time_us; + drv_state.max_axi_khz = clkdata->max_axi_khz; + acpu_freq_tbl_fixup(); + drv_state.wait_for_irq_khz = find_wait_for_irq_khz(); + precompute_stepping(); + if (cpu_is_msm7x25()) + msm7x25_acpu_pll_hw_bug_fix(); + acpuclk_init(); + lpj_init(); + print_acpu_freq_tbl(); +#ifdef CONFIG_CPU_FREQ_MSM + cpufreq_table_init(); + cpufreq_frequency_table_get_attr(freq_table, smp_processor_id()); +#endif +} diff --git a/arch/arm/mach-msm/acpuclock.h b/arch/arm/mach-msm/acpuclock.h index 415de2eb9a5..2aa288476a0 100644 --- a/arch/arm/mach-msm/acpuclock.h +++ b/arch/arm/mach-msm/acpuclock.h @@ -3,7 +3,7 @@ * MSM architecture clock driver header * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2007 QUALCOMM Incorporated + * Copyright (c) 2007-2010, Code Aurora Forum. All rights reserved. * Author: San Mehat * * This software is licensed under the terms of the GNU General Public @@ -20,13 +20,25 @@ #ifndef __ARCH_ARM_MACH_MSM_ACPUCLOCK_H #define __ARCH_ARM_MACH_MSM_ACPUCLOCK_H -int acpuclk_set_rate(unsigned long rate, int for_power_collapse); -unsigned long acpuclk_get_rate(void); +#include + +enum setrate_reason { + SETRATE_CPUFREQ = 0, + SETRATE_SWFI, + SETRATE_PC, + SETRATE_HOTPLUG, + SETRATE_INIT, +}; + +int acpuclk_set_rate(int cpu, unsigned long rate, enum setrate_reason reason); +unsigned long acpuclk_get_rate(int cpu); uint32_t acpuclk_get_switch_time(void); unsigned long acpuclk_wait_for_irq(void); unsigned long acpuclk_power_collapse(void); -unsigned long acpuclk_get_wfi_rate(void); - - +#ifdef CONFIG_ARCH_MSM8960 +void acpuclock_secondary_init(void); +#else +static inline void acpuclock_secondary_init(void) { } #endif +#endif diff --git a/arch/arm/mach-msm/arch-init-scorpion.S b/arch/arm/mach-msm/arch-init-scorpion.S new file mode 100644 index 00000000000..82a6db8bb07 --- /dev/null +++ b/arch/arm/mach-msm/arch-init-scorpion.S @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +/* TODO: + * - style cleanup + * - do we need to do *all* of this at boot? + */ + +.text +.code 32 + +#define DSB .byte 0x4f, 0xf0, 0x7f, 0xf5 +#define ISB .byte 0x6f, 0xf0, 0x7f, 0xf5 + +.equ TCSR_SPARE2, 0xA8700060 + +SET_SA: + ldr r0, =TCSR_SPARE2 + ldr r12, [r0] + + /* pack bits 8,2,0 into 2,1,0 */ + and r0, r12, #0x001 + and r1, r12, #0x004 + and r2, r12, #0x100 + orr r0, r1, lsr #1 + orr r0, r2, lsr #6 + + adr r1, table_l1_acc + mov r0, r0, lsl #2 + ldr r3, [r1, r0] + + /* write 3800XXXX to PVR0F0 */ + orr r0, r3, #0x38000000 + mcr p15, 0, r0, c15, c15, 0 + + /* write XXXX0000 to PVR2F0 */ + mov r1, r3, lsl #16 + mcr p15, 2, r1, c15, c15, 0 + + adr r1, table_l2_acc + and r0, r12, #0x008 + and r2, r12, #0x002 + orr r0, r0, r2, lsl #1 + ldr r2, [r1, r0] + + /* write to L2VR3F1 */ + mcr p15, 3, r2, c15, c15, 1 + + bx lr + +table_l1_acc: + .word 0xFC00 + .word 0xFC00 + .word 0x7C00 + .word 0xFC00 + .word 0x3C00 + .word 0x0400 + .word 0x0C00 + .word 0x1C00 + +table_l2_acc: + .word 0x010102 + .word 0x010102 + .word 0x010101 + .word 0x212102 + +.globl __cpu_early_init +__cpu_early_init: + //; Zero out r0 for use throughout this code. All other GPRs + //; (r1-r3) are set throughout this code to help establish + //; a consistent startup state for any code that follows. + //; Users should add code at the end of this routine to establish + //; their own stack address (r13), add translation page tables, enable + //; the caches, etc. + MOV r0, #0x0 + + + //; Remove hardcoded cache settings. appsbl_handler.s calls Set_SA + //; API to dynamically configure cache for slow/nominal/fast parts + + //; DCIALL to invalidate L2 cache bank (needs to be run 4 times, once per bank) + //; This must be done early in code (prior to enabling the caches) + MOV r1, #0x2 + MCR p15, 0, r1, c9, c0, 6 //; DCIALL bank D ([15:14] == 2'b00) + ORR r1, r1, #0x00004000 + MCR p15, 0, r1, c9, c0, 6 //; DCIALL bank C ([15:14] == 2'b01) + ADD r1, r1, #0x00004000 + MCR p15, 0, r1, c9, c0, 6 //; DCIALL bank B ([15:14] == 2'b10) + ADD r1, r1, #0x00004000 + MCR p15, 0, r1, c9, c0, 6 //; DCIALL bank A ([15:14] == 2'b11) + + //; Initialize the BPCR - setup Global History Mask (GHRM) to all 1's + //; and have all address bits (AM) participate. + //; Different settings can be used to improve performance + // MOVW r1, #0x01FF +.word 0xe30011ff // hardcoded MOVW instruction due to lack of compiler support + // MOVT r1, #0x01FF +.word 0xe34011ff // hardcoded MOVT instruction due to lack of compiler support + MCR p15, 7, r1, c15, c0, 2 //; WCP15_BPCR + + + //; Initialize all I$ Victim Registers to 0 for startup + MCR p15, 0, r0, c9, c1, 0 //; WCP15_ICVIC0 r0 + MCR p15, 0, r0, c9, c1, 1 //; WCP15_ICVIC1 r0 + MCR p15, 0, r0, c9, c1, 2 //; WCP15_ICVIC2 r0 + MCR p15, 0, r0, c9, c1, 3 //; WCP15_ICVIC3 r0 + MCR p15, 0, r0, c9, c1, 4 //; WCP15_ICVIC4 r0 + MCR p15, 0, r0, c9, c1, 5 //; WCP15_ICVIC5 r0 + MCR p15, 0, r0, c9, c1, 6 //; WCP15_ICVIC5 r0 + MCR p15, 0, r0, c9, c1, 7 //; WCP15_ICVIC7 r0 + + //; Initialize all I$ Locked Victim Registers (Unlocked Floors) to 0 + MCR p15, 1, r0, c9, c1, 0 //; WCP15_ICFLOOR0 r0 + MCR p15, 1, r0, c9, c1, 1 //; WCP15_ICFLOOR1 r0 + MCR p15, 1, r0, c9, c1, 2 //; WCP15_ICFLOOR2 r0 + MCR p15, 1, r0, c9, c1, 3 //; WCP15_ICFLOOR3 r0 + MCR p15, 1, r0, c9, c1, 4 //; WCP15_ICFLOOR4 r0 + MCR p15, 1, r0, c9, c1, 5 //; WCP15_ICFLOOR5 r0 + MCR p15, 1, r0, c9, c1, 6 //; WCP15_ICFLOOR6 r0 + MCR p15, 1, r0, c9, c1, 7 //; WCP15_ICFLOOR7 r0 + + //; Initialize all D$ Victim Registers to 0 + MCR p15, 2, r0, c9, c1, 0 //; WP15_DCVIC0 r0 + MCR p15, 2, r0, c9, c1, 1 //; WP15_DCVIC1 r0 + MCR p15, 2, r0, c9, c1, 2 //; WP15_DCVIC2 r0 + MCR p15, 2, r0, c9, c1, 3 //; WP15_DCVIC3 r0 + MCR p15, 2, r0, c9, c1, 4 //; WP15_DCVIC4 r0 + MCR p15, 2, r0, c9, c1, 5 //; WP15_DCVIC5 r0 + MCR p15, 2, r0, c9, c1, 6 //; WP15_DCVIC6 r0 + MCR p15, 2, r0, c9, c1, 7 //; WP15_DCVIC7 r0 + + //; Initialize all D$ Locked VDCtim Registers (Unlocked Floors) to 0 + MCR p15, 3, r0, c9, c1, 0 //; WCP15_DCFLOOR0 r0 + MCR p15, 3, r0, c9, c1, 1 //; WCP15_DCFLOOR1 r0 + MCR p15, 3, r0, c9, c1, 2 //; WCP15_DCFLOOR2 r0 + MCR p15, 3, r0, c9, c1, 3 //; WCP15_DCFLOOR3 r0 + MCR p15, 3, r0, c9, c1, 4 //; WCP15_DCFLOOR4 r0 + MCR p15, 3, r0, c9, c1, 5 //; WCP15_DCFLOOR5 r0 + MCR p15, 3, r0, c9, c1, 6 //; WCP15_DCFLOOR6 r0 + MCR p15, 3, r0, c9, c1, 7 //; WCP15_DCFLOOR7 r0 + + //; Initialize ASID to zero + MCR p15, 0, r0, c13, c0, 1 //; WCP15_CONTEXTIDR r0 + + //; ICIALL to invalidate entire I-Cache + MCR p15, 0, r0, c7, c5, 0 //; ICIALLU + + //; DCIALL to invalidate entire D-Cache + MCR p15, 0, r0, c9, c0, 6 //; DCIALL r0 + + + //; The VBAR (Vector Base Address Register) should be initialized + //; early in your code. We are setting it to zero + MCR p15, 0, r0, c12, c0, 0 //; WCP15_VBAR r0 + + //; Ensure the MCR's above have completed their operation before continuing + DSB + ISB + + //;------------------------------------------------------------------- + //; There are a number of registers that must be set prior to enabling + //; the MMU. The DCAR is one of these registers. We are setting + //; it to zero (no access) to easily detect improper setup in subsequent + //; code sequences + //;------------------------------------------------------------------- + //; Setup DACR (Domain Access Control Register) to zero + MCR p15, 0, r0, c3, c0, 0 //; WCP15_DACR r0 + + //; Setup DCLKCR to allow normal D-Cache line fills + MCR p15, 1, r0, c9, c0, 7 //; WCP15_DCLKCR r0 + + //; Initialize the ADFSR and EFSR registers. + MCR p15, 0, r0, c5, c1, 0 //; ADFSR + MCR p15, 7, r0, c15, c0, 1 //; EFSR + + //; Setup the TLBLKCR + //; Victim = 6'b000000; Floor = 6'b000000; + //; IASIDCFG = 2'b00 (State-Machine); IALLCFG = 2'b01 (Flash); BNA = 1'b0; + MOV r1, #0x02 + MCR p15, 0, r1, c10, c1, 3 //; WCP15_TLBLKCR r1 + + //;Make sure TLBLKCR is complete before continuing + ISB + + //; Invalidate the UTLB + MCR p15, 0, r0, c8, c7, 0 //; UTLBIALL + + //; Make sure UTLB request has been presented to macro before continuing + ISB + + //; setup L2CR1 to some default Instruction and data prefetching values + //; Users may want specific settings for various performance enhancements + //; In Halcyon we do not have broadcasting barriers. So we need to turn + // ; on bit 8 of L2CR1; which DBB:( Disable barrier broadcast ) + MOV r2, #0x100 + MCR p15, 3, r2, c15, c0, 3 //; WCP15_L2CR1 r0 + + + //; Enable Z bit to enable branch prediction (default is off) + MRC p15, 0, r2, c1, c0, 0 //; RCP15_SCTLR r2 + ORR r2, r2, #0x00000800 + MCR p15, 0, r2, c1, c0, 0 //; WCP15_SCTLR r2 + +#ifdef CONFIG_ARCH_QSD8X50 + /* disable predecode repair cache for thumb2 (DPRC, set bit 4 in PVR0F2) */ + mrc p15, 0, r2, c15, c15, 2 + orr r2, r2, #0x10 + mcr p15, 0, r2, c15, c15, 2 +#endif + + mov r1, lr + //; Make sure Link stack is initialized with branch and links to sequential addresses + //; This aids in creating a predictable startup environment + BL SEQ1 +SEQ1: BL SEQ2 +SEQ2: BL SEQ3 +SEQ3: BL SEQ4 +SEQ4: BL SEQ5 +SEQ5: BL SEQ6 +SEQ6: BL SEQ7 +SEQ7: BL SEQ8 +SEQ8: + mov lr, r1 + + //; REMOVE FOLLOWING THREE INSTRUCTIONS WHEN POWER COLLAPSE IS ENA + //;Make sure the DBGOSLSR[LOCK] bit is cleared to allow access to the debug registers + //; Writing anything but the "secret code" to the DBGOSLAR clears the DBGOSLSR[LOCK] bit + MCR p14, 0, r0, c1, c0, 4 //; WCP14_DBGOSLAR r0 + + + //; Read the DBGPRSR to clear the DBGPRSR[STICKYPD] + //; Any read to DBGPRSR clear the STICKYPD bit + //; ISB guarantees the read completes before attempting to + //; execute a CP14 instruction. + MRC p14, 0, r3, c1, c5, 4 //; RCP14_DBGPRSR r3 + ISB + + //; Initialize the Watchpoint Control Registers to zero (optional) + //;;; MCR p14, 0, r0, c0, c0, 7 ; WCP14_DBGWCR0 r0 + //;;; MCR p14, 0, r0, c0, c1, 7 ; WCP14_DBGWCR1 r0 + + + //;---------------------------------------------------------------------- + //; The saved Program Status Registers (SPSRs) should be setup + //; prior to any automatic mode switches. The following + //; code sets these registers up to a known state. Users will need to + //; customize these settings to meet their needs. + //;---------------------------------------------------------------------- + MOV r2, #0x1f + MOV r1, #0x17 //;ABT mode + msr cpsr_c, r1 //;ABT mode + msr spsr_cxfs, r2 //;clear the spsr + MOV r1, #0x1b //;UND mode + msr cpsr_c, r1 //;UND mode + msr spsr_cxfs, r2 //;clear the spsr + MOV r1, #0x11 //;FIQ mode + msr cpsr_c, r1 //;FIQ mode + msr spsr_cxfs, r2 //;clear the spsr + MOV r1, #0x12 //;IRQ mode + msr cpsr_c, r1 //;IRQ mode + msr spsr_cxfs, r2 //;clear the spsr + MOV r1, #0x16 //;Monitor mode + msr cpsr_c, r1 //;Monitor mode + msr spsr_cxfs, r2 //;clear the spsr + MOV r1, #0x13 //;SVC mode + msr cpsr_c, r1 //;SVC mode + msr spsr_cxfs, r2 //;clear the spsr + + + //;---------------------------------------------------------------------- + //; Enabling Error reporting is something users may want to do at + //; some other point in time. We have chosen some default settings + //; that should be reviewed. Most of these registers come up in an + //; unpredictable state after reset. + //;---------------------------------------------------------------------- +//;Start of error and control setting + + //; setup L2CR0 with various L2/TCM control settings + //; enable out of order bus attributes and error reporting + //; this register comes up unpredictable after reset + // MOVW r1, #0x0F0F +.word 0xe3001f0f // hardcoded MOVW instruction due to lack of compiler support + // MOVT r1, #0xC005 +.word 0xe34c1005 // hardcoded MOVW instruction due to lack of compiler support + MCR p15, 3, r1, c15, c0, 1 //; WCP15_L2CR0 r1 + + //; setup L2CPUCR + //; MOV r2, #0xFF + //; Enable I and D cache parity + //;L2CPUCR[7:5] = 3~Rh7 ~V enable parity error reporting for modified, + //;tag, and data parity errors + MOV r2, #0xe0 + MCR p15, 3, r2, c15, c0, 2 //; WCP15_L2CPUCR r2 + + //; setup SPCR + //; enable all error reporting (reset value is unpredicatble for most bits) + MOV r3, #0x0F + MCR p15, 0, r3, c9, c7, 0 //; WCP15_SPCR r3 + + //; setup DMACHCRs (reset value unpredictable) + //; control setting and enable all error reporting + MOV r1, #0x0F + + //; DMACHCR0 = 0000000F + MOV r2, #0x00 //; channel 0 + MCR p15, 0, r2, c11, c0, 0 //; WCP15_DMASELR r2 + MCR p15, 0, r1, c11, c0, 2 //; WCP15_DMACHCR r1 + + //; DMACHCR1 = 0000000F + MOV r2, #0x01 //; channel 1 + MCR p15, 0, r2, c11, c0, 0 //; WCP15_DMASELR r2 + MCR p15, 0, r1, c11, c0, 2 //; WCP15_DMACHCR r1 + + //; DMACHCR2 = 0000000F + MOV r2, #0x02 //; channel 2 + MCR p15, 0, r2, c11, c0, 0 //; WCP15_DMASELR r2 + MCR p15, 0, r1, c11, c0, 2 //; WCP15_DMACHCR r1 + + //; DMACHCR3 = 0000000F + MOV r2, #0x03 //; channel 3 + MCR p15, 0, r2, c11, c0, 0 //; WCP15_DMASELR r2 + MCR p15, 0, r1, c11, c0, 2 //; WCP15_DMACHCR r1 + + //; Set ACTLR (reset unpredictable) + //; Set AVIVT control, error reporting, etc. + //; MOV r3, #0x07 + //; Enable I and D cache parity + //;ACTLR[2:0] = 3'h7 - enable parity error reporting from L2/I$/D$) + //;ACTLR[5:4] = 2'h3 - enable parity + //;ACTLR[19:18] =2'h3 - always generate and check parity(when MMU disabled). + //;Value to be written #0xC0037 + // MOVW r3, #0x0037 +.word 0xe3003037 // hardcoded MOVW instruction due to lack of compiler support + // MOVT r3, #0x000C +.word 0xe340300c // hardcoded MOVW instruction due to lack of compiler support + //; read the version_id to determine if d-cache should be disabled + LDR r2, = 0xa8e00270 //;Read HW_REVISION_NUMBER, HWIO_HW_REVISION_NUMBER_ADDR + LDR r2,[r2] + AND r2,r2,#0xf0000000 //;hw_revision mask off bits 28-31 + //;if HW_revision is 1.0 or older, (revision==0) + CMP r2,#0 + //; Disable d-cache on older QSD8650 (Rev 1.0) silicon + orreq r3, r3, #0x4000 //;disable dcache + MCR p15, 0, r3, c1, c0, 1 //; WCP15_ACTLR r3 + +//;End of error and control setting + + //;---------------------------------------------------------------------- + //; Unlock ETM and read StickyPD to halt the ETM clocks from running. + //; This is required for power saving whether the ETM is used or not. + //;---------------------------------------------------------------------- + + //;Clear ETMOSLSR[LOCK] bit + MOV r1, #0x00000000 + MCR p14, 1, r1, c1, c0, 4 //; WCP14_ETMOSLAR r1 + + //;Clear ETMPDSR[STICKYPD] bit + MRC p14, 1, r2, c1, c5, 4 //; RCP14_ETMPDSR r2 + +/* +#ifdef APPSBL_ETM_ENABLE + ;---------------------------------------------------------------------- + ; Optionally Enable the ETM (Embedded Trace Macro) which is used for debug + ;---------------------------------------------------------------------- + + ; enable ETM clock if disabled + MRC p15, 7, r1, c15, c0, 5 ; RCP15_CPMR r1 + ORR r1, r1, #0x00000008 + MCR p15, 7, r1, c15, c0, 5 ; WCP15_CPMR r1 + ISB + + ; set trigger event to counter1 being zero + MOV r3, #0x00000040 + MCR p14, 1, r3, c0, c2, 0 ; WCP14_ETMTRIGGER r3 + + ; clear ETMSR + MOV r2, #0x00000000 + MCR p14, 1, r2, c0, c4, 0 ; WCP14_ETMSR r2 + + ; clear trace enable single address comparator usage + MCR p14, 1, r2, c0, c7, 0 ; WCP14_ETMTECR2 r2 + + ; set trace enable to always + MOV r2, #0x0000006F + MCR p14, 1, r2, c0, c8, 0 ; WCP14_ETMTEEVR r2 + + ; clear trace enable address range comparator usage and exclude nothing + MOV r2, #0x01000000 + MCR p14, 1, r2, c0, c9, 0 ; WCP14_ETMTECR1 r2 + + ; set view data to always + MOV r2, #0x0000006F + MCR p14, 1, r2, c0, c12, 0 ; WCP14_ETMVDEVR r2 + + ; clear view data single address comparator usage + MOV r2, #0x00000000 + MCR p14, 1, r2, c0, c13, 0 ; WCP14_ETMVDCR1 r2 + + ; clear view data address range comparator usage and exclude nothing + MOV r2, #0x00010000 + MCR p14, 1, r2, c0, c15, 0 ; WCP14_ETMVDCR3 r2 + + ; set counter1 to 194 + MOV r2, #0x000000C2 + MCR p14, 1, r2, c0, c0, 5 ; WCP14_ETMCNTRLDVR1 r2 + + ; set counter1 to never reload + MOV r2, #0x0000406F + MCR p14, 1, r2, c0, c8, 5 ; WCP14_ETMCNTRLDEVR1 r2 + + ; set counter1 to decrement every cycle + MOV r2, #0x0000006F + MCR p14, 1, r2, c0, c4, 5 ; WCP14_ETMCNTENR1 r2 + + ; Set trace synchronization frequency 1024 bytes + MOV r2, #0x00000400 + MCR p14, 1, r2, c0, c8, 7 ; WCP14_ETMSYNCFR r2 + + ; Program etm control register + ; - Set the CPU to ETM clock ratio to 1:1 + ; - Set the ETM to perform data address tracing + MOV r2, #0x00002008 + MCR p14, 1, r2, c0, c0, 0 ; WCP14_ETMCR r2 + ISB +#endif *//* APPSBL_ETM_ENABLE */ + +/* +#ifdef APPSBL_VFP_ENABLE + ;---------------------------------------------------------------------- + ; Perform the following operations if you intend to make use of + ; the VFP/Neon unit. Note that the FMXR instruction requires a CPU ID + ; indicating the VFP unit is present (i.e.Cortex-A8). . + ; Some tools will require full double precision floating point support + ; which will become available in Scorpion pass 2 + ;---------------------------------------------------------------------- + ; allow full access to CP 10 and 11 space for VFP/NEON use + MRC p15, 0, r1, c1, c0, 2 ; Read CP Access Control Register + ORR r1, r1, #0x00F00000 ; enable full access for p10,11 + MCR p15, 0, r1, c1, c0, 2 ; Write CPACR + + ;make sure the CPACR is complete before continuing + ISB + + ; Enable VFP itself (certain OSes may want to dynamically set/clear + ; the enable bit based on the application being executed + MOV r1, #0x40000000 + FMXR FPEXC, r1 +#endif *//* APPSBL_VFP_ENABLE */ + + /* we have no stack, so just tail-call into the SET_SA routine... */ + b SET_SA + +.ltorg diff --git a/arch/arm/mach-msm/avs.c b/arch/arm/mach-msm/avs.c new file mode 100644 index 00000000000..827adab00e2 --- /dev/null +++ b/arch/arm/mach-msm/avs.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "avs.h" + +#define AVSDSCR_INPUT 0x01004860 /* magic # from circuit designer */ +#define TSCSR_INPUT 0x00000001 /* enable temperature sense */ + +#define TEMPRS 16 /* total number of temperature regions */ +#define GET_TEMPR() (avs_get_tscsr() >> 28) /* scale TSCSR[CTEMP] to regions */ + +struct mutex avs_lock; + +static struct avs_state_s +{ + u32 freq_cnt; /* Frequencies supported list */ + short *avs_v; /* Dyanmically allocated storage for + * 2D table of voltages over temp & + * freq. Used as a set of 1D tables. + * Each table is for a single temp. + * For usage see avs_get_voltage + */ + int (*set_vdd) (int); /* Function Ptr for setting voltage */ + int changing; /* Clock frequency is changing */ + u32 freq_idx; /* Current frequency index */ + int vdd; /* Current ACPU voltage */ +} avs_state; + +/* + * Update the AVS voltage vs frequency table, for current temperature + * Adjust based on the AVS delay circuit hardware status + */ +static void avs_update_voltage_table(short *vdd_table) +{ + u32 avscsr; + int cpu; + int vu; + int l2; + int i; + u32 cur_freq_idx; + short cur_voltage; + + cur_freq_idx = avs_state.freq_idx; + cur_voltage = avs_state.vdd; + + avscsr = avs_test_delays(); + AVSDEBUG("avscsr=%x, avsdscr=%x\n", avscsr, avs_get_avsdscr()); + + /* + * Read the results for the various unit's AVS delay circuits + * 2=> up, 1=>down, 0=>no-change + */ + cpu = ((avscsr >> 23) & 2) + ((avscsr >> 16) & 1); + vu = ((avscsr >> 28) & 2) + ((avscsr >> 21) & 1); + l2 = ((avscsr >> 29) & 2) + ((avscsr >> 22) & 1); + + if ((cpu == 3) || (vu == 3) || (l2 == 3)) { + printk(KERN_ERR "AVS: Dly Synth O/P error\n"); + } else if ((cpu == 2) || (l2 == 2) || (vu == 2)) { + /* + * even if one oscillator asks for up, increase the voltage, + * as its an indication we are running outside the + * critical acceptable range of v-f combination. + */ + AVSDEBUG("cpu=%d l2=%d vu=%d\n", cpu, l2, vu); + AVSDEBUG("Voltage up at %d\n", cur_freq_idx); + + if (cur_voltage >= VOLTAGE_MAX) + printk(KERN_ERR + "AVS: Voltage can not get high enough!\n"); + + /* Raise the voltage for all frequencies */ + for (i = 0; i < avs_state.freq_cnt; i++) { + vdd_table[i] = cur_voltage + VOLTAGE_STEP; + if (vdd_table[i] > VOLTAGE_MAX) + vdd_table[i] = VOLTAGE_MAX; + } + } else if ((cpu == 1) && (l2 == 1) && (vu == 1)) { + if ((cur_voltage - VOLTAGE_STEP >= VOLTAGE_MIN) && + (cur_voltage <= vdd_table[cur_freq_idx])) { + vdd_table[cur_freq_idx] = cur_voltage - VOLTAGE_STEP; + AVSDEBUG("Voltage down for %d and lower levels\n", + cur_freq_idx); + + /* clamp to this voltage for all lower levels */ + for (i = 0; i < cur_freq_idx; i++) { + if (vdd_table[i] > vdd_table[cur_freq_idx]) + vdd_table[i] = vdd_table[cur_freq_idx]; + } + } + } +} + +/* + * Return the voltage for the target performance freq_idx and optionally + * use AVS hardware to check the present voltage freq_idx + */ +static short avs_get_target_voltage(int freq_idx, bool update_table) +{ + unsigned cur_tempr = GET_TEMPR(); + unsigned temp_index = cur_tempr*avs_state.freq_cnt; + + /* Table of voltages vs frequencies for this temp */ + short *vdd_table = avs_state.avs_v + temp_index; + + if (update_table) + avs_update_voltage_table(vdd_table); + + return vdd_table[freq_idx]; +} + + +/* + * Set the voltage for the freq_idx and optionally + * use AVS hardware to update the voltage + */ +static int avs_set_target_voltage(int freq_idx, bool update_table) +{ + int rc = 0; + int new_voltage = avs_get_target_voltage(freq_idx, update_table); + if (avs_state.vdd != new_voltage) { + AVSDEBUG("AVS setting V to %d mV @%d\n", + new_voltage, freq_idx); + rc = avs_state.set_vdd(new_voltage); + if (rc) + return rc; + avs_state.vdd = new_voltage; + } + return rc; +} + +/* + * Notify avs of clk frquency transition begin & end + */ +int avs_adjust_freq(u32 freq_idx, int begin) +{ + int rc = 0; + + if (!avs_state.set_vdd) { + /* AVS not initialized */ + return 0; + } + + if (freq_idx >= avs_state.freq_cnt) { + AVSDEBUG("Out of range :%d\n", freq_idx); + return -EINVAL; + } + + mutex_lock(&avs_lock); + if ((begin && (freq_idx > avs_state.freq_idx)) || + (!begin && (freq_idx < avs_state.freq_idx))) { + /* Update voltage before increasing frequency & + * after decreasing frequency + */ + rc = avs_set_target_voltage(freq_idx, 0); + if (rc) + goto aaf_out; + + avs_state.freq_idx = freq_idx; + } + avs_state.changing = begin; +aaf_out: + mutex_unlock(&avs_lock); + + return rc; +} + + +static struct delayed_work avs_work; +static struct workqueue_struct *kavs_wq; +#define AVS_DELAY ((CONFIG_HZ * 50 + 999) / 1000) + +static void do_avs_timer(struct work_struct *work) +{ + int cur_freq_idx; + + mutex_lock(&avs_lock); + if (!avs_state.changing) { + /* Only adjust the voltage if clk is stable */ + cur_freq_idx = avs_state.freq_idx; + avs_set_target_voltage(cur_freq_idx, 1); + } + mutex_unlock(&avs_lock); + queue_delayed_work_on(0, kavs_wq, &avs_work, AVS_DELAY); +} + + +static void __init avs_timer_init(void) +{ + INIT_DELAYED_WORK_DEFERRABLE(&avs_work, do_avs_timer); + queue_delayed_work_on(0, kavs_wq, &avs_work, AVS_DELAY); +} + +static void __exit avs_timer_exit(void) +{ + cancel_delayed_work(&avs_work); +} + +static int __init avs_work_init(void) +{ + kavs_wq = create_workqueue("avs"); + if (!kavs_wq) { + printk(KERN_ERR "AVS initialization failed\n"); + return -EFAULT; + } + avs_timer_init(); + + return 1; +} + +static void __exit avs_work_exit(void) +{ + avs_timer_exit(); + destroy_workqueue(kavs_wq); +} + +int __init avs_init(int (*set_vdd)(int), u32 freq_cnt, u32 freq_idx) +{ + int i; + + mutex_init(&avs_lock); + + if (freq_cnt == 0) + return -EINVAL; + + avs_state.freq_cnt = freq_cnt; + + if (freq_idx >= avs_state.freq_cnt) + return -EINVAL; + + avs_state.avs_v = kmalloc(TEMPRS * avs_state.freq_cnt * + sizeof(avs_state.avs_v[0]), GFP_KERNEL); + + if (avs_state.avs_v == 0) + return -ENOMEM; + + for (i = 0; i < TEMPRS*avs_state.freq_cnt; i++) + avs_state.avs_v[i] = VOLTAGE_MAX; + + avs_reset_delays(AVSDSCR_INPUT); + avs_set_tscsr(TSCSR_INPUT); + + avs_state.set_vdd = set_vdd; + avs_state.changing = 0; + avs_state.freq_idx = -1; + avs_state.vdd = -1; + avs_adjust_freq(freq_idx, 0); + + avs_work_init(); + + return 0; +} + +void __exit avs_exit() +{ + avs_work_exit(); + + kfree(avs_state.avs_v); +} + + diff --git a/arch/arm/mach-msm/avs.h b/arch/arm/mach-msm/avs.h new file mode 100644 index 00000000000..a549e9de7f2 --- /dev/null +++ b/arch/arm/mach-msm/avs.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef AVS_H +#define AVS_H + +#define VOLTAGE_MIN 1000 /* mV */ +#define VOLTAGE_MAX 1250 +#define VOLTAGE_STEP 25 + +int __init avs_init(int (*set_vdd)(int), u32 freq_cnt, u32 freq_idx); +void __exit avs_exit(void); + +int avs_adjust_freq(u32 freq_index, int begin); + +/* Routines exported from avs_hw.S */ +#ifdef CONFIG_MSM_CPU_AVS +u32 avs_test_delays(void); +#else +static inline u32 avs_test_delays(void) +{ return 0; } +#endif + +#ifdef CONFIG_MSM_AVS_HW +u32 avs_reset_delays(u32 avsdscr); +u32 avs_get_avscsr(void); +u32 avs_get_avsdscr(void); +u32 avs_get_tscsr(void); +void avs_set_tscsr(u32 to_tscsr); +void avs_disable(void); +#else +static inline u32 avs_reset_delays(u32 avsdscr) +{ return 0; } +static inline u32 avs_get_avscsr(void) +{ return 0; } +static inline u32 avs_get_avsdscr(void) +{ return 0; } +static inline u32 avs_get_tscsr(void) +{ return 0; } +static inline void avs_set_tscsr(u32 to_tscsr) {} +static inline void avs_disable(void) {} +#endif + +/*#define AVSDEBUG(x...) pr_info("AVS: " x);*/ +#define AVSDEBUG(...) + +#define AVS_DISABLE(cpu) do { \ + if (get_cpu() == (cpu)) \ + avs_disable(); \ + put_cpu(); \ + } while (0); + +#define AVS_ENABLE(cpu, x) do { \ + if (get_cpu() == (cpu)) \ + avs_reset_delays((x)); \ + put_cpu(); \ + } while (0); + +#endif /* AVS_H */ diff --git a/arch/arm/mach-msm/avs_hw.S b/arch/arm/mach-msm/avs_hw.S new file mode 100644 index 00000000000..1cc3ce0b107 --- /dev/null +++ b/arch/arm/mach-msm/avs_hw.S @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + .text + +#ifdef CONFIG_MSM_CPU_AVS + .global avs_test_delays +avs_test_delays: + +/* Read r1=CPMR and enable Never Sleep for VSLPDLY */ + mrc p15, 7, r1, c15, c0, 5 + orr r12, r1, #3, 24 + mcr p15, 7, r12, c15, c0, 5 + +/* Read r2=CPACR and enable full access to CP10 and CP11 space */ + mrc p15, 0, r2, c1, c0, 2 + orr r12, r2, #(0xf << 20) + mcr p15, 0, r12, c1, c0, 2 + isb + +/* Read r3=FPEXC and or in FP enable, VFP/ASE enable = FPEXC[30]; */ + fmrx r3, fpexc + orr r12, r3, #1, 2 + fmxr fpexc, r12 + +/* + * Do floating-point operations to prime the VFP pipeline. Use + * fcpyd d0, d0 as a floating point nop. This avoids changing VFP + * state. + */ + fcpyd d0, d0 + fcpyd d0, d0 + fcpyd d0, d0 + +/* Read r0=AVSCSR to get status from CPU, VFP, and L2 ring oscillators */ + mrc p15, 7, r0, c15, c1, 7 + +/* Restore FPEXC */ + fmxr fpexc, r3 + +/* Restore CPACR */ + MCR p15, 0, r2, c1, c0, 2 + +/* Restore CPMR */ + mcr p15, 7, r1, c15, c0, 5 + isb + + bx lr +#endif + + + .global avs_get_avscsr +/* Read r0=AVSCSR to get status from CPU, VFP, and L2 ring oscillators */ + +avs_get_avscsr: + mrc p15, 7, r0, c15, c1, 7 + bx lr + + .global avs_get_avsdscr +/* Read r0=AVSDSCR to get the AVS Delay Synthesizer control settings */ + +avs_get_avsdscr: + mrc p15, 7, r0, c15, c0, 6 + bx lr + + + + + .global avs_get_tscsr +/* Read r0=TSCSR to get temperature sensor control and status */ + +avs_get_tscsr: + mrc p15, 7, r0, c15, c1, 0 + bx lr + + .global avs_set_tscsr +/* Write TSCSR=r0 to set temperature sensor control and status */ + +avs_set_tscsr: + mcr p15, 7, r0, c15, c1, 0 + bx lr + + + + + + .global avs_reset_delays +avs_reset_delays: + +/* AVSDSCR(dly) to program delay */ + mcr p15, 7, r0, c15, c0, 6 + +/* Read r0=AVSDSCR */ + mrc p15, 7, r0, c15, c0, 6 + +/* AVSCSR(0x61) to enable CPU, V and L2 AVS module */ + mov r3, #0x61 + mcr p15, 7, r3, c15, c1, 7 + + bx lr + + + + .global avs_disable +avs_disable: + +/* Clear AVSCSR */ + mov r0, #0 + +/* Write AVSCSR */ + mcr p15, 7, r0, c15, c1, 7 + + bx lr + + .end + + diff --git a/arch/arm/mach-msm/bam_dmux.c b/arch/arm/mach-msm/bam_dmux.c new file mode 100644 index 00000000000..667016d9e3c --- /dev/null +++ b/arch/arm/mach-msm/bam_dmux.c @@ -0,0 +1,784 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * BAM DMUX module. + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BAM_CH_LOCAL_OPEN 0x1 +#define BAM_CH_REMOTE_OPEN 0x2 + +#define BAM_MUX_HDR_MAGIC_NO 0x33fc + +#define BAM_MUX_HDR_CMD_DATA 0 +#define BAM_MUX_HDR_CMD_OPEN 1 +#define BAM_MUX_HDR_CMD_CLOSE 2 + +#define RX_STATE_HDR_QUEUED 0 +#define RX_STATE_DATA_QUEUED 1 + + +static int msm_bam_dmux_debug_enable; +module_param_named(debug_enable, msm_bam_dmux_debug_enable, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#if defined(DEBUG) +static uint32_t bam_dmux_read_cnt; +static uint32_t bam_dmux_write_cnt; +static uint32_t bam_dmux_write_cpy_cnt; +static uint32_t bam_dmux_write_cpy_bytes; + +#define DBG(x...) do { \ + if (msm_bam_dmux_debug_enable) \ + pr_debug(x); \ + } while (0) + +#define DBG_INC_READ_CNT(x) do { \ + bam_dmux_read_cnt += (x); \ + if (msm_bam_dmux_debug_enable) \ + pr_debug("%s: total read bytes %u\n", \ + __func__, bam_dmux_read_cnt); \ + } while (0) + +#define DBG_INC_WRITE_CNT(x) do { \ + bam_dmux_write_cnt += (x); \ + if (msm_bam_dmux_debug_enable) \ + pr_debug("%s: total written bytes %u\n", \ + __func__, bam_dmux_write_cnt); \ + } while (0) + +#define DBG_INC_WRITE_CPY(x) do { \ + bam_dmux_write_cpy_bytes += (x); \ + bam_dmux_write_cpy_cnt++; \ + if (msm_bam_dmux_debug_enable) \ + pr_debug("%s: total write copy cnt %u, bytes %u\n", \ + __func__, bam_dmux_write_cpy_cnt, \ + bam_dmux_write_cpy_bytes); \ + } while (0) +#else +#define DBG(x...) do { } while (0) +#define DBG_INC_READ_CNT(x...) do { } while (0) +#define DBG_INC_WRITE_CNT(x...) do { } while (0) +#define DBG_INC_WRITE_CPY(x...) do { } while (0) +#endif + +struct bam_ch_info { + uint32_t status; + void (*receive_cb)(void *, struct sk_buff *); + void (*write_done)(void *, struct sk_buff *); + void *priv; + spinlock_t lock; +}; + +struct tx_pkt_info { + struct sk_buff *skb; + dma_addr_t dma_address; + char is_cmd; + uint32_t len; + struct work_struct work; +}; + +struct rx_pkt_info { + struct sk_buff *skb; + dma_addr_t dma_address; + struct work_struct work; + struct list_head list_node; +}; + +#define A2_NUM_PIPES 6 +#define A2_SUMMING_THRESHOLD 4096 +#define A2_DEFAULT_DESCRIPTORS 32 +#define A2_PHYS_BASE 0x124C2000 +#define A2_PHYS_SIZE 0x2000 +#define BUFFER_SIZE 2048 +#define NUM_BUFFERS 32 +static struct delayed_work bam_init_work; +static struct sps_bam_props a2_props; +static struct sps_pipe *bam_tx_pipe; +static struct sps_pipe *bam_rx_pipe; +static struct sps_connect tx_connection; +static struct sps_connect rx_connection; +static struct sps_mem_buffer tx_desc_mem_buf; +static struct sps_mem_buffer rx_desc_mem_buf; +static struct sps_register_event tx_register_event; + +static struct bam_ch_info bam_ch[BAM_DMUX_NUM_CHANNELS]; +static int bam_mux_initialized; + +static LIST_HEAD(bam_rx_pool); +static DEFINE_MUTEX(bam_rx_pool_lock); + +struct bam_mux_hdr { + uint16_t magic_num; + uint8_t reserved; + uint8_t cmd; + uint8_t pad_len; + uint8_t ch_id; + uint16_t pkt_len; +}; + +static void bam_mux_write_done(struct work_struct *work); +static void handle_bam_mux_cmd(struct work_struct *work); +static void rx_timer_work_func(struct work_struct *work); + +static DEFINE_MUTEX(bam_mux_lock); +static DECLARE_WORK(rx_timer_work, rx_timer_work_func); + +static struct workqueue_struct *bam_mux_rx_workqueue; +static struct workqueue_struct *bam_mux_tx_workqueue; + +#define bam_ch_is_open(x) \ + (bam_ch[(x)].status == (BAM_CH_LOCAL_OPEN | BAM_CH_REMOTE_OPEN)) + +#define bam_ch_is_local_open(x) \ + (bam_ch[(x)].status & BAM_CH_LOCAL_OPEN) + +#define bam_ch_is_remote_open(x) \ + (bam_ch[(x)].status & BAM_CH_REMOTE_OPEN) + +static void queue_rx(void) +{ + void *ptr; + struct rx_pkt_info *info; + + info = kmalloc(sizeof(struct rx_pkt_info), GFP_KERNEL); + if (!info) + return; /*need better way to handle this */ + + INIT_WORK(&info->work, handle_bam_mux_cmd); + + info->skb = __dev_alloc_skb(BUFFER_SIZE, GFP_KERNEL); + ptr = skb_put(info->skb, BUFFER_SIZE); + + mutex_lock(&bam_rx_pool_lock); + list_add_tail(&info->list_node, &bam_rx_pool); + mutex_unlock(&bam_rx_pool_lock); + + /* need a way to handle error case */ + info->dma_address = dma_map_single(NULL, ptr, BUFFER_SIZE, + DMA_FROM_DEVICE); + sps_transfer_one(bam_rx_pipe, info->dma_address, + BUFFER_SIZE, info, 0); +} + +static void bam_mux_process_data(struct sk_buff *rx_skb) +{ + unsigned long flags; + struct bam_mux_hdr *rx_hdr; + + rx_hdr = (struct bam_mux_hdr *)rx_skb->data; + + rx_skb->data = (unsigned char *)(rx_hdr + 1); + rx_skb->tail = rx_skb->data + rx_hdr->pkt_len; + rx_skb->len = rx_hdr->pkt_len; + + spin_lock_irqsave(&bam_ch[rx_hdr->ch_id].lock, flags); + if (bam_ch[rx_hdr->ch_id].receive_cb) + bam_ch[rx_hdr->ch_id].receive_cb(bam_ch[rx_hdr->ch_id].priv, + rx_skb); + else + dev_kfree_skb_any(rx_skb); + spin_unlock_irqrestore(&bam_ch[rx_hdr->ch_id].lock, flags); + + queue_rx(); +} + +static void handle_bam_mux_cmd(struct work_struct *work) +{ + unsigned long flags; + struct bam_mux_hdr *rx_hdr; + struct rx_pkt_info *info; + struct sk_buff *rx_skb; + + info = container_of(work, struct rx_pkt_info, work); + rx_skb = info->skb; + kfree(info); + + rx_hdr = (struct bam_mux_hdr *)rx_skb->data; + + DBG_INC_READ_CNT(sizeof(struct bam_mux_hdr)); + DBG("%s: magic %x reserved %d cmd %d pad %d ch %d len %d\n", __func__, + rx_hdr->magic_num, rx_hdr->reserved, rx_hdr->cmd, + rx_hdr->pad_len, rx_hdr->ch_id, rx_hdr->pkt_len); + if (rx_hdr->magic_num != BAM_MUX_HDR_MAGIC_NO) { + pr_err("%s: dropping invalid hdr. magic %x reserved %d cmd %d" + " pad %d ch %d len %d\n", __func__, + rx_hdr->magic_num, rx_hdr->reserved, rx_hdr->cmd, + rx_hdr->pad_len, rx_hdr->ch_id, rx_hdr->pkt_len); + dev_kfree_skb_any(rx_skb); + queue_rx(); + return; + } + switch (rx_hdr->cmd) { + case BAM_MUX_HDR_CMD_DATA: + DBG_INC_READ_CNT(rx_hdr->pkt_len); + bam_mux_process_data(rx_skb); + break; + case BAM_MUX_HDR_CMD_OPEN: + spin_lock_irqsave(&bam_ch[rx_hdr->ch_id].lock, flags); + bam_ch[rx_hdr->ch_id].status |= BAM_CH_REMOTE_OPEN; + spin_unlock_irqrestore(&bam_ch[rx_hdr->ch_id].lock, flags); + dev_kfree_skb_any(rx_skb); + queue_rx(); + break; + case BAM_MUX_HDR_CMD_CLOSE: + /* probably should drop pending write */ + spin_lock_irqsave(&bam_ch[rx_hdr->ch_id].lock, flags); + bam_ch[rx_hdr->ch_id].status &= ~BAM_CH_REMOTE_OPEN; + spin_unlock_irqrestore(&bam_ch[rx_hdr->ch_id].lock, flags); + dev_kfree_skb_any(rx_skb); + queue_rx(); + break; + default: + pr_err("%s: dropping invalid hdr. magic %x reserved %d cmd %d" + " pad %d ch %d len %d\n", __func__, + rx_hdr->magic_num, rx_hdr->reserved, rx_hdr->cmd, + rx_hdr->pad_len, rx_hdr->ch_id, rx_hdr->pkt_len); + dev_kfree_skb_any(rx_skb); + queue_rx(); + return; + } +} + +static int bam_mux_write_cmd(void *data, uint32_t len) +{ + int rc; + struct tx_pkt_info *pkt; + dma_addr_t dma_address; + + mutex_lock(&bam_mux_lock); + pkt = kmalloc(sizeof(struct tx_pkt_info), GFP_KERNEL); + if (pkt == NULL) { + pr_err("%s: mem alloc for tx_pkt_info failed\n", __func__); + rc = -ENOMEM; + mutex_unlock(&bam_mux_lock); + return rc; + } + + dma_address = dma_map_single(NULL, data, len, + DMA_TO_DEVICE); + if (!dma_address) { + pr_err("%s: dma_map_single() failed\n", __func__); + rc = -ENOMEM; + mutex_unlock(&bam_mux_lock); + return rc; + } + pkt->skb = (struct sk_buff *)(data); + pkt->len = len; + pkt->dma_address = dma_address; + pkt->is_cmd = 1; + rc = sps_transfer_one(bam_tx_pipe, dma_address, len, + pkt, SPS_IOVEC_FLAG_INT | SPS_IOVEC_FLAG_EOT); + + mutex_unlock(&bam_mux_lock); + return rc; +} + +static void bam_mux_write_done(struct work_struct *work) +{ + struct sk_buff *skb; + struct bam_mux_hdr *hdr; + struct tx_pkt_info *info; + + info = container_of(work, struct tx_pkt_info, work); + skb = info->skb; + kfree(info); + hdr = (struct bam_mux_hdr *)skb->data; + DBG_INC_WRITE_CNT(skb->data_len); + if (bam_ch[hdr->ch_id].write_done) + bam_ch[hdr->ch_id].write_done( + bam_ch[hdr->ch_id].priv, skb); + else + dev_kfree_skb_any(skb); +} + +int msm_bam_dmux_write(uint32_t id, struct sk_buff *skb) +{ + int rc = 0; + struct bam_mux_hdr *hdr; + unsigned long flags; + struct sk_buff *new_skb = NULL; + dma_addr_t dma_address; + struct tx_pkt_info *pkt; + + if (id >= BAM_DMUX_NUM_CHANNELS) + return -EINVAL; + if (!skb) + return -EINVAL; + if (!bam_mux_initialized) + return -ENODEV; + + DBG("%s: writing to ch %d len %d\n", __func__, id, skb->len); + spin_lock_irqsave(&bam_ch[id].lock, flags); + if (!bam_ch_is_open(id)) { + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + pr_err("%s: port not open: %d\n", __func__, bam_ch[id].status); + return -ENODEV; + } + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + + /* if skb do not have any tailroom for padding, + copy the skb into a new expanded skb */ + if ((skb->len & 0x3) && (skb_tailroom(skb) < (4 - (skb->len & 0x3)))) { + /* revisit, probably dev_alloc_skb and memcpy is effecient */ + new_skb = skb_copy_expand(skb, skb_headroom(skb), + 4 - (skb->len & 0x3), GFP_ATOMIC); + if (new_skb == NULL) { + pr_err("%s: cannot allocate skb\n", __func__); + return -ENOMEM; + } + dev_kfree_skb_any(skb); + skb = new_skb; + DBG_INC_WRITE_CPY(skb->len); + } + + hdr = (struct bam_mux_hdr *)skb_push(skb, sizeof(struct bam_mux_hdr)); + + /* caller should allocate for hdr and padding + hdr is fine, padding is tricky */ + hdr->magic_num = BAM_MUX_HDR_MAGIC_NO; + hdr->cmd = BAM_MUX_HDR_CMD_DATA; + hdr->reserved = 0; + hdr->ch_id = id; + hdr->pkt_len = skb->len - sizeof(struct bam_mux_hdr); + if (skb->len & 0x3) + skb_put(skb, 4 - (skb->len & 0x3)); + + hdr->pad_len = skb->len - (sizeof(struct bam_mux_hdr) + hdr->pkt_len); + + DBG("%s: data %p, tail %p skb len %d pkt len %d pad len %d\n", + __func__, skb->data, skb->tail, skb->len, + hdr->pkt_len, hdr->pad_len); + + pkt = kmalloc(sizeof(struct tx_pkt_info), GFP_ATOMIC); + if (pkt == NULL) { + pr_err("%s: mem alloc for tx_pkt_info failed\n", __func__); + if (new_skb) + dev_kfree_skb_any(new_skb); + return -ENOMEM; + } + + dma_address = dma_map_single(NULL, skb->data, skb->len, + DMA_TO_DEVICE); + if (!dma_address) { + pr_err("%s: dma_map_single() failed\n", __func__); + if (new_skb) + dev_kfree_skb_any(new_skb); + kfree(pkt); + return -ENOMEM; + } + pkt->skb = skb; + pkt->dma_address = dma_address; + pkt->is_cmd = 0; + INIT_WORK(&pkt->work, bam_mux_write_done); + rc = sps_transfer_one(bam_tx_pipe, dma_address, skb->len, + pkt, SPS_IOVEC_FLAG_INT | SPS_IOVEC_FLAG_EOT); + return rc; +} + +int msm_bam_dmux_open(uint32_t id, void *priv, + void (*receive_cb)(void *, struct sk_buff *), + void (*write_done)(void *, struct sk_buff *)) +{ + struct bam_mux_hdr *hdr; + unsigned long flags; + int rc = 0; + + DBG("%s: opening ch %d\n", __func__, id); + if (!bam_mux_initialized) + return -ENODEV; + if (id >= BAM_DMUX_NUM_CHANNELS) + return -EINVAL; + + hdr = kmalloc(sizeof(struct bam_mux_hdr), GFP_KERNEL); + if (hdr == NULL) { + pr_err("%s: hdr kmalloc failed. ch: %d\n", __func__, id); + return -ENOMEM; + } + spin_lock_irqsave(&bam_ch[id].lock, flags); + if (bam_ch_is_open(id)) { + DBG("%s: Already opened %d\n", __func__, id); + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + kfree(hdr); + goto open_done; + } + if (!bam_ch_is_remote_open(id)) { + DBG("%s: Remote not open; ch: %d\n", __func__, id); + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + kfree(hdr); + rc = -ENODEV; + goto open_done; + } + + bam_ch[id].receive_cb = receive_cb; + bam_ch[id].write_done = write_done; + bam_ch[id].priv = priv; + bam_ch[id].status |= BAM_CH_LOCAL_OPEN; + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + + hdr->magic_num = BAM_MUX_HDR_MAGIC_NO; + hdr->cmd = BAM_MUX_HDR_CMD_OPEN; + hdr->reserved = 0; + hdr->ch_id = id; + hdr->pkt_len = 0; + hdr->pad_len = 0; + + rc = bam_mux_write_cmd((void *)hdr, sizeof(struct bam_mux_hdr)); + +open_done: + DBG("%s: opened ch %d\n", __func__, id); + return rc; +} + +int msm_bam_dmux_close(uint32_t id) +{ + struct bam_mux_hdr *hdr; + unsigned long flags; + int rc; + + if (id >= BAM_DMUX_NUM_CHANNELS) + return -EINVAL; + DBG("%s: closing ch %d\n", __func__, id); + if (!bam_mux_initialized) + return -ENODEV; + spin_lock_irqsave(&bam_ch[id].lock, flags); + + bam_ch[id].write_done = NULL; + bam_ch[id].receive_cb = NULL; + bam_ch[id].priv = NULL; + bam_ch[id].status &= ~BAM_CH_LOCAL_OPEN; + spin_unlock_irqrestore(&bam_ch[id].lock, flags); + + hdr = kmalloc(sizeof(struct bam_mux_hdr), GFP_KERNEL); + if (hdr == NULL) { + pr_err("%s: hdr kmalloc failed. ch: %d\n", __func__, id); + return -ENOMEM; + } + hdr->magic_num = BAM_MUX_HDR_MAGIC_NO; + hdr->cmd = BAM_MUX_HDR_CMD_CLOSE; + hdr->reserved = 0; + hdr->ch_id = id; + hdr->pkt_len = 0; + hdr->pad_len = 0; + + rc = bam_mux_write_cmd((void *)hdr, sizeof(struct bam_mux_hdr)); + + DBG("%s: closed ch %d\n", __func__, id); + return rc; +} + +static void rx_timer_work_func(struct work_struct *work) +{ + struct sps_iovec iov; + struct list_head *node; + struct rx_pkt_info *info; + + while (1) { + sps_get_iovec(bam_rx_pipe, &iov); + if (iov.addr == 0) + break; + mutex_lock(&bam_rx_pool_lock); + node = bam_rx_pool.next; + list_del(node); + mutex_unlock(&bam_rx_pool_lock); + info = container_of(node, struct rx_pkt_info, list_node); + handle_bam_mux_cmd(&info->work); + } + + msleep(1); + queue_work(bam_mux_rx_workqueue, &rx_timer_work); +} + +static void bam_mux_tx_notify(struct sps_event_notify *notify) +{ + struct tx_pkt_info *pkt; + + DBG("%s: event %d notified\n", __func__, notify->event_id); + + switch (notify->event_id) { + case SPS_EVENT_EOT: + pkt = notify->data.transfer.user; + if (!pkt->is_cmd) { + dma_unmap_single(NULL, pkt->dma_address, + pkt->skb->len, + DMA_TO_DEVICE); + queue_work(bam_mux_tx_workqueue, &pkt->work); + } else { + dma_unmap_single(NULL, pkt->dma_address, + pkt->len, + DMA_TO_DEVICE); + kfree(pkt->skb); + kfree(pkt); + } + break; + default: + pr_err("%s: recieved unexpected event id %d\n", __func__, + notify->event_id); + } +} + +#ifdef CONFIG_DEBUG_FS + +static int debug_tbl(char *buf, int max) +{ + int i = 0; + int j; + + for (j = 0; j < BAM_DMUX_NUM_CHANNELS; ++j) { + i += scnprintf(buf + i, max - i, + "ch%02d local open=%s remote open=%s\n", + j, bam_ch_is_local_open(j) ? "Y" : "N", + bam_ch_is_remote_open(j) ? "Y" : "N"); + } + + return i; +} + +#define DEBUG_BUFMAX 4096 +static char debug_buffer[DEBUG_BUFMAX]; + +static ssize_t debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int (*fill)(char *buf, int max) = file->private_data; + int bsize = fill(debug_buffer, DEBUG_BUFMAX); + return simple_read_from_buffer(buf, count, ppos, debug_buffer, bsize); +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + + +static const struct file_operations debug_ops = { + .read = debug_read, + .open = debug_open, +}; + +static void debug_create(const char *name, mode_t mode, + struct dentry *dent, + int (*fill)(char *buf, int max)) +{ + debugfs_create_file(name, mode, dent, fill, &debug_ops); +} + +#endif + +static void bam_init(struct work_struct *work) +{ + u32 h; + dma_addr_t dma_addr; + int ret; + void *a2_virt_addr; + int i; + + /* init BAM */ + a2_virt_addr = ioremap_nocache(A2_PHYS_BASE, A2_PHYS_SIZE); + if (!a2_virt_addr) { + pr_err("%s: ioremap failed\n", __func__); + ret = -ENOMEM; + goto register_bam_failed; + } + a2_props.phys_addr = A2_PHYS_BASE; + a2_props.virt_addr = a2_virt_addr; + a2_props.virt_size = A2_PHYS_SIZE; + a2_props.irq = A2_BAM_IRQ; + a2_props.num_pipes = A2_NUM_PIPES; + a2_props.summing_threshold = A2_SUMMING_THRESHOLD; + /* need to free on tear down */ + ret = sps_register_bam_device(&a2_props, &h); + if (ret < 0) { + pr_err("%s: register bam error %d\n", __func__, ret); + goto register_bam_failed; + } + + bam_tx_pipe = sps_alloc_endpoint(); + if (bam_tx_pipe == NULL) { + pr_err("%s: tx alloc endpoint failed\n", __func__); + ret = -ENOMEM; + goto register_bam_failed; + } + ret = sps_get_config(bam_tx_pipe, &tx_connection); + if (ret) { + pr_err("%s: tx get config failed %d\n", __func__, ret); + goto tx_get_config_failed; + } + + tx_connection.source = SPS_DEV_HANDLE_MEM; + tx_connection.src_pipe_index = 0; + tx_connection.destination = h; + tx_connection.dest_pipe_index = 4; + tx_connection.mode = SPS_MODE_DEST; + tx_connection.options = SPS_O_AUTO_ENABLE | SPS_O_EOT; + tx_desc_mem_buf.size = 0x800; /* 2k */ + tx_desc_mem_buf.base = dma_alloc_coherent(NULL, tx_desc_mem_buf.size, + &dma_addr, 0); + if (tx_desc_mem_buf.base == NULL) { + pr_err("%s: tx memory alloc failed\n", __func__); + ret = -ENOMEM; + goto tx_mem_failed; + } + tx_desc_mem_buf.phys_base = dma_addr; + memset(tx_desc_mem_buf.base, 0x0, tx_desc_mem_buf.size); + tx_connection.desc = tx_desc_mem_buf; + tx_connection.event_thresh = 0x10; + + ret = sps_connect(bam_tx_pipe, &tx_connection); + if (ret < 0) { + pr_err("%s: tx connect error %d\n", __func__, ret); + goto tx_connect_failed; + } + + bam_rx_pipe = sps_alloc_endpoint(); + if (bam_rx_pipe == NULL) { + pr_err("%s: rx alloc endpoint failed\n", __func__); + ret = -ENOMEM; + goto tx_connect_failed; + } + ret = sps_get_config(bam_rx_pipe, &rx_connection); + if (ret) { + pr_err("%s: rx get config failed %d\n", __func__, ret); + goto rx_get_config_failed; + } + + rx_connection.source = h; + rx_connection.src_pipe_index = 5; + rx_connection.destination = SPS_DEV_HANDLE_MEM; + rx_connection.dest_pipe_index = 1; + rx_connection.mode = SPS_MODE_SRC; + rx_connection.options = SPS_O_AUTO_ENABLE | SPS_O_EOT | + SPS_O_ACK_TRANSFERS | SPS_O_POLL; + rx_desc_mem_buf.size = 0x800; /* 2k */ + rx_desc_mem_buf.base = dma_alloc_coherent(NULL, rx_desc_mem_buf.size, + &dma_addr, 0); + if (rx_desc_mem_buf.base == NULL) { + pr_err("%s: rx memory alloc failed\n", __func__); + ret = -ENOMEM; + goto rx_mem_failed; + } + rx_desc_mem_buf.phys_base = dma_addr; + memset(rx_desc_mem_buf.base, 0x0, rx_desc_mem_buf.size); + rx_connection.desc = rx_desc_mem_buf; + rx_connection.event_thresh = 0x10; + + ret = sps_connect(bam_rx_pipe, &rx_connection); + if (ret < 0) { + pr_err("%s: rx connect error %d\n", __func__, ret); + goto rx_connect_failed; + } + + tx_register_event.options = SPS_O_EOT; + tx_register_event.mode = SPS_TRIGGER_CALLBACK; + tx_register_event.xfer_done = NULL; + tx_register_event.callback = bam_mux_tx_notify; + tx_register_event.user = NULL; + ret = sps_register_event(bam_tx_pipe, &tx_register_event); + if (ret < 0) { + pr_err("%s: tx register event error %d\n", __func__, ret); + goto rx_event_reg_failed; + } + + bam_mux_initialized = 1; + for (i = 0; i < NUM_BUFFERS; ++i) + queue_rx(); + + queue_work(bam_mux_rx_workqueue, &rx_timer_work); + return; + +rx_event_reg_failed: + sps_disconnect(bam_rx_pipe); +rx_connect_failed: + dma_free_coherent(NULL, rx_desc_mem_buf.size, rx_desc_mem_buf.base, + rx_desc_mem_buf.phys_base); +rx_mem_failed: + sps_disconnect(bam_tx_pipe); +rx_get_config_failed: + sps_free_endpoint(bam_rx_pipe); +tx_connect_failed: + dma_free_coherent(NULL, tx_desc_mem_buf.size, tx_desc_mem_buf.base, + tx_desc_mem_buf.phys_base); +tx_get_config_failed: + sps_free_endpoint(bam_tx_pipe); +tx_mem_failed: + sps_deregister_bam_device(h); +register_bam_failed: + /*destroy_workqueue(bam_mux_workqueue);*/ + /*return ret;*/ + return; +} +static int bam_dmux_probe(struct platform_device *pdev) +{ + int rc; + + DBG("%s probe called\n", __func__); + if (bam_mux_initialized) + return 0; + + bam_mux_rx_workqueue = create_singlethread_workqueue("bam_dmux_rx"); + if (!bam_mux_rx_workqueue) + return -ENOMEM; + + bam_mux_tx_workqueue = create_singlethread_workqueue("bam_dmux_tx"); + if (!bam_mux_tx_workqueue) { + destroy_workqueue(bam_mux_rx_workqueue); + return -ENOMEM; + } + + for (rc = 0; rc < BAM_DMUX_NUM_CHANNELS; ++rc) + spin_lock_init(&bam_ch[rc].lock); + + /* switch over to A2 power status mechanism when avaliable */ + INIT_DELAYED_WORK(&bam_init_work, bam_init); + schedule_delayed_work(&bam_init_work, msecs_to_jiffies(40000)); + + return 0; +} + +static struct platform_driver bam_dmux_driver = { + .probe = bam_dmux_probe, + .driver = { + .name = "BAM_RMNT", + .owner = THIS_MODULE, + }, +}; + +static int __init bam_dmux_init(void) +{ +#ifdef CONFIG_DEBUG_FS + struct dentry *dent; + + dent = debugfs_create_dir("bam_dmux", 0); + if (!IS_ERR(dent)) + debug_create("tbl", 0444, dent, debug_tbl); +#endif + return platform_driver_register(&bam_dmux_driver); +} + +module_init(bam_dmux_init); +MODULE_DESCRIPTION("MSM BAM DMUX"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/bms-batterydata.c b/arch/arm/mach-msm/bms-batterydata.c new file mode 100644 index 00000000000..42871536065 --- /dev/null +++ b/arch/arm/mach-msm/bms-batterydata.c @@ -0,0 +1,93 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +static struct single_row_lut fcc_temp = { + .x = {-30, -20, -10, 0, 10, 25, 40, 60}, + .y = {1103, 1179, 1284, 1330, 1420, 1511, 1541, 1571}, + .cols = 8, +}; + +static struct single_row_lut fcc_sf = { + .x = {100, 200, 300, 400, 500}, + .y = {97, 93, 93, 90, 87}, + .cols = 5, +}; + +static struct pc_sf_lut pc_sf = { + .rows = 10, + .cols = 5, + .cycles = {100, 200, 300, 400, 500}, + .percent = {100, 90, 80, 70, 60, 50, 40, 30, 20, 10}, + .sf = { + {97, 93, 93, 90, 87}, + {97, 93, 93, 90, 87}, + {98, 94, 92, 89, 86}, + {98, 94, 92, 89, 86}, + {99, 94, 92, 88, 86}, + {99, 95, 92, 88, 87}, + {99, 95, 92, 88, 87}, + {99, 95, 92, 88, 87}, + {99, 95, 92, 88, 87}, + {99, 95, 92, 88, 87} + }, +}; + +static struct pc_temp_ocv_lut pc_temp_ocv = { + .rows = 29, + .cols = 8, + .temp = {-30, -20, -10, 0, 10, 25, 40, 60}, + .percent = {100, 95, 90, 85, 80, 75, 70, 65, 60, 55, + 50, 45, 40, 35, 30, 25, 20, 15, 10, 9, + 8, 7, 6, 5, 4, 3, 2, 1, 0 + }, + .ocv = { + {3673, 3814, 3945, 4025, 4106, 4176, 4218, 4260}, + {3613, 3751, 3880, 3959, 4038, 4107, 4149, 4190}, + {3573, 3710, 3837, 3916, 3994, 4062, 4103, 4144}, + {3534, 3670, 3796, 3873, 3951, 4019, 4059, 4099}, + {3491, 3625, 3749, 3826, 3902, 3969, 4009, 4049}, + {3464, 3597, 3721, 3796, 3872, 3939, 3978, 4018}, + {3436, 3568, 3691, 3766, 3841, 3907, 3946, 3985}, + {3407, 3537, 3659, 3733, 3808, 3873, 3912, 3951}, + {3377, 3507, 3627, 3701, 3775, 3840, 3878, 3917}, + {3355, 3484, 3604, 3677, 3751, 3815, 3853, 3891}, + {3339, 3467, 3586, 3659, 3732, 3796, 3834, 3872}, + {3324, 3452, 3570, 3643, 3716, 3780, 3818, 3855}, + {3312, 3440, 3558, 3630, 3703, 3766, 3804, 3842}, + {3303, 3430, 3548, 3620, 3692, 3756, 3793, 3831}, + {3297, 3424, 3541, 3614, 3686, 3749, 3787, 3824}, + {3288, 3414, 3531, 3603, 3675, 3738, 3776, 3813}, + {3272, 3398, 3514, 3586, 3658, 3720, 3757, 3795}, + {3240, 3365, 3480, 3551, 3622, 3684, 3721, 3758}, + {3224, 3348, 3463, 3533, 3604, 3666, 3702, 3739}, + {3221, 3344, 3459, 3530, 3600, 3662, 3695, 3728}, + {3216, 3340, 3454, 3525, 3595, 3657, 3686, 3715}, + {3212, 3335, 3449, 3520, 3590, 3652, 3677, 3703}, + {3203, 3326, 3440, 3510, 3580, 3642, 3664, 3686}, + {3185, 3307, 3420, 3490, 3560, 3621, 3639, 3657}, + {3176, 3298, 3411, 3481, 3550, 3611, 3626, 3640}, + {3151, 3272, 3384, 3453, 3522, 3583, 3593, 3604}, + {3106, 3225, 3335, 3446, 3472, 3531, 3538, 3545}, + {3021, 3217, 3245, 3417, 3429, 3435, 3439, 3442}, + {3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000} + }, +}; + +struct pm8921_bms_battery_data palladium_1500_data = { + .fcc = 1500, + .fcc_temp_lut = &fcc_temp, + .fcc_sf_lut = &fcc_sf, + .pc_temp_ocv_lut = &pc_temp_ocv, + .pc_sf_lut = &pc_sf, +}; diff --git a/arch/arm/mach-msm/board-apq8064.c b/arch/arm/mach-msm/board-apq8064.c new file mode 100644 index 00000000000..4a21203ee6b --- /dev/null +++ b/arch/arm/mach-msm/board-apq8064.c @@ -0,0 +1,101 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "timer.h" +#include "devices.h" + +static void __init apq8064_map_io(void) +{ + msm_map_apq8064_io(); +} + +static void __init apq8064_init_irq(void) +{ + unsigned int i; + gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, + (void *)MSM_QGIC_CPU_BASE); + + /* Edge trigger PPIs except AVS_SVICINT and AVS_SVICINTSWDONE */ + writel_relaxed(0xFFFFD7FF, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); + + writel_relaxed(0x0000FFFF, MSM_QGIC_DIST_BASE + GIC_DIST_ENABLE_SET); + mb(); + + /* + * FIXME: Not installing AVS_SVICINT and AVS_SVICINTSWDONE yet + * as they are configured as level, which does not play nice with + * handle_percpu_irq. + */ + for (i = GIC_PPI_START; i < GIC_SPI_START; i++) { + if (i != AVS_SVICINT && i != AVS_SVICINTSWDONE) + irq_set_handler(i, handle_percpu_irq); + } +} + +static struct platform_device *common_devices[] __initdata = { + &apq8064_device_uart_gsbi3 +}; + +static struct msm_otg_platform_data msm_otg_pdata = { + .mode = USB_PERIPHERAL, + .otg_control = OTG_PHY_CONTROL, + .phy_type = SNPS_28NM_INTEGRATED_PHY, + .pclk_src_name = "dfab_usb_hs_clk", +}; + +static struct msm_ssbi_platform_data apq8064_ssbi_pm8921_pdata __devinitdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, + .slave = { + .name = "pm8921-core", + }, +}; + +static struct msm_ssbi_platform_data apq8064_ssbi_pm8821_pdata __devinitdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, + .slave = { + .name = "pm8821-core", + }, +}; + +static void __init apq8064_common_init(void) +{ + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); + msm_clock_init(msm_clocks_8064_dummy, msm_num_clocks_8064_dummy); + platform_add_devices(common_devices, ARRAY_SIZE(common_devices)); +} + +static void __init apq8064_sim_init(void) +{ + apq8064_common_init(); +} + +MACHINE_START(APQ8064_SIM, "QCT APQ8064 SIMULATOR") + .map_io = apq8064_map_io, + .init_irq = apq8064_init_irq, + .timer = &msm_timer, + .init_machine = apq8064_sim_init, +MACHINE_END + diff --git a/arch/arm/mach-msm/board-fsm9xxx.c b/arch/arm/mach-msm/board-fsm9xxx.c new file mode 100644 index 00000000000..3fbd97e7310 --- /dev/null +++ b/arch/arm/mach-msm/board-fsm9xxx.c @@ -0,0 +1,854 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "devices.h" +#include "timer.h" +#include "pm.h" +#include "spm.h" +#include +#include +#include +#include +#include +#include + +#define PMIC_GPIO_INT 144 +#define PMIC_VREG_WLAN_LEVEL 2900 +#define PMIC_GPIO_SD_DET 165 + +#define GPIO_EPHY_RST_N 37 + +#define GPIO_GRFC_FTR0_0 136 /* GRFC 20 */ +#define GPIO_GRFC_FTR0_1 137 /* GRFC 21 */ +#define GPIO_GRFC_FTR1_0 145 /* GRFC 22 */ +#define GPIO_GRFC_FTR1_1 93 /* GRFC 19 */ +#define GPIO_GRFC_2 110 +#define GPIO_GRFC_3 109 +#define GPIO_GRFC_4 108 +#define GPIO_GRFC_5 107 +#define GPIO_GRFC_6 106 +#define GPIO_GRFC_7 105 +#define GPIO_GRFC_8 104 +#define GPIO_GRFC_9 103 +#define GPIO_GRFC_10 102 +#define GPIO_GRFC_11 101 +#define GPIO_GRFC_13 99 +#define GPIO_GRFC_14 98 +#define GPIO_GRFC_15 97 +#define GPIO_GRFC_16 96 +#define GPIO_GRFC_17 95 +#define GPIO_GRFC_18 94 +#define GPIO_GRFC_24 150 +#define GPIO_GRFC_25 151 +#define GPIO_GRFC_26 152 +#define GPIO_GRFC_27 153 +#define GPIO_GRFC_28 154 +#define GPIO_GRFC_29 155 + +#define FPGA_SDCC_STATUS 0x8E0001A8 + +/* Macros assume PMIC GPIOs start at 0 */ +#define PM8058_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio + NR_MSM_GPIOS) +#define PM8058_GPIO_SYS_TO_PM(sys_gpio) (sys_gpio - NR_MSM_GPIOS) + +#define PMIC_GPIO_5V_PA_PWR 21 /* PMIC GPIO Number 22 */ +#define PMIC_GPIO_4_2V_PA_PWR 22 /* PMIC GPIO Number 23 */ +#define PMIC_MPP_3 2 /* PMIC MPP Number 3 */ +#define PMIC_MPP_6 5 /* PMIC MPP Number 6 */ +#define PMIC_MPP_7 6 /* PMIC MPP Number 7 */ +#define PMIC_MPP_10 9 /* PMIC MPP Number 10 */ + +/* + * PM8058 + */ + +static int pm8058_gpios_init(void) +{ + int i; + int rc; + struct pm8058_gpio_cfg { + int gpio; + struct pm8058_gpio cfg; + }; + + struct pm8058_gpio_cfg gpio_cfgs[] = { + { /* 5V PA Power */ + PMIC_GPIO_5V_PA_PWR, + { + .vin_sel = 0, + .direction = PM_GPIO_DIR_BOTH, + .output_value = 1, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .pull = PM_GPIO_PULL_DN, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, + { /* 4.2V PA Power */ + PMIC_GPIO_4_2V_PA_PWR, + { + .vin_sel = 0, + .direction = PM_GPIO_DIR_BOTH, + .output_value = 1, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .pull = PM_GPIO_PULL_DN, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, + }; + + for (i = 0; i < ARRAY_SIZE(gpio_cfgs); ++i) { + rc = pm8058_gpio_config(gpio_cfgs[i].gpio, &gpio_cfgs[i].cfg); + if (rc < 0) { + pr_err("%s pmic gpio config failed\n", __func__); + return rc; + } + } + + return 0; +} + +static int pm8058_mpps_init(void) +{ + int rc; + + /* Set up MPP 3 and 6 as analog outputs at 1.25V */ + rc = pm8058_mpp_config_analog_output(PMIC_MPP_3, + PM_MPP_AOUT_LVL_1V25_2, PM_MPP_AOUT_CTL_ENABLE); + if (rc) { + pr_err("%s: Config mpp3 on pmic 8058 failed\n", __func__); + return rc; + } + + rc = pm8058_mpp_config_analog_output(PMIC_MPP_6, + PM_MPP_AOUT_LVL_1V25_2, PM_MPP_AOUT_CTL_ENABLE); + if (rc) { + pr_err("%s: Config mpp5 on pmic 8058 failed\n", __func__); + return rc; + } + return 0; +} + +static struct pm8058_gpio_platform_data pm8058_gpio_data = { + .gpio_base = PM8058_GPIO_PM_TO_SYS(0), + .irq_base = PM8058_GPIO_IRQ(PMIC8058_IRQ_BASE, 0), + .init = pm8058_gpios_init, +}; + +static struct pm8058_gpio_platform_data pm8058_mpp_data = { + .gpio_base = PM8058_GPIO_PM_TO_SYS(PM8058_GPIOS), + .irq_base = PM8058_MPP_IRQ(PMIC8058_IRQ_BASE, 0), + .init = pm8058_mpps_init, +}; + +static struct regulator_consumer_supply pm8058_vreg_supply[PM8058_VREG_MAX] = { + [PM8058_VREG_ID_L3] = REGULATOR_SUPPLY("8058_l3", NULL), + [PM8058_VREG_ID_L8] = REGULATOR_SUPPLY("8058_l8", NULL), + [PM8058_VREG_ID_L9] = REGULATOR_SUPPLY("8058_l9", NULL), + [PM8058_VREG_ID_L14] = REGULATOR_SUPPLY("8058_l14", NULL), + [PM8058_VREG_ID_L15] = REGULATOR_SUPPLY("8058_l15", NULL), + [PM8058_VREG_ID_L18] = REGULATOR_SUPPLY("8058_l18", NULL), + [PM8058_VREG_ID_S4] = REGULATOR_SUPPLY("8058_s4", NULL), + + [PM8058_VREG_ID_LVS0] = REGULATOR_SUPPLY("8058_lvs0", NULL), +}; + +#define PM8058_VREG_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, \ + _always_on, _pull_down) \ + [_id] = { \ + .init_data = { \ + .constraints = { \ + .valid_modes_mask = _modes, \ + .valid_ops_mask = _ops, \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .apply_uV = _apply_uV, \ + .always_on = _always_on, \ + }, \ + .num_consumer_supplies = 1, \ + .consumer_supplies = &pm8058_vreg_supply[_id], \ + }, \ + .pull_down_enable = _pull_down, \ + .pin_ctrl = 0, \ + .pin_fn = PM8058_VREG_PIN_FN_ENABLE, \ + } + +#define PM8058_VREG_INIT_LDO(_id, _min_uV, _max_uV) \ + PM8058_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL | \ + REGULATOR_MODE_IDLE | REGULATOR_MODE_STANDBY, \ + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS | \ + REGULATOR_CHANGE_MODE, 1, 1, 1) + +#define PM8058_VREG_INIT_SMPS(_id, _min_uV, _max_uV) \ + PM8058_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL | \ + REGULATOR_MODE_IDLE | REGULATOR_MODE_STANDBY, \ + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS | \ + REGULATOR_CHANGE_MODE, 1, 1, 1) + +#define PM8058_VREG_INIT_LVS(_id, _min_uV, _max_uV) \ + PM8058_VREG_INIT(_id, _min_uV, _min_uV, REGULATOR_MODE_NORMAL, \ + REGULATOR_CHANGE_STATUS, 0, 0, 1) + +static struct pm8058_vreg_pdata pm8058_vreg_init[PM8058_VREG_MAX] = { + PM8058_VREG_INIT_LDO(PM8058_VREG_ID_L3, 1800000, 1800000), + PM8058_VREG_INIT_LDO(PM8058_VREG_ID_L8, 2200000, 2200000), + PM8058_VREG_INIT_LDO(PM8058_VREG_ID_L9, 2050000, 2050000), + PM8058_VREG_INIT_LDO(PM8058_VREG_ID_L14, 2850000, 2850000), + PM8058_VREG_INIT_LDO(PM8058_VREG_ID_L15, 2200000, 2200000), + PM8058_VREG_INIT_LDO(PM8058_VREG_ID_L18, 2200000, 2200000), + PM8058_VREG_INIT_LVS(PM8058_VREG_ID_LVS0, 1800000, 1800000), + PM8058_VREG_INIT_SMPS(PM8058_VREG_ID_S4, 1300000, 1300000), +}; + +#define PM8058_VREG(_id) { \ + .name = "pm8058-regulator", \ + .id = _id, \ + .platform_data = &pm8058_vreg_init[_id], \ +} + +#ifdef CONFIG_SENSORS_MSM_ADC +static struct resource resources_adc[] = { + { + .start = PM8058_ADC_IRQ(PMIC8058_IRQ_BASE), + .end = PM8058_ADC_IRQ(PMIC8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct adc_access_fn xoadc_fn = { + pm8058_xoadc_select_chan_and_start_conv, + pm8058_xoadc_read_adc_code, + pm8058_xoadc_get_properties, + pm8058_xoadc_slot_request, + pm8058_xoadc_restore_slot, + pm8058_xoadc_calibrate, +}; + +static struct msm_adc_channels msm_adc_channels_data[] = { + {"pmic_therm", CHANNEL_ADC_DIE_TEMP, 0, &xoadc_fn, CHAN_PATH_TYPE12, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE1, scale_pmic_therm}, + {"ref_1250mv", CHANNEL_ADC_1250_REF, 0, &xoadc_fn, CHAN_PATH_TYPE13, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE2, scale_default}, + {"xo_therm", CHANNEL_ADC_XOTHERM, 0, &xoadc_fn, CHAN_PATH_TYPE_NONE, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE5, tdkntcgtherm}, + {"fsm_therm", CHANNEL_ADC_FSM_THERM, 0, &xoadc_fn, CHAN_PATH_TYPE6, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE5, tdkntcgtherm}, + {"pa_therm", CHANNEL_ADC_PA_THERM, 0, &xoadc_fn, CHAN_PATH_TYPE7, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE5, tdkntcgtherm}, +}; + +static struct msm_adc_platform_data msm_adc_pdata = { + .channel = msm_adc_channels_data, + .num_chan_supported = ARRAY_SIZE(msm_adc_channels_data), + .target_hw = FSM_9xxx, +}; + +static struct platform_device msm_adc_device = { + .name = "msm_adc", + .id = -1, + .dev = { + .platform_data = &msm_adc_pdata, + }, +}; + +static void pmic8058_xoadc_mpp_config(void) +{ + int rc; + + rc = pm8058_mpp_config_analog_input(XOADC_MPP_7, + PM_MPP_AIN_AMUX_CH5, PM_MPP_AOUT_CTL_DISABLE); + if (rc) + pr_err("%s: Config mpp7 on pmic 8058 failed\n", __func__); + + rc = pm8058_mpp_config_analog_input(XOADC_MPP_10, + PM_MPP_AIN_AMUX_CH6, PM_MPP_AOUT_CTL_DISABLE); + if (rc) + pr_err("%s: Config mpp10 on pmic 8058 failed\n", __func__); +} + +static struct regulator *vreg_ldo18_adc; + +static int pmic8058_xoadc_vreg_config(int on) +{ + int rc; + + if (on) { + rc = regulator_enable(vreg_ldo18_adc); + if (rc) + pr_err("%s: Enable of regulator ldo18_adc " + "failed\n", __func__); + } else { + rc = regulator_disable(vreg_ldo18_adc); + if (rc) + pr_err("%s: Disable of regulator ldo18_adc " + "failed\n", __func__); + } + + return rc; +} + +static int pmic8058_xoadc_vreg_setup(void) +{ + int rc; + + vreg_ldo18_adc = regulator_get(NULL, "8058_l18"); + if (IS_ERR(vreg_ldo18_adc)) { + pr_err("%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_ldo18_adc)); + rc = PTR_ERR(vreg_ldo18_adc); + goto fail; + } + + rc = regulator_set_voltage(vreg_ldo18_adc, 2200000, 2200000); + if (rc) { + pr_err("%s: unable to set ldo18 voltage to 2.2V\n", __func__); + goto fail; + } + + return rc; +fail: + regulator_put(vreg_ldo18_adc); + return rc; +} + +static void pmic8058_xoadc_vreg_shutdown(void) +{ + regulator_put(vreg_ldo18_adc); +} + +/* usec. For this ADC, + * this time represents clk rate @ txco w/ 1024 decimation ratio. + * Each channel has different configuration, thus at the time of starting + * the conversion, xoadc will return actual conversion time + * */ +static struct adc_properties pm8058_xoadc_data = { + .adc_reference = 2200, /* milli-voltage for this adc */ + .bitresolution = 15, + .bipolar = 0, + .conversiontime = 54, +}; + +static struct xoadc_platform_data xoadc_pdata = { + .xoadc_prop = &pm8058_xoadc_data, + .xoadc_mpp_config = pmic8058_xoadc_mpp_config, + .xoadc_vreg_set = pmic8058_xoadc_vreg_config, + .xoadc_num = XOADC_PMIC_0, + .xoadc_vreg_setup = pmic8058_xoadc_vreg_setup, + .xoadc_vreg_shutdown = pmic8058_xoadc_vreg_shutdown, +}; +#endif + +/* Put sub devices with fixed location first in sub_devices array */ +static struct mfd_cell pm8058_subdevs[] = { + { .name = "pm8058-mpp", + .platform_data = &pm8058_mpp_data, + .pdata_size = sizeof(pm8058_mpp_data), + }, + { + .name = "pm8058-gpio", + .id = -1, + .platform_data = &pm8058_gpio_data, + .pdata_size = sizeof(pm8058_gpio_data), + }, +#ifdef CONFIG_SENSORS_MSM_ADC + { + .name = "pm8058-xoadc", + .id = -1, + .num_resources = ARRAY_SIZE(resources_adc), + .resources = resources_adc, + .platform_data = &xoadc_pdata, + .pdata_size =sizeof(xoadc_pdata), + }, +#endif + PM8058_VREG(PM8058_VREG_ID_L3), + PM8058_VREG(PM8058_VREG_ID_L8), + PM8058_VREG(PM8058_VREG_ID_L9), + PM8058_VREG(PM8058_VREG_ID_L14), + PM8058_VREG(PM8058_VREG_ID_L15), + PM8058_VREG(PM8058_VREG_ID_L18), + PM8058_VREG(PM8058_VREG_ID_S4), + PM8058_VREG(PM8058_VREG_ID_LVS0), + PM8058_XO(PM8058_XO_ID_A0), + PM8058_XO(PM8058_XO_ID_A1), +}; + +static struct pm8058_platform_data pm8058_fsm9xxx_data = { + .irq_base = PMIC8058_IRQ_BASE, + + .num_subdevs = ARRAY_SIZE(pm8058_subdevs), + .sub_devices = pm8058_subdevs, +}; + +static struct i2c_board_info pm8058_boardinfo[] __initdata = { + { + I2C_BOARD_INFO("pm8058-core", 0x55), + .irq = MSM_GPIO_TO_INT(47), + .platform_data = &pm8058_fsm9xxx_data, + }, +}; + +static int __init buses_init(void) +{ + if (gpio_tlmm_config(GPIO_CFG(PMIC_GPIO_INT, 5, GPIO_CFG_INPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_2MA), GPIO_CFG_ENABLE)) + pr_err("%s: gpio_tlmm_config (gpio=%d) failed\n", + __func__, PMIC_GPIO_INT); + + i2c_register_board_info(0 /* I2C_SSBI ID */, pm8058_boardinfo, + ARRAY_SIZE(pm8058_boardinfo)); + + return 0; +} + +/* + * EPHY + */ + +static struct msm_gpio phy_config_data[] = { + { GPIO_CFG(GPIO_EPHY_RST_N, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "MAC_RST_N" }, +}; + +static int __init phy_init(void) +{ + msm_gpios_request_enable(phy_config_data, ARRAY_SIZE(phy_config_data)); + gpio_direction_output(GPIO_EPHY_RST_N, 0); + udelay(100); + gpio_set_value(GPIO_EPHY_RST_N, 1); + + return 0; +} + +/* + * RF + */ + +static struct msm_gpio grfc_config_data[] = { + { GPIO_CFG(GPIO_GRFC_FTR0_0, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "HH_RFMODE1_0" }, + { GPIO_CFG(GPIO_GRFC_FTR0_1, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "HH_RFMODE1_1" }, + { GPIO_CFG(GPIO_GRFC_FTR1_0, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "HH_RFMODE2_0" }, + { GPIO_CFG(GPIO_GRFC_FTR1_1, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "HH_RFMODE2_1" }, + { GPIO_CFG(GPIO_GRFC_2, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_2" }, + { GPIO_CFG(GPIO_GRFC_3, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_3" }, + { GPIO_CFG(GPIO_GRFC_4, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_4" }, + { GPIO_CFG(GPIO_GRFC_5, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_5" }, + { GPIO_CFG(GPIO_GRFC_6, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_6" }, + { GPIO_CFG(GPIO_GRFC_7, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_7" }, + { GPIO_CFG(GPIO_GRFC_8, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_8" }, + { GPIO_CFG(GPIO_GRFC_9, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_9" }, + { GPIO_CFG(GPIO_GRFC_10, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_10" }, + { GPIO_CFG(GPIO_GRFC_11, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_11" }, + { GPIO_CFG(GPIO_GRFC_13, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_13" }, + { GPIO_CFG(GPIO_GRFC_14, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_14" }, + { GPIO_CFG(GPIO_GRFC_15, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_15" }, + { GPIO_CFG(GPIO_GRFC_16, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_16" }, + { GPIO_CFG(GPIO_GRFC_17, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_17" }, + { GPIO_CFG(GPIO_GRFC_18, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_18" }, + { GPIO_CFG(GPIO_GRFC_24, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_24" }, + { GPIO_CFG(GPIO_GRFC_25, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_25" }, + { GPIO_CFG(GPIO_GRFC_26, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_26" }, + { GPIO_CFG(GPIO_GRFC_27, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_27" }, + { GPIO_CFG(GPIO_GRFC_28, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_28" }, + { GPIO_CFG(GPIO_GRFC_29, 7, GPIO_CFG_OUTPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), "GPIO_GRFC_29" }, + { GPIO_CFG(39, 1, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "PP2S_EXT_SYNC" }, +}; + +static int __init grfc_init(void) +{ + msm_gpios_request_enable(grfc_config_data, + ARRAY_SIZE(grfc_config_data)); + + return 0; +} + +/* + * UART + */ + +#ifdef CONFIG_SERIAL_MSM_CONSOLE +static struct msm_gpio uart1_config_data[] = { + { GPIO_CFG(138, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1_Rx" }, + { GPIO_CFG(139, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1_Tx" }, +}; + +static void fsm9xxx_init_uart1(void) +{ + msm_gpios_request_enable(uart1_config_data, + ARRAY_SIZE(uart1_config_data)); + +} +#endif + +/* + * SSBI + */ + +#ifdef CONFIG_I2C_SSBI +static struct msm_i2c_ssbi_platform_data msm_i2c_ssbi1_pdata = { + .controller_type = FSM_SBI_CTRL_SSBI, +}; + +static struct msm_i2c_ssbi_platform_data msm_i2c_ssbi2_pdata = { + .controller_type = FSM_SBI_CTRL_SSBI, +}; + +static struct msm_i2c_ssbi_platform_data msm_i2c_ssbi3_pdata = { + .controller_type = FSM_SBI_CTRL_SSBI, +}; + +/* Intialize GPIO configuration for SSBI */ +static struct msm_gpio ssbi_gpio_config_data[] = { + { GPIO_CFG(140, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), + "SSBI_1" }, + { GPIO_CFG(141, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), + "SSBI_2" }, + { GPIO_CFG(92, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_4MA), + "SSBI_3" }, +}; + +static void +fsm9xxx_init_ssbi_gpio(void) +{ + msm_gpios_request_enable(ssbi_gpio_config_data, + ARRAY_SIZE(ssbi_gpio_config_data)); + +} +#endif + +/* + * Crypto + */ + +#define QCE_SIZE 0x10000 + +#define QCE_0_BASE 0x80C00000 +#define QCE_1_BASE 0x80E00000 +#define QCE_2_BASE 0x81000000 + +#define QCE_NO_HW_KEY_SUPPORT 0 /* No shared HW key with external */ +#define QCE_NO_SHARE_CE_RESOURCE 0 /* No CE resource shared with TZ */ +#define QCE_NO_CE_SHARED 0 /* CE not shared with TZ */ +#define QCE_NO_SHA_HMAC_SUPPORT 0 /* No SHA-HMAC by SHA operation */ + +static struct resource qcrypto_resources[] = { + [0] = { + .start = QCE_0_BASE, + .end = QCE_0_BASE + QCE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "crypto_channels", + .start = DMOV_CE1_IN_CHAN, + .end = DMOV_CE1_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV_CE1_IN_CRCI, + .end = DMOV_CE1_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV_CE1_OUT_CRCI, + .end = DMOV_CE1_OUT_CRCI, + .flags = IORESOURCE_DMA, + }, + [4] = { + .name = "crypto_crci_hash", + .start = DMOV_CE1_HASH_CRCI, + .end = DMOV_CE1_HASH_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +static struct msm_ce_hw_support qcrypto_ce_hw_suppport = { + .ce_shared = QCE_NO_CE_SHARED, + .shared_ce_resource = QCE_NO_SHARE_CE_RESOURCE, + .hw_key_support = QCE_NO_HW_KEY_SUPPORT, + .sha_hmac = QCE_NO_SHA_HMAC_SUPPORT, +}; + +struct platform_device qcrypto_device = { + .name = "qcrypto", + .id = 0, + .num_resources = ARRAY_SIZE(qcrypto_resources), + .resource = qcrypto_resources, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &qcrypto_ce_hw_suppport, + }, +}; + +static struct resource qcedev_resources[] = { + [0] = { + .start = QCE_0_BASE, + .end = QCE_0_BASE + QCE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "crypto_channels", + .start = DMOV_CE1_IN_CHAN, + .end = DMOV_CE1_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV_CE1_IN_CRCI, + .end = DMOV_CE1_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV_CE1_OUT_CRCI, + .end = DMOV_CE1_OUT_CRCI, + .flags = IORESOURCE_DMA, + }, + [4] = { + .name = "crypto_crci_hash", + .start = DMOV_CE1_HASH_CRCI, + .end = DMOV_CE1_HASH_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +static struct msm_ce_hw_support qcedev_ce_hw_suppport = { + .ce_shared = QCE_NO_CE_SHARED, + .shared_ce_resource = QCE_NO_SHARE_CE_RESOURCE, + .hw_key_support = QCE_NO_HW_KEY_SUPPORT, + .sha_hmac = QCE_NO_SHA_HMAC_SUPPORT, +}; + +static struct platform_device qcedev_device = { + .name = "qce", + .id = 0, + .num_resources = ARRAY_SIZE(qcedev_resources), + .resource = qcedev_resources, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &qcedev_ce_hw_suppport, + }, +}; + +static struct resource ota_qcrypto_resources[] = { + [0] = { + .start = QCE_1_BASE, + .end = QCE_1_BASE + QCE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "crypto_channels", + .start = DMOV_CE2_IN_CHAN, + .end = DMOV_CE2_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV_CE2_IN_CRCI, + .end = DMOV_CE2_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV_CE2_OUT_CRCI, + .end = DMOV_CE2_OUT_CRCI, + .flags = IORESOURCE_DMA, + }, + [4] = { + .name = "crypto_crci_hash", + .start = DMOV_CE2_HASH_CRCI, + .end = DMOV_CE2_HASH_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device ota_qcrypto_device = { + .name = "qcota", + .id = 0, + .num_resources = ARRAY_SIZE(ota_qcrypto_resources), + .resource = ota_qcrypto_resources, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +/* + * Devices + */ + +static struct platform_device *devices[] __initdata = { + &msm_device_smd, + &msm_device_dmov, + &msm_device_nand, +#ifdef CONFIG_I2C_SSBI + &msm_device_ssbi1, + &msm_device_ssbi2, + &msm_device_ssbi3, +#endif +#ifdef CONFIG_SENSORS_MSM_ADC + &msm_adc_device, +#endif +#ifdef CONFIG_I2C_QUP + &msm_gsbi1_qup_i2c_device, +#endif +#if defined(CONFIG_SERIAL_MSM) || defined(CONFIG_MSM_SERIAL_DEBUGGER) + &msm_device_uart1, +#endif +#if defined(CONFIG_QFP_FUSE) + &fsm_qfp_fuse_device, +#endif + &qfec_device, + &qcrypto_device, + &qcedev_device, + &ota_qcrypto_device, +}; + +static struct msm_acpu_clock_platform_data fsm9xxx_clock_data = { + .acpu_switch_time_us = 50, + .vdd_switch_time_us = 62, +}; + +static void __init fsm9xxx_init_irq(void) +{ + msm_init_irq(); + msm_init_sirc(); +} + +#ifdef CONFIG_MSM_SPM +static struct msm_spm_platform_data msm_spm_data __initdata = { + .reg_base_addr = MSM_SAW_BASE, + + .reg_init_values[MSM_SPM_REG_SAW_CFG] = 0x05, + .reg_init_values[MSM_SPM_REG_SAW_SPM_CTL] = 0x18, + .reg_init_values[MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY] = 0x00006666, + .reg_init_values[MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY] = 0xFF000666, + + .reg_init_values[MSM_SPM_REG_SAW_SPM_PMIC_CTL] = 0xE0F272, + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLK_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN] = 0x03, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN] = 0x00, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLMP_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_RST_EN] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW_SPM_MPM_CFG] = 0x00, + + .awake_vlevel = 0xF2, + .retention_vlevel = 0xE0, + .collapse_vlevel = 0x72, + .retention_mid_vlevel = 0xE0, + .collapse_mid_vlevel = 0xE0, +}; +#endif + +static void __init fsm9xxx_init(void) +{ + if (socinfo_init() < 0) + pr_err("%s: socinfo_init() failed!\n", + __func__); + + msm_acpu_clock_init(&fsm9xxx_clock_data); + + regulator_has_full_constraints(); + + platform_add_devices(devices, ARRAY_SIZE(devices)); + +#ifdef CONFIG_MSM_SPM + msm_spm_init(&msm_spm_data, 1); +#endif + buses_init(); + phy_init(); + grfc_init(); + +#ifdef CONFIG_SERIAL_MSM_CONSOLE + fsm9xxx_init_uart1(); +#endif +#ifdef CONFIG_I2C_SSBI + fsm9xxx_init_ssbi_gpio(); + msm_device_ssbi1.dev.platform_data = &msm_i2c_ssbi1_pdata; + msm_device_ssbi2.dev.platform_data = &msm_i2c_ssbi2_pdata; + msm_device_ssbi3.dev.platform_data = &msm_i2c_ssbi3_pdata; +#endif +} + +static void __init fsm9xxx_map_io(void) +{ + msm_shared_ram_phys = 0x00100000; + msm_map_fsm9xxx_io(); + msm_clock_init(msm_clocks_fsm9xxx, msm_num_clocks_fsm9xxx); +} + +MACHINE_START(FSM9XXX_SURF, "QCT FSM9XXX") + .boot_params = PHYS_OFFSET + 0x100, + .map_io = fsm9xxx_map_io, + .init_irq = fsm9xxx_init_irq, + .init_machine = fsm9xxx_init, + .timer = &msm_timer, +MACHINE_END diff --git a/arch/arm/mach-msm/board-halibut-keypad.c b/arch/arm/mach-msm/board-halibut-keypad.c new file mode 100644 index 00000000000..49c1075627d --- /dev/null +++ b/arch/arm/mach-msm/board-halibut-keypad.c @@ -0,0 +1,177 @@ +/* linux/arch/arm/mach-msm/board-halibut-keypad.c + * + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#undef MODULE_PARAM_PREFIX +#define MODULE_PARAM_PREFIX "board_halibut." +static int halibut_ffa; +module_param_named(ffa, halibut_ffa, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define SCAN_FUNCTION_KEYS 0 /* don't turn this on without updating the ffa support */ + +static unsigned int halibut_row_gpios[] = { + 31, 32, 33, 34, 35, 41 +#if SCAN_FUNCTION_KEYS + , 42 +#endif +}; + +static unsigned int halibut_col_gpios[] = { 36, 37, 38, 39, 40 }; + +/* FFA: + 36: KEYSENSE_N(0) + 37: KEYSENSE_N(1) + 38: KEYSENSE_N(2) + 39: KEYSENSE_N(3) + 40: KEYSENSE_N(4) + + 31: KYPD_17 + 32: KYPD_15 + 33: KYPD_13 + 34: KYPD_11 + 35: KYPD_9 + 41: KYPD_MEMO +*/ + +#define KEYMAP_INDEX(row, col) ((row)*ARRAY_SIZE(halibut_col_gpios) + (col)) + +static const unsigned short halibut_keymap[ARRAY_SIZE(halibut_col_gpios) * ARRAY_SIZE(halibut_row_gpios)] = { + [KEYMAP_INDEX(0, 0)] = KEY_5, + [KEYMAP_INDEX(0, 1)] = KEY_9, + [KEYMAP_INDEX(0, 2)] = 229, /* SOFT1 */ + [KEYMAP_INDEX(0, 3)] = KEY_6, + [KEYMAP_INDEX(0, 4)] = KEY_LEFT, + + [KEYMAP_INDEX(1, 0)] = KEY_0, + [KEYMAP_INDEX(1, 1)] = KEY_RIGHT, + [KEYMAP_INDEX(1, 2)] = KEY_1, + [KEYMAP_INDEX(1, 3)] = 228, /* KEY_SHARP */ + [KEYMAP_INDEX(1, 4)] = KEY_SEND, + + [KEYMAP_INDEX(2, 0)] = KEY_VOLUMEUP, + [KEYMAP_INDEX(2, 1)] = KEY_HOME, /* FA */ + [KEYMAP_INDEX(2, 2)] = KEY_F8, /* QCHT */ + [KEYMAP_INDEX(2, 3)] = KEY_F6, /* R+ */ + [KEYMAP_INDEX(2, 4)] = KEY_F7, /* R- */ + + [KEYMAP_INDEX(3, 0)] = KEY_UP, + [KEYMAP_INDEX(3, 1)] = KEY_CLEAR, + [KEYMAP_INDEX(3, 2)] = KEY_4, + [KEYMAP_INDEX(3, 3)] = KEY_MUTE, /* SPKR */ + [KEYMAP_INDEX(3, 4)] = KEY_2, + + [KEYMAP_INDEX(4, 0)] = 230, /* SOFT2 */ + [KEYMAP_INDEX(4, 1)] = 232, /* KEY_CENTER */ + [KEYMAP_INDEX(4, 2)] = KEY_DOWN, + [KEYMAP_INDEX(4, 3)] = KEY_BACK, /* FB */ + [KEYMAP_INDEX(4, 4)] = KEY_8, + + [KEYMAP_INDEX(5, 0)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(5, 1)] = 227, /* KEY_STAR */ + [KEYMAP_INDEX(5, 2)] = KEY_MAIL, /* MESG */ + [KEYMAP_INDEX(5, 3)] = KEY_3, + [KEYMAP_INDEX(5, 4)] = KEY_7, + +#if SCAN_FUNCTION_KEYS + [KEYMAP_INDEX(6, 0)] = KEY_F5, + [KEYMAP_INDEX(6, 1)] = KEY_F4, + [KEYMAP_INDEX(6, 2)] = KEY_F3, + [KEYMAP_INDEX(6, 3)] = KEY_F2, + [KEYMAP_INDEX(6, 4)] = KEY_F1 +#endif +}; + +static const unsigned short halibut_keymap_ffa[ARRAY_SIZE(halibut_col_gpios) * ARRAY_SIZE(halibut_row_gpios)] = { + /*[KEYMAP_INDEX(0, 0)] = ,*/ + /*[KEYMAP_INDEX(0, 1)] = ,*/ + [KEYMAP_INDEX(0, 2)] = KEY_1, + [KEYMAP_INDEX(0, 3)] = KEY_SEND, + [KEYMAP_INDEX(0, 4)] = KEY_LEFT, + + [KEYMAP_INDEX(1, 0)] = KEY_3, + [KEYMAP_INDEX(1, 1)] = KEY_RIGHT, + [KEYMAP_INDEX(1, 2)] = KEY_VOLUMEUP, + /*[KEYMAP_INDEX(1, 3)] = ,*/ + [KEYMAP_INDEX(1, 4)] = KEY_6, + + [KEYMAP_INDEX(2, 0)] = KEY_HOME, /* A */ + [KEYMAP_INDEX(2, 1)] = KEY_BACK, /* B */ + [KEYMAP_INDEX(2, 2)] = KEY_0, + [KEYMAP_INDEX(2, 3)] = 228, /* KEY_SHARP */ + [KEYMAP_INDEX(2, 4)] = KEY_9, + + [KEYMAP_INDEX(3, 0)] = KEY_UP, + [KEYMAP_INDEX(3, 1)] = 232, /* KEY_CENTER */ /* i */ + [KEYMAP_INDEX(3, 2)] = KEY_4, + /*[KEYMAP_INDEX(3, 3)] = ,*/ + [KEYMAP_INDEX(3, 4)] = KEY_2, + + [KEYMAP_INDEX(4, 0)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(4, 1)] = KEY_SOUND, + [KEYMAP_INDEX(4, 2)] = KEY_DOWN, + [KEYMAP_INDEX(4, 3)] = KEY_8, + [KEYMAP_INDEX(4, 4)] = KEY_5, + + /*[KEYMAP_INDEX(5, 0)] = ,*/ + [KEYMAP_INDEX(5, 1)] = 227, /* KEY_STAR */ + [KEYMAP_INDEX(5, 2)] = 230, /*SOFT2*/ /* 2 */ + [KEYMAP_INDEX(5, 3)] = KEY_MENU, /* 1 */ + [KEYMAP_INDEX(5, 4)] = KEY_7, +}; + +static struct gpio_event_matrix_info halibut_matrix_info = { + .info.func = gpio_event_matrix_func, + .keymap = halibut_keymap, + .output_gpios = halibut_row_gpios, + .input_gpios = halibut_col_gpios, + .noutputs = ARRAY_SIZE(halibut_row_gpios), + .ninputs = ARRAY_SIZE(halibut_col_gpios), + .settle_time.tv.nsec = 0, + .poll_time.tv.nsec = 20 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_DRIVE_INACTIVE | GPIOKPF_PRINT_UNMAPPED_KEYS /*| GPIOKPF_PRINT_MAPPED_KEYS*/ +}; + +struct gpio_event_info *halibut_keypad_info[] = { + &halibut_matrix_info.info +}; + +static struct gpio_event_platform_data halibut_keypad_data = { + .name = "halibut_keypad", + .info = halibut_keypad_info, + .info_count = ARRAY_SIZE(halibut_keypad_info) +}; + +static struct platform_device halibut_keypad_device = { + .name = GPIO_EVENT_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &halibut_keypad_data, + }, +}; + +static int __init halibut_init_keypad(void) +{ + if (!machine_is_halibut()) + return 0; + if (halibut_ffa) + halibut_matrix_info.keymap = halibut_keymap_ffa; + return platform_device_register(&halibut_keypad_device); +} + +device_initcall(halibut_init_keypad); diff --git a/arch/arm/mach-msm/board-halibut-panel.c b/arch/arm/mach-msm/board-halibut-panel.c new file mode 100644 index 00000000000..a498c65344b --- /dev/null +++ b/arch/arm/mach-msm/board-halibut-panel.c @@ -0,0 +1,73 @@ +/* linux/arch/arm/mach-msm/board-halibut-mddi.c +** Author: Brian Swetland +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "proc_comm.h" +#include "devices.h" +#include "board-halibut.h" + +static void halibut_mddi_power_client(struct msm_mddi_client_data *mddi, + int on) +{ +} + +static struct resource resources_msm_fb = { + .start = MSM_FB_BASE, + .end = MSM_FB_BASE + MSM_FB_SIZE - 1, + .flags = IORESOURCE_MEM, +}; + +static struct msm_fb_data fb_data = { + .xres = 800, + .yres = 480, + .output_format = 0, +}; + +static struct msm_mddi_platform_data mddi_pdata = { + .clk_rate = 122880000, + .power_client = halibut_mddi_power_client, + .fb_resource = &resources_msm_fb, + .num_clients = 1, + .client_platform_data = { + { + .product_id = (0x4474 << 16 | 0xc065), + .name = "mddi_c_dummy", + .id = 0, + .client_data = &fb_data, + .clk_rate = 0, + }, + }, +}; + +int __init halibut_init_panel(void) +{ + int rc; + + if (!machine_is_halibut()) + return 0; + + rc = platform_device_register(&msm_device_mdp); + if (rc) + return rc; + + msm_device_mddi0.dev.platform_data = &mddi_pdata; + return platform_device_register(&msm_device_mddi0); +} + +device_initcall(halibut_init_panel); diff --git a/arch/arm/mach-msm/board-halibut.h b/arch/arm/mach-msm/board-halibut.h new file mode 100644 index 00000000000..edcdacb34c2 --- /dev/null +++ b/arch/arm/mach-msm/board-halibut.h @@ -0,0 +1,20 @@ +/* linux/arch/arm/mach-msm/board-trout.h + * ** Author: Brian Swetland + * */ +#ifndef __ARCH_ARM_MACH_MSM_BOARD_HALIBUT_H +#define __ARCH_ARM_MACH_MSM_BOARD_HALIBUT_H + +#define MSM_PMEM_GPU0_BASE (0x10000000 + 64*SZ_1M) +#define MSM_PMEM_GPU0_SIZE 0x800000 +#define MSM_PMEM_MDP_BASE (MSM_PMEM_GPU0_BASE + MSM_PMEM_GPU0_SIZE) +#define MSM_PMEM_MDP_SIZE 0x800000 +#define MSM_PMEM_ADSP_BASE (MSM_PMEM_MDP_BASE + MSM_PMEM_MDP_SIZE) +#define MSM_PMEM_ADSP_SIZE 0x800000 +#define MSM_PMEM_GPU1_BASE (MSM_PMEM_ADSP_BASE + MSM_PMEM_ADSP_SIZE) +#define MSM_PMEM_GPU1_SIZE 0x800000 +#define MSM_FB_BASE (MSM_PMEM_GPU1_BASE + MSM_PMEM_GPU1_SIZE) +#define MSM_FB_SIZE 0x200000 +#define MSM_PMEM_CAMERA_BASE (MSM_FB_BASE + MSM_FB_SIZE) +#define MSM_PMEM_CAMERA_SIZE 0xA00000 + +#endif diff --git a/arch/arm/mach-msm/board-mahimahi-audio.c b/arch/arm/mach-msm/board-mahimahi-audio.c new file mode 100644 index 00000000000..074d84e4d13 --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-audio.c @@ -0,0 +1,283 @@ +/* arch/arm/mach-msm/board-mahimahi-audio.c + * + * Copyright (C) 2009 HTC Corporation + * Copyright (C) 2009 Google Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include + +#include "board-mahimahi.h" +#include "proc_comm.h" +#include "pmic.h" +#include "board-mahimahi-tpa2018d1.h" + +#if 0 +#define D(fmt, args...) printk(KERN_INFO "Audio: "fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif + +static struct mutex mic_lock; +static struct mutex bt_sco_lock; + +static struct q6_hw_info q6_audio_hw[Q6_HW_COUNT] = { + [Q6_HW_HANDSET] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_HEADSET] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_SPEAKER] = { + .min_gain = -1500, + .max_gain = 0, + }, + [Q6_HW_TTY] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_BT_SCO] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_BT_A2DP] = { + .min_gain = -2000, + .max_gain = 0, + }, +}; + +void mahimahi_headset_enable(int en) +{ + D("%s %d\n", __func__, en); + /* enable audio amp */ + if (en) mdelay(15); + gpio_set_value(MAHIMAHI_AUD_JACKHP_EN, !!en); +} + +void mahimahi_speaker_enable(int en) +{ + struct spkr_config_mode scm; + memset(&scm, 0, sizeof(scm)); + + D("%s %d\n", __func__, en); + if (en) { + scm.is_right_chan_en = 0; + scm.is_left_chan_en = 1; + scm.is_stereo_en = 0; + scm.is_hpf_en = 1; + pmic_spkr_en_mute(LEFT_SPKR, 0); + pmic_spkr_en_mute(RIGHT_SPKR, 0); + pmic_set_spkr_configuration(&scm); + pmic_spkr_en(LEFT_SPKR, 1); + pmic_spkr_en(RIGHT_SPKR, 0); + + /* unmute */ + pmic_spkr_en_mute(LEFT_SPKR, 1); + } else { + pmic_spkr_en_mute(LEFT_SPKR, 0); + + pmic_spkr_en(LEFT_SPKR, 0); + pmic_spkr_en(RIGHT_SPKR, 0); + + pmic_set_spkr_configuration(&scm); + } + + if (is_cdma_version(system_rev)) + tpa2018d1_set_speaker_amp(en); +} + +void mahimahi_receiver_enable(int en) +{ + if (is_cdma_version(system_rev) && + ((system_rev == 0xC1) || (system_rev == 0xC2))) { + struct spkr_config_mode scm; + memset(&scm, 0, sizeof(scm)); + + D("%s %d\n", __func__, en); + if (en) { + scm.is_right_chan_en = 1; + scm.is_left_chan_en = 0; + scm.is_stereo_en = 0; + scm.is_hpf_en = 1; + pmic_spkr_en_mute(RIGHT_SPKR, 0); + pmic_set_spkr_configuration(&scm); + pmic_spkr_en(RIGHT_SPKR, 1); + + /* unmute */ + pmic_spkr_en_mute(RIGHT_SPKR, 1); + } else { + pmic_spkr_en_mute(RIGHT_SPKR, 0); + + pmic_spkr_en(RIGHT_SPKR, 0); + + pmic_set_spkr_configuration(&scm); + } + } +} + +static void config_gpio_table(uint32_t *table, int len) +{ + int n; + unsigned id; + for (n = 0; n < len; n++) { + id = table[n]; + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0); + } +} + +static uint32_t bt_sco_enable[] = { + PCOM_GPIO_CFG(MAHIMAHI_BT_PCM_OUT, 1, GPIO_OUTPUT, + GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(MAHIMAHI_BT_PCM_IN, 1, GPIO_INPUT, + GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(MAHIMAHI_BT_PCM_SYNC, 2, GPIO_INPUT, + GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(MAHIMAHI_BT_PCM_CLK, 2, GPIO_INPUT, + GPIO_NO_PULL, GPIO_2MA), +}; + +static uint32_t bt_sco_disable[] = { + PCOM_GPIO_CFG(MAHIMAHI_BT_PCM_OUT, 0, GPIO_OUTPUT, + GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(MAHIMAHI_BT_PCM_IN, 0, GPIO_INPUT, + GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(MAHIMAHI_BT_PCM_SYNC, 0, GPIO_INPUT, + GPIO_NO_PULL, GPIO_2MA), + PCOM_GPIO_CFG(MAHIMAHI_BT_PCM_CLK, 0, GPIO_INPUT, + GPIO_NO_PULL, GPIO_2MA), +}; + +void mahimahi_bt_sco_enable(int en) +{ + static int bt_sco_refcount; + D("%s %d\n", __func__, en); + + mutex_lock(&bt_sco_lock); + if (en) { + if (++bt_sco_refcount == 1) + config_gpio_table(bt_sco_enable, + ARRAY_SIZE(bt_sco_enable)); + } else { + if (--bt_sco_refcount == 0) { + config_gpio_table(bt_sco_disable, + ARRAY_SIZE(bt_sco_disable)); + gpio_set_value(MAHIMAHI_BT_PCM_OUT, 0); + } + } + mutex_unlock(&bt_sco_lock); +} + +void mahimahi_mic_enable(int en) +{ + static int old_state = 0, new_state = 0; + + D("%s %d\n", __func__, en); + + mutex_lock(&mic_lock); + if (!!en) + new_state++; + else + new_state--; + + if (new_state == 1 && old_state == 0) { + gpio_set_value(MAHIMAHI_AUD_2V5_EN, 1); + mdelay(60); + } else if (new_state == 0 && old_state == 1) + gpio_set_value(MAHIMAHI_AUD_2V5_EN, 0); + else + D("%s: do nothing %d %d\n", __func__, old_state, new_state); + + old_state = new_state; + mutex_unlock(&mic_lock); +} + +void mahimahi_analog_init(void) +{ + D("%s\n", __func__); + /* stereo pmic init */ + pmic_spkr_set_gain(LEFT_SPKR, SPKR_GAIN_PLUS12DB); + pmic_spkr_set_gain(RIGHT_SPKR, SPKR_GAIN_PLUS12DB); + pmic_spkr_en_right_chan(OFF_CMD); + pmic_spkr_en_left_chan(OFF_CMD); + pmic_spkr_add_right_left_chan(OFF_CMD); + pmic_spkr_en_stereo(OFF_CMD); + pmic_spkr_select_usb_with_hpf_20hz(OFF_CMD); + pmic_spkr_bypass_mux(OFF_CMD); + pmic_spkr_en_hpf(ON_CMD); + pmic_spkr_en_sink_curr_from_ref_volt_cir(OFF_CMD); + pmic_spkr_set_mux_hpf_corner_freq(SPKR_FREQ_0_73KHZ); + pmic_mic_set_volt(MIC_VOLT_1_80V); + + gpio_request(MAHIMAHI_AUD_JACKHP_EN, "aud_jackhp_en"); + gpio_request(MAHIMAHI_BT_PCM_OUT, "bt_pcm_out"); + + gpio_direction_output(MAHIMAHI_AUD_JACKHP_EN, 0); + + mutex_lock(&bt_sco_lock); + config_gpio_table(bt_sco_disable, + ARRAY_SIZE(bt_sco_disable)); + gpio_direction_output(MAHIMAHI_BT_PCM_OUT, 0); + mutex_unlock(&bt_sco_lock); +} + +int mahimahi_get_rx_vol(uint8_t hw, int level) +{ + int vol; + + if (level > 100) + level = 100; + else if (level < 0) + level = 0; + + if (is_cdma_version(system_rev) && hw == Q6_HW_HANDSET) { + int handset_volume[6] = { -1600, -1300, -1000, -600, -300, 0 }; + vol = handset_volume[5 * level / 100]; + } else { + struct q6_hw_info *info; + info = &q6_audio_hw[hw]; + vol = info->min_gain + ((info->max_gain - info->min_gain) * level) / 100; + } + + D("%s %d\n", __func__, vol); + return vol; +} + +static struct qsd_acoustic_ops acoustic = { + .enable_mic_bias = mahimahi_mic_enable, +}; + +static struct q6audio_analog_ops ops = { + .init = mahimahi_analog_init, + .speaker_enable = mahimahi_speaker_enable, + .headset_enable = mahimahi_headset_enable, + .receiver_enable = mahimahi_receiver_enable, + .bt_sco_enable = mahimahi_bt_sco_enable, + .int_mic_enable = mahimahi_mic_enable, + .ext_mic_enable = mahimahi_mic_enable, + .get_rx_vol = mahimahi_get_rx_vol, +}; + +void __init mahimahi_audio_init(void) +{ + mutex_init(&mic_lock); + mutex_init(&bt_sco_lock); + q6audio_register_analog_ops(&ops); + acoustic_register_ops(&acoustic); + if (is_cdma_version(system_rev) && + ((system_rev == 0xC1) || (system_rev == 0xC2))) + q6audio_set_acdb_file("default_PMIC.acdb"); +} diff --git a/arch/arm/mach-msm/board-mahimahi-flashlight.c b/arch/arm/mach-msm/board-mahimahi-flashlight.c new file mode 100644 index 00000000000..829b1f11cf2 --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-flashlight.c @@ -0,0 +1,279 @@ +/* + * arch/arm/mach-msm/flashlight.c - flashlight driver + * + * Copyright (C) 2009 zion huang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "board-mahimahi-flashlight.h" + +struct flashlight_struct { + struct led_classdev fl_lcdev; + struct early_suspend early_suspend_flashlight; + spinlock_t spin_lock; + struct hrtimer timer; + int brightness; + int gpio_torch; + int gpio_flash; + int flash_duration_ms; +}; + +static struct flashlight_struct the_fl; + +static inline void toggle(void) +{ + gpio_direction_output(the_fl.gpio_torch, 0); + udelay(2); + gpio_direction_output(the_fl.gpio_torch, 1); + udelay(2); +} + +static void flashlight_hw_command(uint8_t addr, uint8_t data) +{ + int i; + + for (i = 0; i < addr + 17; i++) + toggle(); + udelay(500); + + for (i = 0; i < data; i++) + toggle(); + udelay(500); +} + +static enum hrtimer_restart flashlight_timeout(struct hrtimer *timer) +{ + unsigned long flags; + + pr_debug("%s\n", __func__); + + spin_lock_irqsave(&the_fl.spin_lock, flags); + gpio_direction_output(the_fl.gpio_flash, 0); + the_fl.brightness = LED_OFF; + spin_unlock_irqrestore(&the_fl.spin_lock, flags); + + return HRTIMER_NORESTART; +} + +int flashlight_control(int mode) +{ + int ret = 0; + unsigned long flags; + + pr_debug("%s: mode %d -> %d\n", __func__, + the_fl.brightness, mode); + + spin_lock_irqsave(&the_fl.spin_lock, flags); + + the_fl.brightness = mode; + + switch (mode) { + case FLASHLIGHT_TORCH: + pr_info("%s: half\n", __func__); + /* If we are transitioning from flash to torch, make sure to + * cancel the flash timeout timer, otherwise when it expires, + * the torch will go off as well. + */ + hrtimer_cancel(&the_fl.timer); + flashlight_hw_command(2, 4); + break; + + case FLASHLIGHT_FLASH: + pr_info("%s: full\n", __func__); + hrtimer_cancel(&the_fl.timer); + gpio_direction_output(the_fl.gpio_flash, 0); + udelay(40); + gpio_direction_output(the_fl.gpio_flash, 1); + hrtimer_start(&the_fl.timer, + ktime_set(the_fl.flash_duration_ms / 1000, + (the_fl.flash_duration_ms % 1000) * + NSEC_PER_MSEC), + HRTIMER_MODE_REL); + /* Flash overrides torch mode, and after the flash period, the + * flash LED will turn off. + */ + mode = LED_OFF; + break; + + case FLASHLIGHT_OFF: + pr_info("%s: off\n", __func__); + gpio_direction_output(the_fl.gpio_flash, 0); + gpio_direction_output(the_fl.gpio_torch, 0); + break; + + default: + pr_err("%s: unknown flash_light flags: %d\n", __func__, mode); + ret = -EINVAL; + goto done; + } + +done: + spin_unlock_irqrestore(&the_fl.spin_lock, flags); + return ret; +} +EXPORT_SYMBOL(flashlight_control); + +static void fl_lcdev_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int level; + switch (brightness) { + case LED_HALF: + level = FLASHLIGHT_TORCH; + break; + case LED_FULL: + level = FLASHLIGHT_FLASH; + break; + case LED_OFF: + default: + level = FLASHLIGHT_OFF; + }; + + flashlight_control(level); +} + +static void flashlight_early_suspend(struct early_suspend *handler) +{ + flashlight_control(FLASHLIGHT_OFF); +} + +static int flashlight_setup_gpio(struct flashlight_platform_data *fl_pdata) +{ + int ret; + + pr_debug("%s\n", __func__); + + if (fl_pdata->gpio_init) { + ret = fl_pdata->gpio_init(); + if (ret < 0) { + pr_err("%s: gpio init failed: %d\n", __func__, + ret); + return ret; + } + } + + if (fl_pdata->torch) { + ret = gpio_request(fl_pdata->torch, "flashlight_torch"); + if (ret < 0) { + pr_err("%s: gpio_request failed\n", __func__); + return ret; + } + } + + if (fl_pdata->flash) { + ret = gpio_request(fl_pdata->flash, "flashlight_flash"); + if (ret < 0) { + pr_err("%s: gpio_request failed\n", __func__); + gpio_free(fl_pdata->torch); + return ret; + } + } + + the_fl.gpio_torch = fl_pdata->torch; + the_fl.gpio_flash = fl_pdata->flash; + the_fl.flash_duration_ms = fl_pdata->flash_duration_ms; + return 0; +} + +static int flashlight_probe(struct platform_device *pdev) +{ + struct flashlight_platform_data *fl_pdata = pdev->dev.platform_data; + int err = 0; + + pr_debug("%s\n", __func__); + + err = flashlight_setup_gpio(fl_pdata); + if (err < 0) { + pr_err("%s: setup GPIO failed\n", __func__); + goto fail_free_mem; + } + + spin_lock_init(&the_fl.spin_lock); + the_fl.fl_lcdev.name = pdev->name; + the_fl.fl_lcdev.brightness_set = fl_lcdev_brightness_set; + the_fl.fl_lcdev.brightness = LED_OFF; + err = led_classdev_register(&pdev->dev, &the_fl.fl_lcdev); + if (err < 0) { + pr_err("failed on led_classdev_register\n"); + goto fail_free_gpio; + } + + hrtimer_init(&the_fl.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + the_fl.timer.function = flashlight_timeout; + +#ifdef CONFIG_HAS_EARLYSUSPEND + the_fl.early_suspend_flashlight.suspend = flashlight_early_suspend; + the_fl.early_suspend_flashlight.resume = NULL; + register_early_suspend(&the_fl.early_suspend_flashlight); +#endif + + return 0; + +fail_free_gpio: + if (fl_pdata->torch) + gpio_free(fl_pdata->torch); + if (fl_pdata->flash) + gpio_free(fl_pdata->flash); +fail_free_mem: + return err; +} + +static int flashlight_remove(struct platform_device *pdev) +{ + struct flashlight_platform_data *fl_pdata = pdev->dev.platform_data; + + pr_debug("%s\n", __func__); + + hrtimer_cancel(&the_fl.timer); + unregister_early_suspend(&the_fl.early_suspend_flashlight); + flashlight_control(FLASHLIGHT_OFF); + led_classdev_unregister(&the_fl.fl_lcdev); + if (fl_pdata->torch) + gpio_free(fl_pdata->torch); + if (fl_pdata->flash) + gpio_free(fl_pdata->flash); + return 0; +} + +static struct platform_driver flashlight_driver = { + .probe = flashlight_probe, + .remove = flashlight_remove, + .driver = { + .name = FLASHLIGHT_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init flashlight_init(void) +{ + pr_debug("%s\n", __func__); + return platform_driver_register(&flashlight_driver); +} + +static void __exit flashlight_exit(void) +{ + pr_debug("%s\n", __func__); + platform_driver_unregister(&flashlight_driver); +} + +module_init(flashlight_init); +module_exit(flashlight_exit); + +MODULE_AUTHOR("Zion Huang "); +MODULE_DESCRIPTION("flash light driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/board-mahimahi-flashlight.h b/arch/arm/mach-msm/board-mahimahi-flashlight.h new file mode 100644 index 00000000000..93b4095edaa --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-flashlight.h @@ -0,0 +1,20 @@ +#ifndef __ASM_ARM_ARCH_FLASHLIGHT_H +#define __ASM_ARM_ARCH_FLASHLIGHT_H + +#define FLASHLIGHT_NAME "flashlight" + +#define FLASHLIGHT_OFF 0 +#define FLASHLIGHT_TORCH 1 +#define FLASHLIGHT_FLASH 2 +#define FLASHLIGHT_NUM 3 + +struct flashlight_platform_data { + int (*gpio_init) (void); + int torch; + int flash; + int flash_duration_ms; +}; + +int flashlight_control(int level); + +#endif /*__ASM_ARM_ARCH_FLASHLIGHT_H*/ diff --git a/arch/arm/mach-msm/board-mahimahi-keypad.c b/arch/arm/mach-msm/board-mahimahi-keypad.c new file mode 100644 index 00000000000..ab38847d15e --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-keypad.c @@ -0,0 +1,265 @@ +/* arch/arm/mach-msm/board-mahimahi-keypad.c + * + * Copyright (C) 2009 Google, Inc + * Copyright (C) 2009 HTC Corporation. + * + * Author: Dima Zavin + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "board-mahimahi.h" + +struct jog_axis_info { + struct gpio_event_axis_info info; + uint16_t in_state; + uint16_t out_state; +}; + +static struct vreg *jog_vreg; +static bool jog_just_on; +static unsigned long jog_on_jiffies; + +static unsigned int mahimahi_col_gpios[] = { 33, 32, 31 }; +static unsigned int mahimahi_row_gpios[] = { 42, 41, 40 }; + +#define KEYMAP_INDEX(col, row) ((col)*ARRAY_SIZE(mahimahi_row_gpios) + (row)) +#define KEYMAP_SIZE (ARRAY_SIZE(mahimahi_col_gpios) * \ + ARRAY_SIZE(mahimahi_row_gpios)) + +/* keypad */ +static const unsigned short mahimahi_keymap[KEYMAP_SIZE] = { + [KEYMAP_INDEX(0, 0)] = KEY_VOLUMEUP, + [KEYMAP_INDEX(0, 1)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(1, 1)] = MATRIX_KEY(1, BTN_MOUSE), +}; + +static const unsigned short mahimahi_cdma_keymap[KEYMAP_SIZE] = { + [KEYMAP_INDEX(0, 0)] = KEY_VOLUMEUP, + [KEYMAP_INDEX(0, 1)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(1, 1)] = MATRIX_KEY(1, BTN_MOUSE), + + /* Key (2, 2) is not a physical key on mahimahi. The purpose of + * registering the unused matrix key as a dummy key is to make + * userland able to send/receive the key event for some requested tests + * in lab. of some CDMA carriers (e.g. Verizon). + */ + [KEYMAP_INDEX(2, 2)] = KEY_END, +}; + +static struct gpio_event_matrix_info mahimahi_keypad_matrix_info = { + .info.func = gpio_event_matrix_func, + .keymap = mahimahi_keymap, + .output_gpios = mahimahi_col_gpios, + .input_gpios = mahimahi_row_gpios, + .noutputs = ARRAY_SIZE(mahimahi_col_gpios), + .ninputs = ARRAY_SIZE(mahimahi_row_gpios), + .settle_time.tv.nsec = 40 * NSEC_PER_USEC, + .poll_time.tv.nsec = 20 * NSEC_PER_MSEC, + .flags = (GPIOKPF_LEVEL_TRIGGERED_IRQ | + GPIOKPF_REMOVE_PHANTOM_KEYS | + GPIOKPF_PRINT_UNMAPPED_KEYS), +}; + +static struct gpio_event_direct_entry mahimahi_keypad_key_map[] = { + { + .gpio = MAHIMAHI_GPIO_POWER_KEY, + .code = KEY_POWER, + }, +}; + +static struct gpio_event_input_info mahimahi_keypad_key_info = { + .info.func = gpio_event_input_func, + .info.no_suspend = true, + .debounce_time.tv.nsec = 5 * NSEC_PER_MSEC, + .flags = 0, + .type = EV_KEY, + .keymap = mahimahi_keypad_key_map, + .keymap_size = ARRAY_SIZE(mahimahi_keypad_key_map) +}; + +/* jogball */ +static uint16_t jogball_axis_map(struct gpio_event_axis_info *info, uint16_t in) +{ + struct jog_axis_info *ai = + container_of(info, struct jog_axis_info, info); + uint16_t out = ai->out_state; + + if (jog_just_on) { + if (jiffies == jog_on_jiffies || jiffies == jog_on_jiffies + 1) + goto ignore; + jog_just_on = 0; + } + if((ai->in_state ^ in) & 1) + out--; + if((ai->in_state ^ in) & 2) + out++; + ai->out_state = out; +ignore: + ai->in_state = in; + return out; +} + +static int jogball_power(const struct gpio_event_platform_data *pdata, bool on) +{ + if (on) { + vreg_enable(jog_vreg); + jog_just_on = 1; + jog_on_jiffies = jiffies; + } else { + vreg_disable(jog_vreg); + } + + return 0; +} + +static int jogball_power_cdma(const struct gpio_event_platform_data *pdata, bool on) +{ + if (on) { + gpio_set_value(MAHIMAHI_CDMA_JOG_2V6_EN, 1); + jog_just_on = 1; + jog_on_jiffies = jiffies; + } else { + gpio_set_value(MAHIMAHI_CDMA_JOG_2V6_EN, 0); + } + + return 0; +} + +static uint32_t jogball_x_gpios[] = { + MAHIMAHI_GPIO_BALL_LEFT, MAHIMAHI_GPIO_BALL_RIGHT, +}; +static uint32_t jogball_y_gpios[] = { + MAHIMAHI_GPIO_BALL_UP, MAHIMAHI_GPIO_BALL_DOWN, +}; + +static struct jog_axis_info jogball_x_axis = { + .info = { + .info.func = gpio_event_axis_func, + .count = ARRAY_SIZE(jogball_x_gpios), + .dev = 1, + .type = EV_REL, + .code = REL_X, + .decoded_size = 1U << ARRAY_SIZE(jogball_x_gpios), + .map = jogball_axis_map, + .gpio = jogball_x_gpios, + .flags = GPIOEAF_PRINT_UNKNOWN_DIRECTION, + } +}; + +static struct jog_axis_info jogball_y_axis = { + .info = { + .info.func = gpio_event_axis_func, + .count = ARRAY_SIZE(jogball_y_gpios), + .dev = 1, + .type = EV_REL, + .code = REL_Y, + .decoded_size = 1U << ARRAY_SIZE(jogball_y_gpios), + .map = jogball_axis_map, + .gpio = jogball_y_gpios, + .flags = GPIOEAF_PRINT_UNKNOWN_DIRECTION, + } +}; + +static struct gpio_event_info *mahimahi_input_info[] = { + &mahimahi_keypad_matrix_info.info, + &mahimahi_keypad_key_info.info, + &jogball_x_axis.info.info, + &jogball_y_axis.info.info, +}; + +static struct gpio_event_platform_data mahimahi_input_data = { + .names = { + "mahimahi-keypad", + "mahimahi-nav", + NULL, + }, + .info = mahimahi_input_info, + .info_count = ARRAY_SIZE(mahimahi_input_info), + .power = jogball_power, +}; + +static struct platform_device mahimahi_input_device = { + .name = GPIO_EVENT_DEV_NAME, + .id = 0, + .dev = { + .platform_data = &mahimahi_input_data, + }, +}; + +static int mahimahi_reset_keys_up[] = { + KEY_VOLUMEUP, + 0, +}; + +static struct keyreset_platform_data mahimahi_reset_keys_pdata = { + .keys_up = mahimahi_reset_keys_up, + .keys_down = { + KEY_POWER, + KEY_VOLUMEDOWN, + BTN_MOUSE, + 0 + }, +}; + +struct platform_device mahimahi_reset_keys_device = { + .name = KEYRESET_NAME, + .dev = { + .platform_data = &mahimahi_reset_keys_pdata, + }, +}; + + +static int __init mahimahi_init_keypad_jogball(void) +{ + int ret; + + if (!machine_is_mahimahi()) + return 0; + + ret = platform_device_register(&mahimahi_reset_keys_device); + if (ret != 0) + return ret; + + if (is_cdma_version(system_rev)) { + mahimahi_keypad_matrix_info.keymap = mahimahi_cdma_keymap; + /* In the CDMA version, jogball power is supplied by a gpio. */ + ret = gpio_request(MAHIMAHI_CDMA_JOG_2V6_EN, "jog_en"); + if (ret < 0) { + pr_err("%s: gpio_request(%d) failed: %d\n", __func__, + MAHIMAHI_CDMA_JOG_2V6_EN, ret); + return ret; + } + mahimahi_input_data.power = jogball_power_cdma; + } else { + /* in UMTS version, jogball power is supplied by pmic */ + jog_vreg = vreg_get(&mahimahi_input_device.dev, "gp2"); + if (jog_vreg == NULL) + return -ENOENT; + } + + ret = platform_device_register(&mahimahi_input_device); + if (ret != 0) + return ret; + + return 0; +} + +device_initcall(mahimahi_init_keypad_jogball); diff --git a/arch/arm/mach-msm/board-mahimahi-microp.c b/arch/arm/mach-msm/board-mahimahi-microp.c new file mode 100644 index 00000000000..da30672d26a --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-microp.c @@ -0,0 +1,2261 @@ +/* board-mahimahi-microp.c + * Copyright (C) 2009 Google. + * Copyright (C) 2009 HTC Corporation. + * + * The Microp on mahimahi is an i2c device that supports + * the following functions + * - LEDs (Green, Amber, Jogball backlight) + * - Lightsensor + * - Headset remotekeys + * - G-sensor + * - Interrupts + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "board-mahimahi.h" + + +#define MICROP_I2C_NAME "mahimahi-microp" + +#define MICROP_LSENSOR_ADC_CHAN 6 +#define MICROP_REMOTE_KEY_ADC_CHAN 7 + +#define MICROP_I2C_WCMD_MISC 0x20 +#define MICROP_I2C_WCMD_SPI_EN 0x21 +#define MICROP_I2C_WCMD_AUTO_BL_CTL 0x23 +#define MICROP_I2C_RCMD_SPI_BL_STATUS 0x24 +#define MICROP_I2C_WCMD_BUTTONS_LED_CTRL 0x25 +#define MICROP_I2C_RCMD_VERSION 0x30 +#define MICROP_I2C_WCMD_ADC_TABLE 0x42 +#define MICROP_I2C_WCMD_LED_MODE 0x53 +#define MICROP_I2C_RCMD_GREEN_LED_REMAIN_TIME 0x54 +#define MICROP_I2C_RCMD_AMBER_RED_LED_REMAIN_TIME 0x55 +#define MICROP_I2C_RCMD_BLUE_LED_REMAIN_TIME 0x57 +#define MICROP_I2C_WCMD_JOGBALL_LED_MODE 0x5A +#define MICROP_I2C_RCMD_JOGBALL_LED_REMAIN_TIME 0x5B +#define MICROP_I2C_WCMD_JOGBALL_LED_PWM_SET 0x5C +#define MICROP_I2C_WCMD_JOGBALL_LED_PERIOD_SET 0x5D +#define MICROP_I2C_WCMD_READ_ADC_VALUE_REQ 0x60 +#define MICROP_I2C_RCMD_ADC_VALUE 0x62 +#define MICROP_I2C_WCMD_REMOTEKEY_TABLE 0x63 +#define MICROP_I2C_WCMD_LCM_REGISTER 0x70 +#define MICROP_I2C_WCMD_GSENSOR_REG 0x73 +#define MICROP_I2C_WCMD_GSENSOR_REG_DATA_REQ 0x74 +#define MICROP_I2C_RCMD_GSENSOR_REG_DATA 0x75 +#define MICROP_I2C_WCMD_GSENSOR_DATA_REQ 0x76 +#define MICROP_I2C_RCMD_GSENSOR_X_DATA 0x77 +#define MICROP_I2C_RCMD_GSENSOR_Y_DATA 0x78 +#define MICROP_I2C_RCMD_GSENSOR_Z_DATA 0x79 +#define MICROP_I2C_RCMD_GSENSOR_DATA 0x7A +#define MICROP_I2C_WCMD_OJ_REG 0x7B +#define MICROP_I2C_WCMD_OJ_REG_DATA_REQ 0x7C +#define MICROP_I2C_RCMD_OJ_REG_DATA 0x7D +#define MICROP_I2C_WCMD_OJ_POS_DATA_REQ 0x7E +#define MICROP_I2C_RCMD_OJ_POS_DATA 0x7F +#define MICROP_I2C_WCMD_GPI_INT_CTL_EN 0x80 +#define MICROP_I2C_WCMD_GPI_INT_CTL_DIS 0x81 +#define MICROP_I2C_RCMD_GPI_INT_STATUS 0x82 +#define MICROP_I2C_RCMD_GPI_STATUS 0x83 +#define MICROP_I2C_WCMD_GPI_INT_STATUS_CLR 0x84 +#define MICROP_I2C_RCMD_GPI_INT_SETTING 0x85 +#define MICROP_I2C_RCMD_REMOTE_KEYCODE 0x87 +#define MICROP_I2C_WCMD_REMOTE_KEY_DEBN_TIME 0x88 +#define MICROP_I2C_WCMD_REMOTE_PLUG_DEBN_TIME 0x89 +#define MICROP_I2C_WCMD_SIMCARD_DEBN_TIME 0x8A +#define MICROP_I2C_WCMD_GPO_LED_STATUS_EN 0x90 +#define MICROP_I2C_WCMD_GPO_LED_STATUS_DIS 0x91 + +#define IRQ_GSENSOR (1<<10) +#define IRQ_LSENSOR (1<<9) +#define IRQ_REMOTEKEY (1<<7) +#define IRQ_HEADSETIN (1<<2) +#define IRQ_SDCARD (1<<0) + +#define READ_GPI_STATE_HPIN (1<<2) +#define READ_GPI_STATE_SDCARD (1<<0) + +#define ALS_CALIBRATE_MODE 147 + +/* Check pattern, to check if ALS has been calibrated */ +#define ALS_CALIBRATED 0x6DA5 + +/* delay for deferred light sensor read */ +#define LS_READ_DELAY (HZ/2) + +/*#define DEBUG_BMA150 */ +#ifdef DEBUG_BMA150 +/* Debug logging of accelleration data */ +#define GSENSOR_LOG_MAX 2048 /* needs to be power of 2 */ +#define GSENSOR_LOG_MASK (GSENSOR_LOG_MAX - 1) + +struct gsensor_log { + ktime_t timestamp; + short x; + short y; + short z; +}; + +static DEFINE_MUTEX(gsensor_log_lock); +static struct gsensor_log gsensor_log[GSENSOR_LOG_MAX]; +static unsigned gsensor_log_head; +static unsigned gsensor_log_tail; + +void gsensor_log_status(ktime_t time, short x, short y, short z) +{ + unsigned n; + mutex_lock(&gsensor_log_lock); + n = gsensor_log_head; + gsensor_log[n].timestamp = time; + gsensor_log[n].x = x; + gsensor_log[n].y = y; + gsensor_log[n].z = z; + n = (n + 1) & GSENSOR_LOG_MASK; + if (n == gsensor_log_tail) + gsensor_log_tail = (gsensor_log_tail + 1) & GSENSOR_LOG_MASK; + gsensor_log_head = n; + mutex_unlock(&gsensor_log_lock); +} + +static int gsensor_log_print(struct seq_file *sf, void *private) +{ + unsigned n; + + mutex_lock(&gsensor_log_lock); + seq_printf(sf, "timestamp X Y Z\n"); + for (n = gsensor_log_tail; + n != gsensor_log_head; + n = (n + 1) & GSENSOR_LOG_MASK) { + seq_printf(sf, "%10d.%010d %6d %6d %6d\n", + gsensor_log[n].timestamp.tv.sec, + gsensor_log[n].timestamp.tv.nsec, + gsensor_log[n].x, gsensor_log[n].y, + gsensor_log[n].z); + } + mutex_unlock(&gsensor_log_lock); + return 0; +} + +static int gsensor_log_open(struct inode *inode, struct file *file) +{ + return single_open(file, gsensor_log_print, NULL); +} + +static struct file_operations gsensor_log_fops = { + .open = gsensor_log_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif /* def DEBUG_BMA150 */ + +static int microp_headset_has_mic(void); +static int microp_enable_headset_plug_event(void); +static int microp_enable_key_event(void); +static int microp_disable_key_event(void); + +static struct h35mm_platform_data mahimahi_h35mm_data = { + .plug_event_enable = microp_enable_headset_plug_event, + .headset_has_mic = microp_headset_has_mic, + .key_event_enable = microp_enable_key_event, + .key_event_disable = microp_disable_key_event, +}; + +static struct platform_device mahimahi_h35mm = { + .name = "htc_headset", + .id = -1, + .dev = { + .platform_data = &mahimahi_h35mm_data, + }, +}; + +enum led_type { + GREEN_LED, + AMBER_LED, + RED_LED, + BLUE_LED, + JOGBALL_LED, + BUTTONS_LED, + NUM_LEDS, +}; + +static uint16_t lsensor_adc_table[10] = { + 0x000, 0x001, 0x00F, 0x01E, 0x03C, 0x121, 0x190, 0x2BA, 0x26E, 0x3FF +}; + +static uint16_t remote_key_adc_table[6] = { + 0, 33, 43, 110, 129, 220 +}; + +static uint32_t golden_adc = 0xC0; +static uint32_t als_kadc; + +static struct wake_lock microp_i2c_wakelock; + +static struct i2c_client *private_microp_client; + +struct microp_int_pin { + uint16_t int_gsensor; + uint16_t int_lsensor; + uint16_t int_reset; + uint16_t int_simcard; + uint16_t int_hpin; + uint16_t int_remotekey; +}; + +struct microp_led_data { + int type; + struct led_classdev ldev; + struct mutex led_data_mutex; + struct work_struct brightness_work; + spinlock_t brightness_lock; + enum led_brightness brightness; + uint8_t mode; + uint8_t blink; +}; + +struct microp_i2c_work { + struct work_struct work; + struct i2c_client *client; + int (*intr_debounce)(uint8_t *pin_status); + void (*intr_function)(uint8_t *pin_status); +}; + +struct microp_i2c_client_data { + struct microp_led_data leds[NUM_LEDS]; + uint16_t version; + struct microp_i2c_work work; + struct delayed_work hpin_debounce_work; + struct delayed_work ls_read_work; + struct early_suspend early_suspend; + uint8_t enable_early_suspend; + uint8_t enable_reset_button; + int microp_is_suspend; + int auto_backlight_enabled; + uint8_t light_sensor_enabled; + uint8_t force_light_sensor_read; + uint8_t button_led_value; + int headset_is_in; + int is_hpin_pin_stable; + struct input_dev *ls_input_dev; + uint32_t als_kadc; + uint32_t als_gadc; + uint8_t als_calibrating; +}; + +static char *hex2string(uint8_t *data, int len) +{ + static char buf[101]; + int i; + + i = (sizeof(buf) - 1) / 4; + if (len > i) + len = i; + + for (i = 0; i < len; i++) + sprintf(buf + i * 4, "[%02X]", data[i]); + + return buf; +} + +#define I2C_READ_RETRY_TIMES 10 +#define I2C_WRITE_RETRY_TIMES 10 + +static int i2c_read_block(struct i2c_client *client, uint8_t addr, + uint8_t *data, int length) +{ + int retry; + int ret; + struct i2c_msg msgs[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + .buf = data, + } + }; + + mdelay(1); + for (retry = 0; retry <= I2C_READ_RETRY_TIMES; retry++) { + ret = i2c_transfer(client->adapter, msgs, 2); + if (ret == 2) { + dev_dbg(&client->dev, "R [%02X] = %s\n", addr, + hex2string(data, length)); + return 0; + } + msleep(10); + } + + dev_err(&client->dev, "i2c_read_block retry over %d\n", + I2C_READ_RETRY_TIMES); + return -EIO; +} + +#define MICROP_I2C_WRITE_BLOCK_SIZE 21 +static int i2c_write_block(struct i2c_client *client, uint8_t addr, + uint8_t *data, int length) +{ + int retry; + uint8_t buf[MICROP_I2C_WRITE_BLOCK_SIZE]; + int ret; + + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = length + 1, + .buf = buf, + } + }; + + dev_dbg(&client->dev, "W [%02X] = %s\n", addr, + hex2string(data, length)); + + if (length + 1 > MICROP_I2C_WRITE_BLOCK_SIZE) { + dev_err(&client->dev, "i2c_write_block length too long\n"); + return -E2BIG; + } + + buf[0] = addr; + memcpy((void *)&buf[1], (void *)data, length); + + mdelay(1); + for (retry = 0; retry <= I2C_WRITE_RETRY_TIMES; retry++) { + ret = i2c_transfer(client->adapter, msg, 1); + if (ret == 1) + return 0; + msleep(10); + } + dev_err(&client->dev, "i2c_write_block retry over %d\n", + I2C_WRITE_RETRY_TIMES); + return -EIO; +} + +static int microp_read_adc(uint8_t channel, uint16_t *value) +{ + struct i2c_client *client; + int ret; + uint8_t cmd[2], data[2]; + + client = private_microp_client; + cmd[0] = 0; + cmd[1] = channel; + ret = i2c_write_block(client, MICROP_I2C_WCMD_READ_ADC_VALUE_REQ, + cmd, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: request adc fail\n", __func__); + return -EIO; + } + + ret = i2c_read_block(client, MICROP_I2C_RCMD_ADC_VALUE, data, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: read adc fail\n", __func__); + return -EIO; + } + *value = data[0] << 8 | data[1]; + return 0; +} + +static int microp_read_gpi_status(struct i2c_client *client, uint16_t *status) +{ + uint8_t data[2]; + int ret; + + ret = i2c_read_block(client, MICROP_I2C_RCMD_GPI_STATUS, data, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: read failed\n", __func__); + return -EIO; + } + *status = (data[0] << 8) | data[1]; + return 0; +} + +static int microp_interrupt_enable(struct i2c_client *client, + uint16_t interrupt_mask) +{ + uint8_t data[2]; + int ret = -1; + + data[0] = interrupt_mask >> 8; + data[1] = interrupt_mask & 0xFF; + ret = i2c_write_block(client, MICROP_I2C_WCMD_GPI_INT_CTL_EN, data, 2); + + if (ret < 0) + dev_err(&client->dev, "%s: enable 0x%x interrupt failed\n", + __func__, interrupt_mask); + return ret; +} + +static int microp_interrupt_disable(struct i2c_client *client, + uint16_t interrupt_mask) +{ + uint8_t data[2]; + int ret = -1; + + data[0] = interrupt_mask >> 8; + data[1] = interrupt_mask & 0xFF; + ret = i2c_write_block(client, MICROP_I2C_WCMD_GPI_INT_CTL_DIS, data, 2); + + if (ret < 0) + dev_err(&client->dev, "%s: disable 0x%x interrupt failed\n", + __func__, interrupt_mask); + return ret; +} + + +/* + * SD slot card-detect support + */ +static unsigned int sdslot_cd = 0; +static void (*sdslot_status_cb)(int card_present, void *dev_id); +static void *sdslot_mmc_dev; + +int mahimahi_microp_sdslot_status_register( + void (*cb)(int card_present, void *dev_id), + void *dev_id) +{ + if (sdslot_status_cb) + return -EBUSY; + sdslot_status_cb = cb; + sdslot_mmc_dev = dev_id; + return 0; +} + +unsigned int mahimahi_microp_sdslot_status(struct device *dev) +{ + return sdslot_cd; +} + +static void mahimahi_microp_sdslot_update_status(int status) +{ + sdslot_cd = !(status & READ_GPI_STATE_SDCARD); + if (sdslot_status_cb) + sdslot_status_cb(sdslot_cd, sdslot_mmc_dev); +} + +/* + *Headset Support +*/ +static void hpin_debounce_do_work(struct work_struct *work) +{ + uint16_t gpi_status = 0; + struct microp_i2c_client_data *cdata; + int insert = 0; + struct i2c_client *client; + + client = private_microp_client; + cdata = i2c_get_clientdata(client); + + microp_read_gpi_status(client, &gpi_status); + insert = (gpi_status & READ_GPI_STATE_HPIN) ? 0 : 1; + if (insert != cdata->headset_is_in) { + cdata->headset_is_in = insert; + pr_debug("headset %s\n", insert ? "inserted" : "removed"); + htc_35mm_jack_plug_event(cdata->headset_is_in, + &cdata->is_hpin_pin_stable); + } +} + +static int microp_enable_headset_plug_event(void) +{ + int ret; + struct i2c_client *client; + struct microp_i2c_client_data *cdata; + uint16_t stat; + + client = private_microp_client; + cdata = i2c_get_clientdata(client); + + /* enable microp interrupt to detect changes */ + ret = microp_interrupt_enable(client, IRQ_HEADSETIN); + if (ret < 0) { + dev_err(&client->dev, "%s: failed to enable irqs\n", + __func__); + return 0; + } + /* see if headset state has changed */ + microp_read_gpi_status(client, &stat); + stat = !(stat & READ_GPI_STATE_HPIN); + if(cdata->headset_is_in != stat) { + cdata->headset_is_in = stat; + pr_debug("Headset state changed\n"); + htc_35mm_jack_plug_event(stat, &cdata->is_hpin_pin_stable); + } + + return 1; +} + +static int microp_headset_detect_mic(void) +{ + uint16_t data; + + microp_read_adc(MICROP_REMOTE_KEY_ADC_CHAN, &data); + if (data >= 200) + return 1; + else + return 0; +} + +static int microp_headset_has_mic(void) +{ + int mic1 = -1; + int mic2 = -1; + int count = 0; + + mic2 = microp_headset_detect_mic(); + + /* debounce the detection wait until 2 consecutive read are equal */ + while ((mic1 != mic2) && (count < 10)) { + mic1 = mic2; + msleep(600); + mic2 = microp_headset_detect_mic(); + count++; + } + + pr_info("%s: microphone (%d) %s\n", __func__, count, + mic1 ? "present" : "not present"); + + return mic1; +} + +static int microp_enable_key_event(void) +{ + int ret; + struct i2c_client *client; + + client = private_microp_client; + + if (!is_cdma_version(system_rev)) + gpio_set_value(MAHIMAHI_GPIO_35MM_KEY_INT_SHUTDOWN, 1); + + /* turn on key interrupt */ + /* enable microp interrupt to detect changes */ + ret = microp_interrupt_enable(client, IRQ_REMOTEKEY); + if (ret < 0) { + dev_err(&client->dev, "%s: failed to enable irqs\n", + __func__); + return ret; + } + return 0; +} + +static int microp_disable_key_event(void) +{ + int ret; + struct i2c_client *client; + + client = private_microp_client; + + /* shutdown key interrupt */ + if (!is_cdma_version(system_rev)) + gpio_set_value(MAHIMAHI_GPIO_35MM_KEY_INT_SHUTDOWN, 0); + + /* disable microp interrupt to detect changes */ + ret = microp_interrupt_disable(client, IRQ_REMOTEKEY); + if (ret < 0) { + dev_err(&client->dev, "%s: failed to disable irqs\n", + __func__); + return ret; + } + return 0; +} + +static int get_remote_keycode(int *keycode) +{ + struct i2c_client *client = private_microp_client; + int ret; + uint8_t data[2]; + + ret = i2c_read_block(client, MICROP_I2C_RCMD_REMOTE_KEYCODE, data, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: read remote keycode fail\n", + __func__); + return -EIO; + } + pr_debug("%s: key = 0x%x\n", __func__, data[1]); + if (!data[1]) { + *keycode = 0; + return 1; /* no keycode */ + } else { + *keycode = data[1]; + } + return 0; +} + +static ssize_t microp_i2c_remotekey_adc_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + uint16_t value; + int i, button = 0; + int ret; + + client = to_i2c_client(dev); + + microp_read_adc(MICROP_REMOTE_KEY_ADC_CHAN, &value); + + for (i = 0; i < 3; i++) { + if ((value >= remote_key_adc_table[2 * i]) && + (value <= remote_key_adc_table[2 * i + 1])) { + button = i + 1; + } + + } + + ret = sprintf(buf, "Remote Key[0x%03X] => button %d\n", + value, button); + + return ret; +} + +static DEVICE_ATTR(key_adc, 0644, microp_i2c_remotekey_adc_show, NULL); + +/* + * LED support +*/ +static int microp_i2c_write_led_mode(struct i2c_client *client, + struct led_classdev *led_cdev, + uint8_t mode, uint16_t off_timer) +{ + struct microp_i2c_client_data *cdata; + struct microp_led_data *ldata; + uint8_t data[7]; + int ret; + + cdata = i2c_get_clientdata(client); + ldata = container_of(led_cdev, struct microp_led_data, ldev); + + + if (ldata->type == GREEN_LED) { + data[0] = 0x01; + data[1] = mode; + data[2] = off_timer >> 8; + data[3] = off_timer & 0xFF; + data[4] = 0x00; + data[5] = 0x00; + data[6] = 0x00; + } else if (ldata->type == AMBER_LED) { + data[0] = 0x02; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + data[4] = mode; + data[5] = off_timer >> 8; + data[6] = off_timer & 0xFF; + } else if (ldata->type == RED_LED) { + data[0] = 0x02; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + data[4] = mode? 5: 0; + data[5] = off_timer >> 8; + data[6] = off_timer & 0xFF; + } else if (ldata->type == BLUE_LED) { + data[0] = 0x04; + data[1] = mode; + data[2] = off_timer >> 8; + data[3] = off_timer & 0xFF; + data[4] = 0x00; + data[5] = 0x00; + data[6] = 0x00; + } + + ret = i2c_write_block(client, MICROP_I2C_WCMD_LED_MODE, data, 7); + if (ret == 0) { + mutex_lock(&ldata->led_data_mutex); + if (mode > 1) + ldata->blink = mode; + else + ldata->mode = mode; + mutex_unlock(&ldata->led_data_mutex); + } + return ret; +} + +static ssize_t microp_i2c_led_blink_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev; + struct microp_led_data *ldata; + int ret; + + led_cdev = (struct led_classdev *)dev_get_drvdata(dev); + ldata = container_of(led_cdev, struct microp_led_data, ldev); + + mutex_lock(&ldata->led_data_mutex); + ret = sprintf(buf, "%d\n", ldata->blink ? ldata->blink - 1 : 0); + mutex_unlock(&ldata->led_data_mutex); + + return ret; +} + +static ssize_t microp_i2c_led_blink_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev; + struct microp_led_data *ldata; + struct i2c_client *client; + int val, ret; + uint8_t mode; + + val = -1; + sscanf(buf, "%u", &val); + + led_cdev = (struct led_classdev *)dev_get_drvdata(dev); + ldata = container_of(led_cdev, struct microp_led_data, ldev); + client = to_i2c_client(dev->parent); + + mutex_lock(&ldata->led_data_mutex); + switch (val) { + case 0: /* stop flashing */ + mode = ldata->mode; + ldata->blink = 0; + break; + case 1: + case 2: + case 3: + mode = val + 1; + break; + + default: + mutex_unlock(&ldata->led_data_mutex); + return -EINVAL; + } + mutex_unlock(&ldata->led_data_mutex); + + ret = microp_i2c_write_led_mode(client, led_cdev, mode, 0xffff); + if (ret) + dev_err(&client->dev, "%s set blink failed\n", led_cdev->name); + + return count; +} + +static DEVICE_ATTR(blink, 0644, microp_i2c_led_blink_show, + microp_i2c_led_blink_store); + +static ssize_t microp_i2c_led_off_timer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct microp_i2c_client_data *cdata; + struct led_classdev *led_cdev; + struct microp_led_data *ldata; + struct i2c_client *client; + uint8_t data[2]; + int ret, offtime; + + + led_cdev = (struct led_classdev *)dev_get_drvdata(dev); + ldata = container_of(led_cdev, struct microp_led_data, ldev); + client = to_i2c_client(dev->parent); + cdata = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "Getting %s remaining time\n", led_cdev->name); + + if (ldata->type == GREEN_LED) { + ret = i2c_read_block(client, + MICROP_I2C_RCMD_GREEN_LED_REMAIN_TIME, data, 2); + } else if (ldata->type == AMBER_LED) { + ret = i2c_read_block(client, + MICROP_I2C_RCMD_AMBER_RED_LED_REMAIN_TIME, + data, 2); + } else if (ldata->type == RED_LED) { + ret = i2c_read_block(client, + MICROP_I2C_RCMD_AMBER_RED_LED_REMAIN_TIME, + data, 2); + } else if (ldata->type == BLUE_LED) { + ret = i2c_read_block(client, + MICROP_I2C_RCMD_BLUE_LED_REMAIN_TIME, data, 2); + } else { + dev_err(&client->dev, "Unknown led %s\n", ldata->ldev.name); + return -EINVAL; + } + + if (ret) { + dev_err(&client->dev, + "%s get off_timer failed\n", led_cdev->name); + } + offtime = (int)((data[1] | data[0] << 8) * 2); + + ret = sprintf(buf, "Time remains %d:%d\n", offtime / 60, offtime % 60); + return ret; +} + +static ssize_t microp_i2c_led_off_timer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev; + struct microp_led_data *ldata; + struct i2c_client *client; + int min, sec, ret; + uint16_t off_timer; + + min = -1; + sec = -1; + sscanf(buf, "%d %d", &min, &sec); + + if (min < 0 || min > 255) + return -EINVAL; + if (sec < 0 || sec > 255) + return -EINVAL; + + led_cdev = (struct led_classdev *)dev_get_drvdata(dev); + ldata = container_of(led_cdev, struct microp_led_data, ldev); + client = to_i2c_client(dev->parent); + + dev_dbg(&client->dev, "Setting %s off_timer to %d min %d sec\n", + led_cdev->name, min, sec); + + if (!min && !sec) + off_timer = 0xFFFF; + else + off_timer = (min * 60 + sec) / 2; + + ret = microp_i2c_write_led_mode(client, led_cdev, + ldata->mode, off_timer); + if (ret) { + dev_err(&client->dev, + "%s set off_timer %d min %d sec failed\n", + led_cdev->name, min, sec); + } + return count; +} + +static DEVICE_ATTR(off_timer, 0644, microp_i2c_led_off_timer_show, + microp_i2c_led_off_timer_store); + +static ssize_t microp_i2c_jogball_color_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev; + struct microp_led_data *ldata; + struct i2c_client *client; + int rpwm, gpwm, bpwm, ret; + uint8_t data[4]; + + rpwm = -1; + gpwm = -1; + bpwm = -1; + sscanf(buf, "%d %d %d", &rpwm, &gpwm, &bpwm); + + if (rpwm < 0 || rpwm > 255) + return -EINVAL; + if (gpwm < 0 || gpwm > 255) + return -EINVAL; + if (bpwm < 0 || bpwm > 255) + return -EINVAL; + + led_cdev = (struct led_classdev *)dev_get_drvdata(dev); + ldata = container_of(led_cdev, struct microp_led_data, ldev); + client = to_i2c_client(dev->parent); + + dev_dbg(&client->dev, "Setting %s color to R=%d, G=%d, B=%d\n", + led_cdev->name, rpwm, gpwm, bpwm); + + data[0] = rpwm; + data[1] = gpwm; + data[2] = bpwm; + data[3] = 0x00; + + ret = i2c_write_block(client, MICROP_I2C_WCMD_JOGBALL_LED_PWM_SET, + data, 4); + if (ret) { + dev_err(&client->dev, + "%s set color R=%d G=%d B=%d failed\n", + led_cdev->name, rpwm, gpwm, bpwm); + } + return count; +} + +static DEVICE_ATTR(color, 0644, NULL, microp_i2c_jogball_color_store); + +static ssize_t microp_i2c_jogball_period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev; + struct microp_led_data *ldata; + struct i2c_client *client; + int period = -1; + int ret; + uint8_t data[4]; + + sscanf(buf, "%d", &period); + + if (period < 2 || period > 12) + return -EINVAL; + + led_cdev = (struct led_classdev *)dev_get_drvdata(dev); + ldata = container_of(led_cdev, struct microp_led_data, ldev); + client = to_i2c_client(dev->parent); + + dev_info(&client->dev, "Setting Jogball flash period to %d\n", period); + + data[0] = 0x00; + data[1] = period; + + ret = i2c_write_block(client, MICROP_I2C_WCMD_JOGBALL_LED_PERIOD_SET, + data, 2); + if (ret) { + dev_err(&client->dev, "%s set period=%d failed\n", + led_cdev->name, period); + } + return count; +} + +static DEVICE_ATTR(period, 0644, NULL, microp_i2c_jogball_period_store); + +static void microp_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + unsigned long flags; + struct i2c_client *client = to_i2c_client(led_cdev->dev->parent); + struct microp_led_data *ldata = + container_of(led_cdev, struct microp_led_data, ldev); + + dev_dbg(&client->dev, "Setting %s brightness current %d new %d\n", + led_cdev->name, led_cdev->brightness, brightness); + + if (brightness > 255) + brightness = 255; + led_cdev->brightness = brightness; + + spin_lock_irqsave(&ldata->brightness_lock, flags); + ldata->brightness = brightness; + spin_unlock_irqrestore(&ldata->brightness_lock, flags); + + schedule_work(&ldata->brightness_work); +} + +static void microp_led_brightness_set_work(struct work_struct *work) +{ + unsigned long flags; + struct microp_led_data *ldata = + container_of(work, struct microp_led_data, brightness_work); + struct led_classdev *led_cdev = &ldata->ldev; + + struct i2c_client *client = to_i2c_client(led_cdev->dev->parent); + + enum led_brightness brightness; + int ret; + uint8_t mode; + + spin_lock_irqsave(&ldata->brightness_lock, flags); + brightness = ldata->brightness; + spin_unlock_irqrestore(&ldata->brightness_lock, flags); + + if (brightness) + mode = 1; + else + mode = 0; + + ret = microp_i2c_write_led_mode(client, led_cdev, mode, 0xffff); + if (ret) { + dev_err(&client->dev, + "led_brightness_set failed to set mode\n"); + } +} + +struct device_attribute *green_amber_attrs[] = { + &dev_attr_blink, + &dev_attr_off_timer, +}; + +struct device_attribute *jogball_attrs[] = { + &dev_attr_color, + &dev_attr_period, +}; + +static void microp_led_buttons_brightness_set_work(struct work_struct *work) +{ + + unsigned long flags; + struct microp_led_data *ldata = + container_of(work, struct microp_led_data, brightness_work); + struct led_classdev *led_cdev = &ldata->ldev; + + struct i2c_client *client = to_i2c_client(led_cdev->dev->parent); + struct microp_i2c_client_data *cdata = i2c_get_clientdata(client); + + + uint8_t data[4] = {0, 0, 0}; + int ret = 0; + enum led_brightness brightness; + uint8_t value; + + + spin_lock_irqsave(&ldata->brightness_lock, flags); + brightness = ldata->brightness; + spin_unlock_irqrestore(&ldata->brightness_lock, flags); + + value = brightness >= 255 ? 0x20 : 0; + + /* avoid a flicker that can occur when writing the same value */ + if (cdata->button_led_value == value) + return; + cdata->button_led_value = value; + + /* in 40ms */ + data[0] = 0x05; + /* duty cycle 0-255 */ + data[1] = value; + /* bit2 == change brightness */ + data[3] = 0x04; + + ret = i2c_write_block(client, MICROP_I2C_WCMD_BUTTONS_LED_CTRL, + data, 4); + if (ret < 0) + dev_err(&client->dev, "%s failed on set buttons\n", __func__); +} + +static void microp_led_jogball_brightness_set_work(struct work_struct *work) +{ + unsigned long flags; + struct microp_led_data *ldata = + container_of(work, struct microp_led_data, brightness_work); + struct led_classdev *led_cdev = &ldata->ldev; + + struct i2c_client *client = to_i2c_client(led_cdev->dev->parent); + uint8_t data[3] = {0, 0, 0}; + int ret = 0; + enum led_brightness brightness; + + spin_lock_irqsave(&ldata->brightness_lock, flags); + brightness = ldata->brightness; + spin_unlock_irqrestore(&ldata->brightness_lock, flags); + + switch (brightness) { + case 0: + data[0] = 0; + break; + case 3: + data[0] = 1; + data[1] = data[2] = 0xFF; + break; + case 7: + data[0] = 2; + data[1] = 0; + data[2] = 60; + break; + default: + dev_warn(&client->dev, "%s: unknown value: %d\n", + __func__, brightness); + break; + } + ret = i2c_write_block(client, MICROP_I2C_WCMD_JOGBALL_LED_MODE, + data, 3); + if (ret < 0) + dev_err(&client->dev, "%s failed on set jogball mode:0x%2.2X\n", + __func__, data[0]); +} + +/* + * Light Sensor Support + */ +static int microp_i2c_auto_backlight_mode(struct i2c_client *client, + uint8_t enabled) +{ + uint8_t data[2]; + int ret = 0; + + data[0] = 0; + if (enabled) + data[1] = 1; + else + data[1] = 0; + + ret = i2c_write_block(client, MICROP_I2C_WCMD_AUTO_BL_CTL, data, 2); + if (ret != 0) + pr_err("%s: set auto light sensor fail\n", __func__); + + return ret; +} + +static int lightsensor_enable(void) +{ + struct i2c_client *client; + struct microp_i2c_client_data *cdata; + int ret; + + client = private_microp_client; + cdata = i2c_get_clientdata(client); + + if (cdata->microp_is_suspend) { + pr_err("%s: abort, uP is going to suspend after #\n", + __func__); + return -EIO; + } + + disable_irq(client->irq); + ret = microp_i2c_auto_backlight_mode(client, 1); + if (ret < 0) { + pr_err("%s: set auto light sensor fail\n", __func__); + enable_irq(client->irq); + return ret; + } + + cdata->auto_backlight_enabled = 1; + /* TEMPORARY HACK: schedule a deferred light sensor read + * to work around sensor manager race condition + */ + schedule_delayed_work(&cdata->ls_read_work, LS_READ_DELAY); + schedule_work(&cdata->work.work); + + return 0; +} + +static int lightsensor_disable(void) +{ + /* update trigger data when done */ + struct i2c_client *client; + struct microp_i2c_client_data *cdata; + int ret; + + client = private_microp_client; + cdata = i2c_get_clientdata(client); + + if (cdata->microp_is_suspend) { + pr_err("%s: abort, uP is going to suspend after #\n", + __func__); + return -EIO; + } + + cancel_delayed_work(&cdata->ls_read_work); + + ret = microp_i2c_auto_backlight_mode(client, 0); + if (ret < 0) + pr_err("%s: disable auto light sensor fail\n", + __func__); + else + cdata->auto_backlight_enabled = 0; + return 0; +} + +static int microp_lightsensor_read(uint16_t *adc_value, + uint8_t *adc_level) +{ + struct i2c_client *client; + struct microp_i2c_client_data *cdata; + uint8_t i; + int ret; + + client = private_microp_client; + cdata = i2c_get_clientdata(client); + + ret = microp_read_adc(MICROP_LSENSOR_ADC_CHAN, adc_value); + if (ret != 0) + return -1; + + if (*adc_value > 0x3FF) { + pr_warning("%s: get wrong value: 0x%X\n", + __func__, *adc_value); + return -1; + } else { + if (!cdata->als_calibrating) { + *adc_value = *adc_value + * cdata->als_gadc / cdata->als_kadc; + if (*adc_value > 0x3FF) + *adc_value = 0x3FF; + } + + *adc_level = ARRAY_SIZE(lsensor_adc_table) - 1; + for (i = 0; i < ARRAY_SIZE(lsensor_adc_table); i++) { + if (*adc_value <= lsensor_adc_table[i]) { + *adc_level = i; + break; + } + } + pr_debug("%s: ADC value: 0x%X, level: %d #\n", + __func__, *adc_value, *adc_level); + } + + return 0; +} + +static ssize_t microp_i2c_lightsensor_adc_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + uint8_t adc_level = 0; + uint16_t adc_value = 0; + int ret; + + ret = microp_lightsensor_read(&adc_value, &adc_level); + + ret = sprintf(buf, "ADC[0x%03X] => level %d\n", adc_value, adc_level); + + return ret; +} + +static DEVICE_ATTR(ls_adc, 0644, microp_i2c_lightsensor_adc_show, NULL); + +static ssize_t microp_i2c_ls_auto_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + uint8_t data[2] = {0, 0}; + int ret; + + client = to_i2c_client(dev); + + i2c_read_block(client, MICROP_I2C_RCMD_SPI_BL_STATUS, data, 2); + ret = sprintf(buf, "Light sensor Auto = %d, SPI enable = %d\n", + data[0], data[1]); + + return ret; +} + +static ssize_t microp_i2c_ls_auto_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client; + struct microp_i2c_client_data *cdata; + uint8_t enable = 0; + int ls_auto; + + ls_auto = -1; + sscanf(buf, "%d", &ls_auto); + + if (ls_auto != 0 && ls_auto != 1 && ls_auto != ALS_CALIBRATE_MODE) + return -EINVAL; + + client = to_i2c_client(dev); + cdata = i2c_get_clientdata(client); + + if (ls_auto) { + enable = 1; + cdata->als_calibrating = (ls_auto == ALS_CALIBRATE_MODE) ? 1 : 0; + cdata->auto_backlight_enabled = 1; + } else { + enable = 0; + cdata->als_calibrating = 0; + cdata->auto_backlight_enabled = 0; + } + + microp_i2c_auto_backlight_mode(client, enable); + + return count; +} + +static DEVICE_ATTR(ls_auto, 0644, microp_i2c_ls_auto_show, + microp_i2c_ls_auto_store); + +DEFINE_MUTEX(api_lock); +static int lightsensor_opened; + +static int lightsensor_open(struct inode *inode, struct file *file) +{ + int rc = 0; + pr_debug("%s\n", __func__); + mutex_lock(&api_lock); + if (lightsensor_opened) { + pr_err("%s: already opened\n", __func__); + rc = -EBUSY; + } + lightsensor_opened = 1; + mutex_unlock(&api_lock); + return rc; +} + +static int lightsensor_release(struct inode *inode, struct file *file) +{ + pr_debug("%s\n", __func__); + mutex_lock(&api_lock); + lightsensor_opened = 0; + mutex_unlock(&api_lock); + return 0; +} + +static long lightsensor_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int rc, val; + struct i2c_client *client; + struct microp_i2c_client_data *cdata; + + mutex_lock(&api_lock); + + client = private_microp_client; + cdata = i2c_get_clientdata(client); + + pr_debug("%s cmd %d\n", __func__, _IOC_NR(cmd)); + + switch (cmd) { + case LIGHTSENSOR_IOCTL_ENABLE: + if (get_user(val, (unsigned long __user *)arg)) { + rc = -EFAULT; + break; + } + rc = val ? lightsensor_enable() : lightsensor_disable(); + break; + case LIGHTSENSOR_IOCTL_GET_ENABLED: + val = cdata->auto_backlight_enabled; + pr_debug("%s enabled %d\n", __func__, val); + rc = put_user(val, (unsigned long __user *)arg); + break; + default: + pr_err("%s: invalid cmd %d\n", __func__, _IOC_NR(cmd)); + rc = -EINVAL; + } + + mutex_unlock(&api_lock); + return rc; +} + +static struct file_operations lightsensor_fops = { + .owner = THIS_MODULE, + .open = lightsensor_open, + .release = lightsensor_release, + .unlocked_ioctl = lightsensor_ioctl +}; + +struct miscdevice lightsensor_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "lightsensor", + .fops = &lightsensor_fops +}; + +/* + * G-sensor + */ +static int microp_spi_enable(uint8_t on) +{ + struct i2c_client *client; + int ret; + + client = private_microp_client; + ret = i2c_write_block(client, MICROP_I2C_WCMD_SPI_EN, &on, 1); + if (ret < 0) { + dev_err(&client->dev,"%s: i2c_write_block fail\n", __func__); + return ret; + } + msleep(10); + return ret; +} + +static int gsensor_read_reg(uint8_t reg, uint8_t *data) +{ + struct i2c_client *client; + int ret; + uint8_t tmp[2]; + + client = private_microp_client; + ret = i2c_write_block(client, MICROP_I2C_WCMD_GSENSOR_REG_DATA_REQ, + ®, 1); + if (ret < 0) { + dev_err(&client->dev,"%s: i2c_write_block fail\n", __func__); + return ret; + } + msleep(10); + + ret = i2c_read_block(client, MICROP_I2C_RCMD_GSENSOR_REG_DATA, tmp, 2); + if (ret < 0) { + dev_err(&client->dev,"%s: i2c_read_block fail\n", __func__); + return ret; + } + *data = tmp[1]; + return ret; +} + +static int gsensor_write_reg(uint8_t reg, uint8_t data) +{ + struct i2c_client *client; + int ret; + uint8_t tmp[2]; + + client = private_microp_client; + + tmp[0] = reg; + tmp[1] = data; + ret = i2c_write_block(client, MICROP_I2C_WCMD_GSENSOR_REG, tmp, 2); + if (ret < 0) { + dev_err(&client->dev,"%s: i2c_write_block fail\n", __func__); + return ret; + } + + return ret; +} + +static int gsensor_read_acceleration(short *buf) +{ + struct i2c_client *client; + int ret; + uint8_t tmp[6]; + struct microp_i2c_client_data *cdata; + + client = private_microp_client; + + cdata = i2c_get_clientdata(client); + + tmp[0] = 1; + ret = i2c_write_block(client, MICROP_I2C_WCMD_GSENSOR_DATA_REQ, + tmp, 1); + if (ret < 0) { + dev_err(&client->dev,"%s: i2c_write_block fail\n", __func__); + return ret; + } + + msleep(10); + + if (cdata->version <= 0x615) { + /* + * Note the data is a 10bit signed value from the chip. + */ + ret = i2c_read_block(client, MICROP_I2C_RCMD_GSENSOR_X_DATA, + tmp, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: i2c_read_block fail\n", + __func__); + return ret; + } + buf[0] = (short)(tmp[0] << 8 | tmp[1]); + buf[0] >>= 6; + + ret = i2c_read_block(client, MICROP_I2C_RCMD_GSENSOR_Y_DATA, + tmp, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: i2c_read_block fail\n", + __func__); + return ret; + } + buf[1] = (short)(tmp[0] << 8 | tmp[1]); + buf[1] >>= 6; + + ret = i2c_read_block(client, MICROP_I2C_RCMD_GSENSOR_Z_DATA, + tmp, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: i2c_read_block fail\n", + __func__); + return ret; + } + buf[2] = (short)(tmp[0] << 8 | tmp[1]); + buf[2] >>= 6; + } else { + ret = i2c_read_block(client, MICROP_I2C_RCMD_GSENSOR_DATA, + tmp, 6); + if (ret < 0) { + dev_err(&client->dev, "%s: i2c_read_block fail\n", + __func__); + return ret; + } + buf[0] = (short)(tmp[0] << 8 | tmp[1]); + buf[0] >>= 6; + buf[1] = (short)(tmp[2] << 8 | tmp[3]); + buf[1] >>= 6; + buf[2] = (short)(tmp[4] << 8 | tmp[5]); + buf[2] >>= 6; + } + +#ifdef DEBUG_BMA150 + /* Log this to debugfs */ + gsensor_log_status(ktime_get(), buf[0], buf[1], buf[2]); +#endif + return 1; +} + +static int gsensor_init_hw(void) +{ + uint8_t reg; + int ret; + + pr_debug("%s\n", __func__); + + microp_spi_enable(1); + + ret = gsensor_read_reg(RANGE_BWIDTH_REG, ®); + if (ret < 0 ) + return -EIO; + reg &= 0xe0; + ret = gsensor_write_reg(RANGE_BWIDTH_REG, reg); + if (ret < 0 ) + return -EIO; + + ret = gsensor_read_reg(SMB150_CONF2_REG, ®); + if (ret < 0 ) + return -EIO; + reg |= (1 << 3); + ret = gsensor_write_reg(SMB150_CONF2_REG, reg); + + return ret; +} + +static int bma150_set_mode(char mode) +{ + uint8_t reg; + int ret; + + pr_debug("%s mode = %d\n", __func__, mode); + if (mode == BMA_MODE_NORMAL) + microp_spi_enable(1); + + + ret = gsensor_read_reg(SMB150_CTRL_REG, ®); + if (ret < 0 ) + return -EIO; + reg = (reg & 0xfe) | mode; + ret = gsensor_write_reg(SMB150_CTRL_REG, reg); + + if (mode == BMA_MODE_SLEEP) + microp_spi_enable(0); + + return ret; +} +static int gsensor_read(uint8_t *data) +{ + int ret; + uint8_t reg = data[0]; + + ret = gsensor_read_reg(reg, &data[1]); + pr_debug("%s reg = %x data = %x\n", __func__, reg, data[1]); + return ret; +} + +static int gsensor_write(uint8_t *data) +{ + int ret; + uint8_t reg = data[0]; + + pr_debug("%s reg = %x data = %x\n", __func__, reg, data[1]); + ret = gsensor_write_reg(reg, data[1]); + return ret; +} + +static int bma150_open(struct inode *inode, struct file *file) +{ + pr_debug("%s\n", __func__); + return nonseekable_open(inode, file); +} + +static int bma150_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int bma150_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + char rwbuf[8]; + int ret = -1; + short buf[8], temp; + + switch (cmd) { + case BMA_IOCTL_READ: + case BMA_IOCTL_WRITE: + case BMA_IOCTL_SET_MODE: + if (copy_from_user(&rwbuf, argp, sizeof(rwbuf))) + return -EFAULT; + break; + case BMA_IOCTL_READ_ACCELERATION: + if (copy_from_user(&buf, argp, sizeof(buf))) + return -EFAULT; + break; + default: + break; + } + + switch (cmd) { + case BMA_IOCTL_INIT: + ret = gsensor_init_hw(); + if (ret < 0) + return ret; + break; + + case BMA_IOCTL_READ: + if (rwbuf[0] < 1) + return -EINVAL; + ret = gsensor_read(rwbuf); + if (ret < 0) + return ret; + break; + case BMA_IOCTL_WRITE: + if (rwbuf[0] < 2) + return -EINVAL; + ret = gsensor_write(rwbuf); + if (ret < 0) + return ret; + break; + case BMA_IOCTL_READ_ACCELERATION: + ret = gsensor_read_acceleration(&buf[0]); + if (ret < 0) + return ret; + break; + case BMA_IOCTL_SET_MODE: + bma150_set_mode(rwbuf[0]); + break; + case BMA_IOCTL_GET_INT: + temp = 0; + break; + default: + return -ENOTTY; + } + + switch (cmd) { + case BMA_IOCTL_READ: + if (copy_to_user(argp, &rwbuf, sizeof(rwbuf))) + return -EFAULT; + break; + case BMA_IOCTL_READ_ACCELERATION: + if (copy_to_user(argp, &buf, sizeof(buf))) + return -EFAULT; + break; + case BMA_IOCTL_GET_INT: + if (copy_to_user(argp, &temp, sizeof(temp))) + return -EFAULT; + break; + default: + break; + } + + return 0; +} + +static struct file_operations bma_fops = { + .owner = THIS_MODULE, + .open = bma150_open, + .release = bma150_release, + .ioctl = bma150_ioctl, +}; + +static struct miscdevice spi_bma_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = BMA150_G_SENSOR_NAME, + .fops = &bma_fops, +}; + +/* + * Interrupt + */ +static irqreturn_t microp_i2c_intr_irq_handler(int irq, void *dev_id) +{ + struct i2c_client *client; + struct microp_i2c_client_data *cdata; + + client = to_i2c_client(dev_id); + cdata = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "intr_irq_handler\n"); + + disable_irq_nosync(client->irq); + schedule_work(&cdata->work.work); + return IRQ_HANDLED; +} + +static void microp_i2c_intr_work_func(struct work_struct *work) +{ + struct microp_i2c_work *up_work; + struct i2c_client *client; + struct microp_i2c_client_data *cdata; + uint8_t data[3], adc_level; + uint16_t intr_status = 0, adc_value, gpi_status = 0; + int keycode = 0, ret = 0; + + up_work = container_of(work, struct microp_i2c_work, work); + client = up_work->client; + cdata = i2c_get_clientdata(client); + + ret = i2c_read_block(client, MICROP_I2C_RCMD_GPI_INT_STATUS, data, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: read interrupt status fail\n", + __func__); + } + + intr_status = data[0]<<8 | data[1]; + ret = i2c_write_block(client, MICROP_I2C_WCMD_GPI_INT_STATUS_CLR, + data, 2); + if (ret < 0) { + dev_err(&client->dev, "%s: clear interrupt status fail\n", + __func__); + } + pr_debug("intr_status=0x%02x\n", intr_status); + + if ((intr_status & IRQ_LSENSOR) || cdata->force_light_sensor_read) { + ret = microp_lightsensor_read(&adc_value, &adc_level); + if (cdata->force_light_sensor_read) { + /* report an invalid value first to ensure we trigger an event + * when adc_level is zero. + */ + input_report_abs(cdata->ls_input_dev, ABS_MISC, -1); + input_sync(cdata->ls_input_dev); + cdata->force_light_sensor_read = 0; + } + input_report_abs(cdata->ls_input_dev, ABS_MISC, (int)adc_level); + input_sync(cdata->ls_input_dev); + } + + if (intr_status & IRQ_SDCARD) { + microp_read_gpi_status(client, &gpi_status); + mahimahi_microp_sdslot_update_status(gpi_status); + } + + if (intr_status & IRQ_HEADSETIN) { + cdata->is_hpin_pin_stable = 0; + wake_lock_timeout(µp_i2c_wakelock, 3*HZ); + if (!cdata->headset_is_in) + schedule_delayed_work(&cdata->hpin_debounce_work, + msecs_to_jiffies(500)); + else + schedule_delayed_work(&cdata->hpin_debounce_work, + msecs_to_jiffies(300)); + } + if (intr_status & IRQ_REMOTEKEY) { + if ((get_remote_keycode(&keycode) == 0) && + (cdata->is_hpin_pin_stable)) { + htc_35mm_key_event(keycode, &cdata->is_hpin_pin_stable); + } + } + + enable_irq(client->irq); +} + +static void ls_read_do_work(struct work_struct *work) +{ + struct i2c_client *client = private_microp_client; + struct microp_i2c_client_data *cdata = i2c_get_clientdata(client); + + /* force a light sensor reading */ + disable_irq(client->irq); + cdata->force_light_sensor_read = 1; + schedule_work(&cdata->work.work); +} + +static int microp_function_initialize(struct i2c_client *client) +{ + struct microp_i2c_client_data *cdata; + uint8_t data[20]; + uint16_t stat, interrupts = 0; + int i; + int ret; + struct led_classdev *led_cdev; + + cdata = i2c_get_clientdata(client); + + /* Light Sensor */ + if (als_kadc >> 16 == ALS_CALIBRATED) + cdata->als_kadc = als_kadc & 0xFFFF; + else { + cdata->als_kadc = 0; + pr_info("%s: no ALS calibrated\n", __func__); + } + + if (cdata->als_kadc && golden_adc) { + cdata->als_kadc = + (cdata->als_kadc > 0 && cdata->als_kadc < 0x400) + ? cdata->als_kadc : golden_adc; + cdata->als_gadc = + (golden_adc > 0) + ? golden_adc : cdata->als_kadc; + } else { + cdata->als_kadc = 1; + cdata->als_gadc = 1; + } + pr_info("%s: als_kadc=0x%x, als_gadc=0x%x\n", + __func__, cdata->als_kadc, cdata->als_gadc); + + for (i = 0; i < 10; i++) { + data[i] = (uint8_t)(lsensor_adc_table[i] + * cdata->als_kadc / cdata->als_gadc >> 8); + data[i + 10] = (uint8_t)(lsensor_adc_table[i] + * cdata->als_kadc / cdata->als_gadc); + } + ret = i2c_write_block(client, MICROP_I2C_WCMD_ADC_TABLE, data, 20); + if (ret) + goto exit; + + ret = gpio_request(MAHIMAHI_GPIO_LS_EN_N, "microp_i2c"); + if (ret < 0) { + dev_err(&client->dev, "failed on request gpio ls_on\n"); + goto exit; + } + ret = gpio_direction_output(MAHIMAHI_GPIO_LS_EN_N, 0); + if (ret < 0) { + dev_err(&client->dev, "failed on gpio_direction_output" + "ls_on\n"); + goto err_gpio_ls; + } + cdata->light_sensor_enabled = 1; + + /* Headset */ + for (i = 0; i < 6; i++) { + data[i] = (uint8_t)(remote_key_adc_table[i] >> 8); + data[i + 6] = (uint8_t)(remote_key_adc_table[i]); + } + ret = i2c_write_block(client, + MICROP_I2C_WCMD_REMOTEKEY_TABLE, data, 12); + if (ret) + goto exit; + + INIT_DELAYED_WORK( + &cdata->hpin_debounce_work, hpin_debounce_do_work); + INIT_DELAYED_WORK( + &cdata->ls_read_work, ls_read_do_work); + + /* SD Card */ + interrupts |= IRQ_SDCARD; + + /* set LED initial state */ + for (i = 0; i < BLUE_LED; i++) { + led_cdev = &cdata->leds[i].ldev; + microp_i2c_write_led_mode(client, led_cdev, 0, 0xffff); + } + + /* enable the interrupts */ + ret = microp_interrupt_enable(client, interrupts); + if (ret < 0) { + dev_err(&client->dev, "%s: failed to enable gpi irqs\n", + __func__); + goto err_irq_en; + } + + microp_read_gpi_status(client, &stat); + mahimahi_microp_sdslot_update_status(stat); + + return 0; + +err_irq_en: +err_gpio_ls: + gpio_free(MAHIMAHI_GPIO_LS_EN_N); +exit: + return ret; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +void microp_early_suspend(struct early_suspend *h) +{ + struct microp_i2c_client_data *cdata; + struct i2c_client *client = private_microp_client; + int ret; + + if (!client) { + pr_err("%s: dataset: client is empty\n", __func__); + return; + } + cdata = i2c_get_clientdata(client); + + cdata->microp_is_suspend = 1; + + disable_irq(client->irq); + ret = cancel_work_sync(&cdata->work.work); + if (ret != 0) { + enable_irq(client->irq); + } + + if (cdata->auto_backlight_enabled) + microp_i2c_auto_backlight_mode(client, 0); + if (cdata->light_sensor_enabled == 1) { + gpio_set_value(MAHIMAHI_GPIO_LS_EN_N, 1); + cdata->light_sensor_enabled = 0; + } +} + +void microp_early_resume(struct early_suspend *h) +{ + struct i2c_client *client = private_microp_client; + struct microp_i2c_client_data *cdata; + + if (!client) { + pr_err("%s: dataset: client is empty\n", __func__); + return; + } + cdata = i2c_get_clientdata(client); + + gpio_set_value(MAHIMAHI_GPIO_LS_EN_N, 0); + cdata->light_sensor_enabled = 1; + + if (cdata->auto_backlight_enabled) + microp_i2c_auto_backlight_mode(client, 1); + + cdata->microp_is_suspend = 0; + enable_irq(client->irq); +} +#endif + +static int microp_i2c_suspend(struct i2c_client *client, + pm_message_t mesg) +{ + return 0; +} + +static int microp_i2c_resume(struct i2c_client *client) +{ + return 0; +} + +static struct { + const char *name; + void (*led_set_work)(struct work_struct *); + struct device_attribute **attrs; + int attr_cnt; +} microp_leds[] = { + [GREEN_LED] = { + .name = "green", + .led_set_work = microp_led_brightness_set_work, + .attrs = green_amber_attrs, + .attr_cnt = ARRAY_SIZE(green_amber_attrs) + }, + [AMBER_LED] = { + .name = "amber", + .led_set_work = microp_led_brightness_set_work, + .attrs = green_amber_attrs, + .attr_cnt = ARRAY_SIZE(green_amber_attrs) + }, + [RED_LED] = { + .name = "red", + .led_set_work = microp_led_brightness_set_work, + .attrs = green_amber_attrs, + .attr_cnt = ARRAY_SIZE(green_amber_attrs) + }, + [BLUE_LED] = { + .name = "blue", + .led_set_work = microp_led_brightness_set_work, + .attrs = green_amber_attrs, + .attr_cnt = ARRAY_SIZE(green_amber_attrs) + }, + [JOGBALL_LED] = { + .name = "jogball-backlight", + .led_set_work = microp_led_jogball_brightness_set_work, + .attrs = jogball_attrs, + .attr_cnt = ARRAY_SIZE(jogball_attrs) + }, + [BUTTONS_LED] = { + .name = "button-backlight", + .led_set_work = microp_led_buttons_brightness_set_work + }, +}; + +static int microp_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct microp_i2c_client_data *cdata; + uint8_t data[6]; + int ret; + int i; + int j; + + private_microp_client = client; + ret = i2c_read_block(client, MICROP_I2C_RCMD_VERSION, data, 2); + if (ret || !(data[0] && data[1])) { + ret = -ENODEV; + dev_err(&client->dev, "failed on get microp version\n"); + goto err_exit; + } + dev_info(&client->dev, "microp version [%02X][%02X]\n", + data[0], data[1]); + + ret = gpio_request(MAHIMAHI_GPIO_UP_RESET_N, "microp_i2c_wm"); + if (ret < 0) { + dev_err(&client->dev, "failed on request gpio reset\n"); + goto err_exit; + } + ret = gpio_direction_output(MAHIMAHI_GPIO_UP_RESET_N, 1); + if (ret < 0) { + dev_err(&client->dev, + "failed on gpio_direction_output reset\n"); + goto err_gpio_reset; + } + + cdata = kzalloc(sizeof(struct microp_i2c_client_data), GFP_KERNEL); + if (!cdata) { + ret = -ENOMEM; + dev_err(&client->dev, "failed on allocat cdata\n"); + goto err_cdata; + } + + i2c_set_clientdata(client, cdata); + cdata->version = data[0] << 8 | data[1]; + cdata->microp_is_suspend = 0; + cdata->auto_backlight_enabled = 0; + cdata->light_sensor_enabled = 0; + + wake_lock_init(µp_i2c_wakelock, WAKE_LOCK_SUSPEND, + "microp_i2c_present"); + + /* Light Sensor */ + ret = device_create_file(&client->dev, &dev_attr_ls_adc); + ret = device_create_file(&client->dev, &dev_attr_ls_auto); + cdata->ls_input_dev = input_allocate_device(); + if (!cdata->ls_input_dev) { + pr_err("%s: could not allocate input device\n", __func__); + ret = -ENOMEM; + goto err_request_input_dev; + } + cdata->ls_input_dev->name = "lightsensor-level"; + set_bit(EV_ABS, cdata->ls_input_dev->evbit); + input_set_abs_params(cdata->ls_input_dev, ABS_MISC, 0, 9, 0, 0); + + ret = input_register_device(cdata->ls_input_dev); + if (ret < 0) { + dev_err(&client->dev, "%s: can not register input device\n", + __func__); + goto err_register_input_dev; + } + + ret = misc_register(&lightsensor_misc); + if (ret < 0) { + dev_err(&client->dev, "%s: can not register misc device\n", + __func__); + goto err_register_misc_register; + } + + /* LEDs */ + ret = 0; + for (i = 0; i < ARRAY_SIZE(microp_leds) && !ret; ++i) { + struct microp_led_data *ldata = &cdata->leds[i]; + + ldata->type = i; + ldata->ldev.name = microp_leds[i].name; + ldata->ldev.brightness_set = microp_brightness_set; + mutex_init(&ldata->led_data_mutex); + INIT_WORK(&ldata->brightness_work, microp_leds[i].led_set_work); + spin_lock_init(&ldata->brightness_lock); + ret = led_classdev_register(&client->dev, &ldata->ldev); + if (ret) { + ldata->ldev.name = NULL; + break; + } + + for (j = 0; j < microp_leds[i].attr_cnt && !ret; ++j) + ret = device_create_file(ldata->ldev.dev, + microp_leds[i].attrs[j]); + } + if (ret) { + dev_err(&client->dev, "failed to add leds\n"); + goto err_add_leds; + } + + /* Headset */ + cdata->headset_is_in = 0; + cdata->is_hpin_pin_stable = 1; + platform_device_register(&mahimahi_h35mm); + + ret = device_create_file(&client->dev, &dev_attr_key_adc); + + /* G-sensor */ + ret = misc_register(&spi_bma_device); + if (ret < 0) { + pr_err("%s: init bma150 misc_register fail\n", + __func__); + goto err_register_bma150; + } +#ifdef DEBUG_BMA150 + debugfs_create_file("gsensor_log", 0444, NULL, NULL, &gsensor_log_fops); +#endif + /* Setup IRQ handler */ + INIT_WORK(&cdata->work.work, microp_i2c_intr_work_func); + cdata->work.client = client; + + ret = request_irq(client->irq, + microp_i2c_intr_irq_handler, + IRQF_TRIGGER_LOW, + "microp_interrupt", + &client->dev); + if (ret) { + dev_err(&client->dev, "request_irq failed\n"); + goto err_intr; + } + ret = set_irq_wake(client->irq, 1); + if (ret) { + dev_err(&client->dev, "set_irq_wake failed\n"); + goto err_intr; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + if (cdata->enable_early_suspend) { + cdata->early_suspend.level = + EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + cdata->early_suspend.suspend = microp_early_suspend; + cdata->early_suspend.resume = microp_early_resume; + register_early_suspend(&cdata->early_suspend); + } +#endif + + ret = microp_function_initialize(client); + if (ret) { + dev_err(&client->dev, "failed on microp function initialize\n"); + goto err_fun_init; + } + + return 0; + +err_fun_init: +err_intr: + misc_deregister(&spi_bma_device); + +err_register_bma150: + platform_device_unregister(&mahimahi_h35mm); + device_remove_file(&client->dev, &dev_attr_key_adc); + +err_add_leds: + for (i = 0; i < ARRAY_SIZE(microp_leds); ++i) { + if (!cdata->leds[i].ldev.name) + continue; + led_classdev_unregister(&cdata->leds[i].ldev); + for (j = 0; j < microp_leds[i].attr_cnt; ++j) + device_remove_file(cdata->leds[i].ldev.dev, + microp_leds[i].attrs[j]); + } + + misc_deregister(&lightsensor_misc); + +err_register_misc_register: + input_unregister_device(cdata->ls_input_dev); + +err_register_input_dev: + input_free_device(cdata->ls_input_dev); + +err_request_input_dev: + wake_lock_destroy(µp_i2c_wakelock); + device_remove_file(&client->dev, &dev_attr_ls_adc); + device_remove_file(&client->dev, &dev_attr_ls_auto); + kfree(cdata); + i2c_set_clientdata(client, NULL); + +err_cdata: +err_gpio_reset: + gpio_free(MAHIMAHI_GPIO_UP_RESET_N); +err_exit: + return ret; +} + +static int __devexit microp_i2c_remove(struct i2c_client *client) +{ + struct microp_i2c_client_data *cdata; + int i; + int j; + + cdata = i2c_get_clientdata(client); + + for (i = 0; i < ARRAY_SIZE(microp_leds); ++i) { + struct microp_led_data *ldata = &cdata->leds[i]; + cancel_work_sync(&ldata->brightness_work); + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + if (cdata->enable_early_suspend) { + unregister_early_suspend(&cdata->early_suspend); + } +#endif + + for (i = 0; i < ARRAY_SIZE(microp_leds); ++i) { + if (!cdata->leds[i].ldev.name) + continue; + led_classdev_unregister(&cdata->leds[i].ldev); + for (j = 0; j < microp_leds[i].attr_cnt; ++j) + device_remove_file(cdata->leds[i].ldev.dev, + microp_leds[i].attrs[j]); + } + + free_irq(client->irq, &client->dev); + + gpio_free(MAHIMAHI_GPIO_UP_RESET_N); + + misc_deregister(&lightsensor_misc); + input_unregister_device(cdata->ls_input_dev); + input_free_device(cdata->ls_input_dev); + device_remove_file(&client->dev, &dev_attr_ls_adc); + device_remove_file(&client->dev, &dev_attr_key_adc); + device_remove_file(&client->dev, &dev_attr_ls_auto); + + platform_device_unregister(&mahimahi_h35mm); + + /* G-sensor */ + misc_deregister(&spi_bma_device); + + kfree(cdata); + + return 0; +} + +#define ATAG_ALS 0x5441001b +static int __init parse_tag_als_kadc(const struct tag *tags) +{ + int found = 0; + struct tag *t = (struct tag *)tags; + + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_ALS) { + found = 1; + break; + } + } + + if (found) + als_kadc = t->u.revision.rev; + pr_debug("%s: als_kadc = 0x%x\n", __func__, als_kadc); + return 0; +} +__tagtable(ATAG_ALS, parse_tag_als_kadc); + +static const struct i2c_device_id microp_i2c_id[] = { + { MICROP_I2C_NAME, 0 }, + { } +}; + +static struct i2c_driver microp_i2c_driver = { + .driver = { + .name = MICROP_I2C_NAME, + }, + .id_table = microp_i2c_id, + .probe = microp_i2c_probe, + .suspend = microp_i2c_suspend, + .resume = microp_i2c_resume, + .remove = __devexit_p(microp_i2c_remove), +}; + + +static int __init microp_i2c_init(void) +{ + return i2c_add_driver(µp_i2c_driver); +} + +static void __exit microp_i2c_exit(void) +{ + i2c_del_driver(µp_i2c_driver); +} + +module_init(microp_i2c_init); +module_exit(microp_i2c_exit); + +MODULE_AUTHOR("Eric Olsen "); +MODULE_DESCRIPTION("MicroP I2C driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/board-mahimahi-mmc.c b/arch/arm/mach-msm/board-mahimahi-mmc.c new file mode 100644 index 00000000000..78ed97fc49d --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-mmc.c @@ -0,0 +1,454 @@ +/* linux/arch/arm/mach-msm/board-mahimahi-mmc.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "board-mahimahi.h" +#include "devices.h" +#include "proc_comm.h" + +#undef MAHIMAHI_DEBUG_MMC + +static bool opt_disable_sdcard; +static int __init mahimahi_disablesdcard_setup(char *str) +{ + opt_disable_sdcard = (bool)simple_strtol(str, NULL, 0); + return 1; +} + +__setup("board_mahimahi.disable_sdcard=", mahimahi_disablesdcard_setup); + +static void config_gpio_table(uint32_t *table, int len) +{ + int n; + unsigned id; + for(n = 0; n < len; n++) { + id = table[n]; + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0); + } +} + +static uint32_t sdcard_on_gpio_table[] = { + PCOM_GPIO_CFG(62, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_8MA), /* CLK */ + PCOM_GPIO_CFG(63, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), /* CMD */ + PCOM_GPIO_CFG(64, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT3 */ + PCOM_GPIO_CFG(65, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT2 */ + PCOM_GPIO_CFG(66, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT1 */ + PCOM_GPIO_CFG(67, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT0 */ +}; + +static uint32_t sdcard_off_gpio_table[] = { + PCOM_GPIO_CFG(62, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* CLK */ + PCOM_GPIO_CFG(63, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* CMD */ + PCOM_GPIO_CFG(64, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT3 */ + PCOM_GPIO_CFG(65, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT2 */ + PCOM_GPIO_CFG(66, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT1 */ + PCOM_GPIO_CFG(67, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT0 */ +}; + +static struct vreg *sdslot_vreg; +static uint32_t sdslot_vdd = 0xffffffff; +static uint32_t sdslot_vreg_enabled; + +static struct { + int mask; + int level; +} mmc_vdd_table[] = { + { MMC_VDD_165_195, 1800 }, + { MMC_VDD_20_21, 2050 }, + { MMC_VDD_21_22, 2150 }, + { MMC_VDD_22_23, 2250 }, + { MMC_VDD_23_24, 2350 }, + { MMC_VDD_24_25, 2450 }, + { MMC_VDD_25_26, 2550 }, + { MMC_VDD_26_27, 2650 }, + { MMC_VDD_27_28, 2750 }, + { MMC_VDD_28_29, 2850 }, + { MMC_VDD_29_30, 2950 }, +}; + +static uint32_t mahimahi_sdslot_switchvdd(struct device *dev, unsigned int vdd) +{ + int i; + int ret; + + if (vdd == sdslot_vdd) + return 0; + + sdslot_vdd = vdd; + + if (vdd == 0) { + config_gpio_table(sdcard_off_gpio_table, + ARRAY_SIZE(sdcard_off_gpio_table)); + vreg_disable(sdslot_vreg); + sdslot_vreg_enabled = 0; + return 0; + } + + if (!sdslot_vreg_enabled) { + ret = vreg_enable(sdslot_vreg); + if (ret) + pr_err("%s: Error enabling vreg (%d)\n", __func__, ret); + config_gpio_table(sdcard_on_gpio_table, + ARRAY_SIZE(sdcard_on_gpio_table)); + sdslot_vreg_enabled = 1; + } + + for (i = 0; i < ARRAY_SIZE(mmc_vdd_table); i++) { + if (mmc_vdd_table[i].mask != (1 << vdd)) + continue; + ret = vreg_set_level(sdslot_vreg, mmc_vdd_table[i].level); + if (ret) + pr_err("%s: Error setting level (%d)\n", __func__, ret); + return 0; + } + + pr_err("%s: Invalid VDD (%d) specified\n", __func__, vdd); + return 0; +} + +static uint32_t mahimahi_cdma_sdslot_switchvdd(struct device *dev, unsigned int vdd) +{ + if (!vdd == !sdslot_vdd) + return 0; + + /* In CDMA version, the vdd of sdslot is not configurable, and it is + * fixed in 2.85V by hardware design. + */ + + sdslot_vdd = vdd ? MMC_VDD_28_29 : 0; + + if (vdd) { + gpio_set_value(MAHIMAHI_CDMA_SD_2V85_EN, 1); + config_gpio_table(sdcard_on_gpio_table, + ARRAY_SIZE(sdcard_on_gpio_table)); + } else { + config_gpio_table(sdcard_off_gpio_table, + ARRAY_SIZE(sdcard_off_gpio_table)); + gpio_set_value(MAHIMAHI_CDMA_SD_2V85_EN, 0); + } + + sdslot_vreg_enabled = !!vdd; + + return 0; +} + +static unsigned int mahimahi_sdslot_status_rev0(struct device *dev) +{ + return !gpio_get_value(MAHIMAHI_GPIO_SDMC_CD_REV0_N); +} + +#define MAHIMAHI_MMC_VDD (MMC_VDD_165_195 | MMC_VDD_20_21 | \ + MMC_VDD_21_22 | MMC_VDD_22_23 | \ + MMC_VDD_23_24 | MMC_VDD_24_25 | \ + MMC_VDD_25_26 | MMC_VDD_26_27 | \ + MMC_VDD_27_28 | MMC_VDD_28_29 | \ + MMC_VDD_29_30) + +int mahimahi_microp_sdslot_status_register(void (*cb)(int, void *), void *); +unsigned int mahimahi_microp_sdslot_status(struct device *); + +static struct mmc_platform_data mahimahi_sdslot_data = { + .ocr_mask = MAHIMAHI_MMC_VDD, + .status = mahimahi_microp_sdslot_status, + .register_status_notify = mahimahi_microp_sdslot_status_register, + .translate_vdd = mahimahi_sdslot_switchvdd, +}; + +static uint32_t wifi_on_gpio_table[] = { + PCOM_GPIO_CFG(51, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT3 */ + PCOM_GPIO_CFG(52, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT2 */ + PCOM_GPIO_CFG(53, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT1 */ + PCOM_GPIO_CFG(54, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT0 */ + PCOM_GPIO_CFG(55, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), /* CMD */ + PCOM_GPIO_CFG(56, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_8MA), /* CLK */ + PCOM_GPIO_CFG(152, 0, GPIO_INPUT, GPIO_NO_PULL, GPIO_4MA), /* WLAN IRQ */ +}; + +static uint32_t wifi_off_gpio_table[] = { + PCOM_GPIO_CFG(51, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT3 */ + PCOM_GPIO_CFG(52, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT2 */ + PCOM_GPIO_CFG(53, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT1 */ + PCOM_GPIO_CFG(54, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT0 */ + PCOM_GPIO_CFG(55, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* CMD */ + PCOM_GPIO_CFG(56, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* CLK */ + PCOM_GPIO_CFG(152, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* WLAN IRQ */ +}; + +/* 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 mahimahi_wifi_emb_data = { + .cccr = { + .sdio_vsn = 2, + .multi_block = 1, + .low_speed = 0, + .wide_bus = 0, + .high_power = 1, + .high_speed = 1, + }, +}; + +static int mahimahi_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 mahimahi_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 mahimahi_wifi_status(struct device *dev) +{ + return mahimahi_wifi_cd; +} + +static struct mmc_platform_data mahimahi_wifi_data = { + .ocr_mask = MMC_VDD_28_29, + .built_in = 1, + .status = mahimahi_wifi_status, + .register_status_notify = mahimahi_wifi_status_register, + .embedded_sdio = &mahimahi_wifi_emb_data, +}; + +int mahimahi_wifi_set_carddetect(int val) +{ + pr_info("%s: %d\n", __func__, val); + mahimahi_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 mahimahi_wifi_power_state; + +int mahimahi_wifi_power(int on) +{ + printk("%s: %d\n", __func__, on); + + if (on) { + config_gpio_table(wifi_on_gpio_table, + ARRAY_SIZE(wifi_on_gpio_table)); + mdelay(50); + } else { + config_gpio_table(wifi_off_gpio_table, + ARRAY_SIZE(wifi_off_gpio_table)); + } + + mdelay(100); + gpio_set_value(MAHIMAHI_GPIO_WIFI_SHUTDOWN_N, on); /* WIFI_SHUTDOWN */ + mdelay(200); + + mahimahi_wifi_power_state = on; + return 0; +} + +static int mahimahi_wifi_reset_state; + +int mahimahi_wifi_reset(int on) +{ + printk("%s: do nothing\n", __func__); + mahimahi_wifi_reset_state = on; + return 0; +} + +int msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat, + unsigned int stat_irq, unsigned long stat_irq_flags); + +int __init mahimahi_init_mmc(unsigned int sys_rev, unsigned debug_uart) +{ + uint32_t id; + + printk("%s()+\n", __func__); + + /* initial WIFI_SHUTDOWN# */ + id = PCOM_GPIO_CFG(MAHIMAHI_GPIO_WIFI_SHUTDOWN_N, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA), + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0); + + msm_add_sdcc(1, &mahimahi_wifi_data, 0, 0); + + if (debug_uart) { + pr_info("%s: sdcard disabled due to debug uart\n", __func__); + goto done; + } + if (opt_disable_sdcard) { + pr_info("%s: sdcard disabled on cmdline\n", __func__); + goto done; + } + + sdslot_vreg_enabled = 0; + + if (is_cdma_version(sys_rev)) { + /* In the CDMA version, sdslot is supplied by a gpio. */ + int rc = gpio_request(MAHIMAHI_CDMA_SD_2V85_EN, "sdslot_en"); + if (rc < 0) { + pr_err("%s: gpio_request(%d) failed: %d\n", __func__, + MAHIMAHI_CDMA_SD_2V85_EN, rc); + return rc; + } + mahimahi_sdslot_data.translate_vdd = mahimahi_cdma_sdslot_switchvdd; + } else { + /* in UMTS version, sdslot is supplied by pmic */ + sdslot_vreg = vreg_get(0, "gp6"); + if (IS_ERR(sdslot_vreg)) + return PTR_ERR(sdslot_vreg); + } + + if (system_rev > 0) + msm_add_sdcc(2, &mahimahi_sdslot_data, 0, 0); + else { + mahimahi_sdslot_data.status = mahimahi_sdslot_status_rev0; + mahimahi_sdslot_data.register_status_notify = NULL; + set_irq_wake(MSM_GPIO_TO_INT(MAHIMAHI_GPIO_SDMC_CD_REV0_N), 1); + msm_add_sdcc(2, &mahimahi_sdslot_data, + MSM_GPIO_TO_INT(MAHIMAHI_GPIO_SDMC_CD_REV0_N), + IORESOURCE_IRQ_LOWEDGE | IORESOURCE_IRQ_HIGHEDGE); + } + +done: + printk("%s()-\n", __func__); + return 0; +} + +#if defined(MAHIMAHI_DEBUG_MMC) && defined(CONFIG_DEBUG_FS) + +static int mahimahimmc_dbg_wifi_reset_set(void *data, u64 val) +{ + mahimahi_wifi_reset((int) val); + return 0; +} + +static int mahimahimmc_dbg_wifi_reset_get(void *data, u64 *val) +{ + *val = mahimahi_wifi_reset_state; + return 0; +} + +static int mahimahimmc_dbg_wifi_cd_set(void *data, u64 val) +{ + mahimahi_wifi_set_carddetect((int) val); + return 0; +} + +static int mahimahimmc_dbg_wifi_cd_get(void *data, u64 *val) +{ + *val = mahimahi_wifi_cd; + return 0; +} + +static int mahimahimmc_dbg_wifi_pwr_set(void *data, u64 val) +{ + mahimahi_wifi_power((int) val); + return 0; +} + +static int mahimahimmc_dbg_wifi_pwr_get(void *data, u64 *val) +{ + *val = mahimahi_wifi_power_state; + return 0; +} + +static int mahimahimmc_dbg_sd_pwr_set(void *data, u64 val) +{ + mahimahi_sdslot_switchvdd(NULL, (unsigned int) val); + return 0; +} + +static int mahimahimmc_dbg_sd_pwr_get(void *data, u64 *val) +{ + *val = sdslot_vdd; + return 0; +} + +static int mahimahimmc_dbg_sd_cd_set(void *data, u64 val) +{ + return -ENOSYS; +} + +static int mahimahimmc_dbg_sd_cd_get(void *data, u64 *val) +{ + *val = mahimahi_sdslot_data.status(NULL); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(mahimahimmc_dbg_wifi_reset_fops, + mahimahimmc_dbg_wifi_reset_get, + mahimahimmc_dbg_wifi_reset_set, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(mahimahimmc_dbg_wifi_cd_fops, + mahimahimmc_dbg_wifi_cd_get, + mahimahimmc_dbg_wifi_cd_set, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(mahimahimmc_dbg_wifi_pwr_fops, + mahimahimmc_dbg_wifi_pwr_get, + mahimahimmc_dbg_wifi_pwr_set, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(mahimahimmc_dbg_sd_pwr_fops, + mahimahimmc_dbg_sd_pwr_get, + mahimahimmc_dbg_sd_pwr_set, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(mahimahimmc_dbg_sd_cd_fops, + mahimahimmc_dbg_sd_cd_get, + mahimahimmc_dbg_sd_cd_set, "%llu\n"); + +static int __init mahimahimmc_dbg_init(void) +{ + struct dentry *dent; + + if (!machine_is_mahimahi()) + return 0; + + dent = debugfs_create_dir("mahimahi_mmc_dbg", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debugfs_create_file("wifi_reset", 0644, dent, NULL, + &mahimahimmc_dbg_wifi_reset_fops); + debugfs_create_file("wifi_cd", 0644, dent, NULL, + &mahimahimmc_dbg_wifi_cd_fops); + debugfs_create_file("wifi_pwr", 0644, dent, NULL, + &mahimahimmc_dbg_wifi_pwr_fops); + debugfs_create_file("sd_pwr", 0644, dent, NULL, + &mahimahimmc_dbg_sd_pwr_fops); + debugfs_create_file("sd_cd", 0644, dent, NULL, + &mahimahimmc_dbg_sd_cd_fops); + return 0; +} + +device_initcall(mahimahimmc_dbg_init); +#endif diff --git a/arch/arm/mach-msm/board-mahimahi-panel.c b/arch/arm/mach-msm/board-mahimahi-panel.c new file mode 100644 index 00000000000..64b66b7a37d --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-panel.c @@ -0,0 +1,998 @@ +/* linux/arch/arm/mach-msm/board-mahimahi-panel.c + * + * Copyright (c) 2009 Google Inc. + * Author: Dima Zavin + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include "proc_comm.h" + +#include "board-mahimahi.h" +#include "devices.h" + + +#define SPI_CONFIG (0x00000000) +#define SPI_IO_CONTROL (0x00000004) +#define SPI_OPERATIONAL (0x00000030) +#define SPI_ERROR_FLAGS_EN (0x00000038) +#define SPI_ERROR_FLAGS (0x00000038) +#define SPI_OUTPUT_FIFO (0x00000100) + +static void __iomem *spi_base; +static struct clk *spi_clk ; +static struct vreg *vreg_lcm_rftx_2v6; +static struct vreg *vreg_lcm_aux_2v6; + +static int qspi_send(uint32_t id, uint8_t data) +{ + uint32_t err; + + /* bit-5: OUTPUT_FIFO_NOT_EMPTY */ + while (readl(spi_base + SPI_OPERATIONAL) & (1<<5)) { + if ((err = readl(spi_base + SPI_ERROR_FLAGS))) { + pr_err("%s: ERROR: SPI_ERROR_FLAGS=0x%08x\n", __func__, + err); + return -EIO; + } + } + writel((0x7000 | (id << 9) | data) << 16, spi_base + SPI_OUTPUT_FIFO); + udelay(100); + + return 0; +} + +static int qspi_send_9bit(uint32_t id, uint8_t data) +{ + uint32_t err; + + while (readl(spi_base + SPI_OPERATIONAL) & (1<<5)) { + err = readl(spi_base + SPI_ERROR_FLAGS); + if (err) { + pr_err("%s: ERROR: SPI_ERROR_FLAGS=0x%08x\n", __func__, + err); + return -EIO; + } + } + writel(((id << 8) | data) << 23, spi_base + SPI_OUTPUT_FIFO); + udelay(100); + + return 0; +} + +static int lcm_writeb(uint8_t reg, uint8_t val) +{ + qspi_send(0x0, reg); + qspi_send(0x1, val); + return 0; +} + +static int lcm_writew(uint8_t reg, uint16_t val) +{ + qspi_send(0x0, reg); + qspi_send(0x1, val >> 8); + qspi_send(0x1, val & 0xff); + return 0; +} + +static struct resource resources_msm_fb[] = { + { + .start = MSM_FB_BASE, + .end = MSM_FB_BASE + MSM_FB_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct lcm_tbl { + uint8_t reg; + uint8_t val; +}; + +static struct lcm_tbl samsung_oled_rgb565_init_table[] = { + { 0x31, 0x08 }, + { 0x32, 0x14 }, + { 0x30, 0x2 }, + { 0x27, 0x1 }, + { 0x12, 0x8 }, + { 0x13, 0x8 }, + { 0x15, 0x0 }, + { 0x16, 0x02 }, + { 0x39, 0x24 }, + { 0x17, 0x22 }, + { 0x18, 0x33 }, + { 0x19, 0x3 }, + { 0x1A, 0x1 }, + { 0x22, 0xA4 }, + { 0x23, 0x0 }, + { 0x26, 0xA0 }, +}; + +static struct lcm_tbl samsung_oled_rgb666_init_table[] = { + { 0x31, 0x08 }, + { 0x32, 0x14 }, + { 0x30, 0x2 }, + { 0x27, 0x1 }, + { 0x12, 0x8 }, + { 0x13, 0x8 }, + { 0x15, 0x0 }, + { 0x16, 0x01 }, + { 0x39, 0x24 }, + { 0x17, 0x22 }, + { 0x18, 0x33 }, + { 0x19, 0x3 }, + { 0x1A, 0x1 }, + { 0x22, 0xA4 }, + { 0x23, 0x0 }, + { 0x26, 0xA0 }, +}; + +static struct lcm_tbl *init_tablep = samsung_oled_rgb565_init_table; +static size_t init_table_sz = ARRAY_SIZE(samsung_oled_rgb565_init_table); + +#define OLED_GAMMA_TABLE_SIZE (7 * 3) +static struct lcm_tbl samsung_oled_gamma_table[][OLED_GAMMA_TABLE_SIZE] = { + /* level 10 */ + { + /* Gamma-R */ + { 0x40, 0x0 }, + { 0x41, 0x3f }, + { 0x42, 0x3f }, + { 0x43, 0x35 }, + { 0x44, 0x30 }, + { 0x45, 0x2c }, + { 0x46, 0x13 }, + /* Gamma -G */ + { 0x50, 0x0 }, + { 0x51, 0x0 }, + { 0x52, 0x0 }, + { 0x53, 0x0 }, + { 0x54, 0x27 }, + { 0x55, 0x2b }, + { 0x56, 0x12 }, + /* Gamma -B */ + { 0x60, 0x0 }, + { 0x61, 0x3f }, + { 0x62, 0x3f }, + { 0x63, 0x34 }, + { 0x64, 0x2f }, + { 0x65, 0x2b }, + { 0x66, 0x1b }, + }, + + /* level 40 */ + { + /* Gamma -R */ + { 0x40, 0x0 }, + { 0x41, 0x3f }, + { 0x42, 0x3e }, + { 0x43, 0x2e }, + { 0x44, 0x2d }, + { 0x45, 0x28 }, + { 0x46, 0x21 }, + /* Gamma -G */ + { 0x50, 0x0 }, + { 0x51, 0x0 }, + { 0x52, 0x0 }, + { 0x53, 0x21 }, + { 0x54, 0x2a }, + { 0x55, 0x28 }, + { 0x56, 0x20 }, + /* Gamma -B */ + { 0x60, 0x0 }, + { 0x61, 0x3f }, + { 0x62, 0x3e }, + { 0x63, 0x2d }, + { 0x64, 0x2b }, + { 0x65, 0x26 }, + { 0x66, 0x2d }, + }, + + /* level 70 */ + { + /* Gamma -R */ + { 0x40, 0x0 }, + { 0x41, 0x3f }, + { 0x42, 0x35 }, + { 0x43, 0x2c }, + { 0x44, 0x2b }, + { 0x45, 0x26 }, + { 0x46, 0x29 }, + /* Gamma -G */ + { 0x50, 0x0 }, + { 0x51, 0x0 }, + { 0x52, 0x0 }, + { 0x53, 0x25 }, + { 0x54, 0x29 }, + { 0x55, 0x26 }, + { 0x56, 0x28 }, + /* Gamma -B */ + { 0x60, 0x0 }, + { 0x61, 0x3f }, + { 0x62, 0x34 }, + { 0x63, 0x2b }, + { 0x64, 0x2a }, + { 0x65, 0x23 }, + { 0x66, 0x37 }, + }, + + /* level 100 */ + { + /* Gamma -R */ + { 0x40, 0x0 }, + { 0x41, 0x3f }, + { 0x42, 0x30 }, + { 0x43, 0x2a }, + { 0x44, 0x2b }, + { 0x45, 0x24 }, + { 0x46, 0x2f }, + /* Gamma -G */ + { 0x50, 0x0 }, + { 0x51, 0x0 }, + { 0x52, 0x0 }, + { 0x53, 0x25 }, + { 0x54, 0x29 }, + { 0x55, 0x24 }, + { 0x56, 0x2e }, + /* Gamma -B */ + { 0x60, 0x0 }, + { 0x61, 0x3f }, + { 0x62, 0x2f }, + { 0x63, 0x29 }, + { 0x64, 0x29 }, + { 0x65, 0x21 }, + { 0x66, 0x3f }, + }, + + /* level 130 */ + { + /* Gamma -R */ + { 0x40, 0x0 }, + { 0x41, 0x3f }, + { 0x42, 0x2e }, + { 0x43, 0x29 }, + { 0x44, 0x2a }, + { 0x45, 0x23 }, + { 0x46, 0x34 }, + /* Gamma -G */ + { 0x50, 0x0 }, + { 0x51, 0x0 }, + { 0x52, 0xa }, + { 0x53, 0x25 }, + { 0x54, 0x28 }, + { 0x55, 0x23 }, + { 0x56, 0x33 }, + /* Gamma -B */ + { 0x60, 0x0 }, + { 0x61, 0x3f }, + { 0x62, 0x2d }, + { 0x63, 0x28 }, + { 0x64, 0x27 }, + { 0x65, 0x20 }, + { 0x66, 0x46 }, + }, + + /* level 160 */ + { + /* Gamma -R */ + { 0x40, 0x0 }, + { 0x41, 0x3f }, + { 0x42, 0x2b }, + { 0x43, 0x29 }, + { 0x44, 0x28 }, + { 0x45, 0x23 }, + { 0x46, 0x38 }, + /* Gamma -G */ + { 0x50, 0x0 }, + { 0x51, 0x0 }, + { 0x52, 0xb }, + { 0x53, 0x25 }, + { 0x54, 0x27 }, + { 0x55, 0x23 }, + { 0x56, 0x37 }, + /* Gamma -B */ + { 0x60, 0x0 }, + { 0x61, 0x3f }, + { 0x62, 0x29 }, + { 0x63, 0x28 }, + { 0x64, 0x25 }, + { 0x65, 0x20 }, + { 0x66, 0x4b }, + }, + + /* level 190 */ + { + /* Gamma -R */ + { 0x40, 0x0 }, + { 0x41, 0x3f }, + { 0x42, 0x29 }, + { 0x43, 0x29 }, + { 0x44, 0x27 }, + { 0x45, 0x22 }, + { 0x46, 0x3c }, + /* Gamma -G */ + { 0x50, 0x0 }, + { 0x51, 0x0 }, + { 0x52, 0x10 }, + { 0x53, 0x26 }, + { 0x54, 0x26 }, + { 0x55, 0x22 }, + { 0x56, 0x3b }, + /* Gamma -B */ + { 0x60, 0x0 }, + { 0x61, 0x3f }, + { 0x62, 0x28 }, + { 0x63, 0x28 }, + { 0x64, 0x24 }, + { 0x65, 0x1f }, + { 0x66, 0x50 }, + }, + + /* level 220 */ + { + /* Gamma -R */ + { 0x40, 0x0 }, + { 0x41, 0x3f }, + { 0x42, 0x28 }, + { 0x43, 0x28 }, + { 0x44, 0x28 }, + { 0x45, 0x20 }, + { 0x46, 0x40 }, + /* Gamma -G */ + { 0x50, 0x0 }, + { 0x51, 0x0 }, + { 0x52, 0x11 }, + { 0x53, 0x25 }, + { 0x54, 0x27 }, + { 0x55, 0x20 }, + { 0x56, 0x3f }, + /* Gamma -B */ + { 0x60, 0x0 }, + { 0x61, 0x3f }, + { 0x62, 0x27 }, + { 0x63, 0x26 }, + { 0x64, 0x26 }, + { 0x65, 0x1c }, + { 0x66, 0x56 }, + }, + + /* level 250 */ + { + /* Gamma -R */ + { 0x40, 0x0 }, + { 0x41, 0x3f }, + { 0x42, 0x2a }, + { 0x43, 0x27 }, + { 0x44, 0x27 }, + { 0x45, 0x1f }, + { 0x46, 0x44 }, + /* Gamma -G */ + { 0x50, 0x0 }, + { 0x51, 0x0 }, + { 0x52, 0x17 }, + { 0x53, 0x24 }, + { 0x54, 0x26 }, + { 0x55, 0x1f }, + { 0x56, 0x43 }, + /* Gamma -B */ + { 0x60, 0x0 }, + { 0x61, 0x3f }, + { 0x62, 0x2a }, + { 0x63, 0x25 }, + { 0x64, 0x24 }, + { 0x65, 0x1b }, + { 0x66, 0x5c }, + }, +}; +#define SAMSUNG_OLED_NUM_LEVELS ARRAY_SIZE(samsung_oled_gamma_table) + +#define SAMSUNG_OLED_MIN_VAL 10 +#define SAMSUNG_OLED_MAX_VAL 250 +#define SAMSUNG_OLED_DEFAULT_VAL (SAMSUNG_OLED_MIN_VAL + \ + (SAMSUNG_OLED_MAX_VAL - \ + SAMSUNG_OLED_MIN_VAL) / 2) + +#define SAMSUNG_OLED_LEVEL_STEP ((SAMSUNG_OLED_MAX_VAL - \ + SAMSUNG_OLED_MIN_VAL) / \ + (SAMSUNG_OLED_NUM_LEVELS - 1)) + + +#define SONY_TFT_DEF_USER_VAL 102 +#define SONY_TFT_MIN_USER_VAL 30 +#define SONY_TFT_MAX_USER_VAL 255 +#define SONY_TFT_DEF_PANEL_VAL 155 +#define SONY_TFT_MIN_PANEL_VAL 26 +#define SONY_TFT_MAX_PANEL_VAL 255 + + +static DEFINE_MUTEX(panel_lock); +static struct work_struct brightness_delayed_work; +static DEFINE_SPINLOCK(brightness_lock); +static uint8_t new_val = SAMSUNG_OLED_DEFAULT_VAL; +static uint8_t last_val = SAMSUNG_OLED_DEFAULT_VAL; +static uint8_t table_sel_vals[] = { 0x43, 0x34 }; +static int table_sel_idx = 0; +static uint8_t tft_panel_on; + +static void gamma_table_bank_select(void) +{ + lcm_writeb(0x39, table_sel_vals[table_sel_idx]); + table_sel_idx ^= 1; +} + +static void samsung_oled_set_gamma_val(int val) +{ + int i; + int level; + int frac; + + val = clamp(val, SAMSUNG_OLED_MIN_VAL, SAMSUNG_OLED_MAX_VAL); + val = (val / 2) * 2; + + level = (val - SAMSUNG_OLED_MIN_VAL) / SAMSUNG_OLED_LEVEL_STEP; + frac = (val - SAMSUNG_OLED_MIN_VAL) % SAMSUNG_OLED_LEVEL_STEP; + + clk_enable(spi_clk); + + for (i = 0; i < OLED_GAMMA_TABLE_SIZE; ++i) { + unsigned int v1; + unsigned int v2 = 0; + u8 v; + if (frac == 0) { + v = samsung_oled_gamma_table[level][i].val; + } else { + + v1 = samsung_oled_gamma_table[level][i].val; + v2 = samsung_oled_gamma_table[level+1][i].val; + v = (v1 * (SAMSUNG_OLED_LEVEL_STEP - frac) + + v2 * frac) / SAMSUNG_OLED_LEVEL_STEP; + } + lcm_writeb(samsung_oled_gamma_table[level][i].reg, v); + } + + gamma_table_bank_select(); + clk_disable(spi_clk); + last_val = val; +} + +static int samsung_oled_panel_init(struct msm_lcdc_panel_ops *ops) +{ + pr_info("%s: +()\n", __func__); + mutex_lock(&panel_lock); + + clk_enable(spi_clk); + /* Set the gamma write target to 4, leave the current gamma set at 2 */ + lcm_writeb(0x39, 0x24); + clk_disable(spi_clk); + + mutex_unlock(&panel_lock); + pr_info("%s: -()\n", __func__); + return 0; +} + +static int samsung_oled_panel_unblank(struct msm_lcdc_panel_ops *ops) +{ + int i; + + pr_info("%s: +()\n", __func__); + + mutex_lock(&panel_lock); + + gpio_set_value(MAHIMAHI_GPIO_LCD_RST_N, 1); + udelay(50); + gpio_set_value(MAHIMAHI_GPIO_LCD_RST_N, 0); + udelay(20); + gpio_set_value(MAHIMAHI_GPIO_LCD_RST_N, 1); + msleep(20); + + clk_enable(spi_clk); + + for (i = 0; i < init_table_sz; i++) + lcm_writeb(init_tablep[i].reg, init_tablep[i].val); + + lcm_writew(0xef, 0xd0e8); + lcm_writeb(0x1d, 0xa0); + table_sel_idx = 0; + gamma_table_bank_select(); + samsung_oled_set_gamma_val(last_val); + msleep(250); + lcm_writeb(0x14, 0x03); + clk_disable(spi_clk); + + mutex_unlock(&panel_lock); + + pr_info("%s: -()\n", __func__); + return 0; +} + +static int samsung_oled_panel_blank(struct msm_lcdc_panel_ops *ops) +{ + pr_info("%s: +()\n", __func__); + mutex_lock(&panel_lock); + + clk_enable(spi_clk); + lcm_writeb(0x14, 0x0); + mdelay(1); + lcm_writeb(0x1d, 0xa1); + clk_disable(spi_clk); + msleep(200); + + gpio_set_value(MAHIMAHI_GPIO_LCD_RST_N, 0); + + mutex_unlock(&panel_lock); + pr_info("%s: -()\n", __func__); + return 0; +} + +struct lcm_cmd { + int reg; + uint32_t val; + unsigned delay; +}; + +#define LCM_GPIO_CFG(gpio, func, str) \ + PCOM_GPIO_CFG(gpio, func, GPIO_OUTPUT, GPIO_NO_PULL, str) + +static uint32_t sony_tft_display_on_gpio_table[] = { + LCM_GPIO_CFG(MAHIMAHI_LCD_R1, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_R2, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_R3, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_R4, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_R5, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G0, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G1, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G2, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G3, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G4, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G5, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B1, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B2, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B3, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B4, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B5, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_PCLK, 1, GPIO_4MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_VSYNC, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_HSYNC, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_DE, 1, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_SPI_CLK, 1, GPIO_4MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_SPI_DO, 1, GPIO_4MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_SPI_CSz, 1, GPIO_4MA), +}; + +static uint32_t sony_tft_display_off_gpio_table[] = { + LCM_GPIO_CFG(MAHIMAHI_LCD_R1, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_R2, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_R3, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_R4, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_R5, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G0, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G1, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G2, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G3, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G4, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_G5, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B1, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B2, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B3, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B4, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_B5, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_PCLK, 0, GPIO_4MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_VSYNC, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_HSYNC, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_DE, 0, GPIO_8MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_SPI_CLK, 0, GPIO_4MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_SPI_DO, 0, GPIO_4MA), + LCM_GPIO_CFG(MAHIMAHI_LCD_SPI_CSz, 0, GPIO_4MA), +}; + +#undef LCM_GPIO_CFG + +#define SONY_TFT_DEF_PANEL_DELTA \ + (SONY_TFT_DEF_PANEL_VAL - SONY_TFT_MIN_PANEL_VAL) +#define SONY_TFT_DEF_USER_DELTA \ + (SONY_TFT_DEF_USER_VAL - SONY_TFT_MIN_USER_VAL) + +static void sony_tft_set_pwm_val(int val) +{ + pr_info("%s: %d\n", __func__, val); + + last_val = val; + + if (!tft_panel_on) + return; + + if (val <= SONY_TFT_DEF_USER_VAL) { + if (val <= SONY_TFT_MIN_USER_VAL) + val = SONY_TFT_MIN_PANEL_VAL; + else + val = SONY_TFT_DEF_PANEL_DELTA * + (val - SONY_TFT_MIN_USER_VAL) / + SONY_TFT_DEF_USER_DELTA + + SONY_TFT_MIN_PANEL_VAL; + } else + val = (SONY_TFT_MAX_PANEL_VAL - SONY_TFT_DEF_PANEL_VAL) * + (val - SONY_TFT_DEF_USER_VAL) / + (SONY_TFT_MAX_USER_VAL - SONY_TFT_DEF_USER_VAL) + + SONY_TFT_DEF_PANEL_VAL; + + clk_enable(spi_clk); + qspi_send_9bit(0x0, 0x51); + qspi_send_9bit(0x1, val); + qspi_send_9bit(0x0, 0x53); + qspi_send_9bit(0x1, 0x24); + clk_disable(spi_clk); +} + +#undef SONY_TFT_DEF_PANEL_DELTA +#undef SONY_TFT_DEF_USER_DELTA + +static void sony_tft_panel_config_gpio_table(uint32_t *table, int len) +{ + int n; + unsigned id; + for (n = 0; n < len; n++) { + id = table[n]; + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0); + } +} + + +static int sony_tft_panel_power(int on) +{ + unsigned id, on_off; + + if (on) { + on_off = 0; + + vreg_enable(vreg_lcm_aux_2v6); + vreg_enable(vreg_lcm_rftx_2v6); + + id = PM_VREG_PDOWN_AUX_ID; + msm_proc_comm(PCOM_VREG_PULLDOWN, &on_off, &id); + + id = PM_VREG_PDOWN_RFTX_ID; + msm_proc_comm(PCOM_VREG_PULLDOWN, &on_off, &id); + mdelay(10); + gpio_set_value(MAHIMAHI_GPIO_LCD_RST_N, 1); + mdelay(10); + gpio_set_value(MAHIMAHI_GPIO_LCD_RST_N, 0); + udelay(500); + gpio_set_value(MAHIMAHI_GPIO_LCD_RST_N, 1); + mdelay(10); + sony_tft_panel_config_gpio_table( + sony_tft_display_on_gpio_table, + ARRAY_SIZE(sony_tft_display_on_gpio_table)); + } else { + on_off = 1; + + gpio_set_value(MAHIMAHI_GPIO_LCD_RST_N, 0); + + mdelay(120); + + vreg_disable(vreg_lcm_rftx_2v6); + vreg_disable(vreg_lcm_aux_2v6); + + id = PM_VREG_PDOWN_RFTX_ID; + msm_proc_comm(PCOM_VREG_PULLDOWN, &on_off, &id); + + id = PM_VREG_PDOWN_AUX_ID; + msm_proc_comm(PCOM_VREG_PULLDOWN, &on_off, &id); + sony_tft_panel_config_gpio_table( + sony_tft_display_off_gpio_table, + ARRAY_SIZE(sony_tft_display_off_gpio_table)); + } + return 0; +} + +static int sony_tft_panel_init(struct msm_lcdc_panel_ops *ops) +{ + return 0; +} + +static int sony_tft_panel_unblank(struct msm_lcdc_panel_ops *ops) +{ + pr_info("%s: +()\n", __func__); + + mutex_lock(&panel_lock); + + if (tft_panel_on) { + pr_info("%s: -() already unblanked\n", __func__); + goto done; + } + + sony_tft_panel_power(1); + msleep(45); + + clk_enable(spi_clk); + qspi_send_9bit(0x0, 0x11); + msleep(5); + qspi_send_9bit(0x0, 0x3a); + qspi_send_9bit(0x1, 0x05); + msleep(100); + qspi_send_9bit(0x0, 0x29); + /* unlock register page for pwm setting */ + qspi_send_9bit(0x0, 0xf0); + qspi_send_9bit(0x1, 0x5a); + qspi_send_9bit(0x1, 0x5a); + qspi_send_9bit(0x0, 0xf1); + qspi_send_9bit(0x1, 0x5a); + qspi_send_9bit(0x1, 0x5a); + qspi_send_9bit(0x0, 0xd0); + qspi_send_9bit(0x1, 0x5a); + qspi_send_9bit(0x1, 0x5a); + + qspi_send_9bit(0x0, 0xc2); + qspi_send_9bit(0x1, 0x53); + qspi_send_9bit(0x1, 0x12); + clk_disable(spi_clk); + msleep(100); + tft_panel_on = 1; + sony_tft_set_pwm_val(last_val); + + pr_info("%s: -()\n", __func__); +done: + mutex_unlock(&panel_lock); + return 0; +} + +static int sony_tft_panel_blank(struct msm_lcdc_panel_ops *ops) +{ + pr_info("%s: +()\n", __func__); + + mutex_lock(&panel_lock); + + clk_enable(spi_clk); + qspi_send_9bit(0x0, 0x28); + qspi_send_9bit(0x0, 0x10); + clk_disable(spi_clk); + + msleep(40); + sony_tft_panel_power(0); + tft_panel_on = 0; + + mutex_unlock(&panel_lock); + + pr_info("%s: -()\n", __func__); + return 0; +} + +static struct msm_lcdc_panel_ops mahimahi_lcdc_amoled_panel_ops = { + .init = samsung_oled_panel_init, + .blank = samsung_oled_panel_blank, + .unblank = samsung_oled_panel_unblank, +}; + +static struct msm_lcdc_panel_ops mahimahi_lcdc_tft_panel_ops = { + .init = sony_tft_panel_init, + .blank = sony_tft_panel_blank, + .unblank = sony_tft_panel_unblank, +}; + + +static struct msm_lcdc_timing mahimahi_lcdc_amoled_timing = { + .clk_rate = 24576000, + .hsync_pulse_width = 4, + .hsync_back_porch = 8, + .hsync_front_porch = 8, + .hsync_skew = 0, + .vsync_pulse_width = 2, + .vsync_back_porch = 8, + .vsync_front_porch = 8, + .vsync_act_low = 1, + .hsync_act_low = 1, + .den_act_low = 1, +}; + +static struct msm_lcdc_timing mahimahi_lcdc_tft_timing = { + .clk_rate = 24576000, + .hsync_pulse_width = 2, + .hsync_back_porch = 20, + .hsync_front_porch = 20, + .hsync_skew = 0, + .vsync_pulse_width = 2, + .vsync_back_porch = 6, + .vsync_front_porch = 4, + .vsync_act_low = 1, + .hsync_act_low = 1, + .den_act_low = 0, +}; + +static struct msm_fb_data mahimahi_lcdc_fb_data = { + .xres = 480, + .yres = 800, + .width = 48, + .height = 80, + .output_format = MSM_MDP_OUT_IF_FMT_RGB565, +}; + +static struct msm_lcdc_platform_data mahimahi_lcdc_amoled_platform_data = { + .panel_ops = &mahimahi_lcdc_amoled_panel_ops, + .timing = &mahimahi_lcdc_amoled_timing, + .fb_id = 0, + .fb_data = &mahimahi_lcdc_fb_data, + .fb_resource = &resources_msm_fb[0], +}; + +static struct msm_lcdc_platform_data mahimahi_lcdc_tft_platform_data = { + .panel_ops = &mahimahi_lcdc_tft_panel_ops, + .timing = &mahimahi_lcdc_tft_timing, + .fb_id = 0, + .fb_data = &mahimahi_lcdc_fb_data, + .fb_resource = &resources_msm_fb[0], +}; + +static struct platform_device mahimahi_lcdc_amoled_device = { + .name = "msm_mdp_lcdc", + .id = -1, + .dev = { + .platform_data = &mahimahi_lcdc_amoled_platform_data, + }, +}; + +static struct platform_device mahimahi_lcdc_tft_device = { + .name = "msm_mdp_lcdc", + .id = -1, + .dev = { + .platform_data = &mahimahi_lcdc_tft_platform_data, + }, +}; + +static int mahimahi_init_spi_hack(void) +{ + int ret; + + spi_base = ioremap(MSM_SPI_PHYS, MSM_SPI_SIZE); + if (!spi_base) + return -1; + + spi_clk = clk_get(&msm_device_spi.dev, "spi_clk"); + if (IS_ERR(spi_clk)) { + pr_err("%s: unable to get spi_clk\n", __func__); + ret = PTR_ERR(spi_clk); + goto err_clk_get; + } + + clk_enable(spi_clk); + + printk("spi: SPI_CONFIG=%x\n", readl(spi_base + SPI_CONFIG)); + printk("spi: SPI_IO_CONTROL=%x\n", readl(spi_base + SPI_IO_CONTROL)); + printk("spi: SPI_OPERATIONAL=%x\n", readl(spi_base + SPI_OPERATIONAL)); + printk("spi: SPI_ERROR_FLAGS_EN=%x\n", + readl(spi_base + SPI_ERROR_FLAGS_EN)); + printk("spi: SPI_ERROR_FLAGS=%x\n", readl(spi_base + SPI_ERROR_FLAGS)); + printk("-%s()\n", __FUNCTION__); + clk_disable(spi_clk); + + return 0; + +err_clk_get: + iounmap(spi_base); + return ret; +} + +static void mahimahi_brightness_set(struct led_classdev *led_cdev, + enum led_brightness val) +{ + unsigned long flags; + led_cdev->brightness = val; + + spin_lock_irqsave(&brightness_lock, flags); + new_val = val; + spin_unlock_irqrestore(&brightness_lock, flags); + + schedule_work(&brightness_delayed_work); +} + +static void mahimahi_brightness_amoled_set_work(struct work_struct *work_ptr) +{ + unsigned long flags; + uint8_t val; + + spin_lock_irqsave(&brightness_lock, flags); + val = new_val; + spin_unlock_irqrestore(&brightness_lock, flags); + + mutex_lock(&panel_lock); + samsung_oled_set_gamma_val(val); + mutex_unlock(&panel_lock); +} + +static void mahimahi_brightness_tft_set_work(struct work_struct *work_ptr) +{ + unsigned long flags; + uint8_t val; + + spin_lock_irqsave(&brightness_lock, flags); + val = new_val; + spin_unlock_irqrestore(&brightness_lock, flags); + + mutex_lock(&panel_lock); + sony_tft_set_pwm_val(val); + mutex_unlock(&panel_lock); +} + +static struct led_classdev mahimahi_brightness_led = { + .name = "lcd-backlight", + .brightness = LED_FULL, + .brightness_set = mahimahi_brightness_set, +}; + +int __init mahimahi_init_panel(void) +{ + int ret; + + if (!machine_is_mahimahi()) + return 0; + + if (system_rev > 0xC0) { + /* CDMA version (except for EVT1) supports RGB666 */ + init_tablep = samsung_oled_rgb666_init_table; + init_table_sz = ARRAY_SIZE(samsung_oled_rgb666_init_table); + mahimahi_lcdc_fb_data.output_format = MSM_MDP_OUT_IF_FMT_RGB666; + } + + ret = platform_device_register(&msm_device_mdp); + if (ret != 0) + return ret; + + ret = mahimahi_init_spi_hack(); + if (ret != 0) + return ret; + + if (gpio_get_value(MAHIMAHI_GPIO_LCD_ID0)) { + pr_info("%s: tft panel\n", __func__); + vreg_lcm_rftx_2v6 = vreg_get(0, "rftx"); + if (IS_ERR(vreg_lcm_rftx_2v6)) + return PTR_ERR(vreg_lcm_rftx_2v6); + vreg_set_level(vreg_lcm_rftx_2v6, 2600); + + vreg_lcm_aux_2v6 = vreg_get(0, "gp4"); + if (IS_ERR(vreg_lcm_aux_2v6)) + return PTR_ERR(vreg_lcm_aux_2v6); + + if (gpio_get_value(MAHIMAHI_GPIO_LCD_RST_N)) + tft_panel_on = 1; + ret = platform_device_register(&mahimahi_lcdc_tft_device); + INIT_WORK(&brightness_delayed_work, mahimahi_brightness_tft_set_work); + } else { + pr_info("%s: amoled panel\n", __func__); + ret = platform_device_register(&mahimahi_lcdc_amoled_device); + INIT_WORK(&brightness_delayed_work, mahimahi_brightness_amoled_set_work); + } + + if (ret != 0) + return ret; + + ret = led_classdev_register(NULL, &mahimahi_brightness_led); + if (ret != 0) { + pr_err("%s: Cannot register brightness led\n", __func__); + return ret; + } + + return 0; +} + +device_initcall(mahimahi_init_panel); diff --git a/arch/arm/mach-msm/board-mahimahi-rfkill.c b/arch/arm/mach-msm/board-mahimahi-rfkill.c new file mode 100644 index 00000000000..05c9bb0b4d5 --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-rfkill.c @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "board-mahimahi.h" + +static struct rfkill *bt_rfk; +static const char bt_name[] = "bcm4329"; + +static int bluetooth_set_power(void *data, bool blocked) +{ + if (!blocked) { + gpio_direction_output(MAHIMAHI_GPIO_BT_RESET_N, 1); + gpio_direction_output(MAHIMAHI_GPIO_BT_SHUTDOWN_N, 1); + } else { + gpio_direction_output(MAHIMAHI_GPIO_BT_SHUTDOWN_N, 0); + gpio_direction_output(MAHIMAHI_GPIO_BT_RESET_N, 0); + } + return 0; +} + +static struct rfkill_ops mahimahi_rfkill_ops = { + .set_block = bluetooth_set_power, +}; + +static int mahimahi_rfkill_probe(struct platform_device *pdev) +{ + int rc = 0; + bool default_state = true; /* off */ + + rc = gpio_request(MAHIMAHI_GPIO_BT_RESET_N, "bt_reset"); + if (rc) + goto err_gpio_reset; + rc = gpio_request(MAHIMAHI_GPIO_BT_SHUTDOWN_N, "bt_shutdown"); + if (rc) + goto err_gpio_shutdown; + + bluetooth_set_power(NULL, default_state); + + bt_rfk = rfkill_alloc(bt_name, &pdev->dev, RFKILL_TYPE_BLUETOOTH, + &mahimahi_rfkill_ops, NULL); + if (!bt_rfk) { + rc = -ENOMEM; + goto err_rfkill_alloc; + } + + rfkill_set_states(bt_rfk, default_state, false); + + /* userspace cannot take exclusive control */ + + rc = rfkill_register(bt_rfk); + if (rc) + goto err_rfkill_reg; + + return 0; + +err_rfkill_reg: + rfkill_destroy(bt_rfk); +err_rfkill_alloc: + gpio_free(MAHIMAHI_GPIO_BT_SHUTDOWN_N); +err_gpio_shutdown: + gpio_free(MAHIMAHI_GPIO_BT_RESET_N); +err_gpio_reset: + return rc; +} + +static int mahimahi_rfkill_remove(struct platform_device *dev) +{ + rfkill_unregister(bt_rfk); + rfkill_destroy(bt_rfk); + gpio_free(MAHIMAHI_GPIO_BT_SHUTDOWN_N); + gpio_free(MAHIMAHI_GPIO_BT_RESET_N); + + return 0; +} + +static struct platform_driver mahimahi_rfkill_driver = { + .probe = mahimahi_rfkill_probe, + .remove = mahimahi_rfkill_remove, + .driver = { + .name = "mahimahi_rfkill", + .owner = THIS_MODULE, + }, +}; + +static int __init mahimahi_rfkill_init(void) +{ + if (!machine_is_mahimahi()) + return 0; + + return platform_driver_register(&mahimahi_rfkill_driver); +} + +static void __exit mahimahi_rfkill_exit(void) +{ + platform_driver_unregister(&mahimahi_rfkill_driver); +} + +module_init(mahimahi_rfkill_init); +module_exit(mahimahi_rfkill_exit); +MODULE_DESCRIPTION("mahimahi rfkill"); +MODULE_AUTHOR("Nick Pelly "); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/board-mahimahi-smb329.c b/arch/arm/mach-msm/board-mahimahi-smb329.c new file mode 100755 index 00000000000..b80db78491e --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-smb329.c @@ -0,0 +1,177 @@ +/* drivers/i2c/chips/smb329.c + * + * SMB329B Switch Charger (SUMMIT Microelectronics) + * + * Copyright (C) 2009 HTC Corporation + * Author: Justin Lin + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "board-mahimahi-smb329.h" + +static struct smb329_data { + struct i2c_client *client; + uint8_t version; + struct work_struct work; + struct mutex state_lock; + int chg_state; +} smb329; + +static int smb329_i2c_write(uint8_t *value, uint8_t reg, uint8_t num_bytes) +{ + int ret; + struct i2c_msg msg; + + /* write the first byte of buffer as the register address */ + value[0] = reg; + msg.addr = smb329.client->addr; + msg.len = num_bytes + 1; + msg.flags = 0; + msg.buf = value; + + ret = i2c_transfer(smb329.client->adapter, &msg, 1); + + return (ret >= 0) ? 0 : ret; +} + +static int smb329_i2c_read(uint8_t *value, uint8_t reg, uint8_t num_bytes) +{ + int ret; + struct i2c_msg msg[2]; + + /* setup the address to read */ + msg[0].addr = smb329.client->addr; + msg[0].len = 1; + msg[0].flags = 0; + msg[0].buf = ® + + /* setup the read buffer */ + msg[1].addr = smb329.client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = num_bytes; + msg[1].buf = value; + + ret = i2c_transfer(smb329.client->adapter, msg, 2); + + return (ret >= 0) ? 0 : ret; +} + +static int smb329_i2c_write_byte(uint8_t value, uint8_t reg) +{ + int ret; + uint8_t buf[2] = { 0 }; + + buf[1] = value; + ret = smb329_i2c_write(buf, reg, 1); + if (ret) + pr_err("smb329: write byte error (%d)\n", ret); + + return ret; +} + +static int smb329_i2c_read_byte(uint8_t *value, uint8_t reg) +{ + int ret = smb329_i2c_read(value, reg, 1); + if (ret) + pr_err("smb329: read byte error (%d)\n", ret); + + return ret; +} + +int smb329_set_charger_ctrl(uint32_t ctl) +{ + mutex_lock(&smb329.state_lock); + smb329.chg_state = ctl; + schedule_work(&smb329.work); + mutex_unlock(&smb329.state_lock); + return 0; +} + +static void smb329_work_func(struct work_struct *work) +{ + mutex_lock(&smb329.state_lock); + + switch (smb329.chg_state) { + case SMB329_ENABLE_FAST_CHG: + pr_info("smb329: charger on (fast)\n"); + smb329_i2c_write_byte(0x84, 0x31); + smb329_i2c_write_byte(0x08, 0x05); + if ((smb329.version & 0x18) == 0x0) + smb329_i2c_write_byte(0xA9, 0x00); + break; + + case SMB329_DISABLE_CHG: + case SMB329_ENABLE_SLOW_CHG: + pr_info("smb329: charger off/slow\n"); + smb329_i2c_write_byte(0x88, 0x31); + smb329_i2c_write_byte(0x08, 0x05); + break; + default: + pr_err("smb329: unknown charger state %d\n", + smb329.chg_state); + } + + mutex_unlock(&smb329.state_lock); +} + +static int smb329_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C) == 0) { + dev_dbg(&client->dev, "[SMB329]:I2C fail\n"); + return -EIO; + } + + smb329.client = client; + mutex_init(&smb329.state_lock); + INIT_WORK(&smb329.work, smb329_work_func); + + smb329_i2c_read_byte(&smb329.version, 0x3B); + pr_info("smb329 version: 0x%02x\n", smb329.version); + + return 0; +} + +static const struct i2c_device_id smb329_id[] = { + { "smb329", 0 }, + { }, +}; + +static struct i2c_driver smb329_driver = { + .driver.name = "smb329", + .id_table = smb329_id, + .probe = smb329_probe, +}; + +static int __init smb329_init(void) +{ + int ret = i2c_add_driver(&smb329_driver); + if (ret) + pr_err("smb329_init: failed\n"); + + return ret; +} + +module_init(smb329_init); + +MODULE_AUTHOR("Justin Lin "); +MODULE_DESCRIPTION("SUMMIT Microelectronics SMB329B switch charger"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/board-mahimahi-smb329.h b/arch/arm/mach-msm/board-mahimahi-smb329.h new file mode 100644 index 00000000000..13b326fa71d --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-smb329.h @@ -0,0 +1,32 @@ +/* include/linux/smb329.h - smb329 switch charger driver + * + * Copyright (C) 2009 HTC Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _LINUX_SMB329_H +#define _LINUX_SMB329_H + +#ifdef __KERNEL__ + +enum { + SMB329_DISABLE_CHG, + SMB329_ENABLE_SLOW_CHG, + SMB329_ENABLE_FAST_CHG, +}; + +extern int smb329_set_charger_ctrl(uint32_t ctl); + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_SMB329_H */ + diff --git a/arch/arm/mach-msm/board-mahimahi-tpa2018d1.c b/arch/arm/mach-msm/board-mahimahi-tpa2018d1.c new file mode 100644 index 00000000000..78919b9b195 --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-tpa2018d1.c @@ -0,0 +1,368 @@ +/* drivers/i2c/chips/tpa2018d1.c + * + * TI TPA2018D1 Speaker Amplifier + * + * Copyright (C) 2009 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* TODO: content validation in TPA2018_SET_CONFIG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "board-mahimahi-tpa2018d1.h" + +static struct i2c_client *this_client; +static struct tpa2018d1_platform_data *pdata; +static int is_on; +static char spk_amp_cfg[8]; +static const char spk_amp_on[8] = { /* same length as spk_amp_cfg */ + 0x01, 0xc3, 0x20, 0x01, 0x00, 0x08, 0x1a, 0x21 +}; +static const char spk_amp_off[] = {0x01, 0xa2}; + +static DEFINE_MUTEX(spk_amp_lock); +static int tpa2018d1_opened; +static char *config_data; +static int tpa2018d1_num_modes; + +#define DEBUG 0 + +static int tpa2018_i2c_write(const char *txData, int length) +{ + struct i2c_msg msg[] = { + { + .addr = this_client->addr, + .flags = 0, + .len = length, + .buf = txData, + }, + }; + + if (i2c_transfer(this_client->adapter, msg, 1) < 0) { + pr_err("%s: I2C transfer error\n", __func__); + return -EIO; + } else + return 0; +} + +static int tpa2018_i2c_read(char *rxData, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = this_client->addr, + .flags = I2C_M_RD, + .len = length, + .buf = rxData, + }, + }; + + if (i2c_transfer(this_client->adapter, msgs, 1) < 0) { + pr_err("%s: I2C transfer error\n", __func__); + return -EIO; + } + +#if DEBUG + do { + int i = 0; + for (i = 0; i < length; i++) + pr_info("%s: rx[%d] = %2x\n", + __func__, i, rxData[i]); + } while(0); +#endif + + return 0; +} + +static int tpa2018d1_open(struct inode *inode, struct file *file) +{ + int rc = 0; + + mutex_lock(&spk_amp_lock); + + if (tpa2018d1_opened) { + pr_err("%s: busy\n", __func__); + rc = -EBUSY; + goto done; + } + + tpa2018d1_opened = 1; +done: + mutex_unlock(&spk_amp_lock); + return rc; +} + +static int tpa2018d1_release(struct inode *inode, struct file *file) +{ + mutex_lock(&spk_amp_lock); + tpa2018d1_opened = 0; + mutex_unlock(&spk_amp_lock); + + return 0; +} + +static int tpa2018d1_read_config(void __user *argp) +{ + int rc = 0; + unsigned char reg_idx = 0x01; + unsigned char tmp[7]; + + if (!is_on) { + gpio_set_value(pdata->gpio_tpa2018_spk_en, 1); + msleep(5); /* According to TPA2018D1 Spec */ + } + + rc = tpa2018_i2c_write(®_idx, sizeof(reg_idx)); + if (rc < 0) + goto err; + + rc = tpa2018_i2c_read(tmp, sizeof(tmp)); + if (rc < 0) + goto err; + + if (copy_to_user(argp, &tmp, sizeof(tmp))) + rc = -EFAULT; + +err: + if (!is_on) + gpio_set_value(pdata->gpio_tpa2018_spk_en, 0); + return rc; +} + +static int tpa2018d1_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int rc = 0; + int mode = -1; + int offset = 0; + struct tpa2018d1_config_data cfg; + + mutex_lock(&spk_amp_lock); + + switch (cmd) { + case TPA2018_SET_CONFIG: + if (copy_from_user(spk_amp_cfg, argp, sizeof(spk_amp_cfg))) + rc = -EFAULT; + break; + + case TPA2018_READ_CONFIG: + rc = tpa2018d1_read_config(argp); + break; + + case TPA2018_SET_MODE: + if (copy_from_user(&mode, argp, sizeof(mode))) { + rc = -EFAULT; + break; + } + if (mode >= tpa2018d1_num_modes || mode < 0) { + pr_err("%s: unsupported tpa2018d1 mode %d\n", + __func__, mode); + rc = -EINVAL; + break; + } + if (!config_data) { + pr_err("%s: no config data!\n", __func__); + rc = -EIO; + break; + } + memcpy(spk_amp_cfg, config_data + mode * TPA2018D1_CMD_LEN, + TPA2018D1_CMD_LEN); + break; + + case TPA2018_SET_PARAM: + if (copy_from_user(&cfg, argp, sizeof(cfg))) { + pr_err("%s: copy from user failed.\n", __func__); + rc = -EFAULT; + break; + } + tpa2018d1_num_modes = cfg.mode_num; + if (tpa2018d1_num_modes > TPA2018_NUM_MODES) { + pr_err("%s: invalid number of modes %d\n", __func__, + tpa2018d1_num_modes); + rc = -EINVAL; + break; + } + if (cfg.data_len != tpa2018d1_num_modes*TPA2018D1_CMD_LEN) { + pr_err("%s: invalid data length %d, expecting %d\n", + __func__, cfg.data_len, + tpa2018d1_num_modes * TPA2018D1_CMD_LEN); + rc = -EINVAL; + break; + } + /* Free the old data */ + if (config_data) + kfree(config_data); + config_data = kmalloc(cfg.data_len, GFP_KERNEL); + if (!config_data) { + pr_err("%s: out of memory\n", __func__); + rc = -ENOMEM; + break; + } + if (copy_from_user(config_data, cfg.cmd_data, cfg.data_len)) { + pr_err("%s: copy data from user failed.\n", __func__); + kfree(config_data); + config_data = NULL; + rc = -EFAULT; + break; + } + /* replace default setting with playback setting */ + if (tpa2018d1_num_modes >= TPA2018_MODE_PLAYBACK) { + offset = TPA2018_MODE_PLAYBACK * TPA2018D1_CMD_LEN; + memcpy(spk_amp_cfg, config_data + offset, + TPA2018D1_CMD_LEN); + } + break; + + default: + pr_err("%s: invalid command %d\n", __func__, _IOC_NR(cmd)); + rc = -EINVAL; + break; + } + mutex_unlock(&spk_amp_lock); + return rc; +} + +static struct file_operations tpa2018d1_fops = { + .owner = THIS_MODULE, + .open = tpa2018d1_open, + .release = tpa2018d1_release, + .ioctl = tpa2018d1_ioctl, +}; + +static struct miscdevice tpa2018d1_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "tpa2018d1", + .fops = &tpa2018d1_fops, +}; + +void tpa2018d1_set_speaker_amp(int on) +{ + if (!pdata) { + pr_err("%s: no platform data!\n", __func__); + return; + } + mutex_lock(&spk_amp_lock); + if (on && !is_on) { + gpio_set_value(pdata->gpio_tpa2018_spk_en, 1); + msleep(5); /* According to TPA2018D1 Spec */ + + if (tpa2018_i2c_write(spk_amp_cfg, sizeof(spk_amp_cfg)) == 0) { + is_on = 1; + pr_info("%s: ON\n", __func__); + } + } else if (!on && is_on) { + if (tpa2018_i2c_write(spk_amp_off, sizeof(spk_amp_off)) == 0) { + is_on = 0; + msleep(2); + gpio_set_value(pdata->gpio_tpa2018_spk_en, 0); + pr_info("%s: OFF\n", __func__); + } + } + mutex_unlock(&spk_amp_lock); +} + +static int tpa2018d1_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int ret = 0; + + pdata = client->dev.platform_data; + + if (!pdata) { + ret = -EINVAL; + pr_err("%s: platform data is NULL\n", __func__); + goto err_no_pdata; + } + + this_client = client; + + ret = gpio_request(pdata->gpio_tpa2018_spk_en, "tpa2018"); + if (ret < 0) { + pr_err("%s: gpio request aud_spk_en pin failed\n", __func__); + goto err_free_gpio; + } + + ret = gpio_direction_output(pdata->gpio_tpa2018_spk_en, 1); + if (ret < 0) { + pr_err("%s: request aud_spk_en gpio direction failed\n", + __func__); + goto err_free_gpio; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("%s: i2c check functionality error\n", __func__); + ret = -ENODEV; + goto err_free_gpio; + } + + gpio_set_value(pdata->gpio_tpa2018_spk_en, 0); /* Default Low */ + + ret = misc_register(&tpa2018d1_device); + if (ret) { + pr_err("%s: tpa2018d1_device register failed\n", __func__); + goto err_free_gpio; + } + memcpy(spk_amp_cfg, spk_amp_on, sizeof(spk_amp_on)); + return 0; + +err_free_gpio: + gpio_free(pdata->gpio_tpa2018_spk_en); +err_no_pdata: + return ret; +} + +static int tpa2018d1_suspend(struct i2c_client *client, pm_message_t mesg) +{ + return 0; +} + +static int tpa2018d1_resume(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id tpa2018d1_id[] = { + { TPA2018D1_I2C_NAME, 0 }, + { } +}; + +static struct i2c_driver tpa2018d1_driver = { + .probe = tpa2018d1_probe, + .suspend = tpa2018d1_suspend, + .resume = tpa2018d1_resume, + .id_table = tpa2018d1_id, + .driver = { + .name = TPA2018D1_I2C_NAME, + }, +}; + +static int __init tpa2018d1_init(void) +{ + pr_info("%s\n", __func__); + return i2c_add_driver(&tpa2018d1_driver); +} + +module_init(tpa2018d1_init); + +MODULE_DESCRIPTION("tpa2018d1 speaker amp driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/board-mahimahi-tpa2018d1.h b/arch/arm/mach-msm/board-mahimahi-tpa2018d1.h new file mode 100644 index 00000000000..dc110122094 --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-tpa2018d1.h @@ -0,0 +1,35 @@ +/* include/linux/tpa2018d1.h - tpa2018d1 speaker amplifier driver + * + * Copyright (C) 2009 HTC Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#ifndef __ASM_ARM_ARCH_TPA2018D1_H +#define __ASM_ARM_ARCH_TPA2018D1_H + +#define TPA2018D1_I2C_NAME "tpa2018d1" +#define TPA2018D1_CMD_LEN 8 + +struct tpa2018d1_platform_data { + uint32_t gpio_tpa2018_spk_en; +}; + +struct tpa2018d1_config_data { + unsigned char *cmd_data; /* [mode][cmd_len][cmds..] */ + unsigned int mode_num; + unsigned int data_len; +}; + +extern void tpa2018d1_set_speaker_amp(int on); + +#endif /* __ASM_ARM_ARCH_TPA2018D1_H */ diff --git a/arch/arm/mach-msm/board-mahimahi-wifi.c b/arch/arm/mach-msm/board-mahimahi-wifi.c new file mode 100644 index 00000000000..8cd24766b03 --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi-wifi.c @@ -0,0 +1,146 @@ +/* linux/arch/arm/mach-msm/board-mahimahi-wifi.c +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "board-mahimahi.h" + +int mahimahi_wifi_power(int on); +int mahimahi_wifi_reset(int on); +int mahimahi_wifi_set_carddetect(int on); + +#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 *mahimahi_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 mahimahi_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 mahimahi_wifi_resources[] = { + [0] = { + .name = "bcm4329_wlan_irq", + .start = MSM_GPIO_TO_INT(MAHIMAHI_GPIO_WIFI_IRQ), + .end = MSM_GPIO_TO_INT(MAHIMAHI_GPIO_WIFI_IRQ), + .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE, + }, +}; + +static struct wifi_platform_data mahimahi_wifi_control = { + .set_power = mahimahi_wifi_power, + .set_reset = mahimahi_wifi_reset, + .set_carddetect = mahimahi_wifi_set_carddetect, + .mem_prealloc = mahimahi_wifi_mem_prealloc, +}; + +static struct platform_device mahimahi_wifi_device = { + .name = "bcm4329_wlan", + .id = 1, + .num_resources = ARRAY_SIZE(mahimahi_wifi_resources), + .resource = mahimahi_wifi_resources, + .dev = { + .platform_data = &mahimahi_wifi_control, + }, +}; + +extern unsigned char *get_wifi_nvs_ram(void); +extern int wifi_calibration_size_set(void); + +static unsigned mahimahi_wifi_update_nvs(char *str, int add_flag) +{ +#define NVS_LEN_OFFSET 0x0C +#define NVS_DATA_OFFSET 0x40 + unsigned char *ptr; + unsigned len; + + if (!str) + return -EINVAL; + ptr = get_wifi_nvs_ram(); + /* Size in format LE assumed */ + memcpy(&len, ptr + NVS_LEN_OFFSET, sizeof(len)); + /* if the last byte in NVRAM is 0, trim it */ + if (ptr[NVS_DATA_OFFSET + len - 1] == 0) + len -= 1; + if (add_flag) { + strcpy(ptr + NVS_DATA_OFFSET + len, str); + len += strlen(str); + } else { + if (strnstr(ptr + NVS_DATA_OFFSET, str, len)) + len -= strlen(str); + } + memcpy(ptr + NVS_LEN_OFFSET, &len, sizeof(len)); + wifi_calibration_size_set(); + return 0; +} + +static int __init mahimahi_wifi_init(void) +{ + int ret; + + if (!machine_is_mahimahi()) + return 0; + + printk("%s: start\n", __func__); + mahimahi_wifi_update_nvs("sd_oobonly=1\r\n", 0); + mahimahi_wifi_update_nvs("btc_params70=0x32\r\n", 1); + mahimahi_init_wifi_mem(); + ret = platform_device_register(&mahimahi_wifi_device); + return ret; +} + +late_initcall(mahimahi_wifi_init); diff --git a/arch/arm/mach-msm/board-mahimahi.h b/arch/arm/mach-msm/board-mahimahi.h new file mode 100644 index 00000000000..9696a47c400 --- /dev/null +++ b/arch/arm/mach-msm/board-mahimahi.h @@ -0,0 +1,175 @@ +/* arch/arm/mach-msm/board-mahimahi.h + * + * Copyright (C) 2009 HTC Corporation. + * Author: Haley Teng + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +#ifndef __ARCH_ARM_MACH_MSM_BOARD_MAHIMAHI_H +#define __ARCH_ARM_MACH_MSM_BOARD_MAHIMAHI_H + +#include + +#define MSM_SMI_BASE 0x02B00000 +#define MSM_SMI_SIZE 0x01500000 + +#define MSM_RAM_CONSOLE_BASE 0x03A00000 +#define MSM_RAM_CONSOLE_SIZE 0x00040000 + +#define MSM_FB_BASE 0x03B00000 +#define MSM_FB_SIZE 0x00465000 + +#define MSM_EBI1_BANK0_BASE 0x20000000 +#define MSM_EBI1_BANK0_SIZE 0x0E000000 + +#define MSM_GPU_MEM_BASE 0x2DB00000 +#define MSM_GPU_MEM_SIZE 0x00500000 + +#define MSM_EBI1_BANK1_BASE 0x30000000 +#define MSM_EBI1_BANK1_SIZE 0x10000000 + +#define MSM_PMEM_MDP_BASE 0x30000000 +#define MSM_PMEM_MDP_SIZE 0x02000000 + +#define MSM_PMEM_ADSP_BASE 0x32000000 +#define MSM_PMEM_ADSP_SIZE 0x02900000 + +#define MSM_PMEM_CAMERA_BASE 0x34900000 +#define MSM_PMEM_CAMERA_SIZE 0x00800000 + +#define MSM_HIGHMEM_BASE 0x35100000 +#define MSM_HIGHMEM_SIZE 0x0AF00000 + +#define MAHIMAHI_GPIO_PS_HOLD 25 + +#define MAHIMAHI_GPIO_UP_INT_N 35 +#define MAHIMAHI_GPIO_UP_RESET_N 82 +#define MAHIMAHI_GPIO_LS_EN_N 119 + +#define MAHIMAHI_GPIO_TP_INT_N 92 +#define MAHIMAHI_GPIO_TP_LS_EN 93 +#define MAHIMAHI_GPIO_TP_EN 160 + +#define MAHIMAHI_GPIO_POWER_KEY 94 +#define MAHIMAHI_GPIO_SDMC_CD_REV0_N 153 + +#define MAHIMAHI_GPIO_WIFI_SHUTDOWN_N 127 +#define MAHIMAHI_GPIO_WIFI_IRQ 152 + +#define MAHIMAHI_GPIO_BALL_UP 38 +#define MAHIMAHI_GPIO_BALL_DOWN 37 +#define MAHIMAHI_GPIO_BALL_LEFT 145 +#define MAHIMAHI_GPIO_BALL_RIGHT 21 + +#define MAHIMAHI_GPIO_BT_UART1_RTS 43 +#define MAHIMAHI_GPIO_BT_UART1_CTS 44 +#define MAHIMAHI_GPIO_BT_UART1_RX 45 +#define MAHIMAHI_GPIO_BT_UART1_TX 46 +#define MAHIMAHI_GPIO_BT_RESET_N 146 +#define MAHIMAHI_GPIO_BT_SHUTDOWN_N 128 + +#define MAHIMAHI_GPIO_BT_WAKE 57 +#define MAHIMAHI_GPIO_BT_HOST_WAKE 86 + +#define MAHIMAHI_GPIO_PROXIMITY_INT_N 90 +#define MAHIMAHI_GPIO_PROXIMITY_EN 120 + +#define MAHIMAHI_GPIO_DS2482_SLP_N 87 +#define MAHIMAHI_GPIO_VIBRATOR_ON 89 +/* Compass */ +#define MAHIMAHI_REV0_GPIO_COMPASS_INT_N 36 + +#define MAHIMAHI_GPIO_COMPASS_INT_N 153 +#define MAHIMAHI_GPIO_COMPASS_RST_N 107 +#define MAHIMAHI_PROJECT_NAME "mahimahi" +#define MAHIMAHI_LAYOUTS { \ + { {-1, 0, 0}, { 0, -1, 0}, {0, 0, 1} }, \ + { { 0, -1, 0}, { 1, 0, 0}, {0, 0, -1} }, \ + { { 0, -1, 0}, { 1, 0, 0}, {0, 0, 1} }, \ + { {-1, 0, 0}, { 0, 0, -1}, {0, 1, 0} } \ +} + +/* Audio */ +#define MAHIMAHI_AUD_JACKHP_EN 157 +#define MAHIMAHI_AUD_2V5_EN 158 +#define MAHIMAHI_AUD_MICPATH_SEL 111 +#define MAHIMAHI_AUD_A1026_INT 112 +#define MAHIMAHI_AUD_A1026_WAKEUP 113 +#define MAHIMAHI_AUD_A1026_RESET 129 +#define MAHIMAHI_AUD_A1026_CLK -1 +#define MAHIMAHI_CDMA_XA_AUD_A1026_CLK 105 +/* NOTE: MAHIMAHI_CDMA_XB_AUD_A1026_WAKEUP on CDMA is the same GPIO as + * MAHIMAHI_GPIO_BATTERY_CHARGER_CURRENT on UMTS. Also, + * MAHIMAHI_CDMA_XB_AUD_A1026_RESET is the same as + * GPIO MAHIMAHI_GPIO_35MM_KEY_INT_SHUTDOWN on UMTS. + */ +#define MAHIMAHI_CDMA_XB_AUD_A1026_WAKEUP 16 +#define MAHIMAHI_CDMA_XB_AUD_A1026_RESET 19 +#define MAHIMAHI_CDMA_XB_AUD_A1026_CLK -1 + +/* Bluetooth PCM */ +#define MAHIMAHI_BT_PCM_OUT 68 +#define MAHIMAHI_BT_PCM_IN 69 +#define MAHIMAHI_BT_PCM_SYNC 70 +#define MAHIMAHI_BT_PCM_CLK 71 +/* flash light */ +#define MAHIMAHI_GPIO_FLASHLIGHT_TORCH 58 +#define MAHIMAHI_GPIO_FLASHLIGHT_FLASH 84 + +#define MAHIMAHI_GPIO_LED_3V3_EN 85 +#define MAHIMAHI_GPIO_LCD_RST_N 29 +#define MAHIMAHI_GPIO_LCD_ID0 147 + +/* 3.5mm remote control key interrupt shutdown signal */ +#define MAHIMAHI_GPIO_35MM_KEY_INT_SHUTDOWN 19 + +#define MAHIMAHI_GPIO_DOCK 106 + +/* speaker amplifier enable pin for mahimahi CDMA version */ +#define MAHIMAHI_CDMA_GPIO_AUD_SPK_AMP_EN 104 + +#define MAHIMAHI_GPIO_BATTERY_DETECTION 39 +#define MAHIMAHI_GPIO_BATTERY_CHARGER_EN 22 +#define MAHIMAHI_GPIO_BATTERY_CHARGER_CURRENT 16 + +#define MAHIMAHI_CDMA_GPIO_BT_WAKE 28 +#define MAHIMAHI_CDMA_GPIO_FLASHLIGHT_TORCH 26 + +#define MAHIMAHI_CDMA_SD_2V85_EN 100 +#define MAHIMAHI_CDMA_JOG_2V6_EN 150 +/* display relative */ +#define MAHIMAHI_LCD_SPI_CLK (17) +#define MAHIMAHI_LCD_SPI_DO (18) +#define MAHIMAHI_LCD_SPI_CSz (20) +#define MAHIMAHI_LCD_RSTz (29) +#define MAHIMAHI_LCD_R1 (114) +#define MAHIMAHI_LCD_R2 (115) +#define MAHIMAHI_LCD_R3 (116) +#define MAHIMAHI_LCD_R4 (117) +#define MAHIMAHI_LCD_R5 (118) +#define MAHIMAHI_LCD_G0 (121) +#define MAHIMAHI_LCD_G1 (122) +#define MAHIMAHI_LCD_G2 (123) +#define MAHIMAHI_LCD_G3 (124) +#define MAHIMAHI_LCD_G4 (125) +#define MAHIMAHI_LCD_G5 (126) +#define MAHIMAHI_LCD_B1 (130) +#define MAHIMAHI_LCD_B2 (131) +#define MAHIMAHI_LCD_B3 (132) +#define MAHIMAHI_LCD_B4 (133) +#define MAHIMAHI_LCD_B5 (134) +#define MAHIMAHI_LCD_PCLK (135) +#define MAHIMAHI_LCD_VSYNC (136) +#define MAHIMAHI_LCD_HSYNC (137) +#define MAHIMAHI_LCD_DE (138) +#define is_cdma_version(rev) (((rev) & 0xF0) == 0xC0) + +#endif /* __ARCH_ARM_MACH_MSM_BOARD_MAHIMAHI_H */ diff --git a/arch/arm/mach-msm/board-msm7x27.c b/arch/arm/mach-msm/board-msm7x27.c index c03f269e2e4..1e62998538a 100644 --- a/arch/arm/mach-msm/board-msm7x27.c +++ b/arch/arm/mach-msm/board-msm7x27.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -15,13 +15,16 @@ */ #include +#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -32,18 +35,59 @@ #include #endif +#include #include #include -#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include #include "devices.h" -#include "socinfo.h" #include "clock.h" +#include "msm-keypad-devices.h" +#include "pm.h" + +#ifdef CONFIG_USB_ANDROID +#include +#endif + +#ifdef CONFIG_ARCH_MSM7X25 +#define MSM_PMEM_MDP_SIZE 0xb21000 +#define MSM_PMEM_ADSP_SIZE 0x97b000 +#define MSM_PMEM_AUDIO_SIZE 0x121000 +#define MSM_FB_SIZE 0x200000 +#define PMEM_KERNEL_EBI1_SIZE 0x64000 +#endif + +#ifdef CONFIG_ARCH_MSM7X27 +#define MSM_PMEM_MDP_SIZE 0x1B76000 +#define MSM_PMEM_ADSP_SIZE 0xC8A000 +#define MSM_PMEM_AUDIO_SIZE 0x5B000 + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +#define MSM_FB_SIZE 0x233000 +#else +#define MSM_FB_SIZE 0x177000 +#endif + +#define PMEM_KERNEL_EBI1_SIZE 0x1C000 +#endif static struct resource smc91x_resources[] = { [0] = { @@ -51,28 +95,1391 @@ static struct resource smc91x_resources[] = { .end = 0x9C0043ff, .flags = IORESOURCE_MEM, }, - [1] = { - .start = MSM_GPIO_TO_INT(132), - .end = MSM_GPIO_TO_INT(132), - .flags = IORESOURCE_IRQ, + [1] = { + .start = MSM_GPIO_TO_INT(132), + .end = MSM_GPIO_TO_INT(132), + .flags = IORESOURCE_IRQ, + }, +}; + +#ifdef CONFIG_USB_FUNCTION +static struct usb_mass_storage_platform_data usb_mass_storage_pdata = { + .nluns = 0x02, + .buf_size = 16384, + .vendor = "GOOGLE", + .product = "Mass storage", + .release = 0xffff, +}; + +static struct platform_device mass_storage_device = { + .name = "usb_mass_storage", + .id = -1, + .dev = { + .platform_data = &usb_mass_storage_pdata, + }, +}; +#endif +#ifdef CONFIG_USB_ANDROID +static char *usb_functions_default[] = { + "diag", + "modem", + "nmea", + "rmnet", + "usb_mass_storage", +}; + +static char *usb_functions_default_adb[] = { + "diag", + "adb", + "modem", + "nmea", + "rmnet", + "usb_mass_storage", +}; + +static char *usb_functions_rndis[] = { + "rndis", +}; + +static char *usb_functions_rndis_adb[] = { + "rndis", + "adb", +}; + +static char *usb_functions_all[] = { +#ifdef CONFIG_USB_ANDROID_RNDIS + "rndis", +#endif +#ifdef CONFIG_USB_ANDROID_DIAG + "diag", +#endif + "adb", +#ifdef CONFIG_USB_F_SERIAL + "modem", + "nmea", +#endif +#ifdef CONFIG_USB_ANDROID_RMNET + "rmnet", +#endif + "usb_mass_storage", +#ifdef CONFIG_USB_ANDROID_ACM + "acm", +#endif +}; + +static struct android_usb_product usb_products[] = { + { + .product_id = 0x9026, + .num_functions = ARRAY_SIZE(usb_functions_default), + .functions = usb_functions_default, + }, + { + .product_id = 0x9025, + .num_functions = ARRAY_SIZE(usb_functions_default_adb), + .functions = usb_functions_default_adb, + }, + { + .product_id = 0xf00e, + .num_functions = ARRAY_SIZE(usb_functions_rndis), + .functions = usb_functions_rndis, + }, + { + .product_id = 0x9024, + .num_functions = ARRAY_SIZE(usb_functions_rndis_adb), + .functions = usb_functions_rndis_adb, + }, +}; + +static struct usb_mass_storage_platform_data mass_storage_pdata = { + .nluns = 1, + .vendor = "Qualcomm Incorporated", + .product = "Mass storage", + .release = 0x0100, + .can_stall = 1, +}; + +static struct platform_device usb_mass_storage_device = { + .name = "usb_mass_storage", + .id = -1, + .dev = { + .platform_data = &mass_storage_pdata, + }, +}; + +static struct usb_ether_platform_data rndis_pdata = { + /* ethaddr is filled by board_serialno_setup */ + .vendorID = 0x05C6, + .vendorDescr = "Qualcomm Incorporated", +}; + +static struct platform_device rndis_device = { + .name = "rndis", + .id = -1, + .dev = { + .platform_data = &rndis_pdata, + }, +}; + +static struct android_usb_platform_data android_usb_pdata = { + .vendor_id = 0x05C6, + .product_id = 0x9026, + .version = 0x0100, + .product_name = "Qualcomm HSUSB Device", + .manufacturer_name = "Qualcomm Incorporated", + .num_products = ARRAY_SIZE(usb_products), + .products = usb_products, + .num_functions = ARRAY_SIZE(usb_functions_all), + .functions = usb_functions_all, + .serial_number = "1234567890ABCDEF", +}; + +static struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &android_usb_pdata, + }, +}; + +static int __init board_serialno_setup(char *serialno) +{ + int i; + char *src = serialno; + + /* create a fake MAC address from our serial number. + * first byte is 0x02 to signify locally administered. + */ + rndis_pdata.ethaddr[0] = 0x02; + for (i = 0; *src; i++) { + /* XOR the USB serial across the remaining bytes */ + rndis_pdata.ethaddr[i % (ETH_ALEN - 1) + 1] ^= *src++; + } + + android_usb_pdata.serial_number = serialno; + return 1; +} +__setup("androidboot.serialno=", board_serialno_setup); +#endif + +static struct platform_device smc91x_device = { + .name = "smc91x", + .id = 0, + .num_resources = ARRAY_SIZE(smc91x_resources), + .resource = smc91x_resources, +}; + +#ifdef CONFIG_USB_FUNCTION +static struct usb_function_map usb_functions_map[] = { + {"diag", 0}, + {"adb", 1}, + {"modem", 2}, + {"nmea", 3}, + {"mass_storage", 4}, + {"ethernet", 5}, + {"rmnet", 6}, +}; + +/* dynamic composition */ +static struct usb_composition usb_func_composition[] = { + { + .product_id = 0x9012, + .functions = 0x5, /* 0101 */ + }, + + { + .product_id = 0x9013, + .functions = 0x15, /* 10101 */ + }, + + { + .product_id = 0x9014, + .functions = 0x30, /* 110000 */ + }, + + { + .product_id = 0x9016, + .functions = 0xD, /* 01101 */ + }, + + { + .product_id = 0x9017, + .functions = 0x1D, /* 11101 */ + }, + + { + .product_id = 0xF000, + .functions = 0x10, /* 10000 */ + }, + + { + .product_id = 0xF009, + .functions = 0x20, /* 100000 */ + }, + + { + .product_id = 0x9018, + .functions = 0x1F, /* 011111 */ + }, +#ifdef CONFIG_USB_FUNCTION_RMNET + { + .product_id = 0x9021, + /* DIAG + RMNET */ + .functions = 0x41, + }, + { + .product_id = 0x9022, + /* DIAG + ADB + RMNET */ + .functions = 0x43, + }, +#endif + +}; + +static struct msm_hsusb_platform_data msm_hsusb_pdata = { + .version = 0x0100, + .phy_info = (USB_PHY_INTEGRATED | USB_PHY_MODEL_65NM), + .vendor_id = 0x5c6, + .product_name = "Qualcomm HSUSB Device", + .serial_number = "1234567890ABCDEF", + .manufacturer_name = "Qualcomm Incorporated", + .compositions = usb_func_composition, + .num_compositions = ARRAY_SIZE(usb_func_composition), + .function_map = usb_functions_map, + .num_functions = ARRAY_SIZE(usb_functions_map), + .config_gpio = NULL, +}; +#endif + +#ifdef CONFIG_USB_EHCI_MSM_72K +static void msm_hsusb_vbus_power(unsigned phy_info, int on) +{ + if (on) + msm_hsusb_vbus_powerup(); + else + msm_hsusb_vbus_shutdown(); +} + +static struct msm_usb_host_platform_data msm_usb_host_pdata = { + .phy_info = (USB_PHY_INTEGRATED | USB_PHY_MODEL_65NM), +}; + +static void __init msm7x2x_init_host(void) +{ + if (machine_is_msm7x25_ffa() || machine_is_msm7x27_ffa()) + return; + + msm_add_host(0, &msm_usb_host_pdata); +} +#endif + +#ifdef CONFIG_USB_MSM_OTG_72K +static int hsusb_rpc_connect(int connect) +{ + if (connect) + return msm_hsusb_rpc_connect(); + else + return msm_hsusb_rpc_close(); +} +#endif + +#ifdef CONFIG_USB_MSM_OTG_72K +struct vreg *vreg_3p3; +static int msm_hsusb_ldo_init(int init) +{ + if (init) { + /* + * PHY 3.3V analog domain(VDDA33) is powered up by + * an always enabled power supply (LP5900TL-3.3). + * USB VREG default source is VBUS line. Turning + * on USB VREG has a side effect on the USB suspend + * current. Hence USB VREG is explicitly turned + * off here. + */ + vreg_3p3 = vreg_get(NULL, "usb"); + if (IS_ERR(vreg_3p3)) + return PTR_ERR(vreg_3p3); + vreg_enable(vreg_3p3); + vreg_disable(vreg_3p3); + vreg_put(vreg_3p3); + } + + return 0; +} + +static int msm_hsusb_pmic_notif_init(void (*callback)(int online), int init) +{ + int ret; + + if (init) { + ret = msm_pm_app_rpc_init(callback); + } else { + msm_pm_app_rpc_deinit(callback); + ret = 0; + } + return ret; +} + +static int msm_otg_rpc_phy_reset(void __iomem *regs) +{ + return msm_hsusb_phy_reset(); +} + +static struct msm_otg_platform_data msm_otg_pdata = { + .rpc_connect = hsusb_rpc_connect, + .pmic_vbus_notif_init = msm_hsusb_pmic_notif_init, + .chg_vbus_draw = hsusb_chg_vbus_draw, + .chg_connected = hsusb_chg_connected, + .chg_init = hsusb_chg_init, +#ifdef CONFIG_USB_EHCI_MSM_72K + .vbus_power = msm_hsusb_vbus_power, +#endif + .ldo_init = msm_hsusb_ldo_init, + .pclk_required_during_lpm = 1, + .pclk_src_name = "ebi1_usb_clk", +}; + +#ifdef CONFIG_USB_GADGET +static struct msm_hsusb_gadget_platform_data msm_gadget_pdata; +#endif +#endif + +#define SND(desc, num) { .name = #desc, .id = num } +static struct snd_endpoint snd_endpoints_list[] = { + SND(HANDSET, 0), + SND(MONO_HEADSET, 2), + SND(HEADSET, 3), + SND(SPEAKER, 6), + SND(TTY_HEADSET, 8), + SND(TTY_VCO, 9), + SND(TTY_HCO, 10), + SND(BT, 12), + SND(IN_S_SADC_OUT_HANDSET, 16), + SND(IN_S_SADC_OUT_SPEAKER_PHONE, 25), + SND(CURRENT, 27), +}; +#undef SND + +static struct msm_snd_endpoints msm_device_snd_endpoints = { + .endpoints = snd_endpoints_list, + .num = sizeof(snd_endpoints_list) / sizeof(struct snd_endpoint) +}; + +static struct platform_device msm_device_snd = { + .name = "msm_snd", + .id = -1, + .dev = { + .platform_data = &msm_device_snd_endpoints + }, +}; + +#define DEC0_FORMAT ((1< 0; i--) + vreg_disable(vreg[i - 1]); + } + + return rc; +} +static struct lcdc_platform_data lcdc_pdata = { + .lcdc_gpio_config = msm_fb_lcdc_config, + .lcdc_power_save = msm_fb_lcdc_power_save, +}; + +static struct msm_panel_common_pdata lcdc_gordon_panel_data = { + .panel_config_gpio = lcdc_gordon_config_gpios, + .gpio_num = gpio_array_num, +}; + +static struct platform_device lcdc_gordon_panel_device = { + .name = "lcdc_gordon_vga", + .id = 0, + .dev = { + .platform_data = &lcdc_gordon_panel_data, + } +}; + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; + +static int msm_fb_detect_panel(const char *name) +{ + int ret = -EPERM; + + if (machine_is_msm7x25_ffa() || machine_is_msm7x27_ffa()) { + if (!strcmp(name, "lcdc_gordon_vga")) + ret = 0; + else + ret = -ENODEV; + } + + return ret; +} + +static struct msm_fb_platform_data msm_fb_pdata = { + .detect_client = msm_fb_detect_panel, + .mddi_prescan = 1, +}; + +static struct platform_device msm_fb_device = { + .name = "msm_fb", + .id = 0, + .num_resources = ARRAY_SIZE(msm_fb_resources), + .resource = msm_fb_resources, + .dev = { + .platform_data = &msm_fb_pdata, + } +}; + +#ifdef CONFIG_BT +static struct platform_device msm_bt_power_device = { + .name = "bt_power", +}; + +enum { + BT_WAKE, + BT_RFR, + BT_CTS, + BT_RX, + BT_TX, + BT_PCM_DOUT, + BT_PCM_DIN, + BT_PCM_SYNC, + BT_PCM_CLK, + BT_HOST_WAKE, +}; + +static unsigned bt_config_power_on[] = { + GPIO_CFG(42, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* WAKE */ + GPIO_CFG(43, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* RFR */ + GPIO_CFG(44, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* CTS */ + GPIO_CFG(45, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* Rx */ + GPIO_CFG(46, 3, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* Tx */ + GPIO_CFG(68, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_DOUT */ + GPIO_CFG(69, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_DIN */ + GPIO_CFG(70, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_SYNC */ + GPIO_CFG(71, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_CLK */ + GPIO_CFG(83, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* HOST_WAKE */ +}; +static unsigned bt_config_power_off[] = { + GPIO_CFG(42, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* WAKE */ + GPIO_CFG(43, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* RFR */ + GPIO_CFG(44, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* CTS */ + GPIO_CFG(45, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* Rx */ + GPIO_CFG(46, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* Tx */ + GPIO_CFG(68, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* PCM_DOUT */ + GPIO_CFG(69, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* PCM_DIN */ + GPIO_CFG(70, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* PCM_SYNC */ + GPIO_CFG(71, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* PCM_CLK */ + GPIO_CFG(83, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* HOST_WAKE */ +}; + +static int bluetooth_power(int on) +{ + struct vreg *vreg_bt; + int pin, rc; + + printk(KERN_DEBUG "%s\n", __func__); + + /* do not have vreg bt defined, gp6 is the same */ + /* vreg_get parameter 1 (struct device *) is ignored */ + vreg_bt = vreg_get(NULL, "gp6"); + + if (IS_ERR(vreg_bt)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_bt)); + return PTR_ERR(vreg_bt); + } + + if (on) { + for (pin = 0; pin < ARRAY_SIZE(bt_config_power_on); pin++) { + rc = gpio_tlmm_config(bt_config_power_on[pin], + GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR + "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, bt_config_power_on[pin], rc); + return -EIO; + } + } + + /* units of mV, steps of 50 mV */ + rc = vreg_set_level(vreg_bt, 2600); + if (rc) { + printk(KERN_ERR "%s: vreg set level failed (%d)\n", + __func__, rc); + return -EIO; + } + rc = vreg_enable(vreg_bt); + if (rc) { + printk(KERN_ERR "%s: vreg enable failed (%d)\n", + __func__, rc); + return -EIO; + } + } else { + rc = vreg_disable(vreg_bt); + if (rc) { + printk(KERN_ERR "%s: vreg disable failed (%d)\n", + __func__, rc); + return -EIO; + } + for (pin = 0; pin < ARRAY_SIZE(bt_config_power_off); pin++) { + rc = gpio_tlmm_config(bt_config_power_off[pin], + GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR + "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, bt_config_power_off[pin], rc); + return -EIO; + } + } + } + return 0; +} + +static void __init bt_power_init(void) +{ + msm_bt_power_device.dev.platform_data = &bluetooth_power; +} +#else +#define bt_power_init(x) do {} while (0) +#endif + +static struct platform_device msm_device_pmic_leds = { + .name = "pmic-leds", + .id = -1, +}; + +static struct resource bluesleep_resources[] = { + { + .name = "gpio_host_wake", + .start = 83, + .end = 83, + .flags = IORESOURCE_IO, + }, + { + .name = "gpio_ext_wake", + .start = 42, + .end = 42, + .flags = IORESOURCE_IO, + }, + { + .name = "host_wake", + .start = MSM_GPIO_TO_INT(83), + .end = MSM_GPIO_TO_INT(83), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_bluesleep_device = { + .name = "bluesleep", + .id = -1, + .num_resources = ARRAY_SIZE(bluesleep_resources), + .resource = bluesleep_resources, +}; + +static struct i2c_board_info i2c_devices[] = { +#ifdef CONFIG_MT9D112 + { + I2C_BOARD_INFO("mt9d112", 0x78 >> 1), + }, +#endif +#ifdef CONFIG_S5K3E2FX + { + I2C_BOARD_INFO("s5k3e2fx", 0x20 >> 1), + }, +#endif +#ifdef CONFIG_MT9P012 + { + I2C_BOARD_INFO("mt9p012", 0x6C >> 1), + }, +#endif +#ifdef CONFIG_MT9P012_KM + { + I2C_BOARD_INFO("mt9p012_km", 0x6C >> 2), + }, +#endif +#if defined(CONFIG_MT9T013) || defined(CONFIG_SENSORS_MT9T013) + { + I2C_BOARD_INFO("mt9t013", 0x6C), + }, +#endif +#ifdef CONFIG_VB6801 + { + I2C_BOARD_INFO("vb6801", 0x20), + }, +#endif +}; + +#ifdef CONFIG_MSM_CAMERA +static uint32_t camera_off_gpio_table[] = { + /* parallel CAMERA interfaces */ + GPIO_CFG(0, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT0 */ + GPIO_CFG(1, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT1 */ + GPIO_CFG(2, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT2 */ + GPIO_CFG(3, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT3 */ + GPIO_CFG(4, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT4 */ + GPIO_CFG(5, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT5 */ + GPIO_CFG(6, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT6 */ + GPIO_CFG(7, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT7 */ + GPIO_CFG(8, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT8 */ + GPIO_CFG(9, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT9 */ + GPIO_CFG(10, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT10 */ + GPIO_CFG(11, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT11 */ + GPIO_CFG(12, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* PCLK */ + GPIO_CFG(13, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* HSYNC_IN */ + GPIO_CFG(14, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* VSYNC_IN */ + GPIO_CFG(15, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* MCLK */ +}; + +static uint32_t camera_on_gpio_table[] = { + /* parallel CAMERA interfaces */ + GPIO_CFG(0, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT0 */ + GPIO_CFG(1, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT1 */ + GPIO_CFG(2, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT2 */ + GPIO_CFG(3, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT3 */ + GPIO_CFG(4, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT4 */ + GPIO_CFG(5, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT5 */ + GPIO_CFG(6, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT6 */ + GPIO_CFG(7, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT7 */ + GPIO_CFG(8, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT8 */ + GPIO_CFG(9, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT9 */ + GPIO_CFG(10, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT10 */ + GPIO_CFG(11, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT11 */ + GPIO_CFG(12, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_16MA), /* PCLK */ + GPIO_CFG(13, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* HSYNC_IN */ + GPIO_CFG(14, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* VSYNC_IN */ + GPIO_CFG(15, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_16MA), /* MCLK */ + }; + +static void config_gpio_table(uint32_t *table, int len) +{ + int n, rc; + for (n = 0; n < len; n++) { + rc = gpio_tlmm_config(table[n], GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, table[n], rc); + break; + } + } +} + +static struct vreg *vreg_gp2; +static struct vreg *vreg_gp3; + +static void msm_camera_vreg_config(int vreg_en) +{ + int rc; + + if (vreg_gp2 == NULL) { + vreg_gp2 = vreg_get(NULL, "gp2"); + if (IS_ERR(vreg_gp2)) { + printk(KERN_ERR "%s: vreg_get(%s) failed (%ld)\n", + __func__, "gp2", PTR_ERR(vreg_gp2)); + return; + } + + rc = vreg_set_level(vreg_gp2, 1800); + if (rc) { + printk(KERN_ERR "%s: GP2 set_level failed (%d)\n", + __func__, rc); + } + } + + if (vreg_gp3 == NULL) { + vreg_gp3 = vreg_get(NULL, "gp3"); + if (IS_ERR(vreg_gp3)) { + printk(KERN_ERR "%s: vreg_get(%s) failed (%ld)\n", + __func__, "gp3", PTR_ERR(vreg_gp3)); + return; + } + + rc = vreg_set_level(vreg_gp3, 2850); + if (rc) { + printk(KERN_ERR "%s: GP3 set level failed (%d)\n", + __func__, rc); + } + } + + if (vreg_en) { + rc = vreg_enable(vreg_gp2); + if (rc) { + printk(KERN_ERR "%s: GP2 enable failed (%d)\n", + __func__, rc); + } + + rc = vreg_enable(vreg_gp3); + if (rc) { + printk(KERN_ERR "%s: GP3 enable failed (%d)\n", + __func__, rc); + } + } else { + rc = vreg_disable(vreg_gp2); + if (rc) { + printk(KERN_ERR "%s: GP2 disable failed (%d)\n", + __func__, rc); + } + + rc = vreg_disable(vreg_gp3); + if (rc) { + printk(KERN_ERR "%s: GP3 disable failed (%d)\n", + __func__, rc); + } + } +} + +static int config_camera_on_gpios(void) +{ + int vreg_en = 1; + + if (machine_is_msm7x25_ffa() || + machine_is_msm7x27_ffa()) + msm_camera_vreg_config(vreg_en); + + config_gpio_table(camera_on_gpio_table, + ARRAY_SIZE(camera_on_gpio_table)); + return 0; +} + +static void config_camera_off_gpios(void) +{ + int vreg_en = 0; + + if (machine_is_msm7x25_ffa() || + machine_is_msm7x27_ffa()) + msm_camera_vreg_config(vreg_en); + + config_gpio_table(camera_off_gpio_table, + ARRAY_SIZE(camera_off_gpio_table)); +} + +static struct msm_camera_device_platform_data msm_camera_device_data = { + .camera_gpio_on = config_camera_on_gpios, + .camera_gpio_off = config_camera_off_gpios, + .ioext.mdcphy = MSM_MDC_PHYS, + .ioext.mdcsz = MSM_MDC_SIZE, + .ioext.appphy = MSM_CLK_CTL_PHYS, + .ioext.appsz = MSM_CLK_CTL_SIZE, +}; + +int pmic_set_flash_led_current(enum pmic8058_leds id, unsigned mA) +{ + int rc; + rc = pmic_flash_led_set_current(mA); + return rc; +} + +static struct msm_camera_sensor_flash_src msm_flash_src = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_PMIC, + ._fsrc.pmic_src.num_of_src = 1, + ._fsrc.pmic_src.low_current = 30, + ._fsrc.pmic_src.high_current = 100, + ._fsrc.pmic_src.led_src_1 = 0, + ._fsrc.pmic_src.led_src_2 = 0, + ._fsrc.pmic_src.pmic_set_current = pmic_set_flash_led_current, +}; + +#ifdef CONFIG_MT9D112 +static struct msm_camera_sensor_flash_data flash_mt9d112 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9d112_data = { + .sensor_name = "mt9d112", + .sensor_reset = 89, + .sensor_pwd = 85, + .vcm_pwd = 0, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .flash_data = &flash_mt9d112 +}; + +static struct platform_device msm_camera_sensor_mt9d112 = { + .name = "msm_camera_mt9d112", + .dev = { + .platform_data = &msm_camera_sensor_mt9d112_data, + }, +}; +#endif + +#ifdef CONFIG_S5K3E2FX +static struct msm_camera_sensor_flash_data flash_s5k3e2fx = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_s5k3e2fx_data = { + .sensor_name = "s5k3e2fx", + .sensor_reset = 89, + .sensor_pwd = 85, + .vcm_pwd = 0, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .flash_data = &flash_s5k3e2fx +}; + +static struct platform_device msm_camera_sensor_s5k3e2fx = { + .name = "msm_camera_s5k3e2fx", + .dev = { + .platform_data = &msm_camera_sensor_s5k3e2fx_data, + }, +}; +#endif + +#ifdef CONFIG_MT9P012 +static struct msm_camera_sensor_flash_data flash_mt9p012 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9p012_data = { + .sensor_name = "mt9p012", + .sensor_reset = 89, + .sensor_pwd = 85, + .vcm_pwd = 88, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .flash_data = &flash_mt9p012 +}; + +static struct platform_device msm_camera_sensor_mt9p012 = { + .name = "msm_camera_mt9p012", + .dev = { + .platform_data = &msm_camera_sensor_mt9p012_data, }, }; +#endif -static struct platform_device smc91x_device = { - .name = "smc91x", - .id = 0, - .num_resources = ARRAY_SIZE(smc91x_resources), - .resource = smc91x_resources, +#ifdef CONFIG_MT9P012_KM +static struct msm_camera_sensor_flash_data flash_mt9p012_km = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9p012_km_data = { + .sensor_name = "mt9p012_km", + .sensor_reset = 89, + .sensor_pwd = 85, + .vcm_pwd = 88, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .flash_data = &flash_mt9p012_km +}; + +static struct platform_device msm_camera_sensor_mt9p012_km = { + .name = "msm_camera_mt9p012_km", + .dev = { + .platform_data = &msm_camera_sensor_mt9p012_km_data, + }, +}; +#endif + +#ifdef CONFIG_MT9T013 +static struct msm_camera_sensor_flash_data flash_mt9t013 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9t013_data = { + .sensor_name = "mt9t013", + .sensor_reset = 89, + .sensor_pwd = 85, + .vcm_pwd = 0, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .flash_data = &flash_mt9t013 +}; + +static struct platform_device msm_camera_sensor_mt9t013 = { + .name = "msm_camera_mt9t013", + .dev = { + .platform_data = &msm_camera_sensor_mt9t013_data, + }, +}; +#endif + +#ifdef CONFIG_VB6801 +static struct msm_camera_sensor_flash_data flash_vb6801 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_vb6801_data = { + .sensor_name = "vb6801", + .sensor_reset = 89, + .sensor_pwd = 88, + .vcm_pwd = 0, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .flash_data = &flash_vb6801 +}; + +static struct platform_device msm_camera_sensor_vb6801 = { + .name = "msm_camera_vb6801", + .dev = { + .platform_data = &msm_camera_sensor_vb6801_data, + }, +}; +#endif +#endif + +static u32 msm_calculate_batt_capacity(u32 current_voltage); + +static struct msm_psy_batt_pdata msm_psy_batt_data = { + .voltage_min_design = 2800, + .voltage_max_design = 4300, + .avail_chg_sources = AC_CHG | USB_CHG , + .batt_technology = POWER_SUPPLY_TECHNOLOGY_LION, + .calculate_capacity = &msm_calculate_batt_capacity, +}; + +static u32 msm_calculate_batt_capacity(u32 current_voltage) +{ + u32 low_voltage = msm_psy_batt_data.voltage_min_design; + u32 high_voltage = msm_psy_batt_data.voltage_max_design; + + return (current_voltage - low_voltage) * 100 + / (high_voltage - low_voltage); +} + +static struct platform_device msm_batt_device = { + .name = "msm-battery", + .id = -1, + .dev.platform_data = &msm_psy_batt_data, }; + static struct platform_device *devices[] __initdata = { - &msm_device_uart3, + &asoc_msm_pcm, + &asoc_msm_dai0, + &asoc_msm_dai1, + &msm_device_smd, &msm_device_dmov, &msm_device_nand, + +#ifdef CONFIG_USB_MSM_OTG_72K + &msm_device_otg, +#ifdef CONFIG_USB_GADGET + &msm_device_gadget_peripheral, +#endif +#endif + +#ifdef CONFIG_USB_FUNCTION + &msm_device_hsusb_peripheral, + &mass_storage_device, +#endif + +#ifdef CONFIG_USB_ANDROID + &usb_mass_storage_device, + &rndis_device, +#ifdef CONFIG_USB_ANDROID_DIAG + &usb_diag_device, +#endif +#ifdef CONFIG_USB_F_SERIAL + &usb_gadget_fserial_device, +#endif + &android_usb_device, +#endif + &msm_device_i2c, &smc91x_device, + &msm_device_tssc, + &android_pmem_device, + &android_pmem_adsp_device, + &android_pmem_audio_device, + &msm_fb_device, + &lcdc_gordon_panel_device, + &msm_device_uart_dm1, +#ifdef CONFIG_BT + &msm_bt_power_device, +#endif + &msm_device_pmic_leds, + &msm_device_snd, + &msm_device_adspdec, +#ifdef CONFIG_MT9T013 + &msm_camera_sensor_mt9t013, +#endif +#ifdef CONFIG_MT9D112 + &msm_camera_sensor_mt9d112, +#endif +#ifdef CONFIG_S5K3E2FX + &msm_camera_sensor_s5k3e2fx, +#endif +#ifdef CONFIG_MT9P012 + &msm_camera_sensor_mt9p012, +#endif +#ifdef CONFIG_MT9P012_KM + &msm_camera_sensor_mt9p012_km, +#endif +#ifdef CONFIG_VB6801 + &msm_camera_sensor_vb6801, +#endif + &msm_bluesleep_device, +#ifdef CONFIG_ARCH_MSM7X27 + &msm_kgsl_3d0, +#endif +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + &msm_device_tsif, +#endif + &hs_device, + &msm_batt_device, +}; + +static struct msm_panel_common_pdata mdp_pdata = { + .gpio = 97, }; +static void __init msm_fb_add_devices(void) +{ + msm_fb_register_device("mdp", &mdp_pdata); + msm_fb_register_device("pmdh", 0); + msm_fb_register_device("lcdc", &lcdc_pdata); +} + extern struct sys_timer msm_timer; static void __init msm7x2x_init_irq(void) @@ -80,51 +1487,632 @@ static void __init msm7x2x_init_irq(void) msm_init_irq(); } +static struct msm_acpu_clock_platform_data msm7x2x_clock_data = { + .acpu_switch_time_us = 50, + .max_speed_delta_khz = 400000, + .vdd_switch_time_us = 62, + .max_axi_khz = 160000, +}; + +void msm_serial_debug_init(unsigned int base, int irq, + struct device *clk_device, int signal_irq); + +#if (defined(CONFIG_MMC_MSM_SDC1_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC2_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC3_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC4_SUPPORT)) + +static unsigned long vreg_sts, gpio_sts; +static struct vreg *vreg_mmc; +static unsigned mpp_mmc = 2; + +struct sdcc_gpio { + struct msm_gpio *cfg_data; + uint32_t size; + struct msm_gpio *sleep_cfg_data; +}; + +static struct msm_gpio sdc1_cfg_data[] = { + {GPIO_CFG(51, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_3"}, + {GPIO_CFG(52, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_2"}, + {GPIO_CFG(53, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_1"}, + {GPIO_CFG(54, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_0"}, + {GPIO_CFG(55, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_cmd"}, + {GPIO_CFG(56, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "sdc1_clk"}, +}; + +static struct msm_gpio sdc2_cfg_data[] = { + {GPIO_CFG(62, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "sdc2_clk"}, + {GPIO_CFG(63, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_cmd"}, + {GPIO_CFG(64, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_3"}, + {GPIO_CFG(65, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_2"}, + {GPIO_CFG(66, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_1"}, + {GPIO_CFG(67, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_0"}, +}; + +static struct msm_gpio sdc2_sleep_cfg_data[] = { + {GPIO_CFG(62, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "sdc2_clk"}, + {GPIO_CFG(63, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "sdc2_cmd"}, + {GPIO_CFG(64, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "sdc2_dat_3"}, + {GPIO_CFG(65, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "sdc2_dat_2"}, + {GPIO_CFG(66, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "sdc2_dat_1"}, + {GPIO_CFG(67, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "sdc2_dat_0"}, +}; +static struct msm_gpio sdc3_cfg_data[] = { + {GPIO_CFG(88, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "sdc3_clk"}, + {GPIO_CFG(89, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_cmd"}, + {GPIO_CFG(90, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_3"}, + {GPIO_CFG(91, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_2"}, + {GPIO_CFG(92, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_1"}, + {GPIO_CFG(93, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_0"}, +}; + +static struct msm_gpio sdc4_cfg_data[] = { + {GPIO_CFG(19, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_3"}, + {GPIO_CFG(20, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_2"}, + {GPIO_CFG(21, 4, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_1"}, + {GPIO_CFG(107, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_cmd"}, + {GPIO_CFG(108, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_0"}, + {GPIO_CFG(109, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "sdc4_clk"}, +}; + +static struct sdcc_gpio sdcc_cfg_data[] = { + { + .cfg_data = sdc1_cfg_data, + .size = ARRAY_SIZE(sdc1_cfg_data), + .sleep_cfg_data = NULL, + }, + { + .cfg_data = sdc2_cfg_data, + .size = ARRAY_SIZE(sdc2_cfg_data), + .sleep_cfg_data = sdc2_sleep_cfg_data, + }, + { + .cfg_data = sdc3_cfg_data, + .size = ARRAY_SIZE(sdc3_cfg_data), + .sleep_cfg_data = NULL, + }, + { + .cfg_data = sdc4_cfg_data, + .size = ARRAY_SIZE(sdc4_cfg_data), + .sleep_cfg_data = NULL, + }, +}; + +static void msm_sdcc_setup_gpio(int dev_id, unsigned int enable) +{ + int rc = 0; + struct sdcc_gpio *curr; + + curr = &sdcc_cfg_data[dev_id - 1]; + if (!(test_bit(dev_id, &gpio_sts)^enable)) + return; + + if (enable) { + set_bit(dev_id, &gpio_sts); + rc = msm_gpios_request_enable(curr->cfg_data, curr->size); + if (rc) + printk(KERN_ERR "%s: Failed to turn on GPIOs for slot %d\n", + __func__, dev_id); + } else { + clear_bit(dev_id, &gpio_sts); + if (curr->sleep_cfg_data) { + msm_gpios_enable(curr->sleep_cfg_data, curr->size); + msm_gpios_free(curr->sleep_cfg_data, curr->size); + return; + } + msm_gpios_disable_free(curr->cfg_data, curr->size); + } +} + +static uint32_t msm_sdcc_setup_power(struct device *dv, unsigned int vdd) +{ + int rc = 0; + struct platform_device *pdev; + + pdev = container_of(dv, struct platform_device, dev); + msm_sdcc_setup_gpio(pdev->id, !!vdd); + + if (vdd == 0) { + if (!vreg_sts) + return 0; + + clear_bit(pdev->id, &vreg_sts); + + if (!vreg_sts) { + if (machine_is_msm7x25_ffa() || + machine_is_msm7x27_ffa()) { + rc = mpp_config_digital_out(mpp_mmc, + MPP_CFG(MPP_DLOGIC_LVL_MSMP, + MPP_DLOGIC_OUT_CTRL_LOW)); + } else + rc = vreg_disable(vreg_mmc); + if (rc) + printk(KERN_ERR "%s: return val: %d \n", + __func__, rc); + } + return 0; + } + + if (!vreg_sts) { + if (machine_is_msm7x25_ffa() || machine_is_msm7x27_ffa()) { + rc = mpp_config_digital_out(mpp_mmc, + MPP_CFG(MPP_DLOGIC_LVL_MSMP, + MPP_DLOGIC_OUT_CTRL_HIGH)); + } else { + rc = vreg_set_level(vreg_mmc, 2850); + if (!rc) + rc = vreg_enable(vreg_mmc); + } + if (rc) + printk(KERN_ERR "%s: return val: %d \n", + __func__, rc); + } + set_bit(pdev->id, &vreg_sts); + return 0; +} + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static struct mmc_platform_data msm7x2x_sdc1_data = { + .ocr_mask = MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +#ifdef CONFIG_MMC_MSM_SDC1_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static struct mmc_platform_data msm7x2x_sdc2_data = { + .ocr_mask = MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#ifdef CONFIG_MMC_MSM_SDIO_SUPPORT + .sdiowakeup_irq = MSM_GPIO_TO_INT(66), +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +#ifdef CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static struct mmc_platform_data msm7x2x_sdc3_data = { + .ocr_mask = MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +#ifdef CONFIG_MMC_MSM_SDC3_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT +static struct mmc_platform_data msm7x2x_sdc4_data = { + .ocr_mask = MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +#ifdef CONFIG_MMC_MSM_SDC4_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +}; +#endif + +static void __init msm7x2x_init_mmc(void) +{ + if (!machine_is_msm7x25_ffa() && !machine_is_msm7x27_ffa()) { + vreg_mmc = vreg_get(NULL, "mmc"); + if (IS_ERR(vreg_mmc)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_mmc)); + return; + } + } + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + msm_add_sdcc(1, &msm7x2x_sdc1_data); +#endif + + if (machine_is_msm7x25_surf() || machine_is_msm7x27_surf() || + machine_is_msm7x27_ffa()) { +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + msm_sdcc_setup_gpio(2, 1); + msm_add_sdcc(2, &msm7x2x_sdc2_data); +#endif + } + + if (machine_is_msm7x25_surf() || machine_is_msm7x27_surf()) { +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + msm_add_sdcc(3, &msm7x2x_sdc3_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT + msm_add_sdcc(4, &msm7x2x_sdc4_data); +#endif + } +} +#else +#define msm7x2x_init_mmc() do {} while (0) +#endif + + +static struct msm_pm_platform_data msm7x25_pm_data[MSM_PM_SLEEP_MODE_NR] = { + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE].latency = 16000, + + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN].latency = 12000, + + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT].latency = 2000, +}; + +static struct msm_pm_platform_data msm7x27_pm_data[MSM_PM_SLEEP_MODE_NR] = { + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 16000, + .residency = 20000, + }, + + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 12000, + .residency = 20000, + }, + + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 2000, + .residency = 0, + }, +}; + +static void +msm_i2c_gpio_config(int iface, int config_type) +{ + int gpio_scl; + int gpio_sda; + if (iface) { + gpio_scl = 95; + gpio_sda = 96; + } else { + gpio_scl = 60; + gpio_sda = 61; + } + if (config_type) { + gpio_tlmm_config(GPIO_CFG(gpio_scl, 1, GPIO_CFG_INPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_16MA), GPIO_CFG_ENABLE); + gpio_tlmm_config(GPIO_CFG(gpio_sda, 1, GPIO_CFG_INPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_16MA), GPIO_CFG_ENABLE); + } else { + gpio_tlmm_config(GPIO_CFG(gpio_scl, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_16MA), GPIO_CFG_ENABLE); + gpio_tlmm_config(GPIO_CFG(gpio_sda, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_16MA), GPIO_CFG_ENABLE); + } +} + +static struct msm_i2c_platform_data msm_i2c_pdata = { + .clk_freq = 100000, + .rmutex = 0, + .pri_clk = 60, + .pri_dat = 61, + .aux_clk = 95, + .aux_dat = 96, + .msm_i2c_config_gpio = msm_i2c_gpio_config, +}; + +static void __init msm_device_i2c_init(void) +{ + if (gpio_request(60, "i2c_pri_clk")) + pr_err("failed to request gpio i2c_pri_clk\n"); + if (gpio_request(61, "i2c_pri_dat")) + pr_err("failed to request gpio i2c_pri_dat\n"); + if (gpio_request(95, "i2c_sec_clk")) + pr_err("failed to request gpio i2c_sec_clk\n"); + if (gpio_request(96, "i2c_sec_dat")) + pr_err("failed to request gpio i2c_sec_dat\n"); + + if (cpu_is_msm7x27()) + msm_i2c_pdata.pm_lat = + msm7x27_pm_data[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] + .latency; + else + msm_i2c_pdata.pm_lat = + msm7x25_pm_data[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] + .latency; + + msm_device_i2c.dev.platform_data = &msm_i2c_pdata; +} + +static void usb_mpp_init(void) +{ + unsigned rc; + unsigned mpp_usb = 7; + + if (machine_is_msm7x25_ffa() || machine_is_msm7x27_ffa()) { + rc = mpp_config_digital_out(mpp_usb, + MPP_CFG(MPP_DLOGIC_LVL_VDD, + MPP_DLOGIC_OUT_CTRL_HIGH)); + if (rc) + pr_err("%s: configuring mpp pin" + "to enable 3.3V LDO failed\n", __func__); + } +} + +static void msm7x27_wlan_init(void) +{ + int rc = 0; + /* TBD: if (machine_is_msm7x27_ffa_with_wcn1312()) */ + if (machine_is_msm7x27_ffa()) { + rc = mpp_config_digital_out(3, MPP_CFG(MPP_DLOGIC_LVL_MSMP, + MPP_DLOGIC_OUT_CTRL_LOW)); + if (rc) + printk(KERN_ERR "%s: return val: %d \n", + __func__, rc); + } +} + static void __init msm7x2x_init(void) { - if (socinfo_init() < 0) - BUG(); +#ifdef CONFIG_ARCH_MSM7X25 + msm_clock_init(msm_clocks_7x25, msm_num_clocks_7x25); +#elif defined(CONFIG_ARCH_MSM7X27) + msm_clock_init(msm_clocks_7x27, msm_num_clocks_7x27); +#endif + +#if defined(CONFIG_SMC91X) if (machine_is_msm7x25_ffa() || machine_is_msm7x27_ffa()) { smc91x_resources[0].start = 0x98000300; smc91x_resources[0].end = 0x980003ff; smc91x_resources[1].start = MSM_GPIO_TO_INT(85); smc91x_resources[1].end = MSM_GPIO_TO_INT(85); if (gpio_tlmm_config(GPIO_CFG(85, 0, - GPIO_INPUT, - GPIO_PULL_DOWN, - GPIO_2MA), - GPIO_ENABLE)) { + GPIO_CFG_INPUT, + GPIO_CFG_PULL_DOWN, + GPIO_CFG_2MA), + GPIO_CFG_ENABLE)) { printk(KERN_ERR "%s: Err: Config GPIO-85 INT\n", __func__); } } +#endif + + if (cpu_is_msm7x27()) + msm7x2x_clock_data.max_axi_khz = 200000; + + msm_acpu_clock_init(&msm7x2x_clock_data); + + usb_mpp_init(); + +#ifdef CONFIG_USB_FUNCTION + msm_hsusb_pdata.swfi_latency = + msm7x27_pm_data + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT].latency; + + msm_device_hsusb_peripheral.dev.platform_data = &msm_hsusb_pdata; +#endif + +#ifdef CONFIG_USB_MSM_OTG_72K + msm_device_otg.dev.platform_data = &msm_otg_pdata; + if (machine_is_msm7x25_surf() || machine_is_msm7x25_ffa()) { + msm_otg_pdata.pemp_level = + PRE_EMPHASIS_WITH_20_PERCENT; + msm_otg_pdata.drv_ampl = HS_DRV_AMPLITUDE_5_PERCENT; + msm_otg_pdata.cdr_autoreset = CDR_AUTO_RESET_ENABLE; + msm_otg_pdata.phy_reset = msm_otg_rpc_phy_reset; + } + if (machine_is_msm7x27_surf() || machine_is_msm7x27_ffa()) { + msm_otg_pdata.pemp_level = + PRE_EMPHASIS_WITH_10_PERCENT; + msm_otg_pdata.drv_ampl = HS_DRV_AMPLITUDE_5_PERCENT; + msm_otg_pdata.cdr_autoreset = CDR_AUTO_RESET_DISABLE; + msm_otg_pdata.phy_reset_sig_inverted = 1; + } +#ifdef CONFIG_USB_GADGET + msm_otg_pdata.swfi_latency = + msm7x27_pm_data + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT].latency; + msm_device_gadget_peripheral.dev.platform_data = &msm_gadget_pdata; + msm_gadget_pdata.is_phy_status_timer_on = 1; +#endif +#endif +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + msm_device_tsif.dev.platform_data = &tsif_platform_data; +#endif + platform_add_devices(msm_footswitch_devices, + msm_num_footswitch_devices); platform_add_devices(devices, ARRAY_SIZE(devices)); +#ifdef CONFIG_MSM_CAMERA + config_camera_off_gpios(); /* might not be necessary */ +#endif + msm_device_i2c_init(); + i2c_register_board_info(0, i2c_devices, ARRAY_SIZE(i2c_devices)); + +#ifdef CONFIG_SURF_FFA_GPIO_KEYPAD + if (machine_is_msm7x25_ffa() || machine_is_msm7x27_ffa()) + platform_device_register(&keypad_device_7k_ffa); + else + platform_device_register(&keypad_device_surf); +#endif + lcdc_gordon_gpio_init(); + msm_fb_add_devices(); +#ifdef CONFIG_USB_EHCI_MSM_72K + msm7x2x_init_host(); +#endif + msm7x2x_init_mmc(); + bt_power_init(); + + if (cpu_is_msm7x27()) + msm_pm_set_platform_data(msm7x27_pm_data, + ARRAY_SIZE(msm7x27_pm_data)); + else + msm_pm_set_platform_data(msm7x25_pm_data, + ARRAY_SIZE(msm7x25_pm_data)); + msm7x27_wlan_init(); +} + +static unsigned pmem_kernel_ebi1_size = PMEM_KERNEL_EBI1_SIZE; +static int __init pmem_kernel_ebi1_size_setup(char *p) +{ + pmem_kernel_ebi1_size = memparse(p, NULL); + return 0; +} +early_param("pmem_kernel_ebi1_size", pmem_kernel_ebi1_size_setup); + +static unsigned pmem_mdp_size = MSM_PMEM_MDP_SIZE; +static int __init pmem_mdp_size_setup(char *p) +{ + pmem_mdp_size = memparse(p, NULL); + return 0; +} +early_param("pmem_mdp_size", pmem_mdp_size_setup); + +static unsigned pmem_adsp_size = MSM_PMEM_ADSP_SIZE; +static int __init pmem_adsp_size_setup(char *p) +{ + pmem_adsp_size = memparse(p, NULL); + return 0; +} +early_param("pmem_adsp_size", pmem_adsp_size_setup); + +static unsigned pmem_audio_size = MSM_PMEM_AUDIO_SIZE; +static int __init pmem_audio_size_setup(char *p) +{ + pmem_audio_size = memparse(p, NULL); + return 0; +} +early_param("pmem_audio_size", pmem_audio_size_setup); + +static unsigned fb_size = MSM_FB_SIZE; +static int __init fb_size_setup(char *p) +{ + fb_size = memparse(p, NULL); + return 0; +} +early_param("fb_size", fb_size_setup); + +static void __init msm_msm7x2x_allocate_memory_regions(void) +{ + void *addr; + unsigned long size; + + size = fb_size ? : MSM_FB_SIZE; + addr = alloc_bootmem_align(size, 0x1000); + msm_fb_resources[0].start = __pa(addr); + msm_fb_resources[0].end = msm_fb_resources[0].start + size - 1; + pr_info("allocating %lu bytes at %p (%lx physical) for fb\n", + size, addr, __pa(addr)); +} + +static struct memtype_reserve msm7x27_reserve_table[] __initdata = { + [MEMTYPE_SMI] = { + }, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, +}; + +static void __init size_pmem_devices(void) +{ +#ifdef CONFIG_ANDROID_PMEM + android_pmem_adsp_pdata.size = pmem_adsp_size; + android_pmem_pdata.size = pmem_mdp_size; + android_pmem_audio_pdata.size = pmem_audio_size; +#endif +} + +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + msm7x27_reserve_table[p->memory_type].size += p->size; +} + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM + reserve_memory_for(&android_pmem_adsp_pdata); + reserve_memory_for(&android_pmem_pdata); + reserve_memory_for(&android_pmem_audio_pdata); + msm7x27_reserve_table[MEMTYPE_EBI1].size += pmem_kernel_ebi1_size; +#endif +} + +static void __init msm7x27_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); +} + +static int msm7x27_paddr_to_memtype(unsigned int paddr) +{ + return MEMTYPE_EBI1; +} + +static struct reserve_info msm7x27_reserve_info __initdata = { + .memtype_reserve_table = msm7x27_reserve_table, + .calculate_reserve_sizes = msm7x27_calculate_reserve_sizes, + .paddr_to_memtype = msm7x27_paddr_to_memtype, +}; + +static void __init msm7x27_reserve(void) +{ + reserve_info = &msm7x27_reserve_info; + msm_reserve(); +} + +static void __init msm7x27_init_early(void) +{ + msm_msm7x2x_allocate_memory_regions(); } static void __init msm7x2x_map_io(void) { msm_map_common_io(); - /* Technically dependent on the SoC but using machine_is - * macros since socinfo is not available this early and there - * are plans to restructure the code which will eliminate the - * need for socinfo. - */ - if (machine_is_msm7x27_surf() || machine_is_msm7x27_ffa()) - msm_clock_init(msm_clocks_7x27, msm_num_clocks_7x27); - if (machine_is_msm7x25_surf() || machine_is_msm7x25_ffa()) - msm_clock_init(msm_clocks_7x25, msm_num_clocks_7x25); + if (socinfo_init() < 0) + BUG(); #ifdef CONFIG_CACHE_L2X0 if (machine_is_msm7x27_surf() || machine_is_msm7x27_ffa()) { /* 7x27 has 256KB L2 cache: 64Kb/Way and 4-Way Associativity; - R/W latency: 3 cycles; evmon/parity/share disabled. */ - l2x0_init(MSM_L2CC_BASE, 0x00068012, 0xfe000000); + if ((SOCINFO_VERSION_MAJOR(socinfo_get_version()) > 1) + || ((SOCINFO_VERSION_MAJOR(socinfo_get_version()) == 1) + && (SOCINFO_VERSION_MINOR(socinfo_get_version()) >= 3))) + /* R/W latency: 4 cycles; */ + l2x0_init(MSM_L2CC_BASE, 0x0006801B, 0xfe000000); + else + /* R/W latency: 3 cycles; */ + l2x0_init(MSM_L2CC_BASE, 0x00068012, 0xfe000000); } #endif } @@ -132,31 +2120,39 @@ static void __init msm7x2x_map_io(void) MACHINE_START(MSM7X27_SURF, "QCT MSM7x27 SURF") .boot_params = PLAT_PHYS_OFFSET + 0x100, .map_io = msm7x2x_map_io, + .reserve = msm7x27_reserve, .init_irq = msm7x2x_init_irq, .init_machine = msm7x2x_init, .timer = &msm_timer, + .init_early = msm7x27_init_early, MACHINE_END MACHINE_START(MSM7X27_FFA, "QCT MSM7x27 FFA") .boot_params = PLAT_PHYS_OFFSET + 0x100, .map_io = msm7x2x_map_io, + .reserve = msm7x27_reserve, .init_irq = msm7x2x_init_irq, .init_machine = msm7x2x_init, .timer = &msm_timer, + .init_early = msm7x27_init_early, MACHINE_END MACHINE_START(MSM7X25_SURF, "QCT MSM7x25 SURF") .boot_params = PLAT_PHYS_OFFSET + 0x100, .map_io = msm7x2x_map_io, + .reserve = msm7x27_reserve, .init_irq = msm7x2x_init_irq, .init_machine = msm7x2x_init, .timer = &msm_timer, + .init_early = msm7x27_init_early, MACHINE_END MACHINE_START(MSM7X25_FFA, "QCT MSM7x25 FFA") .boot_params = PLAT_PHYS_OFFSET + 0x100, .map_io = msm7x2x_map_io, + .reserve = msm7x27_reserve, .init_irq = msm7x2x_init_irq, .init_machine = msm7x2x_init, .timer = &msm_timer, + .init_early = msm7x27_init_early, MACHINE_END diff --git a/arch/arm/mach-msm/board-msm7x27a.c b/arch/arm/mach-msm/board-msm7x27a.c new file mode 100644 index 00000000000..6f2249b4ca0 --- /dev/null +++ b/arch/arm/mach-msm/board-msm7x27a.c @@ -0,0 +1,3249 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "devices.h" +#include "timer.h" +#include "devices-msm7x2xa.h" +#include "pm.h" +#include +#include + +#define PMEM_KERNEL_EBI1_SIZE 0x3A000 +#define MSM_PMEM_AUDIO_SIZE 0x5B000 +#define BAHAMA_SLAVE_ID_FM_ADDR 0x2A +#define BAHAMA_SLAVE_ID_QMEMBIST_ADDR 0x7B +#define FM_GPIO 83 + +enum { + GPIO_EXPANDER_IRQ_BASE = NR_MSM_IRQS + NR_GPIO_IRQS, + GPIO_EXPANDER_GPIO_BASE = NR_MSM_GPIOS, + /* SURF expander */ + GPIO_CORE_EXPANDER_BASE = GPIO_EXPANDER_GPIO_BASE, + GPIO_BT_SYS_REST_EN = GPIO_CORE_EXPANDER_BASE, + GPIO_WLAN_EXT_POR_N, + GPIO_DISPLAY_PWR_EN, + GPIO_BACKLIGHT_EN, + GPIO_PRESSURE_XCLR, + GPIO_VREG_S3_EXP, + GPIO_UBM2M_PWRDWN, + GPIO_ETM_MODE_CS_N, + GPIO_HOST_VBUS_EN, + GPIO_SPI_MOSI, + GPIO_SPI_MISO, + GPIO_SPI_CLK, + GPIO_SPI_CS0_N, + GPIO_CORE_EXPANDER_IO13, + GPIO_CORE_EXPANDER_IO14, + GPIO_CORE_EXPANDER_IO15, + /* Camera expander */ + GPIO_CAM_EXPANDER_BASE = GPIO_CORE_EXPANDER_BASE + 16, + GPIO_CAM_GP_STROBE_READY = GPIO_CAM_EXPANDER_BASE, + GPIO_CAM_GP_AFBUSY, + GPIO_CAM_GP_CAM_PWDN, + GPIO_CAM_GP_CAM1MP_XCLR, + GPIO_CAM_GP_CAMIF_RESET_N, + GPIO_CAM_GP_STROBE_CE, + GPIO_CAM_GP_LED_EN1, + GPIO_CAM_GP_LED_EN2, +}; + +#if defined(CONFIG_GPIO_SX150X) +enum { + SX150X_CORE, + SX150X_CAM, +}; + +static struct sx150x_platform_data sx150x_data[] __initdata = { + [SX150X_CORE] = { + .gpio_base = GPIO_CORE_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0, + .io_pulldn_ena = 0, + .io_open_drain_ena = 0xfef8, + .irq_summary = -1, + }, + [SX150X_CAM] = { + .gpio_base = GPIO_CAM_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0, + .io_pulldn_ena = 0, + .io_open_drain_ena = 0x23, + .irq_summary = -1, + }, +}; +#endif + +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) + + /* FM Platform power and shutdown routines */ +#define FPGA_MSM_CNTRL_REG2 0x90008010 +static void config_pcm_i2s_mode(int mode) +{ + void __iomem *cfg_ptr; + u8 reg2; + + cfg_ptr = ioremap_nocache(FPGA_MSM_CNTRL_REG2, sizeof(char)); + + if (!cfg_ptr) + return; + if (mode) { + /*enable the pcm mode in FPGA*/ + reg2 = readb_relaxed(cfg_ptr); + if (reg2 == 0) { + reg2 = 1; + writeb_relaxed(reg2, cfg_ptr); + } + } else { + /*enable i2s mode in FPGA*/ + reg2 = readb_relaxed(cfg_ptr); + if (reg2 == 1) { + reg2 = 0; + writeb_relaxed(reg2, cfg_ptr); + } + } + iounmap(cfg_ptr); +} + +static unsigned fm_i2s_config_power_on[] = { + /*FM_I2S_SD*/ + GPIO_CFG(68, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + /*FM_I2S_WS*/ + GPIO_CFG(70, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + /*FM_I2S_SCK*/ + GPIO_CFG(71, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), +}; + +static unsigned fm_i2s_config_power_off[] = { + /*FM_I2S_SD*/ + GPIO_CFG(68, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /*FM_I2S_WS*/ + GPIO_CFG(70, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /*FM_I2S_SCK*/ + GPIO_CFG(71, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), +}; + +static unsigned bt_config_power_on[] = { + /*RFR*/ + GPIO_CFG(43, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + /*CTS*/ + GPIO_CFG(44, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + /*RX*/ + GPIO_CFG(45, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + /*TX*/ + GPIO_CFG(46, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), +}; +static unsigned bt_config_pcm_on[] = { + /*PCM_DOUT*/ + GPIO_CFG(68, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + /*PCM_DIN*/ + GPIO_CFG(69, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + /*PCM_SYNC*/ + GPIO_CFG(70, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + /*PCM_CLK*/ + GPIO_CFG(71, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), +}; +static unsigned bt_config_power_off[] = { + /*RFR*/ + GPIO_CFG(43, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /*CTS*/ + GPIO_CFG(44, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /*RX*/ + GPIO_CFG(45, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /*TX*/ + GPIO_CFG(46, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), +}; +static unsigned bt_config_pcm_off[] = { + /*PCM_DOUT*/ + GPIO_CFG(68, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /*PCM_DIN*/ + GPIO_CFG(69, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /*PCM_SYNC*/ + GPIO_CFG(70, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /*PCM_CLK*/ + GPIO_CFG(71, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), +}; + +static int config_i2s(int mode) +{ + int pin, rc = 0; + + if (mode == FM_I2S_ON) { + if (machine_is_msm7x27a_surf()) + config_pcm_i2s_mode(0); + pr_err("%s mode = FM_I2S_ON", __func__); + for (pin = 0; pin < ARRAY_SIZE(fm_i2s_config_power_on); + pin++) { + rc = gpio_tlmm_config( + fm_i2s_config_power_on[pin], + GPIO_CFG_ENABLE + ); + if (rc < 0) + return rc; + } + } else if (mode == FM_I2S_OFF) { + pr_err("%s mode = FM_I2S_OFF", __func__); + for (pin = 0; pin < ARRAY_SIZE(fm_i2s_config_power_off); + pin++) { + rc = gpio_tlmm_config( + fm_i2s_config_power_off[pin], + GPIO_CFG_ENABLE + ); + if (rc < 0) + return rc; + } + } + return rc; +} +static int config_pcm(int mode) +{ + int pin, rc = 0; + + if (mode == BT_PCM_ON) { + if (machine_is_msm7x27a_surf()) + config_pcm_i2s_mode(1); + pr_err("%s mode =BT_PCM_ON", __func__); + for (pin = 0; pin < ARRAY_SIZE(bt_config_pcm_on); + pin++) { + rc = gpio_tlmm_config(bt_config_pcm_on[pin], + GPIO_CFG_ENABLE); + if (rc < 0) + return rc; + } + } else if (mode == BT_PCM_OFF) { + pr_err("%s mode =BT_PCM_OFF", __func__); + for (pin = 0; pin < ARRAY_SIZE(bt_config_pcm_off); + pin++) { + rc = gpio_tlmm_config(bt_config_pcm_off[pin], + GPIO_CFG_ENABLE); + if (rc < 0) + return rc; + } + + } + + return rc; +} + +static int msm_bahama_setup_pcm_i2s(int mode) +{ + int fm_state = 0, bt_state = 0; + int rc = 0; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA}; + + fm_state = marimba_get_fm_status(&config); + bt_state = marimba_get_bt_status(&config); + + switch (mode) { + case BT_PCM_ON: + case BT_PCM_OFF: + if (!fm_state) + rc = config_pcm(mode); + break; + case FM_I2S_ON: + rc = config_i2s(mode); + break; + case FM_I2S_OFF: + if (bt_state) + rc = config_pcm(BT_PCM_ON); + else + rc = config_i2s(mode); + break; + default: + rc = -EIO; + pr_err("%s:Unsupported mode", __func__); + } + return rc; +} + +static struct vreg *fm_regulator; +static int fm_radio_setup(struct marimba_fm_platform_data *pdata) +{ + int rc = 0; + const char *id = "FMPW"; + uint32_t irqcfg; + + /* Voting for 1.8V Regulator */ + fm_regulator = vreg_get(NULL , "msme1"); + if (IS_ERR(fm_regulator)) { + pr_err("%s: vreg get failed with : (%ld)\n", + __func__, PTR_ERR(fm_regulator)); + return -EINVAL; + } + + /* Set the voltage level to 1.8V */ + rc = vreg_set_level(fm_regulator, 1800); + if (rc < 0) { + pr_err("%s: set regulator level failed with :(%d)\n", + __func__, rc); + goto fm_vreg_fail; + } + + /* Enabling the 1.8V regulator */ + rc = vreg_enable(fm_regulator); + if (rc) { + pr_err("%s: enable regulator failed with :(%d)\n", + __func__, rc); + goto fm_vreg_fail; + } + + /* Voting for 19.2MHz clock */ + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_D1, + PMAPP_CLOCK_VOTE_ON); + if (rc < 0) { + pr_err("%s: clock vote failed with :(%d)\n", + __func__, rc); + goto fm_clock_vote_fail; + } + + /* Configuring the FM GPIO */ + irqcfg = GPIO_CFG(FM_GPIO, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA); + + rc = gpio_tlmm_config(irqcfg, GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", + __func__, irqcfg, rc); + goto fm_gpio_config_fail; + } + + return 0; + +fm_gpio_config_fail: + pmapp_clock_vote(id, PMAPP_CLOCK_ID_D1, + PMAPP_CLOCK_VOTE_OFF); +fm_clock_vote_fail: + vreg_disable(fm_regulator); + +fm_vreg_fail: + vreg_put(fm_regulator); + + return rc; +}; + +static void fm_radio_shutdown(struct marimba_fm_platform_data *pdata) +{ + int rc; + const char *id = "FMPW"; + + /* Releasing the GPIO line used by FM */ + uint32_t irqcfg = GPIO_CFG(FM_GPIO, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_UP, + GPIO_CFG_2MA); + + rc = gpio_tlmm_config(irqcfg, GPIO_CFG_ENABLE); + if (rc) + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", + __func__, irqcfg, rc); + + /* Releasing the 1.8V Regulator */ + if (fm_regulator != NULL) { + rc = vreg_disable(fm_regulator); + + if (rc) + pr_err("%s: disable regulator failed:(%d)\n", + __func__, rc); + fm_regulator = NULL; + } + + /* Voting off the clock */ + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_D1, + PMAPP_CLOCK_VOTE_OFF); + + if (rc < 0) + pr_err("%s: voting off failed with :(%d)\n", + __func__, rc); +} + +static struct marimba_fm_platform_data marimba_fm_pdata = { + .fm_setup = fm_radio_setup, + .fm_shutdown = fm_radio_shutdown, + .irq = MSM_GPIO_TO_INT(FM_GPIO), + .vreg_s2 = NULL, + .vreg_xo_out = NULL, + /* Configuring the FM SoC as I2S Master */ + .is_fm_soc_i2s_master = true, + .config_i2s_gpio = msm_bahama_setup_pcm_i2s, +}; + +static struct platform_device msm_bt_power_device = { + .name = "bt_power", +}; + struct bahama_config_register { + u8 reg; + u8 value; + u8 mask; + }; +static const char * const vregs_bahama_name[] = { + "msme1", + "bt", +}; +static struct vreg *vregs_bahama[ARRAY_SIZE(vregs_bahama_name)]; + +static int bahama_bt(int on) +{ + + int rc = 0; + int i; + + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA}; + + struct bahama_variant_register { + const size_t size; + const struct bahama_config_register *set; + }; + + const struct bahama_config_register *p; + + u8 version; + + const struct bahama_config_register v10_bt_on[] = { + { 0xE9, 0x00, 0xFF }, + { 0xF4, 0x80, 0xFF }, + { 0xE4, 0x00, 0xFF }, + { 0xE5, 0x00, 0x0F }, +#ifdef CONFIG_WLAN + { 0xE6, 0x38, 0x7F }, + { 0xE7, 0x06, 0xFF }, +#endif + { 0xE9, 0x21, 0xFF }, + { 0x01, 0x0C, 0x1F }, + { 0x01, 0x08, 0x1F }, + }; + + const struct bahama_config_register v20_bt_on_fm_off[] = { + { 0x11, 0x0C, 0xFF }, + { 0x13, 0x01, 0xFF }, + { 0xF4, 0x80, 0xFF }, + { 0xF0, 0x00, 0xFF }, + { 0xE9, 0x00, 0xFF }, +#ifdef CONFIG_WLAN + { 0x81, 0x00, 0x7F }, + { 0x82, 0x00, 0xFF }, + { 0xE6, 0x38, 0x7F }, + { 0xE7, 0x06, 0xFF }, +#endif + { 0x8E, 0x15, 0xFF }, + { 0x8F, 0x15, 0xFF }, + { 0x90, 0x15, 0xFF }, + + { 0xE9, 0x21, 0xFF }, + }; + + const struct bahama_config_register v20_bt_on_fm_on[] = { + { 0x11, 0x0C, 0xFF }, + { 0x13, 0x01, 0xFF }, + { 0xF4, 0x86, 0xFF }, + { 0xF0, 0x06, 0xFF }, + { 0xE9, 0x00, 0xFF }, +#ifdef CONFIG_WLAN + { 0x81, 0x00, 0x7F }, + { 0x82, 0x00, 0xFF }, + { 0xE6, 0x38, 0x7F }, + { 0xE7, 0x06, 0xFF }, +#endif + { 0xE9, 0x21, 0xFF }, + }; + + const struct bahama_config_register v10_bt_off[] = { + { 0xE9, 0x00, 0xFF }, + }; + + const struct bahama_config_register v20_bt_off_fm_off[] = { + { 0xF4, 0x84, 0xFF }, + { 0xF0, 0x04, 0xFF }, + { 0xE9, 0x00, 0xFF } + }; + + const struct bahama_config_register v20_bt_off_fm_on[] = { + { 0xF4, 0x86, 0xFF }, + { 0xF0, 0x06, 0xFF }, + { 0xE9, 0x00, 0xFF } + }; + const struct bahama_variant_register bt_bahama[2][3] = { + { + { ARRAY_SIZE(v10_bt_off), v10_bt_off }, + { ARRAY_SIZE(v20_bt_off_fm_off), v20_bt_off_fm_off }, + { ARRAY_SIZE(v20_bt_off_fm_on), v20_bt_off_fm_on } + }, + { + { ARRAY_SIZE(v10_bt_on), v10_bt_on }, + { ARRAY_SIZE(v20_bt_on_fm_off), v20_bt_on_fm_off }, + { ARRAY_SIZE(v20_bt_on_fm_on), v20_bt_on_fm_on } + } + }; + + u8 offset = 0; /* index into bahama configs */ + on = on ? 1 : 0; + version = marimba_read_bahama_ver(&config); + + if (version == BAHAMA_VER_UNSUPPORTED) { + dev_err(&msm_bt_power_device.dev, + "%s: unsupported version\n", + __func__); + return -EIO; + } + + if (version == BAHAMA_VER_2_0) { + if (marimba_get_fm_status(&config)) + offset = 0x01; + } + + p = bt_bahama[on][version + offset].set; + + dev_info(&msm_bt_power_device.dev, + "%s: found version %d\n", __func__, version); + + for (i = 0; i < bt_bahama[on][version + offset].size; i++) { + u8 value = (p+i)->value; + rc = marimba_write_bit_mask(&config, + (p+i)->reg, + &value, + sizeof((p+i)->value), + (p+i)->mask); + if (rc < 0) { + dev_err(&msm_bt_power_device.dev, + "%s: reg %x write failed: %d\n", + __func__, (p+i)->reg, rc); + return rc; + } + dev_info(&msm_bt_power_device.dev, + "%s: reg 0x%02x write value 0x%02x mask 0x%02x\n", + __func__, (p+i)->reg, + value, (p+i)->mask); + value = 0; + rc = marimba_read_bit_mask(&config, + (p+i)->reg, &value, + sizeof((p+i)->value), (p+i)->mask); + if (rc < 0) + dev_err(&msm_bt_power_device.dev, "%s marimba_read_bit_mask- error", + __func__); + dev_info(&msm_bt_power_device.dev, + "%s: reg 0x%02x read value 0x%02x mask 0x%02x\n", + __func__, (p+i)->reg, + value, (p+i)->mask); + } + /* Update BT Status */ + if (on) + marimba_set_bt_status(&config, true); + else + marimba_set_bt_status(&config, false); + return rc; +} +static int bluetooth_switch_regulators(int on) +{ + int i, rc = 0; + + for (i = 0; i < ARRAY_SIZE(vregs_bahama_name); i++) { + if (!vregs_bahama[i]) { + pr_err("%s: vreg_get %s failed(%d)\n", + __func__, vregs_bahama_name[i], rc); + goto vreg_fail; + } + rc = on ? vreg_set_level(vregs_bahama[i], i ? 2900 : + 1800) : 0; + + if (rc < 0) { + pr_err("%s: vreg set level failed (%d)\n", + __func__, rc); + goto vreg_set_level_fail; + } + + rc = on ? vreg_enable(vregs_bahama[i]) : + vreg_disable(vregs_bahama[i]); + + if (rc < 0) { + pr_err("%s: vreg %s %s failed(%d)\n", + __func__, vregs_bahama_name[i], + on ? "enable" : "disable", rc); + goto vreg_fail; + } + } + return rc; + +vreg_fail: + while (i) { + if (on) + vreg_disable(vregs_bahama[--i]); + } +vreg_set_level_fail: + vreg_put(vregs_bahama[0]); + vreg_put(vregs_bahama[1]); + return rc; +} + +static unsigned int msm_bahama_setup_power(void) +{ + int rc = 0; + struct vreg *vreg_s3 = NULL; + + vreg_s3 = vreg_get(NULL, "msme1"); + if (IS_ERR(vreg_s3)) { + pr_err("%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_s3)); + return PTR_ERR(vreg_s3); + } + rc = vreg_set_level(vreg_s3, 1800); + if (rc < 0) { + pr_err("%s: vreg set level failed (%d)\n", + __func__, rc); + goto vreg_fail; + } + rc = vreg_enable(vreg_s3); + if (rc < 0) { + pr_err("%s: vreg enable failed (%d)\n", + __func__, rc); + goto vreg_fail; + } + + /*setup Bahama_sys_reset_n*/ + rc = gpio_request(GPIO_BT_SYS_REST_EN, "bahama sys_rst_n"); + if (rc < 0) { + pr_err("%s: gpio_request %d = %d\n", __func__, + GPIO_BT_SYS_REST_EN, rc); + goto vreg_fail; + } + rc = gpio_direction_output(GPIO_BT_SYS_REST_EN, 1); + if (rc < 0) { + pr_err("%s: gpio_direction_output %d = %d\n", __func__, + GPIO_BT_SYS_REST_EN, rc); + goto gpio_fail; + } + return rc; + +gpio_fail: + gpio_free(GPIO_BT_SYS_REST_EN); +vreg_fail: + vreg_put(vreg_s3); + return rc; +} + +static unsigned int msm_bahama_shutdown_power(int value) +{ + int rc = 0; + struct vreg *vreg_s3 = NULL; + + vreg_s3 = vreg_get(NULL, "msme1"); + if (IS_ERR(vreg_s3)) { + pr_err("%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_s3)); + return PTR_ERR(vreg_s3); + } + rc = vreg_disable(vreg_s3); + if (rc) { + pr_err("%s: vreg disable failed (%d)\n", + __func__, rc); + vreg_put(vreg_s3); + return rc; + } + + return rc; +} + + +static unsigned int msm_bahama_core_config(int type) +{ + int rc = 0; + + if (type == BAHAMA_ID) { + int i; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA }; + const struct bahama_config_register v20_init[] = { + /* reg, value, mask */ + { 0xF4, 0x84, 0xFF }, /* AREG */ + { 0xF0, 0x04, 0xFF } /* DREG */ + }; + if (marimba_read_bahama_ver(&config) == BAHAMA_VER_2_0) { + for (i = 0; i < ARRAY_SIZE(v20_init); i++) { + u8 value = v20_init[i].value; + rc = marimba_write_bit_mask(&config, + v20_init[i].reg, + &value, + sizeof(v20_init[i].value), + v20_init[i].mask); + if (rc < 0) { + pr_err("%s: reg %d write failed: %d\n", + __func__, v20_init[i].reg, rc); + return rc; + } + pr_debug("%s: reg 0x%02x value 0x%02x" + " mask 0x%02x\n", + __func__, v20_init[i].reg, + v20_init[i].value, v20_init[i].mask); + } + } + } + pr_debug("core type: %d\n", type); + return rc; +} + +static int bluetooth_power(int on) +{ + int pin, rc = 0; + const char *id = "BTPW"; + int cid = 0; + + cid = adie_get_detected_connectivity_type(); + if (cid != BAHAMA_ID) { + pr_err("%s: unexpected adie connectivity type: %d\n", + __func__, cid); + return -ENODEV; + } + if (on) { + /*setup power for BT SOC*/ + rc = bluetooth_switch_regulators(on); + if (rc < 0) { + pr_err("%s: bluetooth_switch_regulators rc = %d", + __func__, rc); + goto exit; + } + /*setup BT GPIO lines*/ + for (pin = 0; pin < ARRAY_SIZE(bt_config_power_on); + pin++) { + rc = gpio_tlmm_config(bt_config_power_on[pin], + GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", + __func__, + bt_config_power_on[pin], + rc); + goto fail_power; + } + } + /*Setup BT clocks*/ + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_D1, + PMAPP_CLOCK_VOTE_ON); + if (rc < 0) { + pr_err("Failed to vote for TCXO_D1 ON\n"); + goto fail_clock; + } + msleep(20); + + /*I2C config for Bahama*/ + rc = bahama_bt(1); + if (rc < 0) { + pr_err("%s: bahama_bt rc = %d", __func__, rc); + goto fail_i2c; + } + msleep(20); + + /*setup BT PCM lines*/ + rc = msm_bahama_setup_pcm_i2s(BT_PCM_ON); + if (rc < 0) { + pr_err("%s: msm_bahama_setup_pcm_i2s , rc =%d\n", + __func__, rc); + goto fail_power; + } + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_D1, + PMAPP_CLOCK_VOTE_PIN_CTRL); + if (rc < 0) + pr_err("%s:Pin Control Failed, rc = %d", + __func__, rc); + + } else { + rc = bahama_bt(0); + if (rc < 0) + pr_err("%s: bahama_bt rc = %d", __func__, rc); +fail_i2c: + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_D1, + PMAPP_CLOCK_VOTE_OFF); + if (rc < 0) + pr_err("%s: Failed to vote Off D1\n", __func__); +fail_clock: + for (pin = 0; pin < ARRAY_SIZE(bt_config_power_off); + pin++) { + rc = gpio_tlmm_config(bt_config_power_off[pin], + GPIO_CFG_ENABLE); + if (rc < 0) { + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", + __func__, bt_config_power_off[pin], rc); + } + } + rc = msm_bahama_setup_pcm_i2s(BT_PCM_OFF); + if (rc < 0) { + pr_err("%s: msm_bahama_setup_pcm_i2s, rc =%d\n", + __func__, rc); + } +fail_power: + rc = bluetooth_switch_regulators(0); + if (rc < 0) { + pr_err("%s: switch_regulators : rc = %d",\ + __func__, rc); + goto exit; + } + } + return rc; +exit: + pr_err("%s: failed with rc = %d", __func__, rc); + return rc; +} + +static int __init bt_power_init(void) +{ + int i, rc = 0; + for (i = 0; i < ARRAY_SIZE(vregs_bahama_name); i++) { + vregs_bahama[i] = vreg_get(NULL, + vregs_bahama_name[i]); + if (IS_ERR(vregs_bahama[i])) { + pr_err("%s: vreg get %s failed (%ld)\n", + __func__, vregs_bahama_name[i], + PTR_ERR(vregs_bahama[i])); + rc = PTR_ERR(vregs_bahama[i]); + goto vreg_get_fail; + } + } + + msm_bt_power_device.dev.platform_data = &bluetooth_power; + + return rc; + +vreg_get_fail: + while (i) + vreg_put(vregs_bahama[--i]); + return rc; +} + +static struct marimba_platform_data marimba_pdata = { + .slave_id[SLAVE_ID_BAHAMA_FM] = BAHAMA_SLAVE_ID_FM_ADDR, + .slave_id[SLAVE_ID_BAHAMA_QMEMBIST] = BAHAMA_SLAVE_ID_QMEMBIST_ADDR, + .bahama_setup = msm_bahama_setup_power, + .bahama_shutdown = msm_bahama_shutdown_power, + .bahama_core_config = msm_bahama_core_config, + .fm = &marimba_fm_pdata, +}; + +#endif + +#if defined(CONFIG_I2C) && defined(CONFIG_GPIO_SX150X) +static struct i2c_board_info core_exp_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1509q", 0x3e), + }, +}; +static struct i2c_board_info cam_exp_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1508q", 0x22), + .platform_data = &sx150x_data[SX150X_CAM], + }, +}; +#endif +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) +static struct i2c_board_info bahama_devices[] = { +{ + I2C_BOARD_INFO("marimba", 0xc), + .platform_data = &marimba_pdata, +}, +}; +#endif + +#if defined(CONFIG_I2C) && defined(CONFIG_GPIO_SX150X) +static void __init register_i2c_devices(void) +{ + + i2c_register_board_info(MSM_GSBI0_QUP_I2C_BUS_ID, + cam_exp_i2c_info, + ARRAY_SIZE(cam_exp_i2c_info)); + + if (machine_is_msm7x27a_surf()) + sx150x_data[SX150X_CORE].io_open_drain_ena = 0xe0f0; + + core_exp_i2c_info[0].platform_data = + &sx150x_data[SX150X_CORE]; + + i2c_register_board_info(MSM_GSBI1_QUP_I2C_BUS_ID, + core_exp_i2c_info, + ARRAY_SIZE(core_exp_i2c_info)); +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) + i2c_register_board_info(MSM_GSBI1_QUP_I2C_BUS_ID, + bahama_devices, + ARRAY_SIZE(bahama_devices)); +#endif +} +#endif + +static struct msm_gpio qup_i2c_gpios_io[] = { + { GPIO_CFG(60, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "qup_scl" }, + { GPIO_CFG(61, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "qup_sda" }, + { GPIO_CFG(131, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "qup_scl" }, + { GPIO_CFG(132, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "qup_sda" }, +}; + +static struct msm_gpio qup_i2c_gpios_hw[] = { + { GPIO_CFG(60, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "qup_scl" }, + { GPIO_CFG(61, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "qup_sda" }, + { GPIO_CFG(131, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "qup_scl" }, + { GPIO_CFG(132, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "qup_sda" }, +}; + +static void gsbi_qup_i2c_gpio_config(int adap_id, int config_type) +{ + int rc; + + if (adap_id < 0 || adap_id > 1) + return; + + /* Each adapter gets 2 lines from the table */ + if (config_type) + rc = msm_gpios_request_enable(&qup_i2c_gpios_hw[adap_id*2], 2); + else + rc = msm_gpios_request_enable(&qup_i2c_gpios_io[adap_id*2], 2); + if (rc < 0) + pr_err("QUP GPIO request/enable failed: %d\n", rc); +} + +static struct msm_i2c_platform_data msm_gsbi0_qup_i2c_pdata = { + .clk_freq = 100000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_qup_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm_gsbi1_qup_i2c_pdata = { + .clk_freq = 100000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_qup_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +#ifdef CONFIG_ARCH_MSM7X27A +#define MSM_PMEM_MDP_SIZE 0x1DD1000 +#define MSM_PMEM_ADSP_SIZE 0x1000000 + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +#define MSM_FB_SIZE 0x260000 +#else +#define MSM_FB_SIZE 0x195000 +#endif + +#endif + +static struct android_usb_platform_data android_usb_pdata = { + .update_pid_and_serial_num = usb_diag_update_pid_and_serial_num, +}; + +static struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &android_usb_pdata, + }, +}; + +#ifdef CONFIG_USB_EHCI_MSM_72K +static void msm_hsusb_vbus_power(unsigned phy_info, int on) +{ + int rc = 0; + unsigned gpio; + + gpio = GPIO_HOST_VBUS_EN; + + rc = gpio_request(gpio, "i2c_host_vbus_en"); + if (rc < 0) { + pr_err("failed to request %d GPIO\n", gpio); + return; + } + gpio_direction_output(gpio, !!on); + gpio_set_value_cansleep(gpio, !!on); + gpio_free(gpio); +} + +static struct msm_usb_host_platform_data msm_usb_host_pdata = { + .phy_info = (USB_PHY_INTEGRATED | USB_PHY_MODEL_45NM), +}; + +static void __init msm7x2x_init_host(void) +{ + msm_add_host(0, &msm_usb_host_pdata); +} +#endif + +#ifdef CONFIG_USB_MSM_OTG_72K +static int hsusb_rpc_connect(int connect) +{ + if (connect) + return msm_hsusb_rpc_connect(); + else + return msm_hsusb_rpc_close(); +} + +static struct vreg *vreg_3p3; +static int msm_hsusb_ldo_init(int init) +{ + if (init) { + vreg_3p3 = vreg_get(NULL, "usb"); + if (IS_ERR(vreg_3p3)) + return PTR_ERR(vreg_3p3); + } else + vreg_put(vreg_3p3); + + return 0; +} + +static int msm_hsusb_ldo_enable(int enable) +{ + static int ldo_status; + + if (!vreg_3p3 || IS_ERR(vreg_3p3)) + return -ENODEV; + + if (ldo_status == enable) + return 0; + + ldo_status = enable; + + if (enable) + return vreg_enable(vreg_3p3); + + return vreg_disable(vreg_3p3); +} + +#ifndef CONFIG_USB_EHCI_MSM_72K +static int msm_hsusb_pmic_notif_init(void (*callback)(int online), int init) +{ + int ret = 0; + + if (init) + ret = msm_pm_app_rpc_init(callback); + else + msm_pm_app_rpc_deinit(callback); + + return ret; +} +#endif + +static struct msm_otg_platform_data msm_otg_pdata = { +#ifndef CONFIG_USB_EHCI_MSM_72K + .pmic_vbus_notif_init = msm_hsusb_pmic_notif_init, +#else + .vbus_power = msm_hsusb_vbus_power, +#endif + .rpc_connect = hsusb_rpc_connect, + .core_clk = 1, + .pemp_level = PRE_EMPHASIS_WITH_20_PERCENT, + .cdr_autoreset = CDR_AUTO_RESET_DISABLE, + .drv_ampl = HS_DRV_AMPLITUDE_DEFAULT, + .se1_gating = SE1_GATING_DISABLE, + .ldo_init = msm_hsusb_ldo_init, + .ldo_enable = msm_hsusb_ldo_enable, + .chg_init = hsusb_chg_init, + .chg_connected = hsusb_chg_connected, + .chg_vbus_draw = hsusb_chg_vbus_draw, +}; +#endif + +static struct msm_hsusb_gadget_platform_data msm_gadget_pdata = { + .is_phy_status_timer_on = 1, +}; + +static struct resource smc91x_resources[] = { + [0] = { + .start = 0x90000300, + .end = 0x900003ff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MSM_GPIO_TO_INT(4), + .end = MSM_GPIO_TO_INT(4), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device smc91x_device = { + .name = "smc91x", + .id = 0, + .num_resources = ARRAY_SIZE(smc91x_resources), + .resource = smc91x_resources, +}; + +#if (defined(CONFIG_MMC_MSM_SDC1_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC2_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC3_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC4_SUPPORT)) + +static unsigned long vreg_sts, gpio_sts; +static struct vreg *vreg_mmc; +static struct vreg *vreg_emmc; + +struct sdcc_vreg { + struct vreg *vreg_data; + unsigned level; +}; + +static struct sdcc_vreg sdcc_vreg_data[4]; + +struct sdcc_gpio { + struct msm_gpio *cfg_data; + uint32_t size; + struct msm_gpio *sleep_cfg_data; +}; + +static struct msm_gpio sdc1_cfg_data[] = { + {GPIO_CFG(51, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc1_dat_3"}, + {GPIO_CFG(52, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc1_dat_2"}, + {GPIO_CFG(53, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc1_dat_1"}, + {GPIO_CFG(54, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc1_dat_0"}, + {GPIO_CFG(55, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc1_cmd"}, + {GPIO_CFG(56, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "sdc1_clk"}, +}; + +static struct msm_gpio sdc2_cfg_data[] = { + {GPIO_CFG(62, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "sdc2_clk"}, + {GPIO_CFG(63, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc2_cmd"}, + {GPIO_CFG(64, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc2_dat_3"}, + {GPIO_CFG(65, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc2_dat_2"}, + {GPIO_CFG(66, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc2_dat_1"}, + {GPIO_CFG(67, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc2_dat_0"}, +}; + +static struct msm_gpio sdc2_sleep_cfg_data[] = { + {GPIO_CFG(62, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "sdc2_clk"}, + {GPIO_CFG(63, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "sdc2_cmd"}, + {GPIO_CFG(64, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "sdc2_dat_3"}, + {GPIO_CFG(65, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "sdc2_dat_2"}, + {GPIO_CFG(66, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "sdc2_dat_1"}, + {GPIO_CFG(67, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "sdc2_dat_0"}, +}; +static struct msm_gpio sdc3_cfg_data[] = { + {GPIO_CFG(88, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "sdc3_clk"}, + {GPIO_CFG(89, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc3_cmd"}, + {GPIO_CFG(90, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc3_dat_3"}, + {GPIO_CFG(91, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc3_dat_2"}, + {GPIO_CFG(92, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc3_dat_1"}, + {GPIO_CFG(93, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc3_dat_0"}, +#ifdef CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT + {GPIO_CFG(19, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc3_dat_7"}, + {GPIO_CFG(20, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc3_dat_6"}, + {GPIO_CFG(21, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc3_dat_5"}, + {GPIO_CFG(108, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc3_dat_4"}, +#endif +}; + +static struct msm_gpio sdc4_cfg_data[] = { + {GPIO_CFG(19, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc4_dat_3"}, + {GPIO_CFG(20, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc4_dat_2"}, + {GPIO_CFG(21, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc4_dat_1"}, + {GPIO_CFG(107, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc4_cmd"}, + {GPIO_CFG(108, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), + "sdc4_dat_0"}, + {GPIO_CFG(109, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), + "sdc4_clk"}, +}; + +static struct sdcc_gpio sdcc_cfg_data[] = { + { + .cfg_data = sdc1_cfg_data, + .size = ARRAY_SIZE(sdc1_cfg_data), + }, + { + .cfg_data = sdc2_cfg_data, + .size = ARRAY_SIZE(sdc2_cfg_data), + .sleep_cfg_data = sdc2_sleep_cfg_data, + }, + { + .cfg_data = sdc3_cfg_data, + .size = ARRAY_SIZE(sdc3_cfg_data), + }, + { + .cfg_data = sdc4_cfg_data, + .size = ARRAY_SIZE(sdc4_cfg_data), + }, +}; + +static int msm_sdcc_setup_gpio(int dev_id, unsigned int enable) +{ + int rc = 0; + struct sdcc_gpio *curr; + + curr = &sdcc_cfg_data[dev_id - 1]; + if (!(test_bit(dev_id, &gpio_sts)^enable)) + return rc; + + if (enable) { + set_bit(dev_id, &gpio_sts); + rc = msm_gpios_request_enable(curr->cfg_data, curr->size); + if (rc) + pr_err("%s: Failed to turn on GPIOs for slot %d\n", + __func__, dev_id); + } else { + clear_bit(dev_id, &gpio_sts); + if (curr->sleep_cfg_data) { + rc = msm_gpios_enable(curr->sleep_cfg_data, curr->size); + msm_gpios_free(curr->sleep_cfg_data, curr->size); + return rc; + } + msm_gpios_disable_free(curr->cfg_data, curr->size); + } + return rc; +} + +static int msm_sdcc_setup_vreg(int dev_id, unsigned int enable) +{ + int rc = 0; + struct sdcc_vreg *curr; + + curr = &sdcc_vreg_data[dev_id - 1]; + + if (!(test_bit(dev_id, &vreg_sts)^enable)) + return rc; + + if (enable) { + set_bit(dev_id, &vreg_sts); + rc = vreg_set_level(curr->vreg_data, curr->level); + if (rc) + pr_err("%s: vreg_set_level() = %d\n", __func__, rc); + + rc = vreg_enable(curr->vreg_data); + if (rc) + pr_err("%s: vreg_enable() = %d\n", __func__, rc); + } else { + clear_bit(dev_id, &vreg_sts); + rc = vreg_disable(curr->vreg_data); + if (rc) + pr_err("%s: vreg_disable() = %d\n", __func__, rc); + } + return rc; +} + +static uint32_t msm_sdcc_setup_power(struct device *dv, unsigned int vdd) +{ + int rc = 0; + struct platform_device *pdev; + + pdev = container_of(dv, struct platform_device, dev); + + rc = msm_sdcc_setup_gpio(pdev->id, !!vdd); + if (rc) + goto out; + + rc = msm_sdcc_setup_vreg(pdev->id, !!vdd); +out: + return rc; +} + +#define GPIO_SDC1_HW_DET 85 + +#if defined(CONFIG_MMC_MSM_SDC1_SUPPORT) \ + && defined(CONFIG_MMC_MSM_CARD_HW_DETECTION) +static unsigned int msm7x2xa_sdcc_slot_status(struct device *dev) +{ + int status; + + status = gpio_tlmm_config(GPIO_CFG(GPIO_SDC1_HW_DET, 2, GPIO_CFG_INPUT, + GPIO_CFG_PULL_UP, GPIO_CFG_8MA), GPIO_CFG_ENABLE); + if (status) + pr_err("%s:Failed to configure tlmm for GPIO %d\n", __func__, + GPIO_SDC1_HW_DET); + + status = gpio_request(GPIO_SDC1_HW_DET, "SD_HW_Detect"); + if (status) { + pr_err("%s:Failed to request GPIO %d\n", __func__, + GPIO_SDC1_HW_DET); + } else { + status = gpio_direction_input(GPIO_SDC1_HW_DET); + if (!status) + status = gpio_get_value(GPIO_SDC1_HW_DET); + gpio_free(GPIO_SDC1_HW_DET); + } + return status; +} +#endif + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static struct mmc_platform_data sdc1_plat_data = { + .ocr_mask = MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + .status = msm7x2xa_sdcc_slot_status, + .status_irq = MSM_GPIO_TO_INT(GPIO_SDC1_HW_DET), + .irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, +#endif +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static struct mmc_platform_data sdc2_plat_data = { + /* + * SDC2 supports only 1.8V, claim for 2.85V range is just + * for allowing buggy cards who advertise 2.8V even though + * they can operate at 1.8V supply. + */ + .ocr_mask = MMC_VDD_28_29 | MMC_VDD_165_195, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#ifdef CONFIG_MMC_MSM_SDIO_SUPPORT + .sdiowakeup_irq = MSM_GPIO_TO_INT(66), +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, +#ifdef CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static struct mmc_platform_data sdc3_plat_data = { + .ocr_mask = MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, +#ifdef CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT + .mmc_bus_width = MMC_CAP_8_BIT_DATA, +#else + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 1, +}; +#endif + +#if (defined(CONFIG_MMC_MSM_SDC4_SUPPORT)\ + && !defined(CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT)) +static struct mmc_platform_data sdc4_plat_data = { + .ocr_mask = MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, +}; +#endif +#endif + +#ifdef CONFIG_SERIAL_MSM_HS +static struct msm_serial_hs_platform_data msm_uart_dm1_pdata = { + .inject_rx_on_wakeup = 1, + .rx_to_inject = 0xFD, +}; +#endif +static struct msm_pm_platform_data msm7x27a_pm_data[MSM_PM_SLEEP_MODE_NR] = { + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 16000, + .residency = 20000, + }, + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 12000, + .residency = 20000, + }, + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 1, + .latency = 2000, + .residency = 0, + }, + [MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 2, + .residency = 0, + }, +}; + +static struct android_pmem_platform_data android_pmem_adsp_pdata = { + .name = "pmem_adsp", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, + .memory_type = MEMTYPE_EBI1, +}; + +static struct platform_device android_pmem_adsp_device = { + .name = "android_pmem", + .id = 1, + .dev = { .platform_data = &android_pmem_adsp_pdata }, +}; + +static unsigned pmem_mdp_size = MSM_PMEM_MDP_SIZE; +static int __init pmem_mdp_size_setup(char *p) +{ + pmem_mdp_size = memparse(p, NULL); + return 0; +} + +early_param("pmem_mdp_size", pmem_mdp_size_setup); + +static unsigned pmem_adsp_size = MSM_PMEM_ADSP_SIZE; +static int __init pmem_adsp_size_setup(char *p) +{ + pmem_adsp_size = memparse(p, NULL); + return 0; +} + +early_param("pmem_adsp_size", pmem_adsp_size_setup); + +static unsigned fb_size = MSM_FB_SIZE; +static int __init fb_size_setup(char *p) +{ + fb_size = memparse(p, NULL); + return 0; +} + +early_param("fb_size", fb_size_setup); + + +#define LCDC_CONFIG_PROC 21 +#define LCDC_UN_CONFIG_PROC 22 +#define LCDC_API_PROG 0x30000066 +#define LCDC_API_VERS 0x00010001 + +static struct msm_rpc_endpoint *lcdc_ep; + +static int msm_fb_lcdc_config(int on) +{ + int rc = 0; + struct rpc_request_hdr hdr; + + if (on) + pr_info("lcdc config\n"); + else + pr_info("lcdc un-config\n"); + + lcdc_ep = msm_rpc_connect_compatible(LCDC_API_PROG, LCDC_API_VERS, 0); + + if (IS_ERR(lcdc_ep)) { + printk(KERN_ERR "%s: msm_rpc_connect failed! rc = %ld\n", + __func__, PTR_ERR(lcdc_ep)); + return -EINVAL; + } + + rc = msm_rpc_call(lcdc_ep, + (on) ? LCDC_CONFIG_PROC : LCDC_UN_CONFIG_PROC, + &hdr, sizeof(hdr), 5 * HZ); + + if (rc) + printk(KERN_ERR + "%s: msm_rpc_call failed! rc = %d\n", __func__, rc); + + msm_rpc_close(lcdc_ep); + return rc; +} + +static const char * const msm_fb_lcdc_vreg[] = { + "gp2", + "msme1", +}; + +static const int msm_fb_lcdc_vreg_mV[] = { + 2850, + 1800, +}; + +struct vreg *lcdc_vreg[ARRAY_SIZE(msm_fb_lcdc_vreg)]; + +static uint32_t lcdc_gpio_initialized; + +static void lcdc_toshiba_gpio_init(void) +{ + int i, rc = 0; + if (!lcdc_gpio_initialized) { + if (gpio_request(GPIO_SPI_CLK, "spi_clk")) { + pr_err("failed to request gpio spi_clk\n"); + return; + } + if (gpio_request(GPIO_SPI_CS0_N, "spi_cs")) { + pr_err("failed to request gpio spi_cs0_N\n"); + goto fail_gpio6; + } + if (gpio_request(GPIO_SPI_MOSI, "spi_mosi")) { + pr_err("failed to request gpio spi_mosi\n"); + goto fail_gpio5; + } + if (gpio_request(GPIO_SPI_MISO, "spi_miso")) { + pr_err("failed to request gpio spi_miso\n"); + goto fail_gpio4; + } + if (gpio_request(GPIO_DISPLAY_PWR_EN, "gpio_disp_pwr")) { + pr_err("failed to request gpio_disp_pwr\n"); + goto fail_gpio3; + } + if (gpio_request(GPIO_BACKLIGHT_EN, "gpio_bkl_en")) { + pr_err("failed to request gpio_bkl_en\n"); + goto fail_gpio2; + } + pmapp_disp_backlight_init(); + + for (i = 0; i < ARRAY_SIZE(msm_fb_lcdc_vreg); i++) { + lcdc_vreg[i] = vreg_get(0, msm_fb_lcdc_vreg[i]); + + rc = vreg_set_level(lcdc_vreg[i], + msm_fb_lcdc_vreg_mV[i]); + + if (rc < 0) { + pr_err("%s: set regulator level failed " + "with :(%d)\n", __func__, rc); + goto fail_gpio1; + } + } + lcdc_gpio_initialized = 1; + } + return; + +fail_gpio1: + for (; i > 0; i--) + vreg_put(lcdc_vreg[i - 1]); + + gpio_free(GPIO_BACKLIGHT_EN); +fail_gpio2: + gpio_free(GPIO_DISPLAY_PWR_EN); +fail_gpio3: + gpio_free(GPIO_SPI_MISO); +fail_gpio4: + gpio_free(GPIO_SPI_MOSI); +fail_gpio5: + gpio_free(GPIO_SPI_CS0_N); +fail_gpio6: + gpio_free(GPIO_SPI_CLK); + lcdc_gpio_initialized = 0; +} + +static uint32_t lcdc_gpio_table[] = { + GPIO_SPI_CLK, + GPIO_SPI_CS0_N, + GPIO_SPI_MOSI, + GPIO_DISPLAY_PWR_EN, + GPIO_BACKLIGHT_EN, + GPIO_SPI_MISO, +}; + +static void config_lcdc_gpio_table(uint32_t *table, int len, unsigned enable) +{ + int n; + + if (lcdc_gpio_initialized) { + /* All are IO Expander GPIOs */ + for (n = 0; n < (len - 1); n++) + gpio_direction_output(table[n], 1); + } +} + +static void lcdc_toshiba_config_gpios(int enable) +{ + config_lcdc_gpio_table(lcdc_gpio_table, + ARRAY_SIZE(lcdc_gpio_table), enable); +} + +static int msm_fb_lcdc_power_save(int on) +{ + int i, rc = 0; + /* Doing the init of the LCDC GPIOs very late as they are from + an I2C-controlled IO Expander */ + lcdc_toshiba_gpio_init(); + + if (lcdc_gpio_initialized) { + gpio_set_value_cansleep(GPIO_DISPLAY_PWR_EN, on); + gpio_set_value_cansleep(GPIO_BACKLIGHT_EN, on); + + for (i = 0; i < ARRAY_SIZE(msm_fb_lcdc_vreg); i++) { + if (on) { + rc = vreg_enable(lcdc_vreg[i]); + + if (rc) { + printk(KERN_ERR "vreg_enable: %s vreg" + "operation failed\n", + msm_fb_lcdc_vreg[i]); + goto lcdc_vreg_fail; + } + } else { + rc = vreg_disable(lcdc_vreg[i]); + + if (rc) { + printk(KERN_ERR "vreg_disable: %s vreg " + "operation failed\n", + msm_fb_lcdc_vreg[i]); + goto lcdc_vreg_fail; + } + } + } + } + + return rc; + +lcdc_vreg_fail: + if (on) { + for (; i > 0; i--) + vreg_disable(lcdc_vreg[i - 1]); + } else { + for (; i > 0; i--) + vreg_enable(lcdc_vreg[i - 1]); + } + +return rc; + +} + + +static int lcdc_toshiba_set_bl(int level) +{ + int ret; + + ret = pmapp_disp_backlight_set_brightness(level); + if (ret) + pr_err("%s: can't set lcd backlight!\n", __func__); + + return ret; +} + + +static struct lcdc_platform_data lcdc_pdata = { + .lcdc_gpio_config = msm_fb_lcdc_config, + .lcdc_power_save = msm_fb_lcdc_power_save, +}; + +static int lcd_panel_spi_gpio_num[] = { + GPIO_SPI_MOSI, /* spi_sdi */ + GPIO_SPI_MISO, /* spi_sdoi */ + GPIO_SPI_CLK, /* spi_clk */ + GPIO_SPI_CS0_N, /* spi_cs */ +}; + +static struct msm_panel_common_pdata lcdc_toshiba_panel_data = { + .panel_config_gpio = lcdc_toshiba_config_gpios, + .pmic_backlight = lcdc_toshiba_set_bl, + .gpio_num = lcd_panel_spi_gpio_num, +}; + +static struct platform_device lcdc_toshiba_panel_device = { + .name = "lcdc_toshiba_fwvga_pt", + .id = 0, + .dev = { + .platform_data = &lcdc_toshiba_panel_data, + } +}; + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; + +static int msm_fb_detect_panel(const char *name) +{ + int ret = -EPERM; + + if (machine_is_msm7x27a_surf()) { + if (!strncmp(name, "lcdc_toshiba_fwvga_pt", 21)) + ret = 0; + } else { + ret = -ENODEV; + } + + return ret; +} + +static struct msm_fb_platform_data msm_fb_pdata = { + .detect_client = msm_fb_detect_panel, +}; + +static struct platform_device msm_fb_device = { + .name = "msm_fb", + .id = 0, + .num_resources = ARRAY_SIZE(msm_fb_resources), + .resource = msm_fb_resources, + .dev = { + .platform_data = &msm_fb_pdata, + } +}; + +#ifdef CONFIG_FB_MSM_MIPI_DSI +static int mipi_renesas_set_bl(int level) +{ + int ret; + + ret = pmapp_disp_backlight_set_brightness(level); + + if (ret) + pr_err("%s: can't set lcd backlight!\n", __func__); + + return ret; +} + +static struct msm_panel_common_pdata mipi_renesas_pdata = { + .pmic_backlight = mipi_renesas_set_bl, +}; + + +static struct platform_device mipi_dsi_renesas_panel_device = { + .name = "mipi_renesas", + .id = 0, + .dev = { + .platform_data = &mipi_renesas_pdata, + } +}; +#endif + +static void __init msm7x27a_init_mmc(void) +{ + vreg_emmc = vreg_get(NULL, "emmc"); + if (IS_ERR(vreg_emmc)) { + pr_err("%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_emmc)); + return; + } + + vreg_mmc = vreg_get(NULL, "mmc"); + if (IS_ERR(vreg_mmc)) { + pr_err("%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_mmc)); + return; + } + + /* eMMC slot */ +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + sdcc_vreg_data[2].vreg_data = vreg_emmc; + sdcc_vreg_data[2].level = 3000; + msm_add_sdcc(3, &sdc3_plat_data); +#endif + /* Micro-SD slot */ +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + sdcc_vreg_data[0].vreg_data = vreg_mmc; + sdcc_vreg_data[0].level = 2850; + msm_add_sdcc(1, &sdc1_plat_data); +#endif + /* SDIO WLAN slot */ +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + sdcc_vreg_data[1].vreg_data = vreg_mmc; + sdcc_vreg_data[1].level = 2850; + msm_add_sdcc(2, &sdc2_plat_data); +#endif + /* Not Used */ +#if (defined(CONFIG_MMC_MSM_SDC4_SUPPORT)\ + && !defined(CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT)) + sdcc_vreg_data[3].vreg_data = vreg_mmc; + sdcc_vreg_data[3].level = 2850; + msm_add_sdcc(4, &sdc4_plat_data); +#endif +} +#define SND(desc, num) { .name = #desc, .id = num } +static struct snd_endpoint snd_endpoints_list[] = { + SND(HANDSET, 0), + SND(MONO_HEADSET, 2), + SND(HEADSET, 3), + SND(SPEAKER, 6), + SND(TTY_HEADSET, 8), + SND(TTY_VCO, 9), + SND(TTY_HCO, 10), + SND(BT, 12), + SND(IN_S_SADC_OUT_HANDSET, 16), + SND(IN_S_SADC_OUT_SPEAKER_PHONE, 25), + SND(FM_DIGITAL_STEREO_HEADSET, 26), + SND(FM_DIGITAL_SPEAKER_PHONE, 27), + SND(FM_DIGITAL_BT_A2DP_HEADSET, 28), + SND(CURRENT, 34), + SND(FM_ANALOG_STEREO_HEADSET, 35), + SND(FM_ANALOG_STEREO_HEADSET_CODEC, 36), +}; +#undef SND + +static struct msm_snd_endpoints msm_device_snd_endpoints = { + .endpoints = snd_endpoints_list, + .num = sizeof(snd_endpoints_list) / sizeof(struct snd_endpoint) +}; + +static struct platform_device msm_device_snd = { + .name = "msm_snd", + .id = -1, + .dev = { + .platform_data = &msm_device_snd_endpoints + }, +}; + +#define DEC0_FORMAT ((1<= 0; i--) + gpio_tlmm_config(camera_off_gpio_table[i], + GPIO_CFG_ENABLE); + break; + } + } + return rc; +} + +static struct msm_camera_sensor_info msm_camera_sensor_s5k4e1_data; +static struct msm_camera_sensor_info msm_camera_sensor_ov9726_data; +static int config_camera_on_gpios_rear(void) +{ + int rc = 0; + + if (machine_is_msm7x27a_ffa()) + msm_camera_vreg_config(1); + + rc = config_gpio_table(camera_on_gpio_table, + ARRAY_SIZE(camera_on_gpio_table)); + if (rc < 0) { + pr_err("%s: CAMSENSOR gpio table request" + "failed\n", __func__); + return rc; + } + + return rc; +} + +static void config_camera_off_gpios_rear(void) +{ + if (machine_is_msm7x27a_ffa()) + msm_camera_vreg_config(0); + + config_gpio_table(camera_off_gpio_table, + ARRAY_SIZE(camera_off_gpio_table)); +} + +static int config_camera_on_gpios_front(void) +{ + int rc = 0; + + if (machine_is_msm7x27a_ffa()) + msm_camera_vreg_config(1); + + rc = config_gpio_table(camera_on_gpio_table, + ARRAY_SIZE(camera_on_gpio_table)); + if (rc < 0) { + pr_err("%s: CAMSENSOR gpio table request" + "failed\n", __func__); + return rc; + } + + return rc; +} + +static void config_camera_off_gpios_front(void) +{ + if (machine_is_msm7x27a_ffa()) + msm_camera_vreg_config(0); + + config_gpio_table(camera_off_gpio_table, + ARRAY_SIZE(camera_off_gpio_table)); +} + +struct msm_camera_device_platform_data msm_camera_device_data_rear = { + .camera_gpio_on = config_camera_on_gpios_rear, + .camera_gpio_off = config_camera_off_gpios_rear, + .ioext.csiphy = 0xA1000000, + .ioext.csisz = 0x00100000, + .ioext.csiirq = INT_CSI_IRQ_1, + .ioclk.mclk_clk_rate = 24000000, + .ioclk.vfe_clk_rate = 192000000, + .ioext.appphy = MSM_CLK_CTL_PHYS, + .ioext.appsz = MSM_CLK_CTL_SIZE, +}; + +struct msm_camera_device_platform_data msm_camera_device_data_front = { + .camera_gpio_on = config_camera_on_gpios_front, + .camera_gpio_off = config_camera_off_gpios_front, + .ioext.csiphy = 0xA0F00000, + .ioext.csisz = 0x00100000, + .ioext.csiirq = INT_CSI_IRQ_0, + .ioclk.mclk_clk_rate = 24000000, + .ioclk.vfe_clk_rate = 192000000, + .ioext.appphy = MSM_CLK_CTL_PHYS, + .ioext.appsz = MSM_CLK_CTL_SIZE, +}; + +#ifdef CONFIG_S5K4E1 +static struct msm_camera_sensor_platform_info s5k4e1_sensor_7627a_info = { + .mount_angle = 90 +}; + +static struct msm_camera_sensor_flash_data flash_s5k4e1 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_s5k4e1_data = { + .sensor_name = "s5k4e1", + .sensor_reset_enable = 1, + .sensor_reset = GPIO_CAM_GP_CAMIF_RESET_N, + .sensor_pwd = 85, + .vcm_pwd = GPIO_CAM_GP_CAM_PWDN, + .vcm_enable = 1, + .pdata = &msm_camera_device_data_rear, + .flash_data = &flash_s5k4e1, + .sensor_platform_info = &s5k4e1_sensor_7627a_info, + .csi_if = 1 +}; + +static struct platform_device msm_camera_sensor_s5k4e1 = { + .name = "msm_camera_s5k4e1", + .dev = { + .platform_data = &msm_camera_sensor_s5k4e1_data, + }, +}; +#endif + +#ifdef CONFIG_IMX072 +static struct msm_camera_sensor_platform_info imx072_sensor_7627a_info = { + .mount_angle = 90 +}; + +static struct msm_camera_sensor_flash_data flash_imx072 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_imx072_data = { + .sensor_name = "imx072", + .sensor_reset_enable = 1, + .sensor_reset = GPIO_CAM_GP_CAMIF_RESET_N, /* TODO 106,*/ + .sensor_pwd = 85, + .vcm_pwd = GPIO_CAM_GP_CAM_PWDN, + .vcm_enable = 1, + .pdata = &msm_camera_device_data_rear, + .flash_data = &flash_imx072, + .sensor_platform_info = &imx072_sensor_7627a_info, + .csi_if = 1 +}; + +static struct platform_device msm_camera_sensor_imx072 = { + .name = "msm_camera_imx072", + .dev = { + .platform_data = &msm_camera_sensor_imx072_data, + }, +}; +#endif + +#ifdef CONFIG_WEBCAM_OV9726 +static struct msm_camera_sensor_platform_info ov9726_sensor_7627a_info = { + .mount_angle = 90 +}; + +static struct msm_camera_sensor_flash_data flash_ov9726 = { + .flash_type = MSM_CAMERA_FLASH_NONE, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov9726_data = { + .sensor_name = "ov9726", + .sensor_reset_enable = 0, + .sensor_reset = GPIO_CAM_GP_CAM1MP_XCLR, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data_front, + .flash_data = &flash_ov9726, + .sensor_platform_info = &ov9726_sensor_7627a_info, + .csi_if = 1 +}; + +static struct platform_device msm_camera_sensor_ov9726 = { + .name = "msm_camera_ov9726", + .dev = { + .platform_data = &msm_camera_sensor_ov9726_data, + }, +}; +#endif + +#ifdef CONFIG_MT9E013 +static struct msm_camera_sensor_platform_info mt9e013_sensor_7627a_info = { + .mount_angle = 90 +}; + +static struct msm_camera_sensor_flash_data flash_mt9e013 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9e013_data = { + .sensor_name = "mt9e013", + .sensor_reset = 0, + .sensor_reset_enable = 1, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data_rear, + .flash_data = &flash_mt9e013, + .sensor_platform_info = &mt9e013_sensor_7627a_info, + .csi_if = 1 +}; + +static struct platform_device msm_camera_sensor_mt9e013 = { + .name = "msm_camera_mt9e013", + .dev = { + .platform_data = &msm_camera_sensor_mt9e013_data, + }, +}; +#endif + +static struct i2c_board_info i2c_camera_devices[] = { + #ifdef CONFIG_S5K4E1 + { + I2C_BOARD_INFO("s5k4e1", 0x36), + }, + { + I2C_BOARD_INFO("s5k4e1_af", 0x8c >> 1), + }, + #endif + #ifdef CONFIG_WEBCAM_OV9726 + { + I2C_BOARD_INFO("ov9726", 0x10), + }, + #endif + #ifdef CONFIG_IMX072 + { + I2C_BOARD_INFO("imx072", 0x34), + }, + #endif + #ifdef CONFIG_MT9E013 + { + I2C_BOARD_INFO("mt9e013", 0x6C >> 2), + }, + #endif + { + I2C_BOARD_INFO("sc628a", 0x37), + }, +}; +#endif +#if defined(CONFIG_SERIAL_MSM_HSL_CONSOLE) \ + && defined(CONFIG_MSM_SHARED_GPIO_FOR_UART2DM) +static struct msm_gpio uart2dm_gpios[] = { + {GPIO_CFG(19, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "uart2dm_rfr_n" }, + {GPIO_CFG(20, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "uart2dm_cts_n" }, + {GPIO_CFG(21, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "uart2dm_rx" }, + {GPIO_CFG(108, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "uart2dm_tx" }, +}; + +static void msm7x27a_cfg_uart2dm_serial(void) +{ + int ret; + ret = msm_gpios_request_enable(uart2dm_gpios, + ARRAY_SIZE(uart2dm_gpios)); + if (ret) + pr_err("%s: unable to enable gpios for uart2dm\n", __func__); +} +#else +static void msm7x27a_cfg_uart2dm_serial(void) { } +#endif + +static struct platform_device *rumi_sim_devices[] __initdata = { + &msm_device_dmov, + &msm_device_smd, + &smc91x_device, + &msm_device_uart1, + &msm_device_nand, + &msm_device_uart_dm1, + &msm_gsbi0_qup_i2c_device, + &msm_gsbi1_qup_i2c_device, +}; + +static struct platform_device *surf_ffa_devices[] __initdata = { + &msm_device_dmov, + &msm_device_smd, + &msm_device_uart1, + &msm_device_uart_dm1, + &msm_device_uart_dm2, + &msm_device_nand, + &msm_gsbi0_qup_i2c_device, + &msm_gsbi1_qup_i2c_device, + &msm_device_otg, + &msm_device_gadget_peripheral, + &android_usb_device, + &android_pmem_device, + &android_pmem_adsp_device, + &android_pmem_audio_device, + &msm_device_snd, + &msm_device_adspdec, + &msm_fb_device, + &lcdc_toshiba_panel_device, + &msm_batt_device, + &smsc911x_device, +#ifdef CONFIG_S5K4E1 + &msm_camera_sensor_s5k4e1, +#endif +#ifdef CONFIG_IMX072 + &msm_camera_sensor_imx072, +#endif +#ifdef CONFIG_WEBCAM_OV9726 + &msm_camera_sensor_ov9726, +#endif +#ifdef CONFIG_MT9E013 + &msm_camera_sensor_mt9e013, +#endif +#ifdef CONFIG_FB_MSM_MIPI_DSI + &mipi_dsi_renesas_panel_device, +#endif + &msm_kgsl_3d0, +#ifdef CONFIG_BT + &msm_bt_power_device, +#endif +}; + +static unsigned pmem_kernel_ebi1_size = PMEM_KERNEL_EBI1_SIZE; +static int __init pmem_kernel_ebi1_size_setup(char *p) +{ + pmem_kernel_ebi1_size = memparse(p, NULL); + return 0; +} +early_param("pmem_kernel_ebi1_size", pmem_kernel_ebi1_size_setup); + +static unsigned pmem_audio_size = MSM_PMEM_AUDIO_SIZE; +static int __init pmem_audio_size_setup(char *p) +{ + pmem_audio_size = memparse(p, NULL); + return 0; +} +early_param("pmem_audio_size", pmem_audio_size_setup); + +static void __init msm_msm7x2x_allocate_memory_regions(void) +{ + void *addr; + unsigned long size; + + size = fb_size ? : MSM_FB_SIZE; + addr = alloc_bootmem_align(size, 0x1000); + msm_fb_resources[0].start = __pa(addr); + msm_fb_resources[0].end = msm_fb_resources[0].start + size - 1; + pr_info("allocating %lu bytes at %p (%lx physical) for fb\n", + size, addr, __pa(addr)); +} + +static struct memtype_reserve msm7x27a_reserve_table[] __initdata = { + [MEMTYPE_SMI] = { + }, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, +}; + +static void __init size_pmem_devices(void) +{ +#ifdef CONFIG_ANDROID_PMEM + android_pmem_adsp_pdata.size = pmem_adsp_size; + android_pmem_pdata.size = pmem_mdp_size; + android_pmem_audio_pdata.size = pmem_audio_size; +#endif +} + +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + msm7x27a_reserve_table[p->memory_type].size += p->size; +} + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM + reserve_memory_for(&android_pmem_adsp_pdata); + reserve_memory_for(&android_pmem_pdata); + reserve_memory_for(&android_pmem_audio_pdata); + msm7x27a_reserve_table[MEMTYPE_EBI1].size += pmem_kernel_ebi1_size; +#endif +} + +static void __init msm7x27a_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); +} + +static int msm7x27a_paddr_to_memtype(unsigned int paddr) +{ + return MEMTYPE_EBI1; +} + +static struct reserve_info msm7x27a_reserve_info __initdata = { + .memtype_reserve_table = msm7x27a_reserve_table, + .calculate_reserve_sizes = msm7x27a_calculate_reserve_sizes, + .paddr_to_memtype = msm7x27a_paddr_to_memtype, +}; + +static void __init msm7x27a_reserve(void) +{ + reserve_info = &msm7x27a_reserve_info; + msm_reserve(); +} + +static void __init msm_device_i2c_init(void) +{ + msm_gsbi0_qup_i2c_device.dev.platform_data = &msm_gsbi0_qup_i2c_pdata; + msm_gsbi1_qup_i2c_device.dev.platform_data = &msm_gsbi1_qup_i2c_pdata; +} + +static struct msm_panel_common_pdata mdp_pdata = { + .gpio = 97, + .mdp_rev = MDP_REV_303, +}; + +#define GPIO_LCDC_BRDG_PD 128 +#define GPIO_LCDC_BRDG_RESET_N 129 + +#define LCDC_RESET_PHYS 0x90008014 +static void __iomem *lcdc_reset_ptr; + +static unsigned mipi_dsi_gpio[] = { + GPIO_CFG(GPIO_LCDC_BRDG_RESET_N, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), /* LCDC_BRDG_RESET_N */ + GPIO_CFG(GPIO_LCDC_BRDG_PD, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), /* LCDC_BRDG_RESET_N */ +}; + +enum { + DSI_SINGLE_LANE = 1, + DSI_TWO_LANES, +}; + +static int msm_fb_get_lane_config(void) +{ + int rc = DSI_TWO_LANES; + + if (cpu_is_msm7x25a() || cpu_is_msm7x25aa()) { + rc = DSI_SINGLE_LANE; + pr_info("DSI Single Lane\n"); + } else { + pr_info("DSI Two Lanes\n"); + } + return rc; +} + +static int msm_fb_dsi_client_reset(void) +{ + int rc = 0; + + rc = gpio_request(GPIO_LCDC_BRDG_RESET_N, "lcdc_brdg_reset_n"); + if (rc < 0) { + pr_err("failed to request lcd brdg reset_n\n"); + return rc; + } + + rc = gpio_request(GPIO_LCDC_BRDG_PD, "lcdc_brdg_pd"); + if (rc < 0) { + pr_err("failed to request lcd brdg pd\n"); + return rc; + } + + rc = gpio_tlmm_config(mipi_dsi_gpio[0], GPIO_CFG_ENABLE); + if (rc) { + pr_err("Failed to enable LCDC Bridge reset enable\n"); + goto gpio_error; + } + + rc = gpio_tlmm_config(mipi_dsi_gpio[1], GPIO_CFG_ENABLE); + if (rc) { + pr_err("Failed to enable LCDC Bridge pd enable\n"); + goto gpio_error2; + } + + rc = gpio_direction_output(GPIO_LCDC_BRDG_RESET_N, 1); + rc |= gpio_direction_output(GPIO_LCDC_BRDG_PD, 1); + gpio_set_value_cansleep(GPIO_LCDC_BRDG_PD, 0); + + if (!rc) { + if (machine_is_msm7x27a_surf()) { + lcdc_reset_ptr = ioremap_nocache(LCDC_RESET_PHYS, + sizeof(uint32_t)); + + if (!lcdc_reset_ptr) + return 0; + } + return rc; + } else { + goto gpio_error; + } + +gpio_error2: + pr_err("Failed GPIO bridge pd\n"); + gpio_free(GPIO_LCDC_BRDG_PD); + +gpio_error: + pr_err("Failed GPIO bridge reset\n"); + gpio_free(GPIO_LCDC_BRDG_RESET_N); + return rc; +} + +static const char * const msm_fb_dsi_vreg[] = { + "gp2", + "msme1", +}; + +static const int msm_fb_dsi_vreg_mV[] = { + 2850, + 1800, +}; + +static struct vreg *dsi_vreg[ARRAY_SIZE(msm_fb_dsi_vreg)]; +static int dsi_gpio_initialized; + +static int mipi_dsi_panel_power(int on) +{ + int i, rc = 0; + uint32_t lcdc_reset_cfg; + + /* I2C-controlled GPIO Expander -init of the GPIOs very late */ + if (!dsi_gpio_initialized) { + pmapp_disp_backlight_init(); + + rc = gpio_request(GPIO_DISPLAY_PWR_EN, "gpio_disp_pwr"); + if (rc < 0) { + pr_err("failed to request gpio_disp_pwr\n"); + return rc; + } + + if (machine_is_msm7x27a_surf()) { + rc = gpio_direction_output(GPIO_DISPLAY_PWR_EN, 1); + if (rc < 0) { + pr_err("failed to enable display pwr\n"); + goto fail_gpio1; + } + + rc = gpio_request(GPIO_BACKLIGHT_EN, "gpio_bkl_en"); + if (rc < 0) { + pr_err("failed to request gpio_bkl_en\n"); + goto fail_gpio1; + } + + rc = gpio_direction_output(GPIO_BACKLIGHT_EN, 1); + if (rc < 0) { + pr_err("failed to enable backlight\n"); + goto fail_gpio2; + } + } + + for (i = 0; i < ARRAY_SIZE(msm_fb_dsi_vreg); i++) { + dsi_vreg[i] = vreg_get(0, msm_fb_dsi_vreg[i]); + + if (IS_ERR(dsi_vreg[i])) { + pr_err("%s: vreg get failed with : (%ld)\n", + __func__, PTR_ERR(dsi_vreg[i])); + goto fail_gpio2; + } + + rc = vreg_set_level(dsi_vreg[i], + msm_fb_dsi_vreg_mV[i]); + + if (rc < 0) { + pr_err("%s: set regulator level failed " + "with :(%d)\n", __func__, rc); + goto vreg_fail1; + } + } + dsi_gpio_initialized = 1; + } + + if (machine_is_msm7x27a_surf()) { + gpio_set_value_cansleep(GPIO_DISPLAY_PWR_EN, on); + gpio_set_value_cansleep(GPIO_BACKLIGHT_EN, on); + } else if (machine_is_msm7x27a_ffa()) { + if (on) { + /* This line drives an active low pin on FFA */ + rc = gpio_direction_output(GPIO_DISPLAY_PWR_EN, + !on); + if (rc < 0) + pr_err("failed to set direction for " + "display pwr\n"); + } else { + gpio_set_value_cansleep(GPIO_DISPLAY_PWR_EN, + !on); + rc = gpio_direction_input(GPIO_DISPLAY_PWR_EN); + if (rc < 0) + pr_err("failed to set direction for " + "display pwr\n"); + } + } + + if (on) { + gpio_set_value_cansleep(GPIO_LCDC_BRDG_PD, 0); + + if (machine_is_msm7x27a_surf()) { + lcdc_reset_cfg = readl_relaxed(lcdc_reset_ptr); + rmb(); + lcdc_reset_cfg &= ~1; + + writel_relaxed(lcdc_reset_cfg, lcdc_reset_ptr); + msleep(20); + wmb(); + lcdc_reset_cfg |= 1; + writel_relaxed(lcdc_reset_cfg, lcdc_reset_ptr); + } else { + gpio_set_value_cansleep(GPIO_LCDC_BRDG_RESET_N, + 0); + msleep(20); + gpio_set_value_cansleep(GPIO_LCDC_BRDG_RESET_N, + 1); + } + + if (pmapp_disp_backlight_set_brightness(100)) + pr_err("backlight set brightness failed\n"); + } else { + gpio_set_value_cansleep(GPIO_LCDC_BRDG_PD, 1); + + if (pmapp_disp_backlight_set_brightness(0)) + pr_err("backlight set brightness failed\n"); + } + + /*Configure vreg lines */ + for (i = 0; i < ARRAY_SIZE(msm_fb_dsi_vreg); i++) { + if (on) { + rc = vreg_enable(dsi_vreg[i]); + + if (rc) { + printk(KERN_ERR "vreg_enable: %s vreg" + "operation failed\n", + msm_fb_dsi_vreg[i]); + + goto vreg_fail2; + } + } else { + rc = vreg_disable(dsi_vreg[i]); + + if (rc) { + printk(KERN_ERR "vreg_disable: %s vreg " + "operation failed\n", + msm_fb_dsi_vreg[i]); + goto vreg_fail2; + } + } + } + + return rc; + +vreg_fail2: + if (on) { + for (; i > 0; i--) + vreg_disable(dsi_vreg[i - 1]); + } else { + for (; i > 0; i--) + vreg_enable(dsi_vreg[i - 1]); + } + + return rc; + +vreg_fail1: + for (; i > 0; i--) + vreg_put(dsi_vreg[i - 1]); + +fail_gpio2: + gpio_free(GPIO_BACKLIGHT_EN); +fail_gpio1: + gpio_free(GPIO_DISPLAY_PWR_EN); + dsi_gpio_initialized = 0; + return rc; +} + +#define MDP_303_VSYNC_GPIO 97 + +#ifdef CONFIG_FB_MSM_MDP303 +static struct mipi_dsi_platform_data mipi_dsi_pdata = { + .vsync_gpio = MDP_303_VSYNC_GPIO, + .dsi_power_save = mipi_dsi_panel_power, + .dsi_client_reset = msm_fb_dsi_client_reset, + .get_lane_config = msm_fb_get_lane_config, +}; +#endif + +static void __init msm_fb_add_devices(void) +{ + msm_fb_register_device("mdp", &mdp_pdata); + msm_fb_register_device("lcdc", &lcdc_pdata); + msm_fb_register_device("mipi_dsi", &mipi_dsi_pdata); +} + +#define MSM_EBI2_PHYS 0xa0d00000 +#define MSM_EBI2_XMEM_CS2_CFG1 0xa0d10030 + +static void __init msm7x27a_init_ebi2(void) +{ + uint32_t ebi2_cfg; + void __iomem *ebi2_cfg_ptr; + + ebi2_cfg_ptr = ioremap_nocache(MSM_EBI2_PHYS, sizeof(uint32_t)); + if (!ebi2_cfg_ptr) + return; + + ebi2_cfg = readl(ebi2_cfg_ptr); + if (machine_is_msm7x27a_rumi3() || machine_is_msm7x27a_surf()) + ebi2_cfg |= (1 << 4); /* CS2 */ + + writel(ebi2_cfg, ebi2_cfg_ptr); + iounmap(ebi2_cfg_ptr); + + /* Enable A/D MUX[bit 31] from EBI2_XMEM_CS2_CFG1 */ + ebi2_cfg_ptr = ioremap_nocache(MSM_EBI2_XMEM_CS2_CFG1, + sizeof(uint32_t)); + if (!ebi2_cfg_ptr) + return; + + ebi2_cfg = readl(ebi2_cfg_ptr); + if (machine_is_msm7x27a_surf()) + ebi2_cfg |= (1 << 31); + + writel(ebi2_cfg, ebi2_cfg_ptr); + iounmap(ebi2_cfg_ptr); +} + +#define ATMEL_TS_I2C_NAME "maXTouch" +static struct vreg *vreg_l12; +static struct vreg *vreg_s3; + +#define ATMEL_TS_GPIO_IRQ 82 + +static int atmel_ts_power_on(bool on) +{ + int rc; + + rc = on ? vreg_enable(vreg_l12) : vreg_disable(vreg_l12); + if (rc) { + pr_err("%s: vreg %sable failed (%d)\n", + __func__, on ? "en" : "dis", rc); + return rc; + } + + rc = on ? vreg_enable(vreg_s3) : vreg_disable(vreg_s3); + if (rc) { + pr_err("%s: vreg %sable failed (%d) for S3\n", + __func__, on ? "en" : "dis", rc); + !on ? vreg_enable(vreg_l12) : vreg_disable(vreg_l12); + return rc; + } + /* vreg stabilization delay */ + msleep(50); + return 0; +} + +static int atmel_ts_platform_init(struct i2c_client *client) +{ + int rc; + + vreg_l12 = vreg_get(NULL, "gp2"); + if (IS_ERR(vreg_l12)) { + pr_err("%s: vreg_get for L2 failed\n", __func__); + return PTR_ERR(vreg_l12); + } + + rc = vreg_set_level(vreg_l12, 2850); + if (rc) { + pr_err("%s: vreg set level failed (%d) for l2\n", + __func__, rc); + goto vreg_put_l2; + } + + vreg_s3 = vreg_get(NULL, "msme1"); + if (IS_ERR(vreg_s3)) { + pr_err("%s: vreg_get for S3 failed\n", __func__); + rc = PTR_ERR(vreg_s3); + goto vreg_put_l2; + } + + rc = vreg_set_level(vreg_s3, 1800); + if (rc) { + pr_err("%s: vreg set level failed (%d) for S3\n", + __func__, rc); + goto vreg_put_s3; + } + + rc = gpio_tlmm_config(GPIO_CFG(ATMEL_TS_GPIO_IRQ, 0, + GPIO_CFG_INPUT, GPIO_CFG_PULL_UP, + GPIO_CFG_8MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config for %d failed\n", + __func__, ATMEL_TS_GPIO_IRQ); + goto vreg_put_s3; + } + + /* configure touchscreen interrupt gpio */ + rc = gpio_request(ATMEL_TS_GPIO_IRQ, "atmel_maxtouch_gpio"); + if (rc) { + pr_err("%s: unable to request gpio %d\n", + __func__, ATMEL_TS_GPIO_IRQ); + goto ts_gpio_tlmm_unconfig; + } + + rc = gpio_direction_input(ATMEL_TS_GPIO_IRQ); + if (rc < 0) { + pr_err("%s: unable to set the direction of gpio %d\n", + __func__, ATMEL_TS_GPIO_IRQ); + goto free_ts_gpio; + } + return 0; + +free_ts_gpio: + gpio_free(ATMEL_TS_GPIO_IRQ); +ts_gpio_tlmm_unconfig: + gpio_tlmm_config(GPIO_CFG(ATMEL_TS_GPIO_IRQ, 0, + GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_DISABLE); +vreg_put_s3: + vreg_put(vreg_s3); +vreg_put_l2: + vreg_put(vreg_l12); + return rc; +} + +static int atmel_ts_platform_exit(struct i2c_client *client) +{ + gpio_free(ATMEL_TS_GPIO_IRQ); + gpio_tlmm_config(GPIO_CFG(ATMEL_TS_GPIO_IRQ, 0, + GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_DISABLE); + vreg_disable(vreg_s3); + vreg_put(vreg_s3); + vreg_disable(vreg_l12); + vreg_put(vreg_l12); + return 0; +} + +static u8 atmel_ts_read_chg(void) +{ + return gpio_get_value(ATMEL_TS_GPIO_IRQ); +} + +static u8 atmel_ts_valid_interrupt(void) +{ + return !atmel_ts_read_chg(); +} + +#define ATMEL_X_OFFSET 13 +#define ATMEL_Y_OFFSET 0 + +static struct mxt_platform_data atmel_ts_pdata = { + .numtouch = 4, + .init_platform_hw = atmel_ts_platform_init, + .exit_platform_hw = atmel_ts_platform_exit, + .power_on = atmel_ts_power_on, + .display_res_x = 480, + .display_res_y = 864, + .min_x = ATMEL_X_OFFSET, + .max_x = (505 - ATMEL_X_OFFSET), + .min_y = ATMEL_Y_OFFSET, + .max_y = (863 - ATMEL_Y_OFFSET), + .valid_interrupt = atmel_ts_valid_interrupt, + .read_chg = atmel_ts_read_chg, +}; + +static struct i2c_board_info atmel_ts_i2c_info[] __initdata = { + { + I2C_BOARD_INFO(ATMEL_TS_I2C_NAME, 0x4a), + .platform_data = &atmel_ts_pdata, + .irq = MSM_GPIO_TO_INT(ATMEL_TS_GPIO_IRQ), + }, +}; + +#define KP_INDEX(row, col) ((row)*ARRAY_SIZE(kp_col_gpios) + (col)) + +static unsigned int kp_row_gpios[] = {31, 32, 33, 34, 35}; +static unsigned int kp_col_gpios[] = {36, 37, 38, 39, 40}; + +static const unsigned short keymap[ARRAY_SIZE(kp_col_gpios) * + ARRAY_SIZE(kp_row_gpios)] = { + [KP_INDEX(0, 0)] = KEY_7, + [KP_INDEX(0, 1)] = KEY_DOWN, + [KP_INDEX(0, 2)] = KEY_UP, + [KP_INDEX(0, 3)] = KEY_RIGHT, + [KP_INDEX(0, 4)] = KEY_ENTER, + + [KP_INDEX(1, 0)] = KEY_LEFT, + [KP_INDEX(1, 1)] = KEY_SEND, + [KP_INDEX(1, 2)] = KEY_1, + [KP_INDEX(1, 3)] = KEY_4, + [KP_INDEX(1, 4)] = KEY_CLEAR, + + [KP_INDEX(2, 0)] = KEY_6, + [KP_INDEX(2, 1)] = KEY_5, + [KP_INDEX(2, 2)] = KEY_8, + [KP_INDEX(2, 3)] = KEY_3, + [KP_INDEX(2, 4)] = KEY_NUMERIC_STAR, + + [KP_INDEX(3, 0)] = KEY_9, + [KP_INDEX(3, 1)] = KEY_NUMERIC_POUND, + [KP_INDEX(3, 2)] = KEY_0, + [KP_INDEX(3, 3)] = KEY_2, + [KP_INDEX(3, 4)] = KEY_SLEEP, + + [KP_INDEX(4, 0)] = KEY_BACK, + [KP_INDEX(4, 1)] = KEY_HOME, + [KP_INDEX(4, 2)] = KEY_MENU, + [KP_INDEX(4, 3)] = KEY_VOLUMEUP, + [KP_INDEX(4, 4)] = KEY_VOLUMEDOWN, +}; + +/* SURF keypad platform device information */ +static struct gpio_event_matrix_info kp_matrix_info = { + .info.func = gpio_event_matrix_func, + .keymap = keymap, + .output_gpios = kp_row_gpios, + .input_gpios = kp_col_gpios, + .noutputs = ARRAY_SIZE(kp_row_gpios), + .ninputs = ARRAY_SIZE(kp_col_gpios), + .settle_time.tv_nsec = 40 * NSEC_PER_USEC, + .poll_time.tv_nsec = 20 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_DRIVE_INACTIVE | + GPIOKPF_PRINT_UNMAPPED_KEYS, +}; + +static struct gpio_event_info *kp_info[] = { + &kp_matrix_info.info +}; + +static struct gpio_event_platform_data kp_pdata = { + .name = "7x27a_kp", + .info = kp_info, + .info_count = ARRAY_SIZE(kp_info) +}; + +static struct platform_device kp_pdev = { + .name = GPIO_EVENT_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &kp_pdata, + }, +}; + +static struct msm_handset_platform_data hs_platform_data = { + .hs_name = "7k_handset", + .pwr_key_delay_ms = 500, /* 0 will disable end key */ +}; + +static struct platform_device hs_pdev = { + .name = "msm-handset", + .id = -1, + .dev = { + .platform_data = &hs_platform_data, + }, +}; + +#define LED_GPIO_PDM 96 +#define UART1DM_RX_GPIO 45 +static void __init msm7x2x_init(void) +{ + + /* Common functions for SURF/FFA/RUMI3 */ + msm_device_i2c_init(); + msm7x27a_init_mmc(); + msm7x27a_init_ebi2(); + msm7x27a_cfg_uart2dm_serial(); +#ifdef CONFIG_SERIAL_MSM_HS + msm_uart_dm1_pdata.wakeup_irq = gpio_to_irq(UART1DM_RX_GPIO); + msm_device_uart_dm1.dev.platform_data = &msm_uart_dm1_pdata; +#endif + + if (machine_is_msm7x27a_rumi3()) { + platform_add_devices(rumi_sim_devices, + ARRAY_SIZE(rumi_sim_devices)); + } + if (machine_is_msm7x27a_surf() || machine_is_msm7x27a_ffa()) { +#ifdef CONFIG_USB_MSM_OTG_72K + msm_otg_pdata.swfi_latency = + msm7x27a_pm_data + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT].latency; + msm_device_otg.dev.platform_data = &msm_otg_pdata; +#endif + msm_device_gadget_peripheral.dev.platform_data = + &msm_gadget_pdata; + msm7x27a_cfg_smsc911x(); + platform_add_devices(msm_footswitch_devices, + msm_num_footswitch_devices); + platform_add_devices(surf_ffa_devices, + ARRAY_SIZE(surf_ffa_devices)); + msm_fb_add_devices(); +#ifdef CONFIG_USB_EHCI_MSM_72K + msm7x2x_init_host(); +#endif + } + + msm_pm_set_platform_data(msm7x27a_pm_data, + ARRAY_SIZE(msm7x27a_pm_data)); + +#if defined(CONFIG_I2C) && defined(CONFIG_GPIO_SX150X) + register_i2c_devices(); +#endif +#if defined(CONFIG_BT) && defined(CONFIG_MARIMBA_CORE) + bt_power_init(); +#endif + if (cpu_is_msm7x25a() || cpu_is_msm7x25aa()) { + atmel_ts_pdata.min_x = 0; + atmel_ts_pdata.max_x = 480; + atmel_ts_pdata.min_y = 0; + atmel_ts_pdata.max_y = 320; + } + + i2c_register_board_info(MSM_GSBI1_QUP_I2C_BUS_ID, + atmel_ts_i2c_info, + ARRAY_SIZE(atmel_ts_i2c_info)); + + i2c_register_board_info(MSM_GSBI0_QUP_I2C_BUS_ID, + i2c_camera_devices, + ARRAY_SIZE(i2c_camera_devices)); + platform_device_register(&kp_pdev); + platform_device_register(&hs_pdev); + + /* configure it as a pdm function*/ + if (gpio_tlmm_config(GPIO_CFG(LED_GPIO_PDM, 3, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_8MA), GPIO_CFG_ENABLE)) + pr_err("%s: gpio_tlmm_config for %d failed\n", + __func__, LED_GPIO_PDM); + else + platform_device_register(&led_pdev); + +#ifdef CONFIG_MSM_RPC_VIBRATOR + if (machine_is_msm7x27a_ffa()) + msm_init_pmic_vibrator(); +#endif + /*7x25a kgsl initializations*/ + msm7x25a_kgsl_3d0_init(); +} + +static void __init msm7x2x_init_early(void) +{ + msm7x2x_misc_init(); + msm_msm7x2x_allocate_memory_regions(); +} + +MACHINE_START(MSM7X27A_RUMI3, "QCT MSM7x27a RUMI3") + .boot_params = PHYS_OFFSET + 0x100, + .map_io = msm_common_io_init, + .reserve = msm7x27a_reserve, + .init_irq = msm_init_irq, + .init_machine = msm7x2x_init, + .timer = &msm_timer, + .init_early = msm7x2x_init_early, +MACHINE_END +MACHINE_START(MSM7X27A_SURF, "QCT MSM7x27a SURF") + .boot_params = PHYS_OFFSET + 0x100, + .map_io = msm_common_io_init, + .reserve = msm7x27a_reserve, + .init_irq = msm_init_irq, + .init_machine = msm7x2x_init, + .timer = &msm_timer, + .init_early = msm7x2x_init_early, +MACHINE_END +MACHINE_START(MSM7X27A_FFA, "QCT MSM7x27a FFA") + .boot_params = PHYS_OFFSET + 0x100, + .map_io = msm_common_io_init, + .reserve = msm7x27a_reserve, + .init_irq = msm_init_irq, + .init_machine = msm7x2x_init, + .timer = &msm_timer, + .init_early = msm7x2x_init_early, +MACHINE_END diff --git a/arch/arm/mach-msm/board-msm7x30.c b/arch/arm/mach-msm/board-msm7x30.c index b7a84966b71..f92845b6729 100644 --- a/arch/arm/mach-msm/board-msm7x30.c +++ b/arch/arm/mach-msm/board-msm7x30.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and @@ -9,10 +9,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include @@ -20,111 +16,7270 @@ #include #include #include +#include #include +#ifdef CONFIG_SPI_QSD +#include +#endif +#include +#include +#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "devices.h" +#include "timer.h" +#ifdef CONFIG_USB_G_ANDROID +#include +#include +#endif +#include "pm.h" +#include "spm.h" +#include +#include +#include +#include +#include +#include +#include "smd_private.h" +#include + +#define MSM_PMEM_SF_SIZE 0x1700000 +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +#define MSM_FB_SIZE 0x780000 +#else +#define MSM_FB_SIZE 0x500000 +#endif +#define MSM_PMEM_ADSP_SIZE 0x1E00000 +#define MSM_FLUID_PMEM_ADSP_SIZE 0x2800000 +#define PMEM_KERNEL_EBI0_SIZE 0x600000 +#define MSM_PMEM_AUDIO_SIZE 0x200000 + +#define PMIC_GPIO_INT 27 +#define PMIC_VREG_WLAN_LEVEL 2900 +#define PMIC_GPIO_SD_DET 36 +#define PMIC_GPIO_SDC4_EN_N 17 /* PMIC GPIO Number 18 */ +#define PMIC_GPIO_HDMI_5V_EN_V3 32 /* PMIC GPIO for V3 H/W */ +#define PMIC_GPIO_HDMI_5V_EN_V2 39 /* PMIC GPIO for V2 H/W */ + +#define ADV7520_I2C_ADDR 0x39 + +#define FPGA_SDCC_STATUS 0x8E0001A8 + +#define FPGA_OPTNAV_GPIO_ADDR 0x8E000026 +#define OPTNAV_I2C_SLAVE_ADDR (0xB0 >> 1) +#define OPTNAV_IRQ 20 +#define OPTNAV_CHIP_SELECT 19 + +/* Macros assume PMIC GPIOs start at 0 */ +#define PM8058_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio + NR_GPIO_IRQS) +#define PM8058_GPIO_SYS_TO_PM(sys_gpio) (sys_gpio - NR_GPIO_IRQS) + +#define PMIC_GPIO_FLASH_BOOST_ENABLE 15 /* PMIC GPIO Number 16 */ +#define PMIC_GPIO_HAP_ENABLE 16 /* PMIC GPIO Number 17 */ + +#define PMIC_GPIO_WLAN_EXT_POR 22 /* PMIC GPIO NUMBER 23 */ + +#define BMA150_GPIO_INT 1 + +#define HAP_LVL_SHFT_MSM_GPIO 24 + +#define PMIC_GPIO_QUICKVX_CLK 37 /* PMIC GPIO 38 */ + +#define PM_FLIP_MPP 5 /* PMIC MPP 06 */ +static int pm8058_gpios_init(void) +{ + int rc; + int pmic_gpio_hdmi_5v_en; + +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + struct pm8058_gpio sdcc_det = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_1P5, + .vin_sel = 2, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }; +#endif + struct pm8058_gpio sdc4_en = { + .direction = PM_GPIO_DIR_OUT, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_L5, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + .out_strength = PM_GPIO_STRENGTH_LOW, + .output_value = 0, + }; + + struct pm8058_gpio haptics_enable = { + .direction = PM_GPIO_DIR_OUT, + .pull = PM_GPIO_PULL_NO, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + .vin_sel = 2, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 0, + }; + + struct pm8058_gpio hdmi_5V_en = { + .direction = PM_GPIO_DIR_OUT, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_VPH, + .function = PM_GPIO_FUNC_NORMAL, + .out_strength = PM_GPIO_STRENGTH_LOW, + .output_value = 0, + }; + + struct pm8058_gpio flash_boost_enable = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 0, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S3, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_2, + }; + + struct pm8058_gpio gpio23 = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 0, + .pull = PM_GPIO_PULL_NO, + .vin_sel = 2, + .out_strength = PM_GPIO_STRENGTH_LOW, + .function = PM_GPIO_FUNC_NORMAL, + }; + + + if (machine_is_msm8x55_svlte_surf() || machine_is_msm8x55_svlte_ffa() || + machine_is_msm7x30_fluid()) + pmic_gpio_hdmi_5v_en = PMIC_GPIO_HDMI_5V_EN_V2 ; + else + pmic_gpio_hdmi_5v_en = PMIC_GPIO_HDMI_5V_EN_V3 ; + + if (machine_is_msm7x30_fluid()) { + rc = pm8058_gpio_config(PMIC_GPIO_HAP_ENABLE, &haptics_enable); + if (rc) { + pr_err("%s: PMIC GPIO %d write failed\n", __func__, + (PMIC_GPIO_HAP_ENABLE + 1)); + return rc; + } + rc = pm8058_gpio_config(PMIC_GPIO_FLASH_BOOST_ENABLE, + &flash_boost_enable); + if (rc) { + pr_err("%s: PMIC GPIO %d write failed\n", __func__, + (PMIC_GPIO_FLASH_BOOST_ENABLE + 1)); + return rc; + } + } + +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + if (machine_is_msm7x30_fluid()) + sdcc_det.inv_int_pol = 1; + + rc = pm8058_gpio_config(PMIC_GPIO_SD_DET - 1, &sdcc_det); + if (rc) { + pr_err("%s PMIC_GPIO_SD_DET config failed\n", __func__); + return rc; + } +#endif + + rc = pm8058_gpio_config(pmic_gpio_hdmi_5v_en, &hdmi_5V_en); + if (rc) { + pr_err("%s PMIC_GPIO_HDMI_5V_EN config failed\n", __func__); + return rc; + } + + /* Deassert GPIO#23 (source for Ext_POR on WLAN-Volans) */ + rc = pm8058_gpio_config(PMIC_GPIO_WLAN_EXT_POR, &gpio23); + if (rc) { + pr_err("%s PMIC_GPIO_WLAN_EXT_POR config failed\n", __func__); + return rc; + } + + if (machine_is_msm7x30_fluid()) { + rc = pm8058_gpio_config(PMIC_GPIO_SDC4_EN_N, &sdc4_en); + if (rc) { + pr_err("%s PMIC_GPIO_SDC4_EN_N config failed\n", + __func__); + return rc; + } + rc = gpio_request(PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC4_EN_N), + "sdc4_en"); + if (rc) { + pr_err("%s PMIC_GPIO_SDC4_EN_N gpio_request failed\n", + __func__); + return rc; + } + gpio_set_value_cansleep( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC4_EN_N), 0); + } + + return 0; +} + +/*virtual key support */ +static ssize_t tma300_vkeys_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, + __stringify(EV_KEY) ":" __stringify(KEY_BACK) ":50:842:80:100" + ":" __stringify(EV_KEY) ":" __stringify(KEY_MENU) ":170:842:80:100" + ":" __stringify(EV_KEY) ":" __stringify(KEY_HOME) ":290:842:80:100" + ":" __stringify(EV_KEY) ":" __stringify(KEY_SEARCH) ":410:842:80:100" + "\n"); +} + +static struct kobj_attribute tma300_vkeys_attr = { + .attr = { + .mode = S_IRUGO, + }, + .show = &tma300_vkeys_show, +}; + +static struct attribute *tma300_properties_attrs[] = { + &tma300_vkeys_attr.attr, + NULL +}; + +static struct attribute_group tma300_properties_attr_group = { + .attrs = tma300_properties_attrs, +}; + +static struct kobject *properties_kobj; + +#define CYTTSP_TS_GPIO_IRQ 150 +static int cyttsp_platform_init(struct i2c_client *client) +{ + int rc = -EINVAL; + struct vreg *vreg_ldo8, *vreg_ldo15; + + vreg_ldo8 = vreg_get(NULL, "gp7"); + + if (!vreg_ldo8) { + pr_err("%s: VREG L8 get failed\n", __func__); + return rc; + } + + rc = vreg_set_level(vreg_ldo8, 1800); + if (rc) { + pr_err("%s: VREG L8 set failed\n", __func__); + goto l8_put; + } + + rc = vreg_enable(vreg_ldo8); + if (rc) { + pr_err("%s: VREG L8 enable failed\n", __func__); + goto l8_put; + } + + vreg_ldo15 = vreg_get(NULL, "gp6"); + + if (!vreg_ldo15) { + pr_err("%s: VREG L15 get failed\n", __func__); + goto l8_disable; + } + + rc = vreg_set_level(vreg_ldo15, 3050); + if (rc) { + pr_err("%s: VREG L15 set failed\n", __func__); + goto l8_disable; + } + + rc = vreg_enable(vreg_ldo15); + if (rc) { + pr_err("%s: VREG L15 enable failed\n", __func__); + goto l8_disable; + } + + /* check this device active by reading first byte/register */ + rc = i2c_smbus_read_byte_data(client, 0x01); + if (rc < 0) { + pr_err("%s: i2c sanity check failed\n", __func__); + goto l8_disable; + } + + rc = gpio_tlmm_config(GPIO_CFG(CYTTSP_TS_GPIO_IRQ, 0, GPIO_CFG_INPUT, + GPIO_CFG_PULL_UP, GPIO_CFG_6MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: Could not configure gpio %d\n", + __func__, CYTTSP_TS_GPIO_IRQ); + goto l8_disable; + } + + /* virtual keys */ + tma300_vkeys_attr.attr.name = "virtualkeys.cyttsp-i2c"; + properties_kobj = kobject_create_and_add("board_properties", + NULL); + if (properties_kobj) + rc = sysfs_create_group(properties_kobj, + &tma300_properties_attr_group); + if (!properties_kobj || rc) + pr_err("%s: failed to create board_properties\n", + __func__); + + return CY_OK; + +l8_disable: + vreg_disable(vreg_ldo8); +l8_put: + vreg_put(vreg_ldo8); + return rc; +} + +static int cyttsp_platform_resume(struct i2c_client *client) +{ + /* add any special code to strobe a wakeup pin or chip reset */ + mdelay(10); + + return CY_OK; +} + +static struct cyttsp_platform_data cyttsp_data = { + .fw_fname = "cyttsp_7630_fluid.hex", + .panel_maxx = 479, + .panel_maxy = 799, + .disp_maxx = 469, + .disp_maxy = 799, + .disp_minx = 10, + .disp_miny = 0, + .flags = 0, + .gen = CY_GEN3, /* or */ + .use_st = CY_USE_ST, + .use_mt = CY_USE_MT, + .use_hndshk = CY_SEND_HNDSHK, + .use_trk_id = CY_USE_TRACKING_ID, + .use_sleep = CY_USE_SLEEP, + .use_gestures = CY_USE_GESTURES, + /* activate up to 4 groups + * and set active distance + */ + .gest_set = CY_GEST_GRP1 | CY_GEST_GRP2 | + CY_GEST_GRP3 | CY_GEST_GRP4 | + CY_ACT_DIST, + /* change act_intrvl to customize the Active power state + * scanning/processing refresh interval for Operating mode + */ + .act_intrvl = CY_ACT_INTRVL_DFLT, + /* change tch_tmout to customize the touch timeout for the + * Active power state for Operating mode + */ + .tch_tmout = CY_TCH_TMOUT_DFLT, + /* change lp_intrvl to customize the Low Power power state + * scanning/processing refresh interval for Operating mode + */ + .lp_intrvl = CY_LP_INTRVL_DFLT, + .resume = cyttsp_platform_resume, + .init = cyttsp_platform_init, + .sleep_gpio = -1, + .resout_gpio = -1, + .irq_gpio = CYTTSP_TS_GPIO_IRQ, +}; + +static int pm8058_pwm_config(struct pwm_device *pwm, int ch, int on) +{ + struct pm8058_gpio pwm_gpio_config = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 0, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S3, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_2, + }; + int rc = -EINVAL; + int id, mode, max_mA; + + id = mode = max_mA = 0; + switch (ch) { + case 0: + case 1: + case 2: + if (on) { + id = 24 + ch; + rc = pm8058_gpio_config(id - 1, &pwm_gpio_config); + if (rc) + pr_err("%s: pm8058_gpio_config(%d): rc=%d\n", + __func__, id, rc); + } + break; + + case 3: + id = PM_PWM_LED_KPD; + mode = PM_PWM_CONF_DTEST3; + max_mA = 200; + break; + + case 4: + id = PM_PWM_LED_0; + mode = PM_PWM_CONF_PWM1; + max_mA = 40; + break; + + case 5: + id = PM_PWM_LED_2; + mode = PM_PWM_CONF_PWM2; + max_mA = 40; + break; + + case 6: + id = PM_PWM_LED_FLASH; + mode = PM_PWM_CONF_DTEST3; + max_mA = 200; + break; + + default: + break; + } + + if (ch >= 3 && ch <= 6) { + if (!on) { + mode = PM_PWM_CONF_NONE; + max_mA = 0; + } + rc = pm8058_pwm_config_led(pwm, id, mode, max_mA); + if (rc) + pr_err("%s: pm8058_pwm_config_led(ch=%d): rc=%d\n", + __func__, ch, rc); + } + + return rc; +} + +static int pm8058_pwm_enable(struct pwm_device *pwm, int ch, int on) +{ + int rc; + + switch (ch) { + case 7: + rc = pm8058_pwm_set_dtest(pwm, on); + if (rc) + pr_err("%s: pwm_set_dtest(%d): rc=%d\n", + __func__, on, rc); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +static const unsigned int fluid_keymap[] = { + KEY(0, 0, KEY_7), + KEY(0, 1, KEY_ENTER), + KEY(0, 2, KEY_UP), + /* drop (0,3) as it always shows up in pair with(0,2) */ + KEY(0, 4, KEY_DOWN), + + KEY(1, 0, KEY_CAMERA_SNAPSHOT), + KEY(1, 1, KEY_SELECT), + KEY(1, 2, KEY_1), + KEY(1, 3, KEY_VOLUMEUP), + KEY(1, 4, KEY_VOLUMEDOWN), +}; + +static const unsigned int surf_keymap[] = { + KEY(0, 0, KEY_7), + KEY(0, 1, KEY_DOWN), + KEY(0, 2, KEY_UP), + KEY(0, 3, KEY_RIGHT), + KEY(0, 4, KEY_ENTER), + KEY(0, 5, KEY_L), + KEY(0, 6, KEY_BACK), + KEY(0, 7, KEY_M), + + KEY(1, 0, KEY_LEFT), + KEY(1, 1, KEY_SEND), + KEY(1, 2, KEY_1), + KEY(1, 3, KEY_4), + KEY(1, 4, KEY_CLEAR), + KEY(1, 5, KEY_MSDOS), + KEY(1, 6, KEY_SPACE), + KEY(1, 7, KEY_COMMA), + + KEY(2, 0, KEY_6), + KEY(2, 1, KEY_5), + KEY(2, 2, KEY_8), + KEY(2, 3, KEY_3), + KEY(2, 4, KEY_NUMERIC_STAR), + KEY(2, 5, KEY_UP), + KEY(2, 6, KEY_DOWN), /* SYN */ + KEY(2, 7, KEY_LEFTSHIFT), + + KEY(3, 0, KEY_9), + KEY(3, 1, KEY_NUMERIC_POUND), + KEY(3, 2, KEY_0), + KEY(3, 3, KEY_2), + KEY(3, 4, KEY_SLEEP), + KEY(3, 5, KEY_F1), + KEY(3, 6, KEY_F2), + KEY(3, 7, KEY_F3), + + KEY(4, 0, KEY_BACK), + KEY(4, 1, KEY_HOME), + KEY(4, 2, KEY_MENU), + KEY(4, 3, KEY_VOLUMEUP), + KEY(4, 4, KEY_VOLUMEDOWN), + KEY(4, 5, KEY_F4), + KEY(4, 6, KEY_F5), + KEY(4, 7, KEY_F6), + + KEY(5, 0, KEY_R), + KEY(5, 1, KEY_T), + KEY(5, 2, KEY_Y), + KEY(5, 3, KEY_LEFTALT), + KEY(5, 4, KEY_KPENTER), + KEY(5, 5, KEY_Q), + KEY(5, 6, KEY_W), + KEY(5, 7, KEY_E), + + KEY(6, 0, KEY_F), + KEY(6, 1, KEY_G), + KEY(6, 2, KEY_H), + KEY(6, 3, KEY_CAPSLOCK), + KEY(6, 4, KEY_PAGEUP), + KEY(6, 5, KEY_A), + KEY(6, 6, KEY_S), + KEY(6, 7, KEY_D), + + KEY(7, 0, KEY_V), + KEY(7, 1, KEY_B), + KEY(7, 2, KEY_N), + KEY(7, 3, KEY_MENU), /* REVISIT - SYM */ + KEY(7, 4, KEY_PAGEDOWN), + KEY(7, 5, KEY_Z), + KEY(7, 6, KEY_X), + KEY(7, 7, KEY_C), + + KEY(8, 0, KEY_P), + KEY(8, 1, KEY_J), + KEY(8, 2, KEY_K), + KEY(8, 3, KEY_INSERT), + KEY(8, 4, KEY_LINEFEED), + KEY(8, 5, KEY_U), + KEY(8, 6, KEY_I), + KEY(8, 7, KEY_O), + + KEY(9, 0, KEY_4), + KEY(9, 1, KEY_5), + KEY(9, 2, KEY_6), + KEY(9, 3, KEY_7), + KEY(9, 4, KEY_8), + KEY(9, 5, KEY_1), + KEY(9, 6, KEY_2), + KEY(9, 7, KEY_3), + + KEY(10, 0, KEY_F7), + KEY(10, 1, KEY_F8), + KEY(10, 2, KEY_F9), + KEY(10, 3, KEY_F10), + KEY(10, 4, KEY_FN), + KEY(10, 5, KEY_9), + KEY(10, 6, KEY_0), + KEY(10, 7, KEY_DOT), + + KEY(11, 0, KEY_LEFTCTRL), + KEY(11, 1, KEY_F11), /* START */ + KEY(11, 2, KEY_ENTER), + KEY(11, 3, KEY_SEARCH), + KEY(11, 4, KEY_DELETE), + KEY(11, 5, KEY_RIGHT), + KEY(11, 6, KEY_LEFT), + KEY(11, 7, KEY_RIGHTSHIFT), +}; + +static struct resource resources_keypad[] = { + { + .start = PM8058_KEYPAD_IRQ(PMIC8058_IRQ_BASE), + .end = PM8058_KEYPAD_IRQ(PMIC8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .start = PM8058_KEYSTUCK_IRQ(PMIC8058_IRQ_BASE), + .end = PM8058_KEYSTUCK_IRQ(PMIC8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct matrix_keymap_data surf_keymap_data = { + .keymap_size = ARRAY_SIZE(surf_keymap), + .keymap = surf_keymap, +}; + + +static struct pmic8058_keypad_data surf_keypad_data = { + .input_name = "surf_keypad", + .input_phys_device = "surf_keypad/input0", + .num_rows = 12, + .num_cols = 8, + .rows_gpio_start = 8, + .cols_gpio_start = 0, + .debounce_ms = {8, 10}, + .scan_delay_ms = 32, + .row_hold_ns = 91500, + .wakeup = 1, + .keymap_data = &surf_keymap_data, +}; + +static struct matrix_keymap_data fluid_keymap_data = { + .keymap_size = ARRAY_SIZE(fluid_keymap), + .keymap = fluid_keymap, +}; + + + +static struct pmic8058_keypad_data fluid_keypad_data = { + .input_name = "fluid-keypad", + .input_phys_device = "fluid-keypad/input0", + .num_rows = 5, + .num_cols = 5, + .rows_gpio_start = 8, + .cols_gpio_start = 0, + .debounce_ms = {8, 10}, + .scan_delay_ms = 32, + .row_hold_ns = 91500, + .wakeup = 1, + .keymap_data = &fluid_keymap_data, +}; + +static struct pm8058_pwm_pdata pm8058_pwm_data = { + .config = pm8058_pwm_config, + .enable = pm8058_pwm_enable, +}; + +/* Put sub devices with fixed location first in sub_devices array */ +#define PM8058_SUBDEV_KPD 0 +#define PM8058_SUBDEV_LED 1 + +static struct pm8058_gpio_platform_data pm8058_gpio_data = { + .gpio_base = PM8058_GPIO_PM_TO_SYS(0), + .irq_base = PM8058_GPIO_IRQ(PMIC8058_IRQ_BASE, 0), + .init = pm8058_gpios_init, +}; + +static struct pm8058_gpio_platform_data pm8058_mpp_data = { + .gpio_base = PM8058_GPIO_PM_TO_SYS(PM8058_GPIOS), + .irq_base = PM8058_MPP_IRQ(PMIC8058_IRQ_BASE, 0), +}; + +static struct pmic8058_led pmic8058_ffa_leds[] = { + [0] = { + .name = "keyboard-backlight", + .max_brightness = 15, + .id = PMIC8058_ID_LED_KB_LIGHT, + }, +}; + +static struct pmic8058_leds_platform_data pm8058_ffa_leds_data = { + .num_leds = ARRAY_SIZE(pmic8058_ffa_leds), + .leds = pmic8058_ffa_leds, +}; + +static struct pmic8058_led pmic8058_surf_leds[] = { + [0] = { + .name = "keyboard-backlight", + .max_brightness = 15, + .id = PMIC8058_ID_LED_KB_LIGHT, + }, + [1] = { + .name = "voice:red", + .max_brightness = 20, + .id = PMIC8058_ID_LED_0, + }, + [2] = { + .name = "wlan:green", + .max_brightness = 20, + .id = PMIC8058_ID_LED_2, + }, +}; + +static struct mfd_cell pm8058_subdevs[] = { + { .name = "pm8058-keypad", + .id = -1, + .num_resources = ARRAY_SIZE(resources_keypad), + .resources = resources_keypad, + }, + { .name = "pm8058-led", + .id = -1, + }, + { .name = "pm8058-gpio", + .id = -1, + .platform_data = &pm8058_gpio_data, + .pdata_size = sizeof(pm8058_gpio_data), + }, + { .name = "pm8058-mpp", + .id = -1, + .platform_data = &pm8058_mpp_data, + .pdata_size = sizeof(pm8058_mpp_data), + }, + { .name = "pm8058-pwm", + .id = -1, + .platform_data = &pm8058_pwm_data, + .pdata_size = sizeof(pm8058_pwm_data), + }, + { .name = "pm8058-nfc", + .id = -1, + }, + { .name = "pm8058-upl", + .id = -1, + }, +}; + +static struct pmic8058_leds_platform_data pm8058_surf_leds_data = { + .num_leds = ARRAY_SIZE(pmic8058_surf_leds), + .leds = pmic8058_surf_leds, +}; + +static struct pmic8058_led pmic8058_fluid_leds[] = { + [0] = { + .name = "keyboard-backlight", + .max_brightness = 15, + .id = PMIC8058_ID_LED_KB_LIGHT, + }, + [1] = { + .name = "flash:led_0", + .max_brightness = 15, + .id = PMIC8058_ID_FLASH_LED_0, + }, + [2] = { + .name = "flash:led_1", + .max_brightness = 15, + .id = PMIC8058_ID_FLASH_LED_1, + }, +}; + +static struct pmic8058_leds_platform_data pm8058_fluid_leds_data = { + .num_leds = ARRAY_SIZE(pmic8058_fluid_leds), + .leds = pmic8058_fluid_leds, +}; + +static struct pm8058_platform_data pm8058_7x30_data = { + .irq_base = PMIC8058_IRQ_BASE, + + .num_subdevs = ARRAY_SIZE(pm8058_subdevs), + .sub_devices = pm8058_subdevs, + .irq_trigger_flags = IRQF_TRIGGER_LOW, +}; + +static struct i2c_board_info pm8058_boardinfo[] __initdata = { + { + I2C_BOARD_INFO("pm8058-core", 0x55), + .irq = MSM_GPIO_TO_INT(PMIC_GPIO_INT), + .platform_data = &pm8058_7x30_data, + }, +}; + +static struct i2c_board_info cy8info[] __initdata = { + { + I2C_BOARD_INFO(CY_I2C_NAME, 0x24), + .platform_data = &cyttsp_data, +#ifndef CY_USE_TIMER + .irq = MSM_GPIO_TO_INT(CYTTSP_TS_GPIO_IRQ), +#endif /* CY_USE_TIMER */ + }, +}; + +static struct i2c_board_info msm_camera_boardinfo[] __initdata = { +#ifdef CONFIG_MT9D112 + { + I2C_BOARD_INFO("mt9d112", 0x78 >> 1), + }, +#endif +#ifdef CONFIG_WEBCAM_OV9726 + { + I2C_BOARD_INFO("ov9726", 0x10), + }, +#endif +#ifdef CONFIG_S5K3E2FX + { + I2C_BOARD_INFO("s5k3e2fx", 0x20 >> 1), + }, +#endif +#ifdef CONFIG_MT9P012 + { + I2C_BOARD_INFO("mt9p012", 0x6C >> 1), + }, +#endif +#ifdef CONFIG_VX6953 + { + I2C_BOARD_INFO("vx6953", 0x20), + }, +#endif +#ifdef CONFIG_MT9E013 + { + I2C_BOARD_INFO("mt9e013", 0x6C >> 2), + }, +#endif +#ifdef CONFIG_SN12M0PZ + { + I2C_BOARD_INFO("sn12m0pz", 0x34 >> 1), + }, +#endif +#if defined(CONFIG_MT9T013) || defined(CONFIG_SENSORS_MT9T013) + { + I2C_BOARD_INFO("mt9t013", 0x6C), + }, +#endif + +}; + +#ifdef CONFIG_MSM_CAMERA +#define CAM_STNDBY 143 +static uint32_t camera_off_vcm_gpio_table[] = { +GPIO_CFG(1, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* VCM */ +}; + +static uint32_t camera_off_gpio_table[] = { + /* parallel CAMERA interfaces */ + GPIO_CFG(0, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* RST */ + GPIO_CFG(2, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT2 */ + GPIO_CFG(3, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT3 */ + GPIO_CFG(4, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT4 */ + GPIO_CFG(5, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT5 */ + GPIO_CFG(6, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT6 */ + GPIO_CFG(7, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT7 */ + GPIO_CFG(8, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT8 */ + GPIO_CFG(9, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT9 */ + GPIO_CFG(10, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT10 */ + GPIO_CFG(11, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT11 */ + GPIO_CFG(12, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* PCLK */ + GPIO_CFG(13, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* HSYNC_IN */ + GPIO_CFG(14, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* VSYNC_IN */ + GPIO_CFG(15, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* MCLK */ +}; + +static uint32_t camera_on_vcm_gpio_table[] = { +GPIO_CFG(1, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_2MA), /* VCM */ +}; + +static uint32_t camera_on_gpio_table[] = { + /* parallel CAMERA interfaces */ + GPIO_CFG(0, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* RST */ + GPIO_CFG(2, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT2 */ + GPIO_CFG(3, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT3 */ + GPIO_CFG(4, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT4 */ + GPIO_CFG(5, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT5 */ + GPIO_CFG(6, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT6 */ + GPIO_CFG(7, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT7 */ + GPIO_CFG(8, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT8 */ + GPIO_CFG(9, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT9 */ + GPIO_CFG(10, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT10 */ + GPIO_CFG(11, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT11 */ + GPIO_CFG(12, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* PCLK */ + GPIO_CFG(13, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* HSYNC_IN */ + GPIO_CFG(14, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* VSYNC_IN */ + GPIO_CFG(15, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* MCLK */ +}; + +static uint32_t camera_off_gpio_fluid_table[] = { + /* FLUID: CAM_VGA_RST_N */ + GPIO_CFG(31, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* FLUID: CAMIF_STANDBY */ + GPIO_CFG(CAM_STNDBY, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA) +}; + +static uint32_t camera_on_gpio_fluid_table[] = { + /* FLUID: CAM_VGA_RST_N */ + GPIO_CFG(31, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + /* FLUID: CAMIF_STANDBY */ + GPIO_CFG(CAM_STNDBY, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA) +}; + +static void config_gpio_table(uint32_t *table, int len) +{ + int n, rc; + for (n = 0; n < len; n++) { + rc = gpio_tlmm_config(table[n], GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", + __func__, table[n], rc); + break; + } + } +} +static int config_camera_on_gpios(void) +{ + config_gpio_table(camera_on_gpio_table, + ARRAY_SIZE(camera_on_gpio_table)); + + if (adie_get_detected_codec_type() != TIMPANI_ID) + /* GPIO1 is shared also used in Timpani RF card so + only configure it for non-Timpani RF card */ + config_gpio_table(camera_on_vcm_gpio_table, + ARRAY_SIZE(camera_on_vcm_gpio_table)); + + if (machine_is_msm7x30_fluid()) { + config_gpio_table(camera_on_gpio_fluid_table, + ARRAY_SIZE(camera_on_gpio_fluid_table)); + /* FLUID: turn on 5V booster */ + gpio_set_value( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_FLASH_BOOST_ENABLE), 1); + /* FLUID: drive high to put secondary sensor to STANDBY */ + gpio_set_value(CAM_STNDBY, 1); + } + return 0; +} + +static void config_camera_off_gpios(void) +{ + config_gpio_table(camera_off_gpio_table, + ARRAY_SIZE(camera_off_gpio_table)); + + if (adie_get_detected_codec_type() != TIMPANI_ID) + /* GPIO1 is shared also used in Timpani RF card so + only configure it for non-Timpani RF card */ + config_gpio_table(camera_off_vcm_gpio_table, + ARRAY_SIZE(camera_off_vcm_gpio_table)); + + if (machine_is_msm7x30_fluid()) { + config_gpio_table(camera_off_gpio_fluid_table, + ARRAY_SIZE(camera_off_gpio_fluid_table)); + /* FLUID: turn off 5V booster */ + gpio_set_value( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_FLASH_BOOST_ENABLE), 0); + } +} + +struct resource msm_camera_resources[] = { + { + .start = 0xA6000000, + .end = 0xA6000000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_VFE, + .end = INT_VFE, + .flags = IORESOURCE_IRQ, + }, + { + .flags = IORESOURCE_DMA, + } +}; + +struct msm_camera_device_platform_data msm_camera_device_data = { + .camera_gpio_on = config_camera_on_gpios, + .camera_gpio_off = config_camera_off_gpios, + .ioext.camifpadphy = 0xAB000000, + .ioext.camifpadsz = 0x00000400, + .ioext.csiphy = 0xA6100000, + .ioext.csisz = 0x00000400, + .ioext.csiirq = INT_CSI, + .ioclk.mclk_clk_rate = 24000000, + .ioclk.vfe_clk_rate = 147456000, +}; + +static struct msm_camera_sensor_flash_src msm_flash_src_pwm = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_PWM, + ._fsrc.pwm_src.freq = 1000, + ._fsrc.pwm_src.max_load = 300, + ._fsrc.pwm_src.low_load = 30, + ._fsrc.pwm_src.high_load = 100, + ._fsrc.pwm_src.channel = 7, +}; + +#ifdef CONFIG_MT9D112 +static struct msm_camera_sensor_flash_data flash_mt9d112 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_pwm +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9d112_data = { + .sensor_name = "mt9d112", + .sensor_reset = 0, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_mt9d112, + .csi_if = 0 +}; + +static struct platform_device msm_camera_sensor_mt9d112 = { + .name = "msm_camera_mt9d112", + .dev = { + .platform_data = &msm_camera_sensor_mt9d112_data, + }, +}; +#endif + +#ifdef CONFIG_WEBCAM_OV9726 + +static struct msm_camera_sensor_platform_info ov9726_sensor_7630_info = { + .mount_angle = 90 +}; + +static struct msm_camera_sensor_flash_data flash_ov9726 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_pwm +}; +static struct msm_camera_sensor_info msm_camera_sensor_ov9726_data = { + .sensor_name = "ov9726", + .sensor_reset = 0, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_ov9726, + .sensor_platform_info = &ov9726_sensor_7630_info, + .csi_if = 1 +}; +struct platform_device msm_camera_sensor_ov9726 = { + .name = "msm_camera_ov9726", + .dev = { + .platform_data = &msm_camera_sensor_ov9726_data, + }, +}; +#endif + +#ifdef CONFIG_S5K3E2FX +static struct msm_camera_sensor_flash_data flash_s5k3e2fx = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_pwm, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_s5k3e2fx_data = { + .sensor_name = "s5k3e2fx", + .sensor_reset = 0, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_s5k3e2fx, + .csi_if = 0 +}; + +static struct platform_device msm_camera_sensor_s5k3e2fx = { + .name = "msm_camera_s5k3e2fx", + .dev = { + .platform_data = &msm_camera_sensor_s5k3e2fx_data, + }, +}; +#endif + +#ifdef CONFIG_MT9P012 +static struct msm_camera_sensor_flash_data flash_mt9p012 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_pwm +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9p012_data = { + .sensor_name = "mt9p012", + .sensor_reset = 0, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 1, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_mt9p012, + .csi_if = 0 +}; + +static struct platform_device msm_camera_sensor_mt9p012 = { + .name = "msm_camera_mt9p012", + .dev = { + .platform_data = &msm_camera_sensor_mt9p012_data, + }, +}; +#endif + +#ifdef CONFIG_MT9E013 +static struct msm_camera_sensor_platform_info mt9e013_sensor_7630_info = { + .mount_angle = 0 +}; + +static struct msm_camera_sensor_flash_data flash_mt9e013 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_pwm +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9e013_data = { + .sensor_name = "mt9e013", + .sensor_reset = 0, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 1, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_mt9e013, + .sensor_platform_info = &mt9e013_sensor_7630_info, + .csi_if = 1 +}; + +static struct platform_device msm_camera_sensor_mt9e013 = { + .name = "msm_camera_mt9e013", + .dev = { + .platform_data = &msm_camera_sensor_mt9e013_data, + }, +}; +#endif + +#ifdef CONFIG_VX6953 +static struct msm_camera_sensor_flash_data flash_vx6953 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_pwm +}; +static struct msm_camera_sensor_info msm_camera_sensor_vx6953_data = { + .sensor_name = "vx6953", + .sensor_reset = 0, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_vx6953, + .csi_if = 1 +}; +static struct platform_device msm_camera_sensor_vx6953 = { + .name = "msm_camera_vx6953", + .dev = { + .platform_data = &msm_camera_sensor_vx6953_data, + }, +}; +#endif + +#ifdef CONFIG_SN12M0PZ +static struct msm_camera_sensor_flash_src msm_flash_src_current_driver = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_CURRENT_DRIVER, + ._fsrc.current_driver_src.low_current = 210, + ._fsrc.current_driver_src.high_current = 700, + ._fsrc.current_driver_src.driver_channel = &pm8058_fluid_leds_data, +}; + +static struct msm_camera_sensor_flash_data flash_sn12m0pz = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_current_driver +}; +static struct msm_camera_sensor_info msm_camera_sensor_sn12m0pz_data = { + .sensor_name = "sn12m0pz", + .sensor_reset = 0, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 1, + .pdata = &msm_camera_device_data, + .flash_data = &flash_sn12m0pz, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .csi_if = 0 +}; + +static struct platform_device msm_camera_sensor_sn12m0pz = { + .name = "msm_camera_sn12m0pz", + .dev = { + .platform_data = &msm_camera_sensor_sn12m0pz_data, + }, +}; +#endif + +#ifdef CONFIG_MT9T013 +static struct msm_camera_sensor_flash_data flash_mt9t013 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src_pwm +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9t013_data = { + .sensor_name = "mt9t013", + .sensor_reset = 0, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_mt9t013, + .csi_if = 1 +}; + +static struct platform_device msm_camera_sensor_mt9t013 = { + .name = "msm_camera_mt9t013", + .dev = { + .platform_data = &msm_camera_sensor_mt9t013_data, + }, +}; +#endif + +#ifdef CONFIG_MSM_GEMINI +static struct resource msm_gemini_resources[] = { + { + .start = 0xA3A00000, + .end = 0xA3A00000 + 0x0150 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_JPEG, + .end = INT_JPEG, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_gemini_device = { + .name = "msm_gemini", + .resource = msm_gemini_resources, + .num_resources = ARRAY_SIZE(msm_gemini_resources), +}; +#endif + +#ifdef CONFIG_MSM_VPE +static struct resource msm_vpe_resources[] = { + { + .start = 0xAD200000, + .end = 0xAD200000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_VPE, + .end = INT_VPE, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_vpe_device = { + .name = "msm_vpe", + .id = 0, + .num_resources = ARRAY_SIZE(msm_vpe_resources), + .resource = msm_vpe_resources, +}; +#endif + +#endif /*CONFIG_MSM_CAMERA*/ + +#ifdef CONFIG_MSM7KV2_AUDIO +static uint32_t audio_pamp_gpio_config = + GPIO_CFG(82, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA); + +static uint32_t audio_fluid_icodec_tx_config = + GPIO_CFG(85, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA); + +static int __init snddev_poweramp_gpio_init(void) +{ + int rc; + + pr_info("snddev_poweramp_gpio_init \n"); + rc = gpio_tlmm_config(audio_pamp_gpio_config, GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR + "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, audio_pamp_gpio_config, rc); + } + return rc; +} + +void msm_snddev_tx_route_config(void) +{ + int rc; + + pr_debug("%s()\n", __func__); + + if (machine_is_msm7x30_fluid()) { + rc = gpio_tlmm_config(audio_fluid_icodec_tx_config, + GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR + "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, audio_fluid_icodec_tx_config, rc); + } else + gpio_set_value(85, 0); + } +} + +void msm_snddev_tx_route_deconfig(void) +{ + int rc; + + pr_debug("%s()\n", __func__); + + if (machine_is_msm7x30_fluid()) { + rc = gpio_tlmm_config(audio_fluid_icodec_tx_config, + GPIO_CFG_DISABLE); + if (rc) { + printk(KERN_ERR + "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, audio_fluid_icodec_tx_config, rc); + } + } +} + +void msm_snddev_poweramp_on(void) +{ + gpio_set_value(82, 1); /* enable spkr poweramp */ + pr_info("%s: power on amplifier\n", __func__); +} + +void msm_snddev_poweramp_off(void) +{ + gpio_set_value(82, 0); /* disable spkr poweramp */ + pr_info("%s: power off amplifier\n", __func__); +} + +static struct vreg *snddev_vreg_ncp, *snddev_vreg_gp4; + +void msm_snddev_hsed_voltage_on(void) +{ + int rc; + + snddev_vreg_gp4 = vreg_get(NULL, "gp4"); + if (IS_ERR(snddev_vreg_gp4)) { + pr_err("%s: vreg_get(%s) failed (%ld)\n", + __func__, "gp4", PTR_ERR(snddev_vreg_gp4)); + return; + } + rc = vreg_enable(snddev_vreg_gp4); + if (rc) + pr_err("%s: vreg_enable(gp4) failed (%d)\n", __func__, rc); + + snddev_vreg_ncp = vreg_get(NULL, "ncp"); + if (IS_ERR(snddev_vreg_ncp)) { + pr_err("%s: vreg_get(%s) failed (%ld)\n", + __func__, "ncp", PTR_ERR(snddev_vreg_ncp)); + return; + } + rc = vreg_enable(snddev_vreg_ncp); + if (rc) + pr_err("%s: vreg_enable(ncp) failed (%d)\n", __func__, rc); +} + +void msm_snddev_hsed_voltage_off(void) +{ + int rc; + + if (IS_ERR(snddev_vreg_ncp)) { + pr_err("%s: vreg_get(%s) failed (%ld)\n", + __func__, "ncp", PTR_ERR(snddev_vreg_ncp)); + return; + } + rc = vreg_disable(snddev_vreg_ncp); + if (rc) + pr_err("%s: vreg_disable(ncp) failed (%d)\n", __func__, rc); + vreg_put(snddev_vreg_ncp); + + if (IS_ERR(snddev_vreg_gp4)) { + pr_err("%s: vreg_get(%s) failed (%ld)\n", + __func__, "gp4", PTR_ERR(snddev_vreg_gp4)); + return; + } + rc = vreg_disable(snddev_vreg_gp4); + if (rc) + pr_err("%s: vreg_disable(gp4) failed (%d)\n", __func__, rc); + + vreg_put(snddev_vreg_gp4); + +} + +static unsigned aux_pcm_gpio_on[] = { + GPIO_CFG(138, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_DOUT */ + GPIO_CFG(139, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_DIN */ + GPIO_CFG(140, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_SYNC */ + GPIO_CFG(141, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_CLK */ +}; + +static int __init aux_pcm_gpio_init(void) +{ + int pin, rc; + + pr_info("aux_pcm_gpio_init \n"); + for (pin = 0; pin < ARRAY_SIZE(aux_pcm_gpio_on); pin++) { + rc = gpio_tlmm_config(aux_pcm_gpio_on[pin], + GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR + "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, aux_pcm_gpio_on[pin], rc); + } + } + return rc; +} + +static struct msm_gpio mi2s_clk_gpios[] = { + { GPIO_CFG(145, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "MI2S_SCLK"}, + { GPIO_CFG(144, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "MI2S_WS"}, + { GPIO_CFG(120, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "MI2S_MCLK_A"}, +}; + +static struct msm_gpio mi2s_rx_data_lines_gpios[] = { + { GPIO_CFG(121, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "MI2S_DATA_SD0_A"}, + { GPIO_CFG(122, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "MI2S_DATA_SD1_A"}, + { GPIO_CFG(123, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "MI2S_DATA_SD2_A"}, + { GPIO_CFG(146, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "MI2S_DATA_SD3"}, +}; + +static struct msm_gpio mi2s_tx_data_lines_gpios[] = { + { GPIO_CFG(146, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "MI2S_DATA_SD3"}, +}; + +int mi2s_config_clk_gpio(void) +{ + int rc = 0; + + rc = msm_gpios_request_enable(mi2s_clk_gpios, + ARRAY_SIZE(mi2s_clk_gpios)); + if (rc) { + pr_err("%s: enable mi2s clk gpios failed\n", + __func__); + return rc; + } + return 0; +} + +int mi2s_unconfig_data_gpio(u32 direction, u8 sd_line_mask) +{ + int i, rc = 0; + sd_line_mask &= MI2S_SD_LINE_MASK; + + switch (direction) { + case DIR_TX: + msm_gpios_disable_free(mi2s_tx_data_lines_gpios, 1); + break; + case DIR_RX: + i = 0; + while (sd_line_mask) { + if (sd_line_mask & 0x1) + msm_gpios_disable_free( + mi2s_rx_data_lines_gpios + i , 1); + sd_line_mask = sd_line_mask >> 1; + i++; + } + break; + default: + pr_err("%s: Invaild direction direction = %u\n", + __func__, direction); + rc = -EINVAL; + break; + } + return rc; +} + +int mi2s_config_data_gpio(u32 direction, u8 sd_line_mask) +{ + int i , rc = 0; + u8 sd_config_done_mask = 0; + + sd_line_mask &= MI2S_SD_LINE_MASK; + + switch (direction) { + case DIR_TX: + if ((sd_line_mask & MI2S_SD_0) || (sd_line_mask & MI2S_SD_1) || + (sd_line_mask & MI2S_SD_2) || !(sd_line_mask & MI2S_SD_3)) { + pr_err("%s: can not use SD0 or SD1 or SD2 for TX" + ".only can use SD3. sd_line_mask = 0x%x\n", + __func__ , sd_line_mask); + rc = -EINVAL; + } else { + rc = msm_gpios_request_enable(mi2s_tx_data_lines_gpios, + 1); + if (rc) + pr_err("%s: enable mi2s gpios for TX failed\n", + __func__); + } + break; + case DIR_RX: + i = 0; + while (sd_line_mask && (rc == 0)) { + if (sd_line_mask & 0x1) { + rc = msm_gpios_request_enable( + mi2s_rx_data_lines_gpios + i , 1); + if (rc) { + pr_err("%s: enable mi2s gpios for" + "RX failed. SD line = %s\n", + __func__, + (mi2s_rx_data_lines_gpios + i)->label); + mi2s_unconfig_data_gpio(DIR_RX, + sd_config_done_mask); + } else + sd_config_done_mask |= (1 << i); + } + sd_line_mask = sd_line_mask >> 1; + i++; + } + break; + default: + pr_err("%s: Invaild direction direction = %u\n", + __func__, direction); + rc = -EINVAL; + break; + } + return rc; +} + +int mi2s_unconfig_clk_gpio(void) +{ + msm_gpios_disable_free(mi2s_clk_gpios, ARRAY_SIZE(mi2s_clk_gpios)); + return 0; +} + +#endif /* CONFIG_MSM7KV2_AUDIO */ + +static int __init buses_init(void) +{ + if (gpio_tlmm_config(GPIO_CFG(PMIC_GPIO_INT, 1, GPIO_CFG_INPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_2MA), GPIO_CFG_ENABLE)) + pr_err("%s: gpio_tlmm_config (gpio=%d) failed\n", + __func__, PMIC_GPIO_INT); + + if (machine_is_msm7x30_fluid()) { + pm8058_7x30_data.sub_devices[PM8058_SUBDEV_KPD].platform_data + = &fluid_keypad_data; + } else { + pm8058_7x30_data.sub_devices[PM8058_SUBDEV_KPD].platform_data + = &surf_keypad_data; + pm8058_7x30_data.sub_devices[PM8058_SUBDEV_KPD].pdata_size + = sizeof(surf_keypad_data); + } + + i2c_register_board_info(6 /* I2C_SSBI ID */, pm8058_boardinfo, + ARRAY_SIZE(pm8058_boardinfo)); + + return 0; +} + +#define TIMPANI_RESET_GPIO 1 + +struct bahama_config_register{ + u8 reg; + u8 value; + u8 mask; +}; + +enum version{ + VER_1_0, + VER_2_0, + VER_UNSUPPORTED = 0xFF +}; + + +static struct vreg *vreg_marimba_1; +static struct vreg *vreg_marimba_2; +static struct vreg *vreg_marimba_3; + +static struct msm_gpio timpani_reset_gpio_cfg[] = { +{ GPIO_CFG(TIMPANI_RESET_GPIO, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "timpani_reset"} }; + +static u8 read_bahama_ver(void) +{ + int rc; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA }; + u8 bahama_version; + + rc = marimba_read_bit_mask(&config, 0x00, &bahama_version, 1, 0x1F); + if (rc < 0) { + printk(KERN_ERR + "%s: version read failed: %d\n", + __func__, rc); + return rc; + } else { + printk(KERN_INFO + "%s: version read got: 0x%x\n", + __func__, bahama_version); + } + + switch (bahama_version) { + case 0x08: /* varient of bahama v1 */ + case 0x10: + case 0x00: + return VER_1_0; + case 0x09: /* variant of bahama v2 */ + return VER_2_0; + default: + return VER_UNSUPPORTED; + } +} + +static int config_timpani_reset(void) +{ + int rc; + + rc = msm_gpios_request_enable(timpani_reset_gpio_cfg, + ARRAY_SIZE(timpani_reset_gpio_cfg)); + if (rc < 0) { + printk(KERN_ERR + "%s: msm_gpios_request_enable failed (%d)\n", + __func__, rc); + } + return rc; +} + +static unsigned int msm_timpani_setup_power(void) +{ + int rc; + + rc = config_timpani_reset(); + if (rc < 0) + goto out; + + rc = vreg_enable(vreg_marimba_1); + if (rc) { + printk(KERN_ERR "%s: vreg_enable() = %d\n", + __func__, rc); + goto out; + } + rc = vreg_enable(vreg_marimba_2); + if (rc) { + printk(KERN_ERR "%s: vreg_enable() = %d\n", + __func__, rc); + goto fail_disable_vreg_marimba_1; + } + + rc = gpio_direction_output(TIMPANI_RESET_GPIO, 1); + if (rc < 0) { + printk(KERN_ERR + "%s: gpio_direction_output failed (%d)\n", + __func__, rc); + msm_gpios_free(timpani_reset_gpio_cfg, + ARRAY_SIZE(timpani_reset_gpio_cfg)); + vreg_disable(vreg_marimba_2); + } else + goto out; + + +fail_disable_vreg_marimba_1: + vreg_disable(vreg_marimba_1); + +out: + return rc; +}; + +static void msm_timpani_shutdown_power(void) +{ + int rc; + + rc = vreg_disable(vreg_marimba_1); + if (rc) { + printk(KERN_ERR "%s: return val: %d\n", + __func__, rc); + } + rc = vreg_disable(vreg_marimba_2); + if (rc) { + printk(KERN_ERR "%s: return val: %d\n", + __func__, rc); + } + + rc = gpio_direction_output(TIMPANI_RESET_GPIO, 0); + if (rc < 0) { + printk(KERN_ERR + "%s: gpio_direction_output failed (%d)\n", + __func__, rc); + } + + msm_gpios_free(timpani_reset_gpio_cfg, + ARRAY_SIZE(timpani_reset_gpio_cfg)); +}; + +static unsigned int msm_bahama_core_config(int type) +{ + int rc = 0; + + if (type == BAHAMA_ID) { + + int i; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA }; + + const struct bahama_config_register v20_init[] = { + /* reg, value, mask */ + { 0xF4, 0x84, 0xFF }, /* AREG */ + { 0xF0, 0x04, 0xFF } /* DREG */ + }; + + if (read_bahama_ver() == VER_2_0) { + for (i = 0; i < ARRAY_SIZE(v20_init); i++) { + u8 value = v20_init[i].value; + rc = marimba_write_bit_mask(&config, + v20_init[i].reg, + &value, + sizeof(v20_init[i].value), + v20_init[i].mask); + if (rc < 0) { + printk(KERN_ERR + "%s: reg %d write failed: %d\n", + __func__, v20_init[i].reg, rc); + return rc; + } + printk(KERN_INFO "%s: reg 0x%02x value 0x%02x" + " mask 0x%02x\n", + __func__, v20_init[i].reg, + v20_init[i].value, v20_init[i].mask); + } + } + } + printk(KERN_INFO "core type: %d\n", type); + + return rc; +} + +static unsigned int msm_bahama_setup_power(void) +{ + int rc; + + rc = vreg_enable(vreg_marimba_3); + if (rc) { + printk(KERN_ERR "%s: vreg_enable() = %d\n", + __func__, rc); + } + + return rc; +}; + +static unsigned int msm_bahama_shutdown_power(int value) +{ + int rc = 0; + + if (value != BAHAMA_ID) { + rc = vreg_disable(vreg_marimba_3); + if (rc) { + printk(KERN_ERR "%s: return val: %d\n", + __func__, rc); + } + } + + return rc; +}; + +static struct msm_gpio marimba_svlte_config_clock[] = { + { GPIO_CFG(34, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "MARIMBA_SVLTE_CLOCK_ENABLE" }, +}; + +static unsigned int msm_marimba_gpio_config_svlte(int gpio_cfg_marimba) +{ + if (machine_is_msm8x55_svlte_surf() || + machine_is_msm8x55_svlte_ffa()) { + if (gpio_cfg_marimba) + gpio_set_value(GPIO_PIN + (marimba_svlte_config_clock->gpio_cfg), 1); + else + gpio_set_value(GPIO_PIN + (marimba_svlte_config_clock->gpio_cfg), 0); + } + + return 0; +}; + +static unsigned int msm_marimba_setup_power(void) +{ + int rc; + + rc = vreg_enable(vreg_marimba_1); + if (rc) { + printk(KERN_ERR "%s: vreg_enable() = %d \n", + __func__, rc); + goto out; + } + rc = vreg_enable(vreg_marimba_2); + if (rc) { + printk(KERN_ERR "%s: vreg_enable() = %d \n", + __func__, rc); + goto out; + } + + if (machine_is_msm8x55_svlte_surf() || machine_is_msm8x55_svlte_ffa()) { + rc = msm_gpios_request_enable(marimba_svlte_config_clock, + ARRAY_SIZE(marimba_svlte_config_clock)); + if (rc < 0) { + printk(KERN_ERR + "%s: msm_gpios_request_enable failed (%d)\n", + __func__, rc); + return rc; + } + + rc = gpio_direction_output(GPIO_PIN + (marimba_svlte_config_clock->gpio_cfg), 0); + if (rc < 0) { + printk(KERN_ERR + "%s: gpio_direction_output failed (%d)\n", + __func__, rc); + return rc; + } + } + +out: + return rc; +}; + +static void msm_marimba_shutdown_power(void) +{ + int rc; + + rc = vreg_disable(vreg_marimba_1); + if (rc) { + printk(KERN_ERR "%s: return val: %d\n", + __func__, rc); + } + rc = vreg_disable(vreg_marimba_2); + if (rc) { + printk(KERN_ERR "%s: return val: %d \n", + __func__, rc); + } +}; + +static int bahama_present(void) +{ + int id; + switch (id = adie_get_detected_connectivity_type()) { + case BAHAMA_ID: + return 1; + + case MARIMBA_ID: + return 0; + + case TIMPANI_ID: + default: + printk(KERN_ERR "%s: unexpected adie connectivity type: %d\n", + __func__, id); + return -ENODEV; + } +} + +struct vreg *fm_regulator; +static int fm_radio_setup(struct marimba_fm_platform_data *pdata) +{ + int rc; + uint32_t irqcfg; + const char *id = "FMPW"; + + int bahama_not_marimba = bahama_present(); + + if (bahama_not_marimba == -1) { + printk(KERN_WARNING "%s: bahama_present: %d\n", + __func__, bahama_not_marimba); + return -ENODEV; + } + if (bahama_not_marimba) + fm_regulator = vreg_get(NULL, "s3"); + else + fm_regulator = vreg_get(NULL, "s2"); + + if (IS_ERR(fm_regulator)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(fm_regulator)); + return -1; + } + if (!bahama_not_marimba) { + + rc = pmapp_vreg_level_vote(id, PMAPP_VREG_S2, 1300); + + if (rc < 0) { + printk(KERN_ERR "%s: voltage level vote failed (%d)\n", + __func__, rc); + return rc; + } + } + rc = vreg_enable(fm_regulator); + if (rc) { + printk(KERN_ERR "%s: vreg_enable() = %d\n", + __func__, rc); + return rc; + } + + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_DO, + PMAPP_CLOCK_VOTE_ON); + if (rc < 0) { + printk(KERN_ERR "%s: clock vote failed (%d)\n", + __func__, rc); + goto fm_clock_vote_fail; + } + /*Request the Clock Using GPIO34/AP2MDM_MRMBCK_EN in case + of svlte*/ + if (machine_is_msm8x55_svlte_surf() || + machine_is_msm8x55_svlte_ffa()) { + rc = marimba_gpio_config(1); + if (rc < 0) + printk(KERN_ERR "%s: clock enable for svlte : %d\n", + __func__, rc); + } + irqcfg = GPIO_CFG(147, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA); + rc = gpio_tlmm_config(irqcfg, GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, irqcfg, rc); + rc = -EIO; + goto fm_gpio_config_fail; + + } + return 0; +fm_gpio_config_fail: + pmapp_clock_vote(id, PMAPP_CLOCK_ID_DO, + PMAPP_CLOCK_VOTE_OFF); +fm_clock_vote_fail: + vreg_disable(fm_regulator); + return rc; + +}; + +static void fm_radio_shutdown(struct marimba_fm_platform_data *pdata) +{ + int rc; + const char *id = "FMPW"; + uint32_t irqcfg = GPIO_CFG(147, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_UP, + GPIO_CFG_2MA); + + int bahama_not_marimba = bahama_present(); + if (bahama_not_marimba == -1) { + printk(KERN_WARNING "%s: bahama_present: %d\n", + __func__, bahama_not_marimba); + return; + } + + rc = gpio_tlmm_config(irqcfg, GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, irqcfg, rc); + } + if (fm_regulator != NULL) { + rc = vreg_disable(fm_regulator); + + if (rc) { + printk(KERN_ERR "%s: return val: %d\n", + __func__, rc); + } + fm_regulator = NULL; + } + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_DO, + PMAPP_CLOCK_VOTE_OFF); + if (rc < 0) + printk(KERN_ERR "%s: clock_vote return val: %d\n", + __func__, rc); + + /*Disable the Clock Using GPIO34/AP2MDM_MRMBCK_EN in case + of svlte*/ + if (machine_is_msm8x55_svlte_surf() || + machine_is_msm8x55_svlte_ffa()) { + rc = marimba_gpio_config(0); + if (rc < 0) + printk(KERN_ERR "%s: clock disable for svlte : %d\n", + __func__, rc); + } + + + if (!bahama_not_marimba) { + rc = pmapp_vreg_level_vote(id, PMAPP_VREG_S2, 0); + + if (rc < 0) + printk(KERN_ERR "%s: vreg level vote return val: %d\n", + __func__, rc); + } +} + +static struct marimba_fm_platform_data marimba_fm_pdata = { + .fm_setup = fm_radio_setup, + .fm_shutdown = fm_radio_shutdown, + .irq = MSM_GPIO_TO_INT(147), + .vreg_s2 = NULL, + .vreg_xo_out = NULL, + .is_fm_soc_i2s_master = false, + .config_i2s_gpio = NULL, +}; + + +/* Slave id address for FM/CDC/QMEMBIST + * Values can be programmed using Marimba slave id 0 + * should there be a conflict with other I2C devices + * */ +#define MARIMBA_SLAVE_ID_FM_ADDR 0x2A +#define MARIMBA_SLAVE_ID_CDC_ADDR 0x77 +#define MARIMBA_SLAVE_ID_QMEMBIST_ADDR 0X66 + +#define BAHAMA_SLAVE_ID_FM_ADDR 0x2A +#define BAHAMA_SLAVE_ID_QMEMBIST_ADDR 0x7B + +static const char *tsadc_id = "MADC"; +static const char *vregs_tsadc_name[] = { + "gp12", + "s2", +}; +static struct vreg *vregs_tsadc[ARRAY_SIZE(vregs_tsadc_name)]; + +static const char *vregs_timpani_tsadc_name[] = { + "s3", + "gp12", + "gp16" +}; +static struct vreg *vregs_timpani_tsadc[ARRAY_SIZE(vregs_timpani_tsadc_name)]; + +static int marimba_tsadc_power(int vreg_on) +{ + int i, rc = 0; + int tsadc_adie_type = adie_get_detected_codec_type(); + + if (tsadc_adie_type == TIMPANI_ID) { + for (i = 0; i < ARRAY_SIZE(vregs_timpani_tsadc_name); i++) { + if (!vregs_timpani_tsadc[i]) { + pr_err("%s: vreg_get %s failed(%d)\n", + __func__, vregs_timpani_tsadc_name[i], rc); + goto vreg_fail; + } + + rc = vreg_on ? vreg_enable(vregs_timpani_tsadc[i]) : + vreg_disable(vregs_timpani_tsadc[i]); + if (rc < 0) { + pr_err("%s: vreg %s %s failed(%d)\n", + __func__, vregs_timpani_tsadc_name[i], + vreg_on ? "enable" : "disable", rc); + goto vreg_fail; + } + } + /* Vote for D0 and D1 buffer */ + rc = pmapp_clock_vote(tsadc_id, PMAPP_CLOCK_ID_D1, + vreg_on ? PMAPP_CLOCK_VOTE_ON : PMAPP_CLOCK_VOTE_OFF); + if (rc) { + pr_err("%s: unable to %svote for d1 clk\n", + __func__, vreg_on ? "" : "de-"); + goto do_vote_fail; + } + rc = pmapp_clock_vote(tsadc_id, PMAPP_CLOCK_ID_DO, + vreg_on ? PMAPP_CLOCK_VOTE_ON : PMAPP_CLOCK_VOTE_OFF); + if (rc) { + pr_err("%s: unable to %svote for d1 clk\n", + __func__, vreg_on ? "" : "de-"); + goto do_vote_fail; + } + } else if (tsadc_adie_type == MARIMBA_ID) { + for (i = 0; i < ARRAY_SIZE(vregs_tsadc_name); i++) { + if (!vregs_tsadc[i]) { + pr_err("%s: vreg_get %s failed (%d)\n", + __func__, vregs_tsadc_name[i], rc); + goto vreg_fail; + } + + rc = vreg_on ? vreg_enable(vregs_tsadc[i]) : + vreg_disable(vregs_tsadc[i]); + if (rc < 0) { + pr_err("%s: vreg %s %s failed (%d)\n", + __func__, vregs_tsadc_name[i], + vreg_on ? "enable" : "disable", rc); + goto vreg_fail; + } + } + /* If marimba vote for DO buffer */ + rc = pmapp_clock_vote(tsadc_id, PMAPP_CLOCK_ID_DO, + vreg_on ? PMAPP_CLOCK_VOTE_ON : PMAPP_CLOCK_VOTE_OFF); + if (rc) { + pr_err("%s: unable to %svote for d0 clk\n", + __func__, vreg_on ? "" : "de-"); + goto do_vote_fail; + } + } else { + pr_err("%s:Adie %d not supported\n", + __func__, tsadc_adie_type); + return -ENODEV; + } + + msleep(5); /* ensure power is stable */ + + return 0; + +do_vote_fail: +vreg_fail: + while (i) { + if (vreg_on) { + if (tsadc_adie_type == TIMPANI_ID) + vreg_disable(vregs_timpani_tsadc[--i]); + else if (tsadc_adie_type == MARIMBA_ID) + vreg_disable(vregs_tsadc[--i]); + } else { + if (tsadc_adie_type == TIMPANI_ID) + vreg_enable(vregs_timpani_tsadc[--i]); + else if (tsadc_adie_type == MARIMBA_ID) + vreg_enable(vregs_tsadc[--i]); + } + } + + return rc; +} + +static int marimba_tsadc_vote(int vote_on) +{ + int rc = 0; + + if (adie_get_detected_codec_type() == MARIMBA_ID) { + int level = vote_on ? 1300 : 0; + rc = pmapp_vreg_level_vote(tsadc_id, PMAPP_VREG_S2, level); + if (rc < 0) + pr_err("%s: vreg level %s failed (%d)\n", + __func__, vote_on ? "on" : "off", rc); + } + + return rc; +} + +static int marimba_tsadc_init(void) +{ + int i, rc = 0; + int tsadc_adie_type = adie_get_detected_codec_type(); + + if (tsadc_adie_type == TIMPANI_ID) { + for (i = 0; i < ARRAY_SIZE(vregs_timpani_tsadc_name); i++) { + vregs_timpani_tsadc[i] = vreg_get(NULL, + vregs_timpani_tsadc_name[i]); + if (IS_ERR(vregs_timpani_tsadc[i])) { + pr_err("%s: vreg get %s failed (%ld)\n", + __func__, vregs_timpani_tsadc_name[i], + PTR_ERR(vregs_timpani_tsadc[i])); + rc = PTR_ERR(vregs_timpani_tsadc[i]); + goto vreg_get_fail; + } + } + } else if (tsadc_adie_type == MARIMBA_ID) { + for (i = 0; i < ARRAY_SIZE(vregs_tsadc_name); i++) { + vregs_tsadc[i] = vreg_get(NULL, vregs_tsadc_name[i]); + if (IS_ERR(vregs_tsadc[i])) { + pr_err("%s: vreg get %s failed (%ld)\n", + __func__, vregs_tsadc_name[i], + PTR_ERR(vregs_tsadc[i])); + rc = PTR_ERR(vregs_tsadc[i]); + goto vreg_get_fail; + } + } + } else { + pr_err("%s:Adie %d not supported\n", + __func__, tsadc_adie_type); + return -ENODEV; + } + + return 0; + +vreg_get_fail: + while (i) { + if (tsadc_adie_type == TIMPANI_ID) + vreg_put(vregs_timpani_tsadc[--i]); + else if (tsadc_adie_type == MARIMBA_ID) + vreg_put(vregs_tsadc[--i]); + } + return rc; +} + +static int marimba_tsadc_exit(void) +{ + int i, rc = 0; + int tsadc_adie_type = adie_get_detected_codec_type(); + + if (tsadc_adie_type == TIMPANI_ID) { + for (i = 0; i < ARRAY_SIZE(vregs_timpani_tsadc_name); i++) { + if (vregs_tsadc[i]) + vreg_put(vregs_timpani_tsadc[i]); + } + } else if (tsadc_adie_type == MARIMBA_ID) { + for (i = 0; i < ARRAY_SIZE(vregs_tsadc_name); i++) { + if (vregs_tsadc[i]) + vreg_put(vregs_tsadc[i]); + } + rc = pmapp_vreg_level_vote(tsadc_id, PMAPP_VREG_S2, 0); + if (rc < 0) + pr_err("%s: vreg level off failed (%d)\n", + __func__, rc); + } else { + pr_err("%s:Adie %d not supported\n", + __func__, tsadc_adie_type); + rc = -ENODEV; + } + + return rc; +} + + +static struct msm_ts_platform_data msm_ts_data = { + .min_x = 0, + .max_x = 4096, + .min_y = 0, + .max_y = 4096, + .min_press = 0, + .max_press = 255, + .inv_x = 4096, + .inv_y = 4096, + .can_wakeup = false, +}; + +static struct marimba_tsadc_platform_data marimba_tsadc_pdata = { + .marimba_tsadc_power = marimba_tsadc_power, + .init = marimba_tsadc_init, + .exit = marimba_tsadc_exit, + .level_vote = marimba_tsadc_vote, + .tsadc_prechg_en = true, + .can_wakeup = false, + .setup = { + .pen_irq_en = true, + .tsadc_en = true, + }, + .params2 = { + .input_clk_khz = 2400, + .sample_prd = TSADC_CLK_3, + }, + .params3 = { + .prechg_time_nsecs = 6400, + .stable_time_nsecs = 6400, + .tsadc_test_mode = 0, + }, + .tssc_data = &msm_ts_data, +}; + +static struct vreg *vreg_codec_s4; +static int msm_marimba_codec_power(int vreg_on) +{ + int rc = 0; + + if (!vreg_codec_s4) { + + vreg_codec_s4 = vreg_get(NULL, "s4"); + + if (IS_ERR(vreg_codec_s4)) { + printk(KERN_ERR "%s: vreg_get() failed (%ld)\n", + __func__, PTR_ERR(vreg_codec_s4)); + rc = PTR_ERR(vreg_codec_s4); + goto vreg_codec_s4_fail; + } + } + + if (vreg_on) { + rc = vreg_enable(vreg_codec_s4); + if (rc) + printk(KERN_ERR "%s: vreg_enable() = %d \n", + __func__, rc); + goto vreg_codec_s4_fail; + } else { + rc = vreg_disable(vreg_codec_s4); + if (rc) + printk(KERN_ERR "%s: vreg_disable() = %d \n", + __func__, rc); + goto vreg_codec_s4_fail; + } + +vreg_codec_s4_fail: + return rc; +} + +static struct marimba_codec_platform_data mariba_codec_pdata = { + .marimba_codec_power = msm_marimba_codec_power, +#ifdef CONFIG_MARIMBA_CODEC + .snddev_profile_init = msm_snddev_init, +#endif +}; + +static struct marimba_platform_data marimba_pdata = { + .slave_id[MARIMBA_SLAVE_ID_FM] = MARIMBA_SLAVE_ID_FM_ADDR, + .slave_id[MARIMBA_SLAVE_ID_CDC] = MARIMBA_SLAVE_ID_CDC_ADDR, + .slave_id[MARIMBA_SLAVE_ID_QMEMBIST] = MARIMBA_SLAVE_ID_QMEMBIST_ADDR, + .slave_id[SLAVE_ID_BAHAMA_FM] = BAHAMA_SLAVE_ID_FM_ADDR, + .slave_id[SLAVE_ID_BAHAMA_QMEMBIST] = BAHAMA_SLAVE_ID_QMEMBIST_ADDR, + .marimba_setup = msm_marimba_setup_power, + .marimba_shutdown = msm_marimba_shutdown_power, + .bahama_setup = msm_bahama_setup_power, + .bahama_shutdown = msm_bahama_shutdown_power, + .marimba_gpio_config = msm_marimba_gpio_config_svlte, + .bahama_core_config = msm_bahama_core_config, + .fm = &marimba_fm_pdata, + .codec = &mariba_codec_pdata, + .tsadc_ssbi_adap = MARIMBA_SSBI_ADAP, +}; + +static void __init msm7x30_init_marimba(void) +{ + int rc; + + vreg_marimba_1 = vreg_get(NULL, "s3"); + if (IS_ERR(vreg_marimba_1)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_marimba_1)); + return; + } + rc = vreg_set_level(vreg_marimba_1, 1800); + + vreg_marimba_2 = vreg_get(NULL, "gp16"); + if (IS_ERR(vreg_marimba_1)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_marimba_1)); + return; + } + rc = vreg_set_level(vreg_marimba_2, 1200); + + vreg_marimba_3 = vreg_get(NULL, "usb2"); + if (IS_ERR(vreg_marimba_3)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_marimba_3)); + return; + } + rc = vreg_set_level(vreg_marimba_3, 1800); +} + +static struct marimba_codec_platform_data timpani_codec_pdata = { + .marimba_codec_power = msm_marimba_codec_power, +#ifdef CONFIG_TIMPANI_CODEC + .snddev_profile_init = msm_snddev_init_timpani, +#endif +}; + +static struct marimba_platform_data timpani_pdata = { + .slave_id[MARIMBA_SLAVE_ID_CDC] = MARIMBA_SLAVE_ID_CDC_ADDR, + .slave_id[MARIMBA_SLAVE_ID_QMEMBIST] = MARIMBA_SLAVE_ID_QMEMBIST_ADDR, + .marimba_setup = msm_timpani_setup_power, + .marimba_shutdown = msm_timpani_shutdown_power, + .codec = &timpani_codec_pdata, + .tsadc = &marimba_tsadc_pdata, + .tsadc_ssbi_adap = MARIMBA_SSBI_ADAP, +}; + +#define TIMPANI_I2C_SLAVE_ADDR 0xD + +static struct i2c_board_info msm_i2c_gsbi7_timpani_info[] = { + { + I2C_BOARD_INFO("timpani", TIMPANI_I2C_SLAVE_ADDR), + .platform_data = &timpani_pdata, + }, +}; + +#ifdef CONFIG_MSM7KV2_AUDIO +static struct resource msm_aictl_resources[] = { + { + .name = "aictl", + .start = 0xa5000100, + .end = 0xa5000100, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_mi2s_resources[] = { + { + .name = "hdmi", + .start = 0xac900000, + .end = 0xac900038, + .flags = IORESOURCE_MEM, + }, + { + .name = "codec_rx", + .start = 0xac940040, + .end = 0xac940078, + .flags = IORESOURCE_MEM, + }, + { + .name = "codec_tx", + .start = 0xac980080, + .end = 0xac9800B8, + .flags = IORESOURCE_MEM, + } + +}; + +static struct msm_lpa_platform_data lpa_pdata = { + .obuf_hlb_size = 0x2BFF8, + .dsp_proc_id = 0, + .app_proc_id = 2, + .nosb_config = { + .llb_min_addr = 0, + .llb_max_addr = 0x3ff8, + .sb_min_addr = 0, + .sb_max_addr = 0, + }, + .sb_config = { + .llb_min_addr = 0, + .llb_max_addr = 0x37f8, + .sb_min_addr = 0x3800, + .sb_max_addr = 0x3ff8, + } +}; + +static struct resource msm_lpa_resources[] = { + { + .name = "lpa", + .start = 0xa5000000, + .end = 0xa50000a0, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_aux_pcm_resources[] = { + + { + .name = "aux_codec_reg_addr", + .start = 0xac9c00c0, + .end = 0xac9c00c8, + .flags = IORESOURCE_MEM, + }, + { + .name = "aux_pcm_dout", + .start = 138, + .end = 138, + .flags = IORESOURCE_IO, + }, + { + .name = "aux_pcm_din", + .start = 139, + .end = 139, + .flags = IORESOURCE_IO, + }, + { + .name = "aux_pcm_syncout", + .start = 140, + .end = 140, + .flags = IORESOURCE_IO, + }, + { + .name = "aux_pcm_clkin_a", + .start = 141, + .end = 141, + .flags = IORESOURCE_IO, + }, +}; + +static struct platform_device msm_aux_pcm_device = { + .name = "msm_aux_pcm", + .id = 0, + .num_resources = ARRAY_SIZE(msm_aux_pcm_resources), + .resource = msm_aux_pcm_resources, +}; + +struct platform_device msm_aictl_device = { + .name = "audio_interct", + .id = 0, + .num_resources = ARRAY_SIZE(msm_aictl_resources), + .resource = msm_aictl_resources, +}; + +struct platform_device msm_mi2s_device = { + .name = "mi2s", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mi2s_resources), + .resource = msm_mi2s_resources, +}; + +struct platform_device msm_lpa_device = { + .name = "lpa", + .id = 0, + .num_resources = ARRAY_SIZE(msm_lpa_resources), + .resource = msm_lpa_resources, + .dev = { + .platform_data = &lpa_pdata, + }, +}; +#endif /* CONFIG_MSM7KV2_AUDIO */ + +#define DEC0_FORMAT ((1<> 12; + + qsd_spi_resources[4].start = DMOV_USB_CHAN; + qsd_spi_resources[4].end = DMOV_TSIF_CHAN; + + switch (spi_mux) { + case (1): + qsd_spi_resources[5].start = DMOV_HSUART1_RX_CRCI; + qsd_spi_resources[5].end = DMOV_HSUART1_TX_CRCI; + break; + case (2): + qsd_spi_resources[5].start = DMOV_HSUART2_RX_CRCI; + qsd_spi_resources[5].end = DMOV_HSUART2_TX_CRCI; + break; + case (3): + qsd_spi_resources[5].start = DMOV_CE_OUT_CRCI; + qsd_spi_resources[5].end = DMOV_CE_IN_CRCI; + break; + default: + ret = -ENOENT; + } + + iounmap(ct_adm_base); + + return ret; +} + +static struct platform_device qsd_device_spi = { + .name = "spi_qsd", + .id = 0, + .num_resources = ARRAY_SIZE(qsd_spi_resources), + .resource = qsd_spi_resources, +}; + +#ifdef CONFIG_SPI_QSD +static struct spi_board_info lcdc_sharp_spi_board_info[] __initdata = { + { + .modalias = "lcdc_sharp_ls038y7dx01", + .mode = SPI_MODE_1, + .bus_num = 0, + .chip_select = 0, + .max_speed_hz = 26331429, + } +}; +static struct spi_board_info lcdc_toshiba_spi_board_info[] __initdata = { + { + .modalias = "lcdc_toshiba_ltm030dd40", + .mode = SPI_MODE_3|SPI_CS_HIGH, + .bus_num = 0, + .chip_select = 0, + .max_speed_hz = 9963243, + } +}; +#endif + +static struct msm_gpio qsd_spi_gpio_config_data[] = { + { GPIO_CFG(45, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_clk" }, + { GPIO_CFG(46, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_cs0" }, + { GPIO_CFG(47, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "spi_mosi" }, + { GPIO_CFG(48, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_miso" }, +}; + +static int msm_qsd_spi_gpio_config(void) +{ + return msm_gpios_request_enable(qsd_spi_gpio_config_data, + ARRAY_SIZE(qsd_spi_gpio_config_data)); +} + +static void msm_qsd_spi_gpio_release(void) +{ + msm_gpios_disable_free(qsd_spi_gpio_config_data, + ARRAY_SIZE(qsd_spi_gpio_config_data)); +} + +static struct msm_spi_platform_data qsd_spi_pdata = { + .max_clock_speed = 26331429, + .gpio_config = msm_qsd_spi_gpio_config, + .gpio_release = msm_qsd_spi_gpio_release, + .dma_config = msm_qsd_spi_dma_config, +}; + +static void __init msm_qsd_spi_init(void) +{ + qsd_device_spi.dev.platform_data = &qsd_spi_pdata; +} + +#ifdef CONFIG_USB_EHCI_MSM_72K +static void msm_hsusb_vbus_power(unsigned phy_info, int on) +{ + int rc; + static int vbus_is_on; + struct pm8058_gpio usb_vbus = { + .direction = PM_GPIO_DIR_OUT, + .pull = PM_GPIO_PULL_NO, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .vin_sel = 2, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }; + + /* If VBUS is already on (or off), do nothing. */ + if (unlikely(on == vbus_is_on)) + return; + + if (on) { + rc = pm8058_gpio_config(36, &usb_vbus); + if (rc) { + pr_err("%s PMIC GPIO 36 write failed\n", __func__); + return; + } + } else { + gpio_set_value_cansleep(PM8058_GPIO_PM_TO_SYS(36), 0); + } + + vbus_is_on = on; +} + +static struct msm_usb_host_platform_data msm_usb_host_pdata = { + .phy_info = (USB_PHY_INTEGRATED | USB_PHY_MODEL_45NM), + .vbus_power = msm_hsusb_vbus_power, + .power_budget = 180, +}; +#endif + +#ifdef CONFIG_USB_MSM_OTG_72K +static int hsusb_rpc_connect(int connect) +{ + if (connect) + return msm_hsusb_rpc_connect(); + else + return msm_hsusb_rpc_close(); +} +#endif + +#ifdef CONFIG_USB_MSM_OTG_72K +static struct vreg *vreg_3p3; +static int msm_hsusb_ldo_init(int init) +{ + uint32_t version = 0; + int def_vol = 3400; + + version = socinfo_get_version(); + + if (SOCINFO_VERSION_MAJOR(version) >= 2 && + SOCINFO_VERSION_MINOR(version) >= 1) { + def_vol = 3075; + pr_debug("%s: default voltage:%d\n", __func__, def_vol); + } + + if (init) { + vreg_3p3 = vreg_get(NULL, "usb"); + if (IS_ERR(vreg_3p3)) + return PTR_ERR(vreg_3p3); + vreg_set_level(vreg_3p3, def_vol); + } else + vreg_put(vreg_3p3); + + return 0; +} + +static int msm_hsusb_ldo_enable(int enable) +{ + static int ldo_status; + + if (!vreg_3p3 || IS_ERR(vreg_3p3)) + return -ENODEV; + + if (ldo_status == enable) + return 0; + + ldo_status = enable; + + if (enable) + return vreg_enable(vreg_3p3); + + return vreg_disable(vreg_3p3); +} + +static int msm_hsusb_ldo_set_voltage(int mV) +{ + static int cur_voltage = 3400; + + if (!vreg_3p3 || IS_ERR(vreg_3p3)) + return -ENODEV; + + if (cur_voltage == mV) + return 0; + + cur_voltage = mV; + + pr_debug("%s: (%d)\n", __func__, mV); + + return vreg_set_level(vreg_3p3, mV); +} +#endif + +#ifndef CONFIG_USB_EHCI_MSM_72K +static int msm_hsusb_pmic_notif_init(void (*callback)(int online), int init); +#endif +static struct msm_otg_platform_data msm_otg_pdata = { + .rpc_connect = hsusb_rpc_connect, + +#ifndef CONFIG_USB_EHCI_MSM_72K + .pmic_vbus_notif_init = msm_hsusb_pmic_notif_init, +#else + .vbus_power = msm_hsusb_vbus_power, +#endif + .core_clk = 1, + .pemp_level = PRE_EMPHASIS_WITH_20_PERCENT, + .cdr_autoreset = CDR_AUTO_RESET_DISABLE, + .drv_ampl = HS_DRV_AMPLITUDE_DEFAULT, + .se1_gating = SE1_GATING_DISABLE, + .chg_vbus_draw = hsusb_chg_vbus_draw, + .chg_connected = hsusb_chg_connected, + .chg_init = hsusb_chg_init, + .ldo_enable = msm_hsusb_ldo_enable, + .ldo_init = msm_hsusb_ldo_init, + .ldo_set_voltage = msm_hsusb_ldo_set_voltage, +}; + +#ifdef CONFIG_USB_GADGET +static struct msm_hsusb_gadget_platform_data msm_gadget_pdata = { + .is_phy_status_timer_on = 1, +}; +#endif +#ifndef CONFIG_USB_EHCI_MSM_72K +typedef void (*notify_vbus_state) (int); +notify_vbus_state notify_vbus_state_func_ptr; +int vbus_on_irq; +static irqreturn_t pmic_vbus_on_irq(int irq, void *data) +{ + pr_info("%s: vbus notification from pmic\n", __func__); + + (*notify_vbus_state_func_ptr) (1); + + return IRQ_HANDLED; +} +static int msm_hsusb_pmic_notif_init(void (*callback)(int online), int init) +{ + int ret; + + if (init) { + if (!callback) + return -ENODEV; + + notify_vbus_state_func_ptr = callback; + vbus_on_irq = platform_get_irq_byname(&msm_device_otg, + "vbus_on"); + if (vbus_on_irq <= 0) { + pr_err("%s: unable to get vbus on irq\n", __func__); + return -ENODEV; + } + + ret = request_any_context_irq(vbus_on_irq, pmic_vbus_on_irq, + IRQF_TRIGGER_RISING, "msm_otg_vbus_on", NULL); + if (ret < 0) { + pr_info("%s: request_irq for vbus_on" + "interrupt failed\n", __func__); + return ret; + } + msm_otg_pdata.pmic_vbus_irq = vbus_on_irq; + return 0; + } else { + free_irq(vbus_on_irq, 0); + notify_vbus_state_func_ptr = NULL; + return 0; + } +} +#endif + +static struct android_pmem_platform_data android_pmem_pdata = { + .name = "pmem", + .allocator_type = PMEM_ALLOCATORTYPE_ALLORNOTHING, + .cached = 1, + .memory_type = MEMTYPE_EBI0, +}; + +static struct platform_device android_pmem_device = { + .name = "android_pmem", + .id = 0, + .dev = { .platform_data = &android_pmem_pdata }, +}; + +#ifndef CONFIG_SPI_QSD +static int lcdc_gpio_array_num[] = { + 45, /* spi_clk */ + 46, /* spi_cs */ + 47, /* spi_mosi */ + 48, /* spi_miso */ + }; + +static struct msm_gpio lcdc_gpio_config_data[] = { + { GPIO_CFG(45, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_clk" }, + { GPIO_CFG(46, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_cs0" }, + { GPIO_CFG(47, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_mosi" }, + { GPIO_CFG(48, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_miso" }, +}; + +static void lcdc_config_gpios(int enable) +{ + if (enable) { + msm_gpios_request_enable(lcdc_gpio_config_data, + ARRAY_SIZE( + lcdc_gpio_config_data)); + } else + msm_gpios_disable_free(lcdc_gpio_config_data, + ARRAY_SIZE( + lcdc_gpio_config_data)); +} +#endif + +static struct msm_panel_common_pdata lcdc_sharp_panel_data = { +#ifndef CONFIG_SPI_QSD + .panel_config_gpio = lcdc_config_gpios, + .gpio_num = lcdc_gpio_array_num, +#endif + .gpio = 2, /* LPG PMIC_GPIO26 channel number */ +}; + +static struct platform_device lcdc_sharp_panel_device = { + .name = "lcdc_sharp_wvga", + .id = 0, + .dev = { + .platform_data = &lcdc_sharp_panel_data, + } +}; + +static struct msm_gpio dtv_panel_irq_gpios[] = { + { GPIO_CFG(18, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), + "hdmi_int" }, +}; + +static struct msm_gpio dtv_panel_gpios[] = { + { GPIO_CFG(120, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "wca_mclk" }, + { GPIO_CFG(121, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "wca_sd0" }, + { GPIO_CFG(122, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "wca_sd1" }, + { GPIO_CFG(123, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "wca_sd2" }, + { GPIO_CFG(124, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "dtv_pclk" }, + { GPIO_CFG(125, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_en" }, + { GPIO_CFG(126, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_vsync" }, + { GPIO_CFG(127, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_hsync" }, + { GPIO_CFG(128, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data0" }, + { GPIO_CFG(129, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data1" }, + { GPIO_CFG(130, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data2" }, + { GPIO_CFG(131, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data3" }, + { GPIO_CFG(132, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data4" }, + { GPIO_CFG(160, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data5" }, + { GPIO_CFG(161, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data6" }, + { GPIO_CFG(162, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data7" }, + { GPIO_CFG(163, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data8" }, + { GPIO_CFG(164, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_data9" }, + { GPIO_CFG(165, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat10" }, + { GPIO_CFG(166, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat11" }, + { GPIO_CFG(167, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat12" }, + { GPIO_CFG(168, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat13" }, + { GPIO_CFG(169, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat14" }, + { GPIO_CFG(170, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat15" }, + { GPIO_CFG(171, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat16" }, + { GPIO_CFG(172, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat17" }, + { GPIO_CFG(173, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat18" }, + { GPIO_CFG(174, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat19" }, + { GPIO_CFG(175, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat20" }, + { GPIO_CFG(176, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat21" }, + { GPIO_CFG(177, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat22" }, + { GPIO_CFG(178, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_4MA), "dtv_dat23" }, +}; + + +#ifdef HDMI_RESET +static unsigned dtv_reset_gpio = + GPIO_CFG(37, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA); +#endif + +static int gpio_set(const char *label, const char *name, int level, int on) +{ + struct vreg *vreg = vreg_get(NULL, label); + int rc; + + if (IS_ERR(vreg)) { + rc = PTR_ERR(vreg); + pr_err("%s: vreg %s get failed (%d)\n", + __func__, name, rc); + return rc; + } + + rc = vreg_set_level(vreg, level); + if (rc) { + pr_err("%s: vreg %s set level failed (%d)\n", + __func__, name, rc); + return rc; + } + + if (on) + rc = vreg_enable(vreg); + else + rc = vreg_disable(vreg); + if (rc) + pr_err("%s: vreg %s enable failed (%d)\n", + __func__, name, rc); + return rc; +} + +#if defined(CONFIG_FB_MSM_HDMI_ADV7520_PANEL) || defined(CONFIG_BOSCH_BMA150) +/* there is an i2c address conflict between adv7520 and bma150 sensor after + * power up on fluid. As a solution, the default address of adv7520's packet + * memory is changed as soon as possible + */ +static int __init fluid_i2c_address_fixup(void) +{ + unsigned char wBuff[16]; + unsigned char rBuff[16]; + struct i2c_msg msgs[3]; + int res; + int rc = -EINVAL; + struct vreg *vreg_ldo8; + struct i2c_adapter *adapter; + + if (machine_is_msm7x30_fluid()) { + adapter = i2c_get_adapter(0); + if (!adapter) { + pr_err("%s: invalid i2c adapter\n", __func__); + return PTR_ERR(adapter); + } + + /* turn on LDO8 */ + vreg_ldo8 = vreg_get(NULL, "gp7"); + if (!vreg_ldo8) { + pr_err("%s: VREG L8 get failed\n", __func__); + goto adapter_put; + } + + rc = vreg_set_level(vreg_ldo8, 1800); + if (rc) { + pr_err("%s: VREG L8 set failed\n", __func__); + goto ldo8_put; + } + + rc = vreg_enable(vreg_ldo8); + if (rc) { + pr_err("%s: VREG L8 enable failed\n", __func__); + goto ldo8_put; + } + + /* change packet memory address to 0x74 */ + wBuff[0] = 0x45; + wBuff[1] = 0x74; + + msgs[0].addr = ADV7520_I2C_ADDR; + msgs[0].flags = 0; + msgs[0].buf = (unsigned char *) wBuff; + msgs[0].len = 2; + + res = i2c_transfer(adapter, msgs, 1); + if (res != 1) { + pr_err("%s: error writing adv7520\n", __func__); + goto ldo8_disable; + } + + /* powerdown adv7520 using bit 6 */ + /* i2c read first */ + wBuff[0] = 0x41; + + msgs[0].addr = ADV7520_I2C_ADDR; + msgs[0].flags = 0; + msgs[0].buf = (unsigned char *) wBuff; + msgs[0].len = 1; + + msgs[1].addr = ADV7520_I2C_ADDR; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = rBuff; + msgs[1].len = 1; + res = i2c_transfer(adapter, msgs, 2); + if (res != 2) { + pr_err("%s: error reading adv7520\n", __func__); + goto ldo8_disable; + } + + /* i2c write back */ + wBuff[0] = 0x41; + wBuff[1] = rBuff[0] | 0x40; + + msgs[0].addr = ADV7520_I2C_ADDR; + msgs[0].flags = 0; + msgs[0].buf = (unsigned char *) wBuff; + msgs[0].len = 2; + + res = i2c_transfer(adapter, msgs, 1); + if (res != 1) { + pr_err("%s: error writing adv7520\n", __func__); + goto ldo8_disable; + } + + /* for successful fixup, we release the i2c adapter */ + /* but leave ldo8 on so that the adv7520 is not repowered */ + i2c_put_adapter(adapter); + pr_info("%s: fluid i2c address conflict resolved\n", __func__); + } + return 0; + +ldo8_disable: + vreg_disable(vreg_ldo8); +ldo8_put: + vreg_put(vreg_ldo8); +adapter_put: + i2c_put_adapter(adapter); + return rc; +} +fs_initcall_sync(fluid_i2c_address_fixup); +#endif + +static int hdmi_comm_power(int on, int show) +{ + int rc = gpio_set("gp7", "LDO8", 1800, on); + if (rc) { + pr_warning("hdmi_comm_power: LDO8 gpio failed: rc=%d\n", rc); + return rc; + } + rc = gpio_set("gp4", "LDO10", 2600, on); + if (rc) + pr_warning("hdmi_comm_power: LDO10 gpio failed: rc=%d\n", rc); + if (show) + pr_info("%s: i2c comm: %d \n", __func__, on); + return rc; +} + +static int hdmi_init_irq(void) +{ + int rc = msm_gpios_enable(dtv_panel_irq_gpios, + ARRAY_SIZE(dtv_panel_irq_gpios)); + if (rc < 0) { + pr_err("%s: gpio enable failed: %d\n", __func__, rc); + return rc; + } + pr_info("%s\n", __func__); + + return 0; +} + +static int hdmi_enable_5v(int on) +{ + int pmic_gpio_hdmi_5v_en ; + + if (machine_is_msm8x55_svlte_surf() || machine_is_msm8x55_svlte_ffa() || + machine_is_msm7x30_fluid()) + pmic_gpio_hdmi_5v_en = PMIC_GPIO_HDMI_5V_EN_V2 ; + else + pmic_gpio_hdmi_5v_en = PMIC_GPIO_HDMI_5V_EN_V3 ; + + pr_info("%s: %d\n", __func__, on); + if (on) { + int rc; + rc = gpio_request(PM8058_GPIO_PM_TO_SYS(pmic_gpio_hdmi_5v_en), + "hdmi_5V_en"); + if (rc) { + pr_err("%s PMIC_GPIO_HDMI_5V_EN gpio_request failed\n", + __func__); + return rc; + } + gpio_set_value_cansleep( + PM8058_GPIO_PM_TO_SYS(pmic_gpio_hdmi_5v_en), 1); + } else { + gpio_set_value_cansleep( + PM8058_GPIO_PM_TO_SYS(pmic_gpio_hdmi_5v_en), 0); + gpio_free(PM8058_GPIO_PM_TO_SYS(pmic_gpio_hdmi_5v_en)); + } + return 0; +} + +static int hdmi_core_power(int on, int show) +{ + if (show) + pr_info("%s: %d \n", __func__, on); + return gpio_set("gp7", "LDO8", 1800, on); +} + +static int hdmi_cec_power(int on) +{ + pr_info("%s: %d \n", __func__, on); + return gpio_set("gp11", "LDO17", 2600, on); +} + +static bool hdmi_check_hdcp_hw_support(void) +{ + if (machine_is_msm7x30_fluid()) + return false; + else + return true; +} + +static int dtv_panel_power(int on) +{ + int flag_on = !!on; + static int dtv_power_save_on; + int rc; + + if (dtv_power_save_on == flag_on) + return 0; + + dtv_power_save_on = flag_on; + pr_info("%s: %d\n", __func__, on); + +#ifdef HDMI_RESET + if (on) { + /* reset Toshiba WeGA chip -- toggle reset pin -- gpio_180 */ + rc = gpio_tlmm_config(dtv_reset_gpio, GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", + __func__, dtv_reset_gpio, rc); + return rc; + } + + /* bring reset line low to hold reset*/ + gpio_set_value(37, 0); + } +#endif + + if (on) { + rc = msm_gpios_enable(dtv_panel_gpios, + ARRAY_SIZE(dtv_panel_gpios)); + if (rc < 0) { + printk(KERN_ERR "%s: gpio enable failed: %d\n", + __func__, rc); + return rc; + } + } else { + rc = msm_gpios_disable(dtv_panel_gpios, + ARRAY_SIZE(dtv_panel_gpios)); + if (rc < 0) { + printk(KERN_ERR "%s: gpio disable failed: %d\n", + __func__, rc); + return rc; + } + } + + mdelay(5); /* ensure power is stable */ + +#ifdef HDMI_RESET + if (on) { + gpio_set_value(37, 1); /* bring reset line high */ + mdelay(10); /* 10 msec before IO can be accessed */ + } +#endif + + return rc; +} + +static struct lcdc_platform_data dtv_pdata = { + .lcdc_power_save = dtv_panel_power, +}; + +static struct msm_serial_hs_platform_data msm_uart_dm1_pdata = { + .inject_rx_on_wakeup = 1, + .rx_to_inject = 0xFD, +}; + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; + +static int msm_fb_detect_panel(const char *name) +{ + if (machine_is_msm7x30_fluid()) { + if (!strcmp(name, "lcdc_sharp_wvga_pt")) + return 0; + } else { + if (!strncmp(name, "mddi_toshiba_wvga_pt", 20)) + return -EPERM; + else if (!strncmp(name, "lcdc_toshiba_wvga_pt", 20)) + return 0; + else if (!strcmp(name, "mddi_orise")) + return -EPERM; + else if (!strcmp(name, "mddi_quickvx")) + return -EPERM; + } + return -ENODEV; +} + +static struct msm_fb_platform_data msm_fb_pdata = { + .detect_client = msm_fb_detect_panel, + .mddi_prescan = 1, +}; + +static struct platform_device msm_fb_device = { + .name = "msm_fb", + .id = 0, + .num_resources = ARRAY_SIZE(msm_fb_resources), + .resource = msm_fb_resources, + .dev = { + .platform_data = &msm_fb_pdata, + } +}; + +static struct platform_device msm_migrate_pages_device = { + .name = "msm_migrate_pages", + .id = -1, +}; + +static struct android_pmem_platform_data android_pmem_adsp_pdata = { + .name = "pmem_adsp", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, + .memory_type = MEMTYPE_EBI0, +}; + +static struct android_pmem_platform_data android_pmem_audio_pdata = { + .name = "pmem_audio", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, + .memory_type = MEMTYPE_EBI0, +}; + +static struct platform_device android_pmem_adsp_device = { + .name = "android_pmem", + .id = 2, + .dev = { .platform_data = &android_pmem_adsp_pdata }, +}; + +static struct platform_device android_pmem_audio_device = { + .name = "android_pmem", + .id = 4, + .dev = { .platform_data = &android_pmem_audio_pdata }, +}; + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + +#define QCE_SIZE 0x10000 +#define QCE_0_BASE 0xA8400000 + +#define QCE_HW_KEY_SUPPORT 1 +#define QCE_SHA_HMAC_SUPPORT 0 +#define QCE_SHARE_CE_RESOURCE 0 +#define QCE_CE_SHARED 0 + +static struct resource qcrypto_resources[] = { + [0] = { + .start = QCE_0_BASE, + .end = QCE_0_BASE + QCE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "crypto_channels", + .start = DMOV_CE_IN_CHAN, + .end = DMOV_CE_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV_CE_IN_CRCI, + .end = DMOV_CE_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV_CE_OUT_CRCI, + .end = DMOV_CE_OUT_CRCI, + .flags = IORESOURCE_DMA, + }, + [4] = { + .name = "crypto_crci_hash", + .start = DMOV_CE_HASH_CRCI, + .end = DMOV_CE_HASH_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource qcedev_resources[] = { + [0] = { + .start = QCE_0_BASE, + .end = QCE_0_BASE + QCE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "crypto_channels", + .start = DMOV_CE_IN_CHAN, + .end = DMOV_CE_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV_CE_IN_CRCI, + .end = DMOV_CE_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV_CE_OUT_CRCI, + .end = DMOV_CE_OUT_CRCI, + .flags = IORESOURCE_DMA, + }, + [4] = { + .name = "crypto_crci_hash", + .start = DMOV_CE_HASH_CRCI, + .end = DMOV_CE_HASH_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) + +static struct msm_ce_hw_support qcrypto_ce_hw_suppport = { + .ce_shared = QCE_CE_SHARED, + .shared_ce_resource = QCE_SHARE_CE_RESOURCE, + .hw_key_support = QCE_HW_KEY_SUPPORT, + .sha_hmac = QCE_SHA_HMAC_SUPPORT, +}; + +static struct platform_device qcrypto_device = { + .name = "qcrypto", + .id = 0, + .num_resources = ARRAY_SIZE(qcrypto_resources), + .resource = qcrypto_resources, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &qcrypto_ce_hw_suppport, + }, +}; +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + +static struct msm_ce_hw_support qcedev_ce_hw_suppport = { + .ce_shared = QCE_CE_SHARED, + .shared_ce_resource = QCE_SHARE_CE_RESOURCE, + .hw_key_support = QCE_HW_KEY_SUPPORT, + .sha_hmac = QCE_SHA_HMAC_SUPPORT, +}; +static struct platform_device qcedev_device = { + .name = "qce", + .id = 0, + .num_resources = ARRAY_SIZE(qcedev_resources), + .resource = qcedev_resources, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &qcedev_ce_hw_suppport, + }, +}; +#endif + +static int mddi_toshiba_pmic_bl(int level) +{ + int ret = -EPERM; + + ret = pmic_set_led_intensity(LED_LCD, level); + + if (ret) + printk(KERN_WARNING "%s: can't set lcd backlight!\n", + __func__); + return ret; +} + +static struct msm_panel_common_pdata mddi_toshiba_pdata = { + .pmic_backlight = mddi_toshiba_pmic_bl, +}; + +static struct platform_device mddi_toshiba_device = { + .name = "mddi_toshiba", + .id = 0, + .dev = { + .platform_data = &mddi_toshiba_pdata, + } +}; + +static unsigned wega_reset_gpio = + GPIO_CFG(180, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA); + +static struct msm_gpio fluid_vee_reset_gpio[] = { + { GPIO_CFG(20, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "vee_reset" }, +}; + +static unsigned char quickvx_mddi_client = 1, other_mddi_client = 1; +static unsigned char quickvx_ldo_enabled; + +static unsigned quickvx_vlp_gpio = + GPIO_CFG(97, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA); + +static struct pm8058_gpio pmic_quickvx_clk_gpio = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S3, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_2, +}; + +static int display_common_power(int on) +{ + int rc = 0, flag_on = !!on; + static int display_common_power_save_on; + struct vreg *vreg_ldo12, *vreg_ldo15 = NULL, *vreg_ldo6; + struct vreg *vreg_ldo20, *vreg_ldo16, *vreg_ldo8 = NULL; + + if (display_common_power_save_on == flag_on) + return 0; + + display_common_power_save_on = flag_on; + + if (on) { + /* reset Toshiba WeGA chip -- toggle reset pin -- gpio_180 */ + rc = gpio_tlmm_config(wega_reset_gpio, GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", + __func__, wega_reset_gpio, rc); + return rc; + } + + /* bring reset line low to hold reset*/ + gpio_set_value(180, 0); + + if (quickvx_mddi_client) { + /* QuickVX chip -- VLP pin -- gpio 97 */ + rc = gpio_tlmm_config(quickvx_vlp_gpio, + GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", + __func__, quickvx_vlp_gpio, rc); + return rc; + } + + /* bring QuickVX VLP line low */ + gpio_set_value(97, 0); + + rc = pm8058_gpio_config(PMIC_GPIO_QUICKVX_CLK, + &pmic_quickvx_clk_gpio); + if (rc) { + pr_err("%s: pm8058_gpio_config(%#x)=%d\n", + __func__, PMIC_GPIO_QUICKVX_CLK + 1, + rc); + return rc; + } + + gpio_set_value_cansleep(PM8058_GPIO_PM_TO_SYS( + PMIC_GPIO_QUICKVX_CLK), 0); + } + } + + /* Toshiba WeGA power -- has 3 power source */ + /* 1.5V -- LDO20*/ + vreg_ldo20 = vreg_get(NULL, "gp13"); + + if (IS_ERR(vreg_ldo20)) { + rc = PTR_ERR(vreg_ldo20); + pr_err("%s: gp13 vreg get failed (%d)\n", + __func__, rc); + return rc; + } + + /* 1.8V -- LDO12 */ + vreg_ldo12 = vreg_get(NULL, "gp9"); + + if (IS_ERR(vreg_ldo12)) { + rc = PTR_ERR(vreg_ldo12); + pr_err("%s: gp9 vreg get failed (%d)\n", + __func__, rc); + return rc; + } + + /* 2.6V -- LDO16 */ + vreg_ldo16 = vreg_get(NULL, "gp10"); + + if (IS_ERR(vreg_ldo16)) { + rc = PTR_ERR(vreg_ldo16); + pr_err("%s: gp10 vreg get failed (%d)\n", + __func__, rc); + return rc; + } + + /* 3.075V -- LDO6 */ + vreg_ldo6 = vreg_get(NULL, "usb"); + + if (IS_ERR(vreg_ldo6)) { + rc = PTR_ERR(vreg_ldo6); + pr_err("%s: usb vreg get failed (%d)\n", + __func__, rc); + return rc; + } + + if (machine_is_msm7x30_fluid()) { + /* 1.8V -- LDO8 */ + vreg_ldo8 = vreg_get(NULL, "gp7"); + + if (IS_ERR(vreg_ldo8)) { + rc = PTR_ERR(vreg_ldo8); + pr_err("%s: gp7 vreg get failed (%d)\n", + __func__, rc); + return rc; + } + } else { + /* lcd panel power */ + /* 3.1V -- LDO15 */ + vreg_ldo15 = vreg_get(NULL, "gp6"); + + if (IS_ERR(vreg_ldo15)) { + rc = PTR_ERR(vreg_ldo15); + pr_err("%s: gp6 vreg get failed (%d)\n", + __func__, rc); + return rc; + } + } + + /* For QuickLogic chip, LDO20 requires 1.8V */ + /* Toshiba chip requires 1.5V, but can tolerate 1.8V since max is 3V */ + if (quickvx_mddi_client) + rc = vreg_set_level(vreg_ldo20, 1800); + else + rc = vreg_set_level(vreg_ldo20, 1500); + if (rc) { + pr_err("%s: vreg LDO20 set level failed (%d)\n", + __func__, rc); + return rc; + } + + rc = vreg_set_level(vreg_ldo12, 1800); + if (rc) { + pr_err("%s: vreg LDO12 set level failed (%d)\n", + __func__, rc); + return rc; + } + + if (other_mddi_client) { + rc = vreg_set_level(vreg_ldo16, 2600); + if (rc) { + pr_err("%s: vreg LDO16 set level failed (%d)\n", + __func__, rc); + return rc; + } + } + + if (quickvx_mddi_client) { + rc = vreg_set_level(vreg_ldo6, 3075); + if (rc) { + pr_err("%s: vreg LDO6 set level failed (%d)\n", + __func__, rc); + return rc; + } + } + + if (machine_is_msm7x30_fluid()) { + rc = vreg_set_level(vreg_ldo8, 1800); + if (rc) { + pr_err("%s: vreg LDO8 set level failed (%d)\n", + __func__, rc); + return rc; + } + } else { + rc = vreg_set_level(vreg_ldo15, 3100); + if (rc) { + pr_err("%s: vreg LDO15 set level failed (%d)\n", + __func__, rc); + return rc; + } + } + + if (on) { + rc = vreg_enable(vreg_ldo20); + if (rc) { + pr_err("%s: LDO20 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + + rc = vreg_enable(vreg_ldo12); + if (rc) { + pr_err("%s: LDO12 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + + if (other_mddi_client) { + rc = vreg_enable(vreg_ldo16); + if (rc) { + pr_err("%s: LDO16 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + } + + if (quickvx_mddi_client && quickvx_ldo_enabled) { + /* Disable LDO6 during display ON */ + rc = vreg_disable(vreg_ldo6); + if (rc) { + pr_err("%s: LDO6 vreg disable failed (%d)\n", + __func__, rc); + return rc; + } + quickvx_ldo_enabled = 0; + } + + if (machine_is_msm7x30_fluid()) { + rc = vreg_enable(vreg_ldo8); + if (rc) { + pr_err("%s: LDO8 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + } else { + rc = vreg_enable(vreg_ldo15); + if (rc) { + pr_err("%s: LDO15 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + } + + mdelay(5); /* ensure power is stable */ + + if (machine_is_msm7x30_fluid()) { + rc = msm_gpios_request_enable(fluid_vee_reset_gpio, + ARRAY_SIZE(fluid_vee_reset_gpio)); + if (rc) + pr_err("%s gpio_request_enable failed rc=%d\n", + __func__, rc); + else { + /* assert vee reset_n */ + gpio_set_value(20, 1); + gpio_set_value(20, 0); + mdelay(1); + gpio_set_value(20, 1); + } + } + + gpio_set_value(180, 1); /* bring reset line high */ + mdelay(10); /* 10 msec before IO can be accessed */ + + if (quickvx_mddi_client) { + gpio_set_value(97, 1); + msleep(2); + gpio_set_value_cansleep(PM8058_GPIO_PM_TO_SYS( + PMIC_GPIO_QUICKVX_CLK), 1); + msleep(2); + } + + rc = pmapp_display_clock_config(1); + if (rc) { + pr_err("%s pmapp_display_clock_config rc=%d\n", + __func__, rc); + return rc; + } + + } else { + rc = vreg_disable(vreg_ldo20); + if (rc) { + pr_err("%s: LDO20 vreg disable failed (%d)\n", + __func__, rc); + return rc; + } + + + if (other_mddi_client) { + rc = vreg_disable(vreg_ldo16); + if (rc) { + pr_err("%s: LDO16 vreg disable failed (%d)\n", + __func__, rc); + return rc; + } + } + + if (quickvx_mddi_client && !quickvx_ldo_enabled) { + /* Enable LDO6 during display OFF for + Quicklogic chip to sleep with data retention */ + rc = vreg_enable(vreg_ldo6); + if (rc) { + pr_err("%s: LDO6 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + quickvx_ldo_enabled = 1; + } + + gpio_set_value(180, 0); /* bring reset line low */ + + if (quickvx_mddi_client) { + gpio_set_value(97, 0); + gpio_set_value_cansleep(PM8058_GPIO_PM_TO_SYS( + PMIC_GPIO_QUICKVX_CLK), 0); + } + + if (machine_is_msm7x30_fluid()) { + rc = vreg_disable(vreg_ldo8); + if (rc) { + pr_err("%s: LDO8 vreg disable failed (%d)\n", + __func__, rc); + return rc; + } + } else { + rc = vreg_disable(vreg_ldo15); + if (rc) { + pr_err("%s: LDO15 vreg disable failed (%d)\n", + __func__, rc); + return rc; + } + } + + mdelay(5); /* ensure power is stable */ + + rc = vreg_disable(vreg_ldo12); + if (rc) { + pr_err("%s: LDO12 vreg disable failed (%d)\n", + __func__, rc); + return rc; + } + + if (machine_is_msm7x30_fluid()) { + msm_gpios_disable_free(fluid_vee_reset_gpio, + ARRAY_SIZE(fluid_vee_reset_gpio)); + } + + rc = pmapp_display_clock_config(0); + if (rc) { + pr_err("%s pmapp_display_clock_config rc=%d\n", + __func__, rc); + return rc; + } + } + + return rc; +} + +static int msm_fb_mddi_sel_clk(u32 *clk_rate) +{ + *clk_rate *= 2; + return 0; +} + +static int msm_fb_mddi_client_power(u32 client_id) +{ + struct vreg *vreg_ldo20, *vreg_ldo16; + int rc; + + printk(KERN_NOTICE "\n client_id = 0x%x", client_id); + /* Check if it is Quicklogic client */ + if (client_id == 0xc5835800) { + printk(KERN_NOTICE "\n Quicklogic MDDI client"); + other_mddi_client = 0; + vreg_ldo16 = vreg_get(NULL, "gp10"); + + if (IS_ERR(vreg_ldo16)) { + rc = PTR_ERR(vreg_ldo16); + pr_err("%s: gp10 vreg get failed (%d)\n", + __func__, rc); + return rc; + } + + rc = vreg_disable(vreg_ldo16); + if (rc) { + pr_err("%s: LDO16 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + } else { + printk(KERN_NOTICE "\n Non-Quicklogic MDDI client"); + quickvx_mddi_client = 0; + gpio_set_value(97, 0); + gpio_set_value_cansleep(PM8058_GPIO_PM_TO_SYS( + PMIC_GPIO_QUICKVX_CLK), 0); + + vreg_ldo20 = vreg_get(NULL, "gp13"); + + if (IS_ERR(vreg_ldo20)) { + rc = PTR_ERR(vreg_ldo20); + pr_err("%s: gp13 vreg get failed (%d)\n", + __func__, rc); + return rc; + } + rc = vreg_set_level(vreg_ldo20, 1500); + if (rc) { + pr_err("%s: vreg LDO20 set level failed (%d)\n", + __func__, rc); + return rc; + } + } + return 0; +} + +static struct mddi_platform_data mddi_pdata = { + .mddi_power_save = display_common_power, + .mddi_sel_clk = msm_fb_mddi_sel_clk, + .mddi_client_power = msm_fb_mddi_client_power, +}; + +int mdp_core_clk_rate_table[] = { + 122880000, + 122880000, + 122880000, + 192000000, +}; + +static struct msm_panel_common_pdata mdp_pdata = { + .hw_revision_addr = 0xac001270, + .gpio = 30, + .mdp_core_clk_rate = 122880000, + .mdp_core_clk_table = mdp_core_clk_rate_table, + .num_mdp_clk = ARRAY_SIZE(mdp_core_clk_rate_table), +}; + +static int lcd_panel_spi_gpio_num[] = { + 45, /* spi_clk */ + 46, /* spi_cs */ + 47, /* spi_mosi */ + 48, /* spi_miso */ + }; + +static struct msm_gpio lcd_panel_gpios[] = { +/* Workaround, since HDMI_INT is using the same GPIO line (18), and is used as + * input. if there is a hardware revision; we should reassign this GPIO to a + * new open line; and removing it will just ensure that this will be missed in + * the future. + { GPIO_CFG(18, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn0" }, + */ + { GPIO_CFG(19, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn1" }, + { GPIO_CFG(20, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu0" }, + { GPIO_CFG(21, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu1" }, + { GPIO_CFG(22, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu2" }, + { GPIO_CFG(23, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red0" }, + { GPIO_CFG(24, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red1" }, + { GPIO_CFG(25, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red2" }, +#ifndef CONFIG_SPI_QSD + { GPIO_CFG(45, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_clk" }, + { GPIO_CFG(46, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_cs0" }, + { GPIO_CFG(47, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_mosi" }, + { GPIO_CFG(48, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_miso" }, +#endif + { GPIO_CFG(90, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_pclk" }, + { GPIO_CFG(91, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_en" }, + { GPIO_CFG(92, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_vsync" }, + { GPIO_CFG(93, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_hsync" }, + { GPIO_CFG(94, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn2" }, + { GPIO_CFG(95, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn3" }, + { GPIO_CFG(96, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn4" }, + { GPIO_CFG(97, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn5" }, + { GPIO_CFG(98, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn6" }, + { GPIO_CFG(99, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn7" }, + { GPIO_CFG(100, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu3" }, + { GPIO_CFG(101, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu4" }, + { GPIO_CFG(102, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu5" }, + { GPIO_CFG(103, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu6" }, + { GPIO_CFG(104, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu7" }, + { GPIO_CFG(105, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red3" }, + { GPIO_CFG(106, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red4" }, + { GPIO_CFG(107, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red5" }, + { GPIO_CFG(108, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red6" }, + { GPIO_CFG(109, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red7" }, +}; + +static struct msm_gpio lcd_sharp_panel_gpios[] = { + { GPIO_CFG(22, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu2" }, + { GPIO_CFG(25, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red2" }, + { GPIO_CFG(90, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_pclk" }, + { GPIO_CFG(91, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_en" }, + { GPIO_CFG(92, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_vsync" }, + { GPIO_CFG(93, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_hsync" }, + { GPIO_CFG(94, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn2" }, + { GPIO_CFG(95, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn3" }, + { GPIO_CFG(96, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn4" }, + { GPIO_CFG(97, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn5" }, + { GPIO_CFG(98, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn6" }, + { GPIO_CFG(99, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_grn7" }, + { GPIO_CFG(100, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu3" }, + { GPIO_CFG(101, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu4" }, + { GPIO_CFG(102, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu5" }, + { GPIO_CFG(103, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu6" }, + { GPIO_CFG(104, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_blu7" }, + { GPIO_CFG(105, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red3" }, + { GPIO_CFG(106, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red4" }, + { GPIO_CFG(107, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red5" }, + { GPIO_CFG(108, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red6" }, + { GPIO_CFG(109, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "lcdc_red7" }, +}; + +static int lcdc_toshiba_panel_power(int on) +{ + int rc, i; + struct msm_gpio *gp; + + rc = display_common_power(on); + if (rc < 0) { + printk(KERN_ERR "%s display_common_power failed: %d\n", + __func__, rc); + return rc; + } + + if (on) { + rc = msm_gpios_enable(lcd_panel_gpios, + ARRAY_SIZE(lcd_panel_gpios)); + if (rc < 0) { + printk(KERN_ERR "%s: gpio enable failed: %d\n", + __func__, rc); + } + } else { /* off */ + gp = lcd_panel_gpios; + for (i = 0; i < ARRAY_SIZE(lcd_panel_gpios); i++) { + /* ouput low */ + gpio_set_value(GPIO_PIN(gp->gpio_cfg), 0); + gp++; + } + } + + return rc; +} + +static int lcdc_sharp_panel_power(int on) +{ + int rc, i; + struct msm_gpio *gp; + + rc = display_common_power(on); + if (rc < 0) { + printk(KERN_ERR "%s display_common_power failed: %d\n", + __func__, rc); + return rc; + } + + if (on) { + rc = msm_gpios_enable(lcd_sharp_panel_gpios, + ARRAY_SIZE(lcd_sharp_panel_gpios)); + if (rc < 0) { + printk(KERN_ERR "%s: gpio enable failed: %d\n", + __func__, rc); + } + } else { /* off */ + gp = lcd_sharp_panel_gpios; + for (i = 0; i < ARRAY_SIZE(lcd_sharp_panel_gpios); i++) { + /* ouput low */ + gpio_set_value(GPIO_PIN(gp->gpio_cfg), 0); + gp++; + } + } + + return rc; +} + +static int lcdc_panel_power(int on) +{ + int flag_on = !!on; + static int lcdc_power_save_on; + + if (lcdc_power_save_on == flag_on) + return 0; + + lcdc_power_save_on = flag_on; + quickvx_mddi_client = 0; + + if (machine_is_msm7x30_fluid()) + return lcdc_sharp_panel_power(on); + else + return lcdc_toshiba_panel_power(on); +} + +static struct lcdc_platform_data lcdc_pdata = { + .lcdc_power_save = lcdc_panel_power, +}; + +static int atv_dac_power(int on) +{ + int rc = 0; + struct vreg *vreg_s4, *vreg_ldo9; + + vreg_s4 = vreg_get(NULL, "s4"); + if (IS_ERR(vreg_s4)) { + rc = PTR_ERR(vreg_s4); + pr_err("%s: s4 vreg get failed (%d)\n", + __func__, rc); + return -1; + } + vreg_ldo9 = vreg_get(NULL, "gp1"); + if (IS_ERR(vreg_ldo9)) { + rc = PTR_ERR(vreg_ldo9); + pr_err("%s: ldo9 vreg get failed (%d)\n", + __func__, rc); + return rc; + } + + if (on) { + rc = vreg_enable(vreg_s4); + if (rc) { + pr_err("%s: s4 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + rc = vreg_enable(vreg_ldo9); + if (rc) { + pr_err("%s: ldo9 vreg enable failed (%d)\n", + __func__, rc); + return rc; + } + } else { + rc = vreg_disable(vreg_ldo9); + if (rc) { + pr_err("%s: ldo9 vreg disable failed (%d)\n", + __func__, rc); + return rc; + } + rc = vreg_disable(vreg_s4); + if (rc) { + pr_err("%s: s4 vreg disable failed (%d)\n", + __func__, rc); + return rc; + } + } + return rc; +} + +static struct tvenc_platform_data atv_pdata = { + .poll = 1, + .pm_vid_en = atv_dac_power, +}; + +static void __init msm_fb_add_devices(void) +{ + msm_fb_register_device("mdp", &mdp_pdata); + msm_fb_register_device("pmdh", &mddi_pdata); + msm_fb_register_device("lcdc", &lcdc_pdata); + msm_fb_register_device("dtv", &dtv_pdata); + msm_fb_register_device("tvenc", &atv_pdata); +#ifdef CONFIG_FB_MSM_TVOUT + msm_fb_register_device("tvout_device", NULL); +#endif +} + +static struct msm_panel_common_pdata lcdc_toshiba_panel_data = { + .gpio_num = lcd_panel_spi_gpio_num, +}; + +static struct platform_device lcdc_toshiba_panel_device = { + .name = "lcdc_toshiba_wvga", + .id = 0, + .dev = { + .platform_data = &lcdc_toshiba_panel_data, + } +}; + +#if defined(CONFIG_MARIMBA_CORE) && \ + (defined(CONFIG_MSM_BT_POWER) || defined(CONFIG_MSM_BT_POWER_MODULE)) +static struct platform_device msm_bt_power_device = { + .name = "bt_power", + .id = -1 +}; + +enum { + BT_RFR, + BT_CTS, + BT_RX, + BT_TX, +}; + +static struct msm_gpio bt_config_power_on[] = { + { GPIO_CFG(134, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "UART1DM_RFR" }, + { GPIO_CFG(135, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "UART1DM_CTS" }, + { GPIO_CFG(136, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "UART1DM_Rx" }, + { GPIO_CFG(137, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "UART1DM_Tx" } +}; + +static struct msm_gpio bt_config_power_off[] = { + { GPIO_CFG(134, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1DM_RFR" }, + { GPIO_CFG(135, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1DM_CTS" }, + { GPIO_CFG(136, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1DM_Rx" }, + { GPIO_CFG(137, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1DM_Tx" } +}; + +static const char *vregs_bt_marimba_name[] = { + "s3", + "s2", + "gp16", + "wlan" +}; +static struct vreg *vregs_bt_marimba[ARRAY_SIZE(vregs_bt_marimba_name)]; + +static const char *vregs_bt_bahama_name[] = { + "s3", + "usb2", + "s2", + "wlan" +}; +static struct vreg *vregs_bt_bahama[ARRAY_SIZE(vregs_bt_bahama_name)]; + +static u8 bahama_version; + +static int marimba_bt(int on) +{ + int rc; + int i; + struct marimba config = { .mod_id = MARIMBA_SLAVE_ID_MARIMBA }; + + struct marimba_config_register { + u8 reg; + u8 value; + u8 mask; + }; + + struct marimba_variant_register { + const size_t size; + const struct marimba_config_register *set; + }; + + const struct marimba_config_register *p; + + u8 version; + + const struct marimba_config_register v10_bt_on[] = { + { 0xE5, 0x0B, 0x0F }, + { 0x05, 0x02, 0x07 }, + { 0x06, 0x88, 0xFF }, + { 0xE7, 0x21, 0x21 }, + { 0xE3, 0x38, 0xFF }, + { 0xE4, 0x06, 0xFF }, + }; + + const struct marimba_config_register v10_bt_off[] = { + { 0xE5, 0x0B, 0x0F }, + { 0x05, 0x08, 0x0F }, + { 0x06, 0x88, 0xFF }, + { 0xE7, 0x00, 0x21 }, + { 0xE3, 0x00, 0xFF }, + { 0xE4, 0x00, 0xFF }, + }; + + const struct marimba_config_register v201_bt_on[] = { + { 0x05, 0x08, 0x07 }, + { 0x06, 0x88, 0xFF }, + { 0xE7, 0x21, 0x21 }, + { 0xE3, 0x38, 0xFF }, + { 0xE4, 0x06, 0xFF }, + }; + + const struct marimba_config_register v201_bt_off[] = { + { 0x05, 0x08, 0x07 }, + { 0x06, 0x88, 0xFF }, + { 0xE7, 0x00, 0x21 }, + { 0xE3, 0x00, 0xFF }, + { 0xE4, 0x00, 0xFF }, + }; + + const struct marimba_config_register v210_bt_on[] = { + { 0xE9, 0x01, 0x01 }, + { 0x06, 0x88, 0xFF }, + { 0xE7, 0x21, 0x21 }, + { 0xE3, 0x38, 0xFF }, + { 0xE4, 0x06, 0xFF }, + }; + + const struct marimba_config_register v210_bt_off[] = { + { 0x06, 0x88, 0xFF }, + { 0xE7, 0x00, 0x21 }, + { 0xE9, 0x00, 0x01 }, + { 0xE3, 0x00, 0xFF }, + { 0xE4, 0x00, 0xFF }, + }; + + const struct marimba_variant_register bt_marimba[2][4] = { + { + { ARRAY_SIZE(v10_bt_off), v10_bt_off }, + { 0, NULL }, + { ARRAY_SIZE(v201_bt_off), v201_bt_off }, + { ARRAY_SIZE(v210_bt_off), v210_bt_off } + }, + { + { ARRAY_SIZE(v10_bt_on), v10_bt_on }, + { 0, NULL }, + { ARRAY_SIZE(v201_bt_on), v201_bt_on }, + { ARRAY_SIZE(v210_bt_on), v210_bt_on } + } + }; + + on = on ? 1 : 0; + + rc = marimba_read_bit_mask(&config, 0x11, &version, 1, 0x1F); + if (rc < 0) { + printk(KERN_ERR + "%s: version read failed: %d\n", + __func__, rc); + return rc; + } + + if ((version >= ARRAY_SIZE(bt_marimba[on])) || + (bt_marimba[on][version].size == 0)) { + printk(KERN_ERR + "%s: unsupported version\n", + __func__); + return -EIO; + } + + p = bt_marimba[on][version].set; + + printk(KERN_INFO "%s: found version %d\n", __func__, version); + + for (i = 0; i < bt_marimba[on][version].size; i++) { + u8 value = (p+i)->value; + rc = marimba_write_bit_mask(&config, + (p+i)->reg, + &value, + sizeof((p+i)->value), + (p+i)->mask); + if (rc < 0) { + printk(KERN_ERR + "%s: reg %d write failed: %d\n", + __func__, (p+i)->reg, rc); + return rc; + } + printk(KERN_INFO "%s: reg 0x%02x value 0x%02x mask 0x%02x\n", + __func__, (p+i)->reg, + value, (p+i)->mask); + } + return 0; +} + +static int bahama_bt(int on) +{ + int rc; + int i; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA }; + + struct bahama_variant_register { + const size_t size; + const struct bahama_config_register *set; + }; + + const struct bahama_config_register *p; + + + const struct bahama_config_register v10_bt_on[] = { + { 0xE9, 0x00, 0xFF }, + { 0xF4, 0x80, 0xFF }, + { 0xF0, 0x06, 0xFF }, + { 0xE4, 0x00, 0xFF }, + { 0xE5, 0x00, 0x0F }, +#ifdef CONFIG_WLAN + { 0xE6, 0x38, 0x7F }, + { 0xE7, 0x06, 0xFF }, +#endif + { 0x11, 0x13, 0xFF }, + { 0xE9, 0x21, 0xFF }, + { 0x01, 0x0C, 0x1F }, + { 0x01, 0x08, 0x1F }, + }; + + const struct bahama_config_register v20_bt_on_fm_off[] = { + { 0x11, 0x0C, 0xFF }, + { 0x13, 0x01, 0xFF }, + { 0xF4, 0x80, 0xFF }, + { 0xF0, 0x00, 0xFF }, + { 0xE9, 0x00, 0xFF }, +#ifdef CONFIG_WLAN + { 0x81, 0x00, 0xFF }, + { 0x82, 0x00, 0xFF }, + { 0xE6, 0x38, 0x7F }, + { 0xE7, 0x06, 0xFF }, +#endif + { 0xE9, 0x21, 0xFF } + }; + + const struct bahama_config_register v20_bt_on_fm_on[] = { + { 0x11, 0x0C, 0xFF }, + { 0x13, 0x01, 0xFF }, + { 0xF4, 0x86, 0xFF }, + { 0xF0, 0x06, 0xFF }, + { 0xE9, 0x00, 0xFF }, +#ifdef CONFIG_WLAN + { 0x81, 0x00, 0xFF }, + { 0x82, 0x00, 0xFF }, + { 0xE6, 0x38, 0x7F }, + { 0xE7, 0x06, 0xFF }, +#endif + { 0xE9, 0x21, 0xFF } + }; + + const struct bahama_config_register v10_bt_off[] = { + { 0xE9, 0x00, 0xFF }, + }; + + const struct bahama_config_register v20_bt_off_fm_off[] = { + { 0xF4, 0x84, 0xFF }, + { 0xF0, 0x04, 0xFF }, + { 0xE9, 0x00, 0xFF } + }; + + const struct bahama_config_register v20_bt_off_fm_on[] = { + { 0xF4, 0x86, 0xFF }, + { 0xF0, 0x06, 0xFF }, + { 0xE9, 0x00, 0xFF } + }; + + const struct bahama_variant_register bt_bahama[2][3] = { + { + { ARRAY_SIZE(v10_bt_off), v10_bt_off }, + { ARRAY_SIZE(v20_bt_off_fm_off), v20_bt_off_fm_off }, + { ARRAY_SIZE(v20_bt_off_fm_on), v20_bt_off_fm_on } + }, + { + { ARRAY_SIZE(v10_bt_on), v10_bt_on }, + { ARRAY_SIZE(v20_bt_on_fm_off), v20_bt_on_fm_off }, + { ARRAY_SIZE(v20_bt_on_fm_on), v20_bt_on_fm_on } + } + }; + + u8 offset = 0; /* index into bahama configs */ + + on = on ? 1 : 0; + + bahama_version = read_bahama_ver(); + + if (((int)bahama_version) < 0 || + bahama_version == VER_UNSUPPORTED) { + dev_err(&msm_bt_power_device.dev, + "%s: i2c failure or unsupported version: %d\n", + __func__, bahama_version); + return -EIO; + } + + if (bahama_version == VER_2_0) { + if (marimba_get_fm_status(&config)) + offset = 0x01; + } + + p = bt_bahama[on][bahama_version + offset].set; + + dev_info(&msm_bt_power_device.dev, + "%s: found version %d\n", __func__, bahama_version); + + for (i = 0; i < bt_bahama[on][bahama_version + offset].size; i++) { + u8 value = (p+i)->value; + rc = marimba_write_bit_mask(&config, + (p+i)->reg, + &value, + sizeof((p+i)->value), + (p+i)->mask); + if (rc < 0) { + dev_err(&msm_bt_power_device.dev, + "%s: reg %d write failed: %d\n", + __func__, (p+i)->reg, rc); + return rc; + } + dev_info(&msm_bt_power_device.dev, + "%s: reg 0x%02x write value 0x%02x mask 0x%02x\n", + __func__, (p+i)->reg, + value, (p+i)->mask); + } + /* Update BT status */ + if (on) + marimba_set_bt_status(&config, true); + else + marimba_set_bt_status(&config, false); + + if (bahama_version == VER_2_0 && on) { /* variant of bahama v2 */ + /* Disable s2 as bahama v2 uses internal LDO regulator */ + for (i = 0; i < ARRAY_SIZE(vregs_bt_bahama_name); i++) { + if (!strcmp(vregs_bt_bahama_name[i], "s2")) { + rc = vreg_disable(vregs_bt_bahama[i]); + if (rc < 0) { + printk(KERN_ERR + "%s: vreg %s disable " + "failed (%d)\n", + __func__, + vregs_bt_bahama_name[i], rc); + return -EIO; + } + rc = pmapp_vreg_level_vote("BTPW", + PMAPP_VREG_S2, + 0); + if (rc < 0) { + printk(KERN_ERR "%s: vreg " + "level off failed (%d)\n", + __func__, rc); + return -EIO; + } + printk(KERN_INFO "%s: vreg disable & " + "level off successful (%d)\n", + __func__, rc); + } + } + } + + return 0; +} + +static int bluetooth_power_regulators(int on, int bahama_not_marimba) +{ + int i, rc; + const char **vregs_name; + struct vreg **vregs; + int vregs_size; + + if (bahama_not_marimba) { + vregs_name = vregs_bt_bahama_name; + vregs = vregs_bt_bahama; + vregs_size = ARRAY_SIZE(vregs_bt_bahama_name); + } else { + vregs_name = vregs_bt_marimba_name; + vregs = vregs_bt_marimba; + vregs_size = ARRAY_SIZE(vregs_bt_marimba_name); + } + + for (i = 0; i < vregs_size; i++) { + if (bahama_not_marimba && (bahama_version == VER_2_0) && + !on && !strcmp(vregs_bt_bahama_name[i], "s2")) + continue; + rc = on ? vreg_enable(vregs[i]) : + vreg_disable(vregs[i]); + if (rc < 0) { + printk(KERN_ERR "%s: vreg %s %s failed (%d)\n", + __func__, vregs_name[i], + on ? "enable" : "disable", rc); + return -EIO; + } + } + return 0; +} + +static int bluetooth_power(int on) +{ + int rc; + const char *id = "BTPW"; + + int bahama_not_marimba = bahama_present(); + + if (bahama_not_marimba == -1) { + printk(KERN_WARNING "%s: bahama_present: %d\n", + __func__, bahama_not_marimba); + return -ENODEV; + } + + if (on) { + rc = pmapp_vreg_level_vote(id, PMAPP_VREG_S2, 1300); + if (rc < 0) { + printk(KERN_ERR "%s: vreg level on failed (%d)\n", + __func__, rc); + return rc; + } + + rc = bluetooth_power_regulators(on, bahama_not_marimba); + if (rc < 0) + return -EIO; + + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_DO, + PMAPP_CLOCK_VOTE_ON); + if (rc < 0) + return -EIO; + + if (machine_is_msm8x55_svlte_surf() || + machine_is_msm8x55_svlte_ffa()) { + rc = marimba_gpio_config(1); + if (rc < 0) + return -EIO; + } + + rc = (bahama_not_marimba ? bahama_bt(on) : marimba_bt(on)); + if (rc < 0) + return -EIO; + + msleep(10); + + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_DO, + PMAPP_CLOCK_VOTE_PIN_CTRL); + if (rc < 0) + return -EIO; + + if (machine_is_msm8x55_svlte_surf() || + machine_is_msm8x55_svlte_ffa()) { + rc = marimba_gpio_config(0); + if (rc < 0) + return -EIO; + } + + rc = msm_gpios_enable(bt_config_power_on, + ARRAY_SIZE(bt_config_power_on)); + + if (rc < 0) + return rc; + + } else { + rc = msm_gpios_enable(bt_config_power_off, + ARRAY_SIZE(bt_config_power_off)); + if (rc < 0) + return rc; + + /* check for initial RFKILL block (power off) */ + if (platform_get_drvdata(&msm_bt_power_device) == NULL) + goto out; + + rc = (bahama_not_marimba ? bahama_bt(on) : marimba_bt(on)); + if (rc < 0) + return -EIO; + + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_DO, + PMAPP_CLOCK_VOTE_OFF); + if (rc < 0) + return -EIO; + + rc = bluetooth_power_regulators(on, bahama_not_marimba); + if (rc < 0) + return -EIO; + + if (bahama_version == VER_1_0) { + rc = pmapp_vreg_level_vote(id, PMAPP_VREG_S2, 0); + if (rc < 0) { + printk(KERN_ERR "%s: vreg level off failed " + "(%d)\n", __func__, rc); + return -EIO; + } + } + } + +out: + printk(KERN_DEBUG "Bluetooth power switch: %d\n", on); + + return 0; +} + +static void __init bt_power_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vregs_bt_marimba_name); i++) { + vregs_bt_marimba[i] = vreg_get(NULL, vregs_bt_marimba_name[i]); + if (IS_ERR(vregs_bt_marimba[i])) { + printk(KERN_ERR "%s: vreg get %s failed (%ld)\n", + __func__, vregs_bt_marimba_name[i], + PTR_ERR(vregs_bt_marimba[i])); + return; + } + } + + for (i = 0; i < ARRAY_SIZE(vregs_bt_bahama_name); i++) { + vregs_bt_bahama[i] = vreg_get(NULL, vregs_bt_bahama_name[i]); + if (IS_ERR(vregs_bt_bahama[i])) { + printk(KERN_ERR "%s: vreg get %s failed (%ld)\n", + __func__, vregs_bt_bahama_name[i], + PTR_ERR(vregs_bt_bahama[i])); + return; + } + } + + msm_bt_power_device.dev.platform_data = &bluetooth_power; +} +#else +#define bt_power_init(x) do {} while (0) +#endif + +static struct msm_psy_batt_pdata msm_psy_batt_data = { + .voltage_min_design = 2800, + .voltage_max_design = 4300, + .avail_chg_sources = AC_CHG | USB_CHG , + .batt_technology = POWER_SUPPLY_TECHNOLOGY_LION, +}; + +static struct platform_device msm_batt_device = { + .name = "msm-battery", + .id = -1, + .dev.platform_data = &msm_psy_batt_data, +}; + +static char *msm_adc_fluid_device_names[] = { + "LTC_ADC1", + "LTC_ADC2", + "LTC_ADC3", +}; + +static char *msm_adc_surf_device_names[] = { + "XO_ADC", +}; + +static struct msm_adc_platform_data msm_adc_pdata; + +static struct platform_device msm_adc_device = { + .name = "msm_adc", + .id = -1, + .dev = { + .platform_data = &msm_adc_pdata, + }, +}; + +#ifdef CONFIG_MSM_SDIO_AL +static struct msm_gpio mdm2ap_status = { + GPIO_CFG(77, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "mdm2ap_status" +}; + + +static int configure_mdm2ap_status(int on) +{ + if (on) + return msm_gpios_request_enable(&mdm2ap_status, 1); + else { + msm_gpios_disable_free(&mdm2ap_status, 1); + return 0; + } +} + +static int get_mdm2ap_status(void) +{ + return gpio_get_value(GPIO_PIN(mdm2ap_status.gpio_cfg)); +} + +static struct sdio_al_platform_data sdio_al_pdata = { + .config_mdm2ap_status = configure_mdm2ap_status, + .get_mdm2ap_status = get_mdm2ap_status, + .allow_sdioc_version_major_2 = 1, + .peer_sdioc_version_minor = 0x0001, + .peer_sdioc_version_major = 0x0003, + .peer_sdioc_boot_version_minor = 0x0001, + .peer_sdioc_boot_version_major = 0x0003, +}; + +struct platform_device msm_device_sdio_al = { + .name = "msm_sdio_al", + .id = -1, + .dev = { + .platform_data = &sdio_al_pdata, + }, +}; + +#endif /* CONFIG_MSM_SDIO_AL */ + +static struct platform_device *devices[] __initdata = { +#if defined(CONFIG_SERIAL_MSM) || defined(CONFIG_MSM_SERIAL_DEBUGGER) + &msm_device_uart2, +#endif + &asoc_msm_pcm, + &asoc_msm_dai0, + &asoc_msm_dai1, +#if defined (CONFIG_SND_MSM_MVS_DAI_SOC) + &asoc_msm_mvs, + &asoc_mvs_dai0, + &asoc_mvs_dai1, +#endif + &msm_device_smd, + &msm_device_dmov, + &smc91x_device, + &smsc911x_device, + &msm_device_nand, +#ifdef CONFIG_USB_MSM_OTG_72K + &msm_device_otg, +#ifdef CONFIG_USB_GADGET + &msm_device_gadget_peripheral, +#endif +#endif +#ifdef CONFIG_USB_G_ANDROID + &android_usb_device, +#endif + &qsd_device_spi, +#ifdef CONFIG_I2C_SSBI + &msm_device_ssbi6, + &msm_device_ssbi7, +#endif + &android_pmem_device, + &msm_fb_device, + &msm_migrate_pages_device, + &mddi_toshiba_device, + &lcdc_toshiba_panel_device, +#ifdef CONFIG_MSM_ROTATOR + &msm_rotator_device, +#endif + &lcdc_sharp_panel_device, + &android_pmem_adsp_device, + &android_pmem_audio_device, + &msm_device_i2c, + &msm_device_i2c_2, + &msm_device_uart_dm1, + &hs_device, +#ifdef CONFIG_MSM7KV2_AUDIO + &msm_aictl_device, + &msm_mi2s_device, + &msm_lpa_device, + &msm_aux_pcm_device, +#endif + &msm_device_adspdec, + &qup_device_i2c, +#if defined(CONFIG_MARIMBA_CORE) && \ + (defined(CONFIG_MSM_BT_POWER) || defined(CONFIG_MSM_BT_POWER_MODULE)) + &msm_bt_power_device, +#endif + &msm_kgsl_3d0, + &msm_kgsl_2d0, +#ifdef CONFIG_MT9T013 + &msm_camera_sensor_mt9t013, +#endif +#ifdef CONFIG_MT9D112 + &msm_camera_sensor_mt9d112, +#endif +#ifdef CONFIG_WEBCAM_OV9726 + &msm_camera_sensor_ov9726, +#endif +#ifdef CONFIG_S5K3E2FX + &msm_camera_sensor_s5k3e2fx, +#endif +#ifdef CONFIG_MT9P012 + &msm_camera_sensor_mt9p012, +#endif +#ifdef CONFIG_MT9E013 + &msm_camera_sensor_mt9e013, +#endif +#ifdef CONFIG_VX6953 + &msm_camera_sensor_vx6953, +#endif +#ifdef CONFIG_SN12M0PZ + &msm_camera_sensor_sn12m0pz, +#endif + &msm_device_vidc_720p, +#ifdef CONFIG_MSM_GEMINI + &msm_gemini_device, +#endif +#ifdef CONFIG_MSM_VPE + &msm_vpe_device, +#endif +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + &msm_device_tsif, +#endif +#ifdef CONFIG_MSM_SDIO_AL + &msm_device_sdio_al, +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) + &qcrypto_device, +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + &qcedev_device, +#endif + + &msm_batt_device, + &msm_adc_device, + &msm_ebi0_thermal, + &msm_ebi1_thermal +}; + +static struct msm_gpio msm_i2c_gpios_hw[] = { + { GPIO_CFG(70, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "i2c_scl" }, + { GPIO_CFG(71, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "i2c_sda" }, +}; + +static struct msm_gpio msm_i2c_gpios_io[] = { + { GPIO_CFG(70, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "i2c_scl" }, + { GPIO_CFG(71, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "i2c_sda" }, +}; + +static struct msm_gpio qup_i2c_gpios_io[] = { + { GPIO_CFG(16, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "qup_scl" }, + { GPIO_CFG(17, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "qup_sda" }, +}; +static struct msm_gpio qup_i2c_gpios_hw[] = { + { GPIO_CFG(16, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "qup_scl" }, + { GPIO_CFG(17, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "qup_sda" }, +}; + +static void +msm_i2c_gpio_config(int adap_id, int config_type) +{ + struct msm_gpio *msm_i2c_table; + + /* Each adapter gets 2 lines from the table */ + if (adap_id > 0) + return; + if (config_type) + msm_i2c_table = &msm_i2c_gpios_hw[adap_id*2]; + else + msm_i2c_table = &msm_i2c_gpios_io[adap_id*2]; + msm_gpios_enable(msm_i2c_table, 2); +} +/*This needs to be enabled only for OEMS*/ +#ifndef CONFIG_QUP_EXCLUSIVE_TO_CAMERA +static struct vreg *qup_vreg; +#endif +static void +qup_i2c_gpio_config(int adap_id, int config_type) +{ + int rc = 0; + struct msm_gpio *qup_i2c_table; + /* Each adapter gets 2 lines from the table */ + if (adap_id != 4) + return; + if (config_type) + qup_i2c_table = qup_i2c_gpios_hw; + else + qup_i2c_table = qup_i2c_gpios_io; + rc = msm_gpios_enable(qup_i2c_table, 2); + if (rc < 0) + printk(KERN_ERR "QUP GPIO enable failed: %d\n", rc); + /*This needs to be enabled only for OEMS*/ +#ifndef CONFIG_QUP_EXCLUSIVE_TO_CAMERA + if (qup_vreg) { + int rc = vreg_set_level(qup_vreg, 1800); + if (rc) { + pr_err("%s: vreg LVS1 set level failed (%d)\n", + __func__, rc); + } + rc = vreg_enable(qup_vreg); + if (rc) { + pr_err("%s: vreg_enable() = %d \n", + __func__, rc); + } + } +#endif +} + +static struct msm_i2c_platform_data msm_i2c_pdata = { + .clk_freq = 100000, + .pri_clk = 70, + .pri_dat = 71, + .rmutex = 1, + .rsl_id = "D:I2C02000021", + .msm_i2c_config_gpio = msm_i2c_gpio_config, +}; + +static void __init msm_device_i2c_init(void) +{ + if (msm_gpios_request(msm_i2c_gpios_hw, ARRAY_SIZE(msm_i2c_gpios_hw))) + pr_err("failed to request I2C gpios\n"); + + msm_device_i2c.dev.platform_data = &msm_i2c_pdata; +} + +static struct msm_i2c_platform_data msm_i2c_2_pdata = { + .clk_freq = 100000, + .rmutex = 1, + .rsl_id = "D:I2C02000022", + .msm_i2c_config_gpio = msm_i2c_gpio_config, +}; + +static void __init msm_device_i2c_2_init(void) +{ + msm_device_i2c_2.dev.platform_data = &msm_i2c_2_pdata; +} + +static struct msm_i2c_platform_data qup_i2c_pdata = { + .clk_freq = 384000, + .pclk = "camif_pad_pclk", + .msm_i2c_config_gpio = qup_i2c_gpio_config, +}; + +static void __init qup_device_i2c_init(void) +{ + if (msm_gpios_request(qup_i2c_gpios_hw, ARRAY_SIZE(qup_i2c_gpios_hw))) + pr_err("failed to request I2C gpios\n"); + + qup_device_i2c.dev.platform_data = &qup_i2c_pdata; + /*This needs to be enabled only for OEMS*/ +#ifndef CONFIG_QUP_EXCLUSIVE_TO_CAMERA + qup_vreg = vreg_get(NULL, "lvsw1"); + if (IS_ERR(qup_vreg)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(qup_vreg)); + } +#endif +} + +#ifdef CONFIG_I2C_SSBI +static struct msm_i2c_ssbi_platform_data msm_i2c_ssbi6_pdata = { + .rsl_id = "D:PMIC_SSBI", + .controller_type = MSM_SBI_CTRL_SSBI2, +}; + +static struct msm_i2c_ssbi_platform_data msm_i2c_ssbi7_pdata = { + .rsl_id = "D:CODEC_SSBI", + .controller_type = MSM_SBI_CTRL_SSBI, +}; +#endif + +static struct msm_acpu_clock_platform_data msm7x30_clock_data = { + .acpu_switch_time_us = 50, + .vdd_switch_time_us = 62, +}; + +static void __init msm7x30_init_irq(void) +{ + msm_init_irq(); +} + +static struct msm_gpio msm_nand_ebi2_cfg_data[] = { + {GPIO_CFG(86, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "ebi2_cs1"}, + {GPIO_CFG(115, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "ebi2_busy1"}, +}; + +struct vreg *vreg_s3; +struct vreg *vreg_mmc; + +#if (defined(CONFIG_MMC_MSM_SDC1_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC2_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC3_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC4_SUPPORT)) + +struct sdcc_gpio { + struct msm_gpio *cfg_data; + uint32_t size; + struct msm_gpio *sleep_cfg_data; +}; +#if defined(CONFIG_MMC_MSM_SDC1_SUPPORT) +static struct msm_gpio sdc1_lvlshft_cfg_data[] = { + {GPIO_CFG(35, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_16MA), "sdc1_lvlshft"}, +}; +#endif +static struct msm_gpio sdc1_cfg_data[] = { + {GPIO_CFG(38, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "sdc1_clk"}, + {GPIO_CFG(39, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_cmd"}, + {GPIO_CFG(40, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_3"}, + {GPIO_CFG(41, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_2"}, + {GPIO_CFG(42, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_1"}, + {GPIO_CFG(43, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_0"}, +}; + +static struct msm_gpio sdc2_cfg_data[] = { + {GPIO_CFG(64, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "sdc2_clk"}, + {GPIO_CFG(65, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_cmd"}, + {GPIO_CFG(66, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_3"}, + {GPIO_CFG(67, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_2"}, + {GPIO_CFG(68, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_1"}, + {GPIO_CFG(69, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_0"}, + +#ifdef CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT + {GPIO_CFG(115, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_4"}, + {GPIO_CFG(114, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_5"}, + {GPIO_CFG(113, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_6"}, + {GPIO_CFG(112, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_7"}, +#endif +}; + +static struct msm_gpio sdc3_cfg_data[] = { + {GPIO_CFG(110, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "sdc3_clk"}, + {GPIO_CFG(111, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_cmd"}, + {GPIO_CFG(116, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_3"}, + {GPIO_CFG(117, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_2"}, + {GPIO_CFG(118, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_1"}, + {GPIO_CFG(119, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_0"}, +}; + +static struct msm_gpio sdc3_sleep_cfg_data[] = { + {GPIO_CFG(110, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "sdc3_clk"}, + {GPIO_CFG(111, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "sdc3_cmd"}, + {GPIO_CFG(116, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "sdc3_dat_3"}, + {GPIO_CFG(117, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "sdc3_dat_2"}, + {GPIO_CFG(118, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "sdc3_dat_1"}, + {GPIO_CFG(119, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "sdc3_dat_0"}, +}; + +static struct msm_gpio sdc4_cfg_data[] = { + {GPIO_CFG(58, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), "sdc4_clk"}, + {GPIO_CFG(59, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_cmd"}, + {GPIO_CFG(60, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_3"}, + {GPIO_CFG(61, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_2"}, + {GPIO_CFG(62, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_1"}, + {GPIO_CFG(63, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_0"}, +}; + +static struct sdcc_gpio sdcc_cfg_data[] = { + { + .cfg_data = sdc1_cfg_data, + .size = ARRAY_SIZE(sdc1_cfg_data), + .sleep_cfg_data = NULL, + }, + { + .cfg_data = sdc2_cfg_data, + .size = ARRAY_SIZE(sdc2_cfg_data), + .sleep_cfg_data = NULL, + }, + { + .cfg_data = sdc3_cfg_data, + .size = ARRAY_SIZE(sdc3_cfg_data), + .sleep_cfg_data = sdc3_sleep_cfg_data, + }, + { + .cfg_data = sdc4_cfg_data, + .size = ARRAY_SIZE(sdc4_cfg_data), + .sleep_cfg_data = NULL, + }, +}; + +struct sdcc_vreg { + struct vreg *vreg_data; + unsigned level; +}; + +static struct sdcc_vreg sdcc_vreg_data[4]; + +static unsigned long vreg_sts, gpio_sts; + +static uint32_t msm_sdcc_setup_gpio(int dev_id, unsigned int enable) +{ + int rc = 0; + struct sdcc_gpio *curr; + + curr = &sdcc_cfg_data[dev_id - 1]; + + if (!(test_bit(dev_id, &gpio_sts)^enable)) + return rc; + + if (enable) { + set_bit(dev_id, &gpio_sts); + rc = msm_gpios_request_enable(curr->cfg_data, curr->size); + if (rc) + printk(KERN_ERR "%s: Failed to turn on GPIOs for slot %d\n", + __func__, dev_id); + } else { + clear_bit(dev_id, &gpio_sts); + if (curr->sleep_cfg_data) { + msm_gpios_enable(curr->sleep_cfg_data, curr->size); + msm_gpios_free(curr->sleep_cfg_data, curr->size); + } else { + msm_gpios_disable_free(curr->cfg_data, curr->size); + } + } + + return rc; +} + +static uint32_t msm_sdcc_setup_vreg(int dev_id, unsigned int enable) +{ + int rc = 0; + struct sdcc_vreg *curr; + static int enabled_once[] = {0, 0, 0, 0}; + + curr = &sdcc_vreg_data[dev_id - 1]; + + if (!(test_bit(dev_id, &vreg_sts)^enable)) + return rc; + + if (!enable || enabled_once[dev_id - 1]) + return 0; + + if (enable) { + set_bit(dev_id, &vreg_sts); + rc = vreg_set_level(curr->vreg_data, curr->level); + if (rc) { + printk(KERN_ERR "%s: vreg_set_level() = %d \n", + __func__, rc); + } + rc = vreg_enable(curr->vreg_data); + if (rc) { + printk(KERN_ERR "%s: vreg_enable() = %d \n", + __func__, rc); + } + enabled_once[dev_id - 1] = 1; + } else { + clear_bit(dev_id, &vreg_sts); + rc = vreg_disable(curr->vreg_data); + if (rc) { + printk(KERN_ERR "%s: vreg_disable() = %d \n", + __func__, rc); + } + } + return rc; +} + +static uint32_t msm_sdcc_setup_power(struct device *dv, unsigned int vdd) +{ + int rc = 0; + struct platform_device *pdev; + + pdev = container_of(dv, struct platform_device, dev); + rc = msm_sdcc_setup_gpio(pdev->id, (vdd ? 1 : 0)); + if (rc) + goto out; + + if (pdev->id == 4) /* S3 is always ON and cannot be disabled */ + rc = msm_sdcc_setup_vreg(pdev->id, (vdd ? 1 : 0)); +out: + return rc; +} + +#if defined(CONFIG_MMC_MSM_SDC1_SUPPORT) && \ + defined(CONFIG_CSDIO_VENDOR_ID) && \ + defined(CONFIG_CSDIO_DEVICE_ID) && \ + (CONFIG_CSDIO_VENDOR_ID == 0x70 && CONFIG_CSDIO_DEVICE_ID == 0x1117) + +#define MBP_ON 1 +#define MBP_OFF 0 + +#define MBP_RESET_N \ + GPIO_CFG(44, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA) +#define MBP_INT0 \ + GPIO_CFG(46, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA) + +#define MBP_MODE_CTRL_0 \ + GPIO_CFG(35, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA) +#define MBP_MODE_CTRL_1 \ + GPIO_CFG(36, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA) +#define MBP_MODE_CTRL_2 \ + GPIO_CFG(34, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA) +#define TSIF_EN \ + GPIO_CFG(35, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_DATA \ + GPIO_CFG(36, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_CLK \ + GPIO_CFG(34, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) + +static struct msm_gpio mbp_cfg_data[] = { + {GPIO_CFG(44, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), + "mbp_reset"}, + {GPIO_CFG(85, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), + "mbp_io_voltage"}, +}; + +static int mbp_config_gpios_pre_init(int enable) +{ + int rc = 0; + + if (enable) { + rc = msm_gpios_request_enable(mbp_cfg_data, + ARRAY_SIZE(mbp_cfg_data)); + if (rc) { + printk(KERN_ERR + "%s: Failed to turnon GPIOs for mbp chip(%d)\n", + __func__, rc); + } + } else + msm_gpios_disable_free(mbp_cfg_data, ARRAY_SIZE(mbp_cfg_data)); + return rc; +} + +static int mbp_setup_rf_vregs(int state) +{ + struct vreg *vreg_rf = NULL; + struct vreg *vreg_rf_switch = NULL; + int rc; + + vreg_rf = vreg_get(NULL, "s2"); + if (IS_ERR(vreg_rf)) { + pr_err("%s: s2 vreg get failed (%ld)", + __func__, PTR_ERR(vreg_rf)); + return -EFAULT; + } + vreg_rf_switch = vreg_get(NULL, "rf"); + if (IS_ERR(vreg_rf_switch)) { + pr_err("%s: rf vreg get failed (%ld)", + __func__, PTR_ERR(vreg_rf_switch)); + return -EFAULT; + } + + if (state) { + rc = vreg_set_level(vreg_rf, 1300); + if (rc) { + pr_err("%s: vreg s2 set level failed (%d)\n", + __func__, rc); + return rc; + } + + rc = vreg_enable(vreg_rf); + if (rc) { + printk(KERN_ERR "%s: vreg_enable(s2) = %d\n", + __func__, rc); + } + + rc = vreg_set_level(vreg_rf_switch, 2600); + if (rc) { + pr_err("%s: vreg rf switch set level failed (%d)\n", + __func__, rc); + return rc; + } + rc = vreg_enable(vreg_rf_switch); + if (rc) { + printk(KERN_ERR "%s: vreg_enable(rf) = %d\n", + __func__, rc); + } + } else { + (void) vreg_disable(vreg_rf); + (void) vreg_disable(vreg_rf_switch); + } + return 0; +} + +static int mbp_setup_vregs(int state) +{ + struct vreg *vreg_analog = NULL; + struct vreg *vreg_io = NULL; + int rc; + + vreg_analog = vreg_get(NULL, "gp4"); + if (IS_ERR(vreg_analog)) { + pr_err("%s: gp4 vreg get failed (%ld)", + __func__, PTR_ERR(vreg_analog)); + return -EFAULT; + } + vreg_io = vreg_get(NULL, "s3"); + if (IS_ERR(vreg_io)) { + pr_err("%s: s3 vreg get failed (%ld)", + __func__, PTR_ERR(vreg_io)); + return -EFAULT; + } + if (state) { + rc = vreg_set_level(vreg_analog, 2600); + if (rc) { + pr_err("%s: vreg_set_level failed (%d)", + __func__, rc); + } + rc = vreg_enable(vreg_analog); + if (rc) { + pr_err("%s: analog vreg enable failed (%d)", + __func__, rc); + } + rc = vreg_set_level(vreg_io, 1800); + if (rc) { + pr_err("%s: vreg_set_level failed (%d)", + __func__, rc); + } + rc = vreg_enable(vreg_io); + if (rc) { + pr_err("%s: io vreg enable failed (%d)", + __func__, rc); + } + } else { + rc = vreg_disable(vreg_analog); + if (rc) { + pr_err("%s: analog vreg disable failed (%d)", + __func__, rc); + } + rc = vreg_disable(vreg_io); + if (rc) { + pr_err("%s: io vreg disable failed (%d)", + __func__, rc); + } + } + return rc; +} + +static int mbp_set_tcxo_en(int enable) +{ + int rc; + const char *id = "UBMC"; + struct vreg *vreg_analog = NULL; + + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_A1, + enable ? PMAPP_CLOCK_VOTE_ON : PMAPP_CLOCK_VOTE_OFF); + if (rc < 0) { + printk(KERN_ERR "%s: unable to %svote for a1 clk\n", + __func__, enable ? "" : "de-"); + return -EIO; + } + if (!enable) { + vreg_analog = vreg_get(NULL, "gp4"); + if (IS_ERR(vreg_analog)) { + pr_err("%s: gp4 vreg get failed (%ld)", + __func__, PTR_ERR(vreg_analog)); + return -EFAULT; + } + + (void) vreg_disable(vreg_analog); + } + return rc; +} + +static void mbp_set_freeze_io(int state) +{ + if (state) + gpio_set_value(85, 0); + else + gpio_set_value(85, 1); +} + +static int mbp_set_core_voltage_en(int enable) +{ + int rc; + struct vreg *vreg_core1p2 = NULL; + + vreg_core1p2 = vreg_get(NULL, "gp16"); + if (IS_ERR(vreg_core1p2)) { + pr_err("%s: gp16 vreg get failed (%ld)", + __func__, PTR_ERR(vreg_core1p2)); + return -EFAULT; + } + if (enable) { + rc = vreg_set_level(vreg_core1p2, 1200); + if (rc) { + pr_err("%s: vreg_set_level failed (%d)", + __func__, rc); + } + (void) vreg_enable(vreg_core1p2); + + return 80; + } else { + gpio_set_value(85, 1); + return 0; + } + return rc; +} + +static void mbp_set_reset(int state) +{ + if (state) + gpio_set_value(GPIO_PIN(MBP_RESET_N), 0); + else + gpio_set_value(GPIO_PIN(MBP_RESET_N), 1); +} + +static int mbp_config_interface_mode(int state) +{ + if (state) { + gpio_tlmm_config(MBP_MODE_CTRL_0, GPIO_CFG_ENABLE); + gpio_tlmm_config(MBP_MODE_CTRL_1, GPIO_CFG_ENABLE); + gpio_tlmm_config(MBP_MODE_CTRL_2, GPIO_CFG_ENABLE); + gpio_set_value(GPIO_PIN(MBP_MODE_CTRL_0), 0); + gpio_set_value(GPIO_PIN(MBP_MODE_CTRL_1), 1); + gpio_set_value(GPIO_PIN(MBP_MODE_CTRL_2), 0); + } else { + gpio_tlmm_config(MBP_MODE_CTRL_0, GPIO_CFG_DISABLE); + gpio_tlmm_config(MBP_MODE_CTRL_1, GPIO_CFG_DISABLE); + gpio_tlmm_config(MBP_MODE_CTRL_2, GPIO_CFG_DISABLE); + } + return 0; +} + +static int mbp_setup_adc_vregs(int state) +{ + struct vreg *vreg_adc = NULL; + int rc; + + vreg_adc = vreg_get(NULL, "s4"); + if (IS_ERR(vreg_adc)) { + pr_err("%s: s4 vreg get failed (%ld)", + __func__, PTR_ERR(vreg_adc)); + return -EFAULT; + } + if (state) { + rc = vreg_set_level(vreg_adc, 2200); + if (rc) { + pr_err("%s: vreg_set_level failed (%d)", + __func__, rc); + } + rc = vreg_enable(vreg_adc); + if (rc) { + pr_err("%s: enable vreg adc failed (%d)", + __func__, rc); + } + } else { + rc = vreg_disable(vreg_adc); + if (rc) { + pr_err("%s: disable vreg adc failed (%d)", + __func__, rc); + } + } + return rc; +} + +static int mbp_power_up(void) +{ + int rc; + + rc = mbp_config_gpios_pre_init(MBP_ON); + if (rc) + goto exit; + pr_debug("%s: mbp_config_gpios_pre_init() done\n", __func__); + + rc = mbp_setup_vregs(MBP_ON); + if (rc) + goto exit; + pr_debug("%s: gp4 (2.6) and s3 (1.8) done\n", __func__); + + rc = mbp_set_tcxo_en(MBP_ON); + if (rc) + goto exit; + pr_debug("%s: tcxo clock done\n", __func__); + + mbp_set_freeze_io(MBP_OFF); + pr_debug("%s: set gpio 85 to 1 done\n", __func__); + + udelay(100); + mbp_set_reset(MBP_ON); + + udelay(300); + rc = mbp_config_interface_mode(MBP_ON); + if (rc) + goto exit; + pr_debug("%s: mbp_config_interface_mode() done\n", __func__); + + udelay(100 + mbp_set_core_voltage_en(MBP_ON)); + pr_debug("%s: power gp16 1.2V done\n", __func__); + + mbp_set_freeze_io(MBP_ON); + pr_debug("%s: set gpio 85 to 0 done\n", __func__); + + udelay(100); + + rc = mbp_setup_rf_vregs(MBP_ON); + if (rc) + goto exit; + pr_debug("%s: s2 1.3V and rf 2.6V done\n", __func__); + + rc = mbp_setup_adc_vregs(MBP_ON); + if (rc) + goto exit; + pr_debug("%s: s4 2.2V done\n", __func__); + + udelay(200); + + mbp_set_reset(MBP_OFF); + pr_debug("%s: close gpio 44 done\n", __func__); + + msleep(20); +exit: + return rc; +} + +static int mbp_power_down(void) +{ + int rc; + struct vreg *vreg_adc = NULL; + + vreg_adc = vreg_get(NULL, "s4"); + if (IS_ERR(vreg_adc)) { + pr_err("%s: s4 vreg get failed (%ld)", + __func__, PTR_ERR(vreg_adc)); + return -EFAULT; + } + + mbp_set_reset(MBP_ON); + pr_debug("%s: mbp_set_reset(MBP_ON) done\n", __func__); + + udelay(100); + + rc = mbp_setup_adc_vregs(MBP_OFF); + if (rc) + goto exit; + pr_debug("%s: vreg_disable(vreg_adc) done\n", __func__); + + udelay(5); + + rc = mbp_setup_rf_vregs(MBP_OFF); + if (rc) + goto exit; + pr_debug("%s: mbp_setup_rf_vregs(MBP_OFF) done\n", __func__); + + udelay(5); + + mbp_set_freeze_io(MBP_OFF); + pr_debug("%s: mbp_set_freeze_io(MBP_OFF) done\n", __func__); + + udelay(100); + rc = mbp_set_core_voltage_en(MBP_OFF); + if (rc) + goto exit; + pr_debug("%s: mbp_set_core_voltage_en(MBP_OFF) done\n", __func__); + + gpio_set_value(85, 1); + + rc = mbp_set_tcxo_en(MBP_OFF); + if (rc) + goto exit; + pr_debug("%s: mbp_set_tcxo_en(MBP_OFF) done\n", __func__); + + rc = mbp_config_gpios_pre_init(MBP_OFF); + if (rc) + goto exit; +exit: + return rc; +} + +static void (*mbp_status_notify_cb)(int card_present, void *dev_id); +static void *mbp_status_notify_cb_devid; +static int mbp_power_status; +static int mbp_power_init_done; + +static uint32_t mbp_setup_power(struct device *dv, + unsigned int power_status) +{ + int rc = 0; + struct platform_device *pdev; + + pdev = container_of(dv, struct platform_device, dev); + + if (power_status == mbp_power_status) + goto exit; + if (power_status) { + pr_debug("turn on power of mbp slot"); + rc = mbp_power_up(); + mbp_power_status = 1; + } else { + pr_debug("turn off power of mbp slot"); + rc = mbp_power_down(); + mbp_power_status = 0; + } +exit: + return rc; +}; + +int mbp_register_status_notify(void (*callback)(int, void *), + void *dev_id) +{ + mbp_status_notify_cb = callback; + mbp_status_notify_cb_devid = dev_id; + return 0; +} + +static unsigned int mbp_status(struct device *dev) +{ + return mbp_power_status; +} + +static uint32_t msm_sdcc_setup_power_mbp(struct device *dv, unsigned int vdd) +{ + struct platform_device *pdev; + uint32_t rc = 0; + + pdev = container_of(dv, struct platform_device, dev); + rc = msm_sdcc_setup_power(dv, vdd); + if (rc) { + pr_err("%s: Failed to setup power (%d)\n", + __func__, rc); + goto out; + } + if (!mbp_power_init_done) { + mbp_setup_power(dv, 1); + mbp_setup_power(dv, 0); + mbp_power_init_done = 1; + } + if (vdd >= 0x8000) { + rc = mbp_setup_power(dv, (0x8000 == vdd) ? 0 : 1); + if (rc) { + pr_err("%s: Failed to config mbp chip power (%d)\n", + __func__, rc); + goto out; + } + if (mbp_status_notify_cb) { + mbp_status_notify_cb(mbp_power_status, + mbp_status_notify_cb_devid); + } + } +out: + /* should return 0 only */ + return 0; +} + +#endif + +#endif + +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION +static unsigned int msm7x30_sdcc_slot_status(struct device *dev) +{ + return (unsigned int) + gpio_get_value_cansleep( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SD_DET - 1)); +} +#endif + +static int msm_sdcc_get_wpswitch(struct device *dv) +{ + void __iomem *wp_addr = 0; + uint32_t ret = 0; + struct platform_device *pdev; + + if (!(machine_is_msm7x30_surf())) + return -1; + pdev = container_of(dv, struct platform_device, dev); + + wp_addr = ioremap(FPGA_SDCC_STATUS, 4); + if (!wp_addr) { + pr_err("%s: Could not remap %x\n", __func__, FPGA_SDCC_STATUS); + return -ENOMEM; + } + + ret = (((readl(wp_addr) >> 4) >> (pdev->id-1)) & 0x01); + pr_info("%s: WP Status for Slot %d = 0x%x \n", __func__, + pdev->id, ret); + iounmap(wp_addr); + + return ret; +} +#endif + +#if defined(CONFIG_MMC_MSM_SDC1_SUPPORT) +#if defined(CONFIG_CSDIO_VENDOR_ID) && \ + defined(CONFIG_CSDIO_DEVICE_ID) && \ + (CONFIG_CSDIO_VENDOR_ID == 0x70 && CONFIG_CSDIO_DEVICE_ID == 0x1117) +static struct mmc_platform_data msm7x30_sdc1_data = { + .ocr_mask = MMC_VDD_165_195 | MMC_VDD_27_28 | MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power_mbp, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .status = mbp_status, + .register_status_notify = mbp_register_status_notify, +#ifdef CONFIG_MMC_MSM_SDC1_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 24576000, + .nonremovable = 0, +}; +#else +static struct mmc_platform_data msm7x30_sdc1_data = { + .ocr_mask = MMC_VDD_165_195, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#ifdef CONFIG_MMC_MSM_SDC1_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +}; +#endif +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static struct mmc_platform_data msm7x30_sdc2_data = { + .ocr_mask = MMC_VDD_165_195 | MMC_VDD_27_28, + .translate_vdd = msm_sdcc_setup_power, +#ifdef CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT + .mmc_bus_width = MMC_CAP_8_BIT_DATA, +#else + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#endif +#ifdef CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 1, +}; +#endif -#include -#include -#include -#include +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static struct mmc_platform_data msm7x30_sdc3_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#ifdef CONFIG_MMC_MSM_SDIO_SUPPORT + .sdiowakeup_irq = MSM_GPIO_TO_INT(118), +#endif +#ifdef CONFIG_MMC_MSM_SDC3_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +}; +#endif -#include -#include -#include -#include +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT +static struct mmc_platform_data msm7x30_sdc4_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + .status = msm7x30_sdcc_slot_status, + .status_irq = PM8058_GPIO_IRQ(PMIC8058_IRQ_BASE, PMIC_GPIO_SD_DET - 1), + .irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, +#endif + .wpswitch = msm_sdcc_get_wpswitch, +#ifdef CONFIG_MMC_MSM_SDC4_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 24576000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +}; +#endif -#include -#include "devices.h" -#include "gpiomux.h" -#include "proc_comm.h" +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static void msm_sdc1_lvlshft_enable(void) +{ + int rc; + + /* Enable LDO5, an input to the FET that powers slot 1 */ + rc = vreg_set_level(vreg_mmc, 2850); + if (rc) + printk(KERN_ERR "%s: vreg_set_level() = %d \n", __func__, rc); + + rc = vreg_enable(vreg_mmc); + if (rc) + printk(KERN_ERR "%s: vreg_enable() = %d \n", __func__, rc); + + /* Enable GPIO 35, to turn on the FET that powers slot 1 */ + rc = msm_gpios_request_enable(sdc1_lvlshft_cfg_data, + ARRAY_SIZE(sdc1_lvlshft_cfg_data)); + if (rc) + printk(KERN_ERR "%s: Failed to enable GPIO 35\n", __func__); + + rc = gpio_direction_output(GPIO_PIN(sdc1_lvlshft_cfg_data[0].gpio_cfg), + 1); + if (rc) + printk(KERN_ERR "%s: Failed to turn on GPIO 35\n", __func__); +} +#endif + +static void __init msm7x30_init_mmc(void) +{ + vreg_s3 = vreg_get(NULL, "s3"); + if (IS_ERR(vreg_s3)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_s3)); + return; + } + + vreg_mmc = vreg_get(NULL, "mmc"); + if (IS_ERR(vreg_mmc)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_mmc)); + return; + } + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + if (machine_is_msm7x30_fluid()) { + msm7x30_sdc1_data.ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29; + msm_sdc1_lvlshft_enable(); + } + sdcc_vreg_data[0].vreg_data = vreg_s3; + sdcc_vreg_data[0].level = 1800; + msm_add_sdcc(1, &msm7x30_sdc1_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + if (machine_is_msm8x55_svlte_surf()) + msm7x30_sdc2_data.msmsdcc_fmax = 24576000; + sdcc_vreg_data[1].vreg_data = vreg_s3; + sdcc_vreg_data[1].level = 1800; + msm_add_sdcc(2, &msm7x30_sdc2_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + sdcc_vreg_data[2].vreg_data = vreg_s3; + sdcc_vreg_data[2].level = 1800; + msm_sdcc_setup_gpio(3, 1); + msm_add_sdcc(3, &msm7x30_sdc3_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT + sdcc_vreg_data[3].vreg_data = vreg_mmc; + sdcc_vreg_data[3].level = 2850; + msm_add_sdcc(4, &msm7x30_sdc4_data); +#endif + +} + +static void __init msm7x30_init_nand(void) +{ + char *build_id; + struct flash_platform_data *plat_data; + + build_id = socinfo_get_build_id(); + if (build_id == NULL) { + pr_err("%s: Build ID not available from socinfo\n", __func__); + return; + } -extern struct sys_timer msm_timer; + if (build_id[8] == 'C' && + !msm_gpios_request_enable(msm_nand_ebi2_cfg_data, + ARRAY_SIZE(msm_nand_ebi2_cfg_data))) { + plat_data = msm_device_nand.dev.platform_data; + plat_data->interleave = 1; + printk(KERN_INFO "%s: Interleave mode Build ID found\n", + __func__); + } +} -static int hsusb_phy_init_seq[] = { - 0x30, 0x32, /* Enable and set Pre-Emphasis Depth to 20% */ - 0x02, 0x36, /* Disable CDR Auto Reset feature */ - -1 +#ifdef CONFIG_SERIAL_MSM_CONSOLE +static struct msm_gpio uart2_config_data[] = { + { GPIO_CFG(49, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "UART2_RFR"}, + { GPIO_CFG(50, 2, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "UART2_CTS"}, + { GPIO_CFG(51, 2, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "UART2_Rx"}, + { GPIO_CFG(52, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), "UART2_Tx"}, }; -static struct msm_otg_platform_data msm_otg_pdata = { - .phy_init_seq = hsusb_phy_init_seq, - .mode = USB_PERIPHERAL, - .otg_control = OTG_PHY_CONTROL, +static void msm7x30_init_uart2(void) +{ + msm_gpios_request_enable(uart2_config_data, + ARRAY_SIZE(uart2_config_data)); + +} +#endif + +/* TSIF begin */ +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + +#define TSIF_B_SYNC GPIO_CFG(37, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_B_DATA GPIO_CFG(36, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_B_EN GPIO_CFG(35, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_B_CLK GPIO_CFG(34, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) + +static const struct msm_gpio tsif_gpios[] = { + { .gpio_cfg = TSIF_B_CLK, .label = "tsif_clk", }, + { .gpio_cfg = TSIF_B_EN, .label = "tsif_en", }, + { .gpio_cfg = TSIF_B_DATA, .label = "tsif_data", }, + { .gpio_cfg = TSIF_B_SYNC, .label = "tsif_sync", }, }; -struct msm_gpiomux_config msm_gpiomux_configs[GPIOMUX_NGPIOS] = { -#ifdef CONFIG_SERIAL_MSM_CONSOLE - [49] = { /* UART2 RFR */ - .suspended = GPIOMUX_DRV_2MA | GPIOMUX_PULL_DOWN | - GPIOMUX_FUNC_2 | GPIOMUX_VALID, - }, - [50] = { /* UART2 CTS */ - .suspended = GPIOMUX_DRV_2MA | GPIOMUX_PULL_DOWN | - GPIOMUX_FUNC_2 | GPIOMUX_VALID, +static struct msm_tsif_platform_data tsif_platform_data = { + .num_gpios = ARRAY_SIZE(tsif_gpios), + .gpios = tsif_gpios, + .tsif_pclk = "tsif_pclk", + .tsif_ref_clk = "tsif_ref_clk", +}; +#endif /* defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) */ +/* TSIF end */ + +static void __init pmic8058_leds_init(void) +{ + if (machine_is_msm7x30_surf()) { + pm8058_7x30_data.sub_devices[PM8058_SUBDEV_LED].platform_data + = &pm8058_surf_leds_data; + } else if (!machine_is_msm7x30_fluid()) { + pm8058_7x30_data.sub_devices[PM8058_SUBDEV_LED].platform_data + = &pm8058_ffa_leds_data; + } else if (machine_is_msm7x30_fluid()) { + pm8058_7x30_data.sub_devices[PM8058_SUBDEV_LED].platform_data + = &pm8058_fluid_leds_data; + } +} + +static struct msm_spm_platform_data msm_spm_data __initdata = { + .reg_base_addr = MSM_SAW_BASE, + + .reg_init_values[MSM_SPM_REG_SAW_CFG] = 0x05, + .reg_init_values[MSM_SPM_REG_SAW_SPM_CTL] = 0x18, + .reg_init_values[MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY] = 0x00006666, + .reg_init_values[MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY] = 0xFF000666, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLK_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN] = 0x03, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN] = 0x00, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLMP_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_RST_EN] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW_SPM_MPM_CFG] = 0x00, + + .awake_vlevel = 0xF2, + .retention_vlevel = 0xE0, + .collapse_vlevel = 0x72, + .retention_mid_vlevel = 0xE0, + .collapse_mid_vlevel = 0xE0, + + .vctl_timeout_us = 50, +}; + +#if defined(CONFIG_TOUCHSCREEN_TSC2007) || \ + defined(CONFIG_TOUCHSCREEN_TSC2007_MODULE) + +#define TSC2007_TS_PEN_INT 20 + +static struct msm_gpio tsc2007_config_data[] = { + { GPIO_CFG(TSC2007_TS_PEN_INT, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "tsc2007_irq" }, +}; + +static struct vreg *vreg_tsc_s3; +static struct vreg *vreg_tsc_s2; + +static int tsc2007_init(void) +{ + int rc; + + vreg_tsc_s3 = vreg_get(NULL, "s3"); + if (IS_ERR(vreg_tsc_s3)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_tsc_s3)); + return -ENODEV; + } + + rc = vreg_set_level(vreg_tsc_s3, 1800); + if (rc) { + pr_err("%s: vreg_set_level failed \n", __func__); + goto fail_vreg_set_level; + } + + rc = vreg_enable(vreg_tsc_s3); + if (rc) { + pr_err("%s: vreg_enable failed \n", __func__); + goto fail_vreg_set_level; + } + + vreg_tsc_s2 = vreg_get(NULL, "s2"); + if (IS_ERR(vreg_tsc_s2)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_tsc_s2)); + goto fail_vreg_get; + } + + rc = vreg_set_level(vreg_tsc_s2, 1300); + if (rc) { + pr_err("%s: vreg_set_level failed \n", __func__); + goto fail_vreg_s2_level; + } + + rc = vreg_enable(vreg_tsc_s2); + if (rc) { + pr_err("%s: vreg_enable failed \n", __func__); + goto fail_vreg_s2_level; + } + + rc = msm_gpios_request_enable(tsc2007_config_data, + ARRAY_SIZE(tsc2007_config_data)); + if (rc) { + pr_err("%s: Unable to request gpios\n", __func__); + goto fail_gpio_req; + } + + return 0; + +fail_gpio_req: + vreg_disable(vreg_tsc_s2); +fail_vreg_s2_level: + vreg_put(vreg_tsc_s2); +fail_vreg_get: + vreg_disable(vreg_tsc_s3); +fail_vreg_set_level: + vreg_put(vreg_tsc_s3); + return rc; +} + +static int tsc2007_get_pendown_state(void) +{ + int rc; + + rc = gpio_get_value(TSC2007_TS_PEN_INT); + if (rc < 0) { + pr_err("%s: MSM GPIO %d read failed\n", __func__, + TSC2007_TS_PEN_INT); + return rc; + } + + return (rc == 0 ? 1 : 0); +} + +static void tsc2007_exit(void) +{ + vreg_disable(vreg_tsc_s3); + vreg_put(vreg_tsc_s3); + vreg_disable(vreg_tsc_s2); + vreg_put(vreg_tsc_s2); + + msm_gpios_disable_free(tsc2007_config_data, + ARRAY_SIZE(tsc2007_config_data)); +} + +static int tsc2007_power_shutdown(bool enable) +{ + int rc; + + if (enable == false) { + rc = vreg_enable(vreg_tsc_s2); + if (rc) { + pr_err("%s: vreg_enable failed\n", __func__); + return rc; + } + rc = vreg_enable(vreg_tsc_s3); + if (rc) { + pr_err("%s: vreg_enable failed\n", __func__); + vreg_disable(vreg_tsc_s2); + return rc; + } + /* Voltage settling delay */ + msleep(20); + } else { + rc = vreg_disable(vreg_tsc_s2); + if (rc) { + pr_err("%s: vreg_disable failed\n", __func__); + return rc; + } + rc = vreg_disable(vreg_tsc_s3); + if (rc) { + pr_err("%s: vreg_disable failed\n", __func__); + vreg_enable(vreg_tsc_s2); + return rc; + } + } + + return rc; +} + +static struct tsc2007_platform_data tsc2007_ts_data = { + .model = 2007, + .x_plate_ohms = 300, + .irq_flags = IRQF_TRIGGER_LOW, + .init_platform_hw = tsc2007_init, + .exit_platform_hw = tsc2007_exit, + .power_shutdown = tsc2007_power_shutdown, + .invert_x = true, + .invert_y = true, + /* REVISIT: Temporary fix for reversed pressure */ + .invert_z1 = true, + .invert_z2 = true, + .get_pendown_state = tsc2007_get_pendown_state, +}; + +static struct i2c_board_info tsc_i2c_board_info[] = { + { + I2C_BOARD_INFO("tsc2007", 0x48), + .irq = MSM_GPIO_TO_INT(TSC2007_TS_PEN_INT), + .platform_data = &tsc2007_ts_data, }, - [51] = { /* UART2 RX */ - .suspended = GPIOMUX_DRV_2MA | GPIOMUX_PULL_DOWN | - GPIOMUX_FUNC_2 | GPIOMUX_VALID, +}; +#endif + +static const char *vregs_isa1200_name[] = { + "gp7", + "gp10", +}; + +static const int vregs_isa1200_val[] = { + 1800, + 2600, +}; +static struct vreg *vregs_isa1200[ARRAY_SIZE(vregs_isa1200_name)]; + +static int isa1200_power(int vreg_on) +{ + int i, rc = 0; + + for (i = 0; i < ARRAY_SIZE(vregs_isa1200_name); i++) { + if (!vregs_isa1200[i]) { + pr_err("%s: vreg_get %s failed (%d)\n", + __func__, vregs_isa1200_name[i], rc); + goto vreg_fail; + } + + rc = vreg_on ? vreg_enable(vregs_isa1200[i]) : + vreg_disable(vregs_isa1200[i]); + if (rc < 0) { + pr_err("%s: vreg %s %s failed (%d)\n", + __func__, vregs_isa1200_name[i], + vreg_on ? "enable" : "disable", rc); + goto vreg_fail; + } + } + + /* vote for DO buffer */ + rc = pmapp_clock_vote("VIBR", PMAPP_CLOCK_ID_DO, + vreg_on ? PMAPP_CLOCK_VOTE_ON : PMAPP_CLOCK_VOTE_OFF); + if (rc) { + pr_err("%s: unable to %svote for d0 clk\n", + __func__, vreg_on ? "" : "de-"); + goto vreg_fail; + } + + return 0; + +vreg_fail: + while (i) + vreg_disable(vregs_isa1200[--i]); + return rc; +} + +static int isa1200_dev_setup(bool enable) +{ + int i, rc; + + if (enable == true) { + for (i = 0; i < ARRAY_SIZE(vregs_isa1200_name); i++) { + vregs_isa1200[i] = vreg_get(NULL, + vregs_isa1200_name[i]); + if (IS_ERR(vregs_isa1200[i])) { + pr_err("%s: vreg get %s failed (%ld)\n", + __func__, vregs_isa1200_name[i], + PTR_ERR(vregs_isa1200[i])); + rc = PTR_ERR(vregs_isa1200[i]); + goto vreg_get_fail; + } + rc = vreg_set_level(vregs_isa1200[i], + vregs_isa1200_val[i]); + if (rc) { + pr_err("%s: vreg_set_level() = %d\n", + __func__, rc); + goto vreg_get_fail; + } + } + + rc = gpio_tlmm_config(GPIO_CFG(HAP_LVL_SHFT_MSM_GPIO, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: Could not configure gpio %d\n", + __func__, HAP_LVL_SHFT_MSM_GPIO); + goto vreg_get_fail; + } + + rc = gpio_request(HAP_LVL_SHFT_MSM_GPIO, "haptics_shft_lvl_oe"); + if (rc) { + pr_err("%s: unable to request gpio %d (%d)\n", + __func__, HAP_LVL_SHFT_MSM_GPIO, rc); + goto vreg_get_fail; + } + + gpio_set_value(HAP_LVL_SHFT_MSM_GPIO, 1); + } else { + for (i = 0; i < ARRAY_SIZE(vregs_isa1200_name); i++) + vreg_put(vregs_isa1200[i]); + + gpio_free(HAP_LVL_SHFT_MSM_GPIO); + } + + return 0; +vreg_get_fail: + while (i) + vreg_put(vregs_isa1200[--i]); + return rc; +} +static struct isa1200_platform_data isa1200_1_pdata = { + .name = "vibrator", + .power_on = isa1200_power, + .dev_setup = isa1200_dev_setup, + .pwm_ch_id = 1, /*channel id*/ + /*gpio to enable haptic*/ + .hap_en_gpio = PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_HAP_ENABLE), + .max_timeout = 15000, + .mode_ctrl = PWM_GEN_MODE, + .pwm_fd = { + .pwm_div = 256, }, - [52] = { /* UART2 TX */ - .suspended = GPIOMUX_DRV_2MA | GPIOMUX_PULL_DOWN | - GPIOMUX_FUNC_2 | GPIOMUX_VALID, + .is_erm = false, + .smart_en = true, + .ext_clk_en = true, + .chip_en = 1, +}; + +static struct i2c_board_info msm_isa1200_board_info[] = { + { + I2C_BOARD_INFO("isa1200_1", 0x90>>1), + .platform_data = &isa1200_1_pdata, }, -#endif }; -static struct platform_device *devices[] __initdata = { -#if defined(CONFIG_SERIAL_MSM) || defined(CONFIG_MSM_SERIAL_DEBUGGER) - &msm_device_uart2, -#endif - &msm_device_smd, - &msm_device_otg, - &msm_device_hsusb, - &msm_device_hsusb_host, + +static int kp_flip_mpp_config(void) +{ + return pm8058_mpp_config_digital_in(PM_FLIP_MPP, + PM8058_MPP_DIG_LEVEL_S3, PM_MPP_DIN_TO_INT); +} + +static struct flip_switch_pdata flip_switch_data = { + .name = "kp_flip_switch", + .flip_gpio = PM8058_GPIO_PM_TO_SYS(PM8058_GPIOS) + PM_FLIP_MPP, + .left_key = KEY_OPEN, + .right_key = KEY_CLOSE, + .active_low = 0, + .wakeup = 1, + .flip_mpp_config = kp_flip_mpp_config, }; -static void __init msm7x30_init_irq(void) +static struct platform_device flip_switch_device = { + .name = "kp_flip_switch", + .id = -1, + .dev = { + .platform_data = &flip_switch_data, + } +}; + +static const char *vregs_tma300_name[] = { + "gp6", + "gp7", +}; + +static const int vregs_tma300_val[] = { + 3050, + 1800, +}; +static struct vreg *vregs_tma300[ARRAY_SIZE(vregs_tma300_name)]; + +static int tma300_power(int vreg_on) { - msm_init_irq(); + int i, rc = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(vregs_tma300_name); i++) { + /* Never disable gp6 for fluid as lcd has a problem with it */ + if (!i && !vreg_on) + continue; + if (!vregs_tma300[i]) { + printk(KERN_ERR "%s: vreg_get %s failed (%d)\n", + __func__, vregs_tma300_name[i], rc); + return rc; + } + + rc = vreg_on ? vreg_enable(vregs_tma300[i]) : + vreg_disable(vregs_tma300[i]); + if (rc < 0) { + printk(KERN_ERR "%s: vreg %s %s failed (%d)\n", + __func__, vregs_tma300_name[i], + vreg_on ? "enable" : "disable", rc); + return rc; + } + } + + return 0; +} + +#define TS_GPIO_IRQ 150 + +static int tma300_dev_setup(bool enable) +{ + int i, rc; + + if (enable) { + /* get voltage sources */ + for (i = 0; i < ARRAY_SIZE(vregs_tma300_name); i++) { + vregs_tma300[i] = vreg_get(NULL, vregs_tma300_name[i]); + if (IS_ERR(vregs_tma300[i])) { + pr_err("%s: vreg get %s failed (%ld)\n", + __func__, vregs_tma300_name[i], + PTR_ERR(vregs_tma300[i])); + rc = PTR_ERR(vregs_tma300[i]); + goto vreg_get_fail; + } + rc = vreg_set_level(vregs_tma300[i], + vregs_tma300_val[i]); + if (rc) { + pr_err("%s: vreg_set_level() = %d\n", + __func__, rc); + i++; + goto vreg_get_fail; + } + } + + /* enable interrupt gpio */ + rc = gpio_tlmm_config(GPIO_CFG(TS_GPIO_IRQ, 0, GPIO_CFG_INPUT, + GPIO_CFG_PULL_UP, GPIO_CFG_6MA), GPIO_CFG_ENABLE); + if (rc) { + pr_err("%s: Could not configure gpio %d\n", + __func__, TS_GPIO_IRQ); + goto vreg_get_fail; + } + + /* virtual keys */ + tma300_vkeys_attr.attr.name = "virtualkeys.msm_tma300_ts"; + properties_kobj = kobject_create_and_add("board_properties", + NULL); + if (!properties_kobj) { + pr_err("%s: failed to create a kobject" + "for board_properites\n", __func__); + rc = -ENOMEM; + goto vreg_get_fail; + } + rc = sysfs_create_group(properties_kobj, + &tma300_properties_attr_group); + if (rc) { + pr_err("%s: failed to create a sysfs entry %s\n", + __func__, tma300_vkeys_attr.attr.name); + kobject_put(properties_kobj); + goto vreg_get_fail; + } + } else { + /* put voltage sources */ + for (i = 0; i < ARRAY_SIZE(vregs_tma300_name); i++) + vreg_put(vregs_tma300[i]); + /* destroy virtual keys */ + if (properties_kobj) { + sysfs_remove_group(properties_kobj, + &tma300_properties_attr_group); + kobject_put(properties_kobj); + } + } + return 0; + +vreg_get_fail: + while (i) + vreg_put(vregs_tma300[--i]); + return rc; } +static struct cy8c_ts_platform_data cy8ctma300_pdata = { + .power_on = tma300_power, + .dev_setup = tma300_dev_setup, + .ts_name = "msm_tma300_ts", + .dis_min_x = 0, + .dis_max_x = 479, + .dis_min_y = 0, + .dis_max_y = 799, + .res_x = 479, + .res_y = 1009, + .min_tid = 1, + .max_tid = 255, + .min_touch = 0, + .max_touch = 255, + .min_width = 0, + .max_width = 255, + .invert_y = 1, + .nfingers = 4, + .irq_gpio = TS_GPIO_IRQ, + .resout_gpio = -1, +}; + +static struct i2c_board_info cy8ctma300_board_info[] = { + { + I2C_BOARD_INFO("cy8ctma300", 0x2), + .platform_data = &cy8ctma300_pdata, + } +}; + static void __init msm7x30_init(void) { + int rc; + unsigned smem_size; + uint32_t usb_hub_gpio_cfg_value = GPIO_CFG(56, + 0, + GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, + GPIO_CFG_2MA); + uint32_t soc_version = 0; + + if (socinfo_init() < 0) + printk(KERN_ERR "%s: socinfo_init() failed!\n", + __func__); + + soc_version = socinfo_get_version(); + + msm7x30_clock_init(); +#ifdef CONFIG_SERIAL_MSM_CONSOLE + msm7x30_init_uart2(); +#endif + msm_spm_init(&msm_spm_data, 1); + msm_acpu_clock_init(&msm7x30_clock_data); + if (machine_is_msm7x30_surf() || machine_is_msm7x30_fluid()) + msm7x30_cfg_smsc911x(); + +#ifdef CONFIG_USB_MSM_OTG_72K + if (SOCINFO_VERSION_MAJOR(soc_version) >= 2 && + SOCINFO_VERSION_MINOR(soc_version) >= 1) { + pr_debug("%s: SOC Version:2.(1 or more)\n", __func__); + msm_otg_pdata.ldo_set_voltage = 0; + } + msm_device_otg.dev.platform_data = &msm_otg_pdata; - msm_device_hsusb.dev.parent = &msm_device_otg.dev; - msm_device_hsusb_host.dev.parent = &msm_device_otg.dev; +#ifdef CONFIG_USB_GADGET + msm_otg_pdata.swfi_latency = + msm_pm_data + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT].latency; + msm_device_gadget_peripheral.dev.platform_data = &msm_gadget_pdata; +#endif +#endif + msm_uart_dm1_pdata.wakeup_irq = gpio_to_irq(136); + msm_device_uart_dm1.dev.platform_data = &msm_uart_dm1_pdata; +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + msm_device_tsif.dev.platform_data = &tsif_platform_data; +#endif + if (machine_is_msm7x30_fluid()) { + msm_adc_pdata.dev_names = msm_adc_fluid_device_names; + msm_adc_pdata.num_adc = ARRAY_SIZE(msm_adc_fluid_device_names); + } else { + msm_adc_pdata.dev_names = msm_adc_surf_device_names; + msm_adc_pdata.num_adc = ARRAY_SIZE(msm_adc_surf_device_names); + } + platform_add_devices(msm_footswitch_devices, + msm_num_footswitch_devices); platform_add_devices(devices, ARRAY_SIZE(devices)); +#ifdef CONFIG_USB_EHCI_MSM_72K + msm_add_host(0, &msm_usb_host_pdata); +#endif + msm7x30_init_mmc(); + msm7x30_init_nand(); + msm_qsd_spi_init(); + +#ifdef CONFIG_SPI_QSD + if (machine_is_msm7x30_fluid()) + spi_register_board_info(lcdc_sharp_spi_board_info, + ARRAY_SIZE(lcdc_sharp_spi_board_info)); + else + spi_register_board_info(lcdc_toshiba_spi_board_info, + ARRAY_SIZE(lcdc_toshiba_spi_board_info)); +#endif + + msm_fb_add_devices(); + msm_pm_set_platform_data(msm_pm_data, ARRAY_SIZE(msm_pm_data)); + msm_device_i2c_init(); + msm_device_i2c_2_init(); + qup_device_i2c_init(); + buses_init(); + msm7x30_init_marimba(); +#ifdef CONFIG_MSM7KV2_AUDIO + snddev_poweramp_gpio_init(); + aux_pcm_gpio_init(); +#endif + + i2c_register_board_info(0, msm_i2c_board_info, + ARRAY_SIZE(msm_i2c_board_info)); + + if (!machine_is_msm8x55_svlte_ffa() && !machine_is_msm7x30_fluid()) + marimba_pdata.tsadc = &marimba_tsadc_pdata; + + if (machine_is_msm7x30_fluid()) + i2c_register_board_info(0, cy8info, + ARRAY_SIZE(cy8info)); +#ifdef CONFIG_BOSCH_BMA150 + if (machine_is_msm7x30_fluid()) + i2c_register_board_info(0, bma150_board_info, + ARRAY_SIZE(bma150_board_info)); +#endif + + i2c_register_board_info(2, msm_marimba_board_info, + ARRAY_SIZE(msm_marimba_board_info)); + + i2c_register_board_info(2, msm_i2c_gsbi7_timpani_info, + ARRAY_SIZE(msm_i2c_gsbi7_timpani_info)); + + i2c_register_board_info(4 /* QUP ID */, msm_camera_boardinfo, + ARRAY_SIZE(msm_camera_boardinfo)); + + bt_power_init(); +#ifdef CONFIG_I2C_SSBI + msm_device_ssbi6.dev.platform_data = &msm_i2c_ssbi6_pdata; + msm_device_ssbi7.dev.platform_data = &msm_i2c_ssbi7_pdata; +#endif + if (machine_is_msm7x30_fluid()) + i2c_register_board_info(0, msm_isa1200_board_info, + ARRAY_SIZE(msm_isa1200_board_info)); + +#if defined(CONFIG_TOUCHSCREEN_TSC2007) || \ + defined(CONFIG_TOUCHSCREEN_TSC2007_MODULE) + if (machine_is_msm8x55_svlte_ffa()) + i2c_register_board_info(2, tsc_i2c_board_info, + ARRAY_SIZE(tsc_i2c_board_info)); +#endif + + if (machine_is_msm7x30_surf()) + platform_device_register(&flip_switch_device); + pmic8058_leds_init(); + + if (machine_is_msm7x30_fluid()) { + /* Initialize platform data for fluid v2 hardware */ + if (SOCINFO_VERSION_MAJOR( + socinfo_get_platform_version()) == 2) { + cy8ctma300_pdata.res_y = 920; + cy8ctma300_pdata.invert_y = 0; + } + i2c_register_board_info(0, cy8ctma300_board_info, + ARRAY_SIZE(cy8ctma300_board_info)); + } + + if (machine_is_msm8x55_svlte_surf() || machine_is_msm8x55_svlte_ffa()) { + rc = gpio_tlmm_config(usb_hub_gpio_cfg_value, GPIO_CFG_ENABLE); + if (rc) + pr_err("%s: gpio_tlmm_config(%#x)=%d\n", + __func__, usb_hub_gpio_cfg_value, rc); + } + + boot_reason = *(unsigned int *) + (smem_get_entry(SMEM_POWER_ON_STATUS_INFO, &smem_size)); + printk(KERN_NOTICE "Boot Reason = 0x%02x\n", boot_reason); +} + +static unsigned pmem_sf_size = MSM_PMEM_SF_SIZE; +static int __init pmem_sf_size_setup(char *p) +{ + pmem_sf_size = memparse(p, NULL); + return 0; +} +early_param("pmem_sf_size", pmem_sf_size_setup); + +static unsigned fb_size = MSM_FB_SIZE; +static int __init fb_size_setup(char *p) +{ + fb_size = memparse(p, NULL); + return 0; +} +early_param("fb_size", fb_size_setup); + +static unsigned pmem_adsp_size = MSM_PMEM_ADSP_SIZE; +static int __init pmem_adsp_size_setup(char *p) +{ + pmem_adsp_size = memparse(p, NULL); + return 0; +} +early_param("pmem_adsp_size", pmem_adsp_size_setup); + +static unsigned fluid_pmem_adsp_size = MSM_FLUID_PMEM_ADSP_SIZE; +static int __init fluid_pmem_adsp_size_setup(char *p) +{ + fluid_pmem_adsp_size = memparse(p, NULL); + return 0; +} +early_param("fluid_pmem_adsp_size", fluid_pmem_adsp_size_setup); + +static unsigned pmem_audio_size = MSM_PMEM_AUDIO_SIZE; +static int __init pmem_audio_size_setup(char *p) +{ + pmem_audio_size = memparse(p, NULL); + return 0; +} +early_param("pmem_audio_size", pmem_audio_size_setup); + +static unsigned pmem_kernel_ebi0_size = PMEM_KERNEL_EBI0_SIZE; +static int __init pmem_kernel_ebi0_size_setup(char *p) +{ + pmem_kernel_ebi0_size = memparse(p, NULL); + return 0; +} +early_param("pmem_kernel_ebi0_size", pmem_kernel_ebi0_size_setup); + +static struct memtype_reserve msm7x30_reserve_table[] __initdata = { + [MEMTYPE_SMI] = { + }, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, +}; + +static void __init size_pmem_devices(void) +{ +#ifdef CONFIG_ANDROID_PMEM + unsigned long size; + + if machine_is_msm7x30_fluid() + size = fluid_pmem_adsp_size; + else + size = pmem_adsp_size; + android_pmem_adsp_pdata.size = size; + android_pmem_audio_pdata.size = pmem_audio_size; + android_pmem_pdata.size = pmem_sf_size; +#endif +} + +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + msm7x30_reserve_table[p->memory_type].size += p->size; +} + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM + reserve_memory_for(&android_pmem_adsp_pdata); + reserve_memory_for(&android_pmem_audio_pdata); + reserve_memory_for(&android_pmem_pdata); + msm7x30_reserve_table[MEMTYPE_EBI0].size += pmem_kernel_ebi0_size; +#endif +} + +static void __init msm7x30_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); +} + +static int msm7x30_paddr_to_memtype(unsigned int paddr) +{ + if (paddr < 0x40000000) + return MEMTYPE_EBI0; + if (paddr >= 0x40000000 && paddr < 0x80000000) + return MEMTYPE_EBI1; + return MEMTYPE_NONE; +} + +static struct reserve_info msm7x30_reserve_info __initdata = { + .memtype_reserve_table = msm7x30_reserve_table, + .calculate_reserve_sizes = msm7x30_calculate_reserve_sizes, + .paddr_to_memtype = msm7x30_paddr_to_memtype, +}; + +static void __init msm7x30_reserve(void) +{ + reserve_info = &msm7x30_reserve_info; + msm_reserve(); +} + +static void __init msm7x30_allocate_memory_regions(void) +{ + void *addr; + unsigned long size; + + size = fb_size ? : MSM_FB_SIZE; + addr = alloc_bootmem_align(size, 0x1000); + msm_fb_resources[0].start = __pa(addr); + msm_fb_resources[0].end = msm_fb_resources[0].start + size - 1; + pr_info("allocating %lu bytes at %p (%lx physical) for fb\n", + size, addr, __pa(addr)); } static void __init msm7x30_map_io(void) { + msm_shared_ram_phys = 0x00100000; msm_map_msm7x30_io(); - msm_clock_init(msm_clocks_7x30, msm_num_clocks_7x30); +} + +static void __init msm7x30_init_early(void) +{ + msm7x30_allocate_memory_regions(); } MACHINE_START(MSM7X30_SURF, "QCT MSM7X30 SURF") .boot_params = PLAT_PHYS_OFFSET + 0x100, .map_io = msm7x30_map_io, + .reserve = msm7x30_reserve, .init_irq = msm7x30_init_irq, .init_machine = msm7x30_init, .timer = &msm_timer, + .init_early = msm7x30_init_early, MACHINE_END MACHINE_START(MSM7X30_FFA, "QCT MSM7X30 FFA") .boot_params = PLAT_PHYS_OFFSET + 0x100, .map_io = msm7x30_map_io, + .reserve = msm7x30_reserve, .init_irq = msm7x30_init_irq, .init_machine = msm7x30_init, .timer = &msm_timer, + .init_early = msm7x30_init_early, MACHINE_END MACHINE_START(MSM7X30_FLUID, "QCT MSM7X30 FLUID") .boot_params = PLAT_PHYS_OFFSET + 0x100, .map_io = msm7x30_map_io, + .reserve = msm7x30_reserve, + .init_irq = msm7x30_init_irq, + .init_machine = msm7x30_init, + .timer = &msm_timer, + .init_early = msm7x30_init_early, +MACHINE_END + +MACHINE_START(MSM8X55_SURF, "QCT MSM8X55 SURF") + .boot_params = PHYS_OFFSET + 0x100, + .map_io = msm7x30_map_io, + .reserve = msm7x30_reserve, + .init_irq = msm7x30_init_irq, + .init_machine = msm7x30_init, + .timer = &msm_timer, + .init_early = msm7x30_init_early, +MACHINE_END + +MACHINE_START(MSM8X55_FFA, "QCT MSM8X55 FFA") + .boot_params = PHYS_OFFSET + 0x100, + .map_io = msm7x30_map_io, + .reserve = msm7x30_reserve, + .init_irq = msm7x30_init_irq, + .init_machine = msm7x30_init, + .timer = &msm_timer, + .init_early = msm7x30_init_early, +MACHINE_END +MACHINE_START(MSM8X55_SVLTE_SURF, "QCT MSM8X55 SVLTE SURF") + .boot_params = PHYS_OFFSET + 0x100, + .map_io = msm7x30_map_io, + .reserve = msm7x30_reserve, + .init_irq = msm7x30_init_irq, + .init_machine = msm7x30_init, + .timer = &msm_timer, + .init_early = msm7x30_init_early, +MACHINE_END +MACHINE_START(MSM8X55_SVLTE_FFA, "QCT MSM8X55 SVLTE FFA") + .boot_params = PHYS_OFFSET + 0x100, + .map_io = msm7x30_map_io, + .reserve = msm7x30_reserve, .init_irq = msm7x30_init_irq, .init_machine = msm7x30_init, .timer = &msm_timer, + .init_early = msm7x30_init_early, MACHINE_END diff --git a/arch/arm/mach-msm/board-msm8960-regulator.c b/arch/arm/mach-msm/board-msm8960-regulator.c new file mode 100644 index 00000000000..d4b664362f4 --- /dev/null +++ b/arch/arm/mach-msm/board-msm8960-regulator.c @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "board-msm8960.h" + +#define VREG_CONSUMERS(_id) \ + static struct regulator_consumer_supply vreg_consumers_##_id[] + +/* + * Consumer specific regulator names: + * regulator name consumer dev_name + */ +VREG_CONSUMERS(L1) = { + REGULATOR_SUPPLY("8921_l1", NULL), +}; +VREG_CONSUMERS(L2) = { + REGULATOR_SUPPLY("8921_l2", NULL), + REGULATOR_SUPPLY("dsi_vdda", "mipi_dsi.1"), + REGULATOR_SUPPLY("mipi_csi_vdd", "msm_camera_imx074.0"), + REGULATOR_SUPPLY("mipi_csi_vdd", "msm_camera_ov2720.0"), +}; +VREG_CONSUMERS(L3) = { + REGULATOR_SUPPLY("8921_l3", NULL), + REGULATOR_SUPPLY("HSUSB_3p3", "msm_otg"), +}; +VREG_CONSUMERS(L4) = { + REGULATOR_SUPPLY("8921_l4", NULL), + REGULATOR_SUPPLY("HSUSB_1p8", "msm_otg"), + REGULATOR_SUPPLY("iris_vddxo", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(L5) = { + REGULATOR_SUPPLY("8921_l5", NULL), + REGULATOR_SUPPLY("sdc_vdd", "msm_sdcc.1"), +}; +VREG_CONSUMERS(L6) = { + REGULATOR_SUPPLY("8921_l6", NULL), + REGULATOR_SUPPLY("sdc_vdd", "msm_sdcc.3"), +}; +VREG_CONSUMERS(L7) = { + REGULATOR_SUPPLY("8921_l7", NULL), + REGULATOR_SUPPLY("sdc_vddp", "msm_sdcc.3"), +}; +VREG_CONSUMERS(L8) = { + REGULATOR_SUPPLY("8921_l8", NULL), + REGULATOR_SUPPLY("dsi_vdc", "mipi_dsi.1"), +}; +VREG_CONSUMERS(L9) = { + REGULATOR_SUPPLY("8921_l9", NULL), + REGULATOR_SUPPLY("vdd", "3-0024"), +}; +VREG_CONSUMERS(L10) = { + REGULATOR_SUPPLY("8921_l10", NULL), + REGULATOR_SUPPLY("iris_vddpa", "wcnss_wlan.0"), + +}; +VREG_CONSUMERS(L11) = { + REGULATOR_SUPPLY("8921_l11", NULL), + REGULATOR_SUPPLY("cam_vana", "msm_camera_imx074.0"), + REGULATOR_SUPPLY("cam_vana", "msm_camera_ov2720.0"), +}; +VREG_CONSUMERS(L12) = { + REGULATOR_SUPPLY("8921_l12", NULL), + REGULATOR_SUPPLY("cam_vdig", "msm_camera_imx074.0"), + REGULATOR_SUPPLY("cam_vdig", "msm_camera_ov2720.0"), +}; +VREG_CONSUMERS(L14) = { + REGULATOR_SUPPLY("8921_l14", NULL), +}; +VREG_CONSUMERS(L15) = { + REGULATOR_SUPPLY("8921_l15", NULL), +}; +VREG_CONSUMERS(L16) = { + REGULATOR_SUPPLY("8921_l16", NULL), + REGULATOR_SUPPLY("cam_vaf", "msm_camera_imx074.0"), + REGULATOR_SUPPLY("cam_vaf", "msm_camera_ov2720.0"), +}; +VREG_CONSUMERS(L17) = { + REGULATOR_SUPPLY("8921_l17", NULL), +}; +VREG_CONSUMERS(L18) = { + REGULATOR_SUPPLY("8921_l18", NULL), +}; +VREG_CONSUMERS(L21) = { + REGULATOR_SUPPLY("8921_l21", NULL), +}; +VREG_CONSUMERS(L22) = { + REGULATOR_SUPPLY("8921_l22", NULL), +}; +VREG_CONSUMERS(L23) = { + REGULATOR_SUPPLY("8921_l23", NULL), + REGULATOR_SUPPLY("dsi_vddio", "mipi_dsi.1"), + REGULATOR_SUPPLY("hdmi_avdd", "hdmi_msm.0"), +}; +VREG_CONSUMERS(L24) = { + REGULATOR_SUPPLY("8921_l24", NULL), + REGULATOR_SUPPLY("riva_vddmx", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(L25) = { + REGULATOR_SUPPLY("8921_l25", NULL), + REGULATOR_SUPPLY("VDDD_CDC_D", "tabla-slim"), + REGULATOR_SUPPLY("CDC_VDDA_A_1P2V", "tabla-slim"), +}; +VREG_CONSUMERS(L26) = { + REGULATOR_SUPPLY("8921_l26", NULL), + REGULATOR_SUPPLY("lpass_q6", NULL), +}; +VREG_CONSUMERS(L27) = { + REGULATOR_SUPPLY("8921_l27", NULL), + REGULATOR_SUPPLY("modem_sw_q6", NULL), +}; +VREG_CONSUMERS(L28) = { + REGULATOR_SUPPLY("8921_l28", NULL), + REGULATOR_SUPPLY("modem_fw_q6", NULL), +}; +VREG_CONSUMERS(L29) = { + REGULATOR_SUPPLY("8921_l29", NULL), +}; +VREG_CONSUMERS(S1) = { + REGULATOR_SUPPLY("8921_s1", NULL), +}; +VREG_CONSUMERS(S2) = { + REGULATOR_SUPPLY("8921_s2", NULL), + REGULATOR_SUPPLY("iris_vddrfa", "wcnss_wlan.0"), + +}; +VREG_CONSUMERS(S3) = { + REGULATOR_SUPPLY("8921_s3", NULL), + REGULATOR_SUPPLY("HSUSB_VDDCX", "msm_otg"), + REGULATOR_SUPPLY("riva_vddcx", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(S4) = { + REGULATOR_SUPPLY("8921_s4", NULL), + REGULATOR_SUPPLY("sdc_vccq", "msm_sdcc.1"), + REGULATOR_SUPPLY("riva_vddpx", "wcnss_wlan.0"), + REGULATOR_SUPPLY("hdmi_vcc", "hdmi_msm.0"), + REGULATOR_SUPPLY("VDDIO_CDC", "tabla-slim"), + REGULATOR_SUPPLY("CDC_VDD_CP", "tabla-slim"), + REGULATOR_SUPPLY("CDC_VDDA_TX", "tabla-slim"), + REGULATOR_SUPPLY("CDC_VDDA_RX", "tabla-slim"), +}; +VREG_CONSUMERS(S5) = { + REGULATOR_SUPPLY("8921_s5", NULL), + REGULATOR_SUPPLY("krait0", NULL), +}; +VREG_CONSUMERS(S6) = { + REGULATOR_SUPPLY("8921_s6", NULL), + REGULATOR_SUPPLY("krait1", NULL), +}; +VREG_CONSUMERS(S7) = { + REGULATOR_SUPPLY("8921_s7", NULL), +}; +VREG_CONSUMERS(S8) = { + REGULATOR_SUPPLY("8921_s8", NULL), +}; +VREG_CONSUMERS(LVS1) = { + REGULATOR_SUPPLY("8921_lvs1", NULL), + REGULATOR_SUPPLY("sdc_vdd", "msm_sdcc.4"), + REGULATOR_SUPPLY("iris_vddio", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(LVS2) = { + REGULATOR_SUPPLY("8921_lvs2", NULL), + REGULATOR_SUPPLY("iris_vdddig", "wcnss_wlan.0"), +}; +VREG_CONSUMERS(LVS3) = { + REGULATOR_SUPPLY("8921_lvs3", NULL), +}; +VREG_CONSUMERS(LVS4) = { + REGULATOR_SUPPLY("8921_lvs4", NULL), + REGULATOR_SUPPLY("vcc_i2c", "3-0024"), +}; +VREG_CONSUMERS(LVS5) = { + REGULATOR_SUPPLY("8921_lvs5", NULL), + REGULATOR_SUPPLY("cam_vio", "msm_camera_imx074.0"), + REGULATOR_SUPPLY("cam_vio", "msm_camera_ov2720.0"), +}; +VREG_CONSUMERS(LVS6) = { + REGULATOR_SUPPLY("8921_lvs6", NULL), + REGULATOR_SUPPLY("vdd_io", "spi0.0"), +}; +VREG_CONSUMERS(LVS7) = { + REGULATOR_SUPPLY("8921_lvs7", NULL), +}; +VREG_CONSUMERS(USB_OTG) = { + REGULATOR_SUPPLY("8921_usb_otg", NULL), + REGULATOR_SUPPLY("vbus_otg", "msm_otg"), +}; +VREG_CONSUMERS(HDMI_MVS) = { + REGULATOR_SUPPLY("8921_hdmi_mvs", NULL), + REGULATOR_SUPPLY("hdmi_mvs", "hdmi_msm.0"), +}; +VREG_CONSUMERS(NCP) = { + REGULATOR_SUPPLY("8921_ncp", NULL), +}; +VREG_CONSUMERS(EXT_5V) = { + REGULATOR_SUPPLY("ext_5v", NULL), +}; +VREG_CONSUMERS(EXT_L2) = { + REGULATOR_SUPPLY("ext_l2", NULL), + REGULATOR_SUPPLY("vdd_phy", "spi0.0"), +}; + +#define PM8921_VREG_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, \ + _pull_down, _always_on, _supply_regulator, \ + _system_uA) \ + { \ + .init_data = { \ + .constraints = { \ + .valid_modes_mask = _modes, \ + .valid_ops_mask = _ops, \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .input_uV = _max_uV, \ + .apply_uV = _apply_uV, \ + .always_on = _always_on, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id), \ + .consumer_supplies = vreg_consumers_##_id, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = PM8921_VREG_ID_##_id, \ + .pull_down_enable = _pull_down, \ + .system_uA = _system_uA, \ + } + +#define PM8921_VREG_INIT_LDO(_id, _always_on, _pull_down, _min_uV, _max_uV, \ + _supply_regulator, _system_uA) \ + PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL \ + | REGULATOR_MODE_IDLE, REGULATOR_CHANGE_VOLTAGE | \ + REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \ + REGULATOR_CHANGE_DRMS, 0, _pull_down, _always_on, \ + _supply_regulator, _system_uA) + +#define PM8921_VREG_INIT_NLDO1200(_id, _always_on, _pull_down, _min_uV, \ + _max_uV, _supply_regulator, _system_uA) \ + PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL \ + | REGULATOR_MODE_IDLE, REGULATOR_CHANGE_VOLTAGE | \ + REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \ + REGULATOR_CHANGE_DRMS, 0, _pull_down, _always_on, \ + _supply_regulator, _system_uA) + +#define PM8921_VREG_INIT_SMPS(_id, _always_on, _pull_down, _min_uV, _max_uV, \ + _supply_regulator, _system_uA) \ + PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL \ + | REGULATOR_MODE_IDLE, REGULATOR_CHANGE_VOLTAGE | \ + REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \ + REGULATOR_CHANGE_DRMS, 0, _pull_down, _always_on, \ + _supply_regulator, _system_uA) + +#define PM8921_VREG_INIT_FTSMPS(_id, _always_on, _pull_down, _min_uV, _max_uV, \ + _supply_regulator, _system_uA) \ + PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL, \ + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS \ + | REGULATOR_CHANGE_MODE, 0, _pull_down, _always_on, \ + _supply_regulator, _system_uA) + +#define PM8921_VREG_INIT_VS(_id, _always_on, _pull_down, _supply_regulator) \ + PM8921_VREG_INIT(_id, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, _pull_down, \ + _always_on, _supply_regulator, 0) + +#define PM8921_VREG_INIT_VS300(_id, _always_on, _pull_down, _supply_regulator) \ + PM8921_VREG_INIT(_id, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, _pull_down, \ + _always_on, _supply_regulator, 0) + +#define PM8921_VREG_INIT_NCP(_id, _always_on, _min_uV, _max_uV, \ + _supply_regulator) \ + PM8921_VREG_INIT(_id, _min_uV, _max_uV, 0, REGULATOR_CHANGE_VOLTAGE | \ + REGULATOR_CHANGE_STATUS, 0, 0, _always_on, _supply_regulator, 0) + +/* Pin control initialization */ +#define PM8921_PC_INIT(_id, _always_on, _pin_fn, _pin_ctrl, _supply_regulator) \ + { \ + .init_data = { \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + .always_on = _always_on, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id##_PC), \ + .consumer_supplies = vreg_consumers_##_id##_PC, \ + .supply_regulator = _supply_regulator, \ + }, \ + .id = PM8921_VREG_ID_##_id##_PC, \ + .pin_fn = PM8921_VREG_PIN_FN_##_pin_fn, \ + .pin_ctrl = _pin_ctrl, \ + } + +#define GPIO_VREG_INIT(_id, _reg_name, _gpio_label, _gpio) \ + [GPIO_VREG_ID_##_id] = { \ + .init_data = { \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + }, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id), \ + .consumer_supplies = vreg_consumers_##_id, \ + }, \ + .regulator_name = _reg_name, \ + .gpio_label = _gpio_label, \ + .gpio = _gpio, \ + } + +#define SAW_VREG_INIT(_id, _name, _min_uV, _max_uV) \ + { \ + .constraints = { \ + .name = _name, \ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + }, \ + .num_consumer_supplies = ARRAY_SIZE(vreg_consumers_##_id), \ + .consumer_supplies = vreg_consumers_##_id, \ + } + +/* GPIO regulator constraints */ +struct gpio_regulator_platform_data msm_gpio_regulator_pdata[] __devinitdata = { + GPIO_VREG_INIT(EXT_5V, "ext_5v", "ext_5v_en", PM8921_MPP_PM_TO_SYS(7)), + GPIO_VREG_INIT(EXT_L2, "ext_l2", "ext_l2_en", 91), +}; + +/* SAW regulator constraints */ +struct regulator_init_data msm_saw_regulator_pdata_s5 = + /* ID vreg_name min_uV max_uV */ + SAW_VREG_INIT(S5, "8921_s5", 1050000, 1150000); +struct regulator_init_data msm_saw_regulator_pdata_s6 = + SAW_VREG_INIT(S6, "8921_s6", 1050000, 1150000); + +/* PM8921 regulator constraints */ +struct pm8921_regulator_platform_data +msm_pm8921_regulator_pdata[] __devinitdata = { + /* ID always_on pd min_uV max_uV supply sys_uA */ + PM8921_VREG_INIT_SMPS(S1, 1, 1, 1225000, 1225000, NULL, 100000), + PM8921_VREG_INIT_SMPS(S2, 0, 1, 1300000, 1300000, NULL, 0), + PM8921_VREG_INIT_SMPS(S3, 1, 1, 1150000, 1150000, NULL, 100000), + PM8921_VREG_INIT_SMPS(S4, 1, 1, 1800000, 1800000, NULL, 100000), + PM8921_VREG_INIT_SMPS(S7, 0, 1, 1150000, 1150000, NULL, 100000), + PM8921_VREG_INIT_SMPS(S8, 0, 1, 2200000, 2200000, NULL, 100000), + + PM8921_VREG_INIT_LDO(L1, 1, 1, 1050000, 1050000, "8921_s4", 0), + PM8921_VREG_INIT_LDO(L2, 0, 1, 1200000, 1200000, "8921_s4", 0), + PM8921_VREG_INIT_LDO(L3, 0, 1, 3075000, 3075000, NULL, 0), + PM8921_VREG_INIT_LDO(L4, 1, 1, 1800000, 1800000, NULL, 0), + PM8921_VREG_INIT_LDO(L5, 0, 1, 2950000, 2950000, NULL, 0), + PM8921_VREG_INIT_LDO(L6, 0, 1, 2950000, 2950000, NULL, 0), + PM8921_VREG_INIT_LDO(L7, 1, 1, 1850000, 2950000, NULL, 0), + PM8921_VREG_INIT_LDO(L8, 0, 1, 2800000, 3000000, NULL, 0), + PM8921_VREG_INIT_LDO(L9, 0, 1, 2850000, 2850000, NULL, 0), + PM8921_VREG_INIT_LDO(L10, 0, 1, 2900000, 2900000, NULL, 0), + PM8921_VREG_INIT_LDO(L11, 0, 1, 2850000, 2850000, NULL, 0), + PM8921_VREG_INIT_LDO(L12, 0, 1, 1200000, 1200000, "8921_s4", 0), + PM8921_VREG_INIT_LDO(L14, 0, 1, 1800000, 1800000, NULL, 0), + PM8921_VREG_INIT_LDO(L15, 0, 1, 1800000, 2950000, NULL, 0), + PM8921_VREG_INIT_LDO(L16, 0, 1, 2800000, 2800000, NULL, 0), + PM8921_VREG_INIT_LDO(L17, 0, 1, 1800000, 2950000, NULL, 0), + PM8921_VREG_INIT_LDO(L18, 0, 1, 1300000, 1300000, "8921_s4", 0), + PM8921_VREG_INIT_LDO(L21, 0, 1, 1900000, 1900000, "8921_s8", 0), + PM8921_VREG_INIT_LDO(L22, 0, 1, 2750000, 2750000, NULL, 0), + PM8921_VREG_INIT_LDO(L23, 1, 1, 1800000, 1800000, "8921_s8", 0), + PM8921_VREG_INIT_NLDO1200(L24, 1, 1, 1150000, 1150000, "8921_s1", + 10000), + PM8921_VREG_INIT_NLDO1200(L25, 1, 1, 1225000, 1225000, "8921_s1", 0), + PM8921_VREG_INIT_NLDO1200(L26, 0, 1, 1050000, 1050000, "8921_s7", 0), + PM8921_VREG_INIT_NLDO1200(L27, 0, 1, 1050000, 1050000, "8921_s7", 0), + PM8921_VREG_INIT_NLDO1200(L28, 0, 1, 1050000, 1050000, "8921_s7", 0), + PM8921_VREG_INIT_LDO(L29, 0, 1, 2050000, 2100000, "8921_s8", 0), + + PM8921_VREG_INIT_VS(LVS1, 0, 1, "8921_s4"), + PM8921_VREG_INIT_VS300(LVS2, 0, 1, "8921_s1"), + PM8921_VREG_INIT_VS(LVS3, 0, 1, "8921_s4"), + PM8921_VREG_INIT_VS(LVS4, 0, 1, "8921_s4"), + PM8921_VREG_INIT_VS(LVS5, 0, 1, "8921_s4"), + PM8921_VREG_INIT_VS(LVS6, 0, 1, "8921_s4"), + PM8921_VREG_INIT_VS(LVS7, 0, 1, "8921_s4"), + + PM8921_VREG_INIT_VS300(USB_OTG, 0, 1, "ext_5v"), + PM8921_VREG_INIT_VS300(HDMI_MVS, 0, 1, "ext_5v"), + + PM8921_VREG_INIT_NCP(NCP, 0, 1800000, 1800000, "8921_l6"), +}; + +int msm_pm8921_regulator_pdata_len __devinitdata = + ARRAY_SIZE(msm_pm8921_regulator_pdata); diff --git a/arch/arm/mach-msm/board-msm8960.c b/arch/arm/mach-msm/board-msm8960.c index 35c7ceeb3f2..db2cdfd7ddc 100644 --- a/arch/arm/mach-msm/board-msm8960.c +++ b/arch/arm/mach-msm/board-msm8960.c @@ -9,83 +9,3351 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * */ #include #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_ANDROID_PMEM +#include +#endif +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#ifdef CONFIG_USB_MSM_OTG_72K +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_WCD9310_CODEC +#include +#include +#include +#endif + +#include "timer.h" +#include "gpiomux.h" +#include "devices.h" +#include "devices-msm8x60.h" +#include "gpiomux.h" +#include "spm.h" +#include "board-msm8960.h" +#include "pm.h" +#include "cpuidle.h" +#include "rpm_resources.h" +#include "mpm.h" + +static struct platform_device msm_fm_platform_init = { + .name = "iris_fm", + .id = -1, +}; + +struct pm8xxx_gpio_init { + unsigned gpio; + struct pm_gpio config; +}; + +struct pm8xxx_mpp_init { + unsigned mpp; + struct pm8xxx_mpp_config_data config; +}; + +#define PM8XXX_GPIO_INIT(_gpio, _dir, _buf, _val, _pull, _vin, _out_strength, \ + _func, _inv, _disable) \ +{ \ + .gpio = PM8921_GPIO_PM_TO_SYS(_gpio), \ + .config = { \ + .direction = _dir, \ + .output_buffer = _buf, \ + .output_value = _val, \ + .pull = _pull, \ + .vin_sel = _vin, \ + .out_strength = _out_strength, \ + .function = _func, \ + .inv_int_pol = _inv, \ + .disable_pin = _disable, \ + } \ +} + +#define PM8XXX_MPP_INIT(_mpp, _type, _level, _control) \ +{ \ + .mpp = PM8921_MPP_PM_TO_SYS(_mpp), \ + .config = { \ + .type = PM8XXX_MPP_TYPE_##_type, \ + .level = _level, \ + .control = PM8XXX_MPP_##_control, \ + } \ +} + +#define PM8XXX_GPIO_DISABLE(_gpio) \ + PM8XXX_GPIO_INIT(_gpio, PM_GPIO_DIR_IN, 0, 0, 0, PM_GPIO_VIN_S4, \ + 0, 0, 0, 1) + +#define PM8XXX_GPIO_OUTPUT(_gpio, _val) \ + PM8XXX_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, PM_GPIO_VIN_S4, \ + PM_GPIO_STRENGTH_HIGH, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +#define PM8XXX_GPIO_INPUT(_gpio, _pull) \ + PM8XXX_GPIO_INIT(_gpio, PM_GPIO_DIR_IN, PM_GPIO_OUT_BUF_CMOS, 0, \ + _pull, PM_GPIO_VIN_S4, \ + PM_GPIO_STRENGTH_NO, \ + PM_GPIO_FUNC_NORMAL, 0, 0) + +#define PM8XXX_GPIO_OUTPUT_FUNC(_gpio, _val, _func) \ + PM8XXX_GPIO_INIT(_gpio, PM_GPIO_DIR_OUT, PM_GPIO_OUT_BUF_CMOS, _val, \ + PM_GPIO_PULL_NO, PM_GPIO_VIN_S4, \ + PM_GPIO_STRENGTH_HIGH, \ + _func, 0, 0) + +/* Initial PM8921 GPIO configurations */ +static struct pm8xxx_gpio_init pm8921_gpios[] __initdata = { + PM8XXX_GPIO_DISABLE(6), /* Disable unused */ + PM8XXX_GPIO_DISABLE(7), /* Disable NFC */ + PM8XXX_GPIO_INPUT(16, PM_GPIO_PULL_UP_30), /* SD_CARD_WP */ + PM8XXX_GPIO_DISABLE(22), /* Disable NFC */ + PM8XXX_GPIO_OUTPUT_FUNC(24, 0, PM_GPIO_FUNC_1), /* Bl: Off, PWM mode */ + PM8XXX_GPIO_INPUT(26, PM_GPIO_PULL_UP_30), /* SD_CARD_DET_N */ + PM8XXX_GPIO_OUTPUT(43, 0), /* DISP_RESET_N */ +}; + +/* Initial PM8921 MPP configurations */ +static struct pm8xxx_mpp_init pm8921_mpps[] __initdata = { + /* External 5V regulator enable; shared by HDMI and USB_OTG switches. */ + PM8XXX_MPP_INIT(7, D_INPUT, PM8921_MPP_DIG_LEVEL_VPH, DIN_TO_INT), + PM8XXX_MPP_INIT(PM8921_AMUX_MPP_3, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH6, + DOUT_CTRL_LOW), + PM8XXX_MPP_INIT(PM8921_AMUX_MPP_8, A_INPUT, PM8XXX_MPP_AIN_AMUX_CH8, + DOUT_CTRL_LOW), +}; + +static void __init pm8921_gpio_mpp_init(void) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(pm8921_gpios); i++) { + rc = pm8xxx_gpio_config(pm8921_gpios[i].gpio, + &pm8921_gpios[i].config); + if (rc) { + pr_err("%s: pm8xxx_gpio_config: rc=%d\n", __func__, rc); + break; + } + } + + for (i = 0; i < ARRAY_SIZE(pm8921_mpps); i++) { + rc = pm8xxx_mpp_config(pm8921_mpps[i].mpp, + &pm8921_mpps[i].config); + if (rc) { + pr_err("%s: pm8xxx_mpp_config: rc=%d\n", __func__, rc); + break; + } + } +} + +#define FPGA_CS_GPIO 14 +#define KS8851_RST_GPIO 89 +#define KS8851_IRQ_GPIO 90 + +/* Macros assume PMIC GPIOs and MPPs start at 1 */ +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) +#define PM8921_MPP_BASE (PM8921_GPIO_BASE + PM8921_NR_GPIOS) +#define PM8921_MPP_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_MPP_BASE) +#define PM8921_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) +#define PM8921_MPP_IRQ_BASE (PM8921_IRQ_BASE + NR_GPIO_IRQS) + +static struct gpiomux_setting gsbi1 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi3 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi4 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi5 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi10 = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi12 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cdc_mclk = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gpio_eth_config = { + .pull = GPIOMUX_PULL_NONE, + .drv = GPIOMUX_DRV_8MA, + .func = GPIOMUX_FUNC_GPIO, +}; + +static struct gpiomux_setting slimbus = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_KEEPER, +}; + +struct msm_gpiomux_config msm8960_gpiomux_configs[NR_GPIO_IRQS] = { + { + .gpio = KS8851_IRQ_GPIO, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_eth_config, + } + }, + { + .gpio = KS8851_RST_GPIO, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_eth_config, + } + }, + { + .gpio = FPGA_CS_GPIO, + .settings = { + [GPIOMUX_SUSPENDED] = &gpio_eth_config, + } + }, +}; + +static struct msm_gpiomux_config msm8960_gsbi_configs[] __initdata = { + { + .gpio = 6, /* GSBI1 QUP SPI_DATA_MOSI */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi1, + }, + }, + { + .gpio = 7, /* GSBI1 QUP SPI_DATA_MISO */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi1, + }, + }, + { + .gpio = 8, /* GSBI1 QUP SPI_CS_N */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi1, + }, + }, + { + .gpio = 9, /* GSBI1 QUP SPI_CLK */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi1, + }, + }, + { + .gpio = 16, /* GSBI3 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3, + }, + }, + { + .gpio = 17, /* GSBI3 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi3, + }, + }, + { + .gpio = 20, /* GSBI4 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi4, + }, + }, + { + .gpio = 21, /* GSBI4 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi4, + }, + }, + { + .gpio = 22, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5, + }, + }, + { + .gpio = 23, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5, + }, + }, + { + .gpio = 24, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5, + }, + }, + { + .gpio = 25, /* GSBI5 UART2 */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi5, + }, + }, + { + .gpio = 44, /* GSBI12 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi12, + }, + }, + { + .gpio = 45, /* GSBI12 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi12, + }, + }, + { + .gpio = 73, /* GSBI10 I2C QUP SDA */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi10, + }, + }, + { + .gpio = 74, /* GSBI10 I2C QUP SCL */ + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi10, + }, + }, +}; + +static struct msm_gpiomux_config msm8960_slimbus_config[] __initdata = { + { + .gpio = 60, /* slimbus data */ + .settings = { + [GPIOMUX_SUSPENDED] = &slimbus, + }, + }, + { + .gpio = 61, /* slimbus clk */ + .settings = { + [GPIOMUX_SUSPENDED] = &slimbus, + }, + }, +}; + +static struct msm_gpiomux_config msm8960_audio_codec_configs[] __initdata = { + { + .gpio = 59, + .settings = { + [GPIOMUX_SUSPENDED] = &cdc_mclk, + }, + }, +}; +static struct gpiomux_setting wcnss_5wire_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting wcnss_5wire_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct msm_gpiomux_config wcnss_5wire_interface[] = { + { + .gpio = 84, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, + { + .gpio = 85, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, + { + .gpio = 86, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, + { + .gpio = 87, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, + { + .gpio = 88, + .settings = { + [GPIOMUX_ACTIVE] = &wcnss_5wire_active_cfg, + [GPIOMUX_SUSPENDED] = &wcnss_5wire_suspend_cfg, + }, + }, +}; + +static struct gpiomux_setting cam_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting cam_active_1_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cam_active_2_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cam_active_3_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct msm_gpiomux_config msm8960_cam_configs[] __initdata = { + { + .gpio = 2, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_2_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 3, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_1_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 4, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_1_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 5, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_1_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 18, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_3_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 19, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_3_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 20, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_3_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 21, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_3_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 76, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_2_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 107, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_2_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, +}; + +static struct gpiomux_setting cyts_resout_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting cyts_resout_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting cyts_sleep_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting cyts_sleep_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting cyts_int_act_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting cyts_int_sus_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + + +static struct msm_gpiomux_config msm8960_cyts_configs[] __initdata = { + { /* TS INTERRUPT */ + .gpio = 11, + .settings = { + [GPIOMUX_ACTIVE] = &cyts_int_act_cfg, + [GPIOMUX_SUSPENDED] = &cyts_int_sus_cfg, + }, + }, + { /* TS SLEEP */ + .gpio = 50, + .settings = { + [GPIOMUX_ACTIVE] = &cyts_sleep_act_cfg, + [GPIOMUX_SUSPENDED] = &cyts_sleep_sus_cfg, + }, + }, + { /* TS RESOUT */ + .gpio = 52, + .settings = { + [GPIOMUX_ACTIVE] = &cyts_resout_act_cfg, + [GPIOMUX_SUSPENDED] = &cyts_resout_sus_cfg, + }, + }, +}; + +#define MSM_PMEM_KERNEL_EBI1_SIZE 0x110C000 +#define MSM_PMEM_ADSP_SIZE 0x3800000 +#define MSM_PMEM_AUDIO_SIZE 0x279000 +#define MSM_PMEM_SIZE 0x1800000 /* 24 Mbytes */ + +#ifdef CONFIG_KERNEL_PMEM_EBI_REGION +static unsigned pmem_kernel_ebi1_size = MSM_PMEM_KERNEL_EBI1_SIZE; +static int __init pmem_kernel_ebi1_size_setup(char *p) +{ + pmem_kernel_ebi1_size = memparse(p, NULL); + return 0; +} +early_param("pmem_kernel_ebi1_size", pmem_kernel_ebi1_size_setup); +#endif + +#ifdef CONFIG_ANDROID_PMEM +static unsigned pmem_size = MSM_PMEM_SIZE; +static int __init pmem_size_setup(char *p) +{ + pmem_size = memparse(p, NULL); + return 0; +} +early_param("pmem_size", pmem_size_setup); + +static unsigned pmem_adsp_size = MSM_PMEM_ADSP_SIZE; + +static int __init pmem_adsp_size_setup(char *p) +{ + pmem_adsp_size = memparse(p, NULL); + return 0; +} +early_param("pmem_adsp_size", pmem_adsp_size_setup); + +static unsigned pmem_audio_size = MSM_PMEM_AUDIO_SIZE; + +static int __init pmem_audio_size_setup(char *p) +{ + pmem_audio_size = memparse(p, NULL); + return 0; +} +early_param("pmem_audio_size", pmem_audio_size_setup); +#endif + +#ifdef CONFIG_ANDROID_PMEM +static struct android_pmem_platform_data android_pmem_pdata = { + .name = "pmem", + .allocator_type = PMEM_ALLOCATORTYPE_ALLORNOTHING, + .cached = 1, + .memory_type = MEMTYPE_EBI1, +}; + +static struct platform_device android_pmem_device = { + .name = "android_pmem", + .id = 0, + .dev = {.platform_data = &android_pmem_pdata}, +}; + +static struct android_pmem_platform_data android_pmem_adsp_pdata = { + .name = "pmem_adsp", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, + .memory_type = MEMTYPE_EBI1, +}; +static struct platform_device android_pmem_adsp_device = { + .name = "android_pmem", + .id = 2, + .dev = { .platform_data = &android_pmem_adsp_pdata }, +}; + +static struct android_pmem_platform_data android_pmem_audio_pdata = { + .name = "pmem_audio", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, + .memory_type = MEMTYPE_EBI1, +}; + +static struct platform_device android_pmem_audio_device = { + .name = "android_pmem", + .id = 4, + .dev = { .platform_data = &android_pmem_audio_pdata }, +}; +#endif + +static struct memtype_reserve msm8960_reserve_table[] __initdata = { + [MEMTYPE_SMI] = { + }, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, +}; + +static void __init size_pmem_devices(void) +{ +#ifdef CONFIG_ANDROID_PMEM + android_pmem_adsp_pdata.size = pmem_adsp_size; + android_pmem_pdata.size = pmem_size; + android_pmem_audio_pdata.size = MSM_PMEM_AUDIO_SIZE; +#endif +} + +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + msm8960_reserve_table[p->memory_type].size += p->size; +} + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM + reserve_memory_for(&android_pmem_adsp_pdata); + reserve_memory_for(&android_pmem_pdata); + reserve_memory_for(&android_pmem_audio_pdata); + msm8960_reserve_table[MEMTYPE_EBI1].size += pmem_kernel_ebi1_size; +#endif +} + +static void __init msm8960_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); +} + +static int msm8960_paddr_to_memtype(unsigned int paddr) +{ + return MEMTYPE_EBI1; +} + +static struct reserve_info msm8960_reserve_info __initdata = { + .memtype_reserve_table = msm8960_reserve_table, + .calculate_reserve_sizes = msm8960_calculate_reserve_sizes, + .paddr_to_memtype = msm8960_paddr_to_memtype, +}; + +static void __init msm8960_reserve(void) +{ + reserve_info = &msm8960_reserve_info; + msm_reserve(); +} + +#ifdef CONFIG_MSM_CAMERA + +static int msm_cam_gpio_tbl[] = { + 5, /*CAMIF_MCLK*/ + 20, /*CAMIF_I2C_DATA*/ + 21, /*CAMIF_I2C_CLK*/ +}; + +#define VFE_CAMIF_TIMER1_GPIO 2 +#define VFE_CAMIF_TIMER2_GPIO 3 +#define VFE_CAMIF_TIMER3_GPIO_INT 4 +struct msm_camera_sensor_strobe_flash_data strobe_flash_xenon = { + .flash_trigger = VFE_CAMIF_TIMER2_GPIO, + .flash_charge = VFE_CAMIF_TIMER1_GPIO, + .flash_charge_done = VFE_CAMIF_TIMER3_GPIO_INT, + .flash_recharge_duration = 50000, + .irq = MSM_GPIO_TO_INT(VFE_CAMIF_TIMER3_GPIO_INT), +}; + +#ifdef CONFIG_IMX074 +static struct msm_camera_sensor_platform_info sensor_board_info = { + .mount_angle = 0 +}; +#endif + +static int config_gpio_table(int gpio_en) +{ + int rc = 0, i = 0; + if (gpio_en) { + for (i = 0; i < ARRAY_SIZE(msm_cam_gpio_tbl); i++) { + rc = gpio_request(msm_cam_gpio_tbl[i], "CAM_GPIO"); + if (rc < 0) { + pr_err("%s not able to get gpio\n", __func__); + for (i--; i >= 0; i--) + gpio_free(msm_cam_gpio_tbl[i]); + break; + } + } + } else { + for (i = 0; i < ARRAY_SIZE(msm_cam_gpio_tbl); i++) + gpio_free(msm_cam_gpio_tbl[i]); + } + return rc; +} + +static int config_camera_on_gpios(void) +{ + int rc = 0; + + rc = config_gpio_table(1); + if (rc < 0) { + printk(KERN_ERR "%s: CAMSENSOR gpio table request" + "failed\n", __func__); + return rc; + } + return rc; +} + +static void config_camera_off_gpios(void) +{ + config_gpio_table(0); +} + +struct msm_camera_device_platform_data msm_camera_csi0_device_data = { + .camera_gpio_on = config_camera_on_gpios, + .camera_gpio_off = config_camera_off_gpios, + .ioclk.mclk_clk_rate = 24000000, + .ioclk.vfe_clk_rate = 228570000, + .csid_core = 0, +}; + +struct msm_camera_device_platform_data msm_camera_csi1_device_data = { + .camera_gpio_on = config_camera_on_gpios, + .camera_gpio_off = config_camera_off_gpios, + .ioclk.mclk_clk_rate = 24000000, + .ioclk.vfe_clk_rate = 228570000, + .csid_core = 1, +}; + +#ifdef CONFIG_IMX074 +static struct msm_camera_sensor_flash_data flash_imx074 = { + .flash_type = MSM_CAMERA_FLASH_LED, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_imx074_data = { + .sensor_name = "imx074", + .sensor_reset = 107, + .sensor_pwd = 85, + .vcm_pwd = 0, + .vcm_enable = 1, + .pdata = &msm_camera_csi0_device_data, + .flash_data = &flash_imx074, + .strobe_flash_data = &strobe_flash_xenon, + .sensor_platform_info = &sensor_board_info, + .csi_if = 1 +}; + +struct platform_device msm8960_camera_sensor_imx074 = { + .name = "msm_camera_imx074", + .dev = { + .platform_data = &msm_camera_sensor_imx074_data, + }, +}; +#endif +#ifdef CONFIG_OV2720 +static struct msm_camera_sensor_flash_data flash_ov2720 = { + .flash_type = MSM_CAMERA_FLASH_LED, +}; + +static struct msm_camera_sensor_info msm_camera_sensor_ov2720_data = { + .sensor_name = "ov2720", + .sensor_reset = 76, + .sensor_pwd = 85, + .vcm_pwd = 0, + .vcm_enable = 1, + .pdata = &msm_camera_csi1_device_data, + .flash_data = &flash_ov2720, + .csi_if = 1 +}; + +struct platform_device msm8960_camera_sensor_ov2720 = { + .name = "msm_camera_ov2720", + .dev = { + .platform_data = &msm_camera_sensor_ov2720_data, + }, +}; +#endif +static void __init msm8960_init_cam(void) +{ + int i; + struct platform_device *cam_dev[] = { + &msm8960_camera_sensor_imx074, + &msm8960_camera_sensor_ov2720, + }; + + for (i = 0; i < ARRAY_SIZE(cam_dev); i++) { + struct msm_camera_sensor_info *s_info; + s_info = cam_dev[i]->dev.platform_data; + msm_get_cam_resources(s_info); + platform_device_register(cam_dev[i]); + } +} +#endif + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +/* prim = 608 x 1024 x 4(bpp) x 3(pages) */ +#define MSM_FB_PRIM_BUF_SIZE 0x720000 +#else +/* prim = 608 x 1024 x 4(bpp) x 2(pages) */ +#define MSM_FB_PRIM_BUF_SIZE 0x4C0000 +#endif + +#ifdef CONFIG_FB_MSM_MIPI_DSI +/* 960 x 540 x 3 x 2 */ +#define MIPI_DSI_WRITEBACK_SIZE 0x300000 +#else +#define MIPI_DSI_WRITEBACK_SIZE 0 +#endif + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +/* hdmi = 1920 x 1088 x 2(bpp) x 1(page) */ +#define MSM_FB_EXT_BUF_SIZE 0x3FC000 +#elif defined(CONFIG_FB_MSM_TVOUT) +/* tvout = 720 x 576 x 2(bpp) x 2(pages) */ +#define MSM_FB_EXT_BUF_SIZE 0x195000 +#else /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ +#define MSM_FB_EXT_BUF_SIZE 0 +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ + +#define MSM_FB_SIZE roundup(MSM_FB_PRIM_BUF_SIZE + MSM_FB_EXT_BUF_SIZE +\ + MIPI_DSI_WRITEBACK_SIZE, 4096) + +#define MDP_VSYNC_GPIO 0 + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; + +static struct platform_device msm_fb_device = { + .name = "msm_fb", + .id = 0, + .num_resources = ARRAY_SIZE(msm_fb_resources), + .resource = msm_fb_resources, +}; + +static bool dsi_power_on; + +static int mipi_dsi_panel_power(int on) +{ + static struct regulator *reg_l8, *reg_l23, *reg_l2; + static int gpio24, gpio43; + int rc; + + struct pm_gpio gpio43_param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 0, + .pull = PM_GPIO_PULL_NO, + .vin_sel = 2, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_PAIRED, + .inv_int_pol = 0, + .disable_pin = 0, + }; + + struct pm_gpio gpio24_param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = 2, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + .disable_pin = 0, + }; + + pr_info("%s: state : %d\n", __func__, on); + + if (!dsi_power_on) { + + reg_l8 = regulator_get(&msm_mipi_dsi1_device.dev, + "dsi_vdc"); + if (IS_ERR(reg_l8)) { + pr_err("could not get 8921_l8, rc = %ld\n", + PTR_ERR(reg_l8)); + return -ENODEV; + } + + reg_l23 = regulator_get(&msm_mipi_dsi1_device.dev, + "dsi_vddio"); + if (IS_ERR(reg_l23)) { + pr_err("could not get 8921_l23, rc = %ld\n", + PTR_ERR(reg_l23)); + return -ENODEV; + } + + reg_l2 = regulator_get(&msm_mipi_dsi1_device.dev, + "dsi_vdda"); + if (IS_ERR(reg_l2)) { + pr_err("could not get 8921_l2, rc = %ld\n", + PTR_ERR(reg_l2)); + return -ENODEV; + } + + rc = regulator_set_voltage(reg_l8, 2800000, 3000000); + if (rc) { + pr_err("set_voltage l8 failed, rc=%d\n", rc); + return -EINVAL; + } + rc = regulator_set_voltage(reg_l23, 1800000, 1800000); + if (rc) { + pr_err("set_voltage l23 failed, rc=%d\n", rc); + return -EINVAL; + } + rc = regulator_set_voltage(reg_l2, 1200000, 1200000); + if (rc) { + pr_err("set_voltage l2 failed, rc=%d\n", rc); + return -EINVAL; + } + + gpio43 = PM8921_GPIO_PM_TO_SYS(43); + rc = gpio_request(gpio43, "disp_rst_n"); + if (rc) { + pr_err("request gpio 43 failed, rc=%d\n", rc); + return -ENODEV; + } + + gpio24 = PM8921_GPIO_PM_TO_SYS(24); + rc = gpio_request(gpio24, "disp_backlight"); + if (rc) { + pr_err("request gpio 24 failed, rc=%d\n", rc); + return -EINVAL; + } + dsi_power_on = true; + } + + if (on) { + rc = regulator_set_optimum_mode(reg_l8, 100000); + if (rc < 0) { + pr_err("set_optimum_mode l8 failed, rc=%d\n", rc); + return -EINVAL; + } + rc = regulator_set_optimum_mode(reg_l23, 100000); + if (rc < 0) { + pr_err("set_optimum_mode l23 failed, rc=%d\n", rc); + return -EINVAL; + } + rc = regulator_set_optimum_mode(reg_l2, 100000); + if (rc < 0) { + pr_err("set_optimum_mode l2 failed, rc=%d\n", rc); + return -EINVAL; + } + rc = regulator_enable(reg_l8); + if (rc) { + pr_err("enable l8 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_enable(reg_l23); + if (rc) { + pr_err("enable l8 failed, rc=%d\n", rc); + return -ENODEV; + } + rc = regulator_enable(reg_l2); + if (rc) { + pr_err("enable l2 failed, rc=%d\n", rc); + return -ENODEV; + } + + gpio43_param.pull = PM_GPIO_PULL_NO; + rc = pm8xxx_gpio_config(gpio43, &gpio43_param); + if (rc) { + pr_err("gpio_config 43 failed (1), rc=%d\n", rc); + return -EINVAL; + } + gpio43_param.pull = PM_GPIO_PULL_UP_30; + rc = pm8xxx_gpio_config(gpio43, &gpio43_param); + if (rc) { + pr_err("gpio_config 43 failed (2), rc=%d\n", rc); + return -EINVAL; + } + gpio43_param.pull = PM_GPIO_PULL_NO; + rc = pm8xxx_gpio_config(gpio43, &gpio43_param); + if (rc) { + pr_err("gpio_config 43 failed (3), rc=%d\n", rc); + return -EINVAL; + } + gpio43_param.pull = PM_GPIO_PULL_UP_30; + rc = pm8xxx_gpio_config(gpio43, &gpio43_param); + if (rc) { + pr_err("gpio_config 43 failed (4), rc=%d\n", rc); + return -EINVAL; + } + + rc = pm8xxx_gpio_config(gpio24, &gpio24_param); + if (rc) { + pr_err("gpio_config 24 failed, rc=%d\n", rc); + return -EINVAL; + } + + gpio_set_value_cansleep(gpio43, 1); + } else { + rc = regulator_set_optimum_mode(reg_l8, 100); + if (rc < 0) { + pr_err("set_optimum_mode l8 failed, rc=%d\n", rc); + return -EINVAL; + } + rc = regulator_set_optimum_mode(reg_l23, 100); + if (rc < 0) { + pr_err("set_optimum_mode l23 failed, rc=%d\n", rc); + return -EINVAL; + } + rc = regulator_set_optimum_mode(reg_l2, 100); + if (rc < 0) { + pr_err("set_optimum_mode l2 failed, rc=%d\n", rc); + return -EINVAL; + } + gpio_set_value_cansleep(gpio43, 0); + } + return 0; +} + +static struct mipi_dsi_platform_data mipi_dsi_pdata = { + .vsync_gpio = MDP_VSYNC_GPIO, + .dsi_power_save = mipi_dsi_panel_power, +}; + +#ifdef CONFIG_MSM_BUS_SCALING + +static struct msm_bus_vectors mdp_init_vectors[] = { + /* For now, 0th array entry is reserved. + * Please leave 0 as is and don't use it + */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors mdp_vga_vectors[] = { + /* VGA and less video */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 175110000, + .ib = 218887500, + }, + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 175110000, + .ib = 218887500, + }, +}; + +static struct msm_bus_vectors mdp_720p_vectors[] = { + /* 720p and less video */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 230400000, + .ib = 288000000, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 230400000, + .ib = 288000000, + }, +}; + +static struct msm_bus_vectors mdp_1080p_vectors[] = { + /* 1080p and less video */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 334080000, + .ib = 417600000, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 334080000, + .ib = 417600000, + }, +}; + +static struct msm_bus_vectors mdp_rgb_vectors[] = { + /* RGB playing on VG or RGB pipe, might be on EBI */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 334080000, + .ib = 417600000, + }, + /* FB on EBI, request for EBI too*/ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 334080000, + .ib = 417600000, + }, +}; + +static struct msm_bus_paths mdp_bus_scale_usecases[] = { + { + ARRAY_SIZE(mdp_init_vectors), + mdp_init_vectors, + }, + { + ARRAY_SIZE(mdp_vga_vectors), + mdp_vga_vectors, + }, + { + ARRAY_SIZE(mdp_720p_vectors), + mdp_720p_vectors, + }, + { + ARRAY_SIZE(mdp_1080p_vectors), + mdp_1080p_vectors, + }, + { + ARRAY_SIZE(mdp_rgb_vectors), + mdp_rgb_vectors, + }, +}; + +static struct msm_bus_scale_pdata mdp_bus_scale_pdata = { + mdp_bus_scale_usecases, + ARRAY_SIZE(mdp_bus_scale_usecases), + .name = "mdp", +}; + +#endif + +int mdp_core_clk_rate_table[] = { + 85330000, + 85330000, + 128000000, + 200000000, + 200000000, +}; + +static struct msm_panel_common_pdata mdp_pdata = { + .gpio = MDP_VSYNC_GPIO, + .mdp_core_clk_rate = 85330000, + .mdp_core_clk_table = mdp_core_clk_rate_table, + .num_mdp_clk = ARRAY_SIZE(mdp_core_clk_rate_table), +#ifdef CONFIG_MSM_BUS_SCALING + .mdp_bus_scale_table = &mdp_bus_scale_pdata, +#endif + .mdp_rev = MDP_REV_42, +}; + +static struct platform_device mipi_dsi_renesas_panel_device = { + .name = "mipi_renesas", + .id = 0, +}; + +static struct platform_device mipi_dsi_simulator_panel_device = { + .name = "mipi_simulator", + .id = 0, +}; + +static struct platform_device mipi_dsi_toshiba_panel_device = { + .name = "mipi_toshiba", + .id = 0, +}; + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +static struct resource hdmi_msm_resources[] = { + { + .name = "hdmi_msm_qfprom_addr", + .start = 0x00700000, + .end = 0x007060FF, + .flags = IORESOURCE_MEM, + }, + { + .name = "hdmi_msm_hdmi_addr", + .start = 0x04A00000, + .end = 0x04A00FFF, + .flags = IORESOURCE_MEM, + }, + { + .name = "hdmi_msm_irq", + .start = HDMI_IRQ, + .end = HDMI_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static int hdmi_enable_5v(int on); +static int hdmi_core_power(int on, int show); +static int hdmi_cec_power(int on); + +static struct msm_hdmi_platform_data hdmi_msm_data = { + .irq = HDMI_IRQ, + .enable_5v = hdmi_enable_5v, + .core_power = hdmi_core_power, + .cec_power = hdmi_cec_power, +}; + +static struct platform_device hdmi_msm_device = { + .name = "hdmi_msm", + .id = 0, + .num_resources = ARRAY_SIZE(hdmi_msm_resources), + .resource = hdmi_msm_resources, + .dev.platform_data = &hdmi_msm_data, +}; +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ + +static void __init msm_fb_add_devices(void) +{ + if (machine_is_msm8x60_rumi3()) { + msm_fb_register_device("mdp", NULL); + mipi_dsi_pdata.target_type = 1; + } else + msm_fb_register_device("mdp", &mdp_pdata); + msm_fb_register_device("mipi_dsi", &mipi_dsi_pdata); +} + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +static struct gpiomux_setting hdmi_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting hdmi_active_1_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting hdmi_active_2_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct msm_gpiomux_config msm8960_hdmi_configs[] __initdata = { + { + .gpio = 99, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_1_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 100, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_1_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 101, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_1_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 102, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_2_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, +}; + +static int hdmi_enable_5v(int on) +{ + /* TBD: PM8921 regulator instead of 8901 */ + static struct regulator *reg_8921_hdmi_mvs; /* HDMI_5V */ + static int prev_on; + int rc; + + if (on == prev_on) + return 0; + + if (!reg_8921_hdmi_mvs) + reg_8921_hdmi_mvs = regulator_get(&hdmi_msm_device.dev, + "hdmi_mvs"); + + if (on) { + rc = regulator_enable(reg_8921_hdmi_mvs); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "8921_hdmi_mvs", rc); + return rc; + } + pr_debug("%s(on): success\n", __func__); + } else { + rc = regulator_disable(reg_8921_hdmi_mvs); + if (rc) + pr_warning("'%s' regulator disable failed, rc=%d\n", + "8921_hdmi_mvs", rc); + pr_debug("%s(off): success\n", __func__); + } + + prev_on = on; + + return 0; +} + +static int hdmi_core_power(int on, int show) +{ + static struct regulator *reg_8921_l23, *reg_8921_s4; + static int prev_on; + int rc; + + if (on == prev_on) + return 0; + + /* TBD: PM8921 regulator instead of 8901 */ + if (!reg_8921_l23) + reg_8921_l23 = regulator_get(&hdmi_msm_device.dev, "hdmi_avdd"); + + if (!reg_8921_s4) + reg_8921_s4 = regulator_get(&hdmi_msm_device.dev, "hdmi_vcc"); + + if (on) { + rc = regulator_set_optimum_mode(reg_8921_l23, 100000); + if (rc < 0) { + pr_err("set_optimum_mode l23 failed, rc=%d\n", rc); + return -EINVAL; + } + + rc = regulator_set_voltage(reg_8921_l23, 1800000, 1800000); + if (!rc) + rc = regulator_enable(reg_8921_l23); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "hdmi_avdd", rc); + return rc; + } + rc = regulator_set_voltage(reg_8921_s4, 1800000, 1800000); + if (!rc) + rc = regulator_enable(reg_8921_s4); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "hdmi_vcc", rc); + return rc; + } + + rc = gpio_request(100, "HDMI_DDC_CLK"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_DDC_CLK", 100, rc); + goto error1; + } + rc = gpio_request(101, "HDMI_DDC_DATA"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_DDC_DATA", 101, rc); + goto error2; + } + rc = gpio_request(102, "HDMI_HPD"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_HPD", 102, rc); + goto error3; + } + pr_debug("%s(on): success\n", __func__); + } else { + gpio_free(100); + gpio_free(101); + gpio_free(102); + + rc = regulator_set_optimum_mode(reg_8921_l23, 100); + if (rc < 0) { + pr_err("set_optimum_mode l23 failed, rc=%d\n", rc); + return -EINVAL; + } + + pr_debug("%s(off): success\n", __func__); + } + + prev_on = on; + + return 0; + +error3: + gpio_free(101); +error2: + gpio_free(100); +error1: + regulator_disable(reg_8921_l23); + return rc; +} + +static int hdmi_cec_power(int on) +{ + static int prev_on; + int rc; + + if (on == prev_on) + return 0; + + if (on) { + rc = gpio_request(99, "HDMI_CEC_VAR"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_CEC_VAR", 99, rc); + goto error; + } + pr_debug("%s(on): success\n", __func__); + } else { + gpio_free(99); + pr_debug("%s(off): success\n", __func__); + } + + prev_on = on; + + return 0; +error: + return rc; +} +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ + +static void __init msm8960_allocate_memory_regions(void) +{ + void *addr; + unsigned long size; + + size = MSM_FB_SIZE; + addr = alloc_bootmem_align(size, 0x1000); + msm_fb_resources[0].start = __pa(addr); + msm_fb_resources[0].end = msm_fb_resources[0].start + size - 1; + pr_info("allocating %lu bytes at %p (%lx physical) for fb\n", + size, addr, __pa(addr)); + +} +#ifdef CONFIG_WCD9310_CODEC + +#define TABLA_INTERRUPT_BASE (NR_MSM_IRQS + NR_GPIO_IRQS + NR_PM8921_IRQS) + +static struct tabla_pdata tabla_platform_data = { + .slimbus_slave_device = { + .name = "tabla-slave", + .e_addr = {0, 0, 0x10, 0, 0x17, 2}, + }, + .irq = MSM_GPIO_TO_INT(62), + .irq_base = TABLA_INTERRUPT_BASE, + .num_irqs = NR_TABLA_IRQS, + .reset_gpio = PM8921_GPIO_PM_TO_SYS(34), +}; + +static struct slim_device msm_slim_tabla = { + .name = "tabla-slim", + .e_addr = {0, 1, 0x10, 0, 0x17, 2}, + .dev = { + .platform_data = &tabla_platform_data, + }, +}; +#endif + +static struct slim_boardinfo msm_slim_devices[] = { +#ifdef CONFIG_WCD9310_CODEC + { + .bus_num = 1, + .slim_slave = &msm_slim_tabla, + }, +#endif + /* add more slimbus slaves as needed */ +}; + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + +#define QCE_SIZE 0x10000 +#define QCE_0_BASE 0x18500000 + +#define QCE_HW_KEY_SUPPORT 0 +#define QCE_SHA_HMAC_SUPPORT 1 +#define QCE_SHARE_CE_RESOURCE 1 +#define QCE_CE_SHARED 0 + +static struct resource qcrypto_resources[] = { + [0] = { + .start = QCE_0_BASE, + .end = QCE_0_BASE + QCE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "crypto_channels", + .start = DMOV_CE_IN_CHAN, + .end = DMOV_CE_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV_CE_IN_CRCI, + .end = DMOV_CE_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV_CE_OUT_CRCI, + .end = DMOV_CE_OUT_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource qcedev_resources[] = { + [0] = { + .start = QCE_0_BASE, + .end = QCE_0_BASE + QCE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "crypto_channels", + .start = DMOV_CE_IN_CHAN, + .end = DMOV_CE_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV_CE_IN_CRCI, + .end = DMOV_CE_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV_CE_OUT_CRCI, + .end = DMOV_CE_OUT_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) + +static struct msm_ce_hw_support qcrypto_ce_hw_suppport = { + .ce_shared = QCE_CE_SHARED, + .shared_ce_resource = QCE_SHARE_CE_RESOURCE, + .hw_key_support = QCE_HW_KEY_SUPPORT, + .sha_hmac = QCE_SHA_HMAC_SUPPORT, +}; + +static struct platform_device qcrypto_device = { + .name = "qcrypto", + .id = 0, + .num_resources = ARRAY_SIZE(qcrypto_resources), + .resource = qcrypto_resources, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &qcrypto_ce_hw_suppport, + }, +}; +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + +static struct msm_ce_hw_support qcedev_ce_hw_suppport = { + .ce_shared = QCE_CE_SHARED, + .shared_ce_resource = QCE_SHARE_CE_RESOURCE, + .hw_key_support = QCE_HW_KEY_SUPPORT, + .sha_hmac = QCE_SHA_HMAC_SUPPORT, +}; + +static struct platform_device qcedev_device = { + .name = "qce", + .id = 0, + .num_resources = ARRAY_SIZE(qcedev_resources), + .resource = qcedev_resources, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &qcedev_ce_hw_suppport, + }, +}; +#endif + + +static int __init gpiomux_init(void) +{ + int rc; + + rc = msm_gpiomux_init(NR_GPIO_IRQS); + if (rc) { + pr_err(KERN_ERR "msm_gpiomux_init failed %d\n", rc); + return rc; + } + + msm_gpiomux_install(msm8960_cam_configs, + ARRAY_SIZE(msm8960_cam_configs)); + + msm_gpiomux_install(msm8960_gpiomux_configs, + ARRAY_SIZE(msm8960_gsbi_configs)); + + msm_gpiomux_install(msm8960_gsbi_configs, + ARRAY_SIZE(msm8960_gsbi_configs)); + + msm_gpiomux_install(msm8960_cyts_configs, + ARRAY_SIZE(msm8960_cyts_configs)); + + msm_gpiomux_install(msm8960_slimbus_config, + ARRAY_SIZE(msm8960_slimbus_config)); + + msm_gpiomux_install(msm8960_audio_codec_configs, + ARRAY_SIZE(msm8960_audio_codec_configs)); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL + msm_gpiomux_install(msm8960_hdmi_configs, + ARRAY_SIZE(msm8960_hdmi_configs)); +#endif + + msm_gpiomux_install(wcnss_5wire_interface, + ARRAY_SIZE(wcnss_5wire_interface)); + + return 0; +} + +static struct msm_acpu_clock_platform_data msm8960_acpu_clock_data = { + .acpu_switch_time_us = 0, + .vdd_switch_time_us = 0, +}; + +#define MSM_SHARED_RAM_PHYS 0x80000000 + +static struct pm8921_adc_amux pm8921_adc_channels_data[] = { + {"vcoin", CHANNEL_VCOIN, CHAN_PATH_SCALING2, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"vbat", CHANNEL_VBAT, CHAN_PATH_SCALING2, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"dcin", CHANNEL_DCIN, CHAN_PATH_SCALING4, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"ichg", CHANNEL_ICHG, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"vph_pwr", CHANNEL_VPH_PWR, CHAN_PATH_SCALING2, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"ibat", CHANNEL_IBAT, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"m4", CHANNEL_MPP_1, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"m5", CHANNEL_MPP_2, CHAN_PATH_SCALING2, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"batt_therm", CHANNEL_BATT_THERM, CHAN_PATH_SCALING1, AMUX_RSV2, + ADC_DECIMATION_TYPE2, ADC_SCALE_BATT_THERM}, + {"batt_id", CHANNEL_BATT_ID, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"usbin", CHANNEL_USBIN, CHAN_PATH_SCALING3, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"pmic_therm", CHANNEL_DIE_TEMP, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_PMIC_THERM}, + {"625mv", CHANNEL_625MV, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"125v", CHANNEL_125V, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, + {"chg_temp", CHANNEL_CHG_TEMP, CHAN_PATH_SCALING1, AMUX_RSV1, + ADC_DECIMATION_TYPE2, ADC_SCALE_DEFAULT}, +}; + +static struct pm8921_adc_properties pm8921_adc_data = { + .adc_vdd_reference = 1800, /* milli-voltage for this adc */ + .bitresolution = 15, + .bipolar = 0, +}; + +static struct pm8921_adc_platform_data pm8921_adc_pdata = { + .adc_channel = pm8921_adc_channels_data, + .adc_num_channel = ARRAY_SIZE(pm8921_adc_channels_data), + .adc_prop = &pm8921_adc_data, +}; + +static void __init msm8960_map_io(void) +{ + msm_shared_ram_phys = MSM_SHARED_RAM_PHYS; + msm_map_msm8960_io(); +} + +static void __init msm8960_init_irq(void) +{ + unsigned int i; + + msm_mpm_irq_extn_init(); + gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, + (void *)MSM_QGIC_CPU_BASE); + + /* Edge trigger PPIs except AVS_SVICINT and AVS_SVICINTSWDONE */ + writel_relaxed(0xFFFFD7FF, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); + + writel_relaxed(0x0000FFFF, MSM_QGIC_DIST_BASE + GIC_DIST_ENABLE_SET); + mb(); + + /* FIXME: Not installing AVS_SVICINT and AVS_SVICINTSWDONE yet + * as they are configured as level, which does not play nice with + * handle_percpu_irq. + */ + for (i = GIC_PPI_START; i < GIC_SPI_START; i++) { + if (i != AVS_SVICINT && i != AVS_SVICINTSWDONE) + irq_set_handler(i, handle_percpu_irq); + } +} + +/* MSM8960 have 5 SDCC controllers */ +enum sdcc_controllers { + SDCC1, + SDCC2, + SDCC3, + SDCC4, + SDCC5, + MAX_SDCC_CONTROLLER +}; + +/* All SDCC controllers requires VDD/VCC voltage */ +static struct msm_mmc_reg_data mmc_vdd_reg_data[MAX_SDCC_CONTROLLER] = { + /* SDCC1 : eMMC card connected */ + [SDCC1] = { + .name = "sdc_vdd", + .set_voltage_sup = 1, + .level = 2950000, + .always_on = 1, + .lpm_sup = 1, + .lpm_uA = 9000, + .hpm_uA = 200000, /* 200mA */ + }, + /* SDCC3 : External card slot connected */ + [SDCC3] = { + .name = "sdc_vdd", + .set_voltage_sup = 1, + .level = 2950000, + .hpm_uA = 600000, /* 600mA */ + } +}; + +/* Only slots having eMMC card will require VCCQ voltage */ +static struct msm_mmc_reg_data mmc_vccq_reg_data[1] = { + /* SDCC1 : eMMC card connected */ + [SDCC1] = { + .name = "sdc_vccq", + .set_voltage_sup = 1, + .always_on = 1, + .level = 1800000, + .hpm_uA = 200000, /* 200mA */ + } +}; + +/* All SDCC controllers may require voting for VDD PAD voltage */ +static struct msm_mmc_reg_data mmc_vddp_reg_data[MAX_SDCC_CONTROLLER] = { + /* SDCC3 : External card slot connected */ + [SDCC3] = { + .name = "sdc_vddp", + .set_voltage_sup = 1, + .level = 2950000, + .always_on = 1, + .lpm_sup = 1, + /* Max. Active current required is 16 mA */ + .hpm_uA = 16000, + /* + * Sleep current required is ~300 uA. But min. vote can be + * in terms of mA (min. 1 mA). So let's vote for 2 mA + * during sleep. + */ + .lpm_uA = 2000, + } +}; + +static struct msm_mmc_slot_reg_data mmc_slot_vreg_data[MAX_SDCC_CONTROLLER] = { + /* SDCC1 : eMMC card connected */ + [SDCC1] = { + .vdd_data = &mmc_vdd_reg_data[SDCC1], + .vccq_data = &mmc_vccq_reg_data[SDCC1], + }, + /* SDCC3 : External card slot connected */ + [SDCC3] = { + .vdd_data = &mmc_vdd_reg_data[SDCC3], + .vddp_data = &mmc_vddp_reg_data[SDCC3], + } +}; + +/* SDC1 pad data */ +static struct msm_mmc_pad_drv sdc1_pad_drv_on_cfg[] = { + {TLMM_HDRV_SDC1_CLK, GPIO_CFG_16MA}, + {TLMM_HDRV_SDC1_CMD, GPIO_CFG_10MA}, + {TLMM_HDRV_SDC1_DATA, GPIO_CFG_10MA} +}; + +static struct msm_mmc_pad_drv sdc1_pad_drv_off_cfg[] = { + {TLMM_HDRV_SDC1_CLK, GPIO_CFG_2MA}, + {TLMM_HDRV_SDC1_CMD, GPIO_CFG_2MA}, + {TLMM_HDRV_SDC1_DATA, GPIO_CFG_2MA} +}; + +static struct msm_mmc_pad_pull sdc1_pad_pull_on_cfg[] = { + {TLMM_PULL_SDC1_CMD, GPIO_CFG_PULL_UP}, + {TLMM_PULL_SDC1_DATA, GPIO_CFG_PULL_UP} +}; + +static struct msm_mmc_pad_pull sdc1_pad_pull_off_cfg[] = { + {TLMM_PULL_SDC1_CMD, GPIO_CFG_PULL_DOWN}, + {TLMM_PULL_SDC1_DATA, GPIO_CFG_PULL_DOWN} +}; + +/* SDC3 pad data */ +static struct msm_mmc_pad_drv sdc3_pad_drv_on_cfg[] = { + {TLMM_HDRV_SDC3_CLK, GPIO_CFG_8MA}, + {TLMM_HDRV_SDC3_CMD, GPIO_CFG_8MA}, + {TLMM_HDRV_SDC3_DATA, GPIO_CFG_8MA} +}; + +static struct msm_mmc_pad_drv sdc3_pad_drv_off_cfg[] = { + {TLMM_HDRV_SDC3_CLK, GPIO_CFG_2MA}, + {TLMM_HDRV_SDC3_CMD, GPIO_CFG_2MA}, + {TLMM_HDRV_SDC3_DATA, GPIO_CFG_2MA} +}; + +static struct msm_mmc_pad_pull sdc3_pad_pull_on_cfg[] = { + {TLMM_PULL_SDC3_CMD, GPIO_CFG_PULL_UP}, + {TLMM_PULL_SDC3_DATA, GPIO_CFG_PULL_UP} +}; + +static struct msm_mmc_pad_pull sdc3_pad_pull_off_cfg[] = { + {TLMM_PULL_SDC3_CMD, GPIO_CFG_PULL_DOWN}, + {TLMM_PULL_SDC3_DATA, GPIO_CFG_PULL_DOWN} +}; + +struct msm_mmc_pad_pull_data mmc_pad_pull_data[MAX_SDCC_CONTROLLER] = { + [SDCC1] = { + .on = sdc1_pad_pull_on_cfg, + .off = sdc1_pad_pull_off_cfg, + .size = ARRAY_SIZE(sdc1_pad_pull_on_cfg) + }, + [SDCC3] = { + .on = sdc3_pad_pull_on_cfg, + .off = sdc3_pad_pull_off_cfg, + .size = ARRAY_SIZE(sdc3_pad_pull_on_cfg) + }, +}; + +struct msm_mmc_pad_drv_data mmc_pad_drv_data[MAX_SDCC_CONTROLLER] = { + [SDCC1] = { + .on = sdc1_pad_drv_on_cfg, + .off = sdc1_pad_drv_off_cfg, + .size = ARRAY_SIZE(sdc1_pad_drv_on_cfg) + }, + [SDCC3] = { + .on = sdc3_pad_drv_on_cfg, + .off = sdc3_pad_drv_off_cfg, + .size = ARRAY_SIZE(sdc3_pad_drv_on_cfg) + }, +}; + +struct msm_mmc_pad_data mmc_pad_data[MAX_SDCC_CONTROLLER] = { + [SDCC1] = { + .pull = &mmc_pad_pull_data[SDCC1], + .drv = &mmc_pad_drv_data[SDCC1] + }, + [SDCC3] = { + .pull = &mmc_pad_pull_data[SDCC3], + .drv = &mmc_pad_drv_data[SDCC3] + }, +}; + +struct msm_mmc_pin_data mmc_slot_pin_data[MAX_SDCC_CONTROLLER] = { + [SDCC1] = { + .pad_data = &mmc_pad_data[SDCC1], + }, + [SDCC3] = { + .pad_data = &mmc_pad_data[SDCC3], + }, +}; + +static unsigned int sdc1_sup_clk_rates[] = { + 400000, 24000000, 48000000 +}; + +static unsigned int sdc3_sup_clk_rates[] = { + 400000, 24000000, 48000000, 96000000 +}; + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static struct mmc_platform_data msm8960_sdc1_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, +#ifdef CONFIG_MMC_MSM_SDC1_8_BIT_SUPPORT + .mmc_bus_width = MMC_CAP_8_BIT_DATA, +#else + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#endif + .sup_clk_table = sdc1_sup_clk_rates, + .sup_clk_cnt = ARRAY_SIZE(sdc1_sup_clk_rates), + .nonremovable = 1, + .sdcc_v4_sup = true, + .vreg_data = &mmc_slot_vreg_data[SDCC1], + .pin_data = &mmc_slot_pin_data[SDCC1] +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static struct mmc_platform_data msm8960_sdc3_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .sup_clk_table = sdc3_sup_clk_rates, + .sup_clk_cnt = ARRAY_SIZE(sdc3_sup_clk_rates), + .wpswitch_gpio = PM8921_GPIO_PM_TO_SYS(16), + .sdcc_v4_sup = true, + .vreg_data = &mmc_slot_vreg_data[SDCC3], + .pin_data = &mmc_slot_pin_data[SDCC3], +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + .status_gpio = PM8921_GPIO_PM_TO_SYS(26), + .status_irq = PM8921_GPIO_IRQ(PM8921_IRQ_BASE, 26), + .irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, +#endif + .xpc_cap = 1, + .uhs_caps = (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_DDR50 | + MMC_CAP_MAX_CURRENT_600) +}; +#endif + +static void __init msm8960_init_mmc(void) +{ +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + /* SDC1 : eMMC card connected */ + msm_add_sdcc(1, &msm8960_sdc1_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + /* SDC3: External card slot */ + msm_add_sdcc(3, &msm8960_sdc3_data); +#endif +} + +static void __init msm8960_init_buses(void) +{ +#ifdef CONFIG_MSM_BUS_SCALING + msm_bus_apps_fabric_pdata.rpm_enabled = 1; + msm_bus_sys_fabric_pdata.rpm_enabled = 1; + msm_bus_mm_fabric_pdata.rpm_enabled = 1; + msm_bus_sys_fpb_pdata.rpm_enabled = 1; + msm_bus_cpss_fpb_pdata.rpm_enabled = 1; + msm_bus_apps_fabric.dev.platform_data = &msm_bus_apps_fabric_pdata; + msm_bus_sys_fabric.dev.platform_data = &msm_bus_sys_fabric_pdata; + msm_bus_mm_fabric.dev.platform_data = &msm_bus_mm_fabric_pdata; + msm_bus_sys_fpb.dev.platform_data = &msm_bus_sys_fpb_pdata; + msm_bus_cpss_fpb.dev.platform_data = &msm_bus_cpss_fpb_pdata; +#endif +} -#include -#include -#include +static struct msm_spi_platform_data msm8960_qup_spi_gsbi1_pdata = { + .max_clock_speed = 15060000, +}; -#include -#include +#ifdef CONFIG_USB_MSM_OTG_72K +static struct msm_otg_platform_data msm_otg_pdata; +#else +#define USB_5V_EN 42 +static void msm_hsusb_vbus_power(bool on) +{ + int rc; + static bool vbus_is_on; + static struct regulator *mvs_otg_switch; + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + }; -#include "devices.h" + if (vbus_is_on == on) + return; -static void __init msm8960_map_io(void) + if (on) { + mvs_otg_switch = regulator_get(&msm_device_otg.dev, "vbus_otg"); + if (IS_ERR(mvs_otg_switch)) { + pr_err("Unable to get mvs_otg_switch\n"); + return; + } + + rc = gpio_request(PM8921_GPIO_PM_TO_SYS(USB_5V_EN), + "usb_5v_en"); + if (rc < 0) { + pr_err("failed to request usb_5v_en gpio\n"); + goto put_mvs_otg; + } + + if (regulator_enable(mvs_otg_switch)) { + pr_err("unable to enable mvs_otg_switch\n"); + goto free_usb_5v_en; + } + + rc = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(USB_5V_EN), + ¶m); + if (rc < 0) { + pr_err("failed to configure usb_5v_en gpio\n"); + goto disable_mvs_otg; + } + vbus_is_on = true; + return; + } +disable_mvs_otg: + regulator_disable(mvs_otg_switch); +free_usb_5v_en: + gpio_free(PM8921_GPIO_PM_TO_SYS(USB_5V_EN)); +put_mvs_otg: + regulator_put(mvs_otg_switch); + vbus_is_on = false; +} + +static struct msm_otg_platform_data msm_otg_pdata = { + .mode = USB_OTG, + .otg_control = OTG_PMIC_CONTROL, + .phy_type = SNPS_28NM_INTEGRATED_PHY, + .pclk_src_name = "dfab_usb_hs_clk", + .pmic_id_irq = PM8921_USB_ID_IN_IRQ(PM8921_IRQ_BASE), + .vbus_power = msm_hsusb_vbus_power, +}; +#endif + +#define PID_MAGIC_ID 0x71432909 +#define SERIAL_NUM_MAGIC_ID 0x61945374 +#define SERIAL_NUMBER_LENGTH 127 +#define DLOAD_USB_BASE_ADD 0x2A03F0C8 + +struct magic_num_struct { + uint32_t pid; + uint32_t serial_num; +}; + +struct dload_struct { + uint32_t reserved1; + uint32_t reserved2; + uint32_t reserved3; + uint16_t reserved4; + uint16_t pid; + char serial_number[SERIAL_NUMBER_LENGTH]; + uint16_t reserved5; + struct magic_num_struct magic_struct; +}; + +static int usb_diag_update_pid_and_serial_num(uint32_t pid, const char *snum) { - msm_map_msm8960_io(); + struct dload_struct __iomem *dload = 0; + + dload = ioremap(DLOAD_USB_BASE_ADD, sizeof(*dload)); + if (!dload) { + pr_err("%s: cannot remap I/O memory region: %08x\n", + __func__, DLOAD_USB_BASE_ADD); + return -ENXIO; + } + + pr_debug("%s: dload:%p pid:%x serial_num:%s\n", + __func__, dload, pid, snum); + /* update pid */ + dload->magic_struct.pid = PID_MAGIC_ID; + dload->pid = pid; + + /* update serial number */ + dload->magic_struct.serial_num = 0; + if (!snum) { + memset(dload->serial_number, 0, SERIAL_NUMBER_LENGTH); + goto out; + } + + dload->magic_struct.serial_num = SERIAL_NUM_MAGIC_ID; + strncpy(dload->serial_number, snum, SERIAL_NUMBER_LENGTH); + dload->serial_number[SERIAL_NUMBER_LENGTH - 1] = '\0'; +out: + iounmap(dload); + return 0; } -static void __init msm8960_init_irq(void) +static struct android_usb_platform_data android_usb_pdata = { + .update_pid_and_serial_num = usb_diag_update_pid_and_serial_num, +}; + +struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &android_usb_pdata, + }, +}; + +static uint8_t spm_wfi_cmd_sequence[] __initdata = { + 0x03, 0x0f, +}; + +static uint8_t spm_power_collapse_without_rpm[] __initdata = { + 0x00, 0x24, 0x54, 0x10, + 0x09, 0x03, 0x01, + 0x10, 0x54, 0x30, 0x0C, + 0x24, 0x30, 0x0f, +}; + +static uint8_t spm_power_collapse_with_rpm[] __initdata = { + 0x00, 0x24, 0x54, 0x10, + 0x09, 0x07, 0x01, 0x0B, + 0x10, 0x54, 0x30, 0x0C, + 0x24, 0x30, 0x0f, +}; + +static struct msm_spm_seq_entry msm_spm_seq_list[] __initdata = { + [0] = { + .mode = MSM_SPM_MODE_CLOCK_GATING, + .notify_rpm = false, + .cmd = spm_wfi_cmd_sequence, + }, + [1] = { + .mode = MSM_SPM_MODE_POWER_COLLAPSE, + .notify_rpm = false, + .cmd = spm_power_collapse_without_rpm, + }, + [2] = { + .mode = MSM_SPM_MODE_POWER_COLLAPSE, + .notify_rpm = true, + .cmd = spm_power_collapse_with_rpm, + }, +}; + +static struct msm_spm_platform_data msm_spm_data[] __initdata = { + [0] = { + .reg_base_addr = MSM_SAW0_BASE, + .reg_init_values[MSM_SPM_REG_SAW2_SECURE] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW2_CFG] = 0x1F, + .reg_init_values[MSM_SPM_REG_SAW2_VCTL] = 0x9C, +#if defined(CONFIG_MSM_AVS_HW) + .reg_init_values[MSM_SPM_REG_SAW2_AVS_CTL] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x00, +#endif + .reg_init_values[MSM_SPM_REG_SAW2_SPM_CTL] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DLY] = 0x02020202, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x0060009C, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x0000001C, + .vctl_timeout_us = 50, + .num_modes = ARRAY_SIZE(msm_spm_seq_list), + .modes = msm_spm_seq_list, + }, + [1] = { + .reg_base_addr = MSM_SAW1_BASE, + .reg_init_values[MSM_SPM_REG_SAW2_SECURE] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW2_CFG] = 0x1F, + .reg_init_values[MSM_SPM_REG_SAW2_VCTL] = 0x9C, +#if defined(CONFIG_MSM_AVS_HW) + .reg_init_values[MSM_SPM_REG_SAW2_AVS_CTL] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x00, +#endif + .reg_init_values[MSM_SPM_REG_SAW2_SPM_CTL] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DLY] = 0x02020202, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x0060009C, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x0000001C, + .vctl_timeout_us = 50, + .num_modes = ARRAY_SIZE(msm_spm_seq_list), + .modes = msm_spm_seq_list, + }, +}; + +static uint8_t l2_spm_wfi_cmd_sequence[] __initdata = { + 0x00, 0x20, 0x03, 0x20, + 0x00, 0x0f, +}; + +static uint8_t l2_spm_gdhs_cmd_sequence[] __initdata = { + 0x00, 0x20, 0x34, 0x64, + 0x48, 0x07, 0x48, 0x20, + 0x50, 0x64, 0x04, 0x34, + 0x50, 0x0f, +}; +static uint8_t l2_spm_power_off_cmd_sequence[] __initdata = { + 0x00, 0x10, 0x34, 0x64, + 0x48, 0x07, 0x48, 0x10, + 0x50, 0x64, 0x04, 0x34, + 0x50, 0x0F, +}; + +static struct msm_spm_seq_entry msm_spm_l2_seq_list[] __initdata = { + [0] = { + .mode = MSM_SPM_L2_MODE_RETENTION, + .notify_rpm = false, + .cmd = l2_spm_wfi_cmd_sequence, + }, + [1] = { + .mode = MSM_SPM_L2_MODE_GDHS, + .notify_rpm = true, + .cmd = l2_spm_gdhs_cmd_sequence, + }, + [2] = { + .mode = MSM_SPM_L2_MODE_POWER_COLLAPSE, + .notify_rpm = true, + .cmd = l2_spm_power_off_cmd_sequence, + }, +}; + + +static struct msm_spm_platform_data msm_spm_l2_data[] __initdata = { + [0] = { + .reg_base_addr = MSM_SAW_L2_BASE, + .reg_init_values[MSM_SPM_REG_SAW2_SECURE] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW2_SPM_CTL] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DLY] = 0x02020202, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x00A000AE, + .reg_init_values[MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x00A00020, + .modes = msm_spm_l2_seq_list, + .num_modes = ARRAY_SIZE(msm_spm_l2_seq_list), + }, +}; + +#define CYTTSP_TS_GPIO_IRQ 11 +#define CYTTSP_TS_SLEEP_GPIO 50 +#define CYTTSP_TS_RESOUT_N_GPIO 52 + +/*virtual key support */ +static ssize_t tma340_vkeys_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) { - unsigned int i; - gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, - (void *)MSM_QGIC_CPU_BASE); + return snprintf(buf, 200, + __stringify(EV_KEY) ":" __stringify(KEY_BACK) ":73:1120:97:97" + ":" __stringify(EV_KEY) ":" __stringify(KEY_MENU) ":230:1120:97:97" + ":" __stringify(EV_KEY) ":" __stringify(KEY_HOME) ":389:1120:97:97" + ":" __stringify(EV_KEY) ":" __stringify(KEY_SEARCH) ":544:1120:97:97" + "\n"); +} - /* Edge trigger PPIs except AVS_SVICINT and AVS_SVICINTSWDONE */ - writel(0xFFFFD7FF, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); +static struct kobj_attribute tma340_vkeys_attr = { + .attr = { + .mode = S_IRUGO, + }, + .show = &tma340_vkeys_show, +}; - if (machine_is_msm8960_rumi3()) - writel(0x0000FFFF, MSM_QGIC_DIST_BASE + GIC_DIST_ENABLE_SET); +static struct attribute *tma340_properties_attrs[] = { + &tma340_vkeys_attr.attr, + NULL +}; - /* FIXME: Not installing AVS_SVICINT and AVS_SVICINTSWDONE yet - * as they are configured as level, which does not play nice with - * handle_percpu_irq. +static struct attribute_group tma340_properties_attr_group = { + .attrs = tma340_properties_attrs, +}; + + +static int cyttsp_platform_init(struct i2c_client *client) +{ + int rc = 0; + static struct kobject *tma340_properties_kobj; + + tma340_vkeys_attr.attr.name = "virtualkeys.cyttsp-i2c"; + tma340_properties_kobj = kobject_create_and_add("board_properties", + NULL); + if (tma340_properties_kobj) + rc = sysfs_create_group(tma340_properties_kobj, + &tma340_properties_attr_group); + if (!tma340_properties_kobj || rc) + pr_err("%s: failed to create board_properties\n", + __func__); + + return 0; +} + +static struct cyttsp_regulator regulator_data[] = { + { + .name = "vdd", + .min_uV = CY_TMA300_VTG_MIN_UV, + .max_uV = CY_TMA300_VTG_MAX_UV, + .load_uA = CY_TMA300_CURR_24HZ_UA, + }, + /* TODO: Remove after runtime PM is enabled in I2C driver */ + { + .name = "vcc_i2c", + .min_uV = CY_I2C_VTG_MIN_UV, + .max_uV = CY_I2C_VTG_MAX_UV, + .load_uA = CY_I2C_CURR_UA, + }, +}; + +static struct cyttsp_platform_data cyttsp_pdata = { + .panel_maxx = 634, + .panel_maxy = 1166, + .disp_maxx = 616, + .disp_maxy = 1023, + .disp_minx = 0, + .disp_miny = 16, + .flags = 0x01, + .gen = CY_GEN3, /* or */ + .use_st = CY_USE_ST, + .use_mt = CY_USE_MT, + .use_hndshk = CY_SEND_HNDSHK, + .use_trk_id = CY_USE_TRACKING_ID, + .use_sleep = CY_USE_SLEEP, + .use_gestures = CY_USE_GESTURES, + .fw_fname = "cyttsp_8960_cdp.hex", + /* activate up to 4 groups + * and set active distance */ - for (i = GIC_PPI_START; i < GIC_SPI_START; i++) { - if (i != AVS_SVICINT && i != AVS_SVICINTSWDONE) - irq_set_handler(i, handle_percpu_irq); - } + .gest_set = CY_GEST_GRP1 | CY_GEST_GRP2 | + CY_GEST_GRP3 | CY_GEST_GRP4 | + CY_ACT_DIST, + /* change act_intrvl to customize the Active power state + * scanning/processing refresh interval for Operating mode + */ + .act_intrvl = CY_ACT_INTRVL_DFLT, + /* change tch_tmout to customize the touch timeout for the + * Active power state for Operating mode + */ + .tch_tmout = CY_TCH_TMOUT_DFLT, + /* change lp_intrvl to customize the Low Power power state + * scanning/processing refresh interval for Operating mode + */ + .lp_intrvl = CY_LP_INTRVL_DFLT, + .sleep_gpio = CYTTSP_TS_SLEEP_GPIO, + .resout_gpio = CYTTSP_TS_RESOUT_N_GPIO, + .irq_gpio = CYTTSP_TS_GPIO_IRQ, + .regulator_info = regulator_data, + .num_regulators = ARRAY_SIZE(regulator_data), + .init = cyttsp_platform_init, +}; + +static struct i2c_board_info cyttsp_info[] __initdata = { + { + I2C_BOARD_INFO(CY_I2C_NAME, 0x24), + .platform_data = &cyttsp_pdata, +#ifndef CY_USE_TIMER + .irq = MSM_GPIO_TO_INT(CYTTSP_TS_GPIO_IRQ), +#endif /* CY_USE_TIMER */ + }, +}; + +static void gsbi_qup_i2c_gpio_config(int adap_id, int config_type) +{ } +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi4_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi3_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi10_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm8960_i2c_qup_gsbi12_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_rpm_platform_data msm_rpm_data = { + .reg_base_addrs = { + [MSM_RPM_PAGE_STATUS] = MSM_RPM_BASE, + [MSM_RPM_PAGE_CTRL] = MSM_RPM_BASE + 0x400, + [MSM_RPM_PAGE_REQ] = MSM_RPM_BASE + 0x600, + [MSM_RPM_PAGE_ACK] = MSM_RPM_BASE + 0xa00, + }, + + .irq_ack = RPM_APCC_CPU0_GP_HIGH_IRQ, + .irq_err = RPM_APCC_CPU0_GP_LOW_IRQ, + .irq_vmpm = RPM_APCC_CPU0_GP_MEDIUM_IRQ, + .msm_apps_ipc_rpm_reg = MSM_APCS_GCC_BASE + 0x008, + .msm_apps_ipc_rpm_val = 4, +}; + +static struct spi_board_info spi_board_info[] __initdata = { + { + .modalias = "ks8851", + .irq = MSM_GPIO_TO_INT(KS8851_IRQ_GPIO), + .max_speed_hz = 19200000, + .bus_num = 0, + .chip_select = 0, + .mode = SPI_MODE_0, + }, +}; + +static struct platform_device msm_device_saw_core0 = { + .name = "saw-regulator", + .id = 0, + .dev = { + .platform_data = &msm_saw_regulator_pdata_s5, + }, +}; + +static struct platform_device msm_device_saw_core1 = { + .name = "saw-regulator", + .id = 1, + .dev = { + .platform_data = &msm_saw_regulator_pdata_s6, + }, +}; + +#ifdef CONFIG_MSM_FAKE_BATTERY +static struct platform_device fish_battery_device = { + .name = "fish_battery", +}; +#endif + +struct platform_device msm8960_device_ext_5v_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = PM8921_MPP_PM_TO_SYS(7), + .dev = { + .platform_data = &msm_gpio_regulator_pdata[GPIO_VREG_ID_EXT_5V], + }, +}; + +struct platform_device msm8960_device_ext_l2_vreg __devinitdata = { + .name = GPIO_REGULATOR_DEV_NAME, + .id = 91, + .dev = { + .platform_data = &msm_gpio_regulator_pdata[GPIO_VREG_ID_EXT_L2], + }, +}; + +static struct platform_device *common_devices[] __initdata = { + &msm_device_dmov, + &msm_device_smd, + &msm8960_device_uart_gsbi5, + &msm_device_saw_core0, + &msm_device_saw_core1, + &msm8960_device_ext_5v_vreg, + &msm8960_device_ext_l2_vreg, + &msm8960_device_ssbi_pm8921, + &msm8960_device_qup_spi_gsbi1, + &msm8960_device_qup_i2c_gsbi3, + &msm8960_device_qup_i2c_gsbi4, + &msm8960_device_qup_i2c_gsbi10, +#ifndef CONFIG_MSM_DSPS + &msm8960_device_qup_i2c_gsbi12, +#endif + &msm_slim_ctrl, + &msm_device_wcnss_wlan, +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) + &qcrypto_device, +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + &qcedev_device, +#endif +#ifdef CONFIG_MSM_ROTATOR + &msm_rotator_device, +#endif + &msm_device_sps, +#ifdef CONFIG_MSM_FAKE_BATTERY + &fish_battery_device, +#endif +#ifdef CONFIG_ANDROID_PMEM + &android_pmem_device, + &android_pmem_adsp_device, + &android_pmem_audio_device, +#endif + &msm_fb_device, + &msm_device_vidc, + &msm_device_bam_dmux, + &msm_fm_platform_init, +}; + static struct platform_device *sim_devices[] __initdata = { - &msm8960_device_uart_gsbi2, + &msm_device_otg, + &msm_device_gadget_peripheral, + &msm_device_hsusb_host, + &android_usb_device, + &msm_device_vidc, + &mipi_dsi_simulator_panel_device, + &msm_bus_apps_fabric, + &msm_bus_sys_fabric, + &msm_bus_mm_fabric, + &msm_bus_sys_fpb, + &msm_bus_cpss_fpb, + &msm_pcm, + &msm_pcm_routing, + &msm_cpudai0, + &msm_cpudai1, + &msm_cpudai_hdmi_rx, + &msm_cpudai_bt_rx, + &msm_cpudai_bt_tx, + &msm_cpudai_fm_rx, + &msm_cpudai_fm_tx, + &msm_cpu_fe, + &msm_stub_codec, + &msm_voice, + &msm_voip, + &msm_lpa_pcm, + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) + &qcrypto_device, +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + &qcedev_device, +#endif + }; static struct platform_device *rumi3_devices[] __initdata = { - &msm8960_device_uart_gsbi5, + &msm_kgsl_3d0, + &msm_kgsl_2d0, + &msm_kgsl_2d1, + &mipi_dsi_renesas_panel_device, +#ifdef CONFIG_MSM_GEMINI + &msm8960_gemini_device, +#endif +}; + +static struct platform_device *cdp_devices[] __initdata = { + &msm_device_otg, + &msm_device_gadget_peripheral, + &msm_device_hsusb_host, + &android_usb_device, + &msm_pcm, + &msm_pcm_routing, + &msm_cpudai0, + &msm_cpudai1, + &msm_cpudai_hdmi_rx, + &msm_cpudai_bt_rx, + &msm_cpudai_bt_tx, + &msm_cpudai_fm_rx, + &msm_cpudai_fm_tx, + &msm_cpu_fe, + &msm_stub_codec, + &msm_kgsl_3d0, +#ifdef CONFIG_MSM_KGSL_2D + &msm_kgsl_2d0, + &msm_kgsl_2d1, +#endif + &mipi_dsi_toshiba_panel_device, +#ifdef CONFIG_MSM_GEMINI + &msm8960_gemini_device, +#endif + &msm_voice, + &msm_voip, + &msm_lpa_pcm, +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL + &hdmi_msm_device, +#endif + &msm_pcm_hostless, + &msm_bus_apps_fabric, + &msm_bus_sys_fabric, + &msm_bus_mm_fabric, + &msm_bus_sys_fpb, + &msm_bus_cpss_fpb, +}; + +static void __init msm8960_i2c_init(void) +{ + msm8960_device_qup_i2c_gsbi4.dev.platform_data = + &msm8960_i2c_qup_gsbi4_pdata; + + msm8960_device_qup_i2c_gsbi3.dev.platform_data = + &msm8960_i2c_qup_gsbi3_pdata; + + msm8960_device_qup_i2c_gsbi10.dev.platform_data = + &msm8960_i2c_qup_gsbi10_pdata; + + msm8960_device_qup_i2c_gsbi12.dev.platform_data = + &msm8960_i2c_qup_gsbi12_pdata; +} + +static struct pm8xxx_irq_platform_data pm8xxx_irq_pdata __devinitdata = { + .irq_base = PM8921_IRQ_BASE, + .devirq = MSM_GPIO_TO_INT(104), + .irq_trigger_flag = IRQF_TRIGGER_LOW, }; +static struct pm8xxx_gpio_platform_data pm8xxx_gpio_pdata __devinitdata = { + .gpio_base = PM8921_GPIO_PM_TO_SYS(1), +}; + +static struct pm8xxx_mpp_platform_data pm8xxx_mpp_pdata __devinitdata = { + .mpp_base = PM8921_MPP_PM_TO_SYS(1), +}; + +static struct pm8xxx_rtc_platform_data pm8xxx_rtc_pdata __devinitdata = { + .rtc_write_enable = false, +}; + +static struct pm8xxx_pwrkey_platform_data pm8xxx_pwrkey_pdata = { + .pull_up = 1, + .kpd_trigger_delay_us = 970, + .wakeup = 1, +}; + +static const unsigned int keymap[] = { + KEY(0, 0, KEY_VOLUMEUP), + KEY(0, 1, KEY_VOLUMEDOWN), + KEY(0, 2, KEY_CAMERA_SNAPSHOT), + KEY(0, 3, KEY_CAMERA_FOCUS), +}; + +static struct matrix_keymap_data keymap_data = { + .keymap_size = ARRAY_SIZE(keymap), + .keymap = keymap, +}; + +static struct pm8xxx_keypad_platform_data keypad_data = { + .input_name = "keypad_8960", + .input_phys_device = "keypad_8960/input0", + .num_rows = 1, + .num_cols = 5, + .rows_gpio_start = PM8921_GPIO_PM_TO_SYS(9), + .cols_gpio_start = PM8921_GPIO_PM_TO_SYS(1), + .debounce_ms = 15, + .scan_delay_ms = 32, + .row_hold_ns = 91500, + .wakeup = 1, + .keymap_data = &keymap_data, +}; + +static const unsigned int keymap_sim[] = { + KEY(0, 0, KEY_7), + KEY(0, 1, KEY_DOWN), + KEY(0, 2, KEY_UP), + KEY(0, 3, KEY_RIGHT), + KEY(0, 4, KEY_ENTER), + KEY(0, 5, KEY_L), + KEY(0, 6, KEY_BACK), + KEY(0, 7, KEY_M), + + KEY(1, 0, KEY_LEFT), + KEY(1, 1, KEY_SEND), + KEY(1, 2, KEY_1), + KEY(1, 3, KEY_4), + KEY(1, 4, KEY_CLEAR), + KEY(1, 5, KEY_MSDOS), + KEY(1, 6, KEY_SPACE), + KEY(1, 7, KEY_COMMA), + + KEY(2, 0, KEY_6), + KEY(2, 1, KEY_5), + KEY(2, 2, KEY_8), + KEY(2, 3, KEY_3), + KEY(2, 4, KEY_NUMERIC_STAR), + KEY(2, 5, KEY_UP), + KEY(2, 6, KEY_DOWN), + KEY(2, 7, KEY_LEFTSHIFT), + + KEY(3, 0, KEY_9), + KEY(3, 1, KEY_NUMERIC_POUND), + KEY(3, 2, KEY_0), + KEY(3, 3, KEY_2), + KEY(3, 4, KEY_SLEEP), + KEY(3, 5, KEY_F1), + KEY(3, 6, KEY_F2), + KEY(3, 7, KEY_F3), + + KEY(4, 0, KEY_BACK), + KEY(4, 1, KEY_HOME), + KEY(4, 2, KEY_MENU), + KEY(4, 3, KEY_VOLUMEUP), + KEY(4, 4, KEY_VOLUMEDOWN), + KEY(4, 5, KEY_F4), + KEY(4, 6, KEY_F5), + KEY(4, 7, KEY_F6), + + KEY(5, 0, KEY_R), + KEY(5, 1, KEY_T), + KEY(5, 2, KEY_Y), + KEY(5, 3, KEY_LEFTALT), + KEY(5, 4, KEY_KPENTER), + KEY(5, 5, KEY_Q), + KEY(5, 6, KEY_W), + KEY(5, 7, KEY_E), + + KEY(6, 0, KEY_F), + KEY(6, 1, KEY_G), + KEY(6, 2, KEY_H), + KEY(6, 3, KEY_CAPSLOCK), + KEY(6, 4, KEY_PAGEUP), + KEY(6, 5, KEY_A), + KEY(6, 6, KEY_S), + KEY(6, 7, KEY_D), + + KEY(7, 0, KEY_V), + KEY(7, 1, KEY_B), + KEY(7, 2, KEY_N), + KEY(7, 3, KEY_MENU), + KEY(7, 4, KEY_PAGEDOWN), + KEY(7, 5, KEY_Z), + KEY(7, 6, KEY_X), + KEY(7, 7, KEY_C), + + KEY(8, 0, KEY_P), + KEY(8, 1, KEY_J), + KEY(8, 2, KEY_K), + KEY(8, 3, KEY_INSERT), + KEY(8, 4, KEY_LINEFEED), + KEY(8, 5, KEY_U), + KEY(8, 6, KEY_I), + KEY(8, 7, KEY_O), + + KEY(9, 0, KEY_4), + KEY(9, 1, KEY_5), + KEY(9, 2, KEY_6), + KEY(9, 3, KEY_7), + KEY(9, 4, KEY_8), + KEY(9, 5, KEY_1), + KEY(9, 6, KEY_2), + KEY(9, 7, KEY_3), + + KEY(10, 0, KEY_F7), + KEY(10, 1, KEY_F8), + KEY(10, 2, KEY_F9), + KEY(10, 3, KEY_F10), + KEY(10, 4, KEY_FN), + KEY(10, 5, KEY_9), + KEY(10, 6, KEY_0), + KEY(10, 7, KEY_DOT), + + KEY(11, 0, KEY_LEFTCTRL), + KEY(11, 1, KEY_F11), + KEY(11, 2, KEY_ENTER), + KEY(11, 3, KEY_SEARCH), + KEY(11, 4, KEY_DELETE), + KEY(11, 5, KEY_RIGHT), + KEY(11, 6, KEY_LEFT), + KEY(11, 7, KEY_RIGHTSHIFT), + KEY(0, 0, KEY_VOLUMEUP), + KEY(0, 1, KEY_VOLUMEDOWN), + KEY(0, 2, KEY_CAMERA_SNAPSHOT), + KEY(0, 3, KEY_CAMERA_FOCUS), +}; + +static struct matrix_keymap_data keymap_data_sim = { + .keymap_size = ARRAY_SIZE(keymap_sim), + .keymap = keymap_sim, +}; + +static struct pm8xxx_keypad_platform_data keypad_data_sim = { + .input_name = "keypad_8960", + .input_phys_device = "keypad_8960/input0", + .num_rows = 12, + .num_cols = 8, + .rows_gpio_start = PM8921_GPIO_PM_TO_SYS(9), + .cols_gpio_start = PM8921_GPIO_PM_TO_SYS(1), + .debounce_ms = 15, + .scan_delay_ms = 32, + .row_hold_ns = 91500, + .wakeup = 1, + .keymap_data = &keymap_data_sim, +}; + +static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata = { + .safety_time = 180, + .update_time = 1, + .max_voltage = 4200, + .min_voltage = 3200, + .resume_voltage = 4100, + .term_current = 100, +}; + +static struct pm8xxx_misc_platform_data pm8xxx_misc_pdata = { + .priority = 0, +}; + +static struct pm8921_bms_platform_data pm8921_bms_pdata __devinitdata = { + .r_sense = 10, + .i_test = 2500, + .v_failure = 3000, + .calib_delay_ms = 600000, + .batt_data = &palladium_1500_data, +}; + +static struct led_info pm8921_led_info[] = { + [0] = { + .name = "led:drv1", + .flags = PM8XXX_ID_LED_1, + }, +}; + +static struct led_platform_data pm8xxx_leds_pdata = { + .num_leds = ARRAY_SIZE(pm8921_led_info), + .leds = pm8921_led_info, +}; + +static struct pm8921_platform_data pm8921_platform_data __devinitdata = { + .irq_pdata = &pm8xxx_irq_pdata, + .gpio_pdata = &pm8xxx_gpio_pdata, + .mpp_pdata = &pm8xxx_mpp_pdata, + .rtc_pdata = &pm8xxx_rtc_pdata, + .pwrkey_pdata = &pm8xxx_pwrkey_pdata, + .keypad_pdata = &keypad_data, + .misc_pdata = &pm8xxx_misc_pdata, + .regulator_pdatas = msm_pm8921_regulator_pdata, + .charger_pdata = &pm8921_chg_pdata, + .bms_pdata = &pm8921_bms_pdata, + .adc_pdata = &pm8921_adc_pdata, + .leds_pdata = &pm8xxx_leds_pdata, +}; + +static struct msm_ssbi_platform_data msm8960_ssbi_pm8921_pdata __devinitdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, + .slave = { + .name = "pm8921-core", + .platform_data = &pm8921_platform_data, + }, +}; + +static void msm8960_wcnss_init(void) +{ + int i, ret, j; + + for (i = 0; i < ARRAY_SIZE(wcnss_5wire_interface); i++) { + ret = gpio_request(wcnss_5wire_interface[i].gpio, + "wcnss_5_wire"); + if (ret) { + pr_err("wcnss_5_wire gpio %d failed: %d\n", + wcnss_5wire_interface[i].gpio, ret); + goto fail; + } + } + + pr_info("%s: Iris 5-wire gpios configured\n", __func__); + + return; + +fail: + for (j = 0; j < i; j++) + gpio_free(wcnss_5wire_interface[j].gpio); +} + +static int ethernet_init(void) +{ + int ret; + ret = gpio_request(KS8851_IRQ_GPIO, "ks8851_irq"); + if (ret) { + pr_err("ks8851 gpio_request failed: %d\n", ret); + goto fail; + } + + ret = gpio_request(KS8851_RST_GPIO, "ks8851_rst"); + if (ret) { + pr_err("ks8851 gpio_request failed: %d\n", ret); + goto fail_rst; + } + + ret = gpio_request(FPGA_CS_GPIO, "fpga_cs"); + if (ret) { + pr_err("ks8851 gpio_request failed: %d\n", ret); + goto fail_cs; + } + + gpio_direction_output(FPGA_CS_GPIO, 1); + gpio_direction_output(KS8851_RST_GPIO, 1); + return 0; +fail_cs: + gpio_free(KS8851_RST_GPIO); +fail_rst: + gpio_free(KS8851_IRQ_GPIO); +fail: + return ret; +} + +static struct msm_cpuidle_state msm_cstates[] __initdata = { + {0, 0, "C0", "WFI", + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT}, + + {0, 1, "C1", "STANDALONE_POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE}, + + {0, 2, "C2", "POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE}, + + {1, 0, "C0", "WFI", + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT}, + + {1, 1, "C1", "STANDALONE_POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE}, +}; + +static struct msm_pm_platform_data msm_pm_data[MSM_PM_SLEEP_MODE_NR * 2] = { + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 4000, + .residency = 13000, + }, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 500, + .residency = 6000, + }, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 2, + .residency = 0, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 0, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 600, + .residency = 7200, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 500, + .residency = 6000, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { + .idle_supported = 1, + .suspend_supported = 0, + .idle_enabled = 1, + .suspend_enabled = 0, + .latency = 2, + .residency = 0, + }, +}; + +static struct msm_rpmrs_level msm_rpmrs_levels[] __initdata = { + { + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1, 8000, 100000, 1, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1500, 5000, 60100000, 3000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, GDHS, MAX, ACTIVE), + false, + 1800, 5000, 60350000, 3500, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, HSFS_OPEN, MAX, ACTIVE), + false, + 2800, 2500, 65350000, 4800, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, GDHS, MAX, ACTIVE), + false, + 3800, 4500, 67850000, 5500, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, MAX, ACTIVE), + false, + 4800, 2000, 71850000, 6800, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, ACTIVE, RET_HIGH), + false, + 6800, 500, 75850000, 8800, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, RET_HIGH, RET_LOW), + false, + 7800, 0, 76350000, 9800, + }, +}; + +#ifdef CONFIG_I2C +#define I2C_SURF 1 +#define I2C_FFA (1 << 1) +#define I2C_RUMI (1 << 2) +#define I2C_SIM (1 << 3) +#define I2C_FLUID (1 << 4) +#define MSM_8960_GSBI4_QUP_I2C_BUS_ID 4 +#define MSM_8960_GSBI3_QUP_I2C_BUS_ID 3 + +struct i2c_registry { + u8 machs; + int bus; + struct i2c_board_info *info; + int len; +}; + +#ifdef CONFIG_MSM_CAMERA +static struct i2c_board_info msm_camera_boardinfo[] __initdata = { +#ifdef CONFIG_IMX074 + { + I2C_BOARD_INFO("imx074", 0x1A), + }, +#endif +#ifdef CONFIG_OV2720 + { + I2C_BOARD_INFO("ov2720", 0x6C), + }, +#endif +}; +#endif + +/* Sensors DSPS platform data */ +#ifdef CONFIG_MSM_DSPS +#define DSPS_PIL_GENERIC_NAME "dsps" +#endif /* CONFIG_MSM_DSPS */ + +static void __init msm8960_init_dsps(void) +{ +#ifdef CONFIG_MSM_DSPS + struct msm_dsps_platform_data *pdata = + msm_dsps_device.dev.platform_data; + pdata->pil_name = DSPS_PIL_GENERIC_NAME; + pdata->gpios = NULL; + pdata->gpios_num = 0; + + platform_device_register(&msm_dsps_device); +#endif /* CONFIG_MSM_DSPS */ +} + +static struct i2c_registry msm8960_i2c_devices[] __initdata = { +#ifdef CONFIG_MSM_CAMERA + { + I2C_SURF | I2C_FFA | I2C_FLUID | I2C_RUMI, + MSM_8960_GSBI4_QUP_I2C_BUS_ID, + msm_camera_boardinfo, + ARRAY_SIZE(msm_camera_boardinfo), + }, +#endif + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_8960_GSBI3_QUP_I2C_BUS_ID, + cyttsp_info, + ARRAY_SIZE(cyttsp_info), + } +}; +#endif /* CONFIG_I2C */ + +static void __init register_i2c_devices(void) +{ +#ifdef CONFIG_I2C + u8 mach_mask = 0; + int i; + + /* Build the matching 'supported_machs' bitmask */ + if (machine_is_msm8960_cdp()) + mach_mask = I2C_SURF; + else if (machine_is_msm8960_rumi3()) + mach_mask = I2C_RUMI; + else if (machine_is_msm8960_sim()) + mach_mask = I2C_SIM; + else + pr_err("unmatched machine ID in register_i2c_devices\n"); + + /* Run the array and install devices as appropriate */ + for (i = 0; i < ARRAY_SIZE(msm8960_i2c_devices); ++i) { + if (msm8960_i2c_devices[i].machs & mach_mask) + i2c_register_board_info(msm8960_i2c_devices[i].bus, + msm8960_i2c_devices[i].info, + msm8960_i2c_devices[i].len); + } +#endif +} + static void __init msm8960_sim_init(void) { + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); + + BUG_ON(msm_rpm_init(&msm_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(msm_rpmrs_levels, + ARRAY_SIZE(msm_rpmrs_levels))); + regulator_suppress_info_printing(); + msm8960_clock_init(); + msm8960_device_ssbi_pm8921.dev.platform_data = + &msm8960_ssbi_pm8921_pdata; + pm8921_platform_data.num_regulators = msm_pm8921_regulator_pdata_len; + msm8960_device_qup_spi_gsbi1.dev.platform_data = + &msm8960_qup_spi_gsbi1_pdata; + + /* Simulator supports a QWERTY keypad */ + pm8921_platform_data.keypad_pdata = &keypad_data_sim; + + msm_device_otg.dev.platform_data = &msm_otg_pdata; + msm_device_gadget_peripheral.dev.parent = &msm_device_otg.dev; + msm_device_hsusb_host.dev.parent = &msm_device_otg.dev; + gpiomux_init(); + ethernet_init(); + msm8960_i2c_init(); + msm_spm_init(msm_spm_data, ARRAY_SIZE(msm_spm_data)); + msm_spm_l2_init(msm_spm_l2_data); + msm8960_init_buses(); + platform_add_devices(common_devices, ARRAY_SIZE(common_devices)); + pm8921_gpio_mpp_init(); platform_add_devices(sim_devices, ARRAY_SIZE(sim_devices)); + msm_acpu_clock_init(&msm8960_acpu_clock_data); + + msm8960_device_qup_spi_gsbi1.dev.platform_data = + &msm8960_qup_spi_gsbi1_pdata; + spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info)); + + msm8960_init_mmc(); + msm_fb_add_devices(); + slim_register_board_info(msm_slim_devices, + ARRAY_SIZE(msm_slim_devices)); + msm_pm_set_platform_data(msm_pm_data, ARRAY_SIZE(msm_pm_data)); + msm_pm_set_rpm_wakeup_irq(RPM_APCC_CPU0_WAKE_UP_IRQ); + msm_cpuidle_set_states(msm_cstates, ARRAY_SIZE(msm_cstates), + msm_pm_data); } static void __init msm8960_rumi3_init(void) { + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); + + BUG_ON(msm_rpm_init(&msm_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(msm_rpmrs_levels, + ARRAY_SIZE(msm_rpmrs_levels))); + regulator_suppress_info_printing(); + msm8960_clock_init_dummy(); + gpiomux_init(); + ethernet_init(); + msm8960_device_ssbi_pm8921.dev.platform_data = + &msm8960_ssbi_pm8921_pdata; + pm8921_platform_data.num_regulators = msm_pm8921_regulator_pdata_len; + msm8960_device_qup_spi_gsbi1.dev.platform_data = + &msm8960_qup_spi_gsbi1_pdata; + spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info)); + msm8960_i2c_init(); + msm_spm_init(msm_spm_data, ARRAY_SIZE(msm_spm_data)); + msm_spm_l2_init(msm_spm_l2_data); + platform_add_devices(common_devices, ARRAY_SIZE(common_devices)); + pm8921_gpio_mpp_init(); platform_add_devices(rumi3_devices, ARRAY_SIZE(rumi3_devices)); + msm8960_init_mmc(); + + register_i2c_devices(); + msm_fb_add_devices(); + slim_register_board_info(msm_slim_devices, + ARRAY_SIZE(msm_slim_devices)); + msm_pm_set_platform_data(msm_pm_data, ARRAY_SIZE(msm_pm_data)); + msm_pm_set_rpm_wakeup_irq(RPM_APCC_CPU0_WAKE_UP_IRQ); + msm_cpuidle_set_states(msm_cstates, ARRAY_SIZE(msm_cstates), + msm_pm_data); +} + +static void __init msm8960_cdp_init(void) +{ + if (socinfo_init() < 0) + pr_err("socinfo_init() failed!\n"); + + BUG_ON(msm_rpm_init(&msm_rpm_data)); + BUG_ON(msm_rpmrs_levels_init(msm_rpmrs_levels, + ARRAY_SIZE(msm_rpmrs_levels))); + regulator_suppress_info_printing(); + if (msm_xo_init()) + pr_err("Failed to initialize XO votes\n"); + msm8960_clock_init(); + msm_device_otg.dev.platform_data = &msm_otg_pdata; + msm_device_gadget_peripheral.dev.parent = &msm_device_otg.dev; + msm_device_hsusb_host.dev.parent = &msm_device_otg.dev; + gpiomux_init(); + ethernet_init(); + msm8960_device_qup_spi_gsbi1.dev.platform_data = + &msm8960_qup_spi_gsbi1_pdata; + spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info)); + msm8960_device_ssbi_pm8921.dev.platform_data = + &msm8960_ssbi_pm8921_pdata; + pm8921_platform_data.num_regulators = msm_pm8921_regulator_pdata_len; + msm8960_i2c_init(); + msm_spm_init(msm_spm_data, ARRAY_SIZE(msm_spm_data)); + msm_spm_l2_init(msm_spm_l2_data); + msm8960_init_buses(); + platform_add_devices(msm_footswitch_devices, + msm_num_footswitch_devices); + platform_add_devices(common_devices, ARRAY_SIZE(common_devices)); + pm8921_gpio_mpp_init(); + platform_add_devices(cdp_devices, ARRAY_SIZE(cdp_devices)); + msm8960_init_cam(); + msm8960_init_mmc(); + msm_acpu_clock_init(&msm8960_acpu_clock_data); + register_i2c_devices(); + msm8960_wcnss_init(); + msm_fb_add_devices(); + slim_register_board_info(msm_slim_devices, + ARRAY_SIZE(msm_slim_devices)); + msm8960_init_dsps(); + msm_pm_set_platform_data(msm_pm_data, ARRAY_SIZE(msm_pm_data)); + msm_pm_set_rpm_wakeup_irq(RPM_APCC_CPU0_WAKE_UP_IRQ); + msm_cpuidle_set_states(msm_cstates, ARRAY_SIZE(msm_cstates), + msm_pm_data); } MACHINE_START(MSM8960_SIM, "QCT MSM8960 SIMULATOR") .map_io = msm8960_map_io, + .reserve = msm8960_reserve, .init_irq = msm8960_init_irq, .timer = &msm_timer, .init_machine = msm8960_sim_init, + .init_early = msm8960_allocate_memory_regions, MACHINE_END MACHINE_START(MSM8960_RUMI3, "QCT MSM8960 RUMI3") .map_io = msm8960_map_io, + .reserve = msm8960_reserve, .init_irq = msm8960_init_irq, .timer = &msm_timer, .init_machine = msm8960_rumi3_init, + .init_early = msm8960_allocate_memory_regions, +MACHINE_END + +MACHINE_START(MSM8960_CDP, "QCT MSM8960 CDP") + .map_io = msm8960_map_io, + .reserve = msm8960_reserve, + .init_irq = msm8960_init_irq, + .timer = &msm_timer, + .init_machine = msm8960_cdp_init, + .init_early = msm8960_allocate_memory_regions, +MACHINE_END + +MACHINE_START(MSM8960_MTP, "QCT MSM8960 MTP") + .map_io = msm8960_map_io, + .reserve = msm8960_reserve, + .init_irq = msm8960_init_irq, + .timer = &msm_timer, + .init_machine = msm8960_cdp_init, + .init_early = msm8960_allocate_memory_regions, MACHINE_END +MACHINE_START(MSM8960_FLUID, "QCT MSM8960 FLUID") + .map_io = msm8960_map_io, + .reserve = msm8960_reserve, + .init_irq = msm8960_init_irq, + .timer = &msm_timer, + .init_machine = msm8960_cdp_init, + .init_early = msm8960_allocate_memory_regions, +MACHINE_END diff --git a/arch/arm/mach-msm/board-msm8960.h b/arch/arm/mach-msm/board-msm8960.h new file mode 100644 index 00000000000..26e82fd1e32 --- /dev/null +++ b/arch/arm/mach-msm/board-msm8960.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ARCH_ARM_MACH_MSM_BOARD_MSM8960_H +#define __ARCH_ARM_MACH_MSM_BOARD_MSM8960_H + +#include +#include + +/* Macros assume PMIC GPIOs and MPPs start at 1 */ +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) +#define PM8921_MPP_BASE (PM8921_GPIO_BASE + PM8921_NR_GPIOS) +#define PM8921_MPP_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_MPP_BASE) +#define PM8921_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) +#define PM8921_MPP_IRQ_BASE (PM8921_IRQ_BASE + NR_GPIO_IRQS) + +extern struct pm8921_regulator_platform_data + msm_pm8921_regulator_pdata[] __devinitdata; + +extern int msm_pm8921_regulator_pdata_len __devinitdata; + +#define GPIO_VREG_ID_EXT_5V 0 +#define GPIO_VREG_ID_EXT_L2 1 + +extern struct gpio_regulator_platform_data + msm_gpio_regulator_pdata[] __devinitdata; + +extern struct regulator_init_data msm_saw_regulator_pdata_s5; +extern struct regulator_init_data msm_saw_regulator_pdata_s6; + +extern struct pm8921_bms_battery_data palladium_1500_data; +#endif diff --git a/arch/arm/mach-msm/board-msm8x60-vcm.c b/arch/arm/mach-msm/board-msm8x60-vcm.c new file mode 100644 index 00000000000..6078367c05f --- /dev/null +++ b/arch/arm/mach-msm/board-msm8x60-vcm.c @@ -0,0 +1,168 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include +#include + +#define MSM_SMI_BASE 0x38000000 +#define MSM_SMI_SIZE 0x04000000 + +#define SMI_16M 0 +#define SMI_1M 1 +#define SMI_64K 2 +#define SMI_4K 3 +#define EBI_16M 4 +#define EBI_1M 5 +#define EBI_64K 6 +#define EBI_4K 7 + +static void free_ebi_pools(void); + +static struct physmem_region memory[] = { + { /* SMI 16M */ + .addr = MSM_SMI_BASE, + .size = SZ_16M, + .chunk_size = SZ_16M + }, + { /* SMI 1M */ + .addr = MSM_SMI_BASE + SZ_16M, + .size = SZ_8M, + .chunk_size = SZ_1M + }, + { /* SMI 64K */ + .addr = MSM_SMI_BASE + SZ_16M + SZ_8M, + .size = SZ_4M, + .chunk_size = SZ_64K + }, + { /* SMI 4K */ + .addr = MSM_SMI_BASE + SZ_16M + SZ_8M + SZ_4M, + .size = SZ_4M, + .chunk_size = SZ_4K + }, + + { /* EBI 16M */ + .addr = 0, + .size = SZ_16M, + .chunk_size = SZ_16M + }, + { /* EBI 1M */ + .addr = 0, + .size = SZ_8M, + .chunk_size = SZ_1M + }, + { /* EBI 64K */ + .addr = 0, + .size = SZ_4M, + .chunk_size = SZ_64K + }, + { /* EBI 4K */ + .addr = 0, + .size = SZ_4M, + .chunk_size = SZ_4K + } +}; + + +/* The pool priority MUST be in descending order of size */ +static struct vcm_memtype_map mt_map[] __initdata = { + { + /* MEMTYPE_0 */ + .pool_id = {SMI_16M, SMI_1M, SMI_64K, SMI_4K}, + .num_pools = 4, + }, + { + /* MEMTYPE_1 */ + .pool_id = {SMI_16M, SMI_1M, SMI_64K, EBI_4K}, + .num_pools = 4, + }, + { /* MEMTYPE_2 */ + .pool_id = {EBI_16M, EBI_1M, EBI_64K, EBI_4K}, + .num_pools = 4, + }, + { + /* MEMTYPE_3 */ + .pool_id = {SMI_16M, SMI_1M, EBI_1M, SMI_64K, EBI_64K, EBI_4K}, + .num_pools = 6, + } +}; + +static int __init msm8x60_vcm_init(void) +{ + int ret, i; + void *ebi_chunk; + + + for (i = 0; i < ARRAY_SIZE(memory); i++) { + if (memory[i].addr == 0) { + ebi_chunk = __alloc_bootmem(memory[i].size, + memory[i].size, 0); + if (!ebi_chunk) { + pr_err("Could not allocate VCM-managed physical" + " memory\n"); + ret = -ENOMEM; + goto fail; + } + memory[i].addr = __pa(ebi_chunk); + } + } + + ret = vcm_sys_init(memory, ARRAY_SIZE(memory), + mt_map, ARRAY_SIZE(mt_map), + (void *)MSM_SMI_BASE + MSM_SMI_SIZE - SZ_8M, SZ_8M); + + if (ret != 0) { + pr_err("vcm_sys_init() ret %i\n", ret); + goto fail; + } + + return 0; +fail: + free_ebi_pools(); + return ret; +}; + +static void free_ebi_pools(void) +{ + int i; + phys_addr_t r; + for (i = 0; i < ARRAY_SIZE(memory); i++) { + r = memory[i].addr; + if (r > MSM_SMI_BASE + MSM_SMI_SIZE) + free_bootmem((unsigned long)__va(r), memory[i].size); + } +} + + +/* Useful for testing, and if VCM is ever unloaded */ +static void __exit msm8x60_vcm_exit(void) +{ + int ret; + + ret = vcm_sys_destroy(); + if (ret != 0) { + pr_err("vcm_sys_destroy() ret %i\n", ret); + goto fail; + } + free_ebi_pools(); +fail: + return; +} + + +subsys_initcall(msm8x60_vcm_init); +module_exit(msm8x60_vcm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Stepan Moskovchenko "); diff --git a/arch/arm/mach-msm/board-msm8x60.c b/arch/arm/mach-msm/board-msm8x60.c index 1163b6fd05d..573bd57fb88 100644 --- a/arch/arm/mach-msm/board-msm8x60.c +++ b/arch/arm/mach-msm/board-msm8x60.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and @@ -9,85 +9,9862 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ANDROID_PMEM +#include +#endif + +#if defined(CONFIG_SMB137B_CHARGER) || defined(CONFIG_SMB137B_CHARGER_MODULE) +#include +#endif +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_MSM_DSPS +#include +#endif +#include +#include +#include +#include +#ifdef CONFIG_USB_G_ANDROID +#include +#include +#endif +#include +#include +#include +#include +#include + +#include "devices.h" +#include "devices-msm8x60.h" +#include "cpuidle.h" +#include "pm.h" +#include "mpm.h" +#include "spm.h" +#include "rpm_log.h" +#include "timer.h" +#include "gpiomux.h" +#include "gpiomux-8x60.h" +#include "rpm_stats.h" +#include "peripheral-loader.h" +#include +#include "rpm_resources.h" + +#define MSM_SHARED_RAM_PHYS 0x40000000 + +/* Macros assume PMIC GPIOs start at 0 */ +#define PM8058_GPIO_BASE NR_MSM_GPIOS +#define PM8058_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio + PM8058_GPIO_BASE) +#define PM8058_GPIO_SYS_TO_PM(sys_gpio) (sys_gpio - PM8058_GPIO_BASE) +#define PM8058_MPP_BASE (PM8058_GPIO_BASE + PM8058_GPIOS) +#define PM8058_MPP_PM_TO_SYS(pm_gpio) (pm_gpio + PM8058_MPP_BASE) +#define PM8058_MPP_SYS_TO_PM(sys_gpio) (sys_gpio - PM8058_MPP_BASE) +#define PM8058_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) + +#define PM8901_GPIO_BASE (PM8058_GPIO_BASE + \ + PM8058_GPIOS + PM8058_MPPS) +#define PM8901_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio + PM8901_GPIO_BASE) +#define PM8901_GPIO_SYS_TO_PM(sys_gpio) (sys_gpio - PM901_GPIO_BASE) +#define PM8901_IRQ_BASE (PM8058_IRQ_BASE + \ + NR_PMIC8058_IRQS) + +#define MDM2AP_SYNC 129 + +#define LCDC_SPI_GPIO_CLK 73 +#define LCDC_SPI_GPIO_CS 72 +#define LCDC_SPI_GPIO_MOSI 70 +#define LCDC_AUO_PANEL_NAME "lcdc_auo_wvga" +#define LCDC_SAMSUNG_OLED_PANEL_NAME "lcdc_samsung_oled" +#define LCDC_SAMSUNG_WSVGA_PANEL_NAME "lcdc_samsung_wsvga" +#define LCDC_SAMSUNG_SPI_DEVICE_NAME "lcdc_samsung_ams367pe02" +#define LCDC_AUO_SPI_DEVICE_NAME "lcdc_auo_nt35582" + +#define DSPS_PIL_GENERIC_NAME "dsps" +#define DSPS_PIL_FLUID_NAME "dsps_fluid" + +enum { + GPIO_EXPANDER_IRQ_BASE = PM8901_IRQ_BASE + NR_PMIC8901_IRQS, + GPIO_EXPANDER_GPIO_BASE = PM8901_GPIO_BASE + PM8901_MPPS, + /* CORE expander */ + GPIO_CORE_EXPANDER_BASE = GPIO_EXPANDER_GPIO_BASE, + GPIO_CLASS_D1_EN = GPIO_CORE_EXPANDER_BASE, + GPIO_WLAN_DEEP_SLEEP_N, + GPIO_LVDS_SHUTDOWN_N, + GPIO_DISP_RESX_N = GPIO_LVDS_SHUTDOWN_N, + GPIO_MS_SYS_RESET_N, + GPIO_CAP_TS_RESOUT_N, + GPIO_CAP_GAUGE_BI_TOUT, + GPIO_ETHERNET_PME, + GPIO_EXT_GPS_LNA_EN, + GPIO_MSM_WAKES_BT, + GPIO_ETHERNET_RESET_N, + GPIO_HEADSET_DET_N, + GPIO_USB_UICC_EN, + GPIO_BACKLIGHT_EN, + GPIO_EXT_CAMIF_PWR_EN, + GPIO_BATT_GAUGE_INT_N, + GPIO_BATT_GAUGE_EN, + /* DOCKING expander */ + GPIO_DOCKING_EXPANDER_BASE = GPIO_EXPANDER_GPIO_BASE + 16, + GPIO_MIPI_DSI_RST_N = GPIO_DOCKING_EXPANDER_BASE, + GPIO_AUX_JTAG_DET_N, + GPIO_DONGLE_DET_N, + GPIO_SVIDEO_LOAD_DET, + GPIO_SVID_AMP_SHUTDOWN1_N, + GPIO_SVID_AMP_SHUTDOWN0_N, + GPIO_SDC_WP, + GPIO_IRDA_PWDN, + GPIO_IRDA_RESET_N, + GPIO_DONGLE_GPIO0, + GPIO_DONGLE_GPIO1, + GPIO_DONGLE_GPIO2, + GPIO_DONGLE_GPIO3, + GPIO_DONGLE_PWR_EN, + GPIO_EMMC_RESET_N, + GPIO_TP_EXP2_IO15, + /* SURF expander */ + GPIO_SURF_EXPANDER_BASE = GPIO_EXPANDER_GPIO_BASE + (16 * 2), + GPIO_SD_CARD_DET_1 = GPIO_SURF_EXPANDER_BASE, + GPIO_SD_CARD_DET_2, + GPIO_SD_CARD_DET_4, + GPIO_SD_CARD_DET_5, + GPIO_UIM3_RST, + GPIO_SURF_EXPANDER_IO5, + GPIO_SURF_EXPANDER_IO6, + GPIO_ADC_I2C_EN, + GPIO_SURF_EXPANDER_IO8, + GPIO_SURF_EXPANDER_IO9, + GPIO_SURF_EXPANDER_IO10, + GPIO_SURF_EXPANDER_IO11, + GPIO_SURF_EXPANDER_IO12, + GPIO_SURF_EXPANDER_IO13, + GPIO_SURF_EXPANDER_IO14, + GPIO_SURF_EXPANDER_IO15, + /* LEFT KB IO expander */ + GPIO_LEFT_KB_EXPANDER_BASE = GPIO_EXPANDER_GPIO_BASE + (16 * 3), + GPIO_LEFT_LED_1 = GPIO_LEFT_KB_EXPANDER_BASE, + GPIO_LEFT_LED_2, + GPIO_LEFT_LED_3, + GPIO_LEFT_LED_WLAN, + GPIO_JOYSTICK_EN, + GPIO_CAP_TS_SLEEP, + GPIO_LEFT_KB_IO6, + GPIO_LEFT_LED_5, + /* RIGHT KB IO expander */ + GPIO_RIGHT_KB_EXPANDER_BASE = GPIO_EXPANDER_GPIO_BASE + (16 * 3) + 8, + GPIO_RIGHT_LED_1 = GPIO_RIGHT_KB_EXPANDER_BASE, + GPIO_RIGHT_LED_2, + GPIO_RIGHT_LED_3, + GPIO_RIGHT_LED_BT, + GPIO_WEB_CAMIF_STANDBY, + GPIO_COMPASS_RST_N, + GPIO_WEB_CAMIF_RESET_N, + GPIO_RIGHT_LED_5, + GPIO_R_ALTIMETER_RESET_N, + /* FLUID S IO expander */ + GPIO_SOUTH_EXPANDER_BASE, + GPIO_MIC2_ANCR_SEL = GPIO_SOUTH_EXPANDER_BASE, + GPIO_MIC1_ANCL_SEL, + GPIO_HS_MIC4_SEL, + GPIO_FML_MIC3_SEL, + GPIO_FMR_MIC5_SEL, + GPIO_TS_SLEEP, + GPIO_HAP_SHIFT_LVL_OE, + GPIO_HS_SW_DIR, + /* FLUID N IO expander */ + GPIO_NORTH_EXPANDER_BASE, + GPIO_EPM_3_3V_EN = GPIO_NORTH_EXPANDER_BASE, + GPIO_EPM_5V_BOOST_EN, + GPIO_AUX_CAM_2P7_EN, + GPIO_LED_FLASH_EN, + GPIO_LED1_GREEN_N, + GPIO_LED2_RED_N, + GPIO_FRONT_CAM_RESET_N, + GPIO_EPM_LVLSFT_EN, + GPIO_N_ALTIMETER_RESET_N, + /* EPM expander */ + GPIO_EPM_EXPANDER_BASE, + GPIO_PWR_MON_START = GPIO_EPM_EXPANDER_BASE, + GPIO_PWR_MON_RESET_N, + GPIO_ADC1_PWDN_N, + GPIO_ADC2_PWDN_N, + GPIO_EPM_EXPANDER_IO4, + GPIO_ADC1_MUX_SPI_INT_N_3_3V, + GPIO_ADC2_MUX_SPI_INT_N, + GPIO_EPM_EXPANDER_IO7, + GPIO_PWR_MON_ENABLE, + GPIO_EPM_SPI_ADC1_CS_N, + GPIO_EPM_SPI_ADC2_CS_N, + GPIO_EPM_EXPANDER_IO11, + GPIO_EPM_EXPANDER_IO12, + GPIO_EPM_EXPANDER_IO13, + GPIO_EPM_EXPANDER_IO14, + GPIO_EPM_EXPANDER_IO15, +}; + +/* + * The UI_INTx_N lines are pmic gpio lines which connect i2c + * gpio expanders to the pm8058. + */ +#define UI_INT1_N 25 +#define UI_INT2_N 34 +#define UI_INT3_N 14 +/* +FM GPIO is GPIO 18 on PMIC 8058. +As the index starts from 0 in the PMIC driver, and hence 17 +corresponds to GPIO 18 on PMIC 8058. +*/ +#define FM_GPIO 17 + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static void (*sdc2_status_notify_cb)(int card_present, void *dev_id); +static void *sdc2_status_notify_cb_devid; +#endif + +#ifdef CONFIG_MMC_MSM_SDC5_SUPPORT +static void (*sdc5_status_notify_cb)(int card_present, void *dev_id); +static void *sdc5_status_notify_cb_devid; +#endif + +static struct msm_spm_platform_data msm_spm_data_v1[] __initdata = { + [0] = { + .reg_base_addr = MSM_SAW0_BASE, + +#ifdef CONFIG_MSM_AVS_HW + .reg_init_values[MSM_SPM_REG_SAW_AVS_CTL] = 0x586020FF, +#endif + .reg_init_values[MSM_SPM_REG_SAW_CFG] = 0x0F, + .reg_init_values[MSM_SPM_REG_SAW_SPM_CTL] = 0x68, + .reg_init_values[MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY] = 0xFFFFFFFF, + .reg_init_values[MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY] = 0xFFFFFFFF, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLK_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN] = 0x07, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN] = 0x00, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLMP_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_RST_EN] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW_SPM_MPM_CFG] = 0x00, + + .awake_vlevel = 0x94, + .retention_vlevel = 0x81, + .collapse_vlevel = 0x20, + .retention_mid_vlevel = 0x94, + .collapse_mid_vlevel = 0x8C, + + .vctl_timeout_us = 50, + }, + + [1] = { + .reg_base_addr = MSM_SAW1_BASE, + +#ifdef CONFIG_MSM_AVS_HW + .reg_init_values[MSM_SPM_REG_SAW_AVS_CTL] = 0x586020FF, +#endif + .reg_init_values[MSM_SPM_REG_SAW_CFG] = 0x0F, + .reg_init_values[MSM_SPM_REG_SAW_SPM_CTL] = 0x68, + .reg_init_values[MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY] = 0xFFFFFFFF, + .reg_init_values[MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY] = 0xFFFFFFFF, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLK_EN] = 0x13, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN] = 0x07, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN] = 0x00, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLMP_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_RST_EN] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW_SPM_MPM_CFG] = 0x00, + + .awake_vlevel = 0x94, + .retention_vlevel = 0x81, + .collapse_vlevel = 0x20, + .retention_mid_vlevel = 0x94, + .collapse_mid_vlevel = 0x8C, + + .vctl_timeout_us = 50, + }, +}; + +static struct msm_spm_platform_data msm_spm_data[] __initdata = { + [0] = { + .reg_base_addr = MSM_SAW0_BASE, + +#ifdef CONFIG_MSM_AVS_HW + .reg_init_values[MSM_SPM_REG_SAW_AVS_CTL] = 0x586020FF, +#endif + .reg_init_values[MSM_SPM_REG_SAW_CFG] = 0x1C, + .reg_init_values[MSM_SPM_REG_SAW_SPM_CTL] = 0x68, + .reg_init_values[MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY] = 0x0C0CFFFF, + .reg_init_values[MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY] = 0x78780FFF, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLK_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN] = 0x07, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN] = 0x00, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLMP_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_RST_EN] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW_SPM_MPM_CFG] = 0x00, + + .awake_vlevel = 0xA0, + .retention_vlevel = 0x89, + .collapse_vlevel = 0x20, + .retention_mid_vlevel = 0x89, + .collapse_mid_vlevel = 0x89, + + .vctl_timeout_us = 50, + }, + + [1] = { + .reg_base_addr = MSM_SAW1_BASE, + +#ifdef CONFIG_MSM_AVS_HW + .reg_init_values[MSM_SPM_REG_SAW_AVS_CTL] = 0x586020FF, +#endif + .reg_init_values[MSM_SPM_REG_SAW_CFG] = 0x1C, + .reg_init_values[MSM_SPM_REG_SAW_SPM_CTL] = 0x68, + .reg_init_values[MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY] = 0x0C0CFFFF, + .reg_init_values[MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY] = 0x78780FFF, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLK_EN] = 0x13, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN] = 0x07, + .reg_init_values[MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN] = 0x00, + + .reg_init_values[MSM_SPM_REG_SAW_SLP_CLMP_EN] = 0x01, + .reg_init_values[MSM_SPM_REG_SAW_SLP_RST_EN] = 0x00, + .reg_init_values[MSM_SPM_REG_SAW_SPM_MPM_CFG] = 0x00, + + .awake_vlevel = 0xA0, + .retention_vlevel = 0x89, + .collapse_vlevel = 0x20, + .retention_mid_vlevel = 0x89, + .collapse_mid_vlevel = 0x89, + + .vctl_timeout_us = 50, + }, +}; + +static struct msm_acpu_clock_platform_data msm8x60_acpu_clock_data = { +}; + +/* + * Consumer specific regulator names: + * regulator name consumer dev_name + */ +static struct regulator_consumer_supply vreg_consumers_8901_S0[] = { + REGULATOR_SUPPLY("8901_s0", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_8901_S1[] = { + REGULATOR_SUPPLY("8901_s1", NULL), +}; + +static struct regulator_init_data saw_s0_init_data = { + .constraints = { + .name = "8901_s0", + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .min_uV = 840000, + .max_uV = 1250000, + }, + .consumer_supplies = vreg_consumers_8901_S0, + .num_consumer_supplies = ARRAY_SIZE(vreg_consumers_8901_S0), +}; + +static struct regulator_init_data saw_s1_init_data = { + .constraints = { + .name = "8901_s1", + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .min_uV = 840000, + .max_uV = 1250000, + }, + .consumer_supplies = vreg_consumers_8901_S1, + .num_consumer_supplies = ARRAY_SIZE(vreg_consumers_8901_S1), +}; + +static struct platform_device msm_device_saw_s0 = { + .name = "saw-regulator", + .id = 0, + .dev = { + .platform_data = &saw_s0_init_data, + }, +}; + +static struct platform_device msm_device_saw_s1 = { + .name = "saw-regulator", + .id = 1, + .dev = { + .platform_data = &saw_s1_init_data, + }, +}; + +/* + * The smc91x configuration varies depending on platform. + * The resources data structure is filled in at runtime. + */ +static struct resource smc91x_resources[] = { + [0] = { + .flags = IORESOURCE_MEM, + }, + [1] = { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device smc91x_device = { + .name = "smc91x", + .id = 0, + .num_resources = ARRAY_SIZE(smc91x_resources), + .resource = smc91x_resources, +}; + +static struct resource smsc911x_resources[] = { + [0] = { + .flags = IORESOURCE_MEM, + .start = 0x1b800000, + .end = 0x1b8000ff + }, + [1] = { + .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_LOWLEVEL, + }, +}; + +static struct smsc911x_platform_config smsc911x_config = { + .irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_LOW, + .irq_type = SMSC911X_IRQ_TYPE_PUSH_PULL, + .flags = SMSC911X_USE_16BIT, + .has_reset_gpio = 1, + .reset_gpio = GPIO_ETHERNET_RESET_N +}; + +static struct platform_device smsc911x_device = { + .name = "smsc911x", + .id = 0, + .num_resources = ARRAY_SIZE(smsc911x_resources), + .resource = smsc911x_resources, + .dev = { + .platform_data = &smsc911x_config + } +}; + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + +#define QCE_SIZE 0x10000 +#define QCE_0_BASE 0x18500000 + +#define QCE_HW_KEY_SUPPORT 0 +#define QCE_SHA_HMAC_SUPPORT 0 +#define QCE_SHARE_CE_RESOURCE 2 +#define QCE_CE_SHARED 1 + +static struct resource qcrypto_resources[] = { + [0] = { + .start = QCE_0_BASE, + .end = QCE_0_BASE + QCE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "crypto_channels", + .start = DMOV_CE_IN_CHAN, + .end = DMOV_CE_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV_CE_IN_CRCI, + .end = DMOV_CE_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV_CE_OUT_CRCI, + .end = DMOV_CE_OUT_CRCI, + .flags = IORESOURCE_DMA, + }, + [4] = { + .name = "crypto_crci_hash", + .start = DMOV_CE_HASH_CRCI, + .end = DMOV_CE_HASH_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource qcedev_resources[] = { + [0] = { + .start = QCE_0_BASE, + .end = QCE_0_BASE + QCE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "crypto_channels", + .start = DMOV_CE_IN_CHAN, + .end = DMOV_CE_OUT_CHAN, + .flags = IORESOURCE_DMA, + }, + [2] = { + .name = "crypto_crci_in", + .start = DMOV_CE_IN_CRCI, + .end = DMOV_CE_IN_CRCI, + .flags = IORESOURCE_DMA, + }, + [3] = { + .name = "crypto_crci_out", + .start = DMOV_CE_OUT_CRCI, + .end = DMOV_CE_OUT_CRCI, + .flags = IORESOURCE_DMA, + }, + [4] = { + .name = "crypto_crci_hash", + .start = DMOV_CE_HASH_CRCI, + .end = DMOV_CE_HASH_CRCI, + .flags = IORESOURCE_DMA, + }, +}; + +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) + +static struct msm_ce_hw_support qcrypto_ce_hw_suppport = { + .ce_shared = QCE_CE_SHARED, + .shared_ce_resource = QCE_SHARE_CE_RESOURCE, + .hw_key_support = QCE_HW_KEY_SUPPORT, + .sha_hmac = QCE_SHA_HMAC_SUPPORT, +}; + +static struct platform_device qcrypto_device = { + .name = "qcrypto", + .id = 0, + .num_resources = ARRAY_SIZE(qcrypto_resources), + .resource = qcrypto_resources, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &qcrypto_ce_hw_suppport, + }, +}; +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + +static struct msm_ce_hw_support qcedev_ce_hw_suppport = { + .ce_shared = QCE_CE_SHARED, + .shared_ce_resource = QCE_SHARE_CE_RESOURCE, + .hw_key_support = QCE_HW_KEY_SUPPORT, + .sha_hmac = QCE_SHA_HMAC_SUPPORT, +}; + +static struct platform_device qcedev_device = { + .name = "qce", + .id = 0, + .num_resources = ARRAY_SIZE(qcedev_resources), + .resource = qcedev_resources, + .dev = { + .coherent_dma_mask = DMA_BIT_MASK(32), + .platform_data = &qcedev_ce_hw_suppport, + }, +}; +#endif + +#if defined(CONFIG_HAPTIC_ISA1200) || \ + defined(CONFIG_HAPTIC_ISA1200_MODULE) + +static const char *vregs_isa1200_name[] = { + "8058_s3", + "8901_l4", +}; + +static const int vregs_isa1200_val[] = { + 1800000,/* uV */ + 2600000, +}; +static struct regulator *vregs_isa1200[ARRAY_SIZE(vregs_isa1200_name)]; +static struct msm_xo_voter *xo_handle_a1; + +static int isa1200_power(int vreg_on) +{ + int i, rc = 0; + + for (i = 0; i < ARRAY_SIZE(vregs_isa1200_name); i++) { + rc = vreg_on ? regulator_enable(vregs_isa1200[i]) : + regulator_disable(vregs_isa1200[i]); + if (rc < 0) { + pr_err("%s: vreg %s %s failed (%d)\n", + __func__, vregs_isa1200_name[i], + vreg_on ? "enable" : "disable", rc); + goto vreg_fail; + } + } + + rc = vreg_on ? msm_xo_mode_vote(xo_handle_a1, MSM_XO_MODE_ON) : + msm_xo_mode_vote(xo_handle_a1, MSM_XO_MODE_OFF); + if (rc < 0) { + pr_err("%s: failed to %svote for TCXO A1 buffer%d\n", + __func__, vreg_on ? "" : "de-", rc); + goto vreg_fail; + } + return 0; + +vreg_fail: + while (i--) + !vreg_on ? regulator_enable(vregs_isa1200[i]) : + regulator_disable(vregs_isa1200[i]); + return rc; +} + +static int isa1200_dev_setup(bool enable) +{ + int i, rc; + + if (enable == true) { + for (i = 0; i < ARRAY_SIZE(vregs_isa1200_name); i++) { + vregs_isa1200[i] = regulator_get(NULL, + vregs_isa1200_name[i]); + if (IS_ERR(vregs_isa1200[i])) { + pr_err("%s: regulator get of %s failed (%ld)\n", + __func__, vregs_isa1200_name[i], + PTR_ERR(vregs_isa1200[i])); + rc = PTR_ERR(vregs_isa1200[i]); + goto vreg_get_fail; + } + rc = regulator_set_voltage(vregs_isa1200[i], + vregs_isa1200_val[i], vregs_isa1200_val[i]); + if (rc) { + pr_err("%s: regulator_set_voltage(%s) failed\n", + __func__, vregs_isa1200_name[i]); + goto vreg_get_fail; + } + } + + rc = gpio_request(GPIO_HAP_SHIFT_LVL_OE, "haptics_shft_lvl_oe"); + if (rc) { + pr_err("%s: unable to request gpio %d (%d)\n", + __func__, GPIO_HAP_SHIFT_LVL_OE, rc); + goto vreg_get_fail; + } + + rc = gpio_direction_output(GPIO_HAP_SHIFT_LVL_OE, 1); + if (rc) { + pr_err("%s: Unable to set direction\n", __func__);; + goto free_gpio; + } + + xo_handle_a1 = msm_xo_get(MSM_XO_TCXO_A1, "isa1200"); + if (IS_ERR(xo_handle_a1)) { + rc = PTR_ERR(xo_handle_a1); + pr_err("%s: failed to get the handle for A1(%d)\n", + __func__, rc); + goto gpio_set_dir; + } + } else { + gpio_set_value(GPIO_HAP_SHIFT_LVL_OE, 0); + gpio_free(GPIO_HAP_SHIFT_LVL_OE); + + for (i = 0; i < ARRAY_SIZE(vregs_isa1200_name); i++) + regulator_put(vregs_isa1200[i]); + + msm_xo_put(xo_handle_a1); + } + + return 0; +gpio_set_dir: + gpio_set_value(GPIO_HAP_SHIFT_LVL_OE, 0); +free_gpio: + gpio_free(GPIO_HAP_SHIFT_LVL_OE); +vreg_get_fail: + while (i) + regulator_put(vregs_isa1200[--i]); + return rc; +} + +#define PMIC_GPIO_HAP_ENABLE 18 /* PMIC GPIO Number 19 */ +static struct isa1200_platform_data isa1200_1_pdata = { + .name = "vibrator", + .power_on = isa1200_power, + .dev_setup = isa1200_dev_setup, + /*gpio to enable haptic*/ + .hap_en_gpio = PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_HAP_ENABLE), + .max_timeout = 15000, + .mode_ctrl = PWM_GEN_MODE, + .pwm_fd = { + .pwm_div = 256, + }, + .is_erm = false, + .smart_en = true, + .ext_clk_en = true, + .chip_en = 1, +}; + +static struct i2c_board_info msm_isa1200_board_info[] = { + { + I2C_BOARD_INFO("isa1200_1", 0x90>>1), + .platform_data = &isa1200_1_pdata, + }, +}; +#endif + +#if defined(CONFIG_BATTERY_BQ27520) || \ + defined(CONFIG_BATTERY_BQ27520_MODULE) +static struct bq27520_platform_data bq27520_pdata = { + .name = "fuel-gauge", + .vreg_name = "8058_s3", + .vreg_value = 1800000, + .soc_int = GPIO_BATT_GAUGE_INT_N, + .bi_tout = GPIO_CAP_GAUGE_BI_TOUT, + .chip_en = GPIO_BATT_GAUGE_EN, + .enable_dlog = 0, /* if enable coulomb counter logger */ +}; + +static struct i2c_board_info msm_bq27520_board_info[] = { + { + I2C_BOARD_INFO("bq27520", 0xaa>>1), + .platform_data = &bq27520_pdata, + }, +}; +#endif + +static struct msm_pm_platform_data msm_pm_data[MSM_PM_SLEEP_MODE_NR * 2] = { + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 4000, + .residency = 13000, + }, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 500, + .residency = 6000, + }, + + [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 2, + .residency = 0, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 600, + .residency = 7200, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 0, + .latency = 500, + .residency = 6000, + }, + + [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 2, + .residency = 0, + }, +}; + +static struct msm_cpuidle_state msm_cstates[] __initdata = { + {0, 0, "C0", "WFI", + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT}, + + {0, 1, "C1", "STANDALONE_POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE}, + + {0, 2, "C2", "POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE}, + + {1, 0, "C0", "WFI", + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT}, + + {1, 1, "C1", "STANDALONE_POWER_COLLAPSE", + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE}, +}; + +static struct msm_rpmrs_level msm_rpmrs_levels[] __initdata = { + { + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1, 8000, 100000, 1, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + true, + 1500, 5000, 60100000, 3000, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, ACTIVE, MAX, ACTIVE), + false, + 1800, 5000, 60350000, 3500, + }, + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, ACTIVE, MAX, ACTIVE), + false, + 3800, 4500, 65350000, 5500, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(ON, HSFS_OPEN, MAX, ACTIVE), + false, + 2800, 2500, 66850000, 4800, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, MAX, ACTIVE), + false, + 4800, 2000, 71850000, 6800, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, ACTIVE, RET_HIGH), + false, + 6800, 500, 75850000, 8800, + }, + + { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_RPMRS_LIMITS(OFF, HSFS_OPEN, RET_HIGH, RET_LOW), + false, + 7800, 0, 76350000, 9800, + }, +}; + +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + +#define ISP1763_INT_GPIO 117 +#define ISP1763_RST_GPIO 152 +static struct resource isp1763_resources[] = { + [0] = { + .flags = IORESOURCE_MEM, + .start = 0x1D000000, + .end = 0x1D005FFF, /* 24KB */ + }, + [1] = { + .flags = IORESOURCE_IRQ, + }, +}; +static void __init msm8x60_cfg_isp1763(void) +{ + isp1763_resources[1].start = gpio_to_irq(ISP1763_INT_GPIO); + isp1763_resources[1].end = gpio_to_irq(ISP1763_INT_GPIO); +} + +static int isp1763_setup_gpio(int enable) +{ + int status = 0; + + if (enable) { + status = gpio_request(ISP1763_INT_GPIO, "isp1763_usb"); + if (status) { + pr_err("%s:Failed to request GPIO %d\n", + __func__, ISP1763_INT_GPIO); + return status; + } + status = gpio_direction_input(ISP1763_INT_GPIO); + if (status) { + pr_err("%s:Failed to configure GPIO %d\n", + __func__, ISP1763_INT_GPIO); + goto gpio_free_int; + } + status = gpio_request(ISP1763_RST_GPIO, "isp1763_usb"); + if (status) { + pr_err("%s:Failed to request GPIO %d\n", + __func__, ISP1763_RST_GPIO); + goto gpio_free_int; + } + status = gpio_direction_output(ISP1763_RST_GPIO, 1); + if (status) { + pr_err("%s:Failed to configure GPIO %d\n", + __func__, ISP1763_RST_GPIO); + goto gpio_free_rst; + } + pr_debug("\nISP GPIO configuration done\n"); + return status; + } + +gpio_free_rst: + gpio_free(ISP1763_RST_GPIO); +gpio_free_int: + gpio_free(ISP1763_INT_GPIO); + + return status; +} +static struct isp1763_platform_data isp1763_pdata = { + .reset_gpio = ISP1763_RST_GPIO, + .setup_gpio = isp1763_setup_gpio +}; + +static struct platform_device isp1763_device = { + .name = "isp1763_usb", + .num_resources = ARRAY_SIZE(isp1763_resources), + .resource = isp1763_resources, + .dev = { + .platform_data = &isp1763_pdata + } +}; +#endif + +#if defined(CONFIG_USB_GADGET_MSM_72K) || defined(CONFIG_USB_EHCI_MSM_72K) +static struct regulator *ldo6_3p3; +static struct regulator *ldo7_1p8; +static struct regulator *vdd_cx; +#define PMICID_INT PM8058_GPIO_IRQ(PM8058_IRQ_BASE, 36) +notify_vbus_state notify_vbus_state_func_ptr; +static int usb_phy_susp_dig_vol = 750000; +static int pmic_id_notif_supported; + +#ifdef CONFIG_USB_EHCI_MSM_72K +#define USB_PMIC_ID_DET_DELAY msecs_to_jiffies(100) +struct delayed_work pmic_id_det; + +static int __init usb_id_pin_rework_setup(char *support) +{ + if (strncmp(support, "true", 4) == 0) + pmic_id_notif_supported = 1; + + return 1; +} +__setup("usb_id_pin_rework=", usb_id_pin_rework_setup); + +static void pmic_id_detect(struct work_struct *w) +{ + int val = gpio_get_value_cansleep(PM8058_GPIO_PM_TO_SYS(36)); + pr_debug("%s(): gpio_read_value = %d\n", __func__, val); + + if (notify_vbus_state_func_ptr) + (*notify_vbus_state_func_ptr) (val); +} + +static irqreturn_t pmic_id_on_irq(int irq, void *data) +{ + /* + * Spurious interrupts are observed on pmic gpio line + * even though there is no state change on USB ID. Schedule the + * work to to allow debounce on gpio + */ + schedule_delayed_work(&pmic_id_det, USB_PMIC_ID_DET_DELAY); + + return IRQ_HANDLED; +} + +static int msm_hsusb_pmic_id_notif_init(void (*callback)(int online), int init) +{ + unsigned ret = -ENODEV; + + if (!callback) + return -EINVAL; + + if (machine_is_msm8x60_fluid()) + return -ENOTSUPP; + + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) != 2) { + pr_debug("%s: USB_ID pin is not routed to PMIC" + "on V1 surf/ffa\n", __func__); + return -ENOTSUPP; + } + + if ((machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) && + !pmic_id_notif_supported) { + pr_debug("%s: USB_ID is not routed to PMIC" + "on V2 ffa\n", __func__); + return -ENOTSUPP; + } + + usb_phy_susp_dig_vol = 500000; + + if (init) { + notify_vbus_state_func_ptr = callback; + ret = pm8901_mpp_config_digital_out(1, + PM8901_MPP_DIG_LEVEL_L5, 1); + if (ret) { + pr_err("%s: MPP2 configuration failed\n", __func__); + return -ENODEV; + } + INIT_DELAYED_WORK(&pmic_id_det, pmic_id_detect); + ret = request_threaded_irq(PMICID_INT, NULL, pmic_id_on_irq, + (IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING), + "msm_otg_id", NULL); + if (ret) { + pm8901_mpp_config_digital_out(1, + PM8901_MPP_DIG_LEVEL_L5, 0); + pr_err("%s:pmic_usb_id interrupt registration failed", + __func__); + return ret; + } + /* Notify the initial Id status */ + pmic_id_detect(&pmic_id_det.work); + } else { + free_irq(PMICID_INT, 0); + cancel_delayed_work_sync(&pmic_id_det); + notify_vbus_state_func_ptr = NULL; + ret = pm8901_mpp_config_digital_out(1, + PM8901_MPP_DIG_LEVEL_L5, 0); + if (ret) { + pr_err("%s:MPP2 configuration failed\n", __func__); + return -ENODEV; + } + } + return 0; +} +#endif + +#define USB_PHY_OPERATIONAL_MIN_VDD_DIG_VOL 1000000 +#define USB_PHY_MAX_VDD_DIG_VOL 1320000 +static int msm_hsusb_init_vddcx(int init) +{ + int ret = 0; + + if (init) { + vdd_cx = regulator_get(NULL, "8058_s1"); + if (IS_ERR(vdd_cx)) { + return PTR_ERR(vdd_cx); + } + + ret = regulator_set_voltage(vdd_cx, + USB_PHY_OPERATIONAL_MIN_VDD_DIG_VOL, + USB_PHY_MAX_VDD_DIG_VOL); + if (ret) { + pr_err("%s: unable to set the voltage for regulator" + "vdd_cx\n", __func__); + regulator_put(vdd_cx); + return ret; + } + + ret = regulator_enable(vdd_cx); + if (ret) { + pr_err("%s: unable to enable regulator" + "vdd_cx\n", __func__); + regulator_put(vdd_cx); + } + } else { + ret = regulator_disable(vdd_cx); + if (ret) { + pr_err("%s: Unable to disable the regulator:" + "vdd_cx\n", __func__); + return ret; + } + + regulator_put(vdd_cx); + } + + return ret; +} + +static int msm_hsusb_config_vddcx(int high) +{ + int max_vol = USB_PHY_MAX_VDD_DIG_VOL; + int min_vol; + int ret; + + if (high) + min_vol = USB_PHY_OPERATIONAL_MIN_VDD_DIG_VOL; + else + min_vol = usb_phy_susp_dig_vol; + + ret = regulator_set_voltage(vdd_cx, min_vol, max_vol); + if (ret) { + pr_err("%s: unable to set the voltage for regulator" + "vdd_cx\n", __func__); + return ret; + } + + pr_debug("%s: min_vol:%d max_vol:%d\n", __func__, min_vol, max_vol); + + return ret; +} + +#define USB_PHY_3P3_VOL_MIN 3050000 /* uV */ +#define USB_PHY_3P3_VOL_MAX 3050000 /* uV */ +#define USB_PHY_3P3_HPM_LOAD 50000 /* uA */ +#define USB_PHY_3P3_LPM_LOAD 4000 /* uA */ + +#define USB_PHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_PHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_PHY_1P8_HPM_LOAD 50000 /* uA */ +#define USB_PHY_1P8_LPM_LOAD 4000 /* uA */ +static int msm_hsusb_ldo_init(int init) +{ + int rc = 0; + + if (init) { + ldo6_3p3 = regulator_get(NULL, "8058_l6"); + if (IS_ERR(ldo6_3p3)) + return PTR_ERR(ldo6_3p3); + + ldo7_1p8 = regulator_get(NULL, "8058_l7"); + if (IS_ERR(ldo7_1p8)) { + rc = PTR_ERR(ldo7_1p8); + goto put_3p3; + } + + rc = regulator_set_voltage(ldo6_3p3, USB_PHY_3P3_VOL_MIN, + USB_PHY_3P3_VOL_MAX); + if (rc) { + pr_err("%s: Unable to set voltage level for" + "ldo6_3p3 regulator\n", __func__); + goto put_1p8; + } + rc = regulator_enable(ldo6_3p3); + if (rc) { + pr_err("%s: Unable to enable the regulator:" + "ldo6_3p3\n", __func__); + goto put_1p8; + } + rc = regulator_set_voltage(ldo7_1p8, USB_PHY_1P8_VOL_MIN, + USB_PHY_1P8_VOL_MAX); + if (rc) { + pr_err("%s: Unable to set voltage level for" + "ldo7_1p8 regulator\n", __func__); + goto disable_3p3; + } + rc = regulator_enable(ldo7_1p8); + if (rc) { + pr_err("%s: Unable to enable the regulator:" + "ldo7_1p8\n", __func__); + goto disable_3p3; + } + + return 0; + } + + regulator_disable(ldo7_1p8); +disable_3p3: + regulator_disable(ldo6_3p3); +put_1p8: + regulator_put(ldo7_1p8); +put_3p3: + regulator_put(ldo6_3p3); + return rc; +} + +static int msm_hsusb_ldo_enable(int on) +{ + int ret = 0; + + if (!ldo7_1p8 || IS_ERR(ldo7_1p8)) { + pr_err("%s: ldo7_1p8 is not initialized\n", __func__); + return -ENODEV; + } + + if (!ldo6_3p3 || IS_ERR(ldo6_3p3)) { + pr_err("%s: ldo6_3p3 is not initialized\n", __func__); + return -ENODEV; + } + + if (on) { + ret = regulator_set_optimum_mode(ldo7_1p8, + USB_PHY_1P8_HPM_LOAD); + if (ret < 0) { + pr_err("%s: Unable to set HPM of the regulator:" + "ldo7_1p8\n", __func__); + return ret; + } + ret = regulator_set_optimum_mode(ldo6_3p3, + USB_PHY_3P3_HPM_LOAD); + if (ret < 0) { + pr_err("%s: Unable to set HPM of the regulator:" + "ldo6_3p3\n", __func__); + regulator_set_optimum_mode(ldo7_1p8, + USB_PHY_1P8_LPM_LOAD); + return ret; + } + } else { + ret = regulator_set_optimum_mode(ldo7_1p8, + USB_PHY_1P8_LPM_LOAD); + if (ret < 0) + pr_err("%s: Unable to set LPM of the regulator:" + "ldo7_1p8\n", __func__); + ret = regulator_set_optimum_mode(ldo6_3p3, + USB_PHY_3P3_LPM_LOAD); + if (ret < 0) + pr_err("%s: Unable to set LPM of the regulator:" + "ldo6_3p3\n", __func__); + } + + pr_debug("reg (%s)\n", on ? "HPM" : "LPM"); + return ret < 0 ? ret : 0; + } +#endif +#ifdef CONFIG_USB_EHCI_MSM_72K +#if defined(CONFIG_SMB137B_CHARGER) || defined(CONFIG_SMB137B_CHARGER_MODULE) +static void msm_hsusb_smb137b_vbus_power(unsigned phy_info, int on) +{ + static int vbus_is_on; + + /* If VBUS is already on (or off), do nothing. */ + if (on == vbus_is_on) + return; + smb137b_otg_power(on); + vbus_is_on = on; +} +#endif +static void msm_hsusb_vbus_power(unsigned phy_info, int on) +{ + static struct regulator *votg_5v_switch; + static struct regulator *ext_5v_reg; + static int vbus_is_on; + + /* If VBUS is already on (or off), do nothing. */ + if (on == vbus_is_on) + return; + + if (!votg_5v_switch) { + votg_5v_switch = regulator_get(NULL, "8901_usb_otg"); + if (IS_ERR(votg_5v_switch)) { + pr_err("%s: unable to get votg_5v_switch\n", __func__); + return; + } + } + if (!ext_5v_reg) { + ext_5v_reg = regulator_get(NULL, "8901_mpp0"); + if (IS_ERR(ext_5v_reg)) { + pr_err("%s: unable to get ext_5v_reg\n", __func__); + return; + } + } + if (on) { + if (regulator_enable(ext_5v_reg)) { + pr_err("%s: Unable to enable the regulator:" + " ext_5v_reg\n", __func__); + return; + } + if (regulator_enable(votg_5v_switch)) { + pr_err("%s: Unable to enable the regulator:" + " votg_5v_switch\n", __func__); + return; + } + } else { + if (regulator_disable(votg_5v_switch)) + pr_err("%s: Unable to enable the regulator:" + " votg_5v_switch\n", __func__); + if (regulator_disable(ext_5v_reg)) + pr_err("%s: Unable to enable the regulator:" + " ext_5v_reg\n", __func__); + } + + vbus_is_on = on; +} + +static struct msm_usb_host_platform_data msm_usb_host_pdata = { + .phy_info = (USB_PHY_INTEGRATED | USB_PHY_MODEL_45NM), + .power_budget = 390, +}; +#endif + +#ifdef CONFIG_BATTERY_MSM8X60 +static int msm_hsusb_pmic_vbus_notif_init(void (*callback)(int online), + int init) +{ + int ret = -ENOTSUPP; + +#if defined(CONFIG_SMB137B_CHARGER) || defined(CONFIG_SMB137B_CHARGER_MODULE) + if (machine_is_msm8x60_fluid()) { + if (init) + msm_charger_register_vbus_sn(callback); + else + msm_charger_unregister_vbus_sn(callback); + return 0; + } +#endif + /* ID and VBUS lines are connected to pmic on 8660.V2.SURF, + * hence, irrespective of either peripheral only mode or + * OTG (host and peripheral) modes, can depend on pmic for + * vbus notifications + */ + if ((SOCINFO_VERSION_MAJOR(socinfo_get_version()) == 2) + && (machine_is_msm8x60_surf() || + pmic_id_notif_supported)) { + if (init) + ret = msm_charger_register_vbus_sn(callback); + else { + msm_charger_unregister_vbus_sn(callback); + ret = 0; + } + } else { +#if !defined(CONFIG_USB_EHCI_MSM_72K) + if (init) + ret = msm_charger_register_vbus_sn(callback); + else { + msm_charger_unregister_vbus_sn(callback); + ret = 0; + } +#endif + } + return ret; +} +#endif + +#if defined(CONFIG_USB_GADGET_MSM_72K) || defined(CONFIG_USB_EHCI_MSM_72K) +static struct msm_otg_platform_data msm_otg_pdata = { + /* if usb link is in sps there is no need for + * usb pclk as dayatona fabric clock will be + * used instead + */ + .pclk_src_name = "dfab_usb_hs_clk", + .pemp_level = PRE_EMPHASIS_WITH_20_PERCENT, + .cdr_autoreset = CDR_AUTO_RESET_DISABLE, + .se1_gating = SE1_GATING_DISABLE, +#ifdef CONFIG_USB_EHCI_MSM_72K + .pmic_id_notif_init = msm_hsusb_pmic_id_notif_init, +#endif +#ifdef CONFIG_USB_EHCI_MSM_72K + .vbus_power = msm_hsusb_vbus_power, +#endif +#ifdef CONFIG_BATTERY_MSM8X60 + .pmic_vbus_notif_init = msm_hsusb_pmic_vbus_notif_init, +#endif + .ldo_init = msm_hsusb_ldo_init, + .ldo_enable = msm_hsusb_ldo_enable, + .config_vddcx = msm_hsusb_config_vddcx, + .init_vddcx = msm_hsusb_init_vddcx, +#ifdef CONFIG_BATTERY_MSM8X60 + .chg_vbus_draw = msm_charger_vbus_draw, +#endif +}; +#endif + +#ifdef CONFIG_USB_GADGET_MSM_72K +static struct msm_hsusb_gadget_platform_data msm_gadget_pdata = { + .is_phy_status_timer_on = 1, +}; +#endif + +#ifdef CONFIG_USB_G_ANDROID + +#define PID_MAGIC_ID 0x71432909 +#define SERIAL_NUM_MAGIC_ID 0x61945374 +#define SERIAL_NUMBER_LENGTH 127 +#define DLOAD_USB_BASE_ADD 0x2A05F0C8 + +struct magic_num_struct { + uint32_t pid; + uint32_t serial_num; +}; + +struct dload_struct { + uint32_t reserved1; + uint32_t reserved2; + uint32_t reserved3; + uint16_t reserved4; + uint16_t pid; + char serial_number[SERIAL_NUMBER_LENGTH]; + uint16_t reserved5; + struct magic_num_struct + magic_struct; +}; + +static int usb_diag_update_pid_and_serial_num(uint32_t pid, const char *snum) +{ + struct dload_struct __iomem *dload = 0; + + dload = ioremap(DLOAD_USB_BASE_ADD, sizeof(*dload)); + if (!dload) { + pr_err("%s: cannot remap I/O memory region: %08x\n", + __func__, DLOAD_USB_BASE_ADD); + return -ENXIO; + } + + pr_debug("%s: dload:%p pid:%x serial_num:%s\n", + __func__, dload, pid, snum); + /* update pid */ + dload->magic_struct.pid = PID_MAGIC_ID; + dload->pid = pid; + + /* update serial number */ + dload->magic_struct.serial_num = 0; + if (!snum) + return 0; + + dload->magic_struct.serial_num = SERIAL_NUM_MAGIC_ID; + strncpy(dload->serial_number, snum, SERIAL_NUMBER_LENGTH); + dload->serial_number[SERIAL_NUMBER_LENGTH - 1] = '\0'; + + iounmap(dload); + + return 0; +} + +static struct android_usb_platform_data android_usb_pdata = { + .update_pid_and_serial_num = usb_diag_update_pid_and_serial_num, +}; + +static struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &android_usb_pdata, + }, +}; + + +#endif + +#ifdef CONFIG_MSM_VPE +static struct resource msm_vpe_resources[] = { + { + .start = 0x05300000, + .end = 0x05300000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_VPE, + .end = INT_VPE, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_vpe_device = { + .name = "msm_vpe", + .id = 0, + .num_resources = ARRAY_SIZE(msm_vpe_resources), + .resource = msm_vpe_resources, +}; +#endif + +#ifdef CONFIG_MSM_CAMERA +#ifdef CONFIG_MSM_CAMERA_FLASH +#define VFE_CAMIF_TIMER1_GPIO 29 +#define VFE_CAMIF_TIMER2_GPIO 30 +#define VFE_CAMIF_TIMER3_GPIO_INT 31 +#define FUSION_VFE_CAMIF_TIMER1_GPIO 42 +static struct msm_camera_sensor_flash_src msm_flash_src = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_PMIC, + ._fsrc.pmic_src.num_of_src = 2, + ._fsrc.pmic_src.low_current = 100, + ._fsrc.pmic_src.high_current = 300, + ._fsrc.pmic_src.led_src_1 = PMIC8058_ID_FLASH_LED_0, + ._fsrc.pmic_src.led_src_2 = PMIC8058_ID_FLASH_LED_1, + ._fsrc.pmic_src.pmic_set_current = pm8058_set_flash_led_current, +}; +#ifdef CONFIG_IMX074 +static struct msm_camera_sensor_strobe_flash_data strobe_flash_xenon = { + .flash_trigger = VFE_CAMIF_TIMER2_GPIO, + .flash_charge = VFE_CAMIF_TIMER1_GPIO, + .flash_charge_done = VFE_CAMIF_TIMER3_GPIO_INT, + .flash_recharge_duration = 50000, + .irq = MSM_GPIO_TO_INT(VFE_CAMIF_TIMER3_GPIO_INT), +}; +#endif +#endif + +int msm_cam_gpio_tbl[] = { + 32,/*CAMIF_MCLK*/ + 47,/*CAMIF_I2C_DATA*/ + 48,/*CAMIF_I2C_CLK*/ + 105,/*STANDBY*/ +}; + +enum msm_cam_stat{ + MSM_CAM_OFF, + MSM_CAM_ON, +}; + +static int config_gpio_table(enum msm_cam_stat stat) +{ + int rc = 0, i = 0; + if (stat == MSM_CAM_ON) { + for (i = 0; i < ARRAY_SIZE(msm_cam_gpio_tbl); i++) { + rc = gpio_request(msm_cam_gpio_tbl[i], "CAM_GPIO"); + if (unlikely(rc < 0)) { + pr_err("%s not able to get gpio\n", __func__); + for (i--; i >= 0; i--) + gpio_free(msm_cam_gpio_tbl[i]); + break; + } + } + } else { + for (i = 0; i < ARRAY_SIZE(msm_cam_gpio_tbl); i++) + gpio_free(msm_cam_gpio_tbl[i]); + } + return rc; +} + +static struct msm_camera_sensor_platform_info sensor_board_info = { + .mount_angle = 0 +}; + +/*external regulator VREG_5V*/ +static struct regulator *reg_flash_5V; + +static int config_camera_on_gpios_fluid(void) +{ + int rc = 0; + + reg_flash_5V = regulator_get(NULL, "8901_mpp0"); + if (IS_ERR(reg_flash_5V)) { + pr_err("'%s' regulator not found, rc=%ld\n", + "8901_mpp0", IS_ERR(reg_flash_5V)); + return -ENODEV; + } + + rc = regulator_enable(reg_flash_5V); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "8901_mpp0", rc); + regulator_put(reg_flash_5V); + return rc; + } + +#ifdef CONFIG_IMX074 + sensor_board_info.mount_angle = 90; +#endif + rc = config_gpio_table(MSM_CAM_ON); + if (rc < 0) { + printk(KERN_ERR "%s: CAMSENSOR gpio table request" + "failed\n", __func__); + return rc; + } + + rc = gpio_request(GPIO_EXT_CAMIF_PWR_EN, "CAM_EN"); + if (rc < 0) { + printk(KERN_ERR "%s: CAMSENSOR gpio %d request" + "failed\n", __func__, GPIO_EXT_CAMIF_PWR_EN); + regulator_disable(reg_flash_5V); + regulator_put(reg_flash_5V); + return rc; + } + gpio_direction_output(GPIO_EXT_CAMIF_PWR_EN, 0); + msleep(20); + gpio_set_value_cansleep(GPIO_EXT_CAMIF_PWR_EN, 1); + + + /*Enable LED_FLASH_EN*/ + rc = gpio_request(GPIO_LED_FLASH_EN, "LED_FLASH_EN"); + if (rc < 0) { + printk(KERN_ERR "%s: CAMSENSOR gpio %d request" + "failed\n", __func__, GPIO_LED_FLASH_EN); + + regulator_disable(reg_flash_5V); + regulator_put(reg_flash_5V); + config_gpio_table(MSM_CAM_OFF); + gpio_set_value_cansleep(GPIO_EXT_CAMIF_PWR_EN, 0); + gpio_free(GPIO_EXT_CAMIF_PWR_EN); + return rc; + } + gpio_direction_output(GPIO_LED_FLASH_EN, 1); + msleep(20); + return rc; +} + + +static void config_camera_off_gpios_fluid(void) +{ + regulator_disable(reg_flash_5V); + regulator_put(reg_flash_5V); + + gpio_direction_output(GPIO_LED_FLASH_EN, 0); + gpio_free(GPIO_LED_FLASH_EN); + + config_gpio_table(MSM_CAM_OFF); + + gpio_set_value_cansleep(GPIO_EXT_CAMIF_PWR_EN, 0); + gpio_free(GPIO_EXT_CAMIF_PWR_EN); +} +static int config_camera_on_gpios(void) +{ + int rc = 0; + + if (machine_is_msm8x60_fluid()) + return config_camera_on_gpios_fluid(); + + rc = config_gpio_table(MSM_CAM_ON); + if (rc < 0) { + printk(KERN_ERR "%s: CAMSENSOR gpio table request" + "failed\n", __func__); + return rc; + } + + rc = gpio_request(GPIO_EXT_CAMIF_PWR_EN, "CAM_EN"); + if (rc < 0) { + config_gpio_table(MSM_CAM_OFF); + printk(KERN_ERR "%s: CAMSENSOR gpio %d request" + "failed\n", __func__, GPIO_EXT_CAMIF_PWR_EN); + return rc; + } + gpio_direction_output(GPIO_EXT_CAMIF_PWR_EN, 0); + mdelay(20); + gpio_set_value_cansleep(GPIO_EXT_CAMIF_PWR_EN, 1); + +#ifdef CONFIG_MSM_CAMERA_FLASH +#ifdef CONFIG_IMX074 + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) + strobe_flash_xenon.flash_charge = FUSION_VFE_CAMIF_TIMER1_GPIO; +#endif +#endif + return rc; +} + +static void config_camera_off_gpios(void) +{ + if (machine_is_msm8x60_fluid()) + return config_camera_off_gpios_fluid(); + + + config_gpio_table(MSM_CAM_OFF); + + gpio_set_value_cansleep(GPIO_EXT_CAMIF_PWR_EN, 0); + gpio_free(GPIO_EXT_CAMIF_PWR_EN); +} + +#ifdef CONFIG_QS_S5K4E1 + +#define QS_CAM_HC37_CAM_PD PM8058_GPIO_PM_TO_SYS(26) + +static int config_camera_on_gpios_qs_cam_fluid(void) +{ + int rc = 0; + + /* request QS_CAM_HC37_CAM_PD as an output to HC37 ASIC pin CAM_PD */ + rc = gpio_request(QS_CAM_HC37_CAM_PD, "QS_CAM_HC37_CAM_PD"); + if (rc < 0) { + printk(KERN_ERR "%s: QS_CAM_HC37_CAM_PD gpio %d request" + " failed\n", __func__, QS_CAM_HC37_CAM_PD); + return rc; + } + gpio_direction_output(QS_CAM_HC37_CAM_PD, 0); + msleep(20); + gpio_set_value_cansleep(QS_CAM_HC37_CAM_PD, 1); + msleep(20); + + /* + * Set GPIO_AUX_CAM_2P7_EN to 1 on North Expander IO2 + * to enable 2.7V power to Camera + */ + rc = gpio_request(GPIO_AUX_CAM_2P7_EN, "CAM_2P7_EN"); + if (rc < 0) { + printk(KERN_ERR "%s: CAMSENSOR gpio %d request" + " failed\n", __func__, GPIO_AUX_CAM_2P7_EN); + gpio_set_value_cansleep(QS_CAM_HC37_CAM_PD, 0); + gpio_free(QS_CAM_HC37_CAM_PD); + return rc; + } + gpio_direction_output(GPIO_AUX_CAM_2P7_EN, 0); + msleep(20); + gpio_set_value_cansleep(GPIO_AUX_CAM_2P7_EN, 1); + msleep(20); + + rc = config_camera_on_gpios_fluid(); + if (rc < 0) { + printk(KERN_ERR "%s: config_camera_on_gpios_fluid" + " failed\n", __func__); + gpio_set_value_cansleep(QS_CAM_HC37_CAM_PD, 0); + gpio_free(QS_CAM_HC37_CAM_PD); + gpio_set_value_cansleep(GPIO_AUX_CAM_2P7_EN, 0); + gpio_free(GPIO_AUX_CAM_2P7_EN); + return rc; + } + return rc; +} + +static void config_camera_off_gpios_qs_cam_fluid(void) +{ + /* + * Set GPIO_AUX_CAM_2P7_EN to 0 on North Expander IO2 + * to disable 2.7V power to Camera + */ + gpio_set_value_cansleep(GPIO_AUX_CAM_2P7_EN, 0); + gpio_free(GPIO_AUX_CAM_2P7_EN); + + /* set QS_CAM_HC37_CAM_PD to 0 to power off HC37 ASIC*/ + gpio_set_value_cansleep(QS_CAM_HC37_CAM_PD, 0); + gpio_free(QS_CAM_HC37_CAM_PD); + + config_camera_off_gpios_fluid(); + return; +} + +static int config_camera_on_gpios_qs_cam(void) +{ + int rc = 0; + + if (machine_is_msm8x60_fluid()) + return config_camera_on_gpios_qs_cam_fluid(); + + rc = config_camera_on_gpios(); + return rc; +} + +static void config_camera_off_gpios_qs_cam(void) +{ + if (machine_is_msm8x60_fluid()) + return config_camera_off_gpios_qs_cam_fluid(); + + config_camera_off_gpios(); + return; +} +#endif + +static int config_camera_on_gpios_web_cam(void) +{ + int rc = 0; + rc = config_gpio_table(MSM_CAM_ON); + if (rc < 0) { + printk(KERN_ERR "%s: CAMSENSOR gpio table request" + "failed\n", __func__); + return rc; + } + + if (!machine_is_msm8x60_fluid()) { + rc = gpio_request(GPIO_WEB_CAMIF_STANDBY, "CAM_EN"); + if (rc < 0) { + config_gpio_table(MSM_CAM_OFF); + pr_err(KERN_ERR "%s: CAMSENSOR gpio %d request" + "failed\n", __func__, GPIO_WEB_CAMIF_STANDBY); + return rc; + } + gpio_direction_output(GPIO_WEB_CAMIF_STANDBY, 0); + } + return rc; +} + +static void config_camera_off_gpios_web_cam(void) +{ + config_gpio_table(MSM_CAM_OFF); + if (!machine_is_msm8x60_fluid()) { + gpio_set_value_cansleep(GPIO_WEB_CAMIF_STANDBY, 1); + gpio_free(GPIO_WEB_CAMIF_STANDBY); + } + return; +} + +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors cam_init_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_preview_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 283115520, + .ib = 452984832, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_video_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 283115520, + .ib = 452984832, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 283115520, + .ib = 452984832, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 319610880, + .ib = 511377408, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_snapshot_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 566231040, + .ib = 905969664, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 69984000, + .ib = 111974400, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 320864256, + .ib = 513382810, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 320864256, + .ib = 513382810, + }, +}; + +static struct msm_bus_vectors cam_zsl_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 566231040, + .ib = 905969664, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 706199040, + .ib = 1129918464, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 320864256, + .ib = 513382810, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 320864256, + .ib = 513382810, + }, +}; + +static struct msm_bus_vectors cam_stereo_video_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 212336640, + .ib = 339738624, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 25090560, + .ib = 40144896, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 239708160, + .ib = 383533056, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 79902720, + .ib = 127844352, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_stereo_snapshot_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 300902400, + .ib = 481443840, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 230307840, + .ib = 368492544, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 245113344, + .ib = 392181351, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 106536960, + .ib = 170459136, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 106536960, + .ib = 170459136, + }, +}; + +static struct msm_bus_paths cam_bus_client_config[] = { + { + ARRAY_SIZE(cam_init_vectors), + cam_init_vectors, + }, + { + ARRAY_SIZE(cam_preview_vectors), + cam_preview_vectors, + }, + { + ARRAY_SIZE(cam_video_vectors), + cam_video_vectors, + }, + { + ARRAY_SIZE(cam_snapshot_vectors), + cam_snapshot_vectors, + }, + { + ARRAY_SIZE(cam_zsl_vectors), + cam_zsl_vectors, + }, + { + ARRAY_SIZE(cam_stereo_video_vectors), + cam_stereo_video_vectors, + }, + { + ARRAY_SIZE(cam_stereo_snapshot_vectors), + cam_stereo_snapshot_vectors, + }, +}; + +static struct msm_bus_scale_pdata cam_bus_client_pdata = { + cam_bus_client_config, + ARRAY_SIZE(cam_bus_client_config), + .name = "msm_camera", +}; +#endif + +struct msm_camera_device_platform_data msm_camera_device_data = { + .camera_gpio_on = config_camera_on_gpios, + .camera_gpio_off = config_camera_off_gpios, + .ioext.csiphy = 0x04800000, + .ioext.csisz = 0x00000400, + .ioext.csiirq = CSI_0_IRQ, + .ioclk.mclk_clk_rate = 24000000, + .ioclk.vfe_clk_rate = 228570000, +#ifdef CONFIG_MSM_BUS_SCALING + .cam_bus_scale_table = &cam_bus_client_pdata, +#endif +}; + +#ifdef CONFIG_QS_S5K4E1 +struct msm_camera_device_platform_data msm_camera_device_data_qs_cam = { + .camera_gpio_on = config_camera_on_gpios_qs_cam, + .camera_gpio_off = config_camera_off_gpios_qs_cam, + .ioext.csiphy = 0x04800000, + .ioext.csisz = 0x00000400, + .ioext.csiirq = CSI_0_IRQ, + .ioclk.mclk_clk_rate = 24000000, + .ioclk.vfe_clk_rate = 228570000, +#ifdef CONFIG_MSM_BUS_SCALING + .cam_bus_scale_table = &cam_bus_client_pdata, +#endif +}; +#endif + +struct msm_camera_device_platform_data msm_camera_device_data_web_cam = { + .camera_gpio_on = config_camera_on_gpios_web_cam, + .camera_gpio_off = config_camera_off_gpios_web_cam, + .ioext.csiphy = 0x04900000, + .ioext.csisz = 0x00000400, + .ioext.csiirq = CSI_1_IRQ, + .ioclk.mclk_clk_rate = 24000000, + .ioclk.vfe_clk_rate = 228570000, +#ifdef CONFIG_MSM_BUS_SCALING + .cam_bus_scale_table = &cam_bus_client_pdata, +#endif +}; + +struct resource msm_camera_resources[] = { + { + .start = 0x04500000, + .end = 0x04500000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = VFE_IRQ, + .end = VFE_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; +#ifdef CONFIG_MT9E013 +static struct msm_camera_sensor_platform_info mt9e013_sensor_8660_info = { + .mount_angle = 0 +}; + +static struct msm_camera_sensor_flash_data flash_mt9e013 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9e013_data = { + .sensor_name = "mt9e013", + .sensor_reset = 106, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_mt9e013, + .strobe_flash_data = &strobe_flash_xenon, + .sensor_platform_info = &mt9e013_sensor_8660_info, + .csi_if = 1 +}; +struct platform_device msm_camera_sensor_mt9e013 = { + .name = "msm_camera_mt9e013", + .dev = { + .platform_data = &msm_camera_sensor_mt9e013_data, + }, +}; +#endif + +#ifdef CONFIG_IMX074 +static struct msm_camera_sensor_flash_data flash_imx074 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_imx074_data = { + .sensor_name = "imx074", + .sensor_reset = 106, + .sensor_pwd = 85, + .vcm_pwd = GPIO_AUX_CAM_2P7_EN, + .vcm_enable = 1, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_imx074, + .strobe_flash_data = &strobe_flash_xenon, + .sensor_platform_info = &sensor_board_info, + .csi_if = 1 +}; +struct platform_device msm_camera_sensor_imx074 = { + .name = "msm_camera_imx074", + .dev = { + .platform_data = &msm_camera_sensor_imx074_data, + }, +}; +#endif +#ifdef CONFIG_WEBCAM_OV9726 + +static struct msm_camera_sensor_platform_info ov9726_sensor_8660_info = { + .mount_angle = 0 +}; + +static struct msm_camera_sensor_flash_data flash_ov9726 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; +static struct msm_camera_sensor_info msm_camera_sensor_ov9726_data = { + .sensor_name = "ov9726", + .sensor_reset = GPIO_FRONT_CAM_RESET_N, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data_web_cam, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_ov9726, + .sensor_platform_info = &ov9726_sensor_8660_info, + .csi_if = 1 +}; +struct platform_device msm_camera_sensor_webcam_ov9726 = { + .name = "msm_camera_ov9726", + .dev = { + .platform_data = &msm_camera_sensor_ov9726_data, + }, +}; +#endif +#ifdef CONFIG_WEBCAM_OV7692 +static struct msm_camera_sensor_flash_data flash_ov7692 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; +static struct msm_camera_sensor_info msm_camera_sensor_ov7692_data = { + .sensor_name = "ov7692", + .sensor_reset = GPIO_WEB_CAMIF_RESET_N, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data_web_cam, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_ov7692, + .csi_if = 1 +}; + +static struct platform_device msm_camera_sensor_webcam_ov7692 = { + .name = "msm_camera_ov7692", + .dev = { + .platform_data = &msm_camera_sensor_ov7692_data, + }, +}; +#endif +#ifdef CONFIG_QS_S5K4E1 + +static char eeprom_data[864]; +static struct msm_camera_sensor_flash_data flash_qs_s5k4e1 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_qs_s5k4e1_data = { + .sensor_name = "qs_s5k4e1", + .sensor_reset = 106, + .sensor_pwd = 85, + .vcm_pwd = 1, + .vcm_enable = 0, + .pdata = &msm_camera_device_data_qs_cam, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_qs_s5k4e1, + .strobe_flash_data = &strobe_flash_xenon, + .csi_if = 1, + .eeprom_data = eeprom_data, +}; +struct platform_device msm_camera_sensor_qs_s5k4e1 = { + .name = "msm_camera_qs_s5k4e1", + .dev = { + .platform_data = &msm_camera_sensor_qs_s5k4e1_data, + }, +}; +#endif +static struct i2c_board_info msm_camera_boardinfo[] __initdata = { + #ifdef CONFIG_MT9E013 + { + I2C_BOARD_INFO("mt9e013", 0x6C >> 2), + }, + #endif + #ifdef CONFIG_IMX074 + { + I2C_BOARD_INFO("imx074", 0x1A), + }, + #endif + #ifdef CONFIG_WEBCAM_OV7692 + { + I2C_BOARD_INFO("ov7692", 0x78), + }, + #endif + #ifdef CONFIG_WEBCAM_OV9726 + { + I2C_BOARD_INFO("ov9726", 0x10), + }, + #endif + #ifdef CONFIG_QS_S5K4E1 + { + I2C_BOARD_INFO("qs_s5k4e1", 0x20), + }, + #endif +}; +#endif + +#ifdef CONFIG_MSM_GEMINI +static struct resource msm_gemini_resources[] = { + { + .start = 0x04600000, + .end = 0x04600000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_JPEG, + .end = INT_JPEG, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_gemini_device = { + .name = "msm_gemini", + .resource = msm_gemini_resources, + .num_resources = ARRAY_SIZE(msm_gemini_resources), +}; +#endif + +#ifdef CONFIG_I2C_QUP +static void gsbi_qup_i2c_gpio_config(int adap_id, int config_type) +{ +} + +static struct msm_i2c_platform_data msm_gsbi3_qup_i2c_pdata = { + .clk_freq = 384000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm_gsbi4_qup_i2c_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm_gsbi7_qup_i2c_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm_gsbi8_qup_i2c_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm_gsbi9_qup_i2c_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; + +static struct msm_i2c_platform_data msm_gsbi12_qup_i2c_pdata = { + .clk_freq = 100000, + .src_clk_rate = 24000000, + .clk = "gsbi_qup_clk", + .pclk = "gsbi_pclk", + .use_gsbi_shared_mode = 1, + .msm_i2c_config_gpio = gsbi_qup_i2c_gpio_config, +}; +#endif + +#if defined(CONFIG_SPI_QUP) || defined(CONFIG_SPI_QUP_MODULE) +static struct msm_spi_platform_data msm_gsbi1_qup_spi_pdata = { + .max_clock_speed = 24000000, +}; + +static struct msm_spi_platform_data msm_gsbi10_qup_spi_pdata = { + .max_clock_speed = 24000000, +}; +#endif + +#ifdef CONFIG_I2C_SSBI +/* PMIC SSBI */ +static struct msm_i2c_ssbi_platform_data msm_ssbi1_pdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, +}; + +/* PMIC SSBI */ +static struct msm_i2c_ssbi_platform_data msm_ssbi2_pdata = { + .controller_type = MSM_SBI_CTRL_PMIC_ARBITER, +}; + +/* CODEC/TSSC SSBI */ +static struct msm_i2c_ssbi_platform_data msm_ssbi3_pdata = { + .controller_type = MSM_SBI_CTRL_SSBI, +}; +#endif + +#ifdef CONFIG_BATTERY_MSM +/* Use basic value for fake MSM battery */ +static struct msm_psy_batt_pdata msm_psy_batt_data = { + .avail_chg_sources = AC_CHG, +}; + +static struct platform_device msm_batt_device = { + .name = "msm-battery", + .id = -1, + .dev.platform_data = &msm_psy_batt_data, +}; +#endif + +#ifdef CONFIG_FB_MSM_LCDC_DSUB +/* VGA = 1440 x 900 x 4(bpp) x 2(pages) + prim = 1024 x 600 x 4(bpp) x 2(pages) + This is the difference. */ +#define MSM_FB_DSUB_PMEM_ADDER (0xA32000-0x4B0000) +#else +#define MSM_FB_DSUB_PMEM_ADDER (0) +#endif + +/* Sensors DSPS platform data */ +#ifdef CONFIG_MSM_DSPS + +static struct dsps_gpio_info dsps_surf_gpios[] = { + { + .name = "compass_rst_n", + .num = GPIO_COMPASS_RST_N, + .on_val = 1, /* device not in reset */ + .off_val = 0, /* device in reset */ + }, + { + .name = "gpio_r_altimeter_reset_n", + .num = GPIO_R_ALTIMETER_RESET_N, + .on_val = 1, /* device not in reset */ + .off_val = 0, /* device in reset */ + } +}; + +static struct dsps_gpio_info dsps_fluid_gpios[] = { + { + .name = "gpio_n_altimeter_reset_n", + .num = GPIO_N_ALTIMETER_RESET_N, + .on_val = 1, /* device not in reset */ + .off_val = 0, /* device in reset */ + } +}; + +static void __init msm8x60_init_dsps(void) +{ + struct msm_dsps_platform_data *pdata = + msm_dsps_device.dev.platform_data; + /* + * On Fluid the Compass sensor Chip-Select (CS) is directly connected + * to the power supply and not controled via GPIOs. Fluid uses a + * different IO-Expender (north) than used on surf/ffa. + */ + if (machine_is_msm8x60_fluid()) { + /* fluid has different firmware, gpios */ + peripheral_dsps.name = DSPS_PIL_FLUID_NAME; + pdata->pil_name = DSPS_PIL_FLUID_NAME; + pdata->gpios = dsps_fluid_gpios; + pdata->gpios_num = ARRAY_SIZE(dsps_fluid_gpios); + } else { + peripheral_dsps.name = DSPS_PIL_GENERIC_NAME; + pdata->pil_name = DSPS_PIL_GENERIC_NAME; + pdata->gpios = dsps_surf_gpios; + pdata->gpios_num = ARRAY_SIZE(dsps_surf_gpios); + } + + msm_pil_add_device(&peripheral_dsps); + + platform_device_register(&msm_dsps_device); +} +#endif /* CONFIG_MSM_DSPS */ + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +/* prim = 1024 x 600 x 4(bpp) x 3(pages) */ +#define MSM_FB_PRIM_BUF_SIZE 0x708000 +#else +/* prim = 1024 x 600 x 4(bpp) x 2(pages) */ +#define MSM_FB_PRIM_BUF_SIZE 0x4B0000 +#endif + + +#ifdef CONFIG_FB_MSM_OVERLAY_WRITEBACK +/* 960 x 540 x 3 x 2 */ +#define MSM_FB_WRITEBACK_SIZE 0x300000 +#else +#define MSM_FB_WRITEBACK_SIZE 0 +#endif + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +/* prim = 1024 x 600 x 4(bpp) x 2(pages) + * hdmi = 1920 x 1080 x 2(bpp) x 1(page) + * Note: must be multiple of 4096 */ +#define MSM_FB_SIZE roundup(MSM_FB_PRIM_BUF_SIZE + 0x3F4800 + \ + MSM_FB_WRITEBACK_SIZE + MSM_FB_DSUB_PMEM_ADDER, 4096) +#elif defined(CONFIG_FB_MSM_TVOUT) +/* prim = 1024 x 600 x 4(bpp) x 2(pages) + * tvout = 720 x 576 x 2(bpp) x 2(pages) + * Note: must be multiple of 4096 */ +#define MSM_FB_SIZE roundup(MSM_FB_PRIM_BUF_SIZE + 0x195000 + \ + MSM_FB_WRITEBACK_SIZE + MSM_FB_DSUB_PMEM_ADDER, 4096) +#else /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ +#define MSM_FB_SIZE roundup(MSM_FB_PRIM_BUF_SIZE + \ + MSM_FB_WRITEBACK_SIZE + MSM_FB_DSUB_PMEM_ADDER, 4096) +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ + +#define MSM_PMEM_SF_SIZE 0x4000000 /* 64 Mbytes */ + +#define MSM_PMEM_KERNEL_EBI1_SIZE 0x600000 +#define MSM_PMEM_ADSP_SIZE 0x2000000 +#define MSM_PMEM_AUDIO_SIZE 0x279000 + +#define MSM_SMI_BASE 0x38000000 +#define MSM_SMI_SIZE 0x4000000 + +#define KERNEL_SMI_BASE (MSM_SMI_BASE) +#define KERNEL_SMI_SIZE 0x300000 + +#define USER_SMI_BASE (KERNEL_SMI_BASE + KERNEL_SMI_SIZE) +#define USER_SMI_SIZE (MSM_SMI_SIZE - KERNEL_SMI_SIZE) +#define MSM_PMEM_SMIPOOL_SIZE USER_SMI_SIZE + +static unsigned fb_size; +static int __init fb_size_setup(char *p) +{ + fb_size = memparse(p, NULL); + return 0; +} +early_param("fb_size", fb_size_setup); + +static unsigned pmem_kernel_ebi1_size = MSM_PMEM_KERNEL_EBI1_SIZE; +static int __init pmem_kernel_ebi1_size_setup(char *p) +{ + pmem_kernel_ebi1_size = memparse(p, NULL); + return 0; +} +early_param("pmem_kernel_ebi1_size", pmem_kernel_ebi1_size_setup); + +#ifdef CONFIG_ANDROID_PMEM +static unsigned pmem_sf_size = MSM_PMEM_SF_SIZE; +static int __init pmem_sf_size_setup(char *p) +{ + pmem_sf_size = memparse(p, NULL); + return 0; +} +early_param("pmem_sf_size", pmem_sf_size_setup); + +static unsigned pmem_adsp_size = MSM_PMEM_ADSP_SIZE; + +static int __init pmem_adsp_size_setup(char *p) +{ + pmem_adsp_size = memparse(p, NULL); + return 0; +} +early_param("pmem_adsp_size", pmem_adsp_size_setup); + +static unsigned pmem_audio_size = MSM_PMEM_AUDIO_SIZE; + +static int __init pmem_audio_size_setup(char *p) +{ + pmem_audio_size = memparse(p, NULL); + return 0; +} +early_param("pmem_audio_size", pmem_audio_size_setup); +#endif + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; + +#ifdef CONFIG_FB_MSM_LCDC_AUTO_DETECT +static int msm_fb_detect_panel(const char *name) +{ + if (machine_is_msm8x60_fluid()) { + uint32_t soc_platform_version = socinfo_get_platform_version(); + if (SOCINFO_VERSION_MAJOR(soc_platform_version) < 3) { +#ifdef CONFIG_FB_MSM_LCDC_SAMSUNG_OLED_PT + if (!strncmp(name, LCDC_SAMSUNG_OLED_PANEL_NAME, + strlen(LCDC_SAMSUNG_OLED_PANEL_NAME))) + return 0; +#endif + } else { /*P3 and up use AUO panel */ +#ifdef CONFIG_FB_MSM_LCDC_AUO_WVGA + if (!strncmp(name, LCDC_AUO_PANEL_NAME, + strlen(LCDC_AUO_PANEL_NAME))) + return 0; +#endif + } + if (!strncmp(name, LCDC_SAMSUNG_WSVGA_PANEL_NAME, + strlen(LCDC_SAMSUNG_WSVGA_PANEL_NAME))) + return -ENODEV; + } else { + if (!strncmp(name, LCDC_SAMSUNG_WSVGA_PANEL_NAME, + strlen(LCDC_SAMSUNG_WSVGA_PANEL_NAME))) + return 0; + if (!strncmp(name, LCDC_SAMSUNG_OLED_PANEL_NAME, + strlen(LCDC_SAMSUNG_OLED_PANEL_NAME))) + return -ENODEV; + } + pr_warning("%s: not supported '%s'", __func__, name); + return -ENODEV; +} + +static struct msm_fb_platform_data msm_fb_pdata = { + .detect_client = msm_fb_detect_panel, +}; +#endif /* CONFIG_FB_MSM_LCDC_AUTO_DETECT */ + +static struct platform_device msm_fb_device = { + .name = "msm_fb", + .id = 0, + .num_resources = ARRAY_SIZE(msm_fb_resources), + .resource = msm_fb_resources, +#ifdef CONFIG_FB_MSM_LCDC_AUTO_DETECT + .dev.platform_data = &msm_fb_pdata, +#endif /* CONFIG_FB_MSM_LCDC_AUTO_DETECT */ +}; + +#ifdef CONFIG_ANDROID_PMEM +static struct android_pmem_platform_data android_pmem_pdata = { + .name = "pmem", + .allocator_type = PMEM_ALLOCATORTYPE_ALLORNOTHING, + .cached = 1, + .memory_type = MEMTYPE_EBI1, +}; + +static struct platform_device android_pmem_device = { + .name = "android_pmem", + .id = 0, + .dev = {.platform_data = &android_pmem_pdata}, +}; + +static struct android_pmem_platform_data android_pmem_adsp_pdata = { + .name = "pmem_adsp", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, + .memory_type = MEMTYPE_EBI1, +}; + +static struct platform_device android_pmem_adsp_device = { + .name = "android_pmem", + .id = 2, + .dev = { .platform_data = &android_pmem_adsp_pdata }, +}; + +static struct android_pmem_platform_data android_pmem_audio_pdata = { + .name = "pmem_audio", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, + .memory_type = MEMTYPE_EBI1, +}; + +static struct platform_device android_pmem_audio_device = { + .name = "android_pmem", + .id = 4, + .dev = { .platform_data = &android_pmem_audio_pdata }, +}; + +static struct android_pmem_platform_data android_pmem_smipool_pdata = { + .name = "pmem_smipool", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, + .memory_type = MEMTYPE_SMI, +}; +static struct platform_device android_pmem_smipool_device = { + .name = "android_pmem", + .id = 7, + .dev = { .platform_data = &android_pmem_smipool_pdata }, +}; + +#endif + +#define GPIO_DONGLE_PWR_EN 258 +static void setup_display_power(void); +static int lcdc_vga_enabled; +static int vga_enable_request(int enable) +{ + if (enable) + lcdc_vga_enabled = 1; + else + lcdc_vga_enabled = 0; + setup_display_power(); + + return 0; +} + +#define GPIO_BACKLIGHT_PWM0 0 +#define GPIO_BACKLIGHT_PWM1 1 + +static int pmic_backlight_gpio[2] + = { GPIO_BACKLIGHT_PWM0, GPIO_BACKLIGHT_PWM1 }; +static struct msm_panel_common_pdata lcdc_samsung_panel_data = { + .gpio_num = pmic_backlight_gpio, /* two LPG CHANNELS for backlight */ + .vga_switch = vga_enable_request, +}; + +static struct platform_device lcdc_samsung_panel_device = { + .name = LCDC_SAMSUNG_WSVGA_PANEL_NAME, + .id = 0, + .dev = { + .platform_data = &lcdc_samsung_panel_data, + } +}; +#if (!defined(CONFIG_SPI_QUP)) && \ + (defined(CONFIG_FB_MSM_LCDC_SAMSUNG_OLED_PT) || \ + defined(CONFIG_FB_MSM_LCDC_AUO_WVGA)) + +static int lcdc_spi_gpio_array_num[] = { + LCDC_SPI_GPIO_CLK, + LCDC_SPI_GPIO_CS, + LCDC_SPI_GPIO_MOSI, +}; + +static uint32_t lcdc_spi_gpio_config_data[] = { + GPIO_CFG(LCDC_SPI_GPIO_CLK, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + GPIO_CFG(LCDC_SPI_GPIO_CS, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + GPIO_CFG(LCDC_SPI_GPIO_MOSI, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), +}; + +static void lcdc_config_spi_gpios(int enable) +{ + int n; + for (n = 0; n < ARRAY_SIZE(lcdc_spi_gpio_config_data); ++n) + gpio_tlmm_config(lcdc_spi_gpio_config_data[n], 0); +} +#endif + +#ifdef CONFIG_FB_MSM_LCDC_SAMSUNG_OLED_PT +#ifdef CONFIG_SPI_QUP +static struct spi_board_info lcdc_samsung_spi_board_info[] __initdata = { + { + .modalias = LCDC_SAMSUNG_SPI_DEVICE_NAME, + .mode = SPI_MODE_3, + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 10800000, + } +}; +#endif /* CONFIG_SPI_QUP */ + +static struct msm_panel_common_pdata lcdc_samsung_oled_panel_data = { +#ifndef CONFIG_SPI_QUP + .panel_config_gpio = lcdc_config_spi_gpios, + .gpio_num = lcdc_spi_gpio_array_num, +#endif +}; + +static struct platform_device lcdc_samsung_oled_panel_device = { + .name = LCDC_SAMSUNG_OLED_PANEL_NAME, + .id = 0, + .dev.platform_data = &lcdc_samsung_oled_panel_data, +}; +#endif /*CONFIG_FB_MSM_LCDC_SAMSUNG_OLED_PT */ + +#ifdef CONFIG_FB_MSM_LCDC_AUO_WVGA +#ifdef CONFIG_SPI_QUP +static struct spi_board_info lcdc_auo_spi_board_info[] __initdata = { + { + .modalias = LCDC_AUO_SPI_DEVICE_NAME, + .mode = SPI_MODE_3, + .bus_num = 1, + .chip_select = 0, + .max_speed_hz = 10800000, + } +}; +#endif + +static struct msm_panel_common_pdata lcdc_auo_wvga_panel_data = { +#ifndef CONFIG_SPI_QUP + .panel_config_gpio = lcdc_config_spi_gpios, + .gpio_num = lcdc_spi_gpio_array_num, +#endif +}; + +static struct platform_device lcdc_auo_wvga_panel_device = { + .name = LCDC_AUO_PANEL_NAME, + .id = 0, + .dev.platform_data = &lcdc_auo_wvga_panel_data, +}; +#endif /*CONFIG_FB_MSM_LCDC_AUO_WVGA*/ + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +static struct resource hdmi_msm_resources[] = { + { + .name = "hdmi_msm_qfprom_addr", + .start = 0x00700000, + .end = 0x007060FF, + .flags = IORESOURCE_MEM, + }, + { + .name = "hdmi_msm_hdmi_addr", + .start = 0x04A00000, + .end = 0x04A00FFF, + .flags = IORESOURCE_MEM, + }, + { + .name = "hdmi_msm_irq", + .start = HDMI_IRQ, + .end = HDMI_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static int hdmi_enable_5v(int on); +static int hdmi_core_power(int on, int show); +static int hdmi_cec_power(int on); + +static struct msm_hdmi_platform_data hdmi_msm_data = { + .irq = HDMI_IRQ, + .enable_5v = hdmi_enable_5v, + .core_power = hdmi_core_power, + .cec_power = hdmi_cec_power, +}; + +static struct platform_device hdmi_msm_device = { + .name = "hdmi_msm", + .id = 0, + .num_resources = ARRAY_SIZE(hdmi_msm_resources), + .resource = hdmi_msm_resources, + .dev.platform_data = &hdmi_msm_data, +}; +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ + +#ifdef CONFIG_FB_MSM_MIPI_DSI +static struct platform_device mipi_dsi_toshiba_panel_device = { + .name = "mipi_toshiba", + .id = 0, +}; + +#define FPGA_3D_GPIO_CONFIG_ADDR 0x1D00017A + +static struct mipi_dsi_novatek_platform_data novatek_pdata = { + .fpga_3d_config_addr = FPGA_3D_GPIO_CONFIG_ADDR, +}; + +static struct platform_device mipi_dsi_novatek_panel_device = { + .name = "mipi_novatek", + .id = 0, + .dev = { + .platform_data = &novatek_pdata, + } +}; +#endif + +static void __init msm8x60_allocate_memory_regions(void) +{ + void *addr; + unsigned long size; + + size = MSM_FB_SIZE; + addr = alloc_bootmem_align(size, 0x1000); + msm_fb_resources[0].start = __pa(addr); + msm_fb_resources[0].end = msm_fb_resources[0].start + size - 1; + pr_info("allocating %lu bytes at %p (%lx physical) for fb\n", + size, addr, __pa(addr)); + +} + +#if defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C) || \ + defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C_MODULE) +/*virtual key support */ +static ssize_t tma300_vkeys_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, + __stringify(EV_KEY) ":" __stringify(KEY_BACK) ":60:900:90:120" + ":" __stringify(EV_KEY) ":" __stringify(KEY_MENU) ":180:900:90:120" + ":" __stringify(EV_KEY) ":" __stringify(KEY_HOME) ":300:900:90:120" + ":" __stringify(EV_KEY) ":" __stringify(KEY_SEARCH) ":420:900:90:120" + "\n"); +} + +static struct kobj_attribute tma300_vkeys_attr = { + .attr = { + .mode = S_IRUGO, + }, + .show = &tma300_vkeys_show, +}; + +static struct attribute *tma300_properties_attrs[] = { + &tma300_vkeys_attr.attr, + NULL +}; + +static struct attribute_group tma300_properties_attr_group = { + .attrs = tma300_properties_attrs, +}; + +static struct kobject *properties_kobj; + + + +#define CYTTSP_TS_GPIO_IRQ 61 +static int cyttsp_platform_init(struct i2c_client *client) +{ + int rc = -EINVAL; + struct regulator *pm8058_l5 = NULL, *pm8058_s3; + + if (machine_is_msm8x60_fluid()) { + pm8058_l5 = regulator_get(NULL, "8058_l5"); + if (IS_ERR(pm8058_l5)) { + pr_err("%s: regulator get of 8058_l5 failed (%ld)\n", + __func__, PTR_ERR(pm8058_l5)); + rc = PTR_ERR(pm8058_l5); + return rc; + } + rc = regulator_set_voltage(pm8058_l5, 2850000, 2850000); + if (rc) { + pr_err("%s: regulator_set_voltage of 8058_l5 failed(%d)\n", + __func__, rc); + goto reg_l5_put; + } + + rc = regulator_enable(pm8058_l5); + if (rc) { + pr_err("%s: regulator_enable of 8058_l5 failed(%d)\n", + __func__, rc); + goto reg_l5_put; + } + } + /* vote for s3 to enable i2c communication lines */ + pm8058_s3 = regulator_get(NULL, "8058_s3"); + if (IS_ERR(pm8058_s3)) { + pr_err("%s: regulator get of 8058_s3 failed (%ld)\n", + __func__, PTR_ERR(pm8058_s3)); + rc = PTR_ERR(pm8058_s3); + goto reg_l5_disable; + } + + rc = regulator_set_voltage(pm8058_s3, 1800000, 1800000); + if (rc) { + pr_err("%s: regulator_set_voltage() = %d\n", + __func__, rc); + goto reg_s3_put; + } + + rc = regulator_enable(pm8058_s3); + if (rc) { + pr_err("%s: regulator_enable of 8058_l5 failed(%d)\n", + __func__, rc); + goto reg_s3_put; + } + + /* wait for vregs to stabilize */ + usleep_range(10000, 10000); + + /* check this device active by reading first byte/register */ + rc = i2c_smbus_read_byte_data(client, 0x01); + if (rc < 0) { + pr_err("%s: i2c sanity check failed\n", __func__); + goto reg_s3_disable; + } + + /* virtual keys */ + if (machine_is_msm8x60_fluid()) { + tma300_vkeys_attr.attr.name = "virtualkeys.cyttsp-i2c"; + properties_kobj = kobject_create_and_add("board_properties", + NULL); + if (properties_kobj) + rc = sysfs_create_group(properties_kobj, + &tma300_properties_attr_group); + if (!properties_kobj || rc) + pr_err("%s: failed to create board_properties\n", + __func__); + } + return CY_OK; + +reg_s3_disable: + regulator_disable(pm8058_s3); +reg_s3_put: + regulator_put(pm8058_s3); +reg_l5_disable: + if (machine_is_msm8x60_fluid()) + regulator_disable(pm8058_l5); +reg_l5_put: + if (machine_is_msm8x60_fluid()) + regulator_put(pm8058_l5); + return rc; +} + +static int cyttsp_platform_resume(struct i2c_client *client) +{ + /* add any special code to strobe a wakeup pin or chip reset */ + msleep(10); + + return CY_OK; +} + +static struct cyttsp_platform_data cyttsp_fluid_pdata = { + .flags = 0x04, + .gen = CY_GEN3, /* or */ + .use_st = CY_USE_ST, + .use_mt = CY_USE_MT, + .use_hndshk = CY_SEND_HNDSHK, + .use_trk_id = CY_USE_TRACKING_ID, + .use_sleep = CY_USE_SLEEP, + .use_gestures = CY_USE_GESTURES, + /* activate up to 4 groups + * and set active distance + */ + .gest_set = CY_GEST_GRP1 | CY_GEST_GRP2 | + CY_GEST_GRP3 | CY_GEST_GRP4 | + CY_ACT_DIST, + /* change act_intrvl to customize the Active power state + * scanning/processing refresh interval for Operating mode + */ + .act_intrvl = CY_ACT_INTRVL_DFLT, + /* change tch_tmout to customize the touch timeout for the + * Active power state for Operating mode + */ + .tch_tmout = CY_TCH_TMOUT_DFLT, + /* change lp_intrvl to customize the Low Power power state + * scanning/processing refresh interval for Operating mode + */ + .lp_intrvl = CY_LP_INTRVL_DFLT, + .sleep_gpio = -1, + .resout_gpio = -1, + .irq_gpio = CYTTSP_TS_GPIO_IRQ, + .resume = cyttsp_platform_resume, + .init = cyttsp_platform_init, +}; + +static struct cyttsp_platform_data cyttsp_tmg240_pdata = { + .panel_maxx = 1083, + .panel_maxy = 659, + .disp_minx = 30, + .disp_maxx = 1053, + .disp_miny = 30, + .disp_maxy = 629, + .correct_fw_ver = 8, + .fw_fname = "cyttsp_8660_ffa.hex", + .flags = 0x00, + .gen = CY_GEN2, /* or */ + .use_st = CY_USE_ST, + .use_mt = CY_USE_MT, + .use_hndshk = CY_SEND_HNDSHK, + .use_trk_id = CY_USE_TRACKING_ID, + .use_sleep = CY_USE_SLEEP, + .use_gestures = CY_USE_GESTURES, + /* activate up to 4 groups + * and set active distance + */ + .gest_set = CY_GEST_GRP1 | CY_GEST_GRP2 | + CY_GEST_GRP3 | CY_GEST_GRP4 | + CY_ACT_DIST, + /* change act_intrvl to customize the Active power state + * scanning/processing refresh interval for Operating mode + */ + .act_intrvl = CY_ACT_INTRVL_DFLT, + /* change tch_tmout to customize the touch timeout for the + * Active power state for Operating mode + */ + .tch_tmout = CY_TCH_TMOUT_DFLT, + /* change lp_intrvl to customize the Low Power power state + * scanning/processing refresh interval for Operating mode + */ + .lp_intrvl = CY_LP_INTRVL_DFLT, + .sleep_gpio = -1, + .resout_gpio = -1, + .irq_gpio = CYTTSP_TS_GPIO_IRQ, + .resume = cyttsp_platform_resume, + .init = cyttsp_platform_init, +}; +static void cyttsp_set_params(void) +{ + if (SOCINFO_VERSION_MAJOR(socinfo_get_platform_version()) < 3) { + cyttsp_fluid_pdata.fw_fname = "cyttsp_8660_fluid_p2.hex"; + cyttsp_fluid_pdata.panel_maxx = 539; + cyttsp_fluid_pdata.panel_maxy = 994; + cyttsp_fluid_pdata.disp_minx = 30; + cyttsp_fluid_pdata.disp_maxx = 509; + cyttsp_fluid_pdata.disp_miny = 60; + cyttsp_fluid_pdata.disp_maxy = 859; + cyttsp_fluid_pdata.correct_fw_ver = 4; + } else { + cyttsp_fluid_pdata.fw_fname = "cyttsp_8660_fluid_p3.hex"; + cyttsp_fluid_pdata.panel_maxx = 550; + cyttsp_fluid_pdata.panel_maxy = 1013; + cyttsp_fluid_pdata.disp_minx = 35; + cyttsp_fluid_pdata.disp_maxx = 515; + cyttsp_fluid_pdata.disp_miny = 69; + cyttsp_fluid_pdata.disp_maxy = 869; + cyttsp_fluid_pdata.correct_fw_ver = 5; + } + +} + +static struct i2c_board_info cyttsp_fluid_info[] __initdata = { + { + I2C_BOARD_INFO(CY_I2C_NAME, 0x24), + .platform_data = &cyttsp_fluid_pdata, +#ifndef CY_USE_TIMER + .irq = MSM_GPIO_TO_INT(CYTTSP_TS_GPIO_IRQ), +#endif /* CY_USE_TIMER */ + }, +}; + +static struct i2c_board_info cyttsp_ffa_info[] __initdata = { + { + I2C_BOARD_INFO(CY_I2C_NAME, 0x3b), + .platform_data = &cyttsp_tmg240_pdata, +#ifndef CY_USE_TIMER + .irq = MSM_GPIO_TO_INT(CYTTSP_TS_GPIO_IRQ), +#endif /* CY_USE_TIMER */ + }, +}; +#endif + +static struct regulator *vreg_tmg200; + +#define TS_PEN_IRQ_GPIO 61 +static int tmg200_power(int vreg_on) +{ + int rc = -EINVAL; + + if (!vreg_tmg200) { + printk(KERN_ERR "%s: regulator 8058_s3 not found (%d)\n", + __func__, rc); + return rc; + } + + rc = vreg_on ? regulator_enable(vreg_tmg200) : + regulator_disable(vreg_tmg200); + if (rc < 0) + printk(KERN_ERR "%s: vreg 8058_s3 %s failed (%d)\n", + __func__, vreg_on ? "enable" : "disable", rc); + + /* wait for vregs to stabilize */ + usleep_range(10000, 10000); + + return rc; +} + +static int tmg200_dev_setup(bool enable) +{ + int rc; + + if (enable) { + vreg_tmg200 = regulator_get(NULL, "8058_s3"); + if (IS_ERR(vreg_tmg200)) { + pr_err("%s: regulator get of 8058_s3 failed (%ld)\n", + __func__, PTR_ERR(vreg_tmg200)); + rc = PTR_ERR(vreg_tmg200); + return rc; + } + + rc = regulator_set_voltage(vreg_tmg200, 1800000, 1800000); + if (rc) { + pr_err("%s: regulator_set_voltage() = %d\n", + __func__, rc); + goto reg_put; + } + } else { + /* put voltage sources */ + regulator_put(vreg_tmg200); + } + return 0; +reg_put: + regulator_put(vreg_tmg200); + return rc; +} + +static struct cy8c_ts_platform_data cy8ctmg200_pdata = { + .ts_name = "msm_tmg200_ts", + .dis_min_x = 0, + .dis_max_x = 1023, + .dis_min_y = 0, + .dis_max_y = 599, + .min_tid = 0, + .max_tid = 255, + .min_touch = 0, + .max_touch = 255, + .min_width = 0, + .max_width = 255, + .power_on = tmg200_power, + .dev_setup = tmg200_dev_setup, + .nfingers = 2, + .irq_gpio = TS_PEN_IRQ_GPIO, + .resout_gpio = GPIO_CAP_TS_RESOUT_N, +}; + +static struct i2c_board_info cy8ctmg200_board_info[] = { + { + I2C_BOARD_INFO("cy8ctmg200", 0x2), + .platform_data = &cy8ctmg200_pdata, + } +}; + +#ifdef CONFIG_SERIAL_MSM_HS +static int configure_uart_gpios(int on) +{ + int ret = 0, i; + int uart_gpios[] = {53, 54, 55, 56}; + for (i = 0; i < ARRAY_SIZE(uart_gpios); i++) { + if (on) { + ret = msm_gpiomux_get(uart_gpios[i]); + if (unlikely(ret)) + break; + } else { + ret = msm_gpiomux_put(uart_gpios[i]); + if (unlikely(ret)) + return ret; + } + } + if (ret) + for (; i >= 0; i--) + msm_gpiomux_put(uart_gpios[i]); + return ret; +} +static struct msm_serial_hs_platform_data msm_uart_dm1_pdata = { + .inject_rx_on_wakeup = 1, + .rx_to_inject = 0xFD, + .gpio_config = configure_uart_gpios, +}; +#endif + + +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) + +static struct gpio_led gpio_exp_leds_config[] = { + { + .name = "left_led1:green", + .gpio = GPIO_LEFT_LED_1, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, + { + .name = "left_led2:red", + .gpio = GPIO_LEFT_LED_2, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, + { + .name = "left_led3:green", + .gpio = GPIO_LEFT_LED_3, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, + { + .name = "wlan_led:orange", + .gpio = GPIO_LEFT_LED_WLAN, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, + { + .name = "left_led5:green", + .gpio = GPIO_LEFT_LED_5, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, + { + .name = "right_led1:green", + .gpio = GPIO_RIGHT_LED_1, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, + { + .name = "right_led2:red", + .gpio = GPIO_RIGHT_LED_2, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, + { + .name = "right_led3:green", + .gpio = GPIO_RIGHT_LED_3, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, + { + .name = "bt_led:blue", + .gpio = GPIO_RIGHT_LED_BT, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, + { + .name = "right_led5:green", + .gpio = GPIO_RIGHT_LED_5, + .active_low = 1, + .retain_state_suspended = 0, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + }, +}; + +static struct gpio_led_platform_data gpio_leds_pdata = { + .num_leds = ARRAY_SIZE(gpio_exp_leds_config), + .leds = gpio_exp_leds_config, +}; + +static struct platform_device gpio_leds = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = &gpio_leds_pdata, + }, +}; + +static struct gpio_led fluid_gpio_leds[] = { + { + .name = "dual_led:green", + .gpio = GPIO_LED1_GREEN_N, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + .active_low = 1, + .retain_state_suspended = 0, + }, + { + .name = "dual_led:red", + .gpio = GPIO_LED2_RED_N, + .default_state = LEDS_GPIO_DEFSTATE_OFF, + .active_low = 1, + .retain_state_suspended = 0, + }, +}; + +static struct gpio_led_platform_data gpio_led_pdata = { + .leds = fluid_gpio_leds, + .num_leds = ARRAY_SIZE(fluid_gpio_leds), +}; + +static struct platform_device fluid_leds_gpio = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = &gpio_led_pdata, + }, +}; + +#endif + +#if defined(CONFIG_MSM_RPM_LOG) || defined(CONFIG_MSM_RPM_LOG_MODULE) + +static struct msm_rpm_log_platform_data msm_rpm_log_pdata = { + .phys_addr_base = 0x00106000, + .reg_offsets = { + [MSM_RPM_LOG_PAGE_INDICES] = 0x00000C80, + [MSM_RPM_LOG_PAGE_BUFFER] = 0x00000CA0, + }, + .phys_size = SZ_8K, + .log_len = 4096, /* log's buffer length in bytes */ + .log_len_mask = (4096 >> 2) - 1, /* length mask in units of u32 */ +}; + +static struct platform_device msm_rpm_log_device = { + .name = "msm_rpm_log", + .id = -1, + .dev = { + .platform_data = &msm_rpm_log_pdata, + }, +}; +#endif + +#ifdef CONFIG_BATTERY_MSM8X60 +static struct msm_charger_platform_data msm_charger_data = { + .safety_time = 180, + .update_time = 1, + .max_voltage = 4200, + .min_voltage = 3200, +}; + +static struct platform_device msm_charger_device = { + .name = "msm-charger", + .id = -1, + .dev = { + .platform_data = &msm_charger_data, + } +}; +#endif + +/* + * Consumer specific regulator names: + * regulator name consumer dev_name + */ +static struct regulator_consumer_supply vreg_consumers_PM8058_L0[] = { + REGULATOR_SUPPLY("8058_l0", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L1[] = { + REGULATOR_SUPPLY("8058_l1", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L2[] = { + REGULATOR_SUPPLY("8058_l2", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L3[] = { + REGULATOR_SUPPLY("8058_l3", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L4[] = { + REGULATOR_SUPPLY("8058_l4", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L5[] = { + REGULATOR_SUPPLY("8058_l5", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L6[] = { + REGULATOR_SUPPLY("8058_l6", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L7[] = { + REGULATOR_SUPPLY("8058_l7", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L8[] = { + REGULATOR_SUPPLY("8058_l8", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L9[] = { + REGULATOR_SUPPLY("8058_l9", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L10[] = { + REGULATOR_SUPPLY("8058_l10", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L11[] = { + REGULATOR_SUPPLY("8058_l11", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L12[] = { + REGULATOR_SUPPLY("8058_l12", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L13[] = { + REGULATOR_SUPPLY("8058_l13", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L14[] = { + REGULATOR_SUPPLY("8058_l14", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L15[] = { + REGULATOR_SUPPLY("8058_l15", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L16[] = { + REGULATOR_SUPPLY("8058_l16", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L17[] = { + REGULATOR_SUPPLY("8058_l17", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L18[] = { + REGULATOR_SUPPLY("8058_l18", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L19[] = { + REGULATOR_SUPPLY("8058_l19", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L20[] = { + REGULATOR_SUPPLY("8058_l20", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L21[] = { + REGULATOR_SUPPLY("8058_l21", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L22[] = { + REGULATOR_SUPPLY("8058_l22", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L23[] = { + REGULATOR_SUPPLY("8058_l23", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L24[] = { + REGULATOR_SUPPLY("8058_l24", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_L25[] = { + REGULATOR_SUPPLY("8058_l25", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_S0[] = { + REGULATOR_SUPPLY("8058_s0", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_S1[] = { + REGULATOR_SUPPLY("8058_s1", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_S2[] = { + REGULATOR_SUPPLY("8058_s2", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_S3[] = { + REGULATOR_SUPPLY("8058_s3", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_S4[] = { + REGULATOR_SUPPLY("8058_s4", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_LVS0[] = { + REGULATOR_SUPPLY("8058_lvs0", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_LVS1[] = { + REGULATOR_SUPPLY("8058_lvs1", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8058_NCP[] = { + REGULATOR_SUPPLY("8058_ncp", NULL), +}; + +static struct regulator_consumer_supply vreg_consumers_PM8901_L0[] = { + REGULATOR_SUPPLY("8901_l0", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_L1[] = { + REGULATOR_SUPPLY("8901_l1", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_L2[] = { + REGULATOR_SUPPLY("8901_l2", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_L3[] = { + REGULATOR_SUPPLY("8901_l3", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_L4[] = { + REGULATOR_SUPPLY("8901_l4", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_L5[] = { + REGULATOR_SUPPLY("8901_l5", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_L6[] = { + REGULATOR_SUPPLY("8901_l6", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_S2[] = { + REGULATOR_SUPPLY("8901_s2", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_S3[] = { + REGULATOR_SUPPLY("8901_s3", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_S4[] = { + REGULATOR_SUPPLY("8901_s4", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_LVS0[] = { + REGULATOR_SUPPLY("8901_lvs0", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_LVS1[] = { + REGULATOR_SUPPLY("8901_lvs1", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_LVS2[] = { + REGULATOR_SUPPLY("8901_lvs2", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_LVS3[] = { + REGULATOR_SUPPLY("8901_lvs3", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_PM8901_MVS0[] = { + REGULATOR_SUPPLY("8901_mvs0", NULL), +}; + +#define RPM_VREG_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, \ + _default_uV, _peak_uA, _avg_uA, _pull_down, _pin_ctrl, \ + _freq, _pin_fn, _rpm_mode, _state, _sleep_selectable, \ + _always_on) \ + [RPM_VREG_ID_##_id] = { \ + .init_data = { \ + .constraints = { \ + .valid_modes_mask = _modes, \ + .valid_ops_mask = _ops, \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .input_uV = _min_uV, \ + .apply_uV = _apply_uV, \ + .always_on = _always_on, \ + }, \ + .consumer_supplies = vreg_consumers_##_id, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_##_id), \ + }, \ + .default_uV = _default_uV, \ + .peak_uA = _peak_uA, \ + .avg_uA = _avg_uA, \ + .pull_down_enable = _pull_down, \ + .pin_ctrl = _pin_ctrl, \ + .freq = _freq, \ + .pin_fn = _pin_fn, \ + .mode = _rpm_mode, \ + .state = _state, \ + .sleep_selectable = _sleep_selectable, \ + } + +/* + * The default LPM/HPM state of an RPM controlled regulator can be controlled + * via the peak_uA value specified in the table below. If the value is less + * than the high power min threshold for the regulator, then the regulator will + * be set to LPM. Otherwise, it will be set to HPM. + * + * This value can be further overridden by specifying an initial mode via + * .init_data.constraints.initial_mode. + */ + +#define RPM_VREG_INIT_LDO(_id, _always_on, _pd, _sleep_selectable, _min_uV, \ + _max_uV, _init_peak_uA, _pin_ctrl) \ + RPM_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_FAST | \ + REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE | \ + REGULATOR_MODE_STANDBY, REGULATOR_CHANGE_VOLTAGE | \ + REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \ + REGULATOR_CHANGE_DRMS, 0, _min_uV, _init_peak_uA, \ + _init_peak_uA, _pd, _pin_ctrl, RPM_VREG_FREQ_NONE, \ + RPM_VREG_PIN_FN_ENABLE, RPM_VREG_MODE_NONE, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on) + +#define RPM_VREG_INIT_LDO_PF(_id, _always_on, _pd, _sleep_selectable, _min_uV, \ + _max_uV, _init_peak_uA, _pin_ctrl, _pin_fn) \ + RPM_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_FAST | \ + REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE | \ + REGULATOR_MODE_STANDBY, REGULATOR_CHANGE_VOLTAGE | \ + REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \ + REGULATOR_CHANGE_DRMS, 0, _min_uV, _init_peak_uA, \ + _init_peak_uA, _pd, _pin_ctrl, RPM_VREG_FREQ_NONE, \ + _pin_fn, RPM_VREG_MODE_NONE, RPM_VREG_STATE_OFF, \ + _sleep_selectable, _always_on) + +#define RPM_VREG_INIT_SMPS(_id, _always_on, _pd, _sleep_selectable, _min_uV, \ + _max_uV, _init_peak_uA, _pin_ctrl, _freq) \ + RPM_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_FAST | \ + REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE | \ + REGULATOR_MODE_STANDBY, REGULATOR_CHANGE_VOLTAGE | \ + REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \ + REGULATOR_CHANGE_DRMS, 0, _min_uV, _init_peak_uA, \ + _init_peak_uA, _pd, _pin_ctrl, _freq, \ + RPM_VREG_PIN_FN_ENABLE, RPM_VREG_MODE_NONE, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on) + +#define RPM_VREG_INIT_VS(_id, _always_on, _pd, _sleep_selectable, _pin_ctrl) \ + RPM_VREG_INIT(_id, 0, 0, REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE, \ + REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE, 0, 0, \ + 1000, 1000, _pd, _pin_ctrl, RPM_VREG_FREQ_NONE, \ + RPM_VREG_PIN_FN_ENABLE, RPM_VREG_MODE_NONE, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on) + +#define RPM_VREG_INIT_NCP(_id, _always_on, _pd, _sleep_selectable, _min_uV, \ + _max_uV, _pin_ctrl) \ + RPM_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL, \ + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, 0, \ + _min_uV, 1000, 1000, _pd, _pin_ctrl, RPM_VREG_FREQ_NONE, \ + RPM_VREG_PIN_FN_ENABLE, RPM_VREG_MODE_NONE, \ + RPM_VREG_STATE_OFF, _sleep_selectable, _always_on) + +#define LDO50HMIN RPM_VREG_LDO_50_HPM_MIN_LOAD +#define LDO150HMIN RPM_VREG_LDO_150_HPM_MIN_LOAD +#define LDO300HMIN RPM_VREG_LDO_300_HPM_MIN_LOAD +#define SMPS_HMIN RPM_VREG_SMPS_HPM_MIN_LOAD +#define FTS_HMIN RPM_VREG_FTSMPS_HPM_MIN_LOAD + +static struct rpm_vreg_pdata rpm_vreg_init_pdata[RPM_VREG_ID_MAX] = { + RPM_VREG_INIT_LDO(PM8058_L0, 0, 1, 0, 1200000, 1200000, LDO150HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L1, 0, 1, 0, 1200000, 1200000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L2, 0, 1, 0, 1800000, 2600000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L3, 0, 1, 0, 1800000, 1800000, LDO150HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L4, 0, 1, 0, 2850000, 2850000, LDO50HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L5, 0, 1, 0, 2850000, 2850000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L6, 0, 1, 0, 3000000, 3600000, LDO50HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L7, 0, 1, 0, 1800000, 1800000, LDO50HMIN, 0), + RPM_VREG_INIT_LDO_PF(PM8058_L8, 0, 1, 0, 2900000, 3050000, LDO300HMIN, + RPM_VREG_PIN_CTRL_NONE, RPM_VREG_PIN_FN_SLEEP_B), + RPM_VREG_INIT_LDO(PM8058_L9, 0, 1, 0, 1800000, 1800000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L10, 0, 1, 0, 2600000, 2600000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L11, 0, 1, 0, 1500000, 1500000, LDO150HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L12, 0, 1, 0, 2900000, 2900000, LDO150HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L13, 0, 1, 0, 2050000, 2050000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L14, 0, 0, 0, 2850000, 2850000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L15, 0, 1, 0, 2850000, 2850000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L16, 1, 1, 0, 1800000, 1800000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L17, 0, 1, 0, 2600000, 2600000, LDO150HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L18, 0, 1, 0, 2200000, 2200000, LDO150HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L19, 0, 1, 0, 2500000, 2500000, LDO150HMIN, 0), + RPM_VREG_INIT_LDO_PF(PM8058_L20, 0, 1, 0, 1800000, 1800000, LDO150HMIN, + RPM_VREG_PIN_CTRL_NONE, RPM_VREG_PIN_FN_SLEEP_B), + RPM_VREG_INIT_LDO_PF(PM8058_L21, 1, 1, 0, 1200000, 1200000, LDO150HMIN, + RPM_VREG_PIN_CTRL_NONE, RPM_VREG_PIN_FN_SLEEP_B), + RPM_VREG_INIT_LDO(PM8058_L22, 0, 1, 0, 1200000, 1200000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L23, 0, 1, 0, 1200000, 1200000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L24, 0, 1, 0, 1200000, 1200000, LDO150HMIN, 0), + RPM_VREG_INIT_LDO(PM8058_L25, 0, 1, 0, 1200000, 1200000, LDO150HMIN, 0), + + RPM_VREG_INIT_SMPS(PM8058_S0, 0, 1, 1, 500000, 1250000, SMPS_HMIN, 0, + RPM_VREG_FREQ_1p60), + RPM_VREG_INIT_SMPS(PM8058_S1, 0, 1, 1, 500000, 1250000, SMPS_HMIN, 0, + RPM_VREG_FREQ_1p60), + RPM_VREG_INIT_SMPS(PM8058_S2, 0, 1, 1, 1200000, 1400000, SMPS_HMIN, + RPM_VREG_PIN_CTRL_A0, RPM_VREG_FREQ_1p60), + RPM_VREG_INIT_SMPS(PM8058_S3, 1, 1, 0, 1800000, 1800000, SMPS_HMIN, 0, + RPM_VREG_FREQ_1p60), + RPM_VREG_INIT_SMPS(PM8058_S4, 1, 1, 0, 2200000, 2200000, SMPS_HMIN, 0, + RPM_VREG_FREQ_1p60), + + RPM_VREG_INIT_VS(PM8058_LVS0, 0, 1, 0, 0), + RPM_VREG_INIT_VS(PM8058_LVS1, 0, 1, 0, 0), + + RPM_VREG_INIT_NCP(PM8058_NCP, 0, 1, 0, 1800000, 1800000, 0), + + RPM_VREG_INIT_LDO(PM8901_L0, 0, 1, 0, 1200000, 1200000, LDO300HMIN, + RPM_VREG_PIN_CTRL_A0), + RPM_VREG_INIT_LDO(PM8901_L1, 0, 1, 0, 3300000, 3300000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8901_L2, 0, 1, 0, 2850000, 3300000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8901_L3, 0, 1, 0, 3300000, 3300000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8901_L4, 0, 1, 0, 2600000, 2600000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8901_L5, 0, 1, 0, 2850000, 2850000, LDO300HMIN, 0), + RPM_VREG_INIT_LDO(PM8901_L6, 0, 1, 0, 2200000, 2200000, LDO300HMIN, 0), + + RPM_VREG_INIT_SMPS(PM8901_S2, 0, 1, 0, 1300000, 1300000, FTS_HMIN, 0, + RPM_VREG_FREQ_1p60), + RPM_VREG_INIT_SMPS(PM8901_S3, 0, 1, 0, 1100000, 1100000, FTS_HMIN, 0, + RPM_VREG_FREQ_1p60), + RPM_VREG_INIT_SMPS(PM8901_S4, 0, 1, 0, 1225000, 1225000, FTS_HMIN, + RPM_VREG_PIN_CTRL_A0, RPM_VREG_FREQ_1p60), + + RPM_VREG_INIT_VS(PM8901_LVS0, 1, 1, 0, 0), + RPM_VREG_INIT_VS(PM8901_LVS1, 0, 1, 0, 0), + RPM_VREG_INIT_VS(PM8901_LVS2, 0, 1, 0, 0), + RPM_VREG_INIT_VS(PM8901_LVS3, 0, 1, 0, 0), + RPM_VREG_INIT_VS(PM8901_MVS0, 0, 1, 0, 0), +}; + +#define RPM_VREG(_id) \ + [_id] = { \ + .name = "rpm-regulator", \ + .id = _id, \ + .dev = { \ + .platform_data = &rpm_vreg_init_pdata[_id], \ + }, \ + } + +static struct platform_device rpm_vreg_device[RPM_VREG_ID_MAX] = { + RPM_VREG(RPM_VREG_ID_PM8058_L0), + RPM_VREG(RPM_VREG_ID_PM8058_L1), + RPM_VREG(RPM_VREG_ID_PM8058_L2), + RPM_VREG(RPM_VREG_ID_PM8058_L3), + RPM_VREG(RPM_VREG_ID_PM8058_L4), + RPM_VREG(RPM_VREG_ID_PM8058_L5), + RPM_VREG(RPM_VREG_ID_PM8058_L6), + RPM_VREG(RPM_VREG_ID_PM8058_L7), + RPM_VREG(RPM_VREG_ID_PM8058_L8), + RPM_VREG(RPM_VREG_ID_PM8058_L9), + RPM_VREG(RPM_VREG_ID_PM8058_L10), + RPM_VREG(RPM_VREG_ID_PM8058_L11), + RPM_VREG(RPM_VREG_ID_PM8058_L12), + RPM_VREG(RPM_VREG_ID_PM8058_L13), + RPM_VREG(RPM_VREG_ID_PM8058_L14), + RPM_VREG(RPM_VREG_ID_PM8058_L15), + RPM_VREG(RPM_VREG_ID_PM8058_L16), + RPM_VREG(RPM_VREG_ID_PM8058_L17), + RPM_VREG(RPM_VREG_ID_PM8058_L18), + RPM_VREG(RPM_VREG_ID_PM8058_L19), + RPM_VREG(RPM_VREG_ID_PM8058_L20), + RPM_VREG(RPM_VREG_ID_PM8058_L21), + RPM_VREG(RPM_VREG_ID_PM8058_L22), + RPM_VREG(RPM_VREG_ID_PM8058_L23), + RPM_VREG(RPM_VREG_ID_PM8058_L24), + RPM_VREG(RPM_VREG_ID_PM8058_L25), + RPM_VREG(RPM_VREG_ID_PM8058_S0), + RPM_VREG(RPM_VREG_ID_PM8058_S1), + RPM_VREG(RPM_VREG_ID_PM8058_S2), + RPM_VREG(RPM_VREG_ID_PM8058_S3), + RPM_VREG(RPM_VREG_ID_PM8058_S4), + RPM_VREG(RPM_VREG_ID_PM8058_LVS0), + RPM_VREG(RPM_VREG_ID_PM8058_LVS1), + RPM_VREG(RPM_VREG_ID_PM8058_NCP), + RPM_VREG(RPM_VREG_ID_PM8901_L0), + RPM_VREG(RPM_VREG_ID_PM8901_L1), + RPM_VREG(RPM_VREG_ID_PM8901_L2), + RPM_VREG(RPM_VREG_ID_PM8901_L3), + RPM_VREG(RPM_VREG_ID_PM8901_L4), + RPM_VREG(RPM_VREG_ID_PM8901_L5), + RPM_VREG(RPM_VREG_ID_PM8901_L6), + RPM_VREG(RPM_VREG_ID_PM8901_S2), + RPM_VREG(RPM_VREG_ID_PM8901_S3), + RPM_VREG(RPM_VREG_ID_PM8901_S4), + RPM_VREG(RPM_VREG_ID_PM8901_LVS0), + RPM_VREG(RPM_VREG_ID_PM8901_LVS1), + RPM_VREG(RPM_VREG_ID_PM8901_LVS2), + RPM_VREG(RPM_VREG_ID_PM8901_LVS3), + RPM_VREG(RPM_VREG_ID_PM8901_MVS0), +}; + +static struct platform_device *early_regulators[] __initdata = { + &msm_device_saw_s0, + &msm_device_saw_s1, +#ifdef CONFIG_PMIC8058 + &rpm_vreg_device[RPM_VREG_ID_PM8058_S0], + &rpm_vreg_device[RPM_VREG_ID_PM8058_S1], +#endif +}; + +static struct platform_device *early_devices[] __initdata = { +#ifdef CONFIG_MSM_BUS_SCALING + &msm_bus_apps_fabric, + &msm_bus_sys_fabric, + &msm_bus_mm_fabric, + &msm_bus_sys_fpb, + &msm_bus_cpss_fpb, +#endif + &msm_device_dmov_adm0, + &msm_device_dmov_adm1, +}; + +#if (defined(CONFIG_MARIMBA_CORE)) && \ + (defined(CONFIG_MSM_BT_POWER) || defined(CONFIG_MSM_BT_POWER_MODULE)) + +static int bluetooth_power(int); +static struct platform_device msm_bt_power_device = { + .name = "bt_power", + .id = -1, + .dev = { + .platform_data = &bluetooth_power, + }, +}; +#endif + +static struct platform_device msm_tsens_device = { + .name = "tsens-tm", + .id = -1, +}; + +static struct platform_device *rumi_sim_devices[] __initdata = { + &smc91x_device, + &msm_device_uart_dm12, +#ifdef CONFIG_I2C_QUP + &msm_gsbi3_qup_i2c_device, + &msm_gsbi4_qup_i2c_device, + &msm_gsbi7_qup_i2c_device, + &msm_gsbi8_qup_i2c_device, + &msm_gsbi9_qup_i2c_device, + &msm_gsbi12_qup_i2c_device, +#endif +#ifdef CONFIG_I2C_SSBI + &msm_device_ssbi1, + &msm_device_ssbi2, + &msm_device_ssbi3, +#endif +#ifdef CONFIG_ANDROID_PMEM + &android_pmem_device, + &android_pmem_adsp_device, + &android_pmem_audio_device, + &android_pmem_smipool_device, +#endif +#ifdef CONFIG_MSM_ROTATOR + &msm_rotator_device, +#endif + &msm_fb_device, + &msm_kgsl_3d0, + &msm_kgsl_2d0, + &msm_kgsl_2d1, + &lcdc_samsung_panel_device, +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL + &hdmi_msm_device, +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ +#ifdef CONFIG_MSM_CAMERA +#ifdef CONFIG_MT9E013 + &msm_camera_sensor_mt9e013, +#endif +#ifdef CONFIG_IMX074 + &msm_camera_sensor_imx074, +#endif +#ifdef CONFIG_WEBCAM_OV7692 + &msm_camera_sensor_webcam_ov7692, +#endif +#ifdef CONFIG_WEBCAM_OV9726 + &msm_camera_sensor_webcam_ov9726, +#endif +#ifdef CONFIG_QS_S5K4E1 + &msm_camera_sensor_qs_s5k4e1, +#endif +#endif +#ifdef CONFIG_MSM_GEMINI + &msm_gemini_device, +#endif +#ifdef CONFIG_MSM_VPE + &msm_vpe_device, +#endif + &msm_device_vidc, +}; + +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) +enum { + SX150X_CORE, + SX150X_DOCKING, + SX150X_SURF, + SX150X_LEFT_FHA, + SX150X_RIGHT_FHA, + SX150X_SOUTH, + SX150X_NORTH, + SX150X_CORE_FLUID, +}; + +static struct sx150x_platform_data sx150x_data[] __initdata = { + [SX150X_CORE] = { + .gpio_base = GPIO_CORE_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x0c08, + .io_pulldn_ena = 0x4060, + .io_open_drain_ena = 0x000c, + .io_polarity = 0, + .irq_summary = -1, /* see fixup_i2c_configs() */ + .irq_base = GPIO_EXPANDER_IRQ_BASE, + }, + [SX150X_DOCKING] = { + .gpio_base = GPIO_DOCKING_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x5e06, + .io_pulldn_ena = 0x81b8, + .io_open_drain_ena = 0, + .io_polarity = 0, + .irq_summary = PM8058_GPIO_IRQ(PM8058_IRQ_BASE, + UI_INT2_N), + .irq_base = GPIO_EXPANDER_IRQ_BASE + + GPIO_DOCKING_EXPANDER_BASE - + GPIO_EXPANDER_GPIO_BASE, + }, + [SX150X_SURF] = { + .gpio_base = GPIO_SURF_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0, + .io_pulldn_ena = 0, + .io_open_drain_ena = 0, + .io_polarity = 0, + .irq_summary = PM8058_GPIO_IRQ(PM8058_IRQ_BASE, + UI_INT1_N), + .irq_base = GPIO_EXPANDER_IRQ_BASE + + GPIO_SURF_EXPANDER_BASE - + GPIO_EXPANDER_GPIO_BASE, + }, + [SX150X_LEFT_FHA] = { + .gpio_base = GPIO_LEFT_KB_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0, + .io_pulldn_ena = 0x40, + .io_open_drain_ena = 0, + .io_polarity = 0, + .irq_summary = PM8058_GPIO_IRQ(PM8058_IRQ_BASE, + UI_INT3_N), + .irq_base = GPIO_EXPANDER_IRQ_BASE + + GPIO_LEFT_KB_EXPANDER_BASE - + GPIO_EXPANDER_GPIO_BASE, + }, + [SX150X_RIGHT_FHA] = { + .gpio_base = GPIO_RIGHT_KB_EXPANDER_BASE, + .oscio_is_gpo = true, + .io_pullup_ena = 0, + .io_pulldn_ena = 0, + .io_open_drain_ena = 0, + .io_polarity = 0, + .irq_summary = PM8058_GPIO_IRQ(PM8058_IRQ_BASE, + UI_INT3_N), + .irq_base = GPIO_EXPANDER_IRQ_BASE + + GPIO_RIGHT_KB_EXPANDER_BASE - + GPIO_EXPANDER_GPIO_BASE, + }, + [SX150X_SOUTH] = { + .gpio_base = GPIO_SOUTH_EXPANDER_BASE, + .irq_base = GPIO_EXPANDER_IRQ_BASE + + GPIO_SOUTH_EXPANDER_BASE - + GPIO_EXPANDER_GPIO_BASE, + .irq_summary = PM8058_GPIO_IRQ(PM8058_IRQ_BASE, UI_INT3_N), + }, + [SX150X_NORTH] = { + .gpio_base = GPIO_NORTH_EXPANDER_BASE, + .irq_base = GPIO_EXPANDER_IRQ_BASE + + GPIO_NORTH_EXPANDER_BASE - + GPIO_EXPANDER_GPIO_BASE, + .irq_summary = PM8058_GPIO_IRQ(PM8058_IRQ_BASE, UI_INT3_N), + .oscio_is_gpo = true, + .io_open_drain_ena = 0x30, + }, + [SX150X_CORE_FLUID] = { + .gpio_base = GPIO_CORE_EXPANDER_BASE, + .oscio_is_gpo = false, + .io_pullup_ena = 0x0408, + .io_pulldn_ena = 0x4060, + .io_open_drain_ena = 0x0008, + .io_polarity = 0, + .irq_summary = -1, /* see fixup_i2c_configs() */ + .irq_base = GPIO_EXPANDER_IRQ_BASE, + }, +}; + +#ifdef CONFIG_SENSORS_MSM_ADC +/* Configuration of EPM expander is done when client + * request an adc read + */ +static struct sx150x_platform_data sx150x_epmdata = { + .gpio_base = GPIO_EPM_EXPANDER_BASE, + .irq_base = GPIO_EXPANDER_IRQ_BASE + + GPIO_EPM_EXPANDER_BASE - + GPIO_EXPANDER_GPIO_BASE, + .irq_summary = -1, +}; +#endif + +/* sx150x_low_power_cfg * + * This data and init function are used to put unused gpio-expander output + * lines into their low-power states at boot. The init + * function must be deferred until a later init stage because the i2c + * gpio expander drivers do not probe until after they are registered + * (see register_i2c_devices) and the work-queues for those registrations + * are processed. Because these lines are unused, there is no risk of + * competing with a device driver for the gpio. + * + * gpio lines whose low-power states are input are naturally in their low- + * power configurations once probed, see the platform data structures above. + */ +struct sx150x_low_power_cfg { + unsigned gpio; + unsigned val; +}; + +static struct sx150x_low_power_cfg +common_sx150x_lp_cfgs[] __initdata = { + {GPIO_WLAN_DEEP_SLEEP_N, 0}, + {GPIO_EXT_GPS_LNA_EN, 0}, + {GPIO_MSM_WAKES_BT, 0}, + {GPIO_USB_UICC_EN, 0}, + {GPIO_BATT_GAUGE_EN, 0}, +}; + +static struct sx150x_low_power_cfg +surf_ffa_sx150x_lp_cfgs[] __initdata = { + {GPIO_MIPI_DSI_RST_N, 0}, + {GPIO_DONGLE_PWR_EN, 0}, + {GPIO_CAP_TS_SLEEP, 1}, + {GPIO_WEB_CAMIF_RESET_N, 0}, +}; + +static void __init +cfg_gpio_low_power(struct sx150x_low_power_cfg *cfgs, unsigned nelems) +{ + unsigned n; + int rc; + + for (n = 0; n < nelems; ++n) { + rc = gpio_request(cfgs[n].gpio, NULL); + if (!rc) { + rc = gpio_direction_output(cfgs[n].gpio, cfgs[n].val); + gpio_free(cfgs[n].gpio); + } + + if (rc) { + printk(KERN_NOTICE "%s: failed to sleep gpio %d: %d\n", + __func__, cfgs[n].gpio, rc); + } + } +} + +static int __init cfg_sx150xs_low_power(void) +{ + cfg_gpio_low_power(common_sx150x_lp_cfgs, + ARRAY_SIZE(common_sx150x_lp_cfgs)); + if (!machine_is_msm8x60_fluid()) + cfg_gpio_low_power(surf_ffa_sx150x_lp_cfgs, + ARRAY_SIZE(surf_ffa_sx150x_lp_cfgs)); + return 0; +} +module_init(cfg_sx150xs_low_power); + +#ifdef CONFIG_I2C +static struct i2c_board_info core_expander_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1509q", 0x3e), + .platform_data = &sx150x_data[SX150X_CORE] + }, +}; + +static struct i2c_board_info docking_expander_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1509q", 0x3f), + .platform_data = &sx150x_data[SX150X_DOCKING] + }, +}; + +static struct i2c_board_info surf_expanders_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1509q", 0x70), + .platform_data = &sx150x_data[SX150X_SURF] + } +}; + +static struct i2c_board_info fha_expanders_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1508q", 0x21), + .platform_data = &sx150x_data[SX150X_LEFT_FHA] + }, + { + I2C_BOARD_INFO("sx1508q", 0x22), + .platform_data = &sx150x_data[SX150X_RIGHT_FHA] + } +}; + +static struct i2c_board_info fluid_expanders_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1508q", 0x23), + .platform_data = &sx150x_data[SX150X_SOUTH] + }, + { + I2C_BOARD_INFO("sx1508q", 0x20), + .platform_data = &sx150x_data[SX150X_NORTH] + } +}; + +static struct i2c_board_info fluid_core_expander_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("sx1509q", 0x3e), + .platform_data = &sx150x_data[SX150X_CORE_FLUID] + }, +}; + +#ifdef CONFIG_SENSORS_MSM_ADC +static struct i2c_board_info fluid_expanders_i2c_epm_info[] = { + { + I2C_BOARD_INFO("sx1509q", 0x3e), + .platform_data = &sx150x_epmdata + }, +}; +#endif +#endif +#endif + +#ifdef CONFIG_SENSORS_MSM_ADC +static struct resource resources_adc[] = { + { + .start = PM8058_ADC_IRQ(PM8058_IRQ_BASE), + .end = PM8058_ADC_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct adc_access_fn xoadc_fn = { + pm8058_xoadc_select_chan_and_start_conv, + pm8058_xoadc_read_adc_code, + pm8058_xoadc_get_properties, + pm8058_xoadc_slot_request, + pm8058_xoadc_restore_slot, + pm8058_xoadc_calibrate, +}; + +#if defined(CONFIG_I2C) && \ + (defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE)) +static struct regulator *vreg_adc_epm1; + +static struct i2c_client *epm_expander_i2c_register_board(void) + +{ + struct i2c_adapter *i2c_adap; + struct i2c_client *client = NULL; + i2c_adap = i2c_get_adapter(0x0); + + if (i2c_adap == NULL) + printk(KERN_ERR "\nepm_expander_i2c_adapter is NULL\n"); + + if (i2c_adap != NULL) + client = i2c_new_device(i2c_adap, + &fluid_expanders_i2c_epm_info[0]); + return client; + +} + +static unsigned int msm_adc_gpio_configure_expander_enable(void) +{ + int rc = 0; + static struct i2c_client *epm_i2c_client; + + printk(KERN_DEBUG "Enter msm_adc_gpio_configure_expander_enable\n"); + + vreg_adc_epm1 = regulator_get(NULL, "8058_s3"); + + if (IS_ERR(vreg_adc_epm1)) { + printk(KERN_ERR "%s: Unable to get 8058_s3\n", __func__); + return 0; + } + + rc = regulator_set_voltage(vreg_adc_epm1, 1800000, 1800000); + if (rc) + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable: " + "regulator set voltage failed\n"); + + rc = regulator_enable(vreg_adc_epm1); + if (rc) { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable: " + "Error while enabling regulator for epm s3 %d\n", rc); + return rc; + } + + printk(KERN_DEBUG "msm_adc_gpio_configure_expander_enable: Start" + " setting the value of the EPM 3.3, 5v and lvlsft\n"); + + msleep(1000); + + rc = gpio_request(GPIO_EPM_5V_BOOST_EN, "boost_epm_5v"); + if (!rc) { + printk(KERN_DEBUG "msm_adc_gpio_configure_expander_enable: " + "Configure 5v boost\n"); + gpio_direction_output(GPIO_EPM_5V_BOOST_EN, 1); + } else { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable: " + "Error for epm 5v boost en\n"); + goto exit_vreg_epm; + } + + msleep(500); + + rc = gpio_request(GPIO_EPM_3_3V_EN, "epm_3_3v"); + if (!rc) { + gpio_direction_output(GPIO_EPM_3_3V_EN, 1); + printk(KERN_DEBUG "msm_adc_gpio_configure_expander_enable: " + "Configure epm 3.3v\n"); + } else { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable: " + "Error for gpio 3.3ven\n"); + goto exit_vreg_epm; + } + msleep(500); + + printk(KERN_DEBUG "msm_adc_gpio_configure_expander_enable: " + "Trying to request EPM LVLSFT_EN\n"); + rc = gpio_request(GPIO_EPM_LVLSFT_EN, "lvsft_en"); + if (!rc) { + gpio_direction_output(GPIO_EPM_LVLSFT_EN, 1); + printk(KERN_DEBUG "msm_adc_gpio_configure_expander_enable: " + "Configure the lvlsft\n"); + } else { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable: " + "Error for epm lvlsft_en\n"); + goto exit_vreg_epm; + } + + msleep(500); + + if (!epm_i2c_client) + epm_i2c_client = epm_expander_i2c_register_board(); + + rc = gpio_request(GPIO_PWR_MON_ENABLE, "pwr_mon_enable"); + if (!rc) + rc = gpio_direction_output(GPIO_PWR_MON_ENABLE, 1); + if (rc) { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable" + ": GPIO PWR MON Enable issue\n"); + goto exit_vreg_epm; + } + + msleep(1000); + + rc = gpio_request(GPIO_ADC1_PWDN_N, "adc1_pwdn"); + if (!rc) { + rc = gpio_direction_output(GPIO_ADC1_PWDN_N, 1); + if (rc) { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable" + ": ADC1_PWDN error direction out\n"); + goto exit_vreg_epm; + } + } + + msleep(100); + + rc = gpio_request(GPIO_ADC2_PWDN_N, "adc2_pwdn"); + if (!rc) { + rc = gpio_direction_output(GPIO_ADC2_PWDN_N, 1); + if (rc) { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable" + ": ADC2_PWD error direction out\n"); + goto exit_vreg_epm; + } + } + + msleep(1000); + + rc = gpio_request(GPIO_PWR_MON_START, "pwr_mon_start"); + if (!rc) { + rc = gpio_direction_output(GPIO_PWR_MON_START, 0); + if (rc) { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable" + "Gpio request problem %d\n", rc); + goto exit_vreg_epm; + } + } + + rc = gpio_request(GPIO_EPM_SPI_ADC1_CS_N, "spi_adc1_cs"); + if (!rc) { + rc = gpio_direction_output(GPIO_EPM_SPI_ADC1_CS_N, 0); + if (rc) { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable" + ": EPM_SPI_ADC1_CS_N error\n"); + goto exit_vreg_epm; + } + } + + rc = gpio_request(GPIO_EPM_SPI_ADC2_CS_N, "spi_adc2_cs"); + if (!rc) { + rc = gpio_direction_output(GPIO_EPM_SPI_ADC2_CS_N, 0); + if (rc) { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable" + ": EPM_SPI_ADC2_Cs_N error\n"); + goto exit_vreg_epm; + } + } + + printk(KERN_DEBUG "msm_adc_gpio_configure_expander_enable: Set " + "the power monitor reset for epm\n"); + + rc = gpio_request(GPIO_PWR_MON_RESET_N, "pwr_mon_reset_n"); + if (!rc) { + gpio_direction_output(GPIO_PWR_MON_RESET_N, 0); + if (rc) { + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable" + ": Error in the power mon reset\n"); + goto exit_vreg_epm; + } + } + + msleep(1000); + + gpio_set_value_cansleep(GPIO_PWR_MON_RESET_N, 1); + + msleep(500); + + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC1_CS_N, 1); + + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC2_CS_N, 1); + + return rc; + +exit_vreg_epm: + regulator_disable(vreg_adc_epm1); + + printk(KERN_ERR "msm_adc_gpio_configure_expander_enable: Exit." + " rc = %d.\n", rc); + return rc; +}; + +static unsigned int msm_adc_gpio_configure_expander_disable(void) +{ + int rc = 0; + + gpio_set_value_cansleep(GPIO_PWR_MON_RESET_N, 0); + gpio_free(GPIO_PWR_MON_RESET_N); + + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC1_CS_N, 0); + gpio_free(GPIO_EPM_SPI_ADC1_CS_N); + + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC2_CS_N, 0); + gpio_free(GPIO_EPM_SPI_ADC2_CS_N); + + gpio_set_value_cansleep(GPIO_PWR_MON_START, 0); + gpio_free(GPIO_PWR_MON_START); + + gpio_direction_output(GPIO_ADC1_PWDN_N, 0); + gpio_free(GPIO_ADC1_PWDN_N); + + gpio_direction_output(GPIO_ADC2_PWDN_N, 0); + gpio_free(GPIO_ADC2_PWDN_N); + + gpio_set_value_cansleep(GPIO_PWR_MON_ENABLE, 0); + gpio_free(GPIO_PWR_MON_ENABLE); + + gpio_set_value_cansleep(GPIO_EPM_LVLSFT_EN, 0); + gpio_free(GPIO_EPM_LVLSFT_EN); + + gpio_set_value_cansleep(GPIO_EPM_5V_BOOST_EN, 0); + gpio_free(GPIO_EPM_5V_BOOST_EN); + + gpio_set_value_cansleep(GPIO_EPM_3_3V_EN, 0); + gpio_free(GPIO_EPM_3_3V_EN); + + rc = regulator_disable(vreg_adc_epm1); + if (rc) + printk(KERN_DEBUG "msm_adc_gpio_configure_expander_disable: " + "Error while enabling regulator for epm s3 %d\n", rc); + regulator_put(vreg_adc_epm1); + + printk(KERN_DEBUG "Exi msm_adc_gpio_configure_expander_disable\n"); + return rc; +}; + +unsigned int msm_adc_gpio_expander_enable(int cs_enable) +{ + int rc = 0; + + printk(KERN_DEBUG "msm_adc_gpio_expander_enable: cs_enable = %d", + cs_enable); + + if (cs_enable < 16) { + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC1_CS_N, 0); + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC2_CS_N, 1); + } else { + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC2_CS_N, 0); + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC1_CS_N, 1); + } + return rc; +}; + +unsigned int msm_adc_gpio_expander_disable(int cs_disable) +{ + int rc = 0; + + printk(KERN_DEBUG "Enter msm_adc_gpio_expander_disable.\n"); + + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC1_CS_N, 1); + + gpio_set_value_cansleep(GPIO_EPM_SPI_ADC2_CS_N, 1); + + return rc; +}; +#endif + +static struct msm_adc_channels msm_adc_channels_data[] = { + {"vbatt", CHANNEL_ADC_VBATT, 0, &xoadc_fn, CHAN_PATH_TYPE2, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE3, scale_default}, + {"vcoin", CHANNEL_ADC_VCOIN, 0, &xoadc_fn, CHAN_PATH_TYPE1, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE2, scale_default}, + {"vcharger_channel", CHANNEL_ADC_VCHG, 0, &xoadc_fn, CHAN_PATH_TYPE3, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE4, scale_default}, + {"charger_current_monitor", CHANNEL_ADC_CHG_MONITOR, 0, &xoadc_fn, + CHAN_PATH_TYPE4, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE1, scale_default}, + {"vph_pwr", CHANNEL_ADC_VPH_PWR, 0, &xoadc_fn, CHAN_PATH_TYPE5, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE3, scale_default}, + {"usb_vbus", CHANNEL_ADC_USB_VBUS, 0, &xoadc_fn, CHAN_PATH_TYPE11, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE3, scale_default}, + {"pmic_therm", CHANNEL_ADC_DIE_TEMP, 0, &xoadc_fn, CHAN_PATH_TYPE12, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE1, scale_pmic_therm}, + {"pmic_therm_4K", CHANNEL_ADC_DIE_TEMP_4K, 0, &xoadc_fn, + CHAN_PATH_TYPE12, + ADC_CONFIG_TYPE1, ADC_CALIB_CONFIG_TYPE7, scale_pmic_therm}, + {"xo_therm", CHANNEL_ADC_XOTHERM, 0, &xoadc_fn, CHAN_PATH_TYPE_NONE, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE5, tdkntcgtherm}, + {"xo_therm_4K", CHANNEL_ADC_XOTHERM_4K, 0, &xoadc_fn, + CHAN_PATH_TYPE_NONE, + ADC_CONFIG_TYPE1, ADC_CALIB_CONFIG_TYPE6, tdkntcgtherm}, + {"hdset_detect", CHANNEL_ADC_HDSET, 0, &xoadc_fn, CHAN_PATH_TYPE6, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE1, scale_default}, + {"chg_batt_amon", CHANNEL_ADC_BATT_AMON, 0, &xoadc_fn, CHAN_PATH_TYPE10, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE1, + scale_xtern_chgr_cur}, + {"msm_therm", CHANNEL_ADC_MSM_THERM, 0, &xoadc_fn, CHAN_PATH_TYPE8, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE2, scale_msm_therm}, + {"batt_therm", CHANNEL_ADC_BATT_THERM, 0, &xoadc_fn, CHAN_PATH_TYPE7, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE2, scale_batt_therm}, + {"batt_id", CHANNEL_ADC_BATT_ID, 0, &xoadc_fn, CHAN_PATH_TYPE9, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE2, scale_default}, + {"ref_625mv", CHANNEL_ADC_625_REF, 0, &xoadc_fn, CHAN_PATH_TYPE15, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE2, scale_default}, + {"ref_1250mv", CHANNEL_ADC_1250_REF, 0, &xoadc_fn, CHAN_PATH_TYPE13, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE2, scale_default}, + {"ref_325mv", CHANNEL_ADC_325_REF, 0, &xoadc_fn, CHAN_PATH_TYPE14, + ADC_CONFIG_TYPE2, ADC_CALIB_CONFIG_TYPE2, scale_default}, +}; + +static char *msm_adc_fluid_device_names[] = { + "ADS_ADC1", + "ADS_ADC2", +}; + +static struct msm_adc_platform_data msm_adc_pdata = { + .channel = msm_adc_channels_data, + .num_chan_supported = ARRAY_SIZE(msm_adc_channels_data), +#if defined(CONFIG_I2C) && \ + (defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE)) + .adc_gpio_enable = msm_adc_gpio_expander_enable, + .adc_gpio_disable = msm_adc_gpio_expander_disable, + .adc_fluid_enable = msm_adc_gpio_configure_expander_enable, + .adc_fluid_disable = msm_adc_gpio_configure_expander_disable, +#endif +}; + +static struct platform_device msm_adc_device = { + .name = "msm_adc", + .id = -1, + .dev = { + .platform_data = &msm_adc_pdata, + }, +}; + +static void pmic8058_xoadc_mpp_config(void) +{ + int rc; + + rc = pm8901_mpp_config_digital_out(XOADC_MPP_4, + PM8901_MPP_DIG_LEVEL_S4, PM_MPP_DOUT_CTL_LOW); + if (rc) + pr_err("%s: Config mpp4 on pmic 8901 failed\n", __func__); + + rc = pm8058_mpp_config_analog_input(XOADC_MPP_3, + PM_MPP_AIN_AMUX_CH5, PM_MPP_AOUT_CTL_DISABLE); + if (rc) + pr_err("%s: Config mpp3 on pmic 8058 failed\n", __func__); + + rc = pm8058_mpp_config_analog_input(XOADC_MPP_5, + PM_MPP_AIN_AMUX_CH9, PM_MPP_AOUT_CTL_DISABLE); + if (rc) + pr_err("%s: Config mpp5 on pmic 8058 failed\n", __func__); + + rc = pm8058_mpp_config_analog_input(XOADC_MPP_7, + PM_MPP_AIN_AMUX_CH6, PM_MPP_AOUT_CTL_DISABLE); + if (rc) + pr_err("%s: Config mpp7 on pmic 8058 failed\n", __func__); + + rc = pm8058_mpp_config_analog_input(XOADC_MPP_8, + PM_MPP_AIN_AMUX_CH8, PM_MPP_AOUT_CTL_DISABLE); + if (rc) + pr_err("%s: Config mpp8 on pmic 8058 failed\n", __func__); + + rc = pm8058_mpp_config_analog_input(XOADC_MPP_10, + PM_MPP_AIN_AMUX_CH7, PM_MPP_AOUT_CTL_DISABLE); + if (rc) + pr_err("%s: Config mpp10 on pmic 8058 failed\n", __func__); +} + +static struct regulator *vreg_ldo18_adc; + +static int pmic8058_xoadc_vreg_config(int on) +{ + int rc; + + if (on) { + rc = regulator_enable(vreg_ldo18_adc); + if (rc) + pr_err("%s: Enable of regulator ldo18_adc " + "failed\n", __func__); + } else { + rc = regulator_disable(vreg_ldo18_adc); + if (rc) + pr_err("%s: Disable of regulator ldo18_adc " + "failed\n", __func__); + } + + return rc; +} + +static int pmic8058_xoadc_vreg_setup(void) +{ + int rc; + + vreg_ldo18_adc = regulator_get(NULL, "8058_l18"); + if (IS_ERR(vreg_ldo18_adc)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_ldo18_adc)); + rc = PTR_ERR(vreg_ldo18_adc); + goto fail; + } + + rc = regulator_set_voltage(vreg_ldo18_adc, 2200000, 2200000); + if (rc) { + pr_err("%s: unable to set ldo18 voltage to 2.2V\n", __func__); + goto fail; + } + + return rc; +fail: + regulator_put(vreg_ldo18_adc); + return rc; +} + +static void pmic8058_xoadc_vreg_shutdown(void) +{ + regulator_put(vreg_ldo18_adc); +} + +/* usec. For this ADC, + * this time represents clk rate @ txco w/ 1024 decimation ratio. + * Each channel has different configuration, thus at the time of starting + * the conversion, xoadc will return actual conversion time + * */ +static struct adc_properties pm8058_xoadc_data = { + .adc_reference = 2200, /* milli-voltage for this adc */ + .bitresolution = 15, + .bipolar = 0, + .conversiontime = 54, +}; + +static struct xoadc_platform_data xoadc_pdata = { + .xoadc_prop = &pm8058_xoadc_data, + .xoadc_mpp_config = pmic8058_xoadc_mpp_config, + .xoadc_vreg_set = pmic8058_xoadc_vreg_config, + .xoadc_num = XOADC_PMIC_0, + .xoadc_vreg_setup = pmic8058_xoadc_vreg_setup, + .xoadc_vreg_shutdown = pmic8058_xoadc_vreg_shutdown, +}; +#endif + +#ifdef CONFIG_MSM_SDIO_AL + +static unsigned mdm2ap_status = 140; + +static int configure_mdm2ap_status(int on) +{ + int ret = 0; + if (on) + ret = msm_gpiomux_get(mdm2ap_status); + else + ret = msm_gpiomux_put(mdm2ap_status); + + if (ret) + pr_err("%s: mdm2ap_status config failed, on = %d\n", __func__, + on); + + return ret; +} + + +static int get_mdm2ap_status(void) +{ + return gpio_get_value(mdm2ap_status); +} + +static struct sdio_al_platform_data sdio_al_pdata = { + .config_mdm2ap_status = configure_mdm2ap_status, + .get_mdm2ap_status = get_mdm2ap_status, + .allow_sdioc_version_major_2 = 0, + .peer_sdioc_version_minor = 0x0101, + .peer_sdioc_version_major = 0x0004, + .peer_sdioc_boot_version_minor = 0x0001, + .peer_sdioc_boot_version_major = 0x0003 +}; + +struct platform_device msm_device_sdio_al = { + .name = "msm_sdio_al", + .id = -1, + .dev = { + .platform_data = &sdio_al_pdata, + }, +}; + +#endif /* CONFIG_MSM_SDIO_AL */ + +static struct platform_device *charm_devices[] __initdata = { + &msm_charm_modem, +#ifdef CONFIG_MSM_SDIO_AL + &msm_device_sdio_al, +#endif +}; + +static struct platform_device *surf_devices[] __initdata = { + &msm_device_smd, + &msm_device_uart_dm12, +#ifdef CONFIG_I2C_QUP + &msm_gsbi3_qup_i2c_device, + &msm_gsbi4_qup_i2c_device, + &msm_gsbi7_qup_i2c_device, + &msm_gsbi8_qup_i2c_device, + &msm_gsbi9_qup_i2c_device, + &msm_gsbi12_qup_i2c_device, +#endif +#ifdef CONFIG_SERIAL_MSM_HS + &msm_device_uart_dm1, +#endif +#ifdef CONFIG_I2C_SSBI + &msm_device_ssbi1, + &msm_device_ssbi2, + &msm_device_ssbi3, +#endif +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + &isp1763_device, +#endif + + &asoc_msm_pcm, + &asoc_msm_dai0, + &asoc_msm_dai1, +#if defined (CONFIG_MSM_8x60_VOIP) + &asoc_msm_mvs, + &asoc_mvs_dai0, + &asoc_mvs_dai1, +#endif +#if defined(CONFIG_USB_GADGET_MSM_72K) || defined(CONFIG_USB_EHCI_HCD) + &msm_device_otg, +#endif +#ifdef CONFIG_USB_GADGET_MSM_72K + &msm_device_gadget_peripheral, +#endif +#ifdef CONFIG_USB_G_ANDROID + &android_usb_device, +#endif +#ifdef CONFIG_BATTERY_MSM + &msm_batt_device, +#endif +#ifdef CONFIG_ANDROID_PMEM + &android_pmem_device, + &android_pmem_adsp_device, + &android_pmem_audio_device, + &android_pmem_smipool_device, +#endif +#ifdef CONFIG_MSM_ROTATOR + &msm_rotator_device, +#endif + &msm_fb_device, + &msm_kgsl_3d0, + &msm_kgsl_2d0, + &msm_kgsl_2d1, + &lcdc_samsung_panel_device, +#ifdef CONFIG_FB_MSM_LCDC_SAMSUNG_OLED_PT + &lcdc_samsung_oled_panel_device, +#endif +#ifdef CONFIG_FB_MSM_LCDC_AUO_WVGA + &lcdc_auo_wvga_panel_device, +#endif +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL + &hdmi_msm_device, +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ +#ifdef CONFIG_FB_MSM_MIPI_DSI + &mipi_dsi_toshiba_panel_device, + &mipi_dsi_novatek_panel_device, +#endif +#ifdef CONFIG_MSM_CAMERA +#ifdef CONFIG_MT9E013 + &msm_camera_sensor_mt9e013, +#endif +#ifdef CONFIG_IMX074 + &msm_camera_sensor_imx074, +#endif +#ifdef CONFIG_WEBCAM_OV7692 + &msm_camera_sensor_webcam_ov7692, +#endif +#ifdef CONFIG_WEBCAM_OV9726 + &msm_camera_sensor_webcam_ov9726, +#endif +#ifdef CONFIG_QS_S5K4E1 + &msm_camera_sensor_qs_s5k4e1, +#endif +#endif +#ifdef CONFIG_MSM_GEMINI + &msm_gemini_device, +#endif +#ifdef CONFIG_MSM_VPE + &msm_vpe_device, +#endif + +#if defined(CONFIG_MSM_RPM_LOG) || defined(CONFIG_MSM_RPM_LOG_MODULE) + &msm_rpm_log_device, +#endif +#if defined(CONFIG_MSM_RPM_STATS_LOG) + &msm_rpm_stat_device, +#endif + &msm_device_vidc, +#if (defined(CONFIG_MARIMBA_CORE)) && \ + (defined(CONFIG_MSM_BT_POWER) || defined(CONFIG_MSM_BT_POWER_MODULE)) + &msm_bt_power_device, +#endif +#ifdef CONFIG_SENSORS_MSM_ADC + &msm_adc_device, +#endif +#ifdef CONFIG_PMIC8058 + &rpm_vreg_device[RPM_VREG_ID_PM8058_L0], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L1], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L2], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L3], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L4], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L5], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L6], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L7], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L8], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L9], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L10], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L11], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L12], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L13], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L14], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L15], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L16], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L17], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L18], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L19], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L20], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L21], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L22], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L23], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L24], + &rpm_vreg_device[RPM_VREG_ID_PM8058_L25], + &rpm_vreg_device[RPM_VREG_ID_PM8058_S2], + &rpm_vreg_device[RPM_VREG_ID_PM8058_S3], + &rpm_vreg_device[RPM_VREG_ID_PM8058_S4], + &rpm_vreg_device[RPM_VREG_ID_PM8058_LVS0], + &rpm_vreg_device[RPM_VREG_ID_PM8058_LVS1], + &rpm_vreg_device[RPM_VREG_ID_PM8058_NCP], +#endif +#ifdef CONFIG_PMIC8901 + &rpm_vreg_device[RPM_VREG_ID_PM8901_L0], + &rpm_vreg_device[RPM_VREG_ID_PM8901_L1], + &rpm_vreg_device[RPM_VREG_ID_PM8901_L2], + &rpm_vreg_device[RPM_VREG_ID_PM8901_L3], + &rpm_vreg_device[RPM_VREG_ID_PM8901_L4], + &rpm_vreg_device[RPM_VREG_ID_PM8901_L5], + &rpm_vreg_device[RPM_VREG_ID_PM8901_L6], + &rpm_vreg_device[RPM_VREG_ID_PM8901_S2], + &rpm_vreg_device[RPM_VREG_ID_PM8901_S3], + &rpm_vreg_device[RPM_VREG_ID_PM8901_S4], + &rpm_vreg_device[RPM_VREG_ID_PM8901_LVS0], + &rpm_vreg_device[RPM_VREG_ID_PM8901_LVS1], + &rpm_vreg_device[RPM_VREG_ID_PM8901_LVS2], + &rpm_vreg_device[RPM_VREG_ID_PM8901_LVS3], + &rpm_vreg_device[RPM_VREG_ID_PM8901_MVS0], +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCRYPTO) || \ + defined(CONFIG_CRYPTO_DEV_QCRYPTO_MODULE) + &qcrypto_device, +#endif + +#if defined(CONFIG_CRYPTO_DEV_QCEDEV) || \ + defined(CONFIG_CRYPTO_DEV_QCEDEV_MODULE) + &qcedev_device, +#endif + +#ifdef CONFIG_MSM_SDIO_AL + &msm_device_sdio_al, +#endif + +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) +#ifdef CONFIG_MSM_USE_TSIF1 + &msm_device_tsif[1], +#else + &msm_device_tsif[0], +#endif /* CONFIG_MSM_USE_TSIF1 */ +#endif /* CONFIG_TSIF */ + +#ifdef CONFIG_HW_RANDOM_MSM + &msm_device_rng, +#endif + + &msm_tsens_device, + +}; + +static struct memtype_reserve msm8x60_reserve_table[] __initdata = { + /* Kernel SMI memory pool for video core, used for firmware */ + /* and encoder, decoder scratch buffers */ + /* Kernel SMI memory pool should always precede the user space */ + /* SMI memory pool, as the video core will use offset address */ + /* from the Firmware base */ + [MEMTYPE_SMI_KERNEL] = { + .start = KERNEL_SMI_BASE, + .limit = KERNEL_SMI_SIZE, + .size = KERNEL_SMI_SIZE, + .flags = MEMTYPE_FLAGS_FIXED, + }, + /* User space SMI memory pool for video core */ + /* used for encoder, decoder input & output buffers */ + [MEMTYPE_SMI] = { + .start = USER_SMI_BASE, + .limit = USER_SMI_SIZE, + .flags = MEMTYPE_FLAGS_FIXED, + }, + [MEMTYPE_EBI0] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, + [MEMTYPE_EBI1] = { + .flags = MEMTYPE_FLAGS_1M_ALIGN, + }, +}; + +static void __init size_pmem_devices(void) +{ +#ifdef CONFIG_ANDROID_PMEM + android_pmem_adsp_pdata.size = pmem_adsp_size; + android_pmem_smipool_pdata.size = MSM_PMEM_SMIPOOL_SIZE; + android_pmem_audio_pdata.size = MSM_PMEM_AUDIO_SIZE; + android_pmem_pdata.size = pmem_sf_size; +#endif +} + +static void __init reserve_memory_for(struct android_pmem_platform_data *p) +{ + msm8x60_reserve_table[p->memory_type].size += p->size; +} + +static void __init reserve_pmem_memory(void) +{ +#ifdef CONFIG_ANDROID_PMEM + reserve_memory_for(&android_pmem_adsp_pdata); + reserve_memory_for(&android_pmem_smipool_pdata); + reserve_memory_for(&android_pmem_audio_pdata); + reserve_memory_for(&android_pmem_pdata); + msm8x60_reserve_table[MEMTYPE_EBI1].size += pmem_kernel_ebi1_size; +#endif +} + +static void __init msm8x60_calculate_reserve_sizes(void) +{ + size_pmem_devices(); + reserve_pmem_memory(); +} + +static int msm8x60_paddr_to_memtype(unsigned int paddr) +{ + if (paddr >= 0x40000000 && paddr < 0x60000000) + return MEMTYPE_EBI1; + if (paddr >= 0x38000000 && paddr < 0x40000000) + return MEMTYPE_SMI; + return MEMTYPE_NONE; +} + +static struct reserve_info msm8x60_reserve_info __initdata = { + .memtype_reserve_table = msm8x60_reserve_table, + .calculate_reserve_sizes = msm8x60_calculate_reserve_sizes, + .paddr_to_memtype = msm8x60_paddr_to_memtype, +}; + +static void __init msm8x60_reserve(void) +{ + reserve_info = &msm8x60_reserve_info; + msm_reserve(); +} + +#define EXT_CHG_VALID_MPP 10 +#define EXT_CHG_VALID_MPP_2 11 + +#ifdef CONFIG_ISL9519_CHARGER +static int isl_detection_setup(void) +{ + int ret = 0; + + ret = pm8058_mpp_config_digital_in(EXT_CHG_VALID_MPP, + PM8058_MPP_DIG_LEVEL_S3, + PM_MPP_DIN_TO_INT); + ret |= pm8058_mpp_config_bi_dir(EXT_CHG_VALID_MPP_2, + PM8058_MPP_DIG_LEVEL_S3, + PM_MPP_BI_PULLUP_10KOHM + ); + return ret; +} + +static struct isl_platform_data isl_data __initdata = { + .chgcurrent = 700, + .valid_n_gpio = PM8058_MPP_PM_TO_SYS(10), + .chg_detection_config = isl_detection_setup, + .max_system_voltage = 4200, + .min_system_voltage = 3200, + .term_current = 120, + .input_current = 2048, +}; + +static struct i2c_board_info isl_charger_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("isl9519q", 0x9), + .irq = PM8058_CBLPWR_IRQ(PM8058_IRQ_BASE), + .platform_data = &isl_data, + }, +}; +#endif + +#if defined(CONFIG_SMB137B_CHARGER) || defined(CONFIG_SMB137B_CHARGER_MODULE) +static int smb137b_detection_setup(void) +{ + int ret = 0; + + ret = pm8058_mpp_config_digital_in(EXT_CHG_VALID_MPP, + PM8058_MPP_DIG_LEVEL_S3, + PM_MPP_DIN_TO_INT); + ret |= pm8058_mpp_config_bi_dir(EXT_CHG_VALID_MPP_2, + PM8058_MPP_DIG_LEVEL_S3, + PM_MPP_BI_PULLUP_10KOHM); + return ret; +} + +static struct smb137b_platform_data smb137b_data __initdata = { + .chg_detection_config = smb137b_detection_setup, + .valid_n_gpio = PM8058_MPP_PM_TO_SYS(10), + .batt_mah_rating = 950, +}; + +static struct i2c_board_info smb137b_charger_i2c_info[] __initdata = { + { + I2C_BOARD_INFO("smb137b", 0x08), + .irq = PM8058_CBLPWR_IRQ(PM8058_IRQ_BASE), + .platform_data = &smb137b_data, + }, +}; +#endif + +#ifdef CONFIG_PMIC8058 +#define PMIC_GPIO_SDC3_DET 22 + +static int pm8058_gpios_init(void) +{ + int i; + int rc; + struct pm8058_gpio_cfg { + int gpio; + struct pm8058_gpio cfg; + }; + + struct pm8058_gpio_cfg gpio_cfgs[] = { + { /* FFA ethernet */ + 6, + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_DN, + .vin_sel = 2, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + { + PMIC_GPIO_SDC3_DET - 1, + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_30, + .vin_sel = 2, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, +#endif + { /* core&surf gpio expander */ + UI_INT1_N, + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S3, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, + { /* docking gpio expander */ + UI_INT2_N, + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S3, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, + { /* FHA/keypad gpio expanders */ + UI_INT3_N, + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S3, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }, + }, + { /* TouchDisc Interrupt */ + 5, + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_1P5, + .vin_sel = 2, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + } + }, + { /* Timpani Reset */ + 20, + { + .direction = PM_GPIO_DIR_OUT, + .output_value = 1, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .pull = PM_GPIO_PULL_DN, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .vin_sel = 2, + .inv_int_pol = 0, + } + }, + { /* PMIC ID interrupt */ + 36, + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_1P5, + .function = PM_GPIO_FUNC_NORMAL, + .vin_sel = 2, + .inv_int_pol = 0, + } + }, + }; + +#if defined(CONFIG_HAPTIC_ISA1200) || \ + defined(CONFIG_HAPTIC_ISA1200_MODULE) + + struct pm8058_gpio_cfg en_hap_gpio_cfg = { + PMIC_GPIO_HAP_ENABLE, + { + .direction = PM_GPIO_DIR_OUT, + .pull = PM_GPIO_PULL_NO, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + .vin_sel = 2, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 0, + } + + }; +#endif + +#if defined(CONFIG_PMIC8058_OTHC) || defined(CONFIG_PMIC8058_OTHC_MODULE) + struct pm8058_gpio_cfg line_in_gpio_cfg = { + 18, + { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_1P5, + .vin_sel = 2, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + } + }; +#endif + +#if defined(CONFIG_QS_S5K4E1) + { + struct pm8058_gpio_cfg qs_hc37_cam_pd_gpio_cfg = { + 26, + { + .direction = PM_GPIO_DIR_OUT, + .output_value = 0, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .pull = PM_GPIO_PULL_DN, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_NORMAL, + .vin_sel = 2, + .inv_int_pol = 0, + } + }; +#endif + +#if defined(CONFIG_HAPTIC_ISA1200) || \ + defined(CONFIG_HAPTIC_ISA1200_MODULE) + if (machine_is_msm8x60_fluid()) { + rc = pm8058_gpio_config(en_hap_gpio_cfg.gpio, + &en_hap_gpio_cfg.cfg); + if (rc < 0) { + pr_err("%s pmic haptics gpio config failed\n", + __func__); + return rc; + } + } +#endif + +#if defined(CONFIG_PMIC8058_OTHC) || defined(CONFIG_PMIC8058_OTHC_MODULE) + /* Line_in only for 8660 ffa & surf */ + if (machine_is_msm8x60_ffa() || machine_is_msm8x60_surf() || + machine_is_msm8x60_fusion() || + machine_is_msm8x60_fusn_ffa()) { + rc = pm8058_gpio_config(line_in_gpio_cfg.gpio, + &line_in_gpio_cfg.cfg); + if (rc < 0) { + pr_err("%s pmic line_in gpio config failed\n", + __func__); + return rc; + } + } +#endif + +#if defined(CONFIG_QS_S5K4E1) + /* qs_cam_hc37_cam_pd only for 8660 fluid qs camera*/ + if (machine_is_msm8x60_fluid()) { + rc = pm8058_gpio_config(qs_hc37_cam_pd_gpio_cfg.gpio, + &qs_hc37_cam_pd_gpio_cfg.cfg); + if (rc < 0) { + pr_err("%s pmic qs_hc37_cam_pd gpio config failed\n", + __func__); + return rc; + } + } + } +#endif + + for (i = 0; i < ARRAY_SIZE(gpio_cfgs); ++i) { + rc = pm8058_gpio_config(gpio_cfgs[i].gpio, + &gpio_cfgs[i].cfg); + if (rc < 0) { + pr_err("%s pmic gpio config failed\n", + __func__); + return rc; + } + } + + return 0; +} + +static const unsigned int ffa_keymap[] = { + KEY(0, 0, KEY_FN_F1), /* LS - PUSH1 */ + KEY(0, 1, KEY_UP), /* NAV - UP */ + KEY(0, 2, KEY_LEFT), /* NAV - LEFT */ + KEY(0, 3, KEY_VOLUMEUP), /* Shuttle SW_UP */ + + KEY(1, 0, KEY_FN_F2), /* LS - PUSH2 */ + KEY(1, 1, KEY_RIGHT), /* NAV - RIGHT */ + KEY(1, 2, KEY_DOWN), /* NAV - DOWN */ + KEY(1, 3, KEY_VOLUMEDOWN), + + KEY(2, 3, KEY_ENTER), /* SW_PUSH key */ + + KEY(4, 0, KEY_CAMERA_FOCUS), /* RS - PUSH1 */ + KEY(4, 1, KEY_UP), /* USER_UP */ + KEY(4, 2, KEY_LEFT), /* USER_LEFT */ + KEY(4, 3, KEY_HOME), /* Right switch: MIC Bd */ + KEY(4, 4, KEY_FN_F3), /* Reserved MIC */ + + KEY(5, 0, KEY_CAMERA), /* RS - PUSH2 */ + KEY(5, 1, KEY_RIGHT), /* USER_RIGHT */ + KEY(5, 2, KEY_DOWN), /* USER_DOWN */ + KEY(5, 3, KEY_BACK), /* Left switch: MIC */ + KEY(5, 4, KEY_MENU), /* Center switch: MIC */ +}; + +static struct resource resources_keypad[] = { + { + .start = PM8058_KEYPAD_IRQ(PM8058_IRQ_BASE), + .end = PM8058_KEYPAD_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .start = PM8058_KEYSTUCK_IRQ(PM8058_IRQ_BASE), + .end = PM8058_KEYSTUCK_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct matrix_keymap_data ffa_keymap_data = { + .keymap_size = ARRAY_SIZE(ffa_keymap), + .keymap = ffa_keymap, +}; + +static struct pmic8058_keypad_data ffa_keypad_data = { + .input_name = "ffa-keypad", + .input_phys_device = "ffa-keypad/input0", + .num_rows = 6, + .num_cols = 5, + .rows_gpio_start = 8, + .cols_gpio_start = 0, + .debounce_ms = {8, 10}, + .scan_delay_ms = 32, + .row_hold_ns = 91500, + .wakeup = 1, + .keymap_data = &ffa_keymap_data, +}; + +static const unsigned int fluid_keymap[] = { + KEY(0, 0, KEY_FN_F1), /* LS - PUSH1 */ + KEY(0, 1, KEY_UP), /* NAV - UP */ + KEY(0, 2, KEY_LEFT), /* NAV - LEFT */ + KEY(0, 3, KEY_VOLUMEDOWN), /* Shuttle SW_UP */ + + KEY(1, 0, KEY_FN_F2), /* LS - PUSH2 */ + KEY(1, 1, KEY_RIGHT), /* NAV - RIGHT */ + KEY(1, 2, KEY_DOWN), /* NAV - DOWN */ + KEY(1, 3, KEY_VOLUMEUP), + + KEY(2, 3, KEY_ENTER), /* SW_PUSH key */ + + KEY(4, 0, KEY_CAMERA_FOCUS), /* RS - PUSH1 */ + KEY(4, 1, KEY_UP), /* USER_UP */ + KEY(4, 2, KEY_LEFT), /* USER_LEFT */ + KEY(4, 3, KEY_HOME), /* Right switch: MIC Bd */ + KEY(4, 4, KEY_FN_F3), /* Reserved MIC */ + + KEY(5, 0, KEY_CAMERA_SNAPSHOT), /* RS - PUSH2 */ + KEY(5, 1, KEY_RIGHT), /* USER_RIGHT */ + KEY(5, 2, KEY_DOWN), /* USER_DOWN */ + KEY(5, 3, KEY_BACK), /* Left switch: MIC */ + KEY(5, 4, KEY_MENU), /* Center switch: MIC */ +}; + +static struct matrix_keymap_data fluid_keymap_data = { + .keymap_size = ARRAY_SIZE(fluid_keymap), + .keymap = fluid_keymap, +}; + +static struct pmic8058_keypad_data fluid_keypad_data = { + .input_name = "fluid-keypad", + .input_phys_device = "fluid-keypad/input0", + .num_rows = 6, + .num_cols = 5, + .rows_gpio_start = 8, + .cols_gpio_start = 0, + .debounce_ms = {8, 10}, + .scan_delay_ms = 32, + .row_hold_ns = 91500, + .wakeup = 1, + .keymap_data = &fluid_keymap_data, +}; + +static struct resource resources_pwrkey[] = { + { + .start = PM8058_PWRKEY_REL_IRQ(PM8058_IRQ_BASE), + .end = PM8058_PWRKEY_REL_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .start = PM8058_PWRKEY_PRESS_IRQ(PM8058_IRQ_BASE), + .end = PM8058_PWRKEY_PRESS_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct pmic8058_pwrkey_pdata pwrkey_pdata = { + .pull_up = 1, + .kpd_trigger_delay_us = 970, + .wakeup = 1, + .pwrkey_time_ms = 500, +}; + +static struct pmic8058_vibrator_pdata pmic_vib_pdata = { + .initial_vibrate_ms = 500, + .level_mV = 3000, + .max_timeout_ms = 15000, +}; + +#if defined(CONFIG_PMIC8058_OTHC) || defined(CONFIG_PMIC8058_OTHC_MODULE) +#define PM8058_OTHC_CNTR_BASE0 0xA0 +#define PM8058_OTHC_CNTR_BASE1 0x134 +#define PM8058_OTHC_CNTR_BASE2 0x137 +#define PM8058_LINE_IN_DET_GPIO PM8058_GPIO_PM_TO_SYS(18) + +static struct othc_accessory_info othc_accessories[] = { + { + .accessory = OTHC_SVIDEO_OUT, + .detect_flags = OTHC_MICBIAS_DETECT | OTHC_SWITCH_DETECT + | OTHC_ADC_DETECT, + .key_code = SW_VIDEOOUT_INSERT, + .enabled = false, + .adc_thres = { + .min_threshold = 20, + .max_threshold = 40, + }, + }, + { + .accessory = OTHC_ANC_HEADPHONE, + .detect_flags = OTHC_MICBIAS_DETECT | OTHC_GPIO_DETECT | + OTHC_SWITCH_DETECT, + .gpio = PM8058_LINE_IN_DET_GPIO, + .active_low = 1, + .key_code = SW_HEADPHONE_INSERT, + .enabled = true, + }, + { + .accessory = OTHC_ANC_HEADSET, + .detect_flags = OTHC_MICBIAS_DETECT | OTHC_GPIO_DETECT, + .gpio = PM8058_LINE_IN_DET_GPIO, + .active_low = 1, + .key_code = SW_HEADPHONE_INSERT, + .enabled = true, + }, + { + .accessory = OTHC_HEADPHONE, + .detect_flags = OTHC_MICBIAS_DETECT | OTHC_SWITCH_DETECT, + .key_code = SW_HEADPHONE_INSERT, + .enabled = true, + }, + { + .accessory = OTHC_MICROPHONE, + .detect_flags = OTHC_GPIO_DETECT, + .gpio = PM8058_LINE_IN_DET_GPIO, + .active_low = 1, + .key_code = SW_MICROPHONE_INSERT, + .enabled = true, + }, + { + .accessory = OTHC_HEADSET, + .detect_flags = OTHC_MICBIAS_DETECT, + .key_code = SW_HEADPHONE_INSERT, + .enabled = true, + }, +}; + +static struct othc_switch_info switch_info[] = { + { + .min_adc_threshold = 0, + .max_adc_threshold = 100, + .key_code = KEY_PLAYPAUSE, + }, + { + .min_adc_threshold = 100, + .max_adc_threshold = 200, + .key_code = KEY_REWIND, + }, + { + .min_adc_threshold = 200, + .max_adc_threshold = 500, + .key_code = KEY_FASTFORWARD, + }, +}; + +static struct othc_n_switch_config switch_config = { + .voltage_settling_time_ms = 0, + .num_adc_samples = 3, + .adc_channel = CHANNEL_ADC_HDSET, + .switch_info = switch_info, + .num_keys = ARRAY_SIZE(switch_info), + .default_sw_en = true, + .default_sw_idx = 0, +}; + +static struct hsed_bias_config hsed_bias_config = { + /* HSED mic bias config info */ + .othc_headset = OTHC_HEADSET_NO, + .othc_lowcurr_thresh_uA = 100, + .othc_highcurr_thresh_uA = 600, + .othc_hyst_prediv_us = 7800, + .othc_period_clkdiv_us = 62500, + .othc_hyst_clk_us = 121000, + .othc_period_clk_us = 312500, + .othc_wakeup = 1, +}; + +static struct othc_hsed_config hsed_config_1 = { + .hsed_bias_config = &hsed_bias_config, + /* + * The detection delay and switch reporting delay are + * required to encounter a hardware bug (spurious switch + * interrupts on slow insertion/removal of the headset). + * This will introduce a delay in reporting the accessory + * insertion and removal to the userspace. + */ + .detection_delay_ms = 1500, + /* Switch info */ + .switch_debounce_ms = 1500, + .othc_support_n_switch = false, + .switch_config = &switch_config, + .ir_gpio = -1, + /* Accessory info */ + .accessories_support = true, + .accessories = othc_accessories, + .othc_num_accessories = ARRAY_SIZE(othc_accessories), +}; + +static struct othc_regulator_config othc_reg = { + .regulator = "8058_l5", + .max_uV = 2850000, + .min_uV = 2850000, +}; + +/* MIC_BIAS0 is configured as normal MIC BIAS */ +static struct pmic8058_othc_config_pdata othc_config_pdata_0 = { + .micbias_select = OTHC_MICBIAS_0, + .micbias_capability = OTHC_MICBIAS, + .micbias_enable = OTHC_SIGNAL_OFF, + .micbias_regulator = &othc_reg, +}; + +/* MIC_BIAS1 is configured as HSED_BIAS for OTHC */ +static struct pmic8058_othc_config_pdata othc_config_pdata_1 = { + .micbias_select = OTHC_MICBIAS_1, + .micbias_capability = OTHC_MICBIAS_HSED, + .micbias_enable = OTHC_SIGNAL_PWM_TCXO, + .micbias_regulator = &othc_reg, + .hsed_config = &hsed_config_1, + .hsed_name = "8660_handset", +}; + +/* MIC_BIAS2 is configured as normal MIC BIAS */ +static struct pmic8058_othc_config_pdata othc_config_pdata_2 = { + .micbias_select = OTHC_MICBIAS_2, + .micbias_capability = OTHC_MICBIAS, + .micbias_enable = OTHC_SIGNAL_OFF, + .micbias_regulator = &othc_reg, +}; + +static struct resource resources_othc_0[] = { + { + .name = "othc_base", + .start = PM8058_OTHC_CNTR_BASE0, + .end = PM8058_OTHC_CNTR_BASE0, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource resources_othc_1[] = { + { + .start = PM8058_SW_1_IRQ(PM8058_IRQ_BASE), + .end = PM8058_SW_1_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .start = PM8058_IR_1_IRQ(PM8058_IRQ_BASE), + .end = PM8058_IR_1_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "othc_base", + .start = PM8058_OTHC_CNTR_BASE1, + .end = PM8058_OTHC_CNTR_BASE1, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource resources_othc_2[] = { + { + .name = "othc_base", + .start = PM8058_OTHC_CNTR_BASE2, + .end = PM8058_OTHC_CNTR_BASE2, + .flags = IORESOURCE_IO, + }, +}; + +static void __init msm8x60_init_pm8058_othc(void) +{ + int i; + + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) == 2 || + machine_is_msm8x60_fluid() || machine_is_msm8x60_fusion() || + machine_is_msm8x60_fusn_ffa()) { + /* 3-switch headset supported only by V2 FFA and FLUID */ + hsed_config_1.accessories_adc_support = true, + /* ADC based accessory detection works only on V2 and FLUID */ + hsed_config_1.accessories_adc_channel = CHANNEL_ADC_HDSET, + hsed_config_1.othc_support_n_switch = true; + } + + /* IR GPIO is absent on FLUID */ + if (machine_is_msm8x60_fluid()) + hsed_config_1.ir_gpio = -1; + + for (i = 0; i < ARRAY_SIZE(othc_accessories); i++) { + if (machine_is_msm8x60_fluid()) { + switch (othc_accessories[i].accessory) { + case OTHC_ANC_HEADPHONE: + case OTHC_ANC_HEADSET: + othc_accessories[i].gpio = GPIO_HEADSET_DET_N; + break; + case OTHC_MICROPHONE: + othc_accessories[i].enabled = false; + break; + case OTHC_SVIDEO_OUT: + othc_accessories[i].enabled = true; + hsed_config_1.video_out_gpio = GPIO_HS_SW_DIR; + break; + } + } + } +} +#endif + +static struct resource resources_pm8058_charger[] = { + { .name = "CHGVAL", + .start = PM8058_CHGVAL_IRQ(PM8058_IRQ_BASE), + .end = PM8058_CHGVAL_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { .name = "CHGINVAL", + .start = PM8058_CHGINVAL_IRQ(PM8058_IRQ_BASE), + .end = PM8058_CHGINVAL_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "CHGILIM", + .start = PM8058_CHGILIM_IRQ(PM8058_IRQ_BASE), + .end = PM8058_CHGILIM_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "VCP", + .start = PM8058_VCP_IRQ(PM8058_IRQ_BASE), + .end = PM8058_VCP_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "ATC_DONE", + .start = PM8058_ATC_DONE_IRQ(PM8058_IRQ_BASE), + .end = PM8058_ATC_DONE_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "ATCFAIL", + .start = PM8058_ATCFAIL_IRQ(PM8058_IRQ_BASE), + .end = PM8058_ATCFAIL_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "AUTO_CHGDONE", + .start = PM8058_AUTO_CHGDONE_IRQ(PM8058_IRQ_BASE), + .end = PM8058_AUTO_CHGDONE_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "AUTO_CHGFAIL", + .start = PM8058_AUTO_CHGFAIL_IRQ(PM8058_IRQ_BASE), + .end = PM8058_AUTO_CHGFAIL_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "CHGSTATE", + .start = PM8058_CHGSTATE_IRQ(PM8058_IRQ_BASE), + .end = PM8058_CHGSTATE_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "FASTCHG", + .start = PM8058_FASTCHG_IRQ(PM8058_IRQ_BASE), + .end = PM8058_FASTCHG_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "CHG_END", + .start = PM8058_CHG_END_IRQ(PM8058_IRQ_BASE), + .end = PM8058_CHG_END_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATTTEMP", + .start = PM8058_BATTTEMP_IRQ(PM8058_IRQ_BASE), + .end = PM8058_BATTTEMP_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "CHGHOT", + .start = PM8058_CHGHOT_IRQ(PM8058_IRQ_BASE), + .end = PM8058_CHGHOT_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "CHGTLIMIT", + .start = PM8058_CHGTLIMIT_IRQ(PM8058_IRQ_BASE), + .end = PM8058_CHGTLIMIT_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "CHG_GONE", + .start = PM8058_CHG_GONE_IRQ(PM8058_IRQ_BASE), + .end = PM8058_CHG_GONE_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "VCPMAJOR", + .start = PM8058_VCPMAJOR_IRQ(PM8058_IRQ_BASE), + .end = PM8058_VCPMAJOR_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBATDET", + .start = PM8058_VBATDET_IRQ(PM8058_IRQ_BASE), + .end = PM8058_VBATDET_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATFET", + .start = PM8058_BATFET_IRQ(PM8058_IRQ_BASE), + .end = PM8058_BATFET_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATT_REPLACE", + .start = PM8058_BATT_REPLACE_IRQ(PM8058_IRQ_BASE), + .end = PM8058_BATT_REPLACE_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "BATTCONNECT", + .start = PM8058_BATTCONNECT_IRQ(PM8058_IRQ_BASE), + .end = PM8058_BATTCONNECT_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBATDET_LOW", + .start = PM8058_VBATDET_LOW_IRQ(PM8058_IRQ_BASE), + .end = PM8058_VBATDET_LOW_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +static int pm8058_pwm_config(struct pwm_device *pwm, int ch, int on) +{ + struct pm8058_gpio pwm_gpio_config = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 0, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_VPH, + .out_strength = PM_GPIO_STRENGTH_HIGH, + .function = PM_GPIO_FUNC_2, + }; + + int rc = -EINVAL; + int id, mode, max_mA; + + id = mode = max_mA = 0; + switch (ch) { + case 0: + case 1: + case 2: + if (on) { + id = 24 + ch; + rc = pm8058_gpio_config(id - 1, &pwm_gpio_config); + if (rc) + pr_err("%s: pm8058_gpio_config(%d): rc=%d\n", + __func__, id, rc); + } + break; + + case 6: + id = PM_PWM_LED_FLASH; + mode = PM_PWM_CONF_PWM1; + max_mA = 300; + break; + + case 7: + id = PM_PWM_LED_FLASH1; + mode = PM_PWM_CONF_PWM1; + max_mA = 300; + break; + + default: + break; + } + + if (ch >= 6 && ch <= 7) { + if (!on) { + mode = PM_PWM_CONF_NONE; + max_mA = 0; + } + rc = pm8058_pwm_config_led(pwm, id, mode, max_mA); + if (rc) + pr_err("%s: pm8058_pwm_config_led(ch=%d): rc=%d\n", + __func__, ch, rc); + } + return rc; + +} + +static struct pm8058_pwm_pdata pm8058_pwm_data = { + .config = pm8058_pwm_config, +}; + +#define PM8058_GPIO_INT 88 + +static struct pm8058_gpio_platform_data pm8058_gpio_data = { + .gpio_base = PM8058_GPIO_PM_TO_SYS(0), + .irq_base = PM8058_GPIO_IRQ(PM8058_IRQ_BASE, 0), + .init = pm8058_gpios_init, +}; + +static struct pm8058_gpio_platform_data pm8058_mpp_data = { + .gpio_base = PM8058_GPIO_PM_TO_SYS(PM8058_GPIOS), + .irq_base = PM8058_MPP_IRQ(PM8058_IRQ_BASE, 0), +}; + +static struct resource resources_rtc[] = { + { + .start = PM8058_RTC_IRQ(PM8058_IRQ_BASE), + .end = PM8058_RTC_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .start = PM8058_RTC_ALARM_IRQ(PM8058_IRQ_BASE), + .end = PM8058_RTC_ALARM_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct pmic8058_led pmic8058_flash_leds[] = { + [0] = { + .name = "camera:flash0", + .max_brightness = 15, + .id = PMIC8058_ID_FLASH_LED_0, + }, + [1] = { + .name = "camera:flash1", + .max_brightness = 15, + .id = PMIC8058_ID_FLASH_LED_1, + }, +}; + +static struct pmic8058_leds_platform_data pm8058_flash_leds_data = { + .num_leds = ARRAY_SIZE(pmic8058_flash_leds), + .leds = pmic8058_flash_leds, +}; + +static struct pmic8058_led pmic8058_fluid_flash_leds[] = { + [0] = { + .name = "led:drv0", + .max_brightness = 15, + .id = PMIC8058_ID_FLASH_LED_0, + },/* 300 mA flash led0 drv sink */ + [1] = { + .name = "led:drv1", + .max_brightness = 15, + .id = PMIC8058_ID_FLASH_LED_1, + },/* 300 mA flash led1 sink */ + [2] = { + .name = "led:drv2", + .max_brightness = 20, + .id = PMIC8058_ID_LED_0, + },/* 40 mA led0 sink */ + [3] = { + .name = "keypad:drv", + .max_brightness = 15, + .id = PMIC8058_ID_LED_KB_LIGHT, + },/* 300 mA keypad drv sink */ +}; + +static struct pmic8058_leds_platform_data pm8058_fluid_flash_leds_data = { + .num_leds = ARRAY_SIZE(pmic8058_fluid_flash_leds), + .leds = pmic8058_fluid_flash_leds, +}; + +static struct resource resources_temp_alarm[] = { + { + .start = PM8058_TEMP_ALARM_IRQ(PM8058_IRQ_BASE), + .end = PM8058_TEMP_ALARM_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource resources_pm8058_misc[] = { + { + .start = PM8058_OSCHALT_IRQ(PM8058_IRQ_BASE), + .end = PM8058_OSCHALT_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource resources_pm8058_batt_alarm[] = { + { + .start = PM8058_BATT_ALARM_IRQ(PM8058_IRQ_BASE), + .end = PM8058_BATT_ALARM_IRQ(PM8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +#define PM8058_SUBDEV_KPD 0 +#define PM8058_SUBDEV_LED 1 +#define PM8058_SUBDEV_VIB 2 + +static struct mfd_cell pm8058_subdevs[] = { + { + .name = "pm8058-keypad", + .id = -1, + .num_resources = ARRAY_SIZE(resources_keypad), + .resources = resources_keypad, + }, + { .name = "pm8058-led", + .id = -1, + }, + { + .name = "pm8058-vib", + .id = -1, + }, + { .name = "pm8058-gpio", + .id = -1, + .platform_data = &pm8058_gpio_data, + .pdata_size = sizeof(pm8058_gpio_data), + }, + { .name = "pm8058-mpp", + .id = -1, + .platform_data = &pm8058_mpp_data, + .pdata_size = sizeof(pm8058_mpp_data), + }, + { .name = "pm8058-pwrkey", + .id = -1, + .resources = resources_pwrkey, + .num_resources = ARRAY_SIZE(resources_pwrkey), + .platform_data = &pwrkey_pdata, + .pdata_size = sizeof(pwrkey_pdata), + }, + { + .name = "pm8058-pwm", + .id = -1, + .platform_data = &pm8058_pwm_data, + .pdata_size = sizeof(pm8058_pwm_data), + }, +#ifdef CONFIG_SENSORS_MSM_ADC + { + .name = "pm8058-xoadc", + .id = -1, + .num_resources = ARRAY_SIZE(resources_adc), + .resources = resources_adc, + .platform_data = &xoadc_pdata, + .pdata_size = sizeof(xoadc_pdata), + }, +#endif +#if defined(CONFIG_PMIC8058_OTHC) || defined(CONFIG_PMIC8058_OTHC_MODULE) + { + .name = "pm8058-othc", + .id = 0, + .platform_data = &othc_config_pdata_0, + .pdata_size = sizeof(othc_config_pdata_0), + .num_resources = ARRAY_SIZE(resources_othc_0), + .resources = resources_othc_0, + }, + { + /* OTHC1 module has headset/switch dection */ + .name = "pm8058-othc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_othc_1), + .resources = resources_othc_1, + .platform_data = &othc_config_pdata_1, + .pdata_size = sizeof(othc_config_pdata_1), + }, + { + .name = "pm8058-othc", + .id = 2, + .platform_data = &othc_config_pdata_2, + .pdata_size = sizeof(othc_config_pdata_2), + .num_resources = ARRAY_SIZE(resources_othc_2), + .resources = resources_othc_2, + }, +#endif + { + .name = "pm8058-rtc", + .id = -1, + .num_resources = ARRAY_SIZE(resources_rtc), + .resources = resources_rtc, + }, + { + .name = "pm8058-tm", + .id = -1, + .num_resources = ARRAY_SIZE(resources_temp_alarm), + .resources = resources_temp_alarm, + }, + { .name = "pm8058-upl", + .id = -1, + }, + { + .name = "pm8058-misc", + .id = -1, + .num_resources = ARRAY_SIZE(resources_pm8058_misc), + .resources = resources_pm8058_misc, + }, + { .name = "pm8058-batt-alarm", + .id = -1, + .num_resources = ARRAY_SIZE(resources_pm8058_batt_alarm), + .resources = resources_pm8058_batt_alarm, + }, +}; + +static struct mfd_cell pm8058_charger_sub_dev = { + .name = "pm8058-charger", + .id = -1, + .num_resources = ARRAY_SIZE(resources_pm8058_charger), + .resources = resources_pm8058_charger, +}; + +static struct pm8058_platform_data pm8058_platform_data = { + .irq_base = PM8058_IRQ_BASE, + + .num_subdevs = ARRAY_SIZE(pm8058_subdevs), + .sub_devices = pm8058_subdevs, + .irq_trigger_flags = IRQF_TRIGGER_LOW, +}; + +static struct i2c_board_info pm8058_boardinfo[] __initdata = { + { + I2C_BOARD_INFO("pm8058-core", 0x55), + .irq = MSM_GPIO_TO_INT(PM8058_GPIO_INT), + .platform_data = &pm8058_platform_data, + }, +}; +#endif /* CONFIG_PMIC8058 */ + +#if defined(CONFIG_TOUCHDISC_VTD518_SHINETSU) || \ + defined(CONFIG_TOUCHDISC_VTD518_SHINETSU_MODULE) +#define TDISC_I2C_SLAVE_ADDR 0x67 +#define PMIC_GPIO_TDISC PM8058_GPIO_PM_TO_SYS(5) +#define TDISC_INT PM8058_GPIO_IRQ(PM8058_IRQ_BASE, 5) + +static const char *vregs_tdisc_name[] = { + "8058_l5", + "8058_s3", +}; + +static const int vregs_tdisc_val[] = { + 2850000,/* uV */ + 1800000, +}; +static struct regulator *vregs_tdisc[ARRAY_SIZE(vregs_tdisc_name)]; + +static int tdisc_shinetsu_setup(void) +{ + int rc, i; + + rc = gpio_request(PMIC_GPIO_TDISC, "tdisc_interrupt"); + if (rc) { + pr_err("%s: gpio_request failed for PMIC_GPIO_TDISC\n", + __func__); + return rc; + } + + rc = gpio_request(GPIO_JOYSTICK_EN, "tdisc_oe"); + if (rc) { + pr_err("%s: gpio_request failed for GPIO_JOYSTICK_EN\n", + __func__); + goto fail_gpio_oe; + } + + rc = gpio_direction_output(GPIO_JOYSTICK_EN, 1); + if (rc) { + pr_err("%s: gpio_direction_output failed for GPIO_JOYSTICK_EN\n", + __func__); + gpio_free(GPIO_JOYSTICK_EN); + goto fail_gpio_oe; + } + + for (i = 0; i < ARRAY_SIZE(vregs_tdisc_name); i++) { + vregs_tdisc[i] = regulator_get(NULL, vregs_tdisc_name[i]); + if (IS_ERR(vregs_tdisc[i])) { + printk(KERN_ERR "%s: regulator get %s failed (%ld)\n", + __func__, vregs_tdisc_name[i], + PTR_ERR(vregs_tdisc[i])); + rc = PTR_ERR(vregs_tdisc[i]); + goto vreg_get_fail; + } + + rc = regulator_set_voltage(vregs_tdisc[i], + vregs_tdisc_val[i], vregs_tdisc_val[i]); + if (rc) { + printk(KERN_ERR "%s: regulator_set_voltage() = %d\n", + __func__, rc); + goto vreg_set_voltage_fail; + } + } + + return rc; +vreg_set_voltage_fail: + i++; +vreg_get_fail: + while (i) + regulator_put(vregs_tdisc[--i]); +fail_gpio_oe: + gpio_free(PMIC_GPIO_TDISC); + return rc; +} + +static void tdisc_shinetsu_release(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vregs_tdisc_name); i++) + regulator_put(vregs_tdisc[i]); + + gpio_free(PMIC_GPIO_TDISC); + gpio_free(GPIO_JOYSTICK_EN); +} + +static int tdisc_shinetsu_enable(void) +{ + int i, rc = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(vregs_tdisc_name); i++) { + rc = regulator_enable(vregs_tdisc[i]); + if (rc < 0) { + printk(KERN_ERR "%s: vreg %s enable failed (%d)\n", + __func__, vregs_tdisc_name[i], rc); + goto vreg_fail; + } + } + + /* Enable the OE (output enable) gpio */ + gpio_set_value_cansleep(GPIO_JOYSTICK_EN, 1); + /* voltage and gpio stabilization delay */ + msleep(50); + + return 0; +vreg_fail: + while (i) + regulator_disable(vregs_tdisc[--i]); + return rc; +} + +static int tdisc_shinetsu_disable(void) +{ + int i, rc; + + for (i = 0; i < ARRAY_SIZE(vregs_tdisc_name); i++) { + rc = regulator_disable(vregs_tdisc[i]); + if (rc < 0) { + printk(KERN_ERR "%s: vreg %s disable failed (%d)\n", + __func__, vregs_tdisc_name[i], rc); + goto tdisc_reg_fail; + } + } + + /* Disable the OE (output enable) gpio */ + gpio_set_value_cansleep(GPIO_JOYSTICK_EN, 0); + + return 0; + +tdisc_reg_fail: + while (i) + regulator_enable(vregs_tdisc[--i]); + return rc; +} + +static struct tdisc_abs_values tdisc_abs = { + .x_max = 32, + .y_max = 32, + .x_min = -32, + .y_min = -32, + .pressure_max = 32, + .pressure_min = 0, +}; + +static struct tdisc_platform_data tdisc_data = { + .tdisc_setup = tdisc_shinetsu_setup, + .tdisc_release = tdisc_shinetsu_release, + .tdisc_enable = tdisc_shinetsu_enable, + .tdisc_disable = tdisc_shinetsu_disable, + .tdisc_wakeup = 0, + .tdisc_gpio = PMIC_GPIO_TDISC, + .tdisc_report_keys = true, + .tdisc_report_relative = true, + .tdisc_report_absolute = false, + .tdisc_report_wheel = false, + .tdisc_reverse_x = false, + .tdisc_reverse_y = true, + .tdisc_abs = &tdisc_abs, +}; + +static struct i2c_board_info msm_i2c_gsbi3_tdisc_info[] = { + { + I2C_BOARD_INFO("vtd518", TDISC_I2C_SLAVE_ADDR), + .irq = TDISC_INT, + .platform_data = &tdisc_data, + }, +}; +#endif + +#define PM_GPIO_CDC_RST_N 20 +#define GPIO_CDC_RST_N PM8058_GPIO_PM_TO_SYS(PM_GPIO_CDC_RST_N) + +static struct regulator *vreg_timpani_1; +static struct regulator *vreg_timpani_2; + +static unsigned int msm_timpani_setup_power(void) +{ + int rc; + + vreg_timpani_1 = regulator_get(NULL, "8058_l0"); + if (IS_ERR(vreg_timpani_1)) { + pr_err("%s: Unable to get 8058_l0\n", __func__); + return -ENODEV; + } + + vreg_timpani_2 = regulator_get(NULL, "8058_s3"); + if (IS_ERR(vreg_timpani_2)) { + pr_err("%s: Unable to get 8058_s3\n", __func__); + regulator_put(vreg_timpani_1); + return -ENODEV; + } + + rc = regulator_set_voltage(vreg_timpani_1, 1200000, 1200000); + if (rc) { + pr_err("%s: unable to set L0 voltage to 1.2V\n", __func__); + goto fail; + } + + rc = regulator_set_voltage(vreg_timpani_2, 1800000, 1800000); + if (rc) { + pr_err("%s: unable to set S3 voltage to 1.8V\n", __func__); + goto fail; + } + + rc = regulator_enable(vreg_timpani_1); + if (rc) { + pr_err("%s: Enable regulator 8058_l0 failed\n", __func__); + goto fail; + } + + /* The settings for LDO0 should be set such that + * it doesn't require to reset the timpani. */ + rc = regulator_set_optimum_mode(vreg_timpani_1, 5000); + if (rc < 0) { + pr_err("Timpani regulator optimum mode setting failed\n"); + goto fail; + } + + rc = regulator_enable(vreg_timpani_2); + if (rc) { + pr_err("%s: Enable regulator 8058_s3 failed\n", __func__); + regulator_disable(vreg_timpani_1); + goto fail; + } + + rc = gpio_request(GPIO_CDC_RST_N, "CDC_RST_N"); + if (rc) { + pr_err("%s: GPIO Request %d failed\n", __func__, + GPIO_CDC_RST_N); + regulator_disable(vreg_timpani_1); + regulator_disable(vreg_timpani_2); + goto fail; + } else { + gpio_direction_output(GPIO_CDC_RST_N, 1); + usleep_range(1000, 1050); + gpio_direction_output(GPIO_CDC_RST_N, 0); + usleep_range(1000, 1050); + gpio_direction_output(GPIO_CDC_RST_N, 1); + gpio_free(GPIO_CDC_RST_N); + } + return rc; + +fail: + regulator_put(vreg_timpani_1); + regulator_put(vreg_timpani_2); + return rc; +} + +static void msm_timpani_shutdown_power(void) +{ + int rc; + + rc = regulator_disable(vreg_timpani_1); + if (rc) + pr_err("%s: Disable regulator 8058_l0 failed\n", __func__); + + regulator_put(vreg_timpani_1); + + rc = regulator_disable(vreg_timpani_2); + if (rc) + pr_err("%s: Disable regulator 8058_s3 failed\n", __func__); + + regulator_put(vreg_timpani_2); +} + +/* Power analog function of codec */ +static struct regulator *vreg_timpani_cdc_apwr; +static int msm_timpani_codec_power(int vreg_on) +{ + int rc = 0; + + if (!vreg_timpani_cdc_apwr) { + + vreg_timpani_cdc_apwr = regulator_get(NULL, "8058_s4"); + + if (IS_ERR(vreg_timpani_cdc_apwr)) { + pr_err("%s: vreg_get failed (%ld)\n", + __func__, PTR_ERR(vreg_timpani_cdc_apwr)); + rc = PTR_ERR(vreg_timpani_cdc_apwr); + return rc; + } + } + + if (vreg_on) { + + rc = regulator_set_voltage(vreg_timpani_cdc_apwr, + 2200000, 2200000); + if (rc) { + pr_err("%s: unable to set 8058_s4 voltage to 2.2 V\n", + __func__); + goto vreg_fail; + } + + rc = regulator_enable(vreg_timpani_cdc_apwr); + if (rc) { + pr_err("%s: vreg_enable failed %d\n", __func__, rc); + goto vreg_fail; + } + } else { + rc = regulator_disable(vreg_timpani_cdc_apwr); + if (rc) { + pr_err("%s: vreg_disable failed %d\n", + __func__, rc); + goto vreg_fail; + } + } + + return 0; + +vreg_fail: + regulator_put(vreg_timpani_cdc_apwr); + vreg_timpani_cdc_apwr = NULL; + return rc; +} + +static struct marimba_codec_platform_data timpani_codec_pdata = { + .marimba_codec_power = msm_timpani_codec_power, +}; + +#define TIMPANI_SLAVE_ID_CDC_ADDR 0X77 +#define TIMPANI_SLAVE_ID_QMEMBIST_ADDR 0X66 + +static struct marimba_platform_data timpani_pdata = { + .slave_id[MARIMBA_SLAVE_ID_CDC] = TIMPANI_SLAVE_ID_CDC_ADDR, + .slave_id[MARIMBA_SLAVE_ID_QMEMBIST] = TIMPANI_SLAVE_ID_QMEMBIST_ADDR, + .marimba_setup = msm_timpani_setup_power, + .marimba_shutdown = msm_timpani_shutdown_power, + .codec = &timpani_codec_pdata, + .tsadc_ssbi_adap = MARIMBA_SSBI_ADAP, +}; + +#define TIMPANI_I2C_SLAVE_ADDR 0xD + +static struct i2c_board_info msm_i2c_gsbi7_timpani_info[] = { + { + I2C_BOARD_INFO("timpani", TIMPANI_I2C_SLAVE_ADDR), + .platform_data = &timpani_pdata, + }, +}; + +#ifdef CONFIG_PMIC8901 + +#define PM8901_GPIO_INT 91 + +static struct pm8901_gpio_platform_data pm8901_mpp_data = { + .gpio_base = PM8901_GPIO_PM_TO_SYS(0), + .irq_base = PM8901_MPP_IRQ(PM8901_IRQ_BASE, 0), +}; + +static struct resource pm8901_temp_alarm[] = { + { + .start = PM8901_TEMP_ALARM_IRQ(PM8901_IRQ_BASE), + .end = PM8901_TEMP_ALARM_IRQ(PM8901_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, + { + .start = PM8901_TEMP_HI_ALARM_IRQ(PM8901_IRQ_BASE), + .end = PM8901_TEMP_HI_ALARM_IRQ(PM8901_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +/* + * Consumer specific regulator names: + * regulator name consumer dev_name + */ +static struct regulator_consumer_supply vreg_consumers_8901_MPP0[] = { + REGULATOR_SUPPLY("8901_mpp0", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_8901_USB_OTG[] = { + REGULATOR_SUPPLY("8901_usb_otg", NULL), +}; +static struct regulator_consumer_supply vreg_consumers_8901_HDMI_MVS[] = { + REGULATOR_SUPPLY("8901_hdmi_mvs", NULL), +}; + +#define PM8901_VREG_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, \ + _always_on, _active_high) \ + [PM8901_VREG_ID_##_id] = { \ + .init_data = { \ + .constraints = { \ + .valid_modes_mask = _modes, \ + .valid_ops_mask = _ops, \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .input_uV = _min_uV, \ + .apply_uV = _apply_uV, \ + .always_on = _always_on, \ + }, \ + .consumer_supplies = vreg_consumers_8901_##_id, \ + .num_consumer_supplies = \ + ARRAY_SIZE(vreg_consumers_8901_##_id), \ + }, \ + .active_high = _active_high, \ + } + +#define PM8901_VREG_INIT_MPP(_id, _active_high) \ + PM8901_VREG_INIT(_id, 0, 0, REGULATOR_MODE_NORMAL, \ + REGULATOR_CHANGE_STATUS, 0, 0, _active_high) + +#define PM8901_VREG_INIT_VS(_id) \ + PM8901_VREG_INIT(_id, 0, 0, REGULATOR_MODE_NORMAL, \ + REGULATOR_CHANGE_STATUS, 0, 0, 0) + +static struct pm8901_vreg_pdata pm8901_vreg_init_pdata[PM8901_VREG_MAX] = { + PM8901_VREG_INIT_MPP(MPP0, 1), + + PM8901_VREG_INIT_VS(USB_OTG), + PM8901_VREG_INIT_VS(HDMI_MVS), +}; + +#define PM8901_VREG(_id) { \ + .name = "pm8901-regulator", \ + .id = _id, \ + .platform_data = &pm8901_vreg_init_pdata[_id], \ + .pdata_size = sizeof(pm8901_vreg_init_pdata[_id]), \ +} + +static struct mfd_cell pm8901_subdevs[] = { + { .name = "pm8901-mpp", + .id = -1, + .platform_data = &pm8901_mpp_data, + .pdata_size = sizeof(pm8901_mpp_data), + }, + { .name = "pm8901-tm", + .id = -1, + .num_resources = ARRAY_SIZE(pm8901_temp_alarm), + .resources = pm8901_temp_alarm, + }, + PM8901_VREG(PM8901_VREG_ID_MPP0), + PM8901_VREG(PM8901_VREG_ID_USB_OTG), + PM8901_VREG(PM8901_VREG_ID_HDMI_MVS), +}; + +static struct pm8901_platform_data pm8901_platform_data = { + .irq_base = PM8901_IRQ_BASE, + .num_subdevs = ARRAY_SIZE(pm8901_subdevs), + .sub_devices = pm8901_subdevs, + .irq_trigger_flags = IRQF_TRIGGER_LOW, +}; + +static struct i2c_board_info pm8901_boardinfo[] __initdata = { + { + I2C_BOARD_INFO("pm8901-core", 0x55), + .irq = MSM_GPIO_TO_INT(PM8901_GPIO_INT), + .platform_data = &pm8901_platform_data, + }, +}; + +#endif /* CONFIG_PMIC8901 */ + +#if defined(CONFIG_MARIMBA_CORE) && (defined(CONFIG_GPIO_SX150X) \ + || defined(CONFIG_GPIO_SX150X_MODULE)) + +static struct regulator *vreg_bahama; + +struct bahama_config_register{ + u8 reg; + u8 value; + u8 mask; +}; + +enum version{ + VER_1_0, + VER_2_0, + VER_UNSUPPORTED = 0xFF +}; + +static u8 read_bahama_ver(void) +{ + int rc; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA }; + u8 bahama_version; + + rc = marimba_read_bit_mask(&config, 0x00, &bahama_version, 1, 0x1F); + if (rc < 0) { + printk(KERN_ERR + "%s: version read failed: %d\n", + __func__, rc); + return VER_UNSUPPORTED; + } else { + printk(KERN_INFO + "%s: version read got: 0x%x\n", + __func__, bahama_version); + } + + switch (bahama_version) { + case 0x08: /* varient of bahama v1 */ + case 0x10: + case 0x00: + return VER_1_0; + case 0x09: /* variant of bahama v2 */ + return VER_2_0; + default: + return VER_UNSUPPORTED; + } +} + +static unsigned int msm_bahama_setup_power(void) +{ + int rc = 0; + const char *msm_bahama_regulator = "8058_s3"; + vreg_bahama = regulator_get(NULL, msm_bahama_regulator); + + if (IS_ERR(vreg_bahama)) { + rc = PTR_ERR(vreg_bahama); + pr_err("%s: regulator_get %s = %d\n", __func__, + msm_bahama_regulator, rc); + } + + if (!rc) + rc = regulator_set_voltage(vreg_bahama, 1800000, 1800000); + else { + pr_err("%s: regulator_set_voltage %s = %d\n", __func__, + msm_bahama_regulator, rc); + goto unget; + } + + if (!rc) + rc = regulator_enable(vreg_bahama); + else { + pr_err("%s: regulator_enable %s = %d\n", __func__, + msm_bahama_regulator, rc); + goto unget; + } + + if (!rc) + rc = gpio_request(GPIO_MS_SYS_RESET_N, "bahama sys_rst_n"); + else { + pr_err("%s: gpio_request %d = %d\n", __func__, + GPIO_MS_SYS_RESET_N, rc); + goto unenable; + } + + if (!rc) + rc = gpio_direction_output(GPIO_MS_SYS_RESET_N, 1); + else { + pr_err("%s: gpio_direction_output %d = %d\n", __func__, + GPIO_MS_SYS_RESET_N, rc); + goto unrequest; + } + + return rc; + +unrequest: + gpio_free(GPIO_MS_SYS_RESET_N); +unenable: + regulator_disable(vreg_bahama); +unget: + regulator_put(vreg_bahama); + return rc; +}; +static unsigned int msm_bahama_shutdown_power(int value) + + +{ + gpio_set_value_cansleep(GPIO_MS_SYS_RESET_N, 0); + + gpio_free(GPIO_MS_SYS_RESET_N); + + regulator_disable(vreg_bahama); + + regulator_put(vreg_bahama); + + return 0; +}; + +static unsigned int msm_bahama_core_config(int type) +{ + int rc = 0; + + if (type == BAHAMA_ID) { + + int i; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA }; + + const struct bahama_config_register v20_init[] = { + /* reg, value, mask */ + { 0xF4, 0x84, 0xFF }, /* AREG */ + { 0xF0, 0x04, 0xFF } /* DREG */ + }; + + if (read_bahama_ver() == VER_2_0) { + for (i = 0; i < ARRAY_SIZE(v20_init); i++) { + u8 value = v20_init[i].value; + rc = marimba_write_bit_mask(&config, + v20_init[i].reg, + &value, + sizeof(v20_init[i].value), + v20_init[i].mask); + if (rc < 0) { + printk(KERN_ERR + "%s: reg %d write failed: %d\n", + __func__, v20_init[i].reg, rc); + return rc; + } + printk(KERN_INFO "%s: reg 0x%02x value 0x%02x" + " mask 0x%02x\n", + __func__, v20_init[i].reg, + v20_init[i].value, v20_init[i].mask); + } + } + } + printk(KERN_INFO "core type: %d\n", type); + + return rc; +} + +static struct regulator *fm_regulator_s3; +static struct msm_xo_voter *fm_clock; + +static int fm_radio_setup(struct marimba_fm_platform_data *pdata) +{ + int rc = 0; + struct pm8058_gpio cfg = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S3, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 0, + }; + + if (!fm_regulator_s3) { + fm_regulator_s3 = regulator_get(NULL, "8058_s3"); + if (IS_ERR(fm_regulator_s3)) { + rc = PTR_ERR(fm_regulator_s3); + printk(KERN_ERR "%s: regulator get s3 (%d)\n", + __func__, rc); + goto out; + } + } + + + rc = regulator_set_voltage(fm_regulator_s3, 1800000, 1800000); + if (rc < 0) { + printk(KERN_ERR "%s: regulator set voltage failed (%d)\n", + __func__, rc); + goto fm_fail_put; + } + + rc = regulator_enable(fm_regulator_s3); + if (rc < 0) { + printk(KERN_ERR "%s: regulator s3 enable failed (%d)\n", + __func__, rc); + goto fm_fail_put; + } + + /*Vote for XO clock*/ + fm_clock = msm_xo_get(MSM_XO_TCXO_D0, "fm_power"); + + if (IS_ERR(fm_clock)) { + rc = PTR_ERR(fm_clock); + printk(KERN_ERR "%s: Couldn't get TCXO_D0 vote for FM (%d)\n", + __func__, rc); + goto fm_fail_switch; + } + + rc = msm_xo_mode_vote(fm_clock, MSM_XO_MODE_ON); + if (rc < 0) { + printk(KERN_ERR "%s: Failed to vote for TCX0_D0 ON (%d)\n", + __func__, rc); + goto fm_fail_vote; + } + + /*GPIO 18 on PMIC is FM_IRQ*/ + rc = pm8058_gpio_config(FM_GPIO, &cfg); + if (rc) { + printk(KERN_ERR "%s: return val of pm8058_gpio_config: %d\n", + __func__, rc); + goto fm_fail_clock; + } + goto out; + +fm_fail_clock: + msm_xo_mode_vote(fm_clock, MSM_XO_MODE_OFF); +fm_fail_vote: + msm_xo_put(fm_clock); +fm_fail_switch: + regulator_disable(fm_regulator_s3); +fm_fail_put: + regulator_put(fm_regulator_s3); +out: + return rc; +}; + +static void fm_radio_shutdown(struct marimba_fm_platform_data *pdata) +{ + int rc = 0; + if (fm_regulator_s3 != NULL) { + rc = regulator_disable(fm_regulator_s3); + if (rc < 0) { + printk(KERN_ERR "%s: regulator s3 disable (%d)\n", + __func__, rc); + } + regulator_put(fm_regulator_s3); + fm_regulator_s3 = NULL; + } + printk(KERN_ERR "%s: Voting off for XO", __func__); + + if (fm_clock != NULL) { + rc = msm_xo_mode_vote(fm_clock, MSM_XO_MODE_OFF); + if (rc < 0) { + printk(KERN_ERR "%s: Voting off XO clock (%d)\n", + __func__, rc); + } + msm_xo_put(fm_clock); + } + printk(KERN_ERR "%s: coming out of fm_radio_shutdown", __func__); +} + +/* Slave id address for FM/CDC/QMEMBIST + * Values can be programmed using Marimba slave id 0 + * should there be a conflict with other I2C devices + * */ +#define BAHAMA_SLAVE_ID_FM_ADDR 0x2A +#define BAHAMA_SLAVE_ID_QMEMBIST_ADDR 0x7B + +static struct marimba_fm_platform_data marimba_fm_pdata = { + .fm_setup = fm_radio_setup, + .fm_shutdown = fm_radio_shutdown, + .irq = PM8058_GPIO_IRQ(PM8058_IRQ_BASE, FM_GPIO), + .is_fm_soc_i2s_master = false, + .config_i2s_gpio = NULL, +}; + +/* +Just initializing the BAHAMA related slave +*/ +static struct marimba_platform_data marimba_pdata = { + .slave_id[SLAVE_ID_BAHAMA_FM] = BAHAMA_SLAVE_ID_FM_ADDR, + .slave_id[SLAVE_ID_BAHAMA_QMEMBIST] = BAHAMA_SLAVE_ID_QMEMBIST_ADDR, + .bahama_setup = msm_bahama_setup_power, + .bahama_shutdown = msm_bahama_shutdown_power, + .bahama_core_config = msm_bahama_core_config, + .fm = &marimba_fm_pdata, + .tsadc_ssbi_adap = MARIMBA_SSBI_ADAP, +}; + + +static struct i2c_board_info msm_marimba_board_info[] = { + { + I2C_BOARD_INFO("marimba", 0xc), + .platform_data = &marimba_pdata, + } +}; +#endif /* CONFIG_MAIMBA_CORE */ + +#ifdef CONFIG_I2C +#define I2C_SURF 1 +#define I2C_FFA (1 << 1) +#define I2C_RUMI (1 << 2) +#define I2C_SIM (1 << 3) +#define I2C_FLUID (1 << 4) + +struct i2c_registry { + u8 machs; + int bus; + struct i2c_board_info *info; + int len; +}; + +static struct i2c_registry msm8x60_i2c_devices[] __initdata = { +#ifdef CONFIG_PMIC8058 + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_SSBI1_I2C_BUS_ID, + pm8058_boardinfo, + ARRAY_SIZE(pm8058_boardinfo), + }, +#endif +#ifdef CONFIG_PMIC8901 + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_SSBI2_I2C_BUS_ID, + pm8901_boardinfo, + ARRAY_SIZE(pm8901_boardinfo), + }, +#endif +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) + { + I2C_SURF | I2C_FFA, + MSM_GSBI8_QUP_I2C_BUS_ID, + core_expander_i2c_info, + ARRAY_SIZE(core_expander_i2c_info), + }, + { + I2C_SURF | I2C_FFA, + MSM_GSBI8_QUP_I2C_BUS_ID, + docking_expander_i2c_info, + ARRAY_SIZE(docking_expander_i2c_info), + }, + { + I2C_SURF, + MSM_GSBI8_QUP_I2C_BUS_ID, + surf_expanders_i2c_info, + ARRAY_SIZE(surf_expanders_i2c_info), + }, + { + I2C_SURF | I2C_FFA, + MSM_GSBI3_QUP_I2C_BUS_ID, + fha_expanders_i2c_info, + ARRAY_SIZE(fha_expanders_i2c_info), + }, + { + I2C_FLUID, + MSM_GSBI3_QUP_I2C_BUS_ID, + fluid_expanders_i2c_info, + ARRAY_SIZE(fluid_expanders_i2c_info), + }, + { + I2C_FLUID, + MSM_GSBI8_QUP_I2C_BUS_ID, + fluid_core_expander_i2c_info, + ARRAY_SIZE(fluid_core_expander_i2c_info), + }, +#endif +#if defined(CONFIG_TOUCHDISC_VTD518_SHINETSU) || \ + defined(CONFIG_TOUCHDISC_VTD518_SHINETSU_MODULE) + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_GSBI3_QUP_I2C_BUS_ID, + msm_i2c_gsbi3_tdisc_info, + ARRAY_SIZE(msm_i2c_gsbi3_tdisc_info), + }, +#endif + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_GSBI3_QUP_I2C_BUS_ID, + cy8ctmg200_board_info, + ARRAY_SIZE(cy8ctmg200_board_info), + }, +#if defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C) || \ + defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C_MODULE) + { + I2C_FLUID, + MSM_GSBI3_QUP_I2C_BUS_ID, + cyttsp_fluid_info, + ARRAY_SIZE(cyttsp_fluid_info), + }, + { + I2C_FFA | I2C_SURF, + MSM_GSBI3_QUP_I2C_BUS_ID, + cyttsp_ffa_info, + ARRAY_SIZE(cyttsp_ffa_info), + }, +#endif +#ifdef CONFIG_MSM_CAMERA + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_GSBI4_QUP_I2C_BUS_ID, + msm_camera_boardinfo, + ARRAY_SIZE(msm_camera_boardinfo), + }, +#endif + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_GSBI7_QUP_I2C_BUS_ID, + msm_i2c_gsbi7_timpani_info, + ARRAY_SIZE(msm_i2c_gsbi7_timpani_info), + }, +#if defined(CONFIG_MARIMBA_CORE) + { + I2C_SURF | I2C_FFA | I2C_FLUID, + MSM_GSBI7_QUP_I2C_BUS_ID, + msm_marimba_board_info, + ARRAY_SIZE(msm_marimba_board_info), + }, +#endif /* CONFIG_MARIMBA_CORE */ +#ifdef CONFIG_ISL9519_CHARGER + { + I2C_SURF | I2C_FFA, + MSM_GSBI8_QUP_I2C_BUS_ID, + isl_charger_i2c_info, + ARRAY_SIZE(isl_charger_i2c_info), + }, +#endif +#if defined(CONFIG_HAPTIC_ISA1200) || \ + defined(CONFIG_HAPTIC_ISA1200_MODULE) + { + I2C_FLUID, + MSM_GSBI8_QUP_I2C_BUS_ID, + msm_isa1200_board_info, + ARRAY_SIZE(msm_isa1200_board_info), + }, +#endif +#if defined(CONFIG_SMB137B_CHARGER) || defined(CONFIG_SMB137B_CHARGER_MODULE) + { + I2C_FLUID, + MSM_GSBI8_QUP_I2C_BUS_ID, + smb137b_charger_i2c_info, + ARRAY_SIZE(smb137b_charger_i2c_info), + }, +#endif +#if defined(CONFIG_BATTERY_BQ27520) || \ + defined(CONFIG_BATTERY_BQ27520_MODULE) + { + I2C_FLUID, + MSM_GSBI8_QUP_I2C_BUS_ID, + msm_bq27520_board_info, + ARRAY_SIZE(msm_bq27520_board_info), + }, +#endif +}; +#endif /* CONFIG_I2C */ + +static void fixup_i2c_configs(void) +{ +#ifdef CONFIG_I2C +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) + if (machine_is_msm8x60_surf() || machine_is_msm8x60_fusion()) + sx150x_data[SX150X_CORE].irq_summary = + PM8058_GPIO_IRQ(PM8058_IRQ_BASE, UI_INT2_N); + else if (machine_is_msm8x60_ffa() || machine_is_msm8x60_fusn_ffa()) + sx150x_data[SX150X_CORE].irq_summary = + PM8058_GPIO_IRQ(PM8058_IRQ_BASE, UI_INT1_N); + else if (machine_is_msm8x60_fluid()) + sx150x_data[SX150X_CORE_FLUID].irq_summary = + PM8058_GPIO_IRQ(PM8058_IRQ_BASE, UI_INT1_N); +#endif + /* + * Set PMIC 8901 MPP0 active_high to 0 for surf and charm_surf. This + * implies that the regulator connected to MPP0 is enabled when + * MPP0 is low. + */ + if (machine_is_msm8x60_surf() || machine_is_msm8x60_fusion()) + pm8901_vreg_init_pdata[PM8901_VREG_ID_MPP0].active_high = 0; + else + pm8901_vreg_init_pdata[PM8901_VREG_ID_MPP0].active_high = 1; +#endif +} + +static void register_i2c_devices(void) +{ +#ifdef CONFIG_I2C + u8 mach_mask = 0; + int i; + + /* Build the matching 'supported_machs' bitmask */ + if (machine_is_msm8x60_surf() || machine_is_msm8x60_fusion()) + mach_mask = I2C_SURF; + else if (machine_is_msm8x60_ffa() || machine_is_msm8x60_fusn_ffa()) + mach_mask = I2C_FFA; + else if (machine_is_msm8x60_rumi3()) + mach_mask = I2C_RUMI; + else if (machine_is_msm8x60_sim()) + mach_mask = I2C_SIM; + else if (machine_is_msm8x60_fluid()) + mach_mask = I2C_FLUID; + else + pr_err("unmatched machine ID in register_i2c_devices\n"); + + /* Run the array and install devices as appropriate */ + for (i = 0; i < ARRAY_SIZE(msm8x60_i2c_devices); ++i) { + if (msm8x60_i2c_devices[i].machs & mach_mask) + i2c_register_board_info(msm8x60_i2c_devices[i].bus, + msm8x60_i2c_devices[i].info, + msm8x60_i2c_devices[i].len); + } +#endif +} + +static void __init msm8x60_init_uart12dm(void) +{ +#if !defined(CONFIG_USB_PEHCI_HCD) && !defined(CONFIG_USB_PEHCI_HCD_MODULE) + /* 0x1D000000 now belongs to EBI2:CS3 i.e. USB ISP Controller */ + void *fpga_mem = ioremap_nocache(0x1D000000, SZ_4K); + + if (!fpga_mem) + pr_err("%s(): Error getting memory\n", __func__); + + /* Advanced mode */ + writew(0xFFFF, fpga_mem + 0x15C); + /* FPGA_UART_SEL */ + writew(0, fpga_mem + 0x172); + /* FPGA_GPIO_CONFIG_117 */ + writew(1, fpga_mem + 0xEA); + /* FPGA_GPIO_CONFIG_118 */ + writew(1, fpga_mem + 0xEC); + mb(); + iounmap(fpga_mem); +#endif +} + +#define MSM_GSBI9_PHYS 0x19900000 +#define GSBI_DUAL_MODE_CODE 0x60 + +static void __init msm8x60_init_buses(void) +{ +#ifdef CONFIG_I2C_QUP + void *gsbi_mem = ioremap_nocache(0x19C00000, 4); + /* Setting protocol code to 0x60 for dual UART/I2C in GSBI12 */ + writel_relaxed(0x6 << 4, gsbi_mem); + /* Ensure protocol code is written before proceeding further */ + mb(); + iounmap(gsbi_mem); + + msm_gsbi3_qup_i2c_device.dev.platform_data = &msm_gsbi3_qup_i2c_pdata; + msm_gsbi4_qup_i2c_device.dev.platform_data = &msm_gsbi4_qup_i2c_pdata; + msm_gsbi7_qup_i2c_device.dev.platform_data = &msm_gsbi7_qup_i2c_pdata; + msm_gsbi8_qup_i2c_device.dev.platform_data = &msm_gsbi8_qup_i2c_pdata; + +#ifdef CONFIG_MSM_GSBI9_UART + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) { + /* Setting protocol code to 0x60 for dual UART/I2C in GSBI9 */ + gsbi_mem = ioremap_nocache(MSM_GSBI9_PHYS, 4); + writel_relaxed(GSBI_DUAL_MODE_CODE, gsbi_mem); + iounmap(gsbi_mem); + msm_gsbi9_qup_i2c_pdata.use_gsbi_shared_mode = 1; + } +#endif + msm_gsbi9_qup_i2c_device.dev.platform_data = &msm_gsbi9_qup_i2c_pdata; + msm_gsbi12_qup_i2c_device.dev.platform_data = &msm_gsbi12_qup_i2c_pdata; +#endif +#if defined(CONFIG_SPI_QUP) || defined(CONFIG_SPI_QUP_MODULE) + msm_gsbi1_qup_spi_device.dev.platform_data = &msm_gsbi1_qup_spi_pdata; +#endif +#ifdef CONFIG_I2C_SSBI + msm_device_ssbi1.dev.platform_data = &msm_ssbi1_pdata; + msm_device_ssbi2.dev.platform_data = &msm_ssbi2_pdata; + msm_device_ssbi3.dev.platform_data = &msm_ssbi3_pdata; +#endif + + if (machine_is_msm8x60_fluid()) { +#if (defined(CONFIG_USB_EHCI_MSM_72K) && \ + (defined(CONFIG_SMB137B_CHARGER) || \ + defined(CONFIG_SMB137B_CHARGER_MODULE))) + msm_otg_pdata.vbus_power = msm_hsusb_smb137b_vbus_power; +#endif +#if defined(CONFIG_SPI_QUP) || defined(CONFIG_SPI_QUP_MODULE) + msm_gsbi10_qup_spi_device.dev.platform_data = + &msm_gsbi10_qup_spi_pdata; +#endif + } + +#if defined(CONFIG_USB_GADGET_MSM_72K) || defined(CONFIG_USB_EHCI_HCD) + /* + * We can not put USB regulators (8058_l6 and 8058_l7) in LPM + * when we depend on USB PHY for VBUS/ID notifications. VBUS + * and ID notifications are available only on V2 surf and FFA + * with a hardware workaround. + */ + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) == 2 && + (machine_is_msm8x60_surf() || + (machine_is_msm8x60_ffa() && + pmic_id_notif_supported))) + msm_otg_pdata.phy_can_powercollapse = 1; + msm_device_otg.dev.platform_data = &msm_otg_pdata; +#endif + +#ifdef CONFIG_USB_GADGET_MSM_72K + msm_device_gadget_peripheral.dev.platform_data = &msm_gadget_pdata; +#endif + +#ifdef CONFIG_SERIAL_MSM_HS + msm_uart_dm1_pdata.wakeup_irq = gpio_to_irq(54); /* GSBI6(2) */ + msm_device_uart_dm1.dev.platform_data = &msm_uart_dm1_pdata; +#endif +#ifdef CONFIG_MSM_GSBI9_UART + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) { + msm_device_uart_gsbi9 = msm_add_gsbi9_uart(); + if (IS_ERR(msm_device_uart_gsbi9)) + pr_err("%s(): Failed to create uart gsbi9 device\n", + __func__); + } +#endif + +#ifdef CONFIG_MSM_BUS_SCALING + + /* RPM calls are only enabled on V2 */ + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) == 2) { + msm_bus_apps_fabric_pdata.rpm_enabled = 1; + msm_bus_sys_fabric_pdata.rpm_enabled = 1; + msm_bus_mm_fabric_pdata.rpm_enabled = 1; + msm_bus_sys_fpb_pdata.rpm_enabled = 1; + msm_bus_cpss_fpb_pdata.rpm_enabled = 1; + } + + msm_bus_apps_fabric.dev.platform_data = &msm_bus_apps_fabric_pdata; + msm_bus_sys_fabric.dev.platform_data = &msm_bus_sys_fabric_pdata; + msm_bus_mm_fabric.dev.platform_data = &msm_bus_mm_fabric_pdata; + msm_bus_sys_fpb.dev.platform_data = &msm_bus_sys_fpb_pdata; + msm_bus_cpss_fpb.dev.platform_data = &msm_bus_cpss_fpb_pdata; +#endif +} + +static void __init msm8x60_map_io(void) +{ + msm_shared_ram_phys = MSM_SHARED_RAM_PHYS; + msm_map_msm8x60_io(); +} + +/* + * Most segments of the EBI2 bus are disabled by default. + */ +static void __init msm8x60_init_ebi2(void) +{ + uint32_t ebi2_cfg; + void *ebi2_cfg_ptr; + + ebi2_cfg_ptr = ioremap_nocache(0x1a100000, sizeof(uint32_t)); + if (ebi2_cfg_ptr != 0) { + ebi2_cfg = readl_relaxed(ebi2_cfg_ptr); + + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() || + machine_is_msm8x60_fluid()) + ebi2_cfg |= (1 << 4) | (1 << 5); /* CS2, CS3 */ + else if (machine_is_msm8x60_sim()) + ebi2_cfg |= (1 << 4); /* CS2 */ + else if (machine_is_msm8x60_rumi3()) + ebi2_cfg |= (1 << 5); /* CS3 */ + + writel_relaxed(ebi2_cfg, ebi2_cfg_ptr); + iounmap(ebi2_cfg_ptr); + } + + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() || + machine_is_msm8x60_fluid()) { + ebi2_cfg_ptr = ioremap_nocache(0x1a110000, SZ_4K); + if (ebi2_cfg_ptr != 0) { + /* EBI2_XMEM_CFG:PWRSAVE_MODE off */ + writel_relaxed(0UL, ebi2_cfg_ptr); + + /* CS2: Delay 9 cycles (140ns@64MHz) between SMSC + * LAN9221 Ethernet controller reads and writes. + * The lowest 4 bits are the read delay, the next + * 4 are the write delay. */ + writel_relaxed(0x031F1C99, ebi2_cfg_ptr + 0x10); +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + /* + * RECOVERY=5, HOLD_WR=1 + * INIT_LATENCY_WR=1, INIT_LATENCY_RD=1 + * WAIT_WR=1, WAIT_RD=2 + */ + writel_relaxed(0x51010112, ebi2_cfg_ptr + 0x14); + /* + * HOLD_RD=1 + * ADV_OE_RECOVERY=0, ADDR_HOLD_ENA=1 + */ + writel_relaxed(0x01000020, ebi2_cfg_ptr + 0x34); +#else + /* EBI2 CS3 muxed address/data, + * two cyc addr enable */ + writel_relaxed(0xA3030020, ebi2_cfg_ptr + 0x34); + +#endif + iounmap(ebi2_cfg_ptr); + } + } +} + +static void __init msm8x60_configure_smc91x(void) +{ + if (machine_is_msm8x60_sim()) { + + smc91x_resources[0].start = 0x1b800300; + smc91x_resources[0].end = 0x1b8003ff; + + smc91x_resources[1].start = (NR_MSM_IRQS + 40); + smc91x_resources[1].end = (NR_MSM_IRQS + 40); + + } else if (machine_is_msm8x60_rumi3()) { + + smc91x_resources[0].start = 0x1d000300; + smc91x_resources[0].end = 0x1d0003ff; + + smc91x_resources[1].start = TLMM_MSM_DIR_CONN_IRQ_0; + smc91x_resources[1].end = TLMM_MSM_DIR_CONN_IRQ_0; + } +} + +static void __init msm8x60_init_tlmm(void) +{ + if (machine_is_msm8x60_rumi3()) + msm_gpio_install_direct_irq(0, 0, 1); +} + +#if (defined(CONFIG_MMC_MSM_SDC1_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC2_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC3_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC4_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC5_SUPPORT)) + +/* 8x60 is having 5 SDCC controllers */ +#define MAX_SDCC_CONTROLLER 5 + +struct msm_sdcc_gpio { + /* maximum 10 GPIOs per SDCC controller */ + s16 no; + /* name of this GPIO */ + const char *name; + bool always_on; + bool is_enabled; +}; + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static struct msm_sdcc_gpio sdc1_gpio_cfg[] = { + {159, "sdc1_dat_0"}, + {160, "sdc1_dat_1"}, + {161, "sdc1_dat_2"}, + {162, "sdc1_dat_3"}, +#ifdef CONFIG_MMC_MSM_SDC1_8_BIT_SUPPORT + {163, "sdc1_dat_4"}, + {164, "sdc1_dat_5"}, + {165, "sdc1_dat_6"}, + {166, "sdc1_dat_7"}, +#endif + {167, "sdc1_clk"}, + {168, "sdc1_cmd"} +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static struct msm_sdcc_gpio sdc2_gpio_cfg[] = { + {143, "sdc2_dat_0"}, + {144, "sdc2_dat_1", 1}, + {145, "sdc2_dat_2"}, + {146, "sdc2_dat_3"}, +#ifdef CONFIG_MMC_MSM_SDC2_8_BIT_SUPPORT + {147, "sdc2_dat_4"}, + {148, "sdc2_dat_5"}, + {149, "sdc2_dat_6"}, + {150, "sdc2_dat_7"}, +#endif + {151, "sdc2_cmd"}, + {152, "sdc2_clk", 1} +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC5_SUPPORT +static struct msm_sdcc_gpio sdc5_gpio_cfg[] = { + {95, "sdc5_cmd"}, + {96, "sdc5_dat_3"}, + {97, "sdc5_clk", 1}, + {98, "sdc5_dat_2"}, + {99, "sdc5_dat_1", 1}, + {100, "sdc5_dat_0"} +}; +#endif + +struct msm_sdcc_pad_pull_cfg { + enum msm_tlmm_pull_tgt pull; + u32 pull_val; +}; + +struct msm_sdcc_pad_drv_cfg { + enum msm_tlmm_hdrive_tgt drv; + u32 drv_val; +}; + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static struct msm_sdcc_pad_drv_cfg sdc3_pad_on_drv_cfg[] = { + {TLMM_HDRV_SDC3_CLK, GPIO_CFG_8MA}, + {TLMM_HDRV_SDC3_CMD, GPIO_CFG_8MA}, + {TLMM_HDRV_SDC3_DATA, GPIO_CFG_8MA} +}; + +static struct msm_sdcc_pad_pull_cfg sdc3_pad_on_pull_cfg[] = { + {TLMM_PULL_SDC3_CMD, GPIO_CFG_PULL_UP}, + {TLMM_PULL_SDC3_DATA, GPIO_CFG_PULL_UP} +}; + +static struct msm_sdcc_pad_drv_cfg sdc3_pad_off_drv_cfg[] = { + {TLMM_HDRV_SDC3_CLK, GPIO_CFG_2MA}, + {TLMM_HDRV_SDC3_CMD, GPIO_CFG_2MA}, + {TLMM_HDRV_SDC3_DATA, GPIO_CFG_2MA} +}; + +static struct msm_sdcc_pad_pull_cfg sdc3_pad_off_pull_cfg[] = { + {TLMM_PULL_SDC3_CMD, GPIO_CFG_PULL_DOWN}, + {TLMM_PULL_SDC3_DATA, GPIO_CFG_PULL_DOWN} +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT +static struct msm_sdcc_pad_drv_cfg sdc4_pad_on_drv_cfg[] = { + {TLMM_HDRV_SDC4_CLK, GPIO_CFG_8MA}, + {TLMM_HDRV_SDC4_CMD, GPIO_CFG_8MA}, + {TLMM_HDRV_SDC4_DATA, GPIO_CFG_8MA} +}; + +static struct msm_sdcc_pad_pull_cfg sdc4_pad_on_pull_cfg[] = { + {TLMM_PULL_SDC4_CMD, GPIO_CFG_PULL_UP}, + {TLMM_PULL_SDC4_DATA, GPIO_CFG_PULL_UP} +}; + +static struct msm_sdcc_pad_drv_cfg sdc4_pad_off_drv_cfg[] = { + {TLMM_HDRV_SDC4_CLK, GPIO_CFG_2MA}, + {TLMM_HDRV_SDC4_CMD, GPIO_CFG_2MA}, + {TLMM_HDRV_SDC4_DATA, GPIO_CFG_2MA} +}; + +static struct msm_sdcc_pad_pull_cfg sdc4_pad_off_pull_cfg[] = { + {TLMM_PULL_SDC4_CMD, GPIO_CFG_PULL_DOWN}, + {TLMM_PULL_SDC4_DATA, GPIO_CFG_PULL_DOWN} +}; +#endif + +struct msm_sdcc_pin_cfg { + /* + * = 1 if controller pins are using gpios + * = 0 if controller has dedicated MSM pins + */ + u8 is_gpio; + u8 cfg_sts; + u8 gpio_data_size; + struct msm_sdcc_gpio *gpio_data; + struct msm_sdcc_pad_drv_cfg *pad_drv_on_data; + struct msm_sdcc_pad_drv_cfg *pad_drv_off_data; + struct msm_sdcc_pad_pull_cfg *pad_pull_on_data; + struct msm_sdcc_pad_pull_cfg *pad_pull_off_data; + u8 pad_drv_data_size; + u8 pad_pull_data_size; + u8 sdio_lpm_gpio_cfg; +}; + + +static struct msm_sdcc_pin_cfg sdcc_pin_cfg_data[MAX_SDCC_CONTROLLER] = { +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + [0] = { + .is_gpio = 1, + .gpio_data_size = ARRAY_SIZE(sdc1_gpio_cfg), + .gpio_data = sdc1_gpio_cfg + }, +#endif +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + [1] = { + .is_gpio = 1, + .gpio_data_size = ARRAY_SIZE(sdc2_gpio_cfg), + .gpio_data = sdc2_gpio_cfg + }, +#endif +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + [2] = { + .is_gpio = 0, + .pad_drv_on_data = sdc3_pad_on_drv_cfg, + .pad_drv_off_data = sdc3_pad_off_drv_cfg, + .pad_pull_on_data = sdc3_pad_on_pull_cfg, + .pad_pull_off_data = sdc3_pad_off_pull_cfg, + .pad_drv_data_size = ARRAY_SIZE(sdc3_pad_on_drv_cfg), + .pad_pull_data_size = ARRAY_SIZE(sdc3_pad_on_pull_cfg) + }, +#endif +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT + [3] = { + .is_gpio = 0, + .pad_drv_on_data = sdc4_pad_on_drv_cfg, + .pad_drv_off_data = sdc4_pad_off_drv_cfg, + .pad_pull_on_data = sdc4_pad_on_pull_cfg, + .pad_pull_off_data = sdc4_pad_off_pull_cfg, + .pad_drv_data_size = ARRAY_SIZE(sdc4_pad_on_drv_cfg), + .pad_pull_data_size = ARRAY_SIZE(sdc4_pad_on_pull_cfg) + }, +#endif +#ifdef CONFIG_MMC_MSM_SDC5_SUPPORT + [4] = { + .is_gpio = 1, + .gpio_data_size = ARRAY_SIZE(sdc5_gpio_cfg), + .gpio_data = sdc5_gpio_cfg + } +#endif +}; + +static int msm_sdcc_setup_gpio(int dev_id, unsigned int enable) +{ + int rc = 0; + struct msm_sdcc_pin_cfg *curr; + int n; + + curr = &sdcc_pin_cfg_data[dev_id - 1]; + if (!curr->gpio_data) + goto out; + + for (n = 0; n < curr->gpio_data_size; n++) { + if (enable) { + + if (curr->gpio_data[n].always_on && + curr->gpio_data[n].is_enabled) + continue; + pr_debug("%s: enable: %s\n", __func__, + curr->gpio_data[n].name); + rc = gpio_request(curr->gpio_data[n].no, + curr->gpio_data[n].name); + if (rc) { + pr_err("%s: gpio_request(%d, %s)" + "failed", __func__, + curr->gpio_data[n].no, + curr->gpio_data[n].name); + goto free_gpios; + } + /* set direction as output for all GPIOs */ + rc = gpio_direction_output( + curr->gpio_data[n].no, 1); + if (rc) { + pr_err("%s: gpio_direction_output" + "(%d, 1) failed\n", __func__, + curr->gpio_data[n].no); + goto free_gpios; + } + curr->gpio_data[n].is_enabled = 1; + } else { + /* + * now free this GPIO which will put GPIO + * in low power mode and will also put GPIO + * in input mode + */ + if (curr->gpio_data[n].always_on) + continue; + pr_debug("%s: disable: %s\n", __func__, + curr->gpio_data[n].name); + gpio_free(curr->gpio_data[n].no); + curr->gpio_data[n].is_enabled = 0; + } + } + curr->cfg_sts = enable; + goto out; + +free_gpios: + for (; n >= 0; n--) + gpio_free(curr->gpio_data[n].no); +out: + return rc; +} + +static int msm_sdcc_setup_pad(int dev_id, unsigned int enable) +{ + int rc = 0; + struct msm_sdcc_pin_cfg *curr; + int n; + + curr = &sdcc_pin_cfg_data[dev_id - 1]; + if (!curr->pad_drv_on_data || !curr->pad_pull_on_data) + goto out; + + if (enable) { + /* + * set up the normal driver strength and + * pull config for pads + */ + for (n = 0; n < curr->pad_drv_data_size; n++) { + if (curr->sdio_lpm_gpio_cfg) { + if (curr->pad_drv_on_data[n].drv == + TLMM_HDRV_SDC4_DATA) + continue; + } + msm_tlmm_set_hdrive(curr->pad_drv_on_data[n].drv, + curr->pad_drv_on_data[n].drv_val); + } + for (n = 0; n < curr->pad_pull_data_size; n++) { + if (curr->sdio_lpm_gpio_cfg) { + if (curr->pad_pull_on_data[n].pull == + TLMM_PULL_SDC4_DATA) + continue; + } + msm_tlmm_set_pull(curr->pad_pull_on_data[n].pull, + curr->pad_pull_on_data[n].pull_val); + } + } else { + /* set the low power config for pads */ + for (n = 0; n < curr->pad_drv_data_size; n++) { + if (curr->sdio_lpm_gpio_cfg) { + if (curr->pad_drv_off_data[n].drv == + TLMM_HDRV_SDC4_DATA) + continue; + } + msm_tlmm_set_hdrive( + curr->pad_drv_off_data[n].drv, + curr->pad_drv_off_data[n].drv_val); + } + for (n = 0; n < curr->pad_pull_data_size; n++) { + if (curr->sdio_lpm_gpio_cfg) { + if (curr->pad_pull_off_data[n].pull == + TLMM_PULL_SDC4_DATA) + continue; + } + msm_tlmm_set_pull( + curr->pad_pull_off_data[n].pull, + curr->pad_pull_off_data[n].pull_val); + } + } + curr->cfg_sts = enable; +out: + return rc; +} + +struct sdcc_reg { + /* VDD/VCC/VCCQ regulator name on PMIC8058/PMIC8089*/ + const char *reg_name; + /* + * is set voltage supported for this regulator? + * 0 = not supported, 1 = supported + */ + unsigned char set_voltage_sup; + /* voltage level to be set */ + unsigned int level; + /* VDD/VCC/VCCQ voltage regulator handle */ + struct regulator *reg; + /* is this regulator enabled? */ + bool enabled; + /* is this regulator needs to be always on? */ + bool always_on; + /* is operating power mode setting required for this regulator? */ + bool op_pwr_mode_sup; + /* Load values for low power and high power mode */ + unsigned int lpm_uA; + unsigned int hpm_uA; +}; +/* all SDCC controllers requires VDD/VCC voltage */ +static struct sdcc_reg sdcc_vdd_reg_data[MAX_SDCC_CONTROLLER]; +/* only SDCC1 requires VCCQ voltage */ +static struct sdcc_reg sdcc_vccq_reg_data[1]; +/* all SDCC controllers may require voting for VDD PAD voltage */ +static struct sdcc_reg sdcc_vddp_reg_data[MAX_SDCC_CONTROLLER]; + +struct sdcc_reg_data { + struct sdcc_reg *vdd_data; /* keeps VDD/VCC regulator info */ + struct sdcc_reg *vccq_data; /* keeps VCCQ regulator info */ + struct sdcc_reg *vddp_data; /* keeps VDD Pad regulator info */ + unsigned char sts; /* regulator enable/disable status */ +}; +/* msm8x60 have 5 SDCC controllers */ +static struct sdcc_reg_data sdcc_vreg_data[MAX_SDCC_CONTROLLER]; + +static int msm_sdcc_vreg_init_reg(struct sdcc_reg *vreg) +{ + int rc = 0; + + /* Get the regulator handle */ + vreg->reg = regulator_get(NULL, vreg->reg_name); + if (IS_ERR(vreg->reg)) { + rc = PTR_ERR(vreg->reg); + pr_err("%s: regulator_get(%s) failed. rc=%d\n", + __func__, vreg->reg_name, rc); + goto out; + } + + /* Set the voltage level if required */ + if (vreg->set_voltage_sup) { + rc = regulator_set_voltage(vreg->reg, vreg->level, + vreg->level); + if (rc) { + pr_err("%s: regulator_set_voltage(%s) failed rc=%d\n", + __func__, vreg->reg_name, rc); + goto vreg_put; + } + } + goto out; + +vreg_put: + regulator_put(vreg->reg); +out: + return rc; +} + +static inline void msm_sdcc_vreg_deinit_reg(struct sdcc_reg *vreg) +{ + regulator_put(vreg->reg); +} + +/* this init function should be called only once for each SDCC */ +static int msm_sdcc_vreg_init(int dev_id, unsigned char init) +{ + int rc = 0; + struct sdcc_reg *curr_vdd_reg, *curr_vccq_reg, *curr_vddp_reg; + struct sdcc_reg_data *curr; + + curr = &sdcc_vreg_data[dev_id - 1]; + curr_vdd_reg = curr->vdd_data; + curr_vccq_reg = curr->vccq_data; + curr_vddp_reg = curr->vddp_data; + + if (init) { + /* + * get the regulator handle from voltage regulator framework + * and then try to set the voltage level for the regulator + */ + if (curr_vdd_reg) { + rc = msm_sdcc_vreg_init_reg(curr_vdd_reg); + if (rc) + goto out; + } + if (curr_vccq_reg) { + rc = msm_sdcc_vreg_init_reg(curr_vccq_reg); + if (rc) + goto vdd_reg_deinit; + } + if (curr_vddp_reg) { + rc = msm_sdcc_vreg_init_reg(curr_vddp_reg); + if (rc) + goto vccq_reg_deinit; + } + goto out; + } else + /* deregister with all regulators from regulator framework */ + goto vddp_reg_deinit; + +vddp_reg_deinit: + if (curr_vddp_reg) + msm_sdcc_vreg_deinit_reg(curr_vddp_reg); +vccq_reg_deinit: + if (curr_vccq_reg) + msm_sdcc_vreg_deinit_reg(curr_vccq_reg); +vdd_reg_deinit: + if (curr_vdd_reg) + msm_sdcc_vreg_deinit_reg(curr_vdd_reg); +out: + return rc; +} + +static int msm_sdcc_vreg_enable(struct sdcc_reg *vreg) +{ + int rc; + + if (!vreg->enabled) { + rc = regulator_enable(vreg->reg); + if (rc) { + pr_err("%s: regulator_enable(%s) failed. rc=%d\n", + __func__, vreg->reg_name, rc); + goto out; + } + vreg->enabled = 1; + } + + /* Put always_on regulator in HPM (high power mode) */ + if (vreg->always_on && vreg->op_pwr_mode_sup) { + rc = regulator_set_optimum_mode(vreg->reg, vreg->hpm_uA); + if (rc < 0) { + pr_err("%s: reg=%s: HPM setting failed" + " hpm_uA=%d, rc=%d\n", + __func__, vreg->reg_name, + vreg->hpm_uA, rc); + goto vreg_disable; + } + rc = 0; + } + goto out; + +vreg_disable: + regulator_disable(vreg->reg); + vreg->enabled = 0; +out: + return rc; +} + +static int msm_sdcc_vreg_disable(struct sdcc_reg *vreg) +{ + int rc; + + /* Never disable always_on regulator */ + if (!vreg->always_on) { + rc = regulator_disable(vreg->reg); + if (rc) { + pr_err("%s: regulator_disable(%s) failed. rc=%d\n", + __func__, vreg->reg_name, rc); + goto out; + } + vreg->enabled = 0; + } + + /* Put always_on regulator in LPM (low power mode) */ + if (vreg->always_on && vreg->op_pwr_mode_sup) { + rc = regulator_set_optimum_mode(vreg->reg, vreg->lpm_uA); + if (rc < 0) { + pr_err("%s: reg=%s: LPM setting failed" + " lpm_uA=%d, rc=%d\n", + __func__, + vreg->reg_name, + vreg->lpm_uA, rc); + goto out; + } + rc = 0; + } + +out: + return rc; +} + +static int msm_sdcc_setup_vreg(int dev_id, unsigned char enable) +{ + int rc = 0; + struct sdcc_reg *curr_vdd_reg, *curr_vccq_reg, *curr_vddp_reg; + struct sdcc_reg_data *curr; + + curr = &sdcc_vreg_data[dev_id - 1]; + curr_vdd_reg = curr->vdd_data; + curr_vccq_reg = curr->vccq_data; + curr_vddp_reg = curr->vddp_data; + + /* check if regulators are initialized or not? */ + if ((curr_vdd_reg && !curr_vdd_reg->reg) || + (curr_vccq_reg && !curr_vccq_reg->reg) || + (curr_vddp_reg && !curr_vddp_reg->reg)) { + /* initialize voltage regulators required for this SDCC */ + rc = msm_sdcc_vreg_init(dev_id, 1); + if (rc) { + pr_err("%s: regulator init failed = %d\n", + __func__, rc); + goto out; + } + } + + if (curr->sts == enable) + goto out; + + if (curr_vdd_reg) { + if (enable) + rc = msm_sdcc_vreg_enable(curr_vdd_reg); + else + rc = msm_sdcc_vreg_disable(curr_vdd_reg); + if (rc) + goto out; + } + + if (curr_vccq_reg) { + if (enable) + rc = msm_sdcc_vreg_enable(curr_vccq_reg); + else + rc = msm_sdcc_vreg_disable(curr_vccq_reg); + if (rc) + goto out; + } + + if (curr_vddp_reg) { + if (enable) + rc = msm_sdcc_vreg_enable(curr_vddp_reg); + else + rc = msm_sdcc_vreg_disable(curr_vddp_reg); + if (rc) + goto out; + } + curr->sts = enable; + +out: + return rc; +} + +static u32 msm_sdcc_setup_power(struct device *dv, unsigned int vdd) +{ + u32 rc_pin_cfg = 0; + u32 rc_vreg_cfg = 0; + u32 rc = 0; + struct platform_device *pdev; + struct msm_sdcc_pin_cfg *curr_pin_cfg; + + pdev = container_of(dv, struct platform_device, dev); + + /* setup gpio/pad */ + curr_pin_cfg = &sdcc_pin_cfg_data[pdev->id - 1]; + if (curr_pin_cfg->cfg_sts == !!vdd) + goto setup_vreg; + + if (curr_pin_cfg->is_gpio) + rc_pin_cfg = msm_sdcc_setup_gpio(pdev->id, !!vdd); + else + rc_pin_cfg = msm_sdcc_setup_pad(pdev->id, !!vdd); + +setup_vreg: + /* setup voltage regulators */ + rc_vreg_cfg = msm_sdcc_setup_vreg(pdev->id, !!vdd); + + if (rc_pin_cfg || rc_vreg_cfg) + rc = rc_pin_cfg ? rc_pin_cfg : rc_vreg_cfg; + + return rc; +} + +static void msm_sdcc_sdio_lpm_gpio(struct device *dv, unsigned int active) +{ + struct msm_sdcc_pin_cfg *curr_pin_cfg; + struct platform_device *pdev; + + pdev = container_of(dv, struct platform_device, dev); + /* setup gpio/pad */ + curr_pin_cfg = &sdcc_pin_cfg_data[pdev->id - 1]; + + if (curr_pin_cfg->cfg_sts == active) + return; + + curr_pin_cfg->sdio_lpm_gpio_cfg = 1; + if (curr_pin_cfg->is_gpio) + msm_sdcc_setup_gpio(pdev->id, active); + else + msm_sdcc_setup_pad(pdev->id, active); + curr_pin_cfg->sdio_lpm_gpio_cfg = 0; +} + +static int msm_sdc3_get_wpswitch(struct device *dev) +{ + struct platform_device *pdev; + int status; + pdev = container_of(dev, struct platform_device, dev); + + status = gpio_request(GPIO_SDC_WP, "SD_WP_Switch"); + if (status) { + pr_err("%s:Failed to request GPIO %d\n", + __func__, GPIO_SDC_WP); + } else { + status = gpio_direction_input(GPIO_SDC_WP); + if (!status) { + status = gpio_get_value_cansleep(GPIO_SDC_WP); + pr_info("%s: WP Status for Slot %d = %d\n", + __func__, pdev->id, status); + } + gpio_free(GPIO_SDC_WP); + } + return status; +} + +#ifdef CONFIG_MMC_MSM_SDC5_SUPPORT +int sdc5_register_status_notify(void (*callback)(int, void *), + void *dev_id) +{ + sdc5_status_notify_cb = callback; + sdc5_status_notify_cb_devid = dev_id; + return 0; +} +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +int sdc2_register_status_notify(void (*callback)(int, void *), + void *dev_id) +{ + sdc2_status_notify_cb = callback; + sdc2_status_notify_cb_devid = dev_id; + return 0; +} +#endif + +/* Interrupt handler for SDC2 and SDC5 detection + * This function uses dual-edge interrputs settings in order + * to get SDIO detection when the GPIO is rising and SDIO removal + * when the GPIO is falling */ +static irqreturn_t msm8x60_multi_sdio_slot_status_irq(int irq, void *dev_id) +{ + int status; + + if (!machine_is_msm8x60_fusion() && + !machine_is_msm8x60_fusn_ffa()) + return IRQ_NONE; + + status = gpio_get_value(MDM2AP_SYNC); + pr_info("%s: MDM2AP_SYNC Status = %d\n", + __func__, status); + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + if (sdc2_status_notify_cb) { + pr_info("%s: calling sdc2_status_notify_cb\n", __func__); + sdc2_status_notify_cb(status, + sdc2_status_notify_cb_devid); + } +#endif + +#ifdef CONFIG_MMC_MSM_SDC5_SUPPORT + if (sdc5_status_notify_cb) { + pr_info("%s: calling sdc5_status_notify_cb\n", __func__); + sdc5_status_notify_cb(status, + sdc5_status_notify_cb_devid); + } +#endif + return IRQ_HANDLED; +} + +static int msm8x60_multi_sdio_init(void) +{ + int ret, irq_num; + + if (!machine_is_msm8x60_fusion() && + !machine_is_msm8x60_fusn_ffa()) + return 0; + + ret = msm_gpiomux_get(MDM2AP_SYNC); + if (ret) { + pr_err("%s:Failed to request GPIO %d, ret=%d\n", + __func__, MDM2AP_SYNC, ret); + return ret; + } + + irq_num = gpio_to_irq(MDM2AP_SYNC); + + ret = request_irq(irq_num, + msm8x60_multi_sdio_slot_status_irq, + IRQ_TYPE_EDGE_BOTH, + "sdio_multidetection", NULL); + + if (ret) { + pr_err("%s:Failed to request irq, ret=%d\n", + __func__, ret); + return ret; + } + + return ret; +} + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION +static unsigned int msm8x60_sdcc_slot_status(struct device *dev) +{ + int status; + + status = gpio_request(PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC3_DET - 1) + , "SD_HW_Detect"); + if (status) { + pr_err("%s:Failed to request GPIO %d\n", __func__, + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC3_DET - 1)); + } else { + status = gpio_direction_input( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC3_DET - 1)); + if (!status) + status = !(gpio_get_value_cansleep( + PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC3_DET - 1))); + gpio_free(PM8058_GPIO_PM_TO_SYS(PMIC_GPIO_SDC3_DET - 1)); + } + return (unsigned int) status; +} +#endif +#endif + +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT +static int msm_sdcc_cfg_mpm_sdiowakeup(struct device *dev, unsigned mode) +{ + struct platform_device *pdev; + enum msm_mpm_pin pin; + int ret = 0; + + pdev = container_of(dev, struct platform_device, dev); + + /* Only SDCC4 slot connected to WLAN chip has wakeup capability */ + if (pdev->id == 4) + pin = MSM_MPM_PIN_SDC4_DAT1; + else + return -EINVAL; + + switch (mode) { + case SDC_DAT1_DISABLE: + ret = msm_mpm_enable_pin(pin, 0); + break; + case SDC_DAT1_ENABLE: + ret = msm_mpm_set_pin_type(pin, IRQ_TYPE_LEVEL_LOW); + ret = msm_mpm_enable_pin(pin, 1); + break; + case SDC_DAT1_ENWAKE: + ret = msm_mpm_set_pin_wake(pin, 1); + break; + case SDC_DAT1_DISWAKE: + ret = msm_mpm_set_pin_wake(pin, 0); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} +#endif +#endif + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static struct mmc_platform_data msm8x60_sdc1_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, +#ifdef CONFIG_MMC_MSM_SDC1_8_BIT_SUPPORT + .mmc_bus_width = MMC_CAP_8_BIT_DATA, +#else + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#endif + .msmsdcc_fmin = 400000, + .msmsdcc_fmid = 24000000, + .msmsdcc_fmax = 48000000, + .nonremovable = 1, + .pclk_src_dfab = 1, +#ifdef CONFIG_MMC_MSM_SDC1_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static struct mmc_platform_data msm8x60_sdc2_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_165_195, + .translate_vdd = msm_sdcc_setup_power, + .sdio_lpm_gpio_setup = msm_sdcc_sdio_lpm_gpio, + .mmc_bus_width = MMC_CAP_8_BIT_DATA, + .msmsdcc_fmin = 400000, + .msmsdcc_fmid = 24000000, + .msmsdcc_fmax = 48000000, + .nonremovable = 0, + .pclk_src_dfab = 1, + .register_status_notify = sdc2_register_status_notify, +#ifdef CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +#ifdef CONFIG_MSM_SDIO_AL + .is_sdio_al_client = 1, +#endif +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static struct mmc_platform_data msm8x60_sdc3_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .wpswitch = msm_sdc3_get_wpswitch, +#ifdef CONFIG_MMC_MSM_CARD_HW_DETECTION + .status = msm8x60_sdcc_slot_status, + .status_irq = PM8058_GPIO_IRQ(PM8058_IRQ_BASE, + PMIC_GPIO_SDC3_DET - 1), + .irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, +#endif + .msmsdcc_fmin = 400000, + .msmsdcc_fmid = 24000000, + .msmsdcc_fmax = 48000000, + .nonremovable = 0, + .pclk_src_dfab = 1, +#ifdef CONFIG_MMC_MSM_SDC3_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT +static struct mmc_platform_data msm8x60_sdc4_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .msmsdcc_fmin = 400000, + .msmsdcc_fmid = 24000000, + .msmsdcc_fmax = 48000000, + .nonremovable = 0, + .pclk_src_dfab = 1, + .cfg_mpm_sdiowakeup = msm_sdcc_cfg_mpm_sdiowakeup, +#ifdef CONFIG_MMC_MSM_SDC4_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC5_SUPPORT +static struct mmc_platform_data msm8x60_sdc5_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_165_195, + .translate_vdd = msm_sdcc_setup_power, + .sdio_lpm_gpio_setup = msm_sdcc_sdio_lpm_gpio, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .msmsdcc_fmin = 400000, + .msmsdcc_fmid = 24000000, + .msmsdcc_fmax = 48000000, + .nonremovable = 0, + .pclk_src_dfab = 1, + .register_status_notify = sdc5_register_status_notify, +#ifdef CONFIG_MMC_MSM_SDC5_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif +#ifdef CONFIG_MSM_SDIO_AL + .is_sdio_al_client = 1, +#endif +}; +#endif + +static void __init msm8x60_init_mmc(void) +{ +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + /* SDCC1 : eMMC card connected */ + sdcc_vreg_data[0].vdd_data = &sdcc_vdd_reg_data[0]; + sdcc_vreg_data[0].vdd_data->reg_name = "8901_l5"; + sdcc_vreg_data[0].vdd_data->set_voltage_sup = 1; + sdcc_vreg_data[0].vdd_data->level = 2850000; + sdcc_vreg_data[0].vdd_data->always_on = 1; + sdcc_vreg_data[0].vdd_data->op_pwr_mode_sup = 1; + sdcc_vreg_data[0].vdd_data->lpm_uA = 9000; + sdcc_vreg_data[0].vdd_data->hpm_uA = 200000; + + sdcc_vreg_data[0].vccq_data = &sdcc_vccq_reg_data[0]; + sdcc_vreg_data[0].vccq_data->reg_name = "8901_lvs0"; + sdcc_vreg_data[0].vccq_data->set_voltage_sup = 0; + sdcc_vreg_data[0].vccq_data->always_on = 1; + + msm_add_sdcc(1, &msm8x60_sdc1_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + /* + * MDM SDIO client is connected to SDC2 on charm SURF/FFA + * and no card is connected on 8660 SURF/FFA/FLUID. + */ + sdcc_vreg_data[1].vdd_data = &sdcc_vdd_reg_data[1]; + sdcc_vreg_data[1].vdd_data->reg_name = "8058_s3"; + sdcc_vreg_data[1].vdd_data->set_voltage_sup = 1; + sdcc_vreg_data[1].vdd_data->level = 1800000; + + sdcc_vreg_data[1].vccq_data = NULL; + + if (machine_is_msm8x60_fusion()) + msm8x60_sdc2_data.msmsdcc_fmax = 24000000; + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) { +#ifdef CONFIG_MMC_MSM_SDIO_SUPPORT + msm8x60_sdc2_data.sdiowakeup_irq = gpio_to_irq(144); + msm_sdcc_setup_gpio(2, 1); +#endif + msm_add_sdcc(2, &msm8x60_sdc2_data); + } +#endif +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + /* SDCC3 : External card slot connected */ + sdcc_vreg_data[2].vdd_data = &sdcc_vdd_reg_data[2]; + sdcc_vreg_data[2].vdd_data->reg_name = "8058_l14"; + sdcc_vreg_data[2].vdd_data->set_voltage_sup = 1; + sdcc_vreg_data[2].vdd_data->level = 2850000; + sdcc_vreg_data[2].vdd_data->always_on = 1; + sdcc_vreg_data[2].vdd_data->op_pwr_mode_sup = 1; + sdcc_vreg_data[2].vdd_data->lpm_uA = 9000; + sdcc_vreg_data[2].vdd_data->hpm_uA = 200000; + + sdcc_vreg_data[2].vccq_data = NULL; + + sdcc_vreg_data[2].vddp_data = &sdcc_vddp_reg_data[2]; + sdcc_vreg_data[2].vddp_data->reg_name = "8058_l5"; + sdcc_vreg_data[2].vddp_data->set_voltage_sup = 1; + sdcc_vreg_data[2].vddp_data->level = 2850000; + sdcc_vreg_data[2].vddp_data->always_on = 1; + sdcc_vreg_data[2].vddp_data->op_pwr_mode_sup = 1; + /* Sleep current required is ~300 uA. But min. RPM + * vote can be in terms of mA (min. 1 mA). + * So let's vote for 2 mA during sleep. + */ + sdcc_vreg_data[2].vddp_data->lpm_uA = 2000; + /* Max. Active current required is 16 mA */ + sdcc_vreg_data[2].vddp_data->hpm_uA = 16000; + + if (machine_is_msm8x60_fluid()) + msm8x60_sdc3_data.wpswitch = NULL; + msm_add_sdcc(3, &msm8x60_sdc3_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT + /* SDCC4 : WLAN WCN1314 chip is connected */ + sdcc_vreg_data[3].vdd_data = &sdcc_vdd_reg_data[3]; + sdcc_vreg_data[3].vdd_data->reg_name = "8058_s3"; + sdcc_vreg_data[3].vdd_data->set_voltage_sup = 1; + sdcc_vreg_data[3].vdd_data->level = 1800000; + + sdcc_vreg_data[3].vccq_data = NULL; + + msm_add_sdcc(4, &msm8x60_sdc4_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC5_SUPPORT + /* + * MDM SDIO client is connected to SDC5 on charm SURF/FFA + * and no card is connected on 8660 SURF/FFA/FLUID. + */ + sdcc_vreg_data[4].vdd_data = &sdcc_vdd_reg_data[4]; + sdcc_vreg_data[4].vdd_data->reg_name = "8058_s3"; + sdcc_vreg_data[4].vdd_data->set_voltage_sup = 1; + sdcc_vreg_data[4].vdd_data->level = 1800000; + + sdcc_vreg_data[4].vccq_data = NULL; + + if (machine_is_msm8x60_fusion()) + msm8x60_sdc5_data.msmsdcc_fmax = 24000000; + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) { +#ifdef CONFIG_MMC_MSM_SDIO_SUPPORT + msm8x60_sdc5_data.sdiowakeup_irq = gpio_to_irq(99); + msm_sdcc_setup_gpio(5, 1); +#endif + msm_add_sdcc(5, &msm8x60_sdc5_data); + } +#endif +} + +#if !defined(CONFIG_GPIO_SX150X) && !defined(CONFIG_GPIO_SX150X_MODULE) +static inline void display_common_power(int on) {} +#else + +#define _GET_REGULATOR(var, name) do { \ + if (var == NULL) { \ + var = regulator_get(NULL, name); \ + if (IS_ERR(var)) { \ + pr_err("'%s' regulator not found, rc=%ld\n", \ + name, PTR_ERR(var)); \ + var = NULL; \ + } \ + } \ +} while (0) + +static int dsub_regulator(int on) +{ + static struct regulator *dsub_reg; + static struct regulator *mpp0_reg; + static int dsub_reg_enabled; + int rc = 0; + + _GET_REGULATOR(dsub_reg, "8901_l3"); + if (IS_ERR(dsub_reg)) { + printk(KERN_ERR "%s: failed to get reg 8901_l3 err=%ld", + __func__, PTR_ERR(dsub_reg)); + return PTR_ERR(dsub_reg); + } + + _GET_REGULATOR(mpp0_reg, "8901_mpp0"); + if (IS_ERR(mpp0_reg)) { + printk(KERN_ERR "%s: failed to get reg 8901_mpp0 err=%ld", + __func__, PTR_ERR(mpp0_reg)); + return PTR_ERR(mpp0_reg); + } + + if (on && !dsub_reg_enabled) { + rc = regulator_set_voltage(dsub_reg, 3300000, 3300000); + if (rc) { + printk(KERN_ERR "%s: failed to set reg 8901_l3 voltage" + " err=%d", __func__, rc); + goto dsub_regulator_err; + } + rc = regulator_enable(dsub_reg); + if (rc) { + printk(KERN_ERR "%s: failed to enable reg 8901_l3" + " err=%d", __func__, rc); + goto dsub_regulator_err; + } + rc = regulator_enable(mpp0_reg); + if (rc) { + printk(KERN_ERR "%s: failed to enable reg 8901_mpp0" + " err=%d", __func__, rc); + goto dsub_regulator_err; + } + dsub_reg_enabled = 1; + } else if (!on && dsub_reg_enabled) { + rc = regulator_disable(dsub_reg); + if (rc) + printk(KERN_WARNING "%s: failed to disable reg 8901_l3" + " err=%d", __func__, rc); + rc = regulator_disable(mpp0_reg); + if (rc) + printk(KERN_WARNING "%s: failed to disable reg " + "8901_mpp0 err=%d", __func__, rc); + dsub_reg_enabled = 0; + } + + return rc; + +dsub_regulator_err: + regulator_put(mpp0_reg); + regulator_put(dsub_reg); + return rc; +} + +static int display_power_on; +static void setup_display_power(void) +{ + if (display_power_on) + if (lcdc_vga_enabled) { + dsub_regulator(1); + gpio_set_value_cansleep(GPIO_LVDS_SHUTDOWN_N, 0); + gpio_set_value_cansleep(GPIO_BACKLIGHT_EN, 0); + if (machine_is_msm8x60_ffa() || + machine_is_msm8x60_fusn_ffa()) + gpio_set_value_cansleep(GPIO_DONGLE_PWR_EN, 1); + } else { + dsub_regulator(0); + gpio_set_value_cansleep(GPIO_LVDS_SHUTDOWN_N, 1); + gpio_set_value_cansleep(GPIO_BACKLIGHT_EN, 1); + if (machine_is_msm8x60_ffa() || + machine_is_msm8x60_fusn_ffa()) + gpio_set_value_cansleep(GPIO_DONGLE_PWR_EN, 0); + } + else { + dsub_regulator(0); + if (machine_is_msm8x60_ffa() || machine_is_msm8x60_fusn_ffa()) + gpio_set_value_cansleep(GPIO_DONGLE_PWR_EN, 0); + /* BACKLIGHT */ + gpio_set_value_cansleep(GPIO_BACKLIGHT_EN, 0); + /* LVDS */ + gpio_set_value_cansleep(GPIO_LVDS_SHUTDOWN_N, 0); + } +} + +#define _GET_REGULATOR(var, name) do { \ + if (var == NULL) { \ + var = regulator_get(NULL, name); \ + if (IS_ERR(var)) { \ + pr_err("'%s' regulator not found, rc=%ld\n", \ + name, PTR_ERR(var)); \ + var = NULL; \ + } \ + } \ +} while (0) + +#define GPIO_RESX_N (GPIO_EXPANDER_GPIO_BASE + 2) + +static void display_common_power(int on) +{ + int rc; + static struct regulator *display_reg; + + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() || + machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) { + if (on) { + /* LVDS */ + _GET_REGULATOR(display_reg, "8901_l2"); + if (!display_reg) + return; + rc = regulator_set_voltage(display_reg, + 3300000, 3300000); + if (rc) + goto out; + rc = regulator_enable(display_reg); + if (rc) + goto out; + rc = gpio_request(GPIO_LVDS_SHUTDOWN_N, + "LVDS_STDN_OUT_N"); + if (rc) { + printk(KERN_ERR "%s: LVDS gpio %d request" + "failed\n", __func__, + GPIO_LVDS_SHUTDOWN_N); + goto out2; + } + + /* BACKLIGHT */ + rc = gpio_request(GPIO_BACKLIGHT_EN, "BACKLIGHT_EN"); + if (rc) { + printk(KERN_ERR "%s: BACKLIGHT gpio %d request" + "failed\n", __func__, + GPIO_BACKLIGHT_EN); + goto out3; + } + + if (machine_is_msm8x60_ffa() || + machine_is_msm8x60_fusn_ffa()) { + rc = gpio_request(GPIO_DONGLE_PWR_EN, + "DONGLE_PWR_EN"); + if (rc) { + printk(KERN_ERR "%s: DONGLE_PWR_EN gpio" + " %d request failed\n", __func__, + GPIO_DONGLE_PWR_EN); + goto out4; + } + } + + gpio_direction_output(GPIO_LVDS_SHUTDOWN_N, 0); + gpio_direction_output(GPIO_BACKLIGHT_EN, 0); + if (machine_is_msm8x60_ffa() || + machine_is_msm8x60_fusn_ffa()) + gpio_direction_output(GPIO_DONGLE_PWR_EN, 0); + mdelay(20); + display_power_on = 1; + setup_display_power(); + } else { + if (display_power_on) { + display_power_on = 0; + setup_display_power(); + mdelay(20); + if (machine_is_msm8x60_ffa() || + machine_is_msm8x60_fusn_ffa()) + gpio_free(GPIO_DONGLE_PWR_EN); + goto out4; + } + } + } +#if defined(CONFIG_FB_MSM_LCDC_SAMSUNG_OLED_PT) || \ + defined(CONFIG_FB_MSM_LCDC_AUO_WVGA) + else if (machine_is_msm8x60_fluid()) { + static struct regulator *fluid_reg; + static struct regulator *fluid_reg2; + + if (on) { + _GET_REGULATOR(fluid_reg, "8901_l2"); + if (!fluid_reg) + return; + _GET_REGULATOR(fluid_reg2, "8058_s3"); + if (!fluid_reg2) { + regulator_put(fluid_reg); + return; + } + rc = gpio_request(GPIO_RESX_N, "RESX_N"); + if (rc) { + regulator_put(fluid_reg2); + regulator_put(fluid_reg); + return; + } + regulator_set_voltage(fluid_reg, 2850000, 2850000); + regulator_set_voltage(fluid_reg2, 1800000, 1800000); + regulator_enable(fluid_reg); + regulator_enable(fluid_reg2); + msleep(20); + gpio_direction_output(GPIO_RESX_N, 0); + udelay(10); + gpio_set_value_cansleep(GPIO_RESX_N, 1); + display_power_on = 1; + setup_display_power(); + } else { + gpio_set_value_cansleep(GPIO_RESX_N, 0); + gpio_free(GPIO_RESX_N); + msleep(20); + regulator_disable(fluid_reg2); + regulator_disable(fluid_reg); + regulator_put(fluid_reg2); + regulator_put(fluid_reg); + display_power_on = 0; + setup_display_power(); + fluid_reg = NULL; + fluid_reg2 = NULL; + } + } +#endif + return; + +out4: + gpio_free(GPIO_BACKLIGHT_EN); +out3: + gpio_free(GPIO_LVDS_SHUTDOWN_N); +out2: + regulator_disable(display_reg); +out: + regulator_put(display_reg); + display_reg = NULL; +} +#undef _GET_REGULATOR +#endif + +static int mipi_dsi_panel_power(int on); + +#define LCDC_NUM_GPIO 28 +#define LCDC_GPIO_START 0 + +static void lcdc_samsung_panel_power(int on) +{ + int n, ret = 0; + + display_common_power(on); + + for (n = 0; n < LCDC_NUM_GPIO; n++) { + if (on) { + ret = gpio_request(LCDC_GPIO_START + n, "LCDC_GPIO"); + if (unlikely(ret)) { + pr_err("%s not able to get gpio\n", __func__); + break; + } + } else + gpio_free(LCDC_GPIO_START + n); + } + + if (ret) { + for (n--; n >= 0; n--) + gpio_free(LCDC_GPIO_START + n); + } + + mipi_dsi_panel_power(0); /* set 8058_ldo0 to LPM */ +} + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +#define _GET_REGULATOR(var, name) do { \ + var = regulator_get(NULL, name); \ + if (IS_ERR(var)) { \ + pr_err("'%s' regulator not found, rc=%ld\n", \ + name, IS_ERR(var)); \ + var = NULL; \ + return -ENODEV; \ + } \ +} while (0) + +static int hdmi_enable_5v(int on) +{ + static struct regulator *reg_8901_hdmi_mvs; /* HDMI_5V */ + static struct regulator *reg_8901_mpp0; /* External 5V */ + static int prev_on; + int rc; + + if (on == prev_on) + return 0; + + if (!reg_8901_hdmi_mvs) + _GET_REGULATOR(reg_8901_hdmi_mvs, "8901_hdmi_mvs"); + if (!reg_8901_mpp0) + _GET_REGULATOR(reg_8901_mpp0, "8901_mpp0"); + + if (on) { + rc = regulator_enable(reg_8901_mpp0); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "reg_8901_mpp0", rc); + return rc; + } + rc = regulator_enable(reg_8901_hdmi_mvs); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "8901_hdmi_mvs", rc); + return rc; + } + pr_info("%s(on): success\n", __func__); + } else { + rc = regulator_disable(reg_8901_hdmi_mvs); + if (rc) + pr_warning("'%s' regulator disable failed, rc=%d\n", + "8901_hdmi_mvs", rc); + rc = regulator_disable(reg_8901_mpp0); + if (rc) + pr_warning("'%s' regulator disable failed, rc=%d\n", + "reg_8901_mpp0", rc); + pr_info("%s(off): success\n", __func__); + } + + prev_on = on; + + return 0; +} + +static int hdmi_core_power(int on, int show) +{ + static struct regulator *reg_8058_l16; /* VDD_HDMI */ + static int prev_on; + int rc; + + if (on == prev_on) + return 0; + + if (!reg_8058_l16) + _GET_REGULATOR(reg_8058_l16, "8058_l16"); + + if (on) { + rc = regulator_set_voltage(reg_8058_l16, 1800000, 1800000); + if (!rc) + rc = regulator_enable(reg_8058_l16); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "8058_l16", rc); + return rc; + } + rc = gpio_request(170, "HDMI_DDC_CLK"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_DDC_CLK", 170, rc); + goto error1; + } + rc = gpio_request(171, "HDMI_DDC_DATA"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_DDC_DATA", 171, rc); + goto error2; + } + rc = gpio_request(172, "HDMI_HPD"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_HPD", 172, rc); + goto error3; + } + pr_info("%s(on): success\n", __func__); + } else { + gpio_free(170); + gpio_free(171); + gpio_free(172); + rc = regulator_disable(reg_8058_l16); + if (rc) + pr_warning("'%s' regulator disable failed, rc=%d\n", + "8058_l16", rc); + pr_info("%s(off): success\n", __func__); + } + + prev_on = on; + + return 0; + +error3: + gpio_free(171); +error2: + gpio_free(170); +error1: + regulator_disable(reg_8058_l16); + return rc; +} + +static int hdmi_cec_power(int on) +{ + static struct regulator *reg_8901_l3; /* HDMI_CEC */ + static int prev_on; + int rc; + + if (on == prev_on) + return 0; + + if (!reg_8901_l3) + _GET_REGULATOR(reg_8901_l3, "8901_l3"); + + if (on) { + rc = regulator_set_voltage(reg_8901_l3, 3300000, 3300000); + if (!rc) + rc = regulator_enable(reg_8901_l3); + if (rc) { + pr_err("'%s' regulator enable failed, rc=%d\n", + "8901_l3", rc); + return rc; + } + rc = gpio_request(169, "HDMI_CEC_VAR"); + if (rc) { + pr_err("'%s'(%d) gpio_request failed, rc=%d\n", + "HDMI_CEC_VAR", 169, rc); + goto error; + } + pr_info("%s(on): success\n", __func__); + } else { + gpio_free(169); + rc = regulator_disable(reg_8901_l3); + if (rc) + pr_warning("'%s' regulator disable failed, rc=%d\n", + "8901_l3", rc); + pr_info("%s(off): success\n", __func__); + } + + prev_on = on; + + return 0; +error: + regulator_disable(reg_8901_l3); + return rc; +} + +#undef _GET_REGULATOR + +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL */ + +static int lcdc_panel_power(int on) +{ + int flag_on = !!on; + static int lcdc_power_save_on; + + if (lcdc_power_save_on == flag_on) + return 0; + + lcdc_power_save_on = flag_on; + + lcdc_samsung_panel_power(on); + + return 0; +} + +#ifdef CONFIG_MSM_BUS_SCALING +#ifdef CONFIG_FB_MSM_LCDC_DSUB +static struct msm_bus_vectors mdp_init_vectors[] = { + /* For now, 0th array entry is reserved. + * Please leave 0 as is and don't use it + */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors mdp_sd_smi_vectors[] = { + /* Default case static display/UI/2d/3d if FB SMI */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 388800000, + .ib = 486000000, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors mdp_sd_ebi_vectors[] = { + /* Default case static display/UI/2d/3d if FB SMI */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 388800000, + .ib = 486000000 * 2, + }, +}; +static struct msm_bus_vectors mdp_vga_vectors[] = { + /* VGA and less video */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 458092800, + .ib = 572616000, + }, + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 458092800, + .ib = 572616000 * 2, + }, +}; +static struct msm_bus_vectors mdp_720p_vectors[] = { + /* 720p and less video */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 471744000, + .ib = 589680000, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 471744000, + .ib = 589680000 * 2, + }, +}; + +static struct msm_bus_vectors mdp_1080p_vectors[] = { + /* 1080p and less video */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 575424000, + .ib = 719280000, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 575424000, + .ib = 719280000 * 2, + }, +}; + +#else +static struct msm_bus_vectors mdp_init_vectors[] = { + /* For now, 0th array entry is reserved. + * Please leave 0 as is and don't use it + */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors mdp_sd_smi_vectors[] = { + /* Default case static display/UI/2d/3d if FB SMI */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 175110000, + .ib = 218887500, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors mdp_sd_ebi_vectors[] = { + /* Default case static display/UI/2d/3d if FB SMI */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 216000000, + .ib = 270000000 * 2, + }, +}; +static struct msm_bus_vectors mdp_vga_vectors[] = { + /* VGA and less video */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 216000000, + .ib = 270000000, + }, + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 216000000, + .ib = 270000000 * 2, + }, +}; + +static struct msm_bus_vectors mdp_720p_vectors[] = { + /* 720p and less video */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 230400000, + .ib = 288000000, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 230400000, + .ib = 288000000 * 2, + }, +}; + +static struct msm_bus_vectors mdp_1080p_vectors[] = { + /* 1080p and less video */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 334080000, + .ib = 417600000, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 334080000, + .ib = 417600000 * 2, + }, +}; + +#endif +static struct msm_bus_paths mdp_bus_scale_usecases[] = { + { + ARRAY_SIZE(mdp_init_vectors), + mdp_init_vectors, + }, + { + ARRAY_SIZE(mdp_sd_smi_vectors), + mdp_sd_smi_vectors, + }, + { + ARRAY_SIZE(mdp_sd_ebi_vectors), + mdp_sd_ebi_vectors, + }, + { + ARRAY_SIZE(mdp_vga_vectors), + mdp_vga_vectors, + }, + { + ARRAY_SIZE(mdp_720p_vectors), + mdp_720p_vectors, + }, + { + ARRAY_SIZE(mdp_1080p_vectors), + mdp_1080p_vectors, + }, +}; +static struct msm_bus_scale_pdata mdp_bus_scale_pdata = { + mdp_bus_scale_usecases, + ARRAY_SIZE(mdp_bus_scale_usecases), + .name = "mdp", +}; + +#endif +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors dtv_bus_init_vectors[] = { + /* For now, 0th array entry is reserved. + * Please leave 0 as is and don't use it + */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; +static struct msm_bus_vectors dtv_bus_def_vectors[] = { + /* For now, 0th array entry is reserved. + * Please leave 0 as is and don't use it + */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 566092800, + .ib = 707616000, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 566092800, + .ib = 707616000, + }, +}; +static struct msm_bus_paths dtv_bus_scale_usecases[] = { + { + ARRAY_SIZE(dtv_bus_init_vectors), + dtv_bus_init_vectors, + }, + { + ARRAY_SIZE(dtv_bus_def_vectors), + dtv_bus_def_vectors, + }, +}; +static struct msm_bus_scale_pdata dtv_bus_scale_pdata = { + dtv_bus_scale_usecases, + ARRAY_SIZE(dtv_bus_scale_usecases), + .name = "dtv", +}; + +static struct lcdc_platform_data dtv_pdata = { + .bus_scale_table = &dtv_bus_scale_pdata, +}; +#endif + + +static struct lcdc_platform_data lcdc_pdata = { + .lcdc_power_save = lcdc_panel_power, +}; + + +#define MDP_VSYNC_GPIO 28 + +/* + * MIPI_DSI only use 8058_LDO0 which need always on + * therefore it need to be put at low power mode if + * it was not used instead of turn it off. */ +static int mipi_dsi_panel_power(int on) +{ + int flag_on = !!on; + static int mipi_dsi_power_save_on; + static struct regulator *ldo0; + int rc = 0; -#include -#include -#include -#include + if (mipi_dsi_power_save_on == flag_on) + return 0; -#include -#include -#include + mipi_dsi_power_save_on = flag_on; -#include -#include + if (ldo0 == NULL) { /* init */ + ldo0 = regulator_get(NULL, "8058_l0"); + if (IS_ERR(ldo0)) { + pr_debug("%s: LDO0 failed\n", __func__); + rc = PTR_ERR(ldo0); + return rc; + } + rc = regulator_set_voltage(ldo0, 1200000, 1200000); + if (rc) + goto out; -static void __init msm8x60_map_io(void) + rc = regulator_enable(ldo0); + if (rc) + goto out; + } + + if (on) { + /* set ldo0 to HPM */ + rc = regulator_set_optimum_mode(ldo0, 100000); + if (rc < 0) + goto out; + } else { + /* set ldo0 to LPM */ + rc = regulator_set_optimum_mode(ldo0, 9000); + if (rc < 0) + goto out; + } + + return 0; +out: + regulator_disable(ldo0); + regulator_put(ldo0); + ldo0 = NULL; + return rc; +} + +static struct mipi_dsi_platform_data mipi_dsi_pdata = { + .vsync_gpio = MDP_VSYNC_GPIO, + .dsi_power_save = mipi_dsi_panel_power, +}; + +#ifdef CONFIG_FB_MSM_TVOUT +static struct regulator *reg_8058_l13; + +static int atv_dac_power(int on) { - msm_map_msm8x60_io(); + int rc = 0; + #define _GET_REGULATOR(var, name) do { \ + var = regulator_get(NULL, name); \ + if (IS_ERR(var)) { \ + pr_info("'%s' regulator not found, rc=%ld\n", \ + name, IS_ERR(var)); \ + var = NULL; \ + return -ENODEV; \ + } \ + } while (0) + + if (!reg_8058_l13) + _GET_REGULATOR(reg_8058_l13, "8058_l13"); + #undef _GET_REGULATOR + + if (on) { + rc = regulator_set_voltage(reg_8058_l13, 2050000, 2050000); + if (rc) { + pr_info("%s: '%s' regulator set voltage failed,\ + rc=%d\n", __func__, "8058_l13", rc); + return rc; + } + + rc = regulator_enable(reg_8058_l13); + if (rc) { + pr_err("%s: '%s' regulator enable failed,\ + rc=%d\n", __func__, "8058_l13", rc); + return rc; + } + } else { + rc = regulator_force_disable(reg_8058_l13); + if (rc) + pr_warning("%s: '%s' regulator disable failed, rc=%d\n", + __func__, "8058_l13", rc); + } + return rc; + +} +#endif + +#ifdef CONFIG_FB_MSM_MIPI_DSI +int mdp_core_clk_rate_table[] = { + 85330000, + 85330000, + 160000000, + 200000000, +}; +#else +int mdp_core_clk_rate_table[] = { + 59080000, + 59080000, + 85330000, + 200000000, +}; +#endif + +static struct msm_panel_common_pdata mdp_pdata = { + .gpio = MDP_VSYNC_GPIO, + .mdp_core_clk_rate = 59080000, + .mdp_core_clk_table = mdp_core_clk_rate_table, + .num_mdp_clk = ARRAY_SIZE(mdp_core_clk_rate_table), +#ifdef CONFIG_MSM_BUS_SCALING + .mdp_bus_scale_table = &mdp_bus_scale_pdata, +#endif + .mdp_rev = MDP_REV_41, +}; + +#ifdef CONFIG_FB_MSM_TVOUT + +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors atv_bus_init_vectors[] = { + /* For now, 0th array entry is reserved. + * Please leave 0 as is and don't use it + */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; +static struct msm_bus_vectors atv_bus_def_vectors[] = { + /* For now, 0th array entry is reserved. + * Please leave 0 as is and don't use it + */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 236390400, + .ib = 265939200, + }, + /* Master and slaves can be from different fabrics */ + { + .src = MSM_BUS_MASTER_MDP_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 236390400, + .ib = 265939200, + }, +}; +static struct msm_bus_paths atv_bus_scale_usecases[] = { + { + ARRAY_SIZE(atv_bus_init_vectors), + atv_bus_init_vectors, + }, + { + ARRAY_SIZE(atv_bus_def_vectors), + atv_bus_def_vectors, + }, +}; +static struct msm_bus_scale_pdata atv_bus_scale_pdata = { + atv_bus_scale_usecases, + ARRAY_SIZE(atv_bus_scale_usecases), + .name = "atv", +}; +#endif + +static struct tvenc_platform_data atv_pdata = { + .poll = 0, + .pm_vid_en = atv_dac_power, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &atv_bus_scale_pdata, +#endif +}; +#endif + +static void __init msm_fb_add_devices(void) +{ +#ifdef CONFIG_FB_MSM_LCDC_DSUB + mdp_pdata.mdp_core_clk_table = NULL; + mdp_pdata.num_mdp_clk = 0; + mdp_pdata.mdp_core_clk_rate = 200000000; +#endif + if (machine_is_msm8x60_rumi3()) + msm_fb_register_device("mdp", NULL); + else + msm_fb_register_device("mdp", &mdp_pdata); + + msm_fb_register_device("lcdc", &lcdc_pdata); + msm_fb_register_device("mipi_dsi", &mipi_dsi_pdata); +#ifdef CONFIG_MSM_BUS_SCALING + msm_fb_register_device("dtv", &dtv_pdata); +#endif +#ifdef CONFIG_FB_MSM_TVOUT + msm_fb_register_device("tvenc", &atv_pdata); + msm_fb_register_device("tvout_device", NULL); +#endif +} + +#if (defined(CONFIG_MARIMBA_CORE)) && \ + (defined(CONFIG_MSM_BT_POWER) || defined(CONFIG_MSM_BT_POWER_MODULE)) + +static const struct { + char *name; + int vmin; + int vmax; +} bt_regs_info[] = { + { "8058_s3", 1800000, 1800000 }, + { "8058_s2", 1300000, 1300000 }, + { "8058_l8", 2900000, 3050000 }, +}; + +static struct { + bool enabled; +} bt_regs_status[] = { + { false }, + { false }, + { false }, +}; +static struct regulator *bt_regs[ARRAY_SIZE(bt_regs_info)]; + +static int bahama_bt(int on) +{ + int rc; + int i; + struct marimba config = { .mod_id = SLAVE_ID_BAHAMA}; + + struct bahama_variant_register { + const size_t size; + const struct bahama_config_register *set; + }; + + const struct bahama_config_register *p; + + u8 version; + + const struct bahama_config_register v10_bt_on[] = { + { 0xE9, 0x00, 0xFF }, + { 0xF4, 0x80, 0xFF }, + { 0xE4, 0x00, 0xFF }, + { 0xE5, 0x00, 0x0F }, +#ifdef CONFIG_WLAN + { 0xE6, 0x38, 0x7F }, + { 0xE7, 0x06, 0xFF }, +#endif + { 0xE9, 0x21, 0xFF }, + { 0x01, 0x0C, 0x1F }, + { 0x01, 0x08, 0x1F }, + }; + + const struct bahama_config_register v20_bt_on_fm_off[] = { + { 0x11, 0x0C, 0xFF }, + { 0x13, 0x01, 0xFF }, + { 0xF4, 0x80, 0xFF }, + { 0xF0, 0x00, 0xFF }, + { 0xE9, 0x00, 0xFF }, +#ifdef CONFIG_WLAN + { 0x81, 0x00, 0x7F }, + { 0x82, 0x00, 0xFF }, + { 0xE6, 0x38, 0x7F }, + { 0xE7, 0x06, 0xFF }, +#endif + { 0xE9, 0x21, 0xFF }, + }; + + const struct bahama_config_register v20_bt_on_fm_on[] = { + { 0x11, 0x0C, 0xFF }, + { 0x13, 0x01, 0xFF }, + { 0xF4, 0x86, 0xFF }, + { 0xF0, 0x06, 0xFF }, + { 0xE9, 0x00, 0xFF }, +#ifdef CONFIG_WLAN + { 0x81, 0x00, 0x7F }, + { 0x82, 0x00, 0xFF }, + { 0xE6, 0x38, 0x7F }, + { 0xE7, 0x06, 0xFF }, +#endif + { 0xE9, 0x21, 0xFF }, + }; + + const struct bahama_config_register v10_bt_off[] = { + { 0xE9, 0x00, 0xFF }, + }; + + const struct bahama_config_register v20_bt_off_fm_off[] = { + { 0xF4, 0x84, 0xFF }, + { 0xF0, 0x04, 0xFF }, + { 0xE9, 0x00, 0xFF } + }; + + const struct bahama_config_register v20_bt_off_fm_on[] = { + { 0xF4, 0x86, 0xFF }, + { 0xF0, 0x06, 0xFF }, + { 0xE9, 0x00, 0xFF } + }; + const struct bahama_variant_register bt_bahama[2][3] = { + { + { ARRAY_SIZE(v10_bt_off), v10_bt_off }, + { ARRAY_SIZE(v20_bt_off_fm_off), v20_bt_off_fm_off }, + { ARRAY_SIZE(v20_bt_off_fm_on), v20_bt_off_fm_on } + }, + { + { ARRAY_SIZE(v10_bt_on), v10_bt_on }, + { ARRAY_SIZE(v20_bt_on_fm_off), v20_bt_on_fm_off }, + { ARRAY_SIZE(v20_bt_on_fm_on), v20_bt_on_fm_on } + } + }; + + u8 offset = 0; /* index into bahama configs */ + + on = on ? 1 : 0; + version = read_bahama_ver(); + + if (version == VER_UNSUPPORTED) { + dev_err(&msm_bt_power_device.dev, + "%s: unsupported version\n", + __func__); + return -EIO; + } + + if (version == VER_2_0) { + if (marimba_get_fm_status(&config)) + offset = 0x01; + } + + /* Voting off 1.3V S2 Regulator,BahamaV2 used in Normal mode */ + if (on && (version == VER_2_0)) { + for (i = 0; i < ARRAY_SIZE(bt_regs_info); i++) { + if ((!strcmp(bt_regs_info[i].name, "8058_s2")) + && (bt_regs_status[i].enabled == true)) { + if (regulator_disable(bt_regs[i])) { + dev_err(&msm_bt_power_device.dev, + "%s: regulator disable failed", + __func__); + } + bt_regs_status[i].enabled = false; + break; + } + } + } + + p = bt_bahama[on][version + offset].set; + + dev_info(&msm_bt_power_device.dev, + "%s: found version %d\n", __func__, version); + + for (i = 0; i < bt_bahama[on][version + offset].size; i++) { + u8 value = (p+i)->value; + rc = marimba_write_bit_mask(&config, + (p+i)->reg, + &value, + sizeof((p+i)->value), + (p+i)->mask); + if (rc < 0) { + dev_err(&msm_bt_power_device.dev, + "%s: reg %d write failed: %d\n", + __func__, (p+i)->reg, rc); + return rc; + } + dev_dbg(&msm_bt_power_device.dev, + "%s: reg 0x%02x write value 0x%02x mask 0x%02x\n", + __func__, (p+i)->reg, + value, (p+i)->mask); + } + /* Update BT Status */ + if (on) + marimba_set_bt_status(&config, true); + else + marimba_set_bt_status(&config, false); + + return 0; +} + +static int bluetooth_use_regulators(int on) +{ + int i, recover = -1, rc = 0; + + for (i = 0; i < ARRAY_SIZE(bt_regs_info); i++) { + bt_regs[i] = on ? regulator_get(&msm_bt_power_device.dev, + bt_regs_info[i].name) : + (regulator_put(bt_regs[i]), NULL); + if (IS_ERR(bt_regs[i])) { + rc = PTR_ERR(bt_regs[i]); + dev_err(&msm_bt_power_device.dev, + "regulator %s get failed (%d)\n", + bt_regs_info[i].name, rc); + recover = i - 1; + bt_regs[i] = NULL; + break; + } + + if (!on) + continue; + + rc = regulator_set_voltage(bt_regs[i], + bt_regs_info[i].vmin, + bt_regs_info[i].vmax); + if (rc < 0) { + dev_err(&msm_bt_power_device.dev, + "regulator %s voltage set (%d)\n", + bt_regs_info[i].name, rc); + recover = i; + break; + } + } + + if (on && (recover > -1)) + for (i = recover; i >= 0; i--) { + regulator_put(bt_regs[i]); + bt_regs[i] = NULL; + } + + return rc; +} + +static int bluetooth_switch_regulators(int on) +{ + int i, rc = 0; + + for (i = 0; i < ARRAY_SIZE(bt_regs_info); i++) { + if (on && (bt_regs_status[i].enabled == false)) { + rc = regulator_enable(bt_regs[i]); + if (rc < 0) { + dev_err(&msm_bt_power_device.dev, + "regulator %s %s failed (%d)\n", + bt_regs_info[i].name, + "enable", rc); + if (i > 0) { + while (--i) { + regulator_disable(bt_regs[i]); + bt_regs_status[i].enabled + = false; + } + break; + } + } + bt_regs_status[i].enabled = true; + } else if (!on && (bt_regs_status[i].enabled == true)) { + rc = regulator_disable(bt_regs[i]); + if (rc < 0) { + dev_err(&msm_bt_power_device.dev, + "regulator %s %s failed (%d)\n", + bt_regs_info[i].name, + "disable", rc); + break; + } + bt_regs_status[i].enabled = false; + } + } + return rc; +} + +static struct msm_xo_voter *bt_clock; + +static int bluetooth_power(int on) +{ + int rc = 0; + int id; + + /* In case probe function fails, cur_connv_type would be -1 */ + id = adie_get_detected_connectivity_type(); + if (id != BAHAMA_ID) { + pr_err("%s: unexpected adie connectivity type: %d\n", + __func__, id); + return -ENODEV; + } + + if (on) { + + rc = bluetooth_use_regulators(1); + if (rc < 0) + goto out; + + rc = bluetooth_switch_regulators(1); + + if (rc < 0) + goto fail_put; + + bt_clock = msm_xo_get(MSM_XO_TCXO_D0, "bt_power"); + + if (IS_ERR(bt_clock)) { + pr_err("Couldn't get TCXO_D0 voter\n"); + goto fail_switch; + } + + rc = msm_xo_mode_vote(bt_clock, MSM_XO_MODE_ON); + + if (rc < 0) { + pr_err("Failed to vote for TCXO_DO ON\n"); + goto fail_vote; + } + + rc = bahama_bt(1); + + if (rc < 0) + goto fail_clock; + + msleep(10); + + rc = msm_xo_mode_vote(bt_clock, MSM_XO_MODE_PIN_CTRL); + + if (rc < 0) { + pr_err("Failed to vote for TCXO_DO pin control\n"); + goto fail_vote; + } + } else { + /* check for initial RFKILL block (power off) */ + /* some RFKILL versions/configurations rfkill_register */ + /* calls here for an initial set_block */ + /* avoid calling i2c and regulator before unblock (on) */ + if (platform_get_drvdata(&msm_bt_power_device) == NULL) { + dev_info(&msm_bt_power_device.dev, + "%s: initialized OFF/blocked\n", __func__); + goto out; + } + + bahama_bt(0); + +fail_clock: + msm_xo_mode_vote(bt_clock, MSM_XO_MODE_OFF); +fail_vote: + msm_xo_put(bt_clock); +fail_switch: + bluetooth_switch_regulators(0); +fail_put: + bluetooth_use_regulators(0); + } + +out: + if (rc < 0) + on = 0; + dev_info(&msm_bt_power_device.dev, + "Bluetooth power switch: state %d result %d\n", on, rc); + + return rc; } -static void __init msm8x60_init_irq(void) +#endif /*CONFIG_MARIMBA_CORE, CONFIG_MSM_BT_POWER, CONFIG_MSM_BT_POWER_MODULE*/ + +static void __init msm8x60_cfg_smsc911x(void) { - unsigned int i; + smsc911x_resources[1].start = + PM8058_GPIO_IRQ(PM8058_IRQ_BASE, 6); + smsc911x_resources[1].end = + PM8058_GPIO_IRQ(PM8058_IRQ_BASE, 6); +} + +#ifdef CONFIG_MSM_RPM +static struct msm_rpm_platform_data msm_rpm_data = { + .reg_base_addrs = { + [MSM_RPM_PAGE_STATUS] = MSM_RPM_BASE, + [MSM_RPM_PAGE_CTRL] = MSM_RPM_BASE + 0x400, + [MSM_RPM_PAGE_REQ] = MSM_RPM_BASE + 0x600, + [MSM_RPM_PAGE_ACK] = MSM_RPM_BASE + 0xa00, + }, + + .irq_ack = RPM_SCSS_CPU0_GP_HIGH_IRQ, + .irq_err = RPM_SCSS_CPU0_GP_LOW_IRQ, + .irq_vmpm = RPM_SCSS_CPU0_GP_MEDIUM_IRQ, + .msm_apps_ipc_rpm_reg = MSM_GCC_BASE + 0x008, + .msm_apps_ipc_rpm_val = 4, +}; +#endif + +struct msm_board_data { + struct msm_gpiomux_configs *gpiomux_cfgs; +}; + +static struct msm_board_data msm8x60_rumi3_board_data __initdata = { + .gpiomux_cfgs = msm8x60_surf_ffa_gpiomux_cfgs, +}; + +static struct msm_board_data msm8x60_sim_board_data __initdata = { + .gpiomux_cfgs = msm8x60_surf_ffa_gpiomux_cfgs, +}; + +static struct msm_board_data msm8x60_surf_board_data __initdata = { + .gpiomux_cfgs = msm8x60_surf_ffa_gpiomux_cfgs, +}; - gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, - (void *)MSM_QGIC_CPU_BASE); +static struct msm_board_data msm8x60_ffa_board_data __initdata = { + .gpiomux_cfgs = msm8x60_surf_ffa_gpiomux_cfgs, +}; - /* Edge trigger PPIs except AVS_SVICINT and AVS_SVICINTSWDONE */ - writel(0xFFFFD7FF, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); +static struct msm_board_data msm8x60_fluid_board_data __initdata = { + .gpiomux_cfgs = msm8x60_fluid_gpiomux_cfgs, +}; - /* RUMI does not adhere to GIC spec by enabling STIs by default. - * Enable/clear is supposed to be RO for STIs, but is RW on RUMI. +static struct msm_board_data msm8x60_charm_surf_board_data __initdata = { + .gpiomux_cfgs = msm8x60_charm_gpiomux_cfgs, +}; + +static struct msm_board_data msm8x60_charm_ffa_board_data __initdata = { + .gpiomux_cfgs = msm8x60_charm_gpiomux_cfgs, +}; + +static void __init msm8x60_init(struct msm_board_data *board_data) +{ + uint32_t soc_platform_version; + + /* + * Initialize RPM first as other drivers and devices may need + * it for their initialization. */ - if (!machine_is_msm8x60_sim()) - writel(0x0000FFFF, MSM_QGIC_DIST_BASE + GIC_DIST_ENABLE_SET); +#ifdef CONFIG_MSM_RPM + BUG_ON(msm_rpm_init(&msm_rpm_data)); +#endif + BUG_ON(msm_rpmrs_levels_init(msm_rpmrs_levels, + ARRAY_SIZE(msm_rpmrs_levels))); + if (msm_xo_init()) + pr_err("Failed to initialize XO votes\n"); + + if (socinfo_init() < 0) + printk(KERN_ERR "%s: socinfo_init() failed!\n", + __func__); + msm8x60_check_2d_hardware(); + + /* Change SPM handling of core 1 if PMM 8160 is present. */ + soc_platform_version = socinfo_get_platform_version(); + if (SOCINFO_VERSION_MAJOR(soc_platform_version) == 1 && + SOCINFO_VERSION_MINOR(soc_platform_version) >= 2) { + struct msm_spm_platform_data *spm_data; + + spm_data = &msm_spm_data_v1[1]; + spm_data->reg_init_values[MSM_SPM_REG_SAW_CFG] &= ~0x0F00UL; + spm_data->reg_init_values[MSM_SPM_REG_SAW_CFG] |= 0x0100UL; + + spm_data = &msm_spm_data[1]; + spm_data->reg_init_values[MSM_SPM_REG_SAW_CFG] &= ~0x0F00UL; + spm_data->reg_init_values[MSM_SPM_REG_SAW_CFG] |= 0x0100UL; + } + + /* + * Initialize SPM before acpuclock as the latter calls into SPM + * driver to set ACPU voltages. + */ + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) != 1) + msm_spm_init(msm_spm_data, ARRAY_SIZE(msm_spm_data)); + else + msm_spm_init(msm_spm_data_v1, ARRAY_SIZE(msm_spm_data_v1)); + + /* + * Set regulators 8901_l4 and 8901_l6 to be always on in HPM for SURF + * devices so that the RPM doesn't drop into a low power mode that an + * un-reworked SURF cannot resume from. + */ + if (machine_is_msm8x60_surf()) { + rpm_vreg_init_pdata[RPM_VREG_ID_PM8901_L4] + .init_data.constraints.always_on = 1; + rpm_vreg_init_pdata[RPM_VREG_ID_PM8901_L6] + .init_data.constraints.always_on = 1; + } + + /* + * Disable regulator info printing so that regulator registration + * messages do not enter the kmsg log. + */ + regulator_suppress_info_printing(); + + /* Initialize regulators needed for clock_init. */ + platform_add_devices(early_regulators, ARRAY_SIZE(early_regulators)); + + msm8660_clock_init(); + + /* Buses need to be initialized before early-device registration + * to get the platform data for fabrics. + */ + msm8x60_init_buses(); + platform_add_devices(early_devices, ARRAY_SIZE(early_devices)); + /* CPU frequency control is not supported on simulated targets. */ + if (!machine_is_msm8x60_rumi3() && !machine_is_msm8x60_sim()) + msm_acpu_clock_init(&msm8x60_acpu_clock_data); + + /* No EBI2 on 8660 charm targets */ + if (!machine_is_msm8x60_fusion() && !machine_is_msm8x60_fusn_ffa()) + msm8x60_init_ebi2(); + msm8x60_init_tlmm(); + msm8x60_init_gpiomux(board_data->gpiomux_cfgs); + msm8x60_init_uart12dm(); + msm8x60_init_mmc(); + +#if defined(CONFIG_PMIC8058_OTHC) || defined(CONFIG_PMIC8058_OTHC_MODULE) + msm8x60_init_pm8058_othc(); +#endif + + if (machine_is_msm8x60_fluid()) { + pm8058_platform_data.sub_devices[PM8058_SUBDEV_KPD]. + platform_data = &fluid_keypad_data; + pm8058_platform_data.sub_devices[PM8058_SUBDEV_KPD].pdata_size + = sizeof(fluid_keypad_data); + } else { + pm8058_platform_data.sub_devices[PM8058_SUBDEV_KPD]. + platform_data = &ffa_keypad_data; + pm8058_platform_data.sub_devices[PM8058_SUBDEV_KPD].pdata_size + = sizeof(ffa_keypad_data); - /* FIXME: Not installing AVS_SVICINT and AVS_SVICINTSWDONE yet - * as they are configured as level, which does not play nice with - * handle_percpu_irq. + } + + /* Disable END_CALL simulation function of powerkey on fluid */ + if (machine_is_msm8x60_fluid()) { + pwrkey_pdata.pwrkey_time_ms = 0; + } + + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() || + machine_is_msm8x60_fluid() || machine_is_msm8x60_fusion() || + machine_is_msm8x60_fusn_ffa()) { + msm8x60_cfg_smsc911x(); + if (SOCINFO_VERSION_MAJOR(socinfo_get_version()) != 1) + platform_add_devices(msm_footswitch_devices, + msm_num_footswitch_devices); + platform_add_devices(surf_devices, + ARRAY_SIZE(surf_devices)); + +#ifdef CONFIG_MSM_DSPS + if (machine_is_msm8x60_fluid()) { + platform_device_unregister(&msm_gsbi12_qup_i2c_device); + msm8x60_init_dsps(); + } +#endif + +#ifdef CONFIG_USB_EHCI_MSM_72K + /* + * Drive MPP2 pin HIGH for PHY to generate ID interrupts on 8660 + * fluid */ - for (i = GIC_PPI_START; i < GIC_SPI_START; i++) { - if (i != AVS_SVICINT && i != AVS_SVICINTSWDONE) - irq_set_handler(i, handle_percpu_irq); + if (machine_is_msm8x60_fluid()) { + pm8901_mpp_config_digital_out(1, + PM8901_MPP_DIG_LEVEL_L5, 1); + } + msm_add_host(0, &msm_usb_host_pdata); +#endif + } else { + msm8x60_configure_smc91x(); + platform_add_devices(rumi_sim_devices, + ARRAY_SIZE(rumi_sim_devices)); + } +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa()) + msm8x60_cfg_isp1763(); +#endif +#ifdef CONFIG_BATTERY_MSM8X60 + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() || + machine_is_msm8x60_fusion() || + machine_is_msm8x60_fusn_ffa() || machine_is_msm8x60_fluid()) + platform_device_register(&msm_charger_device); +#endif + + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) + platform_add_devices(charm_devices, ARRAY_SIZE(charm_devices)); + + if (!machine_is_msm8x60_fluid()) + pm8058_platform_data.charger_sub_device + = &pm8058_charger_sub_dev; + +#if defined(CONFIG_SPI_QUP) || defined(CONFIG_SPI_QUP_MODULE) + if (machine_is_msm8x60_fluid()) + platform_device_register(&msm_gsbi10_qup_spi_device); + else + platform_device_register(&msm_gsbi1_qup_spi_device); +#endif + +#if defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C) || \ + defined(CONFIG_TOUCHSCREEN_CYTTSP_I2C_MODULE) + if (machine_is_msm8x60_fluid()) + cyttsp_set_params(); +#endif + if (!machine_is_msm8x60_sim()) + msm_fb_add_devices(); + fixup_i2c_configs(); + register_i2c_devices(); + + platform_device_register(&smsc911x_device); + +#if (defined(CONFIG_SPI_QUP)) && \ + (defined(CONFIG_FB_MSM_LCDC_SAMSUNG_OLED_PT) || \ + defined(CONFIG_FB_MSM_LCDC_AUO_WVGA)) + + if (machine_is_msm8x60_fluid()) { +#ifdef CONFIG_FB_MSM_LCDC_SAMSUNG_OLED_PT + if (SOCINFO_VERSION_MAJOR(soc_platform_version) < 3) { + spi_register_board_info(lcdc_samsung_spi_board_info, + ARRAY_SIZE(lcdc_samsung_spi_board_info)); + } else +#endif + { +#ifdef CONFIG_FB_MSM_LCDC_AUO_WVGA + spi_register_board_info(lcdc_auo_spi_board_info, + ARRAY_SIZE(lcdc_auo_spi_board_info)); +#endif + } } +#endif + + msm_pm_set_platform_data(msm_pm_data, ARRAY_SIZE(msm_pm_data)); + msm_pm_set_rpm_wakeup_irq(RPM_SCSS_CPU0_WAKE_UP_IRQ); + msm_cpuidle_set_states(msm_cstates, ARRAY_SIZE(msm_cstates), + msm_pm_data); + +#ifdef CONFIG_SENSORS_MSM_ADC + if (machine_is_msm8x60_fluid()) { + msm_adc_pdata.dev_names = msm_adc_fluid_device_names; + msm_adc_pdata.num_adc = ARRAY_SIZE(msm_adc_fluid_device_names); + if (SOCINFO_VERSION_MAJOR(soc_platform_version) < 3) + msm_adc_pdata.gpio_config = APROC_CONFIG; + else + msm_adc_pdata.gpio_config = MPROC_CONFIG; + } + msm_adc_pdata.target_hw = MSM_8x60; +#endif +#ifdef CONFIG_MSM8X60_AUDIO + msm_snddev_init(); +#endif +#if defined(CONFIG_GPIO_SX150X) || defined(CONFIG_GPIO_SX150X_MODULE) + if (machine_is_msm8x60_fluid()) + platform_device_register(&fluid_leds_gpio); + else + platform_device_register(&gpio_leds); +#endif + + /* configure pmic leds */ + if (machine_is_msm8x60_fluid()) { + pm8058_platform_data.sub_devices[PM8058_SUBDEV_LED]. + platform_data = &pm8058_fluid_flash_leds_data; + pm8058_platform_data.sub_devices[PM8058_SUBDEV_LED].pdata_size + = sizeof(pm8058_fluid_flash_leds_data); + } else { + pm8058_platform_data.sub_devices[PM8058_SUBDEV_LED]. + platform_data = &pm8058_flash_leds_data; + pm8058_platform_data.sub_devices[PM8058_SUBDEV_LED].pdata_size + = sizeof(pm8058_flash_leds_data); + } + + if (machine_is_msm8x60_ffa() || machine_is_msm8x60_fusn_ffa()) { + pm8058_platform_data.sub_devices[PM8058_SUBDEV_VIB]. + platform_data = &pmic_vib_pdata; + pm8058_platform_data.sub_devices[PM8058_SUBDEV_VIB]. + pdata_size = sizeof(pmic_vib_pdata); + } + + msm8x60_multi_sdio_init(); +} + +static void __init msm8x60_rumi3_init(void) +{ + msm8x60_init(&msm8x60_rumi3_board_data); +} + +static void __init msm8x60_sim_init(void) +{ + msm8x60_init(&msm8x60_sim_board_data); +} + +static void __init msm8x60_surf_init(void) +{ + msm8x60_init(&msm8x60_surf_board_data); +} + +static void __init msm8x60_ffa_init(void) +{ + msm8x60_init(&msm8x60_ffa_board_data); +} + +static void __init msm8x60_fluid_init(void) +{ + msm8x60_init(&msm8x60_fluid_board_data); +} + +static void __init msm8x60_charm_surf_init(void) +{ + msm8x60_init(&msm8x60_charm_surf_board_data); +} + +static void __init msm8x60_charm_ffa_init(void) +{ + msm8x60_init(&msm8x60_charm_ffa_board_data); } -static void __init msm8x60_init(void) +static void __init msm8x60_charm_init_early(void) { + msm8x60_allocate_memory_regions(); } MACHINE_START(MSM8X60_RUMI3, "QCT MSM8X60 RUMI3") .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, .init_irq = msm8x60_init_irq, - .init_machine = msm8x60_init, + .init_machine = msm8x60_rumi3_init, .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, MACHINE_END -MACHINE_START(MSM8X60_SURF, "QCT MSM8X60 SURF") +MACHINE_START(MSM8X60_SIM, "QCT MSM8X60 SIMULATOR") .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, .init_irq = msm8x60_init_irq, - .init_machine = msm8x60_init, + .init_machine = msm8x60_sim_init, .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, MACHINE_END -MACHINE_START(MSM8X60_SIM, "QCT MSM8X60 SIMULATOR") +MACHINE_START(MSM8X60_SURF, "QCT MSM8X60 SURF") .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, .init_irq = msm8x60_init_irq, - .init_machine = msm8x60_init, + .init_machine = msm8x60_surf_init, .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, MACHINE_END MACHINE_START(MSM8X60_FFA, "QCT MSM8X60 FFA") .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, + .init_irq = msm8x60_init_irq, + .init_machine = msm8x60_ffa_init, + .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, +MACHINE_END + +MACHINE_START(MSM8X60_FLUID, "QCT MSM8X60 FLUID") + .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, + .init_irq = msm8x60_init_irq, + .init_machine = msm8x60_fluid_init, + .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, +MACHINE_END + +MACHINE_START(MSM8X60_FUSION, "QCT MSM8X60 FUSION SURF") + .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, + .init_irq = msm8x60_init_irq, + .init_machine = msm8x60_charm_surf_init, + .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, +MACHINE_END + +MACHINE_START(MSM8X60_FUSN_FFA, "QCT MSM8X60 FUSION FFA") + .map_io = msm8x60_map_io, + .reserve = msm8x60_reserve, .init_irq = msm8x60_init_irq, - .init_machine = msm8x60_init, + .init_machine = msm8x60_charm_ffa_init, .timer = &msm_timer, + .init_early = msm8x60_charm_init_early, MACHINE_END diff --git a/arch/arm/mach-msm/board-qsd8x50.c b/arch/arm/mach-msm/board-qsd8x50.c index 6a96911b0ad..b6170f9efa4 100644 --- a/arch/arm/mach-msm/board-qsd8x50.c +++ b/arch/arm/mach-msm/board-qsd8x50.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and @@ -9,107 +9,2111 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include #include #include #include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include #include #include #include #include +#include +#include +#include #include -#include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devices.h" +#include "timer.h" +#include "msm-keypad-devices.h" +#include "pm.h" +#include "proc_comm.h" +#ifdef CONFIG_USB_ANDROID +#include +#endif + +#define TOUCHPAD_SUSPEND 34 +#define TOUCHPAD_IRQ 38 + +#define MSM_PMEM_SF_SIZE 0x1700000 + +#define SMEM_SPINLOCK_I2C "S:6" + +#define MSM_PMEM_ADSP_SIZE 0x2A05000 +#define MSM_FB_SIZE 0x2EE000 +#define MSM_AUDIO_SIZE 0x80000 + +#ifdef CONFIG_MSM_SOC_REV_A +#define MSM_SMI_BASE 0xE0000000 +#else +#define MSM_SMI_BASE 0x00000000 +#endif + +#define MSM_SHARED_RAM_PHYS (MSM_SMI_BASE + 0x00100000) + +#define MSM_PMEM_SMI_BASE (MSM_SMI_BASE + 0x02B00000) +#define MSM_PMEM_SMI_SIZE 0x01500000 + +#define MSM_FB_BASE MSM_PMEM_SMI_BASE +#define MSM_PMEM_SMIPOOL_BASE (MSM_FB_BASE + MSM_FB_SIZE) +#define MSM_PMEM_SMIPOOL_SIZE (MSM_PMEM_SMI_SIZE - MSM_FB_SIZE) + +#define PMEM_KERNEL_EBI1_SIZE 0x28000 + +#define PMIC_VREG_WLAN_LEVEL 2600 +#define PMIC_VREG_GP6_LEVEL 2900 + +#define FPGA_SDCC_STATUS 0x70000280 + +static struct resource smc91x_resources[] = { + [0] = { + .flags = IORESOURCE_MEM, + }, + [1] = { + .flags = IORESOURCE_IRQ, + }, +}; + +#ifdef CONFIG_USB_FUNCTION +static struct usb_mass_storage_platform_data usb_mass_storage_pdata = { + .nluns = 0x02, + .buf_size = 16384, + .vendor = "GOOGLE", + .product = "Mass storage", + .release = 0xffff, +}; + +static struct platform_device mass_storage_device = { + .name = "usb_mass_storage", + .id = -1, + .dev = { + .platform_data = &usb_mass_storage_pdata, + }, +}; +#endif + +#ifdef CONFIG_USB_ANDROID +static char *usb_functions_default[] = { + "diag", + "modem", + "nmea", + "rmnet", + "usb_mass_storage", +}; + +static char *usb_functions_default_adb[] = { + "diag", + "adb", + "modem", + "nmea", + "rmnet", + "usb_mass_storage", +}; + +static char *usb_functions_rndis[] = { + "rndis", +}; + +static char *usb_functions_rndis_adb[] = { + "rndis", + "adb", +}; + +static char *usb_functions_all[] = { +#ifdef CONFIG_USB_ANDROID_RNDIS + "rndis", +#endif +#ifdef CONFIG_USB_ANDROID_DIAG + "diag", +#endif + "adb", +#ifdef CONFIG_USB_F_SERIAL + "modem", + "nmea", +#endif +#ifdef CONFIG_USB_ANDROID_RMNET + "rmnet", +#endif + "usb_mass_storage", +#ifdef CONFIG_USB_ANDROID_ACM + "acm", +#endif +}; + +static struct android_usb_product usb_products[] = { + { + .product_id = 0x9026, + .num_functions = ARRAY_SIZE(usb_functions_default), + .functions = usb_functions_default, + }, + { + .product_id = 0x9025, + .num_functions = ARRAY_SIZE(usb_functions_default_adb), + .functions = usb_functions_default_adb, + }, + { + .product_id = 0xf00e, + .num_functions = ARRAY_SIZE(usb_functions_rndis), + .functions = usb_functions_rndis, + }, + { + .product_id = 0x9024, + .num_functions = ARRAY_SIZE(usb_functions_rndis_adb), + .functions = usb_functions_rndis_adb, + }, +}; + +static struct usb_mass_storage_platform_data mass_storage_pdata = { + .nluns = 1, + .vendor = "Qualcomm Incorporated", + .product = "Mass storage", + .release = 0x0100, +}; + +static struct platform_device usb_mass_storage_device = { + .name = "usb_mass_storage", + .id = -1, + .dev = { + .platform_data = &mass_storage_pdata, + }, +}; + +static struct usb_ether_platform_data rndis_pdata = { + /* ethaddr is filled by board_serialno_setup */ + .vendorID = 0x05C6, + .vendorDescr = "Qualcomm Incorporated", +}; + +static struct platform_device rndis_device = { + .name = "rndis", + .id = -1, + .dev = { + .platform_data = &rndis_pdata, + }, +}; + +static struct android_usb_platform_data android_usb_pdata = { + .vendor_id = 0x05C6, + .product_id = 0x9026, + .version = 0x0100, + .product_name = "Qualcomm HSUSB Device", + .manufacturer_name = "Qualcomm Incorporated", + .num_products = ARRAY_SIZE(usb_products), + .products = usb_products, + .num_functions = ARRAY_SIZE(usb_functions_all), + .functions = usb_functions_all, + .serial_number = "1234567890ABCDEF", +}; + +static struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &android_usb_pdata, + }, +}; + +static int __init board_serialno_setup(char *serialno) +{ + int i; + char *src = serialno; + + /* create a fake MAC address from our serial number. + * first byte is 0x02 to signify locally administered. + */ + rndis_pdata.ethaddr[0] = 0x02; + for (i = 0; *src; i++) { + /* XOR the USB serial across the remaining bytes */ + rndis_pdata.ethaddr[i % (ETH_ALEN - 1) + 1] ^= *src++; + } + + android_usb_pdata.serial_number = serialno; + return 1; +} +__setup("androidboot.serialno=", board_serialno_setup); +#endif + +static struct platform_device smc91x_device = { + .name = "smc91x", + .id = 0, + .num_resources = ARRAY_SIZE(smc91x_resources), + .resource = smc91x_resources, +}; + +#ifdef CONFIG_USB_FUNCTION +static struct usb_function_map usb_functions_map[] = { + {"diag", 0}, + {"adb", 1}, + {"modem", 2}, + {"nmea", 3}, + {"mass_storage", 4}, + {"ethernet", 5}, +}; + +/* dynamic composition */ +static struct usb_composition usb_func_composition[] = { + { + .product_id = 0x9012, + .functions = 0x5, /* 0101 */ + }, + + { + .product_id = 0x9013, + .functions = 0x15, /* 10101 */ + }, + + { + .product_id = 0x9014, + .functions = 0x30, /* 110000 */ + }, + + { + .product_id = 0x9015, + .functions = 0x12, /* 10010 */ + }, + + { + .product_id = 0x9016, + .functions = 0xD, /* 01101 */ + }, + + { + .product_id = 0x9017, + .functions = 0x1D, /* 11101 */ + }, + + { + .product_id = 0xF000, + .functions = 0x10, /* 10000 */ + }, + + { + .product_id = 0xF009, + .functions = 0x20, /* 100000 */ + }, + + { + .product_id = 0x9018, + .functions = 0x1F, /* 011111 */ + }, + + { + .product_id = 0x901A, + .functions = 0x0F, /* 01111 */ + }, +}; +#endif + +static struct msm_handset_platform_data hs_platform_data = { + .hs_name = "8k_handset", + .pwr_key_delay_ms = 500, /* 0 will disable end key */ +}; + +static struct platform_device hs_device = { + .name = "msm-handset", + .id = -1, + .dev = { + .platform_data = &hs_platform_data, + }, +}; + +#ifdef CONFIG_USB_FS_HOST +static struct msm_gpio fsusb_config[] = { + { GPIO_CFG(139, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "fs_dat" }, + { GPIO_CFG(140, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "fs_se0" }, + { GPIO_CFG(141, 3, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "fs_oe_n" }, +}; + +static int fsusb_gpio_init(void) +{ + return msm_gpios_request(fsusb_config, ARRAY_SIZE(fsusb_config)); +} + +static void msm_fsusb_setup_gpio(unsigned int enable) +{ + if (enable) + msm_gpios_enable(fsusb_config, ARRAY_SIZE(fsusb_config)); + else + msm_gpios_disable(fsusb_config, ARRAY_SIZE(fsusb_config)); + +} +#endif + +#define MSM_USB_BASE ((unsigned)addr) + +static struct msm_hsusb_platform_data msm_hsusb_pdata = { +#ifdef CONFIG_USB_FUNCTION + .version = 0x0100, + .phy_info = (USB_PHY_INTEGRATED | USB_PHY_MODEL_180NM), + .vendor_id = 0x5c6, + .product_name = "Qualcomm HSUSB Device", + .serial_number = "1234567890ABCDEF", + .manufacturer_name = "Qualcomm Incorporated", + .compositions = usb_func_composition, + .num_compositions = ARRAY_SIZE(usb_func_composition), + .function_map = usb_functions_map, + .num_functions = ARRAY_SIZE(usb_functions_map), + .config_gpio = NULL, + +#endif +}; + +static struct vreg *vreg_usb; +static void msm_hsusb_vbus_power(unsigned phy_info, int on) +{ + + switch (PHY_TYPE(phy_info)) { + case USB_PHY_INTEGRATED: + if (on) + msm_hsusb_vbus_powerup(); + else + msm_hsusb_vbus_shutdown(); + break; + case USB_PHY_SERIAL_PMIC: + if (on) + vreg_enable(vreg_usb); + else + vreg_disable(vreg_usb); + break; + default: + pr_err("%s: undefined phy type ( %X ) \n", __func__, + phy_info); + } + +} + +static struct msm_usb_host_platform_data msm_usb_host_pdata = { + .phy_info = (USB_PHY_INTEGRATED | USB_PHY_MODEL_180NM), +}; + +#ifdef CONFIG_USB_FS_HOST +static struct msm_usb_host_platform_data msm_usb_host2_pdata = { + .phy_info = USB_PHY_SERIAL_PMIC, + .config_gpio = msm_fsusb_setup_gpio, + .vbus_power = msm_hsusb_vbus_power, +}; +#endif + +static struct android_pmem_platform_data android_pmem_kernel_ebi1_pdata = { + .name = PMEM_KERNEL_EBI1_DATA_NAME, + /* if no allocator_type, defaults to PMEM_ALLOCATORTYPE_BITMAP, + * the only valid choice at this time. The board structure is + * set to all zeros by the C runtime initialization and that is now + * the enum value of PMEM_ALLOCATORTYPE_BITMAP, now forced to 0 in + * include/linux/android_pmem.h. + */ + .cached = 0, +}; + +#ifdef CONFIG_KERNEL_PMEM_SMI_REGION + +static struct android_pmem_platform_data android_pmem_kernel_smi_pdata = { + .name = PMEM_KERNEL_SMI_DATA_NAME, + /* if no allocator_type, defaults to PMEM_ALLOCATORTYPE_BITMAP, + * the only valid choice at this time. The board structure is + * set to all zeros by the C runtime initialization and that is now + * the enum value of PMEM_ALLOCATORTYPE_BITMAP, now forced to 0 in + * include/linux/android_pmem.h. + */ + .cached = 0, +}; + +#endif + +static struct android_pmem_platform_data android_pmem_pdata = { + .name = "pmem", + .allocator_type = PMEM_ALLOCATORTYPE_ALLORNOTHING, + .cached = 1, +}; + +static struct android_pmem_platform_data android_pmem_adsp_pdata = { + .name = "pmem_adsp", + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, +}; + +static struct android_pmem_platform_data android_pmem_smipool_pdata = { + .name = "pmem_smipool", + .size = MSM_PMEM_SMIPOOL_SIZE, + .allocator_type = PMEM_ALLOCATORTYPE_BITMAP, + .cached = 0, +}; + + +static struct platform_device android_pmem_device = { + .name = "android_pmem", + .id = 0, + .dev = { .platform_data = &android_pmem_pdata }, +}; + +static struct platform_device android_pmem_adsp_device = { + .name = "android_pmem", + .id = 1, + .dev = { .platform_data = &android_pmem_adsp_pdata }, +}; + +static struct platform_device android_pmem_smipool_device = { + .name = "android_pmem", + .id = 2, + .dev = { .platform_data = &android_pmem_smipool_pdata }, +}; + + +static struct platform_device android_pmem_kernel_ebi1_device = { + .name = "android_pmem", + .id = 3, + .dev = { .platform_data = &android_pmem_kernel_ebi1_pdata }, +}; + +#ifdef CONFIG_KERNEL_PMEM_SMI_REGION +static struct platform_device android_pmem_kernel_smi_device = { + .name = "android_pmem", + .id = 4, + .dev = { .platform_data = &android_pmem_kernel_smi_pdata }, +}; +#endif + +static struct resource msm_fb_resources[] = { + { + .flags = IORESOURCE_DMA, + } +}; + +static int msm_fb_detect_panel(const char *name) +{ + int ret = -EPERM; + + if (machine_is_qsd8x50_ffa()) { + if (!strncmp(name, "mddi_toshiba_wvga_pt", 20)) + ret = 0; + else + ret = -ENODEV; + } else if ((machine_is_qsd8x50_surf()) + && !strcmp(name, "lcdc_external")) + ret = 0; + + return ret; +} + +static struct msm_fb_platform_data msm_fb_pdata = { + .detect_client = msm_fb_detect_panel, +}; + +static struct platform_device msm_fb_device = { + .name = "msm_fb", + .id = 0, + .num_resources = ARRAY_SIZE(msm_fb_resources), + .resource = msm_fb_resources, + .dev = { + .platform_data = &msm_fb_pdata, + } +}; + +static struct msm_gpio bma_spi_gpio_config_data[] = { + { GPIO_CFG(22, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "bma_irq" }, +}; + +static int msm_bma_gpio_setup(struct device *dev) +{ + int rc; + + rc = msm_gpios_request_enable(bma_spi_gpio_config_data, + ARRAY_SIZE(bma_spi_gpio_config_data)); + + return rc; +} + +static void msm_bma_gpio_teardown(struct device *dev) +{ + msm_gpios_disable_free(bma_spi_gpio_config_data, + ARRAY_SIZE(bma_spi_gpio_config_data)); +} + +static struct bma150_platform_data bma_pdata = { + .setup = msm_bma_gpio_setup, + .teardown = msm_bma_gpio_teardown, +}; + +static struct resource qsd_spi_resources[] = { + { + .name = "spi_irq_in", + .start = INT_SPI_INPUT, + .end = INT_SPI_INPUT, + .flags = IORESOURCE_IRQ, + }, + { + .name = "spi_irq_out", + .start = INT_SPI_OUTPUT, + .end = INT_SPI_OUTPUT, + .flags = IORESOURCE_IRQ, + }, + { + .name = "spi_irq_err", + .start = INT_SPI_ERROR, + .end = INT_SPI_ERROR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "spi_base", + .start = 0xA1200000, + .end = 0xA1200000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "spidm_channels", + .flags = IORESOURCE_DMA, + }, + { + .name = "spidm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static struct platform_device qsd_device_spi = { + .name = "spi_qsd", + .id = 0, + .num_resources = ARRAY_SIZE(qsd_spi_resources), + .resource = qsd_spi_resources, +}; + +static struct spi_board_info msm_spi_board_info[] __initdata = { + { + .modalias = "bma150", + .mode = SPI_MODE_3, + .irq = MSM_GPIO_TO_INT(22), + .bus_num = 0, + .chip_select = 0, + .max_speed_hz = 10000000, + .platform_data = &bma_pdata, + }, +}; + +#define CT_CSR_PHYS 0xA8700000 +#define TCSR_SPI_MUX (ct_csr_base + 0x54) +static int msm_qsd_spi_dma_config(void) +{ + void __iomem *ct_csr_base = 0; + u32 spi_mux; + int ret = 0; + + ct_csr_base = ioremap(CT_CSR_PHYS, PAGE_SIZE); + if (!ct_csr_base) { + pr_err("%s: Could not remap %x\n", __func__, CT_CSR_PHYS); + return -1; + } + + spi_mux = readl(TCSR_SPI_MUX); + switch (spi_mux) { + case (1): + qsd_spi_resources[4].start = DMOV_HSUART1_RX_CHAN; + qsd_spi_resources[4].end = DMOV_HSUART1_TX_CHAN; + qsd_spi_resources[5].start = DMOV_HSUART1_RX_CRCI; + qsd_spi_resources[5].end = DMOV_HSUART1_TX_CRCI; + break; + case (2): + qsd_spi_resources[4].start = DMOV_HSUART2_RX_CHAN; + qsd_spi_resources[4].end = DMOV_HSUART2_TX_CHAN; + qsd_spi_resources[5].start = DMOV_HSUART2_RX_CRCI; + qsd_spi_resources[5].end = DMOV_HSUART2_TX_CRCI; + break; + case (3): + qsd_spi_resources[4].start = DMOV_CE_OUT_CHAN; + qsd_spi_resources[4].end = DMOV_CE_IN_CHAN; + qsd_spi_resources[5].start = DMOV_CE_OUT_CRCI; + qsd_spi_resources[5].end = DMOV_CE_IN_CRCI; + break; + default: + ret = -1; + } + + iounmap(ct_csr_base); + return ret; +} + +static struct msm_gpio qsd_spi_gpio_config_data[] = { + { GPIO_CFG(17, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_clk" }, + { GPIO_CFG(18, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_mosi" }, + { GPIO_CFG(19, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_miso" }, + { GPIO_CFG(20, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), "spi_cs0" }, + { GPIO_CFG(21, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_16MA), "spi_pwr" }, +}; + +static int msm_qsd_spi_gpio_config(void) +{ + int rc; + + rc = msm_gpios_request_enable(qsd_spi_gpio_config_data, + ARRAY_SIZE(qsd_spi_gpio_config_data)); + if (rc) + return rc; + + /* Set direction for SPI_PWR */ + gpio_direction_output(21, 1); + + return 0; +} + +static void msm_qsd_spi_gpio_release(void) +{ + msm_gpios_disable_free(qsd_spi_gpio_config_data, + ARRAY_SIZE(qsd_spi_gpio_config_data)); +} + +static struct msm_spi_platform_data qsd_spi_pdata = { + .max_clock_speed = 19200000, + .gpio_config = msm_qsd_spi_gpio_config, + .gpio_release = msm_qsd_spi_gpio_release, + .dma_config = msm_qsd_spi_dma_config, +}; + +static void __init msm_qsd_spi_init(void) +{ + qsd_device_spi.dev.platform_data = &qsd_spi_pdata; +} + +static int mddi_toshiba_pmic_bl(int level) +{ + int ret = -EPERM; + + if (machine_is_qsd8x50_ffa()) { + ret = pmic_set_led_intensity(LED_LCD, level); + + if (ret) + printk(KERN_WARNING "%s: can't set lcd backlight!\n", + __func__); + } + + return ret; +} + +static struct msm_panel_common_pdata mddi_toshiba_pdata = { + .pmic_backlight = mddi_toshiba_pmic_bl, +}; + +static struct platform_device mddi_toshiba_device = { + .name = "mddi_toshiba", + .id = 0, + .dev = { + .platform_data = &mddi_toshiba_pdata, + } +}; + +static void msm_fb_vreg_config(const char *name, int on) +{ + struct vreg *vreg; + int ret = 0; + + vreg = vreg_get(NULL, name); + if (IS_ERR(vreg)) { + printk(KERN_ERR "%s: vreg_get(%s) failed (%ld)\n", + __func__, name, PTR_ERR(vreg)); + return; + } + + ret = (on) ? vreg_enable(vreg) : vreg_disable(vreg); + if (ret) + printk(KERN_ERR "%s: %s(%s) failed!\n", + __func__, (on) ? "vreg_enable" : "vreg_disable", name); +} + +#define MDDI_RST_OUT_GPIO 100 + +static int mddi_power_save_on; +static int msm_fb_mddi_power_save(int on) +{ + int flag_on = !!on; + int ret = 0; + + + if (mddi_power_save_on == flag_on) + return ret; + + mddi_power_save_on = flag_on; + + if (!flag_on && machine_is_qsd8x50_ffa()) { + gpio_set_value(MDDI_RST_OUT_GPIO, 0); + mdelay(1); + } + + ret = pmic_lp_mode_control(flag_on ? OFF_CMD : ON_CMD, + PM_VREG_LP_MSME2_ID); + if (ret) + printk(KERN_ERR "%s: pmic_lp_mode_control failed!\n", __func__); + + msm_fb_vreg_config("gp5", flag_on); + msm_fb_vreg_config("boost", flag_on); + + if (flag_on && machine_is_qsd8x50_ffa()) { + gpio_set_value(MDDI_RST_OUT_GPIO, 0); + mdelay(1); + gpio_set_value(MDDI_RST_OUT_GPIO, 1); + gpio_set_value(MDDI_RST_OUT_GPIO, 1); + mdelay(1); + } + + return ret; +} + +static int msm_fb_mddi_sel_clk(u32 *clk_rate) +{ + *clk_rate *= 2; + return 0; +} + +static struct mddi_platform_data mddi_pdata = { + .mddi_power_save = msm_fb_mddi_power_save, + .mddi_sel_clk = msm_fb_mddi_sel_clk, +}; + +static struct msm_panel_common_pdata mdp_pdata = { + .gpio = 98, +}; + +static void __init msm_fb_add_devices(void) +{ + msm_fb_register_device("mdp", &mdp_pdata); + msm_fb_register_device("pmdh", &mddi_pdata); + msm_fb_register_device("emdh", &mddi_pdata); + msm_fb_register_device("tvenc", 0); + msm_fb_register_device("lcdc", 0); +} + +static struct resource msm_audio_resources[] = { + { + .flags = IORESOURCE_DMA, + }, + { + .name = "aux_pcm_dout", + .start = 68, + .end = 68, + .flags = IORESOURCE_IO, + }, + { + .name = "aux_pcm_din", + .start = 69, + .end = 69, + .flags = IORESOURCE_IO, + }, + { + .name = "aux_pcm_syncout", + .start = 70, + .end = 70, + .flags = IORESOURCE_IO, + }, + { + .name = "aux_pcm_clkin_a", + .start = 71, + .end = 71, + .flags = IORESOURCE_IO, + }, + { + .name = "sdac_din", + .start = 144, + .end = 144, + .flags = IORESOURCE_IO, + }, + { + .name = "sdac_dout", + .start = 145, + .end = 145, + .flags = IORESOURCE_IO, + }, + { + .name = "sdac_wsout", + .start = 143, + .end = 143, + .flags = IORESOURCE_IO, + }, + { + .name = "cc_i2s_clk", + .start = 142, + .end = 142, + .flags = IORESOURCE_IO, + }, + { + .name = "audio_master_clkout", + .start = 146, + .end = 146, + .flags = IORESOURCE_IO, + }, + { + .name = "audio_base_addr", + .start = 0xa0700000, + .end = 0xa0700000 + 4, + .flags = IORESOURCE_MEM, + }, + +}; + +static unsigned audio_gpio_on[] = { + GPIO_CFG(68, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_DOUT */ + GPIO_CFG(69, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_DIN */ + GPIO_CFG(70, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_SYNC */ + GPIO_CFG(71, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* PCM_CLK */ + GPIO_CFG(142, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* CC_I2S_CLK */ + GPIO_CFG(143, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* SADC_WSOUT */ + GPIO_CFG(144, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* SADC_DIN */ + GPIO_CFG(145, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* SDAC_DOUT */ + GPIO_CFG(146, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* MA_CLK_OUT */ +}; + +static void __init audio_gpio_init(void) +{ + int pin, rc; + + for (pin = 0; pin < ARRAY_SIZE(audio_gpio_on); pin++) { + rc = gpio_tlmm_config(audio_gpio_on[pin], + GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR + "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, audio_gpio_on[pin], rc); + return; + } + } +} + +static struct platform_device msm_audio_device = { + .name = "msm_audio", + .id = 0, + .num_resources = ARRAY_SIZE(msm_audio_resources), + .resource = msm_audio_resources, +}; + +static struct resource bluesleep_resources[] = { + { + .name = "gpio_host_wake", + .start = 21, + .end = 21, + .flags = IORESOURCE_IO, + }, + { + .name = "gpio_ext_wake", + .start = 19, + .end = 19, + .flags = IORESOURCE_IO, + }, + { + .name = "host_wake", + .start = MSM_GPIO_TO_INT(21), + .end = MSM_GPIO_TO_INT(21), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_bluesleep_device = { + .name = "bluesleep", + .id = -1, + .num_resources = ARRAY_SIZE(bluesleep_resources), + .resource = bluesleep_resources, +}; + +#ifdef CONFIG_BT +static struct platform_device msm_bt_power_device = { + .name = "bt_power", +}; + +enum { + BT_SYSRST, + BT_WAKE, + BT_HOST_WAKE, + BT_VDD_IO, + BT_RFR, + BT_CTS, + BT_RX, + BT_TX, + BT_VDD_FREG +}; + +static struct msm_gpio bt_config_power_off[] = { + { GPIO_CFG(18, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "BT SYSRST" }, + { GPIO_CFG(19, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "BT WAKE" }, + { GPIO_CFG(21, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "HOST WAKE" }, + { GPIO_CFG(22, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "BT VDD_IO" }, + { GPIO_CFG(43, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1DM_RFR" }, + { GPIO_CFG(44, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1DM_CTS" }, + { GPIO_CFG(45, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1DM_RX" }, + { GPIO_CFG(46, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "UART1DM_TX" } +}; + +static struct msm_gpio bt_config_power_on[] = { + { GPIO_CFG(18, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "BT SYSRST" }, + { GPIO_CFG(19, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "BT WAKE" }, + { GPIO_CFG(21, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "HOST WAKE" }, + { GPIO_CFG(22, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "BT VDD_IO" }, + { GPIO_CFG(43, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "UART1DM_RFR" }, + { GPIO_CFG(44, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "UART1DM_CTS" }, + { GPIO_CFG(45, 2, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "UART1DM_RX" }, + { GPIO_CFG(46, 2, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "UART1DM_TX" } +}; + +static struct msm_gpio wlan_config_power_off[] = { + { GPIO_CFG(62, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "SDC2_CLK" }, + { GPIO_CFG(63, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "SDC2_CMD" }, + { GPIO_CFG(64, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "SDC2_D3" }, + { GPIO_CFG(65, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "SDC2_D2" }, + { GPIO_CFG(66, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "SDC2_D1" }, + { GPIO_CFG(67, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "SDC2_D0" }, + { GPIO_CFG(113, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "VDD_WLAN" }, + { GPIO_CFG(138, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + "WLAN_PWD" } +}; + +static struct msm_gpio wlan_config_power_on[] = { + { GPIO_CFG(62, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "SDC2_CLK" }, + { GPIO_CFG(63, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "SDC2_CMD" }, + { GPIO_CFG(64, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "SDC2_D3" }, + { GPIO_CFG(65, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "SDC2_D2" }, + { GPIO_CFG(66, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "SDC2_D1" }, + { GPIO_CFG(67, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "SDC2_D0" }, + { GPIO_CFG(113, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "VDD_WLAN" }, + { GPIO_CFG(138, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), + "WLAN_PWD" } +}; + +static int bluetooth_power(int on) +{ + int rc; + struct vreg *vreg_wlan; + + vreg_wlan = vreg_get(NULL, "wlan"); + + if (IS_ERR(vreg_wlan)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_wlan)); + return PTR_ERR(vreg_wlan); + } + + if (on) { + /* units of mV, steps of 50 mV */ + rc = vreg_set_level(vreg_wlan, PMIC_VREG_WLAN_LEVEL); + if (rc) { + printk(KERN_ERR "%s: vreg wlan set level failed (%d)\n", + __func__, rc); + return -EIO; + } + rc = vreg_enable(vreg_wlan); + if (rc) { + printk(KERN_ERR "%s: vreg wlan enable failed (%d)\n", + __func__, rc); + return -EIO; + } + + rc = msm_gpios_enable(bt_config_power_on, + ARRAY_SIZE(bt_config_power_on)); + if (rc < 0) { + printk(KERN_ERR + "%s: bt power on gpio config failed: %d\n", + __func__, rc); + return rc; + } + + if (machine_is_qsd8x50_ffa()) { + rc = msm_gpios_enable + (wlan_config_power_on, + ARRAY_SIZE(wlan_config_power_on)); + if (rc < 0) { + printk + (KERN_ERR + "%s: wlan power on gpio config failed: %d\n", + __func__, rc); + return rc; + } + } + + gpio_set_value(22, on); /* VDD_IO */ + gpio_set_value(18, on); /* SYSRST */ + + if (machine_is_qsd8x50_ffa()) { + gpio_set_value(138, 0); /* WLAN: CHIP_PWD */ + gpio_set_value(113, on); /* WLAN */ + } + } else { + if (machine_is_qsd8x50_ffa()) { + gpio_set_value(138, on); /* WLAN: CHIP_PWD */ + gpio_set_value(113, on); /* WLAN */ + } + + gpio_set_value(18, on); /* SYSRST */ + gpio_set_value(22, on); /* VDD_IO */ + + rc = vreg_disable(vreg_wlan); + if (rc) { + printk(KERN_ERR "%s: vreg wlan disable failed (%d)\n", + __func__, rc); + return -EIO; + } + + rc = msm_gpios_enable(bt_config_power_off, + ARRAY_SIZE(bt_config_power_off)); + if (rc < 0) { + printk(KERN_ERR + "%s: bt power off gpio config failed: %d\n", + __func__, rc); + return rc; + } + + if (machine_is_qsd8x50_ffa()) { + rc = msm_gpios_enable + (wlan_config_power_off, + ARRAY_SIZE(wlan_config_power_off)); + if (rc < 0) { + printk + (KERN_ERR + "%s: wlan power off gpio config failed: %d\n", + __func__, rc); + return rc; + } + } + } + + printk(KERN_DEBUG "Bluetooth power switch: %d\n", on); + + return 0; +} + +static void __init bt_power_init(void) +{ + struct vreg *vreg_bt; + int rc; + + if (machine_is_qsd8x50_ffa()) { + gpio_set_value(138, 0); /* WLAN: CHIP_PWD */ + gpio_set_value(113, 0); /* WLAN */ + } + + gpio_set_value(18, 0); /* SYSRST */ + gpio_set_value(22, 0); /* VDD_IO */ + + /* do not have vreg bt defined, gp6 is the same */ + /* vreg_get parameter 1 (struct device *) is ignored */ + vreg_bt = vreg_get(NULL, "gp6"); + + if (IS_ERR(vreg_bt)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_bt)); + goto exit; + } + + /* units of mV, steps of 50 mV */ + rc = vreg_set_level(vreg_bt, PMIC_VREG_GP6_LEVEL); + if (rc) { + printk(KERN_ERR "%s: vreg bt set level failed (%d)\n", + __func__, rc); + goto exit; + } + rc = vreg_enable(vreg_bt); + if (rc) { + printk(KERN_ERR "%s: vreg bt enable failed (%d)\n", + __func__, rc); + goto exit; + } + + if (bluetooth_power(0)) + goto exit; + + msm_bt_power_device.dev.platform_data = &bluetooth_power; + + printk(KERN_DEBUG "Bluetooth power switch: initialized\n"); + +exit: + return; +} +#else +#define bt_power_init(x) do {} while (0) +#endif + +static struct platform_device msm_device_pmic_leds = { + .name = "pmic-leds", + .id = -1, +}; + +/* TSIF begin */ +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + +#define TSIF_A_SYNC GPIO_CFG(106, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_A_DATA GPIO_CFG(107, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_A_EN GPIO_CFG(108, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_A_CLK GPIO_CFG(109, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) + +static const struct msm_gpio tsif_gpios[] = { + { .gpio_cfg = TSIF_A_CLK, .label = "tsif_clk", }, + { .gpio_cfg = TSIF_A_EN, .label = "tsif_en", }, + { .gpio_cfg = TSIF_A_DATA, .label = "tsif_data", }, + { .gpio_cfg = TSIF_A_SYNC, .label = "tsif_sync", }, +}; + +static struct msm_tsif_platform_data tsif_platform_data = { + .num_gpios = ARRAY_SIZE(tsif_gpios), + .gpios = tsif_gpios, + .tsif_clk = "tsif_clk", + .tsif_ref_clk = "tsif_ref_clk", +}; + +#endif /* defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) */ +/* TSIF end */ + +#ifdef CONFIG_QSD_SVS +#define TPS65023_MAX_DCDC1 1600 +#else +#define TPS65023_MAX_DCDC1 CONFIG_QSD_PMIC_DEFAULT_DCDC1 +#endif + +static int qsd8x50_tps65023_set_dcdc1(int mVolts) +{ + int rc = 0; +#ifdef CONFIG_QSD_SVS + rc = tps65023_set_dcdc1_level(mVolts); + /* By default the TPS65023 will be initialized to 1.225V. + * So we can safely switch to any frequency within this + * voltage even if the device is not probed/ready. + */ + if (rc == -ENODEV && mVolts <= CONFIG_QSD_PMIC_DEFAULT_DCDC1) + rc = 0; +#else + /* Disallow frequencies not supported in the default PMIC + * output voltage. + */ + if (mVolts > CONFIG_QSD_PMIC_DEFAULT_DCDC1) + rc = -EFAULT; +#endif + return rc; +} + +static struct msm_acpu_clock_platform_data qsd8x50_clock_data = { + .acpu_switch_time_us = 20, + .max_speed_delta_khz = 256000, + .vdd_switch_time_us = 62, + .max_vdd = TPS65023_MAX_DCDC1, + .acpu_set_vdd = qsd8x50_tps65023_set_dcdc1, +}; + + +static void touchpad_gpio_release(void) +{ + gpio_free(TOUCHPAD_IRQ); + gpio_free(TOUCHPAD_SUSPEND); +} + +static int touchpad_gpio_setup(void) +{ + int rc; + int suspend_pin = TOUCHPAD_SUSPEND; + int irq_pin = TOUCHPAD_IRQ; + unsigned suspend_cfg = + GPIO_CFG(suspend_pin, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA); + unsigned irq_cfg = + GPIO_CFG(irq_pin, 1, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA); + + rc = gpio_request(irq_pin, "msm_touchpad_irq"); + if (rc) { + pr_err("gpio_request failed on pin %d (rc=%d)\n", + irq_pin, rc); + goto err_gpioconfig; + } + rc = gpio_request(suspend_pin, "msm_touchpad_suspend"); + if (rc) { + pr_err("gpio_request failed on pin %d (rc=%d)\n", + suspend_pin, rc); + goto err_gpioconfig; + } + rc = gpio_tlmm_config(suspend_cfg, GPIO_CFG_ENABLE); + if (rc) { + pr_err("gpio_tlmm_config failed on pin %d (rc=%d)\n", + suspend_pin, rc); + goto err_gpioconfig; + } + rc = gpio_tlmm_config(irq_cfg, GPIO_CFG_ENABLE); + if (rc) { + pr_err("gpio_tlmm_config failed on pin %d (rc=%d)\n", + irq_pin, rc); + goto err_gpioconfig; + } + return rc; + +err_gpioconfig: + touchpad_gpio_release(); + return rc; +} + +static struct msm_touchpad_platform_data msm_touchpad_data = { + .gpioirq = TOUCHPAD_IRQ, + .gpiosuspend = TOUCHPAD_SUSPEND, + .gpio_setup = touchpad_gpio_setup, + .gpio_shutdown = touchpad_gpio_release +}; + +#define KBD_RST 35 +#define KBD_IRQ 36 + +static void kbd_gpio_release(void) +{ + gpio_free(KBD_IRQ); + gpio_free(KBD_RST); +} + +static int kbd_gpio_setup(void) +{ + int rc; + int respin = KBD_RST; + int irqpin = KBD_IRQ; + unsigned rescfg = + GPIO_CFG(respin, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA); + unsigned irqcfg = + GPIO_CFG(irqpin, 0, GPIO_CFG_INPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA); + + rc = gpio_request(irqpin, "gpio_keybd_irq"); + if (rc) { + pr_err("gpio_request failed on pin %d (rc=%d)\n", + irqpin, rc); + goto err_gpioconfig; + } + rc = gpio_request(respin, "gpio_keybd_reset"); + if (rc) { + pr_err("gpio_request failed on pin %d (rc=%d)\n", + respin, rc); + goto err_gpioconfig; + } + rc = gpio_tlmm_config(rescfg, GPIO_CFG_ENABLE); + if (rc) { + pr_err("gpio_tlmm_config failed on pin %d (rc=%d)\n", + respin, rc); + goto err_gpioconfig; + } + rc = gpio_tlmm_config(irqcfg, GPIO_CFG_ENABLE); + if (rc) { + pr_err("gpio_tlmm_config failed on pin %d (rc=%d)\n", + irqpin, rc); + goto err_gpioconfig; + } + return rc; + +err_gpioconfig: + kbd_gpio_release(); + return rc; +} + +/* use gpio output pin to toggle keyboard external reset pin */ +static void kbd_hwreset(int kbd_mclrpin) +{ + gpio_direction_output(kbd_mclrpin, 0); + gpio_direction_output(kbd_mclrpin, 1); +} + +static struct msm_i2ckbd_platform_data msm_kybd_data = { + .hwrepeat = 0, + .scanset1 = 1, + .gpioreset = KBD_RST, + .gpioirq = KBD_IRQ, + .gpio_setup = kbd_gpio_setup, + .gpio_shutdown = kbd_gpio_release, + .hw_reset = kbd_hwreset, +}; + +static struct i2c_board_info msm_i2c_board_info[] __initdata = { + { + I2C_BOARD_INFO("glidesensor", 0x2A), + .irq = MSM_GPIO_TO_INT(TOUCHPAD_IRQ), + .platform_data = &msm_touchpad_data + }, + { + I2C_BOARD_INFO("msm-i2ckbd", 0x3A), + .type = "msm-i2ckbd", + .irq = MSM_GPIO_TO_INT(KBD_IRQ), + .platform_data = &msm_kybd_data + }, +#ifdef CONFIG_MT9D112 + { + I2C_BOARD_INFO("mt9d112", 0x78 >> 1), + }, +#endif +#ifdef CONFIG_S5K3E2FX + { + I2C_BOARD_INFO("s5k3e2fx", 0x20 >> 1), + }, +#endif +#ifdef CONFIG_MT9P012 + { + I2C_BOARD_INFO("mt9p012", 0x6C >> 1), + }, +#endif +#ifdef CONFIG_MT9P012_KM + { + I2C_BOARD_INFO("mt9p012_km", 0x6C >> 2), + }, +#endif +#if defined(CONFIG_MT9T013) || defined(CONFIG_SENSORS_MT9T013) + { + I2C_BOARD_INFO("mt9t013", 0x6C), + }, +#endif + { + I2C_BOARD_INFO("tps65023", 0x48), + }, +}; + +#ifdef CONFIG_MSM_CAMERA +static uint32_t camera_off_gpio_table[] = { + /* parallel CAMERA interfaces */ + GPIO_CFG(0, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT0 */ + GPIO_CFG(1, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT1 */ + GPIO_CFG(2, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT2 */ + GPIO_CFG(3, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT3 */ + GPIO_CFG(4, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT4 */ + GPIO_CFG(5, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT5 */ + GPIO_CFG(6, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT6 */ + GPIO_CFG(7, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT7 */ + GPIO_CFG(8, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT8 */ + GPIO_CFG(9, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT9 */ + GPIO_CFG(10, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT10 */ + GPIO_CFG(11, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT11 */ + GPIO_CFG(12, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* PCLK */ + GPIO_CFG(13, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* HSYNC_IN */ + GPIO_CFG(14, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* VSYNC_IN */ + GPIO_CFG(15, 0, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_2MA), /* MCLK */ +}; + +static uint32_t camera_on_gpio_table[] = { + /* parallel CAMERA interfaces */ + GPIO_CFG(0, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT0 */ + GPIO_CFG(1, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT1 */ + GPIO_CFG(2, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT2 */ + GPIO_CFG(3, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT3 */ + GPIO_CFG(4, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT4 */ + GPIO_CFG(5, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT5 */ + GPIO_CFG(6, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT6 */ + GPIO_CFG(7, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT7 */ + GPIO_CFG(8, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT8 */ + GPIO_CFG(9, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT9 */ + GPIO_CFG(10, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT10 */ + GPIO_CFG(11, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* DAT11 */ + GPIO_CFG(12, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* PCLK */ + GPIO_CFG(13, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* HSYNC_IN */ + GPIO_CFG(14, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), /* VSYNC_IN */ + GPIO_CFG(15, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_16MA), /* MCLK */ +}; + +static uint32_t camera_on_gpio_ffa_table[] = { + /* parallel CAMERA interfaces */ + GPIO_CFG(95, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_16MA), /* I2C_SCL */ + GPIO_CFG(96, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_16MA), /* I2C_SDA */ + /* FFA front Sensor Reset */ + GPIO_CFG(137, 1, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_16MA), +}; + +static uint32_t camera_off_gpio_ffa_table[] = { + /* FFA front Sensor Reset */ + GPIO_CFG(137, 0, GPIO_CFG_INPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_16MA), +}; + +static void config_gpio_table(uint32_t *table, int len) +{ + int n, rc; + for (n = 0; n < len; n++) { + rc = gpio_tlmm_config(table[n], GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR "%s: gpio_tlmm_config(%#x)=%d\n", + __func__, table[n], rc); + break; + } + } +} + +static struct vreg *vreg_gp2; +static struct vreg *vreg_gp3; + +static void msm_camera_vreg_config(int vreg_en) +{ + int rc; + + if (vreg_gp2 == NULL) { + vreg_gp2 = vreg_get(NULL, "gp2"); + if (IS_ERR(vreg_gp2)) { + printk(KERN_ERR "%s: vreg_get(%s) failed (%ld)\n", + __func__, "gp2", PTR_ERR(vreg_gp2)); + return; + } + + rc = vreg_set_level(vreg_gp2, 1800); + if (rc) { + printk(KERN_ERR "%s: GP2 set_level failed (%d)\n", + __func__, rc); + } + } + + if (vreg_gp3 == NULL) { + vreg_gp3 = vreg_get(NULL, "gp3"); + if (IS_ERR(vreg_gp3)) { + printk(KERN_ERR "%s: vreg_get(%s) failed (%ld)\n", + __func__, "gp3", PTR_ERR(vreg_gp3)); + return; + } + + rc = vreg_set_level(vreg_gp3, 2800); + if (rc) { + printk(KERN_ERR "%s: GP3 set level failed (%d)\n", + __func__, rc); + } + } + + if (vreg_en) { + rc = vreg_enable(vreg_gp2); + if (rc) { + printk(KERN_ERR "%s: GP2 enable failed (%d)\n", + __func__, rc); + } + + rc = vreg_enable(vreg_gp3); + if (rc) { + printk(KERN_ERR "%s: GP3 enable failed (%d)\n", + __func__, rc); + } + } else { + rc = vreg_disable(vreg_gp2); + if (rc) { + printk(KERN_ERR "%s: GP2 disable failed (%d)\n", + __func__, rc); + } + + rc = vreg_disable(vreg_gp3); + if (rc) { + printk(KERN_ERR "%s: GP3 disable failed (%d)\n", + __func__, rc); + } + } +} -#include "devices.h" +static int config_camera_on_gpios(void) +{ + int vreg_en = 1; -extern struct sys_timer msm_timer; + if (machine_is_qsd8x50_ffa()) { + config_gpio_table(camera_on_gpio_ffa_table, + ARRAY_SIZE(camera_on_gpio_ffa_table)); -static const resource_size_t qsd8x50_surf_smc91x_base __initdata = 0x70000300; -static const unsigned qsd8x50_surf_smc91x_gpio __initdata = 156; + msm_camera_vreg_config(vreg_en); + gpio_set_value(137, 0); + } + config_gpio_table(camera_on_gpio_table, + ARRAY_SIZE(camera_on_gpio_table)); + return 0; +} -/* Leave smc91x resources empty here, as we'll fill them in - * at run-time: they vary from board to board, and the true - * configuration won't be known until boot. - */ -static struct resource smc91x_resources[] = { - [0] = { - .flags = IORESOURCE_MEM, +static void config_camera_off_gpios(void) +{ + int vreg_en = 0; + + if (machine_is_qsd8x50_ffa()) { + config_gpio_table(camera_off_gpio_ffa_table, + ARRAY_SIZE(camera_off_gpio_ffa_table)); + + msm_camera_vreg_config(vreg_en); + } + config_gpio_table(camera_off_gpio_table, + ARRAY_SIZE(camera_off_gpio_table)); +} + +static struct resource msm_camera_resources[] = { + { + .start = 0xA0F00000, + .end = 0xA0F00000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, }, - [1] = { - .flags = IORESOURCE_IRQ, + { + .start = INT_VFE, + .end = INT_VFE, + .flags = IORESOURCE_IRQ, }, }; -static struct platform_device smc91x_device = { - .name = "smc91x", - .id = 0, - .num_resources = ARRAY_SIZE(smc91x_resources), - .resource = smc91x_resources, +static struct msm_camera_device_platform_data msm_camera_device_data = { + .camera_gpio_on = config_camera_on_gpios, + .camera_gpio_off = config_camera_off_gpios, + .ioext.mdcphy = MSM_MDC_PHYS, + .ioext.mdcsz = MSM_MDC_SIZE, + .ioext.appphy = MSM_CLK_CTL_PHYS, + .ioext.appsz = MSM_CLK_CTL_SIZE, }; -static int __init msm_init_smc91x(void) +int pmic_set_flash_led_current(enum pmic8058_leds id, unsigned mA) { - if (machine_is_qsd8x50_surf()) { - smc91x_resources[0].start = qsd8x50_surf_smc91x_base; - smc91x_resources[0].end = qsd8x50_surf_smc91x_base + 0xff; - smc91x_resources[1].start = - gpio_to_irq(qsd8x50_surf_smc91x_gpio); - smc91x_resources[1].end = - gpio_to_irq(qsd8x50_surf_smc91x_gpio); - platform_device_register(&smc91x_device); - } + int rc; + rc = pmic_flash_led_set_current(mA); + return rc; +} +static struct msm_camera_sensor_flash_src msm_flash_src = { + .flash_sr_type = MSM_CAMERA_FLASH_SRC_PMIC, + ._fsrc.pmic_src.num_of_src = 1, + ._fsrc.pmic_src.low_current = 30, + ._fsrc.pmic_src.high_current = 100, + ._fsrc.pmic_src.led_src_1 = 0, + ._fsrc.pmic_src.led_src_2 = 0, + ._fsrc.pmic_src.pmic_set_current = pmic_set_flash_led_current, +}; - return 0; +#ifdef CONFIG_MT9D112 +static struct msm_camera_sensor_flash_data flash_mt9d112 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9d112_data = { + .sensor_name = "mt9d112", + .sensor_reset = 17, + .sensor_pwd = 85, + .vcm_pwd = 0, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_mt9d112 +}; + +static struct platform_device msm_camera_sensor_mt9d112 = { + .name = "msm_camera_mt9d112", + .dev = { + .platform_data = &msm_camera_sensor_mt9d112_data, + }, +}; +#endif + +#ifdef CONFIG_S5K3E2FX +static struct msm_camera_sensor_flash_data flash_s5k3e2fx = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_s5k3e2fx_data = { + .sensor_name = "s5k3e2fx", + .sensor_reset = 17, + .sensor_pwd = 85, + /*.vcm_pwd = 31, */ /* CAM1_VCM_EN, enabled in a9 */ + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_s5k3e2fx +}; + +static struct platform_device msm_camera_sensor_s5k3e2fx = { + .name = "msm_camera_s5k3e2fx", + .dev = { + .platform_data = &msm_camera_sensor_s5k3e2fx_data, + }, +}; +#endif + +#ifdef CONFIG_MT9P012 +static struct msm_camera_sensor_flash_data flash_mt9p012 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9p012_data = { + .sensor_name = "mt9p012", + .sensor_reset = 17, + .sensor_pwd = 85, + .vcm_pwd = 88, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_mt9p012 +}; + +static struct platform_device msm_camera_sensor_mt9p012 = { + .name = "msm_camera_mt9p012", + .dev = { + .platform_data = &msm_camera_sensor_mt9p012_data, + }, +}; +#endif + +#ifdef CONFIG_MT9P012_KM +static struct msm_camera_sensor_flash_data flash_mt9p012_km = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9p012_km_data = { + .sensor_name = "mt9p012_km", + .sensor_reset = 17, + .sensor_pwd = 85, + .vcm_pwd = 88, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_mt9p012_km +}; + +static struct platform_device msm_camera_sensor_mt9p012_km = { + .name = "msm_camera_mt9p012_km", + .dev = { + .platform_data = &msm_camera_sensor_mt9p012_km_data, + }, +}; +#endif + +#ifdef CONFIG_MT9T013 +static struct msm_camera_sensor_flash_data flash_mt9t013 = { + .flash_type = MSM_CAMERA_FLASH_LED, + .flash_src = &msm_flash_src +}; + +static struct msm_camera_sensor_info msm_camera_sensor_mt9t013_data = { + .sensor_name = "mt9t013", + .sensor_reset = 17, + .sensor_pwd = 85, + .vcm_pwd = 0, + .vcm_enable = 0, + .pdata = &msm_camera_device_data, + .resource = msm_camera_resources, + .num_resources = ARRAY_SIZE(msm_camera_resources), + .flash_data = &flash_mt9t013 +}; + +static struct platform_device msm_camera_sensor_mt9t013 = { + .name = "msm_camera_mt9t013", + .dev = { + .platform_data = &msm_camera_sensor_mt9t013_data, + }, +}; +#endif +#endif /*CONFIG_MSM_CAMERA*/ + +static u32 msm_calculate_batt_capacity(u32 current_voltage); + +static struct msm_psy_batt_pdata msm_psy_batt_data = { + .voltage_min_design = 3200, + .voltage_max_design = 4200, + .avail_chg_sources = AC_CHG | USB_CHG , + .batt_technology = POWER_SUPPLY_TECHNOLOGY_LION, + .calculate_capacity = &msm_calculate_batt_capacity, +}; + +static u32 msm_calculate_batt_capacity(u32 current_voltage) +{ + u32 low_voltage = msm_psy_batt_data.voltage_min_design; + u32 high_voltage = msm_psy_batt_data.voltage_max_design; + + return (current_voltage - low_voltage) * 100 + / (high_voltage - low_voltage); } -module_init(msm_init_smc91x); -static int hsusb_phy_init_seq[] = { - 0x08, 0x31, /* Increase HS Driver Amplitude */ - 0x20, 0x32, /* Enable and set Pre-Emphasis Depth to 10% */ - -1 +static struct platform_device msm_batt_device = { + .name = "msm-battery", + .id = -1, + .dev.platform_data = &msm_psy_batt_data, }; +static int hsusb_rpc_connect(int connect) +{ + if (connect) + return msm_hsusb_rpc_connect(); + else + return msm_hsusb_rpc_close(); +} + +static int msm_hsusb_pmic_notif_init(void (*callback)(int online), int init) +{ + int ret; + + if (init) { + ret = msm_pm_app_rpc_init(callback); + } else { + msm_pm_app_rpc_deinit(callback); + ret = 0; + } + return ret; +} +static int msm_hsusb_ldo_init(int init); +static int msm_hsusb_ldo_enable(int enable); + static struct msm_otg_platform_data msm_otg_pdata = { - .phy_init_seq = hsusb_phy_init_seq, - .mode = USB_PERIPHERAL, - .otg_control = OTG_PHY_CONTROL, + .rpc_connect = hsusb_rpc_connect, + .pmic_vbus_notif_init = msm_hsusb_pmic_notif_init, + .pemp_level = PRE_EMPHASIS_WITH_10_PERCENT, + .cdr_autoreset = CDR_AUTO_RESET_DEFAULT, + .drv_ampl = HS_DRV_AMPLITUDE_5_PERCENT, + .vbus_power = msm_hsusb_vbus_power, + .chg_vbus_draw = hsusb_chg_vbus_draw, + .chg_connected = hsusb_chg_connected, + .chg_init = hsusb_chg_init, + .phy_can_powercollapse = 1, + .ldo_init = msm_hsusb_ldo_init, + .ldo_enable = msm_hsusb_ldo_enable, + .pclk_src_name = "ebi1_usb_clk", }; +static struct msm_hsusb_gadget_platform_data msm_gadget_pdata; + static struct platform_device *devices[] __initdata = { - &msm_device_uart3, + &msm_fb_device, + &mddi_toshiba_device, + &smc91x_device, &msm_device_smd, - &msm_device_otg, - &msm_device_hsusb, - &msm_device_hsusb_host, + &msm_device_dmov, + &android_pmem_kernel_ebi1_device, +#ifdef CONFIG_KERNEL_PMEM_SMI_REGION + &android_pmem_kernel_smi_device, +#endif + &android_pmem_device, + &android_pmem_adsp_device, + &android_pmem_smipool_device, + &msm_device_nand, + &msm_device_i2c, + &qsd_device_spi, +#ifdef CONFIG_USB_FUNCTION + &mass_storage_device, +#endif +#ifdef CONFIG_USB_ANDROID + &usb_mass_storage_device, + &rndis_device, +#ifdef CONFIG_USB_ANDROID_DIAG + &usb_diag_device, +#endif +#ifdef CONFIG_USB_F_SERIAL + &usb_gadget_fserial_device, +#endif + &android_usb_device, +#endif + &msm_device_tssc, + &msm_audio_device, + &msm_device_uart_dm1, + &msm_bluesleep_device, +#ifdef CONFIG_BT + &msm_bt_power_device, +#endif +#if !defined(CONFIG_MSM_SERIAL_DEBUGGER) + &msm_device_uart3, +#endif + &msm_device_pmic_leds, + &msm_kgsl_3d0, + &hs_device, +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + &msm_device_tsif, +#endif +#ifdef CONFIG_MT9T013 + &msm_camera_sensor_mt9t013, +#endif +#ifdef CONFIG_MT9D112 + &msm_camera_sensor_mt9d112, +#endif +#ifdef CONFIG_S5K3E2FX + &msm_camera_sensor_s5k3e2fx, +#endif +#ifdef CONFIG_MT9P012 + &msm_camera_sensor_mt9p012, +#endif +#ifdef CONFIG_MT9P012_KM + &msm_camera_sensor_mt9p012_km, +#endif + &msm_batt_device, }; -static struct msm_mmc_gpio sdc1_gpio_cfg[] = { - {51, "sdc1_dat_3"}, - {52, "sdc1_dat_2"}, - {53, "sdc1_dat_1"}, - {54, "sdc1_dat_0"}, - {55, "sdc1_cmd"}, - {56, "sdc1_clk"} -}; +static void __init qsd8x50_init_irq(void) +{ + msm_init_irq(); + msm_init_sirc(); +} + +static void usb_mpp_init(void) +{ + unsigned rc; + unsigned mpp_usb = 20; + + if (machine_is_qsd8x50_ffa()) { + rc = mpp_config_digital_out(mpp_usb, + MPP_CFG(MPP_DLOGIC_LVL_VDD, + MPP_DLOGIC_OUT_CTRL_HIGH)); + if (rc) + pr_err("%s: configuring mpp pin" + "to enable 3.3V LDO failed\n", __func__); + } +} + +/* TBD: 8x50 FFAs have internal 3p3 voltage regulator as opposed to + * external 3p3 voltage regulator on Surf platform. There is no way + * s/w can detect fi concerned regulator is internal or external to + * to MSM. Internal 3p3 regulator is powered through boost voltage + * regulator where as external 3p3 regulator is powered through VPH. + * So for internal voltage regulator it is required to power on + * boost voltage regulator first. Unfortunately some of the FFAs are + * re-worked to install external 3p3 regulator. For now, assuming all + * FFAs have 3p3 internal regulators and all SURFs have external 3p3 + * regulator as there is no way s/w can determine if theregulator is + * internal or external. May be, we can implement this flag as kernel + * boot parameters so that we can change code behaviour dynamically + */ +static int regulator_3p3_is_internal; +static struct vreg *vreg_5v; +static struct vreg *vreg_3p3; +static int msm_hsusb_ldo_init(int init) +{ + if (init) { + if (regulator_3p3_is_internal) { + vreg_5v = vreg_get(NULL, "boost"); + if (IS_ERR(vreg_5v)) + return PTR_ERR(vreg_5v); + vreg_set_level(vreg_5v, 5000); + } + + vreg_3p3 = vreg_get(NULL, "usb"); + if (IS_ERR(vreg_3p3)) + return PTR_ERR(vreg_3p3); + vreg_set_level(vreg_3p3, 3300); + } else { + if (regulator_3p3_is_internal) + vreg_put(vreg_5v); + vreg_put(vreg_3p3); + } + + return 0; +} + +static int msm_hsusb_ldo_enable(int enable) +{ + static int ldo_status; + int ret; + + if (ldo_status == enable) + return 0; + + if (regulator_3p3_is_internal && (!vreg_5v || IS_ERR(vreg_5v))) + return -ENODEV; + if (!vreg_3p3 || IS_ERR(vreg_3p3)) + return -ENODEV; + + ldo_status = enable; + + if (enable) { + if (regulator_3p3_is_internal) { + ret = vreg_enable(vreg_5v); + if (ret) + return ret; + + /* power supply to 3p3 regulator can vary from + * USB VBUS or VREG 5V. If the power supply is + * USB VBUS cable disconnection cannot be + * deteted. Select power supply to VREG 5V + */ + /* TBD: comeup with a better name */ + ret = pmic_vote_3p3_pwr_sel_switch(1); + if (ret) + return ret; + } + ret = vreg_enable(vreg_3p3); + + return ret; + } else { + if (regulator_3p3_is_internal) { + ret = vreg_disable(vreg_5v); + if (ret) + return ret; + ret = pmic_vote_3p3_pwr_sel_switch(0); + if (ret) + return ret; + } + ret = vreg_disable(vreg_3p3); + + return ret; + } +} + +static void __init qsd8x50_init_usb(void) +{ + usb_mpp_init(); + + if (machine_is_qsd8x50_ffa()) + regulator_3p3_is_internal = 1; + +#ifdef CONFIG_USB_MSM_OTG_72K + platform_device_register(&msm_device_otg); +#endif + +#ifdef CONFIG_USB_FUNCTION_MSM_HSUSB + platform_device_register(&msm_device_hsusb_peripheral); +#endif + +#ifdef CONFIG_USB_MSM_72K + platform_device_register(&msm_device_gadget_peripheral); +#endif + + if (machine_is_qsd8x50_ffa()) + return; + + vreg_usb = vreg_get(NULL, "boost"); + + if (IS_ERR(vreg_usb)) { + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_usb)); + return; + } + + platform_device_register(&msm_device_hsusb_otg); + msm_add_host(0, &msm_usb_host_pdata); +#ifdef CONFIG_USB_FS_HOST + if (fsusb_gpio_init()) + return; + msm_add_host(1, &msm_usb_host2_pdata); +#endif +} static struct vreg *vreg_mmc; -static unsigned long vreg_sts; + +#if (defined(CONFIG_MMC_MSM_SDC1_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC2_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC3_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC4_SUPPORT)) + +struct sdcc_gpio { + struct msm_gpio *cfg_data; + uint32_t size; +}; + +static struct msm_gpio sdc1_cfg_data[] = { + {GPIO_CFG(51, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_3"}, + {GPIO_CFG(52, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_2"}, + {GPIO_CFG(53, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_1"}, + {GPIO_CFG(54, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_dat_0"}, + {GPIO_CFG(55, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc1_cmd"}, + {GPIO_CFG(56, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "sdc1_clk"}, +}; + +static struct msm_gpio sdc2_cfg_data[] = { + {GPIO_CFG(62, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "sdc2_clk"}, + {GPIO_CFG(63, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_cmd"}, + {GPIO_CFG(64, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_3"}, + {GPIO_CFG(65, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_2"}, + {GPIO_CFG(66, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_1"}, + {GPIO_CFG(67, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc2_dat_0"}, +}; + +static struct msm_gpio sdc3_cfg_data[] = { + {GPIO_CFG(88, 1, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "sdc3_clk"}, + {GPIO_CFG(89, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_cmd"}, + {GPIO_CFG(90, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_3"}, + {GPIO_CFG(91, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_2"}, + {GPIO_CFG(92, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_1"}, + {GPIO_CFG(93, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_0"}, + +#ifdef CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT + {GPIO_CFG(158, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_4"}, + {GPIO_CFG(159, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_5"}, + {GPIO_CFG(160, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_6"}, + {GPIO_CFG(161, 1, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc3_dat_7"}, +#endif +}; + +static struct msm_gpio sdc4_cfg_data[] = { + {GPIO_CFG(142, 3, GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, GPIO_CFG_8MA), "sdc4_clk"}, + {GPIO_CFG(143, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_cmd"}, + {GPIO_CFG(144, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_0"}, + {GPIO_CFG(145, 2, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_1"}, + {GPIO_CFG(146, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_2"}, + {GPIO_CFG(147, 3, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_UP, GPIO_CFG_8MA), "sdc4_dat_3"}, +}; + +static struct sdcc_gpio sdcc_cfg_data[] = { + { + .cfg_data = sdc1_cfg_data, + .size = ARRAY_SIZE(sdc1_cfg_data), + }, + { + .cfg_data = sdc2_cfg_data, + .size = ARRAY_SIZE(sdc2_cfg_data), + }, + { + .cfg_data = sdc3_cfg_data, + .size = ARRAY_SIZE(sdc3_cfg_data), + }, + { + .cfg_data = sdc4_cfg_data, + .size = ARRAY_SIZE(sdc4_cfg_data), + }, +}; + +static unsigned long vreg_sts, gpio_sts; + +static void msm_sdcc_setup_gpio(int dev_id, unsigned int enable) +{ + int rc = 0; + struct sdcc_gpio *curr; + + curr = &sdcc_cfg_data[dev_id - 1]; + if (!(test_bit(dev_id, &gpio_sts)^enable)) + return; + + if (enable) { + set_bit(dev_id, &gpio_sts); + rc = msm_gpios_request_enable(curr->cfg_data, curr->size); + if (rc) + printk(KERN_ERR "%s: Failed to turn on GPIOs for slot %d\n", + __func__, dev_id); + } else { + clear_bit(dev_id, &gpio_sts); + msm_gpios_disable_free(curr->cfg_data, curr->size); + } +} static uint32_t msm_sdcc_setup_power(struct device *dv, unsigned int vdd) { @@ -117,6 +2121,7 @@ static uint32_t msm_sdcc_setup_power(struct device *dv, unsigned int vdd) struct platform_device *pdev; pdev = container_of(dv, struct platform_device, dev); + msm_sdcc_setup_gpio(pdev->id, !!vdd); if (vdd == 0) { if (!vreg_sts) @@ -127,69 +2132,436 @@ static uint32_t msm_sdcc_setup_power(struct device *dv, unsigned int vdd) if (!vreg_sts) { rc = vreg_disable(vreg_mmc); if (rc) - pr_err("vreg_mmc disable failed for slot " - "%d: %d\n", pdev->id, rc); + printk(KERN_ERR "%s: return val: %d \n", + __func__, rc); } return 0; } if (!vreg_sts) { - rc = vreg_set_level(vreg_mmc, 2900); - if (rc) - pr_err("vreg_mmc set level failed for slot %d: %d\n", - pdev->id, rc); - rc = vreg_enable(vreg_mmc); + rc = vreg_set_level(vreg_mmc, PMIC_VREG_GP6_LEVEL); + if (!rc) + rc = vreg_enable(vreg_mmc); if (rc) - pr_err("vreg_mmc enable failed for slot %d: %d\n", - pdev->id, rc); + printk(KERN_ERR "%s: return val: %d \n", + __func__, rc); } set_bit(pdev->id, &vreg_sts); return 0; } -static struct msm_mmc_gpio_data sdc1_gpio = { - .gpio = sdc1_gpio_cfg, - .size = ARRAY_SIZE(sdc1_gpio_cfg), -}; +#endif +#if (defined(CONFIG_MMC_MSM_SDC1_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC2_SUPPORT)\ + || defined(CONFIG_MMC_MSM_SDC4_SUPPORT)) + +static int msm_sdcc_get_wpswitch(struct device *dv) +{ + void __iomem *wp_addr = 0; + uint32_t ret = 0; + struct platform_device *pdev; -static struct msm_mmc_platform_data qsd8x50_sdc1_data = { + if (!machine_is_qsd8x50_surf()) + return -1; + + pdev = container_of(dv, struct platform_device, dev); + + wp_addr = ioremap(FPGA_SDCC_STATUS, 4); + if (!wp_addr) { + pr_err("%s: Could not remap %x\n", __func__, FPGA_SDCC_STATUS); + return -ENOMEM; + } + + ret = (readl(wp_addr) >> ((pdev->id - 1) << 1)) & (0x03); + pr_info("%s: WP/CD Status for Slot %d = 0x%x \n", __func__, + pdev->id, ret); + iounmap(wp_addr); + return ((ret == 0x02) ? 1 : 0); + +} +#endif + +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT +static struct mmc_platform_data qsd8x50_sdc1_data = { .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, .translate_vdd = msm_sdcc_setup_power, - .gpio_data = &sdc1_gpio, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .wpswitch = msm_sdcc_get_wpswitch, +#ifdef CONFIG_MMC_MSM_SDC1_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 25000000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, }; +#endif + +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT +static struct mmc_platform_data qsd8x50_sdc2_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .wpswitch = msm_sdcc_get_wpswitch, +#ifdef CONFIG_MMC_MSM_SDC2_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 25000000, + .msmsdcc_fmax = 49152000, + .nonremovable = 1, +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT +static struct mmc_platform_data qsd8x50_sdc3_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, +#ifdef CONFIG_MMC_MSM_SDC3_8_BIT_SUPPORT + .mmc_bus_width = MMC_CAP_8_BIT_DATA, +#else + .mmc_bus_width = MMC_CAP_4_BIT_DATA, +#endif +#ifdef CONFIG_MMC_MSM_SDC3_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 25000000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +}; +#endif + +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT +static struct mmc_platform_data qsd8x50_sdc4_data = { + .ocr_mask = MMC_VDD_27_28 | MMC_VDD_28_29, + .translate_vdd = msm_sdcc_setup_power, + .mmc_bus_width = MMC_CAP_4_BIT_DATA, + .wpswitch = msm_sdcc_get_wpswitch, +#ifdef CONFIG_MMC_MSM_SDC4_DUMMY52_REQUIRED + .dummy52_required = 1, +#endif + .msmsdcc_fmin = 144000, + .msmsdcc_fmid = 25000000, + .msmsdcc_fmax = 49152000, + .nonremovable = 0, +}; +#endif static void __init qsd8x50_init_mmc(void) { - vreg_mmc = vreg_get(NULL, "gp5"); + if (machine_is_qsd8x50_ffa()) + vreg_mmc = vreg_get(NULL, "gp6"); + else + vreg_mmc = vreg_get(NULL, "gp5"); if (IS_ERR(vreg_mmc)) { - pr_err("vreg get for vreg_mmc failed (%ld)\n", - PTR_ERR(vreg_mmc)); + printk(KERN_ERR "%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_mmc)); return; } - msm_add_sdcc(1, &qsd8x50_sdc1_data, 0, 0); +#ifdef CONFIG_MMC_MSM_SDC1_SUPPORT + msm_add_sdcc(1, &qsd8x50_sdc1_data); +#endif + + if (machine_is_qsd8x50_surf()) { +#ifdef CONFIG_MMC_MSM_SDC2_SUPPORT + msm_add_sdcc(2, &qsd8x50_sdc2_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC3_SUPPORT + msm_add_sdcc(3, &qsd8x50_sdc3_data); +#endif +#ifdef CONFIG_MMC_MSM_SDC4_SUPPORT + msm_add_sdcc(4, &qsd8x50_sdc4_data); +#endif + } + } -static void __init qsd8x50_map_io(void) +static void __init qsd8x50_cfg_smc91x(void) { - msm_map_qsd8x50_io(); - msm_clock_init(msm_clocks_8x50, msm_num_clocks_8x50); + int rc = 0; + + if (machine_is_qsd8x50_surf()) { + smc91x_resources[0].start = 0x70000300; + smc91x_resources[0].end = 0x700003ff; + smc91x_resources[1].start = MSM_GPIO_TO_INT(156); + smc91x_resources[1].end = MSM_GPIO_TO_INT(156); + } else if (machine_is_qsd8x50_ffa()) { + smc91x_resources[0].start = 0x84000300; + smc91x_resources[0].end = 0x840003ff; + smc91x_resources[1].start = MSM_GPIO_TO_INT(87); + smc91x_resources[1].end = MSM_GPIO_TO_INT(87); + + rc = gpio_tlmm_config(GPIO_CFG(87, 0, GPIO_CFG_INPUT, + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + if (rc) { + printk(KERN_ERR "%s: gpio_tlmm_config=%d\n", + __func__, rc); + } + } else + printk(KERN_ERR "%s: invalid machine type\n", __func__); } -static void __init qsd8x50_init_irq(void) +static struct msm_pm_platform_data msm_pm_data[MSM_PM_SLEEP_MODE_NR] = { + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 8594, + .residency = 23740, + }, + + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 4594, + .residency = 23740, + }, + + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 0, + .suspend_enabled = 1, + .latency = 443, + .residency = 1098, + }, + + [MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT] = { + .idle_supported = 1, + .suspend_supported = 1, + .idle_enabled = 1, + .suspend_enabled = 1, + .latency = 2, + .residency = 0, + }, +}; + +static void +msm_i2c_gpio_config(int iface, int config_type) { - msm_init_irq(); - msm_init_sirc(); + int gpio_scl; + int gpio_sda; + if (iface) { + gpio_scl = 60; + gpio_sda = 61; + } else { + gpio_scl = 95; + gpio_sda = 96; + } + if (config_type) { + gpio_tlmm_config(GPIO_CFG(gpio_scl, 1, GPIO_CFG_INPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_16MA), GPIO_CFG_ENABLE); + gpio_tlmm_config(GPIO_CFG(gpio_sda, 1, GPIO_CFG_INPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_16MA), GPIO_CFG_ENABLE); + } else { + gpio_tlmm_config(GPIO_CFG(gpio_scl, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_16MA), GPIO_CFG_ENABLE); + gpio_tlmm_config(GPIO_CFG(gpio_sda, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_16MA), GPIO_CFG_ENABLE); + } +} + +static struct msm_i2c_platform_data msm_i2c_pdata = { + .clk_freq = 100000, + .rsl_id = SMEM_SPINLOCK_I2C, + .pri_clk = 95, + .pri_dat = 96, + .aux_clk = 60, + .aux_dat = 61, + .msm_i2c_config_gpio = msm_i2c_gpio_config, +}; + +static void __init msm_device_i2c_init(void) +{ + if (gpio_request(95, "i2c_pri_clk")) + pr_err("failed to request gpio i2c_pri_clk\n"); + if (gpio_request(96, "i2c_pri_dat")) + pr_err("failed to request gpio i2c_pri_dat\n"); + if (gpio_request(60, "i2c_sec_clk")) + pr_err("failed to request gpio i2c_sec_clk\n"); + if (gpio_request(61, "i2c_sec_dat")) + pr_err("failed to request gpio i2c_sec_dat\n"); + + msm_i2c_pdata.rmutex = 1; + msm_i2c_pdata.pm_lat = + msm_pm_data[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] + .latency; + msm_device_i2c.dev.platform_data = &msm_i2c_pdata; +} + +static unsigned pmem_kernel_ebi1_size = PMEM_KERNEL_EBI1_SIZE; +static int __init pmem_kernel_ebi1_size_setup(char *p) +{ + pmem_kernel_ebi1_size = memparse(p, NULL); + return 0; +} +early_param("pmem_kernel_ebi1_size", pmem_kernel_ebi1_size_setup); + +#ifdef CONFIG_KERNEL_PMEM_SMI_REGION +static unsigned pmem_kernel_smi_size = MSM_PMEM_SMIPOOL_SIZE; +static int __init pmem_kernel_smi_size_setup(char *p) +{ + pmem_kernel_smi_size = memparse(p, NULL); + + /* Make sure that we don't allow more SMI memory then is + available - the kernel mapping code has no way of knowing + if it has gone over the edge */ + + if (pmem_kernel_smi_size > MSM_PMEM_SMIPOOL_SIZE) + pmem_kernel_smi_size = MSM_PMEM_SMIPOOL_SIZE; + return 0; +} +early_param("pmem_kernel_smi_size", pmem_kernel_smi_size_setup); +#endif + +static unsigned pmem_sf_size = MSM_PMEM_SF_SIZE; +static int __init pmem_sf_size_setup(char *p) +{ + pmem_sf_size = memparse(p, NULL); + return 0; +} +early_param("pmem_sf_size", pmem_sf_size_setup); + +static unsigned pmem_adsp_size = MSM_PMEM_ADSP_SIZE; +static int __init pmem_adsp_size_setup(char *p) +{ + pmem_adsp_size = memparse(p, NULL); + return 0; } +early_param("pmem_adsp_size", pmem_adsp_size_setup); + + +static unsigned audio_size = MSM_AUDIO_SIZE; +static int __init audio_size_setup(char *p) +{ + audio_size = memparse(p, NULL); + return 0; +} +early_param("audio_size", audio_size_setup); static void __init qsd8x50_init(void) { + if (socinfo_init() < 0) + printk(KERN_ERR "%s: socinfo_init() failed!\n", + __func__); + msm_clock_init(msm_clocks_8x50, msm_num_clocks_8x50); + qsd8x50_cfg_smc91x(); + msm_acpu_clock_init(&qsd8x50_clock_data); + + msm_hsusb_pdata.swfi_latency = + msm_pm_data + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT].latency; + msm_device_hsusb_peripheral.dev.platform_data = &msm_hsusb_pdata; + + msm_otg_pdata.swfi_latency = + msm_pm_data + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT].latency; msm_device_otg.dev.platform_data = &msm_otg_pdata; - msm_device_hsusb.dev.parent = &msm_device_otg.dev; - msm_device_hsusb_host.dev.parent = &msm_device_otg.dev; + msm_device_gadget_peripheral.dev.platform_data = &msm_gadget_pdata; + msm_gadget_pdata.is_phy_status_timer_on = 1; + +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + msm_device_tsif.dev.platform_data = &tsif_platform_data; +#endif platform_add_devices(devices, ARRAY_SIZE(devices)); + msm_fb_add_devices(); +#ifdef CONFIG_MSM_CAMERA + config_camera_off_gpios(); /* might not be necessary */ +#endif + qsd8x50_init_usb(); qsd8x50_init_mmc(); + bt_power_init(); + audio_gpio_init(); + msm_device_i2c_init(); + msm_qsd_spi_init(); + i2c_register_board_info(0, msm_i2c_board_info, + ARRAY_SIZE(msm_i2c_board_info)); + spi_register_board_info(msm_spi_board_info, + ARRAY_SIZE(msm_spi_board_info)); + msm_pm_set_platform_data(msm_pm_data, ARRAY_SIZE(msm_pm_data)); + +#ifdef CONFIG_SURF_FFA_GPIO_KEYPAD + if (machine_is_qsd8x50_ffa()) + platform_device_register(&keypad_device_8k_ffa); + else + platform_device_register(&keypad_device_surf); +#endif +} + +static void __init qsd8x50_allocate_memory_regions(void) +{ + void *addr; + unsigned long size; + + size = pmem_kernel_ebi1_size; + if (size) { + addr = alloc_bootmem_align(size, 0x100000); + android_pmem_kernel_ebi1_pdata.size = size; + pr_info("allocating %lu bytes at %p (%lx physical) for kernel" + " ebi1 pmem arena\n", size, addr, __pa(addr)); + } + +#ifdef CONFIG_KERNEL_PMEM_SMI_REGION + size = pmem_kernel_smi_size; + if (size > MSM_PMEM_SMIPOOL_SIZE) { + printk(KERN_ERR "pmem kernel smi arena size %lu is too big\n", + size); + + size = MSM_PMEM_SMIPOOL_SIZE; + } + + android_pmem_kernel_smi_pdata.size = size; + + pr_info("allocating %lu bytes at %lx (%lx physical)" + "for pmem kernel smi arena\n", size, + (long unsigned int) MSM_PMEM_SMIPOOL_BASE, + __pa(MSM_PMEM_SMIPOOL_BASE)); +#endif + + size = pmem_sf_size; + if (size) { + addr = alloc_bootmem(size); + android_pmem_pdata.size = size; + pr_info("allocating %lu bytes at %p (%lx physical) for sf " + "pmem arena\n", size, addr, __pa(addr)); + } + + size = pmem_adsp_size; + if (size) { + addr = alloc_bootmem(size); + android_pmem_adsp_pdata.size = size; + pr_info("allocating %lu bytes at %p (%lx physical) for adsp " + "pmem arena\n", size, addr, __pa(addr)); + } + + + size = MSM_FB_SIZE; + addr = (void *)MSM_FB_BASE; + msm_fb_resources[0].start = (unsigned long)addr; + msm_fb_resources[0].end = msm_fb_resources[0].start + size - 1; + pr_info("using %lu bytes of SMI at %lx physical for fb\n", + size, (unsigned long)addr); + + size = audio_size ? : MSM_AUDIO_SIZE; + addr = alloc_bootmem(size); + msm_audio_resources[0].start = __pa(addr); + msm_audio_resources[0].end = msm_audio_resources[0].start + size - 1; + pr_info("allocating %lu bytes at %p (%lx physical) for audio\n", + size, addr, __pa(addr)); +} + +static void __init qsd8x50_map_io(void) +{ + msm_shared_ram_phys = MSM_SHARED_RAM_PHYS; + msm_map_qsd8x50_io(); + qsd8x50_allocate_memory_regions(); } MACHINE_START(QSD8X50_SURF, "QCT QSD8X50 SURF") @@ -200,7 +2572,7 @@ MACHINE_START(QSD8X50_SURF, "QCT QSD8X50 SURF") .timer = &msm_timer, MACHINE_END -MACHINE_START(QSD8X50A_ST1_5, "QCT QSD8X50A ST1.5") +MACHINE_START(QSD8X50_FFA, "QCT QSD8X50 FFA") .boot_params = PLAT_PHYS_OFFSET + 0x100, .map_io = qsd8x50_map_io, .init_irq = qsd8x50_init_irq, diff --git a/arch/arm/mach-msm/board-sapphire-gpio.c b/arch/arm/mach-msm/board-sapphire-gpio.c new file mode 100644 index 00000000000..375440c5afb --- /dev/null +++ b/arch/arm/mach-msm/board-sapphire-gpio.c @@ -0,0 +1,326 @@ +/* arch/arm/mach-msm/board-sapphire-gpio.c + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "gpio_chip.h" +#include "board-sapphire.h" + +#ifdef DEBUG_SAPPHIRE_GPIO +#define DBG(fmt, arg...) printk(KERN_INFO "%s: " fmt "\n", __func__, ## arg) +#else +#define DBG(fmt, arg...) do {} while (0) +#endif + +#define SAPPHIRE_CPLD_INT_STATUS (SAPPHIRE_CPLD_BASE + 0x0E) +#define SAPPHIRE_CPLD_INT_LEVEL (SAPPHIRE_CPLD_BASE + 0x08) +#define SAPPHIRE_CPLD_INT_MASK (SAPPHIRE_CPLD_BASE + 0x0C) + +/*CPLD misc reg offset*/ +static const int _g_CPLD_MISCn_Offset[] = { 0x0A, /*misc1 reg*/ + 0x00, /*misc2 reg*/ + 0x02, /*misc3 reg*/ + 0x04, /*misc4 reg*/ + 0x06}; /*misc5 reg*/ +/*CPLD INT Bank*/ +/*BANK0: int1 status, int2 level, int3 mask*/ +static const int _g_INT_BANK_Offset[][3] = {{0x0E, 0x08, 0x0C} }; + +static uint8_t sapphire_cpld_initdata[4] = { + [0] = 0x80, /* for serial debug UART3, low current misc2*/ + [1] = 0x34, /* jog & tp enable, I2C pull misc3*/ + [3] = 0x04, /* mmdi 32k en misc5*/ +}; + +/*save current working int mask, so the value can be restored after resume. +Sapphire has only bank0.*/ +static uint8_t sapphire_int_mask[] = { + [0] = 0xfb, /* enable all interrupts, bit 2 is not used */ +}; + +/*Sleep have to prepare the wake up source in advance. +default to disable all wakeup sources when suspend.*/ +static uint8_t sapphire_sleep_int_mask[] = { + [0] = 0x00, /* bit2 is not used */ +}; + +static int sapphire_suspended; + +static int sapphire_gpio_read(struct gpio_chip *chip, unsigned n) +{ + if (n < SAPPHIRE_GPIO_INT_B0_BASE) /*MISCn*/ + return !!(readb(CPLD_GPIO_REG(n)) & CPLD_GPIO_BIT_POS_MASK(n)); + else if (n <= SAPPHIRE_GPIO_END) /*gpio n is INT pin*/ + return !!(readb(CPLD_INT_LEVEL_REG_G(n)) & + CPLD_GPIO_BIT_POS_MASK(n)); + return 0; +} + +/*CPLD Write only register :MISC2, MISC3, MISC4, MISC5 => reg=0,2,4,6 +Reading from write-only registers is undefined, so the writing value +should be kept in shadow for later usage.*/ +int sapphire_gpio_write(struct gpio_chip *chip, unsigned n, unsigned on) +{ + unsigned long flags; + uint8_t reg_val; + if (n > SAPPHIRE_GPIO_END) + return -1; + + local_irq_save(flags); + reg_val = readb(CPLD_GPIO_REG(n)); + if (on) + reg_val |= CPLD_GPIO_BIT_POS_MASK(n); + else + reg_val &= ~CPLD_GPIO_BIT_POS_MASK(n); + writeb(reg_val, CPLD_GPIO_REG(n)); + + DBG("gpio=%d, l=0x%x\r\n", n, readb(SAPPHIRE_CPLD_INT_LEVEL)); + + local_irq_restore(flags); + + return 0; +} + +static int sapphire_gpio_configure(struct gpio_chip *chip, unsigned int gpio, + unsigned long flags) +{ + if (flags & (GPIOF_OUTPUT_LOW | GPIOF_OUTPUT_HIGH)) + sapphire_gpio_write(chip, gpio, flags & GPIOF_OUTPUT_HIGH); + + DBG("gpio=%d, l=0x%x\r\n", gpio, readb(SAPPHIRE_CPLD_INT_LEVEL)); + + return 0; +} + +static int sapphire_gpio_get_irq_num(struct gpio_chip *chip, unsigned int gpio, + unsigned int *irqp, unsigned long *irqnumflagsp) +{ + DBG("gpio=%d, l=0x%x\r\n", gpio, readb(SAPPHIRE_CPLD_INT_LEVEL)); + DBG("SAPPHIRE_GPIO_INT_B0_BASE=%d, SAPPHIRE_GPIO_LAST_INT=%d\r\n", + SAPPHIRE_GPIO_INT_B0_BASE, SAPPHIRE_GPIO_LAST_INT); + if ((gpio < SAPPHIRE_GPIO_INT_B0_BASE) || + (gpio > SAPPHIRE_GPIO_LAST_INT)) + return -ENOENT; + *irqp = SAPPHIRE_GPIO_TO_INT(gpio); + DBG("*irqp=%d\r\n", *irqp); + if (irqnumflagsp) + *irqnumflagsp = 0; + return 0; +} + +/*write 1 to clear INT status bit.*/ +static void sapphire_gpio_irq_ack(unsigned int irq) +{ + /*write 1 to clear*/ + writeb(SAPPHIRE_INT_BIT_MASK(irq), CPLD_INT_STATUS_REG(irq)); +} + +/*unmask/enable the INT +static void sapphire_gpio_irq_unmask(unsigned int irq)*/ +static void sapphire_gpio_irq_enable(unsigned int irq) +{ + unsigned long flags; + uint8_t reg_val; + + local_irq_save(flags); /*disabling all interrupts*/ + + reg_val = readb(CPLD_INT_MASK_REG(irq)) | SAPPHIRE_INT_BIT_MASK(irq); + DBG("(irq=%d,0x%x, 0x%x)\r\n", irq, CPLD_INT_MASK_REG(irq), + SAPPHIRE_INT_BIT_MASK(irq)); + DBG("sapphire_suspended=%d\r\n", sapphire_suspended); + /*printk(KERN_INFO "sapphire_gpio_irq_mask irq %d => %d:%02x\n", + irq, bank, reg_val);*/ + if (!sapphire_suspended) + writeb(reg_val, CPLD_INT_MASK_REG(irq)); + + reg_val = readb(CPLD_INT_MASK_REG(irq)); + DBG("reg_val= 0x%x\r\n", reg_val); + DBG("l=0x%x\r\n", readb(SAPPHIRE_CPLD_INT_LEVEL)); + + local_irq_restore(flags); /*restore the interrupts*/ +} + +/*mask/disable INT +static void sapphire_gpio_irq_mask(unsigned int irq)*/ +static void sapphire_gpio_irq_disable(unsigned int irq) +{ + unsigned long flags; + uint8_t reg_val; + + local_irq_save(flags); + reg_val = readb(CPLD_INT_MASK_REG(irq)) & ~SAPPHIRE_INT_BIT_MASK(irq); + /*CPLD INT MASK is r/w now.*/ + + /*printk(KERN_INFO "sapphire_gpio_irq_unmask irq %d => %d:%02x\n", + irq, bank, reg_val);*/ + DBG("(%d,0x%x, 0x%x, 0x%x)\r\n", irq, reg_val, CPLD_INT_MASK_REG(irq), + SAPPHIRE_INT_BIT_MASK(irq)); + DBG("sapphire_suspended=%d\r\n", sapphire_suspended); + if (!sapphire_suspended) + writeb(reg_val, CPLD_INT_MASK_REG(irq)); + + reg_val = readb(CPLD_INT_MASK_REG(irq)); + DBG("reg_val= 0x%x\r\n", reg_val); + DBG("l=0x%x\r\n", readb(SAPPHIRE_CPLD_INT_LEVEL)); + + local_irq_restore(flags); +} + +/*preparing enable/disable wake source before sleep*/ +int sapphire_gpio_irq_set_wake(unsigned int irq, unsigned int on) +{ + unsigned long flags; + uint8_t mask = SAPPHIRE_INT_BIT_MASK(irq); + + local_irq_save(flags); + + if (on) /*wake on -> mask the bit*/ + sapphire_sleep_int_mask[CPLD_INT_TO_BANK(irq)] |= mask; + else /*no wake -> unmask the bit*/ + sapphire_sleep_int_mask[CPLD_INT_TO_BANK(irq)] &= ~mask; + local_irq_restore(flags); + return 0; +} + +/*Sapphire has only one INT Bank.*/ +static void sapphire_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + int j; + unsigned v; + int int_base = SAPPHIRE_INT_START; + + v = readb(SAPPHIRE_CPLD_INT_STATUS); /*INT1 status reg, BANK0*/ + + for (j = 0; j < 8 ; j++) { /*8 bit per bank*/ + if (v & (1U << j)) { /*got the INT Bit*/ + DBG("generic_handle_irq j=0x%x\r\n", j); + generic_handle_irq(int_base + j); + } + } + + desc->chip->ack(irq); /*clear CPLD INT in SOC side.*/ + DBG("irq=%d, l=0x%x\r\n", irq, readb(SAPPHIRE_CPLD_INT_LEVEL)); +} + +/*Save current working sources before sleep, so we can restore it after + * resume.*/ +static int sapphire_sysdev_suspend(struct sys_device *dev, pm_message_t state) +{ + sapphire_suspended = 1; + /*save current masking*/ + sapphire_int_mask[0] = readb(SAPPHIRE_CPLD_BASE + + SAPPHIRE_GPIO_INT_B0_MASK_REG); + + /*set waking source before sleep.*/ + writeb(sapphire_sleep_int_mask[0], + SAPPHIRE_CPLD_BASE + SAPPHIRE_GPIO_INT_B0_MASK_REG); + + return 0; +} + +/*All the registers will be kept till a power loss...*/ +int sapphire_sysdev_resume(struct sys_device *dev) +{ + /*restore the working mask saved before sleep*/ + writeb(sapphire_int_mask[0], SAPPHIRE_CPLD_BASE + + SAPPHIRE_GPIO_INT_B0_MASK_REG); + sapphire_suspended = 0; + return 0; +} + +/** + * linux/irq.h :: struct irq_chip + * @enable: enable the interrupt (defaults to chip->unmask if NULL) + * @disable: disable the interrupt (defaults to chip->mask if NULL) + * @ack: start of a new interrupt + * @mask: mask an interrupt source + * @mask_ack: ack and mask an interrupt source + * @unmask: unmask an interrupt source + */ +static struct irq_chip sapphire_gpio_irq_chip = { + .name = "sapphiregpio", + .ack = sapphire_gpio_irq_ack, + .mask = sapphire_gpio_irq_disable, /*sapphire_gpio_irq_mask,*/ + .unmask = sapphire_gpio_irq_enable, /*sapphire_gpio_irq_unmask,*/ + .set_wake = sapphire_gpio_irq_set_wake, + /*.set_type = sapphire_gpio_irq_set_type,*/ +}; + +/*Thomas:For CPLD*/ +static struct gpio_chip sapphire_gpio_chip = { + .start = SAPPHIRE_GPIO_START, + .end = SAPPHIRE_GPIO_END, + .configure = sapphire_gpio_configure, + .get_irq_num = sapphire_gpio_get_irq_num, + .read = sapphire_gpio_read, + .write = sapphire_gpio_write, +/* .read_detect_status = sapphire_gpio_read_detect_status, + .clear_detect_status = sapphire_gpio_clear_detect_status */ +}; + +struct sysdev_class sapphire_sysdev_class = { + .name = "sapphiregpio_irq", + .suspend = sapphire_sysdev_suspend, + .resume = sapphire_sysdev_resume, +}; + +static struct sys_device sapphire_irq_device = { + .cls = &sapphire_sysdev_class, +}; + +int sapphire_init_gpio(void) +{ + int i; + if (!machine_is_sapphire()) + return 0; + + DBG("%d,%d\r\n", SAPPHIRE_INT_START, SAPPHIRE_INT_END); + DBG("NR_MSM_IRQS=%d, NR_GPIO_IRQS=%d\r\n", NR_MSM_IRQS, NR_GPIO_IRQS); + for (i = SAPPHIRE_INT_START; i <= SAPPHIRE_INT_END; i++) { + set_irq_chip(i, &sapphire_gpio_irq_chip); + set_irq_handler(i, handle_edge_irq); + set_irq_flags(i, IRQF_VALID); + } + + register_gpio_chip(&sapphire_gpio_chip); + + /*setup CPLD INT connecting to SOC's gpio 17 */ + set_irq_type(MSM_GPIO_TO_INT(17), IRQF_TRIGGER_HIGH); + set_irq_chained_handler(MSM_GPIO_TO_INT(17), sapphire_gpio_irq_handler); + set_irq_wake(MSM_GPIO_TO_INT(17), 1); + + if (sysdev_class_register(&sapphire_sysdev_class) == 0) + sysdev_register(&sapphire_irq_device); + + return 0; +} + +int sapphire_init_cpld(unsigned int sys_rev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sapphire_cpld_initdata); i++) + writeb(sapphire_cpld_initdata[i], SAPPHIRE_CPLD_BASE + i * 2); + return 0; +} + +postcore_initcall(sapphire_init_gpio); diff --git a/arch/arm/mach-msm/board-sapphire-h2w.c b/arch/arm/mach-msm/board-sapphire-h2w.c new file mode 100644 index 00000000000..aa83e216974 --- /dev/null +++ b/arch/arm/mach-msm/board-sapphire-h2w.c @@ -0,0 +1,545 @@ +/* + * H2W device detection driver. + * + * Copyright (C) 2008 HTC Corporation. + * Copyright (C) 2008 Google, Inc. + * + * Authors: + * Laurence Chen + * Nick Pelly + * + * 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. + * + */ + +/* For detecting HTC 2 Wire devices, such as wired headset. + + Logically, the H2W driver is always present, and H2W state (hi->state) + indicates what is currently plugged into the H2W interface. + + When the headset is plugged in, CABLE_IN1 is pulled low. When the headset + button is pressed, CABLE_IN2 is pulled low. These two lines are shared with + the TX and RX (respectively) of UART3 - used for serial debugging. + + This headset driver keeps the CPLD configured as UART3 for as long as + possible, so that we can do serial FIQ debugging even when the kernel is + locked and this driver no longer runs. So it only configures the CPLD to + GPIO while the headset is plugged in, and for 10ms during detection work. + + Unfortunately we can't leave the CPLD as UART3 while a headset is plugged + in, UART3 is pullup on TX but the headset is pull-down, causing a 55 mA + drain on sapphire. + + The headset detection work involves setting CPLD to GPIO, and then pulling + CABLE_IN1 high with a stronger pullup than usual. A H2W headset will still + pull this line low, whereas other attachments such as a serial console + would get pulled up by this stronger pullup. + + Headset insertion/removal causes UEvent's to be sent, and + /sys/class/switch/h2w/state to be updated. + + Button presses are interpreted as input event (KEY_MEDIA). Button presses + are ignored if the headset is plugged in, so the buttons on 11 pin -> 3.5mm + jack adapters do not work until a headset is plugged into the adapter. This + is to avoid serial RX traffic causing spurious button press events. + + We tend to check the status of CABLE_IN1 a few more times than strictly + necessary during headset detection, to avoid spurious headset insertion + events caused by serial debugger TX traffic. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "board-sapphire.h" + +#ifdef CONFIG_DEBUG_SAPPHIRE_H2W +#define H2W_DBG(fmt, arg...) printk(KERN_INFO "[H2W] %s " fmt "\n", __FUNCTION__, ## arg) +#else +#define H2W_DBG(fmt, arg...) do {} while (0) +#endif + +static struct workqueue_struct *g_detection_work_queue; +static void detection_work(struct work_struct *work); +static DECLARE_WORK(g_detection_work, detection_work); +enum { + NO_DEVICE = 0, + HTC_HEADSET = 1, +}; + +enum { + UART3 = 0, + GPIO = 1, +}; + +struct h2w_info { + struct switch_dev sdev; + struct input_dev *input; + + atomic_t btn_state; + int ignore_btn; + + unsigned int irq; + unsigned int irq_btn; + + struct hrtimer timer; + ktime_t debounce_time; + + struct hrtimer btn_timer; + ktime_t btn_debounce_time; +}; +static struct h2w_info *hi; + +static ssize_t sapphire_h2w_print_name(struct switch_dev *sdev, char *buf) +{ + switch (switch_get_state(&hi->sdev)) { + case NO_DEVICE: + return sprintf(buf, "No Device\n"); + case HTC_HEADSET: + return sprintf(buf, "Headset\n"); + } + return -EINVAL; +} + +static void configure_cpld(int route) +{ + H2W_DBG(" route = %s", route == UART3 ? "UART3" : "GPIO"); + switch (route) { + case UART3: + gpio_set_value(SAPPHIRE_GPIO_H2W_SEL0, 0); + gpio_set_value(SAPPHIRE_GPIO_H2W_SEL1, 1); + break; + case GPIO: + gpio_set_value(SAPPHIRE_GPIO_H2W_SEL0, 0); + gpio_set_value(SAPPHIRE_GPIO_H2W_SEL1, 0); + break; + } +} + +static void button_pressed(void) +{ + H2W_DBG(""); + atomic_set(&hi->btn_state, 1); + input_report_key(hi->input, KEY_MEDIA, 1); + input_sync(hi->input); +} + +static void button_released(void) +{ + H2W_DBG(""); + atomic_set(&hi->btn_state, 0); + input_report_key(hi->input, KEY_MEDIA, 0); + input_sync(hi->input); +} + +#ifdef CONFIG_MSM_SERIAL_DEBUGGER +extern void msm_serial_debug_enable(int); +#endif + +static void insert_headset(void) +{ + unsigned long irq_flags; + + H2W_DBG(""); + + switch_set_state(&hi->sdev, HTC_HEADSET); + configure_cpld(GPIO); + +#ifdef CONFIG_MSM_SERIAL_DEBUGGER + msm_serial_debug_enable(false); +#endif + + + /* On some non-standard headset adapters (usually those without a + * button) the btn line is pulled down at the same time as the detect + * line. We can check here by sampling the button line, if it is + * low then it is probably a bad adapter so ignore the button. + * If the button is released then we stop ignoring the button, so that + * the user can recover from the situation where a headset is plugged + * in with button held down. + */ + hi->ignore_btn = !gpio_get_value(SAPPHIRE_GPIO_CABLE_IN2); + + /* Enable button irq */ + local_irq_save(irq_flags); + enable_irq(hi->irq_btn); + local_irq_restore(irq_flags); + + hi->debounce_time = ktime_set(0, 20000000); /* 20 ms */ +} + +static void remove_headset(void) +{ + unsigned long irq_flags; + + H2W_DBG(""); + + switch_set_state(&hi->sdev, NO_DEVICE); + configure_cpld(UART3); + + /* Disable button */ + local_irq_save(irq_flags); + disable_irq(hi->irq_btn); + local_irq_restore(irq_flags); + + if (atomic_read(&hi->btn_state)) + button_released(); + + hi->debounce_time = ktime_set(0, 100000000); /* 100 ms */ +} + +static void detection_work(struct work_struct *work) +{ + unsigned long irq_flags; + int clk, cable_in1; + + H2W_DBG(""); + + if (gpio_get_value(SAPPHIRE_GPIO_CABLE_IN1) != 0) { + /* Headset not plugged in */ + if (switch_get_state(&hi->sdev) == HTC_HEADSET) + remove_headset(); + return; + } + + /* Something plugged in, lets make sure its a headset */ + + /* Switch CPLD to GPIO to do detection */ + configure_cpld(GPIO); + /* Disable headset interrupt while detecting.*/ + local_irq_save(irq_flags); + disable_irq(hi->irq); + local_irq_restore(irq_flags); + + /* Set GPIO_CABLE_IN1 as output high */ + gpio_direction_output(SAPPHIRE_GPIO_CABLE_IN1, 1); + /* Delay 10ms for pin stable. */ + msleep(10); + /* Save H2W_CLK */ + clk = gpio_get_value(SAPPHIRE_GPIO_H2W_CLK_GPI); + /* Set GPIO_CABLE_IN1 as input */ + gpio_direction_input(SAPPHIRE_GPIO_CABLE_IN1); + + /* Restore IRQs */ + local_irq_save(irq_flags); + enable_irq(hi->irq); + local_irq_restore(irq_flags); + + cable_in1 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN1); + + if (cable_in1 == 0 && clk == 0) { + if (switch_get_state(&hi->sdev) == NO_DEVICE) + insert_headset(); + } else { + configure_cpld(UART3); + H2W_DBG("CABLE_IN1 was low, but not a headset " + "(recent cable_in1 = %d, clk = %d)", cable_in1, clk); + } +} + +static enum hrtimer_restart button_event_timer_func(struct hrtimer *data) +{ + H2W_DBG(""); + + if (switch_get_state(&hi->sdev) == HTC_HEADSET) { + if (gpio_get_value(SAPPHIRE_GPIO_CABLE_IN2)) { + if (hi->ignore_btn) + hi->ignore_btn = 0; + else if (atomic_read(&hi->btn_state)) + button_released(); + } else { + if (!hi->ignore_btn && !atomic_read(&hi->btn_state)) + button_pressed(); + } + } + + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart detect_event_timer_func(struct hrtimer *data) +{ + H2W_DBG(""); + + queue_work(g_detection_work_queue, &g_detection_work); + return HRTIMER_NORESTART; +} + +static irqreturn_t detect_irq_handler(int irq, void *dev_id) +{ + int value1, value2; + int retry_limit = 10; + + H2W_DBG(""); + do { + value1 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN1); + set_irq_type(hi->irq, value1 ? + IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); + value2 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN1); + } while (value1 != value2 && retry_limit-- > 0); + + H2W_DBG("value2 = %d (%d retries)", value2, (10-retry_limit)); + + if ((switch_get_state(&hi->sdev) == NO_DEVICE) ^ value2) { + if (switch_get_state(&hi->sdev) == HTC_HEADSET) + hi->ignore_btn = 1; + /* Do the rest of the work in timer context */ + hrtimer_start(&hi->timer, hi->debounce_time, HRTIMER_MODE_REL); + } + + return IRQ_HANDLED; +} + +static irqreturn_t button_irq_handler(int irq, void *dev_id) +{ + int value1, value2; + int retry_limit = 10; + + H2W_DBG(""); + do { + value1 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN2); + set_irq_type(hi->irq_btn, value1 ? + IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); + value2 = gpio_get_value(SAPPHIRE_GPIO_CABLE_IN2); + } while (value1 != value2 && retry_limit-- > 0); + + H2W_DBG("value2 = %d (%d retries)", value2, (10-retry_limit)); + + hrtimer_start(&hi->btn_timer, hi->btn_debounce_time, HRTIMER_MODE_REL); + + return IRQ_HANDLED; +} + +#if defined(CONFIG_DEBUG_FS) +static void h2w_debug_set(void *data, u64 val) +{ + switch_set_state(&hi->sdev, (int)val); +} + +static u64 h2w_debug_get(void *data) +{ + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(h2w_debug_fops, h2w_debug_get, h2w_debug_set, "%llu\n"); +static int __init h2w_debug_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("h2w", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debugfs_create_file("state", 0644, dent, NULL, &h2w_debug_fops); + + return 0; +} + +device_initcall(h2w_debug_init); +#endif + +static int sapphire_h2w_probe(struct platform_device *pdev) +{ + int ret; + unsigned long irq_flags; + + printk(KERN_INFO "H2W: Registering H2W (headset) driver\n"); + hi = kzalloc(sizeof(struct h2w_info), GFP_KERNEL); + if (!hi) + return -ENOMEM; + + atomic_set(&hi->btn_state, 0); + hi->ignore_btn = 0; + + hi->debounce_time = ktime_set(0, 100000000); /* 100 ms */ + hi->btn_debounce_time = ktime_set(0, 10000000); /* 10 ms */ + hi->sdev.name = "h2w"; + hi->sdev.print_name = sapphire_h2w_print_name; + + ret = switch_dev_register(&hi->sdev); + if (ret < 0) + goto err_switch_dev_register; + + g_detection_work_queue = create_workqueue("detection"); + if (g_detection_work_queue == NULL) { + ret = -ENOMEM; + goto err_create_work_queue; + } + + ret = gpio_request(SAPPHIRE_GPIO_CABLE_IN1, "h2w_detect"); + if (ret < 0) + goto err_request_detect_gpio; + + ret = gpio_request(SAPPHIRE_GPIO_CABLE_IN2, "h2w_button"); + if (ret < 0) + goto err_request_button_gpio; + + ret = gpio_direction_input(SAPPHIRE_GPIO_CABLE_IN1); + if (ret < 0) + goto err_set_detect_gpio; + + ret = gpio_direction_input(SAPPHIRE_GPIO_CABLE_IN2); + if (ret < 0) + goto err_set_button_gpio; + + hi->irq = gpio_to_irq(SAPPHIRE_GPIO_CABLE_IN1); + if (hi->irq < 0) { + ret = hi->irq; + goto err_get_h2w_detect_irq_num_failed; + } + + hi->irq_btn = gpio_to_irq(SAPPHIRE_GPIO_CABLE_IN2); + if (hi->irq_btn < 0) { + ret = hi->irq_btn; + goto err_get_button_irq_num_failed; + } + + /* Set CPLD MUX to H2W <-> CPLD GPIO */ + configure_cpld(UART3); + /* Set the CPLD connected H2W GPIO's to input */ + gpio_set_value(SAPPHIRE_GPIO_H2W_CLK_DIR, 0); + gpio_set_value(SAPPHIRE_GPIO_H2W_DAT_DIR, 0); + + hrtimer_init(&hi->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hi->timer.function = detect_event_timer_func; + hrtimer_init(&hi->btn_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hi->btn_timer.function = button_event_timer_func; + + ret = request_irq(hi->irq, detect_irq_handler, + IRQF_TRIGGER_LOW, "h2w_detect", NULL); + if (ret < 0) + goto err_request_detect_irq; + + /* Disable button until plugged in */ + set_irq_flags(hi->irq_btn, IRQF_VALID | IRQF_NOAUTOEN); + ret = request_irq(hi->irq_btn, button_irq_handler, + IRQF_TRIGGER_LOW, "h2w_button", NULL); + if (ret < 0) + goto err_request_h2w_headset_button_irq; + + ret = set_irq_wake(hi->irq, 1); + if (ret < 0) + goto err_request_input_dev; + ret = set_irq_wake(hi->irq_btn, 1); + if (ret < 0) + goto err_request_input_dev; + + hi->input = input_allocate_device(); + if (!hi->input) { + ret = -ENOMEM; + goto err_request_input_dev; + } + + hi->input->name = "h2w headset"; + hi->input->evbit[0] = BIT_MASK(EV_KEY); + hi->input->keybit[BIT_WORD(KEY_MEDIA)] = BIT_MASK(KEY_MEDIA); + + ret = input_register_device(hi->input); + if (ret < 0) + goto err_register_input_dev; + + return 0; + +err_register_input_dev: + input_free_device(hi->input); +err_request_input_dev: + free_irq(hi->irq_btn, 0); +err_request_h2w_headset_button_irq: + free_irq(hi->irq, 0); +err_request_detect_irq: +err_get_button_irq_num_failed: +err_get_h2w_detect_irq_num_failed: +err_set_button_gpio: +err_set_detect_gpio: + gpio_free(SAPPHIRE_GPIO_CABLE_IN2); +err_request_button_gpio: + gpio_free(SAPPHIRE_GPIO_CABLE_IN1); +err_request_detect_gpio: + destroy_workqueue(g_detection_work_queue); +err_create_work_queue: + switch_dev_unregister(&hi->sdev); +err_switch_dev_register: + printk(KERN_ERR "H2W: Failed to register driver\n"); + + return ret; +} + +static int sapphire_h2w_remove(struct platform_device *pdev) +{ + H2W_DBG(""); + if (switch_get_state(&hi->sdev)) + remove_headset(); + input_unregister_device(hi->input); + gpio_free(SAPPHIRE_GPIO_CABLE_IN2); + gpio_free(SAPPHIRE_GPIO_CABLE_IN1); + free_irq(hi->irq_btn, 0); + free_irq(hi->irq, 0); + destroy_workqueue(g_detection_work_queue); + switch_dev_unregister(&hi->sdev); + + return 0; +} + +static struct platform_device sapphire_h2w_device = { + .name = "sapphire-h2w", +}; + +static struct platform_driver sapphire_h2w_driver = { + .probe = sapphire_h2w_probe, + .remove = sapphire_h2w_remove, + .driver = { + .name = "sapphire-h2w", + .owner = THIS_MODULE, + }, +}; + +static int __init sapphire_h2w_init(void) +{ + if (!machine_is_sapphire()) + return 0; + int ret; + H2W_DBG(""); + ret = platform_driver_register(&sapphire_h2w_driver); + if (ret) + return ret; + return platform_device_register(&sapphire_h2w_device); +} + +static void __exit sapphire_h2w_exit(void) +{ + platform_device_unregister(&sapphire_h2w_device); + platform_driver_unregister(&sapphire_h2w_driver); +} + +module_init(sapphire_h2w_init); +module_exit(sapphire_h2w_exit); + +MODULE_AUTHOR("Laurence Chen "); +MODULE_DESCRIPTION("HTC 2 Wire detection driver for sapphire"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/board-sapphire-keypad.c b/arch/arm/mach-msm/board-sapphire-keypad.c new file mode 100755 index 00000000000..5c8fc37649a --- /dev/null +++ b/arch/arm/mach-msm/board-sapphire-keypad.c @@ -0,0 +1,132 @@ +/* arch/arm/mach-msm/board-sapphire-keypad.c + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include "gpio_chip.h" +#include "board-sapphire.h" +static char *keycaps = "--qwerty"; +#undef MODULE_PARAM_PREFIX +#define MODULE_PARAM_PREFIX "board_sapphire." +module_param_named(keycaps, keycaps, charp, 0); + + +static unsigned int sapphire_col_gpios[] = { 35, 34 }; + +/* KP_MKIN2 (GPIO40) is not used? */ +static unsigned int sapphire_row_gpios[] = { 42, 41 }; + +#define KEYMAP_INDEX(col, row) ((col)*ARRAY_SIZE(sapphire_row_gpios) + (row)) + +/*scan matrix key*/ +/* HOME(up) MENU (up) Back Search */ +static const unsigned short sapphire_keymap2[ARRAY_SIZE(sapphire_col_gpios) * ARRAY_SIZE(sapphire_row_gpios)] = { + [KEYMAP_INDEX(0, 0)] = KEY_COMPOSE, + [KEYMAP_INDEX(0, 1)] = KEY_BACK, + + [KEYMAP_INDEX(1, 0)] = KEY_MENU, + [KEYMAP_INDEX(1, 1)] = KEY_SEND, +}; + +/* HOME(up) + MENU (down)*/ +static const unsigned short sapphire_keymap1[ARRAY_SIZE(sapphire_col_gpios) * + ARRAY_SIZE(sapphire_row_gpios)] = { + [KEYMAP_INDEX(0, 0)] = KEY_BACK, + [KEYMAP_INDEX(0, 1)] = KEY_MENU, + + [KEYMAP_INDEX(1, 0)] = KEY_HOME, + [KEYMAP_INDEX(1, 1)] = KEY_SEND, +}; + +/* MENU(up) + HOME (down)*/ +static const unsigned short sapphire_keymap0[ARRAY_SIZE(sapphire_col_gpios) * + ARRAY_SIZE(sapphire_row_gpios)] = { + [KEYMAP_INDEX(0, 0)] = KEY_BACK, + [KEYMAP_INDEX(0, 1)] = KEY_HOME, + + [KEYMAP_INDEX(1, 0)] = KEY_MENU, + [KEYMAP_INDEX(1, 1)] = KEY_SEND, +}; + +static struct gpio_event_matrix_info sapphire_keypad_matrix_info = { + .info.func = gpio_event_matrix_func, + .keymap = sapphire_keymap2, + .output_gpios = sapphire_col_gpios, + .input_gpios = sapphire_row_gpios, + .noutputs = ARRAY_SIZE(sapphire_col_gpios), + .ninputs = ARRAY_SIZE(sapphire_row_gpios), + .settle_time.tv.nsec = 40 * NSEC_PER_USEC, + .poll_time.tv.nsec = 20 * NSEC_PER_MSEC, + .debounce_delay.tv.nsec = 50 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | + GPIOKPF_REMOVE_PHANTOM_KEYS | + GPIOKPF_PRINT_UNMAPPED_KEYS /*| GPIOKPF_PRINT_MAPPED_KEYS*/ +}; + +static struct gpio_event_direct_entry sapphire_keypad_nav_map[] = { + { SAPPHIRE_POWER_KEY, KEY_END }, + { SAPPHIRE_VOLUME_UP, KEY_VOLUMEUP }, + { SAPPHIRE_VOLUME_DOWN, KEY_VOLUMEDOWN }, +}; + +static struct gpio_event_input_info sapphire_keypad_nav_info = { + .info.func = gpio_event_input_func, + .flags = 0, + .type = EV_KEY, + .keymap = sapphire_keypad_nav_map, + .debounce_time.tv.nsec = 20 * NSEC_PER_MSEC, + .keymap_size = ARRAY_SIZE(sapphire_keypad_nav_map) +}; + +static struct gpio_event_info *sapphire_keypad_info[] = { + &sapphire_keypad_matrix_info.info, + &sapphire_keypad_nav_info.info, +}; + +static struct gpio_event_platform_data sapphire_keypad_data = { + .name = "sapphire-keypad", + .info = sapphire_keypad_info, + .info_count = ARRAY_SIZE(sapphire_keypad_info) +}; + +static struct platform_device sapphire_keypad_device = { + .name = GPIO_EVENT_DEV_NAME, + .id = 0, + .dev = { + .platform_data = &sapphire_keypad_data, + }, +}; + +static int __init sapphire_init_keypad(void) +{ + if (!machine_is_sapphire()) + return 0; + + switch (sapphire_get_hwid()) { + case 0: + sapphire_keypad_matrix_info.keymap = sapphire_keymap0; + break; + default: + if(system_rev != 0x80) + sapphire_keypad_matrix_info.keymap = sapphire_keymap1; + break; + } + return platform_device_register(&sapphire_keypad_device); +} + +device_initcall(sapphire_init_keypad); + diff --git a/arch/arm/mach-msm/board-sapphire-mmc.c b/arch/arm/mach-msm/board-sapphire-mmc.c new file mode 100755 index 00000000000..a8a7963abe4 --- /dev/null +++ b/arch/arm/mach-msm/board-sapphire-mmc.c @@ -0,0 +1,486 @@ +/* linux/arch/arm/mach-msm/board-sapphire-mmc.c + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "devices.h" +#include "gpio_chip.h" +#include "board-sapphire.h" +#include "proc_comm.h" + +#define DEBUG_SDSLOT_VDD 0 + +extern int msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat, + unsigned int stat_irq, unsigned long stat_irq_flags); + +/* ---- COMMON ---- */ +static void config_gpio_table(uint32_t *table, int len) +{ + int n; + unsigned id; + for (n = 0; n < len; n++) { + id = table[n]; + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0); + } +} + +/* ---- SDCARD ---- */ + +static uint32_t sdcard_on_gpio_table[] = { + PCOM_GPIO_CFG(62, 2, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_8MA), /* CLK */ + PCOM_GPIO_CFG(63, 2, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), /* CMD */ + PCOM_GPIO_CFG(64, 2, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), /* DAT3 */ + PCOM_GPIO_CFG(65, 2, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), /* DAT2 */ + PCOM_GPIO_CFG(66, 2, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT1 */ + PCOM_GPIO_CFG(67, 2, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT0 */ +}; + +static uint32_t sdcard_off_gpio_table[] = { + PCOM_GPIO_CFG(62, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* CLK */ + PCOM_GPIO_CFG(63, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* CMD */ + PCOM_GPIO_CFG(64, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT3 */ + PCOM_GPIO_CFG(65, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT2 */ + PCOM_GPIO_CFG(66, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT1 */ + PCOM_GPIO_CFG(67, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT0 */ +}; + +static uint opt_disable_sdcard; + +static int __init sapphire_disablesdcard_setup(char *str) +{ + int cal = simple_strtol(str, NULL, 0); + + opt_disable_sdcard = cal; + return 1; +} + +__setup("board_sapphire.disable_sdcard=", sapphire_disablesdcard_setup); + +static struct vreg *vreg_sdslot; /* SD slot power */ + +struct mmc_vdd_xlat { + int mask; + int level; +}; + +static struct mmc_vdd_xlat mmc_vdd_table[] = { + { MMC_VDD_165_195, 1800 }, + { MMC_VDD_20_21, 2050 }, + { MMC_VDD_21_22, 2150 }, + { MMC_VDD_22_23, 2250 }, + { MMC_VDD_23_24, 2350 }, + { MMC_VDD_24_25, 2450 }, + { MMC_VDD_25_26, 2550 }, + { MMC_VDD_26_27, 2650 }, + { MMC_VDD_27_28, 2750 }, + { MMC_VDD_28_29, 2850 }, + { MMC_VDD_29_30, 2950 }, +}; + +static unsigned int sdslot_vdd = 0xffffffff; +static unsigned int sdslot_vreg_enabled; + +static uint32_t sapphire_sdslot_switchvdd(struct device *dev, unsigned int vdd) +{ + int i, rc; + + BUG_ON(!vreg_sdslot); + + if (vdd == sdslot_vdd) + return 0; + + sdslot_vdd = vdd; + + if (vdd == 0) { +#if DEBUG_SDSLOT_VDD + printk(KERN_DEBUG "%s: Disabling SD slot power\n", __func__); +#endif + config_gpio_table(sdcard_off_gpio_table, + ARRAY_SIZE(sdcard_off_gpio_table)); + vreg_disable(vreg_sdslot); + sdslot_vreg_enabled = 0; + return 0; + } + + if (!sdslot_vreg_enabled) { + rc = vreg_enable(vreg_sdslot); + if (rc) { + printk(KERN_ERR "%s: Error enabling vreg (%d)\n", + __func__, rc); + } + config_gpio_table(sdcard_on_gpio_table, + ARRAY_SIZE(sdcard_on_gpio_table)); + sdslot_vreg_enabled = 1; + } + + for (i = 0; i < ARRAY_SIZE(mmc_vdd_table); i++) { + if (mmc_vdd_table[i].mask == (1 << vdd)) { +#if DEBUG_SDSLOT_VDD + printk(KERN_DEBUG "%s: Setting level to %u\n", + __func__, mmc_vdd_table[i].level); +#endif + rc = vreg_set_level(vreg_sdslot, + mmc_vdd_table[i].level); + if (rc) { + printk(KERN_ERR + "%s: Error setting vreg level (%d)\n", + __func__, rc); + } + return 0; + } + } + + printk(KERN_ERR "%s: Invalid VDD %d specified\n", __func__, vdd); + return 0; +} + +static unsigned int sapphire_sdslot_status(struct device *dev) +{ + unsigned int status; + + status = (unsigned int) gpio_get_value(SAPPHIRE_GPIO_SDMC_CD_N); + return !status; +} + +#define SAPPHIRE_MMC_VDD (MMC_VDD_165_195 | MMC_VDD_20_21 | MMC_VDD_21_22 \ + | MMC_VDD_22_23 | MMC_VDD_23_24 | MMC_VDD_24_25 \ + | MMC_VDD_25_26 | MMC_VDD_26_27 | MMC_VDD_27_28 \ + | MMC_VDD_28_29 | MMC_VDD_29_30) + +static struct mmc_platform_data sapphire_sdslot_data = { + .ocr_mask = SAPPHIRE_MMC_VDD, + .status = sapphire_sdslot_status, + .translate_vdd = sapphire_sdslot_switchvdd, +}; + +/* ---- WIFI ---- */ + +static uint32_t wifi_on_gpio_table[] = { + PCOM_GPIO_CFG(51, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT3 */ + PCOM_GPIO_CFG(52, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT2 */ + PCOM_GPIO_CFG(53, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT1 */ + PCOM_GPIO_CFG(54, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), /* DAT0 */ + PCOM_GPIO_CFG(55, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), /* CMD */ + PCOM_GPIO_CFG(56, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_8MA), /* CLK */ + PCOM_GPIO_CFG(29, 0, GPIO_INPUT, GPIO_NO_PULL, GPIO_4MA), /* WLAN IRQ */ +}; + +static uint32_t wifi_off_gpio_table[] = { + PCOM_GPIO_CFG(51, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT3 */ + PCOM_GPIO_CFG(52, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT2 */ + PCOM_GPIO_CFG(53, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT1 */ + PCOM_GPIO_CFG(54, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* DAT0 */ + PCOM_GPIO_CFG(55, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* CMD */ + PCOM_GPIO_CFG(56, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* CLK */ + PCOM_GPIO_CFG(29, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), /* WLAN IRQ */ +}; + +static struct vreg *vreg_wifi_osc; /* WIFI 32khz oscilator */ +static int sapphire_wifi_cd = 0; /* WIFI virtual 'card detect' status */ + +static struct sdio_embedded_func wifi_func = { + .f_class = SDIO_CLASS_WLAN, + .f_maxblksize = 512, +}; + +static struct embedded_sdio_data sapphire_wifi_emb_data = { + .cis = { + .vendor = 0x104c, + .device = 0x9066, + .blksize = 512, + .max_dtr = 20000000, + }, + .cccr = { + .multi_block = 0, + .low_speed = 0, + .wide_bus = 1, + .high_power = 0, + .high_speed = 0, + }, + .funcs = &wifi_func, + .num_funcs = 1, +}; + +static void (*wifi_status_cb)(int card_present, void *dev_id); +static void *wifi_status_cb_devid; + +static int sapphire_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 sapphire_wifi_status(struct device *dev) +{ + return sapphire_wifi_cd; +} + +int sapphire_wifi_set_carddetect(int val) +{ + printk(KERN_DEBUG "%s: %d\n", __func__, val); + sapphire_wifi_cd = val; + if (wifi_status_cb) + wifi_status_cb(val, wifi_status_cb_devid); + else + printk(KERN_WARNING "%s: Nobody to notify\n", __func__); + return 0; +} +#ifndef CONFIG_WIFI_CONTROL_FUNC +EXPORT_SYMBOL(sapphire_wifi_set_carddetect); +#endif + +int sapphire_wifi_power_state=0; +int sapphire_bt_power_state=0; + +int sapphire_wifi_power(int on) +{ + int rc; + + printk(KERN_DEBUG "%s: %d\n", __func__, on); + + if (on) { + config_gpio_table(wifi_on_gpio_table, + ARRAY_SIZE(wifi_on_gpio_table)); + rc = vreg_enable(vreg_wifi_osc); + if (rc) + return rc; + htc_pwrsink_set(PWRSINK_WIFI, 70); + } else { + config_gpio_table(wifi_off_gpio_table, + ARRAY_SIZE(wifi_off_gpio_table)); + htc_pwrsink_set(PWRSINK_WIFI, 0); + } + gpio_set_value(SAPPHIRE_GPIO_MAC_32K_EN, on); + mdelay(100); + gpio_set_value(SAPPHIRE_GPIO_WIFI_EN, on); + mdelay(100); + if (!on) { + if(!sapphire_bt_power_state) + { + vreg_disable(vreg_wifi_osc); + printk("WiFi disable vreg_wifi_osc.\n"); + } + else + printk("WiFi shouldn't disable vreg_wifi_osc. BT is using it!!\n"); + } + sapphire_wifi_power_state = on; + return 0; +} +#ifndef CONFIG_WIFI_CONTROL_FUNC +EXPORT_SYMBOL(sapphire_wifi_power); +#endif + +/* Eenable VREG_MMC pin to turn on fastclock oscillator : colin */ +int sapphire_bt_fastclock_power(int on) +{ + int rc; + + printk(KERN_DEBUG "sapphire_bt_fastclock_power on = %d\n", on); + if (vreg_wifi_osc) { + if (on) { + rc = vreg_enable(vreg_wifi_osc); + printk(KERN_DEBUG "BT vreg_enable vreg_mmc, rc=%d\n", + rc); + if (rc) { + printk("Error turn sapphire_bt_fastclock_power rc=%d\n", rc); + return rc; + } + } else { + if (!sapphire_wifi_power_state) { + vreg_disable(vreg_wifi_osc); + printk(KERN_DEBUG "BT disable vreg_wifi_osc.\n"); + } else + printk(KERN_DEBUG "BT shouldn't disable vreg_wifi_osc. WiFi is using it!!\n"); + } + } + sapphire_bt_power_state = on; + return 0; +} +EXPORT_SYMBOL(sapphire_bt_fastclock_power); + +static int sapphire_wifi_reset_state; +void sapphire_wifi_reset(int on) +{ + printk(KERN_DEBUG "%s: %d\n", __func__, on); + gpio_set_value(SAPPHIRE_GPIO_WIFI_PA_RESETX, !on); + sapphire_wifi_reset_state = on; + mdelay(50); +} +#ifndef CONFIG_WIFI_CONTROL_FUNC +EXPORT_SYMBOL(sapphire_wifi_reset); +#endif + +static struct mmc_platform_data sapphire_wifi_data = { + .ocr_mask = MMC_VDD_28_29, + .status = sapphire_wifi_status, + .register_status_notify = sapphire_wifi_status_register, + .embedded_sdio = &sapphire_wifi_emb_data, +}; + +int __init sapphire_init_mmc(unsigned int sys_rev) +{ + wifi_status_cb = NULL; + + sdslot_vreg_enabled = 0; + + vreg_sdslot = vreg_get(0, "gp6"); + if (IS_ERR(vreg_sdslot)) + return PTR_ERR(vreg_sdslot); + vreg_wifi_osc = vreg_get(0, "mmc"); + if (IS_ERR(vreg_wifi_osc)) + return PTR_ERR(vreg_wifi_osc); + + set_irq_wake(SAPPHIRE_GPIO_TO_INT(SAPPHIRE_GPIO_SDMC_CD_N), 1); + + msm_add_sdcc(1, &sapphire_wifi_data, 0, 0); + + if (!opt_disable_sdcard) + msm_add_sdcc(2, &sapphire_sdslot_data, + SAPPHIRE_GPIO_TO_INT(SAPPHIRE_GPIO_SDMC_CD_N), 0); + else + printk(KERN_INFO "sapphire: SD-Card interface disabled\n"); + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +static int sapphiremmc_dbg_wifi_reset_set(void *data, u64 val) +{ + sapphire_wifi_reset((int) val); + return 0; +} + +static int sapphiremmc_dbg_wifi_reset_get(void *data, u64 *val) +{ + *val = sapphire_wifi_reset_state; + return 0; +} + +static int sapphiremmc_dbg_wifi_cd_set(void *data, u64 val) +{ + sapphire_wifi_set_carddetect((int) val); + return 0; +} + +static int sapphiremmc_dbg_wifi_cd_get(void *data, u64 *val) +{ + *val = sapphire_wifi_cd; + return 0; +} + +static int sapphiremmc_dbg_wifi_pwr_set(void *data, u64 val) +{ + sapphire_wifi_power((int) val); + return 0; +} + +static int sapphiremmc_dbg_wifi_pwr_get(void *data, u64 *val) +{ + + *val = sapphire_wifi_power_state; + return 0; +} + +static int sapphiremmc_dbg_sd_pwr_set(void *data, u64 val) +{ + sapphire_sdslot_switchvdd(NULL, (unsigned int) val); + return 0; +} + +static int sapphiremmc_dbg_sd_pwr_get(void *data, u64 *val) +{ + *val = sdslot_vdd; + return 0; +} + +static int sapphiremmc_dbg_sd_cd_set(void *data, u64 val) +{ + return -ENOSYS; +} + +static int sapphiremmc_dbg_sd_cd_get(void *data, u64 *val) +{ + *val = sapphire_sdslot_status(NULL); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(sapphiremmc_dbg_wifi_reset_fops, + sapphiremmc_dbg_wifi_reset_get, + sapphiremmc_dbg_wifi_reset_set, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(sapphiremmc_dbg_wifi_cd_fops, + sapphiremmc_dbg_wifi_cd_get, + sapphiremmc_dbg_wifi_cd_set, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(sapphiremmc_dbg_wifi_pwr_fops, + sapphiremmc_dbg_wifi_pwr_get, + sapphiremmc_dbg_wifi_pwr_set, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(sapphiremmc_dbg_sd_pwr_fops, + sapphiremmc_dbg_sd_pwr_get, + sapphiremmc_dbg_sd_pwr_set, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(sapphiremmc_dbg_sd_cd_fops, + sapphiremmc_dbg_sd_cd_get, + sapphiremmc_dbg_sd_cd_set, "%llu\n"); + +static int __init sapphiremmc_dbg_init(void) +{ + struct dentry *dent; + + if (!machine_is_sapphire()) + return 0; + + dent = debugfs_create_dir("sapphiremmc_dbg", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debugfs_create_file("wifi_reset", 0644, dent, NULL, + &sapphiremmc_dbg_wifi_reset_fops); + debugfs_create_file("wifi_cd", 0644, dent, NULL, + &sapphiremmc_dbg_wifi_cd_fops); + debugfs_create_file("wifi_pwr", 0644, dent, NULL, + &sapphiremmc_dbg_wifi_pwr_fops); + + debugfs_create_file("sd_pwr", 0644, dent, NULL, + &sapphiremmc_dbg_sd_pwr_fops); + debugfs_create_file("sd_cd", 0644, dent, NULL, + &sapphiremmc_dbg_sd_cd_fops); + + return 0; +} + +device_initcall(sapphiremmc_dbg_init); + +#endif diff --git a/arch/arm/mach-msm/board-sapphire-panel.c b/arch/arm/mach-msm/board-sapphire-panel.c new file mode 100644 index 00000000000..f71ca0113c5 --- /dev/null +++ b/arch/arm/mach-msm/board-sapphire-panel.c @@ -0,0 +1,1272 @@ +/* linux/arch/arm/mach-msm/board-sapphire-panel.c + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "gpio_chip.h" +#include "board-sapphire.h" +#include "proc_comm.h" +#include "devices.h" + +#define DEBUG_SAPPHIRE_PANEL 0 +#define userid 0xD10 + +#define VSYNC_GPIO 97 + +enum sapphire_panel_type { + SAPPHIRE_PANEL_SHARP = 0, + SAPPHIRE_PANEL_TOPPOLY, + NUM_OF_SAPPHIRE_PANELS, +}; +static int g_panel_id = -1 ; +static int g_panel_inited = 0 ; + +#define SAPPHIRE_DEFAULT_BACKLIGHT_BRIGHTNESS 132 +#define GOOGLE_DEFAULT_BACKLIGHT_BRIGHTNESS 102 +#define SDBB SAPPHIRE_DEFAULT_BACKLIGHT_BRIGHTNESS +#define GDBB GOOGLE_DEFAULT_BACKLIGHT_BRIGHTNESS + +static int sapphire_backlight_off; +static int sapphire_backlight_brightness = + SAPPHIRE_DEFAULT_BACKLIGHT_BRIGHTNESS; + +static uint8_t sapphire_backlight_last_level = 33; +static DEFINE_MUTEX(sapphire_backlight_lock); + +/* Divide dimming level into 12 sections, and restrict maximum level to 27 */ +#define DIMMING_STEPS 12 +static unsigned dimming_levels[NUM_OF_SAPPHIRE_PANELS][DIMMING_STEPS] = { + {0, 1, 2, 3, 6, 9, 11, 13, 16, 19, 22, 25}, /* Sharp */ + {0, 1, 2, 4, 7, 10, 13, 15, 18, 21, 24, 27}, /* Toppolly */ +}; +static unsigned pwrsink_percents[] = {0, 6, 8, 15, 26, 34, 46, 54, 65, 77, 87, + 100}; + +static void sapphire_set_backlight_level(uint8_t level) +{ + unsigned dimming_factor = 255/DIMMING_STEPS + 1; + int index, new_level ; + unsigned percent; + unsigned long flags; + int i = 0; + + /* Non-linear transform for the difference between two + * kind of default backlight settings. + */ + new_level = level<=GDBB ? + level*SDBB/GDBB : (SDBB + (level-GDBB)*(255-SDBB) / (255-GDBB)) ; + index = new_level/dimming_factor ; + +#if DEBUG_SAPPHIRE_PANEL + printk(KERN_INFO "level=%d, new level=%d, dimming_levels[%d]=%d\n", + level, new_level, index, dimming_levels[g_panel_id][index]); +#endif + percent = pwrsink_percents[index]; + level = dimming_levels[g_panel_id][index]; + + if (sapphire_backlight_last_level == level) + return; + + if (level == 0) { + gpio_set_value(27, 0); + msleep(2); + } else { + local_irq_save(flags); + if (sapphire_backlight_last_level == 0) { + gpio_set_value(27, 1); + udelay(40); + sapphire_backlight_last_level = 33; + } + i = (sapphire_backlight_last_level - level + 33) % 33; + while (i-- > 0) { + gpio_set_value(27, 0); + udelay(1); + gpio_set_value(27, 1); + udelay(1); + } + local_irq_restore(flags); + } + sapphire_backlight_last_level = level; + htc_pwrsink_set(PWRSINK_BACKLIGHT, percent); +} + +#define MDDI_CLIENT_CORE_BASE 0x108000 +#define LCD_CONTROL_BLOCK_BASE 0x110000 +#define SPI_BLOCK_BASE 0x120000 +#define I2C_BLOCK_BASE 0x130000 +#define PWM_BLOCK_BASE 0x140000 +#define GPIO_BLOCK_BASE 0x150000 +#define SYSTEM_BLOCK1_BASE 0x160000 +#define SYSTEM_BLOCK2_BASE 0x170000 + + +#define DPSUS (MDDI_CLIENT_CORE_BASE|0x24) +#define SYSCLKENA (MDDI_CLIENT_CORE_BASE|0x2C) +#define PWM0OFF (PWM_BLOCK_BASE|0x1C) + +#define V_VDDE2E_VDD2_GPIO 0 +#define V_VDDE2E_VDD2_GPIO_5M 89 +#define MDDI_RST_N 82 + +#define MDDICAP0 (MDDI_CLIENT_CORE_BASE|0x00) +#define MDDICAP1 (MDDI_CLIENT_CORE_BASE|0x04) +#define MDDICAP2 (MDDI_CLIENT_CORE_BASE|0x08) +#define MDDICAP3 (MDDI_CLIENT_CORE_BASE|0x0C) +#define MDCAPCHG (MDDI_CLIENT_CORE_BASE|0x10) +#define MDCRCERC (MDDI_CLIENT_CORE_BASE|0x14) +#define TTBUSSEL (MDDI_CLIENT_CORE_BASE|0x18) +#define DPSET0 (MDDI_CLIENT_CORE_BASE|0x1C) +#define DPSET1 (MDDI_CLIENT_CORE_BASE|0x20) +#define DPSUS (MDDI_CLIENT_CORE_BASE|0x24) +#define DPRUN (MDDI_CLIENT_CORE_BASE|0x28) +#define SYSCKENA (MDDI_CLIENT_CORE_BASE|0x2C) +#define TESTMODE (MDDI_CLIENT_CORE_BASE|0x30) +#define FIFOMONI (MDDI_CLIENT_CORE_BASE|0x34) +#define INTMONI (MDDI_CLIENT_CORE_BASE|0x38) +#define MDIOBIST (MDDI_CLIENT_CORE_BASE|0x3C) +#define MDIOPSET (MDDI_CLIENT_CORE_BASE|0x40) +#define BITMAP0 (MDDI_CLIENT_CORE_BASE|0x44) +#define BITMAP1 (MDDI_CLIENT_CORE_BASE|0x48) +#define BITMAP2 (MDDI_CLIENT_CORE_BASE|0x4C) +#define BITMAP3 (MDDI_CLIENT_CORE_BASE|0x50) +#define BITMAP4 (MDDI_CLIENT_CORE_BASE|0x54) + +#define SRST (LCD_CONTROL_BLOCK_BASE|0x00) +#define PORT_ENB (LCD_CONTROL_BLOCK_BASE|0x04) +#define START (LCD_CONTROL_BLOCK_BASE|0x08) +#define PORT (LCD_CONTROL_BLOCK_BASE|0x0C) +#define CMN (LCD_CONTROL_BLOCK_BASE|0x10) +#define GAMMA (LCD_CONTROL_BLOCK_BASE|0x14) +#define INTFLG (LCD_CONTROL_BLOCK_BASE|0x18) +#define INTMSK (LCD_CONTROL_BLOCK_BASE|0x1C) +#define MPLFBUF (LCD_CONTROL_BLOCK_BASE|0x20) +#define HDE_LEFT (LCD_CONTROL_BLOCK_BASE|0x24) +#define VDE_TOP (LCD_CONTROL_BLOCK_BASE|0x28) +#define PXL (LCD_CONTROL_BLOCK_BASE|0x30) +#define HCYCLE (LCD_CONTROL_BLOCK_BASE|0x34) +#define HSW (LCD_CONTROL_BLOCK_BASE|0x38) +#define HDE_START (LCD_CONTROL_BLOCK_BASE|0x3C) +#define HDE_SIZE (LCD_CONTROL_BLOCK_BASE|0x40) +#define VCYCLE (LCD_CONTROL_BLOCK_BASE|0x44) +#define VSW (LCD_CONTROL_BLOCK_BASE|0x48) +#define VDE_START (LCD_CONTROL_BLOCK_BASE|0x4C) +#define VDE_SIZE (LCD_CONTROL_BLOCK_BASE|0x50) +#define WAKEUP (LCD_CONTROL_BLOCK_BASE|0x54) +#define WSYN_DLY (LCD_CONTROL_BLOCK_BASE|0x58) +#define REGENB (LCD_CONTROL_BLOCK_BASE|0x5C) +#define VSYNIF (LCD_CONTROL_BLOCK_BASE|0x60) +#define WRSTB (LCD_CONTROL_BLOCK_BASE|0x64) +#define RDSTB (LCD_CONTROL_BLOCK_BASE|0x68) +#define ASY_DATA (LCD_CONTROL_BLOCK_BASE|0x6C) +#define ASY_DATB (LCD_CONTROL_BLOCK_BASE|0x70) +#define ASY_DATC (LCD_CONTROL_BLOCK_BASE|0x74) +#define ASY_DATD (LCD_CONTROL_BLOCK_BASE|0x78) +#define ASY_DATE (LCD_CONTROL_BLOCK_BASE|0x7C) +#define ASY_DATF (LCD_CONTROL_BLOCK_BASE|0x80) +#define ASY_DATG (LCD_CONTROL_BLOCK_BASE|0x84) +#define ASY_DATH (LCD_CONTROL_BLOCK_BASE|0x88) +#define ASY_CMDSET (LCD_CONTROL_BLOCK_BASE|0x8C) + +#define SSICTL (SPI_BLOCK_BASE|0x00) +#define SSITIME (SPI_BLOCK_BASE|0x04) +#define SSITX (SPI_BLOCK_BASE|0x08) +#define SSIRX (SPI_BLOCK_BASE|0x0C) +#define SSIINTC (SPI_BLOCK_BASE|0x10) +#define SSIINTS (SPI_BLOCK_BASE|0x14) +#define SSIDBG1 (SPI_BLOCK_BASE|0x18) +#define SSIDBG2 (SPI_BLOCK_BASE|0x1C) +#define SSIID (SPI_BLOCK_BASE|0x20) + +#define WKREQ (SYSTEM_BLOCK1_BASE|0x00) +#define CLKENB (SYSTEM_BLOCK1_BASE|0x04) +#define DRAMPWR (SYSTEM_BLOCK1_BASE|0x08) +#define INTMASK (SYSTEM_BLOCK1_BASE|0x0C) +#define GPIOSEL (SYSTEM_BLOCK2_BASE|0x00) + +#define GPIODATA (GPIO_BLOCK_BASE|0x00) +#define GPIODIR (GPIO_BLOCK_BASE|0x04) +#define GPIOIS (GPIO_BLOCK_BASE|0x08) +#define GPIOIBE (GPIO_BLOCK_BASE|0x0C) +#define GPIOIEV (GPIO_BLOCK_BASE|0x10) +#define GPIOIE (GPIO_BLOCK_BASE|0x14) +#define GPIORIS (GPIO_BLOCK_BASE|0x18) +#define GPIOMIS (GPIO_BLOCK_BASE|0x1C) +#define GPIOIC (GPIO_BLOCK_BASE|0x20) +#define GPIOOMS (GPIO_BLOCK_BASE|0x24) +#define GPIOPC (GPIO_BLOCK_BASE|0x28) +#define GPIOID (GPIO_BLOCK_BASE|0x30) + +#define SPI_WRITE(reg, val) \ + { SSITX, 0x00010000 | (((reg) & 0xff) << 8) | ((val) & 0xff) }, \ + { 0, 5 }, + +#define SPI_WRITE1(reg) \ + { SSITX, (reg) & 0xff }, \ + { 0, 5 }, + +struct mddi_table { + uint32_t reg; + uint32_t value; +}; +static struct mddi_table mddi_toshiba_init_table[] = { + { DPSET0, 0x09e90046 }, + { DPSET1, 0x00000118 }, + { DPSUS, 0x00000000 }, + { DPRUN, 0x00000001 }, + { 1, 14 }, /* msleep 14 */ + { SYSCKENA, 0x00000001 }, + /*{ CLKENB, 0x000000EF } */ + { CLKENB, 0x0000A1EF }, /* # SYS.CLKENB # Enable clocks for each module (without DCLK , i2cCLK) */ + /*{ CLKENB, 0x000025CB }, Clock enable register */ + + { GPIODATA, 0x02000200 }, /* # GPI .GPIODATA # GPIO2(RESET_LCD_N) set to 0 , GPIO3(eDRAM_Power) set to 0 */ + { GPIODIR, 0x000030D }, /* 24D # GPI .GPIODIR # Select direction of GPIO port (0,2,3,6,9 output) */ + { GPIOSEL, 0/*0x00000173*/}, /* # SYS.GPIOSEL # GPIO port multiplexing control */ + { GPIOPC, 0x03C300C0 }, /* # GPI .GPIOPC # GPIO2,3 PD cut */ + { WKREQ, 0x00000000 }, /* # SYS.WKREQ # Wake-up request event is VSYNC alignment */ + + { GPIOIBE, 0x000003FF }, + { GPIOIS, 0x00000000 }, + { GPIOIC, 0x000003FF }, + { GPIOIE, 0x00000000 }, + + { GPIODATA, 0x00040004 }, /* # GPI .GPIODATA # eDRAM VD supply */ + { 1, 1 }, /* msleep 1 */ + { GPIODATA, 0x02040004 }, /* # GPI .GPIODATA # eDRAM VD supply */ + { DRAMPWR, 0x00000001 }, /* eDRAM power */ +}; + +static struct mddi_table mddi_toshiba_panel_init_table[] = { + { SRST, 0x00000003 }, /* FIFO/LCDC not reset */ + { PORT_ENB, 0x00000001 }, /* Enable sync. Port */ + { START, 0x00000000 }, /* To stop operation */ + /*{ START, 0x00000001 }, To start operation */ + { PORT, 0x00000004 }, /* Polarity of VS/HS/DE. */ + { CMN, 0x00000000 }, + { GAMMA, 0x00000000 }, /* No Gamma correction */ + { INTFLG, 0x00000000 }, /* VSYNC interrupt flag clear/status */ + { INTMSK, 0x00000000 }, /* VSYNC interrupt mask is off. */ + { MPLFBUF, 0x00000000 }, /* Select frame buffer's base address. */ + { HDE_LEFT, 0x00000000 }, /* The value of HDE_LEFT. */ + { VDE_TOP, 0x00000000 }, /* The value of VDE_TPO. */ + { PXL, 0x00000001 }, /* 1. RGB666 */ + /* 2. Data is valid from 1st frame of beginning. */ + { HDE_START, 0x00000006 }, /* HDE_START= 14 PCLK */ + { HDE_SIZE, 0x0000009F }, /* HDE_SIZE=320 PCLK */ + { HSW, 0x00000004 }, /* HSW= 10 PCLK */ + { VSW, 0x00000001 }, /* VSW=2 HCYCLE */ + { VDE_START, 0x00000003 }, /* VDE_START=4 HCYCLE */ + { VDE_SIZE, 0x000001DF }, /* VDE_SIZE=480 HCYCLE */ + { WAKEUP, 0x000001e2 }, /* Wakeup position in VSYNC mode. */ + { WSYN_DLY, 0x00000000 }, /* Wakeup position in VSIN mode. */ + { REGENB, 0x00000001 }, /* Set 1 to enable to change the value of registers. */ + { CLKENB, 0x000025CB }, /* Clock enable register */ + + { SSICTL, 0x00000170 }, /* SSI control register */ + { SSITIME, 0x00000250 }, /* SSI timing control register */ + { SSICTL, 0x00000172 }, /* SSI control register */ +}; + + +static struct mddi_table mddi_sharp_init_table[] = { + { VCYCLE, 0x000001eb }, + { HCYCLE, 0x000000ae }, + { REGENB, 0x00000001 }, /* Set 1 to enable to change the value of registers. */ + { GPIODATA, 0x00040000 }, /* GPIO2 low */ + { GPIODIR, 0x00000004 }, /* GPIO2 out */ + { 1, 1 }, /* msleep 1 */ + { GPIODATA, 0x00040004 }, /* GPIO2 high */ + { 1, 10 }, /* msleep 10 */ + SPI_WRITE(0x5f, 0x01) + SPI_WRITE1(0x11) + { 1, 200 }, /* msleep 200 */ + SPI_WRITE1(0x29) + SPI_WRITE1(0xde) + { START, 0x00000001 }, /* To start operation */ +}; + +static struct mddi_table mddi_sharp_deinit_table[] = { + { 1, 200 }, /* msleep 200 */ + SPI_WRITE(0x10, 0x1) + { 1, 100 }, /* msleep 100 */ + { GPIODATA, 0x00040004 }, /* GPIO2 high */ + { GPIODIR, 0x00000004 }, /* GPIO2 out */ + { GPIODATA, 0x00040000 }, /* GPIO2 low */ + { 1, 10 }, /* msleep 10 */ +}; + +static struct mddi_table mddi_tpo_init_table[] = { + { VCYCLE, 0x000001e5 }, + { HCYCLE, 0x000000ac }, + { REGENB, 0x00000001 }, /* Set 1 to enable to change the value of registers. */ + { 0, 20 }, /* udelay 20 */ + { GPIODATA, 0x00000004 }, /* GPIO2 high */ + { GPIODIR, 0x00000004 }, /* GPIO2 out */ + { 0, 20 }, /* udelay 20 */ + + SPI_WRITE(0x08, 0x01) + { 0, 500 }, /* udelay 500 */ + SPI_WRITE(0x08, 0x00) + SPI_WRITE(0x02, 0x00) + SPI_WRITE(0x03, 0x04) + SPI_WRITE(0x04, 0x0e) + SPI_WRITE(0x09, 0x02) + SPI_WRITE(0x0b, 0x08) + SPI_WRITE(0x0c, 0x53) + SPI_WRITE(0x0d, 0x01) + SPI_WRITE(0x0e, 0xe0) + SPI_WRITE(0x0f, 0x01) + SPI_WRITE(0x10, 0x58) + SPI_WRITE(0x20, 0x1e) + SPI_WRITE(0x21, 0x0a) + SPI_WRITE(0x22, 0x0a) + SPI_WRITE(0x23, 0x1e) + SPI_WRITE(0x25, 0x32) + SPI_WRITE(0x26, 0x00) + SPI_WRITE(0x27, 0xac) + SPI_WRITE(0x29, 0x06) + SPI_WRITE(0x2a, 0xa4) + SPI_WRITE(0x2b, 0x45) + SPI_WRITE(0x2c, 0x45) + SPI_WRITE(0x2d, 0x15) + SPI_WRITE(0x2e, 0x5a) + SPI_WRITE(0x2f, 0xff) + SPI_WRITE(0x30, 0x6b) + SPI_WRITE(0x31, 0x0d) + SPI_WRITE(0x32, 0x48) + SPI_WRITE(0x33, 0x82) + SPI_WRITE(0x34, 0xbd) + SPI_WRITE(0x35, 0xe7) + SPI_WRITE(0x36, 0x18) + SPI_WRITE(0x37, 0x94) + SPI_WRITE(0x38, 0x01) + SPI_WRITE(0x39, 0x5d) + SPI_WRITE(0x3a, 0xae) + SPI_WRITE(0x3b, 0xff) + SPI_WRITE(0x07, 0x09) + { 0, 10 }, /* udelay 10 */ + { START, 0x00000001 }, /* To start operation */ +}; + +static struct mddi_table mddi_tpo_deinit_table[] = { + SPI_WRITE(0x07, 0x19) + { START, 0x00000000 }, /* To stop operation */ + { GPIODATA, 0x00040004 }, /* GPIO2 high */ + { GPIODIR, 0x00000004 }, /* GPIO2 out */ + { GPIODATA, 0x00040000 }, /* GPIO2 low */ + { 0, 5 }, /* usleep 5 */ +}; + + +#define GPIOSEL_VWAKEINT (1U << 0) +#define INTMASK_VWAKEOUT (1U << 0) + +static void sapphire_process_mddi_table( + struct msm_mddi_client_data *client_data, + const struct mddi_table *table, + size_t count) +{ + int i; + for (i = 0; i < count; i++) { + uint32_t reg = table[i].reg; + uint32_t value = table[i].value; + + if (reg == 0) + udelay(value); + else if (reg == 1) + msleep(value); + else + client_data->remote_write(client_data, value, reg); + } +} + +static struct vreg *vreg_lcm_2v85; + +static void sapphire_mddi_power_client(struct msm_mddi_client_data *client_data, + int on) +{ + unsigned id, on_off; +#if DEBUG_SAPPHIRE_PANEL + printk(KERN_INFO "sapphire_mddi_client_power:%d\r\n", on); +#endif + if (on) { + on_off = 0; + id = PM_VREG_PDOWN_MDDI_ID; + msm_proc_comm(PCOM_VREG_PULLDOWN, &on_off, &id); + + gpio_set_value(SAPPHIRE_MDDI_1V5_EN, 1); + mdelay(5); /* delay time >5ms and <10ms */ + + if (is_12pin_camera()) + gpio_set_value(V_VDDE2E_VDD2_GPIO_5M, 1); + else + gpio_set_value(V_VDDE2E_VDD2_GPIO, 1); + + gpio_set_value(SAPPHIRE_GPIO_MDDI_32K_EN, 1); + msleep(3); + id = PM_VREG_PDOWN_AUX_ID; + msm_proc_comm(PCOM_VREG_PULLDOWN, &on_off, &id); + vreg_enable(vreg_lcm_2v85); + msleep(3); + } else { + gpio_set_value(SAPPHIRE_GPIO_MDDI_32K_EN, 0); + gpio_set_value(MDDI_RST_N, 0); + msleep(10); + vreg_disable(vreg_lcm_2v85); + on_off = 1; + id = PM_VREG_PDOWN_AUX_ID; + msm_proc_comm(PCOM_VREG_PULLDOWN, &on_off, &id); + msleep(5); + if (is_12pin_camera()) + gpio_set_value(V_VDDE2E_VDD2_GPIO_5M, 0); + else + gpio_set_value(V_VDDE2E_VDD2_GPIO, 0); + + msleep(200); + gpio_set_value(SAPPHIRE_MDDI_1V5_EN, 0); + id = PM_VREG_PDOWN_MDDI_ID; + msm_proc_comm(PCOM_VREG_PULLDOWN, &on_off, &id); + } +} + +static int sapphire_mddi_toshiba_client_init( + struct msm_mddi_bridge_platform_data *bridge_data, + struct msm_mddi_client_data *client_data) +{ + int panel_id; + + /* Set the MDDI_RST_N accroding to MDDI client repectively( + * been set in sapphire_mddi_power_client() originally) + */ + gpio_set_value(MDDI_RST_N, 1); + msleep(10); + + client_data->auto_hibernate(client_data, 0); + sapphire_process_mddi_table(client_data, mddi_toshiba_init_table, + ARRAY_SIZE(mddi_toshiba_init_table)); + client_data->auto_hibernate(client_data, 1); + g_panel_id = panel_id = + (client_data->remote_read(client_data, GPIODATA) >> 4) & 3; + if (panel_id > 1) { +#if DEBUG_SAPPHIRE_PANEL + printk(KERN_ERR "unknown panel id at mddi_enable\n"); +#endif + return -1; + } + return 0; +} + +static int sapphire_mddi_toshiba_client_uninit( + struct msm_mddi_bridge_platform_data *bridge_data, + struct msm_mddi_client_data *client_data) +{ + gpio_set_value(MDDI_RST_N, 0); + msleep(10); + + return 0; +} + +static int sapphire_mddi_panel_unblank( + struct msm_mddi_bridge_platform_data *bridge_data, + struct msm_mddi_client_data *client_data) +{ + int panel_id, ret = 0; + + sapphire_set_backlight_level(0); + client_data->auto_hibernate(client_data, 0); + sapphire_process_mddi_table(client_data, mddi_toshiba_panel_init_table, + ARRAY_SIZE(mddi_toshiba_panel_init_table)); + panel_id = (client_data->remote_read(client_data, GPIODATA) >> 4) & 3; + switch (panel_id) { + case 0: +#if DEBUG_SAPPHIRE_PANEL + printk(KERN_DEBUG "init sharp panel\n"); +#endif + sapphire_process_mddi_table(client_data, + mddi_sharp_init_table, + ARRAY_SIZE(mddi_sharp_init_table)); + break; + case 1: +#if DEBUG_SAPPHIRE_PANEL + printk(KERN_DEBUG "init tpo panel\n"); +#endif + sapphire_process_mddi_table(client_data, + mddi_tpo_init_table, + ARRAY_SIZE(mddi_tpo_init_table)); + break; + default: + + printk(KERN_DEBUG "unknown panel_id: %d\n", panel_id); + ret = -1; + }; + mutex_lock(&sapphire_backlight_lock); + sapphire_set_backlight_level(sapphire_backlight_brightness); + sapphire_backlight_off = 0; + mutex_unlock(&sapphire_backlight_lock); + client_data->auto_hibernate(client_data, 1); + /* reenable vsync */ + client_data->remote_write(client_data, GPIOSEL_VWAKEINT, + GPIOSEL); + client_data->remote_write(client_data, INTMASK_VWAKEOUT, + INTMASK); + return ret; + +} + +static int sapphire_mddi_panel_blank( + struct msm_mddi_bridge_platform_data *bridge_data, + struct msm_mddi_client_data *client_data) +{ + int panel_id, ret = 0; + + panel_id = (client_data->remote_read(client_data, GPIODATA) >> 4) & 3; + client_data->auto_hibernate(client_data, 0); + switch (panel_id) { + case 0: + printk(KERN_DEBUG "deinit sharp panel\n"); + sapphire_process_mddi_table(client_data, + mddi_sharp_deinit_table, + ARRAY_SIZE(mddi_sharp_deinit_table)); + break; + case 1: + printk(KERN_DEBUG "deinit tpo panel\n"); + sapphire_process_mddi_table(client_data, + mddi_tpo_deinit_table, + ARRAY_SIZE(mddi_tpo_deinit_table)); + break; + default: + printk(KERN_DEBUG "unknown panel_id: %d\n", panel_id); + ret = -1; + }; + client_data->auto_hibernate(client_data, 1); + mutex_lock(&sapphire_backlight_lock); + sapphire_set_backlight_level(0); + sapphire_backlight_off = 1; + mutex_unlock(&sapphire_backlight_lock); + client_data->remote_write(client_data, 0, SYSCLKENA); + client_data->remote_write(client_data, 1, DPSUS); + + return ret; +} + + +/* Initial sequence of sharp panel with Novatek NT35399 MDDI client */ +static const struct mddi_table sharp2_init_table[] = { + { 0x02A0, 0x00 }, + { 0x02A1, 0x00 }, + { 0x02A2, 0x3F }, + { 0x02A3, 0x01 }, + { 0x02B0, 0x00 }, + { 0x02B1, 0x00 }, + { 0x02B2, 0xDF }, + { 0x02B3, 0x01 }, + { 0x02D0, 0x00 }, + { 0x02D1, 0x00 }, + { 0x02D2, 0x00 }, + { 0x02D3, 0x00 }, + { 0x0350, 0x80 }, /* Set frame tearing effect(FTE) position */ + { 0x0351, 0x00 }, + { 0x0360, 0x30 }, + { 0x0361, 0xC1 }, + { 0x0362, 0x00 }, + { 0x0370, 0x00 }, + { 0x0371, 0xEF }, + { 0x0372, 0x01 }, + + { 0x0B00, 0x10 }, + + { 0x0B10, 0x00 }, + { 0x0B20, 0x22 }, + { 0x0B30, 0x46 }, + { 0x0B40, 0x07 }, + { 0x0B41, 0x1C }, + { 0x0B50, 0x0F }, + { 0x0B51, 0x7A }, + { 0x0B60, 0x16 }, + { 0x0B70, 0x0D }, + { 0x0B80, 0x04 }, + { 0x0B90, 0x07 }, + { 0x0BA0, 0x04 }, + { 0x0BA1, 0x86 }, + { 0x0BB0, 0xFF }, + { 0x0BB1, 0x01 }, + { 0x0BB2, 0xF7 }, + { 0x0BB3, 0x01 }, + { 0x0BC0, 0x00 }, + { 0x0BC1, 0x00 }, + { 0x0BC2, 0x00 }, + { 0x0BC3, 0x00 }, + { 0x0BE0, 0x01 }, + { 0x0BE1, 0x3F }, + + { 0x0BF0, 0x03 }, + + { 0x0C10, 0x02 }, + + { 0x0C30, 0x22 }, + { 0x0C31, 0x20 }, + { 0x0C40, 0x48 }, + { 0x0C41, 0x06 }, + + { 0xE00, 0x0028}, + { 0xE01, 0x002F}, + { 0xE02, 0x0032}, + { 0xE03, 0x000A}, + { 0xE04, 0x0023}, + { 0xE05, 0x0024}, + { 0xE06, 0x0022}, + { 0xE07, 0x0012}, + { 0xE08, 0x000D}, + { 0xE09, 0x0035}, + { 0xE0A, 0x000E}, + { 0xE0B, 0x001A}, + { 0xE0C, 0x003C}, + { 0xE0D, 0x003A}, + { 0xE0E, 0x0050}, + { 0xE0F, 0x0069}, + { 0xE10, 0x0006}, + { 0xE11, 0x001F}, + { 0xE12, 0x0035}, + { 0xE13, 0x0020}, + { 0xE14, 0x0043}, + { 0xE15, 0x0030}, + { 0xE16, 0x003C}, + { 0xE17, 0x0010}, + { 0xE18, 0x0009}, + { 0xE19, 0x0051}, + { 0xE1A, 0x001D}, + { 0xE1B, 0x003C}, + { 0xE1C, 0x0053}, + { 0xE1D, 0x0041}, + { 0xE1E, 0x0045}, + { 0xE1F, 0x004B}, + { 0xE20, 0x000A}, + { 0xE21, 0x0014}, + { 0xE22, 0x001C}, + { 0xE23, 0x0013}, + { 0xE24, 0x002E}, + { 0xE25, 0x0029}, + { 0xE26, 0x001B}, + { 0xE27, 0x0014}, + { 0xE28, 0x000E}, + { 0xE29, 0x0032}, + { 0xE2A, 0x000D}, + { 0xE2B, 0x001B}, + { 0xE2C, 0x0033}, + { 0xE2D, 0x0033}, + { 0xE2E, 0x005B}, + { 0xE2F, 0x0069}, + { 0xE30, 0x0006}, + { 0xE31, 0x0014}, + { 0xE32, 0x003D}, + { 0xE33, 0x0029}, + { 0xE34, 0x0042}, + { 0xE35, 0x0032}, + { 0xE36, 0x003F}, + { 0xE37, 0x000E}, + { 0xE38, 0x0008}, + { 0xE39, 0x0059}, + { 0xE3A, 0x0015}, + { 0xE3B, 0x002E}, + { 0xE3C, 0x0049}, + { 0xE3D, 0x0058}, + { 0xE3E, 0x0061}, + { 0xE3F, 0x006B}, + { 0xE40, 0x000A}, + { 0xE41, 0x001A}, + { 0xE42, 0x0022}, + { 0xE43, 0x0014}, + { 0xE44, 0x002F}, + { 0xE45, 0x002A}, + { 0xE46, 0x001A}, + { 0xE47, 0x0014}, + { 0xE48, 0x000E}, + { 0xE49, 0x002F}, + { 0xE4A, 0x000F}, + { 0xE4B, 0x001B}, + { 0xE4C, 0x0030}, + { 0xE4D, 0x002C}, + { 0xE4E, 0x0051}, + { 0xE4F, 0x0069}, + { 0xE50, 0x0006}, + { 0xE51, 0x001E}, + { 0xE52, 0x0043}, + { 0xE53, 0x002F}, + { 0xE54, 0x0043}, + { 0xE55, 0x0032}, + { 0xE56, 0x0043}, + { 0xE57, 0x000D}, + { 0xE58, 0x0008}, + { 0xE59, 0x0059}, + { 0xE5A, 0x0016}, + { 0xE5B, 0x0030}, + { 0xE5C, 0x004B}, + { 0xE5D, 0x0051}, + { 0xE5E, 0x005A}, + { 0xE5F, 0x006B}, + + { 0x0290, 0x01 }, +}; + +#undef TPO2_ONE_GAMMA +/* Initial sequence of TPO panel with Novatek NT35399 MDDI client */ + +static const struct mddi_table tpo2_init_table[] = { + /* Panel interface control */ + { 0xB30, 0x44 }, + { 0xB40, 0x00 }, + { 0xB41, 0x87 }, + { 0xB50, 0x06 }, + { 0xB51, 0x7B }, + { 0xB60, 0x0E }, + { 0xB70, 0x0F }, + { 0xB80, 0x03 }, + { 0xB90, 0x00 }, + { 0x350, 0x70 }, /* FTE is at line 0x70 */ + + /* Entry Mode */ + { 0x360, 0x30 }, + { 0x361, 0xC1 }, + { 0x362, 0x04 }, + +/* 0x2 for gray scale gamma correction, 0x12 for RGB gamma correction */ +#ifdef TPO2_ONE_GAMMA + { 0xB00, 0x02 }, +#else + { 0xB00, 0x12 }, +#endif + /* Driver output control */ + { 0x371, 0xEF }, + { 0x372, 0x03 }, + + /* DCDC on glass control */ + { 0xC31, 0x10 }, + { 0xBA0, 0x00 }, + { 0xBA1, 0x86 }, + + /* VCOMH voltage control */ + { 0xC50, 0x3b }, + + /* Special function control */ + { 0xC10, 0x82 }, + + /* Power control */ + { 0xC40, 0x44 }, + { 0xC41, 0x02 }, + + /* Source output control */ + { 0xBE0, 0x01 }, + { 0xBE1, 0x00 }, + + /* Windows address setting */ + { 0x2A0, 0x00 }, + { 0x2A1, 0x00 }, + { 0x2A2, 0x3F }, + { 0x2A3, 0x01 }, + { 0x2B0, 0x00 }, + { 0x2B1, 0x00 }, + { 0x2B2, 0xDF }, + { 0x2B3, 0x01 }, + + /* RAM address setting */ + { 0x2D0, 0x00 }, + { 0x2D1, 0x00 }, + { 0x2D2, 0x00 }, + { 0x2D3, 0x00 }, + + { 0xF20, 0x55 }, + { 0xF21, 0xAA }, + { 0xF22, 0x66 }, + { 0xF57, 0x45 }, + +/* + * The NT35399 provides gray or RGB gamma correction table, + * which determinated by register-0xb00, and following table + */ +#ifdef TPO2_ONE_GAMMA + /* Positive Gamma setting */ + { 0xE00, 0x04 }, + { 0xE01, 0x12 }, + { 0xE02, 0x18 }, + { 0xE03, 0x10 }, + { 0xE04, 0x29 }, + { 0xE05, 0x26 }, + { 0xE06, 0x1f }, + { 0xE07, 0x11 }, + { 0xE08, 0x0c }, + { 0xE09, 0x3a }, + { 0xE0A, 0x0d }, + { 0xE0B, 0x28 }, + { 0xE0C, 0x40 }, + { 0xE0D, 0x4e }, + { 0xE0E, 0x6f }, + { 0xE0F, 0x5E }, + + /* Negative Gamma setting */ + { 0xE10, 0x0B }, + { 0xE11, 0x00 }, + { 0xE12, 0x00 }, + { 0xE13, 0x1F }, + { 0xE14, 0x4b }, + { 0xE15, 0x33 }, + { 0xE16, 0x13 }, + { 0xE17, 0x12 }, + { 0xE18, 0x0d }, + { 0xE19, 0x2f }, + { 0xE1A, 0x16 }, + { 0xE1B, 0x2e }, + { 0xE1C, 0x49 }, + { 0xE1D, 0x41 }, + { 0xE1E, 0x46 }, + { 0xE1F, 0x55 }, +#else + /* Red Positive Gamma */ + { 0xE00, 0x0f }, + { 0xE01, 0x19 }, + { 0xE02, 0x22 }, + { 0xE03, 0x0b }, + { 0xE04, 0x23 }, + { 0xE05, 0x23 }, + { 0xE06, 0x14 }, + { 0xE07, 0x13 }, + { 0xE08, 0x0f }, + { 0xE09, 0x2a }, + { 0xE0A, 0x0d }, + { 0xE0B, 0x26 }, + { 0xE0C, 0x43 }, + { 0xE0D, 0x20 }, + { 0xE0E, 0x2a }, + { 0xE0F, 0x5c }, + + /* Red Negative Gamma */ + { 0xE10, 0x0d }, + { 0xE11, 0x45 }, + { 0xE12, 0x4c }, + { 0xE13, 0x1c }, + { 0xE14, 0x4d }, + { 0xE15, 0x33 }, + { 0xE16, 0x23 }, + { 0xE17, 0x0f }, + { 0xE18, 0x0b }, + { 0xE19, 0x3a }, + { 0xE1A, 0x19 }, + { 0xE1B, 0x32 }, + { 0xE1C, 0x4e }, + { 0xE1D, 0x37 }, + { 0xE1E, 0x38 }, + { 0xE1F, 0x3b }, + + /* Green Positive Gamma */ + { 0xE20, 0x00 }, + { 0xE21, 0x09 }, + { 0xE22, 0x10 }, + { 0xE23, 0x0f }, + { 0xE24, 0x29 }, + { 0xE25, 0x23 }, + { 0xE26, 0x0b }, + { 0xE27, 0x14 }, + { 0xE28, 0x12 }, + { 0xE29, 0x25 }, + { 0xE2A, 0x12 }, + { 0xE2B, 0x2f }, + { 0xE2C, 0x43 }, + { 0xE2D, 0x2d }, + { 0xE2E, 0x52 }, + { 0xE2F, 0x61 }, + + /* Green Negative Gamma */ + { 0xE30, 0x08 }, + { 0xE31, 0x1d }, + { 0xE32, 0x3f }, + { 0xE33, 0x1c }, + { 0xE34, 0x44 }, + { 0xE35, 0x2e }, + { 0xE36, 0x28 }, + { 0xE37, 0x0c }, + { 0xE38, 0x0a }, + { 0xE39, 0x42 }, + { 0xE3A, 0x17 }, + { 0xE3B, 0x30 }, + { 0xE3C, 0x4b }, + { 0xE3D, 0x3f }, + { 0xE3E, 0x43 }, + { 0xE3F, 0x45 }, + + /* Blue Positive Gamma */ + { 0xE40, 0x32 }, + { 0xE41, 0x32 }, + { 0xE42, 0x31 }, + { 0xE43, 0x06 }, + { 0xE44, 0x08 }, + { 0xE45, 0x0d }, + { 0xE46, 0x04 }, + { 0xE47, 0x14 }, + { 0xE48, 0x0f }, + { 0xE49, 0x1d }, + { 0xE4A, 0x1a }, + { 0xE4B, 0x39 }, + { 0xE4C, 0x4c }, + { 0xE4D, 0x1e }, + { 0xE4E, 0x43 }, + { 0xE4F, 0x61 }, + + /* Blue Negative Gamma */ + { 0xE50, 0x08 }, + { 0xE51, 0x2c }, + { 0xE52, 0x4e }, + { 0xE53, 0x13 }, + { 0xE54, 0x3a }, + { 0xE55, 0x26 }, + { 0xE56, 0x30 }, + { 0xE57, 0x0f }, + { 0xE58, 0x0a }, + { 0xE59, 0x49 }, + { 0xE5A, 0x34 }, + { 0xE5B, 0x4a }, + { 0xE5C, 0x53 }, + { 0xE5D, 0x28 }, + { 0xE5E, 0x26 }, + { 0xE5F, 0x27 }, + +#endif + /* Sleep in mode */ + { 0x110, 0x00 }, + { 0x1, 0x23 }, + /* Display on mode */ + { 0x290, 0x00 }, + { 0x1, 0x27 }, + /* Driver output control */ + { 0x372, 0x01 }, + { 0x1, 0x40 }, + /* Display on mode */ + { 0x290, 0x01 }, +}; + +static const struct mddi_table tpo2_display_on[] = { + { 0x290, 0x01 }, +}; + +static const struct mddi_table tpo2_display_off[] = { + { 0x110, 0x01 }, + { 0x290, 0x00 }, + { 0x1, 100 }, +}; + +static const struct mddi_table tpo2_power_off[] = { + { 0x0110, 0x01 }, +}; + +static int nt35399_detect_panel(struct msm_mddi_client_data *client_data) +{ + int id = -1, i ; + + /* If the MDDI client is failed to report the panel ID, + * perform retrial 5 times. + */ + for( i=0; i < 5; i++ ) { + client_data->remote_write(client_data, 0, 0x110); + msleep(5); + id = client_data->remote_read(client_data, userid) ; + if( id == 0 || id == 1 ) { + if(i==0) { + printk(KERN_ERR "%s: got valid panel ID=%d, " + "without retry\n", + __FUNCTION__, id); + } + else { + printk(KERN_ERR "%s: got valid panel ID=%d, " + "after %d retry\n", + __FUNCTION__, id, i+1); + } + break ; + } + printk(KERN_ERR "%s: got invalid panel ID:%d, trial #%d\n", + __FUNCTION__, id, i+1); + + gpio_set_value(MDDI_RST_N, 0); + msleep(5); + + gpio_set_value(MDDI_RST_N, 1); + msleep(10); + gpio_set_value(MDDI_RST_N, 0); + udelay(100); + gpio_set_value(MDDI_RST_N, 1); + mdelay(10); + } + printk(KERN_INFO "%s: final panel id=%d\n", __FUNCTION__, id); + + switch(id) { + case 0: + return SAPPHIRE_PANEL_TOPPOLY; + case 1: + return SAPPHIRE_PANEL_SHARP; + default : + printk(KERN_ERR "%s(): Invalid panel ID: %d, " + "treat as sharp panel.", __FUNCTION__, id); + return SAPPHIRE_PANEL_SHARP; + } +} + +static int nt35399_client_init( + struct msm_mddi_bridge_platform_data *bridge_data, + struct msm_mddi_client_data *client_data) +{ + int panel_id; + + if (g_panel_inited == 0) { + g_panel_id = panel_id = nt35399_detect_panel(client_data); + g_panel_inited = 1 ; + } else { + gpio_set_value(MDDI_RST_N, 1); + msleep(10); + gpio_set_value(MDDI_RST_N, 0); + udelay(100); + gpio_set_value(MDDI_RST_N, 1); + mdelay(10); + + g_panel_id = panel_id = nt35399_detect_panel(client_data); + if (panel_id == -1) { + printk("Invalid panel id\n"); + return -1; + } + + client_data->auto_hibernate(client_data, 0); + if (panel_id == SAPPHIRE_PANEL_TOPPOLY) { + sapphire_process_mddi_table(client_data, tpo2_init_table, + ARRAY_SIZE(tpo2_init_table)); + } else if(panel_id == SAPPHIRE_PANEL_SHARP) { + sapphire_process_mddi_table(client_data, sharp2_init_table, + ARRAY_SIZE(sharp2_init_table)); + } + + client_data->auto_hibernate(client_data, 1); + } + + return 0; +} + +static int nt35399_client_uninit( + struct msm_mddi_bridge_platform_data *bridge_data, + struct msm_mddi_client_data *cdata) +{ + return 0; +} + +static int nt35399_panel_unblank( + struct msm_mddi_bridge_platform_data *bridge_data, + struct msm_mddi_client_data *client_data) +{ + int ret = 0; + + mdelay(20); + sapphire_set_backlight_level(0); + client_data->auto_hibernate(client_data, 0); + + mutex_lock(&sapphire_backlight_lock); + sapphire_set_backlight_level(sapphire_backlight_brightness); + sapphire_backlight_off = 0; + mutex_unlock(&sapphire_backlight_lock); + + client_data->auto_hibernate(client_data, 1); + + return ret; +} + +static int nt35399_panel_blank( + struct msm_mddi_bridge_platform_data *bridge_data, + struct msm_mddi_client_data *client_data) +{ + int ret = 0; + + client_data->auto_hibernate(client_data, 0); + sapphire_process_mddi_table(client_data, tpo2_display_off, + ARRAY_SIZE(tpo2_display_off)); + client_data->auto_hibernate(client_data, 1); + + mutex_lock(&sapphire_backlight_lock); + sapphire_set_backlight_level(0); + sapphire_backlight_off = 1; + mutex_unlock(&sapphire_backlight_lock); + + return ret; +} + +static void sapphire_brightness_set(struct led_classdev *led_cdev, enum led_brightness value) +{ + mutex_lock(&sapphire_backlight_lock); + sapphire_backlight_brightness = value; + if (!sapphire_backlight_off) + sapphire_set_backlight_level(sapphire_backlight_brightness); + mutex_unlock(&sapphire_backlight_lock); +} + +static struct led_classdev sapphire_backlight_led = { + .name = "lcd-backlight", + .brightness = SAPPHIRE_DEFAULT_BACKLIGHT_BRIGHTNESS, + .brightness_set = sapphire_brightness_set, +}; + +static int sapphire_backlight_probe(struct platform_device *pdev) +{ + led_classdev_register(&pdev->dev, &sapphire_backlight_led); + return 0; +} + +static int sapphire_backlight_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&sapphire_backlight_led); + return 0; +} + +static struct platform_driver sapphire_backlight_driver = { + .probe = sapphire_backlight_probe, + .remove = sapphire_backlight_remove, + .driver = { + .name = "sapphire-backlight", + .owner = THIS_MODULE, + }, +}; + +static struct resource resources_msm_fb[] = { + { + .start = SMI64_MSM_FB_BASE, + .end = SMI64_MSM_FB_BASE + SMI64_MSM_FB_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_mddi_bridge_platform_data toshiba_client_data = { + .init = sapphire_mddi_toshiba_client_init, + .uninit = sapphire_mddi_toshiba_client_uninit, + .blank = sapphire_mddi_panel_blank, + .unblank = sapphire_mddi_panel_unblank, + .fb_data = { + .xres = 320, + .yres = 480, + .width = 45, + .height = 67, + .output_format = 0, + }, +}; + +#define NT35399_MFR_NAME 0x0bda +#define NT35399_PRODUCT_CODE 0x8a47 + +static void nt35399_fixup(uint16_t * mfr_name, uint16_t * product_code) +{ + printk(KERN_DEBUG "%s: enter.\n", __func__); + *mfr_name = NT35399_MFR_NAME ; + *product_code= NT35399_PRODUCT_CODE ; +} + +static struct msm_mddi_bridge_platform_data nt35399_client_data = { + + .init = nt35399_client_init, + .uninit = nt35399_client_uninit, + .blank = nt35399_panel_blank, + .unblank = nt35399_panel_unblank, + .fb_data = { + .xres = 320, + .yres = 480, + .output_format = 0, + }, +}; + +static struct msm_mddi_platform_data mddi_pdata = { + .clk_rate = 122880000, + .power_client = sapphire_mddi_power_client, + .fixup = nt35399_fixup, + .vsync_irq = MSM_GPIO_TO_INT(VSYNC_GPIO), + .fb_resource = resources_msm_fb, + .num_clients = 2, + .client_platform_data = { + { + .product_id = (0xd263 << 16 | 0), + .name = "mddi_c_d263_0000", + .id = 0, + .client_data = &toshiba_client_data, + .clk_rate = 0, + }, + { + .product_id = + (NT35399_MFR_NAME << 16 | NT35399_PRODUCT_CODE), + .name = "mddi_c_simple" , + .id = 0, + .client_data = &nt35399_client_data, + .clk_rate = 0, + }, + }, +}; + +static struct platform_device sapphire_backlight = { + .name = "sapphire-backlight", +}; + +int __init sapphire_init_panel(void) +{ + int rc = -1; + uint32_t config = PCOM_GPIO_CFG(27, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_8MA); /* GPIO27 */ + + if (!machine_is_sapphire()) + return 0; + + /* checking board as soon as possible */ + printk("sapphire_init_panel:machine_is_sapphire=%d, machine_arch_type=%d, MACH_TYPE_SAPPHIRE=%d\r\n", machine_is_sapphire(), machine_arch_type, MACH_TYPE_SAPPHIRE); + if (!machine_is_sapphire()) + return 0; + + vreg_lcm_2v85 = vreg_get(0, "gp4"); + if (IS_ERR(vreg_lcm_2v85)) + return PTR_ERR(vreg_lcm_2v85); + + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &config, 0); + + /* setup FB by SMI size */ + if (sapphire_get_smi_size() == 32) { + resources_msm_fb[0].start = SMI32_MSM_FB_BASE; + resources_msm_fb[0].end = SMI32_MSM_FB_BASE + SMI32_MSM_FB_SIZE - 1; + } + + rc = gpio_request(VSYNC_GPIO, "vsync"); + if (rc) + return rc; + rc = gpio_direction_input(VSYNC_GPIO); + if (rc) + return rc; + rc = platform_device_register(&msm_device_mdp); + if (rc) + return rc; + msm_device_mddi0.dev.platform_data = &mddi_pdata; + rc = platform_device_register(&msm_device_mddi0); + if (rc) + return rc; + platform_device_register(&sapphire_backlight); + return platform_driver_register(&sapphire_backlight_driver); +} + +device_initcall(sapphire_init_panel); diff --git a/arch/arm/mach-msm/board-sapphire-rfkill.c b/arch/arm/mach-msm/board-sapphire-rfkill.c new file mode 100644 index 00000000000..2fd6ea198e3 --- /dev/null +++ b/arch/arm/mach-msm/board-sapphire-rfkill.c @@ -0,0 +1,105 @@ +/* linux/arch/arm/mach-msm/board-sapphire-rfkill.c + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai + * + * 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. +*/ + +/* Control bluetooth power for sapphire platform */ + +#include +#include +#include +#include +#include +#include +#include +#include "gpio_chip.h" +#include "board-sapphire.h" + +static struct rfkill *bt_rfk; +static const char bt_name[] = "brf6300"; + +extern int sapphire_bt_fastclock_power(int on); + +static int bluetooth_set_power(void *data, bool blocked) +{ + if (!blocked) { + sapphire_bt_fastclock_power(1); + gpio_set_value(SAPPHIRE_GPIO_BT_32K_EN, 1); + udelay(10); + gpio_direction_output(101, 1); + } else { + gpio_direction_output(101, 0); + gpio_set_value(SAPPHIRE_GPIO_BT_32K_EN, 0); + sapphire_bt_fastclock_power(0); + } + return 0; +} + +static struct rfkill_ops sapphire_rfkill_ops = { + .set_block = bluetooth_set_power, +}; + +static int sapphire_rfkill_probe(struct platform_device *pdev) +{ + int rc = 0; + bool default_state = true; /* off */ + + bluetooth_set_power(NULL, default_state); + + bt_rfk = rfkill_alloc(bt_name, &pdev->dev, RFKILL_TYPE_BLUETOOTH, + &sapphire_rfkill_ops, NULL); + if (!bt_rfk) + return -ENOMEM; + + /* userspace cannot take exclusive control */ + + rfkill_set_states(bt_rfk, default_state, false); + + rc = rfkill_register(bt_rfk); + + if (rc) + rfkill_destroy(bt_rfk); + return rc; +} + +static int sapphire_rfkill_remove(struct platform_device *dev) +{ + rfkill_unregister(bt_rfk); + rfkill_destroy(bt_rfk); + + return 0; +} + +static struct platform_driver sapphire_rfkill_driver = { + .probe = sapphire_rfkill_probe, + .remove = sapphire_rfkill_remove, + .driver = { + .name = "sapphire_rfkill", + .owner = THIS_MODULE, + }, +}; + +static int __init sapphire_rfkill_init(void) +{ + return platform_driver_register(&sapphire_rfkill_driver); +} + +static void __exit sapphire_rfkill_exit(void) +{ + platform_driver_unregister(&sapphire_rfkill_driver); +} + +module_init(sapphire_rfkill_init); +module_exit(sapphire_rfkill_exit); +MODULE_DESCRIPTION("sapphire rfkill"); +MODULE_AUTHOR("Nick Pelly "); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/board-sapphire-wifi.c b/arch/arm/mach-msm/board-sapphire-wifi.c new file mode 100644 index 00000000000..43f827c60f1 --- /dev/null +++ b/arch/arm/mach-msm/board-sapphire-wifi.c @@ -0,0 +1,74 @@ +/* arch/arm/mach-msm/board-sapphire-wifi.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Dmitry Shmidt + * + * 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. + * + */ + +#ifdef CONFIG_WIFI_CONTROL_FUNC +#include +#include +#include +#include +#include +#include + +extern int sapphire_wifi_set_carddetect(int val); +extern int sapphire_wifi_power(int on); +extern int sapphire_wifi_reset(int on); + +#ifdef CONFIG_WIFI_MEM_PREALLOC +typedef struct wifi_mem_prealloc_struct { + void *mem_ptr; + unsigned long size; +} wifi_mem_prealloc_t; + +static wifi_mem_prealloc_t wifi_mem_array[WMPA_NUMBER_OF_SECTIONS] = { + { NULL, (WMPA_SECTION_SIZE_0 + WMPA_SECTION_HEADER) }, + { NULL, (WMPA_SECTION_SIZE_1 + WMPA_SECTION_HEADER) }, + { NULL, (WMPA_SECTION_SIZE_2 + WMPA_SECTION_HEADER) } +}; + +static void *sapphire_wifi_mem_prealloc(int section, unsigned long size) +{ + if ((section < 0) || (section >= WMPA_NUMBER_OF_SECTIONS)) + return NULL; + if (wifi_mem_array[section].size < size) + return NULL; + return wifi_mem_array[section].mem_ptr; +} + +int __init sapphire_init_wifi_mem (void) +{ + int i; + + for (i = 0; (i < WMPA_NUMBER_OF_SECTIONS); i++) { + wifi_mem_array[i].mem_ptr = vmalloc(wifi_mem_array[i].size); + if (wifi_mem_array[i].mem_ptr == NULL) + return -ENOMEM; + } + return 0; +} +#endif + +struct wifi_platform_data sapphire_wifi_control = { + .set_power = sapphire_wifi_power, + .set_reset = sapphire_wifi_reset, + .set_carddetect = sapphire_wifi_set_carddetect, +#ifdef CONFIG_WIFI_MEM_PREALLOC + .mem_prealloc = sapphire_wifi_mem_prealloc, +#else + .mem_prealloc = NULL, +#endif +}; + +#endif diff --git a/arch/arm/mach-msm/board-sapphire.h b/arch/arm/mach-msm/board-sapphire.h new file mode 100644 index 00000000000..d96760a25ef --- /dev/null +++ b/arch/arm/mach-msm/board-sapphire.h @@ -0,0 +1,224 @@ +/* linux/arch/arm/mach-msm/board-sapphire.h + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ +#ifndef __ARCH_ARM_MACH_MSM_BOARD_SAPPHIRE_H +#define __ARCH_ARM_MACH_MSM_BOARD_SAPPHIRE_H + +#include + +#define MSM_SMI_BASE 0x00000000 +#define MSM_SMI_SIZE 0x00800000 + +#define MSM_EBI_BASE 0x10000000 +#define MSM_EBI_SIZE 0x07100000 + +#define MSM_PMEM_GPU0_BASE 0x00000000 +#define MSM_PMEM_GPU0_SIZE 0x00700000 + +#define SMI64_MSM_PMEM_MDP_BASE 0x15900000 +#define SMI64_MSM_PMEM_MDP_SIZE 0x00800000 + +#define SMI64_MSM_PMEM_ADSP_BASE 0x16100000 +#define SMI64_MSM_PMEM_ADSP_SIZE 0x00800000 + +#define SMI64_MSM_PMEM_CAMERA_BASE 0x15400000 +#define SMI64_MSM_PMEM_CAMERA_SIZE 0x00500000 + +#define SMI64_MSM_FB_BASE 0x00700000 +#define SMI64_MSM_FB_SIZE 0x00100000 + +#define SMI64_MSM_LINUX_BASE MSM_EBI_BASE +#define SMI64_MSM_LINUX_SIZE 0x068e0000 + +#define SMI64_MSM_LINUX_BASE_1 0x02000000 +#define SMI64_MSM_LINUX_SIZE_1 0x02000000 + +#define SMI64_MSM_LINUX_BASE_2 MSM_EBI_BASE +#define SMI64_MSM_LINUX_SIZE_2 0x05400000 + +#define SMI32_MSM_LINUX_BASE MSM_EBI_BASE +#define SMI32_MSM_LINUX_SIZE 0x5400000 + +#define SMI32_MSM_PMEM_MDP_BASE SMI32_MSM_LINUX_BASE + SMI32_MSM_LINUX_SIZE +#define SMI32_MSM_PMEM_MDP_SIZE 0x800000 + +#define SMI32_MSM_PMEM_ADSP_BASE SMI32_MSM_PMEM_MDP_BASE + SMI32_MSM_PMEM_MDP_SIZE +#define SMI32_MSM_PMEM_ADSP_SIZE 0x800000 + +#define SMI32_MSM_FB_BASE SMI32_MSM_PMEM_ADSP_BASE + SMI32_MSM_PMEM_ADSP_SIZE +#define SMI32_MSM_FB_SIZE 0x9b000 + + +#define MSM_PMEM_GPU1_SIZE 0x800000 +#define MSM_PMEM_GPU1_BASE (MSM_RAM_CONSOLE_BASE + MSM_RAM_CONSOLE_SIZE) + +#define MSM_RAM_CONSOLE_BASE 0x169E0000 +#define MSM_RAM_CONSOLE_SIZE 128 * SZ_1K + +#if (SMI32_MSM_FB_BASE + SMI32_MSM_FB_SIZE) >= (MSM_PMEM_GPU1_BASE) +#error invalid memory map +#endif + +#if (SMI64_MSM_FB_BASE + SMI64_MSM_FB_SIZE) >= (MSM_PMEM_GPU1_BASE) +#error invalid memory map +#endif + +#define DECLARE_MSM_IOMAP +#include + +/* +** SOC GPIO +*/ +#define SAPPHIRE_BALL_UP_0 94 +#define SAPPHIRE_BALL_LEFT_0 18 +#define SAPPHIRE_BALL_DOWN_0 49 +#define SAPPHIRE_BALL_RIGHT_0 19 + +#define SAPPHIRE_POWER_KEY 20 +#define SAPPHIRE_VOLUME_UP 36 +#define SAPPHIRE_VOLUME_DOWN 39 + +#define SAPPHIRE_GPIO_PS_HOLD (25) +#define SAPPHIRE_MDDI_1V5_EN (28) +#define SAPPHIRE_BL_PWM (27) +#define SAPPHIRE_TP_LS_EN (1) +#define SAPPHIRE20_TP_LS_EN (88) + +/* H2W */ +#define SAPPHIRE_GPIO_CABLE_IN1 (83) +#define SAPPHIRE_GPIO_CABLE_IN2 (37) +#define SAPPHIRE_GPIO_UART3_RX (86) +#define SAPPHIRE_GPIO_UART3_TX (87) +#define SAPPHIRE_GPIO_H2W_DATA (86) +#define SAPPHIRE_GPIO_H2W_CLK (87) + +#define SAPPHIRE_GPIO_UART1_RTS (43) +#define SAPPHIRE_GPIO_UART1_CTS (44) + +/* +** CPLD GPIO +** +** Sapphire Altera CPLD can keep the registers value and +** doesn't need a shadow to backup. +**/ +#define SAPPHIRE_CPLD_BASE 0xFA000000 /* VA */ +#define SAPPHIRE_CPLD_START 0x98000000 /* PA */ +#define SAPPHIRE_CPLD_SIZE SZ_4K + +#define SAPPHIRE_GPIO_START (128) /* Pseudo GPIO number */ + +/* Sapphire has one INT BANK only. */ +#define SAPPHIRE_GPIO_INT_B0_MASK_REG (0x0c) /*INT3 MASK*/ +#define SAPPHIRE_GPIO_INT_B0_STAT_REG (0x0e) /*INT1 STATUS*/ + +/* LED control register */ +#define SAPPHIRE_CPLD_LED_BASE (SAPPHIRE_CPLD_BASE + 0x10) /* VA */ +#define SAPPHIRE_CPLD_LED_START (SAPPHIRE_CPLD_START + 0x10) /* PA */ +#define SAPPHIRE_CPLD_LED_SIZE 0x08 + +/* MISCn: GPO pin to Enable/Disable some functions. */ +#define SAPPHIRE_GPIO_MISC1_BASE (SAPPHIRE_GPIO_START + 0x00) +#define SAPPHIRE_GPIO_MISC2_BASE (SAPPHIRE_GPIO_START + 0x08) +#define SAPPHIRE_GPIO_MISC3_BASE (SAPPHIRE_GPIO_START + 0x10) +#define SAPPHIRE_GPIO_MISC4_BASE (SAPPHIRE_GPIO_START + 0x18) +#define SAPPHIRE_GPIO_MISC5_BASE (SAPPHIRE_GPIO_START + 0x20) + +/* INT BANK0: INT1: int status, INT2: int level, INT3: int Mask */ +#define SAPPHIRE_GPIO_INT_B0_BASE (SAPPHIRE_GPIO_START + 0x28) + +/* MISCn GPIO: */ +#define SAPPHIRE_GPIO_CPLD128_VER_0 (SAPPHIRE_GPIO_MISC1_BASE + 4) +#define SAPPHIRE_GPIO_CPLD128_VER_1 (SAPPHIRE_GPIO_MISC1_BASE + 5) +#define SAPPHIRE_GPIO_CPLD128_VER_2 (SAPPHIRE_GPIO_MISC1_BASE + 6) +#define SAPPHIRE_GPIO_CPLD128_VER_3 (SAPPHIRE_GPIO_MISC1_BASE + 7) + +#define SAPPHIRE_GPIO_H2W_DAT_DIR (SAPPHIRE_GPIO_MISC2_BASE + 2) +#define SAPPHIRE_GPIO_H2W_CLK_DIR (SAPPHIRE_GPIO_MISC2_BASE + 3) +#define SAPPHIRE_GPIO_H2W_SEL0 (SAPPHIRE_GPIO_MISC2_BASE + 6) +#define SAPPHIRE_GPIO_H2W_SEL1 (SAPPHIRE_GPIO_MISC2_BASE + 7) + +#define SAPPHIRE_GPIO_I2C_PULL (SAPPHIRE_GPIO_MISC3_BASE + 2) +#define SAPPHIRE_GPIO_TP_EN (SAPPHIRE_GPIO_MISC3_BASE + 4) +#define SAPPHIRE_GPIO_JOG_EN (SAPPHIRE_GPIO_MISC3_BASE + 5) +#define SAPPHIRE_GPIO_JOG_LED_EN (SAPPHIRE_GPIO_MISC3_BASE + 6) +#define SAPPHIRE_GPIO_APKEY_LED_EN (SAPPHIRE_GPIO_MISC3_BASE + 7) + +#define SAPPHIRE_GPIO_VCM_PWDN (SAPPHIRE_GPIO_MISC4_BASE + 0) +#define SAPPHIRE_GPIO_USB_H2W_SW (SAPPHIRE_GPIO_MISC4_BASE + 1) +#define SAPPHIRE_GPIO_COMPASS_RST_N (SAPPHIRE_GPIO_MISC4_BASE + 2) +#define SAPPHIRE_GPIO_USB_PHY_RST_N (SAPPHIRE_GPIO_MISC4_BASE + 5) +#define SAPPHIRE_GPIO_WIFI_PA_RESETX (SAPPHIRE_GPIO_MISC4_BASE + 6) +#define SAPPHIRE_GPIO_WIFI_EN (SAPPHIRE_GPIO_MISC4_BASE + 7) + +#define SAPPHIRE_GPIO_BT_32K_EN (SAPPHIRE_GPIO_MISC5_BASE + 0) +#define SAPPHIRE_GPIO_MAC_32K_EN (SAPPHIRE_GPIO_MISC5_BASE + 1) +#define SAPPHIRE_GPIO_MDDI_32K_EN (SAPPHIRE_GPIO_MISC5_BASE + 2) +#define SAPPHIRE_GPIO_COMPASS_32K_EN (SAPPHIRE_GPIO_MISC5_BASE + 3) + +/* INT STATUS/LEVEL/MASK : INT GPIO should be the last. */ +#define SAPPHIRE_GPIO_NAVI_ACT_N (SAPPHIRE_GPIO_INT_B0_BASE + 0) +#define SAPPHIRE_GPIO_COMPASS_IRQ (SAPPHIRE_GPIO_INT_B0_BASE + 1) +#define SAPPHIRE_GPIO_SEARCH_ACT_N (SAPPHIRE_GPIO_INT_B0_BASE + 2) +#define SAPPHIRE_GPIO_AUD_HSMIC_DET_N (SAPPHIRE_GPIO_INT_B0_BASE + 3) +#define SAPPHIRE_GPIO_SDMC_CD_N (SAPPHIRE_GPIO_INT_B0_BASE + 4) +#define SAPPHIRE_GPIO_CAM_BTN_STEP1_N (SAPPHIRE_GPIO_INT_B0_BASE + 5) +#define SAPPHIRE_GPIO_CAM_BTN_STEP2_N (SAPPHIRE_GPIO_INT_B0_BASE + 6) +#define SAPPHIRE_GPIO_TP_ATT_N (SAPPHIRE_GPIO_INT_B0_BASE + 7) + +#define SAPPHIRE_GPIO_END SAPPHIRE_GPIO_TP_ATT_N +#define SAPPHIRE_GPIO_LAST_INT (SAPPHIRE_GPIO_TP_ATT_N) + +/* Bit position in the CPLD MISCn by the CPLD GPIOn: only bit0-7 is used. */ +#define CPLD_GPIO_BIT_POS_MASK(n) (1U << ((n) & 7)) +#define CPLD_GPIO_REG_OFFSET(n) _g_CPLD_MISCn_Offset[((n)-SAPPHIRE_GPIO_START) >> 3] +#define CPLD_GPIO_REG(n) (CPLD_GPIO_REG_OFFSET(n) + SAPPHIRE_CPLD_BASE) + +/* +** CPLD INT Start +*/ +#define SAPPHIRE_INT_START (NR_MSM_IRQS + NR_GPIO_IRQS) /* pseudo number for CPLD INT */ +/* Using INT status/Bank0 for GPIO to INT */ +#define SAPPHIRE_GPIO_TO_INT(n) ((n-SAPPHIRE_GPIO_INT_B0_BASE) + SAPPHIRE_INT_START) +#define SAPPHIRE_INT_END (SAPPHIRE_GPIO_TO_INT(SAPPHIRE_GPIO_END)) + +/* get the INT reg by GPIO number */ +#define CPLD_INT_GPIO_TO_BANK(n) (((n)-SAPPHIRE_GPIO_INT_B0_BASE) >> 3) +#define CPLD_INT_STATUS_REG_OFFSET_G(n) _g_INT_BANK_Offset[CPLD_INT_GPIO_TO_BANK(n)][0] +#define CPLD_INT_LEVEL_REG_OFFSET_G(n) _g_INT_BANK_Offset[CPLD_INT_GPIO_TO_BANK(n)][1] +#define CPLD_INT_MASK_REG_OFFSET_G(n) _g_INT_BANK_Offset[CPLD_INT_GPIO_TO_BANK(n)][2] +#define CPLD_INT_STATUS_REG_G(n) (SAPPHIRE_CPLD_BASE + CPLD_INT_STATUS_REG_OFFSET_G(n)) +#define CPLD_INT_LEVEL_REG_G(n) (SAPPHIRE_CPLD_BASE + CPLD_INT_LEVEL_REG_OFFSET_G(n)) +#define CPLD_INT_MASK_REG_G(n) (SAPPHIRE_CPLD_BASE + CPLD_INT_MASK_REG_OFFSET_G(n)) + +/* get the INT reg by INT number */ +#define CPLD_INT_TO_BANK(i) ((i-SAPPHIRE_INT_START) >> 3) +#define CPLD_INT_STATUS_REG_OFFSET(i) _g_INT_BANK_Offset[CPLD_INT_TO_BANK(i)][0] +#define CPLD_INT_LEVEL_REG_OFFSET(i) _g_INT_BANK_Offset[CPLD_INT_TO_BANK(i)][1] +#define CPLD_INT_MASK_REG_OFFSET(i) _g_INT_BANK_Offset[CPLD_INT_TO_BANK(i)][2] +#define CPLD_INT_STATUS_REG(i) (SAPPHIRE_CPLD_BASE + CPLD_INT_STATUS_REG_OFFSET(i)) +#define CPLD_INT_LEVEL_REG(i) (SAPPHIRE_CPLD_BASE + CPLD_INT_LEVEL_REG_OFFSET(i)) +#define CPLD_INT_MASK_REG(i) (SAPPHIRE_CPLD_BASE + CPLD_INT_MASK_REG_OFFSET(i) ) + +/* return the bit mask by INT number */ +#define SAPPHIRE_INT_BIT_MASK(i) (1U << ((i - SAPPHIRE_INT_START) & 7)) + +void config_sapphire_camera_on_gpios(void); +void config_sapphire_camera_off_gpios(void); +int sapphire_get_smi_size(void); +unsigned int sapphire_get_hwid(void); +unsigned int sapphire_get_skuid(void); +unsigned int is_12pin_camera(void); +int sapphire_is_5M_camera(void); +int sapphire_gpio_write(struct gpio_chip *chip, unsigned n, unsigned on); + +#endif /* GUARD */ diff --git a/arch/arm/mach-msm/board-swordfish-keypad.c b/arch/arm/mach-msm/board-swordfish-keypad.c new file mode 100644 index 00000000000..f2c2f3962f6 --- /dev/null +++ b/arch/arm/mach-msm/board-swordfish-keypad.c @@ -0,0 +1,177 @@ +/* linux/arch/arm/mach-msm/board-swordfish-keypad.c + * + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#undef MODULE_PARAM_PREFIX +#define MODULE_PARAM_PREFIX "board_swordfish." +static int swordfish_ffa; +module_param_named(ffa, swordfish_ffa, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define SCAN_FUNCTION_KEYS 0 /* don't turn this on without updating the ffa support */ + +static unsigned int swordfish_row_gpios[] = { + 31, 32, 33, 34, 35, 41 +#if SCAN_FUNCTION_KEYS + , 42 +#endif +}; + +static unsigned int swordfish_col_gpios[] = { 36, 37, 38, 39, 40 }; + +/* FFA: + 36: KEYSENSE_N(0) + 37: KEYSENSE_N(1) + 38: KEYSENSE_N(2) + 39: KEYSENSE_N(3) + 40: KEYSENSE_N(4) + + 31: KYPD_17 + 32: KYPD_15 + 33: KYPD_13 + 34: KYPD_11 + 35: KYPD_9 + 41: KYPD_MEMO +*/ + +#define KEYMAP_INDEX(row, col) ((row)*ARRAY_SIZE(swordfish_col_gpios) + (col)) + +static const unsigned short swordfish_keymap[ARRAY_SIZE(swordfish_col_gpios) * ARRAY_SIZE(swordfish_row_gpios)] = { + [KEYMAP_INDEX(0, 0)] = KEY_5, + [KEYMAP_INDEX(0, 1)] = KEY_9, + [KEYMAP_INDEX(0, 2)] = 229, /* SOFT1 */ + [KEYMAP_INDEX(0, 3)] = KEY_6, + [KEYMAP_INDEX(0, 4)] = KEY_LEFT, + + [KEYMAP_INDEX(1, 0)] = KEY_0, + [KEYMAP_INDEX(1, 1)] = KEY_RIGHT, + [KEYMAP_INDEX(1, 2)] = KEY_1, + [KEYMAP_INDEX(1, 3)] = 228, /* KEY_SHARP */ + [KEYMAP_INDEX(1, 4)] = KEY_SEND, + + [KEYMAP_INDEX(2, 0)] = KEY_VOLUMEUP, + [KEYMAP_INDEX(2, 1)] = KEY_HOME, /* FA */ + [KEYMAP_INDEX(2, 2)] = KEY_F8, /* QCHT */ + [KEYMAP_INDEX(2, 3)] = KEY_F6, /* R+ */ + [KEYMAP_INDEX(2, 4)] = KEY_F7, /* R- */ + + [KEYMAP_INDEX(3, 0)] = KEY_UP, + [KEYMAP_INDEX(3, 1)] = KEY_CLEAR, + [KEYMAP_INDEX(3, 2)] = KEY_4, + [KEYMAP_INDEX(3, 3)] = KEY_MUTE, /* SPKR */ + [KEYMAP_INDEX(3, 4)] = KEY_2, + + [KEYMAP_INDEX(4, 0)] = 230, /* SOFT2 */ + [KEYMAP_INDEX(4, 1)] = 232, /* KEY_CENTER */ + [KEYMAP_INDEX(4, 2)] = KEY_DOWN, + [KEYMAP_INDEX(4, 3)] = KEY_BACK, /* FB */ + [KEYMAP_INDEX(4, 4)] = KEY_8, + + [KEYMAP_INDEX(5, 0)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(5, 1)] = 227, /* KEY_STAR */ + [KEYMAP_INDEX(5, 2)] = KEY_MAIL, /* MESG */ + [KEYMAP_INDEX(5, 3)] = KEY_3, + [KEYMAP_INDEX(5, 4)] = KEY_7, + +#if SCAN_FUNCTION_KEYS + [KEYMAP_INDEX(6, 0)] = KEY_F5, + [KEYMAP_INDEX(6, 1)] = KEY_F4, + [KEYMAP_INDEX(6, 2)] = KEY_F3, + [KEYMAP_INDEX(6, 3)] = KEY_F2, + [KEYMAP_INDEX(6, 4)] = KEY_F1 +#endif +}; + +static const unsigned short swordfish_keymap_ffa[ARRAY_SIZE(swordfish_col_gpios) * ARRAY_SIZE(swordfish_row_gpios)] = { + /*[KEYMAP_INDEX(0, 0)] = ,*/ + /*[KEYMAP_INDEX(0, 1)] = ,*/ + [KEYMAP_INDEX(0, 2)] = KEY_1, + [KEYMAP_INDEX(0, 3)] = KEY_SEND, + [KEYMAP_INDEX(0, 4)] = KEY_LEFT, + + [KEYMAP_INDEX(1, 0)] = KEY_3, + [KEYMAP_INDEX(1, 1)] = KEY_RIGHT, + [KEYMAP_INDEX(1, 2)] = KEY_VOLUMEUP, + /*[KEYMAP_INDEX(1, 3)] = ,*/ + [KEYMAP_INDEX(1, 4)] = KEY_6, + + [KEYMAP_INDEX(2, 0)] = KEY_HOME, /* A */ + [KEYMAP_INDEX(2, 1)] = KEY_BACK, /* B */ + [KEYMAP_INDEX(2, 2)] = KEY_0, + [KEYMAP_INDEX(2, 3)] = 228, /* KEY_SHARP */ + [KEYMAP_INDEX(2, 4)] = KEY_9, + + [KEYMAP_INDEX(3, 0)] = KEY_UP, + [KEYMAP_INDEX(3, 1)] = 232, /* KEY_CENTER */ /* i */ + [KEYMAP_INDEX(3, 2)] = KEY_4, + /*[KEYMAP_INDEX(3, 3)] = ,*/ + [KEYMAP_INDEX(3, 4)] = KEY_2, + + [KEYMAP_INDEX(4, 0)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(4, 1)] = KEY_SOUND, + [KEYMAP_INDEX(4, 2)] = KEY_DOWN, + [KEYMAP_INDEX(4, 3)] = KEY_8, + [KEYMAP_INDEX(4, 4)] = KEY_5, + + /*[KEYMAP_INDEX(5, 0)] = ,*/ + [KEYMAP_INDEX(5, 1)] = 227, /* KEY_STAR */ + [KEYMAP_INDEX(5, 2)] = 230, /*SOFT2*/ /* 2 */ + [KEYMAP_INDEX(5, 3)] = KEY_MENU, /* 1 */ + [KEYMAP_INDEX(5, 4)] = KEY_7, +}; + +static struct gpio_event_matrix_info swordfish_matrix_info = { + .info.func = gpio_event_matrix_func, + .keymap = swordfish_keymap, + .output_gpios = swordfish_row_gpios, + .input_gpios = swordfish_col_gpios, + .noutputs = ARRAY_SIZE(swordfish_row_gpios), + .ninputs = ARRAY_SIZE(swordfish_col_gpios), + .settle_time.tv.nsec = 0, + .poll_time.tv.nsec = 20 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_DRIVE_INACTIVE | GPIOKPF_PRINT_UNMAPPED_KEYS /*| GPIOKPF_PRINT_MAPPED_KEYS*/ +}; + +struct gpio_event_info *swordfish_keypad_info[] = { + &swordfish_matrix_info.info +}; + +static struct gpio_event_platform_data swordfish_keypad_data = { + .name = "swordfish_keypad", + .info = swordfish_keypad_info, + .info_count = ARRAY_SIZE(swordfish_keypad_info) +}; + +static struct platform_device swordfish_keypad_device = { + .name = GPIO_EVENT_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &swordfish_keypad_data, + }, +}; + +static int __init swordfish_init_keypad(void) +{ + if (!machine_is_swordfish()) + return 0; + if (swordfish_ffa) + swordfish_matrix_info.keymap = swordfish_keymap_ffa; + return platform_device_register(&swordfish_keypad_device); +} + +device_initcall(swordfish_init_keypad); diff --git a/arch/arm/mach-msm/board-swordfish-mmc.c b/arch/arm/mach-msm/board-swordfish-mmc.c new file mode 100644 index 00000000000..bb0b1734b60 --- /dev/null +++ b/arch/arm/mach-msm/board-swordfish-mmc.c @@ -0,0 +1,263 @@ +/* linux/arch/arm/mach-msm/board-swordfish-mmc.c + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "proc_comm.h" +#include "devices.h" + +#define FPGA_BASE 0x70000000 +#define FPGA_SDIO_STATUS 0x280 + +static void __iomem *fpga_base; + +#define DEBUG_SWORDFISH_MMC 1 + +extern int msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat, + unsigned int stat_irq, unsigned long stat_irq_flags); + +static int config_gpio_table(unsigned *table, int len, int enable) +{ + int n; + int rc = 0; + + for (n = 0; n < len; n++) { + unsigned dis = !enable; + unsigned id = table[n]; + + if (msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, &dis)) { + pr_err("%s: id=0x%08x dis=%d\n", __func__, table[n], + dis); + rc = -1; + } + } + + return rc; +} + +static unsigned sdc1_gpio_table[] = { + PCOM_GPIO_CFG(51, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(52, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(53, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(54, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(55, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(56, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), +}; + +static unsigned sdc2_gpio_table[] = { + PCOM_GPIO_CFG(62, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), + PCOM_GPIO_CFG(63, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(64, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(65, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(66, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(67, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_4MA), +}; + +static unsigned sdc3_gpio_table[] = { + PCOM_GPIO_CFG(88, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), + PCOM_GPIO_CFG(89, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(90, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(91, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(92, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(93, 1, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), +}; + +static unsigned sdc4_gpio_table[] = { + PCOM_GPIO_CFG(142, 3, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_4MA), + PCOM_GPIO_CFG(143, 3, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(144, 2, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(145, 2, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(146, 3, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), + PCOM_GPIO_CFG(147, 3, GPIO_OUTPUT, GPIO_PULL_UP, GPIO_8MA), +}; + +struct sdc_info { + unsigned *table; + unsigned len; +}; + +static struct sdc_info sdcc_gpio_tables[] = { + [0] = { + .table = sdc1_gpio_table, + .len = ARRAY_SIZE(sdc1_gpio_table), + }, + [1] = { + .table = sdc2_gpio_table, + .len = ARRAY_SIZE(sdc2_gpio_table), + }, + [2] = { + .table = sdc3_gpio_table, + .len = ARRAY_SIZE(sdc3_gpio_table), + }, + [3] = { + .table = sdc4_gpio_table, + .len = ARRAY_SIZE(sdc4_gpio_table), + }, +}; + +static int swordfish_sdcc_setup_gpio(int dev_id, unsigned enable) +{ + struct sdc_info *info; + + if (dev_id < 1 || dev_id > 4) + return -1; + + info = &sdcc_gpio_tables[dev_id - 1]; + return config_gpio_table(info->table, info->len, enable); +} + +struct mmc_vdd_xlat { + int mask; + int level; +}; + +static struct mmc_vdd_xlat mmc_vdd_table[] = { + { MMC_VDD_165_195, 1800 }, + { MMC_VDD_20_21, 2050 }, + { MMC_VDD_21_22, 2150 }, + { MMC_VDD_22_23, 2250 }, + { MMC_VDD_23_24, 2350 }, + { MMC_VDD_24_25, 2450 }, + { MMC_VDD_25_26, 2550 }, + { MMC_VDD_26_27, 2650 }, + { MMC_VDD_27_28, 2750 }, + { MMC_VDD_28_29, 2850 }, + { MMC_VDD_29_30, 2950 }, +}; + +static struct vreg *vreg_sdcc; +static unsigned int vreg_sdcc_enabled; +static unsigned int sdcc_vdd = 0xffffffff; + +static uint32_t sdcc_translate_vdd(struct device *dev, unsigned int vdd) +{ + int i; + int rc = 0; + struct platform_device *pdev; + + pdev = container_of(dev, struct platform_device, dev); + BUG_ON(!vreg_sdcc); + + if (vdd == sdcc_vdd) + return 0; + + sdcc_vdd = vdd; + + /* enable/disable the signals to the slot */ + swordfish_sdcc_setup_gpio(pdev->id, !!vdd); + + /* power down */ + if (vdd == 0) { +#if DEBUG_SWORDFISH_MMC + pr_info("%s: disable sdcc power\n", __func__); +#endif + vreg_disable(vreg_sdcc); + vreg_sdcc_enabled = 0; + return 0; + } + + if (!vreg_sdcc_enabled) { + rc = vreg_enable(vreg_sdcc); + if (rc) + pr_err("%s: Error enabling vreg (%d)\n", __func__, rc); + vreg_sdcc_enabled = 1; + } + + for (i = 0; i < ARRAY_SIZE(mmc_vdd_table); i++) { + if (mmc_vdd_table[i].mask != (1 << vdd)) + continue; +#if DEBUG_SWORDFISH_MMC + pr_info("%s: Setting level to %u\n", __func__, + mmc_vdd_table[i].level); +#endif + rc = vreg_set_level(vreg_sdcc, mmc_vdd_table[i].level); + if (rc) + pr_err("%s: Error setting vreg level (%d)\n", __func__, rc); + return 0; + } + + pr_err("%s: Invalid VDD %d specified\n", __func__, vdd); + return 0; +} + +static unsigned int swordfish_sdcc_slot_status (struct device *dev) +{ + struct platform_device *pdev; + uint32_t sdcc_stat; + + pdev = container_of(dev, struct platform_device, dev); + + sdcc_stat = readl(fpga_base + FPGA_SDIO_STATUS); + + /* bit 0 - sdcc1 crd_det + * bit 1 - sdcc1 wr_prt + * bit 2 - sdcc2 crd_det + * bit 3 - sdcc2 wr_prt + * etc... + */ + + /* crd_det is active low */ + return !(sdcc_stat & (1 << ((pdev->id - 1) << 1))); +} + +#define SWORDFISH_MMC_VDD (MMC_VDD_165_195 | MMC_VDD_20_21 | MMC_VDD_21_22 \ + | MMC_VDD_22_23 | MMC_VDD_23_24 | MMC_VDD_24_25 \ + | MMC_VDD_25_26 | MMC_VDD_26_27 | MMC_VDD_27_28 \ + | MMC_VDD_28_29 | MMC_VDD_29_30) + +static struct mmc_platform_data swordfish_sdcc_data = { + .ocr_mask = SWORDFISH_MMC_VDD/*MMC_VDD_27_28 | MMC_VDD_28_29*/, + .status = swordfish_sdcc_slot_status, + .translate_vdd = sdcc_translate_vdd, +}; + +int __init swordfish_init_mmc(void) +{ + vreg_sdcc_enabled = 0; + vreg_sdcc = vreg_get(NULL, "gp5"); + if (IS_ERR(vreg_sdcc)) { + pr_err("%s: vreg get failed (%ld)\n", + __func__, PTR_ERR(vreg_sdcc)); + return PTR_ERR(vreg_sdcc); + } + + fpga_base = ioremap(FPGA_BASE, SZ_4K); + if (!fpga_base) { + pr_err("%s: Can't ioremap FPGA base address (0x%08x)\n", + __func__, FPGA_BASE); + vreg_put(vreg_sdcc); + return -EIO; + } + + msm_add_sdcc(1, &swordfish_sdcc_data, 0, 0); + msm_add_sdcc(4, &swordfish_sdcc_data, 0, 0); + + return 0; +} + diff --git a/arch/arm/mach-msm/board-swordfish-panel.c b/arch/arm/mach-msm/board-swordfish-panel.c new file mode 100644 index 00000000000..cf5f3f62b76 --- /dev/null +++ b/arch/arm/mach-msm/board-swordfish-panel.c @@ -0,0 +1,116 @@ +/* linux/arch/arm/mach-msm/board-swordfish-panel.c + * + * Copyright (c) 2009 Google Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Author: Dima Zavin + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include "board-swordfish.h" +#include "devices.h" + +#define CLK_NS_TO_RATE(ns) (1000000000UL / (ns)) + +int swordfish_panel_blank(struct msm_lcdc_panel_ops *ops) +{ + /* TODO: Turn backlight off? */ + return 0; +} + +int swordfish_panel_unblank(struct msm_lcdc_panel_ops *ops) +{ + /* TODO: Turn backlight on? */ + return 0; +} + +int swordfish_panel_init(struct msm_lcdc_panel_ops *ops) +{ + return 0; +} + +static struct resource resources_msm_fb[] = { + { + .start = MSM_FB_BASE, + .end = MSM_FB_BASE + MSM_FB_SIZE, + .flags = IORESOURCE_MEM, + }, +}; + +static struct msm_lcdc_timing swordfish_lcdc_timing = { + .clk_rate = CLK_NS_TO_RATE(26), + .hsync_pulse_width = 60, + .hsync_back_porch = 81, + .hsync_front_porch = 81, + .hsync_skew = 0, + .vsync_pulse_width = 2, + .vsync_back_porch = 20, + .vsync_front_porch = 27, + .vsync_act_low = 0, + .hsync_act_low = 0, + .den_act_low = 0, +}; + +static struct msm_fb_data swordfish_lcdc_fb_data = { + .xres = 800, + .yres = 480, + .width = 94, + .height = 57, + .output_format = 0, +}; + +static struct msm_lcdc_panel_ops swordfish_lcdc_panel_ops = { + .init = swordfish_panel_init, + .blank = swordfish_panel_blank, + .unblank = swordfish_panel_unblank, +}; + +static struct msm_lcdc_platform_data swordfish_lcdc_platform_data = { + .panel_ops = &swordfish_lcdc_panel_ops, + .timing = &swordfish_lcdc_timing, + .fb_id = 0, + .fb_data = &swordfish_lcdc_fb_data, + .fb_resource = &resources_msm_fb[0], +}; + +static struct platform_device swordfish_lcdc_device = { + .name = "msm_mdp_lcdc", + .id = -1, + .dev = { + .platform_data = &swordfish_lcdc_platform_data, + }, +}; + +int __init swordfish_init_panel(void) +{ + int rc; + if (!machine_is_swordfish()) + return 0; + + if ((rc = platform_device_register(&msm_device_mdp)) != 0) + return rc; + + if ((rc = platform_device_register(&swordfish_lcdc_device)) != 0) + return rc; + + return 0; +} + +device_initcall(swordfish_init_panel); diff --git a/arch/arm/mach-msm/board-swordfish.c b/arch/arm/mach-msm/board-swordfish.c new file mode 100644 index 00000000000..5888e23f288 --- /dev/null +++ b/arch/arm/mach-msm/board-swordfish.c @@ -0,0 +1,366 @@ +/* linux/arch/arm/mach-msm/board-swordfish.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "board-swordfish.h" +#include "devices.h" +#include "proc_comm.h" + +extern int swordfish_init_mmc(void); + +static struct resource smc91x_resources[] = { + [0] = { + .start = 0x70000300, + .end = 0x70000400, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = MSM_GPIO_TO_INT(156), + .end = MSM_GPIO_TO_INT(156), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device smc91x_device = { + .name = "smc91x", + .id = 0, + .num_resources = ARRAY_SIZE(smc91x_resources), + .resource = smc91x_resources, +}; + +static int swordfish_phy_init_seq[] = { + 0x0C, 0x31, + 0x1D, 0x0D, + 0x1D, 0x10, + -1 +}; + +static void swordfish_usb_phy_reset(void) +{ + u32 id; + int ret; + + id = PCOM_CLKRGM_APPS_RESET_USB_PHY; + ret = msm_proc_comm(PCOM_CLK_REGIME_SEC_RESET_ASSERT, &id, NULL); + if (ret) { + pr_err("%s: Cannot assert (%d)\n", __func__, ret); + return; + } + + msleep(1); + + id = PCOM_CLKRGM_APPS_RESET_USB_PHY; + ret = msm_proc_comm(PCOM_CLK_REGIME_SEC_RESET_DEASSERT, &id, NULL); + if (ret) { + pr_err("%s: Cannot assert (%d)\n", __func__, ret); + return; + } +} + +static void swordfish_usb_hw_reset(bool enable) +{ + u32 id; + int ret; + u32 func; + + id = PCOM_CLKRGM_APPS_RESET_USBH; + if (enable) + func = PCOM_CLK_REGIME_SEC_RESET_ASSERT; + else + func = PCOM_CLK_REGIME_SEC_RESET_DEASSERT; + ret = msm_proc_comm(func, &id, NULL); + if (ret) + pr_err("%s: Cannot set reset to %d (%d)\n", __func__, enable, + ret); +} + + +static struct msm_hsusb_platform_data msm_hsusb_pdata = { + .phy_init_seq = swordfish_phy_init_seq, + .phy_reset = swordfish_usb_phy_reset, + .hw_reset = swordfish_usb_hw_reset, +}; + +static struct usb_mass_storage_platform_data mass_storage_pdata = { + .nluns = 1, + .vendor = "Qualcomm", + .product = "Swordfish", + .release = 0x0100, +}; + +static struct platform_device usb_mass_storage_device = { + .name = "usb_mass_storage", + .id = -1, + .dev = { + .platform_data = &mass_storage_pdata, + }, +}; + +static struct resource msm_kgsl_resources[] = { + { + .name = "kgsl_reg_memory", + .start = MSM_GPU_REG_PHYS, + .end = MSM_GPU_REG_PHYS + MSM_GPU_REG_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "kgsl_phys_memory", + .start = MSM_GPU_MEM_BASE, + .end = MSM_GPU_MEM_BASE + MSM_GPU_MEM_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_GRAPHICS, + .end = INT_GRAPHICS, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_kgsl_device = { + .name = "kgsl", + .id = -1, + .resource = msm_kgsl_resources, + .num_resources = ARRAY_SIZE(msm_kgsl_resources), +}; + +static struct android_pmem_platform_data mdp_pmem_pdata = { + .name = "pmem", + .start = MSM_PMEM_MDP_BASE, + .size = MSM_PMEM_MDP_SIZE, + .no_allocator = 0, + .cached = 1, +}; + +static struct android_pmem_platform_data android_pmem_gpu0_pdata = { + .name = "pmem_gpu0", + .start = MSM_PMEM_GPU0_BASE, + .size = MSM_PMEM_GPU0_SIZE, + .no_allocator = 0, + .cached = 0, +}; + +static struct android_pmem_platform_data android_pmem_gpu1_pdata = { + .name = "pmem_gpu1", + .start = MSM_PMEM_GPU1_BASE, + .size = MSM_PMEM_GPU1_SIZE, + .no_allocator = 0, + .cached = 0, +}; + +static struct android_pmem_platform_data android_pmem_adsp_pdata = { + .name = "pmem_adsp", + .start = MSM_PMEM_ADSP_BASE, + .size = MSM_PMEM_ADSP_SIZE, + .no_allocator = 0, + .cached = 0, +}; + +static struct platform_device android_pmem_mdp_device = { + .name = "android_pmem", + .id = 0, + .dev = { + .platform_data = &mdp_pmem_pdata + }, +}; + +static struct platform_device android_pmem_adsp_device = { + .name = "android_pmem", + .id = 1, + .dev = { + .platform_data = &android_pmem_adsp_pdata, + }, +}; + +static struct platform_device android_pmem_gpu0_device = { + .name = "android_pmem", + .id = 2, + .dev = { + .platform_data = &android_pmem_gpu0_pdata, + }, +}; + +static struct platform_device android_pmem_gpu1_device = { + .name = "android_pmem", + .id = 3, + .dev = { + .platform_data = &android_pmem_gpu1_pdata, + }, +}; + +static char *usb_functions[] = { "usb_mass_storage" }; +static char *usb_functions_adb[] = { "usb_mass_storage", "adb" }; + +static struct android_usb_product usb_products[] = { + { + .product_id = 0x0c01, + .num_functions = ARRAY_SIZE(usb_functions), + .functions = usb_functions, + }, + { + .product_id = 0x0c02, + .num_functions = ARRAY_SIZE(usb_functions_adb), + .functions = usb_functions_adb, + }, +}; + +static struct android_usb_platform_data android_usb_pdata = { + .vendor_id = 0x18d1, + .product_id = 0x0d01, + .version = 0x0100, + .serial_number = "42", + .product_name = "Swordfishdroid", + .manufacturer_name = "Qualcomm", + .num_products = ARRAY_SIZE(usb_products), + .products = usb_products, + .num_functions = ARRAY_SIZE(usb_functions_adb), + .functions = usb_functions_adb, +}; + +static struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &android_usb_pdata, + }, +}; + +static struct platform_device fish_battery_device = { + .name = "fish_battery", +}; + +static struct msm_ts_platform_data swordfish_ts_pdata = { + .min_x = 296, + .max_x = 3800, + .min_y = 296, + .max_y = 3800, + .min_press = 0, + .max_press = 256, + .inv_x = 4096, + .inv_y = 4096, +}; + +static struct platform_device *devices[] __initdata = { +#if !defined(CONFIG_MSM_SERIAL_DEBUGGER) + &msm_device_uart3, +#endif + &msm_device_smd, + &msm_device_dmov, + &msm_device_nand, + &msm_device_hsusb, + &usb_mass_storage_device, + &android_usb_device, + &fish_battery_device, + &smc91x_device, + &msm_device_touchscreen, + &android_pmem_mdp_device, + &android_pmem_adsp_device, + &android_pmem_gpu0_device, + &android_pmem_gpu1_device, + &msm_kgsl_device, +}; + +extern struct sys_timer msm_timer; + +static struct msm_acpu_clock_platform_data swordfish_clock_data = { + .acpu_switch_time_us = 20, + .max_speed_delta_khz = 256000, + .vdd_switch_time_us = 62, + .power_collapse_khz = 128000000, + .wait_for_irq_khz = 128000000, +}; + +void msm_serial_debug_init(unsigned int base, int irq, + struct device *clk_device, int signal_irq); + +static void __init swordfish_init(void) +{ + int rc; + + msm_acpu_clock_init(&swordfish_clock_data); +#if defined(CONFIG_MSM_SERIAL_DEBUGGER) + msm_serial_debug_init(MSM_UART3_PHYS, INT_UART3, + &msm_device_uart3.dev, 1); +#endif + msm_device_hsusb.dev.platform_data = &msm_hsusb_pdata; + msm_device_touchscreen.dev.platform_data = &swordfish_ts_pdata; + platform_add_devices(devices, ARRAY_SIZE(devices)); + msm_hsusb_set_vbus_state(1); + rc = swordfish_init_mmc(); + if (rc) + pr_crit("%s: MMC init failure (%d)\n", __func__, rc); +} + +static void __init swordfish_fixup(struct machine_desc *desc, struct tag *tags, + char **cmdline, struct meminfo *mi) +{ + mi->nr_banks = 1; + mi->bank[0].start = PHYS_OFFSET; + mi->bank[0].node = PHYS_TO_NID(PHYS_OFFSET); + mi->bank[0].size = (101*1024*1024); +} + +static void __init swordfish_map_io(void) +{ + msm_map_qsd8x50_io(); + msm_clock_init(msm_clocks_8x50, msm_num_clocks_8x50); +} + +MACHINE_START(SWORDFISH, "Swordfish Board (QCT SURF8250)") +#ifdef CONFIG_MSM_DEBUG_UART + .phys_io = MSM_DEBUG_UART_PHYS, + .io_pg_offst = ((MSM_DEBUG_UART_BASE) >> 18) & 0xfffc, +#endif + .boot_params = 0x20000100, + .fixup = swordfish_fixup, + .map_io = swordfish_map_io, + .init_irq = msm_init_irq, + .init_machine = swordfish_init, + .timer = &msm_timer, +MACHINE_END + +MACHINE_START(QSD8X50_FFA, "qsd8x50 FFA Board (QCT FFA8250)") +#ifdef CONFIG_MSM_DEBUG_UART + .phys_io = MSM_DEBUG_UART_PHYS, + .io_pg_offst = ((MSM_DEBUG_UART_BASE) >> 18) & 0xfffc, +#endif + .boot_params = 0x20000100, + .fixup = swordfish_fixup, + .map_io = swordfish_map_io, + .init_irq = msm_init_irq, + .init_machine = swordfish_init, + .timer = &msm_timer, +MACHINE_END diff --git a/arch/arm/mach-msm/board-swordfish.h b/arch/arm/mach-msm/board-swordfish.h new file mode 100644 index 00000000000..b9ea54f680d --- /dev/null +++ b/arch/arm/mach-msm/board-swordfish.h @@ -0,0 +1,48 @@ +/* arch/arm/mach-msm/board-swordfish.h + * + * Copyright (C) 2009 Google Inc. + * Author: Dima Zavin + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +#ifndef __ARCH_ARM_MACH_MSM_BOARD_SWORDFISH_H +#define __ARCH_ARM_MACH_MSM_BOARD_SWORDFISH_H + +#include + +#define MSM_SMI_BASE 0x02B00000 +#define MSM_SMI_SIZE 0x01500000 + +#define MSM_PMEM_MDP_BASE 0x03000000 +#define MSM_PMEM_MDP_SIZE 0x01000000 + +#define MSM_EBI1_BASE 0x20000000 +#define MSM_EBI1_SIZE 0x0E000000 + +#define MSM_PMEM_ADSP_BASE 0x2A300000 +#define MSM_PMEM_ADSP_SIZE 0x02000000 + +#define MSM_PMEM_GPU1_BASE 0x2C300000 +#define MSM_PMEM_GPU1_SIZE 0x01400000 + +#define MSM_PMEM_GPU0_BASE 0x2D700000 +#define MSM_PMEM_GPU0_SIZE 0x00400000 + +#define MSM_GPU_MEM_BASE 0x2DB00000 +#define MSM_GPU_MEM_SIZE 0x00200000 + +#define MSM_RAM_CONSOLE_BASE 0x2DD00000 +#define MSM_RAM_CONSOLE_SIZE 0x00040000 + +#define MSM_FB_BASE 0x2DE00000 +#define MSM_FB_SIZE 0x00200000 + +#endif /* __ARCH_ARM_MACH_MSM_BOARD_SWORDFISH_H */ diff --git a/arch/arm/mach-msm/board-trout-keypad.c b/arch/arm/mach-msm/board-trout-keypad.c new file mode 100644 index 00000000000..0299d0686de --- /dev/null +++ b/arch/arm/mach-msm/board-trout-keypad.c @@ -0,0 +1,345 @@ +/* arch/arm/mach-msm/board-trout-keypad.c + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "board-trout.h" + +static char *keycaps = "--qwerty"; +#undef MODULE_PARAM_PREFIX +#define MODULE_PARAM_PREFIX "board_trout." +module_param_named(keycaps, keycaps, charp, 0); + + +static unsigned int trout_col_gpios[] = { 35, 34, 33, 32, 31, 23, 30, 78 }; +static unsigned int trout_row_gpios[] = { 42, 41, 40, 39, 38, 37, 36 }; + +#define KEYMAP_INDEX(col, row) ((col)*ARRAY_SIZE(trout_row_gpios) + (row)) + +static const unsigned short trout_keymap[ARRAY_SIZE(trout_col_gpios) * ARRAY_SIZE(trout_row_gpios)] = { + [KEYMAP_INDEX(0, 0)] = KEY_BACK, + [KEYMAP_INDEX(0, 1)] = KEY_HOME, +// [KEYMAP_INDEX(0, 2)] = KEY_, + [KEYMAP_INDEX(0, 3)] = KEY_BACKSPACE, + [KEYMAP_INDEX(0, 4)] = KEY_ENTER, + [KEYMAP_INDEX(0, 5)] = KEY_RIGHTALT, + [KEYMAP_INDEX(0, 6)] = KEY_P, + + [KEYMAP_INDEX(1, 0)] = KEY_MENU, +// [KEYMAP_INDEX(1, 0)] = 229, // SOFT1 + [KEYMAP_INDEX(1, 1)] = KEY_SEND, + [KEYMAP_INDEX(1, 2)] = KEY_END, + [KEYMAP_INDEX(1, 3)] = KEY_LEFTALT, + [KEYMAP_INDEX(1, 4)] = KEY_A, + [KEYMAP_INDEX(1, 5)] = KEY_LEFTSHIFT, + [KEYMAP_INDEX(1, 6)] = KEY_Q, + + [KEYMAP_INDEX(2, 0)] = KEY_U, + [KEYMAP_INDEX(2, 1)] = KEY_7, + [KEYMAP_INDEX(2, 2)] = KEY_K, + [KEYMAP_INDEX(2, 3)] = KEY_J, + [KEYMAP_INDEX(2, 4)] = KEY_M, + [KEYMAP_INDEX(2, 5)] = KEY_SLASH, + [KEYMAP_INDEX(2, 6)] = KEY_8, + + [KEYMAP_INDEX(3, 0)] = KEY_5, + [KEYMAP_INDEX(3, 1)] = KEY_6, + [KEYMAP_INDEX(3, 2)] = KEY_B, + [KEYMAP_INDEX(3, 3)] = KEY_H, + [KEYMAP_INDEX(3, 4)] = KEY_N, + [KEYMAP_INDEX(3, 5)] = KEY_SPACE, + [KEYMAP_INDEX(3, 6)] = KEY_Y, + + [KEYMAP_INDEX(4, 0)] = KEY_4, + [KEYMAP_INDEX(4, 1)] = KEY_R, + [KEYMAP_INDEX(4, 2)] = KEY_V, + [KEYMAP_INDEX(4, 3)] = KEY_G, + [KEYMAP_INDEX(4, 4)] = KEY_C, + //[KEYMAP_INDEX(4, 5)] = KEY_, + [KEYMAP_INDEX(4, 6)] = KEY_T, + + [KEYMAP_INDEX(5, 0)] = KEY_2, + [KEYMAP_INDEX(5, 1)] = KEY_W, + [KEYMAP_INDEX(5, 2)] = KEY_COMPOSE, + [KEYMAP_INDEX(5, 3)] = KEY_VOLUMEUP, + [KEYMAP_INDEX(5, 4)] = KEY_S, + [KEYMAP_INDEX(5, 5)] = KEY_Z, + [KEYMAP_INDEX(5, 6)] = KEY_1, + + [KEYMAP_INDEX(6, 0)] = KEY_I, + [KEYMAP_INDEX(6, 1)] = KEY_0, + [KEYMAP_INDEX(6, 2)] = KEY_O, + [KEYMAP_INDEX(6, 3)] = KEY_L, + [KEYMAP_INDEX(6, 4)] = KEY_DOT, + [KEYMAP_INDEX(6, 5)] = KEY_COMMA, + [KEYMAP_INDEX(6, 6)] = KEY_9, + + [KEYMAP_INDEX(7, 0)] = KEY_3, + [KEYMAP_INDEX(7, 1)] = KEY_E, + [KEYMAP_INDEX(7, 2)] = KEY_EMAIL, // @ + [KEYMAP_INDEX(7, 3)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(7, 4)] = KEY_X, + [KEYMAP_INDEX(7, 5)] = KEY_F, + [KEYMAP_INDEX(7, 6)] = KEY_D +}; + +static unsigned int trout_col_gpios_evt2[] = { 35, 34, 33, 32, 31, 23, 30, 109 }; +static unsigned int trout_row_gpios_evt2[] = { 42, 41, 40, 39, 38, 37, 36 }; + +static const unsigned short trout_keymap_evt2_1[ARRAY_SIZE(trout_col_gpios) * ARRAY_SIZE(trout_row_gpios)] = { + [KEYMAP_INDEX(0, 0)] = KEY_BACK, + [KEYMAP_INDEX(0, 1)] = KEY_HOME, +// [KEYMAP_INDEX(0, 2)] = KEY_, + [KEYMAP_INDEX(0, 3)] = KEY_BACKSPACE, + [KEYMAP_INDEX(0, 4)] = KEY_ENTER, + [KEYMAP_INDEX(0, 5)] = KEY_RIGHTSHIFT, + [KEYMAP_INDEX(0, 6)] = KEY_P, + + [KEYMAP_INDEX(1, 0)] = KEY_MENU, + [KEYMAP_INDEX(1, 1)] = KEY_SEND, +// [KEYMAP_INDEX(1, 2)] = KEY_, + [KEYMAP_INDEX(1, 3)] = KEY_LEFTSHIFT, + [KEYMAP_INDEX(1, 4)] = KEY_A, + [KEYMAP_INDEX(1, 5)] = KEY_COMPOSE, + [KEYMAP_INDEX(1, 6)] = KEY_Q, + + [KEYMAP_INDEX(2, 0)] = KEY_U, + [KEYMAP_INDEX(2, 1)] = KEY_7, + [KEYMAP_INDEX(2, 2)] = KEY_K, + [KEYMAP_INDEX(2, 3)] = KEY_J, + [KEYMAP_INDEX(2, 4)] = KEY_M, + [KEYMAP_INDEX(2, 5)] = KEY_SLASH, + [KEYMAP_INDEX(2, 6)] = KEY_8, + + [KEYMAP_INDEX(3, 0)] = KEY_5, + [KEYMAP_INDEX(3, 1)] = KEY_6, + [KEYMAP_INDEX(3, 2)] = KEY_B, + [KEYMAP_INDEX(3, 3)] = KEY_H, + [KEYMAP_INDEX(3, 4)] = KEY_N, + [KEYMAP_INDEX(3, 5)] = KEY_SPACE, + [KEYMAP_INDEX(3, 6)] = KEY_Y, + + [KEYMAP_INDEX(4, 0)] = KEY_4, + [KEYMAP_INDEX(4, 1)] = KEY_R, + [KEYMAP_INDEX(4, 2)] = KEY_V, + [KEYMAP_INDEX(4, 3)] = KEY_G, + [KEYMAP_INDEX(4, 4)] = KEY_C, +// [KEYMAP_INDEX(4, 5)] = KEY_, + [KEYMAP_INDEX(4, 6)] = KEY_T, + + [KEYMAP_INDEX(5, 0)] = KEY_2, + [KEYMAP_INDEX(5, 1)] = KEY_W, + [KEYMAP_INDEX(5, 2)] = KEY_LEFTALT, + [KEYMAP_INDEX(5, 3)] = KEY_VOLUMEUP, + [KEYMAP_INDEX(5, 4)] = KEY_S, + [KEYMAP_INDEX(5, 5)] = KEY_Z, + [KEYMAP_INDEX(5, 6)] = KEY_1, + + [KEYMAP_INDEX(6, 0)] = KEY_I, + [KEYMAP_INDEX(6, 1)] = KEY_0, + [KEYMAP_INDEX(6, 2)] = KEY_O, + [KEYMAP_INDEX(6, 3)] = KEY_L, + [KEYMAP_INDEX(6, 4)] = KEY_COMMA, + [KEYMAP_INDEX(6, 5)] = KEY_DOT, + [KEYMAP_INDEX(6, 6)] = KEY_9, + + [KEYMAP_INDEX(7, 0)] = KEY_3, + [KEYMAP_INDEX(7, 1)] = KEY_E, + [KEYMAP_INDEX(7, 2)] = KEY_EMAIL, // @ + [KEYMAP_INDEX(7, 3)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(7, 4)] = KEY_X, + [KEYMAP_INDEX(7, 5)] = KEY_F, + [KEYMAP_INDEX(7, 6)] = KEY_D +}; + +static const unsigned short trout_keymap_evt2_2[ARRAY_SIZE(trout_col_gpios) * ARRAY_SIZE(trout_row_gpios)] = { + [KEYMAP_INDEX(0, 0)] = KEY_BACK, + [KEYMAP_INDEX(0, 1)] = KEY_HOME, +// [KEYMAP_INDEX(0, 2)] = KEY_, + [KEYMAP_INDEX(0, 3)] = KEY_BACKSPACE, + [KEYMAP_INDEX(0, 4)] = KEY_ENTER, + [KEYMAP_INDEX(0, 5)] = KEY_RIGHTSHIFT, + [KEYMAP_INDEX(0, 6)] = KEY_P, + + [KEYMAP_INDEX(1, 0)] = KEY_MENU, /* external menu key */ + [KEYMAP_INDEX(1, 1)] = KEY_SEND, +// [KEYMAP_INDEX(1, 2)] = KEY_, + [KEYMAP_INDEX(1, 3)] = KEY_LEFTSHIFT, + [KEYMAP_INDEX(1, 4)] = KEY_A, + [KEYMAP_INDEX(1, 5)] = KEY_F1, /* qwerty menu key */ + [KEYMAP_INDEX(1, 6)] = KEY_Q, + + [KEYMAP_INDEX(2, 0)] = KEY_U, + [KEYMAP_INDEX(2, 1)] = KEY_7, + [KEYMAP_INDEX(2, 2)] = KEY_K, + [KEYMAP_INDEX(2, 3)] = KEY_J, + [KEYMAP_INDEX(2, 4)] = KEY_M, + [KEYMAP_INDEX(2, 5)] = KEY_DOT, + [KEYMAP_INDEX(2, 6)] = KEY_8, + + [KEYMAP_INDEX(3, 0)] = KEY_5, + [KEYMAP_INDEX(3, 1)] = KEY_6, + [KEYMAP_INDEX(3, 2)] = KEY_B, + [KEYMAP_INDEX(3, 3)] = KEY_H, + [KEYMAP_INDEX(3, 4)] = KEY_N, + [KEYMAP_INDEX(3, 5)] = KEY_SPACE, + [KEYMAP_INDEX(3, 6)] = KEY_Y, + + [KEYMAP_INDEX(4, 0)] = KEY_4, + [KEYMAP_INDEX(4, 1)] = KEY_R, + [KEYMAP_INDEX(4, 2)] = KEY_V, + [KEYMAP_INDEX(4, 3)] = KEY_G, + [KEYMAP_INDEX(4, 4)] = KEY_C, + [KEYMAP_INDEX(4, 5)] = KEY_EMAIL, // @ + [KEYMAP_INDEX(4, 6)] = KEY_T, + + [KEYMAP_INDEX(5, 0)] = KEY_2, + [KEYMAP_INDEX(5, 1)] = KEY_W, + [KEYMAP_INDEX(5, 2)] = KEY_LEFTALT, + [KEYMAP_INDEX(5, 3)] = KEY_VOLUMEUP, + [KEYMAP_INDEX(5, 4)] = KEY_S, + [KEYMAP_INDEX(5, 5)] = KEY_Z, + [KEYMAP_INDEX(5, 6)] = KEY_1, + + [KEYMAP_INDEX(6, 0)] = KEY_I, + [KEYMAP_INDEX(6, 1)] = KEY_0, + [KEYMAP_INDEX(6, 2)] = KEY_O, + [KEYMAP_INDEX(6, 3)] = KEY_L, + [KEYMAP_INDEX(6, 4)] = KEY_COMMA, + [KEYMAP_INDEX(6, 5)] = KEY_RIGHTALT, + [KEYMAP_INDEX(6, 6)] = KEY_9, + + [KEYMAP_INDEX(7, 0)] = KEY_3, + [KEYMAP_INDEX(7, 1)] = KEY_E, + [KEYMAP_INDEX(7, 2)] = KEY_COMPOSE, + [KEYMAP_INDEX(7, 3)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(7, 4)] = KEY_X, + [KEYMAP_INDEX(7, 5)] = KEY_F, + [KEYMAP_INDEX(7, 6)] = KEY_D +}; + +static struct gpio_event_matrix_info trout_keypad_matrix_info = { + .info.func = gpio_event_matrix_func, + .keymap = trout_keymap, + .output_gpios = trout_col_gpios, + .input_gpios = trout_row_gpios, + .noutputs = ARRAY_SIZE(trout_col_gpios), + .ninputs = ARRAY_SIZE(trout_row_gpios), + .settle_time.tv.nsec = 40 * NSEC_PER_USEC, + .poll_time.tv.nsec = 20 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_REMOVE_PHANTOM_KEYS |GPIOKPF_PRINT_UNMAPPED_KEYS /*| GPIOKPF_PRINT_MAPPED_KEYS*/ +}; + +static struct gpio_event_direct_entry trout_keypad_nav_map[] = { + { TROUT_POWER_KEY, KEY_POWER }, + { TROUT_GPIO_CAM_BTN_STEP1_N, KEY_CAMERA-1 }, //steal KEY_HP + { TROUT_GPIO_CAM_BTN_STEP2_N, KEY_CAMERA }, +}; + +static struct gpio_event_direct_entry trout_keypad_nav_map_evt2[] = { + { TROUT_POWER_KEY, KEY_END }, + { TROUT_GPIO_CAM_BTN_STEP1_N, KEY_CAMERA-1 }, //steal KEY_HP + { TROUT_GPIO_CAM_BTN_STEP2_N, KEY_CAMERA }, +}; + +static struct gpio_event_input_info trout_keypad_nav_info = { + .info.func = gpio_event_input_func, + .flags = 0, + .type = EV_KEY, + .keymap = trout_keypad_nav_map, + .keymap_size = ARRAY_SIZE(trout_keypad_nav_map) +}; + +static struct gpio_event_direct_entry trout_keypad_switch_map[] = { + { TROUT_GPIO_SLIDING_DET, SW_LID } +}; + +static struct gpio_event_input_info trout_keypad_switch_info = { + .info.func = gpio_event_input_func, + .flags = 0, + .type = EV_SW, + .keymap = trout_keypad_switch_map, + .keymap_size = ARRAY_SIZE(trout_keypad_switch_map) +}; + +static struct gpio_event_info *trout_keypad_info[] = { + &trout_keypad_matrix_info.info, + &trout_keypad_nav_info.info, + &trout_keypad_switch_info.info, +}; + +static struct gpio_event_platform_data trout_keypad_data = { + .name = "trout-keypad", + .info = trout_keypad_info, + .info_count = ARRAY_SIZE(trout_keypad_info) +}; + +static struct platform_device trout_keypad_device = { + .name = GPIO_EVENT_DEV_NAME, + .id = 0, + .dev = { + .platform_data = &trout_keypad_data, + }, +}; + +static int __init trout_init_keypad(void) +{ + if (!machine_is_trout()) + return 0; + + switch (system_rev) { + case 0: + /* legacy default keylayout */ + break; + case 1: + /* v1 has a new keyboard layout */ + trout_keypad_matrix_info.keymap = trout_keymap_evt2_1; + trout_keypad_matrix_info.output_gpios = trout_col_gpios_evt2; + trout_keypad_matrix_info.input_gpios = trout_row_gpios_evt2; + + /* v1 has new direct keys */ + trout_keypad_nav_info.keymap = trout_keypad_nav_map_evt2; + trout_keypad_nav_info.keymap_size = ARRAY_SIZE(trout_keypad_nav_map_evt2); + + /* userspace needs to know about these changes as well */ + trout_keypad_data.name = "trout-keypad-v2"; + break; + default: /* 2, 3, 4 currently */ + /* v2 has a new keyboard layout */ + trout_keypad_matrix_info.keymap = trout_keymap_evt2_2; + trout_keypad_matrix_info.output_gpios = trout_col_gpios_evt2; + trout_keypad_matrix_info.input_gpios = trout_row_gpios_evt2; + + /* v2 has new direct keys */ + trout_keypad_nav_info.keymap = trout_keypad_nav_map_evt2; + trout_keypad_nav_info.keymap_size = ARRAY_SIZE(trout_keypad_nav_map_evt2); + + /* userspace needs to know about these changes as well */ + if (!strcmp(keycaps, "qwertz")) { + trout_keypad_data.name = "trout-keypad-qwertz"; + } else { + trout_keypad_data.name = "trout-keypad-v3"; + } + break; + } + return platform_device_register(&trout_keypad_device); +} + +device_initcall(trout_init_keypad); + diff --git a/arch/arm/mach-msm/board-trout-rfkill.c b/arch/arm/mach-msm/board-trout-rfkill.c new file mode 100644 index 00000000000..e68eb2ae4c5 --- /dev/null +++ b/arch/arm/mach-msm/board-trout-rfkill.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Author: Nick Pelly + * + * 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. + * + */ + +/* Control bluetooth power for trout platform */ + +#include +#include +#include +#include +#include +#include + +#include "board-trout.h" + +static struct rfkill *bt_rfk; +static const char bt_name[] = "brf6300"; + +static int bluetooth_set_power(void *data, bool blocked) +{ + if (!blocked) { + gpio_set_value(TROUT_GPIO_BT_32K_EN, 1); + udelay(10); + gpio_direction_output(101, 1); + } else { + gpio_direction_output(101, 0); + gpio_set_value(TROUT_GPIO_BT_32K_EN, 0); + } + return 0; +} + +static struct rfkill_ops trout_rfkill_ops = { + .set_block = bluetooth_set_power, +}; + +static int trout_rfkill_probe(struct platform_device *pdev) +{ + int rc = 0; + bool default_state = true; /* off */ + + bluetooth_set_power(NULL, default_state); + + bt_rfk = rfkill_alloc(bt_name, &pdev->dev, RFKILL_TYPE_BLUETOOTH, + &trout_rfkill_ops, NULL); + if (!bt_rfk) + return -ENOMEM; + + rfkill_set_states(bt_rfk, default_state, false); + + /* userspace cannot take exclusive control */ + + rc = rfkill_register(bt_rfk); + + if (rc) + rfkill_destroy(bt_rfk); + return rc; +} + +static int trout_rfkill_remove(struct platform_device *dev) +{ + rfkill_unregister(bt_rfk); + rfkill_destroy(bt_rfk); + + return 0; +} + +static struct platform_driver trout_rfkill_driver = { + .probe = trout_rfkill_probe, + .remove = trout_rfkill_remove, + .driver = { + .name = "trout_rfkill", + .owner = THIS_MODULE, + }, +}; + +static int __init trout_rfkill_init(void) +{ + return platform_driver_register(&trout_rfkill_driver); +} + +static void __exit trout_rfkill_exit(void) +{ + platform_driver_unregister(&trout_rfkill_driver); +} + +module_init(trout_rfkill_init); +module_exit(trout_rfkill_exit); +MODULE_DESCRIPTION("trout rfkill"); +MODULE_AUTHOR("Nick Pelly "); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/board-trout-wifi.c b/arch/arm/mach-msm/board-trout-wifi.c new file mode 100644 index 00000000000..51b26a40536 --- /dev/null +++ b/arch/arm/mach-msm/board-trout-wifi.c @@ -0,0 +1,74 @@ +/* arch/arm/mach-msm/board-trout-wifi.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Dmitry Shmidt + * + * 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. + * + */ + +#ifdef CONFIG_WIFI_CONTROL_FUNC +#include +#include +#include +#include +#include +#include + +extern int trout_wifi_set_carddetect(int val); +extern int trout_wifi_power(int on); +extern int trout_wifi_reset(int on); + +#ifdef CONFIG_WIFI_MEM_PREALLOC +typedef struct wifi_mem_prealloc_struct { + void *mem_ptr; + unsigned long size; +} wifi_mem_prealloc_t; + +static wifi_mem_prealloc_t wifi_mem_array[WMPA_NUMBER_OF_SECTIONS] = { + { NULL, (WMPA_SECTION_SIZE_0 + WMPA_SECTION_HEADER) }, + { NULL, (WMPA_SECTION_SIZE_1 + WMPA_SECTION_HEADER) }, + { NULL, (WMPA_SECTION_SIZE_2 + WMPA_SECTION_HEADER) } +}; + +static void *trout_wifi_mem_prealloc(int section, unsigned long size) +{ + if( (section < 0) || (section >= WMPA_NUMBER_OF_SECTIONS) ) + return NULL; + if( wifi_mem_array[section].size < size ) + return NULL; + return wifi_mem_array[section].mem_ptr; +} + +int __init trout_init_wifi_mem( void ) +{ + int i; + + for(i=0;( i < WMPA_NUMBER_OF_SECTIONS );i++) { + wifi_mem_array[i].mem_ptr = vmalloc(wifi_mem_array[i].size); + if( wifi_mem_array[i].mem_ptr == NULL ) + return -ENOMEM; + } + return 0; +} +#endif + +struct wifi_platform_data trout_wifi_control = { + .set_power = trout_wifi_power, + .set_reset = trout_wifi_reset, + .set_carddetect = trout_wifi_set_carddetect, +#ifdef CONFIG_WIFI_MEM_PREALLOC + .mem_prealloc = trout_wifi_mem_prealloc, +#else + .mem_prealloc = NULL, +#endif +}; + +#endif diff --git a/arch/arm/mach-msm/btpintest.c b/arch/arm/mach-msm/btpintest.c new file mode 100644 index 00000000000..0abe09b8495 --- /dev/null +++ b/arch/arm/mach-msm/btpintest.c @@ -0,0 +1,234 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gpiomux.h" + +#define VERSION "1.0" +struct dentry *pin_debugfs_dent; + +/* UART GPIO lines for 8660 */ +enum uartpins { + UARTDM_TX = 53, + UARTDM_RX = 54, + UARTDM_CTS = 55, + UARTDM_RFR = 56 +}; + +/* Aux PCM GPIO lines for 8660 */ +enum auxpcmpins { + AUX_PCM_CLK = 114, + AUX_PCM_SYNC = 113, + AUX_PCM_DIN = 112, + AUX_PCM_DOUT = 111 +}; +/*Number of UART and PCM pins */ +#define PIN_COUNT 8 + +static struct gpiomux_setting pin_test_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; +/* Static array to intialise the return config */ +static struct gpiomux_setting currentconfig[2*PIN_COUNT]; + +static struct msm_gpiomux_config pin_test_configs[] = { + { + .gpio = AUX_PCM_DOUT, + .settings = { + [GPIOMUX_ACTIVE] = &pin_test_config, + [GPIOMUX_SUSPENDED] = &pin_test_config, + }, + }, + { + .gpio = AUX_PCM_DIN, + .settings = { + [GPIOMUX_ACTIVE] = &pin_test_config, + [GPIOMUX_SUSPENDED] = &pin_test_config, + }, + }, + { + .gpio = AUX_PCM_SYNC, + .settings = { + [GPIOMUX_ACTIVE] = &pin_test_config, + [GPIOMUX_SUSPENDED] = &pin_test_config, + }, + }, + { + .gpio = AUX_PCM_CLK, + .settings = { + [GPIOMUX_ACTIVE] = &pin_test_config, + [GPIOMUX_SUSPENDED] = &pin_test_config, + }, + }, + { + .gpio = UARTDM_TX, + .settings = { + [GPIOMUX_ACTIVE] = &pin_test_config, + [GPIOMUX_SUSPENDED] = &pin_test_config, + }, + }, + { + .gpio = UARTDM_RX, + .settings = { + [GPIOMUX_ACTIVE] = &pin_test_config, + [GPIOMUX_SUSPENDED] = &pin_test_config, + }, + }, + { + .gpio = UARTDM_CTS, + .settings = { + [GPIOMUX_ACTIVE] = &pin_test_config, + [GPIOMUX_SUSPENDED] = &pin_test_config, + }, + }, + { + .gpio = UARTDM_RFR, + .settings = { + [GPIOMUX_ACTIVE] = &pin_test_config, + [GPIOMUX_SUSPENDED] = &pin_test_config, + }, + }, +}; +static struct msm_gpiomux_config pin_config[PIN_COUNT]; + +static int pintest_open(struct inode *inode, struct file *file) +{ + /* non-seekable */ + file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); + return 0; +} + +static int pintest_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int configure_pins(struct msm_gpiomux_config *config, + struct msm_gpiomux_config *oldconfig, + unsigned int num_configs) +{ + int rc = 0, j, i; + for (i = 0; i < num_configs; i++) { + for (j = 0; j < GPIOMUX_NSETTINGS; j++) { + (oldconfig + i)->gpio = (config + i)->gpio; + rc = msm_gpiomux_write((config + i)->gpio, + j, + (config + i)->settings[j], + (oldconfig + i)->settings[j]); + if (rc < 0) + break; + } + + } + return rc; +} + +static void init_current_config_pointers(void) +{ + int i = 0, j = 0; + /* The current config variables will hold the current configuration + * which is getting overwritten during a msm_gpiomux_write call + */ + for (i = 0, j = 0; i < PIN_COUNT; i += 1, j += 2) { + pin_config[i].settings[GPIOMUX_ACTIVE] = ¤tconfig[j]; + pin_config[i].settings[GPIOMUX_SUSPENDED] = + ¤tconfig[j + 1]; + } + +} + +static ssize_t pintest_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + char mode; + int rc = 0; + + if (count < 1) + return -EINVAL; + + if (buff == NULL) + return -EINVAL; + + if (copy_from_user(&mode, buff, count)) + return -EFAULT; + mode = mode - '0'; + + init_current_config_pointers(); + + if (mode) { + /* Configure all pin test gpios for the custom settings */ + rc = configure_pins(pin_test_configs, pin_config, + ARRAY_SIZE(pin_test_configs)); + if (rc < 0) + return rc; + } else { + /* Configure all pin test gpios for the original settings */ + rc = configure_pins(pin_config, pin_test_configs, + ARRAY_SIZE(pin_test_configs)); + if (rc < 0) + return rc; + } + return rc; +} + +static const struct file_operations pintest_debugfs_fops = { + .open = pintest_open, + .release = pintest_release, + .write = pintest_write, +}; + +static int __init bluepintest_init(void) +{ + pin_debugfs_dent = debugfs_create_dir("btpintest", NULL); + + if (IS_ERR(pin_debugfs_dent)) { + printk(KERN_ERR "%s(%d): debugfs_create_dir fail, error %ld\n", + __FILE__, __LINE__, PTR_ERR(pin_debugfs_dent)); + return -ENOMEM; + } + + if (debugfs_create_file("enable", 0644, pin_debugfs_dent, + 0, &pintest_debugfs_fops) == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: index fail\n", + __FILE__, __LINE__); + return -ENOMEM; + } + return 0; +} + +static void __exit bluepintest_exit(void) +{ + debugfs_remove_recursive(pin_debugfs_dent); +} + +module_init(bluepintest_init); +module_exit(bluepintest_exit); + +MODULE_DESCRIPTION("Bluetooth Pin Connectivty Test Driver ver %s " VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/clock-7x30.c b/arch/arm/mach-msm/clock-7x30.c new file mode 100644 index 00000000000..d42db8ac0e5 --- /dev/null +++ b/arch/arm/mach-msm/clock-7x30.c @@ -0,0 +1,2994 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "clock.h" +#include "clock-local.h" +#include "clock-pcom.h" +#include "clock-voter.h" +#include "proc_comm.h" + +#define REG_BASE(off) (MSM_CLK_CTL_BASE + (off)) +#define REG(off) (MSM_CLK_CTL_SH2_BASE + (off)) + +/* Shadow-region 2 (SH2) registers. */ +#define QUP_I2C_NS_REG REG(0x04F0) +#define CAM_NS_REG REG(0x0374) +#define CAM_VFE_NS_REG REG(0x0044) +#define CLK_HALT_STATEA_REG REG(0x0108) +#define CLK_HALT_STATEB_REG REG(0x010C) +#define CLK_HALT_STATEC_REG REG(0x02D4) +#define CSI_NS_REG REG(0x0174) +#define EMDH_NS_REG REG(0x0050) +#define GLBL_CLK_ENA_2_SC_REG REG(0x03C0) +#define GLBL_CLK_ENA_SC_REG REG(0x03BC) +#define GLBL_CLK_STATE_2_REG REG(0x037C) +#define GLBL_CLK_STATE_REG REG(0x0004) +#define GRP_2D_NS_REG REG(0x0034) +#define GRP_NS_REG REG(0x0084) +#define HDMI_NS_REG REG(0x0484) +#define I2C_2_NS_REG REG(0x02D8) +#define I2C_NS_REG REG(0x0068) +#define JPEG_NS_REG REG(0x0164) +#define LPA_CORE_CLK_MA0_REG REG(0x04F4) +#define LPA_CORE_CLK_MA2_REG REG(0x04FC) +#define LPA_NS_REG REG(0x02E8) +#define MDC_NS_REG REG(0x007C) +#define MDP_LCDC_NS_REG REG(0x0390) +#define MDP_NS_REG REG(0x014C) +#define MDP_VSYNC_REG REG(0x0460) +#define MFC_NS_REG REG(0x0154) +#define MI2S_CODEC_RX_DIV_REG REG(0x02EC) +#define MI2S_CODEC_TX_DIV_REG REG(0x02F0) +#define MI2S_DIV_REG REG(0x02E4) +#define MI2S_NS_REG REG(0x02E0) +#define MI2S_RX_NS_REG REG(0x0070) +#define MI2S_TX_NS_REG REG(0x0078) +#define MIDI_NS_REG REG(0x02D0) +#define PLL_ENA_REG REG(0x0264) +#define PMDH_NS_REG REG(0x008C) +#define SDAC_NS_REG REG(0x009C) +#define SDCn_NS_REG(n) REG(0x00A4+(0x8*((n)-1))) +#define SPI_NS_REG REG(0x02C8) +#define TSIF_NS_REG REG(0x00C4) +#define TV_NS_REG REG(0x00CC) +#define UART1DM_NS_REG REG(0x00D4) +#define UART2DM_NS_REG REG(0x00DC) +#define UART2_NS_REG REG(0x0464) +#define UART_NS_REG REG(0x00E0) +#define USBH2_NS_REG REG(0x046C) +#define USBH3_NS_REG REG(0x0470) +#define USBH_MD_REG REG(0x02BC) +#define USBH_NS_REG REG(0x02C0) +#define VPE_NS_REG REG(0x015C) + +/* Registers in the base (non-shadow) region. */ +#define CLK_TEST_BASE_REG REG_BASE(0x011C) +#define CLK_TEST_2_BASE_REG REG_BASE(0x0384) +#define MISC_CLK_CTL_BASE_REG REG_BASE(0x0110) +#define PRPH_WEB_NS_BASE_REG REG_BASE(0x0080) +#define PLL0_STATUS_BASE_REG REG_BASE(0x0318) +#define PLL1_STATUS_BASE_REG REG_BASE(0x0334) +#define PLL2_STATUS_BASE_REG REG_BASE(0x0350) +#define PLL3_STATUS_BASE_REG REG_BASE(0x036C) +#define PLL4_STATUS_BASE_REG REG_BASE(0x0254) +#define PLL5_STATUS_BASE_REG REG_BASE(0x0258) +#define PLL6_STATUS_BASE_REG REG_BASE(0x04EC) +#define RINGOSC_CNT_BASE_REG REG_BASE(0x00FC) +#define SH2_OWN_APPS1_BASE_REG REG_BASE(0x040C) +#define SH2_OWN_APPS2_BASE_REG REG_BASE(0x0414) +#define SH2_OWN_APPS3_BASE_REG REG_BASE(0x0444) +#define SH2_OWN_GLBL_BASE_REG REG_BASE(0x0404) +#define SH2_OWN_ROW1_BASE_REG REG_BASE(0x041C) +#define SH2_OWN_ROW2_BASE_REG REG_BASE(0x0424) +#define TCXO_CNT_BASE_REG REG_BASE(0x00F8) +#define TCXO_CNT_DONE_BASE_REG REG_BASE(0x00F8) + + +/* MUX source input identifiers. */ +#define SRC_SEL_pll0 4 /* Modem PLL */ +#define SRC_SEL_pll1 1 /* Global PLL */ +#define SRC_SEL_pll3 3 /* Multimedia/Peripheral PLL or Backup PLL1 */ +#define SRC_SEL_pll4 2 /* Display PLL */ +#define SRC_SEL_SDAC_lpxo 5 /* Low-power XO for SDAC */ +#define SRC_SEL_lpxo 6 /* Low-power XO */ +#define SRC_SEL_tcxo 0 /* Used for rates from TCXO */ +#define SRC_SEL_axi 0 /* Used for rates that sync to AXI */ +#define SRC_SEL_gnd 7 /* No clock */ + +/* Clock declaration macros. */ +#define MN_MODE_DUAL_EDGE 0x2 +#define MD8(m, n) (BVAL(15, 8, m) | BVAL(7, 0, ~(n))) +#define N8(msb, lsb, m, n) (BVAL(msb, lsb, ~(n-m)) | BVAL(6, 5, \ + (MN_MODE_DUAL_EDGE * !!(n)))) +#define MD16(m, n) (BVAL(31, 16, m) | BVAL(15, 0, ~(n))) +#define N16(m, n) (BVAL(31, 16, ~(n-m)) | BVAL(6, 5, \ + (MN_MODE_DUAL_EDGE * !!(n)))) +#define SPDIV(s, d) (BVAL(4, 3, d-1) | BVAL(2, 0, s)) +#define SDIV(s, d) (BVAL(6, 3, d-1) | BVAL(2, 0, s)) +#define F_MASK_BASIC (BM(6, 3)|BM(2, 0)) +#define F_MASK_MND16 (BM(31, 16)|BM(6, 5)|BM(4, 3)|BM(2, 0)) +#define F_MASK_MND8(m, l) (BM(m, l)|BM(6, 5)|BM(4, 3)|BM(2, 0)) + +/* + * Clock frequency definitions and macros + */ +#define F_BASIC(f, s, div, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = SDIV(SRC_SEL_##s, div), \ + .sys_vdd = v, \ + } + +#define F_MND16(f, s, div, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD16(m, n), \ + .ns_val = N16(m, n) | SPDIV(SRC_SEL_##s, div), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } + +#define F_MND8(f, nmsb, nlsb, s, div, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(m, n), \ + .ns_val = N8(nmsb, nlsb, m, n) | SPDIV(SRC_SEL_##s, div), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } + +static struct clk_ops soc_clk_ops_7x30; + +#define PCOM_XO_DISABLE 0 +#define PCOM_XO_ENABLE 1 +#define PCOM_XO_TCXO 0 +#define PCOM_XO_LPXO 1 + +static bool pcom_is_local(struct clk *clk) +{ + return false; +} + +static int pcom_xo_enable(unsigned pcom_id, unsigned enable) +{ + /* TODO: Check return code in pcom_id */ + return msm_proc_comm(PCOM_CLKCTL_RPC_SRC_REQUEST, &pcom_id, &enable); +} + +static int tcxo_clk_enable(struct clk *clk) +{ + return pcom_xo_enable(PCOM_XO_TCXO, PCOM_XO_ENABLE); +} + +static void tcxo_clk_disable(struct clk *clk) +{ + pcom_xo_enable(PCOM_XO_TCXO, PCOM_XO_DISABLE); +} + +static struct clk_ops clk_ops_tcxo = { + .enable = tcxo_clk_enable, + .disable = tcxo_clk_disable, + .get_rate = fixed_clk_get_rate, + .is_local = pcom_is_local, +}; + +static struct fixed_clk tcxo_clk = { + .rate = 19200000, + .c = { + .dbg_name = "tcxo_clk", + .ops = &clk_ops_tcxo, + CLK_INIT(tcxo_clk.c), + }, +}; + +static int lpxo_clk_enable(struct clk *clk) +{ + return pcom_xo_enable(PCOM_XO_LPXO, PCOM_XO_ENABLE); +} + +static void lpxo_clk_disable(struct clk *clk) +{ + pcom_xo_enable(PCOM_XO_LPXO, PCOM_XO_DISABLE); +} + +static struct clk_ops clk_ops_lpxo = { + .enable = lpxo_clk_enable, + .disable = lpxo_clk_disable, + .get_rate = fixed_clk_get_rate, + .is_local = pcom_is_local, +}; + +static struct fixed_clk lpxo_clk = { + .rate = 24576000, + .c = { + .dbg_name = "lpxo_clk", + .ops = &clk_ops_lpxo, + CLK_INIT(lpxo_clk.c), + }, +}; + +static struct pll_vote_clk pll1_clk = { + .rate = 768000000, + .en_reg = PLL_ENA_REG, + .en_mask = BIT(1), + .status_reg = PLL1_STATUS_BASE_REG, + .parent = &tcxo_clk.c, + .c = { + .dbg_name = "pll1_clk", + .ops = &clk_ops_pll_vote, + CLK_INIT(pll1_clk.c), + }, +}; + +static struct pll_vote_clk pll2_clk = { + .rate = 806400000, /* TODO: Support scaling */ + .en_reg = PLL_ENA_REG, + .en_mask = BIT(2), + .status_reg = PLL2_STATUS_BASE_REG, + .parent = &tcxo_clk.c, + .c = { + .dbg_name = "pll2_clk", + .ops = &clk_ops_pll_vote, + CLK_INIT(pll2_clk.c), + }, +}; + +static struct pll_vote_clk pll3_clk = { + .rate = 737280000, + .en_reg = PLL_ENA_REG, + .en_mask = BIT(3), + .status_reg = PLL3_STATUS_BASE_REG, + .parent = &lpxo_clk.c, + .c = { + .dbg_name = "pll3_clk", + .ops = &clk_ops_pll_vote, + CLK_INIT(pll3_clk.c), + }, +}; + +static struct pll_vote_clk pll4_clk = { + .rate = 891000000, + .en_reg = PLL_ENA_REG, + .en_mask = BIT(4), + .status_reg = PLL4_STATUS_BASE_REG, + .parent = &lpxo_clk.c, + .c = { + .dbg_name = "pll4_clk", + .ops = &clk_ops_pll_vote, + CLK_INIT(pll4_clk.c), + }, +}; + +static struct clk_ops clk_ops_branch; + +static struct clk_freq_tbl clk_tbl_axi[] = { + F_RAW(1, &lpxo_clk.c, 0, 0, 0, 0, NOMINAL, NULL), + F_END, +}; + +/* For global clocks to be on we must have GLBL_ROOT_ENA set */ +static struct rcg_clk glbl_root_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(29), + .halt_check = NOCHECK, + }, + .freq_tbl = clk_tbl_axi, + .set_rate = set_rate_nop, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_nop, + .c = { + .dbg_name = "glbl_root_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(glbl_root_clk.c), + }, +}; + +/* AXI bridge clocks. */ +static struct branch_clk axi_li_apps_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(2), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 2, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "axi_li_apps_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_li_apps_clk.c), + }, +}; + +static struct branch_clk axi_li_adsp_a_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(14), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 14, + }, + .parent = &axi_li_apps_clk.c, + .c = { + .dbg_name = "axi_li_adsp_a_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_li_adsp_a_clk.c), + }, +}; + +static struct branch_clk axi_li_jpeg_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(19), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 19, + }, + .parent = &axi_li_apps_clk.c, + .c = { + .dbg_name = "axi_li_jpeg_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_li_jpeg_clk.c), + }, +}; + +static struct branch_clk axi_li_vfe_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(23), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 23, + }, + .parent = &axi_li_apps_clk.c, + .c = { + .dbg_name = "axi_li_vfe_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_li_vfe_clk.c), + }, +}; + +static struct branch_clk axi_mdp_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(29), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 29, + }, + .parent = &axi_li_apps_clk.c, + .c = { + .dbg_name = "axi_mdp_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_mdp_clk.c), + }, +}; + +static struct branch_clk axi_li_vg_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(3), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 3, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "axi_li_vg_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_li_vg_clk.c), + }, +}; + +static struct branch_clk axi_grp_2d_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(21), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 21, + }, + .parent = &axi_li_vg_clk.c, + .c = { + .dbg_name = "axi_grp_2d_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_grp_2d_clk.c), + }, +}; + +static struct branch_clk axi_li_grp_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(22), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 22, + }, + .parent = &axi_li_vg_clk.c, + .c = { + .dbg_name = "axi_li_grp_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_li_grp_clk.c), + }, +}; + +static struct branch_clk axi_mfc_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(20), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 20, + }, + .parent = &axi_li_vg_clk.c, + .c = { + .dbg_name = "axi_mfc_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_mfc_clk.c), + }, +}; + +static struct branch_clk axi_rotator_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(22), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 22, + .reset_mask = P_AXI_ROTATOR_CLK, + }, + .parent = &axi_li_vg_clk.c, + .c = { + .dbg_name = "axi_rotator_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_rotator_clk.c), + }, +}; + +static struct branch_clk axi_vpe_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(21), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 21, + }, + .parent = &axi_li_vg_clk.c, + .c = { + .dbg_name = "axi_vpe_clk", + .ops = &clk_ops_branch, + CLK_INIT(axi_vpe_clk.c), + }, +}; + +/* Peripheral bus clocks. */ +static struct branch_clk adm_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(5), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 5, + .reset_mask = P_ADM_CLK, + }, + .parent = &axi_li_apps_clk.c, + .c = { + .dbg_name = "adm_clk", + .ops = &clk_ops_branch, + CLK_INIT(adm_clk.c), + }, +}; + +static struct branch_clk adm_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(15), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 15, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "adm_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(adm_p_clk.c), + }, +}; + +static struct branch_clk ce_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(6), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 6, + .reset_mask = P_CE_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "ce_clk", + .ops = &clk_ops_branch, + CLK_INIT(ce_clk.c), + }, +}; + +static struct branch_clk camif_pad_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(9), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 9, + .reset_mask = P_CAMIF_PAD_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "camif_pad_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(camif_pad_p_clk.c), + }, +}; + +static struct branch_clk csi0_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(30), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 30, + .reset_mask = P_CSI0_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "csi0_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0_p_clk.c), + }, +}; + +static struct branch_clk emdh_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(3), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 3, + .reset_mask = P_EMDH_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "emdh_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(emdh_p_clk.c), + }, +}; + +static struct branch_clk grp_2d_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(24), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 24, + .reset_mask = P_GRP_2D_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "grp_2d_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(grp_2d_p_clk.c), + }, +}; + +static struct branch_clk grp_3d_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(17), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 17, + .reset_mask = P_GRP_3D_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "grp_3d_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(grp_3d_p_clk.c), + }, +}; + +static struct branch_clk jpeg_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(24), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 24, + .reset_mask = P_JPEG_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "jpeg_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(jpeg_p_clk.c), + }, +}; + +static struct branch_clk lpa_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(7), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 7, + .reset_mask = P_LPA_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "lpa_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(lpa_p_clk.c), + }, +}; + +static struct branch_clk mdp_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(6), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 6, + .reset_mask = P_MDP_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "mdp_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdp_p_clk.c), + }, +}; + +static struct branch_clk mfc_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(26), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 26, + .reset_mask = P_MFC_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "mfc_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(mfc_p_clk.c), + }, +}; + +static struct branch_clk pmdh_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(4), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 4, + .reset_mask = P_PMDH_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "pmdh_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(pmdh_p_clk.c), + }, +}; + +static struct branch_clk rotator_imem_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(23), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 23, + .reset_mask = P_ROTATOR_IMEM_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "rotator_imem_clk", + .ops = &clk_ops_branch, + CLK_INIT(rotator_imem_clk.c), + }, +}; + +static struct branch_clk rotator_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(25), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 25, + .reset_mask = P_ROTATOR_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "rotator_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(rotator_p_clk.c), + }, +}; + +static struct branch_clk sdc1_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(7), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 7, + .reset_mask = P_SDC1_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "sdc1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc1_p_clk.c), + }, +}; + +static struct branch_clk sdc2_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(8), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 8, + .reset_mask = P_SDC2_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "sdc2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc2_p_clk.c), + }, +}; + +static struct branch_clk sdc3_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(27), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 27, + .reset_mask = P_SDC3_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "sdc3_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc3_p_clk.c), + }, +}; + +static struct branch_clk sdc4_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(28), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 28, + .reset_mask = P_SDC4_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "sdc4_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc4_p_clk.c), + }, +}; + +static struct branch_clk spi_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(10), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 10, + .reset_mask = P_SPI_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "spi_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(spi_p_clk.c), + }, +}; + +static struct branch_clk tsif_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(18), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 18, + .reset_mask = P_TSIF_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "tsif_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(tsif_p_clk.c), + }, +}; + +static struct branch_clk uart1dm_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(17), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 17, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "uart1dm_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(uart1dm_p_clk.c), + }, +}; + +static struct branch_clk uart2dm_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(26), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 26, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "uart2dm_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(uart2dm_p_clk.c), + }, +}; + +static struct branch_clk usb_hs2_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(8), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 8, + .reset_mask = P_USB_HS2_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "usb_hs2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs2_p_clk.c), + }, +}; + +static struct branch_clk usb_hs3_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(9), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 9, + .reset_mask = P_USB_HS3_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "usb_hs3_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs3_p_clk.c), + }, +}; + +static struct branch_clk usb_hs_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_SC_REG, + .en_mask = BIT(25), + .halt_reg = GLBL_CLK_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 25, + .reset_mask = P_USB_HS_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "usb_hs_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs_p_clk.c), + }, +}; + +static struct branch_clk vfe_p_clk = { + .b = { + .ctl_reg = GLBL_CLK_ENA_2_SC_REG, + .en_mask = BIT(27), + .halt_reg = GLBL_CLK_STATE_2_REG, + .halt_check = HALT_VOTED, + .halt_bit = 27, + .reset_mask = P_VFE_P_CLK, + }, + .parent = &glbl_root_clk.c, + .c = { + .dbg_name = "vfe_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(vfe_p_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_csi[] = { + F_MND8( 0, 0, 0, gnd, 1, 0, 0, NONE), + F_MND8(153600000, 24, 17, pll1, 2, 2, 5, NOMINAL), + F_MND8(192000000, 24, 17, pll1, 4, 0, 0, NOMINAL), + F_MND8(384000000, 24, 17, pll1, 2, 0, 0, NOMINAL), + F_END, +}; + +static struct rcg_clk csi0_clk = { + .b = { + .ctl_reg = CSI_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 17, + .reset_mask = P_CSI0_CLK, + }, + .ns_reg = CSI_NS_REG, + .md_reg = CSI_NS_REG - 4, + .ns_mask = F_MASK_MND8(24, 17), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_csi, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "csi0_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(csi0_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_tcxo[] = { + F_RAW(19200000, &tcxo_clk.c, 0, 0, 0, 0, NOMINAL, NULL), + F_END, +}; + +static struct rcg_clk i2c_clk = { + .b = { + .ctl_reg = I2C_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 15, + .reset_mask = P_I2C_CLK, + }, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_tcxo, + .root_en_mask = BIT(11), + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "i2c_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(i2c_clk.c), + }, +}; + +static struct rcg_clk i2c_2_clk = { + .b = { + .ctl_reg = I2C_2_NS_REG, + .en_mask = BIT(0), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 2, + .reset_mask = P_I2C_2_CLK, + }, + .root_en_mask = BIT(2), + .freq_tbl = clk_tbl_tcxo, + .set_rate = set_rate_nop, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "i2c_2_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(i2c_2_clk.c), + }, +}; + +static struct rcg_clk qup_i2c_clk = { + .b = { + .ctl_reg = QUP_I2C_NS_REG, + .en_mask = BIT(0), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 31, + .reset_mask = P_QUP_I2C_CLK, + }, + .root_en_mask = BIT(2), + .freq_tbl = clk_tbl_tcxo, + .set_rate = set_rate_nop, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "qup_i2c_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(qup_i2c_clk.c), + }, +}; + +static struct rcg_clk uart1_clk = { + .b = { + .ctl_reg = UART_NS_REG, + .en_mask = BIT(5), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 7, + .reset_mask = P_UART1_CLK, + }, + .root_en_mask = BIT(4), + .freq_tbl = clk_tbl_tcxo, + .set_rate = set_rate_nop, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "uart1_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(uart1_clk.c), + }, +}; + +static struct rcg_clk uart2_clk = { + .b = { + .ctl_reg = UART2_NS_REG, + .en_mask = BIT(5), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 5, + .reset_mask = P_UART2_CLK, + }, + .root_en_mask = BIT(4), + .freq_tbl = clk_tbl_tcxo, + .set_rate = set_rate_nop, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "uart2_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(uart2_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_uartdm[] = { + F_MND16( 0, gnd, 1, 0, 0, NONE), + F_MND16( 3686400, pll3, 3, 3, 200, NOMINAL), + F_MND16( 7372800, pll3, 3, 3, 100, NOMINAL), + F_MND16(14745600, pll3, 3, 3, 50, NOMINAL), + F_MND16(32000000, pll3, 3, 25, 192, NOMINAL), + F_MND16(40000000, pll3, 3, 125, 768, NOMINAL), + F_MND16(46400000, pll3, 3, 145, 768, NOMINAL), + F_MND16(48000000, pll3, 3, 25, 128, NOMINAL), + F_MND16(51200000, pll3, 3, 5, 24, NOMINAL), + F_MND16(56000000, pll3, 3, 175, 768, NOMINAL), + F_MND16(58982400, pll3, 3, 6, 25, NOMINAL), + F_MND16(64000000, pll1, 4, 1, 3, NOMINAL), + F_END, +}; + +static struct rcg_clk uart1dm_clk = { + .b = { + .ctl_reg = UART1DM_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 6, + .reset_mask = P_UART1DM_CLK, + }, + .ns_reg = UART1DM_NS_REG, + .md_reg = UART1DM_NS_REG - 4, + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_uartdm, + .ns_mask = F_MASK_MND16, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "uart1dm_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(uart1dm_clk.c), + }, +}; + +static struct rcg_clk uart2dm_clk = { + .b = { + .ctl_reg = UART2DM_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 23, + .reset_mask = P_UART2DM_CLK, + }, + .ns_reg = UART2DM_NS_REG, + .md_reg = UART2DM_NS_REG - 4, + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_uartdm, + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "uart2dm_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(uart2dm_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mdh[] = { + F_BASIC( 0, gnd, 1, NONE), + F_BASIC( 49150000, pll3, 15, NOMINAL), + F_BASIC( 92160000, pll3, 8, NOMINAL), + F_BASIC(122880000, pll3, 6, NOMINAL), + F_BASIC(184320000, pll3, 4, NOMINAL), + F_BASIC(245760000, pll3, 3, NOMINAL), + F_BASIC(368640000, pll3, 2, NOMINAL), + F_BASIC(384000000, pll1, 2, NOMINAL), + F_BASIC(445500000, pll4, 2, NOMINAL), + F_END, +}; + +static struct rcg_clk emdh_clk = { + .b = { + .ctl_reg = EMDH_NS_REG, + .halt_check = DELAY, + .reset_mask = P_EMDH_CLK, + }, + .root_en_mask = BIT(11), + .ns_reg = EMDH_NS_REG, + .ns_mask = F_MASK_BASIC, + .depends = &axi_li_adsp_a_clk.c, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_mdh, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "emdh_clk", + .flags = CLKFLAG_MIN | CLKFLAG_MAX, + .ops = &soc_clk_ops_7x30, + CLK_INIT(emdh_clk.c), + }, +}; + +static struct rcg_clk pmdh_clk = { + .b = { + .ctl_reg = PMDH_NS_REG, + .halt_check = DELAY, + .reset_mask = P_PMDH_CLK, + }, + .root_en_mask = BIT(11), + .ns_reg = PMDH_NS_REG, + .ns_mask = F_MASK_BASIC, + .depends = &axi_li_adsp_a_clk.c, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_mdh, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "pmdh_clk", + .flags = CLKFLAG_MIN | CLKFLAG_MAX, + .ops = &soc_clk_ops_7x30, + CLK_INIT(pmdh_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_grp[] = { + F_BASIC( 24576000, lpxo, 1, NOMINAL), + F_BASIC( 46080000, pll3, 16, NOMINAL), + F_BASIC( 49152000, pll3, 15, NOMINAL), + F_BASIC( 52662875, pll3, 14, NOMINAL), + F_BASIC( 56713846, pll3, 13, NOMINAL), + F_BASIC( 61440000, pll3, 12, NOMINAL), + F_BASIC( 67025454, pll3, 11, NOMINAL), + F_BASIC( 73728000, pll3, 10, NOMINAL), + F_BASIC( 81920000, pll3, 9, NOMINAL), + F_BASIC( 92160000, pll3, 8, NOMINAL), + F_BASIC(105325714, pll3, 7, NOMINAL), + F_BASIC(122880000, pll3, 6, NOMINAL), + F_BASIC(147456000, pll3, 5, NOMINAL), + F_BASIC(184320000, pll3, 4, NOMINAL), + F_BASIC(192000000, pll1, 4, NOMINAL), + F_BASIC(245760000, pll3, 3, HIGH), + /* Sync to AXI. Hence this "rate" is not fixed. */ + F_RAW(1, &lpxo_clk.c, 0, BIT(14), 0, 0, NOMINAL, NULL), + F_END, +}; + +static struct rcg_clk grp_2d_clk = { + .b = { + .ctl_reg = GRP_2D_NS_REG, + .en_mask = BIT(7), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 31, + .reset_mask = P_GRP_2D_CLK, + }, + .ns_reg = GRP_2D_NS_REG, + .root_en_mask = BIT(11), + .ns_mask = F_MASK_BASIC | (7 << 12), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_grp, + .depends = &axi_grp_2d_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "grp_2d_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(grp_2d_clk.c), + }, +}; + +static struct rcg_clk grp_3d_src_clk = { + .ns_reg = GRP_NS_REG, + .b = { + .ctl_reg = GRP_NS_REG, + .halt_check = NOCHECK, + }, + .root_en_mask = BIT(11), + .ns_mask = F_MASK_BASIC | (7 << 12), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_grp, + .depends = &axi_li_grp_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "grp_3d_src_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(grp_3d_src_clk.c), + }, +}; + +static struct branch_clk grp_3d_clk = { + .b = { + .ctl_reg = GRP_NS_REG, + .en_mask = BIT(7), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 18, + .reset_mask = P_GRP_3D_CLK, + }, + .parent = &grp_3d_src_clk.c, + .c = { + .dbg_name = "grp_3d_clk", + .ops = &clk_ops_branch, + CLK_INIT(grp_3d_clk.c), + }, +}; + +static struct branch_clk imem_clk = { + .b = { + .ctl_reg = GRP_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 19, + .reset_mask = P_IMEM_CLK, + }, + .parent = &grp_3d_src_clk.c, + .c = { + .dbg_name = "imem_clk", + .ops = &clk_ops_branch, + CLK_INIT(imem_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_sdc1_3[] = { + F_MND8( 0, 0, 0, gnd, 1, 0, 0, NONE), + F_MND8( 144000, 19, 12, lpxo, 1, 1, 171, NOMINAL), + F_MND8( 400000, 19, 12, lpxo, 1, 2, 123, NOMINAL), + F_MND8(16027000, 19, 12, pll3, 3, 14, 215, NOMINAL), + F_MND8(17000000, 19, 12, pll3, 4, 19, 206, NOMINAL), + F_MND8(20480000, 19, 12, pll3, 4, 23, 212, NOMINAL), + F_MND8(24576000, 19, 12, lpxo, 1, 0, 0, NOMINAL), + F_MND8(49152000, 19, 12, pll3, 3, 1, 5, NOMINAL), + F_END, +}; + +static struct rcg_clk sdc1_clk = { + .b = { + .ctl_reg = SDCn_NS_REG(1), + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 1, + .reset_mask = P_SDC1_CLK, + }, + .ns_reg = SDCn_NS_REG(1), + .md_reg = SDCn_NS_REG(1) - 4, + .ns_mask = F_MASK_MND8(19, 12), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_sdc1_3, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "sdc1_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(sdc1_clk.c), + }, +}; + +static struct rcg_clk sdc3_clk = { + .b = { + .ctl_reg = SDCn_NS_REG(3), + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 24, + .reset_mask = P_SDC3_CLK, + }, + .ns_reg = SDCn_NS_REG(3), + .md_reg = SDCn_NS_REG(3) - 4, + .ns_mask = F_MASK_MND8(19, 12), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_sdc1_3, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "sdc3_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(sdc3_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_sdc2_4[] = { + F_MND8( 0, 0, 0, gnd, 1, 0, 0, NONE), + F_MND8( 144000, 20, 13, lpxo, 1, 1, 171, NOMINAL), + F_MND8( 400000, 20, 13, lpxo, 1, 2, 123, NOMINAL), + F_MND8(16027000, 20, 13, pll3, 3, 14, 215, NOMINAL), + F_MND8(17000000, 20, 13, pll3, 4, 19, 206, NOMINAL), + F_MND8(20480000, 20, 13, pll3, 4, 23, 212, NOMINAL), + F_MND8(24576000, 20, 13, lpxo, 1, 0, 0, NOMINAL), + F_MND8(49152000, 20, 13, pll3, 3, 1, 5, NOMINAL), + F_END, +}; + +static struct rcg_clk sdc2_clk = { + .b = { + .ctl_reg = SDCn_NS_REG(2), + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 0, + .reset_mask = P_SDC2_CLK, + }, + .ns_reg = SDCn_NS_REG(2), + .md_reg = SDCn_NS_REG(2) - 4, + .ns_mask = F_MASK_MND8(20, 13), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_sdc2_4, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "sdc2_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(sdc2_clk.c), + }, +}; + +static struct rcg_clk sdc4_clk = { + .b = { + .ctl_reg = SDCn_NS_REG(4), + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 25, + .reset_mask = P_SDC4_CLK, + }, + .ns_reg = SDCn_NS_REG(4), + .md_reg = SDCn_NS_REG(4) - 4, + .ns_mask = F_MASK_MND8(20, 13), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_sdc2_4, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "sdc4_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(sdc4_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mdp_core[] = { + F_BASIC( 24576000, lpxo, 1, NOMINAL), + F_BASIC( 46080000, pll3, 16, NOMINAL), + F_BASIC( 49152000, pll3, 15, NOMINAL), + F_BASIC( 52663000, pll3, 14, NOMINAL), + F_BASIC( 92160000, pll3, 8, NOMINAL), + F_BASIC(122880000, pll3, 6, NOMINAL), + F_BASIC(147456000, pll3, 5, NOMINAL), + F_BASIC(153600000, pll1, 5, NOMINAL), + F_BASIC(192000000, pll1, 4, HIGH), + F_END, +}; + +static struct rcg_clk mdp_clk = { + .b = { + .ctl_reg = MDP_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 16, + .reset_mask = P_MDP_CLK, + }, + .ns_reg = MDP_NS_REG, + .root_en_mask = BIT(11), + .ns_mask = F_MASK_BASIC, + .depends = &axi_mdp_clk.c, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_mdp_core, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mdp_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(mdp_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mdp_lcdc[] = { + F_MND16( 0, gnd, 1, 0, 0, NONE), + F_MND16(24576000, lpxo, 1, 0, 0, NOMINAL), + F_MND16(30720000, pll3, 4, 1, 6, NOMINAL), + F_MND16(32768000, pll3, 3, 2, 15, NOMINAL), + F_MND16(40960000, pll3, 2, 1, 9, NOMINAL), + F_MND16(73728000, pll3, 2, 1, 5, NOMINAL), + F_END, +}; + +static struct rcg_clk mdp_lcdc_pclk_clk = { + .b = { + .ctl_reg = MDP_LCDC_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 28, + .reset_mask = P_MDP_LCDC_PCLK_CLK, + }, + .ns_reg = MDP_LCDC_NS_REG, + .md_reg = MDP_LCDC_NS_REG - 4, + .root_en_mask = BIT(11), + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_mdp_lcdc, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mdp_lcdc_pclk_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(mdp_lcdc_pclk_clk.c), + }, +}; + +static struct branch_clk mdp_lcdc_pad_pclk_clk = { + .b = { + .ctl_reg = MDP_LCDC_NS_REG, + .en_mask = BIT(12), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 29, + .reset_mask = P_MDP_LCDC_PAD_PCLK_CLK, + }, + .parent = &mdp_lcdc_pclk_clk.c, + .c = { + .dbg_name = "mdp_lcdc_pad_pclk_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdp_lcdc_pad_pclk_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mdp_vsync[] = { + F_RAW( 0, &gnd_clk.c, 0, (0x3<<2), 0, 0, NONE, NULL), + F_RAW(24576000, &lpxo_clk.c, 0, (0x1<<2), 0, 0, NOMINAL, NULL), + F_END, +}; + +static struct rcg_clk mdp_vsync_clk = { + .b = { + .ctl_reg = MDP_VSYNC_REG, + .en_mask = BIT(0), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 30, + .reset_mask = P_MDP_VSYNC_CLK, + }, + .ns_reg = MDP_VSYNC_REG, + .ns_mask = BM(3, 2), + .freq_tbl = clk_tbl_mdp_vsync, + .set_rate = set_rate_nop, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mdp_vsync_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(mdp_vsync_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mi2s_codec[] = { + F_MND16( 0, gnd, 1, 0, 0, NONE), + F_MND16( 2048000, lpxo, 4, 1, 3, NOMINAL), + F_MND16(12288000, lpxo, 2, 0, 0, NOMINAL), + F_END, +}; + +static struct rcg_clk mi2s_codec_rx_m_clk = { + .b = { + .ctl_reg = MI2S_RX_NS_REG, + .en_mask = BIT(12), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 12, + .reset_mask = P_MI2S_CODEC_RX_M_CLK, + }, + .ns_reg = MI2S_RX_NS_REG, + .md_reg = MI2S_RX_NS_REG - 4, + .root_en_mask = BIT(11), + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_mi2s_codec, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mi2s_codec_rx_m_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(mi2s_codec_rx_m_clk.c), + }, +}; + +static struct branch_clk mi2s_codec_rx_s_clk = { + .b = { + .ctl_reg = MI2S_RX_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 13, + .reset_mask = P_MI2S_CODEC_RX_S_CLK, + }, + .parent = &mi2s_codec_rx_m_clk.c, + .c = { + .dbg_name = "mi2s_codec_rx_s_clk", + .ops = &clk_ops_branch, + CLK_INIT(mi2s_codec_rx_s_clk.c), + }, +}; + +static struct rcg_clk mi2s_codec_tx_m_clk = { + .b = { + .ctl_reg = MI2S_TX_NS_REG, + .en_mask = BIT(12), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 8, + .reset_mask = P_MI2S_CODEC_TX_M_CLK, + }, + .ns_reg = MI2S_TX_NS_REG, + .md_reg = MI2S_TX_NS_REG - 4, + .root_en_mask = BIT(11), + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_mi2s_codec, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mi2s_codec_tx_m_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(mi2s_codec_tx_m_clk.c), + }, +}; + +static struct branch_clk mi2s_codec_tx_s_clk = { + .b = { + .ctl_reg = MI2S_TX_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 11, + .reset_mask = P_MI2S_CODEC_TX_S_CLK, + }, + .parent = &mi2s_codec_tx_m_clk.c, + .c = { + .dbg_name = "mi2s_codec_tx_s_clk", + .ops = &clk_ops_branch, + CLK_INIT(mi2s_codec_tx_s_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mi2s[] = { + F_MND16( 0, gnd, 1, 0, 0, NONE), + F_MND16(12288000, lpxo, 2, 0, 0, NOMINAL), + F_END, +}; + +static struct rcg_clk mi2s_m_clk = { + .b = { + .ctl_reg = MI2S_NS_REG, + .en_mask = BIT(12), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 4, + .reset_mask = P_MI2S_M_CLK, + }, + .ns_reg = MI2S_NS_REG, + .md_reg = MI2S_NS_REG - 4, + .root_en_mask = BIT(11), + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_mi2s, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mi2s_m_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(mi2s_m_clk.c), + }, +}; + +static struct branch_clk mi2s_s_clk = { + .b = { + .ctl_reg = MI2S_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 3, + .reset_mask = P_MI2S_S_CLK, + }, + .parent = &mi2s_m_clk.c, + .c = { + .dbg_name = "mi2s_s_clk", + .ops = &clk_ops_branch, + CLK_INIT(mi2s_s_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_midi[] = { + F_MND8( 0, 0, 0, gnd, 1, 0, 0, NONE), + F_MND8(98304000, 19, 12, pll3, 3, 2, 5, NOMINAL), + F_END, +}; + +static struct rcg_clk midi_clk = { + .b = { + .ctl_reg = MIDI_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 1, + }, + .ns_reg = MIDI_NS_REG, + .md_reg = MIDI_NS_REG - 4, + .ns_mask = F_MASK_MND8(19, 12), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_midi, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "midi_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(midi_clk.c), + }, +}; + +#define F_SDAC(f, s, div, m, n, v) \ + { \ + .freq_hz = f, \ + .md_val = MD16(m, n), \ + .ns_val = N16(m, n) | SPDIV(SRC_SEL_SDAC_##s, div), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + .src_clk = &s##_clk.c, \ + } + +static struct clk_freq_tbl clk_tbl_sdac[] = { + F_SDAC( 256000, lpxo, 4, 1, 24, NOMINAL), + F_SDAC( 352800, lpxo, 1, 147, 10240, NOMINAL), + F_SDAC( 384000, lpxo, 4, 1, 16, NOMINAL), + F_SDAC( 512000, lpxo, 4, 1, 12, NOMINAL), + F_SDAC( 705600, lpxo, 1, 147, 5120, NOMINAL), + F_SDAC( 768000, lpxo, 4, 1, 8, NOMINAL), + F_SDAC(1024000, lpxo, 4, 1, 6, NOMINAL), + F_SDAC(1411200, lpxo, 1, 147, 2560, NOMINAL), + F_SDAC(1536000, lpxo, 4, 1, 4, NOMINAL), + F_END, +}; + +static struct rcg_clk sdac_clk = { + .b = { + .ctl_reg = SDAC_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 2, + .reset_mask = P_SDAC_CLK, + }, + .ns_reg = SDAC_NS_REG, + .md_reg = SDAC_NS_REG - 4, + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_sdac, + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "sdac_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(sdac_clk.c), + }, +}; + +static struct branch_clk sdac_m_clk = { + .b = { + .ctl_reg = SDAC_NS_REG, + .en_mask = BIT(12), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 17, + .reset_mask = P_SDAC_M_CLK, + }, + .parent = &sdac_clk.c, + .c = { + .dbg_name = "sdac_m_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdac_m_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_tv[] = { + F_MND8( 0, 0, 0, gnd, 1, 0, 0, NONE), + F_MND8(27000000, 23, 16, pll4, 2, 2, 33, NOMINAL), + F_MND8(74250000, 23, 16, pll4, 2, 1, 6, NOMINAL), + F_END, +}; + +static struct rcg_clk tv_clk = { + .ns_reg = TV_NS_REG, + .b = { + .ctl_reg = TV_NS_REG, + .halt_check = NOCHECK, + }, + .md_reg = TV_NS_REG - 4, + .ns_mask = F_MASK_MND8(23, 16), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_tv, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "tv_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(tv_clk.c), + }, +}; + +static struct branch_clk hdmi_clk = { + .b = { + .ctl_reg = HDMI_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 7, + .reset_mask = P_HDMI_CLK, + }, + .parent = &tv_clk.c, + .c = { + .dbg_name = "hdmi_clk", + .ops = &clk_ops_branch, + CLK_INIT(hdmi_clk.c), + }, +}; + +static struct branch_clk tv_dac_clk = { + .b = { + .ctl_reg = TV_NS_REG, + .en_mask = BIT(12), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 27, + .reset_mask = P_TV_DAC_CLK, + }, + .parent = &tv_clk.c, + .c = { + .dbg_name = "tv_dac_clk", + .ops = &clk_ops_branch, + CLK_INIT(tv_dac_clk.c), + }, +}; + +static struct branch_clk tv_enc_clk = { + .b = { + .ctl_reg = TV_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 10, + .reset_mask = P_TV_ENC_CLK, + }, + .parent = &tv_clk.c, + .c = { + .dbg_name = "tv_enc_clk", + .ops = &clk_ops_branch, + CLK_INIT(tv_enc_clk.c), + }, +}; + +/* Hacking root & branch into one param. */ +static struct branch_clk tsif_ref_clk = { + .b = { + .ctl_reg = TSIF_NS_REG, + .en_mask = BIT(9)|BIT(11), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 11, + .reset_mask = P_TSIF_REF_CLK, + }, + .parent = &tv_clk.c, + .c = { + .dbg_name = "tsif_ref_clk", + .ops = &clk_ops_branch, + CLK_INIT(tsif_ref_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_usb[] = { + F_MND8( 0, 0, 0, gnd, 1, 0, 0, NONE), + F_MND8(60000000, 23, 16, pll1, 2, 5, 32, NOMINAL), + F_END, +}; + +static struct rcg_clk usb_hs_src_clk = { + .ns_reg = USBH_NS_REG, + .b = { + .ctl_reg = USBH_NS_REG, + .halt_check = NOCHECK, + }, + .md_reg = USBH_NS_REG - 4, + .ns_mask = F_MASK_MND8(23, 16), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_usb, + .current_freq = &local_dummy_freq, + .depends = &axi_li_adsp_a_clk.c, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "usb_hs_src_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(usb_hs_src_clk.c), + }, +}; + +static struct branch_clk usb_hs_clk = { + .b = { + .ctl_reg = USBH_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 26, + .reset_mask = P_USB_HS_CLK, + }, + .c = { + .dbg_name = "usb_hs_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs_clk.c), + }, +}; + +static struct branch_clk usb_hs_core_clk = { + .b = { + .ctl_reg = USBH_NS_REG, + .en_mask = BIT(13), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 27, + .reset_mask = P_USB_HS_CORE_CLK, + }, + .parent = &usb_hs_src_clk.c, + .c = { + .dbg_name = "usb_hs_core_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs_core_clk.c), + }, +}; + +static struct branch_clk usb_hs2_clk = { + .b = { + .ctl_reg = USBH2_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 3, + .reset_mask = P_USB_HS2_CLK, + }, + .parent = &usb_hs_src_clk.c, + .c = { + .dbg_name = "usb_hs2_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs2_clk.c), + }, +}; + +static struct branch_clk usb_hs2_core_clk = { + .b = { + .ctl_reg = USBH2_NS_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 28, + .reset_mask = P_USB_HS2_CORE_CLK, + }, + .parent = &usb_hs_src_clk.c, + .c = { + .dbg_name = "usb_hs2_core_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs2_core_clk.c), + }, +}; + +static struct branch_clk usb_hs3_clk = { + .b = { + .ctl_reg = USBH3_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 2, + .reset_mask = P_USB_HS3_CLK, + }, + .parent = &usb_hs_src_clk.c, + .c = { + .dbg_name = "usb_hs3_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs3_clk.c), + }, +}; + +static struct branch_clk usb_hs3_core_clk = { + .b = { + .ctl_reg = USBH3_NS_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 29, + .reset_mask = P_USB_HS3_CORE_CLK, + }, + .parent = &usb_hs_src_clk.c, + .c = { + .dbg_name = "usb_hs3_core_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs3_core_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_vfe_jpeg[] = { + F_MND16( 24576000, lpxo, 1, 0, 0, NOMINAL), + F_MND16( 36864000, pll3, 4, 1, 5, NOMINAL), + F_MND16( 46080000, pll3, 4, 1, 4, NOMINAL), + F_MND16( 61440000, pll3, 4, 1, 3, NOMINAL), + F_MND16( 73728000, pll3, 2, 1, 5, NOMINAL), + F_MND16( 81920000, pll3, 3, 1, 3, NOMINAL), + F_MND16( 92160000, pll3, 4, 1, 2, NOMINAL), + F_MND16( 98304000, pll3, 3, 2, 5, NOMINAL), + F_MND16(105326000, pll3, 2, 2, 7, NOMINAL), + F_MND16(122880000, pll3, 2, 1, 3, NOMINAL), + F_MND16(147456000, pll3, 2, 2, 5, NOMINAL), + F_MND16(153600000, pll1, 2, 2, 5, NOMINAL), + F_MND16(192000000, pll1, 4, 0, 0, HIGH), + F_END, +}; + +static struct rcg_clk jpeg_clk = { + .b = { + .ctl_reg = JPEG_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 1, + .reset_mask = P_JPEG_CLK, + }, + .ns_reg = JPEG_NS_REG, + .md_reg = JPEG_NS_REG - 4, + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_vfe_jpeg, + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .depends = &axi_li_jpeg_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "jpeg_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(jpeg_clk.c), + }, +}; + +static struct rcg_clk vfe_clk = { + .b = { + .ctl_reg = CAM_VFE_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEB_REG, + .halt_bit = 0, + .reset_mask = P_VFE_CLK, + }, + .ns_reg = CAM_VFE_NS_REG, + .md_reg = CAM_VFE_NS_REG - 4, + .root_en_mask = BIT(13), + .freq_tbl = clk_tbl_vfe_jpeg, + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .depends = &axi_li_vfe_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "vfe_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(vfe_clk.c), + }, +}; + +static struct branch_clk vfe_mdc_clk = { + .b = { + .ctl_reg = CAM_VFE_NS_REG, + .en_mask = BIT(11), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 9, + .reset_mask = P_VFE_MDC_CLK, + }, + .parent = &vfe_clk.c, + .c = { + .dbg_name = "vfe_mdc_clk", + .ops = &clk_ops_branch, + CLK_INIT(vfe_mdc_clk.c), + }, +}; + +static struct branch_clk vfe_camif_clk = { + .b = { + .ctl_reg = CAM_VFE_NS_REG, + .en_mask = BIT(15), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 13, + .reset_mask = P_VFE_CAMIF_CLK, + }, + .parent = &vfe_clk.c, + .c = { + .dbg_name = "vfe_camif_clk", + .ops = &clk_ops_branch, + CLK_INIT(vfe_camif_clk.c), + }, +}; + +static struct branch_clk csi0_vfe_clk = { + .b = { + .ctl_reg = CSI_NS_REG, + .en_mask = BIT(15), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 16, + .reset_mask = P_CSI0_VFE_CLK, + }, + .parent = &vfe_clk.c, + .c = { + .dbg_name = "csi0_vfe_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0_vfe_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_cam[] = { + F_MND16( 0, gnd, 1, 0, 0, NONE), + F_MND16( 6000000, pll1, 4, 1, 32, NOMINAL), + F_MND16( 8000000, pll1, 4, 1, 24, NOMINAL), + F_MND16(12000000, pll1, 4, 1, 16, NOMINAL), + F_MND16(16000000, pll1, 4, 1, 12, NOMINAL), + F_MND16(19200000, pll1, 4, 1, 10, NOMINAL), + F_MND16(24000000, pll1, 4, 1, 8, NOMINAL), + F_MND16(32000000, pll1, 4, 1, 6, NOMINAL), + F_MND16(48000000, pll1, 4, 1, 4, NOMINAL), + F_MND16(64000000, pll1, 4, 1, 3, NOMINAL), + F_END, +}; + +static struct rcg_clk cam_m_clk = { + .b = { + .ctl_reg = CAM_NS_REG, + .halt_check = DELAY, + .reset_mask = P_CAM_M_CLK, + }, + .ns_reg = CAM_NS_REG, + .md_reg = CAM_NS_REG - 4, + .root_en_mask = BIT(9), + .freq_tbl = clk_tbl_cam, + .ns_mask = F_MASK_MND16, + .set_rate = set_rate_mnd, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "cam_m_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(cam_m_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_vpe[] = { + F_MND8( 24576000, 22, 15, lpxo, 1, 0, 0, NOMINAL), + F_MND8( 30720000, 22, 15, pll3, 4, 1, 6, NOMINAL), + F_MND8( 61440000, 22, 15, pll3, 4, 1, 3, NOMINAL), + F_MND8( 81920000, 22, 15, pll3, 3, 1, 3, NOMINAL), + F_MND8(122880000, 22, 15, pll3, 3, 1, 2, NOMINAL), + F_MND8(147456000, 22, 15, pll3, 1, 1, 5, NOMINAL), + F_MND8(153600000, 22, 15, pll1, 1, 1, 5, NOMINAL), + F_END, +}; + +static struct rcg_clk vpe_clk = { + .b = { + .ctl_reg = VPE_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 10, + .reset_mask = P_VPE_CLK, + }, + .ns_reg = VPE_NS_REG, + .md_reg = VPE_NS_REG - 4, + .ns_mask = F_MASK_MND8(22, 15), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_vpe, + .current_freq = &local_dummy_freq, + .depends = &axi_vpe_clk.c, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "vpe_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(vpe_clk.c), + }, +}; + + +static struct clk_freq_tbl clk_tbl_mfc[] = { + F_MND8( 24576000, 24, 17, lpxo, 1, 0, 0, NOMINAL), + F_MND8( 30720000, 24, 17, pll3, 4, 1, 6, NOMINAL), + F_MND8( 61440000, 24, 17, pll3, 4, 1, 3, NOMINAL), + F_MND8( 81920000, 24, 17, pll3, 3, 1, 3, NOMINAL), + F_MND8(122880000, 24, 17, pll3, 3, 1, 2, NOMINAL), + F_MND8(147456000, 24, 17, pll3, 1, 1, 5, NOMINAL), + F_MND8(153600000, 24, 17, pll1, 1, 1, 5, NOMINAL), + F_MND8(170667000, 24, 17, pll1, 1, 2, 9, NOMINAL), + F_END, +}; + +static struct rcg_clk mfc_clk = { + .b = { + .ctl_reg = MFC_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 12, + .reset_mask = P_MFC_CLK, + }, + .ns_reg = MFC_NS_REG, + .md_reg = MFC_NS_REG - 4, + .ns_mask = F_MASK_MND8(24, 17), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_mfc, + .current_freq = &local_dummy_freq, + .depends = &axi_mfc_clk.c, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "mfc_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(mfc_clk.c), + }, +}; + +static struct branch_clk mfc_div2_clk = { + .b = { + .ctl_reg = MFC_NS_REG, + .en_mask = BIT(15), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 11, + .reset_mask = P_MFC_DIV2_CLK, + }, + .parent = &mfc_clk.c, + .c = { + .dbg_name = "mfc_div2_clk", + .ops = &clk_ops_branch, + CLK_INIT(mfc_div2_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_spi[] = { + F_MND8( 0, 0, 0, gnd, 1, 0, 0, NONE), + F_MND8( 9963243, 19, 12, pll3, 4, 2, 37, NOMINAL), + F_MND8(26331429, 19, 12, pll3, 4, 1, 7, NOMINAL), + F_END, +}; + +static struct rcg_clk spi_clk = { + .b = { + .ctl_reg = SPI_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 0, + .reset_mask = P_SPI_CLK, + }, + .ns_reg = SPI_NS_REG, + .md_reg = SPI_NS_REG - 4, + .ns_mask = F_MASK_MND8(19, 12), + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_spi, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_mnd, + .c = { + .dbg_name = "spi_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(spi_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_lpa_codec[] = { + F_RAW(1, NULL, 0, 0, 0, 0, LOW, NULL), /* src MI2S_CODEC_RX */ + F_RAW(2, NULL, 0, 1, 0, 0, LOW, NULL), /* src ECODEC_CIF */ + F_RAW(3, NULL, 0, 2, 0, 0, LOW, NULL), /* src MI2S */ + F_RAW(4, NULL, 0, 3, 0, 0, LOW, NULL), /* src SDAC */ + F_END, +}; + +static struct rcg_clk lpa_codec_clk = { + .b = { + .ctl_reg = LPA_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 6, + .reset_mask = P_LPA_CODEC_CLK, + }, + .ns_reg = LPA_NS_REG, + .ns_mask = BM(1, 0), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_lpa_codec, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "lpa_codec_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(lpa_codec_clk.c), + }, +}; + +static struct clk_freq_tbl clk_tbl_mdc[] = { + F_RAW(1, NULL, 0, 0, 0, 0, LOW, NULL), + F_END +}; + +static struct rcg_clk mdc_clk = { + .b = { + .ctl_reg = MDC_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_STATEA_REG, + .halt_bit = 10, + .reset_mask = P_MDC_CLK, + }, + .ns_reg = MDC_NS_REG, + .root_en_mask = BIT(11), + .freq_tbl = clk_tbl_mdc, + .current_freq = &local_dummy_freq, + .set_rate = set_rate_nop, + .c = { + .dbg_name = "mdc_clk", + .ops = &soc_clk_ops_7x30, + CLK_INIT(mdc_clk.c), + }, +}; + +static struct branch_clk lpa_core_clk = { + .b = { + .ctl_reg = LPA_NS_REG, + .en_mask = BIT(5), + .halt_reg = CLK_HALT_STATEC_REG, + .halt_bit = 5, + .reset_mask = P_LPA_CORE_CLK, + }, + .c = { + .dbg_name = "lpa_core_clk", + .ops = &clk_ops_branch, + CLK_INIT(lpa_core_clk.c), + }, +}; + +static DEFINE_CLK_PCOM(adsp_clk, ADSP_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(codec_ssbi_clk, CODEC_SSBI_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(ebi1_clk, EBI1_CLK, CLKFLAG_SKIP_AUTO_OFF | CLKFLAG_MIN); +static DEFINE_CLK_PCOM(ebi1_fixed_clk, EBI1_FIXED_CLK, CLKFLAG_MIN | + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(ecodec_clk, ECODEC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(gp_clk, GP_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(uart3_clk, UART3_CLK, 0); +static DEFINE_CLK_PCOM(usb_phy_clk, USB_PHY_CLK, CLKFLAG_MIN); + +static DEFINE_CLK_PCOM(p_grp_2d_clk, GRP_2D_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_grp_2d_p_clk, GRP_2D_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_hdmi_clk, HDMI_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_jpeg_clk, JPEG_CLK, 0); +static DEFINE_CLK_PCOM(p_jpeg_p_clk, JPEG_P_CLK, 0); +static DEFINE_CLK_PCOM(p_lpa_codec_clk, LPA_CODEC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_lpa_core_clk, LPA_CORE_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_lpa_p_clk, LPA_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mi2s_m_clk, MI2S_M_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mi2s_s_clk, MI2S_S_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mi2s_codec_rx_m_clk, MI2S_CODEC_RX_M_CLK, + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mi2s_codec_rx_s_clk, MI2S_CODEC_RX_S_CLK, + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mi2s_codec_tx_m_clk, MI2S_CODEC_TX_M_CLK, + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mi2s_codec_tx_s_clk, MI2S_CODEC_TX_S_CLK, + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_sdac_clk, SDAC_CLK, 0); +static DEFINE_CLK_PCOM(p_sdac_m_clk, SDAC_M_CLK, 0); +static DEFINE_CLK_PCOM(p_vfe_clk, VFE_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_vfe_camif_clk, VFE_CAMIF_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_vfe_mdc_clk, VFE_MDC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_vfe_p_clk, VFE_P_CLK, 0); +static DEFINE_CLK_PCOM(p_grp_3d_clk, GRP_3D_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_grp_3d_p_clk, GRP_3D_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_imem_clk, IMEM_CLK, 0); +static DEFINE_CLK_PCOM(p_mdp_lcdc_pad_pclk_clk, MDP_LCDC_PAD_PCLK_CLK, + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mdp_lcdc_pclk_clk, MDP_LCDC_PCLK_CLK, + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mdp_p_clk, MDP_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mdp_vsync_clk, MDP_VSYNC_CLK, 0); +static DEFINE_CLK_PCOM(p_tsif_ref_clk, TSIF_REF_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_tsif_p_clk, TSIF_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_tv_dac_clk, TV_DAC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_tv_enc_clk, TV_ENC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_emdh_clk, EMDH_CLK, CLKFLAG_MIN | CLKFLAG_MAX); +static DEFINE_CLK_PCOM(p_emdh_p_clk, EMDH_P_CLK, 0); +static DEFINE_CLK_PCOM(p_i2c_clk, I2C_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_i2c_2_clk, I2C_2_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mdc_clk, MDC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_pmdh_clk, PMDH_CLK, CLKFLAG_MIN | CLKFLAG_MAX); +static DEFINE_CLK_PCOM(p_pmdh_p_clk, PMDH_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_sdc1_clk, SDC1_CLK, 0); +static DEFINE_CLK_PCOM(p_sdc1_p_clk, SDC1_P_CLK, 0); +static DEFINE_CLK_PCOM(p_sdc2_clk, SDC2_CLK, 0); +static DEFINE_CLK_PCOM(p_sdc2_p_clk, SDC2_P_CLK, 0); +static DEFINE_CLK_PCOM(p_sdc3_clk, SDC3_CLK, 0); +static DEFINE_CLK_PCOM(p_sdc3_p_clk, SDC3_P_CLK, 0); +static DEFINE_CLK_PCOM(p_sdc4_clk, SDC4_CLK, 0); +static DEFINE_CLK_PCOM(p_sdc4_p_clk, SDC4_P_CLK, 0); +static DEFINE_CLK_PCOM(p_uart2_clk, UART2_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_usb_hs2_clk, USB_HS2_CLK, 0); +static DEFINE_CLK_PCOM(p_usb_hs2_core_clk, USB_HS2_CORE_CLK, 0); +static DEFINE_CLK_PCOM(p_usb_hs2_p_clk, USB_HS2_P_CLK, 0); +static DEFINE_CLK_PCOM(p_usb_hs3_clk, USB_HS3_CLK, 0); +static DEFINE_CLK_PCOM(p_usb_hs3_core_clk, USB_HS3_CORE_CLK, 0); +static DEFINE_CLK_PCOM(p_usb_hs3_p_clk, USB_HS3_P_CLK, 0); +static DEFINE_CLK_PCOM(p_qup_i2c_clk, QUP_I2C_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_spi_clk, SPI_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_spi_p_clk, SPI_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_uart1_clk, UART1_CLK, 0); +static DEFINE_CLK_PCOM(p_uart1dm_clk, UART1DM_CLK, 0); +static DEFINE_CLK_PCOM(p_uart2dm_clk, UART2DM_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_usb_hs_clk, USB_HS_CLK, 0); +static DEFINE_CLK_PCOM(p_usb_hs_core_clk, USB_HS_CORE_CLK, 0); +static DEFINE_CLK_PCOM(p_usb_hs_p_clk, USB_HS_P_CLK, 0); +static DEFINE_CLK_PCOM(p_cam_m_clk, CAM_M_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_camif_pad_p_clk, CAMIF_PAD_P_CLK, 0); +static DEFINE_CLK_PCOM(p_csi0_clk, CSI0_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_csi0_vfe_clk, CSI0_VFE_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_csi0_p_clk, CSI0_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mdp_clk, MDP_CLK, 0); +static DEFINE_CLK_PCOM(p_mfc_clk, MFC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mfc_div2_clk, MFC_DIV2_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_mfc_p_clk, MFC_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_vpe_clk, VPE_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_adm_clk, ADM_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_ce_clk, CE_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_axi_rotator_clk, AXI_ROTATOR_CLK, + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(p_rotator_imem_clk, ROTATOR_IMEM_CLK, 0); +static DEFINE_CLK_PCOM(p_rotator_p_clk, ROTATOR_P_CLK, 0); + +static DEFINE_CLK_VOTER(ebi_dtv_clk, &ebi1_fixed_clk.c); +static DEFINE_CLK_VOTER(ebi_kgsl_clk, &ebi1_fixed_clk.c); +static DEFINE_CLK_VOTER(ebi_lcdc_clk, &ebi1_fixed_clk.c); +static DEFINE_CLK_VOTER(ebi_mddi_clk, &ebi1_fixed_clk.c); +static DEFINE_CLK_VOTER(ebi_tv_clk, &ebi1_fixed_clk.c); +static DEFINE_CLK_VOTER(ebi_vcd_clk, &ebi1_fixed_clk.c); +static DEFINE_CLK_VOTER(ebi_vfe_clk, &ebi1_fixed_clk.c); +static DEFINE_CLK_VOTER(ebi_adm_clk, &ebi1_fixed_clk.c); + +/* + * SoC-specific functions required by clock-local driver + */ + +/* Update the sys_vdd voltage given a level. */ +static int msm7x30_update_sys_vdd(enum sys_vdd_level level) +{ + int rc, target_mv; + static const int mv[NUM_SYS_VDD_LEVELS] = { + [NONE...LOW] = 1000, + [NOMINAL] = 1100, + [HIGH] = 1200, + }; + + target_mv = mv[level]; + rc = msm_proc_comm(PCOM_CLKCTL_RPC_MIN_MSMC1, &target_mv, NULL); + if (rc) + goto out; + if (target_mv) { + rc = -EINVAL; + goto out; + } +out: + return rc; +} + +#ifdef CONFIG_DEBUG_FS + +#define CLK_TEST_2(s) (s) +#define CLK_TEST_HS(s) (0x4000 | ((s) << 8)) +#define CLK_TEST_LS(s) (0x4D40 | (s)) + +struct measure_sel { + u32 test_vector; + struct clk *clk; +}; + +static struct measure_sel measure_mux[] = { + { CLK_TEST_2(0x03), &emdh_p_clk.c }, + { CLK_TEST_2(0x04), &pmdh_p_clk.c }, + { CLK_TEST_2(0x06), &mdp_p_clk.c }, + { CLK_TEST_2(0x07), &lpa_p_clk.c }, + { CLK_TEST_2(0x08), &usb_hs2_p_clk.c }, + { CLK_TEST_2(0x09), &spi_clk.c }, + { CLK_TEST_2(0x0A), &midi_clk.c }, + { CLK_TEST_2(0x0B), &i2c_2_clk.c }, + { CLK_TEST_2(0x0D), &mi2s_m_clk.c }, + { CLK_TEST_2(0x0E), &lpa_core_clk.c }, + { CLK_TEST_2(0x0F), &lpa_codec_clk.c }, + { CLK_TEST_2(0x10), &usb_hs3_p_clk.c }, + { CLK_TEST_2(0x11), &adm_p_clk.c }, + { CLK_TEST_2(0x13), &hdmi_clk.c }, + { CLK_TEST_2(0x14), &usb_hs_core_clk.c }, + { CLK_TEST_2(0x15), &usb_hs2_core_clk.c }, + { CLK_TEST_2(0x16), &usb_hs3_core_clk.c }, + { CLK_TEST_2(0x17), &mi2s_codec_tx_s_clk.c }, + { CLK_TEST_2(0x18), &spi_p_clk.c }, + { CLK_TEST_2(0x1A), &camif_pad_p_clk.c }, + { CLK_TEST_2(0x1C), &qup_i2c_clk.c }, + { CLK_TEST_2(0x1F), &mfc_div2_clk.c }, + { CLK_TEST_2(0x38), &mfc_clk.c }, + + { CLK_TEST_HS(0x00), &adm_clk.c }, + { CLK_TEST_HS(0x01), &mdp_lcdc_pad_pclk_clk.c }, + { CLK_TEST_HS(0x02), &mdp_lcdc_pclk_clk.c }, + { CLK_TEST_HS(0x03), &axi_rotator_clk.c }, + { CLK_TEST_HS(0x07), &axi_li_vg_clk.c }, + { CLK_TEST_HS(0x09), &axi_li_apps_clk.c }, + { CLK_TEST_HS(0x0E), &axi_li_jpeg_clk.c }, + { CLK_TEST_HS(0x0F), &emdh_clk.c }, + { CLK_TEST_HS(0x14), &mdp_clk.c }, + { CLK_TEST_HS(0x15), &pmdh_clk.c }, + { CLK_TEST_HS(0x19), &axi_grp_2d_clk.c }, + { CLK_TEST_HS(0x1A), &axi_li_grp_clk.c }, + { CLK_TEST_HS(0x1B), &axi_li_vfe_clk.c }, + { CLK_TEST_HS(0x1C), &grp_2d_clk.c }, + { CLK_TEST_HS(0x1E), &grp_3d_clk.c }, + { CLK_TEST_HS(0x1F), &imem_clk.c }, + { CLK_TEST_HS(0x20), &jpeg_clk.c }, + { CLK_TEST_HS(0x24), &axi_li_adsp_a_clk.c }, + { CLK_TEST_HS(0x26), &rotator_imem_clk.c }, + { CLK_TEST_HS(0x27), &axi_vpe_clk.c }, + { CLK_TEST_HS(0x2A), &axi_mfc_clk.c }, + { CLK_TEST_HS(0x2B), &axi_mdp_clk.c }, + { CLK_TEST_HS(0x2C), &vpe_clk.c }, + { CLK_TEST_HS(0x30), &vfe_camif_clk.c }, + { CLK_TEST_HS(0x31), &csi0_clk.c }, + { CLK_TEST_HS(0x32), &csi0_vfe_clk.c }, + { CLK_TEST_HS(0x33), &csi0_p_clk.c }, + + { CLK_TEST_LS(0x03), &ce_clk.c }, + { CLK_TEST_LS(0x04), &cam_m_clk.c }, + { CLK_TEST_LS(0x0C), &grp_2d_p_clk.c }, + { CLK_TEST_LS(0x0D), &i2c_clk.c }, + { CLK_TEST_LS(0x0E), &mi2s_codec_rx_m_clk.c }, + { CLK_TEST_LS(0x0F), &mi2s_codec_rx_s_clk.c }, + { CLK_TEST_LS(0x10), &mi2s_codec_tx_m_clk.c }, + { CLK_TEST_LS(0x13), &mdp_vsync_clk.c }, + { CLK_TEST_LS(0x15), &vfe_p_clk.c }, + { CLK_TEST_LS(0x16), &mdc_clk.c }, + { CLK_TEST_LS(0x17), &vfe_mdc_clk.c }, + { CLK_TEST_LS(0x18), &usb_hs_p_clk.c }, + { CLK_TEST_LS(0x1C), &uart1dm_p_clk.c }, + { CLK_TEST_LS(0x1E), &jpeg_p_clk.c }, + { CLK_TEST_LS(0x20), &sdac_clk.c }, + { CLK_TEST_LS(0x21), &sdc1_p_clk.c }, + { CLK_TEST_LS(0x22), &sdc1_clk.c }, + { CLK_TEST_LS(0x23), &sdc2_p_clk.c }, + { CLK_TEST_LS(0x24), &sdc2_clk.c }, + { CLK_TEST_LS(0x25), &tsif_p_clk.c }, + { CLK_TEST_LS(0x26), &sdac_m_clk.c }, + { CLK_TEST_LS(0x27), &grp_3d_p_clk.c }, + { CLK_TEST_LS(0x2A), &tsif_ref_clk.c }, + { CLK_TEST_LS(0x2B), &tv_enc_clk.c }, + { CLK_TEST_LS(0x2C), &tv_dac_clk.c }, + { CLK_TEST_LS(0x2D), &rotator_p_clk.c }, + { CLK_TEST_LS(0x2F), &uart1_clk.c }, + { CLK_TEST_LS(0x30), &uart1dm_clk.c }, + { CLK_TEST_LS(0x31), &uart2_clk.c }, + { CLK_TEST_LS(0x33), &usb_hs2_clk.c }, + { CLK_TEST_LS(0x34), &usb_hs3_clk.c }, + { CLK_TEST_LS(0x35), &mfc_p_clk.c }, + { CLK_TEST_LS(0x36), &vfe_clk.c }, + { CLK_TEST_LS(0x39), &sdc3_p_clk.c }, + { CLK_TEST_LS(0x3A), &sdc3_clk.c }, + { CLK_TEST_LS(0x3B), &sdc4_p_clk.c }, + { CLK_TEST_LS(0x3C), &sdc4_clk.c }, + { CLK_TEST_LS(0x3D), &uart2dm_clk.c }, + { CLK_TEST_LS(0x3E), &uart2dm_p_clk.c }, + { CLK_TEST_LS(0x3F), &usb_hs_clk.c }, +}; + +static struct measure_sel *find_measure_sel(struct clk *clk) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(measure_mux); i++) + if (measure_mux[i].clk == clk) + return &measure_mux[i]; + return NULL; +} + +static int measure_clk_set_parent(struct clk *clk, struct clk *parent) +{ + struct measure_sel *p; + unsigned long flags; + + if (!parent) + return -EINVAL; + + p = find_measure_sel(parent); + if (!p) + return -EINVAL; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + /* Program test vector. */ + if (p->test_vector <= 0xFF) { + /* Select CLK_TEST_2 */ + writel_relaxed(0x4D40, CLK_TEST_BASE_REG); + writel_relaxed(p->test_vector, CLK_TEST_2_BASE_REG); + } else + writel_relaxed(p->test_vector, CLK_TEST_BASE_REG); + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +/* Sample clock for 'tcxo4_ticks' reference clock ticks. */ +static u32 run_measurement(unsigned tcxo4_ticks) +{ + /* TCXO4_CNT_EN and RINGOSC_CNT_EN register values. */ + u32 reg_val_enable = readl_relaxed(MISC_CLK_CTL_BASE_REG) | 0x3; + u32 reg_val_disable = reg_val_enable & ~0x3; + + /* Stop counters and set the TCXO4 counter start value. */ + writel_relaxed(reg_val_disable, MISC_CLK_CTL_BASE_REG); + writel_relaxed(tcxo4_ticks, TCXO_CNT_BASE_REG); + + /* Run measurement and wait for completion. */ + writel_relaxed(reg_val_enable, MISC_CLK_CTL_BASE_REG); + while (readl_relaxed(TCXO_CNT_DONE_BASE_REG) == 0) + cpu_relax(); + + /* Stop counters. */ + writel_relaxed(reg_val_disable, MISC_CLK_CTL_BASE_REG); + + return readl_relaxed(RINGOSC_CNT_BASE_REG); +} + +/* Perform a hardware rate measurement for a given clock. + FOR DEBUG USE ONLY: Measurements take ~15 ms! */ +static unsigned measure_clk_get_rate(struct clk *clk) +{ + unsigned long flags; + u32 regval, prph_web_reg_old; + u64 raw_count_short, raw_count_full; + unsigned ret; + + clk_enable(&tcxo_clk.c); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + /* Enable TCXO4 clock branch and root. */ + prph_web_reg_old = readl_relaxed(PRPH_WEB_NS_BASE_REG); + regval = prph_web_reg_old | BIT(9) | BIT(11); + writel_relaxed(regval, PRPH_WEB_NS_BASE_REG); + + /* + * The ring oscillator counter will not reset if the measured clock + * is not running. To detect this, run a short measurement before + * the full measurement. If the raw results of the two are the same + * then the clock must be off. + */ + + /* Run a short measurement. (~1 ms) */ + raw_count_short = run_measurement(0x1000); + /* Run a full measurement. (~14 ms) */ + raw_count_full = run_measurement(0x10000); + + /* Disable TCXO4 clock branch and root. */ + writel_relaxed(prph_web_reg_old, PRPH_WEB_NS_BASE_REG); + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Return 0 if the clock is off. */ + if (raw_count_full == raw_count_short) + ret = 0; + else { + /* Compute rate in Hz. */ + raw_count_full = ((raw_count_full * 10) + 15) * 4800000; + do_div(raw_count_full, ((0x10000 * 10) + 35)); + ret = raw_count_full; + } + + clk_disable(&tcxo_clk.c); + + return ret; +} +#else /* !CONFIG_DEBUG_FS */ +static int measure_clk_set_parent(struct clk *clk, struct clk *parent) +{ + return -EINVAL; +} + +static unsigned measure_clk_get_rate(struct clk *clk) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static struct clk_ops measure_clk_ops = { + .set_parent = measure_clk_set_parent, + .get_rate = measure_clk_get_rate, + .is_local = local_clk_is_local, +}; + +static struct clk measure_clk = { + .dbg_name = "measure_clk", + .ops = &measure_clk_ops, + CLK_INIT(measure_clk), +}; + +/* Implementation for clk_set_flags(). */ +int soc_clk_set_flags(struct clk *clk, unsigned clk_flags) +{ + uint32_t regval, ret = 0; + unsigned long flags; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + if (clk == &vfe_clk.c) { + regval = readl_relaxed(CAM_VFE_NS_REG); + /* Flag values chosen for backward compatibility + * with proc_comm remote clock control. */ + if (clk_flags == 0x00000100) { + /* Select external source. */ + regval |= BIT(14); + } else if (clk_flags == 0x00000200) { + /* Select internal source. */ + regval &= ~BIT(14); + } else + ret = -EINVAL; + + writel_relaxed(regval, CAM_VFE_NS_REG); + /* Make sure write is issued before returning. */ + mb(); + } else + ret = -EPERM; + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return ret; +} + +static int msm7x30_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + /* reset_mask is actually a proc_comm id */ + unsigned id = to_rcg_clk(clk)->b.reset_mask; + return pc_clk_reset(id, action); +} + +static int soc_branch_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + unsigned id = to_branch_clk(clk)->b.reset_mask; + return pc_clk_reset(id, action); +} + +/* + * Clock ownership detection code + */ + +enum { + SH2_OWN_GLBL, + SH2_OWN_APPS1, + SH2_OWN_APPS2, + SH2_OWN_ROW1, + SH2_OWN_ROW2, + SH2_OWN_APPS3, + NUM_OWNERSHIP +}; +static __initdata uint32_t ownership_regs[NUM_OWNERSHIP]; + +static void __init cache_ownership(void) +{ + ownership_regs[SH2_OWN_GLBL] = readl_relaxed(SH2_OWN_GLBL_BASE_REG); + ownership_regs[SH2_OWN_APPS1] = readl_relaxed(SH2_OWN_APPS1_BASE_REG); + ownership_regs[SH2_OWN_APPS2] = readl_relaxed(SH2_OWN_APPS2_BASE_REG); + ownership_regs[SH2_OWN_ROW1] = readl_relaxed(SH2_OWN_ROW1_BASE_REG); + ownership_regs[SH2_OWN_ROW2] = readl_relaxed(SH2_OWN_ROW2_BASE_REG); + ownership_regs[SH2_OWN_APPS3] = readl_relaxed(SH2_OWN_APPS3_BASE_REG); +} + +static void __init print_ownership(void) +{ + pr_info("Clock ownership\n"); + pr_info(" GLBL : %08x\n", ownership_regs[SH2_OWN_GLBL]); + pr_info(" APPS : %08x %08x %08x\n", ownership_regs[SH2_OWN_APPS1], + ownership_regs[SH2_OWN_APPS2], ownership_regs[SH2_OWN_APPS3]); + pr_info(" ROW : %08x %08x\n", ownership_regs[SH2_OWN_ROW1], + ownership_regs[SH2_OWN_ROW2]); +} + +#define O(x) (&ownership_regs[(SH2_OWN_##x)]) +#define OWN(r, b, name, clk, dev) \ + { \ + .lk = CLK_LOOKUP(name, clk.c, dev), \ + .remote = &p_##clk.c, \ + .reg = O(r), \ + .bit = BIT(b), \ + } + +static struct clk_local_ownership { + struct clk_lookup lk; + const u32 *reg; + const u32 bit; + struct clk *remote; +} ownership_map[] __initdata = { + /* Sources */ + { CLK_LOOKUP("pll1_clk", pll1_clk.c, "acpu") }, + { CLK_LOOKUP("pll2_clk", pll2_clk.c, "acpu") }, + { CLK_LOOKUP("pll3_clk", pll3_clk.c, "acpu") }, + { CLK_LOOKUP("measure", measure_clk, "debug") }, + + /* PCOM */ + { CLK_LOOKUP("adsp_clk", adsp_clk.c, NULL) }, + { CLK_LOOKUP("codec_ssbi_clk", codec_ssbi_clk.c, NULL) }, + { CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL) }, + { CLK_LOOKUP("ebi1_fixed_clk", ebi1_fixed_clk.c, NULL) }, + { CLK_LOOKUP("ecodec_clk", ecodec_clk.c, NULL) }, + { CLK_LOOKUP("gp_clk", gp_clk.c, NULL) }, + { CLK_LOOKUP("uart_clk", uart3_clk.c, "msm_serial.2") }, + { CLK_LOOKUP("usb_phy_clk", usb_phy_clk.c, NULL) }, + + /* Voters */ + { CLK_LOOKUP("ebi1_dtv_clk", ebi_dtv_clk.c, NULL) }, + { CLK_LOOKUP("ebi1_kgsl_clk", ebi_kgsl_clk.c, NULL) }, + { CLK_LOOKUP("ebi1_lcdc_clk", ebi_lcdc_clk.c, NULL) }, + { CLK_LOOKUP("ebi1_mddi_clk", ebi_mddi_clk.c, NULL) }, + { CLK_LOOKUP("ebi1_tv_clk", ebi_tv_clk.c, NULL) }, + { CLK_LOOKUP("ebi1_vcd_clk", ebi_vcd_clk.c, NULL) }, + { CLK_LOOKUP("ebi1_vfe_clk", ebi_vfe_clk.c, NULL) }, + { CLK_LOOKUP("ebi1_clk", ebi_adm_clk.c, "msm_dmov") }, + + /* + * This is a many-to-one mapping because we don't know how the remote + * clock code has decided to handle the dependencies between clocks for + * a particular hardware block. We determine the ownership for all the + * clocks going into a block by checking the ownership bit of one + * register (usually the ns register). + */ + OWN(APPS1, 6, "grp_2d_clk", grp_2d_clk, NULL), + OWN(APPS1, 6, "grp_2d_pclk", grp_2d_p_clk, NULL), + OWN(APPS1, 31, "hdmi_clk", hdmi_clk, NULL), + OWN(APPS1, 0, "jpeg_clk", jpeg_clk, NULL), + OWN(APPS1, 0, "jpeg_pclk", jpeg_p_clk, NULL), + OWN(APPS1, 23, "lpa_codec_clk", lpa_codec_clk, NULL), + OWN(APPS1, 23, "lpa_core_clk", lpa_core_clk, NULL), + OWN(APPS1, 23, "lpa_pclk", lpa_p_clk, NULL), + OWN(APPS1, 28, "mi2s_m_clk", mi2s_m_clk, NULL), + OWN(APPS1, 28, "mi2s_s_clk", mi2s_s_clk, NULL), + OWN(APPS1, 12, "mi2s_codec_rx_m_clk", mi2s_codec_rx_m_clk, NULL), + OWN(APPS1, 12, "mi2s_codec_rx_s_clk", mi2s_codec_rx_s_clk, NULL), + OWN(APPS1, 14, "mi2s_codec_tx_m_clk", mi2s_codec_tx_m_clk, NULL), + OWN(APPS1, 14, "mi2s_codec_tx_s_clk", mi2s_codec_tx_s_clk, NULL), + { CLK_LOOKUP("midi_clk", midi_clk.c, NULL), + O(APPS1), BIT(22) }, + OWN(APPS1, 26, "sdac_clk", sdac_clk, NULL), + OWN(APPS1, 26, "sdac_m_clk", sdac_m_clk, NULL), + OWN(APPS1, 8, "vfe_clk", vfe_clk, NULL), + OWN(APPS1, 8, "vfe_camif_clk", vfe_camif_clk, NULL), + OWN(APPS1, 8, "vfe_mdc_clk", vfe_mdc_clk, NULL), + OWN(APPS1, 8, "vfe_pclk", vfe_p_clk, NULL), + + OWN(APPS2, 0, "grp_clk", grp_3d_clk, NULL), + OWN(APPS2, 0, "grp_pclk", grp_3d_p_clk, NULL), + { CLK_LOOKUP("grp_src_clk", grp_3d_src_clk.c, NULL), + O(APPS2), BIT(0), &p_grp_3d_clk.c }, + OWN(APPS2, 0, "imem_clk", imem_clk, NULL), + OWN(APPS2, 4, "mdp_lcdc_pad_pclk_clk", mdp_lcdc_pad_pclk_clk, NULL), + OWN(APPS2, 4, "mdp_lcdc_pclk_clk", mdp_lcdc_pclk_clk, NULL), + OWN(APPS2, 4, "mdp_pclk", mdp_p_clk, NULL), + OWN(APPS2, 28, "mdp_vsync_clk", mdp_vsync_clk, NULL), + OWN(APPS2, 5, "tsif_ref_clk", tsif_ref_clk, NULL), + OWN(APPS2, 5, "tsif_pclk", tsif_p_clk, NULL), + { CLK_LOOKUP("tv_src_clk", tv_clk.c, NULL), + O(APPS2), BIT(2), &p_tv_enc_clk.c }, + OWN(APPS2, 2, "tv_dac_clk", tv_dac_clk, NULL), + OWN(APPS2, 2, "tv_enc_clk", tv_enc_clk, NULL), + + OWN(ROW1, 7, "emdh_clk", emdh_clk, "msm_mddi.1"), + OWN(ROW1, 7, "emdh_pclk", emdh_p_clk, "msm_mddi.1"), + OWN(ROW1, 11, "i2c_clk", i2c_clk, "msm_i2c.0"), + OWN(ROW1, 12, "i2c_clk", i2c_2_clk, "msm_i2c.2"), + OWN(ROW1, 17, "mdc_clk", mdc_clk, NULL), + OWN(ROW1, 19, "mddi_clk", pmdh_clk, NULL), + OWN(ROW1, 19, "mddi_pclk", pmdh_p_clk, NULL), + OWN(ROW1, 23, "sdc_clk", sdc1_clk, "msm_sdcc.1"), + OWN(ROW1, 23, "sdc_pclk", sdc1_p_clk, "msm_sdcc.1"), + OWN(ROW1, 25, "sdc_clk", sdc2_clk, "msm_sdcc.2"), + OWN(ROW1, 25, "sdc_pclk", sdc2_p_clk, "msm_sdcc.2"), + OWN(ROW1, 27, "sdc_clk", sdc3_clk, "msm_sdcc.3"), + OWN(ROW1, 27, "sdc_pclk", sdc3_p_clk, "msm_sdcc.3"), + OWN(ROW1, 29, "sdc_clk", sdc4_clk, "msm_sdcc.4"), + OWN(ROW1, 29, "sdc_pclk", sdc4_p_clk, "msm_sdcc.4"), + OWN(ROW1, 0, "uart_clk", uart2_clk, "msm_serial.1"), + OWN(ROW1, 2, "usb_hs2_clk", usb_hs2_clk, NULL), + OWN(ROW1, 2, "usb_hs2_core_clk", usb_hs2_core_clk, NULL), + OWN(ROW1, 2, "usb_hs2_pclk", usb_hs2_p_clk, NULL), + OWN(ROW1, 4, "usb_hs3_clk", usb_hs3_clk, NULL), + OWN(ROW1, 4, "usb_hs3_core_clk", usb_hs3_core_clk, NULL), + OWN(ROW1, 4, "usb_hs3_pclk", usb_hs3_p_clk, NULL), + + OWN(ROW2, 3, "qup_clk", qup_i2c_clk, "qup_i2c.4"), + OWN(ROW2, 1, "spi_clk", spi_clk, NULL), + OWN(ROW2, 1, "spi_pclk", spi_p_clk, NULL), + OWN(ROW2, 9, "uart_clk", uart1_clk, "msm_serial.0"), + OWN(ROW2, 6, "uartdm_clk", uart1dm_clk, "msm_serial_hs.0"), + OWN(ROW2, 8, "uartdm_clk", uart2dm_clk, "msm_serial_hs.1"), + OWN(ROW2, 11, "usb_hs_clk", usb_hs_clk, NULL), + OWN(ROW2, 11, "usb_hs_core_clk", usb_hs_core_clk, NULL), + OWN(ROW2, 11, "usb_hs_pclk", usb_hs_p_clk, NULL), + + OWN(APPS3, 6, "cam_m_clk", cam_m_clk, NULL), + OWN(APPS3, 6, "camif_pad_pclk", camif_pad_p_clk, NULL), + OWN(APPS3, 11, "csi_clk", csi0_clk, NULL), + OWN(APPS3, 11, "csi_vfe_clk", csi0_vfe_clk, NULL), + OWN(APPS3, 11, "csi_pclk", csi0_p_clk, NULL), + OWN(APPS3, 0, "mdp_clk", mdp_clk, NULL), + OWN(APPS3, 2, "mfc_clk", mfc_clk, NULL), + OWN(APPS3, 2, "mfc_div2_clk", mfc_div2_clk, NULL), + OWN(APPS3, 2, "mfc_pclk", mfc_p_clk, NULL), + OWN(APPS3, 4, "vpe_clk", vpe_clk, NULL), + + OWN(GLBL, 8, "adm_clk", adm_clk, NULL), + { CLK_LOOKUP("adm_pclk", adm_p_clk.c, NULL), + O(GLBL), BIT(13), &dummy_clk }, + OWN(GLBL, 8, "ce_clk", ce_clk, NULL), + OWN(GLBL, 13, "rotator_clk", axi_rotator_clk, NULL), + OWN(GLBL, 13, "rotator_imem_clk", rotator_imem_clk, NULL), + OWN(GLBL, 13, "rotator_pclk", rotator_p_clk, NULL), + { CLK_LOOKUP("uartdm_pclk", uart1dm_p_clk.c, "msm_serial_hs.0"), + O(GLBL), BIT(8), &dummy_clk }, + { CLK_LOOKUP("uartdm_pclk", uart2dm_p_clk.c, "msm_serial_hs.1"), + O(GLBL), BIT(8), &dummy_clk }, +}; + +static struct clk_lookup msm_clocks_7x30[ARRAY_SIZE(ownership_map)]; + +static void __init set_clock_ownership(void) +{ + unsigned i; + struct clk_lookup *lk; + + for (i = 0; i < ARRAY_SIZE(ownership_map); i++) { + const u32 *reg = ownership_map[i].reg; + u32 bit = ownership_map[i].bit; + struct clk *remote = ownership_map[i].remote; + + lk = &ownership_map[i].lk; + memcpy(&msm_clocks_7x30[i], lk, sizeof(*lk)); + + if (reg && !(*reg & bit)) + msm_clocks_7x30[i].clk = remote; + } +} + +/* + * Miscellaneous clock register initializations + */ +static const struct reg_init { + const void __iomem *reg; + uint32_t mask; + uint32_t val; +} ri_list[] __initconst = { + /* Enable UMDX_P clock. Known to causes issues, so never turn off. */ + {GLBL_CLK_ENA_2_SC_REG, BIT(2), BIT(2)}, + + /* Disable all the child clocks of USB_HS_SRC. */ + { USBH_NS_REG, BIT(13) | BIT(9), 0 }, + { USBH2_NS_REG, BIT(9) | BIT(4), 0 }, + { USBH3_NS_REG, BIT(9) | BIT(4), 0 }, + + {EMDH_NS_REG, BM(18, 17) , BVAL(18, 17, 0x3)}, /* RX div = div-4. */ + {PMDH_NS_REG, BM(18, 17), BVAL(18, 17, 0x3)}, /* RX div = div-4. */ + /* MI2S_CODEC_RX_S src = MI2S_CODEC_RX_M. */ + {MI2S_RX_NS_REG, BIT(14), 0x0}, + /* MI2S_CODEC_TX_S src = MI2S_CODEC_TX_M. */ + {MI2S_TX_NS_REG, BIT(14), 0x0}, + {MI2S_NS_REG, BIT(14), 0x0}, /* MI2S_S src = MI2S_M. */ + /* Allow DSP to decide the LPA CORE src. */ + {LPA_CORE_CLK_MA0_REG, BIT(0), BIT(0)}, + {LPA_CORE_CLK_MA2_REG, BIT(0), BIT(0)}, + {MI2S_CODEC_RX_DIV_REG, 0xF, 0xD}, /* MI2S_CODEC_RX_S div = div-8. */ + {MI2S_CODEC_TX_DIV_REG, 0xF, 0xD}, /* MI2S_CODEC_TX_S div = div-8. */ + {MI2S_DIV_REG, 0xF, 0x7}, /* MI2S_S div = div-8. */ + {MDC_NS_REG, 0x3, 0x3}, /* MDC src = external MDH src. */ + {SDAC_NS_REG, BM(15, 14), 0x0}, /* SDAC div = div-1. */ + /* Disable sources TCXO/5 & TCXO/6. UART1 src = TCXO*/ + {UART_NS_REG, BM(26, 25) | BM(2, 0), 0x0}, + /* HDMI div = div-1, non-inverted. tv_enc_src = tv_clk_src */ + {HDMI_NS_REG, 0x7, 0x0}, + {TV_NS_REG, BM(15, 14), 0x0}, /* tv_clk_src_div2 = div-1 */ + + /* USBH core clocks src = USB_HS_SRC. */ + {USBH_NS_REG, BIT(15), BIT(15)}, + {USBH2_NS_REG, BIT(6), BIT(6)}, + {USBH3_NS_REG, BIT(6), BIT(6)}, +}; + +/* Local clock driver initialization. */ +void __init msm7x30_clock_init(void) +{ + int i; + uint32_t val; + + cache_ownership(); + print_ownership(); + set_clock_ownership(); + + soc_update_sys_vdd = msm7x30_update_sys_vdd; + + /* When we have no local clock control, the rest of the code in this + * function is a NOP since writes to shadow regions that we don't own + * are ignored. */ + + clk_set_rate(&usb_hs_src_clk.c, clk_tbl_usb[1].freq_hz); + + for (i = 0; i < ARRAY_SIZE(ri_list); i++) { + val = readl_relaxed(ri_list[i].reg); + val &= ~ri_list[i].mask; + val |= ri_list[i].val; + writel_relaxed(val, ri_list[i].reg); + } + + clk_set_rate(&i2c_clk.c, 19200000); + clk_set_rate(&i2c_2_clk.c, 19200000); + clk_set_rate(&qup_i2c_clk.c, 19200000); + clk_set_rate(&uart1_clk.c, 19200000); + clk_set_rate(&uart2_clk.c, 19200000); + clk_set_rate(&mi2s_m_clk.c, 12288000); + clk_set_rate(&midi_clk.c, 98304000); + clk_set_rate(&mdp_vsync_clk.c, 24576000); + clk_set_rate(&glbl_root_clk.c, 1); + clk_set_rate(&mdc_clk.c, 1); + /* Sync the LPA_CODEC clock to MI2S_CODEC_RX */ + clk_set_rate(&lpa_codec_clk.c, 1); + /* Sync the GRP2D clock to AXI */ + clk_set_rate(&grp_2d_clk.c, 1); + + msm_clock_init(msm_clocks_7x30, ARRAY_SIZE(msm_clocks_7x30)); +} + +/* + * Clock operation handler registration + */ +static struct clk_ops soc_clk_ops_7x30 = { + .enable = local_clk_enable, + .disable = local_clk_disable, + .auto_off = local_clk_auto_off, + .set_rate = local_clk_set_rate, + .set_min_rate = local_clk_set_min_rate, + .set_max_rate = local_clk_set_max_rate, + .get_rate = local_clk_get_rate, + .list_rate = local_clk_list_rate, + .is_enabled = local_clk_is_enabled, + .round_rate = local_clk_round_rate, + .reset = msm7x30_clk_reset, + .set_flags = soc_clk_set_flags, + .is_local = local_clk_is_local, + .get_parent = local_clk_get_parent, +}; + +static struct clk_ops clk_ops_branch = { + .enable = branch_clk_enable, + .disable = branch_clk_disable, + .auto_off = branch_clk_auto_off, + .is_enabled = branch_clk_is_enabled, + .reset = soc_branch_clk_reset, + .set_flags = soc_clk_set_flags, + .is_local = local_clk_is_local, + .get_parent = branch_clk_get_parent, + .set_parent = branch_clk_set_parent, +}; diff --git a/arch/arm/mach-msm/clock-8960.c b/arch/arm/mach-msm/clock-8960.c new file mode 100644 index 00000000000..e205440b044 --- /dev/null +++ b/arch/arm/mach-msm/clock-8960.c @@ -0,0 +1,4134 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "clock-local.h" +#include "clock-rpm.h" +#include "clock-voter.h" +#include "clock-dss-8960.h" +#include "devices.h" + +#define REG(off) (MSM_CLK_CTL_BASE + (off)) +#define REG_MM(off) (MSM_MMSS_CLK_CTL_BASE + (off)) +#define REG_LPA(off) (MSM_LPASS_CLK_CTL_BASE + (off)) + +/* Peripheral clock registers. */ +#define CE1_HCLK_CTL_REG REG(0x2720) +#define CE1_CORE_CLK_CTL_REG REG(0x2724) +#define DMA_BAM_HCLK_CTL REG(0x25C0) +#define CLK_HALT_CFPB_STATEA_REG REG(0x2FCC) +#define CLK_HALT_CFPB_STATEB_REG REG(0x2FD0) +#define CLK_HALT_CFPB_STATEC_REG REG(0x2FD4) +#define CLK_HALT_DFAB_STATE_REG REG(0x2FC8) +#define CLK_HALT_MSS_SMPSS_MISC_STATE_REG REG(0x2FDC) +#define CLK_HALT_SFPB_MISC_STATE_REG REG(0x2FD8) +#define CLK_TEST_REG REG(0x2FA0) +#define GSBIn_HCLK_CTL_REG(n) REG(0x29C0+(0x20*((n)-1))) +#define GSBIn_QUP_APPS_MD_REG(n) REG(0x29C8+(0x20*((n)-1))) +#define GSBIn_QUP_APPS_NS_REG(n) REG(0x29CC+(0x20*((n)-1))) +#define GSBIn_RESET_REG(n) REG(0x29DC+(0x20*((n)-1))) +#define GSBIn_UART_APPS_MD_REG(n) REG(0x29D0+(0x20*((n)-1))) +#define GSBIn_UART_APPS_NS_REG(n) REG(0x29D4+(0x20*((n)-1))) +#define LPASS_XO_SRC_CLK_CTL_REG REG(0x2EC0) +#define PDM_CLK_NS_REG REG(0x2CC0) +#define BB_PLL_ENA_Q6_SW_REG REG(0x3500) +#define BB_PLL_ENA_SC0_REG REG(0x34C0) +#define BB_PLL0_STATUS_REG REG(0x30D8) +#define BB_PLL5_STATUS_REG REG(0x30F8) +#define BB_PLL6_STATUS_REG REG(0x3118) +#define BB_PLL7_STATUS_REG REG(0x3138) +#define BB_PLL8_L_VAL_REG REG(0x3144) +#define BB_PLL8_M_VAL_REG REG(0x3148) +#define BB_PLL8_MODE_REG REG(0x3140) +#define BB_PLL8_N_VAL_REG REG(0x314C) +#define BB_PLL8_STATUS_REG REG(0x3158) +#define BB_PLL8_CONFIG_REG REG(0x3154) +#define BB_PLL8_TEST_CTL_REG REG(0x3150) +#define PLLTEST_PAD_CFG_REG REG(0x2FA4) +#define PMEM_ACLK_CTL_REG REG(0x25A0) +#define RINGOSC_NS_REG REG(0x2DC0) +#define RINGOSC_STATUS_REG REG(0x2DCC) +#define RINGOSC_TCXO_CTL_REG REG(0x2DC4) +#define SC0_U_CLK_BRANCH_ENA_VOTE_REG REG(0x3080) +#define SDCn_APPS_CLK_MD_REG(n) REG(0x2828+(0x20*((n)-1))) +#define SDCn_APPS_CLK_NS_REG(n) REG(0x282C+(0x20*((n)-1))) +#define SDCn_HCLK_CTL_REG(n) REG(0x2820+(0x20*((n)-1))) +#define SDCn_RESET_REG(n) REG(0x2830+(0x20*((n)-1))) +#define SLIMBUS_XO_SRC_CLK_CTL_REG REG(0x2628) +#define TSIF_HCLK_CTL_REG REG(0x2700) +#define TSIF_REF_CLK_MD_REG REG(0x270C) +#define TSIF_REF_CLK_NS_REG REG(0x2710) +#define TSSC_CLK_CTL_REG REG(0x2CA0) +#define USB_FSn_HCLK_CTL_REG(n) REG(0x2960+(0x20*((n)-1))) +#define USB_FSn_RESET_REG(n) REG(0x2974+(0x20*((n)-1))) +#define USB_FSn_SYSTEM_CLK_CTL_REG(n) REG(0x296C+(0x20*((n)-1))) +#define USB_FSn_XCVR_FS_CLK_MD_REG(n) REG(0x2964+(0x20*((n)-1))) +#define USB_FSn_XCVR_FS_CLK_NS_REG(n) REG(0x2968+(0x20*((n)-1))) +#define USB_HS1_HCLK_CTL_REG REG(0x2900) +#define USB_HS1_RESET_REG REG(0x2910) +#define USB_HS1_XCVR_FS_CLK_MD_REG REG(0x2908) +#define USB_HS1_XCVR_FS_CLK_NS_REG REG(0x290C) +#define USB_PHY0_RESET_REG REG(0x2E20) + +/* Multimedia clock registers. */ +#define AHB_EN_REG REG_MM(0x0008) +#define AHB_EN2_REG REG_MM(0x0038) +#define AHB_NS_REG REG_MM(0x0004) +#define AXI_NS_REG REG_MM(0x0014) +#define CAMCLKn_NS_REG(n) REG_MM(0x0148+(0x14*(n))) +#define CAMCLKn_CC_REG(n) REG_MM(0x0140+(0x14*(n))) +#define CAMCLKn_MD_REG(n) REG_MM(0x0144+(0x14*(n))) +#define CSI0_NS_REG REG_MM(0x0048) +#define CSI0_CC_REG REG_MM(0x0040) +#define CSI0_MD_REG REG_MM(0x0044) +#define CSI1_NS_REG REG_MM(0x0010) +#define CSI1_CC_REG REG_MM(0x0024) +#define CSI1_MD_REG REG_MM(0x0028) +#define CSIPHYTIMER_CC_REG REG_MM(0x0160) +#define CSIPHYTIMER_MD_REG REG_MM(0x0164) +#define CSIPHYTIMER_NS_REG REG_MM(0x0168) +#define DSI1_BYTE_NS_REG REG_MM(0x00B0) +#define DSI1_BYTE_CC_REG REG_MM(0x0090) +#define DSI2_BYTE_NS_REG REG_MM(0x00BC) +#define DSI2_BYTE_CC_REG REG_MM(0x00B4) +#define DSI1_ESC_NS_REG REG_MM(0x011C) +#define DSI1_ESC_CC_REG REG_MM(0x00CC) +#define DSI2_ESC_NS_REG REG_MM(0x0150) +#define DSI2_ESC_CC_REG REG_MM(0x013C) +#define DSI_PIXEL_CC_REG REG_MM(0x0130) +#define DSI2_PIXEL_CC_REG REG_MM(0x0094) +#define DBG_BUS_VEC_A_REG REG_MM(0x01C8) +#define DBG_BUS_VEC_B_REG REG_MM(0x01CC) +#define DBG_BUS_VEC_C_REG REG_MM(0x01D0) +#define DBG_BUS_VEC_D_REG REG_MM(0x01D4) +#define DBG_BUS_VEC_E_REG REG_MM(0x01D8) +#define DBG_BUS_VEC_F_REG REG_MM(0x01DC) +#define DBG_BUS_VEC_G_REG REG_MM(0x01E0) +#define DBG_BUS_VEC_H_REG REG_MM(0x01E4) +#define DBG_BUS_VEC_I_REG REG_MM(0x01E8) +#define DBG_CFG_REG_HS_REG REG_MM(0x01B4) +#define DBG_CFG_REG_LS_REG REG_MM(0x01B8) +#define GFX2D0_CC_REG REG_MM(0x0060) +#define GFX2D0_MD0_REG REG_MM(0x0064) +#define GFX2D0_MD1_REG REG_MM(0x0068) +#define GFX2D0_NS_REG REG_MM(0x0070) +#define GFX2D1_CC_REG REG_MM(0x0074) +#define GFX2D1_MD0_REG REG_MM(0x0078) +#define GFX2D1_MD1_REG REG_MM(0x006C) +#define GFX2D1_NS_REG REG_MM(0x007C) +#define GFX3D_CC_REG REG_MM(0x0080) +#define GFX3D_MD0_REG REG_MM(0x0084) +#define GFX3D_MD1_REG REG_MM(0x0088) +#define GFX3D_NS_REG REG_MM(0x008C) +#define IJPEG_CC_REG REG_MM(0x0098) +#define IJPEG_MD_REG REG_MM(0x009C) +#define IJPEG_NS_REG REG_MM(0x00A0) +#define JPEGD_CC_REG REG_MM(0x00A4) +#define JPEGD_NS_REG REG_MM(0x00AC) +#define MAXI_EN_REG REG_MM(0x0018) +#define MAXI_EN2_REG REG_MM(0x0020) +#define MAXI_EN3_REG REG_MM(0x002C) +#define MAXI_EN4_REG REG_MM(0x0114) +#define MDP_CC_REG REG_MM(0x00C0) +#define MDP_LUT_CC_REG REG_MM(0x016C) +#define MDP_MD0_REG REG_MM(0x00C4) +#define MDP_MD1_REG REG_MM(0x00C8) +#define MDP_NS_REG REG_MM(0x00D0) +#define MISC_CC_REG REG_MM(0x0058) +#define MISC_CC2_REG REG_MM(0x005C) +#define MM_PLL1_MODE_REG REG_MM(0x031C) +#define ROT_CC_REG REG_MM(0x00E0) +#define ROT_NS_REG REG_MM(0x00E8) +#define SAXI_EN_REG REG_MM(0x0030) +#define SW_RESET_AHB_REG REG_MM(0x020C) +#define SW_RESET_AHB2_REG REG_MM(0x0200) +#define SW_RESET_ALL_REG REG_MM(0x0204) +#define SW_RESET_AXI_REG REG_MM(0x0208) +#define SW_RESET_CORE_REG REG_MM(0x0210) +#define TV_CC_REG REG_MM(0x00EC) +#define TV_CC2_REG REG_MM(0x0124) +#define TV_MD_REG REG_MM(0x00F0) +#define TV_NS_REG REG_MM(0x00F4) +#define VCODEC_CC_REG REG_MM(0x00F8) +#define VCODEC_MD0_REG REG_MM(0x00FC) +#define VCODEC_MD1_REG REG_MM(0x0128) +#define VCODEC_NS_REG REG_MM(0x0100) +#define VFE_CC_REG REG_MM(0x0104) +#define VFE_MD_REG REG_MM(0x0108) +#define VFE_NS_REG REG_MM(0x010C) +#define VPE_CC_REG REG_MM(0x0110) +#define VPE_NS_REG REG_MM(0x0118) + +/* Low-power Audio clock registers. */ +#define LCC_CLK_LS_DEBUG_CFG_REG REG_LPA(0x00A8) +#define LCC_CODEC_I2S_MIC_MD_REG REG_LPA(0x0064) +#define LCC_CODEC_I2S_MIC_NS_REG REG_LPA(0x0060) +#define LCC_CODEC_I2S_MIC_STATUS_REG REG_LPA(0x0068) +#define LCC_CODEC_I2S_SPKR_MD_REG REG_LPA(0x0070) +#define LCC_CODEC_I2S_SPKR_NS_REG REG_LPA(0x006C) +#define LCC_CODEC_I2S_SPKR_STATUS_REG REG_LPA(0x0074) +#define LCC_MI2S_MD_REG REG_LPA(0x004C) +#define LCC_MI2S_NS_REG REG_LPA(0x0048) +#define LCC_MI2S_STATUS_REG REG_LPA(0x0050) +#define LCC_PCM_MD_REG REG_LPA(0x0058) +#define LCC_PCM_NS_REG REG_LPA(0x0054) +#define LCC_PCM_STATUS_REG REG_LPA(0x005C) +#define LCC_PLL0_STATUS_REG REG_LPA(0x0018) +#define LCC_PRI_PLL_CLK_CTL_REG REG_LPA(0x00C4) +#define LCC_PXO_SRC_CLK_CTL_REG REG_LPA(0x00B4) +#define LCC_SPARE_I2S_MIC_MD_REG REG_LPA(0x007C) +#define LCC_SPARE_I2S_MIC_NS_REG REG_LPA(0x0078) +#define LCC_SPARE_I2S_MIC_STATUS_REG REG_LPA(0x0080) +#define LCC_SPARE_I2S_SPKR_MD_REG REG_LPA(0x0088) +#define LCC_SPARE_I2S_SPKR_NS_REG REG_LPA(0x0084) +#define LCC_SPARE_I2S_SPKR_STATUS_REG REG_LPA(0x008C) +#define LCC_SLIMBUS_NS_REG REG_LPA(0x00CC) +#define LCC_SLIMBUS_MD_REG REG_LPA(0x00D0) +#define LCC_SLIMBUS_STATUS_REG REG_LPA(0x00D4) +#define LCC_AHBEX_BRANCH_CTL_REG REG_LPA(0x00E4) + +/* MUX source input identifiers. */ +#define pxo_to_bb_mux 0 +#define cxo_to_bb_mux pxo_to_bb_mux +#define pll0_to_bb_mux 2 +#define pll8_to_bb_mux 3 +#define pll6_to_bb_mux 4 +#define gnd_to_bb_mux 5 +#define pxo_to_mm_mux 0 +#define pll1_to_mm_mux 1 +#define pll2_to_mm_mux 1 +#define pll8_to_mm_mux 2 +#define pll0_to_mm_mux 3 +#define gnd_to_mm_mux 4 +#define hdmi_pll_to_mm_mux 3 +#define cxo_to_xo_mux 0 +#define pxo_to_xo_mux 1 +#define gnd_to_xo_mux 3 +#define pxo_to_lpa_mux 0 +#define cxo_to_lpa_mux 1 +#define pll4_to_lpa_mux 2 +#define gnd_to_lpa_mux 6 + +/* Test Vector Macros */ +#define TEST_TYPE_PER_LS 1 +#define TEST_TYPE_PER_HS 2 +#define TEST_TYPE_MM_LS 3 +#define TEST_TYPE_MM_HS 4 +#define TEST_TYPE_LPA 5 +#define TEST_TYPE_SHIFT 24 +#define TEST_CLK_SEL_MASK BM(23, 0) +#define TEST_VECTOR(s, t) (((t) << TEST_TYPE_SHIFT) | BVAL(23, 0, (s))) +#define TEST_PER_LS(s) TEST_VECTOR((s), TEST_TYPE_PER_LS) +#define TEST_PER_HS(s) TEST_VECTOR((s), TEST_TYPE_PER_HS) +#define TEST_MM_LS(s) TEST_VECTOR((s), TEST_TYPE_MM_LS) +#define TEST_MM_HS(s) TEST_VECTOR((s), TEST_TYPE_MM_HS) +#define TEST_LPA(s) TEST_VECTOR((s), TEST_TYPE_LPA) + +#define MN_MODE_DUAL_EDGE 0x2 + +/* MD Registers */ +#define MD4(m_lsb, m, n_lsb, n) \ + (BVAL((m_lsb+3), m_lsb, m) | BVAL((n_lsb+3), n_lsb, ~(n))) +#define MD8(m_lsb, m, n_lsb, n) \ + (BVAL((m_lsb+7), m_lsb, m) | BVAL((n_lsb+7), n_lsb, ~(n))) +#define MD16(m, n) (BVAL(31, 16, m) | BVAL(15, 0, ~(n))) + +/* NS Registers */ +#define NS(n_msb, n_lsb, n, m, mde_lsb, d_msb, d_lsb, d, s_msb, s_lsb, s) \ + (BVAL(n_msb, n_lsb, ~(n-m)) \ + | (BVAL((mde_lsb+1), mde_lsb, MN_MODE_DUAL_EDGE) * !!(n)) \ + | BVAL(d_msb, d_lsb, (d-1)) | BVAL(s_msb, s_lsb, s)) + +#define NS_MM(n_msb, n_lsb, n, m, d_msb, d_lsb, d, s_msb, s_lsb, s) \ + (BVAL(n_msb, n_lsb, ~(n-m)) | BVAL(d_msb, d_lsb, (d-1)) \ + | BVAL(s_msb, s_lsb, s)) + +#define NS_DIVSRC(d_msb , d_lsb, d, s_msb, s_lsb, s) \ + (BVAL(d_msb, d_lsb, (d-1)) | BVAL(s_msb, s_lsb, s)) + +#define NS_DIV(d_msb , d_lsb, d) \ + BVAL(d_msb, d_lsb, (d-1)) + +#define NS_SRC_SEL(s_msb, s_lsb, s) \ + BVAL(s_msb, s_lsb, s) + +#define NS_MND_BANKED4(n0_lsb, n1_lsb, n, m, s0_lsb, s1_lsb, s) \ + (BVAL((n0_lsb+3), n0_lsb, ~(n-m)) \ + | BVAL((n1_lsb+3), n1_lsb, ~(n-m)) \ + | BVAL((s0_lsb+2), s0_lsb, s) \ + | BVAL((s1_lsb+2), s1_lsb, s)) + +#define NS_MND_BANKED8(n0_lsb, n1_lsb, n, m, s0_lsb, s1_lsb, s) \ + (BVAL((n0_lsb+7), n0_lsb, ~(n-m)) \ + | BVAL((n1_lsb+7), n1_lsb, ~(n-m)) \ + | BVAL((s0_lsb+2), s0_lsb, s) \ + | BVAL((s1_lsb+2), s1_lsb, s)) + +#define NS_DIVSRC_BANKED(d0_msb, d0_lsb, d1_msb, d1_lsb, d, \ + s0_msb, s0_lsb, s1_msb, s1_lsb, s) \ + (BVAL(d0_msb, d0_lsb, (d-1)) | BVAL(d1_msb, d1_lsb, (d-1)) \ + | BVAL(s0_msb, s0_lsb, s) \ + | BVAL(s1_msb, s1_lsb, s)) + +/* CC Registers */ +#define CC(mde_lsb, n) (BVAL((mde_lsb+1), mde_lsb, MN_MODE_DUAL_EDGE) * !!(n)) +#define CC_BANKED(mde0_lsb, mde1_lsb, n) \ + ((BVAL((mde0_lsb+1), mde0_lsb, MN_MODE_DUAL_EDGE) \ + | BVAL((mde1_lsb+1), mde1_lsb, MN_MODE_DUAL_EDGE)) \ + * !!(n)) + +struct pll_rate { + const uint32_t l_val; + const uint32_t m_val; + const uint32_t n_val; + const uint32_t vco; + const uint32_t post_div; + const uint32_t i_bits; +}; +#define PLL_RATE(l, m, n, v, d, i) { l, m, n, v, (d>>1), i } + +/* + * Clock Descriptions + */ + +static struct msm_xo_voter *xo_pxo, *xo_cxo; + +static int pxo_clk_enable(struct clk *clk) +{ + return msm_xo_mode_vote(xo_pxo, MSM_XO_MODE_ON); +} + +static void pxo_clk_disable(struct clk *clk) +{ + msm_xo_mode_vote(xo_pxo, MSM_XO_MODE_OFF); +} + +static struct clk_ops clk_ops_pxo = { + .enable = pxo_clk_enable, + .disable = pxo_clk_disable, + .get_rate = fixed_clk_get_rate, + .is_local = local_clk_is_local, +}; + +static struct fixed_clk pxo_clk = { + .rate = 27000000, + .c = { + .dbg_name = "pxo_clk", + .ops = &clk_ops_pxo, + CLK_INIT(pxo_clk.c), + }, +}; + +static int cxo_clk_enable(struct clk *clk) +{ + return msm_xo_mode_vote(xo_cxo, MSM_XO_MODE_ON); +} + +static void cxo_clk_disable(struct clk *clk) +{ + msm_xo_mode_vote(xo_cxo, MSM_XO_MODE_OFF); +} + +static struct clk_ops clk_ops_cxo = { + .enable = cxo_clk_enable, + .disable = cxo_clk_disable, + .get_rate = fixed_clk_get_rate, + .is_local = local_clk_is_local, +}; + +static struct fixed_clk cxo_clk = { + .rate = 19200000, + .c = { + .dbg_name = "cxo_clk", + .ops = &clk_ops_cxo, + CLK_INIT(cxo_clk.c), + }, +}; + +static struct pll_clk pll2_clk = { + .rate = 800000000, + .mode_reg = MM_PLL1_MODE_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll2_clk", + .ops = &clk_ops_pll, + CLK_INIT(pll2_clk.c), + }, +}; + +static struct pll_vote_clk pll4_clk = { + .rate = 393216000, + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(4), + .status_reg = LCC_PLL0_STATUS_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll4_clk", + .ops = &clk_ops_pll_vote, + CLK_INIT(pll4_clk.c), + }, +}; + +static struct pll_vote_clk pll8_clk = { + .rate = 384000000, + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(8), + .status_reg = BB_PLL8_STATUS_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll8_clk", + .ops = &clk_ops_pll_vote, + CLK_INIT(pll8_clk.c), + }, +}; + +/* + * SoC-specific functions required by clock-local driver + */ + +/* Update the sys_vdd voltage given a level. */ +static int msm8960_update_sys_vdd(enum sys_vdd_level level) +{ + static const int vdd_uv[] = { + [NONE...LOW] = 945000, + [NOMINAL] = 1050000, + [HIGH] = 1150000, + }; + + return rpm_vreg_set_voltage(RPM_VREG_ID_PM8921_S3, RPM_VREG_VOTER3, + vdd_uv[level], vdd_uv[HIGH], 1); +} + +static int soc_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + return branch_reset(&to_rcg_clk(clk)->b, action); +} + +static struct clk_ops soc_clk_ops_8960 = { + .enable = local_clk_enable, + .disable = local_clk_disable, + .auto_off = local_clk_auto_off, + .set_rate = local_clk_set_rate, + .set_min_rate = local_clk_set_min_rate, + .set_max_rate = local_clk_set_max_rate, + .get_rate = local_clk_get_rate, + .list_rate = local_clk_list_rate, + .is_enabled = local_clk_is_enabled, + .round_rate = local_clk_round_rate, + .reset = soc_clk_reset, + .is_local = local_clk_is_local, + .get_parent = local_clk_get_parent, +}; + +static struct clk_ops clk_ops_branch = { + .enable = branch_clk_enable, + .disable = branch_clk_disable, + .auto_off = branch_clk_auto_off, + .is_enabled = branch_clk_is_enabled, + .reset = branch_clk_reset, + .is_local = local_clk_is_local, + .get_parent = branch_clk_get_parent, + .set_parent = branch_clk_set_parent, +}; + +static struct clk_ops clk_ops_reset = { + .reset = branch_clk_reset, + .is_local = local_clk_is_local, +}; + +/* AXI Interfaces */ +static struct branch_clk gmem_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(24), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 6, + }, + .c = { + .dbg_name = "gmem_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(gmem_axi_clk.c), + }, +}; + +static struct branch_clk ijpeg_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(21), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(14), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 4, + }, + .c = { + .dbg_name = "ijpeg_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(ijpeg_axi_clk.c), + }, +}; + +static struct branch_clk imem_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(22), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(10), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "imem_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(imem_axi_clk.c), + }, +}; + +static struct branch_clk jpegd_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(25), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 5, + }, + .c = { + .dbg_name = "jpegd_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(jpegd_axi_clk.c), + }, +}; + +static struct branch_clk vcodec_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(19), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(7), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 3, + }, + .c = { + .dbg_name = "vcodec_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcodec_axi_clk.c), + }, +}; + +static struct branch_clk vcodec_axi_a_clk = { + .b = { + .ctl_reg = MAXI_EN4_REG, + .en_mask = BIT(25), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(5), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 26, + }, + .c = { + .dbg_name = "vcodec_axi_a_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcodec_axi_a_clk.c), + }, +}; + +static struct branch_clk vcodec_axi_b_clk = { + .b = { + .ctl_reg = MAXI_EN4_REG, + .en_mask = BIT(23), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(4), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 25, + }, + .c = { + .dbg_name = "vcodec_axi_b_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcodec_axi_b_clk.c), + }, +}; + +static struct branch_clk vfe_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(18), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(9), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 0, + }, + .c = { + .dbg_name = "vfe_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(vfe_axi_clk.c), + }, +}; + +static struct branch_clk mdp_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(23), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(13), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_check = HALT, + .halt_bit = 8, + }, + .c = { + .dbg_name = "mdp_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdp_axi_clk.c), + }, +}; + +static struct branch_clk rot_axi_clk = { + .b = { + .ctl_reg = MAXI_EN2_REG, + .en_mask = BIT(24), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(6), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_check = HALT, + .halt_bit = 2, + }, + .c = { + .dbg_name = "rot_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(rot_axi_clk.c), + }, +}; + +static struct branch_clk vpe_axi_clk = { + .b = { + .ctl_reg = MAXI_EN2_REG, + .en_mask = BIT(26), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(15), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_check = HALT, + .halt_bit = 1, + }, + .c = { + .dbg_name = "vpe_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(vpe_axi_clk.c), + }, +}; + +/* AHB Interfaces */ +static struct branch_clk amp_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(24), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 18, + }, + .c = { + .dbg_name = "amp_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(amp_p_clk.c), + }, +}; + +static struct branch_clk csi0_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(7), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(17), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 16, + }, + .c = { + .dbg_name = "csi0_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0_p_clk.c), + }, +}; + +static struct branch_clk dsi1_m_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(9), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(6), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 19, + }, + .c = { + .dbg_name = "dsi1_m_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(dsi1_m_p_clk.c), + }, +}; + +static struct branch_clk dsi1_s_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(18), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(5), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 21, + }, + .c = { + .dbg_name = "dsi1_s_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(dsi1_s_p_clk.c), + }, +}; + +static struct branch_clk dsi2_m_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(17), + .reset_reg = SW_RESET_AHB2_REG, + .reset_mask = BIT(1), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 18, + }, + .c = { + .dbg_name = "dsi2_m_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(dsi2_m_p_clk.c), + }, +}; + +static struct branch_clk dsi2_s_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(22), + .reset_reg = SW_RESET_AHB2_REG, + .reset_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 20, + }, + .c = { + .dbg_name = "dsi2_s_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(dsi2_s_p_clk.c), + }, +}; + +static struct branch_clk gfx2d0_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(19), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(12), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 2, + }, + .c = { + .dbg_name = "gfx2d0_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gfx2d0_p_clk.c), + }, +}; + +static struct branch_clk gfx2d1_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(2), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(11), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 3, + }, + .c = { + .dbg_name = "gfx2d1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gfx2d1_p_clk.c), + }, +}; + +static struct branch_clk gfx3d_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(3), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(10), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 4, + }, + .c = { + .dbg_name = "gfx3d_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gfx3d_p_clk.c), + }, +}; + +static struct branch_clk hdmi_m_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(14), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(9), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 5, + }, + .c = { + .dbg_name = "hdmi_m_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(hdmi_m_p_clk.c), + }, +}; + +static struct branch_clk hdmi_s_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(4), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(9), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 6, + }, + .c = { + .dbg_name = "hdmi_s_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(hdmi_s_p_clk.c), + }, +}; + +static struct branch_clk ijpeg_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(5), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(7), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 9, + }, + .c = { + .dbg_name = "ijpeg_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(ijpeg_p_clk.c), + }, +}; + +static struct branch_clk imem_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(6), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(8), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 10, + }, + .c = { + .dbg_name = "imem_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(imem_p_clk.c), + }, +}; + +static struct branch_clk jpegd_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(21), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(4), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "jpegd_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(jpegd_p_clk.c), + }, +}; + +static struct branch_clk mdp_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(10), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(3), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 11, + }, + .c = { + .dbg_name = "mdp_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdp_p_clk.c), + }, +}; + +static struct branch_clk rot_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(12), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(2), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 13, + }, + .c = { + .dbg_name = "rot_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(rot_p_clk.c), + }, +}; + +static struct branch_clk smmu_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(15), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 22, + }, + .c = { + .dbg_name = "smmu_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(smmu_p_clk.c), + }, +}; + +static struct branch_clk tv_enc_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(25), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(15), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 23, + }, + .c = { + .dbg_name = "tv_enc_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(tv_enc_p_clk.c), + }, +}; + +static struct branch_clk vcodec_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(11), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(1), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 12, + }, + .c = { + .dbg_name = "vcodec_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcodec_p_clk.c), + }, +}; + +static struct branch_clk vfe_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(13), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 14, + }, + .c = { + .dbg_name = "vfe_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(vfe_p_clk.c), + }, +}; + +static struct branch_clk vpe_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(16), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(14), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 15, + }, + .c = { + .dbg_name = "vpe_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(vpe_p_clk.c), + }, +}; + +/* + * Peripheral Clocks + */ +#define CLK_GSBI_UART(i, n, h_r, h_b) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = GSBIn_UART_APPS_NS_REG(n), \ + .en_mask = BIT(9), \ + .reset_reg = GSBIn_RESET_REG(n), \ + .reset_mask = BIT(0), \ + .halt_reg = h_r, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = GSBIn_UART_APPS_NS_REG(n), \ + .md_reg = GSBIn_UART_APPS_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(31, 16) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_uart, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8960, \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_UART(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD16(m, n), \ + .ns_val = NS(31, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_gsbi_uart[] = { + F_GSBI_UART( 0, gnd, 1, 0, 0, NONE), + F_GSBI_UART( 1843200, pll8, 1, 3, 625, LOW), + F_GSBI_UART( 3686400, pll8, 1, 6, 625, LOW), + F_GSBI_UART( 7372800, pll8, 1, 12, 625, LOW), + F_GSBI_UART(14745600, pll8, 1, 24, 625, LOW), + F_GSBI_UART(16000000, pll8, 4, 1, 6, LOW), + F_GSBI_UART(24000000, pll8, 4, 1, 4, LOW), + F_GSBI_UART(32000000, pll8, 4, 1, 3, LOW), + F_GSBI_UART(40000000, pll8, 1, 5, 48, NOMINAL), + F_GSBI_UART(46400000, pll8, 1, 29, 240, NOMINAL), + F_GSBI_UART(48000000, pll8, 4, 1, 2, NOMINAL), + F_GSBI_UART(51200000, pll8, 1, 2, 15, NOMINAL), + F_GSBI_UART(56000000, pll8, 1, 7, 48, NOMINAL), + F_GSBI_UART(58982400, pll8, 1, 96, 625, NOMINAL), + F_GSBI_UART(64000000, pll8, 2, 1, 3, NOMINAL), + F_END +}; + +static CLK_GSBI_UART(gsbi1_uart, 1, CLK_HALT_CFPB_STATEA_REG, 10); +static CLK_GSBI_UART(gsbi2_uart, 2, CLK_HALT_CFPB_STATEA_REG, 6); +static CLK_GSBI_UART(gsbi3_uart, 3, CLK_HALT_CFPB_STATEA_REG, 2); +static CLK_GSBI_UART(gsbi4_uart, 4, CLK_HALT_CFPB_STATEB_REG, 26); +static CLK_GSBI_UART(gsbi5_uart, 5, CLK_HALT_CFPB_STATEB_REG, 22); +static CLK_GSBI_UART(gsbi6_uart, 6, CLK_HALT_CFPB_STATEB_REG, 18); +static CLK_GSBI_UART(gsbi7_uart, 7, CLK_HALT_CFPB_STATEB_REG, 14); +static CLK_GSBI_UART(gsbi8_uart, 8, CLK_HALT_CFPB_STATEB_REG, 10); +static CLK_GSBI_UART(gsbi9_uart, 9, CLK_HALT_CFPB_STATEB_REG, 6); +static CLK_GSBI_UART(gsbi10_uart, 10, CLK_HALT_CFPB_STATEB_REG, 2); +static CLK_GSBI_UART(gsbi11_uart, 11, CLK_HALT_CFPB_STATEC_REG, 17); +static CLK_GSBI_UART(gsbi12_uart, 12, CLK_HALT_CFPB_STATEC_REG, 13); + +#define CLK_GSBI_QUP(i, n, h_r, h_b) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = GSBIn_QUP_APPS_NS_REG(n), \ + .en_mask = BIT(9), \ + .reset_reg = GSBIn_RESET_REG(n), \ + .reset_mask = BIT(0), \ + .halt_reg = h_r, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = GSBIn_QUP_APPS_NS_REG(n), \ + .md_reg = GSBIn_QUP_APPS_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_qup, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8960, \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_QUP(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(16, m, 0, n), \ + .ns_val = NS(23, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_gsbi_qup[] = { + F_GSBI_QUP( 0, gnd, 1, 0, 0, NONE), + F_GSBI_QUP( 1100000, pxo, 1, 2, 49, LOW), + F_GSBI_QUP( 5400000, pxo, 1, 1, 5, LOW), + F_GSBI_QUP(10800000, pxo, 1, 2, 5, LOW), + F_GSBI_QUP(15060000, pll8, 1, 2, 51, LOW), + F_GSBI_QUP(24000000, pll8, 4, 1, 4, LOW), + F_GSBI_QUP(25600000, pll8, 1, 1, 15, NOMINAL), + F_GSBI_QUP(27000000, pxo, 1, 0, 0, NOMINAL), + F_GSBI_QUP(48000000, pll8, 4, 1, 2, NOMINAL), + F_GSBI_QUP(51200000, pll8, 1, 2, 15, NOMINAL), + F_END +}; + +static CLK_GSBI_QUP(gsbi1_qup, 1, CLK_HALT_CFPB_STATEA_REG, 9); +static CLK_GSBI_QUP(gsbi2_qup, 2, CLK_HALT_CFPB_STATEA_REG, 4); +static CLK_GSBI_QUP(gsbi3_qup, 3, CLK_HALT_CFPB_STATEA_REG, 0); +static CLK_GSBI_QUP(gsbi4_qup, 4, CLK_HALT_CFPB_STATEB_REG, 24); +static CLK_GSBI_QUP(gsbi5_qup, 5, CLK_HALT_CFPB_STATEB_REG, 20); +static CLK_GSBI_QUP(gsbi6_qup, 6, CLK_HALT_CFPB_STATEB_REG, 16); +static CLK_GSBI_QUP(gsbi7_qup, 7, CLK_HALT_CFPB_STATEB_REG, 12); +static CLK_GSBI_QUP(gsbi8_qup, 8, CLK_HALT_CFPB_STATEB_REG, 8); +static CLK_GSBI_QUP(gsbi9_qup, 9, CLK_HALT_CFPB_STATEB_REG, 4); +static CLK_GSBI_QUP(gsbi10_qup, 10, CLK_HALT_CFPB_STATEB_REG, 0); +static CLK_GSBI_QUP(gsbi11_qup, 11, CLK_HALT_CFPB_STATEC_REG, 15); +static CLK_GSBI_QUP(gsbi12_qup, 12, CLK_HALT_CFPB_STATEC_REG, 11); + +#define F_PDM(f, s, d, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(1, 0, s##_to_xo_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_pdm[] = { + F_PDM( 0, gnd, 1, NONE), + F_PDM(27000000, pxo, 1, LOW), + F_END +}; + +static struct rcg_clk pdm_clk = { + .b = { + .ctl_reg = PDM_CLK_NS_REG, + .en_mask = BIT(9), + .reset_reg = PDM_CLK_NS_REG, + .reset_mask = BIT(12), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 3, + }, + .ns_reg = PDM_CLK_NS_REG, + .root_en_mask = BIT(11), + .ns_mask = BM(1, 0), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_pdm, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "pdm_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(pdm_clk.c), + }, +}; + +static struct branch_clk pmem_clk = { + .b = { + .ctl_reg = PMEM_ACLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 20, + }, + .c = { + .dbg_name = "pmem_clk", + .ops = &clk_ops_branch, + CLK_INIT(pmem_clk.c), + }, +}; + +#define F_PRNG(f, s, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_prng[] = { + F_PRNG(64000000, pll8, NOMINAL), + F_END +}; + +static struct rcg_clk prng_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(10), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 10, + }, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_prng, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "prng_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(prng_clk.c), + }, +}; + +#define CLK_SDC(i, n, h_r, h_c, h_b) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = SDCn_APPS_CLK_NS_REG(n), \ + .en_mask = BIT(9), \ + .reset_reg = SDCn_RESET_REG(n), \ + .reset_mask = BIT(0), \ + .halt_reg = h_r, \ + .halt_check = h_c, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = SDCn_APPS_CLK_NS_REG(n), \ + .md_reg = SDCn_APPS_CLK_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_sdc, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8960, \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_SDC(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(16, m, 0, n), \ + .ns_val = NS(23, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_sdc[] = { + F_SDC( 0, gnd, 1, 0, 0, NONE), + F_SDC( 144000, pxo, 3, 2, 125, LOW), + F_SDC( 400000, pll8, 4, 1, 240, LOW), + F_SDC( 16000000, pll8, 4, 1, 6, LOW), + F_SDC( 17070000, pll8, 1, 2, 45, LOW), + F_SDC( 20210000, pll8, 1, 1, 19, LOW), + F_SDC( 24000000, pll8, 4, 1, 4, LOW), + F_SDC( 48000000, pll8, 4, 1, 2, NOMINAL), + F_SDC( 64000000, pll8, 3, 1, 2, NOMINAL), + F_SDC( 96000000, pll8, 4, 0, 0, NOMINAL), + F_SDC(192000000, pll8, 2, 0, 0, NOMINAL), + F_END +}; + +static CLK_SDC(sdc1, 1, CLK_HALT_DFAB_STATE_REG, HALT, 6); +static CLK_SDC(sdc2, 2, CLK_HALT_DFAB_STATE_REG, HALT, 5); +static CLK_SDC(sdc3, 3, CLK_HALT_DFAB_STATE_REG, HALT, 4); +static CLK_SDC(sdc4, 4, CLK_HALT_DFAB_STATE_REG, HALT, 3); +static CLK_SDC(sdc5, 5, CLK_HALT_DFAB_STATE_REG, HALT, 2); + +#define F_TSIF_REF(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD16(m, n), \ + .ns_val = NS(31, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_tsif_ref[] = { + F_TSIF_REF( 0, gnd, 1, 0, 0, NONE), + F_TSIF_REF(105000, pxo, 1, 1, 256, LOW), + F_END +}; + +static struct rcg_clk tsif_ref_clk = { + .b = { + .ctl_reg = TSIF_REF_CLK_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 5, + }, + .ns_reg = TSIF_REF_CLK_NS_REG, + .md_reg = TSIF_REF_CLK_MD_REG, + .root_en_mask = BIT(11), + .ns_mask = (BM(31, 16) | BM(6, 0)), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_tsif_ref, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "tsif_ref_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(tsif_ref_clk.c), + }, +}; + +#define F_TSSC(f, s, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(1, 0, s##_to_xo_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_tssc[] = { + F_TSSC( 0, gnd, NONE), + F_TSSC(27000000, pxo, LOW), + F_END +}; + +static struct rcg_clk tssc_clk = { + .b = { + .ctl_reg = TSSC_CLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 4, + }, + .ns_reg = TSSC_CLK_CTL_REG, + .ns_mask = BM(1, 0), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_tssc, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "tssc_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(tssc_clk.c), + }, +}; + +#define F_USB(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(16, m, 0, n), \ + .ns_val = NS(23, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_usb[] = { + F_USB( 0, gnd, 1, 0, 0, NONE), + F_USB(60000000, pll8, 1, 5, 32, NOMINAL), + F_END +}; + +static struct rcg_clk usb_hs1_xcvr_clk = { + .b = { + .ctl_reg = USB_HS1_XCVR_FS_CLK_NS_REG, + .en_mask = BIT(9), + .reset_reg = USB_HS1_RESET_REG, + .reset_mask = BIT(0), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 0, + }, + .ns_reg = USB_HS1_XCVR_FS_CLK_NS_REG, + .md_reg = USB_HS1_XCVR_FS_CLK_MD_REG, + .root_en_mask = BIT(11), + .ns_mask = (BM(23, 16) | BM(6, 0)), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_usb, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "usb_hs1_xcvr_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(usb_hs1_xcvr_clk.c), + }, +}; + +static struct branch_clk usb_phy0_clk = { + .b = { + .reset_reg = USB_PHY0_RESET_REG, + .reset_mask = BIT(0), + }, + .c = { + .dbg_name = "usb_phy0_clk", + .ops = &clk_ops_reset, + CLK_INIT(usb_phy0_clk.c), + }, +}; + +#define CLK_USB_FS(i, n) \ + struct rcg_clk i##_clk = { \ + .ns_reg = USB_FSn_XCVR_FS_CLK_NS_REG(n), \ + .b = { \ + .ctl_reg = USB_FSn_XCVR_FS_CLK_NS_REG(n), \ + .halt_check = NOCHECK, \ + }, \ + .md_reg = USB_FSn_XCVR_FS_CLK_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_usb, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8960, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +static CLK_USB_FS(usb_fs1_src, 1); +static struct branch_clk usb_fs1_xcvr_clk = { + .b = { + .ctl_reg = USB_FSn_XCVR_FS_CLK_NS_REG(1), + .en_mask = BIT(9), + .reset_reg = USB_FSn_RESET_REG(1), + .reset_mask = BIT(1), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 15, + }, + .parent = &usb_fs1_src_clk.c, + .c = { + .dbg_name = "usb_fs1_xcvr_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs1_xcvr_clk.c), + }, +}; + +static struct branch_clk usb_fs1_sys_clk = { + .b = { + .ctl_reg = USB_FSn_SYSTEM_CLK_CTL_REG(1), + .en_mask = BIT(4), + .reset_reg = USB_FSn_RESET_REG(1), + .reset_mask = BIT(0), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 16, + }, + .parent = &usb_fs1_src_clk.c, + .c = { + .dbg_name = "usb_fs1_sys_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs1_sys_clk.c), + }, +}; + +static CLK_USB_FS(usb_fs2_src, 2); +static struct branch_clk usb_fs2_xcvr_clk = { + .b = { + .ctl_reg = USB_FSn_XCVR_FS_CLK_NS_REG(2), + .en_mask = BIT(9), + .reset_reg = USB_FSn_RESET_REG(2), + .reset_mask = BIT(1), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 12, + }, + .parent = &usb_fs2_src_clk.c, + .c = { + .dbg_name = "usb_fs2_xcvr_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs2_xcvr_clk.c), + }, +}; + +static struct branch_clk usb_fs2_sys_clk = { + .b = { + .ctl_reg = USB_FSn_SYSTEM_CLK_CTL_REG(2), + .en_mask = BIT(4), + .reset_reg = USB_FSn_RESET_REG(2), + .reset_mask = BIT(0), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 13, + }, + .parent = &usb_fs2_src_clk.c, + .c = { + .dbg_name = "usb_fs2_sys_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs2_sys_clk.c), + }, +}; + +/* Fast Peripheral Bus Clocks */ +static struct branch_clk ce1_core_clk = { + .b = { + .ctl_reg = CE1_CORE_CLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 27, + }, + .c = { + .dbg_name = "ce1_core_clk", + .ops = &clk_ops_branch, + CLK_INIT(ce1_core_clk.c), + }, +}; +static struct branch_clk ce1_p_clk = { + .b = { + .ctl_reg = CE1_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 1, + }, + .c = { + .dbg_name = "ce1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(ce1_p_clk.c), + }, +}; + +static struct branch_clk dma_bam_p_clk = { + .b = { + .ctl_reg = DMA_BAM_HCLK_CTL, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 12, + }, + .c = { + .dbg_name = "dma_bam_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(dma_bam_p_clk.c), + }, +}; + +static struct branch_clk gsbi1_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(1), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 11, + }, + .c = { + .dbg_name = "gsbi1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi1_p_clk.c), + }, +}; + +static struct branch_clk gsbi2_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(2), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "gsbi2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi2_p_clk.c), + }, +}; + +static struct branch_clk gsbi3_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(3), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 3, + }, + .c = { + .dbg_name = "gsbi3_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi3_p_clk.c), + }, +}; + +static struct branch_clk gsbi4_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(4), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 27, + }, + .c = { + .dbg_name = "gsbi4_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi4_p_clk.c), + }, +}; + +static struct branch_clk gsbi5_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(5), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 23, + }, + .c = { + .dbg_name = "gsbi5_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi5_p_clk.c), + }, +}; + +static struct branch_clk gsbi6_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(6), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 19, + }, + .c = { + .dbg_name = "gsbi6_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi6_p_clk.c), + }, +}; + +static struct branch_clk gsbi7_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(7), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 15, + }, + .c = { + .dbg_name = "gsbi7_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi7_p_clk.c), + }, +}; + +static struct branch_clk gsbi8_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(8), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 11, + }, + .c = { + .dbg_name = "gsbi8_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi8_p_clk.c), + }, +}; + +static struct branch_clk gsbi9_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(9), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "gsbi9_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi9_p_clk.c), + }, +}; + +static struct branch_clk gsbi10_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(10), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 3, + }, + .c = { + .dbg_name = "gsbi10_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi10_p_clk.c), + }, +}; + +static struct branch_clk gsbi11_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(11), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 18, + }, + .c = { + .dbg_name = "gsbi11_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi11_p_clk.c), + }, +}; + +static struct branch_clk gsbi12_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(12), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 14, + }, + .c = { + .dbg_name = "gsbi12_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi12_p_clk.c), + }, +}; + +static struct branch_clk tsif_p_clk = { + .b = { + .ctl_reg = TSIF_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "tsif_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(tsif_p_clk.c), + }, +}; + +static struct branch_clk usb_fs1_p_clk = { + .b = { + .ctl_reg = USB_FSn_HCLK_CTL_REG(1), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 17, + }, + .c = { + .dbg_name = "usb_fs1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs1_p_clk.c), + }, +}; + +static struct branch_clk usb_fs2_p_clk = { + .b = { + .ctl_reg = USB_FSn_HCLK_CTL_REG(2), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 14, + }, + .c = { + .dbg_name = "usb_fs2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs2_p_clk.c), + }, +}; + +static struct branch_clk usb_hs1_p_clk = { + .b = { + .ctl_reg = USB_HS1_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 1, + }, + .c = { + .dbg_name = "usb_hs1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs1_p_clk.c), + }, +}; + +static struct branch_clk sdc1_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(1), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 11, + }, + .c = { + .dbg_name = "sdc1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc1_p_clk.c), + }, +}; + +static struct branch_clk sdc2_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(2), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 10, + }, + .c = { + .dbg_name = "sdc2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc2_p_clk.c), + }, +}; + +static struct branch_clk sdc3_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(3), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 9, + }, + .c = { + .dbg_name = "sdc3_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc3_p_clk.c), + }, +}; + +static struct branch_clk sdc4_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(4), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 8, + }, + .c = { + .dbg_name = "sdc4_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc4_p_clk.c), + }, +}; + +static struct branch_clk sdc5_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(5), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "sdc5_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc5_p_clk.c), + }, +}; + +/* HW-Voteable Clocks */ +static struct branch_clk adm0_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(2), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 14, + }, + .c = { + .dbg_name = "adm0_clk", + .ops = &clk_ops_branch, + CLK_INIT(adm0_clk.c), + }, +}; + +static struct branch_clk adm0_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(3), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 13, + }, + .c = { + .dbg_name = "adm0_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(adm0_p_clk.c), + }, +}; + +static struct branch_clk pmic_arb0_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(8), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 22, + }, + .c = { + .dbg_name = "pmic_arb0_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(pmic_arb0_p_clk.c), + }, +}; + +static struct branch_clk pmic_arb1_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 21, + }, + .c = { + .dbg_name = "pmic_arb1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(pmic_arb1_p_clk.c), + }, +}; + +static struct branch_clk pmic_ssbi2_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(7), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 23, + }, + .c = { + .dbg_name = "pmic_ssbi2_clk", + .ops = &clk_ops_branch, + CLK_INIT(pmic_ssbi2_clk.c), + }, +}; + +static struct branch_clk rpm_msg_ram_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(6), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 12, + }, + .c = { + .dbg_name = "rpm_msg_ram_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(rpm_msg_ram_p_clk.c), + }, +}; + +/* + * Multimedia Clocks + */ + +static struct branch_clk amp_clk = { + .b = { + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(20), + }, + .c = { + .dbg_name = "amp_clk", + .ops = &clk_ops_reset, + CLK_INIT(amp_clk.c), + }, +}; + +#define CLK_CAM(i, n, hb) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = CAMCLKn_CC_REG(n), \ + .en_mask = BIT(0), \ + .halt_reg = DBG_BUS_VEC_I_REG, \ + .halt_bit = hb, \ + }, \ + .ns_reg = CAMCLKn_NS_REG(n), \ + .md_reg = CAMCLKn_MD_REG(n), \ + .root_en_mask = BIT(2), \ + .ns_mask = (BM(31, 24) | BM(15, 14) | BM(2, 0)), \ + .ctl_mask = BM(7, 6), \ + .set_rate = set_rate_mnd_8, \ + .freq_tbl = clk_tbl_cam, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8960, \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_CAM(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(31, 24, n, m, 15, 14, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_cam[] = { + F_CAM( 0, gnd, 1, 0, 0, NONE), + F_CAM( 6000000, pll8, 4, 1, 16, LOW), + F_CAM( 8000000, pll8, 4, 1, 12, LOW), + F_CAM( 12000000, pll8, 4, 1, 8, LOW), + F_CAM( 16000000, pll8, 4, 1, 6, LOW), + F_CAM( 19200000, pll8, 4, 1, 5, LOW), + F_CAM( 24000000, pll8, 4, 1, 4, LOW), + F_CAM( 32000000, pll8, 4, 1, 3, LOW), + F_CAM( 48000000, pll8, 4, 1, 2, LOW), + F_CAM( 64000000, pll8, 3, 1, 2, LOW), + F_CAM( 96000000, pll8, 4, 0, 0, NOMINAL), + F_CAM(128000000, pll8, 3, 0, 0, NOMINAL), + F_END +}; + +static CLK_CAM(cam0, 0, 15); +static CLK_CAM(cam1, 1, 16); + +#define F_CSI(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(31, 24, n, m, 15, 14, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_csi[] = { + F_CSI( 0, gnd, 1, 0, 0, NONE), + F_CSI( 85330000, pll8, 1, 2, 9, LOW), + F_CSI(177780000, pll2, 1, 2, 9, NOMINAL), + F_END +}; + +static struct rcg_clk csi0_src_clk = { + .ns_reg = CSI0_NS_REG, + .b = { + .ctl_reg = CSI0_CC_REG, + .halt_check = NOCHECK, + }, + .md_reg = CSI0_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = BM(31, 24) | BM(15, 12) | BM(2, 0), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_csi, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "csi0_src_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(csi0_src_clk.c), + }, +}; + +static struct branch_clk csi0_clk = { + .b = { + .ctl_reg = CSI0_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(8), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 13, + }, + .parent = &csi0_src_clk.c, + .c = { + .dbg_name = "csi0_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0_clk.c), + }, +}; + +static struct branch_clk csi0_phy_clk = { + .b = { + .ctl_reg = CSI0_CC_REG, + .en_mask = BIT(8), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(29), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 9, + }, + .parent = &csi0_src_clk.c, + .c = { + .dbg_name = "csi0_phy_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0_phy_clk.c), + }, +}; + +static struct rcg_clk csi1_src_clk = { + .ns_reg = CSI1_NS_REG, + .b = { + .ctl_reg = CSI1_CC_REG, + .halt_check = NOCHECK, + }, + .md_reg = CSI1_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = BM(31, 24) | BM(15, 12) | BM(2, 0), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_csi, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "csi1_src_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(csi1_src_clk.c), + }, +}; + +static struct branch_clk csi1_clk = { + .b = { + .ctl_reg = CSI1_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(18), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 14, + }, + .parent = &csi1_src_clk.c, + .c = { + .dbg_name = "csi1_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi1_clk.c), + }, +}; + +static struct branch_clk csi1_phy_clk = { + .b = { + .ctl_reg = CSI1_CC_REG, + .en_mask = BIT(8), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(28), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 10, + }, + .parent = &csi1_src_clk.c, + .c = { + .dbg_name = "csi1_phy_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi1_phy_clk.c), + }, +}; + +#define F_CSI_PIX(s) \ + { \ + .src_clk = &csi##s##_clk.c, \ + .freq_hz = s, \ + .ns_val = BVAL(25, 25, s), \ + } +static struct clk_freq_tbl clk_tbl_csi_pix[] = { + F_CSI_PIX(0), /* CSI0 source */ + F_CSI_PIX(1), /* CSI1 source */ + F_END +}; + +#define F_CSI_RDI(s) \ + { \ + .src_clk = &csi##s##_clk.c, \ + .freq_hz = s, \ + .ns_val = BVAL(12, 12, s), \ + } +static struct clk_freq_tbl clk_tbl_csi_rdi[] = { + F_CSI_RDI(0), /* CSI0 source */ + F_CSI_RDI(1), /* CSI1 source */ + F_END +}; + +static struct rcg_clk csi_pix_clk = { + .b = { + .ctl_reg = MISC_CC_REG, + .en_mask = BIT(26), + .halt_check = DELAY, + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(26), + }, + .ns_reg = MISC_CC_REG, + .ns_mask = BIT(25), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_csi_pix, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "csi_pix_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(csi_pix_clk.c), + }, +}; + +static struct rcg_clk csi_rdi_clk = { + .b = { + .ctl_reg = MISC_CC_REG, + .en_mask = BIT(13), + .halt_check = DELAY, + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(27), + }, + .ns_reg = MISC_CC_REG, + .ns_mask = BIT(12), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_csi_rdi, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "csi_rdi_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(csi_rdi_clk.c), + }, +}; + +#define F_CSI_PHYTIMER(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(31, 24, n, m, 15, 14, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_csi_phytimer[] = { + F_CSI_PHYTIMER( 0, gnd, 1, 0, 0, NONE), + F_CSI_PHYTIMER( 85330000, pll8, 1, 2, 9, LOW), + F_CSI_PHYTIMER(177780000, pll2, 1, 2, 9, NOMINAL), + F_END +}; + +static struct rcg_clk csiphy_timer_src_clk = { + .ns_reg = CSIPHYTIMER_NS_REG, + .b = { + .ctl_reg = CSIPHYTIMER_CC_REG, + .halt_check = NOCHECK, + }, + .md_reg = CSIPHYTIMER_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(31, 24) | BM(15, 14) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd_8, + .freq_tbl = clk_tbl_csi_phytimer, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "csiphy_timer_src_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(csiphy_timer_src_clk.c), + }, +}; + +static struct branch_clk csi0phy_timer_clk = { + .b = { + .ctl_reg = CSIPHYTIMER_CC_REG, + .en_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 17, + }, + .parent = &csiphy_timer_src_clk.c, + .c = { + .dbg_name = "csi0phy_timer_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0phy_timer_clk.c), + }, +}; + +static struct branch_clk csi1phy_timer_clk = { + .b = { + .ctl_reg = CSIPHYTIMER_CC_REG, + .en_mask = BIT(9), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 18, + }, + .parent = &csiphy_timer_src_clk.c, + .c = { + .dbg_name = "csi1phy_timer_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi1phy_timer_clk.c), + }, +}; + +#define F_DSI(d) \ + { \ + .freq_hz = d, \ + .ns_val = BVAL(15, 12, (d-1)), \ + } +/* + * The DSI_BYTE/ESC clock is sourced from the DSI PHY PLL, which may change rate + * without this clock driver knowing. So, overload the clk_set_rate() to set + * the divider (1 to 16) of the clock with respect to the PLL rate. + */ +static struct clk_freq_tbl clk_tbl_dsi_byte[] = { + F_DSI(1), F_DSI(2), F_DSI(3), F_DSI(4), + F_DSI(5), F_DSI(6), F_DSI(7), F_DSI(8), + F_DSI(9), F_DSI(10), F_DSI(11), F_DSI(12), + F_DSI(13), F_DSI(14), F_DSI(15), F_DSI(16), + F_END +}; + +static struct rcg_clk dsi1_byte_clk = { + .b = { + .ctl_reg = DSI1_BYTE_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(7), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 21, + }, + .ns_reg = DSI1_BYTE_NS_REG, + .root_en_mask = BIT(2), + .ns_mask = BM(15, 12), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_dsi_byte, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "dsi1_byte_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(dsi1_byte_clk.c), + }, +}; + +static struct rcg_clk dsi2_byte_clk = { + .b = { + .ctl_reg = DSI2_BYTE_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(25), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 20, + }, + .ns_reg = DSI2_BYTE_NS_REG, + .root_en_mask = BIT(2), + .ns_mask = BM(15, 12), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_dsi_byte, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "dsi2_byte_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(dsi2_byte_clk.c), + }, +}; + +static struct rcg_clk dsi1_esc_clk = { + .b = { + .ctl_reg = DSI1_ESC_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 1, + }, + .ns_reg = DSI1_ESC_NS_REG, + .root_en_mask = BIT(2), + .ns_mask = BM(15, 12), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_dsi_byte, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "dsi1_esc_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(dsi1_esc_clk.c), + }, +}; + +static struct rcg_clk dsi2_esc_clk = { + .b = { + .ctl_reg = DSI2_ESC_CC_REG, + .en_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 3, + }, + .ns_reg = DSI2_ESC_NS_REG, + .root_en_mask = BIT(2), + .ns_mask = BM(15, 12), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_dsi_byte, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "dsi2_esc_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(dsi2_esc_clk.c), + }, +}; + +#define F_GFX2D(f, s, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD4(4, m, 0, n), \ + .ns_val = NS_MND_BANKED4(20, 16, n, m, 3, 0, s##_to_mm_mux), \ + .ctl_val = CC_BANKED(9, 6, n), \ + .mnd_en_mask = (BIT(8) | BIT(5)) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_gfx2d[] = { + F_GFX2D( 0, gnd, 0, 0, NONE), + F_GFX2D( 27000000, pxo, 0, 0, LOW), + F_GFX2D( 48000000, pll8, 1, 8, LOW), + F_GFX2D( 54857000, pll8, 1, 7, LOW), + F_GFX2D( 64000000, pll8, 1, 6, LOW), + F_GFX2D( 76800000, pll8, 1, 5, LOW), + F_GFX2D( 96000000, pll8, 1, 4, LOW), + F_GFX2D(128000000, pll8, 1, 3, NOMINAL), + F_GFX2D(145455000, pll2, 2, 11, NOMINAL), + F_GFX2D(160000000, pll2, 1, 5, NOMINAL), + F_GFX2D(177778000, pll2, 2, 9, NOMINAL), + F_GFX2D(200000000, pll2, 1, 4, NOMINAL), + F_GFX2D(228571000, pll2, 2, 7, HIGH), + F_END +}; + +static struct bank_masks bmnd_info_gfx2d0 = { + .bank_sel_mask = BIT(11), + .bank0_mask = { + .md_reg = GFX2D0_MD0_REG, + .ns_mask = BM(23, 20) | BM(5, 3), + .rst_mask = BIT(25), + .mnd_en_mask = BIT(8), + .mode_mask = BM(10, 9), + }, + .bank1_mask = { + .md_reg = GFX2D0_MD1_REG, + .ns_mask = BM(19, 16) | BM(2, 0), + .rst_mask = BIT(24), + .mnd_en_mask = BIT(5), + .mode_mask = BM(7, 6), + }, +}; + +static struct rcg_clk gfx2d0_clk = { + .b = { + .ctl_reg = GFX2D0_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(14), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 9, + }, + .ns_reg = GFX2D0_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx2d, + .bank_masks = &bmnd_info_gfx2d0, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "gfx2d0_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(gfx2d0_clk.c), + }, +}; + +static struct bank_masks bmnd_info_gfx2d1 = { + .bank_sel_mask = BIT(11), + .bank0_mask = { + .md_reg = GFX2D1_MD0_REG, + .ns_mask = BM(23, 20) | BM(5, 3), + .rst_mask = BIT(25), + .mnd_en_mask = BIT(8), + .mode_mask = BM(10, 9), + }, + .bank1_mask = { + .md_reg = GFX2D1_MD1_REG, + .ns_mask = BM(19, 16) | BM(2, 0), + .rst_mask = BIT(24), + .mnd_en_mask = BIT(5), + .mode_mask = BM(7, 6), + }, +}; + +static struct rcg_clk gfx2d1_clk = { + .b = { + .ctl_reg = GFX2D1_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(13), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 14, + }, + .ns_reg = GFX2D1_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx2d, + .bank_masks = &bmnd_info_gfx2d1, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "gfx2d1_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(gfx2d1_clk.c), + }, +}; + +#define F_GFX3D(f, s, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD4(4, m, 0, n), \ + .ns_val = NS_MND_BANKED4(18, 14, n, m, 3, 0, s##_to_mm_mux), \ + .ctl_val = CC_BANKED(9, 6, n), \ + .mnd_en_mask = (BIT(8) | BIT(5)) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_gfx3d[] = { + F_GFX3D( 0, gnd, 0, 0, NONE), + F_GFX3D( 27000000, pxo, 0, 0, LOW), + F_GFX3D( 48000000, pll8, 1, 8, LOW), + F_GFX3D( 54857000, pll8, 1, 7, LOW), + F_GFX3D( 64000000, pll8, 1, 6, LOW), + F_GFX3D( 76800000, pll8, 1, 5, LOW), + F_GFX3D( 96000000, pll8, 1, 4, LOW), + F_GFX3D(128000000, pll8, 1, 3, NOMINAL), + F_GFX3D(145455000, pll2, 2, 11, NOMINAL), + F_GFX3D(160000000, pll2, 1, 5, NOMINAL), + F_GFX3D(177778000, pll2, 2, 9, NOMINAL), + F_GFX3D(200000000, pll2, 1, 4, NOMINAL), + F_GFX3D(228571000, pll2, 2, 7, NOMINAL), + F_GFX3D(266667000, pll2, 1, 3, NOMINAL), + F_GFX3D(320000000, pll2, 2, 5, HIGH), + F_END +}; + +static struct bank_masks bmnd_info_gfx3d = { + .bank_sel_mask = BIT(11), + .bank0_mask = { + .md_reg = GFX3D_MD0_REG, + .ns_mask = BM(21, 18) | BM(5, 3), + .rst_mask = BIT(23), + .mnd_en_mask = BIT(8), + .mode_mask = BM(10, 9), + }, + .bank1_mask = { + .md_reg = GFX3D_MD1_REG, + .ns_mask = BM(17, 14) | BM(2, 0), + .rst_mask = BIT(22), + .mnd_en_mask = BIT(5), + .mode_mask = BM(7, 6), + }, +}; + +static struct rcg_clk gfx3d_clk = { + .b = { + .ctl_reg = GFX3D_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(12), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 4, + }, + .ns_reg = GFX3D_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx3d, + .bank_masks = &bmnd_info_gfx3d, + .depends = &gmem_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "gfx3d_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(gfx3d_clk.c), + }, +}; + +#define F_IJPEG(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(23, 16, n, m, 15, 12, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_ijpeg[] = { + F_IJPEG( 0, gnd, 1, 0, 0, NONE), + F_IJPEG( 27000000, pxo, 1, 0, 0, LOW), + F_IJPEG( 36570000, pll8, 1, 2, 21, LOW), + F_IJPEG( 54860000, pll8, 7, 0, 0, LOW), + F_IJPEG( 96000000, pll8, 4, 0, 0, LOW), + F_IJPEG(109710000, pll8, 1, 2, 7, LOW), + F_IJPEG(128000000, pll8, 3, 0, 0, NOMINAL), + F_IJPEG(153600000, pll8, 1, 2, 5, NOMINAL), + F_IJPEG(200000000, pll2, 4, 0, 0, NOMINAL), + F_IJPEG(228571000, pll2, 1, 2, 7, NOMINAL), + F_END +}; + +static struct rcg_clk ijpeg_clk = { + .b = { + .ctl_reg = IJPEG_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(9), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 24, + }, + .ns_reg = IJPEG_NS_REG, + .md_reg = IJPEG_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(23, 16) | BM(15, 12) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_ijpeg, + .depends = &ijpeg_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "ijpeg_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(ijpeg_clk.c), + }, +}; + +#define F_JPEGD(f, s, d, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_jpegd[] = { + F_JPEGD( 0, gnd, 1, NONE), + F_JPEGD( 64000000, pll8, 6, LOW), + F_JPEGD( 76800000, pll8, 5, LOW), + F_JPEGD( 96000000, pll8, 4, LOW), + F_JPEGD(160000000, pll2, 5, NOMINAL), + F_JPEGD(200000000, pll2, 4, NOMINAL), + F_END +}; + +static struct rcg_clk jpegd_clk = { + .b = { + .ctl_reg = JPEGD_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(19), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 19, + }, + .ns_reg = JPEGD_NS_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(15, 12) | BM(2, 0)), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_jpegd, + .depends = &jpegd_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "jpegd_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(jpegd_clk.c), + }, +}; + +#define F_MDP(f, s, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MND_BANKED8(22, 14, n, m, 3, 0, s##_to_mm_mux), \ + .ctl_val = CC_BANKED(9, 6, n), \ + .mnd_en_mask = (BIT(8) | BIT(5)) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_mdp[] = { + F_MDP( 0, gnd, 0, 0, NONE), + F_MDP( 9600000, pll8, 1, 40, LOW), + F_MDP( 13710000, pll8, 1, 28, LOW), + F_MDP( 27000000, pxo, 0, 0, LOW), + F_MDP( 29540000, pll8, 1, 13, LOW), + F_MDP( 34910000, pll8, 1, 11, LOW), + F_MDP( 38400000, pll8, 1, 10, LOW), + F_MDP( 59080000, pll8, 2, 13, LOW), + F_MDP( 76800000, pll8, 1, 5, LOW), + F_MDP( 85330000, pll8, 2, 9, LOW), + F_MDP( 96000000, pll8, 1, 4, NOMINAL), + F_MDP(128000000, pll8, 1, 3, NOMINAL), + F_MDP(160000000, pll2, 1, 5, NOMINAL), + F_MDP(177780000, pll2, 2, 9, NOMINAL), + F_MDP(200000000, pll2, 1, 4, NOMINAL), + F_END +}; + +static struct bank_masks bmnd_info_mdp = { + .bank_sel_mask = BIT(11), + .bank0_mask = { + .md_reg = MDP_MD0_REG, + .ns_mask = BM(29, 22) | BM(5, 3), + .rst_mask = BIT(31), + .mnd_en_mask = BIT(8), + .mode_mask = BM(10, 9), + }, + .bank1_mask = { + .md_reg = MDP_MD1_REG, + .ns_mask = BM(21, 14) | BM(2, 0), + .rst_mask = BIT(30), + .mnd_en_mask = BIT(5), + .mode_mask = BM(7, 6), + }, +}; + +static struct rcg_clk mdp_clk = { + .b = { + .ctl_reg = MDP_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(21), + .halt_reg = DBG_BUS_VEC_C_REG, + .halt_bit = 10, + }, + .ns_reg = MDP_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_mdp, + .bank_masks = &bmnd_info_mdp, + .depends = &mdp_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mdp_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(mdp_clk.c), + }, +}; + +static struct branch_clk lut_mdp_clk = { + .b = { + .ctl_reg = MDP_LUT_CC_REG, + .en_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_I_REG, + .halt_bit = 13, + }, + .parent = &mdp_clk.c, + .c = { + .dbg_name = "lut_mdp_clk", + .ops = &clk_ops_branch, + CLK_INIT(lut_mdp_clk.c), + }, +}; + +#define F_MDP_VSYNC(f, s, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(13, 13, s##_to_bb_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_mdp_vsync[] = { + F_MDP_VSYNC(27000000, pxo, LOW), + F_END +}; + +static struct rcg_clk mdp_vsync_clk = { + .b = { + .ctl_reg = MISC_CC_REG, + .en_mask = BIT(6), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(3), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 22, + }, + .ns_reg = MISC_CC2_REG, + .ns_mask = BIT(13), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_mdp_vsync, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mdp_vsync_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(mdp_vsync_clk.c), + }, +}; + +#define F_ROT(f, s, d, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC_BANKED(29, 26, 25, 22, d, \ + 21, 19, 18, 16, s##_to_mm_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_rot[] = { + F_ROT( 0, gnd, 1, NONE), + F_ROT( 27000000, pxo, 1, LOW), + F_ROT( 29540000, pll8, 13, LOW), + F_ROT( 32000000, pll8, 12, LOW), + F_ROT( 38400000, pll8, 10, LOW), + F_ROT( 48000000, pll8, 8, LOW), + F_ROT( 54860000, pll8, 7, LOW), + F_ROT( 64000000, pll8, 6, LOW), + F_ROT( 76800000, pll8, 5, LOW), + F_ROT( 96000000, pll8, 4, NOMINAL), + F_ROT(100000000, pll2, 8, NOMINAL), + F_ROT(114290000, pll2, 7, NOMINAL), + F_ROT(133330000, pll2, 6, NOMINAL), + F_ROT(160000000, pll2, 5, NOMINAL), + F_END +}; + +static struct bank_masks bdiv_info_rot = { + .bank_sel_mask = BIT(30), + .bank0_mask = { + .ns_mask = BM(25, 22) | BM(18, 16), + }, + .bank1_mask = { + .ns_mask = BM(29, 26) | BM(21, 19), + }, +}; + +static struct rcg_clk rot_clk = { + .b = { + .ctl_reg = ROT_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(2), + .halt_reg = DBG_BUS_VEC_C_REG, + .halt_bit = 15, + }, + .ns_reg = ROT_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_div_banked, + .freq_tbl = clk_tbl_rot, + .bank_masks = &bdiv_info_rot, + .current_freq = &local_dummy_freq, + .depends = &rot_axi_clk.c, + .c = { + .dbg_name = "rot_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(rot_clk.c), + }, +}; + +static int hdmi_pll_clk_enable(struct clk *clk) +{ + int ret; + unsigned long flags; + spin_lock_irqsave(&local_clock_reg_lock, flags); + ret = hdmi_pll_enable(); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + return ret; +} + +static void hdmi_pll_clk_disable(struct clk *clk) +{ + unsigned long flags; + spin_lock_irqsave(&local_clock_reg_lock, flags); + hdmi_pll_disable(); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static unsigned hdmi_pll_clk_get_rate(struct clk *clk) +{ + return hdmi_pll_get_rate(); +} + +static struct clk_ops clk_ops_hdmi_pll = { + .enable = hdmi_pll_clk_enable, + .disable = hdmi_pll_clk_disable, + .get_rate = hdmi_pll_clk_get_rate, + .is_local = local_clk_is_local, +}; + +static struct clk hdmi_pll_clk = { + .dbg_name = "hdmi_pll_clk", + .ops = &clk_ops_hdmi_pll, + CLK_INIT(hdmi_pll_clk), +}; + +#define F_TV_GND(f, s, p_r, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(23, 16, n, m, 15, 14, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +#define F_TV(f, s, p_r, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(23, 16, n, m, 15, 14, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + .extra_freq_data = (void *)p_r, \ + } +/* Switching TV freqs requires PLL reconfiguration. */ +static struct clk_freq_tbl clk_tbl_tv[] = { + F_TV_GND( 0, gnd, 0, 1, 0, 0, NONE), + F_TV( 25200000, hdmi_pll, 25200000, 1, 0, 0, LOW), + F_TV( 27000000, hdmi_pll, 27000000, 1, 0, 0, LOW), + F_TV( 27030000, hdmi_pll, 27030000, 1, 0, 0, LOW), + F_TV( 74250000, hdmi_pll, 74250000, 1, 0, 0, NOMINAL), + F_TV(148500000, hdmi_pll, 148500000, 1, 0, 0, NOMINAL), + F_END +}; + +/* + * Unlike other clocks, the TV rate is adjusted through PLL + * re-programming. It is also routed through an MND divider. + */ +void set_rate_tv(struct rcg_clk *clk, struct clk_freq_tbl *nf) +{ + unsigned long pll_rate = (unsigned long)nf->extra_freq_data; + if (pll_rate) + hdmi_pll_set_rate(pll_rate); + set_rate_mnd(clk, nf); +} + +static struct rcg_clk tv_src_clk = { + .ns_reg = TV_NS_REG, + .b = { + .ctl_reg = TV_CC_REG, + .halt_check = NOCHECK, + }, + .md_reg = TV_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(23, 16) | BM(15, 14) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_tv, + .freq_tbl = clk_tbl_tv, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "tv_src_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(tv_src_clk.c), + }, +}; + +static struct branch_clk tv_enc_clk = { + .b = { + .ctl_reg = TV_CC_REG, + .en_mask = BIT(8), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_D_REG, + .halt_bit = 9, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "tv_enc_clk", + .ops = &clk_ops_branch, + CLK_INIT(tv_enc_clk.c), + }, +}; + +static struct branch_clk tv_dac_clk = { + .b = { + .ctl_reg = TV_CC_REG, + .en_mask = BIT(10), + .halt_reg = DBG_BUS_VEC_D_REG, + .halt_bit = 10, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "tv_dac_clk", + .ops = &clk_ops_branch, + CLK_INIT(tv_dac_clk.c), + }, +}; + +static struct branch_clk mdp_tv_clk = { + .b = { + .ctl_reg = TV_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(4), + .halt_reg = DBG_BUS_VEC_D_REG, + .halt_bit = 12, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "mdp_tv_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdp_tv_clk.c), + }, +}; + +static struct branch_clk hdmi_tv_clk = { + .b = { + .ctl_reg = TV_CC_REG, + .en_mask = BIT(12), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(1), + .halt_reg = DBG_BUS_VEC_D_REG, + .halt_bit = 11, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "hdmi_tv_clk", + .ops = &clk_ops_branch, + CLK_INIT(hdmi_tv_clk.c), + }, +}; + +static struct branch_clk hdmi_app_clk = { + .b = { + .ctl_reg = MISC_CC2_REG, + .en_mask = BIT(11), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(11), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 25, + }, + .c = { + .dbg_name = "hdmi_app_clk", + .ops = &clk_ops_branch, + CLK_INIT(hdmi_app_clk.c), + }, +}; + +static struct bank_masks bmnd_info_vcodec = { + .bank_sel_mask = BIT(13), + .bank0_mask = { + .md_reg = VCODEC_MD0_REG, + .ns_mask = BM(18, 11) | BM(2, 0), + .rst_mask = BIT(31), + .mnd_en_mask = BIT(5), + .mode_mask = BM(7, 6), + }, + .bank1_mask = { + .md_reg = VCODEC_MD1_REG, + .ns_mask = BM(26, 19) | BM(29, 27), + .rst_mask = BIT(30), + .mnd_en_mask = BIT(10), + .mode_mask = BM(12, 11), + }, +}; +#define F_VCODEC(f, s, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MND_BANKED8(11, 19, n, m, 0, 27, s##_to_mm_mux), \ + .ctl_val = CC_BANKED(6, 11, n), \ + .mnd_en_mask = (BIT(10) | BIT(5)) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_vcodec[] = { + F_VCODEC( 0, gnd, 0, 0, NONE), + F_VCODEC( 27000000, pxo, 0, 0, LOW), + F_VCODEC( 32000000, pll8, 1, 12, LOW), + F_VCODEC( 48000000, pll8, 1, 8, LOW), + F_VCODEC( 54860000, pll8, 1, 7, LOW), + F_VCODEC( 96000000, pll8, 1, 4, LOW), + F_VCODEC(133330000, pll2, 1, 6, NOMINAL), + F_VCODEC(200000000, pll2, 1, 4, NOMINAL), + F_VCODEC(228570000, pll2, 2, 7, HIGH), + F_END +}; + +static struct rcg_clk vcodec_clk = { + .b = { + .ctl_reg = VCODEC_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(6), + .halt_reg = DBG_BUS_VEC_C_REG, + .halt_bit = 29, + }, + .ns_reg = VCODEC_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .bank_masks = &bmnd_info_vcodec, + .freq_tbl = clk_tbl_vcodec, + .depends = &vcodec_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "vcodec_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(vcodec_clk.c), + }, +}; + +#define F_VPE(f, s, d, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_vpe[] = { + F_VPE( 0, gnd, 1, NONE), + F_VPE( 27000000, pxo, 1, LOW), + F_VPE( 34909000, pll8, 11, LOW), + F_VPE( 38400000, pll8, 10, LOW), + F_VPE( 64000000, pll8, 6, LOW), + F_VPE( 76800000, pll8, 5, LOW), + F_VPE( 96000000, pll8, 4, NOMINAL), + F_VPE(100000000, pll2, 8, NOMINAL), + F_VPE(160000000, pll2, 5, NOMINAL), + F_END +}; + +static struct rcg_clk vpe_clk = { + .b = { + .ctl_reg = VPE_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(17), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 28, + }, + .ns_reg = VPE_NS_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(15, 12) | BM(2, 0)), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_vpe, + .current_freq = &local_dummy_freq, + .depends = &vpe_axi_clk.c, + .c = { + .dbg_name = "vpe_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(vpe_clk.c), + }, +}; + +#define F_VFE(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(23, 16, n, m, 11, 10, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_vfe[] = { + F_VFE( 0, gnd, 1, 0, 0, NONE), + F_VFE( 13960000, pll8, 1, 2, 55, LOW), + F_VFE( 27000000, pxo, 1, 0, 0, LOW), + F_VFE( 36570000, pll8, 1, 2, 21, LOW), + F_VFE( 38400000, pll8, 2, 1, 5, LOW), + F_VFE( 45180000, pll8, 1, 2, 17, LOW), + F_VFE( 48000000, pll8, 2, 1, 4, LOW), + F_VFE( 54860000, pll8, 1, 1, 7, LOW), + F_VFE( 64000000, pll8, 2, 1, 3, LOW), + F_VFE( 76800000, pll8, 1, 1, 5, LOW), + F_VFE( 96000000, pll8, 2, 1, 2, LOW), + F_VFE(109710000, pll8, 1, 2, 7, LOW), + F_VFE(128000000, pll8, 1, 1, 3, NOMINAL), + F_VFE(153600000, pll8, 1, 2, 5, NOMINAL), + F_VFE(200000000, pll2, 2, 1, 2, NOMINAL), + F_VFE(228570000, pll2, 1, 2, 7, NOMINAL), + F_VFE(266667000, pll2, 1, 1, 3, NOMINAL), + F_END +}; + + +static struct rcg_clk vfe_clk = { + .b = { + .ctl_reg = VFE_CC_REG, + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(15), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 6, + .en_mask = BIT(0), + }, + .ns_reg = VFE_NS_REG, + .md_reg = VFE_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(23, 16) | BM(11, 10) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_vfe, + .depends = &vfe_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "vfe_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(vfe_clk.c), + }, +}; + +static struct branch_clk csi0_vfe_clk = { + .b = { + .ctl_reg = VFE_CC_REG, + .en_mask = BIT(12), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(24), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 8, + }, + .parent = &vfe_clk.c, + .c = { + .dbg_name = "csi0_vfe_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0_vfe_clk.c), + }, +}; + +/* + * Low Power Audio Clocks + */ +#define F_AIF_OSR(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS(31, 24, n, m, 5, 4, 3, d, 2, 0, s##_to_lpa_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_aif_osr[] = { + F_AIF_OSR( 0, gnd, 1, 0, 0, NONE), + F_AIF_OSR( 768000, pll4, 4, 1, 128, LOW), + F_AIF_OSR( 1024000, pll4, 4, 1, 96, LOW), + F_AIF_OSR( 1536000, pll4, 4, 1, 64, LOW), + F_AIF_OSR( 2048000, pll4, 4, 1, 48, LOW), + F_AIF_OSR( 3072000, pll4, 4, 1, 32, LOW), + F_AIF_OSR( 4096000, pll4, 4, 1, 24, LOW), + F_AIF_OSR( 6144000, pll4, 4, 1, 16, LOW), + F_AIF_OSR( 8192000, pll4, 4, 1, 12, LOW), + F_AIF_OSR(12288000, pll4, 4, 1, 8, LOW), + F_AIF_OSR(24576000, pll4, 4, 1, 4, LOW), + F_END +}; + +#define CLK_AIF_OSR(i, ns, md, h_r) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(17), \ + .reset_reg = ns, \ + .reset_mask = BIT(19), \ + .halt_reg = h_r, \ + .halt_check = ENABLE, \ + .halt_bit = 1, \ + }, \ + .ns_reg = ns, \ + .md_reg = md, \ + .root_en_mask = BIT(9), \ + .ns_mask = (BM(31, 24) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_aif_osr, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8960, \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define CLK_AIF_OSR_DIV(i, ns, md, h_r) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(21), \ + .reset_reg = ns, \ + .reset_mask = BIT(23), \ + .halt_reg = h_r, \ + .halt_check = ENABLE, \ + .halt_bit = 1, \ + }, \ + .ns_reg = ns, \ + .md_reg = md, \ + .root_en_mask = BIT(9), \ + .ns_mask = (BM(31, 24) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_aif_osr, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8960, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +#define F_AIF_BIT(d, s) \ + { \ + .freq_hz = d, \ + .ns_val = (BVAL(14, 14, s) | BVAL(13, 10, (d-1))) \ + } +static struct clk_freq_tbl clk_tbl_aif_bit[] = { + F_AIF_BIT(0, 1), /* Use external clock. */ + F_AIF_BIT(1, 0), F_AIF_BIT(2, 0), F_AIF_BIT(3, 0), F_AIF_BIT(4, 0), + F_AIF_BIT(5, 0), F_AIF_BIT(6, 0), F_AIF_BIT(7, 0), F_AIF_BIT(8, 0), + F_AIF_BIT(9, 0), F_AIF_BIT(10, 0), F_AIF_BIT(11, 0), F_AIF_BIT(12, 0), + F_AIF_BIT(13, 0), F_AIF_BIT(14, 0), F_AIF_BIT(15, 0), F_AIF_BIT(16, 0), + F_END +}; + +#define CLK_AIF_BIT(i, ns, h_r) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(15), \ + .halt_reg = h_r, \ + .halt_check = DELAY, \ + }, \ + .ns_reg = ns, \ + .ns_mask = BM(14, 10), \ + .set_rate = set_rate_nop, \ + .freq_tbl = clk_tbl_aif_bit, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8960, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +#define F_AIF_BIT_D(d, s) \ + { \ + .freq_hz = d, \ + .ns_val = (BVAL(18, 18, s) | BVAL(17, 10, (d-1))) \ + } +static struct clk_freq_tbl clk_tbl_aif_bit_div[] = { + F_AIF_BIT_D(0, 1), /* Use external clock. */ + F_AIF_BIT_D(1, 0), F_AIF_BIT_D(2, 0), F_AIF_BIT_D(3, 0), + F_AIF_BIT_D(4, 0), F_AIF_BIT_D(5, 0), F_AIF_BIT_D(6, 0), + F_AIF_BIT_D(7, 0), F_AIF_BIT_D(8, 0), F_AIF_BIT_D(9, 0), + F_AIF_BIT_D(10, 0), F_AIF_BIT_D(11, 0), F_AIF_BIT_D(12, 0), + F_AIF_BIT_D(13, 0), F_AIF_BIT_D(14, 0), F_AIF_BIT_D(15, 0), + F_AIF_BIT_D(16, 0), + F_END +}; + +#define CLK_AIF_BIT_DIV(i, ns, h_r) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(19), \ + .halt_reg = h_r, \ + .halt_check = ENABLE, \ + }, \ + .ns_reg = ns, \ + .ns_mask = BM(18, 10), \ + .set_rate = set_rate_nop, \ + .freq_tbl = clk_tbl_aif_bit_div, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8960, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +static CLK_AIF_OSR(mi2s_osr, LCC_MI2S_NS_REG, LCC_MI2S_MD_REG, + LCC_MI2S_STATUS_REG); +static CLK_AIF_BIT(mi2s_bit, LCC_MI2S_NS_REG, LCC_MI2S_STATUS_REG); + +static CLK_AIF_OSR_DIV(codec_i2s_mic_osr, LCC_CODEC_I2S_MIC_NS_REG, + LCC_CODEC_I2S_MIC_MD_REG, LCC_CODEC_I2S_MIC_STATUS_REG); +static CLK_AIF_BIT_DIV(codec_i2s_mic_bit, LCC_CODEC_I2S_MIC_NS_REG, + LCC_CODEC_I2S_MIC_STATUS_REG); + +static CLK_AIF_OSR_DIV(spare_i2s_mic_osr, LCC_SPARE_I2S_MIC_NS_REG, + LCC_SPARE_I2S_MIC_MD_REG, LCC_SPARE_I2S_MIC_STATUS_REG); +static CLK_AIF_BIT_DIV(spare_i2s_mic_bit, LCC_SPARE_I2S_MIC_NS_REG, + LCC_SPARE_I2S_MIC_STATUS_REG); + +static CLK_AIF_OSR_DIV(codec_i2s_spkr_osr, LCC_CODEC_I2S_SPKR_NS_REG, + LCC_CODEC_I2S_SPKR_MD_REG, LCC_CODEC_I2S_SPKR_STATUS_REG); +static CLK_AIF_BIT_DIV(codec_i2s_spkr_bit, LCC_CODEC_I2S_SPKR_NS_REG, + LCC_CODEC_I2S_SPKR_STATUS_REG); + +static CLK_AIF_OSR_DIV(spare_i2s_spkr_osr, LCC_SPARE_I2S_SPKR_NS_REG, + LCC_SPARE_I2S_SPKR_MD_REG, LCC_SPARE_I2S_SPKR_STATUS_REG); +static CLK_AIF_BIT_DIV(spare_i2s_spkr_bit, LCC_SPARE_I2S_SPKR_NS_REG, + LCC_SPARE_I2S_SPKR_STATUS_REG); + +#define F_PCM(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD16(m, n), \ + .ns_val = NS(31, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_lpa_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_pcm[] = { + F_PCM( 0, gnd, 1, 0, 0, NONE), + F_PCM( 512000, pll4, 4, 1, 192, LOW), + F_PCM( 768000, pll4, 4, 1, 128, LOW), + F_PCM( 1024000, pll4, 4, 1, 96, LOW), + F_PCM( 1536000, pll4, 4, 1, 64, LOW), + F_PCM( 2048000, pll4, 4, 1, 48, LOW), + F_PCM( 3072000, pll4, 4, 1, 32, LOW), + F_PCM( 4096000, pll4, 4, 1, 24, LOW), + F_PCM( 6144000, pll4, 4, 1, 16, LOW), + F_PCM( 8192000, pll4, 4, 1, 12, LOW), + F_PCM(12288000, pll4, 4, 1, 8, LOW), + F_PCM(24576000, pll4, 4, 1, 4, LOW), + F_END +}; + +static struct rcg_clk pcm_clk = { + .b = { + .ctl_reg = LCC_PCM_NS_REG, + .en_mask = BIT(11), + .reset_reg = LCC_PCM_NS_REG, + .reset_mask = BIT(13), + .halt_reg = LCC_PCM_STATUS_REG, + .halt_check = ENABLE, + .halt_bit = 0, + }, + .ns_reg = LCC_PCM_NS_REG, + .md_reg = LCC_PCM_MD_REG, + .root_en_mask = BIT(9), + .ns_mask = (BM(31, 16) | BM(6, 0)), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_pcm, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "pcm_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(pcm_clk.c), + }, +}; + +static struct rcg_clk audio_slimbus_clk = { + .b = { + .ctl_reg = LCC_SLIMBUS_NS_REG, + .en_mask = BIT(10), + .reset_reg = LCC_AHBEX_BRANCH_CTL_REG, + .reset_mask = BIT(5), + .halt_reg = LCC_SLIMBUS_STATUS_REG, + .halt_check = ENABLE, + .halt_bit = 0, + }, + .ns_reg = LCC_SLIMBUS_NS_REG, + .md_reg = LCC_SLIMBUS_MD_REG, + .root_en_mask = BIT(9), + .ns_mask = (BM(31, 24) | BM(6, 0)), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_aif_osr, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "audio_slimbus_clk", + .ops = &soc_clk_ops_8960, + CLK_INIT(audio_slimbus_clk.c), + }, +}; + +static struct branch_clk sps_slimbus_clk = { + .b = { + .ctl_reg = LCC_SLIMBUS_NS_REG, + .en_mask = BIT(12), + .halt_reg = LCC_SLIMBUS_STATUS_REG, + .halt_check = ENABLE, + .halt_bit = 1, + }, + .parent = &audio_slimbus_clk.c, + .c = { + .dbg_name = "sps_slimbus_clk", + .ops = &clk_ops_branch, + CLK_INIT(sps_slimbus_clk.c), + }, +}; + +static struct branch_clk slimbus_xo_src_clk = { + .b = { + .ctl_reg = SLIMBUS_XO_SRC_CLK_CTL_REG, + .en_mask = BIT(2), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_check = HALT, + .halt_bit = 28, + }, + .parent = &sps_slimbus_clk.c, + .c = { + .dbg_name = "slimbus_xo_src_clk", + .ops = &clk_ops_branch, + CLK_INIT(slimbus_xo_src_clk.c), + }, +}; + +DEFINE_CLK_RPM(afab_clk, afab_a_clk, APPS_FABRIC); +DEFINE_CLK_RPM(cfpb_clk, cfpb_a_clk, CFPB); +DEFINE_CLK_RPM(dfab_clk, dfab_a_clk, DAYTONA_FABRIC); +DEFINE_CLK_RPM(ebi1_clk, ebi1_a_clk, EBI1); +DEFINE_CLK_RPM(mmfab_clk, mmfab_a_clk, MM_FABRIC); +DEFINE_CLK_RPM(mmfpb_clk, mmfpb_a_clk, MMFPB); +DEFINE_CLK_RPM(sfab_clk, sfab_a_clk, SYSTEM_FABRIC); +DEFINE_CLK_RPM(sfpb_clk, sfpb_a_clk, SFPB); + +static DEFINE_CLK_VOTER(dfab_dsps_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_usb_hs_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc1_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc2_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc3_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc4_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc5_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sps_clk, &dfab_clk.c); + +static DEFINE_CLK_VOTER(ebi1_msmbus_clk, &ebi1_clk.c); +/* + * TODO: replace dummy_clk below with ebi1_clk.c once the + * bus driver starts voting on ebi1 rates. + */ +static DEFINE_CLK_VOTER(ebi1_adm_clk, &dummy_clk); + +#ifdef CONFIG_DEBUG_FS +struct measure_sel { + u32 test_vector; + struct clk *clk; +}; + +static struct measure_sel measure_mux[] = { + { TEST_PER_LS(0x08), &slimbus_xo_src_clk.c }, + { TEST_PER_LS(0x12), &sdc1_p_clk.c }, + { TEST_PER_LS(0x13), &sdc1_clk.c }, + { TEST_PER_LS(0x14), &sdc2_p_clk.c }, + { TEST_PER_LS(0x15), &sdc2_clk.c }, + { TEST_PER_LS(0x16), &sdc3_p_clk.c }, + { TEST_PER_LS(0x17), &sdc3_clk.c }, + { TEST_PER_LS(0x18), &sdc4_p_clk.c }, + { TEST_PER_LS(0x19), &sdc4_clk.c }, + { TEST_PER_LS(0x1A), &sdc5_p_clk.c }, + { TEST_PER_LS(0x1B), &sdc5_clk.c }, + { TEST_PER_LS(0x25), &dfab_clk.c }, + { TEST_PER_LS(0x25), &dfab_a_clk.c }, + { TEST_PER_LS(0x26), &pmem_clk.c }, + { TEST_PER_LS(0x32), &dma_bam_p_clk.c }, + { TEST_PER_LS(0x33), &cfpb_clk.c }, + { TEST_PER_LS(0x33), &cfpb_a_clk.c }, + { TEST_PER_LS(0x3D), &gsbi1_p_clk.c }, + { TEST_PER_LS(0x3E), &gsbi1_uart_clk.c }, + { TEST_PER_LS(0x3F), &gsbi1_qup_clk.c }, + { TEST_PER_LS(0x41), &gsbi2_p_clk.c }, + { TEST_PER_LS(0x42), &gsbi2_uart_clk.c }, + { TEST_PER_LS(0x44), &gsbi2_qup_clk.c }, + { TEST_PER_LS(0x45), &gsbi3_p_clk.c }, + { TEST_PER_LS(0x46), &gsbi3_uart_clk.c }, + { TEST_PER_LS(0x48), &gsbi3_qup_clk.c }, + { TEST_PER_LS(0x49), &gsbi4_p_clk.c }, + { TEST_PER_LS(0x4A), &gsbi4_uart_clk.c }, + { TEST_PER_LS(0x4C), &gsbi4_qup_clk.c }, + { TEST_PER_LS(0x4D), &gsbi5_p_clk.c }, + { TEST_PER_LS(0x4E), &gsbi5_uart_clk.c }, + { TEST_PER_LS(0x50), &gsbi5_qup_clk.c }, + { TEST_PER_LS(0x51), &gsbi6_p_clk.c }, + { TEST_PER_LS(0x52), &gsbi6_uart_clk.c }, + { TEST_PER_LS(0x54), &gsbi6_qup_clk.c }, + { TEST_PER_LS(0x55), &gsbi7_p_clk.c }, + { TEST_PER_LS(0x56), &gsbi7_uart_clk.c }, + { TEST_PER_LS(0x58), &gsbi7_qup_clk.c }, + { TEST_PER_LS(0x59), &gsbi8_p_clk.c }, + { TEST_PER_LS(0x5A), &gsbi8_uart_clk.c }, + { TEST_PER_LS(0x5C), &gsbi8_qup_clk.c }, + { TEST_PER_LS(0x5D), &gsbi9_p_clk.c }, + { TEST_PER_LS(0x5E), &gsbi9_uart_clk.c }, + { TEST_PER_LS(0x60), &gsbi9_qup_clk.c }, + { TEST_PER_LS(0x61), &gsbi10_p_clk.c }, + { TEST_PER_LS(0x62), &gsbi10_uart_clk.c }, + { TEST_PER_LS(0x64), &gsbi10_qup_clk.c }, + { TEST_PER_LS(0x65), &gsbi11_p_clk.c }, + { TEST_PER_LS(0x66), &gsbi11_uart_clk.c }, + { TEST_PER_LS(0x68), &gsbi11_qup_clk.c }, + { TEST_PER_LS(0x69), &gsbi12_p_clk.c }, + { TEST_PER_LS(0x6A), &gsbi12_uart_clk.c }, + { TEST_PER_LS(0x6C), &gsbi12_qup_clk.c }, + { TEST_PER_LS(0x78), &sfpb_clk.c }, + { TEST_PER_LS(0x78), &sfpb_a_clk.c }, + { TEST_PER_LS(0x7A), &pmic_ssbi2_clk.c }, + { TEST_PER_LS(0x7B), &pmic_arb0_p_clk.c }, + { TEST_PER_LS(0x7C), &pmic_arb1_p_clk.c }, + { TEST_PER_LS(0x7D), &prng_clk.c }, + { TEST_PER_LS(0x7F), &rpm_msg_ram_p_clk.c }, + { TEST_PER_LS(0x80), &adm0_p_clk.c }, + { TEST_PER_LS(0x84), &usb_hs1_p_clk.c }, + { TEST_PER_LS(0x85), &usb_hs1_xcvr_clk.c }, + { TEST_PER_LS(0x89), &usb_fs1_p_clk.c }, + { TEST_PER_LS(0x8A), &usb_fs1_sys_clk.c }, + { TEST_PER_LS(0x8B), &usb_fs1_xcvr_clk.c }, + { TEST_PER_LS(0x8C), &usb_fs2_p_clk.c }, + { TEST_PER_LS(0x8D), &usb_fs2_sys_clk.c }, + { TEST_PER_LS(0x8E), &usb_fs2_xcvr_clk.c }, + { TEST_PER_LS(0x8F), &tsif_p_clk.c }, + { TEST_PER_LS(0x91), &tsif_ref_clk.c }, + { TEST_PER_LS(0x92), &ce1_p_clk.c }, + { TEST_PER_LS(0x94), &tssc_clk.c }, + { TEST_PER_LS(0xA4), &ce1_core_clk.c }, + + { TEST_PER_HS(0x07), &afab_clk.c }, + { TEST_PER_HS(0x07), &afab_a_clk.c }, + { TEST_PER_HS(0x18), &sfab_clk.c }, + { TEST_PER_HS(0x18), &sfab_a_clk.c }, + { TEST_PER_HS(0x2A), &adm0_clk.c }, + { TEST_PER_HS(0x34), &ebi1_clk.c }, + { TEST_PER_HS(0x34), &ebi1_a_clk.c }, + + { TEST_MM_LS(0x00), &dsi1_byte_clk.c }, + { TEST_MM_LS(0x01), &dsi2_byte_clk.c }, + { TEST_MM_LS(0x02), &cam1_clk.c }, + { TEST_MM_LS(0x06), &_p_clk.c }, + { TEST_MM_LS(0x07), &csi0_p_clk.c }, + { TEST_MM_LS(0x08), &dsi2_s_p_clk.c }, + { TEST_MM_LS(0x09), &dsi1_m_p_clk.c }, + { TEST_MM_LS(0x0A), &dsi1_s_p_clk.c }, + { TEST_MM_LS(0x0C), &gfx2d0_p_clk.c }, + { TEST_MM_LS(0x0D), &gfx2d1_p_clk.c }, + { TEST_MM_LS(0x0E), &gfx3d_p_clk.c }, + { TEST_MM_LS(0x0F), &hdmi_m_p_clk.c }, + { TEST_MM_LS(0x10), &hdmi_s_p_clk.c }, + { TEST_MM_LS(0x11), &ijpeg_p_clk.c }, + { TEST_MM_LS(0x12), &imem_p_clk.c }, + { TEST_MM_LS(0x13), &jpegd_p_clk.c }, + { TEST_MM_LS(0x14), &mdp_p_clk.c }, + { TEST_MM_LS(0x16), &rot_p_clk.c }, + { TEST_MM_LS(0x17), &dsi1_esc_clk.c }, + { TEST_MM_LS(0x18), &smmu_p_clk.c }, + { TEST_MM_LS(0x19), &tv_enc_p_clk.c }, + { TEST_MM_LS(0x1A), &vcodec_p_clk.c }, + { TEST_MM_LS(0x1B), &vfe_p_clk.c }, + { TEST_MM_LS(0x1C), &vpe_p_clk.c }, + { TEST_MM_LS(0x1D), &cam0_clk.c }, + { TEST_MM_LS(0x1F), &hdmi_app_clk.c }, + { TEST_MM_LS(0x20), &mdp_vsync_clk.c }, + { TEST_MM_LS(0x21), &tv_dac_clk.c }, + { TEST_MM_LS(0x22), &tv_enc_clk.c }, + { TEST_MM_LS(0x23), &dsi2_esc_clk.c }, + { TEST_MM_LS(0x25), &mmfpb_clk.c }, + { TEST_MM_LS(0x25), &mmfpb_a_clk.c }, + { TEST_MM_LS(0x26), &dsi2_m_p_clk.c }, + + { TEST_MM_HS(0x00), &csi0_clk.c }, + { TEST_MM_HS(0x01), &csi1_clk.c }, + { TEST_MM_HS(0x04), &csi0_vfe_clk.c }, + { TEST_MM_HS(0x05), &ijpeg_clk.c }, + { TEST_MM_HS(0x06), &vfe_clk.c }, + { TEST_MM_HS(0x07), &gfx2d0_clk.c }, + { TEST_MM_HS(0x08), &gfx2d1_clk.c }, + { TEST_MM_HS(0x09), &gfx3d_clk.c }, + { TEST_MM_HS(0x0A), &jpegd_clk.c }, + { TEST_MM_HS(0x0B), &vcodec_clk.c }, + { TEST_MM_HS(0x0F), &mmfab_clk.c }, + { TEST_MM_HS(0x0F), &mmfab_a_clk.c }, + { TEST_MM_HS(0x11), &gmem_axi_clk.c }, + { TEST_MM_HS(0x12), &ijpeg_axi_clk.c }, + { TEST_MM_HS(0x13), &imem_axi_clk.c }, + { TEST_MM_HS(0x14), &jpegd_axi_clk.c }, + { TEST_MM_HS(0x15), &mdp_axi_clk.c }, + { TEST_MM_HS(0x16), &rot_axi_clk.c }, + { TEST_MM_HS(0x17), &vcodec_axi_clk.c }, + { TEST_MM_HS(0x18), &vfe_axi_clk.c }, + { TEST_MM_HS(0x19), &vpe_axi_clk.c }, + { TEST_MM_HS(0x1A), &mdp_clk.c }, + { TEST_MM_HS(0x1B), &rot_clk.c }, + { TEST_MM_HS(0x1C), &vpe_clk.c }, + { TEST_MM_HS(0x1E), &hdmi_tv_clk.c }, + { TEST_MM_HS(0x1F), &mdp_tv_clk.c }, + { TEST_MM_HS(0x24), &csi0_phy_clk.c }, + { TEST_MM_HS(0x25), &csi1_phy_clk.c }, + { TEST_MM_HS(0x26), &csi_pix_clk.c }, + { TEST_MM_HS(0x27), &csi_rdi_clk.c }, + { TEST_MM_HS(0x28), &lut_mdp_clk.c }, + { TEST_MM_HS(0x29), &vcodec_axi_a_clk.c }, + { TEST_MM_HS(0x2A), &vcodec_axi_b_clk.c }, + { TEST_MM_HS(0x2B), &csi1phy_timer_clk.c }, + { TEST_MM_HS(0x2C), &csi0phy_timer_clk.c }, + + { TEST_LPA(0x0F), &mi2s_bit_clk.c }, + { TEST_LPA(0x10), &codec_i2s_mic_bit_clk.c }, + { TEST_LPA(0x11), &codec_i2s_spkr_bit_clk.c }, + { TEST_LPA(0x12), &spare_i2s_mic_bit_clk.c }, + { TEST_LPA(0x13), &spare_i2s_spkr_bit_clk.c }, + { TEST_LPA(0x14), &pcm_clk.c }, + { TEST_LPA(0x1D), &audio_slimbus_clk.c }, +}; + +static struct measure_sel *find_measure_sel(struct clk *clk) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(measure_mux); i++) + if (measure_mux[i].clk == clk) + return &measure_mux[i]; + return NULL; +} + +static int measure_clk_set_parent(struct clk *clk, struct clk *parent) +{ + int ret = 0; + u32 clk_sel; + struct measure_sel *p; + unsigned long flags; + + if (!parent) + return -EINVAL; + + p = find_measure_sel(parent); + if (!p) + return -EINVAL; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + /* Program the test vector. */ + clk_sel = p->test_vector & TEST_CLK_SEL_MASK; + switch (p->test_vector >> TEST_TYPE_SHIFT) { + case TEST_TYPE_PER_LS: + writel_relaxed(0x4030D00|BVAL(7, 0, clk_sel), CLK_TEST_REG); + break; + case TEST_TYPE_PER_HS: + writel_relaxed(0x4020000|BVAL(16, 10, clk_sel), CLK_TEST_REG); + break; + case TEST_TYPE_MM_LS: + writel_relaxed(0x4030D97, CLK_TEST_REG); + writel_relaxed(BVAL(6, 1, clk_sel)|BIT(0), DBG_CFG_REG_LS_REG); + break; + case TEST_TYPE_MM_HS: + writel_relaxed(0x402B800, CLK_TEST_REG); + writel_relaxed(BVAL(6, 1, clk_sel)|BIT(0), DBG_CFG_REG_HS_REG); + break; + case TEST_TYPE_LPA: + writel_relaxed(0x4030D98, CLK_TEST_REG); + writel_relaxed(BVAL(6, 1, clk_sel)|BIT(0), + LCC_CLK_LS_DEBUG_CFG_REG); + break; + default: + ret = -EPERM; + } + /* Make sure test vector is set before starting measurements. */ + mb(); + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return ret; +} + +/* Sample clock for 'ticks' reference clock ticks. */ +static u32 run_measurement(unsigned ticks) +{ + /* Stop counters and set the XO4 counter start value. */ + writel_relaxed(0x0, RINGOSC_TCXO_CTL_REG); + writel_relaxed(ticks, RINGOSC_TCXO_CTL_REG); + + /* Wait for timer to become ready. */ + while ((readl_relaxed(RINGOSC_STATUS_REG) & BIT(25)) != 0) + cpu_relax(); + + /* Run measurement and wait for completion. */ + writel_relaxed(BIT(20)|ticks, RINGOSC_TCXO_CTL_REG); + while ((readl_relaxed(RINGOSC_STATUS_REG) & BIT(25)) == 0) + cpu_relax(); + + /* Stop counters. */ + writel_relaxed(0x0, RINGOSC_TCXO_CTL_REG); + + /* Return measured ticks. */ + return readl_relaxed(RINGOSC_STATUS_REG) & BM(24, 0); +} + + +/* Perform a hardware rate measurement for a given clock. + FOR DEBUG USE ONLY: Measurements take ~15 ms! */ +static unsigned measure_clk_get_rate(struct clk *clk) +{ + unsigned long flags; + u32 pdm_reg_backup, ringosc_reg_backup; + u64 raw_count_short, raw_count_full; + unsigned ret; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + /* Enable CXO/4 and RINGOSC branch and root. */ + pdm_reg_backup = readl_relaxed(PDM_CLK_NS_REG); + ringosc_reg_backup = readl_relaxed(RINGOSC_NS_REG); + writel_relaxed(0x2898, PDM_CLK_NS_REG); + writel_relaxed(0xA00, RINGOSC_NS_REG); + + /* + * The ring oscillator counter will not reset if the measured clock + * is not running. To detect this, run a short measurement before + * the full measurement. If the raw results of the two are the same + * then the clock must be off. + */ + + /* Run a short measurement. (~1 ms) */ + raw_count_short = run_measurement(0x1000); + /* Run a full measurement. (~14 ms) */ + raw_count_full = run_measurement(0x10000); + + writel_relaxed(ringosc_reg_backup, RINGOSC_NS_REG); + writel_relaxed(pdm_reg_backup, PDM_CLK_NS_REG); + + /* Return 0 if the clock is off. */ + if (raw_count_full == raw_count_short) + ret = 0; + else { + /* Compute rate in Hz. */ + raw_count_full = ((raw_count_full * 10) + 15) * 4800000; + do_div(raw_count_full, ((0x10000 * 10) + 35)); + ret = raw_count_full; + } + + /* Route dbg_hs_clk to PLLTEST. 300mV single-ended amplitude. */ + writel_relaxed(0x3CF8, PLLTEST_PAD_CFG_REG); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return ret; +} +#else /* !CONFIG_DEBUG_FS */ +static int measure_clk_set_parent(struct clk *clk, struct clk *parent) +{ + return -EINVAL; +} + +static unsigned measure_clk_get_rate(struct clk *clk) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static struct clk_ops measure_clk_ops = { + .set_parent = measure_clk_set_parent, + .get_rate = measure_clk_get_rate, + .is_local = local_clk_is_local, +}; + +static struct clk measure_clk = { + .dbg_name = "measure_clk", + .ops = &measure_clk_ops, + CLK_INIT(measure_clk), +}; + +static struct clk_lookup msm_clocks_8960[] = { + CLK_LOOKUP("cxo", cxo_clk.c, NULL), + CLK_LOOKUP("pll2", pll2_clk.c, NULL), + CLK_LOOKUP("pll8", pll8_clk.c, NULL), + CLK_LOOKUP("pll4", pll4_clk.c, NULL), + CLK_LOOKUP("measure", measure_clk, "debug"), + + CLK_LOOKUP("afab_clk", afab_clk.c, NULL), + CLK_LOOKUP("afab_a_clk", afab_a_clk.c, NULL), + CLK_LOOKUP("cfpb_clk", cfpb_clk.c, NULL), + CLK_LOOKUP("cfpb_a_clk", cfpb_a_clk.c, NULL), + CLK_LOOKUP("dfab_clk", dfab_clk.c, NULL), + CLK_LOOKUP("dfab_a_clk", dfab_a_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("ebi1_a_clk", ebi1_a_clk.c, NULL), + CLK_LOOKUP("mmfab_clk", mmfab_clk.c, NULL), + CLK_LOOKUP("mmfab_a_clk", mmfab_a_clk.c, NULL), + CLK_LOOKUP("mmfpb_clk", mmfpb_clk.c, NULL), + CLK_LOOKUP("mmfpb_a_clk", mmfpb_a_clk.c, NULL), + CLK_LOOKUP("sfab_clk", sfab_clk.c, NULL), + CLK_LOOKUP("sfab_a_clk", sfab_a_clk.c, NULL), + CLK_LOOKUP("sfpb_clk", sfpb_clk.c, NULL), + CLK_LOOKUP("sfpb_a_clk", sfpb_a_clk.c, NULL), + + CLK_LOOKUP("gsbi_uart_clk", gsbi1_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi2_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi3_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi4_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi5_uart_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("uartdm_clk", gsbi6_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi7_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi8_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi9_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi10_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi11_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi12_uart_clk.c, NULL), + CLK_LOOKUP("spi_clk", gsbi1_qup_clk.c, "spi_qsd.0"), + CLK_LOOKUP("gsbi_qup_clk", gsbi2_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi3_qup_clk.c, "qup_i2c.3"), + CLK_LOOKUP("gsbi_qup_clk", gsbi4_qup_clk.c, "qup_i2c.4"), + CLK_LOOKUP("gsbi_qup_clk", gsbi5_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi6_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi7_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi8_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi9_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi10_qup_clk.c, "qup_i2c.10"), + CLK_LOOKUP("gsbi_qup_clk", gsbi11_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi12_qup_clk.c, "qup_i2c.12"), + CLK_LOOKUP("pdm_clk", pdm_clk.c, NULL), + CLK_LOOKUP("pmem_clk", pmem_clk.c, NULL), + CLK_LOOKUP("prng_clk", prng_clk.c, NULL), + CLK_LOOKUP("sdc_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("sdc_clk", sdc5_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("slimbus_xo_src_clk", slimbus_xo_src_clk.c, NULL), + CLK_LOOKUP("tsif_ref_clk", tsif_ref_clk.c, NULL), + CLK_LOOKUP("tssc_clk", tssc_clk.c, NULL), + CLK_LOOKUP("usb_hs_clk", usb_hs1_xcvr_clk.c, NULL), + CLK_LOOKUP("usb_phy_clk", usb_phy0_clk.c, NULL), + CLK_LOOKUP("usb_fs_clk", usb_fs1_xcvr_clk.c, NULL), + CLK_LOOKUP("usb_fs_sys_clk", usb_fs1_sys_clk.c, NULL), + CLK_LOOKUP("usb_fs_src_clk", usb_fs1_src_clk.c, NULL), + CLK_LOOKUP("usb_fs_clk", usb_fs2_xcvr_clk.c, NULL), + CLK_LOOKUP("usb_fs_sys_clk", usb_fs2_sys_clk.c, NULL), + CLK_LOOKUP("usb_fs_src_clk", usb_fs2_src_clk.c, NULL), + CLK_LOOKUP("ce_pclk", ce1_p_clk.c, NULL), + CLK_LOOKUP("ce_clk", ce1_core_clk.c, NULL), + CLK_LOOKUP("dma_bam_pclk", dma_bam_p_clk.c, NULL), + CLK_LOOKUP("spi_pclk", gsbi1_p_clk.c, "spi_qsd.0"), + CLK_LOOKUP("gsbi_pclk", gsbi2_p_clk.c, NULL), + CLK_LOOKUP("gsbi_pclk", gsbi3_p_clk.c, "qup_i2c.3"), + CLK_LOOKUP("gsbi_pclk", gsbi4_p_clk.c, "qup_i2c.4"), + CLK_LOOKUP("gsbi_pclk", gsbi5_p_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("uartdm_pclk", gsbi6_p_clk.c, NULL), + CLK_LOOKUP("gsbi_pclk", gsbi7_p_clk.c, NULL), + CLK_LOOKUP("gsbi_pclk", gsbi8_p_clk.c, NULL), + CLK_LOOKUP("gsbi_pclk", gsbi9_p_clk.c, NULL), + CLK_LOOKUP("gsbi_pclk", gsbi10_p_clk.c, "qup_i2c.10"), + CLK_LOOKUP("gsbi_pclk", gsbi11_p_clk.c, NULL), + CLK_LOOKUP("gsbi_pclk", gsbi12_p_clk.c, "qup_i2c.12"), + CLK_LOOKUP("tsif_pclk", tsif_p_clk.c, NULL), + CLK_LOOKUP("usb_fs_pclk", usb_fs1_p_clk.c, NULL), + CLK_LOOKUP("usb_fs_pclk", usb_fs2_p_clk.c, NULL), + CLK_LOOKUP("usb_hs_pclk", usb_hs1_p_clk.c, NULL), + CLK_LOOKUP("sdc_pclk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_pclk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_pclk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_pclk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("sdc_pclk", sdc5_p_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("adm_clk", adm0_clk.c, NULL), + CLK_LOOKUP("adm_pclk", adm0_p_clk.c, NULL), + CLK_LOOKUP("pmic_arb_pclk", pmic_arb0_p_clk.c, NULL), + CLK_LOOKUP("pmic_arb_pclk", pmic_arb1_p_clk.c, NULL), + CLK_LOOKUP("pmic_ssbi2", pmic_ssbi2_clk.c, NULL), + CLK_LOOKUP("rpm_msg_ram_pclk", rpm_msg_ram_p_clk.c, NULL), + CLK_LOOKUP("amp_clk", amp_clk.c, NULL), + CLK_LOOKUP("cam_clk", cam0_clk.c, NULL), + CLK_LOOKUP("cam_clk", cam1_clk.c, NULL), + CLK_LOOKUP("cam_clk", cam0_clk.c, "msm_camera_imx074.0"), + CLK_LOOKUP("cam_clk", cam0_clk.c, "msm_camera_ov2720.0"), + CLK_LOOKUP("csi_src_clk", csi0_src_clk.c, NULL), + CLK_LOOKUP("csi_src_clk", csi1_src_clk.c, NULL), + CLK_LOOKUP("csi_src_clk", csi0_src_clk.c, "msm_camera_imx074.0"), + CLK_LOOKUP("csi_src_clk", csi1_src_clk.c, "msm_camera_ov2720.0"), + CLK_LOOKUP("csi_clk", csi0_clk.c, NULL), + CLK_LOOKUP("csi_clk", csi1_clk.c, NULL), + CLK_LOOKUP("csi_clk", csi0_clk.c, "msm_camera_imx074.0"), + CLK_LOOKUP("csi_clk", csi1_clk.c, "msm_camera_ov2720.0"), + CLK_LOOKUP("csi_phy_clk", csi0_phy_clk.c, NULL), + CLK_LOOKUP("csi_phy_clk", csi1_phy_clk.c, NULL), + CLK_LOOKUP("csi_phy_clk", csi0_phy_clk.c, "msm_camera_imx074.0"), + CLK_LOOKUP("csi_phy_clk", csi1_phy_clk.c, "msm_camera_ov2720.0"), + CLK_LOOKUP("csi_pix_clk", csi_pix_clk.c, NULL), + CLK_LOOKUP("csi_rdi_clk", csi_rdi_clk.c, NULL), + CLK_LOOKUP("csiphy_timer_src_clk", csiphy_timer_src_clk.c, NULL), + CLK_LOOKUP("csi0phy_timer_clk", csi0phy_timer_clk.c, NULL), + CLK_LOOKUP("csi1phy_timer_clk", csi1phy_timer_clk.c, NULL), + CLK_LOOKUP("dsi_byte_div_clk", dsi1_byte_clk.c, NULL), + CLK_LOOKUP("dsi_byte_div_clk", dsi2_byte_clk.c, NULL), + CLK_LOOKUP("dsi_esc_clk", dsi1_esc_clk.c, NULL), + CLK_LOOKUP("dsi_esc_clk", dsi2_esc_clk.c, NULL), + CLK_LOOKUP("gfx2d0_clk", gfx2d0_clk.c, NULL), + CLK_LOOKUP("gfx2d1_clk", gfx2d1_clk.c, NULL), + CLK_LOOKUP("gfx3d_clk", gfx3d_clk.c, NULL), + CLK_LOOKUP("ijpeg_axi_clk", ijpeg_axi_clk.c, NULL), + CLK_LOOKUP("imem_axi_clk", imem_axi_clk.c, NULL), + CLK_LOOKUP("ijpeg_clk", ijpeg_clk.c, NULL), + CLK_LOOKUP("jpegd_clk", jpegd_clk.c, NULL), + CLK_LOOKUP("mdp_clk", mdp_clk.c, NULL), + CLK_LOOKUP("mdp_vsync_clk", mdp_vsync_clk.c, NULL), + CLK_LOOKUP("lut_mdp", lut_mdp_clk.c, NULL), + CLK_LOOKUP("rot_clk", rot_clk.c, NULL), + CLK_LOOKUP("tv_src_clk", tv_src_clk.c, NULL), + CLK_LOOKUP("tv_enc_clk", tv_enc_clk.c, NULL), + CLK_LOOKUP("tv_dac_clk", tv_dac_clk.c, NULL), + CLK_LOOKUP("vcodec_clk", vcodec_clk.c, NULL), + CLK_LOOKUP("mdp_tv_clk", mdp_tv_clk.c, NULL), + CLK_LOOKUP("hdmi_clk", hdmi_tv_clk.c, NULL), + CLK_LOOKUP("hdmi_app_clk", hdmi_app_clk.c, NULL), + CLK_LOOKUP("vpe_clk", vpe_clk.c, NULL), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("csi_vfe_clk", csi0_vfe_clk.c, NULL), + CLK_LOOKUP("vfe_axi_clk", vfe_axi_clk.c, NULL), + CLK_LOOKUP("mdp_axi_clk", mdp_axi_clk.c, NULL), + CLK_LOOKUP("rot_axi_clk", rot_axi_clk.c, NULL), + CLK_LOOKUP("vcodec_axi_clk", vcodec_axi_clk.c, NULL), + CLK_LOOKUP("vcodec_axi_a_clk", vcodec_axi_a_clk.c, NULL), + CLK_LOOKUP("vcodec_axi_b_clk", vcodec_axi_b_clk.c, NULL), + CLK_LOOKUP("vpe_axi_clk", vpe_axi_clk.c, NULL), + CLK_LOOKUP("amp_pclk", amp_p_clk.c, NULL), + CLK_LOOKUP("csi_pclk", csi0_p_clk.c, NULL), + CLK_LOOKUP("dsi_m_pclk", dsi1_m_p_clk.c, NULL), + CLK_LOOKUP("dsi_s_pclk", dsi1_s_p_clk.c, NULL), + CLK_LOOKUP("dsi_m_pclk", dsi2_m_p_clk.c, NULL), + CLK_LOOKUP("dsi_s_pclk", dsi2_s_p_clk.c, NULL), + CLK_LOOKUP("gfx2d0_pclk", gfx2d0_p_clk.c, NULL), + CLK_LOOKUP("gfx2d1_pclk", gfx2d1_p_clk.c, NULL), + CLK_LOOKUP("gfx3d_pclk", gfx3d_p_clk.c, NULL), + CLK_LOOKUP("hdmi_m_pclk", hdmi_m_p_clk.c, NULL), + CLK_LOOKUP("hdmi_s_pclk", hdmi_s_p_clk.c, NULL), + CLK_LOOKUP("ijpeg_pclk", ijpeg_p_clk.c, NULL), + CLK_LOOKUP("jpegd_pclk", jpegd_p_clk.c, NULL), + CLK_LOOKUP("imem_pclk", imem_p_clk.c, NULL), + CLK_LOOKUP("mdp_pclk", mdp_p_clk.c, NULL), + CLK_LOOKUP("smmu_pclk", smmu_p_clk.c, NULL), + CLK_LOOKUP("rotator_pclk", rot_p_clk.c, NULL), + CLK_LOOKUP("tv_enc_pclk", tv_enc_p_clk.c, NULL), + CLK_LOOKUP("vcodec_pclk", vcodec_p_clk.c, NULL), + CLK_LOOKUP("vfe_pclk", vfe_p_clk.c, NULL), + CLK_LOOKUP("vpe_pclk", vpe_p_clk.c, NULL), + CLK_LOOKUP("mi2s_bit_clk", mi2s_bit_clk.c, NULL), + CLK_LOOKUP("mi2s_osr_clk", mi2s_osr_clk.c, NULL), + CLK_LOOKUP("i2s_mic_bit_clk", codec_i2s_mic_bit_clk.c, NULL), + CLK_LOOKUP("i2s_mic_osr_clk", codec_i2s_mic_osr_clk.c, NULL), + CLK_LOOKUP("i2s_mic_bit_clk", spare_i2s_mic_bit_clk.c, NULL), + CLK_LOOKUP("i2s_mic_osr_clk", spare_i2s_mic_osr_clk.c, NULL), + CLK_LOOKUP("i2s_spkr_bit_clk", codec_i2s_spkr_bit_clk.c, NULL), + CLK_LOOKUP("i2s_spkr_osr_clk", codec_i2s_spkr_osr_clk.c, NULL), + CLK_LOOKUP("i2s_spkr_bit_clk", spare_i2s_spkr_bit_clk.c, NULL), + CLK_LOOKUP("i2s_spkr_osr_clk", spare_i2s_spkr_osr_clk.c, NULL), + CLK_LOOKUP("pcm_clk", pcm_clk.c, NULL), + CLK_LOOKUP("sps_slimbus_clk", sps_slimbus_clk.c, NULL), + CLK_LOOKUP("audio_slimbus_clk", audio_slimbus_clk.c, NULL), + CLK_LOOKUP("iommu_clk", jpegd_axi_clk.c, "msm_iommu.0"), + CLK_LOOKUP("iommu_clk", vpe_axi_clk.c, "msm_iommu.1"), + CLK_LOOKUP("iommu_clk", mdp_axi_clk.c, "msm_iommu.2"), + CLK_LOOKUP("iommu_clk", mdp_axi_clk.c, "msm_iommu.3"), + CLK_LOOKUP("iommu_clk", rot_axi_clk.c, "msm_iommu.4"), + CLK_LOOKUP("iommu_clk", ijpeg_axi_clk.c, "msm_iommu.5"), + CLK_LOOKUP("iommu_clk", vfe_axi_clk.c, "msm_iommu.6"), + CLK_LOOKUP("iommu_clk", vcodec_axi_a_clk.c, "msm_iommu.7"), + CLK_LOOKUP("iommu_clk", vcodec_axi_b_clk.c, "msm_iommu.8"), + CLK_LOOKUP("iommu_clk", gfx3d_clk.c, "msm_iommu.9"), + CLK_LOOKUP("iommu_clk", gfx2d0_clk.c, "msm_iommu.10"), + CLK_LOOKUP("iommu_clk", gfx2d1_clk.c, "msm_iommu.11"), + CLK_LOOKUP("dfab_dsps_clk", dfab_dsps_clk.c, NULL), + CLK_LOOKUP("dfab_usb_hs_clk", dfab_usb_hs_clk.c, NULL), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc5_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("dfab_clk", dfab_sps_clk.c, NULL /* sps */), + + CLK_LOOKUP("ebi1_msmbus_clk", ebi1_msmbus_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_adm_clk.c, "msm_dmov"), +}; + +/* + * Miscellaneous clock register initializations + */ + +/* Read, modify, then write-back a register. */ +static void __init rmwreg(uint32_t val, void *reg, uint32_t mask) +{ + uint32_t regval = readl_relaxed(reg); + regval &= ~mask; + regval |= val; + writel_relaxed(regval, reg); +} + +static void __init reg_init(void) +{ + /* TODO: Remove once LPASS starts voting */ + u32 reg; + reg = readl_relaxed(BB_PLL_ENA_Q6_SW_REG); + reg |= BIT(4); + writel_relaxed(reg, BB_PLL_ENA_Q6_SW_REG); + + /* Setup LPASS toplevel muxes */ + writel_relaxed(0x15, LPASS_XO_SRC_CLK_CTL_REG); /* Select PXO */ + writel_relaxed(0x1, LCC_PXO_SRC_CLK_CTL_REG); /* Select PXO */ + writel_relaxed(0x1, LCC_PRI_PLL_CLK_CTL_REG); /* Select PLL4 */ + + /* Deassert MM SW_RESET_ALL signal. */ + writel_relaxed(0, SW_RESET_ALL_REG); + + /* Initialize MM AHB registers: Enable the FPB clock and disable HW + * gating for all clocks. Also set VFE_AHB's FORCE_CORE_ON bit to + * prevent its memory from being collapsed when the clock is halted. + * The sleep and wake-up delays are set to safe values. */ + rmwreg(0x00000003, AHB_EN_REG, 0x0F7FFFFF); + rmwreg(0x000007F9, AHB_EN2_REG, 0xFFFFBFFF); + + /* Deassert all locally-owned MM AHB resets. */ + rmwreg(0, SW_RESET_AHB_REG, 0xFFF7DFFF); + + /* Initialize MM AXI registers: Enable HW gating for all clocks that + * support it. Also set FORCE_CORE_ON bits, and any sleep and wake-up + * delays to safe values. */ + /* TODO: Enable HW Gating */ + rmwreg(0x000007F9, MAXI_EN_REG, 0x0FFFFFFF); + rmwreg(0x1027FCFF, MAXI_EN2_REG, 0x1FFFFFFF); + writel_relaxed(0x0027FCFF, MAXI_EN3_REG); + writel_relaxed(0x0027FCFF, MAXI_EN4_REG); + writel_relaxed(0x000003C7, SAXI_EN_REG); + + /* Initialize MM CC registers: Set MM FORCE_CORE_ON bits so that core + * memories retain state even when not clocked. Also, set sleep and + * wake-up delays to safe values. */ + writel_relaxed(0x00000000, CSI0_CC_REG); + writel_relaxed(0x00000000, CSI1_CC_REG); + rmwreg(0x80FF0000, DSI1_BYTE_CC_REG, BM(31, 29) | BM(23, 16)); + rmwreg(0x80FF0000, DSI2_BYTE_CC_REG, BM(31, 29) | BM(23, 16)); + rmwreg(0x80FF0000, DSI_PIXEL_CC_REG, BM(31, 29) | BM(23, 16)); + rmwreg(0x80FF0000, DSI2_PIXEL_CC_REG, BM(31, 29) | BM(23, 16)); + writel_relaxed(0x80FF0000, GFX2D0_CC_REG); + writel_relaxed(0x80FF0000, GFX2D1_CC_REG); + writel_relaxed(0x80FF0000, GFX3D_CC_REG); + writel_relaxed(0x80FF0000, IJPEG_CC_REG); + writel_relaxed(0x80FF0000, JPEGD_CC_REG); + /* MDP clocks may be running at boot, don't turn them off. */ + rmwreg(0x80FF0000, MDP_CC_REG, BM(31, 29) | BM(23, 16)); + rmwreg(0x80FF0000, MDP_LUT_CC_REG, BM(31, 29) | BM(23, 16)); + writel_relaxed(0x80FF0000, ROT_CC_REG); + writel_relaxed(0x80FF0000, TV_CC_REG); + writel_relaxed(0x000004FF, TV_CC2_REG); + writel_relaxed(0xC0FF0000, VCODEC_CC_REG); + writel_relaxed(0x80FF0000, VFE_CC_REG); + writel_relaxed(0x80FF0000, VPE_CC_REG); + + /* De-assert MM AXI resets to all hardware blocks. */ + writel_relaxed(0, SW_RESET_AXI_REG); + + /* Deassert all MM core resets. */ + writel_relaxed(0, SW_RESET_CORE_REG); + + /* Reset 3D core once more, with its clock enabled. This can + * eventually be done as part of the GDFS footswitch driver. */ + clk_set_rate(&gfx3d_clk.c, 27000000); + clk_enable(&gfx3d_clk.c); + writel_relaxed(BIT(12), SW_RESET_CORE_REG); + mb(); + udelay(5); + writel_relaxed(0, SW_RESET_CORE_REG); + /* Make sure reset is de-asserted before clock is disabled. */ + mb(); + clk_disable(&gfx3d_clk.c); + + /* Enable TSSC and PDM PXO sources. */ + writel_relaxed(BIT(11), TSSC_CLK_CTL_REG); + writel_relaxed(BIT(15), PDM_CLK_NS_REG); + + /* Source SLIMBus xo src from slimbus reference clock */ + writel_relaxed(0x3, SLIMBUS_XO_SRC_CLK_CTL_REG); + + /* Source the dsi_byte_clks from the DSI PHY PLLs */ + rmwreg(0x1, DSI1_BYTE_NS_REG, 0x7); + rmwreg(0x2, DSI2_BYTE_NS_REG, 0x7); +} + +static int wr_pll_clk_enable(struct clk *clk) +{ + u32 mode; + unsigned long flags; + struct pll_clk *pll = to_pll_clk(clk); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + mode = readl_relaxed(pll->mode_reg); + /* De-assert active-low PLL reset. */ + mode |= BIT(2); + writel_relaxed(mode, pll->mode_reg); + + /* + * H/W requires a 5us delay between disabling the bypass and + * de-asserting the reset. Delay 10us just to be safe. + */ + mb(); + udelay(10); + + /* Disable PLL bypass mode. */ + mode |= BIT(1); + writel_relaxed(mode, pll->mode_reg); + + /* Wait until PLL is locked. */ + mb(); + udelay(60); + + /* Enable PLL output. */ + mode |= BIT(0); + writel_relaxed(mode, pll->mode_reg); + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + return 0; +} + +void __init msm8960_clock_init_dummy(void) +{ + soc_update_sys_vdd = msm8960_update_sys_vdd; + local_vote_sys_vdd(HIGH); + msm_clock_init(msm_clocks_8960_dummy, msm_num_clocks_8960_dummy); +} + +/* Local clock driver initialization. */ +void __init msm8960_clock_init(void) +{ + xo_pxo = msm_xo_get(MSM_XO_PXO, "clock-8960"); + if (IS_ERR(xo_pxo)) { + pr_err("%s: msm_xo_get(PXO) failed.\n", __func__); + BUG(); + } + xo_cxo = msm_xo_get(MSM_XO_TCXO_D0, "clock-8960"); + if (IS_ERR(xo_cxo)) { + pr_err("%s: msm_xo_get(CXO) failed.\n", __func__); + BUG(); + } + + soc_update_sys_vdd = msm8960_update_sys_vdd; + local_vote_sys_vdd(HIGH); + + clk_ops_pll.enable = wr_pll_clk_enable; + + /* Initialize clock registers. */ + reg_init(); + + /* Initialize rates for clocks that only support one. */ + clk_set_rate(&pdm_clk.c, 27000000); + clk_set_rate(&prng_clk.c, 64000000); + clk_set_rate(&mdp_vsync_clk.c, 27000000); + clk_set_rate(&tsif_ref_clk.c, 105000); + clk_set_rate(&tssc_clk.c, 27000000); + clk_set_rate(&usb_hs1_xcvr_clk.c, 60000000); + clk_set_rate(&usb_fs1_src_clk.c, 60000000); + clk_set_rate(&usb_fs2_src_clk.c, 60000000); + + /* + * The halt status bits for PDM and TSSC may be incorrect at boot. + * Toggle these clocks on and off to refresh them. + */ + local_clk_enable(&pdm_clk.c); + local_clk_disable(&pdm_clk.c); + local_clk_enable(&tssc_clk.c); + local_clk_disable(&tssc_clk.c); + + if (machine_is_msm8960_sim()) { + clk_set_rate(&sdc1_clk.c, 48000000); + clk_enable(&sdc1_clk.c); + clk_enable(&sdc1_p_clk.c); + clk_set_rate(&sdc3_clk.c, 48000000); + clk_enable(&sdc3_clk.c); + clk_enable(&sdc3_p_clk.c); + } + + msm_clock_init(msm_clocks_8960, ARRAY_SIZE(msm_clocks_8960)); +} + +static int __init msm_clk_soc_late_init(void) +{ + return local_unvote_sys_vdd(HIGH); +} +late_initcall(msm_clk_soc_late_init); diff --git a/arch/arm/mach-msm/clock-8x60.c b/arch/arm/mach-msm/clock-8x60.c new file mode 100644 index 00000000000..f51cf88a0c3 --- /dev/null +++ b/arch/arm/mach-msm/clock-8x60.c @@ -0,0 +1,3822 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "clock-local.h" +#include "clock-rpm.h" +#include "clock-voter.h" + +#ifdef CONFIG_MSM_SECURE_IO +#undef readl_relaxed +#undef writel_relaxed +#define readl_relaxed secure_readl +#define writel_relaxed secure_writel +#endif + +#define REG(off) (MSM_CLK_CTL_BASE + (off)) +#define REG_MM(off) (MSM_MMSS_CLK_CTL_BASE + (off)) +#define REG_LPA(off) (MSM_LPASS_CLK_CTL_BASE + (off)) + +/* Peripheral clock registers. */ +#define CE2_HCLK_CTL_REG REG(0x2740) +#define CLK_HALT_CFPB_STATEA_REG REG(0x2FCC) +#define CLK_HALT_CFPB_STATEB_REG REG(0x2FD0) +#define CLK_HALT_CFPB_STATEC_REG REG(0x2FD4) +#define CLK_HALT_DFAB_STATE_REG REG(0x2FC8) +#define CLK_HALT_MSS_SMPSS_MISC_STATE_REG REG(0x2FDC) +#define CLK_HALT_SFPB_MISC_STATE_REG REG(0x2FD8) +#define CLK_TEST_REG REG(0x2FA0) +#define GSBIn_HCLK_CTL_REG(n) REG(0x29C0+(0x20*((n)-1))) +#define GSBIn_QUP_APPS_MD_REG(n) REG(0x29C8+(0x20*((n)-1))) +#define GSBIn_QUP_APPS_NS_REG(n) REG(0x29CC+(0x20*((n)-1))) +#define GSBIn_RESET_REG(n) REG(0x29DC+(0x20*((n)-1))) +#define GSBIn_UART_APPS_MD_REG(n) REG(0x29D0+(0x20*((n)-1))) +#define GSBIn_UART_APPS_NS_REG(n) REG(0x29D4+(0x20*((n)-1))) +#define PDM_CLK_NS_REG REG(0x2CC0) +#define BB_PLL_ENA_SC0_REG REG(0x34C0) +#define BB_PLL0_STATUS_REG REG(0x30D8) +#define BB_PLL6_STATUS_REG REG(0x3118) +#define BB_PLL8_L_VAL_REG REG(0x3144) +#define BB_PLL8_M_VAL_REG REG(0x3148) +#define BB_PLL8_MODE_REG REG(0x3140) +#define BB_PLL8_N_VAL_REG REG(0x314C) +#define BB_PLL8_STATUS_REG REG(0x3158) +#define PLLTEST_PAD_CFG_REG REG(0x2FA4) +#define PMEM_ACLK_CTL_REG REG(0x25A0) +#define PPSS_HCLK_CTL_REG REG(0x2580) +#define RINGOSC_NS_REG REG(0x2DC0) +#define RINGOSC_STATUS_REG REG(0x2DCC) +#define RINGOSC_TCXO_CTL_REG REG(0x2DC4) +#define SC0_U_CLK_BRANCH_ENA_VOTE_REG REG(0x3080) +#define SC1_U_CLK_BRANCH_ENA_VOTE_REG REG(0x30A0) +#define SC0_U_CLK_SLEEP_ENA_VOTE_REG REG(0x3084) +#define SC1_U_CLK_SLEEP_ENA_VOTE_REG REG(0x30A4) +#define SDCn_APPS_CLK_MD_REG(n) REG(0x2828+(0x20*((n)-1))) +#define SDCn_APPS_CLK_NS_REG(n) REG(0x282C+(0x20*((n)-1))) +#define SDCn_HCLK_CTL_REG(n) REG(0x2820+(0x20*((n)-1))) +#define SDCn_RESET_REG(n) REG(0x2830+(0x20*((n)-1))) +#define TSIF_HCLK_CTL_REG REG(0x2700) +#define TSIF_REF_CLK_MD_REG REG(0x270C) +#define TSIF_REF_CLK_NS_REG REG(0x2710) +#define TSSC_CLK_CTL_REG REG(0x2CA0) +#define USB_FSn_HCLK_CTL_REG(n) REG(0x2960+(0x20*((n)-1))) +#define USB_FSn_RESET_REG(n) REG(0x2974+(0x20*((n)-1))) +#define USB_FSn_SYSTEM_CLK_CTL_REG(n) REG(0x296C+(0x20*((n)-1))) +#define USB_FSn_XCVR_FS_CLK_MD_REG(n) REG(0x2964+(0x20*((n)-1))) +#define USB_FSn_XCVR_FS_CLK_NS_REG(n) REG(0x2968+(0x20*((n)-1))) +#define USB_HS1_HCLK_CTL_REG REG(0x2900) +#define USB_HS1_RESET_REG REG(0x2910) +#define USB_HS1_XCVR_FS_CLK_MD_REG REG(0x2908) +#define USB_HS1_XCVR_FS_CLK_NS_REG REG(0x290C) +#define USB_PHY0_RESET_REG REG(0x2E20) + +/* Multimedia clock registers. */ +#define AHB_EN_REG REG_MM(0x0008) +#define AHB_EN2_REG REG_MM(0x0038) +#define AHB_NS_REG REG_MM(0x0004) +#define AXI_NS_REG REG_MM(0x0014) +#define CAMCLK_CC_REG REG_MM(0x0140) +#define CAMCLK_MD_REG REG_MM(0x0144) +#define CAMCLK_NS_REG REG_MM(0x0148) +#define CSI_CC_REG REG_MM(0x0040) +#define CSI_NS_REG REG_MM(0x0048) +#define DBG_BUS_VEC_A_REG REG_MM(0x01C8) +#define DBG_BUS_VEC_B_REG REG_MM(0x01CC) +#define DBG_BUS_VEC_C_REG REG_MM(0x01D0) +#define DBG_BUS_VEC_D_REG REG_MM(0x01D4) +#define DBG_BUS_VEC_E_REG REG_MM(0x01D8) +#define DBG_BUS_VEC_F_REG REG_MM(0x01DC) +#define DBG_BUS_VEC_H_REG REG_MM(0x01E4) +#define DBG_CFG_REG_HS_REG REG_MM(0x01B4) +#define DBG_CFG_REG_LS_REG REG_MM(0x01B8) +#define GFX2D0_CC_REG REG_MM(0x0060) +#define GFX2D0_MD0_REG REG_MM(0x0064) +#define GFX2D0_MD1_REG REG_MM(0x0068) +#define GFX2D0_NS_REG REG_MM(0x0070) +#define GFX2D1_CC_REG REG_MM(0x0074) +#define GFX2D1_MD0_REG REG_MM(0x0078) +#define GFX2D1_MD1_REG REG_MM(0x006C) +#define GFX2D1_NS_REG REG_MM(0x007C) +#define GFX3D_CC_REG REG_MM(0x0080) +#define GFX3D_MD0_REG REG_MM(0x0084) +#define GFX3D_MD1_REG REG_MM(0x0088) +#define GFX3D_NS_REG REG_MM(0x008C) +#define IJPEG_CC_REG REG_MM(0x0098) +#define IJPEG_MD_REG REG_MM(0x009C) +#define IJPEG_NS_REG REG_MM(0x00A0) +#define JPEGD_CC_REG REG_MM(0x00A4) +#define JPEGD_NS_REG REG_MM(0x00AC) +#define MAXI_EN_REG REG_MM(0x0018) +#define MAXI_EN3_REG REG_MM(0x002C) +#define MDP_CC_REG REG_MM(0x00C0) +#define MDP_MD0_REG REG_MM(0x00C4) +#define MDP_MD1_REG REG_MM(0x00C8) +#define MDP_NS_REG REG_MM(0x00D0) +#define MISC_CC_REG REG_MM(0x0058) +#define MISC_CC2_REG REG_MM(0x005C) +#define PIXEL_CC_REG REG_MM(0x00D4) +#define PIXEL_CC2_REG REG_MM(0x0120) +#define PIXEL_MD_REG REG_MM(0x00D8) +#define PIXEL_NS_REG REG_MM(0x00DC) +#define MM_PLL0_MODE_REG REG_MM(0x0300) +#define MM_PLL1_MODE_REG REG_MM(0x031C) +#define MM_PLL2_CONFIG_REG REG_MM(0x0348) +#define MM_PLL2_L_VAL_REG REG_MM(0x033C) +#define MM_PLL2_M_VAL_REG REG_MM(0x0340) +#define MM_PLL2_MODE_REG REG_MM(0x0338) +#define MM_PLL2_N_VAL_REG REG_MM(0x0344) +#define ROT_CC_REG REG_MM(0x00E0) +#define ROT_NS_REG REG_MM(0x00E8) +#define SAXI_EN_REG REG_MM(0x0030) +#define SW_RESET_AHB_REG REG_MM(0x020C) +#define SW_RESET_ALL_REG REG_MM(0x0204) +#define SW_RESET_AXI_REG REG_MM(0x0208) +#define SW_RESET_CORE_REG REG_MM(0x0210) +#define TV_CC_REG REG_MM(0x00EC) +#define TV_CC2_REG REG_MM(0x0124) +#define TV_MD_REG REG_MM(0x00F0) +#define TV_NS_REG REG_MM(0x00F4) +#define VCODEC_CC_REG REG_MM(0x00F8) +#define VCODEC_MD0_REG REG_MM(0x00FC) +#define VCODEC_MD1_REG REG_MM(0x0128) +#define VCODEC_NS_REG REG_MM(0x0100) +#define VFE_CC_REG REG_MM(0x0104) +#define VFE_MD_REG REG_MM(0x0108) +#define VFE_NS_REG REG_MM(0x010C) +#define VPE_CC_REG REG_MM(0x0110) +#define VPE_NS_REG REG_MM(0x0118) + +/* Low-power Audio clock registers. */ +#define LCC_CLK_LS_DEBUG_CFG_REG REG_LPA(0x00A8) +#define LCC_CODEC_I2S_MIC_MD_REG REG_LPA(0x0064) +#define LCC_CODEC_I2S_MIC_NS_REG REG_LPA(0x0060) +#define LCC_CODEC_I2S_MIC_STATUS_REG REG_LPA(0x0068) +#define LCC_CODEC_I2S_SPKR_MD_REG REG_LPA(0x0070) +#define LCC_CODEC_I2S_SPKR_NS_REG REG_LPA(0x006C) +#define LCC_CODEC_I2S_SPKR_STATUS_REG REG_LPA(0x0074) +#define LCC_MI2S_MD_REG REG_LPA(0x004C) +#define LCC_MI2S_NS_REG REG_LPA(0x0048) +#define LCC_MI2S_STATUS_REG REG_LPA(0x0050) +#define LCC_PCM_MD_REG REG_LPA(0x0058) +#define LCC_PCM_NS_REG REG_LPA(0x0054) +#define LCC_PCM_STATUS_REG REG_LPA(0x005C) +#define LCC_PLL0_CONFIG_REG REG_LPA(0x0014) +#define LCC_PLL0_L_VAL_REG REG_LPA(0x0004) +#define LCC_PLL0_M_VAL_REG REG_LPA(0x0008) +#define LCC_PLL0_MODE_REG REG_LPA(0x0000) +#define LCC_PLL0_N_VAL_REG REG_LPA(0x000C) +#define LCC_PRI_PLL_CLK_CTL_REG REG_LPA(0x00C4) +#define LCC_SPARE_I2S_MIC_MD_REG REG_LPA(0x007C) +#define LCC_SPARE_I2S_MIC_NS_REG REG_LPA(0x0078) +#define LCC_SPARE_I2S_MIC_STATUS_REG REG_LPA(0x0080) +#define LCC_SPARE_I2S_SPKR_MD_REG REG_LPA(0x0088) +#define LCC_SPARE_I2S_SPKR_NS_REG REG_LPA(0x0084) +#define LCC_SPARE_I2S_SPKR_STATUS_REG REG_LPA(0x008C) + +/* MUX source input identifiers. */ +#define pxo_to_bb_mux 0 +#define mxo_to_bb_mux 1 +#define cxo_to_bb_mux pxo_to_bb_mux +#define pll0_to_bb_mux 2 +#define pll8_to_bb_mux 3 +#define pll6_to_bb_mux 4 +#define gnd_to_bb_mux 6 +#define pxo_to_mm_mux 0 +#define pll1_to_mm_mux 1 /* or MMSS_PLL0 */ +#define pll2_to_mm_mux 1 /* or MMSS_PLL1 */ +#define pll3_to_mm_mux 3 /* or MMSS_PLL2 */ +#define pll8_to_mm_mux 2 /* or MMSS_GPERF */ +#define pll0_to_mm_mux 3 /* or MMSS_GPLL0 */ +#define mxo_to_mm_mux 4 +#define gnd_to_mm_mux 6 +#define cxo_to_xo_mux 0 +#define pxo_to_xo_mux 1 +#define mxo_to_xo_mux 2 +#define gnd_to_xo_mux 3 +#define pxo_to_lpa_mux 0 +#define cxo_to_lpa_mux 1 +#define pll4_to_lpa_mux 2 /* or LPA_PLL0 */ +#define gnd_to_lpa_mux 6 + +/* Test Vector Macros */ +#define TEST_TYPE_PER_LS 1 +#define TEST_TYPE_PER_HS 2 +#define TEST_TYPE_MM_LS 3 +#define TEST_TYPE_MM_HS 4 +#define TEST_TYPE_LPA 5 +#define TEST_TYPE_SC 6 +#define TEST_TYPE_MM_HS2X 7 +#define TEST_TYPE_SHIFT 24 +#define TEST_CLK_SEL_MASK BM(23, 0) +#define TEST_VECTOR(s, t) (((t) << TEST_TYPE_SHIFT) | BVAL(23, 0, (s))) +#define TEST_PER_LS(s) TEST_VECTOR((s), TEST_TYPE_PER_LS) +#define TEST_PER_HS(s) TEST_VECTOR((s), TEST_TYPE_PER_HS) +#define TEST_MM_LS(s) TEST_VECTOR((s), TEST_TYPE_MM_LS) +#define TEST_MM_HS(s) TEST_VECTOR((s), TEST_TYPE_MM_HS) +#define TEST_LPA(s) TEST_VECTOR((s), TEST_TYPE_LPA) +#define TEST_SC(s) TEST_VECTOR((s), TEST_TYPE_SC) +#define TEST_MM_HS2X(s) TEST_VECTOR((s), TEST_TYPE_MM_HS2X) + +struct pll_rate { + const uint32_t l_val; + const uint32_t m_val; + const uint32_t n_val; + const uint32_t vco; + const uint32_t post_div; + const uint32_t i_bits; +}; +#define PLL_RATE(l, m, n, v, d, i) { l, m, n, v, (d>>1), i } +/* + * Clock frequency definitions and macros + */ +#define MN_MODE_DUAL_EDGE 0x2 + +/* MD Registers */ +#define MD4(m_lsb, m, n_lsb, n) \ + (BVAL((m_lsb+3), m_lsb, m) | BVAL((n_lsb+3), n_lsb, ~(n))) +#define MD8(m_lsb, m, n_lsb, n) \ + (BVAL((m_lsb+7), m_lsb, m) | BVAL((n_lsb+7), n_lsb, ~(n))) +#define MD16(m, n) (BVAL(31, 16, m) | BVAL(15, 0, ~(n))) + +/* NS Registers */ +#define NS(n_msb, n_lsb, n, m, mde_lsb, d_msb, d_lsb, d, s_msb, s_lsb, s) \ + (BVAL(n_msb, n_lsb, ~(n-m)) \ + | (BVAL((mde_lsb+1), mde_lsb, MN_MODE_DUAL_EDGE) * !!(n)) \ + | BVAL(d_msb, d_lsb, (d-1)) | BVAL(s_msb, s_lsb, s)) + +#define NS_MM(n_msb, n_lsb, n, m, d_msb, d_lsb, d, s_msb, s_lsb, s) \ + (BVAL(n_msb, n_lsb, ~(n-m)) | BVAL(d_msb, d_lsb, (d-1)) \ + | BVAL(s_msb, s_lsb, s)) + +#define NS_DIVSRC(d_msb , d_lsb, d, s_msb, s_lsb, s) \ + (BVAL(d_msb, d_lsb, (d-1)) | BVAL(s_msb, s_lsb, s)) + +#define NS_DIV(d_msb , d_lsb, d) \ + BVAL(d_msb, d_lsb, (d-1)) + +#define NS_SRC_SEL(s_msb, s_lsb, s) \ + BVAL(s_msb, s_lsb, s) + +#define NS_MND_BANKED4(n0_lsb, n1_lsb, n, m, s0_lsb, s1_lsb, s) \ + (BVAL((n0_lsb+3), n0_lsb, ~(n-m)) \ + | BVAL((n1_lsb+3), n1_lsb, ~(n-m)) \ + | BVAL((s0_lsb+2), s0_lsb, s) \ + | BVAL((s1_lsb+2), s1_lsb, s)) + +#define NS_MND_BANKED8(n0_lsb, n1_lsb, n, m, s0_lsb, s1_lsb, s) \ + (BVAL((n0_lsb+7), n0_lsb, ~(n-m)) \ + | BVAL((n1_lsb+7), n1_lsb, ~(n-m)) \ + | BVAL((s0_lsb+2), s0_lsb, s) \ + | BVAL((s1_lsb+2), s1_lsb, s)) + +#define NS_DIVSRC_BANKED(d0_msb, d0_lsb, d1_msb, d1_lsb, d, \ + s0_msb, s0_lsb, s1_msb, s1_lsb, s) \ + (BVAL(d0_msb, d0_lsb, (d-1)) | BVAL(d1_msb, d1_lsb, (d-1)) \ + | BVAL(s0_msb, s0_lsb, s) \ + | BVAL(s1_msb, s1_lsb, s)) + +/* CC Registers */ +#define CC(mde_lsb, n) (BVAL((mde_lsb+1), mde_lsb, MN_MODE_DUAL_EDGE) * !!(n)) +#define CC_BANKED(mde0_lsb, mde1_lsb, n) \ + ((BVAL((mde0_lsb+1), mde0_lsb, MN_MODE_DUAL_EDGE) \ + | BVAL((mde1_lsb+1), mde1_lsb, MN_MODE_DUAL_EDGE)) \ + * !!(n)) + +static struct msm_xo_voter *xo_pxo, *xo_cxo; + +static bool xo_clk_is_local(struct clk *clk) +{ + return false; +} + +static int pxo_clk_enable(struct clk *clk) +{ + return msm_xo_mode_vote(xo_pxo, MSM_XO_MODE_ON); +} + +static void pxo_clk_disable(struct clk *clk) +{ + msm_xo_mode_vote(xo_pxo, MSM_XO_MODE_OFF); +} + +static struct clk_ops clk_ops_pxo = { + .enable = pxo_clk_enable, + .disable = pxo_clk_disable, + .get_rate = fixed_clk_get_rate, + .is_local = xo_clk_is_local, +}; + +static struct fixed_clk pxo_clk = { + .rate = 27000000, + .c = { + .dbg_name = "pxo_clk", + .ops = &clk_ops_pxo, + CLK_INIT(pxo_clk.c), + }, +}; + +static int cxo_clk_enable(struct clk *clk) +{ + return msm_xo_mode_vote(xo_cxo, MSM_XO_MODE_ON); +} + +static void cxo_clk_disable(struct clk *clk) +{ + msm_xo_mode_vote(xo_cxo, MSM_XO_MODE_OFF); +} + +static struct clk_ops clk_ops_cxo = { + .enable = cxo_clk_enable, + .disable = cxo_clk_disable, + .get_rate = fixed_clk_get_rate, + .is_local = xo_clk_is_local, +}; + +static struct fixed_clk cxo_clk = { + .rate = 19200000, + .c = { + .dbg_name = "cxo_clk", + .ops = &clk_ops_cxo, + CLK_INIT(cxo_clk.c), + }, +}; + +static struct pll_vote_clk pll8_clk = { + .rate = 384000000, + .en_reg = BB_PLL_ENA_SC0_REG, + .en_mask = BIT(8), + .status_reg = BB_PLL8_STATUS_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll8_clk", + .ops = &clk_ops_pll_vote, + CLK_INIT(pll8_clk.c), + }, +}; + +static struct pll_clk pll2_clk = { + .rate = 800000000, + .mode_reg = MM_PLL1_MODE_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll2_clk", + .ops = &clk_ops_pll, + CLK_INIT(pll2_clk.c), + }, +}; + +static struct pll_clk pll3_clk = { + .rate = 0, /* TODO: Detect rate dynamically */ + .mode_reg = MM_PLL2_MODE_REG, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "pll3_clk", + .ops = &clk_ops_pll, + CLK_INIT(pll3_clk.c), + }, +}; + +static int pll4_clk_enable(struct clk *clk) +{ + struct msm_rpm_iv_pair iv = { MSM_RPM_ID_PLL_4, 1 }; + return msm_rpm_set_noirq(MSM_RPM_CTX_SET_0, &iv, 1); +} + +static void pll4_clk_disable(struct clk *clk) +{ + struct msm_rpm_iv_pair iv = { MSM_RPM_ID_PLL_4, 0 }; + msm_rpm_set_noirq(MSM_RPM_CTX_SET_0, &iv, 1); +} + +static struct clk *pll4_clk_get_parent(struct clk *clk) +{ + return &pxo_clk.c; +} + +static bool pll4_clk_is_local(struct clk *clk) +{ + return false; +} + +static struct clk_ops clk_ops_pll4 = { + .enable = pll4_clk_enable, + .disable = pll4_clk_disable, + .get_rate = fixed_clk_get_rate, + .get_parent = pll4_clk_get_parent, + .is_local = pll4_clk_is_local, +}; + +static struct fixed_clk pll4_clk = { + .rate = 540672000, + .c = { + .dbg_name = "pll4_clk", + .ops = &clk_ops_pll4, + CLK_INIT(pll4_clk.c), + }, +}; + +/* + * SoC-specific Set-Rate Functions + */ + +/* Unlike other clocks, the TV rate is adjusted through PLL + * re-programming. It is also routed through an MND divider. */ +static void set_rate_tv(struct rcg_clk *clk, struct clk_freq_tbl *nf) +{ + struct pll_rate *rate = nf->extra_freq_data; + uint32_t pll_mode, pll_config, misc_cc2; + + /* Disable PLL output. */ + pll_mode = readl_relaxed(MM_PLL2_MODE_REG); + pll_mode &= ~BIT(0); + writel_relaxed(pll_mode, MM_PLL2_MODE_REG); + + /* Assert active-low PLL reset. */ + pll_mode &= ~BIT(2); + writel_relaxed(pll_mode, MM_PLL2_MODE_REG); + + /* Program L, M and N values. */ + writel_relaxed(rate->l_val, MM_PLL2_L_VAL_REG); + writel_relaxed(rate->m_val, MM_PLL2_M_VAL_REG); + writel_relaxed(rate->n_val, MM_PLL2_N_VAL_REG); + + /* Configure MN counter, post-divide, VCO, and i-bits. */ + pll_config = readl_relaxed(MM_PLL2_CONFIG_REG); + pll_config &= ~(BM(22, 20) | BM(18, 0)); + pll_config |= rate->n_val ? BIT(22) : 0; + pll_config |= BVAL(21, 20, rate->post_div); + pll_config |= BVAL(17, 16, rate->vco); + pll_config |= rate->i_bits; + writel_relaxed(pll_config, MM_PLL2_CONFIG_REG); + + /* Configure MND. */ + set_rate_mnd(clk, nf); + + /* Configure hdmi_ref_clk to be equal to the TV clock rate. */ + misc_cc2 = readl_relaxed(MISC_CC2_REG); + misc_cc2 &= ~(BIT(28)|BM(21, 18)); + misc_cc2 |= (BIT(28)|BVAL(21, 18, (nf->ns_val >> 14) & 0x3)); + writel_relaxed(misc_cc2, MISC_CC2_REG); + + /* De-assert active-low PLL reset. */ + pll_mode |= BIT(2); + writel_relaxed(pll_mode, MM_PLL2_MODE_REG); + + /* Enable PLL output. */ + pll_mode |= BIT(0); + writel_relaxed(pll_mode, MM_PLL2_MODE_REG); +} + +/* + * SoC-specific functions required by clock-local driver + */ + +/* Update the sys_vdd voltage given a level. */ +static int msm8660_update_sys_vdd(enum sys_vdd_level level) +{ + static const int vdd_uv[] = { + [NONE] = 500000, + [LOW] = 1000000, + [NOMINAL] = 1100000, + [HIGH] = 1200000, + }; + + return rpm_vreg_set_voltage(RPM_VREG_ID_PM8058_S1, RPM_VREG_VOTER3, + vdd_uv[level], vdd_uv[HIGH], 1); +} + +static int soc_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + return branch_reset(&to_rcg_clk(clk)->b, action); +} + +static struct clk_ops soc_clk_ops_8x60 = { + .enable = local_clk_enable, + .disable = local_clk_disable, + .auto_off = local_clk_auto_off, + .set_rate = local_clk_set_rate, + .set_min_rate = local_clk_set_min_rate, + .set_max_rate = local_clk_set_max_rate, + .get_rate = local_clk_get_rate, + .list_rate = local_clk_list_rate, + .is_enabled = local_clk_is_enabled, + .round_rate = local_clk_round_rate, + .reset = soc_clk_reset, + .is_local = local_clk_is_local, + .get_parent = local_clk_get_parent, +}; + +static struct clk_ops clk_ops_branch = { + .enable = branch_clk_enable, + .disable = branch_clk_disable, + .auto_off = branch_clk_auto_off, + .is_enabled = branch_clk_is_enabled, + .reset = branch_clk_reset, + .is_local = local_clk_is_local, + .get_parent = branch_clk_get_parent, + .set_parent = branch_clk_set_parent, +}; + +static struct clk_ops clk_ops_reset = { + .reset = branch_clk_reset, + .is_local = local_clk_is_local, +}; + +/* + * Clock Descriptions + */ + +/* AXI Interfaces */ +static struct branch_clk gmem_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(24), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 6, + }, + .c = { + .dbg_name = "gmem_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(gmem_axi_clk.c), + }, +}; + +static struct branch_clk ijpeg_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(21), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(14), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 4, + }, + .c = { + .dbg_name = "ijpeg_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(ijpeg_axi_clk.c), + }, +}; + +static struct branch_clk imem_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(22), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(10), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "imem_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(imem_axi_clk.c), + }, +}; + +static struct branch_clk jpegd_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(25), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 5, + }, + .c = { + .dbg_name = "jpegd_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(jpegd_axi_clk.c), + }, +}; + +static struct branch_clk mdp_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(23), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(13), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 8, + }, + .c = { + .dbg_name = "mdp_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdp_axi_clk.c), + }, +}; + +static struct branch_clk vcodec_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(19), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(4)|BIT(5), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 3, + }, + .c = { + .dbg_name = "vcodec_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcodec_axi_clk.c), + }, +}; + +static struct branch_clk vfe_axi_clk = { + .b = { + .ctl_reg = MAXI_EN_REG, + .en_mask = BIT(18), + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(9), + .halt_reg = DBG_BUS_VEC_E_REG, + .halt_bit = 0, + }, + .c = { + .dbg_name = "vfe_axi_clk", + .ops = &clk_ops_branch, + CLK_INIT(vfe_axi_clk.c), + }, +}; + +static struct branch_clk rot_axi_clk = { + .b = { + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(6), + }, + .c = { + .dbg_name = "rot_axi_clk", + .ops = &clk_ops_reset, + CLK_INIT(rot_axi_clk.c), + }, +}; + +static struct branch_clk vpe_axi_clk = { + .b = { + .reset_reg = SW_RESET_AXI_REG, + .reset_mask = BIT(15), + }, + .c = { + .dbg_name = "vpe_axi_clk", + .ops = &clk_ops_reset, + CLK_INIT(vpe_axi_clk.c), + }, +}; + +/* AHB Interfaces */ +static struct branch_clk amp_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(24), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 18, + }, + .c = { + .dbg_name = "amp_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(amp_p_clk.c), + }, +}; + +static struct branch_clk csi0_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(7), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(17), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 16, + }, + .c = { + .dbg_name = "csi0_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0_p_clk.c), + }, +}; + +static struct branch_clk csi1_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(20), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(16), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 17, + }, + .c = { + .dbg_name = "csi1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi1_p_clk.c), + }, +}; + +static struct branch_clk dsi_m_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(9), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(6), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 19, + }, + .c = { + .dbg_name = "dsi_m_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(dsi_m_p_clk.c), + }, +}; + +static struct branch_clk dsi_s_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(18), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(5), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 20, + }, + .c = { + .dbg_name = "dsi_s_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(dsi_s_p_clk.c), + }, +}; + +static struct branch_clk gfx2d0_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(19), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(12), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 2, + }, + .c = { + .dbg_name = "gfx2d0_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gfx2d0_p_clk.c), + }, +}; + +static struct branch_clk gfx2d1_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(2), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(11), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 3, + }, + .c = { + .dbg_name = "gfx2d1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gfx2d1_p_clk.c), + }, +}; + +static struct branch_clk gfx3d_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(3), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(10), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 4, + }, + .c = { + .dbg_name = "gfx3d_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gfx3d_p_clk.c), + }, +}; + +static struct branch_clk hdmi_m_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(14), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(9), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 5, + }, + .c = { + .dbg_name = "hdmi_m_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(hdmi_m_p_clk.c), + }, +}; + +static struct branch_clk hdmi_s_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(4), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(9), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 6, + }, + .c = { + .dbg_name = "hdmi_s_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(hdmi_s_p_clk.c), + }, +}; + +static struct branch_clk ijpeg_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(5), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(7), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 9, + }, + .c = { + .dbg_name = "ijpeg_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(ijpeg_p_clk.c), + }, +}; + +static struct branch_clk imem_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(6), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(8), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 10, + }, + .c = { + .dbg_name = "imem_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(imem_p_clk.c), + }, +}; + +static struct branch_clk jpegd_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(21), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(4), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "jpegd_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(jpegd_p_clk.c), + }, +}; + +static struct branch_clk mdp_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(10), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(3), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 11, + }, + .c = { + .dbg_name = "mdp_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdp_p_clk.c), + }, +}; + +static struct branch_clk rot_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(12), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(2), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 13, + }, + .c = { + .dbg_name = "rot_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(rot_p_clk.c), + }, +}; + +static struct branch_clk smmu_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(15), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 22, + }, + .c = { + .dbg_name = "smmu_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(smmu_p_clk.c), + }, +}; + +static struct branch_clk tv_enc_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(25), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(15), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 23, + }, + .c = { + .dbg_name = "tv_enc_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(tv_enc_p_clk.c), + }, +}; + +static struct branch_clk vcodec_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(11), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(1), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 12, + }, + .c = { + .dbg_name = "vcodec_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(vcodec_p_clk.c), + }, +}; + +static struct branch_clk vfe_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(13), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 14, + }, + .c = { + .dbg_name = "vfe_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(vfe_p_clk.c), + }, +}; + +static struct branch_clk vpe_p_clk = { + .b = { + .ctl_reg = AHB_EN_REG, + .en_mask = BIT(16), + .reset_reg = SW_RESET_AHB_REG, + .reset_mask = BIT(14), + .halt_reg = DBG_BUS_VEC_F_REG, + .halt_bit = 15, + }, + .c = { + .dbg_name = "vpe_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(vpe_p_clk.c), + }, +}; + +/* + * Peripheral Clocks + */ +#define CLK_GSBI_UART(i, n, h_r, h_b) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = GSBIn_UART_APPS_NS_REG(n), \ + .en_mask = BIT(9), \ + .reset_reg = GSBIn_RESET_REG(n), \ + .reset_mask = BIT(0), \ + .halt_reg = h_r, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = GSBIn_UART_APPS_NS_REG(n), \ + .md_reg = GSBIn_UART_APPS_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(31, 16) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_uart, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8x60, \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_UART(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD16(m, n), \ + .ns_val = NS(31, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_gsbi_uart[] = { + F_GSBI_UART( 0, gnd, 1, 0, 0, NONE), + F_GSBI_UART( 1843200, pll8, 1, 3, 625, LOW), + F_GSBI_UART( 3686400, pll8, 1, 6, 625, LOW), + F_GSBI_UART( 7372800, pll8, 1, 12, 625, LOW), + F_GSBI_UART(14745600, pll8, 1, 24, 625, LOW), + F_GSBI_UART(16000000, pll8, 4, 1, 6, LOW), + F_GSBI_UART(24000000, pll8, 4, 1, 4, LOW), + F_GSBI_UART(32000000, pll8, 4, 1, 3, LOW), + F_GSBI_UART(40000000, pll8, 1, 5, 48, NOMINAL), + F_GSBI_UART(46400000, pll8, 1, 29, 240, NOMINAL), + F_GSBI_UART(48000000, pll8, 4, 1, 2, NOMINAL), + F_GSBI_UART(51200000, pll8, 1, 2, 15, NOMINAL), + F_GSBI_UART(56000000, pll8, 1, 7, 48, NOMINAL), + F_GSBI_UART(58982400, pll8, 1, 96, 625, NOMINAL), + F_GSBI_UART(64000000, pll8, 2, 1, 3, NOMINAL), + F_END +}; + +static CLK_GSBI_UART(gsbi1_uart, 1, CLK_HALT_CFPB_STATEA_REG, 10); +static CLK_GSBI_UART(gsbi2_uart, 2, CLK_HALT_CFPB_STATEA_REG, 6); +static CLK_GSBI_UART(gsbi3_uart, 3, CLK_HALT_CFPB_STATEA_REG, 2); +static CLK_GSBI_UART(gsbi4_uart, 4, CLK_HALT_CFPB_STATEB_REG, 26); +static CLK_GSBI_UART(gsbi5_uart, 5, CLK_HALT_CFPB_STATEB_REG, 22); +static CLK_GSBI_UART(gsbi6_uart, 6, CLK_HALT_CFPB_STATEB_REG, 18); +static CLK_GSBI_UART(gsbi7_uart, 7, CLK_HALT_CFPB_STATEB_REG, 14); +static CLK_GSBI_UART(gsbi8_uart, 8, CLK_HALT_CFPB_STATEB_REG, 10); +static CLK_GSBI_UART(gsbi9_uart, 9, CLK_HALT_CFPB_STATEB_REG, 6); +static CLK_GSBI_UART(gsbi10_uart, 10, CLK_HALT_CFPB_STATEB_REG, 2); +static CLK_GSBI_UART(gsbi11_uart, 11, CLK_HALT_CFPB_STATEC_REG, 17); +static CLK_GSBI_UART(gsbi12_uart, 12, CLK_HALT_CFPB_STATEC_REG, 13); + +#define CLK_GSBI_QUP(i, n, h_r, h_b) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = GSBIn_QUP_APPS_NS_REG(n), \ + .en_mask = BIT(9), \ + .reset_reg = GSBIn_RESET_REG(n), \ + .reset_mask = BIT(0), \ + .halt_reg = h_r, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = GSBIn_QUP_APPS_NS_REG(n), \ + .md_reg = GSBIn_QUP_APPS_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_gsbi_qup, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8x60, \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_GSBI_QUP(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(16, m, 0, n), \ + .ns_val = NS(23, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_gsbi_qup[] = { + F_GSBI_QUP( 0, gnd, 1, 0, 0, NONE), + F_GSBI_QUP( 1100000, pxo, 1, 2, 49, LOW), + F_GSBI_QUP( 5400000, pxo, 1, 1, 5, LOW), + F_GSBI_QUP(10800000, pxo, 1, 2, 5, LOW), + F_GSBI_QUP(15060000, pll8, 1, 2, 51, LOW), + F_GSBI_QUP(24000000, pll8, 4, 1, 4, LOW), + F_GSBI_QUP(25600000, pll8, 1, 1, 15, NOMINAL), + F_GSBI_QUP(27000000, pxo, 1, 0, 0, NOMINAL), + F_GSBI_QUP(48000000, pll8, 4, 1, 2, NOMINAL), + F_GSBI_QUP(51200000, pll8, 1, 2, 15, NOMINAL), + F_END +}; + +static CLK_GSBI_QUP(gsbi1_qup, 1, CLK_HALT_CFPB_STATEA_REG, 9); +static CLK_GSBI_QUP(gsbi2_qup, 2, CLK_HALT_CFPB_STATEA_REG, 4); +static CLK_GSBI_QUP(gsbi3_qup, 3, CLK_HALT_CFPB_STATEA_REG, 0); +static CLK_GSBI_QUP(gsbi4_qup, 4, CLK_HALT_CFPB_STATEB_REG, 24); +static CLK_GSBI_QUP(gsbi5_qup, 5, CLK_HALT_CFPB_STATEB_REG, 20); +static CLK_GSBI_QUP(gsbi6_qup, 6, CLK_HALT_CFPB_STATEB_REG, 16); +static CLK_GSBI_QUP(gsbi7_qup, 7, CLK_HALT_CFPB_STATEB_REG, 12); +static CLK_GSBI_QUP(gsbi8_qup, 8, CLK_HALT_CFPB_STATEB_REG, 8); +static CLK_GSBI_QUP(gsbi9_qup, 9, CLK_HALT_CFPB_STATEB_REG, 4); +static CLK_GSBI_QUP(gsbi10_qup, 10, CLK_HALT_CFPB_STATEB_REG, 0); +static CLK_GSBI_QUP(gsbi11_qup, 11, CLK_HALT_CFPB_STATEC_REG, 15); +static CLK_GSBI_QUP(gsbi12_qup, 12, CLK_HALT_CFPB_STATEC_REG, 11); + +#define F_PDM(f, s, d, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(1, 0, s##_to_xo_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_pdm[] = { + F_PDM( 0, gnd, 1, NONE), + F_PDM(27000000, pxo, 1, LOW), + F_END +}; + +static struct rcg_clk pdm_clk = { + .b = { + .ctl_reg = PDM_CLK_NS_REG, + .en_mask = BIT(9), + .reset_reg = PDM_CLK_NS_REG, + .reset_mask = BIT(12), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 3, + }, + .ns_reg = PDM_CLK_NS_REG, + .root_en_mask = BIT(11), + .ns_mask = BM(1, 0), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_pdm, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "pdm_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(pdm_clk.c), + }, +}; + +static struct branch_clk pmem_clk = { + .b = { + .ctl_reg = PMEM_ACLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 20, + }, + .c = { + .dbg_name = "pmem_clk", + .ops = &clk_ops_branch, + CLK_INIT(pmem_clk.c), + }, +}; + +#define F_PRNG(f, s, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_prng[] = { + F_PRNG(64000000, pll8, NOMINAL), + F_END +}; + +static struct rcg_clk prng_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(10), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 10, + }, + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_prng, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "prng_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(prng_clk.c), + }, +}; + +#define CLK_SDC(i, n, h_r, h_b) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = SDCn_APPS_CLK_NS_REG(n), \ + .en_mask = BIT(9), \ + .reset_reg = SDCn_RESET_REG(n), \ + .reset_mask = BIT(0), \ + .halt_reg = h_r, \ + .halt_bit = h_b, \ + }, \ + .ns_reg = SDCn_APPS_CLK_NS_REG(n), \ + .md_reg = SDCn_APPS_CLK_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_sdc, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8x60, \ + CLK_INIT(i##_clk.c), \ + }, \ + } +#define F_SDC(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(16, m, 0, n), \ + .ns_val = NS(23, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_sdc[] = { + F_SDC( 0, gnd, 1, 0, 0, NONE), + F_SDC( 144000, pxo, 3, 2, 125, LOW), + F_SDC( 400000, pll8, 4, 1, 240, LOW), + F_SDC(16000000, pll8, 4, 1, 6, LOW), + F_SDC(17070000, pll8, 1, 2, 45, LOW), + F_SDC(20210000, pll8, 1, 1, 19, LOW), + F_SDC(24000000, pll8, 4, 1, 4, LOW), + F_SDC(48000000, pll8, 4, 1, 2, NOMINAL), + F_END +}; + +static CLK_SDC(sdc1, 1, CLK_HALT_DFAB_STATE_REG, 6); +static CLK_SDC(sdc2, 2, CLK_HALT_DFAB_STATE_REG, 5); +static CLK_SDC(sdc3, 3, CLK_HALT_DFAB_STATE_REG, 4); +static CLK_SDC(sdc4, 4, CLK_HALT_DFAB_STATE_REG, 3); +static CLK_SDC(sdc5, 5, CLK_HALT_DFAB_STATE_REG, 2); + +#define F_TSIF_REF(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD16(m, n), \ + .ns_val = NS(31, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_tsif_ref[] = { + F_TSIF_REF( 0, gnd, 1, 0, 0, NONE), + F_TSIF_REF(105000, pxo, 1, 1, 256, LOW), + F_END +}; + +static struct rcg_clk tsif_ref_clk = { + .b = { + .ctl_reg = TSIF_REF_CLK_NS_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 5, + }, + .ns_reg = TSIF_REF_CLK_NS_REG, + .md_reg = TSIF_REF_CLK_MD_REG, + .root_en_mask = BIT(11), + .ns_mask = (BM(31, 16) | BM(6, 0)), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_tsif_ref, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "tsif_ref_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(tsif_ref_clk.c), + }, +}; + +#define F_TSSC(f, s, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(1, 0, s##_to_xo_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_tssc[] = { + F_TSSC( 0, gnd, NONE), + F_TSSC(27000000, pxo, LOW), + F_END +}; + +static struct rcg_clk tssc_clk = { + .b = { + .ctl_reg = TSSC_CLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 4, + }, + .ns_reg = TSSC_CLK_CTL_REG, + .ns_mask = BM(1, 0), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_tssc, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "tssc_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(tssc_clk.c), + }, +}; + +#define F_USB(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(16, m, 0, n), \ + .ns_val = NS(23, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_bb_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_usb[] = { + F_USB( 0, gnd, 1, 0, 0, NONE), + F_USB(60000000, pll8, 1, 5, 32, NOMINAL), + F_END +}; + +static struct rcg_clk usb_hs1_xcvr_clk = { + .b = { + .ctl_reg = USB_HS1_XCVR_FS_CLK_NS_REG, + .en_mask = BIT(9), + .reset_reg = USB_HS1_RESET_REG, + .reset_mask = BIT(0), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 0, + }, + .ns_reg = USB_HS1_XCVR_FS_CLK_NS_REG, + .md_reg = USB_HS1_XCVR_FS_CLK_MD_REG, + .root_en_mask = BIT(11), + .ns_mask = (BM(23, 16) | BM(6, 0)), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_usb, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "usb_hs1_xcvr_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(usb_hs1_xcvr_clk.c), + }, +}; + +static struct branch_clk usb_phy0_clk = { + .b = { + .reset_reg = USB_PHY0_RESET_REG, + .reset_mask = BIT(0), + }, + .c = { + .dbg_name = "usb_phy0_clk", + .ops = &clk_ops_reset, + CLK_INIT(usb_phy0_clk.c), + }, +}; + +#define CLK_USB_FS(i, n) \ + struct rcg_clk i##_clk = { \ + .ns_reg = USB_FSn_XCVR_FS_CLK_NS_REG(n), \ + .b = { \ + .ctl_reg = USB_FSn_XCVR_FS_CLK_NS_REG(n), \ + .halt_check = NOCHECK, \ + }, \ + .md_reg = USB_FSn_XCVR_FS_CLK_MD_REG(n), \ + .root_en_mask = BIT(11), \ + .ns_mask = (BM(23, 16) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_usb, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8x60, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +static CLK_USB_FS(usb_fs1_src, 1); +static struct branch_clk usb_fs1_xcvr_clk = { + .b = { + .ctl_reg = USB_FSn_XCVR_FS_CLK_NS_REG(1), + .en_mask = BIT(9), + .reset_reg = USB_FSn_RESET_REG(1), + .reset_mask = BIT(1), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 15, + }, + .parent = &usb_fs1_src_clk.c, + .c = { + .dbg_name = "usb_fs1_xcvr_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs1_xcvr_clk.c), + }, +}; + +static struct branch_clk usb_fs1_sys_clk = { + .b = { + .ctl_reg = USB_FSn_SYSTEM_CLK_CTL_REG(1), + .en_mask = BIT(4), + .reset_reg = USB_FSn_RESET_REG(1), + .reset_mask = BIT(0), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 16, + }, + .parent = &usb_fs1_src_clk.c, + .c = { + .dbg_name = "usb_fs1_sys_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs1_sys_clk.c), + }, +}; + +static CLK_USB_FS(usb_fs2_src, 2); +static struct branch_clk usb_fs2_xcvr_clk = { + .b = { + .ctl_reg = USB_FSn_XCVR_FS_CLK_NS_REG(2), + .en_mask = BIT(9), + .reset_reg = USB_FSn_RESET_REG(2), + .reset_mask = BIT(1), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 12, + }, + .parent = &usb_fs2_src_clk.c, + .c = { + .dbg_name = "usb_fs2_xcvr_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs2_xcvr_clk.c), + }, +}; + +static struct branch_clk usb_fs2_sys_clk = { + .b = { + .ctl_reg = USB_FSn_SYSTEM_CLK_CTL_REG(2), + .en_mask = BIT(4), + .reset_reg = USB_FSn_RESET_REG(2), + .reset_mask = BIT(0), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 13, + }, + .parent = &usb_fs2_src_clk.c, + .c = { + .dbg_name = "usb_fs2_sys_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs2_sys_clk.c), + }, +}; + +/* Fast Peripheral Bus Clocks */ +static struct branch_clk ce2_p_clk = { + .b = { + .ctl_reg = CE2_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 0, + }, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "ce2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(ce2_p_clk.c), + }, +}; + +static struct branch_clk gsbi1_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(1), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 11, + }, + .c = { + .dbg_name = "gsbi1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi1_p_clk.c), + }, +}; + +static struct branch_clk gsbi2_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(2), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "gsbi2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi2_p_clk.c), + }, +}; + +static struct branch_clk gsbi3_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(3), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 3, + }, + .c = { + .dbg_name = "gsbi3_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi3_p_clk.c), + }, +}; + +static struct branch_clk gsbi4_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(4), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 27, + }, + .c = { + .dbg_name = "gsbi4_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi4_p_clk.c), + }, +}; + +static struct branch_clk gsbi5_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(5), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 23, + }, + .c = { + .dbg_name = "gsbi5_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi5_p_clk.c), + }, +}; + +static struct branch_clk gsbi6_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(6), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 19, + }, + .c = { + .dbg_name = "gsbi6_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi6_p_clk.c), + }, +}; + +static struct branch_clk gsbi7_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(7), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 15, + }, + .c = { + .dbg_name = "gsbi7_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi7_p_clk.c), + }, +}; + +static struct branch_clk gsbi8_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(8), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 11, + }, + .c = { + .dbg_name = "gsbi8_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi8_p_clk.c), + }, +}; + +static struct branch_clk gsbi9_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(9), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "gsbi9_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi9_p_clk.c), + }, +}; + +static struct branch_clk gsbi10_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(10), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEB_REG, + .halt_bit = 3, + }, + .c = { + .dbg_name = "gsbi10_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi10_p_clk.c), + }, +}; + +static struct branch_clk gsbi11_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(11), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 18, + }, + .c = { + .dbg_name = "gsbi11_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi11_p_clk.c), + }, +}; + +static struct branch_clk gsbi12_p_clk = { + .b = { + .ctl_reg = GSBIn_HCLK_CTL_REG(12), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 14, + }, + .c = { + .dbg_name = "gsbi12_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(gsbi12_p_clk.c), + }, +}; + +static struct branch_clk ppss_p_clk = { + .b = { + .ctl_reg = PPSS_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 19, + }, + .c = { + .dbg_name = "ppss_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(ppss_p_clk.c), + }, +}; + +static struct branch_clk tsif_p_clk = { + .b = { + .ctl_reg = TSIF_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEC_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "tsif_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(tsif_p_clk.c), + }, +}; + +static struct branch_clk usb_fs1_p_clk = { + .b = { + .ctl_reg = USB_FSn_HCLK_CTL_REG(1), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 17, + }, + .c = { + .dbg_name = "usb_fs1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs1_p_clk.c), + }, +}; + +static struct branch_clk usb_fs2_p_clk = { + .b = { + .ctl_reg = USB_FSn_HCLK_CTL_REG(2), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_CFPB_STATEA_REG, + .halt_bit = 14, + }, + .c = { + .dbg_name = "usb_fs2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_fs2_p_clk.c), + }, +}; + +static struct branch_clk usb_hs1_p_clk = { + .b = { + .ctl_reg = USB_HS1_HCLK_CTL_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 1, + }, + .c = { + .dbg_name = "usb_hs1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(usb_hs1_p_clk.c), + }, +}; + +static struct branch_clk sdc1_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(1), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 11, + }, + .c = { + .dbg_name = "sdc1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc1_p_clk.c), + }, +}; + +static struct branch_clk sdc2_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(2), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 10, + }, + .c = { + .dbg_name = "sdc2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc2_p_clk.c), + }, +}; + +static struct branch_clk sdc3_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(3), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 9, + }, + .c = { + .dbg_name = "sdc3_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc3_p_clk.c), + }, +}; + +static struct branch_clk sdc4_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(4), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 8, + }, + .c = { + .dbg_name = "sdc4_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc4_p_clk.c), + }, +}; + +static struct branch_clk sdc5_p_clk = { + .b = { + .ctl_reg = SDCn_HCLK_CTL_REG(5), + .en_mask = BIT(4), + .halt_reg = CLK_HALT_DFAB_STATE_REG, + .halt_bit = 7, + }, + .c = { + .dbg_name = "sdc5_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(sdc5_p_clk.c), + }, +}; + +/* HW-Voteable Clocks */ +static struct branch_clk adm0_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(2), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 14, + }, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "adm0_clk", + .ops = &clk_ops_branch, + CLK_INIT(adm0_clk.c), + }, +}; + +static struct branch_clk adm0_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(3), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 13, + }, + .c = { + .dbg_name = "adm0_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(adm0_p_clk.c), + }, +}; + +static struct branch_clk adm1_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(4), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 12, + }, + .parent = &pxo_clk.c, + .c = { + .dbg_name = "adm1_clk", + .ops = &clk_ops_branch, + CLK_INIT(adm1_clk.c), + }, +}; + +static struct branch_clk adm1_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(5), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 11, + }, + .c = { + .dbg_name = "adm1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(adm1_p_clk.c), + }, +}; + +static struct branch_clk modem_ahb1_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(0), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 8, + }, + .c = { + .dbg_name = "modem_ahb1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(modem_ahb1_p_clk.c), + }, +}; + +static struct branch_clk modem_ahb2_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(1), + .halt_reg = CLK_HALT_MSS_SMPSS_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 7, + }, + .c = { + .dbg_name = "modem_ahb2_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(modem_ahb2_p_clk.c), + }, +}; + +static struct branch_clk pmic_arb0_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(8), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 22, + }, + .c = { + .dbg_name = "pmic_arb0_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(pmic_arb0_p_clk.c), + }, +}; + +static struct branch_clk pmic_arb1_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(9), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 21, + }, + .c = { + .dbg_name = "pmic_arb1_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(pmic_arb1_p_clk.c), + }, +}; + +static struct branch_clk pmic_ssbi2_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(7), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 23, + }, + .c = { + .dbg_name = "pmic_ssbi2_clk", + .ops = &clk_ops_branch, + CLK_INIT(pmic_ssbi2_clk.c), + }, +}; + +static struct branch_clk rpm_msg_ram_p_clk = { + .b = { + .ctl_reg = SC0_U_CLK_BRANCH_ENA_VOTE_REG, + .en_mask = BIT(6), + .halt_reg = CLK_HALT_SFPB_MISC_STATE_REG, + .halt_check = HALT_VOTED, + .halt_bit = 12, + }, + .c = { + .dbg_name = "rpm_msg_ram_p_clk", + .ops = &clk_ops_branch, + CLK_INIT(rpm_msg_ram_p_clk.c), + }, +}; + +/* + * Multimedia Clocks + */ + +static struct branch_clk amp_clk = { + .b = { + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(20), + }, + .c = { + .dbg_name = "amp_clk", + .ops = &clk_ops_reset, + CLK_INIT(amp_clk.c), + }, +}; + +#define F_CAM(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(31, 24, n, m, 15, 14, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_cam[] = { + F_CAM( 0, gnd, 1, 0, 0, NONE), + F_CAM( 6000000, pll8, 4, 1, 16, LOW), + F_CAM( 8000000, pll8, 4, 1, 12, LOW), + F_CAM( 12000000, pll8, 4, 1, 8, LOW), + F_CAM( 16000000, pll8, 4, 1, 6, LOW), + F_CAM( 19200000, pll8, 4, 1, 5, LOW), + F_CAM( 24000000, pll8, 4, 1, 4, LOW), + F_CAM( 32000000, pll8, 4, 1, 3, LOW), + F_CAM( 48000000, pll8, 4, 1, 2, LOW), + F_CAM( 64000000, pll8, 3, 1, 2, LOW), + F_CAM( 96000000, pll8, 4, 0, 0, NOMINAL), + F_CAM(128000000, pll8, 3, 0, 0, NOMINAL), + F_END +}; + +static struct rcg_clk cam_clk = { + .b = { + .ctl_reg = CAMCLK_CC_REG, + .en_mask = BIT(0), + .halt_check = DELAY, + }, + .ns_reg = CAMCLK_NS_REG, + .md_reg = CAMCLK_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(31, 24) | BM(15, 14) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd_8, + .freq_tbl = clk_tbl_cam, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "cam_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(cam_clk.c), + }, +}; + +#define F_CSI(f, s, d, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_csi[] = { + F_CSI( 0, gnd, 1, NONE), + F_CSI(192000000, pll8, 2, LOW), + F_CSI(384000000, pll8, 1, NOMINAL), + F_END +}; + +static struct rcg_clk csi_src_clk = { + .ns_reg = CSI_NS_REG, + .b = { + .ctl_reg = CSI_CC_REG, + .halt_check = NOCHECK, + }, + .root_en_mask = BIT(2), + .ns_mask = (BM(15, 12) | BM(2, 0)), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_csi, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "csi_src_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(csi_src_clk.c), + }, +}; + +static struct branch_clk csi0_clk = { + .b = { + .ctl_reg = CSI_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(8), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 13, + }, + .parent = &csi_src_clk.c, + .c = { + .dbg_name = "csi0_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0_clk.c), + }, +}; + +static struct branch_clk csi1_clk = { + .b = { + .ctl_reg = CSI_CC_REG, + .en_mask = BIT(7), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(18), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 14, + }, + .parent = &csi_src_clk.c, + .c = { + .dbg_name = "csi1_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi1_clk.c), + }, +}; + +#define F_DSI(d) \ + { \ + .freq_hz = d, \ + .ns_val = BVAL(27, 24, (d-1)), \ + } +/* The DSI_BYTE clock is sourced from the DSI PHY PLL, which may change rate + * without this clock driver knowing. So, overload the clk_set_rate() to set + * the divider (1 to 16) of the clock with respect to the PLL rate. */ +static struct clk_freq_tbl clk_tbl_dsi_byte[] = { + F_DSI(1), F_DSI(2), F_DSI(3), F_DSI(4), + F_DSI(5), F_DSI(6), F_DSI(7), F_DSI(8), + F_DSI(9), F_DSI(10), F_DSI(11), F_DSI(12), + F_DSI(13), F_DSI(14), F_DSI(15), F_DSI(16), + F_END +}; + + +static struct rcg_clk dsi_byte_clk = { + .b = { + .ctl_reg = MISC_CC_REG, + .halt_check = DELAY, + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(7), + }, + .ns_reg = MISC_CC2_REG, + .root_en_mask = BIT(2), + .ns_mask = BM(27, 24), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_dsi_byte, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "dsi_byte_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(dsi_byte_clk.c), + }, +}; + +static struct branch_clk dsi_esc_clk = { + .b = { + .ctl_reg = MISC_CC_REG, + .en_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 24, + }, + .c = { + .dbg_name = "dsi_esc_clk", + .ops = &clk_ops_branch, + CLK_INIT(dsi_esc_clk.c), + }, +}; + +#define F_GFX2D(f, s, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD4(4, m, 0, n), \ + .ns_val = NS_MND_BANKED4(20, 16, n, m, 3, 0, s##_to_mm_mux), \ + .ctl_val = CC_BANKED(9, 6, n), \ + .mnd_en_mask = (BIT(8) | BIT(5)) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_gfx2d[] = { + F_GFX2D( 0, gnd, 0, 0, NONE), + F_GFX2D( 27000000, pxo, 0, 0, LOW), + F_GFX2D( 48000000, pll8, 1, 8, LOW), + F_GFX2D( 54857000, pll8, 1, 7, LOW), + F_GFX2D( 64000000, pll8, 1, 6, LOW), + F_GFX2D( 76800000, pll8, 1, 5, LOW), + F_GFX2D( 96000000, pll8, 1, 4, LOW), + F_GFX2D(128000000, pll8, 1, 3, NOMINAL), + F_GFX2D(145455000, pll2, 2, 11, NOMINAL), + F_GFX2D(160000000, pll2, 1, 5, NOMINAL), + F_GFX2D(177778000, pll2, 2, 9, NOMINAL), + F_GFX2D(200000000, pll2, 1, 4, NOMINAL), + F_GFX2D(228571000, pll2, 2, 7, HIGH), + F_END +}; + +static struct bank_masks bmnd_info_gfx2d0 = { + .bank_sel_mask = BIT(11), + .bank0_mask = { + .md_reg = GFX2D0_MD0_REG, + .ns_mask = BM(23, 20) | BM(5, 3), + .rst_mask = BIT(25), + .mnd_en_mask = BIT(8), + .mode_mask = BM(10, 9), + }, + .bank1_mask = { + .md_reg = GFX2D0_MD1_REG, + .ns_mask = BM(19, 16) | BM(2, 0), + .rst_mask = BIT(24), + .mnd_en_mask = BIT(5), + .mode_mask = BM(7, 6), + }, +}; + +static struct rcg_clk gfx2d0_clk = { + .b = { + .ctl_reg = GFX2D0_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(14), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 9, + }, + .ns_reg = GFX2D0_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx2d, + .bank_masks = &bmnd_info_gfx2d0, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "gfx2d0_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(gfx2d0_clk.c), + }, +}; + +static struct bank_masks bmnd_info_gfx2d1 = { + .bank_sel_mask = BIT(11), + .bank0_mask = { + .md_reg = GFX2D1_MD0_REG, + .ns_mask = BM(23, 20) | BM(5, 3), + .rst_mask = BIT(25), + .mnd_en_mask = BIT(8), + .mode_mask = BM(10, 9), + }, + .bank1_mask = { + .md_reg = GFX2D1_MD1_REG, + .ns_mask = BM(19, 16) | BM(2, 0), + .rst_mask = BIT(24), + .mnd_en_mask = BIT(5), + .mode_mask = BM(7, 6), + }, +}; + +static struct rcg_clk gfx2d1_clk = { + .b = { + .ctl_reg = GFX2D1_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(13), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 14, + }, + .ns_reg = GFX2D1_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx2d, + .bank_masks = &bmnd_info_gfx2d1, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "gfx2d1_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(gfx2d1_clk.c), + }, +}; + +#define F_GFX3D(f, s, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD4(4, m, 0, n), \ + .ns_val = NS_MND_BANKED4(18, 14, n, m, 3, 0, s##_to_mm_mux), \ + .ctl_val = CC_BANKED(9, 6, n), \ + .mnd_en_mask = (BIT(8) | BIT(5)) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_gfx3d[] = { + F_GFX3D( 0, gnd, 0, 0, NONE), + F_GFX3D( 27000000, pxo, 0, 0, LOW), + F_GFX3D( 48000000, pll8, 1, 8, LOW), + F_GFX3D( 54857000, pll8, 1, 7, LOW), + F_GFX3D( 64000000, pll8, 1, 6, LOW), + F_GFX3D( 76800000, pll8, 1, 5, LOW), + F_GFX3D( 96000000, pll8, 1, 4, LOW), + F_GFX3D(128000000, pll8, 1, 3, NOMINAL), + F_GFX3D(145455000, pll2, 2, 11, NOMINAL), + F_GFX3D(160000000, pll2, 1, 5, NOMINAL), + F_GFX3D(177778000, pll2, 2, 9, NOMINAL), + F_GFX3D(200000000, pll2, 1, 4, NOMINAL), + F_GFX3D(228571000, pll2, 2, 7, HIGH), + F_GFX3D(266667000, pll2, 1, 3, HIGH), + F_GFX3D(320000000, pll2, 2, 5, HIGH), + F_END +}; + +static struct bank_masks bmnd_info_gfx3d = { + .bank_sel_mask = BIT(11), + .bank0_mask = { + .md_reg = GFX3D_MD0_REG, + .ns_mask = BM(21, 18) | BM(5, 3), + .rst_mask = BIT(23), + .mnd_en_mask = BIT(8), + .mode_mask = BM(10, 9), + }, + .bank1_mask = { + .md_reg = GFX3D_MD1_REG, + .ns_mask = BM(17, 14) | BM(2, 0), + .rst_mask = BIT(22), + .mnd_en_mask = BIT(5), + .mode_mask = BM(7, 6), + }, +}; + +static struct rcg_clk gfx3d_clk = { + .b = { + .ctl_reg = GFX3D_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(12), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 4, + }, + .ns_reg = GFX3D_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_gfx3d, + .bank_masks = &bmnd_info_gfx3d, + .depends = &gmem_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "gfx3d_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(gfx3d_clk.c), + }, +}; + +#define F_IJPEG(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(23, 16, n, m, 15, 12, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!n, \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_ijpeg[] = { + F_IJPEG( 0, gnd, 1, 0, 0, NONE), + F_IJPEG( 27000000, pxo, 1, 0, 0, LOW), + F_IJPEG( 36570000, pll8, 1, 2, 21, LOW), + F_IJPEG( 54860000, pll8, 7, 0, 0, LOW), + F_IJPEG( 96000000, pll8, 4, 0, 0, LOW), + F_IJPEG(109710000, pll8, 1, 2, 7, LOW), + F_IJPEG(128000000, pll8, 3, 0, 0, NOMINAL), + F_IJPEG(153600000, pll8, 1, 2, 5, NOMINAL), + F_IJPEG(200000000, pll2, 4, 0, 0, NOMINAL), + F_IJPEG(228571000, pll2, 1, 2, 7, NOMINAL), + F_END +}; + +static struct rcg_clk ijpeg_clk = { + .b = { + .ctl_reg = IJPEG_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(9), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 24, + }, + .ns_reg = IJPEG_NS_REG, + .md_reg = IJPEG_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(23, 16) | BM(15, 12) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_ijpeg, + .depends = &ijpeg_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "ijpeg_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(ijpeg_clk.c), + }, +}; + +#define F_JPEGD(f, s, d, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_jpegd[] = { + F_JPEGD( 0, gnd, 1, NONE), + F_JPEGD( 64000000, pll8, 6, LOW), + F_JPEGD( 76800000, pll8, 5, LOW), + F_JPEGD( 96000000, pll8, 4, LOW), + F_JPEGD(160000000, pll2, 5, NOMINAL), + F_JPEGD(200000000, pll2, 4, NOMINAL), + F_END +}; + +static struct rcg_clk jpegd_clk = { + .b = { + .ctl_reg = JPEGD_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(19), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 19, + }, + .ns_reg = JPEGD_NS_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(15, 12) | BM(2, 0)), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_jpegd, + .depends = &jpegd_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "jpegd_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(jpegd_clk.c), + }, +}; + +#define F_MDP(f, s, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MND_BANKED8(22, 14, n, m, 3, 0, s##_to_mm_mux), \ + .ctl_val = CC_BANKED(9, 6, n), \ + .mnd_en_mask = (BIT(8) | BIT(5)) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_mdp[] = { + F_MDP( 0, gnd, 0, 0, NONE), + F_MDP( 9600000, pll8, 1, 40, LOW), + F_MDP( 13710000, pll8, 1, 28, LOW), + F_MDP( 27000000, pxo, 0, 0, LOW), + F_MDP( 29540000, pll8, 1, 13, LOW), + F_MDP( 34910000, pll8, 1, 11, LOW), + F_MDP( 38400000, pll8, 1, 10, LOW), + F_MDP( 59080000, pll8, 2, 13, LOW), + F_MDP( 76800000, pll8, 1, 5, LOW), + F_MDP( 85330000, pll8, 2, 9, LOW), + F_MDP( 96000000, pll8, 1, 4, NOMINAL), + F_MDP(128000000, pll8, 1, 3, NOMINAL), + F_MDP(160000000, pll2, 1, 5, NOMINAL), + F_MDP(177780000, pll2, 2, 9, NOMINAL), + F_MDP(200000000, pll2, 1, 4, NOMINAL), + F_END +}; + +static struct bank_masks bmnd_info_mdp = { + .bank_sel_mask = BIT(11), + .bank0_mask = { + .md_reg = MDP_MD0_REG, + .ns_mask = BM(29, 22) | BM(5, 3), + .rst_mask = BIT(31), + .mnd_en_mask = BIT(8), + .mode_mask = BM(10, 9), + }, + .bank1_mask = { + .md_reg = MDP_MD1_REG, + .ns_mask = BM(21, 14) | BM(2, 0), + .rst_mask = BIT(30), + .mnd_en_mask = BIT(5), + .mode_mask = BM(7, 6), + }, +}; + +static struct rcg_clk mdp_clk = { + .b = { + .ctl_reg = MDP_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(21), + .halt_reg = DBG_BUS_VEC_C_REG, + .halt_bit = 10, + }, + .ns_reg = MDP_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_mnd_banked, + .freq_tbl = clk_tbl_mdp, + .bank_masks = &bmnd_info_mdp, + .depends = &mdp_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mdp_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(mdp_clk.c), + }, +}; + +#define F_MDP_VSYNC(f, s, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_SRC_SEL(13, 13, s##_to_bb_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_mdp_vsync[] = { + F_MDP_VSYNC(27000000, pxo, LOW), + F_END +}; + +static struct rcg_clk mdp_vsync_clk = { + .b = { + .ctl_reg = MISC_CC_REG, + .en_mask = BIT(6), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(3), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 22, + }, + .ns_reg = MISC_CC2_REG, + .ns_mask = BIT(13), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_mdp_vsync, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "mdp_vsync_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(mdp_vsync_clk.c), + }, +}; + +#define F_PIXEL_MDP(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD16(m, n), \ + .ns_val = NS_MM(31, 16, n, m, 15, 14, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_pixel_mdp[] = { + F_PIXEL_MDP( 0, gnd, 1, 0, 0, NONE), + F_PIXEL_MDP( 25600000, pll8, 3, 1, 5, LOW), + F_PIXEL_MDP( 42667000, pll8, 1, 1, 9, LOW), + F_PIXEL_MDP( 43192000, pll8, 1, 64, 569, LOW), + F_PIXEL_MDP( 48000000, pll8, 4, 1, 2, LOW), + F_PIXEL_MDP( 53990000, pll8, 2, 169, 601, LOW), + F_PIXEL_MDP( 64000000, pll8, 2, 1, 3, LOW), + F_PIXEL_MDP( 69300000, pll8, 1, 231, 1280, LOW), + F_PIXEL_MDP( 76800000, pll8, 1, 1, 5, LOW), + F_PIXEL_MDP( 85333000, pll8, 1, 2, 9, LOW), + F_PIXEL_MDP(106500000, pll8, 1, 71, 256, NOMINAL), + F_PIXEL_MDP(109714000, pll8, 1, 2, 7, NOMINAL), + F_END +}; + +static struct rcg_clk pixel_mdp_clk = { + .ns_reg = PIXEL_NS_REG, + .md_reg = PIXEL_MD_REG, + .b = { + .ctl_reg = PIXEL_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(5), + .halt_reg = DBG_BUS_VEC_C_REG, + .halt_bit = 23, + }, + .root_en_mask = BIT(2), + .ns_mask = (BM(31, 16) | BM(15, 14) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_pixel_mdp, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "pixel_mdp_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(pixel_mdp_clk.c), + }, +}; + +static struct branch_clk pixel_lcdc_clk = { + .b = { + .ctl_reg = PIXEL_CC_REG, + .en_mask = BIT(8), + .halt_reg = DBG_BUS_VEC_C_REG, + .halt_bit = 21, + }, + .parent = &pixel_mdp_clk.c, + .c = { + .dbg_name = "pixel_lcdc_clk", + .ops = &clk_ops_branch, + CLK_INIT(pixel_lcdc_clk.c), + }, +}; + +#define F_ROT(f, s, d, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC_BANKED(29, 26, 25, 22, d, \ + 21, 19, 18, 16, s##_to_mm_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_rot[] = { + F_ROT( 0, gnd, 1, NONE), + F_ROT( 27000000, pxo, 1, LOW), + F_ROT( 29540000, pll8, 13, LOW), + F_ROT( 32000000, pll8, 12, LOW), + F_ROT( 38400000, pll8, 10, LOW), + F_ROT( 48000000, pll8, 8, LOW), + F_ROT( 54860000, pll8, 7, LOW), + F_ROT( 64000000, pll8, 6, LOW), + F_ROT( 76800000, pll8, 5, LOW), + F_ROT( 96000000, pll8, 4, NOMINAL), + F_ROT(100000000, pll2, 8, NOMINAL), + F_ROT(114290000, pll2, 7, NOMINAL), + F_ROT(133330000, pll2, 6, NOMINAL), + F_ROT(160000000, pll2, 5, NOMINAL), + F_END +}; + +static struct bank_masks bdiv_info_rot = { + .bank_sel_mask = BIT(30), + .bank0_mask = { + .ns_mask = BM(25, 22) | BM(18, 16), + }, + .bank1_mask = { + .ns_mask = BM(29, 26) | BM(21, 19), + }, +}; + +static struct rcg_clk rot_clk = { + .b = { + .ctl_reg = ROT_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(2), + .halt_reg = DBG_BUS_VEC_C_REG, + .halt_bit = 15, + }, + .ns_reg = ROT_NS_REG, + .root_en_mask = BIT(2), + .set_rate = set_rate_div_banked, + .freq_tbl = clk_tbl_rot, + .bank_masks = &bdiv_info_rot, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "rot_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(rot_clk.c), + }, +}; + +#define F_TV(f, s, p_r, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(23, 16, n, m, 15, 14, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + .extra_freq_data = p_r, \ + } +/* Switching TV freqs requires PLL reconfiguration. */ +static struct pll_rate mm_pll2_rate[] = { + [0] = PLL_RATE( 7, 6301, 13500, 0, 4, 0x4248B), /* 50400500 Hz */ + [1] = PLL_RATE( 8, 0, 0, 0, 4, 0x4248B), /* 54000000 Hz */ + [2] = PLL_RATE(16, 2, 125, 0, 4, 0x5248F), /* 108108000 Hz */ + [3] = PLL_RATE(22, 0, 0, 2, 4, 0x6248B), /* 148500000 Hz */ + [4] = PLL_RATE(44, 0, 0, 2, 4, 0x6248F), /* 297000000 Hz */ +}; +static struct clk_freq_tbl clk_tbl_tv[] = { + F_TV( 0, gnd, &mm_pll2_rate[0], 1, 0, 0, NONE), + F_TV( 25200000, pll3, &mm_pll2_rate[0], 2, 0, 0, LOW), + F_TV( 27000000, pll3, &mm_pll2_rate[1], 2, 0, 0, LOW), + F_TV( 27030000, pll3, &mm_pll2_rate[2], 4, 0, 0, LOW), + F_TV( 74250000, pll3, &mm_pll2_rate[3], 2, 0, 0, NOMINAL), + F_TV(148500000, pll3, &mm_pll2_rate[4], 2, 0, 0, NOMINAL), + F_END +}; + +static struct rcg_clk tv_src_clk = { + .ns_reg = TV_NS_REG, + .b = { + .ctl_reg = TV_CC_REG, + .halt_check = NOCHECK, + }, + .md_reg = TV_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(23, 16) | BM(15, 14) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_tv, + .freq_tbl = clk_tbl_tv, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "tv_src_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(tv_src_clk.c), + }, +}; + +static struct branch_clk tv_enc_clk = { + .b = { + .ctl_reg = TV_CC_REG, + .en_mask = BIT(8), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(0), + .halt_reg = DBG_BUS_VEC_D_REG, + .halt_bit = 8, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "tv_enc_clk", + .ops = &clk_ops_branch, + CLK_INIT(tv_enc_clk.c), + }, +}; + +static struct branch_clk tv_dac_clk = { + .b = { + .ctl_reg = TV_CC_REG, + .en_mask = BIT(10), + .halt_reg = DBG_BUS_VEC_D_REG, + .halt_bit = 9, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "tv_dac_clk", + .ops = &clk_ops_branch, + CLK_INIT(tv_dac_clk.c), + }, +}; + +static struct branch_clk mdp_tv_clk = { + .b = { + .ctl_reg = TV_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(4), + .halt_reg = DBG_BUS_VEC_D_REG, + .halt_bit = 11, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "mdp_tv_clk", + .ops = &clk_ops_branch, + CLK_INIT(mdp_tv_clk.c), + }, +}; + +static struct branch_clk hdmi_tv_clk = { + .b = { + .ctl_reg = TV_CC_REG, + .en_mask = BIT(12), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(1), + .halt_reg = DBG_BUS_VEC_D_REG, + .halt_bit = 10, + }, + .parent = &tv_src_clk.c, + .c = { + .dbg_name = "hdmi_tv_clk", + .ops = &clk_ops_branch, + CLK_INIT(hdmi_tv_clk.c), + }, +}; + +static struct branch_clk hdmi_app_clk = { + .b = { + .ctl_reg = MISC_CC2_REG, + .en_mask = BIT(11), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(11), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 25, + }, + .c = { + .dbg_name = "hdmi_app_clk", + .ops = &clk_ops_branch, + CLK_INIT(hdmi_app_clk.c), + }, +}; + +#define F_VCODEC(f, s, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(18, 11, n, m, 0, 0, 1, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_vcodec[] = { + F_VCODEC( 0, gnd, 0, 0, NONE), + F_VCODEC( 27000000, pxo, 0, 0, LOW), + F_VCODEC( 32000000, pll8, 1, 12, LOW), + F_VCODEC( 48000000, pll8, 1, 8, LOW), + F_VCODEC( 54860000, pll8, 1, 7, LOW), + F_VCODEC( 96000000, pll8, 1, 4, LOW), + F_VCODEC(133330000, pll2, 1, 6, NOMINAL), + F_VCODEC(200000000, pll2, 1, 4, NOMINAL), + F_VCODEC(228570000, pll2, 2, 7, HIGH), + F_END +}; + +static struct rcg_clk vcodec_clk = { + .b = { + .ctl_reg = VCODEC_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(6), + .halt_reg = DBG_BUS_VEC_C_REG, + .halt_bit = 29, + }, + .ns_reg = VCODEC_NS_REG, + .md_reg = VCODEC_MD0_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(18, 11) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_vcodec, + .depends = &vcodec_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "vcodec_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(vcodec_clk.c), + }, +}; + +#define F_VPE(f, s, d, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .ns_val = NS_DIVSRC(15, 12, d, 2, 0, s##_to_mm_mux), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_vpe[] = { + F_VPE( 0, gnd, 1, NONE), + F_VPE( 27000000, pxo, 1, LOW), + F_VPE( 34909000, pll8, 11, LOW), + F_VPE( 38400000, pll8, 10, LOW), + F_VPE( 64000000, pll8, 6, LOW), + F_VPE( 76800000, pll8, 5, LOW), + F_VPE( 96000000, pll8, 4, NOMINAL), + F_VPE(100000000, pll2, 8, NOMINAL), + F_VPE(160000000, pll2, 5, NOMINAL), + F_VPE(200000000, pll2, 4, HIGH), + F_END +}; + +static struct rcg_clk vpe_clk = { + .b = { + .ctl_reg = VPE_CC_REG, + .en_mask = BIT(0), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(17), + .halt_reg = DBG_BUS_VEC_A_REG, + .halt_bit = 28, + }, + .ns_reg = VPE_NS_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(15, 12) | BM(2, 0)), + .set_rate = set_rate_nop, + .freq_tbl = clk_tbl_vpe, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "vpe_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(vpe_clk.c), + }, +}; + +#define F_VFE(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS_MM(23, 16, n, m, 11, 10, d, 2, 0, s##_to_mm_mux), \ + .ctl_val = CC(6, n), \ + .mnd_en_mask = BIT(5) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_vfe[] = { + F_VFE( 0, gnd, 1, 0, 0, NONE), + F_VFE( 13960000, pll8, 1, 2, 55, LOW), + F_VFE( 27000000, pxo, 1, 0, 0, LOW), + F_VFE( 36570000, pll8, 1, 2, 21, LOW), + F_VFE( 38400000, pll8, 2, 1, 5, LOW), + F_VFE( 45180000, pll8, 1, 2, 17, LOW), + F_VFE( 48000000, pll8, 2, 1, 4, LOW), + F_VFE( 54860000, pll8, 1, 1, 7, LOW), + F_VFE( 64000000, pll8, 2, 1, 3, LOW), + F_VFE( 76800000, pll8, 1, 1, 5, LOW), + F_VFE( 96000000, pll8, 2, 1, 2, LOW), + F_VFE(109710000, pll8, 1, 2, 7, LOW), + F_VFE(128000000, pll8, 1, 1, 3, NOMINAL), + F_VFE(153600000, pll8, 1, 2, 5, NOMINAL), + F_VFE(200000000, pll2, 2, 1, 2, NOMINAL), + F_VFE(228570000, pll2, 1, 2, 7, NOMINAL), + F_VFE(266667000, pll2, 1, 1, 3, HIGH), + F_END +}; + +static struct rcg_clk vfe_clk = { + .b = { + .ctl_reg = VFE_CC_REG, + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(15), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 6, + .en_mask = BIT(0), + }, + .ns_reg = VFE_NS_REG, + .md_reg = VFE_MD_REG, + .root_en_mask = BIT(2), + .ns_mask = (BM(23, 16) | BM(11, 10) | BM(2, 0)), + .ctl_mask = BM(7, 6), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_vfe, + .depends = &vfe_axi_clk.c, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "vfe_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(vfe_clk.c), + }, +}; + +static struct branch_clk csi0_vfe_clk = { + .b = { + .ctl_reg = VFE_CC_REG, + .en_mask = BIT(12), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(24), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 7, + }, + .parent = &vfe_clk.c, + .c = { + .dbg_name = "csi0_vfe_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi0_vfe_clk.c), + }, +}; + +static struct branch_clk csi1_vfe_clk = { + .b = { + .ctl_reg = VFE_CC_REG, + .en_mask = BIT(10), + .reset_reg = SW_RESET_CORE_REG, + .reset_mask = BIT(23), + .halt_reg = DBG_BUS_VEC_B_REG, + .halt_bit = 8, + }, + .parent = &vfe_clk.c, + .c = { + .dbg_name = "csi1_vfe_clk", + .ops = &clk_ops_branch, + CLK_INIT(csi1_vfe_clk.c), + }, +}; + +/* + * Low Power Audio Clocks + */ +#define F_AIF_OSR(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD8(8, m, 0, n), \ + .ns_val = NS(31, 24, n, m, 5, 4, 3, d, 2, 0, s##_to_lpa_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_aif_osr[] = { + F_AIF_OSR( 0, gnd, 1, 0, 0, NONE), + F_AIF_OSR( 768000, pll4, 4, 1, 176, LOW), + F_AIF_OSR( 1024000, pll4, 4, 1, 132, LOW), + F_AIF_OSR( 1536000, pll4, 4, 1, 88, LOW), + F_AIF_OSR( 2048000, pll4, 4, 1, 66, LOW), + F_AIF_OSR( 3072000, pll4, 4, 1, 44, LOW), + F_AIF_OSR( 4096000, pll4, 4, 1, 33, LOW), + F_AIF_OSR( 6144000, pll4, 4, 1, 22, LOW), + F_AIF_OSR( 8192000, pll4, 2, 1, 33, LOW), + F_AIF_OSR(12288000, pll4, 4, 1, 11, LOW), + F_AIF_OSR(24576000, pll4, 2, 1, 11, LOW), + F_END +}; + +#define CLK_AIF_OSR(i, ns, md, h_r) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(17), \ + .reset_reg = ns, \ + .reset_mask = BIT(19), \ + .halt_reg = h_r, \ + .halt_check = ENABLE, \ + .halt_bit = 1, \ + }, \ + .ns_reg = ns, \ + .md_reg = md, \ + .root_en_mask = BIT(9), \ + .ns_mask = (BM(31, 24) | BM(6, 0)), \ + .set_rate = set_rate_mnd, \ + .freq_tbl = clk_tbl_aif_osr, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8x60, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +#define F_AIF_BIT(d, s) \ + { \ + .freq_hz = d, \ + .ns_val = (BVAL(14, 14, s) | BVAL(13, 10, (d-1))) \ + } +static struct clk_freq_tbl clk_tbl_aif_bit[] = { + F_AIF_BIT(0, 1), /* Use external clock. */ + F_AIF_BIT(1, 0), F_AIF_BIT(2, 0), F_AIF_BIT(3, 0), F_AIF_BIT(4, 0), + F_AIF_BIT(5, 0), F_AIF_BIT(6, 0), F_AIF_BIT(7, 0), F_AIF_BIT(8, 0), + F_AIF_BIT(9, 0), F_AIF_BIT(10, 0), F_AIF_BIT(11, 0), F_AIF_BIT(12, 0), + F_AIF_BIT(13, 0), F_AIF_BIT(14, 0), F_AIF_BIT(15, 0), F_AIF_BIT(16, 0), + F_END +}; + +#define CLK_AIF_BIT(i, ns, h_r) \ + struct rcg_clk i##_clk = { \ + .b = { \ + .ctl_reg = ns, \ + .en_mask = BIT(15), \ + .halt_reg = h_r, \ + .halt_check = DELAY, \ + }, \ + .ns_reg = ns, \ + .ns_mask = BM(14, 10), \ + .set_rate = set_rate_nop, \ + .freq_tbl = clk_tbl_aif_bit, \ + .current_freq = &local_dummy_freq, \ + .c = { \ + .dbg_name = #i "_clk", \ + .ops = &soc_clk_ops_8x60, \ + CLK_INIT(i##_clk.c), \ + }, \ + } + +static CLK_AIF_OSR(mi2s_osr, LCC_MI2S_NS_REG, LCC_MI2S_MD_REG, + LCC_MI2S_STATUS_REG); +static CLK_AIF_BIT(mi2s_bit, LCC_MI2S_NS_REG, LCC_MI2S_STATUS_REG); + +static CLK_AIF_OSR(codec_i2s_mic_osr, LCC_CODEC_I2S_MIC_NS_REG, + LCC_CODEC_I2S_MIC_MD_REG, LCC_CODEC_I2S_MIC_STATUS_REG); +static CLK_AIF_BIT(codec_i2s_mic_bit, LCC_CODEC_I2S_MIC_NS_REG, + LCC_CODEC_I2S_MIC_STATUS_REG); + +static CLK_AIF_OSR(spare_i2s_mic_osr, LCC_SPARE_I2S_MIC_NS_REG, + LCC_SPARE_I2S_MIC_MD_REG, LCC_SPARE_I2S_MIC_STATUS_REG); +static CLK_AIF_BIT(spare_i2s_mic_bit, LCC_SPARE_I2S_MIC_NS_REG, + LCC_SPARE_I2S_MIC_STATUS_REG); + +static CLK_AIF_OSR(codec_i2s_spkr_osr, LCC_CODEC_I2S_SPKR_NS_REG, + LCC_CODEC_I2S_SPKR_MD_REG, LCC_CODEC_I2S_SPKR_STATUS_REG); +static CLK_AIF_BIT(codec_i2s_spkr_bit, LCC_CODEC_I2S_SPKR_NS_REG, + LCC_CODEC_I2S_SPKR_STATUS_REG); + +static CLK_AIF_OSR(spare_i2s_spkr_osr, LCC_SPARE_I2S_SPKR_NS_REG, + LCC_SPARE_I2S_SPKR_MD_REG, LCC_SPARE_I2S_SPKR_STATUS_REG); +static CLK_AIF_BIT(spare_i2s_spkr_bit, LCC_SPARE_I2S_SPKR_NS_REG, + LCC_SPARE_I2S_SPKR_STATUS_REG); + +#define F_PCM(f, s, d, m, n, v) \ + { \ + .freq_hz = f, \ + .src_clk = &s##_clk.c, \ + .md_val = MD16(m, n), \ + .ns_val = NS(31, 16, n, m, 5, 4, 3, d, 2, 0, s##_to_lpa_mux), \ + .mnd_en_mask = BIT(8) * !!(n), \ + .sys_vdd = v, \ + } +static struct clk_freq_tbl clk_tbl_pcm[] = { + F_PCM( 0, gnd, 1, 0, 0, NONE), + F_PCM( 512000, pll4, 4, 1, 264, LOW), + F_PCM( 768000, pll4, 4, 1, 176, LOW), + F_PCM( 1024000, pll4, 4, 1, 132, LOW), + F_PCM( 1536000, pll4, 4, 1, 88, LOW), + F_PCM( 2048000, pll4, 4, 1, 66, LOW), + F_PCM( 3072000, pll4, 4, 1, 44, LOW), + F_PCM( 4096000, pll4, 4, 1, 33, LOW), + F_PCM( 6144000, pll4, 4, 1, 22, LOW), + F_PCM( 8192000, pll4, 2, 1, 33, LOW), + F_PCM(12288000, pll4, 4, 1, 11, LOW), + F_PCM(24580000, pll4, 2, 1, 11, LOW), + F_END +}; + +static struct rcg_clk pcm_clk = { + .b = { + .ctl_reg = LCC_PCM_NS_REG, + .en_mask = BIT(11), + .reset_reg = LCC_PCM_NS_REG, + .reset_mask = BIT(13), + .halt_reg = LCC_PCM_STATUS_REG, + .halt_check = ENABLE, + .halt_bit = 0, + }, + .ns_reg = LCC_PCM_NS_REG, + .md_reg = LCC_PCM_MD_REG, + .root_en_mask = BIT(9), + .ns_mask = (BM(31, 16) | BM(6, 0)), + .set_rate = set_rate_mnd, + .freq_tbl = clk_tbl_pcm, + .current_freq = &local_dummy_freq, + .c = { + .dbg_name = "pcm_clk", + .ops = &soc_clk_ops_8x60, + CLK_INIT(pcm_clk.c), + }, +}; + +DEFINE_CLK_RPM(afab_clk, afab_a_clk, APPS_FABRIC); +DEFINE_CLK_RPM(cfpb_clk, cfpb_a_clk, CFPB); +DEFINE_CLK_RPM(dfab_clk, dfab_a_clk, DAYTONA_FABRIC); +DEFINE_CLK_RPM(ebi1_clk, ebi1_a_clk, EBI1); +DEFINE_CLK_RPM(mmfab_clk, mmfab_a_clk, MM_FABRIC); +DEFINE_CLK_RPM(mmfpb_clk, mmfpb_a_clk, MMFPB); +DEFINE_CLK_RPM(sfab_clk, sfab_a_clk, SYSTEM_FABRIC); +DEFINE_CLK_RPM(sfpb_clk, sfpb_a_clk, SFPB); +DEFINE_CLK_RPM(smi_clk, smi_a_clk, SMI); + +static DEFINE_CLK_VOTER(dfab_dsps_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_usb_hs_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc1_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc2_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc3_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc4_clk, &dfab_clk.c); +static DEFINE_CLK_VOTER(dfab_sdc5_clk, &dfab_clk.c); + +static DEFINE_CLK_VOTER(ebi1_msmbus_clk, &ebi1_clk.c); +static DEFINE_CLK_VOTER(ebi1_adm0_clk, &ebi1_clk.c); +static DEFINE_CLK_VOTER(ebi1_adm1_clk, &ebi1_clk.c); + +static DEFINE_CLK_MEASURE(sc0_m_clk); +static DEFINE_CLK_MEASURE(sc1_m_clk); +static DEFINE_CLK_MEASURE(l2_m_clk); + +#ifdef CONFIG_DEBUG_FS +struct measure_sel { + u32 test_vector; + struct clk *clk; +}; + +static struct measure_sel measure_mux[] = { + { TEST_PER_LS(0x08), &modem_ahb1_p_clk.c }, + { TEST_PER_LS(0x09), &modem_ahb2_p_clk.c }, + { TEST_PER_LS(0x12), &sdc1_p_clk.c }, + { TEST_PER_LS(0x13), &sdc1_clk.c }, + { TEST_PER_LS(0x14), &sdc2_p_clk.c }, + { TEST_PER_LS(0x15), &sdc2_clk.c }, + { TEST_PER_LS(0x16), &sdc3_p_clk.c }, + { TEST_PER_LS(0x17), &sdc3_clk.c }, + { TEST_PER_LS(0x18), &sdc4_p_clk.c }, + { TEST_PER_LS(0x19), &sdc4_clk.c }, + { TEST_PER_LS(0x1A), &sdc5_p_clk.c }, + { TEST_PER_LS(0x1B), &sdc5_clk.c }, + { TEST_PER_LS(0x25), &dfab_clk.c }, + { TEST_PER_LS(0x25), &dfab_a_clk.c }, + { TEST_PER_LS(0x26), &pmem_clk.c }, + { TEST_PER_LS(0x2B), &ppss_p_clk.c }, + { TEST_PER_LS(0x33), &cfpb_clk.c }, + { TEST_PER_LS(0x33), &cfpb_a_clk.c }, + { TEST_PER_LS(0x3D), &gsbi1_p_clk.c }, + { TEST_PER_LS(0x3E), &gsbi1_uart_clk.c }, + { TEST_PER_LS(0x3F), &gsbi1_qup_clk.c }, + { TEST_PER_LS(0x41), &gsbi2_p_clk.c }, + { TEST_PER_LS(0x42), &gsbi2_uart_clk.c }, + { TEST_PER_LS(0x44), &gsbi2_qup_clk.c }, + { TEST_PER_LS(0x45), &gsbi3_p_clk.c }, + { TEST_PER_LS(0x46), &gsbi3_uart_clk.c }, + { TEST_PER_LS(0x48), &gsbi3_qup_clk.c }, + { TEST_PER_LS(0x49), &gsbi4_p_clk.c }, + { TEST_PER_LS(0x4A), &gsbi4_uart_clk.c }, + { TEST_PER_LS(0x4C), &gsbi4_qup_clk.c }, + { TEST_PER_LS(0x4D), &gsbi5_p_clk.c }, + { TEST_PER_LS(0x4E), &gsbi5_uart_clk.c }, + { TEST_PER_LS(0x50), &gsbi5_qup_clk.c }, + { TEST_PER_LS(0x51), &gsbi6_p_clk.c }, + { TEST_PER_LS(0x52), &gsbi6_uart_clk.c }, + { TEST_PER_LS(0x54), &gsbi6_qup_clk.c }, + { TEST_PER_LS(0x55), &gsbi7_p_clk.c }, + { TEST_PER_LS(0x56), &gsbi7_uart_clk.c }, + { TEST_PER_LS(0x58), &gsbi7_qup_clk.c }, + { TEST_PER_LS(0x59), &gsbi8_p_clk.c }, + { TEST_PER_LS(0x5A), &gsbi8_uart_clk.c }, + { TEST_PER_LS(0x5C), &gsbi8_qup_clk.c }, + { TEST_PER_LS(0x5D), &gsbi9_p_clk.c }, + { TEST_PER_LS(0x5E), &gsbi9_uart_clk.c }, + { TEST_PER_LS(0x60), &gsbi9_qup_clk.c }, + { TEST_PER_LS(0x61), &gsbi10_p_clk.c }, + { TEST_PER_LS(0x62), &gsbi10_uart_clk.c }, + { TEST_PER_LS(0x64), &gsbi10_qup_clk.c }, + { TEST_PER_LS(0x65), &gsbi11_p_clk.c }, + { TEST_PER_LS(0x66), &gsbi11_uart_clk.c }, + { TEST_PER_LS(0x68), &gsbi11_qup_clk.c }, + { TEST_PER_LS(0x69), &gsbi12_p_clk.c }, + { TEST_PER_LS(0x6A), &gsbi12_uart_clk.c }, + { TEST_PER_LS(0x6C), &gsbi12_qup_clk.c }, + { TEST_PER_LS(0x78), &sfpb_clk.c }, + { TEST_PER_LS(0x78), &sfpb_a_clk.c }, + { TEST_PER_LS(0x7A), &pmic_ssbi2_clk.c }, + { TEST_PER_LS(0x7B), &pmic_arb0_p_clk.c }, + { TEST_PER_LS(0x7C), &pmic_arb1_p_clk.c }, + { TEST_PER_LS(0x7D), &prng_clk.c }, + { TEST_PER_LS(0x7F), &rpm_msg_ram_p_clk.c }, + { TEST_PER_LS(0x80), &adm0_p_clk.c }, + { TEST_PER_LS(0x81), &adm1_p_clk.c }, + { TEST_PER_LS(0x84), &usb_hs1_p_clk.c }, + { TEST_PER_LS(0x85), &usb_hs1_xcvr_clk.c }, + { TEST_PER_LS(0x89), &usb_fs1_p_clk.c }, + { TEST_PER_LS(0x8A), &usb_fs1_sys_clk.c }, + { TEST_PER_LS(0x8B), &usb_fs1_xcvr_clk.c }, + { TEST_PER_LS(0x8C), &usb_fs2_p_clk.c }, + { TEST_PER_LS(0x8D), &usb_fs2_sys_clk.c }, + { TEST_PER_LS(0x8E), &usb_fs2_xcvr_clk.c }, + { TEST_PER_LS(0x8F), &tsif_p_clk.c }, + { TEST_PER_LS(0x91), &tsif_ref_clk.c }, + { TEST_PER_LS(0x93), &ce2_p_clk.c }, + { TEST_PER_LS(0x94), &tssc_clk.c }, + + { TEST_PER_HS(0x07), &afab_clk.c }, + { TEST_PER_HS(0x07), &afab_a_clk.c }, + { TEST_PER_HS(0x18), &sfab_clk.c }, + { TEST_PER_HS(0x18), &sfab_a_clk.c }, + { TEST_PER_HS(0x2A), &adm0_clk.c }, + { TEST_PER_HS(0x2B), &adm1_clk.c }, + { TEST_PER_HS(0x34), &ebi1_clk.c }, + { TEST_PER_HS(0x34), &ebi1_a_clk.c }, + + { TEST_MM_LS(0x00), &dsi_byte_clk.c }, + { TEST_MM_LS(0x01), &pixel_lcdc_clk.c }, + { TEST_MM_LS(0x04), &pixel_mdp_clk.c }, + { TEST_MM_LS(0x06), &_p_clk.c }, + { TEST_MM_LS(0x07), &csi0_p_clk.c }, + { TEST_MM_LS(0x08), &csi1_p_clk.c }, + { TEST_MM_LS(0x09), &dsi_m_p_clk.c }, + { TEST_MM_LS(0x0A), &dsi_s_p_clk.c }, + { TEST_MM_LS(0x0C), &gfx2d0_p_clk.c }, + { TEST_MM_LS(0x0D), &gfx2d1_p_clk.c }, + { TEST_MM_LS(0x0E), &gfx3d_p_clk.c }, + { TEST_MM_LS(0x0F), &hdmi_m_p_clk.c }, + { TEST_MM_LS(0x10), &hdmi_s_p_clk.c }, + { TEST_MM_LS(0x11), &ijpeg_p_clk.c }, + { TEST_MM_LS(0x12), &imem_p_clk.c }, + { TEST_MM_LS(0x13), &jpegd_p_clk.c }, + { TEST_MM_LS(0x14), &mdp_p_clk.c }, + { TEST_MM_LS(0x16), &rot_p_clk.c }, + { TEST_MM_LS(0x18), &smmu_p_clk.c }, + { TEST_MM_LS(0x19), &tv_enc_p_clk.c }, + { TEST_MM_LS(0x1A), &vcodec_p_clk.c }, + { TEST_MM_LS(0x1B), &vfe_p_clk.c }, + { TEST_MM_LS(0x1C), &vpe_p_clk.c }, + { TEST_MM_LS(0x1D), &cam_clk.c }, + { TEST_MM_LS(0x1F), &hdmi_app_clk.c }, + { TEST_MM_LS(0x20), &mdp_vsync_clk.c }, + { TEST_MM_LS(0x21), &tv_dac_clk.c }, + { TEST_MM_LS(0x22), &tv_enc_clk.c }, + { TEST_MM_LS(0x23), &dsi_esc_clk.c }, + { TEST_MM_LS(0x25), &mmfpb_clk.c }, + { TEST_MM_LS(0x25), &mmfpb_a_clk.c }, + + { TEST_MM_HS(0x00), &csi0_clk.c }, + { TEST_MM_HS(0x01), &csi1_clk.c }, + { TEST_MM_HS(0x03), &csi0_vfe_clk.c }, + { TEST_MM_HS(0x04), &csi1_vfe_clk.c }, + { TEST_MM_HS(0x05), &ijpeg_clk.c }, + { TEST_MM_HS(0x06), &vfe_clk.c }, + { TEST_MM_HS(0x07), &gfx2d0_clk.c }, + { TEST_MM_HS(0x08), &gfx2d1_clk.c }, + { TEST_MM_HS(0x09), &gfx3d_clk.c }, + { TEST_MM_HS(0x0A), &jpegd_clk.c }, + { TEST_MM_HS(0x0B), &vcodec_clk.c }, + { TEST_MM_HS(0x0F), &mmfab_clk.c }, + { TEST_MM_HS(0x0F), &mmfab_a_clk.c }, + { TEST_MM_HS(0x11), &gmem_axi_clk.c }, + { TEST_MM_HS(0x12), &ijpeg_axi_clk.c }, + { TEST_MM_HS(0x13), &imem_axi_clk.c }, + { TEST_MM_HS(0x14), &jpegd_axi_clk.c }, + { TEST_MM_HS(0x15), &mdp_axi_clk.c }, + { TEST_MM_HS(0x17), &vcodec_axi_clk.c }, + { TEST_MM_HS(0x18), &vfe_axi_clk.c }, + { TEST_MM_HS(0x1A), &mdp_clk.c }, + { TEST_MM_HS(0x1B), &rot_clk.c }, + { TEST_MM_HS(0x1C), &vpe_clk.c }, + { TEST_MM_HS(0x1E), &hdmi_tv_clk.c }, + { TEST_MM_HS(0x1F), &mdp_tv_clk.c }, + + { TEST_MM_HS2X(0x24), &smi_clk.c }, + { TEST_MM_HS2X(0x24), &smi_a_clk.c }, + + { TEST_LPA(0x0A), &mi2s_osr_clk.c }, + { TEST_LPA(0x0B), &mi2s_bit_clk.c }, + { TEST_LPA(0x0C), &codec_i2s_mic_osr_clk.c }, + { TEST_LPA(0x0D), &codec_i2s_mic_bit_clk.c }, + { TEST_LPA(0x0E), &codec_i2s_spkr_osr_clk.c }, + { TEST_LPA(0x0F), &codec_i2s_spkr_bit_clk.c }, + { TEST_LPA(0x10), &spare_i2s_mic_osr_clk.c }, + { TEST_LPA(0x11), &spare_i2s_mic_bit_clk.c }, + { TEST_LPA(0x12), &spare_i2s_spkr_osr_clk.c }, + { TEST_LPA(0x13), &spare_i2s_spkr_bit_clk.c }, + { TEST_LPA(0x14), &pcm_clk.c }, + + { TEST_SC(0x40), &sc0_m_clk }, + { TEST_SC(0x41), &sc1_m_clk }, + { TEST_SC(0x42), &l2_m_clk }, +}; + +static struct measure_sel *find_measure_sel(struct clk *clk) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(measure_mux); i++) + if (measure_mux[i].clk == clk) + return &measure_mux[i]; + return NULL; +} + +static int measure_clk_set_parent(struct clk *c, struct clk *parent) +{ + int ret = 0; + u32 clk_sel; + struct measure_sel *p; + struct measure_clk *clk = to_measure_clk(c); + unsigned long flags; + + if (!parent) + return -EINVAL; + + p = find_measure_sel(parent); + if (!p) + return -EINVAL; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + /* + * Program the test vector, measurement period (sample_ticks) + * and scaling factors (multiplier, divider). + */ + clk_sel = p->test_vector & TEST_CLK_SEL_MASK; + clk->sample_ticks = 0x10000; + clk->multiplier = 1; + clk->divider = 1; + switch (p->test_vector >> TEST_TYPE_SHIFT) { + case TEST_TYPE_PER_LS: + writel_relaxed(0x4030D00|BVAL(7, 0, clk_sel), CLK_TEST_REG); + break; + case TEST_TYPE_PER_HS: + writel_relaxed(0x4020000|BVAL(16, 10, clk_sel), CLK_TEST_REG); + break; + case TEST_TYPE_MM_LS: + writel_relaxed(0x4030D97, CLK_TEST_REG); + writel_relaxed(BVAL(6, 1, clk_sel)|BIT(0), DBG_CFG_REG_LS_REG); + break; + case TEST_TYPE_MM_HS2X: + clk->divider = 2; + case TEST_TYPE_MM_HS: + writel_relaxed(0x402B800, CLK_TEST_REG); + writel_relaxed(BVAL(6, 1, clk_sel)|BIT(0), DBG_CFG_REG_HS_REG); + break; + case TEST_TYPE_LPA: + writel_relaxed(0x4030D98, CLK_TEST_REG); + writel_relaxed(BVAL(6, 1, clk_sel)|BIT(0), + LCC_CLK_LS_DEBUG_CFG_REG); + break; + case TEST_TYPE_SC: + writel_relaxed(0x5020000|BVAL(16, 10, clk_sel), CLK_TEST_REG); + clk->sample_ticks = 0x4000; + clk->multiplier = 2; + break; + default: + ret = -EPERM; + } + /* Make sure test vector is set before starting measurements. */ + mb(); + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return ret; +} + +/* Sample clock for 'ticks' reference clock ticks. */ +static u32 run_measurement(unsigned ticks) +{ + /* Stop counters and set the XO4 counter start value. */ + writel_relaxed(0x0, RINGOSC_TCXO_CTL_REG); + writel_relaxed(ticks, RINGOSC_TCXO_CTL_REG); + + /* Wait for timer to become ready. */ + while ((readl_relaxed(RINGOSC_STATUS_REG) & BIT(25)) != 0) + cpu_relax(); + + /* Run measurement and wait for completion. */ + writel_relaxed(BIT(20)|ticks, RINGOSC_TCXO_CTL_REG); + while ((readl_relaxed(RINGOSC_STATUS_REG) & BIT(25)) == 0) + cpu_relax(); + + /* Stop counters. */ + writel_relaxed(0x0, RINGOSC_TCXO_CTL_REG); + + /* Return measured ticks. */ + return readl_relaxed(RINGOSC_STATUS_REG) & BM(24, 0); +} + +/* Perform a hardware rate measurement for a given clock. + FOR DEBUG USE ONLY: Measurements take ~15 ms! */ +static unsigned measure_clk_get_rate(struct clk *c) +{ + unsigned long flags; + u32 pdm_reg_backup, ringosc_reg_backup; + u64 raw_count_short, raw_count_full; + struct measure_clk *clk = to_measure_clk(c); + unsigned ret; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + /* Enable CXO/4 and RINGOSC branch and root. */ + pdm_reg_backup = readl_relaxed(PDM_CLK_NS_REG); + ringosc_reg_backup = readl_relaxed(RINGOSC_NS_REG); + writel_relaxed(0x2898, PDM_CLK_NS_REG); + writel_relaxed(0xA00, RINGOSC_NS_REG); + + /* + * The ring oscillator counter will not reset if the measured clock + * is not running. To detect this, run a short measurement before + * the full measurement. If the raw results of the two are the same + * then the clock must be off. + */ + + /* Run a short measurement. (~1 ms) */ + raw_count_short = run_measurement(0x1000); + /* Run a full measurement. (~14 ms) */ + raw_count_full = run_measurement(clk->sample_ticks); + + writel_relaxed(ringosc_reg_backup, RINGOSC_NS_REG); + writel_relaxed(pdm_reg_backup, PDM_CLK_NS_REG); + + /* Return 0 if the clock is off. */ + if (raw_count_full == raw_count_short) + ret = 0; + else { + /* Compute rate in Hz. */ + raw_count_full = ((raw_count_full * 10) + 15) * 4800000; + do_div(raw_count_full, + (((clk->sample_ticks * 10) + 35) * clk->divider)); + ret = (raw_count_full * clk->multiplier); + } + + /* Route dbg_hs_clk to PLLTEST. 300mV single-ended amplitude. */ + writel_relaxed(0x3CF8, PLLTEST_PAD_CFG_REG); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return ret; +} +#else /* !CONFIG_DEBUG_FS */ +static int measure_clk_set_parent(struct clk *clk, struct clk *parent) +{ + return -EINVAL; +} + +static unsigned measure_clk_get_rate(struct clk *clk) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static struct clk_ops measure_clk_ops = { + .set_parent = measure_clk_set_parent, + .get_rate = measure_clk_get_rate, + .is_local = local_clk_is_local, +}; + +static struct measure_clk measure_clk = { + .c = { + .dbg_name = "measure_clk", + .ops = &measure_clk_ops, + CLK_INIT(measure_clk.c), + }, + .multiplier = 1, + .divider = 1, +}; + +static struct clk_lookup msm_clocks_8x60[] = { + CLK_LOOKUP("cxo", cxo_clk.c, NULL), + CLK_LOOKUP("pll4", pll4_clk.c, NULL), + CLK_LOOKUP("pll4", pll4_clk.c, "peripheral-reset"), + CLK_LOOKUP("measure", measure_clk.c, "debug"), + + CLK_LOOKUP("afab_clk", afab_clk.c, NULL), + CLK_LOOKUP("afab_a_clk", afab_a_clk.c, NULL), + CLK_LOOKUP("cfpb_clk", cfpb_clk.c, NULL), + CLK_LOOKUP("cfpb_a_clk", cfpb_a_clk.c, NULL), + CLK_LOOKUP("dfab_clk", dfab_clk.c, NULL), + CLK_LOOKUP("dfab_a_clk", dfab_a_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("ebi1_a_clk", ebi1_a_clk.c, NULL), + CLK_LOOKUP("mmfab_clk", mmfab_clk.c, NULL), + CLK_LOOKUP("mmfab_a_clk", mmfab_a_clk.c, NULL), + CLK_LOOKUP("mmfpb_clk", mmfpb_clk.c, NULL), + CLK_LOOKUP("mmfpb_a_clk", mmfpb_a_clk.c, NULL), + CLK_LOOKUP("sfab_clk", sfab_clk.c, NULL), + CLK_LOOKUP("sfab_a_clk", sfab_a_clk.c, NULL), + CLK_LOOKUP("sfpb_clk", sfpb_clk.c, NULL), + CLK_LOOKUP("sfpb_a_clk", sfpb_a_clk.c, NULL), + CLK_LOOKUP("smi_clk", smi_clk.c, NULL), + CLK_LOOKUP("smi_a_clk", smi_a_clk.c, NULL), + + CLK_LOOKUP("gsbi_uart_clk", gsbi1_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi2_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi3_uart_clk.c, "msm_serial_hsl.2"), + CLK_LOOKUP("gsbi_uart_clk", gsbi4_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi5_uart_clk.c, NULL), + CLK_LOOKUP("uartdm_clk", gsbi6_uart_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("gsbi_uart_clk", gsbi7_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi8_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi9_uart_clk.c, "msm_serial_hsl.1"), + CLK_LOOKUP("gsbi_uart_clk", gsbi10_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi11_uart_clk.c, NULL), + CLK_LOOKUP("gsbi_uart_clk", gsbi12_uart_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("spi_clk", gsbi1_qup_clk.c, "spi_qsd.0"), + CLK_LOOKUP("gsbi_qup_clk", gsbi2_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi3_qup_clk.c, "qup_i2c.0"), + CLK_LOOKUP("gsbi_qup_clk", gsbi4_qup_clk.c, "qup_i2c.1"), + CLK_LOOKUP("gsbi_qup_clk", gsbi5_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi6_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi7_qup_clk.c, "qup_i2c.4"), + CLK_LOOKUP("gsbi_qup_clk", gsbi8_qup_clk.c, "qup_i2c.3"), + CLK_LOOKUP("gsbi_qup_clk", gsbi9_qup_clk.c, "qup_i2c.2"), + CLK_LOOKUP("spi_clk", gsbi10_qup_clk.c, "spi_qsd.1"), + CLK_LOOKUP("gsbi_qup_clk", gsbi11_qup_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi12_qup_clk.c, "msm_dsps.0"), + CLK_LOOKUP("gsbi_qup_clk", gsbi12_qup_clk.c, "qup_i2c.5"), + CLK_LOOKUP("pdm_clk", pdm_clk.c, NULL), + CLK_LOOKUP("pmem_clk", pmem_clk.c, NULL), + CLK_LOOKUP("prng_clk", prng_clk.c, NULL), + CLK_LOOKUP("sdc_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("sdc_clk", sdc5_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("tsif_ref_clk", tsif_ref_clk.c, NULL), + CLK_LOOKUP("tssc_clk", tssc_clk.c, NULL), + CLK_LOOKUP("usb_hs_clk", usb_hs1_xcvr_clk.c, NULL), + CLK_LOOKUP("usb_phy_clk", usb_phy0_clk.c, NULL), + CLK_LOOKUP("usb_fs_clk", usb_fs1_xcvr_clk.c, NULL), + CLK_LOOKUP("usb_fs_sys_clk", usb_fs1_sys_clk.c, NULL), + CLK_LOOKUP("usb_fs_src_clk", usb_fs1_src_clk.c, NULL), + CLK_LOOKUP("usb_fs_clk", usb_fs2_xcvr_clk.c, NULL), + CLK_LOOKUP("usb_fs_sys_clk", usb_fs2_sys_clk.c, NULL), + CLK_LOOKUP("usb_fs_src_clk", usb_fs2_src_clk.c, NULL), + CLK_LOOKUP("ce_clk", ce2_p_clk.c, NULL), + CLK_LOOKUP("spi_pclk", gsbi1_p_clk.c, "spi_qsd.0"), + CLK_LOOKUP("gsbi_pclk", gsbi2_p_clk.c, NULL), + CLK_LOOKUP("gsbi_pclk", gsbi3_p_clk.c, "msm_serial_hsl.2"), + CLK_LOOKUP("gsbi_pclk", gsbi3_p_clk.c, "qup_i2c.0"), + CLK_LOOKUP("gsbi_pclk", gsbi4_p_clk.c, "qup_i2c.1"), + CLK_LOOKUP("gsbi_pclk", gsbi5_p_clk.c, NULL), + CLK_LOOKUP("uartdm_pclk", gsbi6_p_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("gsbi_pclk", gsbi7_p_clk.c, "qup_i2c.4"), + CLK_LOOKUP("gsbi_pclk", gsbi8_p_clk.c, "qup_i2c.3"), + CLK_LOOKUP("gsbi_pclk", gsbi9_p_clk.c, "msm_serial_hsl.1"), + CLK_LOOKUP("gsbi_pclk", gsbi9_p_clk.c, "qup_i2c.2"), + CLK_LOOKUP("spi_pclk", gsbi10_p_clk.c, "spi_qsd.1"), + CLK_LOOKUP("gsbi_pclk", gsbi11_p_clk.c, NULL), + CLK_LOOKUP("gsbi_pclk", gsbi12_p_clk.c, "msm_dsps.0"), + CLK_LOOKUP("gsbi_pclk", gsbi12_p_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("gsbi_pclk", gsbi12_p_clk.c, "qup_i2c.5"), + CLK_LOOKUP("ppss_pclk", ppss_p_clk.c, NULL), + CLK_LOOKUP("tsif_pclk", tsif_p_clk.c, NULL), + CLK_LOOKUP("usb_fs_pclk", usb_fs1_p_clk.c, NULL), + CLK_LOOKUP("usb_fs_pclk", usb_fs2_p_clk.c, NULL), + CLK_LOOKUP("usb_hs_pclk", usb_hs1_p_clk.c, NULL), + CLK_LOOKUP("sdc_pclk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_pclk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_pclk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_pclk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("sdc_pclk", sdc5_p_clk.c, "msm_sdcc.5"), + CLK_LOOKUP("adm_clk", adm0_clk.c, "msm_dmov.0"), + CLK_LOOKUP("adm_pclk", adm0_p_clk.c, "msm_dmov.0"), + CLK_LOOKUP("adm_clk", adm1_clk.c, "msm_dmov.1"), + CLK_LOOKUP("adm_pclk", adm1_p_clk.c, "msm_dmov.1"), + CLK_LOOKUP("modem_ahb1_pclk", modem_ahb1_p_clk.c, NULL), + CLK_LOOKUP("modem_ahb2_pclk", modem_ahb2_p_clk.c, NULL), + CLK_LOOKUP("pmic_arb_pclk", pmic_arb0_p_clk.c, NULL), + CLK_LOOKUP("pmic_arb_pclk", pmic_arb1_p_clk.c, NULL), + CLK_LOOKUP("pmic_ssbi2", pmic_ssbi2_clk.c, NULL), + CLK_LOOKUP("rpm_msg_ram_pclk", rpm_msg_ram_p_clk.c, NULL), + CLK_LOOKUP("amp_clk", amp_clk.c, NULL), + CLK_LOOKUP("cam_clk", cam_clk.c, NULL), + CLK_LOOKUP("csi_clk", csi0_clk.c, NULL), + CLK_LOOKUP("csi_clk", csi1_clk.c, "msm_camera_ov7692.0"), + CLK_LOOKUP("csi_clk", csi1_clk.c, "msm_camera_ov9726.0"), + CLK_LOOKUP("csi_src_clk", csi_src_clk.c, NULL), + CLK_LOOKUP("dsi_byte_div_clk", dsi_byte_clk.c, NULL), + CLK_LOOKUP("dsi_esc_clk", dsi_esc_clk.c, NULL), + CLK_LOOKUP("gfx2d0_clk", gfx2d0_clk.c, NULL), + CLK_LOOKUP("gfx2d1_clk", gfx2d1_clk.c, NULL), + CLK_LOOKUP("gfx3d_clk", gfx3d_clk.c, NULL), + CLK_LOOKUP("ijpeg_clk", ijpeg_clk.c, NULL), + CLK_LOOKUP("jpegd_clk", jpegd_clk.c, NULL), + CLK_LOOKUP("mdp_clk", mdp_clk.c, NULL), + CLK_LOOKUP("mdp_vsync_clk", mdp_vsync_clk.c, NULL), + CLK_LOOKUP("pixel_lcdc_clk", pixel_lcdc_clk.c, NULL), + CLK_LOOKUP("pixel_mdp_clk", pixel_mdp_clk.c, NULL), + CLK_LOOKUP("rot_clk", rot_clk.c, NULL), + CLK_LOOKUP("tv_enc_clk", tv_enc_clk.c, NULL), + CLK_LOOKUP("tv_dac_clk", tv_dac_clk.c, NULL), + CLK_LOOKUP("vcodec_clk", vcodec_clk.c, NULL), + CLK_LOOKUP("mdp_tv_clk", mdp_tv_clk.c, NULL), + CLK_LOOKUP("hdmi_clk", hdmi_tv_clk.c, NULL), + CLK_LOOKUP("tv_src_clk", tv_src_clk.c, NULL), + CLK_LOOKUP("hdmi_app_clk", hdmi_app_clk.c, NULL), + CLK_LOOKUP("vpe_clk", vpe_clk.c, NULL), + CLK_LOOKUP("csi_vfe_clk", csi0_vfe_clk.c, NULL), + CLK_LOOKUP("csi_vfe_clk", csi1_vfe_clk.c, "msm_camera_ov7692.0"), + CLK_LOOKUP("csi_vfe_clk", csi1_vfe_clk.c, "msm_camera_ov9726.0"), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("smmu_jpegd_clk", jpegd_axi_clk.c, NULL), + CLK_LOOKUP("smmu_vfe_clk", vfe_axi_clk.c, NULL), + CLK_LOOKUP("vfe_axi_clk", vfe_axi_clk.c, NULL), + CLK_LOOKUP("ijpeg_axi_clk", ijpeg_axi_clk.c, NULL), + CLK_LOOKUP("imem_axi_clk", imem_axi_clk.c, NULL), + CLK_LOOKUP("mdp_axi_clk", mdp_axi_clk.c, NULL), + CLK_LOOKUP("rot_axi_clk", rot_axi_clk.c, NULL), + CLK_LOOKUP("vcodec_axi_clk", vcodec_axi_clk.c, NULL), + CLK_LOOKUP("vpe_axi_clk", vpe_axi_clk.c, NULL), + CLK_LOOKUP("amp_pclk", amp_p_clk.c, NULL), + CLK_LOOKUP("csi_pclk", csi0_p_clk.c, NULL), + CLK_LOOKUP("csi_pclk", csi1_p_clk.c, "msm_camera_ov7692.0"), + CLK_LOOKUP("csi_pclk", csi1_p_clk.c, "msm_camera_ov9726.0"), + CLK_LOOKUP("dsi_m_pclk", dsi_m_p_clk.c, NULL), + CLK_LOOKUP("dsi_s_pclk", dsi_s_p_clk.c, NULL), + CLK_LOOKUP("gfx2d0_pclk", gfx2d0_p_clk.c, NULL), + CLK_LOOKUP("gfx2d1_pclk", gfx2d1_p_clk.c, NULL), + CLK_LOOKUP("gfx3d_pclk", gfx3d_p_clk.c, NULL), + CLK_LOOKUP("hdmi_m_pclk", hdmi_m_p_clk.c, NULL), + CLK_LOOKUP("hdmi_s_pclk", hdmi_s_p_clk.c, NULL), + CLK_LOOKUP("ijpeg_pclk", ijpeg_p_clk.c, NULL), + CLK_LOOKUP("jpegd_pclk", jpegd_p_clk.c, NULL), + CLK_LOOKUP("imem_pclk", imem_p_clk.c, NULL), + CLK_LOOKUP("mdp_pclk", mdp_p_clk.c, NULL), + CLK_LOOKUP("smmu_pclk", smmu_p_clk.c, NULL), + CLK_LOOKUP("rotator_pclk", rot_p_clk.c, NULL), + CLK_LOOKUP("tv_enc_pclk", tv_enc_p_clk.c, NULL), + CLK_LOOKUP("vcodec_pclk", vcodec_p_clk.c, NULL), + CLK_LOOKUP("vfe_pclk", vfe_p_clk.c, NULL), + CLK_LOOKUP("vpe_pclk", vpe_p_clk.c, NULL), + CLK_LOOKUP("mi2s_osr_clk", mi2s_osr_clk.c, NULL), + CLK_LOOKUP("mi2s_bit_clk", mi2s_bit_clk.c, NULL), + CLK_LOOKUP("i2s_mic_osr_clk", codec_i2s_mic_osr_clk.c, NULL), + CLK_LOOKUP("i2s_mic_bit_clk", codec_i2s_mic_bit_clk.c, NULL), + CLK_LOOKUP("i2s_mic_osr_clk", spare_i2s_mic_osr_clk.c, NULL), + CLK_LOOKUP("i2s_mic_bit_clk", spare_i2s_mic_bit_clk.c, NULL), + CLK_LOOKUP("i2s_spkr_osr_clk", codec_i2s_spkr_osr_clk.c, NULL), + CLK_LOOKUP("i2s_spkr_bit_clk", codec_i2s_spkr_bit_clk.c, NULL), + CLK_LOOKUP("i2s_spkr_osr_clk", spare_i2s_spkr_osr_clk.c, NULL), + CLK_LOOKUP("i2s_spkr_bit_clk", spare_i2s_spkr_bit_clk.c, NULL), + CLK_LOOKUP("pcm_clk", pcm_clk.c, NULL), + CLK_LOOKUP("iommu_clk", jpegd_axi_clk.c, "msm_iommu.0"), + CLK_LOOKUP("iommu_clk", mdp_axi_clk.c, "msm_iommu.2"), + CLK_LOOKUP("iommu_clk", mdp_axi_clk.c, "msm_iommu.3"), + CLK_LOOKUP("iommu_clk", ijpeg_axi_clk.c, "msm_iommu.5"), + CLK_LOOKUP("iommu_clk", vfe_axi_clk.c, "msm_iommu.6"), + CLK_LOOKUP("iommu_clk", vcodec_axi_clk.c, "msm_iommu.7"), + CLK_LOOKUP("iommu_clk", vcodec_axi_clk.c, "msm_iommu.8"), + CLK_LOOKUP("iommu_clk", gfx3d_clk.c, "msm_iommu.9"), + CLK_LOOKUP("iommu_clk", gfx2d0_clk.c, "msm_iommu.10"), + CLK_LOOKUP("iommu_clk", gfx2d1_clk.c, "msm_iommu.11"), + + CLK_LOOKUP("dfab_dsps_clk", dfab_dsps_clk.c, NULL), + CLK_LOOKUP("dfab_usb_hs_clk", dfab_usb_hs_clk.c, NULL), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("dfab_sdc_clk", dfab_sdc5_clk.c, "msm_sdcc.5"), + + CLK_LOOKUP("ebi1_msmbus_clk", ebi1_msmbus_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_adm0_clk.c, "msm_dmov.0"), + CLK_LOOKUP("ebi1_clk", ebi1_adm1_clk.c, "msm_dmov.1"), + + CLK_LOOKUP("sc0_mclk", sc0_m_clk, NULL), + CLK_LOOKUP("sc1_mclk", sc1_m_clk, NULL), + CLK_LOOKUP("l2_mclk", l2_m_clk, NULL), +}; + +/* + * Miscellaneous clock register initializations + */ + +/* Read, modify, then write-back a register. */ +static void __init rmwreg(uint32_t val, void *reg, uint32_t mask) +{ + uint32_t regval = readl_relaxed(reg); + regval &= ~mask; + regval |= val; + writel_relaxed(regval, reg); +} + +static void __init reg_init(void) +{ + /* Setup MM_PLL2 (PLL3), but turn it off. Rate set by set_rate_tv(). */ + rmwreg(0, MM_PLL2_MODE_REG, BIT(0)); /* Disable output */ + /* Set ref, bypass, assert reset, disable output, disable test mode */ + writel_relaxed(0, MM_PLL2_MODE_REG); /* PXO */ + writel_relaxed(0x00800000, MM_PLL2_CONFIG_REG); /* Enable main out. */ + + /* The clock driver doesn't use SC1's voting register to control + * HW-voteable clocks. Clear its bits so that disabling bits in the + * SC0 register will cause the corresponding clocks to be disabled. */ + rmwreg(BIT(12)|BIT(11), SC0_U_CLK_BRANCH_ENA_VOTE_REG, BM(12, 11)); + writel_relaxed(BIT(12)|BIT(11), SC1_U_CLK_BRANCH_ENA_VOTE_REG); + /* Let sc_aclk and sc_clk halt when both Scorpions are collapsed. */ + writel_relaxed(BIT(12)|BIT(11), SC0_U_CLK_SLEEP_ENA_VOTE_REG); + writel_relaxed(BIT(12)|BIT(11), SC1_U_CLK_SLEEP_ENA_VOTE_REG); + + /* Deassert MM SW_RESET_ALL signal. */ + writel_relaxed(0, SW_RESET_ALL_REG); + + /* Initialize MM AHB registers: Enable the FPB clock and disable HW + * gating for all clocks. Also set VFE_AHB's FORCE_CORE_ON bit to + * prevent its memory from being collapsed when the clock is halted. + * The sleep and wake-up delays are set to safe values. */ + rmwreg(0x00000003, AHB_EN_REG, 0x0F7FFFFF); + rmwreg(0x000007F9, AHB_EN2_REG, 0x7FFFBFFF); + + /* Deassert all locally-owned MM AHB resets. */ + rmwreg(0, SW_RESET_AHB_REG, 0xFFF7DFFF); + + /* Initialize MM AXI registers: Enable HW gating for all clocks that + * support it. Also set FORCE_CORE_ON bits, and any sleep and wake-up + * delays to safe values. */ + rmwreg(0x000207F9, MAXI_EN_REG, 0x0FFFFFFF); + /* MAXI_EN2_REG is owned by the RPM. Don't touch it. */ + writel_relaxed(0x3FE7FCFF, MAXI_EN3_REG); + writel_relaxed(0x000001D8, SAXI_EN_REG); + + /* Initialize MM CC registers: Set MM FORCE_CORE_ON bits so that core + * memories retain state even when not clocked. Also, set sleep and + * wake-up delays to safe values. */ + writel_relaxed(0x00000000, CSI_CC_REG); + rmwreg(0x00000000, MISC_CC_REG, 0xFEFFF3FF); + rmwreg(0x000007FD, MISC_CC2_REG, 0xFFFF7FFF); + writel_relaxed(0x80FF0000, GFX2D0_CC_REG); + writel_relaxed(0x80FF0000, GFX2D1_CC_REG); + writel_relaxed(0x80FF0000, GFX3D_CC_REG); + writel_relaxed(0x80FF0000, IJPEG_CC_REG); + writel_relaxed(0x80FF0000, JPEGD_CC_REG); + /* MDP and PIXEL clocks may be running at boot, don't turn them off. */ + rmwreg(0x80FF0000, MDP_CC_REG, BM(31, 29) | BM(23, 16)); + rmwreg(0x80FF0000, PIXEL_CC_REG, BM(31, 29) | BM(23, 16)); + writel_relaxed(0x000004FF, PIXEL_CC2_REG); + writel_relaxed(0x80FF0000, ROT_CC_REG); + writel_relaxed(0x80FF0000, TV_CC_REG); + writel_relaxed(0x000004FF, TV_CC2_REG); + writel_relaxed(0xC0FF0000, VCODEC_CC_REG); + writel_relaxed(0x80FF0000, VFE_CC_REG); + writel_relaxed(0x80FF0000, VPE_CC_REG); + + /* De-assert MM AXI resets to all hardware blocks. */ + writel_relaxed(0, SW_RESET_AXI_REG); + + /* Deassert all MM core resets. */ + writel_relaxed(0, SW_RESET_CORE_REG); + + /* Reset 3D core once more, with its clock enabled. This can + * eventually be done as part of the GDFS footswitch driver. */ + clk_set_rate(&gfx3d_clk.c, 27000000); + clk_enable(&gfx3d_clk.c); + writel_relaxed(BIT(12), SW_RESET_CORE_REG); + mb(); + udelay(5); + writel_relaxed(0, SW_RESET_CORE_REG); + /* Make sure reset is de-asserted before clock is disabled. */ + mb(); + clk_disable(&gfx3d_clk.c); + + /* Enable TSSC and PDM PXO sources. */ + writel_relaxed(BIT(11), TSSC_CLK_CTL_REG); + writel_relaxed(BIT(15), PDM_CLK_NS_REG); + /* Set the dsi_byte_clk src to the DSI PHY PLL, + * dsi_esc_clk to PXO/2, and the hdmi_app_clk src to PXO */ + rmwreg(0x400001, MISC_CC2_REG, 0x424003); +} + +/* Local clock driver initialization. */ +void __init msm8660_clock_init(void) +{ + soc_update_sys_vdd = msm8660_update_sys_vdd; + xo_pxo = msm_xo_get(MSM_XO_PXO, "clock-8x60"); + if (IS_ERR(xo_pxo)) { + pr_err("%s: msm_xo_get(PXO) failed.\n", __func__); + BUG(); + } + xo_cxo = msm_xo_get(MSM_XO_TCXO_D1, "clock-8x60"); + if (IS_ERR(xo_cxo)) { + pr_err("%s: msm_xo_get(CXO) failed.\n", __func__); + BUG(); + } + + local_vote_sys_vdd(HIGH); + /* Initialize clock registers. */ + reg_init(); + + /* Initialize rates for clocks that only support one. */ + clk_set_rate(&pdm_clk.c, 27000000); + clk_set_rate(&prng_clk.c, 64000000); + clk_set_rate(&mdp_vsync_clk.c, 27000000); + clk_set_rate(&tsif_ref_clk.c, 105000); + clk_set_rate(&tssc_clk.c, 27000000); + clk_set_rate(&usb_hs1_xcvr_clk.c, 60000000); + clk_set_rate(&usb_fs1_src_clk.c, 60000000); + clk_set_rate(&usb_fs2_src_clk.c, 60000000); + + /* The halt status bits for PDM and TSSC may be incorrect at boot. + * Toggle these clocks on and off to refresh them. */ + local_clk_enable(&pdm_clk.c); + local_clk_disable(&pdm_clk.c); + local_clk_enable(&tssc_clk.c); + local_clk_disable(&tssc_clk.c); + + msm_clock_init(msm_clocks_8x60, ARRAY_SIZE(msm_clocks_8x60)); +} + +static int __init msm_clk_soc_late_init(void) +{ + int rc; + + /* Vote for MMFPB to be at least 64MHz when an Apps CPU is active. */ + struct clk *mmfpb_a_clk = clk_get(NULL, "mmfpb_a_clk"); + if (WARN(IS_ERR(mmfpb_a_clk), "mmfpb_a_clk not found (%ld)\n", + PTR_ERR(mmfpb_a_clk))) + return PTR_ERR(mmfpb_a_clk); + rc = clk_set_min_rate(mmfpb_a_clk, 64000000); + if (WARN(rc, "mmfpb_a_clk rate was not set (%d)\n", rc)) + return rc; + rc = clk_enable(mmfpb_a_clk); + if (WARN(rc, "mmfpb_a_clk not enabled (%d)\n", rc)) + return rc; + + /* Remove temporary vote for HIGH vdd_dig. */ + rc = local_unvote_sys_vdd(HIGH); + WARN(rc, "local_unvote_sys_vdd(HIGH) failed (%d)\n", rc); + + return rc; +} +late_initcall(msm_clk_soc_late_init); diff --git a/arch/arm/mach-msm/clock-debug.c b/arch/arm/mach-msm/clock-debug.c index 4886404d42f..36bc124299a 100644 --- a/arch/arm/mach-msm/clock-debug.c +++ b/arch/arm/mach-msm/clock-debug.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2007-2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2007-2011, Code Aurora Forum. 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 @@ -17,7 +17,11 @@ #include #include #include +#include #include +#include +#include + #include "clock.h" static int clock_debug_rate_set(void *data, u64 val) @@ -27,15 +31,15 @@ static int clock_debug_rate_set(void *data, u64 val) /* Only increases to max rate will succeed, but that's actually good * for debugging purposes so we don't check for error. */ - if (clock->flags & CLK_MAX) + if (clock->flags & CLKFLAG_MAX) clk_set_max_rate(clock, val); - if (clock->flags & CLK_MIN) + if (clock->flags & CLKFLAG_MIN) ret = clk_set_min_rate(clock, val); else ret = clk_set_rate(clock, val); if (ret != 0) printk(KERN_ERR "clk_set%s_rate failed (%d)\n", - (clock->flags & CLK_MIN) ? "_min" : "", ret); + (clock->flags & CLKFLAG_MIN) ? "_min" : "", ret); return ret; } @@ -49,15 +53,32 @@ static int clock_debug_rate_get(void *data, u64 *val) DEFINE_SIMPLE_ATTRIBUTE(clock_rate_fops, clock_debug_rate_get, clock_debug_rate_set, "%llu\n"); +static struct clk *measure; + +static int clock_debug_measure_get(void *data, u64 *val) +{ + int ret; + struct clk *clock = data; + + ret = clk_set_parent(measure, clock); + if (!ret) + *val = clk_get_rate(measure); + + return ret; +} + +DEFINE_SIMPLE_ATTRIBUTE(clock_measure_fops, clock_debug_measure_get, + NULL, "%lld\n"); + static int clock_debug_enable_set(void *data, u64 val) { struct clk *clock = data; int rc = 0; if (val) - rc = clock->ops->enable(clock->id); + rc = clk_enable(clock); else - clock->ops->disable(clock->id); + clk_disable(clock); return rc; } @@ -65,20 +86,25 @@ static int clock_debug_enable_set(void *data, u64 val) static int clock_debug_enable_get(void *data, u64 *val) { struct clk *clock = data; + int enabled; - *val = clock->ops->is_enabled(clock->id); + if (clock->ops->is_enabled) + enabled = clock->ops->is_enabled(clock); + else + enabled = !!(clock->count); + *val = enabled; return 0; } DEFINE_SIMPLE_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get, - clock_debug_enable_set, "%llu\n"); + clock_debug_enable_set, "%lld\n"); static int clock_debug_local_get(void *data, u64 *val) { struct clk *clock = data; - *val = clock->ops->is_local(clock->id); + *val = clock->ops->is_local(clock); return 0; } @@ -87,15 +113,83 @@ DEFINE_SIMPLE_ATTRIBUTE(clock_local_fops, clock_debug_local_get, NULL, "%llu\n"); static struct dentry *debugfs_base; +static u32 debug_suspend; +static struct clk_lookup *msm_clocks; +static unsigned num_msm_clocks; -int __init clock_debug_init(void) +int __init clock_debug_init(struct clk_lookup *clocks, unsigned num_clocks) { + int ret = 0; + debugfs_base = debugfs_create_dir("clk", NULL); if (!debugfs_base) return -ENOMEM; + if (!debugfs_create_u32("debug_suspend", S_IRUGO | S_IWUSR, + debugfs_base, &debug_suspend)) { + debugfs_remove_recursive(debugfs_base); + return -ENOMEM; + } + msm_clocks = clocks; + num_msm_clocks = num_clocks; + + measure = clk_get_sys("debug", "measure"); + if (IS_ERR(measure)) { + ret = PTR_ERR(measure); + measure = NULL; + } + + return ret; +} + +void clock_debug_print_enabled(void) +{ + struct clk *clk; + unsigned i; + int cnt = 0; + + if (likely(!debug_suspend)) + return; + + pr_info("Enabled clocks:\n"); + for (i = 0; i < num_msm_clocks; i++) { + clk = msm_clocks[i].clk; + + if (clk && clk->ops->is_enabled(clk)) { + pr_info("\t%s\n", clk->dbg_name); + cnt++; + } + } + + if (cnt) + pr_info("Enabled clock count: %d\n", cnt); + else + pr_info("No clocks enabled.\n"); + +} + +static int list_rates_show(struct seq_file *m, void *unused) +{ + struct clk *clock = m->private; + int rate, i = 0; + + while ((rate = clock->ops->list_rate(clock, i++)) >= 0) + seq_printf(m, "%d\n", rate); + return 0; } +static int list_rates_open(struct inode *inode, struct file *file) +{ + return single_open(file, list_rates_show, inode->i_private); +} + +static const struct file_operations list_rates_fops = { + .open = list_rates_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + int __init clock_debug_add(struct clk *clock) { char temp[50], *ptr; @@ -123,6 +217,18 @@ int __init clock_debug_add(struct clk *clock) if (!debugfs_create_file("is_local", S_IRUGO, clk_dir, clock, &clock_local_fops)) goto error; + + if (measure && + !clk_set_parent(measure, clock) && + !debugfs_create_file("measure", S_IRUGO, clk_dir, clock, + &clock_measure_fops)) + goto error; + + if (clock->ops->list_rate) + if (!debugfs_create_file("list_rates", + S_IRUGO, clk_dir, clock, &list_rates_fops)) + goto error; + return 0; error: debugfs_remove_recursive(clk_dir); diff --git a/arch/arm/mach-msm/clock-dss-8960.c b/arch/arm/mach-msm/clock-dss-8960.c new file mode 100644 index 00000000000..7374f92d9bc --- /dev/null +++ b/arch/arm/mach-msm/clock-dss-8960.c @@ -0,0 +1,328 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include "clock-dss-8960.h" + +/* HDMI PLL macros */ +#define HDMI_PHY_PLL_REFCLK_CFG (MSM_HDMI_BASE + 0x00000500) +#define HDMI_PHY_PLL_CHRG_PUMP_CFG (MSM_HDMI_BASE + 0x00000504) +#define HDMI_PHY_PLL_LOOP_FLT_CFG0 (MSM_HDMI_BASE + 0x00000508) +#define HDMI_PHY_PLL_LOOP_FLT_CFG1 (MSM_HDMI_BASE + 0x0000050c) +#define HDMI_PHY_PLL_IDAC_ADJ_CFG (MSM_HDMI_BASE + 0x00000510) +#define HDMI_PHY_PLL_I_VI_KVCO_CFG (MSM_HDMI_BASE + 0x00000514) +#define HDMI_PHY_PLL_PWRDN_B (MSM_HDMI_BASE + 0x00000518) +#define HDMI_PHY_PLL_SDM_CFG0 (MSM_HDMI_BASE + 0x0000051c) +#define HDMI_PHY_PLL_SDM_CFG1 (MSM_HDMI_BASE + 0x00000520) +#define HDMI_PHY_PLL_SDM_CFG2 (MSM_HDMI_BASE + 0x00000524) +#define HDMI_PHY_PLL_SDM_CFG3 (MSM_HDMI_BASE + 0x00000528) +#define HDMI_PHY_PLL_SDM_CFG4 (MSM_HDMI_BASE + 0x0000052c) +#define HDMI_PHY_PLL_SSC_CFG0 (MSM_HDMI_BASE + 0x00000530) +#define HDMI_PHY_PLL_SSC_CFG1 (MSM_HDMI_BASE + 0x00000534) +#define HDMI_PHY_PLL_SSC_CFG2 (MSM_HDMI_BASE + 0x00000538) +#define HDMI_PHY_PLL_SSC_CFG3 (MSM_HDMI_BASE + 0x0000053c) +#define HDMI_PHY_PLL_LOCKDET_CFG0 (MSM_HDMI_BASE + 0x00000540) +#define HDMI_PHY_PLL_LOCKDET_CFG1 (MSM_HDMI_BASE + 0x00000544) +#define HDMI_PHY_PLL_LOCKDET_CFG2 (MSM_HDMI_BASE + 0x00000548) +#define HDMI_PHY_PLL_VCOCAL_CFG0 (MSM_HDMI_BASE + 0x0000054c) +#define HDMI_PHY_PLL_VCOCAL_CFG1 (MSM_HDMI_BASE + 0x00000550) +#define HDMI_PHY_PLL_VCOCAL_CFG2 (MSM_HDMI_BASE + 0x00000554) +#define HDMI_PHY_PLL_VCOCAL_CFG3 (MSM_HDMI_BASE + 0x00000558) +#define HDMI_PHY_PLL_VCOCAL_CFG4 (MSM_HDMI_BASE + 0x0000055c) +#define HDMI_PHY_PLL_VCOCAL_CFG5 (MSM_HDMI_BASE + 0x00000560) +#define HDMI_PHY_PLL_VCOCAL_CFG6 (MSM_HDMI_BASE + 0x00000564) +#define HDMI_PHY_PLL_VCOCAL_CFG7 (MSM_HDMI_BASE + 0x00000568) +#define HDMI_PHY_PLL_DEBUG_SEL (MSM_HDMI_BASE + 0x0000056c) +#define HDMI_PHY_PLL_MISC0 (MSM_HDMI_BASE + 0x00000570) +#define HDMI_PHY_PLL_MISC1 (MSM_HDMI_BASE + 0x00000574) +#define HDMI_PHY_PLL_MISC2 (MSM_HDMI_BASE + 0x00000578) +#define HDMI_PHY_PLL_MISC3 (MSM_HDMI_BASE + 0x0000057c) +#define HDMI_PHY_PLL_MISC4 (MSM_HDMI_BASE + 0x00000580) +#define HDMI_PHY_PLL_MISC5 (MSM_HDMI_BASE + 0x00000584) +#define HDMI_PHY_PLL_MISC6 (MSM_HDMI_BASE + 0x00000588) +#define HDMI_PHY_PLL_DEBUG_BUS0 (MSM_HDMI_BASE + 0x0000058c) +#define HDMI_PHY_PLL_DEBUG_BUS1 (MSM_HDMI_BASE + 0x00000590) +#define HDMI_PHY_PLL_DEBUG_BUS2 (MSM_HDMI_BASE + 0x00000594) +#define HDMI_PHY_PLL_STATUS0 (MSM_HDMI_BASE + 0x00000598) +#define HDMI_PHY_PLL_STATUS1 (MSM_HDMI_BASE + 0x0000059c) +#define HDMI_PHY_CTRL (MSM_HDMI_BASE + 0x000002D4) +#define HDMI_PHY_REG_0 (MSM_HDMI_BASE + 0x00000400) +#define HDMI_PHY_REG_1 (MSM_HDMI_BASE + 0x00000404) +#define HDMI_PHY_REG_2 (MSM_HDMI_BASE + 0x00000408) +#define HDMI_PHY_REG_3 (MSM_HDMI_BASE + 0x0000040c) +#define HDMI_PHY_REG_4 (MSM_HDMI_BASE + 0x00000410) +#define HDMI_PHY_REG_5 (MSM_HDMI_BASE + 0x00000414) +#define HDMI_PHY_REG_6 (MSM_HDMI_BASE + 0x00000418) +#define HDMI_PHY_REG_7 (MSM_HDMI_BASE + 0x0000041c) +#define HDMI_PHY_REG_8 (MSM_HDMI_BASE + 0x00000420) +#define HDMI_PHY_REG_9 (MSM_HDMI_BASE + 0x00000424) +#define HDMI_PHY_REG_10 (MSM_HDMI_BASE + 0x00000428) +#define HDMI_PHY_REG_11 (MSM_HDMI_BASE + 0x0000042c) +#define HDMI_PHY_REG_12 (MSM_HDMI_BASE + 0x00000430) +#define HDMI_PHY_REG_BIST_CFG (MSM_HDMI_BASE + 0x00000434) +#define HDMI_PHY_DEBUG_BUS_SEL (MSM_HDMI_BASE + 0x00000438) +#define HDMI_PHY_REG_MISC0 (MSM_HDMI_BASE + 0x0000043c) +#define HDMI_PHY_REG_13 (MSM_HDMI_BASE + 0x00000440) +#define HDMI_PHY_REG_14 (MSM_HDMI_BASE + 0x00000444) +#define HDMI_PHY_REG_15 (MSM_HDMI_BASE + 0x00000448) + +#define AHB_EN_REG (MSM_MMSS_CLK_CTL_BASE + 0x0008) + +/* HDMI PHY/PLL bit field macros */ +#define SW_RESET BIT(2) +#define SW_RESET_PLL BIT(0) +#define PWRDN_B BIT(7) + +#define PLL_PWRDN_B BIT(3) +#define PD_PLL BIT(1) + +static unsigned current_rate; +static unsigned hdmi_pll_on; + +int hdmi_pll_enable(void) +{ + unsigned int val; + u32 ahb_en_reg, ahb_enabled; + + ahb_en_reg = readl_relaxed(AHB_EN_REG); + ahb_enabled = ahb_en_reg & BIT(4); + if (!ahb_enabled) { + writel_relaxed(ahb_en_reg | BIT(4), AHB_EN_REG); + mb(); + } + + val = readl_relaxed(HDMI_PHY_PLL_PWRDN_B); + val |= PLL_PWRDN_B; + writel_relaxed(val, HDMI_PHY_PLL_PWRDN_B); + mb(); + val &= ~PD_PLL; + writel_relaxed(val, HDMI_PHY_PLL_PWRDN_B); + mb(); + + while (!(readl_relaxed(HDMI_PHY_PLL_STATUS0) & BIT(0))) + cpu_relax(); + + if (!ahb_enabled) + writel_relaxed(ahb_en_reg & ~BIT(4), AHB_EN_REG); + hdmi_pll_on = 1; + return 0; +} + +void hdmi_pll_disable(void) +{ + unsigned int val; + u32 ahb_en_reg, ahb_enabled; + + ahb_en_reg = readl_relaxed(AHB_EN_REG); + ahb_enabled = ahb_en_reg & BIT(4); + if (!ahb_enabled) { + writel_relaxed(ahb_en_reg | BIT(4), AHB_EN_REG); + mb(); + } + + val = readl_relaxed(HDMI_PHY_PLL_PWRDN_B); + val |= PD_PLL; + writel_relaxed(val, HDMI_PHY_PLL_PWRDN_B); + mb(); + + val = val & (~PLL_PWRDN_B); + writel_relaxed(val, HDMI_PHY_PLL_PWRDN_B); + mb(); + + if (!ahb_enabled) + writel_relaxed(ahb_en_reg & ~BIT(4), AHB_EN_REG); + hdmi_pll_on = 0; +} + +unsigned hdmi_pll_get_rate(void) +{ + return current_rate; +} + +int hdmi_pll_set_rate(unsigned rate) +{ + unsigned int set_power_dwn = 0; + unsigned int val; + u32 ahb_en_reg = readl_relaxed(AHB_EN_REG); + u32 ahb_enabled = ahb_en_reg & BIT(4); + + if (!ahb_enabled) { + writel_relaxed(ahb_en_reg | BIT(4), AHB_EN_REG); + mb(); + } + + writel_relaxed(0x7f, HDMI_PHY_REG_2); + writel_relaxed(0x3f, HDMI_PHY_REG_2); + writel_relaxed(0x1f, HDMI_PHY_REG_2); + + val = readl_relaxed(HDMI_PHY_REG_12); + val |= PWRDN_B; + writel_relaxed(val, HDMI_PHY_REG_12); + mb(); + + writel_relaxed(0x81, HDMI_PHY_REG_2); + if (hdmi_pll_on) { + hdmi_pll_disable(); + set_power_dwn = 1; + } + + switch (rate) { + case 27030000: + /* 480p60/480i60 case */ + writel_relaxed(0x32, HDMI_PHY_PLL_REFCLK_CFG); + writel_relaxed(0x2, HDMI_PHY_PLL_CHRG_PUMP_CFG); + writel_relaxed(0x08, HDMI_PHY_PLL_LOOP_FLT_CFG0); + writel_relaxed(0x77, HDMI_PHY_PLL_LOOP_FLT_CFG1); + writel_relaxed(0x2C, HDMI_PHY_PLL_IDAC_ADJ_CFG); + writel_relaxed(0x6, HDMI_PHY_PLL_I_VI_KVCO_CFG); + writel_relaxed(0x7b, HDMI_PHY_PLL_SDM_CFG0); + writel_relaxed(0x01, HDMI_PHY_PLL_SDM_CFG1); + writel_relaxed(0x4C, HDMI_PHY_PLL_SDM_CFG2); + writel_relaxed(0xC0, HDMI_PHY_PLL_SDM_CFG3); + writel_relaxed(0x00, HDMI_PHY_PLL_SDM_CFG4); + writel_relaxed(0x9A, HDMI_PHY_PLL_SSC_CFG0); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG1); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG2); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG3); + writel_relaxed(0x10, HDMI_PHY_PLL_LOCKDET_CFG0); + writel_relaxed(0x1A, HDMI_PHY_PLL_LOCKDET_CFG1); + writel_relaxed(0x0D, HDMI_PHY_PLL_LOCKDET_CFG2); + writel_relaxed(0x2A, HDMI_PHY_PLL_VCOCAL_CFG0); + writel_relaxed(0x03, HDMI_PHY_PLL_VCOCAL_CFG1); + writel_relaxed(0x2B, HDMI_PHY_PLL_VCOCAL_CFG2); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG3); + writel_relaxed(0x86, HDMI_PHY_PLL_VCOCAL_CFG4); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG5); + writel_relaxed(0x33, HDMI_PHY_PLL_VCOCAL_CFG6); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG7); + break; + + case 25200000: + /* 640x480p60 */ + writel_relaxed(0x32, HDMI_PHY_PLL_REFCLK_CFG); + writel_relaxed(0x2, HDMI_PHY_PLL_CHRG_PUMP_CFG); + writel_relaxed(0x01, HDMI_PHY_PLL_LOOP_FLT_CFG0); + writel_relaxed(0x33, HDMI_PHY_PLL_LOOP_FLT_CFG1); + writel_relaxed(0x2C, HDMI_PHY_PLL_IDAC_ADJ_CFG); + writel_relaxed(0x6, HDMI_PHY_PLL_I_VI_KVCO_CFG); + writel_relaxed(0x77, HDMI_PHY_PLL_SDM_CFG0); + writel_relaxed(0x4C, HDMI_PHY_PLL_SDM_CFG1); + writel_relaxed(0x00, HDMI_PHY_PLL_SDM_CFG2); + writel_relaxed(0xC0, HDMI_PHY_PLL_SDM_CFG3); + writel_relaxed(0x00, HDMI_PHY_PLL_SDM_CFG4); + writel_relaxed(0x9A, HDMI_PHY_PLL_SSC_CFG0); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG1); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG2); + writel_relaxed(0x20, HDMI_PHY_PLL_SSC_CFG3); + writel_relaxed(0x10, HDMI_PHY_PLL_LOCKDET_CFG0); + writel_relaxed(0x1A, HDMI_PHY_PLL_LOCKDET_CFG1); + writel_relaxed(0x0D, HDMI_PHY_PLL_LOCKDET_CFG2); + writel_relaxed(0xF4, HDMI_PHY_PLL_VCOCAL_CFG0); + writel_relaxed(0x02, HDMI_PHY_PLL_VCOCAL_CFG1); + writel_relaxed(0x2B, HDMI_PHY_PLL_VCOCAL_CFG2); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG3); + writel_relaxed(0x86, HDMI_PHY_PLL_VCOCAL_CFG4); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG5); + writel_relaxed(0x33, HDMI_PHY_PLL_VCOCAL_CFG6); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG7); + break; + + case 27000000: + /* 576p50/576i50 case */ + writel_relaxed(0x32, HDMI_PHY_PLL_REFCLK_CFG); + writel_relaxed(0x2, HDMI_PHY_PLL_CHRG_PUMP_CFG); + writel_relaxed(0x01, HDMI_PHY_PLL_LOOP_FLT_CFG0); + writel_relaxed(0x33, HDMI_PHY_PLL_LOOP_FLT_CFG1); + writel_relaxed(0x2C, HDMI_PHY_PLL_IDAC_ADJ_CFG); + writel_relaxed(0x6, HDMI_PHY_PLL_I_VI_KVCO_CFG); + writel_relaxed(0x7B, HDMI_PHY_PLL_SDM_CFG0); + writel_relaxed(0x01, HDMI_PHY_PLL_SDM_CFG1); + writel_relaxed(0x4C, HDMI_PHY_PLL_SDM_CFG2); + writel_relaxed(0xC0, HDMI_PHY_PLL_SDM_CFG3); + writel_relaxed(0x00, HDMI_PHY_PLL_SDM_CFG4); + writel_relaxed(0x9A, HDMI_PHY_PLL_SSC_CFG0); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG1); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG2); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG3); + writel_relaxed(0x10, HDMI_PHY_PLL_LOCKDET_CFG0); + writel_relaxed(0x1A, HDMI_PHY_PLL_LOCKDET_CFG1); + writel_relaxed(0x0D, HDMI_PHY_PLL_LOCKDET_CFG2); + writel_relaxed(0x2a, HDMI_PHY_PLL_VCOCAL_CFG0); + writel_relaxed(0x03, HDMI_PHY_PLL_VCOCAL_CFG1); + writel_relaxed(0x2B, HDMI_PHY_PLL_VCOCAL_CFG2); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG3); + writel_relaxed(0x86, HDMI_PHY_PLL_VCOCAL_CFG4); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG5); + writel_relaxed(0x33, HDMI_PHY_PLL_VCOCAL_CFG6); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG7); + break; + + case 74250000: + /* 720p60/720p50/1080i60/1080i50 + * 1080p24/1080p30/1080p25 case + */ + writel_relaxed(0x12, HDMI_PHY_PLL_REFCLK_CFG); + writel_relaxed(0x01, HDMI_PHY_PLL_LOOP_FLT_CFG0); + writel_relaxed(0x33, HDMI_PHY_PLL_LOOP_FLT_CFG1); + writel_relaxed(0x76, HDMI_PHY_PLL_SDM_CFG0); + writel_relaxed(0xE6, HDMI_PHY_PLL_VCOCAL_CFG0); + writel_relaxed(0x02, HDMI_PHY_PLL_VCOCAL_CFG1); + break; + + case 148500000: + /* 1080p60/1080p50 case */ + writel_relaxed(0x2, HDMI_PHY_PLL_REFCLK_CFG); + writel_relaxed(0x2, HDMI_PHY_PLL_CHRG_PUMP_CFG); + writel_relaxed(0x01, HDMI_PHY_PLL_LOOP_FLT_CFG0); + writel_relaxed(0x33, HDMI_PHY_PLL_LOOP_FLT_CFG1); + writel_relaxed(0x2C, HDMI_PHY_PLL_IDAC_ADJ_CFG); + writel_relaxed(0x6, HDMI_PHY_PLL_I_VI_KVCO_CFG); + writel_relaxed(0x76, HDMI_PHY_PLL_SDM_CFG0); + writel_relaxed(0x01, HDMI_PHY_PLL_SDM_CFG1); + writel_relaxed(0x4C, HDMI_PHY_PLL_SDM_CFG2); + writel_relaxed(0xC0, HDMI_PHY_PLL_SDM_CFG3); + writel_relaxed(0x00, HDMI_PHY_PLL_SDM_CFG4); + writel_relaxed(0x9A, HDMI_PHY_PLL_SSC_CFG0); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG1); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG2); + writel_relaxed(0x00, HDMI_PHY_PLL_SSC_CFG3); + writel_relaxed(0x10, HDMI_PHY_PLL_LOCKDET_CFG0); + writel_relaxed(0x1A, HDMI_PHY_PLL_LOCKDET_CFG1); + writel_relaxed(0x0D, HDMI_PHY_PLL_LOCKDET_CFG2); + writel_relaxed(0xe6, HDMI_PHY_PLL_VCOCAL_CFG0); + writel_relaxed(0x02, HDMI_PHY_PLL_VCOCAL_CFG1); + writel_relaxed(0x2B, HDMI_PHY_PLL_VCOCAL_CFG2); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG3); + writel_relaxed(0x86, HDMI_PHY_PLL_VCOCAL_CFG4); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG5); + writel_relaxed(0x33, HDMI_PHY_PLL_VCOCAL_CFG6); + writel_relaxed(0x00, HDMI_PHY_PLL_VCOCAL_CFG7); + writel_relaxed(0x0D, HDMI_PHY_PLL_LOCKDET_CFG2); + break; + } + + mb(); + + if (set_power_dwn) + hdmi_pll_enable(); + + current_rate = rate; + if (!ahb_enabled) + writel_relaxed(ahb_en_reg & ~BIT(4), AHB_EN_REG); + + return 0; +} diff --git a/arch/arm/mach-msm/clock-dss-8960.h b/arch/arm/mach-msm/clock-dss-8960.h new file mode 100644 index 00000000000..4734cdea9ba --- /dev/null +++ b/arch/arm/mach-msm/clock-dss-8960.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ARCH_ARM_MACH_MSM_CLOCK_DSS_8960 +#define __ARCH_ARM_MACH_MSM_CLOCK_DSS_8960 + +int hdmi_pll_enable(void); +void hdmi_pll_disable(void); +unsigned hdmi_pll_get_rate(void); +int hdmi_pll_set_rate(unsigned rate); + +#endif diff --git a/arch/arm/mach-msm/clock-dummy.c b/arch/arm/mach-msm/clock-dummy.c new file mode 100644 index 00000000000..1798a3c5a5d --- /dev/null +++ b/arch/arm/mach-msm/clock-dummy.c @@ -0,0 +1,70 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "clock.h" + +static int dummy_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + return 0; +} + +static int dummy_clk_set_rate(struct clk *clk, unsigned rate) +{ + return 0; +} + +static int dummy_clk_set_min_rate(struct clk *clk, unsigned rate) +{ + return 0; +} + +static int dummy_clk_set_max_rate(struct clk *clk, unsigned rate) +{ + return 0; +} + +static int dummy_clk_set_flags(struct clk *clk, unsigned flags) +{ + return 0; +} + +static unsigned dummy_clk_get_rate(struct clk *clk) +{ + return 0; +} + +static long dummy_clk_round_rate(struct clk *clk, unsigned rate) +{ + return rate; +} + +static bool dummy_clk_is_local(struct clk *clk) +{ + return true; +} + +static struct clk_ops clk_ops_dummy = { + .reset = dummy_clk_reset, + .set_rate = dummy_clk_set_rate, + .set_min_rate = dummy_clk_set_min_rate, + .set_max_rate = dummy_clk_set_max_rate, + .set_flags = dummy_clk_set_flags, + .get_rate = dummy_clk_get_rate, + .round_rate = dummy_clk_round_rate, + .is_local = dummy_clk_is_local, +}; + +struct clk dummy_clk = { + .dbg_name = "dummy_clk", + .ops = &clk_ops_dummy, + CLK_INIT(dummy_clk), +}; diff --git a/arch/arm/mach-msm/clock-fsm9xxx.c b/arch/arm/mach-msm/clock-fsm9xxx.c new file mode 100644 index 00000000000..4011e3fd1d0 --- /dev/null +++ b/arch/arm/mach-msm/clock-fsm9xxx.c @@ -0,0 +1,45 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include + +#include + +#include "clock-local.h" + +int soc_update_sys_vdd(enum sys_vdd_level level) +{ + return -EINVAL; +} + +int soc_set_pwr_rail(struct clk *clk, int enable) +{ + return 0; +} + +void __init msm_clk_soc_init(void) +{ +} + +/* + * Clocks + */ + +struct clk_lookup msm_clocks_fsm9xxx[] = { + CLK_DUMMY("adm_clk", ADM0_CLK, NULL, OFF), + CLK_DUMMY("uart_clk", UART1_CLK, "msm_serial.0", OFF), + CLK_DUMMY("ce_clk", CE_CLK, NULL, OFF), +}; + +unsigned msm_num_clocks_fsm9xxx = ARRAY_SIZE(msm_clocks_fsm9xxx); + diff --git a/arch/arm/mach-msm/clock-local.c b/arch/arm/mach-msm/clock-local.c new file mode 100644 index 00000000000..17f00d81cff --- /dev/null +++ b/arch/arm/mach-msm/clock-local.c @@ -0,0 +1,978 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "clock.h" +#include "clock-local.h" + +#ifdef CONFIG_MSM_SECURE_IO +#undef readl_relaxed +#undef writel_relaxed +#define readl_relaxed secure_readl +#define writel_relaxed secure_writel +#endif + +/* + * When enabling/disabling a clock, check the halt bit up to this number + * number of times (with a 1 us delay in between) before continuing. + */ +#define HALT_CHECK_MAX_LOOPS 100 +/* For clock without halt checking, wait this long after enables/disables. */ +#define HALT_CHECK_DELAY_US 10 + +DEFINE_SPINLOCK(local_clock_reg_lock); +struct clk_freq_tbl local_dummy_freq = F_END; + +unsigned local_sys_vdd_votes[NUM_SYS_VDD_LEVELS]; +static DEFINE_SPINLOCK(sys_vdd_vote_lock); + +/* + * Common Set-Rate Functions + */ + +/* For clocks with MND dividers. */ +void set_rate_mnd(struct rcg_clk *clk, struct clk_freq_tbl *nf) +{ + uint32_t ns_reg_val, ctl_reg_val; + + /* Assert MND reset. */ + ns_reg_val = readl_relaxed(clk->ns_reg); + ns_reg_val |= BIT(7); + writel_relaxed(ns_reg_val, clk->ns_reg); + + /* Program M and D values. */ + writel_relaxed(nf->md_val, clk->md_reg); + + /* If the clock has a separate CC register, program it. */ + if (clk->ns_reg != clk->b.ctl_reg) { + ctl_reg_val = readl_relaxed(clk->b.ctl_reg); + ctl_reg_val &= ~(clk->ctl_mask); + ctl_reg_val |= nf->ctl_val; + writel_relaxed(ctl_reg_val, clk->b.ctl_reg); + } + + /* Deassert MND reset. */ + ns_reg_val &= ~BIT(7); + writel_relaxed(ns_reg_val, clk->ns_reg); +} + +void set_rate_nop(struct rcg_clk *clk, struct clk_freq_tbl *nf) +{ + /* + * Nothing to do for fixed-rate or integer-divider clocks. Any settings + * in NS registers are applied in the enable path, since power can be + * saved by leaving an un-clocked or slowly-clocked source selected + * until the clock is enabled. + */ +} + +void set_rate_mnd_8(struct rcg_clk *clk, struct clk_freq_tbl *nf) +{ + uint32_t ctl_reg_val; + + /* Assert MND reset. */ + ctl_reg_val = readl_relaxed(clk->b.ctl_reg); + ctl_reg_val |= BIT(8); + writel_relaxed(ctl_reg_val, clk->b.ctl_reg); + + /* Program M and D values. */ + writel_relaxed(nf->md_val, clk->md_reg); + + /* Program MN counter Enable and Mode. */ + ctl_reg_val &= ~(clk->ctl_mask); + ctl_reg_val |= nf->ctl_val; + writel_relaxed(ctl_reg_val, clk->b.ctl_reg); + + /* Deassert MND reset. */ + ctl_reg_val &= ~BIT(8); + writel_relaxed(ctl_reg_val, clk->b.ctl_reg); +} + +void set_rate_mnd_banked(struct rcg_clk *clk, struct clk_freq_tbl *nf) +{ + struct bank_masks *banks = clk->bank_masks; + const struct bank_mask_info *new_bank_masks; + const struct bank_mask_info *old_bank_masks; + uint32_t ns_reg_val, ctl_reg_val; + uint32_t bank_sel; + + /* + * Determine active bank and program the other one. If the clock is + * off, program the active bank since bank switching won't work if + * both banks aren't running. + */ + ctl_reg_val = readl_relaxed(clk->b.ctl_reg); + bank_sel = !!(ctl_reg_val & banks->bank_sel_mask); + /* If clock isn't running, don't switch banks. */ + bank_sel ^= (!clk->enabled || clk->current_freq->freq_hz == 0); + if (bank_sel == 0) { + new_bank_masks = &banks->bank1_mask; + old_bank_masks = &banks->bank0_mask; + } else { + new_bank_masks = &banks->bank0_mask; + old_bank_masks = &banks->bank1_mask; + } + + ns_reg_val = readl_relaxed(clk->ns_reg); + + /* Assert bank MND reset. */ + ns_reg_val |= new_bank_masks->rst_mask; + writel_relaxed(ns_reg_val, clk->ns_reg); + + /* + * Program NS only if the clock is enabled, since the NS will be set + * as part of the enable procedure and should remain with a low-power + * MUX input selected until then. + */ + if (clk->enabled) { + ns_reg_val &= ~(new_bank_masks->ns_mask); + ns_reg_val |= (nf->ns_val & new_bank_masks->ns_mask); + writel_relaxed(ns_reg_val, clk->ns_reg); + } + + writel_relaxed(nf->md_val, new_bank_masks->md_reg); + + /* Enable counter only if clock is enabled. */ + if (clk->enabled) + ctl_reg_val |= new_bank_masks->mnd_en_mask; + else + ctl_reg_val &= ~(new_bank_masks->mnd_en_mask); + + ctl_reg_val &= ~(new_bank_masks->mode_mask); + ctl_reg_val |= (nf->ctl_val & new_bank_masks->mode_mask); + writel_relaxed(ctl_reg_val, clk->b.ctl_reg); + + /* Deassert bank MND reset. */ + ns_reg_val &= ~(new_bank_masks->rst_mask); + writel_relaxed(ns_reg_val, clk->ns_reg); + + /* + * Switch to the new bank if clock is running. If it isn't, then + * no switch is necessary since we programmed the active bank. + */ + if (clk->enabled && clk->current_freq->freq_hz) { + ctl_reg_val ^= banks->bank_sel_mask; + writel_relaxed(ctl_reg_val, clk->b.ctl_reg); + /* + * Wait at least 6 cycles of slowest bank's clock + * for the glitch-free MUX to fully switch sources. + */ + mb(); + udelay(1); + + /* Disable old bank's MN counter. */ + ctl_reg_val &= ~(old_bank_masks->mnd_en_mask); + writel_relaxed(ctl_reg_val, clk->b.ctl_reg); + + /* Program old bank to a low-power source and divider. */ + ns_reg_val &= ~(old_bank_masks->ns_mask); + ns_reg_val |= (clk->freq_tbl->ns_val & old_bank_masks->ns_mask); + writel_relaxed(ns_reg_val, clk->ns_reg); + } + + /* + * If this freq requires the MN counter to be enabled, + * update the enable mask to match the current bank. + */ + if (nf->mnd_en_mask) + nf->mnd_en_mask = new_bank_masks->mnd_en_mask; + /* Update the NS mask to match the current bank. */ + clk->ns_mask = new_bank_masks->ns_mask; +} + +void set_rate_div_banked(struct rcg_clk *clk, struct clk_freq_tbl *nf) +{ + struct bank_masks *banks = clk->bank_masks; + const struct bank_mask_info *new_bank_masks; + const struct bank_mask_info *old_bank_masks; + uint32_t ns_reg_val, bank_sel; + + /* + * Determine active bank and program the other one. If the clock is + * off, program the active bank since bank switching won't work if + * both banks aren't running. + */ + ns_reg_val = readl_relaxed(clk->ns_reg); + bank_sel = !!(ns_reg_val & banks->bank_sel_mask); + /* If clock isn't running, don't switch banks. */ + bank_sel ^= (!clk->enabled || clk->current_freq->freq_hz == 0); + if (bank_sel == 0) { + new_bank_masks = &banks->bank1_mask; + old_bank_masks = &banks->bank0_mask; + } else { + new_bank_masks = &banks->bank0_mask; + old_bank_masks = &banks->bank1_mask; + } + + /* + * Program NS only if the clock is enabled, since the NS will be set + * as part of the enable procedure and should remain with a low-power + * MUX input selected until then. + */ + if (clk->enabled) { + ns_reg_val &= ~(new_bank_masks->ns_mask); + ns_reg_val |= (nf->ns_val & new_bank_masks->ns_mask); + writel_relaxed(ns_reg_val, clk->ns_reg); + } + + /* + * Switch to the new bank if clock is running. If it isn't, then + * no switch is necessary since we programmed the active bank. + */ + if (clk->enabled && clk->current_freq->freq_hz) { + ns_reg_val ^= banks->bank_sel_mask; + writel_relaxed(ns_reg_val, clk->ns_reg); + /* + * Wait at least 6 cycles of slowest bank's clock + * for the glitch-free MUX to fully switch sources. + */ + mb(); + udelay(1); + + /* Program old bank to a low-power source and divider. */ + ns_reg_val &= ~(old_bank_masks->ns_mask); + ns_reg_val |= (clk->freq_tbl->ns_val & old_bank_masks->ns_mask); + writel_relaxed(ns_reg_val, clk->ns_reg); + } + + /* Update the NS mask to match the current bank. */ + clk->ns_mask = new_bank_masks->ns_mask; +} + +int (*soc_update_sys_vdd)(enum sys_vdd_level level); + +/* + * SYS_VDD voting functions + */ + +/* Update system voltage level given the current votes. */ +static int local_update_sys_vdd(void) +{ + static int cur_level = NUM_SYS_VDD_LEVELS; + int level, rc = 0; + + if (local_sys_vdd_votes[HIGH]) + level = HIGH; + else if (local_sys_vdd_votes[NOMINAL]) + level = NOMINAL; + else if (local_sys_vdd_votes[LOW]) + level = LOW; + else + level = NONE; + + if (level == cur_level) + return rc; + + rc = soc_update_sys_vdd(level); + if (!rc) + cur_level = level; + + return rc; +} + +/* Vote for a system voltage level. */ +int local_vote_sys_vdd(unsigned level) +{ + int rc = 0; + unsigned long flags; + + /* Bounds checking. */ + if (level >= ARRAY_SIZE(local_sys_vdd_votes)) + return -EINVAL; + + spin_lock_irqsave(&sys_vdd_vote_lock, flags); + local_sys_vdd_votes[level]++; + rc = local_update_sys_vdd(); + if (rc) + local_sys_vdd_votes[level]--; + spin_unlock_irqrestore(&sys_vdd_vote_lock, flags); + + return rc; +} + +/* Remove vote for a system voltage level. */ +int local_unvote_sys_vdd(unsigned level) +{ + int rc = 0; + unsigned long flags; + + /* Bounds checking. */ + if (level >= ARRAY_SIZE(local_sys_vdd_votes)) + return -EINVAL; + + spin_lock_irqsave(&sys_vdd_vote_lock, flags); + + if (WARN(!local_sys_vdd_votes[level], + "Reference counts are incorrect for level %d!\n", level)) + goto out; + + local_sys_vdd_votes[level]--; + rc = local_update_sys_vdd(); + if (rc) + local_sys_vdd_votes[level]++; +out: + spin_unlock_irqrestore(&sys_vdd_vote_lock, flags); + return rc; +} +/* + * Clock enable/disable functions + */ + +/* Return non-zero if a clock status registers shows the clock is halted. */ +static int branch_clk_is_halted(const struct branch *clk) +{ + int invert = (clk->halt_check == ENABLE); + int status_bit = readl_relaxed(clk->halt_reg) & BIT(clk->halt_bit); + return invert ? !status_bit : status_bit; +} + +static void __branch_clk_enable_reg(const struct branch *clk, const char *name) +{ + u32 reg_val; + + if (clk->en_mask) { + reg_val = readl_relaxed(clk->ctl_reg); + reg_val |= clk->en_mask; + writel_relaxed(reg_val, clk->ctl_reg); + } + + /* + * Use a memory barrier since some halt status registers are + * not within the same 1K segment as the branch/root enable + * registers. It's also needed in the udelay() case to ensure + * the delay starts after the branch enable. + */ + mb(); + + /* Wait for clock to enable before returning. */ + if (clk->halt_check == DELAY) + udelay(HALT_CHECK_DELAY_US); + else if (clk->halt_check == ENABLE || clk->halt_check == HALT + || clk->halt_check == ENABLE_VOTED + || clk->halt_check == HALT_VOTED) { + int count; + + /* Wait up to HALT_CHECK_MAX_LOOPS for clock to enable. */ + for (count = HALT_CHECK_MAX_LOOPS; branch_clk_is_halted(clk) + && count > 0; count--) + udelay(1); + WARN(count == 0, "%s status stuck at 'off'", name); + } +} + +/* Perform any register operations required to enable the clock. */ +static void __local_clk_enable_reg(struct rcg_clk *clk) +{ + u32 reg_val; + void __iomem *const reg = clk->b.ctl_reg; + + WARN(clk->current_freq == &local_dummy_freq, + "Attempting to enable %s before setting its rate. " + "Set the rate first!\n", clk->c.dbg_name); + + /* + * Program the NS register, if applicable. NS registers are not + * set in the set_rate path because power can be saved by deferring + * the selection of a clocked source until the clock is enabled. + */ + if (clk->ns_mask) { + reg_val = readl_relaxed(clk->ns_reg); + reg_val &= ~(clk->ns_mask); + reg_val |= (clk->current_freq->ns_val & clk->ns_mask); + writel_relaxed(reg_val, clk->ns_reg); + } + + /* Enable MN counter, if applicable. */ + reg_val = readl_relaxed(reg); + if (clk->current_freq->mnd_en_mask) { + reg_val |= clk->current_freq->mnd_en_mask; + writel_relaxed(reg_val, reg); + } + /* Enable root. */ + if (clk->root_en_mask) { + reg_val |= clk->root_en_mask; + writel_relaxed(reg_val, reg); + } + __branch_clk_enable_reg(&clk->b, clk->c.dbg_name); +} + +/* Perform any register operations required to disable the branch. */ +static u32 __branch_clk_disable_reg(const struct branch *clk, const char *name) +{ + u32 reg_val; + + reg_val = readl_relaxed(clk->ctl_reg); + if (clk->en_mask) { + reg_val &= ~(clk->en_mask); + writel_relaxed(reg_val, clk->ctl_reg); + } + + /* + * Use a memory barrier since some halt status registers are + * not within the same K segment as the branch/root enable + * registers. It's also needed in the udelay() case to ensure + * the delay starts after the branch disable. + */ + mb(); + + /* Wait for clock to disable before continuing. */ + if (clk->halt_check == DELAY || clk->halt_check == ENABLE_VOTED + || clk->halt_check == HALT_VOTED) + udelay(HALT_CHECK_DELAY_US); + else if (clk->halt_check == ENABLE || clk->halt_check == HALT) { + int count; + + /* Wait up to HALT_CHECK_MAX_LOOPS for clock to disable. */ + for (count = HALT_CHECK_MAX_LOOPS; !branch_clk_is_halted(clk) + && count > 0; count--) + udelay(1); + WARN(count == 0, "%s status stuck at 'on'", name); + } + + return reg_val; +} + +/* Perform any register operations required to disable the generator. */ +static void __local_clk_disable_reg(struct rcg_clk *clk) +{ + void __iomem *const reg = clk->b.ctl_reg; + uint32_t reg_val; + + reg_val = __branch_clk_disable_reg(&clk->b, clk->c.dbg_name); + /* Disable root. */ + if (clk->root_en_mask) { + reg_val &= ~(clk->root_en_mask); + writel_relaxed(reg_val, reg); + } + /* Disable MN counter, if applicable. */ + if (clk->current_freq->mnd_en_mask) { + reg_val &= ~(clk->current_freq->mnd_en_mask); + writel_relaxed(reg_val, reg); + } + /* + * Program NS register to low-power value with an un-clocked or + * slowly-clocked source selected. + */ + if (clk->ns_mask) { + reg_val = readl_relaxed(clk->ns_reg); + reg_val &= ~(clk->ns_mask); + reg_val |= (clk->freq_tbl->ns_val & clk->ns_mask); + writel_relaxed(reg_val, clk->ns_reg); + } +} + +static int _local_clk_enable(struct rcg_clk *clk) +{ + unsigned long flags; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __local_clk_enable_reg(clk); + clk->enabled = true; + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +static void _local_clk_disable(struct rcg_clk *clk) +{ + unsigned long flags; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __local_clk_disable_reg(clk); + clk->enabled = false; + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +/* Enable a clock and any related power rail. */ +int local_clk_enable(struct clk *c) +{ + int rc; + struct rcg_clk *clk = to_rcg_clk(c); + + rc = local_vote_sys_vdd(clk->current_freq->sys_vdd); + if (rc) + goto err_vdd; + rc = clk_enable(clk->depends); + if (rc) + goto err_dep; + rc = _local_clk_enable(clk); + if (rc) + goto err_enable; + return rc; + +err_enable: + clk_disable(clk->depends); +err_dep: + local_unvote_sys_vdd(clk->current_freq->sys_vdd); +err_vdd: + return rc; +} + +/* Disable a clock and any related power rail. */ +void local_clk_disable(struct clk *c) +{ + struct rcg_clk *clk = to_rcg_clk(c); + + _local_clk_disable(clk); + clk_disable(clk->depends); + local_unvote_sys_vdd(clk->current_freq->sys_vdd); +} + +/* Turn off a clock at boot, without checking refcounts or disabling depends. */ +void local_clk_auto_off(struct clk *c) +{ + _local_clk_disable(to_rcg_clk(c)); +} + +/* + * Frequency-related functions + */ + +/* Set a clock's frequency. */ +static int _local_clk_set_rate(struct rcg_clk *clk, struct clk_freq_tbl *nf) +{ + struct clk_freq_tbl *cf; + int rc = 0; + struct clk *chld; + unsigned long flags; + + spin_lock_irqsave(&clk->c.lock, flags); + + /* Check if frequency is actually changed. */ + cf = clk->current_freq; + if (nf == cf) + goto unlock; + + if (clk->enabled) { + /* Vote for voltage and source for new freq. */ + rc = local_vote_sys_vdd(nf->sys_vdd); + if (rc) + goto unlock; + rc = clk_enable(nf->src_clk); + if (rc) { + local_unvote_sys_vdd(nf->sys_vdd); + goto unlock; + } + } + + spin_lock(&local_clock_reg_lock); + + /* Disable branch if clock isn't dual-banked with a glitch-free MUX. */ + if (clk->bank_masks == NULL) { + /* Disable all branches to prevent glitches. */ + list_for_each_entry(chld, &clk->c.children, siblings) { + struct branch_clk *x = to_branch_clk(chld); + /* + * We don't need to grab the child's lock because + * we hold the local_clock_reg_lock and 'enabled' is + * only modified within lock. + */ + if (x->enabled) + __branch_clk_disable_reg(&x->b, x->c.dbg_name); + } + if (clk->enabled) + __local_clk_disable_reg(clk); + } + + /* Perform clock-specific frequency switch operations. */ + BUG_ON(!clk->set_rate); + clk->set_rate(clk, nf); + + /* + * Current freq must be updated before __local_clk_enable_reg() + * is called to make sure the MNCNTR_EN bit is set correctly. + */ + clk->current_freq = nf; + + /* Enable any clocks that were disabled. */ + if (clk->bank_masks == NULL) { + if (clk->enabled) + __local_clk_enable_reg(clk); + /* Enable only branches that were ON before. */ + list_for_each_entry(chld, &clk->c.children, siblings) { + struct branch_clk *x = to_branch_clk(chld); + if (x->enabled) + __branch_clk_enable_reg(&x->b, x->c.dbg_name); + } + } + + spin_unlock(&local_clock_reg_lock); + + /* Release requirements of the old freq. */ + if (clk->enabled) { + clk_disable(cf->src_clk); + local_unvote_sys_vdd(cf->sys_vdd); + } +unlock: + spin_unlock_irqrestore(&clk->c.lock, flags); + + return rc; +} + +/* Set a clock to an exact rate. */ +int local_clk_set_rate(struct clk *c, unsigned rate) +{ + struct rcg_clk *clk = to_rcg_clk(c); + struct clk_freq_tbl *nf; + + for (nf = clk->freq_tbl; nf->freq_hz != FREQ_END + && nf->freq_hz != rate; nf++) + ; + + if (nf->freq_hz == FREQ_END) + return -EINVAL; + + return _local_clk_set_rate(clk, nf); +} + +/* Set a clock to a rate greater than some minimum. */ +int local_clk_set_min_rate(struct clk *c, unsigned rate) +{ + struct rcg_clk *clk = to_rcg_clk(c); + struct clk_freq_tbl *nf; + + for (nf = clk->freq_tbl; nf->freq_hz != FREQ_END + && nf->freq_hz < rate; nf++) + ; + + if (nf->freq_hz == FREQ_END) + return -EINVAL; + + return _local_clk_set_rate(clk, nf); +} + +/* Set a clock to a maximum rate. */ +int local_clk_set_max_rate(struct clk *clk, unsigned rate) +{ + return -EPERM; +} + +/* Get the currently-set rate of a clock in Hz. */ +unsigned local_clk_get_rate(struct clk *c) +{ + struct rcg_clk *clk = to_rcg_clk(c); + unsigned long flags; + unsigned ret = 0; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + ret = clk->current_freq->freq_hz; + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* + * Return 0 if the rate has never been set. Might not be correct, + * but it's good enough. + */ + if (ret == FREQ_END) + ret = 0; + + return ret; +} + +/* Check if a clock is currently enabled. */ +int local_clk_is_enabled(struct clk *clk) +{ + return to_rcg_clk(clk)->enabled; +} + +/* Return a supported rate that's at least the specified rate. */ +long local_clk_round_rate(struct clk *c, unsigned rate) +{ + struct rcg_clk *clk = to_rcg_clk(c); + struct clk_freq_tbl *f; + + for (f = clk->freq_tbl; f->freq_hz != FREQ_END; f++) + if (f->freq_hz >= rate) + return f->freq_hz; + + return -EPERM; +} + +bool local_clk_is_local(struct clk *clk) +{ + return true; +} + +/* Return the nth supported frequency for a given clock. */ +int local_clk_list_rate(struct clk *c, unsigned n) +{ + struct rcg_clk *clk = to_rcg_clk(c); + + if (!clk->freq_tbl || clk->freq_tbl->freq_hz == FREQ_END) + return -ENXIO; + + return (clk->freq_tbl + n)->freq_hz; +} + +struct clk *local_clk_get_parent(struct clk *clk) +{ + return to_rcg_clk(clk)->current_freq->src_clk; +} + +static int pll_vote_clk_enable(struct clk *clk) +{ + u32 ena; + unsigned long flags; + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + ena = readl_relaxed(pll->en_reg); + ena |= pll->en_mask; + writel_relaxed(ena, pll->en_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Wait until PLL is enabled */ + while ((readl_relaxed(pll->status_reg) & BIT(16)) == 0) + cpu_relax(); + + return 0; +} + +static void pll_vote_clk_disable(struct clk *clk) +{ + u32 ena; + unsigned long flags; + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + ena = readl_relaxed(pll->en_reg); + ena &= ~(pll->en_mask); + writel_relaxed(ena, pll->en_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static unsigned pll_vote_clk_get_rate(struct clk *clk) +{ + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + return pll->rate; +} + +static struct clk *pll_vote_clk_get_parent(struct clk *clk) +{ + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + return pll->parent; +} + +static int pll_vote_clk_is_enabled(struct clk *clk) +{ + struct pll_vote_clk *pll = to_pll_vote_clk(clk); + return !!(readl_relaxed(pll->status_reg) & BIT(16)); +} + +struct clk_ops clk_ops_pll_vote = { + .enable = pll_vote_clk_enable, + .disable = pll_vote_clk_disable, + .is_enabled = pll_vote_clk_is_enabled, + .get_rate = pll_vote_clk_get_rate, + .get_parent = pll_vote_clk_get_parent, + .is_local = local_clk_is_local, +}; + +static int pll_clk_enable(struct clk *clk) +{ + u32 mode; + unsigned long flags; + struct pll_clk *pll = to_pll_clk(clk); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + mode = readl_relaxed(pll->mode_reg); + /* Disable PLL bypass mode. */ + mode |= BIT(1); + writel_relaxed(mode, pll->mode_reg); + + /* + * H/W requires a 5us delay between disabling the bypass and + * de-asserting the reset. Delay 10us just to be safe. + */ + mb(); + udelay(10); + + /* De-assert active-low PLL reset. */ + mode |= BIT(2); + writel_relaxed(mode, pll->mode_reg); + + /* Wait until PLL is locked. */ + mb(); + udelay(50); + + /* Enable PLL output. */ + mode |= BIT(0); + writel_relaxed(mode, pll->mode_reg); + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + return 0; +} + +static void pll_clk_disable(struct clk *clk) +{ + u32 mode; + unsigned long flags; + struct pll_clk *pll = to_pll_clk(clk); + + /* + * Disable the PLL output, disable test mode, enable + * the bypass mode, and assert the reset. + */ + spin_lock_irqsave(&local_clock_reg_lock, flags); + mode = readl_relaxed(pll->mode_reg); + mode &= ~BM(3, 0); + writel_relaxed(mode, pll->mode_reg); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +static unsigned pll_clk_get_rate(struct clk *clk) +{ + struct pll_clk *pll = to_pll_clk(clk); + return pll->rate; +} + +static struct clk *pll_clk_get_parent(struct clk *clk) +{ + struct pll_clk *pll = to_pll_clk(clk); + return pll->parent; +} + +struct clk_ops clk_ops_pll = { + .enable = pll_clk_enable, + .disable = pll_clk_disable, + .get_rate = pll_clk_get_rate, + .get_parent = pll_clk_get_parent, + .is_local = local_clk_is_local, +}; + +struct clk_ops clk_ops_gnd = { + .get_rate = fixed_clk_get_rate, + .is_local = local_clk_is_local, +}; + +struct fixed_clk gnd_clk = { + .c = { + .dbg_name = "ground_clk", + .ops = &clk_ops_gnd, + CLK_INIT(gnd_clk.c), + }, +}; + +struct clk_ops clk_ops_measure = { + .is_local = local_clk_is_local, +}; + +int branch_clk_enable(struct clk *clk) +{ + unsigned long flags; + struct branch_clk *branch = to_branch_clk(clk); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __branch_clk_enable_reg(&branch->b, branch->c.dbg_name); + branch->enabled = true; + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + return 0; +} + +void branch_clk_disable(struct clk *clk) +{ + unsigned long flags; + struct branch_clk *branch = to_branch_clk(clk); + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __branch_clk_disable_reg(&branch->b, branch->c.dbg_name); + branch->enabled = false; + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +struct clk *branch_clk_get_parent(struct clk *clk) +{ + struct branch_clk *branch = to_branch_clk(clk); + return branch->parent; +} + +int branch_clk_set_parent(struct clk *clk, struct clk *parent) +{ + /* + * We setup the parent pointer at init time in msm_clock_init(). + * This check is to make sure drivers can't change the parent. + */ + if (parent && list_empty(&clk->siblings)) { + list_add(&clk->siblings, &parent->children); + return 0; + } + return -EINVAL; +} + +int branch_clk_is_enabled(struct clk *clk) +{ + struct branch_clk *branch = to_branch_clk(clk); + return branch->enabled; +} + +void branch_clk_auto_off(struct clk *clk) +{ + struct branch_clk *branch = to_branch_clk(clk); + unsigned long flags; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + __branch_clk_disable_reg(&branch->b, branch->c.dbg_name); + spin_unlock_irqrestore(&local_clock_reg_lock, flags); +} + +int branch_reset(struct branch *clk, enum clk_reset_action action) +{ + int ret = 0; + u32 reg_val; + unsigned long flags; + + if (!clk->reset_reg) + return -EPERM; + + spin_lock_irqsave(&local_clock_reg_lock, flags); + + reg_val = readl_relaxed(clk->reset_reg); + switch (action) { + case CLK_RESET_ASSERT: + reg_val |= clk->reset_mask; + break; + case CLK_RESET_DEASSERT: + reg_val &= ~(clk->reset_mask); + break; + default: + ret = -EINVAL; + } + writel_relaxed(reg_val, clk->reset_reg); + + spin_unlock_irqrestore(&local_clock_reg_lock, flags); + + /* Make sure write is issued before returning. */ + mb(); + + return ret; +} + +int branch_clk_reset(struct clk *clk, enum clk_reset_action action) +{ + return branch_reset(&to_branch_clk(clk)->b, action); +} diff --git a/arch/arm/mach-msm/clock-local.h b/arch/arm/mach-msm/clock-local.h new file mode 100644 index 00000000000..e465e086e3e --- /dev/null +++ b/arch/arm/mach-msm/clock-local.h @@ -0,0 +1,323 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_H +#define __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_H + +#include +#include "clock.h" + +/* + * Bit manipulation macros + */ +#define BM(msb, lsb) (((((uint32_t)-1) << (31-msb)) >> (31-msb+lsb)) << lsb) +#define BVAL(msb, lsb, val) (((val) << lsb) & BM(msb, lsb)) + +/* + * Halt/Status Checking Mode Macros + */ +#define HALT 0 /* Bit pol: 1 = halted */ +#define NOCHECK 1 /* No bit to check, do nothing */ +#define HALT_VOTED 2 /* Bit pol: 1 = halted; delay on disable */ +#define ENABLE 3 /* Bit pol: 1 = running */ +#define ENABLE_VOTED 4 /* Bit pol: 1 = running; delay on disable */ +#define DELAY 5 /* No bit to check, just delay */ + +/* + * Clock Definition Macros + */ +#define DEFINE_CLK_MEASURE(name) \ + struct clk name = { \ + .ops = &clk_ops_measure, \ + .dbg_name = #name, \ + CLK_INIT(name), \ + }; \ + +/* + * Generic frequency-definition structs and macros + */ +struct clk_freq_tbl { + const uint32_t freq_hz; + struct clk *src_clk; + const uint32_t md_val; + const uint32_t ns_val; + const uint32_t ctl_val; + uint32_t mnd_en_mask; + const unsigned sys_vdd; + void *const extra_freq_data; +}; + +/* Some clocks have two banks to avoid glitches when switching frequencies. + * The unused bank is programmed while running on the other bank, and + * switched to afterwards. The following two structs describe the banks. */ +struct bank_mask_info { + void *const md_reg; + const uint32_t ns_mask; + const uint32_t rst_mask; + const uint32_t mnd_en_mask; + const uint32_t mode_mask; +}; + +struct bank_masks { + const uint32_t bank_sel_mask; + const struct bank_mask_info bank0_mask; + const struct bank_mask_info bank1_mask; +}; + +#define F_RAW(f, sc, m_v, n_v, c_v, m_m, v, e) { \ + .freq_hz = f, \ + .src_clk = sc, \ + .md_val = m_v, \ + .ns_val = n_v, \ + .ctl_val = c_v, \ + .mnd_en_mask = m_m, \ + .sys_vdd = v, \ + .extra_freq_data = e, \ + } +#define FREQ_END (UINT_MAX-1) +#define F_END \ + { \ + .freq_hz = FREQ_END, \ + .sys_vdd = LOW, \ + } + +/** + * struct branch - branch on/off + * @ctl_reg: clock control register + * @en_mask: ORed with @ctl_reg to enable the clock + * @halt_reg: halt register + * @halt_check: type of halt check to perform + * @halt_bit: ANDed with @halt_reg to test for clock halted + * @reset_reg: reset register + * @reset_mask: ORed with @reset_reg to reset the clock domain + */ +struct branch { + void __iomem *const ctl_reg; + const u32 en_mask; + + void __iomem *const halt_reg; + const u16 halt_check; + const u16 halt_bit; + + void __iomem *const reset_reg; + const u32 reset_mask; +}; + +int branch_reset(struct branch *clk, enum clk_reset_action action); + +/* + * Generic clock-definition struct and macros + */ +struct rcg_clk { + bool enabled; + void *const ns_reg; + void *const md_reg; + + const uint32_t root_en_mask; + uint32_t ns_mask; + const uint32_t ctl_mask; + struct bank_masks *const bank_masks; + + void (*set_rate)(struct rcg_clk *, struct clk_freq_tbl *); + struct clk_freq_tbl *const freq_tbl; + struct clk_freq_tbl *current_freq; + + struct clk *depends; + struct branch b; + struct clk c; +}; + +static inline struct rcg_clk *to_rcg_clk(struct clk *clk) +{ + return container_of(clk, struct rcg_clk, c); +} + +/* + * SYS_VDD voltage levels + */ +enum sys_vdd_level { + NONE, + LOW, + NOMINAL, + HIGH, + NUM_SYS_VDD_LEVELS +}; + +/** + * struct fixed_clk - fixed rate clock (used for crystal oscillators) + * @rate: output rate + * @c: clk + */ +struct fixed_clk { + unsigned long rate; + struct clk c; +}; + +static inline struct fixed_clk *to_fixed_clk(struct clk *clk) +{ + return container_of(clk, struct fixed_clk, c); +} + +static inline unsigned fixed_clk_get_rate(struct clk *clk) +{ + struct fixed_clk *f = to_fixed_clk(clk); + return f->rate; +} + + +/** + * struct pll_vote_clk - phase locked loop (HW voteable) + * @rate: output rate + * @en_reg: enable register + * @en_mask: ORed with @en_reg to enable the clock + * @status_reg: status register + * @parent: clock source + * @c: clk + */ +struct pll_vote_clk { + unsigned long rate; + + void __iomem *const en_reg; + const u32 en_mask; + + void __iomem *const status_reg; + + struct clk *parent; + struct clk c; +}; + +extern struct clk_ops clk_ops_pll_vote; + +static inline struct pll_vote_clk *to_pll_vote_clk(struct clk *clk) +{ + return container_of(clk, struct pll_vote_clk, c); +} + +/** + * struct pll_clk - phase locked loop + * @rate: output rate + * @mode_reg: enable register + * @parent: clock source + * @c: clk + */ +struct pll_clk { + unsigned long rate; + + void __iomem *const mode_reg; + + struct clk *parent; + struct clk c; +}; + +extern struct clk_ops clk_ops_pll; + +static inline struct pll_clk *to_pll_clk(struct clk *clk) +{ + return container_of(clk, struct pll_clk, c); +} + +/** + * struct branch_clk - branch + * @enabled: true if clock is on, false otherwise + * @b: branch + * @parent: clock source + * @c: clk + * + * An on/off switch with a rate derived from the parent. + */ +struct branch_clk { + bool enabled; + struct branch b; + struct clk *parent; + struct clk c; +}; + +static inline struct branch_clk *to_branch_clk(struct clk *clk) +{ + return container_of(clk, struct branch_clk, c); +} + +int branch_clk_enable(struct clk *clk); +void branch_clk_disable(struct clk *clk); +struct clk *branch_clk_get_parent(struct clk *clk); +int branch_clk_set_parent(struct clk *clk, struct clk *parent); +int branch_clk_is_enabled(struct clk *clk); +void branch_clk_auto_off(struct clk *clk); +int branch_clk_reset(struct clk *c, enum clk_reset_action action); + +/** + * struct measure_clk - for rate measurement debug use + * @sample_ticks: sample period in reference clock ticks + * @multiplier: measurement scale-up factor + * @divider: measurement scale-down factor + * @c: clk +*/ +struct measure_clk { + u64 sample_ticks; + u32 multiplier; + u32 divider; + struct clk c; +}; + +extern struct clk_ops clk_ops_measure; + +static inline struct measure_clk *to_measure_clk(struct clk *clk) +{ + return container_of(clk, struct measure_clk, c); +} + +/* + * Variables from clock-local driver + */ +extern spinlock_t local_clock_reg_lock; +extern struct clk_freq_tbl local_dummy_freq; +extern struct fixed_clk gnd_clk; + +/* + * Local-clock APIs + */ +int local_vote_sys_vdd(enum sys_vdd_level level); +int local_unvote_sys_vdd(enum sys_vdd_level level); + +/* + * clk_ops APIs + */ +int local_clk_enable(struct clk *clk); +void local_clk_disable(struct clk *clk); +void local_clk_auto_off(struct clk *clk); +int local_clk_set_rate(struct clk *clk, unsigned rate); +int local_clk_set_min_rate(struct clk *clk, unsigned rate); +int local_clk_set_max_rate(struct clk *clk, unsigned rate); +unsigned local_clk_get_rate(struct clk *clk); +int local_clk_list_rate(struct clk *clk, unsigned n); +int local_clk_is_enabled(struct clk *clk); +long local_clk_round_rate(struct clk *clk, unsigned rate); +bool local_clk_is_local(struct clk *clk); +struct clk *local_clk_get_parent(struct clk *c); + +/* + * Required SoC-specific functions, implemented for every supported SoC + */ +extern int (*soc_update_sys_vdd)(enum sys_vdd_level level); + +/* + * Generic set-rate implementations + */ +void set_rate_mnd(struct rcg_clk *clk, struct clk_freq_tbl *nf); +void set_rate_nop(struct rcg_clk *clk, struct clk_freq_tbl *nf); +void set_rate_mnd_8(struct rcg_clk *clk, struct clk_freq_tbl *nf); +void set_rate_mnd_banked(struct rcg_clk *clk, struct clk_freq_tbl *nf); +void set_rate_div_banked(struct rcg_clk *clk, struct clk_freq_tbl *nf); + +#endif /* __ARCH_ARM_MACH_MSM_CLOCK_LOCAL_H */ + diff --git a/arch/arm/mach-msm/clock-pcom-lookup.c b/arch/arm/mach-msm/clock-pcom-lookup.c new file mode 100644 index 00000000000..7936be009fe --- /dev/null +++ b/arch/arm/mach-msm/clock-pcom-lookup.c @@ -0,0 +1,385 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "clock.h" +#include "clock-pcom.h" +#include "clock-voter.h" + +static DEFINE_CLK_PCOM(adm_clk, ADM_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(adsp_clk, ADSP_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(ahb_m_clk, AHB_M_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(ahb_s_clk, AHB_S_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(cam_m_clk, CAM_M_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(axi_rotator_clk, AXI_ROTATOR_CLK, 0); +static DEFINE_CLK_PCOM(ce_clk, CE_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(csi0_clk, CSI0_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(csi0_p_clk, CSI0_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(csi0_vfe_clk, CSI0_VFE_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(csi1_clk, CSI1_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(csi1_p_clk, CSI1_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(csi1_vfe_clk, CSI1_VFE_CLK, CLKFLAG_SKIP_AUTO_OFF); + +static struct pcom_clk dsi_byte_clk = { + .id = P_DSI_BYTE_CLK, + .c = { + .ops = &clk_ops_pcom_ext_config, + .dbg_name = "dsi_byte_clk", + CLK_INIT(dsi_byte_clk.c), + }, +}; + +static struct pcom_clk dsi_clk = { + .id = P_DSI_CLK, + .c = { + .ops = &clk_ops_pcom_ext_config, + .dbg_name = "dsi_clk", + CLK_INIT(dsi_clk.c), + }, +}; + +static struct pcom_clk dsi_esc_clk = { + .id = P_DSI_ESC_CLK, + .c = { + .ops = &clk_ops_pcom_ext_config, + .dbg_name = "dsi_esc_clk", + CLK_INIT(dsi_esc_clk.c), + }, +}; + +static struct pcom_clk dsi_pixel_clk = { + .id = P_DSI_PIXEL_CLK, + .c = { + .ops = &clk_ops_pcom_ext_config, + .dbg_name = "dsi_pixel_clk", + CLK_INIT(dsi_pixel_clk.c), + }, +}; + +static DEFINE_CLK_PCOM(dsi_ref_clk, DSI_REF_CLK, 0); +static DEFINE_CLK_PCOM(ebi1_clk, EBI1_CLK, + CLKFLAG_SKIP_AUTO_OFF | CLKFLAG_MIN); +static DEFINE_CLK_PCOM(ebi2_clk, EBI2_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(ecodec_clk, ECODEC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(emdh_clk, EMDH_CLK, CLKFLAG_MIN | CLKFLAG_MAX); +static DEFINE_CLK_PCOM(gp_clk, GP_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(grp_2d_clk, GRP_2D_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(grp_2d_p_clk, GRP_2D_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(grp_3d_clk, GRP_3D_CLK, 0); +static DEFINE_CLK_PCOM(grp_3d_p_clk, GRP_3D_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(gsbi1_qup_clk, GSBI1_QUP_CLK, 0); +static DEFINE_CLK_PCOM(gsbi1_qup_p_clk, GSBI1_QUP_P_CLK, 0); +static DEFINE_CLK_PCOM(gsbi2_qup_clk, GSBI2_QUP_CLK, 0); +static DEFINE_CLK_PCOM(gsbi2_qup_p_clk, GSBI2_QUP_P_CLK, 0); +static DEFINE_CLK_PCOM(gsbi_clk, GSBI_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(gsbi_p_clk, GSBI_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(hdmi_clk, HDMI_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(i2c_clk, I2C_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(icodec_rx_clk, ICODEC_RX_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(icodec_tx_clk, ICODEC_TX_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(imem_clk, IMEM_CLK, 0); +static DEFINE_CLK_PCOM(mdc_clk, MDC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(mdp_clk, MDP_CLK, 0); +static DEFINE_CLK_PCOM(mdp_lcdc_pad_pclk_clk, MDP_LCDC_PAD_PCLK_CLK, + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(mdp_lcdc_pclk_clk, MDP_LCDC_PCLK_CLK, + CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(mdp_vsync_clk, MDP_VSYNC_CLK, 0); +static DEFINE_CLK_PCOM(mdp_dsi_p_clk, MDP_DSI_P_CLK, 0); +static DEFINE_CLK_PCOM(pbus_clk, PBUS_CLK, + CLKFLAG_SKIP_AUTO_OFF | CLKFLAG_MIN); +static DEFINE_CLK_PCOM(pcm_clk, PCM_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(pmdh_clk, PMDH_CLK, CLKFLAG_MIN | CLKFLAG_MAX); +static DEFINE_CLK_PCOM(sdac_clk, SDAC_CLK, 0); +static DEFINE_CLK_PCOM(sdc1_clk, SDC1_CLK, 0); +static DEFINE_CLK_PCOM(sdc1_p_clk, SDC1_P_CLK, 0); +static DEFINE_CLK_PCOM(sdc2_clk, SDC2_CLK, 0); +static DEFINE_CLK_PCOM(sdc2_p_clk, SDC2_P_CLK, 0); +static DEFINE_CLK_PCOM(sdc3_clk, SDC3_CLK, 0); +static DEFINE_CLK_PCOM(sdc3_p_clk, SDC3_P_CLK, 0); +static DEFINE_CLK_PCOM(sdc4_clk, SDC4_CLK, 0); +static DEFINE_CLK_PCOM(sdc4_p_clk, SDC4_P_CLK, 0); +static DEFINE_CLK_PCOM(spi_clk, SPI_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(tsif_clk, TSIF_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(tsif_p_clk, TSIF_P_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(tsif_ref_clk, TSIF_REF_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(tv_dac_clk, TV_DAC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(tv_enc_clk, TV_ENC_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(uart1_clk, UART1_CLK, 0); +static DEFINE_CLK_PCOM(uart1dm_clk, UART1DM_CLK, 0); +static DEFINE_CLK_PCOM(uart2_clk, UART2_CLK, 0); +static DEFINE_CLK_PCOM(uart2dm_clk, UART2DM_CLK, 0); +static DEFINE_CLK_PCOM(uart3_clk, UART3_CLK, 0); +static DEFINE_CLK_PCOM(usb_hs2_clk, USB_HS2_CLK, 0); +static DEFINE_CLK_PCOM(usb_hs2_p_clk, USB_HS2_P_CLK, 0); +static DEFINE_CLK_PCOM(usb_hs3_clk, USB_HS3_CLK, 0); +static DEFINE_CLK_PCOM(usb_hs3_p_clk, USB_HS3_P_CLK, 0); +static DEFINE_CLK_PCOM(usb_hs_clk, USB_HS_CLK, 0); +static DEFINE_CLK_PCOM(usb_hs_core_clk, USB_HS_CORE_CLK, 0); +static DEFINE_CLK_PCOM(usb_hs_p_clk, USB_HS_P_CLK, 0); +static DEFINE_CLK_PCOM(usb_otg_clk, USB_OTG_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(usb_phy_clk, USB_PHY_CLK, CLKFLAG_SKIP_AUTO_OFF); +static DEFINE_CLK_PCOM(vdc_clk, VDC_CLK, CLKFLAG_MIN); +static DEFINE_CLK_PCOM(vfe_axi_clk, VFE_AXI_CLK, 0); +static DEFINE_CLK_PCOM(vfe_clk, VFE_CLK, 0); +static DEFINE_CLK_PCOM(vfe_mdc_clk, VFE_MDC_CLK, 0); + +static DEFINE_CLK_VOTER(ebi_acpu_clk, &ebi1_clk.c); +static DEFINE_CLK_VOTER(ebi_kgsl_clk, &ebi1_clk.c); +static DEFINE_CLK_VOTER(ebi_lcdc_clk, &ebi1_clk.c); +static DEFINE_CLK_VOTER(ebi_mddi_clk, &ebi1_clk.c); +static DEFINE_CLK_VOTER(ebi_tv_clk, &ebi1_clk.c); +static DEFINE_CLK_VOTER(ebi_usb_clk, &ebi1_clk.c); +static DEFINE_CLK_VOTER(ebi_vfe_clk, &ebi1_clk.c); +static DEFINE_CLK_VOTER(ebi_adm_clk, &ebi1_clk.c); + +struct clk_lookup msm_clocks_7x01a[] = { + CLK_LOOKUP("adm_clk", adm_clk.c, NULL), + CLK_LOOKUP("adsp_clk", adsp_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("ebi2_clk", ebi2_clk.c, NULL), + CLK_LOOKUP("ecodec_clk", ecodec_clk.c, NULL), + CLK_LOOKUP("emdh_clk", emdh_clk.c, NULL), + CLK_LOOKUP("gp_clk", gp_clk.c, NULL), + CLK_LOOKUP("grp_clk", grp_3d_clk.c, NULL), + CLK_LOOKUP("i2c_clk", i2c_clk.c, "msm_i2c.0"), + CLK_LOOKUP("icodec_rx_clk", icodec_rx_clk.c, NULL), + CLK_LOOKUP("icodec_tx_clk", icodec_tx_clk.c, NULL), + CLK_LOOKUP("imem_clk", imem_clk.c, NULL), + CLK_LOOKUP("mdc_clk", mdc_clk.c, NULL), + CLK_LOOKUP("mddi_clk", pmdh_clk.c, NULL), + CLK_LOOKUP("mdp_clk", mdp_clk.c, NULL), + CLK_LOOKUP("pbus_clk", pbus_clk.c, NULL), + CLK_LOOKUP("pcm_clk", pcm_clk.c, NULL), + CLK_LOOKUP("sdac_clk", sdac_clk.c, NULL), + CLK_LOOKUP("sdc_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_pclk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_pclk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_pclk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("sdc_pclk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("tsif_clk", tsif_clk.c, NULL), + CLK_LOOKUP("tsif_ref_clk", tsif_ref_clk.c, NULL), + CLK_LOOKUP("tv_dac_clk", tv_dac_clk.c, NULL), + CLK_LOOKUP("tv_enc_clk", tv_enc_clk.c, NULL), + CLK_LOOKUP("uart_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("uart_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("uart_clk", uart3_clk.c, "msm_serial.2"), + CLK_LOOKUP("uartdm_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("uartdm_clk", uart2dm_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("usb_hs_clk", usb_hs_clk.c, "msm_otg"), + CLK_LOOKUP("usb_hs_pclk", usb_hs_p_clk.c, "msm_otg"), + CLK_LOOKUP("usb_otg_clk", usb_otg_clk.c, NULL), + CLK_LOOKUP("vdc_clk", vdc_clk.c, NULL), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("vfe_mdc_clk", vfe_mdc_clk.c, NULL), +}; +unsigned msm_num_clocks_7x01a = ARRAY_SIZE(msm_clocks_7x01a); + +struct clk_lookup msm_clocks_7x27[] = { + CLK_LOOKUP("adm_clk", adm_clk.c, NULL), + CLK_LOOKUP("adsp_clk", adsp_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("ebi2_clk", ebi2_clk.c, NULL), + CLK_LOOKUP("ecodec_clk", ecodec_clk.c, NULL), + CLK_LOOKUP("gp_clk", gp_clk.c, NULL), + CLK_LOOKUP("grp_clk", grp_3d_clk.c, NULL), + CLK_LOOKUP("grp_pclk", grp_3d_p_clk.c, NULL), + CLK_LOOKUP("i2c_clk", i2c_clk.c, "msm_i2c.0"), + CLK_LOOKUP("icodec_rx_clk", icodec_rx_clk.c, NULL), + CLK_LOOKUP("icodec_tx_clk", icodec_tx_clk.c, NULL), + CLK_LOOKUP("imem_clk", imem_clk.c, NULL), + CLK_LOOKUP("mdc_clk", mdc_clk.c, NULL), + CLK_LOOKUP("mddi_clk", pmdh_clk.c, NULL), + CLK_LOOKUP("mdp_clk", mdp_clk.c, NULL), + CLK_LOOKUP("mdp_lcdc_pclk_clk", mdp_lcdc_pclk_clk.c, NULL), + CLK_LOOKUP("mdp_lcdc_pad_pclk_clk", mdp_lcdc_pad_pclk_clk.c, NULL), + CLK_LOOKUP("mdp_vsync_clk", mdp_vsync_clk.c, NULL), + CLK_LOOKUP("pbus_clk", pbus_clk.c, NULL), + CLK_LOOKUP("pcm_clk", pcm_clk.c, NULL), + CLK_LOOKUP("sdac_clk", sdac_clk.c, NULL), + CLK_LOOKUP("sdc_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_pclk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_pclk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_pclk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("sdc_pclk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("tsif_clk", tsif_clk.c, NULL), + CLK_LOOKUP("tsif_ref_clk", tsif_ref_clk.c, NULL), + CLK_LOOKUP("tsif_pclk", tsif_p_clk.c, NULL), + CLK_LOOKUP("uart_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("uart_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("uartdm_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("uartdm_clk", uart2dm_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("usb_hs_clk", usb_hs_clk.c, NULL), + CLK_LOOKUP("usb_hs_pclk", usb_hs_p_clk.c, NULL), + CLK_LOOKUP("usb_otg_clk", usb_otg_clk.c, NULL), + CLK_LOOKUP("usb_phy_clk", usb_phy_clk.c, NULL), + CLK_LOOKUP("vdc_clk", vdc_clk.c, NULL), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("vfe_mdc_clk", vfe_mdc_clk.c, NULL), + + CLK_LOOKUP("ebi1_acpu_clk", ebi_acpu_clk.c, NULL), + CLK_LOOKUP("ebi1_kgsl_clk", ebi_kgsl_clk.c, NULL), + CLK_LOOKUP("ebi1_lcdc_clk", ebi_lcdc_clk.c, NULL), + CLK_LOOKUP("ebi1_mddi_clk", ebi_mddi_clk.c, NULL), + CLK_LOOKUP("ebi1_usb_clk", ebi_usb_clk.c, NULL), + CLK_LOOKUP("ebi1_vfe_clk", ebi_vfe_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi_adm_clk.c, "msm_dmov"), +}; +unsigned msm_num_clocks_7x27 = ARRAY_SIZE(msm_clocks_7x27); + +struct clk_lookup msm_clocks_7x27a[] = { + CLK_LOOKUP("adm_clk", adm_clk.c, NULL), + CLK_LOOKUP("adsp_clk", adsp_clk.c, NULL), + CLK_LOOKUP("ahb_m_clk", ahb_m_clk.c, NULL), + CLK_LOOKUP("ahb_s_clk", ahb_s_clk.c, NULL), + CLK_LOOKUP("cam_m_clk", cam_m_clk.c, NULL), + CLK_LOOKUP("csi_clk", csi0_clk.c, "msm_camera_ov9726.0"), + CLK_LOOKUP("csi_pclk", csi0_p_clk.c, "msm_camera_ov9726.0"), + CLK_LOOKUP("csi_vfe_clk", csi0_vfe_clk.c, "msm_camera_ov9726.0"), + CLK_LOOKUP("csi_clk", csi1_clk.c, NULL), + CLK_LOOKUP("csi_pclk", csi1_p_clk.c, NULL), + CLK_LOOKUP("csi_vfe_clk", csi1_vfe_clk.c, NULL), + CLK_LOOKUP("dsi_byte_clk", dsi_byte_clk.c, NULL), + CLK_LOOKUP("dsi_clk", dsi_clk.c, NULL), + CLK_LOOKUP("dsi_esc_clk", dsi_esc_clk.c, NULL), + CLK_LOOKUP("dsi_pixel_clk", dsi_pixel_clk.c, NULL), + CLK_LOOKUP("dsi_ref_clk", dsi_ref_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("ebi2_clk", ebi2_clk.c, NULL), + CLK_LOOKUP("ecodec_clk", ecodec_clk.c, NULL), + CLK_LOOKUP("gp_clk", gp_clk.c, NULL), + CLK_LOOKUP("grp_clk", grp_3d_clk.c, NULL), + CLK_LOOKUP("grp_pclk", grp_3d_p_clk.c, NULL), + CLK_LOOKUP("gsbi_qup_clk", gsbi1_qup_clk.c, "qup_i2c.0"), + CLK_LOOKUP("gsbi_qup_clk", gsbi2_qup_clk.c, "qup_i2c.1"), + CLK_LOOKUP("gsbi_qup_pclk", gsbi1_qup_p_clk.c, "qup_i2c.0"), + CLK_LOOKUP("gsbi_qup_pclk", gsbi2_qup_p_clk.c, "qup_i2c.1"), + CLK_LOOKUP("icodec_rx_clk", icodec_rx_clk.c, NULL), + CLK_LOOKUP("icodec_tx_clk", icodec_tx_clk.c, NULL), + CLK_LOOKUP("imem_clk", imem_clk.c, NULL), + CLK_LOOKUP("mddi_clk", pmdh_clk.c, NULL), + CLK_LOOKUP("mdp_clk", mdp_clk.c, NULL), + CLK_LOOKUP("mdp_lcdc_pclk_clk", mdp_lcdc_pclk_clk.c, NULL), + CLK_LOOKUP("mdp_lcdc_pad_pclk_clk", mdp_lcdc_pad_pclk_clk.c, NULL), + CLK_LOOKUP("mdp_vsync_clk", mdp_vsync_clk.c, NULL), + CLK_LOOKUP("mdp_dsi_pclk", mdp_dsi_p_clk.c, NULL), + CLK_LOOKUP("pbus_clk", pbus_clk.c, NULL), + CLK_LOOKUP("pcm_clk", pcm_clk.c, NULL), + CLK_LOOKUP("sdac_clk", sdac_clk.c, NULL), + CLK_LOOKUP("sdc_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_pclk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_pclk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_pclk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("sdc_pclk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("tsif_ref_clk", tsif_ref_clk.c, NULL), + CLK_LOOKUP("tsif_pclk", tsif_p_clk.c, NULL), + CLK_LOOKUP("uart_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("uart_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("uartdm_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("uartdm_clk", uart2dm_clk.c, "msm_serial_hsl.0"), + CLK_LOOKUP("usb_hs_core_clk", usb_hs_core_clk.c, NULL), + CLK_LOOKUP("usb_hs2_clk", usb_hs2_clk.c, NULL), + CLK_LOOKUP("usb_hs_clk", usb_hs_clk.c, NULL), + CLK_LOOKUP("usb_hs_pclk", usb_hs_p_clk.c, NULL), + CLK_LOOKUP("usb_phy_clk", usb_phy_clk.c, NULL), + CLK_LOOKUP("vdc_clk", vdc_clk.c, NULL), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("vfe_mdc_clk", vfe_mdc_clk.c, NULL), + + CLK_LOOKUP("ebi1_acpu_clk", ebi_acpu_clk.c, NULL), + CLK_LOOKUP("ebi1_kgsl_clk", ebi_kgsl_clk.c, NULL), + CLK_LOOKUP("ebi1_lcdc_clk", ebi_lcdc_clk.c, NULL), + CLK_LOOKUP("ebi1_mddi_clk", ebi_mddi_clk.c, NULL), + CLK_LOOKUP("ebi1_usb_clk", ebi_usb_clk.c, NULL), + CLK_LOOKUP("ebi1_vfe_clk", ebi_vfe_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi_adm_clk.c, "msm_dmov"), +}; +unsigned msm_num_clocks_7x27a = ARRAY_SIZE(msm_clocks_7x27a); + +struct clk_lookup msm_clocks_8x50[] = { + CLK_LOOKUP("adm_clk", adm_clk.c, NULL), + CLK_LOOKUP("ce_clk", ce_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("ebi2_clk", ebi2_clk.c, NULL), + CLK_LOOKUP("ecodec_clk", ecodec_clk.c, NULL), + CLK_LOOKUP("emdh_clk", emdh_clk.c, NULL), + CLK_LOOKUP("gp_clk", gp_clk.c, NULL), + CLK_LOOKUP("grp_clk", grp_3d_clk.c, NULL), + CLK_LOOKUP("i2c_clk", i2c_clk.c, "msm_i2c.0"), + CLK_LOOKUP("icodec_rx_clk", icodec_rx_clk.c, NULL), + CLK_LOOKUP("icodec_tx_clk", icodec_tx_clk.c, NULL), + CLK_LOOKUP("imem_clk", imem_clk.c, NULL), + CLK_LOOKUP("mdc_clk", mdc_clk.c, NULL), + CLK_LOOKUP("mddi_clk", pmdh_clk.c, NULL), + CLK_LOOKUP("mdp_clk", mdp_clk.c, NULL), + CLK_LOOKUP("mdp_lcdc_pclk_clk", mdp_lcdc_pclk_clk.c, NULL), + CLK_LOOKUP("mdp_lcdc_pad_pclk_clk", mdp_lcdc_pad_pclk_clk.c, NULL), + CLK_LOOKUP("mdp_vsync_clk", mdp_vsync_clk.c, NULL), + CLK_LOOKUP("pbus_clk", pbus_clk.c, NULL), + CLK_LOOKUP("pcm_clk", pcm_clk.c, NULL), + CLK_LOOKUP("sdac_clk", sdac_clk.c, NULL), + CLK_LOOKUP("sdc_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_pclk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_pclk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_pclk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("sdc_pclk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("spi_clk", spi_clk.c, NULL), + CLK_DUMMY("spi_pclk", SPI_P_CLK, "spi_qsd.0", 0), + CLK_LOOKUP("tsif_clk", tsif_clk.c, NULL), + CLK_LOOKUP("tsif_ref_clk", tsif_ref_clk.c, NULL), + CLK_LOOKUP("tv_dac_clk", tv_dac_clk.c, NULL), + CLK_LOOKUP("tv_enc_clk", tv_enc_clk.c, NULL), + CLK_LOOKUP("uart_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("uart_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("uart_clk", uart3_clk.c, "msm_serial.2"), + CLK_LOOKUP("uartdm_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("uartdm_clk", uart2dm_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("usb_hs_clk", usb_hs_clk.c, NULL), + CLK_LOOKUP("usb_hs_pclk", usb_hs_p_clk.c, NULL), + CLK_LOOKUP("usb_otg_clk", usb_otg_clk.c, NULL), + CLK_LOOKUP("vdc_clk", vdc_clk.c, NULL), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("vfe_mdc_clk", vfe_mdc_clk.c, NULL), + CLK_LOOKUP("vfe_axi_clk", vfe_axi_clk.c, NULL), + CLK_LOOKUP("usb_hs2_clk", usb_hs2_clk.c, NULL), + CLK_LOOKUP("usb_hs2_pclk", usb_hs2_p_clk.c, NULL), + CLK_LOOKUP("usb_hs3_clk", usb_hs3_clk.c, NULL), + CLK_LOOKUP("usb_hs3_pclk", usb_hs3_p_clk.c, NULL), + CLK_LOOKUP("usb_phy_clk", usb_phy_clk.c, NULL), + + CLK_LOOKUP("ebi1_acpu_clk", ebi_acpu_clk.c, NULL), + CLK_LOOKUP("ebi1_kgsl_clk", ebi_kgsl_clk.c, NULL), + CLK_LOOKUP("ebi1_lcdc_clk", ebi_lcdc_clk.c, NULL), + CLK_LOOKUP("ebi1_mddi_clk", ebi_mddi_clk.c, NULL), + CLK_LOOKUP("ebi1_tv_clk", ebi_tv_clk.c, NULL), + CLK_LOOKUP("ebi1_usb_clk", ebi_usb_clk.c, NULL), + CLK_LOOKUP("ebi1_vfe_clk", ebi_vfe_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi_adm_clk.c, "msm_dmov"), + + CLK_LOOKUP("grp_pclk", grp_3d_p_clk.c, NULL), + CLK_LOOKUP("grp_2d_clk", grp_2d_clk.c, NULL), + CLK_LOOKUP("grp_2d_pclk", grp_2d_p_clk.c, NULL), + CLK_LOOKUP("qup_clk", gsbi_clk.c, "qup_i2c.4"), + CLK_LOOKUP("qup_pclk", gsbi_p_clk.c, "qup_i2c.4"), +}; +unsigned msm_num_clocks_8x50 = ARRAY_SIZE(msm_clocks_8x50); diff --git a/arch/arm/mach-msm/clock-pcom.c b/arch/arm/mach-msm/clock-pcom.c index 63b71131108..f7f3666616e 100644 --- a/arch/arm/mach-msm/clock-pcom.c +++ b/arch/arm/mach-msm/clock-pcom.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2007-2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2007-2011, Code Aurora Forum. 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 @@ -13,10 +13,11 @@ * */ +#include #include -#include -#include + #include +#include #include "proc_comm.h" #include "clock.h" @@ -25,17 +26,30 @@ /* * glue for the proc_comm interface */ -int pc_clk_enable(unsigned id) +static int pc_clk_enable(struct clk *clk) { - int rc = msm_proc_comm(PCOM_CLKCTL_RPC_ENABLE, &id, NULL); + int rc; + int id = to_pcom_clk(clk)->id; + + /* Ignore clocks that are always on */ + if (id == P_EBI1_CLK || id == P_EBI1_FIXED_CLK) + return 0; + + rc = msm_proc_comm(PCOM_CLKCTL_RPC_ENABLE, &id, NULL); if (rc < 0) return rc; else return (int)id < 0 ? -EINVAL : 0; } -void pc_clk_disable(unsigned id) +static void pc_clk_disable(struct clk *clk) { + int id = to_pcom_clk(clk)->id; + + /* Ignore clocks that are always on */ + if (id == P_EBI1_CLK || id == P_EBI1_FIXED_CLK) + return; + msm_proc_comm(PCOM_CLKCTL_RPC_DISABLE, &id, NULL); } @@ -54,12 +68,19 @@ int pc_clk_reset(unsigned id, enum clk_reset_action action) return (int)id < 0 ? -EINVAL : 0; } -int pc_clk_set_rate(unsigned id, unsigned rate) +static int pc_reset(struct clk *clk, enum clk_reset_action action) +{ + int id = to_pcom_clk(clk)->id; + return pc_clk_reset(id, action); +} + +static int pc_clk_set_rate(struct clk *clk, unsigned rate) { /* The rate _might_ be rounded off to the nearest KHz value by the * remote function. So a return value of 0 doesn't necessarily mean * that the exact rate was set successfully. */ + int id = to_pcom_clk(clk)->id; int rc = msm_proc_comm(PCOM_CLKCTL_RPC_SET_RATE, &id, &rate); if (rc < 0) return rc; @@ -67,17 +88,25 @@ int pc_clk_set_rate(unsigned id, unsigned rate) return (int)id < 0 ? -EINVAL : 0; } -int pc_clk_set_min_rate(unsigned id, unsigned rate) +static int pc_clk_set_min_rate(struct clk *clk, unsigned rate) { - int rc = msm_proc_comm(PCOM_CLKCTL_RPC_MIN_RATE, &id, &rate); + int rc; + int id = to_pcom_clk(clk)->id; + bool ignore_error = (cpu_is_msm7x27() && id == P_EBI1_CLK && + rate >= INT_MAX); + + rc = msm_proc_comm(PCOM_CLKCTL_RPC_MIN_RATE, &id, &rate); if (rc < 0) return rc; + else if (ignore_error) + return 0; else return (int)id < 0 ? -EINVAL : 0; } -int pc_clk_set_max_rate(unsigned id, unsigned rate) +static int pc_clk_set_max_rate(struct clk *clk, unsigned rate) { + int id = to_pcom_clk(clk)->id; int rc = msm_proc_comm(PCOM_CLKCTL_RPC_MAX_RATE, &id, &rate); if (rc < 0) return rc; @@ -85,8 +114,9 @@ int pc_clk_set_max_rate(unsigned id, unsigned rate) return (int)id < 0 ? -EINVAL : 0; } -int pc_clk_set_flags(unsigned id, unsigned flags) +static int pc_clk_set_flags(struct clk *clk, unsigned flags) { + int id = to_pcom_clk(clk)->id; int rc = msm_proc_comm(PCOM_CLKCTL_RPC_SET_FLAGS, &id, &flags); if (rc < 0) return rc; @@ -94,30 +124,42 @@ int pc_clk_set_flags(unsigned id, unsigned flags) return (int)id < 0 ? -EINVAL : 0; } -unsigned pc_clk_get_rate(unsigned id) +static int pc_clk_set_ext_config(struct clk *clk, unsigned config) { + int id = to_pcom_clk(clk)->id; + int rc = msm_proc_comm(PCOM_CLKCTL_RPC_SET_EXT_CONFIG, &id, &config); + if (rc < 0) + return rc; + else + return (int)id < 0 ? -EINVAL : 0; +} + +static unsigned pc_clk_get_rate(struct clk *clk) +{ + int id = to_pcom_clk(clk)->id; if (msm_proc_comm(PCOM_CLKCTL_RPC_RATE, &id, NULL)) return 0; else return id; } -unsigned pc_clk_is_enabled(unsigned id) +static int pc_clk_is_enabled(struct clk *clk) { + int id = to_pcom_clk(clk)->id; if (msm_proc_comm(PCOM_CLKCTL_RPC_ENABLED, &id, NULL)) return 0; else return id; } -long pc_clk_round_rate(unsigned id, unsigned rate) +static long pc_clk_round_rate(struct clk *clk, unsigned rate) { /* Not really supported; pc_clk_set_rate() does rounding on it's own. */ return rate; } -static bool pc_clk_is_local(unsigned id) +static bool pc_clk_is_local(struct clk *clk) { return false; } @@ -126,7 +168,7 @@ struct clk_ops clk_ops_pcom = { .enable = pc_clk_enable, .disable = pc_clk_disable, .auto_off = pc_clk_disable, - .reset = pc_clk_reset, + .reset = pc_reset, .set_rate = pc_clk_set_rate, .set_min_rate = pc_clk_set_min_rate, .set_max_rate = pc_clk_set_max_rate, @@ -136,3 +178,19 @@ struct clk_ops clk_ops_pcom = { .round_rate = pc_clk_round_rate, .is_local = pc_clk_is_local, }; + +struct clk_ops clk_ops_pcom_ext_config = { + .enable = pc_clk_enable, + .disable = pc_clk_disable, + .auto_off = pc_clk_disable, + .reset = pc_reset, + .set_rate = pc_clk_set_ext_config, + .set_min_rate = pc_clk_set_min_rate, + .set_max_rate = pc_clk_set_max_rate, + .set_flags = pc_clk_set_flags, + .get_rate = pc_clk_get_rate, + .is_enabled = pc_clk_is_enabled, + .round_rate = pc_clk_round_rate, + .is_local = pc_clk_is_local, +}; + diff --git a/arch/arm/mach-msm/clock-pcom.h b/arch/arm/mach-msm/clock-pcom.h index 974d0032f3a..723c53c7d75 100644 --- a/arch/arm/mach-msm/clock-pcom.h +++ b/arch/arm/mach-msm/clock-pcom.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2009, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and @@ -117,24 +117,54 @@ #define P_GSBI_P_CLK 99 #define P_CE_CLK 100 /* Crypto engine */ #define P_CODEC_SSBI_CLK 101 +#define P_TCXO_DIV4_CLK 102 +#define P_GSBI1_QUP_CLK 103 +#define P_GSBI2_QUP_CLK 104 +#define P_GSBI1_QUP_P_CLK 105 +#define P_GSBI2_QUP_P_CLK 106 +#define P_DSI_CLK 107 +#define P_DSI_ESC_CLK 108 +#define P_DSI_PIXEL_CLK 109 +#define P_DSI_BYTE_CLK 110 +#define P_EBI1_FIXED_CLK 111 /* Not dropped during power-collapse */ +#define P_DSI_REF_CLK 112 +#define P_MDP_DSI_P_CLK 113 +#define P_AHB_M_CLK 114 +#define P_AHB_S_CLK 115 -#define P_NR_CLKS 102 +#define P_NR_CLKS 116 + +extern int pc_clk_reset(unsigned id, enum clk_reset_action action); struct clk_ops; extern struct clk_ops clk_ops_pcom; +extern struct clk_ops clk_ops_pcom_div2; +extern struct clk_ops clk_ops_pcom_ext_config; + +/* + * struct pcom_clk - proc_comm controlled clock + * @id: proc_comm identifier + * @c: + */ +struct pcom_clk { + unsigned id; + struct clk c; +}; -int pc_clk_reset(unsigned id, enum clk_reset_action action); +static inline struct pcom_clk *to_pcom_clk(struct clk *clk) +{ + return container_of(clk, struct pcom_clk, c); +} -#define CLK_PCOM(clk_name, clk_id, clk_dev, clk_flags) { \ - .con_id = clk_name, \ - .dev_id = clk_dev, \ - .clk = &(struct clk){ \ +#define DEFINE_CLK_PCOM(clk_name, clk_id, clk_flags) \ + struct pcom_clk clk_name = { \ .id = P_##clk_id, \ - .remote_id = P_##clk_id, \ - .ops = &clk_ops_pcom, \ - .flags = clk_flags, \ - .dbg_name = #clk_id, \ - }, \ + .c = { \ + .ops = &clk_ops_pcom, \ + .flags = clk_flags, \ + .dbg_name = #clk_id, \ + CLK_INIT(clk_name.c), \ + }, \ } #endif diff --git a/arch/arm/mach-msm/clock-rpm.c b/arch/arm/mach-msm/clock-rpm.c new file mode 100644 index 00000000000..13263445950 --- /dev/null +++ b/arch/arm/mach-msm/clock-rpm.c @@ -0,0 +1,198 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + +#include "rpm_resources.h" +#include "clock.h" +#include "clock-rpm.h" + +static DEFINE_SPINLOCK(rpm_clock_lock); + +static int rpm_clk_enable(struct clk *clk) +{ + unsigned long flags; + struct msm_rpm_iv_pair iv; + int rc = 0; + struct rpm_clk *r = to_rpm_clk(clk); + unsigned this_khz, this_sleep_khz; + unsigned peer_khz = 0, peer_sleep_khz = 0; + struct rpm_clk *peer = r->peer; + + spin_lock_irqsave(&rpm_clock_lock, flags); + + this_khz = r->last_set_khz; + /* Don't send requests to the RPM if the rate has not been set. */ + if (this_khz == 0) + goto out; + + this_sleep_khz = r->last_set_sleep_khz; + + iv.id = r->rpm_clk_id; + + /* Take peer clock's rate into account only if it's enabled. */ + if (peer->enabled) { + peer_khz = peer->last_set_khz; + peer_sleep_khz = peer->last_set_sleep_khz; + } + + iv.value = max(this_khz, peer_khz); + rc = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_0, &iv, 1); + if (rc) + goto out; + + iv.value = max(this_sleep_khz, peer_sleep_khz); + rc = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_SLEEP, &iv, 1); +out: + if (!rc) + r->enabled = true; + + spin_unlock_irqrestore(&rpm_clock_lock, flags); + + return rc; +} + +static void rpm_clk_disable(struct clk *clk) +{ + unsigned long flags; + struct rpm_clk *r = to_rpm_clk(clk); + + spin_lock_irqsave(&rpm_clock_lock, flags); + + if (r->last_set_khz) { + struct msm_rpm_iv_pair iv; + struct rpm_clk *peer = r->peer; + unsigned peer_khz = 0, peer_sleep_khz = 0; + int rc; + + iv.id = r->rpm_clk_id; + + /* Take peer clock's rate into account only if it's enabled. */ + if (peer->enabled) { + peer_khz = peer->last_set_khz; + peer_sleep_khz = peer->last_set_sleep_khz; + } + + iv.value = peer_khz; + rc = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_0, &iv, 1); + if (rc) + goto out; + + iv.value = peer_sleep_khz; + rc = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_SLEEP, &iv, 1); + } + r->enabled = false; +out: + spin_unlock_irqrestore(&rpm_clock_lock, flags); + + return; +} + +static void rpm_clk_auto_off(struct clk *clk) +{ + /* Not supported */ +} + +static int rpm_clk_set_min_rate(struct clk *clk, unsigned rate) +{ + unsigned long flags; + struct rpm_clk *r = to_rpm_clk(clk); + unsigned this_khz, this_sleep_khz; + int rc = 0; + + this_khz = DIV_ROUND_UP(rate, 1000); + + spin_lock_irqsave(&rpm_clock_lock, flags); + + /* Ignore duplicate requests. */ + if (r->last_set_khz == this_khz) + goto out; + + /* Active-only clocks don't care what the rate is during sleep. So, + * they vote for zero. */ + if (r->active_only) + this_sleep_khz = 0; + else + this_sleep_khz = this_khz; + + if (r->enabled) { + struct msm_rpm_iv_pair iv; + struct rpm_clk *peer = r->peer; + unsigned peer_khz = 0, peer_sleep_khz = 0; + + iv.id = r->rpm_clk_id; + + /* Take peer clock's rate into account only if it's enabled. */ + if (peer->enabled) { + peer_khz = peer->last_set_khz; + peer_sleep_khz = peer->last_set_sleep_khz; + } + + iv.value = max(this_khz, peer_khz); + rc = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_0, &iv, 1); + if (rc) + goto out; + + iv.value = max(this_sleep_khz, peer_sleep_khz); + rc = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_SLEEP, &iv, 1); + } + if (!rc) { + r->last_set_khz = this_khz; + r->last_set_sleep_khz = this_sleep_khz; + } + +out: + spin_unlock_irqrestore(&rpm_clock_lock, flags); + + return rc; +} + +static unsigned rpm_clk_get_rate(struct clk *clk) +{ + struct rpm_clk *r = to_rpm_clk(clk); + struct msm_rpm_iv_pair iv = { r->rpm_status_id }; + int rc; + + rc = msm_rpm_get_status(&iv, 1); + if (rc < 0) + return rc; + return iv.value * 1000; +} + +static int rpm_clk_is_enabled(struct clk *clk) +{ + return !!(rpm_clk_get_rate(clk)); +} + +static long rpm_clk_round_rate(struct clk *clk, unsigned rate) +{ + /* Not supported. */ + return rate; +} + +static bool rpm_clk_is_local(struct clk *clk) +{ + return false; +} + +struct clk_ops clk_ops_rpm = { + .enable = rpm_clk_enable, + .disable = rpm_clk_disable, + .auto_off = rpm_clk_auto_off, + .set_min_rate = rpm_clk_set_min_rate, + .get_rate = rpm_clk_get_rate, + .is_enabled = rpm_clk_is_enabled, + .round_rate = rpm_clk_round_rate, + .is_local = rpm_clk_is_local, +}; diff --git a/arch/arm/mach-msm/clock-rpm.h b/arch/arm/mach-msm/clock-rpm.h new file mode 100644 index 00000000000..3490143aef0 --- /dev/null +++ b/arch/arm/mach-msm/clock-rpm.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_CLOCK_RPM_H +#define __ARCH_ARM_MACH_MSM_CLOCK_RPM_H + +#include + +struct clk_ops; +extern struct clk_ops clk_ops_rpm; + +struct rpm_clk { + const int rpm_clk_id; + const int rpm_status_id; + const bool active_only; + unsigned last_set_khz; + /* 0 if active_only. Otherwise, same as last_set_khz. */ + unsigned last_set_sleep_khz; + bool enabled; + + struct rpm_clk *peer; + struct clk c; +}; + +static inline struct rpm_clk *to_rpm_clk(struct clk *clk) +{ + return container_of(clk, struct rpm_clk, c); +} + +#define DEFINE_CLK_RPM(name, active, r_id) \ + static struct rpm_clk active; \ + static struct rpm_clk name = { \ + .rpm_clk_id = MSM_RPM_ID_##r_id##_CLK, \ + .rpm_status_id = MSM_RPM_STATUS_ID_##r_id##_CLK, \ + .peer = &active, \ + .c = { \ + .ops = &clk_ops_rpm, \ + .flags = CLKFLAG_SKIP_AUTO_OFF | CLKFLAG_MIN, \ + .dbg_name = #name, \ + CLK_INIT(name.c), \ + }, \ + }; \ + static struct rpm_clk active = { \ + .rpm_clk_id = MSM_RPM_ID_##r_id##_CLK, \ + .rpm_status_id = MSM_RPM_STATUS_ID_##r_id##_CLK, \ + .peer = &name, \ + .active_only = true, \ + .c = { \ + .ops = &clk_ops_rpm, \ + .flags = CLKFLAG_SKIP_AUTO_OFF | CLKFLAG_MIN, \ + .dbg_name = #active, \ + CLK_INIT(active.c), \ + }, \ + }; + +#endif diff --git a/arch/arm/mach-msm/clock-voter.c b/arch/arm/mach-msm/clock-voter.c new file mode 100644 index 00000000000..1c742b171c8 --- /dev/null +++ b/arch/arm/mach-msm/clock-voter.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include "clock.h" +#include "clock-voter.h" + +static DEFINE_SPINLOCK(voter_clk_lock); + +/* Aggregate the rate of clocks that are currently on. */ +static unsigned voter_clk_aggregate_rate(const struct clk *parent) +{ + struct clk *clk; + unsigned rate = 0; + + list_for_each_entry(clk, &parent->children, siblings) { + struct clk_voter *v = to_clk_voter(clk); + if (v->enabled) + rate = max(v->rate, rate); + } + return rate; +} + +static int voter_clk_set_rate(struct clk *clk, unsigned rate) +{ + int ret = 0; + unsigned long flags; + struct clk *clkp; + struct clk_voter *clkh, *v = to_clk_voter(clk); + unsigned cur_rate, new_rate, other_rate = 0; + + spin_lock_irqsave(&voter_clk_lock, flags); + + if (v->enabled) { + struct clk *parent = v->parent; + + /* + * Get the aggregate rate without this clock's vote and update + * if the new rate is different than the current rate + */ + list_for_each_entry(clkp, &parent->children, siblings) { + clkh = to_clk_voter(clkp); + if (clkh->enabled && clkh != v) + other_rate = max(clkh->rate, other_rate); + } + + cur_rate = max(other_rate, v->rate); + new_rate = max(other_rate, rate); + + if (new_rate != cur_rate) { + ret = clk_set_min_rate(parent, new_rate); + if (ret) + goto unlock; + } + } + v->rate = rate; +unlock: + spin_unlock_irqrestore(&voter_clk_lock, flags); + + return ret; +} + +static int voter_clk_enable(struct clk *clk) +{ + int ret; + unsigned long flags; + unsigned cur_rate; + struct clk *parent; + struct clk_voter *v = to_clk_voter(clk); + + spin_lock_irqsave(&voter_clk_lock, flags); + parent = v->parent; + + /* + * Increase the rate if this clock is voting for a higher rate + * than the current rate. + */ + cur_rate = voter_clk_aggregate_rate(parent); + if (v->rate > cur_rate) { + ret = clk_set_min_rate(parent, v->rate); + if (ret) + goto out; + } + v->enabled = true; +out: + spin_unlock_irqrestore(&voter_clk_lock, flags); + + return ret; +} + +static void voter_clk_disable(struct clk *clk) +{ + unsigned long flags; + struct clk *parent; + struct clk_voter *v = to_clk_voter(clk); + unsigned cur_rate, new_rate; + + spin_lock_irqsave(&voter_clk_lock, flags); + parent = v->parent; + + /* + * Decrease the rate if this clock was the only one voting for + * the highest rate. + */ + v->enabled = false; + new_rate = voter_clk_aggregate_rate(parent); + cur_rate = max(new_rate, v->rate); + + if (new_rate < cur_rate) + clk_set_min_rate(parent, new_rate); + + spin_unlock_irqrestore(&voter_clk_lock, flags); +} + +static unsigned voter_clk_get_rate(struct clk *clk) +{ + unsigned rate; + unsigned long flags; + struct clk_voter *v = to_clk_voter(clk); + + spin_lock_irqsave(&voter_clk_lock, flags); + rate = v->rate; + spin_unlock_irqrestore(&voter_clk_lock, flags); + + return rate; +} + +static int voter_clk_is_enabled(struct clk *clk) +{ + struct clk_voter *v = to_clk_voter(clk); + return v->enabled; +} + +static long voter_clk_round_rate(struct clk *clk, unsigned rate) +{ + struct clk_voter *v = to_clk_voter(clk); + return clk_round_rate(v->parent, rate); +} + +static int voter_clk_set_parent(struct clk *clk, struct clk *parent) +{ + unsigned long flags; + + spin_lock_irqsave(&voter_clk_lock, flags); + if (list_empty(&clk->siblings)) + list_add(&clk->siblings, &parent->children); + spin_unlock_irqrestore(&voter_clk_lock, flags); + + return 0; +} + +static struct clk *voter_clk_get_parent(struct clk *clk) +{ + struct clk_voter *v = to_clk_voter(clk); + return v->parent; +} + +static bool voter_clk_is_local(struct clk *clk) +{ + return true; +} + +struct clk_ops clk_ops_voter = { + .enable = voter_clk_enable, + .disable = voter_clk_disable, + .set_rate = voter_clk_set_rate, + .set_min_rate = voter_clk_set_rate, + .get_rate = voter_clk_get_rate, + .is_enabled = voter_clk_is_enabled, + .round_rate = voter_clk_round_rate, + .set_parent = voter_clk_set_parent, + .get_parent = voter_clk_get_parent, + .is_local = voter_clk_is_local, +}; diff --git a/arch/arm/mach-msm/clock-voter.h b/arch/arm/mach-msm/clock-voter.h new file mode 100644 index 00000000000..64baf3bc47c --- /dev/null +++ b/arch/arm/mach-msm/clock-voter.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_CLOCK_VOTER_H +#define __ARCH_ARM_MACH_MSM_CLOCK_VOTER_H + +struct clk_ops; +extern struct clk_ops clk_ops_voter; + +struct clk_voter { + bool enabled; + unsigned rate; + struct clk *parent; + struct clk c; +}; + +static inline struct clk_voter *to_clk_voter(struct clk *clk) +{ + return container_of(clk, struct clk_voter, c); +} + +#define DEFINE_CLK_VOTER(clk_name, _parent) \ + struct clk_voter clk_name = { \ + .parent = _parent, \ + .c = { \ + .dbg_name = #clk_name, \ + .ops = &clk_ops_voter, \ + .flags = CLKFLAG_SKIP_AUTO_OFF, \ + CLK_INIT(clk_name.c), \ + }, \ + } + +#endif diff --git a/arch/arm/mach-msm/clock.c b/arch/arm/mach-msm/clock.c index 22a53766962..05265f8af34 100644 --- a/arch/arm/mach-msm/clock.c +++ b/arch/arm/mach-msm/clock.c @@ -1,7 +1,7 @@ /* arch/arm/mach-msm/clock.c * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2007-2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2007-2011, Code Aurora Forum. 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 @@ -15,109 +15,141 @@ */ #include -#include #include #include -#include -#include -#include #include #include +#include #include #include "clock.h" -static DEFINE_MUTEX(clocks_mutex); -static DEFINE_SPINLOCK(clocks_lock); -static LIST_HEAD(clocks); - /* * Standard clock functions defined in include/linux/clk.h */ int clk_enable(struct clk *clk) { + int ret = 0; unsigned long flags; - spin_lock_irqsave(&clocks_lock, flags); + struct clk *parent; + + if (!clk) + return 0; + + spin_lock_irqsave(&clk->lock, flags); + if (clk->count == 0) { + parent = clk_get_parent(clk); + ret = clk_enable(parent); + if (ret) + goto out; + + if (clk->ops->enable) + ret = clk->ops->enable(clk); + if (ret) { + clk_disable(parent); + goto out; + } + } clk->count++; - if (clk->count == 1) - clk->ops->enable(clk->id); - spin_unlock_irqrestore(&clocks_lock, flags); - return 0; +out: + spin_unlock_irqrestore(&clk->lock, flags); + + return ret; } EXPORT_SYMBOL(clk_enable); void clk_disable(struct clk *clk) { unsigned long flags; - spin_lock_irqsave(&clocks_lock, flags); - BUG_ON(clk->count == 0); + struct clk *parent; + + if (!clk) + return; + + spin_lock_irqsave(&clk->lock, flags); + if (WARN_ON(clk->count == 0)) + goto out; + if (clk->count == 1) { + if (clk->ops->disable) + clk->ops->disable(clk); + parent = clk_get_parent(clk); + clk_disable(parent); + } clk->count--; - if (clk->count == 0) - clk->ops->disable(clk->id); - spin_unlock_irqrestore(&clocks_lock, flags); +out: + spin_unlock_irqrestore(&clk->lock, flags); } EXPORT_SYMBOL(clk_disable); int clk_reset(struct clk *clk, enum clk_reset_action action) { - return clk->ops->reset(clk->remote_id, action); + if (!clk->ops->reset) + return -ENOSYS; + + return clk->ops->reset(clk, action); } EXPORT_SYMBOL(clk_reset); unsigned long clk_get_rate(struct clk *clk) { - return clk->ops->get_rate(clk->id); + if (!clk->ops->get_rate) + return 0; + + return clk->ops->get_rate(clk); } EXPORT_SYMBOL(clk_get_rate); int clk_set_rate(struct clk *clk, unsigned long rate) { - int ret; - if (clk->flags & CLKFLAG_MAX) { - ret = clk->ops->set_max_rate(clk->id, rate); - if (ret) - return ret; - } - if (clk->flags & CLKFLAG_MIN) { - ret = clk->ops->set_min_rate(clk->id, rate); - if (ret) - return ret; - } + if (!clk->ops->set_rate) + return -ENOSYS; - if (clk->flags & CLKFLAG_MAX || clk->flags & CLKFLAG_MIN) - return ret; - - return clk->ops->set_rate(clk->id, rate); + return clk->ops->set_rate(clk, rate); } EXPORT_SYMBOL(clk_set_rate); long clk_round_rate(struct clk *clk, unsigned long rate) { - return clk->ops->round_rate(clk->id, rate); + if (!clk->ops->round_rate) + return -ENOSYS; + + return clk->ops->round_rate(clk, rate); } EXPORT_SYMBOL(clk_round_rate); int clk_set_min_rate(struct clk *clk, unsigned long rate) { - return clk->ops->set_min_rate(clk->id, rate); + if (!clk->ops->set_min_rate) + return -ENOSYS; + + return clk->ops->set_min_rate(clk, rate); } EXPORT_SYMBOL(clk_set_min_rate); int clk_set_max_rate(struct clk *clk, unsigned long rate) { - return clk->ops->set_max_rate(clk->id, rate); + if (!clk->ops->set_max_rate) + return -ENOSYS; + + return clk->ops->set_max_rate(clk, rate); } EXPORT_SYMBOL(clk_set_max_rate); int clk_set_parent(struct clk *clk, struct clk *parent) { - return -ENOSYS; + if (!clk->ops->set_parent) + return 0; + + return clk->ops->set_parent(clk, parent); } EXPORT_SYMBOL(clk_set_parent); struct clk *clk_get_parent(struct clk *clk) { - return ERR_PTR(-ENOSYS); + if (!clk->ops->get_parent) + return NULL; + + return clk->ops->get_parent(clk); } EXPORT_SYMBOL(clk_get_parent); @@ -125,60 +157,57 @@ int clk_set_flags(struct clk *clk, unsigned long flags) { if (clk == NULL || IS_ERR(clk)) return -EINVAL; - return clk->ops->set_flags(clk->id, flags); + if (!clk->ops->set_flags) + return -ENOSYS; + + return clk->ops->set_flags(clk, flags); } EXPORT_SYMBOL(clk_set_flags); -/* EBI1 is the only shared clock that several clients want to vote on as of - * this commit. If this changes in the future, then it might be better to - * make clk_min_rate handle the voting or make ebi1_clk_set_min_rate more - * generic to support different clocks. - */ -static struct clk *ebi1_clk; +static struct clk_lookup *msm_clocks; +static unsigned msm_num_clocks; -void __init msm_clock_init(struct clk_lookup *clock_tbl, unsigned num_clocks) +void __init msm_clock_init(struct clk_lookup *clock_tbl, size_t num_clocks) { unsigned n; - mutex_lock(&clocks_mutex); for (n = 0; n < num_clocks; n++) { - clkdev_add(&clock_tbl[n]); - list_add_tail(&clock_tbl[n].clk->list, &clocks); + struct clk *clk = clock_tbl[n].clk; + struct clk *parent = clk_get_parent(clk); + clk_set_parent(clk, parent); } - mutex_unlock(&clocks_mutex); - - ebi1_clk = clk_get(NULL, "ebi1_clk"); - BUG_ON(ebi1_clk == NULL); + clkdev_add_table(clock_tbl, num_clocks); + msm_clocks = clock_tbl; + msm_num_clocks = num_clocks; } -/* The bootloader and/or AMSS may have left various clocks enabled. - * Disable any clocks that belong to us (CLKFLAG_AUTO_OFF) but have - * not been explicitly enabled by a clk_enable() call. +/* + * The bootloader and/or AMSS may have left various clocks enabled. + * Disable any clocks that have not been explicitly enabled by a + * clk_enable() call and don't have the CLKFLAG_SKIP_AUTO_OFF flag. */ static int __init clock_late_init(void) { + unsigned n; unsigned long flags; - struct clk *clk; unsigned count = 0; - clock_debug_init(); - mutex_lock(&clocks_mutex); - list_for_each_entry(clk, &clocks, list) { + clock_debug_init(msm_clocks, msm_num_clocks); + for (n = 0; n < msm_num_clocks; n++) { + struct clk *clk = msm_clocks[n].clk; + clock_debug_add(clk); - if (clk->flags & CLKFLAG_AUTO_OFF) { - spin_lock_irqsave(&clocks_lock, flags); - if (!clk->count) { + if (!(clk->flags & CLKFLAG_SKIP_AUTO_OFF)) { + spin_lock_irqsave(&clk->lock, flags); + if (!clk->count && clk->ops->auto_off) { count++; - clk->ops->auto_off(clk->id); + clk->ops->auto_off(clk); } - spin_unlock_irqrestore(&clocks_lock, flags); + spin_unlock_irqrestore(&clk->lock, flags); } } - mutex_unlock(&clocks_mutex); pr_info("clock_late_init() disabled %d unused clocks\n", count); return 0; } - late_initcall(clock_late_init); - diff --git a/arch/arm/mach-msm/clock.h b/arch/arm/mach-msm/clock.h index 2c007f606d2..e7d84e5050a 100644 --- a/arch/arm/mach-msm/clock.h +++ b/arch/arm/mach-msm/clock.h @@ -1,7 +1,7 @@ /* arch/arm/mach-msm/clock.h * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2007-2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2007-2011, Code Aurora Forum. 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 @@ -18,7 +18,11 @@ #define __ARCH_ARM_MACH_MSM_CLOCK_H #include +#include #include +#include +#include + #include #define CLKFLAG_INVERT 0x00000001 @@ -27,46 +31,76 @@ #define CLKFLAG_NORESET 0x00000008 #define CLK_FIRST_AVAILABLE_FLAG 0x00000100 -#define CLKFLAG_AUTO_OFF 0x00000200 +#define CLKFLAG_SKIP_AUTO_OFF 0x00000200 #define CLKFLAG_MIN 0x00000400 #define CLKFLAG_MAX 0x00000800 struct clk_ops { - int (*enable)(unsigned id); - void (*disable)(unsigned id); - void (*auto_off)(unsigned id); - int (*reset)(unsigned id, enum clk_reset_action action); - int (*set_rate)(unsigned id, unsigned rate); - int (*set_min_rate)(unsigned id, unsigned rate); - int (*set_max_rate)(unsigned id, unsigned rate); - int (*set_flags)(unsigned id, unsigned flags); - unsigned (*get_rate)(unsigned id); - unsigned (*is_enabled)(unsigned id); - long (*round_rate)(unsigned id, unsigned rate); - bool (*is_local)(unsigned id); + int (*enable)(struct clk *clk); + void (*disable)(struct clk *clk); + void (*auto_off)(struct clk *clk); + int (*reset)(struct clk *clk, enum clk_reset_action action); + int (*set_rate)(struct clk *clk, unsigned rate); + int (*set_min_rate)(struct clk *clk, unsigned rate); + int (*set_max_rate)(struct clk *clk, unsigned rate); + int (*set_flags)(struct clk *clk, unsigned flags); + unsigned (*get_rate)(struct clk *clk); + int (*list_rate)(struct clk *clk, unsigned n); + int (*is_enabled)(struct clk *clk); + long (*round_rate)(struct clk *clk, unsigned rate); + int (*set_parent)(struct clk *clk, struct clk *parent); + struct clk *(*get_parent)(struct clk *clk); + bool (*is_local)(struct clk *clk); }; +/** + * struct clk + * @count: enable refcount + * @lock: protects clk_enable()/clk_disable() path and @count + */ struct clk { - uint32_t id; - uint32_t remote_id; - uint32_t count; uint32_t flags; struct clk_ops *ops; const char *dbg_name; - struct list_head list; + + struct list_head children; + struct list_head siblings; + + unsigned count; + spinlock_t lock; }; -#define OFF CLKFLAG_AUTO_OFF -#define CLK_MIN CLKFLAG_MIN -#define CLK_MAX CLKFLAG_MAX -#define CLK_MINMAX (CLK_MIN | CLK_MAX) +#define CLK_INIT(name) \ + .lock = __SPIN_LOCK_UNLOCKED((name).lock), \ + .children = LIST_HEAD_INIT((name).children), \ + .siblings = LIST_HEAD_INIT((name).siblings) + +void msm_clock_init(struct clk_lookup *clock_tbl, size_t num_clocks); +void msm7x30_clock_init(void); +void msm8660_clock_init(void); +void msm8960_clock_init(void); +void msm8960_clock_init_dummy(void); #ifdef CONFIG_DEBUG_FS -int __init clock_debug_init(void); +int __init clock_debug_init(struct clk_lookup *clocks, unsigned num_clocks); int __init clock_debug_add(struct clk *clock); +void clock_debug_print_enabled(void); #else -static inline int __init clock_debug_init(void) { return 0; } +static inline int __init clock_debug_init(struct clk_lookup *clocks, + unsigned num_clocks) { return 0; } static inline int __init clock_debug_add(struct clk *clock) { return 0; } +static inline void clock_debug_print_enabled(void) { return; } #endif +extern struct clk dummy_clk; + +#define CLK_DUMMY(clk_name, clk_id, clk_dev, flags) { \ + .con_id = clk_name, \ + .dev_id = clk_dev, \ + .clk = &dummy_clk, \ + } + +#define CLK_LOOKUP(con, c, dev) { .con_id = con, .clk = &c, .dev_id = dev } + #endif + diff --git a/arch/arm/mach-msm/cp14.S b/arch/arm/mach-msm/cp14.S new file mode 100644 index 00000000000..94c74d37a9b --- /dev/null +++ b/arch/arm/mach-msm/cp14.S @@ -0,0 +1,669 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#if (NR_CPUS > 2) +#error code only tested for 1 or 2 CPUs. +#endif + +/* Add 1 slot to store the register count for ETM state */ +#define MAX_ETM_REGS (56 + 1) +#define MAX_ETM_STATE_SIZE (MAX_ETM_REGS * 4) + +.global etm_read_reg +etm_read_reg: + cmp r0, #0x7F + bhi read_high + ldr r2, =read_table + add r2, r2, r0, lsl #3 + mov pc, r2 + +read_high: + cmp r0, #0xC1 + beq read_c1 + cmp r0, #0xC4 + beq read_c4 + cmp r0, #0xC5 + beq read_c5 + mov r0, #0 + bx lr + +read_c1: + mrc p14, 1, r0, c1, c1, 4 // register 0xC1 (OS Lock Status Reg) + bx lr + +read_c4: + mrc p14, 1, r0, c1, c4, 4 // register 0xC4 (Powerdown Ctl Reg) + bx lr + +read_c5: + mrc p14, 1, r0, c1, c5, 4 // register 0xC5 (Powerdown Status Reg) + bx lr + +read_table: + mrc p14, 1, r0, c0, c0, 0 // register 0x00 + bx lr + mrc p14, 1, r0, c0, c1, 0 // register 0x01 + bx lr + mrc p14, 1, r0, c0, c2, 0 // register 0x02 + bx lr + mrc p14, 1, r0, c0, c3, 0 // register 0x03 + bx lr + mrc p14, 1, r0, c0, c4, 0 // register 0x04 + bx lr + mrc p14, 1, r0, c0, c5, 0 // register 0x05 + bx lr + mrc p14, 1, r0, c0, c6, 0 // register 0x06 + bx lr + mrc p14, 1, r0, c0, c7, 0 // register 0x07 + bx lr + mrc p14, 1, r0, c0, c8, 0 // register 0x08 + bx lr + mrc p14, 1, r0, c0, c9, 0 // register 0x09 + bx lr + mrc p14, 1, r0, c0, c10, 0 // register 0x0A + bx lr + mrc p14, 1, r0, c0, c11, 0 // register 0x0B + bx lr + mrc p14, 1, r0, c0, c12, 0 // register 0x0C + bx lr + mrc p14, 1, r0, c0, c13, 0 // register 0x0D + bx lr + mrc p14, 1, r0, c0, c14, 0 // register 0x0E + bx lr + mrc p14, 1, r0, c0, c15, 0 // register 0x0F + bx lr + mrc p14, 1, r0, c0, c0, 1 // register 0x10 + bx lr + mrc p14, 1, r0, c0, c1, 1 // register 0x11 + bx lr + mrc p14, 1, r0, c0, c2, 1 // register 0x12 + bx lr + mrc p14, 1, r0, c0, c3, 1 // register 0x13 + bx lr + mrc p14, 1, r0, c0, c4, 1 // register 0x14 + bx lr + mrc p14, 1, r0, c0, c5, 1 // register 0x15 + bx lr + mrc p14, 1, r0, c0, c6, 1 // register 0x16 + bx lr + mrc p14, 1, r0, c0, c7, 1 // register 0x17 + bx lr + mrc p14, 1, r0, c0, c8, 1 // register 0x18 + bx lr + mrc p14, 1, r0, c0, c9, 1 // register 0x19 + bx lr + mrc p14, 1, r0, c0, c10, 1 // register 0x1A + bx lr + mrc p14, 1, r0, c0, c11, 1 // register 0x1B + bx lr + mrc p14, 1, r0, c0, c12, 1 // register 0x1C + bx lr + mrc p14, 1, r0, c0, c13, 1 // register 0x1D + bx lr + mrc p14, 1, r0, c0, c14, 1 // register 0x1E + bx lr + mrc p14, 1, r0, c0, c15, 1 // register 0x1F + bx lr + mrc p14, 1, r0, c0, c0, 2 // register 0x20 + bx lr + mrc p14, 1, r0, c0, c1, 2 // register 0x21 + bx lr + mrc p14, 1, r0, c0, c2, 2 // register 0x22 + bx lr + mrc p14, 1, r0, c0, c3, 2 // register 0x23 + bx lr + mrc p14, 1, r0, c0, c4, 2 // register 0x24 + bx lr + mrc p14, 1, r0, c0, c5, 2 // register 0x25 + bx lr + mrc p14, 1, r0, c0, c6, 2 // register 0x26 + bx lr + mrc p14, 1, r0, c0, c7, 2 // register 0x27 + bx lr + mrc p14, 1, r0, c0, c8, 2 // register 0x28 + bx lr + mrc p14, 1, r0, c0, c9, 2 // register 0x29 + bx lr + mrc p14, 1, r0, c0, c10, 2 // register 0x2A + bx lr + mrc p14, 1, r0, c0, c11, 2 // register 0x2B + bx lr + mrc p14, 1, r0, c0, c12, 2 // register 0x2C + bx lr + mrc p14, 1, r0, c0, c13, 2 // register 0x2D + bx lr + mrc p14, 1, r0, c0, c14, 2 // register 0x2E + bx lr + mrc p14, 1, r0, c0, c15, 2 // register 0x2F + bx lr + mrc p14, 1, r0, c0, c0, 3 // register 0x30 + bx lr + mrc p14, 1, r0, c0, c1, 3 // register 0x31 + bx lr + mrc p14, 1, r0, c0, c2, 3 // register 0x32 + bx lr + mrc p14, 1, r0, c0, c3, 3 // register 0x33 + bx lr + mrc p14, 1, r0, c0, c4, 3 // register 0x34 + bx lr + mrc p14, 1, r0, c0, c5, 3 // register 0x35 + bx lr + mrc p14, 1, r0, c0, c6, 3 // register 0x36 + bx lr + mrc p14, 1, r0, c0, c7, 3 // register 0x37 + bx lr + mrc p14, 1, r0, c0, c8, 3 // register 0x38 + bx lr + mrc p14, 1, r0, c0, c9, 3 // register 0x39 + bx lr + mrc p14, 1, r0, c0, c10, 3 // register 0x3A + bx lr + mrc p14, 1, r0, c0, c11, 3 // register 0x3B + bx lr + mrc p14, 1, r0, c0, c12, 3 // register 0x3C + bx lr + mrc p14, 1, r0, c0, c13, 3 // register 0x3D + bx lr + mrc p14, 1, r0, c0, c14, 3 // register 0x3E + bx lr + mrc p14, 1, r0, c0, c15, 3 // register 0x3F + bx lr + mrc p14, 1, r0, c0, c0, 4 // register 0x40 + bx lr + mrc p14, 1, r0, c0, c1, 4 // register 0x41 + bx lr + mrc p14, 1, r0, c0, c2, 4 // register 0x42 + bx lr + mrc p14, 1, r0, c0, c3, 4 // register 0x43 + bx lr + mrc p14, 1, r0, c0, c4, 4 // register 0x44 + bx lr + mrc p14, 1, r0, c0, c5, 4 // register 0x45 + bx lr + mrc p14, 1, r0, c0, c6, 4 // register 0x46 + bx lr + mrc p14, 1, r0, c0, c7, 4 // register 0x47 + bx lr + mrc p14, 1, r0, c0, c8, 4 // register 0x48 + bx lr + mrc p14, 1, r0, c0, c9, 4 // register 0x49 + bx lr + mrc p14, 1, r0, c0, c10, 4 // register 0x4A + bx lr + mrc p14, 1, r0, c0, c11, 4 // register 0x4B + bx lr + mrc p14, 1, r0, c0, c12, 4 // register 0x4C + bx lr + mrc p14, 1, r0, c0, c13, 4 // register 0x4D + bx lr + mrc p14, 1, r0, c0, c14, 4 // register 0x4E + bx lr + mrc p14, 1, r0, c0, c15, 4 // register 0x4F + bx lr + mrc p14, 1, r0, c0, c0, 5 // register 0x50 + bx lr + mrc p14, 1, r0, c0, c1, 5 // register 0x51 + bx lr + mrc p14, 1, r0, c0, c2, 5 // register 0x52 + bx lr + mrc p14, 1, r0, c0, c3, 5 // register 0x53 + bx lr + mrc p14, 1, r0, c0, c4, 5 // register 0x54 + bx lr + mrc p14, 1, r0, c0, c5, 5 // register 0x55 + bx lr + mrc p14, 1, r0, c0, c6, 5 // register 0x56 + bx lr + mrc p14, 1, r0, c0, c7, 5 // register 0x57 + bx lr + mrc p14, 1, r0, c0, c8, 5 // register 0x58 + bx lr + mrc p14, 1, r0, c0, c9, 5 // register 0x59 + bx lr + mrc p14, 1, r0, c0, c10, 5 // register 0x5A + bx lr + mrc p14, 1, r0, c0, c11, 5 // register 0x5B + bx lr + mrc p14, 1, r0, c0, c12, 5 // register 0x5C + bx lr + mrc p14, 1, r0, c0, c13, 5 // register 0x5D + bx lr + mrc p14, 1, r0, c0, c14, 5 // register 0x5E + bx lr + mrc p14, 1, r0, c0, c15, 5 // register 0x5F + bx lr + mrc p14, 1, r0, c0, c0, 6 // register 0x60 + bx lr + mrc p14, 1, r0, c0, c1, 6 // register 0x61 + bx lr + mrc p14, 1, r0, c0, c2, 6 // register 0x62 + bx lr + mrc p14, 1, r0, c0, c3, 6 // register 0x63 + bx lr + mrc p14, 1, r0, c0, c4, 6 // register 0x64 + bx lr + mrc p14, 1, r0, c0, c5, 6 // register 0x65 + bx lr + mrc p14, 1, r0, c0, c6, 6 // register 0x66 + bx lr + mrc p14, 1, r0, c0, c7, 6 // register 0x67 + bx lr + mrc p14, 1, r0, c0, c8, 6 // register 0x68 + bx lr + mrc p14, 1, r0, c0, c9, 6 // register 0x69 + bx lr + mrc p14, 1, r0, c0, c10, 6 // register 0x6A + bx lr + mrc p14, 1, r0, c0, c11, 6 // register 0x6B + bx lr + mrc p14, 1, r0, c0, c12, 6 // register 0x6C + bx lr + mrc p14, 1, r0, c0, c13, 6 // register 0x6D + bx lr + mrc p14, 1, r0, c0, c14, 6 // register 0x6E + bx lr + mrc p14, 1, r0, c0, c15, 6 // register 0x6F + bx lr + mrc p14, 1, r0, c0, c0, 7 // register 0x70 + bx lr + mrc p14, 1, r0, c0, c1, 7 // register 0x71 + bx lr + mrc p14, 1, r0, c0, c2, 7 // register 0x72 + bx lr + mrc p14, 1, r0, c0, c3, 7 // register 0x73 + bx lr + mrc p14, 1, r0, c0, c4, 7 // register 0x74 + bx lr + mrc p14, 1, r0, c0, c5, 7 // register 0x75 + bx lr + mrc p14, 1, r0, c0, c6, 7 // register 0x76 + bx lr + mrc p14, 1, r0, c0, c7, 7 // register 0x77 + bx lr + mrc p14, 1, r0, c0, c8, 7 // register 0x78 + bx lr + mrc p14, 1, r0, c0, c9, 7 // register 0x79 + bx lr + mrc p14, 1, r0, c0, c10, 7 // register 0x7A + bx lr + mrc p14, 1, r0, c0, c11, 7 // register 0x7B + bx lr + mrc p14, 1, r0, c0, c12, 7 // register 0x7C + bx lr + mrc p14, 1, r0, c0, c13, 7 // register 0x7D + bx lr + mrc p14, 1, r0, c0, c14, 7 // register 0x7E + bx lr + mrc p14, 1, r0, c0, c15, 7 // register 0x7F + bx lr + +.global etm_write_reg +etm_write_reg: + cmp r0, #0x7F + bhi write_high + ldr r2, =write_table + add r2, r2, r0, lsl #3 + mov pc, r2 + +write_high: + cmp r0, #0xC0 + beq write_c0 + cmp r0, #0xC4 + beq write_c4 + bx lr + +write_c0: + mcr p14, 1, r1, c1, c0, 4 // register 0xC0 (OS Lock Access Reg) + bx lr + +write_c4: + mcr p14, 1, r1, c1, c4, 4 // register 0xC4 (Powerdown Ctl Reg) + bx lr + +write_table: + mcr p14, 1, r1, c0, c0, 0 // register 0x00 + bx lr + mcr p14, 1, r1, c0, c1, 0 // register 0x01 + bx lr + mcr p14, 1, r1, c0, c2, 0 // register 0x02 + bx lr + mcr p14, 1, r1, c0, c3, 0 // register 0x03 + bx lr + mcr p14, 1, r1, c0, c4, 0 // register 0x04 + bx lr + mcr p14, 1, r1, c0, c5, 0 // register 0x05 + bx lr + mcr p14, 1, r1, c0, c6, 0 // register 0x06 + bx lr + mcr p14, 1, r1, c0, c7, 0 // register 0x07 + bx lr + mcr p14, 1, r1, c0, c8, 0 // register 0x08 + bx lr + mcr p14, 1, r1, c0, c9, 0 // register 0x09 + bx lr + mcr p14, 1, r1, c0, c10, 0 // register 0x0A + bx lr + mcr p14, 1, r1, c0, c11, 0 // register 0x0B + bx lr + mcr p14, 1, r1, c0, c12, 0 // register 0x0C + bx lr + mcr p14, 1, r1, c0, c13, 0 // register 0x0D + bx lr + mcr p14, 1, r1, c0, c14, 0 // register 0x0E + bx lr + mcr p14, 1, r1, c0, c15, 0 // register 0x0F + bx lr + mcr p14, 1, r1, c0, c0, 1 // register 0x10 + bx lr + mcr p14, 1, r1, c0, c1, 1 // register 0x11 + bx lr + mcr p14, 1, r1, c0, c2, 1 // register 0x12 + bx lr + mcr p14, 1, r1, c0, c3, 1 // register 0x13 + bx lr + mcr p14, 1, r1, c0, c4, 1 // register 0x14 + bx lr + mcr p14, 1, r1, c0, c5, 1 // register 0x15 + bx lr + mcr p14, 1, r1, c0, c6, 1 // register 0x16 + bx lr + mcr p14, 1, r1, c0, c7, 1 // register 0x17 + bx lr + mcr p14, 1, r1, c0, c8, 1 // register 0x18 + bx lr + mcr p14, 1, r1, c0, c9, 1 // register 0x19 + bx lr + mcr p14, 1, r1, c0, c10, 1 // register 0x1A + bx lr + mcr p14, 1, r1, c0, c11, 1 // register 0x1B + bx lr + mcr p14, 1, r1, c0, c12, 1 // register 0x1C + bx lr + mcr p14, 1, r1, c0, c13, 1 // register 0x1D + bx lr + mcr p14, 1, r1, c0, c14, 1 // register 0x1E + bx lr + mcr p14, 1, r1, c0, c15, 1 // register 0x1F + bx lr + mcr p14, 1, r1, c0, c0, 2 // register 0x20 + bx lr + mcr p14, 1, r1, c0, c1, 2 // register 0x21 + bx lr + mcr p14, 1, r1, c0, c2, 2 // register 0x22 + bx lr + mcr p14, 1, r1, c0, c3, 2 // register 0x23 + bx lr + mcr p14, 1, r1, c0, c4, 2 // register 0x24 + bx lr + mcr p14, 1, r1, c0, c5, 2 // register 0x25 + bx lr + mcr p14, 1, r1, c0, c6, 2 // register 0x26 + bx lr + mcr p14, 1, r1, c0, c7, 2 // register 0x27 + bx lr + mcr p14, 1, r1, c0, c8, 2 // register 0x28 + bx lr + mcr p14, 1, r1, c0, c9, 2 // register 0x29 + bx lr + mcr p14, 1, r1, c0, c10, 2 // register 0x2A + bx lr + mcr p14, 1, r1, c0, c11, 2 // register 0x2B + bx lr + mcr p14, 1, r1, c0, c12, 2 // register 0x2C + bx lr + mcr p14, 1, r1, c0, c13, 2 // register 0x2D + bx lr + mcr p14, 1, r1, c0, c14, 2 // register 0x2E + bx lr + mcr p14, 1, r1, c0, c15, 2 // register 0x2F + bx lr + mcr p14, 1, r1, c0, c0, 3 // register 0x30 + bx lr + mcr p14, 1, r1, c0, c1, 3 // register 0x31 + bx lr + mcr p14, 1, r1, c0, c2, 3 // register 0x32 + bx lr + mcr p14, 1, r1, c0, c3, 3 // register 0x33 + bx lr + mcr p14, 1, r1, c0, c4, 3 // register 0x34 + bx lr + mcr p14, 1, r1, c0, c5, 3 // register 0x35 + bx lr + mcr p14, 1, r1, c0, c6, 3 // register 0x36 + bx lr + mcr p14, 1, r1, c0, c7, 3 // register 0x37 + bx lr + mcr p14, 1, r1, c0, c8, 3 // register 0x38 + bx lr + mcr p14, 1, r1, c0, c9, 3 // register 0x39 + bx lr + mcr p14, 1, r1, c0, c10, 3 // register 0x3A + bx lr + mcr p14, 1, r1, c0, c11, 3 // register 0x3B + bx lr + mcr p14, 1, r1, c0, c12, 3 // register 0x3C + bx lr + mcr p14, 1, r1, c0, c13, 3 // register 0x3D + bx lr + mcr p14, 1, r1, c0, c14, 3 // register 0x3E + bx lr + mcr p14, 1, r1, c0, c15, 3 // register 0x3F + bx lr + mcr p14, 1, r1, c0, c0, 4 // register 0x40 + bx lr + mcr p14, 1, r1, c0, c1, 4 // register 0x41 + bx lr + mcr p14, 1, r1, c0, c2, 4 // register 0x42 + bx lr + mcr p14, 1, r1, c0, c3, 4 // register 0x43 + bx lr + mcr p14, 1, r1, c0, c4, 4 // register 0x44 + bx lr + mcr p14, 1, r1, c0, c5, 4 // register 0x45 + bx lr + mcr p14, 1, r1, c0, c6, 4 // register 0x46 + bx lr + mcr p14, 1, r1, c0, c7, 4 // register 0x47 + bx lr + mcr p14, 1, r1, c0, c8, 4 // register 0x48 + bx lr + mcr p14, 1, r1, c0, c9, 4 // register 0x49 + bx lr + mcr p14, 1, r1, c0, c10, 4 // register 0x4A + bx lr + mcr p14, 1, r1, c0, c11, 4 // register 0x4B + bx lr + mcr p14, 1, r1, c0, c12, 4 // register 0x4C + bx lr + mcr p14, 1, r1, c0, c13, 4 // register 0x4D + bx lr + mcr p14, 1, r1, c0, c14, 4 // register 0x4E + bx lr + mcr p14, 1, r1, c0, c15, 4 // register 0x4F + bx lr + mcr p14, 1, r1, c0, c0, 5 // register 0x50 + bx lr + mcr p14, 1, r1, c0, c1, 5 // register 0x51 + bx lr + mcr p14, 1, r1, c0, c2, 5 // register 0x52 + bx lr + mcr p14, 1, r1, c0, c3, 5 // register 0x53 + bx lr + mcr p14, 1, r1, c0, c4, 5 // register 0x54 + bx lr + mcr p14, 1, r1, c0, c5, 5 // register 0x55 + bx lr + mcr p14, 1, r1, c0, c6, 5 // register 0x56 + bx lr + mcr p14, 1, r1, c0, c7, 5 // register 0x57 + bx lr + mcr p14, 1, r1, c0, c8, 5 // register 0x58 + bx lr + mcr p14, 1, r1, c0, c9, 5 // register 0x59 + bx lr + mcr p14, 1, r1, c0, c10, 5 // register 0x5A + bx lr + mcr p14, 1, r1, c0, c11, 5 // register 0x5B + bx lr + mcr p14, 1, r1, c0, c12, 5 // register 0x5C + bx lr + mcr p14, 1, r1, c0, c13, 5 // register 0x5D + bx lr + mcr p14, 1, r1, c0, c14, 5 // register 0x5E + bx lr + mcr p14, 1, r1, c0, c15, 5 // register 0x5F + bx lr + mcr p14, 1, r1, c0, c0, 6 // register 0x60 + bx lr + mcr p14, 1, r1, c0, c1, 6 // register 0x61 + bx lr + mcr p14, 1, r1, c0, c2, 6 // register 0x62 + bx lr + mcr p14, 1, r1, c0, c3, 6 // register 0x63 + bx lr + mcr p14, 1, r1, c0, c4, 6 // register 0x64 + bx lr + mcr p14, 1, r1, c0, c5, 6 // register 0x65 + bx lr + mcr p14, 1, r1, c0, c6, 6 // register 0x66 + bx lr + mcr p14, 1, r1, c0, c7, 6 // register 0x67 + bx lr + mcr p14, 1, r1, c0, c8, 6 // register 0x68 + bx lr + mcr p14, 1, r1, c0, c9, 6 // register 0x69 + bx lr + mcr p14, 1, r1, c0, c10, 6 // register 0x6A + bx lr + mcr p14, 1, r1, c0, c11, 6 // register 0x6B + bx lr + mcr p14, 1, r1, c0, c12, 6 // register 0x6C + bx lr + mcr p14, 1, r1, c0, c13, 6 // register 0x6D + bx lr + mcr p14, 1, r1, c0, c14, 6 // register 0x6E + bx lr + mcr p14, 1, r1, c0, c15, 6 // register 0x6F + bx lr + mcr p14, 1, r1, c0, c0, 7 // register 0x70 + bx lr + mcr p14, 1, r1, c0, c1, 7 // register 0x71 + bx lr + mcr p14, 1, r1, c0, c2, 7 // register 0x72 + bx lr + mcr p14, 1, r1, c0, c3, 7 // register 0x73 + bx lr + mcr p14, 1, r1, c0, c4, 7 // register 0x74 + bx lr + mcr p14, 1, r1, c0, c5, 7 // register 0x75 + bx lr + mcr p14, 1, r1, c0, c6, 7 // register 0x76 + bx lr + mcr p14, 1, r1, c0, c7, 7 // register 0x77 + bx lr + mcr p14, 1, r1, c0, c8, 7 // register 0x78 + bx lr + mcr p14, 1, r1, c0, c9, 7 // register 0x79 + bx lr + mcr p14, 1, r1, c0, c10, 7 // register 0x7A + bx lr + mcr p14, 1, r1, c0, c11, 7 // register 0x7B + bx lr + mcr p14, 1, r1, c0, c12, 7 // register 0x7C + bx lr + mcr p14, 1, r1, c0, c13, 7 // register 0x7D + bx lr + mcr p14, 1, r1, c0, c14, 7 // register 0x7E + bx lr + mcr p14, 1, r1, c0, c15, 7 // register 0x7F + bx lr + +.global l2tevselr0_write +l2tevselr0_write: + mcr p15, 3, r0, c15, c5, 2 + bx lr + +.global etm_save_reg +etm_save_reg: + ldr r3, =etm_state /* store state at etm_state */ +#if (NR_CPUS >= 2) + mrc p15, 0, r2, c0, c0, 5 /* MPIDR */ + and r2, r2, #15 /* What CPU am I */ + ldr r1, =MAX_ETM_STATE_SIZE + mul r2, r2, r1 + add r3, r3, r2 +#endif + + /* save etm state */ + mrc p14, 1, r0, c1, c5, 4 /* read ETM PDSR to clear sticky bit */ + isb + ldr r1, =0xc5ACCE55 /* set ETMOSLAR lock */ + mcr p14, 1, r1, c1, c0, 4 + isb + + mrc p14, 1, r1, c1, c2, 4 /* ETMOSSRR state register count */ + cmp r1, #(MAX_ETM_REGS) /* check for state overflow */ + movgt r1, #0 /* if not enough space, don't save */ + str r1,[r3],#4 /* save count for restore */ + +1: cmp r1, #0 + mrcne p14, 1, r2, c1, c2, 4 /* ETMOSSRR state value */ + strne r2, [r3], #4 /* push value */ + subne r1, r1, #1 + bne 1b + + mcr p14, 1, r1, c1, c0, 4 /* r1 = 0, unlock ETMOSLAR */ + isb + + bx lr + +.global etm_restore_reg +etm_restore_reg: + /* restore debug registers after power collapse */ + ldr r3, =etm_state /* load state from etm_state */ +#if (NR_CPUS >= 2) + mrc p15, 0, r2, c0, c0, 5 /* MPIDR */ + and r2, r2, #15 /* What CPU am I */ + ldr r1, =MAX_ETM_STATE_SIZE + mul r2, r2, r1 + add r3, r3, r2 +#endif + + /* restore etm state */ + mrc p14, 1, r0, c1, c5, 4 /* read ETM PDSR to clear sticky bit */ + isb + ldr r1, =0xc5ACCE55 /* set ETMOSLAR lock */ + mcr p14, 1, r1, c1, c0, 4 + isb + + mrc p14, 1, r1, c1, c2, 4 /* ETMOSSRR dummy read (required)*/ + ldr r1, [r3], #4 /* load saved count */ + cmp r1, #0 /* skip if none stored */ + beq end_etm_restore_reg + +1: ldr r2,[r3],#4 + mcr p14, 1, r2, c1, c2, 4 /* ETMOSSRR write state value */ + subs r1, r1, #1 + bne 1b +end_etm_restore_reg: + mcr p14, 1, r1, c1, c0, 4 /* r1 = 0, unlock ETMOSLAR */ + isb + + bx lr + + + .data + +etm_state: + .space MAX_ETM_STATE_SIZE * NR_CPUS diff --git a/arch/arm/mach-msm/cp14.h b/arch/arm/mach-msm/cp14.h new file mode 100644 index 00000000000..0946e07218d --- /dev/null +++ b/arch/arm/mach-msm/cp14.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +uint32_t etm_read_reg(uint32_t reg); +void etm_write_reg(uint32_t reg, uint32_t val); +void l2tevselr0_write(uint32_t val); +void etm_save_reg(void); +void etm_restore_reg(void); diff --git a/arch/arm/mach-msm/cpufreq.c b/arch/arm/mach-msm/cpufreq.c new file mode 100644 index 00000000000..c01332a74ad --- /dev/null +++ b/arch/arm/mach-msm/cpufreq.c @@ -0,0 +1,281 @@ +/* arch/arm/mach-msm/cpufreq.c + * + * MSM architecture cpufreq driver + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2010, Code Aurora Forum. All rights reserved. + * Author: Mike A. Chan + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acpuclock.h" + +#ifdef CONFIG_SMP +struct cpufreq_work_struct { + struct work_struct work; + struct cpufreq_policy *policy; + struct completion complete; + int frequency; + int status; +}; + +static DEFINE_PER_CPU(struct cpufreq_work_struct, cpufreq_work); +static struct workqueue_struct *msm_cpufreq_wq; +#endif + +struct cpufreq_suspend_t { + struct mutex suspend_mutex; + int device_suspended; +}; + +static DEFINE_PER_CPU(struct cpufreq_suspend_t, cpufreq_suspend); + +static int set_cpu_freq(struct cpufreq_policy *policy, unsigned int new_freq) +{ + int ret = 0; + struct cpufreq_freqs freqs; + + freqs.old = policy->cur; + freqs.new = new_freq; + freqs.cpu = policy->cpu; + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + ret = acpuclk_set_rate(policy->cpu, new_freq, SETRATE_CPUFREQ); + if (!ret) + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return ret; +} + +#ifdef CONFIG_SMP +static void set_cpu_work(struct work_struct *work) +{ + struct cpufreq_work_struct *cpu_work = + container_of(work, struct cpufreq_work_struct, work); + + cpu_work->status = set_cpu_freq(cpu_work->policy, cpu_work->frequency); + complete(&cpu_work->complete); +} +#endif + +static int msm_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + int ret = -EFAULT; + int index; + struct cpufreq_frequency_table *table; +#ifdef CONFIG_SMP + struct cpufreq_work_struct *cpu_work = NULL; + cpumask_var_t mask; + + if (!alloc_cpumask_var(&mask, GFP_KERNEL)) + return -ENOMEM; + + if (!cpu_active(policy->cpu)) { + pr_info("cpufreq: cpu %d is not active.\n", policy->cpu); + return -ENODEV; + } +#endif + + mutex_lock(&per_cpu(cpufreq_suspend, policy->cpu).suspend_mutex); + + if (per_cpu(cpufreq_suspend, policy->cpu).device_suspended) { + pr_debug("cpufreq: cpu%d scheduling frequency change " + "in suspend.\n", policy->cpu); + ret = -EFAULT; + goto done; + } + + table = cpufreq_frequency_get_table(policy->cpu); + if (cpufreq_frequency_table_target(policy, table, target_freq, relation, + &index)) { + pr_err("cpufreq: invalid target_freq: %d\n", target_freq); + ret = -EINVAL; + goto done; + } + +#ifdef CONFIG_CPU_FREQ_DEBUG + pr_debug("CPU[%d] target %d relation %d (%d-%d) selected %d\n", + policy->cpu, target_freq, relation, + policy->min, policy->max, table[index].frequency); +#endif + +#ifdef CONFIG_SMP + cpu_work = &per_cpu(cpufreq_work, policy->cpu); + cpu_work->policy = policy; + cpu_work->frequency = table[index].frequency; + cpu_work->status = -ENODEV; + + cpumask_clear(mask); + cpumask_set_cpu(policy->cpu, mask); + if (cpumask_equal(mask, ¤t->cpus_allowed)) { + ret = set_cpu_freq(cpu_work->policy, cpu_work->frequency); + goto done; + } else { + cancel_work_sync(&cpu_work->work); + INIT_COMPLETION(cpu_work->complete); + queue_work_on(policy->cpu, msm_cpufreq_wq, &cpu_work->work); + wait_for_completion(&cpu_work->complete); + } + + free_cpumask_var(mask); + ret = cpu_work->status; +#else + ret = set_cpu_freq(policy, table[index].frequency); +#endif + +done: + mutex_unlock(&per_cpu(cpufreq_suspend, policy->cpu).suspend_mutex); + return ret; +} + +static int msm_cpufreq_verify(struct cpufreq_policy *policy) +{ + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + return 0; +} + +static int __cpuinit msm_cpufreq_init(struct cpufreq_policy *policy) +{ + int cur_freq; + int index; + struct cpufreq_frequency_table *table; +#ifdef CONFIG_SMP + struct cpufreq_work_struct *cpu_work = NULL; +#endif + + table = cpufreq_frequency_get_table(policy->cpu); + if (cpufreq_frequency_table_cpuinfo(policy, table)) { +#ifdef CONFIG_MSM_CPU_FREQ_SET_MIN_MAX + policy->cpuinfo.min_freq = CONFIG_MSM_CPU_FREQ_MIN; + policy->cpuinfo.max_freq = CONFIG_MSM_CPU_FREQ_MAX; +#endif + } +#ifdef CONFIG_MSM_CPU_FREQ_SET_MIN_MAX + policy->min = CONFIG_MSM_CPU_FREQ_MIN; + policy->max = CONFIG_MSM_CPU_FREQ_MAX; +#endif + + cur_freq = acpuclk_get_rate(policy->cpu); + if (cpufreq_frequency_table_target(policy, table, cur_freq, + CPUFREQ_RELATION_H, &index)) { + pr_info("cpufreq: cpu%d at invalid freq: %d\n", + policy->cpu, cur_freq); + return -EINVAL; + } + + if (cur_freq != table[index].frequency) { + int ret = 0; + ret = acpuclk_set_rate(policy->cpu, table[index].frequency, + SETRATE_CPUFREQ); + if (ret) + return ret; + pr_info("cpufreq: cpu%d init at %d switching to %d\n", + policy->cpu, cur_freq, table[index].frequency); + cur_freq = table[index].frequency; + } + + policy->cur = cur_freq; + + policy->cpuinfo.transition_latency = + acpuclk_get_switch_time() * NSEC_PER_USEC; +#ifdef CONFIG_SMP + cpu_work = &per_cpu(cpufreq_work, policy->cpu); + INIT_WORK(&cpu_work->work, set_cpu_work); + init_completion(&cpu_work->complete); +#endif + + return 0; +} + +static int msm_cpufreq_suspend(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + mutex_lock(&per_cpu(cpufreq_suspend, cpu).suspend_mutex); + per_cpu(cpufreq_suspend, cpu).device_suspended = 1; + mutex_unlock(&per_cpu(cpufreq_suspend, cpu).suspend_mutex); + } + + return NOTIFY_DONE; +} + +static int msm_cpufreq_resume(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + per_cpu(cpufreq_suspend, cpu).device_suspended = 0; + } + + return NOTIFY_DONE; +} + +static int msm_cpufreq_pm_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + switch (event) { + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + return msm_cpufreq_resume(); + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + return msm_cpufreq_suspend(); + default: + return NOTIFY_DONE; + } +} + +static struct cpufreq_driver msm_cpufreq_driver = { + /* lps calculations are handled here. */ + .flags = CPUFREQ_STICKY | CPUFREQ_CONST_LOOPS, + .init = msm_cpufreq_init, + .verify = msm_cpufreq_verify, + .target = msm_cpufreq_target, + .name = "msm", +}; + +static struct notifier_block msm_cpufreq_pm_notifier = { + .notifier_call = msm_cpufreq_pm_event, +}; + +static int __init msm_cpufreq_register(void) +{ + int cpu; + + for_each_possible_cpu(cpu) { + mutex_init(&(per_cpu(cpufreq_suspend, cpu).suspend_mutex)); + per_cpu(cpufreq_suspend, cpu).device_suspended = 0; + } + +#ifdef CONFIG_SMP + msm_cpufreq_wq = create_workqueue("msm-cpufreq"); +#endif + + register_pm_notifier(&msm_cpufreq_pm_notifier); + return cpufreq_register_driver(&msm_cpufreq_driver); +} + +late_initcall(msm_cpufreq_register); + diff --git a/arch/arm/mach-msm/cpuidle.c b/arch/arm/mach-msm/cpuidle.c new file mode 100644 index 00000000000..4f22feb9394 --- /dev/null +++ b/arch/arm/mach-msm/cpuidle.c @@ -0,0 +1,151 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include "cpuidle.h" +#include "pm.h" + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct cpuidle_device, msm_cpuidle_devs); +static struct cpuidle_driver msm_cpuidle_driver = { + .name = "msm_idle", + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_MSM_SLEEP_STATS +static DEFINE_PER_CPU(struct atomic_notifier_head, msm_cpuidle_notifiers); + +int msm_cpuidle_register_notifier(unsigned int cpu, struct notifier_block *nb) +{ + struct atomic_notifier_head *head = + &per_cpu(msm_cpuidle_notifiers, cpu); + + return atomic_notifier_chain_register(head, nb); +} +EXPORT_SYMBOL(msm_cpuidle_register_notifier); + +int msm_cpuidle_unregister_notifier(unsigned int cpu, struct notifier_block *nb) +{ + struct atomic_notifier_head *head = + &per_cpu(msm_cpuidle_notifiers, cpu); + + return atomic_notifier_chain_unregister(head, nb); +} +EXPORT_SYMBOL(msm_cpuidle_unregister_notifier); +#endif + +static int msm_cpuidle_enter( + struct cpuidle_device *dev, struct cpuidle_state *state) +{ + int ret; +#ifdef CONFIG_MSM_SLEEP_STATS + struct atomic_notifier_head *head = + &__get_cpu_var(msm_cpuidle_notifiers); +#endif + + local_irq_disable(); + +#ifdef CONFIG_MSM_SLEEP_STATS + atomic_notifier_call_chain(head, MSM_CPUIDLE_STATE_ENTER, NULL); +#endif + + ret = msm_pm_idle_enter((enum msm_pm_sleep_mode) (state->driver_data)); + +#ifdef CONFIG_MSM_SLEEP_STATS + atomic_notifier_call_chain(head, MSM_CPUIDLE_STATE_EXIT, NULL); +#endif + + local_irq_enable(); + + return ret; +} + +void __init msm_cpuidle_set_states(struct msm_cpuidle_state *states, + int nr_states, struct msm_pm_platform_data *pm_data) +{ + unsigned int cpu; + + for_each_possible_cpu(cpu) { + struct cpuidle_device *dev = &per_cpu(msm_cpuidle_devs, cpu); + int i; + + dev->cpu = cpu; + dev->prepare = msm_pm_idle_prepare; + + for (i = 0; i < nr_states; i++) { + struct msm_cpuidle_state *cstate = &states[i]; + struct cpuidle_state *state; + struct msm_pm_platform_data *pm_mode; + + if (cstate->cpu != cpu) + continue; + + state = &dev->states[cstate->state_nr]; + pm_mode = &pm_data[MSM_PM_MODE(cpu, cstate->mode_nr)]; + + snprintf(state->name, CPUIDLE_NAME_LEN, cstate->name); + snprintf(state->desc, CPUIDLE_DESC_LEN, cstate->desc); + state->driver_data = (void *) cstate->mode_nr; + state->flags = CPUIDLE_FLAG_TIME_VALID; + state->exit_latency = pm_mode->latency; + state->power_usage = 0; + state->target_residency = pm_mode->residency; + state->enter = msm_cpuidle_enter; + } + + for (i = 0; i < CPUIDLE_STATE_MAX; i++) { + if (dev->states[i].enter == NULL) + break; + dev->state_count = i + 1; + } + } +} + +int __init msm_cpuidle_init(void) +{ + unsigned int cpu; + int ret; + + ret = cpuidle_register_driver(&msm_cpuidle_driver); + if (ret) + pr_err("%s: failed to register cpuidle driver: %d\n", + __func__, ret); + + for_each_possible_cpu(cpu) { + struct cpuidle_device *dev = &per_cpu(msm_cpuidle_devs, cpu); + + ret = cpuidle_register_device(dev); + if (ret) { + pr_err("%s: failed to register cpuidle device for " + "cpu %u: %d\n", __func__, cpu, ret); + return ret; + } + } + + return 0; +} + +static int __init msm_cpuidle_early_init(void) +{ +#ifdef CONFIG_MSM_SLEEP_STATS + unsigned int cpu; + + for_each_possible_cpu(cpu) + ATOMIC_INIT_NOTIFIER_HEAD(&per_cpu(msm_cpuidle_notifiers, cpu)); +#endif + return 0; +} + +early_initcall(msm_cpuidle_early_init); diff --git a/arch/arm/mach-msm/cpuidle.h b/arch/arm/mach-msm/cpuidle.h new file mode 100644 index 00000000000..37b8d001ebb --- /dev/null +++ b/arch/arm/mach-msm/cpuidle.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_CPUIDLE_H +#define __ARCH_ARM_MACH_MSM_CPUIDLE_H + +#include +#include "pm.h" + +struct msm_cpuidle_state { + unsigned int cpu; + int state_nr; + char *name; + char *desc; + enum msm_pm_sleep_mode mode_nr; +}; + +#ifdef CONFIG_CPU_IDLE +void msm_cpuidle_set_states(struct msm_cpuidle_state *states, + int nr_states, struct msm_pm_platform_data *pm_data); + +int msm_cpuidle_init(void); +#else +static inline void msm_cpuidle_set_states(struct msm_cpuidle_state *states, + int nr_states, struct msm_pm_platform_data *pm_data) {} + +static inline int msm_cpuidle_init(void) +{ return -ENOSYS; } +#endif + +#ifdef CONFIG_MSM_SLEEP_STATS +enum { + MSM_CPUIDLE_STATE_ENTER, + MSM_CPUIDLE_STATE_EXIT +}; + +int msm_cpuidle_register_notifier(unsigned int cpu, + struct notifier_block *nb); +int msm_cpuidle_unregister_notifier(unsigned int cpu, + struct notifier_block *nb); +#else +static inline int msm_cpuidle_register_notifier(unsigned int cpu, + struct notifier_block *nb) +{ return -ENODEV; } +static inline int msm_cpuidle_unregister_notifier(unsigned int cpu, + struct notifier_block *nb) +{ return -ENODEV; } +#endif + +#endif /* __ARCH_ARM_MACH_MSM_CPUIDLE_H */ diff --git a/arch/arm/mach-msm/dal.c b/arch/arm/mach-msm/dal.c new file mode 100644 index 00000000000..218bd0f8b23 --- /dev/null +++ b/arch/arm/mach-msm/dal.c @@ -0,0 +1,1326 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Device access library (DAL) implementation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DALRPC_PROTOCOL_VERSION 0x11 +#define DALRPC_SUCCESS 0 +#define DALRPC_MAX_PORTNAME_LEN 64 +#define DALRPC_MAX_ATTACH_PARAM_LEN 64 +#define DALRPC_MAX_SERVICE_NAME_LEN 32 +#define DALRPC_MAX_PARAMS 128 +#define DALRPC_MAX_PARAMS_SIZE (DALRPC_MAX_PARAMS * 4) +#define DALRPC_MAX_MSG_SIZE (sizeof(struct dalrpc_msg_hdr) + \ + DALRPC_MAX_PARAMS_SIZE) +#define DALRPC_MSGID_DDI 0x0 +#define DALRPC_MSGID_DDI_REPLY 0x80 +#define DALRPC_MSGID_ATTACH_REPLY 0x81 +#define DALRPC_MSGID_DETACH_REPLY 0x82 +#define DALRPC_MSGID_ASYNCH 0xC0 +#define ROUND_BUFLEN(x) (((x + 3) & ~0x3)) + +struct dalrpc_msg_hdr { + uint32_t len:16; + uint32_t proto_ver:8; + uint32_t prio:7; + uint32_t async:1; + uint32_t ddi_idx:16; + uint32_t proto_id:8; + uint32_t msgid:8; + void *from; + void *to; +}; + +struct dalrpc_msg { + struct dalrpc_msg_hdr hdr; + uint32_t param[DALRPC_MAX_PARAMS]; +}; + +struct dalrpc_event_handle { + struct list_head list; + + int flag; + spinlock_t lock; +}; + +struct dalrpc_cb_handle { + struct list_head list; + + void (*fn)(void *, uint32_t, void *, uint32_t); + void *context; +}; + +struct daldevice_handle {; + struct list_head list; + + void *remote_handle; + struct completion read_completion; + struct dalrpc_port *port; + struct dalrpc_msg msg; + struct mutex client_lock; +}; + +struct dalrpc_port { + struct list_head list; + + char port[DALRPC_MAX_PORTNAME_LEN+1]; + int refcount; + + struct workqueue_struct *wq; + struct work_struct port_work; + struct mutex write_lock; + + smd_channel_t *ch; + + struct dalrpc_msg msg_in; + struct daldevice_handle *msg_owner; + unsigned msg_bytes_read; + + struct list_head event_list; + struct mutex event_list_lock; + + struct list_head cb_list; + struct mutex cb_list_lock; +}; + +static LIST_HEAD(port_list); +static LIST_HEAD(client_list); +static DEFINE_MUTEX(pc_lists_lock); + +static DECLARE_WAIT_QUEUE_HEAD(event_wq); + +static int client_exists(void *handle) +{ + struct daldevice_handle *h; + + if (!handle) + return 0; + + mutex_lock(&pc_lists_lock); + + list_for_each_entry(h, &client_list, list) + if (h == handle) { + mutex_unlock(&pc_lists_lock); + return 1; + } + + mutex_unlock(&pc_lists_lock); + + return 0; +} + +static int client_exists_locked(void *handle) +{ + struct daldevice_handle *h; + + /* this function must be called with pc_lists_lock acquired */ + + if (!handle) + return 0; + + list_for_each_entry(h, &client_list, list) + if (h == handle) + return 1; + + return 0; +} + +static int port_exists(struct dalrpc_port *p) +{ + struct dalrpc_port *p_iter; + + /* this function must be called with pc_lists_lock acquired */ + + if (!p) + return 0; + + list_for_each_entry(p_iter, &port_list, list) + if (p_iter == p) + return 1; + + return 0; +} + +static struct dalrpc_port *port_name_exists(char *port) +{ + struct dalrpc_port *p; + + /* this function must be called with pc_lists_lock acquired */ + + list_for_each_entry(p, &port_list, list) + if (!strcmp(p->port, port)) + return p; + + return NULL; +} + +static void port_close(struct dalrpc_port *p) +{ + mutex_lock(&pc_lists_lock); + + p->refcount--; + if (p->refcount == 0) + list_del(&p->list); + + mutex_unlock(&pc_lists_lock); + + if (p->refcount == 0) { + destroy_workqueue(p->wq); + smd_close(p->ch); + kfree(p); + } +} + +static int event_exists(struct dalrpc_port *p, + struct dalrpc_event_handle *ev) +{ + struct dalrpc_event_handle *ev_iter; + + /* this function must be called with event_list_lock acquired */ + + list_for_each_entry(ev_iter, &p->event_list, list) + if (ev_iter == ev) + return 1; + + return 0; +} + +static int cb_exists(struct dalrpc_port *p, + struct dalrpc_cb_handle *cb) +{ + struct dalrpc_cb_handle *cb_iter; + + /* this function must be called with the cb_list_lock acquired */ + + list_for_each_entry(cb_iter, &p->cb_list, list) + if (cb_iter == cb) + return 1; + + return 0; +} + +static int check_version(struct dalrpc_msg_hdr *msg_hdr) +{ + static int version_msg = 1; + + /* disabled because asynch events currently have no version */ + return 0; + + if (msg_hdr->proto_ver != DALRPC_PROTOCOL_VERSION) { + if (version_msg) { + printk(KERN_ERR "dalrpc: incompatible verison\n"); + version_msg = 0; + } + return -1; + } + return 0; +} + +static void process_asynch(struct dalrpc_port *p) +{ + struct dalrpc_event_handle *ev; + struct dalrpc_cb_handle *cb; + + ev = (struct dalrpc_event_handle *)p->msg_in.param[0]; + cb = (struct dalrpc_cb_handle *)p->msg_in.param[0]; + + mutex_lock(&p->event_list_lock); + if (event_exists(p, ev)) { + spin_lock(&ev->lock); + ev->flag = 1; + spin_unlock(&ev->lock); + smp_mb(); + wake_up_all(&event_wq); + mutex_unlock(&p->event_list_lock); + return; + } + mutex_unlock(&p->event_list_lock); + + mutex_lock(&p->cb_list_lock); + if (cb_exists(p, cb)) { + cb->fn(cb->context, p->msg_in.param[1], + &p->msg_in.param[3], p->msg_in.param[2]); + mutex_unlock(&p->cb_list_lock); + return; + } + mutex_unlock(&p->cb_list_lock); +} + +static void process_msg(struct dalrpc_port *p) +{ + switch (p->msg_in.hdr.msgid) { + + case DALRPC_MSGID_DDI_REPLY: + case DALRPC_MSGID_ATTACH_REPLY: + case DALRPC_MSGID_DETACH_REPLY: + complete(&p->msg_owner->read_completion); + break; + + case DALRPC_MSGID_ASYNCH: + process_asynch(p); + break; + + default: + printk(KERN_ERR "process_msg: bad msgid %#x\n", + p->msg_in.hdr.msgid); + } +} + +static void flush_msg(struct dalrpc_port *p) +{ + int bytes_read, len; + + len = p->msg_in.hdr.len - sizeof(struct dalrpc_msg_hdr); + while (len > 0) { + bytes_read = smd_read(p->ch, NULL, len); + if (bytes_read <= 0) + break; + len -= bytes_read; + } + p->msg_bytes_read = 0; +} + +static int check_header(struct dalrpc_port *p) +{ + if (check_version(&p->msg_in.hdr) || + p->msg_in.hdr.len > DALRPC_MAX_MSG_SIZE || + (p->msg_in.hdr.msgid != DALRPC_MSGID_ASYNCH && + !client_exists_locked(p->msg_in.hdr.to))) { + printk(KERN_ERR "dalrpc_read_msg: bad msg\n"); + flush_msg(p); + return 1; + } + p->msg_owner = (struct daldevice_handle *)p->msg_in.hdr.to; + + if (p->msg_in.hdr.msgid != DALRPC_MSGID_ASYNCH) + memcpy(&p->msg_owner->msg.hdr, &p->msg_in.hdr, + sizeof(p->msg_in.hdr)); + + return 0; +} + +static int dalrpc_read_msg(struct dalrpc_port *p) +{ + uint8_t *read_ptr; + int bytes_read; + + /* read msg header */ + while (p->msg_bytes_read < sizeof(p->msg_in.hdr)) { + read_ptr = (uint8_t *)&p->msg_in.hdr + p->msg_bytes_read; + + bytes_read = smd_read(p->ch, read_ptr, + sizeof(p->msg_in.hdr) - + p->msg_bytes_read); + if (bytes_read <= 0) + return 0; + p->msg_bytes_read += bytes_read; + + if (p->msg_bytes_read == sizeof(p->msg_in.hdr) && + check_header(p)) + return 1; + } + + /* read remainder of msg */ + if (p->msg_in.hdr.msgid != DALRPC_MSGID_ASYNCH) + read_ptr = (uint8_t *)&p->msg_owner->msg; + else + read_ptr = (uint8_t *)&p->msg_in; + read_ptr += p->msg_bytes_read; + + while (p->msg_bytes_read < p->msg_in.hdr.len) { + bytes_read = smd_read(p->ch, read_ptr, + p->msg_in.hdr.len - p->msg_bytes_read); + if (bytes_read <= 0) + return 0; + p->msg_bytes_read += bytes_read; + read_ptr += bytes_read; + } + + process_msg(p); + p->msg_bytes_read = 0; + p->msg_owner = NULL; + + return 1; +} + +static void dalrpc_work(struct work_struct *work) +{ + struct dalrpc_port *p = container_of(work, + struct dalrpc_port, + port_work); + + /* must lock port/client lists to ensure port doesn't disappear + under an asynch event */ + mutex_lock(&pc_lists_lock); + if (port_exists(p)) + while (dalrpc_read_msg(p)) + ; + mutex_unlock(&pc_lists_lock); +} + +static void dalrpc_smd_cb(void *priv, unsigned smd_flags) +{ + struct dalrpc_port *p = priv; + + if (smd_flags != SMD_EVENT_DATA) + return; + + queue_work(p->wq, &p->port_work); +} + +static struct dalrpc_port *dalrpc_port_open(char *port, int cpu) +{ + struct dalrpc_port *p; + char wq_name[32]; + + p = port_name_exists(port); + if (p) { + p->refcount++; + return p; + } + + p = kzalloc(sizeof(struct dalrpc_port), GFP_KERNEL); + if (!p) + return NULL; + + strncpy(p->port, port, sizeof(p->port) - 1); + p->refcount = 1; + + snprintf(wq_name, sizeof(wq_name), "dalrpc_rcv_%s", port); + p->wq = create_singlethread_workqueue(wq_name); + if (!p->wq) { + printk(KERN_ERR "dalrpc_init: unable to create workqueue\n"); + goto no_wq; + } + INIT_WORK(&p->port_work, dalrpc_work); + + mutex_init(&p->write_lock); + mutex_init(&p->event_list_lock); + mutex_init(&p->cb_list_lock); + + INIT_LIST_HEAD(&p->event_list); + INIT_LIST_HEAD(&p->cb_list); + + p->msg_owner = NULL; + p->msg_bytes_read = 0; + + if (smd_named_open_on_edge(port, cpu, &p->ch, p, + dalrpc_smd_cb)) { + printk(KERN_ERR "dalrpc_port_init() failed to open port\n"); + goto no_smd; + } + + list_add(&p->list, &port_list); + + return p; + +no_smd: + destroy_workqueue(p->wq); +no_wq: + kfree(p); + return NULL; +} + +static void dalrpc_sendwait(struct daldevice_handle *h) +{ + u8 *buf = (u8 *)&h->msg; + int len = h->msg.hdr.len; + int written; + + mutex_lock(&h->port->write_lock); + do { + written = smd_write(h->port->ch, buf + (h->msg.hdr.len - len), + len); + if (written < 0) + break; + len -= written; + } while (len); + mutex_unlock(&h->port->write_lock); + + if (!h->msg.hdr.async) + wait_for_completion(&h->read_completion); +} + +int daldevice_attach(uint32_t device_id, char *port, int cpu, + void **handle_ptr) +{ + struct daldevice_handle *h; + char dyn_port[DALRPC_MAX_PORTNAME_LEN + 1] = "DAL00"; + int ret; + int tries = 0; + + if (!port) + port = dyn_port; + + if (strlen(port) > DALRPC_MAX_PORTNAME_LEN) + return -EINVAL; + + h = kzalloc(sizeof(struct daldevice_handle), GFP_KERNEL); + if (!h) { + *handle_ptr = NULL; + return -ENOMEM; + } + + init_completion(&h->read_completion); + mutex_init(&h->client_lock); + + mutex_lock(&pc_lists_lock); + list_add(&h->list, &client_list); + mutex_unlock(&pc_lists_lock); + + /* 3 attempts, enough for one each on the user specified port, the + * dynamic discovery port, and the port recommended by the dynamic + * discovery port */ + while (tries < 3) { + tries++; + + mutex_lock(&pc_lists_lock); + h->port = dalrpc_port_open(port, cpu); + if (!h->port) { + list_del(&h->list); + mutex_unlock(&pc_lists_lock); + printk(KERN_ERR "daldevice_attach: could not " + "open port\n"); + kfree(h); + *handle_ptr = NULL; + return -EIO; + } + mutex_unlock(&pc_lists_lock); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 4 + + DALRPC_MAX_ATTACH_PARAM_LEN + + DALRPC_MAX_SERVICE_NAME_LEN; + h->msg.hdr.proto_ver = DALRPC_PROTOCOL_VERSION; + h->msg.hdr.ddi_idx = 0; + h->msg.hdr.msgid = 0x1; + h->msg.hdr.prio = 0; + h->msg.hdr.async = 0; + h->msg.hdr.from = h; + h->msg.hdr.to = 0; + h->msg.param[0] = device_id; + + memset(&h->msg.param[1], 0, + DALRPC_MAX_ATTACH_PARAM_LEN + + DALRPC_MAX_SERVICE_NAME_LEN); + + dalrpc_sendwait(h); + ret = h->msg.param[0]; + + if (ret == DALRPC_SUCCESS) { + h->remote_handle = h->msg.hdr.from; + *handle_ptr = h; + break; + } else if (strnlen((char *)&h->msg.param[1], + DALRPC_MAX_PORTNAME_LEN)) { + /* another port was recommended in the response. */ + strncpy(dyn_port, (char *)&h->msg.param[1], + DALRPC_MAX_PORTNAME_LEN); + dyn_port[DALRPC_MAX_PORTNAME_LEN] = 0; + port = dyn_port; + } else if (port == dyn_port) { + /* the dynamic discovery port (or port that + * was recommended by it) did not recognize + * the device id, give up */ + daldevice_detach(h); + break; + } else + /* the user specified port did not work, try + * the dynamic discovery port */ + port = dyn_port; + + port_close(h->port); + } + + return ret; +} +EXPORT_SYMBOL(daldevice_attach); + +static void dalrpc_ddi_prologue(uint32_t ddi_idx, struct daldevice_handle *h, + uint32_t idx_async) +{ + h->msg.hdr.proto_ver = DALRPC_PROTOCOL_VERSION; + h->msg.hdr.prio = 0; + h->msg.hdr.async = idx_async; + h->msg.hdr.msgid = DALRPC_MSGID_DDI; + h->msg.hdr.from = h; + h->msg.hdr.to = h->remote_handle; + h->msg.hdr.ddi_idx = ddi_idx; +} + +int daldevice_detach(void *handle) +{ + struct daldevice_handle *h = handle; + + if (!client_exists(h)) + return -EINVAL; + + dalrpc_ddi_prologue(0, h, 0); + + if (!h->remote_handle) + goto norpc; + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 4; + h->msg.hdr.msgid = 0x2; + h->msg.param[0] = 0; + + dalrpc_sendwait(h); + +norpc: + mutex_lock(&pc_lists_lock); + list_del(&h->list); + mutex_unlock(&pc_lists_lock); + + port_close(h->port); + + kfree(h); + + return 0; +} +EXPORT_SYMBOL(daldevice_detach); + +uint32_t dalrpc_fcn_0(uint32_t ddi_idx, void *handle, uint32_t s1) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 4; + h->msg.hdr.proto_id = 0; + h->msg.param[0] = s1; + + dalrpc_sendwait(h); + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_0); + +uint32_t dalrpc_fcn_1(uint32_t ddi_idx, void *handle, uint32_t s1, + uint32_t s2) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 8; + h->msg.hdr.proto_id = 1; + h->msg.param[0] = s1; + h->msg.param[1] = s2; + + dalrpc_sendwait(h); + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_1); + +uint32_t dalrpc_fcn_2(uint32_t ddi_idx, void *handle, uint32_t s1, + uint32_t *p_s2) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 4; + h->msg.hdr.proto_id = 2; + h->msg.param[0] = s1; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) + *p_s2 = h->msg.param[1]; + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_2); + +uint32_t dalrpc_fcn_3(uint32_t ddi_idx, void *handle, uint32_t s1, + uint32_t s2, uint32_t s3) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 12; + h->msg.hdr.proto_id = 3; + h->msg.param[0] = s1; + h->msg.param[1] = s2; + h->msg.param[2] = s3; + + dalrpc_sendwait(h); + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_3); + +uint32_t dalrpc_fcn_4(uint32_t ddi_idx, void *handle, uint32_t s1, + uint32_t s2, uint32_t *p_s3) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 8; + h->msg.hdr.proto_id = 4; + h->msg.param[0] = s1; + h->msg.param[1] = s2; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) + *p_s3 = h->msg.param[1]; + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_4); + +uint32_t dalrpc_fcn_5(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen) +{ + struct daldevice_handle *h = handle; + uint32_t ret, idx_async; + + if ((ilen + 4) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + idx_async = (ddi_idx & 0x80000000) >> 31; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, idx_async); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 4 + + ROUND_BUFLEN(ilen); + h->msg.hdr.proto_id = 5; + h->msg.param[0] = ilen; + memcpy(&h->msg.param[1], ibuf, ilen); + + dalrpc_sendwait(h); + + if (h->msg.hdr.async) + ret = DALRPC_SUCCESS; + else + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_5); + +uint32_t dalrpc_fcn_6(uint32_t ddi_idx, void *handle, uint32_t s1, + const void *ibuf, uint32_t ilen) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + + if ((ilen + 8) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 8 + + ROUND_BUFLEN(ilen); + h->msg.hdr.proto_id = 6; + h->msg.param[0] = s1; + h->msg.param[1] = ilen; + memcpy(&h->msg.param[2], ibuf, ilen); + + dalrpc_sendwait(h); + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_6); + +uint32_t dalrpc_fcn_7(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, void *obuf, uint32_t olen, + uint32_t *oalen) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + int param_idx; + + if ((ilen + 8) > DALRPC_MAX_PARAMS_SIZE || + (olen + 4) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 8 + + ROUND_BUFLEN(ilen); + h->msg.hdr.proto_id = 7; + h->msg.param[0] = ilen; + memcpy(&h->msg.param[1], ibuf, ilen); + param_idx = (ROUND_BUFLEN(ilen) / 4) + 1; + h->msg.param[param_idx] = olen; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) { + if (h->msg.param[1] > olen) { + mutex_unlock(&h->client_lock); + return -EIO; + } + *oalen = h->msg.param[1]; + memcpy(obuf, &h->msg.param[2], h->msg.param[1]); + } + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_7); + +uint32_t dalrpc_fcn_8(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, void *obuf, uint32_t olen) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + int param_idx; + + if ((ilen + 8) > DALRPC_MAX_PARAMS_SIZE || + (olen + 4) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 8 + + ROUND_BUFLEN(ilen); + h->msg.hdr.proto_id = 8; + h->msg.param[0] = ilen; + memcpy(&h->msg.param[1], ibuf, ilen); + param_idx = (ROUND_BUFLEN(ilen) / 4) + 1; + h->msg.param[param_idx] = olen; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) { + if (h->msg.param[1] > olen) { + mutex_unlock(&h->client_lock); + return -EIO; + } + memcpy(obuf, &h->msg.param[2], h->msg.param[1]); + } + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_8); + +uint32_t dalrpc_fcn_9(uint32_t ddi_idx, void *handle, void *obuf, + uint32_t olen) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + + if ((olen + 4) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 4; + h->msg.hdr.proto_id = 9; + h->msg.param[0] = olen; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) { + if (h->msg.param[1] > olen) { + mutex_unlock(&h->client_lock); + return -EIO; + } + memcpy(obuf, &h->msg.param[2], h->msg.param[1]); + } + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_9); + +uint32_t dalrpc_fcn_10(uint32_t ddi_idx, void *handle, uint32_t s1, + const void *ibuf, uint32_t ilen, void *obuf, + uint32_t olen, uint32_t *oalen) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + int param_idx; + + if ((ilen + 12) > DALRPC_MAX_PARAMS_SIZE || + (olen + 4) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 12 + + ROUND_BUFLEN(ilen); + h->msg.hdr.proto_id = 10; + h->msg.param[0] = s1; + h->msg.param[1] = ilen; + memcpy(&h->msg.param[2], ibuf, ilen); + param_idx = (ROUND_BUFLEN(ilen) / 4) + 2; + h->msg.param[param_idx] = olen; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) { + if (h->msg.param[1] > olen) { + mutex_unlock(&h->client_lock); + return -EIO; + } + *oalen = h->msg.param[1]; + memcpy(obuf, &h->msg.param[2], h->msg.param[1]); + } + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_10); + +uint32_t dalrpc_fcn_11(uint32_t ddi_idx, void *handle, uint32_t s1, + void *obuf, uint32_t olen) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + + if ((olen + 4) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 8; + h->msg.hdr.proto_id = 11; + h->msg.param[0] = s1; + h->msg.param[1] = olen; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) { + if (h->msg.param[1] > olen) { + mutex_unlock(&h->client_lock); + return -EIO; + } + memcpy(obuf, &h->msg.param[2], h->msg.param[1]); + } + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_11); + +uint32_t dalrpc_fcn_12(uint32_t ddi_idx, void *handle, uint32_t s1, + void *obuf, uint32_t olen, uint32_t *oalen) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + + if ((olen + 4) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 8; + h->msg.hdr.proto_id = 12; + h->msg.param[0] = s1; + h->msg.param[1] = olen; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) { + if (h->msg.param[1] > olen) { + mutex_unlock(&h->client_lock); + return -EIO; + } + *oalen = h->msg.param[1]; + memcpy(obuf, &h->msg.param[2], h->msg.param[1]); + } + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_12); + +uint32_t dalrpc_fcn_13(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, const void *ibuf2, uint32_t ilen2, + void *obuf, uint32_t olen) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + int param_idx; + + if ((ilen + ilen2 + 12) > DALRPC_MAX_PARAMS_SIZE || + (olen + 4) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 12 + + ROUND_BUFLEN(ilen) + ROUND_BUFLEN(ilen2); + h->msg.hdr.proto_id = 13; + h->msg.param[0] = ilen; + memcpy(&h->msg.param[1], ibuf, ilen); + param_idx = (ROUND_BUFLEN(ilen) / 4) + 1; + h->msg.param[param_idx++] = ilen2; + memcpy(&h->msg.param[param_idx], ibuf2, ilen2); + param_idx += (ROUND_BUFLEN(ilen2) / 4); + h->msg.param[param_idx] = olen; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) { + if (h->msg.param[1] > olen) { + mutex_unlock(&h->client_lock); + return -EIO; + } + memcpy(obuf, &h->msg.param[2], h->msg.param[1]); + } + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_13); + +uint32_t dalrpc_fcn_14(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, void *obuf, uint32_t olen, + void *obuf2, uint32_t olen2, uint32_t *oalen2) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + int param_idx; + + if ((ilen + 12) > DALRPC_MAX_PARAMS_SIZE || + (olen + olen2 + 8) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 12 + + ROUND_BUFLEN(ilen); + h->msg.hdr.proto_id = 14; + h->msg.param[0] = ilen; + memcpy(&h->msg.param[1], ibuf, ilen); + param_idx = (ROUND_BUFLEN(ilen) / 4) + 1; + h->msg.param[param_idx++] = olen; + h->msg.param[param_idx] = olen2; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) { + if (h->msg.param[1] > olen) { + mutex_unlock(&h->client_lock); + return -EIO; + } + param_idx = (ROUND_BUFLEN(h->msg.param[1]) / 4) + 2; + if (h->msg.param[param_idx] > olen2) { + mutex_unlock(&h->client_lock); + return -EIO; + } + memcpy(obuf, &h->msg.param[2], h->msg.param[1]); + memcpy(obuf2, &h->msg.param[param_idx + 1], + h->msg.param[param_idx]); + *oalen2 = h->msg.param[param_idx]; + } + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_14); + +uint32_t dalrpc_fcn_15(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, const void *ibuf2, uint32_t ilen2, + void *obuf, uint32_t olen, uint32_t *oalen, + void *obuf2, uint32_t olen2) +{ + struct daldevice_handle *h = handle; + uint32_t ret; + int param_idx; + + if ((ilen + ilen2 + 16) > DALRPC_MAX_PARAMS_SIZE || + (olen + olen2 + 8) > DALRPC_MAX_PARAMS_SIZE) + return -EINVAL; + + if (!client_exists(h)) + return -EINVAL; + + mutex_lock(&h->client_lock); + + dalrpc_ddi_prologue(ddi_idx, h, 0); + + h->msg.hdr.len = sizeof(struct dalrpc_msg_hdr) + 16 + + ROUND_BUFLEN(ilen) + ROUND_BUFLEN(ilen2); + h->msg.hdr.proto_id = 15; + h->msg.param[0] = ilen; + memcpy(&h->msg.param[1], ibuf, ilen); + param_idx = (ROUND_BUFLEN(ilen) / 4) + 1; + h->msg.param[param_idx++] = ilen2; + memcpy(&h->msg.param[param_idx], ibuf2, ilen2); + param_idx += (ROUND_BUFLEN(ilen2) / 4); + h->msg.param[param_idx++] = olen; + h->msg.param[param_idx] = olen2; + + dalrpc_sendwait(h); + + if (h->msg.param[0] == DALRPC_SUCCESS) { + if (h->msg.param[1] > olen) { + mutex_unlock(&h->client_lock); + return -EIO; + } + param_idx = (ROUND_BUFLEN(h->msg.param[1]) / 4) + 2; + if (h->msg.param[param_idx] > olen2) { + mutex_unlock(&h->client_lock); + return -EIO; + } + memcpy(obuf, &h->msg.param[2], h->msg.param[1]); + memcpy(obuf2, &h->msg.param[param_idx + 1], + h->msg.param[param_idx]); + *oalen = h->msg.param[1]; + } + + ret = h->msg.param[0]; + mutex_unlock(&h->client_lock); + return ret; +} +EXPORT_SYMBOL(dalrpc_fcn_15); + +void *dalrpc_alloc_event(void *handle) +{ + struct daldevice_handle *h; + struct dalrpc_event_handle *ev; + + h = (struct daldevice_handle *)handle; + + if (!client_exists(h)) + return NULL; + + ev = kmalloc(sizeof(struct dalrpc_event_handle), GFP_KERNEL); + if (!ev) + return NULL; + + ev->flag = 0; + spin_lock_init(&ev->lock); + + mutex_lock(&h->port->event_list_lock); + list_add(&ev->list, &h->port->event_list); + mutex_unlock(&h->port->event_list_lock); + + return ev; +} +EXPORT_SYMBOL(dalrpc_alloc_event); + +void *dalrpc_alloc_cb(void *handle, + void (*fn)(void *, uint32_t, void *, uint32_t), + void *context) +{ + struct daldevice_handle *h; + struct dalrpc_cb_handle *cb; + + h = (struct daldevice_handle *)handle; + + if (!client_exists(h)) + return NULL; + + cb = kmalloc(sizeof(struct dalrpc_cb_handle), GFP_KERNEL); + if (!cb) + return NULL; + + cb->fn = fn; + cb->context = context; + + mutex_lock(&h->port->cb_list_lock); + list_add(&cb->list, &h->port->cb_list); + mutex_unlock(&h->port->cb_list_lock); + + return cb; +} +EXPORT_SYMBOL(dalrpc_alloc_cb); + +void dalrpc_dealloc_event(void *handle, + void *ev_h) +{ + struct daldevice_handle *h; + struct dalrpc_event_handle *ev; + + h = (struct daldevice_handle *)handle; + ev = (struct dalrpc_event_handle *)ev_h; + + mutex_lock(&h->port->event_list_lock); + list_del(&ev->list); + mutex_unlock(&h->port->event_list_lock); + kfree(ev); +} +EXPORT_SYMBOL(dalrpc_dealloc_event); + +void dalrpc_dealloc_cb(void *handle, + void *cb_h) +{ + struct daldevice_handle *h; + struct dalrpc_cb_handle *cb; + + h = (struct daldevice_handle *)handle; + cb = (struct dalrpc_cb_handle *)cb_h; + + mutex_lock(&h->port->cb_list_lock); + list_del(&cb->list); + mutex_unlock(&h->port->cb_list_lock); + kfree(cb); +} +EXPORT_SYMBOL(dalrpc_dealloc_cb); + +static int event_occurred(int num_events, struct dalrpc_event_handle **events, + int *occurred) +{ + int i; + + for (i = 0; i < num_events; i++) { + spin_lock(&events[i]->lock); + if (events[i]->flag) { + events[i]->flag = 0; + spin_unlock(&events[i]->lock); + *occurred = i; + return 1; + } + spin_unlock(&events[i]->lock); + } + + return 0; +} + +int dalrpc_event_wait_multiple(int num, void **ev_h, int timeout) +{ + struct dalrpc_event_handle **events; + int ret, occurred; + + events = (struct dalrpc_event_handle **)ev_h; + + if (timeout == DALRPC_TIMEOUT_INFINITE) { + wait_event(event_wq, + event_occurred(num, events, &occurred)); + return occurred; + } + + ret = wait_event_timeout(event_wq, + event_occurred(num, events, &occurred), + timeout); + if (ret > 0) + return occurred; + else + return -ETIMEDOUT; +} +EXPORT_SYMBOL(dalrpc_event_wait_multiple); diff --git a/arch/arm/mach-msm/dal_axi.c b/arch/arm/mach-msm/dal_axi.c new file mode 100644 index 00000000000..739b7dcd1b2 --- /dev/null +++ b/arch/arm/mach-msm/dal_axi.c @@ -0,0 +1,102 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +/* The AXI device ID */ +#define DALDEVICEID_AXI 0x02000053 +#define DALRPC_PORT_NAME "DAL00" + +enum { + DALRPC_AXI_CONFIGURE_BRIDGE = DALDEVICE_FIRST_DEVICE_API_IDX + 11 +}; + +enum { + DAL_AXI_BRIDGE_CFG_CGR_SS_2DGRP_SYNC_MODE = 14, + DAL_AXI_BRIDGE_CFG_CGR_SS_2DGRP_ASYNC_MODE, + DAL_AXI_BRIDGE_CFG_CGR_SS_2DGRP_ISOSYNC_MODE, + DAL_AXI_BRIDGE_CFG_CGR_SS_2DGRP_DEBUG_EN, + DAL_AXI_BRIDGE_CFG_CGR_SS_2DGRP_DEBUG_DIS, + DAL_AXI_BRIDGE_CFG_CGR_SS_3DGRP_SYNC_MODE, + DAL_AXI_BRIDGE_CFG_CGR_SS_3DGRP_ASYNC_MODE, + DAL_AXI_BRIDGE_CFG_CGR_SS_3DGRP_ISOSYNC_MODE, + DAL_AXI_BRIDGE_CFG_CGR_SS_3DGRP_DEBUG_EN, + DAL_AXI_BRIDGE_CFG_CGR_SS_3DGRP_DEBUG_DIS, + /* 7x27(A) Graphics Subsystem Bridge Configuration */ + DAL_AXI_BRIDGE_CFG_GRPSS_XBAR_SYNC_MODE = 58, + DAL_AXI_BRIDGE_CFG_GRPSS_XBAR_ASYNC_MODE = 59, + DAL_AXI_BRIDGE_CFG_GRPSS_XBAR_ISOSYNC_MODE = 60 + +}; + +static int axi_configure_bridge_grfx_sync_mode(int bridge_mode) +{ + int rc; + void *dev_handle; + + /* get device handle */ + rc = daldevice_attach( + DALDEVICEID_AXI, DALRPC_PORT_NAME, + DALRPC_DEST_MODEM, &dev_handle + ); + if (rc) { + printk(KERN_ERR "%s: failed to attach AXI bus device (%d)\n", + __func__, rc); + goto fail_dal_attach_detach; + } + + /* call ConfigureBridge */ + rc = dalrpc_fcn_0( + DALRPC_AXI_CONFIGURE_BRIDGE, dev_handle, + bridge_mode + ); + if (rc) { + printk(KERN_ERR "%s: AXI bus device (%d) failed to be configured\n", + __func__, rc); + goto fail_dal_fcn_0; + } + + /* close device handle */ + rc = daldevice_detach(dev_handle); + if (rc) { + printk(KERN_ERR "%s: failed to detach AXI bus device (%d)\n", + __func__, rc); + goto fail_dal_attach_detach; + } + + return 0; + +fail_dal_fcn_0: + (void)daldevice_detach(dev_handle); +fail_dal_attach_detach: + + return rc; +} + + + +int set_grp2d_async(void) +{ + return axi_configure_bridge_grfx_sync_mode( + DAL_AXI_BRIDGE_CFG_CGR_SS_2DGRP_ASYNC_MODE); +} + +int set_grp3d_async(void) +{ + return axi_configure_bridge_grfx_sync_mode( + DAL_AXI_BRIDGE_CFG_CGR_SS_3DGRP_ASYNC_MODE); +} + +int set_grp_xbar_async(void) +{ return axi_configure_bridge_grfx_sync_mode( + DAL_AXI_BRIDGE_CFG_GRPSS_XBAR_ASYNC_MODE); +} diff --git a/arch/arm/mach-msm/dal_remotetest.c b/arch/arm/mach-msm/dal_remotetest.c new file mode 100644 index 00000000000..d7a3f34c945 --- /dev/null +++ b/arch/arm/mach-msm/dal_remotetest.c @@ -0,0 +1,410 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * DAL remote test device test suite. + */ + +#include +#include +#include +#include +#include + +#include "dal_remotetest.h" + +#define BYTEBUF_LEN 64 + +#define rpc_error(num) \ + do { \ + errmask |= (1 << num); \ + printk(KERN_INFO "%s: remote_unittest_%d failed (%d)\n", \ + __func__, num, ret); \ + } while (0) + +#define verify_error(num, field) \ + do { \ + errmask |= (1 << num); \ + printk(KERN_INFO "%s: remote_unittest_%d failed (%s)\n", \ + __func__, num, field); \ + } while (0) + + +static struct dentry *debugfs_dir_entry; +static struct dentry *debugfs_modem_entry; +static struct dentry *debugfs_dsp_entry; + +static uint8_t in_bytebuf[BYTEBUF_LEN]; +static uint8_t out_bytebuf[BYTEBUF_LEN]; +static uint8_t out_bytebuf2[BYTEBUF_LEN]; +static struct remote_test_data in_data; +static struct remote_test_data out_data; +static int block_until_cb = 1; + +static void init_data(struct remote_test_data *data) +{ + int i; + data->regular_event = REMOTE_UNITTEST_INPUT_HANDLE; + data->payload_event = REMOTE_UNITTEST_INPUT_HANDLE; + for (i = 0; i < 32; i++) + data->test[i] = i; +} + +static int verify_data(struct remote_test_data *data) +{ + int i; + if (data->regular_event != REMOTE_UNITTEST_INPUT_HANDLE || + data->payload_event != REMOTE_UNITTEST_INPUT_HANDLE) + return -1; + for (i = 0; i < 32; i++) + if (data->test[i] != i) + return -1; + + return 0; +} + +static int verify_uint32_buffer(uint32_t *buf) +{ + int i; + for (i = 0; i < 32; i++) + if (buf[i] != i) + return -1; + + return 0; +} + +static void init_bytebuf(uint8_t *bytebuf) +{ + int i; + for (i = 0; i < BYTEBUF_LEN; i++) + bytebuf[i] = i & 0xff; +} + +static int verify_bytebuf(uint8_t *bytebuf) +{ + int i; + for (i = 0; i < BYTEBUF_LEN; i++) + if (bytebuf[i] != (i & 0xff)) + return -1; + + return 0; +} + +static void test_cb(void *context, uint32_t param, void *data, uint32_t len) +{ + block_until_cb = 0; +} + +static int remotetest_exec(int dest, u64 *val) +{ + void *dev_handle; + void *event_handles[3]; + void *cb_handle; + int ret; + u64 errmask = 0; + uint32_t ouint; + uint32_t oalen; + + /* test daldevice_attach */ + ret = daldevice_attach(REMOTE_UNITTEST_DEVICEID, NULL, + dest, &dev_handle); + if (ret) { + printk(KERN_INFO "%s: failed to attach (%d)\n", __func__, ret); + *val = 0xffffffff; + return 0; + } + + /* test remote_unittest_0 */ + ret = remote_unittest_0(dev_handle, REMOTE_UNITTEST_INARG_1); + if (ret) + rpc_error(0); + + /* test remote_unittest_1 */ + ret = remote_unittest_1(dev_handle, REMOTE_UNITTEST_INARG_1, + REMOTE_UNITTEST_INARG_2); + if (ret) + rpc_error(1); + + /* test remote_unittest_2 */ + ouint = 0; + ret = remote_unittest_2(dev_handle, REMOTE_UNITTEST_INARG_1, &ouint); + if (ret) + rpc_error(2); + else if (ouint != REMOTE_UNITTEST_OUTARG_1) + verify_error(2, "ouint"); + + /* test remote_unittest_3 */ + ret = remote_unittest_3(dev_handle, REMOTE_UNITTEST_INARG_1, + REMOTE_UNITTEST_INARG_2, + REMOTE_UNITTEST_INARG_3); + if (ret) + rpc_error(3); + + /* test remote_unittest_4 */ + ouint = 0; + ret = remote_unittest_4(dev_handle, REMOTE_UNITTEST_INARG_1, + REMOTE_UNITTEST_INARG_2, &ouint); + if (ret) + rpc_error(4); + else if (ouint != REMOTE_UNITTEST_OUTARG_1) + verify_error(4, "ouint"); + + /* test remote_unittest_5 */ + init_data(&in_data); + ret = remote_unittest_5(dev_handle, &in_data, sizeof(in_data)); + if (ret) + rpc_error(5); + + /* test remote_unittest_6 */ + init_data(&in_data); + ret = remote_unittest_6(dev_handle, REMOTE_UNITTEST_INARG_1, + &in_data.test, sizeof(in_data.test)); + if (ret) + rpc_error(6); + + /* test remote_unittest_7 */ + init_data(&in_data); + memset(&out_data, 0, sizeof(out_data)); + ret = remote_unittest_7(dev_handle, &in_data, sizeof(in_data), + &out_data.test, sizeof(out_data.test), + &oalen); + if (ret) + rpc_error(7); + else if (oalen != sizeof(out_data.test)) + verify_error(7, "oalen"); + else if (verify_uint32_buffer(out_data.test)) + verify_error(7, "obuf"); + + /* test remote_unittest_8 */ + init_bytebuf(in_bytebuf); + memset(&out_data, 0, sizeof(out_data)); + ret = remote_unittest_8(dev_handle, in_bytebuf, sizeof(in_bytebuf), + &out_data, sizeof(out_data)); + if (ret) + rpc_error(8); + else if (verify_data(&out_data)) + verify_error(8, "obuf"); + + /* test remote_unittest_9 */ + memset(&out_bytebuf, 0, sizeof(out_bytebuf)); + ret = remote_unittest_9(dev_handle, out_bytebuf, sizeof(out_bytebuf)); + if (ret) + rpc_error(9); + else if (verify_bytebuf(out_bytebuf)) + verify_error(9, "obuf"); + + /* test remote_unittest_10 */ + init_bytebuf(in_bytebuf); + memset(&out_bytebuf, 0, sizeof(out_bytebuf)); + ret = remote_unittest_10(dev_handle, REMOTE_UNITTEST_INARG_1, + in_bytebuf, sizeof(in_bytebuf), + out_bytebuf, sizeof(out_bytebuf), &oalen); + if (ret) + rpc_error(10); + else if (oalen != sizeof(out_bytebuf)) + verify_error(10, "oalen"); + else if (verify_bytebuf(out_bytebuf)) + verify_error(10, "obuf"); + + /* test remote_unittest_11 */ + memset(&out_bytebuf, 0, sizeof(out_bytebuf)); + ret = remote_unittest_11(dev_handle, REMOTE_UNITTEST_INARG_1, + out_bytebuf, sizeof(out_bytebuf)); + if (ret) + rpc_error(11); + else if (verify_bytebuf(out_bytebuf)) + verify_error(11, "obuf"); + + /* test remote_unittest_12 */ + memset(&out_bytebuf, 0, sizeof(out_bytebuf)); + ret = remote_unittest_12(dev_handle, REMOTE_UNITTEST_INARG_1, + out_bytebuf, sizeof(out_bytebuf), &oalen); + if (ret) + rpc_error(12); + else if (oalen != sizeof(out_bytebuf)) + verify_error(12, "oalen"); + else if (verify_bytebuf(out_bytebuf)) + verify_error(12, "obuf"); + + /* test remote_unittest_13 */ + init_data(&in_data); + memset(&out_data, 0, sizeof(out_data)); + ret = remote_unittest_13(dev_handle, in_data.test, sizeof(in_data.test), + &in_data, sizeof(in_data), + &out_data, sizeof(out_data)); + if (ret) + rpc_error(13); + else if (verify_data(&out_data)) + verify_error(13, "obuf"); + + /* test remote_unittest_14 */ + init_data(&in_data); + memset(out_bytebuf, 0, sizeof(out_bytebuf)); + memset(out_bytebuf2, 0, sizeof(out_bytebuf2)); + ret = remote_unittest_14(dev_handle, + in_data.test, sizeof(in_data.test), + out_bytebuf, sizeof(out_bytebuf), + out_bytebuf2, sizeof(out_bytebuf2), &oalen); + if (ret) + rpc_error(14); + else if (verify_bytebuf(out_bytebuf)) + verify_error(14, "obuf"); + else if (oalen != sizeof(out_bytebuf2)) + verify_error(14, "oalen"); + else if (verify_bytebuf(out_bytebuf2)) + verify_error(14, "obuf2"); + + /* test remote_unittest_15 */ + init_data(&in_data); + memset(out_bytebuf, 0, sizeof(out_bytebuf)); + memset(&out_data, 0, sizeof(out_data)); + ret = remote_unittest_15(dev_handle, + in_data.test, sizeof(in_data.test), + &in_data, sizeof(in_data), + &out_data, sizeof(out_data), &oalen, + out_bytebuf, sizeof(out_bytebuf)); + if (ret) + rpc_error(15); + else if (oalen != sizeof(out_data)) + verify_error(15, "oalen"); + else if (verify_bytebuf(out_bytebuf)) + verify_error(15, "obuf"); + else if (verify_data(&out_data)) + verify_error(15, "obuf2"); + + /* test setting up asynch events */ + event_handles[0] = dalrpc_alloc_event(dev_handle); + event_handles[1] = dalrpc_alloc_event(dev_handle); + event_handles[2] = dalrpc_alloc_event(dev_handle); + cb_handle = dalrpc_alloc_cb(dev_handle, test_cb, &out_data); + in_data.regular_event = (uint32_t)event_handles[2]; + in_data.payload_event = (uint32_t)cb_handle; + ret = remote_unittest_eventcfg(dev_handle, &in_data, sizeof(in_data)); + if (ret) { + errmask |= (1 << 16); + printk(KERN_INFO "%s: failed to configure asynch (%d)\n", + __func__, ret); + } + + /* test event */ + ret = remote_unittest_eventtrig(dev_handle, + REMOTE_UNITTEST_REGULAR_EVENT); + if (ret) { + errmask |= (1 << 17); + printk(KERN_INFO "%s: failed to trigger event (%d)\n", + __func__, ret); + } + ret = dalrpc_event_wait(event_handles[2], 1000); + if (ret) { + errmask |= (1 << 18); + printk(KERN_INFO "%s: failed to receive event (%d)\n", + __func__, ret); + } + + /* test event again */ + ret = remote_unittest_eventtrig(dev_handle, + REMOTE_UNITTEST_REGULAR_EVENT); + if (ret) { + errmask |= (1 << 19); + printk(KERN_INFO "%s: failed to trigger event (%d)\n", + __func__, ret); + } + ret = dalrpc_event_wait_multiple(3, event_handles, 1000); + if (ret != 2) { + errmask |= (1 << 20); + printk(KERN_INFO "%s: failed to receive event (%d)\n", + __func__, ret); + } + + /* test callback */ + ret = remote_unittest_eventtrig(dev_handle, + REMOTE_UNITTEST_CALLBACK_EVENT); + if (ret) { + errmask |= (1 << 21); + printk(KERN_INFO "%s: failed to trigger callback (%d)\n", + __func__, ret); + } else + while (block_until_cb) + ; + + dalrpc_dealloc_cb(dev_handle, cb_handle); + dalrpc_dealloc_event(dev_handle, event_handles[0]); + dalrpc_dealloc_event(dev_handle, event_handles[1]); + dalrpc_dealloc_event(dev_handle, event_handles[2]); + + /* test daldevice_detach */ + ret = daldevice_detach(dev_handle); + if (ret) { + errmask |= (1 << 22); + printk(KERN_INFO "%s: failed to detach (%d)\n", __func__, ret); + } + + printk(KERN_INFO "%s: remote_unittest complete\n", __func__); + + *val = errmask; + return 0; +} + +static int remotetest_modem_exec(void *data, u64 *val) +{ + return remotetest_exec(DALRPC_DEST_MODEM, val); +} + +static int remotetest_dsp_exec(void *data, u64 *val) +{ + return remotetest_exec(DALRPC_DEST_QDSP, val); +} + +DEFINE_SIMPLE_ATTRIBUTE(dal_modemtest_fops, remotetest_modem_exec, + NULL, "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(dal_dsptest_fops, remotetest_dsp_exec, + NULL, "%llu\n"); + +static int __init remotetest_init(void) +{ + debugfs_dir_entry = debugfs_create_dir("dal", 0); + if (IS_ERR(debugfs_dir_entry)) + return PTR_ERR(debugfs_dir_entry); + + debugfs_modem_entry = debugfs_create_file("modem_test", 0444, + debugfs_dir_entry, + NULL, &dal_modemtest_fops); + if (IS_ERR(debugfs_modem_entry)) { + debugfs_remove(debugfs_dir_entry); + return PTR_ERR(debugfs_modem_entry); + } + + debugfs_dsp_entry = debugfs_create_file("dsp_test", 0444, + debugfs_dir_entry, + NULL, &dal_dsptest_fops); + if (IS_ERR(debugfs_dsp_entry)) { + debugfs_remove(debugfs_modem_entry); + debugfs_remove(debugfs_dir_entry); + return PTR_ERR(debugfs_dsp_entry); + } + + return 0; +} + +static void __exit remotetest_exit(void) +{ + debugfs_remove(debugfs_modem_entry); + debugfs_remove(debugfs_dsp_entry); + debugfs_remove(debugfs_dir_entry); +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Test for DAL RPC"); +MODULE_VERSION("1.0"); + +module_init(remotetest_init); +module_exit(remotetest_exit); diff --git a/arch/arm/mach-msm/dal_remotetest.h b/arch/arm/mach-msm/dal_remotetest.h new file mode 100644 index 00000000000..cb998c904a8 --- /dev/null +++ b/arch/arm/mach-msm/dal_remotetest.h @@ -0,0 +1,172 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * DAL remote test device API. + */ + +#include + +#include + +#define REMOTE_UNITTEST_DEVICEID 0xDA1DA1DA + +enum { + DALRPC_TEST_API_0 = DALDEVICE_FIRST_DEVICE_API_IDX, + DALRPC_TEST_API_1, + DALRPC_TEST_API_2, + DALRPC_TEST_API_3, + DALRPC_TEST_API_4, + DALRPC_TEST_API_5, + DALRPC_TEST_API_6, + DALRPC_TEST_API_7, + DALRPC_TEST_API_8, + DALRPC_TEST_API_9, + DALRPC_TEST_API_10, + DALRPC_TEST_API_11, + DALRPC_TEST_API_12, + DALRPC_TEST_API_13, + DALRPC_TEST_API_14, + DALRPC_TEST_API_15, + DALRPC_TEST_API_16, + DALRPC_TEST_API_17 +}; + +#define REMOTE_UNITTEST_INARG_1 0x01010101 +#define REMOTE_UNITTEST_INARG_2 0x20202020 +#define REMOTE_UNITTEST_INARG_3 0x12121212 +#define REMOTE_UNITTEST_INPUT_HANDLE 0xDA1FDA1F +#define REMOTE_UNITTEST_OUTARG_1 0xBEEFDEAD + +#define REMOTE_UNITTEST_REGULAR_EVENT 0 +#define REMOTE_UNITTEST_CALLBACK_EVENT 1 + +#define REMOTE_UNITTEST_BAD_PARAM 0x10 + +struct remote_test_data { + uint32_t regular_event; + uint32_t test[32]; + uint32_t payload_event; +}; + +static int remote_unittest_0(void *handle, uint32_t s1) +{ + return dalrpc_fcn_0(DALRPC_TEST_API_0, handle, s1); +} + +static int remote_unittest_1(void *handle, uint32_t s1, uint32_t s2) +{ + return dalrpc_fcn_1(DALRPC_TEST_API_1, handle, s1, s2); +} + +static int remote_unittest_2(void *handle, uint32_t s1, uint32_t *p_s2) +{ + return dalrpc_fcn_2(DALRPC_TEST_API_2, handle, s1, p_s2); +} + +static int remote_unittest_3(void *handle, uint32_t s1, uint32_t s2, + uint32_t s3) +{ + return dalrpc_fcn_3(DALRPC_TEST_API_3, handle, s1, s2, s3); +} + +static int remote_unittest_4(void *handle, uint32_t s1, uint32_t s2, + uint32_t *p_s3) +{ + return dalrpc_fcn_4(DALRPC_TEST_API_4, handle, s1, s2, p_s3); +} + +static int remote_unittest_5(void *handle, const void *ibuf, uint32_t ilen) +{ + return dalrpc_fcn_5(DALRPC_TEST_API_5, handle, ibuf, ilen); +} + +static int remote_unittest_6(void *handle, uint32_t s1, const void *ibuf, + uint32_t ilen) +{ + return dalrpc_fcn_6(DALRPC_TEST_API_6, handle, s1, ibuf, ilen); +} + +static int remote_unittest_7(void *handle, const void *ibuf, uint32_t ilen, + void *obuf, uint32_t olen, uint32_t *oalen) +{ + return dalrpc_fcn_7(DALRPC_TEST_API_7, handle, ibuf, ilen, obuf, + olen, oalen); +} + +static int remote_unittest_8(void *handle, const void *ibuf, uint32_t ilen, + void *obuf, uint32_t olen) +{ + return dalrpc_fcn_8(DALRPC_TEST_API_8, handle, ibuf, ilen, obuf, olen); +} + +static int remote_unittest_9(void *handle, void *obuf, uint32_t olen) +{ + return dalrpc_fcn_9(DALRPC_TEST_API_9, handle, obuf, olen); +} + +static int remote_unittest_10(void *handle, uint32_t s1, const void *ibuf, + uint32_t ilen, void *obuf, uint32_t olen, + uint32_t *oalen) +{ + return dalrpc_fcn_10(DALRPC_TEST_API_10, handle, s1, ibuf, ilen, obuf, + olen, oalen); +} + +static int remote_unittest_11(void *handle, uint32_t s1, void *obuf, + uint32_t olen) +{ + return dalrpc_fcn_11(DALRPC_TEST_API_11, handle, s1, obuf, olen); +} + +static int remote_unittest_12(void *handle, uint32_t s1, void *obuf, + uint32_t olen, uint32_t *oalen) +{ + return dalrpc_fcn_12(DALRPC_TEST_API_12, handle, s1, obuf, olen, + oalen); +} + +static int remote_unittest_13(void *handle, const void *ibuf, uint32_t ilen, + const void *ibuf2, uint32_t ilen2, void *obuf, + uint32_t olen) +{ + return dalrpc_fcn_13(DALRPC_TEST_API_13, handle, ibuf, ilen, ibuf2, + ilen2, obuf, olen); +} + +static int remote_unittest_14(void *handle, const void *ibuf, uint32_t ilen, + void *obuf, uint32_t olen, void *obuf2, + uint32_t olen2, uint32_t *oalen2) +{ + return dalrpc_fcn_14(DALRPC_TEST_API_14, handle, ibuf, ilen, obuf, + olen, obuf2, olen2, oalen2); +} + +static int remote_unittest_15(void *handle, const void *ibuf, uint32_t ilen, + const void *ibuf2, uint32_t ilen2, void *obuf, + uint32_t olen, uint32_t *oalen, void *obuf2, + uint32_t olen2) +{ + return dalrpc_fcn_15(DALRPC_TEST_API_15, handle, ibuf, ilen, ibuf2, + ilen2, obuf, olen, oalen, obuf2, olen2); +} + +static int remote_unittest_eventcfg(void *handle, const void *ibuf, + uint32_t ilen) +{ + return dalrpc_fcn_5(DALRPC_TEST_API_16, handle, ibuf, ilen); +} + +static int remote_unittest_eventtrig(void *handle, uint32_t event_idx) +{ + return dalrpc_fcn_0(DALRPC_TEST_API_17, handle, event_idx); +} diff --git a/arch/arm/mach-msm/devices-8064.c b/arch/arm/mach-msm/devices-8064.c new file mode 100644 index 00000000000..3c4fe1b2385 --- /dev/null +++ b/arch/arm/mach-msm/devices-8064.c @@ -0,0 +1,565 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "clock.h" +#include "devices.h" + +/* Address of GSBI blocks */ +#define MSM_GSBI3_PHYS 0x16200000 +#define MSM_UART3DM_PHYS (MSM_GSBI3_PHYS + 0x40000) + +static struct resource resources_uart_gsbi3[] = { + { + .start = GSBI3_UARTDM_IRQ, + .end = GSBI3_UARTDM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART3DM_PHYS, + .end = MSM_UART3DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = MSM_GSBI3_PHYS, + .end = MSM_GSBI3_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device apq8064_device_uart_gsbi3 = { + .name = "msm_serial_hsl", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart_gsbi3), + .resource = resources_uart_gsbi3, +}; + +static struct resource resources_qup_spi_gsbi5[] = { + { + .name = "spi_base", + .start = MSM_GSBI5_QUP_PHYS, + .end = MSM_GSBI5_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_base", + .start = MSM_GSBI5_PHYS, + .end = MSM_GSBI5_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "spi_irq_in", + .start = GSBI5_QUP_IRQ, + .end = GSBI5_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device apq8064_device_qup_spi_gsbi5 = { + .name = "spi_qsd", + .id = 0, + .num_resources = ARRAY_SIZE(resources_qup_spi_gsbi5), + .resource = resources_qup_spi_gsbi5, +}; + +static struct resource resources_ssbi_pmic1[] = { + { + .start = MSM_PMIC1_SSBI_CMD_PHYS, + .end = MSM_PMIC1_SSBI_CMD_PHYS + MSM_PMIC_SSBI_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device apq8064_device_ssbi_pmic1 = { + .name = "msm_ssbi", + .id = 0, + .resource = resources_ssbi_pmic1, + .num_resources = ARRAY_SIZE(resources_ssbi_pmic1), +}; + +static struct resource resources_ssbi_pmic2[] = { + { + .start = MSM_PMIC2_SSBI_CMD_PHYS, + .end = MSM_PMIC2_SSBI_CMD_PHYS + MSM_PMIC_SSBI_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device apq8064_device_ssbi_pmic2 = { + .name = "msm_ssbi", + .id = 1, + .resource = resources_ssbi_pmic2, + .num_resources = ARRAY_SIZE(resources_ssbi_pmic2), +}; + +static struct resource resources_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + MSM_HSUSB_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_otg), + .resource = resources_otg, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct resource resources_hsusb[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + MSM_HSUSB_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_gadget_peripheral = { + .name = "msm_hsusb", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb), + .resource = resources_hsusb, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +#define MSM_SDC1_BASE 0x12400000 +#define MSM_SDC1_DML_BASE (MSM_SDC1_BASE + 0x800) +#define MSM_SDC1_BAM_BASE (MSM_SDC1_BASE + 0x2000) +#define MSM_SDC2_BASE 0x12140000 +#define MSM_SDC2_DML_BASE (MSM_SDC2_BASE + 0x800) +#define MSM_SDC2_BAM_BASE (MSM_SDC2_BASE + 0x2000) +#define MSM_SDC3_BASE 0x12180000 +#define MSM_SDC3_DML_BASE (MSM_SDC3_BASE + 0x800) +#define MSM_SDC3_BAM_BASE (MSM_SDC3_BASE + 0x2000) +#define MSM_SDC4_BASE 0x121C0000 +#define MSM_SDC4_DML_BASE (MSM_SDC4_BASE + 0x800) +#define MSM_SDC4_BAM_BASE (MSM_SDC4_BASE + 0x2000) + +static struct resource resources_sdc1[] = { + { + .name = "core_mem", + .flags = IORESOURCE_MEM, + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_DML_BASE - 1, + }, + { + .name = "core_irq", + .flags = IORESOURCE_IRQ, + .start = SDC1_IRQ_0, + .end = SDC1_IRQ_0 + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC1_DML_BASE, + .end = MSM_SDC1_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC1_BAM_BASE, + .end = MSM_SDC1_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC1_BAM_IRQ, + .end = SDC1_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#endif +}; + +static struct resource resources_sdc2[] = { + { + .name = "core_mem", + .flags = IORESOURCE_MEM, + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_DML_BASE - 1, + }, + { + .name = "core_irq", + .flags = IORESOURCE_IRQ, + .start = SDC2_IRQ_0, + .end = SDC2_IRQ_0 + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC2_DML_BASE, + .end = MSM_SDC2_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC2_BAM_BASE, + .end = MSM_SDC2_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC2_BAM_IRQ, + .end = SDC2_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#endif +}; + +static struct resource resources_sdc3[] = { + { + .name = "core_mem", + .flags = IORESOURCE_MEM, + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_DML_BASE - 1, + }, + { + .name = "core_irq", + .flags = IORESOURCE_IRQ, + .start = SDC3_IRQ_0, + .end = SDC3_IRQ_0 + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC3_DML_BASE, + .end = MSM_SDC3_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC3_BAM_BASE, + .end = MSM_SDC3_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC3_BAM_IRQ, + .end = SDC3_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#endif +}; + +static struct resource resources_sdc4[] = { + { + .name = "core_mem", + .flags = IORESOURCE_MEM, + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_DML_BASE - 1, + }, + { + .name = "core_irq", + .flags = IORESOURCE_IRQ, + .start = SDC4_IRQ_0, + .end = SDC4_IRQ_0 + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC4_DML_BASE, + .end = MSM_SDC4_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC4_BAM_BASE, + .end = MSM_SDC4_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC4_BAM_IRQ, + .end = SDC4_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#endif +}; + +struct platform_device apq8064_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_sdc1), + .resource = resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device apq8064_device_sdc2 = { + .name = "msm_sdcc", + .id = 2, + .num_resources = ARRAY_SIZE(resources_sdc2), + .resource = resources_sdc2, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device apq8064_device_sdc3 = { + .name = "msm_sdcc", + .id = 3, + .num_resources = ARRAY_SIZE(resources_sdc3), + .resource = resources_sdc3, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device apq8064_device_sdc4 = { + .name = "msm_sdcc", + .id = 4, + .num_resources = ARRAY_SIZE(resources_sdc4), + .resource = resources_sdc4, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct platform_device *apq8064_sdcc_devices[] __initdata = { + &apq8064_device_sdc1, + &apq8064_device_sdc2, + &apq8064_device_sdc3, + &apq8064_device_sdc4, +}; + +int __init apq8064_add_sdcc(unsigned int controller, + struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (!plat) + return 0; + if (controller < 1 || controller > 4) + return -EINVAL; + + pdev = apq8064_sdcc_devices[controller-1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +static struct clk_lookup msm_clocks_8064_dummy[] = { + CLK_DUMMY("pll2", PLL2, NULL, 0), + CLK_DUMMY("pll8", PLL8, NULL, 0), + CLK_DUMMY("pll4", PLL4, NULL, 0), + + CLK_DUMMY("afab_clk", AFAB_CLK, NULL, 0), + CLK_DUMMY("afab_a_clk", AFAB_A_CLK, NULL, 0), + CLK_DUMMY("cfpb_clk", CFPB_CLK, NULL, 0), + CLK_DUMMY("cfpb_a_clk", CFPB_A_CLK, NULL, 0), + CLK_DUMMY("dfab_clk", DFAB_CLK, NULL, 0), + CLK_DUMMY("dfab_a_clk", DFAB_A_CLK, NULL, 0), + CLK_DUMMY("ebi1_clk", EBI1_CLK, NULL, 0), + CLK_DUMMY("ebi1_a_clk", EBI1_A_CLK, NULL, 0), + CLK_DUMMY("mmfab_clk", MMFAB_CLK, NULL, 0), + CLK_DUMMY("mmfab_a_clk", MMFAB_A_CLK, NULL, 0), + CLK_DUMMY("mmfpb_clk", MMFPB_CLK, NULL, 0), + CLK_DUMMY("mmfpb_a_clk", MMFPB_A_CLK, NULL, 0), + CLK_DUMMY("sfab_clk", SFAB_CLK, NULL, 0), + CLK_DUMMY("sfab_a_clk", SFAB_A_CLK, NULL, 0), + CLK_DUMMY("sfpb_clk", SFPB_CLK, NULL, 0), + CLK_DUMMY("sfpb_a_clk", SFPB_A_CLK, NULL, 0), + + CLK_DUMMY("gsbi_uart_clk", GSBI1_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI2_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI3_UART_CLK, + "msm_serial_hsl.0", OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI4_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI5_UART_CLK, NULL, OFF), + CLK_DUMMY("uartdm_clk", GSBI6_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI7_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI8_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI9_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI10_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI11_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI12_UART_CLK, NULL, OFF), + CLK_DUMMY("spi_clk", GSBI1_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI2_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI3_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI4_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI5_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI6_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI7_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI8_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI9_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI10_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI11_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI12_QUP_CLK, NULL, OFF), + CLK_DUMMY("pdm_clk", PDM_CLK, NULL, OFF), + CLK_DUMMY("pmem_clk", PMEM_CLK, NULL, OFF), + CLK_DUMMY("prng_clk", PRNG_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC1_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC2_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC3_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC4_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC5_CLK, NULL, OFF), + CLK_DUMMY("tsif_ref_clk", TSIF_REF_CLK, NULL, OFF), + CLK_DUMMY("tssc_clk", TSSC_CLK, NULL, OFF), + CLK_DUMMY("usb_hs_clk", USB_HS1_XCVR_CLK, NULL, OFF), + CLK_DUMMY("usb_phy_clk", USB_PHY0_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_src_clk", USB_FS1_SRC_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_clk", USB_FS1_XCVR_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_sys_clk", USB_FS1_SYS_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_src_clk", USB_FS2_SRC_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_clk", USB_FS2_XCVR_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_sys_clk", USB_FS2_SYS_CLK, NULL, OFF), + CLK_DUMMY("ce_pclk", CE2_CLK, NULL, OFF), + CLK_DUMMY("ce_clk", CE1_CORE_CLK, NULL, OFF), + CLK_DUMMY("spi_pclk", GSBI1_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI2_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI3_P_CLK, + "msm_serial_hsl.0", OFF), + CLK_DUMMY("gsbi_pclk", GSBI4_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI5_P_CLK, NULL, OFF), + CLK_DUMMY("uartdm_pclk", GSBI6_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI7_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI8_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI9_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI10_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI11_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI12_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI12_P_CLK, NULL, OFF), + CLK_DUMMY("tsif_pclk", TSIF_P_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_pclk", USB_FS1_P_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_pclk", USB_FS2_P_CLK, NULL, OFF), + CLK_DUMMY("usb_hs_pclk", USB_HS1_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC1_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC2_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC3_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC4_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC5_P_CLK, NULL, OFF), + CLK_DUMMY("adm_clk", ADM0_CLK, NULL, OFF), + CLK_DUMMY("adm_pclk", ADM0_P_CLK, NULL, OFF), + CLK_DUMMY("pmic_arb_pclk", PMIC_ARB0_P_CLK, NULL, OFF), + CLK_DUMMY("pmic_arb_pclk", PMIC_ARB1_P_CLK, NULL, OFF), + CLK_DUMMY("pmic_ssbi2", PMIC_SSBI2_CLK, NULL, OFF), + CLK_DUMMY("rpm_msg_ram_pclk", RPM_MSG_RAM_P_CLK, NULL, OFF), + CLK_DUMMY("amp_clk", AMP_CLK, NULL, OFF), + CLK_DUMMY("cam_clk", CAM0_CLK, NULL, OFF), + CLK_DUMMY("cam_clk", CAM1_CLK, NULL, OFF), + CLK_DUMMY("csi_src_clk", CSI0_SRC_CLK, NULL, OFF), + CLK_DUMMY("csi_src_clk", CSI1_SRC_CLK, NULL, OFF), + CLK_DUMMY("csi_clk", CSI0_CLK, NULL, OFF), + CLK_DUMMY("csi_clk", CSI1_CLK, NULL, OFF), + CLK_DUMMY("csi_pix_clk", CSI_PIX_CLK, NULL, OFF), + CLK_DUMMY("csi_rdi_clk", CSI_RDI_CLK, NULL, OFF), + CLK_DUMMY("csiphy_timer_src_clk", CSIPHY_TIMER_SRC_CLK, NULL, OFF), + CLK_DUMMY("csi0phy_timer_clk", CSIPHY0_TIMER_CLK, NULL, OFF), + CLK_DUMMY("csi1phy_timer_clk", CSIPHY1_TIMER_CLK, NULL, OFF), + CLK_DUMMY("dsi_byte_div_clk", DSI1_BYTE_CLK, NULL, OFF), + CLK_DUMMY("dsi_byte_div_clk", DSI2_BYTE_CLK, NULL, OFF), + CLK_DUMMY("dsi_esc_clk", DSI1_ESC_CLK, NULL, OFF), + CLK_DUMMY("dsi_esc_clk", DSI2_ESC_CLK, NULL, OFF), + CLK_DUMMY("gfx2d0_clk", GFX2D0_CLK, NULL, OFF), + CLK_DUMMY("gfx2d1_clk", GFX2D1_CLK, NULL, OFF), + CLK_DUMMY("gfx3d_clk", GFX3D_CLK, NULL, OFF), + CLK_DUMMY("ijpeg_clk", IJPEG_CLK, NULL, OFF), + CLK_DUMMY("imem_clk", IMEM_CLK, NULL, OFF), + CLK_DUMMY("jpegd_clk", JPEGD_CLK, NULL, OFF), + CLK_DUMMY("mdp_clk", MDP_CLK, NULL, OFF), + CLK_DUMMY("mdp_vsync_clk", MDP_VSYNC_CLK, NULL, OFF), + CLK_DUMMY("lut_mdp", LUT_MDP_CLK, NULL, OFF), + CLK_DUMMY("rot_clk", ROT_CLK, NULL, OFF), + CLK_DUMMY("tv_src_clk", TV_SRC_CLK, NULL, OFF), + CLK_DUMMY("tv_enc_clk", TV_ENC_CLK, NULL, OFF), + CLK_DUMMY("tv_dac_clk", TV_DAC_CLK, NULL, OFF), + CLK_DUMMY("vcodec_clk", VCODEC_CLK, NULL, OFF), + CLK_DUMMY("mdp_tv_clk", MDP_TV_CLK, NULL, OFF), + CLK_DUMMY("hdmi_clk", HDMI_TV_CLK, NULL, OFF), + CLK_DUMMY("hdmi_app_clk", HDMI_APP_CLK, NULL, OFF), + CLK_DUMMY("vpe_clk", VPE_CLK, NULL, OFF), + CLK_DUMMY("vfe_clk", VFE_CLK, NULL, OFF), + CLK_DUMMY("csi_vfe_clk", CSI0_VFE_CLK, NULL, OFF), + CLK_DUMMY("vfe_axi_clk", VFE_AXI_CLK, NULL, OFF), + CLK_DUMMY("ijpeg_axi_clk", IJPEG_AXI_CLK, NULL, OFF), + CLK_DUMMY("mdp_axi_clk", MDP_AXI_CLK, NULL, OFF), + CLK_DUMMY("rot_axi_clk", ROT_AXI_CLK, NULL, OFF), + CLK_DUMMY("vcodec_axi_clk", VCODEC_AXI_CLK, NULL, OFF), + CLK_DUMMY("vcodec_axi_a_clk", VCODEC_AXI_A_CLK, NULL, OFF), + CLK_DUMMY("vcodec_axi_b_clk", VCODEC_AXI_B_CLK, NULL, OFF), + CLK_DUMMY("vpe_axi_clk", VPE_AXI_CLK, NULL, OFF), + CLK_DUMMY("amp_pclk", AMP_P_CLK, NULL, OFF), + CLK_DUMMY("csi_pclk", CSI0_P_CLK, NULL, OFF), + CLK_DUMMY("dsi_m_pclk", DSI1_M_P_CLK, NULL, OFF), + CLK_DUMMY("dsi_s_pclk", DSI1_S_P_CLK, NULL, OFF), + CLK_DUMMY("dsi_m_pclk", DSI2_M_P_CLK, NULL, OFF), + CLK_DUMMY("dsi_s_pclk", DSI2_S_P_CLK, NULL, OFF), + CLK_DUMMY("gfx2d0_pclk", GFX2D0_P_CLK, NULL, OFF), + CLK_DUMMY("gfx2d1_pclk", GFX2D1_P_CLK, NULL, OFF), + CLK_DUMMY("gfx3d_pclk", GFX3D_P_CLK, NULL, OFF), + CLK_DUMMY("hdmi_m_pclk", HDMI_M_P_CLK, NULL, OFF), + CLK_DUMMY("hdmi_s_pclk", HDMI_S_P_CLK, NULL, OFF), + CLK_DUMMY("ijpeg_pclk", IJPEG_P_CLK, NULL, OFF), + CLK_DUMMY("jpegd_pclk", JPEGD_P_CLK, NULL, OFF), + CLK_DUMMY("imem_pclk", IMEM_P_CLK, NULL, OFF), + CLK_DUMMY("mdp_pclk", MDP_P_CLK, NULL, OFF), + CLK_DUMMY("smmu_pclk", SMMU_P_CLK, NULL, OFF), + CLK_DUMMY("rotator_pclk", ROT_P_CLK, NULL, OFF), + CLK_DUMMY("tv_enc_pclk", TV_ENC_P_CLK, NULL, OFF), + CLK_DUMMY("vcodec_pclk", VCODEC_P_CLK, NULL, OFF), + CLK_DUMMY("vfe_pclk", VFE_P_CLK, NULL, OFF), + CLK_DUMMY("vpe_pclk", VPE_P_CLK, NULL, OFF), + CLK_DUMMY("mi2s_osr_clk", MI2S_OSR_CLK, NULL, OFF), + CLK_DUMMY("mi2s_bit_clk", MI2S_BIT_CLK, NULL, OFF), + CLK_DUMMY("i2s_mic_osr_clk", CODEC_I2S_MIC_OSR_CLK, NULL, OFF), + CLK_DUMMY("i2s_mic_bit_clk", CODEC_I2S_MIC_BIT_CLK, NULL, OFF), + CLK_DUMMY("i2s_mic_osr_clk", SPARE_I2S_MIC_OSR_CLK, NULL, OFF), + CLK_DUMMY("i2s_mic_bit_clk", SPARE_I2S_MIC_BIT_CLK, NULL, OFF), + CLK_DUMMY("i2s_spkr_osr_clk", CODEC_I2S_SPKR_OSR_CLK, NULL, OFF), + CLK_DUMMY("i2s_spkr_bit_clk", CODEC_I2S_SPKR_BIT_CLK, NULL, OFF), + CLK_DUMMY("i2s_spkr_osr_clk", SPARE_I2S_SPKR_OSR_CLK, NULL, OFF), + CLK_DUMMY("i2s_spkr_bit_clk", SPARE_I2S_SPKR_BIT_CLK, NULL, OFF), + CLK_DUMMY("pcm_clk", PCM_CLK, NULL, OFF), + CLK_DUMMY("iommu_clk", JPEGD_AXI_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", VFE_AXI_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", VCODEC_AXI_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", GFX3D_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", GFX2D0_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", GFX2D1_CLK, NULL, 0), + + CLK_DUMMY("dfab_dsps_clk", DFAB_DSPS_CLK, NULL, 0), + CLK_DUMMY("dfab_usb_hs_clk", DFAB_USB_HS_CLK, NULL, 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC1_CLK, NULL, 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC2_CLK, NULL, 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC3_CLK, NULL, 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC4_CLK, NULL, 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC5_CLK, NULL, 0), + CLK_DUMMY("dfab_clk", DFAB_CLK, NULL, 0), + CLK_DUMMY("dma_bam_pclk", DMA_BAM_P_CLK, NULL, 0), +}; + +unsigned msm_num_clocks_8064_dummy = ARRAY_SIZE(msm_clocks_8064_dummy); diff --git a/arch/arm/mach-msm/devices-8960.c b/arch/arm/mach-msm/devices-8960.c new file mode 100644 index 00000000000..886f1434086 --- /dev/null +++ b/arch/arm/mach-msm/devices-8960.c @@ -0,0 +1,2052 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "clock.h" +#include "devices.h" +#include "devices-msm8x60.h" +#include "footswitch.h" + +#ifdef CONFIG_MSM_MPM +#include "mpm.h" +#endif +#ifdef CONFIG_MSM_DSPS +#include +#endif + + +/* Address of GSBI blocks */ +#define MSM_GSBI1_PHYS 0x16000000 +#define MSM_GSBI2_PHYS 0x16100000 +#define MSM_GSBI3_PHYS 0x16200000 +#define MSM_GSBI4_PHYS 0x16300000 +#define MSM_GSBI5_PHYS 0x16400000 +#define MSM_GSBI6_PHYS 0x16500000 +#define MSM_GSBI7_PHYS 0x16600000 +#define MSM_GSBI8_PHYS 0x1A000000 +#define MSM_GSBI9_PHYS 0x1A100000 +#define MSM_GSBI10_PHYS 0x1A200000 +#define MSM_GSBI11_PHYS 0x12440000 +#define MSM_GSBI12_PHYS 0x12480000 + +#define MSM_UART2DM_PHYS (MSM_GSBI2_PHYS + 0x40000) +#define MSM_UART5DM_PHYS (MSM_GSBI5_PHYS + 0x40000) + +/* GSBI QUP devices */ +#define MSM_GSBI1_QUP_PHYS (MSM_GSBI1_PHYS + 0x80000) +#define MSM_GSBI2_QUP_PHYS (MSM_GSBI2_PHYS + 0x80000) +#define MSM_GSBI3_QUP_PHYS (MSM_GSBI3_PHYS + 0x80000) +#define MSM_GSBI4_QUP_PHYS (MSM_GSBI4_PHYS + 0x80000) +#define MSM_GSBI5_QUP_PHYS (MSM_GSBI5_PHYS + 0x80000) +#define MSM_GSBI6_QUP_PHYS (MSM_GSBI6_PHYS + 0x80000) +#define MSM_GSBI7_QUP_PHYS (MSM_GSBI7_PHYS + 0x80000) +#define MSM_GSBI8_QUP_PHYS (MSM_GSBI8_PHYS + 0x80000) +#define MSM_GSBI9_QUP_PHYS (MSM_GSBI9_PHYS + 0x80000) +#define MSM_GSBI10_QUP_PHYS (MSM_GSBI10_PHYS + 0x80000) +#define MSM_GSBI11_QUP_PHYS (MSM_GSBI11_PHYS + 0x20000) +#define MSM_GSBI12_QUP_PHYS (MSM_GSBI12_PHYS + 0x20000) +#define MSM_QUP_SIZE SZ_4K + +#define MSM_PMIC1_SSBI_CMD_PHYS 0x00500000 +#define MSM_PMIC2_SSBI_CMD_PHYS 0x00C00000 +#define MSM_PMIC_SSBI_SIZE SZ_4K + +static struct resource resources_otg[] = { + { + .start = MSM8960_HSUSB_PHYS, + .end = MSM8960_HSUSB_PHYS + MSM8960_HSUSB_SIZE, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_otg), + .resource = resources_otg, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct resource resources_hsusb[] = { + { + .start = MSM8960_HSUSB_PHYS, + .end = MSM8960_HSUSB_PHYS + MSM8960_HSUSB_SIZE, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_gadget_peripheral = { + .name = "msm_hsusb", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb), + .resource = resources_hsusb, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct resource resources_hsusb_host[] = { + { + .start = MSM8960_HSUSB_PHYS, + .end = MSM8960_HSUSB_PHYS + MSM8960_HSUSB_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 dma_mask = 0xffffffffULL; +struct platform_device msm_device_hsusb_host = { + .name = "msm_hsusb_host", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_host), + .resource = resources_hsusb_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct resource resources_uart_gsbi2[] = { + { + .start = MSM8960_GSBI2_UARTDM_IRQ, + .end = MSM8960_GSBI2_UARTDM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = MSM_GSBI2_PHYS, + .end = MSM_GSBI2_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm8960_device_uart_gsbi2 = { + .name = "msm_serial_hsl", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart_gsbi2), + .resource = resources_uart_gsbi2, +}; + +static struct resource resources_uart_gsbi5[] = { + { + .start = GSBI5_UARTDM_IRQ, + .end = GSBI5_UARTDM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART5DM_PHYS, + .end = MSM_UART5DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = MSM_GSBI5_PHYS, + .end = MSM_GSBI5_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm8960_device_uart_gsbi5 = { + .name = "msm_serial_hsl", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart_gsbi5), + .resource = resources_uart_gsbi5, +}; +/* MSM Video core device */ +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors vidc_init_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; +static struct msm_bus_vectors vidc_venc_vga_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 54525952, + .ib = 436207616, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 72351744, + .ib = 289406976, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 500000, + .ib = 1000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 500000, + .ib = 1000000, + }, +}; +static struct msm_bus_vectors vidc_vdec_vga_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 40894464, + .ib = 327155712, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 48234496, + .ib = 192937984, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 500000, + .ib = 2000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 500000, + .ib = 2000000, + }, +}; +static struct msm_bus_vectors vidc_venc_720p_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 163577856, + .ib = 1308622848, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 219152384, + .ib = 876609536, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1750000, + .ib = 3500000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1750000, + .ib = 3500000, + }, +}; +static struct msm_bus_vectors vidc_vdec_720p_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 121634816, + .ib = 973078528, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 155189248, + .ib = 620756992, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1750000, + .ib = 7000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1750000, + .ib = 7000000, + }, +}; +static struct msm_bus_vectors vidc_venc_1080p_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 372244480, + .ib = 1861222400, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 501219328, + .ib = 2004877312, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 2500000, + .ib = 5000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 2500000, + .ib = 5000000, + }, +}; +static struct msm_bus_vectors vidc_vdec_1080p_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 222298112, + .ib = 1778384896, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 330301440, + .ib = 1321205760, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 2500000, + .ib = 700000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 2500000, + .ib = 10000000, + }, +}; + +static struct msm_bus_paths vidc_bus_client_config[] = { + { + ARRAY_SIZE(vidc_init_vectors), + vidc_init_vectors, + }, + { + ARRAY_SIZE(vidc_venc_vga_vectors), + vidc_venc_vga_vectors, + }, + { + ARRAY_SIZE(vidc_vdec_vga_vectors), + vidc_vdec_vga_vectors, + }, + { + ARRAY_SIZE(vidc_venc_720p_vectors), + vidc_venc_720p_vectors, + }, + { + ARRAY_SIZE(vidc_vdec_720p_vectors), + vidc_vdec_720p_vectors, + }, + { + ARRAY_SIZE(vidc_venc_1080p_vectors), + vidc_venc_1080p_vectors, + }, + { + ARRAY_SIZE(vidc_vdec_1080p_vectors), + vidc_vdec_1080p_vectors, + }, +}; + +static struct msm_bus_scale_pdata vidc_bus_client_data = { + vidc_bus_client_config, + ARRAY_SIZE(vidc_bus_client_config), + .name = "vidc", +}; +#endif + +#define MSM_VIDC_BASE_PHYS 0x04400000 +#define MSM_VIDC_BASE_SIZE 0x00100000 + +static struct resource msm_device_vidc_resources[] = { + { + .start = MSM_VIDC_BASE_PHYS, + .end = MSM_VIDC_BASE_PHYS + MSM_VIDC_BASE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = VCODEC_IRQ, + .end = VCODEC_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct msm_vidc_platform_data vidc_platform_data = { +#ifdef CONFIG_MSM_BUS_SCALING + .vidc_bus_client_pdata = &vidc_bus_client_data, +#endif + .memtype = MEMTYPE_EBI1 +}; + +struct platform_device msm_device_vidc = { + .name = "msm_vidc", + .id = 0, + .num_resources = ARRAY_SIZE(msm_device_vidc_resources), + .resource = msm_device_vidc_resources, + .dev = { + .platform_data = &vidc_platform_data, + }, +}; + +#define MSM_WCNSS_PHYS 0x03000000 +#define MSM_WCNSS_SIZE 0x280000 + +static struct resource resources_wcnss_wlan[] = { + { + .start = RIVA_APPS_WLAN_RX_DATA_AVAIL_IRQ, + .end = RIVA_APPS_WLAN_RX_DATA_AVAIL_IRQ, + .name = "wcnss_wlanrx_irq", + .flags = IORESOURCE_IRQ, + }, + { + .start = RIVA_APPS_WLAN_DATA_XFER_DONE_IRQ, + .end = RIVA_APPS_WLAN_DATA_XFER_DONE_IRQ, + .name = "wcnss_wlantx_irq", + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_WCNSS_PHYS, + .end = MSM_WCNSS_PHYS + MSM_WCNSS_SIZE - 1, + .name = "wcnss_mmio", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_wcnss_wlan = { + .name = "wcnss_wlan", + .id = 0, + .num_resources = ARRAY_SIZE(resources_wcnss_wlan), + .resource = resources_wcnss_wlan, +}; + +#define MSM_SDC1_BASE 0x12400000 +#define MSM_SDC1_DML_BASE (MSM_SDC1_BASE + 0x800) +#define MSM_SDC1_BAM_BASE (MSM_SDC1_BASE + 0x2000) +#define MSM_SDC2_BASE 0x12140000 +#define MSM_SDC2_DML_BASE (MSM_SDC2_BASE + 0x800) +#define MSM_SDC2_BAM_BASE (MSM_SDC2_BASE + 0x2000) +#define MSM_SDC2_BASE 0x12140000 +#define MSM_SDC3_BASE 0x12180000 +#define MSM_SDC3_DML_BASE (MSM_SDC3_BASE + 0x800) +#define MSM_SDC3_BAM_BASE (MSM_SDC3_BASE + 0x2000) +#define MSM_SDC4_BASE 0x121C0000 +#define MSM_SDC4_DML_BASE (MSM_SDC4_BASE + 0x800) +#define MSM_SDC4_BAM_BASE (MSM_SDC4_BASE + 0x2000) +#define MSM_SDC5_BASE 0x12200000 +#define MSM_SDC5_DML_BASE (MSM_SDC5_BASE + 0x800) +#define MSM_SDC5_BAM_BASE (MSM_SDC5_BASE + 0x2000) + +static struct resource resources_sdc1[] = { + { + .name = "core_mem", + .flags = IORESOURCE_MEM, + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_DML_BASE - 1, + }, + { + .name = "core_irq", + .flags = IORESOURCE_IRQ, + .start = SDC1_IRQ_0, + .end = SDC1_IRQ_0 + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC1_DML_BASE, + .end = MSM_SDC1_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC1_BAM_BASE, + .end = MSM_SDC1_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC1_BAM_IRQ, + .end = SDC1_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#endif +}; + +static struct resource resources_sdc2[] = { + { + .name = "core_mem", + .flags = IORESOURCE_MEM, + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_DML_BASE - 1, + }, + { + .name = "core_irq", + .flags = IORESOURCE_IRQ, + .start = SDC2_IRQ_0, + .end = SDC2_IRQ_0 + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC2_DML_BASE, + .end = MSM_SDC2_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC2_BAM_BASE, + .end = MSM_SDC2_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC2_BAM_IRQ, + .end = SDC2_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#endif +}; + +static struct resource resources_sdc3[] = { + { + .name = "core_mem", + .flags = IORESOURCE_MEM, + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_DML_BASE - 1, + }, + { + .name = "core_irq", + .flags = IORESOURCE_IRQ, + .start = SDC3_IRQ_0, + .end = SDC3_IRQ_0 + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC3_DML_BASE, + .end = MSM_SDC3_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC3_BAM_BASE, + .end = MSM_SDC3_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC3_BAM_IRQ, + .end = SDC3_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#endif +}; + +static struct resource resources_sdc4[] = { + { + .name = "core_mem", + .flags = IORESOURCE_MEM, + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_DML_BASE - 1, + }, + { + .name = "core_irq", + .flags = IORESOURCE_IRQ, + .start = SDC4_IRQ_0, + .end = SDC4_IRQ_0 + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC4_DML_BASE, + .end = MSM_SDC4_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC4_BAM_BASE, + .end = MSM_SDC4_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC4_BAM_IRQ, + .end = SDC4_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#endif +}; + +static struct resource resources_sdc5[] = { + { + .name = "core_mem", + .flags = IORESOURCE_MEM, + .start = MSM_SDC5_BASE, + .end = MSM_SDC5_DML_BASE - 1, + }, + { + .name = "core_irq", + .flags = IORESOURCE_IRQ, + .start = SDC5_IRQ_0, + .end = SDC5_IRQ_0 + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC5_DML_BASE, + .end = MSM_SDC5_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC5_BAM_BASE, + .end = MSM_SDC5_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC5_BAM_IRQ, + .end = SDC5_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#endif +}; + +struct platform_device msm_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_sdc1), + .resource = resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc2 = { + .name = "msm_sdcc", + .id = 2, + .num_resources = ARRAY_SIZE(resources_sdc2), + .resource = resources_sdc2, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc3 = { + .name = "msm_sdcc", + .id = 3, + .num_resources = ARRAY_SIZE(resources_sdc3), + .resource = resources_sdc3, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc4 = { + .name = "msm_sdcc", + .id = 4, + .num_resources = ARRAY_SIZE(resources_sdc4), + .resource = resources_sdc4, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc5 = { + .name = "msm_sdcc", + .id = 5, + .num_resources = ARRAY_SIZE(resources_sdc5), + .resource = resources_sdc5, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +struct platform_device msm_device_bam_dmux = { + .name = "BAM_RMNT", + .id = -1, +}; + +struct resource msm_dmov_resource[] = { + { + .start = ADM_0_SCSS_1_IRQ, + .end = (resource_size_t)MSM_DMOV_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), +}; + +static struct platform_device *msm_sdcc_devices[] __initdata = { + &msm_device_sdc1, + &msm_device_sdc2, + &msm_device_sdc3, + &msm_device_sdc4, + &msm_device_sdc5, +}; + +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (controller < 1 || controller > 5) + return -EINVAL; + + pdev = msm_sdcc_devices[controller-1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +static struct resource resources_qup_i2c_gsbi4[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI4_PHYS, + .end = MSM_GSBI4_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI4_QUP_PHYS, + .end = MSM_GSBI4_QUP_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI4_QUP_IRQ, + .end = GSBI4_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_qup_i2c_gsbi4 = { + .name = "qup_i2c", + .id = 4, + .num_resources = ARRAY_SIZE(resources_qup_i2c_gsbi4), + .resource = resources_qup_i2c_gsbi4, +}; + +static struct resource resources_qup_i2c_gsbi3[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI3_PHYS, + .end = MSM_GSBI3_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI3_QUP_PHYS, + .end = MSM_GSBI3_QUP_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI3_QUP_IRQ, + .end = GSBI3_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_qup_i2c_gsbi3 = { + .name = "qup_i2c", + .id = 3, + .num_resources = ARRAY_SIZE(resources_qup_i2c_gsbi3), + .resource = resources_qup_i2c_gsbi3, +}; + +static struct resource resources_qup_i2c_gsbi10[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI10_PHYS, + .end = MSM_GSBI10_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI10_QUP_PHYS, + .end = MSM_GSBI10_QUP_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI10_QUP_IRQ, + .end = GSBI10_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_qup_i2c_gsbi10 = { + .name = "qup_i2c", + .id = 10, + .num_resources = ARRAY_SIZE(resources_qup_i2c_gsbi10), + .resource = resources_qup_i2c_gsbi10, +}; + +static struct resource resources_qup_i2c_gsbi12[] = { + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI12_PHYS, + .end = MSM_GSBI12_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_phys_addr", + .start = MSM_GSBI12_QUP_PHYS, + .end = MSM_GSBI12_QUP_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI12_QUP_IRQ, + .end = GSBI12_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_qup_i2c_gsbi12 = { + .name = "qup_i2c", + .id = 12, + .num_resources = ARRAY_SIZE(resources_qup_i2c_gsbi12), + .resource = resources_qup_i2c_gsbi12, +}; + +#ifdef CONFIG_MSM_CAMERA +struct resource msm_camera_resources[] = { + { + .name = "vfe", + .start = 0x04500000, + .end = 0x04500000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "vfe", + .start = VFE_IRQ, + .end = VFE_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "vid_buf", + .flags = IORESOURCE_DMA, + }, + { + .name = "ispif", + .start = 0x04800800, + .end = 0x04800800 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "ispif", + .start = ISPIF_IRQ, + .end = ISPIF_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "csid0", + .start = 0x04800000, + .end = 0x04800000 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csid0", + .start = CSI_0_IRQ, + .end = CSI_0_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "csiphy0", + .start = 0x04800C00, + .end = 0x04800C00 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csiphy0", + .start = CSIPHY_4LN_IRQ, + .end = CSIPHY_4LN_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "csid1", + .start = 0x04800400, + .end = 0x04800400 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csid1", + .start = CSI_1_IRQ, + .end = CSI_1_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "csiphy1", + .start = 0x04801000, + .end = 0x04801000 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "csiphy1", + .start = MSM8960_CSIPHY_2LN_IRQ, + .end = MSM8960_CSIPHY_2LN_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +int __init msm_get_cam_resources(struct msm_camera_sensor_info *s_info) +{ + s_info->resource = msm_camera_resources; + s_info->num_resources = ARRAY_SIZE(msm_camera_resources); + return 0; +} +#endif + +static struct resource resources_ssbi_pm8921[] = { + { + .start = MSM_PMIC1_SSBI_CMD_PHYS, + .end = MSM_PMIC1_SSBI_CMD_PHYS + MSM_PMIC_SSBI_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm8960_device_ssbi_pm8921 = { + .name = "msm_ssbi", + .id = 0, + .resource = resources_ssbi_pm8921, + .num_resources = ARRAY_SIZE(resources_ssbi_pm8921), +}; + +static struct resource resources_qup_spi_gsbi1[] = { + { + .name = "spi_base", + .start = MSM_GSBI1_QUP_PHYS, + .end = MSM_GSBI1_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_base", + .start = MSM_GSBI1_PHYS, + .end = MSM_GSBI1_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "spi_irq_in", + .start = MSM8960_GSBI1_QUP_IRQ, + .end = MSM8960_GSBI1_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_device_qup_spi_gsbi1 = { + .name = "spi_qsd", + .id = 0, + .num_resources = ARRAY_SIZE(resources_qup_spi_gsbi1), + .resource = resources_qup_spi_gsbi1, +}; + +struct platform_device msm_pcm = { + .name = "msm-pcm-dsp", + .id = -1, +}; + +struct platform_device msm_pcm_routing = { + .name = "msm-pcm-routing", + .id = -1, +}; + +struct platform_device msm_cpudai0 = { + .name = "msm-dai-q6", + .id = 0x4000, +}; + +struct platform_device msm_cpudai1 = { + .name = "msm-dai-q6", + .id = 0x4001, +}; + +struct platform_device msm_cpudai_hdmi_rx = { + .name = "msm-dai-q6", + .id = 8, +}; + +struct platform_device msm_cpudai_bt_rx = { + .name = "msm-dai-q6", + .id = 0x3000, +}; + +struct platform_device msm_cpudai_bt_tx = { + .name = "msm-dai-q6", + .id = 0x3001, +}; + +struct platform_device msm_cpudai_fm_rx = { + .name = "msm-dai-q6", + .id = 0x3004, +}; + +struct platform_device msm_cpudai_fm_tx = { + .name = "msm-dai-q6", + .id = 0x3005, +}; + +struct platform_device msm_cpu_fe = { + .name = "msm-dai-fe", + .id = -1, +}; + +struct platform_device msm_stub_codec = { + .name = "msm-stub-codec", + .id = 1, +}; + +struct platform_device msm_voice = { + .name = "msm-pcm-voice", + .id = -1, +}; + +struct platform_device msm_voip = { + .name = "msm-voip-dsp", + .id = -1, +}; + +struct platform_device msm_lpa_pcm = { + .name = "msm-pcm-lpa", + .id = -1, +}; + +struct platform_device msm_pcm_hostless = { + .name = "msm-pcm-hostless", + .id = -1, +}; + +struct platform_device *msm_footswitch_devices[] = { +}; +unsigned msm_num_footswitch_devices = ARRAY_SIZE(msm_footswitch_devices); + +#ifdef CONFIG_MSM_ROTATOR +#define ROTATOR_HW_BASE 0x04E00000 +static struct resource resources_msm_rotator[] = { + { + .start = ROTATOR_HW_BASE, + .end = ROTATOR_HW_BASE + 0x100000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = ROT_IRQ, + .end = ROT_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct msm_rot_clocks rotator_clocks[] = { + { + .clk_name = "rot_clk", + .clk_type = ROTATOR_CORE_CLK, + .clk_rate = 160 * 1000 * 1000, + }, + { + .clk_name = "rotator_pclk", + .clk_type = ROTATOR_PCLK, + .clk_rate = 0, + }, + { + .clk_name = "rot_axi_clk", + .clk_type = ROTATOR_AXI_CLK, + .clk_rate = 0, + }, +}; + +static struct msm_rotator_platform_data rotator_pdata = { + .number_of_clocks = ARRAY_SIZE(rotator_clocks), + .hardware_version_number = 0x01020309, + .rotator_clks = rotator_clocks, + .regulator_name = "fs_rot", +}; + +struct platform_device msm_rotator_device = { + .name = "msm_rotator", + .id = 0, + .num_resources = ARRAY_SIZE(resources_msm_rotator), + .resource = resources_msm_rotator, + .dev = { + .platform_data = &rotator_pdata, + }, +}; +#endif + +#define MIPI_DSI_HW_BASE 0x04700000 +#define MDP_HW_BASE 0x05100000 + +static struct resource msm_mipi_dsi1_resources[] = { + { + .name = "mipi_dsi", + .start = MIPI_DSI_HW_BASE, + .end = MIPI_DSI_HW_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = DSI1_IRQ, + .end = DSI1_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_mipi_dsi1_device = { + .name = "mipi_dsi", + .id = 1, + .num_resources = ARRAY_SIZE(msm_mipi_dsi1_resources), + .resource = msm_mipi_dsi1_resources, +}; + +static struct resource msm_mdp_resources[] = { + { + .name = "mdp", + .start = MDP_HW_BASE, + .end = MDP_HW_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = MDP_IRQ, + .end = MDP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_mdp_device = { + .name = "mdp", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mdp_resources), + .resource = msm_mdp_resources, +}; + +static void __init msm_register_device(struct platform_device *pdev, void *data) +{ + int ret; + + pdev->dev.platform_data = data; + ret = platform_device_register(pdev); + if (ret) + dev_err(&pdev->dev, + "%s: platform_device_register() failed = %d\n", + __func__, ret); +} + +void __init msm_fb_register_device(char *name, void *data) +{ + if (!strncmp(name, "mdp", 3)) + msm_register_device(&msm_mdp_device, data); + else if (!strncmp(name, "mipi_dsi", 8)) + msm_register_device(&msm_mipi_dsi1_device, data); + else + printk(KERN_ERR "%s: unknown device! %s\n", __func__, name); +} + +static struct resource resources_sps[] = { + { + .name = "pipe_mem", + .start = 0x12800000, + .end = 0x12800000 + 0x4000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "bamdma_dma", + .start = 0x12240000, + .end = 0x12240000 + 0x1000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "bamdma_bam", + .start = 0x12244000, + .end = 0x12244000 + 0x4000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "bamdma_irq", + .start = SPS_BAM_DMA_IRQ, + .end = SPS_BAM_DMA_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct msm_sps_platform_data msm_sps_pdata = { + .bamdma_restricted_pipes = 0x06, +}; + +struct platform_device msm_device_sps = { + .name = "msm_sps", + .id = -1, + .num_resources = ARRAY_SIZE(resources_sps), + .resource = resources_sps, + .dev.platform_data = &msm_sps_pdata, +}; + +#ifdef CONFIG_MSM_MPM +static uint16_t msm_mpm_irqs_m2a[MSM_MPM_NR_MPM_IRQS] = { + [1] = MSM_GPIO_TO_INT(61), + [4] = MSM_GPIO_TO_INT(87), + [5] = MSM_GPIO_TO_INT(88), + [6] = MSM_GPIO_TO_INT(89), + [7] = MSM_GPIO_TO_INT(90), + [8] = MSM_GPIO_TO_INT(91), + [9] = MSM_GPIO_TO_INT(34), + [10] = MSM_GPIO_TO_INT(38), + [11] = MSM_GPIO_TO_INT(42), + [12] = MSM_GPIO_TO_INT(46), + [13] = MSM_GPIO_TO_INT(50), + [14] = MSM_GPIO_TO_INT(54), + [15] = MSM_GPIO_TO_INT(58), + [16] = MSM_GPIO_TO_INT(63), + [17] = MSM_GPIO_TO_INT(160), + [18] = MSM_GPIO_TO_INT(162), + [19] = MSM_GPIO_TO_INT(144), + [20] = MSM_GPIO_TO_INT(146), + [25] = USB1_HS_IRQ, + [26] = TV_ENC_IRQ, + [27] = HDMI_IRQ, + [29] = MSM_GPIO_TO_INT(123), + [30] = MSM_GPIO_TO_INT(172), + [31] = MSM_GPIO_TO_INT(99), + [32] = MSM_GPIO_TO_INT(96), + [33] = MSM_GPIO_TO_INT(67), + [34] = MSM_GPIO_TO_INT(71), + [35] = MSM_GPIO_TO_INT(105), + [36] = MSM_GPIO_TO_INT(117), + [37] = MSM_GPIO_TO_INT(29), + [38] = MSM_GPIO_TO_INT(30), + [39] = MSM_GPIO_TO_INT(31), + [40] = MSM_GPIO_TO_INT(37), + [41] = MSM_GPIO_TO_INT(40), + [42] = MSM_GPIO_TO_INT(41), + [43] = MSM_GPIO_TO_INT(45), + [44] = MSM_GPIO_TO_INT(51), + [45] = MSM_GPIO_TO_INT(52), + [46] = MSM_GPIO_TO_INT(57), + [47] = MSM_GPIO_TO_INT(73), + [48] = MSM_GPIO_TO_INT(93), + [49] = MSM_GPIO_TO_INT(94), + [50] = MSM_GPIO_TO_INT(103), + [51] = MSM_GPIO_TO_INT(104), + [52] = MSM_GPIO_TO_INT(106), + [53] = MSM_GPIO_TO_INT(115), + [54] = MSM_GPIO_TO_INT(124), + [55] = MSM_GPIO_TO_INT(125), + [56] = MSM_GPIO_TO_INT(126), + [57] = MSM_GPIO_TO_INT(127), + [58] = MSM_GPIO_TO_INT(128), + [59] = MSM_GPIO_TO_INT(129), +}; + +static uint16_t msm_mpm_bypassed_apps_irqs[] = { + TLMM_MSM_SUMMARY_IRQ, + RPM_APCC_CPU0_GP_HIGH_IRQ, + RPM_APCC_CPU0_GP_MEDIUM_IRQ, + RPM_APCC_CPU0_GP_LOW_IRQ, + RPM_APCC_CPU0_WAKE_UP_IRQ, + RPM_APCC_CPU1_GP_HIGH_IRQ, + RPM_APCC_CPU1_GP_MEDIUM_IRQ, + RPM_APCC_CPU1_GP_LOW_IRQ, + RPM_APCC_CPU1_WAKE_UP_IRQ, + MSS_TO_APPS_IRQ_0, + MSS_TO_APPS_IRQ_1, + MSS_TO_APPS_IRQ_2, + MSS_TO_APPS_IRQ_3, + MSS_TO_APPS_IRQ_4, + MSS_TO_APPS_IRQ_5, + MSS_TO_APPS_IRQ_6, + MSS_TO_APPS_IRQ_7, + MSS_TO_APPS_IRQ_8, + MSS_TO_APPS_IRQ_9, + LPASS_SCSS_GP_LOW_IRQ, + LPASS_SCSS_GP_MEDIUM_IRQ, + LPASS_SCSS_GP_HIGH_IRQ, + SPS_MTI_31, +}; + +struct msm_mpm_device_data msm_mpm_dev_data = { + .irqs_m2a = msm_mpm_irqs_m2a, + .irqs_m2a_size = ARRAY_SIZE(msm_mpm_irqs_m2a), + .bypassed_apps_irqs = msm_mpm_bypassed_apps_irqs, + .bypassed_apps_irqs_size = ARRAY_SIZE(msm_mpm_bypassed_apps_irqs), + .mpm_request_reg_base = MSM_RPM_BASE + 0x9d8, + .mpm_status_reg_base = MSM_RPM_BASE + 0xdf8, + .mpm_apps_ipc_reg = MSM_APCS_GCC_BASE + 0x008, + .mpm_apps_ipc_val = BIT(1), + .mpm_ipc_irq = RPM_APCC_CPU0_GP_MEDIUM_IRQ, + +}; +#endif + +struct clk_lookup msm_clocks_8960_dummy[] = { + CLK_DUMMY("pll2", PLL2, NULL, 0), + CLK_DUMMY("pll8", PLL8, NULL, 0), + CLK_DUMMY("pll4", PLL4, NULL, 0), + + CLK_DUMMY("afab_clk", AFAB_CLK, NULL, 0), + CLK_DUMMY("afab_a_clk", AFAB_A_CLK, NULL, 0), + CLK_DUMMY("cfpb_clk", CFPB_CLK, NULL, 0), + CLK_DUMMY("cfpb_a_clk", CFPB_A_CLK, NULL, 0), + CLK_DUMMY("dfab_clk", DFAB_CLK, NULL, 0), + CLK_DUMMY("dfab_a_clk", DFAB_A_CLK, NULL, 0), + CLK_DUMMY("ebi1_clk", EBI1_CLK, NULL, 0), + CLK_DUMMY("ebi1_a_clk", EBI1_A_CLK, NULL, 0), + CLK_DUMMY("mmfab_clk", MMFAB_CLK, NULL, 0), + CLK_DUMMY("mmfab_a_clk", MMFAB_A_CLK, NULL, 0), + CLK_DUMMY("mmfpb_clk", MMFPB_CLK, NULL, 0), + CLK_DUMMY("mmfpb_a_clk", MMFPB_A_CLK, NULL, 0), + CLK_DUMMY("sfab_clk", SFAB_CLK, NULL, 0), + CLK_DUMMY("sfab_a_clk", SFAB_A_CLK, NULL, 0), + CLK_DUMMY("sfpb_clk", SFPB_CLK, NULL, 0), + CLK_DUMMY("sfpb_a_clk", SFPB_A_CLK, NULL, 0), + + CLK_DUMMY("gsbi_uart_clk", GSBI1_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI2_UART_CLK, + "msm_serial_hsl.0", OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI3_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI4_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI5_UART_CLK, NULL, OFF), + CLK_DUMMY("uartdm_clk", GSBI6_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI7_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI8_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI9_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI10_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI11_UART_CLK, NULL, OFF), + CLK_DUMMY("gsbi_uart_clk", GSBI12_UART_CLK, NULL, OFF), + CLK_DUMMY("spi_clk", GSBI1_QUP_CLK, "spi_qsd.0", OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI2_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI3_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI4_QUP_CLK, + "qup_i2c.4", OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI5_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI6_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI7_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI8_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI9_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI10_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI11_QUP_CLK, NULL, OFF), + CLK_DUMMY("gsbi_qup_clk", GSBI12_QUP_CLK, NULL, OFF), + CLK_DUMMY("pdm_clk", PDM_CLK, NULL, OFF), + CLK_DUMMY("pmem_clk", PMEM_CLK, NULL, OFF), + CLK_DUMMY("prng_clk", PRNG_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC1_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC2_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC3_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC4_CLK, NULL, OFF), + CLK_DUMMY("sdc_clk", SDC5_CLK, NULL, OFF), + CLK_DUMMY("tsif_ref_clk", TSIF_REF_CLK, NULL, OFF), + CLK_DUMMY("tssc_clk", TSSC_CLK, NULL, OFF), + CLK_DUMMY("usb_hs_clk", USB_HS1_XCVR_CLK, NULL, OFF), + CLK_DUMMY("usb_phy_clk", USB_PHY0_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_src_clk", USB_FS1_SRC_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_clk", USB_FS1_XCVR_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_sys_clk", USB_FS1_SYS_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_src_clk", USB_FS2_SRC_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_clk", USB_FS2_XCVR_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_sys_clk", USB_FS2_SYS_CLK, NULL, OFF), + CLK_DUMMY("ce_pclk", CE2_CLK, NULL, OFF), + CLK_DUMMY("ce_clk", CE1_CORE_CLK, NULL, OFF), + CLK_DUMMY("spi_pclk", GSBI1_P_CLK, "spi_qsd.0", OFF), + CLK_DUMMY("gsbi_pclk", GSBI2_P_CLK, + "msm_serial_hsl.0", OFF), + CLK_DUMMY("gsbi_pclk", GSBI3_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI4_P_CLK, + "qup_i2c.4", OFF), + CLK_DUMMY("gsbi_pclk", GSBI5_P_CLK, NULL, OFF), + CLK_DUMMY("uartdm_pclk", GSBI6_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI7_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI8_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI9_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI10_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI11_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI12_P_CLK, NULL, OFF), + CLK_DUMMY("gsbi_pclk", GSBI12_P_CLK, NULL, OFF), + CLK_DUMMY("tsif_pclk", TSIF_P_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_pclk", USB_FS1_P_CLK, NULL, OFF), + CLK_DUMMY("usb_fs_pclk", USB_FS2_P_CLK, NULL, OFF), + CLK_DUMMY("usb_hs_pclk", USB_HS1_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC1_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC2_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC3_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC4_P_CLK, NULL, OFF), + CLK_DUMMY("sdc_pclk", SDC5_P_CLK, NULL, OFF), + CLK_DUMMY("adm_clk", ADM0_CLK, NULL, OFF), + CLK_DUMMY("adm_pclk", ADM0_P_CLK, NULL, OFF), + CLK_DUMMY("pmic_arb_pclk", PMIC_ARB0_P_CLK, NULL, OFF), + CLK_DUMMY("pmic_arb_pclk", PMIC_ARB1_P_CLK, NULL, OFF), + CLK_DUMMY("pmic_ssbi2", PMIC_SSBI2_CLK, NULL, OFF), + CLK_DUMMY("rpm_msg_ram_pclk", RPM_MSG_RAM_P_CLK, NULL, OFF), + CLK_DUMMY("amp_clk", AMP_CLK, NULL, OFF), + CLK_DUMMY("cam_clk", CAM0_CLK, NULL, OFF), + CLK_DUMMY("cam_clk", CAM1_CLK, NULL, OFF), + CLK_DUMMY("csi_src_clk", CSI0_SRC_CLK, NULL, OFF), + CLK_DUMMY("csi_src_clk", CSI1_SRC_CLK, NULL, OFF), + CLK_DUMMY("csi_clk", CSI0_CLK, NULL, OFF), + CLK_DUMMY("csi_clk", CSI1_CLK, NULL, OFF), + CLK_DUMMY("csi_pix_clk", CSI_PIX_CLK, NULL, OFF), + CLK_DUMMY("csi_rdi_clk", CSI_RDI_CLK, NULL, OFF), + CLK_DUMMY("csiphy_timer_src_clk", CSIPHY_TIMER_SRC_CLK, NULL, OFF), + CLK_DUMMY("csi0phy_timer_clk", CSIPHY0_TIMER_CLK, NULL, OFF), + CLK_DUMMY("csi1phy_timer_clk", CSIPHY1_TIMER_CLK, NULL, OFF), + CLK_DUMMY("dsi_byte_div_clk", DSI1_BYTE_CLK, "mipi_dsi.1", OFF), + CLK_DUMMY("dsi_byte_div_clk", DSI2_BYTE_CLK, "mipi_dsi.2", OFF), + CLK_DUMMY("dsi_esc_clk", DSI1_ESC_CLK, "mipi_dsi.1", OFF), + CLK_DUMMY("dsi_esc_clk", DSI2_ESC_CLK, "mipi_dsi.2", OFF), + CLK_DUMMY("gfx2d0_clk", GFX2D0_CLK, NULL, OFF), + CLK_DUMMY("gfx2d1_clk", GFX2D1_CLK, NULL, OFF), + CLK_DUMMY("gfx3d_clk", GFX3D_CLK, NULL, OFF), + CLK_DUMMY("ijpeg_clk", IJPEG_CLK, NULL, OFF), + CLK_DUMMY("imem_clk", IMEM_CLK, NULL, OFF), + CLK_DUMMY("jpegd_clk", JPEGD_CLK, NULL, OFF), + CLK_DUMMY("mdp_clk", MDP_CLK, NULL, OFF), + CLK_DUMMY("mdp_vsync_clk", MDP_VSYNC_CLK, NULL, OFF), + CLK_DUMMY("lut_mdp", LUT_MDP_CLK, NULL, OFF), + CLK_DUMMY("rot_clk", ROT_CLK, NULL, OFF), + CLK_DUMMY("tv_src_clk", TV_SRC_CLK, NULL, OFF), + CLK_DUMMY("tv_enc_clk", TV_ENC_CLK, NULL, OFF), + CLK_DUMMY("tv_dac_clk", TV_DAC_CLK, NULL, OFF), + CLK_DUMMY("vcodec_clk", VCODEC_CLK, NULL, OFF), + CLK_DUMMY("mdp_tv_clk", MDP_TV_CLK, NULL, OFF), + CLK_DUMMY("hdmi_clk", HDMI_TV_CLK, NULL, OFF), + CLK_DUMMY("hdmi_app_clk", HDMI_APP_CLK, NULL, OFF), + CLK_DUMMY("vpe_clk", VPE_CLK, NULL, OFF), + CLK_DUMMY("vfe_clk", VFE_CLK, NULL, OFF), + CLK_DUMMY("csi_vfe_clk", CSI0_VFE_CLK, NULL, OFF), + CLK_DUMMY("vfe_axi_clk", VFE_AXI_CLK, NULL, OFF), + CLK_DUMMY("ijpeg_axi_clk", IJPEG_AXI_CLK, NULL, OFF), + CLK_DUMMY("mdp_axi_clk", MDP_AXI_CLK, NULL, OFF), + CLK_DUMMY("rot_axi_clk", ROT_AXI_CLK, NULL, OFF), + CLK_DUMMY("vcodec_axi_clk", VCODEC_AXI_CLK, NULL, OFF), + CLK_DUMMY("vcodec_axi_a_clk", VCODEC_AXI_A_CLK, NULL, OFF), + CLK_DUMMY("vcodec_axi_b_clk", VCODEC_AXI_B_CLK, NULL, OFF), + CLK_DUMMY("vpe_axi_clk", VPE_AXI_CLK, NULL, OFF), + CLK_DUMMY("amp_pclk", AMP_P_CLK, NULL, OFF), + CLK_DUMMY("csi_pclk", CSI0_P_CLK, NULL, OFF), + CLK_DUMMY("dsi_m_pclk", DSI1_M_P_CLK, "mipi_dsi.1", OFF), + CLK_DUMMY("dsi_s_pclk", DSI1_S_P_CLK, "mipi_dsi.1", OFF), + CLK_DUMMY("dsi_m_pclk", DSI2_M_P_CLK, "mipi_dsi.2", OFF), + CLK_DUMMY("dsi_s_pclk", DSI2_S_P_CLK, "mipi_dsi.2", OFF), + CLK_DUMMY("gfx2d0_pclk", GFX2D0_P_CLK, NULL, OFF), + CLK_DUMMY("gfx2d1_pclk", GFX2D1_P_CLK, NULL, OFF), + CLK_DUMMY("gfx3d_pclk", GFX3D_P_CLK, NULL, OFF), + CLK_DUMMY("hdmi_m_pclk", HDMI_M_P_CLK, NULL, OFF), + CLK_DUMMY("hdmi_s_pclk", HDMI_S_P_CLK, NULL, OFF), + CLK_DUMMY("ijpeg_pclk", IJPEG_P_CLK, NULL, OFF), + CLK_DUMMY("jpegd_pclk", JPEGD_P_CLK, NULL, OFF), + CLK_DUMMY("imem_pclk", IMEM_P_CLK, NULL, OFF), + CLK_DUMMY("mdp_pclk", MDP_P_CLK, NULL, OFF), + CLK_DUMMY("smmu_pclk", SMMU_P_CLK, NULL, OFF), + CLK_DUMMY("rotator_pclk", ROT_P_CLK, NULL, OFF), + CLK_DUMMY("tv_enc_pclk", TV_ENC_P_CLK, NULL, OFF), + CLK_DUMMY("vcodec_pclk", VCODEC_P_CLK, NULL, OFF), + CLK_DUMMY("vfe_pclk", VFE_P_CLK, NULL, OFF), + CLK_DUMMY("vpe_pclk", VPE_P_CLK, NULL, OFF), + CLK_DUMMY("mi2s_osr_clk", MI2S_OSR_CLK, NULL, OFF), + CLK_DUMMY("mi2s_bit_clk", MI2S_BIT_CLK, NULL, OFF), + CLK_DUMMY("i2s_mic_osr_clk", CODEC_I2S_MIC_OSR_CLK, NULL, OFF), + CLK_DUMMY("i2s_mic_bit_clk", CODEC_I2S_MIC_BIT_CLK, NULL, OFF), + CLK_DUMMY("i2s_mic_osr_clk", SPARE_I2S_MIC_OSR_CLK, NULL, OFF), + CLK_DUMMY("i2s_mic_bit_clk", SPARE_I2S_MIC_BIT_CLK, NULL, OFF), + CLK_DUMMY("i2s_spkr_osr_clk", CODEC_I2S_SPKR_OSR_CLK, NULL, OFF), + CLK_DUMMY("i2s_spkr_bit_clk", CODEC_I2S_SPKR_BIT_CLK, NULL, OFF), + CLK_DUMMY("i2s_spkr_osr_clk", SPARE_I2S_SPKR_OSR_CLK, NULL, OFF), + CLK_DUMMY("i2s_spkr_bit_clk", SPARE_I2S_SPKR_BIT_CLK, NULL, OFF), + CLK_DUMMY("pcm_clk", PCM_CLK, NULL, OFF), + CLK_DUMMY("iommu_clk", JPEGD_AXI_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", VFE_AXI_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", VCODEC_AXI_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", GFX3D_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", GFX2D0_CLK, NULL, 0), + CLK_DUMMY("iommu_clk", GFX2D1_CLK, NULL, 0), + + CLK_DUMMY("dfab_dsps_clk", DFAB_DSPS_CLK, NULL, 0), + CLK_DUMMY("dfab_usb_hs_clk", DFAB_USB_HS_CLK, NULL, 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC1_CLK, "msm_sdcc.1", 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC2_CLK, "msm_sdcc.2", 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC3_CLK, "msm_sdcc.3", 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC4_CLK, "msm_sdcc.4", 0), + CLK_DUMMY("dfab_sdc_clk", DFAB_SDC5_CLK, "msm_sdcc.5", 0), + CLK_DUMMY("dfab_clk", DFAB_CLK, NULL, 0), + CLK_DUMMY("dma_bam_pclk", DMA_BAM_P_CLK, NULL, 0), +}; + +unsigned msm_num_clocks_8960_dummy = ARRAY_SIZE(msm_clocks_8960_dummy); + +#define LPASS_SLIMBUS_PHYS 0x28080000 +#define LPASS_SLIMBUS_BAM_PHYS 0x28084000 +/* Board info for the slimbus slave device */ +static struct resource slimbus_res[] = { + { + .start = LPASS_SLIMBUS_PHYS, + .end = LPASS_SLIMBUS_PHYS + 8191, + .flags = IORESOURCE_MEM, + .name = "slimbus_physical", + }, + { + .start = LPASS_SLIMBUS_BAM_PHYS, + .end = LPASS_SLIMBUS_BAM_PHYS + 8191, + .flags = IORESOURCE_MEM, + .name = "slimbus_bam_physical", + }, + { + .start = SLIMBUS0_CORE_EE1_IRQ, + .end = SLIMBUS0_CORE_EE1_IRQ, + .flags = IORESOURCE_IRQ, + .name = "slimbus_irq", + }, + { + .start = SLIMBUS0_BAM_EE1_IRQ, + .end = SLIMBUS0_BAM_EE1_IRQ, + .flags = IORESOURCE_IRQ, + .name = "slimbus_bam_irq", + }, +}; + +struct platform_device msm_slim_ctrl = { + .name = "msm_slim_ctrl", + .id = 1, + .num_resources = ARRAY_SIZE(slimbus_res), + .resource = slimbus_res, + .dev = { + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors grp3d_init_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors grp3d_nominal_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 200800000U, + }, +}; + +static struct msm_bus_vectors grp3d_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 2096000000U, + }, +}; + +static struct msm_bus_paths grp3d_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp3d_init_vectors), + grp3d_init_vectors, + }, + { + ARRAY_SIZE(grp3d_nominal_vectors), + grp3d_nominal_vectors, + }, + { + ARRAY_SIZE(grp3d_max_vectors), + grp3d_max_vectors, + }, +}; + +static struct msm_bus_scale_pdata grp3d_bus_scale_pdata = { + grp3d_bus_scale_usecases, + ARRAY_SIZE(grp3d_bus_scale_usecases), + .name = "grp3d", +}; + +static struct msm_bus_vectors grp2d0_init_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors grp2d0_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 248000000, + }, +}; + +static struct msm_bus_paths grp2d0_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp2d0_init_vectors), + grp2d0_init_vectors, + }, + { + ARRAY_SIZE(grp2d0_max_vectors), + grp2d0_max_vectors, + }, +}; + +struct msm_bus_scale_pdata grp2d0_bus_scale_pdata = { + grp2d0_bus_scale_usecases, + ARRAY_SIZE(grp2d0_bus_scale_usecases), + .name = "grp2d0", +}; + +static struct msm_bus_vectors grp2d1_init_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors grp2d1_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 248000000, + }, +}; + +static struct msm_bus_paths grp2d1_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp2d1_init_vectors), + grp2d1_init_vectors, + }, + { + ARRAY_SIZE(grp2d1_max_vectors), + grp2d1_max_vectors, + }, +}; + +struct msm_bus_scale_pdata grp2d1_bus_scale_pdata = { + grp2d1_bus_scale_usecases, + ARRAY_SIZE(grp2d1_bus_scale_usecases), + .name = "grp2d1", +}; +#endif + +static struct resource kgsl_3d0_resources[] = { + { + .name = KGSL_3D0_REG_MEMORY, + .start = 0x04300000, /* GFX3D address */ + .end = 0x0431ffff, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_3D0_IRQ, + .start = GFX3D_IRQ, + .end = GFX3D_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_3d0_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 266667000, + .bus_freq = 2, + }, + { + .gpu_freq = 228571000, + .bus_freq = 1, + }, + { + .gpu_freq = 266667000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 3, + .set_grp_async = NULL, + .idle_timeout = HZ/5, +#ifdef CONFIG_MSM_BUS_SCALING + .nap_allowed = true, +#endif + }, + .clk = { + .name = { + .clk = "gfx3d_clk", + .pclk = "gfx3d_pclk", + }, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp3d_bus_scale_pdata, +#endif + }, + .imem_clk_name = { + .clk = NULL, + .pclk = "imem_pclk", + }, +}; + +struct platform_device msm_kgsl_3d0 = { + .name = "kgsl-3d0", + .id = 0, + .num_resources = ARRAY_SIZE(kgsl_3d0_resources), + .resource = kgsl_3d0_resources, + .dev = { + .platform_data = &kgsl_3d0_pdata, + }, +}; + +static struct resource kgsl_2d0_resources[] = { + { + .name = KGSL_2D0_REG_MEMORY, + .start = 0x04100000, /* Z180 base address */ + .end = 0x04100FFF, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_2D0_IRQ, + .start = GFX2D0_IRQ, + .end = GFX2D0_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_2d0_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 200000000, + .bus_freq = 1, + }, + { + .gpu_freq = 200000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 2, + .set_grp_async = NULL, + .idle_timeout = HZ/10, +#ifdef CONFIG_MSM_BUS_SCALING + .nap_allowed = true, +#endif + }, + .clk = { + .name = { + /* note: 2d clocks disabled on v1 */ + .clk = "gfx2d0_clk", + .pclk = "gfx2d0_pclk", + }, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp2d0_bus_scale_pdata, +#endif + }, +}; + +struct platform_device msm_kgsl_2d0 = { + .name = "kgsl-2d0", + .id = 0, + .num_resources = ARRAY_SIZE(kgsl_2d0_resources), + .resource = kgsl_2d0_resources, + .dev = { + .platform_data = &kgsl_2d0_pdata, + }, +}; + +static struct resource kgsl_2d1_resources[] = { + { + .name = KGSL_2D1_REG_MEMORY, + .start = 0x04200000, /* Z180 device 1 base address */ + .end = 0x04200FFF, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_2D1_IRQ, + .start = GFX2D1_IRQ, + .end = GFX2D1_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_2d1_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 200000000, + .bus_freq = 1, + }, + { + .gpu_freq = 200000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 2, + .set_grp_async = NULL, + .idle_timeout = HZ/10, +#ifdef CONFIG_MSM_BUS_SCALING + .nap_allowed = true, +#endif + }, + .clk = { + .name = { + .clk = "gfx2d1_clk", + .pclk = "gfx2d1_pclk", + }, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp2d1_bus_scale_pdata, +#endif + }, +}; + +struct platform_device msm_kgsl_2d1 = { + .name = "kgsl-2d1", + .id = 1, + .num_resources = ARRAY_SIZE(kgsl_2d1_resources), + .resource = kgsl_2d1_resources, + .dev = { + .platform_data = &kgsl_2d1_pdata, + }, +}; + +#ifdef CONFIG_MSM_GEMINI +static struct resource msm_gemini_resources[] = { + { + .start = 0x04600000, + .end = 0x04600000 + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = JPEG_IRQ, + .end = JPEG_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm8960_gemini_device = { + .name = "msm_gemini", + .resource = msm_gemini_resources, + .num_resources = ARRAY_SIZE(msm_gemini_resources), +}; +#endif + +struct msm_rpm_map_data rpm_map_data[] __initdata = { + MSM_RPM_MAP(TRIGGER_TIMED_TO, TRIGGER_TIMED, 1), + MSM_RPM_MAP(TRIGGER_TIMED_SCLK_COUNT, TRIGGER_TIMED, 1), + + MSM_RPM_MAP(RPM_CTL, RPM_CTL, 1), + + MSM_RPM_MAP(CXO_CLK, CXO_CLK, 1), + MSM_RPM_MAP(PXO_CLK, PXO_CLK, 1), + MSM_RPM_MAP(APPS_FABRIC_CLK, APPS_FABRIC_CLK, 1), + MSM_RPM_MAP(SYSTEM_FABRIC_CLK, SYSTEM_FABRIC_CLK, 1), + MSM_RPM_MAP(MM_FABRIC_CLK, MM_FABRIC_CLK, 1), + MSM_RPM_MAP(DAYTONA_FABRIC_CLK, DAYTONA_FABRIC_CLK, 1), + MSM_RPM_MAP(SFPB_CLK, SFPB_CLK, 1), + MSM_RPM_MAP(CFPB_CLK, CFPB_CLK, 1), + MSM_RPM_MAP(MMFPB_CLK, MMFPB_CLK, 1), + MSM_RPM_MAP(EBI1_CLK, EBI1_CLK, 1), + + MSM_RPM_MAP(APPS_FABRIC_CFG_HALT_0, APPS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(APPS_FABRIC_CFG_CLKMOD_0, APPS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(APPS_FABRIC_CFG_IOCTL, APPS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(APPS_FABRIC_ARB_0, APPS_FABRIC_ARB, 12), + + MSM_RPM_MAP(SYS_FABRIC_CFG_HALT_0, SYS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(SYS_FABRIC_CFG_CLKMOD_0, SYS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(SYS_FABRIC_CFG_IOCTL, SYS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(SYSTEM_FABRIC_ARB_0, SYSTEM_FABRIC_ARB, 27), + + MSM_RPM_MAP(MMSS_FABRIC_CFG_HALT_0, MMSS_FABRIC_CFG_HALT, 2), + MSM_RPM_MAP(MMSS_FABRIC_CFG_CLKMOD_0, MMSS_FABRIC_CFG_CLKMOD, 3), + MSM_RPM_MAP(MMSS_FABRIC_CFG_IOCTL, MMSS_FABRIC_CFG_IOCTL, 1), + MSM_RPM_MAP(MM_FABRIC_ARB_0, MM_FABRIC_ARB, 23), + + MSM_RPM_MAP(PM8921_S1_0, PM8921_S1, 2), + MSM_RPM_MAP(PM8921_S2_0, PM8921_S2, 2), + MSM_RPM_MAP(PM8921_S3_0, PM8921_S3, 2), + MSM_RPM_MAP(PM8921_S4_0, PM8921_S4, 2), + MSM_RPM_MAP(PM8921_S5_0, PM8921_S5, 2), + MSM_RPM_MAP(PM8921_S6_0, PM8921_S6, 2), + MSM_RPM_MAP(PM8921_S7_0, PM8921_S7, 2), + MSM_RPM_MAP(PM8921_S8_0, PM8921_S8, 2), + MSM_RPM_MAP(PM8921_L1_0, PM8921_L1, 2), + MSM_RPM_MAP(PM8921_L2_0, PM8921_L2, 2), + MSM_RPM_MAP(PM8921_L3_0, PM8921_L3, 2), + MSM_RPM_MAP(PM8921_L4_0, PM8921_L4, 2), + MSM_RPM_MAP(PM8921_L5_0, PM8921_L5, 2), + MSM_RPM_MAP(PM8921_L6_0, PM8921_L6, 2), + MSM_RPM_MAP(PM8921_L7_0, PM8921_L7, 2), + MSM_RPM_MAP(PM8921_L8_0, PM8921_L8, 2), + MSM_RPM_MAP(PM8921_L9_0, PM8921_L9, 2), + MSM_RPM_MAP(PM8921_L10_0, PM8921_L10, 2), + MSM_RPM_MAP(PM8921_L11_0, PM8921_L11, 2), + MSM_RPM_MAP(PM8921_L12_0, PM8921_L12, 2), + MSM_RPM_MAP(PM8921_L13_0, PM8921_L13, 2), + MSM_RPM_MAP(PM8921_L14_0, PM8921_L14, 2), + MSM_RPM_MAP(PM8921_L15_0, PM8921_L15, 2), + MSM_RPM_MAP(PM8921_L16_0, PM8921_L16, 2), + MSM_RPM_MAP(PM8921_L17_0, PM8921_L17, 2), + MSM_RPM_MAP(PM8921_L18_0, PM8921_L18, 2), + MSM_RPM_MAP(PM8921_L19_0, PM8921_L19, 2), + MSM_RPM_MAP(PM8921_L20_0, PM8921_L20, 2), + MSM_RPM_MAP(PM8921_L21_0, PM8921_L21, 2), + MSM_RPM_MAP(PM8921_L22_0, PM8921_L22, 2), + MSM_RPM_MAP(PM8921_L23_0, PM8921_L23, 2), + MSM_RPM_MAP(PM8921_L24_0, PM8921_L24, 2), + MSM_RPM_MAP(PM8921_L25_0, PM8921_L25, 2), + MSM_RPM_MAP(PM8921_L26_0, PM8921_L26, 2), + MSM_RPM_MAP(PM8921_L27_0, PM8921_L27, 2), + MSM_RPM_MAP(PM8921_L28_0, PM8921_L28, 2), + MSM_RPM_MAP(PM8921_L29_0, PM8921_L29, 2), + MSM_RPM_MAP(PM8921_CLK1_0, PM8921_CLK1, 2), + MSM_RPM_MAP(PM8921_CLK2_0, PM8921_CLK2, 2), + MSM_RPM_MAP(PM8921_LVS1, PM8921_LVS1, 1), + MSM_RPM_MAP(PM8921_LVS2, PM8921_LVS2, 1), + MSM_RPM_MAP(PM8921_LVS3, PM8921_LVS3, 1), + MSM_RPM_MAP(PM8921_LVS4, PM8921_LVS4, 1), + MSM_RPM_MAP(PM8921_LVS5, PM8921_LVS5, 1), + MSM_RPM_MAP(PM8921_LVS6, PM8921_LVS6, 1), + MSM_RPM_MAP(PM8921_LVS7, PM8921_LVS7, 1), + MSM_RPM_MAP(NCP_0, NCP, 2), + MSM_RPM_MAP(CXO_BUFFERS, CXO_BUFFERS, 1), + MSM_RPM_MAP(USB_OTG_SWITCH, USB_OTG_SWITCH, 1), + MSM_RPM_MAP(HDMI_SWITCH, HDMI_SWITCH, 1), + +}; +unsigned int rpm_map_data_size = ARRAY_SIZE(rpm_map_data); + +struct platform_device msm_bus_sys_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_SYSTEM, +}; +struct platform_device msm_bus_apps_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_APPSS, +}; +struct platform_device msm_bus_mm_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_MMSS, +}; +struct platform_device msm_bus_sys_fpb = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_SYSTEM_FPB, +}; +struct platform_device msm_bus_cpss_fpb = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_CPSS_FPB, +}; + +/* Sensors DSPS platform data */ +#ifdef CONFIG_MSM_DSPS + +#define PPSS_REG_PHYS_BASE 0x12080000 + +static struct dsps_clk_info dsps_clks[] = {}; +static struct dsps_regulator_info dsps_regs[] = {}; + +/* + * Note: GPIOs field is intialized in run-time at the function + * msm8960_init_dsps(). + */ + +struct msm_dsps_platform_data msm_dsps_pdata = { + .clks = dsps_clks, + .clks_num = ARRAY_SIZE(dsps_clks), + .gpios = NULL, + .gpios_num = 0, + .regs = dsps_regs, + .regs_num = ARRAY_SIZE(dsps_regs), + .dsps_pwr_ctl_en = 1, + .signature = DSPS_SIGNATURE, +}; + +static struct resource msm_dsps_resources[] = { + { + .start = PPSS_REG_PHYS_BASE, + .end = PPSS_REG_PHYS_BASE + SZ_8K - 1, + .name = "ppss_reg", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_dsps_device = { + .name = "msm_dsps", + .id = 0, + .num_resources = ARRAY_SIZE(msm_dsps_resources), + .resource = msm_dsps_resources, + .dev.platform_data = &msm_dsps_pdata, +}; + +#endif /* CONFIG_MSM_DSPS */ diff --git a/arch/arm/mach-msm/devices-fsm9xxx.c b/arch/arm/mach-msm/devices-fsm9xxx.c new file mode 100644 index 00000000000..fdcd2f24548 --- /dev/null +++ b/arch/arm/mach-msm/devices-fsm9xxx.c @@ -0,0 +1,371 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include + +#include +#include +#include +#include +#include + +#include "devices.h" +#include "smd_private.h" +#include "clock-local.h" + +#include +#include + +/* + * UARTs + */ + +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART1_PHYS, + .end = MSM_UART1_PHYS + MSM_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_uart1 = { + .name = "msm_serial", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart1), + .resource = resources_uart1, +}; + +static struct resource resources_uart2[] = { + { + .start = INT_UART2, + .end = INT_UART2, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART2_PHYS, + .end = MSM_UART2_PHYS + MSM_UART2_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_uart2 = { + .name = "msm_serial", + .id = 1, + .num_resources = ARRAY_SIZE(resources_uart2), + .resource = resources_uart2, +}; + +/* + * SSBIs + */ + +#ifdef CONFIG_I2C_SSBI + +#define MSM_SSBI1_PHYS 0x94080000 +#define MSM_SSBI1_SIZE SZ_4K + +static struct resource msm_ssbi1_resources[] = { + { + .name = "ssbi_base", + .start = MSM_SSBI1_PHYS, + .end = MSM_SSBI1_PHYS + MSM_SSBI1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi1 = { + .name = "i2c_ssbi", + .id = 0, + .num_resources = ARRAY_SIZE(msm_ssbi1_resources), + .resource = msm_ssbi1_resources, +}; + +#define MSM_SSBI2_PHYS 0x94090000 +#define MSM_SSBI2_SIZE SZ_4K + +static struct resource msm_ssbi2_resources[] = { + { + .name = "ssbi_base", + .start = MSM_SSBI2_PHYS, + .end = MSM_SSBI2_PHYS + MSM_SSBI2_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi2 = { + .name = "i2c_ssbi", + .id = 1, + .num_resources = ARRAY_SIZE(msm_ssbi2_resources), + .resource = msm_ssbi2_resources, +}; + +#define MSM_SSBI3_PHYS 0x940c0000 +#define MSM_SSBI3_SIZE SZ_4K + +static struct resource msm_ssbi3_resources[] = { + { + .name = "ssbi_base", + .start = MSM_SSBI3_PHYS, + .end = MSM_SSBI3_PHYS + MSM_SSBI3_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi3 = { + .name = "i2c_ssbi", + .id = 2, + .num_resources = ARRAY_SIZE(msm_ssbi3_resources), + .resource = msm_ssbi3_resources, +}; + +#endif /* CONFIG_I2C_SSBI */ + +/* + * GSBI + */ + +#ifdef CONFIG_I2C_QUP + +#define MSM_GSBI1_PHYS 0x81200000 +#define MSM_GSBI1_QUP_PHYS 0x81a00000 + +static struct resource gsbi1_qup_i2c_resources[] = { + { + .name = "qup_phys_addr", + .start = MSM_GSBI1_QUP_PHYS, + .end = MSM_GSBI1_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI1_PHYS, + .end = MSM_GSBI1_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = INT_GSBI_QUP_ERROR, + .end = INT_GSBI_QUP_ERROR, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_gsbi1_qup_i2c_device = { + .name = "qup_i2c", + .id = 3, + .num_resources = ARRAY_SIZE(gsbi1_qup_i2c_resources), + .resource = gsbi1_qup_i2c_resources, +}; + +#endif /* CONFIG_I2C_QUP */ + +/* + * NAND + */ + +#define MSM_NAND_PHYS 0x81600000 +#define MSM_NAND_SIZE SZ_4K +#define MSM_EBI2_CTRL_PHYS 0x81400000 +#define MSM_EBI2_CTRL_SIZE SZ_4K + +static struct resource resources_nand[] = { + [0] = { + .name = "msm_nand_dmac", + .start = DMOV_NAND_CHAN, + .end = DMOV_NAND_CHAN, + .flags = IORESOURCE_DMA, + }, + [1] = { + .name = "msm_nand_phys", + .start = MSM_NAND_PHYS, + .end = MSM_NAND_PHYS + MSM_NAND_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [3] = { + .name = "ebi2_reg_base", + .start = MSM_EBI2_CTRL_PHYS, + .end = MSM_EBI2_CTRL_PHYS + MSM_EBI2_CTRL_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct flash_platform_data msm_nand_data = { + .parts = NULL, + .nr_parts = 0, + .interleave = 0, +}; + +struct platform_device msm_device_nand = { + .name = "msm_nand", + .id = -1, + .num_resources = ARRAY_SIZE(resources_nand), + .resource = resources_nand, + .dev = { + .platform_data = &msm_nand_data, + }, +}; + +/* + * SMD + */ + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +/* + * ADM + */ + +struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .end = (resource_size_t) MSM_DMOV_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), +}; + +/* + * SDC + */ + +#define MSM_SDC1_PHYS 0x80A00000 +#define MSM_SDC1_SIZE SZ_4K + +static struct resource resources_sdc1[] = { + { + .start = MSM_SDC1_PHYS, + .end = MSM_SDC1_PHYS + MSM_SDC1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC1_0, + .end = INT_SDC1_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_SDC1_CHAN, + .end = DMOV_SDC1_CHAN, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device msm_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_sdc1), + .resource = resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct platform_device *msm_sdcc_devices[] __initdata = { + &msm_device_sdc1, +}; + +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (controller != 1) + return -EINVAL; + + pdev = msm_sdcc_devices[controller-1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +/* + * QFEC + */ + +# define QFEC_MAC_IRQ INT_SBD_IRQ +# define QFEC_MAC_BASE 0x40000000 +# define QFEC_CLK_BASE 0x94020000 + +# define QFEC_MAC_SIZE 0x2000 +# define QFEC_CLK_SIZE 0x18100 + +# define QFEC_MAC_FUSE_BASE 0x80004210 +# define QFEC_MAC_FUSE_SIZE 16 + +static struct resource qfec_resources[] = { + [0] = { + .start = QFEC_MAC_BASE, + .end = QFEC_MAC_BASE + QFEC_MAC_SIZE, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = QFEC_MAC_IRQ, + .end = QFEC_MAC_IRQ, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = QFEC_CLK_BASE, + .end = QFEC_CLK_BASE + QFEC_CLK_SIZE, + .flags = IORESOURCE_IO, + }, + [3] = { + .start = QFEC_MAC_FUSE_BASE, + .end = QFEC_MAC_FUSE_BASE + QFEC_MAC_FUSE_SIZE, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device qfec_device = { + .name = "qfec", + .id = 0, + .num_resources = ARRAY_SIZE(qfec_resources), + .resource = qfec_resources, +}; + +/* + * FUSE + */ + +#if defined(CONFIG_QFP_FUSE) + +char fuse_regulator_name[] = "8058_lvs0"; + +struct resource qfp_fuse_resources[] = { + { + .start = (uint32_t) MSM_QFP_FUSE_BASE, + .end = (uint32_t) MSM_QFP_FUSE_BASE + MSM_QFP_FUSE_SIZE, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device fsm_qfp_fuse_device = { + .name = "qfp_fuse_driver", + .id = 0, + .dev = {.platform_data = fuse_regulator_name}, + .num_resources = ARRAY_SIZE(qfp_fuse_resources), + .resource = qfp_fuse_resources, +}; + +#endif + diff --git a/arch/arm/mach-msm/devices-iommu.c b/arch/arm/mach-msm/devices-iommu.c index 24030d0da6e..a945349e0b1 100644 --- a/arch/arm/mach-msm/devices-iommu.c +++ b/arch/arm/mach-msm/devices-iommu.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/arch/arm/mach-msm/devices-msm7x00.c b/arch/arm/mach-msm/devices-msm7x00.c index c4f5e26feb4..d7c7aabb227 100644 --- a/arch/arm/mach-msm/devices-msm7x00.c +++ b/arch/arm/mach-msm/devices-msm7x00.c @@ -15,18 +15,20 @@ #include #include -#include +#include +#include #include #include +#include +#include #include "devices.h" +#include "proc_comm.h" #include #include #include -#include "clock.h" -#include "clock-pcom.h" #include static struct resource resources_uart1[] = { @@ -92,6 +94,92 @@ struct platform_device msm_device_uart3 = { .resource = resources_uart3, }; +static struct resource msm_uart1_dm_resources[] = { + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART1DM_IRQ, + .end = INT_UART1DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART1DM_RX, + .end = INT_UART1DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART1_TX_CHAN, + .end = DMOV_HSUART1_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART1_TX_CRCI, + .end = DMOV_HSUART1_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm1_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm1 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart1_dm_resources), + .resource = msm_uart1_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm1_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource msm_uart2_dm_resources[] = { + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART2DM_IRQ, + .end = INT_UART2DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART2DM_RX, + .end = INT_UART2DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART2_TX_CHAN, + .end = DMOV_HSUART2_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART2_TX_CRCI, + .end = DMOV_HSUART2_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm2_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm2 = { + .name = "msm_serial_hs", + .id = 1, + .num_resources = ARRAY_SIZE(msm_uart2_dm_resources), + .resource = msm_uart2_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm2_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + static struct resource resources_i2c[] = { { .start = MSM_I2C_PHYS, @@ -112,6 +200,30 @@ struct platform_device msm_device_i2c = { .resource = resources_i2c, }; +#define GPIO_I2C_CLK 60 +#define GPIO_I2C_DAT 61 +void msm_set_i2c_mux(bool gpio, int *gpio_clk, int *gpio_dat) +{ + unsigned id; + if (gpio) { + id = PCOM_GPIO_CFG(GPIO_I2C_CLK, 0, GPIO_OUTPUT, + GPIO_NO_PULL, GPIO_2MA); + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0); + id = PCOM_GPIO_CFG(GPIO_I2C_DAT, 0, GPIO_OUTPUT, + GPIO_NO_PULL, GPIO_2MA); + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0); + *gpio_clk = GPIO_I2C_CLK; + *gpio_dat = GPIO_I2C_DAT; + } else { + id = PCOM_GPIO_CFG(GPIO_I2C_CLK, 1, GPIO_INPUT, + GPIO_NO_PULL, GPIO_8MA); + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0); + id = PCOM_GPIO_CFG(GPIO_I2C_DAT , 1, GPIO_INPUT, + GPIO_NO_PULL, GPIO_8MA); + msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0); + } +} + static struct resource resources_hsusb[] = { { .start = MSM_HSUSB_PHYS, @@ -326,8 +438,7 @@ static struct platform_device *msm_sdcc_devices[] __initdata = { &msm_device_sdc4, }; -int __init msm_add_sdcc(unsigned int controller, - struct msm_mmc_platform_data *plat, +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat, unsigned int stat_irq, unsigned long stat_irq_flags) { struct platform_device *pdev; @@ -418,48 +529,30 @@ struct platform_device msm_device_mdp = { .resource = resources_mdp, }; -struct clk_lookup msm_clocks_7x01a[] = { - CLK_PCOM("adm_clk", ADM_CLK, NULL, 0), - CLK_PCOM("adsp_clk", ADSP_CLK, NULL, 0), - CLK_PCOM("ebi1_clk", EBI1_CLK, NULL, 0), - CLK_PCOM("ebi2_clk", EBI2_CLK, NULL, 0), - CLK_PCOM("ecodec_clk", ECODEC_CLK, NULL, 0), - CLK_PCOM("emdh_clk", EMDH_CLK, NULL, OFF), - CLK_PCOM("gp_clk", GP_CLK, NULL, 0), - CLK_PCOM("grp_clk", GRP_3D_CLK, NULL, OFF), - CLK_PCOM("i2c_clk", I2C_CLK, "msm_i2c.0", 0), - CLK_PCOM("icodec_rx_clk", ICODEC_RX_CLK, NULL, 0), - CLK_PCOM("icodec_tx_clk", ICODEC_TX_CLK, NULL, 0), - CLK_PCOM("imem_clk", IMEM_CLK, NULL, OFF), - CLK_PCOM("mdc_clk", MDC_CLK, NULL, 0), - CLK_PCOM("mdp_clk", MDP_CLK, NULL, OFF), - CLK_PCOM("pbus_clk", PBUS_CLK, NULL, 0), - CLK_PCOM("pcm_clk", PCM_CLK, NULL, 0), - CLK_PCOM("mddi_clk", PMDH_CLK, NULL, OFF | CLK_MINMAX), - CLK_PCOM("sdac_clk", SDAC_CLK, NULL, OFF), - CLK_PCOM("sdc_clk", SDC1_CLK, "msm_sdcc.1", OFF), - CLK_PCOM("sdc_pclk", SDC1_P_CLK, "msm_sdcc.1", OFF), - CLK_PCOM("sdc_clk", SDC2_CLK, "msm_sdcc.2", OFF), - CLK_PCOM("sdc_pclk", SDC2_P_CLK, "msm_sdcc.2", OFF), - CLK_PCOM("sdc_clk", SDC3_CLK, "msm_sdcc.3", OFF), - CLK_PCOM("sdc_pclk", SDC3_P_CLK, "msm_sdcc.3", OFF), - CLK_PCOM("sdc_clk", SDC4_CLK, "msm_sdcc.4", OFF), - CLK_PCOM("sdc_pclk", SDC4_P_CLK, "msm_sdcc.4", OFF), - CLK_PCOM("tsif_clk", TSIF_CLK, NULL, 0), - CLK_PCOM("tsif_ref_clk", TSIF_REF_CLK, NULL, 0), - CLK_PCOM("tv_dac_clk", TV_DAC_CLK, NULL, 0), - CLK_PCOM("tv_enc_clk", TV_ENC_CLK, NULL, 0), - CLK_PCOM("uart_clk", UART1_CLK, "msm_serial.0", OFF), - CLK_PCOM("uart_clk", UART2_CLK, "msm_serial.1", 0), - CLK_PCOM("uart_clk", UART3_CLK, "msm_serial.2", OFF), - CLK_PCOM("uart1dm_clk", UART1DM_CLK, NULL, OFF), - CLK_PCOM("uart2dm_clk", UART2DM_CLK, NULL, 0), - CLK_PCOM("usb_hs_clk", USB_HS_CLK, "msm_hsusb", OFF), - CLK_PCOM("usb_hs_pclk", USB_HS_P_CLK, "msm_hsusb", OFF), - CLK_PCOM("usb_otg_clk", USB_OTG_CLK, NULL, 0), - CLK_PCOM("vdc_clk", VDC_CLK, NULL, OFF ), - CLK_PCOM("vfe_clk", VFE_CLK, NULL, OFF), - CLK_PCOM("vfe_mdc_clk", VFE_MDC_CLK, NULL, OFF), -}; - -unsigned msm_num_clocks_7x01a = ARRAY_SIZE(msm_clocks_7x01a); +static struct resource resources_tssc[] = { + { + .start = MSM_TSSC_PHYS, + .end = MSM_TSSC_PHYS + MSM_TSSC_SIZE - 1, + .name = "tssc", + .flags = IORESOURCE_MEM, + }, + { + .start = INT_TCHSCRN1, + .end = INT_TCHSCRN1, + .name = "tssc1", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, + { + .start = INT_TCHSCRN2, + .end = INT_TCHSCRN2, + .name = "tssc2", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, +}; + +struct platform_device msm_device_touchscreen = { + .name = "msm_touchscreen", + .id = 0, + .num_resources = ARRAY_SIZE(resources_tssc), + .resource = resources_tssc, +}; diff --git a/arch/arm/mach-msm/devices-msm7x01a.c b/arch/arm/mach-msm/devices-msm7x01a.c new file mode 100644 index 00000000000..9ed6fd15c0b --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x01a.c @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. 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 +#include + +#include +#include +#include +#include +#include +#include + +#include "devices.h" + +#include + +#include +#include + +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART1_PHYS, + .end = MSM_UART1_PHYS + MSM_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_uart2[] = { + { + .start = INT_UART2, + .end = INT_UART2, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART2_PHYS, + .end = MSM_UART2_PHYS + MSM_UART2_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_uart3[] = { + { + .start = INT_UART3, + .end = INT_UART3, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART3_PHYS, + .end = MSM_UART3_PHYS + MSM_UART3_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_uart1 = { + .name = "msm_serial", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart1), + .resource = resources_uart1, +}; + +struct platform_device msm_device_uart2 = { + .name = "msm_serial", + .id = 1, + .num_resources = ARRAY_SIZE(resources_uart2), + .resource = resources_uart2, +}; + +struct platform_device msm_device_uart3 = { + .name = "msm_serial", + .id = 2, + .num_resources = ARRAY_SIZE(resources_uart3), + .resource = resources_uart3, +}; + +#define MSM_UART1DM_PHYS 0xA0200000 +#define MSM_UART2DM_PHYS 0xA0300000 +static struct resource msm_uart1_dm_resources[] = { + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART1DM_IRQ, + .end = INT_UART1DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART1DM_RX, + .end = INT_UART1DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART1_TX_CHAN, + .end = DMOV_HSUART1_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART1_TX_CRCI, + .end = DMOV_HSUART1_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm1_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm1 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart1_dm_resources), + .resource = msm_uart1_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm1_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource msm_uart2_dm_resources[] = { + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART2DM_IRQ, + .end = INT_UART2DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART2DM_RX, + .end = INT_UART2DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART2_TX_CHAN, + .end = DMOV_HSUART2_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART2_TX_CRCI, + .end = DMOV_HSUART2_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm2_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm2 = { + .name = "msm_serial_hs", + .id = 1, + .num_resources = ARRAY_SIZE(msm_uart2_dm_resources), + .resource = msm_uart2_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm2_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +#define MSM_I2C_SIZE SZ_4K +#define MSM_I2C_PHYS 0xA9900000 +static struct resource resources_i2c[] = { + { + .start = MSM_I2C_PHYS, + .end = MSM_I2C_PHYS + MSM_I2C_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_PWB_I2C, + .end = INT_PWB_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_i2c = { + .name = "msm_i2c", + .id = 0, + .num_resources = ARRAY_SIZE(resources_i2c), + .resource = resources_i2c, +}; + +#define MSM_HSUSB_PHYS 0xA0800000 +static struct resource resources_hsusb_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 dma_mask = 0xffffffffULL; +struct platform_device msm_device_hsusb_otg = { + .name = "msm_hsusb_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_otg), + .resource = resources_hsusb_otg, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_hsusb_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, + { + .name = "vbus_interrupt", + .start = MSM_GPIO_TO_INT(112), + .end = MSM_GPIO_TO_INT(112), + .flags = IORESOURCE_IRQ, + }, + { + .name = "id_interrupt", + .start = MSM_GPIO_TO_INT(114), + .end = MSM_GPIO_TO_INT(114), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource resources_gadget_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsusb_peripheral = { + .name = "msm_hsusb_peripheral", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_peripheral), + .resource = resources_hsusb_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct platform_device msm_device_gadget_peripheral = { + .name = "msm_hsusb", + .id = -1, + .num_resources = ARRAY_SIZE(resources_gadget_peripheral), + .resource = resources_gadget_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_hsusb_host[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsusb_host = { + .name = "msm_hsusb_host", + .id = 0, + .num_resources = ARRAY_SIZE(resources_hsusb_host), + .resource = resources_hsusb_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct platform_device *msm_host_devices[] = { + &msm_device_hsusb_host, +}; + +int msm_add_host(unsigned int host, struct msm_usb_host_platform_data *plat) +{ + struct platform_device *pdev; + + pdev = msm_host_devices[host]; + if (!pdev) + return -ENODEV; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#define MSM_NAND_PHYS 0xA0A00000 +static struct resource resources_nand[] = { + [0] = { + .name = "msm_nand_dmac", + .start = DMOV_NAND_CHAN, + .end = DMOV_NAND_CHAN, + .flags = IORESOURCE_DMA, + }, + [1] = { + .name = "msm_nand_phys", + .start = MSM_NAND_PHYS, + .end = MSM_NAND_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_otg), + .resource = resources_otg, + .dev = { + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct flash_platform_data msm_nand_data = { + .parts = NULL, + .nr_parts = 0, +}; + +struct platform_device msm_device_nand = { + .name = "msm_nand", + .id = -1, + .num_resources = ARRAY_SIZE(resources_nand), + .resource = resources_nand, + .dev = { + .platform_data = &msm_nand_data, + }, +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .end = (resource_size_t)MSM_DMOV_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), +}; + +#define MSM_SDC1_BASE 0xA0400000 +#define MSM_SDC2_BASE 0xA0500000 +#define MSM_SDC3_BASE 0xA0600000 +#define MSM_SDC4_BASE 0xA0700000 +static struct resource resources_sdc1[] = { + { + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC1_0, + .end = INT_SDC1_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc2[] = { + { + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC2_0, + .end = INT_SDC2_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc3[] = { + { + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC3_0, + .end = INT_SDC3_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc4[] = { + { + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC4_0, + .end = INT_SDC4_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device msm_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_sdc1), + .resource = resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc2 = { + .name = "msm_sdcc", + .id = 2, + .num_resources = ARRAY_SIZE(resources_sdc2), + .resource = resources_sdc2, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc3 = { + .name = "msm_sdcc", + .id = 3, + .num_resources = ARRAY_SIZE(resources_sdc3), + .resource = resources_sdc3, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc4 = { + .name = "msm_sdcc", + .id = 4, + .num_resources = ARRAY_SIZE(resources_sdc4), + .resource = resources_sdc4, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct platform_device *msm_sdcc_devices[] __initdata = { + &msm_device_sdc1, + &msm_device_sdc2, + &msm_device_sdc3, + &msm_device_sdc4, +}; + +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (controller < 1 || controller > 4) + return -EINVAL; + + pdev = msm_sdcc_devices[controller-1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#if defined(CONFIG_FB_MSM_MDP40) +#define MDP_BASE 0xA3F00000 +#define PMDH_BASE 0xAD600000 +#define EMDH_BASE 0xAD700000 +#define TVENC_BASE 0xAD400000 +#else +#define MDP_BASE 0xAA200000 +#define PMDH_BASE 0xAA600000 +#define EMDH_BASE 0xAA700000 +#define TVENC_BASE 0xAA400000 +#endif + +static struct resource msm_mdp_resources[] = { + { + .name = "mdp", + .start = MDP_BASE, + .end = MDP_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_MDP, + .end = INT_MDP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_mddi_resources[] = { + { + .name = "pmdh", + .start = PMDH_BASE, + .end = PMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_mddi_ext_resources[] = { + { + .name = "emdh", + .start = EMDH_BASE, + .end = EMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_ebi2_lcd_resources[] = { + { + .name = "base", + .start = 0xa0d00000, + .end = 0xa0d00000 + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd01", + .start = 0x98000000, + .end = 0x98000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd02", + .start = 0x9c000000, + .end = 0x9c000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource msm_tvenc_resources[] = { + { + .name = "tvenc", + .start = TVENC_BASE, + .end = TVENC_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct platform_device msm_mdp_device = { + .name = "mdp", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mdp_resources), + .resource = msm_mdp_resources, +}; + +static struct platform_device msm_mddi_device = { + .name = "mddi", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_resources), + .resource = msm_mddi_resources, +}; + +static struct platform_device msm_mddi_ext_device = { + .name = "mddi_ext", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_ext_resources), + .resource = msm_mddi_ext_resources, +}; + +static struct platform_device msm_ebi2_lcd_device = { + .name = "ebi2_lcd", + .id = 0, + .num_resources = ARRAY_SIZE(msm_ebi2_lcd_resources), + .resource = msm_ebi2_lcd_resources, +}; + +static struct platform_device msm_lcdc_device = { + .name = "lcdc", + .id = 0, +}; + +static struct platform_device msm_tvenc_device = { + .name = "tvenc", + .id = 0, + .num_resources = ARRAY_SIZE(msm_tvenc_resources), + .resource = msm_tvenc_resources, +}; + +/* TSIF begin */ +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + +#define MSM_TSIF_PHYS (0xa0100000) +#define MSM_TSIF_SIZE (0x200) + +static struct resource tsif_resources[] = { + [0] = { + .flags = IORESOURCE_IRQ, + .start = INT_TSIF_IRQ, + .end = INT_TSIF_IRQ, + }, + [1] = { + .flags = IORESOURCE_MEM, + .start = MSM_TSIF_PHYS, + .end = MSM_TSIF_PHYS + MSM_TSIF_SIZE - 1, + }, + [2] = { + .flags = IORESOURCE_DMA, + .start = DMOV_TSIF_CHAN, + .end = DMOV_TSIF_CRCI, + }, +}; + +static void tsif_release(struct device *dev) +{ + dev_info(dev, "release\n"); +} + +struct platform_device msm_device_tsif = { + .name = "msm_tsif", + .id = 0, + .num_resources = ARRAY_SIZE(tsif_resources), + .resource = tsif_resources, + .dev = { + .release = tsif_release, + }, +}; +#endif /* defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) */ +/* TSIF end */ + +#define MSM_TSSC_PHYS 0xAA300000 +static struct resource resources_tssc[] = { + { + .start = MSM_TSSC_PHYS, + .end = MSM_TSSC_PHYS + SZ_4K - 1, + .name = "tssc", + .flags = IORESOURCE_MEM, + }, + { + .start = INT_TCHSCRN1, + .end = INT_TCHSCRN1, + .name = "tssc1", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, + { + .start = INT_TCHSCRN2, + .end = INT_TCHSCRN2, + .name = "tssc2", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, +}; + +struct platform_device msm_device_tssc = { + .name = "msm_touchscreen", + .id = 0, + .num_resources = ARRAY_SIZE(resources_tssc), + .resource = resources_tssc, +}; + +static void __init msm_register_device(struct platform_device *pdev, void *data) +{ + int ret; + + pdev->dev.platform_data = data; + + ret = platform_device_register(pdev); + if (ret) + dev_err(&pdev->dev, + "%s: platform_device_register() failed = %d\n", + __func__, ret); +} + +void __init msm_fb_register_device(char *name, void *data) +{ + if (!strncmp(name, "mdp", 3)) + msm_register_device(&msm_mdp_device, data); + else if (!strncmp(name, "pmdh", 4)) + msm_register_device(&msm_mddi_device, data); + else if (!strncmp(name, "emdh", 4)) + msm_register_device(&msm_mddi_ext_device, data); + else if (!strncmp(name, "ebi2", 4)) + msm_register_device(&msm_ebi2_lcd_device, data); + else if (!strncmp(name, "tvenc", 5)) + msm_register_device(&msm_tvenc_device, data); + else if (!strncmp(name, "lcdc", 4)) + msm_register_device(&msm_lcdc_device, data); + else + printk(KERN_ERR "%s: unknown device! %s\n", __func__, name); +} + +static struct platform_device msm_camera_device = { + .name = "msm_camera", + .id = 0, +}; + +void __init msm_camera_register_device(void *res, uint32_t num, + void *data) +{ + msm_camera_device.num_resources = num; + msm_camera_device.resource = res; + + msm_register_device(&msm_camera_device, data); +} diff --git a/arch/arm/mach-msm/devices-msm7x25.c b/arch/arm/mach-msm/devices-msm7x25.c new file mode 100644 index 00000000000..748229e3a83 --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x25.c @@ -0,0 +1,941 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. 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 +#include + +#include +#include +#include +#include +#include +#include + +#include "devices.h" +#include "smd_private.h" + +#include + +#include +#include +#include +#include +#include + +#include "clock-pcom.h" + +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART1_PHYS, + .end = MSM_UART1_PHYS + MSM_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_uart2[] = { + { + .start = INT_UART2, + .end = INT_UART2, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART2_PHYS, + .end = MSM_UART2_PHYS + MSM_UART2_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_uart3[] = { + { + .start = INT_UART3, + .end = INT_UART3, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART3_PHYS, + .end = MSM_UART3_PHYS + MSM_UART3_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_uart1 = { + .name = "msm_serial", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart1), + .resource = resources_uart1, +}; + +struct platform_device msm_device_uart2 = { + .name = "msm_serial", + .id = 1, + .num_resources = ARRAY_SIZE(resources_uart2), + .resource = resources_uart2, +}; + +struct platform_device msm_device_uart3 = { + .name = "msm_serial", + .id = 2, + .num_resources = ARRAY_SIZE(resources_uart3), + .resource = resources_uart3, +}; + +#define MSM_UART1DM_PHYS 0xA0200000 +#define MSM_UART2DM_PHYS 0xA0300000 +static struct resource msm_uart1_dm_resources[] = { + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART1DM_IRQ, + .end = INT_UART1DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART1DM_RX, + .end = INT_UART1DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART1_TX_CHAN, + .end = DMOV_HSUART1_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART1_TX_CRCI, + .end = DMOV_HSUART1_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm1_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm1 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart1_dm_resources), + .resource = msm_uart1_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm1_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource msm_uart2_dm_resources[] = { + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART2DM_IRQ, + .end = INT_UART2DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART2DM_RX, + .end = INT_UART2DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART2_TX_CHAN, + .end = DMOV_HSUART2_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART2_TX_CRCI, + .end = DMOV_HSUART2_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm2_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm2 = { + .name = "msm_serial_hs", + .id = 1, + .num_resources = ARRAY_SIZE(msm_uart2_dm_resources), + .resource = msm_uart2_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm2_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +#define MSM_I2C_SIZE SZ_4K +#define MSM_I2C_PHYS 0xA9900000 +static struct resource resources_i2c[] = { + { + .start = MSM_I2C_PHYS, + .end = MSM_I2C_PHYS + MSM_I2C_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_PWB_I2C, + .end = INT_PWB_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_i2c = { + .name = "msm_i2c", + .id = 0, + .num_resources = ARRAY_SIZE(resources_i2c), + .resource = resources_i2c, +}; + +#define MSM_HSUSB_PHYS 0xA0800000 +static struct resource resources_hsusb_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 dma_mask = 0xffffffffULL; +struct platform_device msm_device_hsusb_otg = { + .name = "msm_hsusb_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_otg), + .resource = resources_hsusb_otg, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_hsusb_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource resources_gadget_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsusb_peripheral = { + .name = "msm_hsusb_peripheral", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_peripheral), + .resource = resources_hsusb_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct platform_device msm_device_gadget_peripheral = { + .name = "msm_hsusb", + .id = -1, + .num_resources = ARRAY_SIZE(resources_gadget_peripheral), + .resource = resources_gadget_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_hsusb_host[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsusb_host = { + .name = "msm_hsusb_host", + .id = 0, + .num_resources = ARRAY_SIZE(resources_hsusb_host), + .resource = resources_hsusb_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct platform_device *msm_host_devices[] = { + &msm_device_hsusb_host, +}; + +int msm_add_host(unsigned int host, struct msm_usb_host_platform_data *plat) +{ + struct platform_device *pdev; + + pdev = msm_host_devices[host]; + if (!pdev) + return -ENODEV; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#ifdef CONFIG_USB_ANDROID_DIAG +struct usb_diag_platform_data usb_diag_pdata = { + .ch_name = DIAG_LEGACY, + .update_pid_and_serial_num = usb_diag_update_pid_and_serial_num, +}; + +struct platform_device usb_diag_device = { + .name = "usb_diag", + .id = -1, + .dev = { + .platform_data = &usb_diag_pdata, + }, +}; +#endif + +#ifdef CONFIG_USB_F_SERIAL +static struct usb_gadget_fserial_platform_data fserial_pdata = { + .no_ports = 2, +}; + +struct platform_device usb_gadget_fserial_device = { + .name = "usb_fserial", + .id = -1, + .dev = { + .platform_data = &fserial_pdata, + }, +}; +#endif + +#define MSM_NAND_PHYS 0xA0A00000 +static struct resource resources_nand[] = { + [0] = { + .name = "msm_nand_dmac", + .start = DMOV_NAND_CHAN, + .end = DMOV_NAND_CHAN, + .flags = IORESOURCE_DMA, + }, + [1] = { + .name = "msm_nand_phys", + .start = MSM_NAND_PHYS, + .end = MSM_NAND_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_otg), + .resource = resources_otg, + .dev = { + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct flash_platform_data msm_nand_data = { + .parts = NULL, + .nr_parts = 0, +}; + +struct platform_device msm_device_nand = { + .name = "msm_nand", + .id = -1, + .num_resources = ARRAY_SIZE(resources_nand), + .resource = resources_nand, + .dev = { + .platform_data = &msm_nand_data, + }, +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .end = (resource_size_t)MSM_DMOV_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), +}; + +#define MSM_SDC1_BASE 0xA0400000 +#define MSM_SDC2_BASE 0xA0500000 +#define MSM_SDC3_BASE 0xA0600000 +#define MSM_SDC4_BASE 0xA0700000 +static struct resource resources_sdc1[] = { + { + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC1_0, + .end = INT_SDC1_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc2[] = { + { + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC2_0, + .end = INT_SDC2_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc3[] = { + { + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC3_0, + .end = INT_SDC3_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc4[] = { + { + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC4_0, + .end = INT_SDC4_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device msm_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_sdc1), + .resource = resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc2 = { + .name = "msm_sdcc", + .id = 2, + .num_resources = ARRAY_SIZE(resources_sdc2), + .resource = resources_sdc2, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc3 = { + .name = "msm_sdcc", + .id = 3, + .num_resources = ARRAY_SIZE(resources_sdc3), + .resource = resources_sdc3, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc4 = { + .name = "msm_sdcc", + .id = 4, + .num_resources = ARRAY_SIZE(resources_sdc4), + .resource = resources_sdc4, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct platform_device *msm_sdcc_devices[] __initdata = { + &msm_device_sdc1, + &msm_device_sdc2, + &msm_device_sdc3, + &msm_device_sdc4, +}; + +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (controller < 1 || controller > 4) + return -EINVAL; + + pdev = msm_sdcc_devices[controller-1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#define RAMFS_INFO_MAGICNUMBER 0x654D4D43 +#define RAMFS_INFO_VERSION 0x00000001 +#define RAMFS_MODEMSTORAGE_ID 0x4D454653 + +static struct resource rmt_storage_resources[] = { + { + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device rmt_storage_device = { + .name = "rmt_storage", + .id = -1, + .num_resources = ARRAY_SIZE(rmt_storage_resources), + .resource = rmt_storage_resources, +}; + +struct shared_ramfs_entry { + uint32_t client_id; /* Client id to uniquely identify a client */ + uint32_t base_addr; /* Base address of shared RAMFS memory */ + uint32_t size; /* Size of the shared RAMFS memory */ + uint32_t reserved; /* Reserved attribute for future use */ +}; +struct shared_ramfs_table { + uint32_t magic_id; /* Identify RAMFS details in SMEM */ + uint32_t version; /* Version of shared_ramfs_table */ + uint32_t entries; /* Total number of valid entries */ + struct shared_ramfs_entry ramfs_entry[3]; /* List all entries */ +}; + +int __init rmt_storage_add_ramfs(void) +{ + struct shared_ramfs_table *ramfs_table; + struct shared_ramfs_entry *ramfs_entry; + int index; + + ramfs_table = smem_alloc(SMEM_SEFS_INFO, + sizeof(struct shared_ramfs_table)); + + if (!ramfs_table) { + printk(KERN_WARNING "%s: No RAMFS table in SMEM\n", __func__); + return -ENOENT; + } + + if ((ramfs_table->magic_id != (u32) RAMFS_INFO_MAGICNUMBER) || + (ramfs_table->version != (u32) RAMFS_INFO_VERSION)) { + printk(KERN_WARNING "%s: Magic / Version mismatch:, " + "magic_id=%#x, format_version=%#x\n", __func__, + ramfs_table->magic_id, ramfs_table->version); + return -ENOENT; + } + + for (index = 0; index < ramfs_table->entries; index++) { + ramfs_entry = &ramfs_table->ramfs_entry[index]; + + /* Find a match for the Modem Storage RAMFS area */ + if (ramfs_entry->client_id == (u32) RAMFS_MODEMSTORAGE_ID) { + printk(KERN_INFO "%s: RAMFS Info (from SMEM): " + "Baseaddr = 0x%08x, Size = 0x%08x\n", __func__, + ramfs_entry->base_addr, ramfs_entry->size); + + rmt_storage_resources[0].start = ramfs_entry->base_addr; + rmt_storage_resources[0].end = ramfs_entry->base_addr + + ramfs_entry->size - 1; + platform_device_register(&rmt_storage_device); + return 0; + } + } + return -ENOENT; +} + +#if defined(CONFIG_FB_MSM_MDP40) +#define MDP_BASE 0xA3F00000 +#define PMDH_BASE 0xAD600000 +#define EMDH_BASE 0xAD700000 +#define TVENC_BASE 0xAD400000 +#else +#define MDP_BASE 0xAA200000 +#define PMDH_BASE 0xAA600000 +#define EMDH_BASE 0xAA700000 +#define TVENC_BASE 0xAA400000 +#endif + +static struct resource msm_mdp_resources[] = { + { + .name = "mdp", + .start = MDP_BASE, + .end = MDP_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_MDP, + .end = INT_MDP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_mddi_resources[] = { + { + .name = "pmdh", + .start = PMDH_BASE, + .end = PMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_mddi_ext_resources[] = { + { + .name = "emdh", + .start = EMDH_BASE, + .end = EMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_ebi2_lcd_resources[] = { + { + .name = "base", + .start = 0xa0d00000, + .end = 0xa0d00000 + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd01", + .start = 0x98000000, + .end = 0x98000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd02", + .start = 0x9c000000, + .end = 0x9c000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource msm_tvenc_resources[] = { + { + .name = "tvenc", + .start = TVENC_BASE, + .end = TVENC_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct platform_device msm_mdp_device = { + .name = "mdp", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mdp_resources), + .resource = msm_mdp_resources, +}; + +static struct platform_device msm_mddi_device = { + .name = "mddi", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_resources), + .resource = msm_mddi_resources, +}; + +static struct platform_device msm_mddi_ext_device = { + .name = "mddi_ext", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_ext_resources), + .resource = msm_mddi_ext_resources, +}; + +static struct platform_device msm_ebi2_lcd_device = { + .name = "ebi2_lcd", + .id = 0, + .num_resources = ARRAY_SIZE(msm_ebi2_lcd_resources), + .resource = msm_ebi2_lcd_resources, +}; + +static struct platform_device msm_lcdc_device = { + .name = "lcdc", + .id = 0, +}; + +static struct platform_device msm_tvenc_device = { + .name = "tvenc", + .id = 0, + .num_resources = ARRAY_SIZE(msm_tvenc_resources), + .resource = msm_tvenc_resources, +}; + +/* TSIF begin */ +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + +#define MSM_TSIF_PHYS (0xa0100000) +#define MSM_TSIF_SIZE (0x200) + +static struct resource tsif_resources[] = { + [0] = { + .flags = IORESOURCE_IRQ, + .start = INT_TSIF_IRQ, + .end = INT_TSIF_IRQ, + }, + [1] = { + .flags = IORESOURCE_MEM, + .start = MSM_TSIF_PHYS, + .end = MSM_TSIF_PHYS + MSM_TSIF_SIZE - 1, + }, + [2] = { + .flags = IORESOURCE_DMA, + .start = DMOV_TSIF_CHAN, + .end = DMOV_TSIF_CRCI, + }, +}; + +static void tsif_release(struct device *dev) +{ + dev_info(dev, "release\n"); +} + +struct platform_device msm_device_tsif = { + .name = "msm_tsif", + .id = 0, + .num_resources = ARRAY_SIZE(tsif_resources), + .resource = tsif_resources, + .dev = { + .release = tsif_release, + }, +}; +#endif /* defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) */ +/* TSIF end */ + +#define MSM_TSSC_PHYS 0xAA300000 +static struct resource resources_tssc[] = { + { + .start = MSM_TSSC_PHYS, + .end = MSM_TSSC_PHYS + SZ_4K - 1, + .name = "tssc", + .flags = IORESOURCE_MEM, + }, + { + .start = INT_TCHSCRN1, + .end = INT_TCHSCRN1, + .name = "tssc1", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, + { + .start = INT_TCHSCRN2, + .end = INT_TCHSCRN2, + .name = "tssc2", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, +}; + +struct platform_device msm_device_tssc = { + .name = "msm_touchscreen", + .id = 0, + .num_resources = ARRAY_SIZE(resources_tssc), + .resource = resources_tssc, +}; + +static void __init msm_register_device(struct platform_device *pdev, void *data) +{ + int ret; + + pdev->dev.platform_data = data; + + ret = platform_device_register(pdev); + if (ret) + dev_err(&pdev->dev, + "%s: platform_device_register() failed = %d\n", + __func__, ret); +} + +void __init msm_fb_register_device(char *name, void *data) +{ + if (!strncmp(name, "mdp", 3)) + msm_register_device(&msm_mdp_device, data); + else if (!strncmp(name, "pmdh", 4)) + msm_register_device(&msm_mddi_device, data); + else if (!strncmp(name, "emdh", 4)) + msm_register_device(&msm_mddi_ext_device, data); + else if (!strncmp(name, "ebi2", 4)) + msm_register_device(&msm_ebi2_lcd_device, data); + else if (!strncmp(name, "tvenc", 5)) + msm_register_device(&msm_tvenc_device, data); + else if (!strncmp(name, "lcdc", 4)) + msm_register_device(&msm_lcdc_device, data); + else + printk(KERN_ERR "%s: unknown device! %s\n", __func__, name); +} + +static struct platform_device msm_camera_device = { + .name = "msm_camera", + .id = 0, +}; + +void __init msm_camera_register_device(void *res, uint32_t num, + void *data) +{ + msm_camera_device.num_resources = num; + msm_camera_device.resource = res; + + msm_register_device(&msm_camera_device, data); +} + +static DEFINE_CLK_PCOM(adm_clk, ADM_CLK, 0); +static DEFINE_CLK_PCOM(adsp_clk, ADSP_CLK, 0); +static DEFINE_CLK_PCOM(ebi1_clk, EBI1_CLK, CLK_MIN); +static DEFINE_CLK_PCOM(ebi2_clk, EBI2_CLK, 0); +static DEFINE_CLK_PCOM(ecodec_clk, ECODEC_CLK, 0); +static DEFINE_CLK_PCOM(gp_clk, GP_CLK, 0); +static DEFINE_CLK_PCOM(i2c_clk, I2C_CLK, 0); +static DEFINE_CLK_PCOM(icodec_rx_clk, ICODEC_RX_CLK, 0); +static DEFINE_CLK_PCOM(icodec_tx_clk, ICODEC_TX_CLK, 0); +static DEFINE_CLK_PCOM(imem_clk, IMEM_CLK, OFF); +static DEFINE_CLK_PCOM(mdc_clk, MDC_CLK, 0); +static DEFINE_CLK_PCOM(pmdh_clk, PMDH_CLK, OFF | CLK_MINMAX); +static DEFINE_CLK_PCOM(mdp_clk, MDP_CLK, OFF); +static DEFINE_CLK_PCOM(mdp_lcdc_pclk_clk, MDP_LCDC_PCLK_CLK, 0); +static DEFINE_CLK_PCOM(mdp_lcdc_pad_pclk_clk, MDP_LCDC_PAD_PCLK_CLK, 0); +static DEFINE_CLK_PCOM(mdp_vsync_clk, MDP_VSYNC_CLK, OFF); +static DEFINE_CLK_PCOM(pbus_clk, PBUS_CLK, CLK_MIN); +static DEFINE_CLK_PCOM(pcm_clk, PCM_CLK, 0); +static DEFINE_CLK_PCOM(sdac_clk, SDAC_CLK, OFF); +static DEFINE_CLK_PCOM(sdc1_clk, SDC1_CLK, OFF); +static DEFINE_CLK_PCOM(sdc1_p_clk, SDC1_P_CLK, OFF); +static DEFINE_CLK_PCOM(sdc2_clk, SDC2_CLK, OFF); +static DEFINE_CLK_PCOM(sdc2_p_clk, SDC2_P_CLK, OFF); +static DEFINE_CLK_PCOM(sdc3_clk, SDC3_CLK, OFF); +static DEFINE_CLK_PCOM(sdc3_p_clk, SDC3_P_CLK, OFF); +static DEFINE_CLK_PCOM(sdc4_clk, SDC4_CLK, OFF); +static DEFINE_CLK_PCOM(sdc4_p_clk, SDC4_P_CLK, OFF); +static DEFINE_CLK_PCOM(uart1_clk, UART1_CLK, OFF); +static DEFINE_CLK_PCOM(uart2_clk, UART2_CLK, 0); +static DEFINE_CLK_PCOM(uart3_clk, UART3_CLK, OFF); +static DEFINE_CLK_PCOM(uart1dm_clk, UART1DM_CLK, OFF); +static DEFINE_CLK_PCOM(uart2dm_clk, UART2DM_CLK, 0); +static DEFINE_CLK_PCOM(usb_hs_clk, USB_HS_CLK, OFF); +static DEFINE_CLK_PCOM(usb_hs_p_clk, USB_HS_P_CLK, OFF); +static DEFINE_CLK_PCOM(usb_otg_clk, USB_OTG_CLK, 0); +static DEFINE_CLK_PCOM(vdc_clk, VDC_CLK, OFF | CLK_MIN); +static DEFINE_CLK_PCOM(vfe_clk, VFE_CLK, OFF); +static DEFINE_CLK_PCOM(vfe_mdc_clk, VFE_MDC_CLK, OFF); + +struct clk_lookup msm_clocks_7x25[] = { + CLK_LOOKUP("adm_clk", adm_clk.c, NULL), + CLK_LOOKUP("adsp_clk", adsp_clk.c, NULL), + CLK_LOOKUP("ebi1_clk", ebi1_clk.c, NULL), + CLK_LOOKUP("ebi2_clk", ebi2_clk.c, NULL), + CLK_LOOKUP("ecodec_clk", ecodec_clk.c, NULL), + CLK_LOOKUP("gp_clk", gp_clk.c, NULL), + CLK_LOOKUP("i2c_clk", i2c_clk.c, "msm_i2c.0"), + CLK_LOOKUP("icodec_rx_clk", icodec_rx_clk.c, NULL), + CLK_LOOKUP("icodec_tx_clk", icodec_tx_clk.c, NULL), + CLK_LOOKUP("imem_clk", imem_clk.c, NULL), + CLK_LOOKUP("mdc_clk", mdc_clk.c, NULL), + CLK_LOOKUP("mddi_clk", pmdh_clk.c, NULL), + CLK_LOOKUP("mdp_clk", mdp_clk.c, NULL), + CLK_LOOKUP("mdp_lcdc_pclk_clk", mdp_lcdc_pclk_clk.c, NULL), + CLK_LOOKUP("mdp_lcdc_pad_pclk_clk", mdp_lcdc_pad_pclk_clk.c, NULL), + CLK_LOOKUP("mdp_vsync_clk", mdp_vsync_clk.c, NULL), + CLK_LOOKUP("pbus_clk", pbus_clk.c, NULL), + CLK_LOOKUP("pcm_clk", pcm_clk.c, NULL), + CLK_LOOKUP("sdac_clk", sdac_clk.c, NULL), + CLK_LOOKUP("sdc_clk", sdc1_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_pclk", sdc1_p_clk.c, "msm_sdcc.1"), + CLK_LOOKUP("sdc_clk", sdc2_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_pclk", sdc2_p_clk.c, "msm_sdcc.2"), + CLK_LOOKUP("sdc_clk", sdc3_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_pclk", sdc3_p_clk.c, "msm_sdcc.3"), + CLK_LOOKUP("sdc_clk", sdc4_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("sdc_pclk", sdc4_p_clk.c, "msm_sdcc.4"), + CLK_LOOKUP("uart_clk", uart1_clk.c, "msm_serial.0"), + CLK_LOOKUP("uart_clk", uart2_clk.c, "msm_serial.1"), + CLK_LOOKUP("uart_clk", uart3_clk.c, "msm_serial.2"), + CLK_LOOKUP("uartdm_clk", uart1dm_clk.c, "msm_serial_hs.0"), + CLK_LOOKUP("uartdm_clk", uart2dm_clk.c, "msm_serial_hs.1"), + CLK_LOOKUP("usb_hs_clk", usb_hs_clk.c, NULL), + CLK_LOOKUP("usb_hs_pclk", usb_hs_p_clk.c, NULL), + CLK_LOOKUP("usb_otg_clk", usb_otg_clk.c, NULL), + CLK_LOOKUP("vdc_clk", vdc_clk.c, NULL), + CLK_LOOKUP("vfe_clk", vfe_clk.c, NULL), + CLK_LOOKUP("vfe_mdc_clk", vfe_mdc_clk.c, NULL), +}; + +unsigned msm_num_clocks_7x25 = ARRAY_SIZE(msm_clocks_7x25); + diff --git a/arch/arm/mach-msm/devices-msm7x27.c b/arch/arm/mach-msm/devices-msm7x27.c new file mode 100644 index 00000000000..c74ff29baa1 --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x27.c @@ -0,0 +1,841 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devices.h" +#include "gpio_hw.h" +#include "footswitch.h" + +#include + +#include +#include +#include +#include +#include + +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART1_PHYS, + .end = MSM_UART1_PHYS + MSM_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_uart2[] = { + { + .start = INT_UART2, + .end = INT_UART2, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART2_PHYS, + .end = MSM_UART2_PHYS + MSM_UART2_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_uart1 = { + .name = "msm_serial", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart1), + .resource = resources_uart1, +}; + +struct platform_device msm_device_uart2 = { + .name = "msm_serial", + .id = 1, + .num_resources = ARRAY_SIZE(resources_uart2), + .resource = resources_uart2, +}; + +#define MSM_UART1DM_PHYS 0xA0200000 +#define MSM_UART2DM_PHYS 0xA0300000 +static struct resource msm_uart1_dm_resources[] = { + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART1DM_IRQ, + .end = INT_UART1DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART1DM_RX, + .end = INT_UART1DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART1_TX_CHAN, + .end = DMOV_HSUART1_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART1_TX_CRCI, + .end = DMOV_HSUART1_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm1_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm1 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart1_dm_resources), + .resource = msm_uart1_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm1_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource msm_uart2_dm_resources[] = { + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART2DM_IRQ, + .end = INT_UART2DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART2DM_RX, + .end = INT_UART2DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART2_TX_CHAN, + .end = DMOV_HSUART2_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART2_TX_CRCI, + .end = DMOV_HSUART2_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm2_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm2 = { + .name = "msm_serial_hs", + .id = 1, + .num_resources = ARRAY_SIZE(msm_uart2_dm_resources), + .resource = msm_uart2_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm2_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +#define MSM_I2C_SIZE SZ_4K +#define MSM_I2C_PHYS 0xA9900000 +static struct resource resources_i2c[] = { + { + .start = MSM_I2C_PHYS, + .end = MSM_I2C_PHYS + MSM_I2C_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_PWB_I2C, + .end = INT_PWB_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_i2c = { + .name = "msm_i2c", + .id = 0, + .num_resources = ARRAY_SIZE(resources_i2c), + .resource = resources_i2c, +}; + +#define MSM_HSUSB_PHYS 0xA0800000 +static struct resource resources_hsusb_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 dma_mask = 0xffffffffULL; +struct platform_device msm_device_hsusb_otg = { + .name = "msm_hsusb_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_otg), + .resource = resources_hsusb_otg, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_hsusb_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource resources_gadget_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsusb_peripheral = { + .name = "msm_hsusb_peripheral", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_peripheral), + .resource = resources_hsusb_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct platform_device msm_device_gadget_peripheral = { + .name = "msm_hsusb", + .id = -1, + .num_resources = ARRAY_SIZE(resources_gadget_peripheral), + .resource = resources_gadget_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_hsusb_host[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsusb_host = { + .name = "msm_hsusb_host", + .id = 0, + .num_resources = ARRAY_SIZE(resources_hsusb_host), + .resource = resources_hsusb_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct platform_device *msm_host_devices[] = { + &msm_device_hsusb_host, +}; + +int msm_add_host(unsigned int host, struct msm_usb_host_platform_data *plat) +{ + struct platform_device *pdev; + + pdev = msm_host_devices[host]; + if (!pdev) + return -ENODEV; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#ifdef CONFIG_USB_ANDROID_DIAG +struct usb_diag_platform_data usb_diag_pdata = { + .ch_name = DIAG_LEGACY, + .update_pid_and_serial_num = usb_diag_update_pid_and_serial_num, +}; + +struct platform_device usb_diag_device = { + .name = "usb_diag", + .id = -1, + .dev = { + .platform_data = &usb_diag_pdata, + }, +}; +#endif + +#ifdef CONFIG_USB_F_SERIAL +static struct usb_gadget_fserial_platform_data fserial_pdata = { + .no_ports = 2, +}; + +struct platform_device usb_gadget_fserial_device = { + .name = "usb_fserial", + .id = -1, + .dev = { + .platform_data = &fserial_pdata, + }, +}; +#endif + +struct platform_device asoc_msm_pcm = { + .name = "msm-dsp-audio", + .id = 0, +}; + +struct platform_device asoc_msm_dai0 = { + .name = "msm-codec-dai", + .id = 0, +}; + +struct platform_device asoc_msm_dai1 = { + .name = "msm-cpu-dai", + .id = 0, +}; + +#define MSM_NAND_PHYS 0xA0A00000 +static struct resource resources_nand[] = { + [0] = { + .name = "msm_nand_dmac", + .start = DMOV_NAND_CHAN, + .end = DMOV_NAND_CHAN, + .flags = IORESOURCE_DMA, + }, + [1] = { + .name = "msm_nand_phys", + .start = MSM_NAND_PHYS, + .end = MSM_NAND_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_otg), + .resource = resources_otg, + .dev = { + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct flash_platform_data msm_nand_data = { + .parts = NULL, + .nr_parts = 0, +}; + +struct platform_device msm_device_nand = { + .name = "msm_nand", + .id = -1, + .num_resources = ARRAY_SIZE(resources_nand), + .resource = resources_nand, + .dev = { + .platform_data = &msm_nand_data, + }, +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .end = (resource_size_t)MSM_DMOV_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), +}; + +#define MSM_SDC1_BASE 0xA0400000 +#define MSM_SDC2_BASE 0xA0500000 +#define MSM_SDC3_BASE 0xA0600000 +#define MSM_SDC4_BASE 0xA0700000 +static struct resource resources_sdc1[] = { + { + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC1_0, + .end = INT_SDC1_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc2[] = { + { + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC2_0, + .end = INT_SDC2_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc3[] = { + { + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC3_0, + .end = INT_SDC3_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc4[] = { + { + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC4_0, + .end = INT_SDC4_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device msm_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_sdc1), + .resource = resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc2 = { + .name = "msm_sdcc", + .id = 2, + .num_resources = ARRAY_SIZE(resources_sdc2), + .resource = resources_sdc2, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc3 = { + .name = "msm_sdcc", + .id = 3, + .num_resources = ARRAY_SIZE(resources_sdc3), + .resource = resources_sdc3, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc4 = { + .name = "msm_sdcc", + .id = 4, + .num_resources = ARRAY_SIZE(resources_sdc4), + .resource = resources_sdc4, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct platform_device *msm_sdcc_devices[] __initdata = { + &msm_device_sdc1, + &msm_device_sdc2, + &msm_device_sdc3, + &msm_device_sdc4, +}; + +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (controller < 1 || controller > 4) + return -EINVAL; + + pdev = msm_sdcc_devices[controller-1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#if defined(CONFIG_FB_MSM_MDP40) +#define MDP_BASE 0xA3F00000 +#define PMDH_BASE 0xAD600000 +#define EMDH_BASE 0xAD700000 +#define TVENC_BASE 0xAD400000 +#else +#define MDP_BASE 0xAA200000 +#define PMDH_BASE 0xAA600000 +#define EMDH_BASE 0xAA700000 +#define TVENC_BASE 0xAA400000 +#endif + +static struct resource msm_mdp_resources[] = { + { + .name = "mdp", + .start = MDP_BASE, + .end = MDP_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_MDP, + .end = INT_MDP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_mddi_resources[] = { + { + .name = "pmdh", + .start = PMDH_BASE, + .end = PMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_mddi_ext_resources[] = { + { + .name = "emdh", + .start = EMDH_BASE, + .end = EMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_ebi2_lcd_resources[] = { + { + .name = "base", + .start = 0xa0d00000, + .end = 0xa0d00000 + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd01", + .start = 0x98000000, + .end = 0x98000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd02", + .start = 0x9c000000, + .end = 0x9c000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource msm_tvenc_resources[] = { + { + .name = "tvenc", + .start = TVENC_BASE, + .end = TVENC_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct platform_device msm_mdp_device = { + .name = "mdp", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mdp_resources), + .resource = msm_mdp_resources, +}; + +static struct platform_device msm_mddi_device = { + .name = "mddi", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_resources), + .resource = msm_mddi_resources, +}; + +static struct platform_device msm_mddi_ext_device = { + .name = "mddi_ext", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_ext_resources), + .resource = msm_mddi_ext_resources, +}; + +static struct platform_device msm_ebi2_lcd_device = { + .name = "ebi2_lcd", + .id = 0, + .num_resources = ARRAY_SIZE(msm_ebi2_lcd_resources), + .resource = msm_ebi2_lcd_resources, +}; + +static struct platform_device msm_lcdc_device = { + .name = "lcdc", + .id = 0, +}; + +static struct platform_device msm_tvenc_device = { + .name = "tvenc", + .id = 0, + .num_resources = ARRAY_SIZE(msm_tvenc_resources), + .resource = msm_tvenc_resources, +}; + +/* TSIF begin */ +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + +#define MSM_TSIF_PHYS (0xa0100000) +#define MSM_TSIF_SIZE (0x200) + +static struct resource tsif_resources[] = { + [0] = { + .flags = IORESOURCE_IRQ, + .start = INT_TSIF_IRQ, + .end = INT_TSIF_IRQ, + }, + [1] = { + .flags = IORESOURCE_MEM, + .start = MSM_TSIF_PHYS, + .end = MSM_TSIF_PHYS + MSM_TSIF_SIZE - 1, + }, + [2] = { + .flags = IORESOURCE_DMA, + .start = DMOV_TSIF_CHAN, + .end = DMOV_TSIF_CRCI, + }, +}; + +static void tsif_release(struct device *dev) +{ + dev_info(dev, "release\n"); +} + +struct platform_device msm_device_tsif = { + .name = "msm_tsif", + .id = 0, + .num_resources = ARRAY_SIZE(tsif_resources), + .resource = tsif_resources, + .dev = { + .release = tsif_release, + }, +}; +#endif /* defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) */ +/* TSIF end */ + +#define MSM_TSSC_PHYS 0xAA300000 +static struct resource resources_tssc[] = { + { + .start = MSM_TSSC_PHYS, + .end = MSM_TSSC_PHYS + SZ_4K - 1, + .name = "tssc", + .flags = IORESOURCE_MEM, + }, + { + .start = INT_TCHSCRN1, + .end = INT_TCHSCRN1, + .name = "tssc1", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, + { + .start = INT_TCHSCRN2, + .end = INT_TCHSCRN2, + .name = "tssc2", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, +}; + +struct platform_device msm_device_tssc = { + .name = "msm_touchscreen", + .id = 0, + .num_resources = ARRAY_SIZE(resources_tssc), + .resource = resources_tssc, +}; + +static void __init msm_register_device(struct platform_device *pdev, void *data) +{ + int ret; + + pdev->dev.platform_data = data; + + ret = platform_device_register(pdev); + if (ret) + dev_err(&pdev->dev, + "%s: platform_device_register() failed = %d\n", + __func__, ret); +} + +void __init msm_fb_register_device(char *name, void *data) +{ + if (!strncmp(name, "mdp", 3)) + msm_register_device(&msm_mdp_device, data); + else if (!strncmp(name, "pmdh", 4)) + msm_register_device(&msm_mddi_device, data); + else if (!strncmp(name, "emdh", 4)) + msm_register_device(&msm_mddi_ext_device, data); + else if (!strncmp(name, "ebi2", 4)) + msm_register_device(&msm_ebi2_lcd_device, data); + else if (!strncmp(name, "tvenc", 5)) + msm_register_device(&msm_tvenc_device, data); + else if (!strncmp(name, "lcdc", 4)) + msm_register_device(&msm_lcdc_device, data); + else + printk(KERN_ERR "%s: unknown device! %s\n", __func__, name); +} + +static struct platform_device msm_camera_device = { + .name = "msm_camera", + .id = 0, +}; + +void __init msm_camera_register_device(void *res, uint32_t num, + void *data) +{ + msm_camera_device.num_resources = num; + msm_camera_device.resource = res; + + msm_register_device(&msm_camera_device, data); +} + +static struct resource kgsl_3d0_resources[] = { + { + .name = KGSL_3D0_REG_MEMORY, + .start = 0xA0000000, + .end = 0xA001ffff, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_3D0_IRQ, + .start = INT_GRAPHICS, + .end = INT_GRAPHICS, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_3d0_pdata = { + .pwr_data = { + /* bus_freq has been set to 160000 for power savings. + * OEMs may modify the value at their discretion for performance + * The appropriate maximum replacement for 160000 is: + * msm7x2x_clock_data.max_axi_khz + */ + .pwrlevel = { + { + .gpu_freq = 0, + .bus_freq = 160000000, + }, + }, + .init_level = 0, + .num_levels = 1, + .set_grp_async = NULL, + .idle_timeout = HZ/5, + }, + .clk = { + .name = { + .clk = "grp_clk", + .pclk = "grp_pclk", + }, + }, + .imem_clk_name = { + .clk = "imem_clk", + }, +}; + +struct platform_device msm_kgsl_3d0 = { + .name = "kgsl-3d0", + .id = 0, + .num_resources = ARRAY_SIZE(kgsl_3d0_resources), + .resource = kgsl_3d0_resources, + .dev = { + .platform_data = &kgsl_3d0_pdata, + }, +}; + +struct platform_device *msm_footswitch_devices[] = { + FS_PCOM(FS_GFX3D, "fs_gfx3d"), +}; +unsigned msm_num_footswitch_devices = ARRAY_SIZE(msm_footswitch_devices); diff --git a/arch/arm/mach-msm/devices-msm7x27a.c b/arch/arm/mach-msm/devices-msm7x27a.c new file mode 100644 index 00000000000..88160be14ea --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x27a.c @@ -0,0 +1,699 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "devices.h" +#include "devices-msm7x2xa.h" +#include "footswitch.h" + +/* Address of GSBI blocks */ +#define MSM_GSBI0_PHYS 0xA1200000 +#define MSM_GSBI1_PHYS 0xA1300000 + +/* GSBI QUPe devices */ +#define MSM_GSBI0_QUP_PHYS (MSM_GSBI0_PHYS + 0x80000) +#define MSM_GSBI1_QUP_PHYS (MSM_GSBI1_PHYS + 0x80000) + +static struct resource gsbi0_qup_i2c_resources[] = { + { + .name = "qup_phys_addr", + .start = MSM_GSBI0_QUP_PHYS, + .end = MSM_GSBI0_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI0_PHYS, + .end = MSM_GSBI0_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = INT_PWB_I2C, + .end = INT_PWB_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +/* Use GSBI0 QUP for /dev/i2c-0 */ +struct platform_device msm_gsbi0_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI0_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi0_qup_i2c_resources), + .resource = gsbi0_qup_i2c_resources, +}; + +static struct resource gsbi1_qup_i2c_resources[] = { + { + .name = "qup_phys_addr", + .start = MSM_GSBI1_QUP_PHYS, + .end = MSM_GSBI1_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI1_PHYS, + .end = MSM_GSBI1_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = INT_ARM11_DMA, + .end = INT_ARM11_DMA, + .flags = IORESOURCE_IRQ, + }, +}; + +/* Use GSBI1 QUP for /dev/i2c-1 */ +struct platform_device msm_gsbi1_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI1_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi1_qup_i2c_resources), + .resource = gsbi1_qup_i2c_resources, +}; + +#define MSM_HSUSB_PHYS 0xA0800000 +static struct resource resources_hsusb_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +static u64 dma_mask = 0xffffffffULL; +struct platform_device msm_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_otg), + .resource = resources_hsusb_otg, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_gadget_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_gadget_peripheral = { + .name = "msm_hsusb", + .id = -1, + .num_resources = ARRAY_SIZE(resources_gadget_peripheral), + .resource = resources_gadget_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_hsusb_host[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsusb_host = { + .name = "msm_hsusb_host", + .id = 0, + .num_resources = ARRAY_SIZE(resources_hsusb_host), + .resource = resources_hsusb_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct platform_device *msm_host_devices[] = { + &msm_device_hsusb_host, +}; + +int msm_add_host(unsigned int host, struct msm_usb_host_platform_data *plat) +{ + struct platform_device *pdev; + + pdev = msm_host_devices[host]; + if (!pdev) + return -ENODEV; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +static struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .end = (resource_size_t)MSM_DMOV_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART1_PHYS, + .end = MSM_UART1_PHYS + MSM_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_uart1 = { + .name = "msm_serial", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart1), + .resource = resources_uart1, +}; + +#define MSM_UART1DM_PHYS 0xA0200000 +static struct resource msm_uart1_dm_resources[] = { + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART1DM_IRQ, + .end = INT_UART1DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART1DM_RX, + .end = INT_UART1DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART1_TX_CHAN, + .end = DMOV_HSUART1_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART1_TX_CRCI, + .end = DMOV_HSUART1_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm1_dma_mask = DMA_BIT_MASK(32); +struct platform_device msm_device_uart_dm1 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart1_dm_resources), + .resource = msm_uart1_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm1_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +#define MSM_UART2DM_PHYS 0xA0300000 +static struct resource msm_uart2dm_resources[] = { + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART2DM_IRQ, + .end = INT_UART2DM_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_uart_dm2 = { + .name = "msm_serial_hsl", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart2dm_resources), + .resource = msm_uart2dm_resources, +}; + +#define MSM_NAND_PHYS 0xA0A00000 +#define MSM_NANDC01_PHYS 0xA0A40000 +#define MSM_NANDC10_PHYS 0xA0A80000 +#define MSM_NANDC11_PHYS 0xA0AC0000 +#define EBI2_REG_BASE 0xA0D00000 +static struct resource resources_nand[] = { + [0] = { + .name = "msm_nand_dmac", + .start = DMOV_NAND_CHAN, + .end = DMOV_NAND_CHAN, + .flags = IORESOURCE_DMA, + }, + [1] = { + .name = "msm_nand_phys", + .start = MSM_NAND_PHYS, + .end = MSM_NAND_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, + [2] = { + .name = "msm_nandc01_phys", + .start = MSM_NANDC01_PHYS, + .end = MSM_NANDC01_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, + [3] = { + .name = "msm_nandc10_phys", + .start = MSM_NANDC10_PHYS, + .end = MSM_NANDC10_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, + [4] = { + .name = "msm_nandc11_phys", + .start = MSM_NANDC11_PHYS, + .end = MSM_NANDC11_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, + [5] = { + .name = "ebi2_reg_base", + .start = EBI2_REG_BASE, + .end = EBI2_REG_BASE + 0x60, + .flags = IORESOURCE_MEM, + }, +}; + +struct flash_platform_data msm_nand_data; + +struct platform_device msm_device_nand = { + .name = "msm_nand", + .id = -1, + .num_resources = ARRAY_SIZE(resources_nand), + .resource = resources_nand, + .dev = { + .platform_data = &msm_nand_data, + }, +}; + +#define MSM_SDC1_BASE 0xA0400000 +#define MSM_SDC2_BASE 0xA0500000 +#define MSM_SDC3_BASE 0xA0600000 +#define MSM_SDC4_BASE 0xA0700000 +static struct resource resources_sdc1[] = { + { + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC1_0, + .end = INT_SDC1_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc2[] = { + { + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC2_0, + .end = INT_SDC2_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc3[] = { + { + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC3_0, + .end = INT_SDC3_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc4[] = { + { + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC4_0, + .end = INT_SDC4_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device msm_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_sdc1), + .resource = resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc2 = { + .name = "msm_sdcc", + .id = 2, + .num_resources = ARRAY_SIZE(resources_sdc2), + .resource = resources_sdc2, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc3 = { + .name = "msm_sdcc", + .id = 3, + .num_resources = ARRAY_SIZE(resources_sdc3), + .resource = resources_sdc3, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc4 = { + .name = "msm_sdcc", + .id = 4, + .num_resources = ARRAY_SIZE(resources_sdc4), + .resource = resources_sdc4, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct platform_device *msm_sdcc_devices[] __initdata = { + &msm_device_sdc1, + &msm_device_sdc2, + &msm_device_sdc3, + &msm_device_sdc4, +}; + +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (controller < 1 || controller > 4) + return -EINVAL; + + pdev = msm_sdcc_devices[controller-1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#define MDP_BASE 0xAA200000 +#define MIPI_DSI_HW_BASE 0xA1100000 + +static struct resource msm_mipi_dsi_resources[] = { + { + .name = "mipi_dsi", + .start = MIPI_DSI_HW_BASE, + .end = MIPI_DSI_HW_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_DSI_IRQ, + .end = INT_DSI_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_mipi_dsi_device = { + .name = "mipi_dsi", + .id = 1, + .num_resources = ARRAY_SIZE(msm_mipi_dsi_resources), + .resource = msm_mipi_dsi_resources, +}; + +static struct resource msm_mdp_resources[] = { + { + .name = "mdp", + .start = MDP_BASE, + .end = MDP_BASE + 0x000F1008 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_MDP, + .end = INT_MDP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_mdp_device = { + .name = "mdp", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mdp_resources), + .resource = msm_mdp_resources, +}; + +static struct platform_device msm_lcdc_device = { + .name = "lcdc", + .id = 0, +}; + +static struct resource kgsl_3d0_resources[] = { + { + .name = KGSL_3D0_REG_MEMORY, + .start = 0xA0000000, + .end = 0xA001ffff, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_3D0_IRQ, + .start = INT_GRAPHICS, + .end = INT_GRAPHICS, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_3d0_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 245760000, + .bus_freq = 200000000, + }, + { + .gpu_freq = 133330000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 2, + .set_grp_async = set_grp_xbar_async, + .idle_timeout = HZ/5, + .nap_allowed = false, + }, + .clk = { + .name = { + .clk = "grp_clk", + .pclk = "grp_pclk", + }, + }, + .imem_clk_name = { + .clk = "imem_clk", + .pclk = NULL, + }, + +}; + +struct platform_device msm_kgsl_3d0 = { + .name = "kgsl-3d0", + .id = 0, + .num_resources = ARRAY_SIZE(kgsl_3d0_resources), + .resource = kgsl_3d0_resources, + .dev = { + .platform_data = &kgsl_3d0_pdata, + }, +}; + +void __init msm7x25a_kgsl_3d0_init(void) +{ + if (cpu_is_msm7x25a() || cpu_is_msm7x25aa()) { + kgsl_3d0_pdata.pwr_data.pwrlevel[0].gpu_freq = 133330000; + kgsl_3d0_pdata.pwr_data.pwrlevel[0].bus_freq = 200000000; + kgsl_3d0_pdata.pwr_data.pwrlevel[1].gpu_freq = 96000000; + kgsl_3d0_pdata.pwr_data.pwrlevel[1].bus_freq = 0; + } +} + +static void __init msm_register_device(struct platform_device *pdev, void *data) +{ + int ret; + + pdev->dev.platform_data = data; + + ret = platform_device_register(pdev); + + if (ret) + dev_err(&pdev->dev, + "%s: platform_device_register() failed = %d\n", + __func__, ret); +} + +void __init msm_fb_register_device(char *name, void *data) +{ + if (!strncmp(name, "mdp", 3)) + msm_register_device(&msm_mdp_device, data); + else if (!strncmp(name, "mipi_dsi", 8)) + msm_register_device(&msm_mipi_dsi_device, data); + else if (!strncmp(name, "lcdc", 4)) + msm_register_device(&msm_lcdc_device, data); + else + printk(KERN_ERR "%s: unknown device! %s\n", __func__, name); +} + +#define PERPH_WEB_BLOCK_ADDR (0xA9D00040) +#define PDM0_CTL_OFFSET (0x04) +#define SIZE_8B (0x08) + +static struct resource resources_led[] = { + { + .start = PERPH_WEB_BLOCK_ADDR, + .end = PERPH_WEB_BLOCK_ADDR + (SIZE_8B) - 1, + .name = "led-gpio-pdm", + .flags = IORESOURCE_MEM, + }, +}; + +static struct led_info msm_kpbl_pdm_led_pdata = { + .name = "keyboard-backlight", +}; + +struct platform_device led_pdev = { + .name = "leds-msm-pdm", + /* use pdev id to represent pdm id */ + .id = 0, + .num_resources = ARRAY_SIZE(resources_led), + .resource = resources_led, + .dev = { + .platform_data = &msm_kpbl_pdm_led_pdata, + }, +}; + +static struct msm_acpu_clock_platform_data msm7x2x_clock_data = { + .acpu_switch_time_us = 50, + .max_speed_delta_khz = 400000, + .vdd_switch_time_us = 62, + .max_axi_khz = 200000, +}; + +int __init msm7x2x_misc_init(void) +{ + if (socinfo_init() < 0) + pr_err("%s: socinfo_init() failed!\n", __func__); + + msm_clock_init(msm_clocks_7x27a, msm_num_clocks_7x27a); + msm_acpu_clock_init(&msm7x2x_clock_data); + + return 0; +} + +#ifdef CONFIG_CACHE_L2X0 +static int __init msm7x27x_cache_init(void) +{ + int aux_ctrl = 0; + + /* Way Size 010(0x2) 32KB */ + aux_ctrl = (0x1 << L2X0_AUX_CTRL_SHARE_OVERRIDE_SHIFT) | \ + (0x2 << L2X0_AUX_CTRL_WAY_SIZE_SHIFT) | \ + (0x1 << L2X0_AUX_CTRL_EVNT_MON_BUS_EN_SHIFT); + + l2x0_init(MSM_L2CC_BASE, aux_ctrl, L2X0_AUX_CTRL_MASK); + + return 0; +} +#else +static int __init msm_cache_init(void){ return 0; } +#endif + +void __init msm_common_io_init(void) +{ + msm_map_common_io(); + msm7x27x_cache_init(); +} + +struct platform_device *msm_footswitch_devices[] = { + FS_PCOM(FS_GFX3D, "fs_gfx3d"), +}; +unsigned msm_num_footswitch_devices = ARRAY_SIZE(msm_footswitch_devices); diff --git a/arch/arm/mach-msm/devices-msm7x2xa.h b/arch/arm/mach-msm/devices-msm7x2xa.h new file mode 100644 index 00000000000..d04dfe020c2 --- /dev/null +++ b/arch/arm/mach-msm/devices-msm7x2xa.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __ARCH_ARM_MACH_MSM_DEVICES_MSM7X2XA_H +#define __ARCH_ARM_MACH_MSM_DEVICES_MSM7X2XA_H + +#define MSM_GSBI0_QUP_I2C_BUS_ID 0 +#define MSM_GSBI1_QUP_I2C_BUS_ID 1 + +void __init msm_common_io_init(void); +void __init msm_init_pmic_vibrator(void); +void __init msm7x25a_kgsl_3d0_init(void); +int __init msm7x2x_misc_init(void); +#endif diff --git a/arch/arm/mach-msm/devices-msm7x30.c b/arch/arm/mach-msm/devices-msm7x30.c index 09b4f140382..92f00107e10 100644 --- a/arch/arm/mach-msm/devices-msm7x30.c +++ b/arch/arm/mach-msm/devices-msm7x30.c @@ -15,23 +15,77 @@ #include #include - +#include #include -#include +#include +#include +#include #include #include #include #include +#include #include "devices.h" -#include "smd_private.h" +#include "gpio_hw.h" +#include "footswitch.h" #include -#include "clock-pcom.h" -#include "clock-7x30.h" +#include +#include +#ifdef CONFIG_PMIC8058 +#include +#endif +#include +#include + +/* EBI THERMAL DRIVER */ +static struct resource msm_ebi0_thermal_resources[] = { + { + .start = 0xA8600000, + .end = 0xA86005FF, + .name = "physbase", + .flags = IORESOURCE_MEM + } +}; -#include +struct platform_device msm_ebi0_thermal = { + .name = "msm_popmem-tm", + .id = 0, + .num_resources = 1, + .resource = msm_ebi0_thermal_resources +}; + +static struct resource msm_ebi1_thermal_resources[] = { + { + .start = 0xA8700000, + .end = 0xA87005FF, + .name = "physbase", + .flags = IORESOURCE_MEM + } +}; + +struct platform_device msm_ebi1_thermal = { + .name = "msm_popmem-tm", + .id = 1, + .num_resources = 1, + .resource = msm_ebi1_thermal_resources +}; + + +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART1_PHYS, + .end = MSM_UART1_PHYS + MSM_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; static struct resource resources_uart2[] = { { @@ -47,6 +101,26 @@ static struct resource resources_uart2[] = { }, }; +static struct resource resources_uart3[] = { + { + .start = INT_UART3, + .end = INT_UART3, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART3_PHYS, + .end = MSM_UART3_PHYS + MSM_UART3_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_uart1 = { + .name = "msm_serial", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart1), + .resource = resources_uart1, +}; + struct platform_device msm_device_uart2 = { .name = "msm_serial", .id = 1, @@ -54,15 +128,228 @@ struct platform_device msm_device_uart2 = { .resource = resources_uart2, }; -struct platform_device msm_device_smd = { - .name = "msm_smd", - .id = -1, +struct platform_device msm_device_uart3 = { + .name = "msm_serial", + .id = 2, + .num_resources = ARRAY_SIZE(resources_uart3), + .resource = resources_uart3, }; -static struct resource resources_otg[] = { +#define MSM_UART1DM_PHYS 0xA3300000 +#define MSM_UART2DM_PHYS 0xA3200000 +static struct resource msm_uart1_dm_resources[] = { + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART1DM_IRQ, + .end = INT_UART1DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART1DM_RX, + .end = INT_UART1DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART1_TX_CHAN, + .end = DMOV_HSUART1_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART1_TX_CRCI, + .end = DMOV_HSUART1_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm1_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm1 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart1_dm_resources), + .resource = msm_uart1_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm1_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource msm_uart2_dm_resources[] = { + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART2DM_IRQ, + .end = INT_UART2DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART2DM_RX, + .end = INT_UART2DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART2_TX_CHAN, + .end = DMOV_HSUART2_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART2_TX_CRCI, + .end = DMOV_HSUART2_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm2_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm2 = { + .name = "msm_serial_hs", + .id = 1, + .num_resources = ARRAY_SIZE(msm_uart2_dm_resources), + .resource = msm_uart2_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm2_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +#define MSM_I2C_SIZE SZ_4K +#define MSM_I2C_PHYS 0xACD00000 +#define MSM_I2C_2_PHYS 0xACF00000 +static struct resource resources_i2c_2[] = { + { + .start = MSM_I2C_2_PHYS, + .end = MSM_I2C_2_PHYS + MSM_I2C_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_PWB_I2C_2, + .end = INT_PWB_I2C_2, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_i2c_2 = { + .name = "msm_i2c", + .id = 2, + .num_resources = ARRAY_SIZE(resources_i2c_2), + .resource = resources_i2c_2, +}; + +static struct resource resources_i2c[] = { + { + .start = MSM_I2C_PHYS, + .end = MSM_I2C_PHYS + MSM_I2C_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_PWB_I2C, + .end = INT_PWB_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_i2c = { + .name = "msm_i2c", + .id = 0, + .num_resources = ARRAY_SIZE(resources_i2c), + .resource = resources_i2c, +}; + +#define MSM_QUP_PHYS 0xA8301000 +#define MSM_GSBI_QUP_I2C_PHYS 0xA8300000 +#define MSM_QUP_SIZE SZ_4K +static struct resource resources_qup[] = { + { + .name = "qup_phys_addr", + .start = MSM_QUP_PHYS, + .end = MSM_QUP_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI_QUP_I2C_PHYS, + .end = MSM_GSBI_QUP_I2C_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_in_intr", + .start = INT_PWB_QUP_IN, + .end = INT_PWB_QUP_IN, + .flags = IORESOURCE_IRQ, + }, + { + .name = "qup_out_intr", + .start = INT_PWB_QUP_OUT, + .end = INT_PWB_QUP_OUT, + .flags = IORESOURCE_IRQ, + }, + { + .name = "qup_err_intr", + .start = INT_PWB_QUP_ERR, + .end = INT_PWB_QUP_ERR, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device qup_device_i2c = { + .name = "qup_i2c", + .id = 4, + .num_resources = ARRAY_SIZE(resources_qup), + .resource = resources_qup, +}; + +#ifdef CONFIG_I2C_SSBI +#define MSM_SSBI6_PHYS 0xAD900000 +static struct resource msm_ssbi6_resources[] = { + { + .name = "ssbi_base", + .start = MSM_SSBI6_PHYS, + .end = MSM_SSBI6_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi6 = { + .name = "i2c_ssbi", + .id = 6, + .num_resources = ARRAY_SIZE(msm_ssbi6_resources), + .resource = msm_ssbi6_resources, +}; + +#define MSM_SSBI7_PHYS 0xAC800000 +static struct resource msm_ssbi7_resources[] = { + { + .name = "ssbi_base", + .start = MSM_SSBI7_PHYS, + .end = MSM_SSBI7_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi7 = { + .name = "i2c_ssbi", + .id = 7, + .num_resources = ARRAY_SIZE(msm_ssbi7_resources), + .resource = msm_ssbi7_resources, +}; +#endif /* CONFIG_I2C_SSBI */ + +#define MSM_HSUSB_PHYS 0xA3600000 +static struct resource resources_hsusb_otg[] = { { .start = MSM_HSUSB_PHYS, - .end = MSM_HSUSB_PHYS + MSM_HSUSB_SIZE, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, .flags = IORESOURCE_MEM, }, { @@ -72,20 +359,35 @@ static struct resource resources_otg[] = { }, }; -struct platform_device msm_device_otg = { - .name = "msm_otg", +static u64 dma_mask = 0xffffffffULL; +struct platform_device msm_device_hsusb_otg = { + .name = "msm_hsusb_otg", .id = -1, - .num_resources = ARRAY_SIZE(resources_otg), - .resource = resources_otg, + .num_resources = ARRAY_SIZE(resources_hsusb_otg), + .resource = resources_hsusb_otg, .dev = { - .coherent_dma_mask = 0xffffffff, + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_hsusb_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, }, }; -static struct resource resources_hsusb[] = { +static struct resource resources_gadget_peripheral[] = { { .start = MSM_HSUSB_PHYS, - .end = MSM_HSUSB_PHYS + MSM_HSUSB_SIZE, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, .flags = IORESOURCE_MEM, }, { @@ -95,21 +397,32 @@ static struct resource resources_hsusb[] = { }, }; -struct platform_device msm_device_hsusb = { +struct platform_device msm_device_hsusb_peripheral = { + .name = "msm_hsusb_peripheral", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_peripheral), + .resource = resources_hsusb_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct platform_device msm_device_gadget_peripheral = { .name = "msm_hsusb", .id = -1, - .num_resources = ARRAY_SIZE(resources_hsusb), - .resource = resources_hsusb, + .num_resources = ARRAY_SIZE(resources_gadget_peripheral), + .resource = resources_gadget_peripheral, .dev = { - .coherent_dma_mask = 0xffffffff, + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, }, }; -static u64 dma_mask = 0xffffffffULL; static struct resource resources_hsusb_host[] = { { .start = MSM_HSUSB_PHYS, - .end = MSM_HSUSB_PHYS + MSM_HSUSB_SIZE, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, .flags = IORESOURCE_MEM, }, { @@ -121,90 +434,730 @@ static struct resource resources_hsusb_host[] = { struct platform_device msm_device_hsusb_host = { .name = "msm_hsusb_host", - .id = -1, + .id = 0, .num_resources = ARRAY_SIZE(resources_hsusb_host), .resource = resources_hsusb_host, .dev = { - .dma_mask = &dma_mask, - .coherent_dma_mask = 0xffffffffULL, - }, -}; - -struct clk_lookup msm_clocks_7x30[] = { - CLK_PCOM("adm_clk", ADM_CLK, NULL, 0), - CLK_PCOM("adsp_clk", ADSP_CLK, NULL, 0), - CLK_PCOM("cam_m_clk", CAM_M_CLK, NULL, 0), - CLK_PCOM("camif_pad_pclk", CAMIF_PAD_P_CLK, NULL, OFF), - CLK_PCOM("ce_clk", CE_CLK, NULL, 0), - CLK_PCOM("codec_ssbi_clk", CODEC_SSBI_CLK, NULL, 0), - CLK_PCOM("ebi1_clk", EBI1_CLK, NULL, CLK_MIN), - CLK_PCOM("ecodec_clk", ECODEC_CLK, NULL, 0), - CLK_PCOM("emdh_clk", EMDH_CLK, NULL, OFF | CLK_MINMAX), - CLK_PCOM("emdh_pclk", EMDH_P_CLK, NULL, OFF), - CLK_PCOM("gp_clk", GP_CLK, NULL, 0), - CLK_PCOM("grp_2d_clk", GRP_2D_CLK, NULL, 0), - CLK_PCOM("grp_2d_pclk", GRP_2D_P_CLK, NULL, 0), - CLK_PCOM("grp_clk", GRP_3D_CLK, NULL, 0), - CLK_PCOM("grp_pclk", GRP_3D_P_CLK, NULL, 0), - CLK_7X30S("grp_src_clk", GRP_3D_SRC_CLK, GRP_3D_CLK, NULL, 0), - CLK_PCOM("hdmi_clk", HDMI_CLK, NULL, 0), - CLK_PCOM("imem_clk", IMEM_CLK, NULL, OFF), - CLK_PCOM("jpeg_clk", JPEG_CLK, NULL, OFF), - CLK_PCOM("jpeg_pclk", JPEG_P_CLK, NULL, OFF), - CLK_PCOM("lpa_codec_clk", LPA_CODEC_CLK, NULL, 0), - CLK_PCOM("lpa_core_clk", LPA_CORE_CLK, NULL, 0), - CLK_PCOM("lpa_pclk", LPA_P_CLK, NULL, 0), - CLK_PCOM("mdc_clk", MDC_CLK, NULL, 0), - CLK_PCOM("mddi_clk", PMDH_CLK, NULL, OFF | CLK_MINMAX), - CLK_PCOM("mddi_pclk", PMDH_P_CLK, NULL, 0), - CLK_PCOM("mdp_clk", MDP_CLK, NULL, OFF), - CLK_PCOM("mdp_pclk", MDP_P_CLK, NULL, 0), - CLK_PCOM("mdp_lcdc_pclk_clk", MDP_LCDC_PCLK_CLK, NULL, 0), - CLK_PCOM("mdp_lcdc_pad_pclk_clk", MDP_LCDC_PAD_PCLK_CLK, NULL, 0), - CLK_PCOM("mdp_vsync_clk", MDP_VSYNC_CLK, NULL, 0), - CLK_PCOM("mfc_clk", MFC_CLK, NULL, 0), - CLK_PCOM("mfc_div2_clk", MFC_DIV2_CLK, NULL, 0), - CLK_PCOM("mfc_pclk", MFC_P_CLK, NULL, 0), - CLK_PCOM("mi2s_m_clk", MI2S_M_CLK, NULL, 0), - CLK_PCOM("mi2s_s_clk", MI2S_S_CLK, NULL, 0), - CLK_PCOM("mi2s_codec_rx_m_clk", MI2S_CODEC_RX_M_CLK, NULL, 0), - CLK_PCOM("mi2s_codec_rx_s_clk", MI2S_CODEC_RX_S_CLK, NULL, 0), - CLK_PCOM("mi2s_codec_tx_m_clk", MI2S_CODEC_TX_M_CLK, NULL, 0), - CLK_PCOM("mi2s_codec_tx_s_clk", MI2S_CODEC_TX_S_CLK, NULL, 0), - CLK_PCOM("pbus_clk", PBUS_CLK, NULL, CLK_MIN), - CLK_PCOM("pcm_clk", PCM_CLK, NULL, 0), - CLK_PCOM("rotator_clk", AXI_ROTATOR_CLK, NULL, 0), - CLK_PCOM("rotator_imem_clk", ROTATOR_IMEM_CLK, NULL, OFF), - CLK_PCOM("rotator_pclk", ROTATOR_P_CLK, NULL, OFF), - CLK_PCOM("sdac_clk", SDAC_CLK, NULL, OFF), - CLK_PCOM("spi_clk", SPI_CLK, NULL, 0), - CLK_PCOM("spi_pclk", SPI_P_CLK, NULL, 0), - CLK_7X30S("tv_src_clk", TV_CLK, TV_ENC_CLK, NULL, 0), - CLK_PCOM("tv_dac_clk", TV_DAC_CLK, NULL, 0), - CLK_PCOM("tv_enc_clk", TV_ENC_CLK, NULL, 0), - CLK_PCOM("uart_clk", UART2_CLK, "msm_serial.1", 0), - CLK_PCOM("usb_phy_clk", USB_PHY_CLK, NULL, 0), - CLK_PCOM("usb_hs_clk", USB_HS_CLK, NULL, OFF), - CLK_PCOM("usb_hs_pclk", USB_HS_P_CLK, NULL, OFF), - CLK_PCOM("usb_hs_core_clk", USB_HS_CORE_CLK, NULL, OFF), - CLK_PCOM("usb_hs2_clk", USB_HS2_CLK, NULL, OFF), - CLK_PCOM("usb_hs2_pclk", USB_HS2_P_CLK, NULL, OFF), - CLK_PCOM("usb_hs2_core_clk", USB_HS2_CORE_CLK, NULL, OFF), - CLK_PCOM("usb_hs3_clk", USB_HS3_CLK, NULL, OFF), - CLK_PCOM("usb_hs3_pclk", USB_HS3_P_CLK, NULL, OFF), - CLK_PCOM("usb_hs3_core_clk", USB_HS3_CORE_CLK, NULL, OFF), - CLK_PCOM("vdc_clk", VDC_CLK, NULL, OFF | CLK_MIN), - CLK_PCOM("vfe_camif_clk", VFE_CAMIF_CLK, NULL, 0), - CLK_PCOM("vfe_clk", VFE_CLK, NULL, 0), - CLK_PCOM("vfe_mdc_clk", VFE_MDC_CLK, NULL, 0), - CLK_PCOM("vfe_pclk", VFE_P_CLK, NULL, OFF), - CLK_PCOM("vpe_clk", VPE_CLK, NULL, 0), - - /* 7x30 v2 hardware only. */ - CLK_PCOM("csi_clk", CSI0_CLK, NULL, 0), - CLK_PCOM("csi_pclk", CSI0_P_CLK, NULL, 0), - CLK_PCOM("csi_vfe_clk", CSI0_VFE_CLK, NULL, 0), -}; - -unsigned msm_num_clocks_7x30 = ARRAY_SIZE(msm_clocks_7x30); + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct platform_device *msm_host_devices[] = { + &msm_device_hsusb_host, +}; + +int msm_add_host(unsigned int host, struct msm_usb_host_platform_data *plat) +{ + struct platform_device *pdev; + + pdev = msm_host_devices[host]; + if (!pdev) + return -ENODEV; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +struct platform_device asoc_msm_pcm = { + .name = "msm-dsp-audio", + .id = 0, +}; + +struct platform_device asoc_msm_dai0 = { + .name = "msm-codec-dai", + .id = 0, +}; + +struct platform_device asoc_msm_dai1 = { + .name = "msm-cpu-dai", + .id = 0, +}; + +#if defined (CONFIG_SND_MSM_MVS_DAI_SOC) +struct platform_device asoc_msm_mvs = { + .name = "msm-mvs-audio", + .id = 0, +}; + +struct platform_device asoc_mvs_dai0 = { + .name = "mvs-codec-dai", + .id = 0, +}; + +struct platform_device asoc_mvs_dai1 = { + .name = "mvs-cpu-dai", + .id = 0, +}; +#endif + +#define MSM_NAND_PHYS 0xA0200000 +#define MSM_NANDC01_PHYS 0xA0240000 +#define MSM_NANDC10_PHYS 0xA0280000 +#define MSM_NANDC11_PHYS 0xA02C0000 +#define EBI2_REG_BASE 0xA0000000 +static struct resource resources_nand[] = { + [0] = { + .name = "msm_nand_dmac", + .start = DMOV_NAND_CHAN, + .end = DMOV_NAND_CHAN, + .flags = IORESOURCE_DMA, + }, + [1] = { + .name = "msm_nand_phys", + .start = MSM_NAND_PHYS, + .end = MSM_NAND_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, + [2] = { + .name = "msm_nandc01_phys", + .start = MSM_NANDC01_PHYS, + .end = MSM_NANDC01_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, + [3] = { + .name = "msm_nandc10_phys", + .start = MSM_NANDC10_PHYS, + .end = MSM_NANDC10_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, + [4] = { + .name = "msm_nandc11_phys", + .start = MSM_NANDC11_PHYS, + .end = MSM_NANDC11_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, + [5] = { + .name = "ebi2_reg_base", + .start = EBI2_REG_BASE, + .end = EBI2_REG_BASE + 0x60, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_otg[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, + }, + { + .name = "vbus_on", + .start = PM8058_CHGVAL_IRQ(PMIC8058_IRQ_BASE), + .end = PM8058_CHGVAL_IRQ(PMIC8058_IRQ_BASE), + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_otg), + .resource = resources_otg, + .dev = { + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct flash_platform_data msm_nand_data = { + .parts = NULL, + .nr_parts = 0, + .interleave = 0, +}; + +struct platform_device msm_device_nand = { + .name = "msm_nand", + .id = -1, + .num_resources = ARRAY_SIZE(resources_nand), + .resource = resources_nand, + .dev = { + .platform_data = &msm_nand_data, + }, +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +struct resource msm_dmov_resource[] = { + { + .start = INT_ADM_AARM, + .end = (resource_size_t)MSM_DMOV_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), +}; + +#define MSM_SDC1_BASE 0xA0400000 +#define MSM_SDC2_BASE 0xA0500000 +#define MSM_SDC3_BASE 0xA3000000 +#define MSM_SDC4_BASE 0xA3100000 +static struct resource resources_sdc1[] = { + { + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC1_0, + .end = INT_SDC1_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc2[] = { + { + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC2_0, + .end = INT_SDC2_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc3[] = { + { + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC3_0, + .end = INT_SDC3_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +static struct resource resources_sdc4[] = { + { + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_SDC4_0, + .end = INT_SDC4_1, + .flags = IORESOURCE_IRQ, + }, + { + .start = 8, + .end = 8, + .flags = IORESOURCE_DMA, + }, +}; + +struct platform_device msm_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_sdc1), + .resource = resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc2 = { + .name = "msm_sdcc", + .id = 2, + .num_resources = ARRAY_SIZE(resources_sdc2), + .resource = resources_sdc2, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc3 = { + .name = "msm_sdcc", + .id = 3, + .num_resources = ARRAY_SIZE(resources_sdc3), + .resource = resources_sdc3, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc4 = { + .name = "msm_sdcc", + .id = 4, + .num_resources = ARRAY_SIZE(resources_sdc4), + .resource = resources_sdc4, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct platform_device *msm_sdcc_devices[] __initdata = { + &msm_device_sdc1, + &msm_device_sdc2, + &msm_device_sdc3, + &msm_device_sdc4, +}; + +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (controller < 1 || controller > 4) + return -EINVAL; + + pdev = msm_sdcc_devices[controller-1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +static struct resource msm_vidc_720p_resources[] = { + { + .start = 0xA3B00000, + .end = 0xA3B00000 + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_MFC720, + .end = INT_MFC720, + .flags = IORESOURCE_IRQ, + }, +}; +struct msm_vidc_platform_data vidc_platform_data = { + .memtype = MEMTYPE_EBI0 +}; + +struct platform_device msm_device_vidc_720p = { + .name = "msm_vidc", + .id = 0, + .num_resources = ARRAY_SIZE(msm_vidc_720p_resources), + .resource = msm_vidc_720p_resources, + .dev = { + .platform_data = &vidc_platform_data, + }, +}; + +#if defined(CONFIG_FB_MSM_MDP40) +#define MDP_BASE 0xA3F00000 +#define PMDH_BASE 0xAD600000 +#define EMDH_BASE 0xAD700000 +#define TVENC_BASE 0xAD400000 +#else +#define MDP_BASE 0xAA200000 +#define PMDH_BASE 0xAA600000 +#define EMDH_BASE 0xAA700000 +#define TVENC_BASE 0xAA400000 +#endif + +static struct resource msm_mdp_resources[] = { + { + .name = "mdp", + .start = MDP_BASE, + .end = MDP_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_MDP, + .end = INT_MDP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_mddi_resources[] = { + { + .name = "pmdh", + .start = PMDH_BASE, + .end = PMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_mddi_ext_resources[] = { + { + .name = "emdh", + .start = EMDH_BASE, + .end = EMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_ebi2_lcd_resources[] = { + { + .name = "base", + .start = 0xa0d00000, + .end = 0xa0d00000 + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd01", + .start = 0x98000000, + .end = 0x98000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd02", + .start = 0x9c000000, + .end = 0x9c000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource msm_tvenc_resources[] = { + { + .name = "tvenc", + .start = TVENC_BASE, + .end = TVENC_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +#ifdef CONFIG_FB_MSM_TVOUT +static struct resource tvout_device_resources[] = { + { + .name = "tvout_device_irq", + .start = INT_TV_ENC, + .end = INT_TV_ENC, + .flags = IORESOURCE_IRQ, + }, +}; +#endif + +static struct platform_device msm_mdp_device = { + .name = "mdp", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mdp_resources), + .resource = msm_mdp_resources, +}; + +static struct platform_device msm_mddi_device = { + .name = "mddi", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_resources), + .resource = msm_mddi_resources, +}; + +static struct platform_device msm_mddi_ext_device = { + .name = "mddi_ext", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_ext_resources), + .resource = msm_mddi_ext_resources, +}; + +static struct platform_device msm_ebi2_lcd_device = { + .name = "ebi2_lcd", + .id = 0, + .num_resources = ARRAY_SIZE(msm_ebi2_lcd_resources), + .resource = msm_ebi2_lcd_resources, +}; + +static struct platform_device msm_lcdc_device = { + .name = "lcdc", + .id = 0, +}; + +static struct platform_device msm_dtv_device = { + .name = "dtv", + .id = 0, +}; + +static struct platform_device msm_tvenc_device = { + .name = "tvenc", + .id = 0, + .num_resources = ARRAY_SIZE(msm_tvenc_resources), + .resource = msm_tvenc_resources, +}; + +#ifdef CONFIG_FB_MSM_TVOUT +static struct platform_device tvout_msm_device = { + .name = "tvout_device", + .id = 0, + .num_resources = ARRAY_SIZE(tvout_device_resources), + .resource = tvout_device_resources, +}; +#endif + +/* TSIF begin */ +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + +#define MSM_TSIF_PHYS (0xa3400000) +#define MSM_TSIF_SIZE (0x200) + +static struct resource tsif_resources[] = { + [0] = { + .flags = IORESOURCE_IRQ, + .start = INT_TSIF, + .end = INT_TSIF, + }, + [1] = { + .flags = IORESOURCE_MEM, + .start = MSM_TSIF_PHYS, + .end = MSM_TSIF_PHYS + MSM_TSIF_SIZE - 1, + }, + [2] = { + .flags = IORESOURCE_DMA, + .start = DMOV_TSIF_CHAN, + .end = DMOV_TSIF_CRCI, + }, +}; + +static void tsif_release(struct device *dev) +{ + dev_info(dev, "release\n"); +} + +struct platform_device msm_device_tsif = { + .name = "msm_tsif", + .id = 0, + .num_resources = ARRAY_SIZE(tsif_resources), + .resource = tsif_resources, + .dev = { + .release = tsif_release, + }, +}; +#endif /* defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) */ +/* TSIF end */ + + + +#ifdef CONFIG_MSM_ROTATOR +static struct resource resources_msm_rotator[] = { + { + .start = 0xA3E00000, + .end = 0xA3F00000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_ROTATOR, + .end = INT_ROTATOR, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct msm_rot_clocks rotator_clocks[] = { + { + .clk_name = "rotator_clk", + .clk_type = ROTATOR_CORE_CLK, + .clk_rate = 0, + }, + { + .clk_name = "rotator_pclk", + .clk_type = ROTATOR_PCLK, + .clk_rate = 0, + }, + { + .clk_name = "rotator_imem_clk", + .clk_type = ROTATOR_IMEM_CLK, + .clk_rate = 0, + }, +}; + +static struct msm_rotator_platform_data rotator_pdata = { + .number_of_clocks = ARRAY_SIZE(rotator_clocks), + .hardware_version_number = 0x1000303, + .rotator_clks = rotator_clocks, + .regulator_name = "fs_rot", +}; + +struct platform_device msm_rotator_device = { + .name = "msm_rotator", + .id = 0, + .num_resources = ARRAY_SIZE(resources_msm_rotator), + .resource = resources_msm_rotator, + .dev = { + .platform_data = &rotator_pdata, + }, +}; +#endif + +static void __init msm_register_device(struct platform_device *pdev, void *data) +{ + int ret; + + pdev->dev.platform_data = data; + + ret = platform_device_register(pdev); + if (ret) + dev_err(&pdev->dev, + "%s: platform_device_register() failed = %d\n", + __func__, ret); +} + +void __init msm_fb_register_device(char *name, void *data) +{ + if (!strncmp(name, "mdp", 3)) + msm_register_device(&msm_mdp_device, data); + else if (!strncmp(name, "pmdh", 4)) + msm_register_device(&msm_mddi_device, data); + else if (!strncmp(name, "emdh", 4)) + msm_register_device(&msm_mddi_ext_device, data); + else if (!strncmp(name, "ebi2", 4)) + msm_register_device(&msm_ebi2_lcd_device, data); + else if (!strncmp(name, "tvenc", 5)) + msm_register_device(&msm_tvenc_device, data); + else if (!strncmp(name, "lcdc", 4)) + msm_register_device(&msm_lcdc_device, data); + else if (!strncmp(name, "dtv", 3)) + msm_register_device(&msm_dtv_device, data); +#ifdef CONFIG_FB_MSM_TVOUT + else if (!strncmp(name, "tvout_device", 12)) + msm_register_device(&tvout_msm_device, data); +#endif + else + printk(KERN_ERR "%s: unknown device! %s\n", __func__, name); +} + +static struct platform_device msm_camera_device = { + .name = "msm_camera", + .id = 0, +}; + +void __init msm_camera_register_device(void *res, uint32_t num, + void *data) +{ + msm_camera_device.num_resources = num; + msm_camera_device.resource = res; + + msm_register_device(&msm_camera_device, data); +} + +struct resource kgsl_3d0_resources[] = { + { + .name = KGSL_3D0_REG_MEMORY, + .start = 0xA3500000, /* 3D GRP address */ + .end = 0xA351ffff, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_3D0_IRQ, + .start = INT_GRP_3D, + .end = INT_GRP_3D, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_3d0_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 245760000, + .bus_freq = 192000000, + }, + { + .gpu_freq = 192000000, + .bus_freq = 152000000, + }, + { + .gpu_freq = 192000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 3, + .set_grp_async = set_grp3d_async, + .idle_timeout = HZ/20, + .nap_allowed = true, + }, + .clk = { + .name = { + .clk = "grp_clk", + .pclk = "grp_pclk", + }, + }, + .imem_clk_name = { + .clk = "imem_clk", + .pclk = NULL, + }, +}; + +struct platform_device msm_kgsl_3d0 = { + .name = "kgsl-3d0", + .id = 0, + .num_resources = ARRAY_SIZE(kgsl_3d0_resources), + .resource = kgsl_3d0_resources, + .dev = { + .platform_data = &kgsl_3d0_pdata, + }, +}; + +static struct resource kgsl_2d0_resources[] = { + { + .name = KGSL_2D0_REG_MEMORY, + .start = 0xA3900000, /* Z180 base address */ + .end = 0xA3900FFF, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_2D0_IRQ, + .start = INT_GRP_2D, + .end = INT_GRP_2D, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_2d0_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 0, + .bus_freq = 192000000, + }, + }, + .init_level = 0, + .num_levels = 1, + /* HW workaround, run Z180 SYNC @ 192 MHZ */ + .set_grp_async = NULL, + .idle_timeout = HZ/10, + .nap_allowed = true, + }, + .clk = { + .name = { + .clk = "grp_2d_clk", + .pclk = "grp_2d_pclk", + }, + }, +}; + +struct platform_device msm_kgsl_2d0 = { + .name = "kgsl-2d0", + .id = 0, + .num_resources = ARRAY_SIZE(kgsl_2d0_resources), + .resource = kgsl_2d0_resources, + .dev = { + .platform_data = &kgsl_2d0_pdata, + }, +}; + +struct platform_device *msm_footswitch_devices[] = { + FS_PCOM(FS_GFX2D0, "fs_gfx2d0"), + FS_PCOM(FS_GFX3D, "fs_gfx3d"), + FS_PCOM(FS_MDP, "fs_mdp"), + FS_PCOM(FS_MFC, "fs_mfc"), + FS_PCOM(FS_ROT, "fs_rot"), + FS_PCOM(FS_VFE, "fs_vfe"), + FS_PCOM(FS_VPE, "fs_vpe"), +}; +unsigned msm_num_footswitch_devices = ARRAY_SIZE(msm_footswitch_devices); diff --git a/arch/arm/mach-msm/devices-msm8960.c b/arch/arm/mach-msm/devices-msm8960.c deleted file mode 100644 index d9e1f26475d..00000000000 --- a/arch/arm/mach-msm/devices-msm8960.c +++ /dev/null @@ -1,85 +0,0 @@ -/* Copyright (c) 2011, Code Aurora Forum. 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 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -#include -#include - -#include -#include -#include - -#include "devices.h" - -#define MSM_GSBI2_PHYS 0x16100000 -#define MSM_UART2DM_PHYS (MSM_GSBI2_PHYS + 0x40000) - -#define MSM_GSBI5_PHYS 0x16400000 -#define MSM_UART5DM_PHYS (MSM_GSBI5_PHYS + 0x40000) - -static struct resource resources_uart_gsbi2[] = { - { - .start = GSBI2_UARTDM_IRQ, - .end = GSBI2_UARTDM_IRQ, - .flags = IORESOURCE_IRQ, - }, - { - .start = MSM_UART2DM_PHYS, - .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, - .name = "uart_resource", - .flags = IORESOURCE_MEM, - }, - { - .start = MSM_GSBI2_PHYS, - .end = MSM_GSBI2_PHYS + PAGE_SIZE - 1, - .name = "gsbi_resource", - .flags = IORESOURCE_MEM, - }, -}; - -struct platform_device msm8960_device_uart_gsbi2 = { - .name = "msm_serial", - .id = 0, - .num_resources = ARRAY_SIZE(resources_uart_gsbi2), - .resource = resources_uart_gsbi2, -}; - -static struct resource resources_uart_gsbi5[] = { - { - .start = GSBI5_UARTDM_IRQ, - .end = GSBI5_UARTDM_IRQ, - .flags = IORESOURCE_IRQ, - }, - { - .start = MSM_UART5DM_PHYS, - .end = MSM_UART5DM_PHYS + PAGE_SIZE - 1, - .name = "uart_resource", - .flags = IORESOURCE_MEM, - }, - { - .start = MSM_GSBI5_PHYS, - .end = MSM_GSBI5_PHYS + PAGE_SIZE - 1, - .name = "gsbi_resource", - .flags = IORESOURCE_MEM, - }, -}; - -struct platform_device msm8960_device_uart_gsbi5 = { - .name = "msm_serial", - .id = 0, - .num_resources = ARRAY_SIZE(resources_uart_gsbi5), - .resource = resources_uart_gsbi5, -}; diff --git a/arch/arm/mach-msm/devices-msm8x60.c b/arch/arm/mach-msm/devices-msm8x60.c new file mode 100644 index 00000000000..c6f1cce545b --- /dev/null +++ b/arch/arm/mach-msm/devices-msm8x60.c @@ -0,0 +1,2339 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "footswitch.h" +#include "clock.h" +#include "clock-rpm.h" +#include "clock-voter.h" +#include "devices.h" +#include "devices-msm8x60.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_MSM_DSPS +#include +#endif +#include +#include +#include +#include +#include +#include +#include "rpm_stats.h" +#include "mpm.h" + +/* Address of GSBI blocks */ +#define MSM_GSBI1_PHYS 0x16000000 +#define MSM_GSBI2_PHYS 0x16100000 +#define MSM_GSBI3_PHYS 0x16200000 +#define MSM_GSBI4_PHYS 0x16300000 +#define MSM_GSBI5_PHYS 0x16400000 +#define MSM_GSBI6_PHYS 0x16500000 +#define MSM_GSBI7_PHYS 0x16600000 +#define MSM_GSBI8_PHYS 0x19800000 +#define MSM_GSBI9_PHYS 0x19900000 +#define MSM_GSBI10_PHYS 0x19A00000 +#define MSM_GSBI11_PHYS 0x19B00000 +#define MSM_GSBI12_PHYS 0x19C00000 + +/* GSBI QUPe devices */ +#define MSM_GSBI1_QUP_PHYS 0x16080000 +#define MSM_GSBI2_QUP_PHYS 0x16180000 +#define MSM_GSBI3_QUP_PHYS 0x16280000 +#define MSM_GSBI4_QUP_PHYS 0x16380000 +#define MSM_GSBI5_QUP_PHYS 0x16480000 +#define MSM_GSBI6_QUP_PHYS 0x16580000 +#define MSM_GSBI7_QUP_PHYS 0x16680000 +#define MSM_GSBI8_QUP_PHYS 0x19880000 +#define MSM_GSBI9_QUP_PHYS 0x19980000 +#define MSM_GSBI10_QUP_PHYS 0x19A80000 +#define MSM_GSBI11_QUP_PHYS 0x19B80000 +#define MSM_GSBI12_QUP_PHYS 0x19C80000 + +/* GSBI UART devices */ +#define MSM_UART1DM_PHYS (MSM_GSBI6_PHYS + 0x40000) +#define INT_UART1DM_IRQ GSBI6_UARTDM_IRQ +#define INT_UART2DM_IRQ GSBI12_UARTDM_IRQ +#define MSM_UART2DM_PHYS 0x19C40000 +#define MSM_UART3DM_PHYS (MSM_GSBI3_PHYS + 0x40000) +#define INT_UART3DM_IRQ GSBI3_UARTDM_IRQ +#define TCSR_BASE_PHYS 0x16b00000 + +/* PRNG device */ +#define MSM_PRNG_PHYS 0x16C00000 +#define MSM_UART9DM_PHYS (MSM_GSBI9_PHYS + 0x40000) +#define INT_UART9DM_IRQ GSBI9_UARTDM_IRQ + +static void charm_ap2mdm_kpdpwr_on(void) +{ + gpio_direction_output(AP2MDM_PMIC_RESET_N, 0); + if (machine_is_msm8x60_fusion()) + gpio_direction_output(AP2MDM_KPDPWR_N, 0); + else + gpio_direction_output(AP2MDM_KPDPWR_N, 1); +} + +static void charm_ap2mdm_kpdpwr_off(void) +{ + int i; + + gpio_direction_output(AP2MDM_ERRFATAL, 1); + + for (i = 20; i > 0; i--) { + if (gpio_get_value(MDM2AP_STATUS) == 0) + break; + msleep(100); + } + gpio_direction_output(AP2MDM_ERRFATAL, 0); + + if (i == 0) { + pr_err("%s: MDM2AP_STATUS never went low. Doing a hard reset \ + of the charm modem.\n", __func__); + gpio_direction_output(AP2MDM_PMIC_RESET_N, 1); + /* + * Currently, there is a debounce timer on the charm PMIC. It is + * necessary to hold the AP2MDM_PMIC_RESET low for ~3.5 seconds + * for the reset to fully take place. Sleep here to ensure the + * reset has occured before the function exits. + */ + msleep(4000); + gpio_direction_output(AP2MDM_PMIC_RESET_N, 0); + } +} + +static struct resource charm_resources[] = { + /* MDM2AP_ERRFATAL */ + { + .start = MSM_GPIO_TO_INT(MDM2AP_ERRFATAL), + .end = MSM_GPIO_TO_INT(MDM2AP_ERRFATAL), + .flags = IORESOURCE_IRQ, + }, + /* MDM2AP_STATUS */ + { + .start = MSM_GPIO_TO_INT(MDM2AP_STATUS), + .end = MSM_GPIO_TO_INT(MDM2AP_STATUS), + .flags = IORESOURCE_IRQ, + } +}; + +static struct charm_platform_data mdm_platform_data = { + .charm_modem_on = charm_ap2mdm_kpdpwr_on, + .charm_modem_off = charm_ap2mdm_kpdpwr_off, +}; + +struct platform_device msm_charm_modem = { + .name = "charm_modem", + .id = -1, + .num_resources = ARRAY_SIZE(charm_resources), + .resource = charm_resources, + .dev = { + .platform_data = &mdm_platform_data, + }, +}; + +#ifdef CONFIG_MSM_DSPS +#define GSBI12_DEV (&msm_dsps_device.dev) +#else +#define GSBI12_DEV (&msm_gsbi12_qup_i2c_device.dev) +#endif + +void __init msm8x60_init_irq(void) +{ + unsigned int i; + + msm_mpm_irq_extn_init(); + gic_init(0, GIC_PPI_START, MSM_QGIC_DIST_BASE, (void *)MSM_QGIC_CPU_BASE); + + /* Edge trigger PPIs except AVS_SVICINT and AVS_SVICINTSWDONE */ + writel(0xFFFFD7FF, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); + + /* FIXME: Not installing AVS_SVICINT and AVS_SVICINTSWDONE yet + * as they are configured as level, which does not play nice with + * handle_percpu_irq. + */ + for (i = GIC_PPI_START; i < GIC_SPI_START; i++) { + if (i != AVS_SVICINT && i != AVS_SVICINTSWDONE) + irq_set_handler(i, handle_percpu_irq); + } +} + +static struct resource msm_uart1_dm_resources[] = { + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART1DM_IRQ, + .end = INT_UART1DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + /* GSBI6 is UARTDM1 */ + .start = MSM_GSBI6_PHYS, + .end = MSM_GSBI6_PHYS + 4 - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = DMOV_HSUART1_TX_CHAN, + .end = DMOV_HSUART1_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART1_TX_CRCI, + .end = DMOV_HSUART1_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm1_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm1 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart1_dm_resources), + .resource = msm_uart1_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm1_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource msm_uart3_dm_resources[] = { + { + .start = MSM_UART3DM_PHYS, + .end = MSM_UART3DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART3DM_IRQ, + .end = INT_UART3DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_GSBI3_PHYS, + .end = MSM_GSBI3_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_uart_dm3 = { + .name = "msm_serial_hsl", + .id = 2, + .num_resources = ARRAY_SIZE(msm_uart3_dm_resources), + .resource = msm_uart3_dm_resources, +}; + +static struct resource msm_uart12_dm_resources[] = { + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART2DM_IRQ, + .end = INT_UART2DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + /* GSBI 12 is UARTDM2 */ + .start = MSM_GSBI12_PHYS, + .end = MSM_GSBI12_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_uart_dm12 = { + .name = "msm_serial_hsl", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart12_dm_resources), + .resource = msm_uart12_dm_resources, +}; + +#ifdef CONFIG_MSM_GSBI9_UART +static struct msm_serial_hslite_platform_data uart_gsbi9_pdata = { + .config_gpio = 1, + .uart_tx_gpio = 67, + .uart_rx_gpio = 66, +}; + +static struct resource msm_uart_gsbi9_resources[] = { + { + .start = MSM_UART9DM_PHYS, + .end = MSM_UART9DM_PHYS + PAGE_SIZE - 1, + .name = "uartdm_resource", + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART9DM_IRQ, + .end = INT_UART9DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + /* GSBI 9 is UART_GSBI9 */ + .start = MSM_GSBI9_PHYS, + .end = MSM_GSBI9_PHYS + PAGE_SIZE - 1, + .name = "gsbi_resource", + .flags = IORESOURCE_MEM, + }, +}; +struct platform_device *msm_device_uart_gsbi9; +struct platform_device *msm_add_gsbi9_uart(void) +{ + return platform_device_register_resndata(NULL, "msm_serial_hsl", + 1, msm_uart_gsbi9_resources, + ARRAY_SIZE(msm_uart_gsbi9_resources), + &uart_gsbi9_pdata, + sizeof(uart_gsbi9_pdata)); +} +#endif + +static struct resource gsbi3_qup_i2c_resources[] = { + { + .name = "qup_phys_addr", + .start = MSM_GSBI3_QUP_PHYS, + .end = MSM_GSBI3_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI3_PHYS, + .end = MSM_GSBI3_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI3_QUP_IRQ, + .end = GSBI3_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "i2c_clk", + .start = 44, + .end = 44, + .flags = IORESOURCE_IO, + }, + { + .name = "i2c_sda", + .start = 43, + .end = 43, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource gsbi4_qup_i2c_resources[] = { + { + .name = "qup_phys_addr", + .start = MSM_GSBI4_QUP_PHYS, + .end = MSM_GSBI4_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI4_PHYS, + .end = MSM_GSBI4_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI4_QUP_IRQ, + .end = GSBI4_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource gsbi7_qup_i2c_resources[] = { + { + .name = "qup_phys_addr", + .start = MSM_GSBI7_QUP_PHYS, + .end = MSM_GSBI7_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI7_PHYS, + .end = MSM_GSBI7_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI7_QUP_IRQ, + .end = GSBI7_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "i2c_clk", + .start = 60, + .end = 60, + .flags = IORESOURCE_IO, + }, + { + .name = "i2c_sda", + .start = 59, + .end = 59, + .flags = IORESOURCE_IO, + }, +}; + +static struct resource gsbi8_qup_i2c_resources[] = { + { + .name = "qup_phys_addr", + .start = MSM_GSBI8_QUP_PHYS, + .end = MSM_GSBI8_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI8_PHYS, + .end = MSM_GSBI8_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI8_QUP_IRQ, + .end = GSBI8_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource gsbi9_qup_i2c_resources[] = { + { + .name = "qup_phys_addr", + .start = MSM_GSBI9_QUP_PHYS, + .end = MSM_GSBI9_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI9_PHYS, + .end = MSM_GSBI9_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI9_QUP_IRQ, + .end = GSBI9_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource gsbi12_qup_i2c_resources[] = { + { + .name = "qup_phys_addr", + .start = MSM_GSBI12_QUP_PHYS, + .end = MSM_GSBI12_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI12_PHYS, + .end = MSM_GSBI12_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = GSBI12_QUP_IRQ, + .end = GSBI12_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors grp3d_init_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors grp3d_nominal_low_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 1300000000U, + }, +}; + +static struct msm_bus_vectors grp3d_nominal_high_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 2008000000U, + }, +}; + +static struct msm_bus_vectors grp3d_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_3D, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 2484000000U, + }, +}; + +static struct msm_bus_paths grp3d_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp3d_init_vectors), + grp3d_init_vectors, + }, + { + ARRAY_SIZE(grp3d_nominal_low_vectors), + grp3d_nominal_low_vectors, + }, + { + ARRAY_SIZE(grp3d_nominal_high_vectors), + grp3d_nominal_high_vectors, + }, + { + ARRAY_SIZE(grp3d_max_vectors), + grp3d_max_vectors, + }, +}; + +static struct msm_bus_scale_pdata grp3d_bus_scale_pdata = { + grp3d_bus_scale_usecases, + ARRAY_SIZE(grp3d_bus_scale_usecases), + .name = "grp3d", +}; + +static struct msm_bus_vectors grp2d0_init_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors grp2d0_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 1300000000U, + }, +}; + +static struct msm_bus_paths grp2d0_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp2d0_init_vectors), + grp2d0_init_vectors, + }, + { + ARRAY_SIZE(grp2d0_max_vectors), + grp2d0_max_vectors, + }, +}; + +static struct msm_bus_scale_pdata grp2d0_bus_scale_pdata = { + grp2d0_bus_scale_usecases, + ARRAY_SIZE(grp2d0_bus_scale_usecases), + .name = "grp2d0", +}; + +static struct msm_bus_vectors grp2d1_init_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors grp2d1_max_vectors[] = { + { + .src = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 1300000000U, + }, +}; + +static struct msm_bus_paths grp2d1_bus_scale_usecases[] = { + { + ARRAY_SIZE(grp2d1_init_vectors), + grp2d1_init_vectors, + }, + { + ARRAY_SIZE(grp2d1_max_vectors), + grp2d1_max_vectors, + }, +}; + +static struct msm_bus_scale_pdata grp2d1_bus_scale_pdata = { + grp2d1_bus_scale_usecases, + ARRAY_SIZE(grp2d1_bus_scale_usecases), + .name = "grp2d1", +}; +#endif + +#ifdef CONFIG_HW_RANDOM_MSM +static struct resource rng_resources = { + .flags = IORESOURCE_MEM, + .start = MSM_PRNG_PHYS, + .end = MSM_PRNG_PHYS + SZ_512 - 1, +}; + +struct platform_device msm_device_rng = { + .name = "msm_rng", + .id = 0, + .num_resources = 1, + .resource = &rng_resources, +}; +#endif + +static struct resource kgsl_3d0_resources[] = { + { + .name = KGSL_3D0_REG_MEMORY, + .start = 0x04300000, /* GFX3D address */ + .end = 0x0431ffff, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_3D0_IRQ, + .start = GFX3D_IRQ, + .end = GFX3D_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_3d0_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 266667000, + .bus_freq = 3, + }, + { + .gpu_freq = 228571000, + .bus_freq = 2, + }, + { + .gpu_freq = 200000000, + .bus_freq = 1, + }, + { + .gpu_freq = 27000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 4, + .set_grp_async = NULL, + .idle_timeout = HZ/5, +#ifdef CONFIG_MSM_BUS_SCALING + .nap_allowed = true, + .idle_pass = true, +#endif + }, + .clk = { + .name = { + .clk = "gfx3d_clk", + .pclk = "gfx3d_pclk", + }, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp3d_bus_scale_pdata, +#endif + }, + .imem_clk_name = { + .clk = NULL, + .pclk = "imem_pclk", + }, +}; + +struct platform_device msm_kgsl_3d0 = { + .name = "kgsl-3d0", + .id = 0, + .num_resources = ARRAY_SIZE(kgsl_3d0_resources), + .resource = kgsl_3d0_resources, + .dev = { + .platform_data = &kgsl_3d0_pdata, + }, +}; + +static struct resource kgsl_2d0_resources[] = { + { + .name = KGSL_2D0_REG_MEMORY, + .start = 0x04100000, /* Z180 base address */ + .end = 0x04100FFF, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_2D0_IRQ, + .start = GFX2D0_IRQ, + .end = GFX2D0_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_2d0_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 200000000, + .bus_freq = 1, + }, + { + .gpu_freq = 200000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 2, + .set_grp_async = NULL, + .idle_timeout = HZ/10, +#ifdef CONFIG_MSM_BUS_SCALING + .nap_allowed = true, +#endif + }, + .clk = { + .name = { + /* note: 2d clocks disabled on v1 */ + .clk = "gfx2d0_clk", + .pclk = "gfx2d0_pclk", + }, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp2d0_bus_scale_pdata, +#endif + }, +}; + +struct platform_device msm_kgsl_2d0 = { + .name = "kgsl-2d0", + .id = 0, + .num_resources = ARRAY_SIZE(kgsl_2d0_resources), + .resource = kgsl_2d0_resources, + .dev = { + .platform_data = &kgsl_2d0_pdata, + }, +}; + +static struct resource kgsl_2d1_resources[] = { + { + .name = KGSL_2D1_REG_MEMORY, + .start = 0x04200000, /* Z180 device 1 base address */ + .end = 0x04200FFF, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_2D1_IRQ, + .start = GFX2D1_IRQ, + .end = GFX2D1_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_2d1_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 200000000, + .bus_freq = 1, + }, + { + .gpu_freq = 200000000, + .bus_freq = 0, + }, + }, + .init_level = 0, + .num_levels = 2, + .set_grp_async = NULL, + .idle_timeout = HZ/10, +#ifdef CONFIG_MSM_BUS_SCALING + .nap_allowed = true, +#endif + }, + .clk = { + .name = { + .clk = "gfx2d1_clk", + .pclk = "gfx2d1_pclk", + }, +#ifdef CONFIG_MSM_BUS_SCALING + .bus_scale_table = &grp2d1_bus_scale_pdata, +#endif + }, +}; + +struct platform_device msm_kgsl_2d1 = { + .name = "kgsl-2d1", + .id = 1, + .num_resources = ARRAY_SIZE(kgsl_2d1_resources), + .resource = kgsl_2d1_resources, + .dev = { + .platform_data = &kgsl_2d1_pdata, + }, +}; + +/* + * this a software workaround for not having two distinct board + * files for 8660v1 and 8660v2. 8660v1 has a faulty 2d clock, and + * this workaround detects the cpu version to tell if the kernel is on a + * 8660v1, and should disable the 2d core. it is called from the board file + */ +void __init msm8x60_check_2d_hardware(void) +{ + if ((SOCINFO_VERSION_MAJOR(socinfo_get_version()) == 1) && + (SOCINFO_VERSION_MINOR(socinfo_get_version()) == 0)) { + printk(KERN_WARNING "kgsl: 2D cores disabled on 8660v1\n"); + kgsl_2d0_pdata.clk.name.clk = NULL; + kgsl_2d1_pdata.clk.name.clk = NULL; + } +} + +/* Use GSBI3 QUP for /dev/i2c-0 */ +struct platform_device msm_gsbi3_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI3_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi3_qup_i2c_resources), + .resource = gsbi3_qup_i2c_resources, +}; + +/* Use GSBI4 QUP for /dev/i2c-1 */ +struct platform_device msm_gsbi4_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI4_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi4_qup_i2c_resources), + .resource = gsbi4_qup_i2c_resources, +}; + +/* Use GSBI8 QUP for /dev/i2c-3 */ +struct platform_device msm_gsbi8_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI8_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi8_qup_i2c_resources), + .resource = gsbi8_qup_i2c_resources, +}; + +/* Use GSBI9 QUP for /dev/i2c-2 */ +struct platform_device msm_gsbi9_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI9_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi9_qup_i2c_resources), + .resource = gsbi9_qup_i2c_resources, +}; + +/* Use GSBI7 QUP for /dev/i2c-4 (Marimba) */ +struct platform_device msm_gsbi7_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI7_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi7_qup_i2c_resources), + .resource = gsbi7_qup_i2c_resources, +}; + +/* Use GSBI12 QUP for /dev/i2c-5 (Sensors) */ +struct platform_device msm_gsbi12_qup_i2c_device = { + .name = "qup_i2c", + .id = MSM_GSBI12_QUP_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(gsbi12_qup_i2c_resources), + .resource = gsbi12_qup_i2c_resources, +}; + +#ifdef CONFIG_I2C_SSBI +/* 8058 PMIC SSBI on /dev/i2c-6 */ +#define MSM_SSBI1_PMIC1C_PHYS 0x00500000 +static struct resource msm_ssbi1_resources[] = { + { + .name = "ssbi_base", + .start = MSM_SSBI1_PMIC1C_PHYS, + .end = MSM_SSBI1_PMIC1C_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi1 = { + .name = "i2c_ssbi", + .id = MSM_SSBI1_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(msm_ssbi1_resources), + .resource = msm_ssbi1_resources, +}; + +/* 8901 PMIC SSBI on /dev/i2c-7 */ +#define MSM_SSBI2_PMIC2B_PHYS 0x00C00000 +static struct resource msm_ssbi2_resources[] = { + { + .name = "ssbi_base", + .start = MSM_SSBI2_PMIC2B_PHYS, + .end = MSM_SSBI2_PMIC2B_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi2 = { + .name = "i2c_ssbi", + .id = MSM_SSBI2_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(msm_ssbi2_resources), + .resource = msm_ssbi2_resources, +}; + +/* CODEC SSBI on /dev/i2c-8 */ +#define MSM_SSBI3_PHYS 0x18700000 +static struct resource msm_ssbi3_resources[] = { + { + .name = "ssbi_base", + .start = MSM_SSBI3_PHYS, + .end = MSM_SSBI3_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_device_ssbi3 = { + .name = "i2c_ssbi", + .id = MSM_SSBI3_I2C_BUS_ID, + .num_resources = ARRAY_SIZE(msm_ssbi3_resources), + .resource = msm_ssbi3_resources, +}; +#endif /* CONFIG_I2C_SSBI */ + +static struct resource gsbi1_qup_spi_resources[] = { + { + .name = "spi_base", + .start = MSM_GSBI1_QUP_PHYS, + .end = MSM_GSBI1_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_base", + .start = MSM_GSBI1_PHYS, + .end = MSM_GSBI1_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "spi_irq_in", + .start = GSBI1_QUP_IRQ, + .end = GSBI1_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "spidm_channels", + .start = 5, + .end = 6, + .flags = IORESOURCE_DMA, + }, + { + .name = "spidm_crci", + .start = 8, + .end = 7, + .flags = IORESOURCE_DMA, + }, + { + .name = "spi_clk", + .start = 36, + .end = 36, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_cs", + .start = 35, + .end = 35, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_miso", + .start = 34, + .end = 34, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_mosi", + .start = 33, + .end = 33, + .flags = IORESOURCE_IO, + }, +}; + +/* Use GSBI1 QUP for SPI-0 */ +struct platform_device msm_gsbi1_qup_spi_device = { + .name = "spi_qsd", + .id = 0, + .num_resources = ARRAY_SIZE(gsbi1_qup_spi_resources), + .resource = gsbi1_qup_spi_resources, +}; + + +static struct resource gsbi10_qup_spi_resources[] = { + { + .name = "spi_base", + .start = MSM_GSBI10_QUP_PHYS, + .end = MSM_GSBI10_QUP_PHYS + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_base", + .start = MSM_GSBI10_PHYS, + .end = MSM_GSBI10_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "spi_irq_in", + .start = GSBI10_QUP_IRQ, + .end = GSBI10_QUP_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .name = "spi_clk", + .start = 73, + .end = 73, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_cs", + .start = 72, + .end = 72, + .flags = IORESOURCE_IO, + }, + { + .name = "spi_mosi", + .start = 70, + .end = 70, + .flags = IORESOURCE_IO, + }, +}; + +/* Use GSBI10 QUP for SPI-1 */ +struct platform_device msm_gsbi10_qup_spi_device = { + .name = "spi_qsd", + .id = 1, + .num_resources = ARRAY_SIZE(gsbi10_qup_spi_resources), + .resource = gsbi10_qup_spi_resources, +}; +#define MSM_SDC1_BASE 0x12400000 +#define MSM_SDC1_DML_BASE (MSM_SDC1_BASE + 0x800) +#define MSM_SDC1_BAM_BASE (MSM_SDC1_BASE + 0x2000) +#define MSM_SDC2_BASE 0x12140000 +#define MSM_SDC2_DML_BASE (MSM_SDC2_BASE + 0x800) +#define MSM_SDC2_BAM_BASE (MSM_SDC2_BASE + 0x2000) +#define MSM_SDC3_BASE 0x12180000 +#define MSM_SDC3_DML_BASE (MSM_SDC3_BASE + 0x800) +#define MSM_SDC3_BAM_BASE (MSM_SDC3_BASE + 0x2000) +#define MSM_SDC4_BASE 0x121C0000 +#define MSM_SDC4_DML_BASE (MSM_SDC4_BASE + 0x800) +#define MSM_SDC4_BAM_BASE (MSM_SDC4_BASE + 0x2000) +#define MSM_SDC5_BASE 0x12200000 +#define MSM_SDC5_DML_BASE (MSM_SDC5_BASE + 0x800) +#define MSM_SDC5_BAM_BASE (MSM_SDC5_BASE + 0x2000) + +static struct resource resources_sdc1[] = { + { + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_DML_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = SDC1_IRQ_0, + .end = SDC1_IRQ_0, + .flags = IORESOURCE_IRQ, + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC1_DML_BASE, + .end = MSM_SDC1_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC1_BAM_BASE, + .end = MSM_SDC1_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC1_BAM_IRQ, + .end = SDC1_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#else + { + .start = DMOV_SDC1_CHAN, + .end = DMOV_SDC1_CHAN, + .flags = IORESOURCE_DMA, + }, +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ +}; + +static struct resource resources_sdc2[] = { + { + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_DML_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = SDC2_IRQ_0, + .end = SDC2_IRQ_0, + .flags = IORESOURCE_IRQ, + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC2_DML_BASE, + .end = MSM_SDC2_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC2_BAM_BASE, + .end = MSM_SDC2_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC2_BAM_IRQ, + .end = SDC2_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#else + { + .start = DMOV_SDC2_CHAN, + .end = DMOV_SDC2_CHAN, + .flags = IORESOURCE_DMA, + }, +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ +}; + +static struct resource resources_sdc3[] = { + { + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_DML_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = SDC3_IRQ_0, + .end = SDC3_IRQ_0, + .flags = IORESOURCE_IRQ, + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC3_DML_BASE, + .end = MSM_SDC3_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC3_BAM_BASE, + .end = MSM_SDC3_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC3_BAM_IRQ, + .end = SDC3_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#else + { + .start = DMOV_SDC3_CHAN, + .end = DMOV_SDC3_CHAN, + .flags = IORESOURCE_DMA, + }, +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ +}; + +static struct resource resources_sdc4[] = { + { + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_DML_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = SDC4_IRQ_0, + .end = SDC4_IRQ_0, + .flags = IORESOURCE_IRQ, + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC4_DML_BASE, + .end = MSM_SDC4_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC4_BAM_BASE, + .end = MSM_SDC4_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC4_BAM_IRQ, + .end = SDC4_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#else + { + .start = DMOV_SDC4_CHAN, + .end = DMOV_SDC4_CHAN, + .flags = IORESOURCE_DMA, + }, +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ +}; + +static struct resource resources_sdc5[] = { + { + .start = MSM_SDC5_BASE, + .end = MSM_SDC5_DML_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = SDC5_IRQ_0, + .end = SDC5_IRQ_0, + .flags = IORESOURCE_IRQ, + }, +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT + { + .name = "sdcc_dml_addr", + .start = MSM_SDC5_DML_BASE, + .end = MSM_SDC5_BAM_BASE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_addr", + .start = MSM_SDC5_BAM_BASE, + .end = MSM_SDC5_BAM_BASE + (2 * SZ_4K) - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "sdcc_bam_irq", + .start = SDC5_BAM_IRQ, + .end = SDC5_BAM_IRQ, + .flags = IORESOURCE_IRQ, + }, +#else + { + .start = DMOV_SDC5_CHAN, + .end = DMOV_SDC5_CHAN, + .flags = IORESOURCE_DMA, + }, +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ +}; + +struct platform_device msm_device_sdc1 = { + .name = "msm_sdcc", + .id = 1, + .num_resources = ARRAY_SIZE(resources_sdc1), + .resource = resources_sdc1, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc2 = { + .name = "msm_sdcc", + .id = 2, + .num_resources = ARRAY_SIZE(resources_sdc2), + .resource = resources_sdc2, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc3 = { + .name = "msm_sdcc", + .id = 3, + .num_resources = ARRAY_SIZE(resources_sdc3), + .resource = resources_sdc3, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc4 = { + .name = "msm_sdcc", + .id = 4, + .num_resources = ARRAY_SIZE(resources_sdc4), + .resource = resources_sdc4, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +struct platform_device msm_device_sdc5 = { + .name = "msm_sdcc", + .id = 5, + .num_resources = ARRAY_SIZE(resources_sdc5), + .resource = resources_sdc5, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; + +static struct platform_device *msm_sdcc_devices[] __initdata = { + &msm_device_sdc1, + &msm_device_sdc2, + &msm_device_sdc3, + &msm_device_sdc4, + &msm_device_sdc5, +}; + +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) +{ + struct platform_device *pdev; + + if (controller < 1 || controller > 5) + return -EINVAL; + + pdev = msm_sdcc_devices[controller-1]; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#define MIPI_DSI_HW_BASE 0x04700000 +#define ROTATOR_HW_BASE 0x04E00000 +#define TVENC_HW_BASE 0x04F00000 +#define MDP_HW_BASE 0x05100000 + +static struct resource msm_mipi_dsi_resources[] = { + { + .name = "mipi_dsi", + .start = MIPI_DSI_HW_BASE, + .end = MIPI_DSI_HW_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = DSI_IRQ, + .end = DSI_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_mipi_dsi_device = { + .name = "mipi_dsi", + .id = 1, + .num_resources = ARRAY_SIZE(msm_mipi_dsi_resources), + .resource = msm_mipi_dsi_resources, +}; + +static struct resource msm_mdp_resources[] = { + { + .name = "mdp", + .start = MDP_HW_BASE, + .end = MDP_HW_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_MDP, + .end = INT_MDP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device msm_mdp_device = { + .name = "mdp", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mdp_resources), + .resource = msm_mdp_resources, +}; +#ifdef CONFIG_MSM_ROTATOR +static struct resource resources_msm_rotator[] = { + { + .start = 0x04E00000, + .end = 0x04F00000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = ROT_IRQ, + .end = ROT_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct msm_rot_clocks rotator_clocks[] = { + { + .clk_name = "rot_clk", + .clk_type = ROTATOR_CORE_CLK, + .clk_rate = 160 * 1000 * 1000, + }, + { + .clk_name = "rotator_pclk", + .clk_type = ROTATOR_PCLK, + .clk_rate = 0, + }, +}; + +static struct msm_rotator_platform_data rotator_pdata = { + .number_of_clocks = ARRAY_SIZE(rotator_clocks), + .hardware_version_number = 0x01010307, + .rotator_clks = rotator_clocks, + .regulator_name = "fs_rot", +}; + +struct platform_device msm_rotator_device = { + .name = "msm_rotator", + .id = 0, + .num_resources = ARRAY_SIZE(resources_msm_rotator), + .resource = resources_msm_rotator, + .dev = { + .platform_data = &rotator_pdata, + }, +}; +#endif + + +/* Sensors DSPS platform data */ +#ifdef CONFIG_MSM_DSPS + +#define PPSS_REG_PHYS_BASE 0x12080000 + +#define MHZ (1000*1000) + +static struct dsps_clk_info dsps_clks[] = { + { + .name = "ppss_pclk", + .rate = 0, /* no rate just on/off */ + }, + { + .name = "pmem_clk", + .rate = 0, /* no rate just on/off */ + }, + { + .name = "gsbi_qup_clk", + .rate = 24 * MHZ, /* See clk_tbl_gsbi_qup[] */ + }, + { + .name = "dfab_dsps_clk", + .rate = 64 * MHZ, /* Same rate as USB. */ + } +}; + +static struct dsps_regulator_info dsps_regs[] = { + { + .name = "8058_l5", + .volt = 2850000, /* in uV */ + }, + { + .name = "8058_s3", + .volt = 1800000, /* in uV */ + } +}; + +/* + * Note: GPIOs field is intialized in run-time at the function + * msm8x60_init_dsps(). + */ + +struct msm_dsps_platform_data msm_dsps_pdata = { + .clks = dsps_clks, + .clks_num = ARRAY_SIZE(dsps_clks), + .gpios = NULL, + .gpios_num = 0, + .regs = dsps_regs, + .regs_num = ARRAY_SIZE(dsps_regs), + .signature = DSPS_SIGNATURE, +}; + +static struct resource msm_dsps_resources[] = { + { + .start = PPSS_REG_PHYS_BASE, + .end = PPSS_REG_PHYS_BASE + SZ_8K - 1, + .name = "ppss_reg", + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device msm_dsps_device = { + .name = "msm_dsps", + .id = 0, + .num_resources = ARRAY_SIZE(msm_dsps_resources), + .resource = msm_dsps_resources, + .dev.platform_data = &msm_dsps_pdata, +}; + +#endif /* CONFIG_MSM_DSPS */ + +#ifdef CONFIG_FB_MSM_TVOUT +static struct resource msm_tvenc_resources[] = { + { + .name = "tvenc", + .start = TVENC_HW_BASE, + .end = TVENC_HW_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource tvout_device_resources[] = { + { + .name = "tvout_device_irq", + .start = TV_ENC_IRQ, + .end = TV_ENC_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; +#endif +static void __init msm_register_device(struct platform_device *pdev, void *data) +{ + int ret; + + pdev->dev.platform_data = data; + + ret = platform_device_register(pdev); + if (ret) + dev_err(&pdev->dev, + "%s: platform_device_register() failed = %d\n", + __func__, ret); +} + +static struct platform_device msm_lcdc_device = { + .name = "lcdc", + .id = 0, +}; + +#ifdef CONFIG_FB_MSM_TVOUT +static struct platform_device msm_tvenc_device = { + .name = "tvenc", + .id = 0, + .num_resources = ARRAY_SIZE(msm_tvenc_resources), + .resource = msm_tvenc_resources, +}; + +static struct platform_device msm_tvout_device = { + .name = "tvout_device", + .id = 0, + .num_resources = ARRAY_SIZE(tvout_device_resources), + .resource = tvout_device_resources, +}; +#endif + +#ifdef CONFIG_MSM_BUS_SCALING +static struct platform_device msm_dtv_device = { + .name = "dtv", + .id = 0, +}; +#endif + +void __init msm_fb_register_device(char *name, void *data) +{ + if (!strncmp(name, "mdp", 3)) + msm_register_device(&msm_mdp_device, data); + else if (!strncmp(name, "lcdc", 4)) + msm_register_device(&msm_lcdc_device, data); + else if (!strncmp(name, "mipi_dsi", 8)) + msm_register_device(&msm_mipi_dsi_device, data); +#ifdef CONFIG_FB_MSM_TVOUT + else if (!strncmp(name, "tvenc", 5)) + msm_register_device(&msm_tvenc_device, data); + else if (!strncmp(name, "tvout_device", 12)) + msm_register_device(&msm_tvout_device, data); +#endif +#ifdef CONFIG_MSM_BUS_SCALING + else if (!strncmp(name, "dtv", 3)) + msm_register_device(&msm_dtv_device, data); +#endif + else + printk(KERN_ERR "%s: unknown device! %s\n", __func__, name); +} + +static struct resource resources_otg[] = { + { + .start = 0x12500000, + .end = 0x12500000 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_otg), + .resource = resources_otg, +}; + +static u64 dma_mask = 0xffffffffULL; +struct platform_device msm_device_gadget_peripheral = { + .name = "msm_hsusb", + .id = -1, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; +#ifdef CONFIG_USB_EHCI_MSM_72K +static struct resource resources_hsusb_host[] = { + { + .start = 0x12500000, + .end = 0x12500000 + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = USB1_HS_IRQ, + .end = USB1_HS_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsusb_host = { + .name = "msm_hsusb_host", + .id = 0, + .num_resources = ARRAY_SIZE(resources_hsusb_host), + .resource = resources_hsusb_host, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct platform_device *msm_host_devices[] = { + &msm_device_hsusb_host, +}; + +int msm_add_host(unsigned int host, struct msm_usb_host_platform_data *plat) +{ + struct platform_device *pdev; + + pdev = msm_host_devices[host]; + if (!pdev) + return -ENODEV; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} +#endif + +#define MSM_TSIF0_PHYS (0x18200000) +#define MSM_TSIF1_PHYS (0x18201000) +#define MSM_TSIF_SIZE (0x200) +#define TCSR_ADM_0_A_CRCI_MUX_SEL 0x0070 + +#define TSIF_0_CLK GPIO_CFG(93, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_0_EN GPIO_CFG(94, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_0_DATA GPIO_CFG(95, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_0_SYNC GPIO_CFG(96, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_1_CLK GPIO_CFG(97, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_1_EN GPIO_CFG(98, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_1_DATA GPIO_CFG(99, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) +#define TSIF_1_SYNC GPIO_CFG(100, 1, GPIO_CFG_INPUT, \ + GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA) + +static const struct msm_gpio tsif0_gpios[] = { + { .gpio_cfg = TSIF_0_CLK, .label = "tsif_clk", }, + { .gpio_cfg = TSIF_0_EN, .label = "tsif_en", }, + { .gpio_cfg = TSIF_0_DATA, .label = "tsif_data", }, + { .gpio_cfg = TSIF_0_SYNC, .label = "tsif_sync", }, +}; + +static const struct msm_gpio tsif1_gpios[] = { + { .gpio_cfg = TSIF_1_CLK, .label = "tsif_clk", }, + { .gpio_cfg = TSIF_1_EN, .label = "tsif_en", }, + { .gpio_cfg = TSIF_1_DATA, .label = "tsif_data", }, + { .gpio_cfg = TSIF_1_SYNC, .label = "tsif_sync", }, +}; + +static void tsif_release(struct device *dev) +{ +} + +static void tsif_init1(struct msm_tsif_platform_data *data) +{ + int val; + + /* configure mux to use correct tsif instance */ + val = secure_readl(MSM_TCSR_BASE + TCSR_ADM_0_A_CRCI_MUX_SEL); + val |= 0x80000000; + secure_writel(val, MSM_TCSR_BASE + TCSR_ADM_0_A_CRCI_MUX_SEL); +} + +struct msm_tsif_platform_data tsif1_platform_data = { + .num_gpios = ARRAY_SIZE(tsif1_gpios), + .gpios = tsif1_gpios, + .tsif_pclk = "tsif_pclk", + .tsif_ref_clk = "tsif_ref_clk", + .init = tsif_init1 +}; + +struct resource tsif1_resources[] = { + [0] = { + .flags = IORESOURCE_IRQ, + .start = TSIF2_IRQ, + .end = TSIF2_IRQ, + }, + [1] = { + .flags = IORESOURCE_MEM, + .start = MSM_TSIF1_PHYS, + .end = MSM_TSIF1_PHYS + MSM_TSIF_SIZE - 1, + }, + [2] = { + .flags = IORESOURCE_DMA, + .start = DMOV_TSIF_CHAN, + .end = DMOV_TSIF_CRCI, + }, +}; + +static void tsif_init0(struct msm_tsif_platform_data *data) +{ + int val; + + /* configure mux to use correct tsif instance */ + val = secure_readl(MSM_TCSR_BASE + TCSR_ADM_0_A_CRCI_MUX_SEL); + val &= 0x7FFFFFFF; + secure_writel(val, MSM_TCSR_BASE + TCSR_ADM_0_A_CRCI_MUX_SEL); +} + +struct msm_tsif_platform_data tsif0_platform_data = { + .num_gpios = ARRAY_SIZE(tsif0_gpios), + .gpios = tsif0_gpios, + .tsif_pclk = "tsif_pclk", + .tsif_ref_clk = "tsif_ref_clk", + .init = tsif_init0 +}; +struct resource tsif0_resources[] = { + [0] = { + .flags = IORESOURCE_IRQ, + .start = TSIF1_IRQ, + .end = TSIF1_IRQ, + }, + [1] = { + .flags = IORESOURCE_MEM, + .start = MSM_TSIF0_PHYS, + .end = MSM_TSIF0_PHYS + MSM_TSIF_SIZE - 1, + }, + [2] = { + .flags = IORESOURCE_DMA, + .start = DMOV_TSIF_CHAN, + .end = DMOV_TSIF_CRCI, + }, +}; + +struct platform_device msm_device_tsif[2] = { + { + .name = "msm_tsif", + .id = 0, + .num_resources = ARRAY_SIZE(tsif0_resources), + .resource = tsif0_resources, + .dev = { + .release = tsif_release, + .platform_data = &tsif0_platform_data + }, + }, + { + .name = "msm_tsif", + .id = 1, + .num_resources = ARRAY_SIZE(tsif1_resources), + .resource = tsif1_resources, + .dev = { + .release = tsif_release, + .platform_data = &tsif1_platform_data + }, + } +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +struct resource msm_dmov_resource_adm0[] = { + { + .start = INT_ADM0_AARM, + .end = (resource_size_t)MSM_DMOV_ADM0_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct resource msm_dmov_resource_adm1[] = { + { + .start = INT_ADM1_AARM, + .end = (resource_size_t)MSM_DMOV_ADM1_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_dmov_adm0 = { + .name = "msm_dmov", + .id = 0, + .resource = msm_dmov_resource_adm0, + .num_resources = ARRAY_SIZE(msm_dmov_resource_adm0), +}; + +struct platform_device msm_device_dmov_adm1 = { + .name = "msm_dmov", + .id = 1, + .resource = msm_dmov_resource_adm1, + .num_resources = ARRAY_SIZE(msm_dmov_resource_adm1), +}; + +/* MSM Video core device */ +#ifdef CONFIG_MSM_BUS_SCALING +static struct msm_bus_vectors vidc_init_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 0, + .ib = 0, + }, +}; +static struct msm_bus_vectors vidc_venc_vga_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 54525952, + .ib = 436207616, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 72351744, + .ib = 289406976, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 500000, + .ib = 1000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 500000, + .ib = 1000000, + }, +}; +static struct msm_bus_vectors vidc_vdec_vga_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 40894464, + .ib = 327155712, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 48234496, + .ib = 192937984, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 500000, + .ib = 2000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 500000, + .ib = 2000000, + }, +}; +static struct msm_bus_vectors vidc_venc_720p_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 163577856, + .ib = 1308622848, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 219152384, + .ib = 876609536, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1750000, + .ib = 3500000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 1750000, + .ib = 3500000, + }, +}; +static struct msm_bus_vectors vidc_vdec_720p_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 121634816, + .ib = 973078528, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 155189248, + .ib = 620756992, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1750000, + .ib = 7000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 1750000, + .ib = 7000000, + }, +}; +static struct msm_bus_vectors vidc_venc_1080p_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 372244480, + .ib = 1861222400, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 501219328, + .ib = 2004877312, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 2500000, + .ib = 5000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 2500000, + .ib = 5000000, + }, +}; +static struct msm_bus_vectors vidc_vdec_1080p_vectors[] = { + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 222298112, + .ib = 1778384896, + }, + { + .src = MSM_BUS_MASTER_HD_CODEC_PORT1, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 330301440, + .ib = 1321205760, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 2500000, + .ib = 700000000, + }, + { + .src = MSM_BUS_MASTER_AMPSS_M0, + .dst = MSM_BUS_SLAVE_SMI, + .ab = 2500000, + .ib = 10000000, + }, +}; + +static struct msm_bus_paths vidc_bus_client_config[] = { + { + ARRAY_SIZE(vidc_init_vectors), + vidc_init_vectors, + }, + { + ARRAY_SIZE(vidc_venc_vga_vectors), + vidc_venc_vga_vectors, + }, + { + ARRAY_SIZE(vidc_vdec_vga_vectors), + vidc_vdec_vga_vectors, + }, + { + ARRAY_SIZE(vidc_venc_720p_vectors), + vidc_venc_720p_vectors, + }, + { + ARRAY_SIZE(vidc_vdec_720p_vectors), + vidc_vdec_720p_vectors, + }, + { + ARRAY_SIZE(vidc_venc_1080p_vectors), + vidc_venc_1080p_vectors, + }, + { + ARRAY_SIZE(vidc_vdec_1080p_vectors), + vidc_vdec_1080p_vectors, + }, +}; + +static struct msm_bus_scale_pdata vidc_bus_client_data = { + vidc_bus_client_config, + ARRAY_SIZE(vidc_bus_client_config), + .name = "vidc", +}; + +#endif + +#define MSM_VIDC_BASE_PHYS 0x04400000 +#define MSM_VIDC_BASE_SIZE 0x00100000 + +static struct resource msm_device_vidc_resources[] = { + { + .start = MSM_VIDC_BASE_PHYS, + .end = MSM_VIDC_BASE_PHYS + MSM_VIDC_BASE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = VCODEC_IRQ, + .end = VCODEC_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +struct msm_vidc_platform_data vidc_platform_data = { +#ifdef CONFIG_MSM_BUS_SCALING + .vidc_bus_client_pdata = &vidc_bus_client_data, +#endif + .memtype = MEMTYPE_SMI_KERNEL +}; + +struct platform_device msm_device_vidc = { + .name = "msm_vidc", + .id = 0, + .num_resources = ARRAY_SIZE(msm_device_vidc_resources), + .resource = msm_device_vidc_resources, + .dev = { + .platform_data = &vidc_platform_data, + }, +}; + +#if defined(CONFIG_MSM_RPM_STATS_LOG) +static struct msm_rpmstats_platform_data msm_rpm_stat_pdata = { + .phys_addr_base = 0x00107E04, + .phys_size = SZ_8K, +}; + +struct platform_device msm_rpm_stat_device = { + .name = "msm_rpm_stat", + .id = -1, + .dev = { + .platform_data = &msm_rpm_stat_pdata, + }, +}; +#endif + +#ifdef CONFIG_MSM_MPM +static uint16_t msm_mpm_irqs_m2a[MSM_MPM_NR_MPM_IRQS] = { + [1] = MSM_GPIO_TO_INT(61), + [4] = MSM_GPIO_TO_INT(87), + [5] = MSM_GPIO_TO_INT(88), + [6] = MSM_GPIO_TO_INT(89), + [7] = MSM_GPIO_TO_INT(90), + [8] = MSM_GPIO_TO_INT(91), + [9] = MSM_GPIO_TO_INT(34), + [10] = MSM_GPIO_TO_INT(38), + [11] = MSM_GPIO_TO_INT(42), + [12] = MSM_GPIO_TO_INT(46), + [13] = MSM_GPIO_TO_INT(50), + [14] = MSM_GPIO_TO_INT(54), + [15] = MSM_GPIO_TO_INT(58), + [16] = MSM_GPIO_TO_INT(63), + [17] = MSM_GPIO_TO_INT(160), + [18] = MSM_GPIO_TO_INT(162), + [19] = MSM_GPIO_TO_INT(144), + [20] = MSM_GPIO_TO_INT(146), + [25] = USB1_HS_IRQ, + [26] = TV_ENC_IRQ, + [27] = HDMI_IRQ, + [29] = MSM_GPIO_TO_INT(123), + [30] = MSM_GPIO_TO_INT(172), + [31] = MSM_GPIO_TO_INT(99), + [32] = MSM_GPIO_TO_INT(96), + [33] = MSM_GPIO_TO_INT(67), + [34] = MSM_GPIO_TO_INT(71), + [35] = MSM_GPIO_TO_INT(105), + [36] = MSM_GPIO_TO_INT(117), + [37] = MSM_GPIO_TO_INT(29), + [38] = MSM_GPIO_TO_INT(30), + [39] = MSM_GPIO_TO_INT(31), + [40] = MSM_GPIO_TO_INT(37), + [41] = MSM_GPIO_TO_INT(40), + [42] = MSM_GPIO_TO_INT(41), + [43] = MSM_GPIO_TO_INT(45), + [44] = MSM_GPIO_TO_INT(51), + [45] = MSM_GPIO_TO_INT(52), + [46] = MSM_GPIO_TO_INT(57), + [47] = MSM_GPIO_TO_INT(73), + [48] = MSM_GPIO_TO_INT(93), + [49] = MSM_GPIO_TO_INT(94), + [50] = MSM_GPIO_TO_INT(103), + [51] = MSM_GPIO_TO_INT(104), + [52] = MSM_GPIO_TO_INT(106), + [53] = MSM_GPIO_TO_INT(115), + [54] = MSM_GPIO_TO_INT(124), + [55] = MSM_GPIO_TO_INT(125), + [56] = MSM_GPIO_TO_INT(126), + [57] = MSM_GPIO_TO_INT(127), + [58] = MSM_GPIO_TO_INT(128), + [59] = MSM_GPIO_TO_INT(129), +}; + +static uint16_t msm_mpm_bypassed_apps_irqs[] = { + TLMM_MSM_SUMMARY_IRQ, + RPM_SCSS_CPU0_GP_HIGH_IRQ, + RPM_SCSS_CPU0_GP_MEDIUM_IRQ, + RPM_SCSS_CPU0_GP_LOW_IRQ, + RPM_SCSS_CPU0_WAKE_UP_IRQ, + RPM_SCSS_CPU1_GP_HIGH_IRQ, + RPM_SCSS_CPU1_GP_MEDIUM_IRQ, + RPM_SCSS_CPU1_GP_LOW_IRQ, + RPM_SCSS_CPU1_WAKE_UP_IRQ, + MARM_SCSS_GP_IRQ_0, + MARM_SCSS_GP_IRQ_1, + MARM_SCSS_GP_IRQ_2, + MARM_SCSS_GP_IRQ_3, + MARM_SCSS_GP_IRQ_4, + MARM_SCSS_GP_IRQ_5, + MARM_SCSS_GP_IRQ_6, + MARM_SCSS_GP_IRQ_7, + MARM_SCSS_GP_IRQ_8, + MARM_SCSS_GP_IRQ_9, + LPASS_SCSS_GP_LOW_IRQ, + LPASS_SCSS_GP_MEDIUM_IRQ, + LPASS_SCSS_GP_HIGH_IRQ, + SDC4_IRQ_0, + SPS_MTI_31, +}; + +struct msm_mpm_device_data msm_mpm_dev_data = { + .irqs_m2a = msm_mpm_irqs_m2a, + .irqs_m2a_size = ARRAY_SIZE(msm_mpm_irqs_m2a), + .bypassed_apps_irqs = msm_mpm_bypassed_apps_irqs, + .bypassed_apps_irqs_size = ARRAY_SIZE(msm_mpm_bypassed_apps_irqs), + .mpm_request_reg_base = MSM_RPM_BASE + 0x9d8, + .mpm_status_reg_base = MSM_RPM_BASE + 0xdf8, + .mpm_apps_ipc_reg = MSM_GCC_BASE + 0x008, + .mpm_apps_ipc_val = BIT(1), + .mpm_ipc_irq = RPM_SCSS_CPU0_GP_MEDIUM_IRQ, + +}; +#endif + + +#ifdef CONFIG_MSM_BUS_SCALING +struct platform_device msm_bus_sys_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_SYSTEM, +}; +struct platform_device msm_bus_apps_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_APPSS, +}; +struct platform_device msm_bus_mm_fabric = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_MMSS, +}; +struct platform_device msm_bus_sys_fpb = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_SYSTEM_FPB, +}; +struct platform_device msm_bus_cpss_fpb = { + .name = "msm_bus_fabric", + .id = MSM_BUS_FAB_CPSS_FPB, +}; +#endif + +struct platform_device asoc_msm_pcm = { + .name = "msm-dsp-audio", + .id = 0, +}; + +struct platform_device asoc_msm_dai0 = { + .name = "msm-codec-dai", + .id = 0, +}; + +struct platform_device asoc_msm_dai1 = { + .name = "msm-cpu-dai", + .id = 0, +}; + +#if defined (CONFIG_MSM_8x60_VOIP) +struct platform_device asoc_msm_mvs = { + .name = "msm-mvs-audio", + .id = 0, +}; + +struct platform_device asoc_mvs_dai0 = { + .name = "mvs-codec-dai", + .id = 0, +}; + +struct platform_device asoc_mvs_dai1 = { + .name = "mvs-cpu-dai", + .id = 0, +}; +#endif + +struct platform_device *msm_footswitch_devices[] = { + FS_8X60(FS_IJPEG, "fs_ijpeg"), + FS_8X60(FS_MDP, "fs_mdp"), + FS_8X60(FS_ROT, "fs_rot"), + FS_8X60(FS_VED, "fs_ved"), + FS_8X60(FS_VFE, "fs_vfe"), + FS_8X60(FS_VPE, "fs_vpe"), + FS_8X60(FS_GFX3D, "fs_gfx3d"), + FS_8X60(FS_GFX2D0, "fs_gfx2d0"), + FS_8X60(FS_GFX2D1, "fs_gfx2d1"), +}; +unsigned msm_num_footswitch_devices = ARRAY_SIZE(msm_footswitch_devices); + +#ifdef CONFIG_MSM_RPM +struct msm_rpm_map_data rpm_map_data[] __initdata = { + MSM_RPM_MAP(TRIGGER_TIMED_TO, TRIGGER_TIMED, 1), + MSM_RPM_MAP(TRIGGER_TIMED_SCLK_COUNT, TRIGGER_TIMED, 1), + MSM_RPM_MAP(TRIGGER_SET_FROM, TRIGGER_SET, 1), + MSM_RPM_MAP(TRIGGER_SET_TO, TRIGGER_SET, 1), + MSM_RPM_MAP(TRIGGER_SET_TRIGGER, TRIGGER_SET, 1), + MSM_RPM_MAP(TRIGGER_CLEAR_FROM, TRIGGER_CLEAR, 1), + MSM_RPM_MAP(TRIGGER_CLEAR_TO, TRIGGER_CLEAR, 1), + MSM_RPM_MAP(TRIGGER_CLEAR_TRIGGER, TRIGGER_CLEAR, 1), + + MSM_RPM_MAP(CXO_CLK, CXO_CLK, 1), + MSM_RPM_MAP(PXO_CLK, PXO_CLK, 1), + MSM_RPM_MAP(PLL_4, PLL_4, 1), + MSM_RPM_MAP(APPS_FABRIC_CLK, APPS_FABRIC_CLK, 1), + MSM_RPM_MAP(SYSTEM_FABRIC_CLK, SYSTEM_FABRIC_CLK, 1), + MSM_RPM_MAP(MM_FABRIC_CLK, MM_FABRIC_CLK, 1), + MSM_RPM_MAP(DAYTONA_FABRIC_CLK, DAYTONA_FABRIC_CLK, 1), + MSM_RPM_MAP(SFPB_CLK, SFPB_CLK, 1), + MSM_RPM_MAP(CFPB_CLK, CFPB_CLK, 1), + MSM_RPM_MAP(MMFPB_CLK, MMFPB_CLK, 1), + MSM_RPM_MAP(SMI_CLK, SMI_CLK, 1), + MSM_RPM_MAP(EBI1_CLK, EBI1_CLK, 1), + + MSM_RPM_MAP(APPS_L2_CACHE_CTL, APPS_L2_CACHE_CTL, 1), + + MSM_RPM_MAP(APPS_FABRIC_HALT_0, APPS_FABRIC_HALT, 2), + MSM_RPM_MAP(APPS_FABRIC_CLOCK_MODE_0, APPS_FABRIC_CLOCK_MODE, 3), + MSM_RPM_MAP(APPS_FABRIC_ARB_0, APPS_FABRIC_ARB, 6), + + MSM_RPM_MAP(SYSTEM_FABRIC_HALT_0, SYSTEM_FABRIC_HALT, 2), + MSM_RPM_MAP(SYSTEM_FABRIC_CLOCK_MODE_0, SYSTEM_FABRIC_CLOCK_MODE, 3), + MSM_RPM_MAP(SYSTEM_FABRIC_ARB_0, SYSTEM_FABRIC_ARB, 22), + + MSM_RPM_MAP(MM_FABRIC_HALT_0, MM_FABRIC_HALT, 2), + MSM_RPM_MAP(MM_FABRIC_CLOCK_MODE_0, MM_FABRIC_CLOCK_MODE, 3), + MSM_RPM_MAP(MM_FABRIC_ARB_0, MM_FABRIC_ARB, 23), + + MSM_RPM_MAP(SMPS0B_0, SMPS0B, 2), + MSM_RPM_MAP(SMPS1B_0, SMPS1B, 2), + MSM_RPM_MAP(SMPS2B_0, SMPS2B, 2), + MSM_RPM_MAP(SMPS3B_0, SMPS3B, 2), + MSM_RPM_MAP(SMPS4B_0, SMPS4B, 2), + MSM_RPM_MAP(LDO0B_0, LDO0B, 2), + MSM_RPM_MAP(LDO1B_0, LDO1B, 2), + MSM_RPM_MAP(LDO2B_0, LDO2B, 2), + MSM_RPM_MAP(LDO3B_0, LDO3B, 2), + MSM_RPM_MAP(LDO4B_0, LDO4B, 2), + MSM_RPM_MAP(LDO5B_0, LDO5B, 2), + MSM_RPM_MAP(LDO6B_0, LDO6B, 2), + MSM_RPM_MAP(LVS0B, LVS0B, 1), + MSM_RPM_MAP(LVS1B, LVS1B, 1), + MSM_RPM_MAP(LVS2B, LVS2B, 1), + MSM_RPM_MAP(LVS3B, LVS3B, 1), + MSM_RPM_MAP(MVS, MVS, 1), + + MSM_RPM_MAP(SMPS0_0, SMPS0, 2), + MSM_RPM_MAP(SMPS1_0, SMPS1, 2), + MSM_RPM_MAP(SMPS2_0, SMPS2, 2), + MSM_RPM_MAP(SMPS3_0, SMPS3, 2), + MSM_RPM_MAP(SMPS4_0, SMPS4, 2), + MSM_RPM_MAP(LDO0_0, LDO0, 2), + MSM_RPM_MAP(LDO1_0, LDO1, 2), + MSM_RPM_MAP(LDO2_0, LDO2, 2), + MSM_RPM_MAP(LDO3_0, LDO3, 2), + MSM_RPM_MAP(LDO4_0, LDO4, 2), + MSM_RPM_MAP(LDO5_0, LDO5, 2), + MSM_RPM_MAP(LDO6_0, LDO6, 2), + MSM_RPM_MAP(LDO7_0, LDO7, 2), + MSM_RPM_MAP(LDO8_0, LDO8, 2), + MSM_RPM_MAP(LDO9_0, LDO9, 2), + MSM_RPM_MAP(LDO10_0, LDO10, 2), + MSM_RPM_MAP(LDO11_0, LDO11, 2), + MSM_RPM_MAP(LDO12_0, LDO12, 2), + MSM_RPM_MAP(LDO13_0, LDO13, 2), + MSM_RPM_MAP(LDO14_0, LDO14, 2), + MSM_RPM_MAP(LDO15_0, LDO15, 2), + MSM_RPM_MAP(LDO16_0, LDO16, 2), + MSM_RPM_MAP(LDO17_0, LDO17, 2), + MSM_RPM_MAP(LDO18_0, LDO18, 2), + MSM_RPM_MAP(LDO19_0, LDO19, 2), + MSM_RPM_MAP(LDO20_0, LDO20, 2), + MSM_RPM_MAP(LDO21_0, LDO21, 2), + MSM_RPM_MAP(LDO22_0, LDO22, 2), + MSM_RPM_MAP(LDO23_0, LDO23, 2), + MSM_RPM_MAP(LDO24_0, LDO24, 2), + MSM_RPM_MAP(LDO25_0, LDO25, 2), + MSM_RPM_MAP(LVS0, LVS0, 1), + MSM_RPM_MAP(LVS1, LVS1, 1), + MSM_RPM_MAP(NCP_0, NCP, 2), + + MSM_RPM_MAP(CXO_BUFFERS, CXO_BUFFERS, 1), +}; +unsigned int rpm_map_data_size = ARRAY_SIZE(rpm_map_data); + +#endif diff --git a/arch/arm/mach-msm/devices-msm8x60.h b/arch/arm/mach-msm/devices-msm8x60.h new file mode 100644 index 00000000000..60a1f60a692 --- /dev/null +++ b/arch/arm/mach-msm/devices-msm8x60.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ARCH_ARM_MACH_MSM_DEVICES_MSM8X60_H +#define __ARCH_ARM_MACH_MSM_DEVICES_MSM8X60_H + +#define MSM_GSBI3_QUP_I2C_BUS_ID 0 +#define MSM_GSBI4_QUP_I2C_BUS_ID 1 +#define MSM_GSBI9_QUP_I2C_BUS_ID 2 +#define MSM_GSBI8_QUP_I2C_BUS_ID 3 +#define MSM_GSBI7_QUP_I2C_BUS_ID 4 +#define MSM_GSBI12_QUP_I2C_BUS_ID 5 +#define MSM_SSBI1_I2C_BUS_ID 6 +#define MSM_SSBI2_I2C_BUS_ID 7 +#define MSM_SSBI3_I2C_BUS_ID 8 + +#ifdef CONFIG_SPI_QUP +extern struct platform_device msm_gsbi1_qup_spi_device; +extern struct platform_device msm_gsbi10_qup_spi_device; +#endif + +extern struct platform_device msm_bus_apps_fabric; +extern struct platform_device msm_bus_sys_fabric; +extern struct platform_device msm_bus_mm_fabric; +extern struct platform_device msm_bus_sys_fpb; +extern struct platform_device msm_bus_cpss_fpb; + +extern struct platform_device msm_device_smd; +extern struct platform_device msm_device_gpio; +extern struct platform_device msm_device_vidc; + +extern struct platform_device msm_charm_modem; + +#ifdef CONFIG_HW_RANDOM_MSM +extern struct platform_device msm_device_rng; +#endif + +void __init msm8x60_init_irq(void); +void __init msm8x60_check_2d_hardware(void); + +#ifdef CONFIG_MSM_DSPS +extern struct platform_device msm_dsps_device; +#endif + +#if defined(CONFIG_MSM_RPM_STATS_LOG) +extern struct platform_device msm_rpm_stat_device; +#endif +#endif diff --git a/arch/arm/mach-msm/devices-qsd8x50.c b/arch/arm/mach-msm/devices-qsd8x50.c index 12d8deb78d9..c0caf703961 100644 --- a/arch/arm/mach-msm/devices-qsd8x50.c +++ b/arch/arm/mach-msm/devices-qsd8x50.c @@ -15,20 +15,51 @@ #include #include -#include -#include +#include +#include +#include #include #include #include #include #include "devices.h" +#include "gpio_hw.h" #include -#include -#include "clock-pcom.h" +#include +#include +#include +#include +#include + +static struct resource resources_uart1[] = { + { + .start = INT_UART1, + .end = INT_UART1, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART1_PHYS, + .end = MSM_UART1_PHYS + MSM_UART1_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_uart2[] = { + { + .start = INT_UART2, + .end = INT_UART2, + .flags = IORESOURCE_IRQ, + }, + { + .start = MSM_UART2_PHYS, + .end = MSM_UART2_PHYS + MSM_UART2_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; static struct resource resources_uart3[] = { { @@ -44,6 +75,20 @@ static struct resource resources_uart3[] = { }, }; +struct platform_device msm_device_uart1 = { + .name = "msm_serial", + .id = 0, + .num_resources = ARRAY_SIZE(resources_uart1), + .resource = resources_uart1, +}; + +struct platform_device msm_device_uart2 = { + .name = "msm_serial", + .id = 1, + .num_resources = ARRAY_SIZE(resources_uart2), + .resource = resources_uart2, +}; + struct platform_device msm_device_uart3 = { .name = "msm_serial", .id = 2, @@ -51,15 +96,122 @@ struct platform_device msm_device_uart3 = { .resource = resources_uart3, }; -struct platform_device msm_device_smd = { - .name = "msm_smd", - .id = -1, +#define MSM_UART1DM_PHYS 0xA0200000 +#define MSM_UART2DM_PHYS 0xA0900000 +static struct resource msm_uart1_dm_resources[] = { + { + .start = MSM_UART1DM_PHYS, + .end = MSM_UART1DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART1DM_IRQ, + .end = INT_UART1DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART1DM_RX, + .end = INT_UART1DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART1_TX_CHAN, + .end = DMOV_HSUART1_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART1_TX_CRCI, + .end = DMOV_HSUART1_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, }; -static struct resource resources_otg[] = { +static u64 msm_uart_dm1_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm1 = { + .name = "msm_serial_hs", + .id = 0, + .num_resources = ARRAY_SIZE(msm_uart1_dm_resources), + .resource = msm_uart1_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm1_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +static struct resource msm_uart2_dm_resources[] = { + { + .start = MSM_UART2DM_PHYS, + .end = MSM_UART2DM_PHYS + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_UART2DM_IRQ, + .end = INT_UART2DM_IRQ, + .flags = IORESOURCE_IRQ, + }, + { + .start = INT_UART2DM_RX, + .end = INT_UART2DM_RX, + .flags = IORESOURCE_IRQ, + }, + { + .start = DMOV_HSUART2_TX_CHAN, + .end = DMOV_HSUART2_RX_CHAN, + .name = "uartdm_channels", + .flags = IORESOURCE_DMA, + }, + { + .start = DMOV_HSUART2_TX_CRCI, + .end = DMOV_HSUART2_RX_CRCI, + .name = "uartdm_crci", + .flags = IORESOURCE_DMA, + }, +}; + +static u64 msm_uart_dm2_dma_mask = DMA_BIT_MASK(32); + +struct platform_device msm_device_uart_dm2 = { + .name = "msm_serial_hs", + .id = 1, + .num_resources = ARRAY_SIZE(msm_uart2_dm_resources), + .resource = msm_uart2_dm_resources, + .dev = { + .dma_mask = &msm_uart_dm2_dma_mask, + .coherent_dma_mask = DMA_BIT_MASK(32), + }, +}; + +#define MSM_I2C_SIZE SZ_4K +#define MSM_I2C_PHYS 0xA9900000 + +static struct resource resources_i2c[] = { + { + .start = MSM_I2C_PHYS, + .end = MSM_I2C_PHYS + MSM_I2C_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_PWB_I2C, + .end = INT_PWB_I2C, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_i2c = { + .name = "msm_i2c", + .id = 0, + .num_resources = ARRAY_SIZE(resources_i2c), + .resource = resources_i2c, +}; + +#define MSM_HSUSB_PHYS 0xA0800000 +static struct resource resources_hsusb_otg[] = { { .start = MSM_HSUSB_PHYS, - .end = MSM_HSUSB_PHYS + MSM_HSUSB_SIZE, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, .flags = IORESOURCE_MEM, }, { @@ -69,20 +221,35 @@ static struct resource resources_otg[] = { }, }; -struct platform_device msm_device_otg = { - .name = "msm_otg", +static u64 dma_mask = 0xffffffffULL; +struct platform_device msm_device_hsusb_otg = { + .name = "msm_hsusb_otg", .id = -1, - .num_resources = ARRAY_SIZE(resources_otg), - .resource = resources_otg, + .num_resources = ARRAY_SIZE(resources_hsusb_otg), + .resource = resources_hsusb_otg, .dev = { - .coherent_dma_mask = 0xffffffff, + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +static struct resource resources_hsusb_peripheral[] = { + { + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_HS, + .end = INT_USB_HS, + .flags = IORESOURCE_IRQ, }, }; -static struct resource resources_hsusb[] = { +static struct resource resources_gadget_peripheral[] = { { .start = MSM_HSUSB_PHYS, - .end = MSM_HSUSB_PHYS + MSM_HSUSB_SIZE, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, .flags = IORESOURCE_MEM, }, { @@ -92,21 +259,59 @@ static struct resource resources_hsusb[] = { }, }; -struct platform_device msm_device_hsusb = { +struct platform_device msm_device_hsusb_peripheral = { + .name = "msm_hsusb_peripheral", + .id = -1, + .num_resources = ARRAY_SIZE(resources_hsusb_peripheral), + .resource = resources_hsusb_peripheral, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct platform_device msm_device_gadget_peripheral = { .name = "msm_hsusb", .id = -1, - .num_resources = ARRAY_SIZE(resources_hsusb), - .resource = resources_hsusb, + .num_resources = ARRAY_SIZE(resources_gadget_peripheral), + .resource = resources_gadget_peripheral, .dev = { - .coherent_dma_mask = 0xffffffff, + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, }, }; -static u64 dma_mask = 0xffffffffULL; +#ifdef CONFIG_USB_FS_HOST +#define MSM_HS2USB_PHYS 0xA0800400 +static struct resource resources_hsusb_host2[] = { + { + .start = MSM_HS2USB_PHYS, + .end = MSM_HS2USB_PHYS + SZ_1K - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_USB_OTG, + .end = INT_USB_OTG, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_hsusb_host2 = { + .name = "msm_hsusb_host", + .id = 1, + .num_resources = ARRAY_SIZE(resources_hsusb_host2), + .resource = resources_hsusb_host2, + .dev = { + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, + }, +}; +#endif + static struct resource resources_hsusb_host[] = { { .start = MSM_HSUSB_PHYS, - .end = MSM_HSUSB_PHYS + MSM_HSUSB_SIZE, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, .flags = IORESOURCE_MEM, }, { @@ -118,36 +323,150 @@ static struct resource resources_hsusb_host[] = { struct platform_device msm_device_hsusb_host = { .name = "msm_hsusb_host", - .id = -1, + .id = 0, .num_resources = ARRAY_SIZE(resources_hsusb_host), .resource = resources_hsusb_host, .dev = { - .dma_mask = &dma_mask, - .coherent_dma_mask = 0xffffffffULL, + .dma_mask = &dma_mask, + .coherent_dma_mask = 0xffffffffULL, }, }; -static struct resource resources_sdc1[] = { +static struct platform_device *msm_host_devices[] = { + &msm_device_hsusb_host, +#ifdef CONFIG_USB_FS_HOST + &msm_device_hsusb_host2, +#endif +}; + +int msm_add_host(unsigned int host, struct msm_usb_host_platform_data *plat) +{ + struct platform_device *pdev; + + pdev = msm_host_devices[host]; + if (!pdev) + return -ENODEV; + pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} + +#ifdef CONFIG_USB_ANDROID +struct usb_diag_platform_data usb_diag_pdata = { + .ch_name = DIAG_LEGACY, + .update_pid_and_serial_num = usb_diag_update_pid_and_serial_num, +}; + +struct platform_device usb_diag_device = { + .name = "usb_diag", + .id = -1, + .dev = { + .platform_data = &usb_diag_pdata, + }, +}; +#endif + +#ifdef CONFIG_USB_F_SERIAL +static struct usb_gadget_fserial_platform_data fserial_pdata = { + .no_ports = 2, +}; + +struct platform_device usb_gadget_fserial_device = { + .name = "usb_fserial", + .id = -1, + .dev = { + .platform_data = &fserial_pdata, + }, +}; +#endif + +#define MSM_NAND_PHYS 0xA0A00000 +static struct resource resources_nand[] = { + [0] = { + .name = "msm_nand_dmac", + .start = DMOV_NAND_CHAN, + .end = DMOV_NAND_CHAN, + .flags = IORESOURCE_DMA, + }, + [1] = { + .name = "msm_nand_phys", + .start = MSM_NAND_PHYS, + .end = MSM_NAND_PHYS + 0x7FF, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource resources_otg[] = { { - .start = MSM_SDC1_PHYS, - .end = MSM_SDC1_PHYS + MSM_SDC1_SIZE - 1, + .start = MSM_HSUSB_PHYS, + .end = MSM_HSUSB_PHYS + SZ_1K - 1, .flags = IORESOURCE_MEM, }, { - .start = INT_SDC1_0, - .end = INT_SDC1_0, + .start = INT_USB_HS, + .end = INT_USB_HS, .flags = IORESOURCE_IRQ, - .name = "cmd_irq", }, +}; + +struct platform_device msm_device_otg = { + .name = "msm_otg", + .id = -1, + .num_resources = ARRAY_SIZE(resources_otg), + .resource = resources_otg, + .dev = { + .coherent_dma_mask = 0xffffffffULL, + }, +}; + +struct flash_platform_data msm_nand_data = { + .parts = NULL, + .nr_parts = 0, +}; + +struct platform_device msm_device_nand = { + .name = "msm_nand", + .id = -1, + .num_resources = ARRAY_SIZE(resources_nand), + .resource = resources_nand, + .dev = { + .platform_data = &msm_nand_data, + }, +}; + +struct platform_device msm_device_smd = { + .name = "msm_smd", + .id = -1, +}; + +struct resource msm_dmov_resource[] = { { - .start = INT_SDC1_1, - .end = INT_SDC1_1, - .flags = IORESOURCE_IRQ, - .name = "pio_irq", + .start = INT_ADM_AARM, + .end = (resource_size_t)MSM_DMOV_BASE, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device msm_device_dmov = { + .name = "msm_dmov", + .id = -1, + .resource = msm_dmov_resource, + .num_resources = ARRAY_SIZE(msm_dmov_resource), +}; + +#define MSM_SDC1_BASE 0xA0300000 +#define MSM_SDC2_BASE 0xA0400000 +#define MSM_SDC3_BASE 0xA0500000 +#define MSM_SDC4_BASE 0xA0600000 +static struct resource resources_sdc1[] = { + { + .start = MSM_SDC1_BASE, + .end = MSM_SDC1_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, }, { - .flags = IORESOURCE_IRQ | IORESOURCE_DISABLED, - .name = "status_irq" + .start = INT_SDC1_0, + .end = INT_SDC1_1, + .flags = IORESOURCE_IRQ, }, { .start = 8, @@ -158,25 +477,14 @@ static struct resource resources_sdc1[] = { static struct resource resources_sdc2[] = { { - .start = MSM_SDC2_PHYS, - .end = MSM_SDC2_PHYS + MSM_SDC2_SIZE - 1, + .start = MSM_SDC2_BASE, + .end = MSM_SDC2_BASE + SZ_4K - 1, .flags = IORESOURCE_MEM, }, { .start = INT_SDC2_0, - .end = INT_SDC2_0, - .flags = IORESOURCE_IRQ, - .name = "cmd_irq", - }, - { - .start = INT_SDC2_1, .end = INT_SDC2_1, .flags = IORESOURCE_IRQ, - .name = "pio_irq", - }, - { - .flags = IORESOURCE_IRQ | IORESOURCE_DISABLED, - .name = "status_irq" }, { .start = 8, @@ -187,25 +495,14 @@ static struct resource resources_sdc2[] = { static struct resource resources_sdc3[] = { { - .start = MSM_SDC3_PHYS, - .end = MSM_SDC3_PHYS + MSM_SDC3_SIZE - 1, + .start = MSM_SDC3_BASE, + .end = MSM_SDC3_BASE + SZ_4K - 1, .flags = IORESOURCE_MEM, }, { .start = INT_SDC3_0, - .end = INT_SDC3_0, - .flags = IORESOURCE_IRQ, - .name = "cmd_irq", - }, - { - .start = INT_SDC3_1, .end = INT_SDC3_1, .flags = IORESOURCE_IRQ, - .name = "pio_irq", - }, - { - .flags = IORESOURCE_IRQ | IORESOURCE_DISABLED, - .name = "status_irq" }, { .start = 8, @@ -216,25 +513,14 @@ static struct resource resources_sdc3[] = { static struct resource resources_sdc4[] = { { - .start = MSM_SDC4_PHYS, - .end = MSM_SDC4_PHYS + MSM_SDC4_SIZE - 1, + .start = MSM_SDC4_BASE, + .end = MSM_SDC4_BASE + SZ_4K - 1, .flags = IORESOURCE_MEM, }, { .start = INT_SDC4_0, - .end = INT_SDC4_0, - .flags = IORESOURCE_IRQ, - .name = "cmd_irq", - }, - { - .start = INT_SDC4_1, .end = INT_SDC4_1, .flags = IORESOURCE_IRQ, - .name = "pio_irq", - }, - { - .flags = IORESOURCE_IRQ | IORESOURCE_DISABLED, - .name = "status_irq" }, { .start = 8, @@ -290,84 +576,330 @@ static struct platform_device *msm_sdcc_devices[] __initdata = { &msm_device_sdc4, }; -int __init msm_add_sdcc(unsigned int controller, - struct msm_mmc_platform_data *plat, - unsigned int stat_irq, unsigned long stat_irq_flags) +int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat) { struct platform_device *pdev; - struct resource *res; if (controller < 1 || controller > 4) return -EINVAL; pdev = msm_sdcc_devices[controller-1]; pdev->dev.platform_data = plat; + return platform_device_register(pdev); +} - res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "status_irq"); - if (!res) - return -EINVAL; - else if (stat_irq) { - res->start = res->end = stat_irq; - res->flags &= ~IORESOURCE_DISABLED; - res->flags |= stat_irq_flags; +#if defined(CONFIG_FB_MSM_MDP40) +#define MDP_BASE 0xA3F00000 +#define PMDH_BASE 0xAD600000 +#define EMDH_BASE 0xAD700000 +#define TVENC_BASE 0xAD400000 +#else +#define MDP_BASE 0xAA200000 +#define PMDH_BASE 0xAA600000 +#define EMDH_BASE 0xAA700000 +#define TVENC_BASE 0xAA400000 +#endif + +static struct resource msm_mdp_resources[] = { + { + .name = "mdp", + .start = MDP_BASE, + .end = MDP_BASE + 0x000F0000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .start = INT_MDP, + .end = INT_MDP, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct resource msm_mddi_resources[] = { + { + .name = "pmdh", + .start = PMDH_BASE, + .end = PMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, } +}; - return platform_device_register(pdev); +static struct resource msm_mddi_ext_resources[] = { + { + .name = "emdh", + .start = EMDH_BASE, + .end = EMDH_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct resource msm_ebi2_lcd_resources[] = { + { + .name = "base", + .start = 0xa0d00000, + .end = 0xa0d00000 + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd01", + .start = 0x98000000, + .end = 0x98000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "lcd02", + .start = 0x9c000000, + .end = 0x9c000000 + 0x80000 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource msm_tvenc_resources[] = { + { + .name = "tvenc", + .start = TVENC_BASE, + .end = TVENC_BASE + PAGE_SIZE - 1, + .flags = IORESOURCE_MEM, + } +}; + +static struct platform_device msm_mdp_device = { + .name = "mdp", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mdp_resources), + .resource = msm_mdp_resources, +}; + +static struct platform_device msm_mddi_device = { + .name = "mddi", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_resources), + .resource = msm_mddi_resources, +}; + +static struct platform_device msm_mddi_ext_device = { + .name = "mddi_ext", + .id = 0, + .num_resources = ARRAY_SIZE(msm_mddi_ext_resources), + .resource = msm_mddi_ext_resources, +}; + +static struct platform_device msm_ebi2_lcd_device = { + .name = "ebi2_lcd", + .id = 0, + .num_resources = ARRAY_SIZE(msm_ebi2_lcd_resources), + .resource = msm_ebi2_lcd_resources, +}; + +static struct platform_device msm_lcdc_device = { + .name = "lcdc", + .id = 0, +}; + +static struct platform_device msm_tvenc_device = { + .name = "tvenc", + .id = 0, + .num_resources = ARRAY_SIZE(msm_tvenc_resources), + .resource = msm_tvenc_resources, +}; + +#if defined(CONFIG_MSM_SOC_REV_A) +#define MSM_QUP_PHYS 0xA1680000 +#define MSM_GSBI_QUP_I2C_PHYS 0xA1600000 +#define INT_PWB_QUP_ERR INT_GSBI_QUP +#else +#define MSM_QUP_PHYS 0xA9900000 +#define MSM_GSBI_QUP_I2C_PHYS 0xA9900000 +#define INT_PWB_QUP_ERR INT_PWB_I2C +#endif +#define MSM_QUP_SIZE SZ_4K +static struct resource resources_qup[] = { + { + .name = "qup_phys_addr", + .start = MSM_QUP_PHYS, + .end = MSM_QUP_PHYS + MSM_QUP_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "gsbi_qup_i2c_addr", + .start = MSM_GSBI_QUP_I2C_PHYS, + .end = MSM_GSBI_QUP_I2C_PHYS + 4 - 1, + .flags = IORESOURCE_MEM, + }, + { + .name = "qup_err_intr", + .start = INT_PWB_QUP_ERR, + .end = INT_PWB_QUP_ERR, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device qup_device_i2c = { + .name = "qup_i2c", + .id = 4, + .num_resources = ARRAY_SIZE(resources_qup), + .resource = resources_qup, +}; + +/* TSIF begin */ +#if defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) + +#define MSM_TSIF_PHYS (0xa0100000) +#define MSM_TSIF_SIZE (0x200) + +static struct resource tsif_resources[] = { + [0] = { + .flags = IORESOURCE_IRQ, + .start = INT_TSIF_IRQ, + .end = INT_TSIF_IRQ, + }, + [1] = { + .flags = IORESOURCE_MEM, + .start = MSM_TSIF_PHYS, + .end = MSM_TSIF_PHYS + MSM_TSIF_SIZE - 1, + }, + [2] = { + .flags = IORESOURCE_DMA, + .start = DMOV_TSIF_CHAN, + .end = DMOV_TSIF_CRCI, + }, +}; + +static void tsif_release(struct device *dev) +{ + dev_info(dev, "release\n"); } -struct clk_lookup msm_clocks_8x50[] = { - CLK_PCOM("adm_clk", ADM_CLK, NULL, 0), - CLK_PCOM("ce_clk", CE_CLK, NULL, 0), - CLK_PCOM("ebi1_clk", EBI1_CLK, NULL, CLK_MIN), - CLK_PCOM("ebi2_clk", EBI2_CLK, NULL, 0), - CLK_PCOM("ecodec_clk", ECODEC_CLK, NULL, 0), - CLK_PCOM("emdh_clk", EMDH_CLK, NULL, OFF | CLK_MINMAX), - CLK_PCOM("gp_clk", GP_CLK, NULL, 0), - CLK_PCOM("grp_clk", GRP_3D_CLK, NULL, 0), - CLK_PCOM("i2c_clk", I2C_CLK, NULL, 0), - CLK_PCOM("icodec_rx_clk", ICODEC_RX_CLK, NULL, 0), - CLK_PCOM("icodec_tx_clk", ICODEC_TX_CLK, NULL, 0), - CLK_PCOM("imem_clk", IMEM_CLK, NULL, OFF), - CLK_PCOM("mdc_clk", MDC_CLK, NULL, 0), - CLK_PCOM("mddi_clk", PMDH_CLK, NULL, OFF | CLK_MINMAX), - CLK_PCOM("mdp_clk", MDP_CLK, NULL, OFF), - CLK_PCOM("mdp_lcdc_pclk_clk", MDP_LCDC_PCLK_CLK, NULL, 0), - CLK_PCOM("mdp_lcdc_pad_pclk_clk", MDP_LCDC_PAD_PCLK_CLK, NULL, 0), - CLK_PCOM("mdp_vsync_clk", MDP_VSYNC_CLK, NULL, 0), - CLK_PCOM("pbus_clk", PBUS_CLK, NULL, CLK_MIN), - CLK_PCOM("pcm_clk", PCM_CLK, NULL, 0), - CLK_PCOM("sdac_clk", SDAC_CLK, NULL, OFF), - CLK_PCOM("sdc_clk", SDC1_CLK, "msm_sdcc.1", OFF), - CLK_PCOM("sdc_pclk", SDC1_P_CLK, "msm_sdcc.1", OFF), - CLK_PCOM("sdc_clk", SDC2_CLK, "msm_sdcc.2", OFF), - CLK_PCOM("sdc_pclk", SDC2_P_CLK, "msm_sdcc.2", OFF), - CLK_PCOM("sdc_clk", SDC3_CLK, "msm_sdcc.3", OFF), - CLK_PCOM("sdc_pclk", SDC3_P_CLK, "msm_sdcc.3", OFF), - CLK_PCOM("sdc_clk", SDC4_CLK, "msm_sdcc.4", OFF), - CLK_PCOM("sdc_pclk", SDC4_P_CLK, "msm_sdcc.4", OFF), - CLK_PCOM("spi_clk", SPI_CLK, NULL, 0), - CLK_PCOM("tsif_clk", TSIF_CLK, NULL, 0), - CLK_PCOM("tsif_ref_clk", TSIF_REF_CLK, NULL, 0), - CLK_PCOM("tv_dac_clk", TV_DAC_CLK, NULL, 0), - CLK_PCOM("tv_enc_clk", TV_ENC_CLK, NULL, 0), - CLK_PCOM("uart_clk", UART1_CLK, NULL, OFF), - CLK_PCOM("uart_clk", UART2_CLK, NULL, 0), - CLK_PCOM("uart_clk", UART3_CLK, "msm_serial.2", OFF), - CLK_PCOM("uartdm_clk", UART1DM_CLK, NULL, OFF), - CLK_PCOM("uartdm_clk", UART2DM_CLK, NULL, 0), - CLK_PCOM("usb_hs_clk", USB_HS_CLK, NULL, OFF), - CLK_PCOM("usb_hs_pclk", USB_HS_P_CLK, NULL, OFF), - CLK_PCOM("usb_otg_clk", USB_OTG_CLK, NULL, 0), - CLK_PCOM("vdc_clk", VDC_CLK, NULL, OFF | CLK_MIN), - CLK_PCOM("vfe_clk", VFE_CLK, NULL, OFF), - CLK_PCOM("vfe_mdc_clk", VFE_MDC_CLK, NULL, OFF), - CLK_PCOM("vfe_axi_clk", VFE_AXI_CLK, NULL, OFF), - CLK_PCOM("usb_hs2_clk", USB_HS2_CLK, NULL, OFF), - CLK_PCOM("usb_hs2_pclk", USB_HS2_P_CLK, NULL, OFF), - CLK_PCOM("usb_hs3_clk", USB_HS3_CLK, NULL, OFF), - CLK_PCOM("usb_hs3_pclk", USB_HS3_P_CLK, NULL, OFF), - CLK_PCOM("usb_phy_clk", USB_PHY_CLK, NULL, 0), -}; - -unsigned msm_num_clocks_8x50 = ARRAY_SIZE(msm_clocks_8x50); +struct platform_device msm_device_tsif = { + .name = "msm_tsif", + .id = 0, + .num_resources = ARRAY_SIZE(tsif_resources), + .resource = tsif_resources, + .dev = { + .release = tsif_release, + }, +}; +#endif /* defined(CONFIG_TSIF) || defined(CONFIG_TSIF_MODULE) */ +/* TSIF end */ + +#define MSM_TSSC_PHYS 0xAA300000 +static struct resource resources_tssc[] = { + { + .start = MSM_TSSC_PHYS, + .end = MSM_TSSC_PHYS + SZ_4K - 1, + .name = "tssc", + .flags = IORESOURCE_MEM, + }, + { + .start = INT_TCHSCRN1, + .end = INT_TCHSCRN1, + .name = "tssc1", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, + { + .start = INT_TCHSCRN2, + .end = INT_TCHSCRN2, + .name = "tssc2", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, +}; + +struct platform_device msm_device_tssc = { + .name = "msm_touchscreen", + .id = 0, + .num_resources = ARRAY_SIZE(resources_tssc), + .resource = resources_tssc, +}; + +static void __init msm_register_device(struct platform_device *pdev, void *data) +{ + int ret; + + pdev->dev.platform_data = data; + + ret = platform_device_register(pdev); + if (ret) + dev_err(&pdev->dev, + "%s: platform_device_register() failed = %d\n", + __func__, ret); +} + +void __init msm_fb_register_device(char *name, void *data) +{ + if (!strncmp(name, "mdp", 3)) + msm_register_device(&msm_mdp_device, data); + else if (!strncmp(name, "pmdh", 4)) + msm_register_device(&msm_mddi_device, data); + else if (!strncmp(name, "emdh", 4)) + msm_register_device(&msm_mddi_ext_device, data); + else if (!strncmp(name, "ebi2", 4)) + msm_register_device(&msm_ebi2_lcd_device, data); + else if (!strncmp(name, "tvenc", 5)) + msm_register_device(&msm_tvenc_device, data); + else if (!strncmp(name, "lcdc", 4)) + msm_register_device(&msm_lcdc_device, data); + else + printk(KERN_ERR "%s: unknown device! %s\n", __func__, name); +} + +static struct platform_device msm_camera_device = { + .name = "msm_camera", + .id = 0, +}; + +void __init msm_camera_register_device(void *res, uint32_t num, + void *data) +{ + msm_camera_device.num_resources = num; + msm_camera_device.resource = res; + + msm_register_device(&msm_camera_device, data); +} + +static struct resource kgsl_3d0_resources[] = { + { + .name = KGSL_3D0_REG_MEMORY, + .start = 0xA0000000, + .end = 0xA001ffff, + .flags = IORESOURCE_MEM, + }, + { + .name = KGSL_3D0_IRQ, + .start = INT_GRAPHICS, + .end = INT_GRAPHICS, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct kgsl_device_platform_data kgsl_3d0_pdata = { + .pwr_data = { + .pwrlevel = { + { + .gpu_freq = 0, + .bus_freq = 128000000, + }, + }, + .init_level = 0, + .num_levels = 1, + .set_grp_async = NULL, + .idle_timeout = HZ/5, + }, + .clk = { + .name = { + .clk = "grp_clk", + }, + }, + .imem_clk_name = { + .clk = "imem_clk", + }, +}; + +struct platform_device msm_kgsl_3d0 = { + .name = "kgsl-3d0", + .id = 0, + .num_resources = ARRAY_SIZE(kgsl_3d0_resources), + .resource = kgsl_3d0_resources, + .dev = { + .platform_data = &kgsl_3d0_pdata, + }, +}; diff --git a/arch/arm/mach-msm/devices.h b/arch/arm/mach-msm/devices.h index 9545c196c6e..872a189d25c 100644 --- a/arch/arm/mach-msm/devices.h +++ b/arch/arm/mach-msm/devices.h @@ -1,6 +1,7 @@ /* linux/arch/arm/mach-msm/devices.h * * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 @@ -17,42 +18,157 @@ #define __ARCH_ARM_MACH_MSM_DEVICES_H #include +#include #include "clock.h" +extern struct platform_device asoc_msm_pcm; +extern struct platform_device asoc_msm_dai0; +extern struct platform_device asoc_msm_dai1; +#if defined (CONFIG_SND_MSM_MVS_DAI_SOC) +extern struct platform_device asoc_msm_mvs; +extern struct platform_device asoc_mvs_dai0; +extern struct platform_device asoc_mvs_dai1; +#endif + +extern struct platform_device msm_ebi0_thermal; +extern struct platform_device msm_ebi1_thermal; extern struct platform_device msm_device_uart1; extern struct platform_device msm_device_uart2; extern struct platform_device msm_device_uart3; +extern struct platform_device msm_device_uart_dm1; +extern struct platform_device msm_device_uart_dm2; +extern struct platform_device msm_device_uart_dm3; +extern struct platform_device msm_device_uart_dm12; +extern struct platform_device *msm_device_uart_gsbi9; + extern struct platform_device msm8960_device_uart_gsbi2; extern struct platform_device msm8960_device_uart_gsbi5; +extern struct platform_device msm8960_device_ssbi_pm8921; +extern struct platform_device msm8960_device_qup_i2c_gsbi3; +extern struct platform_device msm8960_device_qup_i2c_gsbi4; +extern struct platform_device msm8960_device_qup_i2c_gsbi10; +extern struct platform_device msm8960_device_qup_i2c_gsbi12; +extern struct platform_device msm8960_device_qup_spi_gsbi1; +extern struct platform_device msm8960_gemini_device; + +extern struct platform_device apq8064_device_uart_gsbi3; extern struct platform_device msm_device_sdc1; extern struct platform_device msm_device_sdc2; extern struct platform_device msm_device_sdc3; extern struct platform_device msm_device_sdc4; -extern struct platform_device msm_device_hsusb; -extern struct platform_device msm_device_otg; +extern struct platform_device msm_device_gadget_peripheral; extern struct platform_device msm_device_hsusb_host; +extern struct platform_device msm_device_hsusb_host2; + +extern struct platform_device msm_device_otg; extern struct platform_device msm_device_i2c; +extern struct platform_device msm_device_i2c_2; + +extern struct platform_device qup_device_i2c; + +extern struct platform_device msm_gsbi0_qup_i2c_device; +extern struct platform_device msm_gsbi1_qup_i2c_device; +extern struct platform_device msm_gsbi3_qup_i2c_device; +extern struct platform_device msm_gsbi4_qup_i2c_device; +extern struct platform_device msm_gsbi7_qup_i2c_device; +extern struct platform_device msm_gsbi8_qup_i2c_device; +extern struct platform_device msm_gsbi9_qup_i2c_device; +extern struct platform_device msm_gsbi12_qup_i2c_device; + +extern struct platform_device msm_slim_ctrl; +extern struct platform_device msm_device_sps; +extern struct platform_device msm_device_bam_dmux; extern struct platform_device msm_device_smd; +extern struct platform_device msm_device_dmov; +extern struct platform_device msm_device_dmov_adm0; +extern struct platform_device msm_device_dmov_adm1; extern struct platform_device msm_device_nand; -extern struct platform_device msm_device_mddi0; -extern struct platform_device msm_device_mddi1; -extern struct platform_device msm_device_mdp; +extern struct platform_device msm_device_tssc; + +extern struct platform_device msm_rotator_device; + +extern struct platform_device msm_device_tsif[2]; + +extern struct platform_device msm_device_ssbi1; +extern struct platform_device msm_device_ssbi2; +extern struct platform_device msm_device_ssbi3; +extern struct platform_device msm_device_ssbi6; +extern struct platform_device msm_device_ssbi7; + +extern struct platform_device msm_gsbi1_qup_spi_device; + +extern struct platform_device msm_device_wcnss_wlan; + +extern struct platform_device msm_device_vidc_720p; + +extern struct platform_device msm_pcm; +extern struct platform_device msm_pcm_routing; +extern struct platform_device msm_cpudai0; +extern struct platform_device msm_cpudai1; +extern struct platform_device msm_cpudai_hdmi_rx; +extern struct platform_device msm_cpudai_bt_rx; +extern struct platform_device msm_cpudai_bt_tx; +extern struct platform_device msm_cpudai_fm_rx; +extern struct platform_device msm_cpudai_fm_tx; +extern struct platform_device msm_cpu_fe; +extern struct platform_device msm_stub_codec; +extern struct platform_device msm_voice; +extern struct platform_device msm_voip; +extern struct platform_device msm_lpa_pcm; +extern struct platform_device msm_pcm_hostless; + +extern struct platform_device *msm_footswitch_devices[]; +extern unsigned msm_num_footswitch_devices; + +extern struct platform_device fsm_qfp_fuse_device; + +extern struct platform_device qfec_device; extern struct clk_lookup msm_clocks_7x01a[]; extern unsigned msm_num_clocks_7x01a; -extern struct clk_lookup msm_clocks_7x30[]; -extern unsigned msm_num_clocks_7x30; +extern struct clk_lookup msm_clocks_7x25[]; +extern unsigned msm_num_clocks_7x25; + +extern struct clk_lookup msm_clocks_7x27[]; +extern unsigned msm_num_clocks_7x27; + +extern struct clk_lookup msm_clocks_7x27a[]; +extern unsigned msm_num_clocks_7x27a; extern struct clk_lookup msm_clocks_8x50[]; extern unsigned msm_num_clocks_8x50; +extern struct clk_lookup msm_clocks_8960_dummy[]; +extern unsigned msm_num_clocks_8960_dummy; + +extern struct clk_lookup msm_clocks_8064_dummy[]; +extern unsigned msm_num_clocks_8064_dummy; + +extern struct platform_device msm_kgsl_3d0; +extern struct platform_device msm_kgsl_2d0; +extern struct platform_device msm_kgsl_2d1; + +extern struct platform_device msm_mipi_dsi1_device; + +extern struct clk_lookup msm_clocks_fsm9xxx[]; +extern unsigned msm_num_clocks_fsm9xxx; + +extern struct platform_device msm_footswitch; + +void __init msm_fb_register_device(char *name, void *data); +void __init msm_camera_register_device(void *, uint32_t, void *); +struct platform_device *msm_add_gsbi9_uart(void); +extern struct platform_device msm_device_touchscreen; + +extern struct pil_device peripheral_dsps; +extern struct platform_device led_pdev; #endif diff --git a/arch/arm/mach-msm/devices_htc.c b/arch/arm/mach-msm/devices_htc.c new file mode 100644 index 00000000000..b2b45723e2c --- /dev/null +++ b/arch/arm/mach-msm/devices_htc.c @@ -0,0 +1,450 @@ +/* linux/arch/arm/mach-msm/devices.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include +#include +#include +#include "gpio_chip.h" +#include "devices.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *df_serialno = "000000000000"; + +#if 0 +struct platform_device *devices[] __initdata = { + &msm_device_nand, + &msm_device_smd, + &msm_device_i2c, +}; + +void __init msm_add_devices(void) +{ + platform_add_devices(devices, ARRAY_SIZE(devices)); +} +#endif + +#define HSUSB_API_INIT_PHY_PROC 2 +#define HSUSB_API_PROG 0x30000064 +#define HSUSB_API_VERS 0x10001 +static void internal_phy_reset(void) +{ + struct msm_rpc_endpoint *usb_ep; + int rc; + struct hsusb_phy_start_req { + struct rpc_request_hdr hdr; + } req; + + printk(KERN_INFO "msm_hsusb_phy_reset\n"); + + usb_ep = msm_rpc_connect(HSUSB_API_PROG, HSUSB_API_VERS, 0); + if (IS_ERR(usb_ep)) { + printk(KERN_ERR "%s: init rpc failed! error: %ld\n", + __func__, PTR_ERR(usb_ep)); + goto close; + } + rc = msm_rpc_call(usb_ep, HSUSB_API_INIT_PHY_PROC, + &req, sizeof(req), 5 * HZ); + if (rc < 0) + printk(KERN_ERR "%s: rpc call failed! (%d)\n", __func__, rc); + +close: + msm_rpc_close(usb_ep); +} + +/* adjust eye diagram, disable vbusvalid interrupts */ +static int hsusb_phy_init_seq[] = { 0x40, 0x31, 0x1D, 0x0D, 0x1D, 0x10, -1 }; + +#ifdef CONFIG_USB_FUNCTION +static char *usb_functions[] = { +#if defined(CONFIG_USB_FUNCTION_MASS_STORAGE) || defined(CONFIG_USB_FUNCTION_UMS) + "usb_mass_storage", +#endif +#ifdef CONFIG_USB_FUNCTION_ADB + "adb", +#endif +}; + +static struct msm_hsusb_product usb_products[] = { + { + .product_id = 0x0c01, + .functions = 0x00000041, /* usb_mass_storage */ + }, + { + .product_id = 0x0c02, + .functions = 0x00000043, /* usb_mass_storage + adb */ + }, +}; +#endif + +struct msm_hsusb_platform_data msm_hsusb_pdata = { + .phy_reset = internal_phy_reset, + .phy_init_seq = hsusb_phy_init_seq, +#ifdef CONFIG_USB_FUNCTION + .vendor_id = 0x0bb4, + .product_id = 0x0c02, + .version = 0x0100, + .product_name = "Android Phone", + .manufacturer_name = "HTC", + + .functions = usb_functions, + .num_functions = ARRAY_SIZE(usb_functions), + .products = usb_products, + .num_products = ARRAY_SIZE(usb_products), +#endif +}; + +#ifdef CONFIG_USB_FUNCTION +static struct usb_mass_storage_platform_data mass_storage_pdata = { + .nluns = 1, + .buf_size = 16384, + .vendor = "HTC ", + .product = "Android Phone ", + .release = 0x0100, +}; + +static struct platform_device usb_mass_storage_device = { + .name = "usb_mass_storage", + .id = -1, + .dev = { + .platform_data = &mass_storage_pdata, + }, +}; +#endif + +#ifdef CONFIG_USB_ANDROID +static struct android_usb_platform_data android_usb_pdata = { + .vendor_id = 0x0bb4, + .product_id = 0x0c01, + .adb_product_id = 0x0c02, + .version = 0x0100, + .product_name = "Android Phone", + .manufacturer_name = "HTC", + .nluns = 1, +}; + +static struct platform_device android_usb_device = { + .name = "android_usb", + .id = -1, + .dev = { + .platform_data = &android_usb_pdata, + }, +}; +#endif + +void __init msm_add_usb_devices(void (*phy_reset) (void)) +{ + /* setup */ + if (phy_reset) + msm_hsusb_pdata.phy_reset = phy_reset; + msm_device_hsusb.dev.platform_data = &msm_hsusb_pdata; + platform_device_register(&msm_device_hsusb); +#ifdef CONFIG_USB_FUNCTION_MASS_STORAGE + platform_device_register(&usb_mass_storage_device); +#endif +#ifdef CONFIG_USB_ANDROID + platform_device_register(&android_usb_device); +#endif +} + +static struct android_pmem_platform_data pmem_pdata = { + .name = "pmem", + .allocator_type = PMEM_ALLOCATORTYPE_ALLORNOTHING, + .cached = 1, +}; + +static struct android_pmem_platform_data pmem_adsp_pdata = { + .name = "pmem_adsp", + .allocator_type = PMEM_ALLOCATORTYPE_BUDDYBESTFIT, + .cached = 0, +}; + +static struct android_pmem_platform_data pmem_camera_pdata = { + .name = "pmem_camera", + .allocator_type = PMEM_ALLOCATORTYPE_BUDDYBESTFIT, + .cached = 0, +}; + +static struct android_pmem_platform_data pmem_gpu0_pdata = { + .name = "pmem_gpu0", + .allocator_type = PMEM_ALLOCATORTYPE_ALLORNOTHING, + .cached = 0, + .buffered = 1, +}; + +static struct android_pmem_platform_data pmem_gpu1_pdata = { + .name = "pmem_gpu1", + .allocator_type = PMEM_ALLOCATORTYPE_ALLORNOTHING, + .cached = 0, + .buffered = 1, +}; + +static struct platform_device pmem_device = { + .name = "android_pmem", + .id = 0, + .dev = { .platform_data = &pmem_pdata }, +}; + +static struct platform_device pmem_adsp_device = { + .name = "android_pmem", + .id = 1, + .dev = { .platform_data = &pmem_adsp_pdata }, +}; + +static struct platform_device pmem_gpu0_device = { + .name = "android_pmem", + .id = 2, + .dev = { .platform_data = &pmem_gpu0_pdata }, +}; + +static struct platform_device pmem_gpu1_device = { + .name = "android_pmem", + .id = 3, + .dev = { .platform_data = &pmem_gpu1_pdata }, +}; + +static struct platform_device pmem_camera_device = { + .name = "android_pmem", + .id = 4, + .dev = { .platform_data = &pmem_camera_pdata }, +}; + +static struct resource ram_console_resource[] = { + { + .flags = IORESOURCE_MEM, + } +}; + +static struct platform_device ram_console_device = { + .name = "ram_console", + .id = -1, + .num_resources = ARRAY_SIZE(ram_console_resource), + .resource = ram_console_resource, +}; + +void __init msm_add_mem_devices(struct msm_pmem_setting *setting) +{ + if (setting->pmem_size) { + pmem_pdata.start = setting->pmem_start; + pmem_pdata.size = setting->pmem_size; + platform_device_register(&pmem_device); + } + + if (setting->pmem_adsp_size) { + pmem_adsp_pdata.start = setting->pmem_adsp_start; + pmem_adsp_pdata.size = setting->pmem_adsp_size; + platform_device_register(&pmem_adsp_device); + } + + if (setting->pmem_gpu0_size) { + pmem_gpu0_pdata.start = setting->pmem_gpu0_start; + pmem_gpu0_pdata.size = setting->pmem_gpu0_size; + platform_device_register(&pmem_gpu0_device); + } + + if (setting->pmem_gpu1_size) { + pmem_gpu1_pdata.start = setting->pmem_gpu1_start; + pmem_gpu1_pdata.size = setting->pmem_gpu1_size; + platform_device_register(&pmem_gpu1_device); + } + + if (setting->pmem_camera_size) { + pmem_camera_pdata.start = setting->pmem_camera_start; + pmem_camera_pdata.size = setting->pmem_camera_size; + platform_device_register(&pmem_camera_device); + } + + if (setting->ram_console_size) { + ram_console_resource[0].start = setting->ram_console_start; + ram_console_resource[0].end = setting->ram_console_start + + setting->ram_console_size - 1; + platform_device_register(&ram_console_device); + } +} + +#define PM_LIBPROG 0x30000061 +#if (CONFIG_MSM_AMSS_VERSION == 6220) || (CONFIG_MSM_AMSS_VERSION == 6225) +#define PM_LIBVERS 0xfb837d0b +#else +#define PM_LIBVERS 0x10001 +#endif + +#if 0 +static struct platform_device *msm_serial_devices[] __initdata = { + &msm_device_uart1, + &msm_device_uart2, + &msm_device_uart3, + #ifdef CONFIG_SERIAL_MSM_HS + &msm_device_uart_dm1, + &msm_device_uart_dm2, + #endif +}; + +int __init msm_add_serial_devices(unsigned num) +{ + if (num > MSM_SERIAL_NUM) + return -EINVAL; + + return platform_device_register(msm_serial_devices[num]); +} +#endif + +#define ATAG_SMI 0x4d534D71 +/* setup calls mach->fixup, then parse_tags, parse_cmdline + * We need to setup meminfo in mach->fixup, so this function + * will need to traverse each tag to find smi tag. + */ +int __init parse_tag_smi(const struct tag *tags) +{ + int smi_sz = 0, find = 0; + struct tag *t = (struct tag *)tags; + + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_SMI) { + printk(KERN_DEBUG "find the smi tag\n"); + find = 1; + break; + } + } + if (!find) + return -1; + + printk(KERN_DEBUG "parse_tag_smi: smi size = %d\n", t->u.mem.size); + smi_sz = t->u.mem.size; + return smi_sz; +} +__tagtable(ATAG_SMI, parse_tag_smi); + + +#define ATAG_HWID 0x4d534D72 +int __init parse_tag_hwid(const struct tag *tags) +{ + int hwid = 0, find = 0; + struct tag *t = (struct tag *)tags; + + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_HWID) { + printk(KERN_DEBUG "find the hwid tag\n"); + find = 1; + break; + } + } + + if (find) + hwid = t->u.revision.rev; + printk(KERN_DEBUG "parse_tag_hwid: hwid = 0x%x\n", hwid); + return hwid; +} +__tagtable(ATAG_HWID, parse_tag_hwid); + +#define ATAG_SKUID 0x4d534D73 +int __init parse_tag_skuid(const struct tag *tags) +{ + int skuid = 0, find = 0; + struct tag *t = (struct tag *)tags; + + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_SKUID) { + printk(KERN_DEBUG "find the skuid tag\n"); + find = 1; + break; + } + } + + if (find) + skuid = t->u.revision.rev; + printk(KERN_DEBUG "parse_tag_skuid: hwid = 0x%x\n", skuid); + return skuid; +} +__tagtable(ATAG_SKUID, parse_tag_skuid); + +#define ATAG_ENGINEERID 0x4d534D75 +int __init parse_tag_engineerid(const struct tag *tags) +{ + int engineerid = 0, find = 0; + struct tag *t = (struct tag *)tags; + + for (; t->hdr.size; t = tag_next(t)) { + if (t->hdr.tag == ATAG_ENGINEERID) { + printk(KERN_DEBUG "find the engineer tag\n"); + find = 1; + break; + } + } + + if (find) + engineerid = t->u.revision.rev; + printk(KERN_DEBUG "parse_tag_engineerid: hwid = 0x%x\n", engineerid); + return engineerid; +} +__tagtable(ATAG_ENGINEERID, parse_tag_engineerid); + +static int mfg_mode; +int __init board_mfg_mode_init(char *s) +{ + if (!strcmp(s, "normal")) + mfg_mode = 0; + else if (!strcmp(s, "factory2")) + mfg_mode = 1; + else if (!strcmp(s, "recovery")) + mfg_mode = 2; + else if (!strcmp(s, "charge")) + mfg_mode = 3; + + return 1; +} +__setup("androidboot.mode=", board_mfg_mode_init); + + +int board_mfg_mode(void) +{ + return mfg_mode; +} + +static int __init board_serialno_setup(char *serialno) +{ + char *str; + + if (board_mfg_mode() || !strlen(serialno)) + str = df_serialno; + else + str = serialno; +#ifdef CONFIG_USB_FUNCTION + msm_hsusb_pdata.serial_number = str; +#endif +#ifdef CONFIG_USB_ANDROID + android_usb_pdata.serial_number = str; +#endif + return 1; +} + +__setup("androidboot.serialno=", board_serialno_setup); diff --git a/arch/arm/mach-msm/dfe-fsm9xxx.c b/arch/arm/mach-msm/dfe-fsm9xxx.c new file mode 100644 index 00000000000..d372171d731 --- /dev/null +++ b/arch/arm/mach-msm/dfe-fsm9xxx.c @@ -0,0 +1,436 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * DFE of FSM9XXX + */ + +#define HH_ADDR_MASK 0x000ffffc +#define HH_OFFSET_VALID(offset) (((offset) & ~HH_ADDR_MASK) == 0) +#define HH_REG_IOADDR(offset) ((uint8_t *) MSM_HH_BASE + (offset)) +#define HH_MAKE_OFFSET(blk, adr) (((blk)&0x1F)<<15|((adr)&0x1FFF)<<2) + +#define HH_REG_SCPN_IREQ_MASK HH_REG_IOADDR(HH_MAKE_OFFSET(5, 0x12)) +#define HH_REG_SCPN_IREQ_FLAG HH_REG_IOADDR(HH_MAKE_OFFSET(5, 0x13)) + +/* + * Device private information per device node + */ + +#define HH_IRQ_FIFO_SIZE 64 +#define HH_IRQ_FIFO_EMPTY(pdev) ((pdev)->irq_fifo_head == \ + (pdev)->irq_fifo_tail) +#define HH_IRQ_FIFO_FULL(pdev) ((((pdev)->irq_fifo_tail + 1) % \ + HH_IRQ_FIFO_SIZE) == \ + (pdev)->irq_fifo_head) + +static struct hh_dev_node_info { + spinlock_t hh_lock; + char irq_fifo[HH_IRQ_FIFO_SIZE]; + unsigned int irq_fifo_head, irq_fifo_tail; + wait_queue_head_t wq; +} hh_dev_info; + +/* + * Device private information per file + */ + +struct hh_dev_file_info { + /* Buffer */ + unsigned int *parray; + unsigned int array_num; + + struct dfe_command_entry *pcmd; + unsigned int cmd_num; +}; + +/* + * File interface + */ + +static int hh_open(struct inode *inode, struct file *file) +{ + struct hh_dev_file_info *pdfi; + + /* private data allocation */ + pdfi = kmalloc(sizeof(*pdfi), GFP_KERNEL); + if (pdfi == NULL) + return -ENOMEM; + file->private_data = pdfi; + + /* buffer initialization */ + pdfi->parray = NULL; + pdfi->array_num = 0; + pdfi->pcmd = NULL; + pdfi->cmd_num = 0; + + return 0; +} + +static int hh_release(struct inode *inode, struct file *file) +{ + struct hh_dev_file_info *pdfi; + + pdfi = (struct hh_dev_file_info *) file->private_data; + + kfree(pdfi->parray); + pdfi->parray = NULL; + pdfi->array_num = 0; + + kfree(pdfi->pcmd); + pdfi->pcmd = NULL; + pdfi->cmd_num = 0; + + kfree(file->private_data); + file->private_data = NULL; + + return 0; +} + +static ssize_t hh_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + signed char irq = -1; + unsigned long irq_flags; + + do { + spin_lock_irqsave(&hh_dev_info.hh_lock, irq_flags); + if (!HH_IRQ_FIFO_EMPTY(&hh_dev_info)) { + irq = hh_dev_info.irq_fifo[hh_dev_info.irq_fifo_head]; + if (++hh_dev_info.irq_fifo_head == HH_IRQ_FIFO_SIZE) + hh_dev_info.irq_fifo_head = 0; + } + spin_unlock_irqrestore(&hh_dev_info.hh_lock, irq_flags); + + if (irq < 0) + if (wait_event_interruptible(hh_dev_info.wq, + !HH_IRQ_FIFO_EMPTY(&hh_dev_info)) < 0) + break; + } while (irq < 0); + + if (irq < 0) { + /* No pending interrupt */ + return 0; + } else { + put_user(irq, buf); + return 1; + } + + return 0; +} + +static ssize_t hh_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + return 0; +} + +static long hh_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + unsigned int __user *argp = (unsigned int __user *) arg; + struct hh_dev_file_info *pdfi = + (struct hh_dev_file_info *) file->private_data; + + switch (cmd) { + case DFE_IOCTL_IS_UMTS: + return __raw_readl(MSM_TCSR_BASE + 0x0008) & 0x01; + + case DFE_IOCTL_READ_REGISTER: + { + unsigned int offset, value; + + if (get_user(offset, argp)) + return -EFAULT; + if (!HH_OFFSET_VALID(offset)) + return -EINVAL; + value = __raw_readl(HH_REG_IOADDR(offset)); + if (put_user(value, argp)) + return -EFAULT; + } + break; + + case DFE_IOCTL_WRITE_REGISTER: + { + struct dfe_write_register_param param; + + if (copy_from_user(¶m, argp, sizeof param)) + return -EFAULT; + if (!HH_OFFSET_VALID(param.offset)) + return -EINVAL; + __raw_writel(param.value, + HH_REG_IOADDR(param.offset)); + } + break; + + case DFE_IOCTL_WRITE_REGISTER_WITH_MASK: + { + struct dfe_write_register_mask_param param; + unsigned int value; + unsigned long irq_flags; + + if (copy_from_user(¶m, argp, sizeof param)) + return -EFAULT; + if (!HH_OFFSET_VALID(param.offset)) + return -EINVAL; + spin_lock_irqsave(&hh_dev_info.hh_lock, + irq_flags); + value = __raw_readl(HH_REG_IOADDR(param.offset)); + value &= ~param.mask; + value |= param.value & param.mask; + __raw_writel(value, HH_REG_IOADDR(param.offset)); + spin_unlock_irqrestore(&hh_dev_info.hh_lock, + irq_flags); + } + break; + + case DFE_IOCTL_READ_REGISTER_ARRAY: + case DFE_IOCTL_WRITE_REGISTER_ARRAY: + { + struct dfe_read_write_array_param param; + unsigned int req_sz; + unsigned long irq_flags; + unsigned int i; + void *addr; + + if (copy_from_user(¶m, argp, sizeof param)) + return -EFAULT; + if (!HH_OFFSET_VALID(param.offset)) + return -EINVAL; + if (param.num == 0) + break; + req_sz = sizeof(unsigned int) * param.num; + + if (pdfi->array_num < param.num) { + void *pmem; + + pmem = kmalloc(req_sz, GFP_KERNEL); + if (pmem == NULL) + return -ENOMEM; + pdfi->parray = (unsigned int *) pmem; + pdfi->array_num = param.num; + } + + if (cmd == DFE_IOCTL_WRITE_REGISTER_ARRAY) + if (copy_from_user(pdfi->parray, + param.pArray, req_sz)) + return -EFAULT; + + addr = HH_REG_IOADDR(param.offset); + + spin_lock_irqsave(&hh_dev_info.hh_lock, + irq_flags); + for (i = 0; i < param.num; ++i, addr += 4) { + if (cmd == DFE_IOCTL_READ_REGISTER_ARRAY) + pdfi->parray[i] = __raw_readl(addr); + else + __raw_writel(pdfi->parray[i], addr); + } + spin_unlock_irqrestore(&hh_dev_info.hh_lock, + irq_flags); + + if (cmd == DFE_IOCTL_READ_REGISTER_ARRAY) + if (copy_to_user(pdfi->parray, + param.pArray, req_sz)) + return -EFAULT; + } + break; + + case DFE_IOCTL_COMMAND: + { + struct dfe_command_param param; + unsigned int req_sz; + unsigned long irq_flags; + unsigned int i, value; + struct dfe_command_entry *pcmd; + void *addr; + + if (copy_from_user(¶m, argp, sizeof param)) + return -EFAULT; + if (param.num == 0) + break; + req_sz = sizeof(struct dfe_command_entry) * param.num; + + if (pdfi->cmd_num < param.num) { + void *pmem; + + pmem = kmalloc(req_sz, GFP_KERNEL); + if (pmem == NULL) + return -ENOMEM; + pdfi->pcmd = (struct dfe_command_entry *) pmem; + pdfi->cmd_num = param.num; + } + + if (copy_from_user(pdfi->pcmd, param.pEntry, req_sz)) + return -EFAULT; + + pcmd = pdfi->pcmd; + + spin_lock_irqsave(&hh_dev_info.hh_lock, + irq_flags); + for (i = 0; i < param.num; ++i, ++pcmd) { + if (!HH_OFFSET_VALID(pcmd->offset)) + return -EINVAL; + addr = HH_REG_IOADDR(pcmd->offset); + + switch (pcmd->code) { + case DFE_IOCTL_COMMAND_CODE_WRITE: + __raw_writel(pcmd->value, addr); + break; + case DFE_IOCTL_COMMAND_CODE_WRITE_WITH_MASK: + value = __raw_readl(addr); + value &= ~pcmd->mask; + value |= pcmd->value & pcmd->mask; + __raw_writel(value, addr); + break; + } + } + spin_unlock_irqrestore(&hh_dev_info.hh_lock, + irq_flags); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +static unsigned int hh_poll(struct file *filp, + struct poll_table_struct *wait) +{ + unsigned mask = 0; + + if (!HH_IRQ_FIFO_EMPTY(&hh_dev_info)) + mask |= POLLIN; + + if (mask == 0) { + poll_wait(filp, &hh_dev_info.wq, wait); + if (!HH_IRQ_FIFO_EMPTY(&hh_dev_info)) + mask |= POLLIN; + } + + return mask; +} + +static const struct file_operations hh_fops = { + .owner = THIS_MODULE, + .open = hh_open, + .release = hh_release, + .read = hh_read, + .write = hh_write, + .unlocked_ioctl = hh_ioctl, + .poll = hh_poll, +}; + +/* + * Interrupt handling + */ + +static irqreturn_t hh_irq_handler(int irq, void *data) +{ + unsigned int irq_enable, irq_flag, irq_mask; + int i; + + irq_enable = __raw_readl(HH_REG_SCPN_IREQ_MASK); + irq_flag = __raw_readl(HH_REG_SCPN_IREQ_FLAG); + irq_flag &= irq_enable; + + /* Disables interrupts */ + irq_enable &= ~irq_flag; + __raw_writel(irq_enable, HH_REG_SCPN_IREQ_MASK); + + /* Adds the pending interrupts to irq_fifo */ + spin_lock(&hh_dev_info.hh_lock); + for (i = 0, irq_mask = 1; i < 32; ++i, irq_mask <<= 1) { + if (HH_IRQ_FIFO_FULL(&hh_dev_info)) + break; + if (irq_flag & irq_mask) { + hh_dev_info.irq_fifo[hh_dev_info.irq_fifo_tail] = \ + (char) i; + if (++hh_dev_info.irq_fifo_tail == HH_IRQ_FIFO_SIZE) + hh_dev_info.irq_fifo_tail = 0; + } + } + spin_unlock(&hh_dev_info.hh_lock); + + /* Wakes up pending processes */ + wake_up_interruptible(&hh_dev_info.wq); + + return IRQ_HANDLED; +} + +/* + * Driver initialization & cleanup + */ + +static struct miscdevice hh_misc_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = DFE_HH_DEVICE_NAME, + .fops = &hh_fops, +}; + +static int __init hh_init(void) +{ + int ret; + + /* lock initialization */ + spin_lock_init(&hh_dev_info.hh_lock); + + /* interrupt handler */ + hh_dev_info.irq_fifo_head = 0; + hh_dev_info.irq_fifo_tail = 0; + ret = request_irq(INT_HH_SUPSS_IRQ, hh_irq_handler, + IRQF_TRIGGER_RISING, "hh_dev", 0); + if (ret < 0) { + pr_err("Cannot register HH interrupt handler.\n"); + return ret; + } + + /* wait queue */ + init_waitqueue_head(&hh_dev_info.wq); + + return misc_register(&hh_misc_dev); +} + +static void __exit hh_exit(void) +{ + misc_deregister(&hh_misc_dev); + free_irq(INT_HH_SUPSS_IRQ, 0); +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Rohit Vaswani "); +MODULE_DESCRIPTION("Qualcomm Hammerhead Digital Front End driver"); +MODULE_VERSION("1.0"); + +module_init(hh_init); +module_exit(hh_exit); + diff --git a/arch/arm/mach-msm/dma.c b/arch/arm/mach-msm/dma.c index 02cae5e2951..5899d2bbef2 100644 --- a/arch/arm/mach-msm/dma.c +++ b/arch/arm/mach-msm/dma.c @@ -1,6 +1,7 @@ /* linux/arch/arm/mach-msm/dma.c * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2010, Code Aurora Forum. 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 @@ -17,10 +18,188 @@ #include #include #include -#include +#include +#include +#include +#include #include +#define MODULE_NAME "msm_dmov" + #define MSM_DMOV_CHANNEL_COUNT 16 +#define MSM_DMOV_CRCI_COUNT 16 + +enum { + CLK_DIS, + CLK_TO_BE_DIS, + CLK_EN +}; + +struct msm_dmov_ci_conf { + int start; + int end; + int burst; +}; + +struct msm_dmov_crci_conf { + int sd; + int blk_size; +}; + +struct msm_dmov_chan_conf { + int sd; + int block; + int priority; +}; + +struct msm_dmov_conf { + void *base; + struct msm_dmov_crci_conf *crci_conf; + struct msm_dmov_chan_conf *chan_conf; + int channel_active; + struct list_head ready_commands[MSM_DMOV_CHANNEL_COUNT]; + struct list_head active_commands[MSM_DMOV_CHANNEL_COUNT]; + unsigned int crci_mask; + spinlock_t lock; + unsigned int irq; + struct clk *clk; + struct clk *pclk; + struct clk *ebiclk; + unsigned int clk_ctl; + struct timer_list timer; +}; + +static void msm_dmov_clock_timer(unsigned long); +static int msm_dmov_clk_toggle(int, int); + +#ifdef CONFIG_ARCH_MSM8X60 + +#define DMOV_CHANNEL_DEFAULT_CONF { .sd = 1, .block = 0, .priority = 0 } +#define DMOV_CHANNEL_MODEM_CONF { .sd = 3, .block = 0, .priority = 0 } +#define DMOV_CHANNEL_CONF(secd, blk, pri) \ + { .sd = secd, .block = blk, .priority = pri } + +static struct msm_dmov_chan_conf adm0_chan_conf[] = { + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_DEFAULT_CONF, +}; + +static struct msm_dmov_chan_conf adm1_chan_conf[] = { + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_DEFAULT_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_MODEM_CONF, + DMOV_CHANNEL_MODEM_CONF, +}; + +#define DMOV_CRCI_DEFAULT_CONF { .sd = 1, .blk_size = 0 } +#define DMOV_CRCI_CONF(secd, blk) { .sd = secd, .blk_size = blk } + +static struct msm_dmov_crci_conf adm0_crci_conf[] = { + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_CONF(1, 4), + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, +}; + +static struct msm_dmov_crci_conf adm1_crci_conf[] = { + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_CONF(1, 1), + DMOV_CRCI_CONF(1, 1), + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_CONF(1, 1), + DMOV_CRCI_CONF(1, 1), + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_DEFAULT_CONF, + DMOV_CRCI_CONF(1, 1), + DMOV_CRCI_DEFAULT_CONF, +}; + +static struct msm_dmov_conf dmov_conf[] = { + { + .crci_conf = adm0_crci_conf, + .chan_conf = adm0_chan_conf, + .lock = __SPIN_LOCK_UNLOCKED(dmov_lock), + .clk_ctl = CLK_DIS, + .timer = TIMER_INITIALIZER(msm_dmov_clock_timer, 0, 0), + }, { + .crci_conf = adm1_crci_conf, + .chan_conf = adm1_chan_conf, + .lock = __SPIN_LOCK_UNLOCKED(dmov_lock), + .clk_ctl = CLK_DIS, + .timer = TIMER_INITIALIZER(msm_dmov_clock_timer, 0, 1), + } +}; +#else +static struct msm_dmov_conf dmov_conf[] = { + { + .crci_conf = NULL, + .chan_conf = NULL, + .lock = __SPIN_LOCK_UNLOCKED(dmov_lock), + .clk_ctl = CLK_DIS, + .timer = TIMER_INITIALIZER(msm_dmov_clock_timer, 0, 0), + } +}; +#endif + +#define MSM_DMOV_ID_COUNT (MSM_DMOV_CHANNEL_COUNT * ARRAY_SIZE(dmov_conf)) +#define DMOV_REG(name, adm) ((name) + (dmov_conf[adm].base)) +#define DMOV_ID_TO_ADM(id) ((id) / MSM_DMOV_CHANNEL_COUNT) +#define DMOV_ID_TO_CHAN(id) ((id) % MSM_DMOV_CHANNEL_COUNT) +#define DMOV_CHAN_ADM_TO_ID(ch, adm) ((ch) + (adm) * MSM_DMOV_CHANNEL_COUNT) + +#ifdef CONFIG_MSM_ADM3 +#define DMOV_IRQ_TO_ADM(irq) \ +({ \ + typeof(irq) _irq = irq; \ + ((_irq == INT_ADM1_MASTER) || (_irq == INT_ADM1_AARM)); \ +}) +#else +#define DMOV_IRQ_TO_ADM(irq) 0 +#endif enum { MSM_DMOV_PRINT_ERRORS = 1, @@ -28,11 +207,6 @@ enum { MSM_DMOV_PRINT_FLOW = 4 }; -static DEFINE_SPINLOCK(msm_dmov_lock); -static struct clk *msm_dmov_clk; -static unsigned int channel_active; -static struct list_head ready_commands[MSM_DMOV_CHANNEL_COUNT]; -static struct list_head active_commands[MSM_DMOV_CHANNEL_COUNT]; unsigned int msm_dmov_print_mask = MSM_DMOV_PRINT_ERRORS; #define MSM_DMOV_DPRINTF(mask, format, args...) \ @@ -47,48 +221,252 @@ unsigned int msm_dmov_print_mask = MSM_DMOV_PRINT_ERRORS; #define PRINT_FLOW(format, args...) \ MSM_DMOV_DPRINTF(MSM_DMOV_PRINT_FLOW, format, args); -void msm_dmov_stop_cmd(unsigned id, struct msm_dmov_cmd *cmd, int graceful) +static int msm_dmov_clk_toggle(int adm, int on) { - writel((graceful << 31), DMOV_FLUSH0(id)); + int ret = 0; + + if (on) { + ret = clk_enable(dmov_conf[adm].clk); + if (ret) + goto err; + if (dmov_conf[adm].pclk) { + ret = clk_enable(dmov_conf[adm].pclk); + if (ret) { + clk_disable(dmov_conf[adm].clk); + goto err; + } + } + if (dmov_conf[adm].ebiclk) { + ret = clk_enable(dmov_conf[adm].ebiclk); + if (ret) { + if (dmov_conf[adm].pclk) + clk_disable(dmov_conf[adm].pclk); + clk_disable(dmov_conf[adm].clk); + } + } + } else { + clk_disable(dmov_conf[adm].clk); + if (dmov_conf[adm].pclk) + clk_disable(dmov_conf[adm].pclk); + if (dmov_conf[adm].ebiclk) + clk_disable(dmov_conf[adm].ebiclk); + } +err: + return ret; } -void msm_dmov_enqueue_cmd(unsigned id, struct msm_dmov_cmd *cmd) +static void msm_dmov_clock_timer(unsigned long adm) { unsigned long irq_flags; - unsigned int status; + spin_lock_irqsave(&dmov_conf[adm].lock, irq_flags); + if (dmov_conf[adm].clk_ctl == CLK_TO_BE_DIS) { + BUG_ON(dmov_conf[adm].channel_active); + msm_dmov_clk_toggle(adm, 0); + dmov_conf[adm].clk_ctl = CLK_DIS; + } + spin_unlock_irqrestore(&dmov_conf[adm].lock, irq_flags); +} + +void msm_dmov_stop_cmd(unsigned id, struct msm_dmov_cmd *cmd, int graceful) +{ + int adm = DMOV_ID_TO_ADM(id); + int ch = DMOV_ID_TO_CHAN(id); + writel_relaxed((graceful << 31), DMOV_REG(DMOV_FLUSH0(ch), adm)); + wmb(); +} +EXPORT_SYMBOL(msm_dmov_stop_cmd); + +#define CRCI_UNUSED 0 +#define CRCI_CONFLICT 1 +#define CRCI_MUX_OFF 2 +#define CRCI_MUX_ON 3 + +#ifdef CONFIG_MSM_ADM3 +static int crci_mask_compare(unsigned int x, unsigned int y) +{ + unsigned int mask; + int i; + for (i = 0; i < MSM_DMOV_CRCI_COUNT; i++) { + mask = (x ^ y) >> (2*i); + if ((mask & 3) == CRCI_CONFLICT) + return 1; + } + return 0; +} +#endif + +static int check_crci_conflict(struct msm_dmov_cmd *cmd, int adm) +{ +#ifdef CONFIG_MSM_ADM3 + int i; + struct msm_dmov_cmd *iter; + struct list_head *cmd_list; + unsigned int active_crci_mask = 0; + + for (i = 0; i < MSM_DMOV_CHANNEL_COUNT; i++) { + cmd_list = &dmov_conf[adm].active_commands[i]; + list_for_each_entry(iter, cmd_list, list) { + active_crci_mask |= iter->crci_mask; + } + } + return crci_mask_compare(cmd->crci_mask, active_crci_mask); +#endif + return 0; +} + +#define CRCI_MUXSEL(n) (((n) >> 4) & 1) +#define CRCI_NUM(n) ((n) & 0xF) + +unsigned int msm_dmov_build_crci_mask(int n, ...) +{ + unsigned int mask = 0; +#ifdef CONFIG_MSM_ADM3 + int i; + int crci; + int crci_num; + unsigned int crci_muxsel; + va_list crcis; + va_start(crcis, n); + for (i = 0; i < n; i++) { + crci = va_arg(crcis, int); + crci_muxsel = CRCI_MUXSEL(crci); + crci_num = CRCI_NUM(crci); + mask |= (1 << (2*crci_num + 1)); + mask |= (crci_muxsel << (2*crci_num)); + } + va_end(crcis); +#endif + return mask; +} +EXPORT_SYMBOL(msm_dmov_build_crci_mask); + + +static void set_crci_mask(int crci_mask, int adm) +{ +#ifdef CONFIG_MSM_ADM3 + int i; + int blk_size; + unsigned int crci_ctl; + unsigned int tmp_crci_mask; + unsigned int blank_mask; + + for (i = 0; i < MSM_DMOV_CRCI_COUNT; i++) { + tmp_crci_mask = (crci_mask >> (2*i)) & 3; + if (crci_mask_compare(dmov_conf[adm].crci_mask, + tmp_crci_mask << (2*i))) { + blank_mask = ~(3 << (2*i)); + blk_size = dmov_conf[adm].crci_conf[i].blk_size; + crci_ctl = DMOV_CRCI_CTL_BLK_SZ(blk_size); + if (tmp_crci_mask == CRCI_MUX_ON) + crci_ctl |= DMOV_CRCI_MUX; - spin_lock_irqsave(&msm_dmov_lock, irq_flags); - if (!channel_active) - clk_enable(msm_dmov_clk); - dsb(); - status = readl(DMOV_STATUS(id)); - if (list_empty(&ready_commands[id]) && - (status & DMOV_STATUS_CMD_PTR_RDY)) { -#if 0 - if (list_empty(&active_commands[id])) { - PRINT_FLOW("msm_dmov_enqueue_cmd(%d), enable interrupt\n", id); - writel(DMOV_CONFIG_IRQ_EN, DMOV_CONFIG(id)); + writel_relaxed(crci_ctl, DMOV_REG(DMOV_CRCI_CTL(i), + adm)); + dmov_conf[adm].crci_mask &= blank_mask; + dmov_conf[adm].crci_mask |= (tmp_crci_mask << (2*i)); + } + } + wmb(); +#endif +} + +static void start_ready_cmds(int adm) +{ +#ifdef CONFIG_MSM_ADM3 + int i; + unsigned int status; + struct list_head *rdy; + struct list_head *act; + struct msm_dmov_cmd *cmd; + for (i = 0; i < MSM_DMOV_CHANNEL_COUNT; i++) { + rdy = &dmov_conf[adm].ready_commands[i]; + act = &dmov_conf[adm].active_commands[i]; + cmd = list_entry(rdy->next, typeof(*cmd), list); + if (!list_empty(rdy) && !check_crci_conflict(cmd, adm)) { + status = readl_relaxed(DMOV_REG(DMOV_STATUS(i), adm)); + if (status & DMOV_STATUS_CMD_PTR_RDY) { + list_del(&cmd->list); + list_add_tail(&cmd->list, act); + dmov_conf[adm].channel_active |= (1 << i); + set_crci_mask(cmd->crci_mask, adm); + writel_relaxed(cmd->cmdptr, + DMOV_REG(DMOV_CMD_PTR(i), adm)); + } } + } #endif - if (cmd->execute_func) - cmd->execute_func(cmd); - PRINT_IO("msm_dmov_enqueue_cmd(%d), start command, status %x\n", id, status); - list_add_tail(&cmd->list, &active_commands[id]); - if (!channel_active) - enable_irq(INT_ADM_AARM); - channel_active |= 1U << id; - writel(cmd->cmdptr, DMOV_CMD_PTR(id)); +} + +void msm_dmov_enqueue_cmd_ext(unsigned id, struct msm_dmov_cmd *cmd) +{ + unsigned long irq_flags; + unsigned int status; + int conflict; + int adm = DMOV_ID_TO_ADM(id); + int ch = DMOV_ID_TO_CHAN(id); + + spin_lock_irqsave(&dmov_conf[adm].lock, irq_flags); + if (dmov_conf[adm].clk_ctl == CLK_DIS) + msm_dmov_clk_toggle(adm, 1); + else if (dmov_conf[adm].clk_ctl == CLK_TO_BE_DIS) + del_timer(&dmov_conf[adm].timer); + dmov_conf[adm].clk_ctl = CLK_EN; + + status = readl_relaxed(DMOV_REG(DMOV_STATUS(ch), adm)); + conflict = check_crci_conflict(cmd, adm); + if ((status & DMOV_STATUS_CMD_PTR_RDY) && !conflict) { + PRINT_IO("msm_dmov_enqueue_cmd(%d), start command, status %x\n", + id, status); + if (cmd->exec_func) + cmd->exec_func(cmd); + list_add_tail(&cmd->list, &dmov_conf[adm].active_commands[ch]); + if (!dmov_conf[adm].channel_active) + enable_irq(dmov_conf[adm].irq); + dmov_conf[adm].channel_active |= 1U << ch; + PRINT_IO("Writing %x exactly to register", cmd->cmdptr); + set_crci_mask(cmd->crci_mask, adm); + writel_relaxed(cmd->cmdptr, DMOV_REG(DMOV_CMD_PTR(ch), adm)); } else { - if (!channel_active) - clk_disable(msm_dmov_clk); - if (list_empty(&active_commands[id])) - PRINT_ERROR("msm_dmov_enqueue_cmd(%d), error datamover stalled, status %x\n", id, status); + if (!dmov_conf[adm].channel_active) { + dmov_conf[adm].clk_ctl = CLK_TO_BE_DIS; + mod_timer(&dmov_conf[adm].timer, jiffies + HZ); + } + if (list_empty(&dmov_conf[adm].active_commands[ch]) + && !conflict) + PRINT_ERROR("msm_dmov_enqueue_cmd_ext(%d), stalled, " + "status %x\n", id, status); + PRINT_IO("msm_dmov_enqueue_cmd(%d), enqueue command, status " + "%x\n", id, status); + list_add_tail(&cmd->list, &dmov_conf[adm].ready_commands[ch]); + } + spin_unlock_irqrestore(&dmov_conf[adm].lock, irq_flags); +} +EXPORT_SYMBOL(msm_dmov_enqueue_cmd_ext); - PRINT_IO("msm_dmov_enqueue_cmd(%d), enqueue command, status %x\n", id, status); - list_add_tail(&cmd->list, &ready_commands[id]); +void msm_dmov_enqueue_cmd(unsigned id, struct msm_dmov_cmd *cmd) +{ + /* Disable callback function (for backwards compatibility) */ + cmd->exec_func = NULL; + + msm_dmov_enqueue_cmd_ext(id, cmd); +} +EXPORT_SYMBOL(msm_dmov_enqueue_cmd); + +void msm_dmov_flush(unsigned int id) +{ + unsigned long irq_flags; + int ch = DMOV_ID_TO_CHAN(id); + int adm = DMOV_ID_TO_ADM(id); + spin_lock_irqsave(&dmov_conf[adm].lock, irq_flags); + /* XXX not checking if flush cmd sent already */ + if (!list_empty(&dmov_conf[adm].active_commands[ch])) { + PRINT_IO("msm_dmov_flush(%d), send flush cmd\n", id); + writel_relaxed(DMOV_FLUSH_TYPE, DMOV_REG(DMOV_FLUSH0(ch), adm)); } - spin_unlock_irqrestore(&msm_dmov_lock, irq_flags); + /* spin_unlock_irqrestore has the necessary barrier */ + spin_unlock_irqrestore(&dmov_conf[adm].lock, irq_flags); } +EXPORT_SYMBOL(msm_dmov_flush); struct msm_dmov_exec_cmdptr_cmd { struct msm_dmov_cmd dmov_cmd; @@ -111,20 +489,22 @@ dmov_exec_cmdptr_complete_func(struct msm_dmov_cmd *_cmd, complete(&cmd->complete); } -int msm_dmov_exec_cmd(unsigned id, unsigned int cmdptr) +int msm_dmov_exec_cmd(unsigned id, unsigned int crci_mask, unsigned int cmdptr) { struct msm_dmov_exec_cmdptr_cmd cmd; PRINT_FLOW("dmov_exec_cmdptr(%d, %x)\n", id, cmdptr); cmd.dmov_cmd.cmdptr = cmdptr; + cmd.dmov_cmd.crci_mask = crci_mask; cmd.dmov_cmd.complete_func = dmov_exec_cmdptr_complete_func; - cmd.dmov_cmd.execute_func = NULL; + cmd.dmov_cmd.exec_func = NULL; cmd.id = id; + cmd.result = 0; init_completion(&cmd.complete); msm_dmov_enqueue_cmd(id, &cmd.dmov_cmd); - wait_for_completion(&cmd.complete); + wait_for_completion_io(&cmd.complete); if (cmd.result != 0x80000002) { PRINT_ERROR("dmov_exec_cmdptr(%d): ERROR, result: %x\n", id, cmd.result); @@ -135,40 +515,59 @@ int msm_dmov_exec_cmd(unsigned id, unsigned int cmdptr) PRINT_FLOW("dmov_exec_cmdptr(%d, %x) done\n", id, cmdptr); return 0; } +EXPORT_SYMBOL(msm_dmov_exec_cmd); +static void fill_errdata(struct msm_dmov_errdata *errdata, int ch, int adm) +{ + errdata->flush[0] = readl_relaxed(DMOV_REG(DMOV_FLUSH0(ch), adm)); + errdata->flush[1] = readl_relaxed(DMOV_REG(DMOV_FLUSH1(ch), adm)); + errdata->flush[2] = readl_relaxed(DMOV_REG(DMOV_FLUSH2(ch), adm)); + errdata->flush[3] = readl_relaxed(DMOV_REG(DMOV_FLUSH3(ch), adm)); + errdata->flush[4] = readl_relaxed(DMOV_REG(DMOV_FLUSH4(ch), adm)); + errdata->flush[5] = readl_relaxed(DMOV_REG(DMOV_FLUSH5(ch), adm)); +} static irqreturn_t msm_datamover_irq_handler(int irq, void *dev_id) { - unsigned int int_status, mask, id; + unsigned int int_status; + unsigned int mask; + unsigned int id; + unsigned int ch; unsigned long irq_flags; unsigned int ch_status; unsigned int ch_result; struct msm_dmov_cmd *cmd; + int adm = DMOV_IRQ_TO_ADM(irq); - spin_lock_irqsave(&msm_dmov_lock, irq_flags); - - int_status = readl(DMOV_ISR); /* read and clear interrupt */ + spin_lock_irqsave(&dmov_conf[adm].lock, irq_flags); + /* read and clear isr */ + int_status = readl_relaxed(DMOV_REG(DMOV_ISR, adm)); PRINT_FLOW("msm_datamover_irq_handler: DMOV_ISR %x\n", int_status); while (int_status) { mask = int_status & -int_status; - id = fls(mask) - 1; + ch = fls(mask) - 1; + id = DMOV_CHAN_ADM_TO_ID(ch, adm); PRINT_FLOW("msm_datamover_irq_handler %08x %08x id %d\n", int_status, mask, id); int_status &= ~mask; - ch_status = readl(DMOV_STATUS(id)); + ch_status = readl_relaxed(DMOV_REG(DMOV_STATUS(ch), adm)); if (!(ch_status & DMOV_STATUS_RSLT_VALID)) { - PRINT_FLOW("msm_datamover_irq_handler id %d, result not valid %x\n", id, ch_status); + PRINT_FLOW("msm_datamover_irq_handler id %d, " + "result not valid %x\n", id, ch_status); continue; } do { - ch_result = readl(DMOV_RSLT(id)); - if (list_empty(&active_commands[id])) { + ch_result = readl_relaxed(DMOV_REG(DMOV_RSLT(ch), adm)); + if (list_empty(&dmov_conf[adm].active_commands[ch])) { PRINT_ERROR("msm_datamover_irq_handler id %d, got result " "with no active command, status %x, result %x\n", id, ch_status, ch_result); cmd = NULL; - } else - cmd = list_entry(active_commands[id].next, typeof(*cmd), list); + } else { + cmd = list_entry(dmov_conf[adm]. + active_commands[ch].next, typeof(*cmd), + list); + } PRINT_FLOW("msm_datamover_irq_handler id %d, status %x, result %x\n", id, ch_status, ch_result); if (ch_result & DMOV_RSLT_DONE) { PRINT_FLOW("msm_datamover_irq_handler id %d, status %x\n", @@ -177,95 +576,231 @@ static irqreturn_t msm_datamover_irq_handler(int irq, void *dev_id) "for %p, result %x\n", id, cmd, ch_result); if (cmd) { list_del(&cmd->list); - dsb(); cmd->complete_func(cmd, ch_result, NULL); } } if (ch_result & DMOV_RSLT_FLUSH) { struct msm_dmov_errdata errdata; - errdata.flush[0] = readl(DMOV_FLUSH0(id)); - errdata.flush[1] = readl(DMOV_FLUSH1(id)); - errdata.flush[2] = readl(DMOV_FLUSH2(id)); - errdata.flush[3] = readl(DMOV_FLUSH3(id)); - errdata.flush[4] = readl(DMOV_FLUSH4(id)); - errdata.flush[5] = readl(DMOV_FLUSH5(id)); + fill_errdata(&errdata, ch, adm); PRINT_FLOW("msm_datamover_irq_handler id %d, status %x\n", id, ch_status); PRINT_FLOW("msm_datamover_irq_handler id %d, flush, result %x, flush0 %x\n", id, ch_result, errdata.flush[0]); if (cmd) { list_del(&cmd->list); - dsb(); cmd->complete_func(cmd, ch_result, &errdata); } } if (ch_result & DMOV_RSLT_ERROR) { struct msm_dmov_errdata errdata; - errdata.flush[0] = readl(DMOV_FLUSH0(id)); - errdata.flush[1] = readl(DMOV_FLUSH1(id)); - errdata.flush[2] = readl(DMOV_FLUSH2(id)); - errdata.flush[3] = readl(DMOV_FLUSH3(id)); - errdata.flush[4] = readl(DMOV_FLUSH4(id)); - errdata.flush[5] = readl(DMOV_FLUSH5(id)); + fill_errdata(&errdata, ch, adm); PRINT_ERROR("msm_datamover_irq_handler id %d, status %x\n", id, ch_status); PRINT_ERROR("msm_datamover_irq_handler id %d, error, result %x, flush0 %x\n", id, ch_result, errdata.flush[0]); if (cmd) { list_del(&cmd->list); - dsb(); cmd->complete_func(cmd, ch_result, &errdata); } /* this does not seem to work, once we get an error */ /* the datamover will no longer accept commands */ - writel(0, DMOV_FLUSH0(id)); + writel_relaxed(0, DMOV_REG(DMOV_FLUSH0(ch), + adm)); } - ch_status = readl(DMOV_STATUS(id)); + rmb(); + ch_status = readl_relaxed(DMOV_REG(DMOV_STATUS(ch), + adm)); +#ifndef CONFIG_MSM_ADM3 PRINT_FLOW("msm_datamover_irq_handler id %d, status %x\n", id, ch_status); - if ((ch_status & DMOV_STATUS_CMD_PTR_RDY) && !list_empty(&ready_commands[id])) { - cmd = list_entry(ready_commands[id].next, typeof(*cmd), list); + if ((ch_status & DMOV_STATUS_CMD_PTR_RDY) && + !list_empty(&dmov_conf[adm].ready_commands[ch])) { + cmd = list_entry(dmov_conf[adm]. + ready_commands[ch].next, typeof(*cmd), + list); list_del(&cmd->list); - list_add_tail(&cmd->list, &active_commands[id]); - if (cmd->execute_func) - cmd->execute_func(cmd); + if (cmd->exec_func) + cmd->exec_func(cmd); + list_add_tail(&cmd->list, + &dmov_conf[adm].active_commands[ch]); PRINT_FLOW("msm_datamover_irq_handler id %d, start command\n", id); - writel(cmd->cmdptr, DMOV_CMD_PTR(id)); + writel_relaxed(cmd->cmdptr, + DMOV_REG(DMOV_CMD_PTR(ch), adm)); } +#endif } while (ch_status & DMOV_STATUS_RSLT_VALID); - if (list_empty(&active_commands[id]) && list_empty(&ready_commands[id])) - channel_active &= ~(1U << id); + if (list_empty(&dmov_conf[adm].active_commands[ch]) && + list_empty(&dmov_conf[adm].ready_commands[ch])) + dmov_conf[adm].channel_active &= ~(1U << ch); PRINT_FLOW("msm_datamover_irq_handler id %d, status %x\n", id, ch_status); } - if (!channel_active) { - disable_irq_nosync(INT_ADM_AARM); - clk_disable(msm_dmov_clk); + start_ready_cmds(adm); + if (!dmov_conf[adm].channel_active) { + disable_irq_nosync(dmov_conf[adm].irq); + dmov_conf[adm].clk_ctl = CLK_TO_BE_DIS; + mod_timer(&dmov_conf[adm].timer, jiffies + HZ); } - spin_unlock_irqrestore(&msm_dmov_lock, irq_flags); + spin_unlock_irqrestore(&dmov_conf[adm].lock, irq_flags); return IRQ_HANDLED; } -static int __init msm_init_datamover(void) +static int msm_dmov_suspend_late(struct device *dev) +{ + unsigned long irq_flags; + struct platform_device *pdev = to_platform_device(dev); + int adm = (pdev->id >= 0) ? pdev->id : 0; + spin_lock_irqsave(&dmov_conf[adm].lock, irq_flags); + if (dmov_conf[adm].clk_ctl == CLK_TO_BE_DIS) { + BUG_ON(dmov_conf[adm].channel_active); + del_timer(&dmov_conf[adm].timer); + msm_dmov_clk_toggle(adm, 0); + dmov_conf[adm].clk_ctl = CLK_DIS; + } + spin_unlock_irqrestore(&dmov_conf[adm].lock, irq_flags); + return 0; +} + +static int msm_dmov_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int msm_dmov_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static int msm_dmov_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: idling...\n"); + return 0; +} + +static struct dev_pm_ops msm_dmov_dev_pm_ops = { + .runtime_suspend = msm_dmov_runtime_suspend, + .runtime_resume = msm_dmov_runtime_resume, + .runtime_idle = msm_dmov_runtime_idle, + .suspend = msm_dmov_suspend_late, +}; + +static int msm_dmov_init_clocks(struct platform_device *pdev) +{ + int adm = (pdev->id >= 0) ? pdev->id : 0; + int ret; + + dmov_conf[adm].clk = clk_get(&pdev->dev, "adm_clk"); + if (IS_ERR(dmov_conf[adm].clk)) { + printk(KERN_ERR "%s: Error getting adm_clk\n", __func__); + dmov_conf[adm].clk = NULL; + return -ENOENT; + } + + dmov_conf[adm].pclk = clk_get(&pdev->dev, "adm_pclk"); + if (IS_ERR(dmov_conf[adm].pclk)) { + dmov_conf[adm].pclk = NULL; + /* pclk not present on all SoCs, don't bail on failure */ + } + + dmov_conf[adm].ebiclk = clk_get(&pdev->dev, "ebi1_clk"); + if (IS_ERR(dmov_conf[adm].ebiclk)) { + dmov_conf[adm].ebiclk = NULL; + /* ebiclk not present on all SoCs, don't bail on failure */ + } else { + ret = clk_set_rate(dmov_conf[adm].ebiclk, 27000000); + if (ret) + return -ENOENT; + } + + return 0; +} + +static void config_datamover(int adm) +{ +#ifdef CONFIG_MSM_ADM3 + int i; + for (i = 0; i < MSM_DMOV_CHANNEL_COUNT; i++) { + struct msm_dmov_chan_conf *chan_conf = + dmov_conf[adm].chan_conf; + unsigned conf; + /* Only configure scorpion channels */ + if (chan_conf[i].sd <= 1) { + conf = readl_relaxed(DMOV_REG(DMOV_CONF(i), adm)); + conf &= ~DMOV_CONF_SD(7); + conf |= DMOV_CONF_SD(chan_conf[i].sd); + writel_relaxed(conf | DMOV_CONF_SHADOW_EN, + DMOV_REG(DMOV_CONF(i), adm)); + } + } + for (i = 0; i < MSM_DMOV_CRCI_COUNT; i++) { + struct msm_dmov_crci_conf *crci_conf = + dmov_conf[adm].crci_conf; + + writel_relaxed(DMOV_CRCI_CTL_BLK_SZ(crci_conf[i].blk_size), + DMOV_REG(DMOV_CRCI_CTL(i), adm)); + } +#endif +} + +static int msm_dmov_probe(struct platform_device *pdev) { + int adm = (pdev->id >= 0) ? pdev->id : 0; int i; int ret; - struct clk *clk; + struct resource *res = + platform_get_resource(pdev, IORESOURCE_IRQ, 0); + + if (res) { + dmov_conf[adm].irq = res->start; + dmov_conf[adm].base = (void *)res->end; + } + if (!dmov_conf[adm].base || !dmov_conf[adm].irq) + return -ENXIO; + + ret = request_irq(dmov_conf[adm].irq, msm_datamover_irq_handler, + 0, "msmdatamover", NULL); + if (ret) { + PRINT_ERROR("Requesting ADM%d irq %d failed\n", adm, + dmov_conf[adm].irq); + return ret; + } + disable_irq(dmov_conf[adm].irq); + ret = msm_dmov_init_clocks(pdev); + if (ret) { + PRINT_ERROR("Requesting ADM%d clocks failed\n", adm); + return -ENOENT; + } + config_datamover(adm); for (i = 0; i < MSM_DMOV_CHANNEL_COUNT; i++) { - INIT_LIST_HEAD(&ready_commands[i]); - INIT_LIST_HEAD(&active_commands[i]); - writel(DMOV_CONFIG_IRQ_EN | DMOV_CONFIG_FORCE_TOP_PTR_RSLT | DMOV_CONFIG_FORCE_FLUSH_RSLT, DMOV_CONFIG(i)); + INIT_LIST_HEAD(&dmov_conf[adm].ready_commands[i]); + INIT_LIST_HEAD(&dmov_conf[adm].active_commands[i]); + + writel_relaxed(DMOV_RSLT_CONF_IRQ_EN + | DMOV_RSLT_CONF_FORCE_FLUSH_RSLT, + DMOV_REG(DMOV_RSLT_CONF(i), adm)); } - clk = clk_get(NULL, "adm_clk"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - msm_dmov_clk = clk; - ret = request_irq(INT_ADM_AARM, msm_datamover_irq_handler, 0, "msmdatamover", NULL); + wmb(); + return ret; +} + +static struct platform_driver msm_dmov_driver = { + .probe = msm_dmov_probe, + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + .pm = &msm_dmov_dev_pm_ops, + }, +}; + +/* static int __init */ +static int __init msm_init_datamover(void) +{ + int ret; + ret = platform_driver_register(&msm_dmov_driver); if (ret) return ret; - disable_irq(INT_ADM_AARM); return 0; } - arch_initcall(msm_init_datamover); - diff --git a/arch/arm/mach-msm/dma_test.c b/arch/arm/mach-msm/dma_test.c new file mode 100644 index 00000000000..3a315423f28 --- /dev/null +++ b/arch/arm/mach-msm/dma_test.c @@ -0,0 +1,360 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +/********************************************************************** + * User-space testing of the DMA driver. + * Intended to be loaded as a module. We have a bunch of static + * buffers that the user-side can refer to. The main DMA is simply + * used memory-to-memory. Device DMA is best tested with the specific + * device driver in question. + */ +#define MAX_TEST_BUFFERS 40 +#define MAX_TEST_BUFFER_SIZE 65536 +static void *(buffers[MAX_TEST_BUFFERS]); +static int sizes[MAX_TEST_BUFFERS]; + +/* Anything that allocates or deallocates buffers must lock with this + * mutex. */ +static DEFINE_SEMAPHORE(buffer_lock); + +/* Each buffer has a semaphore associated with it that will be held + * for the duration of any operations on that buffer. It also must be + * available to free the given buffer. */ +static struct semaphore buffer_sems[MAX_TEST_BUFFERS]; + +#define buffer_up(num) up(&buffer_sems[num]) +#define buffer_down(num) down(&buffer_sems[num]) + +/* Use the General Purpose DMA channel as our test channel. This channel + * should be available on any target. */ +#define TEST_CHANNEL DMOV_GP_CHAN + +struct private { + /* Each open instance is allowed a single pending + * operation. */ + struct semaphore sem; + + /* Simple command buffer. Allocated and freed by driver. */ + /* TODO: Allocate these together. */ + dmov_s *command_ptr; + + /* Indirect. */ + u32 *command_ptr_ptr; + + /* Indicates completion with pending request. */ + struct completion complete; +}; + +static void free_buffers(void) +{ + int i; + + for (i = 0; i < MAX_TEST_BUFFERS; i++) { + if (sizes[i] > 0) { + kfree(buffers[i]); + sizes[i] = 0; + } + } +} + +/* Copy between two buffers, using the DMA. */ + +/* Allocate a buffer of a requested size. */ +static int buffer_req(struct msm_dma_alloc_req *req) +{ + int i; + + if (req->size <= 0 || req->size > MAX_TEST_BUFFER_SIZE) + return -EINVAL; + + down(&buffer_lock); + + /* Find a free buffer. */ + for (i = 0; i < MAX_TEST_BUFFERS; i++) + if (sizes[i] == 0) + break; + + if (i >= MAX_TEST_BUFFERS) + goto error; + + buffers[i] = kmalloc(req->size, GFP_KERNEL | __GFP_DMA); + if (buffers[i] == 0) + goto error; + sizes[i] = req->size; + + req->bufnum = i; + + up(&buffer_lock); + return 0; + +error: + up(&buffer_lock); + return -ENOSPC; +} + +static int dma_scopy(struct msm_dma_scopy *scopy, struct private *priv) +{ + int err = 0; + dma_addr_t mapped_cmd; + dma_addr_t mapped_cmd_ptr; + + buffer_down(scopy->srcbuf); + if (scopy->srcbuf != scopy->destbuf) + buffer_down(scopy->destbuf); + + priv->command_ptr->cmd = CMD_PTR_LP | CMD_MODE_SINGLE; + priv->command_ptr->src = dma_map_single(NULL, buffers[scopy->srcbuf], + scopy->size, DMA_TO_DEVICE); + priv->command_ptr->dst = dma_map_single(NULL, buffers[scopy->destbuf], + scopy->size, DMA_FROM_DEVICE); + priv->command_ptr->len = scopy->size; + + mapped_cmd = + dma_map_single(NULL, priv->command_ptr, sizeof(*priv->command_ptr), + DMA_TO_DEVICE); + *(priv->command_ptr_ptr) = CMD_PTR_ADDR(mapped_cmd) | CMD_PTR_LP; + + mapped_cmd_ptr = dma_map_single(NULL, priv->command_ptr_ptr, + sizeof(*priv->command_ptr_ptr), + DMA_TO_DEVICE); + + msm_dmov_exec_cmd(TEST_CHANNEL, 0, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(mapped_cmd_ptr)); + + dma_unmap_single(NULL, (dma_addr_t) mapped_cmd_ptr, + sizeof(*priv->command_ptr_ptr), DMA_TO_DEVICE); + dma_unmap_single(NULL, (dma_addr_t) mapped_cmd, + sizeof(*priv->command_ptr), DMA_TO_DEVICE); + dma_unmap_single(NULL, (dma_addr_t) priv->command_ptr->dst, + scopy->size, DMA_FROM_DEVICE); + dma_unmap_single(NULL, (dma_addr_t) priv->command_ptr->src, + scopy->size, DMA_TO_DEVICE); + + if (scopy->srcbuf != scopy->destbuf) + buffer_up(scopy->destbuf); + buffer_up(scopy->srcbuf); + + return err; +} + +static int dma_test_open(struct inode *inode, struct file *file) +{ + struct private *priv; + + printk(KERN_ALERT "%s\n", __func__); + + priv = kmalloc(sizeof(struct private), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + file->private_data = priv; + + sema_init(&priv->sem, 1); + + /* Note, that these should be allocated together so we don't + * waste 32 bytes for each. */ + + /* Allocate the command pointer. */ + priv->command_ptr = kmalloc(sizeof(&priv->command_ptr), + GFP_KERNEL | __GFP_DMA); + if (priv->command_ptr == NULL) { + kfree(priv); + return -ENOSPC; + } + + /* And the indirect pointer. */ + priv->command_ptr_ptr = kmalloc(sizeof(u32), GFP_KERNEL | __GFP_DMA); + if (priv->command_ptr_ptr == NULL) { + kfree(priv->command_ptr); + kfree(priv); + return -ENOSPC; + } + + return 0; +} + +static int dma_test_release(struct inode *inode, struct file *file) +{ + struct private *priv; + + printk(KERN_ALERT "%s\n", __func__); + + if (file->private_data != NULL) { + priv = file->private_data; + kfree(priv->command_ptr_ptr); + kfree(priv->command_ptr); + } + kfree(file->private_data); + file->private_data = NULL; + + return 0; +} + +static long dma_test_ioctl(struct file *file, unsigned cmd, unsigned long arg) +{ + int err = 0; + int tmp; + struct msm_dma_alloc_req alloc_req; + struct msm_dma_bufxfer xfer; + struct msm_dma_scopy scopy; + struct private *priv = file->private_data; + + /* Verify user arguments. */ + if (_IOC_TYPE(cmd) != MSM_DMA_IOC_MAGIC) + return -ENOTTY; + + switch (cmd) { + case MSM_DMA_IOALLOC: + if (!access_ok(VERIFY_WRITE, (void __user *)arg, + sizeof(alloc_req))) + return -EFAULT; + if (__copy_from_user(&alloc_req, (void __user *)arg, + sizeof(alloc_req))) + return -EFAULT; + err = buffer_req(&alloc_req); + if (err < 0) + return err; + if (__copy_to_user((void __user *)arg, &alloc_req, + sizeof(alloc_req))) + return -EFAULT; + break; + + case MSM_DMA_IOFREEALL: + down(&buffer_lock); + for (tmp = 0; tmp < MAX_TEST_BUFFERS; tmp++) { + buffer_down(tmp); + if (sizes[tmp] > 0) { + kfree(buffers[tmp]); + sizes[tmp] = 0; + } + buffer_up(tmp); + } + up(&buffer_lock); + break; + + case MSM_DMA_IOWBUF: + if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer))) + return -EFAULT; + if (xfer.bufnum < 0 || xfer.bufnum >= MAX_TEST_BUFFERS) + return -EINVAL; + buffer_down(xfer.bufnum); + if (sizes[xfer.bufnum] == 0 || + xfer.size <= 0 || xfer.size > sizes[xfer.bufnum]) { + buffer_up(xfer.bufnum); + return -EINVAL; + } + if (copy_from_user(buffers[xfer.bufnum], + (void __user *)xfer.data, xfer.size)) + err = -EFAULT; + buffer_up(xfer.bufnum); + break; + + case MSM_DMA_IORBUF: + if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer))) + return -EFAULT; + if (xfer.bufnum < 0 || xfer.bufnum >= MAX_TEST_BUFFERS) + return -EINVAL; + buffer_down(xfer.bufnum); + if (sizes[xfer.bufnum] == 0 || + xfer.size <= 0 || xfer.size > sizes[xfer.bufnum]) { + buffer_up(xfer.bufnum); + return -EINVAL; + } + if (copy_to_user((void __user *)xfer.data, buffers[xfer.bufnum], + xfer.size)) + err = -EFAULT; + buffer_up(xfer.bufnum); + break; + + case MSM_DMA_IOSCOPY: + if (copy_from_user(&scopy, (void __user *)arg, sizeof(scopy))) + return -EFAULT; + if (scopy.srcbuf < 0 || scopy.srcbuf >= MAX_TEST_BUFFERS || + sizes[scopy.srcbuf] == 0 || + scopy.destbuf < 0 || scopy.destbuf >= MAX_TEST_BUFFERS || + sizes[scopy.destbuf] == 0 || + scopy.size > sizes[scopy.destbuf] || + scopy.size > sizes[scopy.srcbuf]) + return -EINVAL; +#if 0 + /* Test interface using memcpy. */ + memcpy(buffers[scopy.destbuf], + buffers[scopy.srcbuf], scopy.size); +#else + err = dma_scopy(&scopy, priv); +#endif + break; + + default: + return -ENOTTY; + } + + return err; +} + +/********************************************************************** + * Register ourselves as a misc device to be able to test the DMA code + * from userspace. */ + +static const struct file_operations dma_test_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dma_test_ioctl, + .open = dma_test_open, + .release = dma_test_release, +}; + +static struct miscdevice dma_test_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msmdma", + .fops = &dma_test_fops, +}; +static int dma_test_init(void) +{ + int ret, i; + + ret = misc_register(&dma_test_dev); + if (ret < 0) + return ret; + + for (i = 0; i < MAX_TEST_BUFFERS; i++) + sema_init(&buffer_sems[i], 1); + + printk(KERN_ALERT "%s, minor number %d\n", __func__, dma_test_dev.minor); + return 0; +} + +static void dma_test_exit(void) +{ + free_buffers(); + misc_deregister(&dma_test_dev); + printk(KERN_ALERT "%s\n", __func__); +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("David Brown, Qualcomm, Incorporated"); +MODULE_DESCRIPTION("Test for MSM DMA driver"); +MODULE_VERSION("1.01"); + +module_init(dma_test_init); +module_exit(dma_test_exit); diff --git a/arch/arm/mach-msm/etm.c b/arch/arm/mach-msm/etm.c new file mode 100644 index 00000000000..b970786567a --- /dev/null +++ b/arch/arm/mach-msm/etm.c @@ -0,0 +1,1001 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cp14.h" + +#define LOG_BUF_LEN 32768 +#define ETM_NUM_REGS 128 +#define ETB_NUM_REGS 9 +/* each slot is 4 bytes, 8kb total */ +#define ETB_RAM_SLOTS 2048 + +#define DATALOG_SYNC 0xB5C7 +#define ETM_DUMP_MSG_ID 0x000A6960 +#define ETB_DUMP_MSG_ID 0x000A6961 + +/* ETM Registers */ +#define ETM_REG_CONTROL 0x00 +#define ETM_REG_STATUS 0x04 +#define ETB_REG_CONTROL 0x71 +#define ETB_REG_STATUS 0x72 +#define ETB_REG_COUNT 0x73 +#define ETB_REG_ADDRESS 0x74 +#define ETB_REG_DATA 0x75 + +/* Bitmasks for the ETM control register */ +#define ETM_CONTROL_POWERDOWN 0x00000001 +#define ETM_CONTROL_PROGRAM 0x00000400 + +/* Bitmasks for the ETM status register */ +#define ETM_STATUS_PROGRAMMING 0x00000002 + +/* ETB Status Register bit definitions */ +#define OV 0x00200000 + +/* ETB Control Register bit definitions */ +#define AIR 0x00000008 +#define AIW 0x00000004 +#define CPTM 0x00000002 +#define CPTEN 0x00000001 + +/* Bitmasks for the swconfig field of ETM_CONFIG + * ETM trigger propagated to ETM instances on all cores + */ +#define TRIGGER_ALL 0x00000002 + +#define PROG_TIMEOUT_MS 500 + +static int trace_enabled; +static int *cpu_restore; +static int cpu_to_dump; +static int next_cpu_to_dump; +static struct wake_lock etm_wake_lock; +static struct pm_qos_request_list etm_qos_req; +static int trace_on_boot; +module_param_named( + trace_on_boot, trace_on_boot, int, S_IRUGO +); + +struct b { + uint8_t etm_log_buf[LOG_BUF_LEN]; + uint32_t log_end; +}; + +static struct b buf[NR_CPUS]; +static struct b __percpu * *alloc_b; +static atomic_t etm_dev_in_use; + +/* These default settings will be used to configure the ETM/ETB + * when the driver loads. */ +struct etm_config_struct { + uint32_t etm_00_control; + uint32_t etm_02_trigger_event; + uint32_t etm_06_te_start_stop; + uint32_t etm_07_te_single_addr_comp; + uint32_t etm_08_te_event; + uint32_t etm_09_te_control; + uint32_t etm_0a_fifofull_region; + uint32_t etm_0b_fifofull_level; + uint32_t etm_0c_vd_event; + uint32_t etm_0d_vd_single_addr_comp; + uint32_t etm_0e_vd_mmd; + uint32_t etm_0f_vd_control; + uint32_t etm_addr_comp_value[8]; /* 10 to 17 */ + uint32_t etm_addr_access_type[8]; /* 20 to 27 */ + uint32_t etm_data_comp_value[2]; /* 30 and 32 */ + uint32_t etm_data_comp_mask[2]; /* 40 and 42 */ + uint32_t etm_counter_reload_value[2]; /* 50 to 51 */ + uint32_t etm_counter_enable[2]; /* 54 to 55 */ + uint32_t etm_counter_reload_event[2]; /* 58 to 59 */ + uint32_t etm_60_seq_event_1_to_2; + uint32_t etm_61_seq_event_2_to_1; + uint32_t etm_62_seq_event_2_to_3; + uint32_t etm_63_seq_event_3_to_1; + uint32_t etm_64_seq_event_3_to_2; + uint32_t etm_65_seq_event_1_to_3; + uint32_t etm_6c_cid_comp_value_1; + uint32_t etm_6f_cid_comp_mask; + uint32_t etm_78_sync_freq; + uint32_t swconfig; + uint32_t etb_trig_cnt; + uint32_t etb_init_ptr; +}; + +static struct etm_config_struct etm_config = { + /* etm_00_control 0x0000D84E: 32-bit CID, cycle-accurate, + * monitorCPRT */ + .etm_00_control = 0x0000D84E, + /* etm_02_trigger_event 0x00000000: address comparator 0 matches */ + .etm_02_trigger_event = 0x00000000, + .etm_06_te_start_stop = 0x00000000, + .etm_07_te_single_addr_comp = 0x00000000, + /* etm_08_te_event 0x0000006F: always true */ + .etm_08_te_event = 0x0000006F, + /* etm_09_te_control 0x01000000: exclude none */ + .etm_09_te_control = 0x01000000, + .etm_0a_fifofull_region = 0x00000000, + .etm_0b_fifofull_level = 0x00000000, + /* etm_0c_vd_event 0x0000006F: always true */ + .etm_0c_vd_event = 0x0000006F, + .etm_0d_vd_single_addr_comp = 0x00000000, + .etm_0e_vd_mmd = 0x00000000, + /* etm_0f_vd_control 0x00010000: exclude none */ + .etm_0f_vd_control = 0x00010000, + .etm_addr_comp_value[0] = 0x00000000, + .etm_addr_comp_value[1] = 0x00000000, + .etm_addr_comp_value[2] = 0x00000000, + .etm_addr_comp_value[3] = 0x00000000, + .etm_addr_comp_value[4] = 0x00000000, + .etm_addr_comp_value[5] = 0x00000000, + .etm_addr_comp_value[6] = 0x00000000, + .etm_addr_comp_value[7] = 0x00000000, + .etm_addr_access_type[0] = 0x00000000, + .etm_addr_access_type[1] = 0x00000000, + .etm_addr_access_type[2] = 0x00000000, + .etm_addr_access_type[3] = 0x00000000, + .etm_addr_access_type[4] = 0x00000000, + .etm_addr_access_type[5] = 0x00000000, + .etm_addr_access_type[6] = 0x00000000, + .etm_addr_access_type[7] = 0x00000000, + .etm_data_comp_value[0] = 0x00000000, + .etm_data_comp_value[1] = 0x00000000, + .etm_data_comp_mask[0] = 0x00000000, + .etm_data_comp_mask[1] = 0x00000000, + .etm_counter_reload_value[0] = 0x00000000, + .etm_counter_reload_value[1] = 0x00000000, + .etm_counter_enable[0] = 0x0002406F, + .etm_counter_enable[1] = 0x0002406F, + .etm_counter_reload_event[0] = 0x0000406F, + .etm_counter_reload_event[1] = 0x0000406F, + .etm_60_seq_event_1_to_2 = 0x0000406F, + .etm_61_seq_event_2_to_1 = 0x0000406F, + .etm_62_seq_event_2_to_3 = 0x0000406F, + .etm_63_seq_event_3_to_1 = 0x0000406F, + .etm_64_seq_event_3_to_2 = 0x0000406F, + .etm_65_seq_event_1_to_3 = 0x0000406F, + .etm_6c_cid_comp_value_1 = 0x00000000, + .etm_6f_cid_comp_mask = 0x00000000, + .etm_78_sync_freq = 0x00000400, + .swconfig = 0x00000002, + /* etb_trig_cnt 0x00000000: ignore trigger */ + .etb_trig_cnt = 0x00000000, + /* etb_init_ptr 0x00000010: 16 marker bytes */ + .etb_init_ptr = 0x00000010, +}; + +static void emit_log_char(uint8_t c) +{ + int this_cpu = get_cpu(); + struct b *mybuf = *per_cpu_ptr(alloc_b, this_cpu); + char *log_buf = mybuf->etm_log_buf; + int index = (mybuf->log_end)++ & (LOG_BUF_LEN - 1); + log_buf[index] = c; + put_cpu(); +} + +static void emit_log_word(uint32_t word) +{ + emit_log_char(word >> 24); + emit_log_char(word >> 16); + emit_log_char(word >> 8); + emit_log_char(word >> 0); +} + +static void __cpu_enable_etb(void) +{ + uint32_t etb_control; + uint32_t i; + + /* enable auto-increment on reads and writes */ + etb_control = AIR | AIW; + etm_write_reg(ETB_REG_CONTROL, etb_control); + + /* write tags to the slots before the write pointer so we can + * detect overflow */ + etm_write_reg(ETB_REG_ADDRESS, 0x00000000); + for (i = 0; i < (etm_config.etb_init_ptr >> 2); i++) + etm_write_reg(ETB_REG_DATA, 0xDEADBEEF); + + etm_write_reg(ETB_REG_STATUS, 0x00000000); + + /* initialize write pointer */ + etm_write_reg(ETB_REG_ADDRESS, etm_config.etb_init_ptr); + + /* multiple of 16 */ + etm_write_reg(ETB_REG_COUNT, etm_config.etb_trig_cnt & 0xFFFFFFF0); + + /* Enable ETB and enable the trigger counter as appropriate. A + * trigger count of 0 will be used to signify that the user wants to + * ignore the trigger (just keep writing to the ETB and overwriting + * the oldest data). For "trace before trigger" captures the user + * should set the trigger count to a small number. */ + + etb_control |= CPTEN; + if (etm_config.etb_trig_cnt) + etb_control |= CPTM; + etm_write_reg(ETB_REG_CONTROL, etb_control); +} + +static void __cpu_disable_etb(void) +{ + uint32_t etb_control; + etb_control = etm_read_reg(ETB_REG_CONTROL); + etb_control &= ~CPTEN; + etm_write_reg(ETB_REG_CONTROL, etb_control); +} + +static void __cpu_enable_etm(void) +{ + uint32_t etm_control; + unsigned long timeout = jiffies + msecs_to_jiffies(PROG_TIMEOUT_MS); + + etm_control = etm_read_reg(ETM_REG_CONTROL); + etm_control &= ~ETM_CONTROL_PROGRAM; + etm_write_reg(ETM_REG_CONTROL, etm_control); + + while ((etm_read_reg(ETM_REG_STATUS) & ETM_STATUS_PROGRAMMING) == 1) { + cpu_relax(); + if (time_after(jiffies, timeout)) { + pr_err("etm: timeout while clearing prog bit\n"); + break; + } + } +} + +static void __cpu_disable_etm(void) +{ + uint32_t etm_control; + unsigned long timeout = jiffies + msecs_to_jiffies(PROG_TIMEOUT_MS); + + etm_control = etm_read_reg(ETM_REG_CONTROL); + etm_control |= ETM_CONTROL_PROGRAM; + etm_write_reg(ETM_REG_CONTROL, etm_control); + + while ((etm_read_reg(ETM_REG_STATUS) & ETM_STATUS_PROGRAMMING) == 0) { + cpu_relax(); + if (time_after(jiffies, timeout)) { + pr_err("etm: timeout while setting prog bit\n"); + break; + } + } +} + +static void __cpu_enable_trace(void *unused) +{ + uint32_t etm_control; + uint32_t etm_trigger; + uint32_t etm_external_output; + + get_cpu(); + + etm_read_reg(0xC5); /* clear sticky bit in PDSR */ + + __cpu_disable_etb(); + __cpu_disable_etm(); + + etm_control = (etm_config.etm_00_control & ~ETM_CONTROL_POWERDOWN) + | ETM_CONTROL_PROGRAM; + etm_write_reg(0x00, etm_control); + + etm_trigger = etm_config.etm_02_trigger_event; + etm_external_output = 0x406F; /* always FALSE */ + + if (etm_config.swconfig & TRIGGER_ALL) { + uint32_t function = 0x5; /* A OR B */ + uint32_t resource_b = 0x60; /* external input 1 */ + + etm_trigger &= 0x7F; /* keep resource A, clear function and + * resource B */ + etm_trigger |= (function << 14); + etm_trigger |= (resource_b << 7); + etm_external_output = etm_trigger; + } + + etm_write_reg(0x02, etm_trigger); + etm_write_reg(0x06, etm_config.etm_06_te_start_stop); + etm_write_reg(0x07, etm_config.etm_07_te_single_addr_comp); + etm_write_reg(0x08, etm_config.etm_08_te_event); + etm_write_reg(0x09, etm_config.etm_09_te_control); + etm_write_reg(0x0a, etm_config.etm_0a_fifofull_region); + etm_write_reg(0x0b, etm_config.etm_0b_fifofull_level); + etm_write_reg(0x0c, etm_config.etm_0c_vd_event); + etm_write_reg(0x0d, etm_config.etm_0d_vd_single_addr_comp); + etm_write_reg(0x0e, etm_config.etm_0e_vd_mmd); + etm_write_reg(0x0f, etm_config.etm_0f_vd_control); + etm_write_reg(0x10, etm_config.etm_addr_comp_value[0]); + etm_write_reg(0x11, etm_config.etm_addr_comp_value[1]); + etm_write_reg(0x12, etm_config.etm_addr_comp_value[2]); + etm_write_reg(0x13, etm_config.etm_addr_comp_value[3]); + etm_write_reg(0x14, etm_config.etm_addr_comp_value[4]); + etm_write_reg(0x15, etm_config.etm_addr_comp_value[5]); + etm_write_reg(0x16, etm_config.etm_addr_comp_value[6]); + etm_write_reg(0x17, etm_config.etm_addr_comp_value[7]); + etm_write_reg(0x20, etm_config.etm_addr_access_type[0]); + etm_write_reg(0x21, etm_config.etm_addr_access_type[1]); + etm_write_reg(0x22, etm_config.etm_addr_access_type[2]); + etm_write_reg(0x23, etm_config.etm_addr_access_type[3]); + etm_write_reg(0x24, etm_config.etm_addr_access_type[4]); + etm_write_reg(0x25, etm_config.etm_addr_access_type[5]); + etm_write_reg(0x26, etm_config.etm_addr_access_type[6]); + etm_write_reg(0x27, etm_config.etm_addr_access_type[7]); + etm_write_reg(0x30, etm_config.etm_data_comp_value[0]); + etm_write_reg(0x32, etm_config.etm_data_comp_value[1]); + etm_write_reg(0x40, etm_config.etm_data_comp_mask[0]); + etm_write_reg(0x42, etm_config.etm_data_comp_mask[1]); + etm_write_reg(0x50, etm_config.etm_counter_reload_value[0]); + etm_write_reg(0x51, etm_config.etm_counter_reload_value[1]); + etm_write_reg(0x54, etm_config.etm_counter_enable[0]); + etm_write_reg(0x55, etm_config.etm_counter_enable[1]); + etm_write_reg(0x58, etm_config.etm_counter_reload_event[0]); + etm_write_reg(0x59, etm_config.etm_counter_reload_event[1]); + etm_write_reg(0x60, etm_config.etm_60_seq_event_1_to_2); + etm_write_reg(0x61, etm_config.etm_61_seq_event_2_to_1); + etm_write_reg(0x62, etm_config.etm_62_seq_event_2_to_3); + etm_write_reg(0x63, etm_config.etm_63_seq_event_3_to_1); + etm_write_reg(0x64, etm_config.etm_64_seq_event_3_to_2); + etm_write_reg(0x65, etm_config.etm_65_seq_event_1_to_3); + etm_write_reg(0x68, etm_external_output); + etm_write_reg(0x6c, etm_config.etm_6c_cid_comp_value_1); + etm_write_reg(0x6f, etm_config.etm_6f_cid_comp_mask); + etm_write_reg(0x78, etm_config.etm_78_sync_freq); + + /* Note that we must enable the ETB before we enable the ETM if we + * want to capture the "always true" trigger event. */ + + __cpu_enable_etb(); + __cpu_enable_etm(); + + put_cpu(); +} + +static void __cpu_disable_trace(void *unused) +{ + uint32_t etm_control; + + get_cpu(); + etm_read_reg(0xC5); /* clear sticky bit in PDSR */ + + __cpu_disable_etm(); + + /* program trace enable to be low by using always false event */ + etm_write_reg(0x08, 0x6F | BIT(14)); + + /* set the powerdown bit */ + etm_control = etm_read_reg(ETM_REG_CONTROL); + etm_control |= ETM_CONTROL_POWERDOWN; + etm_write_reg(ETM_REG_CONTROL, etm_control); + + __cpu_enable_etm(); + __cpu_disable_etb(); + + put_cpu(); +} + +static void enable_trace(void) +{ + wake_lock(&etm_wake_lock); + pm_qos_update_request(&etm_qos_req, 0); + + if (etm_config.swconfig & TRIGGER_ALL) { + /* This register is accessible from either core. + * CPU1_extout[0] -> CPU0_extin[0] + * CPU_extout[0] -> CPU1_extin[0] */ + l2tevselr0_write(0x00000001); + } + + get_cpu(); + __cpu_enable_trace(NULL); + smp_call_function(__cpu_enable_trace, NULL, 1); + put_cpu(); + + /* When the smp_call returns, we are guaranteed that all online + * cpus are out of wfi/power_collapse and won't be allowed to enter + * again due to the pm_qos latency request above. + */ + trace_enabled = 1; + + pm_qos_update_request(&etm_qos_req, PM_QOS_DEFAULT_VALUE); + wake_unlock(&etm_wake_lock); +} + +static void disable_trace(void) +{ + int cpu; + + wake_lock(&etm_wake_lock); + pm_qos_update_request(&etm_qos_req, 0); + + get_cpu(); + __cpu_disable_trace(NULL); + smp_call_function(__cpu_disable_trace, NULL, 1); + put_cpu(); + + /* When the smp_call returns, we are guaranteed that all online + * cpus are out of wfi/power_collapse and won't be allowed to enter + * again due to the pm_qos latency request above. + */ + trace_enabled = 0; + + for_each_possible_cpu(cpu) + *per_cpu_ptr(cpu_restore, cpu) = 0; + + cpu_to_dump = next_cpu_to_dump = 0; + + pm_qos_update_request(&etm_qos_req, PM_QOS_DEFAULT_VALUE); + wake_unlock(&etm_wake_lock); +} + +static void generate_etb_dump(void) +{ + uint32_t i; + uint32_t full_slots; + uint32_t etb_control; + uint32_t prim_len; + uint32_t uptime = 0; + + etb_control = etm_read_reg(ETB_REG_CONTROL); + etb_control |= AIR; + etm_write_reg(ETB_REG_CONTROL, etb_control); + + if (etm_read_reg(ETB_REG_STATUS) & OV) + full_slots = ETB_RAM_SLOTS; + else + full_slots = etm_read_reg(ETB_REG_ADDRESS) >> 2; + + prim_len = 28 + (full_slots * 4); + + emit_log_char((DATALOG_SYNC >> 8) & 0xFF); + emit_log_char((DATALOG_SYNC >> 0) & 0xFF); + emit_log_char((prim_len >> 8) & 0xFF); + emit_log_char((prim_len >> 0) & 0xFF); + emit_log_word(uptime); + emit_log_word(ETB_DUMP_MSG_ID); + emit_log_word(etm_read_reg(ETM_REG_CONTROL)); + emit_log_word(etm_config.etb_init_ptr >> 2); + emit_log_word(etm_read_reg(ETB_REG_ADDRESS) >> 2); + emit_log_word((etm_read_reg(ETB_REG_STATUS) & OV) >> 21); + + etm_write_reg(ETB_REG_ADDRESS, 0x00000000); + for (i = 0; i < full_slots; i++) + emit_log_word(etm_read_reg(ETB_REG_DATA)); +} + +static void generate_etm_dump(void) +{ + uint32_t i; + uint32_t prim_len; + uint32_t uptime = 0; + + prim_len = 12 + (4 * ETM_NUM_REGS); + + emit_log_char((DATALOG_SYNC >> 8) & 0xFF); + emit_log_char((DATALOG_SYNC >> 0) & 0xFF); + emit_log_char((prim_len >> 8) & 0xFF); + emit_log_char((prim_len >> 0) & 0xFF); + emit_log_word(uptime); + emit_log_word(ETM_DUMP_MSG_ID); + + /* do not disturb ETB_REG_ADDRESS by reading ETB_REG_DATA */ + for (i = 0; i < ETM_NUM_REGS; i++) + if (i == ETB_REG_DATA) + emit_log_word(0); + else + emit_log_word(etm_read_reg(i)); +} + +static void dump_all(void *unused) +{ + get_cpu(); + etm_read_reg(0xC5); /* clear sticky bit in PDSR in case + * trace hasn't been enabled yet. */ + __cpu_disable_etb(); + generate_etm_dump(); + generate_etb_dump(); + if (trace_enabled) + __cpu_enable_etb(); + put_cpu(); +} + +static void dump_trace(void) +{ + get_cpu(); + dump_all(NULL); + smp_call_function(dump_all, NULL, 1); + put_cpu(); +} + +static int bytes_to_dump; +static uint8_t *etm_buf_ptr; + +static int etm_dev_open(struct inode *inode, struct file *file) +{ + if (atomic_cmpxchg(&etm_dev_in_use, 0, 1)) + return -EBUSY; + + pr_debug("%s: successfully opened\n", __func__); + return 0; +} + +static ssize_t etm_dev_read(struct file *file, char __user *data, + size_t len, loff_t *ppos) +{ + if (cpu_to_dump == next_cpu_to_dump) { + if (cpu_to_dump == 0) + dump_trace(); + bytes_to_dump = buf[cpu_to_dump].log_end; + buf[cpu_to_dump].log_end = 0; + etm_buf_ptr = buf[cpu_to_dump].etm_log_buf; + next_cpu_to_dump++; + if (next_cpu_to_dump >= num_possible_cpus()) + next_cpu_to_dump = 0; + } + + if (len > bytes_to_dump) + len = bytes_to_dump; + + if (copy_to_user(data, etm_buf_ptr, len)) { + pr_debug("%s: copy_to_user failed\n", __func__); + return -EFAULT; + } + + bytes_to_dump -= len; + etm_buf_ptr += len; + + pr_debug("%s: %d bytes copied, %d bytes left (cpu %d)\n", + __func__, len, bytes_to_dump, next_cpu_to_dump); + return len; +} + +static void setup_range_filter(char addr_type, char range, uint32_t reg1, + uint32_t addr1, uint32_t reg2, uint32_t addr2) +{ + etm_config.etm_addr_comp_value[reg1] = addr1; + etm_config.etm_addr_comp_value[reg2] = addr2; + + etm_config.etm_07_te_single_addr_comp |= (1 << reg1); + etm_config.etm_07_te_single_addr_comp |= (1 << reg2); + + etm_config.etm_09_te_control |= (1 << (reg1/2)); + if (range == 'i') + etm_config.etm_09_te_control &= ~(1 << 24); + else if (range == 'e') + etm_config.etm_09_te_control |= (1 << 24); + + if (addr_type == 'i') { + etm_config.etm_addr_access_type[reg1] = 0x99; + etm_config.etm_addr_access_type[reg2] = 0x99; + } else if (addr_type == 'd') { + etm_config.etm_addr_access_type[reg1] = 0x9C; + etm_config.etm_addr_access_type[reg2] = 0x9C; + } +} + +static void setup_start_stop_filter(char addr_type, char start_stop, + uint32_t reg, uint32_t addr) +{ + etm_config.etm_addr_comp_value[reg] = addr; + + if (start_stop == 's') + etm_config.etm_06_te_start_stop |= (1 << reg); + else if (start_stop == 't') + etm_config.etm_06_te_start_stop |= (1 << (reg + 16)); + + etm_config.etm_09_te_control |= (1 << 25); + + if (addr_type == 'i') + etm_config.etm_addr_access_type[reg] = 0x99; + else if (addr_type == 'd') + etm_config.etm_addr_access_type[reg] = 0x9C; +} + +static void setup_viewdata_range_filter(char range, uint32_t reg1, + uint32_t addr1, uint32_t reg2, uint32_t addr2) +{ + etm_config.etm_addr_comp_value[reg1] = addr1; + etm_config.etm_addr_comp_value[reg2] = addr2; + + if (range == 'i') { + etm_config.etm_0d_vd_single_addr_comp |= (1 << reg1); + etm_config.etm_0d_vd_single_addr_comp |= (1 << reg2); + etm_config.etm_0f_vd_control |= (1 << (reg1/2)); + } else if (range == 'e') { + etm_config.etm_0d_vd_single_addr_comp |= (1 << (reg1 + 16)); + etm_config.etm_0d_vd_single_addr_comp |= (1 << (reg2 + 16)); + etm_config.etm_0f_vd_control |= (1 << ((reg1/2) + 8)); + } + etm_config.etm_0f_vd_control &= ~(1 << 16); + + etm_config.etm_addr_access_type[reg1] = 0x9C; + etm_config.etm_addr_access_type[reg2] = 0x9C; +} + +static void setup_viewdata_start_stop_filter(char start_stop, uint32_t reg, + uint32_t addr) +{ + etm_config.etm_addr_comp_value[reg] = addr; + + if (start_stop == 's') + etm_config.etm_06_te_start_stop |= (1 << reg); + else if (start_stop == 't') + etm_config.etm_06_te_start_stop |= (1 << (reg + 16)); + + etm_config.etm_addr_access_type[reg] = 0x9C; +} + +static void setup_access_type(uint32_t reg, uint32_t value) +{ + etm_config.etm_addr_access_type[reg] &= 0xFFFFFFF8; + value &= 0x7; + etm_config.etm_addr_access_type[reg] |= value; +} + +static void reset_filter(void) +{ + etm_config.etm_00_control = 0x0000D84E; + /* etm_02_trigger_event 0x00000000: address comparator 0 matches */ + etm_config.etm_02_trigger_event = 0x00000000; + etm_config.etm_06_te_start_stop = 0x00000000; + etm_config.etm_07_te_single_addr_comp = 0x00000000; + /* etm_08_te_event 0x0000006F: always true */ + etm_config.etm_08_te_event = 0x0000006F; + /* etm_09_te_control 0x01000000: exclude none */ + etm_config.etm_09_te_control = 0x01000000; + etm_config.etm_0a_fifofull_region = 0x00000000; + etm_config.etm_0b_fifofull_level = 0x00000000; + /* etm_0c_vd_event 0x0000006F: always true */ + etm_config.etm_0c_vd_event = 0x0000006F; + etm_config.etm_0d_vd_single_addr_comp = 0x00000000; + etm_config.etm_0e_vd_mmd = 0x00000000; + /* etm_0f_vd_control 0x00010000: exclude none */ + etm_config.etm_0f_vd_control = 0x00010000; + etm_config.etm_addr_comp_value[0] = 0x00000000; + etm_config.etm_addr_comp_value[1] = 0x00000000; + etm_config.etm_addr_comp_value[2] = 0x00000000; + etm_config.etm_addr_comp_value[3] = 0x00000000; + etm_config.etm_addr_comp_value[4] = 0x00000000; + etm_config.etm_addr_comp_value[5] = 0x00000000; + etm_config.etm_addr_comp_value[6] = 0x00000000; + etm_config.etm_addr_comp_value[7] = 0x00000000; + etm_config.etm_addr_access_type[0] = 0x00000000; + etm_config.etm_addr_access_type[1] = 0x00000000; + etm_config.etm_addr_access_type[2] = 0x00000000; + etm_config.etm_addr_access_type[3] = 0x00000000; + etm_config.etm_addr_access_type[4] = 0x00000000; + etm_config.etm_addr_access_type[5] = 0x00000000; + etm_config.etm_addr_access_type[6] = 0x00000000; + etm_config.etm_addr_access_type[7] = 0x00000000; + etm_config.etm_data_comp_value[0] = 0x00000000; + etm_config.etm_data_comp_value[1] = 0x00000000; + etm_config.etm_data_comp_mask[0] = 0x00000000; + etm_config.etm_data_comp_mask[1] = 0x00000000; + etm_config.etm_counter_reload_value[0] = 0x00000000; + etm_config.etm_counter_reload_value[1] = 0x00000000; + etm_config.etm_counter_enable[0] = 0x0002406F; + etm_config.etm_counter_enable[1] = 0x0002406F; + etm_config.etm_counter_reload_event[0] = 0x0000406F; + etm_config.etm_counter_reload_event[1] = 0x0000406F; + etm_config.etm_60_seq_event_1_to_2 = 0x0000406F; + etm_config.etm_61_seq_event_2_to_1 = 0x0000406F; + etm_config.etm_62_seq_event_2_to_3 = 0x0000406F; + etm_config.etm_63_seq_event_3_to_1 = 0x0000406F; + etm_config.etm_64_seq_event_3_to_2 = 0x0000406F; + etm_config.etm_65_seq_event_1_to_3 = 0x0000406F; + etm_config.etm_6c_cid_comp_value_1 = 0x00000000; + etm_config.etm_6f_cid_comp_mask = 0x00000000; + etm_config.etm_78_sync_freq = 0x00000400; + etm_config.swconfig = 0x00000002; + /* etb_trig_cnt 0x00000020: ignore trigger */ + etm_config.etb_trig_cnt = 0x00000000; + /* etb_init_ptr 0x00000010: 16 marker bytes */ + etm_config.etb_init_ptr = 0x00000010; +} + +#define MAX_COMMAND_STRLEN 40 +static ssize_t etm_dev_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + char command[MAX_COMMAND_STRLEN]; + int strlen; + unsigned long value; + unsigned long reg1, reg2; + unsigned long addr1, addr2; + + strlen = strnlen_user(data, MAX_COMMAND_STRLEN); + pr_debug("etm: string length: %d", strlen); + if (strlen == 0 || strlen == (MAX_COMMAND_STRLEN+1)) { + pr_err("etm: error in strlen: %d", strlen); + return -EFAULT; + } + /* includes the null character */ + if (copy_from_user(command, data, strlen)) { + pr_err("etm: error in copy_from_user: %d", strlen); + return -EFAULT; + } + + pr_debug("etm: input = %s", command); + + switch (command[0]) { + case '0': + if (trace_enabled) { + disable_trace(); + pr_info("etm: tracing disabled\n"); + } + break; + case '1': + if (!trace_enabled) { + enable_trace(); + pr_info("etm: tracing enabled\n"); + } + break; + case 'f': + switch (command[2]) { + case 'i': + case 'd': + switch (command[4]) { + case 'i': + if (sscanf(&command[6], "%lx:%lx:%lx:%lx\\0", + ®1, &addr1, ®2, &addr2) != 4) + goto err_out; + if (reg1 > 7 || reg2 > 7 || (reg1 % 2)) + goto err_out; + setup_range_filter(command[2], 'i', + reg1, addr1, reg2, addr2); + break; + case 'e': + if (sscanf(&command[6], "%lx:%lx:%lx:%lx\\0", + ®1, &addr1, ®2, &addr2) != 4) + goto err_out; + if (reg1 > 7 || reg2 > 7 || (reg1 % 2) + || command[2] == 'd') + goto err_out; + setup_range_filter(command[2], 'e', + reg1, addr1, reg2, addr2); + break; + case 's': + if (sscanf(&command[6], "%lx:%lx\\0", + ®1, &addr1) != 2) + goto err_out; + if (reg1 > 7) + goto err_out; + setup_start_stop_filter(command[2], 's', + reg1, addr1); + break; + case 't': + if (sscanf(&command[6], "%lx:%lx\\0", + ®1, &addr1) != 2) + goto err_out; + if (reg1 > 7) + goto err_out; + setup_start_stop_filter(command[2], 't', + reg1, addr1); + break; + default: + goto err_out; + } + break; + case 'r': + reset_filter(); + break; + default: + goto err_out; + } + break; + case 'v': + switch (command[2]) { + case 'd': + switch (command[4]) { + case 'i': + if (sscanf(&command[6], "%lx:%lx:%lx:%lx\\0", + ®1, &addr1, ®2, &addr2) != 4) + goto err_out; + if (reg1 > 7 || reg2 > 7 || (reg1 % 2)) + goto err_out; + setup_viewdata_range_filter('i', + reg1, addr1, reg2, addr2); + break; + case 'e': + if (sscanf(&command[6], "%lx:%lx:%lx:%lx\\0", + ®1, &addr1, ®2, &addr2) != 4) + goto err_out; + if (reg1 > 7 || reg2 > 7 || (reg1 % 2)) + goto err_out; + setup_viewdata_range_filter('e', + reg1, addr1, reg2, addr2); + break; + case 's': + if (sscanf(&command[6], "%lx:%lx\\0", + ®1, &addr1) != 2) + goto err_out; + if (reg1 > 7) + goto err_out; + setup_viewdata_start_stop_filter('s', + reg1, addr1); + break; + case 't': + if (sscanf(&command[6], "%lx:%lx\\0", + ®1, &addr1) != 2) + goto err_out; + if (reg1 > 7) + goto err_out; + setup_viewdata_start_stop_filter('t', + reg1, addr1); + break; + default: + goto err_out; + } + break; + default: + goto err_out; + } + break; + case 'a': + switch (command[2]) { + case 't': + if (sscanf(&command[4], "%lx:%lx\\0", + ®1, &value) != 2) + goto err_out; + if (reg1 > 7 || value > 6) + goto err_out; + setup_access_type(reg1, value); + break; + default: + goto err_out; + } + break; + default: + goto err_out; + } + + return len; + +err_out: + return -EFAULT; +} + +static int etm_dev_release(struct inode *inode, struct file *file) +{ + if (cpu_to_dump == next_cpu_to_dump) + next_cpu_to_dump = 0; + cpu_to_dump = next_cpu_to_dump; + + atomic_set(&etm_dev_in_use, 0); + pr_debug("%s: released\n", __func__); + return 0; +} + +static const struct file_operations etm_dev_fops = { + .owner = THIS_MODULE, + .open = etm_dev_open, + .read = etm_dev_read, + .write = etm_dev_write, + .release = etm_dev_release, +}; + +static struct miscdevice etm_dev = { + .name = "msm_etm", + .minor = MISC_DYNAMIC_MINOR, + .fops = &etm_dev_fops, +}; + +/* etm_save_reg_check and etm_restore_reg_check should be fast + * + * These functions will be called either from: + * 1. per_cpu idle thread context for idle wfi and power collapses. + * 2. per_cpu idle thread context for hotplug/suspend power collapse for + * nonboot cpus. + * 3. suspend thread context for core0. + * + * In all cases we are guaranteed to be running on the same cpu for the + * entire duration. + * + * Another assumption is that etm registers won't change after trace_enabled + * is set. Current usage model guarantees this doesn't happen. + */ +void etm_save_reg_check(void) +{ + if (trace_enabled) { + int cpu = smp_processor_id(); + + /* Don't save the registers if we just got called from per_cpu + * idle thread context of a nonboot cpu after hotplug/suspend + * power collapse. This is to prevent corruption due to saving + * twice since nonboot cpus start out fresh without the + * corresponding restore. + */ + if (!(*per_cpu_ptr(cpu_restore, cpu))) { + etm_save_reg(); + *per_cpu_ptr(cpu_restore, cpu) = 1; + } + } +} + +void etm_restore_reg_check(void) +{ + if (trace_enabled) { + int cpu = smp_processor_id(); + + etm_restore_reg(); + *per_cpu_ptr(cpu_restore, cpu) = 0; + } +} + +static int __init etm_init(void) +{ + int ret, cpu; + + ret = misc_register(&etm_dev); + if (ret) + return -ENODEV; + + alloc_b = alloc_percpu(typeof(*alloc_b)); + if (!alloc_b) + goto err1; + + for_each_possible_cpu(cpu) + *per_cpu_ptr(alloc_b, cpu) = &buf[cpu]; + + cpu_restore = alloc_percpu(int); + if (!cpu_restore) + goto err2; + + for_each_possible_cpu(cpu) + *per_cpu_ptr(cpu_restore, cpu) = 0; + + wake_lock_init(&etm_wake_lock, WAKE_LOCK_SUSPEND, "msm_etm"); + pm_qos_add_request(&etm_qos_req, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + cpu_to_dump = next_cpu_to_dump = 0; + + pr_info("ETM/ETB intialized.\n"); + + if (trace_on_boot) + enable_trace(); + + return 0; + +err2: + free_percpu(alloc_b); +err1: + misc_deregister(&etm_dev); + return -ENOMEM; +} + +static void __exit etm_exit(void) +{ + disable_trace(); + pm_qos_remove_request(&etm_qos_req); + wake_lock_destroy(&etm_wake_lock); + free_percpu(cpu_restore); + free_percpu(alloc_b); + misc_deregister(&etm_dev); +} + +module_init(etm_init); +module_exit(etm_exit); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("embedded trace driver"); diff --git a/arch/arm/mach-msm/fiq.h b/arch/arm/mach-msm/fiq.h new file mode 100644 index 00000000000..cd903908c08 --- /dev/null +++ b/arch/arm/mach-msm/fiq.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_FIQ_H +#define _ARCH_ARM_MACH_MSM_FIQ_H + +extern unsigned char fiq_glue, fiq_glue_end; +void fiq_glue_setup(void *func, void *data, void *sp); + +#endif diff --git a/arch/arm/mach-msm/fiq_glue.S b/arch/arm/mach-msm/fiq_glue.S new file mode 100644 index 00000000000..df1c7084fe1 --- /dev/null +++ b/arch/arm/mach-msm/fiq_glue.S @@ -0,0 +1,112 @@ +/* arch/arm/mach-msm/fiq_glue.S + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + + .text + + .global fiq_glue_end + + /* fiq stack: r0-r15,cpsr,spsr of interrupted mode */ + +ENTRY(fiq_glue) + /* store pc, cpsr from previous mode */ + mrs r12, spsr + sub r11, lr, #4 + subs r10, #1 + bne nested_fiq + + stmfd sp!, {r11-r12, lr} + + /* store r8-r14 from previous mode */ + sub sp, sp, #(7 * 4) + stmia sp, {r8-r14}^ + nop + + /* store r0-r7 from previous mode */ + stmfd sp!, {r0-r7} + + /* setup func(data,regs) arguments */ + mov r0, r9 + mov r1, sp + mov r3, r8 + + mov r7, sp + + /* Get sp and lr from non-user modes */ + and r4, r12, #MODE_MASK + cmp r4, #USR_MODE + beq fiq_from_usr_mode + + mov r7, sp + orr r4, r4, #(PSR_I_BIT | PSR_F_BIT) + msr cpsr_c, r4 + str sp, [r7, #(4 * 13)] + str lr, [r7, #(4 * 14)] + mrs r5, spsr + str r5, [r7, #(4 * 17)] + + cmp r4, #(SVC_MODE | PSR_I_BIT | PSR_F_BIT) + /* use fiq stack if we reenter this mode */ + subne sp, r7, #(4 * 3) + +fiq_from_usr_mode: + msr cpsr_c, #(SVC_MODE | PSR_I_BIT | PSR_F_BIT) + mov r2, sp + sub sp, r7, #12 + stmfd sp!, {r2, ip, lr} + /* call func(data,regs) */ + blx r3 + ldmfd sp, {r2, ip, lr} + mov sp, r2 + + /* restore/discard saved state */ + cmp r4, #USR_MODE + beq fiq_from_usr_mode_exit + + msr cpsr_c, r4 + ldr sp, [r7, #(4 * 13)] + ldr lr, [r7, #(4 * 14)] + msr spsr_cxsf, r5 + +fiq_from_usr_mode_exit: + msr cpsr_c, #(FIQ_MODE | PSR_I_BIT | PSR_F_BIT) + + ldmfd sp!, {r0-r7} + add sp, sp, #(7 * 4) + ldmfd sp!, {r11-r12, lr} +exit_fiq: + msr spsr_cxsf, r12 + add r10, #1 + movs pc, r11 + +nested_fiq: + orr r12, r12, #(PSR_F_BIT) + b exit_fiq + +fiq_glue_end: + +ENTRY(fiq_glue_setup) /* func, data, sp */ + mrs r3, cpsr + msr cpsr_c, #(FIQ_MODE | PSR_I_BIT | PSR_F_BIT) + movs r8, r0 + mov r9, r1 + mov sp, r2 + moveq r10, #0 + movne r10, #1 + msr cpsr_c, r3 + bx lr + diff --git a/arch/arm/mach-msm/fish_battery.c b/arch/arm/mach-msm/fish_battery.c new file mode 100644 index 00000000000..19fbb91fe83 --- /dev/null +++ b/arch/arm/mach-msm/fish_battery.c @@ -0,0 +1,145 @@ +/* arch/arm/mach-msm/fish_battery.c + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * based on: arch/arm/mach-msm/htc_battery.c + */ + +#include +#include +#include +#include +#include +#include + +static enum power_supply_property fish_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static enum power_supply_property fish_power_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *supply_list[] = { + "battery", +}; + +static int fish_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); + +static int fish_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); + +static struct power_supply fish_power_supplies[] = { + { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = fish_battery_properties, + .num_properties = ARRAY_SIZE(fish_battery_properties), + .get_property = fish_battery_get_property, + }, + { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = fish_power_properties, + .num_properties = ARRAY_SIZE(fish_power_properties), + .get_property = fish_power_get_property, + }, +}; + +static int fish_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (psy->type == POWER_SUPPLY_TYPE_MAINS) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fish_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = 100; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int fish_battery_probe(struct platform_device *pdev) +{ + int i; + int rc; + + /* init power supplier framework */ + for (i = 0; i < ARRAY_SIZE(fish_power_supplies); i++) { + rc = power_supply_register(&pdev->dev, &fish_power_supplies[i]); + if (rc) + pr_err("%s: Failed to register power supply (%d)\n", + __func__, rc); + } + + return 0; +} + +static struct platform_driver fish_battery_driver = { + .probe = fish_battery_probe, + .driver = { + .name = "fish_battery", + .owner = THIS_MODULE, + }, +}; + +static int __init fish_battery_init(void) +{ + platform_driver_register(&fish_battery_driver); + return 0; +} + +module_init(fish_battery_init); +MODULE_DESCRIPTION("Qualcomm fish battery driver"); +MODULE_LICENSE("GPL"); + diff --git a/arch/arm/mach-msm/footswitch-8x60.c b/arch/arm/mach-msm/footswitch-8x60.c new file mode 100644 index 00000000000..a3bc92b6501 --- /dev/null +++ b/arch/arm/mach-msm/footswitch-8x60.c @@ -0,0 +1,599 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "clock.h" +#include "footswitch.h" + +#ifdef CONFIG_MSM_SECURE_IO +#undef readl_relaxed +#undef writel_relaxed +#define readl_relaxed secure_readl +#define writel_relaxed secure_writel +#endif + +#define REG(off) (MSM_MMSS_CLK_CTL_BASE + (off)) +#define GEMINI_GFS_CTL_REG REG(0x01A0) +#define GFX2D0_GFS_CTL_REG REG(0x0180) +#define GFX2D1_GFS_CTL_REG REG(0x0184) +#define GFX3D_GFS_CTL_REG REG(0x0188) +#define MDP_GFS_CTL_REG REG(0x0190) +#define ROT_GFS_CTL_REG REG(0x018C) +#define VED_GFS_CTL_REG REG(0x0194) +#define VFE_GFS_CTL_REG REG(0x0198) +#define VPE_GFS_CTL_REG REG(0x019C) + +#define CLAMP_BIT BIT(5) +#define ENABLE_BIT BIT(8) +#define RETENTION_BIT BIT(9) + +#define RESET_DELAY_US 1 +/* Core clock rate to use if one has not previously been set. */ +#define DEFAULT_CLK_RATE 27000000 + +/* + * Lock is only needed to protect against the first footswitch_enable() + * call occuring concurrently with late_footswitch_init(). + */ +static DEFINE_MUTEX(claim_lock); + +struct clock_state { + int ahb_clk_en; + int axi_clk_en; + int core_clk_rate; +}; + +struct footswitch { + struct regulator_dev *rdev; + struct regulator_desc desc; + void *gfs_ctl_reg; + int bus_port1, bus_port2; + bool is_enabled; + bool is_claimed; + const char *core_clk_name; + const char *ahb_clk_name; + const char *axi_clk_name; + struct clk *core_clk; + struct clk *ahb_clk; + struct clk *axi_clk; + unsigned int reset_rate; + struct clock_state clk_state; + unsigned int gfs_delay_cnt:5; +}; + +static int setup_clocks(struct footswitch *fs) +{ + int rc = 0; + + /* + * Enable all clocks in the power domain. If a core requires a + * specific clock rate when being reset, apply it. + */ + fs->clk_state.core_clk_rate = clk_get_rate(fs->core_clk); + if (!fs->clk_state.core_clk_rate || fs->reset_rate) { + int rate = fs->reset_rate ? fs->reset_rate : DEFAULT_CLK_RATE; + rc = clk_set_rate(fs->core_clk, rate); + if (rc) { + pr_err("%s: Failed to set %s rate to %d Hz.\n", + __func__, fs->core_clk_name, + fs->reset_rate); + return rc; + } + } + clk_enable(fs->core_clk); + + /* + * Some AHB and AXI clocks are for reset purposes only. These clocks + * will fail to enable. Keep track of them so we don't try to disable + * them later and crash. + */ + fs->clk_state.ahb_clk_en = !clk_enable(fs->ahb_clk); + if (fs->axi_clk) + fs->clk_state.axi_clk_en = !clk_enable(fs->axi_clk); + + return rc; +} + +static void restore_clocks(struct footswitch *fs) +{ + /* Restore clocks to their orignal states before setup_clocks(). */ + if (fs->axi_clk && fs->clk_state.axi_clk_en) + clk_disable(fs->axi_clk); + if (fs->clk_state.ahb_clk_en) + clk_disable(fs->ahb_clk); + clk_disable(fs->core_clk); + if (fs->clk_state.core_clk_rate) { + if (clk_set_rate(fs->core_clk, fs->clk_state.core_clk_rate)) + pr_err("%s: Failed to restore %s rate.\n", + __func__, fs->core_clk_name); + } +} + +static int footswitch_is_enabled(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + + return fs->is_enabled; +} + +static int footswitch_enable(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + uint32_t regval, rc = 0; + + mutex_lock(&claim_lock); + fs->is_claimed = true; + mutex_unlock(&claim_lock); + + /* Make sure required clocks are on at the correct rates. */ + rc = setup_clocks(fs); + if (rc) + goto out; + + /* Un-halt all bus ports in the power domain. */ + if (fs->bus_port1) { + rc = msm_bus_axi_portunhalt(fs->bus_port1); + if (rc) { + pr_err("%s: Port 1 unhalt failed.\n", __func__); + goto out; + } + } + if (fs->bus_port2) { + rc = msm_bus_axi_portunhalt(fs->bus_port2); + if (rc) { + pr_err("%s: Port 2 unhalt failed.\n", __func__); + goto out; + } + } + + /* + * (Re-)Assert resets for all clocks in the clock domain, since + * footswitch_enable() is first called before footswitch_disable() + * and resets should be asserted before power is restored. + */ + if (fs->axi_clk) + clk_reset(fs->axi_clk, CLK_RESET_ASSERT); + clk_reset(fs->ahb_clk, CLK_RESET_ASSERT); + clk_reset(fs->core_clk, CLK_RESET_ASSERT); + /* Wait for synchronous resets to propagate. */ + udelay(RESET_DELAY_US); + + /* Enable the power rail at the footswitch. */ + regval = readl_relaxed(fs->gfs_ctl_reg); + regval |= ENABLE_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + /* Wait for the rail to fully charge. */ + mb(); + udelay(1); + + /* Un-clamp the I/O ports. */ + regval &= ~CLAMP_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + + /* Deassert resets for all clocks in the power domain. */ + clk_reset(fs->core_clk, CLK_RESET_DEASSERT); + clk_reset(fs->ahb_clk, CLK_RESET_DEASSERT); + if (fs->axi_clk) + clk_reset(fs->axi_clk, CLK_RESET_DEASSERT); + /* Toggle core reset again after first power-on (required for GFX3D). */ + if (fs->desc.id == FS_GFX3D) { + clk_reset(fs->core_clk, CLK_RESET_ASSERT); + udelay(RESET_DELAY_US); + clk_reset(fs->core_clk, CLK_RESET_DEASSERT); + udelay(RESET_DELAY_US); + } + + /* Return clocks to their state before this function. */ + restore_clocks(fs); + + fs->is_enabled = true; +out: + return rc; +} + +static int footswitch_disable(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + uint32_t regval, rc = 0; + + /* Make sure required clocks are on at the correct rates. */ + rc = setup_clocks(fs); + if (rc) + goto out; + + /* Halt all bus ports in the power domain. */ + if (fs->bus_port1) { + rc = msm_bus_axi_porthalt(fs->bus_port1); + if (rc) { + pr_err("%s: Port 1 halt failed.\n", __func__); + goto out; + } + } + if (fs->bus_port2) { + rc = msm_bus_axi_porthalt(fs->bus_port2); + if (rc) { + pr_err("%s: Port 1 halt failed.\n", __func__); + goto err_port2_halt; + } + } + + /* + * Assert resets for all clocks in the clock domain so that + * outputs settle prior to clamping. + */ + if (fs->axi_clk) + clk_reset(fs->axi_clk, CLK_RESET_ASSERT); + clk_reset(fs->ahb_clk, CLK_RESET_ASSERT); + clk_reset(fs->core_clk, CLK_RESET_ASSERT); + /* Wait for synchronous resets to propagate. */ + udelay(RESET_DELAY_US); + + /* + * Clamp the I/O ports of the core to ensure the values + * remain fixed while the core is collapsed. + */ + regval = readl_relaxed(fs->gfs_ctl_reg); + regval |= CLAMP_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + + /* Collapse the power rail at the footswitch. */ + regval &= ~ENABLE_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + + /* Return clocks to their state before this function. */ + restore_clocks(fs); + + fs->is_enabled = false; + + return rc; + +err_port2_halt: + msm_bus_axi_portunhalt(fs->bus_port1); +out: + return rc; +} + +static int gfx2d_footswitch_enable(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + uint32_t regval, rc = 0; + + mutex_lock(&claim_lock); + fs->is_claimed = true; + mutex_unlock(&claim_lock); + + /* Make sure required clocks are on at the correct rates. */ + rc = setup_clocks(fs); + if (rc) + goto out; + + /* Un-halt all bus ports in the power domain. */ + if (fs->bus_port1) { + rc = msm_bus_axi_portunhalt(fs->bus_port1); + if (rc) { + pr_err("%s: Port 1 unhalt failed.\n", __func__); + goto out; + } + } + + /* Disable core clock. */ + clk_disable(fs->core_clk); + + /* + * (Re-)Assert resets for all clocks in the clock domain, since + * footswitch_enable() is first called before footswitch_disable() + * and resets should be asserted before power is restored. + */ + if (fs->axi_clk) + clk_reset(fs->axi_clk, CLK_RESET_ASSERT); + clk_reset(fs->ahb_clk, CLK_RESET_ASSERT); + clk_reset(fs->core_clk, CLK_RESET_ASSERT); + /* Wait for synchronous resets to propagate. */ + udelay(20); + + /* Enable the power rail at the footswitch. */ + regval = readl_relaxed(fs->gfs_ctl_reg); + regval |= ENABLE_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + mb(); + udelay(1); + + /* Un-clamp the I/O ports. */ + regval &= ~CLAMP_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + + /* Deassert resets for all clocks in the power domain. */ + if (fs->axi_clk) + clk_reset(fs->axi_clk, CLK_RESET_DEASSERT); + clk_reset(fs->ahb_clk, CLK_RESET_DEASSERT); + clk_reset(fs->core_clk, CLK_RESET_DEASSERT); + udelay(20); + + /* Re-enable core clock. */ + clk_enable(fs->core_clk); + + /* Return clocks to their state before this function. */ + restore_clocks(fs); + + fs->is_enabled = true; +out: + return rc; +} + +static int gfx2d_footswitch_disable(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + uint32_t regval, rc = 0; + + /* Make sure required clocks are on at the correct rates. */ + rc = setup_clocks(fs); + if (rc) + goto out; + + /* Halt all bus ports in the power domain. */ + if (fs->bus_port1) { + rc = msm_bus_axi_porthalt(fs->bus_port1); + if (rc) { + pr_err("%s: Port 1 halt failed.\n", __func__); + goto out; + } + } + + /* Disable core clock. */ + clk_disable(fs->core_clk); + + /* + * Assert resets for all clocks in the clock domain so that + * outputs settle prior to clamping. + */ + if (fs->axi_clk) + clk_reset(fs->axi_clk, CLK_RESET_ASSERT); + clk_reset(fs->ahb_clk, CLK_RESET_ASSERT); + clk_reset(fs->core_clk, CLK_RESET_ASSERT); + /* Wait for synchronous resets to propagate. */ + udelay(20); + + /* + * Clamp the I/O ports of the core to ensure the values + * remain fixed while the core is collapsed. + */ + regval = readl_relaxed(fs->gfs_ctl_reg); + regval |= CLAMP_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + + /* Collapse the power rail at the footswitch. */ + regval &= ~ENABLE_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + + /* Re-enable core clock. */ + clk_enable(fs->core_clk); + + /* Return clocks to their state before this function. */ + restore_clocks(fs); + + fs->is_enabled = false; + +out: + return rc; +} + +static struct regulator_ops standard_fs_ops = { + .is_enabled = footswitch_is_enabled, + .enable = footswitch_enable, + .disable = footswitch_disable, +}; + +static struct regulator_ops gfx2d_fs_ops = { + .is_enabled = footswitch_is_enabled, + .enable = gfx2d_footswitch_enable, + .disable = gfx2d_footswitch_disable, +}; + +#define FOOTSWITCH(_id, _name, _ops, _gfs_ctl_reg, _dc, _bp1, _bp2, \ + _core_clk, _ahb_clk, _axi_clk, _reset_rate) \ + [(_id)] = { \ + .desc = { \ + .id = (_id), \ + .name = (_name), \ + .ops = (_ops), \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + .gfs_ctl_reg = (_gfs_ctl_reg), \ + .gfs_delay_cnt = (_dc), \ + .bus_port1 = (_bp1), \ + .bus_port2 = (_bp2), \ + .core_clk_name = (_core_clk), \ + .ahb_clk_name = (_ahb_clk), \ + .axi_clk_name = (_axi_clk), \ + .reset_rate = (_reset_rate), \ + } +static struct footswitch footswitches[] = { + FOOTSWITCH(FS_GFX2D0, "fs_gfx2d0", &gfx2d_fs_ops, + GFX2D0_GFS_CTL_REG, 31, + MSM_BUS_MASTER_GRAPHICS_2D_CORE0, 0, + "gfx2d0_clk", "gfx2d0_pclk", NULL, 0), + FOOTSWITCH(FS_GFX2D1, "fs_gfx2d1", &gfx2d_fs_ops, + GFX2D1_GFS_CTL_REG, 31, + MSM_BUS_MASTER_GRAPHICS_2D_CORE1, 0, + "gfx2d1_clk", "gfx2d1_pclk", NULL, 0), + FOOTSWITCH(FS_GFX3D, "fs_gfx3d", &standard_fs_ops, + GFX3D_GFS_CTL_REG, 31, + MSM_BUS_MASTER_GRAPHICS_3D, 0, + "gfx3d_clk", "gfx3d_pclk", NULL, 27000000), + FOOTSWITCH(FS_IJPEG, "fs_ijpeg", &standard_fs_ops, + GEMINI_GFS_CTL_REG, 31, + MSM_BUS_MASTER_JPEG_ENC, 0, + "ijpeg_clk", "ijpeg_pclk", "ijpeg_axi_clk", 0), + FOOTSWITCH(FS_MDP, "fs_mdp", &standard_fs_ops, + MDP_GFS_CTL_REG, 31, + MSM_BUS_MASTER_MDP_PORT0, + MSM_BUS_MASTER_MDP_PORT1, + "mdp_clk", "mdp_pclk", "mdp_axi_clk", 0), + FOOTSWITCH(FS_ROT, "fs_rot", &standard_fs_ops, + ROT_GFS_CTL_REG, 31, + MSM_BUS_MASTER_ROTATOR, 0, + "rot_clk", "rotator_pclk", "rot_axi_clk", 0), + FOOTSWITCH(FS_VED, "fs_ved", &standard_fs_ops, + VED_GFS_CTL_REG, 31, + MSM_BUS_MASTER_HD_CODEC_PORT0, + MSM_BUS_MASTER_HD_CODEC_PORT1, + "vcodec_clk", "vcodec_pclk", "vcodec_axi_clk", 0), + FOOTSWITCH(FS_VFE, "fs_vfe", &standard_fs_ops, + VFE_GFS_CTL_REG, 31, + MSM_BUS_MASTER_VFE, 0, + "vfe_clk", "vfe_pclk", "vfe_axi_clk", 0), + FOOTSWITCH(FS_VPE, "fs_vpe", &standard_fs_ops, + VPE_GFS_CTL_REG, 31, + MSM_BUS_MASTER_VPE, 0, + "vpe_clk", "vpe_pclk", "vpe_axi_clk", 0), +}; + +static int footswitch_probe(struct platform_device *pdev) +{ + struct footswitch *fs; + struct regulator_init_data *init_data; + uint32_t regval, rc = 0; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= MAX_FS) + return -ENODEV; + + fs = &footswitches[pdev->id]; + init_data = pdev->dev.platform_data; + + /* Setup core clock. */ + fs->core_clk = clk_get(NULL, fs->core_clk_name); + if (IS_ERR(fs->core_clk)) { + pr_err("%s: clk_get(\"%s\") failed\n", __func__, + fs->core_clk_name); + rc = PTR_ERR(fs->core_clk); + goto err_core_clk; + } + + /* Setup AHB clock. */ + fs->ahb_clk = clk_get(NULL, fs->ahb_clk_name); + if (IS_ERR(fs->ahb_clk)) { + pr_err("%s: clk_get(\"%s\") failed\n", __func__, + fs->ahb_clk_name); + rc = PTR_ERR(fs->ahb_clk); + goto err_ahb_clk; + } + + /* Setup AXI clock. */ + if (fs->axi_clk_name) { + fs->axi_clk = clk_get(NULL, fs->axi_clk_name); + if (IS_ERR(fs->axi_clk)) { + pr_err("%s: clk_get(\"%s\") failed\n", __func__, + fs->axi_clk_name); + rc = PTR_ERR(fs->axi_clk); + goto err_axi_clk; + } + } + + /* + * Set number of AHB_CLK cycles to delay the assertion of gfs_en_all + * after enabling the footswitch. Also ensure the retention bit is + * clear so disabling the footswitch will power-collapse the core. + */ + regval = readl_relaxed(fs->gfs_ctl_reg); + regval |= fs->gfs_delay_cnt; + regval &= ~RETENTION_BIT; + writel_relaxed(regval, fs->gfs_ctl_reg); + + fs->rdev = regulator_register(&fs->desc, &pdev->dev, init_data, fs); + if (IS_ERR(footswitches[pdev->id].rdev)) { + pr_err("%s: regulator_register(\"%s\") failed\n", + __func__, fs->desc.name); + rc = PTR_ERR(footswitches[pdev->id].rdev); + goto err_register; + } + + return 0; + +err_register: + if (fs->axi_clk_name) + clk_put(fs->axi_clk); +err_axi_clk: + clk_put(fs->ahb_clk); +err_ahb_clk: + clk_put(fs->core_clk); +err_core_clk: + return rc; +} + +static int __devexit footswitch_remove(struct platform_device *pdev) +{ + struct footswitch *fs = &footswitches[pdev->id]; + + clk_put(fs->core_clk); + clk_put(fs->ahb_clk); + if (fs->axi_clk) + clk_put(fs->axi_clk); + + regulator_unregister(fs->rdev); + + return 0; +} + +static struct platform_driver footswitch_driver = { + .probe = footswitch_probe, + .remove = __devexit_p(footswitch_remove), + .driver = { + .name = "footswitch-msm8x60", + .owner = THIS_MODULE, + }, +}; + +static int __init late_footswitch_init(void) +{ + int i; + + mutex_lock(&claim_lock); + /* Turn off all registered but unused footswitches. */ + for (i = 0; i < ARRAY_SIZE(footswitches); i++) + if (footswitches[i].rdev && !footswitches[i].is_claimed) + footswitch_disable(footswitches[i].rdev); + mutex_unlock(&claim_lock); + + return 0; +} +late_initcall(late_footswitch_init); + +static int __init footswitch_init(void) +{ + return platform_driver_register(&footswitch_driver); +} +subsys_initcall(footswitch_init); + +static void __exit footswitch_exit(void) +{ + platform_driver_unregister(&footswitch_driver); +} +module_exit(footswitch_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM8x60 rail footswitch"); +MODULE_ALIAS("platform:footswitch-msm8x60"); diff --git a/arch/arm/mach-msm/footswitch-pcom.c b/arch/arm/mach-msm/footswitch-pcom.c new file mode 100644 index 00000000000..97edf8cfa41 --- /dev/null +++ b/arch/arm/mach-msm/footswitch-pcom.c @@ -0,0 +1,338 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include "footswitch.h" +#include "proc_comm.h" + +/* PCOM power rail IDs */ +#define PCOM_FS_GRP 8 +#define PCOM_FS_GRP_2D 58 +#define PCOM_FS_MDP 14 +#define PCOM_FS_MFC 68 +#define PCOM_FS_ROTATOR 90 +#define PCOM_FS_VFE 41 +#define PCOM_FS_VPE 76 + +#define PCOM_RAIL_MODE_AUTO 0 +#define PCOM_RAIL_MODE_MANUAL 1 + +/** + * struct footswitch - Per-footswitch data and state + * @rdev: Regulator framework device + * @desc: Regulator descriptor + * @init_data: Regulator platform data + * @pcom_id: Proc-comm ID of the footswitch + * @is_enabled: Flag set when footswitch is enabled + * @is_manual: Flag set when footswitch is in manual proc-comm mode + * @core_clk_name: String name of core clock for footswitch power domain + * @set_clk_name: String name of clock used to set the core clocks's rate + * @ahb_clk_name: String name of AHB clock for footswitch power domain + * @core_clk: Clock with name core_clk_name + * @set_clk: Clock with name set_clk_name + * @abh_clk: Clock with name ahb_clk_name + * @set_clk_init_rate: Rate to use for set_clk to if one has not yet been set + * @is_rate_set: Flag set if the core clock's rate has been set + */ +struct footswitch { + struct regulator_dev *rdev; + struct regulator_desc desc; + struct regulator_init_data init_data; + unsigned pcom_id; + bool is_enabled; + bool is_manual; + const char *core_clk_name; + const char *set_clk_name; + const char *ahb_clk_name; + struct clk *core_clk; + struct clk *set_clk; + struct clk *ahb_clk; + const int set_clk_init_rate; + bool is_rate_set; +}; + +static inline int set_rail_mode(int pcom_id, int mode) +{ + int rc; + + rc = msm_proc_comm(PCOM_CLKCTL_RPC_RAIL_CONTROL, &pcom_id, &mode); + if (!rc && pcom_id) + rc = -EINVAL; + + return rc; +} + +static inline int set_rail_state(int pcom_id, int state) +{ + int rc; + + rc = msm_proc_comm(state, &pcom_id, NULL); + if (!rc && pcom_id) + rc = -EINVAL; + + return rc; +} + +static int enable_clocks(struct footswitch *fs) +{ + fs->is_rate_set = !!(clk_get_rate(fs->set_clk)); + if (!fs->is_rate_set) + clk_set_rate(fs->set_clk, fs->set_clk_init_rate); + clk_enable(fs->core_clk); + + if (fs->ahb_clk) + clk_enable(fs->ahb_clk); + + return 0; +} + +static void disable_clocks(struct footswitch *fs) +{ + if (fs->ahb_clk) + clk_disable(fs->ahb_clk); + clk_disable(fs->core_clk); +} + +static int footswitch_is_enabled(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + + return fs->is_enabled; +} + +static int footswitch_enable(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + int rc; + + rc = enable_clocks(fs); + if (rc) + return rc; + + rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE); + if (!rc) + fs->is_enabled = true; + + disable_clocks(fs); + + return rc; +} + +static int footswitch_disable(struct regulator_dev *rdev) +{ + struct footswitch *fs = rdev_get_drvdata(rdev); + int rc; + + rc = enable_clocks(fs); + if (rc) + return rc; + + rc = set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_DISABLE); + if (!rc) + fs->is_enabled = false; + + disable_clocks(fs); + + return rc; +} + +static struct regulator_ops footswitch_ops = { + .is_enabled = footswitch_is_enabled, + .enable = footswitch_enable, + .disable = footswitch_disable, +}; + +#define FOOTSWITCH(_id, _pcom_id, _name, _core_clk, _set_clk, _rate, _ahb_clk) \ + [_id] = { \ + .desc = { \ + .id = _id, \ + .name = _name, \ + .ops = &footswitch_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + .pcom_id = _pcom_id, \ + .core_clk_name = _core_clk, \ + .set_clk_name = _set_clk, \ + .set_clk_init_rate = _rate, \ + .ahb_clk_name = _ahb_clk, \ + } +static struct footswitch footswitches[] = { + FOOTSWITCH(FS_GFX3D, PCOM_FS_GRP, "fs_gfx3d", + "grp_clk", "grp_src_clk", 24576000, "grp_pclk"), + FOOTSWITCH(FS_GFX2D0, PCOM_FS_GRP_2D, "fs_gfx2d0", + "grp_2d_clk", NULL, 24576000, "grp_2d_pclk"), + FOOTSWITCH(FS_MDP, PCOM_FS_MDP, "fs_mdp", + "mdp_clk", NULL, 24576000, "mdp_pclk"), + FOOTSWITCH(FS_MFC, PCOM_FS_MFC, "fs_mfc", + "mfc_clk", NULL, 24576000, "mfc_pclk"), + FOOTSWITCH(FS_ROT, PCOM_FS_ROTATOR, "fs_rot", + "rotator_clk", NULL, 0, "rotator_pclk"), + FOOTSWITCH(FS_VFE, PCOM_FS_VFE, "fs_vfe", + "vfe_clk", NULL, 24576000, "vfe_pclk"), + FOOTSWITCH(FS_VPE, PCOM_FS_VPE, "fs_vpe", + "vpe_clk", NULL, 24576000, NULL), +}; + +static int get_clocks(struct footswitch *fs) +{ + int rc; + + /* + * Some SoCs may not have a separate rate-settable clock. + * If one can't be found, try to use the core clock for + * rate-setting instead. + */ + if (fs->set_clk_name) { + fs->set_clk = clk_get(NULL, fs->set_clk_name); + if (IS_ERR(fs->set_clk)) { + fs->set_clk = clk_get(NULL, fs->core_clk_name); + fs->set_clk_name = fs->core_clk_name; + } + } else { + fs->set_clk = clk_get(NULL, fs->core_clk_name); + fs->set_clk_name = fs->core_clk_name; + } + if (IS_ERR(fs->set_clk)) { + pr_err("clk_get(%s) failed\n", fs->set_clk_name); + rc = PTR_ERR(fs->set_clk); + goto err_set_clk; + } + + fs->core_clk = clk_get(NULL, fs->core_clk_name); + if (IS_ERR(fs->core_clk)) { + pr_err("clk_get(%s) failed\n", fs->core_clk_name); + rc = PTR_ERR(fs->core_clk); + goto err_core_clk; + } + + if (fs->ahb_clk_name) { + fs->ahb_clk = clk_get(NULL, fs->ahb_clk_name); + if (IS_ERR(fs->ahb_clk)) { + pr_err("clk_get(%s) failed\n", fs->ahb_clk_name); + rc = PTR_ERR(fs->ahb_clk); + goto err_ahb_clk; + } + } + + return 0; + +err_ahb_clk: + clk_put(fs->core_clk); +err_core_clk: + clk_put(fs->set_clk); +err_set_clk: + return rc; +} + +static void put_clocks(struct footswitch *fs) +{ + clk_put(fs->set_clk); + clk_put(fs->core_clk); + clk_put(fs->ahb_clk); +} + +static int footswitch_probe(struct platform_device *pdev) +{ + struct footswitch *fs; + struct regulator_init_data *init_data; + int rc; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= MAX_FS) + return -ENODEV; + + fs = &footswitches[pdev->id]; + if (!fs->is_manual) { + pr_err("%s is not in manual mode\n", fs->desc.name); + return -EINVAL; + } + init_data = pdev->dev.platform_data; + + rc = get_clocks(fs); + if (rc) + return rc; + + fs->rdev = regulator_register(&fs->desc, &pdev->dev, init_data, fs); + if (IS_ERR(fs->rdev)) { + pr_err("regulator_register(%s) failed\n", fs->desc.name); + rc = PTR_ERR(fs->rdev); + goto err_register; + } + + return 0; + +err_register: + put_clocks(fs); + + return rc; +} + +static int __devexit footswitch_remove(struct platform_device *pdev) +{ + struct footswitch *fs = &footswitches[pdev->id]; + + regulator_unregister(fs->rdev); + set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_AUTO); + put_clocks(fs); + + return 0; +} + +static struct platform_driver footswitch_driver = { + .probe = footswitch_probe, + .remove = __devexit_p(footswitch_remove), + .driver = { + .name = "footswitch-pcom", + .owner = THIS_MODULE, + }, +}; + +static int __init footswitch_init(void) +{ + struct footswitch *fs; + int ret; + + /* + * Enable all footswitches in manual mode (ie. not controlled along + * with pcom clocks). + */ + for (fs = footswitches; fs < footswitches + ARRAY_SIZE(footswitches); + fs++) { + set_rail_state(fs->pcom_id, PCOM_CLKCTL_RPC_RAIL_ENABLE); + ret = set_rail_mode(fs->pcom_id, PCOM_RAIL_MODE_MANUAL); + if (!ret) + fs->is_manual = 1; + } + + return platform_driver_register(&footswitch_driver); +} +subsys_initcall(footswitch_init); + +static void __exit footswitch_exit(void) +{ + platform_driver_unregister(&footswitch_driver); +} +module_exit(footswitch_exit); + +MODULE_LICENSE("GPLv2"); +MODULE_DESCRIPTION("proc_comm rail footswitch"); +MODULE_ALIAS("platform:footswitch-pcom"); diff --git a/arch/arm/mach-msm/footswitch.h b/arch/arm/mach-msm/footswitch.h new file mode 100644 index 00000000000..0780ca4cdf6 --- /dev/null +++ b/arch/arm/mach-msm/footswitch.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_FOOTSWITCH__ +#define __MSM_FOOTSWITCH__ + +#include + +enum fs_ids { + FS_GFX2D0 = 0, + FS_GFX2D1, + FS_GFX3D, + FS_IJPEG, + FS_MDP, + FS_MFC, + FS_ROT, + FS_VED, + FS_VFE, + FS_VPE, + MAX_FS +}; + +#endif + +#define FS_GENERIC(_drv_name, _id, _name) (&(struct platform_device){ \ + .name = (_drv_name), \ + .id = (_id), \ + .dev = { \ + .platform_data = &(struct regulator_init_data){ \ + .constraints = { \ + .valid_modes_mask = REGULATOR_MODE_NORMAL, \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + }, \ + .num_consumer_supplies = 1, \ + .consumer_supplies = \ + &(struct regulator_consumer_supply) \ + REGULATOR_SUPPLY((_name), NULL), \ + } \ + }, \ +}) +#define FS_PCOM(_id, _name) FS_GENERIC("footswitch-pcom", (_id), (_name)) +#define FS_8X60(_id, _name) FS_GENERIC("footswitch-msm8x60", (_id), (_name)) diff --git a/arch/arm/mach-msm/gpio-fsm9xxx.c b/arch/arm/mach-msm/gpio-fsm9xxx.c new file mode 100644 index 00000000000..0de5919dc75 --- /dev/null +++ b/arch/arm/mach-msm/gpio-fsm9xxx.c @@ -0,0 +1,282 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include "gpio_hw.h" +#include "gpiomux.h" + +#define MSM_GPIO_BANK(bank, first, last) \ + { \ + .regs = { \ + .out = MSM_GPIO_OUT_##bank, \ + .in = MSM_GPIO_IN_##bank, \ + .oe = MSM_GPIO_OE_##bank, \ + }, \ + .chip = { \ + .base = (first), \ + .ngpio = (last) - (first) + 1, \ + .get = msm_gpio_get, \ + .set = msm_gpio_set, \ + .direction_input = msm_gpio_direction_input, \ + .direction_output = msm_gpio_direction_output, \ + .request = msm_gpio_request, \ + .free = msm_gpio_free, \ + } \ + } + +struct msm_gpio_regs { + void __iomem *out; + void __iomem *in; + void __iomem *oe; +}; + +struct msm_gpio_chip { + spinlock_t lock; + struct gpio_chip chip; + struct msm_gpio_regs regs; +}; + +static int msm_gpio_write(struct msm_gpio_chip *msm_chip, + unsigned offset, unsigned on) +{ + unsigned mask = BIT(offset); + unsigned val; + + val = __raw_readl(msm_chip->regs.out); + if (on) + __raw_writel(val | mask, msm_chip->regs.out); + else + __raw_writel(val & ~mask, msm_chip->regs.out); + return 0; +} + +static int msm_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct msm_gpio_chip *msm_chip; + unsigned long irq_flags; + + msm_chip = container_of(chip, struct msm_gpio_chip, chip); + spin_lock_irqsave(&msm_chip->lock, irq_flags); + __raw_writel(__raw_readl(msm_chip->regs.oe) & ~BIT(offset), + msm_chip->regs.oe); + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); + return 0; +} + +static int +msm_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) +{ + struct msm_gpio_chip *msm_chip; + unsigned long irq_flags; + + msm_chip = container_of(chip, struct msm_gpio_chip, chip); + spin_lock_irqsave(&msm_chip->lock, irq_flags); + msm_gpio_write(msm_chip, offset, value); + __raw_writel(__raw_readl(msm_chip->regs.oe) | BIT(offset), + msm_chip->regs.oe); + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); + return 0; +} + +static int msm_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct msm_gpio_chip *msm_chip; + + msm_chip = container_of(chip, struct msm_gpio_chip, chip); + return (__raw_readl(msm_chip->regs.in) & (1U << offset)) ? 1 : 0; +} + +static void msm_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct msm_gpio_chip *msm_chip; + unsigned long irq_flags; + + msm_chip = container_of(chip, struct msm_gpio_chip, chip); + spin_lock_irqsave(&msm_chip->lock, irq_flags); + msm_gpio_write(msm_chip, offset, value); + spin_unlock_irqrestore(&msm_chip->lock, irq_flags); +} + +#ifdef CONFIG_MSM_GPIOMUX +static int msm_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + return msm_gpiomux_get(chip->base + offset); +} + +static void msm_gpio_free(struct gpio_chip *chip, unsigned offset) +{ + msm_gpiomux_put(chip->base + offset); +} +#else +#define msm_gpio_request NULL +#define msm_gpio_free NULL +#endif + +struct msm_gpio_chip msm_gpio_chips[] = { + MSM_GPIO_BANK(0, 0, 31), + MSM_GPIO_BANK(1, 32, 63), + MSM_GPIO_BANK(2, 64, 95), + MSM_GPIO_BANK(3, 96, 127), + MSM_GPIO_BANK(4, 128, 159), + MSM_GPIO_BANK(5, 160, 167), +}; + +void msm_gpio_enter_sleep(int from_idle) +{ + return; +} + +void msm_gpio_exit_sleep(void) +{ + return; +} + +static int __init msm_init_gpio(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(msm_gpio_chips); i++) { + spin_lock_init(&msm_gpio_chips[i].lock); + gpiochip_add(&msm_gpio_chips[i].chip); + } + + return 0; +} + +postcore_initcall(msm_init_gpio); + +int gpio_tlmm_config(unsigned config, unsigned disable) +{ + uint32_t flags; + unsigned gpio = GPIO_PIN(config); + + if (gpio > NR_MSM_GPIOS) + return -EINVAL; + flags = ((GPIO_DRVSTR(config) << 6) & (0x7 << 6)) | + ((GPIO_FUNC(config) << 2) & (0xf << 2)) | + ((GPIO_PULL(config) & 0x3)); + dsb(); + __raw_writel(gpio, MSM_GPIO_PAGE); + dsb(); + __raw_writel(flags, MSM_GPIO_CONFIG); + + return 0; +} +EXPORT_SYMBOL(gpio_tlmm_config); + +int msm_gpios_request_enable(const struct msm_gpio *table, int size) +{ + int rc = msm_gpios_request(table, size); + if (rc) + return rc; + rc = msm_gpios_enable(table, size); + if (rc) + msm_gpios_free(table, size); + return rc; +} +EXPORT_SYMBOL(msm_gpios_request_enable); + +void msm_gpios_disable_free(const struct msm_gpio *table, int size) +{ + msm_gpios_disable(table, size); + msm_gpios_free(table, size); +} +EXPORT_SYMBOL(msm_gpios_disable_free); + +int msm_gpios_request(const struct msm_gpio *table, int size) +{ + int rc; + int i; + const struct msm_gpio *g; + for (i = 0; i < size; i++) { + g = table + i; + rc = gpio_request(GPIO_PIN(g->gpio_cfg), g->label); + if (rc) { + pr_err("gpio_request(%d) <%s> failed: %d\n", + GPIO_PIN(g->gpio_cfg), g->label ?: "?", rc); + goto err; + } + } + return 0; +err: + msm_gpios_free(table, i); + return rc; +} +EXPORT_SYMBOL(msm_gpios_request); + +void msm_gpios_free(const struct msm_gpio *table, int size) +{ + int i; + const struct msm_gpio *g; + for (i = size-1; i >= 0; i--) { + g = table + i; + gpio_free(GPIO_PIN(g->gpio_cfg)); + } +} +EXPORT_SYMBOL(msm_gpios_free); + +int msm_gpios_enable(const struct msm_gpio *table, int size) +{ + int rc; + int i; + const struct msm_gpio *g; + for (i = 0; i < size; i++) { + g = table + i; + rc = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_ENABLE); + if (rc) { + pr_err("gpio_tlmm_config(0x%08x, GPIO_CFG_ENABLE)" + " <%s> failed: %d\n", + g->gpio_cfg, g->label ?: "?", rc); + pr_err("pin %d func %d dir %d pull %d drvstr %d\n", + GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg), + GPIO_DIR(g->gpio_cfg), GPIO_PULL(g->gpio_cfg), + GPIO_DRVSTR(g->gpio_cfg)); + goto err; + } + } + return 0; +err: + msm_gpios_disable(table, i); + return rc; +} +EXPORT_SYMBOL(msm_gpios_enable); + +int msm_gpios_disable(const struct msm_gpio *table, int size) +{ + int rc = 0; + int i; + const struct msm_gpio *g; + for (i = size-1; i >= 0; i--) { + int tmp; + g = table + i; + tmp = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_DISABLE); + if (tmp) { + pr_err("gpio_tlmm_config(0x%08x, GPIO_CFG_DISABLE)" + " <%s> failed: %d\n", + g->gpio_cfg, g->label ?: "?", rc); + pr_err("pin %d func %d dir %d pull %d drvstr %d\n", + GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg), + GPIO_DIR(g->gpio_cfg), GPIO_PULL(g->gpio_cfg), + GPIO_DRVSTR(g->gpio_cfg)); + if (!rc) + rc = tmp; + } + } + + return rc; +} +EXPORT_SYMBOL(msm_gpios_disable); + diff --git a/arch/arm/mach-msm/gpio-v2.c b/arch/arm/mach-msm/gpio-v2.c index cc9c4fd7ccc..d5d6b927d3c 100644 --- a/arch/arm/mach-msm/gpio-v2.c +++ b/arch/arm/mach-msm/gpio-v2.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and @@ -9,66 +9,50 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * */ -#define pr_fmt(fmt) "%s: " fmt, __func__ - #include #include #include #include #include -#include #include +#include #include -#include #include +#include #include #include #include "gpiomux.h" +#include "mpm.h" /* Bits of interest in the GPIO_IN_OUT register. */ enum { - GPIO_IN = 0, - GPIO_OUT = 1 + GPIO_IN_BIT = 0, + GPIO_OUT_BIT = 1 }; /* Bits of interest in the GPIO_INTR_STATUS register. */ enum { - INTR_STATUS = 0, + INTR_STATUS_BIT = 0, }; /* Bits of interest in the GPIO_CFG register. */ enum { - GPIO_OE = 9, + GPIO_OE_BIT = 9, }; /* Bits of interest in the GPIO_INTR_CFG register. - * When a GPIO triggers, two separate decisions are made, controlled - * by two separate flags. - * - * - First, INTR_RAW_STATUS_EN controls whether or not the GPIO_INTR_STATUS - * register for that GPIO will be updated to reflect the triggering of that - * gpio. If this bit is 0, this register will not be updated. - * - Second, INTR_ENABLE controls whether an interrupt is triggered. - * - * If INTR_ENABLE is set and INTR_RAW_STATUS_EN is NOT set, an interrupt - * can be triggered but the status register will not reflect it. */ enum { - INTR_ENABLE = 0, - INTR_POL_CTL = 1, - INTR_DECT_CTL = 2, - INTR_RAW_STATUS_EN = 3, + INTR_ENABLE_BIT = 0, + INTR_POL_CTL_BIT = 1, + INTR_DECT_CTL_BIT = 2, + INTR_RAW_STATUS_EN_BIT = 3, }; /* Codes of interest in GPIO_INTR_CFG_SU. @@ -78,8 +62,83 @@ enum { TARGET_PROC_NONE = 7, }; +/* + * There is no 'DC_POLARITY_LO' because the GIC is incapable + * of asserting on falling edge or level-low conditions. Even though + * the registers allow for low-polarity inputs, the case can never arise. + */ +enum { + DC_POLARITY_HI = BIT(11), + DC_IRQ_ENABLE = BIT(3), +}; + +enum msm_tlmm_register { + SDC4_HDRV_PULL_CTL = 0x20a0, + SDC3_HDRV_PULL_CTL = 0x20a4, + SDC1_HDRV_PULL_CTL = 0x20a0, +}; + +struct tlmm_field_cfg { + enum msm_tlmm_register reg; + u8 off; +}; + +static const struct tlmm_field_cfg tlmm_hdrv_cfgs[] = { + {SDC4_HDRV_PULL_CTL, 6}, /* TLMM_HDRV_SDC4_CLK */ + {SDC4_HDRV_PULL_CTL, 3}, /* TLMM_HDRV_SDC4_CMD */ + {SDC4_HDRV_PULL_CTL, 0}, /* TLMM_HDRV_SDC4_DATA */ + {SDC3_HDRV_PULL_CTL, 6}, /* TLMM_HDRV_SDC3_CLK */ + {SDC3_HDRV_PULL_CTL, 3}, /* TLMM_HDRV_SDC3_CMD */ + {SDC3_HDRV_PULL_CTL, 0}, /* TLMM_HDRV_SDC3_DATA */ + {SDC1_HDRV_PULL_CTL, 6}, /* TLMM_HDRV_SDC1_CLK */ + {SDC1_HDRV_PULL_CTL, 3}, /* TLMM_HDRV_SDC1_CMD */ + {SDC1_HDRV_PULL_CTL, 0}, /* TLMM_HDRV_SDC1_DATA */ +}; + +static const struct tlmm_field_cfg tlmm_pull_cfgs[] = { + {SDC4_HDRV_PULL_CTL, 11}, /* TLMM_PULL_SDC4_CMD */ + {SDC4_HDRV_PULL_CTL, 9}, /* TLMM_PULL_SDC4_DATA */ + {SDC3_HDRV_PULL_CTL, 14}, /* TLMM_PULL_SDC3_CLK */ + {SDC3_HDRV_PULL_CTL, 11}, /* TLMM_PULL_SDC3_CMD */ + {SDC3_HDRV_PULL_CTL, 9}, /* TLMM_PULL_SDC3_DATA */ + {SDC1_HDRV_PULL_CTL, 13}, /* TLMM_PULL_SDC1_CLK */ + {SDC1_HDRV_PULL_CTL, 11}, /* TLMM_PULL_SDC1_CMD */ + {SDC1_HDRV_PULL_CTL, 9}, /* TLMM_PULL_SDC1_DATA */ +}; + +/* + * Supported arch specific irq extension. + * Default make them NULL. + */ +struct irq_chip msm_gpio_irq_extn = { + .irq_eoi = NULL, + .irq_mask = NULL, + .irq_unmask = NULL, + .irq_retrigger = NULL, + .irq_set_type = NULL, + .irq_set_wake = NULL, + .irq_disable = NULL, +}; + +/* + * When a GPIO triggers, two separate decisions are made, controlled + * by two separate flags. + * + * - First, INTR_RAW_STATUS_EN controls whether or not the GPIO_INTR_STATUS + * register for that GPIO will be updated to reflect the triggering of that + * gpio. If this bit is 0, this register will not be updated. + * - Second, INTR_ENABLE controls whether an interrupt is triggered. + * + * If INTR_ENABLE is set and INTR_RAW_STATUS_EN is NOT set, an interrupt + * can be triggered but the status register will not reflect it. + */ +#define INTR_RAW_STATUS_EN BIT(INTR_RAW_STATUS_EN_BIT) +#define INTR_ENABLE BIT(INTR_ENABLE_BIT) +#define INTR_DECT_CTL_EDGE BIT(INTR_DECT_CTL_BIT) +#define INTR_POL_CTL_HI BIT(INTR_POL_CTL_BIT) #define GPIO_INTR_CFG_SU(gpio) (MSM_TLMM_BASE + 0x0400 + (0x04 * (gpio))) +#define DIR_CONN_INTR_CFG_SU(irq) (MSM_TLMM_BASE + 0x0700 + (0x04 * (irq))) #define GPIO_CONFIG(gpio) (MSM_TLMM_BASE + 0x1000 + (0x10 * (gpio))) #define GPIO_IN_OUT(gpio) (MSM_TLMM_BASE + 0x1004 + (0x10 * (gpio))) #define GPIO_INTR_CFG(gpio) (MSM_TLMM_BASE + 0x1008 + (0x10 * (gpio))) @@ -90,7 +149,7 @@ enum { * * @enabled_irqs: a bitmap used to optimize the summary-irq handler. By * keeping track of which gpios are unmasked as irq sources, we avoid - * having to do readl calls on hundreds of iomapped registers each time + * having to do __raw_readl calls on hundreds of iomapped registers each time * the summary interrupt fires in order to locate the active interrupts. * * @wake_irqs: a bitmap for tracking which interrupt lines are enabled @@ -103,9 +162,9 @@ enum { */ struct msm_gpio_dev { struct gpio_chip gpio_chip; - DECLARE_BITMAP(enabled_irqs, NR_GPIO_IRQS); - DECLARE_BITMAP(wake_irqs, NR_GPIO_IRQS); - DECLARE_BITMAP(dual_edge_irqs, NR_GPIO_IRQS); + DECLARE_BITMAP(enabled_irqs, NR_MSM_GPIOS); + DECLARE_BITMAP(wake_irqs, NR_MSM_GPIOS); + DECLARE_BITMAP(dual_edge_irqs, NR_MSM_GPIOS); }; static DEFINE_SPINLOCK(tlmm_lock); @@ -117,22 +176,26 @@ static inline struct msm_gpio_dev *to_msm_gpio_dev(struct gpio_chip *chip) static inline void set_gpio_bits(unsigned n, void __iomem *reg) { - writel(readl(reg) | n, reg); + __raw_writel(__raw_readl(reg) | n, reg); } -static inline void clear_gpio_bits(unsigned n, void __iomem *reg) +static inline void clr_gpio_bits(unsigned n, void __iomem *reg) { - writel(readl(reg) & ~n, reg); + __raw_writel(__raw_readl(reg) & ~n, reg); } static int msm_gpio_get(struct gpio_chip *chip, unsigned offset) { - return readl(GPIO_IN_OUT(offset)) & BIT(GPIO_IN); + int rc; + rc = __raw_readl(GPIO_IN_OUT(offset)) & BIT(GPIO_IN_BIT); + mb(); + return rc; } static void msm_gpio_set(struct gpio_chip *chip, unsigned offset, int val) { - writel(val ? BIT(GPIO_OUT) : 0, GPIO_IN_OUT(offset)); + __raw_writel(val ? BIT(GPIO_OUT_BIT) : 0, GPIO_IN_OUT(offset)); + mb(); } static int msm_gpio_direction_input(struct gpio_chip *chip, unsigned offset) @@ -140,7 +203,8 @@ static int msm_gpio_direction_input(struct gpio_chip *chip, unsigned offset) unsigned long irq_flags; spin_lock_irqsave(&tlmm_lock, irq_flags); - clear_gpio_bits(BIT(GPIO_OE), GPIO_CONFIG(offset)); + clr_gpio_bits(BIT(GPIO_OE_BIT), GPIO_CONFIG(offset)); + mb(); spin_unlock_irqrestore(&tlmm_lock, irq_flags); return 0; } @@ -153,35 +217,36 @@ static int msm_gpio_direction_output(struct gpio_chip *chip, spin_lock_irqsave(&tlmm_lock, irq_flags); msm_gpio_set(chip, offset, val); - set_gpio_bits(BIT(GPIO_OE), GPIO_CONFIG(offset)); + set_gpio_bits(BIT(GPIO_OE_BIT), GPIO_CONFIG(offset)); + mb(); spin_unlock_irqrestore(&tlmm_lock, irq_flags); return 0; } -static int msm_gpio_request(struct gpio_chip *chip, unsigned offset) +static int msm_gpio_to_irq(struct gpio_chip *chip, unsigned offset) { - return msm_gpiomux_get(chip->base + offset); + return MSM_GPIO_TO_INT(offset - chip->base); } -static void msm_gpio_free(struct gpio_chip *chip, unsigned offset) +static inline int msm_irq_to_gpio(struct gpio_chip *chip, unsigned irq) { - msm_gpiomux_put(chip->base + offset); + return irq - MSM_GPIO_TO_INT(chip->base); } -static int msm_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +static int msm_gpio_request(struct gpio_chip *chip, unsigned offset) { - return MSM_GPIO_TO_INT(chip->base + offset); + return msm_gpiomux_get(chip->base + offset); } -static inline int msm_irq_to_gpio(struct gpio_chip *chip, unsigned irq) +static void msm_gpio_free(struct gpio_chip *chip, unsigned offset) { - return irq - MSM_GPIO_TO_INT(chip->base); + msm_gpiomux_put(chip->base + offset); } static struct msm_gpio_dev msm_gpio = { .gpio_chip = { .base = 0, - .ngpio = NR_GPIO_IRQS, + .ngpio = NR_MSM_GPIOS, .direction_input = msm_gpio_direction_input, .direction_output = msm_gpio_direction_output, .get = msm_gpio_get, @@ -192,6 +257,18 @@ static struct msm_gpio_dev msm_gpio = { }, }; +static void switch_mpm_config(struct irq_data *d, unsigned val) +{ + /* switch the configuration in the mpm as well */ + if (!msm_gpio_irq_extn.irq_set_type) + return; + + if (val) + msm_gpio_irq_extn.irq_set_type(d, IRQF_TRIGGER_FALLING); + else + msm_gpio_irq_extn.irq_set_type(d, IRQF_TRIGGER_RISING); +} + /* For dual-edge interrupts in software, since the hardware has no * such support: * @@ -212,34 +289,44 @@ static struct msm_gpio_dev msm_gpio = { * * Algorithm comes from Google's msmgpio driver, see mach-msm/gpio.c. */ -static void msm_gpio_update_dual_edge_pos(unsigned gpio) +static void msm_gpio_update_dual_edge_pos(struct irq_data *d, unsigned gpio) { int loop_limit = 100; unsigned val, val2, intstat; do { - val = readl(GPIO_IN_OUT(gpio)) & BIT(GPIO_IN); + val = __raw_readl(GPIO_IN_OUT(gpio)) & BIT(GPIO_IN_BIT); if (val) - clear_gpio_bits(BIT(INTR_POL_CTL), GPIO_INTR_CFG(gpio)); + clr_gpio_bits(INTR_POL_CTL_HI, GPIO_INTR_CFG(gpio)); else - set_gpio_bits(BIT(INTR_POL_CTL), GPIO_INTR_CFG(gpio)); - val2 = readl(GPIO_IN_OUT(gpio)) & BIT(GPIO_IN); - intstat = readl(GPIO_INTR_STATUS(gpio)) & BIT(INTR_STATUS); - if (intstat || val == val2) + set_gpio_bits(INTR_POL_CTL_HI, GPIO_INTR_CFG(gpio)); + val2 = __raw_readl(GPIO_IN_OUT(gpio)) & BIT(GPIO_IN_BIT); + intstat = __raw_readl(GPIO_INTR_STATUS(gpio)) & + BIT(INTR_STATUS_BIT); + if (intstat || val == val2) { + switch_mpm_config(d, val); return; + } } while (loop_limit-- > 0); - pr_err("dual-edge irq failed to stabilize, " + pr_err("%s: dual-edge irq failed to stabilize, " "interrupts dropped. %#08x != %#08x\n", - val, val2); + __func__, val, val2); } static void msm_gpio_irq_ack(struct irq_data *d) { int gpio = msm_irq_to_gpio(&msm_gpio.gpio_chip, d->irq); - writel(BIT(INTR_STATUS), GPIO_INTR_STATUS(gpio)); + __raw_writel(BIT(INTR_STATUS_BIT), GPIO_INTR_STATUS(gpio)); if (test_bit(gpio, msm_gpio.dual_edge_irqs)) - msm_gpio_update_dual_edge_pos(gpio); + msm_gpio_update_dual_edge_pos(d, gpio); + mb(); +} + +static void __msm_gpio_irq_mask(unsigned int gpio) +{ + __raw_writel(TARGET_PROC_NONE, GPIO_INTR_CFG_SU(gpio)); + clr_gpio_bits(INTR_RAW_STATUS_EN | INTR_ENABLE, GPIO_INTR_CFG(gpio)); } static void msm_gpio_irq_mask(struct irq_data *d) @@ -248,10 +335,20 @@ static void msm_gpio_irq_mask(struct irq_data *d) unsigned long irq_flags; spin_lock_irqsave(&tlmm_lock, irq_flags); - writel(TARGET_PROC_NONE, GPIO_INTR_CFG_SU(gpio)); - clear_gpio_bits(INTR_RAW_STATUS_EN | INTR_ENABLE, GPIO_INTR_CFG(gpio)); + __msm_gpio_irq_mask(gpio); __clear_bit(gpio, msm_gpio.enabled_irqs); + mb(); spin_unlock_irqrestore(&tlmm_lock, irq_flags); + + if (msm_gpio_irq_extn.irq_mask) + msm_gpio_irq_extn.irq_mask(d); + +} + +static void __msm_gpio_irq_unmask(unsigned int gpio) +{ + set_gpio_bits(INTR_RAW_STATUS_EN | INTR_ENABLE, GPIO_INTR_CFG(gpio)); + __raw_writel(TARGET_PROC_SCORPION, GPIO_INTR_CFG_SU(gpio)); } static void msm_gpio_irq_unmask(struct irq_data *d) @@ -261,9 +358,18 @@ static void msm_gpio_irq_unmask(struct irq_data *d) spin_lock_irqsave(&tlmm_lock, irq_flags); __set_bit(gpio, msm_gpio.enabled_irqs); - set_gpio_bits(INTR_RAW_STATUS_EN | INTR_ENABLE, GPIO_INTR_CFG(gpio)); - writel(TARGET_PROC_SCORPION, GPIO_INTR_CFG_SU(gpio)); + __msm_gpio_irq_unmask(gpio); + mb(); spin_unlock_irqrestore(&tlmm_lock, irq_flags); + + if (msm_gpio_irq_extn.irq_mask) + msm_gpio_irq_extn.irq_unmask(d); +} + +static void msm_gpio_irq_disable(struct irq_data *d) +{ + if (msm_gpio_irq_extn.irq_disable) + msm_gpio_irq_extn.irq_disable(d); } static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type) @@ -274,33 +380,37 @@ static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type) spin_lock_irqsave(&tlmm_lock, irq_flags); - bits = readl(GPIO_INTR_CFG(gpio)); + bits = __raw_readl(GPIO_INTR_CFG(gpio)); if (flow_type & IRQ_TYPE_EDGE_BOTH) { - bits |= BIT(INTR_DECT_CTL); + bits |= INTR_DECT_CTL_EDGE; __irq_set_handler_locked(d->irq, handle_edge_irq); if ((flow_type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) __set_bit(gpio, msm_gpio.dual_edge_irqs); else __clear_bit(gpio, msm_gpio.dual_edge_irqs); } else { - bits &= ~BIT(INTR_DECT_CTL); + bits &= ~INTR_DECT_CTL_EDGE; __irq_set_handler_locked(d->irq, handle_level_irq); __clear_bit(gpio, msm_gpio.dual_edge_irqs); } if (flow_type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH)) - bits |= BIT(INTR_POL_CTL); + bits |= INTR_POL_CTL_HI; else - bits &= ~BIT(INTR_POL_CTL); + bits &= ~INTR_POL_CTL_HI; - writel(bits, GPIO_INTR_CFG(gpio)); + __raw_writel(bits, GPIO_INTR_CFG(gpio)); if ((flow_type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) - msm_gpio_update_dual_edge_pos(gpio); + msm_gpio_update_dual_edge_pos(d, gpio); + mb(); spin_unlock_irqrestore(&tlmm_lock, irq_flags); + if (msm_gpio_irq_extn.irq_set_type) + msm_gpio_irq_extn.irq_set_type(d, flow_type); + return 0; } @@ -310,22 +420,24 @@ static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type) * which have been set as summary IRQ lines and which are triggered, * and to call their interrupt handlers. */ -static void msm_summary_irq_handler(unsigned int irq, struct irq_desc *desc) +static irqreturn_t msm_summary_irq_handler(int irq, void *data) { unsigned long i; + struct irq_desc *desc = irq_to_desc(irq); struct irq_chip *chip = irq_desc_get_chip(desc); chained_irq_enter(chip, desc); - for (i = find_first_bit(msm_gpio.enabled_irqs, NR_GPIO_IRQS); - i < NR_GPIO_IRQS; - i = find_next_bit(msm_gpio.enabled_irqs, NR_GPIO_IRQS, i + 1)) { - if (readl(GPIO_INTR_STATUS(i)) & BIT(INTR_STATUS)) + for (i = find_first_bit(msm_gpio.enabled_irqs, NR_MSM_GPIOS); + i < NR_MSM_GPIOS; + i = find_next_bit(msm_gpio.enabled_irqs, NR_MSM_GPIOS, i + 1)) { + if (__raw_readl(GPIO_INTR_STATUS(i)) & BIT(INTR_STATUS_BIT)) generic_handle_irq(msm_gpio_to_irq(&msm_gpio.gpio_chip, i)); } chained_irq_exit(chip, desc); + return IRQ_HANDLED; } static int msm_gpio_irq_set_wake(struct irq_data *d, unsigned int on) @@ -333,15 +445,18 @@ static int msm_gpio_irq_set_wake(struct irq_data *d, unsigned int on) int gpio = msm_irq_to_gpio(&msm_gpio.gpio_chip, d->irq); if (on) { - if (bitmap_empty(msm_gpio.wake_irqs, NR_GPIO_IRQS)) - irq_set_irq_wake(TLMM_SCSS_SUMMARY_IRQ, 1); + if (bitmap_empty(msm_gpio.wake_irqs, NR_MSM_GPIOS)) + irq_set_irq_wake(TLMM_MSM_SUMMARY_IRQ, 1); set_bit(gpio, msm_gpio.wake_irqs); } else { clear_bit(gpio, msm_gpio.wake_irqs); - if (bitmap_empty(msm_gpio.wake_irqs, NR_GPIO_IRQS)) - irq_set_irq_wake(TLMM_SCSS_SUMMARY_IRQ, 0); + if (bitmap_empty(msm_gpio.wake_irqs, NR_MSM_GPIOS)) + irq_set_irq_wake(TLMM_MSM_SUMMARY_IRQ, 0); } + if (msm_gpio_irq_extn.irq_set_wake) + msm_gpio_irq_extn.irq_set_wake(d, on); + return 0; } @@ -352,16 +467,17 @@ static struct irq_chip msm_gpio_irq_chip = { .irq_ack = msm_gpio_irq_ack, .irq_set_type = msm_gpio_irq_set_type, .irq_set_wake = msm_gpio_irq_set_wake, + .irq_disable = msm_gpio_irq_disable, }; -static int __devinit msm_gpio_probe(struct platform_device *dev) +static int __devinit msm_gpio_probe(void) { int i, irq, ret; - bitmap_zero(msm_gpio.enabled_irqs, NR_GPIO_IRQS); - bitmap_zero(msm_gpio.wake_irqs, NR_GPIO_IRQS); - bitmap_zero(msm_gpio.dual_edge_irqs, NR_GPIO_IRQS); - msm_gpio.gpio_chip.label = dev->name; + spin_lock_init(&tlmm_lock); + bitmap_zero(msm_gpio.enabled_irqs, NR_MSM_GPIOS); + bitmap_zero(msm_gpio.wake_irqs, NR_MSM_GPIOS); + bitmap_zero(msm_gpio.dual_edge_irqs, NR_MSM_GPIOS); ret = gpiochip_add(&msm_gpio.gpio_chip); if (ret < 0) return ret; @@ -373,61 +489,190 @@ static int __devinit msm_gpio_probe(struct platform_device *dev) set_irq_flags(irq, IRQF_VALID); } - irq_set_chained_handler(TLMM_SCSS_SUMMARY_IRQ, - msm_summary_irq_handler); + ret = request_irq(TLMM_MSM_SUMMARY_IRQ, msm_summary_irq_handler, + IRQF_TRIGGER_HIGH, "msmgpio", NULL); + if (ret) { + pr_err("Request_irq failed for TLMM_MSM_SUMMARY_IRQ - %d\n", + ret); + return ret; + } return 0; } -static int __devexit msm_gpio_remove(struct platform_device *dev) +static int __devexit msm_gpio_remove(void) { int ret = gpiochip_remove(&msm_gpio.gpio_chip); if (ret < 0) return ret; - irq_set_handler(TLMM_SCSS_SUMMARY_IRQ, NULL); + irq_set_handler(TLMM_MSM_SUMMARY_IRQ, NULL); return 0; } -static struct platform_driver msm_gpio_driver = { - .probe = msm_gpio_probe, - .remove = __devexit_p(msm_gpio_remove), - .driver = { - .name = "msmgpio", - .owner = THIS_MODULE, - }, -}; +#ifdef CONFIG_PM +static int msm_gpio_suspend(void) +{ + unsigned long irq_flags; + unsigned long i; -static struct platform_device msm_device_gpio = { - .name = "msmgpio", - .id = -1, -}; + spin_lock_irqsave(&tlmm_lock, irq_flags); + for_each_set_bit(i, msm_gpio.enabled_irqs, NR_MSM_GPIOS) + __msm_gpio_irq_mask(i); -static int __init msm_gpio_init(void) + for_each_set_bit(i, msm_gpio.wake_irqs, NR_MSM_GPIOS) + __msm_gpio_irq_unmask(i); + mb(); + spin_unlock_irqrestore(&tlmm_lock, irq_flags); + return 0; +} + +extern int msm_show_resume_irq_mask; + +void msm_gpio_show_resume_irq(void) { - int rc; + unsigned long irq_flags; + int i, irq, intstat; - rc = platform_driver_register(&msm_gpio_driver); - if (!rc) { - rc = platform_device_register(&msm_device_gpio); - if (rc) - platform_driver_unregister(&msm_gpio_driver); + if (!msm_show_resume_irq_mask) + return; + + spin_lock_irqsave(&tlmm_lock, irq_flags); + for_each_set_bit(i, msm_gpio.wake_irqs, NR_MSM_GPIOS) { + intstat = __raw_readl(GPIO_INTR_STATUS(i)) & + BIT(INTR_STATUS_BIT); + if (intstat) { + irq = msm_gpio_to_irq(&msm_gpio.gpio_chip, i); + pr_warning("%s: %d triggered\n", + __func__, irq); + } } + spin_unlock_irqrestore(&tlmm_lock, irq_flags); +} - return rc; +static void msm_gpio_resume(void) +{ + unsigned long irq_flags; + unsigned long i; + + msm_gpio_show_resume_irq(); + + spin_lock_irqsave(&tlmm_lock, irq_flags); + for_each_set_bit(i, msm_gpio.wake_irqs, NR_MSM_GPIOS) + __msm_gpio_irq_mask(i); + + for_each_set_bit(i, msm_gpio.enabled_irqs, NR_MSM_GPIOS) + __msm_gpio_irq_unmask(i); + mb(); + spin_unlock_irqrestore(&tlmm_lock, irq_flags); +} +#else +#define msm_gpio_suspend NULL +#define msm_gpio_resume NULL +#endif + +static struct syscore_ops msm_gpio_syscore_ops = { + .suspend = msm_gpio_suspend, + .resume = msm_gpio_resume, +}; + +static int __init msm_gpio_init(void) +{ + msm_gpio_probe(); + register_syscore_ops(&msm_gpio_syscore_ops); + return 0; } static void __exit msm_gpio_exit(void) { - platform_device_unregister(&msm_device_gpio); - platform_driver_unregister(&msm_gpio_driver); + unregister_syscore_ops(&msm_gpio_syscore_ops); + msm_gpio_remove(); } postcore_initcall(msm_gpio_init); module_exit(msm_gpio_exit); +static void msm_tlmm_set_field(const struct tlmm_field_cfg *configs, + unsigned id, unsigned width, unsigned val) +{ + unsigned long irqflags; + u32 mask = (1 << width) - 1; + u32 __iomem *reg = MSM_TLMM_BASE + configs[id].reg; + u32 reg_val; + + spin_lock_irqsave(&tlmm_lock, irqflags); + reg_val = __raw_readl(reg); + reg_val &= ~(mask << configs[id].off); + reg_val |= (val & mask) << configs[id].off; + __raw_writel(reg_val, reg); + mb(); + spin_unlock_irqrestore(&tlmm_lock, irqflags); +} + +void msm_tlmm_set_hdrive(enum msm_tlmm_hdrive_tgt tgt, int drv_str) +{ + msm_tlmm_set_field(tlmm_hdrv_cfgs, tgt, 3, drv_str); +} +EXPORT_SYMBOL(msm_tlmm_set_hdrive); + +void msm_tlmm_set_pull(enum msm_tlmm_pull_tgt tgt, int pull) +{ + msm_tlmm_set_field(tlmm_pull_cfgs, tgt, 2, pull); +} +EXPORT_SYMBOL(msm_tlmm_set_pull); + +int gpio_tlmm_config(unsigned config, unsigned disable) +{ + uint32_t flags; + unsigned gpio = GPIO_PIN(config); + + if (gpio > NR_MSM_GPIOS) + return -EINVAL; + + flags = ((GPIO_DIR(config) << 9) & (0x1 << 9)) | + ((GPIO_DRVSTR(config) << 6) & (0x7 << 6)) | + ((GPIO_FUNC(config) << 2) & (0xf << 2)) | + ((GPIO_PULL(config) & 0x3)); + __raw_writel(flags, GPIO_CONFIG(gpio)); + mb(); + + return 0; +} +EXPORT_SYMBOL(gpio_tlmm_config); + +int msm_gpio_install_direct_irq(unsigned gpio, unsigned irq, + unsigned int input_polarity) +{ + unsigned long irq_flags; + uint32_t bits; + + if (gpio >= NR_MSM_GPIOS || irq >= NR_TLMM_MSM_DIR_CONN_IRQ) + return -EINVAL; + + spin_lock_irqsave(&tlmm_lock, irq_flags); + + __raw_writel(__raw_readl(GPIO_CONFIG(gpio)) | BIT(GPIO_OE_BIT), + GPIO_CONFIG(gpio)); + __raw_writel(__raw_readl(GPIO_INTR_CFG(gpio)) & + ~(INTR_RAW_STATUS_EN | INTR_ENABLE), + GPIO_INTR_CFG(gpio)); + __raw_writel(DC_IRQ_ENABLE | TARGET_PROC_NONE, + GPIO_INTR_CFG_SU(gpio)); + + bits = TARGET_PROC_SCORPION | (gpio << 3); + if (input_polarity) + bits |= DC_POLARITY_HI; + __raw_writel(bits, DIR_CONN_INTR_CFG_SU(irq)); + + mb(); + spin_unlock_irqrestore(&tlmm_lock, irq_flags); + + return 0; +} +EXPORT_SYMBOL(msm_gpio_install_direct_irq); + MODULE_AUTHOR("Gregory Bean "); MODULE_DESCRIPTION("Driver for Qualcomm MSM TLMMv2 SoC GPIOs"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:msmgpio"); +MODULE_ALIAS("sysdev:msmgpio"); diff --git a/arch/arm/mach-msm/gpio.c b/arch/arm/mach-msm/gpio.c index 5ea273b00da..b18db56eace 100644 --- a/arch/arm/mach-msm/gpio.c +++ b/arch/arm/mach-msm/gpio.c @@ -1,7 +1,7 @@ /* linux/arch/arm/mach-msm/gpio.c * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 @@ -22,6 +22,15 @@ #include #include "gpio_hw.h" #include "gpiomux.h" +#include "proc_comm.h" +#include "smd_private.h" + +enum { + GPIO_DEBUG_SLEEP = 1U << 0, +}; +static int msm_gpio_debug_mask; +module_param_named(debug_mask, msm_gpio_debug_mask, int, + S_IRUGO | S_IWUSR | S_IWGRP); #define FIRST_GPIO_IRQ MSM_GPIO_TO_INT(0) @@ -80,11 +89,11 @@ static int msm_gpio_write(struct msm_gpio_chip *msm_chip, unsigned mask = BIT(offset); unsigned val; - val = readl(msm_chip->regs.out); + val = __raw_readl(msm_chip->regs.out); if (on) - writel(val | mask, msm_chip->regs.out); + __raw_writel(val | mask, msm_chip->regs.out); else - writel(val & ~mask, msm_chip->regs.out); + __raw_writel(val & ~mask, msm_chip->regs.out); return 0; } @@ -93,13 +102,13 @@ static void msm_gpio_update_both_edge_detect(struct msm_gpio_chip *msm_chip) int loop_limit = 100; unsigned pol, val, val2, intstat; do { - val = readl(msm_chip->regs.in); - pol = readl(msm_chip->regs.int_pos); + val = __raw_readl(msm_chip->regs.in); + pol = __raw_readl(msm_chip->regs.int_pos); pol = (pol & ~msm_chip->both_edge_detect) | (~val & msm_chip->both_edge_detect); - writel(pol, msm_chip->regs.int_pos); - intstat = readl(msm_chip->regs.int_status); - val2 = readl(msm_chip->regs.in); + __raw_writel(pol, msm_chip->regs.int_pos); + intstat = __raw_readl(msm_chip->regs.int_status); + val2 = __raw_readl(msm_chip->regs.in); if (((val ^ val2) & msm_chip->both_edge_detect & ~intstat) == 0) return; } while (loop_limit-- > 0); @@ -116,10 +125,10 @@ static int msm_gpio_clear_detect_status(struct msm_gpio_chip *msm_chip, /* Save interrupts that already triggered before we loose them. */ /* Any interrupt that triggers between the read of int_status */ /* and the write to int_clear will still be lost though. */ - msm_chip->int_status_copy |= readl(msm_chip->regs.int_status); + msm_chip->int_status_copy |= __raw_readl(msm_chip->regs.int_status); msm_chip->int_status_copy &= ~bit; #endif - writel(bit, msm_chip->regs.int_clear); + __raw_writel(bit, msm_chip->regs.int_clear); msm_gpio_update_both_edge_detect(msm_chip); return 0; } @@ -131,7 +140,9 @@ static int msm_gpio_direction_input(struct gpio_chip *chip, unsigned offset) msm_chip = container_of(chip, struct msm_gpio_chip, chip); spin_lock_irqsave(&msm_chip->lock, irq_flags); - writel(readl(msm_chip->regs.oe) & ~BIT(offset), msm_chip->regs.oe); + __raw_writel(__raw_readl(msm_chip->regs.oe) & ~BIT(offset), + msm_chip->regs.oe); + mb(); spin_unlock_irqrestore(&msm_chip->lock, irq_flags); return 0; } @@ -145,7 +156,9 @@ msm_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) msm_chip = container_of(chip, struct msm_gpio_chip, chip); spin_lock_irqsave(&msm_chip->lock, irq_flags); msm_gpio_write(msm_chip, offset, value); - writel(readl(msm_chip->regs.oe) | BIT(offset), msm_chip->regs.oe); + __raw_writel(__raw_readl(msm_chip->regs.oe) | BIT(offset), + msm_chip->regs.oe); + mb(); spin_unlock_irqrestore(&msm_chip->lock, irq_flags); return 0; } @@ -153,9 +166,12 @@ msm_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) static int msm_gpio_get(struct gpio_chip *chip, unsigned offset) { struct msm_gpio_chip *msm_chip; + int rc; msm_chip = container_of(chip, struct msm_gpio_chip, chip); - return (readl(msm_chip->regs.in) & (1U << offset)) ? 1 : 0; + rc = (__raw_readl(msm_chip->regs.in) & (1U << offset)) ? 1 : 0; + mb(); + return rc; } static void msm_gpio_set(struct gpio_chip *chip, unsigned offset, int value) @@ -166,6 +182,7 @@ static void msm_gpio_set(struct gpio_chip *chip, unsigned offset, int value) msm_chip = container_of(chip, struct msm_gpio_chip, chip); spin_lock_irqsave(&msm_chip->lock, irq_flags); msm_gpio_write(msm_chip, offset, value); + mb(); spin_unlock_irqrestore(&msm_chip->lock, irq_flags); } @@ -228,47 +245,49 @@ struct msm_gpio_chip msm_gpio_chips[] = { static void msm_gpio_irq_ack(struct irq_data *d) { unsigned long irq_flags; - struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + struct msm_gpio_chip *msm_chip = irq_get_chip_data(d->irq); spin_lock_irqsave(&msm_chip->lock, irq_flags); msm_gpio_clear_detect_status(msm_chip, - d->irq - gpio_to_irq(msm_chip->chip.base)); + d->irq - gpio_to_irq(msm_chip->chip.base)); spin_unlock_irqrestore(&msm_chip->lock, irq_flags); } static void msm_gpio_irq_mask(struct irq_data *d) { unsigned long irq_flags; - struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + struct msm_gpio_chip *msm_chip = irq_get_chip_data(d->irq); unsigned offset = d->irq - gpio_to_irq(msm_chip->chip.base); spin_lock_irqsave(&msm_chip->lock, irq_flags); /* level triggered interrupts are also latched */ - if (!(readl(msm_chip->regs.int_edge) & BIT(offset))) + if (!(__raw_readl(msm_chip->regs.int_edge) & BIT(offset))) msm_gpio_clear_detect_status(msm_chip, offset); msm_chip->int_enable[0] &= ~BIT(offset); - writel(msm_chip->int_enable[0], msm_chip->regs.int_en); + __raw_writel(msm_chip->int_enable[0], msm_chip->regs.int_en); + mb(); spin_unlock_irqrestore(&msm_chip->lock, irq_flags); } static void msm_gpio_irq_unmask(struct irq_data *d) { unsigned long irq_flags; - struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + struct msm_gpio_chip *msm_chip = irq_get_chip_data(d->irq); unsigned offset = d->irq - gpio_to_irq(msm_chip->chip.base); spin_lock_irqsave(&msm_chip->lock, irq_flags); /* level triggered interrupts are also latched */ - if (!(readl(msm_chip->regs.int_edge) & BIT(offset))) + if (!(__raw_readl(msm_chip->regs.int_edge) & BIT(offset))) msm_gpio_clear_detect_status(msm_chip, offset); msm_chip->int_enable[0] |= BIT(offset); - writel(msm_chip->int_enable[0], msm_chip->regs.int_en); + __raw_writel(msm_chip->int_enable[0], msm_chip->regs.int_en); + mb(); spin_unlock_irqrestore(&msm_chip->lock, irq_flags); } static int msm_gpio_irq_set_wake(struct irq_data *d, unsigned int on) { unsigned long irq_flags; - struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + struct msm_gpio_chip *msm_chip = irq_get_chip_data(d->irq); unsigned offset = d->irq - gpio_to_irq(msm_chip->chip.base); spin_lock_irqsave(&msm_chip->lock, irq_flags); @@ -285,17 +304,17 @@ static int msm_gpio_irq_set_wake(struct irq_data *d, unsigned int on) static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type) { unsigned long irq_flags; - struct msm_gpio_chip *msm_chip = irq_data_get_irq_chip_data(d); + struct msm_gpio_chip *msm_chip = irq_get_chip_data(d->irq); unsigned offset = d->irq - gpio_to_irq(msm_chip->chip.base); unsigned val, mask = BIT(offset); spin_lock_irqsave(&msm_chip->lock, irq_flags); - val = readl(msm_chip->regs.int_edge); + val = __raw_readl(msm_chip->regs.int_edge); if (flow_type & IRQ_TYPE_EDGE_BOTH) { - writel(val | mask, msm_chip->regs.int_edge); + __raw_writel(val | mask, msm_chip->regs.int_edge); __irq_set_handler_locked(d->irq, handle_edge_irq); } else { - writel(val & ~mask, msm_chip->regs.int_edge); + __raw_writel(val & ~mask, msm_chip->regs.int_edge); __irq_set_handler_locked(d->irq, handle_level_irq); } if ((flow_type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) { @@ -303,12 +322,13 @@ static int msm_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type) msm_gpio_update_both_edge_detect(msm_chip); } else { msm_chip->both_edge_detect &= ~mask; - val = readl(msm_chip->regs.int_pos); + val = __raw_readl(msm_chip->regs.int_pos); if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_HIGH)) - writel(val | mask, msm_chip->regs.int_pos); + __raw_writel(val | mask, msm_chip->regs.int_pos); else - writel(val & ~mask, msm_chip->regs.int_pos); + __raw_writel(val & ~mask, msm_chip->regs.int_pos); } + mb(); spin_unlock_irqrestore(&msm_chip->lock, irq_flags); return 0; } @@ -320,7 +340,7 @@ static void msm_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) for (i = 0; i < ARRAY_SIZE(msm_gpio_chips); i++) { struct msm_gpio_chip *msm_chip = &msm_gpio_chips[i]; - val = readl(msm_chip->regs.int_status); + val = __raw_readl(msm_chip->regs.int_status); val &= msm_chip->int_enable[0]; while (val) { mask = val & -val; @@ -337,14 +357,130 @@ static void msm_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) } static struct irq_chip msm_gpio_irq_chip = { - .name = "msmgpio", - .irq_ack = msm_gpio_irq_ack, - .irq_mask = msm_gpio_irq_mask, - .irq_unmask = msm_gpio_irq_unmask, - .irq_set_wake = msm_gpio_irq_set_wake, - .irq_set_type = msm_gpio_irq_set_type, + .name = "msmgpio", + .irq_ack = msm_gpio_irq_ack, + .irq_mask = msm_gpio_irq_mask, + .irq_unmask = msm_gpio_irq_unmask, + .irq_set_wake = msm_gpio_irq_set_wake, + .irq_set_type = msm_gpio_irq_set_type, }; +#define NUM_GPIO_SMEM_BANKS 6 +#define GPIO_SMEM_NUM_GROUPS 2 +#define GPIO_SMEM_MAX_PC_INTERRUPTS 8 +struct tramp_gpio_smem { + uint16_t num_fired[GPIO_SMEM_NUM_GROUPS]; + uint16_t fired[GPIO_SMEM_NUM_GROUPS][GPIO_SMEM_MAX_PC_INTERRUPTS]; + uint32_t enabled[NUM_GPIO_SMEM_BANKS]; + uint32_t detection[NUM_GPIO_SMEM_BANKS]; + uint32_t polarity[NUM_GPIO_SMEM_BANKS]; +}; + +static void msm_gpio_sleep_int(unsigned long arg) +{ + int i, j; + struct tramp_gpio_smem *smem_gpio; + + BUILD_BUG_ON(NR_GPIO_IRQS > NUM_GPIO_SMEM_BANKS * 32); + + smem_gpio = smem_alloc(SMEM_GPIO_INT, sizeof(*smem_gpio)); + if (smem_gpio == NULL) + return; + + local_irq_disable(); + for (i = 0; i < GPIO_SMEM_NUM_GROUPS; i++) { + int count = smem_gpio->num_fired[i]; + for (j = 0; j < count; j++) { + /* TODO: Check mask */ + generic_handle_irq( + MSM_GPIO_TO_INT(smem_gpio->fired[i][j])); + } + } + local_irq_enable(); +} + +static DECLARE_TASKLET(msm_gpio_sleep_int_tasklet, msm_gpio_sleep_int, 0); + +void msm_gpio_enter_sleep(int from_idle) +{ + int i; + struct tramp_gpio_smem *smem_gpio; + + smem_gpio = smem_alloc(SMEM_GPIO_INT, sizeof(*smem_gpio)); + + if (smem_gpio) { + for (i = 0; i < ARRAY_SIZE(smem_gpio->enabled); i++) { + smem_gpio->enabled[i] = 0; + smem_gpio->detection[i] = 0; + smem_gpio->polarity[i] = 0; + } + } + + for (i = 0; i < ARRAY_SIZE(msm_gpio_chips); i++) { + __raw_writel(msm_gpio_chips[i].int_enable[!from_idle], + msm_gpio_chips[i].regs.int_en); + if (smem_gpio) { + uint32_t tmp; + int start, index, shiftl, shiftr; + start = msm_gpio_chips[i].chip.base; + index = start / 32; + shiftl = start % 32; + shiftr = 32 - shiftl; + tmp = msm_gpio_chips[i].int_enable[!from_idle]; + smem_gpio->enabled[index] |= tmp << shiftl; + smem_gpio->enabled[index+1] |= tmp >> shiftr; + smem_gpio->detection[index] |= + __raw_readl(msm_gpio_chips[i].regs.int_edge) << + shiftl; + smem_gpio->detection[index+1] |= + __raw_readl(msm_gpio_chips[i].regs.int_edge) >> + shiftr; + smem_gpio->polarity[index] |= + __raw_readl(msm_gpio_chips[i].regs.int_pos) << + shiftl; + smem_gpio->polarity[index+1] |= + __raw_readl(msm_gpio_chips[i].regs.int_pos) >> + shiftr; + } + } + mb(); + + if (smem_gpio) { + if (msm_gpio_debug_mask & GPIO_DEBUG_SLEEP) + for (i = 0; i < ARRAY_SIZE(smem_gpio->enabled); i++) { + printk("msm_gpio_enter_sleep gpio %d-%d: enable" + " %08x, edge %08x, polarity %08x\n", + i * 32, i * 32 + 31, + smem_gpio->enabled[i], + smem_gpio->detection[i], + smem_gpio->polarity[i]); + } + for (i = 0; i < GPIO_SMEM_NUM_GROUPS; i++) + smem_gpio->num_fired[i] = 0; + } +} + +void msm_gpio_exit_sleep(void) +{ + int i; + struct tramp_gpio_smem *smem_gpio; + + smem_gpio = smem_alloc(SMEM_GPIO_INT, sizeof(*smem_gpio)); + + for (i = 0; i < ARRAY_SIZE(msm_gpio_chips); i++) { + __raw_writel(msm_gpio_chips[i].int_enable[0], + msm_gpio_chips[i].regs.int_en); + } + mb(); + + if (smem_gpio && (smem_gpio->num_fired[0] || smem_gpio->num_fired[1])) { + if (msm_gpio_debug_mask & GPIO_DEBUG_SLEEP) + printk(KERN_INFO "gpio: fired %x %x\n", + smem_gpio->num_fired[0], smem_gpio->num_fired[1]); + tasklet_schedule(&msm_gpio_sleep_int_tasklet); + } +} + static int __init msm_init_gpio(void) { int i, j = 0; @@ -360,17 +496,145 @@ static int __init msm_init_gpio(void) set_irq_flags(i, IRQF_VALID); } + irq_set_chained_handler(INT_GPIO_GROUP1, msm_gpio_irq_handler); + irq_set_chained_handler(INT_GPIO_GROUP2, msm_gpio_irq_handler); + for (i = 0; i < ARRAY_SIZE(msm_gpio_chips); i++) { spin_lock_init(&msm_gpio_chips[i].lock); - writel(0, msm_gpio_chips[i].regs.int_en); + __raw_writel(0, msm_gpio_chips[i].regs.int_en); gpiochip_add(&msm_gpio_chips[i].chip); } - irq_set_chained_handler(INT_GPIO_GROUP1, msm_gpio_irq_handler); - irq_set_chained_handler(INT_GPIO_GROUP2, msm_gpio_irq_handler); + mb(); irq_set_irq_wake(INT_GPIO_GROUP1, 1); irq_set_irq_wake(INT_GPIO_GROUP2, 2); return 0; } postcore_initcall(msm_init_gpio); + +int gpio_tlmm_config(unsigned config, unsigned disable) +{ + return msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &config, &disable); +} +EXPORT_SYMBOL(gpio_tlmm_config); + +int msm_gpios_request_enable(const struct msm_gpio *table, int size) +{ + int rc = msm_gpios_request(table, size); + if (rc) + return rc; + rc = msm_gpios_enable(table, size); + if (rc) + msm_gpios_free(table, size); + return rc; +} +EXPORT_SYMBOL(msm_gpios_request_enable); + +void msm_gpios_disable_free(const struct msm_gpio *table, int size) +{ + msm_gpios_disable(table, size); + msm_gpios_free(table, size); +} +EXPORT_SYMBOL(msm_gpios_disable_free); + +int msm_gpios_request(const struct msm_gpio *table, int size) +{ + int rc; + int i; + const struct msm_gpio *g; + for (i = 0; i < size; i++) { + g = table + i; + rc = gpio_request(GPIO_PIN(g->gpio_cfg), g->label); + if (rc) { + pr_err("gpio_request(%d) <%s> failed: %d\n", + GPIO_PIN(g->gpio_cfg), g->label ?: "?", rc); + goto err; + } + } + return 0; +err: + msm_gpios_free(table, i); + return rc; +} +EXPORT_SYMBOL(msm_gpios_request); + +void msm_gpios_free(const struct msm_gpio *table, int size) +{ + int i; + const struct msm_gpio *g; + for (i = size-1; i >= 0; i--) { + g = table + i; + gpio_free(GPIO_PIN(g->gpio_cfg)); + } +} +EXPORT_SYMBOL(msm_gpios_free); + +int msm_gpios_enable(const struct msm_gpio *table, int size) +{ + int rc; + int i; + const struct msm_gpio *g; + for (i = 0; i < size; i++) { + g = table + i; + rc = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_ENABLE); + if (rc) { + pr_err("gpio_tlmm_config(0x%08x, GPIO_CFG_ENABLE)" + " <%s> failed: %d\n", + g->gpio_cfg, g->label ?: "?", rc); + pr_err("pin %d func %d dir %d pull %d drvstr %d\n", + GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg), + GPIO_DIR(g->gpio_cfg), GPIO_PULL(g->gpio_cfg), + GPIO_DRVSTR(g->gpio_cfg)); + goto err; + } + } + return 0; +err: + msm_gpios_disable(table, i); + return rc; +} +EXPORT_SYMBOL(msm_gpios_enable); + +int msm_gpios_disable(const struct msm_gpio *table, int size) +{ + int rc = 0; + int i; + const struct msm_gpio *g; + for (i = size-1; i >= 0; i--) { + int tmp; + g = table + i; + tmp = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_DISABLE); + if (tmp) { + pr_err("gpio_tlmm_config(0x%08x, GPIO_CFG_DISABLE)" + " <%s> failed: %d\n", + g->gpio_cfg, g->label ?: "?", rc); + pr_err("pin %d func %d dir %d pull %d drvstr %d\n", + GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg), + GPIO_DIR(g->gpio_cfg), GPIO_PULL(g->gpio_cfg), + GPIO_DRVSTR(g->gpio_cfg)); + if (!rc) + rc = tmp; + } + } + + return rc; +} +EXPORT_SYMBOL(msm_gpios_disable); + +/* Locate the GPIO_OUT register for the given GPIO and return its address + * and the bit position of the gpio's bit within the register. + * + * This function is used by gpiomux-v1 in order to support output transitions. + */ +void msm_gpio_find_out(const unsigned gpio, void __iomem **out, + unsigned *offset) +{ + struct msm_gpio_chip *msm_chip = msm_gpio_chips; + + while (gpio >= msm_chip->chip.base + msm_chip->chip.ngpio) + ++msm_chip; + + *out = msm_chip->regs.out; + *offset = gpio - msm_chip->chip.base; +} diff --git a/arch/arm/mach-msm/gpio.h b/arch/arm/mach-msm/gpio.h new file mode 100644 index 00000000000..59ee8f83270 --- /dev/null +++ b/arch/arm/mach-msm/gpio.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_GPIO_H_ +#define _ARCH_ARM_MACH_MSM_GPIO_H_ + +void msm_gpio_enter_sleep(int from_idle); +void msm_gpio_exit_sleep(void); + +/* Locate the GPIO_OUT register for the given GPIO and return its address + * and the bit position of the gpio's bit within the register. + * + * This function is used by gpiomux-v1 in order to support output transitions. + */ +void msm_gpio_find_out(const unsigned gpio, void __iomem **out, + unsigned *offset); + +#endif diff --git a/arch/arm/mach-msm/gpio_hw.h b/arch/arm/mach-msm/gpio_hw.h index 6b5066038ba..f4ed0c0cef9 100644 --- a/arch/arm/mach-msm/gpio_hw.h +++ b/arch/arm/mach-msm/gpio_hw.h @@ -34,6 +34,8 @@ #if defined(CONFIG_ARCH_MSM7X30) #define MSM_GPIO1_REG(off) (MSM_GPIO1_BASE + (off)) #define MSM_GPIO2_REG(off) (MSM_GPIO2_BASE + 0x400 + (off)) +#elif defined(CONFIG_ARCH_FSM9XXX) +#define MSM_GPIO1_REG(off) (MSM_TLMM_BASE + (off)) #else #define MSM_GPIO1_REG(off) (MSM_GPIO1_BASE + 0x800 + (off)) #define MSM_GPIO2_REG(off) (MSM_GPIO2_BASE + 0xC00 + (off)) @@ -275,4 +277,42 @@ #endif +#if defined(CONFIG_ARCH_FSM9XXX) + +/* output value */ +#define MSM_GPIO_OUT_G(group) MSM_GPIO1_REG(0x00 + (group) * 4) +#define MSM_GPIO_OUT_N(gpio) MSM_GPIO_OUT_G((gpio) / 32) +#define MSM_GPIO_OUT_0 MSM_GPIO_OUT_G(0) /* gpio 31-0 */ +#define MSM_GPIO_OUT_1 MSM_GPIO_OUT_G(1) /* gpio 63-32 */ +#define MSM_GPIO_OUT_2 MSM_GPIO_OUT_G(2) /* gpio 95-64 */ +#define MSM_GPIO_OUT_3 MSM_GPIO_OUT_G(3) /* gpio 127-96 */ +#define MSM_GPIO_OUT_4 MSM_GPIO_OUT_G(4) /* gpio 159-128 */ +#define MSM_GPIO_OUT_5 MSM_GPIO_OUT_G(5) /* gpio 167-160 */ + +/* same pin map as above, output enable */ +#define MSM_GPIO_OE_G(group) MSM_GPIO1_REG(0x20 + (group) * 4) +#define MSM_GPIO_OE_N(gpio) MSM_GPIO_OE_G((gpio) / 32) +#define MSM_GPIO_OE_0 MSM_GPIO_OE_G(0) +#define MSM_GPIO_OE_1 MSM_GPIO_OE_G(1) +#define MSM_GPIO_OE_2 MSM_GPIO_OE_G(2) +#define MSM_GPIO_OE_3 MSM_GPIO_OE_G(3) +#define MSM_GPIO_OE_4 MSM_GPIO_OE_G(4) +#define MSM_GPIO_OE_5 MSM_GPIO_OE_G(5) + +/* same pin map as above, input read */ +#define MSM_GPIO_IN_G(group) MSM_GPIO1_REG(0x48 + (group) * 4) +#define MSM_GPIO_IN_N(gpio) MSM_GPIO_IN_G((gpio) / 32) +#define MSM_GPIO_IN_0 MSM_GPIO_IN_G(0) +#define MSM_GPIO_IN_1 MSM_GPIO_IN_G(1) +#define MSM_GPIO_IN_2 MSM_GPIO_IN_G(2) +#define MSM_GPIO_IN_3 MSM_GPIO_IN_G(3) +#define MSM_GPIO_IN_4 MSM_GPIO_IN_G(4) +#define MSM_GPIO_IN_5 MSM_GPIO_IN_G(5) + +/* configuration */ +#define MSM_GPIO_PAGE MSM_GPIO1_REG(0x40) +#define MSM_GPIO_CONFIG MSM_GPIO1_REG(0x44) + +#endif /* CONFIG_ARCH_FSM9XXX */ + #endif diff --git a/arch/arm/mach-msm/gpiomux-7x27.c b/arch/arm/mach-msm/gpiomux-7x27.c new file mode 100644 index 00000000000..d8579b907a1 --- /dev/null +++ b/arch/arm/mach-msm/gpiomux-7x27.c @@ -0,0 +1,20 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include "gpiomux.h" + +static int __init gpiomux_init(void) +{ + return msm_gpiomux_init(NR_GPIO_IRQS); +} +postcore_initcall(gpiomux_init); diff --git a/arch/arm/mach-msm/gpiomux-7x30.c b/arch/arm/mach-msm/gpiomux-7x30.c new file mode 100644 index 00000000000..d8579b907a1 --- /dev/null +++ b/arch/arm/mach-msm/gpiomux-7x30.c @@ -0,0 +1,20 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include "gpiomux.h" + +static int __init gpiomux_init(void) +{ + return msm_gpiomux_init(NR_GPIO_IRQS); +} +postcore_initcall(gpiomux_init); diff --git a/arch/arm/mach-msm/gpiomux-8x50.c b/arch/arm/mach-msm/gpiomux-8x50.c index f7a4ea593c9..d8579b907a1 100644 --- a/arch/arm/mach-msm/gpiomux-8x50.c +++ b/arch/arm/mach-msm/gpiomux-8x50.c @@ -8,44 +8,13 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ +#include +#include #include "gpiomux.h" -#if defined(CONFIG_MMC_MSM) || defined(CONFIG_MMC_MSM_MODULE) - #define SDCC_DAT_0_3_CMD_ACTV_CFG (GPIOMUX_VALID | GPIOMUX_PULL_UP\ - | GPIOMUX_FUNC_1 | GPIOMUX_DRV_8MA) - #define SDCC_CLK_ACTV_CFG (GPIOMUX_VALID | GPIOMUX_PULL_NONE\ - | GPIOMUX_FUNC_1 | GPIOMUX_DRV_8MA) -#else - #define SDCC_DAT_0_3_CMD_ACTV_CFG 0 - #define SDCC_CLK_ACTV_CFG 0 -#endif - -#define SDC1_SUSPEND_CONFIG (GPIOMUX_VALID | GPIOMUX_PULL_DOWN\ - | GPIOMUX_FUNC_GPIO | GPIOMUX_DRV_2MA) - -struct msm_gpiomux_config msm_gpiomux_configs[GPIOMUX_NGPIOS] = { - [86] = { /* UART3 RX */ - .suspended = GPIOMUX_DRV_2MA | GPIOMUX_PULL_DOWN | - GPIOMUX_FUNC_1 | GPIOMUX_VALID, - }, - [87] = { /* UART3 TX */ - .suspended = GPIOMUX_DRV_2MA | GPIOMUX_PULL_DOWN | - GPIOMUX_FUNC_1 | GPIOMUX_VALID, - }, - /* SDC1 data[3:0] & CMD */ - [51 ... 55] = { - .active = SDCC_DAT_0_3_CMD_ACTV_CFG, - .suspended = SDC1_SUSPEND_CONFIG - }, - /* SDC1 CLK */ - [56] = { - .active = SDCC_CLK_ACTV_CFG, - .suspended = SDC1_SUSPEND_CONFIG - }, -}; +static int __init gpiomux_init(void) +{ + return msm_gpiomux_init(NR_GPIO_IRQS); +} +postcore_initcall(gpiomux_init); diff --git a/arch/arm/mach-msm/gpiomux-8x60.c b/arch/arm/mach-msm/gpiomux-8x60.c index 7b380b31bd0..5cf07768744 100644 --- a/arch/arm/mach-msm/gpiomux-8x60.c +++ b/arch/arm/mach-msm/gpiomux-8x60.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and @@ -8,12 +8,1701 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ +#include +#include +#include #include "gpiomux.h" +#include "gpiomux-8x60.h" + +static struct gpiomux_setting console_uart = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +/* The SPI configurations apply to GSBI1 and GSBI10 */ +static struct gpiomux_setting spi_active = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting spi_suspended_config = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting spi_suspended_cs_config = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +/* This I2C active configuration applies to GSBI3 and GSBI4 */ +static struct gpiomux_setting i2c_active = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting i2c_active_gsbi7 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +/* This I2C suspended configuration applies to GSBI3, GSBI4 and GSBI7 */ +static struct gpiomux_setting i2c_suspended_config = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting gsbi8 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting ps_hold = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_12MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting msm_snddev_active_config = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting msm_snddev_suspend_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting ebi2_a_d = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting ebi2_oe = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting ebi2_we = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting ebi2_cs2 = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting ebi2_cs3 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) +static struct gpiomux_setting ebi2_cs4 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; +#endif + +static struct gpiomux_setting ebi2_adv = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) +static struct gpiomux_setting usb_isp1763_actv_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting usb_isp1763_susp_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; +#endif + +static struct gpiomux_setting sdcc1_dat_0_3_cmd_actv_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_10MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting sdcc1_dat_4_7_cmd_actv_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_10MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting sdcc1_clk_actv_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sdcc1_suspend_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting sdcc2_dat_0_3_cmd_actv_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_10MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting sdcc2_dat_4_7_cmd_actv_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_10MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting sdcc2_clk_actv_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sdcc2_suspend_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sdcc5_dat_0_3_cmd_actv_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_10MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting sdcc5_clk_actv_cfg = { + .func = GPIOMUX_FUNC_2, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting sdcc5_suspend_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting aux_pcm_active_config = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting aux_pcm_suspend_config = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting uart1dm_active = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting uart1dm_suspended = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting mi2s_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting mi2s_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting lcdc_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting lcdc_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting mdp_vsync_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting hdmi_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting mdm2ap_status_active_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting mdm2ap_status_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cam_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting mdm2ap_sync_active_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting mdm2ap_sync_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting tm_active = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting tm_suspended = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting tma_active = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_6MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting ts_suspended = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting mdp_vsync_active_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting hdmi_active_1_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting hdmi_active_2_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting hdmi_active_3_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting pmic_suspended_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cam_active_1_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cam_active_2_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting cam_active_3_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting cam_active_4_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting cam_active_5_cfg = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_4MA, + .pull = GPIOMUX_PULL_NONE, +}; + +#ifdef CONFIG_MSM_GSBI9_UART +static struct gpiomux_setting uart9dm_active = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA , + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting gsbi9 = { + .func = GPIOMUX_FUNC_1, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; +#endif + +static struct gpiomux_setting ap2mdm_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting mdm2ap_status_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting mdm2ap_vfr_active_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_UP, +}; + +static struct gpiomux_setting mdm2ap_vfr_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_DOWN, +}; + +static struct gpiomux_setting mdm2ap_errfatal_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_16MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting ap2mdm_kpdpwr_n_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_8MA, + .pull = GPIOMUX_PULL_NONE, +}; + + +static struct gpiomux_setting mdm2ap_vddmin_active_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct gpiomux_setting mdm2ap_vddmin_suspend_cfg = { + .func = GPIOMUX_FUNC_GPIO, + .drv = GPIOMUX_DRV_2MA, + .pull = GPIOMUX_PULL_NONE, +}; + +static struct msm_gpiomux_config msm8x60_gsbi_configs[] __initdata = { + { + .gpio = 33, + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 34, + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 35, + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_cs_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 36, + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 43, + .settings = { + [GPIOMUX_SUSPENDED] = &i2c_suspended_config, + [GPIOMUX_ACTIVE] = &i2c_active, + }, + }, + { + .gpio = 44, + .settings = { + [GPIOMUX_SUSPENDED] = &i2c_suspended_config, + [GPIOMUX_ACTIVE] = &i2c_active, + }, + }, + { + .gpio = 47, + .settings = { + [GPIOMUX_SUSPENDED] = &i2c_suspended_config, + [GPIOMUX_ACTIVE] = &i2c_active, + }, + }, + { + .gpio = 48, + .settings = { + [GPIOMUX_SUSPENDED] = &i2c_suspended_config, + [GPIOMUX_ACTIVE] = &i2c_active, + }, + }, + { + .gpio = 59, + .settings = { + [GPIOMUX_SUSPENDED] = &i2c_suspended_config, + [GPIOMUX_ACTIVE] = &i2c_active_gsbi7, + }, + }, + { + .gpio = 60, + .settings = { + [GPIOMUX_SUSPENDED] = &i2c_suspended_config, + [GPIOMUX_ACTIVE] = &i2c_active_gsbi7, + }, + }, + { + .gpio = 64, + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi8, + }, + }, + { + .gpio = 65, + .settings = { + [GPIOMUX_SUSPENDED] = &gsbi8, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_fluid_gsbi_configs[] __initdata = { + { + .gpio = 70, + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 72, + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_cs_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, + { + .gpio = 73, + .settings = { + [GPIOMUX_SUSPENDED] = &spi_suspended_config, + [GPIOMUX_ACTIVE] = &spi_active, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_ebi2_configs[] __initdata = { + { + .gpio = 40, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_cs2, + }, + }, + { + .gpio = 123, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 124, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 125, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 126, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 127, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 128, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 129, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 130, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + /* ISP VDD_3V3_EN */ + { + .gpio = 132, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_cs4, + }, + }, +#endif + { + .gpio = 133, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_cs3, + }, + }, + { + .gpio = 135, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 136, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 137, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 138, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 139, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 140, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 141, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 142, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 143, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 144, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 145, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 146, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 147, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 148, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 149, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 150, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_a_d, + }, + }, + { + .gpio = 151, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_oe, + }, + }, + { + .gpio = 153, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_adv, + }, + }, + { + .gpio = 157, + .settings = { + [GPIOMUX_SUSPENDED] = &ebi2_we, + }, + }, +}; + +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) +static struct msm_gpiomux_config msm8x60_isp_usb_configs[] __initdata = { + { + .gpio = 117, + .settings = { + [GPIOMUX_ACTIVE] = &usb_isp1763_actv_cfg, + [GPIOMUX_SUSPENDED] = &usb_isp1763_susp_cfg, + }, + }, + { + .gpio = 152, + .settings = { + [GPIOMUX_ACTIVE] = &usb_isp1763_actv_cfg, + [GPIOMUX_SUSPENDED] = &usb_isp1763_susp_cfg, + }, + }, + +}; +#endif + +static struct msm_gpiomux_config msm8x60_uart_configs[] __initdata = { + { /* UARTDM_TX */ + .gpio = 53, + .settings = { + [GPIOMUX_ACTIVE] = &uart1dm_active, + [GPIOMUX_SUSPENDED] = &uart1dm_suspended, + }, + }, + { /* UARTDM_RX */ + .gpio = 54, + .settings = { + [GPIOMUX_ACTIVE] = &uart1dm_active, + [GPIOMUX_SUSPENDED] = &uart1dm_suspended, + }, + }, + { /* UARTDM_CTS */ + .gpio = 55, + .settings = { + [GPIOMUX_ACTIVE] = &uart1dm_active, + [GPIOMUX_SUSPENDED] = &uart1dm_suspended, + }, + }, + { /* UARTDM_RFR */ + .gpio = 56, + .settings = { + [GPIOMUX_ACTIVE] = &uart1dm_active, + [GPIOMUX_SUSPENDED] = &uart1dm_suspended, + }, + }, + { + .gpio = 115, + .settings = { + [GPIOMUX_SUSPENDED] = &console_uart, + }, + }, + { + .gpio = 116, + .settings = { + [GPIOMUX_SUSPENDED] = &console_uart, + }, + }, +#if !defined(CONFIG_USB_PEHCI_HCD) && !defined(CONFIG_USB_PEHCI_HCD_MODULE) + /* USB ISP1763 may also use 117 GPIO */ + { + .gpio = 117, + .settings = { + [GPIOMUX_SUSPENDED] = &console_uart, + }, + }, +#endif + { + .gpio = 118, + .settings = { + [GPIOMUX_SUSPENDED] = &console_uart, + }, + }, +}; + +#ifdef CONFIG_MSM_GSBI9_UART +static struct msm_gpiomux_config msm8x60_charm_uart_configs[] __initdata = { + { /* UART9DM RX */ + .gpio = 66, + .settings = { + [GPIOMUX_ACTIVE] = &uart9dm_active, + [GPIOMUX_SUSPENDED] = &gsbi9, + }, + }, + { /* UART9DM TX */ + .gpio = 67, + .settings = { + [GPIOMUX_ACTIVE] = &uart9dm_active, + [GPIOMUX_SUSPENDED] = &gsbi9, + }, + }, +}; +#endif + +static struct msm_gpiomux_config msm8x60_ts_configs[] __initdata = { + { + /* TS_ATTN */ + .gpio = 58, + .settings = { + [GPIOMUX_SUSPENDED] = &ts_suspended, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_tmg200_configs[] __initdata = { + { + .gpio = 61, + .settings = { + [GPIOMUX_ACTIVE] = &tm_active, + [GPIOMUX_SUSPENDED] = &tm_suspended, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_tma300_configs[] __initdata = { + { + .gpio = 61, + .settings = { + [GPIOMUX_ACTIVE] = &tma_active, + [GPIOMUX_SUSPENDED] = &tm_suspended, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_aux_pcm_configs[] __initdata = { + { + .gpio = 111, + .settings = { + [GPIOMUX_ACTIVE] = &aux_pcm_active_config, + [GPIOMUX_SUSPENDED] = &aux_pcm_suspend_config, + }, + }, + { + .gpio = 112, + .settings = { + [GPIOMUX_ACTIVE] = &aux_pcm_active_config, + [GPIOMUX_SUSPENDED] = &aux_pcm_suspend_config, + }, + }, + { + .gpio = 113, + .settings = { + [GPIOMUX_ACTIVE] = &aux_pcm_active_config, + [GPIOMUX_SUSPENDED] = &aux_pcm_suspend_config, + }, + }, + { + .gpio = 114, + .settings = { + [GPIOMUX_ACTIVE] = &aux_pcm_active_config, + [GPIOMUX_SUSPENDED] = &aux_pcm_suspend_config, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_sdc_configs[] __initdata = { + /* SDCC1 data[0] */ + { + .gpio = 159, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, + /* SDCC1 data[1] */ + { + .gpio = 160, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, + /* SDCC1 data[2] */ + { + .gpio = 161, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, + /* SDCC1 data[3] */ + { + .gpio = 162, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, + /* SDCC1 data[4] */ + { + .gpio = 163, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_dat_4_7_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, + /* SDCC1 data[5] */ + { + .gpio = 164, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_dat_4_7_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, + /* SDCC1 data[6] */ + { + .gpio = 165, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_dat_4_7_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, + /* SDCC1 data[7] */ + { + .gpio = 166, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_dat_4_7_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, + /* SDCC1 CLK */ + { + .gpio = 167, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_clk_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, + /* SDCC1 CMD */ + { + .gpio = 168, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc1_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc1_suspend_config, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_charm_sdc_configs[] __initdata = { + /* SDCC5 cmd */ + { + .gpio = 95, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc5_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc5_suspend_config, + }, + }, + /* SDCC5 data[3]*/ + { + .gpio = 96, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc5_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc5_suspend_config, + }, + }, + /* SDCC5 clk */ + { + .gpio = 97, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc5_clk_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc5_suspend_config, + }, + }, + /* SDCC5 data[2]*/ + { + .gpio = 98, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc5_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc5_suspend_config, + }, + }, + /* SDCC5 data[1]*/ + { + .gpio = 99, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc5_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc5_suspend_config, + }, + }, + /* SDCC5 data[0]*/ + { + .gpio = 100, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc5_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc5_suspend_config, + }, + }, + /* MDM2AP_SYNC */ + { + .gpio = 129, + .settings = { + [GPIOMUX_ACTIVE] = &mdm2ap_sync_active_cfg, + [GPIOMUX_SUSPENDED] = &mdm2ap_sync_suspend_cfg, + }, + }, + + /* MDM2AP_VDDMIN */ + { + .gpio = 140, + .settings = { + [GPIOMUX_ACTIVE] = &mdm2ap_vddmin_active_cfg, + [GPIOMUX_SUSPENDED] = &mdm2ap_vddmin_suspend_cfg, + }, + }, + /* SDCC2 data[0] */ + { + .gpio = 143, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, + /* SDCC2 data[1] */ + { + .gpio = 144, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, + /* SDCC2 data[2] */ + { + .gpio = 145, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, + /* SDCC2 data[3] */ + { + .gpio = 146, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, + /* SDCC2 data[4] */ + { + .gpio = 147, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_dat_4_7_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, + /* SDCC2 data[5] */ + { + .gpio = 148, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_dat_4_7_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, + /* SDCC2 data[6] */ + { + .gpio = 149, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_dat_4_7_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, + /* SDCC2 data[7] */ + { + .gpio = 150, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_dat_4_7_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, + /* SDCC2 CMD */ + { + .gpio = 151, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_dat_0_3_cmd_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, + + /* SDCC2 CLK */ + { + .gpio = 152, + .settings = { + [GPIOMUX_ACTIVE] = &sdcc2_clk_actv_cfg, + [GPIOMUX_SUSPENDED] = &sdcc2_suspend_config, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_snd_configs[] __initdata = { + { + .gpio = 108, + .settings = { + [GPIOMUX_ACTIVE] = &msm_snddev_active_config, + [GPIOMUX_SUSPENDED] = &msm_snddev_suspend_config, + }, + }, + { + .gpio = 109, + .settings = { + [GPIOMUX_ACTIVE] = &msm_snddev_active_config, + [GPIOMUX_SUSPENDED] = &msm_snddev_suspend_config, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_mi2s_configs[] __initdata = { + /* MI2S WS */ + { + .gpio = 101, + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_active_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_suspend_cfg, + }, + }, + /* MI2S SCLK */ + { + .gpio = 102, + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_active_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_suspend_cfg, + }, + }, + /* MI2S MCLK */ + { + .gpio = 103, + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_active_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_suspend_cfg, + }, + }, + /* MI2S SD3 */ + { + .gpio = 107, + .settings = { + [GPIOMUX_ACTIVE] = &mi2s_active_cfg, + [GPIOMUX_SUSPENDED] = &mi2s_suspend_cfg, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_lcdc_configs[] __initdata = { + /* lcdc_pclk */ + { + .gpio = 0, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_hsync */ + { + .gpio = 1, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_vsync */ + { + .gpio = 2, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_den */ + { + .gpio = 3, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_red7 */ + { + .gpio = 4, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_red6 */ + { + .gpio = 5, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_red5 */ + { + .gpio = 6, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_red4 */ + { + .gpio = 7, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_red3 */ + { + .gpio = 8, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_red2 */ + { + .gpio = 9, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_red1 */ + { + .gpio = 10, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_red0 */ + { + .gpio = 11, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_grn7 */ + { + .gpio = 12, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_grn6 */ + { + .gpio = 13, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_grn5 */ + { + .gpio = 14, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_grn4 */ + { + .gpio = 15, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_grn3 */ + { + .gpio = 16, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_grn2 */ + { + .gpio = 17, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_grn1 */ + { + .gpio = 18, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_grn0 */ + { + .gpio = 19, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_blu7 */ + { + .gpio = 20, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_blu6 */ + { + .gpio = 21, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_blu5 */ + { + .gpio = 22, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_blu4 */ + { + .gpio = 23, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_blu3 */ + { + .gpio = 24, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_blu2 */ + { + .gpio = 25, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_blu1 */ + { + .gpio = 26, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, + /* lcdc_blu0 */ + { + .gpio = 27, + .settings = { + [GPIOMUX_ACTIVE] = &lcdc_active_cfg, + [GPIOMUX_SUSPENDED] = &lcdc_suspend_cfg, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_mdp_vsync_configs[] __initdata = { + { + .gpio = 28, + .settings = { + [GPIOMUX_ACTIVE] = &mdp_vsync_active_cfg, + [GPIOMUX_SUSPENDED] = &mdp_vsync_suspend_cfg, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_hdmi_configs[] __initdata = { + { + .gpio = 169, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_1_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 170, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_2_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 171, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_2_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, + { + .gpio = 172, + .settings = { + [GPIOMUX_ACTIVE] = &hdmi_active_3_cfg, + [GPIOMUX_SUSPENDED] = &hdmi_suspend_cfg, + }, + }, +}; + +/* Because PMIC drivers do not use gpio-management routines and PMIC + * gpios must never sleep, a "good enough" config is obtained by placing + * the active config in the 'suspended' slot and leaving the active + * config invalid: the suspended config will be installed at boot + * and never replaced. + */ + +static struct msm_gpiomux_config msm8x60_pmic_configs[] __initdata = { + { + .gpio = 88, + .settings = { + [GPIOMUX_SUSPENDED] = &pmic_suspended_cfg, + }, + }, + { + .gpio = 91, + .settings = { + [GPIOMUX_SUSPENDED] = &pmic_suspended_cfg, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_common_configs[] __initdata = { + /* MDM2AP_STATUS */ + { + .gpio = 77, + .settings = { + [GPIOMUX_ACTIVE] = &mdm2ap_status_active_cfg, + [GPIOMUX_SUSPENDED] = &mdm2ap_status_suspend_cfg, + }, + }, + /* PS_HOLD */ + { + .gpio = 92, + .settings = { + [GPIOMUX_SUSPENDED] = &ps_hold, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_cam_configs[] __initdata = { + { + .gpio = 29, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_2_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 30, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_1_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 31, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_2_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 32, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_5_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 42, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_2_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 47, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_3_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 48, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_3_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 105, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_4_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, + { + .gpio = 106, + .settings = { + [GPIOMUX_ACTIVE] = &cam_active_4_cfg, + [GPIOMUX_SUSPENDED] = &cam_suspend_cfg, + }, + }, +}; + +static struct msm_gpiomux_config msm8x60_charm_configs[] __initdata = { + /* AP2MDM_WAKEUP */ + { + .gpio = 135, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* MDM2AP_VFR */ + { + .gpio = 94, + .settings = { + [GPIOMUX_ACTIVE] = &mdm2ap_vfr_active_cfg, + [GPIOMUX_SUSPENDED] = &mdm2ap_vfr_suspend_cfg, + } + }, + /* AP2MDM_STATUS */ + { + .gpio = 136, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* MDM2AP_STATUS */ + { + .gpio = 134, + .settings = { + [GPIOMUX_SUSPENDED] = &mdm2ap_status_cfg, + } + }, + /* MDM2AP_WAKEUP */ + { + .gpio = 40, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* MDM2AP_ERRFATAL */ + { + .gpio = 133, + .settings = { + [GPIOMUX_SUSPENDED] = &mdm2ap_errfatal_cfg, + } + }, + /* AP2MDM_ERRFATAL */ + { + .gpio = 93, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_cfg, + } + }, + /* AP2MDM_KPDPWR_N */ + { + .gpio = 132, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_kpdpwr_n_cfg, + } + }, + /* AP2MDM_PMIC_RESET_N */ + { + .gpio = 131, + .settings = { + [GPIOMUX_SUSPENDED] = &ap2mdm_kpdpwr_n_cfg, + } + } +}; + +struct msm_gpiomux_configs +msm8x60_surf_ffa_gpiomux_cfgs[] __initdata = { + {msm8x60_gsbi_configs, ARRAY_SIZE(msm8x60_gsbi_configs)}, + {msm8x60_ebi2_configs, ARRAY_SIZE(msm8x60_ebi2_configs)}, + {msm8x60_uart_configs, ARRAY_SIZE(msm8x60_uart_configs)}, +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + {msm8x60_isp_usb_configs, ARRAY_SIZE(msm8x60_isp_usb_configs)}, +#endif + {msm8x60_ts_configs, ARRAY_SIZE(msm8x60_ts_configs)}, + {msm8x60_aux_pcm_configs, ARRAY_SIZE(msm8x60_aux_pcm_configs)}, + {msm8x60_sdc_configs, ARRAY_SIZE(msm8x60_sdc_configs)}, + {msm8x60_snd_configs, ARRAY_SIZE(msm8x60_snd_configs)}, + {msm8x60_mi2s_configs, ARRAY_SIZE(msm8x60_mi2s_configs)}, + {msm8x60_lcdc_configs, ARRAY_SIZE(msm8x60_lcdc_configs)}, + {msm8x60_mdp_vsync_configs, ARRAY_SIZE(msm8x60_mdp_vsync_configs)}, + {msm8x60_hdmi_configs, ARRAY_SIZE(msm8x60_hdmi_configs)}, + {msm8x60_pmic_configs, ARRAY_SIZE(msm8x60_pmic_configs)}, + {msm8x60_common_configs, ARRAY_SIZE(msm8x60_common_configs)}, + {msm8x60_cam_configs, ARRAY_SIZE(msm8x60_cam_configs)}, + {msm8x60_tmg200_configs, ARRAY_SIZE(msm8x60_tmg200_configs)}, + {NULL, 0}, +}; + +struct msm_gpiomux_configs +msm8x60_fluid_gpiomux_cfgs[] __initdata = { + {msm8x60_gsbi_configs, ARRAY_SIZE(msm8x60_gsbi_configs)}, + {msm8x60_fluid_gsbi_configs, ARRAY_SIZE(msm8x60_fluid_gsbi_configs)}, + {msm8x60_ebi2_configs, ARRAY_SIZE(msm8x60_ebi2_configs)}, + {msm8x60_uart_configs, ARRAY_SIZE(msm8x60_uart_configs)}, + {msm8x60_ts_configs, ARRAY_SIZE(msm8x60_ts_configs)}, + {msm8x60_aux_pcm_configs, ARRAY_SIZE(msm8x60_aux_pcm_configs)}, + {msm8x60_sdc_configs, ARRAY_SIZE(msm8x60_sdc_configs)}, + {msm8x60_snd_configs, ARRAY_SIZE(msm8x60_snd_configs)}, + {msm8x60_mi2s_configs, ARRAY_SIZE(msm8x60_mi2s_configs)}, + {msm8x60_lcdc_configs, ARRAY_SIZE(msm8x60_lcdc_configs)}, + {msm8x60_mdp_vsync_configs, ARRAY_SIZE(msm8x60_mdp_vsync_configs)}, + {msm8x60_hdmi_configs, ARRAY_SIZE(msm8x60_hdmi_configs)}, + {msm8x60_pmic_configs, ARRAY_SIZE(msm8x60_pmic_configs)}, + {msm8x60_common_configs, ARRAY_SIZE(msm8x60_common_configs)}, + {msm8x60_cam_configs, ARRAY_SIZE(msm8x60_cam_configs)}, + {msm8x60_tma300_configs, ARRAY_SIZE(msm8x60_tma300_configs)}, + {NULL, 0}, +}; + +struct msm_gpiomux_configs +msm8x60_charm_gpiomux_cfgs[] __initdata = { + {msm8x60_gsbi_configs, ARRAY_SIZE(msm8x60_gsbi_configs)}, + {msm8x60_uart_configs, ARRAY_SIZE(msm8x60_uart_configs)}, +#ifdef CONFIG_MSM_GSBI9_UART + {msm8x60_charm_uart_configs, ARRAY_SIZE(msm8x60_charm_uart_configs)}, +#endif + {msm8x60_ts_configs, ARRAY_SIZE(msm8x60_ts_configs)}, + {msm8x60_aux_pcm_configs, ARRAY_SIZE(msm8x60_aux_pcm_configs)}, + {msm8x60_sdc_configs, ARRAY_SIZE(msm8x60_sdc_configs)}, + {msm8x60_snd_configs, ARRAY_SIZE(msm8x60_snd_configs)}, + {msm8x60_mi2s_configs, ARRAY_SIZE(msm8x60_mi2s_configs)}, + {msm8x60_lcdc_configs, ARRAY_SIZE(msm8x60_lcdc_configs)}, + {msm8x60_mdp_vsync_configs, ARRAY_SIZE(msm8x60_mdp_vsync_configs)}, + {msm8x60_hdmi_configs, ARRAY_SIZE(msm8x60_hdmi_configs)}, + {msm8x60_pmic_configs, ARRAY_SIZE(msm8x60_pmic_configs)}, + {msm8x60_common_configs, ARRAY_SIZE(msm8x60_common_configs)}, + {msm8x60_cam_configs, ARRAY_SIZE(msm8x60_cam_configs)}, + {msm8x60_tmg200_configs, ARRAY_SIZE(msm8x60_tmg200_configs)}, + {msm8x60_charm_sdc_configs, ARRAY_SIZE(msm8x60_charm_sdc_configs)}, + {msm8x60_charm_configs, ARRAY_SIZE(msm8x60_charm_configs)}, + {NULL, 0}, +}; + +void __init msm8x60_init_gpiomux(struct msm_gpiomux_configs *cfgs) +{ + int rc; + + rc = msm_gpiomux_init(NR_GPIO_IRQS); + if (rc) { + pr_err("%s failure: %d\n", __func__, rc); + return; + } -struct msm_gpiomux_config msm_gpiomux_configs[GPIOMUX_NGPIOS] = {}; + while (cfgs->cfg) { + msm_gpiomux_install(cfgs->cfg, cfgs->ncfg); + ++cfgs; + } +} diff --git a/arch/arm/mach-msm/gpiomux-8x60.h b/arch/arm/mach-msm/gpiomux-8x60.h new file mode 100644 index 00000000000..1839a829945 --- /dev/null +++ b/arch/arm/mach-msm/gpiomux-8x60.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __ARCH_ARM_MACH_MSM_GPIOMUX_8X60_H +#define __ARCH_ARM_MACH_MSM_GPIOMUX_8X60_H + +void __init msm8x60_init_gpiomux(struct msm_gpiomux_configs *cfgs); + +extern struct msm_gpiomux_configs msm8x60_surf_ffa_gpiomux_cfgs[] __initdata; +extern struct msm_gpiomux_configs msm8x60_fluid_gpiomux_cfgs[] __initdata; +extern struct msm_gpiomux_configs msm8x60_charm_gpiomux_cfgs[] __initdata; + +#endif diff --git a/arch/arm/mach-msm/gpiomux-v1.c b/arch/arm/mach-msm/gpiomux-v1.c index 27de2abd714..b2ea7f069ab 100644 --- a/arch/arm/mach-msm/gpiomux-v1.c +++ b/arch/arm/mach-msm/gpiomux-v1.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2011 Code Aurora Forum. 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 and @@ -8,23 +8,37 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ +#include #include +#include #include "gpiomux.h" #include "proc_comm.h" +#include "gpio.h" -void __msm_gpiomux_write(unsigned gpio, gpiomux_config_t val) +void __msm_gpiomux_write(unsigned gpio, struct gpiomux_setting val) { - unsigned tlmm_config = (val & ~GPIOMUX_CTL_MASK) | - ((gpio & 0x3ff) << 4); + unsigned tlmm_config; unsigned tlmm_disable = 0; + void __iomem *out_reg; + unsigned offset; + uint32_t bits; int rc; + tlmm_config = (val.drv << 17) | + (val.pull << 15) | + ((gpio & 0x3ff) << 4) | + val.func; + if (val.func == GPIOMUX_FUNC_GPIO) { + tlmm_config |= (val.dir > GPIOMUX_IN ? BIT(14) : 0); + msm_gpio_find_out(gpio, &out_reg, &offset); + bits = __raw_readl(out_reg); + if (val.dir == GPIOMUX_OUT_HIGH) + __raw_writel(bits | BIT(offset), out_reg); + else + __raw_writel(bits & ~BIT(offset), out_reg); + } + mb(); rc = msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &tlmm_config, &tlmm_disable); if (rc) diff --git a/arch/arm/mach-msm/gpiomux-v1.h b/arch/arm/mach-msm/gpiomux-v1.h index 71d86feba45..7cf4582311d 100644 --- a/arch/arm/mach-msm/gpiomux-v1.h +++ b/arch/arm/mach-msm/gpiomux-v1.h @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #ifndef __ARCH_ARM_MACH_MSM_GPIOMUX_V1_H #define __ARCH_ARM_MACH_MSM_GPIOMUX_V1_H diff --git a/arch/arm/mach-msm/gpiomux-v2.c b/arch/arm/mach-msm/gpiomux-v2.c index 273396d2b12..3029bb4847b 100644 --- a/arch/arm/mach-msm/gpiomux-v2.c +++ b/arch/arm/mach-msm/gpiomux-v2.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and @@ -8,18 +8,25 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ +#include #include #include #include "gpiomux.h" -void __msm_gpiomux_write(unsigned gpio, gpiomux_config_t val) +#define GPIO_CFG(n) (MSM_TLMM_BASE + 0x1000 + (0x10 * n)) +#define GPIO_IN_OUT(n) (MSM_TLMM_BASE + 0x1004 + (0x10 * n)) + +void __msm_gpiomux_write(unsigned gpio, struct gpiomux_setting val) { - writel(val & ~GPIOMUX_CTL_MASK, - MSM_TLMM_BASE + 0x1000 + (0x10 * gpio)); + uint32_t bits; + + bits = (val.drv << 6) | (val.func << 2) | val.pull; + if (val.func == GPIOMUX_FUNC_GPIO) { + bits |= val.dir > GPIOMUX_IN ? BIT(9) : 0; + __raw_writel(val.dir == GPIOMUX_OUT_HIGH ? BIT(1) : 0, + GPIO_IN_OUT(gpio)); + } + __raw_writel(bits, GPIO_CFG(gpio)); + mb(); } diff --git a/arch/arm/mach-msm/gpiomux-v2.h b/arch/arm/mach-msm/gpiomux-v2.h index 3bf10e7f038..b200501788b 100644 --- a/arch/arm/mach-msm/gpiomux-v2.h +++ b/arch/arm/mach-msm/gpiomux-v2.h @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #ifndef __ARCH_ARM_MACH_MSM_GPIOMUX_V2_H #define __ARCH_ARM_MACH_MSM_GPIOMUX_V2_H diff --git a/arch/arm/mach-msm/gpiomux.c b/arch/arm/mach-msm/gpiomux.c index 53af21abd15..68b97ce8808 100644 --- a/arch/arm/mach-msm/gpiomux.c +++ b/arch/arm/mach-msm/gpiomux.c @@ -8,57 +8,76 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include +#include #include #include "gpiomux.h" +struct msm_gpiomux_rec { + struct gpiomux_setting *sets[GPIOMUX_NSETTINGS]; + int ref; +}; static DEFINE_SPINLOCK(gpiomux_lock); +static struct msm_gpiomux_rec *msm_gpiomux_recs; +static struct gpiomux_setting *msm_gpiomux_sets; +static unsigned msm_gpiomux_ngpio; -int msm_gpiomux_write(unsigned gpio, - gpiomux_config_t active, - gpiomux_config_t suspended) +int msm_gpiomux_write(unsigned gpio, enum msm_gpiomux_setting which, + struct gpiomux_setting *setting, struct gpiomux_setting *old_setting) { - struct msm_gpiomux_config *cfg = msm_gpiomux_configs + gpio; + struct msm_gpiomux_rec *rec = msm_gpiomux_recs + gpio; + unsigned set_slot = gpio * GPIOMUX_NSETTINGS + which; unsigned long irq_flags; - gpiomux_config_t setting; + struct gpiomux_setting *new_set; + int status = 0; + + if (!msm_gpiomux_recs) + return -EFAULT; - if (gpio >= GPIOMUX_NGPIOS) + if (gpio >= msm_gpiomux_ngpio) return -EINVAL; spin_lock_irqsave(&gpiomux_lock, irq_flags); - if (active & GPIOMUX_VALID) - cfg->active = active; + if (old_setting) { + if (rec->sets[which] == NULL) + status = 1; + else + *old_setting = *(rec->sets[which]); + } - if (suspended & GPIOMUX_VALID) - cfg->suspended = suspended; + if (setting) { + msm_gpiomux_sets[set_slot] = *setting; + rec->sets[which] = &msm_gpiomux_sets[set_slot]; + } else { + rec->sets[which] = NULL; + } - setting = cfg->ref ? active : suspended; - if (setting & GPIOMUX_VALID) - __msm_gpiomux_write(gpio, setting); + new_set = rec->ref ? rec->sets[GPIOMUX_ACTIVE] : + rec->sets[GPIOMUX_SUSPENDED]; + if (new_set) + __msm_gpiomux_write(gpio, *new_set); spin_unlock_irqrestore(&gpiomux_lock, irq_flags); - return 0; + return status; } EXPORT_SYMBOL(msm_gpiomux_write); int msm_gpiomux_get(unsigned gpio) { - struct msm_gpiomux_config *cfg = msm_gpiomux_configs + gpio; + struct msm_gpiomux_rec *rec = msm_gpiomux_recs + gpio; unsigned long irq_flags; - if (gpio >= GPIOMUX_NGPIOS) + if (!msm_gpiomux_recs) + return -EFAULT; + + if (gpio >= msm_gpiomux_ngpio) return -EINVAL; spin_lock_irqsave(&gpiomux_lock, irq_flags); - if (cfg->ref++ == 0 && cfg->active & GPIOMUX_VALID) - __msm_gpiomux_write(gpio, cfg->active); + if (rec->ref++ == 0 && rec->sets[GPIOMUX_ACTIVE]) + __msm_gpiomux_write(gpio, *rec->sets[GPIOMUX_ACTIVE]); spin_unlock_irqrestore(&gpiomux_lock, irq_flags); return 0; } @@ -66,31 +85,66 @@ EXPORT_SYMBOL(msm_gpiomux_get); int msm_gpiomux_put(unsigned gpio) { - struct msm_gpiomux_config *cfg = msm_gpiomux_configs + gpio; + struct msm_gpiomux_rec *rec = msm_gpiomux_recs + gpio; unsigned long irq_flags; - if (gpio >= GPIOMUX_NGPIOS) + if (!msm_gpiomux_recs) + return -EFAULT; + + if (gpio >= msm_gpiomux_ngpio) return -EINVAL; spin_lock_irqsave(&gpiomux_lock, irq_flags); - BUG_ON(cfg->ref == 0); - if (--cfg->ref == 0 && cfg->suspended & GPIOMUX_VALID) - __msm_gpiomux_write(gpio, cfg->suspended); + BUG_ON(rec->ref == 0); + if (--rec->ref == 0 && rec->sets[GPIOMUX_SUSPENDED]) + __msm_gpiomux_write(gpio, *rec->sets[GPIOMUX_SUSPENDED]); spin_unlock_irqrestore(&gpiomux_lock, irq_flags); return 0; } EXPORT_SYMBOL(msm_gpiomux_put); -static int __init gpiomux_init(void) +int msm_gpiomux_init(size_t ngpio) { - unsigned n; + if (!ngpio) + return -EINVAL; - for (n = 0; n < GPIOMUX_NGPIOS; ++n) { - msm_gpiomux_configs[n].ref = 0; - if (!(msm_gpiomux_configs[n].suspended & GPIOMUX_VALID)) - continue; - __msm_gpiomux_write(n, msm_gpiomux_configs[n].suspended); + if (msm_gpiomux_recs) + return -EPERM; + + msm_gpiomux_recs = kzalloc(sizeof(struct msm_gpiomux_rec) * ngpio, + GFP_KERNEL); + if (!msm_gpiomux_recs) + return -ENOMEM; + + /* There is no need to zero this memory, as clients will be blindly + * installing settings on top of it. + */ + msm_gpiomux_sets = kmalloc(sizeof(struct gpiomux_setting) * ngpio * + GPIOMUX_NSETTINGS, GFP_KERNEL); + if (!msm_gpiomux_sets) { + kfree(msm_gpiomux_recs); + msm_gpiomux_recs = NULL; + return -ENOMEM; } + + msm_gpiomux_ngpio = ngpio; + return 0; } -postcore_initcall(gpiomux_init); +EXPORT_SYMBOL(msm_gpiomux_init); + +void msm_gpiomux_install(struct msm_gpiomux_config *configs, unsigned nconfigs) +{ + unsigned c, s; + int rc; + + for (c = 0; c < nconfigs; ++c) { + for (s = 0; s < GPIOMUX_NSETTINGS; ++s) { + rc = msm_gpiomux_write(configs[c].gpio, s, + configs[c].settings[s], NULL); + if (rc) + pr_err("%s: write failure: %d\n", __func__, rc); + } + } +} +EXPORT_SYMBOL(msm_gpiomux_install); diff --git a/arch/arm/mach-msm/gpiomux.h b/arch/arm/mach-msm/gpiomux.h index b178d9cb742..f75b0e0c847 100644 --- a/arch/arm/mach-msm/gpiomux.h +++ b/arch/arm/mach-msm/gpiomux.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #ifndef __ARCH_ARM_MACH_MSM_GPIOMUX_H #define __ARCH_ARM_MACH_MSM_GPIOMUX_H @@ -20,56 +15,112 @@ #include #include -#if defined(CONFIG_MSM_V2_TLMM) -#include "gpiomux-v2.h" -#else -#include "gpiomux-v1.h" -#endif +enum msm_gpiomux_setting { + GPIOMUX_ACTIVE = 0, + GPIOMUX_SUSPENDED, + GPIOMUX_NSETTINGS +}; + +enum gpiomux_drv { + GPIOMUX_DRV_2MA = 0, + GPIOMUX_DRV_4MA, + GPIOMUX_DRV_6MA, + GPIOMUX_DRV_8MA, + GPIOMUX_DRV_10MA, + GPIOMUX_DRV_12MA, + GPIOMUX_DRV_14MA, + GPIOMUX_DRV_16MA, +}; + +enum gpiomux_func { + GPIOMUX_FUNC_GPIO = 0, + GPIOMUX_FUNC_1, + GPIOMUX_FUNC_2, + GPIOMUX_FUNC_3, + GPIOMUX_FUNC_4, + GPIOMUX_FUNC_5, + GPIOMUX_FUNC_6, + GPIOMUX_FUNC_7, + GPIOMUX_FUNC_8, + GPIOMUX_FUNC_9, + GPIOMUX_FUNC_A, + GPIOMUX_FUNC_B, + GPIOMUX_FUNC_C, + GPIOMUX_FUNC_D, + GPIOMUX_FUNC_E, + GPIOMUX_FUNC_F, +}; + +enum gpiomux_pull { + GPIOMUX_PULL_NONE = 0, + GPIOMUX_PULL_DOWN, + GPIOMUX_PULL_KEEPER, + GPIOMUX_PULL_UP, +}; + +/* Direction settings are only meaningful when GPIOMUX_FUNC_GPIO is selected. + * This element is ignored for all other FUNC selections, as the output- + * enable pin is not under software control in those cases. See the SWI + * for your target for more details. + */ +enum gpiomux_dir { + GPIOMUX_IN = 0, + GPIOMUX_OUT_HIGH, + GPIOMUX_OUT_LOW, +}; + +struct gpiomux_setting { + enum gpiomux_func func; + enum gpiomux_drv drv; + enum gpiomux_pull pull; + enum gpiomux_dir dir; +}; /** * struct msm_gpiomux_config: gpiomux settings for one gpio line. * - * A complete gpiomux config is the bitwise-or of a drive-strength, - * function, and pull. For functions other than GPIO, the OE - * is hard-wired according to the function. For GPIO mode, - * OE is controlled by gpiolib. - * - * Available settings differ by target; see the gpiomux header - * specific to your target arch for available configurations. + * A complete gpiomux config is the combination of a drive-strength, + * function, pull, and (sometimes) direction. For functions other than GPIO, + * the input/output setting is hard-wired according to the function. * - * @active: The configuration to be installed when the line is - * active, or its reference count is > 0. - * @suspended: The configuration to be installed when the line - * is suspended, or its reference count is 0. - * @ref: The reference count of the line. For internal use of - * the gpiomux framework only. + * @gpio: The index number of the gpio being described. + * @settings: The settings to be installed, specifically: + * GPIOMUX_ACTIVE: The setting to be installed when the + * line is active, or its reference count is > 0. + * GPIOMUX_SUSPENDED: The setting to be installed when + * the line is suspended, or its reference count is 0. */ struct msm_gpiomux_config { - gpiomux_config_t active; - gpiomux_config_t suspended; - unsigned ref; + unsigned gpio; + struct gpiomux_setting *settings[GPIOMUX_NSETTINGS]; }; /** - * @GPIOMUX_VALID: If set, the config field contains 'good data'. - * The absence of this bit will prevent the gpiomux - * system from applying the configuration under all - * circumstances. + * struct msm_gpiomux_configs: a collection of gpiomux configs. + * + * It is so common to manage blocks of gpiomux configs that the data structure + * for doing so has been standardized here as a convenience. + * + * @cfg: A pointer to the first config in an array of configs. + * @ncfg: The number of configs in the array. */ -enum { - GPIOMUX_VALID = BIT(sizeof(gpiomux_config_t) * BITS_PER_BYTE - 1), - GPIOMUX_CTL_MASK = GPIOMUX_VALID, +struct msm_gpiomux_configs { + struct msm_gpiomux_config *cfg; + size_t ncfg; }; #ifdef CONFIG_MSM_GPIOMUX -/* Each architecture must provide its own instance of this table. - * To avoid having gpiomux manage any given gpio, one or both of - * the entries can avoid setting GPIOMUX_VALID - the absence - * of that flag will prevent the configuration from being applied - * during state transitions. +/* Before using gpiomux, initialize the subsystem by telling it how many + * gpios are going to be managed. Calling any other gpiomux functions before + * msm_gpiomux_init is unsupported. + */ +int msm_gpiomux_init(size_t ngpio); + +/* Install a block of gpiomux configurations in gpiomux. This is functionally + * identical to calling msm_gpiomux_write many times. */ -extern struct msm_gpiomux_config msm_gpiomux_configs[GPIOMUX_NGPIOS]; +void msm_gpiomux_install(struct msm_gpiomux_config *configs, unsigned nconfigs); /* Increment a gpio's reference count, possibly activating the line. */ int __must_check msm_gpiomux_get(unsigned gpio); @@ -77,12 +128,16 @@ int __must_check msm_gpiomux_get(unsigned gpio); /* Decrement a gpio's reference count, possibly suspending the line. */ int msm_gpiomux_put(unsigned gpio); -/* Install a new configuration to the gpio line. To avoid overwriting - * a configuration, leave the VALID bit out. +/* Install a new setting in a gpio. To erase a slot, use NULL. + * The old setting that was overwritten can be passed back to the caller + * old_setting can be NULL if the caller is not interested in the previous + * setting + * If a previous setting was not available to return (NULL configuration) + * - the function returns 1 + * else function returns 0 */ -int msm_gpiomux_write(unsigned gpio, - gpiomux_config_t active, - gpiomux_config_t suspended); +int msm_gpiomux_write(unsigned gpio, enum msm_gpiomux_setting which, + struct gpiomux_setting *setting, struct gpiomux_setting *old_setting); /* Architecture-internal function for use by the framework only. * This function can assume the following: @@ -92,8 +147,16 @@ int msm_gpiomux_write(unsigned gpio, * This function is not for public consumption. External users * should use msm_gpiomux_write. */ -void __msm_gpiomux_write(unsigned gpio, gpiomux_config_t val); +void __msm_gpiomux_write(unsigned gpio, struct gpiomux_setting val); #else +static inline int msm_gpiomux_init(size_t ngpio) +{ + return -ENOSYS; +} + +static inline void +msm_gpiomux_install(struct msm_gpiomux_config *configs, unsigned nconfigs) {} + static inline int __must_check msm_gpiomux_get(unsigned gpio) { return -ENOSYS; @@ -105,8 +168,8 @@ static inline int msm_gpiomux_put(unsigned gpio) } static inline int msm_gpiomux_write(unsigned gpio, - gpiomux_config_t active, - gpiomux_config_t suspended) + enum msm_gpiomux_setting which, struct gpiomux_setting *setting, + struct gpiomux_setting *old_setting) { return -ENOSYS; } diff --git a/arch/arm/mach-msm/headsmp.S b/arch/arm/mach-msm/headsmp.S index 0c631a9f864..979c1f8ccc9 100644 --- a/arch/arm/mach-msm/headsmp.S +++ b/arch/arm/mach-msm/headsmp.S @@ -1,8 +1,7 @@ /* - * linux/arch/arm/mach-realview/headsmp.S - * * Copyright (c) 2003 ARM Limited * All Rights Reserved + * Copyright (c) 2010, Code Aurora Forum. 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 @@ -11,30 +10,36 @@ #include #include - __CPUINIT - /* * MSM specific entry point for secondary CPUs. This provides * a "holding pen" into which all secondary cores are held until we're * ready for them to initialise. + * + * This is executing in physical space with cache's off. */ ENTRY(msm_secondary_startup) - mrc p15, 0, r0, c0, c0, 5 - and r0, r0, #15 - adr r4, 1f - ldmia r4, {r5, r6} - sub r4, r4, r5 - add r6, r6, r4 -pen: ldr r7, [r6] - cmp r7, r0 + mrc p15, 0, r0, c0, c0, 5 @ MPIDR + and r0, r0, #15 @ What CPU am I + adr r4, 1f @ address of + ldmia r4, {r5, r6} @ load curr addr and pen_rel addr + sub r4, r4, r5 @ determine virtual/phys offsets + add r6, r6, r4 @ apply +pen: + wfe + dsb @ ensure subsequent access is + @ after event + + ldr r7, [r6] @ pen_rel has cpu to remove from reset + cmp r7, r0 @ are we lucky? bne pen /* * we've been released from the holding pen: secondary_stack * should now contain the SVC stack for this core */ + mvn r7, #0 @ -1 to registers + str r7,[r6] @ back to the pen for ack b secondary_startup - .align 1: .long . .long pen_release diff --git a/arch/arm/mach-msm/htc_35mm_jack.c b/arch/arm/mach-msm/htc_35mm_jack.c new file mode 100644 index 00000000000..3f95ff2bb6d --- /dev/null +++ b/arch/arm/mach-msm/htc_35mm_jack.c @@ -0,0 +1,397 @@ +/* arch/arm/mach-msm/htc_35mm_jack.c + * + * Copyright (C) 2009 HTC, Inc. + * Author: Arec Kao + * Copyright (C) 2009 Google, Inc. + * Author: Eric Olsen + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HTC_AUDIOJACK +#include +#endif + +/* #define CONFIG_DEBUG_H2W */ + +#define H2WI(fmt, arg...) \ + printk(KERN_INFO "[H2W] %s " fmt "\r\n", __func__, ## arg) +#define H2WE(fmt, arg...) \ + printk(KERN_ERR "[H2W] %s " fmt "\r\n", __func__, ## arg) + +#ifdef CONFIG_DEBUG_H2W +#define H2W_DBG(fmt, arg...) \ + printk(KERN_INFO "[H2W] %s " fmt "\r\n", __func__, ## arg) +#else +#define H2W_DBG(fmt, arg...) do {} while (0) +#endif + +void detect_h2w_do_work(struct work_struct *w); + +static struct workqueue_struct *detect_wq; +static struct workqueue_struct *button_wq; + +static DECLARE_DELAYED_WORK(detect_h2w_work, detect_h2w_do_work); + +static void insert_35mm_do_work(struct work_struct *work); +static DECLARE_WORK(insert_35mm_work, insert_35mm_do_work); +static void remove_35mm_do_work(struct work_struct *work); +static DECLARE_WORK(remove_35mm_work, remove_35mm_do_work); +static void button_35mm_do_work(struct work_struct *work); +static DECLARE_WORK(button_35mm_work, button_35mm_do_work); + +struct h35_info { + struct mutex mutex_lock; + struct switch_dev hs_change; + unsigned long insert_jiffies; + int ext_35mm_status; + int is_ext_insert; + int key_code; + int mic_bias_state; + int *is_hpin_stable; + struct input_dev *input; + + struct wake_lock headset_wake_lock; +}; + +static struct h35mm_platform_data *pd; +static struct h35_info *hi; + +static ssize_t h35mm_print_name(struct switch_dev *sdev, char *buf) +{ + return sprintf(buf, "Headset\n"); +} + +static void button_35mm_do_work(struct work_struct *work) +{ + int key = 0; + int pressed = 0; + + if (!hi->is_ext_insert) { + /* no headset ignor key event */ + H2WI("3.5mm headset is plugged out, skip report key event"); + return; + } + + switch (hi->key_code) { + case 0x1: /* Play/Pause */ + H2WI("3.5mm RC: Play Pressed"); + key = KEY_MEDIA; + pressed = 1; + break; + case 0x2: + H2WI("3.5mm RC: BACKWARD Pressed"); + key = KEY_PREVIOUSSONG; + pressed = 1; + break; + case 0x3: + H2WI("3.5mm RC: FORWARD Pressed"); + key = KEY_NEXTSONG; + pressed = 1; + break; + case 0x81: /* Play/Pause */ + H2WI("3.5mm RC: Play Released"); + key = KEY_MEDIA; + pressed = 0; + break; + case 0x82: + H2WI("3.5mm RC: BACKWARD Released"); + key = KEY_PREVIOUSSONG; + pressed = 0; + break; + case 0x83: + H2WI("3.5mm RC: FORWARD Released"); + key = KEY_NEXTSONG; + pressed = 0; + break; + default: + H2WI("3.5mm RC: Unknown Button (0x%x) Pressed", hi->key_code); + return; + } + input_report_key(hi->input, key, pressed); + input_sync(hi->input); + + wake_lock_timeout(&hi->headset_wake_lock, 1.5*HZ); +} + +static void remove_35mm_do_work(struct work_struct *work) +{ + wake_lock_timeout(&hi->headset_wake_lock, 2.5*HZ); + + H2W_DBG(""); + /*To solve the insert, remove, insert headset problem*/ + if (time_before_eq(jiffies, hi->insert_jiffies)) + msleep(800); + + if (hi->is_ext_insert) { + H2WI("Skip 3.5mm headset plug out!!!"); + if (hi->is_hpin_stable) + *(hi->is_hpin_stable) = 1; + return; + } + + pr_info("3.5mm_headset plug out\n"); + + if (pd->key_event_disable != NULL) + pd->key_event_disable(); + + if (hi->mic_bias_state) { + turn_mic_bias_on(0); + hi->mic_bias_state = 0; + } + hi->ext_35mm_status = 0; + if (hi->is_hpin_stable) + *(hi->is_hpin_stable) = 0; + + /* Notify framework via switch class */ + mutex_lock(&hi->mutex_lock); + switch_set_state(&hi->hs_change, hi->ext_35mm_status); + mutex_unlock(&hi->mutex_lock); +} + +static void insert_35mm_do_work(struct work_struct *work) +{ + H2W_DBG(""); + hi->insert_jiffies = jiffies + 1*HZ; + + wake_lock_timeout(&hi->headset_wake_lock, 1.5*HZ); + + if (hi->is_ext_insert) { + pr_info("3.5mm_headset plug in\n"); + + if (pd->key_event_enable != NULL) + pd->key_event_enable(); + + /* Turn On Mic Bias */ + if (!hi->mic_bias_state) { + turn_mic_bias_on(1); + hi->mic_bias_state = 1; + /* Wait for pin stable */ + msleep(300); + } + + /* Detect headset with or without microphone */ + if(pd->headset_has_mic) { + if (pd->headset_has_mic() == 0) { + /* without microphone */ + pr_info("3.5mm without microphone\n"); + hi->ext_35mm_status = BIT_HEADSET_NO_MIC; + } else { /* with microphone */ + pr_info("3.5mm with microphone\n"); + hi->ext_35mm_status = BIT_HEADSET; + } + } else { + /* Assume no mic */ + pr_info("3.5mm without microphone\n"); + hi->ext_35mm_status = BIT_HEADSET_NO_MIC; + } + hi->ext_35mm_status |= BIT_35MM_HEADSET; + + /* Notify framework via switch class */ + mutex_lock(&hi->mutex_lock); + switch_set_state(&hi->hs_change, hi->ext_35mm_status); + mutex_unlock(&hi->mutex_lock); + + if (hi->is_hpin_stable) + *(hi->is_hpin_stable) = 1; + } +} + +int htc_35mm_key_event(int keycode, int *hpin_stable) +{ + hi->key_code = keycode; + hi->is_hpin_stable = hpin_stable; + + if ((hi->ext_35mm_status & BIT_HEADSET) == 0) { + *(hi->is_hpin_stable) = 0; + + pr_info("Key press with no mic. Retrying detection\n"); + queue_work(detect_wq, &insert_35mm_work); + } else + queue_work(button_wq, &button_35mm_work); + + return 0; +} + +int htc_35mm_jack_plug_event(int insert, int *hpin_stable) +{ + if (!hi) { + pr_err("Plug event before driver init\n"); + return -1; + } + + mutex_lock(&hi->mutex_lock); + hi->is_ext_insert = insert; + hi->is_hpin_stable = hpin_stable; + mutex_unlock(&hi->mutex_lock); + + H2WI(" %d", hi->is_ext_insert); + if (!hi->is_ext_insert) + queue_work(detect_wq, &remove_35mm_work); + else + queue_work(detect_wq, &insert_35mm_work); + return 1; +} + +static int htc_35mm_probe(struct platform_device *pdev) +{ + int ret; + + pd = pdev->dev.platform_data; + + pr_info("H2W: htc_35mm_jack driver register\n"); + + hi = kzalloc(sizeof(struct h35_info), GFP_KERNEL); + if (!hi) + return -ENOMEM; + + hi->ext_35mm_status = 0; + hi->is_ext_insert = 0; + hi->mic_bias_state = 0; + + mutex_init(&hi->mutex_lock); + + wake_lock_init(&hi->headset_wake_lock, WAKE_LOCK_SUSPEND, "headset"); + + hi->hs_change.name = "h2w"; + hi->hs_change.print_name = h35mm_print_name; + ret = switch_dev_register(&hi->hs_change); + if (ret < 0) + goto err_switch_dev_register; + + detect_wq = create_workqueue("detection"); + if (detect_wq == NULL) { + ret = -ENOMEM; + goto err_create_detect_work_queue; + } + + button_wq = create_workqueue("button"); + if (button_wq == NULL) { + ret = -ENOMEM; + goto err_create_button_work_queue; + } + + hi->input = input_allocate_device(); + if (!hi->input) { + ret = -ENOMEM; + goto err_request_input_dev; + } + + hi->input->name = "h2w headset"; + set_bit(EV_SYN, hi->input->evbit); + set_bit(EV_KEY, hi->input->evbit); + set_bit(KEY_MEDIA, hi->input->keybit); + set_bit(KEY_NEXTSONG, hi->input->keybit); + set_bit(KEY_PLAYPAUSE, hi->input->keybit); + set_bit(KEY_PREVIOUSSONG, hi->input->keybit); + set_bit(KEY_MUTE, hi->input->keybit); + set_bit(KEY_VOLUMEUP, hi->input->keybit); + set_bit(KEY_VOLUMEDOWN, hi->input->keybit); + set_bit(KEY_END, hi->input->keybit); + set_bit(KEY_SEND, hi->input->keybit); + + ret = input_register_device(hi->input); + if (ret < 0) + goto err_register_input_dev; + + /* Enable plug events*/ + if (pd->plug_event_enable == NULL) { + ret = -ENOMEM; + goto err_enable_plug_event; + } + if (pd->plug_event_enable() != 1) { + ret = -ENOMEM; + goto err_enable_plug_event; + } + + return 0; + +err_enable_plug_event: +err_register_input_dev: + input_free_device(hi->input); +err_request_input_dev: + destroy_workqueue(button_wq); +err_create_button_work_queue: + destroy_workqueue(detect_wq); +err_create_detect_work_queue: + switch_dev_unregister(&hi->hs_change); +err_switch_dev_register: + kzfree(hi); + pr_err("H2W: Failed to register driver\n"); + + return ret; +} + +static int htc_35mm_remove(struct platform_device *pdev) +{ + H2W_DBG(""); + switch_dev_unregister(&hi->hs_change); + kzfree(hi); + +#if 0 /* Add keys later */ + input_unregister_device(hi->input); +#endif + return 0; +} + +static struct platform_driver htc_35mm_driver = { + .probe = htc_35mm_probe, + .remove = htc_35mm_remove, + .driver = { + .name = "htc_headset", + .owner = THIS_MODULE, + }, +}; + +static int __init htc_35mm_init(void) +{ + H2W_DBG(""); + return platform_driver_register(&htc_35mm_driver); +} + +static void __exit htc_35mm_exit(void) +{ + platform_driver_unregister(&htc_35mm_driver); +} + +module_init(htc_35mm_init); +module_exit(htc_35mm_exit); + +MODULE_AUTHOR("Eric Olsen "); +MODULE_DESCRIPTION("HTC 3.5MM Driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/htc_acoustic.c b/arch/arm/mach-msm/htc_acoustic.c new file mode 100644 index 00000000000..3de71dddb58 --- /dev/null +++ b/arch/arm/mach-msm/htc_acoustic.c @@ -0,0 +1,239 @@ +/* arch/arm/mach-msm/htc_acoustic.c + * + * Copyright (C) 2007-2008 HTC Corporation + * Author: Laurence Chen + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "smd_private.h" + +#define ACOUSTIC_IOCTL_MAGIC 'p' +#define ACOUSTIC_ARM11_DONE _IOW(ACOUSTIC_IOCTL_MAGIC, 22, unsigned int) + +#define HTCRPOG 0x30100002 +#define HTCVERS 0 +#define ONCRPC_SET_MIC_BIAS_PROC (1) +#define ONCRPC_ACOUSTIC_INIT_PROC (5) +#define ONCRPC_ALLOC_ACOUSTIC_MEM_PROC (6) + +#define HTC_ACOUSTIC_TABLE_SIZE (0x10000) + +#define D(fmt, args...) printk(KERN_INFO "htc-acoustic: "fmt, ##args) +#define E(fmt, args...) printk(KERN_ERR "htc-acoustic: "fmt, ##args) + +struct set_smem_req { + struct rpc_request_hdr hdr; + uint32_t size; +}; + +struct set_smem_rep { + struct rpc_reply_hdr hdr; + int n; +}; + +struct set_acoustic_req { + struct rpc_request_hdr hdr; +}; + +struct set_acoustic_rep { + struct rpc_reply_hdr hdr; + int n; +}; + +static uint32_t htc_acoustic_vir_addr; +static struct msm_rpc_endpoint *endpoint; +static struct mutex api_lock; + +static int acoustic_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long pgoff, delta; + int rc = -EINVAL; + size_t size; + + D("mmap\n"); + + mutex_lock(&api_lock); + + size = vma->vm_end - vma->vm_start; + + if (vma->vm_pgoff != 0) { + E("mmap failed: page offset %lx\n", vma->vm_pgoff); + goto done; + } + + if (!htc_acoustic_vir_addr) { + E("mmap failed: smem region not allocated\n"); + rc = -EIO; + goto done; + } + + pgoff = MSM_SHARED_RAM_PHYS + + (htc_acoustic_vir_addr - (uint32_t)MSM_SHARED_RAM_BASE); + delta = PAGE_ALIGN(pgoff) - pgoff; + + if (size + delta > HTC_ACOUSTIC_TABLE_SIZE) { + E("mmap failed: size %d\n", size); + goto done; + } + + pgoff += delta; + vma->vm_flags |= VM_IO | VM_RESERVED; + + rc = io_remap_pfn_range(vma, vma->vm_start, pgoff >> PAGE_SHIFT, + size, vma->vm_page_prot); + + if (rc < 0) + E("mmap failed: remap error %d\n", rc); + +done: mutex_unlock(&api_lock); + return rc; +} + +static int acoustic_open(struct inode *inode, struct file *file) +{ + int rc = -EIO; + struct set_smem_req req_smem; + struct set_smem_rep rep_smem; + + D("open\n"); + + mutex_lock(&api_lock); + + if (!htc_acoustic_vir_addr) { + if (endpoint == NULL) { + endpoint = msm_rpc_connect(HTCRPOG, HTCVERS, 0); + if (IS_ERR(endpoint)) { + E("init rpc failed! rc = %ld\n", + PTR_ERR(endpoint)); + endpoint = NULL; + goto done; + } + } + + req_smem.size = cpu_to_be32(HTC_ACOUSTIC_TABLE_SIZE); + rc = msm_rpc_call_reply(endpoint, + ONCRPC_ALLOC_ACOUSTIC_MEM_PROC, + &req_smem, sizeof(req_smem), + &rep_smem, sizeof(rep_smem), + 5 * HZ); + + if (rep_smem.n != 0 || rc < 0) { + E("open failed: ALLOC_ACOUSTIC_MEM_PROC error %d.\n", + rc); + goto done; + } + htc_acoustic_vir_addr = + (uint32_t)smem_alloc(SMEM_ID_VENDOR1, + HTC_ACOUSTIC_TABLE_SIZE); + if (!htc_acoustic_vir_addr) { + E("open failed: smem_alloc error\n"); + goto done; + } + } + + rc = 0; +done: + mutex_unlock(&api_lock); + return rc; +} + +static int acoustic_release(struct inode *inode, struct file *file) +{ + D("release\n"); + return 0; +} + +static long acoustic_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int rc, reply_value; + struct set_acoustic_req req; + struct set_acoustic_rep rep; + + D("ioctl\n"); + + mutex_lock(&api_lock); + + switch (cmd) { + case ACOUSTIC_ARM11_DONE: + D("ioctl: ACOUSTIC_ARM11_DONE called %d.\n", current->pid); + rc = msm_rpc_call_reply(endpoint, + ONCRPC_ACOUSTIC_INIT_PROC, &req, + sizeof(req), &rep, sizeof(rep), + 5 * HZ); + + reply_value = be32_to_cpu(rep.n); + if (reply_value != 0 || rc < 0) { + E("ioctl failed: ONCRPC_ACOUSTIC_INIT_PROC "\ + "error %d.\n", rc); + if (rc >= 0) + rc = -EIO; + break; + } + D("ioctl: ONCRPC_ACOUSTIC_INIT_PROC success.\n"); + break; + default: + E("ioctl: invalid command\n"); + rc = -EINVAL; + } + + mutex_unlock(&api_lock); + return 0; +} + + +static struct file_operations acoustic_fops = { + .owner = THIS_MODULE, + .open = acoustic_open, + .release = acoustic_release, + .mmap = acoustic_mmap, + .unlocked_ioctl = acoustic_ioctl, +}; + +static struct miscdevice acoustic_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "htc-acoustic", + .fops = &acoustic_fops, +}; + +static int __init acoustic_init(void) +{ + mutex_init(&api_lock); + return misc_register(&acoustic_misc); +} + +static void __exit acoustic_exit(void) +{ + misc_deregister(&acoustic_misc); +} + +module_init(acoustic_init); +module_exit(acoustic_exit); + +MODULE_AUTHOR("Laurence Chen "); +MODULE_DESCRIPTION("HTC acoustic driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/htc_acoustic_qsd.c b/arch/arm/mach-msm/htc_acoustic_qsd.c new file mode 100644 index 00000000000..ce3c3a0bbfe --- /dev/null +++ b/arch/arm/mach-msm/htc_acoustic_qsd.c @@ -0,0 +1,315 @@ +/* arch/arm/mach-msm/htc_acoustic_qsd.c + * + * Copyright (C) 2009 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "smd_private.h" + +#define ACOUSTIC_IOCTL_MAGIC 'p' +#define ACOUSTIC_UPDATE_ADIE \ + _IOW(ACOUSTIC_IOCTL_MAGIC, 24, unsigned int) + +#define HTCACOUSTICPROG 0x30100003 +#define HTCACOUSTICVERS 0 +#define ONCRPC_ALLOC_ACOUSTIC_MEM_PROC (1) +#define ONCRPC_UPDATE_ADIE_PROC (2) +#define ONCRPC_ENABLE_AUX_PGA_LOOPBACK_PROC (3) +#define ONCRPC_FORCE_HEADSET_SPEAKER_PROC (4) + +#define HTC_ACOUSTIC_TABLE_SIZE (0x20000) + +#define D(fmt, args...) printk(KERN_INFO "htc-acoustic: "fmt, ##args) +#define E(fmt, args...) printk(KERN_ERR "htc-acoustic: "fmt, ##args) + +static uint32_t htc_acoustic_vir_addr; +static struct msm_rpc_endpoint *endpoint; +static struct mutex api_lock; +static struct mutex rpc_connect_lock; +static struct qsd_acoustic_ops *the_ops; + +void acoustic_register_ops(struct qsd_acoustic_ops *ops) +{ + the_ops = ops; +} + +static int is_rpc_connect(void) +{ + mutex_lock(&rpc_connect_lock); + if (endpoint == NULL) { + endpoint = msm_rpc_connect(HTCACOUSTICPROG, + HTCACOUSTICVERS, 0); + if (IS_ERR(endpoint)) { + pr_err("%s: init rpc failed! rc = %ld\n", + __func__, PTR_ERR(endpoint)); + mutex_unlock(&rpc_connect_lock); + return -1; + } + } + mutex_unlock(&rpc_connect_lock); + return 0; +} + +int turn_mic_bias_on(int on) +{ + D("%s called %d\n", __func__, on); + if (the_ops->enable_mic_bias) + the_ops->enable_mic_bias(on); + + return 0; +} +EXPORT_SYMBOL(turn_mic_bias_on); + +int force_headset_speaker_on(int enable) +{ + struct speaker_headset_req { + struct rpc_request_hdr hdr; + uint32_t enable; + } spkr_req; + + D("%s called %d\n", __func__, enable); + + if (is_rpc_connect() == -1) + return -1; + + spkr_req.enable = cpu_to_be32(enable); + return msm_rpc_call(endpoint, + ONCRPC_FORCE_HEADSET_SPEAKER_PROC, + &spkr_req, sizeof(spkr_req), 5 * HZ); +} +EXPORT_SYMBOL(force_headset_speaker_on); + +int enable_aux_loopback(uint32_t enable) +{ + struct aux_loopback_req { + struct rpc_request_hdr hdr; + uint32_t enable; + } aux_req; + + D("%s called %d\n", __func__, enable); + + if (is_rpc_connect() == -1) + return -1; + + aux_req.enable = cpu_to_be32(enable); + return msm_rpc_call(endpoint, + ONCRPC_ENABLE_AUX_PGA_LOOPBACK_PROC, + &aux_req, sizeof(aux_req), 5 * HZ); +} +EXPORT_SYMBOL(enable_aux_loopback); + +static int acoustic_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long pgoff; + int rc = -EINVAL; + size_t size; + + D("mmap\n"); + + mutex_lock(&api_lock); + + size = vma->vm_end - vma->vm_start; + + if (vma->vm_pgoff != 0) { + E("mmap failed: page offset %lx\n", vma->vm_pgoff); + goto done; + } + + if (!htc_acoustic_vir_addr) { + E("mmap failed: smem region not allocated\n"); + rc = -EIO; + goto done; + } + + pgoff = MSM_SHARED_RAM_PHYS + + (htc_acoustic_vir_addr - (uint32_t)MSM_SHARED_RAM_BASE); + pgoff = ((pgoff + 4095) & ~4095); + htc_acoustic_vir_addr = ((htc_acoustic_vir_addr + 4095) & ~4095); + + if (pgoff <= 0) { + E("pgoff wrong. %ld\n", pgoff); + goto done; + } + + if (size <= HTC_ACOUSTIC_TABLE_SIZE) { + pgoff = pgoff >> PAGE_SHIFT; + } else { + E("size > HTC_ACOUSTIC_TABLE_SIZE %d\n", size); + goto done; + } + + vma->vm_flags |= VM_IO | VM_RESERVED; + rc = io_remap_pfn_range(vma, vma->vm_start, pgoff, + size, vma->vm_page_prot); + + if (rc < 0) + E("mmap failed: remap error %d\n", rc); + +done: mutex_unlock(&api_lock); + return rc; +} + +static int acoustic_open(struct inode *inode, struct file *file) +{ + int reply_value; + int rc = -EIO; + struct set_smem_req { + struct rpc_request_hdr hdr; + uint32_t size; + } req_smem; + + struct set_smem_rep { + struct rpc_reply_hdr hdr; + int n; + } rep_smem; + + D("open\n"); + + mutex_lock(&api_lock); + + if (!htc_acoustic_vir_addr) { + if (is_rpc_connect() == -1) + goto done; + + req_smem.size = cpu_to_be32(HTC_ACOUSTIC_TABLE_SIZE); + rc = msm_rpc_call_reply(endpoint, + ONCRPC_ALLOC_ACOUSTIC_MEM_PROC, + &req_smem, sizeof(req_smem), + &rep_smem, sizeof(rep_smem), + 5 * HZ); + + reply_value = be32_to_cpu(rep_smem.n); + if (reply_value != 0 || rc < 0) { + E("open failed: ALLOC_ACOUSTIC_MEM_PROC error %d.\n", + rc); + goto done; + } + htc_acoustic_vir_addr = + (uint32_t)smem_alloc(SMEM_ID_VENDOR1, + HTC_ACOUSTIC_TABLE_SIZE); + if (!htc_acoustic_vir_addr) { + E("open failed: smem_alloc error\n"); + goto done; + } + } + + rc = 0; +done: + mutex_unlock(&api_lock); + return rc; +} + +static int acoustic_release(struct inode *inode, struct file *file) +{ + D("release\n"); + return 0; +} + +static long acoustic_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int rc, reply_value; + + D("ioctl\n"); + + mutex_lock(&api_lock); + + switch (cmd) { + case ACOUSTIC_UPDATE_ADIE: { + struct update_adie_req { + struct rpc_request_hdr hdr; + int id; + } adie_req; + + struct update_adie_rep { + struct rpc_reply_hdr hdr; + int ret; + } adie_rep; + + D("ioctl: ACOUSTIC_UPDATE_ADIE called %d.\n", current->pid); + + adie_req.id = cpu_to_be32(-1); /* update all codecs */ + rc = msm_rpc_call_reply(endpoint, + ONCRPC_UPDATE_ADIE_PROC, &adie_req, + sizeof(adie_req), &adie_rep, + sizeof(adie_rep), 5 * HZ); + + reply_value = be32_to_cpu(adie_rep.ret); + if (reply_value != 0 || rc < 0) { + E("ioctl failed: ONCRPC_UPDATE_ADIE_PROC "\ + "error %d.\n", rc); + if (rc >= 0) + rc = -EIO; + break; + } + D("ioctl: ONCRPC_UPDATE_ADIE_PROC success.\n"); + break; + } + default: + E("ioctl: invalid command\n"); + rc = -EINVAL; + } + + mutex_unlock(&api_lock); + return rc; +} + +struct rpc_set_uplink_mute_args { + int mute; +}; + +static struct file_operations acoustic_fops = { + .owner = THIS_MODULE, + .open = acoustic_open, + .release = acoustic_release, + .mmap = acoustic_mmap, + .unlocked_ioctl = acoustic_ioctl, +}; + +static struct miscdevice acoustic_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "htc-acoustic", + .fops = &acoustic_fops, +}; + +static int __init acoustic_init(void) +{ + mutex_init(&api_lock); + mutex_init(&rpc_connect_lock); + return misc_register(&acoustic_misc); +} + +static void __exit acoustic_exit(void) +{ + misc_deregister(&acoustic_misc); +} + +module_init(acoustic_init); +module_exit(acoustic_exit); + diff --git a/arch/arm/mach-msm/htc_akm_cal.c b/arch/arm/mach-msm/htc_akm_cal.c new file mode 100644 index 00000000000..943083fe0fb --- /dev/null +++ b/arch/arm/mach-msm/htc_akm_cal.c @@ -0,0 +1,64 @@ +/* arch/arm/mach-msm/htc_akm_cal.c + * + * Code to extract compass calibration information from ATAG set up + * by the bootloader. + * + * Copyright (C) 2007-2008 HTC Corporation + * Author: Farmer Tseng + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include + +/* configuration tags specific to AKM8976 */ +#define ATAG_AKM8976 0x89768976 /* AKM8976 */ + +#define MAX_CALI_SIZE 0x1000U + +static char akm_cal_ram[MAX_CALI_SIZE]; + +char *get_akm_cal_ram(void) +{ + return(akm_cal_ram); +} +EXPORT_SYMBOL(get_akm_cal_ram); + +static int __init parse_tag_akm(const struct tag *tag) +{ + unsigned char *dptr = (unsigned char *)(&tag->u); + unsigned size; + + size = min((tag->hdr.size - 2) * sizeof(__u32), MAX_CALI_SIZE); + + printk(KERN_INFO "AKM Data size = %d , 0x%x, size = %d\n", + tag->hdr.size, tag->hdr.tag, size); + +#ifdef ATAG_COMPASS_DEBUG + unsigned i; + unsigned char *ptr; + + ptr = dptr; + printk(KERN_INFO + "AKM Data size = %d , 0x%x\n", + tag->hdr.size, tag->hdr.tag); + for (i = 0; i < size; i++) + printk(KERN_INFO "%02x ", *ptr++); +#endif + memcpy((void *)akm_cal_ram, (void *)dptr, size); + return 0; +} + +__tagtable(ATAG_AKM8976, parse_tag_akm); diff --git a/arch/arm/mach-msm/htc_battery.c b/arch/arm/mach-msm/htc_battery.c new file mode 100644 index 00000000000..d49c23e864c --- /dev/null +++ b/arch/arm/mach-msm/htc_battery.c @@ -0,0 +1,771 @@ +/* arch/arm/mach-msm/htc_battery.c + * + * Copyright (C) 2008 HTC Corporation. + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct wake_lock vbus_wake_lock; + +#define TRACE_BATT 0 + +#if TRACE_BATT +#define BATT(x...) printk(KERN_INFO "[BATT] " x) +#else +#define BATT(x...) do {} while (0) +#endif + +/* rpc related */ +#define APP_BATT_PDEV_NAME "rs30100001" +#define APP_BATT_PROG 0x30100001 +#define APP_BATT_VER 0 +#define HTC_PROCEDURE_BATTERY_NULL 0 +#define HTC_PROCEDURE_GET_BATT_LEVEL 1 +#define HTC_PROCEDURE_GET_BATT_INFO 2 +#define HTC_PROCEDURE_GET_CABLE_STATUS 3 +#define HTC_PROCEDURE_SET_BATT_DELTA 4 + +/* module debugger */ +#define HTC_BATTERY_DEBUG 1 +#define BATTERY_PREVENTION 1 + +/* Enable this will shut down if no battery */ +#define ENABLE_BATTERY_DETECTION 0 + +#define GPIO_BATTERY_DETECTION 21 +#define GPIO_BATTERY_CHARGER_EN 128 + +/* Charge current selection */ +#define GPIO_BATTERY_CHARGER_CURRENT 129 + +typedef enum { + DISABLE = 0, + ENABLE_SLOW_CHG, + ENABLE_FAST_CHG +} batt_ctl_t; + +/* This order is the same as htc_power_supplies[] + * And it's also the same as htc_cable_status_update() + */ +typedef enum { + CHARGER_BATTERY = 0, + CHARGER_USB, + CHARGER_AC +} charger_type_t; + +struct battery_info_reply { + u32 batt_id; /* Battery ID from ADC */ + u32 batt_vol; /* Battery voltage from ADC */ + u32 batt_temp; /* Battery Temperature (C) from formula and ADC */ + u32 batt_current; /* Battery current from ADC */ + u32 level; /* formula */ + u32 charging_source; /* 0: no cable, 1:usb, 2:AC */ + u32 charging_enabled; /* 0: Disable, 1: Enable */ + u32 full_bat; /* Full capacity of battery (mAh) */ +}; + +struct htc_battery_info { + int present; + unsigned long update_time; + + /* lock to protect the battery info */ + struct mutex lock; + + /* lock held while calling the arm9 to query the battery info */ + struct mutex rpc_lock; + struct battery_info_reply rep; +}; + +static struct msm_rpc_endpoint *endpoint; + +static struct htc_battery_info htc_batt_info; + +static unsigned int cache_time = 1000; + +static int htc_battery_initial = 0; + +static enum power_supply_property htc_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static enum power_supply_property htc_power_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *supply_list[] = { + "battery", +}; + +/* HTC dedicated attributes */ +static ssize_t htc_battery_show_property(struct device *dev, + struct device_attribute *attr, + char *buf); + +static int htc_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); + +static int htc_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val); + +static struct power_supply htc_power_supplies[] = { + { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = htc_battery_properties, + .num_properties = ARRAY_SIZE(htc_battery_properties), + .get_property = htc_battery_get_property, + }, + { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = htc_power_properties, + .num_properties = ARRAY_SIZE(htc_power_properties), + .get_property = htc_power_get_property, + }, + { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = htc_power_properties, + .num_properties = ARRAY_SIZE(htc_power_properties), + .get_property = htc_power_get_property, + }, +}; + + +/* -------------------------------------------------------------------------- */ + +#if defined(CONFIG_DEBUG_FS) +int htc_battery_set_charging(batt_ctl_t ctl); +static int batt_debug_set(void *data, u64 val) +{ + return htc_battery_set_charging((batt_ctl_t) val); +} + +static int batt_debug_get(void *data, u64 *val) +{ + return -ENOSYS; +} + +DEFINE_SIMPLE_ATTRIBUTE(batt_debug_fops, batt_debug_get, batt_debug_set, "%llu\n"); +static int __init batt_debug_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("htc_battery", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debugfs_create_file("charger_state", 0644, dent, NULL, &batt_debug_fops); + + return 0; +} + +device_initcall(batt_debug_init); +#endif + +static int init_batt_gpio(void) +{ + if (gpio_request(GPIO_BATTERY_DETECTION, "batt_detect") < 0) + goto gpio_failed; + if (gpio_request(GPIO_BATTERY_CHARGER_EN, "charger_en") < 0) + goto gpio_failed; + if (gpio_request(GPIO_BATTERY_CHARGER_CURRENT, "charge_current") < 0) + goto gpio_failed; + + return 0; + +gpio_failed: + return -EINVAL; + +} + +/* + * battery_charging_ctrl - battery charing control. + * @ctl: battery control command + * + */ +static int battery_charging_ctrl(batt_ctl_t ctl) +{ + int result = 0; + + switch (ctl) { + case DISABLE: + BATT("charger OFF\n"); + /* 0 for enable; 1 disable */ + result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 1); + break; + case ENABLE_SLOW_CHG: + BATT("charger ON (SLOW)\n"); + result = gpio_direction_output(GPIO_BATTERY_CHARGER_CURRENT, 0); + result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 0); + break; + case ENABLE_FAST_CHG: + BATT("charger ON (FAST)\n"); + result = gpio_direction_output(GPIO_BATTERY_CHARGER_CURRENT, 1); + result = gpio_direction_output(GPIO_BATTERY_CHARGER_EN, 0); + break; + default: + printk(KERN_ERR "Not supported battery ctr called.!\n"); + result = -EINVAL; + break; + } + + return result; +} + +int htc_battery_set_charging(batt_ctl_t ctl) +{ + int rc; + + if ((rc = battery_charging_ctrl(ctl)) < 0) + goto result; + + if (!htc_battery_initial) { + htc_batt_info.rep.charging_enabled = ctl & 0x3; + } else { + mutex_lock(&htc_batt_info.lock); + htc_batt_info.rep.charging_enabled = ctl & 0x3; + mutex_unlock(&htc_batt_info.lock); + } +result: + return rc; +} + +int htc_battery_status_update(u32 curr_level) +{ + int notify; + if (!htc_battery_initial) + return 0; + + mutex_lock(&htc_batt_info.lock); + notify = (htc_batt_info.rep.level != curr_level); + htc_batt_info.rep.level = curr_level; + mutex_unlock(&htc_batt_info.lock); + + if (notify) + power_supply_changed(&htc_power_supplies[CHARGER_BATTERY]); + return 0; +} + +int htc_cable_status_update(int status) +{ + int rc = 0; + unsigned source; + + if (!htc_battery_initial) + return 0; + + mutex_lock(&htc_batt_info.lock); + switch(status) { + case CHARGER_BATTERY: + BATT("cable NOT PRESENT\n"); + htc_batt_info.rep.charging_source = CHARGER_BATTERY; + break; + case CHARGER_USB: + BATT("cable USB\n"); + htc_batt_info.rep.charging_source = CHARGER_USB; + break; + case CHARGER_AC: + BATT("cable AC\n"); + htc_batt_info.rep.charging_source = CHARGER_AC; + break; + default: + printk(KERN_ERR "%s: Not supported cable status received!\n", + __FUNCTION__); + rc = -EINVAL; + } + source = htc_batt_info.rep.charging_source; + mutex_unlock(&htc_batt_info.lock); + + msm_hsusb_set_vbus_state(source == CHARGER_USB); + if (source == CHARGER_USB) { + wake_lock(&vbus_wake_lock); + } else { + /* give userspace some time to see the uevent and update + * LED state or whatnot... + */ + wake_lock_timeout(&vbus_wake_lock, HZ / 2); + } + + /* if the power source changes, all power supplies may change state */ + power_supply_changed(&htc_power_supplies[CHARGER_BATTERY]); + power_supply_changed(&htc_power_supplies[CHARGER_USB]); + power_supply_changed(&htc_power_supplies[CHARGER_AC]); + + return rc; +} + +static int htc_get_batt_info(struct battery_info_reply *buffer) +{ + struct rpc_request_hdr req; + + struct htc_get_batt_info_rep { + struct rpc_reply_hdr hdr; + struct battery_info_reply info; + } rep; + + int rc; + + if (buffer == NULL) + return -EINVAL; + + rc = msm_rpc_call_reply(endpoint, HTC_PROCEDURE_GET_BATT_INFO, + &req, sizeof(req), + &rep, sizeof(rep), + 5 * HZ); + if ( rc < 0 ) + return rc; + + mutex_lock(&htc_batt_info.lock); + buffer->batt_id = be32_to_cpu(rep.info.batt_id); + buffer->batt_vol = be32_to_cpu(rep.info.batt_vol); + buffer->batt_temp = be32_to_cpu(rep.info.batt_temp); + buffer->batt_current = be32_to_cpu(rep.info.batt_current); + buffer->level = be32_to_cpu(rep.info.level); + buffer->charging_source = be32_to_cpu(rep.info.charging_source); + buffer->charging_enabled = be32_to_cpu(rep.info.charging_enabled); + buffer->full_bat = be32_to_cpu(rep.info.full_bat); + mutex_unlock(&htc_batt_info.lock); + + return 0; +} + +#if 0 +static int htc_get_cable_status(void) +{ + + struct rpc_request_hdr req; + + struct htc_get_cable_status_rep { + struct rpc_reply_hdr hdr; + int status; + } rep; + + int rc; + + rc = msm_rpc_call_reply(endpoint, HTC_PROCEDURE_GET_CABLE_STATUS, + &req, sizeof(req), + &rep, sizeof(rep), + 5 * HZ); + if (rc < 0) + return rc; + + return be32_to_cpu(rep.status); +} +#endif + +/* -------------------------------------------------------------------------- */ +static int htc_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + charger_type_t charger; + + mutex_lock(&htc_batt_info.lock); + charger = htc_batt_info.rep.charging_source; + mutex_unlock(&htc_batt_info.lock); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (psy->type == POWER_SUPPLY_TYPE_MAINS) + val->intval = (charger == CHARGER_AC ? 1 : 0); + else if (psy->type == POWER_SUPPLY_TYPE_USB) + val->intval = (charger == CHARGER_USB ? 1 : 0); + else + val->intval = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int htc_battery_get_charging_status(void) +{ + u32 level; + charger_type_t charger; + int ret; + + mutex_lock(&htc_batt_info.lock); + charger = htc_batt_info.rep.charging_source; + + switch (charger) { + case CHARGER_BATTERY: + ret = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case CHARGER_USB: + case CHARGER_AC: + level = htc_batt_info.rep.level; + if (level == 100) + ret = POWER_SUPPLY_STATUS_FULL; + else + ret = POWER_SUPPLY_STATUS_CHARGING; + break; + default: + ret = POWER_SUPPLY_STATUS_UNKNOWN; + } + mutex_unlock(&htc_batt_info.lock); + return ret; +} + +static int htc_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = htc_battery_get_charging_status(); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = htc_batt_info.present; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CAPACITY: + mutex_lock(&htc_batt_info.lock); + val->intval = htc_batt_info.rep.level; + mutex_unlock(&htc_batt_info.lock); + break; + default: + return -EINVAL; + } + + return 0; +} + +#define HTC_BATTERY_ATTR(_name) \ +{ \ + .attr = { .name = #_name, .mode = S_IRUGO, .owner = THIS_MODULE }, \ + .show = htc_battery_show_property, \ + .store = NULL, \ +} + +static struct device_attribute htc_battery_attrs[] = { + HTC_BATTERY_ATTR(batt_id), + HTC_BATTERY_ATTR(batt_vol), + HTC_BATTERY_ATTR(batt_temp), + HTC_BATTERY_ATTR(batt_current), + HTC_BATTERY_ATTR(charging_source), + HTC_BATTERY_ATTR(charging_enabled), + HTC_BATTERY_ATTR(full_bat), +}; + +enum { + BATT_ID = 0, + BATT_VOL, + BATT_TEMP, + BATT_CURRENT, + CHARGING_SOURCE, + CHARGING_ENABLED, + FULL_BAT, +}; + +static int htc_rpc_set_delta(unsigned delta) +{ + struct set_batt_delta_req { + struct rpc_request_hdr hdr; + uint32_t data; + } req; + + req.data = cpu_to_be32(delta); + return msm_rpc_call(endpoint, HTC_PROCEDURE_SET_BATT_DELTA, + &req, sizeof(req), 5 * HZ); +} + + +static ssize_t htc_battery_set_delta(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int rc; + unsigned long delta = 0; + + delta = simple_strtoul(buf, NULL, 10); + + if (delta > 100) + return -EINVAL; + + mutex_lock(&htc_batt_info.rpc_lock); + rc = htc_rpc_set_delta(delta); + mutex_unlock(&htc_batt_info.rpc_lock); + if (rc < 0) + return rc; + return count; +} + +static struct device_attribute htc_set_delta_attrs[] = { + __ATTR(delta, S_IWUSR | S_IWGRP, NULL, htc_battery_set_delta), +}; + +static int htc_battery_create_attrs(struct device * dev) +{ + int i, j, rc; + + for (i = 0; i < ARRAY_SIZE(htc_battery_attrs); i++) { + rc = device_create_file(dev, &htc_battery_attrs[i]); + if (rc) + goto htc_attrs_failed; + } + + for (j = 0; j < ARRAY_SIZE(htc_set_delta_attrs); j++) { + rc = device_create_file(dev, &htc_set_delta_attrs[j]); + if (rc) + goto htc_delta_attrs_failed; + } + + goto succeed; + +htc_attrs_failed: + while (i--) + device_remove_file(dev, &htc_battery_attrs[i]); +htc_delta_attrs_failed: + while (j--) + device_remove_file(dev, &htc_set_delta_attrs[i]); +succeed: + return rc; +} + +static ssize_t htc_battery_show_property(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i = 0; + const ptrdiff_t off = attr - htc_battery_attrs; + + /* rpc lock is used to prevent two threads from calling + * into the get info rpc at the same time + */ + + mutex_lock(&htc_batt_info.rpc_lock); + /* check cache time to decide if we need to update */ + if (htc_batt_info.update_time && + time_before(jiffies, htc_batt_info.update_time + + msecs_to_jiffies(cache_time))) + goto dont_need_update; + + if (htc_get_batt_info(&htc_batt_info.rep) < 0) + printk(KERN_ERR "%s: rpc failed!!!\n", __FUNCTION__); + else + htc_batt_info.update_time = jiffies; +dont_need_update: + mutex_unlock(&htc_batt_info.rpc_lock); + + mutex_lock(&htc_batt_info.lock); + switch (off) { + case BATT_ID: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.batt_id); + break; + case BATT_VOL: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.batt_vol); + break; + case BATT_TEMP: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.batt_temp); + break; + case BATT_CURRENT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.batt_current); + break; + case CHARGING_SOURCE: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.charging_source); + break; + case CHARGING_ENABLED: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.charging_enabled); + break; + case FULL_BAT: + i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", + htc_batt_info.rep.full_bat); + break; + default: + i = -EINVAL; + } + mutex_unlock(&htc_batt_info.lock); + + return i; +} + +static int htc_battery_probe(struct platform_device *pdev) +{ + int i, rc; + + if (pdev->id != (APP_BATT_VER & RPC_VERSION_MAJOR_MASK)) + return -EINVAL; + + /* init battery gpio */ + if ((rc = init_batt_gpio()) < 0) { + printk(KERN_ERR "%s: init battery gpio failed!\n", __FUNCTION__); + return rc; + } + + /* init structure data member */ + htc_batt_info.update_time = jiffies; + htc_batt_info.present = gpio_get_value(GPIO_BATTERY_DETECTION); + + /* init rpc */ + endpoint = msm_rpc_connect(APP_BATT_PROG, APP_BATT_VER, 0); + if (IS_ERR(endpoint)) { + printk(KERN_ERR "%s: init rpc failed! rc = %ld\n", + __FUNCTION__, PTR_ERR(endpoint)); + return rc; + } + + /* init power supplier framework */ + for (i = 0; i < ARRAY_SIZE(htc_power_supplies); i++) { + rc = power_supply_register(&pdev->dev, &htc_power_supplies[i]); + if (rc) + printk(KERN_ERR "Failed to register power supply (%d)\n", rc); + } + + /* create htc detail attributes */ + htc_battery_create_attrs(htc_power_supplies[CHARGER_BATTERY].dev); + + /* After battery driver gets initialized, send rpc request to inquiry + * the battery status in case of we lost some info + */ + htc_battery_initial = 1; + + mutex_lock(&htc_batt_info.rpc_lock); + if (htc_get_batt_info(&htc_batt_info.rep) < 0) + printk(KERN_ERR "%s: get info failed\n", __FUNCTION__); + + htc_cable_status_update(htc_batt_info.rep.charging_source); + battery_charging_ctrl(htc_batt_info.rep.charging_enabled ? + ENABLE_SLOW_CHG : DISABLE); + + if (htc_rpc_set_delta(1) < 0) + printk(KERN_ERR "%s: set delta failed\n", __FUNCTION__); + htc_batt_info.update_time = jiffies; + mutex_unlock(&htc_batt_info.rpc_lock); + + if (htc_batt_info.rep.charging_enabled == 0) + battery_charging_ctrl(DISABLE); + + return 0; +} + +static struct platform_driver htc_battery_driver = { + .probe = htc_battery_probe, + .driver = { + .name = APP_BATT_PDEV_NAME, + .owner = THIS_MODULE, + }, +}; + +/* batt_mtoa server definitions */ +#define BATT_MTOA_PROG 0x30100000 +#define BATT_MTOA_VERS 0 +#define RPC_BATT_MTOA_NULL 0 +#define RPC_BATT_MTOA_SET_CHARGING_PROC 1 +#define RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC 2 +#define RPC_BATT_MTOA_LEVEL_UPDATE_PROC 3 + +struct rpc_batt_mtoa_set_charging_args { + int enable; +}; + +struct rpc_batt_mtoa_cable_status_update_args { + int status; +}; + +struct rpc_dem_battery_update_args { + uint32_t level; +}; + +static int handle_battery_call(struct msm_rpc_server *server, + struct rpc_request_hdr *req, unsigned len) +{ + switch (req->procedure) { + case RPC_BATT_MTOA_NULL: + return 0; + + case RPC_BATT_MTOA_SET_CHARGING_PROC: { + struct rpc_batt_mtoa_set_charging_args *args; + args = (struct rpc_batt_mtoa_set_charging_args *)(req + 1); + args->enable = be32_to_cpu(args->enable); + BATT("set_charging: enable=%d\n",args->enable); + htc_battery_set_charging(args->enable); + return 0; + } + case RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC: { + struct rpc_batt_mtoa_cable_status_update_args *args; + args = (struct rpc_batt_mtoa_cable_status_update_args *)(req + 1); + args->status = be32_to_cpu(args->status); + BATT("cable_status_update: status=%d\n",args->status); + htc_cable_status_update(args->status); + return 0; + } + case RPC_BATT_MTOA_LEVEL_UPDATE_PROC: { + struct rpc_dem_battery_update_args *args; + args = (struct rpc_dem_battery_update_args *)(req + 1); + args->level = be32_to_cpu(args->level); + BATT("dem_battery_update: level=%d\n",args->level); + htc_battery_status_update(args->level); + return 0; + } + default: + printk(KERN_ERR "%s: program 0x%08x:%d: unknown procedure %d\n", + __FUNCTION__, req->prog, req->vers, req->procedure); + return -ENODEV; + } +} + +static struct msm_rpc_server battery_server = { + .prog = BATT_MTOA_PROG, + .vers = BATT_MTOA_VERS, + .rpc_call = handle_battery_call, +}; + +static int __init htc_battery_init(void) +{ + wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present"); + mutex_init(&htc_batt_info.lock); + mutex_init(&htc_batt_info.rpc_lock); + msm_rpc_create_server(&battery_server); + platform_driver_register(&htc_battery_driver); + return 0; +} + +module_init(htc_battery_init); +MODULE_DESCRIPTION("HTC Battery Driver"); +MODULE_LICENSE("GPL"); + diff --git a/arch/arm/mach-msm/htc_headset.c b/arch/arm/mach-msm/htc_headset.c new file mode 100644 index 00000000000..a69a2e1ca5f --- /dev/null +++ b/arch/arm/mach-msm/htc_headset.c @@ -0,0 +1,1246 @@ +/* + * H2W device detection driver. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC, Inc. + * + * Authors: + * Laurence Chen + * Nick Pelly + * Thomas Tsai + * Farmer Tseng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +/* For detecting HTC 2 Wire devices, such as wired headset. + + Logically, the H2W driver is always present, and H2W state (hi->state) + indicates what is currently plugged into the H2W interface. + + When the headset is plugged in, CABLE_IN1 is pulled low. When the headset + button is pressed, CABLE_IN2 is pulled low. These two lines are shared with + the TX and RX (respectively) of UART3 - used for serial debugging. + + This headset driver keeps the CPLD configured as UART3 for as long as + possible, so that we can do serial FIQ debugging even when the kernel is + locked and this driver no longer runs. So it only configures the CPLD to + GPIO while the headset is plugged in, and for 10ms during detection work. + + Unfortunately we can't leave the CPLD as UART3 while a headset is plugged + in, UART3 is pullup on TX but the headset is pull-down, causing a 55 mA + drain on trout. + + The headset detection work involves setting CPLD to GPIO, and then pulling + CABLE_IN1 high with a stronger pullup than usual. A H2W headset will still + pull this line low, whereas other attachments such as a serial console + would get pulled up by this stronger pullup. + + Headset insertion/removal causes UEvent's to be sent, and + /sys/class/switch/h2w/state to be updated. + + Button presses are interpreted as input event (KEY_MEDIA). Button presses + are ignored if the headset is plugged in, so the buttons on 11 pin -> 3.5mm + jack adapters do not work until a headset is plugged into the adapter. This + is to avoid serial RX traffic causing spurious button press events. + + We tend to check the status of CABLE_IN1 a few more times than strictly + necessary during headset detection, to avoid spurious headset insertion + events caused by serial debugger TX traffic. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define H2WI(fmt, arg...) \ + printk(KERN_INFO "[H2W] %s " fmt "\r\n", __func__, ## arg) +#define H2WE(fmt, arg...) \ + printk(KERN_ERR "[H2W] %s " fmt "\r\n", __func__, ## arg) + +#ifdef CONFIG_DEBUG_H2W +#define H2W_DBG(fmt, arg...) printk(KERN_INFO "[H2W] %s " fmt "\r\n", __func__, ## arg) +#else +#define H2W_DBG(fmt, arg...) do {} while (0) +#endif + +static struct workqueue_struct *g_detection_work_queue; +static void detection_work(struct work_struct *work); +static DECLARE_WORK(g_detection_work, detection_work); + +struct h2w_info { + struct switch_dev sdev; + struct input_dev *input; + struct mutex mutex_lock; + + atomic_t btn_state; + int ignore_btn; + + unsigned int irq; + unsigned int irq_btn; + + int cable_in1; + int cable_in2; + int h2w_clk; + int h2w_data; + int debug_uart; + + void (*config_cpld) (int); + void (*init_cpld) (void); + /* for h2w */ + void (*set_dat)(int); + void (*set_clk)(int); + void (*set_dat_dir)(int); + void (*set_clk_dir)(int); + int (*get_dat)(void); + int (*get_clk)(void); + + int htc_headset_flag; + + struct hrtimer timer; + ktime_t debounce_time; + + struct hrtimer btn_timer; + ktime_t btn_debounce_time; + + H2W_INFO h2w_info; + H2W_SPEED speed; + struct vreg *vreg_h2w; +}; +static struct h2w_info *hi; + +static ssize_t h2w_print_name(struct switch_dev *sdev, char *buf) +{ + switch (switch_get_state(&hi->sdev)) { + case H2W_NO_DEVICE: + return sprintf(buf, "No Device\n"); + case H2W_HTC_HEADSET: + return sprintf(buf, "Headset\n"); + } + return -EINVAL; +} + +static void button_pressed(void) +{ + H2W_DBG("button_pressed \n"); + atomic_set(&hi->btn_state, 1); + input_report_key(hi->input, KEY_MEDIA, 1); + input_sync(hi->input); +} + +static void button_released(void) +{ + H2W_DBG("button_released \n"); + atomic_set(&hi->btn_state, 0); + input_report_key(hi->input, KEY_MEDIA, 0); + input_sync(hi->input); +} + +/***************** + * H2W proctocol * + *****************/ +static inline void h2w_begin_command(void) +{ + /* Disable H2W interrupt */ + set_irq_type(hi->irq_btn, IRQF_TRIGGER_HIGH); + disable_irq(hi->irq); + disable_irq(hi->irq_btn); + + /* Set H2W_CLK as output low */ + hi->set_clk(0); + hi->set_clk_dir(1); +} + +static inline void h2w_end_command(void) +{ + /* Set H2W_CLK as input */ + hi->set_clk_dir(0); + + /* Enable H2W interrupt */ + enable_irq(hi->irq); + enable_irq(hi->irq_btn); + set_irq_type(hi->irq_btn, IRQF_TRIGGER_RISING); +} + +/* + * One bit write data + * ________ + * SCLK O ______| |______O(L) + * + * + * SDAT I + */ +static inline void one_clock_write(unsigned short flag) +{ + if (flag) + hi->set_dat(1); + else + hi->set_dat(0); + + udelay(hi->speed); + hi->set_clk(1); + udelay(hi->speed); + hi->set_clk(0); +} + +/* + * One bit write data R/W bit + * ________ + * SCLK ______| |______O(L) + * 1----> 1-----> + * 2-------> ______ + * SDAT I + * O(H/L) + */ +static inline void one_clock_write_RWbit(unsigned short flag) +{ + if (flag) + hi->set_dat(1); + else + hi->set_dat(0); + + udelay(hi->speed); + hi->set_clk(1); + udelay(hi->speed); + hi->set_clk(0); + hi->set_dat_dir(0); + udelay(hi->speed); +} + +/* + * H2W Reset + * ___________ + * SCLK O(L)______| |___O(L) + * 1----> + * 4-->1-->1-->1us--> + * ____ + * SDAT O(L)________ | |_______O(L) + * + * H2w reset command needs to be issued before every access + */ +static inline void h2w_reset(void) +{ + /* Set H2W_DAT as output low */ + hi->set_dat(0); + hi->set_dat_dir(1); + + udelay(hi->speed); + hi->set_clk(1); + udelay(4 * hi->speed); + hi->set_dat(1); + udelay(hi->speed); + hi->set_dat(0); + udelay(hi->speed); + hi->set_clk(0); + udelay(hi->speed); +} + +/* + * H2W Start + * ___________ + * SCLK O(L)______| |___O(L) + * 1----> + * 2----------->1--> + * + * SDAT O(L)______________________O(L) + */ +static inline void h2w_start(void) +{ + udelay(hi->speed); + hi->set_clk(1); + udelay(2 * hi->speed); + hi->set_clk(0); + udelay(hi->speed); +} + +/* + * H2W Ack + * __________ + * SCLK _____| |_______O(L) + * 1----> 1------> + * 2---------> + * ________________________ + * SDAT become Input mode here I + */ +static inline int h2w_ack(void) +{ + int retry_times = 0; + +ack_resend: + if (retry_times == MAX_ACK_RESEND_TIMES) + return -1; + + udelay(hi->speed); + hi->set_clk(1); + udelay(2 * hi->speed); + + if (!hi->get_dat()) { + retry_times++; + hi->set_clk(0); + udelay(hi->speed); + goto ack_resend; + } + + hi->set_clk(0); + udelay(hi->speed); + return 0; +} + +/* + * One bit read data + * ________ + * SCLK ______| |______O(L) + * 2----> 2-----> + * 2-------> + * SDAT I + */ +static unsigned char h2w_readc(void) +{ + unsigned char h2w_read_data = 0x0; + int index; + + for (index = 0; index < 8; index++) { + hi->set_clk(0); + udelay(hi->speed); + hi->set_clk(1); + udelay(hi->speed); + if (hi->get_dat()) + h2w_read_data |= (1 << (7 - index)); + } + hi->set_clk(0); + udelay(hi->speed); + + return h2w_read_data; +} + +static int h2w_readc_cmd(H2W_ADDR address) +{ + int ret = -1, retry_times = 0; + unsigned char read_data; + +read_resend: + if (retry_times == MAX_HOST_RESEND_TIMES) + goto err_read; + + h2w_reset(); + h2w_start(); + /* Write address */ + one_clock_write(address & 0x1000); + one_clock_write(address & 0x0800); + one_clock_write(address & 0x0400); + one_clock_write(address & 0x0200); + one_clock_write(address & 0x0100); + one_clock_write(address & 0x0080); + one_clock_write(address & 0x0040); + one_clock_write(address & 0x0020); + one_clock_write(address & 0x0010); + one_clock_write(address & 0x0008); + one_clock_write(address & 0x0004); + one_clock_write(address & 0x0002); + one_clock_write(address & 0x0001); + one_clock_write_RWbit(1); + if (h2w_ack() < 0) { + H2W_DBG("Addr NO ACK(%d).\n", retry_times); + retry_times++; + hi->set_clk(0); + mdelay(RESEND_DELAY); + goto read_resend; + } + + read_data = h2w_readc(); + + if (h2w_ack() < 0) { + H2W_DBG("Data NO ACK(%d).\n", retry_times); + retry_times++; + hi->set_clk(0); + mdelay(RESEND_DELAY); + goto read_resend; + } + ret = (int)read_data; + +err_read: + if (ret < 0) + H2WE("NO ACK.\n"); + + return ret; +} + +static int h2w_writec_cmd(H2W_ADDR address, unsigned char data) +{ + int ret = -1; + int retry_times = 0; + +write_resend: + if (retry_times == MAX_HOST_RESEND_TIMES) + goto err_write; + + h2w_reset(); + h2w_start(); + + /* Write address */ + one_clock_write(address & 0x1000); + one_clock_write(address & 0x0800); + one_clock_write(address & 0x0400); + one_clock_write(address & 0x0200); + one_clock_write(address & 0x0100); + one_clock_write(address & 0x0080); + one_clock_write(address & 0x0040); + one_clock_write(address & 0x0020); + one_clock_write(address & 0x0010); + one_clock_write(address & 0x0008); + one_clock_write(address & 0x0004); + one_clock_write(address & 0x0002); + one_clock_write(address & 0x0001); + one_clock_write_RWbit(0); + if (h2w_ack() < 0) { + H2W_DBG("Addr NO ACK(%d).\n", retry_times); + retry_times++; + hi->set_clk(0); + mdelay(RESEND_DELAY); + goto write_resend; + } + + /* Write data */ + hi->set_dat_dir(1); + one_clock_write(data & 0x0080); + one_clock_write(data & 0x0040); + one_clock_write(data & 0x0020); + one_clock_write(data & 0x0010); + one_clock_write(data & 0x0008); + one_clock_write(data & 0x0004); + one_clock_write(data & 0x0002); + one_clock_write_RWbit(data & 0x0001); + if (h2w_ack() < 0) { + H2W_DBG("Data NO ACK(%d).\n", retry_times); + retry_times++; + hi->set_clk(0); + mdelay(RESEND_DELAY); + goto write_resend; + } + ret = 0; + +err_write: + if (ret < 0) + H2WE("NO ACK.\n"); + + return ret; +} + +static int h2w_get_fnkey(void) +{ + int ret; + h2w_begin_command(); + ret = h2w_readc_cmd(H2W_FNKEY_UPDOWN); + h2w_end_command(); + return ret; +} + +static int h2w_dev_init(H2W_INFO *ph2w_info) +{ + int ret = -1; + unsigned char ascr0 = 0; + int h2w_sys = 0, maxgpadd = 0, maxadd = 0, key = 0; + + hi->speed = H2W_50KHz; + h2w_begin_command(); + + /* read H2W_SYSTEM */ + h2w_sys = h2w_readc_cmd(H2W_SYSTEM); + if (h2w_sys == -1) { + H2WE("read H2W_SYSTEM(0x0000) failed.\n"); + goto err_plugin; + } + ph2w_info->ACC_CLASS = (h2w_sys & 0x03); + ph2w_info->AUDIO_DEVICE = (h2w_sys & 0x04) > 0 ? 1 : 0; + ph2w_info->HW_REV = (h2w_sys & 0x18) >> 3; + ph2w_info->SLEEP_PR = (h2w_sys & 0x20) >> 5; + ph2w_info->CLK_SP = (h2w_sys & 0xC0) >> 6; + + /* enter init mode */ + if (h2w_writec_cmd(H2W_ASCR0, H2W_ASCR_DEVICE_INI) < 0) { + H2WE("write H2W_ASCR0(0x0002) failed.\n"); + goto err_plugin; + } + udelay(10); + + /* read H2W_MAX_GP_ADD */ + maxgpadd = h2w_readc_cmd(H2W_MAX_GP_ADD); + if (maxgpadd == -1) { + H2WE("write H2W_MAX_GP_ADD(0x0001) failed.\n"); + goto err_plugin; + } + ph2w_info->CLK_SP += (maxgpadd & 0x60) >> 3; + ph2w_info->MAX_GP_ADD = (maxgpadd & 0x1F); + + /* read key group */ + if (ph2w_info->MAX_GP_ADD >= 1) { + ph2w_info->KEY_MAXADD = h2w_readc_cmd(H2W_KEY_MAXADD); + if (ph2w_info->KEY_MAXADD == -1) + goto err_plugin; + if (ph2w_info->KEY_MAXADD >= 1) { + key = h2w_readc_cmd(H2W_ASCII_DOWN); + if (key < 0) + goto err_plugin; + ph2w_info->ASCII_DOWN = (key == 0xFF) ? 1 : 0; + } + if (ph2w_info->KEY_MAXADD >= 2) { + key = h2w_readc_cmd(H2W_ASCII_UP); + if (key == -1) + goto err_plugin; + ph2w_info->ASCII_UP = (key == 0xFF) ? 1 : 0; + } + if (ph2w_info->KEY_MAXADD >= 3) { + key = h2w_readc_cmd(H2W_FNKEY_UPDOWN); + if (key == -1) + goto err_plugin; + ph2w_info->FNKEY_UPDOWN = (key == 0xFF) ? 1 : 0; + } + if (ph2w_info->KEY_MAXADD >= 4) { + key = h2w_readc_cmd(H2W_KD_STATUS); + if (key == -1) + goto err_plugin; + ph2w_info->KD_STATUS = (key == 0x01) ? 1 : 0; + } + } + + /* read led group */ + if (ph2w_info->MAX_GP_ADD >= 2) { + ph2w_info->LED_MAXADD = h2w_readc_cmd(H2W_LED_MAXADD); + if (ph2w_info->LED_MAXADD == -1) + goto err_plugin; + if (ph2w_info->LED_MAXADD >= 1) { + key = h2w_readc_cmd(H2W_LEDCT0); + if (key == -1) + goto err_plugin; + ph2w_info->LEDCT0 = (key == 0x02) ? 1 : 0; + } + } + + /* read group 3, 4, 5 */ + if (ph2w_info->MAX_GP_ADD >= 3) { + maxadd = h2w_readc_cmd(H2W_CRDL_MAXADD); + if (maxadd == -1) + goto err_plugin; + } + if (ph2w_info->MAX_GP_ADD >= 4) { + maxadd = h2w_readc_cmd(H2W_CARKIT_MAXADD); + if (maxadd == -1) + goto err_plugin; + } + if (ph2w_info->MAX_GP_ADD >= 5) { + maxadd = h2w_readc_cmd(H2W_USBHOST_MAXADD); + if (maxadd == -1) + goto err_plugin; + } + + /* read medical group */ + if (ph2w_info->MAX_GP_ADD >= 6) { + ph2w_info->MED_MAXADD = h2w_readc_cmd(H2W_MED_MAXADD); + if (ph2w_info->MED_MAXADD == -1) + goto err_plugin; + if (ph2w_info->MED_MAXADD >= 1) { + key = h2w_readc_cmd(H2W_MED_CONTROL); + if (key == -1) + goto err_plugin; + ph2w_info->DATA_EN = (key & 0x01); + ph2w_info->AP_EN = (key & 0x02) >> 1; + ph2w_info->AP_ID = (key & 0x1c) >> 2; + } + if (ph2w_info->MED_MAXADD >= 2) { + key = h2w_readc_cmd(H2W_MED_IN_DATA); + if (key == -1) + goto err_plugin; + } + } + + if (ph2w_info->AUDIO_DEVICE) + ascr0 = H2W_ASCR_AUDIO_IN | H2W_ASCR_ACT_EN; + else + ascr0 = H2W_ASCR_ACT_EN; + + if (h2w_writec_cmd(H2W_ASCR0, ascr0) < 0) + goto err_plugin; + udelay(10); + + ret = 0; + + /* adjust speed */ + if (ph2w_info->MAX_GP_ADD == 2) { + /* Remote control */ + hi->speed = H2W_250KHz; + } else if (ph2w_info->MAX_GP_ADD == 6) { + if (ph2w_info->MED_MAXADD >= 1) { + key = h2w_readc_cmd(H2W_MED_CONTROL); + if (key == -1) + goto err_plugin; + ph2w_info->DATA_EN = (key & 0x01); + ph2w_info->AP_EN = (key & 0x02) >> 1; + ph2w_info->AP_ID = (key & 0x1c) >> 2; + } + } + +err_plugin: + h2w_end_command(); + + return ret; +} + +static inline void h2w_dev_power_on(int on) +{ + if (!hi->vreg_h2w) + return; + + if (on) + vreg_enable(hi->vreg_h2w); + else + vreg_disable(hi->vreg_h2w); +} + +static int h2w_dev_detect(void) +{ + int ret = -1; + int retry_times; + + for (retry_times = 5; retry_times; retry_times--) { + /* Enable H2W Power */ + h2w_dev_power_on(1); + msleep(100); + memset(&hi->h2w_info, 0, sizeof(H2W_INFO)); + if (h2w_dev_init(&hi->h2w_info) < 0) { + h2w_dev_power_on(0); + msleep(100); + } else if (hi->h2w_info.MAX_GP_ADD == 2) { + ret = 0; + break; + } else { + printk(KERN_INFO "h2w_detect: detect error(%d)\n" + , hi->h2w_info.MAX_GP_ADD); + h2w_dev_power_on(0); + msleep(100); + } + printk(KERN_INFO "h2w_detect(%d)\n" + , hi->h2w_info.MAX_GP_ADD); + } + H2W_DBG("h2w_detect:(%d)\n", retry_times); + return ret; +} + +static void remove_headset(void) +{ + unsigned long irq_flags; + + H2W_DBG(""); + + mutex_lock(&hi->mutex_lock); + switch_set_state(&hi->sdev, switch_get_state(&hi->sdev) & + ~(BIT_HEADSET | BIT_HEADSET_NO_MIC)); + mutex_unlock(&hi->mutex_lock); + hi->init_cpld(); + + /* Disable button */ + switch (hi->htc_headset_flag) { + case H2W_HTC_HEADSET: + local_irq_save(irq_flags); + disable_irq(hi->irq_btn); + local_irq_restore(irq_flags); + + if (atomic_read(&hi->btn_state)) + button_released(); + break; + case H2W_DEVICE: + h2w_dev_power_on(0); + set_irq_type(hi->irq_btn, IRQF_TRIGGER_LOW); + disable_irq(hi->irq_btn); + /* 10ms (5-15 with 10ms tick) */ + hi->btn_debounce_time = ktime_set(0, 10000000); + hi->set_clk_dir(0); + hi->set_dat_dir(0); + break; + } + + hi->htc_headset_flag = 0; + hi->debounce_time = ktime_set(0, 100000000); /* 100 ms */ + +} + +#ifdef CONFIG_MSM_SERIAL_DEBUGGER +extern void msm_serial_debug_enable(int); +#endif + +static void insert_headset(int type) +{ + unsigned long irq_flags; + int state; + + H2W_DBG(""); + + hi->htc_headset_flag = type; + state = BIT_HEADSET | BIT_HEADSET_NO_MIC; + + state = switch_get_state(&hi->sdev); + state &= ~(BIT_HEADSET_NO_MIC | BIT_HEADSET); + switch (type) { + case H2W_HTC_HEADSET: + printk(KERN_INFO "insert_headset H2W_HTC_HEADSET\n"); + state |= BIT_HEADSET; + hi->ignore_btn = !gpio_get_value(hi->cable_in2); + /* Enable button irq */ + local_irq_save(irq_flags); + enable_irq(hi->irq_btn); + local_irq_restore(irq_flags); + hi->debounce_time = ktime_set(0, 200000000); /* 20 ms */ + break; + case H2W_DEVICE: + if (h2w_dev_detect() < 0) { + printk(KERN_INFO "H2W_DEVICE -- Non detect\n"); + remove_headset(); + } else { + printk(KERN_INFO "H2W_DEVICE -- detect\n"); + hi->btn_debounce_time = ktime_set(0, 0); + local_irq_save(irq_flags); + enable_irq(hi->irq_btn); + set_irq_type(hi->irq_btn, IRQF_TRIGGER_RISING); + local_irq_restore(irq_flags); + state |= BIT_HEADSET; + } + break; + case H2W_USB_CRADLE: + state |= BIT_HEADSET_NO_MIC; + break; + case H2W_UART_DEBUG: + hi->config_cpld(hi->debug_uart); + printk(KERN_INFO "switch to H2W_UART_DEBUG\n"); + default: + return; + } + mutex_lock(&hi->mutex_lock); + switch_set_state(&hi->sdev, state); + mutex_unlock(&hi->mutex_lock); + +#ifdef CONFIG_MSM_SERIAL_DEBUGGER + msm_serial_debug_enable(false); +#endif + +} +#if 0 +static void remove_headset(void) +{ + unsigned long irq_flags; + + H2W_DBG(""); + + switch_set_state(&hi->sdev, H2W_NO_DEVICE); + + hi->init_cpld(); + + /* Disable button */ + local_irq_save(irq_flags); + disable_irq(hi->irq_btn); + local_irq_restore(irq_flags); + + if (atomic_read(&hi->btn_state)) + button_released(); + + hi->debounce_time = ktime_set(0, 100000000); /* 100 ms */ +} +#endif +static int is_accessary_pluged_in(void) +{ + int type = 0; + int clk1 = 0, dat1 = 0, clk2 = 0, dat2 = 0, clk3 = 0, dat3 = 0; + + /* Step1: save H2W_CLK and H2W_DAT */ + /* Delay 10ms for pin stable. */ + msleep(10); + clk1 = gpio_get_value(hi->h2w_clk); + dat1 = gpio_get_value(hi->h2w_data); + + /* + * Step2: set GPIO_CABLE_IN1 as output high and GPIO_CABLE_IN2 as + * input + */ + gpio_direction_output(hi->cable_in1, 1); + gpio_direction_input(hi->cable_in2); + /* Delay 10ms for pin stable. */ + msleep(10); + /* Step 3: save H2W_CLK and H2W_DAT */ + clk2 = gpio_get_value(hi->h2w_clk); + dat2 = gpio_get_value(hi->h2w_data); + + /* + * Step 4: set GPIO_CABLE_IN1 as input and GPIO_CABLE_IN2 as output + * high + */ + gpio_direction_input(hi->cable_in1); + gpio_direction_output(hi->cable_in2, 1); + /* Delay 10ms for pin stable. */ + msleep(10); + /* Step 5: save H2W_CLK and H2W_DAT */ + clk3 = gpio_get_value(hi->h2w_clk); + dat3 = gpio_get_value(hi->h2w_data); + + /* Step 6: set both GPIO_CABLE_IN1 and GPIO_CABLE_IN2 as input */ + gpio_direction_input(hi->cable_in1); + gpio_direction_input(hi->cable_in2); + + H2W_DBG("(%d,%d) (%d,%d) (%d,%d)\n", + clk1, dat1, clk2, dat2, clk3, dat3); + + if ((clk1 == 0) && (dat1 == 1) && + (clk2 == 0) && (dat2 == 1) && + (clk3 == 0) && (dat3 == 1)) + type = H2W_HTC_HEADSET; + else if ((clk1 == 0) && (dat1 == 0) && + (clk2 == 0) && (dat2 == 0) && + (clk3 == 0) && (dat3 == 0)) + type = NORMAL_HEARPHONE; + else if ((clk1 == 0) && (dat1 == 0) && + (clk2 == 1) && (dat2 == 0) && + (clk3 == 0) && (dat3 == 1)) + type = H2W_DEVICE; + else if ((clk1 == 0) && (dat1 == 0) && + (clk2 == 1) && (dat2 == 1) && + (clk3 == 1) && (dat3 == 1)) + type = H2W_USB_CRADLE; + else if ((clk1 == 0) && (dat1 == 1) && + (clk2 == 1) && (dat2 == 1) && + (clk3 == 0) && (dat3 == 1)) + type = H2W_UART_DEBUG; + else + type = H2W_NO_DEVICE; + + return type; +} + + +static void detection_work(struct work_struct *work) +{ + unsigned long irq_flags; + int type; + + H2W_DBG(""); + + if (gpio_get_value(hi->cable_in1) != 0) { + /* Headset not plugged in */ + if (switch_get_state(&hi->sdev) != H2W_NO_DEVICE) + remove_headset(); + return; + } + + /* Something plugged in, lets make sure its a headset */ + + /* Switch CPLD to GPIO to do detection */ + hi->config_cpld(H2W_GPIO); + + /* Disable headset interrupt while detecting.*/ + local_irq_save(irq_flags); + disable_irq(hi->irq); + local_irq_restore(irq_flags); + + /* Something plugged in, lets make sure its a headset */ + type = is_accessary_pluged_in(); + + /* Restore IRQs */ + local_irq_save(irq_flags); + enable_irq(hi->irq); + local_irq_restore(irq_flags); + + insert_headset(type); +} + +static enum hrtimer_restart button_event_timer_func(struct hrtimer *data) +{ + int key, press, keyname, h2w_key = 1; + + H2W_DBG(""); + + if (switch_get_state(&hi->sdev) == H2W_HTC_HEADSET) { + switch (hi->htc_headset_flag) { + case H2W_HTC_HEADSET: + if (gpio_get_value(hi->cable_in2)) { + if (hi->ignore_btn) + hi->ignore_btn = 0; + else if (atomic_read(&hi->btn_state)) + button_released(); + } else { + if (!hi->ignore_btn && + !atomic_read(&hi->btn_state)) + button_pressed(); + } + break; + case H2W_DEVICE: + if ((hi->get_dat() == 1) && (hi->get_clk() == 1)) { + /* Don't do anything because H2W pull out. */ + H2WE("Remote Control pull out.\n"); + } else { + key = h2w_get_fnkey(); + press = (key > 0x7F) ? 0 : 1; + keyname = key & 0x7F; + /* H2WI("key = %d, press = %d, + keyname = %d \n", + key, press, keyname); */ + switch (keyname) { + case H2W_KEY_PLAY: + H2WI("H2W_KEY_PLAY"); + key = KEY_PLAYPAUSE; + break; + case H2W_KEY_FORWARD: + H2WI("H2W_KEY_FORWARD"); + key = KEY_NEXTSONG; + break; + case H2W_KEY_BACKWARD: + H2WI("H2W_KEY_BACKWARD"); + key = KEY_PREVIOUSSONG; + break; + case H2W_KEY_VOLUP: + H2WI("H2W_KEY_VOLUP"); + key = KEY_VOLUMEUP; + break; + case H2W_KEY_VOLDOWN: + H2WI("H2W_KEY_VOLDOWN"); + key = KEY_VOLUMEDOWN; + break; + case H2W_KEY_PICKUP: + H2WI("H2W_KEY_PICKUP"); + key = KEY_SEND; + break; + case H2W_KEY_HANGUP: + H2WI("H2W_KEY_HANGUP"); + key = KEY_END; + break; + case H2W_KEY_MUTE: + H2WI("H2W_KEY_MUTE"); + key = KEY_MUTE; + break; + case H2W_KEY_HOLD: + H2WI("H2W_KEY_HOLD"); + break; + default: + H2WI("default"); + h2w_key = 0; + } + if (h2w_key) { + if (press) + H2WI("Press\n"); + else + H2WI("Release\n"); + input_report_key(hi->input, key, press); + } + } + break; + } /* end switch */ + } + + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart detect_event_timer_func(struct hrtimer *data) +{ + H2W_DBG(""); + + queue_work(g_detection_work_queue, &g_detection_work); + return HRTIMER_NORESTART; +} + +static irqreturn_t detect_irq_handler(int irq, void *dev_id) +{ + int value1, value2; + int retry_limit = 10; + + H2W_DBG(""); + set_irq_type(hi->irq_btn, IRQF_TRIGGER_LOW); + do { + value1 = gpio_get_value(hi->cable_in1); + set_irq_type(hi->irq, value1 ? + IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); + value2 = gpio_get_value(hi->cable_in1); + } while (value1 != value2 && retry_limit-- > 0); + + H2W_DBG("value2 = %d (%d retries), device=%d", + value2, (10-retry_limit), switch_get_state(&hi->sdev)); + + if ((switch_get_state(&hi->sdev) == H2W_NO_DEVICE) ^ value2) { + if (switch_get_state(&hi->sdev) == H2W_HTC_HEADSET) + hi->ignore_btn = 1; + /* Do the rest of the work in timer context */ + hrtimer_start(&hi->timer, hi->debounce_time, HRTIMER_MODE_REL); + } + + return IRQ_HANDLED; +} + +static irqreturn_t button_irq_handler(int irq, void *dev_id) +{ + int value1, value2; + int retry_limit = 10; + + H2W_DBG(""); + do { + value1 = gpio_get_value(hi->cable_in2); + if (hi->htc_headset_flag != H2W_DEVICE) + set_irq_type(hi->irq_btn, value1 ? + IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); + value2 = gpio_get_value(hi->cable_in2); + } while (value1 != value2 && retry_limit-- > 0); + + H2W_DBG("value2 = %d (%d retries)", value2, (10-retry_limit)); + + hrtimer_start(&hi->btn_timer, hi->btn_debounce_time, HRTIMER_MODE_REL); + + return IRQ_HANDLED; +} + +#if defined(CONFIG_DEBUG_FS) +static int h2w_debug_set(void *data, u64 val) +{ + mutex_lock(&hi->mutex_lock); + switch_set_state(&hi->sdev, (int)val); + mutex_unlock(&hi->mutex_lock); + return 0; +} + +static int h2w_debug_get(void *data, u64 *val) +{ + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(h2w_debug_fops, h2w_debug_get, h2w_debug_set, "%llu\n"); +static int __init h2w_debug_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("h2w", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debugfs_create_file("state", 0644, dent, NULL, &h2w_debug_fops); + + return 0; +} + +device_initcall(h2w_debug_init); +#endif + +static int h2w_probe(struct platform_device *pdev) +{ + int ret; + struct h2w_platform_data *pdata = pdev->dev.platform_data; + + printk(KERN_INFO "H2W: Registering H2W (headset) driver\n"); + hi = kzalloc(sizeof(struct h2w_info), GFP_KERNEL); + if (!hi) + return -ENOMEM; + + atomic_set(&hi->btn_state, 0); + hi->ignore_btn = 0; + + hi->debounce_time = ktime_set(0, 100000000); /* 100 ms */ + hi->btn_debounce_time = ktime_set(0, 10000000); /* 10 ms */ + + hi->htc_headset_flag = 0; + hi->cable_in1 = pdata->cable_in1; + hi->cable_in2 = pdata->cable_in2; + hi->h2w_clk = pdata->h2w_clk; + hi->h2w_data = pdata->h2w_data; + hi->debug_uart = pdata->debug_uart; + hi->config_cpld = pdata->config_cpld; + hi->init_cpld = pdata->init_cpld; + hi->set_dat = pdata->set_dat; + hi->set_clk = pdata->set_clk; + hi->set_dat_dir = pdata->set_dat_dir; + hi->set_clk_dir = pdata->set_clk_dir; + hi->get_dat = pdata->get_dat; + hi->get_clk = pdata->get_clk; + hi->speed = H2W_50KHz; + /* obtain needed VREGs */ + if (pdata->power_name) + hi->vreg_h2w = vreg_get(0, pdata->power_name); + + mutex_init(&hi->mutex_lock); + + hi->sdev.name = "h2w"; + hi->sdev.print_name = h2w_print_name; + + ret = switch_dev_register(&hi->sdev); + if (ret < 0) + goto err_switch_dev_register; + + g_detection_work_queue = create_workqueue("detection"); + if (g_detection_work_queue == NULL) { + ret = -ENOMEM; + goto err_create_work_queue; + } + + ret = gpio_request(hi->cable_in1, "h2w_detect"); + if (ret < 0) + goto err_request_detect_gpio; + + ret = gpio_request(hi->cable_in2, "h2w_button"); + if (ret < 0) + goto err_request_button_gpio; + + ret = gpio_direction_input(hi->cable_in1); + if (ret < 0) + goto err_set_detect_gpio; + + ret = gpio_direction_input(hi->cable_in2); + if (ret < 0) + goto err_set_button_gpio; + + hi->irq = gpio_to_irq(hi->cable_in1); + if (hi->irq < 0) { + ret = hi->irq; + goto err_get_h2w_detect_irq_num_failed; + } + + hi->irq_btn = gpio_to_irq(hi->cable_in2); + if (hi->irq_btn < 0) { + ret = hi->irq_btn; + goto err_get_button_irq_num_failed; + } + + /* Set CPLD MUX to H2W <-> CPLD GPIO */ + hi->init_cpld(); + + hrtimer_init(&hi->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hi->timer.function = detect_event_timer_func; + hrtimer_init(&hi->btn_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hi->btn_timer.function = button_event_timer_func; + + ret = request_irq(hi->irq, detect_irq_handler, + IRQF_TRIGGER_LOW, "h2w_detect", NULL); + if (ret < 0) + goto err_request_detect_irq; + + /* Disable button until plugged in */ + set_irq_flags(hi->irq_btn, IRQF_VALID | IRQF_NOAUTOEN); + ret = request_irq(hi->irq_btn, button_irq_handler, + IRQF_TRIGGER_LOW, "h2w_button", NULL); + if (ret < 0) + goto err_request_h2w_headset_button_irq; + + ret = set_irq_wake(hi->irq, 1); + if (ret < 0) + goto err_request_input_dev; + + ret = set_irq_wake(hi->irq_btn, 1); + if (ret < 0) + goto err_request_input_dev; + + + + hi->input = input_allocate_device(); + if (!hi->input) { + ret = -ENOMEM; + goto err_request_input_dev; + } + + hi->input->name = "h2w headset"; + set_bit(EV_SYN, hi->input->evbit); + set_bit(EV_KEY, hi->input->evbit); + set_bit(KEY_MEDIA, hi->input->keybit); + set_bit(KEY_NEXTSONG, hi->input->keybit); + set_bit(KEY_PLAYPAUSE, hi->input->keybit); + set_bit(KEY_PREVIOUSSONG, hi->input->keybit); + set_bit(KEY_MUTE, hi->input->keybit); + set_bit(KEY_VOLUMEUP, hi->input->keybit); + set_bit(KEY_VOLUMEDOWN, hi->input->keybit); + set_bit(KEY_END, hi->input->keybit); + set_bit(KEY_SEND, hi->input->keybit); + + ret = input_register_device(hi->input); + if (ret < 0) + goto err_register_input_dev; + + return 0; + +err_register_input_dev: + input_free_device(hi->input); +err_request_input_dev: + free_irq(hi->irq_btn, 0); +err_request_h2w_headset_button_irq: + free_irq(hi->irq, 0); +err_request_detect_irq: +err_get_button_irq_num_failed: +err_get_h2w_detect_irq_num_failed: +err_set_button_gpio: +err_set_detect_gpio: + gpio_free(hi->cable_in2); +err_request_button_gpio: + gpio_free(hi->cable_in1); +err_request_detect_gpio: + destroy_workqueue(g_detection_work_queue); +err_create_work_queue: + switch_dev_unregister(&hi->sdev); +err_switch_dev_register: + printk(KERN_ERR "H2W: Failed to register driver\n"); + + return ret; +} + +static int h2w_remove(struct platform_device *pdev) +{ + H2W_DBG(""); + if (switch_get_state(&hi->sdev)) + remove_headset(); + input_unregister_device(hi->input); + gpio_free(hi->cable_in2); + gpio_free(hi->cable_in1); + free_irq(hi->irq_btn, 0); + free_irq(hi->irq, 0); + destroy_workqueue(g_detection_work_queue); + switch_dev_unregister(&hi->sdev); + + return 0; +} + + +static struct platform_driver h2w_driver = { + .probe = h2w_probe, + .remove = h2w_remove, + .driver = { + .name = "h2w", + .owner = THIS_MODULE, + }, +}; + +static int __init h2w_init(void) +{ + H2W_DBG(""); + return platform_driver_register(&h2w_driver); +} + +static void __exit h2w_exit(void) +{ + platform_driver_unregister(&h2w_driver); +} + +module_init(h2w_init); +module_exit(h2w_exit); + +MODULE_AUTHOR("Laurence Chen "); +MODULE_DESCRIPTION("HTC 2 Wire detection driver"); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/htc_power_supply.c b/arch/arm/mach-msm/htc_power_supply.c new file mode 100644 index 00000000000..bd286c9f3a8 --- /dev/null +++ b/arch/arm/mach-msm/htc_power_supply.c @@ -0,0 +1,616 @@ +/* arch/arm/mach-msm/htc_battery.c + * + * Copyright (C) 2008 HTC Corporation. + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "board-mahimahi.h" + +extern void notify_usb_connected(int); + +static char *supply_list[] = { + "battery", +}; + +static struct switch_dev dock_switch = { + .name = "dock", +}; + +static int vbus_present; +static int usb_status; +static bool dock_mains; + +struct dock_state { + struct mutex lock; + u32 t; + u32 last_edge_t[2]; + u32 last_edge_i[2]; + bool level; + bool dock_connected_unknown; +}; + +static struct workqueue_struct *dock_wq; +static struct work_struct dock_work; +static struct wake_lock dock_work_wake_lock; +static struct dock_state ds = { + .lock = __MUTEX_INITIALIZER(ds.lock), +}; + +#define _GPIO_DOCK MAHIMAHI_GPIO_DOCK + +#define dock_out(n) gpio_direction_output(_GPIO_DOCK, n) +#define dock_out2(n) gpio_set_value(_GPIO_DOCK, n) +#define dock_in() gpio_direction_input(_GPIO_DOCK) +#define dock_read() gpio_get_value(_GPIO_DOCK) + +#define MFM_DELAY_NS 10000 + +static int dock_get_edge(struct dock_state *s, u32 timeout, u32 tmin, u32 tmax) +{ + bool lin; + bool in = s->level; + u32 t; + do { + lin = in; + in = dock_read(); + t = msm_read_fast_timer(); + if (in != lin) { + s->last_edge_t[in] = t; + s->last_edge_i[in] = 0; + s->level = in; + if ((s32)(t - tmin) < 0 || (s32)(t - tmax) > 0) + return -1; + return 1; + } + } while((s32)(t - timeout) < 0); + return 0; +} + +static bool dock_sync(struct dock_state *s, u32 timeout) +{ + u32 t; + + s->level = dock_read(); + t = msm_read_fast_timer(); + + if (!dock_get_edge(s, t + timeout, 0, 0)) + return false; + s->last_edge_i[s->level] = 2; + return !!dock_get_edge(s, + s->last_edge_t[s->level] + MFM_DELAY_NS * 4, 0, 0); +} + +static int dock_get_next_bit(struct dock_state *s) +{ + u32 i = s->last_edge_i[!s->level] + ++s->last_edge_i[s->level]; + u32 target = s->last_edge_t[!s->level] + MFM_DELAY_NS * i; + u32 timeout = target + MFM_DELAY_NS / 2; + u32 tmin = target - MFM_DELAY_NS / 4; + u32 tmax = target + MFM_DELAY_NS / 4; + return dock_get_edge(s, timeout, tmin, tmax); +} + +static u32 dock_get_bits(struct dock_state *s, int count, int *errp) +{ + u32 data = 0; + u32 m = 1; + int ret; + int err = 0; + while (count--) { + ret = dock_get_next_bit(s); + if (ret) + data |= m; + if (ret < 0) + err++; + m <<= 1; + } + if (errp) + *errp = err; + return data; +} + +static void dock_delay(u32 timeout) +{ + timeout += msm_read_fast_timer(); + while (((s32)(msm_read_fast_timer() - timeout)) < 0) + ; +} + +static int dock_send_bits(struct dock_state *s, u32 data, int count, int period) +{ + u32 t, t0, to; + + dock_out2(s->level); + t = to = 0; + t0 = msm_read_fast_timer(); + + while (count--) { + if (data & 1) + dock_out2((s->level = !s->level)); + + t = msm_read_fast_timer() - t0; + if (t - to > period / 2) { + pr_info("dock: to = %d, t = %d\n", to, t); + return -EIO; + } + + to += MFM_DELAY_NS; + do { + t = msm_read_fast_timer() - t0; + } while (t < to); + if (t - to > period / 4) { + pr_info("dock: to = %d, t = %d\n", to, t); + return -EIO; + } + data >>= 1; + } + return 0; +} + +static u32 mfm_encode(u16 data, int count, bool p) +{ + u32 mask; + u32 mfm = 0; + u32 clock = ~data & ~(data << 1 | !!p); + for (mask = 1UL << (count - 1); mask; mask >>= 1) { + mfm |= (data & mask); + mfm <<= 1; + mfm |= (clock & mask); + } + return mfm; +} + +static u32 mfm_decode(u32 mfm) +{ + u32 data = 0; + u32 clock = 0; + u32 mask = 1; + while (mfm) { + if (mfm & 1) + clock |= mask; + mfm >>= 1; + if (mfm & 1) + data |= mask; + mfm >>= 1; + mask <<= 1; + } + return data; +} + +static int dock_command(struct dock_state *s, u16 cmd, int len, int retlen) +{ + u32 mfm; + int count; + u32 data = cmd; + int ret; + int err = -1; + unsigned long flags; + + data = data << 2 | 3; /* add 0101 mfm data*/ + mfm = mfm_encode(data, len, false); + count = len * 2 + 2; + + msm_enable_fast_timer(); + local_irq_save(flags); + ret = dock_send_bits(s, mfm, count, MFM_DELAY_NS); + if (!ret) { + dock_in(); + if (dock_sync(s, MFM_DELAY_NS * 5)) + ret = dock_get_bits(s, retlen * 2, &err); + else + ret = -1; + dock_out(s->level); + } + local_irq_restore(flags); + + dock_delay((ret < 0) ? MFM_DELAY_NS * 6 : MFM_DELAY_NS * 2); + msm_disable_fast_timer(); + if (ret < 0) { + pr_warning("dock_command: %x: no response\n", cmd); + return ret; + } + data = mfm_decode(ret); + mfm = mfm_encode(data, retlen, true); + if (mfm != ret || err) { + pr_warning("dock_command: %x: bad response, " + "data %x, mfm %x %x, err %d\n", + cmd, data, mfm, ret, err); + return -EIO; + } + return data; +} + +static int dock_command_retry(struct dock_state *s, u16 cmd, size_t len, size_t retlen) +{ + int retry = 20; + int ret; + while (retry--) { + ret = dock_command(s, cmd, len, retlen); + if (ret >= 0) + return ret; + if (retry != 19) + msleep(10); + } + s->dock_connected_unknown = true; + return -EIO; +} + +static int dock_read_single(struct dock_state *s, int addr) +{ + int ret = -1, last; + int retry = 20; + while (retry--) { + last = ret; + ret = dock_command_retry(s, addr << 1, 6, 8); + if (ret < 0 || ret == last) + return ret; + } + return -EIO; +} + +static int dock_read_multi(struct dock_state *s, int addr, u8 *data, size_t len) +{ + int ret; + int i; + u8 suml, sumr = -1; + int retry = 20; + while (retry--) { + suml = 0; + for (i = 0; i <= len; i++) { + ret = dock_command_retry(s, (addr + i) << 1, 6, 8); + if (ret < 0) + return ret; + if (i < len) { + data[i] = ret; + suml += ret; + } else + sumr = ret; + } + if (sumr == suml) + return 0; + + pr_warning("dock_read_multi(%x): bad checksum, %x != %x\n", + addr, sumr, suml); + } + return -EIO; +} + +static int dock_write_byte(struct dock_state *s, int addr, u8 data) +{ + return dock_command_retry(s, 1 | addr << 1 | data << 4, 6 + 8, 1); +} + +static int dock_write_multi(struct dock_state *s, int addr, u8 *data, size_t len) +{ + int ret; + int i; + u8 sum; + int retry = 2; + while (retry--) { + sum = 0; + for (i = 0; i < len; i++) { + sum += data[i]; + ret = dock_write_byte(s, addr + i, data[i]); + if (ret < 0) + return ret; + } + ret = dock_write_byte(s, addr + len, sum); + if (ret <= 0) + return ret; + } + return -EIO; +} + +static int dock_acquire(struct dock_state *s) +{ + mutex_lock(&s->lock); + dock_in(); + if (dock_read()) { + /* Allow some time for the dock pull-down resistor to discharge + * the capasitor. + */ + msleep(20); + if (dock_read()) { + mutex_unlock(&s->lock); + return -ENOENT; + } + } + dock_out(0); + s->level = false; + return 0; +} + +static void dock_release(struct dock_state *s) +{ + dock_in(); + mutex_unlock(&s->lock); +} + +enum { + DOCK_TYPE = 0x0, + DOCK_BT_ADDR = 0x1, /* - 0x7 */ + + DOCK_PIN_CODE = 0x0, +}; + +static ssize_t bt_addr_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int ret; + u8 bt_addr[6]; + + ret = dock_acquire(&ds); + if (ret < 0) + return ret; + ret = dock_read_multi(&ds, DOCK_BT_ADDR, bt_addr, 6); + dock_release(&ds); + if (ret < 0) + return ret; + + return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + bt_addr[0], bt_addr[1], bt_addr[2], + bt_addr[3], bt_addr[4], bt_addr[5]); +} +static DEVICE_ATTR(bt_addr, S_IRUGO | S_IWUSR, bt_addr_show, NULL); + +static ssize_t bt_pin_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret, i; + u8 pin[4]; + + if (size < 4) + return -EINVAL; + + for (i = 0; i < sizeof(pin); i++) { + if ((pin[i] = buf[i] - '0') > 10) + return -EINVAL; + } + + ret = dock_acquire(&ds); + if (ret < 0) + return ret; + ret = dock_write_multi(&ds, DOCK_PIN_CODE, pin, 4); + dock_release(&ds); + if (ret < 0) + return ret; + + return size; +} +static DEVICE_ATTR(bt_pin, S_IRUGO | S_IWUSR, NULL, bt_pin_store); + + +static int power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + if (psy->type == POWER_SUPPLY_TYPE_MAINS) + val->intval = (vbus_present && (usb_status == 2 || dock_mains)); + else + val->intval = vbus_present; + return 0; +} + +static enum power_supply_property power_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static struct power_supply ac_supply = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = power_properties, + .num_properties = ARRAY_SIZE(power_properties), + .get_property = power_get_property, +}; + +static struct power_supply usb_supply = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .supplied_to = supply_list, + .num_supplicants = ARRAY_SIZE(supply_list), + .properties = power_properties, + .num_properties = ARRAY_SIZE(power_properties), + .get_property = power_get_property, +}; + +/* rpc related */ +#define APP_BATT_PDEV_NAME "rs30100001:00000000" +#define APP_BATT_PROG 0x30100001 +#define APP_BATT_VER MSM_RPC_VERS(0,0) +#define HTC_PROCEDURE_BATTERY_NULL 0 +#define HTC_PROCEDURE_GET_BATT_LEVEL 1 +#define HTC_PROCEDURE_GET_BATT_INFO 2 +#define HTC_PROCEDURE_GET_CABLE_STATUS 3 +#define HTC_PROCEDURE_SET_BATT_DELTA 4 + +static struct msm_rpc_endpoint *endpoint; + +struct battery_info_reply { + u32 batt_id; /* Battery ID from ADC */ + u32 batt_vol; /* Battery voltage from ADC */ + u32 batt_temp; /* Battery Temperature (C) from formula and ADC */ + u32 batt_current; /* Battery current from ADC */ + u32 level; /* formula */ + u32 charging_source; /* 0: no cable, 1:usb, 2:AC */ + u32 charging_enabled; /* 0: Disable, 1: Enable */ + u32 full_bat; /* Full capacity of battery (mAh) */ +}; + +static void dock_work_proc(struct work_struct *work) +{ + int dockid; + + if (!vbus_present || dock_acquire(&ds)) + goto no_dock; + + if (ds.dock_connected_unknown) { + /* force a new dock notification if a command failed */ + switch_set_state(&dock_switch, 0); + ds.dock_connected_unknown = false; + } + + dockid = dock_read_single(&ds, DOCK_TYPE); + dock_release(&ds); + + pr_info("Detected dock with ID %02x\n", dockid); + if (dockid >= 0) { + msm_hsusb_set_vbus_state(0); + dock_mains = !!(dockid & 0x80); + switch_set_state(&dock_switch, (dockid & 1) ? 2 : 1); + goto done; + } +no_dock: + dock_mains = false; + switch_set_state(&dock_switch, 0); + msm_hsusb_set_vbus_state(vbus_present); +done: + power_supply_changed(&ac_supply); + power_supply_changed(&usb_supply); + wake_unlock(&dock_work_wake_lock); +} + +static int htc_battery_probe(struct platform_device *pdev) +{ + struct rpc_request_hdr req; + struct htc_get_batt_info_rep { + struct rpc_reply_hdr hdr; + struct battery_info_reply info; + } rep; + + int rc; + + endpoint = msm_rpc_connect(APP_BATT_PROG, APP_BATT_VER, 0); + if (IS_ERR(endpoint)) { + printk(KERN_ERR "%s: init rpc failed! rc = %ld\n", + __FUNCTION__, PTR_ERR(endpoint)); + return PTR_ERR(endpoint); + } + + /* must do this or we won't get cable status updates */ + rc = msm_rpc_call_reply(endpoint, HTC_PROCEDURE_GET_BATT_INFO, + &req, sizeof(req), + &rep, sizeof(rep), + 5 * HZ); + if (rc < 0) + printk(KERN_ERR "%s: get info failed\n", __FUNCTION__); + + power_supply_register(&pdev->dev, &ac_supply); + power_supply_register(&pdev->dev, &usb_supply); + + INIT_WORK(&dock_work, dock_work_proc); + dock_wq = create_singlethread_workqueue("dock"); + + return 0; +} + +static struct platform_driver htc_battery_driver = { + .probe = htc_battery_probe, + .driver = { + .name = APP_BATT_PDEV_NAME, + .owner = THIS_MODULE, + }, +}; + +/* batt_mtoa server definitions */ +#define BATT_MTOA_PROG 0x30100000 +#define BATT_MTOA_VERS 0 +#define RPC_BATT_MTOA_NULL 0 +#define RPC_BATT_MTOA_SET_CHARGING_PROC 1 +#define RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC 2 +#define RPC_BATT_MTOA_LEVEL_UPDATE_PROC 3 + +struct rpc_batt_mtoa_cable_status_update_args { + int status; +}; + +static int handle_battery_call(struct msm_rpc_server *server, + struct rpc_request_hdr *req, unsigned len) +{ + struct rpc_batt_mtoa_cable_status_update_args *args; + + if (req->procedure != RPC_BATT_MTOA_CABLE_STATUS_UPDATE_PROC) + return 0; + + args = (struct rpc_batt_mtoa_cable_status_update_args *)(req + 1); + args->status = be32_to_cpu(args->status); + pr_info("cable_status_update: status=%d\n",args->status); + + args->status = !!args->status; + + vbus_present = args->status; + wake_lock(&dock_work_wake_lock); + queue_work(dock_wq, &dock_work); + return 0; +} + +void notify_usb_connected(int status) +{ + printk("### notify_usb_connected(%d) ###\n", status); + usb_status = status; + power_supply_changed(&ac_supply); + power_supply_changed(&usb_supply); +} + +int is_ac_power_supplied(void) +{ + return vbus_present && (usb_status == 2 || dock_mains); +} + +static struct msm_rpc_server battery_server = { + .prog = BATT_MTOA_PROG, + .vers = BATT_MTOA_VERS, + .rpc_call = handle_battery_call, +}; + +static int __init htc_battery_init(void) +{ + int ret; + gpio_request(_GPIO_DOCK, "dock"); + dock_in(); + wake_lock_init(&dock_work_wake_lock, WAKE_LOCK_SUSPEND, "dock"); + platform_driver_register(&htc_battery_driver); + msm_rpc_create_server(&battery_server); + if (switch_dev_register(&dock_switch) == 0) { + ret = device_create_file(dock_switch.dev, &dev_attr_bt_addr); + WARN_ON(ret); + ret = device_create_file(dock_switch.dev, &dev_attr_bt_pin); + WARN_ON(ret); + } + + return 0; +} + +module_init(htc_battery_init); +MODULE_DESCRIPTION("HTC Battery Driver"); +MODULE_LICENSE("GPL"); + diff --git a/arch/arm/mach-msm/htc_pwrsink.c b/arch/arm/mach-msm/htc_pwrsink.c new file mode 100644 index 00000000000..2ec2c7f4bb1 --- /dev/null +++ b/arch/arm/mach-msm/htc_pwrsink.c @@ -0,0 +1,281 @@ +/* arch/arm/mach-msm/htc_pwrsink.c + * + * Copyright (C) 2008 HTC Corporation + * Copyright (C) 2008 Google, Inc. + * Author: San Mehat + * Kant Kang + * Eiven Peng + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "smd_private.h" + +enum { + PWRSINK_DEBUG_CURR_CHANGE = 1U << 0, + PWRSINK_DEBUG_CURR_CHANGE_AUDIO = 1U << 1, +}; +static int pwrsink_debug_mask; +module_param_named(debug_mask, pwrsink_debug_mask, int, + S_IRUGO | S_IWUSR | S_IWGRP); + +static int initialized; +static unsigned audio_path = 1; /* HTC_SND_DEVICE_SPEAKER = 1 */ +static struct pwr_sink_audio audio_sink_array[PWRSINK_AUDIO_LAST + 1]; +static struct pwr_sink *sink_array[PWRSINK_LAST + 1]; +static DEFINE_SPINLOCK(sink_lock); +static DEFINE_SPINLOCK(audio_sink_lock); +static unsigned long total_sink; +static uint32_t *smem_total_sink; + +int htc_pwrsink_set(pwrsink_id_type id, unsigned percent_utilized) +{ + unsigned long flags; + + if (!smem_total_sink) + smem_total_sink = smem_alloc(SMEM_ID_VENDOR0, sizeof(uint32_t)); + + if (!initialized) + return -EAGAIN; + + if (id < 0 || id > PWRSINK_LAST) + return -EINVAL; + + spin_lock_irqsave(&sink_lock, flags); + + if (!sink_array[id]) { + spin_unlock_irqrestore(&sink_lock, flags); + return -ENOENT; + } + + if (sink_array[id]->percent_util == percent_utilized) { + spin_unlock_irqrestore(&sink_lock, flags); + return 0; + } + + total_sink -= (sink_array[id]->ua_max * + sink_array[id]->percent_util / 100); + sink_array[id]->percent_util = percent_utilized; + total_sink += (sink_array[id]->ua_max * + sink_array[id]->percent_util / 100); + + if (smem_total_sink) + *smem_total_sink = total_sink / 1000; + + pr_debug("htc_pwrsink: ID %d, Util %d%%, Total %lu uA %s\n", + id, percent_utilized, total_sink, + smem_total_sink ? "SET" : ""); + + spin_unlock_irqrestore(&sink_lock, flags); + + return 0; +} +EXPORT_SYMBOL(htc_pwrsink_set); + +static void compute_audio_current(void) +{ + /* unsigned long flags; */ + unsigned max_percent = 0; + int i, active_audio_sinks = 0; + pwrsink_audio_id_type last_active_audio_sink = 0; + + /* Make sure this segment will be spinlocked + before computing by calling function. */ + /* spin_lock_irqsave(&audio_sink_lock, flags); */ + for (i = 0; i <= PWRSINK_AUDIO_LAST; ++i) { + max_percent = (audio_sink_array[i].percent > max_percent) ? + audio_sink_array[i].percent : max_percent; + if (audio_sink_array[i].percent > 0) { + active_audio_sinks++; + last_active_audio_sink = i; + } + } + if (active_audio_sinks == 0) + htc_pwrsink_set(PWRSINK_AUDIO, 0); + else if (active_audio_sinks == 1) { + pwrsink_audio_id_type laas = last_active_audio_sink; + /* TODO: add volume and routing path current. */ + if (audio_path == 1) /* Speaker */ + htc_pwrsink_set(PWRSINK_AUDIO, + audio_sink_array[laas].percent); + else + htc_pwrsink_set(PWRSINK_AUDIO, + audio_sink_array[laas].percent * 9 / 10); + } else if (active_audio_sinks > 1) { + /* TODO: add volume and routing path current. */ + if (audio_path == 1) /* Speaker */ + htc_pwrsink_set(PWRSINK_AUDIO, max_percent); + else + htc_pwrsink_set(PWRSINK_AUDIO, max_percent * 9 / 10); + } + /* spin_unlock_irqrestore(&audio_sink_lock, flags); */ + + if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO) + pr_info("%s: active_audio_sinks=%d, audio_path=%d\n", __func__, + active_audio_sinks, audio_path); +} + +int htc_pwrsink_audio_set(pwrsink_audio_id_type id, unsigned percent_utilized) +{ + unsigned long flags; + + if (id < 0 || id > PWRSINK_AUDIO_LAST) + return -EINVAL; + + if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO) + pr_info("%s: id=%d, percent=%d, percent_old=%d\n", __func__, + id, percent_utilized, audio_sink_array[id].percent); + + spin_lock_irqsave(&audio_sink_lock, flags); + if (audio_sink_array[id].percent == percent_utilized) { + spin_unlock_irqrestore(&audio_sink_lock, flags); + return 0; + } + audio_sink_array[id].percent = percent_utilized; + spin_unlock_irqrestore(&audio_sink_lock, flags); + compute_audio_current(); + return 0; +} +EXPORT_SYMBOL(htc_pwrsink_audio_set); + +int htc_pwrsink_audio_volume_set(pwrsink_audio_id_type id, unsigned volume) +{ + unsigned long flags; + + if (id < 0 || id > PWRSINK_AUDIO_LAST) + return -EINVAL; + + if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO) + pr_info("%s: id=%d, volume=%d, volume_old=%d\n", __func__, + id, volume, audio_sink_array[id].volume); + + spin_lock_irqsave(&audio_sink_lock, flags); + if (audio_sink_array[id].volume == volume) { + spin_unlock_irqrestore(&audio_sink_lock, flags); + return 0; + } + audio_sink_array[id].volume = volume; + spin_unlock_irqrestore(&audio_sink_lock, flags); + compute_audio_current(); + return 0; +} +EXPORT_SYMBOL(htc_pwrsink_audio_volume_set); + +int htc_pwrsink_audio_path_set(unsigned path) +{ + unsigned long flags; + + if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO) + pr_info("%s: path=%d, path_old=%d\n", + __func__, path, audio_path); + + spin_lock_irqsave(&audio_sink_lock, flags); + if (audio_path == path) { + spin_unlock_irqrestore(&audio_sink_lock, flags); + return 0; + } + audio_path = path; + spin_unlock_irqrestore(&audio_sink_lock, flags); + compute_audio_current(); + return 0; +} +EXPORT_SYMBOL(htc_pwrsink_audio_path_set); + +void htc_pwrsink_suspend_early(struct early_suspend *h) +{ + htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 70); +} + +int htc_pwrsink_suspend_late(struct platform_device *pdev, pm_message_t state) +{ + struct pwr_sink_platform_data *pdata = pdev->dev.platform_data; + + if (pdata && pdata->suspend_late) + pdata->suspend_late(pdev, state); + else + htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 13); + return 0; +} + +int htc_pwrsink_resume_early(struct platform_device *pdev) +{ + struct pwr_sink_platform_data *pdata = pdev->dev.platform_data; + + if (pdata && pdata->resume_early) + pdata->resume_early(pdev); + else + htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 70); + return 0; +} + +void htc_pwrsink_resume_late(struct early_suspend *h) +{ + htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 100); +} + +struct early_suspend htc_pwrsink_early_suspend = { + .level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 1, + .suspend = htc_pwrsink_suspend_early, + .resume = htc_pwrsink_resume_late, +}; + +static int __init htc_pwrsink_probe(struct platform_device *pdev) +{ + struct pwr_sink_platform_data *pdata = pdev->dev.platform_data; + int i; + + if (!pdata) + return -EINVAL; + + total_sink = 0; + for (i = 0; i < pdata->num_sinks; i++) { + sink_array[pdata->sinks[i].id] = &pdata->sinks[i]; + total_sink += (pdata->sinks[i].ua_max * + pdata->sinks[i].percent_util / 100); + } + + initialized = 1; + + if (pdata->suspend_early) + htc_pwrsink_early_suspend.suspend = pdata->suspend_early; + if (pdata->resume_late) + htc_pwrsink_early_suspend.resume = pdata->resume_late; + register_early_suspend(&htc_pwrsink_early_suspend); + + return 0; +} + +static struct platform_driver htc_pwrsink_driver = { + .probe = htc_pwrsink_probe, + .suspend_late = htc_pwrsink_suspend_late, + .resume_early = htc_pwrsink_resume_early, + .driver = { + .name = "htc_pwrsink", + .owner = THIS_MODULE, + }, +}; + +static int __init htc_pwrsink_init(void) +{ + initialized = 0; + memset(sink_array, 0, sizeof(sink_array)); + return platform_driver_register(&htc_pwrsink_driver); +} + +module_init(htc_pwrsink_init); diff --git a/arch/arm/mach-msm/htc_wifi_nvs.c b/arch/arm/mach-msm/htc_wifi_nvs.c new file mode 100644 index 00000000000..2d381a97c07 --- /dev/null +++ b/arch/arm/mach-msm/htc_wifi_nvs.c @@ -0,0 +1,55 @@ +/* arch/arm/mach-msm/htc_wifi_nvs.c + * + * Code to extract WiFi calibration information from ATAG set up + * by the bootloader. + * + * Copyright (C) 2008 Google, Inc. + * Author: Dmitry Shmidt + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include + +/* configuration tags specific to msm */ +#define ATAG_MSM_WIFI 0x57494649 /* MSM WiFi */ + +#define MAX_NVS_SIZE 0x800U +static unsigned char wifi_nvs_ram[MAX_NVS_SIZE]; + +unsigned char *get_wifi_nvs_ram( void ) +{ + return( wifi_nvs_ram ); +} +EXPORT_SYMBOL(get_wifi_nvs_ram); + +static int __init parse_tag_msm_wifi(const struct tag *tag) +{ + unsigned char *dptr = (unsigned char *)(&tag->u); + unsigned size; + + size = min((tag->hdr.size - 2) * sizeof(__u32), MAX_NVS_SIZE); +#ifdef ATAG_MSM_WIFI_DEBUG + unsigned i; + + printk("WiFi Data size = %d , 0x%x\n", tag->hdr.size, tag->hdr.tag); + for (i = 0; i < size; i++) + printk("%02x ", *dptr++); +#endif + memcpy( (void *)wifi_nvs_ram, (void *)dptr, size ); + return 0; +} + +__tagtable(ATAG_MSM_WIFI, parse_tag_msm_wifi); diff --git a/arch/arm/mach-msm/hw3d.c b/arch/arm/mach-msm/hw3d.c new file mode 100644 index 00000000000..c2592ec9efb --- /dev/null +++ b/arch/arm/mach-msm/hw3d.c @@ -0,0 +1,407 @@ +/* arch/arm/mach-msm/hw3d.c + * + * Register/Interrupt access for userspace 3D library. + * + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(hw3d_lock); +static DECLARE_WAIT_QUEUE_HEAD(hw3d_queue); +static int hw3d_pending; +static int hw3d_disabled; + +static struct clk *grp_clk; +static struct clk *imem_clk; +DECLARE_MUTEX(hw3d_sem); +static unsigned int hw3d_granted; +static struct file *hw3d_granted_file; + +static irqreturn_t hw3d_irq_handler(int irq, void *data) +{ + unsigned long flags; + + spin_lock_irqsave(&hw3d_lock, flags); + if (!hw3d_disabled) { + disable_irq(INT_GRAPHICS); + hw3d_disabled = 1; + } + hw3d_pending = 1; + spin_unlock_irqrestore(&hw3d_lock, flags); + + wake_up(&hw3d_queue); + + return IRQ_HANDLED; +} + +static void hw3d_disable_interrupt(void) +{ + unsigned long flags; + spin_lock_irqsave(&hw3d_lock, flags); + if (!hw3d_disabled) { + disable_irq(INT_GRAPHICS); + hw3d_disabled = 1; + } + spin_unlock_irqrestore(&hw3d_lock, flags); +} + +static long hw3d_wait_for_interrupt(void) +{ + unsigned long flags; + int ret; + + for (;;) { + spin_lock_irqsave(&hw3d_lock, flags); + if (hw3d_pending) { + hw3d_pending = 0; + spin_unlock_irqrestore(&hw3d_lock, flags); + return 0; + } + if (hw3d_disabled) { + hw3d_disabled = 0; + enable_irq(INT_GRAPHICS); + } + spin_unlock_irqrestore(&hw3d_lock, flags); + + ret = wait_event_interruptible(hw3d_queue, hw3d_pending); + if (ret < 0) { + hw3d_disable_interrupt(); + return ret; + } + } + + return 0; +} + +#define HW3D_REGS_LEN 0x100000 +static long hw3d_wait_for_revoke(struct hw3d_info *info, struct file *filp) +{ + struct hw3d_data *data = filp->private_data; + int ret; + + if (is_master(info, filp)) { + pr_err("%s: cannot revoke on master node\n", __func__); + return -EPERM; + } + + ret = wait_event_interruptible(info->revoke_wq, + info->revoking || + data->closing); + if (ret == 0 && data->closing) + ret = -EPIPE; + if (ret < 0) + return ret; + return 0; +} + +static void locked_hw3d_client_done(struct hw3d_info *info, int had_timer) +{ + if (info->enabled) { + pr_debug("hw3d: was enabled\n"); + info->enabled = 0; + clk_disable(info->grp_clk); + clk_disable(info->imem_clk); + } + info->revoking = 0; + + /* double check that the irqs are disabled */ + locked_hw3d_irq_disable(info); + + if (had_timer) + wake_unlock(&info->wake_lock); + wake_up(&info->revoke_done_wq); +} + +static void do_force_revoke(struct hw3d_info *info) +{ + unsigned long flags; + + /* at this point, the task had a chance to relinquish the gpu, but + * it hasn't. So, we kill it */ + spin_lock_irqsave(&info->lock, flags); + pr_debug("hw3d: forcing revoke\n"); + locked_hw3d_irq_disable(info); + if (info->client_task) { + pr_info("hw3d: force revoke from pid=%d\n", + info->client_task->pid); + force_sig(SIGKILL, info->client_task); + put_task_struct(info->client_task); + info->client_task = NULL; + } + locked_hw3d_client_done(info, 1); + pr_debug("hw3d: done forcing revoke\n"); + spin_unlock_irqrestore(&info->lock, flags); +} + +#define REVOKE_TIMEOUT (2 * HZ) +static void locked_hw3d_revoke(struct hw3d_info *info) +{ + /* force us to wait to suspend until the revoke is done. If the + * user doesn't release the gpu, the timer will turn off the gpu, + * and force kill the process. */ + wake_lock(&info->wake_lock); + info->revoking = 1; + wake_up(&info->revoke_wq); + mod_timer(&info->revoke_timer, jiffies + REVOKE_TIMEOUT); +} + +bool is_msm_hw3d_file(struct file *file) +{ + struct hw3d_info *info = hw3d_info; + if (MAJOR(file->f_dentry->d_inode->i_rdev) == MAJOR(info->devno) && + (is_master(info, file) || is_client(info, file))) + return 1; + return 0; +} + +void put_msm_hw3d_file(struct file *file) +{ + if (!is_msm_hw3d_file(file)) + return; + fput(file); +} + +static long hw3d_revoke_gpu(struct file *file) +{ + int ret = 0; + unsigned long user_start, user_len; + struct pmem_region region = {.offset = 0x0, .len = HW3D_REGS_LEN}; + + down(&hw3d_sem); + if (!hw3d_granted) + goto end; + /* revoke the pmem region completely */ + if ((ret = pmem_remap(®ion, file, PMEM_UNMAP))) + goto end; + get_pmem_user_addr(file, &user_start, &user_len); + /* reset the gpu */ + clk_disable(grp_clk); + clk_disable(imem_clk); + hw3d_granted = 0; +end: + up(&hw3d_sem); + return ret; +} + +static long hw3d_grant_gpu(struct file *file) +{ + int ret = 0; + struct pmem_region region = {.offset = 0x0, .len = HW3D_REGS_LEN}; + + down(&hw3d_sem); + if (hw3d_granted) { + ret = -1; + goto end; + } + /* map the registers */ + if ((ret = pmem_remap(®ion, file, PMEM_MAP))) + goto end; + clk_enable(grp_clk); + clk_enable(imem_clk); + hw3d_granted = 1; + hw3d_granted_file = file; +end: + up(&hw3d_sem); + return ret; +} + +static int hw3d_release(struct inode *inode, struct file *file) +{ + down(&hw3d_sem); + /* if the gpu is in use, and its inuse by the file that was released */ + if (hw3d_granted && (file == hw3d_granted_file)) { + clk_disable(grp_clk); + clk_disable(imem_clk); + hw3d_granted = 0; + hw3d_granted_file = NULL; + } + up(&hw3d_sem); + return 0; +} + +static void hw3d_vma_open(struct vm_area_struct *vma) +{ + /* XXX: should the master be allowed to fork and keep the mappings? */ + + /* TODO: remap garbage page into here. + * + * For now, just pull the mapping. The user shouldn't be forking + * and using it anyway. */ + zap_page_range(vma, vma->vm_start, vma->vm_end - vma->vm_start, NULL); +} + +static void hw3d_vma_close(struct vm_area_struct *vma) +{ + struct file *file = vma->vm_file; + struct hw3d_data *data = file->private_data; + int i; + + pr_debug("hw3d: current %u ppid %u file %p count %ld\n", + current->pid, current->parent->pid, file, file_count(file)); + + BUG_ON(!data); + + mutex_lock(&data->mutex); + for (i = 0; i < HW3D_NUM_REGIONS; ++i) { + if (data->vmas[i] == vma) { + data->vmas[i] = NULL; + goto done; + } + } + pr_warning("%s: vma %p not of ours during vma_close\n", __func__, vma); +done: + mutex_unlock(&data->mutex); +} + +static int hw3d_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct hw3d_info *info = hw3d_info; + struct hw3d_data *data = file->private_data; + unsigned long vma_size = vma->vm_end - vma->vm_start; + int ret = 0; + int region = REGION_PAGE_ID(vma->vm_pgoff); + + if (region >= HW3D_NUM_REGIONS) { + pr_err("%s: Trying to mmap unknown region %d\n", __func__, + region); + return -EINVAL; + } else if (vma_size > info->regions[region].size) { + pr_err("%s: VMA size %ld exceeds region %d size %ld\n", + __func__, vma_size, region, + info->regions[region].size); + return -EINVAL; + } else if (REGION_PAGE_OFFS(vma->vm_pgoff) != 0 || + (vma_size & ~PAGE_MASK)) { + pr_err("%s: Can't remap part of the region %d\n", __func__, + region); + return -EINVAL; + } else if (!is_master(info, file) && + current->group_leader != info->client_task) { + pr_err("%s: current(%d) != client_task(%d)\n", __func__, + current->group_leader->pid, info->client_task->pid); + return -EPERM; + } else if (!is_master(info, file) && + (info->revoking || info->suspending)) { + pr_err("%s: cannot mmap while revoking(%d) or suspending(%d)\n", + __func__, info->revoking, info->suspending); + return -EPERM; + } + + mutex_lock(&data->mutex); + if (data->vmas[region] != NULL) { + pr_err("%s: Region %d already mapped (pid=%d tid=%d)\n", + __func__, region, current->group_leader->pid, + current->pid); + ret = -EBUSY; + goto done; + } + + /* our mappings are always noncached */ +#ifdef pgprot_noncached + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); +#endif + + ret = io_remap_pfn_range(vma, vma->vm_start, + info->regions[region].pbase >> PAGE_SHIFT, + vma_size, vma->vm_page_prot); + if (ret) { + pr_err("%s: Cannot remap page range for region %d!\n", __func__, + region); + ret = -EAGAIN; + goto done; + } + + /* Prevent a malicious client from stealing another client's data + * by forcing a revoke on it and then mmapping the GPU buffers. + */ + if (region != HW3D_REGS) + memset(info->regions[region].vbase, 0, + info->regions[region].size); + + vma->vm_ops = &hw3d_vm_ops; + + /* mark this region as mapped */ + data->vmas[region] = vma; + +done: + mutex_unlock(&data->mutex); + return ret; +} + +static long hw3d_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case HW3D_REVOKE_GPU: + return hw3d_revoke_gpu(file); + break; + case HW3D_GRANT_GPU: + return hw3d_grant_gpu(file); + break; + case HW3D_WAIT_FOR_INTERRUPT: + return hw3d_wait_for_interrupt(); + break; + default: + return -EINVAL; + } + return 0; +} + +static struct android_pmem_platform_data pmem_data = { + .name = "hw3d", + .start = 0xA0000000, + .size = 0x100000, + .allocator_type = PMEM_ALLOCATORTYPE_ALLORNOTHING, + .cached = 0, +}; + +static int __init hw3d_init(void) +{ + int ret; + + grp_clk = clk_get(NULL, "grp_clk"); + if (IS_ERR(grp_clk)) + return PTR_ERR(grp_clk); + + imem_clk = clk_get(NULL, "imem_clk"); + if (IS_ERR(imem_clk)) { + clk_put(grp_clk); + return PTR_ERR(imem_clk); + } + ret = request_irq(INT_GRAPHICS, hw3d_irq_handler, + IRQF_TRIGGER_HIGH, "hw3d", 0); + if (ret) { + clk_put(grp_clk); + clk_put(imem_clk); + return ret; + } + hw3d_disable_interrupt(); + hw3d_granted = 0; + + return pmem_setup(&pmem_data, hw3d_ioctl, hw3d_release); +} + +device_initcall(hw3d_init); diff --git a/arch/arm/mach-msm/idle-v6.S b/arch/arm/mach-msm/idle-v6.S new file mode 100644 index 00000000000..81608775812 --- /dev/null +++ b/arch/arm/mach-msm/idle-v6.S @@ -0,0 +1,194 @@ +/* + * Idle processing for ARMv6-based Qualcomm SoCs. + * Work around bugs with SWFI. + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2009, Code Aurora Forum. 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 +#include + +.extern write_to_strongly_ordered_memory + +ENTRY(msm_arch_idle) + mrs r2, cpsr /* save the CPSR state */ + cpsid iaf /* explictly disable I,A and F */ + +#if defined(CONFIG_ARCH_MSM7X27) + mov r0, #0 + mcr p15, 0, r0, c7, c10, 0 /* flush entire data cache */ + mcr p15, 0, r0, c7, c10, 4 /* dsb */ + stmfd sp!, {r2, lr} /* preserve r2, thus CPSR and LR */ + bl write_to_strongly_ordered_memory /* flush AXI bus buffer */ + ldmfd sp!, {r2, lr} + mcr p15, 0, r0, c7, c0, 4 /* wait for interrupt */ +#else + mrc p15, 0, r1, c1, c0, 0 /* read current CR */ + bic r0, r1, #(1 << 2) /* clear dcache bit */ + bic r0, r0, #(1 << 12) /* clear icache bit */ + mcr p15, 0, r0, c1, c0, 0 /* disable d/i cache */ + + mov r0, #0 + mcr p15, 0, r0, c7, c5, 0 /* invalidate icache and flush */ + /* branch target cache */ + mcr p15, 0, r0, c7, c14, 0 /* clean and invalidate dcache */ + + mcr p15, 0, r0, c7, c10, 4 /* dsb */ + mcr p15, 0, r0, c7, c0, 4 /* wait for interrupt */ + + mcr p15, 0, r1, c1, c0, 0 /* restore d/i cache */ + mcr p15, 0, r0, c7, c5, 4 /* isb */ +#endif + + msr cpsr_c, r2 /* restore the CPSR state */ + mov pc, lr + +ENTRY(msm_pm_collapse) + ldr r0, =saved_state + stmia r0!, {r4-r14} + + cpsid f + + mrc p15, 0, r1, c1, c0, 0 /* MMU control */ + mrc p15, 0, r2, c2, c0, 0 /* ttb */ + mrc p15, 0, r3, c3, c0, 0 /* dacr */ + mrc p15, 0, ip, c13, c0, 1 /* context ID */ + stmia r0!, {r1-r3, ip} +#if defined(CONFIG_OPROFILE) + mrc p15, 0, r1, c15, c12, 0 /* pmnc */ + mrc p15, 0, r2, c15, c12, 1 /* ccnt */ + mrc p15, 0, r3, c15, c12, 2 /* pmn0 */ + mrc p15, 0, ip, c15, c12, 3 /* pmn1 */ + stmia r0!, {r1-r3, ip} +#endif + mrc p15, 0, r1, c1, c0, 2 /* read CACR */ + stmia r0!, {r1} + + mrc p15, 0, r1, c1, c0, 0 /* read current CR */ + bic r0, r1, #(1 << 2) /* clear dcache bit */ + bic r0, r0, #(1 << 12) /* clear icache bit */ + mcr p15, 0, r0, c1, c0, 0 /* disable d/i cache */ + + mov r0, #0 + mcr p15, 0, r0, c7, c5, 0 /* invalidate icache and flush */ + /* branch target cache */ + mcr p15, 0, r0, c7, c14, 0 /* clean and invalidate dcache */ + + mcr p15, 0, r0, c7, c10, 4 /* dsb */ + mcr p15, 0, r0, c7, c0, 4 /* wait for interrupt */ + + mcr p15, 0, r1, c1, c0, 0 /* restore d/i cache */ + mcr p15, 0, r0, c7, c5, 4 /* isb */ + + cpsie f + + ldr r0, =saved_state /* restore registers */ + ldmfd r0, {r4-r14} + mov r0, #0 /* return power collapse failed */ + mov pc, lr + +ENTRY(msm_pm_collapse_exit) +#if 0 /* serial debug */ + mov r0, #0x80000016 + mcr p15, 0, r0, c15, c2, 4 + mov r0, #0xA9000000 + add r0, r0, #0x00A00000 /* UART1 */ + /*add r0, r0, #0x00C00000*/ /* UART3 */ + mov r1, #'A' + str r1, [r0, #0x00C] +#endif + ldr r1, =saved_state_end + ldr r2, =msm_pm_collapse_exit + adr r3, msm_pm_collapse_exit + add r1, r1, r3 + sub r1, r1, r2 + + ldmdb r1!, {r2} + mcr p15, 0, r2, c1, c0, 2 /* restore CACR */ +#if defined(CONFIG_OPROFILE) + ldmdb r1!, {r2-r5} + mcr p15, 0, r3, c15, c12, 1 /* ccnt */ + mcr p15, 0, r4, c15, c12, 2 /* pmn0 */ + mcr p15, 0, r5, c15, c12, 3 /* pmn1 */ + mcr p15, 0, r2, c15, c12, 0 /* pmnc */ +#endif + ldmdb r1!, {r2-r5} + mcr p15, 0, r4, c3, c0, 0 /* dacr */ + mcr p15, 0, r3, c2, c0, 0 /* ttb */ + mcr p15, 0, r5, c13, c0, 1 /* context ID */ + mov r0, #0 + mcr p15, 0, r0, c7, c5, 4 /* isb */ + ldmdb r1!, {r4-r14} + + /* Add 1:1 map in the PMD to allow smooth switch when turning on MMU */ + and r3, r3, #~0x7F /* mask off lower 7 bits of TTB */ + adr r0, msm_pm_mapped_pa /* get address of the mapped instr */ + lsr r1, r0, #20 /* get the addr range of addr in MB */ + lsl r1, r1, #2 /* multiply by 4 to get to the pg index */ + add r3, r3, r1 /* pgd + pgd_index(addr) */ + ldr r1, [r3] /* save current entry to r1 */ + lsr r0, #20 /* align current addr to 1MB boundary */ + lsl r0, #20 + /* Create new entry for this 1MB page */ + orr r0, r0, #0x400 /* PMD_SECT_AP_WRITE */ + orr r0, r0, #0x2 /* PMD_TYPE_SECT|PMD_DOMAIN(DOMAIN_KERNEL) */ + str r0, [r3] /* put new entry into the MMU table */ + mov r0, #0 + mcr p15, 0, r0, c7, c10, 4 /* dsb */ + mcr p15, 0, r2, c1, c0, 0 /* MMU control */ + mcr p15, 0, r0, c7, c5, 4 /* isb */ +msm_pm_mapped_pa: + /* Switch to virtual */ + adr r2, msm_pm_pa_to_va + ldr r0, =msm_pm_pa_to_va + mov pc, r0 +msm_pm_pa_to_va: + sub r0, r0, r2 + /* Restore r1 in MMU table */ + add r3, r3, r0 + str r1, [r3] + + mov r0, #0 + mcr p15, 0, r0, c7, c10, 0 /* flush entire data cache */ + mcr p15, 0, r0, c7, c10, 4 /* dsb */ + mcr p15, 0, r0, c7, c5, 4 /* isb */ + mcr p15, 0, r0, c8, c7, 0 /* invalidate entire unified TLB */ + mcr p15, 0, r0, c7, c5, 6 /* invalidate entire branch target + * cache */ + mcr p15, 0, r0, c7, c7, 0 /* invalidate both data and instruction + * cache */ + mcr p15, 0, r0, c7, c10, 4 /* dsb */ + mcr p15, 0, r0, c7, c5, 4 /* isb */ + + mov r0, #1 + mov pc, lr + nop + nop + nop + nop + nop +1: b 1b + + + .data + +saved_state: + .space 4 * 11 /* r4-14 */ + .space 4 * 4 /* cp15 - MMU control, ttb, dacr, context ID */ +#if defined(CONFIG_OPROFILE) + .space 4 * 4 /* more cp15 - pmnc, ccnt, pmn0, pmn1 */ +#endif + .space 4 /* cacr */ +saved_state_end: + diff --git a/arch/arm/mach-msm/idle-v7.S b/arch/arm/mach-msm/idle-v7.S new file mode 100644 index 00000000000..98a6354ee0d --- /dev/null +++ b/arch/arm/mach-msm/idle-v7.S @@ -0,0 +1,252 @@ +/* + * Idle processing for ARMv7-based Qualcomm SoCs. + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2009, 2011 Code Aurora Forum. 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 +#include +#include + +#ifdef CONFIG_MSM_CPU_AVS +/* 11 general purpose registers (r4-r14), 10 cp15 registers, 3 AVS registers */ +#define CPU_SAVED_STATE_SIZE (4 * 11 + 4 * 10 + 4 * 3) +#else +/* 11 general purpose registers (r4-r14), 10 cp15 registers */ +#define CPU_SAVED_STATE_SIZE (4 * 11 + 4 * 10) +#endif + +ENTRY(msm_arch_idle) + stmfd sp!, {lr} +#ifdef CONFIG_MSM_JTAG_V7 + bl msm_save_jtag_debug +#endif +#ifdef CONFIG_MSM_ETM + bl etm_save_reg_check +#endif + wfi +#ifdef CONFIG_MSM_ETM + bl etm_restore_reg_check +#endif +#ifdef CONFIG_MSM_JTAG_V7 + bl msm_restore_jtag_debug +#endif + ldmfd sp!, {lr} + bx lr + +ENTRY(msm_pm_collapse) +#if defined(CONFIG_MSM_FIQ_SUPPORT) + cpsid f +#endif + + ldr r0, =saved_state +#if (NR_CPUS >= 2) + mrc p15, 0, r1, c0, c0, 5 /* MPIDR */ + ands r1, r1, #15 /* What CPU am I */ + addne r0, r0, #CPU_SAVED_STATE_SIZE +#endif + + stmia r0!, {r4-r14} + mrc p15, 0, r1, c1, c0, 0 /* MMU control */ + mrc p15, 0, r2, c2, c0, 0 /* TTBR0 */ + mrc p15, 0, r3, c3, c0, 0 /* dacr */ +#ifdef CONFIG_ARCH_MSM_SCORPION + /* This instruction is not valid for non scorpion processors */ + mrc p15, 3, r4, c15, c0, 3 /* L2CR1 is the L2 cache control reg 1 */ +#endif + mrc p15, 0, r5, c10, c2, 0 /* PRRR */ + mrc p15, 0, r6, c10, c2, 1 /* NMRR */ + mrc p15, 0, r7, c1, c0, 1 /* ACTLR */ + mrc p15, 0, r8, c2, c0, 1 /* TTBR1 */ + mrc p15, 0, r9, c13, c0, 3 /* TPIDRURO */ + mrc p15, 0, ip, c13, c0, 1 /* context ID */ + stmia r0!, {r1-r9, ip} +#ifdef CONFIG_MSM_CPU_AVS + mrc p15, 7, r1, c15, c1, 7 /* AVSCSR is the Adaptive Voltage Scaling + * Control and Status Register */ + mrc p15, 7, r2, c15, c0, 6 /* AVSDSCR is the Adaptive Voltage + * Scaling Delay Synthesizer Control + * Register */ +#ifndef CONFIG_ARCH_MSM_KRAIT + mrc p15, 7, r3, c15, c1, 0 /* TSCSR is the Temperature Status and + * Control Register + */ +#endif + + stmia r0!, {r1-r3} +#endif + +#ifdef CONFIG_MSM_JTAG_V7 + bl msm_save_jtag_debug +#endif +#ifdef CONFIG_MSM_ETM + bl etm_save_reg_check +#endif + bl v7_flush_dcache_all + + mrc p15, 0, r1, c1, c0, 0 /* read current CR */ + bic r0, r1, #(1 << 2) /* clear dcache bit */ + bic r0, r0, #(1 << 12) /* clear icache bit */ + mcr p15, 0, r0, c1, c0, 0 /* disable d/i cache */ + + dsb + + wfi + + mcr p15, 0, r1, c1, c0, 0 /* restore d/i cache */ + isb + +#if defined(CONFIG_MSM_FIQ_SUPPORT) + cpsie f +#endif +#ifdef CONFIG_MSM_ETM + bl etm_restore_reg_check +#endif +#ifdef CONFIG_MSM_JTAG_V7 + bl msm_restore_jtag_debug +#endif + ldr r0, =saved_state /* restore registers */ +#if (NR_CPUS >= 2) + mrc p15, 0, r1, c0, c0, 5 /* MPIDR */ + ands r1, r1, #15 /* What CPU am I */ + addne r0, r0, #CPU_SAVED_STATE_SIZE +#endif + + ldmfd r0, {r4-r14} + mov r0, #0 /* return power collapse failed */ + bx lr + +ENTRY(msm_pm_collapse_exit) +#if 0 /* serial debug */ + mov r0, #0x80000016 + mcr p15, 0, r0, c15, c2, 4 + mov r0, #0xA9000000 + add r0, r0, #0x00A00000 /* UART1 */ + /*add r0, r0, #0x00C00000*/ /* UART3 */ + mov r1, #'A' + str r1, [r0, #0x00C] +#endif + ldr r1, =saved_state_end + ldr r2, =msm_pm_collapse_exit + adr r3, msm_pm_collapse_exit + add r1, r1, r3 + sub r1, r1, r2 +#if (NR_CPUS >= 2) + mrc p15, 0, r2, c0, c0, 5 /* MPIDR */ + ands r2, r2, #15 /* What CPU am I */ + subeq r1, r1, #CPU_SAVED_STATE_SIZE +#endif + +#ifdef CONFIG_MSM_CPU_AVS + ldmdb r1!, {r2-r4} +#ifndef CONFIG_ARCH_MSM_KRAIT + mcr p15, 7, r4, c15, c1, 0 /* TSCSR */ +#endif + mcr p15, 7, r3, c15, c0, 6 /* AVSDSCR */ + mcr p15, 7, r2, c15, c1, 7 /* AVSCSR */ +#endif + ldmdb r1!, {r2-r11} + mcr p15, 0, r4, c3, c0, 0 /* dacr */ + mcr p15, 0, r3, c2, c0, 0 /* TTBR0 */ +#ifdef CONFIG_ARCH_MSM_SCORPION + /* This instruction is not valid for non scorpion processors */ + mcr p15, 3, r5, c15, c0, 3 /* L2CR1 */ +#endif + mcr p15, 0, r6, c10, c2, 0 /* PRRR */ + mcr p15, 0, r7, c10, c2, 1 /* NMRR */ + mcr p15, 0, r8, c1, c0, 1 /* ACTLR */ + mcr p15, 0, r9, c2, c0, 1 /* TTBR1 */ + mcr p15, 0, r10, c13, c0, 3 /* TPIDRURO */ + mcr p15, 0, r11, c13, c0, 1 /* context ID */ + isb + ldmdb r1!, {r4-r14} + ldr r0, =msm_pm_pc_pgd + ldr r1, =msm_pm_collapse_exit + adr r3, msm_pm_collapse_exit + add r0, r0, r3 + sub r0, r0, r1 + ldr r0, [r0] + mrc p15, 0, r1, c2, c0, 0 /* save current TTBR0 */ + and r3, r1, #0x7f /* mask to get TTB flags */ + orr r0, r0, r3 /* add TTB flags to switch TTBR value */ + mcr p15, 0, r0, c2, c0, 0 /* temporary switch TTBR0 */ + isb + mcr p15, 0, r2, c1, c0, 0 /* MMU control */ + isb +msm_pm_mapped_pa: + /* Switch to virtual */ + ldr r0, =msm_pm_pa_to_va + mov pc, r0 +msm_pm_pa_to_va: + mcr p15, 0, r1, c2, c0, 0 /* restore TTBR0 */ + isb + mcr p15, 0, r3, c8, c7, 0 /* UTLBIALL */ + mcr p15, 0, r3, c7, c5, 6 /* BPIALL */ + dsb + + isb + stmfd sp!, {lr} + bl v7_flush_kern_cache_all +#ifdef CONFIG_MSM_ETM + bl etm_restore_reg_check +#endif +#ifdef CONFIG_MSM_JTAG_V7 + bl msm_restore_jtag_debug +#endif + ldmfd sp!, {lr} + mov r0, #1 + bx lr + nop + nop + nop + nop + nop +1: b 1b + +ENTRY(msm_pm_boot_entry) + mrc p15, 0, r0, c0, c0, 5 /* MPIDR */ + and r0, r0, #15 /* what CPU am I */ + + ldr r1, =msm_pm_boot_vector + ldr r2, =msm_pm_boot_entry + adr r3, msm_pm_boot_entry + add r1, r1, r3 /* translate virt to phys addr */ + sub r1, r1, r2 + + add r1, r1, r0, LSL #2 /* locate boot vector for our cpu */ + ldr pc, [r1] /* jump */ + +ENTRY(msm_pm_write_boot_vector) + ldr r2, =msm_pm_boot_vector + add r2, r2, r0, LSL #2 /* locate boot vector for our cpu */ + str r1, [r2] + bx lr + + .data + + .globl msm_pm_pc_pgd +msm_pm_pc_pgd: + .long 0x0 + +saved_state: +#if (NR_CPUS >= 2) + .space CPU_SAVED_STATE_SIZE * 2 /* This code only supports 2 cores */ +#else + .space CPU_SAVED_STATE_SIZE +#endif +saved_state_end: + +msm_pm_boot_vector: + .space 4 * NR_CPUS + diff --git a/arch/arm/mach-msm/idle.S b/arch/arm/mach-msm/idle.S index 6a94f052713..d31a39dc991 100644 --- a/arch/arm/mach-msm/idle.S +++ b/arch/arm/mach-msm/idle.S @@ -1,9 +1,9 @@ -/* arch/arm/mach-msm/include/mach/idle.S +/* arch/arm/mach-msm/idle.S * - * Idle processing for MSM7K - work around bugs with SWFI. + * Idle processing for MSM7X00A - work around bugs with SWFI. * - * Copyright (c) 2007 QUALCOMM Incorporated. - * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007 Code Aurora Forum. All rights reserved. + * Copyright (C) 2007 Google, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -14,23 +14,93 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - */ - + */ + #include #include -ENTRY(arch_idle) -#ifdef CONFIG_MSM7X00A_IDLE - mrc p15, 0, r1, c1, c0, 0 /* read current CR */ +ENTRY(msm_pm_collapse) + ldr r0, =saved_state + stmia r0!, {r4-r14} + mrc p15, 0, r1, c1, c0, 0 /* MMU control */ + mrc p15, 0, r2, c2, c0, 0 /* ttb */ + mrc p15, 0, r3, c3, c0, 0 /* dacr */ + mrc p15, 0, ip, c13, c0, 1 /* context ID */ + stmia r0!, {r1-r3, ip} +#if defined(CONFIG_OPROFILE) + mrc p15, 0, r1, c15, c12, 0 /* pmnc */ + mrc p15, 0, r2, c15, c12, 1 /* ccnt */ + mrc p15, 0, r3, c15, c12, 2 /* pmn0 */ + mrc p15, 0, ip, c15, c12, 3 /* pmn1 */ + stmia r0!, {r1-r3, ip} +#endif + /* fall though */ +ENTRY(msm_arch_idle) +#if defined(CONFIG_MSM_FIQ_SUPPORT) + cpsid f +#endif + mrc p15, 0, r1, c1, c0, 0 /* read current CR */ bic r0, r1, #(1 << 2) /* clear dcache bit */ bic r0, r0, #(1 << 12) /* clear icache bit */ mcr p15, 0, r0, c1, c0, 0 /* disable d/i cache */ - mov r0, #0 /* prepare wfi value */ + mov r0, #0 /* prepare wfi value */ /* also used as return value from msm_pm_collapse */ mcr p15, 0, r0, c7, c10, 0 /* flush the cache */ mcr p15, 0, r0, c7, c10, 4 /* memory barrier */ mcr p15, 0, r0, c7, c0, 4 /* wait for interrupt */ mcr p15, 0, r1, c1, c0, 0 /* restore d/i cache */ +#if defined(CONFIG_MSM_FIQ_SUPPORT) + cpsie f #endif mov pc, lr + +ENTRY(msm_pm_collapse_exit) +#if 0 /* serial debug */ + mov r0, #0x80000016 + mcr p15, 0, r0, c15, c2, 4 + mov r0, #0xA9000000 + add r0, r0, #0x00A00000 /* UART1 */ + /*add r0, r0, #0x00C00000*/ /* UART3 */ + mov r1, #'A' + str r1, [r0, #0x00C] +#endif + ldr r1, =saved_state_end + ldr r2, =msm_pm_collapse_exit + adr r3, msm_pm_collapse_exit + add r1, r1, r3 + sub r1, r1, r2 +#if defined(CONFIG_OPROFILE) + ldmdb r1!, {r2-r5} + mcr p15, 0, r3, c15, c12, 1 /* ccnt */ + mcr p15, 0, r4, c15, c12, 2 /* pmn0 */ + mcr p15, 0, r5, c15, c12, 3 /* pmn1 */ + mcr p15, 0, r2, c15, c12, 0 /* pmnc */ +#endif + ldmdb r1!, {r2-r5} + mcr p15, 0, r4, c3, c0, 0 /* dacr */ + mcr p15, 0, r3, c2, c0, 0 /* ttb */ + mcr p15, 0, r5, c13, c0, 1 /* context ID */ + ldmdb r1!, {r4-r14} + mov r0, #1 + + mcr p15, 0, r2, c1, c0, 0 /* MMU control */ + mov pc, lr + nop + nop + nop + nop + nop +1: b 1b + + + .data + +saved_state: + .space 4 * 11 /* r4-14 */ + .space 4 * 4 /* cp15 - MMU control, ttb, dacr, context ID */ +#if defined(CONFIG_OPROFILE) + .space 4 * 4 /* more cp15 - pmnc, ccnt, pmn0, pmn1 */ +#endif +saved_state_end: + diff --git a/arch/arm/mach-msm/idle.h b/arch/arm/mach-msm/idle.h new file mode 100644 index 00000000000..740ef91e519 --- /dev/null +++ b/arch/arm/mach-msm/idle.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2007-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_IDLE_H_ +#define _ARCH_ARM_MACH_MSM_IDLE_H_ + +int msm_arch_idle(void); +int msm_pm_collapse(void); +void msm_pm_collapse_exit(void); + +#ifdef CONFIG_CPU_V7 +void msm_pm_boot_entry(void); +void msm_pm_write_boot_vector(unsigned int cpu, unsigned long address); +extern unsigned long msm_pm_pc_pgd; +#endif + +#endif diff --git a/arch/arm/mach-msm/idle_stats.c b/arch/arm/mach-msm/idle_stats.c new file mode 100644 index 00000000000..6f80e326990 --- /dev/null +++ b/arch/arm/mach-msm/idle_stats.c @@ -0,0 +1,545 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "idle_stats.h" +#include "cpuidle.h" + +/****************************************************************************** + * Debug Definitions + *****************************************************************************/ + +enum { + MSM_IDLE_STATS_DEBUG_API = BIT(0), + MSM_IDLE_STATS_DEBUG_SIGNAL = BIT(1), + MSM_IDLE_STATS_DEBUG_MIGRATION = BIT(2), +}; + +static int msm_idle_stats_debug_mask; +module_param_named( + debug_mask, msm_idle_stats_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); + +/****************************************************************************** + * Driver Definitions + *****************************************************************************/ + +#define MSM_IDLE_STATS_DRIVER_NAME "msm_idle_stats" + +static dev_t msm_idle_stats_dev_nr; +static struct cdev msm_idle_stats_cdev; +static struct class *msm_idle_stats_class; + +/****************************************************************************** + * Device Definitions + *****************************************************************************/ + +struct msm_idle_stats_device { + unsigned int cpu; + struct mutex mutex; + struct notifier_block notifier; + + int64_t collection_expiration; + struct msm_idle_stats stats; + struct hrtimer timer; + + wait_queue_head_t wait_q; + atomic_t collecting; +}; + +static DEFINE_SPINLOCK(msm_idle_stats_devs_lock); +static DEFINE_PER_CPU(struct msm_idle_stats_device *, msm_idle_stats_devs); + +/****************************************************************************** + * + *****************************************************************************/ + +static inline int64_t msm_idle_stats_bound_interval(int64_t interval) +{ + if (interval <= 0) + return 1; + + if (interval > UINT_MAX) + return UINT_MAX; + + return interval; +} + +static enum hrtimer_restart msm_idle_stats_timer(struct hrtimer *timer) +{ + struct msm_idle_stats_device *stats_dev; + unsigned int cpu; + int64_t now; + int64_t interval; + + stats_dev = container_of(timer, struct msm_idle_stats_device, timer); + cpu = get_cpu(); + + if (cpu != stats_dev->cpu) { + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_MIGRATION) + pr_info("%s: timer migrated from cpu%u to cpu%u\n", + __func__, stats_dev->cpu, cpu); + + stats_dev->stats.event = MSM_IDLE_STATS_EVENT_TIMER_MIGRATED; + goto timer_exit; + } + + now = ktime_to_us(ktime_get()); + interval = now - stats_dev->stats.last_busy_start; + + if (stats_dev->stats.busy_timer > 0 && + interval >= stats_dev->stats.busy_timer - 1) + stats_dev->stats.event = + MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED; + else + stats_dev->stats.event = + MSM_IDLE_STATS_EVENT_COLLECTION_TIMER_EXPIRED; + +timer_exit: + atomic_set(&stats_dev->collecting, 0); + wake_up_interruptible(&stats_dev->wait_q); + + put_cpu(); + return HRTIMER_NORESTART; +} + +static void msm_idle_stats_pre_idle(struct msm_idle_stats_device *stats_dev) +{ + int64_t now; + int64_t interval; + + if (smp_processor_id() != stats_dev->cpu) { + WARN_ON(1); + return; + } + + if (!atomic_read(&stats_dev->collecting)) + return; + + hrtimer_cancel(&stats_dev->timer); + + now = ktime_to_us(ktime_get()); + interval = now - stats_dev->stats.last_busy_start; + interval = msm_idle_stats_bound_interval(interval); + + stats_dev->stats.busy_intervals[stats_dev->stats.nr_collected] + = (__u32) interval; + stats_dev->stats.last_idle_start = now; +} + +static void msm_idle_stats_post_idle(struct msm_idle_stats_device *stats_dev) +{ + int64_t now; + int64_t interval; + int64_t timer_interval; + int rc; + + if (smp_processor_id() != stats_dev->cpu) { + WARN_ON(1); + return; + } + + if (!atomic_read(&stats_dev->collecting)) + return; + + now = ktime_to_us(ktime_get()); + interval = now - stats_dev->stats.last_idle_start; + interval = msm_idle_stats_bound_interval(interval); + + stats_dev->stats.idle_intervals[stats_dev->stats.nr_collected] + = (__u32) interval; + stats_dev->stats.nr_collected++; + stats_dev->stats.last_busy_start = now; + + if (stats_dev->stats.nr_collected >= MSM_IDLE_STATS_NR_MAX_INTERVALS) { + stats_dev->stats.event = MSM_IDLE_STATS_EVENT_COLLECTION_FULL; + goto post_idle_collection_done; + } + + timer_interval = stats_dev->collection_expiration - now; + if (timer_interval <= 0) { + stats_dev->stats.event = + MSM_IDLE_STATS_EVENT_COLLECTION_TIMER_EXPIRED; + goto post_idle_collection_done; + } + + if (stats_dev->stats.busy_timer > 0 && + timer_interval > stats_dev->stats.busy_timer) + timer_interval = stats_dev->stats.busy_timer; + + rc = hrtimer_start(&stats_dev->timer, + ktime_set(0, timer_interval * 1000), HRTIMER_MODE_REL_PINNED); + WARN_ON(rc); + + return; + +post_idle_collection_done: + atomic_set(&stats_dev->collecting, 0); + wake_up_interruptible(&stats_dev->wait_q); +} + +static int msm_idle_stats_notified(struct notifier_block *nb, + unsigned long val, void *v) +{ + struct msm_idle_stats_device *stats_dev = container_of( + nb, struct msm_idle_stats_device, notifier); + + if (val == MSM_CPUIDLE_STATE_EXIT) + msm_idle_stats_post_idle(stats_dev); + else + msm_idle_stats_pre_idle(stats_dev); + + return 0; +} + +static int msm_idle_stats_collect(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct msm_idle_stats_device *stats_dev; + struct msm_idle_stats *stats; + int rc; + + stats_dev = (struct msm_idle_stats_device *) filp->private_data; + stats = &stats_dev->stats; + + rc = mutex_lock_interruptible(&stats_dev->mutex); + if (rc) { + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_SIGNAL) + pr_info("%s: interrupted while waiting on device " + "mutex\n", __func__); + + rc = -EINTR; + goto collect_exit; + } + + if (atomic_read(&stats_dev->collecting)) { + pr_err("%s: inconsistent state\n", __func__); + rc = -EBUSY; + goto collect_unlock_exit; + } + + rc = copy_from_user(stats, (void *)arg, sizeof(*stats)); + if (rc) { + rc = -EFAULT; + goto collect_unlock_exit; + } + + if (stats->nr_collected >= MSM_IDLE_STATS_NR_MAX_INTERVALS || + stats->busy_timer > MSM_IDLE_STATS_MAX_TIMER || + stats->collection_timer > MSM_IDLE_STATS_MAX_TIMER) { + rc = -EINVAL; + goto collect_unlock_exit; + } + + if (get_cpu() != stats_dev->cpu) { + put_cpu(); + rc = -EACCES; + goto collect_unlock_exit; + } + + /* + * When collection_timer == 0, stop collecting at the next + * post idle. + */ + stats_dev->collection_expiration = + ktime_to_us(ktime_get()) + stats->collection_timer; + + /* + * Enable collection before starting any timer. + */ + atomic_set(&stats_dev->collecting, 1); + + /* + * When busy_timer == 0, do not set any busy timer. + */ + if (stats->busy_timer > 0) { + rc = hrtimer_start(&stats_dev->timer, + ktime_set(0, stats->busy_timer * 1000), + HRTIMER_MODE_REL_PINNED); + WARN_ON(rc); + } + + put_cpu(); + if (wait_event_interruptible(stats_dev->wait_q, + !atomic_read(&stats_dev->collecting))) { + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_SIGNAL) + pr_info("%s: interrupted while waiting on " + "collection\n", __func__); + + hrtimer_cancel(&stats_dev->timer); + atomic_set(&stats_dev->collecting, 0); + + rc = -EINTR; + goto collect_unlock_exit; + } + + stats->return_timestamp = ktime_to_us(ktime_get()); + + rc = copy_to_user((void *)arg, stats, sizeof(*stats)); + if (rc) { + rc = -EFAULT; + goto collect_unlock_exit; + } + +collect_unlock_exit: + mutex_unlock(&stats_dev->mutex); + +collect_exit: + return rc; +} + +static int msm_idle_stats_open(struct inode *inode, struct file *filp) +{ + struct msm_idle_stats_device *stats_dev; + int rc; + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: enter\n", __func__); + + rc = nonseekable_open(inode, filp); + if (rc) { + pr_err("%s: failed to set nonseekable\n", __func__); + goto open_bail; + } + + stats_dev = (struct msm_idle_stats_device *) + kzalloc(sizeof(*stats_dev), GFP_KERNEL); + if (!stats_dev) { + pr_err("%s: failed to allocate device struct\n", __func__); + rc = -ENOMEM; + goto open_bail; + } + + stats_dev->cpu = MINOR(inode->i_rdev); + mutex_init(&stats_dev->mutex); + stats_dev->notifier.notifier_call = msm_idle_stats_notified; + hrtimer_init(&stats_dev->timer, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED); + stats_dev->timer.function = msm_idle_stats_timer; + init_waitqueue_head(&stats_dev->wait_q); + atomic_set(&stats_dev->collecting, 0); + + filp->private_data = stats_dev; + + /* + * Make sure only one device exists per cpu. + */ + spin_lock(&msm_idle_stats_devs_lock); + if (per_cpu(msm_idle_stats_devs, stats_dev->cpu)) { + spin_unlock(&msm_idle_stats_devs_lock); + rc = -EBUSY; + goto open_free_bail; + } + + per_cpu(msm_idle_stats_devs, stats_dev->cpu) = stats_dev; + spin_unlock(&msm_idle_stats_devs_lock); + + rc = msm_cpuidle_register_notifier(stats_dev->cpu, + &stats_dev->notifier); + if (rc) { + pr_err("%s: failed to register idle notification\n", __func__); + goto open_null_bail; + } + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: done\n", __func__); + return 0; + +open_null_bail: + spin_lock(&msm_idle_stats_devs_lock); + per_cpu(msm_idle_stats_devs, stats_dev->cpu) = NULL; + spin_unlock(&msm_idle_stats_devs_lock); + +open_free_bail: + kfree(stats_dev); + +open_bail: + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: exit, %d\n", __func__, rc); + return rc; +} + +static int msm_idle_stats_release(struct inode *inode, struct file *filp) +{ + struct msm_idle_stats_device *stats_dev; + int rc; + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: enter\n", __func__); + + stats_dev = (struct msm_idle_stats_device *) filp->private_data; + rc = msm_cpuidle_unregister_notifier(stats_dev->cpu, + &stats_dev->notifier); + WARN_ON(rc); + + spin_lock(&msm_idle_stats_devs_lock); + per_cpu(msm_idle_stats_devs, stats_dev->cpu) = NULL; + spin_unlock(&msm_idle_stats_devs_lock); + filp->private_data = NULL; + + hrtimer_cancel(&stats_dev->timer); + kfree(stats_dev); + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: done\n", __func__); + return 0; +} + +static long msm_idle_stats_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int rc; + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: enter\n", __func__); + + switch (cmd) { + case MSM_IDLE_STATS_IOC_COLLECT: + rc = msm_idle_stats_collect(filp, cmd, arg); + break; + + default: + rc = -ENOTTY; + break; + } + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: exit, %d\n", __func__, rc); + return rc; +} + +/****************************************************************************** + * + *****************************************************************************/ + +static const struct file_operations msm_idle_stats_fops = { + .owner = THIS_MODULE, + .open = msm_idle_stats_open, + .release = msm_idle_stats_release, + .unlocked_ioctl = msm_idle_stats_ioctl, +}; + +static int __init msm_idle_stats_init(void) +{ + unsigned int nr_cpus = num_possible_cpus(); + struct device *dev; + int rc; + int i; + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: enter\n", __func__); + + rc = alloc_chrdev_region(&msm_idle_stats_dev_nr, + 0, nr_cpus, MSM_IDLE_STATS_DRIVER_NAME); + if (rc) { + pr_err("%s: failed to allocate device number, rc %d\n", + __func__, rc); + goto init_bail; + } + + msm_idle_stats_class = class_create(THIS_MODULE, + MSM_IDLE_STATS_DRIVER_NAME); + if (IS_ERR(msm_idle_stats_class)) { + pr_err("%s: failed to create device class\n", __func__); + rc = -ENOMEM; + goto init_unreg_bail; + } + + for (i = 0; i < nr_cpus; i++) { + dev = device_create(msm_idle_stats_class, NULL, + msm_idle_stats_dev_nr + i, NULL, + MSM_IDLE_STATS_DRIVER_NAME "%d", i); + + if (!dev) { + pr_err("%s: failed to create device %d\n", + __func__, i); + rc = -ENOMEM; + goto init_remove_bail; + } + } + + cdev_init(&msm_idle_stats_cdev, &msm_idle_stats_fops); + msm_idle_stats_cdev.owner = THIS_MODULE; + + /* + * Call cdev_add() last, after everything else is initialized and + * the driver is ready to accept system calls. + */ + rc = cdev_add(&msm_idle_stats_cdev, msm_idle_stats_dev_nr, nr_cpus); + if (rc) { + pr_err("%s: failed to register char device, rc %d\n", + __func__, rc); + goto init_remove_bail; + } + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: done\n", __func__); + return 0; + +init_remove_bail: + for (i = i - 1; i >= 0; i--) + device_destroy( + msm_idle_stats_class, msm_idle_stats_dev_nr + i); + + class_destroy(msm_idle_stats_class); + +init_unreg_bail: + unregister_chrdev_region(msm_idle_stats_dev_nr, nr_cpus); + +init_bail: + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: exit, %d\n", __func__, rc); + return rc; +} + +static void __exit msm_idle_stats_exit(void) +{ + unsigned int nr_cpus = num_possible_cpus(); + int i; + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: enter\n", __func__); + + cdev_del(&msm_idle_stats_cdev); + + for (i = nr_cpus - 1; i >= 0; i--) + device_destroy( + msm_idle_stats_class, msm_idle_stats_dev_nr + i); + + class_destroy(msm_idle_stats_class); + unregister_chrdev_region(msm_idle_stats_dev_nr, nr_cpus); + + if (msm_idle_stats_debug_mask & MSM_IDLE_STATS_DEBUG_API) + pr_info("%s: done\n", __func__); +} + +module_init(msm_idle_stats_init); +module_exit(msm_idle_stats_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("idle stats driver"); +MODULE_VERSION("1.0"); diff --git a/arch/arm/mach-msm/idle_stats.h b/arch/arm/mach-msm/idle_stats.h new file mode 100644 index 00000000000..6c8db1eb306 --- /dev/null +++ b/arch/arm/mach-msm/idle_stats.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_IDLE_STATS_H +#define __ARCH_ARM_MACH_MSM_IDLE_STATS_H + +#include +#include + +enum msm_idle_stats_event { + MSM_IDLE_STATS_EVENT_BUSY_TIMER_EXPIRED = 1, + MSM_IDLE_STATS_EVENT_COLLECTION_TIMER_EXPIRED = 2, + MSM_IDLE_STATS_EVENT_COLLECTION_FULL = 3, + MSM_IDLE_STATS_EVENT_TIMER_MIGRATED = 4, +}; + +/* + * All time, timer, and time interval values are in units of + * microseconds unless stated otherwise. + */ +#define MSM_IDLE_STATS_NR_MAX_INTERVALS 100 +#define MSM_IDLE_STATS_MAX_TIMER 1000000 + +struct msm_idle_stats { + __u32 busy_timer; + __u32 collection_timer; + + __u32 busy_intervals[MSM_IDLE_STATS_NR_MAX_INTERVALS]; + __u32 idle_intervals[MSM_IDLE_STATS_NR_MAX_INTERVALS]; + __u32 nr_collected; + __s64 last_busy_start; + __s64 last_idle_start; + + enum msm_idle_stats_event event; + __s64 return_timestamp; +}; + +#define MSM_IDLE_STATS_IOC_MAGIC 0xD8 +#define MSM_IDLE_STATS_IOC_COLLECT \ + _IOWR(MSM_IDLE_STATS_IOC_MAGIC, 1, struct msm_idle_stats) + +#endif /* __ARCH_ARM_MACH_MSM_IDLE_STATS_H */ diff --git a/arch/arm/mach-msm/include/mach/audio_dma_msm8k.h b/arch/arm/mach-msm/include/mach/audio_dma_msm8k.h new file mode 100644 index 00000000000..5c9bfb5b84c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/audio_dma_msm8k.h @@ -0,0 +1,216 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_MSM_AUDIO_DMA_H + + +#define BANK_OFFSET 0x1000 + +#define LPAIF_PCM_CTL_OFFSET 0x0000 + #define CTRL_DATA_OE (1 << 18) + #define RATE_8KHZ (0 << 15) + #define RATE_16KHZ (1 << 15) + #define RATE_32KHZ (2 << 15) + #define RATE_64KHZ (4 << 15) + #define RATE_128KHZ (8 << 15) + #define RATE_256KHZ (9 << 15) + #define PCM_LOOPBACK (1 << 14) + #define SYNC_SRC_INT (0 << 13) + #define SYNC_SRC_EXT (1 << 13) + #define PCM_MODE (0 << 12) + #define AUX_MODE (1 << 12) + #define RPCM_WIDTH_8 (0 << 11) + #define RPCM_WIDTH_16 (1 << 11) + #define TPCM_WIDTH_8 (0 << 10) + #define TPCM_WIDTH_16 (1 << 10) + #define RPCM_SLOT(x) (x << 5) + #define TPCM_SLOT(x) x + +#define LPAIF_I2S_CTL_OFFSET(x) (0x0004 + (0x4 * x)) + #define I2S_LOOPBACK (1 << 15) + #define SPK_EN_DISABLE (0 << 14) + #define SPK_EN_ENABLE (1 << 14) + #define SPK_MODE_NONE (0 << 10) + #define SPK_MODE_SD0 (1 << 10) + #define SPK_MODE_SD1 (2 << 10) + #define SPK_MODE_SD2 (3 << 10) + #define SPK_MODE_SD3 (4 << 10) + #define SPK_MODE_QUAD01 (5 << 10) + #define SPK_MODE_QUAD23 (6 << 10) + #define SPK_MODE_6CH (7 << 10) + #define SPK_MODE_8CH (8 << 10) + #define SPK_MONO_STEREO (0 << 9) + #define SPK_MONO_MONO (1 << 9) + #define MIC_EN_DISABLE (0 << 8) + #define MIC_EN_ENABLE (1 << 8) + #define MIC_MODE_NONE (0 << 4) + #define MIC_MODE_SD0 (1 << 4) + #define MIC_MODE_SD1 (2 << 4) + #define MIC_MODE_SD2 (3 << 4) + #define MIC_MODE_SD3 (4 << 4) + #define MIC_MODE_QUAD01 (5 << 4) + #define MIC_MODE_QUAD23 (6 << 4) + #define MIC_MODE_6CH (7 << 4) + #define MIC_MODE_8CH (8 << 4) + #define MIC_MONO_STEREO (0 << 3) + #define MIC_MONO_MONO (1 << 3) + #define WS_SRC_INT (0 << 2) + #define WS_SRC_EXT (1 << 2) + #define BIT_WIDTH_16 (0 << 0) + #define BIT_WIDTH_24 (1 << 0) + #define BIT_WIDTH_32 (2 << 0) + +#define LPAIF_DMIC_CTL 0x0018 + #define DMIC_EN_DISABLE (0 << 4) + #define DMIC_EN_ENABLE (1 << 4) + #define DMIC_MODE_NONE (0 << 1) + #define DMIC_MODE_LEFT0 (1 << 1) + #define DMIC_MODE_RIGHT0 (2 << 1) + #define DMIC_MODE_LEFT1 (3 << 1) + #define DMIC_MODE_RIGHT1 (4 << 1) + #define DMIC_MODE_STEREO0 (5 << 1) + #define DMIC_MODE_STEREO1 (6 << 1) + #define DMIC_MODE_QUAD (7 << 1) + #define BIT_WIDTH_DMIC_16 (0 << 0) + #define BIT_WIDTH_DMIC_20 (1 << 0) + +#define LPAIF_DMIC_VOL_CTL(x) (0x001c + (0x4 * x)) + #define UPDATE_STATUS_COMP (0 << 20) + #define UPDATE_STATUS_PEND (1 << 20) /* Timeout or Zero Crossing */ + #define UPDATE_GAIN_NO (0 << 19) + #define UPDATE_GAIN_YES (1 << 19) + #define TX_HPF_BP_DC_BLOCK (0 << 18) + #define TX_HPF_BP_BYPASS_DC_BLOCK (1 << 18) + #define DMIC_GAIN_BP_GAIN (0 << 17) + #define DMIC_GAIN_BP_BYPASS_GAIN (1 << 17) + #define MUTE_EN_NORMAL (0 << 16) + #define MUTE_EN_MUTE (1 << 16) + #define TIMEOUT_VAL(x) (x << 8) + #define DMIC_GAIN_MUL(x) (x << 0) + +#define LPAIF_SPARE 0x0030 + +#define LPAIF_WRDMA_LPBK_MIX 0x1000 + #define WRDMA_LPBK_MIX_BLOCK(x) (0 << (x - 5)) + #define WRDMA_LPBK_MIX_ALLOW(x) (1 << (x - 5)) + +#define LPAIF_DEBUG_CTL 0x1004 + #define TESTMODE_OFF (0 << 4) + #define TESTMODE_ON (1 << 4) + #define TESTSEL_CH0 (0 << 0) + #define TESTSEL_CH1 (1 << 0) + #define TESTSEL_CH2 (2 << 0) + #define TESTSEL_CH3 (3 << 0) + #define TESTSEL_CH4 (4 << 0) + #define TESTSEL_CH5 (5 << 0) + #define TESTSEL_CH6 (6 << 0) + #define TESTSEL_CH7 (7 << 0) + #define TESTSEL_CH8 (8 << 0) + #define TESTSEL_MIXER (9 << 0) + #define TESTSEL_CODEC_SPKR (10 << 0) + #define TESTSEL_CODEC_MIC (11 << 0) + #define TESTSEL_MI2S (12 << 0) + #define TESTSEL_SEC_SPKR (13 << 0) + #define TESTSEL_SEC_MIC (14 << 0) + #define TESTSEL_DMIC (15 << 0) + +#define LPAIF_MIXER_CTL 0x2000 + #define OVR_DETECTED_NO (0 << 10) + #define OVR_DETECTED_YES (1 << 10) + #define OVR_CLR_NO (0 << 9) + #define OVR_CLR_YES (1 << 9) + #define SAT_EN_DISABLE (0 << 8) + #define SAT_EN_ENABLE (1 << 8) + #define MIXER_BIT_WIDTH_8 (0 << 6) + #define MIXER_BIT_WIDTH_16 (1 << 6) + #define MIXER_BIT_WIDTH_24 (2 << 6) + #define MIXER_BIT_WIDTH_32 (3 << 6) + #define PORT1_CH_NONE (0 << 3) + #define PORT1_CH_0 (1 << 3) + #define PORT1_CH_1 (2 << 3) + #define PORT1_CH_2 (3 << 3) + #define PORT1_CH_3 (4 << 3) + #define PORT1_CH_4 (5 << 3) + #define PORT0_CH_NONE (0 << 0) + #define PORT0_CH_0 (1 << 0) + #define PORT0_CH_1 (2 << 0) + #define PORT0_CH_2 (3 << 0) + #define PORT0_CH_3 (4 << 0) + #define PORT0_CH_4 (5 << 0) + +#define DMA_IRQ_BASE 0x3000 +#define DMA_IRQ_INDEX(x) (BANK_OFFSET * x) +#define DMA_IRQ_ADDR(irq, addr) (DMA_IRQ_BASE \ + + DMA_IRQ_INDEX(irq) + addr) + +/* Audio Interrupt registers for DMA channel confuguration */ +#define LPAIF_IRQ_EN(x) DMA_IRQ_ADDR(x, 0x00) +#define LPAIF_IRQ_STAT(x) DMA_IRQ_ADDR(x, 0x04) +#define LPAIF_IRQ_RAW_STAT(x) DMA_IRQ_ADDR(x, 0x08) +#define LPAIF_IRQ_CLEAR(x) DMA_IRQ_ADDR(x, 0x0c) +#define LPAIF_IRQ_FORCE(x) DMA_IRQ_ADDR(x, 0x10) + #define PER_CH(x) (1 << (3 * x)) + #define UNDER_CH(x) (2 << (3 * x)) + #define ERR_CH(x) (4 << (3 * x)) + +/* Audio DMA registers for DMA channel confuguration */ +#define DMA_CH_CTL_BASE 0x6000 +#define DMA_CH_INDEX(ch) (BANK_OFFSET * ch) + +#define DMA_CTRL_ADDR(ch, addr) (DMA_CH_CTL_BASE \ + + (DMA_CH_INDEX(ch) + addr)) + +#define LPAIF_DMA_CTL(x) DMA_CTRL_ADDR(x, 0x00) + #define BURST_EN (1 << 11) + #define WPSCNT_ONE (0 << 8) + #define WPSCNT_TWO (1 << 8) + #define WPSCNT_THREE (2 << 8) + #define WPSCNT_FOUR (3 << 8) + #define WPSCNT_SIX (5 << 8) + #define WPSCNT_EIGHT (7 << 8) + #define AUDIO_INTF_NONE (0 << 4) + #define AUDIO_INTF_CODEC (1 << 4) + #define AUDIO_INTF_PCM (2 << 4) + #define AUDIO_INTF_SEC_I2S (3 << 4) + #define AUDIO_INTF_MI2S (4 << 4) + #define AUDIO_INTF_HDMI (5 << 4) + #define AUDIO_INTF_MIXOUT (6 << 4) + #define AUDIO_INTF_LOOPBACK1 (7 << 4) + #define AUDIO_INTF_LOOPBACK2 (8 << 4) + #define FIFO_WATERMRK(x) ((x & 0x7) << 1) + #define ENABLE (1 << 0) + +#define LPAIF_DMA_BASE(x) DMA_CTRL_ADDR(x, 0x04) + #define BASE_ADDR (0xFFFFFFFF << 4) + +#define LPAIF_DMA_BUFF_LEN(x) DMA_CTRL_ADDR(x, 0x08) +#define LPAIF_DMA_CURR_ADDR(x) DMA_CTRL_ADDR(x, 0x0c) +#define LPAIF_DMA_PER_LEN(x) DMA_CTRL_ADDR(x, 0x10) +#define LPAIF_DMA_PER_CNT(x) DMA_CTRL_ADDR(x, 0x14) +#define LPAIF_DMA_FRM(x) DMA_CTRL_ADDR(x, 0x18) +#define LPAIF_DMA_FRMCLR(x) DMA_CTRL_ADDR(x, 0x1c) +#define LPAIF_DMA_SET_BUFF_CNT(x) DMA_CTRL_ADDR(x, 0x20) +#define LPAIF_DMA_SET_PER_CNT(x) DMA_CTRL_ADDR(x, 0x24) + +/* channel assignments */ + +#define DMA_CH_0 0 +#define DMA_CH_1 1 +#define DMA_CH_2 2 +#define DMA_CH_3 3 +#define DMA_CH_4 4 +#define DMA_CH_5 5 +#define DMA_CH_6 6 +#define DMA_CH_7 7 + +#endif diff --git a/arch/arm/mach-msm/include/mach/bam_dmux.h b/arch/arm/mach-msm/include/mach/bam_dmux.h new file mode 100644 index 00000000000..e4745953bea --- /dev/null +++ b/arch/arm/mach-msm/include/mach/bam_dmux.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#ifndef _BAM_DMUX_H +#define _BAM_DMUX_H + +enum { + BAM_DMUX_DATA_RMNET_0, + BAM_DMUX_DATA_RMNET_1, + BAM_DMUX_DATA_RMNET_2, + BAM_DMUX_DATA_RMNET_3, + BAM_DMUX_DATA_RMNET_4, + BAM_DMUX_DATA_RMNET_5, + BAM_DMUX_DATA_RMNET_6, + BAM_DMUX_DATA_RMNET_7, + BAM_DMUX_USB_RMNET_0, + BAM_DMUX_NUM_CHANNELS +}; + +int msm_bam_dmux_open(uint32_t id, void *priv, + void (*receive_cb)(void *, struct sk_buff *), + void (*write_done)(void *, struct sk_buff *)); + +int msm_bam_dmux_close(uint32_t id); + +int msm_bam_dmux_write(uint32_t id, struct sk_buff *skb); + +#endif /* _BAM_DMUX_H */ diff --git a/arch/arm/mach-msm/include/mach/barriers.h b/arch/arm/mach-msm/include/mach/barriers.h new file mode 100644 index 00000000000..919186bf4e2 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/barriers.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#define mb() do \ + { \ + dsb();\ + outer_sync(); \ + write_to_strongly_ordered_memory(); \ + } while (0) +#define rmb() do { dmb(); write_to_strongly_ordered_memory(); } while (0) +#define wmb() mb() diff --git a/arch/arm/mach-msm/include/mach/bcm_bt_lpm.h b/arch/arm/mach-msm/include/mach/bcm_bt_lpm.h new file mode 100644 index 00000000000..c2242971880 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/bcm_bt_lpm.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ASM_ARCH_BCM_BT_LPM_H +#define __ASM_ARCH_BCM_BT_LPM_H + +#include + +/* Uart driver must call this every time it beings TX, to ensure + * this driver keeps WAKE asserted during TX. Called with uart + * spinlock held. */ +extern void bcm_bt_lpm_exit_lpm_locked(struct uart_port *uport); + +struct bcm_bt_lpm_platform_data { + unsigned int gpio_wake; /* CPU -> BCM wakeup gpio */ + unsigned int gpio_host_wake; /* BCM -> CPU wakeup gpio */ + + /* Callback to request the uart driver to clock off. + * Called with uart spinlock held. */ + void (*request_clock_off_locked)(struct uart_port *uport); + /* Callback to request the uart driver to clock on. + * Called with uart spinlock held. */ + void (*request_clock_on_locked)(struct uart_port *uport); +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/board.h b/arch/arm/mach-msm/include/mach/board.h index 2ce8f1f2fc4..133a987be81 100644 --- a/arch/arm/mach-msm/include/mach/board.h +++ b/arch/arm/mach-msm/include/mach/board.h @@ -1,6 +1,7 @@ /* arch/arm/mach-msm/include/mach/board.h * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -18,33 +19,382 @@ #define __ASM_ARCH_MSM_BOARD_H #include -#include +#include +#include +#include +#include +#include +#ifdef CONFIG_MSM_BUS_SCALING +#include +#endif /* platform device data structures */ - -struct msm_acpu_clock_platform_data -{ +struct msm_acpu_clock_platform_data { uint32_t acpu_switch_time_us; uint32_t max_speed_delta_khz; uint32_t vdd_switch_time_us; - unsigned long power_collapse_khz; - unsigned long wait_for_irq_khz; + unsigned int max_axi_khz; + unsigned int max_vdd; + int (*acpu_set_vdd) (int mvolts); +}; + +struct msm_camera_io_ext { + uint32_t mdcphy; + uint32_t mdcsz; + uint32_t appphy; + uint32_t appsz; + uint32_t camifpadphy; + uint32_t camifpadsz; + uint32_t csiphy; + uint32_t csisz; + uint32_t csiirq; + uint32_t csiphyphy; + uint32_t csiphysz; + uint32_t csiphyirq; + uint32_t ispifphy; + uint32_t ispifsz; + uint32_t ispifirq; +}; + +struct msm_camera_io_clk { + uint32_t mclk_clk_rate; + uint32_t vfe_clk_rate; +}; + +struct msm_camera_device_platform_data { + int (*camera_gpio_on) (void); + void (*camera_gpio_off)(void); + struct msm_camera_io_ext ioext; + struct msm_camera_io_clk ioclk; + uint8_t csid_core; +#ifdef CONFIG_MSM_BUS_SCALING + struct msm_bus_scale_pdata *cam_bus_scale_table; +#endif +}; +enum msm_camera_csi_data_format { + CSI_8BIT, + CSI_10BIT, + CSI_12BIT, +}; +struct msm_camera_csi_params { + enum msm_camera_csi_data_format data_format; + uint8_t lane_cnt; + uint8_t lane_assign; + uint8_t settle_cnt; + uint8_t dpcm_scheme; }; +#ifdef CONFIG_SENSORS_MT9T013 +struct msm_camera_legacy_device_platform_data { + int sensor_reset; + int sensor_pwd; + int vcm_pwd; + void (*config_gpio_on) (void); + void (*config_gpio_off)(void); +}; +#endif + +#define MSM_CAMERA_FLASH_NONE 0 +#define MSM_CAMERA_FLASH_LED 1 + +#define MSM_CAMERA_FLASH_SRC_PMIC (0x00000001<<0) +#define MSM_CAMERA_FLASH_SRC_PWM (0x00000001<<1) +#define MSM_CAMERA_FLASH_SRC_CURRENT_DRIVER (0x00000001<<2) + +struct msm_camera_sensor_flash_pmic { + uint8_t num_of_src; + uint32_t low_current; + uint32_t high_current; + enum pmic8058_leds led_src_1; + enum pmic8058_leds led_src_2; + int (*pmic_set_current)(enum pmic8058_leds id, unsigned mA); +}; + +struct msm_camera_sensor_flash_pwm { + uint32_t freq; + uint32_t max_load; + uint32_t low_load; + uint32_t high_load; + uint32_t channel; +}; + +struct pmic8058_leds_platform_data; +struct msm_camera_sensor_flash_current_driver { + uint32_t low_current; + uint32_t high_current; + const struct pmic8058_leds_platform_data *driver_channel; + uint32_t led1; + uint32_t led2; +}; + +struct msm_camera_sensor_flash_src { + int flash_sr_type; + + union { + struct msm_camera_sensor_flash_pmic pmic_src; + struct msm_camera_sensor_flash_pwm pwm_src; + struct msm_camera_sensor_flash_current_driver + current_driver_src; + } _fsrc; +}; + +struct msm_camera_sensor_flash_data { + int flash_type; + struct msm_camera_sensor_flash_src *flash_src; +}; + +struct msm_camera_sensor_strobe_flash_data { + uint8_t flash_trigger; + uint8_t flash_charge; /* pin for charge */ + uint8_t flash_charge_done; + uint32_t flash_recharge_duration; + uint32_t irq; + spinlock_t spin_lock; + spinlock_t timer_lock; + int state; +}; + +struct msm_camera_sensor_platform_info { + int mount_angle; +}; + +struct msm_camera_sensor_info { + const char *sensor_name; + int sensor_reset_enable; + int sensor_reset; + int sensor_pwd; + int vcm_pwd; + int vcm_enable; + int mclk; + int flash_type; + struct msm_camera_sensor_platform_info *sensor_platform_info; + struct msm_camera_device_platform_data *pdata; + struct resource *resource; + uint8_t num_resources; + struct msm_camera_sensor_flash_data *flash_data; + int csi_if; + struct msm_camera_csi_params csi_params; + struct msm_camera_sensor_strobe_flash_data *strobe_flash_data; + char *eeprom_data; +}; + +int __init msm_get_cam_resources(struct msm_camera_sensor_info *); + struct clk_lookup; -extern struct sys_timer msm_timer; +struct snd_endpoint { + int id; + const char *name; +}; + +struct msm_snd_endpoints { + struct snd_endpoint *endpoints; + unsigned num; +}; + +#define MSM_MAX_DEC_CNT 14 +/* 7k target ADSP information */ +/* Bit 23:0, for codec identification like mp3, wav etc * + * Bit 27:24, for mode identification like tunnel, non tunnel* + * bit 31:28, for operation support like DM, DMA */ +enum msm_adspdec_concurrency { + MSM_ADSP_CODEC_WAV = 0, + MSM_ADSP_CODEC_ADPCM = 1, + MSM_ADSP_CODEC_MP3 = 2, + MSM_ADSP_CODEC_REALAUDIO = 3, + MSM_ADSP_CODEC_WMA = 4, + MSM_ADSP_CODEC_AAC = 5, + MSM_ADSP_CODEC_RESERVED = 6, + MSM_ADSP_CODEC_MIDI = 7, + MSM_ADSP_CODEC_YADPCM = 8, + MSM_ADSP_CODEC_QCELP = 9, + MSM_ADSP_CODEC_AMRNB = 10, + MSM_ADSP_CODEC_AMRWB = 11, + MSM_ADSP_CODEC_EVRC = 12, + MSM_ADSP_CODEC_WMAPRO = 13, + MSM_ADSP_MODE_TUNNEL = 24, + MSM_ADSP_MODE_NONTUNNEL = 25, + MSM_ADSP_MODE_LP = 26, + MSM_ADSP_OP_DMA = 28, + MSM_ADSP_OP_DM = 29, +}; + +struct msm_adspdec_info { + const char *module_name; + unsigned module_queueid; + int module_decid; /* objid */ + unsigned nr_codec_support; +}; + +/* Carries information about number codec + * supported if same codec or different codecs + */ +struct dec_instance_table { + uint8_t max_instances_same_dec; + uint8_t max_instances_diff_dec; +}; + +struct msm_adspdec_database { + unsigned num_dec; + unsigned num_concurrency_support; + unsigned int *dec_concurrency_table; /* Bit masked entry to * + * represents codec, mode etc */ + struct msm_adspdec_info *dec_info_list; + struct dec_instance_table *dec_instance_list; +}; +enum msm_mdp_hw_revision { + MDP_REV_20 = 1, + MDP_REV_22, + MDP_REV_30, + MDP_REV_303, + MDP_REV_31, + MDP_REV_40, + MDP_REV_41, + MDP_REV_42, +}; + +struct msm_panel_common_pdata { + uintptr_t hw_revision_addr; + int gpio; + int (*backlight_level)(int level, int max, int min); + int (*pmic_backlight)(int level); + int (*panel_num)(void); + void (*panel_config_gpio)(int); + int (*vga_switch)(int select_vga); + int *gpio_num; + int mdp_core_clk_rate; + unsigned num_mdp_clk; + int *mdp_core_clk_table; +#ifdef CONFIG_MSM_BUS_SCALING + struct msm_bus_scale_pdata *mdp_bus_scale_table; +#endif + int mdp_rev; +}; + +struct lcdc_platform_data { + int (*lcdc_gpio_config)(int on); + int (*lcdc_power_save)(int); + unsigned int (*lcdc_get_clk)(void); +#ifdef CONFIG_MSM_BUS_SCALING + struct msm_bus_scale_pdata *bus_scale_table; +#endif +}; + +struct tvenc_platform_data { + int poll; + int (*pm_vid_en)(int on); +#ifdef CONFIG_MSM_BUS_SCALING + struct msm_bus_scale_pdata *bus_scale_table; +#endif +}; + +struct mddi_platform_data { + int (*mddi_power_save)(int on); + int (*mddi_sel_clk)(u32 *clk_rate); + int (*mddi_client_power)(u32 client_id); +}; + +struct mipi_dsi_platform_data { + int vsync_gpio; + int (*dsi_power_save)(int on); + int (*dsi_client_reset)(void); + int (*get_lane_config)(void); + int target_type; +}; + +struct mipi_dsi_novatek_platform_data { + int fpga_3d_config_addr; +}; + +struct msm_fb_platform_data { + int (*detect_client)(const char *name); + int mddi_prescan; + int (*allow_set_offset)(void); +}; + +struct msm_hdmi_platform_data { + int irq; + int (*cable_detect)(int insert); + int (*comm_power)(int on, int show); + int (*enable_5v)(int on); + int (*core_power)(int on, int show); + int (*cec_power)(int on); + int (*init_irq)(void); + bool (*check_hdcp_hw_support)(void); +}; + +struct msm_i2c_platform_data { + int clk_freq; + uint32_t rmutex; + const char *rsl_id; + uint32_t pm_lat; + int pri_clk; + int pri_dat; + int aux_clk; + int aux_dat; + const char *clk; + const char *pclk; + int src_clk_rate; + int use_gsbi_shared_mode; + void (*msm_i2c_config_gpio)(int iface, int config_type); +}; + +struct msm_i2c_ssbi_platform_data { + const char *rsl_id; + enum msm_ssbi_controller_type controller_type; +}; + +struct msm_vidc_platform_data { + int memtype; +#ifdef CONFIG_MSM_BUS_SCALING + struct msm_bus_scale_pdata *vidc_bus_client_pdata; +#endif +}; + +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) +struct isp1763_platform_data { + unsigned reset_gpio; + int (*setup_gpio)(int enable); +}; +#endif /* common init routines for use by arch/arm/mach-msm/board-*.c */ void __init msm_add_devices(void); void __init msm_map_common_io(void); +void __init msm_map_qsd8x50_io(void); +void __init msm_map_msm8x60_io(void); +void __init msm_map_msm8960_io(void); +void __init msm_map_apq8064_io(void); +void __init msm_map_msm7x30_io(void); +void __init msm_map_fsm9xxx_io(void); void __init msm_init_irq(void); -void __init msm_init_gpio(void); -void __init msm_clock_init(struct clk_lookup *clock_tbl, unsigned num_clocks); void __init msm_acpu_clock_init(struct msm_acpu_clock_platform_data *); + +struct mmc_platform_data; int __init msm_add_sdcc(unsigned int controller, - struct msm_mmc_platform_data *plat, - unsigned int stat_irq, unsigned long stat_irq_flags); + struct mmc_platform_data *plat); + +struct msm_usb_host_platform_data; +int __init msm_add_host(unsigned int host, + struct msm_usb_host_platform_data *plat); +#if defined(CONFIG_USB_FUNCTION_MSM_HSUSB) \ + || defined(CONFIG_USB_MSM_72K) || defined(CONFIG_USB_MSM_72K_MODULE) +void msm_hsusb_set_vbus_state(int online); +#else +static inline void msm_hsusb_set_vbus_state(int online) {} +#endif + +void __init msm_snddev_init(void); +void __init msm_snddev_init_timpani(void); +void msm_snddev_poweramp_on(void); +void msm_snddev_poweramp_off(void); +void msm_snddev_hsed_voltage_on(void); +void msm_snddev_hsed_voltage_off(void); +void msm_snddev_tx_route_config(void); +void msm_snddev_tx_route_deconfig(void); + +extern unsigned int msm_shared_ram_phys; /* defined in arch/arm/mach-msm/io.c */ + #endif diff --git a/arch/arm/mach-msm/include/mach/board_htc.h b/arch/arm/mach-msm/include/mach/board_htc.h new file mode 100644 index 00000000000..b537c91b957 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/board_htc.h @@ -0,0 +1,78 @@ +/* arch/arm/mach-msm/include/mach/BOARD_HTC.h + * Copyright (C) 2007-2009 HTC Corporation. + * Author: Thomas Tsai + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __ASM_ARCH_MSM_BOARD_HTC_H +#define __ASM_ARCH_MSM_BOARD_HTC_H + +#include +#include +#include + +struct msm_pmem_setting{ + resource_size_t pmem_start; + resource_size_t pmem_size; + resource_size_t pmem_adsp_start; + resource_size_t pmem_adsp_size; + resource_size_t pmem_gpu0_start; + resource_size_t pmem_gpu0_size; + resource_size_t pmem_gpu1_start; + resource_size_t pmem_gpu1_size; + resource_size_t pmem_camera_start; + resource_size_t pmem_camera_size; + resource_size_t ram_console_start; + resource_size_t ram_console_size; +}; + +enum { + MSM_SERIAL_UART1 = 0, + MSM_SERIAL_UART2, + MSM_SERIAL_UART3, +#ifdef CONFIG_SERIAL_MSM_HS + MSM_SERIAL_UART1DM, + MSM_SERIAL_UART2DM, +#endif + MSM_SERIAL_NUM, +}; + + +/* common init routines for use by arch/arm/mach-msm/board-*.c */ + +void __init msm_add_usb_devices(void (*phy_reset) (void)); +void __init msm_add_mem_devices(struct msm_pmem_setting *setting); +void __init msm_init_pmic_vibrator(void); + +struct mmc_platform_data; +int __init msm_add_sdcc_devices(unsigned int controller, struct mmc_platform_data *plat); +int __init msm_add_serial_devices(unsigned uart); + +#if defined(CONFIG_USB_FUNCTION_MSM_HSUSB) +/* START: add USB connected notify function */ +struct t_usb_status_notifier{ + struct list_head notifier_link; + const char *name; + void (*func)(int online); +}; + int usb_register_notifier(struct t_usb_status_notifier *); + static LIST_HEAD(g_lh_usb_notifier_list); +/* END: add USB connected notify function */ +#endif + +int __init board_mfg_mode(void); +int __init parse_tag_smi(const struct tag *tags); +int __init parse_tag_hwid(const struct tag * tags); +int __init parse_tag_skuid(const struct tag * tags); +int parse_tag_engineerid(const struct tag * tags); + +char *board_serialno(void); + +#endif diff --git a/arch/arm/mach-msm/include/mach/camera.h b/arch/arm/mach-msm/include/mach/camera.h new file mode 100644 index 00000000000..b49300df746 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/camera.h @@ -0,0 +1,631 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM__ARCH_CAMERA_H +#define __ASM__ARCH_CAMERA_H + +#include +#include +#include +#include +#include +#include "linux/types.h" + +#include +#include + +#define CONFIG_MSM_CAMERA_DEBUG +#ifdef CONFIG_MSM_CAMERA_DEBUG +#define CDBG(fmt, args...) pr_debug(fmt, ##args) +#else +#define CDBG(fmt, args...) do { } while (0) +#endif + +#define PAD_TO_2K(a, b) ((!b) ? a : (((a)+2047) & ~2047)) + +#define MSM_CAMERA_MSG 0 +#define MSM_CAMERA_EVT 1 +#define NUM_WB_EXP_NEUTRAL_REGION_LINES 4 +#define NUM_WB_EXP_STAT_OUTPUT_BUFFERS 3 +#define NUM_AUTOFOCUS_MULTI_WINDOW_GRIDS 16 +#define NUM_STAT_OUTPUT_BUFFERS 3 +#define NUM_AF_STAT_OUTPUT_BUFFERS 3 +#define max_control_command_size 260 +#define CROP_LEN 36 + +enum vfe_mode_of_operation{ + VFE_MODE_OF_OPERATION_CONTINUOUS, + VFE_MODE_OF_OPERATION_SNAPSHOT, + VFE_MODE_OF_OPERATION_VIDEO, + VFE_MODE_OF_OPERATION_RAW_SNAPSHOT, + VFE_MODE_OF_OPERATION_ZSL, + VFE_LAST_MODE_OF_OPERATION_ENUM +}; + +enum msm_queue { + MSM_CAM_Q_CTRL, /* control command or control command status */ + MSM_CAM_Q_VFE_EVT, /* adsp event */ + MSM_CAM_Q_VFE_MSG, /* adsp message */ + MSM_CAM_Q_V4L2_REQ, /* v4l2 request */ + MSM_CAM_Q_VPE_MSG, /* vpe message */ + MSM_CAM_Q_PP_MSG, /* pp message */ +}; + +enum vfe_resp_msg { + VFE_EVENT, + VFE_MSG_GENERAL, + VFE_MSG_SNAPSHOT, + VFE_MSG_OUTPUT_P, /* preview (continuous mode ) */ + VFE_MSG_OUTPUT_T, /* thumbnail (snapshot mode )*/ + VFE_MSG_OUTPUT_S, /* main image (snapshot mode )*/ + VFE_MSG_OUTPUT_V, /* video (continuous mode ) */ + VFE_MSG_STATS_AEC, + VFE_MSG_STATS_AF, + VFE_MSG_STATS_AWB, + VFE_MSG_STATS_RS, + VFE_MSG_STATS_CS, + VFE_MSG_STATS_IHIST, + VFE_MSG_STATS_SKIN, + VFE_MSG_STATS_WE, /* AEC + AWB */ + VFE_MSG_SYNC_TIMER0, + VFE_MSG_SYNC_TIMER1, + VFE_MSG_SYNC_TIMER2, + VFE_MSG_COMMON, +}; + +enum vpe_resp_msg { + VPE_MSG_GENERAL, + VPE_MSG_OUTPUT_V, /* video (continuous mode ) */ + VPE_MSG_OUTPUT_ST_L, + VPE_MSG_OUTPUT_ST_R, +}; + +enum msm_camera_type { + BACK_CAMERA_2D, + FRONT_CAMERA_2D, + BACK_CAMERA_3D, +}; + +enum msm_stereo_state { + STEREO_VIDEO_IDLE, + STEREO_VIDEO_ACTIVE, + STEREO_SNAP_IDLE, + STEREO_SNAP_STARTED, + STEREO_SNAP_BUFFER1_PROCESSING, + STEREO_SNAP_BUFFER2_PROCESSING, + STEREO_RAW_SNAP_IDLE, + STEREO_RAW_SNAP_STARTED, +}; + +enum msm_ispif_intftype { + PIX0, + RDI0, + PIX1, + RDI1, + PIX2, + RDI2, +}; + +enum msm_ispif_vc { + VC0, + VC1, + VC2, + VC3, +}; + +enum msm_ispif_cid { + CID0, + CID1, + CID2, + CID3, + CID4, + CID5, + CID6, + CID7, + CID8, + CID9, + CID10, + CID11, + CID12, + CID13, + CID14, + CID15, +}; + +struct msm_ispif_params { + uint8_t intftype; + uint16_t cid_mask; + uint8_t csid; +}; +struct msm_vpe_phy_info { + uint32_t sbuf_phy; + uint32_t y_phy; + uint32_t cbcr_phy; + uint8_t output_id; /* VFE31_OUTPUT_MODE_PT/S/V */ + uint32_t frame_id; +}; + +struct msm_camera_csid_vc_cfg { + uint8_t cid; + uint8_t dt; + uint8_t decode_format; +}; + +struct msm_camera_csid_lut_params { + uint8_t num_cid; + struct msm_camera_csid_vc_cfg *vc_cfg; +}; + +struct msm_camera_csid_params { + uint8_t lane_cnt; + uint8_t lane_assign; + struct msm_camera_csid_lut_params lut_params; +}; + +struct msm_camera_csiphy_params { + uint8_t lane_cnt; + uint8_t settle_cnt; +}; + +#define VFE31_OUTPUT_MODE_PT (0x1 << 0) +#define VFE31_OUTPUT_MODE_S (0x1 << 1) +#define VFE31_OUTPUT_MODE_V (0x1 << 2) +#define VFE31_OUTPUT_MODE_P (0x1 << 3) +#define VFE31_OUTPUT_MODE_T (0x1 << 4) + +#define CSI_EMBED_DATA 0x12 +#define CSI_RAW8 0x2A +#define CSI_RAW10 0x2B +#define CSI_RAW12 0x2C + +#define CSI_DECODE_6BIT 0 +#define CSI_DECODE_8BIT 1 +#define CSI_DECODE_10BIT 2 +#define VFE32_OUTPUT_MODE_PT (0x1 << 0) +#define VFE32_OUTPUT_MODE_S (0x1 << 1) +#define VFE32_OUTPUT_MODE_V (0x1 << 2) + +struct msm_vfe_phy_info { + uint32_t sbuf_phy; + uint32_t y_phy; + uint32_t cbcr_phy; + uint8_t output_id; /* VFE31_OUTPUT_MODE_PT/S/V */ + uint32_t frame_id; +}; + +struct msm_vfe_stats_msg { + uint32_t aec_buff; + uint32_t awb_buff; + uint32_t af_buff; + uint32_t ihist_buff; + uint32_t rs_buff; + uint32_t cs_buff; + uint32_t skin_buff; + uint32_t status_bits; + uint32_t frame_id; +}; + +struct video_crop_t{ + uint32_t in1_w; + uint32_t out1_w; + uint32_t in1_h; + uint32_t out1_h; + uint32_t in2_w; + uint32_t out2_w; + uint32_t in2_h; + uint32_t out2_h; + uint8_t update_flag; +}; + +struct msm_vpe_buf_info { + uint32_t y_phy; + uint32_t cbcr_phy; + struct timespec ts; + uint32_t frame_id; + struct video_crop_t vpe_crop; +}; + +struct msm_vfe_resp { + enum vfe_resp_msg type; + struct msm_cam_evt_msg evt_msg; + struct msm_vfe_phy_info phy; + struct msm_vfe_stats_msg stats_msg; + struct msm_vpe_buf_info vpe_bf; + void *extdata; + int32_t extlen; +}; + +struct msm_vpe_resp { + enum vpe_resp_msg type; + struct msm_cam_evt_msg evt_msg; + struct msm_vpe_phy_info phy; + void *extdata; + int32_t extlen; +}; + +struct msm_vpe_callback { + void (*vpe_resp)(struct msm_vpe_resp *, + enum msm_queue, void *syncdata, + void *time_stamp, gfp_t gfp); + void* (*vpe_alloc)(int, void *syncdata, gfp_t gfp); + void (*vpe_free)(void *ptr); +}; + +struct msm_vfe_callback { + void (*vfe_resp)(struct msm_vfe_resp *, + enum msm_queue, void *syncdata, + gfp_t gfp); + void* (*vfe_alloc)(int, void *syncdata, gfp_t gfp); + void (*vfe_free)(void *ptr); +}; + +struct msm_camvfe_fn { + int (*vfe_init)(struct msm_vfe_callback *, + struct platform_device *); + int (*vfe_enable)(struct camera_enable_cmd *); + int (*vfe_config)(struct msm_vfe_cfg_cmd *, void *); + int (*vfe_disable)(struct camera_enable_cmd *, + struct platform_device *dev); + void (*vfe_release)(struct platform_device *); + void (*vfe_stop)(void); +}; + +struct msm_camvfe_params { + struct msm_vfe_cfg_cmd *vfe_cfg; + void *data; +}; + +struct msm_camvpe_fn { + int (*vpe_reg)(struct msm_vpe_callback *); + int (*vpe_cfg_update) (void *); + void (*send_frame_to_vpe) (uint32_t y_phy, uint32_t cbcr_phy, + struct timespec *ts, int output_id); + int (*vpe_config)(struct msm_vpe_cfg_cmd *, void *); + void (*vpe_cfg_offset)(int frame_pack, uint32_t pyaddr, + uint32_t pcbcraddr, struct timespec *ts, int output_id, + struct msm_st_half st_half, int frameid); + int *dis; +}; + +struct msm_sensor_ctrl { + int (*s_init)(const struct msm_camera_sensor_info *); + int (*s_release)(void); + int (*s_config)(void __user *); + enum msm_camera_type s_camera_type; + uint32_t s_mount_angle; + enum msm_st_frame_packing s_video_packing; + enum msm_st_frame_packing s_snap_packing; +}; +struct msm_strobe_flash_ctrl { + int (*strobe_flash_init) + (struct msm_camera_sensor_strobe_flash_data *); + int (*strobe_flash_release) + (struct msm_camera_sensor_strobe_flash_data *, int32_t); + int (*strobe_flash_charge)(int32_t, int32_t, uint32_t); +}; + +/* this structure is used in kernel */ +struct msm_queue_cmd { + struct list_head list_config; + struct list_head list_control; + struct list_head list_frame; + struct list_head list_pict; + struct list_head list_vpe_frame; + enum msm_queue type; + void *command; + atomic_t on_heap; + struct timespec ts; + uint32_t error_code; +}; + +struct msm_device_queue { + struct list_head list; + spinlock_t lock; + wait_queue_head_t wait; + int max; + int len; + const char *name; +}; + +struct msm_sync { + /* These two queues are accessed from a process context only + * They contain pmem descriptors for the preview frames and the stats + * coming from the camera sensor. + */ + struct hlist_head pmem_frames; + struct hlist_head pmem_stats; + + /* The message queue is used by the control thread to send commands + * to the config thread, and also by the DSP to send messages to the + * config thread. Thus it is the only queue that is accessed from + * both interrupt and process context. + */ + struct msm_device_queue event_q; + + /* This queue contains preview frames. It is accessed by the DSP (in + * in interrupt context, and by the frame thread. + */ + struct msm_device_queue frame_q; + int unblock_poll_frame; + int unblock_poll_pic_frame; + + /* This queue contains snapshot frames. It is accessed by the DSP (in + * interrupt context, and by the control thread. + */ + struct msm_device_queue pict_q; + int get_pic_abort; + struct msm_device_queue vpe_q; + + struct msm_camera_sensor_info *sdata; + struct msm_camvfe_fn vfefn; + struct msm_camvpe_fn vpefn; + struct msm_sensor_ctrl sctrl; + struct msm_strobe_flash_ctrl sfctrl; + struct wake_lock wake_lock; + struct platform_device *pdev; + int16_t ignore_qcmd_type; + uint8_t ignore_qcmd; + uint8_t opencnt; + void *cropinfo; + int croplen; + int core_powered_on; + + struct fd_roi_info fdroiinfo; + + atomic_t vpe_enable; + uint32_t pp_mask; + uint8_t pp_frame_avail; + struct msm_queue_cmd *pp_prev; + struct msm_queue_cmd *pp_snap; + struct msm_queue_cmd *pp_thumb; + int video_fd; + + const char *apps_id; + + struct mutex lock; + struct list_head list; + uint8_t liveshot_enabled; + struct msm_cam_v4l2_device *pcam_sync; + + uint8_t stereocam_enabled; + struct msm_queue_cmd *pp_stereocam; + struct msm_queue_cmd *pp_stereocam2; + struct msm_queue_cmd *pp_stereosnap; + enum msm_stereo_state stereo_state; + int stcam_quality_ind; + uint32_t stcam_conv_value; + + spinlock_t pmem_frame_spinlock; + spinlock_t pmem_stats_spinlock; + spinlock_t abort_pict_lock; + int snap_count; + int thumb_count; +}; + +#define MSM_APPS_ID_V4L2 "msm_v4l2" +#define MSM_APPS_ID_PROP "msm_qct" + +struct msm_cam_device { + struct msm_sync *sync; /* most-frequently accessed */ + struct device *device; + struct cdev cdev; + /* opened is meaningful only for the config and frame nodes, + * which may be opened only once. + */ + atomic_t opened; +}; + +struct msm_control_device { + struct msm_cam_device *pmsm; + + /* Used for MSM_CAM_IOCTL_CTRL_CMD_DONE responses */ + uint8_t ctrl_data[max_control_command_size]; + struct msm_ctrl_cmd ctrl; + struct msm_queue_cmd qcmd; + + /* This queue used by the config thread to send responses back to the + * control thread. It is accessed only from a process context. + */ + struct msm_device_queue ctrl_q; +}; + +struct register_address_value_pair { + uint16_t register_address; + uint16_t register_value; +}; + +struct msm_pmem_region { + struct hlist_node list; + unsigned long paddr; + unsigned long len; + struct file *file; + struct msm_pmem_info info; +}; + +struct axidata { + uint32_t bufnum1; + uint32_t bufnum2; + uint32_t bufnum3; + struct msm_pmem_region *region; +}; + +#ifdef CONFIG_MSM_CAMERA_FLASH +int msm_camera_flash_set_led_state( + struct msm_camera_sensor_flash_data *fdata, + unsigned led_state); +int msm_strobe_flash_init(struct msm_sync *sync, uint32_t sftype); +int msm_flash_ctrl(struct msm_camera_sensor_info *sdata, + struct flash_ctrl_data *flash_info); +#else +static inline int msm_camera_flash_set_led_state( + struct msm_camera_sensor_flash_data *fdata, + unsigned led_state) +{ + return -ENOTSUPP; +} +static inline int msm_strobe_flash_init( + struct msm_sync *sync, uint32_t sftype) +{ + return -ENOTSUPP; +} +static inline int msm_flash_ctrl( + struct msm_camera_sensor_info *sdata, + struct flash_ctrl_data *flash_info) +{ + return -ENOTSUPP; +} +#endif + + + +void msm_camvfe_init(void); +int msm_camvfe_check(void *); +void msm_camvfe_fn_init(struct msm_camvfe_fn *, void *); +void msm_camvpe_fn_init(struct msm_camvpe_fn *, void *); +int msm_camera_drv_start(struct platform_device *dev, + int (*sensor_probe)(const struct msm_camera_sensor_info *, + struct msm_sensor_ctrl *)); + +enum msm_camio_clk_type { + CAMIO_VFE_MDC_CLK, + CAMIO_MDC_CLK, + CAMIO_VFE_CLK, + CAMIO_VFE_AXI_CLK, + + CAMIO_VFE_CAMIF_CLK, + CAMIO_VFE_PBDG_CLK, + CAMIO_CAM_MCLK_CLK, + CAMIO_CAMIF_PAD_PBDG_CLK, + + CAMIO_CSI0_VFE_CLK, + CAMIO_CSI1_VFE_CLK, + CAMIO_VFE_PCLK, + + CAMIO_CSI_SRC_CLK, + CAMIO_CSI0_CLK, + CAMIO_CSI1_CLK, + CAMIO_CSI0_PCLK, + CAMIO_CSI1_PCLK, + + CAMIO_CSI1_SRC_CLK, + CAMIO_CSI_PIX_CLK, + CAMIO_CSI_RDI_CLK, + CAMIO_CSIPHY0_TIMER_CLK, + CAMIO_CSIPHY1_TIMER_CLK, + + CAMIO_JPEG_CLK, + CAMIO_JPEG_PCLK, + CAMIO_VPE_CLK, + CAMIO_VPE_PCLK, + + CAMIO_CSI0_PHY_CLK, + CAMIO_CSI1_PHY_CLK, + CAMIO_CSIPHY_TIMER_SRC_CLK, + + CAMIO_MAX_CLK +}; + +enum msm_camio_clk_src_type { + MSM_CAMIO_CLK_SRC_INTERNAL, + MSM_CAMIO_CLK_SRC_EXTERNAL, + MSM_CAMIO_CLK_SRC_MAX +}; + +enum msm_s_test_mode { + S_TEST_OFF, + S_TEST_1, + S_TEST_2, + S_TEST_3 +}; + +enum msm_s_resolution { + S_QTR_SIZE, + S_FULL_SIZE, + S_INVALID_SIZE +}; + +enum msm_s_reg_update { + /* Sensor egisters that need to be updated during initialization */ + S_REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + S_UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + S_UPDATE_ALL, + /* Not valid update */ + S_UPDATE_INVALID +}; + +enum msm_s_setting { + S_RES_PREVIEW, + S_RES_CAPTURE +}; + +enum msm_bus_perf_setting { + S_INIT, + S_PREVIEW, + S_VIDEO, + S_CAPTURE, + S_ZSL, + S_STEREO_VIDEO, + S_STEREO_CAPTURE, + S_DEFAULT, + S_EXIT +}; + +int msm_camio_enable(struct platform_device *dev); +int msm_camio_jpeg_clk_enable(void); +int msm_camio_jpeg_clk_disable(void); +int msm_camio_vpe_clk_enable(uint32_t); +int msm_camio_vpe_clk_disable(void); + +int msm_camio_clk_enable(enum msm_camio_clk_type clk); +int msm_camio_clk_disable(enum msm_camio_clk_type clk); +int msm_camio_clk_config(uint32_t freq); +void msm_camio_clk_rate_set(int rate); +void msm_camio_vfe_clk_rate_set(int rate); +void msm_camio_clk_rate_set_2(struct clk *clk, int rate); +void msm_camio_clk_set_min_rate(struct clk *clk, int rate); +void msm_camio_clk_axi_rate_set(int rate); +void msm_disable_io_gpio_clk(struct platform_device *); + +void msm_camio_camif_pad_reg_reset(void); +void msm_camio_camif_pad_reg_reset_2(void); + +void msm_camio_vfe_blk_reset(void); + +void msm_camio_clk_sel(enum msm_camio_clk_src_type); +void msm_camio_disable(struct platform_device *); +int msm_camio_probe_on(struct platform_device *); +int msm_camio_probe_off(struct platform_device *); +int msm_camio_sensor_clk_off(struct platform_device *); +int msm_camio_sensor_clk_on(struct platform_device *); +int msm_camio_csi_config(struct msm_camera_csi_params *csi_params); +int msm_camio_csiphy_config(struct msm_camera_csiphy_params *csiphy_params); +int msm_camio_csid_config(struct msm_camera_csid_params *csid_params); +void msm_io_read_interrupt(void); +int add_axi_qos(void); +int update_axi_qos(uint32_t freq); +void release_axi_qos(void); +void msm_io_w(u32 data, void __iomem *addr); +void msm_io_w_mb(u32 data, void __iomem *addr); +u32 msm_io_r(void __iomem *addr); +u32 msm_io_r_mb(void __iomem *addr); +void msm_io_dump(void __iomem *addr, int size); +void msm_io_memcpy(void __iomem *dest_addr, void __iomem *src_addr, u32 len); +void msm_camio_set_perf_lvl(enum msm_bus_perf_setting); + +void *msm_isp_sync_alloc(int size, + void *syncdata __attribute__((unused)), gfp_t gfp); + +void msm_isp_sync_free(void *ptr); +#endif diff --git a/arch/arm/mach-msm/include/mach/cpu.h b/arch/arm/mach-msm/include/mach/cpu.h index a9481b08d5c..0308b7b77f7 100644 --- a/arch/arm/mach-msm/include/mach/cpu.h +++ b/arch/arm/mach-msm/include/mach/cpu.h @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #ifndef __ARCH_ARM_MACH_MSM_CPU_H__ diff --git a/arch/arm/mach-msm/include/mach/dal.h b/arch/arm/mach-msm/include/mach/dal.h new file mode 100644 index 00000000000..d0c754d380f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/dal.h @@ -0,0 +1,150 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __DAL_H__ +#define __DAL_H__ + +#include +#include + +#define DALRPC_DEST_MODEM SMD_APPS_MODEM +#define DALRPC_DEST_QDSP SMD_APPS_QDSP + +#define DALRPC_TIMEOUT_INFINITE -1 + +enum { + DALDEVICE_ATTACH_IDX = 0, + DALDEVICE_DETACH_IDX, + DALDEVICE_INIT_IDX, + DALDEVICE_DEINIT_IDX, + DALDEVICE_OPEN_IDX, + DALDEVICE_CLOSE_IDX, + DALDEVICE_INFO_IDX, + DALDEVICE_POWEREVENT_IDX, + DALDEVICE_SYSREQUEST_IDX, + DALDEVICE_FIRST_DEVICE_API_IDX +}; + +struct daldevice_info_t { + uint32_t size; + uint32_t version; + char name[32]; +}; + +#define DAL_CHUNK_NAME_LENGTH 12 +struct dal_chunk_header { + uint32_t size; + char name[DAL_CHUNK_NAME_LENGTH]; + uint32_t lock; + uint32_t reserved; + uint32_t type; + uint32_t version; +}; + +int daldevice_attach(uint32_t device_id, char *port, int cpu, + void **handle_ptr); + +/* The caller must ensure there are no outstanding dalrpc calls on + * the client before (and while) calling daldevice_detach. */ +int daldevice_detach(void *handle); + +uint32_t dalrpc_fcn_0(uint32_t ddi_idx, void *handle, uint32_t s1); +uint32_t dalrpc_fcn_1(uint32_t ddi_idx, void *handle, uint32_t s1, + uint32_t s2); +uint32_t dalrpc_fcn_2(uint32_t ddi_idx, void *handle, uint32_t s1, + uint32_t *p_s2); +uint32_t dalrpc_fcn_3(uint32_t ddi_idx, void *handle, uint32_t s1, + uint32_t s2, uint32_t s3); +uint32_t dalrpc_fcn_4(uint32_t ddi_idx, void *handle, uint32_t s1, + uint32_t s2, uint32_t *p_s3); +uint32_t dalrpc_fcn_5(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen); +uint32_t dalrpc_fcn_6(uint32_t ddi_idx, void *handle, uint32_t s1, + const void *ibuf, uint32_t ilen); +uint32_t dalrpc_fcn_7(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, void *obuf, uint32_t olen, + uint32_t *oalen); +uint32_t dalrpc_fcn_8(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, void *obuf, uint32_t olen); +uint32_t dalrpc_fcn_9(uint32_t ddi_idx, void *handle, void *obuf, + uint32_t olen); +uint32_t dalrpc_fcn_10(uint32_t ddi_idx, void *handle, uint32_t s1, + const void *ibuf, uint32_t ilen, void *obuf, + uint32_t olen, uint32_t *oalen); +uint32_t dalrpc_fcn_11(uint32_t ddi_idx, void *handle, uint32_t s1, + void *obuf, uint32_t olen); +uint32_t dalrpc_fcn_12(uint32_t ddi_idx, void *handle, uint32_t s1, + void *obuf, uint32_t olen, uint32_t *oalen); +uint32_t dalrpc_fcn_13(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, const void *ibuf2, uint32_t ilen2, + void *obuf, uint32_t olen); +uint32_t dalrpc_fcn_14(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, void *obuf1, uint32_t olen1, + void *obuf2, uint32_t olen2, uint32_t *oalen2); +uint32_t dalrpc_fcn_15(uint32_t ddi_idx, void *handle, const void *ibuf, + uint32_t ilen, const void *ibuf2, uint32_t ilen2, + void *obuf, uint32_t olen, uint32_t *oalen, + void *obuf2, uint32_t olen2); + +static inline uint32_t daldevice_info(void *handle, + struct daldevice_info_t *info, + uint32_t info_size) +{ + return dalrpc_fcn_9(DALDEVICE_INFO_IDX, handle, info, info_size); +} + +static inline uint32_t daldevice_sysrequest(void *handle, uint32_t req_id, + const void *src_ptr, + uint32_t src_len, void *dest_ptr, + uint32_t dest_len, + uint32_t *dest_alen) +{ + return dalrpc_fcn_10(DALDEVICE_SYSREQUEST_IDX, handle, req_id, + src_ptr, src_len, dest_ptr, dest_len, dest_alen); +} + +static inline uint32_t daldevice_init(void *handle) +{ + return dalrpc_fcn_0(DALDEVICE_INIT_IDX, handle, 0); +} + +static inline uint32_t daldevice_deinit(void *handle) +{ + return dalrpc_fcn_0(DALDEVICE_DEINIT_IDX, handle, 0); +} + +static inline uint32_t daldevice_open(void *handle, uint32_t mode) +{ + return dalrpc_fcn_0(DALDEVICE_OPEN_IDX, handle, mode); +} + +static inline uint32_t daldevice_close(void *handle) +{ + return dalrpc_fcn_0(DALDEVICE_CLOSE_IDX, handle, 0); +} + +void *dalrpc_alloc_event(void *handle); +void *dalrpc_alloc_cb(void *handle, + void (*fn)(void *, uint32_t, void *, uint32_t), + void *context); +void dalrpc_dealloc_event(void *handle, + void *ev_h); +void dalrpc_dealloc_cb(void *handle, + void *cb_h); + +#define dalrpc_event_wait(ev_h, timeout) \ + dalrpc_event_wait_multiple(1, &ev_h, timeout) + +int dalrpc_event_wait_multiple(int num, void **ev_h, int timeout); + +#endif /* __DAL_H__ */ diff --git a/arch/arm/mach-msm/include/mach/dal_axi.h b/arch/arm/mach-msm/include/mach/dal_axi.h new file mode 100644 index 00000000000..4e32aa3dabf --- /dev/null +++ b/arch/arm/mach-msm/include/mach/dal_axi.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _DAL_AXI_H +#define _DAL_AXI_H + +#include + +int set_grp2d_async(void); +int set_grp3d_async(void); +int set_grp_xbar_async(void); + +#endif /* _DAL_AXI_H */ diff --git a/arch/arm/mach-msm/include/mach/debug-macro.S b/arch/arm/mach-msm/include/mach/debug-macro.S index 646b99ebc77..905a254b376 100644 --- a/arch/arm/mach-msm/include/mach/debug-macro.S +++ b/arch/arm/mach-msm/include/mach/debug-macro.S @@ -19,27 +19,53 @@ #include #include -#if defined(CONFIG_HAS_MSM_DEBUG_UART_PHYS) && !defined(CONFIG_MSM_DEBUG_UART_NONE) - .macro addruart, rp, rv - ldr \rp, =MSM_DEBUG_UART_PHYS - ldr \rv, =MSM_DEBUG_UART_BASE +#ifdef CONFIG_MSM_DEBUG_UART + .macro addruart, rp, rv + ldr \rp, =MSM_DEBUG_UART_PHYS + ldr \rv, =MSM_DEBUG_UART_BASE .endm .macro senduart,rd,rx - teq \rx, #0 - strne \rd, [\rx, #0x0C] +#ifdef CONFIG_SERIAL_MSM_HSL + @ Clear TX_READY by writing to the UARTDM_CR register + mov r12, #0x300 + str r12, [\rx, #0x10] + @ Write 0x1 to NCF register + mov r12, #0x1 + str r12, [\rx, #0x40] + @ UARTDM reg. Read to induce delay + ldr r12, [\rx, #0x08] + @ Write the 1 character to UARTDM_TF + str \rd, [\rx, #0x70] +#else + teq \rx, #0 + strne \rd, [\rx, #0x0C] +#endif .endm .macro waituart,rd,rx +#ifdef CONFIG_SERIAL_MSM_HSL + @ check for TX_EMT in UARTDM_SR + ldr \rd, [\rx, #0x08] + tst \rd, #0x08 + bne 1002f + @ wait for TXREADY in UARTDM_ISR +1001: ldreq \rd, [\rx, #0x14] + tst \rd, #0x80 + dsb + beq 1001b +#else @ wait for TX_READY -1001: ldr \rd, [\rx, #0x08] - tst \rd, #0x04 - beq 1001b +1001: ldr \rd, [\rx, #0x08] + tst \rd, #0x04 + beq 1001b +#endif +1002: .endm + #else + .macro addruart, rp, rv - mov \rv, #0xff000000 - orr \rv, \rv, #0x00f00000 .endm .macro senduart,rd,rx diff --git a/arch/arm/mach-msm/include/mach/debug_mm.h b/arch/arm/mach-msm/include/mach/debug_mm.h new file mode 100644 index 00000000000..cc545436d3f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/debug_mm.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ARCH_ARM_MACH_MSM_DEBUG_MM_H_ +#define __ARCH_ARM_MACH_MSM_DEBUG_MM_H_ + +/* The below macro removes the directory path name and retains only the + * file name to avoid long path names in log messages that comes as + * part of __FILE__ to compiler. + */ +#define __MM_FILE__ strrchr(__FILE__, '/') ? (strrchr(__FILE__, '/')+1) : \ + __FILE__ + +#define MM_DBG(fmt, args...) pr_debug("[%s] " fmt,\ + __func__, ##args) + +#define MM_INFO(fmt, args...) pr_info("[%s:%s] " fmt,\ + __MM_FILE__, __func__, ##args) + +#define MM_ERR(fmt, args...) pr_err("[%s:%s] " fmt,\ + __MM_FILE__, __func__, ##args) +#endif /* __ARCH_ARM_MACH_MSM_DEBUG_MM_H_ */ diff --git a/arch/arm/mach-msm/include/mach/dma-fsm9xxx.h b/arch/arm/mach-msm/include/mach/dma-fsm9xxx.h new file mode 100644 index 00000000000..300ee8757bc --- /dev/null +++ b/arch/arm/mach-msm/include/mach/dma-fsm9xxx.h @@ -0,0 +1,62 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ASM_ARCH_MSM_DMA_FSM9XXX_H +#define __ASM_ARCH_MSM_DMA_FSM9XXX_H + +#define DMOV_SD_SIZE 0x1400 +#define DMOV_SD_MASTER 0 +#define DMOV_SD_AARM 3 +#define DMOV_SD_MASTER_ADDR(off, ch) DMOV_ADDR(off, ch, DMOV_SD_MASTER) +#define DMOV_SD_AARM_ADDR(off, ch) DMOV_ADDR(off, ch, DMOV_SD_AARM) + +/* DMA channels allocated to Scorpion */ +#define DMOV_GP_CHAN 4 +#define DMOV_CE1_IN_CHAN 5 +#define DMOV_CE1_OUT_CHAN 6 +#define DMOV_NAND_CHAN 7 +#define DMOV_SDC1_CHAN 8 +#define DMOV_GP2_CHAN 10 +#define DMOV_CE2_IN_CHAN 12 +#define DMOV_CE2_OUT_CHAN 13 +#define DMOV_CE3_IN_CHAN 14 +#define DMOV_CE3_OUT_CHAN 15 + +/* CRCIs */ +#define DMOV_CE1_IN_CRCI 1 +#define DMOV_CE1_OUT_CRCI 2 +#define DMOV_CE1_HASH_CRCI 3 + +#define DMOV_NAND_CRCI_DATA 4 +#define DMOV_NAND_CRCI_CMD 5 + +#define DMOV_SDC1_CRCI 6 + +#define DMOV_HSUART_TX_CRCI 7 +#define DMOV_HSUART_RX_CRCI 8 + +#define DMOV_CE2_IN_CRCI 9 +#define DMOV_CE2_OUT_CRCI 10 +#define DMOV_CE2_HASH_CRCI 11 + +#define DMOV_CE3_IN_CRCI 12 +#define DMOV_CE3_OUT_CRCI 13 +#define DMOV_CE3_HASH_DONE_CRCI 14 + +/* Following CRCIs are not defined in FSM9XXX, but these are added to keep + * the existing SDCC host controller driver compatible with FSM9XXX. + */ +#define DMOV_SDC2_CRCI DMOV_SDC1_CRCI +#define DMOV_SDC3_CRCI DMOV_SDC1_CRCI +#define DMOV_SDC4_CRCI DMOV_SDC1_CRCI + +#endif /* __ASM_ARCH_MSM_DMA_FSM9XXX_H */ diff --git a/arch/arm/mach-msm/include/mach/dma.h b/arch/arm/mach-msm/include/mach/dma.h index 05583f56952..de4d85ae1a0 100644 --- a/arch/arm/mach-msm/include/mach/dma.h +++ b/arch/arm/mach-msm/include/mach/dma.h @@ -1,6 +1,7 @@ /* linux/include/asm-arm/arch-msm/dma.h * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. 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 @@ -14,10 +15,15 @@ */ #ifndef __ASM_ARCH_MSM_DMA_H +#define __ASM_ARCH_MSM_DMA_H #include #include +#if defined(CONFIG_ARCH_FSM9XXX) +#include +#endif + struct msm_dmov_errdata { uint32_t flush[6]; }; @@ -25,74 +31,203 @@ struct msm_dmov_errdata { struct msm_dmov_cmd { struct list_head list; unsigned int cmdptr; + unsigned int crci_mask; void (*complete_func)(struct msm_dmov_cmd *cmd, unsigned int result, struct msm_dmov_errdata *err); - void (*execute_func)(struct msm_dmov_cmd *cmd); - void *data; + void (*exec_func)(struct msm_dmov_cmd *cmd); + void *user; /* Pointer for caller's reference */ }; -#ifndef CONFIG_ARCH_MSM8X60 void msm_dmov_enqueue_cmd(unsigned id, struct msm_dmov_cmd *cmd); +void msm_dmov_enqueue_cmd_ext(unsigned id, struct msm_dmov_cmd *cmd); void msm_dmov_stop_cmd(unsigned id, struct msm_dmov_cmd *cmd, int graceful); -int msm_dmov_exec_cmd(unsigned id, unsigned int cmdptr); -#else -static inline -void msm_dmov_enqueue_cmd(unsigned id, struct msm_dmov_cmd *cmd) { } -static inline -void msm_dmov_stop_cmd(unsigned id, struct msm_dmov_cmd *cmd, int graceful) { } -static inline -int msm_dmov_exec_cmd(unsigned id, unsigned int cmdptr) { return -EIO; } -#endif +void msm_dmov_flush(unsigned int id); +int msm_dmov_exec_cmd(unsigned id, unsigned int crci_mask, unsigned int cmdptr); +unsigned int msm_dmov_build_crci_mask(int n, ...); +#define DMOV_CRCIS_PER_CONF 10 -#define DMOV_SD0(off, ch) (MSM_DMOV_BASE + 0x0000 + (off) + ((ch) << 2)) -#define DMOV_SD1(off, ch) (MSM_DMOV_BASE + 0x0400 + (off) + ((ch) << 2)) -#define DMOV_SD2(off, ch) (MSM_DMOV_BASE + 0x0800 + (off) + ((ch) << 2)) -#define DMOV_SD3(off, ch) (MSM_DMOV_BASE + 0x0C00 + (off) + ((ch) << 2)) +#define DMOV_ADDR(off, ch, sd) ((DMOV_SD_SIZE*(sd)) + (off) + ((ch) << 2)) +#define DMOV_SD0(off, ch) DMOV_ADDR(off, ch, 0) +#define DMOV_SD1(off, ch) DMOV_ADDR(off, ch, 1) +#define DMOV_SD2(off, ch) DMOV_ADDR(off, ch, 2) +#define DMOV_SD3(off, ch) DMOV_ADDR(off, ch, 3) #if defined(CONFIG_ARCH_MSM7X30) -#define DMOV_SD_AARM DMOV_SD2 +#define DMOV_SD_SIZE 0x400 +#define DMOV_SD_AARM 2 +#elif defined(CONFIG_ARCH_MSM8960) +#define DMOV_SD_SIZE 0x800 +#define DMOV_SD_AARM 1 +#elif defined(CONFIG_MSM_ADM3) +#define DMOV_SD_SIZE 0x800 +#define DMOV_SD_MASTER 1 +#define DMOV_SD_AARM 1 +#define DMOV_SD_MASTER_ADDR(off, ch) DMOV_ADDR(off, ch, DMOV_SD_MASTER) +#elif defined(CONFIG_ARCH_FSM9XXX) +/* defined in dma-fsm9xxx.h */ #else -#define DMOV_SD_AARM DMOV_SD3 +#define DMOV_SD_SIZE 0x400 +#define DMOV_SD_AARM 3 #endif -#define DMOV_CMD_PTR(ch) DMOV_SD_AARM(0x000, ch) +#define DMOV_SD_AARM_ADDR(off, ch) DMOV_ADDR(off, ch, DMOV_SD_AARM) + +#define DMOV_CMD_PTR(ch) DMOV_SD_AARM_ADDR(0x000, ch) #define DMOV_CMD_LIST (0 << 29) /* does not work */ #define DMOV_CMD_PTR_LIST (1 << 29) /* works */ #define DMOV_CMD_INPUT_CFG (2 << 29) /* untested */ #define DMOV_CMD_OUTPUT_CFG (3 << 29) /* untested */ #define DMOV_CMD_ADDR(addr) ((addr) >> 3) -#define DMOV_RSLT(ch) DMOV_SD_AARM(0x040, ch) +#define DMOV_RSLT(ch) DMOV_SD_AARM_ADDR(0x040, ch) #define DMOV_RSLT_VALID (1 << 31) /* 0 == host has empties result fifo */ #define DMOV_RSLT_ERROR (1 << 3) #define DMOV_RSLT_FLUSH (1 << 2) #define DMOV_RSLT_DONE (1 << 1) /* top pointer done */ #define DMOV_RSLT_USER (1 << 0) /* command with FR force result */ -#define DMOV_FLUSH0(ch) DMOV_SD_AARM(0x080, ch) -#define DMOV_FLUSH1(ch) DMOV_SD_AARM(0x0C0, ch) -#define DMOV_FLUSH2(ch) DMOV_SD_AARM(0x100, ch) -#define DMOV_FLUSH3(ch) DMOV_SD_AARM(0x140, ch) -#define DMOV_FLUSH4(ch) DMOV_SD_AARM(0x180, ch) -#define DMOV_FLUSH5(ch) DMOV_SD_AARM(0x1C0, ch) +#define DMOV_FLUSH0(ch) DMOV_SD_AARM_ADDR(0x080, ch) +#define DMOV_FLUSH1(ch) DMOV_SD_AARM_ADDR(0x0C0, ch) +#define DMOV_FLUSH2(ch) DMOV_SD_AARM_ADDR(0x100, ch) +#define DMOV_FLUSH3(ch) DMOV_SD_AARM_ADDR(0x140, ch) +#define DMOV_FLUSH4(ch) DMOV_SD_AARM_ADDR(0x180, ch) +#define DMOV_FLUSH5(ch) DMOV_SD_AARM_ADDR(0x1C0, ch) +#define DMOV_FLUSH_TYPE (1 << 31) -#define DMOV_STATUS(ch) DMOV_SD_AARM(0x200, ch) +#define DMOV_STATUS(ch) DMOV_SD_AARM_ADDR(0x200, ch) #define DMOV_STATUS_RSLT_COUNT(n) (((n) >> 29)) #define DMOV_STATUS_CMD_COUNT(n) (((n) >> 27) & 3) #define DMOV_STATUS_RSLT_VALID (1 << 1) #define DMOV_STATUS_CMD_PTR_RDY (1 << 0) -#define DMOV_ISR DMOV_SD_AARM(0x380, 0) - -#define DMOV_CONFIG(ch) DMOV_SD_AARM(0x300, ch) -#define DMOV_CONFIG_FORCE_TOP_PTR_RSLT (1 << 2) -#define DMOV_CONFIG_FORCE_FLUSH_RSLT (1 << 1) -#define DMOV_CONFIG_IRQ_EN (1 << 0) +#define DMOV_CONF(ch) DMOV_SD_MASTER_ADDR(0x240, ch) +#define DMOV_CONF_SD(sd) (((sd & 4) << 11) | ((sd & 3) << 4)) +#define DMOV_CONF_IRQ_EN (1 << 6) +#define DMOV_CONF_FORCE_RSLT_EN (1 << 7) +#define DMOV_CONF_SHADOW_EN (1 << 12) +#define DMOV_CONF_MPU_DISABLE (1 << 11) +#define DMOV_CONF_PRIORITY(n) (n << 0) + +#define DMOV_DBG_ERR(ci) DMOV_SD_MASTER_ADDR(0x280, ci) + +#define DMOV_RSLT_CONF(ch) DMOV_SD_AARM_ADDR(0x300, ch) +#define DMOV_RSLT_CONF_FORCE_TOP_PTR_RSLT (1 << 2) +#define DMOV_RSLT_CONF_FORCE_FLUSH_RSLT (1 << 1) +#define DMOV_RSLT_CONF_IRQ_EN (1 << 0) + +#define DMOV_ISR DMOV_SD_AARM_ADDR(0x380, 0) + +#define DMOV_CI_CONF(ci) DMOV_SD_MASTER_ADDR(0x390, ci) +#define DMOV_CI_CONF_RANGE_END(n) ((n) << 24) +#define DMOV_CI_CONF_RANGE_START(n) ((n) << 16) +#define DMOV_CI_CONF_MAX_BURST(n) ((n) << 0) + +#define DMOV_CI_DBG_ERR(ci) DMOV_SD_MASTER_ADDR(0x3B0, ci) + +#define DMOV_CRCI_CONF0 DMOV_SD_MASTER_ADDR(0x3D0, 0) +#define DMOV_CRCI_CONF1 DMOV_SD_MASTER_ADDR(0x3D4, 0) +#define DMOV_CRCI_CONF0_SD(crci, sd) (sd << (crci*3)) +#define DMOV_CRCI_CONF1_SD(crci, sd) (sd << ((crci-DMOV_CRCIS_PER_CONF)*3)) + +#define DMOV_CRCI_CTL(crci) DMOV_SD_AARM_ADDR(0x400, crci) +#define DMOV_CRCI_CTL_BLK_SZ(n) ((n) << 0) +#define DMOV_CRCI_CTL_RST (1 << 17) +#define DMOV_CRCI_MUX (1 << 18) /* channel assignments */ +/* + * Format of CRCI numbers: crci number + (muxsel << 4) + */ + +#if defined(CONFIG_ARCH_MSM8X60) +#define DMOV_GP_CHAN 15 + +#define DMOV_NAND_CHAN 17 +#define DMOV_NAND_CHAN_MODEM 26 +#define DMOV_NAND_CHAN_Q6 27 +#define DMOV_NAND_CRCI_CMD 15 +#define DMOV_NAND_CRCI_DATA 3 + +#define DMOV_CE_IN_CHAN 2 +#define DMOV_CE_IN_CRCI 4 + +#define DMOV_CE_OUT_CHAN 3 +#define DMOV_CE_OUT_CRCI 5 + +#define DMOV_CE_HASH_CRCI 15 + +#define DMOV_SDC1_CHAN 18 +#define DMOV_SDC1_CRCI 1 + +#define DMOV_SDC2_CHAN 19 +#define DMOV_SDC2_CRCI 4 + +#define DMOV_SDC3_CHAN 20 +#define DMOV_SDC3_CRCI 2 + +#define DMOV_SDC4_CHAN 21 +#define DMOV_SDC4_CRCI 5 + +#define DMOV_SDC5_CHAN 21 +#define DMOV_SDC5_CRCI 14 + +#define DMOV_TSIF_CHAN 4 +#define DMOV_TSIF_CRCI 6 + +#define DMOV_HSUART1_TX_CHAN 22 +#define DMOV_HSUART1_TX_CRCI 8 + +#define DMOV_HSUART1_RX_CHAN 23 +#define DMOV_HSUART1_RX_CRCI 9 + +#define DMOV_HSUART2_TX_CHAN 8 +#define DMOV_HSUART2_TX_CRCI 13 + +#define DMOV_HSUART2_RX_CHAN 8 +#define DMOV_HSUART2_RX_CRCI 14 + +#elif defined(CONFIG_ARCH_MSM8960) +#define DMOV_GP_CHAN 13 + +#define DMOV_CE_IN_CHAN 0 +#define DMOV_CE_IN_CRCI 2 + +#define DMOV_CE_OUT_CHAN 1 +#define DMOV_CE_OUT_CRCI 3 + +/* SDC doesn't use ADM on 8960. Need these to compile */ +#define DMOV_SDC1_CHAN 13 +#define DMOV_SDC1_CRCI 0 + +#define DMOV_SDC2_CHAN 13 +#define DMOV_SDC2_CRCI 0 + +#define DMOV_SDC3_CHAN 13 +#define DMOV_SDC3_CRCI 0 + +#define DMOV_SDC4_CHAN 13 +#define DMOV_SDC4_CRCI 0 + +#define DMOV_SDC5_CHAN 13 +#define DMOV_SDC5_CRCI 0 + +#elif defined(CONFIG_ARCH_FSM9XXX) +/* defined in dma-fsm9xxx.h */ + +#else +#define DMOV_GP_CHAN 4 + +#define DMOV_CE_IN_CHAN 5 +#define DMOV_CE_IN_CRCI 1 + +#define DMOV_CE_OUT_CHAN 6 +#define DMOV_CE_OUT_CRCI 2 + +#define DMOV_CE_HASH_CRCI 3 + #define DMOV_NAND_CHAN 7 #define DMOV_NAND_CRCI_CMD 5 #define DMOV_NAND_CRCI_DATA 4 @@ -103,11 +238,31 @@ int msm_dmov_exec_cmd(unsigned id, unsigned int cmdptr) { return -EIO; } #define DMOV_SDC2_CHAN 8 #define DMOV_SDC2_CRCI 7 +#define DMOV_SDC3_CHAN 8 +#define DMOV_SDC3_CRCI 12 + +#define DMOV_SDC4_CHAN 8 +#define DMOV_SDC4_CRCI 13 + #define DMOV_TSIF_CHAN 10 #define DMOV_TSIF_CRCI 10 #define DMOV_USB_CHAN 11 +#define DMOV_HSUART1_TX_CHAN 4 +#define DMOV_HSUART1_TX_CRCI 8 + +#define DMOV_HSUART1_RX_CHAN 9 +#define DMOV_HSUART1_RX_CRCI 9 + +#define DMOV_HSUART2_TX_CHAN 4 +#define DMOV_HSUART2_TX_CRCI 14 + +#define DMOV_HSUART2_RX_CHAN 11 +#define DMOV_HSUART2_RX_CRCI 15 +#endif + + /* no client rate control ifc (eg, ram) */ #define DMOV_NONE_CRCI 0 diff --git a/arch/arm/mach-msm/include/mach/dma_test.h b/arch/arm/mach-msm/include/mach/dma_test.h new file mode 100644 index 00000000000..c0464fa7ded --- /dev/null +++ b/arch/arm/mach-msm/include/mach/dma_test.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_DMA_TEST__ +#define __MSM_DMA_TEST__ + +#include + +#define MSM_DMA_IOC_MAGIC 0x83 + +/* The testing driver can manage a series of buffers. These are + * allocated and freed using these calls. */ +struct msm_dma_alloc_req { + int size; /* Size of this request, in bytes. */ + int bufnum; /* OUT: Number of buffer allocated. */ +}; +#define MSM_DMA_IOALLOC _IOWR(MSM_DMA_IOC_MAGIC, 2, struct msm_dma_alloc_req) + +/* Free the specified buffer. */ +#define MSM_DMA_IOFREE _IOW(MSM_DMA_IOC_MAGIC, 3, int) + +/* Free all used buffers. */ +#define MSM_DMA_IOFREEALL _IO(MSM_DMA_IOC_MAGIC, 7) + +/* Read/write data into kernel buffer. */ +struct msm_dma_bufxfer { + void *data; + int size; + int bufnum; +}; +#define MSM_DMA_IOWBUF _IOW(MSM_DMA_IOC_MAGIC, 4, struct msm_dma_bufxfer) +#define MSM_DMA_IORBUF _IOW(MSM_DMA_IOC_MAGIC, 5, struct msm_dma_bufxfer) + +/* Use the data mover to copy from one buffer to another. */ +struct msm_dma_scopy { + int srcbuf; + int destbuf; + int size; +}; +#define MSM_DMA_IOSCOPY _IOW(MSM_DMA_IOC_MAGIC, 6, struct msm_dma_scopy) + +#endif /* __MSM_DMA_TEST__ */ diff --git a/arch/arm/mach-msm/include/mach/entry-macro-qgic.S b/arch/arm/mach-msm/include/mach/entry-macro-qgic.S index 12467157afb..092e48e907e 100644 --- a/arch/arm/mach-msm/include/mach/entry-macro-qgic.S +++ b/arch/arm/mach-msm/include/mach/entry-macro-qgic.S @@ -26,7 +26,7 @@ * The interrupt numbering scheme is defined in the * interrupt controller spec. To wit: * - * Migrated the code from ARM MP port to be more consistent + * Migrated the code from ARM MP port to be more consistant * with interrupt processing , the following still holds true * however, all interrupts are treated the same regardless of * if they are local IPI or PPI @@ -55,7 +55,11 @@ 9-0 =int # */ bic \irqnr, \irqstat, #0x1c00 @mask src +#ifdef CONFIG_REQUEST_IPI + cmp \irqnr, #0 +#else cmp \irqnr, #15 +#endif ldr \tmp, =1021 cmpcc \irqnr, \irqnr cmpne \irqnr, \tmp @@ -70,19 +74,23 @@ * we won't easily be able to recreate later. */ .macro test_for_ipi, irqnr, irqstat, base, tmp +#ifndef CONFIG_REQUEST_IPI bic \irqnr, \irqstat, #0x1c00 cmp \irqnr, #16 strcc \irqstat, [\base, #GIC_CPU_EOI] cmpcs \irqnr, \irqnr +#endif .endm /* As above, this assumes that irqstat and base are preserved.. */ .macro test_for_ltirq, irqnr, irqstat, base, tmp +#ifndef CONFIG_REQUEST_IPI bic \irqnr, \irqstat, #0x1c00 mov \tmp, #0 cmp \irqnr, #16 moveq \tmp, #1 streq \irqstat, [\base, #GIC_CPU_EOI] cmp \tmp, #0 +#endif .endm diff --git a/arch/arm/mach-msm/include/mach/entry-macro.S b/arch/arm/mach-msm/include/mach/entry-macro.S index b16f082eeb6..eb5921f1c1b 100644 --- a/arch/arm/mach-msm/include/mach/entry-macro.S +++ b/arch/arm/mach-msm/include/mach/entry-macro.S @@ -1,4 +1,5 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* + * Copyright (c) 2009-2010, Code Aurora Forum. 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 and @@ -8,16 +9,12 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * */ -#if defined(CONFIG_ARM_GIC) +#if defined(CONFIG_MSM_VIC) +#include +#elif defined(CONFIG_ARM_GIC) #include #else -#include +#error "No interrupt controller selected!" #endif diff --git a/arch/arm/mach-msm/include/mach/fiq.h b/arch/arm/mach-msm/include/mach/fiq.h new file mode 100644 index 00000000000..29a3ba1f33f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/fiq.h @@ -0,0 +1,33 @@ +/* linux/include/asm-arm/arch-msm/irqs.h + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_MSM_FIQ_H +#define __ASM_ARCH_MSM_FIQ_H + +/* cause an interrupt to be an FIQ instead of a regular IRQ */ +void msm_fiq_select(int number); +void msm_fiq_unselect(int number); + +/* enable/disable an interrupt that is an FIQ (not safe from FIQ context) */ +void msm_fiq_enable(int number); +void msm_fiq_disable(int number); + +/* install an FIQ handler */ +int msm_fiq_set_handler(void (*func)(void *data, void *regs), void *data); + +/* cause an edge triggered interrupt to fire (safe from FIQ context */ +void msm_trigger_irq(int number); + +#endif diff --git a/arch/arm/mach-msm/include/mach/gpio-tlmm-v1.h b/arch/arm/mach-msm/include/mach/gpio-tlmm-v1.h new file mode 100644 index 00000000000..e41fe72d248 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/gpio-tlmm-v1.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ASM_ARCH_MSM_GPIO_TLMM_V1_H +#define __ASM_ARCH_MSM_GPIO_TLMM_V1_H + +/* GPIO TLMM (Top Level Multiplexing) Definitions */ + +/* GPIO TLMM: Function -- GPIO specific */ + +/* GPIO TLMM: Direction */ +enum { + GPIO_CFG_INPUT, + GPIO_CFG_OUTPUT, +}; + +/* GPIO TLMM: Pullup/Pulldown */ +enum { + GPIO_CFG_NO_PULL, + GPIO_CFG_PULL_DOWN, + GPIO_CFG_KEEPER, + GPIO_CFG_PULL_UP, +}; + +/* GPIO TLMM: Drive Strength */ +enum { + GPIO_CFG_2MA, + GPIO_CFG_4MA, + GPIO_CFG_6MA, + GPIO_CFG_8MA, + GPIO_CFG_10MA, + GPIO_CFG_12MA, + GPIO_CFG_14MA, + GPIO_CFG_16MA, +}; + +enum { + GPIO_CFG_ENABLE, + GPIO_CFG_DISABLE, +}; + +#define GPIO_CFG(gpio, func, dir, pull, drvstr) \ + ((((gpio) & 0x3FF) << 4) | \ + ((func) & 0xf) | \ + (((dir) & 0x1) << 14) | \ + (((pull) & 0x3) << 15) | \ + (((drvstr) & 0xF) << 17)) + +/** + * extract GPIO pin from bit-field used for gpio_tlmm_config + */ +#define GPIO_PIN(gpio_cfg) (((gpio_cfg) >> 4) & 0x3ff) +#define GPIO_FUNC(gpio_cfg) (((gpio_cfg) >> 0) & 0xf) +#define GPIO_DIR(gpio_cfg) (((gpio_cfg) >> 14) & 0x1) +#define GPIO_PULL(gpio_cfg) (((gpio_cfg) >> 15) & 0x3) +#define GPIO_DRVSTR(gpio_cfg) (((gpio_cfg) >> 17) & 0xf) + +int gpio_tlmm_config(unsigned config, unsigned disable); + +#endif diff --git a/arch/arm/mach-msm/include/mach/gpio-v1.h b/arch/arm/mach-msm/include/mach/gpio-v1.h new file mode 100644 index 00000000000..eea4c88f127 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/gpio-v1.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ASM_ARCH_MSM_GPIO_V1_H +#define __ASM_ARCH_MSM_GPIO_V1_H + +#include +#include +#include + +#define FIRST_BOARD_GPIO NR_GPIO_IRQS + +static inline int gpio_get_value(unsigned gpio) +{ + return __gpio_get_value(gpio); +} + +static inline void gpio_set_value(unsigned gpio, int value) +{ + __gpio_set_value(gpio, value); +} + +static inline int gpio_cansleep(unsigned gpio) +{ + return __gpio_cansleep(gpio); +} + +static inline int gpio_to_irq(unsigned gpio) +{ + return __gpio_to_irq(gpio); +} + +void msm_gpio_enter_sleep(int from_idle); +void msm_gpio_exit_sleep(void); + +/** + * struct msm_gpio - GPIO pin description + * @gpio_cfg - configuration bitmap, as per gpio_tlmm_config() + * @label - textual label + * + * Usually, GPIO's are operated by sets. + * This struct accumulate all GPIO information in single source + * and facilitete group operations provided by msm_gpios_xxx() + */ +struct msm_gpio { + u32 gpio_cfg; + const char *label; +}; + +/** + * msm_gpios_request_enable() - request and enable set of GPIOs + * + * Request and configure set of GPIO's + * In case of error, all operations rolled back. + * Return error code. + * + * @table: GPIO table + * @size: number of entries in @table + */ +int msm_gpios_request_enable(const struct msm_gpio *table, int size); + +/** + * msm_gpios_disable_free() - disable and free set of GPIOs + * + * @table: GPIO table + * @size: number of entries in @table + */ +void msm_gpios_disable_free(const struct msm_gpio *table, int size); + +/** + * msm_gpios_request() - request set of GPIOs + * In case of error, all operations rolled back. + * Return error code. + * + * @table: GPIO table + * @size: number of entries in @table + */ +int msm_gpios_request(const struct msm_gpio *table, int size); + +/** + * msm_gpios_free() - free set of GPIOs + * + * @table: GPIO table + * @size: number of entries in @table + */ +void msm_gpios_free(const struct msm_gpio *table, int size); + +/** + * msm_gpios_enable() - enable set of GPIOs + * In case of error, all operations rolled back. + * Return error code. + * + * @table: GPIO table + * @size: number of entries in @table + */ +int msm_gpios_enable(const struct msm_gpio *table, int size); + +/** + * msm_gpios_disable() - disable set of GPIOs + * + * @table: GPIO table + * @size: number of entries in @table + */ +int msm_gpios_disable(const struct msm_gpio *table, int size); + +/* GPIO TLMM (Top Level Multiplexing) Definitions */ + +/* GPIO TLMM: Function -- GPIO specific */ + +/* GPIO TLMM: Direction */ +enum { + GPIO_CFG_INPUT, + GPIO_CFG_OUTPUT, +}; + +/* GPIO TLMM: Pullup/Pulldown */ +enum { + GPIO_CFG_NO_PULL, + GPIO_CFG_PULL_DOWN, + GPIO_CFG_KEEPER, + GPIO_CFG_PULL_UP, +}; + +/* GPIO TLMM: Drive Strength */ +enum { + GPIO_CFG_2MA, + GPIO_CFG_4MA, + GPIO_CFG_6MA, + GPIO_CFG_8MA, + GPIO_CFG_10MA, + GPIO_CFG_12MA, + GPIO_CFG_14MA, + GPIO_CFG_16MA, +}; + +enum { + GPIO_CFG_ENABLE, + GPIO_CFG_DISABLE, +}; + +#define GPIO_CFG(gpio, func, dir, pull, drvstr) \ + ((((gpio) & 0x3FF) << 4) | \ + ((func) & 0xf) | \ + (((dir) & 0x1) << 14) | \ + (((pull) & 0x3) << 15) | \ + (((drvstr) & 0xF) << 17)) + +/** + * extract GPIO pin from bit-field used for gpio_tlmm_config + */ +#define GPIO_PIN(gpio_cfg) (((gpio_cfg) >> 4) & 0x3ff) +#define GPIO_FUNC(gpio_cfg) (((gpio_cfg) >> 0) & 0xf) +#define GPIO_DIR(gpio_cfg) (((gpio_cfg) >> 14) & 0x1) +#define GPIO_PULL(gpio_cfg) (((gpio_cfg) >> 15) & 0x3) +#define GPIO_DRVSTR(gpio_cfg) (((gpio_cfg) >> 17) & 0xf) + +int gpio_tlmm_config(unsigned config, unsigned disable); + +#endif /* __ASM_ARCH_MSM_GPIO_V1_H */ diff --git a/arch/arm/mach-msm/include/mach/gpio.h b/arch/arm/mach-msm/include/mach/gpio.h index 36ad50d3bfa..fa69b9f030a 100644 --- a/arch/arm/mach-msm/include/mach/gpio.h +++ b/arch/arm/mach-msm/include/mach/gpio.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. * Author: Mike Lockwood * * This software is licensed under the terms of the GNU General Public @@ -16,11 +16,226 @@ #ifndef __ASM_ARCH_MSM_GPIO_H #define __ASM_ARCH_MSM_GPIO_H +#ifdef CONFIG_ARCH_MSM8X60 +#define ARCH_NR_GPIOS 512 +#endif + +#include #include +#include + +#define FIRST_BOARD_GPIO NR_GPIO_IRQS + +extern struct irq_chip msm_gpio_irq_extn; + +static inline int gpio_get_value(unsigned gpio) +{ + return __gpio_get_value(gpio); +} + +static inline void gpio_set_value(unsigned gpio, int value) +{ + __gpio_set_value(gpio, value); +} + +static inline int gpio_cansleep(unsigned gpio) +{ + return __gpio_cansleep(gpio); +} + +static inline int gpio_to_irq(unsigned gpio) +{ + return __gpio_to_irq(gpio); +} -#define gpio_get_value __gpio_get_value -#define gpio_set_value __gpio_set_value -#define gpio_cansleep __gpio_cansleep -#define gpio_to_irq __gpio_to_irq +/** + * struct msm_gpio - GPIO pin description + * @gpio_cfg - configuration bitmap, as per gpio_tlmm_config() + * @label - textual label + * + * Usually, GPIO's are operated by sets. + * This struct accumulate all GPIO information in single source + * and facilitete group operations provided by msm_gpios_xxx() + */ +struct msm_gpio { + u32 gpio_cfg; + const char *label; +}; + +/** + * msm_gpios_request_enable() - request and enable set of GPIOs + * + * Request and configure set of GPIO's + * In case of error, all operations rolled back. + * Return error code. + * + * @table: GPIO table + * @size: number of entries in @table + */ +int msm_gpios_request_enable(const struct msm_gpio *table, int size); + +/** + * msm_gpios_disable_free() - disable and free set of GPIOs + * + * @table: GPIO table + * @size: number of entries in @table + */ +void msm_gpios_disable_free(const struct msm_gpio *table, int size); + +/** + * msm_gpios_request() - request set of GPIOs + * In case of error, all operations rolled back. + * Return error code. + * + * @table: GPIO table + * @size: number of entries in @table + */ +int msm_gpios_request(const struct msm_gpio *table, int size); + +/** + * msm_gpios_free() - free set of GPIOs + * + * @table: GPIO table + * @size: number of entries in @table + */ +void msm_gpios_free(const struct msm_gpio *table, int size); + +/** + * msm_gpios_enable() - enable set of GPIOs + * In case of error, all operations rolled back. + * Return error code. + * + * @table: GPIO table + * @size: number of entries in @table + */ +int msm_gpios_enable(const struct msm_gpio *table, int size); + +/** + * msm_gpios_disable() - disable set of GPIOs + * + * @table: GPIO table + * @size: number of entries in @table + */ +int msm_gpios_disable(const struct msm_gpio *table, int size); + +/** + * msm_gpios_show_resume_irq() - show the interrupts that could have triggered + * resume + */ +void msm_gpio_show_resume_irq(void); + +/* GPIO TLMM (Top Level Multiplexing) Definitions */ + +/* GPIO TLMM: Function -- GPIO specific */ + +/* GPIO TLMM: Direction */ +enum { + GPIO_CFG_INPUT, + GPIO_CFG_OUTPUT, +}; + +/* GPIO TLMM: Pullup/Pulldown */ +enum { + GPIO_CFG_NO_PULL, + GPIO_CFG_PULL_DOWN, + GPIO_CFG_KEEPER, + GPIO_CFG_PULL_UP, +}; + +/* GPIO TLMM: Drive Strength */ +enum { + GPIO_CFG_2MA, + GPIO_CFG_4MA, + GPIO_CFG_6MA, + GPIO_CFG_8MA, + GPIO_CFG_10MA, + GPIO_CFG_12MA, + GPIO_CFG_14MA, + GPIO_CFG_16MA, +}; + +enum { + GPIO_CFG_ENABLE, + GPIO_CFG_DISABLE, +}; + +#define GPIO_CFG(gpio, func, dir, pull, drvstr) \ + ((((gpio) & 0x3FF) << 4) | \ + ((func) & 0xf) | \ + (((dir) & 0x1) << 14) | \ + (((pull) & 0x3) << 15) | \ + (((drvstr) & 0xF) << 17)) + +/** + * extract GPIO pin from bit-field used for gpio_tlmm_config + */ +#define GPIO_PIN(gpio_cfg) (((gpio_cfg) >> 4) & 0x3ff) +#define GPIO_FUNC(gpio_cfg) (((gpio_cfg) >> 0) & 0xf) +#define GPIO_DIR(gpio_cfg) (((gpio_cfg) >> 14) & 0x1) +#define GPIO_PULL(gpio_cfg) (((gpio_cfg) >> 15) & 0x3) +#define GPIO_DRVSTR(gpio_cfg) (((gpio_cfg) >> 17) & 0xf) + +int gpio_tlmm_config(unsigned config, unsigned disable); + +enum msm_tlmm_hdrive_tgt { + TLMM_HDRV_SDC4_CLK = 0, + TLMM_HDRV_SDC4_CMD, + TLMM_HDRV_SDC4_DATA, + TLMM_HDRV_SDC3_CLK, + TLMM_HDRV_SDC3_CMD, + TLMM_HDRV_SDC3_DATA, + TLMM_HDRV_SDC1_CLK, + TLMM_HDRV_SDC1_CMD, + TLMM_HDRV_SDC1_DATA, +}; + +enum msm_tlmm_pull_tgt { + TLMM_PULL_SDC4_CMD = 0, + TLMM_PULL_SDC4_DATA, + TLMM_PULL_SDC3_CLK, + TLMM_PULL_SDC3_CMD, + TLMM_PULL_SDC3_DATA, + TLMM_PULL_SDC1_CLK, + TLMM_PULL_SDC1_CMD, + TLMM_PULL_SDC1_DATA, +}; + +#ifdef CONFIG_MSM_V2_TLMM +void msm_tlmm_set_hdrive(enum msm_tlmm_hdrive_tgt tgt, int drv_str); +void msm_tlmm_set_pull(enum msm_tlmm_pull_tgt tgt, int pull); + +/* + * A GPIO can be set as a direct-connect IRQ. This can be used to bypass + * the normal summary-interrupt mechanism for those GPIO lines deemed to be + * higher priority or otherwise worthy of special treatment, but resources + * are limited: only a few DC interrupt lines are available. + * Care must be taken when usurping a GPIO in this manner, as the summary + * interrupt controller has no idea that the GPIO has been taken away from it. + * Clients can still register to receive the summary interrupt assigned + * to that GPIO, which will uninstall it as a direct connect IRQ with + * no warning. + * + * The irq passed to this function is the DC IRQ number, not the + * irq number seen by the scorpion when the interrupt triggers. For example, + * if 0 is specified, then when DC IRQ 0 triggers, the scorpion will see + * interrupt TLMM_MSM_DIR_CONN_IRQ_0. + * + * input_polarity parameter specifies when the gpio should raise the direct + * interrupt. A value of 0 means that it is active low, anything else means + * active high + * + */ +int msm_gpio_install_direct_irq(unsigned gpio, unsigned irq, + unsigned int input_polarity); +#else +static inline void msm_tlmm_set_hdrive(enum msm_tlmm_hdrive_tgt tgt, + int drv_str) {} +static inline void msm_tlmm_set_pull(enum msm_tlmm_pull_tgt tgt, int pull) {} +static inline int msm_gpio_install_direct_irq(unsigned gpio, unsigned irq, + unsigned int input_polarity) +{ + return -ENOSYS; +} +#endif #endif /* __ASM_ARCH_MSM_GPIO_H */ diff --git a/arch/arm/mach-msm/include/mach/htc_35mm_jack.h b/arch/arm/mach-msm/include/mach/htc_35mm_jack.h new file mode 100644 index 00000000000..5ce1e2a1e47 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/htc_35mm_jack.h @@ -0,0 +1,31 @@ +/* arch/arm/mach-msm/include/mach/htc_35mm_jack.h + * + * Copyright (C) 2009 HTC, Inc. + * Author: Arec Kao + * + * 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 HTC_35MM_REMOTE_H +#define HTC_35MM_REMOTE_H + +/* Driver interfaces */ +int htc_35mm_jack_plug_event(int insert, int *hpin_stable); +int htc_35mm_key_event(int key, int *hpin_stable); + +/* Platform Specific Callbacks */ +struct h35mm_platform_data { + int (*plug_event_enable)(void); + int (*headset_has_mic)(void); + int (*key_event_enable)(void); + int (*key_event_disable)(void); +}; +#endif diff --git a/arch/arm/mach-msm/include/mach/htc_acoustic_qsd.h b/arch/arm/mach-msm/include/mach/htc_acoustic_qsd.h new file mode 100644 index 00000000000..2139bf9f903 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/htc_acoustic_qsd.h @@ -0,0 +1,29 @@ +/* include/asm/mach-msm/htc_acoustic_qsd.h + * + * Copyright (C) 2009 HTC Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _ARCH_ARM_MACH_MSM_HTC_ACOUSTIC_QSD_H_ +#define _ARCH_ARM_MACH_MSM_HTC_ACOUSTIC_QSD_H_ + +struct qsd_acoustic_ops { + void (*enable_mic_bias)(int en); +}; + +void acoustic_register_ops(struct qsd_acoustic_ops *ops); + +int turn_mic_bias_on(int on); +int force_headset_speaker_on(int enable); +int enable_aux_loopback(uint32_t enable); + +#endif + diff --git a/arch/arm/mach-msm/include/mach/htc_headset.h b/arch/arm/mach-msm/include/mach/htc_headset.h new file mode 100644 index 00000000000..2f4c18db262 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/htc_headset.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2008 HTC, Inc. + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_HTC_HEADSET_H +#define __ASM_ARCH_HTC_HEADSET_H + +struct h2w_platform_data { + char *power_name; + int cable_in1; + int cable_in2; + int h2w_clk; + int h2w_data; + int debug_uart; + void (*config_cpld)(int); + void (*init_cpld)(void); + void (*set_dat)(int); + void (*set_clk)(int); + void (*set_dat_dir)(int); + void (*set_clk_dir)(int); + int (*get_dat)(void); + int (*get_clk)(void); +}; + +#define BIT_HEADSET (1 << 0) +#define BIT_HEADSET_NO_MIC (1 << 1) +#define BIT_TTY (1 << 2) +#define BIT_FM_HEADSET (1 << 3) +#define BIT_FM_SPEAKER (1 << 4) + +enum { + H2W_NO_DEVICE = 0, + H2W_HTC_HEADSET = 1, +/* H2W_TTY_DEVICE = 2,*/ + NORMAL_HEARPHONE= 2, + H2W_DEVICE = 3, + H2W_USB_CRADLE = 4, + H2W_UART_DEBUG = 5, +}; + +enum { + H2W_GPIO = 0, + H2W_UART1 = 1, + H2W_UART3 = 2, + H2W_BT = 3 +}; + +#define RESEND_DELAY (3) /* ms */ +#define MAX_ACK_RESEND_TIMES (6) /* follow spec */ +#define MAX_HOST_RESEND_TIMES (3) /* follow spec */ +#define MAX_HYGEIA_RESEND_TIMES (5) + +#define H2W_ASCR_DEVICE_INI (0x01) +#define H2W_ASCR_ACT_EN (0x02) +#define H2W_ASCR_PHONE_IN (0x04) +#define H2W_ASCR_RESET (0x08) +#define H2W_ASCR_AUDIO_IN (0x10) + +#define H2W_LED_OFF (0x0) +#define H2W_LED_BKL (0x1) +#define H2W_LED_MTL (0x2) + +typedef enum { + /* === system group 0x0000~0x00FF === */ + /* (R) Accessory type register */ + H2W_SYSTEM = 0x0000, + /* (R) Maximum group address */ + H2W_MAX_GP_ADD = 0x0001, + /* (R/W) Accessory system control register0 */ + H2W_ASCR0 = 0x0002, + + /* === key group 0x0100~0x01FF === */ + /* (R) Key group maximum sub address */ + H2W_KEY_MAXADD = 0x0100, + /* (R) ASCII key press down flag */ + H2W_ASCII_DOWN = 0x0101, + /* (R) ASCII key release up flag */ + H2W_ASCII_UP = 0x0102, + /* (R) Function key status flag */ + H2W_FNKEY_UPDOWN = 0x0103, + /* (R/W) Key device status */ + H2W_KD_STATUS = 0x0104, + + /* === led group 0x0200~0x02FF === */ + /* (R) LED group maximum sub address */ + H2W_LED_MAXADD = 0x0200, + /* (R/W) LED control register0 */ + H2W_LEDCT0 = 0x0201, + + /* === crdl group 0x0300~0x03FF === */ + /* (R) Cardle group maximum sub address */ + H2W_CRDL_MAXADD = 0x0300, + /* (R/W) Cardle group function control register0 */ + H2W_CRDLCT0 = 0x0301, + + /* === car kit group 0x0400~0x04FF === */ + H2W_CARKIT_MAXADD = 0x0400, + + /* === usb host group 0x0500~0x05FF === */ + H2W_USBHOST_MAXADD = 0x0500, + + /* === medical group 0x0600~0x06FF === */ + H2W_MED_MAXADD = 0x0600, + H2W_MED_CONTROL = 0x0601, + H2W_MED_IN_DATA = 0x0602, +} H2W_ADDR; + + +typedef struct H2W_INFO { + /* system group */ + unsigned char CLK_SP; + int SLEEP_PR; + unsigned char HW_REV; + int AUDIO_DEVICE; + unsigned char ACC_CLASS; + unsigned char MAX_GP_ADD; + + /* key group */ + int KEY_MAXADD; + int ASCII_DOWN; + int ASCII_UP; + int FNKEY_UPDOWN; + int KD_STATUS; + + /* led group */ + int LED_MAXADD; + int LEDCT0; + + /* medical group */ + int MED_MAXADD; + unsigned char AP_ID; + unsigned char AP_EN; + unsigned char DATA_EN; +} H2W_INFO; + +typedef enum { + H2W_500KHz = 1, + H2W_250KHz = 2, + H2W_166KHz = 3, + H2W_125KHz = 4, + H2W_100KHz = 5, + H2W_83KHz = 6, + H2W_71KHz = 7, + H2W_62KHz = 8, + H2W_55KHz = 9, + H2W_50KHz = 10, +} H2W_SPEED; + +typedef enum { + H2W_KEY_INVALID = -1, + H2W_KEY_PLAY = 0, + H2W_KEY_FORWARD = 1, + H2W_KEY_BACKWARD = 2, + H2W_KEY_VOLUP = 3, + H2W_KEY_VOLDOWN = 4, + H2W_KEY_PICKUP = 5, + H2W_KEY_HANGUP = 6, + H2W_KEY_MUTE = 7, + H2W_KEY_HOLD = 8, + H2W_NUM_KEYFUNC = 9, +} KEYFUNC; +#endif diff --git a/arch/arm/mach-msm/include/mach/htc_pwrsink.h b/arch/arm/mach-msm/include/mach/htc_pwrsink.h new file mode 100644 index 00000000000..c7a91f1d906 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/htc_pwrsink.h @@ -0,0 +1,87 @@ +/* include/asm/mach-msm/htc_pwrsink.h + * + * Copyright (C) 2007 Google, Inc. + * Copyright (C) 2008 HTC Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _ARCH_ARM_MACH_MSM_HTC_PWRSINK_H_ +#define _ARCH_ARM_MACH_MSM_HTC_PWRSINK_H_ + +#include +#include + +typedef enum { + PWRSINK_AUDIO_PCM = 0, + PWRSINK_AUDIO_MP3, + PWRSINK_AUDIO_AAC, + + PWRSINK_AUDIO_LAST = PWRSINK_AUDIO_AAC, + PWRSINK_AUDIO_INVALID +} pwrsink_audio_id_type; + +struct pwr_sink_audio { + unsigned volume; + unsigned percent; +}; + +typedef enum { + PWRSINK_SYSTEM_LOAD = 0, + PWRSINK_AUDIO, + PWRSINK_BACKLIGHT, + PWRSINK_LED_BUTTON, + PWRSINK_LED_KEYBOARD, + PWRSINK_GP_CLK, + PWRSINK_BLUETOOTH, + PWRSINK_CAMERA, + PWRSINK_SDCARD, + PWRSINK_VIDEO, + PWRSINK_WIFI, + + PWRSINK_LAST = PWRSINK_WIFI, + PWRSINK_INVALID +} pwrsink_id_type; + +struct pwr_sink { + pwrsink_id_type id; + unsigned ua_max; + unsigned percent_util; +}; + +struct pwr_sink_platform_data { + unsigned num_sinks; + struct pwr_sink *sinks; + int (*suspend_late)(struct platform_device *, pm_message_t state); + int (*resume_early)(struct platform_device *); + void (*suspend_early)(struct early_suspend *); + void (*resume_late)(struct early_suspend *); +}; + +#ifndef CONFIG_HTC_PWRSINK +static inline int htc_pwrsink_set(pwrsink_id_type id, unsigned percent) +{ + return 0; +} +static inline int htc_pwrsink_audio_set(pwrsink_audio_id_type id, + unsigned percent_utilized) { return 0; } +static inline int htc_pwrsink_audio_volume_set( + pwrsink_audio_id_type id, unsigned volume) { return 0; } +static inline int htc_pwrsink_audio_path_set(unsigned path) { return 0; } +#else +extern int htc_pwrsink_set(pwrsink_id_type id, unsigned percent); +extern int htc_pwrsink_audio_set(pwrsink_audio_id_type id, + unsigned percent_utilized); +extern int htc_pwrsink_audio_volume_set(pwrsink_audio_id_type id, + unsigned volume); +extern int htc_pwrsink_audio_path_set(unsigned path); +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/io.h b/arch/arm/mach-msm/include/mach/io.h index dc1b928745e..bdac617f420 100644 --- a/arch/arm/mach-msm/include/mach/io.h +++ b/arch/arm/mach-msm/include/mach/io.h @@ -23,14 +23,7 @@ void __iomem *__msm_ioremap(unsigned long phys_addr, size_t size, unsigned int mtype); -#define __io(a) __typesafe_io(a) +#define __io(a) __typesafe_io(a) #define __mem_pci(a) (a) -void msm_map_qsd8x50_io(void); -void msm_map_msm7x30_io(void); -void msm_map_msm8x60_io(void); -void msm_map_msm8960_io(void); - -extern unsigned int msm_shared_ram_phys; - #endif diff --git a/arch/arm/mach-msm/include/mach/iommu.h b/arch/arm/mach-msm/include/mach/iommu.h index 5c7c955e6d2..ac66b0fab6a 100644 --- a/arch/arm/mach-msm/include/mach/iommu.h +++ b/arch/arm/mach-msm/include/mach/iommu.h @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #ifndef MSM_IOMMU_H @@ -85,6 +80,7 @@ struct msm_iommu_drvdata { int ncb; struct clk *clk; struct clk *pclk; + const char *name; }; /** diff --git a/arch/arm/mach-msm/include/mach/iommu_domains.h b/arch/arm/mach-msm/include/mach/iommu_domains.h new file mode 100644 index 00000000000..69fe3f0cde5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/iommu_domains.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _ARCH_IOMMU_DOMAINS_H +#define _ARCH_IOMMU_DOMAINS_H + +/* + * Nothing in this file is to be used outside of the iommu wrappers. + * Do NOT try and use anything here in a driver. Doing so is incorrect. + */ + +/* + * These subsytem ids are NOT for public use. Please check the iommu + * wrapper header for the properly abstracted id to pass in. + */ +enum msm_subsystem_id { + INVALID_SUBSYS_ID = -1, + JPEGD_SUBSYS_ID, + VPE_SUBSYS_ID, + MDP0_SUBSYS_ID, + MDP1_SUBSYS_ID, + ROT_SUBSYS_ID, + IJPEG_SUBSYS_ID, + VFE_SUBSYS_ID, + VCODEC_A_SUBSYS_ID, + VCODEC_B_SUBSYS_ID, + GFX3D_SUBSYS_ID, + GFX2D0_SUBSYS_ID, + GFX2D1_SUBSYS_ID, + VIDEO_FWARE_ID, + MAX_SUBSYSTEM_ID +}; + +static inline int msm_subsystem_check_id(int subsys_id) +{ + return subsys_id > INVALID_SUBSYS_ID && subsys_id < MAX_SUBSYSTEM_ID; +} + +#if defined(CONFIG_MSM_IOMMU) +extern struct iommu_domain *msm_subsystem_get_domain(int subsys_id); + +extern struct mem_pool *msm_subsystem_get_pool(int subsys_id); +#else +static inline struct iommu_domain + *msm_subsystem_get_domain(int subsys_id) { return NULL; } + +static inline struct mem_pool + *msm_subsystem_get_pool(int subsys_id) { return NULL; } +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/iommu_hw-8xxx.h b/arch/arm/mach-msm/include/mach/iommu_hw-8xxx.h index fc160101dea..57f2d37846f 100644 --- a/arch/arm/mach-msm/include/mach/iommu_hw-8xxx.h +++ b/arch/arm/mach-msm/include/mach/iommu_hw-8xxx.h @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #ifndef __ARCH_ARM_MACH_MSM_IOMMU_HW_8XXX_H @@ -20,14 +15,14 @@ #define CTX_SHIFT 12 -#define GET_GLOBAL_REG(reg, base) (readl((base) + (reg))) +#define GET_GLOBAL_REG(reg, base) (readl_relaxed((base) + (reg))) #define GET_CTX_REG(reg, base, ctx) \ - (readl((base) + (reg) + ((ctx) << CTX_SHIFT))) + (readl_relaxed((base) + (reg) + ((ctx) << CTX_SHIFT))) -#define SET_GLOBAL_REG(reg, base, val) writel((val), ((base) + (reg))) +#define SET_GLOBAL_REG(reg, base, val) writel_relaxed((val), ((base) + (reg))) #define SET_CTX_REG(reg, base, ctx, val) \ - writel((val), ((base) + (reg) + ((ctx) << CTX_SHIFT))) + writel_relaxed((val), ((base) + (reg) + ((ctx) << CTX_SHIFT))) /* Wrappers for numbered registers */ #define SET_GLOBAL_REG_N(b, n, r, v) SET_GLOBAL_REG(b, ((r) + (n << 2)), (v)) @@ -43,12 +38,13 @@ #define SET_CONTEXT_FIELD(b, c, r, F, v) \ SET_FIELD(((b) + (r) + ((c) << CTX_SHIFT)), F##_MASK, F##_SHIFT, (v)) -#define GET_FIELD(addr, mask, shift) ((readl(addr) >> (shift)) & (mask)) +#define GET_FIELD(addr, mask, shift) ((readl_relaxed(addr) >> (shift)) & (mask)) #define SET_FIELD(addr, mask, shift, v) \ do { \ - int t = readl(addr); \ - writel((t & ~((mask) << (shift))) + (((v) & (mask)) << (shift)), addr);\ + int t = readl_relaxed(addr); \ + writel_relaxed((t & ~((mask) << (shift))) + (((v) & \ + (mask)) << (shift)), addr);\ } while (0) diff --git a/arch/arm/mach-msm/include/mach/irqs-7x00.h b/arch/arm/mach-msm/include/mach/irqs-7x00.h index f1fe70612fe..a8e1da2d62d 100644 --- a/arch/arm/mach-msm/include/mach/irqs-7x00.h +++ b/arch/arm/mach-msm/include/mach/irqs-7x00.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. * Author: Brian Swetland */ @@ -71,5 +71,7 @@ #define NR_MSM_IRQS 64 #define NR_GPIO_IRQS 122 #define NR_BOARD_IRQS 64 +#define NR_SIRC_IRQS 0 +#define INT_ADSP_A11_SMSM INT_ADSP_A11 #endif diff --git a/arch/arm/mach-msm/include/mach/irqs-7x30.h b/arch/arm/mach-msm/include/mach/irqs-7x30.h index 1f15902655f..a624bbf6051 100644 --- a/arch/arm/mach-msm/include/mach/irqs-7x30.h +++ b/arch/arm/mach-msm/include/mach/irqs-7x30.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2009, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and @@ -24,7 +24,7 @@ #define INT_AVS_SVIC_SW_DONE 6 #define INT_SC_DBG_RX_FULL 7 #define INT_SC_DBG_TX_EMPTY 8 -#define INT_ARM11_PM 9 +#define INT_ARMQC_PERFMON 9 #define INT_AVS_REQ_DOWN 10 #define INT_AVS_REQ_UP 11 #define INT_SC_ACG 12 @@ -131,8 +131,8 @@ #define INT_TCHSCRN1 INT_TSSC_SAMPLE #define INT_TCHSCRN2 INT_TSSC_PENUP #define INT_GP_TIMER_EXP INT_GPT0_TIMER_EXP -#define INT_ADSP_A11 INT_AD5A_MPROC_APPS_0 -#define INT_ADSP_A9_A11 INT_AD5A_MPROC_APPS_1 +#define INT_ADSP_A9_A11 INT_AD5A_MPROC_APPS_0 +#define INT_ADSP_A11 INT_AD5A_MPROC_APPS_1 #define INT_MDDI_EXT INT_EMDH #define INT_MDDI_PRI INT_PMDH #define INT_MDDI_CLIENT INT_MDC @@ -142,12 +142,9 @@ #define NR_MSM_IRQS 128 #define NR_GPIO_IRQS 182 #define PMIC8058_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS) -#define NR_PMIC8058_GPIO_IRQS 40 -#define NR_PMIC8058_MPP_IRQS 12 -#define NR_PMIC8058_MISC_IRQS 8 -#define NR_PMIC8058_IRQS (NR_PMIC8058_GPIO_IRQS +\ - NR_PMIC8058_MPP_IRQS +\ - NR_PMIC8058_MISC_IRQS) +#define NR_PMIC8058_IRQS 256 #define NR_BOARD_IRQS NR_PMIC8058_IRQS +#define INT_ADSP_A11_SMSM INT_ADSP_A11 + #endif /* __ASM_ARCH_MSM_IRQS_7X30_H */ diff --git a/arch/arm/mach-msm/include/mach/irqs-7xxx.h b/arch/arm/mach-msm/include/mach/irqs-7xxx.h new file mode 100644 index 00000000000..22970b3083c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-7xxx.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + */ + +#ifndef __ASM_ARCH_MSM_IRQS_7XXX_H +#define __ASM_ARCH_MSM_IRQS_7XXX_H + +/* MSM ARM11 Interrupt Numbers */ +/* See 80-VE113-1 A, pp219-221 */ + +#define INT_A9_M2A_0 0 +#define INT_A9_M2A_1 1 +#define INT_A9_M2A_2 2 +#define INT_A9_M2A_3 3 +#define INT_A9_M2A_4 4 +#define INT_A9_M2A_5 5 +#define INT_A9_M2A_6 6 +#define INT_GP_TIMER_EXP 7 +#define INT_DEBUG_TIMER_EXP 8 +#define INT_UART1 9 +#define INT_UART2 10 +#define INT_UART3 11 +#define INT_UART1_RX 12 +#define INT_UART2_RX 13 +#define INT_UART3_RX 14 +#define INT_USB_OTG 15 +#if defined(CONFIG_ARCH_MSM7X27A) +#define INT_DSI_IRQ 16 +#define INT_CSI_IRQ_1 17 +#define INT_CSI_IRQ_0 18 +#else +#define INT_MDDI_PRI 16 +#define INT_MDDI_EXT 17 +#define INT_MDDI_CLIENT 18 +#endif +#define INT_MDP 19 +#define INT_GRAPHICS 20 +#define INT_ADM_AARM 21 +#define INT_ADSP_A11 22 +#define INT_ADSP_A9_A11 23 +#define INT_SDC1_0 24 +#define INT_SDC1_1 25 +#define INT_SDC2_0 26 +#define INT_SDC2_1 27 +#define INT_KEYSENSE 28 +#define INT_TCHSCRN_SSBI 29 +#define INT_TCHSCRN1 30 +#define INT_TCHSCRN2 31 + +#define INT_GPIO_GROUP1 (32 + 0) +#define INT_GPIO_GROUP2 (32 + 1) +#define INT_PWB_I2C (32 + 2) +#define INT_SOFTRESET (32 + 3) +#define INT_NAND_WR_ER_DONE (32 + 4) +#define INT_NAND_OP_DONE (32 + 5) +#define INT_PBUS_ARM11 (32 + 6) +#define INT_AXI_MPU_SMI (32 + 7) +#define INT_AXI_MPU_EBI1 (32 + 8) +#define INT_AD_HSSD (32 + 9) +#define INT_ARM11_PMU (32 + 10) +#define INT_ARM11_DMA (32 + 11) +#define INT_TSIF_IRQ (32 + 12) +#define INT_UART1DM_IRQ (32 + 13) +#define INT_UART1DM_RX (32 + 14) +#define INT_USB_HS (32 + 15) +#define INT_SDC3_0 (32 + 16) +#define INT_SDC3_1 (32 + 17) +#define INT_SDC4_0 (32 + 18) +#define INT_SDC4_1 (32 + 19) +#define INT_UART2DM_IRQ (32 + 20) +#define INT_UART2DM_RX (32 + 21) + +/* 22-31 are reserved except 7x27a*/ +#if defined(CONFIG_ARCH_MSM7X27A) +#define INT_L2CC_EM (32 + 22) +#define INT_L2CC_INTR (32 + 23) +#define INT_CE_IRQ (32 + 24) +#endif + +/* 7x00A uses 122, but 7x25 has up to 132. */ +#define NR_GPIO_IRQS 133 +#define NR_MSM_IRQS 64 +#define NR_BOARD_IRQS 64 +#define NR_MSM_GPIOS NR_GPIO_IRQS + +#define INT_ADSP_A11_SMSM INT_ADSP_A11 +#endif diff --git a/arch/arm/mach-msm/include/mach/irqs-8064.h b/arch/arm/mach-msm/include/mach/irqs-8064.h new file mode 100644 index 00000000000..59ee8a4cb77 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-8064.h @@ -0,0 +1,323 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ASM_ARCH_MSM_IRQS_8064_H +#define __ASM_ARCH_MSM_IRQS_8064_H + +/* MSM ACPU Interrupt Numbers */ + +/* + * 0-15: STI/SGI (software triggered/generated interrupts) + * 16-31: PPI (private peripheral interrupts) + * 32+: SPI (shared peripheral interrupts) + */ + +#define GIC_PPI_START 16 +#define GIC_SPI_START 32 + +#define INT_VGIC (GIC_PPI_START + 0) +#define INT_DEBUG_TIMER_EXP (GIC_PPI_START + 1) +#define INT_GP_TIMER_EXP (GIC_PPI_START + 2) +#define INT_GP_TIMER2_EXP (GIC_PPI_START + 3) +#define WDT0_ACCSCSSNBARK_INT (GIC_PPI_START + 4) +#define WDT1_ACCSCSSNBARK_INT (GIC_PPI_START + 5) +#define AVS_SVICINT (GIC_PPI_START + 6) +#define AVS_SVICINTSWDONE (GIC_PPI_START + 7) +#define CPU_DBGCPUXCOMMRXFULL (GIC_PPI_START + 8) +#define CPU_DBGCPUXCOMMTXEMPTY (GIC_PPI_START + 9) +#define INT_ARMQC_PERFMON (GIC_PPI_START + 10) +#define SC_AVSCPUXDOWN (GIC_PPI_START + 11) +#define SC_AVSCPUXUP (GIC_PPI_START + 12) +#define SC_SICCPUXACGIRPTREQ (GIC_PPI_START + 13) +#define SC_SICCPUXEXTFAULTIRPTREQ (GIC_PPI_START + 14) +/* PPI 15 is unused */ + +#define APCC_QGICACGIRPTREQ (GIC_SPI_START + 0) +#define APCC_QGICL2PERFMONIRPTREQ (GIC_SPI_START + 1) +#define SC_SICL2PERFMONIRPTREQ APCC_QGICL2PERFMONIRPTREQ +#define APCC_QGICL2IRPTREQ (GIC_SPI_START + 2) +#define APCC_QGICMPUIRPTREQ (GIC_SPI_START + 3) +#define TLMM_MSM_DIR_CONN_IRQ_0 (GIC_SPI_START + 4) +#define TLMM_MSM_DIR_CONN_IRQ_1 (GIC_SPI_START + 5) +#define TLMM_MSM_DIR_CONN_IRQ_2 (GIC_SPI_START + 6) +#define TLMM_MSM_DIR_CONN_IRQ_3 (GIC_SPI_START + 7) +#define TLMM_MSM_DIR_CONN_IRQ_4 (GIC_SPI_START + 8) +#define TLMM_MSM_DIR_CONN_IRQ_5 (GIC_SPI_START + 9) +#define TLMM_MSM_DIR_CONN_IRQ_6 (GIC_SPI_START + 10) +#define TLMM_MSM_DIR_CONN_IRQ_7 (GIC_SPI_START + 11) +#define TLMM_MSM_DIR_CONN_IRQ_8 (GIC_SPI_START + 12) +#define TLMM_MSM_DIR_CONN_IRQ_9 (GIC_SPI_START + 13) +#define PM8921_SEC_IRQ_N (GIC_SPI_START + 14) +#define PM8018_SEC_IRQ_N (GIC_SPI_START + 15) +#define TLMM_MSM_SUMMARY_IRQ (GIC_SPI_START + 16) +#define SPDM_RT_1_IRQ (GIC_SPI_START + 17) +#define SPDM_DIAG_IRQ (GIC_SPI_START + 18) +#define RPM_APCC_CPU0_GP_HIGH_IRQ (GIC_SPI_START + 19) +#define RPM_APCC_CPU0_GP_MEDIUM_IRQ (GIC_SPI_START + 20) +#define RPM_APCC_CPU0_GP_LOW_IRQ (GIC_SPI_START + 21) +#define RPM_APCC_CPU0_WAKE_UP_IRQ (GIC_SPI_START + 22) +#define RPM_APCC_CPU1_GP_HIGH_IRQ (GIC_SPI_START + 23) +#define RPM_APCC_CPU1_GP_MEDIUM_IRQ (GIC_SPI_START + 24) +#define RPM_APCC_CPU1_GP_LOW_IRQ (GIC_SPI_START + 25) +#define RPM_APCC_CPU1_WAKE_UP_IRQ (GIC_SPI_START + 26) +#define SSBI2_2_SC_CPU0_SECURE_IRQ (GIC_SPI_START + 27) +#define SSBI2_2_SC_CPU0_NON_SECURE_IRQ (GIC_SPI_START + 28) +#define SSBI2_1_SC_CPU0_SECURE_IRQ (GIC_SPI_START + 29) +#define SSBI2_1_SC_CPU0_NON_SECURE_IRQ (GIC_SPI_START + 30) +#define MSMC_SC_SEC_CE_IRQ (GIC_SPI_START + 31) +#define MSMC_SC_PRI_CE_IRQ (GIC_SPI_START + 32) +#define SLIMBUS0_CORE_EE1_IRQ (GIC_SPI_START + 33) +#define SLIMBUS0_BAM_EE1_IRQ (GIC_SPI_START + 34) +#define KPSS_SPARE_0 (GIC_SPI_START + 35) +#define GSS_A5_WDOG_EXPIRED (GIC_SPI_START + 36) +#define GSS_TO_APPS_IRQ_0 (GIC_SPI_START + 37) +#define GSS_TO_APPS_IRQ_1 (GIC_SPI_START + 38) +#define GSS_TO_APPS_IRQ_2 (GIC_SPI_START + 39) +#define GSS_TO_APPS_IRQ_3 (GIC_SPI_START + 40) +#define GSS_TO_APPS_IRQ_4 (GIC_SPI_START + 41) +#define GSS_TO_APPS_IRQ_5 (GIC_SPI_START + 42) +#define GSS_TO_APPS_IRQ_6 (GIC_SPI_START + 43) +#define GSS_TO_APPS_IRQ_7 (GIC_SPI_START + 44) +#define GSS_TO_APPS_IRQ_8 (GIC_SPI_START + 45) +#define GSS_TO_APPS_IRQ_9 (GIC_SPI_START + 46) +#define VPE_IRQ (GIC_SPI_START + 47) +#define VFE_IRQ (GIC_SPI_START + 48) +#define VCODEC_IRQ (GIC_SPI_START + 49) +#define KPSS_SPARE_1 (GIC_SPI_START + 50) +#define SMMU_VPE_CB_SC_SECURE_IRQ (GIC_SPI_START + 51) +#define SMMU_VPE_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 52) +#define SMMU_VFE_CB_SC_SECURE_IRQ (GIC_SPI_START + 53) +#define SMMU_VFE_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 54) +#define SMMU_VCODEC_B_CB_SC_SECURE_IRQ (GIC_SPI_START + 55) +#define SMMU_VCODEC_B_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 56) +#define SMMU_VCODEC_A_CB_SC_SECURE_IRQ (GIC_SPI_START + 57) +#define SMMU_VCODEC_A_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 58) +#define SMMU_ROT_CB_SC_SECURE_IRQ (GIC_SPI_START + 59) +#define SMMU_ROT_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 60) +#define SMMU_MDP1_CB_SC_SECURE_IRQ (GIC_SPI_START + 61) +#define SMMU_MDP1_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 62) +#define SMMU_MDP0_CB_SC_SECURE_IRQ (GIC_SPI_START + 63) +#define SMMU_MDP0_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 64) +#define SMMU_JPEGD_CB_SC_SECURE_IRQ (GIC_SPI_START + 65) +#define SMMU_JPEGD_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 66) +#define SMMU_IJPEG_CB_SC_SECURE_IRQ (GIC_SPI_START + 67) +#define SMMU_IJPEG_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 68) +#define SMMU_GFX3D_CB_SC_SECURE_IRQ (GIC_SPI_START + 69) +#define SMMU_GFX3D_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 70) +#define VCAP_VP (GIC_SPI_START + 71) +#define VCAP_VC (GIC_SPI_START + 72) +#define ROT_IRQ (GIC_SPI_START + 73) +#define MMSS_FABRIC_IRQ (GIC_SPI_START + 74) +#define MDP_IRQ (GIC_SPI_START + 75) +#define JPEGD_IRQ (GIC_SPI_START + 76) +#define JPEG_IRQ (GIC_SPI_START + 77) +#define MMSS_IMEM_IRQ (GIC_SPI_START + 78) +#define HDMI_IRQ (GIC_SPI_START + 79) +#define GFX3D_IRQ (GIC_SPI_START + 80) +#define GFX3d_VBIF_IRQ (GIC_SPI_START + 81) +#define DSI1_IRQ (GIC_SPI_START + 82) +#define CSI_1_IRQ (GIC_SPI_START + 83) +#define CSI_0_IRQ (GIC_SPI_START + 84) +#define LPASS_SCSS_AUDIO_IF_OUT0_IRQ (GIC_SPI_START + 85) +#define LPASS_SCSS_MIDI_IRQ (GIC_SPI_START + 86) +#define LPASS_Q6SS_WDOG_EXPIRED (GIC_SPI_START + 87) +#define LPASS_SCSS_GP_LOW_IRQ (GIC_SPI_START + 88) +#define LPASS_SCSS_GP_MEDIUM_IRQ (GIC_SPI_START + 89) +#define LPASS_SCSS_GP_HIGH_IRQ (GIC_SPI_START + 90) +#define TOP_IMEM_IRQ (GIC_SPI_START + 91) +#define FABRIC_SYS_IRQ (GIC_SPI_START + 92) +#define FABRIC_APPS_IRQ (GIC_SPI_START + 93) +#define USB1_HS_BAM_IRQ (GIC_SPI_START + 94) +#define SDC4_BAM_IRQ (GIC_SPI_START + 95) +#define SDC3_BAM_IRQ (GIC_SPI_START + 96) +#define SDC2_BAM_IRQ (GIC_SPI_START + 97) +#define SDC1_BAM_IRQ (GIC_SPI_START + 98) +#define FABRIC_SPS_IRQ (GIC_SPI_START + 99) +#define USB1_HS_IRQ (GIC_SPI_START + 100) +#define SDC4_IRQ_0 (GIC_SPI_START + 101) +#define SDC3_IRQ_0 (GIC_SPI_START + 102) +#define SDC2_IRQ_0 (GIC_SPI_START + 103) +#define SDC1_IRQ_0 (GIC_SPI_START + 104) +#define SPS_BAM_DMA_IRQ (GIC_SPI_START + 105) +#define SPS_SEC_VIOL_IRQ (GIC_SPI_START + 106) +#define SPS_MTI_0 (GIC_SPI_START + 107) +#define SPS_MTI_1 (GIC_SPI_START + 108) +#define SPS_MTI_2 (GIC_SPI_START + 109) +#define SPS_MTI_3 (GIC_SPI_START + 110) +#define SPS_MTI_4 (GIC_SPI_START + 111) +#define SPS_MTI_5 (GIC_SPI_START + 112) +#define SPS_MTI_6 (GIC_SPI_START + 113) +#define SPS_MTI_7 (GIC_SPI_START + 114) +#define SPS_MTI_8 (GIC_SPI_START + 115) +#define SPS_MTI_9 (GIC_SPI_START + 116) +#define SPS_MTI_10 (GIC_SPI_START + 117) +#define SPS_MTI_11 (GIC_SPI_START + 118) +#define SPS_MTI_12 (GIC_SPI_START + 119) +#define SPS_MTI_13 (GIC_SPI_START + 120) +#define SPS_MTI_14 (GIC_SPI_START + 121) +#define SPS_MTI_15 (GIC_SPI_START + 122) +#define SPS_MTI_16 (GIC_SPI_START + 123) +#define SPS_MTI_17 (GIC_SPI_START + 124) +#define SPS_MTI_18 (GIC_SPI_START + 125) +#define SPS_MTI_19 (GIC_SPI_START + 126) +#define SPS_MTI_20 (GIC_SPI_START + 127) +#define SPS_MTI_21 (GIC_SPI_START + 128) +#define SPS_MTI_22 (GIC_SPI_START + 129) +#define SPS_MTI_23 (GIC_SPI_START + 130) +#define SPS_MTI_24 (GIC_SPI_START + 131) +#define SPS_MTI_25 (GIC_SPI_START + 132) +#define SPS_MTI_26 (GIC_SPI_START + 133) +#define SPS_MTI_27 (GIC_SPI_START + 134) +#define SPS_MTI_28 (GIC_SPI_START + 135) +#define SPS_MTI_29 (GIC_SPI_START + 136) +#define SPS_MTI_30 (GIC_SPI_START + 137) +#define SPS_MTI_31 (GIC_SPI_START + 138) +#define CSIPHY_0_4LN_IRQ (GIC_SPI_START + 139) +#define CSIPHY_1_2LN_IRQ (GIC_SPI_START + 140) +#define KPSS_SPARE_2 (GIC_SPI_START + 141) +#define USB1_IRQ (GIC_SPI_START + 142) +#define TSSC_SSBI_IRQ (GIC_SPI_START + 143) +#define TSSC_SAMPLE_IRQ (GIC_SPI_START + 144) +#define TSSC_PENUP_IRQ (GIC_SPI_START + 145) +#define KPSS_SPARE_3 (GIC_SPI_START + 146) +#define KPSS_SPARE_4 (GIC_SPI_START + 147) +#define KPSS_SPARE_5 (GIC_SPI_START + 148) +#define KPSS_SPARE_6 (GIC_SPI_START + 149) +#define GSBI3_UARTDM_IRQ (GIC_SPI_START + 150) +#define GSBI3_QUP_IRQ (GIC_SPI_START + 151) +#define GSBI4_UARTDM_IRQ (GIC_SPI_START + 152) +#define GSBI4_QUP_IRQ (GIC_SPI_START + 153) +#define GSBI5_UARTDM_IRQ (GIC_SPI_START + 154) +#define GSBI5_QUP_IRQ (GIC_SPI_START + 155) +#define GSBI6_UARTDM_IRQ (GIC_SPI_START + 156) +#define GSBI6_QUP_IRQ (GIC_SPI_START + 157) +#define GSBI7_UARTDM_IRQ (GIC_SPI_START + 158) +#define GSBI7_QUP_IRQ (GIC_SPI_START + 159) +#define KPSS_SPARE_7 (GIC_SPI_START + 160) +#define KPSS_SPARE_8 (GIC_SPI_START + 161) +#define TSIF_TSPP_IRQ (GIC_SPI_START + 162) +#define TSIF_BAM_IRQ (GIC_SPI_START + 163) +#define TSIF2_IRQ (GIC_SPI_START + 164) +#define TSIF1_IRQ (GIC_SPI_START + 165) +#define DSI2_IRQ (GIC_SPI_START + 166) +#define ISPIF_IRQ (GIC_SPI_START + 167) +#define MSMC_SC_SEC_TMR_IRQ (GIC_SPI_START + 168) +#define MSMC_SC_SEC_WDOG_BARK_IRQ (GIC_SPI_START + 169) +#define ADM_0_SCSS_0_IRQ (GIC_SPI_START + 170) +#define ADM_0_SCSS_1_IRQ (GIC_SPI_START + 171) +#define ADM_0_SCSS_2_IRQ (GIC_SPI_START + 172) +#define ADM_0_SCSS_3_IRQ (GIC_SPI_START + 173) +#define CC_SCSS_WDT1CPU1BITEEXPIRED (GIC_SPI_START + 174) +#define CC_SCSS_WDT1CPU0BITEEXPIRED (GIC_SPI_START + 175) +#define CC_SCSS_WDT0CPU1BITEEXPIRED (GIC_SPI_START + 176) +#define CC_SCSS_WDT0CPU0BITEEXPIRED (GIC_SPI_START + 177) +#define TSENS_UPPER_LOWER_INT (GIC_SPI_START + 178) +#define SSBI2_2_SC_CPU1_SECURE_INT (GIC_SPI_START + 179) +#define SSBI2_2_SC_CPU1_NON_SECURE_INT (GIC_SPI_START + 180) +#define SSBI2_1_SC_CPU1_SECURE_INT (GIC_SPI_START + 181) +#define SSBI2_1_SC_CPU1_NON_SECURE_INT (GIC_SPI_START + 182) +#define XPU_SUMMARY_IRQ (GIC_SPI_START + 183) +#define BUS_EXCEPTION_SUMMARY_IRQ (GIC_SPI_START + 184) +#define HSDDRX_EBI1CH0_IRQ (GIC_SPI_START + 185) +#define HSDDRX_EBI1CH1_IRQ (GIC_SPI_START + 186) +#define USB3_HS_BAM_IRQ (GIC_SPI_START + 187) +#define USB3_HS_IRQ (GIC_SPI_START + 188) +#define CC_SCSS_WDT1CPU3BITEEXPIRED (GIC_SPI_START + 189) +#define CC_SCSS_WDT1CPU2BITEEXPIRED (GIC_SPI_START + 190) +#define CC_SCSS_WDT0CPU3BITEEXPIRED (GIC_SPI_START + 191) +#define CC_SCSS_WDT0CPU2BITEEXPIRED (GIC_SPI_START + 192) +#define APQ8064_GSBI1_UARTDM_IRQ (GIC_SPI_START + 193) +#define APQ8064_GSBI1_QUP_IRQ (GIC_SPI_START + 194) +#define APQ8064_GSBI2_UARTDM_IRQ (GIC_SPI_START + 195) +#define APQ8064_GSBI2_QUP_IRQ (GIC_SPI_START + 196) +#define RIVA_APSS_LTECOEX_IRQ (GIC_SPI_START + 197) +#define RIVA_APSS_SPARE_IRQ (GIC_SPI_START + 198) +#define RIVA_APSS_WDOG_BITE_RESET_RDY_IRQ (GIC_SPI_START + 199) +#define RIVA_APSS_RESET_DONE_IRQ (GIC_SPI_START + 200) +#define RIVA_APSS_ASIC_IRQ (GIC_SPI_START + 201) +#define RIVA_APPS_WLAN_RX_DATA_AVAIL_IRQ (GIC_SPI_START + 202) +#define RIVA_APPS_WLAN_DATA_XFER_DONE_IRQ (GIC_SPI_START + 203) +#define RIVA_APPS_WLAN_SMSM_IRQ (GIC_SPI_START + 204) +#define RIVA_APPS_LOG_CTRL_IRQ (GIC_SPI_START + 205) +#define RIVA_APPS_FM_CTRL_IRQ (GIC_SPI_START + 206) +#define RIVA_APPS_HCI_IRQ (GIC_SPI_START + 207) +#define RIVA_APPS_WLAN_CTRL_IRQ (GIC_SPI_START + 208) +#define SATA_CONTROLLER_IRQ (GIC_SPI_START + 209) +#define SMMU_GFX3D1_CB_SC_SECURE_IRQ (GIC_SPI_START + 210) +#define SMMU_GFX3D1_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 211) +#define KPSS_SPARE_9 (GIC_SPI_START + 212) +#define PPSS_WDOG_TIMER_IRQ (GIC_SPI_START + 213) +#define USB4_HS_BAM_IRQ (GIC_SPI_START + 214) +#define USB4_HS_IRQ (GIC_SPI_START + 215) +#define QDSS_ETB_IRQ (GIC_SPI_START + 216) +#define QDSS_CTI2KPSS_CPU1_IRQ (GIC_SPI_START + 217) +#define QDSS_CTI2KPSS_CPU0_IRQ (GIC_SPI_START + 218) +#define TLMM_MSM_DIR_CONN_IRQ_16 (GIC_SPI_START + 219) +#define TLMM_MSM_DIR_CONN_IRQ_17 (GIC_SPI_START + 220) +#define TLMM_MSM_DIR_CONN_IRQ_18 (GIC_SPI_START + 221) +#define TLMM_MSM_DIR_CONN_IRQ_19 (GIC_SPI_START + 222) +#define TLMM_MSM_DIR_CONN_IRQ_20 (GIC_SPI_START + 223) +#define TLMM_MSM_DIR_CONN_IRQ_21 (GIC_SPI_START + 224) +#define PM8921_USR_IRQ_N (GIC_SPI_START + 225) +#define PM8821_USR_IRQ_N (GIC_SPI_START + 226) + +#define CSI_2_IRQ (GIC_SPI_START + 227) +#define APQ8064_CSIPHY_2LN_IRQ (GIC_SPI_START + 228) +#define USB2_HSIC_IRQ (GIC_SPI_START + 229) +#define CE2_BAM_XPU_IRQ (GIC_SPI_START + 230) +#define CE1_BAM_XPU_IRQ (GIC_SPI_START + 231) +#define RPM_SCSS_CPU2_WAKE_UP_IRQ (GIC_SPI_START + 232) +#define RPM_SCSS_CPU3_WAKE_UP_IRQ (GIC_SPI_START + 233) +#define CS3_BAM_XPU_IRQ (GIC_SPI_START + 234) +#define CE3_IRQ (GIC_SPI_START + 235) +#define SMMU_VCAP_CB_SC_SECURE_IRQ (GIC_SPI_START + 236) +#define SMMU_VCAM_CP_SC_NON_SECURE_IRQ (GIC_SPI_START + 237) +#define PCIE20_INT_MSI (GIC_SPI_START + 238) +#define PCIE20_INTA (GIC_SPI_START + 239) +#define PCIE20_INTB (GIC_SPI_START + 240) +#define PCIE20_INTC (GIC_SPI_START + 241) +#define PCIE20_INTD (GIC_SPI_START + 242) +#define PCIE20_INT_PLS_HP (GIC_SPI_START + 243) +#define PCIE20_INT_PLS_PME (GIC_SPI_START + 244) +#define PCIE20_INT_LINK_UP (GIC_SPI_START + 245) +#define PCIE20_INT_LINK_DOWN (GIC_SPI_START + 246) +#define PCIE20_INT_HP_LEGACY (GIC_SPI_START + 247) +#define PCIE20_AER_LEGACY (GIC_SPI_START + 248) +#define PCIE20_INT_PME_LEGACY (GIC_SPI_START + 249) +#define PCIE20_INT_BRIDGE_FLUSH_N (GIC_SPI_START + 250) + +/* Backwards compatible IRQ macros. */ +#define INT_ADM_AARM ADM_0_SCSS_0_IRQ + +/* + * For now, use the maximum number of interrupts until a pending GIC issue + * is sorted out + */ +#define NR_MSM_IRQS 256 +#define NR_GPIO_IRQS 150 +#define NR_PM8921_IRQS 256 +#define NR_TABLA_IRQS 49 +#define NR_BOARD_IRQS (NR_PM8921_IRQS + NR_TABLA_IRQS) +#define NR_TLMM_MSM_DIR_CONN_IRQ 8 /*Need to Verify this Count*/ +#define NR_MSM_GPIOS NR_GPIO_IRQS + +/* smd/smsm interrupts */ +#define INT_A9_M2A_0 MSS_TO_APPS_IRQ_0 +#define INT_A9_M2A_5 MSS_TO_APPS_IRQ_1 +#define INT_ADSP_A11 LPASS_SCSS_GP_HIGH_IRQ +#define INT_ADSP_A11_SMSM LPASS_SCSS_GP_MEDIUM_IRQ +#define INT_DSPS_A11 SPS_MTI_31 +#define INT_WCNSS_A11 RIVA_APSS_SPARE_IRQ +#define INT_WCNSS_A11_SMSM RIVA_APPS_WLAN_SMSM_IRQ + +#endif + diff --git a/arch/arm/mach-msm/include/mach/irqs-8960.h b/arch/arm/mach-msm/include/mach/irqs-8960.h index 81ab2a6792b..312ccc315be 100644 --- a/arch/arm/mach-msm/include/mach/irqs-8960.h +++ b/arch/arm/mach-msm/include/mach/irqs-8960.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2011 Code Aurora Forum. All rights reserved. +/* Copyright (c) 2011, Code Aurora Forum. 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 and @@ -32,30 +32,31 @@ #define AVS_SVICINTSWDONE (GIC_PPI_START + 7) #define CPU_DBGCPUXCOMMRXFULL (GIC_PPI_START + 8) #define CPU_DBGCPUXCOMMTXEMPTY (GIC_PPI_START + 9) -#define CPU_SICCPUXPERFMONIRPTREQ (GIC_PPI_START + 10) +#define INT_ARMQC_PERFMON (GIC_PPI_START + 10) #define SC_AVSCPUXDOWN (GIC_PPI_START + 11) #define SC_AVSCPUXUP (GIC_PPI_START + 12) #define SC_SICCPUXACGIRPTREQ (GIC_PPI_START + 13) #define SC_SICCPUXEXTFAULTIRPTREQ (GIC_PPI_START + 14) /* PPI 15 is unused */ -#define SC_SICMPUIRPTREQ (GIC_SPI_START + 0) -#define SC_SICL2IRPTREQ (GIC_SPI_START + 1) -#define SC_SICL2PERFMONIRPTREQ (GIC_SPI_START + 2) -#define SC_SICAGCIRPTREQ (GIC_SPI_START + 3) -#define TLMM_APCC_DIR_CONN_IRQ_0 (GIC_SPI_START + 4) -#define TLMM_APCC_DIR_CONN_IRQ_1 (GIC_SPI_START + 5) -#define TLMM_APCC_DIR_CONN_IRQ_2 (GIC_SPI_START + 6) -#define TLMM_APCC_DIR_CONN_IRQ_3 (GIC_SPI_START + 7) -#define TLMM_APCC_DIR_CONN_IRQ_4 (GIC_SPI_START + 8) -#define TLMM_APCC_DIR_CONN_IRQ_5 (GIC_SPI_START + 9) -#define TLMM_APCC_DIR_CONN_IRQ_6 (GIC_SPI_START + 10) -#define TLMM_APCC_DIR_CONN_IRQ_7 (GIC_SPI_START + 11) -#define TLMM_APCC_DIR_CONN_IRQ_8 (GIC_SPI_START + 12) -#define TLMM_APCC_DIR_CONN_IRQ_9 (GIC_SPI_START + 13) +#define APCC_QGICACGIRPTREQ (GIC_SPI_START + 0) +#define APCC_QGICL2PERFMONIRPTREQ (GIC_SPI_START + 1) +#define SC_SICL2PERFMONIRPTREQ APCC_QGICL2PERFMONIRPTREQ +#define APCC_QGICL2IRPTREQ (GIC_SPI_START + 2) +#define APCC_QGICMPUIRPTREQ (GIC_SPI_START + 3) +#define TLMM_MSM_DIR_CONN_IRQ_0 (GIC_SPI_START + 4) +#define TLMM_MSM_DIR_CONN_IRQ_1 (GIC_SPI_START + 5) +#define TLMM_MSM_DIR_CONN_IRQ_2 (GIC_SPI_START + 6) +#define TLMM_MSM_DIR_CONN_IRQ_3 (GIC_SPI_START + 7) +#define TLMM_MSM_DIR_CONN_IRQ_4 (GIC_SPI_START + 8) +#define TLMM_MSM_DIR_CONN_IRQ_5 (GIC_SPI_START + 9) +#define TLMM_MSM_DIR_CONN_IRQ_6 (GIC_SPI_START + 10) +#define TLMM_MSM_DIR_CONN_IRQ_7 (GIC_SPI_START + 11) +#define TLMM_MSM_DIR_CONN_IRQ_8 (GIC_SPI_START + 12) +#define TLMM_MSM_DIR_CONN_IRQ_9 (GIC_SPI_START + 13) #define PM8921_SEC_IRQ_103 (GIC_SPI_START + 14) #define PM8018_SEC_IRQ_106 (GIC_SPI_START + 15) -#define TLMM_APCC_SUMMARY_IRQ (GIC_SPI_START + 16) +#define TLMM_MSM_SUMMARY_IRQ (GIC_SPI_START + 16) #define SPDM_RT_1_IRQ (GIC_SPI_START + 17) #define SPDM_DIAG_IRQ (GIC_SPI_START + 18) #define RPM_APCC_CPU0_GP_HIGH_IRQ (GIC_SPI_START + 19) @@ -179,16 +180,16 @@ #define SPS_MTI_30 (GIC_SPI_START + 137) #define SPS_MTI_31 (GIC_SPI_START + 138) #define CSIPHY_4LN_IRQ (GIC_SPI_START + 139) -#define CSIPHY_2LN_IRQ (GIC_SPI_START + 140) +#define MSM8960_CSIPHY_2LN_IRQ (GIC_SPI_START + 140) #define USB2_IRQ (GIC_SPI_START + 141) #define USB1_IRQ (GIC_SPI_START + 142) #define TSSC_SSBI_IRQ (GIC_SPI_START + 143) #define TSSC_SAMPLE_IRQ (GIC_SPI_START + 144) #define TSSC_PENUP_IRQ (GIC_SPI_START + 145) -#define GSBI1_UARTDM_IRQ (GIC_SPI_START + 146) -#define GSBI1_QUP_IRQ (GIC_SPI_START + 147) -#define GSBI2_UARTDM_IRQ (GIC_SPI_START + 148) -#define GSBI2_QUP_IRQ (GIC_SPI_START + 149) +#define MSM8960_GSBI1_UARTDM_IRQ (GIC_SPI_START + 146) +#define MSM8960_GSBI1_QUP_IRQ (GIC_SPI_START + 147) +#define MSM8960_GSBI2_UARTDM_IRQ (GIC_SPI_START + 148) +#define MSM8960_GSBI2_QUP_IRQ (GIC_SPI_START + 149) #define GSBI3_UARTDM_IRQ (GIC_SPI_START + 150) #define GSBI3_QUP_IRQ (GIC_SPI_START + 151) #define GSBI4_UARTDM_IRQ (GIC_SPI_START + 152) @@ -209,10 +210,10 @@ #define ISPIF_IRQ (GIC_SPI_START + 167) #define MSMC_SC_SEC_TMR_IRQ (GIC_SPI_START + 168) #define MSMC_SC_SEC_WDOG_BARK_IRQ (GIC_SPI_START + 169) -#define INT_ADM0_SCSS_0_IRQ (GIC_SPI_START + 170) -#define INT_ADM0_SCSS_1_IRQ (GIC_SPI_START + 171) -#define INT_ADM0_SCSS_2_IRQ (GIC_SPI_START + 172) -#define INT_ADM0_SCSS_3_IRQ (GIC_SPI_START + 173) +#define ADM_0_SCSS_0_IRQ (GIC_SPI_START + 170) +#define ADM_0_SCSS_1_IRQ (GIC_SPI_START + 171) +#define ADM_0_SCSS_2_IRQ (GIC_SPI_START + 172) +#define ADM_0_SCSS_3_IRQ (GIC_SPI_START + 173) #define CC_SCSS_WDT1CPU1BITEEXPIRED (GIC_SPI_START + 174) #define CC_SCSS_WDT1CPU0BITEEXPIRED (GIC_SPI_START + 175) #define CC_SCSS_WDT0CPU1BITEEXPIRED (GIC_SPI_START + 176) @@ -239,11 +240,11 @@ #define RIVA_APSS_LTECOEX_IRQ (GIC_SPI_START + 197) #define RIVA_APSS_SPARE_IRQ (GIC_SPI_START + 198) #define RIVA_APSS_WDOG_BITE_RESET_RDY_IRQ (GIC_SPI_START + 199) -#define RIVA_ASS_RESET_DONE_IRQ (GIC_SPI_START + 200) +#define RIVA_APSS_RESET_DONE_IRQ (GIC_SPI_START + 200) #define RIVA_APSS_ASIC_IRQ (GIC_SPI_START + 201) #define RIVA_APPS_WLAN_RX_DATA_AVAIL_IRQ (GIC_SPI_START + 202) #define RIVA_APPS_WLAN_DATA_XFER_DONE_IRQ (GIC_SPI_START + 203) -#define RIVA_APPS_WLAM_SMSM_IRQ (GIC_SPI_START + 204) +#define RIVA_APPS_WLAN_SMSM_IRQ (GIC_SPI_START + 204) #define RIVA_APPS_LOG_CTRL_IRQ (GIC_SPI_START + 205) #define RIVA_APPS_FM_CTRL_IRQ (GIC_SPI_START + 206) #define RIVA_APPS_HCI_IRQ (GIC_SPI_START + 207) @@ -258,20 +259,36 @@ #define QDSS_ETB_IRQ (GIC_SPI_START + 216) #define QDSS_CTI2KPSS_CPU1_IRQ (GIC_SPI_START + 217) #define QDSS_CTI2KPSS_CPU0_IRQ (GIC_SPI_START + 218) -#define TLMM_APCC_DIR_CONN_IRQ_16 (GIC_SPI_START + 219) -#define TLMM_APCC_DIR_CONN_IRQ_17 (GIC_SPI_START + 220) -#define TLMM_APCC_DIR_CONN_IRQ_18 (GIC_SPI_START + 221) -#define TLMM_APCC_DIR_CONN_IRQ_19 (GIC_SPI_START + 222) -#define TLMM_APCC_DIR_CONN_IRQ_20 (GIC_SPI_START + 223) -#define TLMM_APCC_DIR_CONN_IRQ_21 (GIC_SPI_START + 224) +#define TLMM_MSM_DIR_CONN_IRQ_16 (GIC_SPI_START + 219) +#define TLMM_MSM_DIR_CONN_IRQ_17 (GIC_SPI_START + 220) +#define TLMM_MSM_DIR_CONN_IRQ_18 (GIC_SPI_START + 221) +#define TLMM_MSM_DIR_CONN_IRQ_19 (GIC_SPI_START + 222) +#define TLMM_MSM_DIR_CONN_IRQ_20 (GIC_SPI_START + 223) +#define TLMM_MSM_DIR_CONN_IRQ_21 (GIC_SPI_START + 224) #define PM8921_SEC_IRQ_104 (GIC_SPI_START + 225) #define PM8018_SEC_IRQ_107 (GIC_SPI_START + 226) +/* Backwards compatible IRQ macros. */ +#define INT_ADM_AARM ADM_0_SCSS_0_IRQ + /* For now, use the maximum number of interrupts until a pending GIC issue * is sorted out */ -#define NR_MSM_IRQS 1020 -#define NR_BOARD_IRQS 0 -#define NR_GPIO_IRQS 0 +#define NR_MSM_IRQS 256 +#define NR_GPIO_IRQS 150 +#define NR_PM8921_IRQS 256 +#define NR_TABLA_IRQS 49 +#define NR_BOARD_IRQS (NR_PM8921_IRQS + NR_TABLA_IRQS) +#define NR_TLMM_MSM_DIR_CONN_IRQ 8 /*Need to Verify this Count*/ +#define NR_MSM_GPIOS NR_GPIO_IRQS + +/* smd/smsm interrupts */ +#define INT_A9_M2A_0 MSS_TO_APPS_IRQ_0 +#define INT_A9_M2A_5 MSS_TO_APPS_IRQ_1 +#define INT_ADSP_A11 LPASS_SCSS_GP_HIGH_IRQ +#define INT_ADSP_A11_SMSM LPASS_SCSS_GP_MEDIUM_IRQ +#define INT_DSPS_A11 SPS_MTI_31 +#define INT_WCNSS_A11 RIVA_APSS_SPARE_IRQ +#define INT_WCNSS_A11_SMSM RIVA_APPS_WLAN_SMSM_IRQ #endif diff --git a/arch/arm/mach-msm/include/mach/irqs-8x50.h b/arch/arm/mach-msm/include/mach/irqs-8x50.h index 26adbe0e940..f0d70f94a09 100644 --- a/arch/arm/mach-msm/include/mach/irqs-8x50.h +++ b/arch/arm/mach-msm/include/mach/irqs-8x50.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and @@ -57,7 +57,7 @@ #define INT_TCSR_MPRPH_SC2 (32 + 6) #define INT_OP_PEN (32 + 7) #define INT_AD_HSSD (32 + 8) -#define INT_ARM11_PM (32 + 9) +#define INT_ARMQC_PERFMON (32 + 9) #define INT_SDMA_NON_SECURE (32 + 10) #define INT_TSIF_IRQ (32 + 11) #define INT_UART1DM_IRQ (32 + 12) @@ -85,4 +85,5 @@ #define NR_MSM_IRQS 64 #define NR_BOARD_IRQS 64 +#define INT_ADSP_A11_SMSM INT_ADSP_A11 #endif diff --git a/arch/arm/mach-msm/include/mach/irqs-8x60.h b/arch/arm/mach-msm/include/mach/irqs-8x60.h index f65841c74c0..c9729f4bbbd 100644 --- a/arch/arm/mach-msm/include/mach/irqs-8x60.h +++ b/arch/arm/mach-msm/include/mach/irqs-8x60.h @@ -1,8 +1,8 @@ -/* Copyright (c) 2010 Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2011 Code Aurora Forum. 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 free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -17,9 +17,8 @@ /* MSM ACPU Interrupt Numbers */ /* 0-15: STI/SGI (software triggered/generated interrupts) - * 16-31: PPI (private peripheral interrupts) - * 32+: SPI (shared peripheral interrupts) - */ + 16-31: PPI (private peripheral interrupts) + 32+: SPI (shared peripheral interrupts) */ #define GIC_PPI_START 16 #define GIC_SPI_START 32 @@ -33,7 +32,7 @@ #define AVS_SVICINTSWDONE (GIC_PPI_START + 6) #define CPU_DBGCPUXCOMMRXFULL (GIC_PPI_START + 7) #define CPU_DBGCPUXCOMMTXEMPTY (GIC_PPI_START + 8) -#define CPU_SICCPUXPERFMONIRPTREQ (GIC_PPI_START + 9) +#define INT_ARMQC_PERFMON (GIC_PPI_START + 9) #define SC_AVSCPUXDOWN (GIC_PPI_START + 10) #define SC_AVSCPUXUP (GIC_PPI_START + 11) #define SC_SICCPUXACGIRPTREQ (GIC_PPI_START + 12) @@ -42,21 +41,21 @@ #define SC_SICMPUIRPTREQ (GIC_SPI_START + 0) #define SC_SICL2IRPTREQ (GIC_SPI_START + 1) -#define SC_SICL2ACGIRPTREQ (GIC_SPI_START + 2) +#define SC_SICL2PERFMONIRPTREQ (GIC_SPI_START + 2) #define NC (GIC_SPI_START + 3) -#define TLMM_SCSS_DIR_CONN_IRQ_0 (GIC_SPI_START + 4) -#define TLMM_SCSS_DIR_CONN_IRQ_1 (GIC_SPI_START + 5) -#define TLMM_SCSS_DIR_CONN_IRQ_2 (GIC_SPI_START + 6) -#define TLMM_SCSS_DIR_CONN_IRQ_3 (GIC_SPI_START + 7) -#define TLMM_SCSS_DIR_CONN_IRQ_4 (GIC_SPI_START + 8) -#define TLMM_SCSS_DIR_CONN_IRQ_5 (GIC_SPI_START + 9) -#define TLMM_SCSS_DIR_CONN_IRQ_6 (GIC_SPI_START + 10) -#define TLMM_SCSS_DIR_CONN_IRQ_7 (GIC_SPI_START + 11) -#define TLMM_SCSS_DIR_CONN_IRQ_8 (GIC_SPI_START + 12) -#define TLMM_SCSS_DIR_CONN_IRQ_9 (GIC_SPI_START + 13) +#define TLMM_MSM_DIR_CONN_IRQ_0 (GIC_SPI_START + 4) +#define TLMM_MSM_DIR_CONN_IRQ_1 (GIC_SPI_START + 5) +#define TLMM_MSM_DIR_CONN_IRQ_2 (GIC_SPI_START + 6) +#define TLMM_MSM_DIR_CONN_IRQ_3 (GIC_SPI_START + 7) +#define TLMM_MSM_DIR_CONN_IRQ_4 (GIC_SPI_START + 8) +#define TLMM_MSM_DIR_CONN_IRQ_5 (GIC_SPI_START + 9) +#define TLMM_MSM_DIR_CONN_IRQ_6 (GIC_SPI_START + 10) +#define TLMM_MSM_DIR_CONN_IRQ_7 (GIC_SPI_START + 11) +#define TLMM_MSM_DIR_CONN_IRQ_8 (GIC_SPI_START + 12) +#define TLMM_MSM_DIR_CONN_IRQ_9 (GIC_SPI_START + 13) #define PM8058_SEC_IRQ_N (GIC_SPI_START + 14) #define PM8901_SEC_IRQ_N (GIC_SPI_START + 15) -#define TLMM_SCSS_SUMMARY_IRQ (GIC_SPI_START + 16) +#define TLMM_MSM_SUMMARY_IRQ (GIC_SPI_START + 16) #define SPDM_RT_1_IRQ (GIC_SPI_START + 17) #define SPDM_DIAG_IRQ (GIC_SPI_START + 18) #define RPM_SCSS_CPU0_GP_HIGH_IRQ (GIC_SPI_START + 19) @@ -87,7 +86,7 @@ #define MARM_SCSS_GP_IRQ_7 (GIC_SPI_START + 44) #define MARM_SCSS_GP_IRQ_8 (GIC_SPI_START + 45) #define MARM_SCSS_GP_IRQ_9 (GIC_SPI_START + 46) -#define VPE_IRQ (GIC_SPI_START + 47) +#define INT_VPE (GIC_SPI_START + 47) #define VFE_IRQ (GIC_SPI_START + 48) #define VCODEC_IRQ (GIC_SPI_START + 49) #define TV_ENC_IRQ (GIC_SPI_START + 50) @@ -115,9 +114,9 @@ #define SMMU_GFX2D0_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 72) #define ROT_IRQ (GIC_SPI_START + 73) #define MMSS_FABRIC_IRQ (GIC_SPI_START + 74) -#define MDP_IRQ (GIC_SPI_START + 75) +#define INT_MDP (GIC_SPI_START + 75) #define JPEGD_IRQ (GIC_SPI_START + 76) -#define JPEG_IRQ (GIC_SPI_START + 77) +#define INT_JPEG (GIC_SPI_START + 77) #define MMSS_IMEM_IRQ (GIC_SPI_START + 78) #define HDMI_IRQ (GIC_SPI_START + 79) #define GFX3D_IRQ (GIC_SPI_START + 80) @@ -186,21 +185,21 @@ #define TSSC_SSBI_IRQ (GIC_SPI_START + 143) #define TSSC_SAMPLE_IRQ (GIC_SPI_START + 144) #define TSSC_PENUP_IRQ (GIC_SPI_START + 145) -#define INT_UART1DM_IRQ (GIC_SPI_START + 146) -#define GSBI1_QUP_IRQ (GIC_SPI_START + 147) -#define INT_UART2DM_IRQ (GIC_SPI_START + 148) -#define GSBI2_QUP_IRQ (GIC_SPI_START + 149) -#define INT_UART3DM_IRQ (GIC_SPI_START + 150) +#define GSBI1_UARTDM_IRQ (GIC_SPI_START + 146) +#define GSBI1_QUP_IRQ (GIC_SPI_START + 147) +#define GSBI2_UARTDM_IRQ (GIC_SPI_START + 148) +#define GSBI2_QUP_IRQ (GIC_SPI_START + 149) +#define GSBI3_UARTDM_IRQ (GIC_SPI_START + 150) #define GSBI3_QUP_IRQ (GIC_SPI_START + 151) -#define INT_UART4DM_IRQ (GIC_SPI_START + 152) +#define GSBI4_UARTDM_IRQ (GIC_SPI_START + 152) #define GSBI4_QUP_IRQ (GIC_SPI_START + 153) -#define INT_UART5DM_IRQ (GIC_SPI_START + 154) +#define GSBI5_UARTDM_IRQ (GIC_SPI_START + 154) #define GSBI5_QUP_IRQ (GIC_SPI_START + 155) -#define INT_UART6DM_IRQ (GIC_SPI_START + 156) +#define GSBI6_UARTDM_IRQ (GIC_SPI_START + 156) #define GSBI6_QUP_IRQ (GIC_SPI_START + 157) -#define INT_UART7DM_IRQ (GIC_SPI_START + 158) +#define GSBI7_UARTDM_IRQ (GIC_SPI_START + 158) #define GSBI7_QUP_IRQ (GIC_SPI_START + 159) -#define INT_UART8DM_IRQ (GIC_SPI_START + 160) +#define GSBI8_UARTDM_IRQ (GIC_SPI_START + 160) #define GSBI8_QUP_IRQ (GIC_SPI_START + 161) #define TSIF_TSPP_IRQ (GIC_SPI_START + 162) #define TSIF_BAM_IRQ (GIC_SPI_START + 163) @@ -229,20 +228,19 @@ #define HSDDRX_EBI1_IRQ (GIC_SPI_START + 186) #define SDC5_BAM_IRQ (GIC_SPI_START + 187) #define SDC5_IRQ_0 (GIC_SPI_START + 188) -#define INT_UART9DM_IRQ (GIC_SPI_START + 189) +#define GSBI9_UARTDM_IRQ (GIC_SPI_START + 189) #define GSBI9_QUP_IRQ (GIC_SPI_START + 190) -#define INT_UART10DM_IRQ (GIC_SPI_START + 191) +#define GSBI10_UARTDM_IRQ (GIC_SPI_START + 191) #define GSBI10_QUP_IRQ (GIC_SPI_START + 192) -#define INT_UART11DM_IRQ (GIC_SPI_START + 193) +#define GSBI11_UARTDM_IRQ (GIC_SPI_START + 193) #define GSBI11_QUP_IRQ (GIC_SPI_START + 194) -#define INT_UART12DM_IRQ (GIC_SPI_START + 195) +#define GSBI12_UARTDM_IRQ (GIC_SPI_START + 195) #define GSBI12_QUP_IRQ (GIC_SPI_START + 196) -/*SPI 197 to 209 arent used in 8x60*/ -#define SMMU_GFX2D1_CB_SC_SECURE_IRQ (GIC_SPI_START + 210) -#define SMMU_GFX2D1_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 211) +#define SMMU_GFX2D1_CB_SC_SECURE_IRQ (GIC_SPI_START + 210) +#define SMMU_GFX2D1_CB_SC_NON_SECURE_IRQ (GIC_SPI_START + 211) +#define GFX2D1_IRQ (GIC_SPI_START + 212) -/*SPI 212 to 216 arent used in 8x60*/ #define SMPSS_SPARE_1 (GIC_SPI_START + 217) #define SMPSS_SPARE_2 (GIC_SPI_START + 218) #define SMPSS_SPARE_3 (GIC_SPI_START + 219) @@ -251,8 +249,21 @@ #define SMPSS_SPARE_6 (GIC_SPI_START + 222) #define SMPSS_SPARE_7 (GIC_SPI_START + 223) +#define NR_TLMM_MSM_DIR_CONN_IRQ 10 #define NR_GPIO_IRQS 173 +#define NR_MSM_GPIOS NR_GPIO_IRQS #define NR_MSM_IRQS 256 -#define NR_BOARD_IRQS 0 +#define NR_PMIC8058_IRQS 256 +#define NR_PMIC8901_IRQS 72 +#define NR_GPIO_EXPANDER_IRQS 98 +#define NR_BOARD_IRQS (NR_PMIC8058_IRQS + NR_PMIC8901_IRQS +\ + NR_GPIO_EXPANDER_IRQS) + +/* smd/smsm interrupts */ +#define INT_A9_M2A_0 MARM_SCSS_GP_IRQ_0 +#define INT_A9_M2A_5 MARM_SCSS_GP_IRQ_1 +#define INT_ADSP_A11 LPASS_SCSS_GP_HIGH_IRQ +#define INT_ADSP_A11_SMSM LPASS_SCSS_GP_MEDIUM_IRQ +#define INT_DSPS_A11 SPS_MTI_31 #endif diff --git a/arch/arm/mach-msm/include/mach/irqs-fsm9xxx.h b/arch/arm/mach-msm/include/mach/irqs-fsm9xxx.h new file mode 100644 index 00000000000..a0ec244f3c7 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/irqs-fsm9xxx.h @@ -0,0 +1,98 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ASM_ARCH_MSM_IRQS_FSM9XXX_H +#define __ASM_ARCH_MSM_IRQS_FSM9XXX_H + +/* MSM ACPU Interrupt Numbers */ + +#define INT_DEBUG_TIMER_EXP 0 +#define INT_GPT0_TIMER_EXP 1 +#define INT_GPT1_TIMER_EXP 2 +#define INT_WDT0_ACCSCSSBARK 3 +#define INT_WDT1_ACCSCSSBARK 4 +#define INT_AVS_SVIC 5 +#define INT_AVS_SVIC_SW_DONE 6 +#define INT_SC_DBG_RX_FULL 7 +#define INT_SC_DBG_TX_EMPTY 8 +#define INT_ARMQC_PERFMON 9 +#define INT_AVS_REQ_DOWN 10 +#define INT_AVS_REQ_UP 11 +#define INT_SC_ACG 12 +/* SCSS_VICFIQSTS0[13:15] are RESERVED */ +#define INT_BPU_CPU 16 +#define INT_L2_SVICDMANSIRPTREQ 17 +#define INT_L2_SVICDMASIRPTREQ 18 +#define INT_L2_SVICSLVIRPTREQ 19 +#define INT_SEAWOLF_IRQ0 20 +#define INT_SEAWOLF_IRQ1 21 +#define INT_SEAWOLF_IRQ2 22 +#define INT_SEAWOLF_IRQ3 23 +#define INT_CARIBE_SUPSS_IRQ 24 +#define INT_ADM_SEC0_IRQ 25 +/* SCSS_VICFIQSTS0[26] is RESERVED */ +#define INT_GMII_PHY 27 +#define INT_SBD_IRQ 28 +#define INT_HH_SUPSS_IRQ 29 +#define INT_EMAC_SBD_IRQ 30 +#define INT_PERPH_SUPSS_IRQ 31 + +#define INT_Q6_SW_IRQ_0 (32 + 0) +#define INT_Q6_SW_IRQ_1 (32 + 1) +#define INT_Q6_SW_IRQ_2 (32 + 2) +#define INT_Q6_SW_IRQ_3 (32 + 3) +#define INT_Q6_SW_IRQ_4 (32 + 4) +#define INT_Q6_SW_IRQ_5 (32 + 5) +#define INT_Q6_SW_IRQ_6 (32 + 6) +#define INT_Q6_SW_IRQ_7 (32 + 7) +#define INT_IMEM_IRQ (32 + 8) +#define INT_IMEM_ECC_IRQ (32 + 9) +#define INT_HSDDRX_IRQ (32 + 10) +#define INT_BUFMEM_XPU_IRQ (32 + 11) +#define INT_A9_M2A_0 (32 + 12) +#define INT_A9_M2A_1 (32 + 13) +#define INT_A9_M2A_2 (32 + 14) +#define INT_A9_M2A_3 (32 + 15) +#define INT_A9_M2A_4 (32 + 16) +#define INT_A9_M2A_5 (32 + 17) +#define INT_A9_M2A_6 (32 + 18) +#define INT_A9_M2A_7 (32 + 19) +#define INT_SC_PRI_IRQ (32 + 20) +#define INT_SC_SEC_IRQ (32 + 21) +#define INT_Q6_WDOG_IRQ (32 + 22) +#define INT_ADM_SEC3_IRQ (32 + 23) +#define INT_ARM_WAKE_IRQ (32 + 24) +#define INT_ARM_WDOG_IRQ (32 + 25) +#define INT_SUPSS_CFG_XPU_IRQ (32 + 26) +#define INT_SPB_XPU_IRQ (32 + 27) +#define INT_FPB_XPU_IRQ (32 + 28) +#define INT_Q6_XPU_IRQ (32 + 29) +/* SCSS_VICFIQSTS1[30:31] are RESERVED */ +/* SCSS_VICFIQSTS2[0:31] are RESERVED */ +/* SCSS_VICFIQSTS3[0:31] are RESERVED */ + +/* Retrofit universal macro names */ +#define INT_ADM_AARM INT_ADM_SEC3_IRQ +#define INT_GP_TIMER_EXP INT_GPT0_TIMER_EXP +#define INT_ADSP_A11 INT_Q6_SW_IRQ_0 +#define INT_ADSP_A11_SMSM INT_ADSP_A11 +#define INT_SIRC_0 INT_PERPH_SUPSS_IRQ + +#define NR_MSM_IRQS 128 +#define NR_GPIO_IRQS 0 +#define PMIC8058_IRQ_BASE (NR_MSM_IRQS + NR_GPIO_IRQS + NR_SIRC_IRQS) +#define NR_PMIC8058_IRQS 256 +#define NR_BOARD_IRQS (NR_SIRC_IRQS + NR_PMIC8058_IRQS) + +#define NR_MSM_GPIOS 168 + +#endif /* __ASM_ARCH_MSM_IRQS_FSM9XXX_H */ diff --git a/arch/arm/mach-msm/include/mach/irqs.h b/arch/arm/mach-msm/include/mach/irqs.h index 3cd78b165ab..9a2f6a2fb0b 100644 --- a/arch/arm/mach-msm/include/mach/irqs.h +++ b/arch/arm/mach-msm/include/mach/irqs.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -19,6 +19,18 @@ #define MSM_IRQ_BIT(irq) (1 << ((irq) & 31)) +#if defined(CONFIG_ARCH_MSM8960) || defined(CONFIG_ARCH_APQ8064) + +#ifdef CONFIG_ARCH_MSM8960 +#include "irqs-8960.h" +#endif + +#ifdef CONFIG_ARCH_APQ8064 +#include "irqs-8064.h" +#endif + +#else + #if defined(CONFIG_ARCH_MSM7X30) #include "irqs-7x30.h" #elif defined(CONFIG_ARCH_QSD8X50) @@ -26,17 +38,20 @@ #include "sirc.h" #elif defined(CONFIG_ARCH_MSM8X60) #include "irqs-8x60.h" -#elif defined(CONFIG_ARCH_MSM8960) -/* TODO: Make these not generic. */ -#include "irqs-8960.h" -#elif defined(CONFIG_ARCH_MSM_ARM11) -#include "irqs-7x00.h" +#elif defined(CONFIG_ARCH_MSM_ARM11) || defined(CONFIG_ARCH_MSM_CORTEX_A5) +#include "irqs-7xxx.h" +#elif defined(CONFIG_ARCH_FSM9XXX) +#include "irqs-fsm9xxx.h" +#include "sirc.h" #else #error "Unknown architecture specification" #endif +#endif + #define NR_IRQS (NR_MSM_IRQS + NR_GPIO_IRQS + NR_BOARD_IRQS) #define MSM_GPIO_TO_INT(n) (NR_MSM_IRQS + (n)) +#define FIRST_GPIO_IRQ MSM_GPIO_TO_INT(0) #define MSM_INT_TO_REG(base, irq) (base + irq / 32) #endif diff --git a/arch/arm/mach-msm/include/mach/mdm.h b/arch/arm/mach-msm/include/mach/mdm.h new file mode 100644 index 00000000000..26af2236d01 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/mdm.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _ARCH_ARM_MACH_MSM_MDM_H +#define _ARXH_ARM_MACH_MSM_MDM_H + + +struct charm_platform_data { + void (*charm_modem_on)(void); + void (*charm_modem_off)(void); +}; + +#define AP2MDM_STATUS 136 +#define MDM2AP_STATUS 134 +#define MDM2AP_WAKEUP 40 +#define MDM2AP_ERRFATAL 133 +#define AP2MDM_ERRFATAL 93 + +#define AP2MDM_PMIC_RESET_N 131 +#define AP2MDM_KPDPWR_N 132 +#define AP2PMIC_TMPNI_CKEN 141 + +extern void (*charm_intentional_reset)(void); + +#endif diff --git a/arch/arm/mach-msm/include/mach/memory.h b/arch/arm/mach-msm/include/mach/memory.h index f2f8d299ba9..d5858fb907a 100644 --- a/arch/arm/mach-msm/include/mach/memory.h +++ b/arch/arm/mach-msm/include/mach/memory.h @@ -1,6 +1,7 @@ /* arch/arm/mach-msm/include/mach/memory.h * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 @@ -12,24 +13,85 @@ * GNU General Public License for more details. * */ - #ifndef __ASM_ARCH_MEMORY_H #define __ASM_ARCH_MEMORY_H /* physical offset of RAM */ -#if defined(CONFIG_ARCH_QSD8X50) && defined(CONFIG_MSM_SOC_REV_A) -#define PLAT_PHYS_OFFSET UL(0x00000000) -#elif defined(CONFIG_ARCH_QSD8X50) -#define PLAT_PHYS_OFFSET UL(0x20000000) -#elif defined(CONFIG_ARCH_MSM7X30) -#define PLAT_PHYS_OFFSET UL(0x00200000) -#elif defined(CONFIG_ARCH_MSM8X60) -#define PLAT_PHYS_OFFSET UL(0x40200000) -#elif defined(CONFIG_ARCH_MSM8960) -#define PLAT_PHYS_OFFSET UL(0x40200000) -#else -#define PLAT_PHYS_OFFSET UL(0x10000000) +#define PLAT_PHYS_OFFSET UL(CONFIG_PHYS_OFFSET) + +#define MAX_PHYSMEM_BITS 32 +#define SECTION_SIZE_BITS 28 + +/* Certain configurations of MSM7x30 have multiple memory banks. +* One or more of these banks can contain holes in the memory map as well. +* These macros define appropriate conversion routines between the physical +* and virtual address domains for supporting these configurations using +* SPARSEMEM and a 3G/1G VM split. +*/ + +#if defined(CONFIG_ARCH_MSM7X30) + +#define EBI0_PHYS_OFFSET PHYS_OFFSET +#define EBI0_PAGE_OFFSET PAGE_OFFSET +#define EBI0_SIZE 0x10000000 + +#define EBI1_PHYS_OFFSET 0x40000000 +#define EBI1_PAGE_OFFSET (EBI0_PAGE_OFFSET + EBI0_SIZE) + +#if (defined(CONFIG_SPARSEMEM) && defined(CONFIG_VMSPLIT_3G)) + +#define __phys_to_virt(phys) \ + ((phys) >= EBI1_PHYS_OFFSET ? \ + (phys) - EBI1_PHYS_OFFSET + EBI1_PAGE_OFFSET : \ + (phys) - EBI0_PHYS_OFFSET + EBI0_PAGE_OFFSET) + +#define __virt_to_phys(virt) \ + ((virt) >= EBI1_PAGE_OFFSET ? \ + (virt) - EBI1_PAGE_OFFSET + EBI1_PHYS_OFFSET : \ + (virt) - EBI0_PAGE_OFFSET + EBI0_PHYS_OFFSET) + #endif #endif +#define HAS_ARCH_IO_REMAP_PFN_RANGE + +#ifndef __ASSEMBLY__ +void *alloc_bootmem_aligned(unsigned long size, unsigned long alignment); +void *allocate_contiguous_ebi(unsigned long, unsigned long, int); +unsigned long allocate_contiguous_ebi_nomap(unsigned long, unsigned long); +void clean_and_invalidate_caches(unsigned long, unsigned long, unsigned long); +void clean_caches(unsigned long, unsigned long, unsigned long); +void invalidate_caches(unsigned long, unsigned long, unsigned long); +int platform_physical_remove_pages(unsigned long, unsigned long); +int platform_physical_active_pages(unsigned long, unsigned long); +int platform_physical_low_power_pages(unsigned long, unsigned long); + +#if defined(CONFIG_ARCH_MSM_ARM11) || defined(CONFIG_ARCH_MSM_CORTEX_A5) +void write_to_strongly_ordered_memory(void); +void map_page_strongly_ordered(void); +#endif + +#ifdef CONFIG_CACHE_L2X0 +extern void l2x0_cache_sync(void); +#define finish_arch_switch(prev) do { l2x0_cache_sync(); } while (0) +#endif + +#endif + +#if defined CONFIG_ARCH_MSM_SCORPION || defined CONFIG_ARCH_MSM_KRAIT +#define arch_has_speculative_dfetch() 1 +#endif + +#endif + +/* these correspond to values known by the modem */ +#define MEMORY_DEEP_POWERDOWN 0 +#define MEMORY_SELF_REFRESH 1 +#define MEMORY_ACTIVE 2 + +#define NPA_MEMORY_NODE_NAME "/mem/apps/ddr_dpd" + +#ifndef CONFIG_ARCH_MSM7X27 +#define CONSISTENT_DMA_SIZE (SZ_1M * 14) +#endif diff --git a/arch/arm/mach-msm/include/mach/mpp.h b/arch/arm/mach-msm/include/mach/mpp.h new file mode 100644 index 00000000000..8ac1f543875 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/mpp.h @@ -0,0 +1,276 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_MPP_H +#define __ARCH_ARM_MACH_MSM_MPP_H + +#ifdef CONFIG_PMIC8058 +#define MPPS 12 +#else +#define MPPS 22 +#endif + +/* Digital Logical Output Level */ +enum { + MPP_DLOGIC_LVL_MSME, + MPP_DLOGIC_LVL_MSMP, + MPP_DLOGIC_LVL_RUIM, + MPP_DLOGIC_LVL_MMC, + MPP_DLOGIC_LVL_VDD, +}; + +/* Digital Logical Output Control Value */ +enum { + MPP_DLOGIC_OUT_CTRL_LOW, + MPP_DLOGIC_OUT_CTRL_HIGH, + MPP_DLOGIC_OUT_CTRL_MPP, /* MPP Output = MPP Input */ + MPP_DLOGIC_OUT_CTRL_NOT_MPP, /* MPP Output = Inverted MPP Input */ +}; + +/* Digital Logical Input Value */ +enum { + MPP_DLOGIC_IN_DBUS_NONE, + MPP_DLOGIC_IN_DBUS_1, + MPP_DLOGIC_IN_DBUS_2, + MPP_DLOGIC_IN_DBUS_3, +}; + +#define MPP_CFG(level, control) ((((level) & 0x0FFFF) << 16) | \ + ((control) & 0x0FFFF)) +#define MPP_CFG_INPUT(level, dbus) ((((level) & 0x0FFFF) << 16) | \ + ((dbus) & 0x0FFFF)) + +/* Use mpp number starting from 0 */ +int mpp_config_digital_out(unsigned mpp, unsigned config); +int mpp_config_digital_in(unsigned mpp, unsigned config); + +/* PM8058/PM8901 definitions */ + +/* APIs */ +#ifdef CONFIG_PMIC8058 +int pm8058_mpp_config(unsigned mpp, unsigned type, unsigned level, + unsigned control); +#else +static inline int pm8058_mpp_config(unsigned mpp, unsigned type, + unsigned level, unsigned control) +{ + return -EINVAL; +} +#endif + +#ifdef CONFIG_PMIC8901 +int pm8901_mpp_config(unsigned mpp, unsigned type, unsigned level, + unsigned control); +#else +static inline int pm8901_mpp_config(unsigned mpp, unsigned type, + unsigned level, unsigned control) +{ + return -EINVAL; +} +#endif + +/* MPP Type: type */ +#define PM_MPP_TYPE_D_INPUT 0 +#define PM_MPP_TYPE_D_OUTPUT 1 +#define PM_MPP_TYPE_D_BI_DIR 2 +#define PM_MPP_TYPE_A_INPUT 3 +#define PM_MPP_TYPE_A_OUTPUT 4 +#define PM_MPP_TYPE_SINK 5 +#define PM_MPP_TYPE_DTEST_SINK 6 +#define PM_MPP_TYPE_DTEST_OUTPUT 7 + + +/* Digital Input/Output: level [8058] */ +#define PM8058_MPP_DIG_LEVEL_VPH 0 +#define PM8058_MPP_DIG_LEVEL_S3 1 +#define PM8058_MPP_DIG_LEVEL_L2 2 +#define PM8058_MPP_DIG_LEVEL_L3 3 + +/* Digital Input/Output: level [8901] */ +#define PM8901_MPP_DIG_LEVEL_MSMIO 0 +#define PM8901_MPP_DIG_LEVEL_DIG 1 +#define PM8901_MPP_DIG_LEVEL_L5 2 +#define PM8901_MPP_DIG_LEVEL_S4 3 +#define PM8901_MPP_DIG_LEVEL_VPH 4 + +/* Digital Input: control */ +#define PM_MPP_DIN_TO_INT 0 +#define PM_MPP_DIN_TO_DBUS1 1 +#define PM_MPP_DIN_TO_DBUS2 2 +#define PM_MPP_DIN_TO_DBUS3 3 + +/* Digital Output: control */ +#define PM_MPP_DOUT_CTL_LOW 0 +#define PM_MPP_DOUT_CTL_HIGH 1 +#define PM_MPP_DOUT_CTL_MPP 2 +#define PM_MPP_DOUT_CTL_INV_MPP 3 + +/* Bidirectional: control */ +#define PM_MPP_BI_PULLUP_1KOHM 0 +#define PM_MPP_BI_PULLUP_OPEN 1 +#define PM_MPP_BI_PULLUP_10KOHM 2 +#define PM_MPP_BI_PULLUP_30KOHM 3 + +/* Analog Input: level */ +#define PM_MPP_AIN_AMUX_CH5 0 +#define PM_MPP_AIN_AMUX_CH6 1 +#define PM_MPP_AIN_AMUX_CH7 2 +#define PM_MPP_AIN_AMUX_CH8 3 +#define PM_MPP_AIN_AMUX_CH9 4 +#define PM_MPP_AIN_AMUX_ABUS1 5 +#define PM_MPP_AIN_AMUX_ABUS2 6 +#define PM_MPP_AIN_AMUX_ABUS3 7 + +/* Analog Output: level */ +#define PM_MPP_AOUT_LVL_1V25 0 +#define PM_MPP_AOUT_LVL_1V25_2 1 +#define PM_MPP_AOUT_LVL_0V625 2 +#define PM_MPP_AOUT_LVL_0V3125 3 +#define PM_MPP_AOUT_LVL_MPP 4 +#define PM_MPP_AOUT_LVL_ABUS1 5 +#define PM_MPP_AOUT_LVL_ABUS2 6 +#define PM_MPP_AOUT_LVL_ABUS3 7 + +/* Analog Output: control */ +#define PM_MPP_AOUT_CTL_DISABLE 0 +#define PM_MPP_AOUT_CTL_ENABLE 1 +#define PM_MPP_AOUT_CTL_MPP_HIGH_EN 2 +#define PM_MPP_AOUT_CTL_MPP_LOW_EN 3 + +/* Current Sink: level */ +#define PM_MPP_CS_OUT_5MA 0 +#define PM_MPP_CS_OUT_10MA 1 +#define PM_MPP_CS_OUT_15MA 2 +#define PM_MPP_CS_OUT_20MA 3 +#define PM_MPP_CS_OUT_25MA 4 +#define PM_MPP_CS_OUT_30MA 5 +#define PM_MPP_CS_OUT_35MA 6 +#define PM_MPP_CS_OUT_40MA 7 + +/* Current Sink: control */ +#define PM_MPP_CS_CTL_DISABLE 0 +#define PM_MPP_CS_CTL_ENABLE 1 +#define PM_MPP_CS_CTL_MPP_HIGH_EN 2 +#define PM_MPP_CS_CTL_MPP_LOW_EN 3 + +/* DTEST Current Sink: control */ +#define PM_MPP_DTEST_CS_CTL_EN1 0 +#define PM_MPP_DTEST_CS_CTL_EN2 1 +#define PM_MPP_DTEST_CS_CTL_EN3 2 +#define PM_MPP_DTEST_CS_CTL_EN4 3 + +/* DTEST Digital Output: control */ +#define PM_MPP_DTEST_DBUS1 0 +#define PM_MPP_DTEST_DBUS2 1 +#define PM_MPP_DTEST_DBUS3 2 +#define PM_MPP_DTEST_DBUS4 3 + +/* Helper APIs */ +static inline int pm8058_mpp_config_digital_in(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8058_mpp_config(mpp, PM_MPP_TYPE_D_INPUT, level, control); +} + +static inline int pm8058_mpp_config_digital_out(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8058_mpp_config(mpp, PM_MPP_TYPE_D_OUTPUT, level, control); +} + +static inline int pm8058_mpp_config_bi_dir(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8058_mpp_config(mpp, PM_MPP_TYPE_D_BI_DIR, level, control); +} + +static inline int pm8058_mpp_config_analog_input(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8058_mpp_config(mpp, PM_MPP_TYPE_A_INPUT, level, control); +} + +static inline int pm8058_mpp_config_analog_output(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8058_mpp_config(mpp, PM_MPP_TYPE_A_OUTPUT, level, control); +} + +static inline int pm8058_mpp_config_current_sink(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8058_mpp_config(mpp, PM_MPP_TYPE_SINK, level, control); +} + +static inline int pm8058_mpp_config_dtest_sink(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8058_mpp_config(mpp, PM_MPP_TYPE_DTEST_SINK, level, control); +} + +static inline int pm8058_mpp_config_dtest_output(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8058_mpp_config(mpp, PM_MPP_TYPE_DTEST_OUTPUT, + level, control); +} + +static inline int pm8901_mpp_config_digital_in(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8901_mpp_config(mpp, PM_MPP_TYPE_D_INPUT, level, control); +} + +static inline int pm8901_mpp_config_digital_out(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8901_mpp_config(mpp, PM_MPP_TYPE_D_OUTPUT, level, control); +} + +static inline int pm8901_mpp_config_bi_dir(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8901_mpp_config(mpp, PM_MPP_TYPE_D_BI_DIR, level, control); +} + +static inline int pm8901_mpp_config_analog_input(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8901_mpp_config(mpp, PM_MPP_TYPE_A_INPUT, level, control); +} + +static inline int pm8901_mpp_config_analog_output(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8901_mpp_config(mpp, PM_MPP_TYPE_A_OUTPUT, level, control); +} + +static inline int pm8901_mpp_config_current_sink(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8901_mpp_config(mpp, PM_MPP_TYPE_SINK, level, control); +} + +static inline int pm8901_mpp_config_dtest_sink(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8901_mpp_config(mpp, PM_MPP_TYPE_DTEST_SINK, level, control); +} + +static inline int pm8901_mpp_config_dtest_output(unsigned mpp, unsigned level, + unsigned control) +{ + return pm8901_mpp_config(mpp, PM_MPP_TYPE_DTEST_OUTPUT, + level, control); +} +#endif diff --git a/arch/arm/mach-msm/include/mach/msm-krait-l2-accessors.h b/arch/arm/mach-msm/include/mach/msm-krait-l2-accessors.h new file mode 100644 index 00000000000..507d717ea03 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm-krait-l2-accessors.h @@ -0,0 +1,36 @@ +#ifndef __ASM_ARCH_MSM_MSM_KRAIT_L2_ACCESSORS_H +#define __ASM_ARCH_MSM_MSM_KRAIT_L2_ACCESSORS_H + +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifdef CONFIG_ARCH_MSM_KRAIT +extern void set_l2_indirect_reg(u32 reg_addr, u32 val); +extern u32 get_l2_indirect_reg(u32 reg_addr); +extern u32 set_get_l2_indirect_reg(u32 reg_addr, u32 val); +#else + +void set_l2_indirect_reg(u32 reg_addr, u32 val) +{ +} + +u32 set_get_l2_indirect_reg(u32 reg_addr, u32 val) +{ + return 0; +} + +u32 get_l2_indirect_reg(u32 reg_addr) +{ + return 0; +} +#endif +#endif diff --git a/arch/arm/mach-msm/include/mach/msm72k_otg.h b/arch/arm/mach-msm/include/mach/msm72k_otg.h new file mode 100644 index 00000000000..9a61901a186 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm72k_otg.h @@ -0,0 +1,187 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LINUX_USB_GADGET_MSM72K_OTG_H__ +#define __LINUX_USB_GADGET_MSM72K_OTG_H__ + +#include +#include +#include +#include +#include + +#include +#include + +#define OTGSC_BSVIE (1 << 27) +#define OTGSC_IDIE (1 << 24) +#define OTGSC_BSVIS (1 << 19) +#define OTGSC_ID (1 << 8) +#define OTGSC_IDIS (1 << 16) +#define OTGSC_BSV (1 << 11) +#define OTGSC_DPIE (1 << 30) +#define OTGSC_DPIS (1 << 22) +#define OTGSC_HADP (1 << 6) + +#define ULPI_STP_CTRL (1 << 30) +#define ASYNC_INTR_CTRL (1 << 29) +#define ULPI_SYNC_STATE (1 << 27) + +#define PORTSC_PHCD (1 << 23) +#define PORTSC_CSC (1 << 1) +#define disable_phy_clk() (writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC)) +#define enable_phy_clk() (writel(readl(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC)) +#define is_phy_clk_disabled() (readl(USB_PORTSC) & PORTSC_PHCD) +#define is_phy_active() (readl_relaxed(USB_ULPI_VIEWPORT) &\ + ULPI_SYNC_STATE) +#define is_usb_active() (!(readl(USB_PORTSC) & PORTSC_SUSP)) + +/* Timeout (in msec) values (min - max) associated with OTG timers */ + +#define TA_WAIT_VRISE 100 /* ( - 100) */ +#define TA_WAIT_VFALL 500 /* ( - 1000) */ + +/* + * This option is set for embedded hosts or OTG devices in which leakage + * currents are very minimal. + */ +#ifdef CONFIG_MSM_OTG_ENABLE_A_WAIT_BCON_TIMEOUT +#define TA_WAIT_BCON 30000 /* (1100 - 30000) */ +#else +#define TA_WAIT_BCON -1 +#endif + +/* AIDL_BDIS should be 500 */ +#define TA_AIDL_BDIS 200 /* (200 - ) */ +#define TA_BIDL_ADIS 155 /* (155 - 200) */ +#define TB_SRP_FAIL 6000 /* (5000 - 6000) */ +#define TB_ASE0_BRST 155 /* (155 - ) */ + +/* TB_SSEND_SRP and TB_SE0_SRP are combined */ +#define TB_SRP_INIT 2000 /* (1500 - ) */ + +/* Timeout variables */ + +#define A_WAIT_VRISE 0 +#define A_WAIT_VFALL 1 +#define A_WAIT_BCON 2 +#define A_AIDL_BDIS 3 +#define A_BIDL_ADIS 4 +#define B_SRP_FAIL 5 +#define B_ASE0_BRST 6 + +/* Internal flags like a_set_b_hnp_en, b_hnp_en are maintained + * in usb_bus and usb_gadget + */ + +#define A_BUS_DROP 0 +#define A_BUS_REQ 1 +#define A_SRP_DET 2 +#define A_VBUS_VLD 3 +#define B_CONN 4 +#define ID 5 +#define ADP_CHANGE 6 +#define POWER_UP 7 +#define A_CLR_ERR 8 +#define A_BUS_RESUME 9 +#define A_BUS_SUSPEND 10 +#define A_CONN 11 +#define B_BUS_REQ 12 +#define B_SESS_VLD 13 +#define ID_A 14 +#define ID_B 15 +#define ID_C 16 + +#define USB_IDCHG_MIN 500 +#define USB_IDCHG_MAX 1500 +#define USB_IB_UNCFG 2 +#define OTG_ID_POLL_MS 1000 + +struct msm_otg { + struct otg_transceiver otg; + + /* usb clocks */ + struct clk *hs_clk; + struct clk *hs_pclk; + struct clk *hs_cclk; + + /* pclk source for voting */ + struct clk *pclk_src; + + /* clk regime has created dummy clock id for phy so + * that generic clk_reset api can be used to reset phy + */ + struct clk *phy_reset_clk; + + int irq; + int vbus_on_irq; + void __iomem *regs; + atomic_t in_lpm; + /* charger-type is modified by gadget for legacy chargers + * and OTG modifies it for ACA + */ + atomic_t chg_type; + + void (*start_host) (struct usb_bus *bus, int suspend); + /* Enable/disable the clocks */ + int (*set_clk) (struct otg_transceiver *otg, int on); + /* Reset phy and link */ + void (*reset) (struct otg_transceiver *otg, int phy_reset); + /* pmic notfications apis */ + u8 pmic_vbus_notif_supp; + u8 pmic_id_notif_supp; + struct msm_otg_platform_data *pdata; + + spinlock_t lock; /* protects OTG state */ + struct wake_lock wlock; + unsigned long b_last_se0_sess; /* SRP initial condition check */ + unsigned long inputs; + int pmic_id_status; + unsigned long tmouts; + u8 active_tmout; + struct hrtimer timer; + struct workqueue_struct *wq; + struct work_struct sm_work; /* state machine work */ + struct work_struct otg_resume_work; + struct notifier_block usbdev_nb; + struct msm_xo_voter *xo_handle; /*handle to vote for TCXO D1 buffer*/ +#ifdef CONFIG_USB_MSM_ACA + struct timer_list id_timer; /* drives id_status polling */ + unsigned b_max_power; /* ACA: max power of accessory*/ +#endif +}; + +static inline int pclk_requires_voting(struct otg_transceiver *xceiv) +{ + struct msm_otg *dev; + + if (!xceiv) + return 0; + + dev = container_of(xceiv, struct msm_otg, otg); + + if (dev->pdata->pclk_src_name) + return 1; + else + return 0; +} + +static inline int can_phy_power_collapse(struct msm_otg *dev) +{ + if (!dev || !dev->pdata) + return -ENODEV; + + return dev->pdata->phy_can_powercollapse; +} + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_adsp.h b/arch/arm/mach-msm/include/mach/msm_adsp.h new file mode 100644 index 00000000000..bbae6c17a46 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_adsp.h @@ -0,0 +1,103 @@ +/* include/asm-arm/arch-msm/msm_adsp.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM__ARCH_MSM_ADSP_H +#define __ASM__ARCH_MSM_ADSP_H + +struct msm_adsp_module; + +struct msm_adsp_ops { + /* event is called from interrupt context when a message + * arrives from the DSP. Use the provided function pointer + * to copy the message into a local buffer. Do NOT call + * it multiple times. + */ + void (*event)(void *driver_data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)); +}; + +/* Get, Put, Enable, and Disable are synchronous and must only + * be called from thread context. Enable and Disable will block + * up to one second in the event of a fatal DSP error but are + * much faster otherwise. + */ +int msm_adsp_get(const char *name, struct msm_adsp_module **module, + struct msm_adsp_ops *ops, void *driver_data); +void msm_adsp_put(struct msm_adsp_module *module); +int msm_adsp_enable(struct msm_adsp_module *module); +int msm_adsp_disable(struct msm_adsp_module *module); +int adsp_set_clkrate(struct msm_adsp_module *module, unsigned long clk_rate); +int msm_adsp_disable_event_rsp(struct msm_adsp_module *module); +int32_t get_adsp_resource(unsigned short client_idx, + void *cmd_buf, size_t cmd_size); +int32_t put_adsp_resource(unsigned short client_idx, + void *cmd_buf, size_t cmd_size); + +/* Write is safe to call from interrupt context. + */ +int msm_adsp_write(struct msm_adsp_module *module, + unsigned queue_id, + void *data, size_t len); + +#define ADSP_MESSAGE_ID 0xFFFF + +/* Command Queue Indexes */ +#define QDSP_lpmCommandQueue 0 +#define QDSP_mpuAfeQueue 1 +#define QDSP_mpuGraphicsCmdQueue 2 +#define QDSP_mpuModmathCmdQueue 3 +#define QDSP_mpuVDecCmdQueue 4 +#define QDSP_mpuVDecPktQueue 5 +#define QDSP_mpuVEncCmdQueue 6 +#define QDSP_rxMpuDecCmdQueue 7 +#define QDSP_rxMpuDecPktQueue 8 +#define QDSP_txMpuEncQueue 9 +#define QDSP_uPAudPPCmd1Queue 10 +#define QDSP_uPAudPPCmd2Queue 11 +#define QDSP_uPAudPPCmd3Queue 12 +#define QDSP_uPAudPlay0BitStreamCtrlQueue 13 +#define QDSP_uPAudPlay1BitStreamCtrlQueue 14 +#define QDSP_uPAudPlay2BitStreamCtrlQueue 15 +#define QDSP_uPAudPlay3BitStreamCtrlQueue 16 +#define QDSP_uPAudPlay4BitStreamCtrlQueue 17 +#define QDSP_uPAudPreProcCmdQueue 18 +#define QDSP_uPAudRecBitStreamQueue 19 +#define QDSP_uPAudRecCmdQueue 20 +#define QDSP_uPDiagQueue 21 +#define QDSP_uPJpegActionCmdQueue 22 +#define QDSP_uPJpegCfgCmdQueue 23 +#define QDSP_uPVocProcQueue 24 +#define QDSP_vfeCommandQueue 25 +#define QDSP_vfeCommandScaleQueue 26 +#define QDSP_vfeCommandTableQueue 27 +#define QDSP_vfeFtmCmdQueue 28 +#define QDSP_vfeFtmCmdScaleQueue 29 +#define QDSP_vfeFtmCmdTableQueue 30 +#define QDSP_uPJpegFtmCfgCmdQueue 31 +#define QDSP_uPJpegFtmActionCmdQueue 32 +#define QDSP_apuAfeQueue 33 +#define QDSP_mpuRmtQueue 34 +#define QDSP_uPAudPreProcAudRecCmdQueue 35 +#define QDSP_uPAudRec0BitStreamQueue 36 +#define QDSP_uPAudRec0CmdQueue 37 +#define QDSP_uPAudRec1BitStreamQueue 38 +#define QDSP_uPAudRec1CmdQueue 39 +#define QDSP_apuRmtQueue 40 +#define QDSP_uPAudRec2BitStreamQueue 41 +#define QDSP_uPAudRec2CmdQueue 42 +#define QDSP_MAX_NUM_QUEUES 43 + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_audio_aac.h b/arch/arm/mach-msm/include/mach/msm_audio_aac.h new file mode 100644 index 00000000000..8c4d91bc46c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_audio_aac.h @@ -0,0 +1,71 @@ +/* arch/arm/mach-msm/include/mach/msm_audio_aac.h + * + * Copyright (c) 2009 Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MSM_AUDIO_AAC_H +#define __MSM_AUDIO_AAC_H + +#include + +#define AUDIO_SET_AAC_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+0), unsigned) +#define AUDIO_GET_AAC_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+1), unsigned) + +#define AUDIO_AAC_FORMAT_ADTS -1 +#define AUDIO_AAC_FORMAT_RAW 0x0000 +#define AUDIO_AAC_FORMAT_PSUEDO_RAW 0x0001 +#define AUDIO_AAC_FORMAT_LOAS 0x0002 + +#define AUDIO_AAC_OBJECT_LC 0x0002 +#define AUDIO_AAC_OBJECT_LTP 0x0004 +#define AUDIO_AAC_OBJECT_ERLC 0x0011 + +#define AUDIO_AAC_SEC_DATA_RES_ON 0x0001 +#define AUDIO_AAC_SEC_DATA_RES_OFF 0x0000 + +#define AUDIO_AAC_SCA_DATA_RES_ON 0x0001 +#define AUDIO_AAC_SCA_DATA_RES_OFF 0x0000 + +#define AUDIO_AAC_SPEC_DATA_RES_ON 0x0001 +#define AUDIO_AAC_SPEC_DATA_RES_OFF 0x0000 + +#define AUDIO_AAC_SBR_ON_FLAG_ON 0x0001 +#define AUDIO_AAC_SBR_ON_FLAG_OFF 0x0000 + +#define AUDIO_AAC_SBR_PS_ON_FLAG_ON 0x0001 +#define AUDIO_AAC_SBR_PS_ON_FLAG_OFF 0x0000 + +/* Primary channel on both left and right channels */ +#define AUDIO_AAC_DUAL_MONO_PL_PR 0 +/* Secondary channel on both left and right channels */ +#define AUDIO_AAC_DUAL_MONO_SL_SR 1 +/* Primary channel on right channel and 2nd on left channel */ +#define AUDIO_AAC_DUAL_MONO_SL_PR 2 +/* 2nd channel on right channel and primary on left channel */ +#define AUDIO_AAC_DUAL_MONO_PL_SR 3 + +struct msm_audio_aac_config { + signed short format; + unsigned short audio_object; + unsigned short ep_config; /* 0 ~ 3 useful only obj = ERLC */ + unsigned short aac_section_data_resilience_flag; + unsigned short aac_scalefactor_data_resilience_flag; + unsigned short aac_spectral_data_resilience_flag; + unsigned short sbr_on_flag; + unsigned short sbr_ps_on_flag; + unsigned short dual_mono_mode; + unsigned short channel_configuration; +}; + +#endif /* __MSM_AUDIO_AAC_H */ diff --git a/arch/arm/mach-msm/include/mach/msm_battery.h b/arch/arm/mach-msm/include/mach/msm_battery.h new file mode 100644 index 00000000000..c54e7d8aac3 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_battery.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#ifndef __MSM_BATTERY_H__ +#define __MSM_BATTERY_H__ + +#define AC_CHG 0x00000001 +#define USB_CHG 0x00000002 + +struct msm_psy_batt_pdata { + u32 voltage_max_design; + u32 voltage_min_design; + u32 avail_chg_sources; + u32 batt_technology; + u32 (*calculate_capacity)(u32 voltage); +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_bus.h b/arch/arm/mach-msm/include/mach/msm_bus.h new file mode 100644 index 00000000000..6d7a5339f1c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_bus.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _ARCH_ARM_MACH_MSM_BUS_H +#define _ARCH_ARM_MACH_MSM_BUS_H + +#include +#include + +/* + * Macros for clients to convert their data to ib and ab + * Ws : Time window over which to transfer the data in SECONDS + * Bs : Size of the data block in bytes + * Per : Recurrence period + * Tb : Throughput bandwidth to prevent stalling + * R : Ratio of actual bandwidth used to Tb + * Ib : Instantaneous bandwidth + * Ab : Arbitrated bandwidth + * + * IB_RECURRBLOCK and AB_RECURRBLOCK: + * These are used if the requirement is to transfer a + * recurring block of data over a known time window. + * + * IB_THROUGHPUTBW and AB_THROUGHPUTBW: + * These are used for CPU style masters. Here the requirement + * is to have minimum throughput bandwidth available to avoid + * stalling. + */ +#define IB_RECURRBLOCK(Ws, Bs) ((Ws) == 0 ? 0 : ((Bs)/(Ws))) +#define AB_RECURRBLOCK(Ws, Per) ((Ws) == 0 ? 0 : ((Bs)/(Per))) +#define IB_THROUGHPUTBW(Tb) (Tb) +#define AB_THROUGHPUTBW(Tb, R) ((Tb) * (R)) + +struct msm_bus_vectors { + int src; /* Master */ + int dst; /* Slave */ + unsigned int ab; /* Arbitrated bandwidth */ + unsigned int ib; /* Instantaneous bandwidth */ +}; + +struct msm_bus_paths { + int num_paths; + struct msm_bus_vectors *vectors; +}; + +struct msm_bus_scale_pdata { + struct msm_bus_paths *usecase; + int num_usecases; + const char *name; + /* + * If the active_only flag is set to 1, the BW request is applied + * only when at least one CPU is active (powered on). If the flag + * is set to 0, then the BW request is always applied irrespective + * of the CPU state. + */ + unsigned int active_only; +}; + +/* Scaling APIs */ + +/* + * This function returns a handle to the client. This should be used to + * call msm_bus_scale_client_update_request. + * The function returns 0 if bus driver is unable to register a client + */ + +#ifdef CONFIG_MSM_BUS_SCALING +uint32_t msm_bus_scale_register_client(struct msm_bus_scale_pdata *pdata); +int msm_bus_scale_client_update_request(uint32_t cl, unsigned int index); +void msm_bus_scale_unregister_client(uint32_t cl); +/* AXI Port configuration APIs */ +int msm_bus_axi_porthalt(int master_port); +int msm_bus_axi_portunhalt(int master_port); + +#else +static inline uint32_t +msm_bus_scale_register_client(struct msm_bus_scale_pdata *pdata) +{ + return 1; +} + +static inline int +msm_bus_scale_client_update_request(uint32_t cl, unsigned int index) +{ + return 0; +} + +static inline void +msm_bus_scale_unregister_client(uint32_t cl) +{ +} + +static inline int msm_bus_axi_porthalt(int master_port) +{ + return 0; +} + +static inline int msm_bus_axi_portunhalt(int master_port) +{ + return 0; +} +#endif + +#endif /*_ARCH_ARM_MACH_MSM_BUS_H*/ diff --git a/arch/arm/mach-msm/include/mach/msm_bus_board.h b/arch/arm/mach-msm/include/mach/msm_bus_board.h new file mode 100644 index 00000000000..6629010c0c5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_bus_board.h @@ -0,0 +1,271 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ASM_ARCH_MSM_BUS_BOARD_H +#define __ASM_ARCH_MSM_BUS_BOARD_H + +#include +#include + +enum context { + DUAL_CTX, + ACTIVE_CTX, + NUM_CTX +}; + +struct msm_bus_fabric_registration { + unsigned int id; + char *name; + struct msm_bus_node_info *info; + unsigned int len; + int ahb; + const char *fabclk[NUM_CTX]; + unsigned int offset; + unsigned int haltid; + unsigned int rpm_enabled; + const unsigned int nmasters; + const unsigned int nslaves; + const unsigned int ntieredslaves; +}; + +enum msm_bus_bw_tier_type { + MSM_BUS_BW_TIER1 = 1, + MSM_BUS_BW_TIER2, + MSM_BUS_BW_COUNT, + MSM_BUS_BW_SIZE = 0x7FFFFFFF, +}; + +struct msm_bus_halt_vector { + uint32_t haltval; + uint32_t haltmask; +}; + +extern struct msm_bus_fabric_registration msm_bus_apps_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_sys_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_mm_fabric_pdata; +extern struct msm_bus_fabric_registration msm_bus_sys_fpb_pdata; +extern struct msm_bus_fabric_registration msm_bus_cpss_fpb_pdata; + +void msm_bus_board_assign_iids(struct msm_bus_fabric_registration + *fabreg, int fabid); +int msm_bus_board_get_iid(int id); + +/* + * These macros specify the convention followed for allocating + * ids to fabrics, masters and slaves for 8x60. + * + * A node can be identified as a master/slave/fabric by using + * these ids. + */ +#define FABRIC_ID_KEY 1024 +#define SLAVE_ID_KEY ((FABRIC_ID_KEY) >> 1) +#define NUM_FAB 5 +#define MAX_FAB_KEY 7168 /* OR(All fabric ids) */ + +#define GET_FABID(id) ((id) & MAX_FAB_KEY) + +#define NODE_ID(id) ((id) & (FABRIC_ID_KEY - 1)) +#define IS_SLAVE(id) ((NODE_ID(id)) >= SLAVE_ID_KEY ? 1 : 0) + +/* + * The following macros are used to format the data for port halt + * and unhalt requests. + */ +#define MSM_BUS_CLK_HALT 0x1 +#define MSM_BUS_CLK_HALT_MASK 0x1 +#define MSM_BUS_CLK_HALT_FIELDSIZE 0x1 +#define MSM_BUS_CLK_UNHALT 0x0 + +#define MSM_BUS_MASTER_SHIFT(master, fieldsize) \ + ((master) * (fieldsize)) + +#define MSM_BUS_SET_BITFIELD(word, fieldmask, fieldvalue) \ + { \ + (word) &= ~(fieldmask); \ + (word) |= (fieldvalue); \ + } + + +#define MSM_BUS_MASTER_HALT(u32haltmask, u32haltval, master) \ + MSM_BUS_SET_BITFIELD(u32haltmask, \ + MSM_BUS_CLK_HALT_MASK< +#include +#include + +#define DSPS_SIGNATURE 0x12345678 + +/** + * DSPS Clocks Platform data. + * + * @name - clock name. + * @rate - rate to set. zero if not relevant. + * @clock - clock handle, reserved for the driver. + */ +struct dsps_clk_info { + const char *name; + u32 rate; + struct clk *clock; +}; + +/** + * DSPS GPIOs Platform data. + * + * @name - clock name. + * @num - GPIO number. + * @on_val - value to ouptput for ON (depends on polarity). + * @off_val - value to ouptput for OFF (depends on polarity). + * @is_owner - reserved for the driver. + */ +struct dsps_gpio_info { + const char *name; + int num; + int on_val; + int off_val; + int is_owner; +}; + +/** + * DSPS Power regulators Platform data. + * + * @name - regulator name. + * @volt - required voltage (in uV). + * @reg - reserved for the driver. + */ +struct dsps_regulator_info { + const char *name; + int volt; + struct regulator *reg; +}; + +/** + * DSPS Platform data. + * + * @pil_name - peripheral image name + * @clks - array of clocks. + * @clks_num - number of clocks in array. + * @gpios - array of gpios. + * @gpios_num - number of gpios. + * @regs - array of regulators. + * @regs_num - number of regulators. + * @dsps_pwr_ctl_en - to enable DSPS to do power control if set 1 + * otherwise the apps will do power control + * @signature - signature for validity check. + */ +struct msm_dsps_platform_data { + const char *pil_name; + struct dsps_clk_info *clks; + int clks_num; + struct dsps_gpio_info *gpios; + int gpios_num; + struct dsps_regulator_info *regs; + int regs_num; + int dsps_pwr_ctl_en; + u32 signature; +}; + +#endif /* _MSM_DSPS_H_ */ diff --git a/arch/arm/mach-msm/include/mach/msm_fast_timer.h b/arch/arm/mach-msm/include/mach/msm_fast_timer.h new file mode 100644 index 00000000000..e1660c192a3 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_fast_timer.h @@ -0,0 +1,19 @@ +/* arch/arm/mach-msm/include/mach/msm_fast_timer.h + * + * Copyright (C) 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +void msm_enable_fast_timer(void); +void msm_disable_fast_timer(void); +u32 msm_read_fast_timer(void); + diff --git a/arch/arm/mach-msm/include/mach/msm_fb.h b/arch/arm/mach-msm/include/mach/msm_fb.h index 1f4fc81b3d8..339fa463a45 100644 --- a/arch/arm/mach-msm/include/mach/msm_fb.h +++ b/arch/arm/mach-msm/include/mach/msm_fb.h @@ -21,6 +21,10 @@ struct mddi_info; +/* output interface format */ +#define MSM_MDP_OUT_IF_FMT_RGB565 0 +#define MSM_MDP_OUT_IF_FMT_RGB666 1 + struct msm_fb_data { int xres; /* x resolution in pixels */ int yres; /* y resolution in pixels */ @@ -34,9 +38,12 @@ struct msmfb_callback { }; enum { - MSM_MDDI_PMDH_INTERFACE, + MSM_MDDI_PMDH_INTERFACE = 0, MSM_MDDI_EMDH_INTERFACE, MSM_EBI2_INTERFACE, + MSM_LCDC_INTERFACE, + + MSM_MDP_NUM_INTERFACES = MSM_LCDC_INTERFACE + 1, }; #define MSMFB_CAP_PARTIAL_UPDATES (1 << 0) @@ -85,6 +92,8 @@ struct msm_mddi_platform_data { /* fixup the mfr name, product id */ void (*fixup)(uint16_t *mfr_name, uint16_t *product_id); + int vsync_irq; + struct resource *fb_resource; /*optional*/ /* number of clients in the list that follows */ int num_clients; @@ -110,17 +119,50 @@ struct msm_mddi_platform_data { } client_platform_data[]; }; +struct msm_lcdc_timing { + unsigned int clk_rate; /* dclk freq */ + unsigned int hsync_pulse_width; /* in dclks */ + unsigned int hsync_back_porch; /* in dclks */ + unsigned int hsync_front_porch; /* in dclks */ + unsigned int hsync_skew; /* in dclks */ + unsigned int vsync_pulse_width; /* in lines */ + unsigned int vsync_back_porch; /* in lines */ + unsigned int vsync_front_porch; /* in lines */ + + /* control signal polarity */ + unsigned int vsync_act_low:1; + unsigned int hsync_act_low:1; + unsigned int den_act_low:1; +}; + +struct msm_lcdc_panel_ops { + int (*init)(struct msm_lcdc_panel_ops *); + int (*uninit)(struct msm_lcdc_panel_ops *); + int (*blank)(struct msm_lcdc_panel_ops *); + int (*unblank)(struct msm_lcdc_panel_ops *); +}; + +struct msm_lcdc_platform_data { + struct msm_lcdc_panel_ops *panel_ops; + struct msm_lcdc_timing *timing; + int fb_id; + struct msm_fb_data *fb_data; + struct resource *fb_resource; +}; + struct mdp_blit_req; struct fb_info; struct mdp_device { struct device dev; - void (*dma)(struct mdp_device *mpd, uint32_t addr, + void (*dma)(struct mdp_device *mdp, uint32_t addr, uint32_t stride, uint32_t w, uint32_t h, uint32_t x, uint32_t y, struct msmfb_callback *callback, int interface); - void (*dma_wait)(struct mdp_device *mdp); + void (*dma_wait)(struct mdp_device *mdp, int interface); int (*blit)(struct mdp_device *mdp, struct fb_info *fb, struct mdp_blit_req *req); void (*set_grp_disp)(struct mdp_device *mdp, uint32_t disp_id); + int (*check_output_format)(struct mdp_device *mdp, int bpp); + int (*set_output_format)(struct mdp_device *mdp, int bpp); }; struct class_interface; @@ -140,6 +182,9 @@ struct msm_mddi_bridge_platform_data { int (*unblank)(struct msm_mddi_bridge_platform_data *, struct msm_mddi_client_data *); struct msm_fb_data fb_data; + + /* board file will identify what capabilities the panel supports */ + uint32_t panel_caps; }; diff --git a/arch/arm/mach-msm/include/mach/msm_hdmi_audio.h b/arch/arm/mach-msm/include/mach/msm_hdmi_audio.h new file mode 100644 index 00000000000..97dad677d65 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_hdmi_audio.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MSM_HDMI_AUDIO_H +#define __MSM_HDMI_AUDIO_H + +int hdmi_audio_enable(bool on , u32 fifo_water_mark); + +#endif /* __MSM_HDMI_AUDIO_H*/ diff --git a/arch/arm/mach-msm/include/mach/msm_hsusb.h b/arch/arm/mach-msm/include/mach/msm_hsusb.h new file mode 100644 index 00000000000..3fefd524e8f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_hsusb.h @@ -0,0 +1,182 @@ +/* linux/include/mach/hsusb.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_MSM_HSUSB_H +#define __ASM_ARCH_MSM_HSUSB_H + +#include +#include + +#define PHY_TYPE_MASK 0x0F +#define PHY_TYPE_MODE 0xF0 +#define PHY_MODEL_MASK 0xFF00 +#define PHY_TYPE(x) ((x) & PHY_TYPE_MASK) +#define PHY_MODEL(x) ((x) & PHY_MODEL_MASK) + +#define USB_PHY_MODEL_65NM 0x100 +#define USB_PHY_MODEL_180NM 0x200 +#define USB_PHY_MODEL_45NM 0x400 +#define USB_PHY_UNDEFINED 0x00 +#define USB_PHY_INTEGRATED 0x01 +#define USB_PHY_EXTERNAL 0x02 +#define USB_PHY_SERIAL_PMIC 0x04 + +#define REQUEST_STOP 0 +#define REQUEST_START 1 +#define REQUEST_RESUME 2 +#define REQUEST_HNP_SUSPEND 3 +#define REQUEST_HNP_RESUME 4 + +/* Flags required to read ID state of PHY for ACA */ +#define PHY_ID_MASK 0xB0 +#define PHY_ID_GND 0 +#define PHY_ID_C 0x10 +#define PHY_ID_B 0x30 +#define PHY_ID_A 0x90 + +#define phy_id_state(ints) ((ints) & PHY_ID_MASK) +#define phy_id_state_a(ints) (phy_id_state((ints)) == PHY_ID_A) +#define phy_id_state_b(ints) (phy_id_state((ints)) == PHY_ID_B) +#define phy_id_state_c(ints) (phy_id_state((ints)) == PHY_ID_C) +#define phy_id_state_gnd(ints) (phy_id_state((ints)) == PHY_ID_GND) + +/* used to detect the OTG Mode */ +enum otg_mode { + OTG_ID = 0, /* ID pin detection */ + OTG_USER_CONTROL, /* User configurable */ + OTG_VCHG, /* Based on VCHG interrupt */ +}; + +/* used to configure the default mode,if otg_mode is USER_CONTROL */ +enum usb_mode { + USB_HOST_MODE, + USB_PERIPHERAL_MODE, +}; + +enum chg_type { + USB_CHG_TYPE__SDP, + USB_CHG_TYPE__CARKIT, + USB_CHG_TYPE__WALLCHARGER, + USB_CHG_TYPE__INVALID +}; + +enum pre_emphasis_level { + PRE_EMPHASIS_DEFAULT, + PRE_EMPHASIS_DISABLE, + PRE_EMPHASIS_WITH_10_PERCENT = (1 << 5), + PRE_EMPHASIS_WITH_20_PERCENT = (3 << 4), +}; +enum cdr_auto_reset { + CDR_AUTO_RESET_DEFAULT, + CDR_AUTO_RESET_ENABLE, + CDR_AUTO_RESET_DISABLE, +}; + +enum se1_gate_state { + SE1_GATING_DEFAULT, + SE1_GATING_ENABLE, + SE1_GATING_DISABLE, +}; + +enum hs_drv_amplitude { + HS_DRV_AMPLITUDE_DEFAULT, + HS_DRV_AMPLITUDE_ZERO_PERCENT, + HS_DRV_AMPLITUDE_25_PERCENTI = (1 << 2), + HS_DRV_AMPLITUDE_5_PERCENT = (1 << 3), + HS_DRV_AMPLITUDE_75_PERCENT = (3 << 2), +}; + +#define HS_DRV_SLOPE_DEFAULT (-1) + +/* used to configure the analog switch to select b/w host and peripheral */ +enum usb_switch_control { + USB_SWITCH_PERIPHERAL = 0, /* Configure switch in peripheral mode*/ + USB_SWITCH_HOST, /* Host mode */ + USB_SWITCH_DISABLE, /* No mode selected, shutdown power */ +}; + +struct msm_hsusb_gadget_platform_data { + int *phy_init_seq; + void (*phy_reset)(void); + + int self_powered; + int is_phy_status_timer_on; +}; + +struct msm_otg_platform_data { + int (*rpc_connect)(int); + int (*phy_reset)(void __iomem *); + unsigned int core_clk; + int pmic_vbus_irq; + /* if usb link is in sps there is no need for + * usb pclk as dayatona fabric clock will be + * used instead + */ + int usb_in_sps; + enum pre_emphasis_level pemp_level; + enum cdr_auto_reset cdr_autoreset; + enum hs_drv_amplitude drv_ampl; + enum se1_gate_state se1_gating; + int hsdrvslope; + int phy_reset_sig_inverted; + int phy_can_powercollapse; + int pclk_required_during_lpm; + + /* HSUSB core in 8660 has the capability to gate the + * pclk when not being used. Though this feature is + * now being disabled because of H/w issues + */ + int pclk_is_hw_gated; + char *pclk_src_name; + + int (*ldo_init) (int init); + int (*ldo_enable) (int enable); + int (*ldo_set_voltage) (int mV); + + u32 swfi_latency; + /* pmic notfications apis */ + int (*pmic_vbus_notif_init) (void (*callback)(int online), int init); + int (*pmic_id_notif_init) (void (*callback)(int online), int init); + int (*pmic_register_vbus_sn) (void (*callback)(int online)); + void (*pmic_unregister_vbus_sn) (void (*callback)(int online)); + int (*pmic_enable_ldo) (int); + int (*init_gpio)(int on); + void (*setup_gpio)(enum usb_switch_control mode); + u8 otg_mode; + u8 usb_mode; + void (*vbus_power) (unsigned phy_info, int on); + + /* charger notification apis */ + void (*chg_connected)(enum chg_type chg_type); + void (*chg_vbus_draw)(unsigned ma); + int (*chg_init)(int init); + int (*config_vddcx)(int high); + int (*init_vddcx)(int init); + + struct pm_qos_request_list pm_qos_req_dma; +}; + +struct msm_usb_host_platform_data { + unsigned phy_info; + unsigned int power_budget; + void (*config_gpio)(unsigned int config); + void (*vbus_power) (unsigned phy_info, int on); + int (*vbus_init)(int init); + struct clk *ebi1_clk; +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_hsusb_hw.h b/arch/arm/mach-msm/include/mach/msm_hsusb_hw.h new file mode 100644 index 00000000000..69cfb7d12c1 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_hsusb_hw.h @@ -0,0 +1,284 @@ + /* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LINUX_USB_GADGET_MSM72K_UDC_H__ +#define __LINUX_USB_GADGET_MSM72K_UDC_H__ + +#define USB_ID (MSM_USB_BASE + 0x0000) +#define USB_HWGENERAL (MSM_USB_BASE + 0x0004) +#define USB_HWHOST (MSM_USB_BASE + 0x0008) +#define USB_HWDEVICE (MSM_USB_BASE + 0x000C) +#define USB_HWTXBUF (MSM_USB_BASE + 0x0010) +#define USB_HWRXBUF (MSM_USB_BASE + 0x0014) +#define USB_AHB_BURST (MSM_USB_BASE + 0x0090) +#define USB_AHB_MODE (MSM_USB_BASE + 0x0098) +#define USB_ROC_AHB_MODE (MSM_USB_BASE + 0x0090) +#define USB_SBUSCFG (MSM_USB_BASE + 0x0090) + +#define USB_CAPLENGTH (MSM_USB_BASE + 0x0100) /* 8 bit */ +#define USB_HCIVERSION (MSM_USB_BASE + 0x0102) /* 16 bit */ +#define USB_HCSPARAMS (MSM_USB_BASE + 0x0104) +#define USB_HCCPARAMS (MSM_USB_BASE + 0x0108) +#define USB_DCIVERSION (MSM_USB_BASE + 0x0120) /* 16 bit */ +#define USB_USBCMD (MSM_USB_BASE + 0x0140) +#define USB_USBSTS (MSM_USB_BASE + 0x0144) +#define USB_USBINTR (MSM_USB_BASE + 0x0148) +#define USB_FRINDEX (MSM_USB_BASE + 0x014C) +#define USB_DEVICEADDR (MSM_USB_BASE + 0x0154) +#define USB_ENDPOINTLISTADDR (MSM_USB_BASE + 0x0158) +#define USB_BURSTSIZE (MSM_USB_BASE + 0x0160) +#define USB_TXFILLTUNING (MSM_USB_BASE + 0x0164) +#define USB_ULPI_VIEWPORT (MSM_USB_BASE + 0x0170) +#define USB_ENDPTNAK (MSM_USB_BASE + 0x0178) +#define USB_ENDPTNAKEN (MSM_USB_BASE + 0x017C) +#define USB_PORTSC (MSM_USB_BASE + 0x0184) +#define USB_OTGSC (MSM_USB_BASE + 0x01A4) +#define USB_USBMODE (MSM_USB_BASE + 0x01A8) +#define USB_ENDPTSETUPSTAT (MSM_USB_BASE + 0x01AC) +#define USB_ENDPTPRIME (MSM_USB_BASE + 0x01B0) +#define USB_ENDPTFLUSH (MSM_USB_BASE + 0x01B4) +#define USB_ENDPTSTAT (MSM_USB_BASE + 0x01B8) +#define USB_ENDPTCOMPLETE (MSM_USB_BASE + 0x01BC) +#define USB_ENDPTCTRL(n) (MSM_USB_BASE + 0x01C0 + (4 * (n))) + + +#define USBCMD_RESET 2 +#define USBCMD_ATTACH 1 +#define USBCMD_RS (1 << 0) /* run/stop bit */ +#define USBCMD_ATDTW (1 << 14) +#define USBCMD_ITC(n) (n << 16) +#define USBCMD_ITC_MASK (0xFF << 16) +#define ASYNC_INTR_CTRL (1 << 29) +#define ULPI_STP_CTRL (1 << 30) + +#define USBMODE_DEVICE 2 +#define USBMODE_HOST 3 +#define USBMODE_VBUS (1 << 5) /* vbus power select */ + +/* Redefining SDIS bit as it defined incorrectly in ehci.h. */ +#ifdef USBMODE_SDIS +#undef USBMODE_SDIS +#endif +#define USBMODE_SDIS (1 << 4) /* stream disable */ + +struct ept_queue_head { + unsigned config; + unsigned active; /* read-only */ + + unsigned next; + unsigned info; + unsigned page0; + unsigned page1; + unsigned page2; + unsigned page3; + unsigned page4; + unsigned reserved_0; + + unsigned char setup_data[8]; + + unsigned reserved_1; + unsigned reserved_2; + unsigned reserved_3; + unsigned reserved_4; +}; + +#define CONFIG_MAX_PKT(n) ((n) << 16) +#define CONFIG_ZLT (1 << 29) /* stop on zero-len xfer */ +#define CONFIG_IOS (1 << 15) /* IRQ on setup */ + +struct ept_queue_item { + unsigned next; + unsigned info; + unsigned page0; + unsigned page1; + unsigned page2; + unsigned page3; + unsigned page4; + unsigned reserved; +}; + +#define TERMINATE 1 + +#define INFO_BYTES(n) ((n) << 16) +#define INFO_IOC (1 << 15) +#define INFO_ACTIVE (1 << 7) +#define INFO_HALTED (1 << 6) +#define INFO_BUFFER_ERROR (1 << 5) +#define INFO_TXN_ERROR (1 << 3) + + +#define STS_NAKI (1 << 16) /* */ +#define STS_SLI (1 << 8) /* R/WC - suspend state entered */ +#define STS_SRI (1 << 7) /* R/WC - SOF recv'd */ +#define STS_URI (1 << 6) /* R/WC - RESET recv'd */ +#define STS_FRI (1 << 3) /* R/WC - Frame List Rollover */ +#define STS_PCI (1 << 2) /* R/WC - Port Change Detect */ +#define STS_UEI (1 << 1) /* R/WC - USB Error */ +#define STS_UI (1 << 0) /* R/WC - USB Transaction Complete */ + + +/* bits used in all the endpoint status registers */ +#define EPT_TX(n) (1 << ((n) + 16)) +#define EPT_RX(n) (1 << (n)) + + +#define CTRL_TXE (1 << 23) +#define CTRL_TXR (1 << 22) +#define CTRL_TXI (1 << 21) +#define CTRL_TXD (1 << 17) +#define CTRL_TXS (1 << 16) +#define CTRL_RXE (1 << 7) +#define CTRL_RXR (1 << 6) +#define CTRL_RXI (1 << 5) +#define CTRL_RXD (1 << 1) +#define CTRL_RXS (1 << 0) + +#define CTRL_TXT_MASK (3 << 18) +#define CTRL_TXT_CTRL (0 << 18) +#define CTRL_TXT_ISOCH (1 << 18) +#define CTRL_TXT_BULK (2 << 18) +#define CTRL_TXT_INT (3 << 18) +#define CTRL_TXT_EP_TYPE_SHIFT 18 + +#define CTRL_RXT_MASK (3 << 2) +#define CTRL_RXT_CTRL (0 << 2) +#define CTRL_RXT_ISOCH (1 << 2) +#define CTRL_RXT_BULK (2 << 2) +#define CTRL_RXT_INT (3 << 2) +#define CTRL_RXT_EP_TYPE_SHIFT 2 + +#define ULPI_CONFIG_REG 0x31 +#if (defined(CONFIG_ARCH_MSM7X27) && !defined(CONFIG_ARCH_MSM7X27A)) \ + || defined(CONFIG_ARCH_QSD8X50) +#define ULPI_DIGOUT_CTRL 0X31 +#define ULPI_CDR_AUTORESET (1 << 5) +#else +#define ULPI_DIGOUT_CTRL 0X36 +#define ULPI_CDR_AUTORESET (1 << 1) +#endif +#define ULPI_SE1_GATE (1 << 2) +#define ULPI_CONFIG_REG1 0x30 +#define ULPI_CONFIG_REG2 0X31 +#define ULPI_CONFIG_REG3 0X32 +#define ULPI_IFC_CTRL_CLR 0x09 +#define ULPI_AMPLITUDE_MAX 0x0C +#define ULPI_OTG_CTRL 0x0B +#define ULPI_OTG_CTRL_CLR 0x0C +#define ULPI_INT_RISE_CLR 0x0F +#define ULPI_INT_FALL_CLR 0x12 +#define ULPI_PRE_EMPHASIS_MASK (3 << 4) +#define ULPI_HSDRVSLOPE_MASK (0x0F) +#define ULPI_DRV_AMPL_MASK (3 << 2) +#define ULPI_ONCLOCK (1 << 6) +#define ULPI_IDPU (1 << 0) +#define ULPI_HOST_DISCONNECT (1 << 0) +#define ULPI_VBUS_VALID (1 << 1) +#define ULPI_SESS_END (1 << 3) +#define ULPI_ID_GND (1 << 4) +#define ULPI_WAKEUP (1 << 31) +#define ULPI_RUN (1 << 30) +#define ULPI_WRITE (1 << 29) +#define ULPI_READ (0 << 29) +#define ULPI_STATE_NORMAL (1 << 27) +#define ULPI_ADDR(n) (((n) & 255) << 16) +#define ULPI_DATA(n) ((n) & 255) +#define ULPI_DATA_READ(n) (((n) >> 8) & 255) + +/* USB_PORTSC bits for determining port speed */ +#define PORTSC_PSPD_FS (0 << 26) +#define PORTSC_PSPD_LS (1 << 26) +#define PORTSC_PSPD_HS (2 << 26) +#define PORTSC_PSPD_MASK (3 << 26) + + +#define OTGSC_BSVIE (1 << 27) /* R/W - BSV Interrupt Enable */ +#define OTGSC_DPIE (1 << 30) /* R/W - DataPulse Interrupt Enable */ +#define OTGSC_1MSE (1 << 29) /* R/W - 1ms Interrupt Enable */ +#define OTGSC_BSEIE (1 << 28) /* R/W - BSE Interrupt Enable */ +#define OTGSC_ASVIE (1 << 26) /* R/W - ASV Interrupt Enable */ +#define OTGSC_ASEIE (1 << 25) /* R/W - ASE Interrupt Enable */ +#define OTGSC_IDIE (1 << 24) /* R/W - ID Interrupt Enable */ +#define OTGSC_BSVIS (1 << 19) /* R/W - BSV Interrupt Status */ +#define OTGSC_IDPU (1 << 5) +#define OTGSC_ID (1 << 8) +#define OTGSC_IDIS (1 << 16) +#define B_SESSION_VALID (1 << 11) +#define OTGSC_INTR_MASK (OTGSC_BSVIE | OTGSC_DPIE | OTGSC_1MSE | \ + OTGSC_BSEIE | OTGSC_ASVIE | OTGSC_ASEIE | \ + OTGSC_IDIE) +#define OTGSC_INTR_STS_MASK (0x7f << 16) +#define CURRENT_CONNECT_STATUS (1 << 0) + +#define PORTSC_FPR (1 << 6) /* R/W - State normal => suspend */ +#define PORTSC_SUSP (1 << 7) /* Read - Port in suspend state */ +#define PORTSC_LS (3 << 10) /* Read - Port's Line status */ +#define PORTSC_PHCD (1 << 23) /* phy suspend mode */ +#define PORTSC_CCS (1 << 0) /* current connect status */ +#define PORTSC_PORT_RESET 0x00000100 +#define PORTSC_PTS (3 << 30) +#define PORTSC_PTS_ULPI (2 << 30) +#define PORTSC_PTS_SERIAL (3 << 30) + +#define PORTSC_PORT_SPEED_FULL 0x00000000 +#define PORTSC_PORT_SPEED_LOW 0x04000000 +#define PORTSC_PORT_SPEED_HIGH 0x08000000 +#define PORTSC_PORT_SPEED_MASK 0x0c000000 + +#define SBUSCFG_AHBBRST_INCR4 0x01 +#define ULPI_USBINTR_ENABLE_RASING_C 0x0F +#define ULPI_USBINTR_ENABLE_FALLING_C 0x12 +#define ULPI_USBINTR_STATUS 0x13 +#define ULPI_USBINTR_ENABLE_RASING_S 0x0E +#define ULPI_USBINTR_ENABLE_FALLING_S 0x11 +#define ULPI_SESSION_END_RAISE (1 << 3) +#define ULPI_SESSION_END_FALL (1 << 3) +#define ULPI_SESSION_VALID_RAISE (1 << 2) +#define ULPI_SESSION_VALID_FALL (1 << 2) +#define ULPI_VBUS_VALID_RAISE (1 << 1) +#define ULPI_VBUS_VALID_FALL (1 << 1) + +#define ULPI_CHG_DETECT_REG 0x34 +/* control charger detection by ULPI or externally */ +#define ULPI_EXTCHGCTRL_65NM (1 << 2) +#define ULPI_EXTCHGCTRL_180NM (1 << 3) +/* charger detection power on control */ +#define ULPI_CHGDETON (1 << 1) + /* enable charger detection */ +#define ULPI_CHGDETEN (1 << 0) +#define ULPI_CHGTYPE_65NM (1 << 3) +#define ULPI_CHGTYPE_180NM (1 << 4) + +/* test mode support */ +#define J_TEST (0x0100) +#define K_TEST (0x0200) +#define SE0_NAK_TEST (0x0300) +#define TST_PKT_TEST (0x0400) +#define PORTSC_PTC (0xf << 16) +#define PORTSC_PTC_J_STATE (0x01 << 16) +#define PORTSC_PTC_K_STATE (0x02 << 16) +#define PORTSC_PTC_SE0_NAK (0x03 << 16) +#define PORTSC_PTC_TST_PKT (0x04 << 16) + +#define USBH (1 << 15) +#define USB_PHY (1 << 18) + +#define ULPI_DEBUG 0x15 +#define ULPI_FUNC_CTRL_CLR 0x06 +#define ULPI_SUSPENDM (1 << 6) +#define ULPI_CLOCK_SUSPENDM (1 << 3) +#define ULPI_CALIB_STS (1 << 7) +#define ULPI_CALIB_VAL(x) (x & 0x7C) +#endif /* __LINUX_USB_GADGET_MSM72K_UDC_H__ */ diff --git a/arch/arm/mach-msm/include/mach/msm_i2ckbd.h b/arch/arm/mach-msm/include/mach/msm_i2ckbd.h new file mode 100644 index 00000000000..dc33c757bda --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_i2ckbd.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _MSM_I2CKBD_H_ +#define _MSM_I2CKBD_H_ + +struct msm_i2ckbd_platform_data { + uint8_t hwrepeat; + uint8_t scanset1; + int gpioreset; + int gpioirq; + int (*gpio_setup) (void); + void (*gpio_shutdown)(void); + void (*hw_reset) (int); +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-7x00.h b/arch/arm/mach-msm/include/mach/msm_iomap-7x00.h index 8f99d97615a..571391b605b 100644 --- a/arch/arm/mach-msm/include/mach/msm_iomap-7x00.h +++ b/arch/arm/mach-msm/include/mach/msm_iomap-7x00.h @@ -1,7 +1,6 @@ /* arch/arm/mach-msm/include/mach/msm_iomap.h * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2011, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -44,30 +43,38 @@ #define IOMEM(x) ((void __force __iomem *)(x)) #endif -#define MSM_VIC_BASE IOMEM(0xE0000000) +#define MSM_VIC_BASE IOMEM(0xF8000000) #define MSM_VIC_PHYS 0xC0000000 #define MSM_VIC_SIZE SZ_4K -#define MSM7X00_CSR_PHYS 0xC0100000 -#define MSM7X00_CSR_SIZE SZ_4K +#define MSM_CSR_BASE IOMEM(0xF8001000) +#define MSM_CSR_PHYS 0xC0100000 +#define MSM_CSR_SIZE SZ_4K -#define MSM_DMOV_BASE IOMEM(0xE0002000) +#define MSM_TMR_PHYS MSM_CSR_PHYS +#define MSM_TMR_BASE MSM_CSR_BASE +#define MSM_TMR_SIZE SZ_4K + +#define MSM_GPT_BASE MSM_TMR_BASE +#define MSM_DGT_BASE (MSM_TMR_BASE + 0x10) + +#define MSM_DMOV_BASE IOMEM(0xF8002000) #define MSM_DMOV_PHYS 0xA9700000 #define MSM_DMOV_SIZE SZ_4K -#define MSM_GPIO1_BASE IOMEM(0xE0003000) +#define MSM_GPIO1_BASE IOMEM(0xF8003000) #define MSM_GPIO1_PHYS 0xA9200000 #define MSM_GPIO1_SIZE SZ_4K -#define MSM_GPIO2_BASE IOMEM(0xE0004000) +#define MSM_GPIO2_BASE IOMEM(0xF8004000) #define MSM_GPIO2_PHYS 0xA9300000 #define MSM_GPIO2_SIZE SZ_4K -#define MSM_CLK_CTL_BASE IOMEM(0xE0005000) +#define MSM_CLK_CTL_BASE IOMEM(0xF8005000) #define MSM_CLK_CTL_PHYS 0xA8600000 #define MSM_CLK_CTL_SIZE SZ_4K -#define MSM_SHARED_RAM_BASE IOMEM(0xE0100000) +#define MSM_SHARED_RAM_BASE IOMEM(0xF8100000) #define MSM_SHARED_RAM_PHYS 0x01F00000 #define MSM_SHARED_RAM_SIZE SZ_1M @@ -81,7 +88,7 @@ #define MSM_UART3_SIZE SZ_4K #ifdef CONFIG_MSM_DEBUG_UART -#define MSM_DEBUG_UART_BASE 0xE1000000 +#define MSM_DEBUG_UART_BASE 0xF9000000 #if CONFIG_MSM_DEBUG_UART == 1 #define MSM_DEBUG_UART_PHYS MSM_UART1_PHYS #elif CONFIG_MSM_DEBUG_UART == 2 @@ -104,6 +111,9 @@ #define MSM_SDC4_PHYS 0xA0700000 #define MSM_SDC4_SIZE SZ_4K +#define MSM_NAND_PHYS 0xA0A00000 +#define MSM_NAND_SIZE SZ_4K + #define MSM_I2C_PHYS 0xA9900000 #define MSM_I2C_SIZE SZ_4K @@ -119,11 +129,30 @@ #define MSM_MDP_PHYS 0xAA200000 #define MSM_MDP_SIZE 0x000F0000 +#define MSM_MDC_BASE IOMEM(0xF8200000) #define MSM_MDC_PHYS 0xAA500000 #define MSM_MDC_SIZE SZ_1M +#define MSM_AD5_BASE IOMEM(0xF8300000) #define MSM_AD5_PHYS 0xAC000000 #define MSM_AD5_SIZE (SZ_1M*13) +#define MSM_VFE_PHYS 0xA0F00000 +#define MSM_VFE_SIZE SZ_1M + +#define MSM_UART1DM_PHYS 0xA0200000 +#define MSM_UART2DM_PHYS 0xA0300000 + +#define MSM_SSBI_PHYS 0xA8100000 +#define MSM_SSBI_SIZE SZ_4K + +#define MSM_TSSC_PHYS 0xAA300000 +#define MSM_TSSC_SIZE SZ_4K + +#if defined(CONFIG_ARCH_MSM7X30) +#define MSM_GCC_BASE IOMEM(0xF8009000) +#define MSM_GCC_PHYS 0xC0182000 +#define MSM_GCC_SIZE SZ_4K +#endif #endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-7x30.h b/arch/arm/mach-msm/include/mach/msm_iomap-7x30.h index 4d84be15955..fce9e3519ed 100644 --- a/arch/arm/mach-msm/include/mach/msm_iomap-7x30.h +++ b/arch/arm/mach-msm/include/mach/msm_iomap-7x30.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2008-2011 Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -35,51 +35,55 @@ * */ -#define MSM_VIC_BASE IOMEM(0xE0000000) +#define MSM_VIC_BASE IOMEM(0xFA000000) #define MSM_VIC_PHYS 0xC0080000 #define MSM_VIC_SIZE SZ_4K -#define MSM7X30_CSR_PHYS 0xC0100000 -#define MSM7X30_CSR_SIZE SZ_4K +#define MSM_CSR_BASE IOMEM(0xFA001000) +#define MSM_CSR_PHYS 0xC0100000 +#define MSM_CSR_SIZE SZ_4K -#define MSM_DMOV_BASE IOMEM(0xE0002000) +#define MSM_TMR_PHYS MSM_CSR_PHYS +#define MSM_TMR_BASE MSM_CSR_BASE +#define MSM_TMR_SIZE SZ_4K + +#define MSM_DMOV_BASE IOMEM(0xFA002000) #define MSM_DMOV_PHYS 0xAC400000 #define MSM_DMOV_SIZE SZ_4K -#define MSM_GPIO1_BASE IOMEM(0xE0003000) +#define MSM_GPIO1_BASE IOMEM(0xFA003000) #define MSM_GPIO1_PHYS 0xAC001000 #define MSM_GPIO1_SIZE SZ_4K -#define MSM_GPIO2_BASE IOMEM(0xE0004000) +#define MSM_GPIO2_BASE IOMEM(0xFA004000) #define MSM_GPIO2_PHYS 0xAC101000 #define MSM_GPIO2_SIZE SZ_4K -#define MSM_CLK_CTL_BASE IOMEM(0xE0005000) +#define MSM_CLK_CTL_BASE IOMEM(0xFA005000) #define MSM_CLK_CTL_PHYS 0xAB800000 #define MSM_CLK_CTL_SIZE SZ_4K -#define MSM_CLK_CTL_SH2_BASE IOMEM(0xE0006000) +#define MSM_CLK_CTL_SH2_BASE IOMEM(0xFA006000) #define MSM_CLK_CTL_SH2_PHYS 0xABA01000 #define MSM_CLK_CTL_SH2_SIZE SZ_4K -#define MSM_ACC_BASE IOMEM(0xE0007000) +#define MSM_ACC_BASE IOMEM(0xFA007000) #define MSM_ACC_PHYS 0xC0101000 #define MSM_ACC_SIZE SZ_4K -#define MSM_SAW_BASE IOMEM(0xE0008000) +#define MSM_SAW_BASE IOMEM(0xFA008000) #define MSM_SAW_PHYS 0xC0102000 #define MSM_SAW_SIZE SZ_4K -#define MSM_GCC_BASE IOMEM(0xE0009000) +#define MSM_GCC_BASE IOMEM(0xFA009000) #define MSM_GCC_PHYS 0xC0182000 #define MSM_GCC_SIZE SZ_4K -#define MSM_TCSR_BASE IOMEM(0xE000A000) +#define MSM_TCSR_BASE IOMEM(0xFA00A000) #define MSM_TCSR_PHYS 0xAB600000 #define MSM_TCSR_SIZE SZ_4K -#define MSM_SHARED_RAM_BASE IOMEM(0xE0100000) -#define MSM_SHARED_RAM_PHYS 0x00100000 +#define MSM_SHARED_RAM_BASE IOMEM(0xFA100000) #define MSM_SHARED_RAM_SIZE SZ_1M #define MSM_UART1_PHYS 0xACA00000 @@ -92,7 +96,7 @@ #define MSM_UART3_SIZE SZ_4K #ifdef CONFIG_MSM_DEBUG_UART -#define MSM_DEBUG_UART_BASE 0xE1000000 +#define MSM_DEBUG_UART_BASE 0xFB000000 #if CONFIG_MSM_DEBUG_UART == 1 #define MSM_DEBUG_UART_PHYS MSM_UART1_PHYS #elif CONFIG_MSM_DEBUG_UART == 2 @@ -103,15 +107,12 @@ #define MSM_DEBUG_UART_SIZE SZ_4K #endif -#define MSM_MDC_BASE IOMEM(0xE0200000) +#define MSM_MDC_BASE IOMEM(0xFA200000) #define MSM_MDC_PHYS 0xAA500000 #define MSM_MDC_SIZE SZ_1M -#define MSM_AD5_BASE IOMEM(0xE0300000) +#define MSM_AD5_BASE IOMEM(0xFA300000) #define MSM_AD5_PHYS 0xA7000000 #define MSM_AD5_SIZE (SZ_1M*13) -#define MSM_HSUSB_PHYS 0xA3600000 -#define MSM_HSUSB_SIZE SZ_1K - #endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-7xxx.h b/arch/arm/mach-msm/include/mach/msm_iomap-7xxx.h new file mode 100644 index 00000000000..1fb9d0ea992 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-7xxx.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * 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. + * + * + * The MSM peripherals are spread all over across 768MB of physical + * space, which makes just having a simple IO_ADDRESS macro to slide + * them into the right virtual location rough. Instead, we will + * provide a master phys->virt mapping for peripherals here. + * + */ + +#ifndef __ASM_ARCH_MSM_IOMAP_7XXX_H +#define __ASM_ARCH_MSM_IOMAP_7XXX_H + +/* Physical base address and size of peripherals. + * Ordered by the virtual base addresses they will be mapped at. + * + * MSM_VIC_BASE must be an value that can be loaded via a "mov" + * instruction, otherwise entry-macro.S will not compile. + * + * If you add or remove entries here, you'll want to edit the + * msm_io_desc array in arch/arm/mach-msm/io.c to reflect your + * changes. + * + */ + +#define MSM_VIC_BASE IOMEM(0xFA000000) +#define MSM_VIC_PHYS 0xC0000000 +#define MSM_VIC_SIZE SZ_4K + +#define MSM_CSR_BASE IOMEM(0xFA001000) +#define MSM_CSR_PHYS 0xC0100000 +#define MSM_CSR_SIZE SZ_4K + +#define MSM_TMR_PHYS MSM_CSR_PHYS +#define MSM_TMR_BASE MSM_CSR_BASE +#define MSM_TMR_SIZE SZ_4K + +#define MSM_DMOV_BASE IOMEM(0xFA002000) +#define MSM_DMOV_PHYS 0xA9700000 +#define MSM_DMOV_SIZE SZ_4K + +#define MSM_GPIO1_BASE IOMEM(0xFA003000) +#define MSM_GPIO1_PHYS 0xA9200000 +#define MSM_GPIO1_SIZE SZ_4K + +#define MSM_GPIO2_BASE IOMEM(0xFA004000) +#define MSM_GPIO2_PHYS 0xA9300000 +#define MSM_GPIO2_SIZE SZ_4K + +#define MSM_CLK_CTL_BASE IOMEM(0xFA005000) +#define MSM_CLK_CTL_PHYS 0xA8600000 +#define MSM_CLK_CTL_SIZE SZ_4K + +#define MSM_L2CC_BASE IOMEM(0xFA006000) +#define MSM_L2CC_PHYS 0xC0400000 +#define MSM_L2CC_SIZE SZ_4K + +#define MSM_SHARED_RAM_BASE IOMEM(0xFA100000) +#define MSM_SHARED_RAM_SIZE SZ_1M + +#define MSM_UART1_PHYS 0xA9A00000 +#define MSM_UART1_SIZE SZ_4K + +#define MSM_UART2_PHYS 0xA9B00000 +#define MSM_UART2_SIZE SZ_4K + +#define MSM_UART3_PHYS 0xA9C00000 +#define MSM_UART3_SIZE SZ_4K + +#ifdef CONFIG_MSM_DEBUG_UART +#define MSM_DEBUG_UART_BASE 0xFB000000 +#if CONFIG_MSM_DEBUG_UART == 1 +#define MSM_DEBUG_UART_PHYS MSM_UART1_PHYS +#elif CONFIG_MSM_DEBUG_UART == 2 +#define MSM_DEBUG_UART_PHYS MSM_UART2_PHYS +#elif CONFIG_MSM_DEBUG_UART == 3 +#define MSM_DEBUG_UART_PHYS MSM_UART3_PHYS +#endif +#define MSM_DEBUG_UART_SIZE SZ_4K +#endif + +#define MSM_MDC_BASE IOMEM(0xFA200000) +#define MSM_MDC_PHYS 0xAA500000 +#define MSM_MDC_SIZE SZ_1M + +#define MSM_AD5_BASE IOMEM(0xFA300000) +#define MSM_AD5_PHYS 0xAC000000 +#define MSM_AD5_SIZE (SZ_1M*13) + +#define MSM_STRONGLY_ORDERED_PAGE 0xFA0F0000 + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-8064.h b/arch/arm/mach-msm/include/mach/msm_iomap-8064.h new file mode 100644 index 00000000000..0e6eec0ef4e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-8064.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * 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. + * + * + * The MSM peripherals are spread all over across 768MB of physical + * space, which makes just having a simple IO_ADDRESS macro to slide + * them into the right virtual location rough. Instead, we will + * provide a master phys->virt mapping for peripherals here. + * + */ + +#ifndef __ASM_ARCH_MSM_IOMAP_8064_H +#define __ASM_ARCH_MSM_IOMAP_8064_H + +/* Physical base address and size of peripherals. + * Ordered by the virtual base addresses they will be mapped at. + * + * If you add or remove entries here, you'll want to edit the + * msm_io_desc array in arch/arm/mach-msm/io.c to reflect your + * changes. + * + */ + +#define APQ8064_TMR_PHYS 0x0200A000 +#define APQ8064_TMR_SIZE SZ_4K + +#define APQ8064_TMR0_PHYS 0x0208A000 +#define APQ8064_TMR0_SIZE SZ_4K + +#define APQ8064_QGIC_DIST_PHYS 0x02000000 +#define APQ8064_QGIC_DIST_SIZE SZ_4K + +#define APQ8064_QGIC_CPU_PHYS 0x02002000 +#define APQ8064_QGIC_CPU_SIZE SZ_4K + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-8960.h b/arch/arm/mach-msm/include/mach/msm_iomap-8960.h index 3c9d9602a31..38036468cc5 100644 --- a/arch/arm/mach-msm/include/mach/msm_iomap-8960.h +++ b/arch/arm/mach-msm/include/mach/msm_iomap-8960.h @@ -32,17 +32,85 @@ * */ +#define MSM8960_TMR_PHYS 0x0200A000 +#define MSM8960_TMR_SIZE SZ_4K -#define MSM8960_QGIC_DIST_PHYS 0x02000000 -#define MSM8960_QGIC_DIST_SIZE SZ_4K +#define MSM8960_TMR0_PHYS 0x0208A000 +#define MSM8960_TMR0_SIZE SZ_4K -#define MSM8960_QGIC_CPU_PHYS 0x02002000 -#define MSM8960_QGIC_CPU_SIZE SZ_4K +#define MSM8960_RPM_PHYS 0x00108000 +#define MSM8960_RPM_SIZE SZ_4K -#define MSM8960_TMR_PHYS 0x0200A000 -#define MSM8960_TMR_SIZE SZ_4K +#define MSM8960_RPM_MPM_PHYS 0x00200000 +#define MSM8960_RPM_MPM_SIZE SZ_4K -#define MSM8960_TMR0_PHYS 0x0208A000 -#define MSM8960_TMR0_SIZE SZ_4K +#define MSM8960_TCSR_PHYS 0x1A400000 +#define MSM8960_TCSR_SIZE SZ_4K + +#define MSM8960_APCS_GCC_PHYS 0x02011000 +#define MSM8960_APCS_GCC_SIZE SZ_4K + +#define MSM8960_SAW_L2_PHYS 0x02012000 +#define MSM8960_SAW_L2_SIZE SZ_4K + +#define MSM8960_SAW0_PHYS 0x02089000 +#define MSM8960_SAW0_SIZE SZ_4K + +#define MSM8960_SAW1_PHYS 0x02099000 +#define MSM8960_SAW1_SIZE SZ_4K + +#define MSM8960_IMEM_PHYS 0x2A03F000 +#define MSM8960_IMEM_SIZE SZ_4K + +#define MSM8960_ACC0_PHYS 0x02088000 +#define MSM8960_ACC0_SIZE SZ_4K + +#define MSM8960_ACC1_PHYS 0x02098000 +#define MSM8960_ACC1_SIZE SZ_4K + +#define MSM8960_QGIC_DIST_PHYS 0x02000000 +#define MSM8960_QGIC_DIST_SIZE SZ_4K + +#define MSM8960_QGIC_CPU_PHYS 0x02002000 +#define MSM8960_QGIC_CPU_SIZE SZ_4K + +#define MSM8960_CLK_CTL_PHYS 0x00900000 +#define MSM8960_CLK_CTL_SIZE SZ_16K + +#define MSM8960_MMSS_CLK_CTL_PHYS 0x04000000 +#define MSM8960_MMSS_CLK_CTL_SIZE SZ_4K + +#define MSM8960_LPASS_CLK_CTL_PHYS 0x28000000 +#define MSM8960_LPASS_CLK_CTL_SIZE SZ_4K + +#define MSM8960_HFPLL_PHYS 0x00903000 +#define MSM8960_HFPLL_SIZE SZ_4K + +#define MSM8960_TLMM_PHYS 0x00800000 +#define MSM8960_TLMM_SIZE SZ_16K + +#define MSM8960_DMOV_PHYS 0x18320000 +#define MSM8960_DMOV_SIZE SZ_1M + +#define MSM8960_SIC_NON_SECURE_PHYS 0x12100000 +#define MSM8960_SIC_NON_SECURE_SIZE SZ_64K + +#define MSM_GPT_BASE (MSM_TMR_BASE + 0x4) +#define MSM_DGT_BASE (MSM_TMR_BASE + 0x24) + +#define MSM8960_HSUSB_PHYS 0x12500000 +#define MSM8960_HSUSB_SIZE SZ_4K + +#define MSM8960_HDMI_PHYS 0x04A00000 +#define MSM8960_HDMI_SIZE SZ_4K + +#ifdef CONFIG_MSM_DEBUG_UART +#define MSM_DEBUG_UART_BASE IOMEM(0xFA740000) +#define MSM_DEBUG_UART_SIZE SZ_4K + +#ifdef CONFIG_MSM_DEBUG_UART1 +#define MSM_DEBUG_UART_PHYS 0x16440000 +#endif +#endif #endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-8x50.h b/arch/arm/mach-msm/include/mach/msm_iomap-8x50.h index d4143201999..cab40277fdd 100644 --- a/arch/arm/mach-msm/include/mach/msm_iomap-8x50.h +++ b/arch/arm/mach-msm/include/mach/msm_iomap-8x50.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2008-2011 Code Aurora Forum. All rights reserved. + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -35,45 +35,47 @@ * */ -#define MSM_VIC_BASE IOMEM(0xE0000000) +#define MSM_VIC_BASE IOMEM(0xFA000000) #define MSM_VIC_PHYS 0xAC000000 #define MSM_VIC_SIZE SZ_4K -#define QSD8X50_CSR_PHYS 0xAC100000 -#define QSD8X50_CSR_SIZE SZ_4K +#define MSM_CSR_BASE IOMEM(0xFA001000) +#define MSM_CSR_PHYS 0xAC100000 +#define MSM_CSR_SIZE SZ_4K -#define MSM_DMOV_BASE IOMEM(0xE0002000) +#define MSM_TMR_PHYS MSM_CSR_PHYS +#define MSM_TMR_BASE MSM_CSR_BASE +#define MSM_TMR_SIZE SZ_4K + +#define MSM_DMOV_BASE IOMEM(0xFA002000) #define MSM_DMOV_PHYS 0xA9700000 #define MSM_DMOV_SIZE SZ_4K -#define MSM_GPIO1_BASE IOMEM(0xE0003000) +#define MSM_GPIO1_BASE IOMEM(0xFA003000) #define MSM_GPIO1_PHYS 0xA9000000 #define MSM_GPIO1_SIZE SZ_4K -#define MSM_GPIO2_BASE IOMEM(0xE0004000) +#define MSM_GPIO2_BASE IOMEM(0xFA004000) #define MSM_GPIO2_PHYS 0xA9100000 #define MSM_GPIO2_SIZE SZ_4K -#define MSM_CLK_CTL_BASE IOMEM(0xE0005000) +#define MSM_CLK_CTL_BASE IOMEM(0xFA005000) #define MSM_CLK_CTL_PHYS 0xA8600000 #define MSM_CLK_CTL_SIZE SZ_4K -#define MSM_SIRC_BASE IOMEM(0xE1006000) +#define MSM_SIRC_BASE IOMEM(0xFB006000) #define MSM_SIRC_PHYS 0xAC200000 #define MSM_SIRC_SIZE SZ_4K -#define MSM_SCPLL_BASE IOMEM(0xE1007000) +#define MSM_SCPLL_BASE IOMEM(0xFB007000) #define MSM_SCPLL_PHYS 0xA8800000 #define MSM_SCPLL_SIZE SZ_4K -#ifdef CONFIG_MSM_SOC_REV_A -#define MSM_SMI_BASE 0xE0000000 -#else -#define MSM_SMI_BASE 0x00000000 -#endif +#define MSM_TCSR_BASE IOMEM(0xFB008000) +#define MSM_TCSR_PHYS 0xA8700000 +#define MSM_TCSR_SIZE SZ_4K -#define MSM_SHARED_RAM_BASE IOMEM(0xE0100000) -#define MSM_SHARED_RAM_PHYS (MSM_SMI_BASE + 0x00100000) +#define MSM_SHARED_RAM_BASE IOMEM(0xFA100000) #define MSM_SHARED_RAM_SIZE SZ_1M #define MSM_UART1_PHYS 0xA9A00000 @@ -86,7 +88,7 @@ #define MSM_UART3_SIZE SZ_4K #ifdef CONFIG_MSM_DEBUG_UART -#define MSM_DEBUG_UART_BASE 0xE1000000 +#define MSM_DEBUG_UART_BASE 0xFB000000 #if CONFIG_MSM_DEBUG_UART == 1 #define MSM_DEBUG_UART_PHYS MSM_UART1_PHYS #elif CONFIG_MSM_DEBUG_UART == 2 @@ -97,43 +99,12 @@ #define MSM_DEBUG_UART_SIZE SZ_4K #endif -#define MSM_MDC_BASE IOMEM(0xE0200000) +#define MSM_MDC_BASE IOMEM(0xFA200000) #define MSM_MDC_PHYS 0xAA500000 #define MSM_MDC_SIZE SZ_1M -#define MSM_AD5_BASE IOMEM(0xE0300000) +#define MSM_AD5_BASE IOMEM(0xFA300000) #define MSM_AD5_PHYS 0xAC000000 #define MSM_AD5_SIZE (SZ_1M*13) - -#define MSM_I2C_SIZE SZ_4K -#define MSM_I2C_PHYS 0xA9900000 - -#define MSM_HSUSB_PHYS 0xA0800000 -#define MSM_HSUSB_SIZE SZ_1K - -#define MSM_NAND_PHYS 0xA0A00000 - - -#define MSM_TSIF_PHYS (0xa0100000) -#define MSM_TSIF_SIZE (0x200) - -#define MSM_TSSC_PHYS 0xAA300000 - -#define MSM_UART1DM_PHYS 0xA0200000 -#define MSM_UART2DM_PHYS 0xA0900000 - - -#define MSM_SDC1_PHYS 0xA0300000 -#define MSM_SDC1_SIZE SZ_4K - -#define MSM_SDC2_PHYS 0xA0400000 -#define MSM_SDC2_SIZE SZ_4K - -#define MSM_SDC3_PHYS 0xA0500000 -#define MSM_SDC3_SIZE SZ_4K - -#define MSM_SDC4_PHYS 0xA0600000 -#define MSM_SDC4_SIZE SZ_4K - #endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-8x60.h b/arch/arm/mach-msm/include/mach/msm_iomap-8x60.h index 3b19b8f244b..4b91733420a 100644 --- a/arch/arm/mach-msm/include/mach/msm_iomap-8x60.h +++ b/arch/arm/mach-msm/include/mach/msm_iomap-8x60.h @@ -35,31 +35,110 @@ * */ -#define MSM8X60_QGIC_DIST_PHYS 0x02080000 -#define MSM8X60_QGIC_DIST_SIZE SZ_4K +#define MSM_QGIC_DIST_BASE IOMEM(0xFA000000) +#define MSM_QGIC_DIST_PHYS 0x02080000 +#define MSM_QGIC_DIST_SIZE SZ_4K -#define MSM8X60_QGIC_CPU_PHYS 0x02081000 -#define MSM8X60_QGIC_CPU_SIZE SZ_4K +#define MSM_QGIC_CPU_BASE IOMEM(0xFA001000) +#define MSM_QGIC_CPU_PHYS 0x02081000 +#define MSM_QGIC_CPU_SIZE SZ_4K -#define MSM_ACC_BASE IOMEM(0xF0002000) +#define MSM_ACC_BASE IOMEM(0xFA002000) #define MSM_ACC_PHYS 0x02001000 #define MSM_ACC_SIZE SZ_4K -#define MSM_GCC_BASE IOMEM(0xF0003000) +#define MSM_GCC_BASE IOMEM(0xFA003000) #define MSM_GCC_PHYS 0x02082000 #define MSM_GCC_SIZE SZ_4K -#define MSM_TLMM_BASE IOMEM(0xF0004000) +#define MSM_TLMM_BASE IOMEM(0xFA004000) #define MSM_TLMM_PHYS 0x00800000 #define MSM_TLMM_SIZE SZ_16K -#define MSM_SHARED_RAM_BASE IOMEM(0xF0100000) +#define MSM_RPM_BASE IOMEM(0xFA008000) +#define MSM_RPM_PHYS 0x00104000 +#define MSM_RPM_SIZE SZ_4K + +#define MSM_CLK_CTL_BASE IOMEM(0xFA010000) +#define MSM_CLK_CTL_PHYS 0x00900000 +#define MSM_CLK_CTL_SIZE SZ_16K + +#define MSM_MMSS_CLK_CTL_BASE IOMEM(0xFA014000) +#define MSM_MMSS_CLK_CTL_PHYS 0x04000000 +#define MSM_MMSS_CLK_CTL_SIZE SZ_4K + +#define MSM_LPASS_CLK_CTL_BASE IOMEM(0xFA015000) +#define MSM_LPASS_CLK_CTL_PHYS 0x28000000 +#define MSM_LPASS_CLK_CTL_SIZE SZ_4K + +#define MSM_TMR_BASE IOMEM(0xFA016000) +#define MSM_TMR_PHYS 0x02000000 +#define MSM_TMR_SIZE SZ_4K + +#define MSM_TMR0_BASE IOMEM(0xFA017000) +#define MSM_TMR0_PHYS 0x02040000 +#define MSM_TMR0_SIZE SZ_4K + +#define MSM_SCPLL_BASE IOMEM(0xFA018000) +#define MSM_SCPLL_PHYS 0x00903000 +#define MSM_SCPLL_SIZE SZ_1K + +#define MSM_SHARED_RAM_BASE IOMEM(0xFA200000) #define MSM_SHARED_RAM_SIZE SZ_1M -#define MSM8X60_TMR_PHYS 0x02000000 -#define MSM8X60_TMR_SIZE SZ_4K +#define MSM_ACC0_BASE IOMEM(0xFA300000) +#define MSM_ACC0_PHYS 0x02041000 +#define MSM_ACC0_SIZE SZ_4K + +#define MSM_ACC1_BASE IOMEM(0xFA301000) +#define MSM_ACC1_PHYS 0x02051000 +#define MSM_ACC1_SIZE SZ_4K + +#define MSM_RPM_MPM_BASE IOMEM(0xFA302000) +#define MSM_RPM_MPM_PHYS 0x00200000 +#define MSM_RPM_MPM_SIZE SZ_4K + +#define MSM_SAW0_BASE IOMEM(0xFA303000) +#define MSM_SAW0_PHYS 0x02042000 +#define MSM_SAW0_SIZE SZ_4K -#define MSM8X60_TMR0_PHYS 0x02040000 -#define MSM8X60_TMR0_SIZE SZ_4K +#define MSM_SAW1_BASE IOMEM(0xFA304000) +#define MSM_SAW1_PHYS 0x02052000 +#define MSM_SAW1_SIZE SZ_4K +#define MSM_DMOV_ADM0_BASE IOMEM(0xFA400000) +#define MSM_DMOV_ADM0_PHYS 0x18320000 +#define MSM_DMOV_ADM0_SIZE SZ_1M + +#define MSM_DMOV_ADM1_BASE IOMEM(0xFA500000) +#define MSM_DMOV_ADM1_PHYS 0x18420000 +#define MSM_DMOV_ADM1_SIZE SZ_1M + +#define MSM_SIC_NON_SECURE_BASE IOMEM(0xFA600000) +#define MSM_SIC_NON_SECURE_PHYS 0x12100000 +#define MSM_SIC_NON_SECURE_SIZE SZ_64K + +#define MSM_QFPROM_BASE IOMEM(0xFA700000) +#define MSM_QFPROM_PHYS 0x00700000 +#define MSM_QFPROM_SIZE SZ_4K + +#define MSM_TCSR_BASE IOMEM(0xFA701000) +#define MSM_TCSR_PHYS 0x16B00000 +#define MSM_TCSR_SIZE SZ_4K + +#define MSM_IMEM_BASE IOMEM(0xFA702000) +#define MSM_IMEM_PHYS 0x2A05F000 +#define MSM_IMEM_SIZE SZ_4K + +#define MSM_HDMI_BASE IOMEM(0xFA800000) +#define MSM_HDMI_PHYS 0x04A00000 +#define MSM_HDMI_SIZE SZ_4K + +#ifdef CONFIG_MSM_DEBUG_UART + +#define MSM_DEBUG_UART_BASE 0xFBC40000 +#define MSM_DEBUG_UART_PHYS 0x19C40000 +#define MSM_DEBUG_UART_SIZE SZ_4K + +#endif #endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap-fsm9xxx.h b/arch/arm/mach-msm/include/mach/msm_iomap-fsm9xxx.h new file mode 100644 index 00000000000..5261bcc7efd --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_iomap-fsm9xxx.h @@ -0,0 +1,112 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ASM_ARCH_MSM_IOMAP_FSM9XXX_H +#define __ASM_ARCH_MSM_IOMAP_FSM9XXX_H + +/* Physical base address and size of peripherals. + * Ordered by the virtual base addresses they will be mapped at. + * + * If you add or remove entries here, you'll want to edit the + * msm_io_desc array in arch/arm/mach-msm/io.c to reflect your + * changes. + * + */ + +#define MSM_VIC_BASE IOMEM(0xFA000000) +#define MSM_VIC_PHYS 0x9C080000 +#define MSM_VIC_SIZE SZ_4K + +#define MSM_SIRC_BASE IOMEM(0xFA001000) +#define MSM_SIRC_PHYS 0x94190000 +#define MSM_SIRC_SIZE SZ_4K + +#define MSM_CSR_BASE IOMEM(0xFA002000) +#define MSM_CSR_PHYS 0x9C000000 +#define MSM_CSR_SIZE SZ_4K + +#define MSM_TMR_BASE MSM_CSR_BASE + +#define MSM_TLMM_BASE IOMEM(0xFA003000) +#define MSM_TLMM_PHYS 0x94040000 +#define MSM_TLMM_SIZE SZ_4K + +#define MSM_TCSR_BASE IOMEM(0xFA004000) +#define MSM_TCSR_PHYS 0x94030000 +#define MSM_TCSR_SIZE SZ_4K + +#define MSM_CLK_CTL_BASE IOMEM(0xFA005000) +#define MSM_CLK_CTL_PHYS 0x94020000 +#define MSM_CLK_CTL_SIZE SZ_4K + +#define MSM_ACC_BASE IOMEM(0xFA006000) +#define MSM_ACC_PHYS 0x9C001000 +#define MSM_ACC_SIZE SZ_4K + +#define MSM_SAW_BASE IOMEM(0xFA007000) +#define MSM_SAW_PHYS 0x9C002000 +#define MSM_SAW_SIZE SZ_4K + +#define MSM_GCC_BASE IOMEM(0xFA008000) +#define MSM_GCC_PHYS 0x9C082000 +#define MSM_GCC_SIZE SZ_4K + +#define MSM_GRFC_BASE IOMEM(0xFA009000) +#define MSM_GRFC_PHYS 0x94038000 +#define MSM_GRFC_SIZE SZ_4K + +#define MSM_DMOV_SD0_BASE IOMEM(0xFA00A000) +#define MSM_DMOV_SD0_PHYS 0x94310000 +#define MSM_DMOV_SD0_SIZE SZ_4K + +#define MSM_DMOV_SD1_BASE IOMEM(0xFA00B000) +#define MSM_DMOV_SD1_PHYS 0x94410000 +#define MSM_DMOV_SD1_SIZE SZ_4K + +#define MSM_DMOV_SD2_BASE IOMEM(0xFA00C000) +#define MSM_DMOV_SD2_PHYS 0x94510000 +#define MSM_DMOV_SD2_SIZE SZ_4K + +#define MSM_DMOV_SD3_BASE IOMEM(0xFA00D000) +#define MSM_DMOV_SD3_PHYS 0x94610000 +#define MSM_DMOV_SD3_SIZE SZ_4K + +#define MSM_DMOV_BASE MSM_DMOV_SD0_BASE + +#define MSM_QFP_FUSE_BASE IOMEM(0xFA010000) +#define MSM_QFP_FUSE_PHYS 0x80000000 +#define MSM_QFP_FUSE_SIZE SZ_32K + +#define MSM_HH_BASE IOMEM(0xFA100000) +#define MSM_HH_PHYS 0x94200000 +#define MSM_HH_SIZE SZ_1M + +#define MSM_SHARED_RAM_BASE IOMEM(0xFA200000) +#define MSM_SHARED_RAM_SIZE SZ_1M + +#define MSM_UART1_PHYS 0x94000000 +#define MSM_UART1_SIZE SZ_4K + +#define MSM_UART2_PHYS 0x94100000 +#define MSM_UART2_SIZE SZ_4K + +#ifdef CONFIG_MSM_DEBUG_UART +#define MSM_DEBUG_UART_BASE 0xFB000000 +#if CONFIG_MSM_DEBUG_UART == 1 +#define MSM_DEBUG_UART_PHYS MSM_UART1_PHYS +#elif CONFIG_MSM_DEBUG_UART == 2 +#define MSM_DEBUG_UART_PHYS MSM_UART2_PHYS +#endif +#define MSM_DEBUG_UART_SIZE SZ_4K +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_iomap.h b/arch/arm/mach-msm/include/mach/msm_iomap.h index 2f494b6a9d0..b465fa5be48 100644 --- a/arch/arm/mach-msm/include/mach/msm_iomap.h +++ b/arch/arm/mach-msm/include/mach/msm_iomap.h @@ -43,23 +43,53 @@ #define IOMEM(x) ((void __force __iomem *)(x)) #endif +#if defined(CONFIG_ARCH_MSM8960) || defined(CONFIG_ARCH_APQ8064) +/* Unified iomap */ + +#define MSM_TMR_BASE IOMEM(0xFA000000) /* 4K */ +#define MSM_TMR0_BASE IOMEM(0xFA001000) /* 4K */ +#define MSM_QGIC_DIST_BASE IOMEM(0xFA00D000) /* 4K */ +#define MSM_QGIC_CPU_BASE IOMEM(0xFA00E000) /* 4K */ +#define MSM_RPM_BASE IOMEM(0xFA002000) /* 4K */ +#define MSM_RPM_MPM_BASE IOMEM(0xFA003000) /* 4K */ +#define MSM_TCSR_BASE IOMEM(0xFA004000) /* 4K */ +#define MSM_APCS_GCC_BASE IOMEM(0xFA006000) /* 4K */ +#define MSM_SAW_L2_BASE IOMEM(0xFA007000) /* 4K */ +#define MSM_SAW0_BASE IOMEM(0xFA008000) /* 4K */ +#define MSM_SAW1_BASE IOMEM(0xFA009000) /* 4K */ +#define MSM_IMEM_BASE IOMEM(0xFA00A000) /* 4K */ +#define MSM_ACC0_BASE IOMEM(0xFA00B000) /* 4K */ +#define MSM_ACC1_BASE IOMEM(0xFA00C000) /* 4K */ +#define MSM_CLK_CTL_BASE IOMEM(0xFA010000) /* 16K */ +#define MSM_MMSS_CLK_CTL_BASE IOMEM(0xFA014000) /* 4K */ +#define MSM_LPASS_CLK_CTL_BASE IOMEM(0xFA015000) /* 4K */ +#define MSM_HFPLL_BASE IOMEM(0xFA016000) /* 4K */ +#define MSM_TLMM_BASE IOMEM(0xFA017000) /* 16K */ +#define MSM_DMOV_BASE IOMEM(0xFA500000) /* 1M */ +#define MSM_SIC_NON_SECURE_BASE IOMEM(0xFA600000) /* 64K */ +#define MSM_HDMI_BASE IOMEM(0xFA800000) /* 4K */ + +#define MSM_SHARED_RAM_BASE IOMEM(0xFA300000) /* 2M */ +#define MSM_SHARED_RAM_SIZE SZ_2M + +#include "msm_iomap-8960.h" +#include "msm_iomap-8064.h" + +#else +/* Legacy single-target iomap */ + #if defined(CONFIG_ARCH_MSM7X30) #include "msm_iomap-7x30.h" #elif defined(CONFIG_ARCH_QSD8X50) #include "msm_iomap-8x50.h" #elif defined(CONFIG_ARCH_MSM8X60) #include "msm_iomap-8x60.h" +#elif defined(CONFIG_ARCH_FSM9XXX) +#include "msm_iomap-fsm9xxx.h" #else -#include "msm_iomap-7x00.h" +#include "msm_iomap-7xxx.h" #endif -#include "msm_iomap-8960.h" - -/* Virtual addresses shared across all MSM targets. */ -#define MSM_CSR_BASE IOMEM(0xE0001000) -#define MSM_QGIC_DIST_BASE IOMEM(0xF0000000) -#define MSM_QGIC_CPU_BASE IOMEM(0xF0001000) -#define MSM_TMR_BASE IOMEM(0xF0200000) -#define MSM_TMR0_BASE IOMEM(0xF0201000) +#endif #endif diff --git a/arch/arm/mach-msm/include/mach/msm_memtypes.h b/arch/arm/mach-msm/include/mach/msm_memtypes.h new file mode 100644 index 00000000000..23715feb38b --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_memtypes.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +/* The MSM Hardware supports multiple flavors of physical memory. + * This file captures hardware specific information of these types. +*/ + +#ifndef __ASM_ARCH_MSM_MEMTYPES_H +#define __ASM_ARCH_MSM_MEMTYPES_H + +#include +/* Redundant check to prevent this from being included outside of 7x30 */ +#if defined(CONFIG_ARCH_MSM7X30) +unsigned int get_num_populated_chipselects(void); +#endif + +#endif + +enum { + MEMTYPE_NONE = -1, + MEMTYPE_SMI_KERNEL = 0, + MEMTYPE_SMI, + MEMTYPE_EBI0, + MEMTYPE_EBI1, + MEMTYPE_MAX, +}; + +void msm_reserve(void); + +#define MEMTYPE_FLAGS_FIXED 0x1 +#define MEMTYPE_FLAGS_1M_ALIGN 0x2 + +struct memtype_reserve { + unsigned long start; + unsigned long size; + unsigned long limit; + int flags; +}; + +struct reserve_info { + struct memtype_reserve *memtype_reserve_table; + void (*calculate_reserve_sizes)(void); + int (*paddr_to_memtype)(unsigned int); +}; + +extern struct reserve_info *reserve_info; diff --git a/arch/arm/mach-msm/include/mach/msm_migrate_pages.h b/arch/arm/mach-msm/include/mach/msm_migrate_pages.h new file mode 100644 index 00000000000..5812a64fa04 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_migrate_pages.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MACH_MSM_MIGRATE_PAGES_H_ +#define _MACH_MSM_MIGRATE_PAGES_H_ + +unsigned long get_msm_migrate_pages_status(void); +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_otg.h b/arch/arm/mach-msm/include/mach/msm_otg.h new file mode 100644 index 00000000000..178b65a2afc --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_otg.h @@ -0,0 +1,78 @@ +/* Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_OTG_H +#define __ARCH_ARM_MACH_MSM_OTG_H + +#include +#include + +/* + * The otg driver needs to interact with both device side and host side + * usb controllers. it decides which controller is active at a given + * moment, using the transceiver, ID signal. + */ + +struct msm_otg_transceiver { + struct device *dev; + struct clk *clk; + struct clk *pclk; + int in_lpm; + struct msm_otg_ops *dcd_ops; + struct msm_otg_ops *hcd_ops; + int irq; + int flags; + int state; + int active; + void __iomem *regs; /* device memory/io */ + struct work_struct work; + spinlock_t lock; + struct wake_lock wlock; + + /* bind/unbind the host controller */ + int (*set_host)(struct msm_otg_transceiver *otg, + struct msm_otg_ops *hcd_ops); + + /* bind/unbind the peripheral controller */ + int (*set_peripheral)(struct msm_otg_transceiver *otg, + struct msm_otg_ops *dcd_ops); + int (*set_suspend)(struct msm_otg_transceiver *otg, + int suspend); + +}; + +struct msm_otg_ops { + void (*request)(void *, int); + void *handle; +}; + +/* for usb host and peripheral controller drivers */ +#ifdef CONFIG_USB_MSM_OTG + +extern struct msm_otg_transceiver *msm_otg_get_transceiver(void); +extern void msm_otg_put_transceiver(struct msm_otg_transceiver *xceiv); + +#else + +static inline struct msm_otg_transceiver *msm_otg_get_transceiver(void) +{ + return NULL; +} + +static inline void msm_otg_put_transceiver(struct msm_otg_transceiver *xceiv) +{ +} + +#endif /*CONFIG_USB_MSM_OTG*/ + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h b/arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h new file mode 100644 index 00000000000..da2abe2413a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h @@ -0,0 +1,120 @@ +/* arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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_MSM_QDSP6_Q6AUDIO_ +#define _MACH_MSM_QDSP6_Q6AUDIO_ + +#define AUDIO_FLAG_READ 0 +#define AUDIO_FLAG_WRITE 1 +#define AUDIO_FLAG_INCALL_MIXED 2 + +#include + +enum { + DEVICE_UNMUTE = 0, + DEVICE_MUTE, + STREAM_UNMUTE, + STREAM_MUTE, +}; + +struct audio_buffer { + dma_addr_t phys; + void *data; + uint32_t size; + uint32_t used; /* 1 = CPU is waiting for DSP to consume this buf */ + uint32_t actual_size; /* actual number of bytes read by DSP */ +}; + +struct audio_client { + struct audio_buffer buf[2]; + int cpu_buf; /* next buffer the CPU will touch */ + int dsp_buf; /* next buffer the DSP will touch */ + int running; + int session; + + wait_queue_head_t wait; + struct dal_client *client; + + int cb_status; + uint32_t flags; +}; + +/* Obtain a 16bit signed, interleaved audio channel of the specified + * rate (Hz) and channels (1 or 2), with two buffers of bufsz bytes. + */ +struct audio_client *q6audio_open_pcm(uint32_t bufsz, uint32_t rate, + uint32_t channels, uint32_t flags, + uint32_t acdb_id); + +struct audio_client *q6audio_open_auxpcm(uint32_t rate, uint32_t channels, + uint32_t flags, uint32_t acdb_id); + +struct audio_client *q6voice_open(uint32_t flags); + +struct audio_client *q6audio_open_mp3(uint32_t bufsz, uint32_t rate, + uint32_t channels, uint32_t acdb_id); + +struct audio_client *q6audio_open_dtmf(uint32_t rate, uint32_t channels, + uint32_t acdb_id); +int q6audio_play_dtmf(struct audio_client *ac, uint16_t dtmf_hi, + uint16_t dtmf_low, uint16_t duration, uint16_t rx_gain); + +struct audio_client *q6audio_open_aac(uint32_t bufsz, uint32_t samplerate, + uint32_t channels, uint32_t bitrate, + uint32_t stream_format, uint32_t flags, + uint32_t acdb_id); + +struct audio_client *q6audio_open_qcp(uint32_t bufsz, uint32_t min_rate, + uint32_t max_rate, uint32_t flags, + uint32_t format, uint32_t acdb_id); + +struct audio_client *q6audio_open_amrnb(uint32_t bufsz, uint32_t enc_mode, + uint32_t dtx_enable, uint32_t flags, + uint32_t acdb_id); + +int q6audio_close(struct audio_client *ac); +int q6audio_auxpcm_close(struct audio_client *ac); +int q6voice_close(struct audio_client *ac); +int q6audio_mp3_close(struct audio_client *ac); + +int q6audio_read(struct audio_client *ac, struct audio_buffer *ab); +int q6audio_write(struct audio_client *ac, struct audio_buffer *ab); +int q6audio_async(struct audio_client *ac); + +int q6audio_do_routing(uint32_t route, uint32_t acdb_id); +int q6audio_set_tx_mute(int mute); +int q6audio_reinit_acdb(char* filename); +int q6audio_update_acdb(uint32_t id_src, uint32_t id_dst); +int q6audio_set_rx_volume(int level); +int q6audio_set_stream_volume(struct audio_client *ac, int vol); +int q6audio_set_stream_eq_pcm(struct audio_client *ac, void *eq_config); + +struct q6audio_analog_ops { + void (*init)(void); + void (*speaker_enable)(int en); + void (*headset_enable)(int en); + void (*receiver_enable)(int en); + void (*bt_sco_enable)(int en); + void (*int_mic_enable)(int en); + void (*ext_mic_enable)(int en); +}; + +void q6audio_register_analog_ops(struct q6audio_analog_ops *ops); + +/* signal non-recoverable DSP error so we can log and/or panic */ +void q6audio_dsp_not_responding(void); + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_qdsp6_audiov2.h b/arch/arm/mach-msm/include/mach/msm_qdsp6_audiov2.h new file mode 100644 index 00000000000..90a6b568f31 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_qdsp6_audiov2.h @@ -0,0 +1,87 @@ +/* arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h + * + * Copyright (C) 2009 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Author: Brian Swetland + * + * 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_MSM_QDSP6_Q6AUDIO_ +#define _MACH_MSM_QDSP6_Q6AUDIO_ + +#define AUDIO_FLAG_READ 0 +#define AUDIO_FLAG_WRITE 1 + +extern char *audio_data; +extern int32_t audio_phys; +extern uint32_t tx_clk_freq; + +struct audio_buffer { + dma_addr_t phys; + void *data; + uint32_t size; + uint32_t used; /* 1 = CPU is waiting for DSP to consume this buf */ + uint32_t actual_size; /* actual number of bytes read by DSP */ +}; + +struct audio_client { + struct audio_buffer buf[2]; + int cpu_buf; /* next buffer the CPU will touch */ + int dsp_buf; /* next buffer the DSP will touch */ + int running; + int session; + + wait_queue_head_t wait; + struct dal_client *client; + + int cb_status; + uint32_t flags; +}; + +/* Obtain a 16bit signed, interleaved audio channel of the specified + * rate (Hz) and channels (1 or 2), with two buffers of bufsz bytes. + */ + +struct audio_client *q6voice_open(void); +int q6voice_setup(void); +int q6voice_teardown(void); +int q6voice_close(struct audio_client *ac); + + +struct audio_client *q6audio_open(uint32_t bufsz, uint32_t flags); +int q6audio_start(struct audio_client *ac, void *rpc, uint32_t len); + +int q6audio_close(struct audio_client *ac); +int q6audio_read(struct audio_client *ac, struct audio_buffer *ab); +int q6audio_write(struct audio_client *ac, struct audio_buffer *ab); +int q6audio_async(struct audio_client *ac); + +int q6audio_do_routing(uint32_t route); +int q6audio_set_tx_mute(int mute); +int q6audio_update_acdb(uint32_t id_src, uint32_t id_dst); +int q6audio_set_rx_volume(int level); +int q6audio_set_route(const char *name); + +struct q6audio_analog_ops { + void (*init)(void); + void (*speaker_enable)(int en); + void (*headset_enable)(int en); + void (*receiver_enable)(int en); + void (*bt_sco_enable)(int en); + void (*int_mic_enable)(int en); + void (*ext_mic_enable)(int en); +}; + +void q6audio_register_analog_ops(struct q6audio_analog_ops *ops); + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_reqs.h b/arch/arm/mach-msm/include/mach/msm_reqs.h new file mode 100644 index 00000000000..43fc0c5441e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_reqs.h @@ -0,0 +1,87 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_REQS_H +#define __ARCH_ARM_MACH_MSM_REQS_H + +#include + +enum system_bus_flow_ids { + MSM_AXI_FLOW_INVALID = 0, + MSM_AXI_FLOW_APPLICATION_LOW, + MSM_AXI_FLOW_APPLICATION_MED, + MSM_AXI_FLOW_APPLICATION_HI, + MSM_AXI_FLOW_APPLICATION_MAX, + MSM_AXI_FLOW_VIDEO_PLAYBACK_LOW, + MSM_AXI_FLOW_VIDEO_PLAYBACK_MED, + MSM_AXI_FLOW_VIDEO_PLAYBACK_HI, + MSM_AXI_FLOW_VIDEO_PLAYBACK_MAX, + MSM_AXI_FLOW_VIDEO_RECORD_LOW, + MSM_AXI_FLOW_VIDEO_RECORD_MED, + MSM_AXI_FLOW_VIDEO_RECORD_HI, + MSM_AXI_FLOW_GRAPHICS_LOW, + MSM_AXI_FLOW_GRAPHICS_MED, + MSM_AXI_FLOW_GRAPHICS_HI, + MSM_AXI_FLOW_VIEWFINDER_LOW, + MSM_AXI_FLOW_VIEWFINDER_MED, + MSM_AXI_FLOW_VIEWFINDER_HI, + MSM_AXI_FLOW_LAPTOP_DATA_CALL, + MSM_AXI_FLOW_APPLICATION_DATA_CALL, + MSM_AXI_FLOW_GPS, + MSM_AXI_FLOW_TV_OUT_LOW, + MSM_AXI_FLOW_TV_OUT_MED, + MSM_AXI_FLOW_ILCDC_WVGA, + MSM_AXI_FLOW_VOYAGER_DEFAULT, + MSM_AXI_FLOW_2D_GPU_HIGH, + MSM_AXI_FLOW_3D_GPU_HIGH, + MSM_AXI_FLOW_CAMERA_PREVIEW_HIGH, + MSM_AXI_FLOW_CAMERA_SNAPSHOT_12MP, + MSM_AXI_FLOW_CAMERA_RECORDING_720P, + MSM_AXI_FLOW_JPEG_12MP, + MSM_AXI_FLOW_MDP_LCDC_WVGA_2BPP, + MSM_AXI_FLOW_MDP_MDDI_WVGA_2BPP, + MSM_AXI_FLOW_MDP_DTV_720P_2BPP, + MSM_AXI_FLOW_VIDEO_RECORDING_720P, + MSM_AXI_FLOW_VIDEO_PLAYBACK_720P, + MSM_AXI_FLOW_MDP_TVENC_720P_2BPP, + MSM_AXI_FLOW_VIDEO_PLAYBACK_WVGA, + MSM_AXI_FLOW_VIDEO_PLAYBACK_QVGA, + MSM_AXI_FLOW_VIDEO_RECORDING_QVGA, + + MSM_AXI_NUM_FLOWS, +}; + +#define MSM_REQ_DEFAULT_VALUE 0 + +/** + * msm_req_add - Creates an NPA request and returns a handle. Non-blocking. + * @req_name: Name of the request + * @res_name: Name of the NPA resource the request is for + */ +void *msm_req_add(char *res_name, char *client_name); + +/** + * msm_req_update - Updates an existing NPA request. May block. + * @req: Request handle + * @value: Request value + */ +int msm_req_update(void *req, s32 value); + +/** + * msm_req_remove - Removes an existing NPA request. May block. + * @req: Request handle + */ +int msm_req_remove(void *req); + +#endif /* __ARCH_ARM_MACH_MSM_REQS_H */ + diff --git a/arch/arm/mach-msm/include/mach/msm_rotator_imem.h b/arch/arm/mach-msm/include/mach/msm_rotator_imem.h new file mode 100644 index 00000000000..580bc81d00a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_rotator_imem.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_ROTATOR_IMEM_H__ + +enum { + ROTATOR_REQUEST, + JPEG_REQUEST +}; + +/* Allocates imem for the requested owner. + Aquires a mutex, so DO NOT call from isr context */ +int msm_rotator_imem_allocate(int requestor); +/* Frees imem if currently owned by requestor. + Unlocks a mutex, so DO NOT call from isr context */ +void msm_rotator_imem_free(int requestor); + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_rpcrouter.h b/arch/arm/mach-msm/include/mach/msm_rpcrouter.h new file mode 100644 index 00000000000..88918eaf6fd --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_rpcrouter.h @@ -0,0 +1,376 @@ +/** include/asm-arm/arch-msm/msm_rpcrouter.h + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2011, Code Aurora Forum. All rights reserved. + * Author: San Mehat + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM__ARCH_MSM_RPCROUTER_H +#define __ASM__ARCH_MSM_RPCROUTER_H + +#include +#include +#include + +/* RPC API version structure + * Version bit 31 : 1->hashkey versioning, + * 0->major-minor (backward compatible) versioning + * hashkey versioning: + * Version bits 31-0 hashkey + * major-minor (backward compatible) versioning + * Version bits 30-28 reserved (no match) + * Version bits 27-16 major (must match) + * Version bits 15-0 minor (greater or equal) + */ +#define RPC_VERSION_MODE_MASK 0x80000000 +#define RPC_VERSION_MAJOR_MASK 0x0fff0000 +#define RPC_VERSION_MINOR_MASK 0x0000ffff + +/* callback ID for NULL callback function is -1 */ +#define MSM_RPC_CLIENT_NULL_CB_ID 0xffffffff + +struct msm_rpc_endpoint; + +struct rpcsvr_platform_device +{ + struct platform_device base; + uint32_t prog; + uint32_t vers; +}; + +#define RPC_DATA_IN 0 +/* + * Structures for sending / receiving direct RPC requests + * XXX: Any cred/verif lengths > 0 not supported + */ + +struct rpc_request_hdr +{ + uint32_t xid; + uint32_t type; /* 0 */ + uint32_t rpc_vers; /* 2 */ + uint32_t prog; + uint32_t vers; + uint32_t procedure; + uint32_t cred_flavor; + uint32_t cred_length; + uint32_t verf_flavor; + uint32_t verf_length; +}; + +typedef struct +{ + uint32_t low; + uint32_t high; +} rpc_reply_progmismatch_data; + +typedef struct +{ +} rpc_denied_reply_hdr; + +typedef struct +{ + uint32_t verf_flavor; + uint32_t verf_length; + uint32_t accept_stat; +#define RPC_ACCEPTSTAT_SUCCESS 0 +#define RPC_ACCEPTSTAT_PROG_UNAVAIL 1 +#define RPC_ACCEPTSTAT_PROG_MISMATCH 2 +#define RPC_ACCEPTSTAT_PROC_UNAVAIL 3 +#define RPC_ACCEPTSTAT_GARBAGE_ARGS 4 +#define RPC_ACCEPTSTAT_SYSTEM_ERR 5 +#define RPC_ACCEPTSTAT_PROG_LOCKED 6 + /* + * Following data is dependant on accept_stat + * If ACCEPTSTAT == PROG_MISMATCH then there is a + * 'rpc_reply_progmismatch_data' structure following the header. + * Otherwise the data is procedure specific + */ +} rpc_accepted_reply_hdr; + +struct rpc_reply_hdr +{ + uint32_t xid; + uint32_t type; + uint32_t reply_stat; +#define RPCMSG_REPLYSTAT_ACCEPTED 0 +#define RPCMSG_REPLYSTAT_DENIED 1 + union { + rpc_accepted_reply_hdr acc_hdr; + rpc_denied_reply_hdr dny_hdr; + } data; +}; + +struct rpc_board_dev { + uint32_t prog; + struct platform_device pdev; +}; + +/* flags for msm_rpc_connect() */ +#define MSM_RPC_UNINTERRUPTIBLE 0x0001 + +/* use IS_ERR() to check for failure */ +struct msm_rpc_endpoint *msm_rpc_open(void); +/* Connect with the specified server version */ +struct msm_rpc_endpoint *msm_rpc_connect(uint32_t prog, uint32_t vers, unsigned flags); +/* Connect with a compatible server version */ +struct msm_rpc_endpoint *msm_rpc_connect_compatible(uint32_t prog, + uint32_t vers, unsigned flags); +/* check if server version can handle client requested version */ +int msm_rpc_is_compatible_version(uint32_t server_version, + uint32_t client_version); + +int msm_rpc_close(struct msm_rpc_endpoint *ept); +int msm_rpc_write(struct msm_rpc_endpoint *ept, + void *data, int len); +int msm_rpc_read(struct msm_rpc_endpoint *ept, + void **data, unsigned len, long timeout); +void msm_rpc_read_wakeup(struct msm_rpc_endpoint *ept); +void msm_rpc_setup_req(struct rpc_request_hdr *hdr, + uint32_t prog, uint32_t vers, uint32_t proc); +int msm_rpc_register_server(struct msm_rpc_endpoint *ept, + uint32_t prog, uint32_t vers); +int msm_rpc_unregister_server(struct msm_rpc_endpoint *ept, + uint32_t prog, uint32_t vers); + +int msm_rpc_add_board_dev(struct rpc_board_dev *board_dev, int num); + +int msm_rpc_clear_netreset(struct msm_rpc_endpoint *ept); + +int msm_rpc_get_curr_pkt_size(struct msm_rpc_endpoint *ept); +/* simple blocking rpc call + * + * request is mandatory and must have a rpc_request_hdr + * at the start. The header will be filled out for you. + * + * reply provides a buffer for replies of reply_max_size + */ +int msm_rpc_call_reply(struct msm_rpc_endpoint *ept, uint32_t proc, + void *request, int request_size, + void *reply, int reply_max_size, + long timeout); +int msm_rpc_call(struct msm_rpc_endpoint *ept, uint32_t proc, + void *request, int request_size, + long timeout); + +struct msm_rpc_xdr { + void *in_buf; + uint32_t in_size; + uint32_t in_index; + wait_queue_head_t in_buf_wait_q; + + void *out_buf; + uint32_t out_size; + uint32_t out_index; + struct mutex out_lock; + + struct msm_rpc_endpoint *ept; +}; + +int xdr_send_int8(struct msm_rpc_xdr *xdr, const int8_t *value); +int xdr_send_uint8(struct msm_rpc_xdr *xdr, const uint8_t *value); +int xdr_send_int16(struct msm_rpc_xdr *xdr, const int16_t *value); +int xdr_send_uint16(struct msm_rpc_xdr *xdr, const uint16_t *value); +int xdr_send_int32(struct msm_rpc_xdr *xdr, const int32_t *value); +int xdr_send_uint32(struct msm_rpc_xdr *xdr, const uint32_t *value); +int xdr_send_bytes(struct msm_rpc_xdr *xdr, const void **data, uint32_t *size); + +int xdr_recv_int8(struct msm_rpc_xdr *xdr, int8_t *value); +int xdr_recv_uint8(struct msm_rpc_xdr *xdr, uint8_t *value); +int xdr_recv_int16(struct msm_rpc_xdr *xdr, int16_t *value); +int xdr_recv_uint16(struct msm_rpc_xdr *xdr, uint16_t *value); +int xdr_recv_int32(struct msm_rpc_xdr *xdr, int32_t *value); +int xdr_recv_uint32(struct msm_rpc_xdr *xdr, uint32_t *value); +int xdr_recv_bytes(struct msm_rpc_xdr *xdr, void **data, uint32_t *size); + +struct msm_rpc_server +{ + struct list_head list; + uint32_t flags; + + uint32_t prog; + uint32_t vers; + + struct mutex cb_req_lock; + + struct msm_rpc_endpoint *cb_ept; + + struct msm_rpc_xdr cb_xdr; + + uint32_t version; + + int (*rpc_call)(struct msm_rpc_server *server, + struct rpc_request_hdr *req, unsigned len); + + int (*rpc_call2)(struct msm_rpc_server *server, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr); +}; + +int msm_rpc_create_server(struct msm_rpc_server *server); +int msm_rpc_create_server2(struct msm_rpc_server *server); + +#define MSM_RPC_MSGSIZE_MAX 8192 + +struct msm_rpc_client; + +struct msm_rpc_client { + struct task_struct *read_thread; + struct task_struct *cb_thread; + + struct msm_rpc_endpoint *ept; + wait_queue_head_t reply_wait; + + uint32_t prog, ver; + + void *buf; + + struct msm_rpc_xdr xdr; + struct msm_rpc_xdr cb_xdr; + + uint32_t version; + + int (*cb_func)(struct msm_rpc_client *, void *, int); + int (*cb_func2)(struct msm_rpc_client *, struct rpc_request_hdr *req, + struct msm_rpc_xdr *); + void *cb_buf; + int cb_size; + + struct list_head cb_item_list; + struct mutex cb_item_list_lock; + + wait_queue_head_t cb_wait; + int cb_avail; + + atomic_t next_cb_id; + struct mutex cb_list_lock; + struct list_head cb_list; + + uint32_t exit_flag; + struct completion complete; + struct completion cb_complete; + + struct mutex req_lock; + + void (*cb_restart_teardown)(struct msm_rpc_client *client); + void (*cb_restart_setup)(struct msm_rpc_client *client); + int in_reset; +}; + +struct msm_rpc_client_info { + uint32_t pid; + uint32_t cid; + uint32_t prog; + uint32_t vers; +}; + + +int msm_rpc_client_in_reset(struct msm_rpc_client *client); + +struct msm_rpc_client *msm_rpc_register_client( + const char *name, + uint32_t prog, uint32_t ver, + uint32_t create_cb_thread, + int (*cb_func)(struct msm_rpc_client *, void *, int)); + +struct msm_rpc_client *msm_rpc_register_client2( + const char *name, + uint32_t prog, uint32_t ver, + uint32_t create_cb_thread, + int (*cb_func)(struct msm_rpc_client *, struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr)); + +int msm_rpc_unregister_client(struct msm_rpc_client *client); + +int msm_rpc_client_req(struct msm_rpc_client *client, uint32_t proc, + int (*arg_func)(struct msm_rpc_client *, + void *, void *), void *arg_data, + int (*result_func)(struct msm_rpc_client *, + void *, void *), void *result_data, + long timeout); + +int msm_rpc_client_req2(struct msm_rpc_client *client, uint32_t proc, + int (*arg_func)(struct msm_rpc_client *, + struct msm_rpc_xdr *, void *), + void *arg_data, + int (*result_func)(struct msm_rpc_client *, + struct msm_rpc_xdr *, void *), + void *result_data, + long timeout); + +int msm_rpc_register_reset_callbacks( + struct msm_rpc_client *client, + void (*teardown)(struct msm_rpc_client *client), + void (*setup)(struct msm_rpc_client *client) + ); + +void *msm_rpc_start_accepted_reply(struct msm_rpc_client *client, + uint32_t xid, uint32_t accept_status); + +int msm_rpc_send_accepted_reply(struct msm_rpc_client *client, uint32_t size); + +void *msm_rpc_server_start_accepted_reply(struct msm_rpc_server *server, + uint32_t xid, uint32_t accept_status); + +int msm_rpc_server_send_accepted_reply(struct msm_rpc_server *server, + uint32_t size); + +int msm_rpc_add_cb_func(struct msm_rpc_client *client, void *cb_func); + +void *msm_rpc_get_cb_func(struct msm_rpc_client *client, uint32_t cb_id); + +void msm_rpc_remove_cb_func(struct msm_rpc_client *client, void *cb_func); + +int msm_rpc_server_cb_req(struct msm_rpc_server *server, + struct msm_rpc_client_info *clnt_info, + uint32_t cb_proc, + int (*arg_func)(struct msm_rpc_server *server, + void *buf, void *data), + void *arg_data, + int (*ret_func)(struct msm_rpc_server *server, + void *buf, void *data), + void *ret_data, long timeout); + +int msm_rpc_server_cb_req2(struct msm_rpc_server *server, + struct msm_rpc_client_info *clnt_info, + uint32_t cb_proc, + int (*arg_func)(struct msm_rpc_server *server, + struct msm_rpc_xdr *xdr, void *data), + void *arg_data, + int (*ret_func)(struct msm_rpc_server *server, + struct msm_rpc_xdr *xdr, void *data), + void *ret_data, long timeout); + +void msm_rpc_server_get_requesting_client( + struct msm_rpc_client_info *clnt_info); + +int xdr_send_pointer(struct msm_rpc_xdr *xdr, void **obj, + uint32_t obj_size, void *xdr_op); + +int xdr_recv_pointer(struct msm_rpc_xdr *xdr, void **obj, + uint32_t obj_size, void *xdr_op); + +int xdr_send_array(struct msm_rpc_xdr *xdr, void **addr, uint32_t *size, + uint32_t maxsize, uint32_t elm_size, void *xdr_op); + +int xdr_recv_array(struct msm_rpc_xdr *xdr, void **addr, uint32_t *size, + uint32_t maxsize, uint32_t elm_size, void *xdr_op); + +int xdr_recv_req(struct msm_rpc_xdr *xdr, struct rpc_request_hdr *req); +int xdr_recv_reply(struct msm_rpc_xdr *xdr, struct rpc_reply_hdr *reply); +int xdr_start_request(struct msm_rpc_xdr *xdr, uint32_t prog, + uint32_t ver, uint32_t proc); +int xdr_start_accepted_reply(struct msm_rpc_xdr *xdr, uint32_t accept_status); +int xdr_send_msg(struct msm_rpc_xdr *xdr); + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_serial_debugger.h b/arch/arm/mach-msm/include/mach/msm_serial_debugger.h new file mode 100644 index 00000000000..f490b1be4f2 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_serial_debugger.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_MSM_SERIAL_DEBUGGER_H +#define __ASM_ARCH_MSM_SERIAL_DEBUGGER_H + +#if defined(CONFIG_MSM_SERIAL_DEBUGGER) +void msm_serial_debug_init(unsigned int base, int irq, + struct device *clk_device, int signal_irq, int wakeup_irq); +#else +static inline void msm_serial_debug_init(unsigned int base, int irq, + struct device *clk_device, int signal_irq, int wakeup_irq) {} +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_serial_hs.h b/arch/arm/mach-msm/include/mach/msm_serial_hs.h new file mode 100644 index 00000000000..b96640d1003 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_serial_hs.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Author: Nick Pelly + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ASM_ARCH_MSM_SERIAL_HS_H +#define __ASM_ARCH_MSM_SERIAL_HS_H + +#include + +/* Optional platform device data for msm_serial_hs driver. + * Used to configure low power wakeup */ +struct msm_serial_hs_platform_data { + int wakeup_irq; /* wakeup irq */ + /* bool: inject char into rx tty on wakeup */ + unsigned char inject_rx_on_wakeup; + char rx_to_inject; + int (*gpio_config)(int); +}; + +unsigned int msm_hs_tx_empty(struct uart_port *uport); +void msm_hs_request_clock_off(struct uart_port *uport); +void msm_hs_request_clock_on(struct uart_port *uport); +void msm_hs_set_mctrl(struct uart_port *uport, + unsigned int mctrl); +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_serial_hs_lite.h b/arch/arm/mach-msm/include/mach/msm_serial_hs_lite.h new file mode 100644 index 00000000000..e2bdaea4b4a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_serial_hs_lite.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_MSM_SERIAL_HS_LITE_H +#define __ASM_ARCH_MSM_SERIAL_HS_LITE_H + +struct msm_serial_hslite_platform_data { + unsigned config_gpio; + unsigned uart_tx_gpio; + unsigned uart_rx_gpio; +}; + +#endif + diff --git a/arch/arm/mach-msm/include/mach/msm_serial_pdata.h b/arch/arm/mach-msm/include/mach/msm_serial_pdata.h new file mode 100644 index 00000000000..4153cb2080e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_serial_pdata.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +#ifndef __ASM_ARCH_MSM_SERIAL_HS_H +#define __ASM_ARCH_MSM_SERIAL_HS_H + +#include + +/* Optional platform device data for msm_serial driver. + * Used to configure low power wakeup */ +struct msm_serial_platform_data { + int wakeup_irq; /* wakeup irq */ + /* bool: inject char into rx tty on wakeup */ + unsigned char inject_rx_on_wakeup; + char rx_to_inject; +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_smd.h b/arch/arm/mach-msm/include/mach/msm_smd.h index 029463ec875..5d7d21cade2 100644 --- a/arch/arm/mach-msm/include/mach/msm_smd.h +++ b/arch/arm/mach-msm/include/mach/msm_smd.h @@ -1,6 +1,7 @@ /* linux/include/asm-arm/arch-msm/msm_smd.h * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -19,7 +20,7 @@ typedef struct smd_channel smd_channel_t; -extern int (*msm_check_for_modem_crash)(void); +#define SMD_MAX_CH_NAME_LEN 20 /* includes null char at end */ /* warning: notify() may be called before open returns */ int smd_open(const char *name, smd_channel_t **ch, void *priv, @@ -28,11 +29,18 @@ int smd_open(const char *name, smd_channel_t **ch, void *priv, #define SMD_EVENT_DATA 1 #define SMD_EVENT_OPEN 2 #define SMD_EVENT_CLOSE 3 +#define SMD_EVENT_STATUS 4 +#define SMD_EVENT_REOPEN_READY 4 int smd_close(smd_channel_t *ch); /* passing a null pointer for data reads and discards */ int smd_read(smd_channel_t *ch, void *data, int len); +int smd_read_from_cb(smd_channel_t *ch, void *data, int len); +/* Same as smd_read() but takes a data buffer from userspace + * The function might sleep. Only safe to call from user context + */ +int smd_read_user_buffer(smd_channel_t *ch, void *data, int len); /* Write to stream channels may do a partial write and return ** the length actually written. @@ -40,7 +48,10 @@ int smd_read(smd_channel_t *ch, void *data, int len); ** it will return the requested length written or an error. */ int smd_write(smd_channel_t *ch, const void *data, int len); -int smd_write_atomic(smd_channel_t *ch, const void *data, int len); +/* Same as smd_write() but takes a data buffer from userspace + * The function might sleep. Only safe to call from user context + */ +int smd_write_user_buffer(smd_channel_t *ch, const void *data, int len); int smd_write_avail(smd_channel_t *ch); int smd_read_avail(smd_channel_t *ch); @@ -50,12 +61,6 @@ int smd_read_avail(smd_channel_t *ch); */ int smd_cur_packet_size(smd_channel_t *ch); -/* used for tty unthrottling and the like -- causes the notify() -** callback to be called from the same lock context as is used -** when it is called from channel updates -*/ -void smd_kick(smd_channel_t *ch); - #if 0 /* these are interruptable waits which will block you until the specified @@ -65,45 +70,88 @@ int smd_wait_until_readable(smd_channel_t *ch, int bytes); int smd_wait_until_writable(smd_channel_t *ch, int bytes); #endif -typedef enum { - SMD_PORT_DS = 0, - SMD_PORT_DIAG, - SMD_PORT_RPC_CALL, - SMD_PORT_RPC_REPLY, - SMD_PORT_BT, - SMD_PORT_CONTROL, - SMD_PORT_MEMCPY_SPARE1, - SMD_PORT_DATA1, - SMD_PORT_DATA2, - SMD_PORT_DATA3, - SMD_PORT_DATA4, - SMD_PORT_DATA5, - SMD_PORT_DATA6, - SMD_PORT_DATA7, - SMD_PORT_DATA8, - SMD_PORT_DATA9, - SMD_PORT_DATA10, - SMD_PORT_DATA11, - SMD_PORT_DATA12, - SMD_PORT_DATA13, - SMD_PORT_DATA14, - SMD_PORT_DATA15, - SMD_PORT_DATA16, - SMD_PORT_DATA17, - SMD_PORT_DATA18, - SMD_PORT_DATA19, - SMD_PORT_DATA20, - SMD_PORT_GPS_NMEA, - SMD_PORT_BRIDGE_1, - SMD_PORT_BRIDGE_2, - SMD_PORT_BRIDGE_3, - SMD_PORT_BRIDGE_4, - SMD_PORT_BRIDGE_5, - SMD_PORT_LOOPBACK, - SMD_PORT_CS_APPS_MODEM, - SMD_PORT_CS_APPS_DSP, - SMD_PORT_CS_MODEM_DSP, - SMD_NUM_PORTS, -} smd_port_id_type; +/* these are used to get and set the IF sigs of a channel. + * DTR and RTS can be set; DSR, CTS, CD and RI can be read. + */ +int smd_tiocmget(smd_channel_t *ch); +int smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear); + +enum { + SMD_APPS_MODEM = 0, + SMD_APPS_QDSP, + SMD_MODEM_QDSP, + SMD_APPS_DSPS, + SMD_MODEM_DSPS, + SMD_QDSP_DSPS, + SMD_APPS_WCNSS, + SMD_MODEM_WCNSS, + SMD_QDSP_WCNSS, + SMD_DSPS_WCNSS, + SMD_LOOPBACK_TYPE = 100, + +}; + +int smd_named_open_on_edge(const char *name, uint32_t edge, smd_channel_t **_ch, + void *priv, void (*notify)(void *, unsigned)); + +/* Tells the other end of the smd channel that this end wants to recieve + * interrupts when the written data is read. Read interrupts should only + * enabled when there is no space left in the buffer to write to, thus the + * interrupt acts as notification that space may be avaliable. If the + * other side does not support enabling/disabling interrupts on demand, + * then this function has no effect if called. + */ +void smd_enable_read_intr(smd_channel_t *ch); + +/* Tells the other end of the smd channel that this end does not want + * interrupts when written data is read. The interrupts should be + * disabled by default. If the other side does not support enabling/ + * disabling interrupts on demand, then this function has no effect if + * called. + */ +void smd_disable_read_intr(smd_channel_t *ch); + +/* Starts a packet transaction. The size of the packet may exceed the total + * size of the smd ring buffer. + * + * @ch: channel to write the packet to + * @len: total length of the packet + * + * Returns: + * 0 - success + * -ENODEV - invalid smd channel + * -EACCES - non-packet channel specified + * -EINVAL - invalid length + * -EBUSY - transaction already in progress + * -EAGAIN - no enough memory in ring buffer to start transaction + * -EPERM - unable to sucessfully start transaction due to write error + */ +int smd_write_start(smd_channel_t *ch, int len); + +/* Writes a segment of the packet for a packet transaction. + * + * @ch: channel to write packet to + * @data: buffer of data to write + * @len: length of data buffer + * @user_buf: (0) - buffer from kernelspace (1) - buffer from userspace + * + * Returns: + * number of bytes written + * -ENODEV - invalid smd channel + * -EINVAL - invalid length + * -ENOEXEC - transaction not started + */ +int smd_write_segment(smd_channel_t *ch, void *data, int len, int user_buf); + +/* Completes a packet transaction. Do not call from interrupt context. + * + * @ch: channel to complete transaction on + * + * Returns: + * 0 - success + * -ENODEV - invalid smd channel + * -E2BIG - some ammount of packet is not yet written + */ +int smd_write_end(smd_channel_t *ch); #endif diff --git a/arch/arm/mach-msm/include/mach/msm_smsm.h b/arch/arm/mach-msm/include/mach/msm_smsm.h new file mode 100644 index 00000000000..7d1defe92ee --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_smsm.h @@ -0,0 +1,219 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _ARCH_ARM_MACH_MSM_SMSM_H_ +#define _ARCH_ARM_MACH_MSM_SMSM_H_ + +#if defined(CONFIG_MSM_N_WAY_SMSM) +enum { + SMSM_APPS_STATE, + SMSM_MODEM_STATE, + SMSM_Q6_STATE, + SMSM_APPS_DEM, + SMSM_WCNSS_STATE = SMSM_APPS_DEM, + SMSM_MODEM_DEM, + SMSM_Q6_DEM, + SMSM_POWER_MASTER_DEM, + SMSM_TIME_MASTER_DEM, +}; +extern uint32_t SMSM_NUM_ENTRIES; +#else +enum { + SMSM_APPS_STATE = 1, + SMSM_MODEM_STATE = 3, + SMSM_NUM_ENTRIES, +}; +#endif + +enum { + SMSM_APPS, + SMSM_MODEM, + SMSM_Q6, + SMSM_WCNSS, + SMSM_DSPS, +}; +extern uint32_t SMSM_NUM_HOSTS; + +#define SMSM_INIT 0x00000001 +#define SMSM_OSENTERED 0x00000002 +#define SMSM_SMDWAIT 0x00000004 +#define SMSM_SMDINIT 0x00000008 +#define SMSM_RPCWAIT 0x00000010 +#define SMSM_RPCINIT 0x00000020 +#define SMSM_RESET 0x00000040 +#define SMSM_RSA 0x00000080 +#define SMSM_RUN 0x00000100 +#define SMSM_PWRC 0x00000200 +#define SMSM_TIMEWAIT 0x00000400 +#define SMSM_TIMEINIT 0x00000800 +#define SMSM_PWRC_EARLY_EXIT 0x00001000 +#define SMSM_WFPI 0x00002000 +#define SMSM_SLEEP 0x00004000 +#define SMSM_SLEEPEXIT 0x00008000 +#define SMSM_OEMSBL_RELEASE 0x00010000 +#define SMSM_APPS_REBOOT 0x00020000 +#define SMSM_SYSTEM_POWER_DOWN 0x00040000 +#define SMSM_SYSTEM_REBOOT 0x00080000 +#define SMSM_SYSTEM_DOWNLOAD 0x00100000 +#define SMSM_PWRC_SUSPEND 0x00200000 +#define SMSM_APPS_SHUTDOWN 0x00400000 +#define SMSM_SMD_LOOPBACK 0x00800000 +#define SMSM_RUN_QUIET 0x01000000 +#define SMSM_MODEM_WAIT 0x02000000 +#define SMSM_MODEM_BREAK 0x04000000 +#define SMSM_MODEM_CONTINUE 0x08000000 +#define SMSM_SYSTEM_REBOOT_USR 0x20000000 +#define SMSM_SYSTEM_PWRDWN_USR 0x40000000 +#define SMSM_UNKNOWN 0x80000000 + +#define SMSM_WKUP_REASON_RPC 0x00000001 +#define SMSM_WKUP_REASON_INT 0x00000002 +#define SMSM_WKUP_REASON_GPIO 0x00000004 +#define SMSM_WKUP_REASON_TIMER 0x00000008 +#define SMSM_WKUP_REASON_ALARM 0x00000010 +#define SMSM_WKUP_REASON_RESET 0x00000020 + +#define SMSM_A2_POWER_CONTROL 0x00000002 + +#define SMSM_WLAN_TX_RINGS_EMPTY 0x00000200 +#define SMSM_WLAN_TX_ENABLE 0x00000400 + + +void *smem_alloc(unsigned id, unsigned size); +void *smem_get_entry(unsigned id, unsigned *size); +int smsm_change_state(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask); +int smsm_change_intr_mask(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask); +int smsm_get_intr_mask(uint32_t smsm_entry, uint32_t *intr_mask); +uint32_t smsm_get_state(uint32_t smsm_entry); +int smsm_state_cb_register(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t old_state, uint32_t new_state), + void *data); +int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t, uint32_t), void *data); +void smsm_print_sleep_info(uint32_t sleep_delay, uint32_t sleep_limit, + uint32_t irq_mask, uint32_t wakeup_reason, uint32_t pending_irqs); +void smsm_reset_modem(unsigned mode); +void smsm_reset_modem_cont(void); +void smd_sleep_exit(void); + +#define SMEM_NUM_SMD_STREAM_CHANNELS 64 +#define SMEM_NUM_SMD_BLOCK_CHANNELS 64 + +enum { + /* fixed items */ + SMEM_PROC_COMM = 0, + SMEM_HEAP_INFO, + SMEM_ALLOCATION_TABLE, + SMEM_VERSION_INFO, + SMEM_HW_RESET_DETECT, + SMEM_AARM_WARM_BOOT, + SMEM_DIAG_ERR_MESSAGE, + SMEM_SPINLOCK_ARRAY, + SMEM_MEMORY_BARRIER_LOCATION, + SMEM_FIXED_ITEM_LAST = SMEM_MEMORY_BARRIER_LOCATION, + + /* dynamic items */ + SMEM_AARM_PARTITION_TABLE, + SMEM_AARM_BAD_BLOCK_TABLE, + SMEM_RESERVE_BAD_BLOCKS, + SMEM_WM_UUID, + SMEM_CHANNEL_ALLOC_TBL, + SMEM_SMD_BASE_ID, + SMEM_SMEM_LOG_IDX = SMEM_SMD_BASE_ID + SMEM_NUM_SMD_STREAM_CHANNELS, + SMEM_SMEM_LOG_EVENTS, + SMEM_SMEM_STATIC_LOG_IDX, + SMEM_SMEM_STATIC_LOG_EVENTS, + SMEM_SMEM_SLOW_CLOCK_SYNC, + SMEM_SMEM_SLOW_CLOCK_VALUE, + SMEM_BIO_LED_BUF, + SMEM_SMSM_SHARED_STATE, + SMEM_SMSM_INT_INFO, + SMEM_SMSM_SLEEP_DELAY, + SMEM_SMSM_LIMIT_SLEEP, + SMEM_SLEEP_POWER_COLLAPSE_DISABLED, + SMEM_KEYPAD_KEYS_PRESSED, + SMEM_KEYPAD_STATE_UPDATED, + SMEM_KEYPAD_STATE_IDX, + SMEM_GPIO_INT, + SMEM_MDDI_LCD_IDX, + SMEM_MDDI_HOST_DRIVER_STATE, + SMEM_MDDI_LCD_DISP_STATE, + SMEM_LCD_CUR_PANEL, + SMEM_MARM_BOOT_SEGMENT_INFO, + SMEM_AARM_BOOT_SEGMENT_INFO, + SMEM_SLEEP_STATIC, + SMEM_SCORPION_FREQUENCY, + SMEM_SMD_PROFILES, + SMEM_TSSC_BUSY, + SMEM_HS_SUSPEND_FILTER_INFO, + SMEM_BATT_INFO, + SMEM_APPS_BOOT_MODE, + SMEM_VERSION_FIRST, + SMEM_VERSION_SMD = SMEM_VERSION_FIRST, + SMEM_VERSION_LAST = SMEM_VERSION_FIRST + 24, + SMEM_OSS_RRCASN1_BUF1, + SMEM_OSS_RRCASN1_BUF2, + SMEM_ID_VENDOR0, + SMEM_ID_VENDOR1, + SMEM_ID_VENDOR2, + SMEM_HW_SW_BUILD_ID, + SMEM_SMD_BLOCK_PORT_BASE_ID, + SMEM_SMD_BLOCK_PORT_PROC0_HEAP = SMEM_SMD_BLOCK_PORT_BASE_ID + + SMEM_NUM_SMD_BLOCK_CHANNELS, + SMEM_SMD_BLOCK_PORT_PROC1_HEAP = SMEM_SMD_BLOCK_PORT_PROC0_HEAP + + SMEM_NUM_SMD_BLOCK_CHANNELS, + SMEM_I2C_MUTEX = SMEM_SMD_BLOCK_PORT_PROC1_HEAP + + SMEM_NUM_SMD_BLOCK_CHANNELS, + SMEM_SCLK_CONVERSION, + SMEM_SMD_SMSM_INTR_MUX, + SMEM_SMSM_CPU_INTR_MASK, + SMEM_APPS_DEM_SLAVE_DATA, + SMEM_QDSP6_DEM_SLAVE_DATA, + SMEM_CLKREGIM_BSP, + SMEM_CLKREGIM_SOURCES, + SMEM_SMD_FIFO_BASE_ID, + SMEM_USABLE_RAM_PARTITION_TABLE = SMEM_SMD_FIFO_BASE_ID + + SMEM_NUM_SMD_STREAM_CHANNELS, + SMEM_POWER_ON_STATUS_INFO, + SMEM_DAL_AREA, + SMEM_SMEM_LOG_POWER_IDX, + SMEM_SMEM_LOG_POWER_WRAP, + SMEM_SMEM_LOG_POWER_EVENTS, + SMEM_ERR_CRASH_LOG, + SMEM_ERR_F3_TRACE_LOG, + SMEM_SMD_BRIDGE_ALLOC_TABLE, + SMEM_SMDLITE_TABLE, + SMEM_SD_IMG_UPGRADE_STATUS, + SMEM_SEFS_INFO, + SMEM_RESET_LOG, + SMEM_RESET_LOG_SYMBOLS, + SMEM_MODEM_SW_BUILD_ID, + SMEM_SMEM_LOG_MPROC_WRAP, + SMEM_BOOT_INFO_FOR_APPS, + SMEM_SMSM_SIZE_INFO, + SMEM_MEM_LAST = SMEM_SMSM_SIZE_INFO, + SMEM_NUM_ITEMS, +}; + +enum { + SMEM_APPS_Q6_SMSM = 3, + SMEM_Q6_APPS_SMSM = 5, + SMSM_NUM_INTR_MUX = 8, +}; + +int smsm_check_for_modem_crash(void); +void *smem_find(unsigned id, unsigned size); +void *smem_get_entry(unsigned id, unsigned *size); + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_spi.h b/arch/arm/mach-msm/include/mach/msm_spi.h new file mode 100644 index 00000000000..51081b67610 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_spi.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * SPI driver for Qualcomm MSM platforms. + */ + +struct msm_spi_platform_data { + u32 max_clock_speed; + int (*gpio_config)(void); + void (*gpio_release)(void); + int (*dma_config)(void); + const char *rsl_id; + uint32_t pm_lat; +}; diff --git a/arch/arm/mach-msm/include/mach/msm_sps.h b/arch/arm/mach-msm/include/mach/msm_sps.h new file mode 100644 index 00000000000..3af6f71acdc --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_sps.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MSM_SPS_H_ +#define _MSM_SPS_H_ + +/** + * struct msm_sps_platform_data - SPS Platform specific data. + * @bamdma_restricted_pipes - Bitmask of pipes restricted from local use. + * + */ +struct msm_sps_platform_data { + u32 bamdma_restricted_pipes; +}; + +#endif /* _MSM_SPS_H_ */ + diff --git a/arch/arm/mach-msm/include/mach/msm_subsystem_map.h b/arch/arm/mach-msm/include/mach/msm_subsystem_map.h new file mode 100644 index 00000000000..34bfec901d6 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_subsystem_map.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __ARCH_MACH_MSM_SUBSYSTEM_MAP_H +#define __ARCH_MACH_MSM_SUBSYSTEM_MAP_H + +#include +#include + +/* map the physical address in the kernel vaddr space */ +#define MSM_SUBSYSTEM_MAP_KADDR 0x1 +/* map the physical address in the iova address space */ +#define MSM_SUBSYSTEM_MAP_IOVA 0x2 +/* ioremaps in the kernel address space are cached */ +#define MSM_SUBSYSTEM_MAP_CACHED 0x4 +/* ioremaps in the kernel address space are uncached */ +#define MSM_SUBSYSTEM_MAP_UNCACHED 0x8 + +#define MSM_SUBSYSTEM_VIDEO VCODEC_A_SUBSYS_ID +#define MSM_SUBSYSTEM_VIDEO_FWARE VIDEO_FWARE_ID +#define MSM_SUBSYSTEM_CAMERA VPE_SUBSYS_ID +#define MSM_SUBSYSTEM_DISPLAY MDP0_SUBSYS_ID +#define MSM_SUBSYSTEM_ROTATOR ROT_SUBSYS_ID + +struct msm_mapped_buffer { + /* + * VA mapped in the kernel address space. This field shall be NULL if + * MSM_SUBSYSTEM_MAP_KADDR was not passed to the map buffer function. + */ + void *vaddr; + /* + * iovas mapped in the iommu address space. The ith entry of this array + * corresponds to the iova mapped in the ith subsystem in the array + * pased in to msm_subsystem_map_buffer. This field shall be NULL if + * MSM_SUBSYSTEM_MAP_IOVA was not passed to the map buffer function, + */ + unsigned long *iova; +}; + +extern struct msm_mapped_buffer *msm_subsystem_map_buffer( + unsigned long phys, + unsigned int length, + unsigned int flags, + int *subsys_ids, + unsigned int nsubsys); + +extern int msm_subsystem_unmap_buffer(struct msm_mapped_buffer *buf); + +extern phys_addr_t msm_subsystem_check_iova_mapping(int subsys_id, + unsigned long iova); + +#endif /* __ARCH_MACH_MSM_SUBSYSTEM_MAP_H */ diff --git a/arch/arm/mach-msm/include/mach/msm_touch.h b/arch/arm/mach-msm/include/mach/msm_touch.h new file mode 100644 index 00000000000..763d6a8f111 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_touch.h @@ -0,0 +1,26 @@ +/* arch/arm/mach-msm/include/mach/msm_touch.h + * + * Platform data for MSM touchscreen driver. + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MACH_MSM_TOUCH_H_ +#define _MACH_MSM_TOUCH_H_ + +struct msm_ts_platform_data { + unsigned int x_max; + unsigned int y_max; + unsigned int pressure_max; +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/msm_touchpad.h b/arch/arm/mach-msm/include/mach/msm_touchpad.h new file mode 100644 index 00000000000..4b2d537abd6 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_touchpad.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Touchpad driver for QSD platform. + */ + +struct msm_touchpad_platform_data { + int gpioirq; + int gpiosuspend; + int (*gpio_setup) (void); + void (*gpio_shutdown)(void); +}; diff --git a/arch/arm/mach-msm/include/mach/msm_tsif.h b/arch/arm/mach-msm/include/mach/msm_tsif.h new file mode 100644 index 00000000000..62595e3ef9d --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_tsif.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2009, 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _MSM_TSIF_H_ +#define _MSM_TSIF_H_ + +struct msm_tsif_platform_data { + int num_gpios; + const struct msm_gpio *gpios; + const char *tsif_clk; + const char *tsif_pclk; + const char *tsif_ref_clk; + void (*init)(struct msm_tsif_platform_data *); +}; + +#endif /* _MSM_TSIF_H_ */ + diff --git a/arch/arm/mach-msm/include/mach/msm_xo.h b/arch/arm/mach-msm/include/mach/msm_xo.h new file mode 100644 index 00000000000..30c3272ea48 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/msm_xo.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __MACH_MSM_XO_H +#define __MACH_MSM_XO_H + +enum msm_xo_ids { + MSM_XO_TCXO_D0, + MSM_XO_TCXO_D1, + MSM_XO_TCXO_A0, + MSM_XO_TCXO_A1, + MSM_XO_TCXO_A2, + MSM_XO_CORE, + MSM_XO_PXO, + NUM_MSM_XO_IDS +}; + +enum msm_xo_modes { + MSM_XO_MODE_OFF, + MSM_XO_MODE_PIN_CTRL, + MSM_XO_MODE_ON, + NUM_MSM_XO_MODES +}; + +struct msm_xo_voter; + +#ifdef CONFIG_MSM_XO +struct msm_xo_voter *msm_xo_get(enum msm_xo_ids xo_id, const char *voter); +void msm_xo_put(struct msm_xo_voter *xo_voter); +int msm_xo_mode_vote(struct msm_xo_voter *xo_voter, enum msm_xo_modes xo_mode); +int __init msm_xo_init(void); +#else +static inline struct msm_xo_voter *msm_xo_get(enum msm_xo_ids xo_id, + const char *voter) +{ + return NULL; +} + +static inline void msm_xo_put(struct msm_xo_voter *xo_voter) { } + +static inline int msm_xo_mode_vote(struct msm_xo_voter *xo_voter, + enum msm_xo_modes xo_mode) +{ + return 0; +} +static inline int msm_xo_init(void) { return 0; } +#endif /* CONFIG_MSM_XO */ + +#endif diff --git a/arch/arm/mach-msm/include/mach/oem_rapi_client.h b/arch/arm/mach-msm/include/mach/oem_rapi_client.h new file mode 100644 index 00000000000..d7a2416fff8 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/oem_rapi_client.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM__ARCH_OEM_RAPI_CLIENT_H +#define __ASM__ARCH_OEM_RAPI_CLIENT_H + +/* + * OEM RAPI CLIENT Driver header file + */ + +#include +#include + +enum { + OEM_RAPI_CLIENT_EVENT_NONE = 0, + + /* + * list of oem rapi client events + */ + + OEM_RAPI_CLIENT_EVENT_MAX + +}; + +struct oem_rapi_client_streaming_func_cb_arg { + uint32_t event; + void *handle; + uint32_t in_len; + char *input; + uint32_t out_len_valid; + uint32_t output_valid; + uint32_t output_size; +}; + +struct oem_rapi_client_streaming_func_cb_ret { + uint32_t *out_len; + char *output; +}; + +struct oem_rapi_client_streaming_func_arg { + uint32_t event; + int (*cb_func)(struct oem_rapi_client_streaming_func_cb_arg *, + struct oem_rapi_client_streaming_func_cb_ret *); + void *handle; + uint32_t in_len; + char *input; + uint32_t out_len_valid; + uint32_t output_valid; + uint32_t output_size; +}; + +struct oem_rapi_client_streaming_func_ret { + uint32_t *out_len; + char *output; +}; + +int oem_rapi_client_streaming_function( + struct msm_rpc_client *client, + struct oem_rapi_client_streaming_func_arg *arg, + struct oem_rapi_client_streaming_func_ret *ret); + +int oem_rapi_client_close(void); + +struct msm_rpc_client *oem_rapi_client_init(void); + +#endif diff --git a/arch/arm/mach-msm/include/mach/peripheral-loader.h b/arch/arm/mach-msm/include/mach/peripheral-loader.h new file mode 100644 index 00000000000..327c82f8fd5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/peripheral-loader.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __MACH_PERIPHERAL_LOADER_H +#define __MACH_PERIPHERAL_LOADER_H + +#ifdef CONFIG_MSM_PIL +extern void *pil_get(const char *name); +extern void pil_put(void *peripheral_handle); +extern void pil_force_shutdown(const char *name); +extern int pil_force_boot(const char *name); +#else +static inline void *pil_get(const char *name) { return NULL; } +static inline void pil_put(void *peripheral_handle) { } +static inline void pil_force_shutdown(const char *name) { } +static inline int pil_force_boot(const char *name) { return -ENOSYS; } +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/pmic.h b/arch/arm/mach-msm/include/mach/pmic.h new file mode 100644 index 00000000000..b143a59b2af --- /dev/null +++ b/arch/arm/mach-msm/include/mach/pmic.h @@ -0,0 +1,748 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_PMIC_H +#define __ARCH_ARM_MACH_PMIC_H + +#include + +enum spkr_ldo_v_sel { + VOLT_LEVEL_1_1V, + VOLT_LEVEL_1_2V, + VOLT_LEVEL_2_0V, +}; + +enum hp_spkr_left_right { + LEFT_HP_SPKR, + RIGHT_HP_SPKR, +}; + +enum spkr_left_right { + LEFT_SPKR, + RIGHT_SPKR, +}; + +enum spkr_gain { + SPKR_GAIN_MINUS16DB, /* -16 db */ + SPKR_GAIN_MINUS12DB, /* -12 db */ + SPKR_GAIN_MINUS08DB, /* -08 db */ + SPKR_GAIN_MINUS04DB, /* -04 db */ + SPKR_GAIN_00DB, /* 00 db */ + SPKR_GAIN_PLUS04DB, /* +04 db */ + SPKR_GAIN_PLUS08DB, /* +08 db */ + SPKR_GAIN_PLUS12DB, /* +12 db */ +}; + +enum spkr_dly { + SPKR_DLY_10MS, /* ~10 ms delay */ + SPKR_DLY_100MS, /* ~100 ms delay */ +}; + +enum spkr_hpf_corner_freq { + SPKR_FREQ_1_39KHZ, /* 1.39 kHz */ + SPKR_FREQ_0_64KHZ, /* 0.64 kHz */ + SPKR_FREQ_0_86KHZ, /* 0.86 kHz */ + SPKR_FREQ_0_51KHZ, /* 0.51 kHz */ + SPKR_FREQ_1_06KHZ, /* 1.06 kHz */ + SPKR_FREQ_0_57KHZ, /* 0.57 kHz */ + SPKR_FREQ_0_73KHZ, /* 0.73 kHz */ + SPKR_FREQ_0_47KHZ, /* 0.47 kHz */ + SPKR_FREQ_1_20KHZ, /* 1.20 kHz */ + SPKR_FREQ_0_60KHZ, /* 0.60 kHz */ + SPKR_FREQ_0_76KHZ, /* 0.76 kHz */ + SPKR_FREQ_0_49KHZ, /* 0.49 kHz */ + SPKR_FREQ_0_95KHZ, /* 0.95 kHz */ + SPKR_FREQ_0_54KHZ, /* 0.54 kHz */ + SPKR_FREQ_0_68KHZ, /* 0.68 kHz */ + SPKR_FREQ_0_45KHZ, /* 0.45 kHz */ +}; + +/* Turn the speaker on or off and enables or disables mute.*/ +enum spkr_cmd { + SPKR_DISABLE, /* Enable Speaker */ + SPKR_ENABLE, /* Disable Speaker */ + SPKR_MUTE_OFF, /* turn speaker mute off, SOUND ON */ + SPKR_MUTE_ON, /* turn speaker mute on, SOUND OFF */ + SPKR_OFF, /* turn speaker OFF (speaker disable and mute on) */ + SPKR_ON, /* turn speaker ON (speaker enable and mute off) */ + SPKR_SET_FREQ_CMD, /* set speaker frequency */ + SPKR_GET_FREQ_CMD, /* get speaker frequency */ + SPKR_SET_GAIN_CMD, /* set speaker gain */ + SPKR_GET_GAIN_CMD, /* get speaker gain */ + SPKR_SET_DELAY_CMD, /* set speaker delay */ + SPKR_GET_DELAY_CMD, /* get speaker delay */ + SPKR_SET_PDM_MODE, + SPKR_SET_PWM_MODE, +}; + +struct spkr_config_mode { + uint32_t is_right_chan_en; + uint32_t is_left_chan_en; + uint32_t is_right_left_chan_added; + uint32_t is_stereo_en; + uint32_t is_usb_with_hpf_20hz; + uint32_t is_mux_bypassed; + uint32_t is_hpf_en; + uint32_t is_sink_curr_from_ref_volt_cir_en; +}; + +enum mic_volt { + MIC_VOLT_2_00V, /* 2.00 V */ + MIC_VOLT_1_93V, /* 1.93 V */ + MIC_VOLT_1_80V, /* 1.80 V */ + MIC_VOLT_1_73V, /* 1.73 V */ +}; + +enum ledtype { + LED_LCD, + LED_KEYPAD, +}; + +enum flash_led_mode { + FLASH_LED_MODE__MANUAL, + FLASH_LED_MODE__DBUS1, + FLASH_LED_MODE__DBUS2, + FLASH_LED_MODE__DBUS3, +}; + +enum flash_led_pol { + FLASH_LED_POL__ACTIVE_HIGH, + FLASH_LED_POL__ACTIVE_LOW, +}; + +enum switch_cmd { + OFF_CMD, + ON_CMD +}; + +enum vreg_lp_id { + PM_VREG_LP_MSMA_ID, + PM_VREG_LP_MSMP_ID, + PM_VREG_LP_MSME1_ID, + PM_VREG_LP_GP3_ID, + PM_VREG_LP_MSMC_ID, + PM_VREG_LP_MSME2_ID, + PM_VREG_LP_GP4_ID, + PM_VREG_LP_GP1_ID, + PM_VREG_LP_RFTX_ID, + PM_VREG_LP_RFRX1_ID, + PM_VREG_LP_RFRX2_ID, + PM_VREG_LP_WLAN_ID, + PM_VREG_LP_MMC_ID, + PM_VREG_LP_RUIM_ID, + PM_VREG_LP_MSMC0_ID, + PM_VREG_LP_GP2_ID, + PM_VREG_LP_GP5_ID, + PM_VREG_LP_GP6_ID, + PM_VREG_LP_MPLL_ID, + PM_VREG_LP_RFUBM_ID, + PM_VREG_LP_RFA_ID, + PM_VREG_LP_CDC2_ID, + PM_VREG_LP_RFTX2_ID, + PM_VREG_LP_USIM_ID, + PM_VREG_LP_USB2P6_ID, + PM_VREG_LP_TCXO_ID, + PM_VREG_LP_USB3P3_ID, + + PM_VREG_LP_MSME_ID = PM_VREG_LP_MSME1_ID, + /* backward compatible enums only */ + PM_VREG_LP_CAM_ID = PM_VREG_LP_GP1_ID, + PM_VREG_LP_MDDI_ID = PM_VREG_LP_GP2_ID, + PM_VREG_LP_RUIM2_ID = PM_VREG_LP_GP3_ID, + PM_VREG_LP_AUX_ID = PM_VREG_LP_GP4_ID, + PM_VREG_LP_AUX2_ID = PM_VREG_LP_GP5_ID, + PM_VREG_LP_BT_ID = PM_VREG_LP_GP6_ID, + PM_VREG_LP_MSMC_LDO_ID = PM_VREG_LP_MSMC_ID, + PM_VREG_LP_MSME1_LDO_ID = PM_VREG_LP_MSME1_ID, + PM_VREG_LP_MSME2_LDO_ID = PM_VREG_LP_MSME2_ID, + PM_VREG_LP_RFA1_ID = PM_VREG_LP_RFRX2_ID, + PM_VREG_LP_RFA2_ID = PM_VREG_LP_RFTX2_ID, + PM_VREG_LP_XO_ID = PM_VREG_LP_TCXO_ID +}; + +enum vreg_id { + PM_VREG_MSMA_ID = 0, + PM_VREG_MSMP_ID, + PM_VREG_MSME1_ID, + PM_VREG_MSMC1_ID, + PM_VREG_MSMC2_ID, + PM_VREG_GP3_ID, + PM_VREG_MSME2_ID, + PM_VREG_GP4_ID, + PM_VREG_GP1_ID, + PM_VREG_TCXO_ID, + PM_VREG_PA_ID, + PM_VREG_RFTX_ID, + PM_VREG_RFRX1_ID, + PM_VREG_RFRX2_ID, + PM_VREG_SYNT_ID, + PM_VREG_WLAN_ID, + PM_VREG_USB_ID, + PM_VREG_BOOST_ID, + PM_VREG_MMC_ID, + PM_VREG_RUIM_ID, + PM_VREG_MSMC0_ID, + PM_VREG_GP2_ID, + PM_VREG_GP5_ID, + PM_VREG_GP6_ID, + PM_VREG_RF_ID, + PM_VREG_RF_VCO_ID, + PM_VREG_MPLL_ID, + PM_VREG_S2_ID, + PM_VREG_S3_ID, + PM_VREG_RFUBM_ID, + PM_VREG_NCP_ID, + PM_VREG_RF2_ID, + PM_VREG_RFA_ID, + PM_VREG_CDC2_ID, + PM_VREG_RFTX2_ID, + PM_VREG_USIM_ID, + PM_VREG_USB2P6_ID, + PM_VREG_USB3P3_ID, + PM_VREG_EXTCDC1_ID, + PM_VREG_EXTCDC2_ID, + + /* backward compatible enums only */ + PM_VREG_MSME_ID = PM_VREG_MSME1_ID, + PM_VREG_MSME_BUCK_SMPS_ID = PM_VREG_MSME1_ID, + PM_VREG_MSME1_LDO_ID = PM_VREG_MSME1_ID, + PM_VREG_MSMC_ID = PM_VREG_MSMC1_ID, + PM_VREG_MSMC_LDO_ID = PM_VREG_MSMC1_ID, + PM_VREG_MSMC1_BUCK_SMPS_ID = PM_VREG_MSMC1_ID, + PM_VREG_MSME2_LDO_ID = PM_VREG_MSME2_ID, + PM_VREG_CAM_ID = PM_VREG_GP1_ID, + PM_VREG_MDDI_ID = PM_VREG_GP2_ID, + PM_VREG_RUIM2_ID = PM_VREG_GP3_ID, + PM_VREG_AUX_ID = PM_VREG_GP4_ID, + PM_VREG_AUX2_ID = PM_VREG_GP5_ID, + PM_VREG_BT_ID = PM_VREG_GP6_ID, + PM_VREG_RF1_ID = PM_VREG_RF_ID, + PM_VREG_S1_ID = PM_VREG_RF1_ID, + PM_VREG_5V_ID = PM_VREG_BOOST_ID, + PM_VREG_RFA1_ID = PM_VREG_RFRX2_ID, + PM_VREG_RFA2_ID = PM_VREG_RFTX2_ID, + PM_VREG_XO_ID = PM_VREG_TCXO_ID +}; + +enum vreg_pdown_id { + PM_VREG_PDOWN_MSMA_ID, + PM_VREG_PDOWN_MSMP_ID, + PM_VREG_PDOWN_MSME1_ID, + PM_VREG_PDOWN_MSMC1_ID, + PM_VREG_PDOWN_MSMC2_ID, + PM_VREG_PDOWN_GP3_ID, + PM_VREG_PDOWN_MSME2_ID, + PM_VREG_PDOWN_GP4_ID, + PM_VREG_PDOWN_GP1_ID, + PM_VREG_PDOWN_TCXO_ID, + PM_VREG_PDOWN_PA_ID, + PM_VREG_PDOWN_RFTX_ID, + PM_VREG_PDOWN_RFRX1_ID, + PM_VREG_PDOWN_RFRX2_ID, + PM_VREG_PDOWN_SYNT_ID, + PM_VREG_PDOWN_WLAN_ID, + PM_VREG_PDOWN_USB_ID, + PM_VREG_PDOWN_MMC_ID, + PM_VREG_PDOWN_RUIM_ID, + PM_VREG_PDOWN_MSMC0_ID, + PM_VREG_PDOWN_GP2_ID, + PM_VREG_PDOWN_GP5_ID, + PM_VREG_PDOWN_GP6_ID, + PM_VREG_PDOWN_RF_ID, + PM_VREG_PDOWN_RF_VCO_ID, + PM_VREG_PDOWN_MPLL_ID, + PM_VREG_PDOWN_S2_ID, + PM_VREG_PDOWN_S3_ID, + PM_VREG_PDOWN_RFUBM_ID, + /* new for HAN */ + PM_VREG_PDOWN_RF1_ID, + PM_VREG_PDOWN_RF2_ID, + PM_VREG_PDOWN_RFA_ID, + PM_VREG_PDOWN_CDC2_ID, + PM_VREG_PDOWN_RFTX2_ID, + PM_VREG_PDOWN_USIM_ID, + PM_VREG_PDOWN_USB2P6_ID, + PM_VREG_PDOWN_USB3P3_ID, + + /* backward compatible enums only */ + PM_VREG_PDOWN_CAM_ID = PM_VREG_PDOWN_GP1_ID, + PM_VREG_PDOWN_MDDI_ID = PM_VREG_PDOWN_GP2_ID, + PM_VREG_PDOWN_RUIM2_ID = PM_VREG_PDOWN_GP3_ID, + PM_VREG_PDOWN_AUX_ID = PM_VREG_PDOWN_GP4_ID, + PM_VREG_PDOWN_AUX2_ID = PM_VREG_PDOWN_GP5_ID, + PM_VREG_PDOWN_BT_ID = PM_VREG_PDOWN_GP6_ID, + PM_VREG_PDOWN_MSME_ID = PM_VREG_PDOWN_MSME1_ID, + PM_VREG_PDOWN_MSMC_ID = PM_VREG_PDOWN_MSMC1_ID, + PM_VREG_PDOWN_RFA1_ID = PM_VREG_PDOWN_RFRX2_ID, + PM_VREG_PDOWN_RFA2_ID = PM_VREG_PDOWN_RFTX2_ID, + PM_VREG_PDOWN_XO_ID = PM_VREG_PDOWN_TCXO_ID +}; + +enum mpp_which { + PM_MPP_1, + PM_MPP_2, + PM_MPP_3, + PM_MPP_4, + PM_MPP_5, + PM_MPP_6, + PM_MPP_7, + PM_MPP_8, + PM_MPP_9, + PM_MPP_10, + PM_MPP_11, + PM_MPP_12, + PM_MPP_13, + PM_MPP_14, + PM_MPP_15, + PM_MPP_16, + PM_MPP_17, + PM_MPP_18, + PM_MPP_19, + PM_MPP_20, + PM_MPP_21, + PM_MPP_22, + + PM_NUM_MPP_HAN = PM_MPP_4 + 1, + PM_NUM_MPP_KIP = PM_MPP_4 + 1, + PM_NUM_MPP_EPIC = PM_MPP_4 + 1, + PM_NUM_MPP_PM7500 = PM_MPP_22 + 1, + PM_NUM_MPP_PM6650 = PM_MPP_12 + 1, + PM_NUM_MPP_PM6658 = PM_MPP_12 + 1, + PM_NUM_MPP_PANORAMIX = PM_MPP_2 + 1, + PM_NUM_MPP_PM6640 = PM_NUM_MPP_PANORAMIX, + PM_NUM_MPP_PM6620 = PM_NUM_MPP_PANORAMIX +}; + +enum mpp_dlogic_level { + PM_MPP__DLOGIC__LVL_MSME, + PM_MPP__DLOGIC__LVL_MSMP, + PM_MPP__DLOGIC__LVL_RUIM, + PM_MPP__DLOGIC__LVL_MMC, + PM_MPP__DLOGIC__LVL_VDD, +}; + +enum mpp_dlogic_in_dbus { + PM_MPP__DLOGIC_IN__DBUS_NONE, + PM_MPP__DLOGIC_IN__DBUS1, + PM_MPP__DLOGIC_IN__DBUS2, + PM_MPP__DLOGIC_IN__DBUS3, +}; + +enum mpp_dlogic_out_ctrl { + PM_MPP__DLOGIC_OUT__CTRL_LOW, + PM_MPP__DLOGIC_OUT__CTRL_HIGH, + PM_MPP__DLOGIC_OUT__CTRL_MPP, + PM_MPP__DLOGIC_OUT__CTRL_NOT_MPP, +}; + +enum mpp_i_sink_level { + PM_MPP__I_SINK__LEVEL_5mA, + PM_MPP__I_SINK__LEVEL_10mA, + PM_MPP__I_SINK__LEVEL_15mA, + PM_MPP__I_SINK__LEVEL_20mA, + PM_MPP__I_SINK__LEVEL_25mA, + PM_MPP__I_SINK__LEVEL_30mA, + PM_MPP__I_SINK__LEVEL_35mA, + PM_MPP__I_SINK__LEVEL_40mA, +}; + +enum mpp_i_sink_switch { + PM_MPP__I_SINK__SWITCH_DIS, + PM_MPP__I_SINK__SWITCH_ENA, + PM_MPP__I_SINK__SWITCH_ENA_IF_MPP_HIGH, + PM_MPP__I_SINK__SWITCH_ENA_IF_MPP_LOW, +}; + +enum pm_vib_mot_mode { + PM_VIB_MOT_MODE__MANUAL, + PM_VIB_MOT_MODE__DBUS1, + PM_VIB_MOT_MODE__DBUS2, + PM_VIB_MOT_MODE__DBUS3, +}; + +enum pm_vib_mot_pol { + PM_VIB_MOT_POL__ACTIVE_HIGH, + PM_VIB_MOT_POL__ACTIVE_LOW, +}; + +struct rtc_time { + uint sec; +}; + +enum rtc_alarm { + PM_RTC_ALARM_1, +}; + +enum hsed_controller { + PM_HSED_CONTROLLER_0, + PM_HSED_CONTROLLER_1, + PM_HSED_CONTROLLER_2, +}; + +enum hsed_switch { + PM_HSED_SC_SWITCH_TYPE, + PM_HSED_OC_SWITCH_TYPE, +}; + +enum hsed_enable { + PM_HSED_ENABLE_OFF, + PM_HSED_ENABLE_TCXO, + PM_HSED_ENABLE_PWM_TCXO, + PM_HSED_ENABLE_ALWAYS, +}; + +enum hsed_hyst_pre_div { + PM_HSED_HYST_PRE_DIV_1, + PM_HSED_HYST_PRE_DIV_2, + PM_HSED_HYST_PRE_DIV_4, + PM_HSED_HYST_PRE_DIV_8, + PM_HSED_HYST_PRE_DIV_16, + PM_HSED_HYST_PRE_DIV_32, + PM_HSED_HYST_PRE_DIV_64, + PM_HSED_HYST_PRE_DIV_128, +}; + +enum hsed_hyst_time { + PM_HSED_HYST_TIME_1_CLK_CYCLES, + PM_HSED_HYST_TIME_2_CLK_CYCLES, + PM_HSED_HYST_TIME_3_CLK_CYCLES, + PM_HSED_HYST_TIME_4_CLK_CYCLES, + PM_HSED_HYST_TIME_5_CLK_CYCLES, + PM_HSED_HYST_TIME_6_CLK_CYCLES, + PM_HSED_HYST_TIME_7_CLK_CYCLES, + PM_HSED_HYST_TIME_8_CLK_CYCLES, + PM_HSED_HYST_TIME_9_CLK_CYCLES, + PM_HSED_HYST_TIME_10_CLK_CYCLES, + PM_HSED_HYST_TIME_11_CLK_CYCLES, + PM_HSED_HYST_TIME_12_CLK_CYCLES, + PM_HSED_HYST_TIME_13_CLK_CYCLES, + PM_HSED_HYST_TIME_14_CLK_CYCLES, + PM_HSED_HYST_TIME_15_CLK_CYCLES, + PM_HSED_HYST_TIME_16_CLK_CYCLES, +}; + +enum hsed_period_pre_div { + PM_HSED_PERIOD_PRE_DIV_2, + PM_HSED_PERIOD_PRE_DIV_4, + PM_HSED_PERIOD_PRE_DIV_8, + PM_HSED_PERIOD_PRE_DIV_16, + PM_HSED_PERIOD_PRE_DIV_32, + PM_HSED_PERIOD_PRE_DIV_64, + PM_HSED_PERIOD_PRE_DIV_128, + PM_HSED_PERIOD_PRE_DIV_256, +}; + +enum hsed_period_time { + PM_HSED_PERIOD_TIME_1_CLK_CYCLES, + PM_HSED_PERIOD_TIME_2_CLK_CYCLES, + PM_HSED_PERIOD_TIME_3_CLK_CYCLES, + PM_HSED_PERIOD_TIME_4_CLK_CYCLES, + PM_HSED_PERIOD_TIME_5_CLK_CYCLES, + PM_HSED_PERIOD_TIME_6_CLK_CYCLES, + PM_HSED_PERIOD_TIME_7_CLK_CYCLES, + PM_HSED_PERIOD_TIME_8_CLK_CYCLES, + PM_HSED_PERIOD_TIME_9_CLK_CYCLES, + PM_HSED_PERIOD_TIME_10_CLK_CYCLES, + PM_HSED_PERIOD_TIME_11_CLK_CYCLES, + PM_HSED_PERIOD_TIME_12_CLK_CYCLES, + PM_HSED_PERIOD_TIME_13_CLK_CYCLES, + PM_HSED_PERIOD_TIME_14_CLK_CYCLES, + PM_HSED_PERIOD_TIME_15_CLK_CYCLES, + PM_HSED_PERIOD_TIME_16_CLK_CYCLES, +}; + +enum vreg_lpm_id { + VREG_GP1_ID, + VREG_GP2_ID, + VREG_GP3_ID, + VREG_GP4_ID, + VREG_GP5_ID, + VREG_GP6_ID, + VREG_GP7_ID, + VREG_GP8_ID, + VREG_GP9_ID, + VREG_GP10_ID, + VREG_GP11_ID, + VREG_GP12_ID, + VREG_GP13_ID, + VREG_GP14_ID, + VREG_GP15_ID, + VREG_GP16_ID, + VREG_GP17_ID, + VREG_MDDI_ID, + VREG_MPLL_ID, + VREG_MSMC1_ID, + VREG_MSMC2_ID, + VREG_MSME_ID, + VREG_RF_ID, + VREG_RF1_ID, + VREG_RF2_ID, + VREG_RFA_ID, + VREG_SDCC1_ID, + VREG_TCXO_ID, + VREG_USB1P8_ID, + VREG_USB3P3_ID, + VREG_USIM_ID, + VREG_WLAN1_ID, + VREG_WLAN2_ID, + VREG_XO_OUT_D0_ID, + VREG_NCP_ID, + VREG_LVSW0_ID, + VREG_LVSW1_ID, +}; + +enum low_current_led { + LOW_CURRENT_LED_DRV0, + LOW_CURRENT_LED_DRV1, + LOW_CURRENT_LED_DRV2, +}; + +enum ext_signal { + EXT_SIGNAL_CURRENT_SINK_MANUAL_MODE, + EXT_SIGNAL_CURRENT_SINK_PWM1, + EXT_SIGNAL_CURRENT_SINK_PWM2, + EXT_SIGNAL_CURRENT_SINK_PWM3, + EXT_SIGNAL_CURRENT_SINK_DTEST1, + EXT_SIGNAL_CURRENT_SINK_DTEST2, + EXT_SIGNAL_CURRENT_SINK_DTEST3, + EXT_SIGNAL_CURRENT_SINK_DTEST4, +}; + +enum high_current_led { + HIGH_CURRENT_LED_FLASH_DRV0, + HIGH_CURRENT_LED_FLASH_DRV1, + HIGH_CURRENT_LED_KBD_DRV, +}; + +/* PMIC GPIO */ +enum pmic_gpio { + PMIC_GPIO_1, + PMIC_GPIO_2, + PMIC_GPIO_3, + PMIC_GPIO_4, + PMIC_GPIO_5, + PMIC_GPIO_6, + PMIC_GPIO_7, + PMIC_GPIO_8, + PMIC_GPIO_9, + PMIC_GPIO_10, + PMIC_GPIO_11, +}; + +enum pmic_voltage_src { + PMIC_GPIO_VIN0, + PMIC_GPIO_VIN1, + PMIC_GPIO_VIN2, + PMIC_GPIO_VIN3, + PMIC_GPIO_VIN4, + PMIC_GPIO_VIN5, + PMIC_GPIO_VIN6, + PMIC_GPIO_VIN7, +}; + +enum pmic_io_mode { + INPUT_ON, + INPUT_OUTPUT_ON, + OUTPUT_ON, + INPUT_OUTPUT_OFF, +}; + +enum pmic_current_pull_up { + PULL_UP_30uA, + PULL_UP_1_5uA, + PULL_UP_31_5uA, + PULL_UP_1_5uA_PLUS_30uA_BOOST, + PULL_DOWN_10uA, + PULL_NO_PULL, +}; + +enum pmic_op_buf_drv_strength { + BUFFER_OFF, + BUFFER_HIGH, + BUFFER_MEDIUM, + BUFFER_LOW, +}; + +enum pmic_output_buffer_config { + CONFIG_CMOS, + CONFIG_OPEN_DRAIN, +}; + +enum pmic_dtest_buf_onoff { + DTEST_DISABLE, + DTEST_ENABLE, +}; + +enum pmic_ext_pin_config { + EXT_PIN_ENABLE, + /*! Puts EXT_PIN at high Z state & disables the block */ + EXT_PIN_DISABLE, +}; + +enum pmic_source_config { + SOURCE_GND, + SOURCE_PAIRED_GPIO, + SOURCE_SPECIAL_FUNCTION1, + SOURCE_SPECIAL_FUNCTION2, + SOURCE_DTEST1, + SOURCE_DTEST2, + SOURCE_DTEST3, + SOURCE_DTEST4, +}; + +enum pmic_direction_mode { + MODE_INPUT, + MODE_OTPUT_AND_INPUT_ON, + MODE_OUTPUT, + MODE_INPUT_AND_OUTPUT_OFF, +}; + +struct pm8xxx_gpio_rpc_cfg { + enum pmic_gpio gpio; + bool config_gpio; + enum pmic_voltage_src volt_src; + bool mode_on; + enum pmic_io_mode mode; + enum pmic_output_buffer_config buf_config; + bool invert_ext_pin; + enum pmic_current_pull_up src_pull; + enum pmic_op_buf_drv_strength drv_strength; + enum pmic_dtest_buf_onoff dtest_on; + enum pmic_ext_pin_config ext_config; + enum pmic_source_config src_config; + bool int_polarity; +}; + +int pmic_lp_mode_control(enum switch_cmd cmd, enum vreg_lp_id id); +int pmic_vreg_set_level(enum vreg_id vreg, int level); +int pmic_vreg_pull_down_switch(enum switch_cmd cmd, enum vreg_pdown_id id); +int pmic_secure_mpp_control_digital_output(enum mpp_which which, + enum mpp_dlogic_level level, enum mpp_dlogic_out_ctrl out); +int pmic_secure_mpp_config_i_sink(enum mpp_which which, + enum mpp_i_sink_level level, enum mpp_i_sink_switch onoff); +int pmic_secure_mpp_config_digital_input(enum mpp_which which, + enum mpp_dlogic_level level, enum mpp_dlogic_in_dbus dbus); +int pmic_rtc_start(struct rtc_time *time); +int pmic_rtc_stop(void); +int pmic_rtc_get_time(struct rtc_time *time); +int pmic_rtc_enable_alarm(enum rtc_alarm alarm, + struct rtc_time *time); +int pmic_rtc_disable_alarm(enum rtc_alarm alarm); +int pmic_rtc_get_alarm_time(enum rtc_alarm alarm, + struct rtc_time *time); +int pmic_rtc_get_alarm_status(uint *status); +int pmic_rtc_set_time_adjust(uint adjust); +int pmic_rtc_get_time_adjust(uint *adjust); +int pmic_speaker_cmd(const enum spkr_cmd cmd); +int pmic_set_spkr_configuration(struct spkr_config_mode *cfg); +int pmic_get_spkr_configuration(struct spkr_config_mode *cfg); +int pmic_spkr_en_right_chan(uint enable); +int pmic_spkr_is_right_chan_en(uint *enabled); +int pmic_spkr_en_left_chan(uint enable); +int pmic_spkr_is_left_chan_en(uint *enabled); +int pmic_spkr_en(enum spkr_left_right left_right, uint enabled); +int pmic_spkr_is_en(enum spkr_left_right left_right, uint *enabled); +int pmic_spkr_set_gain(enum spkr_left_right left_right, enum spkr_gain gain); +int pmic_spkr_get_gain(enum spkr_left_right left_right, enum spkr_gain *gain); +int pmic_set_speaker_gain(enum spkr_gain gain); +int pmic_set_speaker_delay(enum spkr_dly delay); +int pmic_speaker_1k6_zin_enable(uint enable); +int pmic_spkr_set_mux_hpf_corner_freq(enum spkr_hpf_corner_freq freq); +int pmic_spkr_get_mux_hpf_corner_freq(enum spkr_hpf_corner_freq *freq); +int pmic_spkr_select_usb_with_hpf_20hz(uint enable); +int pmic_spkr_is_usb_with_hpf_20hz(uint *enabled); +int pmic_spkr_bypass_mux(uint enable); +int pmic_spkr_is_mux_bypassed(uint *enabled); +int pmic_spkr_en_hpf(uint enable); +int pmic_spkr_is_hpf_en(uint *enabled); +int pmic_spkr_en_sink_curr_from_ref_volt_cir(uint enable); +int pmic_spkr_is_sink_curr_from_ref_volt_cir_en(uint *enabled); +int pmic_spkr_set_delay(enum spkr_left_right left_right, enum spkr_dly delay); +int pmic_spkr_get_delay(enum spkr_left_right left_right, enum spkr_dly *delay); +int pmic_spkr_en_mute(enum spkr_left_right left_right, uint enabled); +int pmic_spkr_is_mute_en(enum spkr_left_right left_right, uint *enabled); +int pmic_mic_en(uint enable); +int pmic_mic_is_en(uint *enabled); +int pmic_mic_set_volt(enum mic_volt vol); +int pmic_mic_get_volt(enum mic_volt *voltage); +int pmic_set_led_intensity(enum ledtype type, int level); +int pmic_flash_led_set_current(uint16_t milliamps); +int pmic_flash_led_set_mode(enum flash_led_mode mode); +int pmic_flash_led_set_polarity(enum flash_led_pol pol); +int pmic_spkr_add_right_left_chan(uint enable); +int pmic_spkr_is_right_left_chan_added(uint *enabled); +int pmic_spkr_en_stereo(uint enable); +int pmic_spkr_is_stereo_en(uint *enabled); +int pmic_vib_mot_set_volt(uint vol); +int pmic_vib_mot_set_mode(enum pm_vib_mot_mode mode); +int pmic_vib_mot_set_polarity(enum pm_vib_mot_pol pol); +int pmic_vid_en(uint enable); +int pmic_vid_is_en(uint *enabled); +int pmic_vid_load_detect_en(uint enable); + +int pmic_hsed_set_period( + enum hsed_controller controller, + enum hsed_period_pre_div period_pre_div, + enum hsed_period_time period_time +); + +int pmic_hsed_set_hysteresis( + enum hsed_controller controller, + enum hsed_hyst_pre_div hyst_pre_div, + enum hsed_hyst_time hyst_time +); + +int pmic_hsed_set_current_threshold( + enum hsed_controller controller, + enum hsed_switch switch_hsed, + uint32_t current_threshold +); + +int pmic_hsed_enable( + enum hsed_controller controller, + enum hsed_enable enable +); + +int pmic_high_current_led_set_current(enum high_current_led led, + uint16_t milliamps); +int pmic_high_current_led_set_polarity(enum high_current_led led, + enum flash_led_pol polarity); +int pmic_high_current_led_set_mode(enum high_current_led led, + enum flash_led_mode mode); +int pmic_lp_force_lpm_control(enum switch_cmd cmd, + enum vreg_lpm_id vreg); +int pmic_low_current_led_set_ext_signal(enum low_current_led led, + enum ext_signal sig); +int pmic_low_current_led_set_current(enum low_current_led led, + uint16_t milliamps); + +int pmic_spkr_set_vsel_ldo(enum spkr_left_right left_right, + enum spkr_ldo_v_sel vlt_cntrl); +int pmic_spkr_set_boost(enum spkr_left_right left_right, uint enable); +int pmic_spkr_bypass_en(enum spkr_left_right left_right, uint enable); +int pmic_hp_spkr_mstr_en(enum hp_spkr_left_right left_right, uint enable); +int pmic_hp_spkr_mute_en(enum hp_spkr_left_right left_right, uint enable); +int pmic_hp_spkr_prm_in_en(enum hp_spkr_left_right left_right, uint enable); +int pmic_hp_spkr_aux_in_en(enum hp_spkr_left_right left_right, uint enable); +int pmic_hp_spkr_ctrl_prm_gain_input(enum hp_spkr_left_right left_right, + uint prm_gain_ctl); +int pmic_hp_spkr_ctrl_aux_gain_input(enum hp_spkr_left_right left_right, + uint aux_gain_ctl); +int pmic_xo_core_force_enable(uint enable); +int pmic_gpio_direction_input(unsigned gpio); +int pmic_gpio_direction_output(unsigned gpio); +int pmic_gpio_set_value(unsigned gpio, int value); +int pmic_gpio_get_value(unsigned gpio); +int pmic_gpio_get_direction(unsigned gpio); +int pmic_gpio_config(struct pm8xxx_gpio_rpc_cfg *); +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audplaycmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audplaycmdi.h new file mode 100644 index 00000000000..575a286288a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audplaycmdi.h @@ -0,0 +1,129 @@ +#ifndef QDSP5AUDPLAYCMDI_H +#define QDSP5AUDPLAYCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + Q D S P 5 A U D I O P L A Y T A S K C O M M A N D S + +GENERAL DESCRIPTION + Command Interface for AUDPLAYTASK on QDSP5 + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + + audplay_cmd_dec_data_avail + Send buffer to AUDPLAY task + + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/Audio/drivers/QDSP5Driver/QDSP5Interface/main/latest/qdsp5audplaycmdi.h#2 $ + +===========================================================================*/ + +#define AUDPLAY_CMD_BITSTREAM_DATA_AVAIL 0x0000 +#define AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_LEN \ + sizeof(audplay_cmd_bitstream_data_avail) + +/* Type specification of dec_data_avail message sent to AUDPLAYTASK +*/ +typedef struct { + /*command ID*/ + unsigned int cmd_id; + + /* Decoder ID for which message is being sent */ + unsigned int decoder_id; + + /* Start address of data in ARM global memory */ + unsigned int buf_ptr; + + /* Number of 16-bit words of bit-stream data contiguously available at the + * above-mentioned address + */ + unsigned int buf_size; + + /* Partition number used by audPlayTask to communicate with DSP's RTOS + * kernel + */ + unsigned int partition_number; + +} __attribute__((packed)) audplay_cmd_bitstream_data_avail; + +#define AUDPLAY_CMD_HPCM_BUF_CFG 0x0003 +#define AUDPLAY_CMD_HPCM_BUF_CFG_LEN \ + sizeof(struct audplay_cmd_hpcm_buf_cfg) + +struct audplay_cmd_hpcm_buf_cfg { + unsigned int cmd_id; + unsigned int hostpcm_config; + unsigned int feedback_frequency; + unsigned int byte_swap; + unsigned int max_buffers; + unsigned int partition_number; +} __attribute__((packed)); + +#define AUDPLAY_CMD_BUFFER_REFRESH 0x0004 +#define AUDPLAY_CMD_BUFFER_REFRESH_LEN \ + sizeof(struct audplay_cmd_buffer_update) + +struct audplay_cmd_buffer_refresh { + unsigned int cmd_id; + unsigned int num_buffers; + unsigned int buf_read_count; + unsigned int buf0_address; + unsigned int buf0_length; + unsigned int buf1_address; + unsigned int buf1_length; +} __attribute__((packed)); + +#define AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2 0x0005 +#define AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2_LEN \ + sizeof(audplay_cmd_bitstream_data_avail_nt2) + +/* Type specification of dec_data_avail message sent to AUDPLAYTASK + * for NT2 */ +struct audplay_cmd_bitstream_data_avail_nt2 { + /*command ID*/ + unsigned int cmd_id; + + /* Decoder ID for which message is being sent */ + unsigned int decoder_id; + + /* Start address of data in ARM global memory */ + unsigned int buf_ptr; + + /* Number of 16-bit words of bit-stream data contiguously available at the + * above-mentioned address + */ + unsigned int buf_size; + + /* Partition number used by audPlayTask to communicate with DSP's RTOS + * kernel + */ + unsigned int partition_number; + + /* bitstream write pointer */ + unsigned int dspBitstreamWritePtr; + +} __attribute__((packed)); + +#endif /* QDSP5AUDPLAYCMD_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audplaymsg.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audplaymsg.h new file mode 100644 index 00000000000..0bf2468f480 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audplaymsg.h @@ -0,0 +1,84 @@ +#ifndef QDSP5AUDPLAYMSG_H +#define QDSP5AUDPLAYMSG_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + Q D S P 5 A U D I O P L A Y T A S K M S G + +GENERAL DESCRIPTION + Message sent by AUDPLAY task + +REFERENCES + None + + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/Audio/drivers/QDSP5Driver/QDSP5Interface/main/latest/qdsp5audplaymsg.h#3 $ + +===========================================================================*/ +#define AUDPLAY_MSG_DEC_NEEDS_DATA 0x0001 +#define AUDPLAY_MSG_DEC_NEEDS_DATA_MSG_LEN \ + sizeof(audplay_msg_dec_needs_data) + +typedef struct{ + /* reserved*/ + unsigned int dec_id; + + /*The read pointer offset of external memory till which bitstream + has been dme’d in*/ + unsigned int adecDataReadPtrOffset; + + /* The buffer size of external memory. */ + unsigned int adecDataBufSize; + + unsigned int bitstream_free_len; + unsigned int bitstream_write_ptr; + unsigned int bitstarem_buf_start; + unsigned int bitstream_buf_len; +} __attribute__((packed)) audplay_msg_dec_needs_data; + +#define AUDPLAY_UP_STREAM_INFO 0x0003 +#define AUDPLAY_UP_STREAM_INFO_LEN \ + sizeof(struct audplay_msg_stream_info) + +struct audplay_msg_stream_info { + unsigned int decoder_id; + unsigned int channel_info; + unsigned int sample_freq; + unsigned int bitstream_info; + unsigned int bit_rate; +} __attribute__((packed)); + +#define AUDPLAY_MSG_BUFFER_UPDATE 0x0004 +#define AUDPLAY_MSG_BUFFER_UPDATE_LEN \ + sizeof(struct audplay_msg_buffer_update) + +struct audplay_msg_buffer_update { + unsigned int buffer_write_count; + unsigned int num_of_buffer; + unsigned int buf0_address; + unsigned int buf0_length; + unsigned int buf1_address; + unsigned int buf1_length; +} __attribute__((packed)); + +#define ADSP_MESSAGE_ID 0xFFFF +#endif /* QDSP5AUDPLAYMSG_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audppcmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audppcmdi.h new file mode 100644 index 00000000000..c5807746fa2 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audppcmdi.h @@ -0,0 +1,1004 @@ +#ifndef QDSP5AUDPPCMDI_H +#define QDSP5AUDPPCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + A U D I O P O S T P R O C E S S I N G I N T E R N A L C O M M A N D S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by AUDPP Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/Audio/drivers/QDSP5Driver/QDSP5Interface/main/latest/qdsp5audppcmdi.h#2 $ + +===========================================================================*/ + +/* + * ARM to AUDPPTASK Commands + * + * ARM uses three command queues to communicate with AUDPPTASK + * 1)uPAudPPCmd1Queue : Used for more frequent and shorter length commands + * Location : MEMA + * Buffer Size : 6 words + * No of buffers in a queue : 20 for gaming audio and 5 for other images + * 2)uPAudPPCmd2Queue : Used for commands which are not much lengthier + * Location : MEMA + * Buffer Size : 23 + * No of buffers in a queue : 2 + * 3)uPAudOOCmd3Queue : Used for lengthier and more frequent commands + * Location : MEMA + * Buffer Size : 145 + * No of buffers in a queue : 3 + */ + +/* + * Commands Related to uPAudPPCmd1Queue + */ + +/* + * Command Structure to enable or disable the active decoders + */ + +#define AUDPP_CMD_CFG_DEC_TYPE 0x0001 +#define AUDPP_CMD_CFG_DEC_TYPE_LEN sizeof(audpp_cmd_cfg_dec_type) + +/* Enable the decoder */ +#define AUDPP_CMD_DEC_TYPE_M 0x000F + +#define AUDPP_CMD_ENA_DEC_V 0x4000 +#define AUDPP_CMD_DIS_DEC_V 0x0000 +#define AUDPP_CMD_DEC_STATE_M 0x4000 + +#define AUDPP_CMD_UPDATDE_CFG_DEC 0x8000 +#define AUDPP_CMD_DONT_UPDATE_CFG_DEC 0x0000 + + +/* Type specification of cmd_cfg_dec */ + +typedef struct { + unsigned short cmd_id; + unsigned short dec0_cfg; + unsigned short dec1_cfg; + unsigned short dec2_cfg; + unsigned short dec3_cfg; + unsigned short dec4_cfg; +} __attribute__((packed)) audpp_cmd_cfg_dec_type; + +/* + * Command Structure to Pause , Resume and flushes the selected audio decoders + */ + +#define AUDPP_CMD_DEC_CTRL 0x0002 +#define AUDPP_CMD_DEC_CTRL_LEN sizeof(audpp_cmd_dec_ctrl) + +/* Decoder control commands for pause, resume and flush */ +#define AUDPP_CMD_FLUSH_V 0x2000 + +#define AUDPP_CMD_PAUSE_V 0x4000 +#define AUDPP_CMD_RESUME_V 0x0000 + +#define AUDPP_CMD_UPDATE_V 0x8000 +#define AUDPP_CMD_IGNORE_V 0x0000 + + +/* Type Spec for decoder control command*/ + +typedef struct { + unsigned short cmd_id; + unsigned short dec0_ctrl; + unsigned short dec1_ctrl; + unsigned short dec2_ctrl; + unsigned short dec3_ctrl; + unsigned short dec4_ctrl; +} __attribute__((packed)) audpp_cmd_dec_ctrl; + +/* + * Command Structure to Configure the AVSync FeedBack Mechanism + */ + +#define AUDPP_CMD_AVSYNC 0x0003 +#define AUDPP_CMD_AVSYNC_LEN sizeof(audpp_cmd_avsync) + +typedef struct { + unsigned short cmd_id; + unsigned short object_number; + unsigned short interrupt_interval_lsw; + unsigned short interrupt_interval_msw; +} __attribute__((packed)) audpp_cmd_avsync; + +/* + * Command Structure to enable or disable(sleep) the AUDPPTASK + */ + +#define AUDPP_CMD_CFG 0x0004 +#define AUDPP_CMD_CFG_LEN sizeof(audpp_cmd_cfg) + +#define AUDPP_CMD_CFG_SLEEP 0x0000 +#define AUDPP_CMD_CFG_ENABLE 0xFFFF + +typedef struct { + unsigned short cmd_id; + unsigned short cfg; +} __attribute__((packed)) audpp_cmd_cfg; + +/* + * Command Structure to Inject or drop the specified no of samples + */ + +#define AUDPP_CMD_ADJUST_SAMP 0x0005 +#define AUDPP_CMD_ADJUST_SAMP_LEN sizeof(audpp_cmd_adjust_samp) + +#define AUDPP_CMD_SAMP_DROP -1 +#define AUDPP_CMD_SAMP_INSERT 0x0001 + +#define AUDPP_CMD_NUM_SAMPLES 0x0001 + +typedef struct { + unsigned short cmd_id; + unsigned short object_no; + signed short sample_insert_or_drop; + unsigned short num_samples; +} __attribute__((packed)) audpp_cmd_adjust_samp; + +/* + * Command Structure to Configure AVSync Feedback Mechanism + */ + +#define AUDPP_CMD_AVSYNC_CMD_2 0x0006 +#define AUDPP_CMD_AVSYNC_CMD_2_LEN sizeof(audpp_cmd_avsync_cmd_2) + +typedef struct { + unsigned short cmd_id; + unsigned short object_number; + unsigned short interrupt_interval_lsw; + unsigned short interrupt_interval_msw; + unsigned short sample_counter_dlsw; + unsigned short sample_counter_dmsw; + unsigned short sample_counter_msw; + unsigned short byte_counter_dlsw; + unsigned short byte_counter_dmsw; + unsigned short byte_counter_msw; +} __attribute__((packed)) audpp_cmd_avsync_cmd_2; + +/* + * Command Structure to Configure AVSync Feedback Mechanism + */ + +#define AUDPP_CMD_AVSYNC_CMD_3 0x0007 +#define AUDPP_CMD_AVSYNC_CMD_3_LEN sizeof(audpp_cmd_avsync_cmd_3) + +typedef struct { + unsigned short cmd_id; + unsigned short object_number; + unsigned short interrupt_interval_lsw; + unsigned short interrupt_interval_msw; + unsigned short sample_counter_dlsw; + unsigned short sample_counter_dmsw; + unsigned short sample_counter_msw; + unsigned short byte_counter_dlsw; + unsigned short byte_counter_dmsw; + unsigned short byte_counter_msw; +} __attribute__((packed)) audpp_cmd_avsync_cmd_3; + +#define AUDPP_CMD_ROUTING_MODE 0x0008 +#define AUDPP_CMD_ROUTING_MODE_LEN \ +sizeof(struct audpp_cmd_routing_mode) + +struct audpp_cmd_routing_mode { + unsigned short cmd_id; + unsigned short object_number; + unsigned short routing_mode; +} __attribute__((packed)); + +/* + * Commands Related to uPAudPPCmd2Queue + */ + +/* + * Command Structure to configure Per decoder Parameters (Common) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS 0x0000 +#define AUDPP_CMD_CFG_ADEC_PARAMS_COMMON_LEN \ + sizeof(audpp_cmd_cfg_adec_params_common) + +#define AUDPP_CMD_STATUS_MSG_FLAG_ENA_FCM 0x4000 +#define AUDPP_CMD_STATUS_MSG_FLAG_DIS_FCM 0x0000 + +#define AUDPP_CMD_STATUS_MSG_FLAG_ENA_DCM 0x8000 +#define AUDPP_CMD_STATUS_MSG_FLAG_DIS_DCM 0x0000 + +/* Sampling frequency*/ +#define AUDPP_CMD_SAMP_RATE_96000 0x0000 +#define AUDPP_CMD_SAMP_RATE_88200 0x0001 +#define AUDPP_CMD_SAMP_RATE_64000 0x0002 +#define AUDPP_CMD_SAMP_RATE_48000 0x0003 +#define AUDPP_CMD_SAMP_RATE_44100 0x0004 +#define AUDPP_CMD_SAMP_RATE_32000 0x0005 +#define AUDPP_CMD_SAMP_RATE_24000 0x0006 +#define AUDPP_CMD_SAMP_RATE_22050 0x0007 +#define AUDPP_CMD_SAMP_RATE_16000 0x0008 +#define AUDPP_CMD_SAMP_RATE_12000 0x0009 +#define AUDPP_CMD_SAMP_RATE_11025 0x000A +#define AUDPP_CMD_SAMP_RATE_8000 0x000B + + +/* + * Type specification of cmd_adec_cfg sent to all decoder + */ + +typedef struct { + unsigned short cmd_id; + unsigned short length; + unsigned short dec_id; + unsigned short status_msg_flag; + unsigned short decoder_frame_counter_msg_period; + unsigned short input_sampling_frequency; +} __attribute__((packed)) audpp_cmd_cfg_adec_params_common; + +/* + * Command Structure to configure Per decoder Parameters (Wav) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_WAV_LEN \ + sizeof(audpp_cmd_cfg_adec_params_wav) + + +#define AUDPP_CMD_WAV_STEREO_CFG_MONO 0x0001 +#define AUDPP_CMD_WAV_STEREO_CFG_STEREO 0x0002 + +#define AUDPP_CMD_WAV_PCM_WIDTH_8 0x0000 +#define AUDPP_CMD_WAV_PCM_WIDTH_16 0x0001 +#define AUDPP_CMD_WAV_PCM_WIDTH_24 0x0002 + +typedef struct { + audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; + unsigned short pcm_width; + unsigned short sign; +} __attribute__((packed)) audpp_cmd_cfg_adec_params_wav; + +/* + * Command Structure to configure Per decoder Parameters (ADPCM) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_ADPCM_LEN \ + sizeof(audpp_cmd_cfg_adec_params_adpcm) + + +#define AUDPP_CMD_ADPCM_STEREO_CFG_MONO 0x0001 +#define AUDPP_CMD_ADPCM_STEREO_CFG_STEREO 0x0002 + +typedef struct { + audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; + unsigned short block_size; +} __attribute__((packed)) audpp_cmd_cfg_adec_params_adpcm; + +/* + * Command Structure to configure Per decoder Parameters (WMA) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_WMA_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_wma) + +struct audpp_cmd_cfg_adec_params_wma { + audpp_cmd_cfg_adec_params_common common; + unsigned short armdatareqthr; + unsigned short channelsdecoded; + unsigned short wmabytespersec; + unsigned short wmasamplingfreq; + unsigned short wmaencoderopts; +} __attribute__((packed)); + +/* + * Command Structure to configure Per decoder Parameters (WMAPRO) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_WMAPRO_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_wmapro) + +struct audpp_cmd_cfg_adec_params_wmapro { + audpp_cmd_cfg_adec_params_common common; + unsigned short armdatareqthr; + uint8_t validbitspersample; + uint8_t numchannels; + unsigned short formattag; + unsigned short samplingrate; + unsigned short avgbytespersecond; + unsigned short asfpacketlength; + unsigned short channelmask; + unsigned short encodeopt; + unsigned short advancedencodeopt; + uint32_t advancedencodeopt2; +} __attribute__((packed)); + +/* + * Command Structure to configure Per decoder Parameters (MP3) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_MP3_LEN \ + sizeof(audpp_cmd_cfg_adec_params_mp3) + +typedef struct { + audpp_cmd_cfg_adec_params_common common; +} __attribute__((packed)) audpp_cmd_cfg_adec_params_mp3; + + +/* + * Command Structure to configure Per decoder Parameters (AAC) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_AAC_LEN \ + sizeof(audpp_cmd_cfg_adec_params_aac) + + +#define AUDPP_CMD_AAC_FORMAT_ADTS -1 +#define AUDPP_CMD_AAC_FORMAT_RAW 0x0000 +#define AUDPP_CMD_AAC_FORMAT_PSUEDO_RAW 0x0001 +#define AUDPP_CMD_AAC_FORMAT_LOAS 0x0002 + +#define AUDPP_CMD_AAC_AUDIO_OBJECT_LC 0x0002 +#define AUDPP_CMD_AAC_AUDIO_OBJECT_LTP 0x0004 +#define AUDPP_CMD_AAC_AUDIO_OBJECT_ERLC 0x0011 + +#define AUDPP_CMD_AAC_SBR_ON_FLAG_ON 0x0001 +#define AUDPP_CMD_AAC_SBR_ON_FLAG_OFF 0x0000 + +#define AUDPP_CMD_AAC_SBR_PS_ON_FLAG_ON 0x0001 +#define AUDPP_CMD_AAC_SBR_PS_ON_FLAG_OFF 0x0000 + +typedef struct { + audpp_cmd_cfg_adec_params_common common; + signed short format; + unsigned short audio_object; + unsigned short ep_config; + unsigned short aac_section_data_resilience_flag; + unsigned short aac_scalefactor_data_resilience_flag; + unsigned short aac_spectral_data_resilience_flag; + unsigned short sbr_on_flag; + unsigned short sbr_ps_on_flag; + unsigned short channel_configuration; +} __attribute__((packed)) audpp_cmd_cfg_adec_params_aac; + +/* + * Command Structure to configure Per decoder Parameters (V13K) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_V13K_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_v13k) + + +#define AUDPP_CMD_STEREO_CFG_MONO 0x0001 +#define AUDPP_CMD_STEREO_CFG_STEREO 0x0002 + +struct audpp_cmd_cfg_adec_params_v13k { + audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; +} __attribute__((packed)); + +#define AUDPP_CMD_CFG_ADEC_PARAMS_EVRC_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_evrc) + +struct audpp_cmd_cfg_adec_params_evrc { + audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; +} __attribute__ ((packed)); + +/* + * Command Structure to configure Per decoder Parameters (AMRWB) + */ + +struct audpp_cmd_cfg_adec_params_amrwb { + audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; +} __attribute__((packed)) ; + +#define AUDPP_CMD_CFG_ADEC_PARAMS_AMRWB_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_amrwb) + +/* + * Command Structure to configure the HOST PCM interface + */ + +#define AUDPP_CMD_PCM_INTF 0x0001 +#define AUDPP_CMD_PCM_INTF_2 0x0002 +#define AUDPP_CMD_PCM_INTF_LEN sizeof(audpp_cmd_pcm_intf) + +#define AUDPP_CMD_PCM_INTF_MONO_V 0x0001 +#define AUDPP_CMD_PCM_INTF_STEREO_V 0x0002 + +/* These two values differentiate the two types of commands that could be issued + * Interface configuration command and Buffer update command */ + +#define AUDPP_CMD_PCM_INTF_CONFIG_CMD_V 0x0000 +#define AUDPP_CMD_PCM_INTF_BUFFER_CMD_V -1 + +#define AUDPP_CMD_PCM_INTF_RX_ENA_M 0x000F +#define AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V 0x0008 +#define AUDPP_CMD_PCM_INTF_RX_ENA_DSPTOARM_V 0x0004 + +/* These flags control the enabling and disabling of the interface together + * with host interface bit mask. */ + +#define AUDPP_CMD_PCM_INTF_ENA_V -1 +#define AUDPP_CMD_PCM_INTF_DIS_V 0x0000 + + +#define AUDPP_CMD_PCM_INTF_FULL_DUPLEX 0x0 +#define AUDPP_CMD_PCM_INTF_HALF_DUPLEX_TODSP 0x1 + + +#define AUDPP_CMD_PCM_INTF_OBJECT_NUM 0x5 +#define AUDPP_CMD_PCM_INTF_COMMON_OBJECT_NUM 0x6 + + +typedef struct { + unsigned short cmd_id; + unsigned short object_num; + signed short config; + unsigned short intf_type; + + /* DSP -> ARM Configuration */ + unsigned short read_buf1LSW; + unsigned short read_buf1MSW; + unsigned short read_buf1_len; + + unsigned short read_buf2LSW; + unsigned short read_buf2MSW; + unsigned short read_buf2_len; + /* 0:HOST_PCM_INTF disable + ** 0xFFFF: HOST_PCM_INTF enable + */ + signed short dsp_to_arm_flag; + unsigned short partition_number; + + /* ARM -> DSP Configuration */ + unsigned short write_buf1LSW; + unsigned short write_buf1MSW; + unsigned short write_buf1_len; + + unsigned short write_buf2LSW; + unsigned short write_buf2MSW; + unsigned short write_buf2_len; + + /* 0:HOST_PCM_INTF disable + ** 0xFFFF: HOST_PCM_INTF enable + */ + signed short arm_to_rx_flag; + unsigned short weight_decoder_to_rx; + unsigned short weight_arm_to_rx; + + unsigned short partition_number_arm_to_dsp; + unsigned short sample_rate; + unsigned short channel_mode; +} __attribute__((packed)) audpp_cmd_pcm_intf; + +/* + ** BUFFER UPDATE COMMAND + */ +#define AUDPP_CMD_PCM_INTF_SEND_BUF_PARAMS_LEN \ + sizeof(audpp_cmd_pcm_intf_send_buffer) + +typedef struct { + unsigned short cmd_id; + unsigned short host_pcm_object; + /* set config = 0xFFFF for configuration*/ + signed short config; + unsigned short intf_type; + unsigned short dsp_to_arm_buf_id; + unsigned short arm_to_dsp_buf_id; + unsigned short arm_to_dsp_buf_len; +} __attribute__((packed)) audpp_cmd_pcm_intf_send_buffer; + + +/* + * Commands Related to uPAudPPCmd3Queue + */ + +/* + * Command Structure to configure post processing params (Commmon) + */ + +#define AUDPP_CMD_CFG_OBJECT_PARAMS 0x0000 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN \ + sizeof(audpp_cmd_cfg_object_params_common) + +#define AUDPP_CMD_OBJ0_UPDATE 0x8000 +#define AUDPP_CMD_OBJ0_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_OBJ1_UPDATE 0x8000 +#define AUDPP_CMD_OBJ1_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_OBJ2_UPDATE 0x8000 +#define AUDPP_CMD_OBJ2_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_OBJ3_UPDATE 0x8000 +#define AUDPP_CMD_OBJ3_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_OBJ4_UPDATE 0x8000 +#define AUDPP_CMD_OBJ4_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_HPCM_UPDATE 0x8000 +#define AUDPP_CMD_HPCM_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_COMMON_CFG_UPDATE 0x8000 +#define AUDPP_CMD_COMMON_CFG_DONT_UPDATE 0x0000 + +typedef struct { + unsigned short cmd_id; + unsigned short obj0_cfg; + unsigned short obj1_cfg; + unsigned short obj2_cfg; + unsigned short obj3_cfg; + unsigned short obj4_cfg; + unsigned short host_pcm_obj_cfg; + unsigned short comman_cfg; + unsigned short command_type; +} __attribute__((packed)) audpp_cmd_cfg_object_params_common; + +/* + * Command Structure to configure post processing params (Volume) + */ + +#define AUDPP_CMD_CFG_OBJECT_PARAMS_VOLUME_LEN \ + sizeof(audpp_cmd_cfg_object_params_volume) + +typedef struct { + audpp_cmd_cfg_object_params_common common; + unsigned short volume; + unsigned short pan; +} __attribute__((packed)) audpp_cmd_cfg_object_params_volume; + +/* + * Command Structure to configure post processing params (PCM Filter) --DOUBT + */ + +typedef struct { + unsigned short numerator_b0_filter_lsw; + unsigned short numerator_b0_filter_msw; + unsigned short numerator_b1_filter_lsw; + unsigned short numerator_b1_filter_msw; + unsigned short numerator_b2_filter_lsw; + unsigned short numerator_b2_filter_msw; +} __attribute__((packed)) numerator; + +typedef struct { + unsigned short denominator_a0_filter_lsw; + unsigned short denominator_a0_filter_msw; + unsigned short denominator_a1_filter_lsw; + unsigned short denominator_a1_filter_msw; +} __attribute__((packed)) denominator; + +typedef struct { + unsigned short shift_factor_0; +} __attribute__((packed)) shift_factor; + +typedef struct { + unsigned short pan_filter_0; +} __attribute__((packed)) pan; + +typedef struct { + numerator numerator_filter; + denominator denominator_filter; + shift_factor shift_factor_filter; + pan pan_filter; +} __attribute__((packed)) filter_1; + +typedef struct { + numerator numerator_filter[2]; + denominator denominator_filter[2]; + shift_factor shift_factor_filter[2]; + pan pan_filter[2]; +} __attribute__((packed)) filter_2; + +typedef struct { + numerator numerator_filter[3]; + denominator denominator_filter[3]; + shift_factor shift_factor_filter[3]; + pan pan_filter[3]; +} __attribute__((packed)) filter_3; + +typedef struct { + numerator numerator_filter[4]; + denominator denominator_filter[4]; + shift_factor shift_factor_filter[4]; + pan pan_filter[4]; +} __attribute__((packed)) filter_4; + +#define AUDPP_CMD_CFG_OBJECT_PARAMS_PCM_LEN \ + sizeof(audpp_cmd_cfg_object_params_pcm) + + +typedef struct { + audpp_cmd_cfg_object_params_common common; + unsigned short active_flag; + unsigned short num_bands; + union { + filter_1 filter_1_params; + filter_2 filter_2_params; + filter_3 filter_3_params; + filter_4 filter_4_params; + } __attribute__((packed)) params_filter; +} __attribute__((packed)) audpp_cmd_cfg_object_params_pcm; + + +/* + * Command Structure to configure post processing parameters (equalizer) + */ + +#define AUDPP_CMD_CFG_OBJECT_PARAMS_EQALIZER_LEN \ + sizeof(audpp_cmd_cfg_object_params_eqalizer) + +typedef struct { + unsigned short numerator_coeff_0_lsw; + unsigned short numerator_coeff_0_msw; + unsigned short numerator_coeff_1_lsw; + unsigned short numerator_coeff_1_msw; + unsigned short numerator_coeff_2_lsw; + unsigned short numerator_coeff_2_msw; +} __attribute__((packed)) eq_numerator; + +typedef struct { + unsigned short denominator_coeff_0_lsw; + unsigned short denominator_coeff_0_msw; + unsigned short denominator_coeff_1_lsw; + unsigned short denominator_coeff_1_msw; +} __attribute__((packed)) eq_denominator; + +typedef struct { + unsigned short shift_factor; +} __attribute__((packed)) eq_shiftfactor; + +typedef struct { + eq_numerator numerator; + eq_denominator denominator; + eq_shiftfactor shiftfactor; +} __attribute__((packed)) eq_coeff_1; + +typedef struct { + eq_numerator numerator[2]; + eq_denominator denominator[2]; + eq_shiftfactor shiftfactor[2]; +} __attribute__((packed)) eq_coeff_2; + +typedef struct { + eq_numerator numerator[3]; + eq_denominator denominator[3]; + eq_shiftfactor shiftfactor[3]; +} __attribute__((packed)) eq_coeff_3; + +typedef struct { + eq_numerator numerator[4]; + eq_denominator denominator[4]; + eq_shiftfactor shiftfactor[4]; +} __attribute__((packed)) eq_coeff_4; + +typedef struct { + eq_numerator numerator[5]; + eq_denominator denominator[5]; + eq_shiftfactor shiftfactor[5]; +} __attribute__((packed)) eq_coeff_5; + +typedef struct { + eq_numerator numerator[6]; + eq_denominator denominator[6]; + eq_shiftfactor shiftfactor[6]; +} __attribute__((packed)) eq_coeff_6; + +typedef struct { + eq_numerator numerator[7]; + eq_denominator denominator[7]; + eq_shiftfactor shiftfactor[7]; +} __attribute__((packed)) eq_coeff_7; + +typedef struct { + eq_numerator numerator[8]; + eq_denominator denominator[8]; + eq_shiftfactor shiftfactor[8]; +} __attribute__((packed)) eq_coeff_8; + +typedef struct { + eq_numerator numerator[9]; + eq_denominator denominator[9]; + eq_shiftfactor shiftfactor[9]; +} __attribute__((packed)) eq_coeff_9; + +typedef struct { + eq_numerator numerator[10]; + eq_denominator denominator[10]; + eq_shiftfactor shiftfactor[10]; +} __attribute__((packed)) eq_coeff_10; + +typedef struct { + eq_numerator numerator[11]; + eq_denominator denominator[11]; + eq_shiftfactor shiftfactor[11]; +} __attribute__((packed)) eq_coeff_11; + +typedef struct { + eq_numerator numerator[12]; + eq_denominator denominator[12]; + eq_shiftfactor shiftfactor[12]; +} __attribute__((packed)) eq_coeff_12; + + +typedef struct { + audpp_cmd_cfg_object_params_common common; + unsigned short eq_flag; + unsigned short num_bands; + union { + eq_coeff_1 eq_coeffs_1; + eq_coeff_2 eq_coeffs_2; + eq_coeff_3 eq_coeffs_3; + eq_coeff_4 eq_coeffs_4; + eq_coeff_5 eq_coeffs_5; + eq_coeff_6 eq_coeffs_6; + eq_coeff_7 eq_coeffs_7; + eq_coeff_8 eq_coeffs_8; + eq_coeff_9 eq_coeffs_9; + eq_coeff_10 eq_coeffs_10; + eq_coeff_11 eq_coeffs_11; + eq_coeff_12 eq_coeffs_12; + } __attribute__((packed)) eq_coeff; +} __attribute__((packed)) audpp_cmd_cfg_object_params_eqalizer; + + +/* + * Command Structure to configure post processing parameters (ADRC) + */ + +#define AUDPP_CMD_CFG_OBJECT_PARAMS_ADRC_LEN \ + sizeof(audpp_cmd_cfg_object_params_adrc) + + +#define AUDPP_CMD_ADRC_FLAG_DIS 0x0000 +#define AUDPP_CMD_ADRC_FLAG_ENA -1 + +#define AUDPP_MAX_MBADRC_BANDS 5 +#define AUDPP_MBADRC_EXTERNAL_BUF_SIZE 196 + +struct adrc_config { + uint16_t subband_enable; + uint16_t adrc_sub_mute; + uint16_t rms_time; + uint16_t compression_th; + uint16_t compression_slope; + uint16_t attack_const_lsw; + uint16_t attack_const_msw; + uint16_t release_const_lsw; + uint16_t release_const_msw; + uint16_t makeup_gain; +}; + +typedef struct { + audpp_cmd_cfg_object_params_common common; + uint16_t enable; + uint16_t num_bands; + uint16_t down_samp_level; + uint16_t adrc_delay; + uint16_t ext_buf_size; + uint16_t ext_partition; + uint16_t ext_buf_msw; + uint16_t ext_buf_lsw; + struct adrc_config adrc_band[AUDPP_MAX_MBADRC_BANDS]; +} __attribute__((packed)) audpp_cmd_cfg_object_params_mbadrc; + +struct audpp_cmd_cfg_object_params_adrc { + unsigned short adrc_flag; + unsigned short compression_th; + unsigned short compression_slope; + unsigned short rms_time; + unsigned short attack_const_lsw; + unsigned short attack_const_msw; + unsigned short release_const_lsw; + unsigned short release_const_msw; + unsigned short adrc_delay; +}; + +/* + * Command Structure to configure post processing parameters(Spectrum Analizer) + */ + +#define AUDPP_CMD_CFG_OBJECT_PARAMS_SPECTRAM_LEN \ + sizeof(audpp_cmd_cfg_object_params_spectram) + + +typedef struct { + audpp_cmd_cfg_object_params_common common; + unsigned short sample_interval; + unsigned short num_coeff; +} __attribute__((packed)) audpp_cmd_cfg_object_params_spectram; + +/* + * Command Structure to configure post processing parameters (QConcert) + */ + +#define AUDPP_CMD_CFG_OBJECT_PARAMS_QCONCERT_LEN \ + sizeof(audpp_cmd_cfg_object_params_qconcert) + + +#define AUDPP_CMD_QCON_ENA_FLAG_ENA -1 +#define AUDPP_CMD_QCON_ENA_FLAG_DIS 0x0000 + +#define AUDPP_CMD_QCON_OP_MODE_HEADPHONE -1 +#define AUDPP_CMD_QCON_OP_MODE_SPEAKER_FRONT 0x0000 +#define AUDPP_CMD_QCON_OP_MODE_SPEAKER_SIDE 0x0001 +#define AUDPP_CMD_QCON_OP_MODE_SPEAKER_DESKTOP 0x0002 + +#define AUDPP_CMD_QCON_GAIN_UNIT 0x7FFF +#define AUDPP_CMD_QCON_GAIN_SIX_DB 0x4027 + + +#define AUDPP_CMD_QCON_EXPANSION_MAX 0x7FFF + + +typedef struct { + audpp_cmd_cfg_object_params_common common; + signed short enable_flag; + signed short op_mode; + signed short gain; + signed short expansion; + signed short delay; + unsigned short stages_per_mode; + unsigned short reverb_enable; + unsigned short decay_msw; + unsigned short decay_lsw; + unsigned short decay_time_ratio_msw; + unsigned short decay_time_ratio_lsw; + unsigned short reflection_delay_time; + unsigned short late_reverb_gain; + unsigned short late_reverb_delay; + unsigned short delay_buff_size_msw; + unsigned short delay_buff_size_lsw; + unsigned short partition_num; + unsigned short delay_buff_start_msw; + unsigned short delay_buff_start_lsw; +} __attribute__((packed)) audpp_cmd_cfg_object_params_qconcert; + +/* + * Command Structure to configure post processing parameters (Side Chain) + */ + +#define AUDPP_CMD_CFG_OBJECT_PARAMS_SIDECHAIN_LEN \ + sizeof(audpp_cmd_cfg_object_params_sidechain) + + +#define AUDPP_CMD_SIDECHAIN_ACTIVE_FLAG_DIS 0x0000 +#define AUDPP_CMD_SIDECHAIN_ACTIVE_FLAG_ENA -1 + +typedef struct { + audpp_cmd_cfg_object_params_common common; + signed short active_flag; + unsigned short num_bands; + union { + filter_1 filter_1_params; + filter_2 filter_2_params; + filter_3 filter_3_params; + filter_4 filter_4_params; + } __attribute__((packed)) params_filter; +} __attribute__((packed)) audpp_cmd_cfg_object_params_sidechain; + + +/* + * Command Structure to configure post processing parameters (QAFX) + */ + +#define AUDPP_CMD_CFG_OBJECT_PARAMS_QAFX_LEN \ + sizeof(audpp_cmd_cfg_object_params_qafx) + +#define AUDPP_CMD_QAFX_ENA_DISA 0x0000 +#define AUDPP_CMD_QAFX_ENA_ENA_CFG -1 +#define AUDPP_CMD_QAFX_ENA_DIS_CFG 0x0001 + +#define AUDPP_CMD_QAFX_CMD_TYPE_ENV 0x0100 +#define AUDPP_CMD_QAFX_CMD_TYPE_OBJ 0x0010 +#define AUDPP_CMD_QAFX_CMD_TYPE_QUERY 0x1000 + +#define AUDPP_CMD_QAFX_CMDS_ENV_OP_MODE 0x0100 +#define AUDPP_CMD_QAFX_CMDS_ENV_LIS_POS 0x0101 +#define AUDPP_CMD_QAFX_CMDS_ENV_LIS_ORI 0x0102 +#define AUDPP_CMD_QAFX_CMDS_ENV_LIS_VEL 0X0103 +#define AUDPP_CMD_QAFX_CMDS_ENV_ENV_RES 0x0107 + +#define AUDPP_CMD_QAFX_CMDS_OBJ_SAMP_FREQ 0x0010 +#define AUDPP_CMD_QAFX_CMDS_OBJ_VOL 0x0011 +#define AUDPP_CMD_QAFX_CMDS_OBJ_DIST 0x0012 +#define AUDPP_CMD_QAFX_CMDS_OBJ_POS 0x0013 +#define AUDPP_CMD_QAFX_CMDS_OBJ_VEL 0x0014 + + +typedef struct { + audpp_cmd_cfg_object_params_common common; + signed short enable; + unsigned short command_type; + unsigned short num_commands; + unsigned short commands; +} __attribute__((packed)) audpp_cmd_cfg_object_params_qafx; + +/* + * Command Structure to enable , disable or configure the reverberation effect + * (Common) + */ + +#define AUDPP_CMD_REVERB_CONFIG 0x0001 +#define AUDPP_CMD_REVERB_CONFIG_COMMON_LEN \ + sizeof(audpp_cmd_reverb_config_common) + +#define AUDPP_CMD_ENA_ENA 0xFFFF +#define AUDPP_CMD_ENA_DIS 0x0000 +#define AUDPP_CMD_ENA_CFG 0x0001 + +#define AUDPP_CMD_CMD_TYPE_ENV 0x0104 +#define AUDPP_CMD_CMD_TYPE_OBJ 0x0015 +#define AUDPP_CMD_CMD_TYPE_QUERY 0x1000 + + +typedef struct { + unsigned short cmd_id; + unsigned short enable; + unsigned short cmd_type; +} __attribute__((packed)) audpp_cmd_reverb_config_common; + +/* + * Command Structure to enable , disable or configure the reverberation effect + * (ENV-0x0104) + */ + +#define AUDPP_CMD_REVERB_CONFIG_ENV_104_LEN \ + sizeof(audpp_cmd_reverb_config_env_104) + +typedef struct { + audpp_cmd_reverb_config_common common; + unsigned short env_gain; + unsigned short decay_msw; + unsigned short decay_lsw; + unsigned short decay_timeratio_msw; + unsigned short decay_timeratio_lsw; + unsigned short delay_time; + unsigned short reverb_gain; + unsigned short reverb_delay; +} __attribute__((packed)) audpp_cmd_reverb_config_env_104; + +/* + * Command Structure to enable , disable or configure the reverberation effect + * (ENV-0x0015) + */ + +#define AUDPP_CMD_REVERB_CONFIG_ENV_15_LEN \ + sizeof(audpp_cmd_reverb_config_env_15) + +typedef struct { + audpp_cmd_reverb_config_common common; + unsigned short object_num; + unsigned short absolute_gain; +} __attribute__((packed)) audpp_cmd_reverb_config_env_15; + + +#endif /* QDSP5AUDPPCMDI_H */ + diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audppmsg.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audppmsg.h new file mode 100644 index 00000000000..0ba8261582e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audppmsg.h @@ -0,0 +1,321 @@ +#ifndef QDSP5AUDPPMSG_H +#define QDSP5AUDPPMSG_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + Q D S P 5 A U D I O P O S T P R O C E S S I N G M S G + +GENERAL DESCRIPTION + Messages sent by AUDPPTASK to ARM + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + + $Header: //source/qcom/qct/multimedia2/Audio/drivers/QDSP5Driver/QDSP5Interface/main/latest/qdsp5audppmsg.h#4 $ + +===========================================================================*/ + +/* + * AUDPPTASK uses audPPuPRlist to send messages to the ARM + * Location : MEMA + * Buffer Size : 45 + * No of Buffers in a queue : 5 for gaming audio and 1 for other images + */ + +/* + * MSG to Informs the ARM os Success/Failure of bringing up the decoder + */ + +#define AUDPP_MSG_STATUS_MSG 0x0001 +#define AUDPP_MSG_STATUS_MSG_LEN \ + sizeof(audpp_msg_status_msg) + +#define AUDPP_MSG_STATUS_SLEEP 0x0000 +#define AUDPP_MSG_STATUS_INIT 0x0001 +#define AUDPP_MSG_STATUS_CFG 0x0002 +#define AUDPP_MSG_STATUS_PLAY 0x0003 + +#define AUDPP_MSG_REASON_NONE 0x0000 +#define AUDPP_MSG_REASON_MEM 0x0001 +#define AUDPP_MSG_REASON_NODECODER 0x0002 + +typedef struct{ + unsigned short dec_id; + unsigned short status; + unsigned short reason; +} __attribute__((packed)) audpp_msg_status_msg; + +/* + * MSG to communicate the spectrum analyzer output bands to the ARM + */ +#define AUDPP_MSG_SPA_BANDS 0x0002 +#define AUDPP_MSG_SPA_BANDS_LEN \ + sizeof(audpp_msg_spa_bands) + +typedef struct { + unsigned short current_object; + unsigned short spa_band_1; + unsigned short spa_band_2; + unsigned short spa_band_3; + unsigned short spa_band_4; + unsigned short spa_band_5; + unsigned short spa_band_6; + unsigned short spa_band_7; + unsigned short spa_band_8; + unsigned short spa_band_9; + unsigned short spa_band_10; + unsigned short spa_band_11; + unsigned short spa_band_12; + unsigned short spa_band_13; + unsigned short spa_band_14; + unsigned short spa_band_15; + unsigned short spa_band_16; + unsigned short spa_band_17; + unsigned short spa_band_18; + unsigned short spa_band_19; + unsigned short spa_band_20; + unsigned short spa_band_21; + unsigned short spa_band_22; + unsigned short spa_band_23; + unsigned short spa_band_24; + unsigned short spa_band_25; + unsigned short spa_band_26; + unsigned short spa_band_27; + unsigned short spa_band_28; + unsigned short spa_band_29; + unsigned short spa_band_30; + unsigned short spa_band_31; + unsigned short spa_band_32; +} __attribute__((packed)) audpp_msg_spa_bands; + +/* + * MSG to communicate the PCM I/O buffer status to ARM + */ +#define AUDPP_MSG_HOST_PCM_INTF_MSG 0x0003 +#define AUDPP_MSG_HOST_PCM_INTF_MSG_LEN \ + sizeof(audpp_msg_host_pcm_intf_msg) + +#define AUDPP_MSG_HOSTPCM_ID_TX_ARM 0x0000 +#define AUDPP_MSG_HOSTPCM_ID_ARM_TX 0x0001 +#define AUDPP_MSG_HOSTPCM_ID_RX_ARM 0x0002 +#define AUDPP_MSG_HOSTPCM_ID_ARM_RX 0x0003 + +#define AUDPP_MSG_SAMP_FREQ_INDX_96000 0x0000 +#define AUDPP_MSG_SAMP_FREQ_INDX_88200 0x0001 +#define AUDPP_MSG_SAMP_FREQ_INDX_64000 0x0002 +#define AUDPP_MSG_SAMP_FREQ_INDX_48000 0x0003 +#define AUDPP_MSG_SAMP_FREQ_INDX_44100 0x0004 +#define AUDPP_MSG_SAMP_FREQ_INDX_32000 0x0005 +#define AUDPP_MSG_SAMP_FREQ_INDX_24000 0x0006 +#define AUDPP_MSG_SAMP_FREQ_INDX_22050 0x0007 +#define AUDPP_MSG_SAMP_FREQ_INDX_16000 0x0008 +#define AUDPP_MSG_SAMP_FREQ_INDX_12000 0x0009 +#define AUDPP_MSG_SAMP_FREQ_INDX_11025 0x000A +#define AUDPP_MSG_SAMP_FREQ_INDX_8000 0x000B + +#define AUDPP_MSG_CHANNEL_MODE_MONO 0x0001 +#define AUDPP_MSG_CHANNEL_MODE_STEREO 0x0002 + +typedef struct{ + unsigned short obj_num; + unsigned short numbers_of_samples; + unsigned short host_pcm_id; + unsigned short buf_indx; + unsigned short samp_freq_indx; + unsigned short channel_mode; +} __attribute__((packed)) audpp_msg_host_pcm_intf_msg; + + +/* + * MSG to communicate 3D position of the source and listener , source volume + * source rolloff, source orientation + */ + +#define AUDPP_MSG_QAFX_POS 0x0004 +#define AUDPP_MSG_QAFX_POS_LEN \ + sizeof(audpp_msg_qafx_pos) + +typedef struct { + unsigned short current_object; + unsigned short x_pos_lis_msw; + unsigned short x_pos_lis_lsw; + unsigned short y_pos_lis_msw; + unsigned short y_pos_lis_lsw; + unsigned short z_pos_lis_msw; + unsigned short z_pos_lis_lsw; + unsigned short x_fwd_msw; + unsigned short x_fwd_lsw; + unsigned short y_fwd_msw; + unsigned short y_fwd_lsw; + unsigned short z_fwd_msw; + unsigned short z_fwd_lsw; + unsigned short x_up_msw; + unsigned short x_up_lsw; + unsigned short y_up_msw; + unsigned short y_up_lsw; + unsigned short z_up_msw; + unsigned short z_up_lsw; + unsigned short x_vel_lis_msw; + unsigned short x_vel_lis_lsw; + unsigned short y_vel_lis_msw; + unsigned short y_vel_lis_lsw; + unsigned short z_vel_lis_msw; + unsigned short z_vel_lis_lsw; + unsigned short threed_enable_flag; + unsigned short volume; + unsigned short x_pos_source_msw; + unsigned short x_pos_source_lsw; + unsigned short y_pos_source_msw; + unsigned short y_pos_source_lsw; + unsigned short z_pos_source_msw; + unsigned short z_pos_source_lsw; + unsigned short max_dist_0_msw; + unsigned short max_dist_0_lsw; + unsigned short min_dist_0_msw; + unsigned short min_dist_0_lsw; + unsigned short roll_off_factor; + unsigned short mute_after_max_flag; + unsigned short x_vel_source_msw; + unsigned short x_vel_source_lsw; + unsigned short y_vel_source_msw; + unsigned short y_vel_source_lsw; + unsigned short z_vel_source_msw; + unsigned short z_vel_source_lsw; +} __attribute__((packed)) audpp_msg_qafx_pos; + +/* + * MSG to provide AVSYNC feedback from DSP to ARM + */ + +#define AUDPP_MSG_AVSYNC_MSG 0x0005 +#define AUDPP_MSG_AVSYNC_MSG_LEN \ + sizeof(audpp_msg_avsync_msg) + +typedef struct { + unsigned short active_flag; + unsigned short num_samples_counter0_HSW; + unsigned short num_samples_counter0_MSW; + unsigned short num_samples_counter0_LSW; + unsigned short num_bytes_counter0_HSW; + unsigned short num_bytes_counter0_MSW; + unsigned short num_bytes_counter0_LSW; + unsigned short samp_freq_obj_0; + unsigned short samp_freq_obj_1; + unsigned short samp_freq_obj_2; + unsigned short samp_freq_obj_3; + unsigned short samp_freq_obj_4; + unsigned short samp_freq_obj_5; + unsigned short samp_freq_obj_6; + unsigned short samp_freq_obj_7; + unsigned short samp_freq_obj_8; + unsigned short samp_freq_obj_9; + unsigned short samp_freq_obj_10; + unsigned short samp_freq_obj_11; + unsigned short samp_freq_obj_12; + unsigned short samp_freq_obj_13; + unsigned short samp_freq_obj_14; + unsigned short samp_freq_obj_15; + unsigned short num_samples_counter4_HSW; + unsigned short num_samples_counter4_MSW; + unsigned short num_samples_counter4_LSW; + unsigned short num_bytes_counter4_HSW; + unsigned short num_bytes_counter4_MSW; + unsigned short num_bytes_counter4_LSW; +} __attribute__((packed)) audpp_msg_avsync_msg; + +/* + * MSG to provide PCM DMA Missed feedback from the DSP to ARM + */ + +#define AUDPP_MSG_PCMDMAMISSED 0x0006 +#define AUDPP_MSG_PCMDMAMISSED_LEN \ + sizeof(audpp_msg_pcmdmamissed); + +typedef struct{ + /* + ** Bit 0 0 = PCM DMA not missed for object 0 + ** 1 = PCM DMA missed for object0 + ** Bit 1 0 = PCM DMA not missed for object 1 + ** 1 = PCM DMA missed for object1 + ** Bit 2 0 = PCM DMA not missed for object 2 + ** 1 = PCM DMA missed for object2 + ** Bit 3 0 = PCM DMA not missed for object 3 + ** 1 = PCM DMA missed for object3 + ** Bit 4 0 = PCM DMA not missed for object 4 + ** 1 = PCM DMA missed for object4 + */ + unsigned short pcmdmamissed; +} __attribute__((packed)) audpp_msg_pcmdmamissed; + +/* + * MSG to AUDPP enable or disable feedback form DSP to ARM + */ + +#define AUDPP_MSG_CFG_MSG 0x0007 +#define AUDPP_MSG_CFG_MSG_LEN \ + sizeof(audpp_msg_cfg_msg) + +#define AUDPP_MSG_ENA_ENA 0xFFFF +#define AUDPP_MSG_ENA_DIS 0x0000 + +typedef struct{ + /* Enabled - 0xffff + ** Disabled - 0 + */ + unsigned short enabled; +} __attribute__((packed)) audpp_msg_cfg_msg; + +/* + * MSG to communicate the reverb per object volume + */ + +#define AUDPP_MSG_QREVERB_VOLUME 0x0008 +#define AUDPP_MSG_QREVERB_VOLUME_LEN \ + sizeof(audpp_msg_qreverb_volume) + + +typedef struct { + unsigned short obj_0_gain; + unsigned short obj_1_gain; + unsigned short obj_2_gain; + unsigned short obj_3_gain; + unsigned short obj_4_gain; + unsigned short hpcm_obj_volume; +} __attribute__((packed)) audpp_msg_qreverb_volume; + +#define AUDPP_MSG_ROUTING_ACK 0x0009 +#define AUDPP_MSG_ROUTING_ACK_LEN \ + sizeof(struct audpp_msg_routing_ack) + +struct audpp_msg_routing_ack { + unsigned short dec_id; + unsigned short routing_mode; +} __attribute__((packed)); + +#define AUDPP_MSG_FLUSH_ACK 0x000A + +#define ADSP_MESSAGE_ID 0xFFFF + +#endif /* QDSP5AUDPPMSG_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreproc.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreproc.h new file mode 100644 index 00000000000..234c4ac869a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreproc.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef QDSP5AUDPREPROC_H +#define _QDSP5AUDPREPROC_H + +#include +#include + +#define MSM_AUD_ENC_MODE_TUNNEL 0x00000100 +#define MSM_AUD_ENC_MODE_NONTUNNEL 0x00000200 + +#define AUDPREPROC_CODEC_MASK 0x00FF +#define AUDPREPROC_MODE_MASK 0xFF00 + +#define MSM_ADSP_ENC_MODE_TUNNEL 24 +#define MSM_ADSP_ENC_MODE_NON_TUNNEL 25 + +/* Exported common api's from audpreproc layer */ +int audpreproc_aenc_alloc(unsigned enc_type, const char **module_name, + unsigned *queue_id); +void audpreproc_aenc_free(int enc_id); + +#endif /* QDSP5AUDPREPROC_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreproccmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreproccmdi.h new file mode 100644 index 00000000000..8efc916c333 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreproccmdi.h @@ -0,0 +1,256 @@ +#ifndef QDSP5AUDPREPROCCMDI_H +#define QDSP5AUDPREPROCCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + A U D I O P R E P R O C E S S I N G I N T E R N A L C O M M A N D S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by AUDPREPROC Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/Audio/drivers/QDSP5Driver/QDSP5Interface/main/latest/qdsp5audpreproccmdi.h#2 $ + +===========================================================================*/ + +/* + * AUDIOPREPROC COMMANDS: + * ARM uses uPAudPreProcCmdQueue to communicate with AUDPREPROCTASK + * Location : MEMB + * Buffer size : 51 + * Number of buffers in a queue : 3 + */ + +/* + * Command to configure the parameters of AGC + */ + +#define AUDPREPROC_CMD_CFG_AGC_PARAMS 0x0000 +#define AUDPREPROC_CMD_CFG_AGC_PARAMS_LEN \ + sizeof(audpreproc_cmd_cfg_agc_params) + +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_SLOPE 0x0009 +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_TH 0x000A +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_EXP_SLOPE 0x000B +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_EXP_TH 0x000C +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_AIG_FLAG 0x000D +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_STATIC_GAIN 0x000E +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_TX_AGC_ENA_FLAG 0x000F + +#define AUDPREPROC_CMD_TX_AGC_ENA_FLAG_ENA -1 +#define AUDPREPROC_CMD_TX_AGC_ENA_FLAG_DIS 0x0000 + +#define AUDPREPROC_CMD_ADP_GAIN_FLAG_ENA_ADP_GAIN -1 +#define AUDPREPROC_CMD_ADP_GAIN_FLAG_ENA_STATIC_GAIN 0x0000 + +#define AUDPREPROC_CMD_PARAM_MASK_RMS_TAY 0x0004 +#define AUDPREPROC_CMD_PARAM_MASK_RELEASEK 0x0005 +#define AUDPREPROC_CMD_PARAM_MASK_DELAY 0x0006 +#define AUDPREPROC_CMD_PARAM_MASK_ATTACKK 0x0007 +#define AUDPREPROC_CMD_PARAM_MASK_LEAKRATE_SLOW 0x0008 +#define AUDPREPROC_CMD_PARAM_MASK_LEAKRATE_FAST 0x0009 +#define AUDPREPROC_CMD_PARAM_MASK_AIG_RELEASEK 0x000A +#define AUDPREPROC_CMD_PARAM_MASK_AIG_MIN 0x000B +#define AUDPREPROC_CMD_PARAM_MASK_AIG_MAX 0x000C +#define AUDPREPROC_CMD_PARAM_MASK_LEAK_UP 0x000D +#define AUDPREPROC_CMD_PARAM_MASK_LEAK_DOWN 0x000E +#define AUDPREPROC_CMD_PARAM_MASK_AIG_ATTACKK 0x000F + +typedef struct { + unsigned short cmd_id; + unsigned short tx_agc_param_mask; + unsigned short tx_agc_enable_flag; + unsigned short static_gain; + signed short adaptive_gain_flag; + unsigned short expander_th; + unsigned short expander_slope; + unsigned short compressor_th; + unsigned short compressor_slope; + unsigned short param_mask; + unsigned short aig_attackk; + unsigned short aig_leak_down; + unsigned short aig_leak_up; + unsigned short aig_max; + unsigned short aig_min; + unsigned short aig_releasek; + unsigned short aig_leakrate_fast; + unsigned short aig_leakrate_slow; + unsigned short attackk_msw; + unsigned short attackk_lsw; + unsigned short delay; + unsigned short releasek_msw; + unsigned short releasek_lsw; + unsigned short rms_tav; +} __attribute__((packed)) audpreproc_cmd_cfg_agc_params; + + +/* + * Command to configure the params of Advanved AGC + */ + +#define AUDPREPROC_CMD_CFG_AGC_PARAMS_2 0x0001 +#define AUDPREPROC_CMD_CFG_AGC_PARAMS_2_LEN \ + sizeof(audpreproc_cmd_cfg_agc_params_2) + +#define AUDPREPROC_CMD_2_TX_AGC_ENA_FLAG_ENA -1; +#define AUDPREPROC_CMD_2_TX_AGC_ENA_FLAG_DIS 0x0000; + +typedef struct { + unsigned short cmd_id; + unsigned short agc_param_mask; + signed short tx_agc_enable_flag; + unsigned short comp_static_gain; + unsigned short exp_th; + unsigned short exp_slope; + unsigned short comp_th; + unsigned short comp_slope; + unsigned short comp_rms_tav; + unsigned short comp_samp_mask; + unsigned short comp_attackk_msw; + unsigned short comp_attackk_lsw; + unsigned short comp_releasek_msw; + unsigned short comp_releasek_lsw; + unsigned short comp_delay; + unsigned short comp_makeup_gain; +} __attribute__((packed)) audpreproc_cmd_cfg_agc_params_2; + +/* + * Command to configure params for ns + */ + +#define AUDPREPROC_CMD_CFG_NS_PARAMS 0x0002 +#define AUDPREPROC_CMD_CFG_NS_PARAMS_LEN \ + sizeof(audpreproc_cmd_cfg_ns_params) + +#define AUDPREPROC_CMD_EC_MODE_NEW_NLMS_ENA 0x0001 +#define AUDPREPROC_CMD_EC_MODE_NEW_NLMS_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_DES_ENA 0x0002 +#define AUDPREPROC_CMD_EC_MODE_NEW_DES_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_NS_ENA 0x0004 +#define AUDPREPROC_CMD_EC_MODE_NEW_NS_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_CNI_ENA 0x0008 +#define AUDPREPROC_CMD_EC_MODE_NEW_CNI_DIS 0x0000 + +#define AUDPREPROC_CMD_EC_MODE_NEW_NLES_ENA 0x0010 +#define AUDPREPROC_CMD_EC_MODE_NEW_NLES_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_HB_ENA 0x0020 +#define AUDPREPROC_CMD_EC_MODE_NEW_HB_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_VA_ENA 0x0040 +#define AUDPREPROC_CMD_EC_MODE_NEW_VA_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_PCD_ENA 0x0080 +#define AUDPREPROC_CMD_EC_MODE_NEW_PCD_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_FEHI_ENA 0x0100 +#define AUDPREPROC_CMD_EC_MODE_NEW_FEHI_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_NEHI_ENA 0x0200 +#define AUDPREPROC_CMD_EC_MODE_NEW_NEHI_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_NLPP_ENA 0x0400 +#define AUDPREPROC_CMD_EC_MODE_NEW_NLPP_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_FNE_ENA 0x0800 +#define AUDPREPROC_CMD_EC_MODE_NEW_FNE_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEW_PRENLMS_ENA 0x1000 +#define AUDPREPROC_CMD_EC_MODE_NEW_PRENLMS_DIS 0x0000 + +typedef struct { + unsigned short cmd_id; + unsigned short ec_mode_new; + unsigned short dens_gamma_n; + unsigned short dens_nfe_block_size; + unsigned short dens_limit_ns; + unsigned short dens_limit_ns_d; + unsigned short wb_gamma_e; + unsigned short wb_gamma_n; +} __attribute__((packed)) audpreproc_cmd_cfg_ns_params; + +/* + * Command to configure parameters for IIR tuning filter + */ + +#define AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS 0x0003 +#define AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS_LEN \ + sizeof(audpreproc_cmd_cfg_iir_tuning_filter_params) + +#define AUDPREPROC_CMD_IIR_ACTIVE_FLAG_DIS 0x0000 +#define AUDPREPROC_CMD_IIR_ACTIVE_FLAG_ENA 0x0001 + +typedef struct { + unsigned short cmd_id; + unsigned short active_flag; + unsigned short num_bands; + unsigned short numerator_coeff_b0_filter0_lsw; + unsigned short numerator_coeff_b0_filter0_msw; + unsigned short numerator_coeff_b1_filter0_lsw; + unsigned short numerator_coeff_b1_filter0_msw; + unsigned short numerator_coeff_b2_filter0_lsw; + unsigned short numerator_coeff_b2_filter0_msw; + unsigned short numerator_coeff_b0_filter1_lsw; + unsigned short numerator_coeff_b0_filter1_msw; + unsigned short numerator_coeff_b1_filter1_lsw; + unsigned short numerator_coeff_b1_filter1_msw; + unsigned short numerator_coeff_b2_filter1_lsw; + unsigned short numerator_coeff_b2_filter1_msw; + unsigned short numerator_coeff_b0_filter2_lsw; + unsigned short numerator_coeff_b0_filter2_msw; + unsigned short numerator_coeff_b1_filter2_lsw; + unsigned short numerator_coeff_b1_filter2_msw; + unsigned short numerator_coeff_b2_filter2_lsw; + unsigned short numerator_coeff_b2_filter2_msw; + unsigned short numerator_coeff_b0_filter3_lsw; + unsigned short numerator_coeff_b0_filter3_msw; + unsigned short numerator_coeff_b1_filter3_lsw; + unsigned short numerator_coeff_b1_filter3_msw; + unsigned short numerator_coeff_b2_filter3_lsw; + unsigned short numerator_coeff_b2_filter3_msw; + unsigned short denominator_coeff_a0_filter0_lsw; + unsigned short denominator_coeff_a0_filter0_msw; + unsigned short denominator_coeff_a1_filter0_lsw; + unsigned short denominator_coeff_a1_filter0_msw; + unsigned short denominator_coeff_a0_filter1_lsw; + unsigned short denominator_coeff_a0_filter1_msw; + unsigned short denominator_coeff_a1_filter1_lsw; + unsigned short denominator_coeff_a1_filter1_msw; + unsigned short denominator_coeff_a0_filter2_lsw; + unsigned short denominator_coeff_a0_filter2_msw; + unsigned short denominator_coeff_a1_filter2_lsw; + unsigned short denominator_coeff_a1_filter2_msw; + unsigned short denominator_coeff_a0_filter3_lsw; + unsigned short denominator_coeff_a0_filter3_msw; + unsigned short denominator_coeff_a1_filter3_lsw; + unsigned short denominator_coeff_a1_filter3_msw; + + unsigned short shift_factor_filter0; + unsigned short shift_factor_filter1; + unsigned short shift_factor_filter2; + unsigned short shift_factor_filter3; + + unsigned short channel_selected0; + unsigned short channel_selected1; + unsigned short channel_selected2; + unsigned short channel_selected3; +} __attribute__((packed))audpreproc_cmd_cfg_iir_tuning_filter_params; + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreprocmsg.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreprocmsg.h new file mode 100644 index 00000000000..0696066f91b --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audpreprocmsg.h @@ -0,0 +1,85 @@ +#ifndef QDSP5AUDPREPROCMSG_H +#define QDSP5AUDPREPROCMSG_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + A U D I O P R E P R O C E S S I N G M E S S A G E S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of messages + that are rcvd by AUDPREPROC Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + + $Header: //source/qcom/qct/multimedia2/Audio/drivers/QDSP5Driver/QDSP5Interface/main/latest/qdsp5audpreprocmsg.h#3 $ + +===========================================================================*/ + +/* + * ADSPREPROCTASK Messages + * AUDPREPROCTASK uses audPreProcUpRlist to communicate with ARM + * Location : MEMA + * Message Length : 2 + */ + +/* + * Message to indicate particular feature has been enabled or disabled + */ + + +#define AUDPREPROC_MSG_CMD_CFG_DONE_MSG 0x0001 +#define AUDPREPROC_MSG_CMD_CFG_DONE_MSG_LEN \ + sizeof(audpreproc_msg_cmd_cfg_done_msg) + +#define AUDPREPROC_MSG_TYPE_AGC 0x0000 +#define AUDPREPROC_MSG_TYPE_NOISE_REDUCTION 0x0001 +#define AUDPREPROC_MSG_TYPE_IIR_FILTER 0x0002 + + +#define AUDPREPROC_MSG_STATUS_FLAG_ENA -1 +#define AUDPREPROC_MSG_STATUS_FLAG_DIS 0x0000 + +typedef struct { + unsigned short type; + signed short status_flag; +} __attribute__((packed)) audpreproc_msg_cmd_cfg_done_msg; + + +/* + * Message to indicate particular feature has selected for wrong samp freq + */ + +#define AUDPREPROC_MSG_ERROR_MSG_ID 0x0002 +#define AUDPREPROC_MSG_ERROR_MSG_ID_LEN \ + sizeof(audpreproc_msg_error_msg_id) + +#define AUDPREPROC_MSG_ERR_INDEX_NS 0x0000 + +typedef struct { + unsigned short err_index; +} __attribute__((packed)) audpreproc_msg_error_msg_id; + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audreccmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audreccmdi.h new file mode 100644 index 00000000000..5045de05aea --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audreccmdi.h @@ -0,0 +1,401 @@ +#ifndef QDSP5AUDRECCMDI_H +#define QDSP5AUDRECCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + * + * A U D I O R E C O R D I N T E R N A L C O M M A N D S + * + * GENERAL DESCRIPTION + * This file contains defintions of format blocks of commands + * that are accepted by AUDREC Task + * + * REFERENCES + * None + * + * EXTERNALIZED FUNCTIONS + * None + * + * Copyright (c) 1992-2009, 2011 Code Aurora Forum. 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. + * + *====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ + +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + + $Header: //source/qcom/qct/multimedia2/Audio/drivers/QDSP5Driver/QDSP5Interface/main/latest/qdsp5audreccmdi.h#3 $ + +============================================================================*/ + +/* + * AUDRECTASK COMMANDS + * ARM uses 2 queues to communicate with the AUDRECTASK + * 1.uPAudRecCmdQueue + * Location :MEMC + * Buffer Size : 8 + * No of Buffers in a queue : 3 + * 2.audRecUpBitStreamQueue + * Location : MEMC + * Buffer Size : 4 + * No of buffers in a queue : 2 + */ + +/* + * Commands on uPAudRecCmdQueue + */ + +/* + * Command to initiate and terminate the audio recording section + */ + +#define AUDREC_CMD_CFG 0x0000 +#define AUDREC_CMD_CFG_LEN sizeof(audrec_cmd_cfg) + +#define AUDREC_CMD_TYPE_0_INDEX_WAV 0x0000 +#define AUDREC_CMD_TYPE_0_INDEX_AAC 0x0001 +#define AUDREC_CMD_TYPE_0_INDEX_AMRNB 0x000A +#define AUDREC_CMD_TYPE_0_INDEX_EVRC 0x000B +#define AUDREC_CMD_TYPE_0_INDEX_QCELP 0x000C + +#define AUDREC_CMD_TYPE_0_ENA 0x4000 +#define AUDREC_CMD_TYPE_0_DIS 0x0000 + +#define AUDREC_CMD_TYPE_0_NOUPDATE 0x0000 +#define AUDREC_CMD_TYPE_0_UPDATE 0x8000 + +#define AUDREC_CMD_TYPE_1_INDEX_SBC 0x0002 + +#define AUDREC_CMD_TYPE_1_ENA 0x4000 +#define AUDREC_CMD_TYPE_1_DIS 0x0000 + +#define AUDREC_CMD_TYPE_1_NOUPDATE 0x0000 +#define AUDREC_CMD_TYPE_1_UPDATE 0x8000 + +typedef struct { + unsigned short cmd_id; + unsigned short type_0; + unsigned short type_1; +} __attribute__((packed)) audrec_cmd_cfg; + + +/* + * Command to configure the recording parameters for RecType0(AAC/WAV) encoder + */ + +#define AUDREC_CMD_AREC0PARAM_CFG 0x0001 +#define AUDREC_CMD_AREC0PARAM_CFG_LEN \ + sizeof(audrec_cmd_arec0param_cfg) + +#define AUDREC_CMD_SAMP_RATE_INDX_8000 0x000B +#define AUDREC_CMD_SAMP_RATE_INDX_11025 0x000A +#define AUDREC_CMD_SAMP_RATE_INDX_12000 0x0009 +#define AUDREC_CMD_SAMP_RATE_INDX_16000 0x0008 +#define AUDREC_CMD_SAMP_RATE_INDX_22050 0x0007 +#define AUDREC_CMD_SAMP_RATE_INDX_24000 0x0006 +#define AUDREC_CMD_SAMP_RATE_INDX_32000 0x0005 +#define AUDREC_CMD_SAMP_RATE_INDX_44100 0x0004 +#define AUDREC_CMD_SAMP_RATE_INDX_48000 0x0003 + +#define AUDREC_CMD_STEREO_MODE_MONO 0x0000 +#define AUDREC_CMD_STEREO_MODE_STEREO 0x0001 + +typedef struct { + unsigned short cmd_id; + unsigned short ptr_to_extpkt_buffer_msw; + unsigned short ptr_to_extpkt_buffer_lsw; + unsigned short buf_len; + unsigned short samp_rate_index; + unsigned short stereo_mode; + unsigned short rec_quality; +} __attribute__((packed)) audrec_cmd_arec0param_cfg; + +/* + * Command to configure the recording parameters for RecType1(SBC) encoder + */ + +#define AUDREC_CMD_AREC1PARAM_CFG 0x0002 +#define AUDREC_CMD_AREC1PARAM_CFG_LEN \ + sizeof(audrec_cmd_arec1param_cfg) + +#define AUDREC_CMD_PARAM_BUF_BLOCKS_4 0x0000 +#define AUDREC_CMD_PARAM_BUF_BLOCKS_8 0x0001 +#define AUDREC_CMD_PARAM_BUF_BLOCKS_12 0x0002 +#define AUDREC_CMD_PARAM_BUF_BLOCKS_16 0x0003 + +#define AUDREC_CMD_PARAM_BUF_SUB_BANDS_8 0x0010 +#define AUDREC_CMD_PARAM_BUF_MODE_MONO 0x0000 +#define AUDREC_CMD_PARAM_BUF_MODE_DUAL 0x0040 +#define AUDREC_CMD_PARAM_BUF_MODE_STEREO 0x0050 +#define AUDREC_CMD_PARAM_BUF_MODE_JSTEREO 0x0060 +#define AUDREC_CMD_PARAM_BUF_LOUDNESS 0x0000 +#define AUDREC_CMD_PARAM_BUF_SNR 0x0100 +#define AUDREC_CMD_PARAM_BUF_BASIC_VER 0x0000 + +typedef struct { + unsigned short cmd_id; + unsigned short ptr_to_extpkt_buffer_msw; + unsigned short ptr_to_extpkt_buffer_lsw; + unsigned short buf_len; + unsigned short param_buf; + unsigned short bit_rate_0; + unsigned short bit_rate_1; +} __attribute__((packed)) audrec_cmd_arec1param_cfg; + +/* + * Command to enable encoder for the recording + */ + +#define AUDREC_CMD_ENC_CFG 0x0003 +#define AUDREC_CMD_ENC_CFG_LEN \ + sizeof(struct audrec_cmd_enc_cfg) + + +#define AUDREC_CMD_ENC_ENA 0x8000 +#define AUDREC_CMD_ENC_DIS 0x0000 + +#define AUDREC_CMD_ENC_TYPE_MASK 0x001F + +struct audrec_cmd_enc_cfg { + unsigned short cmd_id; + unsigned short audrec_enc_type; + unsigned short audrec_obj_idx; +} __attribute__((packed)); + +/* + * Command to set external memory config for the selected encoder + */ + +#define AUDREC_CMD_ARECMEM_CFG 0x0004 +#define AUDREC_CMD_ARECMEM_CFG_LEN \ + sizeof(struct audrec_cmd_arecmem_cfg) + + +struct audrec_cmd_arecmem_cfg { + unsigned short cmd_id; + unsigned short audrec_obj_idx; + unsigned short audrec_up_pkt_intm_cnt; + unsigned short audrec_extpkt_buffer_msw; + unsigned short audrec_extpkt_buffer_lsw; + unsigned short audrec_extpkt_buffer_num; +} __attribute__((packed)); + +/* + * Command to configure the recording parameters for selected encoder + */ + +#define AUDREC_CMD_ARECPARAM_CFG 0x0005 +#define AUDREC_CMD_ARECPARAM_COMMON_CFG_LEN \ + sizeof(struct audrec_cmd_arecparam_common_cfg) + + +struct audrec_cmd_arecparam_common_cfg { + unsigned short cmd_id; + unsigned short audrec_obj_idx; +} __attribute__((packed)); + +#define AUDREC_CMD_ARECPARAM_WAV_CFG_LEN \ + sizeof(struct audrec_cmd_arecparam_wav_cfg) + + +struct audrec_cmd_arecparam_wav_cfg { + struct audrec_cmd_arecparam_common_cfg common; + unsigned short samp_rate_idx; + unsigned short stereo_mode; +} __attribute__((packed)); + +#define AUDREC_CMD_ARECPARAM_AAC_CFG_LEN \ + sizeof(struct audrec_cmd_arecparam_aac_cfg) + + +struct audrec_cmd_arecparam_aac_cfg { + struct audrec_cmd_arecparam_common_cfg common; + unsigned short samp_rate_idx; + unsigned short stereo_mode; + unsigned short rec_quality; +} __attribute__((packed)); + +#define AUDREC_CMD_ARECPARAM_SBC_CFG_LEN \ + sizeof(struct audrec_cmd_arecparam_sbc_cfg) + + +struct audrec_cmd_arecparam_sbc_cfg { + struct audrec_cmd_arecparam_common_cfg common; + unsigned short param_buf; + unsigned short bit_rate_0; + unsigned short bit_rate_1; +} __attribute__((packed)); + +#define AUDREC_CMD_ARECPARAM_AMRNB_CFG_LEN \ + sizeof(struct audrec_cmd_arecparam_amrnb_cfg) + + +struct audrec_cmd_arecparam_amrnb_cfg { + struct audrec_cmd_arecparam_common_cfg common; + unsigned short samp_rate_idx; + unsigned short voicememoencweight1; + unsigned short voicememoencweight2; + unsigned short voicememoencweight3; + unsigned short voicememoencweight4; + unsigned short update_mode; + unsigned short dtx_mode; + unsigned short test_mode; + unsigned short used_mode; +} __attribute__((packed)); + +#define AUDREC_CMD_ARECPARAM_EVRC_CFG_LEN \ + sizeof(struct audrec_cmd_arecparam_evrc_cfg) + + +struct audrec_cmd_arecparam_evrc_cfg { + struct audrec_cmd_arecparam_common_cfg common; + unsigned short samp_rate_idx; + unsigned short voicememoencweight1; + unsigned short voicememoencweight2; + unsigned short voicememoencweight3; + unsigned short voicememoencweight4; + unsigned short update_mode; + unsigned short enc_min_rate; + unsigned short enc_max_rate; + unsigned short rate_modulation_cmd; +} __attribute__((packed)); + +#define AUDREC_CMD_ARECPARAM_QCELP_CFG_LEN \ + sizeof(struct audrec_cmd_arecparam_qcelp_cfg) + + +struct audrec_cmd_arecparam_qcelp_cfg { + struct audrec_cmd_arecparam_common_cfg common; + unsigned short samp_rate_idx; + unsigned short voicememoencweight1; + unsigned short voicememoencweight2; + unsigned short voicememoencweight3; + unsigned short voicememoencweight4; + unsigned short update_mode; + unsigned short enc_min_rate; + unsigned short enc_max_rate; + unsigned short rate_modulation_cmd; + unsigned short reduced_rate_level; +} __attribute__((packed)); + +#define AUDREC_CMD_ARECPARAM_FGVNB_CFG_LEN \ + sizeof(struct audrec_cmd_arecparam_fgvnb_cfg) + + +struct audrec_cmd_arecparam_fgvnb_cfg { + struct audrec_cmd_arecparam_common_cfg common; + unsigned short samp_rate_idx; + unsigned short voicememoencweight1; + unsigned short voicememoencweight2; + unsigned short voicememoencweight3; + unsigned short voicememoencweight4; + unsigned short update_mode; + unsigned short fgv_min_rate; + unsigned short fgv_max_rate; + unsigned short reduced_rate_level; +} __attribute__((packed)); + +/* + * Command to configure Tunnel(RT) or Non-Tunnel(FTRT) mode + */ + +#define AUDREC_CMD_ROUTING_MODE 0x0006 +#define AUDREC_CMD_ROUTING_MODE_LEN \ + sizeof(struct audpreproc_audrec_cmd_routing_mode) + +#define AUDIO_ROUTING_MODE_FTRT 0x0001 +#define AUDIO_ROUTING_MODE_RT 0x0002 + +struct audrec_cmd_routing_mode { + unsigned short cmd_id; + unsigned short routing_mode; +} __packed; + +/* + * Command to configure pcm input memory + */ + +#define AUDREC_CMD_PCM_CFG_ARM_TO_ENC 0x0007 +#define AUDREC_CMD_PCM_CFG_ARM_TO_ENC_LEN \ + sizeof(struct audrec_cmd_pcm_cfg_arm_to_enc) + +struct audrec_cmd_pcm_cfg_arm_to_enc { + unsigned short cmd_id; + unsigned short config_update_flag; + unsigned short enable_flag; + unsigned short sampling_freq; + unsigned short channels; + unsigned short frequency_of_intimation; + unsigned short max_number_of_buffers; +} __packed; + +#define AUDREC_PCM_CONFIG_UPDATE_FLAG_ENABLE -1 +#define AUDREC_PCM_CONFIG_UPDATE_FLAG_DISABLE 0 + +#define AUDREC_ENABLE_FLAG_VALUE -1 +#define AUDREC_DISABLE_FLAG_VALUE 0 + +/* + * Command to intimate available pcm buffer + */ + +#define AUDREC_CMD_PCM_BUFFER_PTR_REFRESH_ARM_TO_ENC 0x0008 +#define AUDREC_CMD_PCM_BUFFER_PTR_REFRESH_ARM_TO_ENC_LEN \ + sizeof(struct audrec_cmd_pcm_buffer_ptr_refresh_arm_enc) + +struct audrec_cmd_pcm_buffer_ptr_refresh_arm_enc { + unsigned short cmd_id; + unsigned short num_buffers; + unsigned short buffer_write_cnt_msw; + unsigned short buffer_write_cnt_lsw; + unsigned short buf_address_length[8];/*this array holds address + and length details of + two buffers*/ +} __packed; + +/* + * Command to flush + */ + +#define AUDREC_CMD_FLUSH 0x009 +#define AUDREC_CMD_FLUSH_LEN \ + sizeof(struct audrec_cmd_flush) + +struct audrec_cmd_flush { + unsigned short cmd_id; +} __packed; + +/* + * Commands on audRecUpBitStreamQueue + */ + +/* + * Command to indicate the current packet read count + */ + +#define AUDREC_CMD_PACKET_EXT_PTR 0x0000 +#define AUDREC_CMD_PACKET_EXT_PTR_LEN \ + sizeof(audrec_cmd_packet_ext_ptr) + +#define AUDREC_CMD_TYPE_0 0x0000 +#define AUDREC_CMD_TYPE_1 0x0001 + +typedef struct { + unsigned short cmd_id; + unsigned short type; /* audrec_obj_idx */ + unsigned short curr_rec_count_msw; + unsigned short curr_rec_count_lsw; +} __attribute__((packed)) audrec_cmd_packet_ext_ptr; + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audrecmsg.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audrecmsg.h new file mode 100644 index 00000000000..339e4f7dafa --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5audrecmsg.h @@ -0,0 +1,223 @@ +#ifndef QDSP5AUDRECMSGI_H +#define QDSP5AUDRECMSGI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + * + * A U D I O R E C O R D M E S S A G E S + * + * GENERAL DESCRIPTION + * This file contains defintions of format blocks of messages + * that are sent by AUDREC Task + * + * REFERENCES + * None + * + * EXTERNALIZED FUNCTIONS + * None + * + * Copyright (c) 1992-2009, 2011 Code Aurora Forum. 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. + * + *====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ + +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + + $Header: //source/qcom/qct/multimedia2/Audio/drivers/QDSP5Driver/QDSP5Interface/main/latest/qdsp5audrecmsg.h#3 $ + +============================================================================*/ + +/* + * AUDRECTASK MESSAGES + * AUDRECTASK uses audRecUpRlist to communicate with ARM + * Location : MEMC + * Buffer size : 4 + * No of buffers in a queue : 2 + */ + +/* + * Message to notify that config command is done + */ + +#define AUDREC_MSG_CMD_CFG_DONE_MSG 0x0002 +#define AUDREC_MSG_CMD_CFG_DONE_MSG_LEN \ + sizeof(struct audrec_msg_cmd_cfg_done_msg) + + +#define AUDREC_MSG_CFG_DONE_TYPE_0_ENA 0x4000 +#define AUDREC_MSG_CFG_DONE_TYPE_0_DIS 0x0000 + +#define AUDREC_MSG_CFG_DONE_TYPE_0_NO_UPDATE 0x0000 +#define AUDREC_MSG_CFG_DONE_TYPE_0_UPDATE 0x8000 + +#define AUDREC_MSG_CFG_DONE_TYPE_1_ENA 0x4000 +#define AUDREC_MSG_CFG_DONE_TYPE_1_DIS 0x0000 + +#define AUDREC_MSG_CFG_DONE_TYPE_1_NO_UPDATE 0x0000 +#define AUDREC_MSG_CFG_DONE_TYPE_1_UPDATE 0x8000 + +#define AUDREC_MSG_CFG_DONE_ENC_ENA 0x8000 +#define AUDREC_MSG_CFG_DONE_ENC_DIS 0x0000 + +struct audrec_msg_cmd_cfg_done_msg { + unsigned short audrec_enc_type; + unsigned short audrec_obj_idx; +} __attribute__((packed)); + +/* + * Message to notify arec0/1 or concurrent encoder cfg done + * and recording params recieved by task + */ + +#define AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG 0x0003 +#define AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG_LEN \ + sizeof(struct audrec_msg_cmd_arec_param_cfg_done_msg) + + +#define AUDREC_MSG_AREC_PARAM_TYPE_0 0x0000 +#define AUDREC_MSG_AREC_PARAM_TYPE_1 0x0001 + +struct audrec_msg_cmd_arec_param_cfg_done_msg { + unsigned short audrec_obj_idx; +} __attribute__((packed)); + +/* + * Message to notify no more buffers are available in ext mem to DME + * Or no concurrent encoder supported + */ +/* for 7x27 */ +#define AUDREC_MSG_FATAL_ERR_MSG 0x0004 +#define AUDREC_MSG_FATAL_ERR_MSG_LEN \ + sizeof(struct audrec_msg_fatal_err_msg) + + +#define AUDREC_MSG_FATAL_ERR_TYPE_0 0x0000 +#define AUDREC_MSG_FATAL_ERR_TYPE_1 0x0001 + +struct audrec_msg_fatal_err_msg { + unsigned short audrec_obj_idx; + unsigned short audrec_err_id; +} __attribute__((packed)); + +/* for 7x27A */ +#define AUDREC_MSG_NO_EXT_PKT_AVAILABLE_MSG 0x0004 +#define AUDREC_MSG_NO_EXT_PKT_AVAILABLE_MSG_LEN \ + sizeof(struct audrec_msg_no_ext_pkt_avail_msg) + +#define AUDREC_MSG_NO_EXT_PKT_AVAILABLE_TYPE_0 0x0000 +#define AUDREC_MSG_NO_EXT_PKT_AVAILABLE_TYPE_1 0x0001 + +struct audrec_msg_no_ext_pkt_avail_msg { + unsigned short audrec_obj_idx; + unsigned short audrec_err_id; +} __packed; + +/* + * Message to notify DME deliverd the encoded pkt to ext pkt buffer + */ + +#define AUDREC_MSG_PACKET_READY_MSG 0x0005 +#define AUDREC_MSG_PACKET_READY_MSG_LEN \ + sizeof(struct audrec_msg_packet_ready_msg) + + +#define AUDREC_MSG_PACKET_READY_TYPE_0 0x0000 +#define AUDREC_MSG_PACKET_READY_TYPE_1 0x0001 + +struct audrec_msg_packet_ready_msg { + unsigned short audrec_obj_idx; + unsigned short pkt_counter_msw; + unsigned short pkt_counter_lsw; + unsigned short pkt_read_cnt_msw; + unsigned short pkt_read_cnt_lsw; +} __attribute__((packed)); + +/* + * Message to notify external memory cfg done and recieved by task + */ + +#define AUDREC_MSG_CMD_AREC_MEM_CFG_DONE_MSG 0x0006 +#define AUDREC_MSG_CMD_AREC_MEM_CFG_DONE_MSG_LEN \ + sizeof(struct audrec_msg_cmd_arec_mem_cfg_done_msg) + + +struct audrec_msg_cmd_arec_mem_cfg_done_msg { + unsigned short audrec_obj_idx; +} __attribute__((packed)); + +/* + * Message to indicate Routing mode + * configuration success or failure + */ + +#define AUDREC_MSG_CMD_ROUTING_MODE_DONE_MSG 0x0007 +#define AUDREC_MSG_CMD_ROUTING_MODE_DONE_MSG_LEN \ + sizeof(struct audrec_msg_cmd_routing_mode_done_msg) + +struct audrec_msg_cmd_routing_mode_done_msg { + unsigned short configuration; +} __packed; + +/* + * Message to indicate pcm buffer configured + */ + +#define AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG 0x0008 +#define AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG_LEN \ + sizeof(struct audrec_cmd_pcm_cfg_arm_to_enc_msg) + +struct audrec_cmd_pcm_cfg_arm_to_enc_msg { + unsigned short configuration; +} __packed; + +/* + * Message to indicate encoded packet is delivered to external buffer in FTRT + */ + +#define AUDREC_UP_NT_PACKET_READY_MSG 0x0009 +#define AUDREC_UP_NT_PACKET_READY_MSG_LEN \ + sizeof(struct audrec_up_nt_packet_ready_msg) + +struct audrec_up_nt_packet_ready_msg { + unsigned short audrec_packetwrite_cnt_lsw; + unsigned short audrec_packetwrite_cnt_msw; + unsigned short audrec_upprev_readcount_lsw; + unsigned short audrec_upprev_readcount_msw; +} __packed; + +/* + * Message to indicate pcm buffer is consumed + */ + +#define AUDREC_CMD_PCM_BUFFER_PTR_UPDATE_ARM_TO_ENC_MSG 0x000A +#define AUDREC_CMD_PCM_BUFFER_PTR_UPDATE_ARM_TO_ENC_MSG_LEN \ + sizeof(struct audrec_cmd_pcm_buffer_ptr_update_arm_to_enc_msg) + +struct audrec_cmd_pcm_buffer_ptr_update_arm_to_enc_msg { + unsigned short buffer_readcnt_msw; + unsigned short buffer_readcnt_lsw; + unsigned short number_of_buffers; + unsigned short buffer_address_length[]; +} __packed; + +/* + * Message to indicate flush acknowledgement + */ + +#define AUDREC_CMD_FLUSH_DONE_MSG 0x000B + +#define ADSP_MESSAGE_ID 0xFFFF + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5jpegcmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5jpegcmdi.h new file mode 100644 index 00000000000..7f25f4703c4 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5jpegcmdi.h @@ -0,0 +1,377 @@ +#ifndef QDSP5VIDJPEGCMDI_H +#define QDSP5VIDJPEGCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + J P E G I N T E R N A L C O M M A N D S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by JPEG Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + + +$Header: //source/qcom/qct/multimedia2/AdspSvc/7XXX/qdsp5cmd/video/qdsp5jpegcmdi.h#2 $ $DateTime: 2008/07/30 10:50:23 $ $Author: pavanr $ +Revision History: +when who what, where, why +-------- --- ---------------------------------------------------------- +06/09/08 sv initial version +===========================================================================*/ + +/* + * ARM to JPEG configuration commands are passed through the + * uPJpegCfgCmdQueue + */ + +/* + * Command to configure JPEG Encoder + */ + +#define JPEG_CMD_ENC_CFG 0x0000 +#define JPEG_CMD_ENC_CFG_LEN sizeof(jpeg_cmd_enc_cfg) + +#define JPEG_CMD_ENC_PROCESS_CFG_OP_ROTATION_0 0x0000 +#define JPEG_CMD_ENC_PROCESS_CFG_OP_ROTATION_90 0x0100 +#define JPEG_CMD_ENC_PROCESS_CFG_OP_ROTATION_180 0x0200 +#define JPEG_CMD_ENC_PROCESS_CFG_OP_ROTATION_270 0x0300 +#define JPEG_CMD_ENC_PROCESS_CFG_IP_DATA_FORMAT_M 0x0003 +#define JPEG_CMD_ENC_PROCESS_CFG_IP_DATA_FORMAT_H2V2 0x0000 +#define JPEG_CMD_ENC_PROCESS_CFG_IP_DATA_FORMAT_H2V1 0x0001 +#define JPEG_CMD_ENC_PROCESS_CFG_IP_DATA_FORMAT_H1V2 0x0002 + +#define JPEG_CMD_IP_SIZE_CFG_LUMA_HEIGHT_M 0x0000FFFF +#define JPEG_CMD_IP_SIZE_CFG_LUMA_WIDTH_M 0xFFFF0000 +#define JPEG_CMD_ENC_UPSAMP_IP_SIZE_CFG_ENA 0x0001 +#define JPEG_CMD_ENC_UPSAMP_IP_SIZE_CFG_DIS 0x0000 + +#define JPEG_CMD_FRAG_SIZE_LUMA_HEIGHT_M 0xFFFF + +typedef struct { + unsigned int cmd_id; + unsigned int process_cfg; + unsigned int ip_size_cfg; + unsigned int op_size_cfg; + unsigned int frag_cfg; + unsigned int frag_cfg_part[16]; + + unsigned int part_num; + + unsigned int op_buf_0_cfg_part1; + unsigned int op_buf_0_cfg_part2; + unsigned int op_buf_1_cfg_part1; + unsigned int op_buf_1_cfg_part2; + + unsigned int luma_qunt_table[32]; + unsigned int chroma_qunt_table[32]; + + unsigned int upsamp_ip_size_cfg; + unsigned int upsamp_ip_frame_off; + unsigned int upsamp_pp_filter_coeff[64]; +} __attribute__((packed)) jpeg_cmd_enc_cfg; + +/* + * Command to configure JPEG Decoder + */ + +#define JPEG_CMD_DEC_CFG 0x0001 +#define JPEG_CMD_DEC_CFG_LEN sizeof(jpeg_cmd_dec_cfg) + +#define JPEG_CMD_DEC_OP_DATA_FORMAT_M 0x0001 +#define JPEG_CMD_DEC_OP_DATA_FORMAT_H2V2 0x0000 +#define JPEG_CMD_DEC_OP_DATA_FORMAT_H2V1 0x0001 + +#define JPEG_CMD_DEC_OP_DATA_FORMAT_SCALE_FACTOR_8 0x000000 +#define JPEG_CMD_DEC_OP_DATA_FORMAT_SCALE_FACTOR_4 0x010000 +#define JPEG_CMD_DEC_OP_DATA_FORMAT_SCALE_FACTOR_2 0x020000 +#define JPEG_CMD_DEC_OP_DATA_FORMAT_SCALE_FACTOR_1 0x030000 + +#define JPEG_CMD_DEC_IP_STREAM_BUF_CFG_PART3_NOT_FINAL 0x0000 +#define JPEG_CMD_DEC_IP_STREAM_BUF_CFG_PART3_FINAL 0x0001 + + +typedef struct { + unsigned int cmd_id; + unsigned int img_dimension_cfg; + unsigned int op_data_format; + unsigned int restart_interval; + unsigned int ip_buf_partition_num; + unsigned int ip_stream_buf_cfg_part1; + unsigned int ip_stream_buf_cfg_part2; + unsigned int ip_stream_buf_cfg_part3; + unsigned int op_stream_buf_0_cfg_part1; + unsigned int op_stream_buf_0_cfg_part2; + unsigned int op_stream_buf_0_cfg_part3; + unsigned int op_stream_buf_1_cfg_part1; + unsigned int op_stream_buf_1_cfg_part2; + unsigned int op_stream_buf_1_cfg_part3; + unsigned int luma_qunt_table_0_3; + unsigned int luma_qunt_table_4_7; + unsigned int luma_qunt_table_8_11; + unsigned int luma_qunt_table_12_15; + unsigned int luma_qunt_table_16_19; + unsigned int luma_qunt_table_20_23; + unsigned int luma_qunt_table_24_27; + unsigned int luma_qunt_table_28_31; + unsigned int luma_qunt_table_32_35; + unsigned int luma_qunt_table_36_39; + unsigned int luma_qunt_table_40_43; + unsigned int luma_qunt_table_44_47; + unsigned int luma_qunt_table_48_51; + unsigned int luma_qunt_table_52_55; + unsigned int luma_qunt_table_56_59; + unsigned int luma_qunt_table_60_63; + unsigned int chroma_qunt_table_0_3; + unsigned int chroma_qunt_table_4_7; + unsigned int chroma_qunt_table_8_11; + unsigned int chroma_qunt_table_12_15; + unsigned int chroma_qunt_table_16_19; + unsigned int chroma_qunt_table_20_23; + unsigned int chroma_qunt_table_24_27; + unsigned int chroma_qunt_table_28_31; + unsigned int chroma_qunt_table_32_35; + unsigned int chroma_qunt_table_36_39; + unsigned int chroma_qunt_table_40_43; + unsigned int chroma_qunt_table_44_47; + unsigned int chroma_qunt_table_48_51; + unsigned int chroma_qunt_table_52_55; + unsigned int chroma_qunt_table_56_59; + unsigned int chroma_qunt_table_60_63; + unsigned int luma_dc_hm_code_cnt_table_0_3; + unsigned int luma_dc_hm_code_cnt_table_4_7; + unsigned int luma_dc_hm_code_cnt_table_8_11; + unsigned int luma_dc_hm_code_cnt_table_12_15; + unsigned int luma_dc_hm_code_val_table_0_3; + unsigned int luma_dc_hm_code_val_table_4_7; + unsigned int luma_dc_hm_code_val_table_8_11; + unsigned int chroma_dc_hm_code_cnt_table_0_3; + unsigned int chroma_dc_hm_code_cnt_table_4_7; + unsigned int chroma_dc_hm_code_cnt_table_8_11; + unsigned int chroma_dc_hm_code_cnt_table_12_15; + unsigned int chroma_dc_hm_code_val_table_0_3; + unsigned int chroma_dc_hm_code_val_table_4_7; + unsigned int chroma_dc_hm_code_val_table_8_11; + unsigned int luma_ac_hm_code_cnt_table_0_3; + unsigned int luma_ac_hm_code_cnt_table_4_7; + unsigned int luma_ac_hm_code_cnt_table_8_11; + unsigned int luma_ac_hm_code_cnt_table_12_15; + unsigned int luma_ac_hm_code_val_table_0_3; + unsigned int luma_ac_hm_code_val_table_4_7; + unsigned int luma_ac_hm_code_val_table_8_11; + unsigned int luma_ac_hm_code_val_table_12_15; + unsigned int luma_ac_hm_code_val_table_16_19; + unsigned int luma_ac_hm_code_val_table_20_23; + unsigned int luma_ac_hm_code_val_table_24_27; + unsigned int luma_ac_hm_code_val_table_28_31; + unsigned int luma_ac_hm_code_val_table_32_35; + unsigned int luma_ac_hm_code_val_table_36_39; + unsigned int luma_ac_hm_code_val_table_40_43; + unsigned int luma_ac_hm_code_val_table_44_47; + unsigned int luma_ac_hm_code_val_table_48_51; + unsigned int luma_ac_hm_code_val_table_52_55; + unsigned int luma_ac_hm_code_val_table_56_59; + unsigned int luma_ac_hm_code_val_table_60_63; + unsigned int luma_ac_hm_code_val_table_64_67; + unsigned int luma_ac_hm_code_val_table_68_71; + unsigned int luma_ac_hm_code_val_table_72_75; + unsigned int luma_ac_hm_code_val_table_76_79; + unsigned int luma_ac_hm_code_val_table_80_83; + unsigned int luma_ac_hm_code_val_table_84_87; + unsigned int luma_ac_hm_code_val_table_88_91; + unsigned int luma_ac_hm_code_val_table_92_95; + unsigned int luma_ac_hm_code_val_table_96_99; + unsigned int luma_ac_hm_code_val_table_100_103; + unsigned int luma_ac_hm_code_val_table_104_107; + unsigned int luma_ac_hm_code_val_table_108_111; + unsigned int luma_ac_hm_code_val_table_112_115; + unsigned int luma_ac_hm_code_val_table_116_119; + unsigned int luma_ac_hm_code_val_table_120_123; + unsigned int luma_ac_hm_code_val_table_124_127; + unsigned int luma_ac_hm_code_val_table_128_131; + unsigned int luma_ac_hm_code_val_table_132_135; + unsigned int luma_ac_hm_code_val_table_136_139; + unsigned int luma_ac_hm_code_val_table_140_143; + unsigned int luma_ac_hm_code_val_table_144_147; + unsigned int luma_ac_hm_code_val_table_148_151; + unsigned int luma_ac_hm_code_val_table_152_155; + unsigned int luma_ac_hm_code_val_table_156_159; + unsigned int luma_ac_hm_code_val_table_160_161; + unsigned int chroma_ac_hm_code_cnt_table_0_3; + unsigned int chroma_ac_hm_code_cnt_table_4_7; + unsigned int chroma_ac_hm_code_cnt_table_8_11; + unsigned int chroma_ac_hm_code_cnt_table_12_15; + unsigned int chroma_ac_hm_code_val_table_0_3; + unsigned int chroma_ac_hm_code_val_table_4_7; + unsigned int chroma_ac_hm_code_val_table_8_11; + unsigned int chroma_ac_hm_code_val_table_12_15; + unsigned int chroma_ac_hm_code_val_table_16_19; + unsigned int chroma_ac_hm_code_val_table_20_23; + unsigned int chroma_ac_hm_code_val_table_24_27; + unsigned int chroma_ac_hm_code_val_table_28_31; + unsigned int chroma_ac_hm_code_val_table_32_35; + unsigned int chroma_ac_hm_code_val_table_36_39; + unsigned int chroma_ac_hm_code_val_table_40_43; + unsigned int chroma_ac_hm_code_val_table_44_47; + unsigned int chroma_ac_hm_code_val_table_48_51; + unsigned int chroma_ac_hm_code_val_table_52_55; + unsigned int chroma_ac_hm_code_val_table_56_59; + unsigned int chroma_ac_hm_code_val_table_60_63; + unsigned int chroma_ac_hm_code_val_table_64_67; + unsigned int chroma_ac_hm_code_val_table_68_71; + unsigned int chroma_ac_hm_code_val_table_72_75; + unsigned int chroma_ac_hm_code_val_table_76_79; + unsigned int chroma_ac_hm_code_val_table_80_83; + unsigned int chroma_ac_hm_code_val_table_84_87; + unsigned int chroma_ac_hm_code_val_table_88_91; + unsigned int chroma_ac_hm_code_val_table_92_95; + unsigned int chroma_ac_hm_code_val_table_96_99; + unsigned int chroma_ac_hm_code_val_table_100_103; + unsigned int chroma_ac_hm_code_val_table_104_107; + unsigned int chroma_ac_hm_code_val_table_108_111; + unsigned int chroma_ac_hm_code_val_table_112_115; + unsigned int chroma_ac_hm_code_val_table_116_119; + unsigned int chroma_ac_hm_code_val_table_120_123; + unsigned int chroma_ac_hm_code_val_table_124_127; + unsigned int chroma_ac_hm_code_val_table_128_131; + unsigned int chroma_ac_hm_code_val_table_132_135; + unsigned int chroma_ac_hm_code_val_table_136_139; + unsigned int chroma_ac_hm_code_val_table_140_143; + unsigned int chroma_ac_hm_code_val_table_144_147; + unsigned int chroma_ac_hm_code_val_table_148_151; + unsigned int chroma_ac_hm_code_val_table_152_155; + unsigned int chroma_ac_hm_code_val_table_156_159; + unsigned int chroma_ac_hm_code_val_table_160_161; +} __attribute__((packed)) jpeg_cmd_dec_cfg; + + +/* + * ARM to JPEG configuration commands are passed through the + * uPJpegActionCmdQueue + */ + +/* + * Command to start the encode process + */ + +#define JPEG_CMD_ENC_ENCODE 0x0001 +#define JPEG_CMD_ENC_ENCODE_LEN sizeof(jpeg_cmd_enc_encode) + + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) jpeg_cmd_enc_encode; + + +/* + * Command to transition from current state of encoder to IDLE state + */ + +#define JPEG_CMD_ENC_IDLE 0x0006 +#define JPEG_CMD_ENC_IDLE_LEN sizeof(jpeg_cmd_enc_idle) + + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) jpeg_cmd_enc_idle; + + +/* + * Command to inform the encoder that another buffer is ready + */ + +#define JPEG_CMD_ENC_OP_CONSUMED 0x0002 +#define JPEG_CMD_ENC_OP_CONSUMED_LEN sizeof(jpeg_cmd_enc_op_consumed) + + +typedef struct { + unsigned int cmd_id; + unsigned int op_buf_addr; + unsigned int op_buf_size; +} __attribute__((packed)) jpeg_cmd_enc_op_consumed; + + +/* + * Command to start the decoding process + */ + +#define JPEG_CMD_DEC_DECODE 0x0003 +#define JPEG_CMD_DEC_DECODE_LEN sizeof(jpeg_cmd_dec_decode) + + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) jpeg_cmd_dec_decode; + + +/* + * Command to transition from the current state of decoder to IDLE + */ + +#define JPEG_CMD_DEC_IDLE 0x0007 +#define JPEG_CMD_DEC_IDLE_LEN sizeof(jpeg_cmd_dec_idle) + + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) jpeg_cmd_dec_idle; + + +/* + * Command to inform that an op buffer is ready for use + */ + +#define JPEG_CMD_DEC_OP_CONSUMED 0x0004 +#define JPEG_CMD_DEC_OP_CONSUMED_LEN sizeof(jpeg_cmd_dec_op_consumed) + + +typedef struct { + unsigned int cmd_id; + unsigned int luma_op_buf_addr; + unsigned int luma_op_buf_size; + unsigned int chroma_op_buf_addr; +} __attribute__((packed)) jpeg_cmd_dec_op_consumed; + + +/* + * Command to pass a new ip buffer to the jpeg decoder + */ + +#define JPEG_CMD_DEC_IP 0x0005 +#define JPEG_CMD_DEC_IP_LEN sizeof(jpeg_cmd_dec_ip_len) + +#define JPEG_CMD_EOI_INDICATOR_NOT_END 0x0000 +#define JPEG_CMD_EOI_INDICATOR_END 0x0001 + +typedef struct { + unsigned int cmd_id; + unsigned int ip_buf_addr; + unsigned int ip_buf_size; + unsigned int eoi_indicator; +} __attribute__((packed)) jpeg_cmd_dec_ip; + + + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5jpegmsg.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5jpegmsg.h new file mode 100644 index 00000000000..993af420a86 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5jpegmsg.h @@ -0,0 +1,177 @@ +#ifndef QDSP5VIDJPEGMSGI_H +#define QDSP5VIDJPEGMSGI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + J P E G I N T E R N A L M E S S A G E S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of messages + that are sent by JPEG Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/AdspSvc/7XXX/qdsp5cmd/video/qdsp5jpegmsg.h#2 $ $DateTime: 2008/07/30 10:50:23 $ $Author: pavanr $ +Revision History: + +when who what, where, why +-------- --- ---------------------------------------------------------- +05/10/08 sv initial version +===========================================================================*/ + +/* + * Messages from JPEG task to ARM through jpeguPMsgQueue + */ + +/* + * Message is ACK for CMD_JPEGE_ENCODE cmd + */ + +#define JPEG_MSG_ENC_ENCODE_ACK 0x0000 +#define JPEG_MSG_ENC_ENCODE_ACK_LEN \ + sizeof(jpeg_msg_enc_encode_ack) + +typedef struct { +} __attribute__((packed)) jpeg_msg_enc_encode_ack; + + +/* + * Message informs the up when op buffer is ready for consumption and + * when encoding is complete or errors + */ + +#define JPEG_MSG_ENC_OP_PRODUCED 0x0001 +#define JPEG_MSG_ENC_OP_PRODUCED_LEN \ + sizeof(jpeg_msg_enc_op_produced) + +#define JPEG_MSGOP_OP_BUF_STATUS_ENC_DONE_PROGRESS 0x0000 +#define JPEG_MSGOP_OP_BUF_STATUS_ENC_DONE_COMPLETE 0x0001 +#define JPEG_MSGOP_OP_BUF_STATUS_ENC_ERR 0x10000 + +typedef struct { + unsigned int op_buf_addr; + unsigned int op_buf_size; + unsigned int op_buf_status; +} __attribute__((packed)) jpeg_msg_enc_op_produced; + + +/* + * Message to ack CMD_JPEGE_IDLE + */ + +#define JPEG_MSG_ENC_IDLE_ACK 0x0002 +#define JPEG_MSG_ENC_IDLE_ACK_LEN sizeof(jpeg_msg_enc_idle_ack) + + +typedef struct { +} __attribute__ ((packed)) jpeg_msg_enc_idle_ack; + + +/* + * Message to indicate the illegal command + */ + +#define JPEG_MSG_ENC_ILLEGAL_COMMAND 0x0003 +#define JPEG_MSG_ENC_ILLEGAL_COMMAND_LEN \ + sizeof(jpeg_msg_enc_illegal_command) + +typedef struct { + unsigned int status; +} __attribute__((packed)) jpeg_msg_enc_illegal_command; + + +/* + * Message to ACK CMD_JPEGD_DECODE + */ + +#define JPEG_MSG_DEC_DECODE_ACK 0x0004 +#define JPEG_MSG_DEC_DECODE_ACK_LEN \ + sizeof(jpeg_msg_dec_decode_ack) + + +typedef struct { +} __attribute__((packed)) jpeg_msg_dec_decode_ack; + + +/* + * Message to inform up that an op buffer is ready for consumption and when + * decoding is complete or an error occurs + */ + +#define JPEG_MSG_DEC_OP_PRODUCED 0x0005 +#define JPEG_MSG_DEC_OP_PRODUCED_LEN \ + sizeof(jpeg_msg_dec_op_produced) + +#define JPEG_MSG_DEC_OP_BUF_STATUS_PROGRESS 0x0000 +#define JPEG_MSG_DEC_OP_BUF_STATUS_DONE 0x0001 + +typedef struct { + unsigned int luma_op_buf_addr; + unsigned int chroma_op_buf_addr; + unsigned int num_mcus; + unsigned int op_buf_status; +} __attribute__((packed)) jpeg_msg_dec_op_produced; + +/* + * Message to ack CMD_JPEGD_IDLE cmd + */ + +#define JPEG_MSG_DEC_IDLE_ACK 0x0006 +#define JPEG_MSG_DEC_IDLE_ACK_LEN sizeof(jpeg_msg_dec_idle_ack) + + +typedef struct { +} __attribute__((packed)) jpeg_msg_dec_idle_ack; + + +/* + * Message to indicate illegal cmd was received + */ + +#define JPEG_MSG_DEC_ILLEGAL_COMMAND 0x0007 +#define JPEG_MSG_DEC_ILLEGAL_COMMAND_LEN \ + sizeof(jpeg_msg_dec_illegal_command) + + +typedef struct { + unsigned int status; +} __attribute__((packed)) jpeg_msg_dec_illegal_command; + +/* + * Message to request up for the next segment of ip bit stream + */ + +#define JPEG_MSG_DEC_IP_REQUEST 0x0008 +#define JPEG_MSG_DEC_IP_REQUEST_LEN \ + sizeof(jpeg_msg_dec_ip_request) + + +typedef struct { +} __attribute__((packed)) jpeg_msg_dec_ip_request; + + + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5lpmcmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5lpmcmdi.h new file mode 100644 index 00000000000..4ab6cbf44a7 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5lpmcmdi.h @@ -0,0 +1,82 @@ +#ifndef QDSP5LPMCMDI_H +#define QDSP5LPMCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + L P M I N T E R N A L C O M M A N D S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by LPM Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + + +$Header: //source/qcom/qct/multimedia2/AdspSvc/7XXX/qdsp5cmd/video/qdsp5lpmcmdi.h#2 $ $DateTime: 2008/07/30 10:50:23 $ $Author: pavanr $ +Revision History: + +when who what, where, why +-------- --- ---------------------------------------------------------- +06/12/08 sv initial version +===========================================================================*/ + + +/* + * Command to start LPM processing based on the config params + */ + +#define LPM_CMD_START 0x0000 +#define LPM_CMD_START_LEN sizeof(lpm_cmd_start) + +#define LPM_CMD_SPATIAL_FILTER_PART_OPMODE_0 0x00000000 +#define LPM_CMD_SPATIAL_FILTER_PART_OPMODE_1 0x00010000 +typedef struct { + unsigned int cmd_id; + unsigned int ip_data_cfg_part1; + unsigned int ip_data_cfg_part2; + unsigned int ip_data_cfg_part3; + unsigned int ip_data_cfg_part4; + unsigned int op_data_cfg_part1; + unsigned int op_data_cfg_part2; + unsigned int op_data_cfg_part3; + unsigned int spatial_filter_part[32]; +} __attribute__((packed)) lpm_cmd_start; + + + +/* + * Command to stop LPM processing + */ + +#define LPM_CMD_IDLE 0x0001 +#define LPM_CMD_IDLE_LEN sizeof(lpm_cmd_idle) + +typedef struct { + unsigned int cmd_id; +} __attribute__((packed)) lpm_cmd_idle; + + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5lpmmsg.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5lpmmsg.h new file mode 100644 index 00000000000..68f8874ab93 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5lpmmsg.h @@ -0,0 +1,80 @@ +#ifndef QDSP5LPMMSGI_H +#define QDSP5LPMMSGI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + L P M I N T E R N A L M E S S A G E S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by LPM Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/AdspSvc/7XXX/qdsp5cmd/video/qdsp5lpmmsg.h#2 $ $DateTime: 2008/07/30 10:50:23 $ $Author: pavanr $ +Revision History: + +when who what, where, why +-------- --- ---------------------------------------------------------- +06/12/08 sv initial version +===========================================================================*/ + +/* + * Message to acknowledge CMD_LPM_IDLE command + */ + +#define LPM_MSG_IDLE_ACK 0x0000 +#define LPM_MSG_IDLE_ACK_LEN sizeof(lpm_msg_idle_ack) + +typedef struct { +} __attribute__((packed)) lpm_msg_idle_ack; + + +/* + * Message to acknowledge CMD_LPM_START command + */ + + +#define LPM_MSG_START_ACK 0x0001 +#define LPM_MSG_START_ACK_LEN sizeof(lpm_msg_start_ack) + + +typedef struct { +} __attribute__((packed)) lpm_msg_start_ack; + + +/* + * Message to notify the ARM that LPM processing is complete + */ + +#define LPM_MSG_DONE 0x0002 +#define LPM_MSG_DONE_LEN sizeof(lpm_msg_done) + +typedef struct { +} __attribute__((packed)) lpm_msg_done; + + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5rmtcmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5rmtcmdi.h new file mode 100644 index 00000000000..7a66b687d3c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5rmtcmdi.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef QDSP5RMTCMDI_H +#define QDSP5RMTCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + R M T A S K I N T E R N A L C O M M A N D S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by RM Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ + +/* + * ARM to RMTASK Commands + * + * ARM uses one command queue to communicate with AUDPPTASK + * 1) apuRmtQueue: Used to send commands to RMTASK from APPS processor + * Location : MEMA + * Buffer Size : 3 words + */ + +#define RM_CMD_AUD_CODEC_CFG 0x0 + +#define RM_AUD_CLIENT_ID 0x0 +#define RMT_ENABLE 0x1 +#define RMT_DISABLE 0x0 + +struct aud_codec_config_cmd { + unsigned short cmd_id; + unsigned char task_id; + unsigned char client_id; + unsigned short enable; + unsigned short dec_type; +} __attribute__((packed)); + +#endif /* QDSP5RMTCMDI_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5rmtmsg.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5rmtmsg.h new file mode 100644 index 00000000000..a890e76f5f5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5rmtmsg.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef QDSP5RMTMSG_H +#define QDSP5RMTMSG_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + R M T A S K M S G + +GENERAL DESCRIPTION + Messages sent by RMTASK to APPS PROCESSOR + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ + +/* + * RMTASK uses RmtApuRlist to send messages to the APPS PROCESSOR + * Location : MEMA + * Buffer Size : 3 + */ + +#define RMT_CODEC_CONFIG_ACK 0x1 + +struct aud_codec_config_ack { + unsigned char task_id; + unsigned char client_id; + unsigned char reason; + unsigned char enable; + unsigned short dec_type; +} __attribute__((packed)); + +#define RMT_DSP_OUT_OF_MIPS 0x2 + +struct rmt_dsp_out_of_mips { + unsigned short dec_info; + unsigned short rvd_0; + unsigned short rvd_1; +} __attribute__((packed)); + +#endif /* QDSP5RMTMSG_H */ + diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vdeccmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vdeccmdi.h new file mode 100644 index 00000000000..1064b176265 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vdeccmdi.h @@ -0,0 +1,189 @@ +#ifndef QDSP5VIDDECCMDI_H +#define QDSP5VIDDECCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + V I D E O D E C O D E R I N T E R N A L C O M M A N D S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by VIDDEC Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/AdspSvc/7XXX/qdsp5cmd/video/qdsp5vdeccmdi.h#2 $ $DateTime: 2008/07/30 10:50:23 $ $Author: pavanr $ +Revision History: + +when who what, where, why +-------- --- ---------------------------------------------------------- +05/10/08 ac initial version +===========================================================================*/ + + +/* + * Command to inform VIDDEC that new subframe packet is ready + */ + +#define VIDDEC_CMD_SUBFRAME_PKT 0x0000 +#define VIDDEC_CMD_SUBFRAME_PKT_LEN \ + sizeof(viddec_cmd_subframe_pkt) + +#define VIDDEC_CMD_SF_INFO_1_DM_DMA_STATS_EXCHANGE_FLAG_DM 0x0000 +#define VIDDEC_CMD_SF_INFO_1_DM_DMA_STATS_EXCHANGE_FLAG_DMA 0x0001 + +#define VIDDEC_CMD_SF_INFO_0_SUBFRAME_CONTI 0x0000 +#define VIDDEC_CMD_SF_INFO_0_SUBFRAME_FIRST 0x0001 +#define VIDDEC_CMD_SF_INFO_0_SUBFRAME_LAST 0x0002 +#define VIDDEC_CMD_SF_INFO_0_SUBFRAME_FIRST_AND_LAST 0x0003 + +#define VIDDEC_CMD_CODEC_SELECTION_WORD_MPEG_4 0x0000 +#define VIDDEC_CMD_CODEC_SELECTION_WORD_H_263_P0 0x0001 +#define VIDDEC_CMD_CODEC_SELECTION_WORD_H_264 0x0002 +#define VIDDEC_CMD_CODEC_SELECTION_WORD_H_263_p3 0x0003 +#define VIDDEC_CMD_CODEC_SELECTION_WORD_RV9 0x0004 +#define VIDDEC_CMD_CODEC_SELECTION_WORD_WMV9 0x0005 +#define VIDDEC_CMD_CODEC_SELECTION_WORD_SMCDB 0x0006 +#define VIDDEC_CMD_CODEC_SELECTION_WORD_QFRE 0x0007 +#define VIDDEC_CMD_CODEC_SELECTION_WORD_VLD 0x0008 + +typedef struct { + unsigned short cmd_id; + unsigned short packet_seq_number; + unsigned short codec_instance_id; + unsigned short subframe_packet_size_high; + unsigned short subframe_packet_size_low; + unsigned short subframe_packet_high; + unsigned short subframe_packet_low; + unsigned short subframe_packet_partition; + unsigned short statistics_packet_size_high; + unsigned short statistics_packet_size_low; + unsigned short statistics_packet_high; + unsigned short statistics_packet_low; + unsigned short statistics_partition; + unsigned short subframe_info_1; + unsigned short subframe_info_0; + unsigned short codec_selection_word; + unsigned short num_mbs; +} __attribute__((packed)) viddec_cmd_subframe_pkt; + + +/* + * Command to inform VIDDEC task that post processing is required for the frame + */ + +#define VIDDEC_CMD_PP_ENABLE 0x0001 +#define VIDDEC_CMD_PP_ENABLE_LEN \ + sizeof(viddec_cmd_pp_enable) + +#define VIDDEC_CMD_PP_INFO_0_DM_DMA_LS_EXCHANGE_FLAG_DM 0x0000 +#define VIDDEC_CMD_PP_INFO_0_DM_DMA_LS_EXCHANGE_FLAG_DMA 0x0001 + +typedef struct { + unsigned short cmd_id; + unsigned short packet_seq_num; + unsigned short codec_instance_id; + unsigned short postproc_info_0; + unsigned short codec_selection_word; + unsigned short pp_output_addr_high; + unsigned short pp_output_addr_low; + unsigned short postproc_info_1; + unsigned short load_sharing_packet_size_high; + unsigned short load_sharing_packet_size_low; + unsigned short load_sharing_packet_high; + unsigned short load_sharing_packet_low; + unsigned short load_sharing_partition; + unsigned short pp_param_0; + unsigned short pp_param_1; + unsigned short pp_param_2; + unsigned short pp_param_3; +} __attribute__((packed)) viddec_cmd_pp_enable; + + +/* + * FRAME Header Packet : It is at the start of new frame + */ + +#define VIDDEC_CMD_FRAME_HEADER_PACKET 0x0002 + +#define VIDDEC_CMD_FRAME_INFO_0_ERROR_SKIP 0x0000 +#define VIDDEC_CMD_FRAME_INFO_0_ERROR_BLACK 0x0800 + +/* + * SLICE HEADER PACKET + * I-Slice and P-Slice + */ + +#define VIDDEC_CMD_SLICE_HEADER_PKT_ISLICE 0x0003 +#define VIDDEC_CMD_SLICE_HEADER_PKT_ISLICE_LEN \ + sizeof(viddec_cmd_slice_header_pkt_islice) + +#define VIDDEC_CMD_ISLICE_INFO_1_MOD_SLICE_TYPE_PSLICE 0x0000 +#define VIDDEC_CMD_ISLICE_INFO_1_MOD_SLICE_TYPE_BSLICE 0x0100 +#define VIDDEC_CMD_ISLICE_INFO_1_MOD_SLICE_TYPE_ISLICE 0x0200 +#define VIDDEC_CMD_ISLICE_INFO_1_MOD_SLICE_TYPE_SPSLICE 0x0300 +#define VIDDEC_CMD_ISLICE_INFO_1_MOD_SLICE_TYPE_SISLICE 0x0400 +#define VIDDEC_CMD_ISLICE_INFO_1_NOPADDING 0x0000 +#define VIDDEC_CMD_ISLICE_INFO_1_PADDING 0x0800 + +#define VIDDEC_CMD_ISLICE_EOP_MARKER 0x7FFF + +typedef struct { + unsigned short cmd_id; + unsigned short packet_id; + unsigned short slice_info_0; + unsigned short slice_info_1; + unsigned short slice_info_2; + unsigned short num_bytes_in_rbsp_high; + unsigned short num_bytes_in_rbsp_low; + unsigned short num_bytes_in_rbsp_consumed; + unsigned short end_of_packet_marker; +} __attribute__((packed)) viddec_cmd_slice_header_pkt_islice; + + +#define VIDDEC_CMD_SLICE_HEADER_PKT_PSLICE 0x0003 +#define VIDDEC_CMD_SLICE_HEADER_PKT_PSLICE_LEN \ + sizeof(viddec_cmd_slice_header_pkt_pslice) + + +typedef struct { + unsigned short cmd_id; + unsigned short packet_id; + unsigned short slice_info_0; + unsigned short slice_info_1; + unsigned short slice_info_2; + unsigned short slice_info_3; + unsigned short refidx_l0_map_tab_info_0; + unsigned short refidx_l0_map_tab_info_1; + unsigned short refidx_l0_map_tab_info_2; + unsigned short refidx_l0_map_tab_info_3; + unsigned short num_bytes_in_rbsp_high; + unsigned short num_bytes_in_rbsp_low; + unsigned short num_bytes_in_rbsp_consumed; + unsigned short end_of_packet_marker; +} __attribute__((packed)) viddec_cmd_slice_header_pkt_pslice; + + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vdecmsg.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vdecmsg.h new file mode 100644 index 00000000000..2d3ab89e986 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vdecmsg.h @@ -0,0 +1,107 @@ +#ifndef QDSP5VIDDECMSGI_H +#define QDSP5VIDDECMSGI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + V I D E O D E C O D E R I N T E R N A L M E S S A G E S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of messages + that are sent by VIDDEC Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/AdspSvc/7XXX/qdsp5cmd/video/qdsp5vdecmsg.h#2 $ $DateTime: 2008/07/30 10:50:23 $ $Author: pavanr $ +Revision History: + +when who what, where, why +-------- --- ---------------------------------------------------------- +05/10/08 ac initial version +===========================================================================*/ + +/* + * Message to inform ARM which VDEC_SUBFRAME_PKT_CMD processed by VIDDEC TASK + */ + +#define VIDDEC_MSG_SUBF_DONE 0x0000 +#define VIDDEC_MSG_SUBF_DONE_LEN \ + sizeof(viddec_msg_subf_done) + +typedef struct { + unsigned short packet_seq_number; + unsigned short codec_instance_id; +} __attribute__((packed)) viddec_msg_subf_done; + + +/* + * Message to inform ARM one frame has been decoded + */ + +#define VIDDEC_MSG_FRAME_DONE 0x0001 +#define VIDDEC_MSG_FRAME_DONE_LEN \ + sizeof(viddec_msg_frame_done) + +typedef struct { + unsigned short packet_seq_number; + unsigned short codec_instance_id; +} __attribute__((packed)) viddec_msg_frame_done; + + +/* + * Message to inform ARM that post processing frame has been decoded + */ + +#define VIDDEC_MSG_PP_ENABLE_CMD_DONE 0x0002 +#define VIDDEC_MSG_PP_ENABLE_CMD_DONE_LEN \ + sizeof(viddec_msg_pp_enable_cmd_done) + +typedef struct { + unsigned short packet_seq_number; + unsigned short codec_instance_id; +} __attribute__((packed)) viddec_msg_pp_enable_cmd_done; + + +/* + * Message to inform ARM that one post processing frame has been decoded + */ + + +#define VIDDEC_MSG_PP_FRAME_DONE 0x0003 +#define VIDDEC_MSG_PP_FRAME_DONE_LEN \ + sizeof(viddec_msg_pp_frame_done) + +#define VIDDEC_MSG_DISP_WORTHY_DISP 0x0000 +#define VIDDEC_MSG_DISP_WORTHY_DISP_NONE 0xFFFF + + +typedef struct { + unsigned short packet_seq_number; + unsigned short codec_instance_id; + unsigned short display_worthy; +} __attribute__((packed)) viddec_msg_pp_frame_done; + + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5venccmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5venccmdi.h new file mode 100644 index 00000000000..b3c018fc31a --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5venccmdi.h @@ -0,0 +1,231 @@ +#ifndef QDSP5VIDENCCMDI_H +#define QDSP5VIDENCCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + V I D E O E N C O D E R I N T E R N A L C O M M A N D S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by VIDENC Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +Revision History: + +when who what, where, why +-------- --- ---------------------------------------------------------- +09/25/08 umeshp initial version +===========================================================================*/ + + #define VIDENC_CMD_CFG 0x0000 + #define VIDENC_CMD_ACTIVE 0x0001 + #define VIDENC_CMD_IDLE 0x0002 + #define VIDENC_CMD_FRAME_START 0x0003 + #define VIDENC_CMD_STATUS_QUERY 0x0004 + #define VIDENC_CMD_RC_CFG 0x0005 + #define VIDENC_CMD_INTRA_REFRESH 0x0006 + #define VIDENC_CMD_CODEC_CONFIG 0x0007 + #define VIDENC_CMD_VIDEO_CONFIG 0x0008 + #define VIDENC_CMD_PARAMETER_UPDATE 0x0009 + #define VIDENC_CMD_VENC_CLOCK 0x000A + #define VIDENC_CMD_DIS_CFG 0x000B + #define VIDENC_CMD_DIS 0x000C + #define VIDENC_CMD_DIGITAL_ZOOM 0x000D + + + + +/* + * Command to pass the frame message information to VIDENC + */ + + +#define VIDENC_CMD_FRAME_START_LEN \ + sizeof(videnc_cmd_frame_start) + +typedef struct { + unsigned short cmd_id; + unsigned short frame_info; + unsigned short frame_rho_budget_word_high; + unsigned short frame_rho_budget_word_low; + unsigned short input_luma_addr_high; + unsigned short input_luma_addr_low; + unsigned short input_chroma_addr_high; + unsigned short input_chroma_addr_low; + unsigned short ref_vop_buf_ptr_high; + unsigned short ref_vop_buf_ptr_low; + unsigned short enc_pkt_buf_ptr_high; + unsigned short enc_pkt_buf_ptr_low; + unsigned short enc_pkt_buf_size_high; + unsigned short enc_pkt_buf_size_low; + unsigned short unfilt_recon_vop_buf_ptr_high; + unsigned short unfilt_recon_vop_buf_ptr_low; + unsigned short filt_recon_vop_buf_ptr_high; + unsigned short filt_recon_vop_buf_ptr_low; +} __attribute__((packed)) videnc_cmd_frame_start; + +/* + * Command to pass the frame-level digital stabilization parameters to VIDENC + */ + + +#define VIDENC_CMD_DIS_LEN \ + sizeof(videnc_cmd_dis) + +typedef struct { + unsigned short cmd_id; + unsigned short vfe_out_prev_luma_addr_high; + unsigned short vfe_out_prev_luma_addr_low; + unsigned short stabilization_info; +} __attribute__((packed)) videnc_cmd_dis; + +/* + * Command to pass the codec related parameters to VIDENC + */ + + +#define VIDENC_CMD_CFG_LEN \ + sizeof(videnc_cmd_cfg) + +typedef struct { + unsigned short cmd_id; + unsigned short cfg_info_0; + unsigned short cfg_info_1; + unsigned short four_mv_threshold; + unsigned short ise_fse_mv_cost_fac; + unsigned short venc_frame_dim; + unsigned short venc_DM_partition; +} __attribute__((packed)) videnc_cmd_cfg; + +/* + * Command to start the video encoding + */ + + +#define VIDENC_CMD_ACTIVE_LEN \ + sizeof(videnc_cmd_active) + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) videnc_cmd_active; + +/* + * Command to stop the video encoding + */ + + +#define VIDENC_CMD_IDLE_LEN \ + sizeof(videnc_cmd_idle) + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) videnc_cmd_idle; + +/* + * Command to query staus of VIDENC + */ + + +#define VIDENC_CMD_STATUS_QUERY_LEN \ + sizeof(videnc_cmd_status_query) + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) videnc_cmd_status_query; + +/* + * Command to set rate control for a frame + */ + + +#define VIDENC_CMD_RC_CFG_LEN \ + sizeof(videnc_cmd_rc_cfg) + +typedef struct { + unsigned short cmd_id; + unsigned short max_frame_qp_delta; + unsigned short max_min_frame_qp; +} __attribute__((packed)) videnc_cmd_rc_cfg; + +/* + * Command to set intra-refreshing + */ + + +#define VIDENC_CMD_INTRA_REFRESH_LEN \ + sizeof(videnc_cmd_intra_refresh) + +typedef struct { + unsigned short cmd_id; + unsigned short num_mb_refresh; + unsigned short mb_index[15]; +} __attribute__((packed)) videnc_cmd_intra_refresh; + +/* + * Command to pass digital zoom information to the VIDENC + */ +#define VIDENC_CMD_DIGITAL_ZOOM_LEN \ + sizeof(videnc_cmd_digital_zoom) + +typedef struct { + unsigned short cmd_id; + unsigned short digital_zoom_en; + unsigned short luma_frame_shift_X; + unsigned short luma_frame_shift_Y; + unsigned short up_ip_luma_rows; + unsigned short up_ip_luma_cols; + unsigned short up_ip_chroma_rows; + unsigned short up_ip_chroma_cols; + unsigned short luma_ph_incr_V_low; + unsigned short luma_ph_incr_V_high; + unsigned short luma_ph_incr_H_low; + unsigned short luma_ph_incr_H_high; + unsigned short chroma_ph_incr_V_low; + unsigned short chroma_ph_incr_V_high; + unsigned short chroma_ph_incr_H_low; + unsigned short chroma_ph_incr_H_high; +} __attribute__((packed)) videnc_cmd_digital_zoom; + +/* + * Command to configure digital stabilization parameters + */ + +#define VIDENC_CMD_DIS_CFG_LEN \ + sizeof(videnc_cmd_dis_cfg) + +typedef struct { + unsigned short cmd_id; + unsigned short image_stab_subf_start_row_col; + unsigned short image_stab_subf_dim; + unsigned short image_stab_info_0; +} __attribute__((packed)) videnc_cmd_dis_cfg; + + +/* + * Command to set VIDENC_CMD_VENC_CLOCK + */ + + +#define VIDENC_CMD_VENC_CLOCK_LEN \ + sizeof(struct videnc_cmd_venc_clock) + +struct videnc_cmd_venc_clock { + unsigned short cmd_id; + unsigned short payload; +} __attribute__((packed)) ; + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vfecmdi.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vfecmdi.h new file mode 100644 index 00000000000..4c5d752ab42 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vfecmdi.h @@ -0,0 +1,910 @@ +#ifndef QDSP5VFECMDI_H +#define QDSP5VFECMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + V F E I N T E R N A L C O M M A N D S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by VFE Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/AdspSvc/7XXX/qdsp5cmd/video/qdsp5vfecmdi.h#2 $ $DateTime: 2008/07/30 10:50:23 $ $Author: pavanr $ +Revision History: + +when who what, where, why +-------- --- ---------------------------------------------------------- +06/12/08 sv initial version +===========================================================================*/ + +/****************************************************************************** + * Commands through vfeCommandScaleQueue + *****************************************************************************/ + +/* + * Command to program scaler for op1 . max op of scaler is VGA + */ + + +#define VFE_CMD_SCALE_OP1_CFG 0x0000 +#define VFE_CMD_SCALE_OP1_CFG_LEN \ + sizeof(vfe_cmd_scale_op1_cfg) + +#define VFE_CMD_SCALE_OP1_SEL_IP_SEL_Y_STANDARD 0x0000 +#define VFE_CMD_SCALE_OP1_SEL_IP_SEL_Y_CASCADED 0x0001 +#define VFE_CMD_SCALE_OP1_SEL_H_Y_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP1_SEL_H_Y_SCALER_ENA 0x0002 +#define VFE_CMD_SCALE_OP1_SEL_H_PP_Y_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP1_SEL_H_PP_Y_SCALER_ENA 0x0004 +#define VFE_CMD_SCALE_OP1_SEL_V_Y_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP1_SEL_V_Y_SCALER_ENA 0x0008 +#define VFE_CMD_SCALE_OP1_SEL_V_PP_Y_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP1_SEL_V_PP_Y_SCALER_ENA 0x0010 +#define VFE_CMD_SCALE_OP1_SEL_IP_SEL_CBCR_STANDARD 0x0000 +#define VFE_CMD_SCALE_OP1_SEL_IP_SEL_CBCR_CASCADED 0x0020 +#define VFE_CMD_SCALE_OP1_SEL_H_CBCR_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP1_SEL_H_CBCR_SCALER_ENA 0x0040 +#define VFE_CMD_SCALE_OP1_SEL_V_CBCR_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP1_SEL_V_CBCR_SCALER_ENA 0x0080 + +#define VFE_CMD_OP1_PP_Y_SCALER_CFG_PART1_DONT_LOAD_COEFFS 0x80000000 +#define VFE_CMD_OP1_PP_Y_SCALER_CFG_PART1_LOAD_COEFFS 0x80000000 + +typedef struct { + unsigned int cmd_id; + unsigned int scale_op1_sel; + unsigned int y_scaler_cfg_part1; + unsigned int y_scaler_cfg_part2; + unsigned int cbcr_scaler_cfg_part1; + unsigned int cbcr_scaler_cfg_part2; + unsigned int cbcr_scaler_cfg_part3; + unsigned int pp_y_scaler_cfg_part1; + unsigned int pp_y_scaler_cfg_part2; + unsigned int y_scaler_v_coeff_bank_part1[16]; + unsigned int y_scaler_v_coeff_bank_part2[16]; + unsigned int y_scaler_h_coeff_bank_part1[16]; + unsigned int y_scaler_h_coeff_bank_part2[16]; +} __attribute__((packed)) vfe_cmd_scale_op1_cfg; + + +/* + * Command to program scaler for op2 + */ + +#define VFE_CMD_SCALE_OP2_CFG 0x0001 +#define VFE_CMD_SCALE_OP2_CFG_LEN \ + sizeof(vfe_cmd_scale_op2_cfg) + +#define VFE_CMD_SCALE_OP2_SEL_IP_SEL_Y_STANDARD 0x0000 +#define VFE_CMD_SCALE_OP2_SEL_IP_SEL_Y_CASCADED 0x0001 +#define VFE_CMD_SCALE_OP2_SEL_H_Y_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP2_SEL_H_Y_SCALER_ENA 0x0002 +#define VFE_CMD_SCALE_OP2_SEL_H_PP_Y_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP2_SEL_H_PP_Y_SCALER_ENA 0x0004 +#define VFE_CMD_SCALE_OP2_SEL_V_Y_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP2_SEL_V_Y_SCALER_ENA 0x0008 +#define VFE_CMD_SCALE_OP2_SEL_V_PP_Y_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP2_SEL_V_PP_Y_SCALER_ENA 0x0010 +#define VFE_CMD_SCALE_OP2_SEL_IP_SEL_CBCR_STANDARD 0x0000 +#define VFE_CMD_SCALE_OP2_SEL_IP_SEL_CBCR_CASCADED 0x0020 +#define VFE_CMD_SCALE_OP2_SEL_H_CBCR_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP2_SEL_H_CBCR_SCALER_ENA 0x0040 +#define VFE_CMD_SCALE_OP2_SEL_V_CBCR_SCALER_DIS 0x0000 +#define VFE_CMD_SCALE_OP2_SEL_V_CBCR_SCALER_ENA 0x0080 + +#define VFE_CMD_OP2_PP_Y_SCALER_CFG_PART1_DONT_LOAD_COEFFS 0x80000000 +#define VFE_CMD_OP2_PP_Y_SCALER_CFG_PART1_LOAD_COEFFS 0x80000000 + +typedef struct { + unsigned int cmd_id; + unsigned int scale_op2_sel; + unsigned int y_scaler_cfg_part1; + unsigned int y_scaler_cfg_part2; + unsigned int cbcr_scaler_cfg_part1; + unsigned int cbcr_scaler_cfg_part2; + unsigned int cbcr_scaler_cfg_part3; + unsigned int pp_y_scaler_cfg_part1; + unsigned int pp_y_scaler_cfg_part2; + unsigned int y_scaler_v_coeff_bank_part1[16]; + unsigned int y_scaler_v_coeff_bank_part2[16]; + unsigned int y_scaler_h_coeff_bank_part1[16]; + unsigned int y_scaler_h_coeff_bank_part2[16]; +} __attribute__((packed)) vfe_cmd_scale_op2_cfg; + + +/****************************************************************************** + * Commands through vfeCommandTableQueue + *****************************************************************************/ + +/* + * Command to program the AXI ip paths + */ + +#define VFE_CMD_AXI_IP_CFG 0x0000 +#define VFE_CMD_AXI_IP_CFG_LEN sizeof(vfe_cmd_axi_ip_cfg) + +#define VFE_CMD_IP_SEL_IP_FORMAT_8 0x0000 +#define VFE_CMD_IP_SEL_IP_FORMAT_10 0x0001 +#define VFE_CMD_IP_SEL_IP_FORMAT_12 0x0002 + +typedef struct { + unsigned int cmd_id; + unsigned int ip_sel; + unsigned int ip_cfg_part1; + unsigned int ip_cfg_part2; + unsigned int ip_unpack_cfg_part[6]; + unsigned int ip_buf_addr[8]; +} __attribute__ ((packed)) vfe_cmd_axi_ip_cfg; + + +/* + * Command to program axi op paths + */ + +#define VFE_CMD_AXI_OP_CFG 0x0001 +#define VFE_CMD_AXI_OP_CFG_LEN sizeof(vfe_cmd_axi_op_cfg) + +#define VFE_CMD_OP_SEL_OP1 0x0000 +#define VFE_CMD_OP_SEL_OP2 0x0001 +#define VFE_CMD_OP_SEL_OP1_OP2 0x0002 +#define VFE_CMD_OP_SEL_CTOA 0x0003 +#define VFE_CMD_OP_SEL_CTOA_OP1 0x0004 +#define VFE_CMD_OP_SEL_CTOA_OP2 0x0005 +#define VFE_CMD_OP_SEL_OP_FORMAT_8 0x0000 +#define VFE_CMD_OP_SEL_OP_FORMAT_10 0x0008 +#define VFE_CMD_OP_SEL_OP_FORMAT_12 0x0010 + + +typedef struct { + unsigned int cmd_id; + unsigned int op_sel; + unsigned int op1_y_cfg_part1; + unsigned int op1_y_cfg_part2; + unsigned int op1_cbcr_cfg_part1; + unsigned int op1_cbcr_cfg_part2; + unsigned int op2_y_cfg_part1; + unsigned int op2_y_cfg_part2; + unsigned int op2_cbcr_cfg_part1; + unsigned int op2_cbcr_cfg_part2; + unsigned int op1_buf1_addr[16]; + unsigned int op2_buf1_addr[16]; +} __attribute__((packed)) vfe_cmd_axi_op_cfg; + + + + +/* + * Command to program the roll off correction module + */ + +#define VFE_CMD_ROLLOFF_CFG 0x0002 +#define VFE_CMD_ROLLOFF_CFG_LEN \ + sizeof(vfe_cmd_rolloff_cfg) + + +typedef struct { + unsigned int cmd_id; + unsigned int correction_opt_center_pos; + unsigned int radius_square_entry[32]; + unsigned int red_table_entry[32]; + unsigned int green_table_entry[32]; + unsigned int blue_table_entry[32]; +} __attribute__((packed)) vfe_cmd_rolloff_cfg; + +/* + * Command to program RGB gamma table + */ + +#define VFE_CMD_RGB_GAMMA_CFG 0x0003 +#define VFE_CMD_RGB_GAMMA_CFG_LEN \ + sizeof(vfe_cmd_rgb_gamma_cfg) + +#define VFE_CMD_RGB_GAMMA_SEL_LINEAR 0x0000 +#define VFE_CMD_RGB_GAMMA_SEL_PW_LINEAR 0x0001 +typedef struct { + unsigned int cmd_id; + unsigned int rgb_gamma_sel; + unsigned int rgb_gamma_entry[256]; +} __attribute__((packed)) vfe_cmd_rgb_gamma_cfg; + + +/* + * Command to program luma gamma table for the noise reduction path + */ + +#define VFE_CMD_Y_GAMMA_CFG 0x0004 +#define VFE_CMD_Y_GAMMA_CFG_LEN \ + sizeof(vfe_cmd_y_gamma_cfg) + +#define VFE_CMD_Y_GAMMA_SEL_LINEAR 0x0000 +#define VFE_CMD_Y_GAMMA_SEL_PW_LINEAR 0x0001 + +typedef struct { + unsigned int cmd_id; + unsigned int y_gamma_sel; + unsigned int y_gamma_entry[256]; +} __attribute__((packed)) vfe_cmd_y_gamma_cfg; + + + +/****************************************************************************** + * Commands through vfeCommandQueue + *****************************************************************************/ + +/* + * Command to reset the VFE to a known good state.All previously programmed + * Params will be lost + */ + + +#define VFE_CMD_RESET 0x0000 +#define VFE_CMD_RESET_LEN sizeof(vfe_cmd_reset) + + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) vfe_cmd_reset; + + +/* + * Command to start VFE processing based on the config params + */ + + +#define VFE_CMD_START 0x0001 +#define VFE_CMD_START_LEN sizeof(vfe_cmd_start) + +#define VFE_CMD_STARTUP_PARAMS_SRC_CAMIF 0x0000 +#define VFE_CMD_STARTUP_PARAMS_SRC_AXI 0x0001 +#define VFE_CMD_STARTUP_PARAMS_MODE_CONTINUOUS 0x0000 +#define VFE_CMD_STARTUP_PARAMS_MODE_SNAPSHOT 0x0002 + +#define VFE_CMD_IMAGE_PL_BLACK_LVL_CORR_DIS 0x0000 +#define VFE_CMD_IMAGE_PL_BLACK_LVL_CORR_ENA 0x0001 +#define VFE_CMD_IMAGE_PL_ROLLOFF_CORR_DIS 0x0000 +#define VFE_CMD_IMAGE_PL_ROLLOFF_CORR_ENA 0x0002 +#define VFE_CMD_IMAGE_PL_WHITE_BAL_DIS 0x0000 +#define VFE_CMD_IMAGE_PL_WHITE_BAL_ENA 0x0004 +#define VFE_CMD_IMAGE_PL_RGB_GAMMA_DIS 0x0000 +#define VFE_CMD_IMAGE_PL_RGB_GAMMA_ENA 0x0008 +#define VFE_CMD_IMAGE_PL_LUMA_NOISE_RED_PATH_DIS 0x0000 +#define VFE_CMD_IMAGE_PL_LUMA_NOISE_RED_PATH_ENA 0x0010 +#define VFE_CMD_IMAGE_PL_ADP_FILTER_DIS 0x0000 +#define VFE_CMD_IMAGE_PL_ADP_FILTER_ENA 0x0020 +#define VFE_CMD_IMAGE_PL_CHROMA_SAMP_DIS 0x0000 +#define VFE_CMD_IMAGE_PL_CHROMA_SAMP_ENA 0x0040 + + +typedef struct { + unsigned int cmd_id; + unsigned int startup_params; + unsigned int image_pipeline; + unsigned int frame_dimension; +} __attribute__((packed)) vfe_cmd_start; + + +/* + * Command to halt all processing + */ + +#define VFE_CMD_STOP 0x0002 +#define VFE_CMD_STOP_LEN sizeof(vfe_cmd_stop) + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) vfe_cmd_stop; + + +/* + * Command to commit the params that have been programmed to take + * effect on the next frame + */ + +#define VFE_CMD_UPDATE 0x0003 +#define VFE_CMD_UPDATE_LEN sizeof(vfe_cmd_update) + + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) vfe_cmd_update; + + +/* + * Command to program CAMIF module + */ + +#define VFE_CMD_CAMIF_CFG 0x0004 +#define VFE_CMD_CAMIF_CFG_LEN sizeof(vfe_cmd_camif_cfg) + +#define VFE_CMD_CFG_VSYNC_SYNC_EDGE_HIGH 0x0000 +#define VFE_CMD_CFG_VSYNC_SYNC_EDGE_LOW 0x0002 +#define VFE_CMD_CFG_HSYNC_SYNC_EDGE_HIGH 0x0000 +#define VFE_CMD_CFG_HSYNC_SYNC_EDGE_LOW 0x0004 +#define VFE_CMD_CFG_SYNC_MODE_APS 0x0000 +#define VFE_CMD_CFG_SYNC_MODE_EFS 0X0008 +#define VFE_CMD_CFG_SYNC_MODE_ELS 0x0010 +#define VFE_CMD_CFG_SYNC_MODE_RVD 0x0018 +#define VFE_CMD_CFG_VFE_SUBSAMP_EN_DIS 0x0000 +#define VFE_CMD_CFG_VFE_SUBSAMP_EN_ENA 0x0020 +#define VFE_CMD_CFG_BUS_SUBSAMP_EN_DIS 0x0000 +#define VFE_CMD_CFG_BUS_SUBSAMP_EN_ENA 0x0080 +#define VFE_CMD_CFG_IRQ_SUBSAMP_EN_DIS 0x0000 +#define VFE_CMD_CFG_IRQ_SUBSAMP_EN_ENA 0x0800 + +#define VFE_CMD_SUBSAMP2_CFG_PIXEL_SKIP_16 0x0000 +#define VFE_CMD_SUBSAMP2_CFG_PIXEL_SKIP_12 0x0010 + +#define VFE_CMD_EPOCH_IRQ_1_DIS 0x0000 +#define VFE_CMD_EPOCH_IRQ_1_ENA 0x4000 +#define VFE_CMD_EPOCH_IRQ_2_DIS 0x0000 +#define VFE_CMD_EPOCH_IRQ_2_ENA 0x8000 + +typedef struct { + unsigned int cmd_id; + unsigned int cfg; + unsigned int efs_cfg; + unsigned int frame_cfg; + unsigned int window_width_cfg; + unsigned int window_height_cfg; + unsigned int subsamp1_cfg; + unsigned int subsamp2_cfg; + unsigned int epoch_irq; +} __attribute__((packed)) vfe_cmd_camif_cfg; + + + +/* + * Command to program the black level module + */ + +#define VFE_CMD_BLACK_LVL_CFG 0x0005 +#define VFE_CMD_BLACK_LVL_CFG_LEN sizeof(vfe_cmd_black_lvl_cfg) + +#define VFE_CMD_BL_SEL_MANUAL 0x0000 +#define VFE_CMD_BL_SEL_AUTO 0x0001 + +typedef struct { + unsigned int cmd_id; + unsigned int black_lvl_sel; + unsigned int cfg_part[3]; +} __attribute__((packed)) vfe_cmd_black_lvl_cfg; + + +/* + * Command to program the active region by cropping the region of interest + */ + +#define VFE_CMD_ACTIVE_REGION_CFG 0x0006 +#define VFE_CMD_ACTIVE_REGION_CFG_LEN \ + sizeof(vfe_cmd_active_region_cfg) + + +typedef struct { + unsigned int cmd_id; + unsigned int cfg_part1; + unsigned int cfg_part2; +} __attribute__((packed)) vfe_cmd_active_region_cfg; + + + +/* + * Command to program the defective pixel correction(DPC) , + * adaptive bayer filter (ABF) and demosaic modules + */ + +#define VFE_CMD_DEMOSAIC_CFG 0x0007 +#define VFE_CMD_DEMOSAIC_CFG_LEN sizeof(vfe_cmd_demosaic_cfg) + +#define VFE_CMD_DEMOSAIC_PART1_ABF_EN_DIS 0x0000 +#define VFE_CMD_DEMOSAIC_PART1_ABF_EN_ENA 0x0001 +#define VFE_CMD_DEMOSAIC_PART1_DPC_EN_DIS 0x0000 +#define VFE_CMD_DEMOSAIC_PART1_DPC_EN_ENA 0x0002 +#define VFE_CMD_DEMOSAIC_PART1_FORCE_ABF_OFF 0x0000 +#define VFE_CMD_DEMOSAIC_PART1_FORCE_ABF_ON 0x0004 +#define VFE_CMD_DEMOSAIC_PART1_SLOPE_SHIFT_1 0x00000000 +#define VFE_CMD_DEMOSAIC_PART1_SLOPE_SHIFT_2 0x10000000 +#define VFE_CMD_DEMOSAIC_PART1_SLOPE_SHIFT_4 0x20000000 +#define VFE_CMD_DEMOSAIC_PART1_SLOPE_SHIFT_8 0x30000000 +#define VFE_CMD_DEMOSAIC_PART1_SLOPE_SHIFT_1_2 0x50000000 +#define VFE_CMD_DEMOSAIC_PART1_SLOPE_SHIFT_1_4 0x60000000 +#define VFE_CMD_DEMOSAIC_PART1_SLOPE_SHIFT_1_8 0x70000000 + +typedef struct { + unsigned int cmd_id; + unsigned int demosaic_part1; + unsigned int demosaic_part2; + unsigned int demosaic_part3; + unsigned int demosaic_part4; + unsigned int demosaic_part5; +} __attribute__((packed)) vfe_cmd_demosaic_cfg; + + +/* + * Command to program the ip format + */ + +#define VFE_CMD_IP_FORMAT_CFG 0x0008 +#define VFE_CMD_IP_FORMAT_CFG_LEN \ + sizeof(vfe_cmd_ip_format_cfg) + +#define VFE_CMD_IP_FORMAT_SEL_RGRG 0x0000 +#define VFE_CMD_IP_FORMAT_SEL_GRGR 0x0001 +#define VFE_CMD_IP_FORMAT_SEL_BGBG 0x0002 +#define VFE_CMD_IP_FORMAT_SEL_GBGB 0x0003 +#define VFE_CMD_IP_FORMAT_SEL_YCBYCR 0x0004 +#define VFE_CMD_IP_FORMAT_SEL_YCRYCB 0x0005 +#define VFE_CMD_IP_FORMAT_SEL_CBYCRY 0x0006 +#define VFE_CMD_IP_FORMAT_SEL_CRYCBY 0x0007 +#define VFE_CMD_IP_FORMAT_SEL_NO_CHROMA 0x0000 +#define VFE_CMD_IP_FORMAT_SEL_CHROMA 0x0008 + + +typedef struct { + unsigned int cmd_id; + unsigned int ip_format_sel; + unsigned int balance_gains_part1; + unsigned int balance_gains_part2; +} __attribute__((packed)) vfe_cmd_ip_format_cfg; + + + +/* + * Command to program max and min allowed op values + */ + +#define VFE_CMD_OP_CLAMP_CFG 0x0009 +#define VFE_CMD_OP_CLAMP_CFG_LEN \ + sizeof(vfe_cmd_op_clamp_cfg) + +typedef struct { + unsigned int cmd_id; + unsigned int op_clamp_max; + unsigned int op_clamp_min; +} __attribute__((packed)) vfe_cmd_op_clamp_cfg; + + +/* + * Command to program chroma sub sample module + */ + +#define VFE_CMD_CHROMA_SUBSAMPLE_CFG 0x000A +#define VFE_CMD_CHROMA_SUBSAMPLE_CFG_LEN \ + sizeof(vfe_cmd_chroma_subsample_cfg) + +#define VFE_CMD_CHROMA_SUBSAMP_SEL_H_INTERESTIAL_SAMPS 0x0000 +#define VFE_CMD_CHROMA_SUBSAMP_SEL_H_COSITED_SAMPS 0x0001 +#define VFE_CMD_CHROMA_SUBSAMP_SEL_V_INTERESTIAL_SAMPS 0x0000 +#define VFE_CMD_CHROMA_SUBSAMP_SEL_V_COSITED_SAMPS 0x0002 +#define VFE_CMD_CHROMA_SUBSAMP_SEL_H_SUBSAMP_DIS 0x0000 +#define VFE_CMD_CHROMA_SUBSAMP_SEL_H_SUBSAMP_ENA 0x0004 +#define VFE_CMD_CHROMA_SUBSAMP_SEL_V_SUBSAMP_DIS 0x0000 +#define VFE_CMD_CHROMA_SUBSAMP_SEL_V_SUBSAMP_ENA 0x0008 + +typedef struct { + unsigned int cmd_id; + unsigned int chroma_subsamp_sel; +} __attribute__((packed)) vfe_cmd_chroma_subsample_cfg; + + +/* + * Command to program the white balance module + */ + +#define VFE_CMD_WHITE_BALANCE_CFG 0x000B +#define VFE_CMD_WHITE_BALANCE_CFG_LEN \ + sizeof(vfe_cmd_white_balance_cfg) + +typedef struct { + unsigned int cmd_id; + unsigned int white_balance_gains; +} __attribute__((packed)) vfe_cmd_white_balance_cfg; + + +/* + * Command to program the color processing module + */ + +#define VFE_CMD_COLOR_PROCESS_CFG 0x000C +#define VFE_CMD_COLOR_PROCESS_CFG_LEN \ + sizeof(vfe_cmd_color_process_cfg) + +#define VFE_CMD_COLOR_CORRE_PART7_Q7_FACTORS 0x0000 +#define VFE_CMD_COLOR_CORRE_PART7_Q8_FACTORS 0x0001 +#define VFE_CMD_COLOR_CORRE_PART7_Q9_FACTORS 0x0002 +#define VFE_CMD_COLOR_CORRE_PART7_Q10_FACTORS 0x0003 + +typedef struct { + unsigned int cmd_id; + unsigned int color_correction_part1; + unsigned int color_correction_part2; + unsigned int color_correction_part3; + unsigned int color_correction_part4; + unsigned int color_correction_part5; + unsigned int color_correction_part6; + unsigned int color_correction_part7; + unsigned int chroma_enhance_part1; + unsigned int chroma_enhance_part2; + unsigned int chroma_enhance_part3; + unsigned int chroma_enhance_part4; + unsigned int chroma_enhance_part5; + unsigned int luma_calc_part1; + unsigned int luma_calc_part2; +} __attribute__((packed)) vfe_cmd_color_process_cfg; + + +/* + * Command to program adaptive filter module + */ + +#define VFE_CMD_ADP_FILTER_CFG 0x000D +#define VFE_CMD_ADP_FILTER_CFG_LEN \ + sizeof(vfe_cmd_adp_filter_cfg) + +#define VFE_CMD_ASF_CFG_PART_SMOOTH_FILTER_DIS 0x0000 +#define VFE_CMD_ASF_CFG_PART_SMOOTH_FILTER_ENA 0x0001 +#define VFE_CMD_ASF_CFG_PART_NO_SHARP_MODE 0x0000 +#define VFE_CMD_ASF_CFG_PART_SINGLE_FILTER 0x0002 +#define VFE_CMD_ASF_CFG_PART_DUAL_FILTER 0x0004 +#define VFE_CMD_ASF_CFG_PART_SHARP_MODE 0x0007 + +typedef struct { + unsigned int cmd_id; + unsigned int asf_cfg_part[7]; +} __attribute__((packed)) vfe_cmd_adp_filter_cfg; + + +/* + * Command to program for frame skip pattern for op1 and op2 + */ + +#define VFE_CMD_FRAME_SKIP_CFG 0x000E +#define VFE_CMD_FRAME_SKIP_CFG_LEN \ + sizeof(vfe_cmd_frame_skip_cfg) + +typedef struct { + unsigned int cmd_id; + unsigned int frame_skip_pattern_op1; + unsigned int frame_skip_pattern_op2; +} __attribute__((packed)) vfe_cmd_frame_skip_cfg; + + +/* + * Command to program field-of-view crop for digital zoom + */ + +#define VFE_CMD_FOV_CROP 0x000F +#define VFE_CMD_FOV_CROP_LEN sizeof(vfe_cmd_fov_crop) + +typedef struct { + unsigned int cmd_id; + unsigned int fov_crop_part1; + unsigned int fov_crop_part2; +} __attribute__((packed)) vfe_cmd_fov_crop; + + + +/* + * Command to program auto focus(AF) statistics module + */ + +#define VFE_CMD_STATS_AUTOFOCUS_CFG 0x0010 +#define VFE_CMD_STATS_AUTOFOCUS_CFG_LEN \ + sizeof(vfe_cmd_stats_autofocus_cfg) + +#define VFE_CMD_AF_STATS_SEL_STATS_DIS 0x0000 +#define VFE_CMD_AF_STATS_SEL_STATS_ENA 0x0001 +#define VFE_CMD_AF_STATS_SEL_PRI_FIXED 0x0000 +#define VFE_CMD_AF_STATS_SEL_PRI_VAR 0x0002 +#define VFE_CMD_AF_STATS_CFG_PART_METRIC_SUM 0x00000000 +#define VFE_CMD_AF_STATS_CFG_PART_METRIC_MAX 0x00200000 + +typedef struct { + unsigned int cmd_id; + unsigned int af_stats_sel; + unsigned int af_stats_cfg_part[8]; + unsigned int af_stats_op_buf_hdr; + unsigned int af_stats_op_buf[3]; +} __attribute__((packed)) vfe_cmd_stats_autofocus_cfg; + + +/* + * Command to program White balance(wb) and exposure (exp) + * statistics module + */ + +#define VFE_CMD_STATS_WB_EXP_CFG 0x0011 +#define VFE_CMD_STATS_WB_EXP_CFG_LEN \ + sizeof(vfe_cmd_stats_wb_exp_cfg) + +#define VFE_CMD_WB_EXP_STATS_SEL_STATS_DIS 0x0000 +#define VFE_CMD_WB_EXP_STATS_SEL_STATS_ENA 0x0001 +#define VFE_CMD_WB_EXP_STATS_SEL_PRI_FIXED 0x0000 +#define VFE_CMD_WB_EXP_STATS_SEL_PRI_VAR 0x0002 + +#define VFE_CMD_WB_EXP_STATS_CFG_PART1_EXP_REG_8_8 0x0000 +#define VFE_CMD_WB_EXP_STATS_CFG_PART1_EXP_REG_16_16 0x0001 +#define VFE_CMD_WB_EXP_STATS_CFG_PART1_EXP_SREG_8_8 0x0000 +#define VFE_CMD_WB_EXP_STATS_CFG_PART1_EXP_SREG_4_4 0x0002 + +typedef struct { + unsigned int cmd_id; + unsigned int wb_exp_stats_sel; + unsigned int wb_exp_stats_cfg_part1; + unsigned int wb_exp_stats_cfg_part2; + unsigned int wb_exp_stats_cfg_part3; + unsigned int wb_exp_stats_cfg_part4; + unsigned int wb_exp_stats_op_buf_hdr; + unsigned int wb_exp_stats_op_buf[3]; +} __attribute__((packed)) vfe_cmd_stats_wb_exp_cfg; + + +/* + * Command to program histogram(hg) stats module + */ + +#define VFE_CMD_STATS_HG_CFG 0x0012 +#define VFE_CMD_STATS_HG_CFG_LEN \ + sizeof(vfe_cmd_stats_hg_cfg) + +#define VFE_CMD_HG_STATS_SEL_PRI_FIXED 0x0000 +#define VFE_CMD_HG_STATS_SEL_PRI_VAR 0x0002 + +typedef struct { + unsigned int cmd_id; + unsigned int hg_stats_sel; + unsigned int hg_stats_cfg_part1; + unsigned int hg_stats_cfg_part2; + unsigned int hg_stats_op_buf_hdr; + unsigned int hg_stats_op_buf; +} __attribute__((packed)) vfe_cmd_stats_hg_cfg; + + +/* + * Command to acknowledge last MSG_VFE_OP1 message + */ + +#define VFE_CMD_OP1_ACK 0x0013 +#define VFE_CMD_OP1_ACK_LEN sizeof(vfe_cmd_op1_ack) + +typedef struct { + unsigned int cmd_id; + unsigned int op1_buf_y_addr; + unsigned int op1_buf_cbcr_addr; +} __attribute__((packed)) vfe_cmd_op1_ack; + + + +/* + * Command to acknowledge last MSG_VFE_OP2 message + */ + +#define VFE_CMD_OP2_ACK 0x0014 +#define VFE_CMD_OP2_ACK_LEN sizeof(vfe_cmd_op2_ack) + +typedef struct { + unsigned int cmd_id; + unsigned int op2_buf_y_addr; + unsigned int op2_buf_cbcr_addr; +} __attribute__((packed)) vfe_cmd_op2_ack; + + + +/* + * Command to acknowledge MSG_VFE_STATS_AUTOFOCUS msg + */ + +#define VFE_CMD_STATS_AF_ACK 0x0015 +#define VFE_CMD_STATS_AF_ACK_LEN sizeof(vfe_cmd_stats_af_ack) + + +typedef struct { + unsigned int cmd_id; + unsigned int af_stats_op_buf; +} __attribute__((packed)) vfe_cmd_stats_af_ack; + + +/* + * Command to acknowledge MSG_VFE_STATS_WB_EXP msg + */ + +#define VFE_CMD_STATS_WB_EXP_ACK 0x0016 +#define VFE_CMD_STATS_WB_EXP_ACK_LEN sizeof(vfe_cmd_stats_wb_exp_ack) + +typedef struct { + unsigned int cmd_id; + unsigned int wb_exp_stats_op_buf; +} __attribute__((packed)) vfe_cmd_stats_wb_exp_ack; + + +/* + * Command to acknowledge MSG_VFE_EPOCH1 message + */ + +#define VFE_CMD_EPOCH1_ACK 0x0017 +#define VFE_CMD_EPOCH1_ACK_LEN sizeof(vfe_cmd_epoch1_ack) + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) vfe_cmd_epoch1_ack; + + +/* + * Command to acknowledge MSG_VFE_EPOCH2 message + */ + +#define VFE_CMD_EPOCH2_ACK 0x0018 +#define VFE_CMD_EPOCH2_ACK_LEN sizeof(vfe_cmd_epoch2_ack) + +typedef struct { + unsigned short cmd_id; +} __attribute__((packed)) vfe_cmd_epoch2_ack; + + + +/* + * Command to configure, enable or disable synchronous timer1 + */ + +#define VFE_CMD_SYNC_TIMER1_CFG 0x0019 +#define VFE_CMD_SYNC_TIMER1_CFG_LEN \ + sizeof(vfe_cmd_sync_timer1_cfg) + +#define VFE_CMD_SYNC_T1_CFG_PART1_TIMER_DIS 0x0000 +#define VFE_CMD_SYNC_T1_CFG_PART1_TIMER_ENA 0x0001 +#define VFE_CMD_SYNC_T1_CFG_PART1_POL_HIGH 0x0000 +#define VFE_CMD_SYNC_T1_CFG_PART1_POL_LOW 0x0002 + +typedef struct { + unsigned int cmd_id; + unsigned int sync_t1_cfg_part1; + unsigned int sync_t1_h_sync_countdown; + unsigned int sync_t1_pclk_countdown; + unsigned int sync_t1_duration; +} __attribute__((packed)) vfe_cmd_sync_timer1_cfg; + + +/* + * Command to configure, enable or disable synchronous timer1 + */ + +#define VFE_CMD_SYNC_TIMER2_CFG 0x001A +#define VFE_CMD_SYNC_TIMER2_CFG_LEN \ + sizeof(vfe_cmd_sync_timer2_cfg) + +#define VFE_CMD_SYNC_T2_CFG_PART1_TIMER_DIS 0x0000 +#define VFE_CMD_SYNC_T2_CFG_PART1_TIMER_ENA 0x0001 +#define VFE_CMD_SYNC_T2_CFG_PART1_POL_HIGH 0x0000 +#define VFE_CMD_SYNC_T2_CFG_PART1_POL_LOW 0x0002 + +typedef struct { + unsigned int cmd_id; + unsigned int sync_t2_cfg_part1; + unsigned int sync_t2_h_sync_countdown; + unsigned int sync_t2_pclk_countdown; + unsigned int sync_t2_duration; +} __attribute__((packed)) vfe_cmd_sync_timer2_cfg; + + +/* + * Command to configure and start asynchronous timer1 + */ + +#define VFE_CMD_ASYNC_TIMER1_START 0x001B +#define VFE_CMD_ASYNC_TIMER1_START_LEN \ + sizeof(vfe_cmd_async_timer1_start) + +#define VFE_CMD_ASYNC_T1_POLARITY_A_HIGH 0x0000 +#define VFE_CMD_ASYNC_T1_POLARITY_A_LOW 0x0001 +#define VFE_CMD_ASYNC_T1_POLARITY_B_HIGH 0x0000 +#define VFE_CMD_ASYNC_T1_POLARITY_B_LOW 0x0002 + +typedef struct { + unsigned int cmd_id; + unsigned int async_t1a_cfg; + unsigned int async_t1b_cfg; + unsigned int async_t1_polarity; +} __attribute__((packed)) vfe_cmd_async_timer1_start; + + +/* + * Command to configure and start asynchronous timer2 + */ + +#define VFE_CMD_ASYNC_TIMER2_START 0x001C +#define VFE_CMD_ASYNC_TIMER2_START_LEN \ + sizeof(vfe_cmd_async_timer2_start) + +#define VFE_CMD_ASYNC_T2_POLARITY_A_HIGH 0x0000 +#define VFE_CMD_ASYNC_T2_POLARITY_A_LOW 0x0001 +#define VFE_CMD_ASYNC_T2_POLARITY_B_HIGH 0x0000 +#define VFE_CMD_ASYNC_T2_POLARITY_B_LOW 0x0002 + +typedef struct { + unsigned int cmd_id; + unsigned int async_t2a_cfg; + unsigned int async_t2b_cfg; + unsigned int async_t2_polarity; +} __attribute__((packed)) vfe_cmd_async_timer2_start; + + +/* + * Command to program partial configurations of auto focus(af) + */ + +#define VFE_CMD_STATS_AF_UPDATE 0x001D +#define VFE_CMD_STATS_AF_UPDATE_LEN \ + sizeof(vfe_cmd_stats_af_update) + +#define VFE_CMD_AF_UPDATE_PART1_WINDOW_ONE 0x00000000 +#define VFE_CMD_AF_UPDATE_PART1_WINDOW_MULTI 0x80000000 + +typedef struct { + unsigned int cmd_id; + unsigned int af_update_part1; + unsigned int af_update_part2; +} __attribute__((packed)) vfe_cmd_stats_af_update; + + +/* + * Command to program partial cfg of wb and exp + */ + +#define VFE_CMD_STATS_WB_EXP_UPDATE 0x001E +#define VFE_CMD_STATS_WB_EXP_UPDATE_LEN \ + sizeof(vfe_cmd_stats_wb_exp_update) + +#define VFE_CMD_WB_EXP_UPDATE_PART1_REGIONS_8_8 0x0000 +#define VFE_CMD_WB_EXP_UPDATE_PART1_REGIONS_16_16 0x0001 +#define VFE_CMD_WB_EXP_UPDATE_PART1_SREGIONS_8_8 0x0000 +#define VFE_CMD_WB_EXP_UPDATE_PART1_SREGIONS_4_4 0x0002 + +typedef struct { + unsigned int cmd_id; + unsigned int wb_exp_update_part1; + unsigned int wb_exp_update_part2; + unsigned int wb_exp_update_part3; + unsigned int wb_exp_update_part4; +} __attribute__((packed)) vfe_cmd_stats_wb_exp_update; + + + +/* + * Command to re program the CAMIF FRAME CONFIG settings + */ + +#define VFE_CMD_UPDATE_CAMIF_FRAME_CFG 0x001F +#define VFE_CMD_UPDATE_CAMIF_FRAME_CFG_LEN \ + sizeof(vfe_cmd_update_camif_frame_cfg) + +typedef struct { + unsigned int cmd_id; + unsigned int camif_frame_cfg; +} __attribute__((packed)) vfe_cmd_update_camif_frame_cfg; + + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vfemsg.h b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vfemsg.h new file mode 100644 index 00000000000..a628f922b2c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/qdsp5vfemsg.h @@ -0,0 +1,290 @@ +#ifndef QDSP5VFEMSGI_H +#define QDSP5VFEMSGI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + V F E I N T E R N A L M E S S A G E S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are sent by VFE Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ +/*=========================================================================== + + EDIT HISTORY FOR FILE + +This section contains comments describing changes made to this file. +Notice that changes are listed in reverse chronological order. + +$Header: //source/qcom/qct/multimedia2/AdspSvc/7XXX/qdsp5cmd/video/qdsp5vfemsg.h#2 $ $DateTime: 2008/07/30 10:50:23 $ $Author: pavanr $ +Revision History: + +when who what, where, why +-------- --- ---------------------------------------------------------- +06/12/08 sv initial version +===========================================================================*/ + + +/* + * Message to acknowledge CMD_VFE_REST command + */ + +#define VFE_MSG_RESET_ACK 0x0000 +#define VFE_MSG_RESET_ACK_LEN sizeof(vfe_msg_reset_ack) + +typedef struct { +} __attribute__((packed)) vfe_msg_reset_ack; + + +/* + * Message to acknowledge CMD_VFE_START command + */ + +#define VFE_MSG_START_ACK 0x0001 +#define VFE_MSG_START_ACK_LEN sizeof(vfe_msg_start_ack) + +typedef struct { +} __attribute__((packed)) vfe_msg_start_ack; + +/* + * Message to acknowledge CMD_VFE_STOP command + */ + +#define VFE_MSG_STOP_ACK 0x0002 +#define VFE_MSG_STOP_ACK_LEN sizeof(vfe_msg_stop_ack) + +typedef struct { +} __attribute__((packed)) vfe_msg_stop_ack; + + +/* + * Message to acknowledge CMD_VFE_UPDATE command + */ + +#define VFE_MSG_UPDATE_ACK 0x0003 +#define VFE_MSG_UPDATE_ACK_LEN sizeof(vfe_msg_update_ack) + +typedef struct { +} __attribute__((packed)) vfe_msg_update_ack; + + +/* + * Message to notify the ARM that snapshot processing is complete + * and that the VFE is now STATE_VFE_IDLE + */ + +#define VFE_MSG_SNAPSHOT_DONE 0x0004 +#define VFE_MSG_SNAPSHOT_DONE_LEN \ + sizeof(vfe_msg_snapshot_done) + +typedef struct { +} __attribute__((packed)) vfe_msg_snapshot_done; + + + +/* + * Message to notify ARM that illegal cmd was received and + * system is in the IDLE state + */ + +#define VFE_MSG_ILLEGAL_CMD 0x0005 +#define VFE_MSG_ILLEGAL_CMD_LEN \ + sizeof(vfe_msg_illegal_cmd) + +typedef struct { + unsigned int status; +} __attribute__((packed)) vfe_msg_illegal_cmd; + + +/* + * Message to notify ARM that op1 buf is full and ready + */ + +#define VFE_MSG_OP1 0x0006 +#define VFE_MSG_OP1_LEN sizeof(vfe_msg_op1) + +typedef struct { + unsigned int op1_buf_y_addr; + unsigned int op1_buf_cbcr_addr; + unsigned int black_level_even_col; + unsigned int black_level_odd_col; + unsigned int defect_pixels_detected; + unsigned int asf_max_edge; +} __attribute__((packed)) vfe_msg_op1; + + +/* + * Message to notify ARM that op2 buf is full and ready + */ + +#define VFE_MSG_OP2 0x0007 +#define VFE_MSG_OP2_LEN sizeof(vfe_msg_op2) + +typedef struct { + unsigned int op2_buf_y_addr; + unsigned int op2_buf_cbcr_addr; + unsigned int black_level_even_col; + unsigned int black_level_odd_col; + unsigned int defect_pixels_detected; + unsigned int asf_max_edge; +} __attribute__((packed)) vfe_msg_op2; + + +/* + * Message to notify ARM that autofocus(af) stats are ready + */ + +#define VFE_MSG_STATS_AF 0x0008 +#define VFE_MSG_STATS_AF_LEN sizeof(vfe_msg_stats_af) + +typedef struct { + unsigned int af_stats_op_buffer; +} __attribute__((packed)) vfe_msg_stats_af; + + +/* + * Message to notify ARM that white balance(wb) and exposure (exp) + * stats are ready + */ + +#define VFE_MSG_STATS_WB_EXP 0x0009 +#define VFE_MSG_STATS_WB_EXP_LEN \ + sizeof(vfe_msg_stats_wb_exp) + +typedef struct { + unsigned int wb_exp_stats_op_buf; +} __attribute__((packed)) vfe_msg_stats_wb_exp; + + +/* + * Message to notify the ARM that histogram(hg) stats are ready + */ + +#define VFE_MSG_STATS_HG 0x000A +#define VFE_MSG_STATS_HG_LEN sizeof(vfe_msg_stats_hg) + +typedef struct { + unsigned int hg_stats_op_buf; +} __attribute__((packed)) vfe_msg_stats_hg; + + +/* + * Message to notify the ARM that epoch1 event occurred in the CAMIF + */ + +#define VFE_MSG_EPOCH1 0x000B +#define VFE_MSG_EPOCH1_LEN sizeof(vfe_msg_epoch1) + +typedef struct { +} __attribute__((packed)) vfe_msg_epoch1; + + +/* + * Message to notify the ARM that epoch2 event occurred in the CAMIF + */ + +#define VFE_MSG_EPOCH2 0x000C +#define VFE_MSG_EPOCH2_LEN sizeof(vfe_msg_epoch2) + +typedef struct { +} __attribute__((packed)) vfe_msg_epoch2; + + +/* + * Message to notify the ARM that sync timer1 op is completed + */ + +#define VFE_MSG_SYNC_T1_DONE 0x000D +#define VFE_MSG_SYNC_T1_DONE_LEN sizeof(vfe_msg_sync_t1_done) + +typedef struct { +} __attribute__((packed)) vfe_msg_sync_t1_done; + + +/* + * Message to notify the ARM that sync timer2 op is completed + */ + +#define VFE_MSG_SYNC_T2_DONE 0x000E +#define VFE_MSG_SYNC_T2_DONE_LEN sizeof(vfe_msg_sync_t2_done) + +typedef struct { +} __attribute__((packed)) vfe_msg_sync_t2_done; + + +/* + * Message to notify the ARM that async t1 operation completed + */ + +#define VFE_MSG_ASYNC_T1_DONE 0x000F +#define VFE_MSG_ASYNC_T1_DONE_LEN sizeof(vfe_msg_async_t1_done) + +typedef struct { +} __attribute__((packed)) vfe_msg_async_t1_done; + + + +/* + * Message to notify the ARM that async t2 operation completed + */ + +#define VFE_MSG_ASYNC_T2_DONE 0x0010 +#define VFE_MSG_ASYNC_T2_DONE_LEN sizeof(vfe_msg_async_t2_done) + +typedef struct { +} __attribute__((packed)) vfe_msg_async_t2_done; + + + +/* + * Message to notify the ARM that an error has occurred + */ + +#define VFE_MSG_ERROR 0x0011 +#define VFE_MSG_ERROR_LEN sizeof(vfe_msg_error) + +#define VFE_MSG_ERR_COND_NO_CAMIF_ERR 0x0000 +#define VFE_MSG_ERR_COND_CAMIF_ERR 0x0001 +#define VFE_MSG_ERR_COND_OP1_Y_NO_BUS_OF 0x0000 +#define VFE_MSG_ERR_COND_OP1_Y_BUS_OF 0x0002 +#define VFE_MSG_ERR_COND_OP1_CBCR_NO_BUS_OF 0x0000 +#define VFE_MSG_ERR_COND_OP1_CBCR_BUS_OF 0x0004 +#define VFE_MSG_ERR_COND_OP2_Y_NO_BUS_OF 0x0000 +#define VFE_MSG_ERR_COND_OP2_Y_BUS_OF 0x0008 +#define VFE_MSG_ERR_COND_OP2_CBCR_NO_BUS_OF 0x0000 +#define VFE_MSG_ERR_COND_OP2_CBCR_BUS_OF 0x0010 +#define VFE_MSG_ERR_COND_AF_NO_BUS_OF 0x0000 +#define VFE_MSG_ERR_COND_AF_BUS_OF 0x0020 +#define VFE_MSG_ERR_COND_WB_EXP_NO_BUS_OF 0x0000 +#define VFE_MSG_ERR_COND_WB_EXP_BUS_OF 0x0040 +#define VFE_MSG_ERR_COND_NO_AXI_ERR 0x0000 +#define VFE_MSG_ERR_COND_AXI_ERR 0x0080 + +#define VFE_MSG_CAMIF_STS_IDLE 0x0000 +#define VFE_MSG_CAMIF_STS_CAPTURE_DATA 0x0001 + +typedef struct { + unsigned int err_cond; + unsigned int camif_sts; +} __attribute__((packed)) vfe_msg_error; + + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5/snd_adie.h b/arch/arm/mach-msm/include/mach/qdsp5/snd_adie.h new file mode 100644 index 00000000000..bf1714e0086 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5/snd_adie.h @@ -0,0 +1,86 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __SND_ADIE_SVC_H_ +#define __SND_ADIE_SVC_H_ + +#define ADIE_SVC_PROG 0x30000002 +#define ADIE_SVC_VERS 0x00020003 + +#define ADIE_SVC_CLIENT_STATUS_FUNC_PTR_TYPE_PROC 0xFFFFFF01 +#define SND_ADIE_SVC_CLIENT_REGISTER_PROC 34 +#define SND_ADIE_SVC_CONFIG_ADIE_BLOCK_PROC 35 +#define SND_ADIE_SVC_CLIENT_DEREGISTER_PROC 36 + +#define ADIE_SVC_MAX_CLIENTS 5 + +enum adie_svc_client_operation{ + ADIE_SVC_REGISTER_CLIENT, + ADIE_SVC_DEREGISTER_CLIENT, + ADIE_SVC_CONFIG_ADIE_BLOCK, +}; + +enum adie_svc_status_type{ + ADIE_SVC_STATUS_SUCCESS, + ADIE_SVC_STATUS_FAILURE, + ADIE_SVC_STATUS_INUSE +}; + +enum adie_block_enum_type{ + MIC_BIAS, + HSSD, + HPH_PA +}; + +enum adie_config_enum_type{ + DISABLE, + ENABLE +}; + +struct adie_svc_client{ + int client_id; + int cb_id; + enum adie_svc_status_type status; + bool adie_svc_cb_done; + struct mutex lock; + wait_queue_head_t wq; + struct msm_rpc_client *rpc_client; +}; + +struct adie_svc_client_register_cb_cb_args { + int cb_id; + uint32_t size; + int client_id; + enum adie_block_enum_type adie_block; + enum adie_svc_status_type status; + enum adie_svc_client_operation client_operation; +}; + +struct adie_svc_client_register_cb_args { + int cb_id; +}; + +struct adie_svc_client_deregister_cb_args { + int client_id; +}; + +struct adie_svc_config_adie_block_cb_args { + int client_id; + enum adie_block_enum_type adie_block; + enum adie_config_enum_type config; +}; + +int adie_svc_get(void); +int adie_svc_put(int id); +int adie_svc_config_adie_block(int id, + enum adie_block_enum_type adie_block_type, bool enable); +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/acdb_commands.h b/arch/arm/mach-msm/include/mach/qdsp5v2/acdb_commands.h new file mode 100644 index 00000000000..2e6fcdb0aee --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/acdb_commands.h @@ -0,0 +1,303 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _MACH_QDSP5_V2_ACDB_COMMANDS_H +#define _MACH_QDSP5_V2_ACDB_COMMANDS_H + +#define ACDB_VOICE_NETWORK_ID_DEFAULT 0x00010037 +#define ACDB_INITIALISING 0 +#define ACDB_READY 1 + + +/* 4KB */ +#define ACDB_PAGE_SIZE 0x1000 + +#define ACDB_CDMA_NB 0x0108b153 +#define ACDB_CDMA_WB 0x0108b154 +#define ACDB_GSM_NB 0x0108b155 +#define ACDB_GSM_WB 0x0108b156 +#define ACDB_WCDMA_NB 0x0108b157 +#define ACDB_WCDMA_WB 0x0108b158 + + +/* ACDB commands */ + + +/* struct acdb_cmd_install_device */ +#define ACDB_INSTALL_DEVICE 0x0108d245 + +/* struct acdb_cmd_install_device */ +#define ACDB_UNINSTALL_DEVICE 0x0108d246 + +/* struct acdb_cmd_device */ +#define ACDB_GET_DEVICE 0x0108bb92 + +/* struct acdb_cmd_device */ +#define ACDB_SET_DEVICE 0x0108bb93 + +/* struct acdb_cmd_get_device_table */ +#define ACDB_GET_DEVICE_TABLE 0x0108bb97 + +/* struct acdb_cmd_get_device_capabilities */ +#define ACDB_GET_DEVICE_CAPABILITIES 0x0108f5ca + +/* struct acdb_cmd_get_device_info */ +#define ACDB_GET_DEVICE_INFO 0x0108f5cb + +/*command to intitialize ACDB based on codec type*/ +#define ACDB_CMD_INITIALIZE_FOR_ADIE 0x00011283 + + +/* ACDB Error codes */ + +#define ACDB_RES_SUCCESS 0 +#define ACDB_RES_FAILURE -1 +#define ACDB_RES_BADPARM -2 +#define ACDB_RES_BADSTATE -3 + +#define TGTVERS_MSM7x30_BRING_UP 0x00010064 + + + +/* Algorithm Aspect IDs */ + +#define IID_ENABLE_FLAG 0x0108b6b9 + + +#define IID_ENABLE_FLAG_SIZE 1 +#define IID_ECHO_CANCELLER_VERSION_SIZE 2 +#define IID_ECHO_CANCELLER_MODE_SIZE 2 +#define IID_ECHO_CANCELLER_NOISE_SUPPRESSOR_ENABLE_SIZE 1 +#define IID_ECHO_CANCELLER_PARAMETERS_SIZE 32 +#define IID_ECHO_CANCELLER_NEXTGEN_NB_PARAMETERS_SIZE (38 * 2) +#define IID_ECHO_CANCELLER_NEXTGEN_WB_PARAMETERS_SIZE (38 * 2) +#define IID_FLUENCE_PARAMETERS_SIZE 486 +#define IID_AFE_VOLUME_CONTROL_SIZE 6 +#define IID_GAIN_SIZE 2 +#define IID_VOICE_FIR_FILTER_SIZE 14 +#define IID_VOICE_IIR_FILTER_SIZE 114 +#define IID_RX_DBM_OFFSET_SIZE 2 +#define IID_AGC_SIZE 36 +#define IID_AVC_SIZE 80 + +#define IID_AUDIO_IIR_COEFF_SIZE 100 +#define IID_MBADRC_PARAMETERS_SIZE 8 +#define IID_MBADRC_EXT_BUFF_SIZE 392 +#define IID_MBADRC_BAND_CONFIG_SIZE 100 +#define IID_QAFX_PARAMETERS_SIZE 2 +#define IID_QCONCERT_PARAMETERS_SIZE 2 +#define IID_AUDIO_AGC_PARAMETERS_SIZE 42 +#define IID_NS_PARAMETERS_SIZE 14 + +#define IID_ECHO_CANCELLER_VERSION 0x00010042 +#define IID_ECHO_CANCELLER_MODE 0x00010043 +#define IID_ECHO_CANCELLER_NOISE_SUPPRESSOR_ENABLE 0x00010044 +#define IID_ECHO_CANCELLER_PARAMETERS 0x00010045 +#define IID_ECHO_CANCELLER_NEXTGEN_NB_PARAMETERS 0x00010046 +#define IID_ECHO_CANCELLER_NEXTGEN_WB_PARAMETERS 0x00010047 +#define IID_FLUENCE_PARAMETERS 0x00010048 +#define IID_AFE_VOLUME_CONTROL 0x00010049 +#define IID_GAIN 0x0001004A +#define IID_VOICE_FIR_FILTER 0x0001004B +#define IID_VOICE_IIR_FILTER 0x0001004C +#define IID_AGC 0x0001004E +#define IID_AVC 0x0001004F +#define ABID_SIDETONE_GAIN 0x00010050 +#define ABID_TX_VOICE_GAIN 0x00010051 +#define ABID_TX_DTMF_GAIN 0x00010052 +#define ABID_CODEC_TX_GAIN 0x00010053 +#define ABID_HSSD 0x00010054 +#define ABID_TX_AGC 0x00010055 +#define ABID_TX_VOICE_FIR 0x00010056 +#define ABID_TX_VOICE_IIR 0x00010057 +#define ABID_ECHO_CANCELLER 0x00010058 +#define ABID_ECHO_CANCELLER_NB_LVHF 0x00010059 +#define ABID_ECHO_CANCELLER_WB_LVHF 0x0001005A +#define ABID_FLUENCE 0x0001005B +#define ABID_CODEC_RX_GAIN 0x0001005C +#define ABID_RX_DBM_OFFSET 0x0001005D +#define ABID_RX_AGC 0x0001005E +#define ABID_AVC 0x0001005F +#define ABID_RX_VOICE_FIR 0x00010060 +#define ABID_RX_VOICE_IIR 0x00010061 +#define ABID_AFE_VOL_CTRL 0x00010067 + + +/* AUDIO IDs */ +#define ABID_AUDIO_AGC_TX 0x00010068 +#define ABID_AUDIO_NS_TX 0x00010069 +#define ABID_VOICE_NS 0x0001006A +#define ABID_AUDIO_IIR_TX 0x0001006B +#define ABID_AUDIO_IIR_RX 0x0001006C +#define ABID_AUDIO_MBADRC_RX 0x0001006E +#define ABID_AUDIO_QAFX_RX 0x0001006F +#define ABID_AUDIO_QCONCERT_RX 0x00010070 +#define ABID_AUDIO_STF_RX 0x00010071 +#define ABID_AUDIO_CALIBRATION_GAIN_RX 0x00011162 +#define ABID_AUDIO_CALIBRATION_GAIN_TX 0x00011149 +#define ABID_AUDIO_PBE_RX 0x00011197 +#define ABID_AUDIO_RMC_TX 0x00011226 +#define ABID_AUDIO_FLUENCE_TX 0x00011244 + + +#define IID_AUDIO_AGC_PARAMETERS 0x0001007E +#define IID_NS_PARAMETERS 0x00010072 +#define IID_AUDIO_IIR_COEFF 0x00010073 +#define IID_MBADRC_EXT_BUFF 0x00010075 +#define IID_MBADRC_BAND_CONFIG 0x00010076 +#define IID_MBADRC_PARAMETERS 0x00010077 +#define IID_QAFX_PARAMETERS 0x00010079 +#define IID_QCONCERT_PARAMETERS 0x0001007A +#define IID_STF_COEFF 0x0001007B +#define IID_AUDIO_CALIBRATION_GAIN_RX 0x00011163 +#define IID_AUDIO_CALIBRATION_GAIN_TX 0x00011171 +#define IID_PBE_CONFIG_PARAMETERS 0x00011198 +#define IID_AUDIO_PBE_RX_ENABLE_FLAG 0x00011199 +#define IID_AUDIO_RMC_PARAM 0x00011227 +#define IID_AUDIO_FLUENCE_TX 0x00011245 + + +#define TOPID_RX_TOPOLOGY_1 0x00010062 +#define TOPID_TX_TOPOLOGY_1 0x00010063 +#define AFERID_INT_SINK 0x00010065 +#define AFERID_INT_SOURCE 0x00010066 +#define AFERID_NO_SINK 0x00000000 +#define AFERID_NULL_SINK 0x0108ea92 + + +struct acdb_cmd_install_device { + u32 command_id; + u32 device_id; + u32 topology_id; + u32 afe_routing_id; + u32 cad_routing_id; /* see "Sample Rate Bit Mask" below */ + u32 sample_rate_mask; + + /* represents device direction: Tx, Rx (aux pga - loopback) */ + u8 device_type; + u8 channel_config; /* Mono or Stereo */ + u32 adie_codec_path_id; +}; + + +struct acdb_cmd_get_device_capabilities { + u32 command_id; + u32 total_bytes; /* Length in bytes allocated for buffer */ + u32 *phys_buf; /* Physical Address of data */ +}; + + +struct acdb_cmd_get_device_info { + u32 command_id; + u32 device_id; + u32 total_bytes; /* Length in bytes allocated for buffer */ + u32 *phys_buf; /* Physical Address of data */ +}; + +struct acdb_cmd_device { + u32 command_id; + u32 device_id; + u32 network_id; + u32 sample_rate_id; /* Actual sample rate value */ + u32 interface_id; /* See interface id's above */ + u32 algorithm_block_id; /* See enumerations above */ + u32 total_bytes; /* Length in bytes used by buffer */ + u32 *phys_buf; /* Physical Address of data */ +}; + +struct acdb_cmd_get_device_table { + u32 command_id; + u32 device_id; + u32 network_id; + u32 sample_rate_id; /* Actual sample rate value */ + u32 total_bytes; /* Length in bytes used by buffer */ + u32 *phys_buf; /* Physical Address of data */ +}; + +struct acdb_result { + /* This field is populated in response to the */ + /* ACDB_GET_DEVICE_CAPABILITIES command and indicates the total */ + /* devices whose capabilities are copied to the physical memory. */ + u32 total_devices; + u32 *buf; /* Physical Address of data */ + u32 used_bytes; /* The size in bytes of the data */ + u32 result; /* See ACDB Error codes above */ +}; + +struct acdb_device_capability { + u32 device_id; + u32 sample_rate_mask; /* See "Sample Rate Bit Mask" below */ +}; + +struct acdb_dev_info { + u32 cad_routing_id; + u32 sample_rate_mask; /* See "Sample Rate Bit Mask" below */ + u32 adsp_device_id; /* QDSP6 device ID */ + u32 device_type; /* Tx, Rx (aux pga - loopback) */ + u32 channel_config; /* Mono or Stereo */ + s32 min_volume; /* Min volume (mB) */ + s32 max_volume; /* Max volume (mB) */ +}; + +/*structure is used to intialize ACDB software on modem +based on adie type detected*/ +struct acdb_cmd_init_adie { + u32 command_id; + u32 adie_type; +}; + +#define ACDB_CURRENT_ADIE_MODE_UNKNOWN 0 +#define ACDB_CURRENT_ADIE_MODE_TIMPANI 1 +#define ACDB_CURRENT_ADIE_MODE_MARIMBA 2 + +/* Sample Rate Bit Mask */ + +/* AUX PGA devices will have a sample rate mask of 0xFFFFFFFF */ +/* 8kHz 0x00000001 */ +/* 11.025kHz 0x00000002 */ +/* 12kHz 0x00000004 */ +/* 16kHz 0x00000008 */ +/* 22.5kHz 0x00000010 */ +/* 24kHz 0x00000020 */ +/* 32kHz 0x00000040 */ +/* 44.1kHz 0x00000080 */ +/* 48kHz 0x00000100 */ + + +/* Device type enumeration */ +enum { + RX_DEVICE = 1, + TX_DEVICE, + AUXPGA_DEVICE, + DEVICE_TYPE_MAX +}; + +#ifdef CONFIG_DEBUG_FS +/*These are ABID used for RTC*/ +#define ABID_AUDIO_RTC_MBADRC_RX 0x0001118A +#define ABID_AUDIO_RTC_VOLUME_PAN_RX 0x0001118C +#define ABID_AUDIO_RTC_SPA 0x0001118E +#define ABID_AUDIO_RTC_EQUALIZER_PARAMETERS 0x0001119F + +/*These are IID used for RTC*/ +#define IID_AUDIO_RTC_MBADRC_PARAMETERS 0x0001118B +#define IID_AUDIO_RTC_VOLUME_PAN_PARAMETERS 0x0001118D +#define IID_AUDIO_RTC_SPA_PARAMETERS 0x0001118F +#define IID_AUDIO_RTC_EQUALIZER_PARAMETERS 0x0001119E +#define IID_AUDIO_RTC_AGC_PARAMETERS 0x000111A7 +#define IID_AUDIO_RTC_TX_IIR_COEFF 0x000111A8 + +#endif + + +#endif + diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/adie_marimba.h b/arch/arm/mach-msm/include/mach/qdsp5v2/adie_marimba.h new file mode 100644 index 00000000000..919da65d23e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/adie_marimba.h @@ -0,0 +1,93 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_ADIE_MARIMBA_H +#define __MACH_QDSP5_V2_ADIE_MARIMBA_H + +#include + +/* Value Represents a entry */ +#define ADIE_CODEC_ACTION_ENTRY 0x1 +/* Value representing a delay wait */ +#define ADIE_CODEC_ACTION_DELAY_WAIT 0x2 +/* Value representing a stage reached */ +#define ADIE_CODEC_ACTION_STAGE_REACHED 0x3 + +/* This value is the state after the client sets the path */ +#define ADIE_CODEC_PATH_OFF 0x0050 + +/* State to which client asks the drv to proceed to where it can + * set up the clocks and 0-fill PCM buffers + */ +#define ADIE_CODEC_DIGITAL_READY 0x0100 + +/* State to which client asks the drv to proceed to where it can + * start sending data after internal steady state delay + */ +#define ADIE_CODEC_DIGITAL_ANALOG_READY 0x1000 + + +/* Client Asks adie to switch off the Analog portion of the + * the internal codec. After the use of this path + */ +#define ADIE_CODEC_ANALOG_OFF 0x0750 + + +/* Client Asks adie to switch off the digital portion of the + * the internal codec. After switching off the analog portion. + * + * 0-fill PCM may or maynot be sent at this point + * + */ +#define ADIE_CODEC_DIGITAL_OFF 0x0600 + +/* State to which client asks the drv to write the default values + * to the registers */ +#define ADIE_CODEC_FLASH_IMAGE 0x0001 + +/* Path type */ +#define ADIE_CODEC_RX 0 +#define ADIE_CODEC_TX 1 +#define ADIE_CODEC_LB 3 +#define ADIE_CODEC_MAX 4 + +#define ADIE_CODEC_PACK_ENTRY(reg, mask, val) ((val)|(mask << 8)|(reg << 16)) + +#define ADIE_CODEC_UNPACK_ENTRY(packed, reg, mask, val) \ + do { \ + ((reg) = ((packed >> 16) & (0xff))); \ + ((mask) = ((packed >> 8) & (0xff))); \ + ((val) = ((packed) & (0xff))); \ + } while (0); + +struct adie_codec_action_unit { + u32 type; + u32 action; +}; + +struct adie_codec_hwsetting_entry{ + struct adie_codec_action_unit *actions; + u32 action_sz; + u32 freq_plan; + u32 osr; + /* u32 VolMask; + * u32 SidetoneMask; + */ +}; + +struct adie_codec_dev_profile { + u32 path_type; /* RX or TX */ + u32 setting_sz; + struct adie_codec_hwsetting_entry *settings; +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/afe.h b/arch/arm/mach-msm/include/mach/qdsp5v2/afe.h new file mode 100644 index 00000000000..c15faccf571 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/afe.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _MACH_QDSP5_V2_AFE_H +#define _MACH_QDSP5_V2_AFE_H + +#include +#include + +#define AFE_HW_PATH_CODEC_RX 1 +#define AFE_HW_PATH_CODEC_TX 2 +#define AFE_HW_PATH_AUXPCM_RX 3 +#define AFE_HW_PATH_AUXPCM_TX 4 +#define AFE_HW_PATH_MI2S_RX 5 +#define AFE_HW_PATH_MI2S_TX 6 + +#define AFE_VOLUME_UNITY 0x4000 /* Based on Q14 */ + +struct msm_afe_config { + u16 sample_rate; + u16 channel_mode; + u16 volume; + /* To be expaned for AUX CODEC */ +}; + +int afe_enable(u8 path_id, struct msm_afe_config *config); + +int afe_disable(u8 path_id); + +int afe_config_aux_codec(int pcm_ctl_value, int aux_codec_intf_value, + int data_format_pad); +int afe_config_fm_codec(int fm_enable, uint16_t source); + +int afe_config_fm_volume(uint16_t volume); +int afe_config_fm_calibration_gain(uint16_t device_id, + uint16_t calibration_gain); +void afe_loopback(int enable); +void afe_ext_loopback(int enable, int rx_copp_id, int tx_copp_id); + +void afe_device_volume_ctrl(u16 device_id, u16 device_volume); + +int afe_config_rmc_block(struct acdb_rmc_block *acdb_rmc); +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/audio_acdb_def.h b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_acdb_def.h new file mode 100644 index 00000000000..a2a15dcadec --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_acdb_def.h @@ -0,0 +1,51 @@ +/* Copyright (c) 2010 - 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _MACH_QDSP5_V2_AUDIO_ACDB_DEF_H +#define _MACH_QDSP5_V2_AUDIO_ACDB_DEF_H + +/* Define ACDB device ID */ +#define ACDB_ID_HANDSET_SPKR 1 +#define ACDB_ID_HANDSET_MIC 2 +#define ACDB_ID_HEADSET_MIC 3 +#define ACDB_ID_HEADSET_SPKR_MONO 4 +#define ACDB_ID_HEADSET_SPKR_STEREO 5 +#define ACDB_ID_SPKR_PHONE_MIC 6 +#define ACDB_ID_SPKR_PHONE_MONO 7 +#define ACDB_ID_SPKR_PHONE_STEREO 8 +#define ACDB_ID_BT_SCO_MIC 9 +#define ACDB_ID_BT_SCO_SPKR 0x0A +#define ACDB_ID_BT_A2DP_SPKR 0x0B +#define ACDB_ID_BT_A2DP_TX 0x10 +#define ACDB_ID_TTY_HEADSET_MIC 0x0C +#define ACDB_ID_TTY_HEADSET_SPKR 0x0D +#define ACDB_ID_HEADSET_MONO_PLUS_SPKR_MONO_RX 0x11 +#define ACDB_ID_HEADSET_STEREO_PLUS_SPKR_STEREO_RX 0x14 +#define ACDB_ID_FM_TX_LOOPBACK 0x17 +#define ACDB_ID_FM_TX 0x18 +#define ACDB_ID_LP_FM_SPKR_PHONE_STEREO_RX 0x19 +#define ACDB_ID_LP_FM_HEADSET_SPKR_STEREO_RX 0x1A +#define ACDB_ID_I2S_RX 0x20 +#define ACDB_ID_SPKR_PHONE_MIC_BROADSIDE 0x2B +#define ACDB_ID_HANDSET_MIC_BROADSIDE 0x2C +#define ACDB_ID_SPKR_PHONE_MIC_ENDFIRE 0x2D +#define ACDB_ID_HANDSET_MIC_ENDFIRE 0x2E +#define ACDB_ID_I2S_TX 0x30 +#define ACDB_ID_HDMI 0x40 +#define ACDB_ID_FM_RX 0x4F +/*Replace the max device ID,if any new device is added Specific to RTC only*/ +#define ACDB_ID_MAX ACDB_ID_FM_RX + +/* ID used for virtual devices */ +#define PSEUDO_ACDB_ID 0xFFFF + +#endif /* _MACH_QDSP5_V2_AUDIO_ACDB_DEF_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/audio_acdbi.h b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_acdbi.h new file mode 100644 index 00000000000..559073ce747 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_acdbi.h @@ -0,0 +1,303 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _MACH_QDSP5_V2_AUDIO_ACDBI_H +#define _MACH_QDSP5_V2_AUDIO_ACDBI_H + +#define DBOR_SIGNATURE 0x524F4244 + +#ifdef CONFIG_DEBUG_FS +void acdb_rtc_set_err(u32 ErrCode); +#endif + + +struct header { + u32 dbor_signature; + u32 abid; + u32 iid; + u32 data_len; +}; + +enum { + ACDB_AGC_BLOCK = 197, + ACDB_IIR_BLOCK = 245, + ACDB_MBADRC_BLOCK = 343 +}; + +/* Structure to query for acdb parameter */ +struct acdb_get_block { + u32 acdb_id; + u32 sample_rate_id; /* Actual sample rate value */ + u32 interface_id; /* Interface id's */ + u32 algorithm_block_id; /* Algorithm block id */ + u32 total_bytes; /* Length in bytes used by buffer for + configuration */ + u32 *buf_ptr; /* Address for storing configuration + data */ +}; + +struct acdb_agc_block { + u16 enable_status; + u16 comp_rlink_static_gain; + u16 comp_rlink_aig_flag; + u16 exp_rlink_threshold; + u16 exp_rlink_slope; + u16 comp_rlink_threshold; + u16 comp_rlink_slope; + u16 comp_rlink_aig_attack_k; + u16 comp_rlink_aig_leak_down; + u16 comp_rlink_aig_leak_up; + u16 comp_rlink_aig_max; + u16 comp_rlink_aig_min; + u16 comp_rlink_aig_release_k; + u16 comp_rlink_aig_sm_leak_rate_fast; + u16 comp_rlink_aig_sm_leak_rate_slow; + u16 comp_rlink_attack_k_msw; + u16 comp_rlink_attack_k_lsw; + u16 comp_rlink_delay; + u16 comp_rlink_release_k_msw; + u16 comp_rlink_release_k_lsw; + u16 comp_rlink_rms_trav; +}; + + +struct iir_coeff_type { + u16 b0_lo; + u16 b0_hi; + u16 b1_lo; + u16 b1_hi; + u16 b2_lo; + u16 b2_hi; +}; + +struct iir_coeff_stage_a { + u16 a1_lo; + u16 a1_hi; + u16 a2_lo; + u16 a2_hi; +}; + +struct acdb_iir_block { + u16 enable_flag; + u16 stage_count; + struct iir_coeff_type stages[4]; + struct iir_coeff_stage_a stages_a[4]; + u16 shift_factor[4]; + u16 pan[4]; +}; + + + +struct mbadrc_band_config_type { + u16 mbadrc_sub_band_enable; + u16 mbadrc_sub_mute; + u16 mbadrc_comp_rms_tav; + u16 mbadrc_comp_threshold; + u16 mbadrc_comp_slop; + u16 mbadrc_comp_attack_msw; + u16 mbadrc_comp_attack_lsw; + u16 mbadrc_comp_release_msw; + u16 mbadrc_comp_release_lsw; + u16 mbadrc_make_up_gain; +}; + +struct mbadrc_parameter { + u16 mbadrc_enable; + u16 mbadrc_num_bands; + u16 mbadrc_down_sample_level; + u16 mbadrc_delay; +}; + +struct acdb_mbadrc_block { + u16 ext_buf[196]; + struct mbadrc_band_config_type band_config[5]; + struct mbadrc_parameter parameters; +}; + +struct acdb_calib_gain_rx { + u16 audppcalgain; + u16 reserved; +}; + +struct acdb_calib_gain_tx { + u16 audprecalgain; + u16 reserved; +}; + +struct acdb_pbe_block { + s16 realbassmix; + s16 basscolorcontrol; + u16 mainchaindelay; + u16 xoverfltorder; + u16 bandpassfltorder; + s16 adrcdelay; + u16 downsamplelevel; + u16 comprmstav; + s16 expthreshold; + u16 expslope; + u16 compthreshold; + u16 compslope; + u16 cpmpattack_lsw; + u16 compattack_msw; + u16 comprelease_lsw; + u16 comprelease_msw; + u16 compmakeupgain; + s16 baselimthreshold; + s16 highlimthreshold; + s16 basslimmakeupgain; + s16 highlimmakeupgain; + s16 limbassgrc; + s16 limhighgrc; + s16 limdelay; + u16 filter_coeffs[90]; +}; + +struct acdb_rmc_block { + s16 rmc_enable; + u16 rmc_ipw_length_ms; + u16 rmc_detect_start_threshdb; + u16 rmc_peak_length_ms; + s16 rmc_init_pulse_threshdb; + u16 rmc_init_pulse_length_ms; + u16 rmc_total_int_length_ms; + u16 rmc_rampupdn_length_ms; + u16 rmc_delay_length_ms; + u16 reserved00; + u16 reserved01; + s16 reserved02; + s16 reserved03; + s16 reserved04; +}; + +struct acdb_fluence_block { + u16 csmode; + u16 cs_tuningMode; + u16 cs_echo_path_delay_by_80; + u16 cs_echo_path_delay; + u16 af1_twoalpha; + u16 af1_erl; + u16 af1_taps; + u16 af1_preset_coefs; + u16 af1_offset; + u16 af2_twoalpha; + u16 af2_erl; + u16 af2_taps; + u16 af2_preset_coefs; + u16 af2_offset; + u16 pcd_twoalpha; + u16 pcd_offset; + u16 cspcd_threshold; + u16 wgthreshold; + u16 mpthreshold; + u16 sf_init_table_0[8]; + u16 sf_init_table_1[8]; + u16 sf_taps; + u16 sf_twoalpha; + u16 dnns_echoalpharev; + u16 dnns_echoycomp; + u16 dnns_wbthreshold; + u16 dnns_echogammahi; + u16 dnns_echogammalo; + u16 dnns_noisegammas; + u16 dnns_noisegamman; + u16 dnns_noisegainmins; + u16 dnns_noisegainminn; + u16 dnns_noisebiascomp; + u16 dnns_acthreshold; + u16 wb_echo_ratio_2mic; + u16 wb_gamma_e; + u16 wb_gamma_nn; + u16 wb_gamma_sn; + u16 vcodec_delay0; + u16 vcodec_delay1; + u16 vcodec_len0; + u16 vcodec_len1; + u16 vcodec_thr0; + u16 vcodec_thr1; + u16 fixcalfactorleft; + u16 fixcalfactorright; + u16 csoutputgain; + u16 enh_meu_1; + u16 enh_meu_2; + u16 fixed_over_est; + u16 rx_nlpp_limit; + u16 rx_nlpp_gain; + u16 wnd_threshold; + u16 wnd_ns_hover; + u16 wnd_pwr_smalpha; + u16 wnd_det_esmalpha; + u16 wnd_ns_egoffset; + u16 wnd_sm_ratio; + u16 wnd_det_coefs[5]; + u16 wnd_th1; + u16 wnd_th2; + u16 wnd_fq; + u16 wnd_dfc; + u16 wnd_sm_alphainc; + u16 wnd_sm_alphsdec; + u16 lvnv_spdet_far; + u16 lvnv_spdet_mic; + u16 lvnv_spdet_xclip; + u16 dnns_nl_atten; + u16 dnns_cni_level; + u16 dnns_echogammaalpha; + u16 dnns_echogammarescue; + u16 dnns_echogammadt; + u16 mf_noisegammafac; + u16 e_noisegammafac; + u16 dnns_noisegammainit; + u16 sm_noisegammas; + u16 wnd_noisegamman; + u16 af_taps_bg_spkr; + u16 af_erl_bg_spkr; + u16 minimum_erl_bg; + u16 erl_step_bg; + u16 upprisecalpha; + u16 upprisecthresh; + u16 uppriwindbias; + u16 e_pcd_threshold; + u16 nv_maxvadcount; + u16 crystalspeechreserved[38]; + u16 cs_speaker[7]; + u16 ns_fac; + u16 ns_blocksize; + u16 is_bias; + u16 is_bias_inp; + u16 sc_initb; + u16 ac_resetb; + u16 sc_avar; + u16 is_hover[5]; + u16 is_cf_level; + u16 is_cf_ina; + u16 is_cf_inb; + u16 is_cf_a; + u16 is_cf_b; + u16 sc_th; + u16 sc_pscale; + u16 sc_nc; + u16 sc_hover; + u16 sc_alphas; + u16 sc_cfac; + u16 sc_sdmax; + u16 sc_sdmin; + u16 sc_initl; + u16 sc_maxval; + u16 sc_spmin; + u16 is_ec_th; + u16 is_fx_dl; + u16 coeffs_iva_filt_0[32]; + u16 coeffs_iva_filt_1[32]; +}; + +s32 acdb_get_calibration_data(struct acdb_get_block *get_block); +void fluence_feature_update(int enable, int stream_id); +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/audio_def.h b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_def.h new file mode 100644 index 00000000000..236c6f68bdb --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_def.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2009,2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _MACH_QDSP5_V2_AUDIO_DEF_H +#define _MACH_QDSP5_V2_AUDIO_DEF_H + +/* Define sound device capability */ +#define SNDDEV_CAP_RX 0x1 /* RX direction */ +#define SNDDEV_CAP_TX 0x2 /* TX direction */ +#define SNDDEV_CAP_VOICE 0x4 /* Support voice call */ +#define SNDDEV_CAP_PLAYBACK 0x8 /* Support playback */ +#define SNDDEV_CAP_FM 0x10 /* Support FM radio */ +#define SNDDEV_CAP_TTY 0x20 /* Support TTY */ +#define SNDDEV_CAP_ANC 0x40 /* Support ANC */ +#define SNDDEV_CAP_LB 0x80 /* Loopback */ +#define VOC_NB_INDEX 0 +#define VOC_WB_INDEX 1 +#define VOC_RX_VOL_ARRAY_NUM 2 + +/* Device volume types . In Current deisgn only one of these are supported. */ +#define SNDDEV_DEV_VOL_DIGITAL 0x1 /* Codec Digital volume control */ +#define SNDDEV_DEV_VOL_ANALOG 0x2 /* Codec Analog volume control */ + +#define SIDE_TONE_MASK 0x01 + +#endif /* _MACH_QDSP5_V2_AUDIO_DEF_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/audio_dev_ctl.h b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_dev_ctl.h new file mode 100644 index 00000000000..7c0abcc97a9 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_dev_ctl.h @@ -0,0 +1,206 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_SNDDEV_H +#define __MACH_QDSP5_V2_SNDDEV_H +#include + +#define AUDIO_DEV_CTL_MAX_DEV 64 +#define DIR_TX 2 +#define DIR_RX 1 + +#define DEVICE_IGNORE 0xff +#define SESSION_IGNORE 0x00000000 + +#define VOICE_STATE_INVALID 0x0 +#define VOICE_STATE_INCALL 0x1 +#define VOICE_STATE_OFFCALL 0x2 +#define MAX_COPP_NODE_SUPPORTED 6 +#define MAX_AUDREC_SESSIONS 3 + +#define REAL_STEREO_CHANNEL_MODE 9 + +struct msm_snddev_info { + const char *name; + u32 capability; + u32 copp_id; + u32 acdb_id; + u32 dev_volume; + struct msm_snddev_ops { + int (*open)(struct msm_snddev_info *); + int (*close)(struct msm_snddev_info *); + int (*set_freq)(struct msm_snddev_info *, u32); + int (*enable_sidetone)(struct msm_snddev_info *, u32); + int (*set_device_volume)(struct msm_snddev_info *, u32); + } dev_ops; + u8 opened; + void *private_data; + bool state; + u32 sample_rate; + u32 set_sample_rate; + u32 sessions; + int usage_count; + s32 max_voc_rx_vol[VOC_RX_VOL_ARRAY_NUM]; /* [0] is for NB,[1] for WB */ + s32 min_voc_rx_vol[VOC_RX_VOL_ARRAY_NUM]; +}; + +struct msm_volume { + int volume; /* Volume parameter, in % Scale */ + int pan; +}; + +extern struct msm_volume msm_vol_ctl; + +int msm_get_dual_mic_config(int enc_session_id); +int msm_set_dual_mic_config(int enc_session_id, int config); +int msm_reset_all_device(void); +void msm_snddev_register(struct msm_snddev_info *); +void msm_snddev_unregister(struct msm_snddev_info *); +int msm_snddev_devcount(void); +int msm_snddev_query(int dev_id); +unsigned short msm_snddev_route_dec(int popp_id); +unsigned short msm_snddev_route_enc(int enc_id); +int msm_snddev_set_dec(int popp_id, int copp_id, int set); +int msm_snddev_set_enc(int popp_id, int copp_id, int set); +int msm_snddev_is_set(int popp_id, int copp_id); +int msm_get_voc_route(u32 *rx_id, u32 *tx_id); +int msm_set_voc_route(struct msm_snddev_info *dev_info, int stream_type, + int dev_id); +int msm_snddev_enable_sidetone(u32 dev_id, u32 enable); + +struct msm_snddev_info *audio_dev_ctrl_find_dev(u32 dev_id); + +void msm_release_voc_thread(void); + +int snddev_voice_set_volume(int vol, int path); + +struct auddev_evt_voc_devinfo { + u32 dev_type; /* Rx or Tx */ + u32 acdb_dev_id; /* acdb id of device */ + u32 dev_sample; /* Sample rate of device */ + s32 max_rx_vol[VOC_RX_VOL_ARRAY_NUM]; /* unit is mb (milibel), + [0] is for NB, other for WB */ + s32 min_rx_vol[VOC_RX_VOL_ARRAY_NUM]; /* unit is mb */ + u32 dev_id; /* registered device id */ +}; + +struct auddev_evt_audcal_info { + u32 dev_id; + u32 acdb_id; + u32 sample_rate; + u32 dev_type; + u32 sessions; +}; + +struct auddev_evt_devinfo { + u32 dev_id; + u32 acdb_id; + u32 sample_rate; + u32 dev_type; + u32 sessions; +}; + +union msm_vol_mute { + int vol; + bool mute; +}; + +struct auddev_evt_voc_mute_info { + u32 dev_type; + u32 acdb_dev_id; + union msm_vol_mute dev_vm_val; +}; + +struct auddev_evt_freq_info { + u32 dev_type; + u32 acdb_dev_id; + u32 sample_rate; +}; + +union auddev_evt_data { + struct auddev_evt_voc_devinfo voc_devinfo; + struct auddev_evt_voc_mute_info voc_vm_info; + struct auddev_evt_freq_info freq_info; + u32 routing_id; + s32 session_vol; + s32 voice_state; + struct auddev_evt_audcal_info audcal_info; + struct auddev_evt_devinfo devinfo; +}; + +struct message_header { + uint32_t id; + uint32_t data_len; +}; + +#define AUDDEV_EVT_DEV_CHG_VOICE 0x01 /* device change event */ +#define AUDDEV_EVT_DEV_RDY 0x02 /* device ready event */ +#define AUDDEV_EVT_DEV_RLS 0x04 /* device released event */ +#define AUDDEV_EVT_REL_PENDING 0x08 /* device release pending */ +#define AUDDEV_EVT_DEVICE_VOL_MUTE_CHG 0x10 /* device volume changed */ +#define AUDDEV_EVT_START_VOICE 0x20 /* voice call start */ +#define AUDDEV_EVT_END_VOICE 0x40 /* voice call end */ +#define AUDDEV_EVT_STREAM_VOL_CHG 0x80 /* device volume changed */ +#define AUDDEV_EVT_FREQ_CHG 0x100 /* Change in freq */ +#define AUDDEV_EVT_VOICE_STATE_CHG 0x200 /* Change in voice state */ +#define AUDDEV_EVT_DEVICE_INFO 0x400 /* routed device information + event */ + +#define AUDDEV_CLNT_VOC 0x1 /* Vocoder clients */ +#define AUDDEV_CLNT_DEC 0x2 /* Decoder clients */ +#define AUDDEV_CLNT_ENC 0x3 /* Encoder clients */ +#define AUDDEV_CLNT_AUDIOCAL 0x4 /* AudioCalibration client */ + +#define AUDIO_DEV_CTL_MAX_LISTNER 20 /* Max Listeners Supported */ + +struct msm_snd_evt_listner { + uint32_t evt_id; + uint32_t clnt_type; + uint32_t clnt_id; + void *private_data; + void (*auddev_evt_listener)(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data); + struct msm_snd_evt_listner *cb_next; + struct msm_snd_evt_listner *cb_prev; +}; + +struct event_listner { + struct msm_snd_evt_listner *cb; + u32 num_listner; + int state; /* Call state */ /* TODO remove this if not req*/ +}; + +extern struct event_listner event; +int auddev_register_evt_listner(u32 evt_id, u32 clnt_type, u32 clnt_id, + void (*listner)(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data), + void *private_data); +int auddev_unregister_evt_listner(u32 clnt_type, u32 clnt_id); +void mixer_post_event(u32 evt_id, u32 dev_id); +void broadcast_event(u32 evt_id, u32 dev_id, u32 session_id); +int msm_snddev_request_freq(int *freq, u32 session_id, + u32 capability, u32 clnt_type); +int msm_snddev_withdraw_freq(u32 session_id, + u32 capability, u32 clnt_type); +int msm_device_is_voice(int dev_id); +int msm_get_voc_freq(int *tx_freq, int *rx_freq); +int msm_snddev_get_enc_freq(int session_id); +int msm_set_voice_vol(int dir, s32 volume); +int msm_set_voice_mute(int dir, int mute); +int msm_get_voice_state(void); +#ifdef CONFIG_DEBUG_FS +bool is_dev_opened(u32 acdb_id); +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/audio_interct.h b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_interct.h new file mode 100644 index 00000000000..2a7b89ec221 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/audio_interct.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_AUDIO_INTERCT_H +#define __MACH_QDSP5_V2_AUDIO_INTERCT_H + +#define AUDIO_INTERCT_ADSP 0 +#define AUDIO_INTERCT_LPA 1 +#define AUDIO_ADSP_A 1 +#define AUDIO_ADSP_V 0 + +void audio_interct_lpa(u32 source); +void audio_interct_aux_regsel(u32 source); +void audio_interct_rpcm_source(u32 source); +void audio_interct_tpcm_source(u32 source); +void audio_interct_pcmmi2s(u32 source); +void audio_interct_codec(u32 source); +void audio_interct_multichannel(u32 source); + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/audpp.h b/arch/arm/mach-msm/include/mach/qdsp5v2/audpp.h new file mode 100644 index 00000000000..bdec256e2b1 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/audpp.h @@ -0,0 +1,129 @@ +/*arch/arm/mach-msm/qdsp5iv2/audpp.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#ifndef _MACH_QDSP5_V2_AUDPP_H +#define _MACH_QDSP5_V2_AUDPP_H + +#include + +typedef void (*audpp_event_func)(void *private, unsigned id, uint16_t *msg); + +/* worst case delay of 1sec for response */ +#define MSM_AUD_DECODER_WAIT_MS 1000 +#define MSM_AUD_MODE_TUNNEL 0x00000100 +#define MSM_AUD_MODE_NONTUNNEL 0x00000200 +#define MSM_AUD_MODE_LP 0x00000400 +#define MSM_AUD_DECODER_MASK 0x0000FFFF +#define MSM_AUD_OP_MASK 0xFFFF0000 + +/* read call timeout for error cases */ +#define MSM_AUD_BUFFER_UPDATE_WAIT_MS 2000 + +/* stream info error message mask */ +#define AUDPLAY_STREAM_INFO_MSG_MASK 0xFFFF0000 +#define AUDPLAY_ERROR_THRESHOLD_ENABLE 0xFFFFFFFF + +#define NON_TUNNEL_MODE_PLAYBACK 1 +#define TUNNEL_MODE_PLAYBACK 0 + +#define AUDPP_MIXER_ICODEC AUDPP_CMD_CFG_DEV_MIXER_DEV_0 +#define AUDPP_MIXER_1 AUDPP_CMD_CFG_DEV_MIXER_DEV_1 +#define AUDPP_MIXER_2 AUDPP_CMD_CFG_DEV_MIXER_DEV_2 +#define AUDPP_MIXER_3 AUDPP_CMD_CFG_DEV_MIXER_DEV_3 +#define AUDPP_MIXER_HLB AUDPP_CMD_CFG_DEV_MIXER_DEV_4 +#define AUDPP_MIXER_NONHLB (AUDPP_CMD_CFG_DEV_MIXER_DEV_0 | \ + AUDPP_CMD_CFG_DEV_MIXER_DEV_1 | \ + AUDPP_CMD_CFG_DEV_MIXER_DEV_2 | \ + AUDPP_CMD_CFG_DEV_MIXER_DEV_3) +#define AUDPP_MIXER_UPLINK_RX AUDPP_CMD_CFG_DEV_MIXER_DEV_5 +#define AUDPP_MAX_COPP_DEVICES 6 + +enum obj_type { + COPP, + POPP +}; + +enum msm_aud_decoder_state { + MSM_AUD_DECODER_STATE_NONE = 0, + MSM_AUD_DECODER_STATE_FAILURE = 1, + MSM_AUD_DECODER_STATE_SUCCESS = 2, + MSM_AUD_DECODER_STATE_CLOSE = 3, +}; + +int audpp_adec_alloc(unsigned dec_attrb, const char **module_name, + unsigned *queueid); +void audpp_adec_free(int decid); + +struct audpp_event_callback { + audpp_event_func fn; + void *private; +}; + +int audpp_register_event_callback(struct audpp_event_callback *eh); +int audpp_unregister_event_callback(struct audpp_event_callback *eh); +int is_audpp_enable(void); + +int audpp_enable(int id, audpp_event_func func, void *private); +void audpp_disable(int id, void *private); + +int audpp_send_queue1(void *cmd, unsigned len); +int audpp_send_queue2(void *cmd, unsigned len); +int audpp_send_queue3(void *cmd, unsigned len); + +void audpp_route_stream(unsigned short dec_id, unsigned short mixer_mask); + +int audpp_set_volume_and_pan(unsigned id, unsigned volume, int pan, + enum obj_type objtype); +int audpp_pause(unsigned id, int pause); +int audpp_flush(unsigned id); +int audpp_query_avsync(int id); +int audpp_restore_avsync(int id, uint16_t *avsync); + +int audpp_dsp_set_eq(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_eqalizer *eq, + enum obj_type objtype); + +int audpp_dsp_set_spa(unsigned id, + struct audpp_cmd_cfg_object_params_spectram *spa, + enum obj_type objtype); + +int audpp_dsp_set_stf(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_sidechain *stf, + enum obj_type objtype); + +int audpp_dsp_set_vol_pan(unsigned id, + struct audpp_cmd_cfg_object_params_volume *vol_pan, + enum obj_type objtype); + +int audpp_dsp_set_mbadrc(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_mbadrc *mbadrc, + enum obj_type objtype); + +int audpp_dsp_set_qconcert_plus(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_qconcert *qconcert_plus, + enum obj_type objtype); + +int audpp_dsp_set_rx_iir(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_pcm *iir, + enum obj_type objtype); + +int audpp_dsp_set_gain_rx(unsigned id, + struct audpp_cmd_cfg_cal_gain *calib_gain_rx, + enum obj_type objtype); +int audpp_dsp_set_pbe(unsigned id, unsigned enable, + struct audpp_cmd_cfg_pbe *pbe_block, + enum obj_type objtype); +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/audpreproc.h b/arch/arm/mach-msm/include/mach/qdsp5v2/audpreproc.h new file mode 100644 index 00000000000..6abeae120cf --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/audpreproc.h @@ -0,0 +1,96 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _MACH_QDSP5_V2_AUDPREPROC_H +#define _MACH_QDSP5_V2_AUDPREPROC_H + +#include +#include + +#define MAX_ENC_COUNT 3 + +#define MSM_ADSP_ENC_CODEC_WAV 0 +#define MSM_ADSP_ENC_CODEC_AAC 1 +#define MSM_ADSP_ENC_CODEC_SBC 2 +#define MSM_ADSP_ENC_CODEC_AMRNB 3 +#define MSM_ADSP_ENC_CODEC_EVRC 4 +#define MSM_ADSP_ENC_CODEC_QCELP 5 +#define MSM_ADSP_ENC_CODEC_EXT_WAV (15) + +#define MSM_ADSP_ENC_MODE_TUNNEL 24 +#define MSM_ADSP_ENC_MODE_NON_TUNNEL 25 + +#define AUDPREPROC_CODEC_MASK 0x00FF +#define AUDPREPROC_MODE_MASK 0xFF00 + +#define MSM_AUD_ENC_MODE_TUNNEL 0x00000100 +#define MSM_AUD_ENC_MODE_NONTUNNEL 0x00000200 + +#define SOURCE_PIPE_1 0x0001 +#define SOURCE_PIPE_0 0x0000 + +/* event callback routine prototype*/ +typedef void (*audpreproc_event_func)(void *private, unsigned id, void *msg); + +struct audpreproc_event_callback { + audpreproc_event_func fn; + void *private; +}; + +/*holds audrec information*/ +struct audrec_session_info { + int session_id; + int sampling_freq; +}; + +/* Exported common api's from audpreproc layer */ +int audpreproc_aenc_alloc(unsigned enc_type, const char **module_name, + unsigned *queue_id); +void audpreproc_aenc_free(int enc_id); + +int audpreproc_enable(int enc_id, audpreproc_event_func func, void *private); +void audpreproc_disable(int enc_id, void *private); + +int audpreproc_send_audreccmdqueue(void *cmd, unsigned len); + +int audpreproc_send_preproccmdqueue(void *cmd, unsigned len); + +int audpreproc_dsp_set_agc(struct audpreproc_cmd_cfg_agc_params *agc, + unsigned len); +int audpreproc_dsp_set_agc2(struct audpreproc_cmd_cfg_agc_params_2 *agc2, + unsigned len); +int audpreproc_dsp_set_ns(struct audpreproc_cmd_cfg_ns_params *ns, + unsigned len); +int audpreproc_dsp_set_iir( +struct audpreproc_cmd_cfg_iir_tuning_filter_params *iir, unsigned len); + +int audpreproc_dsp_set_agc(struct audpreproc_cmd_cfg_agc_params *agc, + unsigned int len); + +int audpreproc_dsp_set_iir( +struct audpreproc_cmd_cfg_iir_tuning_filter_params *iir, unsigned int len); + +int audpreproc_update_audrec_info(struct audrec_session_info + *audrec_session_info); +int audpreproc_unregister_event_callback(struct audpreproc_event_callback *ecb); + +int audpreproc_register_event_callback(struct audpreproc_event_callback *ecb); + +int audpreproc_dsp_set_gain_tx( + struct audpreproc_cmd_cfg_cal_gain *calib_gain_tx, unsigned len); + +void get_audrec_session_info(int id, struct audrec_session_info *info); + +int audpreproc_dsp_set_lvnv( + struct audpreproc_cmd_cfg_lvnv_param *preproc_lvnv, unsigned len); +#endif /* _MACH_QDSP5_V2_AUDPREPROC_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/aux_pcm.h b/arch/arm/mach-msm/include/mach/qdsp5v2/aux_pcm.h new file mode 100644 index 00000000000..100ddea35ab --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/aux_pcm.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_AUX_PCM_H +#define __MACH_QDSP5_V2_AUX_PCM_H +#include + +/* define some values in AUX_CODEC_CTL register */ +#define AUX_CODEC_CTL__ADSP_CODEC_CTL_EN__MSM_V 0 /* default */ +#define AUX_CODEC_CTL__ADSP_CODEC_CTL_EN__ADSP_V 0x800 +#define AUX_CODEC_CTL__PCM_SYNC_LONG_OFFSET_V 0x400 +#define AUX_CODEC_CTL__PCM_SYNC_SHORT_OFFSET_V 0x200 +#define AUX_CODEC_CTL__I2S_SAMPLE_CLK_SRC__SDAC_V 0 +#define AUX_CODEC_CTL__I2S_SAMPLE_CLK_SRC__ICODEC_V 0x80 +#define AUX_CODEC_CTL__I2S_SAMPLE_CLK_MODE__MASTER_V 0 +#define AUX_CODEC_CTL__I2S_SAMPLE_CLK_MODE__SLAVE_V 0x40 +#define AUX_CODEC_CTL__I2S_RX_MODE__REV_V 0 +#define AUX_CODEC_CTL__I2S_RX_MODE__TRAN_V 0x20 +#define AUX_CODEC_CTL__I2S_CLK_MODE__MASTER_V 0 +#define AUX_CODEC_CTL__I2S_CLK_MODE__SLAVE_V 0x10 +#define AUX_CODEC_CTL__AUX_PCM_MODE__PRIM_MASTER_V 0 +#define AUX_CODEC_CTL__AUX_PCM_MODE__AUX_MASTER_V 0x4 +#define AUX_CODEC_CTL__AUX_PCM_MODE__PRIM_SLAVE_V 0x8 +#define AUX_CODEC_CTL__AUX_CODEC_MDOE__PCM_V 0 +#define AUX_CODEC_CTL__AUX_CODEC_MODE__I2S_V 0x2 + +/* define some values in PCM_PATH_CTL register */ +#define PCM_PATH_CTL__ADSP_CTL_EN__MSM_V 0 +#define PCM_PATH_CTL__ADSP_CTL_EN__ADSP_V 0x8 + +/* define some values for aux codec config of AFE*/ +/* PCM CTL */ +#define PCM_CTL__RPCM_WIDTH__LINEAR_V 0x1 +#define PCM_CTL__TPCM_WIDTH__LINEAR_V 0x2 +/* AUX_CODEC_INTF_CTL */ +#define AUX_CODEC_INTF_CTL__PCMINTF_DATA_EN_V 0x800 +/* DATA_FORMAT_PADDING_INFO */ +#define DATA_FORMAT_PADDING_INFO__RPCM_FORMAT_V 0x400 +#define DATA_FORMAT_PADDING_INFO__TPCM_FORMAT_V 0x2000 + +void aux_codec_adsp_codec_ctl_en(bool msm_adsp_en); +void aux_codec_pcm_path_ctl_en(bool msm_adsp_en); +int aux_pcm_gpios_request(void); +void aux_pcm_gpios_free(void); + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/codec_utils.h b/arch/arm/mach-msm/include/mach/qdsp5v2/codec_utils.h new file mode 100644 index 00000000000..68a3c44222b --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/codec_utils.h @@ -0,0 +1,136 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef CODEC_UTILS_H +#define CODEC_UTILS_H + +#include + +#define ADRV_STATUS_AIO_INTF 0x00000001 +#define ADRV_STATUS_OBUF_GIVEN 0x00000002 +#define ADRV_STATUS_IBUF_GIVEN 0x00000004 +#define ADRV_STATUS_FSYNC 0x00000008 + +#define PCM_BUFSZ_MIN 4800 /* Hold one stereo MP3 frame */ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 +#define AUDPP_DEC_STATUS_EOS 5 + +/* worst case delay of 3secs(3000ms) for AV Sync Query response */ +#define AVSYNC_EVENT_TIMEOUT 3000 + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audio_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct codec_operations { + long (*ioctl)(struct file *, unsigned int, unsigned long); + void (*adec_params)(struct audio *); +}; + +struct audio { + spinlock_t dsp_lock; + + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + struct list_head out_queue; /* queue to retain output buffers */ + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_bits; /* bits per sample (used by PCM decoder) */ + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + uint32_t drv_status; + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int buf_refresh; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audio_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + uint32_t device_events; + uint32_t device_switch; /* Flag to indicate device switch */ + uint64_t bytecount_consumed; + uint64_t bytecount_head; + uint64_t bytecount_given; + uint64_t bytecount_query; + + struct list_head pmem_region_queue; /* protected by lock */ + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; + + unsigned int minor_no; + struct codec_operations codec_ops; +}; + +#endif /* !CODEC_UTILS_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/lpa.h b/arch/arm/mach-msm/include/mach/qdsp5v2/lpa.h new file mode 100644 index 00000000000..d71cf728080 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/lpa.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_LPA_H__ +#define __MACH_QDSP5_V2_LPA_H__ + +#define LPA_OUTPUT_INTF_WB_CODEC 3 +#define LPA_OUTPUT_INTF_SDAC 1 +#define LPA_OUTPUT_INTF_MI2S 2 + +struct lpa_codec_config { + uint32_t sample_rate; + uint32_t sample_width; + uint32_t output_interface; + uint32_t num_channels; +}; + +struct lpa_drv; + +struct lpa_drv *lpa_get(void); +void lpa_put(struct lpa_drv *lpa); +int lpa_cmd_codec_config(struct lpa_drv *lpa, + struct lpa_codec_config *config_ptr); +int lpa_cmd_enable_codec(struct lpa_drv *lpa, bool enable); + +#endif + diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/lpa_hw.h b/arch/arm/mach-msm/include/mach/qdsp5v2/lpa_hw.h new file mode 100644 index 00000000000..bfff3847350 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/lpa_hw.h @@ -0,0 +1,236 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_LPA_HW_H__ +#define __MACH_QDSP5_V2_LPA_HW_H__ + +#define LPA_MAX_BUF_SIZE 0x30000 + +/* LPA Output config registers */ +enum { + LPA_OBUF_CONTROL = 0x00000000, + LPA_OBUF_CODEC = 0x00000004, + LPA_OBUF_HLB_MIN_ADDR = 0x00000008, + LPA_OBUF_HLB_MAX_ADDR = 0x0000000C, + LPA_OBUF_HLB_WPTR = 0x00000010, + LPA_OBUF_HLB_VOLUME_CONTROL = 0x00000014, + LPA_OBUF_LLB_MIN_ADDR = 0x00000018, + LPA_OBUF_LLB_MAX_ADDR = 0x0000001C, + LPA_OBUF_SB_MIN_ADDR = 0x00000020, + LPA_OBUF_SB_MAX_ADDR = 0x00000024, + LPA_OBUF_INTR_ENABLE = 0x00000028, + LPA_OBUF_INTR_STATUS = 0x0000002C, + LPA_OBUF_WMARK_ASSIGN = 0x00000030, + LPA_OBUF_WMARK_0_LLB = 0x00000034, + LPA_OBUF_WMARK_1_LLB = 0x00000038, + LPA_OBUF_WMARK_2_LLB = 0x0000003C, + LPA_OBUF_WMARK_3_LLB = 0x00000040, + LPA_OBUF_WMARK_HLB = 0x00000044, + LPA_OBUF_WMARK_SB = 0x00000048, + LPA_OBUF_RDPTR_LLB = 0x0000004C, + LPA_OBUF_RDPTR_HLB = 0x00000050, + LPA_OBUF_WRPTR_SB = 0x00000054, + LPA_OBUF_UTC_CONFIG = 0x00000058, + LPA_OBUF_UTC_INTR_LOW = 0x0000005C, + LPA_OBUF_UTC_INTR_HIGH = 0x00000060, + LPA_OBUF_UTC_LOW = 0x00000064, + LPA_OBUF_UTC_HIGH = 0x00000068, + LPA_OBUF_MISR = 0x0000006C, + LPA_OBUF_STATUS = 0x00000070, + LPA_OBUF_ACK = 0x00000074, + LPA_OBUF_MEMORY_CONTROL = 0x00000078, + LPA_OBUF_MEMORY_STATUS = 0x0000007C, + LPA_OBUF_MEMORY_TIME_CONTROL = 0x00000080, + LPA_OBUF_ACC_LV = 0x00000084, + LPA_OBUF_ACC_HV = 0x0000008c, + LPA_OBUF_RESETS = 0x00000090, + LPA_OBUF_TESTBUS = 0x00000094, +}; + +/* OBUF_CODEC definition */ +#define LPA_OBUF_CODEC_RESERVED31_22_BMSK 0xffc00000 +#define LPA_OBUF_CODEC_RESERVED31_22_SHFT 0x16 +#define LPA_OBUF_CODEC_LOAD_BMSK 0x200000 +#define LPA_OBUF_CODEC_LOAD_SHFT 0x15 +#define LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK 0x100000 +#define LPA_OBUF_CODEC_CODEC_INTF_EN_SHFT 0x14 +#define LPA_OBUF_CODEC_SAMP_BMSK 0xf0000 +#define LPA_OBUF_CODEC_SAMP_SHFT 0x10 +#define LPA_OBUF_CODEC_BITS_PER_CHAN_BMSK 0xc000 +#define LPA_OBUF_CODEC_BITS_PER_CHAN_SHFT 0xe +#define LPA_OBUF_CODEC_RESERVED_13_7_BMSK 0x3f80 +#define LPA_OBUF_CODEC_RESERVED_13_7_SHFT 0x7 +#define LPA_OBUF_CODEC_INTF_BMSK 0x70 +#define LPA_OBUF_CODEC_INTF_SHFT 0x4 +#define LPA_OBUF_CODEC_NUM_CHAN_BMSK 0xf +#define LPA_OBUF_CODEC_NUM_CHAN_SHFT 0 + +/* OBUF_CONTROL definition */ +#define LPA_OBUF_CONTROL_RESERVED31_9_BMSK 0xfffffe00 +#define LPA_OBUF_CONTROL_RESERVED31_9_SHFT 0x9 +#define LPA_OBUF_CONTROL_TEST_EN_BMSK 0x100 +#define LPA_OBUF_CONTROL_TEST_EN_SHFT 0x8 +#define LPA_OBUF_CONTROL_LLB_CLR_CMD_BMSK 0x80 +#define LPA_OBUF_CONTROL_LLB_CLR_CMD_SHFT 0x7 +#define LPA_OBUF_CONTROL_SB_SAT_EN_BMSK 0x40 +#define LPA_OBUF_CONTROL_SB_SAT_EN_SHFT 0x6 +#define LPA_OBUF_CONTROL_LLB_SAT_EN_BMSK 0x20 +#define LPA_OBUF_CONTROL_LLB_SAT_EN_SHFT 0x5 +#define LPA_OBUF_CONTROL_RESERVED4_BMSK 0x10 +#define LPA_OBUF_CONTROL_RESERVED4_SHFT 0x4 +#define LPA_OBUF_CONTROL_LLB_ACC_EN_BMSK 0x8 +#define LPA_OBUF_CONTROL_LLB_ACC_EN_SHFT 0x3 +#define LPA_OBUF_CONTROL_HLB_EN_BMSK 0x4 +#define LPA_OBUF_CONTROL_HLB_EN_SHFT 0x2 +#define LPA_OBUF_CONTROL_LLB_EN_BMSK 0x2 +#define LPA_OBUF_CONTROL_LLB_EN_SHFT 0x1 +#define LPA_OBUF_CONTROL_SB_EN_BMSK 0x1 +#define LPA_OBUF_CONTROL_SB_EN_SHFT 0 + +/* OBUF_RESET definition */ +#define LPA_OBUF_RESETS_MISR_RESET 0x1 +#define LPA_OBUF_RESETS_OVERALL_RESET 0x2 + +/* OBUF_STATUS definition */ +#define LPA_OBUF_STATUS_RESET_DONE 0x80000 +#define LPA_OBUF_STATUS_LLB_CLR_BMSK 0x40000 +#define LPA_OBUF_STATUS_LLB_CLR_SHFT 0x12 + +/* OBUF_HLB_MIN_ADDR definition */ +#define LPA_OBUF_HLB_MIN_ADDR_LOAD_BMSK 0x40000 +#define LPA_OBUF_HLB_MIN_ADDR_SEG_BMSK 0x3e000 + +/* OBUF_HLB_MAX_ADDR definition */ +#define LPA_OBUF_HLB_MAX_ADDR_SEG_BMSK 0x3fff8 + +/* OBUF_LLB_MIN_ADDR definition */ +#define LPA_OBUF_LLB_MIN_ADDR_LOAD_BMSK 0x40000 +#define LPA_OBUF_LLB_MIN_ADDR_SEG_BMSK 0x3e000 + +/* OBUF_LLB_MAX_ADDR definition */ +#define LPA_OBUF_LLB_MAX_ADDR_SEG_BMSK 0x3ff8 +#define LPA_OBUF_LLB_MAX_ADDR_SEG_SHFT 0x3 + +/* OBUF_SB_MIN_ADDR definition */ +#define LPA_OBUF_SB_MIN_ADDR_LOAD_BMSK 0x4000 +#define LPA_OBUF_SB_MIN_ADDR_SEG_BMSK 0x3e00 + +/* OBUF_SB_MAX_ADDR definition */ +#define LPA_OBUF_SB_MAX_ADDR_SEG_BMSK 0x3ff8 + +/* OBUF_MEMORY_CONTROL definition */ +#define LPA_OBUF_MEM_CTL_PWRUP_BMSK 0xfff +#define LPA_OBUF_MEM_CTL_PWRUP_SHFT 0x0 + +/* OBUF_INTR_ENABLE definition */ +#define LPA_OBUF_INTR_EN_BMSK 0x3 + +/* OBUF_WMARK_ASSIGN definition */ +#define LPA_OBUF_WMARK_ASSIGN_BMSK 0xF +#define LPA_OBUF_WMARK_ASSIGN_DONE 0xF + +/* OBUF_WMARK_n_LLB definition */ +#define LPA_OBUF_WMARK_n_LLB_ADDR(n) (0x00000034 + 0x4 * (n)) +#define LPA_OBUF_LLB_WMARK_CTRL_BMSK 0xc0000 +#define LPA_OBUF_LLB_WMARK_CTRL_SHFT 0x12 +#define LPA_OBUF_LLB_WMARK_MAP_BMSK 0xf00000 +#define LPA_OBUF_LLB_WMARK_MAP_SHFT 0x14 + +/* OBUF_WMARK_SB definition */ +#define LPA_OBUF_SB_WMARK_CTRL_BMSK 0xc0000 +#define LPA_OBUF_SB_WMARK_CTRL_SHFT 0x12 +#define LPA_OBUF_SB_WMARK_MAP_BMSK 0xf00000 +#define LPA_OBUF_SB_WMARK_MAP_SHFT 0x14 + +/* OBUF_WMARK_HLB definition */ +#define LPA_OBUF_HLB_WMARK_CTRL_BMSK 0xc0000 +#define LPA_OBUF_HLB_WMARK_CTRL_SHFT 0x12 +#define LPA_OBUF_HLB_WMARK_MAP_BMSK 0xf00000 +#define LPA_OBUF_HLB_WMARK_MAP_SHFT 0x14 + +/* OBUF_UTC_CONFIG definition */ +#define LPA_OBUF_UTC_CONFIG_MAP_BMSK 0xf0 +#define LPA_OBUF_UTC_CONFIG_MAP_SHFT 0x4 +#define LPA_OBUF_UTC_CONFIG_EN_BMSK 0x1 +#define LPA_OBUF_UTC_CONFIG_EN_SHFT 0 +#define LPA_OBUF_UTC_CONFIG_NO_INTR 0xF + +/* OBUF_ACK definition */ +#define LPA_OBUF_ACK_RESET_DONE_BMSK 0x80000 +#define LPA_OBUF_ACK_RESET_DONE_SHFT 0x13 +enum { + LPA_SAMPLE_RATE_8KHZ = 0x0000, + LPA_SAMPLE_RATE_11P025KHZ = 0x0001, + LPA_SAMPLE_RATE_16KHZ = 0x0002, + LPA_SAMPLE_RATE_22P05KHZ = 0x0003, + LPA_SAMPLE_RATE_32KHZ = 0x0004, + LPA_SAMPLE_RATE_44P1KHZ = 0x0005, + LPA_SAMPLE_RATE_48KHZ = 0x0006, + LPA_SAMPLE_RATE_64KHZ = 0x0007, + LPA_SAMPLE_RATE_96KHZ = 0x0008, +}; + +enum { + LPA_BITS_PER_CHAN_16BITS = 0x0000, + LPA_BITS_PER_CHAN_24BITS = 0x0001, + LPA_BITS_PER_CHAN_32BITS = 0x0002, + LPA_BITS_PER_CHAN_RESERVED = 0x0003, +}; + +enum { + LPA_INTF_WB_CODEC = 0x0000, + LPA_INTF_SDAC = 0x0001, + LPA_INTF_MI2S = 0x0002, + LPA_INTF_RESERVED = 0x0003, +}; + +enum { + LPA_BUF_ID_HLB, /* HLB buffer */ + LPA_BUF_ID_LLB, /* LLB buffer */ + LPA_BUF_ID_SB, /* SB buffer */ + LPA_BUF_ID_UTC, +}; + +/* WB_CODEC & SDAC can only support 16bit mono/stereo. + * MI2S can bit format and number of channel + */ +enum { + LPA_NUM_CHAN_MONO = 0x0000, + LPA_NUM_CHAN_STEREO = 0x0001, + LPA_NUM_CHAN_5P1 = 0x0002, + LPA_NUM_CHAN_7P1 = 0x0003, + LPA_NUM_CHAN_4_CHANNEL = 0x0004, +}; + +enum { + LPA_WMARK_CTL_DISABLED = 0x0, + LPA_WMARK_CTL_NON_BLOCK = 0x1, + LPA_WMARK_CTL_ZERO_INSERT = 0x2, + LPA_WMARK_CTL_RESERVED = 0x3 +}; + +struct lpa_mem_bank_select { + u32 b0:1; /*RAM bank 0 16KB=2Kx64(0) */ + u32 b1:1; /*RAM bank 1 16KB=2Kx64(0) */ + u32 b2:1; /*RAM bank 2 16KB=2Kx64(0) */ + u32 b3:1; /*RAM bank 3 16KB=2Kx64(0) */ + u32 b4:1; /*RAM bank 4 16KB=2Kx64(1) */ + u32 b5:1; /*RAM bank 5 16KB=2Kx64(1) */ + u32 b6:1; /*RAM bank 6 16KB=2Kx64(1) */ + u32 b7:1; /*RAM bank 7 16KB=2Kx64(1) */ + u32 b8:1; /*RAM bank 8 16KB=4Kx32(0) */ + u32 b9:1; /*RAM bank 9 16KB=4Kx32(1) */ + u32 b10:1; /*RAM bank 10 16KB=4Kx32(2) */ + u32 llb:1; /*RAM bank 11 16KB=4Kx32(3) */ +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/marimba_profile.h b/arch/arm/mach-msm/include/mach/qdsp5v2/marimba_profile.h new file mode 100644 index 00000000000..c1cb3fe35fd --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/marimba_profile.h @@ -0,0 +1,3201 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_MARIMBA_PROFILE_H__ +#define __MACH_QDSP5_V2_MARIMBA_PROFILE_H__ + +/***************************************************************************\ + Handset +\***************************************************************************/ + + +#define HANDSET_RX_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x44)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0x8C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x21, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x2710}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HANDSET_RX_16000_OSR_256 HANDSET_RX_8000_OSR_256 + +#define HANDSET_RX_48000_OSR_64\ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x47)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xfF, 0x8C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x42)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x21, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x2710}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HANDSET_RX_48000_OSR_256\ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x44)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xfF, 0x8C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x21, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x2710}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HANDSET_TX_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x5E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xd0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xE6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HANDSET_TX_16000_OSR_256 HANDSET_TX_8000_OSR_256 + +#define HANDSET_TX_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x5A)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xd0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xE6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/***************************************************************************\ + Headset +\***************************************************************************/ + + + +#define HEADSET_STEREO_TX_8000_OSR_256\ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xfd, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x5E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xE8, 0xE8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x1c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xE7)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0e, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0d, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_TX_16000_OSR_256 HEADSET_STEREO_TX_8000_OSR_256 + +#define HEADSET_STEREO_TX_48000_OSR_64\ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xfd, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xfc, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x46)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xE8, 0xE8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x1c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xE7)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0e, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0d, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_TX_48000_OSR_256\ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FALSH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xfd, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xfc, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x5A)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xE8, 0xE8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x1c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xE7)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0e, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0d, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_MONO_TX_16000_OSR_256\ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xfc, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x5E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFf, 0xc8)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xE7)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_MONO_TX_8000_OSR_256 HEADSET_MONO_TX_16000_OSR_256 + +#define HEADSET_MONO_TX_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x5A)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xC8)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xE7)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_RX_CAPLESS_8000_OSR_256\ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0xa2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xeb)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_RX_CAPLESS_48000_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x42)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x67)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0xa2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xab)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_RX_CAPLESS_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0xa2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xab)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HEADSET_RX_LEGACY_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0xa0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_RX_LEGACY_16000_OSR_256 HEADSET_RX_LEGACY_8000_OSR_256 + +#define HEADSET_RX_LEGACY_48000_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x42)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x67)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0xa0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HEADSET_RX_LEGACY_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0xa0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_RX_CLASS_D_LEGACY_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x50, 0x50)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xFF)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xc4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xc4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HEADSET_RX_CLASS_D_LEGACY_11025_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x50, 0x50)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xbb)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xc2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xc2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HEADSET_RX_CLASS_D_LEGACY_16000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x50, 0x50)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xd4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xd4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HEADSET_RX_CLASS_D_LEGACY_22050_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x50, 0x50)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xbb)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xd2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xd2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HEADSET_CLASS_D_LEGACY_32000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x50, 0x50)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xf4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xf4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_RX_CLASS_D_LEGACY_48000_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x50, 0x50)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xc5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xc2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_RX_CLASS_D_LEGACY_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x50, 0x50)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xBB)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_RX_CAPLESS_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x82)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xeb)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_RX_CAPLESS_48000_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x42)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x67)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x82)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xab)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + + +#define HEADSET_STEREO_RX_CAPLESS_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x82)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xab)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x82)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_RX_LEGACY_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xaC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xaC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_RX_LEGACY_48000_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x42)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x67)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_RX_LEGACY_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_RX_CLASS_D_LEGACY_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xFF)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xc4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xc4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HEADSET_STEREO_RX_CLASS_D_LEGACY_11025_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xbb)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xc2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xc2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_RX_CLASS_D_LEGACY_16000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xd4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xd4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HEADSET_STEREO_RX_CLASS_D_LEGACY_22050_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xbb)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xd2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xd2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HEADSET_STEREO_RX_CLASS_D_LEGACY_32000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xf4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xf4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_RX_CLASS_D_LEGACY_48000_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xc5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xc2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_RX_CLASS_D_LEGACY_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xBB)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define SPEAKER_RX_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x37, 0xe2, 0xa2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x8a, 0x8a)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define SPEAKER_RX_48000_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x42)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x67)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x37, 0xe2, 0xa2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x8a, 0x8a)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define SPEAKER_RX_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x37, 0xe2, 0xa2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x8a, 0x8a)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define SPEAKER_STEREO_RX_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x37, 0xe6, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x8a, 0x8a)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x0f, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define SPEAKER_STEREO_RX_48000_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x42)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x67)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x37, 0xe6, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x8a, 0x8a)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x0f, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define SPEAKER_STEREO_RX_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x37, 0xe6, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x8a, 0x8a)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x0f, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define SPEAKER_TX_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x5E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define SPEAKER_TX_48000_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x46)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define SPEAKER_TX_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x5A)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define FM_HANDSET_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x47)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0x8C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x92)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x2710}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define FM_HEADSET_STEREO_CLASS_D_LEGACY_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x92)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define FM_HEADSET_CLASS_AB_STEREO_LEGACY_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x92)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x67)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define FM_HEADSET_CLASS_AB_STEREO_CAPLESS_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x92)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x67)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x82)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xeb)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define FM_SPEAKER_OSR_64 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x92)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x67)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x37, 0xe2, 0xa2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x8a, 0x8a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x2710}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define AUXPGA_HEADSET_STEREO_RX_LEGACY \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0x18, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define AUXPGA_HEADSET_MONO_RX_LEGACY \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0x18, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define AUXPGA_HEADSET_STEREO_RX_CAPLESS \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xeb)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0x18, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define AUXPGA_HEADSET_MONO_RX_CAPLESS \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xeb)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0x18, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define AUXPGA_HEADSET_STEREO_RX_CLASS_D \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x20, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFC, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x20, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define AUXPGA_HEADSET_MONO_RX_CLASS_D \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xDD)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFC, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x20, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define AUXPGA_EAR \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2B, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2C, 0xff, 0x89)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0x20, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x2710}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0x20, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +/***************************************************************************\ + DigitalMicprofile +\***************************************************************************/ +#define DIGITAL_MIC \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xfd, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x1A, 0xff, 0xc0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x66)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x1c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/***************************************************************************\ + DualMicprofile +\***************************************************************************/ +#define SPEAKER_MIC1_LEFT_LINE_IN_RIGHT_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xfd, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x5E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xE2, 0xE2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x1c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xc0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0e, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0d, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define SPEAKER_MIC1_LEFT_AUX_IN_RIGHT_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xfd, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x5E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xE2, 0xE1)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x1c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xc0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0e, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0d, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define MIC1_LEFT_LINE_IN_RIGHT_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xfd, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x5E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xE2, 0xE2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x1c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xCE)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xc0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0e, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0d, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define MIC1_LEFT_AUX_IN_RIGHT_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xfd, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x5E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xE1, 0xE1)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x1c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xCE)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xc0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0e, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0d, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +/***************************************************************************\ + AnalogDualMicProfile +\***************************************************************************/ +#define ANALOG_DUAL_MIC \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xfd, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x5E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xE2, 0xE2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xD0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x1c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0e, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0d, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x1c, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } +/***************************************************************************\ + TTY +\***************************************************************************/ +#define TTY_HEADSET_MONO_TX_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xfc, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x5E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFf, 0xA8)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x0A)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define TTY_HEADSET_MONO_TX_16000_OSR_256 TTY_HEADSET_MONO_TX_8000_OSR_256 + +#define TTY_HEADSET_MONO_RX_CLASS_D_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xFF)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xc4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xc4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0xF4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define TTY_HEADSET_MONO_RX_CLASS_D_16000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xFF)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xd4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xd4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0xF4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define TTY_HEADSET_MONO_RX_CLASS_D_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xf8, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xfF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xBB)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0f)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xFF, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xff)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4a, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0a, 0x0a)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3E8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0xF4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0x0f, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/***************************************************************************\ + FFA +\***************************************************************************/ +#define HANDSET_RX_8000_OSR_256_FFA \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x44)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0x8C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x21, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x2710}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HANDSET_RX_16000_OSR_256_FFA HANDSET_RX_8000_OSR_256_FFA + +#define HANDSET_RX_48000_OSR_64_FFA \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x47)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xfF, 0x8C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x42)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0xD5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x21, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x2710}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HANDSET_RX_48000_OSR_256_FFA \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x44)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xfF, 0x8C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3d, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x21, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x2710}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x05, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x36, 0xc0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HANDSET_TX_8000_OSR_256_FFA \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x5E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xd0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xCE)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HANDSET_TX_16000_OSR_256_FFA HANDSET_TX_8000_OSR_256_FFA + +#define HANDSET_TX_48000_OSR_256_FFA \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x5A)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x10, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xd0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xCE)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_SPEAKER_STEREO_RX_CAPLESS_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xac)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0x82)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x37, 0xe6, 0xa0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0x2b)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x23, 0x23)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x8A, 0x8A)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xa0)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x98)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x88)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x78)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x68)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x58)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x38)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x28)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xaC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0xaC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x7530}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } +#endif /* __MARIMBA_PROFILE_H__ */ + + + + diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/mi2s.h b/arch/arm/mach-msm/include/mach/qdsp5v2/mi2s.h new file mode 100644 index 00000000000..e304e254133 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/mi2s.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _MACH_QDSP5_V2_MI2S_H +#define _MACH_QDSP5_V2_MI2S_H + +#define WT_16_BIT 0 +#define WT_24_BIT 1 +#define WT_32_BIT 2 +#define WT_MAX 4 + +enum mi2s_ret_enum_type { + MI2S_FALSE = 0, + MI2S_TRUE +}; + +#define MI2S_CHAN_MONO_RAW 0 +#define MI2S_CHAN_MONO_PACKED 1 +#define MI2S_CHAN_STEREO 2 +#define MI2S_CHAN_4CHANNELS 3 +#define MI2S_CHAN_6CHANNELS 4 +#define MI2S_CHAN_8CHANNELS 5 +#define MI2S_CHAN_MAX_OUTBOUND_CHANNELS MI2S__CHAN_8CHANNELS + +#define MI2S_SD_0 0x01 +#define MI2S_SD_1 0x02 +#define MI2S_SD_2 0x04 +#define MI2S_SD_3 0x08 + +#define MI2S_SD_LINE_MASK (MI2S_SD_0 | MI2S_SD_1 | MI2S_SD_2 | MI2S_SD_3) + +bool mi2s_set_hdmi_output_path(uint8_t channels, uint8_t size, + uint8_t sd_line); + +bool mi2s_set_hdmi_input_path(uint8_t channels, uint8_t size, uint8_t sd_line); + +bool mi2s_set_codec_output_path(uint8_t channels, uint8_t size); + +bool mi2s_set_codec_input_path(uint8_t channels, uint8_t size); + +#endif /* #ifndef MI2S_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/mp3_funcs.h b/arch/arm/mach-msm/include/mach/qdsp5v2/mp3_funcs.h new file mode 100644 index 00000000000..ac06d3b2839 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/mp3_funcs.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef MP3_FUNCS_H +#define MP3_FUNCS_H + +/* Function Prototypes */ +long mp3_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +void audpp_cmd_cfg_mp3_params(struct audio *audio); + +#endif /* !MP3_FUNCS_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/msm_lpa.h b/arch/arm/mach-msm/include/mach/qdsp5v2/msm_lpa.h new file mode 100644 index 00000000000..0dced94bc54 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/msm_lpa.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _MACH_QDSP5_V2_MSM_LPA_H +#define _MACH_QDSP5_V2_MSM_LPA_H + +struct lpa_mem_config { + u32 llb_min_addr; + u32 llb_max_addr; + u32 sb_min_addr; + u32 sb_max_addr; +}; + +struct msm_lpa_platform_data { + u32 obuf_hlb_size; + u32 dsp_proc_id; + u32 app_proc_id; + struct lpa_mem_config nosb_config; /* no summing */ + struct lpa_mem_config sb_config; /* summing required */ +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/pcm_funcs.h b/arch/arm/mach-msm/include/mach/qdsp5v2/pcm_funcs.h new file mode 100644 index 00000000000..fa650005d1d --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/pcm_funcs.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef PCM_FUNCS_H +#define PCM_FUNCS_H + +long pcm_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +void audpp_cmd_cfg_pcm_params(struct audio *audio); + +#endif /* !PCM_FUNCS_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5afecmdi.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5afecmdi.h new file mode 100644 index 00000000000..25fe3a01c07 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5afecmdi.h @@ -0,0 +1,125 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_QDSP5AFECMDI_H +#define __MACH_QDSP5_V2_QDSP5AFECMDI_H + +#define QDSP5_DEVICE_mI2S_CODEC_RX 1 /* internal codec rx path */ +#define QDSP5_DEVICE_mI2S_CODEC_TX 2 /* internal codec tx path */ +#define QDSP5_DEVICE_AUX_CODEC_RX 3 /* external codec rx path */ +#define QDSP5_DEVICE_AUX_CODEC_TX 4 /* external codec tx path */ +#define QDSP5_DEVICE_mI2S_HDMI_RX 5 /* HDMI/FM block rx path */ +#define QDSP5_DEVICE_mI2S_HDMI_TX 6 /* HDMI/FM block tx path */ +#define QDSP5_DEVICE_ID_MAX 7 + +#define AFE_CMD_CODEC_CONFIG_CMD 0x1 +#define AFE_CMD_CODEC_CONFIG_LEN sizeof(struct afe_cmd_codec_config) + +struct afe_cmd_codec_config{ + uint16_t cmd_id; + uint16_t device_id; + uint16_t activity; + uint16_t sample_rate; + uint16_t channel_mode; + uint16_t volume; + uint16_t reserved; +} __attribute__ ((packed)); + +#define AFE_CMD_DEVICE_VOLUME_CTRL 0x2 +#define AFE_CMD_DEVICE_VOLUME_CTRL_LEN \ + sizeof(struct afe_cmd_device_volume_ctrl) + +struct afe_cmd_device_volume_ctrl { + uint16_t cmd_id; + uint16_t device_id; + uint16_t device_volume; + uint16_t reserved; +} __attribute__ ((packed)); + +#define AFE_CMD_AUX_CODEC_CONFIG_CMD 0x3 +#define AFE_CMD_AUX_CODEC_CONFIG_LEN sizeof(struct afe_cmd_aux_codec_config) + +struct afe_cmd_aux_codec_config{ + uint16_t cmd_id; + uint16_t dma_path_ctl; + uint16_t pcm_ctl; + uint16_t eight_khz_int_mode; + uint16_t aux_codec_intf_ctl; + uint16_t data_format_padding_info; +} __attribute__ ((packed)); + +#define AFE_CMD_FM_RX_ROUTING_CMD 0x6 +#define AFE_CMD_FM_RX_ROUTING_LEN sizeof(struct afe_cmd_fm_codec_config) + +struct afe_cmd_fm_codec_config{ + uint16_t cmd_id; + uint16_t enable; + uint16_t device_id; +} __attribute__ ((packed)); + +#define AFE_CMD_FM_PLAYBACK_VOLUME_CMD 0x8 +#define AFE_CMD_FM_PLAYBACK_VOLUME_LEN sizeof(struct afe_cmd_fm_volume_config) + +struct afe_cmd_fm_volume_config{ + uint16_t cmd_id; + uint16_t volume; + uint16_t reserved; +} __attribute__ ((packed)); + +#define AFE_CMD_FM_CALIBRATION_GAIN_CMD 0x11 +#define AFE_CMD_FM_CALIBRATION_GAIN_LEN \ + sizeof(struct afe_cmd_fm_calibgain_config) + +struct afe_cmd_fm_calibgain_config{ + uint16_t cmd_id; + uint16_t device_id; + uint16_t calibration_gain; +} __attribute__ ((packed)); + +#define AFE_CMD_LOOPBACK 0xD +#define AFE_CMD_EXT_LOOPBACK 0xE +#define AFE_CMD_LOOPBACK_LEN sizeof(struct afe_cmd_loopback) +#define AFE_LOOPBACK_ENABLE_COMMAND 0xFFFF +#define AFE_LOOPBACK_DISABLE_COMMAND 0x0000 + +struct afe_cmd_loopback { + uint16_t cmd_id; + uint16_t enable_flag; + uint16_t reserved[2]; +} __attribute__ ((packed)); + +struct afe_cmd_ext_loopback { + uint16_t cmd_id; + uint16_t enable_flag; + uint16_t source_id; + uint16_t dst_id; + uint16_t reserved[2]; +} __packed; + +#define AFE_CMD_CFG_RMC_PARAMS 0x12 +#define AFE_CMD_CFG_RMC_LEN \ + sizeof(struct afe_cmd_cfg_rmc) + +struct afe_cmd_cfg_rmc { + unsigned short cmd_id; + signed short rmc_mode; + unsigned short rmc_ipw_length_ms; + unsigned short rmc_peak_length_ms; + unsigned short rmc_init_pulse_length_ms; + unsigned short rmc_total_int_length_ms; + unsigned short rmc_rampupdn_length_ms; + unsigned short rmc_delay_length_ms; + unsigned short rmc_detect_start_threshdb; + signed short rmc_init_pulse_threshdb; +} __attribute__((packed)); + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5afemsg.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5afemsg.h new file mode 100644 index 00000000000..16134e3f60e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5afemsg.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_QDSP5AFEMSG_H +#define __MACH_QDSP5_V2_QDSP5AFEMSG_H + +#define AFE_APU_MSG_CODEC_CONFIG_ACK 0x0001 +#define AFE_APU_MSG_CODEC_CONFIG_ACK_LEN \ + sizeof(struct afe_msg_codec_config_ack) + +#define AFE_APU_MSG_VOC_TIMING_SUCCESS 0x0002 + +#define AFE_MSG_CODEC_CONFIG_ENABLED 0x1 +#define AFE_MSG_CODEC_CONFIG_DISABLED 0xFFFF + +struct afe_msg_codec_config_ack { + uint16_t device_id; + uint16_t device_activity; + uint16_t reserved; +} __attribute__((packed)); + +#endif /* QDSP5AFEMSG_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audplaycmdi.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audplaycmdi.h new file mode 100644 index 00000000000..53128d37154 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audplaycmdi.h @@ -0,0 +1,145 @@ +#ifndef QDSP5AUDPLAYCMDI_H +#define QDSP5AUDPLAYCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + Q D S P 5 A U D I O P L A Y T A S K C O M M A N D S + +GENERAL DESCRIPTION + Command Interface for AUDPLAYTASK on QDSP5 + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + + audplay_cmd_dec_data_avail + Send buffer to AUDPLAY task + + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ + +#define AUDPLAY_CMD_BITSTREAM_DATA_AVAIL 0x0000 +#define AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_LEN \ + sizeof(struct audplay_cmd_bitstream_data_avail) + +/* Type specification of dec_data_avail message sent to AUDPLAYTASK +*/ +struct audplay_cmd_bitstream_data_avail{ + /*command ID*/ + unsigned int cmd_id; + + /* Decoder ID for which message is being sent */ + unsigned int decoder_id; + + /* Start address of data in ARM global memory */ + unsigned int buf_ptr; + + /* Number of 16-bit words of bit-stream data contiguously + * available at the above-mentioned address + */ + unsigned int buf_size; + + /* Partition number used by audPlayTask to communicate with DSP's RTOS + * kernel + */ + unsigned int partition_number; + +} __attribute__((packed)); + +#define AUDPLAY_CMD_CHANNEL_INFO 0x0001 +#define AUDPLAY_CMD_CHANNEL_INFO_LEN \ + sizeof(struct audplay_cmd_channel_info) + +struct audplay_cmd_channel_select { + unsigned int cmd_id; + unsigned int stream_id; + unsigned int channel_select; +} __attribute__((packed)); + +struct audplay_cmd_threshold_update { + unsigned int cmd_id; + unsigned int threshold_update; + unsigned int threshold_value; +} __attribute__((packed)); + +union audplay_cmd_channel_info { + struct audplay_cmd_channel_select ch_select; + struct audplay_cmd_threshold_update thr_update; +}; + +#define AUDPLAY_CMD_HPCM_BUF_CFG 0x0003 +#define AUDPLAY_CMD_HPCM_BUF_CFG_LEN \ + sizeof(struct audplay_cmd_hpcm_buf_cfg) + +struct audplay_cmd_hpcm_buf_cfg { + unsigned int cmd_id; + unsigned int hostpcm_config; + unsigned int feedback_frequency; + unsigned int byte_swap; + unsigned int max_buffers; + unsigned int partition_number; +} __attribute__((packed)); + +#define AUDPLAY_CMD_BUFFER_REFRESH 0x0004 +#define AUDPLAY_CMD_BUFFER_REFRESH_LEN \ + sizeof(struct audplay_cmd_buffer_update) + +struct audplay_cmd_buffer_refresh { + unsigned int cmd_id; + unsigned int num_buffers; + unsigned int buf_read_count; + unsigned int buf0_address; + unsigned int buf0_length; + unsigned int buf1_address; + unsigned int buf1_length; +} __attribute__((packed)); + +#define AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2 0x0005 +#define AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2_LEN \ + sizeof(struct audplay_cmd_bitstream_data_avail_nt2) + +/* Type specification of dec_data_avail message sent to AUDPLAYTASK + * for NT2 */ +struct audplay_cmd_bitstream_data_avail_nt2 { + /*command ID*/ + unsigned int cmd_id; + + /* Decoder ID for which message is being sent */ + unsigned int decoder_id; + + /* Start address of data in ARM global memory */ + unsigned int buf_ptr; + + /* Number of 16-bit words of bit-stream data contiguously + * available at the above-mentioned address + */ + unsigned int buf_size; + + /* Partition number used by audPlayTask to communicate with DSP's RTOS + * kernel + */ + unsigned int partition_number; + + /* bitstream write pointer */ + unsigned int dspBitstreamWritePtr; + +} __attribute__((packed)); + +#define AUDPLAY_CMD_OUTPORT_FLUSH 0x0006 + +struct audplay_cmd_outport_flush { + unsigned int cmd_id; +} __attribute__((packed)); + +#endif /* QDSP5AUDPLAYCMD_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audplaymsg.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audplaymsg.h new file mode 100644 index 00000000000..2eeb5576ab9 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audplaymsg.h @@ -0,0 +1,74 @@ +#ifndef QDSP5AUDPLAYMSG_H +#define QDSP5AUDPLAYMSG_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + Q D S P 5 A U D I O P L A Y T A S K M S G + +GENERAL DESCRIPTION + Message sent by AUDPLAY task + +REFERENCES + None + + +Copyright (c) 1992-2009, Code Aurora Forum. 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. +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ + +#define AUDPLAY_MSG_DEC_NEEDS_DATA 0x0001 +#define AUDPLAY_MSG_DEC_NEEDS_DATA_MSG_LEN \ + sizeof(audplay_msg_dec_needs_data) + +struct audplay_msg_dec_needs_data { + /* reserved*/ + unsigned int dec_id; + + /*The read pointer offset of external memory till which bitstream + has been dmed in*/ + unsigned int adecDataReadPtrOffset; + + /*The buffer size of external memory. */ + unsigned int adecDataBufSize; + + unsigned int bitstream_free_len; + unsigned int bitstream_write_ptr; + unsigned int bitstarem_buf_start; + unsigned int bitstream_buf_len; +} __attribute__((packed)); + +#define AUDPLAY_UP_STREAM_INFO 0x0003 +#define AUDPLAY_UP_STREAM_INFO_LEN \ + sizeof(struct audplay_msg_stream_info) + +struct audplay_msg_stream_info { + unsigned int decoder_id; + unsigned int channel_info; + unsigned int sample_freq; + unsigned int bitstream_info; + unsigned int bit_rate; +} __attribute__((packed)); + +#define AUDPLAY_MSG_BUFFER_UPDATE 0x0004 +#define AUDPLAY_MSG_BUFFER_UPDATE_LEN \ + sizeof(struct audplay_msg_buffer_update) + +struct audplay_msg_buffer_update { + unsigned int buffer_write_count; + unsigned int num_of_buffer; + unsigned int buf0_address; + unsigned int buf0_length; + unsigned int buf1_address; + unsigned int buf1_length; +} __attribute__((packed)); + +#define AUDPLAY_UP_OUTPORT_FLUSH_ACK 0x0005 + +#endif /* QDSP5AUDPLAYMSG_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audppcmdi.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audppcmdi.h new file mode 100644 index 00000000000..0416f528633 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audppcmdi.h @@ -0,0 +1,1088 @@ +#ifndef __MACH_QDSP5_V2_QDSP5AUDPPCMDI_H +#define __MACH_QDSP5_V2_QDSP5AUDPPCMDI_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + A U D I O P O S T P R O C E S S I N G I N T E R N A L C O M M A N D S + +GENERAL DESCRIPTION + This file contains defintions of format blocks of commands + that are accepted by AUDPP Task + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright(c) 1992-2011, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ + +/* + * ARM to AUDPPTASK Commands + * + * ARM uses three command queues to communicate with AUDPPTASK + * 1)uPAudPPCmd1Queue : Used for more frequent and shorter length commands + * Location : MEMA + * Buffer Size : 6 words + * No of buffers in a queue : 20 for gaming audio and 5 for other images + * 2)uPAudPPCmd2Queue : Used for commands which are not much lengthier + * Location : MEMA + * Buffer Size : 23 + * No of buffers in a queue : 2 + * 3)uPAudOOCmd3Queue : Used for lengthier and more frequent commands + * Location : MEMA + * Buffer Size : 145 + * No of buffers in a queue : 3 + */ + +/* + * Commands Related to uPAudPPCmd1Queue + */ + +/* + * Command Structure to enable or disable the active decoders + */ + +#define AUDPP_CMD_CFG_DEC_TYPE 0x0001 +#define AUDPP_CMD_CFG_DEC_TYPE_LEN sizeof(struct audpp_cmd_cfg_dec_type) + +/* Enable the decoder */ +#define AUDPP_CMD_DEC_TYPE_M 0x000F + +#define AUDPP_CMD_ENA_DEC_V 0x4000 +#define AUDPP_CMD_DIS_DEC_V 0x0000 +#define AUDPP_CMD_DEC_STATE_M 0x4000 + +#define AUDPP_CMD_UPDATDE_CFG_DEC 0x8000 +#define AUDPP_CMD_DONT_UPDATE_CFG_DEC 0x0000 + + +/* Type specification of cmd_cfg_dec */ + +struct audpp_cmd_cfg_dec_type { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short dec_cfg; + unsigned short dm_mode; +} __attribute__((packed)); + +/* + * Command Structure to Pause , Resume and flushes the selected audio decoders + */ + +#define AUDPP_CMD_DEC_CTRL 0x0002 +#define AUDPP_CMD_DEC_CTRL_LEN sizeof(struct audpp_cmd_dec_ctrl) + +/* Decoder control commands for pause, resume and flush */ +#define AUDPP_CMD_FLUSH_V 0x2000 + +#define AUDPP_CMD_PAUSE_V 0x4000 +#define AUDPP_CMD_RESUME_V 0x0000 + +#define AUDPP_CMD_UPDATE_V 0x8000 +#define AUDPP_CMD_IGNORE_V 0x0000 + + +/* Type Spec for decoder control command*/ + +struct audpp_cmd_dec_ctrl{ + unsigned short cmd_id; + unsigned short stream_id; + unsigned short dec_ctrl; +} __attribute__((packed)); + +/* + * Command Structure to Configure the AVSync FeedBack Mechanism + */ + +#define AUDPP_CMD_AVSYNC 0x0003 +#define AUDPP_CMD_AVSYNC_LEN sizeof(struct audpp_cmd_avsync) + +struct audpp_cmd_avsync{ + unsigned short cmd_id; + unsigned short stream_id; + unsigned short interrupt_interval; + unsigned short sample_counter_dlsw; + unsigned short sample_counter_dmsw; + unsigned short sample_counter_msw; + unsigned short byte_counter_dlsw; + unsigned short byte_counter_dmsw; + unsigned short byte_counter_msw; +} __attribute__((packed)); + +/* + * Macros used to store the AV Sync Info from DSP + */ + +#define AUDPP_AVSYNC_CH_COUNT 1 +#define AUDPP_AVSYNC_NUM_WORDS 6 +/* Timeout of 3000ms for AV Sync Query response */ +#define AUDPP_AVSYNC_EVENT_TIMEOUT 3000 + +/* + * Command Structure to Query AVSync Info from DSP + */ + +#define AUDPP_CMD_QUERY_AVSYNC 0x0006 + +struct audpp_cmd_query_avsync{ + unsigned short cmd_id; + unsigned short stream_id; +} __attribute__((packed)); + +/* + * Command Structure to enable or disable(sleep) the AUDPPTASK + */ + +#define AUDPP_CMD_CFG 0x0004 +#define AUDPP_CMD_CFG_LEN sizeof(struct audpp_cmd_cfg) + +#define AUDPP_CMD_CFG_SLEEP 0x0000 +#define AUDPP_CMD_CFG_ENABLE 0xFFFF + +struct audpp_cmd_cfg { + unsigned short cmd_id; + unsigned short cfg; +} __attribute__((packed)); + +/* + * Command Structure to Inject or drop the specified no of samples + */ + +#define AUDPP_CMD_ADJUST_SAMP 0x0005 +#define AUDPP_CMD_ADJUST_SAMP_LEN sizeof(struct audpp_cmd_adjust_samp) + +#define AUDPP_CMD_SAMP_DROP -1 +#define AUDPP_CMD_SAMP_INSERT 0x0001 + +#define AUDPP_CMD_NUM_SAMPLES 0x0001 + +struct audpp_cmd_adjust_samp { + unsigned short cmd_id; + unsigned short object_no; + signed short sample_insert_or_drop; + unsigned short num_samples; +} __attribute__((packed)); + +/* + * Command Structure to Configure AVSync Feedback Mechanism + */ + +#define AUDPP_CMD_ROUTING_MODE 0x0007 +#define AUDPP_CMD_ROUTING_MODE_LEN \ +sizeof(struct audpp_cmd_routing_mode) + +struct audpp_cmd_routing_mode { + unsigned short cmd_id; + unsigned short object_number; + unsigned short routing_mode; +} __attribute__((packed)); + +/* + * Commands Related to uPAudPPCmd2Queue + */ + +/* + * Command Structure to configure Per decoder Parameters (Common) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS 0x0000 +#define AUDPP_CMD_CFG_ADEC_PARAMS_COMMON_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_common) + +#define AUDPP_CMD_STATUS_MSG_FLAG_ENA_FCM 0x4000 +#define AUDPP_CMD_STATUS_MSG_FLAG_DIS_FCM 0x0000 + +#define AUDPP_CMD_STATUS_MSG_FLAG_ENA_DCM 0x8000 +#define AUDPP_CMD_STATUS_MSG_FLAG_DIS_DCM 0x0000 + +/* Sampling frequency*/ +#define AUDPP_CMD_SAMP_RATE_96000 0x0000 +#define AUDPP_CMD_SAMP_RATE_88200 0x0001 +#define AUDPP_CMD_SAMP_RATE_64000 0x0002 +#define AUDPP_CMD_SAMP_RATE_48000 0x0003 +#define AUDPP_CMD_SAMP_RATE_44100 0x0004 +#define AUDPP_CMD_SAMP_RATE_32000 0x0005 +#define AUDPP_CMD_SAMP_RATE_24000 0x0006 +#define AUDPP_CMD_SAMP_RATE_22050 0x0007 +#define AUDPP_CMD_SAMP_RATE_16000 0x0008 +#define AUDPP_CMD_SAMP_RATE_12000 0x0009 +#define AUDPP_CMD_SAMP_RATE_11025 0x000A +#define AUDPP_CMD_SAMP_RATE_8000 0x000B + + +/* + * Type specification of cmd_adec_cfg sent to all decoder + */ + +struct audpp_cmd_cfg_adec_params_common { + unsigned short cmd_id; + unsigned short dec_id; + unsigned short length; + unsigned short reserved; + unsigned short input_sampling_frequency; +} __attribute__((packed)); + +/* + * Command Structure to configure Per decoder Parameters (Wav) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_WAV_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_wav) + + +#define AUDPP_CMD_WAV_STEREO_CFG_MONO 0x0001 +#define AUDPP_CMD_WAV_STEREO_CFG_STEREO 0x0002 + +#define AUDPP_CMD_WAV_PCM_WIDTH_8 0x0000 +#define AUDPP_CMD_WAV_PCM_WIDTH_16 0x0001 +#define AUDPP_CMD_WAV_PCM_WIDTH_24 0x0002 + +struct audpp_cmd_cfg_adec_params_wav { + struct audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; + unsigned short pcm_width; + unsigned short sign; +} __attribute__((packed)); + +/* + * Command Structure for CMD_CFG_DEV_MIXER + */ + +#define AUDPP_CMD_CFG_DEV_MIXER_PARAMS_LEN \ + sizeof(struct audpp_cmd_cfg_dev_mixer_params) + +#define AUDPP_CMD_CFG_DEV_MIXER 0x0008 + +#define AUDPP_CMD_CFG_DEV_MIXER_ID_0 0 +#define AUDPP_CMD_CFG_DEV_MIXER_ID_1 1 +#define AUDPP_CMD_CFG_DEV_MIXER_ID_2 2 +#define AUDPP_CMD_CFG_DEV_MIXER_ID_3 3 +#define AUDPP_CMD_CFG_DEV_MIXER_ID_4 4 +#define AUDPP_CMD_CFG_DEV_MIXER_ID_5 5 + +#define AUDPP_CMD_CFG_DEV_MIXER_DEV_NONE 0x0000 +#define AUDPP_CMD_CFG_DEV_MIXER_DEV_0 \ + (0x1 << AUDPP_CMD_CFG_DEV_MIXER_ID_0) +#define AUDPP_CMD_CFG_DEV_MIXER_DEV_1 \ + (0x1 << AUDPP_CMD_CFG_DEV_MIXER_ID_1) +#define AUDPP_CMD_CFG_DEV_MIXER_DEV_2 \ + (0x1 << AUDPP_CMD_CFG_DEV_MIXER_ID_2) +#define AUDPP_CMD_CFG_DEV_MIXER_DEV_3 \ + (0x1 << AUDPP_CMD_CFG_DEV_MIXER_ID_3) +#define AUDPP_CMD_CFG_DEV_MIXER_DEV_4 \ + (0x1 << AUDPP_CMD_CFG_DEV_MIXER_ID_4) +#define AUDPP_CMD_CFG_DEV_MIXER_DEV_5 \ + (0x1 << AUDPP_CMD_CFG_DEV_MIXER_ID_5) + +struct audpp_cmd_cfg_dev_mixer_params { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short mixer_cmd; +} __attribute__((packed)); + + +/* + * Command Structure to configure Per decoder Parameters (ADPCM) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_ADPCM_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_adpcm) + + +#define AUDPP_CMD_ADPCM_STEREO_CFG_MONO 0x0001 +#define AUDPP_CMD_ADPCM_STEREO_CFG_STEREO 0x0002 + +struct audpp_cmd_cfg_adec_params_adpcm { + struct audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; + unsigned short block_size; +} __attribute__((packed)); + +/* + * Command Structure to configure Per decoder Parameters (WMA) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_WMA_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_wma) + +struct audpp_cmd_cfg_adec_params_wma { + struct audpp_cmd_cfg_adec_params_common common; + unsigned short armdatareqthr; + unsigned short channelsdecoded; + unsigned short wmabytespersec; + unsigned short wmasamplingfreq; + unsigned short wmaencoderopts; +} __attribute__((packed)); + + +/* + * Command Structure to configure Per decoder Parameters (MP3) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_MP3_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_mp3) + +struct audpp_cmd_cfg_adec_params_mp3 { + struct audpp_cmd_cfg_adec_params_common common; +} __attribute__((packed)); + + +/* + * Command Structure to configure Per decoder Parameters (AAC) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_AAC_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_aac) + + +#define AUDPP_CMD_AAC_FORMAT_ADTS -1 +#define AUDPP_CMD_AAC_FORMAT_RAW 0x0000 +#define AUDPP_CMD_AAC_FORMAT_PSUEDO_RAW 0x0001 +#define AUDPP_CMD_AAC_FORMAT_LOAS 0x0002 + +#define AUDPP_CMD_AAC_AUDIO_OBJECT_LC 0x0002 +#define AUDPP_CMD_AAC_AUDIO_OBJECT_LTP 0x0004 +#define AUDPP_CMD_AAC_AUDIO_OBJECT_ERLC 0x0011 + +#define AUDPP_CMD_AAC_SBR_ON_FLAG_ON 0x0001 +#define AUDPP_CMD_AAC_SBR_ON_FLAG_OFF 0x0000 + +#define AUDPP_CMD_AAC_SBR_PS_ON_FLAG_ON 0x0001 +#define AUDPP_CMD_AAC_SBR_PS_ON_FLAG_OFF 0x0000 + +struct audpp_cmd_cfg_adec_params_aac { + struct audpp_cmd_cfg_adec_params_common common; + signed short format; + unsigned short audio_object; + unsigned short ep_config; + unsigned short aac_section_data_resilience_flag; + unsigned short aac_scalefactor_data_resilience_flag; + unsigned short aac_spectral_data_resilience_flag; + unsigned short sbr_on_flag; + unsigned short sbr_ps_on_flag; + unsigned short channel_configuration; +} __attribute__((packed)); + +/* + * Command Structure to configure Per decoder Parameters (V13K) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_V13K_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_v13k) + + +#define AUDPP_CMD_STEREO_CFG_MONO 0x0001 +#define AUDPP_CMD_STEREO_CFG_STEREO 0x0002 + +struct audpp_cmd_cfg_adec_params_v13k { + struct audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; +} __attribute__((packed)); + +#define AUDPP_CMD_CFG_ADEC_PARAMS_EVRC_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_evrc) + +struct audpp_cmd_cfg_adec_params_evrc { + struct audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; +} __attribute__ ((packed)); + +/* + * Command Structure to configure Per decoder Parameters (AMRWB) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_AMRWB_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_amrwb) + +struct audpp_cmd_cfg_adec_params_amrwb { + struct audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; +} __attribute__((packed)); + +/* + * Command Structure to configure Per decoder Parameters (WMAPRO) + */ + +#define AUDPP_CMD_CFG_ADEC_PARAMS_WMAPRO_LEN \ + sizeof(struct audpp_cmd_cfg_adec_params_wmapro) + +struct audpp_cmd_cfg_adec_params_wmapro { + struct audpp_cmd_cfg_adec_params_common common; + unsigned short armdatareqthr; + uint8_t validbitspersample; + uint8_t numchannels; + unsigned short formattag; + unsigned short samplingrate; + unsigned short avgbytespersecond; + unsigned short asfpacketlength; + unsigned short channelmask; + unsigned short encodeopt; + unsigned short advancedencodeopt; + uint32_t advancedencodeopt2; +} __attribute__((packed)); + +/* + * Command Structure to configure the HOST PCM interface + */ + +#define AUDPP_CMD_PCM_INTF 0x0001 +#define AUDPP_CMD_PCM_INTF_2 0x0002 +#define AUDPP_CMD_PCM_INTF_LEN sizeof(struct audpp_cmd_pcm_intf) + +#define AUDPP_CMD_PCM_INTF_MONO_V 0x0001 +#define AUDPP_CMD_PCM_INTF_STEREO_V 0x0002 + +/* These two values differentiate the two types of commands that could be issued + * Interface configuration command and Buffer update command */ + +#define AUDPP_CMD_PCM_INTF_CONFIG_CMD_V 0x0000 +#define AUDPP_CMD_PCM_INTF_BUFFER_CMD_V -1 + +#define AUDPP_CMD_PCM_INTF_RX_ENA_M 0x000F +#define AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V 0x0008 +#define AUDPP_CMD_PCM_INTF_RX_ENA_DSPTOARM_V 0x0004 + +/* These flags control the enabling and disabling of the interface together + * with host interface bit mask. */ + +#define AUDPP_CMD_PCM_INTF_ENA_V -1 +#define AUDPP_CMD_PCM_INTF_DIS_V 0x0000 + + +#define AUDPP_CMD_PCM_INTF_FULL_DUPLEX 0x0 +#define AUDPP_CMD_PCM_INTF_HALF_DUPLEX_TODSP 0x1 + + +#define AUDPP_CMD_PCM_INTF_OBJECT_NUM 0x5 +#define AUDPP_CMD_PCM_INTF_COMMON_OBJECT_NUM 0x6 + +struct audpp_cmd_pcm_intf { + unsigned short cmd_id; + unsigned short stream; + unsigned short stream_id; + signed short config; + unsigned short intf_type; + + /* DSP -> ARM Configuration */ + unsigned short read_buf1LSW; + unsigned short read_buf1MSW; + unsigned short read_buf1_len; + + unsigned short read_buf2LSW; + unsigned short read_buf2MSW; + unsigned short read_buf2_len; + /* 0:HOST_PCM_INTF disable + ** 0xFFFF: HOST_PCM_INTF enable + */ + signed short dsp_to_arm_flag; + unsigned short partition_number; + + /* ARM -> DSP Configuration */ + unsigned short write_buf1LSW; + unsigned short write_buf1MSW; + unsigned short write_buf1_len; + + unsigned short write_buf2LSW; + unsigned short write_buf2MSW; + unsigned short write_buf2_len; + + /* 0:HOST_PCM_INTF disable + ** 0xFFFF: HOST_PCM_INTF enable + */ + signed short arm_to_rx_flag; + unsigned short weight_decoder_to_rx; + unsigned short weight_arm_to_rx; + + unsigned short partition_number_arm_to_dsp; + unsigned short sample_rate; + unsigned short channel_mode; +} __attribute__((packed)); + +/* + ** BUFFER UPDATE COMMAND + */ +#define AUDPP_CMD_PCM_INTF_SEND_BUF_PARAMS_LEN \ + sizeof(struct audpp_cmd_pcm_intf_send_buffer) + +struct audpp_cmd_pcm_intf_send_buffer { + unsigned short cmd_id; + unsigned short stream; + unsigned short stream_id; + /* set config = 0xFFFF for configuration*/ + signed short config; + unsigned short intf_type; + unsigned short dsp_to_arm_buf_id; + unsigned short arm_to_dsp_buf_id; + unsigned short arm_to_dsp_buf_len; +} __attribute__((packed)); + + +/* + * Commands Related to uPAudPPCmd3Queue + */ + +/* + * Command Structure to configure post processing params (Commmon) + */ + +#define AUDPP_CMD_CFG_OBJECT_PARAMS 0x0000 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN \ + sizeof(struct audpp_cmd_cfg_object_params_common) + +#define AUDPP_CMD_OBJ0_UPDATE 0x8000 +#define AUDPP_CMD_OBJ0_DONT_UPDATE 0x0000 + + +#define AUDPP_CMD_OBJ2_UPDATE 0x8000 +#define AUDPP_CMD_OBJ2_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_OBJ3_UPDATE 0x8000 +#define AUDPP_CMD_OBJ3_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_OBJ4_UPDATE 0x8000 +#define AUDPP_CMD_OBJ4_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_HPCM_UPDATE 0x8000 +#define AUDPP_CMD_HPCM_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_COMMON_CFG_UPDATE 0x8000 +#define AUDPP_CMD_COMMON_CFG_DONT_UPDATE 0x0000 + +#define AUDPP_CMD_POPP_STREAM 0xFFFF +#define AUDPP_CMD_COPP_STREAM 0x0000 + +struct audpp_cmd_cfg_object_params_common{ + unsigned short cmd_id; + unsigned short stream; + unsigned short stream_id; + unsigned short obj_cfg; + unsigned short command_type; +} __attribute__((packed)); + +/* + * Command Structure to configure post processing params (Volume) + */ +#define AUDPP_CMD_VOLUME_PAN 0 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_VOLUME_LEN \ + sizeof(struct audpp_cmd_cfg_object_params_volume) + +struct audpp_cmd_cfg_object_params_volume { + struct audpp_cmd_cfg_object_params_common common; + unsigned short volume; + unsigned short pan; +} __attribute__((packed)); + +/* + * Command Structure to configure post processing params (PCM Filter) + */ + +struct numerator { + unsigned short numerator_b0_filter_lsw; + unsigned short numerator_b0_filter_msw; + unsigned short numerator_b1_filter_lsw; + unsigned short numerator_b1_filter_msw; + unsigned short numerator_b2_filter_lsw; + unsigned short numerator_b2_filter_msw; +} __attribute__((packed)); + +struct denominator { + unsigned short denominator_a0_filter_lsw; + unsigned short denominator_a0_filter_msw; + unsigned short denominator_a1_filter_lsw; + unsigned short denominator_a1_filter_msw; +} __attribute__((packed)); + +struct shift_factor { + unsigned short shift_factor_0; +} __attribute__((packed)); + +struct pan { + unsigned short pan_filter_0; +} __attribute__((packed)); + +struct filter_1 { + struct numerator numerator_filter; + struct denominator denominator_filter; + struct shift_factor shift_factor_filter; + struct pan pan_filter; +} __attribute__((packed)); + +struct filter_2 { + struct numerator numerator_filter[2]; + struct denominator denominator_filter[2]; + struct shift_factor shift_factor_filter[2]; + struct pan pan_filter[2]; +} __attribute__((packed)); + +struct filter_3 { + struct numerator numerator_filter[3]; + struct denominator denominator_filter[3]; + struct shift_factor shift_factor_filter[3]; + struct pan pan_filter[3]; +} __attribute__((packed)); + +struct filter_4 { + struct numerator numerator_filter[4]; + struct denominator denominator_filter[4]; + struct shift_factor shift_factor_filter[4]; + struct pan pan_filter[4]; +} __attribute__((packed)); + +#define AUDPP_CMD_IIR_TUNING_FILTER 1 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_PCM_LEN \ + sizeof(struct audpp_cmd_cfg_object_params_pcm) + + +struct audpp_cmd_cfg_object_params_pcm { + struct audpp_cmd_cfg_object_params_common common; + signed short active_flag; + unsigned short num_bands; + union { + struct filter_1 filter_1_params; + struct filter_2 filter_2_params; + struct filter_3 filter_3_params; + struct filter_4 filter_4_params; + } __attribute__((packed)) params_filter; +} __attribute__((packed)); + +#define AUDPP_CMD_CALIB_GAIN_RX 15 +#define AUDPP_CMD_CFG_CAL_GAIN_LEN sizeof(struct audpp_cmd_cfg_cal_gain) + + +struct audpp_cmd_cfg_cal_gain { + struct audpp_cmd_cfg_object_params_common common; + unsigned short audppcalgain; + unsigned short reserved; +} __attribute__((packed)); + + +/* + * Command Structure to configure post processing parameters (equalizer) + */ +#define AUDPP_CMD_EQUALIZER 2 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_EQALIZER_LEN \ + sizeof(struct audpp_cmd_cfg_object_params_eqalizer) + +struct eq_numerator { + unsigned short numerator_coeff_0_lsw; + unsigned short numerator_coeff_0_msw; + unsigned short numerator_coeff_1_lsw; + unsigned short numerator_coeff_1_msw; + unsigned short numerator_coeff_2_lsw; + unsigned short numerator_coeff_2_msw; +} __attribute__((packed)); + +struct eq_denominator { + unsigned short denominator_coeff_0_lsw; + unsigned short denominator_coeff_0_msw; + unsigned short denominator_coeff_1_lsw; + unsigned short denominator_coeff_1_msw; +} __attribute__((packed)); + +struct eq_shiftfactor { + unsigned short shift_factor; +} __attribute__((packed)); + +struct eq_coeff_1 { + struct eq_numerator numerator; + struct eq_denominator denominator; + struct eq_shiftfactor shiftfactor; +} __attribute__((packed)); + +struct eq_coeff_2 { + struct eq_numerator numerator[2]; + struct eq_denominator denominator[2]; + struct eq_shiftfactor shiftfactor[2]; +} __attribute__((packed)); + +struct eq_coeff_3 { + struct eq_numerator numerator[3]; + struct eq_denominator denominator[3]; + struct eq_shiftfactor shiftfactor[3]; +} __attribute__((packed)); + +struct eq_coeff_4 { + struct eq_numerator numerator[4]; + struct eq_denominator denominator[4]; + struct eq_shiftfactor shiftfactor[4]; +} __attribute__((packed)); + +struct eq_coeff_5 { + struct eq_numerator numerator[5]; + struct eq_denominator denominator[5]; + struct eq_shiftfactor shiftfactor[5]; +} __attribute__((packed)); + +struct eq_coeff_6 { + struct eq_numerator numerator[6]; + struct eq_denominator denominator[6]; + struct eq_shiftfactor shiftfactor[6]; +} __attribute__((packed)); + +struct eq_coeff_7 { + struct eq_numerator numerator[7]; + struct eq_denominator denominator[7]; + struct eq_shiftfactor shiftfactor[7]; +} __attribute__((packed)); + +struct eq_coeff_8 { + struct eq_numerator numerator[8]; + struct eq_denominator denominator[8]; + struct eq_shiftfactor shiftfactor[8]; +} __attribute__((packed)); + +struct eq_coeff_9 { + struct eq_numerator numerator[9]; + struct eq_denominator denominator[9]; + struct eq_shiftfactor shiftfactor[9]; +} __attribute__((packed)); + +struct eq_coeff_10 { + struct eq_numerator numerator[10]; + struct eq_denominator denominator[10]; + struct eq_shiftfactor shiftfactor[10]; +} __attribute__((packed)); + +struct eq_coeff_11 { + struct eq_numerator numerator[11]; + struct eq_denominator denominator[11]; + struct eq_shiftfactor shiftfactor[11]; +} __attribute__((packed)); + +struct eq_coeff_12 { + struct eq_numerator numerator[12]; + struct eq_denominator denominator[12]; + struct eq_shiftfactor shiftfactor[12]; +} __attribute__((packed)); + + +struct audpp_cmd_cfg_object_params_eqalizer { + struct audpp_cmd_cfg_object_params_common common; + signed short eq_flag; + unsigned short num_bands; + union { + struct eq_coeff_1 eq_coeffs_1; + struct eq_coeff_2 eq_coeffs_2; + struct eq_coeff_3 eq_coeffs_3; + struct eq_coeff_4 eq_coeffs_4; + struct eq_coeff_5 eq_coeffs_5; + struct eq_coeff_6 eq_coeffs_6; + struct eq_coeff_7 eq_coeffs_7; + struct eq_coeff_8 eq_coeffs_8; + struct eq_coeff_9 eq_coeffs_9; + struct eq_coeff_10 eq_coeffs_10; + struct eq_coeff_11 eq_coeffs_11; + struct eq_coeff_12 eq_coeffs_12; + } __attribute__((packed)) eq_coeff; +} __attribute__((packed)); + +/* + * Command Structure to configure post processing parameters (ADRC) + */ +#define AUDPP_CMD_ADRC 3 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_ADRC_LEN \ + sizeof(struct audpp_cmd_cfg_object_params_adrc) + + +#define AUDPP_CMD_ADRC_FLAG_DIS 0x0000 +#define AUDPP_CMD_ADRC_FLAG_ENA -1 +#define AUDPP_CMD_PBE_FLAG_DIS 0x0000 +#define AUDPP_CMD_PBE_FLAG_ENA -1 + +struct audpp_cmd_cfg_object_params_adrc { + struct audpp_cmd_cfg_object_params_common common; + signed short adrc_flag; + unsigned short compression_th; + unsigned short compression_slope; + unsigned short rms_time; + unsigned short attack_const_lsw; + unsigned short attack_const_msw; + unsigned short release_const_lsw; + unsigned short release_const_msw; + unsigned short adrc_delay; +}; + +/* + * Command Structure to configure post processing parameters (MB - ADRC) + */ +#define AUDPP_CMD_MBADRC 10 +#define AUDPP_MAX_MBADRC_BANDS 5 + +struct adrc_config { + uint16_t subband_enable; + uint16_t adrc_sub_mute; + uint16_t rms_time; + uint16_t compression_th; + uint16_t compression_slope; + uint16_t attack_const_lsw; + uint16_t attack_const_msw; + uint16_t release_const_lsw; + uint16_t release_const_msw; + uint16_t makeup_gain; +}; + +struct audpp_cmd_cfg_object_params_mbadrc { + struct audpp_cmd_cfg_object_params_common common; + uint16_t enable; + uint16_t num_bands; + uint16_t down_samp_level; + uint16_t adrc_delay; + uint16_t ext_buf_size; + uint16_t ext_partition; + uint16_t ext_buf_msw; + uint16_t ext_buf_lsw; + struct adrc_config adrc_band[AUDPP_MAX_MBADRC_BANDS]; +} __attribute__((packed)); + +/* + * Command Structure to configure post processing parameters(Spectrum Analizer) + */ +#define AUDPP_CMD_SPECTROGRAM 4 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_SPECTRAM_LEN \ + sizeof(struct audpp_cmd_cfg_object_params_spectram) + + +struct audpp_cmd_cfg_object_params_spectram { + struct audpp_cmd_cfg_object_params_common common; + unsigned short sample_interval; + unsigned short num_coeff; +} __attribute__((packed)); + +/* + * Command Structure to configure post processing parameters (QConcert) + */ +#define AUDPP_CMD_QCONCERT 5 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_QCONCERT_LEN \ + sizeof(struct audpp_cmd_cfg_object_params_qconcert) + + +#define AUDPP_CMD_QCON_ENA_FLAG_ENA -1 +#define AUDPP_CMD_QCON_ENA_FLAG_DIS 0x0000 + +#define AUDPP_CMD_QCON_OP_MODE_HEADPHONE -1 +#define AUDPP_CMD_QCON_OP_MODE_SPEAKER_FRONT 0x0000 +#define AUDPP_CMD_QCON_OP_MODE_SPEAKER_SIDE 0x0001 +#define AUDPP_CMD_QCON_OP_MODE_SPEAKER_DESKTOP 0x0002 + +#define AUDPP_CMD_QCON_GAIN_UNIT 0x7FFF +#define AUDPP_CMD_QCON_GAIN_SIX_DB 0x4027 + + +#define AUDPP_CMD_QCON_EXPANSION_MAX 0x7FFF + + +struct audpp_cmd_cfg_object_params_qconcert { + struct audpp_cmd_cfg_object_params_common common; + signed short enable_flag; + signed short op_mode; + signed short gain; + signed short expansion; + signed short delay; + unsigned short stages_per_mode; + unsigned short reverb_enable; + unsigned short decay_msw; + unsigned short decay_lsw; + unsigned short decay_time_ratio_msw; + unsigned short decay_time_ratio_lsw; + unsigned short reflection_delay_time; + unsigned short late_reverb_gain; + unsigned short late_reverb_delay; + unsigned short delay_buff_size_msw; + unsigned short delay_buff_size_lsw; + unsigned short partition_num; + unsigned short delay_buff_start_msw; + unsigned short delay_buff_start_lsw; +} __attribute__((packed)); + +/* + * Command Structure to configure post processing parameters (Side Chain) + */ +#define AUDPP_CMD_SIDECHAIN_TUNING_FILTER 6 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_SIDECHAIN_LEN \ + sizeof(struct audpp_cmd_cfg_object_params_sidechain) + + +#define AUDPP_CMD_SIDECHAIN_ACTIVE_FLAG_DIS 0x0000 +#define AUDPP_CMD_SIDECHAIN_ACTIVE_FLAG_ENA -1 + +struct audpp_cmd_cfg_object_params_sidechain { + struct audpp_cmd_cfg_object_params_common common; + signed short active_flag; + unsigned short num_bands; + union { + struct filter_1 filter_1_params; + struct filter_2 filter_2_params; + struct filter_3 filter_3_params; + struct filter_4 filter_4_params; + } __attribute__((packed)) params_filter; +} __attribute__((packed)); + + +/* + * Command Structure to configure post processing parameters (QAFX) + */ +#define AUDPP_CMD_QAFX 8 +#define AUDPP_CMD_CFG_OBJECT_PARAMS_QAFX_LEN \ + sizeof(struct audpp_cmd_cfg_object_params_qafx) + +#define AUDPP_CMD_QAFX_ENA_DISA 0x0000 +#define AUDPP_CMD_QAFX_ENA_ENA_CFG -1 +#define AUDPP_CMD_QAFX_ENA_DIS_CFG 0x0001 + +#define AUDPP_CMD_QAFX_CMD_TYPE_ENV 0x0100 +#define AUDPP_CMD_QAFX_CMD_TYPE_OBJ 0x0010 +#define AUDPP_CMD_QAFX_CMD_TYPE_QUERY 0x1000 + +#define AUDPP_CMD_QAFX_CMDS_ENV_OP_MODE 0x0100 +#define AUDPP_CMD_QAFX_CMDS_ENV_LIS_POS 0x0101 +#define AUDPP_CMD_QAFX_CMDS_ENV_LIS_ORI 0x0102 +#define AUDPP_CMD_QAFX_CMDS_ENV_LIS_VEL 0X0103 +#define AUDPP_CMD_QAFX_CMDS_ENV_ENV_RES 0x0107 + +#define AUDPP_CMD_QAFX_CMDS_OBJ_SAMP_FREQ 0x0010 +#define AUDPP_CMD_QAFX_CMDS_OBJ_VOL 0x0011 +#define AUDPP_CMD_QAFX_CMDS_OBJ_DIST 0x0012 +#define AUDPP_CMD_QAFX_CMDS_OBJ_POS 0x0013 +#define AUDPP_CMD_QAFX_CMDS_OBJ_VEL 0x0014 + + +struct audpp_cmd_cfg_object_params_qafx { + struct audpp_cmd_cfg_object_params_common common; + signed short enable; + unsigned short command_type; + unsigned short num_commands; + unsigned short commands; +} __attribute__((packed)); + +/* + * Command Structure to enable , disable or configure the reverberation effect + * (REVERB) (Common) + */ + +#define AUDPP_CMD_REVERB_CONFIG 0x0001 +#define AUDPP_CMD_REVERB_CONFIG_COMMON_LEN \ + sizeof(struct audpp_cmd_reverb_config_common) + +#define AUDPP_CMD_ENA_ENA 0xFFFF +#define AUDPP_CMD_ENA_DIS 0x0000 +#define AUDPP_CMD_ENA_CFG 0x0001 + +#define AUDPP_CMD_CMD_TYPE_ENV 0x0104 +#define AUDPP_CMD_CMD_TYPE_OBJ 0x0015 +#define AUDPP_CMD_CMD_TYPE_QUERY 0x1000 + + +struct audpp_cmd_reverb_config_common { + unsigned short cmd_id; + unsigned short enable; + unsigned short cmd_type; +} __attribute__((packed)); + +/* + * Command Structure to enable , disable or configure the reverberation effect + * (ENV-0x0104) + */ + +#define AUDPP_CMD_REVERB_CONFIG_ENV_104_LEN \ + sizeof(struct audpp_cmd_reverb_config_env_104) + +struct audpp_cmd_reverb_config_env_104 { + struct audpp_cmd_reverb_config_common common; + unsigned short env_gain; + unsigned short decay_msw; + unsigned short decay_lsw; + unsigned short decay_timeratio_msw; + unsigned short decay_timeratio_lsw; + unsigned short delay_time; + unsigned short reverb_gain; + unsigned short reverb_delay; +} __attribute__((packed)); + +/* + * Command Structure to enable , disable or configure the reverberation effect + * (ENV-0x0015) + */ + +#define AUDPP_CMD_REVERB_CONFIG_ENV_15_LEN \ + sizeof(struct audpp_cmd_reverb_config_env_15) + +struct audpp_cmd_reverb_config_env_15 { + struct audpp_cmd_reverb_config_common common; + unsigned short object_num; + unsigned short absolute_gain; +} __attribute__((packed)); + +#define AUDPP_CMD_PBE 16 +#define AUDPP_CMD_CFG_PBE_LEN sizeof(struct audpp_cmd_cfg_pbe) + +struct audpp_cmd_cfg_pbe { + struct audpp_cmd_cfg_object_params_common common; + unsigned short pbe_enable; + signed short realbassmix; + signed short basscolorcontrol; + unsigned short mainchaindelay; + unsigned short xoverfltorder; + unsigned short bandpassfltorder; + signed short adrcdelay; + unsigned short downsamplelevel; + unsigned short comprmstav; + signed short expthreshold; + unsigned short expslope; + unsigned short compthreshold; + unsigned short compslope; + unsigned short cpmpattack_lsw; + unsigned short compattack_msw; + unsigned short comprelease_lsw; + unsigned short comprelease_msw; + unsigned short compmakeupgain; + signed short baselimthreshold; + signed short highlimthreshold; + signed short basslimmakeupgain; + signed short highlimmakeupgain; + signed short limbassgrc; + signed short limhighgrc; + signed short limdelay; + unsigned short filter_coeffs[90]; + unsigned short extbuffsize_lsw; + unsigned short extbuffsize_msw; + unsigned short extpartition; + unsigned short extbuffstart_lsw; + unsigned short extbuffstart_msw; +} __attribute__((packed)); + +#define AUDPP_CMD_PP_FEAT_QUERY_PARAMS 0x0002 + +struct audpp_cmd_cfg_object_params_volpan { + struct audpp_cmd_cfg_object_params_common common; + u16 volume ; + u16 pan; +}; + +struct rtc_audpp_read_data { + unsigned short cmd_id; + unsigned short obj_id; + unsigned short route_id; + unsigned short feature_id; + unsigned short extbufsizemsw; + unsigned short extbufsizelsw; + unsigned short extpart; + unsigned short extbufstartmsw; + unsigned short extbufstartlsw; +} __attribute__((packed)) ; + +#define AUDPP_CMD_SAMPLING_FREQUENCY 7 +#define AUDPP_CMD_QRUMBLE 9 + +#endif /* __MACH_QDSP5_V2_QDSP5AUDPPCMDI_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audppmsg.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audppmsg.h new file mode 100644 index 00000000000..b27bd8351eb --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audppmsg.h @@ -0,0 +1,311 @@ +#ifndef QDSP5AUDPPMSG_H +#define QDSP5AUDPPMSG_H + +/*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====* + + Q D S P 5 A U D I O P O S T P R O C E S S I N G M S G + +GENERAL DESCRIPTION + Messages sent by AUDPPTASK to ARM + +REFERENCES + None + +EXTERNALIZED FUNCTIONS + None + +Copyright (c) 1992-2009, Code Aurora Forum. 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. + +*====*====*====*====*====*====*====*====*====*====*====*====*====*====*====*/ + +/* + * AUDPPTASK uses audPPuPRlist to send messages to the ARM + * Location : MEMA + * Buffer Size : 45 + * No of Buffers in a queue : 5 for gaming audio and 1 for other images + */ + +/* + * MSG to Informs the ARM os Success/Failure of bringing up the decoder + */ + +#define AUDPP_MSG_FEAT_QUERY_DM_DONE 0x000b + +#define AUDPP_MSG_STATUS_MSG 0x0001 +#define AUDPP_MSG_STATUS_MSG_LEN \ + sizeof(struct audpp_msg_status_msg) + +#define AUDPP_MSG_STATUS_SLEEP 0x0000 +#define AUDPP_MSG_STATUS_INIT 0x0001 +#define AUDPP_MSG_STATUS_CFG 0x0002 +#define AUDPP_MSG_STATUS_PLAY 0x0003 + +#define AUDPP_MSG_REASON_NONE 0x0000 +#define AUDPP_MSG_REASON_MEM 0x0001 +#define AUDPP_MSG_REASON_NODECODER 0x0002 + +struct audpp_msg_status_msg { + unsigned short dec_id; + unsigned short status; + unsigned short reason; +} __attribute__((packed)); + +/* + * MSG to communicate the spectrum analyzer output bands to the ARM + */ +#define AUDPP_MSG_SPA_BANDS 0x0002 +#define AUDPP_MSG_SPA_BANDS_LEN \ + sizeof(struct audpp_msg_spa_bands) + +struct audpp_msg_spa_bands { + unsigned short current_object; + unsigned short spa_band_1; + unsigned short spa_band_2; + unsigned short spa_band_3; + unsigned short spa_band_4; + unsigned short spa_band_5; + unsigned short spa_band_6; + unsigned short spa_band_7; + unsigned short spa_band_8; + unsigned short spa_band_9; + unsigned short spa_band_10; + unsigned short spa_band_11; + unsigned short spa_band_12; + unsigned short spa_band_13; + unsigned short spa_band_14; + unsigned short spa_band_15; + unsigned short spa_band_16; + unsigned short spa_band_17; + unsigned short spa_band_18; + unsigned short spa_band_19; + unsigned short spa_band_20; + unsigned short spa_band_21; + unsigned short spa_band_22; + unsigned short spa_band_23; + unsigned short spa_band_24; + unsigned short spa_band_25; + unsigned short spa_band_26; + unsigned short spa_band_27; + unsigned short spa_band_28; + unsigned short spa_band_29; + unsigned short spa_band_30; + unsigned short spa_band_31; + unsigned short spa_band_32; +} __attribute__((packed)); + +/* + * MSG to communicate the PCM I/O buffer status to ARM + */ +#define AUDPP_MSG_HOST_PCM_INTF_MSG 0x0003 +#define AUDPP_MSG_HOST_PCM_INTF_MSG_LEN \ + sizeof(struct audpp_msg_host_pcm_intf_msg) + +#define AUDPP_MSG_HOSTPCM_ID_TX_ARM 0x0000 +#define AUDPP_MSG_HOSTPCM_ID_ARM_TX 0x0001 +#define AUDPP_MSG_HOSTPCM_ID_RX_ARM 0x0002 +#define AUDPP_MSG_HOSTPCM_ID_ARM_RX 0x0003 + +#define AUDPP_MSG_SAMP_FREQ_INDX_96000 0x0000 +#define AUDPP_MSG_SAMP_FREQ_INDX_88200 0x0001 +#define AUDPP_MSG_SAMP_FREQ_INDX_64000 0x0002 +#define AUDPP_MSG_SAMP_FREQ_INDX_48000 0x0003 +#define AUDPP_MSG_SAMP_FREQ_INDX_44100 0x0004 +#define AUDPP_MSG_SAMP_FREQ_INDX_32000 0x0005 +#define AUDPP_MSG_SAMP_FREQ_INDX_24000 0x0006 +#define AUDPP_MSG_SAMP_FREQ_INDX_22050 0x0007 +#define AUDPP_MSG_SAMP_FREQ_INDX_16000 0x0008 +#define AUDPP_MSG_SAMP_FREQ_INDX_12000 0x0009 +#define AUDPP_MSG_SAMP_FREQ_INDX_11025 0x000A +#define AUDPP_MSG_SAMP_FREQ_INDX_8000 0x000B + +#define AUDPP_MSG_CHANNEL_MODE_MONO 0x0001 +#define AUDPP_MSG_CHANNEL_MODE_STEREO 0x0002 + +struct audpp_msg_host_pcm_intf_msg { + unsigned short obj_num; + unsigned short numbers_of_samples; + unsigned short host_pcm_id; + unsigned short buf_indx; + unsigned short samp_freq_indx; + unsigned short channel_mode; +} __attribute__((packed)); + + +/* + * MSG to communicate 3D position of the source and listener , source volume + * source rolloff, source orientation + */ + +#define AUDPP_MSG_QAFX_POS 0x0004 +#define AUDPP_MSG_QAFX_POS_LEN \ + sizeof(struct audpp_msg_qafx_pos) + +struct audpp_msg_qafx_pos { + unsigned short current_object; + unsigned short x_pos_lis_msw; + unsigned short x_pos_lis_lsw; + unsigned short y_pos_lis_msw; + unsigned short y_pos_lis_lsw; + unsigned short z_pos_lis_msw; + unsigned short z_pos_lis_lsw; + unsigned short x_fwd_msw; + unsigned short x_fwd_lsw; + unsigned short y_fwd_msw; + unsigned short y_fwd_lsw; + unsigned short z_fwd_msw; + unsigned short z_fwd_lsw; + unsigned short x_up_msw; + unsigned short x_up_lsw; + unsigned short y_up_msw; + unsigned short y_up_lsw; + unsigned short z_up_msw; + unsigned short z_up_lsw; + unsigned short x_vel_lis_msw; + unsigned short x_vel_lis_lsw; + unsigned short y_vel_lis_msw; + unsigned short y_vel_lis_lsw; + unsigned short z_vel_lis_msw; + unsigned short z_vel_lis_lsw; + unsigned short threed_enable_flag; + unsigned short volume; + unsigned short x_pos_source_msw; + unsigned short x_pos_source_lsw; + unsigned short y_pos_source_msw; + unsigned short y_pos_source_lsw; + unsigned short z_pos_source_msw; + unsigned short z_pos_source_lsw; + unsigned short max_dist_0_msw; + unsigned short max_dist_0_lsw; + unsigned short min_dist_0_msw; + unsigned short min_dist_0_lsw; + unsigned short roll_off_factor; + unsigned short mute_after_max_flag; + unsigned short x_vel_source_msw; + unsigned short x_vel_source_lsw; + unsigned short y_vel_source_msw; + unsigned short y_vel_source_lsw; + unsigned short z_vel_source_msw; + unsigned short z_vel_source_lsw; +} __attribute__((packed)); + +/* + * MSG to provide AVSYNC feedback from DSP to ARM + */ + +#define AUDPP_MSG_AVSYNC_MSG 0x0005 +#define AUDPP_MSG_AVSYNC_MSG_LEN \ + sizeof(struct audpp_msg_avsync_msg) + +struct audpp_msg_avsync_msg { + unsigned short active_flag; + unsigned short num_samples_counter0_HSW; + unsigned short num_samples_counter0_MSW; + unsigned short num_samples_counter0_LSW; + unsigned short num_bytes_counter0_HSW; + unsigned short num_bytes_counter0_MSW; + unsigned short num_bytes_counter0_LSW; + unsigned short samp_freq_obj_0; + unsigned short samp_freq_obj_1; + unsigned short samp_freq_obj_2; + unsigned short samp_freq_obj_3; + unsigned short samp_freq_obj_4; + unsigned short samp_freq_obj_5; + unsigned short samp_freq_obj_6; + unsigned short samp_freq_obj_7; + unsigned short samp_freq_obj_8; + unsigned short samp_freq_obj_9; + unsigned short samp_freq_obj_10; + unsigned short samp_freq_obj_11; + unsigned short samp_freq_obj_12; + unsigned short samp_freq_obj_13; + unsigned short samp_freq_obj_14; + unsigned short samp_freq_obj_15; + unsigned short num_samples_counter4_HSW; + unsigned short num_samples_counter4_MSW; + unsigned short num_samples_counter4_LSW; + unsigned short num_bytes_counter4_HSW; + unsigned short num_bytes_counter4_MSW; + unsigned short num_bytes_counter4_LSW; +} __attribute__((packed)); + +/* + * MSG to provide PCM DMA Missed feedback from the DSP to ARM + */ + +#define AUDPP_MSG_PCMDMAMISSED 0x0006 +#define AUDPP_MSG_PCMDMAMISSED_LEN \ + sizeof(struct audpp_msg_pcmdmamissed); + +struct audpp_msg_pcmdmamissed { + /* + ** Bit 0 0 = PCM DMA not missed for object 0 + ** 1 = PCM DMA missed for object0 + ** Bit 1 0 = PCM DMA not missed for object 1 + ** 1 = PCM DMA missed for object1 + ** Bit 2 0 = PCM DMA not missed for object 2 + ** 1 = PCM DMA missed for object2 + ** Bit 3 0 = PCM DMA not missed for object 3 + ** 1 = PCM DMA missed for object3 + ** Bit 4 0 = PCM DMA not missed for object 4 + ** 1 = PCM DMA missed for object4 + */ + unsigned short pcmdmamissed; +} __attribute__((packed)); + +/* + * MSG to AUDPP enable or disable feedback form DSP to ARM + */ + +#define AUDPP_MSG_CFG_MSG 0x0007 +#define AUDPP_MSG_CFG_MSG_LEN \ + sizeof(struct audpp_msg_cfg_msg) + +#define AUDPP_MSG_ENA_ENA 0xFFFF +#define AUDPP_MSG_ENA_DIS 0x0000 + +struct audpp_msg_cfg_msg { + /* Enabled - 0xffff + ** Disabled - 0 + */ + unsigned short enabled; +} __attribute__((packed)); + +/* + * MSG to communicate the reverb per object volume + */ + +#define AUDPP_MSG_QREVERB_VOLUME 0x0008 +#define AUDPP_MSG_QREVERB_VOLUME_LEN \ + sizeof(struct audpp_msg_qreverb_volume) + + +struct audpp_msg_qreverb_volume { + unsigned short obj_0_gain; + unsigned short obj_1_gain; + unsigned short obj_2_gain; + unsigned short obj_3_gain; + unsigned short obj_4_gain; + unsigned short hpcm_obj_volume; +} __attribute__((packed)); + +#define AUDPP_MSG_ROUTING_ACK 0x0009 +#define AUDPP_MSG_ROUTING_ACK_LEN \ + sizeof(struct audpp_msg_routing_ack) + +struct audpp_msg_routing_ack { + unsigned short dec_id; + unsigned short routing_mode; +} __attribute__((packed)); + +#define AUDPP_MSG_FLUSH_ACK 0x000A + +#endif /* QDSP5AUDPPMSG_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audpreproccmdi.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audpreproccmdi.h new file mode 100644 index 00000000000..f579e1a3d31 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audpreproccmdi.h @@ -0,0 +1,519 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef QDSP5AUDPREPROCCMDI_H +#define QDSP5AUDPREPROCCMDI_H + +/* + * AUDIOPREPROC COMMANDS: + * ARM uses uPAudPreProcAudRecCmdQueue to communicate with AUDPREPROCTASK + * Location : MEMB + * Buffer size : 7 + * Number of buffers in a queue : 4 + */ + +/* + * Command to enable or disable particular encoder for new interface + */ + +#define AUDPREPROC_AUDREC_CMD_ENC_CFG 0x0000 +#define AUDPREPROC_AUDREC_CMD_ENC_CFG_LEN \ + sizeof(struct audpreproc_audrec_cmd_enc_cfg) +#define AUDREC_TASK_0 0x00 /* SBC / PCM */ +#define AUDREC_TASK_1 0x01 /* AAC / PCM / VOICE ENC */ + +#define ENCODE_ENABLE 0x8000 + +/* encoder type supported */ +#define ENC_TYPE_WAV 0x00 +#define ENC_TYPE_AAC 0x01 +#define ENC_TYPE_SBC 0x02 +#define ENC_TYPE_AMRNB 0x03 +#define ENC_TYPE_EVRC 0x04 +#define ENC_TYPE_V13K 0x05 +#define ENC_TYPE_EXT_WAV 0x0F /* to dynamically configure frame size */ + +/* structure definitions according to + * command description of ARM-DSP interface specifications + */ +struct audpreproc_audrec_cmd_enc_cfg { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short audrec_enc_type; +} __attribute__((packed)); + +/* + * Command to configure parameters of selected Encoder + */ + +#define AUDPREPROC_AUDREC_CMD_PARAM_CFG 0x0001 + +#define AUDPREPROC_AUDREC_CMD_PARAM_CFG_COMMON_LEN \ + sizeof(struct audpreproc_audrec_cmd_param_cfg_common) + +#define DUAL_MIC_STEREO_RECORDING 2 + +struct audpreproc_audrec_cmd_param_cfg_common { + unsigned short cmd_id; + unsigned short stream_id; +} __attribute__((packed)); + +/* + * Command Structure to configure WAV Encoder + */ + +#define AUDPREPROC_AUDREC_CMD_PARAM_CFG_WAV_LEN \ + sizeof(struct audpreproc_audrec_cmd_parm_cfg_wav) + +#define AUDREC_CMD_MODE_MONO 0 +#define AUDREC_CMD_MODE_STEREO 1 + +struct audpreproc_audrec_cmd_parm_cfg_wav { + struct audpreproc_audrec_cmd_param_cfg_common common; + unsigned short aud_rec_samplerate_idx; + unsigned short aud_rec_stereo_mode; + unsigned short aud_rec_frame_size; +} __attribute__((packed)); + +/* + * Command Structure to configure AAC Encoder + */ + +#define AUDPREPROC_AUDREC_CMD_PARAM_CFG_AAC_LEN \ + sizeof(struct audpreproc_audrec_cmd_parm_cfg_aac) + +struct audpreproc_audrec_cmd_parm_cfg_aac { + struct audpreproc_audrec_cmd_param_cfg_common common; + unsigned short aud_rec_samplerate_idx; + unsigned short aud_rec_stereo_mode; + signed short recording_quality; +} __attribute__((packed)); + +/* + * Command Structure to configure SBC Encoder + */ + +#define AUDPREPROC_AUDREC_CMD_PARAM_CFG_SBC_LEN \ + sizeof(struct audpreproc_audrec_cmd_parm_cfg_sbc) + +/* encoder parameters mask definitions*/ + +#define AUDREC_SBC_ENC_PARAM_VER_MASK 0x000A +#define AUDREC_SBC_ENC_PARAM_ENAHANCED_SBC_BASELINE_VERSION 0x0000 +#define AUDREC_SBC_ENC_PARAM_ENAHANCED_SBC_NA_MASK 0x0400 +#define AUDREC_SBC_ENC_PARAM_BIT_ALLOC_MASK 0x0008 +#define AUDREC_SBC_ENC_PARAM_SNR_MASK 0x0100 +#define AUDREC_SBC_ENC_PARAM_MODE_MASK 0x0006 +#define AUDREC_SBC_ENC_PARAM_MODE_DUAL_MASK 0x0040 +#define AUDREC_SBC_ENC_PARAM_MODE_STEREO_MASK 0x0080 +#define AUDREC_SBC_ENC_PARAM_MODE_JOINT_STEREO_MASK 0x00C0 +#define AUDREC_SBC_ENC_PARAM_NUM_SUB_BANDS_MASK 0x0004 +#define AUDREC_SBC_ENC_PARAM_NUM_SUB_BANDS_8_MASK 0x0001 +#define AUDREC_SBC_ENC_PARAM_NUM_SUB_BLOCKS_MASK 0x0000 +#define AUDREC_SBC_ENC_PARAM_NUM_SUB_BLOCKS_4_MASK 0x0000 +#define AUDREC_SBC_ENC_PARAM_NUM_SUB_BLOCKS_8_MASK 0x0001 +#define AUDREC_SBC_ENC_PARAM_NUM_SUB_BLOCKS_12_MASK 0x0002 +#define AUDREC_SBC_ENC_PARAM_NUM_SUB_BLOCKS_16_MASK 0x0003 + +struct audpreproc_audrec_cmd_parm_cfg_sbc { + struct audpreproc_audrec_cmd_param_cfg_common common; + unsigned short aud_rec_sbc_enc_param; + unsigned short aud_rec_sbc_bit_rate_msw; + unsigned short aud_rec_sbc_bit_rate_lsw; +} __attribute__((packed)); + +/* + * Command Structure to configure AMRNB Encoder + */ + +#define AUDPREPROC_AUDREC_CMD_PARAM_CFG_AMRNB_LEN \ + sizeof(struct audpreproc_audrec_cmd_parm_cfg_amrnb) + +#define AMRNB_DTX_MODE_ENABLE -1 +#define AMRNB_DTX_MODE_DISABLE 0 + +#define AMRNB_TEST_MODE_ENABLE -1 +#define AMRNB_TEST_MODE_DISABLE 0 + +#define AMRNB_USED_MODE_MR475 0x0 +#define AMRNB_USED_MODE_MR515 0x1 +#define AMRNB_USED_MODE_MR59 0x2 +#define AMRNB_USED_MODE_MR67 0x3 +#define AMRNB_USED_MODE_MR74 0x4 +#define AMRNB_USED_MODE_MR795 0x5 +#define AMRNB_USED_MODE_MR102 0x6 +#define AMRNB_USED_MODE_MR122 0x7 + +struct audpreproc_audrec_cmd_parm_cfg_amrnb { + struct audpreproc_audrec_cmd_param_cfg_common common; + signed short dtx_mode; + signed short test_mode; + unsigned short used_mode; +} __attribute__((packed)) ; + +/* + * Command Structure to configure EVRC Encoder + */ + +#define AUDPREPROC_AUDREC_CMD_PARAM_CFG_EVRC_LEN \ + sizeof(struct audpreproc_audrec_cmd_parm_cfg_evrc) + +struct audpreproc_audrec_cmd_parm_cfg_evrc { + struct audpreproc_audrec_cmd_param_cfg_common common; + unsigned short enc_min_rate; + unsigned short enc_max_rate; + unsigned short rate_modulation_cmd; +} __attribute__((packed)); + +/* + * Command Structure to configure QCELP_13K Encoder + */ + +#define AUDPREPROC_AUDREC_CMD_PARAM_CFG_QCELP13K_LEN \ + sizeof(struct audpreproc_audrec_cmd_parm_cfg_qcelp13k) + +struct audpreproc_audrec_cmd_parm_cfg_qcelp13k { + struct audpreproc_audrec_cmd_param_cfg_common common; + unsigned short enc_min_rate; + unsigned short enc_max_rate; + unsigned short rate_modulation_cmd; + unsigned short reduced_rate_level; +} __attribute__((packed)); + +/* + * Command to configure AFE for recording paths + */ +#define AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG 0x0002 + +#define AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_LEN \ + sizeof(struct audpreproc_afe_cmd_audio_record_cfg) + +#define AUDIO_RECORDING_TURN_ON 0xFFFF +#define AUDIO_RECORDING_TURN_OFF 0x0000 + +#define AUDPP_A2DP_PIPE_SOURCE_MIX_MASK 0x0020 +#define VOICE_DL_SOURCE_MIX_MASK 0x0010 +#define VOICE_UL_SOURCE_MIX_MASK 0x0008 +#define FM_SOURCE_MIX_MASK 0x0004 +#define AUX_CODEC_TX_SOURCE_MIX_MASK 0x0002 +#define INTERNAL_CODEC_TX_SOURCE_MIX_MASK 0x0001 + +struct audpreproc_afe_cmd_audio_record_cfg { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short destination_activity; + unsigned short source_mix_mask; + unsigned short pipe_id; + unsigned short reserved; +} __attribute__((packed)); + +/* + * Command to configure Tunnel(RT) or Non-Tunnel(FTRT) mode + */ +#define AUDPREPROC_AUDREC_CMD_ROUTING_MODE 0x0003 +#define AUDPREPROC_AUDREC_CMD_ROUTING_MODE_LEN \ + sizeof(struct audpreproc_audrec_cmd_routing_mode) + +#define AUDIO_ROUTING_MODE_FTRT 0x0001 +#define AUDIO_ROUTING_MODE_RT 0x0002 + +struct audpreproc_audrec_cmd_routing_mode { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short routing_mode; +} __attribute__((packed)); + +/* + * Command to configure DSP for topology where resampler moved + * in front of pre processing chain + */ +#define AUDPREPROC_AUDREC_CMD_ENC_CFG_2 0x0004 +#define AUDPREPROC_AUDREC_CMD_ENC_CFG_2_LEN \ + sizeof(struct audpreproc_audrec_cmd_enc_cfg_2) + + +struct audpreproc_audrec_cmd_enc_cfg_2 { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short audrec_enc_type; +} __attribute__((packed)); + +/* + * AUDIOPREPROC COMMANDS: + * ARM uses uPAudPreProcCmdQueue to communicate with AUDPREPROCTASK + * Location : MEMB + * Buffer size : 52 + * Number of buffers in a queue : 3 + */ + +/* + * Command to configure the parameters of AGC + */ + +#define AUDPREPROC_CMD_CFG_AGC_PARAMS 0x0000 +#define AUDPREPROC_CMD_CFG_AGC_PARAMS_LEN \ + sizeof(struct audpreproc_cmd_cfg_agc_params) + +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_SLOPE 0x0200 +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_TH 0x0400 +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_EXP_SLOPE 0x0800 +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_EXP_TH 0x1000 +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_AIG_FLAG 0x2000 +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_STATIC_GAIN 0x4000 +#define AUDPREPROC_CMD_TX_AGC_PARAM_MASK_TX_AGC_ENA_FLAG 0x8000 + +#define AUDPREPROC_CMD_TX_AGC_ENA_FLAG_ENA -1 +#define AUDPREPROC_CMD_TX_AGC_ENA_FLAG_DIS 0x0000 + +#define AUDPREPROC_CMD_ADP_GAIN_FLAG_ENA_ADP_GAIN -1 +#define AUDPREPROC_CMD_ADP_GAIN_FLAG_ENA_STATIC_GAIN 0x0000 + +#define AUDPREPROC_CMD_PARAM_MASK_RMS_TAY 0x0010 +#define AUDPREPROC_CMD_PARAM_MASK_RELEASEK 0x0020 +#define AUDPREPROC_CMD_PARAM_MASK_DELAY 0x0040 +#define AUDPREPROC_CMD_PARAM_MASK_ATTACKK 0x0080 +#define AUDPREPROC_CMD_PARAM_MASK_LEAKRATE_SLOW 0x0100 +#define AUDPREPROC_CMD_PARAM_MASK_LEAKRATE_FAST 0x0200 +#define AUDPREPROC_CMD_PARAM_MASK_AIG_RELEASEK 0x0400 +#define AUDPREPROC_CMD_PARAM_MASK_AIG_MIN 0x0800 +#define AUDPREPROC_CMD_PARAM_MASK_AIG_MAX 0x1000 +#define AUDPREPROC_CMD_PARAM_MASK_LEAK_UP 0x2000 +#define AUDPREPROC_CMD_PARAM_MASK_LEAK_DOWN 0x4000 +#define AUDPREPROC_CMD_PARAM_MASK_AIG_ATTACKK 0x8000 + +struct audpreproc_cmd_cfg_agc_params { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short tx_agc_param_mask; + signed short tx_agc_enable_flag; + unsigned short comp_rlink_static_gain; + signed short comp_rlink_aig_flag; + unsigned short expander_rlink_th; + unsigned short expander_rlink_slope; + unsigned short compressor_rlink_th; + unsigned short compressor_rlink_slope; + unsigned short tx_adc_agc_param_mask; + unsigned short comp_rlink_aig_attackk; + unsigned short comp_rlink_aig_leak_down; + unsigned short comp_rlink_aig_leak_up; + unsigned short comp_rlink_aig_max; + unsigned short comp_rlink_aig_min; + unsigned short comp_rlink_aig_releasek; + unsigned short comp_rlink_aig_leakrate_fast; + unsigned short comp_rlink_aig_leakrate_slow; + unsigned short comp_rlink_attackk_msw; + unsigned short comp_rlink_attackk_lsw; + unsigned short comp_rlink_delay; + unsigned short comp_rlink_releasek_msw; + unsigned short comp_rlink_releasek_lsw; + unsigned short comp_rlink_rms_tav; +} __attribute__((packed)); + +/* + * Command to configure the params of Advanved AGC + */ + +#define AUDPREPROC_CMD_CFG_AGC_PARAMS_2 0x0001 +#define AUDPREPROC_CMD_CFG_AGC_PARAMS_2_LEN \ + sizeof(struct audpreproc_cmd_cfg_agc_params_2) + +#define AUDPREPROC_CMD_2_TX_AGC_ENA_FLAG_ENA -1; +#define AUDPREPROC_CMD_2_TX_AGC_ENA_FLAG_DIS 0x0000; + +struct audpreproc_cmd_cfg_agc_params_2 { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short agc_param_mask; + signed short tx_agc_enable_flag; + unsigned short comp_rlink_static_gain; + unsigned short exp_rlink_th; + unsigned short exp_rlink_slope; + unsigned short comp_rlink_th; + unsigned short comp_rlink_slope; + unsigned short comp_rlink_rms_tav; + unsigned short comp_rlink_down_samp_mask; + unsigned short comp_rlink_attackk_msw; + unsigned short comp_rlink_attackk_lsw; + unsigned short comp_rlink_releasek_msw; + unsigned short comp_rlink_releasek_lsw; + unsigned short comp_rlink_delay; + unsigned short comp_rlink_makeup_gain; +} __attribute__((packed)); + +/* + * Command to configure params for ns + */ + +#define AUDPREPROC_CMD_CFG_NS_PARAMS 0x0002 +#define AUDPREPROC_CMD_CFG_NS_PARAMS_LEN \ + sizeof(struct audpreproc_cmd_cfg_ns_params) + +#define AUDPREPROC_CMD_EC_MODE_NLMS_ENA 0x0001 +#define AUDPREPROC_CMD_EC_MODE_NLMS_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_DES_ENA 0x0002 +#define AUDPREPROC_CMD_EC_MODE_DES_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NS_ENA 0x0004 +#define AUDPREPROC_CMD_EC_MODE_NS_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_CNI_ENA 0x0008 +#define AUDPREPROC_CMD_EC_MODE_CNI_DIS 0x0000 + +#define AUDPREPROC_CMD_EC_MODE_NLES_ENA 0x0010 +#define AUDPREPROC_CMD_EC_MODE_NLES_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_HB_ENA 0x0020 +#define AUDPREPROC_CMD_EC_MODE_HB_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_VA_ENA 0x0040 +#define AUDPREPROC_CMD_EC_MODE_VA_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_PCD_ENA 0x0080 +#define AUDPREPROC_CMD_EC_MODE_PCD_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_FEHI_ENA 0x0100 +#define AUDPREPROC_CMD_EC_MODE_FEHI_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NEHI_ENA 0x0200 +#define AUDPREPROC_CMD_EC_MODE_NEHI_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_NLPP_ENA 0x0400 +#define AUDPREPROC_CMD_EC_MODE_NLPP_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_FNE_ENA 0x0800 +#define AUDPREPROC_CMD_EC_MODE_FNE_DIS 0x0000 +#define AUDPREPROC_CMD_EC_MODE_PRENLMS_ENA 0x1000 +#define AUDPREPROC_CMD_EC_MODE_PRENLMS_DIS 0x0000 + +struct audpreproc_cmd_cfg_ns_params { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short ec_mode_new; + unsigned short dens_gamma_n; + unsigned short dens_nfe_block_size; + unsigned short dens_limit_ns; + unsigned short dens_limit_ns_d; + unsigned short wb_gamma_e; + unsigned short wb_gamma_n; +} __attribute__((packed)); + +/* + * Command to configure parameters for IIR tuning filter + */ + +#define AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS 0x0003 +#define AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS_LEN \ + sizeof(struct audpreproc_cmd_cfg_iir_tuning_filter_params) + +#define AUDPREPROC_CMD_IIR_ACTIVE_FLAG_DIS 0x0000 +#define AUDPREPROC_CMD_IIR_ACTIVE_FLAG_ENA 0x0001 + +struct audpreproc_cmd_cfg_iir_tuning_filter_params { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short active_flag; + unsigned short num_bands; + + unsigned short numerator_coeff_b0_filter0_lsw; + unsigned short numerator_coeff_b0_filter0_msw; + unsigned short numerator_coeff_b1_filter0_lsw; + unsigned short numerator_coeff_b1_filter0_msw; + unsigned short numerator_coeff_b2_filter0_lsw; + unsigned short numerator_coeff_b2_filter0_msw; + + unsigned short numerator_coeff_b0_filter1_lsw; + unsigned short numerator_coeff_b0_filter1_msw; + unsigned short numerator_coeff_b1_filter1_lsw; + unsigned short numerator_coeff_b1_filter1_msw; + unsigned short numerator_coeff_b2_filter1_lsw; + unsigned short numerator_coeff_b2_filter1_msw; + + unsigned short numerator_coeff_b0_filter2_lsw; + unsigned short numerator_coeff_b0_filter2_msw; + unsigned short numerator_coeff_b1_filter2_lsw; + unsigned short numerator_coeff_b1_filter2_msw; + unsigned short numerator_coeff_b2_filter2_lsw; + unsigned short numerator_coeff_b2_filter2_msw; + + unsigned short numerator_coeff_b0_filter3_lsw; + unsigned short numerator_coeff_b0_filter3_msw; + unsigned short numerator_coeff_b1_filter3_lsw; + unsigned short numerator_coeff_b1_filter3_msw; + unsigned short numerator_coeff_b2_filter3_lsw; + unsigned short numerator_coeff_b2_filter3_msw; + + unsigned short denominator_coeff_a0_filter0_lsw; + unsigned short denominator_coeff_a0_filter0_msw; + unsigned short denominator_coeff_a1_filter0_lsw; + unsigned short denominator_coeff_a1_filter0_msw; + + unsigned short denominator_coeff_a0_filter1_lsw; + unsigned short denominator_coeff_a0_filter1_msw; + unsigned short denominator_coeff_a1_filter1_lsw; + unsigned short denominator_coeff_a1_filter1_msw; + + unsigned short denominator_coeff_a0_filter2_lsw; + unsigned short denominator_coeff_a0_filter2_msw; + unsigned short denominator_coeff_a1_filter2_lsw; + unsigned short denominator_coeff_a1_filter2_msw; + + unsigned short denominator_coeff_a0_filter3_lsw; + unsigned short denominator_coeff_a0_filter3_msw; + unsigned short denominator_coeff_a1_filter3_lsw; + unsigned short denominator_coeff_a1_filter3_msw; + + unsigned short shift_factor_filter0; + unsigned short shift_factor_filter1; + unsigned short shift_factor_filter2; + unsigned short shift_factor_filter3; + + unsigned short pan_of_filter0; + unsigned short pan_of_filter1; + unsigned short pan_of_filter2; + unsigned short pan_of_filter3; +} __attribute__((packed)); + +/* + * Command to configure parameters for calibration gain rx + */ + +#define AUDPREPROC_CMD_CFG_CAL_GAIN_PARAMS 0x0004 +#define AUDPREPROC_CMD_CFG_CAL_GAIN_LEN \ + sizeof(struct audpreproc_cmd_cfg_cal_gain) + +struct audpreproc_cmd_cfg_cal_gain { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short audprecalgain; + unsigned short reserved; +} __attribute__((packed)); + +#define AUDPREPROC_CMD_CFG_LVNV_PARMS 0x0006 +#define AUDPREPROC_CMD_CFG_LVNV_PARMS_LEN \ + sizeof(struct audpreproc_cmd_cfg_lvnv_param) + +struct audpreproc_cmd_cfg_lvnv_param { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short cs_mode; + unsigned short lvnv_ext_buf_size; + unsigned short lvnv_ext_partition; + unsigned short lvnv_ext_buf_start_lsw; + unsigned short lvnv_ext_buf_start_msw; +}; + +#define AUDPREPROC_CMD_FEAT_QUERY_PARAMS 0x0005 + +struct rtc_audpreproc_read_data { + unsigned short cmd_id; + unsigned short stream_id; + unsigned short feature_id; + unsigned short extbufsizemsw; + unsigned short extbufsizelsw; + unsigned short extpart; + unsigned short extbufstartmsw; + unsigned short extbufstartlsw; +} __attribute__((packed)) ; + +#endif /* QDSP5AUDPREPROCCMDI_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audpreprocmsg.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audpreprocmsg.h new file mode 100644 index 00000000000..29da664544c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audpreprocmsg.h @@ -0,0 +1,127 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef QDSP5AUDPREPROCMSG_H +#define QDSP5AUDPREPROCMSG_H + +#define AUDPREPROC_MSG_FEAT_QUERY_DM_DONE 0x0006 + +/* + * ADSPREPROCTASK Messages + * AUDPREPROCTASK uses audPreProcUpRlist to communicate with ARM + * Location : MEMB + * Buffer size : 6 + * No of buffers in queue : 4 + */ + +/* + * Message to indicate Pre processing config command is done + */ + +#define AUDPREPROC_CMD_CFG_DONE_MSG 0x0001 +#define AUDPREPROC_CMD_CFG_DONE_MSG_LEN \ + sizeof(struct audpreproc_cmd_cfg_done_msg) + +#define AUD_PREPROC_TYPE_AGC 0x0 +#define AUD_PREPROC_NOISE_REDUCTION 0x1 +#define AUD_PREPROC_IIR_TUNNING_FILTER 0x2 + +#define AUD_PREPROC_CONFIG_ENABLED -1 +#define AUD_PREPROC_CONFIG_DISABLED 0 + +struct audpreproc_cmd_cfg_done_msg { + unsigned short stream_id; + unsigned short aud_preproc_type; + signed short aud_preproc_status_flag; +} __attribute__((packed)); + +/* + * Message to indicate Pre processing error messages + */ + +#define AUDPREPROC_ERROR_MSG 0x0002 +#define AUDPREPROC_ERROR_MSG_LEN \ + sizeof(struct audpreproc_err_msg) + +#define AUD_PREPROC_ERR_IDX_WRONG_SAMPLING_FREQUENCY 0x00 +#define AUD_PREPROC_ERR_IDX_ENC_NOT_SUPPORTED 0x01 + +struct audpreproc_err_msg { + unsigned short stream_id; + signed short aud_preproc_err_idx; +} __attribute__((packed)); + +/* + * Message to indicate encoder config command + */ + +#define AUDPREPROC_CMD_ENC_CFG_DONE_MSG 0x0003 +#define AUDPREPROC_CMD_ENC_CFG_DONE_MSG_LEN \ + sizeof(struct audpreproc_cmd_enc_cfg_done_msg) + +struct audpreproc_cmd_enc_cfg_done_msg { + unsigned short stream_id; + unsigned short rec_enc_type; +} __attribute__((packed)); + +/* + * Message to indicate encoder param config command + */ + +#define AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG 0x0004 +#define AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG_LEN \ + sizeof(struct audpreproc_cmd_enc_param_cfg_done_msg) + +struct audpreproc_cmd_enc_param_cfg_done_msg { + unsigned short stream_id; +} __attribute__((packed)); + + +/* + * Message to indicate AFE config cmd for + * audio recording is successfully recieved + */ + +#define AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG 0x0005 +#define AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG_LEN \ + sizeof(struct audpreproc_afe_cmd_audio_record_cfg_done) + +struct audpreproc_afe_cmd_audio_record_cfg_done { + unsigned short stream_id; +} __attribute__((packed)); + +/* + * Message to indicate Routing mode + * configuration success or failure + */ + +#define AUDPREPROC_CMD_ROUTING_MODE_DONE_MSG 0x0007 +#define AUDPREPROC_CMD_ROUTING_MODE_DONE_MSG_LEN \ + sizeof(struct audpreproc_cmd_routing_mode_done) + +struct audpreproc_cmd_routing_mode_done { + unsigned short stream_id; + unsigned short configuration; +} __attribute__((packed)); + + +#define AUDPREPROC_CMD_PCM_CFG_ARM_TO_PREPROC_DONE_MSG 0x0008 +#define AUDPREPROC_CMD_PCM_CFG_ARM_TO_PREPROC_DONE_MSG_LEN \ + sizeof(struct audreproc_cmd_pcm_cfg_arm_to_preproc_done) + +struct audreproc_cmd_pcm_cfg_arm_to_preproc_done { + unsigned short stream_id; + unsigned short configuration; +} __attribute__((packed)); + +#endif /* QDSP5AUDPREPROCMSG_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audreccmdi.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audreccmdi.h new file mode 100644 index 00000000000..9ba8645ae06 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audreccmdi.h @@ -0,0 +1,122 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef QDSP5AUDRECCMDI_H +#define QDSP5AUDRECCMDI_H + +/* + * AUDRECTASK COMMANDS + * ARM uses 2 queues to communicate with the AUDRECTASK + * 1.uPAudRec[i]CmdQueue, where i=0,1,2 + * Location :MEMC + * Buffer Size : 5 + * No of Buffers in a queue : 2 + * 2.uPAudRec[i]BitstreamQueue, where i=0,1,2 + * Location : MEMC + * Buffer Size : 5 + * No of buffers in a queue : 3 + */ + +/* + * Commands on uPAudRec[i]CmdQueue, where i=0,1,2 + */ + +/* + * Command to configure memory for enabled encoder + */ + +#define AUDREC_CMD_MEM_CFG_CMD 0x0000 +#define AUDREC_CMD_ARECMEM_CFG_LEN \ + sizeof(struct audrec_cmd_arecmem_cfg) + +struct audrec_cmd_arecmem_cfg { + unsigned short cmd_id; + unsigned short audrec_up_pkt_intm_count; + unsigned short audrec_ext_pkt_start_addr_msw; + unsigned short audrec_ext_pkt_start_addr_lsw; + unsigned short audrec_ext_pkt_buf_number; +} __attribute__((packed)); + +/* + * Command to configure pcm input memory + */ + +#define AUDREC_CMD_PCM_CFG_ARM_TO_ENC 0x0001 +#define AUDREC_CMD_PCM_CFG_ARM_TO_ENC_LEN \ + sizeof(struct audrec_cmd_pcm_cfg_arm_to_enc) + +struct audrec_cmd_pcm_cfg_arm_to_enc { + unsigned short cmd_id; + unsigned short config_update_flag; + unsigned short enable_flag; + unsigned short sampling_freq; + unsigned short channels; + unsigned short frequency_of_intimation; + unsigned short max_number_of_buffers; +} __attribute__((packed)); + +#define AUDREC_PCM_CONFIG_UPDATE_FLAG_ENABLE -1 +#define AUDREC_PCM_CONFIG_UPDATE_FLAG_DISABLE 0 + +#define AUDREC_ENABLE_FLAG_VALUE -1 +#define AUDREC_DISABLE_FLAG_VALUE 0 + +/* + * Command to intimate available pcm buffer + */ + +#define AUDREC_CMD_PCM_BUFFER_PTR_REFRESH_ARM_TO_ENC 0x0002 +#define AUDREC_CMD_PCM_BUFFER_PTR_REFRESH_ARM_TO_ENC_LEN \ + sizeof(struct audrec_cmd_pcm_buffer_ptr_refresh_arm_enc) + +struct audrec_cmd_pcm_buffer_ptr_refresh_arm_enc { + unsigned short cmd_id; + unsigned short num_buffers; + unsigned short buffer_write_cnt_msw; + unsigned short buffer_write_cnt_lsw; + unsigned short buf_address_length[8];/*this array holds address + and length details of + two buffers*/ +} __attribute__((packed)); + +/* + * Command to flush + */ + +#define AUDREC_CMD_FLUSH 0x0003 +#define AUDREC_CMD_FLUSH_LEN \ + sizeof(struct audrec_cmd_flush) + +struct audrec_cmd_flush { + unsigned short cmd_id; +} __attribute__((packed)); + +/* + * Commands on uPAudRec[i]BitstreamQueue, where i=0,1,2 + */ + +/* + * Command to indicate current packet read count + */ + +#define UP_AUDREC_PACKET_EXT_PTR 0x0000 +#define UP_AUDREC_PACKET_EXT_PTR_LEN \ + sizeof(up_audrec_packet_ext_ptr) + +struct up_audrec_packet_ext_ptr { + unsigned short cmd_id; + unsigned short audrec_up_curr_read_count_lsw; + unsigned short audrec_up_curr_read_count_msw; +} __attribute__((packed)); + +#endif /* QDSP5AUDRECCMDI_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audrecmsg.h b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audrecmsg.h new file mode 100644 index 00000000000..32ccbbc42d4 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/qdsp5audrecmsg.h @@ -0,0 +1,115 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef QDSP5AUDRECMSG_H +#define QDSP5AUDRECMSG_H + +/* + * AUDRECTASK MESSAGES + * AUDRECTASK uses audRec[i]UpRlist, where i=0,1,2 to communicate with ARM + * Location : MEMC + * Buffer size : 5 + * No of buffers in a queue : 10 + */ + +/* + * Message to notify 2 error conditions + */ + +#define AUDREC_FATAL_ERR_MSG 0x0001 +#define AUDREC_FATAL_ERR_MSG_LEN \ + sizeof(struct audrec_fatal_err_msg) + +#define AUDREC_FATAL_ERR_MSG_NO_PKT 0x00 + +struct audrec_fatal_err_msg { + unsigned short audrec_err_id; +} __attribute__((packed)); + +/* + * Message to indicate encoded packet is delivered to external buffer + */ + +#define AUDREC_UP_PACKET_READY_MSG 0x0002 +#define AUDREC_UP_PACKET_READY_MSG_LEN \ + sizeof(struct audrec_up_pkt_ready_msg) + +struct audrec_up_pkt_ready_msg { + unsigned short audrec_packet_write_cnt_lsw; + unsigned short audrec_packet_write_cnt_msw; + unsigned short audrec_up_prev_read_cnt_lsw; + unsigned short audrec_up_prev_read_cnt_msw; +} __attribute__((packed)); + +/* + * Message indicates arecmem cfg done + */ +#define AUDREC_CMD_MEM_CFG_DONE_MSG 0x0003 + +/* buffer conntents are nill only message id is required */ + +/* + * Message to indicate pcm buffer configured + */ + +#define AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG 0x0004 +#define AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG_LEN \ + sizeof(struct audrec_cmd_pcm_cfg_arm_to_enc_msg) + +struct audrec_cmd_pcm_cfg_arm_to_enc_msg { + unsigned short configuration; +} __attribute__((packed)); + +/* + * Message to indicate encoded packet is delivered to external buffer in FTRT + */ + +#define AUDREC_UP_NT_PACKET_READY_MSG 0x0005 +#define AUDREC_UP_NT_PACKET_READY_MSG_LEN \ + sizeof(struct audrec_up_nt_packet_ready_msg) + +struct audrec_up_nt_packet_ready_msg { + unsigned short audrec_packetwrite_cnt_lsw; + unsigned short audrec_packetwrite_cnt_msw; + unsigned short audrec_upprev_readcount_lsw; + unsigned short audrec_upprev_readcount_msw; +} __attribute__((packed)); + +/* + * Message to indicate pcm buffer is consumed + */ + +#define AUDREC_CMD_PCM_BUFFER_PTR_UPDATE_ARM_TO_ENC_MSG 0x0006 +#define AUDREC_CMD_PCM_BUFFER_PTR_UPDATE_ARM_TO_ENC_MSG_LEN \ + sizeof(struct audrec_cmd_pcm_buffer_ptr_update_arm_to_enc_msg) + +struct audrec_cmd_pcm_buffer_ptr_update_arm_to_enc_msg { + unsigned short buffer_readcnt_msw; + unsigned short buffer_readcnt_lsw; + unsigned short number_of_buffers; + unsigned short buffer_address_length[]; +} __attribute__((packed)); + +/* + * Message to indicate flush acknowledgement + */ + +#define AUDREC_CMD_FLUSH_DONE_MSG 0x0007 + +/* + * Message to indicate End of Stream acknowledgement + */ + +#define AUDREC_CMD_EOS_ACK_MSG 0x0008 + +#endif /* QDSP5AUDRECMSG_H */ diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_ecodec.h b/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_ecodec.h new file mode 100644 index 00000000000..35c1edbe10e --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_ecodec.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_SNDDEV_ECODEC_H +#define __MACH_QDSP5_V2_SNDDEV_ECODEC_H +#include + +struct snddev_ecodec_data { + u32 capability; /* RX or TX */ + const char *name; + u32 copp_id; /* audpp routing */ + u32 acdb_id; /* Audio Cal purpose */ + u8 channel_mode; + u32 conf_pcm_ctl_val; + u32 conf_aux_codec_intf; + u32 conf_data_format_padding_val; + s32 max_voice_rx_vol[VOC_RX_VOL_ARRAY_NUM]; /* [0]:NB, [1]:WB */ + s32 min_voice_rx_vol[VOC_RX_VOL_ARRAY_NUM]; +}; +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_icodec.h b/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_icodec.h new file mode 100644 index 00000000000..7f9938e3481 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_icodec.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_SNDDEV_ICODEC_H +#define __MACH_QDSP5_V2_SNDDEV_ICODEC_H +#include +#include +#include + +struct snddev_icodec_data { + u32 capability; /* RX or TX */ + const char *name; + u32 copp_id; /* audpp routing */ + u32 acdb_id; /* Audio Cal purpose */ + /* Adie profile */ + struct adie_codec_dev_profile *profile; + /* Afe setting */ + u8 channel_mode; + enum hsed_controller *pmctl_id; /* tx only enable mic bias */ + u32 pmctl_id_sz; + u32 default_sample_rate; + void (*pamp_on) (void); + void (*pamp_off) (void); + void (*voltage_on) (void); + void (*voltage_off) (void); + s32 max_voice_rx_vol[VOC_RX_VOL_ARRAY_NUM]; /* [0]: NB,[1]: WB */ + s32 min_voice_rx_vol[VOC_RX_VOL_ARRAY_NUM]; + u32 dev_vol_type; + u32 property; /*variable used to hold the properties + internal to the device*/ +}; +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_mi2s.h b/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_mi2s.h new file mode 100644 index 00000000000..cd834a58884 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_mi2s.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_SNDDEV_MI2S_H +#define __MACH_QDSP5_V2_SNDDEV_MI2S_H + +struct snddev_mi2s_data { + u32 capability; /* RX or TX */ + const char *name; + u32 copp_id; /* audpp routing */ + u32 acdb_id; /* Audio Cal purpose */ + u8 channel_mode; + u8 sd_lines; + void (*route) (void); + void (*deroute) (void); + u32 default_sample_rate; +}; + +int mi2s_config_clk_gpio(void); + +int mi2s_config_data_gpio(u32 direction, u8 sd_line_mask); + +int mi2s_unconfig_clk_gpio(void); + +int mi2s_unconfig_data_gpio(u32 direction, u8 sd_line_mask); + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_virtual.h b/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_virtual.h new file mode 100644 index 00000000000..695b19d05b4 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/snddev_virtual.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP5_V2_SNDDEV_VIRTUAL_H +#define __MACH_QDSP5_V2_SNDDEV_VIRTUAL_H +#include + +struct snddev_virtual_data { + u32 capability; /* RX or TX */ + const char *name; + u32 copp_id; /* audpp routing */ + u32 acdb_id; /* Audio Cal purpose */ +}; +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp5v2/voice.h b/arch/arm/mach-msm/include/mach/qdsp5v2/voice.h new file mode 100644 index 00000000000..5ca2d6d11e4 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp5v2/voice.h @@ -0,0 +1,117 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _MACH_QDSP5_V2_VOICE_H +#define _MACH_QDSP5_V2_VOICE_H + +#define VOICE_DALRPC_DEVICEID 0x02000075 +#define VOICE_DALRPC_PORT_NAME "DAL00" +#define VOICE_DALRPC_CPU 0 + + +/* Commands sent to Modem */ +#define CMD_VOICE_INIT 0x1 +#define CMD_ACQUIRE_DONE 0x2 +#define CMD_RELEASE_DONE 0x3 +#define CMD_DEVICE_INFO 0x4 +#define CMD_DEVICE_CHANGE 0x6 + +/* EVENTS received from MODEM */ +#define EVENT_ACQUIRE_START 0x51 +#define EVENT_RELEASE_START 0x52 +#define EVENT_CHANGE_START 0x54 +#define EVENT_NETWORK_RECONFIG 0x53 + +/* voice state */ +enum { + VOICE_INIT = 0, + VOICE_ACQUIRE, + VOICE_CHANGE, + VOICE_RELEASE, +}; + +enum { + NETWORK_CDMA = 0, + NETWORK_GSM, + NETWORK_WCDMA, + NETWORK_WCDMA_WB, +}; + +enum { + VOICE_DALRPC_CMD = DALDEVICE_FIRST_DEVICE_API_IDX +}; + +/* device state */ +enum { + DEV_INIT = 0, + DEV_READY, + DEV_CHANGE, + DEV_CONCUR, + DEV_REL_DONE, +}; + +/* Voice Event */ +enum{ + VOICE_RELEASE_START = 1, + VOICE_CHANGE_START, + VOICE_ACQUIRE_START, + VOICE_NETWORK_RECONFIG, +}; + +/* Device Event */ +#define DEV_CHANGE_READY 0x1 + +#define VOICE_CALL_START 0x1 +#define VOICE_CALL_END 0 + +#define VOICE_DEV_ENABLED 0x1 +#define VOICE_DEV_DISABLED 0 + +struct voice_header { + uint32_t id; + uint32_t data_len; +}; + +struct voice_init { + struct voice_header hdr; + void *cb_handle; +}; + + +/* Device information payload structure */ +struct voice_device { + struct voice_header hdr; + uint32_t rx_device; + uint32_t tx_device; + uint32_t rx_volume; + uint32_t rx_mute; + uint32_t tx_mute; + uint32_t rx_sample; + uint32_t tx_sample; +}; + +/*Voice command structure*/ +struct voice_network { + struct voice_header hdr; + uint32_t network_info; +}; + +struct device_data { + uint32_t dev_acdb_id; + uint32_t volume; /* in percentage */ + uint32_t mute; + uint32_t sample; + uint32_t enabled; + uint32_t dev_id; +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp6v2/apr.h b/arch/arm/mach-msm/include/mach/qdsp6v2/apr.h new file mode 100644 index 00000000000..83099539164 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/apr.h @@ -0,0 +1,151 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __APR_H_ +#define __APR_H_ + +#define APR_Q6_NOIMG 0 +#define APR_Q6_LOADING 1 +#define APR_Q6_LOADED 2 + +struct apr_q6 { + void *pil; + uint32_t state; + struct mutex lock; +}; + +struct apr_hdr { + uint16_t hdr_field; + uint16_t pkt_size; + uint8_t src_svc; + uint8_t src_domain; + uint16_t src_port; + uint8_t dest_svc; + uint8_t dest_domain; + uint16_t dest_port; + uint32_t token; + uint32_t opcode; +}; + +#define APR_HDR_LEN(hdr_len) ((hdr_len)/4) +#define APR_PKT_SIZE(hdr_len, payload_len) ((hdr_len) + (payload_len)) +#define APR_HDR_FIELD(msg_type, hdr_len, ver)\ + (((msg_type & 0x3) << 8) | ((hdr_len & 0xF) << 4) | (ver & 0xF)) + +#define APR_HDR_SIZE sizeof(struct apr_hdr) + +/* Version */ +#define APR_PKT_VER 0x0 + +/* Command and Response Types */ +#define APR_MSG_TYPE_EVENT 0x0 +#define APR_MSG_TYPE_CMD_RSP 0x1 +#define APR_MSG_TYPE_SEQ_CMD 0x2 +#define APR_MSG_TYPE_NSEQ_CMD 0x3 +#define APR_MSG_TYPE_MAX 0x04 + +/* APR Basic Response Message */ +#define APR_BASIC_RSP_RESULT 0x000110E8 +#define APR_RSP_ACCEPTED 0x000100BE + +/* Domain IDs */ +#define APR_DOMAIN_SIM 0x1 +#define APR_DOMAIN_PC 0x2 +#define APR_DOMAIN_MODEM 0x3 +#define APR_DOMAIN_ADSP 0x4 +#define APR_DOMAIN_APPS 0x5 +#define APR_DOMAIN_MAX 0x6 + +/* ADSP service IDs */ +#define APR_SVC_TEST_CLIENT 0x2 +#define APR_SVC_ADSP_CORE 0x3 +#define APR_SVC_AFE 0x4 +#define APR_SVC_VSM 0x5 +#define APR_SVC_VPM 0x6 +#define APR_SVC_ASM 0x7 +#define APR_SVC_ADM 0x8 +#define APR_SVC_ADSP_MVM 0x09 +#define APR_SVC_ADSP_CVS 0x0A +#define APR_SVC_ADSP_CVP 0x0B +#define APR_SVC_MAX 0x0C + +/* Modem Service IDs */ +#define APR_SVC_MVS 0x3 +#define APR_SVC_MVM 0x4 +#define APR_SVC_CVS 0x5 +#define APR_SVC_CVP 0x6 +#define APR_SVC_SRD 0x7 + +/* APR Port IDs */ +#define APR_MAX_PORTS 0x40 + +#define APR_NAME_MAX 0x40 + +#define RESET_EVENTS 0xFFFFFFFF + +#define LPASS_RESTART_EVENT 0x1000 +#define LPASS_RESTART_READY 0x1001 + +struct apr_client_data { + uint16_t reset_event; + uint16_t reset_proc; + uint16_t payload_size; + uint16_t hdr_len; + uint16_t msg_type; + uint16_t src; + uint16_t dest_svc; + uint16_t src_port; + uint16_t dest_port; + uint32_t token; + uint32_t opcode; + void *payload; +}; + +typedef int32_t (*apr_fn)(struct apr_client_data *data, void *priv); + +struct apr_svc { + uint16_t id; + uint16_t dest_id; + uint16_t client_id; + uint8_t rvd; + uint8_t port_cnt; + uint8_t svc_cnt; + uint8_t need_reset; + apr_fn port_fn[APR_MAX_PORTS]; + void *port_priv[APR_MAX_PORTS]; + apr_fn fn; + void *priv; + struct mutex m_lock; + spinlock_t w_lock; +}; + +struct apr_client { + uint8_t id; + uint8_t svc_cnt; + uint8_t rvd; + struct mutex m_lock; + struct apr_svc_ch_dev *handle; + struct apr_svc svc[APR_SVC_MAX]; +}; + +struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn, + uint32_t src_port, void *priv); +inline int apr_fill_hdr(void *handle, uint32_t *buf, uint16_t src_port, + uint16_t msg_type, uint16_t dest_port, + uint32_t token, uint32_t opcode, uint16_t len); + +int apr_send_pkt(void *handle, uint32_t *buf); +int apr_deregister(void *handle); +void change_q6_state(int state); +void q6audio_dsp_not_responding(void); +void apr_reset(void *handle); +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp6v2/apr_tal.h b/arch/arm/mach-msm/include/mach/qdsp6v2/apr_tal.h new file mode 100644 index 00000000000..163ba7b7463 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/apr_tal.h @@ -0,0 +1,55 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __APR_TAL_H_ +#define __APR_TAL_H_ + +#include +#include +#include + +/* APR Client IDs */ +#define APR_CLIENT_AUDIO 0x0 +#define APR_CLIENT_VOICE 0x1 +#define APR_CLIENT_MAX 0x2 + +#define APR_DL_SMD 0 +#define APR_DL_MAX 1 + +#define APR_DEST_MODEM 0 +#define APR_DEST_QDSP6 1 +#define APR_DEST_MAX 2 + +#define APR_MAX_BUF 8192 + +#define APR_OPEN_TIMEOUT_MS 5000 + +typedef void (*apr_svc_cb_fn)(void *buf, int len, void *priv); +struct apr_svc_ch_dev *apr_tal_open(uint32_t svc, uint32_t dest, + uint32_t dl, apr_svc_cb_fn func, void *priv); +int apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data, int len); +int apr_tal_close(struct apr_svc_ch_dev *apr_ch); +struct apr_svc_ch_dev { + struct smd_channel *ch; + spinlock_t lock; + spinlock_t w_lock; + struct mutex m_lock; + apr_svc_cb_fn func; + char data[APR_MAX_BUF]; + wait_queue_head_t wait; + void *priv; + uint32_t smd_state; + wait_queue_head_t dest; + uint32_t dest_state; +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp6v2/audio_acdb.h b/arch/arm/mach-msm/include/mach/qdsp6v2/audio_acdb.h new file mode 100644 index 00000000000..477a12211f3 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/audio_acdb.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _AUDIO_ACDB_H +#define _AUDIO_ACDB_H + +#include +#include + +#define NUM_AUDPROC_BUFFERS 6 + +struct acdb_cal_block { + uint32_t cal_size; + uint32_t cal_kvaddr; + uint32_t cal_paddr; +}; + +struct acdb_cal_data { + uint32_t num_cal_blocks; + struct acdb_cal_block *cal_blocks; +}; + +struct audproc_buffer_data { + uint32_t buf_size[NUM_AUDPROC_BUFFERS]; + uint32_t phys_addr[NUM_AUDPROC_BUFFERS]; +}; + + +uint32_t get_voice_rx_topology(void); +uint32_t get_voice_tx_topology(void); +uint32_t get_adm_topology(void); +uint32_t get_asm_topology(void); +void get_all_voice_cal(struct acdb_cal_block *cal_block); +void get_all_cvp_cal(struct acdb_cal_block *cal_block); +void get_all_vocproc_cal(struct acdb_cal_block *cal_block); +void get_all_vocstrm_cal(struct acdb_cal_block *cal_block); +void get_all_vocvol_cal(struct acdb_cal_block *cal_block); +void get_anc_cal(struct acdb_cal_block *cal_block); +void get_audproc_buffer_data(struct audproc_buffer_data *cal_buffers); +void get_audproc_cal(int32_t path, struct acdb_cal_block *cal_block); +void get_audstrm_cal(int32_t path, struct acdb_cal_block *cal_block); +void get_audvol_cal(int32_t path, struct acdb_cal_block *cal_block); +void get_vocproc_cal(struct acdb_cal_data *cal_data); +void get_vocstrm_cal(struct acdb_cal_data *cal_data); +void get_vocvol_cal(struct acdb_cal_data *cal_data); +void get_sidetone_cal(struct sidetone_cal *cal_data); + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp6v2/audio_dev_ctl.h b/arch/arm/mach-msm/include/mach/qdsp6v2/audio_dev_ctl.h new file mode 100644 index 00000000000..5358209b000 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/audio_dev_ctl.h @@ -0,0 +1,216 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP6_V2_SNDDEV_H +#define __MACH_QDSP6_V2_SNDDEV_H +#include +#include + +#define AUDIO_DEV_CTL_MAX_DEV 64 +#define DIR_TX 2 +#define DIR_RX 1 + +#define DEVICE_IGNORE 0xffff +#define SESSION_IGNORE 0x0UL + +/* 8 concurrent sessions with Q6 possible, session:0 + reserved in DSP */ +#define MAX_SESSIONS 0x09 + +/* This represents Maximum bit needed for representing sessions + per clients, MAX_BIT_PER_CLIENT >= MAX_SESSIONS */ +#define MAX_BIT_PER_CLIENT 16 + +#define VOICE_STATE_INVALID 0x0 +#define VOICE_STATE_INCALL 0x1 +#define VOICE_STATE_OFFCALL 0x2 +#define ONE_TO_MANY 1 +#define MANY_TO_ONE 2 + +struct msm_snddev_info { + const char *name; + u32 capability; + u32 copp_id; + u32 acdb_id; + u32 dev_volume; + struct msm_snddev_ops { + int (*open)(struct msm_snddev_info *); + int (*close)(struct msm_snddev_info *); + int (*set_freq)(struct msm_snddev_info *, u32); + int (*enable_sidetone)(struct msm_snddev_info *, u32, uint16_t); + int (*set_device_volume)(struct msm_snddev_info *, u32); + int (*enable_anc)(struct msm_snddev_info *, u32); + } dev_ops; + u8 opened; + void *private_data; + bool state; + u32 sample_rate; + u32 channel_mode; + u32 set_sample_rate; + u64 sessions; + int usage_count; + s32 max_voc_rx_vol[VOC_RX_VOL_ARRAY_NUM]; /* [0] is for NB,[1] for WB */ + s32 min_voc_rx_vol[VOC_RX_VOL_ARRAY_NUM]; +}; + +struct msm_volume { + int volume; /* Volume parameter, in % Scale */ + int pan; +}; + +extern struct msm_volume msm_vol_ctl; + +void msm_snddev_register(struct msm_snddev_info *); +void msm_snddev_unregister(struct msm_snddev_info *); +int msm_snddev_devcount(void); +int msm_snddev_query(int dev_id); +unsigned short msm_snddev_route_dec(int popp_id); +unsigned short msm_snddev_route_enc(int enc_id); + +int msm_snddev_set_dec(int popp_id, int copp_id, int set, + int rate, int channel_mode); +int msm_snddev_set_enc(int popp_id, int copp_id, int set, + int rate, int channel_mode); + +int msm_snddev_is_set(int popp_id, int copp_id); +int msm_get_voc_route(u32 *rx_id, u32 *tx_id); +int msm_set_voc_route(struct msm_snddev_info *dev_info, int stream_type, + int dev_id); +int msm_snddev_enable_sidetone(u32 dev_id, u32 enable, uint16_t gain); + +int msm_set_copp_id(int session_id, int copp_id); + +int msm_clear_copp_id(int session_id, int copp_id); + +int msm_clear_session_id(int session_id); + +int msm_reset_all_device(void); + +int msm_clear_all_session(void); + +struct msm_snddev_info *audio_dev_ctrl_find_dev(u32 dev_id); + +void msm_release_voc_thread(void); + +int snddev_voice_set_volume(int vol, int path); + +struct auddev_evt_voc_devinfo { + u32 dev_type; /* Rx or Tx */ + u32 acdb_dev_id; /* acdb id of device */ + u32 dev_sample; /* Sample rate of device */ + s32 max_rx_vol[VOC_RX_VOL_ARRAY_NUM]; /* unit is mb (milibel), + [0] is for NB, other for WB */ + s32 min_rx_vol[VOC_RX_VOL_ARRAY_NUM]; /* unit is mb */ + u32 dev_id; /* registered device id */ + u32 dev_port_id; +}; + +struct auddev_evt_audcal_info { + u32 dev_id; + u32 acdb_id; + u32 sample_rate; + u32 dev_type; + u32 sessions; +}; + +union msm_vol_mute { + int vol; + bool mute; +}; + +struct auddev_evt_voc_mute_info { + u32 dev_type; + u32 acdb_dev_id; + union msm_vol_mute dev_vm_val; +}; + +struct auddev_evt_freq_info { + u32 dev_type; + u32 acdb_dev_id; + u32 sample_rate; +}; + +union auddev_evt_data { + struct auddev_evt_voc_devinfo voc_devinfo; + struct auddev_evt_voc_mute_info voc_vm_info; + struct auddev_evt_freq_info freq_info; + u32 routing_id; + s32 session_vol; + s32 voice_state; + struct auddev_evt_audcal_info audcal_info; +}; + +struct message_header { + uint32_t id; + uint32_t data_len; +}; + +#define AUDDEV_EVT_DEV_CHG_VOICE 0x01 /* device change event */ +#define AUDDEV_EVT_DEV_RDY 0x02 /* device ready event */ +#define AUDDEV_EVT_DEV_RLS 0x04 /* device released event */ +#define AUDDEV_EVT_REL_PENDING 0x08 /* device release pending */ +#define AUDDEV_EVT_DEVICE_VOL_MUTE_CHG 0x10 /* device volume changed */ +#define AUDDEV_EVT_START_VOICE 0x20 /* voice call start */ +#define AUDDEV_EVT_END_VOICE 0x40 /* voice call end */ +#define AUDDEV_EVT_STREAM_VOL_CHG 0x80 /* device volume changed */ +#define AUDDEV_EVT_FREQ_CHG 0x100 /* Change in freq */ +#define AUDDEV_EVT_VOICE_STATE_CHG 0x200 /* Change in voice state */ + +#define AUDDEV_CLNT_VOC 0x1 /*Vocoder clients*/ +#define AUDDEV_CLNT_DEC 0x2 /*Decoder clients*/ +#define AUDDEV_CLNT_ENC 0x3 /* Encoder clients */ +#define AUDDEV_CLNT_AUDIOCAL 0x4 /* AudioCalibration client */ + +#define AUDIO_DEV_CTL_MAX_LISTNER 20 /* Max Listeners Supported */ + +struct msm_snd_evt_listner { + uint32_t evt_id; + uint32_t clnt_type; + uint32_t clnt_id; + void *private_data; + void (*auddev_evt_listener)(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data); + struct msm_snd_evt_listner *cb_next; + struct msm_snd_evt_listner *cb_prev; +}; + +struct event_listner { + struct msm_snd_evt_listner *cb; + u32 num_listner; + int state; /* Call state */ /* TODO remove this if not req*/ +}; + +extern struct event_listner event; +int auddev_register_evt_listner(u32 evt_id, u32 clnt_type, u32 clnt_id, + void (*listner)(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data), + void *private_data); +int auddev_unregister_evt_listner(u32 clnt_type, u32 clnt_id); +void mixer_post_event(u32 evt_id, u32 dev_id); +void broadcast_event(u32 evt_id, u32 dev_id, u64 session_id); +int auddev_cfg_tx_copp_topology(int session_id, int cfg); +int msm_snddev_request_freq(int *freq, u32 session_id, + u32 capability, u32 clnt_type); +int msm_snddev_withdraw_freq(u32 session_id, + u32 capability, u32 clnt_type); +int msm_device_is_voice(int dev_id); +int msm_get_voc_freq(int *tx_freq, int *rx_freq); +int msm_snddev_get_enc_freq(int session_id); +int msm_set_voice_vol(int dir, s32 volume); +int msm_set_voice_mute(int dir, int mute); +int msm_get_voice_state(void); +int msm_enable_incall_recording(int popp_id, int rec_mode, int rate, + int channel_mode); +int msm_disable_incall_recording(uint32_t popp_id, uint32_t rec_mode); +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp6v2/dsp_debug.h b/arch/arm/mach-msm/include/mach/qdsp6v2/dsp_debug.h new file mode 100644 index 00000000000..94f4ab46936 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/dsp_debug.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __DSP_DEBUG_H_ +#define __DSP_DEBUG_H_ + +typedef int (*dsp_state_cb)(int state); +int dsp_debug_register(dsp_state_cb ptr); + +#define DSP_STATE_CRASHED 0x0 +#define DSP_STATE_CRASH_DUMP_DONE 0x1 + +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp6v2/q6voice.h b/arch/arm/mach-msm/include/mach/qdsp6v2/q6voice.h new file mode 100644 index 00000000000..dfbb372c608 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/q6voice.h @@ -0,0 +1,752 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __QDSP6VOICE_H__ +#define __QDSP6VOICE_H__ + +#include + +/* Device Event */ +#define DEV_CHANGE_READY 0x1 + +#define VOICE_CALL_START 0x1 +#define VOICE_CALL_END 0 + +#define VOICE_DEV_ENABLED 0x1 +#define VOICE_DEV_DISABLED 0 + +#define MAX_VOC_PKT_SIZE 322 + +#define SESSION_NAME_LEN 20 + +struct voice_header { + uint32_t id; + uint32_t data_len; +}; + +struct voice_init { + struct voice_header hdr; + void *cb_handle; +}; + + +/* Device information payload structure */ + +struct device_data { + uint32_t dev_acdb_id; + uint32_t volume; /* in percentage */ + uint32_t mute; + uint32_t sample; + uint32_t enabled; + uint32_t dev_id; + uint32_t dev_port_id; +}; + +enum { + VOC_INIT = 0, + VOC_RUN, + VOC_CHANGE, + VOC_RELEASE, +}; + +/* TO MVM commands */ +#define VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION 0x000110FF +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION 0x000110FE +/* Create a new full control MVM session. */ + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IMVM_CMD_ATTACH_STREAM 0x0001123C +/* Attach a stream to the MVM. */ + +#define VSS_IMVM_CMD_DETACH_STREAM 0x0001123D +/* Detach a stream from the MVM. */ + +#define VSS_IMVM_CMD_ATTACH_VOCPROC 0x0001123E +/* Attach a vocproc to the MVM. The MVM will symmetrically connect this vocproc + * to all the streams currently attached to it. + */ + +#define VSS_IMVM_CMD_DETACH_VOCPROC 0x0001123F +/* Detach a vocproc from the MVM. The MVM will symmetrically disconnect this + * vocproc from all the streams to which it is currently attached. + */ + +#define VSS_IMVM_CMD_START_VOICE 0x00011190 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IMVM_CMD_STOP_VOICE 0x00011192 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ISTREAM_CMD_ATTACH_VOCPROC 0x000110F8 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ISTREAM_CMD_DETACH_VOCPROC 0x000110F9 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + + +#define VSS_ISTREAM_CMD_SET_TTY_MODE 0x00011196 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ICOMMON_CMD_SET_NETWORK 0x0001119C +/* Set the network type. */ + +#define VSS_ICOMMON_CMD_SET_VOICE_TIMING 0x000111E0 +/* Set the voice timing parameters. */ + +struct vss_imvm_cmd_create_control_session_t { + char name[SESSION_NAME_LEN]; + /* + * A variable-sized stream name. + * + * The stream name size is the payload size minus the size of the other + * fields. + */ +} __packed; + +struct vss_istream_cmd_set_tty_mode_t { + uint32_t mode; + /**< + * TTY mode. + * + * 0 : TTY disabled + * 1 : HCO + * 2 : VCO + * 3 : FULL + */ +} __attribute__((packed)); + +struct vss_istream_cmd_attach_vocproc_t { + uint16_t handle; + /**< Handle of vocproc being attached. */ +} __attribute__((packed)); + +struct vss_istream_cmd_detach_vocproc_t { + uint16_t handle; + /**< Handle of vocproc being detached. */ +} __attribute__((packed)); + +struct vss_imvm_cmd_attach_stream_t { + uint16_t handle; + /* The stream handle to attach. */ +} __attribute__((packed)); + +struct vss_imvm_cmd_detach_stream_t { + uint16_t handle; + /* The stream handle to detach. */ +} __attribute__((packed)); + +struct vss_icommon_cmd_set_network_t { + uint32_t network_id; + /* Network ID. (Refer to VSS_NETWORK_ID_XXX). */ +} __attribute__((packed)); + +struct vss_icommon_cmd_set_voice_timing_t { + uint16_t mode; + /* + * The vocoder frame synchronization mode. + * + * 0 : No frame sync. + * 1 : Hard VFR (20ms Vocoder Frame Reference interrupt). + */ + uint16_t enc_offset; + /* + * The offset in microseconds from the VFR to deliver a Tx vocoder + * packet. The offset should be less than 20000us. + */ + uint16_t dec_req_offset; + /* + * The offset in microseconds from the VFR to request for an Rx vocoder + * packet. The offset should be less than 20000us. + */ + uint16_t dec_offset; + /* + * The offset in microseconds from the VFR to indicate the deadline to + * receive an Rx vocoder packet. The offset should be less than 20000us. + * Rx vocoder packets received after this deadline are not guaranteed to + * be processed. + */ +} __attribute__((packed)); + +struct mvm_attach_vocproc_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_attach_vocproc_t mvm_attach_cvp_handle; +} __attribute__((packed)); + +struct mvm_detach_vocproc_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_detach_vocproc_t mvm_detach_cvp_handle; +} __attribute__((packed)); + +struct mvm_create_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_create_control_session_t mvm_session; +} __packed; + +struct mvm_set_tty_mode_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_tty_mode_t tty_mode; +} __attribute__((packed)); + +struct mvm_attach_stream_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_attach_stream_t attach_stream; +} __attribute__((packed)); + +struct mvm_detach_stream_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_detach_stream_t detach_stream; +} __attribute__((packed)); + +struct mvm_set_network_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_set_network_t network; +} __attribute__((packed)); + +struct mvm_set_voice_timing_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_set_voice_timing_t timing; +} __attribute__((packed)); + +/* TO CVS commands */ +#define VSS_ISTREAM_CMD_CREATE_PASSIVE_CONTROL_SESSION 0x00011140 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ISTREAM_CMD_CREATE_FULL_CONTROL_SESSION 0x000110F7 +/* Create a new full control stream session. */ + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C + +#define VSS_ISTREAM_CMD_CACHE_CALIBRATION_DATA 0x000110FB + +#define VSS_ISTREAM_CMD_SET_MUTE 0x00011022 + +#define VSS_ISTREAM_CMD_SET_MEDIA_TYPE 0x00011186 +/* Set media type on the stream. */ + +#define VSS_ISTREAM_EVT_SEND_ENC_BUFFER 0x00011015 +/* Event sent by the stream to its client to provide an encoded packet. */ + +#define VSS_ISTREAM_EVT_REQUEST_DEC_BUFFER 0x00011017 +/* Event sent by the stream to its client requesting for a decoder packet. + * The client should respond with a VSS_ISTREAM_EVT_SEND_DEC_BUFFER event. + */ + +#define VSS_ISTREAM_EVT_SEND_DEC_BUFFER 0x00011016 +/* Event sent by the client to the stream in response to a + * VSS_ISTREAM_EVT_REQUEST_DEC_BUFFER event, providing a decoder packet. + */ + +#define VSS_ISTREAM_CMD_VOC_AMR_SET_ENC_RATE 0x0001113E +/* Set AMR encoder rate. */ + +#define VSS_ISTREAM_CMD_VOC_AMRWB_SET_ENC_RATE 0x0001113F +/* Set AMR-WB encoder rate. */ + +#define VSS_ISTREAM_CMD_CDMA_SET_ENC_MINMAX_RATE 0x00011019 +/* Set encoder minimum and maximum rate. */ + +#define VSS_ISTREAM_CMD_SET_ENC_DTX_MODE 0x0001101D +/* Set encoder DTX mode. */ + +#define VSS_ISTREAM_CMD_START_RECORD 0x00011236 +/* Start in-call conversation recording. */ + +#define VSS_ISTREAM_CMD_STOP_RECORD 0x00011237 +/* Stop in-call conversation recording. */ + +#define VSS_ISTREAM_CMD_START_PLAYBACK 0x00011238 +/* Start in-call music delivery on the Tx voice path. */ + +#define VSS_ISTREAM_CMD_STOP_PLAYBACK 0x00011239 +/* Stop the in-call music delivery on the Tx voice path. */ + +struct vss_istream_cmd_create_passive_control_session_t { + char name[SESSION_NAME_LEN]; + /**< + * A variable-sized stream name. + * + * The stream name size is the payload size minus the size of the other + * fields. + */ +} __attribute__((packed)); + +struct vss_istream_cmd_set_mute_t { + uint16_t direction; + /**< + * 0 : TX only + * 1 : RX only + * 2 : TX and Rx + */ + uint16_t mute_flag; + /**< + * Mute, un-mute. + * + * 0 : Silence disable + * 1 : Silence enable + * 2 : CNG enable. Applicable to TX only. If set on RX behavior + * will be the same as 1 + */ +} __attribute__((packed)); + +struct vss_istream_cmd_create_full_control_session_t { + uint16_t direction; + /* + * Stream direction. + * + * 0 : TX only + * 1 : RX only + * 2 : TX and RX + * 3 : TX and RX loopback + */ + uint32_t enc_media_type; + /* Tx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ + uint32_t dec_media_type; + /* Rx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ + uint32_t network_id; + /* Network ID. (Refer to VSS_NETWORK_ID_XXX). */ + char name[SESSION_NAME_LEN]; + /* + * A variable-sized stream name. + * + * The stream name size is the payload size minus the size of the other + * fields. + */ +} __attribute__((packed)); + +struct vss_istream_cmd_set_media_type_t { + uint32_t rx_media_id; + /* Set the Rx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ + uint32_t tx_media_id; + /* Set the Tx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ +} __attribute__((packed)); + +struct vss_istream_evt_send_enc_buffer_t { + uint32_t media_id; + /* Media ID of the packet. */ + uint8_t packet_data[MAX_VOC_PKT_SIZE]; + /* Packet data buffer. */ +} __attribute__((packed)); + +struct vss_istream_evt_send_dec_buffer_t { + uint32_t media_id; + /* Media ID of the packet. */ + uint8_t packet_data[MAX_VOC_PKT_SIZE]; + /* Packet data. */ +} __attribute__((packed)); + +struct vss_istream_cmd_voc_amr_set_enc_rate_t { + uint32_t mode; + /* Set the AMR encoder rate. + * + * 0x00000000 : 4.75 kbps + * 0x00000001 : 5.15 kbps + * 0x00000002 : 5.90 kbps + * 0x00000003 : 6.70 kbps + * 0x00000004 : 7.40 kbps + * 0x00000005 : 7.95 kbps + * 0x00000006 : 10.2 kbps + * 0x00000007 : 12.2 kbps + */ +} __attribute__((packed)); + +struct vss_istream_cmd_voc_amrwb_set_enc_rate_t { + uint32_t mode; + /* Set the AMR-WB encoder rate. + * + * 0x00000000 : 6.60 kbps + * 0x00000001 : 8.85 kbps + * 0x00000002 : 12.65 kbps + * 0x00000003 : 14.25 kbps + * 0x00000004 : 15.85 kbps + * 0x00000005 : 18.25 kbps + * 0x00000006 : 19.85 kbps + * 0x00000007 : 23.05 kbps + * 0x00000008 : 23.85 kbps + */ +} __attribute__((packed)); + +struct vss_istream_cmd_cdma_set_enc_minmax_rate_t { + uint16_t min_rate; + /* Set the lower bound encoder rate. + * + * 0x0000 : Blank frame + * 0x0001 : Eighth rate + * 0x0002 : Quarter rate + * 0x0003 : Half rate + * 0x0004 : Full rate + */ + uint16_t max_rate; + /* Set the upper bound encoder rate. + * + * 0x0000 : Blank frame + * 0x0001 : Eighth rate + * 0x0002 : Quarter rate + * 0x0003 : Half rate + * 0x0004 : Full rate + */ +} __attribute__((packed)); + +struct vss_istream_cmd_set_enc_dtx_mode_t { + uint32_t enable; + /* Toggle DTX on or off. + * + * 0 : Disables DTX + * 1 : Enables DTX + */ +} __attribute__((packed)); + +#define VSS_TAP_POINT_NONE 0x00010F78 +/* Indicates no tapping for specified path. */ + +#define VSS_TAP_POINT_STREAM_END 0x00010F79 +/* Indicates that specified path should be tapped at the end of the stream. */ + +struct vss_istream_cmd_start_record_t { + uint32_t rx_tap_point; + /* Tap point to use on the Rx path. Supported values are: + * VSS_TAP_POINT_NONE : Do not record Rx path. + * VSS_TAP_POINT_STREAM_END : Rx tap point is at the end of the stream. + */ + uint32_t tx_tap_point; + /* Tap point to use on the Tx path. Supported values are: + * VSS_TAP_POINT_NONE : Do not record tx path. + * VSS_TAP_POINT_STREAM_END : Tx tap point is at the end of the stream. + */ +} __attribute__((packed)); + +struct cvs_create_passive_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_create_passive_control_session_t cvs_session; +} __attribute__((packed)); + +struct cvs_create_full_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_create_full_control_session_t cvs_session; +} __attribute__((packed)); + +struct cvs_destroy_session_cmd { + struct apr_hdr hdr; +} __attribute__((packed)); + +struct cvs_cache_calibration_data_cmd { + struct apr_hdr hdr; +} __attribute__ ((packed)); + +struct cvs_set_mute_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_mute_t cvs_set_mute; +} __attribute__((packed)); + +struct cvs_set_media_type_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_media_type_t media_type; +} __attribute__((packed)); + +struct cvs_send_dec_buf_cmd { + struct apr_hdr hdr; + struct vss_istream_evt_send_dec_buffer_t dec_buf; +} __attribute__((packed)); + +struct cvs_set_amr_enc_rate_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_voc_amr_set_enc_rate_t amr_rate; +} __attribute__((packed)); + +struct cvs_set_amrwb_enc_rate_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_voc_amrwb_set_enc_rate_t amrwb_rate; +} __attribute__((packed)); + +struct cvs_set_cdma_enc_minmax_rate_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_cdma_set_enc_minmax_rate_t cdma_rate; +} __attribute__((packed)); + +struct cvs_set_enc_dtx_mode_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_enc_dtx_mode_t dtx_mode; +} __attribute__((packed)); + +struct cvs_start_record_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_start_record_t rec_mode; +} __attribute__((packed)); + +/* TO CVP commands */ + +#define VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION 0x000100C3 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C + +#define VSS_IVOCPROC_CMD_SET_DEVICE 0x000100C4 + +#define VSS_IVOCPROC_CMD_CACHE_CALIBRATION_DATA 0x000110E3 + +#define VSS_IVOCPROC_CMD_CACHE_VOLUME_CALIBRATION_TABLE 0x000110E4 + +#define VSS_IVOCPROC_CMD_SET_VP3_DATA 0x000110EB + +#define VSS_IVOCPROC_CMD_SET_RX_VOLUME_INDEX 0x000110EE + +#define VSS_IVOCPROC_CMD_ENABLE 0x000100C6 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IVOCPROC_CMD_DISABLE 0x000110E1 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IVOCPROC_TOPOLOGY_ID_NONE 0x00010F70 +#define VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS 0x00010F71 +#define VSS_IVOCPROC_TOPOLOGY_ID_TX_DM_FLUENCE 0x00010F72 + +#define VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT 0x00010F77 + +/* Newtwork IDs */ +#define VSS_NETWORK_ID_DEFAULT 0x00010037 +#define VSS_NETWORK_ID_VOIP_NB 0x00011240 +#define VSS_NETWORK_ID_VOIP_WB 0x00011241 +#define VSS_NETWORK_ID_VOIP_WV 0x00011242 + +/* Media types */ +#define VSS_MEDIA_ID_EVRC_MODEM 0x00010FC2 +/* 80-VF690-47 CDMA enhanced variable rate vocoder modem format. */ +#define VSS_MEDIA_ID_AMR_NB_MODEM 0x00010FC6 +/* 80-VF690-47 UMTS AMR-NB vocoder modem format. */ +#define VSS_MEDIA_ID_AMR_WB_MODEM 0x00010FC7 +/* 80-VF690-47 UMTS AMR-WB vocoder modem format. */ +#define VSS_MEDIA_ID_PCM_NB 0x00010FCB +/* Linear PCM (16-bit, little-endian). */ +#define VSS_MEDIA_ID_G711_ALAW 0x00010FCD +/* G.711 a-law (contains two 10ms vocoder frames). */ +#define VSS_MEDIA_ID_G711_MULAW 0x00010FCE +/* G.711 mu-law (contains two 10ms vocoder frames). */ +#define VSS_MEDIA_ID_G729 0x00010FD0 +/* G.729AB (contains two 10ms vocoder frames. */ + +#define VOICE_CMD_SET_PARAM 0x00011006 +#define VOICE_CMD_GET_PARAM 0x00011007 +#define VOICE_EVT_GET_PARAM_ACK 0x00011008 + +struct vss_ivocproc_cmd_create_full_control_session_t { + uint16_t direction; + /* + * stream direction. + * 0 : TX only + * 1 : RX only + * 2 : TX and RX + */ + uint32_t tx_port_id; + /* + * TX device port ID which vocproc will connect to. If not supplying a + * port ID set to VSS_IVOCPROC_PORT_ID_NONE. + */ + uint32_t tx_topology_id; + /* + * Tx leg topology ID. If not supplying a topology ID set to + * VSS_IVOCPROC_TOPOLOGY_ID_NONE. + */ + uint32_t rx_port_id; + /* + * RX device port ID which vocproc will connect to. If not supplying a + * port ID set to VSS_IVOCPROC_PORT_ID_NONE. + */ + uint32_t rx_topology_id; + /* + * Rx leg topology ID. If not supplying a topology ID set to + * VSS_IVOCPROC_TOPOLOGY_ID_NONE. + */ + int32_t network_id; + /* + * Network ID. (Refer to VSS_NETWORK_ID_XXX). If not supplying a network + * ID set to VSS_NETWORK_ID_DEFAULT. + */ +} __attribute__((packed)); + +struct vss_ivocproc_cmd_set_device_t { + uint32_t tx_port_id; + /**< + * TX device port ID which vocproc will connect to. + * VSS_IVOCPROC_PORT_ID_NONE means vocproc will not connect to any port. + */ + uint32_t tx_topology_id; + /**< + * TX leg topology ID. + * VSS_IVOCPROC_TOPOLOGY_ID_NONE means vocproc does not contain any + * pre/post-processing blocks and is pass-through. + */ + int32_t rx_port_id; + /**< + * RX device port ID which vocproc will connect to. + * VSS_IVOCPROC_PORT_ID_NONE means vocproc will not connect to any port. + */ + uint32_t rx_topology_id; + /**< + * RX leg topology ID. + * VSS_IVOCPROC_TOPOLOGY_ID_NONE means vocproc does not contain any + * pre/post-processing blocks and is pass-through. + */ +} __attribute__((packed)); + +struct vss_ivocproc_cmd_set_volume_index_t { + uint16_t vol_index; + /**< + * Volume index utilized by the vocproc to index into the volume table + * provided in VSS_IVOCPROC_CMD_CACHE_VOLUME_CALIBRATION_TABLE and set + * volume on the VDSP. + */ +} __attribute__((packed)); + +struct cvp_create_full_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_create_full_control_session_t cvp_session; +} __attribute__ ((packed)); + +struct cvp_command { + struct apr_hdr hdr; +} __attribute__((packed)); + +struct cvp_set_device_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_set_device_t cvp_set_device; +} __attribute__ ((packed)); + +struct cvp_cache_calibration_data_cmd { + struct apr_hdr hdr; +} __attribute__((packed)); + +struct cvp_cache_volume_calibration_table_cmd { + struct apr_hdr hdr; +} __attribute__((packed)); + +struct cvp_set_vp3_data_cmd { + struct apr_hdr hdr; +} __attribute__((packed)); + +struct cvp_set_rx_volume_index_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_set_volume_index_t cvp_set_vol_idx; +} __attribute__((packed)); + +/* CB for up-link packets. */ +typedef void (*ul_cb_fn)(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data); + +/* CB for down-link packets. */ +typedef void (*dl_cb_fn)(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data); + + +struct mvs_driver_info { + uint32_t media_type; + uint32_t rate; + uint32_t network_type; + uint32_t dtx_mode; + ul_cb_fn ul_cb; + dl_cb_fn dl_cb; + void *private_data; +}; + +struct incall_rec_info { + uint32_t pending; + uint32_t rec_mode; +}; + +struct incall_music_info { + uint32_t pending; + uint32_t playing; +}; + +struct voice_data { + int voc_state;/*INIT, CHANGE, RELEASE, RUN */ + uint32_t voc_path; + uint32_t adsp_version; + + wait_queue_head_t mvm_wait; + wait_queue_head_t cvs_wait; + wait_queue_head_t cvp_wait; + + uint32_t device_events; + + /* cache the values related to Rx and Tx */ + struct device_data dev_rx; + struct device_data dev_tx; + + /* these default values are for all devices */ + uint32_t default_mute_val; + uint32_t default_vol_val; + uint32_t default_sample_val; + + /* call status */ + int v_call_status; /* Start or End */ + + /* APR to MVM in the modem */ + void *apr_mvm; + /* APR to CVS in the modem */ + void *apr_cvs; + /* APR to CVP in the modem */ + void *apr_cvp; + + /* APR to MVM in the Q6 */ + void *apr_q6_mvm; + /* APR to CVS in the Q6 */ + void *apr_q6_cvs; + /* APR to CVP in the Q6 */ + void *apr_q6_cvp; + + u32 mvm_state; + u32 cvs_state; + u32 cvp_state; + + /* Handle to MVM in the modem */ + u16 mvm_handle; + /* Handle to CVS in the modem */ + u16 cvs_handle; + /* Handle to CVP in the modem */ + u16 cvp_handle; + + /* Handle to MVM in the Q6 */ + u16 mvm_q6_handle; + /* Handle to CVS in the Q6 */ + u16 cvs_q6_handle; + /* Handle to CVP in the Q6 */ + u16 cvp_q6_handle; + + struct mutex lock; + + struct mvs_driver_info mvs_info; + + struct incall_rec_info rec_info; + + struct incall_music_info music_info; +}; + +int voice_set_voc_path_full(uint32_t set); + +void voice_register_mvs_cb(ul_cb_fn ul_cb, + dl_cb_fn dl_cb, + void *private_data); + +void voice_config_vocoder(uint32_t media_type, + uint32_t rate, + uint32_t network_type, + uint32_t dtx_mode); + +int voice_start_record(uint32_t rec_mode, uint32_t set); + +int voice_start_playback(uint32_t set); +#endif diff --git a/arch/arm/mach-msm/include/mach/qdsp6v2/rtac.h b/arch/arm/mach-msm/include/mach/qdsp6v2/rtac.h new file mode 100644 index 00000000000..d321607ba2c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/qdsp6v2/rtac.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __RTAC_H__ +#define __RTAC_H__ + +#ifdef CONFIG_MSM8X60_RTAC + +#include +#include + +/* Voice Modes */ +#define RTAC_CVP 0 +#define RTAC_CVS 1 +#define RTAC_VOICE_MODES 2 + +void update_rtac(u32 evt_id, u32 dev_id, struct msm_snddev_info *dev_info); +void rtac_add_adm_device(u32 port_id, u32 popp_id); +void rtac_remove_adm_device(u32 port_id); +void rtac_add_voice(struct voice_data *v); +void rtac_remove_voice(struct voice_data *v); +void rtac_set_adm_handle(void *handle); +bool rtac_make_adm_callback(uint32_t *payload, u32 payload_size); +void rtac_copy_adm_payload_to_user(void *payload, u32 payload_size); +void rtac_set_asm_handle(u32 session_id, void *handle); +bool rtac_make_asm_callback(u32 session_id, uint32_t *payload, + u32 payload_size); +void rtac_copy_asm_payload_to_user(void *payload, u32 payload_size); +void rtac_set_voice_handle(u32 mode, void *handle); +bool rtac_make_voice_callback(u32 mode, uint32_t *payload, u32 payload_size); +void rtac_copy_voice_payload_to_user(void *payload, u32 payload_size); + +#endif + +#endif + diff --git a/arch/arm/mach-msm/include/mach/remote_spinlock.h b/arch/arm/mach-msm/include/mach/remote_spinlock.h new file mode 100644 index 00000000000..fa889b64bd1 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/remote_spinlock.h @@ -0,0 +1,235 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Part of this this code is based on the standard ARM spinlock + * implementation (asm/spinlock.h) found in the 2.6.29 kernel. + */ + +#ifndef __ASM__ARCH_QC_REMOTE_SPINLOCK_H +#define __ASM__ARCH_QC_REMOTE_SPINLOCK_H + +#include + +/* Remote spinlock definitions. */ + +struct dek_spinlock { + volatile uint8_t self_lock; + volatile uint8_t other_lock; + volatile uint8_t next_yield; + uint8_t pad; +}; + +typedef union { + volatile uint32_t lock; + struct dek_spinlock dek; +} raw_remote_spinlock_t; + +typedef raw_remote_spinlock_t *_remote_spinlock_t; + +#define remote_spinlock_id_t const char * + +static inline void __raw_remote_ex_spin_lock(raw_remote_spinlock_t *lock) +{ + unsigned long tmp; + + __asm__ __volatile__( +"1: ldrex %0, [%1]\n" +" teq %0, #0\n" +" strexeq %0, %2, [%1]\n" +" teqeq %0, #0\n" +" bne 1b" + : "=&r" (tmp) + : "r" (&lock->lock), "r" (1) + : "cc"); + + smp_mb(); +} + +static inline int __raw_remote_ex_spin_trylock(raw_remote_spinlock_t *lock) +{ + unsigned long tmp; + + __asm__ __volatile__( +" ldrex %0, [%1]\n" +" teq %0, #0\n" +" strexeq %0, %2, [%1]\n" + : "=&r" (tmp) + : "r" (&lock->lock), "r" (1) + : "cc"); + + if (tmp == 0) { + smp_mb(); + return 1; + } + return 0; +} + +static inline void __raw_remote_ex_spin_unlock(raw_remote_spinlock_t *lock) +{ + smp_mb(); + + __asm__ __volatile__( +" str %1, [%0]\n" + : + : "r" (&lock->lock), "r" (0) + : "cc"); +} + +static inline void __raw_remote_swp_spin_lock(raw_remote_spinlock_t *lock) +{ + unsigned long tmp; + + __asm__ __volatile__( +"1: swp %0, %2, [%1]\n" +" teq %0, #0\n" +" bne 1b" + : "=&r" (tmp) + : "r" (&lock->lock), "r" (1) + : "cc"); + + smp_mb(); +} + +static inline int __raw_remote_swp_spin_trylock(raw_remote_spinlock_t *lock) +{ + unsigned long tmp; + + __asm__ __volatile__( +" swp %0, %2, [%1]\n" + : "=&r" (tmp) + : "r" (&lock->lock), "r" (1) + : "cc"); + + if (tmp == 0) { + smp_mb(); + return 1; + } + return 0; +} + +static inline void __raw_remote_swp_spin_unlock(raw_remote_spinlock_t *lock) +{ + smp_mb(); + + __asm__ __volatile__( +" str %1, [%0]" + : + : "r" (&lock->lock), "r" (0) + : "cc"); +} + +#define DEK_LOCK_REQUEST 1 +#define DEK_LOCK_YIELD (!DEK_LOCK_REQUEST) +#define DEK_YIELD_TURN_SELF 0 +static inline void __raw_remote_dek_spin_lock(raw_remote_spinlock_t *lock) +{ + lock->dek.self_lock = DEK_LOCK_REQUEST; + + while (lock->dek.other_lock) { + + if (lock->dek.next_yield == DEK_YIELD_TURN_SELF) + lock->dek.self_lock = DEK_LOCK_YIELD; + + while (lock->dek.other_lock) + ; + + lock->dek.self_lock = DEK_LOCK_REQUEST; + } + lock->dek.next_yield = DEK_YIELD_TURN_SELF; + + smp_mb(); +} + +static inline int __raw_remote_dek_spin_trylock(raw_remote_spinlock_t *lock) +{ + lock->dek.self_lock = DEK_LOCK_REQUEST; + + if (lock->dek.other_lock) { + lock->dek.self_lock = DEK_LOCK_YIELD; + return 0; + } + + lock->dek.next_yield = DEK_YIELD_TURN_SELF; + + smp_mb(); + return 1; +} + +static inline void __raw_remote_dek_spin_unlock(raw_remote_spinlock_t *lock) +{ + smp_mb(); + + lock->dek.self_lock = DEK_LOCK_YIELD; +} + +#ifdef CONFIG_MSM_SMD +int _remote_spin_lock_init(remote_spinlock_id_t, _remote_spinlock_t *lock); +#else +static inline +int _remote_spin_lock_init(remote_spinlock_id_t id, _remote_spinlock_t *lock) +{ + return -EINVAL; +} +#endif + +#if defined(CONFIG_MSM_REMOTE_SPINLOCK_DEKKERS) +/* Use Dekker's algorithm when LDREX/STREX and SWP are unavailable for + * shared memory */ +#define _remote_spin_lock(lock) __raw_remote_dek_spin_lock(*lock) +#define _remote_spin_unlock(lock) __raw_remote_dek_spin_unlock(*lock) +#define _remote_spin_trylock(lock) __raw_remote_dek_spin_trylock(*lock) +#elif defined(CONFIG_MSM_REMOTE_SPINLOCK_SWP) +/* Use SWP-based locks when LDREX/STREX are unavailable for shared memory. */ +#define _remote_spin_lock(lock) __raw_remote_swp_spin_lock(*lock) +#define _remote_spin_unlock(lock) __raw_remote_swp_spin_unlock(*lock) +#define _remote_spin_trylock(lock) __raw_remote_swp_spin_trylock(*lock) +#else +/* Use LDREX/STREX for shared memory locking, when available */ +#define _remote_spin_lock(lock) __raw_remote_ex_spin_lock(*lock) +#define _remote_spin_unlock(lock) __raw_remote_ex_spin_unlock(*lock) +#define _remote_spin_trylock(lock) __raw_remote_ex_spin_trylock(*lock) +#endif + +/* Remote mutex definitions. */ + +typedef struct { + _remote_spinlock_t r_spinlock; + uint32_t delay_us; +} _remote_mutex_t; + +struct remote_mutex_id { + remote_spinlock_id_t r_spinlock_id; + uint32_t delay_us; +}; + +#ifdef CONFIG_MSM_SMD +int _remote_mutex_init(struct remote_mutex_id *id, _remote_mutex_t *lock); +void _remote_mutex_lock(_remote_mutex_t *lock); +void _remote_mutex_unlock(_remote_mutex_t *lock); +int _remote_mutex_trylock(_remote_mutex_t *lock); +#else +static inline +int _remote_mutex_init(struct remote_mutex_id *id, _remote_mutex_t *lock) +{ + return -EINVAL; +} +static inline void _remote_mutex_lock(_remote_mutex_t *lock) {} +static inline void _remote_mutex_unlock(_remote_mutex_t *lock) {} +static inline int _remote_mutex_trylock(_remote_mutex_t *lock) +{ + return 0; +} +#endif + +#endif /* __ASM__ARCH_QC_REMOTE_SPINLOCK_H */ diff --git a/arch/arm/mach-msm/include/mach/restart.h b/arch/arm/mach-msm/include/mach/restart.h new file mode 100644 index 00000000000..72ce29b47d4 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/restart.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ASM_ARCH_MSM_RESTART_H_ +#define _ASM_ARCH_MSM_RESTART_H_ + +#define RESTART_NORMAL 0x0 +#define RESTART_DLOAD 0x1 + +#if defined(CONFIG_ARCH_MSM8X60) || defined(CONFIG_ARCH_MSM8960) +void msm_set_restart_mode(int mode); +#else +#define msm_set_restart_mode(mode) +#endif + +#endif + diff --git a/arch/arm/mach-msm/include/mach/rpc_hsusb.h b/arch/arm/mach-msm/include/mach/rpc_hsusb.h new file mode 100644 index 00000000000..225523712c0 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpc_hsusb.h @@ -0,0 +1,99 @@ +/* linux/include/mach/rpc_hsusb.h + * + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#ifndef __ASM_ARCH_MSM_RPC_HSUSB_H +#define __ASM_ARCH_MSM_RPC_HSUSB_H + +#include +#include +#include + +#if defined(CONFIG_MSM_ONCRPCROUTER) && !defined(CONFIG_ARCH_MSM8X60) +int msm_hsusb_rpc_connect(void); +int msm_hsusb_phy_reset(void); +int msm_hsusb_vbus_powerup(void); +int msm_hsusb_vbus_shutdown(void); +int msm_hsusb_reset_rework_installed(void); +int msm_hsusb_enable_pmic_ulpidata0(void); +int msm_hsusb_disable_pmic_ulpidata0(void); +int msm_hsusb_rpc_close(void); + +int msm_chg_rpc_connect(void); +int msm_chg_usb_charger_connected(uint32_t type); +int msm_chg_usb_i_is_available(uint32_t sample); +int msm_chg_usb_i_is_not_available(void); +int msm_chg_usb_charger_disconnected(void); +int msm_chg_rpc_close(void); + +#ifdef CONFIG_USB_GADGET_MSM_72K +int hsusb_chg_init(int connect); +void hsusb_chg_vbus_draw(unsigned mA); +void hsusb_chg_connected(enum chg_type chgtype); +#endif + + +int msm_fsusb_rpc_init(struct msm_otg_ops *ops); +int msm_fsusb_init_phy(void); +int msm_fsusb_reset_phy(void); +int msm_fsusb_suspend_phy(void); +int msm_fsusb_resume_phy(void); +int msm_fsusb_rpc_close(void); +int msm_fsusb_remote_dev_disconnected(void); +int msm_fsusb_set_remote_wakeup(void); +void msm_fsusb_rpc_deinit(void); + +/* wrapper to send pid and serial# info to bootloader */ +int usb_diag_update_pid_and_serial_num(uint32_t pid, const char *snum); +#else +static inline int msm_hsusb_rpc_connect(void) { return 0; } +static inline int msm_hsusb_phy_reset(void) { return 0; } +static inline int msm_hsusb_vbus_powerup(void) { return 0; } +static inline int msm_hsusb_vbus_shutdown(void) { return 0; } +static inline int msm_hsusb_reset_rework_installed(void) { return 0; } +static inline int msm_hsusb_enable_pmic_ulpidata0(void) { return 0; } +static inline int msm_hsusb_disable_pmic_ulpidata0(void) { return 0; } +static inline int msm_hsusb_rpc_close(void) { return 0; } + +static inline int msm_chg_rpc_connect(void) { return 0; } +static inline int msm_chg_usb_charger_connected(uint32_t type) { return 0; } +static inline int msm_chg_usb_i_is_available(uint32_t sample) { return 0; } +static inline int msm_chg_usb_i_is_not_available(void) { return 0; } +static inline int msm_chg_usb_charger_disconnected(void) { return 0; } +static inline int msm_chg_rpc_close(void) { return 0; } + +#ifdef CONFIG_USB_GADGET_MSM_72K +static inline int hsusb_chg_init(int connect) { return 0; } +static inline void hsusb_chg_vbus_draw(unsigned mA) { } +static inline void hsusb_chg_connected(enum chg_type chgtype) { } +#endif + +static inline int msm_fsusb_rpc_init(struct msm_otg_ops *ops) { return 0; } +static inline int msm_fsusb_init_phy(void) { return 0; } +static inline int msm_fsusb_reset_phy(void) { return 0; } +static inline int msm_fsusb_suspend_phy(void) { return 0; } +static inline int msm_fsusb_resume_phy(void) { return 0; } +static inline int msm_fsusb_rpc_close(void) { return 0; } +static inline int msm_fsusb_remote_dev_disconnected(void) { return 0; } +static inline int msm_fsusb_set_remote_wakeup(void) { return 0; } +static inline void msm_fsusb_rpc_deinit(void) { } +static inline int +usb_diag_update_pid_and_serial_num(uint32_t pid, const char *snum) { return 0; } +#endif +#endif diff --git a/arch/arm/mach-msm/include/mach/rpc_pmapp.h b/arch/arm/mach-msm/include/mach/rpc_pmapp.h new file mode 100644 index 00000000000..35fe4777778 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpc_pmapp.h @@ -0,0 +1,74 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_MSM_RPC_PMAPP_H +#define __ASM_ARCH_MSM_RPC_PMAPP_H + +#include + +/* Clock voting ids */ +enum { + PMAPP_CLOCK_ID_DO = 0, + PMAPP_CLOCK_ID_D1, + PMAPP_CLOCK_ID_A0, + PMAPP_CLOCK_ID_A1, +}; + +/* Clock voting types */ +enum { + PMAPP_CLOCK_VOTE_OFF = 0, + PMAPP_CLOCK_VOTE_ON, + PMAPP_CLOCK_VOTE_PIN_CTRL, +}; + +/* vreg ids */ +enum { + PMAPP_VREG_LDO22 = 14, + PMAPP_VREG_S3 = 21, + PMAPP_VREG_S2 = 23, + PMAPP_VREG_S4 = 24, +}; + +/* SMPS clock voting types */ +enum { + PMAPP_SMPS_CLK_VOTE_DONTCARE = 0, + PMAPP_SMPS_CLK_VOTE_2P74, /* 2.74 MHz */ + PMAPP_SMPS_CLK_VOTE_1P6, /* 1.6 MHz */ +}; + +/* SMPS mode voting types */ +enum { + PMAPP_SMPS_MODE_VOTE_DONTCARE = 0, + PMAPP_SMPS_MODE_VOTE_PWM, + PMAPP_SMPS_MODE_VOTE_PFM, + PMAPP_SMPS_MODE_VOTE_AUTO +}; + +int msm_pm_app_rpc_init(void(*callback)(int online)); +void msm_pm_app_rpc_deinit(void(*callback)(int online)); +int msm_pm_app_register_vbus_sn(void (*callback)(int online)); +void msm_pm_app_unregister_vbus_sn(void (*callback)(int online)); +int msm_pm_app_enable_usb_ldo(int); +int pmic_vote_3p3_pwr_sel_switch(int boost); + +int pmapp_display_clock_config(uint enable); + +int pmapp_clock_vote(const char *voter_id, uint clock_id, uint vote); +int pmapp_smps_clock_vote(const char *voter_id, uint vreg_id, uint vote); +int pmapp_vreg_level_vote(const char *voter_id, uint vreg_id, uint level); +int pmapp_smps_mode_vote(const char *voter_id, uint vreg_id, uint mode); +int pmapp_vreg_pincntrl_vote(const char *voter_id, uint vreg_id, + uint clock_id, uint vote); +int pmapp_disp_backlight_set_brightness(int value); +void pmapp_disp_backlight_init(void); +#endif diff --git a/arch/arm/mach-msm/include/mach/rpc_server_handset.h b/arch/arm/mach-msm/include/mach/rpc_server_handset.h new file mode 100644 index 00000000000..e1dc841809b --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpc_server_handset.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_MSM_RPC_SERVER_HANDSET_H +#define __ASM_ARCH_MSM_RPC_SERVER_HANDSET_H + +struct msm_handset_platform_data { + const char *hs_name; + uint32_t pwr_key_delay_ms; /* default 500ms */ +}; + +void report_headset_status(bool connected); + +#endif diff --git a/arch/arm/mach-msm/include/mach/rpm-8660.h b/arch/arm/mach-msm/include/mach/rpm-8660.h new file mode 100644 index 00000000000..7b6ebf92ca9 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-8660.h @@ -0,0 +1,460 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPM_8660_H +#define __ARCH_ARM_MACH_MSM_RPM_8660_H + +/* RPM control message RAM enums */ +enum { + MSM_RPM_CTRL_VERSION_MAJOR, + MSM_RPM_CTRL_VERSION_MINOR, + MSM_RPM_CTRL_VERSION_BUILD, + + MSM_RPM_CTRL_REQ_CTX_0, + MSM_RPM_CTRL_REQ_CTX_7 = MSM_RPM_CTRL_REQ_CTX_0 + 7, + MSM_RPM_CTRL_REQ_SEL_0, + MSM_RPM_CTRL_REQ_SEL_7 = MSM_RPM_CTRL_REQ_SEL_0 + 7, + MSM_RPM_CTRL_ACK_CTX_0, + MSM_RPM_CTRL_ACK_CTX_7 = MSM_RPM_CTRL_ACK_CTX_0 + 7, + MSM_RPM_CTRL_ACK_SEL_0, + MSM_RPM_CTRL_ACK_SEL_7 = MSM_RPM_CTRL_ACK_SEL_0 + 7, +}; + +enum { + MSM_RPM_SEL_NOTIFICATION, + MSM_RPM_SEL_INVALIDATE, + MSM_RPM_SEL_TRIGGER_TIMED, + MSM_RPM_SEL_TRIGGER_SET, + MSM_RPM_SEL_TRIGGER_CLEAR, + + MSM_RPM_SEL_CXO_CLK, + MSM_RPM_SEL_PXO_CLK, + MSM_RPM_SEL_PLL_4, + MSM_RPM_SEL_APPS_FABRIC_CLK, + MSM_RPM_SEL_SYSTEM_FABRIC_CLK, + MSM_RPM_SEL_MM_FABRIC_CLK, + MSM_RPM_SEL_DAYTONA_FABRIC_CLK, + MSM_RPM_SEL_SFPB_CLK, + MSM_RPM_SEL_CFPB_CLK, + MSM_RPM_SEL_MMFPB_CLK, + MSM_RPM_SEL_SMI_CLK, + MSM_RPM_SEL_EBI1_CLK, + + MSM_RPM_SEL_APPS_L2_CACHE_CTL, + + MSM_RPM_SEL_APPS_FABRIC_HALT, + MSM_RPM_SEL_APPS_FABRIC_CLOCK_MODE, + MSM_RPM_SEL_APPS_FABRIC_IOCTL, + MSM_RPM_SEL_APPS_FABRIC_ARB, + + MSM_RPM_SEL_SYSTEM_FABRIC_HALT, + MSM_RPM_SEL_SYSTEM_FABRIC_CLOCK_MODE, + MSM_RPM_SEL_SYSTEM_FABRIC_IOCTL, + MSM_RPM_SEL_SYSTEM_FABRIC_ARB, + + MSM_RPM_SEL_MM_FABRIC_HALT, + MSM_RPM_SEL_MM_FABRIC_CLOCK_MODE, + MSM_RPM_SEL_MM_FABRIC_IOCTL, + MSM_RPM_SEL_MM_FABRIC_ARB, + + MSM_RPM_SEL_SMPS0B, + MSM_RPM_SEL_SMPS1B, + MSM_RPM_SEL_SMPS2B, + MSM_RPM_SEL_SMPS3B, + MSM_RPM_SEL_SMPS4B, + MSM_RPM_SEL_LDO0B, + MSM_RPM_SEL_LDO1B, + MSM_RPM_SEL_LDO2B, + MSM_RPM_SEL_LDO3B, + MSM_RPM_SEL_LDO4B, + MSM_RPM_SEL_LDO5B, + MSM_RPM_SEL_LDO6B, + MSM_RPM_SEL_LVS0B, + MSM_RPM_SEL_LVS1B, + MSM_RPM_SEL_LVS2B, + MSM_RPM_SEL_LVS3B, + MSM_RPM_SEL_MVS, + + MSM_RPM_SEL_SMPS0, + MSM_RPM_SEL_SMPS1, + MSM_RPM_SEL_SMPS2, + MSM_RPM_SEL_SMPS3, + MSM_RPM_SEL_SMPS4, + + MSM_RPM_SEL_LDO0, + MSM_RPM_SEL_LDO1, + MSM_RPM_SEL_LDO2, + MSM_RPM_SEL_LDO3, + MSM_RPM_SEL_LDO4, + MSM_RPM_SEL_LDO5, + MSM_RPM_SEL_LDO6, + MSM_RPM_SEL_LDO7, + MSM_RPM_SEL_LDO8, + MSM_RPM_SEL_LDO9, + MSM_RPM_SEL_LDO10, + MSM_RPM_SEL_LDO11, + MSM_RPM_SEL_LDO12, + MSM_RPM_SEL_LDO13, + MSM_RPM_SEL_LDO14, + MSM_RPM_SEL_LDO15, + MSM_RPM_SEL_LDO16, + MSM_RPM_SEL_LDO17, + MSM_RPM_SEL_LDO18, + MSM_RPM_SEL_LDO19, + MSM_RPM_SEL_LDO20, + MSM_RPM_SEL_LDO21, + MSM_RPM_SEL_LDO22, + MSM_RPM_SEL_LDO23, + MSM_RPM_SEL_LDO24, + MSM_RPM_SEL_LDO25, + MSM_RPM_SEL_LVS0, + MSM_RPM_SEL_LVS1, + MSM_RPM_SEL_NCP, + + MSM_RPM_SEL_CXO_BUFFERS, + + MSM_RPM_SEL_LAST = MSM_RPM_SEL_CXO_BUFFERS, +}; + + +enum { + MSM_RPM_ID_NOTIFICATION_CONFIGURED_0, + MSM_RPM_ID_NOTIFICATION_CONFIGURED_7 = + MSM_RPM_ID_NOTIFICATION_CONFIGURED_0 + 7, + + MSM_RPM_ID_NOTIFICATION_REGISTERED_0, + MSM_RPM_ID_NOTIFICATION_REGISTERED_7 = + MSM_RPM_ID_NOTIFICATION_REGISTERED_0 + 7, + + MSM_RPM_ID_INVALIDATE_0, + MSM_RPM_ID_INVALIDATE_7 = MSM_RPM_ID_INVALIDATE_0 + 7, + + MSM_RPM_ID_TRIGGER_TIMED_TO, + MSM_RPM_ID_TRIGGER_TIMED_SCLK_COUNT, + + MSM_RPM_ID_TRIGGER_SET_FROM, + MSM_RPM_ID_TRIGGER_SET_TO, + MSM_RPM_ID_TRIGGER_SET_TRIGGER, + + MSM_RPM_ID_TRIGGER_CLEAR_FROM, + MSM_RPM_ID_TRIGGER_CLEAR_TO, + MSM_RPM_ID_TRIGGER_CLEAR_TRIGGER, + + MSM_RPM_ID_CXO_CLK, + MSM_RPM_ID_PXO_CLK, + MSM_RPM_ID_PLL_4, + MSM_RPM_ID_APPS_FABRIC_CLK, + MSM_RPM_ID_SYSTEM_FABRIC_CLK, + MSM_RPM_ID_MM_FABRIC_CLK, + MSM_RPM_ID_DAYTONA_FABRIC_CLK, + MSM_RPM_ID_SFPB_CLK, + MSM_RPM_ID_CFPB_CLK, + MSM_RPM_ID_MMFPB_CLK, + MSM_RPM_ID_SMI_CLK, + MSM_RPM_ID_EBI1_CLK, + + MSM_RPM_ID_APPS_L2_CACHE_CTL, + + MSM_RPM_ID_APPS_FABRIC_HALT_0, + MSM_RPM_ID_APPS_FABRIC_HALT_1, + MSM_RPM_ID_APPS_FABRIC_CLOCK_MODE_0, + MSM_RPM_ID_APPS_FABRIC_CLOCK_MODE_1, + MSM_RPM_ID_APPS_FABRIC_CLOCK_MODE_2, + MSM_RPM_ID_APPS_FABRIC_RESERVED_A, + MSM_RPM_ID_APPS_FABRIC_ARB_0, + MSM_RPM_ID_APPS_FABRIC_ARB_5 = MSM_RPM_ID_APPS_FABRIC_ARB_0 + 5, + MSM_RPM_ID_APPS_FABRIC_RESERVED_B_0, + MSM_RPM_ID_APPS_FABRIC_RESERVED_B_5 = + MSM_RPM_ID_APPS_FABRIC_RESERVED_B_0 + 5, + + MSM_RPM_ID_SYSTEM_FABRIC_HALT_0, + MSM_RPM_ID_SYSTEM_FABRIC_HALT_1, + MSM_RPM_ID_SYSTEM_FABRIC_CLOCK_MODE_0, + MSM_RPM_ID_SYSTEM_FABRIC_CLOCK_MODE_1, + MSM_RPM_ID_SYSTEM_FABRIC_CLOCK_MODE_2, + MSM_RPM_ID_SYSTEM_FABRIC_RESERVED_A, + MSM_RPM_ID_SYSTEM_FABRIC_ARB_0, + MSM_RPM_ID_SYSTEM_FABRIC_ARB_21 = MSM_RPM_ID_SYSTEM_FABRIC_ARB_0 + 21, + MSM_RPM_ID_SYSTEM_FABRIC_RESERVED_B_0, + MSM_RPM_ID_SYSTEM_FABRIC_RESERVED_B_13 = + MSM_RPM_ID_SYSTEM_FABRIC_RESERVED_B_0 + 13, + + MSM_RPM_ID_MM_FABRIC_HALT_0, + MSM_RPM_ID_MM_FABRIC_HALT_1, + MSM_RPM_ID_MM_FABRIC_CLOCK_MODE_0, + MSM_RPM_ID_MM_FABRIC_CLOCK_MODE_1, + MSM_RPM_ID_MM_FABRIC_CLOCK_MODE_2, + MSM_RPM_ID_MM_FABRIC_RESERVED_A, + MSM_RPM_ID_MM_FABRIC_ARB_0, + MSM_RPM_ID_MM_FABRIC_ARB_22 = MSM_RPM_ID_MM_FABRIC_ARB_0 + 22, + + /* pmic 8901 */ + MSM_RPM_ID_SMPS0B_0, + MSM_RPM_ID_SMPS0B_1, + MSM_RPM_ID_SMPS1B_0, + MSM_RPM_ID_SMPS1B_1, + MSM_RPM_ID_SMPS2B_0, + MSM_RPM_ID_SMPS2B_1, + MSM_RPM_ID_SMPS3B_0, + MSM_RPM_ID_SMPS3B_1, + MSM_RPM_ID_SMPS4B_0, + MSM_RPM_ID_SMPS4B_1, + MSM_RPM_ID_LDO0B_0, + MSM_RPM_ID_LDO0B_1, + MSM_RPM_ID_LDO1B_0, + MSM_RPM_ID_LDO1B_1, + MSM_RPM_ID_LDO2B_0, + MSM_RPM_ID_LDO2B_1, + MSM_RPM_ID_LDO3B_0, + MSM_RPM_ID_LDO3B_1, + MSM_RPM_ID_LDO4B_0, + MSM_RPM_ID_LDO4B_1, + MSM_RPM_ID_LDO5B_0, + MSM_RPM_ID_LDO5B_1, + MSM_RPM_ID_LDO6B_0, + MSM_RPM_ID_LDO6B_1, + MSM_RPM_ID_LVS0B, + MSM_RPM_ID_LVS1B, + MSM_RPM_ID_LVS2B, + MSM_RPM_ID_LVS3B, + MSM_RPM_ID_MVS, + + /* pmic 8058 */ + MSM_RPM_ID_SMPS0_0, + MSM_RPM_ID_SMPS0_1, + MSM_RPM_ID_SMPS1_0, + MSM_RPM_ID_SMPS1_1, + MSM_RPM_ID_SMPS2_0, + MSM_RPM_ID_SMPS2_1, + MSM_RPM_ID_SMPS3_0, + MSM_RPM_ID_SMPS3_1, + MSM_RPM_ID_SMPS4_0, + MSM_RPM_ID_SMPS4_1, + MSM_RPM_ID_LDO0_0, + MSM_RPM_ID_LDO0_1, + MSM_RPM_ID_LDO1_0, + MSM_RPM_ID_LDO1_1, + MSM_RPM_ID_LDO2_0, + MSM_RPM_ID_LDO2_1, + MSM_RPM_ID_LDO3_0, + MSM_RPM_ID_LDO3_1, + MSM_RPM_ID_LDO4_0, + MSM_RPM_ID_LDO4_1, + MSM_RPM_ID_LDO5_0, + MSM_RPM_ID_LDO5_1, + MSM_RPM_ID_LDO6_0, + MSM_RPM_ID_LDO6_1, + MSM_RPM_ID_LDO7_0, + MSM_RPM_ID_LDO7_1, + MSM_RPM_ID_LDO8_0, + MSM_RPM_ID_LDO8_1, + MSM_RPM_ID_LDO9_0, + MSM_RPM_ID_LDO9_1, + MSM_RPM_ID_LDO10_0, + MSM_RPM_ID_LDO10_1, + MSM_RPM_ID_LDO11_0, + MSM_RPM_ID_LDO11_1, + MSM_RPM_ID_LDO12_0, + MSM_RPM_ID_LDO12_1, + MSM_RPM_ID_LDO13_0, + MSM_RPM_ID_LDO13_1, + MSM_RPM_ID_LDO14_0, + MSM_RPM_ID_LDO14_1, + MSM_RPM_ID_LDO15_0, + MSM_RPM_ID_LDO15_1, + MSM_RPM_ID_LDO16_0, + MSM_RPM_ID_LDO16_1, + MSM_RPM_ID_LDO17_0, + MSM_RPM_ID_LDO17_1, + MSM_RPM_ID_LDO18_0, + MSM_RPM_ID_LDO18_1, + MSM_RPM_ID_LDO19_0, + MSM_RPM_ID_LDO19_1, + MSM_RPM_ID_LDO20_0, + MSM_RPM_ID_LDO20_1, + MSM_RPM_ID_LDO21_0, + MSM_RPM_ID_LDO21_1, + MSM_RPM_ID_LDO22_0, + MSM_RPM_ID_LDO22_1, + MSM_RPM_ID_LDO23_0, + MSM_RPM_ID_LDO23_1, + MSM_RPM_ID_LDO24_0, + MSM_RPM_ID_LDO24_1, + MSM_RPM_ID_LDO25_0, + MSM_RPM_ID_LDO25_1, + MSM_RPM_ID_LVS0, + MSM_RPM_ID_LVS1, + MSM_RPM_ID_NCP_0, + MSM_RPM_ID_NCP_1, + + MSM_RPM_ID_CXO_BUFFERS, + + MSM_RPM_ID_LAST = MSM_RPM_ID_CXO_BUFFERS +}; + +/* RPM resources RPM_ID aliases */ +enum { + MSM_RPMRS_ID_RPM_CTL = MSM_RPM_ID_TRIGGER_SET_FROM, + MSM_RPMRS_ID_PXO_CLK = MSM_RPM_ID_PXO_CLK, + MSM_RPMRS_ID_APPS_L2_CACHE_CTL = MSM_RPM_ID_APPS_L2_CACHE_CTL, + MSM_RPMRS_ID_VDD_MEM_0 = MSM_RPM_ID_SMPS0_0, + MSM_RPMRS_ID_VDD_MEM_1 = MSM_RPM_ID_SMPS0_1, + MSM_RPMRS_ID_VDD_DIG_0 = MSM_RPM_ID_SMPS1_0, + MSM_RPMRS_ID_VDD_DIG_1 = MSM_RPM_ID_SMPS1_1 +}; + +enum { + MSM_RPM_STATUS_ID_VERSION_MAJOR, + MSM_RPM_STATUS_ID_VERSION_MINOR, + MSM_RPM_STATUS_ID_VERSION_BUILD, + MSM_RPM_STATUS_ID_SUPPORTED_RESOURCES_0, + MSM_RPM_STATUS_ID_SUPPORTED_RESOURCES_1, + MSM_RPM_STATUS_ID_SUPPORTED_RESOURCES_2, + MSM_RPM_STATUS_ID_RESERVED_0, + MSM_RPM_STATUS_ID_RESERVED_4 = MSM_RPM_STATUS_ID_RESERVED_0 + 4, + MSM_RPM_STATUS_ID_SEQUENCE, + + MSM_RPM_STATUS_ID_CXO_CLK, + MSM_RPM_STATUS_ID_PXO_CLK, + MSM_RPM_STATUS_ID_PLL_4, + MSM_RPM_STATUS_ID_APPS_FABRIC_CLK, + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_CLK, + MSM_RPM_STATUS_ID_MM_FABRIC_CLK, + MSM_RPM_STATUS_ID_DAYTONA_FABRIC_CLK, + MSM_RPM_STATUS_ID_SFPB_CLK, + MSM_RPM_STATUS_ID_CFPB_CLK, + MSM_RPM_STATUS_ID_MMFPB_CLK, + MSM_RPM_STATUS_ID_SMI_CLK, + MSM_RPM_STATUS_ID_EBI1_CLK, + + MSM_RPM_STATUS_ID_APPS_L2_CACHE_CTL, + + MSM_RPM_STATUS_ID_APPS_FABRIC_HALT, + MSM_RPM_STATUS_ID_APPS_FABRIC_CLOCK_MODE, + MSM_RPM_STATUS_ID_APPS_FABRIC_RESERVED, + MSM_RPM_STATUS_ID_APPS_FABRIC_ARB, + + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_HALT, + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_CLOCK_MODE, + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_RESERVED, + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_ARB, + + MSM_RPM_STATUS_ID_MM_FABRIC_HALT, + MSM_RPM_STATUS_ID_MM_FABRIC_CLOCK_MODE, + MSM_RPM_STATUS_ID_MM_FABRIC_RESERVED, + MSM_RPM_STATUS_ID_MM_FABRIC_ARB, + + /* pmic 8901 */ + MSM_RPM_STATUS_ID_SMPS0B_0, + MSM_RPM_STATUS_ID_SMPS0B_1, + MSM_RPM_STATUS_ID_SMPS1B_0, + MSM_RPM_STATUS_ID_SMPS1B_1, + MSM_RPM_STATUS_ID_SMPS2B_0, + MSM_RPM_STATUS_ID_SMPS2B_1, + MSM_RPM_STATUS_ID_SMPS3B_0, + MSM_RPM_STATUS_ID_SMPS3B_1, + MSM_RPM_STATUS_ID_SMPS4B_0, + MSM_RPM_STATUS_ID_SMPS4B_1, + MSM_RPM_STATUS_ID_LDO0B_0, + MSM_RPM_STATUS_ID_LDO0B_1, + MSM_RPM_STATUS_ID_LDO1B_0, + MSM_RPM_STATUS_ID_LDO1B_1, + MSM_RPM_STATUS_ID_LDO2B_0, + MSM_RPM_STATUS_ID_LDO2B_1, + MSM_RPM_STATUS_ID_LDO3B_0, + MSM_RPM_STATUS_ID_LDO3B_1, + MSM_RPM_STATUS_ID_LDO4B_0, + MSM_RPM_STATUS_ID_LDO4B_1, + MSM_RPM_STATUS_ID_LDO5B_0, + MSM_RPM_STATUS_ID_LDO5B_1, + MSM_RPM_STATUS_ID_LDO6B_0, + MSM_RPM_STATUS_ID_LDO6B_1, + MSM_RPM_STATUS_ID_LVS0B, + MSM_RPM_STATUS_ID_LVS1B, + MSM_RPM_STATUS_ID_LVS2B, + MSM_RPM_STATUS_ID_LVS3B, + MSM_RPM_STATUS_ID_MVS, + + /* pmic 8058 */ + MSM_RPM_STATUS_ID_SMPS0_0, + MSM_RPM_STATUS_ID_SMPS0_1, + MSM_RPM_STATUS_ID_SMPS1_0, + MSM_RPM_STATUS_ID_SMPS1_1, + MSM_RPM_STATUS_ID_SMPS2_0, + MSM_RPM_STATUS_ID_SMPS2_1, + MSM_RPM_STATUS_ID_SMPS3_0, + MSM_RPM_STATUS_ID_SMPS3_1, + MSM_RPM_STATUS_ID_SMPS4_0, + MSM_RPM_STATUS_ID_SMPS4_1, + MSM_RPM_STATUS_ID_LDO0_0, + MSM_RPM_STATUS_ID_LDO0_1, + MSM_RPM_STATUS_ID_LDO1_0, + MSM_RPM_STATUS_ID_LDO1_1, + MSM_RPM_STATUS_ID_LDO2_0, + MSM_RPM_STATUS_ID_LDO2_1, + MSM_RPM_STATUS_ID_LDO3_0, + MSM_RPM_STATUS_ID_LDO3_1, + MSM_RPM_STATUS_ID_LDO4_0, + MSM_RPM_STATUS_ID_LDO4_1, + MSM_RPM_STATUS_ID_LDO5_0, + MSM_RPM_STATUS_ID_LDO5_1, + MSM_RPM_STATUS_ID_LDO6_0, + MSM_RPM_STATUS_ID_LDO6_1, + MSM_RPM_STATUS_ID_LDO7_0, + MSM_RPM_STATUS_ID_LDO7_1, + MSM_RPM_STATUS_ID_LDO8_0, + MSM_RPM_STATUS_ID_LDO8_1, + MSM_RPM_STATUS_ID_LDO9_0, + MSM_RPM_STATUS_ID_LDO9_1, + MSM_RPM_STATUS_ID_LDO10_0, + MSM_RPM_STATUS_ID_LDO10_1, + MSM_RPM_STATUS_ID_LDO11_0, + MSM_RPM_STATUS_ID_LDO11_1, + MSM_RPM_STATUS_ID_LDO12_0, + MSM_RPM_STATUS_ID_LDO12_1, + MSM_RPM_STATUS_ID_LDO13_0, + MSM_RPM_STATUS_ID_LDO13_1, + MSM_RPM_STATUS_ID_LDO14_0, + MSM_RPM_STATUS_ID_LDO14_1, + MSM_RPM_STATUS_ID_LDO15_0, + MSM_RPM_STATUS_ID_LDO15_1, + MSM_RPM_STATUS_ID_LDO16_0, + MSM_RPM_STATUS_ID_LDO16_1, + MSM_RPM_STATUS_ID_LDO17_0, + MSM_RPM_STATUS_ID_LDO17_1, + MSM_RPM_STATUS_ID_LDO18_0, + MSM_RPM_STATUS_ID_LDO18_1, + MSM_RPM_STATUS_ID_LDO19_0, + MSM_RPM_STATUS_ID_LDO19_1, + MSM_RPM_STATUS_ID_LDO20_0, + MSM_RPM_STATUS_ID_LDO20_1, + MSM_RPM_STATUS_ID_LDO21_0, + MSM_RPM_STATUS_ID_LDO21_1, + MSM_RPM_STATUS_ID_LDO22_0, + MSM_RPM_STATUS_ID_LDO22_1, + MSM_RPM_STATUS_ID_LDO23_0, + MSM_RPM_STATUS_ID_LDO23_1, + MSM_RPM_STATUS_ID_LDO24_0, + MSM_RPM_STATUS_ID_LDO24_1, + MSM_RPM_STATUS_ID_LDO25_0, + MSM_RPM_STATUS_ID_LDO25_1, + MSM_RPM_STATUS_ID_LVS0, + MSM_RPM_STATUS_ID_LVS1, + MSM_RPM_STATUS_ID_NCP_0, + MSM_RPM_STATUS_ID_NCP_1, + + MSM_RPM_STATUS_ID_CXO_BUFFERS, + + MSM_RPM_STATUS_ID_LAST = MSM_RPM_STATUS_ID_CXO_BUFFERS +}; + +#endif /* __ARCH_ARM_MACH_MSM_RPM_8660_H */ diff --git a/arch/arm/mach-msm/include/mach/rpm-8960.h b/arch/arm/mach-msm/include/mach/rpm-8960.h new file mode 100644 index 00000000000..b54bab93e79 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-8960.h @@ -0,0 +1,423 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPM_8960_H +#define __ARCH_ARM_MACH_MSM_RPM_8960_H + +/* RPM control message RAM enums */ +enum { + MSM_RPM_CTRL_VERSION_MAJOR, + MSM_RPM_CTRL_VERSION_MINOR, + MSM_RPM_CTRL_VERSION_BUILD, + + MSM_RPM_CTRL_REQ_CTX_0, + MSM_RPM_CTRL_REQ_CTX_7 = MSM_RPM_CTRL_REQ_CTX_0 + 7, + MSM_RPM_CTRL_REQ_SEL_0, + MSM_RPM_CTRL_REQ_SEL_3 = MSM_RPM_CTRL_REQ_SEL_0 + 3, + MSM_RPM_CTRL_ACK_CTX_0, + MSM_RPM_CTRL_ACK_CTX_7 = MSM_RPM_CTRL_ACK_CTX_0 + 7, + MSM_RPM_CTRL_ACK_SEL_0, + MSM_RPM_CTRL_ACK_SEL_7 = MSM_RPM_CTRL_ACK_SEL_0 + 7, +}; + + +/* RPM resource select enums defined for RPM core + NOT IN SEQUENTIAL ORDER */ +enum { + MSM_RPM_SEL_NOTIFICATION = 0, + MSM_RPM_SEL_INVALIDATE = 1, + MSM_RPM_SEL_TRIGGER_TIMED = 2, + MSM_RPM_SEL_RPM_CTL = 3, + + MSM_RPM_SEL_CXO_CLK = 5, + MSM_RPM_SEL_PXO_CLK = 6, + MSM_RPM_SEL_APPS_FABRIC_CLK = 8, + MSM_RPM_SEL_SYSTEM_FABRIC_CLK = 9, + MSM_RPM_SEL_MM_FABRIC_CLK = 10, + MSM_RPM_SEL_DAYTONA_FABRIC_CLK = 11, + MSM_RPM_SEL_SFPB_CLK = 12, + MSM_RPM_SEL_CFPB_CLK = 13, + MSM_RPM_SEL_MMFPB_CLK = 14, + MSM_RPM_SEL_EBI1_CLK = 16, + + MSM_RPM_SEL_APPS_FABRIC_CFG_HALT = 18, + MSM_RPM_SEL_APPS_FABRIC_CFG_CLKMOD = 19, + MSM_RPM_SEL_APPS_FABRIC_CFG_IOCTL = 20, + MSM_RPM_SEL_APPS_FABRIC_ARB = 21, + + MSM_RPM_SEL_SYS_FABRIC_CFG_HALT = 22, + MSM_RPM_SEL_SYS_FABRIC_CFG_CLKMOD = 23, + MSM_RPM_SEL_SYS_FABRIC_CFG_IOCTL = 24, + MSM_RPM_SEL_SYSTEM_FABRIC_ARB = 25, + + MSM_RPM_SEL_MMSS_FABRIC_CFG_HALT = 26, + MSM_RPM_SEL_MMSS_FABRIC_CFG_CLKMOD = 27, + MSM_RPM_SEL_MMSS_FABRIC_CFG_IOCTL = 28, + MSM_RPM_SEL_MM_FABRIC_ARB = 29, + + MSM_RPM_SEL_PM8921_S1 = 30, + MSM_RPM_SEL_PM8921_S2 = 31, + MSM_RPM_SEL_PM8921_S3 = 32, + MSM_RPM_SEL_PM8921_S4 = 33, + MSM_RPM_SEL_PM8921_S5 = 34, + MSM_RPM_SEL_PM8921_S6 = 35, + MSM_RPM_SEL_PM8921_S7 = 36, + MSM_RPM_SEL_PM8921_S8 = 37, + MSM_RPM_SEL_PM8921_L1 = 38, + MSM_RPM_SEL_PM8921_L2 = 39, + MSM_RPM_SEL_PM8921_L3 = 40, + MSM_RPM_SEL_PM8921_L4 = 41, + MSM_RPM_SEL_PM8921_L5 = 42, + MSM_RPM_SEL_PM8921_L6 = 43, + MSM_RPM_SEL_PM8921_L7 = 44, + MSM_RPM_SEL_PM8921_L8 = 45, + MSM_RPM_SEL_PM8921_L9 = 46, + MSM_RPM_SEL_PM8921_L10 = 47, + MSM_RPM_SEL_PM8921_L11 = 48, + MSM_RPM_SEL_PM8921_L12 = 49, + MSM_RPM_SEL_PM8921_L13 = 50, + MSM_RPM_SEL_PM8921_L14 = 51, + MSM_RPM_SEL_PM8921_L15 = 52, + MSM_RPM_SEL_PM8921_L16 = 53, + MSM_RPM_SEL_PM8921_L17 = 54, + MSM_RPM_SEL_PM8921_L18 = 55, + MSM_RPM_SEL_PM8921_L19 = 56, + MSM_RPM_SEL_PM8921_L20 = 57, + MSM_RPM_SEL_PM8921_L21 = 58, + MSM_RPM_SEL_PM8921_L22 = 59, + MSM_RPM_SEL_PM8921_L23 = 60, + MSM_RPM_SEL_PM8921_L24 = 61, + MSM_RPM_SEL_PM8921_L25 = 62, + MSM_RPM_SEL_PM8921_L26 = 63, + MSM_RPM_SEL_PM8921_L27 = 64, + MSM_RPM_SEL_PM8921_L28 = 65, + MSM_RPM_SEL_PM8921_L29 = 66, + MSM_RPM_SEL_PM8921_CLK1 = 67, + MSM_RPM_SEL_PM8921_CLK2 = 68, + MSM_RPM_SEL_PM8921_LVS1 = 69, + MSM_RPM_SEL_PM8921_LVS2 = 70, + MSM_RPM_SEL_PM8921_LVS3 = 71, + MSM_RPM_SEL_PM8921_LVS4 = 72, + MSM_RPM_SEL_PM8921_LVS5 = 73, + MSM_RPM_SEL_PM8921_LVS6 = 74, + MSM_RPM_SEL_PM8921_LVS7 = 75, + + MSM_RPM_SEL_NCP = 80, + MSM_RPM_SEL_CXO_BUFFERS = 81, + MSM_RPM_SEL_USB_OTG_SWITCH = 82, + MSM_RPM_SEL_HDMI_SWITCH = 83, + + MSM_RPM_SEL_LAST = MSM_RPM_SEL_HDMI_SWITCH, +}; + +/* RPM resource (4 byte) word ID enum */ +enum { + MSM_RPM_ID_NOTIFICATION_CONFIGURED_0 = 0, + MSM_RPM_ID_NOTIFICATION_CONFIGURED_3 = + MSM_RPM_ID_NOTIFICATION_CONFIGURED_0 + 3, + + MSM_RPM_ID_NOTIFICATION_REGISTERED_0 = 4, + MSM_RPM_ID_NOTIFICATION_REGISTERED_3 = + MSM_RPM_ID_NOTIFICATION_REGISTERED_0 + 3, + + MSM_RPM_ID_INVALIDATE_0 = 8, + MSM_RPM_ID_INVALIDATE_7 = + MSM_RPM_ID_INVALIDATE_0 + 7, + + MSM_RPM_ID_TRIGGER_TIMED_TO = 16, + MSM_RPM_ID_TRIGGER_TIMED_SCLK_COUNT = 17, + + MSM_RPM_ID_RPM_CTL = 18, + + /* TRIGGER_CLEAR/SET deprecated in these 24 RESERVED bytes */ + MSM_RPM_ID_RESERVED_0 = 19, + MSM_RPM_ID_RESERVED_5 = + MSM_RPM_ID_RESERVED_0 + 5, + + MSM_RPM_ID_CXO_CLK = 25, + MSM_RPM_ID_PXO_CLK = 26, + MSM_RPM_ID_APPS_FABRIC_CLK = 27, + MSM_RPM_ID_SYSTEM_FABRIC_CLK = 28, + MSM_RPM_ID_MM_FABRIC_CLK = 29, + MSM_RPM_ID_DAYTONA_FABRIC_CLK = 30, + MSM_RPM_ID_SFPB_CLK = 31, + MSM_RPM_ID_CFPB_CLK = 32, + MSM_RPM_ID_MMFPB_CLK = 33, + MSM_RPM_ID_EBI1_CLK = 34, + + MSM_RPM_ID_APPS_FABRIC_CFG_HALT_0 = 35, + MSM_RPM_ID_APPS_FABRIC_CFG_HALT_1 = 36, + MSM_RPM_ID_APPS_FABRIC_CFG_CLKMOD_0 = 37, + MSM_RPM_ID_APPS_FABRIC_CFG_CLKMOD_1 = 38, + MSM_RPM_ID_APPS_FABRIC_CFG_CLKMOD_2 = 39, + MSM_RPM_ID_APPS_FABRIC_CFG_IOCTL = 40, + MSM_RPM_ID_APPS_FABRIC_ARB_0 = 41, + MSM_RPM_ID_APPS_FABRIC_ARB_11 = + MSM_RPM_ID_APPS_FABRIC_ARB_0 + 11, + + MSM_RPM_ID_SYS_FABRIC_CFG_HALT_0 = 53, + MSM_RPM_ID_SYS_FABRIC_CFG_HALT_1 = 54, + MSM_RPM_ID_SYS_FABRIC_CFG_CLKMOD_0 = 55, + MSM_RPM_ID_SYS_FABRIC_CFG_CLKMOD_1 = 56, + MSM_RPM_ID_SYS_FABRIC_CFG_CLKMOD_2 = 57, + MSM_RPM_ID_SYS_FABRIC_CFG_IOCTL = 58, + MSM_RPM_ID_SYSTEM_FABRIC_ARB_0 = 59, + MSM_RPM_ID_SYSTEM_FABRIC_ARB_28 = + MSM_RPM_ID_SYSTEM_FABRIC_ARB_0 + 28, + + MSM_RPM_ID_MMSS_FABRIC_CFG_HALT_0 = 88, + MSM_RPM_ID_MMSS_FABRIC_CFG_HALT_1 = 89, + MSM_RPM_ID_MMSS_FABRIC_CFG_CLKMOD_0 = 90, + MSM_RPM_ID_MMSS_FABRIC_CFG_CLKMOD_1 = 91, + MSM_RPM_ID_MMSS_FABRIC_CFG_CLKMOD_2 = 92, + MSM_RPM_ID_MMSS_FABRIC_CFG_IOCTL = 93, + MSM_RPM_ID_MM_FABRIC_ARB_0 = 94, + MSM_RPM_ID_MM_FABRIC_ARB_22 = + MSM_RPM_ID_MM_FABRIC_ARB_0 + 22, + + MSM_RPM_ID_PM8921_S1_0 = 117, + MSM_RPM_ID_PM8921_S1_1 = 118, + MSM_RPM_ID_PM8921_S2_0 = 119, + MSM_RPM_ID_PM8921_S2_1 = 120, + MSM_RPM_ID_PM8921_S3_0 = 121, + MSM_RPM_ID_PM8921_S3_1 = 122, + MSM_RPM_ID_PM8921_S4_0 = 123, + MSM_RPM_ID_PM8921_S4_1 = 124, + MSM_RPM_ID_PM8921_S5_0 = 125, + MSM_RPM_ID_PM8921_S5_1 = 126, + MSM_RPM_ID_PM8921_S6_0 = 127, + MSM_RPM_ID_PM8921_S6_1 = 128, + MSM_RPM_ID_PM8921_S7_0 = 129, + MSM_RPM_ID_PM8921_S7_1 = 130, + MSM_RPM_ID_PM8921_S8_0 = 131, + MSM_RPM_ID_PM8921_S8_1 = 132, + MSM_RPM_ID_PM8921_L1_0 = 133, + MSM_RPM_ID_PM8921_L1_1 = 134, + MSM_RPM_ID_PM8921_L2_0 = 135, + MSM_RPM_ID_PM8921_L2_1 = 136, + MSM_RPM_ID_PM8921_L3_0 = 137, + MSM_RPM_ID_PM8921_L3_1 = 138, + MSM_RPM_ID_PM8921_L4_0 = 139, + MSM_RPM_ID_PM8921_L4_1 = 140, + MSM_RPM_ID_PM8921_L5_0 = 141, + MSM_RPM_ID_PM8921_L5_1 = 142, + MSM_RPM_ID_PM8921_L6_0 = 143, + MSM_RPM_ID_PM8921_L6_1 = 144, + MSM_RPM_ID_PM8921_L7_0 = 145, + MSM_RPM_ID_PM8921_L7_1 = 146, + MSM_RPM_ID_PM8921_L8_0 = 147, + MSM_RPM_ID_PM8921_L8_1 = 148, + MSM_RPM_ID_PM8921_L9_0 = 149, + MSM_RPM_ID_PM8921_L9_1 = 150, + MSM_RPM_ID_PM8921_L10_0 = 151, + MSM_RPM_ID_PM8921_L10_1 = 152, + MSM_RPM_ID_PM8921_L11_0 = 153, + MSM_RPM_ID_PM8921_L11_1 = 154, + MSM_RPM_ID_PM8921_L12_0 = 155, + MSM_RPM_ID_PM8921_L12_1 = 156, + MSM_RPM_ID_PM8921_L13_0 = 157, + MSM_RPM_ID_PM8921_L13_1 = 158, + MSM_RPM_ID_PM8921_L14_0 = 159, + MSM_RPM_ID_PM8921_L14_1 = 160, + MSM_RPM_ID_PM8921_L15_0 = 161, + MSM_RPM_ID_PM8921_L15_1 = 162, + MSM_RPM_ID_PM8921_L16_0 = 163, + MSM_RPM_ID_PM8921_L16_1 = 164, + MSM_RPM_ID_PM8921_L17_0 = 165, + MSM_RPM_ID_PM8921_L17_1 = 166, + MSM_RPM_ID_PM8921_L18_0 = 167, + MSM_RPM_ID_PM8921_L18_1 = 168, + MSM_RPM_ID_PM8921_L19_0 = 169, + MSM_RPM_ID_PM8921_L19_1 = 170, + MSM_RPM_ID_PM8921_L20_0 = 171, + MSM_RPM_ID_PM8921_L20_1 = 172, + MSM_RPM_ID_PM8921_L21_0 = 173, + MSM_RPM_ID_PM8921_L21_1 = 174, + MSM_RPM_ID_PM8921_L22_0 = 175, + MSM_RPM_ID_PM8921_L22_1 = 176, + MSM_RPM_ID_PM8921_L23_0 = 177, + MSM_RPM_ID_PM8921_L23_1 = 178, + MSM_RPM_ID_PM8921_L24_0 = 179, + MSM_RPM_ID_PM8921_L24_1 = 180, + MSM_RPM_ID_PM8921_L25_0 = 181, + MSM_RPM_ID_PM8921_L25_1 = 182, + MSM_RPM_ID_PM8921_L26_0 = 183, + MSM_RPM_ID_PM8921_L26_1 = 184, + MSM_RPM_ID_PM8921_L27_0 = 185, + MSM_RPM_ID_PM8921_L27_1 = 186, + MSM_RPM_ID_PM8921_L28_0 = 187, + MSM_RPM_ID_PM8921_L28_1 = 188, + MSM_RPM_ID_PM8921_L29_0 = 189, + MSM_RPM_ID_PM8921_L29_1 = 190, + MSM_RPM_ID_PM8921_CLK1_0 = 191, + MSM_RPM_ID_PM8921_CLK1_1 = 192, + MSM_RPM_ID_PM8921_CLK2_0 = 193, + MSM_RPM_ID_PM8921_CLK2_1 = 194, + MSM_RPM_ID_PM8921_LVS1 = 195, + MSM_RPM_ID_PM8921_LVS2 = 196, + MSM_RPM_ID_PM8921_LVS3 = 197, + MSM_RPM_ID_PM8921_LVS4 = 198, + MSM_RPM_ID_PM8921_LVS5 = 199, + MSM_RPM_ID_PM8921_LVS6 = 200, + MSM_RPM_ID_PM8921_LVS7 = 201, + MSM_RPM_ID_NCP_0 = 202, + MSM_RPM_ID_NCP_1 = 203, + + MSM_RPM_ID_CXO_BUFFERS = 204, + MSM_RPM_ID_USB_OTG_SWITCH = 205, + MSM_RPM_ID_HDMI_SWITCH = 206, + + MSM_RPM_ID_LAST = MSM_RPM_ID_HDMI_SWITCH +}; + +/* RPM resources RPM_ID aliases */ +enum { + MSM_RPMRS_ID_RPM_CTL = MSM_RPM_ID_RPM_CTL, + MSM_RPMRS_ID_PXO_CLK = MSM_RPM_ID_PXO_CLK, + MSM_RPMRS_ID_VDD_DIG_0 = MSM_RPM_ID_PM8921_S3_0, + MSM_RPMRS_ID_VDD_DIG_1 = MSM_RPM_ID_PM8921_S3_1, + MSM_RPMRS_ID_VDD_MEM_0 = MSM_RPM_ID_PM8921_L24_0, + MSM_RPMRS_ID_VDD_MEM_1 = MSM_RPM_ID_PM8921_L24_1, + + /* MSM8960 L2 cache power control not via RPM + * MSM_RPM_ID_LAST + 1 indicates invalid */ + MSM_RPMRS_ID_APPS_L2_CACHE_CTL = MSM_RPM_ID_LAST + 1 +}; + +/* RPM status ID enum */ +enum { + MSM_RPM_STATUS_ID_VERSION_MAJOR = 0, + MSM_RPM_STATUS_ID_VERSION_MINOR = 1, + MSM_RPM_STATUS_ID_VERSION_BUILD = 2, + MSM_RPM_STATUS_ID_SUPPORTED_RESOURCES_0 = 3, + MSM_RPM_STATUS_ID_SUPPORTED_RESOURCES_1 = 4, + MSM_RPM_STATUS_ID_SUPPORTED_RESOURCES_2 = 5, + MSM_RPM_STATUS_ID_RESERVED_SUPPORTED_RESOURCES_0 = 6, + MSM_RPM_STATUS_ID_SEQUENCE = 7, + MSM_RPM_STATUS_ID_RPM_CTL = 8, + MSM_RPM_STATUS_ID_CXO_CLK = 9, + MSM_RPM_STATUS_ID_PXO_CLK = 10, + MSM_RPM_STATUS_ID_APPS_FABRIC_CLK = 11, + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_CLK = 12, + MSM_RPM_STATUS_ID_MM_FABRIC_CLK = 13, + MSM_RPM_STATUS_ID_DAYTONA_FABRIC_CLK = 14, + MSM_RPM_STATUS_ID_SFPB_CLK = 15, + MSM_RPM_STATUS_ID_CFPB_CLK = 16, + MSM_RPM_STATUS_ID_MMFPB_CLK = 17, + MSM_RPM_STATUS_ID_EBI1_CLK = 18, + MSM_RPM_STATUS_ID_APPS_FABRIC_CFG_HALT = 19, + MSM_RPM_STATUS_ID_APPS_FABRIC_CFG_CLKMOD = 20, + MSM_RPM_STATUS_ID_APPS_FABRIC_CFG_IOCTL = 21, + MSM_RPM_STATUS_ID_APPS_FABRIC_ARB = 22, + MSM_RPM_STATUS_ID_SYS_FABRIC_CFG_HALT = 23, + MSM_RPM_STATUS_ID_SYS_FABRIC_CFG_CLKMOD = 24, + MSM_RPM_STATUS_ID_SYS_FABRIC_CFG_IOCTL = 25, + MSM_RPM_STATUS_ID_SYSTEM_FABRIC_ARB = 26, + MSM_RPM_STATUS_ID_MMSS_FABRIC_CFG_HALT = 27, + MSM_RPM_STATUS_ID_MMSS_FABRIC_CFG_CLKMOD = 28, + MSM_RPM_STATUS_ID_MMSS_FABRIC_CFG_IOCTL = 29, + MSM_RPM_STATUS_ID_MM_FABRIC_ARB = 30, + MSM_RPM_STATUS_ID_PM8921_S1_0 = 31, + MSM_RPM_STATUS_ID_PM8921_S1_1 = 32, + MSM_RPM_STATUS_ID_PM8921_S2_0 = 33, + MSM_RPM_STATUS_ID_PM8921_S2_1 = 34, + MSM_RPM_STATUS_ID_PM8921_S3_0 = 35, + MSM_RPM_STATUS_ID_PM8921_S3_1 = 36, + MSM_RPM_STATUS_ID_PM8921_S4_0 = 37, + MSM_RPM_STATUS_ID_PM8921_S4_1 = 38, + MSM_RPM_STATUS_ID_PM8921_S5_0 = 39, + MSM_RPM_STATUS_ID_PM8921_S5_1 = 40, + MSM_RPM_STATUS_ID_PM8921_S6_0 = 41, + MSM_RPM_STATUS_ID_PM8921_S6_1 = 42, + MSM_RPM_STATUS_ID_PM8921_S7_0 = 43, + MSM_RPM_STATUS_ID_PM8921_S7_1 = 44, + MSM_RPM_STATUS_ID_PM8921_S8_0 = 45, + MSM_RPM_STATUS_ID_PM8921_S8_1 = 46, + MSM_RPM_STATUS_ID_PM8921_L1_0 = 47, + MSM_RPM_STATUS_ID_PM8921_L1_1 = 48, + MSM_RPM_STATUS_ID_PM8921_L2_0 = 49, + MSM_RPM_STATUS_ID_PM8921_L2_1 = 50, + MSM_RPM_STATUS_ID_PM8921_L3_0 = 51, + MSM_RPM_STATUS_ID_PM8921_L3_1 = 52, + MSM_RPM_STATUS_ID_PM8921_L4_0 = 53, + MSM_RPM_STATUS_ID_PM8921_L4_1 = 54, + MSM_RPM_STATUS_ID_PM8921_L5_0 = 55, + MSM_RPM_STATUS_ID_PM8921_L5_1 = 56, + MSM_RPM_STATUS_ID_PM8921_L6_0 = 57, + MSM_RPM_STATUS_ID_PM8921_L6_1 = 58, + MSM_RPM_STATUS_ID_PM8921_L7_0 = 59, + MSM_RPM_STATUS_ID_PM8921_L7_1 = 60, + MSM_RPM_STATUS_ID_PM8921_L8_0 = 61, + MSM_RPM_STATUS_ID_PM8921_L8_1 = 62, + MSM_RPM_STATUS_ID_PM8921_L9_0 = 63, + MSM_RPM_STATUS_ID_PM8921_L9_1 = 64, + MSM_RPM_STATUS_ID_PM8921_L10_0 = 65, + MSM_RPM_STATUS_ID_PM8921_L10_1 = 66, + MSM_RPM_STATUS_ID_PM8921_L11_0 = 67, + MSM_RPM_STATUS_ID_PM8921_L11_1 = 68, + MSM_RPM_STATUS_ID_PM8921_L12_0 = 69, + MSM_RPM_STATUS_ID_PM8921_L12_1 = 70, + MSM_RPM_STATUS_ID_PM8921_L13_0 = 71, + MSM_RPM_STATUS_ID_PM8921_L13_1 = 72, + MSM_RPM_STATUS_ID_PM8921_L14_0 = 73, + MSM_RPM_STATUS_ID_PM8921_L14_1 = 74, + MSM_RPM_STATUS_ID_PM8921_L15_0 = 75, + MSM_RPM_STATUS_ID_PM8921_L15_1 = 76, + MSM_RPM_STATUS_ID_PM8921_L16_0 = 77, + MSM_RPM_STATUS_ID_PM8921_L16_1 = 78, + MSM_RPM_STATUS_ID_PM8921_L17_0 = 79, + MSM_RPM_STATUS_ID_PM8921_L17_1 = 80, + MSM_RPM_STATUS_ID_PM8921_L18_0 = 81, + MSM_RPM_STATUS_ID_PM8921_L18_1 = 82, + MSM_RPM_STATUS_ID_PM8921_L19_0 = 83, + MSM_RPM_STATUS_ID_PM8921_L19_1 = 84, + MSM_RPM_STATUS_ID_PM8921_L20_0 = 85, + MSM_RPM_STATUS_ID_PM8921_L20_1 = 86, + MSM_RPM_STATUS_ID_PM8921_L21_0 = 87, + MSM_RPM_STATUS_ID_PM8921_L21_1 = 88, + MSM_RPM_STATUS_ID_PM8921_L22_0 = 89, + MSM_RPM_STATUS_ID_PM8921_L22_1 = 90, + MSM_RPM_STATUS_ID_PM8921_L23_0 = 91, + MSM_RPM_STATUS_ID_PM8921_L23_1 = 92, + MSM_RPM_STATUS_ID_PM8921_L24_0 = 93, + MSM_RPM_STATUS_ID_PM8921_L24_1 = 94, + MSM_RPM_STATUS_ID_PM8921_L25_0 = 95, + MSM_RPM_STATUS_ID_PM8921_L25_1 = 96, + MSM_RPM_STATUS_ID_PM8921_L26_0 = 97, + MSM_RPM_STATUS_ID_PM8921_L26_1 = 98, + MSM_RPM_STATUS_ID_PM8921_L27_0 = 99, + MSM_RPM_STATUS_ID_PM8921_L27_1 = 100, + MSM_RPM_STATUS_ID_PM8921_L28_0 = 101, + MSM_RPM_STATUS_ID_PM8921_L28_1 = 102, + MSM_RPM_STATUS_ID_PM8921_L29_0 = 103, + MSM_RPM_STATUS_ID_PM8921_L29_1 = 104, + MSM_RPM_STATUS_ID_PM8921_CLK1_0 = 105, + MSM_RPM_STATUS_ID_PM8921_CLK1_1 = 106, + MSM_RPM_STATUS_ID_PM8921_CLK2_0 = 107, + MSM_RPM_STATUS_ID_PM8921_CLK2_1 = 108, + MSM_RPM_STATUS_ID_PM8921_LVS1 = 109, + MSM_RPM_STATUS_ID_PM8921_LVS2 = 110, + MSM_RPM_STATUS_ID_PM8921_LVS3 = 111, + MSM_RPM_STATUS_ID_PM8921_LVS4 = 112, + MSM_RPM_STATUS_ID_PM8921_LVS5 = 113, + MSM_RPM_STATUS_ID_PM8921_LVS6 = 114, + MSM_RPM_STATUS_ID_PM8921_LVS7 = 115, + MSM_RPM_STATUS_ID_NCP_0 = 116, + MSM_RPM_STATUS_ID_NCP_1 = 117, + MSM_RPM_STATUS_ID_CXO_BUFFERS = 118, + MSM_RPM_STATUS_ID_USB_OTG_SWITCH = 119, + MSM_RPM_STATUS_ID_HDMI_SWITCH = 120, + + MSM_RPM_STATUS_ID_LAST = MSM_RPM_STATUS_ID_HDMI_SWITCH +}; + +#endif /* __ARCH_ARM_MACH_MSM_RPM_8960_H */ diff --git a/arch/arm/mach-msm/include/mach/rpm-regulator-8660.h b/arch/arm/mach-msm/include/mach/rpm-regulator-8660.h new file mode 100644 index 00000000000..3bcebd40848 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator-8660.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPM_REGULATOR_8660_H +#define __ARCH_ARM_MACH_MSM_RPM_REGULATOR_8660_H + +#define RPM_VREG_PIN_CTRL_NONE 0x00 +#define RPM_VREG_PIN_CTRL_A0 0x01 +#define RPM_VREG_PIN_CTRL_A1 0x02 +#define RPM_VREG_PIN_CTRL_D0 0x04 +#define RPM_VREG_PIN_CTRL_D1 0x08 + +/* + * Pin Function + * ENABLE - pin control switches between disable and enable + * MODE - pin control switches between LPM and HPM + * SLEEP_B - regulator is forced into LPM by asserting sleep_b signal + * NONE - do not use pin control + * + * The pin function specified in platform data corresponds to the active state + * pin function value. Pin function will be NONE until a consumer requests + * pin control with regulator_set_mode(vreg, REGULATOR_MODE_IDLE). + */ +enum rpm_vreg_pin_fn { + RPM_VREG_PIN_FN_ENABLE = 0, + RPM_VREG_PIN_FN_MODE, + RPM_VREG_PIN_FN_SLEEP_B, + RPM_VREG_PIN_FN_NONE, +}; + +enum rpm_vreg_mode { + RPM_VREG_MODE_PIN_CTRL = 0, + RPM_VREG_MODE_NONE = 0, + RPM_VREG_MODE_LPM, + RPM_VREG_MODE_HPM, +}; + +enum rpm_vreg_state { + RPM_VREG_STATE_OFF = 0, + RPM_VREG_STATE_ON, +}; + +enum rpm_vreg_freq { + RPM_VREG_FREQ_NONE, + RPM_VREG_FREQ_19p20, + RPM_VREG_FREQ_9p60, + RPM_VREG_FREQ_6p40, + RPM_VREG_FREQ_4p80, + RPM_VREG_FREQ_3p84, + RPM_VREG_FREQ_3p20, + RPM_VREG_FREQ_2p74, + RPM_VREG_FREQ_2p40, + RPM_VREG_FREQ_2p13, + RPM_VREG_FREQ_1p92, + RPM_VREG_FREQ_1p75, + RPM_VREG_FREQ_1p60, + RPM_VREG_FREQ_1p48, + RPM_VREG_FREQ_1p37, + RPM_VREG_FREQ_1p28, + RPM_VREG_FREQ_1p20, +}; + +enum rpm_vreg_id { + RPM_VREG_ID_PM8058_L0 = 0, + RPM_VREG_ID_PM8058_L1, + RPM_VREG_ID_PM8058_L2, + RPM_VREG_ID_PM8058_L3, + RPM_VREG_ID_PM8058_L4, + RPM_VREG_ID_PM8058_L5, + RPM_VREG_ID_PM8058_L6, + RPM_VREG_ID_PM8058_L7, + RPM_VREG_ID_PM8058_L8, + RPM_VREG_ID_PM8058_L9, + RPM_VREG_ID_PM8058_L10, + RPM_VREG_ID_PM8058_L11, + RPM_VREG_ID_PM8058_L12, + RPM_VREG_ID_PM8058_L13, + RPM_VREG_ID_PM8058_L14, + RPM_VREG_ID_PM8058_L15, + RPM_VREG_ID_PM8058_L16, + RPM_VREG_ID_PM8058_L17, + RPM_VREG_ID_PM8058_L18, + RPM_VREG_ID_PM8058_L19, + RPM_VREG_ID_PM8058_L20, + RPM_VREG_ID_PM8058_L21, + RPM_VREG_ID_PM8058_L22, + RPM_VREG_ID_PM8058_L23, + RPM_VREG_ID_PM8058_L24, + RPM_VREG_ID_PM8058_L25, + RPM_VREG_ID_PM8058_S0, + RPM_VREG_ID_PM8058_S1, + RPM_VREG_ID_PM8058_S2, + RPM_VREG_ID_PM8058_S3, + RPM_VREG_ID_PM8058_S4, + RPM_VREG_ID_PM8058_LVS0, + RPM_VREG_ID_PM8058_LVS1, + RPM_VREG_ID_PM8058_NCP, + RPM_VREG_ID_PM8901_L0, + RPM_VREG_ID_PM8901_L1, + RPM_VREG_ID_PM8901_L2, + RPM_VREG_ID_PM8901_L3, + RPM_VREG_ID_PM8901_L4, + RPM_VREG_ID_PM8901_L5, + RPM_VREG_ID_PM8901_L6, + RPM_VREG_ID_PM8901_S0, + RPM_VREG_ID_PM8901_S1, + RPM_VREG_ID_PM8901_S2, + RPM_VREG_ID_PM8901_S3, + RPM_VREG_ID_PM8901_S4, + RPM_VREG_ID_PM8901_LVS0, + RPM_VREG_ID_PM8901_LVS1, + RPM_VREG_ID_PM8901_LVS2, + RPM_VREG_ID_PM8901_LVS3, + RPM_VREG_ID_PM8901_MVS0, + RPM_VREG_ID_MAX, +}; + +/* Minimum high power mode loads in uA. */ +#define RPM_VREG_LDO_50_HPM_MIN_LOAD 5000 +#define RPM_VREG_LDO_150_HPM_MIN_LOAD 10000 +#define RPM_VREG_LDO_300_HPM_MIN_LOAD 10000 +#define RPM_VREG_SMPS_HPM_MIN_LOAD 50000 +#define RPM_VREG_FTSMPS_HPM_MIN_LOAD 100000 + +/* + * default_uV = initial voltage to set the regulator to if enable is called + * before set_voltage (e.g. when boot_on or always_on is set). + * peak_uA = initial load requirement sent in RPM request; used to determine + * initial mode. + * avg_uA = initial avg load requirement sent in RPM request; overwritten + * along with peak_uA when regulator_set_mode or + * regulator_set_optimum_mode is called. + * pin_fn = RPM_VREG_PIN_FN_ENABLE - pin control ON/OFF + * = RPM_VREG_PIN_FN_MODE - pin control LPM/HPM + * = RPM_VREG_PIN_FN_SLEEP_B - regulator is forced into LPM by + * asserting sleep_b signal + * = RPM_VREG_PIN_FN_NONE - do not use pin control + * mode = used to specify a force mode which overrides the votes of other + * RPM masters. + * state = initial state sent in RPM request. + * sleep_selectable = flag which indicates that regulator should be accessable + * by external private API and that spinlocks should be used. + */ +struct rpm_vreg_pdata { + struct regulator_init_data init_data; + int default_uV; + unsigned peak_uA; + unsigned avg_uA; + unsigned pull_down_enable; + unsigned pin_ctrl; + enum rpm_vreg_freq freq; + enum rpm_vreg_pin_fn pin_fn; + enum rpm_vreg_mode mode; + enum rpm_vreg_state state; + int sleep_selectable; +}; + +enum rpm_vreg_voter { + RPM_VREG_VOTER_REG_FRAMEWORK = 0, /* for internal use only */ + RPM_VREG_VOTER1, /* for use by the acpu-clock driver */ + RPM_VREG_VOTER2, /* for use by the acpu-clock driver */ + RPM_VREG_VOTER3, /* for use by other drivers */ + RPM_VREG_VOTER_COUNT, +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/rpm-regulator-8960.h b/arch/arm/mach-msm/include/mach/rpm-regulator-8960.h new file mode 100644 index 00000000000..5b3e00eb7fc --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator-8960.h @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPM_REGULATOR_8960_H +#define __ARCH_ARM_MACH_MSM_RPM_REGULATOR_8960_H + +/* Pin control input signals. */ +#define RPM_VREG_PIN_CTRL_NONE 0x00 +#define RPM_VREG_PIN_CTRL_EN0 0x01 +#define RPM_VREG_PIN_CTRL_EN1 0x02 +#define RPM_VREG_PIN_CTRL_EN2 0x04 +#define RPM_VREG_PIN_CTRL_EN3 0x08 +#define RPM_VREG_PIN_CTRL_ALL 0x0F + +#define RPM_VREG_PIN_CTRL_PM8921_D1 RPM_VREG_PIN_CTRL_EN0 +#define RPM_VREG_PIN_CTRL_PM8921_A0 RPM_VREG_PIN_CTRL_EN1 +#define RPM_VREG_PIN_CTRL_PM8921_A1 RPM_VREG_PIN_CTRL_EN2 +#define RPM_VREG_PIN_CTRL_PM8921_A2 RPM_VREG_PIN_CTRL_EN3 + +/** + * enum rpm_vreg_pin_fn - RPM regulator pin function choices + * %RPM_VREG_PIN_FN_DONT_CARE: do not care about pin control state of the + * regulator; allow another master processor to + * specify pin control + * %RPM_VREG_PIN_FN_ENABLE: pin control switches between disable and enable + * %RPM_VREG_PIN_FN_MODE: pin control switches between LPM and HPM + * %RPM_VREG_PIN_FN_SLEEP_B: regulator is forced into LPM when sleep_b signal + * is asserted + * %RPM_VREG_PIN_FN_NONE: do not use pin control for the regulator and do + * not allow another master to request pin control + * + * The pin function specified in platform data corresponds to the active state + * pin function value. Pin function will be NONE until a consumer requests + * pin control to be enabled. + */ +enum rpm_vreg_pin_fn { + RPM_VREG_PIN_FN_DONT_CARE, + RPM_VREG_PIN_FN_ENABLE, + RPM_VREG_PIN_FN_MODE, + RPM_VREG_PIN_FN_SLEEP_B, + RPM_VREG_PIN_FN_NONE, +}; + +/** + * enum rpm_vreg_force_mode - RPM regulator force mode choices + * %RPM_VREG_FORCE_MODE_PIN_CTRL: allow pin control usage + * %RPM_VREG_FORCE_MODE_NONE: do not force any mode + * %RPM_VREG_FORCE_MODE_LPM: force into low power mode + * %RPM_VREG_FORCE_MODE_AUTO: allow regulator to automatically select its + * own mode based on realtime current draw + * (only available for SMPS regulators) + * %RPM_VREG_FORCE_MODE_HPM: force into high power mode + * %RPM_VREG_FORCE_MODE_BYPASS: set regulator to use bypass mode, i.e. to act + * as a switch and not regulate (only available + * for LDO regulators) + * + * Force mode is used to override aggregation with other masters and to set + * special operating modes. + */ +enum rpm_vreg_force_mode { + RPM_VREG_FORCE_MODE_PIN_CTRL = 0, + RPM_VREG_FORCE_MODE_NONE = 0, + RPM_VREG_FORCE_MODE_LPM, + RPM_VREG_FORCE_MODE_AUTO, /* SMPS only */ + RPM_VREG_FORCE_MODE_HPM, + RPM_VREG_FORCE_MODE_BYPASS, /* LDO only */ +}; + +/** + * enum rpm_vreg_power_mode - power mode for SMPS regulators + * %RPM_VREG_POWER_MODE_HYSTERETIC: Use hysteretic mode for HPM and when + * usage goes high in AUTO + * %RPM_VREG_POWER_MODE_PWM: Use PWM mode for HPM and when usage goes + * high in AUTO + */ +enum rpm_vreg_power_mode { + RPM_VREG_POWER_MODE_HYSTERETIC, + RPM_VREG_POWER_MODE_PWM, +}; + +/** + * enum rpm_vreg_state - enable state for switch or NCP + */ +enum rpm_vreg_state { + RPM_VREG_STATE_OFF, + RPM_VREG_STATE_ON, +}; + +/** + * enum rpm_vreg_freq - switching frequency for SMPS or NCP + */ +enum rpm_vreg_freq { + RPM_VREG_FREQ_NONE, + RPM_VREG_FREQ_19p20, + RPM_VREG_FREQ_9p60, + RPM_VREG_FREQ_6p40, + RPM_VREG_FREQ_4p80, + RPM_VREG_FREQ_3p84, + RPM_VREG_FREQ_3p20, + RPM_VREG_FREQ_2p74, + RPM_VREG_FREQ_2p40, + RPM_VREG_FREQ_2p13, + RPM_VREG_FREQ_1p92, + RPM_VREG_FREQ_1p75, + RPM_VREG_FREQ_1p60, + RPM_VREG_FREQ_1p48, + RPM_VREG_FREQ_1p37, + RPM_VREG_FREQ_1p28, + RPM_VREG_FREQ_1p20, +}; + +/** + * enum rpm_vreg_id - RPM regulator ID numbers (both real and pin control) + */ +enum rpm_vreg_id { + RPM_VREG_ID_PM8921_L1, + RPM_VREG_ID_PM8921_L2, + RPM_VREG_ID_PM8921_L3, + RPM_VREG_ID_PM8921_L4, + RPM_VREG_ID_PM8921_L5, + RPM_VREG_ID_PM8921_L6, + RPM_VREG_ID_PM8921_L7, + RPM_VREG_ID_PM8921_L8, + RPM_VREG_ID_PM8921_L9, + RPM_VREG_ID_PM8921_L10, + RPM_VREG_ID_PM8921_L11, + RPM_VREG_ID_PM8921_L12, + RPM_VREG_ID_PM8921_L14, + RPM_VREG_ID_PM8921_L15, + RPM_VREG_ID_PM8921_L16, + RPM_VREG_ID_PM8921_L17, + RPM_VREG_ID_PM8921_L18, + RPM_VREG_ID_PM8921_L21, + RPM_VREG_ID_PM8921_L22, + RPM_VREG_ID_PM8921_L23, + RPM_VREG_ID_PM8921_L24, + RPM_VREG_ID_PM8921_L25, + RPM_VREG_ID_PM8921_L26, + RPM_VREG_ID_PM8921_L27, + RPM_VREG_ID_PM8921_L28, + RPM_VREG_ID_PM8921_L29, + RPM_VREG_ID_PM8921_S1, + RPM_VREG_ID_PM8921_S2, + RPM_VREG_ID_PM8921_S3, + RPM_VREG_ID_PM8921_S4, + RPM_VREG_ID_PM8921_S5, + RPM_VREG_ID_PM8921_S6, + RPM_VREG_ID_PM8921_S7, + RPM_VREG_ID_PM8921_S8, + RPM_VREG_ID_PM8921_LVS1, + RPM_VREG_ID_PM8921_LVS2, + RPM_VREG_ID_PM8921_LVS3, + RPM_VREG_ID_PM8921_LVS4, + RPM_VREG_ID_PM8921_LVS5, + RPM_VREG_ID_PM8921_LVS6, + RPM_VREG_ID_PM8921_LVS7, + RPM_VREG_ID_PM8921_USB_OTG, + RPM_VREG_ID_PM8921_HDMI_MVS, + RPM_VREG_ID_PM8921_NCP, + RPM_VREG_ID_PM8921_MAX_REAL = RPM_VREG_ID_PM8921_NCP, + + /* The following are IDs for regulator devices to enable pin control. */ + RPM_VREG_ID_PM8921_L1_PC, + RPM_VREG_ID_PM8921_L2_PC, + RPM_VREG_ID_PM8921_L3_PC, + RPM_VREG_ID_PM8921_L4_PC, + RPM_VREG_ID_PM8921_L5_PC, + RPM_VREG_ID_PM8921_L6_PC, + RPM_VREG_ID_PM8921_L7_PC, + RPM_VREG_ID_PM8921_L8_PC, + RPM_VREG_ID_PM8921_L9_PC, + RPM_VREG_ID_PM8921_L10_PC, + RPM_VREG_ID_PM8921_L11_PC, + RPM_VREG_ID_PM8921_L12_PC, + RPM_VREG_ID_PM8921_L14_PC, + RPM_VREG_ID_PM8921_L15_PC, + RPM_VREG_ID_PM8921_L16_PC, + RPM_VREG_ID_PM8921_L17_PC, + RPM_VREG_ID_PM8921_L18_PC, + RPM_VREG_ID_PM8921_L21_PC, + RPM_VREG_ID_PM8921_L22_PC, + RPM_VREG_ID_PM8921_L23_PC, + + RPM_VREG_ID_PM8921_L29_PC, + RPM_VREG_ID_PM8921_S1_PC, + RPM_VREG_ID_PM8921_S2_PC, + RPM_VREG_ID_PM8921_S3_PC, + RPM_VREG_ID_PM8921_S4_PC, + + RPM_VREG_ID_PM8921_S7_PC, + RPM_VREG_ID_PM8921_S8_PC, + RPM_VREG_ID_PM8921_LVS1_PC, + + RPM_VREG_ID_PM8921_LVS3_PC, + RPM_VREG_ID_PM8921_LVS4_PC, + RPM_VREG_ID_PM8921_LVS5_PC, + RPM_VREG_ID_PM8921_LVS6_PC, + RPM_VREG_ID_PM8921_LVS7_PC, + + RPM_VREG_ID_PM8921_MAX = RPM_VREG_ID_PM8921_LVS7_PC, +}; + +/* Minimum high power mode loads in uA. */ +#define RPM_VREG_LDO_50_HPM_MIN_LOAD 5000 +#define RPM_VREG_LDO_150_HPM_MIN_LOAD 10000 +#define RPM_VREG_LDO_300_HPM_MIN_LOAD 10000 +#define RPM_VREG_LDO_600_HPM_MIN_LOAD 10000 +#define RPM_VREG_LDO_1200_HPM_MIN_LOAD 10000 +#define RPM_VREG_SMPS_1500_HPM_MIN_LOAD 100000 +#define RPM_VREG_SMPS_2000_HPM_MIN_LOAD 100000 + +/** + * struct rpm_regulator_init_data - RPM regulator initialization data + * @init_data: regulator constraints + * @id: regulator id; from enum rpm_vreg_id + * @sleep_selectable: flag which indicates that regulator should be accessable + * by external private API and that spinlocks should be + * used instead of mutex locks + * @system_uA: current drawn from regulator not accounted for by any + * regulator framework consumer + * @pull_down_enable: 0 = no pulldown, 1 = pulldown when regulator disabled + * @freq: enum value representing the switching frequency of an + * SMPS or NCP + * @pin_ctrl: pin control inputs to use for the regulator; should be + * a combination of RPM_VREG_PIN_CTRL_* values + * @pin_fn: action to perform when pin control pin(s) is/are active + * @force_mode: used to specify a force mode which overrides the votes + * of other RPM masters. + * @default_uV: initial voltage to set the regulator to if enable is + * called before set_voltage (e.g. when boot_on or + * always_on is set). + * @peak_uA: initial peak load requirement sent in RPM request; used + * to determine initial mode. + * @avg_uA: average load requirement sent in RPM request + * @state: initial enable state sent in RPM request for switch or + * NCP + */ +struct rpm_regulator_init_data { + struct regulator_init_data init_data; + enum rpm_vreg_id id; + int sleep_selectable; + int system_uA; + unsigned pull_down_enable; + enum rpm_vreg_freq freq; + unsigned pin_ctrl; + enum rpm_vreg_pin_fn pin_fn; + enum rpm_vreg_force_mode force_mode; + enum rpm_vreg_power_mode power_mode; + int default_uV; + unsigned peak_uA; + unsigned avg_uA; + enum rpm_vreg_state state; +}; + +/** + * struct rpm_regulator_platform_data - RPM regulator platform data + */ +struct rpm_regulator_platform_data { + struct rpm_regulator_init_data *init_data; + int num_regulators; +}; + +/** + * enum rpm_vreg_voter - RPM regulator voter IDs for private APIs + */ +enum rpm_vreg_voter { + RPM_VREG_VOTER_REG_FRAMEWORK, /* for internal use only */ + RPM_VREG_VOTER1, /* for use by the acpu-clock driver */ + RPM_VREG_VOTER2, /* for use by the acpu-clock driver */ + RPM_VREG_VOTER3, /* for use by other drivers */ + RPM_VREG_VOTER_COUNT, +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/rpm-regulator.h b/arch/arm/mach-msm/include/mach/rpm-regulator.h new file mode 100644 index 00000000000..1187efc4e4c --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm-regulator.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPM_REGULATOR_H +#define __ARCH_ARM_MACH_MSM_RPM_REGULATOR_H + +#include + +#define RPM_REGULATOR_DEV_NAME "rpm-regulator" + +#if defined(CONFIG_ARCH_MSM8X60) +#include +#elif defined(CONFIG_ARCH_MSM8960) +#include +#endif + +/** + * rpm_vreg_set_voltage - vote for a min_uV value of specified regualtor + * @vreg: ID for regulator + * @voter: ID for the voter + * @min_uV: minimum acceptable voltage (in uV) that is voted for + * @max_uV: maximum acceptable voltage (in uV) that is voted for + * @sleep_also: 0 for active set only, non-0 for active set and sleep set + * + * Returns 0 on success or errno. + * + * This function is used to vote for the voltage of a regulator without + * using the regulator framework. It is needed by consumers which hold spin + * locks or have interrupts disabled because the regulator framework can sleep. + * It is also needed by consumers which wish to only vote for active set + * regulator voltage. + * + * If sleep_also == 0, then a sleep-set value of 0V will be voted for. + * + * This function may only be called for regulators which have the sleep flag + * specified in their private data. + */ +int rpm_vreg_set_voltage(enum rpm_vreg_id vreg_id, enum rpm_vreg_voter voter, + int min_uV, int max_uV, int sleep_also); + +/** + * rpm_vreg_set_frequency - sets the frequency of a switching regulator + * @vreg: ID for regulator + * @freq: enum corresponding to desired frequency + * + * Returns 0 on success or errno. + */ +int rpm_vreg_set_frequency(enum rpm_vreg_id vreg_id, enum rpm_vreg_freq freq); + +#endif diff --git a/arch/arm/mach-msm/include/mach/rpm.h b/arch/arm/mach-msm/include/mach/rpm.h new file mode 100644 index 00000000000..a4928c487c3 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/rpm.h @@ -0,0 +1,135 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPM_H +#define __ARCH_ARM_MACH_MSM_RPM_H + +#include +#include +#include + +#if defined(CONFIG_ARCH_MSM8X60) +#include +#elif defined(CONFIG_ARCH_MSM8960) +#include +#endif + + +enum { + MSM_RPM_PAGE_STATUS, + MSM_RPM_PAGE_CTRL, + MSM_RPM_PAGE_REQ, + MSM_RPM_PAGE_ACK, + MSM_RPM_PAGE_COUNT +}; + +enum { + MSM_RPM_CTX_SET_0, + MSM_RPM_CTX_SET_SLEEP, + MSM_RPM_CTX_SET_COUNT, + + MSM_RPM_CTX_NOTIFICATION = 30, + MSM_RPM_CTX_REJECTED = 31, +}; + +static inline uint32_t msm_rpm_get_ctx_mask(unsigned int ctx) +{ + return 1UL << ctx; +} + +#define MSM_RPM_SEL_MASK_SIZE (MSM_RPM_SEL_LAST / 32 + 1) + +static inline unsigned int msm_rpm_get_sel_mask_reg(unsigned int sel) +{ + return sel / 32; +} + +static inline uint32_t msm_rpm_get_sel_mask(unsigned int sel) +{ + return 1UL << (sel % 32); +} + +struct msm_rpm_iv_pair { + uint32_t id; + uint32_t value; +}; + +struct msm_rpm_notification { + struct list_head list; /* reserved for RPM use */ + struct semaphore sem; + uint32_t sel_masks[MSM_RPM_SEL_MASK_SIZE]; /* reserved for RPM use */ +}; + +struct msm_rpm_map_data { + uint32_t id; + uint32_t sel; + uint32_t count; +}; + +#define MSM_RPM_MAP(i, s, c) { \ + .id = MSM_RPM_ID_##i, .sel = MSM_RPM_SEL_##s, .count = c } + + +struct msm_rpm_platform_data { + void __iomem *reg_base_addrs[MSM_RPM_PAGE_COUNT]; + + unsigned int irq_ack; + unsigned int irq_err; + unsigned int irq_vmpm; + void *msm_apps_ipc_rpm_reg; + unsigned int msm_apps_ipc_rpm_val; +}; + +extern struct msm_rpm_map_data rpm_map_data[]; +extern unsigned int rpm_map_data_size; + +int msm_rpm_local_request_is_outstanding(void); +int msm_rpm_get_status(struct msm_rpm_iv_pair *status, int count); +int msm_rpm_set(int ctx, struct msm_rpm_iv_pair *req, int count); +int msm_rpm_set_noirq(int ctx, struct msm_rpm_iv_pair *req, int count); + +static inline int msm_rpm_set_nosleep( + int ctx, struct msm_rpm_iv_pair *req, int count) +{ + unsigned long flags; + int rc; + + local_irq_save(flags); + rc = msm_rpm_set_noirq(ctx, req, count); + local_irq_restore(flags); + + return rc; +} + +int msm_rpm_clear(int ctx, struct msm_rpm_iv_pair *req, int count); +int msm_rpm_clear_noirq(int ctx, struct msm_rpm_iv_pair *req, int count); + +static inline int msm_rpm_clear_nosleep( + int ctx, struct msm_rpm_iv_pair *req, int count) +{ + unsigned long flags; + int rc; + + local_irq_save(flags); + rc = msm_rpm_clear_noirq(ctx, req, count); + local_irq_restore(flags); + + return rc; +} + +int msm_rpm_register_notification(struct msm_rpm_notification *n, + struct msm_rpm_iv_pair *req, int count); +int msm_rpm_unregister_notification(struct msm_rpm_notification *n); +int msm_rpm_init(struct msm_rpm_platform_data *data); + +#endif /* __ARCH_ARM_MACH_MSM_RPM_H */ diff --git a/arch/arm/mach-msm/include/mach/scm-io.h b/arch/arm/mach-msm/include/mach/scm-io.h new file mode 100644 index 00000000000..5393da1020d --- /dev/null +++ b/arch/arm/mach-msm/include/mach/scm-io.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __MACH_SCM_IO_H +#define __MACH_SCM_IO_H + +#include + +#ifdef CONFIG_MSM_SECURE_IO + +extern u32 secure_readl(void __iomem *c); +extern void secure_writel(u32 v, void __iomem *c); + +#else + +#define secure_readl(c) readl(c) +#define secure_writel(v, c) writel(v, c) + +#endif + +#endif diff --git a/arch/arm/mach-msm/include/mach/scm.h b/arch/arm/mach-msm/include/mach/scm.h new file mode 100644 index 00000000000..6539ddaf5f3 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/scm.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __MACH_SCM_H +#define __MACH_SCM_H + +#define SCM_SVC_BOOT 0x1 +#define SCM_SVC_PIL 0x2 +#define SCM_SVC_UTIL 0x3 +#define SCM_SVC_TZ 0x4 +#define SCM_SVC_TZSCHEDULER 0xFC + +extern int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, size_t cmd_len, + void *resp_buf, size_t resp_len); + +#define SCM_VERSION(major, minor) (((major) << 16) | ((minor) & 0xFF)) + +extern u32 scm_get_version(void); + +#endif diff --git a/arch/arm/mach-msm/include/mach/sdio_al.h b/arch/arm/mach-msm/include/mach/sdio_al.h new file mode 100644 index 00000000000..e7cd7814b27 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sdio_al.h @@ -0,0 +1,114 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * SDIO-Abstraction-Layer API. + */ + +#ifndef __SDIO_AL__ +#define __SDIO_AL__ + +#include + +struct sdio_channel; /* Forward Declaration */ + +/** + * Channel Events. + * Available bytes notification. + */ +#define SDIO_EVENT_DATA_READ_AVAIL 0x01 +#define SDIO_EVENT_DATA_WRITE_AVAIL 0x02 + +struct sdio_al_platform_data { + int (*config_mdm2ap_status)(int); + int (*get_mdm2ap_status)(void); + int allow_sdioc_version_major_2; + int peer_sdioc_version_minor; + int peer_sdioc_version_major; + int peer_sdioc_boot_version_minor; + int peer_sdioc_boot_version_major; +}; + +/** + * sdio_open - open a channel for read/write data. + * + * @name: channel name - identify the channel to open. + * @ch: channel handle returned. + * @priv: caller private context pointer, passed to the notify callback. + * @notify: notification callback for data available. + * @channel_event: SDIO_EVENT_DATA_READ_AVAIL or SDIO_EVENT_DATA_WRITE_AVAIL + * @return 0 on success, negative value on error. + * + * Warning: notify() may be called before open returns. + */ +int sdio_open(const char *name, struct sdio_channel **ch, void *priv, + void (*notify)(void *priv, unsigned channel_event)); + + +/** + * sdio_close - close a channel. + * + * @ch: channel handle. + * @return 0 on success, negative value on error. + */ +int sdio_close(struct sdio_channel *ch); + +/** + * sdio_read - synchronous read. + * + * @ch: channel handle. + * @data: caller buffer pointer. should be non-cacheable. + * @len: byte count. + * @return 0 on success, negative value on error. + * + * May wait if no available bytes. + * May wait if other channel with higher priority has pending + * transfers. + * Client should check available bytes prior to calling this + * api. + */ +int sdio_read(struct sdio_channel *ch, void *data, int len); + +/** + * sdio_write - synchronous write. + * + * @ch: channel handle. + * @data: caller buffer pointer. should be non-cacheable. + * @len: byte count. + * @return 0 on success, negative value on error. + * + * May wait if no available bytes. + * May wait if other channel with higher priority has pending + * transfers. + * Client should check available bytes prior to calling this + * api. + */ +int sdio_write(struct sdio_channel *ch, const void *data, int len); + +/** + * sdio_write_avail - get available bytes to write. + * + * @ch: channel handle. + * @return byte count on success, negative value on error. + */ +int sdio_write_avail(struct sdio_channel *ch); + +/** + * sdio_read_avail - get available bytes to read. + * + * @ch: channel handle. + * @return byte count on success, negative value on error. + */ +int sdio_read_avail(struct sdio_channel *ch); + +#endif /* __SDIO_AL__ */ diff --git a/arch/arm/mach-msm/include/mach/sdio_cmux.h b/arch/arm/mach-msm/include/mach/sdio_cmux.h new file mode 100644 index 00000000000..2a546e7f880 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sdio_cmux.h @@ -0,0 +1,97 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * SDIO CMUX API + */ + +#ifndef __SDIO_CMUX__ +#define __SDIO_CMUX__ + +enum { + SDIO_CMUX_DATA_CTL_0, + SDIO_CMUX_DATA_CTL_1, + SDIO_CMUX_DATA_CTL_2, + SDIO_CMUX_DATA_CTL_3, + SDIO_CMUX_DATA_CTL_4, + SDIO_CMUX_DATA_CTL_5, + SDIO_CMUX_DATA_CTL_6, + SDIO_CMUX_DATA_CTL_7, + SDIO_CMUX_USB_CTL_0, + SDIO_CMUX_USB_DUN_CTL_0, + SDIO_CMUX_NUM_CHANNELS +}; + + +/* + * sdio_cmux_open - Open the mux channel + * + * @id: Mux Channel id to be opened + * @receive_cb: Notification when data arrives. Parameters are data received, + * size of data, private context pointer. + * @write_done: Notification when data is written. Parameters are data written, + * size of data, private context pointer. Please note that the data + * written pointer will always be NULL as the cmux makes an internal copy + * of the data. + * @priv: caller's private context pointer + */ +int sdio_cmux_open(const int id, + void (*receive_cb)(void *, int, void *), + void (*write_done)(void *, int, void *), + void (*status_callback)(int, void *), + void *priv); + +/* + * sdio_cmux_close - Close the mux channel + * + * @id: Channel id to be closed + */ +int sdio_cmux_close(int id); + +/* + * sdio_cmux_write_avail - Write space avaialable for this channel + * + * @id: Channel id to look for the available write space + */ +int sdio_cmux_write_avail(int id); + +/* + * sdio_cmux_write - Write the data onto the CMUX channel + * + * @id: Channel id onto which the data has to be written + * @data: Starting address of the data buffer to be written + * @len: Length of the data to be written + */ +int sdio_cmux_write(int id, void *data, int len); + +/* these are used to get and set the IF sigs of a channel. + * DTR and RTS can be set; DSR, CTS, CD and RI can be read. + */ +int sdio_cmux_tiocmget(int id); +int sdio_cmux_tiocmset(int id, unsigned int set, unsigned int clear); + +/* + * is_remote_open - Check whether the remote channel is open + * + * @id: Channel id to be checked + */ +int is_remote_open(int id); + +/* + * sdio_cmux_is_channel_reset - Check whether the channel is in reset state + * + * @id: Channel id to be checked + */ +int sdio_cmux_is_channel_reset(int id); + +#endif /* __SDIO_CMUX__ */ diff --git a/arch/arm/mach-msm/include/mach/sdio_dmux.h b/arch/arm/mach-msm/include/mach/sdio_dmux.h new file mode 100644 index 00000000000..8ec31bb55fa --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sdio_dmux.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#ifndef _SDIO_DMUX_H +#define _SDIO_DMUX_H + +enum { + SDIO_DMUX_DATA_RMNET_0, + SDIO_DMUX_DATA_RMNET_1, + SDIO_DMUX_DATA_RMNET_2, + SDIO_DMUX_DATA_RMNET_3, + SDIO_DMUX_DATA_RMNET_4, + SDIO_DMUX_DATA_RMNET_5, + SDIO_DMUX_DATA_RMNET_6, + SDIO_DMUX_DATA_RMNET_7, + SDIO_DMUX_USB_RMNET_0, + SDIO_DMUX_NUM_CHANNELS +}; + +int msm_sdio_dmux_open(uint32_t id, void *priv, + void (*receive_cb)(void *, struct sk_buff *), + void (*write_done)(void *, struct sk_buff *)); + +int msm_sdio_is_channel_in_reset(uint32_t id); + +int msm_sdio_dmux_close(uint32_t id); + +int msm_sdio_dmux_write(uint32_t id, struct sk_buff *skb); + +int msm_sdio_dmux_is_ch_full(uint32_t id); + +int msm_sdio_dmux_is_ch_low(uint32_t id); + +#endif /* _SDIO_DMUX_H */ diff --git a/arch/arm/mach-msm/include/mach/sdio_smem.h b/arch/arm/mach-msm/include/mach/sdio_smem.h new file mode 100644 index 00000000000..b47001f09b1 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sdio_smem.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __SDIO_SMEM_H +#define __SDIO_SMEM_H + +#include +#include + +#define SDIO_SMEM_EVENT_READ_DONE 0 +#define SDIO_SMEM_EVENT_READ_ERR 1 + +int sdio_smem_register_client(void); +int sdio_smem_unregister_client(void); + +struct sdio_smem_client { + void *buf; + int size; + struct platform_device plat_dev; + int (*cb_func)(int event); +}; + +#endif /* __SDIO_SMEM_H */ diff --git a/arch/arm/mach-msm/include/mach/sdio_tty.h b/arch/arm/mach-msm/include/mach/sdio_tty.h new file mode 100644 index 00000000000..86746b37fe1 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sdio_tty.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * SDIO TTY interface. + */ + +#ifndef __SDIO_TTY__ +#define __SDIO_TTY__ + +/** + * sdio_tty_init_tty - Initialize the SDIO TTY driver. + * + * @tty_name: tty name - identify the tty device. + * @sdio_ch_name: channel name - identify the channel. + * @return sdio_tty handle on success, NULL on error. + * + */ +void *sdio_tty_init_tty(char *tty_name, char* sdio_ch_name); + +/** + * sdio_tty_uninit_tty - Uninitialize the SDIO TTY driver. + * + * @sdio_tty_handle: sdio_tty handle. + * @return 0 on success, negative value on error. + */ +int sdio_tty_uninit_tty(void *sdio_tty_handle); + +/** + * sdio_tty_enable_debug_msg - Enable/Disable sdio_tty debug + * messages. + * + * @enable: A flag to indicate if to enable or disable the debug + * messages. + * @return 0 on success, negative value on error. + */ +void sdio_tty_enable_debug_msg(void *sdio_tty_handle, int enable); + +#endif /* __SDIO_TTY__ */ diff --git a/arch/arm/mach-msm/include/mach/sirc-fsm9xxx.h b/arch/arm/mach-msm/include/mach/sirc-fsm9xxx.h new file mode 100644 index 00000000000..b86221103da --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sirc-fsm9xxx.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_MSM_SIRC_FSM9XXX_H +#define __ASM_ARCH_MSM_SIRC_FSM9XXX_H + +/* Group A */ +#define INT_EBI2_WR_ER_DONE (FIRST_SIRC_IRQ + 0) +#define INT_EBI2_OP_DONE (FIRST_SIRC_IRQ + 1) +#define INT_SDC1_0 (FIRST_SIRC_IRQ + 2) +#define INT_SDC1_1 (FIRST_SIRC_IRQ + 3) +#define INT_UARTDM (FIRST_SIRC_IRQ + 4) +#define INT_UART1 (FIRST_SIRC_IRQ + 5) +/* RESERVED 6 */ +#define INT_CE (FIRST_SIRC_IRQ + 7) +#define INT_SYS_ENZO_IEQ (FIRST_SIRC_IRQ + 8) +#define INT_PERPH_ENZO (FIRST_SIRC_IRQ + 9) +#define INT_MXBAR_ENZO (FIRST_SIRC_IRQ + 10) +#define INT_AXIAGG_ENZO (FIRST_SIRC_IRQ + 11) +#define INT_UART3 (FIRST_SIRC_IRQ + 12) +#define INT_UART2 (FIRST_SIRC_IRQ + 13) +#define INT_PORT0_SSBI2 (FIRST_SIRC_IRQ + 14) +#define INT_PORT1_SSBI2 (FIRST_SIRC_IRQ + 15) +#define INT_PORT2_SSBI2 (FIRST_SIRC_IRQ + 16) +#define INT_PORT3_SSBI2 (FIRST_SIRC_IRQ + 17) +#define INT_GSBI_QUP_INBUF (FIRST_SIRC_IRQ + 18) +#define INT_GSBI_QUP_OUTBUF (FIRST_SIRC_IRQ + 19) +#define INT_GSBI_QUP_ERROR (FIRST_SIRC_IRQ + 20) +#define INT_SPB_DECODER (FIRST_SIRC_IRQ + 21) +#define INT_FPB_DEC (FIRST_SIRC_IRQ + 22) +#define INT_BPM_HW (FIRST_SIRC_IRQ + 23) +#define INT_GPIO_167 (FIRST_SIRC_IRQ + 24) + +/* Group B */ +#define INT_GPIO_166 (FIRST_SIRC_IRQ + 25) +#define INT_GPIO_165 (FIRST_SIRC_IRQ + 26) +#define INT_GPIO_164 (FIRST_SIRC_IRQ + 27) +#define INT_GPIO_163 (FIRST_SIRC_IRQ + 28) +#define INT_GPIO_162 (FIRST_SIRC_IRQ + 29) +#define INT_GPIO_161 (FIRST_SIRC_IRQ + 30) +#define INT_GPIO_160 (FIRST_SIRC_IRQ + 31) +#define INT_GPIO_159 (FIRST_SIRC_IRQ + 32) +#define INT_GPIO_158 (FIRST_SIRC_IRQ + 33) +#define INT_GPIO_157 (FIRST_SIRC_IRQ + 34) +#define INT_GPIO_156 (FIRST_SIRC_IRQ + 35) +#define INT_GPIO_155 (FIRST_SIRC_IRQ + 36) +#define INT_GPIO_154 (FIRST_SIRC_IRQ + 37) +#define INT_GPIO_153 (FIRST_SIRC_IRQ + 38) +#define INT_GPIO_152 (FIRST_SIRC_IRQ + 39) +#define INT_GPIO_151 (FIRST_SIRC_IRQ + 40) +#define INT_GPIO_150 (FIRST_SIRC_IRQ + 41) +#define INT_GPIO_149 (FIRST_SIRC_IRQ + 42) +#define INT_GPIO_148 (FIRST_SIRC_IRQ + 43) +#define INT_GPIO_147 (FIRST_SIRC_IRQ + 44) +#define INT_GPIO_146 (FIRST_SIRC_IRQ + 45) +#define INT_GPIO_145 (FIRST_SIRC_IRQ + 46) +#define INT_GPIO_144 (FIRST_SIRC_IRQ + 47) +/* RESERVED 48 */ + +#define NR_SIRC_IRQS_GROUPA 25 +#define NR_SIRC_IRQS_GROUPB 24 +#define NR_SIRC_IRQS 49 +#define SIRC_MASK_GROUPA 0x01ffffff +#define SIRC_MASK_GROUPB 0x00ffffff + +#define SPSS_SIRC_INT_CLEAR (MSM_SIRC_BASE + 0x00) +#define SPSS_SIRC_INT_POLARITY (MSM_SIRC_BASE + 0x5C) +#define SPSS_SIRC_INT_SET (MSM_SIRC_BASE + 0x18) +#define SPSS_SIRC_INT_ENABLE (MSM_SIRC_BASE + 0x20) +#define SPSS_SIRC_IRQ_STATUS (MSM_SIRC_BASE + 0x38) +#define SPSS_SIRC_INT_TYPE (MSM_SIRC_BASE + 0x30) +#define SPSS_SIRC_VEC_INDEX_RD (MSM_SIRC_BASE + 0x48) + +#endif /* __ASM_ARCH_MSM_SIRC_FSM9XXX_H */ diff --git a/arch/arm/mach-msm/include/mach/sirc.h b/arch/arm/mach-msm/include/mach/sirc.h index ef55868a5b8..607bab5da5b 100644 --- a/arch/arm/mach-msm/include/mach/sirc.h +++ b/arch/arm/mach-msm/include/mach/sirc.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2011, Code Aurora Forum. 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 and @@ -25,13 +25,12 @@ struct sirc_regs_t { struct sirc_cascade_regs { void *int_status; unsigned int cascade_irq; + unsigned int cascade_fiq; }; void msm_init_sirc(void); -void msm_sirc_enter_sleep(void); -void msm_sirc_exit_sleep(void); -#if defined(CONFIG_ARCH_MSM_SCORPION) +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) #include @@ -41,6 +40,10 @@ void msm_sirc_exit_sleep(void); #define FIRST_SIRC_IRQ (NR_MSM_IRQS + NR_GPIO_IRQS) +#if defined(CONFIG_ARCH_FSM9XXX) +#include +#else /* CONFIG_ARCH_FSM9XXX */ + #define INT_UART1 (FIRST_SIRC_IRQ + 0) #define INT_UART2 (FIRST_SIRC_IRQ + 1) #define INT_UART3 (FIRST_SIRC_IRQ + 2) @@ -78,8 +81,6 @@ void msm_sirc_exit_sleep(void); #define SIRC_MASK 0x007FFFFF #endif -#define LAST_SIRC_IRQ (FIRST_SIRC_IRQ + NR_SIRC_IRQS - 1) - #define SPSS_SIRC_INT_SELECT (MSM_SIRC_BASE + 0x00) #define SPSS_SIRC_INT_ENABLE (MSM_SIRC_BASE + 0x04) #define SPSS_SIRC_INT_ENABLE_CLEAR (MSM_SIRC_BASE + 0x08) @@ -93,6 +94,10 @@ void msm_sirc_exit_sleep(void); #define SPSS_SIRC_INT_CLEAR (MSM_SIRC_BASE + 0x28) #define SPSS_SIRC_SOFT_INT (MSM_SIRC_BASE + 0x2C) -#endif +#endif /* CONFIG_ARCH_FSM9XXX */ + +#define LAST_SIRC_IRQ (FIRST_SIRC_IRQ + NR_SIRC_IRQS - 1) + +#endif /* CONFIG_ARCH_MSM_SCORPION */ #endif diff --git a/arch/arm/mach-msm/include/mach/smem_log.h b/arch/arm/mach-msm/include/mach/smem_log.h new file mode 100644 index 00000000000..b977a8272d5 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/smem_log.h @@ -0,0 +1,230 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + +#define SMEM_LOG_BASE 0x30 + +#define SMIOC_SETMODE _IOW(SMEM_LOG_BASE, 1, int) +#define SMIOC_SETLOG _IOW(SMEM_LOG_BASE, 2, int) + +#define SMIOC_TEXT 0x00000001 +#define SMIOC_BINARY 0x00000002 +#define SMIOC_LOG 0x00000003 +#define SMIOC_STATIC_LOG 0x00000004 + +/* Event indentifier format: + * bit 31-28 is processor ID 8 => apps, 4 => Q6, 0 => modem + * bits 27-16 are subsystem id (event base) + * bits 15-0 are event id + */ + +#define PROC 0xF0000000 +#define SUB 0x0FFF0000 +#define ID 0x0000FFFF + +#define SMEM_LOG_PROC_ID_MODEM 0x00000000 +#define SMEM_LOG_PROC_ID_Q6 0x40000000 +#define SMEM_LOG_PROC_ID_APPS 0x80000000 + +#define SMEM_LOG_CONT 0x10000000 + +#define SMEM_LOG_DEBUG_EVENT_BASE 0x00000000 +#define SMEM_LOG_ONCRPC_EVENT_BASE 0x00010000 +#define SMEM_LOG_SMEM_EVENT_BASE 0x00020000 +#define SMEM_LOG_TMC_EVENT_BASE 0x00030000 +#define SMEM_LOG_TIMETICK_EVENT_BASE 0x00040000 +#define SMEM_LOG_DEM_EVENT_BASE 0x00050000 +#define SMEM_LOG_ERROR_EVENT_BASE 0x00060000 +#define SMEM_LOG_DCVS_EVENT_BASE 0x00070000 +#define SMEM_LOG_SLEEP_EVENT_BASE 0x00080000 +#define SMEM_LOG_RPC_ROUTER_EVENT_BASE 0x00090000 +#if defined(CONFIG_MSM_N_WAY_SMSM) +#define DEM_SMSM_ISR (SMEM_LOG_DEM_EVENT_BASE + 0x1) +#define DEM_STATE_CHANGE (SMEM_LOG_DEM_EVENT_BASE + 0x2) +#define DEM_STATE_MACHINE_ENTER (SMEM_LOG_DEM_EVENT_BASE + 0x3) +#define DEM_ENTER_SLEEP (SMEM_LOG_DEM_EVENT_BASE + 0x4) +#define DEM_END_SLEEP (SMEM_LOG_DEM_EVENT_BASE + 0x5) +#define DEM_SETUP_SLEEP (SMEM_LOG_DEM_EVENT_BASE + 0x6) +#define DEM_SETUP_POWER_COLLAPSE (SMEM_LOG_DEM_EVENT_BASE + 0x7) +#define DEM_SETUP_SUSPEND (SMEM_LOG_DEM_EVENT_BASE + 0x8) +#define DEM_EARLY_EXIT (SMEM_LOG_DEM_EVENT_BASE + 0x9) +#define DEM_WAKEUP_REASON (SMEM_LOG_DEM_EVENT_BASE + 0xA) +#define DEM_DETECT_WAKEUP (SMEM_LOG_DEM_EVENT_BASE + 0xB) +#define DEM_DETECT_RESET (SMEM_LOG_DEM_EVENT_BASE + 0xC) +#define DEM_DETECT_SLEEPEXIT (SMEM_LOG_DEM_EVENT_BASE + 0xD) +#define DEM_DETECT_RUN (SMEM_LOG_DEM_EVENT_BASE + 0xE) +#define DEM_APPS_SWFI (SMEM_LOG_DEM_EVENT_BASE + 0xF) +#define DEM_SEND_WAKEUP (SMEM_LOG_DEM_EVENT_BASE + 0x10) +#define DEM_ASSERT_OKTS (SMEM_LOG_DEM_EVENT_BASE + 0x11) +#define DEM_NEGATE_OKTS (SMEM_LOG_DEM_EVENT_BASE + 0x12) +#define DEM_PROC_COMM_CMD (SMEM_LOG_DEM_EVENT_BASE + 0x13) +#define DEM_REMOVE_PROC_PWR (SMEM_LOG_DEM_EVENT_BASE + 0x14) +#define DEM_RESTORE_PROC_PWR (SMEM_LOG_DEM_EVENT_BASE + 0x15) +#define DEM_SMI_CLK_DISABLED (SMEM_LOG_DEM_EVENT_BASE + 0x16) +#define DEM_SMI_CLK_ENABLED (SMEM_LOG_DEM_EVENT_BASE + 0x17) +#define DEM_MAO_INTS (SMEM_LOG_DEM_EVENT_BASE + 0x18) +#define DEM_APPS_WAKEUP_INT (SMEM_LOG_DEM_EVENT_BASE + 0x19) +#define DEM_PROC_WAKEUP (SMEM_LOG_DEM_EVENT_BASE + 0x1A) +#define DEM_PROC_POWERUP (SMEM_LOG_DEM_EVENT_BASE + 0x1B) +#define DEM_TIMER_EXPIRED (SMEM_LOG_DEM_EVENT_BASE + 0x1C) +#define DEM_SEND_BATTERY_INFO (SMEM_LOG_DEM_EVENT_BASE + 0x1D) +#define DEM_REMOTE_PWR_CB (SMEM_LOG_DEM_EVENT_BASE + 0x24) +#define DEM_TIME_SYNC_START (SMEM_LOG_DEM_EVENT_BASE + 0x1E) +#define DEM_TIME_SYNC_SEND_VALUE (SMEM_LOG_DEM_EVENT_BASE + 0x1F) +#define DEM_TIME_SYNC_DONE (SMEM_LOG_DEM_EVENT_BASE + 0x20) +#define DEM_TIME_SYNC_REQUEST (SMEM_LOG_DEM_EVENT_BASE + 0x21) +#define DEM_TIME_SYNC_POLL (SMEM_LOG_DEM_EVENT_BASE + 0x22) +#define DEM_TIME_SYNC_INIT (SMEM_LOG_DEM_EVENT_BASE + 0x23) +#define DEM_INIT (SMEM_LOG_DEM_EVENT_BASE + 0x25) +#else +#define DEM_NO_SLEEP (SMEM_LOG_DEM_EVENT_BASE + 1) +#define DEM_INSUF_TIME (SMEM_LOG_DEM_EVENT_BASE + 2) +#define DEMAPPS_ENTER_SLEEP (SMEM_LOG_DEM_EVENT_BASE + 3) +#define DEMAPPS_DETECT_WAKEUP (SMEM_LOG_DEM_EVENT_BASE + 4) +#define DEMAPPS_END_APPS_TCXO (SMEM_LOG_DEM_EVENT_BASE + 5) +#define DEMAPPS_ENTER_SLEEPEXIT (SMEM_LOG_DEM_EVENT_BASE + 6) +#define DEMAPPS_END_APPS_SLEEP (SMEM_LOG_DEM_EVENT_BASE + 7) +#define DEMAPPS_SETUP_APPS_PWRCLPS (SMEM_LOG_DEM_EVENT_BASE + 8) +#define DEMAPPS_PWRCLPS_EARLY_EXIT (SMEM_LOG_DEM_EVENT_BASE + 9) +#define DEMMOD_SEND_WAKEUP (SMEM_LOG_DEM_EVENT_BASE + 0xA) +#define DEMMOD_NO_APPS_VOTE (SMEM_LOG_DEM_EVENT_BASE + 0xB) +#define DEMMOD_NO_TCXO_SLEEP (SMEM_LOG_DEM_EVENT_BASE + 0xC) +#define DEMMOD_BT_CLOCK (SMEM_LOG_DEM_EVENT_BASE + 0xD) +#define DEMMOD_UART_CLOCK (SMEM_LOG_DEM_EVENT_BASE + 0xE) +#define DEMMOD_OKTS (SMEM_LOG_DEM_EVENT_BASE + 0xF) +#define DEM_SLEEP_INFO (SMEM_LOG_DEM_EVENT_BASE + 0x10) +#define DEMMOD_TCXO_END (SMEM_LOG_DEM_EVENT_BASE + 0x11) +#define DEMMOD_END_SLEEP_SIG (SMEM_LOG_DEM_EVENT_BASE + 0x12) +#define DEMMOD_SETUP_APPSSLEEP (SMEM_LOG_DEM_EVENT_BASE + 0x13) +#define DEMMOD_ENTER_TCXO (SMEM_LOG_DEM_EVENT_BASE + 0x14) +#define DEMMOD_WAKE_APPS (SMEM_LOG_DEM_EVENT_BASE + 0x15) +#define DEMMOD_POWER_COLLAPSE_APPS (SMEM_LOG_DEM_EVENT_BASE + 0x16) +#define DEMMOD_RESTORE_APPS_PWR (SMEM_LOG_DEM_EVENT_BASE + 0x17) +#define DEMAPPS_ASSERT_OKTS (SMEM_LOG_DEM_EVENT_BASE + 0x18) +#define DEMAPPS_RESTART_START_TIMER (SMEM_LOG_DEM_EVENT_BASE + 0x19) +#define DEMAPPS_ENTER_RUN (SMEM_LOG_DEM_EVENT_BASE + 0x1A) +#define DEMMOD_MAO_INTS (SMEM_LOG_DEM_EVENT_BASE + 0x1B) +#define DEMMOD_POWERUP_APPS_CALLED (SMEM_LOG_DEM_EVENT_BASE + 0x1C) +#define DEMMOD_PC_TIMER_EXPIRED (SMEM_LOG_DEM_EVENT_BASE + 0x1D) +#define DEM_DETECT_SLEEPEXIT (SMEM_LOG_DEM_EVENT_BASE + 0x1E) +#define DEM_DETECT_RUN (SMEM_LOG_DEM_EVENT_BASE + 0x1F) +#define DEM_SET_APPS_TIMER (SMEM_LOG_DEM_EVENT_BASE + 0x20) +#define DEM_NEGATE_OKTS (SMEM_LOG_DEM_EVENT_BASE + 0x21) +#define DEMMOD_APPS_WAKEUP_INT (SMEM_LOG_DEM_EVENT_BASE + 0x22) +#define DEMMOD_APPS_SWFI (SMEM_LOG_DEM_EVENT_BASE + 0x23) +#define DEM_SEND_BATTERY_INFO (SMEM_LOG_DEM_EVENT_BASE + 0x24) +#define DEM_SMI_CLK_DISABLED (SMEM_LOG_DEM_EVENT_BASE + 0x25) +#define DEM_SMI_CLK_ENABLED (SMEM_LOG_DEM_EVENT_BASE + 0x26) +#define DEMAPPS_SETUP_APPS_SUSPEND (SMEM_LOG_DEM_EVENT_BASE + 0x27) +#define DEM_RPC_EARLY_EXIT (SMEM_LOG_DEM_EVENT_BASE + 0x28) +#define DEMAPPS_WAKEUP_REASON (SMEM_LOG_DEM_EVENT_BASE + 0x29) +#define DEM_INIT (SMEM_LOG_DEM_EVENT_BASE + 0x30) +#endif +#define DEMMOD_UMTS_BASE (SMEM_LOG_DEM_EVENT_BASE + 0x8000) +#define DEMMOD_GL1_GO_TO_SLEEP (DEMMOD_UMTS_BASE + 0x0000) +#define DEMMOD_GL1_SLEEP_START (DEMMOD_UMTS_BASE + 0x0001) +#define DEMMOD_GL1_AFTER_GSM_CLK_ON (DEMMOD_UMTS_BASE + 0x0002) +#define DEMMOD_GL1_BEFORE_RF_ON (DEMMOD_UMTS_BASE + 0x0003) +#define DEMMOD_GL1_AFTER_RF_ON (DEMMOD_UMTS_BASE + 0x0004) +#define DEMMOD_GL1_FRAME_TICK (DEMMOD_UMTS_BASE + 0x0005) +#define DEMMOD_GL1_WCDMA_START (DEMMOD_UMTS_BASE + 0x0006) +#define DEMMOD_GL1_WCDMA_ENDING (DEMMOD_UMTS_BASE + 0x0007) +#define DEMMOD_UMTS_NOT_OKTS (DEMMOD_UMTS_BASE + 0x0008) +#define DEMMOD_UMTS_START_TCXO_SHUTDOWN (DEMMOD_UMTS_BASE + 0x0009) +#define DEMMOD_UMTS_END_TCXO_SHUTDOWN (DEMMOD_UMTS_BASE + 0x000A) +#define DEMMOD_UMTS_START_ARM_HALT (DEMMOD_UMTS_BASE + 0x000B) +#define DEMMOD_UMTS_END_ARM_HALT (DEMMOD_UMTS_BASE + 0x000C) +#define DEMMOD_UMTS_NEXT_WAKEUP_SCLK (DEMMOD_UMTS_BASE + 0x000D) +#define TIME_REMOTE_LOG_EVENT_START (SMEM_LOG_TIMETICK_EVENT_BASE + 0) +#define TIME_REMOTE_LOG_EVENT_GOTO_WAIT (SMEM_LOG_TIMETICK_EVENT_BASE + 1) +#define TIME_REMOTE_LOG_EVENT_GOTO_INIT (SMEM_LOG_TIMETICK_EVENT_BASE + 2) +#define ERR_ERROR_FATAL (SMEM_LOG_ERROR_EVENT_BASE + 1) +#define ERR_ERROR_FATAL_TASK (SMEM_LOG_ERROR_EVENT_BASE + 2) +#define DCVSAPPS_LOG_IDLE (SMEM_LOG_DCVS_EVENT_BASE + 0x0) +#define DCVSAPPS_LOG_ERR (SMEM_LOG_DCVS_EVENT_BASE + 0x1) +#define DCVSAPPS_LOG_CHG (SMEM_LOG_DCVS_EVENT_BASE + 0x2) +#define DCVSAPPS_LOG_REG (SMEM_LOG_DCVS_EVENT_BASE + 0x3) +#define DCVSAPPS_LOG_DEREG (SMEM_LOG_DCVS_EVENT_BASE + 0x4) +#define SMEM_LOG_EVENT_CB (SMEM_LOG_SMEM_EVENT_BASE + 0) +#define SMEM_LOG_EVENT_START (SMEM_LOG_SMEM_EVENT_BASE + 1) +#define SMEM_LOG_EVENT_INIT (SMEM_LOG_SMEM_EVENT_BASE + 2) +#define SMEM_LOG_EVENT_RUNNING (SMEM_LOG_SMEM_EVENT_BASE + 3) +#define SMEM_LOG_EVENT_STOP (SMEM_LOG_SMEM_EVENT_BASE + 4) +#define SMEM_LOG_EVENT_RESTART (SMEM_LOG_SMEM_EVENT_BASE + 5) +#define SMEM_LOG_EVENT_SS (SMEM_LOG_SMEM_EVENT_BASE + 6) +#define SMEM_LOG_EVENT_READ (SMEM_LOG_SMEM_EVENT_BASE + 7) +#define SMEM_LOG_EVENT_WRITE (SMEM_LOG_SMEM_EVENT_BASE + 8) +#define SMEM_LOG_EVENT_SIGS1 (SMEM_LOG_SMEM_EVENT_BASE + 9) +#define SMEM_LOG_EVENT_SIGS2 (SMEM_LOG_SMEM_EVENT_BASE + 10) +#define SMEM_LOG_EVENT_WRITE_DM (SMEM_LOG_SMEM_EVENT_BASE + 11) +#define SMEM_LOG_EVENT_READ_DM (SMEM_LOG_SMEM_EVENT_BASE + 12) +#define SMEM_LOG_EVENT_SKIP_DM (SMEM_LOG_SMEM_EVENT_BASE + 13) +#define SMEM_LOG_EVENT_STOP_DM (SMEM_LOG_SMEM_EVENT_BASE + 14) +#define SMEM_LOG_EVENT_ISR (SMEM_LOG_SMEM_EVENT_BASE + 15) +#define SMEM_LOG_EVENT_TASK (SMEM_LOG_SMEM_EVENT_BASE + 16) +#define SMEM_LOG_EVENT_RS (SMEM_LOG_SMEM_EVENT_BASE + 17) +#define ONCRPC_LOG_EVENT_SMD_WAIT (SMEM_LOG_ONCRPC_EVENT_BASE + 0) +#define ONCRPC_LOG_EVENT_RPC_WAIT (SMEM_LOG_ONCRPC_EVENT_BASE + 1) +#define ONCRPC_LOG_EVENT_RPC_BOTH_WAIT (SMEM_LOG_ONCRPC_EVENT_BASE + 2) +#define ONCRPC_LOG_EVENT_RPC_INIT (SMEM_LOG_ONCRPC_EVENT_BASE + 3) +#define ONCRPC_LOG_EVENT_RUNNING (SMEM_LOG_ONCRPC_EVENT_BASE + 4) +#define ONCRPC_LOG_EVENT_APIS_INITED (SMEM_LOG_ONCRPC_EVENT_BASE + 5) +#define ONCRPC_LOG_EVENT_AMSS_RESET (SMEM_LOG_ONCRPC_EVENT_BASE + 6) +#define ONCRPC_LOG_EVENT_SMD_RESET (SMEM_LOG_ONCRPC_EVENT_BASE + 7) +#define ONCRPC_LOG_EVENT_ONCRPC_RESET (SMEM_LOG_ONCRPC_EVENT_BASE + 8) +#define ONCRPC_LOG_EVENT_CB (SMEM_LOG_ONCRPC_EVENT_BASE + 9) +#define ONCRPC_LOG_EVENT_STD_CALL (SMEM_LOG_ONCRPC_EVENT_BASE + 10) +#define ONCRPC_LOG_EVENT_STD_REPLY (SMEM_LOG_ONCRPC_EVENT_BASE + 11) +#define ONCRPC_LOG_EVENT_STD_CALL_ASYNC (SMEM_LOG_ONCRPC_EVENT_BASE + 12) +#define NO_SLEEP_OLD (SMEM_LOG_SLEEP_EVENT_BASE + 0x1) +#define INSUF_TIME (SMEM_LOG_SLEEP_EVENT_BASE + 0x2) +#define MOD_UART_CLOCK (SMEM_LOG_SLEEP_EVENT_BASE + 0x3) +#define SLEEP_INFO (SMEM_LOG_SLEEP_EVENT_BASE + 0x4) +#define MOD_TCXO_END (SMEM_LOG_SLEEP_EVENT_BASE + 0x5) +#define MOD_ENTER_TCXO (SMEM_LOG_SLEEP_EVENT_BASE + 0x6) +#define NO_SLEEP_NEW (SMEM_LOG_SLEEP_EVENT_BASE + 0x7) +#define RPC_ROUTER_LOG_EVENT_UNKNOWN (SMEM_LOG_RPC_ROUTER_EVENT_BASE) +#define RPC_ROUTER_LOG_EVENT_MSG_READ (SMEM_LOG_RPC_ROUTER_EVENT_BASE + 1) +#define RPC_ROUTER_LOG_EVENT_MSG_WRITTEN (SMEM_LOG_RPC_ROUTER_EVENT_BASE + 2) +#define RPC_ROUTER_LOG_EVENT_MSG_CFM_REQ (SMEM_LOG_RPC_ROUTER_EVENT_BASE + 3) +#define RPC_ROUTER_LOG_EVENT_MSG_CFM_SNT (SMEM_LOG_RPC_ROUTER_EVENT_BASE + 4) +#define RPC_ROUTER_LOG_EVENT_MID_READ (SMEM_LOG_RPC_ROUTER_EVENT_BASE + 5) +#define RPC_ROUTER_LOG_EVENT_MID_WRITTEN (SMEM_LOG_RPC_ROUTER_EVENT_BASE + 6) +#define RPC_ROUTER_LOG_EVENT_MID_CFM_REQ (SMEM_LOG_RPC_ROUTER_EVENT_BASE + 7) + +#ifdef CONFIG_MSM_SMD_LOGGING +void smem_log_event(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3); +void smem_log_event6(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3, uint32_t data4, uint32_t data5, + uint32_t data6); +void smem_log_event_to_static(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3); +void smem_log_event6_to_static(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3, uint32_t data4, uint32_t data5, + uint32_t data6); +#else +void smem_log_event(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3) { } +void smem_log_event6(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3, uint32_t data4, uint32_t data5, + uint32_t data6) { } +void smem_log_event_to_static(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3) { } +void smem_log_event6_to_static(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3, uint32_t data4, uint32_t data5, + uint32_t data6) { } +#endif + diff --git a/arch/arm/mach-msm/include/mach/smp.h b/arch/arm/mach-msm/include/mach/smp.h new file mode 100644 index 00000000000..5b7482ff353 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/smp.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ASM_ARCH_MSM_SMP_H +#define __ASM_ARCH_MSM_SMP_H + +#include + +static inline void smp_cross_call(const struct cpumask *mask, int ipi) +{ + gic_raise_softirq(mask, ipi); +} + +#endif diff --git a/arch/arm/mach-msm/include/mach/socinfo.h b/arch/arm/mach-msm/include/mach/socinfo.h new file mode 100644 index 00000000000..ff340f7a8a2 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/socinfo.h @@ -0,0 +1,202 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_SOCINFO_H_ +#define _ARCH_ARM_MACH_MSM_SOCINFO_H_ + +#include + +#include +#include +/* + * SOC version type with major number in the upper 16 bits and minor + * number in the lower 16 bits. For example: + * 1.0 -> 0x00010000 + * 2.3 -> 0x00020003 + */ +#define SOCINFO_VERSION_MAJOR(ver) ((ver & 0xffff0000) >> 16) +#define SOCINFO_VERSION_MINOR(ver) (ver & 0x0000ffff) + +enum msm_cpu { + MSM_CPU_UNKNOWN = 0, + MSM_CPU_7X01, + MSM_CPU_7X25, + MSM_CPU_7X27, + MSM_CPU_8X50, + MSM_CPU_8X50A, + MSM_CPU_7X30, + MSM_CPU_8X55, + MSM_CPU_8X60, + MSM_CPU_8960, + MSM_CPU_7X27A, + FSM_CPU_9XXX, + MSM_CPU_7X25A, + MSM_CPU_7X25AA, + MSM_CPU_8064, + MSM_CPU_8X30, +}; + +enum msm_cpu socinfo_get_msm_cpu(void); +uint32_t socinfo_get_id(void); +uint32_t socinfo_get_version(void); +char *socinfo_get_build_id(void); +uint32_t socinfo_get_platform_type(void); +uint32_t socinfo_get_platform_subtype(void); +uint32_t socinfo_get_platform_version(void); +int __init socinfo_init(void) __must_check; + +static inline int get_core_count(void) +{ + if (!(read_cpuid_mpidr() & BIT(31))) + return 1; + + if (read_cpuid_mpidr() & BIT(30) && + !machine_is_msm8960_sim() && + !machine_is_apq8064_sim()) + return 1; + + /* 1 + the PART[1:0] field of MIDR */ + return ((read_cpuid_id() >> 4) & 3) + 1; +} + +static inline int read_msm_cpu_type(void) +{ + if (machine_is_msm8960_sim()) + return MSM_CPU_8960; + + switch (read_cpuid_id()) { + case 0x510F02D0: + case 0x510F02D2: + case 0x510F02D4: + return MSM_CPU_8X60; + + case 0x510F04D0: + case 0x510F04D1: + case 0x510F04D2: + return MSM_CPU_8960; + + case 0x511F04D0: + if (get_core_count() == 2) + return MSM_CPU_8960; + else + return MSM_CPU_8X30; + + case 0x510F06F0: + return MSM_CPU_8064; + + default: + return MSM_CPU_UNKNOWN; + }; +} + +static inline int cpu_is_msm7x01(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X01; +} + +static inline int cpu_is_msm7x25(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X25; +} + +static inline int cpu_is_msm7x27(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X27; +} + +static inline int cpu_is_msm7x27a(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X27A; +} + +static inline int cpu_is_msm7x25a(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X25A; +} + +static inline int cpu_is_msm7x25aa(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X25AA; +} + +static inline int cpu_is_msm7x30(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_7X30; +} + +static inline int cpu_is_qsd8x50(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_8X50; +} + +static inline int cpu_is_msm8x55(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == MSM_CPU_8X55; +} + +static inline int cpu_is_msm8x60(void) +{ + return read_msm_cpu_type() == MSM_CPU_8X60; +} + +static inline int cpu_is_msm8960(void) +{ + return read_msm_cpu_type() == MSM_CPU_8960; +} + +static inline int cpu_is_apq8064(void) +{ + return read_msm_cpu_type() == MSM_CPU_8064; +} + +static inline int cpu_is_msm8x30(void) +{ + return read_msm_cpu_type() == MSM_CPU_8X30; +} + +static inline int cpu_is_fsm9xxx(void) +{ + enum msm_cpu cpu = socinfo_get_msm_cpu(); + + BUG_ON(cpu == MSM_CPU_UNKNOWN); + return cpu == FSM_CPU_9XXX; +} + +#endif diff --git a/arch/arm/mach-msm/include/mach/sps.h b/arch/arm/mach-msm/include/mach/sps.h new file mode 100644 index 00000000000..f6cba0d4732 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/sps.h @@ -0,0 +1,1136 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* Smart-Peripheral-Switch (SPS) API. */ + +#ifndef _SPS_H_ +#define _SPS_H_ + +#include /* u32 */ + +/* SPS device handle indicating use of system memory */ +#define SPS_DEV_HANDLE_MEM ((u32)0x7ffffffful) + +/* SPS device handle indicating use of BAM-DMA */ + +/* SPS device handle invalid value */ +#define SPS_DEV_HANDLE_INVALID ((u32)0) + +/* BAM invalid IRQ value */ +#define SPS_IRQ_INVALID 0 + +/* Invalid address value */ +#define SPS_ADDR_INVALID 0 + +/* Invalid peripheral device enumeration class */ +#define SPS_CLASS_INVALID ((u32)-1) + +/* + * This value specifies different configurations for an SPS connection. + * A non-default value instructs the SPS driver to search for the configuration + * in the fixed connection mapping table. + */ +#define SPS_CONFIG_DEFAULT 0 + +/* + * This value instructs the SPS driver to use the default BAM-DMA channel + * threshold + */ +#define SPS_DMA_THRESHOLD_DEFAULT 0 + +/* Flag bits supported by SPS hardware for struct sps_iovec */ +#define SPS_IOVEC_FLAG_INT 0x8000 /* Generate interrupt */ +#define SPS_IOVEC_FLAG_EOT 0x4000 /* Generate end-of-transfer indication */ +#define SPS_IOVEC_FLAG_EOB 0x2000 /* Generate end-of-block indication */ +#define SPS_IOVEC_FLAG_NO_SUBMIT 0x0100 /* Do not submit descriptor to HW */ +#define SPS_IOVEC_FLAG_DEFAULT 0x0001 /* Use driver default */ + +/* BAM device options flags */ + +/* + * BAM will be configured and enabled at boot. Otherwise, BAM will be + * configured and enabled when first pipe connect occurs. + */ +#define SPS_BAM_OPT_ENABLE_AT_BOOT 1UL +/* BAM IRQ is disabled */ +#define SPS_BAM_OPT_IRQ_DISABLED (1UL << 1) +/* BAM peripheral is a BAM-DMA */ +#define SPS_BAM_OPT_BAMDMA (1UL << 2) + +/* BAM device management flags */ + +/* BAM global device control is managed remotely */ +#define SPS_BAM_MGR_DEVICE_REMOTE 1UL +/* BAM device supports multiple execution environments */ +#define SPS_BAM_MGR_MULTI_EE (1UL << 1) +/* BAM pipes are *not* allocated locally */ +#define SPS_BAM_MGR_PIPE_NO_ALLOC (1UL << 2) +/* BAM pipes are *not* configured locally */ +#define SPS_BAM_MGR_PIPE_NO_CONFIG (1UL << 3) +/* BAM pipes are *not* controlled locally */ +#define SPS_BAM_MGR_PIPE_NO_CTRL (1UL << 4) +/* "Globbed" management properties */ +#define SPS_BAM_MGR_NONE \ + (SPS_BAM_MGR_DEVICE_REMOTE | SPS_BAM_MGR_PIPE_NO_ALLOC | \ + SPS_BAM_MGR_PIPE_NO_CONFIG | SPS_BAM_MGR_PIPE_NO_CTRL) +#define SPS_BAM_MGR_LOCAL 0 +#define SPS_BAM_MGR_LOCAL_SHARED SPS_BAM_MGR_MULTI_EE +#define SPS_BAM_MGR_REMOTE_SHARED \ + (SPS_BAM_MGR_DEVICE_REMOTE | SPS_BAM_MGR_MULTI_EE | \ + SPS_BAM_MGR_PIPE_NO_ALLOC) +#define SPS_BAM_MGR_ACCESS_MASK SPS_BAM_MGR_NONE + +/* + * BAM security configuration + */ +#define SPS_BAM_NUM_EES 4 +#define SPS_BAM_SEC_DO_NOT_CONFIG 0 +#define SPS_BAM_SEC_DO_CONFIG 0x0A434553 + +/* This enum specifies the operational mode for an SPS connection */ +enum sps_mode { + SPS_MODE_SRC = 0, /* end point is the source (producer) */ + SPS_MODE_DEST, /* end point is the destination (consumer) */ +}; + + +/* + * This enum is a set of bit flag options for SPS connection. + * The enums should be OR'd together to create the option set + * for the SPS connection. + */ +enum sps_option { + /* + * Options to enable specific SPS hardware interrupts. + * These bit flags are also used to indicate interrupt source + * for the SPS_EVENT_IRQ event. + */ + SPS_O_DESC_DONE = 0x00000001, /* Descriptor processed */ + SPS_O_INACTIVE = 0x00000002, /* Inactivity timeout */ + SPS_O_WAKEUP = 0x00000004, /* Peripheral wake up */ + SPS_O_OUT_OF_DESC = 0x00000008,/* Out of descriptors */ + SPS_O_ERROR = 0x00000010, /* Error */ + SPS_O_EOT = 0x00000020, /* End-of-transfer */ + + /* Options to enable hardware features */ + SPS_O_STREAMING = 0x00010000, /* Enable streaming mode (no EOT) */ + /* Use MTI/SETPEND instead of BAM interrupt */ + SPS_O_IRQ_MTI = 0x00020000, + + /* Options to enable software features */ + /* Transfer operation should be polled */ + SPS_O_POLL = 0x01000000, + /* Disable queuing of transfer events for the connection end point */ + SPS_O_NO_Q = 0x02000000, + SPS_O_FLOWOFF = 0x04000000, /* Graceful halt */ + /* SPS_O_WAKEUP will be disabled after triggered */ + SPS_O_WAKEUP_IS_ONESHOT = 0x08000000, + /** + * Client must read each descriptor from the FIFO + * using sps_get_iovec() + */ + SPS_O_ACK_TRANSFERS = 0x10000000, + /* Connection is automatically enabled */ + SPS_O_AUTO_ENABLE = 0x20000000, + /* DISABLE endpoint synchronization for config/enable/disable */ + SPS_O_NO_EP_SYNC = 0x40000000, +}; + +/** + * This enum specifies BAM DMA channel priority. Clients should use + * SPS_DMA_PRI_DEFAULT unless a specific priority is required. + */ +enum sps_dma_priority { + SPS_DMA_PRI_DEFAULT = 0, + SPS_DMA_PRI_LOW, + SPS_DMA_PRI_MED, + SPS_DMA_PRI_HIGH, +}; + +/* + * This enum specifies the ownership of a connection resource. + * Remote or shared ownership is only possible/meaningful on the processor + * that controls resource. + */ +enum sps_owner { + SPS_OWNER_LOCAL = 0x1, /* Resource is owned by local processor */ + SPS_OWNER_REMOTE = 0x2, /* Resource is owned by a satellite processor */ +}; + +/* This enum indicates the event associated with a client event trigger */ +enum sps_event { + SPS_EVENT_INVALID = 0, + + SPS_EVENT_EOT, /* End-of-transfer */ + SPS_EVENT_DESC_DONE, /* Descriptor processed */ + SPS_EVENT_OUT_OF_DESC, /* Out of descriptors */ + SPS_EVENT_WAKEUP, /* Peripheral wake up */ + SPS_EVENT_FLOWOFF, /* Graceful halt (idle) */ + SPS_EVENT_INACTIVE, /* Inactivity timeout */ + SPS_EVENT_ERROR, /* Error */ + SPS_EVENT_MAX, +}; + +/* + * This enum specifies the event trigger mode and is an argument for the + * sps_register_event() function. + */ +enum sps_trigger { + /* Trigger with payload for callback */ + SPS_TRIGGER_CALLBACK = 0, + /* Trigger without payload for wait or poll */ + SPS_TRIGGER_WAIT, +}; + +/* + * This enum indicates the desired halting mechanism and is an argument for the + * sps_flow_off() function + */ +enum sps_flow_off { + SPS_FLOWOFF_FORCED = 0, /* Force hardware into halt state */ + /* Allow hardware to empty pipe before halting */ + SPS_FLOWOFF_GRACEFUL, +}; + +/* + * This enum indicates the target memory heap and is an argument for the + * sps_mem_alloc() function. + */ +enum sps_mem { + SPS_MEM_LOCAL = 0, /* SPS subsystem local (pipe) memory */ + SPS_MEM_UC, /* Microcontroller (ARM7) local memory */ +}; + +/* + * This enum indicates a timer control operation and is an argument for the + * sps_timer_ctrl() function. + */ +enum sps_timer_op { + SPS_TIMER_OP_CONFIG = 0, + SPS_TIMER_OP_RESET, +/* SPS_TIMER_OP_START, Not supported by hardware yet */ +/* SPS_TIMER_OP_STOP, Not supported by hardware yet */ + SPS_TIMER_OP_READ, +}; + +/* + * This enum indicates the inactivity timer operating mode and is an + * argument for the sps_timer_ctrl() function. + */ +enum sps_timer_mode { + SPS_TIMER_MODE_ONESHOT = 0, +/* SPS_TIMER_MODE_PERIODIC, Not supported by hardware yet */ +}; + +/** + * This data type corresponds to the native I/O vector (BAM descriptor) + * supported by SPS hardware + * + * @addr - Buffer physical address. + * @size - Buffer size in bytes. + * @flags -Flag bitmask (see SPS_IOVEC_FLAG_ #defines). + * + */ +struct sps_iovec { + u32 addr; + u32 size:16; + u32 flags:16; +}; + +/* + * BAM device's security configuation + */ +struct sps_bam_pipe_sec_config_props { + u32 pipe_mask; + u32 vmid; +}; + +struct sps_bam_sec_config_props { + /* Per-EE configuration - This is a pipe bit mask for each EE */ + struct sps_bam_pipe_sec_config_props ees[SPS_BAM_NUM_EES]; +}; + +/** + * This struct defines a BAM device. The client must memset() this struct to + * zero before writing device information. A value of zero for uninitialized + * values will instruct the SPS driver to use general defaults or + * hardware/BIOS supplied values. + * + * + * @options - See SPS_BAM_OPT_* bit flag. + * @phys_addr - BAM base physical address (not peripheral address). + * @virt_addr - BAM base virtual address. + * @virt_size - For virtual mapping. + * @irq - IRQ enum for use in ISR vector install. + * @num_pipes - number of pipes. Can be read from hardware. + * @summing_threshold - BAM event threshold. + * + * @periph_class - Peripheral device enumeration class. + * @periph_dev_id - Peripheral global device ID. + * @periph_phys_addr - Peripheral base physical address, for BAM-DMA only. + * @periph_virt_addr - Peripheral base virtual address. + * @periph_virt_size - Size for virtual mapping. + * + * @event_threshold - Pipe event threshold. + * @desc_size - Size (bytes) of descriptor FIFO. + * @data_size - Size (bytes) of data FIFO. + * @desc_mem_id - Heap ID for default descriptor FIFO allocations. + * @data_mem_id - Heap ID for default data FIFO allocations. + * + * @manage - BAM device management flags (see SPS_BAM_MGR_*). + * @restricted_pipes - Bitmask of pipes restricted from local use. + * @ee - Local execution environment index. + * + * @irq_gen_addr - MTI interrupt generation address. This configuration only + * applies to BAM rev 1 and 2 hardware. MTIs are only supported on BAMs when + * global config is controlled by a remote processor. + * NOTE: This address must correspond to the MTI associated with the "irq" IRQ + * enum specified above. + * + * @sec_config - must be set to SPS_BAM_SEC_DO_CONFIG to perform BAM security + * configuration. Only the processor that manages the BAM is allowed to + * perform the configuration. The global (top-level) BAM interrupt will be + * assigned to the EE of the processor that manages the BAM. + * + * @p_sec_config_props - BAM device's security configuation + * + */ +struct sps_bam_props { + + /* BAM device properties. */ + + u32 options; + u32 phys_addr; + void *virt_addr; + u32 virt_size; + u32 irq; + u32 num_pipes; + u32 summing_threshold; + + /* Peripheral device properties */ + + u32 periph_class; + u32 periph_dev_id; + u32 periph_phys_addr; + void *periph_virt_addr; + u32 periph_virt_size; + + /* Connection pipe parameter defaults. */ + + u32 event_threshold; + u32 desc_size; + u32 data_size; + u32 desc_mem_id; + u32 data_mem_id; + + /* Security properties */ + + u32 manage; + u32 restricted_pipes; + u32 ee; + + /* BAM MTI interrupt generation */ + + u32 irq_gen_addr; + + /* Security configuration properties */ + + u32 sec_config; + struct sps_bam_sec_config_props *p_sec_config_props; +}; + +/** + * This struct specifies memory buffer properties. + * + * @base - Buffer virtual address. + * @phys_base - Buffer physical address. + * @size - Specifies buffer size (or maximum size). + * @min_size - If non-zero, specifies buffer minimum size. + * + */ +struct sps_mem_buffer { + void *base; + u32 phys_base; + u32 size; + u32 min_size; +}; + +/** + * This struct defines a connection's end point and is used as the argument + * for the sps_connect(), sps_get_config(), and sps_set_config() functions. + * For system mode pipe, use SPS_DEV_HANDLE_MEM for the end point that + * corresponds to system memory. + * + * The client can force SPS to reserve a specific pipe on a BAM. + * If the pipe is in use, the sps_connect/set_config() will fail. + * + * @source - Source BAM. + * @src_pipe_index - BAM pipe index, 0 to 30. + * @destination - Destination BAM. + * @dest_pipe_index - BAM pipe index, 0 to 30. + * + * @mode - specifies which end (source or destination) of the connection will + * be controlled/referenced by the client. + * + * @config - This value is for future use and should be set to + * SPS_CONFIG_DEFAULT or left as default from sps_get_config(). + * + * @options - OR'd connection end point options (see SPS_O defines). + * + * WARNING: The memory provided should be physically contiguous and non-cached. + * The user can use one of the following: + * 1. sps_alloc_mem() - allocated from pipe-memory. + * 2. dma_alloc_coherent() - allocate coherent DMA memory. + * 3. dma_map_single() - for using memory allocated by kmalloc(). + * + * @desc - Descriptor FIFO. + * @data - Data FIFO (BAM-to-BAM mode only). + * + * @event_thresh - Pipe event threshold or derivative. + * + * @sps_reserved - Reserved word - client must not modify. + * + */ +struct sps_connect { + u32 source; + u32 src_pipe_index; + u32 destination; + u32 dest_pipe_index; + + enum sps_mode mode; + + u32 config; + + enum sps_option options; + + struct sps_mem_buffer desc; + struct sps_mem_buffer data; + + u32 event_thresh; + + /* SETPEND/MTI interrupt generation parameters */ + + u32 irq_gen_addr; + u32 irq_gen_data; + + u32 sps_reserved; + +}; + +/** + * This struct defines a satellite connection's end point. The client of the + * SPS driver on the satellite processor must call sps_get_config() to + * initialize a struct sps_connect, then copy the values from the struct + * sps_satellite to the struct sps_connect before making the sps_connect() + * call to the satellite SPS driver. + * + */ +struct sps_satellite { + /** + * These values must be copied to either the source or destination + * corresponding values in the connect struct. + */ + u32 dev; + u32 pipe_index; + + /** + * These values must be copied to the corresponding values in the + * connect struct + */ + u32 config; + enum sps_option options; + +}; + +/** + * This struct defines parameters for allocation of a BAM DMA channel. The + * client must memset() this struct to zero before writing allocation + * information. A value of zero for uninitialized values will instruct + * the SPS driver to use defaults or "don't care". + * + * @dev - Associated BAM device handle, or SPS_DEV_HANDLE_DMA. + * + * @src_owner - Source owner processor ID. + * @dest_owner - Destination owner processor ID. + * + */ +struct sps_alloc_dma_chan { + u32 dev; + + /* BAM DMA channel configuration parameters */ + + u32 threshold; + enum sps_dma_priority priority; + + /** + * Owner IDs are global host processor identifiers used by the system + * SROT when establishing execution environments. + */ + u32 src_owner; + u32 dest_owner; + +}; + +/** + * This struct defines parameters for an allocated BAM DMA channel. + * + * @dev - BAM DMA device handle. + * @dest_pipe_index - Destination/input/write pipe index. + * @src_pipe_index - Source/output/read pipe index. + * + */ +struct sps_dma_chan { + u32 dev; + u32 dest_pipe_index; + u32 src_pipe_index; +}; + +/** + * This struct is an argument passed payload when triggering a callback event + * object registered for an SPS connection end point. + * + * @user - Pointer registered with sps_register_event(). + * + * @event_id - Which event. + * + * @iovec - The associated I/O vector. If the end point is a system-mode + * producer, the size will reflect the actual number of bytes written to the + * buffer by the pipe. NOTE: If this I/O vector was part of a set submitted to + * sps_transfer(), then the vector array itself will be updated with all of + * the actual counts. + * + * @user - Pointer registered with the transfer. + * + */ +struct sps_event_notify { + void *user; + + enum sps_event event_id; + + /* Data associated with the event */ + + union { + /* Data for SPS_EVENT_IRQ */ + struct { + u32 mask; + } irq; + + /* Data for SPS_EVENT_EOT or SPS_EVENT_DESC_DONE */ + + struct { + struct sps_iovec iovec; + void *user; + } transfer; + + /* Data for SPS_EVENT_ERROR */ + + struct { + u32 status; + } err; + + } data; +}; + +/** + * This struct defines a event registration parameters and is used as the + * argument for the sps_register_event() function. + * + * @options - Event options that will trigger the event object. + * @mode - Event trigger mode. + * + * @xfer_done - a pointer to a completion object. NULL if not in use. + * + * @callback - a callback to call on completion. NULL if not in use. + * + * @user - User pointer that will be provided in event callback data. + * + */ +struct sps_register_event { + enum sps_option options; + enum sps_trigger mode; + struct completion *xfer_done; + void (*callback)(struct sps_event_notify *notify); + void *user; +}; + +/** + * This struct defines a system memory transfer's parameters and is used as the + * argument for the sps_transfer() function. + * + * @iovec_phys - Physical address of I/O vectors buffer. + * @iovec - Pointer to I/O vectors buffer. + * @iovec_count - Number of I/O vectors. + * @user - User pointer passed in callback event. + * + */ +struct sps_transfer { + u32 iovec_phys; + struct sps_iovec *iovec; + u32 iovec_count; + void *user; +}; + +/** + * This struct defines a timer control operation parameters and is used as an + * argument for the sps_timer_ctrl() function. + * + * @op - Timer control operation. + * @timeout_msec - Inactivity timeout (msec). + * + */ +struct sps_timer_ctrl { + enum sps_timer_op op; + + /** + * The following configuration parameters must be set when the timer + * control operation is SPS_TIMER_OP_CONFIG. + */ + enum sps_timer_mode mode; + u32 timeout_msec; +}; + +/** + * This struct defines a timer control operation result and is used as an + * argument for the sps_timer_ctrl() function. + */ +struct sps_timer_result { + u32 current_timer; +}; + + +/*---------------------------------------------------------------------------- + * Functions specific to sps interface + * -------------------------------------------------------------------------*/ +struct sps_pipe; /* Forward declaration */ + +/** + * Register a BAM device + * + * This function registers a BAM device with the SPS driver. For each + *peripheral that includes a BAM, the peripheral driver must register + * the BAM with the SPS driver. + * + * A requirement is that the peripheral driver must remain attached + * to the SPS driver until the BAM is deregistered. Otherwise, the + * system may attempt to unload the SPS driver. BAM registrations would + * be lost. + * + * @bam_props - Pointer to struct for BAM device properties. + * + * @dev_handle - Device handle will be written to this location (output). + * + * @return 0 on success, negative value on error + * + */ +int sps_register_bam_device(const struct sps_bam_props *bam_props, + u32 *dev_handle); + +/** + * Deregister a BAM device + * + * This function deregisters a BAM device from the SPS driver. The peripheral + * driver should deregister a BAM when the peripheral driver is shut down or + * when BAM use should be disabled. + * + * A BAM cannot be deregistered if any of its pipes is in an active connection. + * + * When all BAMs have been deregistered, the system is free to unload the + * SPS driver. + * + * @dev_handle - BAM device handle. + * + * @return 0 on success, negative value on error + * + */ +int sps_deregister_bam_device(u32 dev_handle); + +/** + * Allocate client state context + * + * This function allocate and initializes a client state context struct. + * + * @return pointer to client state context + * + */ +struct sps_pipe *sps_alloc_endpoint(void); + +/** + * Free client state context + * + * This function de-initializes and free a client state context struct. + * + * @ctx - client context for SPS connection end point + * + * @return 0 on success, negative value on error + * + */ +int sps_free_endpoint(struct sps_pipe *h); + +/** + * Get the configuration parameters for an SPS connection end point + * + * This function retrieves the configuration parameters for an SPS connection + * end point. + * This function may be called before the end point is connected (before + * sps_connect is called). This allows the client to specify parameters before + * the connection is established. + * + * The client must call this function to fill it's struct sps_connect + * struct before modifying values and passing the struct to sps_set_config(). + * + * @h - client context for SPS connection end point + * + * @config - Pointer to buffer for the end point's configuration parameters. + * Must not be NULL. + * + * @return 0 on success, negative value on error + * + */ +int sps_get_config(struct sps_pipe *h, struct sps_connect *config); + +/** + * Allocate memory from the SPS Pipe-Memory. + * + * @h - client context for SPS connection end point + * + * @mem - memory type - N/A. + * + * @mem_buffer - Pointer to struct for allocated memory properties. + * + * @return 0 on success, negative value on error + * + */ +int sps_alloc_mem(struct sps_pipe *h, enum sps_mem mem, + struct sps_mem_buffer *mem_buffer); + +/** + * Free memory from the SPS Pipe-Memory. + * + * @h - client context for SPS connection end point + * + * @mem_buffer - Pointer to struct for allocated memory properties. + * + * @return 0 on success, negative value on error + * + */ +int sps_free_mem(struct sps_pipe *h, struct sps_mem_buffer *mem_buffer); + +/** + * Connect an SPS connection end point + * + * This function creates a connection between two SPS peripherals or between + * an SPS peripheral and the local host processor (via system memory, end + *point SPS_DEV_HANDLE_MEM). Establishing the connection includes + * initialization of the SPS hardware and allocation of any other connection + * resources (buffer memory, etc.). + * + * This function requires the client to specify both the source and + * destination end points of the SPS connection. However, the handle + * returned applies only to the end point of the connection that the client + * controls. The end point under control must be specified by the + * enum sps_mode mode argument, either SPS_MODE_SRC, SPS_MODE_DEST, or + * SPS_MODE_CTL. Note that SPS_MODE_CTL is only supported for I/O + * accelerator connections, and only a limited set of control operations are + * allowed (TBD). + * + * For a connection involving system memory + * (SPS_DEV_HANDLE_MEM), the peripheral end point must be + * specified. For example, SPS_MODE_SRC must be specified for a + * BAM-to-system connection, since the BAM pipe is the data + * producer. + * + * For a specific peripheral-to-peripheral connection, there may be more than + * one required configuration. For example, there might be high-performance + * and low-power configurations for a connection between the two peripherals. + * The config argument allows the client to specify different configurations, + * which may require different system resource allocations and hardware + * initialization. + * + * A client is allowed to create one and only one connection for its + * struct sps_pipe. The handle is used to identify the connection end point + * in subsequent SPS driver calls. A specific connection source or + * destination end point can be associated with one and only one + * struct sps_pipe. + * + * The client must establish an open device handle to the SPS. To do so, the + * client must attach to the SPS driver and open the SPS device by calling + * the following functions. + * + * @h - client context for SPS connection end point + * + * @connect - Pointer to connection parameters + * + * @return 0 on success, negative value on error + * + */ +int sps_connect(struct sps_pipe *h, struct sps_connect *connect); + +/** + * Disconnect an SPS connection end point + * + * This function disconnects an SPS connection end point. + * The SPS hardware associated with that end point will be disabled. + * For a connection involving system memory (SPS_DEV_HANDLE_MEM), all + * connection resources are deallocated. For a peripheral-to-peripheral + * connection, the resources associated with the connection will not be + * deallocated until both end points are closed. + * + * The client must call sps_connect() for the handle before calling + * this function. + * + * @h - client context for SPS connection end point + * + * @return 0 on success, negative value on error + * + */ +int sps_disconnect(struct sps_pipe *h); + +/** + * Register an event object for an SPS connection end point + * + * This function registers a callback event object for an SPS connection end + *point. The registered event object will be triggered for the set of + * events specified in reg->options that are enabled for the end point. + * + * There can only be one registered event object for each event. If an event + * object is already registered for an event, it will be replaced. If + *reg->event handle is NULL, then any registered event object for the + * event will be deregistered. Option bits in reg->options not associated + * with events are ignored. + * + * The client must call sps_connect() for the handle before calling + * this function. + * + * @h - client context for SPS connection end point + * + * @reg - Pointer to event registration parameters + * + * @return 0 on success, negative value on error + * + */ +int sps_register_event(struct sps_pipe *h, struct sps_register_event *reg); + +/** + * Perform a single DMA transfer on an SPS connection end point + * + * This function submits a DMA transfer request consisting of a single buffer + * for an SPS connection end point associated with a peripheral-to/from-memory + * connection. The request will be submitted immediately to hardware if the + * hardware is idle (data flow off, no other pending transfers). Otherwise, it + * will be queued for later handling in the SPS driver work loop. + * + * The data buffer must be DMA ready. The client is responsible for insuring + *physically contiguous memory, cache maintenance, and memory barrier. For + * more information, see Appendix A. + * + * The client must not modify the data buffer until the completion indication is + * received. + * + * This function cannot be used if transfer queuing is disabled (see option + * SPS_O_NO_Q). The client must set the SPS_O_EOT option to receive a callback + * event trigger when the transfer is complete. The SPS driver will insure the + * appropriate flags in the I/O vectors are set to generate the completion + * indication. + * + * The return value from this function may indicate that an error occurred. + * Possible causes include invalid arguments. + * + * @h - client context for SPS connection end point + * + * @addr - Physical address of buffer to transfer. + * + * WARNING: The memory provided should be physically contiguous and + * non-cached. + * + * The user can use one of the following: + * 1. sps_alloc_mem() - allocated from pipe-memory. + * 2. dma_alloc_coherent() - allocate DMA memory. + * 3. dma_map_single() for memory allocated by kmalloc(). + * + * @size - Size in bytes of buffer to transfer + * + * @user - User pointer that will be returned to user as part of + * event payload + * + * @return 0 on success, negative value on error + * + */ +int sps_transfer_one(struct sps_pipe *h, u32 addr, u32 size, + void *user, u32 flags); + +/** + * Read event queue for an SPS connection end point + * + * This function reads event queue for an SPS connection end point. + * + * @h - client context for SPS connection end point + * + * @event - pointer to client's event data buffer + * + * @return 0 on success, negative value on error + * + */ +int sps_get_event(struct sps_pipe *h, struct sps_event_notify *event); + +/** + * Get processed I/O vector (completed transfers) + * + * This function fetches the next processed I/O vector. + * + * @h - client context for SPS connection end point + * + * @iovec - Pointer to I/O vector struct (output). + * This struct will be zeroed if there are no more processed I/O vectors. + * + * @return 0 on success, negative value on error + * + */ +int sps_get_iovec(struct sps_pipe *h, struct sps_iovec *iovec); + +/** + * Enable an SPS connection end point + * + * This function enables an SPS connection end point. + * + * @h - client context for SPS connection end point + * + * @return 0 on success, negative value on error + * + */ +int sps_flow_on(struct sps_pipe *h); + +/** + * Disable an SPS connection end point + * + * This function disables an SPS connection end point. + * + * @h - client context for SPS connection end point + * + * @mode - Desired mode for disabling pipe data flow + * + * @return 0 on success, negative value on error + * + */ +int sps_flow_off(struct sps_pipe *h, enum sps_flow_off mode); + +/** + * Perform a Multiple DMA transfer on an SPS connection end point + * + * This function submits a DMA transfer request for an SPS connection end point + * associated with a peripheral-to/from-memory connection. The request will be + * submitted immediately to hardware if the hardware is idle (data flow off, no + * other pending transfers). Otherwise, it will be queued for later handling in + * the SPS driver work loop. + * + * The data buffers referenced by the I/O vectors must be DMA ready. + * The client is responsible for insuring physically contiguous memory, + * any cache maintenance, and memory barrier. For more information, + * see Appendix A. + * + * The I/O vectors must specify physical addresses for the referenced buffers. + * + * The client must not modify the data buffers referenced by I/O vectors until + * the completion indication is received. + * + * If transfer queuing is disabled (see option SPS_O_NO_Q), the client is + * responsible for setting the appropriate flags in the I/O vectors to generate + * the completion indication. Also, the client is responsible for enabling the + * appropriate connection callback event options for completion indication (see + * sps_connect(), sps_set_config()). + * + * If transfer queuing is enabled, the client must set the SPS_O_EOT option to + * receive a callback event trigger when the transfer is complete. The SPS + * driver will insure the appropriate flags in the I/O vectors are set to + * generate the completion indication. The client must not set any flags in the + * I/O vectors, as this may cause the SPS driver to become out of sync with the + * hardware. + * + * The return value from this function may indicate that an error occurred. + * Possible causes include invalid arguments. If transfer queuing is disabled, + * an error will occur if the pipe is already processing a transfer. + * + * @h - client context for SPS connection end point + * + * @transfer - Pointer to transfer parameter struct + * + * @return 0 on success, negative value on error + * + */ +int sps_transfer(struct sps_pipe *h, struct sps_transfer *transfer); + +/** + * Determine whether an SPS connection end point FIFO is empty + * + * This function returns the empty state of an SPS connection end point. + * + * @h - client context for SPS connection end point + * + * @empty - pointer to client's empty status word (boolean) + * + * @return 0 on success, negative value on error + * + */ +int sps_is_pipe_empty(struct sps_pipe *h, u32 *empty); + +/** + * Reset an SPS BAM device + * + * This function resets an SPS BAM device. + * + * @dev - device handle for the BAM + * + * @return 0 on success, negative value on error + * + */ +int sps_device_reset(u32 dev); + +/** + * Set the configuration parameters for an SPS connection end point + * + * This function sets the configuration parameters for an SPS connection + * end point. This function may be called before the end point is connected + * (before sps_connect is called). This allows the client to specify + *parameters before the connection is established. The client is allowed + * to pre-allocate resources and override driver defaults. + * + * The client must call sps_get_config() to fill it's struct sps_connect + * struct before modifying values and passing the struct to this function. + * Only those parameters that differ from the current configuration will + * be processed. + * + * @h - client context for SPS connection end point + * + * @config - Pointer to the end point's new configuration parameters. + * + * @return 0 on success, negative value on error + * + */ +int sps_set_config(struct sps_pipe *h, struct sps_connect *config); + +/** + * Set ownership of an SPS connection end point + * + * This function sets the ownership of an SPS connection end point to + * either local (default) or non-local. This function is used to + * retrieve the struct sps_connect data that must be used by a + * satellite processor when calling sps_connect(). + * + * Non-local ownership is only possible/meaningful on the processor + * that controls resource allocations (apps processor). Setting ownership + * to non-local on a satellite processor will fail. + * + * Setting ownership from non-local to local will succeed only if the + * owning satellite processor has properly brought the end point to + * an idle condition. + * + * This function will succeed if the connection end point is already in + * the specified ownership state. + * + * @h - client context for SPS connection end point + * + * @owner - New ownership of the connection end point + * + * @connect - Pointer to buffer for satellite processor connect data. + * Can be NULL to avoid retrieving the connect data. Will be ignored + * if the end point ownership is set to local. + * + * @return 0 on success, negative value on error + * + */ +int sps_set_owner(struct sps_pipe *h, enum sps_owner owner, + struct sps_satellite *connect); + +/** + * Allocate a BAM DMA channel + * + * This function allocates a BAM DMA channel. A "BAM DMA" is a special + * DMA peripheral with a BAM front end. The DMA peripheral acts as a conduit + * for data to flow into a consumer pipe and then out of a producer pipe. + * It's primarily purpose is to serve as a path for interprocessor communication + * that allows each processor to control and protect it's own memory space. + * + * @alloc - Pointer to struct for BAM DMA channel allocation properties. + * + * @chan - Allocated channel information will be written to this + * location (output). + * + * @return 0 on success, negative value on error + * + */ +int sps_alloc_dma_chan(const struct sps_alloc_dma_chan *alloc, + struct sps_dma_chan *chan); + +/** + * Free a BAM DMA channel + * + * This function frees a BAM DMA channel. + * + * @chan - Pointer to information for channel to free + * + * @return 0 on success, negative value on error + * + */ +int sps_free_dma_chan(struct sps_dma_chan *chan); + +/** + * Get the BAM handle for BAM-DMA. + * + * The BAM handle should be use as source/destination in the sps_connect(). + * + * @return handle on success, zero on error + * + */ +u32 sps_dma_get_bam_handle(void); + +/** + * Free the BAM handle for BAM-DMA. + * + */ +void sps_dma_free_bam_handle(u32 h); + + +/** + * Get number of free transfer entries for an SPS connection end point + * + * This function returns the number of free transfer entries for an + * SPS connection end point. + * + * @h - client context for SPS connection end point + * + * @count - pointer to count status + * + * @return 0 on success, negative value on error + * + */ +int sps_get_free_count(struct sps_pipe *h, u32 *count); + +/** + * Perform timer control + * + * This function performs timer control operations. + * + * @h - client context for SPS connection end point + * + * @timer_ctrl - Pointer to timer control specification + * + * @timer_result - Pointer to buffer for timer operation result. + * This argument can be NULL if no result is expected for the operation. + * If non-NULL, the current timer value will always provided. + * + * @return 0 on success, negative value on error + * + */ +int sps_timer_ctrl(struct sps_pipe *h, + struct sps_timer_ctrl *timer_ctrl, + struct sps_timer_result *timer_result); + +#endif /* _SPS_H_ */ diff --git a/arch/arm/mach-msm/include/mach/subsystem_notif.h b/arch/arm/mach-msm/include/mach/subsystem_notif.h new file mode 100644 index 00000000000..37d4eeca4eb --- /dev/null +++ b/arch/arm/mach-msm/include/mach/subsystem_notif.h @@ -0,0 +1,80 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * Subsystem restart notifier API header + * + */ + +#ifndef _SUBSYS_NOTIFIER_H +#define _SUBSYS_NOTIFIER_H + +#include + +enum subsys_notif_type { + SUBSYS_BEFORE_SHUTDOWN, + SUBSYS_AFTER_SHUTDOWN, + SUBSYS_BEFORE_POWERUP, + SUBSYS_AFTER_POWERUP, + SUBSYS_NOTIF_TYPE_COUNT +}; + +#if defined(CONFIG_MSM_SUBSYSTEM_RESTART) +/* Use the subsys_notif_register_notifier API to register for notifications for + * a particular subsystem. This API will return a handle that can be used to + * un-reg for notifications using the subsys_notif_unregister_notifier API by + * passing in that handle as an argument. + * + * On receiving a notification, the second (unsigned long) argument of the + * notifier callback will contain the notification type, and the third (void *) + * argument will contain the handle that was returned by + * subsys_notif_register_notifier. + */ +void *subsys_notif_register_notifier( + const char *subsys_name, struct notifier_block *nb); +int subsys_notif_unregister_notifier(void *subsys_handle, + struct notifier_block *nb); + +/* Use the subsys_notif_init_subsys API to initialize the notifier chains form + * a particular subsystem. This API will return a handle that can be used to + * queue notifications using the subsys_notif_queue_notification API by passing + * in that handle as an argument. + */ +void *subsys_notif_add_subsys(const char *); +int subsys_notif_queue_notification(void *subsys_handle, + enum subsys_notif_type notif_type); +#else + +static inline void *subsys_notif_register_notifier( + const char *subsys_name, struct notifier_block *nb) +{ + return NULL; +} + +static inline int subsys_notif_unregister_notifier(void *subsys_handle, + struct notifier_block *nb) +{ + return 0; +} + +static inline void *subsys_notif_add_subsys(const char *subsys_name) +{ + return NULL; +} + +static inline int subsys_notif_queue_notification(void *subsys_handle, + enum subsys_notif_type notif_type) +{ + return 0; +} +#endif /* CONFIG_MSM_SUBSYSTEM_RESTART */ + +#endif diff --git a/arch/arm/mach-msm/include/mach/subsystem_restart.h b/arch/arm/mach-msm/include/mach/subsystem_restart.h new file mode 100644 index 00000000000..f7becefa52f --- /dev/null +++ b/arch/arm/mach-msm/include/mach/subsystem_restart.h @@ -0,0 +1,72 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __SUBSYS_RESTART_H +#define __SUBSYS_RESTART_H + +#include + +#define SUBSYS_NAME_MAX_LENGTH 40 + +enum { + RESET_SOC = 1, + RESET_SUBSYS_COUPLED, + RESET_SUBSYS_INDEPENDENT, + RESET_SUBSYS_MIXED = 25, + RESET_LEVEL_MAX +}; + +struct subsys_data { + const char *name; + int (*shutdown) (const struct subsys_data *); + int (*powerup) (const struct subsys_data *); + void (*crash_shutdown) (const struct subsys_data *); + int (*ramdump) (int, const struct subsys_data *); + + /* Internal use only */ + struct list_head list; + void *notif_handle; + + struct mutex shutdown_lock; + struct mutex powerup_lock; + + void *restart_order; + struct subsys_data *single_restart_list[1]; +}; + +#if defined(CONFIG_MSM_SUBSYSTEM_RESTART) + +int get_restart_level(void); +int subsystem_restart(const char *subsys_name); +int ssr_register_subsystem(struct subsys_data *subsys); + +#else + +static inline int get_restart_level(void) +{ + return 0; +} + +static inline int subsystem_restart(const char *subsystem_name) +{ + return 0; +} + +static inline int ssr_register_subsystem(struct subsys_data *subsys) +{ + return 0; +} + +#endif /* CONFIG_MSM_SUBSYSTEM_RESTART */ + +#endif diff --git a/arch/arm/mach-msm/include/mach/system.h b/arch/arm/mach-msm/include/mach/system.h index d2e83f42ba1..a638b7093cf 100644 --- a/arch/arm/mach-msm/include/mach/system.h +++ b/arch/arm/mach-msm/include/mach/system.h @@ -17,12 +17,19 @@ void arch_idle(void); +#if defined(CONFIG_MSM_NATIVE_RESTART) || defined(CONFIG_ARCH_FSM9XXX) +void arch_reset(char mode, const char *cmd); +#else static inline void arch_reset(char mode, const char *cmd) { for (;;) ; /* depends on IPC w/ other core */ } +#endif /* low level hardware reset hook -- for example, hitting the * PSHOLD line on the PMIC to hard reset the system */ extern void (*msm_hw_reset_hook)(void); + +void msm_set_i2c_mux(bool gpio, int *gpio_clk, int *gpio_dat); + diff --git a/arch/arm/mach-msm/include/mach/timex.h b/arch/arm/mach-msm/include/mach/timex.h index a62e6b215ae..61f1996db26 100644 --- a/arch/arm/mach-msm/include/mach/timex.h +++ b/arch/arm/mach-msm/include/mach/timex.h @@ -18,4 +18,8 @@ #define CLOCK_TICK_RATE 1000000 +#ifdef CONFIG_MSM_SMP +#define ARCH_HAS_READ_CURRENT_TIMER +#endif + #endif diff --git a/arch/arm/mach-msm/include/mach/tpm_st_i2c.h b/arch/arm/mach-msm/include/mach/tpm_st_i2c.h new file mode 100644 index 00000000000..362acbbe530 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/tpm_st_i2c.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _TPM_ST_I2C_H_ +#define _TPM_ST_I2C_H_ + +struct tpm_st_i2c_platform_data { + int accept_cmd_gpio; + int data_avail_gpio; + int accept_cmd_irq; + int data_avail_irq; + int (*gpio_setup)(void); + void (*gpio_release)(void); +}; + +#endif diff --git a/arch/arm/mach-msm/include/mach/uncompress.h b/arch/arm/mach-msm/include/mach/uncompress.h index d94292c29d8..c8513b2a130 100644 --- a/arch/arm/mach-msm/include/mach/uncompress.h +++ b/arch/arm/mach-msm/include/mach/uncompress.h @@ -14,26 +14,71 @@ */ #ifndef __ASM_ARCH_MSM_UNCOMPRESS_H +#define __ASM_ARCH_MSM_UNCOMPRESS_H -#include "hardware.h" -#include "linux/io.h" -#include "mach/msm_iomap.h" +#include +#include +#include +#include + +bool msm_serial_hsl; + +#ifndef CONFIG_DEBUG_ICEDCC static void putc(int c) { #if defined(MSM_DEBUG_UART_PHYS) - unsigned base = MSM_DEBUG_UART_PHYS; - while (!(readl(base + 0x08) & 0x04)) ; - writel(c, base + 0x0c); + unsigned long base = MSM_DEBUG_UART_PHYS; + + if (msm_serial_hsl) { + /* + * Wait for TX_READY to be set; but skip it if we have a + * TX underrun. + */ + if (__raw_readl(base + 0x08) & 0x08) + while (!(__raw_readl(base + 0x14) & 0x80)) + cpu_relax(); + + __raw_writel(0x300, base + 0x10); + __raw_writel(0x1, base + 0x40); + __raw_writel(c, base + 0x70); + + } else { + /* Wait for TX_READY to be set */ + while (!(__raw_readl(base + 0x08) & 0x04)) + cpu_relax(); + __raw_writel(c, base + 0x0c); + } #endif } +#endif static inline void flush(void) { } +#define DEBUG_LL_HS_ENTRY(machine) \ + if (machine_is_##machine()) { \ + msm_serial_hsl = true; \ + break; \ + } + static inline void arch_decomp_setup(void) { + do { + DEBUG_LL_HS_ENTRY(msm8x60_fluid); + DEBUG_LL_HS_ENTRY(msm8x60_surf); + DEBUG_LL_HS_ENTRY(msm8x60_ffa); + DEBUG_LL_HS_ENTRY(msm8x60_fusion); + DEBUG_LL_HS_ENTRY(msm8x60_fusn_ffa); + DEBUG_LL_HS_ENTRY(msm8x60_qrdc); + DEBUG_LL_HS_ENTRY(msm8x60_qt); + DEBUG_LL_HS_ENTRY(msm8960_cdp); + DEBUG_LL_HS_ENTRY(msm8960_mtp); + DEBUG_LL_HS_ENTRY(msm8960_fluid); + DEBUG_LL_HS_ENTRY(msm8960_apq); + DEBUG_LL_HS_ENTRY(msm8960_liquid); + } while (0); } static inline void arch_decomp_wdog(void) diff --git a/arch/arm/mach-msm/include/mach/usb_gadget_fserial.h b/arch/arm/mach-msm/include/mach/usb_gadget_fserial.h new file mode 100644 index 00000000000..0959db5a2e3 --- /dev/null +++ b/arch/arm/mach-msm/include/mach/usb_gadget_fserial.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LINUX_USB_GADGET_FSERIAL_H__ +#define __LINUX_USB_GADGET_FSERIAL_H__ + +#include + +enum transport_type { + USB_GADGET_FSERIAL_TRANSPORT_TTY, + USB_GADGET_FSERIAL_TRANSPORT_SDIO, + USB_GADGET_FSERIAL_TRANSPORT_SMD, +}; + +#define GSERIAL_NO_PORTS 2 +struct usb_gadget_fserial_platform_data { + enum transport_type transport[GSERIAL_NO_PORTS]; + unsigned no_ports; +}; + +struct usb_gadget_facm_pdata { + enum transport_type transport[GSERIAL_NO_PORTS]; + unsigned no_ports; +}; +#endif diff --git a/arch/arm/mach-msm/include/mach/usbdiag.h b/arch/arm/mach-msm/include/mach/usbdiag.h new file mode 100644 index 00000000000..d1e36056eaf --- /dev/null +++ b/arch/arm/mach-msm/include/mach/usbdiag.h @@ -0,0 +1,58 @@ +/* include/asm-arm/arch-msm/usbdiag.h + * + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#ifndef _DRIVERS_USB_DIAG_H_ +#define _DRIVERS_USB_DIAG_H_ + +#define DIAG_LEGACY "diag" +#define DIAG_MDM "diag_mdm" + +#define USB_DIAG_CONNECT 0 +#define USB_DIAG_DISCONNECT 1 +#define USB_DIAG_WRITE_DONE 2 +#define USB_DIAG_READ_DONE 3 + +struct diag_request { + char *buf; + int length; + int actual; + int status; + void *context; +}; + +struct usb_diag_ch { + const char *name; + struct list_head list; + void (*notify)(void *priv, unsigned event, struct diag_request *d_req); + void *priv; + void *priv_usb; +}; + +struct usb_diag_ch *usb_diag_open(const char *name, void *priv, + void (*notify)(void *, unsigned, struct diag_request *)); +void usb_diag_close(struct usb_diag_ch *ch); +int usb_diag_alloc_req(struct usb_diag_ch *ch, int n_write, int n_read); +void usb_diag_free_req(struct usb_diag_ch *ch); +int usb_diag_read(struct usb_diag_ch *ch, struct diag_request *d_req); +int usb_diag_write(struct usb_diag_ch *ch, struct diag_request *d_req); + +int diag_read_from_cb(unsigned char * , int); + +#endif /* _DRIVERS_USB_DIAG_H_ */ diff --git a/arch/arm/mach-msm/include/mach/vmalloc.h b/arch/arm/mach-msm/include/mach/vmalloc.h index d138448eff1..1dada440e0d 100644 --- a/arch/arm/mach-msm/include/mach/vmalloc.h +++ b/arch/arm/mach-msm/include/mach/vmalloc.h @@ -1,6 +1,7 @@ /* arch/arm/mach-msm/include/mach/vmalloc.h * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. 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 @@ -16,7 +17,11 @@ #ifndef __ASM_ARCH_MSM_VMALLOC_H #define __ASM_ARCH_MSM_VMALLOC_H -#define VMALLOC_END 0xd0000000UL +#ifdef CONFIG_VMSPLIT_2G +#define VMALLOC_END (PAGE_OFFSET + 0x7A000000) +#else +#define VMALLOC_END (PAGE_OFFSET + 0x3A000000) +#endif #endif diff --git a/arch/arm/mach-msm/io.c b/arch/arm/mach-msm/io.c index cec6ed1c91d..546f3a046e9 100644 --- a/arch/arm/mach-msm/io.c +++ b/arch/arm/mach-msm/io.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -28,7 +29,7 @@ #include -#define MSM_CHIP_DEVICE(name, chip) { \ +#define MSM_CHIP_DEVICE(name, chip) { \ .virtual = (unsigned long) MSM_##name##_BASE, \ .pfn = __phys_to_pfn(chip##_##name##_PHYS), \ .length = chip##_##name##_SIZE, \ @@ -37,24 +38,48 @@ #define MSM_DEVICE(name) MSM_CHIP_DEVICE(name, MSM) -#if defined(CONFIG_ARCH_MSM7X00A) || defined(CONFIG_ARCH_MSM7X27) \ +/* msm_shared_ram_phys default value of 0x00100000 is the most common value + * and should work as-is for any target without stacked memory. + */ +unsigned int msm_shared_ram_phys = 0x00100000; + +static void msm_map_io(struct map_desc *io_desc, int size) +{ + int i; + + BUG_ON(!size); + for (i = 0; i < size; i++) + if (io_desc[i].virtual == (unsigned long)MSM_SHARED_RAM_BASE) + io_desc[i].pfn = __phys_to_pfn(msm_shared_ram_phys); + + iotable_init(io_desc, size); +} + +#if defined(CONFIG_ARCH_MSM7X01A) || defined(CONFIG_ARCH_MSM7X27) \ || defined(CONFIG_ARCH_MSM7X25) static struct map_desc msm_io_desc[] __initdata = { MSM_DEVICE(VIC), - MSM_CHIP_DEVICE(CSR, MSM7X00), + MSM_DEVICE(CSR), + MSM_DEVICE(TMR), MSM_DEVICE(DMOV), MSM_DEVICE(GPIO1), MSM_DEVICE(GPIO2), MSM_DEVICE(CLK_CTL), + MSM_DEVICE(AD5), + MSM_DEVICE(MDC), #ifdef CONFIG_MSM_DEBUG_UART MSM_DEVICE(DEBUG_UART), #endif -#ifdef CONFIG_ARCH_MSM7X30 - MSM_DEVICE(GCC), +#ifdef CONFIG_CACHE_L2X0 + { + .virtual = (unsigned long) MSM_L2CC_BASE, + .pfn = __phys_to_pfn(MSM_L2CC_PHYS), + .length = MSM_L2CC_SIZE, + .type = MT_DEVICE, + }, #endif { .virtual = (unsigned long) MSM_SHARED_RAM_BASE, - .pfn = __phys_to_pfn(MSM_SHARED_RAM_PHYS), .length = MSM_SHARED_RAM_SIZE, .type = MT_DEVICE, }, @@ -62,19 +87,25 @@ static struct map_desc msm_io_desc[] __initdata = { void __init msm_map_common_io(void) { + /*Peripheral port memory remap, nothing looks to be there for + * cortex a5. + */ +#ifndef CONFIG_ARCH_MSM_CORTEX_A5 /* Make sure the peripheral register window is closed, since * we will use PTE flags (TEX[1]=1,B=0,C=1) to determine which * pages are peripheral interface or not. */ asm("mcr p15, 0, %0, c15, c2, 4" : : "r" (0)); - iotable_init(msm_io_desc, ARRAY_SIZE(msm_io_desc)); +#endif + msm_map_io(msm_io_desc, ARRAY_SIZE(msm_io_desc)); } #endif #ifdef CONFIG_ARCH_QSD8X50 static struct map_desc qsd8x50_io_desc[] __initdata = { MSM_DEVICE(VIC), - MSM_CHIP_DEVICE(CSR, QSD8X50), + MSM_DEVICE(CSR), + MSM_DEVICE(TMR), MSM_DEVICE(DMOV), MSM_DEVICE(GPIO1), MSM_DEVICE(GPIO2), @@ -83,12 +114,12 @@ static struct map_desc qsd8x50_io_desc[] __initdata = { MSM_DEVICE(SCPLL), MSM_DEVICE(AD5), MSM_DEVICE(MDC), + MSM_DEVICE(TCSR), #ifdef CONFIG_MSM_DEBUG_UART MSM_DEVICE(DEBUG_UART), #endif { .virtual = (unsigned long) MSM_SHARED_RAM_BASE, - .pfn = __phys_to_pfn(MSM_SHARED_RAM_PHYS), .length = MSM_SHARED_RAM_SIZE, .type = MT_DEVICE, }, @@ -96,23 +127,49 @@ static struct map_desc qsd8x50_io_desc[] __initdata = { void __init msm_map_qsd8x50_io(void) { - iotable_init(qsd8x50_io_desc, ARRAY_SIZE(qsd8x50_io_desc)); + msm_map_io(qsd8x50_io_desc, ARRAY_SIZE(qsd8x50_io_desc)); } #endif /* CONFIG_ARCH_QSD8X50 */ #ifdef CONFIG_ARCH_MSM8X60 static struct map_desc msm8x60_io_desc[] __initdata = { - MSM_CHIP_DEVICE(QGIC_DIST, MSM8X60), - MSM_CHIP_DEVICE(QGIC_CPU, MSM8X60), - MSM_CHIP_DEVICE(TMR, MSM8X60), - MSM_CHIP_DEVICE(TMR0, MSM8X60), + MSM_DEVICE(QGIC_DIST), + MSM_DEVICE(QGIC_CPU), + MSM_DEVICE(TMR), + MSM_DEVICE(TMR0), + MSM_DEVICE(RPM_MPM), MSM_DEVICE(ACC), + MSM_DEVICE(ACC0), + MSM_DEVICE(ACC1), + MSM_DEVICE(SAW0), + MSM_DEVICE(SAW1), MSM_DEVICE(GCC), + MSM_DEVICE(TLMM), + MSM_DEVICE(DMOV_ADM0), + MSM_DEVICE(DMOV_ADM1), + MSM_DEVICE(SCPLL), + MSM_DEVICE(RPM), + MSM_DEVICE(CLK_CTL), + MSM_DEVICE(MMSS_CLK_CTL), + MSM_DEVICE(LPASS_CLK_CTL), + MSM_DEVICE(TCSR), + MSM_DEVICE(IMEM), + MSM_DEVICE(HDMI), +#ifdef CONFIG_MSM_DEBUG_UART + MSM_DEVICE(DEBUG_UART), +#endif + MSM_DEVICE(SIC_NON_SECURE), + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, + MSM_DEVICE(QFPROM), }; void __init msm_map_msm8x60_io(void) { - iotable_init(msm8x60_io_desc, ARRAY_SIZE(msm8x60_io_desc)); + msm_map_io(msm8x60_io_desc, ARRAY_SIZE(msm8x60_io_desc)); } #endif /* CONFIG_ARCH_MSM8X60 */ @@ -120,20 +177,66 @@ void __init msm_map_msm8x60_io(void) static struct map_desc msm8960_io_desc[] __initdata = { MSM_CHIP_DEVICE(QGIC_DIST, MSM8960), MSM_CHIP_DEVICE(QGIC_CPU, MSM8960), + MSM_CHIP_DEVICE(ACC0, MSM8960), + MSM_CHIP_DEVICE(ACC1, MSM8960), MSM_CHIP_DEVICE(TMR, MSM8960), MSM_CHIP_DEVICE(TMR0, MSM8960), + MSM_CHIP_DEVICE(RPM_MPM, MSM8960), + MSM_CHIP_DEVICE(CLK_CTL, MSM8960), + MSM_CHIP_DEVICE(MMSS_CLK_CTL, MSM8960), + MSM_CHIP_DEVICE(LPASS_CLK_CTL, MSM8960), + MSM_CHIP_DEVICE(RPM, MSM8960), + MSM_CHIP_DEVICE(DMOV, MSM8960), + MSM_CHIP_DEVICE(TLMM, MSM8960), + MSM_CHIP_DEVICE(HFPLL, MSM8960), + MSM_CHIP_DEVICE(SAW0, MSM8960), + MSM_CHIP_DEVICE(SAW1, MSM8960), + MSM_CHIP_DEVICE(SAW_L2, MSM8960), + MSM_CHIP_DEVICE(SIC_NON_SECURE, MSM8960), + MSM_CHIP_DEVICE(APCS_GCC, MSM8960), + MSM_CHIP_DEVICE(TCSR, MSM8960), + MSM_CHIP_DEVICE(IMEM, MSM8960), + MSM_CHIP_DEVICE(HDMI, MSM8960), + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, +#ifdef CONFIG_MSM_DEBUG_UART + MSM_DEVICE(DEBUG_UART), +#endif }; void __init msm_map_msm8960_io(void) { - iotable_init(msm8960_io_desc, ARRAY_SIZE(msm8960_io_desc)); + msm_map_io(msm8960_io_desc, ARRAY_SIZE(msm8960_io_desc)); } #endif /* CONFIG_ARCH_MSM8960 */ +#ifdef CONFIG_ARCH_APQ8064 +static struct map_desc apq8064_io_desc[] __initdata = { + MSM_CHIP_DEVICE(QGIC_DIST, APQ8064), + MSM_CHIP_DEVICE(QGIC_CPU, APQ8064), + MSM_CHIP_DEVICE(TMR, APQ8064), + MSM_CHIP_DEVICE(TMR0, APQ8064), + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, +}; + +void __init msm_map_apq8064_io(void) +{ + msm_map_io(apq8064_io_desc, ARRAY_SIZE(apq8064_io_desc)); +} +#endif /* CONFIG_ARCH_APQ8064 */ + #ifdef CONFIG_ARCH_MSM7X30 static struct map_desc msm7x30_io_desc[] __initdata = { MSM_DEVICE(VIC), - MSM_CHIP_DEVICE(CSR, MSM7X30), + MSM_DEVICE(CSR), + MSM_DEVICE(TMR), MSM_DEVICE(DMOV), MSM_DEVICE(GPIO1), MSM_DEVICE(GPIO2), @@ -150,7 +253,6 @@ static struct map_desc msm7x30_io_desc[] __initdata = { #endif { .virtual = (unsigned long) MSM_SHARED_RAM_BASE, - .pfn = __phys_to_pfn(MSM_SHARED_RAM_PHYS), .length = MSM_SHARED_RAM_SIZE, .type = MT_DEVICE, }, @@ -158,23 +260,56 @@ static struct map_desc msm7x30_io_desc[] __initdata = { void __init msm_map_msm7x30_io(void) { - iotable_init(msm7x30_io_desc, ARRAY_SIZE(msm7x30_io_desc)); + msm_map_io(msm7x30_io_desc, ARRAY_SIZE(msm7x30_io_desc)); } #endif /* CONFIG_ARCH_MSM7X30 */ +#ifdef CONFIG_ARCH_FSM9XXX +static struct map_desc fsm9xxx_io_desc[] __initdata = { + MSM_DEVICE(VIC), + MSM_DEVICE(SIRC), + MSM_DEVICE(CSR), + MSM_DEVICE(TLMM), + MSM_DEVICE(TCSR), + MSM_DEVICE(CLK_CTL), + MSM_DEVICE(ACC), + MSM_DEVICE(SAW), + MSM_DEVICE(GCC), + MSM_DEVICE(GRFC), + MSM_DEVICE(DMOV_SD0), + MSM_DEVICE(DMOV_SD1), + MSM_DEVICE(DMOV_SD2), + MSM_DEVICE(DMOV_SD3), + MSM_DEVICE(QFP_FUSE), + MSM_DEVICE(HH), +#ifdef CONFIG_MSM_DEBUG_UART + MSM_DEVICE(DEBUG_UART), +#endif + { + .virtual = (unsigned long) MSM_SHARED_RAM_BASE, + .length = MSM_SHARED_RAM_SIZE, + .type = MT_DEVICE, + }, +}; + +void __init msm_map_fsm9xxx_io(void) +{ + msm_map_io(fsm9xxx_io_desc, ARRAY_SIZE(fsm9xxx_io_desc)); +} +#endif /* CONFIG_ARCH_FSM9XXX */ + void __iomem * __msm_ioremap(unsigned long phys_addr, size_t size, unsigned int mtype) { if (mtype == MT_DEVICE) { - /* The peripherals in the 88000000 - D0000000 range - * are only accessible by type MT_DEVICE_NONSHARED. + /* The peripherals in the 88000000 - F0000000 range + * are only accessable by type MT_DEVICE_NONSHARED. * Adjust mtype as necessary to make this "just work." */ - if ((phys_addr >= 0x88000000) && (phys_addr < 0xD0000000)) + if ((phys_addr >= 0x88000000) && (phys_addr < 0xF0000000)) mtype = MT_DEVICE_NONSHARED; } - return __arm_ioremap_caller(phys_addr, size, mtype, - __builtin_return_address(0)); + return __arm_ioremap(phys_addr, size, mtype); } EXPORT_SYMBOL(__msm_ioremap); diff --git a/arch/arm/mach-msm/iommu.c b/arch/arm/mach-msm/iommu.c index 1a584e077c6..4de6bfd50fe 100644 --- a/arch/arm/mach-msm/iommu.c +++ b/arch/arm/mach-msm/iommu.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -97,6 +92,7 @@ static int __flush_iotlb(struct iommu_domain *domain) } #endif + mb(); list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) { if (!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent) BUG(); @@ -137,11 +133,14 @@ static void __reset_context(void __iomem *base, int ctx) SET_TLBLKCR(base, ctx, 0); SET_PRRR(base, ctx, 0); SET_NMRR(base, ctx, 0); + mb(); } -static void __program_context(void __iomem *base, int ctx, phys_addr_t pgtable) +static void __program_context(void __iomem *base, int ctx, int ncb, + phys_addr_t pgtable) { unsigned int prrr, nmrr; + int i, j, found; __reset_context(base, ctx); /* Set up HTW mode */ @@ -152,7 +151,7 @@ static void __program_context(void __iomem *base, int ctx, phys_addr_t pgtable) SET_V2PCFG(base, ctx, 0x3); SET_TTBCR(base, ctx, 0); - SET_TTBR0_PA(base, ctx, (pgtable >> 14)); + SET_TTBR0_PA(base, ctx, (pgtable >> TTBR0_PA_SHIFT)); /* Invalidate the TLB for this context */ SET_CTX_TLBIALL(base, ctx, 0); @@ -203,8 +202,38 @@ static void __program_context(void __iomem *base, int ctx, phys_addr_t pgtable) SET_TTBR1_ORGN(base, ctx, 1); /* WB, WA */ #endif + /* Find if this page table is used elsewhere, and re-use ASID */ + found = 0; + for (i = 0; i < ncb; i++) + if (GET_TTBR0_PA(base, i) == (pgtable >> TTBR0_PA_SHIFT) && + i != ctx) { + SET_CONTEXTIDR_ASID(base, ctx, \ + GET_CONTEXTIDR_ASID(base, i)); + found = 1; + break; + } + + /* If page table is new, find an unused ASID */ + if (!found) { + for (i = 0; i < ncb; i++) { + found = 0; + for (j = 0; j < ncb; j++) { + if (GET_CONTEXTIDR_ASID(base, j) == i && + j != ctx) + found = 1; + } + + if (!found) { + SET_CONTEXTIDR_ASID(base, ctx, i); + break; + } + } + BUG_ON(found); + } + /* Enable the MMU */ SET_M(base, ctx, 1); + mb(); } static int msm_iommu_domain_init(struct iommu_domain *domain) @@ -300,7 +329,7 @@ static int msm_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) if (ret) goto fail; - __program_context(iommu_drvdata->base, ctx_dev->num, + __program_context(iommu_drvdata->base, ctx_dev->num, iommu_drvdata->ncb, __pa(priv->pgtable)); __disable_clocks(iommu_drvdata); @@ -414,44 +443,77 @@ static int msm_iommu_map(struct iommu_domain *domain, unsigned long va, if (len == SZ_16M) { int i = 0; + + for (i = 0; i < 16; i++) + if (*(fl_pte+i)) { + ret = -EBUSY; + goto fail; + } + for (i = 0; i < 16; i++) *(fl_pte+i) = (pa & 0xFF000000) | FL_SUPERSECTION | FL_AP_READ | FL_AP_WRITE | FL_TYPE_SECT | FL_SHARED | FL_NG | pgprot; } - if (len == SZ_1M) + if (len == SZ_1M) { + if (*fl_pte) { + ret = -EBUSY; + goto fail; + } + *fl_pte = (pa & 0xFFF00000) | FL_AP_READ | FL_AP_WRITE | FL_NG | FL_TYPE_SECT | FL_SHARED | pgprot; + } /* Need a 2nd level table */ - if ((len == SZ_4K || len == SZ_64K) && (*fl_pte) == 0) { - unsigned long *sl; - sl = (unsigned long *) __get_free_pages(GFP_ATOMIC, + if (len == SZ_4K || len == SZ_64K) { + + if (*fl_pte == 0) { + unsigned long *sl; + sl = (unsigned long *) __get_free_pages(GFP_ATOMIC, get_order(SZ_4K)); - if (!sl) { - pr_debug("Could not allocate second level table\n"); - ret = -ENOMEM; - goto fail; + if (!sl) { + pr_debug("Could not allocate second level table\n"); + ret = -ENOMEM; + goto fail; + } + memset(sl, 0, SZ_4K); + + *fl_pte = ((((int)__pa(sl)) & FL_BASE_MASK) | \ + FL_TYPE_TABLE); } - memset(sl, 0, SZ_4K); - *fl_pte = ((((int)__pa(sl)) & FL_BASE_MASK) | FL_TYPE_TABLE); + if (!(*fl_pte & FL_TYPE_TABLE)) { + ret = -EBUSY; + goto fail; + } } sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK)); sl_offset = SL_OFFSET(va); sl_pte = sl_table + sl_offset; + if (len == SZ_4K) { + if (*sl_pte) { + ret = -EBUSY; + goto fail; + } - if (len == SZ_4K) *sl_pte = (pa & SL_BASE_MASK_SMALL) | SL_AP0 | SL_AP1 | SL_NG | SL_SHARED | SL_TYPE_SMALL | pgprot; + } if (len == SZ_64K) { int i; + for (i = 0; i < 16; i++) + if (*(sl_pte+i)) { + ret = -EBUSY; + goto fail; + } + for (i = 0; i < 16; i++) *(sl_pte+i) = (pa & SL_BASE_MASK_LARGE) | SL_AP0 | SL_NG | SL_AP1 | SL_SHARED | SL_TYPE_LARGE | pgprot; @@ -579,8 +641,10 @@ static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain, /* Invalidate context TLB */ SET_CTX_TLBIALL(base, ctx, 0); + mb(); SET_V2PPR(base, ctx, va & V2Pxx_VA); + mb(); par = GET_PAR(base, ctx); /* We are dealing with a supersection */ @@ -649,6 +713,7 @@ irqreturn_t msm_iommu_fault_handler(int irq, void *dev_id) pr_err("Unexpected IOMMU page fault!\n"); pr_err("base = %08x\n", (unsigned int) base); + pr_err("name = %s\n", drvdata->name); ret = __enable_clocks(drvdata); if (ret) diff --git a/arch/arm/mach-msm/iommu_dev.c b/arch/arm/mach-msm/iommu_dev.c index 8e8fb079852..cf7c920dcce 100644 --- a/arch/arm/mach-msm/iommu_dev.c +++ b/arch/arm/mach-msm/iommu_dev.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -124,13 +119,14 @@ static void msm_iommu_reset(void __iomem *base, int ncb) SET_NMRR(base, ctx, 0); SET_CONTEXTIDR(base, ctx, 0); } + mb(); } static int msm_iommu_probe(struct platform_device *pdev) { struct resource *r, *r2; - struct clk *iommu_clk; - struct clk *iommu_pclk; + struct clk *iommu_clk = NULL; + struct clk *iommu_pclk = NULL; struct msm_iommu_drvdata *drvdata; struct msm_iommu_dev *iommu_dev = pdev->dev.platform_data; void __iomem *regs_base; @@ -204,7 +200,7 @@ static int msm_iommu_probe(struct platform_device *pdev) goto fail_mem; } - irq = platform_get_irq_byname(pdev, "secure_irq"); + irq = platform_get_irq_byname(pdev, "nonsecure_irq"); if (irq < 0) { ret = -ENODEV; goto fail_io; @@ -216,9 +212,11 @@ static int msm_iommu_probe(struct platform_device *pdev) SET_PAR(regs_base, 0, 0); SET_V2PCFG(regs_base, 0, 1); SET_V2PPR(regs_base, 0, 0); + mb(); par = GET_PAR(regs_base, 0); SET_V2PCFG(regs_base, 0, 0); SET_M(regs_base, 0, 0); + mb(); if (!par) { pr_err("%s: Invalid PAR value detected\n", iommu_dev->name); @@ -239,6 +237,7 @@ static int msm_iommu_probe(struct platform_device *pdev) drvdata->base = regs_base; drvdata->irq = irq; drvdata->ncb = iommu_dev->ncb; + drvdata->name = iommu_dev->name; pr_info("device %s mapped at %p, irq %d with %d ctx banks\n", iommu_dev->name, regs_base, irq, iommu_dev->ncb); @@ -341,15 +340,16 @@ static int msm_iommu_ctx_probe(struct platform_device *pdev) /* Set the context number for that MID to this context */ SET_CBNDX(drvdata->base, mid, c->num); - /* Set MID associated with this context bank to 0*/ + /* Set MID associated with this context bank to 0 */ SET_CBVMID(drvdata->base, c->num, 0); - /* Set the ASID for TLB tagging for this context */ - SET_CONTEXTIDR_ASID(drvdata->base, c->num, c->num); + /* Set the ASID for TLB tagging for this context to 0 */ + SET_CONTEXTIDR_ASID(drvdata->base, c->num, 0); /* Set security bit override to be Non-secure */ SET_NSCFG(drvdata->base, mid, 3); } + mb(); if (drvdata->clk) clk_disable(drvdata->clk); diff --git a/arch/arm/mach-msm/iommu_domains.c b/arch/arm/mach-msm/iommu_domains.c new file mode 100644 index 00000000000..eb4582e7511 --- /dev/null +++ b/arch/arm/mach-msm/iommu_domains.c @@ -0,0 +1,273 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct msm_iommu_domain { + int domain_idx; + int iova_pool_idx; +}; + +enum { + GLOBAL_DOMAIN, + VIDEO_DOMAIN, + EMPTY_DOMAIN, + MAX_DOMAINS +}; + +enum { + GLOBAL_MEMORY_POOL, + VIDEO_FIRMWARE_POOL, + VIDEO_ALLOC_POOL, +}; + +struct { + char *name; + int domain; +} msm_iommu_ctx_names[] = { + /* Camera */ + { + .name = "vpe_src", + .domain = GLOBAL_DOMAIN, + }, + /* Camera */ + { + .name = "vpe_dst", + .domain = GLOBAL_DOMAIN, + }, + /* Camera */ + { + .name = "vfe_imgwr", + .domain = GLOBAL_DOMAIN, + }, + /* Camera */ + { + .name = "vfe_misc", + .domain = GLOBAL_DOMAIN, + }, + /* Camera */ + { + .name = "ijpeg_src", + .domain = GLOBAL_DOMAIN, + }, + /* Camera */ + { + .name = "ijpeg_dst", + .domain = GLOBAL_DOMAIN, + }, + /* Camera */ + { + .name = "jpegd_src", + .domain = GLOBAL_DOMAIN, + }, + /* Camera */ + { + .name = "jpegd_dst", + .domain = GLOBAL_DOMAIN, + }, + /* Display */ + { + .name = "mdp_vg1", + .domain = GLOBAL_DOMAIN, + }, + /* Display */ + { + .name = "mdp_vg2", + .domain = GLOBAL_DOMAIN, + }, + /* Display */ + { + .name = "mdp_rgb1", + .domain = GLOBAL_DOMAIN, + }, + /* Display */ + { + .name = "mdp_rgb2", + .domain = GLOBAL_DOMAIN, + }, + /* Rotator */ + { + .name = "rot_src", + .domain = GLOBAL_DOMAIN, + }, + /* Rotator */ + { + .name = "rot_dst", + .domain = GLOBAL_DOMAIN, + }, + /* Video */ + { + .name = "vcodec_a_mm1", + .domain = VIDEO_DOMAIN, + }, + /* Video */ + { + .name = "vcodec_b_mm2", + .domain = VIDEO_DOMAIN, + }, + /* Video */ + { + .name = "vcodec_a_stream", + .domain = VIDEO_DOMAIN, + }, +}; + +static struct iommu_domain *msm_iommu_domains[MAX_DOMAINS]; + +static struct mem_pool msm_iommu_iova_pools[] = { + [GLOBAL_MEMORY_POOL] = { + .paddr = SZ_4K, + .size = SZ_2G - SZ_4K, + }, + /* + * The video hardware has several constraints: + * 1) The start address for firmware must be 128K aligned + * 2) The video firmware must exist at a lower address than + * all other video allocations + * 3) Video allocations cannot be more than 256MB away from the + * firmware + * + * Splitting the video pools makes sure that firmware will + * always be lower than regular allocations and the maximum + * size of 256MB will be enforced. + */ + [VIDEO_FIRMWARE_POOL] = { + .paddr = SZ_128K, + .size = SZ_16M - SZ_128K, + }, + [VIDEO_ALLOC_POOL] = { + .paddr = SZ_16M, + .size = SZ_256M - SZ_16M - SZ_128K, + } +}; + +static struct msm_iommu_domain msm_iommu_subsystems[] = { + [JPEGD_SUBSYS_ID] = { + .domain_idx = GLOBAL_DOMAIN, + .iova_pool_idx = GLOBAL_MEMORY_POOL, + }, + [VPE_SUBSYS_ID] = { + .domain_idx = GLOBAL_DOMAIN, + .iova_pool_idx = GLOBAL_MEMORY_POOL, + }, + [MDP0_SUBSYS_ID] = { + .domain_idx = GLOBAL_DOMAIN, + .iova_pool_idx = GLOBAL_MEMORY_POOL, + }, + [MDP1_SUBSYS_ID] = { + .domain_idx = GLOBAL_DOMAIN, + .iova_pool_idx = GLOBAL_MEMORY_POOL, + }, + [ROT_SUBSYS_ID] = { + .domain_idx = GLOBAL_DOMAIN, + .iova_pool_idx = GLOBAL_MEMORY_POOL, + }, + [IJPEG_SUBSYS_ID] = { + .domain_idx = GLOBAL_DOMAIN, + .iova_pool_idx = GLOBAL_MEMORY_POOL, + }, + [VFE_SUBSYS_ID] = { + .domain_idx = GLOBAL_DOMAIN, + .iova_pool_idx = GLOBAL_MEMORY_POOL, + }, + [VCODEC_A_SUBSYS_ID] = { + .domain_idx = VIDEO_DOMAIN, + .iova_pool_idx = VIDEO_ALLOC_POOL, + }, + [VCODEC_B_SUBSYS_ID] = { + .domain_idx = VIDEO_DOMAIN, + .iova_pool_idx = VIDEO_ALLOC_POOL, + }, + [VIDEO_FWARE_ID] = { + .domain_idx = VIDEO_DOMAIN, + .iova_pool_idx = VIDEO_FIRMWARE_POOL, + } +}; + +struct iommu_domain *msm_subsystem_get_domain(int subsys_id) +{ + int id = msm_iommu_subsystems[subsys_id].domain_idx; + + return msm_iommu_domains[id]; +} + +struct mem_pool *msm_subsystem_get_pool(int subsys_id) +{ + int id = msm_iommu_subsystems[subsys_id].iova_pool_idx; + + return &msm_iommu_iova_pools[id]; +} + +static int __init msm_subsystem_iommu_init(void) +{ + int i; + + for (i = 0; i < (ARRAY_SIZE(msm_iommu_domains) - 1); i++) + msm_iommu_domains[i] = iommu_domain_alloc(); + + for (i = 0; i < ARRAY_SIZE(msm_iommu_iova_pools); i++) { + mutex_init(&msm_iommu_iova_pools[i].pool_mutex); + msm_iommu_iova_pools[i].gpool = gen_pool_create(PAGE_SHIFT, -1); + + if (!msm_iommu_iova_pools[i].gpool) { + pr_err("%s: could not allocate iova pool. iommu" + " programming will not work with iova space" + " %d\n", __func__, i); + continue; + } + + if (gen_pool_add(msm_iommu_iova_pools[i].gpool, + msm_iommu_iova_pools[i].paddr, + msm_iommu_iova_pools[i].size, + -1)) { + pr_err("%s: could not add memory to iova pool. iommu" + " programming will not work with iova space" + " %d\n", __func__, i); + gen_pool_destroy(msm_iommu_iova_pools[i].gpool); + msm_iommu_iova_pools[i].gpool = NULL; + continue; + } + } + + for (i = 0; i < ARRAY_SIZE(msm_iommu_ctx_names); i++) { + int domain_idx; + struct device *ctx = msm_iommu_get_ctx( + msm_iommu_ctx_names[i].name); + + if (!ctx) + continue; + + domain_idx = msm_iommu_ctx_names[i].domain; + + if (!msm_iommu_domains[domain_idx]) + continue; + + if (iommu_attach_device(msm_iommu_domains[domain_idx], ctx)) { + pr_err("%s: could not attach domain %d to context %s." + " iommu programming will not occur.\n", + __func__, domain_idx, + msm_iommu_ctx_names[i].name); + msm_iommu_subsystems[i].domain_idx = EMPTY_DOMAIN; + continue; + } + } + + return 0; +} +device_initcall(msm_subsystem_iommu_init); diff --git a/arch/arm/mach-msm/ipc_router.c b/arch/arm/mach-msm/ipc_router.c new file mode 100644 index 00000000000..5f185c7d5cc --- /dev/null +++ b/arch/arm/mach-msm/ipc_router.c @@ -0,0 +1,2169 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "ipc_router.h" + +enum { + SMEM_LOG = 1U << 0, + RTR_DBG = 1U << 1, + R2R_MSG = 1U << 2, + R2R_RAW = 1U << 3, + NTFY_MSG = 1U << 4, + R2R_RAW_HDR = 1U << 5, +}; + +static int msm_ipc_router_debug_mask; +module_param_named(debug_mask, msm_ipc_router_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define DIAG(x...) pr_info("[RR] ERROR " x) + +#if defined(DEBUG) +#define D(x...) do { \ +if (msm_ipc_router_debug_mask & RTR_DBG) \ + pr_info(x); \ +} while (0) + +#define RR(x...) do { \ +if (msm_ipc_router_debug_mask & R2R_MSG) \ + pr_info("[RR] "x); \ +} while (0) + +#define RAW(x...) do { \ +if (msm_ipc_router_debug_mask & R2R_RAW) \ + pr_info("[RAW] "x); \ +} while (0) + +#define NTFY(x...) do { \ +if (msm_ipc_router_debug_mask & NTFY_MSG) \ + pr_info("[NOTIFY] "x); \ +} while (0) + +#define RAW_HDR(x...) do { \ +if (msm_ipc_router_debug_mask & R2R_RAW_HDR) \ + pr_info("[HDR] "x); \ +} while (0) +#else +#define D(x...) do { } while (0) +#define RR(x...) do { } while (0) +#define RAW(x...) do { } while (0) +#define RAW_HDR(x...) do { } while (0) +#define NTFY(x...) do { } while (0) +#endif + +#define IPC_ROUTER_LOG_EVENT_ERROR 0x10 +#define IPC_ROUTER_LOG_EVENT_TX 0x11 +#define IPC_ROUTER_LOG_EVENT_RX 0x12 + +static LIST_HEAD(control_ports); +static DEFINE_MUTEX(control_ports_lock); + +#define LP_HASH_SIZE 32 +static struct list_head local_ports[LP_HASH_SIZE]; +static DEFINE_MUTEX(local_ports_lock); + +#define SRV_HASH_SIZE 32 +static struct list_head server_list[SRV_HASH_SIZE]; +static DEFINE_MUTEX(server_list_lock); +static wait_queue_head_t newserver_wait; + +struct msm_ipc_server { + struct list_head list; + struct msm_ipc_port_name name; + struct list_head server_port_list; +}; + +struct msm_ipc_server_port { + struct list_head list; + struct msm_ipc_port_addr server_addr; +}; + +#define RP_HASH_SIZE 32 +struct msm_ipc_router_remote_port { + struct list_head list; + uint32_t node_id; + uint32_t port_id; + wait_queue_head_t quota_wait; + uint32_t tx_quota_cnt; + struct mutex quota_lock; +}; + +struct msm_ipc_router_xprt_info { + struct list_head list; + struct msm_ipc_router_xprt *xprt; + uint32_t remote_node_id; + uint32_t initialized; + struct list_head pkt_list; + wait_queue_head_t read_wait; + struct wake_lock wakelock; + struct mutex rx_lock; + struct mutex tx_lock; + uint32_t need_len; + struct work_struct read_data; + struct workqueue_struct *workqueue; +}; + +#define RT_HASH_SIZE 4 +struct msm_ipc_routing_table_entry { + struct list_head list; + uint32_t node_id; + struct list_head remote_port_list[RP_HASH_SIZE]; + struct msm_ipc_router_xprt_info *xprt_info; + struct mutex lock; + unsigned long num_tx_bytes; + unsigned long num_rx_bytes; +}; + +static struct list_head routing_table[RT_HASH_SIZE]; +static DEFINE_MUTEX(routing_table_lock); +static int routing_table_inited; + +static LIST_HEAD(msm_ipc_board_dev_list); +static DEFINE_MUTEX(msm_ipc_board_dev_list_lock); + +static void do_read_data(struct work_struct *work); + +#define RR_STATE_IDLE 0 +#define RR_STATE_HEADER 1 +#define RR_STATE_BODY 2 +#define RR_STATE_ERROR 3 + +#define RESTART_NORMAL 0 +#define RESTART_PEND_SVR 1 +#define RESTART_PEND_NTFY 2 +#define RESTART_PEND_NTFY_SVR 3 + +/* State for remote ep following restart */ +#define RESTART_QUOTA_ABORT 1 + +static LIST_HEAD(xprt_info_list); +static DEFINE_MUTEX(xprt_info_list_lock); + +DECLARE_COMPLETION(msm_ipc_remote_router_up); + +static uint32_t next_port_id; +static DEFINE_MUTEX(next_port_id_lock); + +enum { + CLIENT_PORT, + SERVER_PORT, + CONTROL_PORT, +}; + +enum { + DOWN, + UP, +}; + +static void init_routing_table(void) +{ + int i; + for (i = 0; i < RT_HASH_SIZE; i++) + INIT_LIST_HEAD(&routing_table[i]); +} + +static struct msm_ipc_routing_table_entry *alloc_routing_table_entry( + uint32_t node_id) +{ + int i; + struct msm_ipc_routing_table_entry *rt_entry; + + rt_entry = kmalloc(sizeof(struct msm_ipc_routing_table_entry), + GFP_KERNEL); + if (!rt_entry) { + pr_err("%s: rt_entry allocation failed for %d\n", + __func__, node_id); + return NULL; + } + + for (i = 0; i < RP_HASH_SIZE; i++) + INIT_LIST_HEAD(&rt_entry->remote_port_list[i]); + + mutex_init(&rt_entry->lock); + rt_entry->node_id = node_id; + rt_entry->xprt_info = NULL; + return rt_entry; +} + +/*Please take routing_table_lock before calling this function*/ +static int add_routing_table_entry( + struct msm_ipc_routing_table_entry *rt_entry) +{ + uint32_t key; + + if (!rt_entry) + return -EINVAL; + + key = (rt_entry->node_id % RT_HASH_SIZE); + list_add_tail(&rt_entry->list, &routing_table[key]); + return 0; +} + +/*Please take routing_table_lock before calling this function*/ +static struct msm_ipc_routing_table_entry *lookup_routing_table( + uint32_t node_id) +{ + uint32_t key = (node_id % RT_HASH_SIZE); + struct msm_ipc_routing_table_entry *rt_entry; + + list_for_each_entry(rt_entry, &routing_table[key], list) { + if (rt_entry->node_id == node_id) + return rt_entry; + } + return NULL; +} + +struct rr_packet *rr_read(struct msm_ipc_router_xprt_info *xprt_info) +{ + struct rr_packet *temp_pkt; + + if (!xprt_info) + return NULL; + + mutex_lock(&xprt_info->rx_lock); + while (list_empty(&xprt_info->pkt_list)) { + mutex_unlock(&xprt_info->rx_lock); + wait_event(xprt_info->read_wait, + !list_empty(&xprt_info->pkt_list)); + mutex_lock(&xprt_info->rx_lock); + } + temp_pkt = list_first_entry(&xprt_info->pkt_list, + struct rr_packet, list); + list_del(&temp_pkt->list); + if (list_empty(&xprt_info->pkt_list)) + wake_unlock(&xprt_info->wakelock); + mutex_unlock(&xprt_info->rx_lock); + return temp_pkt; +} + +struct rr_packet *clone_pkt(struct rr_packet *pkt) +{ + struct rr_packet *cloned_pkt; + struct sk_buff *temp_skb, *cloned_skb; + struct sk_buff_head *pkt_fragment_q; + + cloned_pkt = kzalloc(sizeof(struct rr_packet), GFP_KERNEL); + if (!cloned_pkt) { + pr_err("%s: failure\n", __func__); + return NULL; + } + + pkt_fragment_q = kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL); + if (!pkt_fragment_q) { + pr_err("%s: pkt_frag_q alloc failure\n", __func__); + kfree(cloned_pkt); + return NULL; + } + skb_queue_head_init(pkt_fragment_q); + + skb_queue_walk(pkt->pkt_fragment_q, temp_skb) { + cloned_skb = skb_clone(temp_skb, GFP_KERNEL); + if (!cloned_skb) + goto fail_clone; + skb_queue_tail(pkt_fragment_q, cloned_skb); + } + cloned_pkt->pkt_fragment_q = pkt_fragment_q; + cloned_pkt->length = pkt->length; + return cloned_pkt; + +fail_clone: + while (!skb_queue_empty(pkt_fragment_q)) { + temp_skb = skb_dequeue(pkt_fragment_q); + kfree_skb(temp_skb); + } + kfree(pkt_fragment_q); + kfree(cloned_pkt); + return NULL; +} + +struct rr_packet *create_pkt(struct sk_buff_head *data) +{ + struct rr_packet *pkt; + struct sk_buff *temp_skb; + + pkt = kzalloc(sizeof(struct rr_packet), GFP_KERNEL); + if (!pkt) { + pr_err("%s: failure\n", __func__); + return NULL; + } + + pkt->pkt_fragment_q = data; + skb_queue_walk(pkt->pkt_fragment_q, temp_skb) + pkt->length += temp_skb->len; + return pkt; +} + +void release_pkt(struct rr_packet *pkt) +{ + struct sk_buff *temp_skb; + + if (!pkt) + return; + + if (!pkt->pkt_fragment_q) { + kfree(pkt); + return; + } + + while (!skb_queue_empty(pkt->pkt_fragment_q)) { + temp_skb = skb_dequeue(pkt->pkt_fragment_q); + kfree_skb(temp_skb); + } + kfree(pkt->pkt_fragment_q); + kfree(pkt); + return; +} + +static int post_control_ports(struct rr_packet *pkt) +{ + struct msm_ipc_port *port_ptr; + struct rr_packet *cloned_pkt; + + if (!pkt) + return -EINVAL; + + mutex_lock(&control_ports_lock); + list_for_each_entry(port_ptr, &control_ports, list) { + mutex_lock(&port_ptr->port_rx_q_lock); + cloned_pkt = clone_pkt(pkt); + wake_lock(&port_ptr->port_rx_wake_lock); + list_add_tail(&cloned_pkt->list, &port_ptr->port_rx_q); + wake_up(&port_ptr->port_rx_wait_q); + mutex_unlock(&port_ptr->port_rx_q_lock); + } + mutex_unlock(&control_ports_lock); + return 0; +} + +static uint32_t allocate_port_id(void) +{ + uint32_t port_id = 0, prev_port_id, key; + struct msm_ipc_port *port_ptr; + + mutex_lock(&next_port_id_lock); + prev_port_id = next_port_id; + mutex_lock(&local_ports_lock); + do { + next_port_id++; + if ((next_port_id & 0xFFFFFFFE) == 0xFFFFFFFE) + next_port_id = 1; + + key = (next_port_id & (LP_HASH_SIZE - 1)); + if (list_empty(&local_ports[key])) { + port_id = next_port_id; + break; + } + list_for_each_entry(port_ptr, &local_ports[key], list) { + if (port_ptr->this_port.port_id == next_port_id) { + port_id = next_port_id; + break; + } + } + if (!port_id) { + port_id = next_port_id; + break; + } + port_id = 0; + } while (next_port_id != prev_port_id); + mutex_unlock(&local_ports_lock); + mutex_unlock(&next_port_id_lock); + + return port_id; +} + +void msm_ipc_router_add_local_port(struct msm_ipc_port *port_ptr) +{ + uint32_t key; + + if (!port_ptr) + return; + + key = (port_ptr->this_port.port_id & (LP_HASH_SIZE - 1)); + mutex_lock(&local_ports_lock); + list_add_tail(&port_ptr->list, &local_ports[key]); + mutex_unlock(&local_ports_lock); +} + +struct msm_ipc_port *msm_ipc_router_create_raw_port(void *endpoint, + void (*notify)(unsigned event, void *data, + void *addr, void *priv), + void *priv) +{ + struct msm_ipc_port *port_ptr; + + port_ptr = kzalloc(sizeof(struct msm_ipc_port), GFP_KERNEL); + if (!port_ptr) + return NULL; + + port_ptr->this_port.node_id = IPC_ROUTER_NID_LOCAL; + port_ptr->this_port.port_id = allocate_port_id(); + if (!port_ptr->this_port.port_id) { + pr_err("%s: All port ids are in use\n", __func__); + kfree(port_ptr); + return NULL; + } + + spin_lock_init(&port_ptr->port_lock); + INIT_LIST_HEAD(&port_ptr->incomplete); + mutex_init(&port_ptr->incomplete_lock); + INIT_LIST_HEAD(&port_ptr->port_rx_q); + mutex_init(&port_ptr->port_rx_q_lock); + init_waitqueue_head(&port_ptr->port_rx_wait_q); + wake_lock_init(&port_ptr->port_rx_wake_lock, + WAKE_LOCK_SUSPEND, "msm_ipc_read"); + + port_ptr->endpoint = endpoint; + port_ptr->notify = notify; + port_ptr->priv = priv; + + msm_ipc_router_add_local_port(port_ptr); + return port_ptr; +} + +static struct msm_ipc_port *msm_ipc_router_lookup_local_port(uint32_t port_id) +{ + int key = (port_id & (LP_HASH_SIZE - 1)); + struct msm_ipc_port *port_ptr; + + mutex_lock(&local_ports_lock); + list_for_each_entry(port_ptr, &local_ports[key], list) { + if (port_ptr->this_port.port_id == port_id) { + mutex_unlock(&local_ports_lock); + return port_ptr; + } + } + mutex_unlock(&local_ports_lock); + return NULL; +} + +static struct msm_ipc_router_remote_port *msm_ipc_router_lookup_remote_port( + uint32_t node_id, + uint32_t port_id) +{ + struct msm_ipc_router_remote_port *rport_ptr; + struct msm_ipc_routing_table_entry *rt_entry; + int key = (port_id & (RP_HASH_SIZE - 1)); + + mutex_lock(&routing_table_lock); + rt_entry = lookup_routing_table(node_id); + if (!rt_entry) { + mutex_unlock(&routing_table_lock); + pr_err("%s: Node is not up\n", __func__); + return NULL; + } + + mutex_lock(&rt_entry->lock); + list_for_each_entry(rport_ptr, + &rt_entry->remote_port_list[key], list) { + if (rport_ptr->port_id == port_id) { + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + return rport_ptr; + } + } + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + return NULL; +} + +static struct msm_ipc_router_remote_port *msm_ipc_router_create_remote_port( + uint32_t node_id, + uint32_t port_id) +{ + struct msm_ipc_router_remote_port *rport_ptr; + struct msm_ipc_routing_table_entry *rt_entry; + int key = (port_id & (RP_HASH_SIZE - 1)); + + mutex_lock(&routing_table_lock); + rt_entry = lookup_routing_table(node_id); + if (!rt_entry) { + mutex_unlock(&routing_table_lock); + pr_err("%s: Node is not up\n", __func__); + return NULL; + } + + mutex_lock(&rt_entry->lock); + rport_ptr = kmalloc(sizeof(struct msm_ipc_router_remote_port), + GFP_KERNEL); + if (!rport_ptr) { + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + pr_err("%s: Remote port alloc failed\n", __func__); + return NULL; + } + rport_ptr->port_id = port_id; + rport_ptr->node_id = node_id; + rport_ptr->tx_quota_cnt = 0; + init_waitqueue_head(&rport_ptr->quota_wait); + mutex_init(&rport_ptr->quota_lock); + list_add_tail(&rport_ptr->list, + &rt_entry->remote_port_list[key]); + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + return rport_ptr; +} + +static void msm_ipc_router_destroy_remote_port( + struct msm_ipc_router_remote_port *rport_ptr) +{ + uint32_t node_id; + struct msm_ipc_routing_table_entry *rt_entry; + + if (!rport_ptr) + return; + + node_id = rport_ptr->node_id; + mutex_lock(&routing_table_lock); + rt_entry = lookup_routing_table(node_id); + if (!rt_entry) { + mutex_unlock(&routing_table_lock); + pr_err("%s: Node %d is not up\n", __func__, node_id); + return; + } + + mutex_lock(&rt_entry->lock); + list_del(&rport_ptr->list); + kfree(rport_ptr); + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + return; +} + +static struct msm_ipc_server *msm_ipc_router_lookup_server( + uint32_t service, + uint32_t instance, + uint32_t node_id, + uint32_t port_id) +{ + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + int key = (instance & (SRV_HASH_SIZE - 1)); + + mutex_lock(&server_list_lock); + list_for_each_entry(server, &server_list[key], list) { + if ((server->name.service != service) || + (server->name.instance != instance)) + continue; + if ((node_id == 0) && (port_id == 0)) { + mutex_unlock(&server_list_lock); + return server; + } + list_for_each_entry(server_port, &server->server_port_list, + list) { + if ((server_port->server_addr.node_id == node_id) && + (server_port->server_addr.port_id == port_id)) { + mutex_unlock(&server_list_lock); + return server; + } + } + } + mutex_unlock(&server_list_lock); + return NULL; +} + +static struct msm_ipc_server *msm_ipc_router_create_server( + uint32_t service, + uint32_t instance, + uint32_t node_id, + uint32_t port_id) +{ + struct msm_ipc_server *server = NULL; + struct msm_ipc_server_port *server_port; + int key = (instance & (SRV_HASH_SIZE - 1)); + + mutex_lock(&server_list_lock); + list_for_each_entry(server, &server_list[key], list) { + if ((server->name.service == service) && + (server->name.instance == instance)) + goto create_srv_port; + } + + server = kmalloc(sizeof(struct msm_ipc_server), GFP_KERNEL); + if (!server) { + mutex_unlock(&server_list_lock); + pr_err("%s: Server allocation failed\n", __func__); + return NULL; + } + server->name.service = service; + server->name.instance = instance; + INIT_LIST_HEAD(&server->server_port_list); + list_add_tail(&server->list, &server_list[key]); + +create_srv_port: + server_port = kmalloc(sizeof(struct msm_ipc_server_port), GFP_KERNEL); + if (!server_port) { + if (list_empty(&server->server_port_list)) { + list_del(&server->list); + kfree(server); + } + mutex_unlock(&server_list_lock); + pr_err("%s: Server Port allocation failed\n", __func__); + return NULL; + } + server_port->server_addr.node_id = node_id; + server_port->server_addr.port_id = port_id; + list_add_tail(&server_port->list, &server->server_port_list); + mutex_unlock(&server_list_lock); + + return server; +} + +static void msm_ipc_router_destroy_server(struct msm_ipc_server *server, + uint32_t node_id, uint32_t port_id) +{ + struct msm_ipc_server_port *server_port; + + if (!server) + return; + + mutex_lock(&server_list_lock); + list_for_each_entry(server_port, &server->server_port_list, list) { + if ((server_port->server_addr.node_id == node_id) && + (server_port->server_addr.port_id == port_id)) + break; + } + if (server_port) { + list_del(&server_port->list); + kfree(server_port); + } + if (list_empty(&server->server_port_list)) { + list_del(&server->list); + kfree(server); + } + mutex_unlock(&server_list_lock); + return; +} + +static int msm_ipc_router_send_control_msg( + struct msm_ipc_router_xprt_info *xprt_info, + union rr_control_msg *msg) +{ + struct rr_packet *pkt; + struct sk_buff *ipc_rtr_pkt; + struct rr_header *hdr; + int pkt_size; + void *data; + struct sk_buff_head *pkt_fragment_q; + int ret; + + if (!xprt_info || ((msg->cmd != IPC_ROUTER_CTRL_CMD_HELLO) && + !xprt_info->initialized)) { + pr_err("%s: xprt_info not initialized\n", __func__); + return -EINVAL; + } + + if (xprt_info->remote_node_id == IPC_ROUTER_NID_LOCAL) + return 0; + + pkt = kzalloc(sizeof(struct rr_packet), GFP_KERNEL); + if (!pkt) { + pr_err("%s: pkt alloc failed\n", __func__); + return -ENOMEM; + } + + pkt_fragment_q = kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL); + if (!pkt_fragment_q) { + pr_err("%s: pkt_fragment_q alloc failed\n", __func__); + kfree(pkt); + return -ENOMEM; + } + skb_queue_head_init(pkt_fragment_q); + + pkt_size = IPC_ROUTER_HDR_SIZE + sizeof(*msg); + ipc_rtr_pkt = alloc_skb(pkt_size, GFP_KERNEL); + if (!ipc_rtr_pkt) { + pr_err("%s: ipc_rtr_pkt alloc failed\n", __func__); + kfree(pkt_fragment_q); + kfree(pkt); + return -ENOMEM; + } + + skb_reserve(ipc_rtr_pkt, IPC_ROUTER_HDR_SIZE); + data = skb_put(ipc_rtr_pkt, sizeof(*msg)); + memcpy(data, msg, sizeof(*msg)); + hdr = (struct rr_header *)skb_push(ipc_rtr_pkt, IPC_ROUTER_HDR_SIZE); + if (!hdr) { + pr_err("%s: skb_push failed\n", __func__); + kfree_skb(ipc_rtr_pkt); + kfree(pkt_fragment_q); + kfree(pkt); + return -ENOMEM; + } + + hdr->version = IPC_ROUTER_VERSION; + hdr->type = msg->cmd; + hdr->src_node_id = IPC_ROUTER_NID_LOCAL; + hdr->src_port_id = IPC_ROUTER_ADDRESS; + hdr->confirm_rx = 0; + hdr->size = sizeof(*msg); + hdr->dst_node_id = xprt_info->remote_node_id; + hdr->dst_port_id = IPC_ROUTER_ADDRESS; + skb_queue_tail(pkt_fragment_q, ipc_rtr_pkt); + pkt->pkt_fragment_q = pkt_fragment_q; + pkt->length = pkt_size; + + mutex_lock(&xprt_info->tx_lock); + ret = xprt_info->xprt->write(pkt, pkt_size, 0); + mutex_unlock(&xprt_info->tx_lock); + + release_pkt(pkt); + return ret; +} + +static int msm_ipc_router_send_server_list( + struct msm_ipc_router_xprt_info *xprt_info) +{ + union rr_control_msg ctl; + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + int i; + + if (!xprt_info || !xprt_info->initialized) { + pr_err("%s: Xprt info not initialized\n", __func__); + return -EINVAL; + } + + ctl.cmd = IPC_ROUTER_CTRL_CMD_NEW_SERVER; + + mutex_lock(&server_list_lock); + for (i = 0; i < SRV_HASH_SIZE; i++) { + list_for_each_entry(server, &server_list[i], list) { + ctl.srv.service = server->name.service; + ctl.srv.instance = server->name.instance; + list_for_each_entry(server_port, + &server->server_port_list, list) { + if (server_port->server_addr.node_id == + xprt_info->remote_node_id) + continue; + + ctl.srv.node_id = + server_port->server_addr.node_id; + ctl.srv.port_id = + server_port->server_addr.port_id; + msm_ipc_router_send_control_msg(xprt_info, + &ctl); + } + } + } + mutex_unlock(&server_list_lock); + + return 0; +} + +#if defined(DEBUG) +static char *type_to_str(int i) +{ + switch (i) { + case IPC_ROUTER_CTRL_CMD_DATA: + return "data "; + case IPC_ROUTER_CTRL_CMD_HELLO: + return "hello "; + case IPC_ROUTER_CTRL_CMD_BYE: + return "bye "; + case IPC_ROUTER_CTRL_CMD_NEW_SERVER: + return "new_srvr"; + case IPC_ROUTER_CTRL_CMD_REMOVE_SERVER: + return "rmv_srvr"; + case IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT: + return "rmv_clnt"; + case IPC_ROUTER_CTRL_CMD_RESUME_TX: + return "resum_tx"; + case IPC_ROUTER_CTRL_CMD_EXIT: + return "cmd_exit"; + default: + return "invalid"; + } +} +#endif + +static int broadcast_ctl_msg_locally(union rr_control_msg *msg) +{ + struct rr_packet *pkt; + struct sk_buff *ipc_rtr_pkt; + struct rr_header *hdr; + int pkt_size; + void *data; + struct sk_buff_head *pkt_fragment_q; + int ret; + + pkt = kzalloc(sizeof(struct rr_packet), GFP_KERNEL); + if (!pkt) { + pr_err("%s: pkt alloc failed\n", __func__); + return -ENOMEM; + } + + pkt_fragment_q = kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL); + if (!pkt_fragment_q) { + pr_err("%s: pkt_fragment_q alloc failed\n", __func__); + kfree(pkt); + return -ENOMEM; + } + skb_queue_head_init(pkt_fragment_q); + + pkt_size = IPC_ROUTER_HDR_SIZE + sizeof(*msg); + ipc_rtr_pkt = alloc_skb(pkt_size, GFP_KERNEL); + if (!ipc_rtr_pkt) { + pr_err("%s: ipc_rtr_pkt alloc failed\n", __func__); + kfree(pkt_fragment_q); + kfree(pkt); + return -ENOMEM; + } + + skb_reserve(ipc_rtr_pkt, IPC_ROUTER_HDR_SIZE); + data = skb_put(ipc_rtr_pkt, sizeof(*msg)); + memcpy(data, msg, sizeof(*msg)); + hdr = (struct rr_header *)skb_push(ipc_rtr_pkt, IPC_ROUTER_HDR_SIZE); + if (!hdr) { + pr_err("%s: skb_push failed\n", __func__); + kfree_skb(ipc_rtr_pkt); + kfree(pkt_fragment_q); + kfree(pkt); + return -ENOMEM; + } + hdr->version = IPC_ROUTER_VERSION; + hdr->type = msg->cmd; + hdr->src_node_id = IPC_ROUTER_NID_LOCAL; + hdr->src_port_id = IPC_ROUTER_ADDRESS; + hdr->confirm_rx = 0; + hdr->size = sizeof(*msg); + hdr->dst_node_id = IPC_ROUTER_NID_LOCAL; + hdr->dst_port_id = IPC_ROUTER_ADDRESS; + skb_queue_tail(pkt_fragment_q, ipc_rtr_pkt); + pkt->pkt_fragment_q = pkt_fragment_q; + pkt->length = pkt_size; + + ret = post_control_ports(pkt); + release_pkt(pkt); + return ret; +} + +static int broadcast_ctl_msg(union rr_control_msg *ctl) +{ + struct msm_ipc_router_xprt_info *xprt_info; + + mutex_lock(&xprt_info_list_lock); + list_for_each_entry(xprt_info, &xprt_info_list, list) { + msm_ipc_router_send_control_msg(xprt_info, ctl); + } + mutex_unlock(&xprt_info_list_lock); + + return 0; +} + +static int relay_msg(struct msm_ipc_router_xprt_info *xprt_info, + struct rr_packet *pkt) +{ + struct msm_ipc_router_xprt_info *fwd_xprt_info; + + if (!xprt_info || !pkt) + return -EINVAL; + + mutex_lock(&xprt_info_list_lock); + list_for_each_entry(fwd_xprt_info, &xprt_info_list, list) { + mutex_lock(&fwd_xprt_info->tx_lock); + if (xprt_info->xprt->link_id != fwd_xprt_info->xprt->link_id) + fwd_xprt_info->xprt->write(pkt, pkt->length, 0); + mutex_unlock(&fwd_xprt_info->tx_lock); + } + mutex_unlock(&xprt_info_list_lock); + return 0; +} + +static int forward_msg(struct msm_ipc_router_xprt_info *xprt_info, + struct rr_packet *pkt) +{ + uint32_t dst_node_id; + struct sk_buff *head_pkt; + struct rr_header *hdr; + struct msm_ipc_router_xprt_info *fwd_xprt_info; + struct msm_ipc_routing_table_entry *rt_entry; + + if (!xprt_info || !pkt) + return -EINVAL; + + head_pkt = skb_peek(pkt->pkt_fragment_q); + if (!head_pkt) + return -EINVAL; + + hdr = (struct rr_header *)head_pkt->data; + dst_node_id = hdr->dst_node_id; + mutex_lock(&routing_table_lock); + rt_entry = lookup_routing_table(dst_node_id); + if (!(rt_entry) || !(rt_entry->xprt_info)) { + mutex_unlock(&routing_table_lock); + pr_err("%s: Routing table not initialized\n", __func__); + return -ENODEV; + } + + mutex_lock(&rt_entry->lock); + fwd_xprt_info = rt_entry->xprt_info; + mutex_lock(&fwd_xprt_info->tx_lock); + if (xprt_info->remote_node_id == fwd_xprt_info->remote_node_id) { + mutex_unlock(&fwd_xprt_info->tx_lock); + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + pr_err("%s: Discarding Command to route back\n", __func__); + return -EINVAL; + } + + if (xprt_info->xprt->link_id == fwd_xprt_info->xprt->link_id) { + mutex_unlock(&fwd_xprt_info->tx_lock); + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + pr_err("%s: DST in the same cluster\n", __func__); + return 0; + } + fwd_xprt_info->xprt->write(pkt, pkt->length, 0); + mutex_unlock(&fwd_xprt_info->tx_lock); + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + + return 0; +} + +static int process_control_msg(struct msm_ipc_router_xprt_info *xprt_info, + struct rr_packet *pkt) +{ + union rr_control_msg ctl; + union rr_control_msg *msg; + struct msm_ipc_router_remote_port *rport_ptr; + int rc = 0; + static uint32_t first = 1; + struct sk_buff *temp_ptr; + struct rr_header *hdr; + struct msm_ipc_server *server; + struct msm_ipc_routing_table_entry *rt_entry; + + if (pkt->length != (IPC_ROUTER_HDR_SIZE + sizeof(*msg))) { + pr_err("%s: r2r msg size %d != %d\n", __func__, pkt->length, + (IPC_ROUTER_HDR_SIZE + sizeof(*msg))); + return -EINVAL; + } + + temp_ptr = skb_peek(pkt->pkt_fragment_q); + hdr = (struct rr_header *)temp_ptr->data; + msg = (union rr_control_msg *)((char *)hdr + IPC_ROUTER_HDR_SIZE); + + switch (msg->cmd) { + case IPC_ROUTER_CTRL_CMD_HELLO: + RR("o HELLO NID %d\n", hdr->src_node_id); + xprt_info->remote_node_id = hdr->src_node_id; + + mutex_lock(&routing_table_lock); + rt_entry = lookup_routing_table(hdr->src_node_id); + if (!rt_entry) { + rt_entry = alloc_routing_table_entry(hdr->src_node_id); + if (!rt_entry) { + mutex_unlock(&routing_table_lock); + pr_err("%s: rt_entry allocation failed\n", + __func__); + return -ENOMEM; + } + } + mutex_lock(&rt_entry->lock); + rt_entry->xprt_info = xprt_info; + mutex_unlock(&rt_entry->lock); + add_routing_table_entry(rt_entry); + mutex_unlock(&routing_table_lock); + + memset(&ctl, 0, sizeof(ctl)); + ctl.cmd = IPC_ROUTER_CTRL_CMD_HELLO; + msm_ipc_router_send_control_msg(xprt_info, &ctl); + + xprt_info->initialized = 1; + + /* Send list of servers one at a time */ + msm_ipc_router_send_server_list(xprt_info); + + if (first) { + first = 0; + complete_all(&msm_ipc_remote_router_up); + } + RR("HELLO message processed\n"); + break; + case IPC_ROUTER_CTRL_CMD_RESUME_TX: + RR("o RESUME_TX id=%d:%08x\n", + msg->cli.node_id, msg->cli.port_id); + + rport_ptr = msm_ipc_router_lookup_remote_port(msg->cli.node_id, + msg->cli.port_id); + if (!rport_ptr) { + pr_err("%s: Unable to resume client\n", __func__); + break; + } + mutex_lock(&rport_ptr->quota_lock); + rport_ptr->tx_quota_cnt = 0; + mutex_unlock(&rport_ptr->quota_lock); + wake_up(&rport_ptr->quota_wait); + break; + + case IPC_ROUTER_CTRL_CMD_NEW_SERVER: + if (msg->srv.instance == 0) { + pr_err( + "rpcrouter: Server create rejected, version = 0, " + "service = %08x\n", msg->srv.service); + break; + } + + RR("o NEW_SERVER id=%d:%08x service=%08x:%08x\n", + msg->srv.node_id, msg->srv.port_id, + msg->srv.service, msg->srv.instance); + + mutex_lock(&routing_table_lock); + rt_entry = lookup_routing_table(msg->srv.node_id); + if (!rt_entry) { + rt_entry = alloc_routing_table_entry(msg->srv.node_id); + if (!rt_entry) { + mutex_unlock(&routing_table_lock); + pr_err("%s: rt_entry allocation failed\n", + __func__); + return -ENOMEM; + } + mutex_lock(&rt_entry->lock); + rt_entry->xprt_info = xprt_info; + mutex_unlock(&rt_entry->lock); + add_routing_table_entry(rt_entry); + } + mutex_unlock(&routing_table_lock); + + server = msm_ipc_router_lookup_server(msg->srv.service, + msg->srv.instance, + msg->srv.node_id, + msg->srv.port_id); + if (!server) { + server = msm_ipc_router_create_server( + msg->srv.service, msg->srv.instance, + msg->srv.node_id, msg->srv.port_id); + if (!server) { + pr_err("%s: Server Create failed\n", __func__); + return -ENOMEM; + } + + if (!msm_ipc_router_lookup_remote_port( + msg->srv.node_id, msg->srv.port_id)) { + rport_ptr = msm_ipc_router_create_remote_port( + msg->srv.node_id, msg->srv.port_id); + if (!rport_ptr) + pr_err("%s: Remote port create " + "failed\n", __func__); + } + wake_up(&newserver_wait); + } + + relay_msg(xprt_info, pkt); + post_control_ports(pkt); + break; + case IPC_ROUTER_CTRL_CMD_REMOVE_SERVER: + RR("o REMOVE_SERVER service=%08x:%d\n", + msg->srv.service, msg->srv.instance); + server = msm_ipc_router_lookup_server(msg->srv.service, + msg->srv.instance, + msg->srv.node_id, + msg->srv.port_id); + if (server) { + msm_ipc_router_destroy_server(server, + msg->srv.node_id, + msg->srv.port_id); + relay_msg(xprt_info, pkt); + post_control_ports(pkt); + } + break; + case IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT: + RR("o REMOVE_CLIENT id=%d:%08x\n", + msg->cli.node_id, msg->cli.port_id); + rport_ptr = msm_ipc_router_lookup_remote_port(msg->cli.node_id, + msg->cli.port_id); + if (rport_ptr) + msm_ipc_router_destroy_remote_port(rport_ptr); + + relay_msg(xprt_info, pkt); + post_control_ports(pkt); + break; + case IPC_ROUTER_CTRL_CMD_PING: + /* No action needed for ping messages received */ + RR("o PING\n"); + break; + default: + RR("o UNKNOWN(%08x)\n", msg->cmd); + rc = -ENOSYS; + } + + return rc; +} + +static void do_read_data(struct work_struct *work) +{ + struct rr_header *hdr; + struct rr_packet *pkt = NULL; + struct msm_ipc_port *port_ptr; + struct sk_buff *head_skb; + struct msm_ipc_port_addr *src_addr; + uint32_t resume_tx, resume_tx_node_id, resume_tx_port_id; + + struct msm_ipc_router_xprt_info *xprt_info = + container_of(work, + struct msm_ipc_router_xprt_info, + read_data); + + pkt = rr_read(xprt_info); + if (!pkt) { + pr_err("%s: rr_read failed\n", __func__); + goto fail_io; + } + + if (pkt->length < IPC_ROUTER_HDR_SIZE || + pkt->length > MAX_IPC_PKT_SIZE) { + pr_err("%s: Invalid pkt length %d\n", __func__, pkt->length); + goto fail_data; + } + + head_skb = skb_peek(pkt->pkt_fragment_q); + if (!head_skb) { + pr_err("%s: head_skb is invalid\n", __func__); + goto fail_data; + } + + hdr = (struct rr_header *)(head_skb->data); + RR("- ver=%d type=%d src=%d:%08x crx=%d siz=%d dst=%d:%08x\n", + hdr->version, hdr->type, hdr->src_node_id, hdr->src_port_id, + hdr->confirm_rx, hdr->size, hdr->dst_node_id, hdr->dst_port_id); + RAW_HDR("[r rr_h] " + "ver=%i,type=%s,src_node_id=%08x,src_port_id=%08x," + "confirm_rx=%i,size=%3i,dst_node_id=%08x,dst_port_id=%08x\n", + hdr->version, type_to_str(hdr->type), hdr->src_node_id, + hdr->src_port_id, hdr->confirm_rx, hdr->size, hdr->dst_node_id, + hdr->dst_port_id); + + if (hdr->version != IPC_ROUTER_VERSION) { + pr_err("version %d != %d\n", hdr->version, IPC_ROUTER_VERSION); + goto fail_data; + } + + if ((hdr->dst_node_id != IPC_ROUTER_NID_LOCAL) && + ((hdr->type == IPC_ROUTER_CTRL_CMD_RESUME_TX) || + (hdr->type == IPC_ROUTER_CTRL_CMD_DATA))) { + forward_msg(xprt_info, pkt); + release_pkt(pkt); + goto done; + } + + if ((hdr->dst_port_id == IPC_ROUTER_ADDRESS) || + (hdr->type == IPC_ROUTER_CTRL_CMD_HELLO)) { + process_control_msg(xprt_info, pkt); + release_pkt(pkt); + goto done; + } +#if defined(CONFIG_MSM_SMD_LOGGING) +#if defined(DEBUG) + if (msm_ipc_router_debug_mask & SMEM_LOG) { + smem_log_event((SMEM_LOG_PROC_ID_APPS | + SMEM_LOG_RPC_ROUTER_EVENT_BASE | + IPC_ROUTER_LOG_EVENT_RX), + (hdr->src_node_id << 24) | + (hdr->src_port_id & 0xffffff), + (hdr->dst_node_id << 24) | + (hdr->dst_port_id & 0xffffff), + (hdr->type << 24) | (hdr->confirm_rx << 16) | + (hdr->size & 0xffff)); + } +#endif +#endif + + resume_tx = hdr->confirm_rx; + resume_tx_node_id = hdr->dst_node_id; + resume_tx_port_id = hdr->dst_port_id; + + port_ptr = msm_ipc_router_lookup_local_port(hdr->dst_port_id); + if (!port_ptr) { + pr_err("%s: No local port id %08x\n", __func__, + hdr->dst_port_id); + release_pkt(pkt); + goto process_done; + } + + if (!port_ptr->notify) { + mutex_lock(&port_ptr->port_rx_q_lock); + wake_lock(&port_ptr->port_rx_wake_lock); + list_add_tail(&pkt->list, &port_ptr->port_rx_q); + wake_up(&port_ptr->port_rx_wait_q); + mutex_unlock(&port_ptr->port_rx_q_lock); + } else { + src_addr = kmalloc(sizeof(struct msm_ipc_port_addr), + GFP_KERNEL); + if (src_addr) { + src_addr->node_id = hdr->src_node_id; + src_addr->port_id = hdr->src_port_id; + } + skb_pull(head_skb, IPC_ROUTER_HDR_SIZE); + port_ptr->notify(MSM_IPC_ROUTER_READ_CB, pkt->pkt_fragment_q, + src_addr, port_ptr->priv); + pkt->pkt_fragment_q = NULL; + src_addr = NULL; + release_pkt(pkt); + } + +process_done: + if (resume_tx) { + union rr_control_msg msg; + + msg.cmd = IPC_ROUTER_CTRL_CMD_RESUME_TX; + msg.cli.node_id = resume_tx_node_id; + msg.cli.port_id = resume_tx_port_id; + + RR("x RESUME_TX id=%d:%08x\n", + msg.cli.node_id, msg.cli.port_id); + msm_ipc_router_send_control_msg(xprt_info, &msg); + } + +done: + queue_work(xprt_info->workqueue, &xprt_info->read_data); + return; + +fail_data: + release_pkt(pkt); +fail_io: + pr_err("ipc_router has died\n"); +} + +int msm_ipc_router_register_server(struct msm_ipc_port *port_ptr, + struct msm_ipc_addr *name) +{ + struct msm_ipc_server *server; + unsigned long flags; + union rr_control_msg ctl; + + if (!port_ptr || !name) + return -EINVAL; + + if (name->addrtype != MSM_IPC_ADDR_NAME) + return -EINVAL; + + server = msm_ipc_router_lookup_server(name->addr.port_name.service, + name->addr.port_name.instance, + IPC_ROUTER_NID_LOCAL, + port_ptr->this_port.port_id); + if (server) { + pr_err("%s: Server already present\n", __func__); + return -EINVAL; + } + + server = msm_ipc_router_create_server(name->addr.port_name.service, + name->addr.port_name.instance, + IPC_ROUTER_NID_LOCAL, + port_ptr->this_port.port_id); + if (!server) { + pr_err("%s: Server Creation failed\n", __func__); + return -EINVAL; + } + + ctl.cmd = IPC_ROUTER_CTRL_CMD_NEW_SERVER; + ctl.srv.service = server->name.service; + ctl.srv.instance = server->name.instance; + ctl.srv.node_id = IPC_ROUTER_NID_LOCAL; + ctl.srv.port_id = port_ptr->this_port.port_id; + broadcast_ctl_msg(&ctl); + spin_lock_irqsave(&port_ptr->port_lock, flags); + port_ptr->type = SERVER_PORT; + port_ptr->port_name.service = server->name.service; + port_ptr->port_name.instance = server->name.instance; + spin_unlock_irqrestore(&port_ptr->port_lock, flags); + return 0; +} + +int msm_ipc_router_unregister_server(struct msm_ipc_port *port_ptr) +{ + struct msm_ipc_server *server; + unsigned long flags; + union rr_control_msg ctl; + + if (!port_ptr) + return -EINVAL; + + if (port_ptr->type != SERVER_PORT) { + pr_err("%s: Trying to unregister a non-server port\n", + __func__); + return -EINVAL; + } + + if (port_ptr->this_port.node_id != IPC_ROUTER_NID_LOCAL) { + pr_err("%s: Trying to unregister a remote server locally\n", + __func__); + return -EINVAL; + } + + server = msm_ipc_router_lookup_server(port_ptr->port_name.service, + port_ptr->port_name.instance, + port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + if (!server) { + pr_err("%s: Server lookup failed\n", __func__); + return -ENODEV; + } + + ctl.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_SERVER; + ctl.srv.service = server->name.service; + ctl.srv.instance = server->name.instance; + ctl.srv.node_id = IPC_ROUTER_NID_LOCAL; + ctl.srv.port_id = port_ptr->this_port.port_id; + broadcast_ctl_msg(&ctl); + msm_ipc_router_destroy_server(server, port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + spin_lock_irqsave(&port_ptr->port_lock, flags); + port_ptr->type = CLIENT_PORT; + spin_unlock_irqrestore(&port_ptr->port_lock, flags); + return 0; +} + +static int loopback_data(struct msm_ipc_port *src, + uint32_t port_id, + struct sk_buff_head *data) +{ + struct sk_buff *head_skb; + struct rr_header *hdr; + struct msm_ipc_port *port_ptr; + struct rr_packet *pkt; + + if (!data) { + pr_err("%s: Invalid pkt pointer\n", __func__); + return -EINVAL; + } + + pkt = create_pkt(data); + if (!pkt) { + pr_err("%s: New pkt create failed\n", __func__); + return -ENOMEM; + } + + head_skb = skb_peek(pkt->pkt_fragment_q); + hdr = (struct rr_header *)skb_push(head_skb, IPC_ROUTER_HDR_SIZE); + if (!hdr) { + pr_err("%s: Prepend Header failed\n", __func__); + release_pkt(pkt); + return -ENOMEM; + } + hdr->version = IPC_ROUTER_VERSION; + hdr->type = IPC_ROUTER_CTRL_CMD_DATA; + hdr->src_node_id = src->this_port.node_id; + hdr->src_port_id = src->this_port.port_id; + hdr->size = pkt->length; + hdr->confirm_rx = 0; + hdr->dst_node_id = IPC_ROUTER_NID_LOCAL; + hdr->dst_port_id = port_id; + pkt->length += IPC_ROUTER_HDR_SIZE; + + port_ptr = msm_ipc_router_lookup_local_port(port_id); + if (!port_ptr) { + pr_err("%s: Local port %d not present\n", __func__, port_id); + release_pkt(pkt); + return -ENODEV; + } + + mutex_lock(&port_ptr->port_rx_q_lock); + wake_lock(&port_ptr->port_rx_wake_lock); + list_add_tail(&pkt->list, &port_ptr->port_rx_q); + wake_up(&port_ptr->port_rx_wait_q); + mutex_unlock(&port_ptr->port_rx_q_lock); + + return pkt->length; +} + +static int msm_ipc_router_write_pkt(struct msm_ipc_port *src, + struct msm_ipc_router_remote_port *rport_ptr, + struct rr_packet *pkt) +{ + struct sk_buff *head_skb; + struct rr_header *hdr; + struct msm_ipc_router_xprt_info *xprt_info; + struct msm_ipc_routing_table_entry *rt_entry; + int ret; + DEFINE_WAIT(__wait); + + if (!rport_ptr || !src || !pkt) + return -EINVAL; + + head_skb = skb_peek(pkt->pkt_fragment_q); + hdr = (struct rr_header *)skb_push(head_skb, IPC_ROUTER_HDR_SIZE); + if (!hdr) { + pr_err("%s: Prepend Header failed\n", __func__); + return -ENOMEM; + } + hdr->version = IPC_ROUTER_VERSION; + hdr->type = IPC_ROUTER_CTRL_CMD_DATA; + hdr->src_node_id = src->this_port.node_id; + hdr->src_port_id = src->this_port.port_id; + hdr->size = pkt->length; + hdr->confirm_rx = 0; + hdr->dst_node_id = rport_ptr->node_id; + hdr->dst_port_id = rport_ptr->port_id; + pkt->length += IPC_ROUTER_HDR_SIZE; + + for (;;) { + prepare_to_wait(&rport_ptr->quota_wait, &__wait, + TASK_INTERRUPTIBLE); + mutex_lock(&rport_ptr->quota_lock); + if (rport_ptr->tx_quota_cnt < + IPC_ROUTER_DEFAULT_RX_QUOTA) + break; + if (signal_pending(current)) + break; + mutex_unlock(&rport_ptr->quota_lock); + schedule(); + } + finish_wait(&rport_ptr->quota_wait, &__wait); + + if (signal_pending(current)) { + mutex_unlock(&rport_ptr->quota_lock); + return -ERESTARTSYS; + } + rport_ptr->tx_quota_cnt++; + if (rport_ptr->tx_quota_cnt == IPC_ROUTER_DEFAULT_RX_QUOTA) + hdr->confirm_rx = 1; + mutex_unlock(&rport_ptr->quota_lock); + + mutex_lock(&routing_table_lock); + rt_entry = lookup_routing_table(hdr->dst_node_id); + if (!rt_entry || !rt_entry->xprt_info) { + mutex_unlock(&routing_table_lock); + pr_err("%s: Remote node %d not up\n", + __func__, hdr->dst_node_id); + return -ENODEV; + } + mutex_lock(&rt_entry->lock); + xprt_info = rt_entry->xprt_info; + mutex_lock(&xprt_info->tx_lock); + ret = xprt_info->xprt->write(pkt, pkt->length, 0); + mutex_unlock(&xprt_info->tx_lock); + mutex_unlock(&rt_entry->lock); + mutex_unlock(&routing_table_lock); + + if (ret < 0) { + pr_err("%s: Write on XPRT failed\n", __func__); + return ret; + } + + RAW_HDR("[w rr_h] " + "ver=%i,type=%s,src_nid=%08x,src_port_id=%08x," + "confirm_rx=%i,size=%3i,dst_pid=%08x,dst_cid=%08x\n", + hdr->version, type_to_str(hdr->type), + hdr->src_node_id, hdr->src_port_id, + hdr->confirm_rx, hdr->size, + hdr->dst_node_id, hdr->dst_port_id); + +#if defined(CONFIG_MSM_SMD_LOGGING) +#if defined(DEBUG) + if (msm_ipc_router_debug_mask & SMEM_LOG) { + smem_log_event((SMEM_LOG_PROC_ID_APPS | + SMEM_LOG_RPC_ROUTER_EVENT_BASE | + IPC_ROUTER_LOG_EVENT_TX), + (hdr->src_node_id << 24) | + (hdr->src_port_id & 0xffffff), + (hdr->dst_node_id << 24) | + (hdr->dst_port_id & 0xffffff), + (hdr->type << 24) | (hdr->confirm_rx << 16) | + (hdr->size & 0xffff)); + } +#endif +#endif + + return pkt->length; +} + +int msm_ipc_router_send_to(struct msm_ipc_port *src, + struct sk_buff_head *data, + struct msm_ipc_addr *dest) +{ + uint32_t dst_node_id = 0, dst_port_id = 0; + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + struct msm_ipc_router_remote_port *rport_ptr = NULL; + struct rr_packet *pkt; + int ret; + + if (!src || !data || !dest) { + pr_err("%s: Invalid Parameters\n", __func__); + return -EINVAL; + } + + /* Resolve Address*/ + if (dest->addrtype == MSM_IPC_ADDR_ID) { + dst_node_id = dest->addr.port_addr.node_id; + dst_port_id = dest->addr.port_addr.port_id; + } else if (dest->addrtype == MSM_IPC_ADDR_NAME) { + server = msm_ipc_router_lookup_server( + dest->addr.port_name.service, + dest->addr.port_name.instance, + 0, 0); + if (!server) { + pr_err("%s: Destination not reachable\n", __func__); + return -ENODEV; + } + mutex_lock(&server_list_lock); + server_port = list_first_entry(&server->server_port_list, + struct msm_ipc_server_port, + list); + dst_node_id = server_port->server_addr.node_id; + dst_port_id = server_port->server_addr.port_id; + mutex_unlock(&server_list_lock); + } + if (dst_node_id == IPC_ROUTER_NID_LOCAL) { + ret = loopback_data(src, dst_port_id, data); + return ret; + } + + /* Achieve Flow control */ + rport_ptr = msm_ipc_router_lookup_remote_port(dst_node_id, + dst_port_id); + if (!rport_ptr) { + rport_ptr = msm_ipc_router_create_remote_port(dst_node_id, + dst_port_id); + if (!rport_ptr) { + pr_err("%s: Could not create remote port\n", __func__); + return -ENOMEM; + } + } + + pkt = create_pkt(data); + if (!pkt) { + pr_err("%s: Pkt creation failed\n", __func__); + return -ENOMEM; + } + + ret = msm_ipc_router_write_pkt(src, rport_ptr, pkt); + release_pkt(pkt); + + return ret; +} + +int msm_ipc_router_read(struct msm_ipc_port *port_ptr, + struct sk_buff_head **data, + size_t buf_len) +{ + struct rr_packet *pkt; + int ret; + + if (!port_ptr || !data) + return -EINVAL; + + mutex_lock(&port_ptr->port_rx_q_lock); + if (list_empty(&port_ptr->port_rx_q)) { + mutex_unlock(&port_ptr->port_rx_q_lock); + return -EAGAIN; + } + + pkt = list_first_entry(&port_ptr->port_rx_q, struct rr_packet, list); + if ((buf_len) && ((pkt->length - IPC_ROUTER_HDR_SIZE) > buf_len)) { + mutex_unlock(&port_ptr->port_rx_q_lock); + return -ETOOSMALL; + } + list_del(&pkt->list); + if (list_empty(&port_ptr->port_rx_q)) + wake_unlock(&port_ptr->port_rx_wake_lock); + *data = pkt->pkt_fragment_q; + ret = pkt->length; + kfree(pkt); + mutex_unlock(&port_ptr->port_rx_q_lock); + + return ret; +} + +int msm_ipc_router_recv_from(struct msm_ipc_port *port_ptr, + struct sk_buff_head **data, + struct msm_ipc_addr *src, + unsigned long timeout) +{ + int ret, data_len, align_size; + struct sk_buff *temp_skb; + struct rr_header *hdr = NULL; + + if (!port_ptr || !data) { + pr_err("%s: Invalid pointers being passed\n", __func__); + return -EINVAL; + } + + *data = NULL; + mutex_lock(&port_ptr->port_rx_q_lock); + while (list_empty(&port_ptr->port_rx_q)) { + mutex_unlock(&port_ptr->port_rx_q_lock); + if (timeout < 0) { + ret = wait_event_interruptible( + port_ptr->port_rx_wait_q, + !list_empty(&port_ptr->port_rx_q)); + if (ret) + return ret; + } else if (timeout > 0) { + timeout = wait_event_interruptible_timeout( + port_ptr->port_rx_wait_q, + !list_empty(&port_ptr->port_rx_q), + timeout); + if (timeout < 0) + return -EFAULT; + } + if (timeout == 0) + return -ETIMEDOUT; + mutex_lock(&port_ptr->port_rx_q_lock); + } + mutex_unlock(&port_ptr->port_rx_q_lock); + + ret = msm_ipc_router_read(port_ptr, data, 0); + if (ret <= 0 || !(*data)) + return ret; + + temp_skb = skb_peek(*data); + hdr = (struct rr_header *)(temp_skb->data); + if (src) { + src->addrtype = MSM_IPC_ADDR_ID; + src->addr.port_addr.node_id = hdr->src_node_id; + src->addr.port_addr.port_id = hdr->src_port_id; + } + + data_len = hdr->size; + skb_pull(temp_skb, IPC_ROUTER_HDR_SIZE); + align_size = ALIGN_SIZE(data_len); + if (align_size) { + temp_skb = skb_peek_tail(*data); + skb_trim(temp_skb, (temp_skb->len - align_size)); + } + return data_len; +} + +struct msm_ipc_port *msm_ipc_router_create_port( + void (*notify)(unsigned event, void *data, void *addr, void *priv), + void *priv) +{ + struct msm_ipc_port *port_ptr; + + port_ptr = msm_ipc_router_create_raw_port(NULL, notify, priv); + if (!port_ptr) + pr_err("%s: port_ptr alloc failed\n", __func__); + + return port_ptr; +} + +int msm_ipc_router_close_port(struct msm_ipc_port *port_ptr) +{ + union rr_control_msg msg; + struct rr_packet *pkt, *temp_pkt; + struct msm_ipc_server *server; + + if (!port_ptr) + return -EINVAL; + + if (port_ptr->type == SERVER_PORT || port_ptr->type == CLIENT_PORT) { + if (port_ptr->type == SERVER_PORT) { + msg.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_SERVER; + msg.srv.service = port_ptr->port_name.service; + msg.srv.instance = port_ptr->port_name.instance; + msg.srv.node_id = port_ptr->this_port.node_id; + msg.srv.port_id = port_ptr->this_port.port_id; + RR("x REMOVE_SERVER Name=%d:%08x Id=%d:%08x\n", + msg.srv.service, msg.srv.instance, + msg.srv.node_id, msg.srv.port_id); + } else if (port_ptr->type == CLIENT_PORT) { + msg.cmd = IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT; + msg.cli.node_id = port_ptr->this_port.node_id; + msg.cli.port_id = port_ptr->this_port.port_id; + RR("x REMOVE_CLIENT id=%d:%08x\n", + msg.cli.node_id, msg.cli.port_id); + } + broadcast_ctl_msg(&msg); + broadcast_ctl_msg_locally(&msg); + } + + mutex_lock(&port_ptr->port_rx_q_lock); + list_for_each_entry_safe(pkt, temp_pkt, &port_ptr->port_rx_q, list) { + list_del(&pkt->list); + release_pkt(pkt); + } + mutex_unlock(&port_ptr->port_rx_q_lock); + + wake_lock_destroy(&port_ptr->port_rx_wake_lock); + if (port_ptr->type == SERVER_PORT) { + server = msm_ipc_router_lookup_server( + port_ptr->port_name.service, + port_ptr->port_name.instance, + port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + if (server) + msm_ipc_router_destroy_server(server, + port_ptr->this_port.node_id, + port_ptr->this_port.port_id); + mutex_lock(&local_ports_lock); + list_del(&port_ptr->list); + mutex_unlock(&local_ports_lock); + } else if (port_ptr->type == CLIENT_PORT) { + mutex_lock(&local_ports_lock); + list_del(&port_ptr->list); + mutex_unlock(&local_ports_lock); + } else if (port_ptr->type == CONTROL_PORT) { + mutex_lock(&control_ports_lock); + list_del(&port_ptr->list); + mutex_unlock(&control_ports_lock); + } + + kfree(port_ptr); + return 0; +} + +int msm_ipc_router_get_curr_pkt_size(struct msm_ipc_port *port_ptr) +{ + struct rr_packet *pkt; + int rc = 0; + + if (!port_ptr) + return -EINVAL; + + mutex_lock(&port_ptr->port_rx_q_lock); + if (!list_empty(&port_ptr->port_rx_q)) { + pkt = list_first_entry(&port_ptr->port_rx_q, + struct rr_packet, list); + rc = pkt->length; + } + mutex_unlock(&port_ptr->port_rx_q_lock); + + return rc; +} + +int msm_ipc_router_bind_control_port(struct msm_ipc_port *port_ptr) +{ + if (!port_ptr) + return -EINVAL; + + mutex_lock(&local_ports_lock); + list_del(&port_ptr->list); + mutex_unlock(&local_ports_lock); + port_ptr->type = CONTROL_PORT; + mutex_lock(&control_ports_lock); + list_add_tail(&port_ptr->list, &control_ports); + mutex_unlock(&control_ports_lock); + + return 0; +} + +int msm_ipc_router_lookup_server_name(struct msm_ipc_port_name *srv_name, + struct msm_ipc_port_addr *srv_addr, + int num_entries_in_array) +{ + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + int i = 0; /*num_entries_found*/ + + if (!srv_name) { + pr_err("%s: Invalid srv_name\n", __func__); + return -EINVAL; + } + + if (num_entries_in_array && !srv_addr) { + pr_err("%s: srv_addr NULL\n", __func__); + return -EINVAL; + } + + server = msm_ipc_router_lookup_server(srv_name->service, + srv_name->instance, 0, 0); + if (!server) + return -ENODEV; + + mutex_lock(&server_list_lock); + list_for_each_entry(server_port, &server->server_port_list, list) { + if (i < num_entries_in_array) { + srv_addr[i].node_id = server_port->server_addr.node_id; + srv_addr[i].port_id = server_port->server_addr.port_id; + } + i++; + } + mutex_unlock(&server_list_lock); + + return i; +} + +int msm_ipc_router_close(void) +{ + struct msm_ipc_router_xprt_info *xprt_info, *tmp_xprt_info; + + mutex_lock(&xprt_info_list_lock); + list_for_each_entry_safe(xprt_info, tmp_xprt_info, + &xprt_info_list, list) { + xprt_info->xprt->close(); + list_del(&xprt_info->list); + kfree(xprt_info); + } + mutex_unlock(&xprt_info_list_lock); + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +static int dump_routing_table(char *buf, int max) +{ + int i = 0, j; + struct msm_ipc_routing_table_entry *rt_entry; + + for (j = 0; j < RT_HASH_SIZE; j++) { + mutex_lock(&routing_table_lock); + list_for_each_entry(rt_entry, &routing_table[j], list) { + mutex_lock(&rt_entry->lock); + i += scnprintf(buf + i, max - i, + "Node Id: 0x%08x\n", rt_entry->node_id); + if (j == IPC_ROUTER_NID_LOCAL) { + i += scnprintf(buf + i, max - i, + "XPRT Name: Loopback\n"); + i += scnprintf(buf + i, max - i, + "Next Hop: %d\n", rt_entry->node_id); + } else { + i += scnprintf(buf + i, max - i, + "XPRT Name: %s\n", + rt_entry->xprt_info->xprt->name); + i += scnprintf(buf + i, max - i, + "Next Hop: 0x%08x\n", + rt_entry->xprt_info->remote_node_id); + } + i += scnprintf(buf + i, max - i, "\n"); + mutex_unlock(&rt_entry->lock); + } + mutex_unlock(&routing_table_lock); + } + + return i; +} + +static int dump_xprt_info(char *buf, int max) +{ + int i = 0; + struct msm_ipc_router_xprt_info *xprt_info; + + mutex_lock(&xprt_info_list_lock); + list_for_each_entry(xprt_info, &xprt_info_list, list) { + i += scnprintf(buf + i, max - i, "XPRT Name: %s\n", + xprt_info->xprt->name); + i += scnprintf(buf + i, max - i, "Link Id: %d\n", + xprt_info->xprt->link_id); + i += scnprintf(buf + i, max - i, "Initialized: %s\n", + (xprt_info->initialized ? "Y" : "N")); + i += scnprintf(buf + i, max - i, "Remote Node Id: 0x%08x\n", + xprt_info->remote_node_id); + i += scnprintf(buf + i, max - i, "\n"); + } + mutex_unlock(&xprt_info_list_lock); + + return i; +} + +static int dump_servers(char *buf, int max) +{ + int i = 0, j; + struct msm_ipc_server *server; + struct msm_ipc_server_port *server_port; + + mutex_lock(&server_list_lock); + for (j = 0; j < SRV_HASH_SIZE; j++) { + list_for_each_entry(server, &server_list[j], list) { + list_for_each_entry(server_port, + &server->server_port_list, + list) { + i += scnprintf(buf + i, max - i, "Service: " + "0x%08x\n", server->name.service); + i += scnprintf(buf + i, max - i, "Instance: " + "0x%08x\n", server->name.instance); + i += scnprintf(buf + i, max - i, + "Node_id: 0x%08x\n", + server_port->server_addr.node_id); + i += scnprintf(buf + i, max - i, + "Port_id: 0x%08x\n", + server_port->server_addr.port_id); + i += scnprintf(buf + i, max - i, "\n"); + } + } + } + mutex_unlock(&server_list_lock); + + return i; +} + +static int dump_remote_ports(char *buf, int max) +{ + int i = 0, j, k; + struct msm_ipc_router_remote_port *rport_ptr; + struct msm_ipc_routing_table_entry *rt_entry; + + for (j = 0; j < RT_HASH_SIZE; j++) { + mutex_lock(&routing_table_lock); + list_for_each_entry(rt_entry, &routing_table[j], list) { + mutex_lock(&rt_entry->lock); + for (k = 0; k < RP_HASH_SIZE; k++) { + list_for_each_entry(rport_ptr, + &rt_entry->remote_port_list[k], + list) { + i += scnprintf(buf + i, max - i, + "Node_id: 0x%08x\n", + rport_ptr->node_id); + i += scnprintf(buf + i, max - i, + "Port_id: 0x%08x\n", + rport_ptr->port_id); + i += scnprintf(buf + i, max - i, + "Quota_cnt: %d\n", + rport_ptr->tx_quota_cnt); + i += scnprintf(buf + i, max - i, "\n"); + } + } + mutex_unlock(&rt_entry->lock); + } + mutex_unlock(&routing_table_lock); + } + + return i; +} + +static int dump_control_ports(char *buf, int max) +{ + int i = 0; + struct msm_ipc_port *port_ptr; + + mutex_lock(&control_ports_lock); + list_for_each_entry(port_ptr, &control_ports, list) { + i += scnprintf(buf + i, max - i, "Node_id: 0x%08x\n", + port_ptr->this_port.node_id); + i += scnprintf(buf + i, max - i, "Port_id: 0x%08x\n", + port_ptr->this_port.port_id); + i += scnprintf(buf + i, max - i, "\n"); + } + mutex_unlock(&control_ports_lock); + + return i; +} + +static int dump_local_ports(char *buf, int max) +{ + int i = 0, j; + unsigned long flags; + struct msm_ipc_port *port_ptr; + + mutex_lock(&local_ports_lock); + for (j = 0; j < LP_HASH_SIZE; j++) { + list_for_each_entry(port_ptr, &local_ports[j], list) { + spin_lock_irqsave(&port_ptr->port_lock, flags); + i += scnprintf(buf + i, max - i, "Node_id: 0x%08x\n", + port_ptr->this_port.node_id); + i += scnprintf(buf + i, max - i, "Port_id: 0x%08x\n", + port_ptr->this_port.port_id); + i += scnprintf(buf + i, max - i, "# pkts tx'd %d\n", + port_ptr->num_tx); + i += scnprintf(buf + i, max - i, "# pkts rx'd %d\n", + port_ptr->num_rx); + i += scnprintf(buf + i, max - i, "# bytes tx'd %ld\n", + port_ptr->num_tx_bytes); + i += scnprintf(buf + i, max - i, "# bytes rx'd %ld\n", + port_ptr->num_rx_bytes); + spin_unlock_irqrestore(&port_ptr->port_lock, flags); + i += scnprintf(buf + i, max - i, "\n"); + } + } + mutex_unlock(&local_ports_lock); + + return i; +} + +#define DEBUG_BUFMAX 4096 +static char debug_buffer[DEBUG_BUFMAX]; + +static ssize_t debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int (*fill)(char *buf, int max) = file->private_data; + int bsize = fill(debug_buffer, DEBUG_BUFMAX); + return simple_read_from_buffer(buf, count, ppos, debug_buffer, bsize); +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations debug_ops = { + .read = debug_read, + .open = debug_open, +}; + +static void debug_create(const char *name, mode_t mode, + struct dentry *dent, + int (*fill)(char *buf, int max)) +{ + debugfs_create_file(name, mode, dent, fill, &debug_ops); +} + +static void debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("msm_ipc_router", 0); + if (IS_ERR(dent)) + return; + + debug_create("dump_local_ports", 0444, dent, + dump_local_ports); + debug_create("dump_remote_ports", 0444, dent, + dump_remote_ports); + debug_create("dump_control_ports", 0444, dent, + dump_control_ports); + debug_create("dump_servers", 0444, dent, + dump_servers); + debug_create("dump_xprt_info", 0444, dent, + dump_xprt_info); + debug_create("dump_routing_table", 0444, dent, + dump_routing_table); +} + +#else +static void debugfs_init(void) {} +#endif + +static int msm_ipc_router_add_xprt(struct msm_ipc_router_xprt *xprt) +{ + struct msm_ipc_router_xprt_info *xprt_info; + struct msm_ipc_routing_table_entry *rt_entry; + + xprt_info = kmalloc(sizeof(struct msm_ipc_router_xprt_info), + GFP_KERNEL); + if (!xprt_info) + return -ENOMEM; + + xprt_info->xprt = xprt; + xprt_info->initialized = 0; + xprt_info->remote_node_id = -1; + INIT_LIST_HEAD(&xprt_info->pkt_list); + init_waitqueue_head(&xprt_info->read_wait); + mutex_init(&xprt_info->rx_lock); + mutex_init(&xprt_info->tx_lock); + wake_lock_init(&xprt_info->wakelock, + WAKE_LOCK_SUSPEND, xprt->name); + xprt_info->need_len = 0; + INIT_WORK(&xprt_info->read_data, do_read_data); + INIT_LIST_HEAD(&xprt_info->list); + + xprt_info->workqueue = create_singlethread_workqueue(xprt->name); + if (!xprt_info->workqueue) { + kfree(xprt_info); + return -ENOMEM; + } + + if (!strcmp(xprt->name, "msm_ipc_router_loopback_xprt")) { + xprt_info->remote_node_id = IPC_ROUTER_NID_LOCAL; + xprt_info->initialized = 1; + } + + mutex_lock(&xprt_info_list_lock); + list_add_tail(&xprt_info->list, &xprt_info_list); + mutex_unlock(&xprt_info_list_lock); + + mutex_lock(&routing_table_lock); + if (!routing_table_inited) { + init_routing_table(); + rt_entry = alloc_routing_table_entry(IPC_ROUTER_NID_LOCAL); + add_routing_table_entry(rt_entry); + routing_table_inited = 1; + } + mutex_unlock(&routing_table_lock); + + queue_work(xprt_info->workqueue, &xprt_info->read_data); + + xprt->priv = xprt_info; + + return 0; +} + + +void msm_ipc_router_xprt_notify(struct msm_ipc_router_xprt *xprt, + unsigned event, + void *data) +{ + struct msm_ipc_router_xprt_info *xprt_info = xprt->priv; + struct rr_packet *pkt; + + if (event == IPC_ROUTER_XPRT_EVENT_OPEN) { + msm_ipc_router_add_xprt(xprt); + return; + } + + if (!data) + return; + + while (!xprt_info) { + msleep(100); + xprt_info = xprt->priv; + } + + pkt = clone_pkt((struct rr_packet *)data); + if (!pkt) + return; + + mutex_lock(&xprt_info->rx_lock); + list_add_tail(&pkt->list, &xprt_info->pkt_list); + wake_lock(&xprt_info->wakelock); + wake_up(&xprt_info->read_wait); + mutex_unlock(&xprt_info->rx_lock); +} + +static int __init msm_ipc_router_init(void) +{ + int i, ret; + struct msm_ipc_routing_table_entry *rt_entry; + + msm_ipc_router_debug_mask |= SMEM_LOG; + debugfs_init(); + + for (i = 0; i < SRV_HASH_SIZE; i++) + INIT_LIST_HEAD(&server_list[i]); + + for (i = 0; i < LP_HASH_SIZE; i++) + INIT_LIST_HEAD(&local_ports[i]); + + mutex_lock(&routing_table_lock); + if (!routing_table_inited) { + init_routing_table(); + rt_entry = alloc_routing_table_entry(IPC_ROUTER_NID_LOCAL); + add_routing_table_entry(rt_entry); + routing_table_inited = 1; + } + mutex_unlock(&routing_table_lock); + + init_waitqueue_head(&newserver_wait); + ret = msm_ipc_router_init_sockets(); + if (ret < 0) + pr_err("%s: Init sockets failed\n", __func__); + + return ret; +} + +module_init(msm_ipc_router_init); +MODULE_DESCRIPTION("MSM IPC Router"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/ipc_router.h b/arch/arm/mach-msm/ipc_router.h new file mode 100644 index 00000000000..d058e9b677d --- /dev/null +++ b/arch/arm/mach-msm/ipc_router.h @@ -0,0 +1,204 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _ARCH_ARM_MACH_MSM_IPC_ROUTER_H +#define _ARCH_ARM_MACH_MSM_IPC_ROUTER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* definitions for the R2R wire protcol */ +#define IPC_ROUTER_VERSION 1 +#define IPC_ROUTER_PROCESSORS_MAX 4 + +#define IPC_ROUTER_CLIENT_BCAST_ID 0xffffffff +#define IPC_ROUTER_ADDRESS 0xfffffffe + +#define IPC_ROUTER_NID_LOCAL 1 +#define IPC_ROUTER_NID_REMOTE 0 + +#define IPC_ROUTER_CTRL_CMD_DATA 1 +#define IPC_ROUTER_CTRL_CMD_HELLO 2 +#define IPC_ROUTER_CTRL_CMD_BYE 3 +#define IPC_ROUTER_CTRL_CMD_NEW_SERVER 4 +#define IPC_ROUTER_CTRL_CMD_REMOVE_SERVER 5 +#define IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT 6 +#define IPC_ROUTER_CTRL_CMD_RESUME_TX 7 +#define IPC_ROUTER_CTRL_CMD_EXIT 8 +#define IPC_ROUTER_CTRL_CMD_PING 9 + +#define IPC_ROUTER_DEFAULT_RX_QUOTA 5 + +#define IPC_ROUTER_XPRT_EVENT_DATA 1 +#define IPC_ROUTER_XPRT_EVENT_OPEN 2 +#define IPC_ROUTER_XPRT_EVENT_CLOSE 3 + +#define NUM_NODES 2 + +#define IPC_ROUTER_INFINITY -1 +#define DEFAULT_RCV_TIMEO IPC_ROUTER_INFINITY + +#define ALIGN_SIZE(x) ((4 - ((x) & 3)) & 3) + +enum { + MSM_IPC_ROUTER_READ_CB = 0, + MSM_IPC_ROUTER_WRITE_DONE, +}; + +union rr_control_msg { + uint32_t cmd; + struct { + uint32_t cmd; + uint32_t service; + uint32_t instance; + uint32_t node_id; + uint32_t port_id; + } srv; + struct { + uint32_t cmd; + uint32_t node_id; + uint32_t port_id; + } cli; +}; + +struct rr_header { + uint32_t version; + uint32_t type; + uint32_t src_node_id; + uint32_t src_port_id; + uint32_t confirm_rx; + uint32_t size; + uint32_t dst_node_id; + uint32_t dst_port_id; +}; + +#define IPC_ROUTER_HDR_SIZE sizeof(struct rr_header) +#define MAX_IPC_PKT_SIZE 66000 +/* internals */ + +#define IPC_ROUTER_MAX_REMOTE_SERVERS 100 + +struct rr_packet { + struct list_head list; + struct sk_buff_head *pkt_fragment_q; + uint32_t length; +}; + +struct msm_ipc_port { + struct list_head list; + + struct msm_ipc_port_addr this_port; + struct msm_ipc_port_name port_name; + uint32_t type; + unsigned flags; + spinlock_t port_lock; + + struct list_head incomplete; + struct mutex incomplete_lock; + + struct list_head port_rx_q; + struct mutex port_rx_q_lock; + struct wake_lock port_rx_wake_lock; + wait_queue_head_t port_rx_wait_q; + + int restart_state; + spinlock_t restart_lock; + wait_queue_head_t restart_wait; + + void *endpoint; + void (*notify)(unsigned event, void *data, void *addr, void *priv); + + uint32_t num_tx; + uint32_t num_rx; + unsigned long num_tx_bytes; + unsigned long num_rx_bytes; + void *priv; +}; + +struct msm_ipc_sock { + struct sock sk; + struct msm_ipc_port *port; + void *modem_pil; +}; + +enum write_data_type { + HEADER = 1, + PACKMARK, + PAYLOAD, +}; + +struct msm_ipc_router_xprt { + char *name; + uint32_t link_id; + void *priv; + + int (*read_avail)(void); + int (*read)(void *data, uint32_t len); + int (*write_avail)(void); + int (*write)(void *data, uint32_t len, enum write_data_type type); + int (*close)(void); +}; + +extern struct completion msm_ipc_remote_router_up; + +void msm_ipc_router_xprt_notify(struct msm_ipc_router_xprt *xprt, + unsigned event, + void *data); + + +struct rr_packet *clone_pkt(struct rr_packet *pkt); +void release_pkt(struct rr_packet *pkt); + + +struct msm_ipc_port *msm_ipc_router_create_raw_port(void *endpoint, + void (*notify)(unsigned event, void *data, + void *addr, void *priv), + void *priv); +int msm_ipc_router_send_to(struct msm_ipc_port *src, + struct sk_buff_head *data, + struct msm_ipc_addr *dest); +int msm_ipc_router_read(struct msm_ipc_port *port_ptr, + struct sk_buff_head **data, + size_t buf_len); +int msm_ipc_router_get_curr_pkt_size(struct msm_ipc_port *port_ptr); +int msm_ipc_router_bind_control_port(struct msm_ipc_port *port_ptr); +int msm_ipc_router_lookup_server_name(struct msm_ipc_port_name *srv_name, + struct msm_ipc_port_addr *port_addr, + int num_entries_in_array); +int msm_ipc_router_close_port(struct msm_ipc_port *port_ptr); + +struct msm_ipc_port *msm_ipc_router_create_port( + void (*notify)(unsigned event, void *data, + void *addr, void *priv), + void *priv); +int msm_ipc_router_recv_from(struct msm_ipc_port *port_ptr, + struct sk_buff_head **data, + struct msm_ipc_addr *src_addr, + unsigned long timeout); +int msm_ipc_router_register_server(struct msm_ipc_port *server_port, + struct msm_ipc_addr *name); +int msm_ipc_router_unregister_server(struct msm_ipc_port *server_port); + + +int msm_ipc_router_init_sockets(void); +void msm_ipc_router_exit_sockets(void); + +#endif diff --git a/arch/arm/mach-msm/ipc_router_smd_xprt.c b/arch/arm/mach-msm/ipc_router_smd_xprt.c new file mode 100644 index 00000000000..1e2b576c1df --- /dev/null +++ b/arch/arm/mach-msm/ipc_router_smd_xprt.c @@ -0,0 +1,272 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * IPC ROUTER SMD XPRT module. + */ +#define DEBUG + +#include +#include + +#include + +#include "ipc_router.h" +#include "smd_private.h" + +static int msm_ipc_router_smd_xprt_debug_mask; +module_param_named(debug_mask, msm_ipc_router_smd_xprt_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#if defined(DEBUG) +#define D(x...) do { \ +if (msm_ipc_router_smd_xprt_debug_mask) \ + pr_info(x); \ +} while (0) +#else +#define D(x...) do { } while (0) +#endif + +#define MIN_FRAG_SZ (IPC_ROUTER_HDR_SIZE + sizeof(union rr_control_msg)) + +struct msm_ipc_router_smd_xprt { + struct msm_ipc_router_xprt xprt; + + smd_channel_t *channel; +}; + +static struct msm_ipc_router_smd_xprt smd_remote_xprt; + +static void smd_xprt_read_data(struct work_struct *work); +static DECLARE_DELAYED_WORK(work_read_data, smd_xprt_read_data); +static struct workqueue_struct *smd_xprt_workqueue; + +static wait_queue_head_t write_avail_wait_q; +static struct rr_packet *in_pkt; +static int is_partial_in_pkt; + +static int msm_ipc_router_smd_remote_write_avail(void) +{ + return smd_write_avail(smd_remote_xprt.channel); +} + +static int msm_ipc_router_smd_remote_write(void *data, + uint32_t len, + uint32_t type) +{ + struct rr_packet *pkt = (struct rr_packet *)data; + struct sk_buff *ipc_rtr_pkt; + int align_sz, align_data = 0; + int offset, sz_written = 0; + + if (!pkt) + return -EINVAL; + + if (!len || pkt->length != len) + return -EINVAL; + + align_sz = ALIGN_SIZE(pkt->length); + while (smd_write_start(smd_remote_xprt.channel, (len + align_sz)) < 0) + msleep(50); + + D("%s: Ready to write\n", __func__); + skb_queue_walk(pkt->pkt_fragment_q, ipc_rtr_pkt) { + offset = 0; + while (offset < ipc_rtr_pkt->len) { + if (!smd_write_avail(smd_remote_xprt.channel)) + smd_enable_read_intr(smd_remote_xprt.channel); + + wait_event_interruptible_timeout(write_avail_wait_q, + smd_write_avail(smd_remote_xprt.channel), + msecs_to_jiffies(50)); + smd_disable_read_intr(smd_remote_xprt.channel); + + sz_written = smd_write_segment(smd_remote_xprt.channel, + ipc_rtr_pkt->data + offset, + (ipc_rtr_pkt->len - offset), 0); + offset += sz_written; + sz_written = 0; + } + D("%s: Wrote %d bytes\n", __func__, offset); + } + + if (align_sz) { + if (smd_write_avail(smd_remote_xprt.channel) < align_sz) + smd_enable_read_intr(smd_remote_xprt.channel); + + wait_event_interruptible_timeout(write_avail_wait_q, + (smd_write_avail(smd_remote_xprt.channel) >= + align_sz), msecs_to_jiffies(50)); + smd_disable_read_intr(smd_remote_xprt.channel); + + smd_write_segment(smd_remote_xprt.channel, + &align_data, align_sz, 0); + D("%s: Wrote %d align bytes\n", __func__, align_sz); + } + if (!smd_write_end(smd_remote_xprt.channel)) + D("%s: Finished writing\n", __func__); + return len; +} + +static int msm_ipc_router_smd_remote_close(void) +{ + smsm_change_state(SMSM_APPS_STATE, SMSM_RPCINIT, 0); + return smd_close(smd_remote_xprt.channel); +} + +static void smd_xprt_read_data(struct work_struct *work) +{ + int pkt_size, sz_read, sz; + struct sk_buff *ipc_rtr_pkt; + void *data; + + D("%s pkt_size: %d, read_avail: %d\n", __func__, + smd_cur_packet_size(smd_remote_xprt.channel), + smd_read_avail(smd_remote_xprt.channel)); + while ((pkt_size = smd_cur_packet_size(smd_remote_xprt.channel)) && + smd_read_avail(smd_remote_xprt.channel)) { + if (!is_partial_in_pkt) { + in_pkt = kzalloc(sizeof(struct rr_packet), GFP_KERNEL); + if (!in_pkt) { + pr_err("%s: Couldn't alloc rr_packet\n", + __func__); + return; + } + + in_pkt->pkt_fragment_q = kmalloc( + sizeof(struct sk_buff_head), + GFP_KERNEL); + if (!in_pkt->pkt_fragment_q) { + pr_err("%s: Couldn't alloc pkt_fragment_q\n", + __func__); + kfree(in_pkt); + return; + } + skb_queue_head_init(in_pkt->pkt_fragment_q); + is_partial_in_pkt = 1; + D("%s: Allocated rr_packet\n", __func__); + } + + if ((pkt_size >= MIN_FRAG_SZ) && + (smd_read_avail(smd_remote_xprt.channel) < MIN_FRAG_SZ)) + return; + + sz = smd_read_avail(smd_remote_xprt.channel); + do { + ipc_rtr_pkt = alloc_skb(sz, GFP_KERNEL); + if (!ipc_rtr_pkt) { + if (sz <= (PAGE_SIZE/2)) { + queue_delayed_work(smd_xprt_workqueue, + &work_read_data, + msecs_to_jiffies(100)); + return; + } + sz = sz / 2; + } + } while (!ipc_rtr_pkt); + + D("%s: Allocated the sk_buff of size %d\n", + __func__, sz); + data = skb_put(ipc_rtr_pkt, sz); + sz_read = smd_read(smd_remote_xprt.channel, data, sz); + if (sz_read != sz) { + pr_err("%s: Couldn't read completely\n", __func__); + kfree_skb(ipc_rtr_pkt); + release_pkt(in_pkt); + is_partial_in_pkt = 0; + return; + } + skb_queue_tail(in_pkt->pkt_fragment_q, ipc_rtr_pkt); + in_pkt->length += sz_read; + if (sz_read != pkt_size) + is_partial_in_pkt = 1; + else + is_partial_in_pkt = 0; + + if (!is_partial_in_pkt) { + D("%s: Packet size read %d\n", + __func__, in_pkt->length); + msm_ipc_router_xprt_notify(&smd_remote_xprt.xprt, + IPC_ROUTER_XPRT_EVENT_DATA, + (void *)in_pkt); + release_pkt(in_pkt); + in_pkt = NULL; + } + } +} + +static void msm_ipc_router_smd_remote_notify(void *_dev, unsigned event) +{ + if (event == SMD_EVENT_DATA) { + if (smd_read_avail(smd_remote_xprt.channel)) + queue_delayed_work(smd_xprt_workqueue, + &work_read_data, 0); + if (smd_write_avail(smd_remote_xprt.channel)) + wake_up(&write_avail_wait_q); + } +} + +static int msm_ipc_router_smd_remote_probe(struct platform_device *pdev) +{ + int rc; + + smd_xprt_workqueue = create_singlethread_workqueue("smd_xprt"); + if (!smd_xprt_workqueue) + return -ENOMEM; + + smd_remote_xprt.xprt.name = "msm_ipc_router_smd_xprt"; + smd_remote_xprt.xprt.link_id = 1; + smd_remote_xprt.xprt.read_avail = NULL; + smd_remote_xprt.xprt.read = NULL; + smd_remote_xprt.xprt.write_avail = + msm_ipc_router_smd_remote_write_avail; + smd_remote_xprt.xprt.write = msm_ipc_router_smd_remote_write; + smd_remote_xprt.xprt.close = msm_ipc_router_smd_remote_close; + smd_remote_xprt.xprt.priv = NULL; + + init_waitqueue_head(&write_avail_wait_q); + + rc = smd_open("RPCRPY_CNTL", &smd_remote_xprt.channel, NULL, + msm_ipc_router_smd_remote_notify); + if (rc < 0) { + destroy_workqueue(smd_xprt_workqueue); + return rc; + } + + smd_disable_read_intr(smd_remote_xprt.channel); + + msm_ipc_router_xprt_notify(&smd_remote_xprt.xprt, + IPC_ROUTER_XPRT_EVENT_OPEN, + NULL); + D("%s: Notified IPC Router of OPEN Event\n", __func__); + + smsm_change_state(SMSM_APPS_STATE, 0, SMSM_RPCINIT); + + return 0; +} + +static struct platform_driver msm_ipc_router_smd_remote_driver = { + .probe = msm_ipc_router_smd_remote_probe, + .driver = { + .name = "RPCRPY_CNTL", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_ipc_router_smd_init(void) +{ + return platform_driver_register(&msm_ipc_router_smd_remote_driver); +} + +module_init(msm_ipc_router_smd_init); +MODULE_DESCRIPTION("RPC Router SMD XPRT"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/ipc_socket.c b/arch/arm/mach-msm/ipc_socket.c new file mode 100644 index 00000000000..132f2a90d61 --- /dev/null +++ b/arch/arm/mach-msm/ipc_socket.c @@ -0,0 +1,560 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include "ipc_router.h" + +#define msm_ipc_sk(sk) ((struct msm_ipc_sock *)(sk)) +#define msm_ipc_sk_port(sk) ((struct msm_ipc_port *)(msm_ipc_sk(sk)->port)) +#define MODEM_LOAD_TIMEOUT (10 * HZ) + +static int sockets_enabled; +static struct proto msm_ipc_proto; +static const struct proto_ops msm_ipc_proto_ops; + +static void msm_ipc_router_unload_modem(void *pil) +{ + if (pil) + pil_put(pil); +} + +static void *msm_ipc_router_load_modem(void) +{ + void *pil; + int rc; + + pil = pil_get("modem"); + if (IS_ERR(pil)) { + pr_debug("%s: modem load failed\n", __func__); + pil = NULL; + } else { + rc = wait_for_completion_interruptible_timeout( + &msm_ipc_remote_router_up, + MODEM_LOAD_TIMEOUT); + if (!rc) + rc = -ETIMEDOUT; + if (rc < 0) { + pr_err("%s: wait for remote router failed %d\n", + __func__, rc); + msm_ipc_router_unload_modem(pil); + pil = NULL; + } + } + + return pil; +} + +static struct sk_buff_head *msm_ipc_router_build_msg(unsigned int num_sect, + struct iovec const *msg_sect, + size_t total_len) +{ + struct sk_buff_head *msg_head; + struct sk_buff *msg; + int i, copied, first = 1; + int data_size = 0, request_size, offset; + void *data; + + for (i = 0; i < num_sect; i++) + data_size += msg_sect[i].iov_len; + + if (!data_size) + return NULL; + + msg_head = kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL); + if (!msg_head) { + pr_err("%s: cannot allocate skb_head\n", __func__); + return NULL; + } + skb_queue_head_init(msg_head); + + for (copied = 1, i = 0; copied && (i < num_sect); i++) { + data_size = msg_sect[i].iov_len; + offset = 0; + while (offset != msg_sect[i].iov_len) { + request_size = data_size; + if (first) + request_size += IPC_ROUTER_HDR_SIZE; + + msg = alloc_skb(request_size, GFP_KERNEL); + if (!msg) { + if (request_size <= (PAGE_SIZE/2)) { + pr_err("%s: cannot allocated skb\n", + __func__); + goto msg_build_failure; + } + data_size = data_size / 2; + continue; + } + + if (first) { + skb_reserve(msg, IPC_ROUTER_HDR_SIZE); + first = 0; + } + + data = skb_put(msg, data_size); + copied = !copy_from_user(msg->data, + msg_sect[i].iov_base + offset, + data_size); + if (!copied) { + pr_err("%s: copy_from_user failed\n", + __func__); + kfree_skb(msg); + goto msg_build_failure; + } + skb_queue_tail(msg_head, msg); + offset += data_size; + data_size = msg_sect[i].iov_len - offset; + } + } + return msg_head; + +msg_build_failure: + while (!skb_queue_empty(msg_head)) { + msg = skb_dequeue(msg_head); + kfree_skb(msg); + } + kfree(msg_head); + return NULL; +} + +static int msm_ipc_router_extract_msg(struct msghdr *m, + struct sk_buff_head *msg_head) +{ + struct sockaddr_msm_ipc *addr = (struct sockaddr_msm_ipc *)m->msg_name; + struct rr_header *hdr; + struct sk_buff *temp; + int offset = 0, data_len = 0, copy_len; + + if (!m || !msg_head) { + pr_err("%s: Invalid pointers passed\n", __func__); + return -EINVAL; + } + + temp = skb_peek(msg_head); + hdr = (struct rr_header *)(temp->data); + if (addr || (hdr->src_port_id != IPC_ROUTER_ADDRESS)) { + addr->family = AF_MSM_IPC; + addr->address.addrtype = MSM_IPC_ADDR_ID; + addr->address.addr.port_addr.node_id = hdr->src_node_id; + addr->address.addr.port_addr.port_id = hdr->src_port_id; + m->msg_namelen = sizeof(struct sockaddr_msm_ipc); + } + + data_len = hdr->size; + skb_pull(temp, IPC_ROUTER_HDR_SIZE); + skb_queue_walk(msg_head, temp) { + copy_len = data_len < temp->len ? data_len : temp->len; + if (copy_to_user(m->msg_iov->iov_base + offset, temp->data, + copy_len)) { + pr_err("%s: Copy to user failed\n", __func__); + return -EFAULT; + } + offset += copy_len; + data_len -= copy_len; + } + return offset; +} + +static void msm_ipc_router_release_msg(struct sk_buff_head *msg_head) +{ + struct sk_buff *temp; + + if (!msg_head) { + pr_err("%s: Invalid msg pointer\n", __func__); + return; + } + + while (!skb_queue_empty(msg_head)) { + temp = skb_dequeue(msg_head); + kfree_skb(temp); + } + kfree(msg_head); +} + +static int msm_ipc_router_create(struct net *net, + struct socket *sock, + int protocol, + int kern) +{ + struct sock *sk; + struct msm_ipc_port *port_ptr; + void *pil; + + if (unlikely(protocol != 0)) { + pr_err("%s: Protocol not supported\n", __func__); + return -EPROTONOSUPPORT; + } + + switch (sock->type) { + case SOCK_DGRAM: + break; + default: + pr_err("%s: Protocol type not supported\n", __func__); + return -EPROTOTYPE; + } + + sk = sk_alloc(net, AF_MSM_IPC, GFP_KERNEL, &msm_ipc_proto); + if (!sk) { + pr_err("%s: sk_alloc failed\n", __func__); + return -ENOMEM; + } + + port_ptr = msm_ipc_router_create_raw_port(sk, NULL, NULL); + if (!port_ptr) { + pr_err("%s: port_ptr alloc failed\n", __func__); + sk_free(sk); + return -ENOMEM; + } + + sock->ops = &msm_ipc_proto_ops; + sock_init_data(sock, sk); + sk->sk_rcvtimeo = DEFAULT_RCV_TIMEO; + + pil = msm_ipc_router_load_modem(); + msm_ipc_sk(sk)->port = port_ptr; + msm_ipc_sk(sk)->modem_pil = pil; + + return 0; +} + +int msm_ipc_router_bind(struct socket *sock, struct sockaddr *uaddr, + int uaddr_len) +{ + struct sockaddr_msm_ipc *addr = (struct sockaddr_msm_ipc *)uaddr; + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr; + int ret; + + if (!sk) + return -EINVAL; + + if (!uaddr_len) { + pr_err("%s: Invalid address length\n", __func__); + return -EINVAL; + } + + if (addr->family != AF_MSM_IPC) { + pr_err("%s: Address family is incorrect\n", __func__); + return -EAFNOSUPPORT; + } + + if (addr->address.addrtype != MSM_IPC_ADDR_NAME) { + pr_err("%s: Address type is incorrect\n", __func__); + return -EINVAL; + } + + port_ptr = msm_ipc_sk_port(sk); + if (!port_ptr) + return -ENODEV; + + lock_sock(sk); + + ret = msm_ipc_router_register_server(port_ptr, &addr->address); + if (!ret) + sk->sk_rcvtimeo = DEFAULT_RCV_TIMEO; + + release_sock(sk); + return ret; +} + +static int msm_ipc_router_sendmsg(struct kiocb *iocb, struct socket *sock, + struct msghdr *m, size_t total_len) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr = msm_ipc_sk_port(sk); + struct sockaddr_msm_ipc *dest = (struct sockaddr_msm_ipc *)m->msg_name; + struct sk_buff_head *msg; + int ret; + + if (!dest) + return -EDESTADDRREQ; + + if (m->msg_namelen < sizeof(*dest) || dest->family != AF_MSM_IPC) + return -EINVAL; + + if (total_len > MAX_IPC_PKT_SIZE) + return -EINVAL; + + lock_sock(sk); + msg = msm_ipc_router_build_msg(m->msg_iovlen, m->msg_iov, total_len); + if (!msg) { + pr_err("%s: Msg build failure\n", __func__); + ret = -ENOMEM; + goto out_sendmsg; + } + + ret = msm_ipc_router_send_to(port_ptr, msg, &dest->address); + if (ret == (IPC_ROUTER_HDR_SIZE + total_len)) + ret = total_len; + +out_sendmsg: + release_sock(sk); + return ret; +} + +static int msm_ipc_router_recvmsg(struct kiocb *iocb, struct socket *sock, + struct msghdr *m, size_t buf_len, int flags) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr = msm_ipc_sk_port(sk); + struct sk_buff_head *msg; + long timeout; + int ret; + + if (m->msg_iovlen != 1) + return -EOPNOTSUPP; + + if (!buf_len) + return -EINVAL; + + lock_sock(sk); + timeout = sk->sk_rcvtimeo; + mutex_lock(&port_ptr->port_rx_q_lock); + while (list_empty(&port_ptr->port_rx_q)) { + mutex_unlock(&port_ptr->port_rx_q_lock); + release_sock(sk); + if (timeout < 0) { + ret = wait_event_interruptible( + port_ptr->port_rx_wait_q, + !list_empty(&port_ptr->port_rx_q)); + if (ret) + return ret; + } else if (timeout > 0) { + timeout = wait_event_interruptible_timeout( + port_ptr->port_rx_wait_q, + !list_empty(&port_ptr->port_rx_q), + timeout); + if (timeout < 0) + return -EFAULT; + } + + if (timeout == 0) + return -ETIMEDOUT; + lock_sock(sk); + mutex_lock(&port_ptr->port_rx_q_lock); + } + mutex_unlock(&port_ptr->port_rx_q_lock); + + ret = msm_ipc_router_read(port_ptr, &msg, buf_len); + if (ret <= 0 || !msg) { + release_sock(sk); + return ret; + } + + ret = msm_ipc_router_extract_msg(m, msg); + msm_ipc_router_release_msg(msg); + msg = NULL; + release_sock(sk); + return ret; +} + +static int msm_ipc_router_ioctl(struct socket *sock, + unsigned int cmd, unsigned long arg) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr; + struct server_lookup_args server_arg; + struct msm_ipc_port_addr *port_addr = NULL; + unsigned int n, port_addr_sz = 0; + int ret; + + if (!sk) + return -EINVAL; + + lock_sock(sk); + port_ptr = msm_ipc_sk_port(sock->sk); + if (!port_ptr) { + release_sock(sk); + return -EINVAL; + } + + switch (cmd) { + case IPC_ROUTER_IOCTL_GET_VERSION: + n = IPC_ROUTER_VERSION; + ret = put_user(n, (unsigned int *)arg); + break; + + case IPC_ROUTER_IOCTL_GET_MTU: + n = (MAX_IPC_PKT_SIZE - IPC_ROUTER_HDR_SIZE); + ret = put_user(n, (unsigned int *)arg); + break; + + case IPC_ROUTER_IOCTL_GET_CURR_PKT_SIZE: + ret = msm_ipc_router_get_curr_pkt_size(port_ptr); + break; + + case IPC_ROUTER_IOCTL_LOOKUP_SERVER: + ret = copy_from_user(&server_arg, (void *)arg, + sizeof(server_arg)); + if (ret) { + ret = -EFAULT; + break; + } + + if (server_arg.num_entries_in_array < 0) { + ret = -EINVAL; + break; + } + if (server_arg.num_entries_in_array) { + port_addr_sz = server_arg.num_entries_in_array * + sizeof(*port_addr); + port_addr = kmalloc(port_addr_sz, GFP_KERNEL); + if (!port_addr) { + ret = -ENOMEM; + break; + } + } + ret = msm_ipc_router_lookup_server_name(&server_arg.port_name, + port_addr, server_arg.num_entries_in_array); + if (ret < 0) { + pr_err("%s: Server not found\n", __func__); + ret = -ENODEV; + kfree(port_addr); + break; + } + server_arg.num_entries_found = ret; + + ret = copy_to_user((void *)arg, &server_arg, + sizeof(server_arg)); + if (port_addr_sz) { + ret = copy_to_user((void *)(arg + sizeof(server_arg)), + port_addr, port_addr_sz); + if (ret) + ret = -EFAULT; + kfree(port_addr); + } + break; + + case IPC_ROUTER_IOCTL_BIND_CONTROL_PORT: + ret = msm_ipc_router_bind_control_port(port_ptr); + break; + + default: + ret = -EINVAL; + } + release_sock(sk); + return ret; +} + +static unsigned int msm_ipc_router_poll(struct file *file, + struct socket *sock, poll_table *wait) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr; + uint32_t mask = 0; + + if (!sk) + return -EINVAL; + + port_ptr = msm_ipc_sk_port(sk); + if (!port_ptr) + return -EINVAL; + + poll_wait(file, &port_ptr->port_rx_wait_q, wait); + + if (!list_empty(&port_ptr->port_rx_q)) + mask |= (POLLRDNORM | POLLIN); + + return mask; +} + +static int msm_ipc_router_close(struct socket *sock) +{ + struct sock *sk = sock->sk; + struct msm_ipc_port *port_ptr = msm_ipc_sk_port(sk); + void *pil = msm_ipc_sk(sk)->modem_pil; + int ret; + + lock_sock(sk); + ret = msm_ipc_router_close_port(port_ptr); + msm_ipc_router_unload_modem(pil); + release_sock(sk); + sock_put(sk); + sock->sk = NULL; + + return ret; +} + +static const struct net_proto_family msm_ipc_family_ops = { + .owner = THIS_MODULE, + .family = AF_MSM_IPC, + .create = msm_ipc_router_create +}; + +static const struct proto_ops msm_ipc_proto_ops = { + .owner = THIS_MODULE, + .family = AF_MSM_IPC, + .bind = msm_ipc_router_bind, + .connect = sock_no_connect, + .sendmsg = msm_ipc_router_sendmsg, + .recvmsg = msm_ipc_router_recvmsg, + .ioctl = msm_ipc_router_ioctl, + .poll = msm_ipc_router_poll, + .setsockopt = sock_no_setsockopt, + .getsockopt = sock_no_getsockopt, + .release = msm_ipc_router_close, +}; + +static struct proto msm_ipc_proto = { + .name = "MSM_IPC", + .owner = THIS_MODULE, + .obj_size = sizeof(struct msm_ipc_sock), +}; + +int msm_ipc_router_init_sockets(void) +{ + int ret; + + ret = proto_register(&msm_ipc_proto, 1); + if (ret) { + pr_err("Failed to register MSM_IPC protocol type\n"); + goto out_init_sockets; + } + + ret = sock_register(&msm_ipc_family_ops); + if (ret) { + pr_err("Failed to register MSM_IPC socket type\n"); + proto_unregister(&msm_ipc_proto); + goto out_init_sockets; + } + + sockets_enabled = 1; +out_init_sockets: + return ret; +} + +void msm_ipc_router_exit_sockets(void) +{ + if (!sockets_enabled) + return; + + sockets_enabled = 0; + sock_unregister(msm_ipc_family_ops.family); + proto_unregister(&msm_ipc_proto); +} diff --git a/arch/arm/mach-msm/irq-vic.c b/arch/arm/mach-msm/irq-vic.c index 1b54f807c2d..8ab54cd61b1 100644 --- a/arch/arm/mach-msm/irq-vic.c +++ b/arch/arm/mach-msm/irq-vic.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009, 2011 Code Aurora Forum. 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 @@ -23,11 +23,14 @@ #include #include +#include #include #include +#include +#include "fiq.h" #include "smd_private.h" enum { @@ -71,7 +74,7 @@ module_param_named(debug_mask, msm_irq_debug_mask, int, #define VIC_INT_POLARITY3 VIC_REG(0x005C) /* 1: NEG, 0: POS */ #define VIC_NO_PEND_VAL VIC_REG(0x0060) -#if defined(CONFIG_ARCH_MSM_SCORPION) +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) #define VIC_NO_PEND_VAL_FIQ VIC_REG(0x0064) #define VIC_INT_MASTEREN VIC_REG(0x0068) /* 1: IRQ, 2: FIQ */ #define VIC_CONFIG VIC_REG(0x006C) /* 1: USE SC VIC */ @@ -105,7 +108,7 @@ module_param_named(debug_mask, msm_irq_debug_mask, int, #define VIC_IRQ_VEC_PEND_RD VIC_REG(0x00D4) /* pending vector addr */ #define VIC_IRQ_VEC_WR VIC_REG(0x00D8) -#if defined(CONFIG_ARCH_MSM_SCORPION) +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) #define VIC_FIQ_VEC_RD VIC_REG(0x00DC) #define VIC_FIQ_VEC_PEND_RD VIC_REG(0x00E0) #define VIC_FIQ_VEC_WR VIC_REG(0x00E4) @@ -124,7 +127,7 @@ module_param_named(debug_mask, msm_irq_debug_mask, int, #define VIC_VECTPRIORITY(n) VIC_REG(0x0200+((n) * 4)) #define VIC_VECTADDR(n) VIC_REG(0x0400+((n) * 4)) -#if defined(CONFIG_ARCH_MSM7X30) +#if defined(CONFIG_ARCH_MSM7X30) || defined(CONFIG_ARCH_FSM9XXX) #define VIC_NUM_REGS 4 #else #define VIC_NUM_REGS 2 @@ -160,10 +163,13 @@ static struct { static uint32_t msm_irq_idle_disable[VIC_NUM_REGS]; #define SMSM_FAKE_IRQ (0xff) +#if !defined(CONFIG_ARCH_FSM9XXX) static uint8_t msm_irq_to_smsm[NR_IRQS] = { +#if !defined(CONFIG_ARCH_MSM7X27A) [INT_MDDI_EXT] = 1, [INT_MDDI_PRI] = 2, [INT_MDDI_CLIENT] = 3, +#endif [INT_USB_OTG] = 4, [INT_PWB_I2C] = 5, @@ -217,6 +223,17 @@ static uint8_t msm_irq_to_smsm[NR_IRQS] = { [INT_SIRC_1] = SMSM_FAKE_IRQ, #endif }; +# else /* CONFIG_ARCH_FSM9XXX */ +static uint8_t msm_irq_to_smsm[NR_IRQS] = { + [INT_UART1] = 11, + [INT_A9_M2A_0] = SMSM_FAKE_IRQ, + [INT_A9_M2A_1] = SMSM_FAKE_IRQ, + [INT_A9_M2A_5] = SMSM_FAKE_IRQ, + [INT_GP_TIMER_EXP] = SMSM_FAKE_IRQ, + [INT_DEBUG_TIMER_EXP] = SMSM_FAKE_IRQ, + [INT_SIRC_0] = 10, +}; +#endif /* CONFIG_ARCH_FSM9XXX */ static inline void msm_irq_write_all_regs(void __iomem *base, unsigned int val) { @@ -228,8 +245,32 @@ static inline void msm_irq_write_all_regs(void __iomem *base, unsigned int val) static void msm_irq_ack(struct irq_data *d) { + uint32_t mask; + void __iomem *reg = VIC_INT_TO_REG_ADDR(VIC_INT_CLEAR0, d->irq); - writel(1 << (d->irq & 31), reg); + mask = 1 << (d->irq & 31); + writel(mask, reg); + mb(); +} + +static void msm_irq_disable(struct irq_data *d) +{ + void __iomem *reg = VIC_INT_TO_REG_ADDR(VIC_INT_ENCLEAR0, d->irq); + unsigned index = VIC_INT_TO_REG_INDEX(d->irq); + uint32_t mask = 1UL << (d->irq & 31); + int smsm_irq = msm_irq_to_smsm[d->irq]; + + if (!(msm_irq_shadow_reg[index].int_en[1] & mask)) { + msm_irq_shadow_reg[index].int_en[0] &= ~mask; + writel(mask, reg); + mb(); + if (smsm_irq == 0) + msm_irq_idle_disable[index] &= ~mask; + else { + mask = 1UL << (smsm_irq - 1); + msm_irq_smsm_wake_enable[0] &= ~mask; + } + } } static void msm_irq_mask(struct irq_data *d) @@ -241,6 +282,7 @@ static void msm_irq_mask(struct irq_data *d) msm_irq_shadow_reg[index].int_en[0] &= ~mask; writel(mask, reg); + mb(); if (smsm_irq == 0) msm_irq_idle_disable[index] &= ~mask; else { @@ -258,6 +300,7 @@ static void msm_irq_unmask(struct irq_data *d) msm_irq_shadow_reg[index].int_en[0] |= mask; writel(mask, reg); + mb(); if (smsm_irq == 0) msm_irq_idle_disable[index] |= mask; @@ -295,7 +338,7 @@ static int msm_irq_set_wake(struct irq_data *d, unsigned int on) static int msm_irq_set_type(struct irq_data *d, unsigned int flow_type) { - void __iomem *treg = VIC_INT_TO_REG_ADDR(VIC_INT_TYPE0, d->irq); + void __iomem *treg = VIC_INT_TO_REG_ADDR(VIC_INT_TYPE0, d->irq); void __iomem *preg = VIC_INT_TO_REG_ADDR(VIC_INT_POLARITY0, d->irq); unsigned index = VIC_INT_TO_REG_INDEX(d->irq); int b = 1 << (d->irq & 31); @@ -320,18 +363,220 @@ static int msm_irq_set_type(struct irq_data *d, unsigned int flow_type) __irq_set_handler_locked(d->irq, handle_level_irq); } writel(type, treg); + mb(); msm_irq_shadow_reg[index].int_type = type; return 0; } +unsigned int msm_irq_pending(void) +{ + unsigned int i, pending = 0; + + for (i = 0; (i < VIC_NUM_REGS) && !pending; i++) + pending |= readl(VIC_IRQ_STATUS0 + (i * 4)); + + return pending; +} + +int msm_irq_idle_sleep_allowed(void) +{ + uint32_t i, disable = 0; + + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_REQUEST) + DPRINT_ARRAY(msm_irq_idle_disable, + "msm_irq_idle_sleep_allowed: disable"); + + for (i = 0; i < VIC_NUM_REGS; i++) + disable |= msm_irq_idle_disable[i]; + + return !disable; +} + +/* + * Prepare interrupt subsystem for entering sleep -- phase 1. + * If modem_wake is true, return currently enabled interrupts in *irq_mask. + */ +void msm_irq_enter_sleep1(bool modem_wake, int from_idle, uint32_t *irq_mask) +{ + if (modem_wake) { + *irq_mask = msm_irq_smsm_wake_enable[!from_idle]; + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP) + printk(KERN_INFO + "%s irq_mask %x\n", __func__, *irq_mask); + } +} + +/* + * Prepare interrupt subsystem for entering sleep -- phase 2. + * Detect any pending interrupts and configure interrupt hardware. + * + * Return value: + * -EAGAIN: there are pending interrupt(s); interrupt configuration + * is not changed. + * 0: success + */ +int msm_irq_enter_sleep2(bool modem_wake, int from_idle) +{ + int i, limit = 10; + uint32_t pending[VIC_NUM_REGS]; + + if (from_idle && !modem_wake) + return 0; + + /* edge triggered interrupt may get lost if this mode is used */ + WARN_ON_ONCE(!modem_wake && !from_idle); + + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP) + DPRINT_REGS(VIC_IRQ_STATUS, "%s change irq, pend", __func__); + + for (i = 0; i < VIC_NUM_REGS; i++) { + pending[i] = readl(VIC_IRQ_STATUS0 + (i * 4)); + pending[i] &= msm_irq_shadow_reg[i].int_en[!from_idle]; + } + + /* + * Clear INT_A9_M2A_5 since requesting sleep triggers it. + * In some arch e.g. FSM9XXX, INT_A9_M2A_5 may not be in the first set. + */ + pending[INT_A9_M2A_5 / 32] &= ~(1U << (INT_A9_M2A_5 % 32)); + + for (i = 0; i < VIC_NUM_REGS; i++) { + if (pending[i]) { + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_ABORT) + DPRINT_ARRAY(pending, "%s abort", + __func__); + return -EAGAIN; + } + } + + msm_irq_write_all_regs(VIC_INT_EN0, 0); + + while (limit-- > 0) { + int pend_irq; + int irq = readl(VIC_IRQ_VEC_RD); + if (irq == -1) + break; + pend_irq = readl(VIC_IRQ_VEC_PEND_RD); + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_INT) + printk(KERN_INFO "%s cleared int %d (%d)\n", + __func__, irq, pend_irq); + } + + if (modem_wake) { + struct irq_data d = { .irq = INT_A9_M2A_6 }; + msm_irq_set_type(&d, IRQF_TRIGGER_RISING); + __raw_writel(1U << (INT_A9_M2A_6 % 32), + VIC_INT_TO_REG_ADDR(VIC_INT_ENSET0, INT_A9_M2A_6)); + } else { + for (i = 0; i < VIC_NUM_REGS; i++) + writel(msm_irq_shadow_reg[i].int_en[1], + VIC_INT_ENSET0 + (i * 4)); + } + mb(); + + return 0; +} + +/* + * Restore interrupt subsystem from sleep -- phase 1. + * Configure interrupt hardware. + */ +void msm_irq_exit_sleep1(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending_irqs) +{ + int i; + struct irq_data d = { .irq = INT_A9_M2A_6 }; + + msm_irq_ack(&d); + + for (i = 0; i < VIC_NUM_REGS; i++) { + writel(msm_irq_shadow_reg[i].int_type, + VIC_INT_TYPE0 + i * 4); + writel(msm_irq_shadow_reg[i].int_polarity, + VIC_INT_POLARITY0 + i * 4); + writel(msm_irq_shadow_reg[i].int_en[0], + VIC_INT_EN0 + i * 4); + writel(msm_irq_shadow_reg[i].int_select, + VIC_INT_SELECT0 + i * 4); + } + + writel(3, VIC_INT_MASTEREN); + mb(); + + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP) + DPRINT_REGS(VIC_IRQ_STATUS, "%s %x %x %x now", + __func__, irq_mask, pending_irqs, wakeup_reason); +} + +/* + * Restore interrupt subsystem from sleep -- phase 2. + * Poke the specified pending interrupts into interrupt hardware. + */ +void msm_irq_exit_sleep2(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending) +{ + int i; + + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP) + DPRINT_REGS(VIC_IRQ_STATUS, "%s %x %x %x now", + __func__, irq_mask, pending, wakeup_reason); + + for (i = 0; pending && i < ARRAY_SIZE(msm_irq_to_smsm); i++) { + unsigned reg_offset = VIC_INT_TO_REG_ADDR(0, i); + uint32_t reg_mask = 1UL << (i & 31); + int smsm_irq = msm_irq_to_smsm[i]; + uint32_t smsm_mask; + + if (smsm_irq == 0) + continue; + + smsm_mask = 1U << (smsm_irq - 1); + if (!(pending & smsm_mask)) + continue; + + pending &= ~smsm_mask; + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_INT) + DPRINT_REGS(VIC_IRQ_STATUS, + "%s: irq %d still pending %x now", + __func__, i, pending); +#ifdef DEBUG_INTERRUPT_TRIGGER + if (readl(VIC_IRQ_STATUS0 + reg_offset) & reg_mask) + writel(reg_mask, VIC_INT_CLEAR0 + reg_offset); +#endif + if (readl(VIC_IRQ_STATUS0 + reg_offset) & reg_mask) + continue; + + writel(reg_mask, VIC_SOFTINT0 + reg_offset); + + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_INT_TRIGGER) + DPRINT_REGS(VIC_IRQ_STATUS, + "%s: irq %d need trigger, now", + __func__, i); + } + mb(); +} + +/* + * Restore interrupt subsystem from sleep -- phase 3. + * Print debug information. + */ +void msm_irq_exit_sleep3(uint32_t irq_mask, uint32_t wakeup_reason, + uint32_t pending_irqs) +{ + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP) + DPRINT_REGS(VIC_IRQ_STATUS, "%s %x %x %x state %x now", + __func__, irq_mask, pending_irqs, wakeup_reason, + smsm_get_state(SMSM_MODEM_STATE)); +} + static struct irq_chip msm_irq_chip = { - .name = "msm", - .irq_disable = msm_irq_mask, - .irq_ack = msm_irq_ack, - .irq_mask = msm_irq_mask, - .irq_unmask = msm_irq_unmask, - .irq_set_wake = msm_irq_set_wake, - .irq_set_type = msm_irq_set_type, + .name = "msm", + .irq_disable = msm_irq_disable, + .irq_ack = msm_irq_ack, + .irq_mask = msm_irq_mask, + .irq_unmask = msm_irq_unmask, + .irq_set_wake = msm_irq_set_wake, + .irq_set_type = msm_irq_set_type, }; void __init msm_init_irq(void) @@ -353,11 +598,96 @@ void __init msm_init_irq(void) /* don't use vic */ writel(0, VIC_CONFIG); - /* enable interrupt controller */ - writel(3, VIC_INT_MASTEREN); for (n = 0; n < NR_MSM_IRQS; n++) { irq_set_chip_and_handler(n, &msm_irq_chip, handle_level_irq); set_irq_flags(n, IRQF_VALID); } + + /* enable interrupt controller */ + writel(3, VIC_INT_MASTEREN); + mb(); +} + +#if defined(CONFIG_MSM_FIQ_SUPPORT) +void msm_trigger_irq(int irq) +{ + void __iomem *reg = VIC_INT_TO_REG_ADDR(VIC_SOFTINT0, irq); + uint32_t mask = 1UL << (irq & 31); + writel(mask, reg); + mb(); +} + +void msm_fiq_enable(int irq) +{ + struct irq_data d = { .irq = irq }; + unsigned long flags; + local_irq_save(flags); + msm_irq_unmask(&d); + local_irq_restore(flags); +} + +void msm_fiq_disable(int irq) +{ + struct irq_data d = { .irq = irq }; + unsigned long flags; + local_irq_save(flags); + msm_irq_mask(&d); + local_irq_restore(flags); +} + +void msm_fiq_select(int irq) +{ + void __iomem *reg = VIC_INT_TO_REG_ADDR(VIC_INT_SELECT0, irq); + unsigned index = VIC_INT_TO_REG_INDEX(irq); + uint32_t mask = 1UL << (irq & 31); + unsigned long flags; + + local_irq_save(flags); + msm_irq_shadow_reg[index].int_select |= mask; + writel(msm_irq_shadow_reg[index].int_select, reg); + mb(); + local_irq_restore(flags); +} + +void msm_fiq_unselect(int irq) +{ + void __iomem *reg = VIC_INT_TO_REG_ADDR(VIC_INT_SELECT0, irq); + unsigned index = VIC_INT_TO_REG_INDEX(irq); + uint32_t mask = 1UL << (irq & 31); + unsigned long flags; + + local_irq_save(flags); + msm_irq_shadow_reg[index].int_select &= (!mask); + writel(msm_irq_shadow_reg[index].int_select, reg); + mb(); + local_irq_restore(flags); +} +/* set_fiq_handler originally from arch/arm/kernel/fiq.c */ +static void set_fiq_handler(void *start, unsigned int length) +{ + memcpy((void *)0xffff001c, start, length); + flush_icache_range(0xffff001c, 0xffff001c + length); + if (!vectors_high()) + flush_icache_range(0x1c, 0x1c + length); +} + +static void (*fiq_func)(void *data, void *regs); +static unsigned long long fiq_stack[256]; + +int msm_fiq_set_handler(void (*func)(void *data, void *regs), void *data) +{ + unsigned long flags; + int ret = -ENOMEM; + + local_irq_save(flags); + if (fiq_func == 0) { + fiq_func = func; + fiq_glue_setup(func, data, fiq_stack + 255); + set_fiq_handler(&fiq_glue, (&fiq_glue_end - &fiq_glue)); + ret = 0; + } + local_irq_restore(flags); + return ret; } +#endif diff --git a/arch/arm/mach-msm/irq.c b/arch/arm/mach-msm/irq.c index ea514be390c..280160aa018 100644 --- a/arch/arm/mach-msm/irq.c +++ b/arch/arm/mach-msm/irq.c @@ -22,9 +22,25 @@ #include #include +#include + #include #include +#include + +#include "sirc.h" +#include "smd_private.h" + +enum { + IRQ_DEBUG_SLEEP_INT_TRIGGER = 1U << 0, + IRQ_DEBUG_SLEEP_INT = 1U << 1, + IRQ_DEBUG_SLEEP_ABORT = 1U << 2, + IRQ_DEBUG_SLEEP = 1U << 3, + IRQ_DEBUG_SLEEP_REQUEST = 1U << 4, +}; +static int msm_irq_debug_mask; +module_param_named(debug_mask, msm_irq_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); #define VIC_REG(off) (MSM_VIC_BASE + (off)) @@ -41,9 +57,16 @@ #define VIC_INT_POLARITY0 VIC_REG(0x0050) /* 1: NEG, 0: POS */ #define VIC_INT_POLARITY1 VIC_REG(0x0054) /* 1: NEG, 0: POS */ #define VIC_NO_PEND_VAL VIC_REG(0x0060) + +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) +#define VIC_NO_PEND_VAL_FIQ VIC_REG(0x0064) +#define VIC_INT_MASTEREN VIC_REG(0x0068) /* 1: IRQ, 2: FIQ */ +#define VIC_CONFIG VIC_REG(0x006C) /* 1: USE SC VIC */ +#else #define VIC_INT_MASTEREN VIC_REG(0x0064) /* 1: IRQ, 2: FIQ */ -#define VIC_PROTECTION VIC_REG(0x006C) /* 1: ENABLE */ #define VIC_CONFIG VIC_REG(0x0068) /* 1: USE ARM1136 VIC */ +#define VIC_PROTECTION VIC_REG(0x006C) /* 1: ENABLE */ +#endif #define VIC_IRQ_STATUS0 VIC_REG(0x0080) #define VIC_IRQ_STATUS1 VIC_REG(0x0084) #define VIC_FIQ_STATUS0 VIC_REG(0x0090) @@ -57,65 +80,370 @@ #define VIC_IRQ_VEC_RD VIC_REG(0x00D0) /* pending int # */ #define VIC_IRQ_VEC_PEND_RD VIC_REG(0x00D4) /* pending vector addr */ #define VIC_IRQ_VEC_WR VIC_REG(0x00D8) + +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) +#define VIC_FIQ_VEC_RD VIC_REG(0x00DC) +#define VIC_FIQ_VEC_PEND_RD VIC_REG(0x00E0) +#define VIC_FIQ_VEC_WR VIC_REG(0x00E4) +#define VIC_IRQ_IN_SERVICE VIC_REG(0x00E8) +#define VIC_IRQ_IN_STACK VIC_REG(0x00EC) +#define VIC_FIQ_IN_SERVICE VIC_REG(0x00F0) +#define VIC_FIQ_IN_STACK VIC_REG(0x00F4) +#define VIC_TEST_BUS_SEL VIC_REG(0x00F8) +#define VIC_IRQ_CTRL_CONFIG VIC_REG(0x00FC) +#else #define VIC_IRQ_IN_SERVICE VIC_REG(0x00E0) #define VIC_IRQ_IN_STACK VIC_REG(0x00E4) #define VIC_TEST_BUS_SEL VIC_REG(0x00E8) +#endif #define VIC_VECTPRIORITY(n) VIC_REG(0x0200+((n) * 4)) #define VIC_VECTADDR(n) VIC_REG(0x0400+((n) * 4)) -static void msm_irq_ack(struct irq_data *d) +static uint32_t msm_irq_smsm_wake_enable[2]; +static struct { + uint32_t int_en[2]; + uint32_t int_type; + uint32_t int_polarity; + uint32_t int_select; +} msm_irq_shadow_reg[2]; +static uint32_t msm_irq_idle_disable[2]; + +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) +#define INT_INFO_SMSM_ID SMEM_SMSM_INT_INFO +struct smsm_interrupt_info *smsm_int_info; +#else +#define INT_INFO_SMSM_ID SMEM_APPS_DEM_SLAVE_DATA +struct msm_dem_slave_data *smsm_int_info; +#endif + + +#define SMSM_FAKE_IRQ (0xff) +static uint8_t msm_irq_to_smsm[NR_MSM_IRQS + NR_SIRC_IRQS] = { + [INT_MDDI_EXT] = 1, + [INT_MDDI_PRI] = 2, + [INT_MDDI_CLIENT] = 3, + [INT_USB_OTG] = 4, + + /* [INT_PWB_I2C] = 5 -- not usable */ + [INT_SDC1_0] = 6, + [INT_SDC1_1] = 7, + [INT_SDC2_0] = 8, + + [INT_SDC2_1] = 9, + [INT_ADSP_A9_A11] = 10, + [INT_UART1] = 11, + [INT_UART2] = 12, + + [INT_UART3] = 13, + [INT_UART1_RX] = 14, + [INT_UART2_RX] = 15, + [INT_UART3_RX] = 16, + + [INT_UART1DM_IRQ] = 17, + [INT_UART1DM_RX] = 18, + [INT_KEYSENSE] = 19, + [INT_AD_HSSD] = 20, + + [INT_NAND_WR_ER_DONE] = 21, + [INT_NAND_OP_DONE] = 22, + [INT_TCHSCRN1] = 23, + [INT_TCHSCRN2] = 24, + + [INT_TCHSCRN_SSBI] = 25, + [INT_USB_HS] = 26, + [INT_UART2DM_RX] = 27, + [INT_UART2DM_IRQ] = 28, + + [INT_SDC4_1] = 29, + [INT_SDC4_0] = 30, + [INT_SDC3_1] = 31, + [INT_SDC3_0] = 32, + + /* fake wakeup interrupts */ + [INT_GPIO_GROUP1] = SMSM_FAKE_IRQ, + [INT_GPIO_GROUP2] = SMSM_FAKE_IRQ, + [INT_A9_M2A_0] = SMSM_FAKE_IRQ, + [INT_A9_M2A_1] = SMSM_FAKE_IRQ, + [INT_A9_M2A_5] = SMSM_FAKE_IRQ, + [INT_GP_TIMER_EXP] = SMSM_FAKE_IRQ, + [INT_DEBUG_TIMER_EXP] = SMSM_FAKE_IRQ, + [INT_ADSP_A11] = SMSM_FAKE_IRQ, +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) + [INT_SIRC_0] = SMSM_FAKE_IRQ, + [INT_SIRC_1] = SMSM_FAKE_IRQ, +#endif +}; + +static void msm_irq_ack(unsigned int irq) { - void __iomem *reg = VIC_INT_CLEAR0 + ((d->irq & 32) ? 4 : 0); - writel(1 << (d->irq & 31), reg); + void __iomem *reg = VIC_INT_CLEAR0 + ((irq & 32) ? 4 : 0); + irq = 1 << (irq & 31); + writel(irq, reg); } -static void msm_irq_mask(struct irq_data *d) +static void msm_irq_mask(unsigned int irq) { - void __iomem *reg = VIC_INT_ENCLEAR0 + ((d->irq & 32) ? 4 : 0); - writel(1 << (d->irq & 31), reg); + void __iomem *reg = VIC_INT_ENCLEAR0 + ((irq & 32) ? 4 : 0); + unsigned index = (irq >> 5) & 1; + uint32_t mask = 1UL << (irq & 31); + int smsm_irq = msm_irq_to_smsm[irq]; + + msm_irq_shadow_reg[index].int_en[0] &= ~mask; + writel(mask, reg); + if (smsm_irq == 0) + msm_irq_idle_disable[index] &= ~mask; + else { + mask = 1UL << (smsm_irq - 1); + msm_irq_smsm_wake_enable[0] &= ~mask; + } } -static void msm_irq_unmask(struct irq_data *d) +static void msm_irq_unmask(unsigned int irq) { - void __iomem *reg = VIC_INT_ENSET0 + ((d->irq & 32) ? 4 : 0); - writel(1 << (d->irq & 31), reg); + void __iomem *reg = VIC_INT_ENSET0 + ((irq & 32) ? 4 : 0); + unsigned index = (irq >> 5) & 1; + uint32_t mask = 1UL << (irq & 31); + int smsm_irq = msm_irq_to_smsm[irq]; + + msm_irq_shadow_reg[index].int_en[0] |= mask; + writel(mask, reg); + + if (smsm_irq == 0) + msm_irq_idle_disable[index] |= mask; + else { + mask = 1UL << (smsm_irq - 1); + msm_irq_smsm_wake_enable[0] |= mask; + } } -static int msm_irq_set_wake(struct irq_data *d, unsigned int on) +static int msm_irq_set_wake(unsigned int irq, unsigned int on) { - return -EINVAL; + unsigned index = (irq >> 5) & 1; + uint32_t mask = 1UL << (irq & 31); + int smsm_irq = msm_irq_to_smsm[irq]; + + if (smsm_irq == 0) { + printk(KERN_ERR "msm_irq_set_wake: bad wakeup irq %d\n", irq); + return -EINVAL; + } + if (on) + msm_irq_shadow_reg[index].int_en[1] |= mask; + else + msm_irq_shadow_reg[index].int_en[1] &= ~mask; + + if (smsm_irq == SMSM_FAKE_IRQ) + return 0; + + mask = 1UL << (smsm_irq - 1); + if (on) + msm_irq_smsm_wake_enable[1] |= mask; + else + msm_irq_smsm_wake_enable[1] &= ~mask; + return 0; } -static int msm_irq_set_type(struct irq_data *d, unsigned int flow_type) +static int msm_irq_set_type(unsigned int irq, unsigned int flow_type) { - void __iomem *treg = VIC_INT_TYPE0 + ((d->irq & 32) ? 4 : 0); - void __iomem *preg = VIC_INT_POLARITY0 + ((d->irq & 32) ? 4 : 0); - int b = 1 << (d->irq & 31); + void __iomem *treg = VIC_INT_TYPE0 + ((irq & 32) ? 4 : 0); + void __iomem *preg = VIC_INT_POLARITY0 + ((irq & 32) ? 4 : 0); + unsigned index = (irq >> 5) & 1; + int b = 1 << (irq & 31); + uint32_t polarity; + uint32_t type; + polarity = msm_irq_shadow_reg[index].int_polarity; if (flow_type & (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_LOW)) - writel(readl(preg) | b, preg); + polarity |= b; if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_HIGH)) - writel(readl(preg) & (~b), preg); + polarity &= ~b; + writel(polarity, preg); + msm_irq_shadow_reg[index].int_polarity = polarity; + type = msm_irq_shadow_reg[index].int_type; if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { - writel(readl(treg) | b, treg); + type |= b; __irq_set_handler_locked(d->irq, handle_edge_irq); } if (flow_type & (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW)) { - writel(readl(treg) & (~b), treg); + type &= ~b; __irq_set_handler_locked(d->irq, handle_level_irq); } + writel(type, treg); + msm_irq_shadow_reg[index].int_type = type; + return 0; +} + +int msm_irq_pending(void) +{ + return readl(VIC_IRQ_STATUS0) || readl(VIC_IRQ_STATUS1); +} + +int msm_irq_idle_sleep_allowed(void) +{ + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_REQUEST) + printk(KERN_INFO "msm_irq_idle_sleep_allowed: disable %x %x\n", + msm_irq_idle_disable[0], msm_irq_idle_disable[1]); + return !(msm_irq_idle_disable[0] || msm_irq_idle_disable[1] || + !smsm_int_info); +} + +/* If arm9_wake is set: pass control to the other core. + * If from_idle is not set: disable non-wakeup interrupts. + */ +void msm_irq_enter_sleep1(bool arm9_wake, int from_idle) +{ + if (!arm9_wake || !smsm_int_info) + return; + smsm_int_info->interrupt_mask = msm_irq_smsm_wake_enable[!from_idle]; + smsm_int_info->pending_interrupts = 0; +} + +int msm_irq_enter_sleep2(bool arm9_wake, int from_idle) +{ + int limit = 10; + uint32_t pending0, pending1; + + if (from_idle && !arm9_wake) + return 0; + + /* edge triggered interrupt may get lost if this mode is used */ + WARN_ON_ONCE(!arm9_wake && !from_idle); + + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP) + printk(KERN_INFO "msm_irq_enter_sleep change irq, pend %x %x\n", + readl(VIC_IRQ_STATUS0), readl(VIC_IRQ_STATUS1)); + pending0 = readl(VIC_IRQ_STATUS0); + pending1 = readl(VIC_IRQ_STATUS1); + pending0 &= msm_irq_shadow_reg[0].int_en[!from_idle]; + /* Clear INT_A9_M2A_5 since requesting sleep triggers it */ + pending0 &= ~(1U << INT_A9_M2A_5); + pending1 &= msm_irq_shadow_reg[1].int_en[!from_idle]; + if (pending0 || pending1) { + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_ABORT) + printk(KERN_INFO "msm_irq_enter_sleep2 abort %x %x\n", + pending0, pending1); + return -EAGAIN; + } + + writel(0, VIC_INT_EN0); + writel(0, VIC_INT_EN1); + + while (limit-- > 0) { + int pend_irq; + int irq = readl(VIC_IRQ_VEC_RD); + if (irq == -1) + break; + pend_irq = readl(VIC_IRQ_VEC_PEND_RD); + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_INT) + printk(KERN_INFO "msm_irq_enter_sleep cleared " + "int %d (%d)\n", irq, pend_irq); + } + + if (arm9_wake) { + msm_irq_set_type(INT_A9_M2A_6, IRQF_TRIGGER_RISING); + msm_irq_ack(INT_A9_M2A_6); + writel(1U << INT_A9_M2A_6, VIC_INT_ENSET0); + } else { + writel(msm_irq_shadow_reg[0].int_en[1], VIC_INT_ENSET0); + writel(msm_irq_shadow_reg[1].int_en[1], VIC_INT_ENSET1); + } return 0; } +void msm_irq_exit_sleep1(void) +{ + int i; + + msm_irq_ack(INT_A9_M2A_6); + msm_irq_ack(INT_PWB_I2C); + for (i = 0; i < 2; i++) { + writel(msm_irq_shadow_reg[i].int_type, VIC_INT_TYPE0 + i * 4); + writel(msm_irq_shadow_reg[i].int_polarity, VIC_INT_POLARITY0 + i * 4); + writel(msm_irq_shadow_reg[i].int_en[0], VIC_INT_EN0 + i * 4); + writel(msm_irq_shadow_reg[i].int_select, VIC_INT_SELECT0 + i * 4); + } + writel(3, VIC_INT_MASTEREN); + if (!smsm_int_info) { + printk(KERN_ERR "msm_irq_exit_sleep \n"); + return; + } + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP) + printk(KERN_INFO "msm_irq_exit_sleep1 %x %x %x now %x %x\n", + smsm_int_info->interrupt_mask, + smsm_int_info->pending_interrupts, + smsm_int_info->wakeup_reason, + readl(VIC_IRQ_STATUS0), readl(VIC_IRQ_STATUS1)); +} + +void msm_irq_exit_sleep2(void) +{ + int i; + uint32_t pending; + + if (!smsm_int_info) { + printk(KERN_ERR "msm_irq_exit_sleep \n"); + return; + } + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP) + printk(KERN_INFO "msm_irq_exit_sleep2 %x %x %x now %x %x\n", + smsm_int_info->interrupt_mask, + smsm_int_info->pending_interrupts, + smsm_int_info->wakeup_reason, + readl(VIC_IRQ_STATUS0), readl(VIC_IRQ_STATUS1)); + pending = smsm_int_info->pending_interrupts; + for (i = 0; pending && i < ARRAY_SIZE(msm_irq_to_smsm); i++) { + unsigned reg_offset = (i & 32) ? 4 : 0; + uint32_t reg_mask = 1UL << (i & 31); + int smsm_irq = msm_irq_to_smsm[i]; + uint32_t smsm_mask; + if (smsm_irq == 0) + continue; + smsm_mask = 1U << (smsm_irq - 1); + if (!(pending & smsm_mask)) + continue; + pending &= ~smsm_mask; + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_INT) + printk(KERN_INFO "msm_irq_exit_sleep2: irq %d " + "still pending %x now %x %x\n", i, pending, + readl(VIC_IRQ_STATUS0), readl(VIC_IRQ_STATUS1)); +#if 0 /* debug intetrrupt trigger */ + if (readl(VIC_IRQ_STATUS0 + reg_offset) & reg_mask) + writel(reg_mask, VIC_INT_CLEAR0 + reg_offset); +#endif + if (readl(VIC_IRQ_STATUS0 + reg_offset) & reg_mask) + continue; + writel(reg_mask, VIC_SOFTINT0 + reg_offset); + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP_INT_TRIGGER) + printk(KERN_INFO "msm_irq_exit_sleep2: irq %d need " + "trigger, now %x %x\n", i, + readl(VIC_IRQ_STATUS0), readl(VIC_IRQ_STATUS1)); + } +} + +void msm_irq_exit_sleep3(void) +{ + if (!smsm_int_info) { + printk(KERN_ERR "msm_irq_exit_sleep \n"); + return; + } + if (msm_irq_debug_mask & IRQ_DEBUG_SLEEP) + printk(KERN_INFO "msm_irq_exit_sleep3 %x %x %x now %x %x " + "state %x\n", smsm_int_info->interrupt_mask, + smsm_int_info->pending_interrupts, + smsm_int_info->wakeup_reason, readl(VIC_IRQ_STATUS0), + readl(VIC_IRQ_STATUS1), + smsm_get_state(SMSM_STATE_MODEM)); +} + static struct irq_chip msm_irq_chip = { - .name = "msm", - .irq_ack = msm_irq_ack, - .irq_mask = msm_irq_mask, - .irq_unmask = msm_irq_unmask, - .irq_set_wake = msm_irq_set_wake, - .irq_set_type = msm_irq_set_type, + .name = "msm", + .disable = msm_irq_mask, + .ack = msm_irq_ack, + .mask = msm_irq_mask, + .unmask = msm_irq_unmask, + .set_wake = msm_irq_set_wake, + .set_type = msm_irq_set_type, }; void __init msm_init_irq(void) @@ -142,10 +470,138 @@ void __init msm_init_irq(void) writel(0, VIC_CONFIG); /* enable interrupt controller */ - writel(1, VIC_INT_MASTEREN); + writel(3, VIC_INT_MASTEREN); for (n = 0; n < NR_MSM_IRQS; n++) { irq_set_chip_and_handler(n, &msm_irq_chip, handle_level_irq); set_irq_flags(n, IRQF_VALID); } + + msm_init_sirc(); +} + +static int __init msm_init_irq_late(void) +{ + smsm_int_info = smem_alloc(INT_INFO_SMSM_ID, sizeof(*smsm_int_info)); + if (!smsm_int_info) + pr_err("set_wakeup_mask NO INT_INFO (%d)\n", INT_INFO_SMSM_ID); + return 0; +} +late_initcall(msm_init_irq_late); + +#if defined(CONFIG_MSM_FIQ_SUPPORT) +void msm_trigger_irq(int irq) +{ + void __iomem *reg = VIC_SOFTINT0 + ((irq & 32) ? 4 : 0); + uint32_t mask = 1UL << (irq & 31); + writel(mask, reg); +} + +void msm_fiq_enable(int irq) +{ + unsigned long flags; + local_irq_save(flags); + irq_desc[irq].chip->unmask(irq); + local_irq_restore(flags); +} + +void msm_fiq_disable(int irq) +{ + unsigned long flags; + local_irq_save(flags); + irq_desc[irq].chip->mask(irq); + local_irq_restore(flags); +} + +static void _msm_fiq_select(int irq) +{ + void __iomem *reg = VIC_INT_SELECT0 + ((irq & 32) ? 4 : 0); + unsigned index = (irq >> 5) & 1; + uint32_t mask = 1UL << (irq & 31); + unsigned long flags; + + local_irq_save(flags); + msm_irq_shadow_reg[index].int_select |= mask; + writel(msm_irq_shadow_reg[index].int_select, reg); + local_irq_restore(flags); +} + +static void _msm_fiq_unselect(int irq) +{ + void __iomem *reg = VIC_INT_SELECT0 + ((irq & 32) ? 4 : 0); + unsigned index = (irq >> 5) & 1; + uint32_t mask = 1UL << (irq & 31); + unsigned long flags; + + local_irq_save(flags); + msm_irq_shadow_reg[index].int_select &= (!mask); + writel(msm_irq_shadow_reg[index].int_select, reg); + local_irq_restore(flags); +} + +void msm_fiq_select(int irq) +{ + if (irq < FIRST_SIRC_IRQ) + _msm_fiq_select(irq); + else if (irq < FIRST_GPIO_IRQ) + sirc_fiq_select(irq, true); + else + pr_err("unsupported fiq %d", irq); +} + +void msm_fiq_unselect(int irq) +{ + if (irq < FIRST_SIRC_IRQ) + _msm_fiq_unselect(irq); + else if (irq < FIRST_GPIO_IRQ) + sirc_fiq_select(irq, false); + else + pr_err("unsupported fiq %d", irq); +} + +/* set_fiq_handler originally from arch/arm/kernel/fiq.c */ +static void set_fiq_handler(void *start, unsigned int length) +{ + memcpy((void *)0xffff001c, start, length); + flush_icache_range(0xffff001c, 0xffff001c + length); + if (!vectors_high()) + flush_icache_range(0x1c, 0x1c + length); +} + +extern unsigned char fiq_glue, fiq_glue_end; + +static void (*fiq_func)(void *data, void *regs, void *svc_sp); +static void *fiq_data; +static void *fiq_stack; + +void fiq_glue_setup(void *func, void *data, void *sp); + +int msm_fiq_set_handler(void (*func)(void *data, void *regs, void *svc_sp), + void *data) +{ + unsigned long flags; + int ret = -ENOMEM; + + if (!fiq_stack) + fiq_stack = kzalloc(THREAD_SIZE, GFP_KERNEL); + if (!fiq_stack) + return -ENOMEM; + + local_irq_save(flags); + if (fiq_func == 0) { + fiq_func = func; + fiq_data = data; + fiq_glue_setup(func, data, fiq_stack + THREAD_START_SP); + set_fiq_handler(&fiq_glue, (&fiq_glue_end - &fiq_glue)); + ret = 0; + } + local_irq_restore(flags); + return ret; +} + +void msm_fiq_exit_sleep(void) +{ + if (fiq_stack) + fiq_glue_setup(fiq_func, fiq_data, fiq_stack + THREAD_START_SP); } +#endif diff --git a/arch/arm/mach-msm/irq.h b/arch/arm/mach-msm/irq.h new file mode 100644 index 00000000000..8b0fbc04794 --- /dev/null +++ b/arch/arm/mach-msm/irq.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_IRQ_H_ +#define _ARCH_ARM_MACH_MSM_IRQ_H_ + +int msm_irq_idle_sleep_allowed(void); +unsigned int msm_irq_pending(void); +void msm_irq_enter_sleep1(bool arm9_wake, int from_idle, uint32_t *irq_mask); +int msm_irq_enter_sleep2(bool arm9_wake, int from_idle); +void msm_irq_exit_sleep1 + (uint32_t irq_mask, uint32_t wakeup_reason, uint32_t pending_irqs); +void msm_irq_exit_sleep2 + (uint32_t irq_mask, uint32_t wakeup_reason, uint32_t pending); +void msm_irq_exit_sleep3 + (uint32_t irq_mask, uint32_t wakeup_reason, uint32_t pending_irqs); + +#endif diff --git a/arch/arm/mach-msm/jtag-v7.S b/arch/arm/mach-msm/jtag-v7.S new file mode 100644 index 00000000000..975ddf71666 --- /dev/null +++ b/arch/arm/mach-msm/jtag-v7.S @@ -0,0 +1,123 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * JTAG support functions for ARMv7-based Qualcomm SoCs. + */ +#include +#include +#include + +#if (NR_CPUS > 2) +#error code only tested for 1 or 2 CPUs. +#endif + +/* Add 1 slot to store the register count for JTAG state */ +#define MAX_JTAG_REGS (32 + 1) +#define MAX_DEBUG_STATE_SIZE (MAX_JTAG_REGS * 4) + +ENTRY(msm_save_jtag_debug) + ldr r3, =dbg_state /* store state at dbg_state */ +#if (NR_CPUS >= 2) + mrc p15, 0, r2, c0, c0, 5 /* MPIDR */ + and r2, r2, #15 /* What CPU am I */ + ldr r1, =MAX_DEBUG_STATE_SIZE + mul r2, r2, r1 + add r3, r3, r2 +#endif + + /* save jtag state */ + ldr r1, =0xc5ACCE55 /* set DBGOSLAR lock */ + mcr p14, 0, r1, c1, c0, 4 + isb + + mrc p14, 0, r1, c1, c2, 4 /* DBGOSSRR state register count */ + cmp r1, #(MAX_JTAG_REGS) /* check for state overflow */ + movgt r1, #0 /* if not enough space, don't save */ + str r1, [r3], #4 /* save count for restore */ + +1: cmp r1, #0 + mrcne p14, 0, r2, c1, c2, 4 /* DBGOSSRR state value */ + strne r2, [r3], #4 /* push value */ + subne r1, r1, #1 + bne 1b + + mcr p14, 0, r1, c1, c0, 4 /* unlock DBGOSLAR */ + isb + + bx lr + +ENTRY(msm_restore_jtag_debug) + /* restore debug registers after power collapse */ + ldr r3, =dbg_state /* load state from dbg_state */ +#if (NR_CPUS >= 2) + mrc p15, 0, r2, c0, c0, 5 /* MPIDR */ + and r2, r2, #15 /* What CPU am I */ + ldr r1, =MAX_DEBUG_STATE_SIZE + mul r2, r2, r1 + add r3, r3, r2 +#endif + + /* restore jtag state */ + mrc p14, 0, r1, c1, c5, 4 /* clear sticky power down bit */ + isb + ldr r1, =0xc5ACCE55 /* set DBGOSLAR lock */ + mcr p14, 0, r1, c1, c0, 4 + isb + + mrc p14, 0, r1, c1, c2, 4 /* DBGOSSRR dummy read (required)*/ + ldr r1, [r3], #4 /* load saved count */ + cmp r1, #0 /* skip if none stored */ + beq msm_pm_dbg_restore_end + + /* restores debug state except DBGDSCR */ +1: ldr r2, [r3], #4 + cmp r1, #0x10 /* DBGDSCR special case */ + biceq r2, r2, #0xc000 /* DBGDSCR = DBGDSCR & ~0xc000 */ + mcr p14, 0, r2, c1, c2, 4 /* DBGOSSRR write state value */ + subs r1, r1, #1 + bne 1b + + ldr r3, =dbg_state /* load state from dbg_state */ +#if (NR_CPUS >= 2) + mrc p15, 0, r2, c0, c0, 5 /* MPIDR */ + and r2, r2, #15 /* What CPU am I */ + ldr r1, =MAX_DEBUG_STATE_SIZE + mul r2, r2, r1 + add r3, r3, r2 +#endif + ldr r1, =0xc5ACCE55 /* set DBGOSLAR lock */ + mcr p14, 0, r1, c1, c0, 4 + isb + + mrc p14, 0, r1, c1, c2, 4 /* DBGOSSRR dummy read (required)*/ + ldr r1, [r3], #4 /* load saved count */ + cmp r1, #0 /* skip if none stored */ + beq msm_pm_dbg_restore_end + + /* second pass to restore debug state including DBGDSCR */ +1: ldr r2, [r3], #4 + mcr p14, 0, r2, c1, c2, 4 /* DBGOSSRR write state value */ + subs r1,r1,#1 + bne 1b +msm_pm_dbg_restore_end: + mcr p14, 0, r1, c1, c0, 4 /* unlock DBGOSLAR */ + isb + + bx lr + + + .data + +dbg_state: + .space MAX_DEBUG_STATE_SIZE * NR_CPUS diff --git a/arch/arm/mach-msm/keypad-surf-ffa.c b/arch/arm/mach-msm/keypad-surf-ffa.c new file mode 100644 index 00000000000..a8bbafd90b3 --- /dev/null +++ b/arch/arm/mach-msm/keypad-surf-ffa.c @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + +#include + +/* don't turn this on without updating the ffa support */ +#define SCAN_FUNCTION_KEYS 0 + +/* FFA: + 36: KEYSENSE_N(0) + 37: KEYSENSE_N(1) + 38: KEYSENSE_N(2) + 39: KEYSENSE_N(3) + 40: KEYSENSE_N(4) + + 31: KYPD_17 + 32: KYPD_15 + 33: KYPD_13 + 34: KYPD_11 + 35: KYPD_9 + 41: KYPD_MEMO +*/ + +static unsigned int keypad_row_gpios[] = { + 31, 32, 33, 34, 35, 41 +#if SCAN_FUNCTION_KEYS + , 42 +#endif +}; + +static unsigned int keypad_col_gpios[] = { 36, 37, 38, 39, 40 }; + +static unsigned int keypad_row_gpios_8k_ffa[] = {31, 32, 33, 34, 35, 36}; +static unsigned int keypad_col_gpios_8k_ffa[] = {38, 39, 40, 41, 42}; + +#define KEYMAP_INDEX(row, col) ((row)*ARRAY_SIZE(keypad_col_gpios) + (col)) +#define FFA_8K_KEYMAP_INDEX(row, col) ((row)* \ + ARRAY_SIZE(keypad_col_gpios_8k_ffa) + (col)) + +static const unsigned short keypad_keymap_surf[ARRAY_SIZE(keypad_col_gpios) * + ARRAY_SIZE(keypad_row_gpios)] = { + [KEYMAP_INDEX(0, 0)] = KEY_5, + [KEYMAP_INDEX(0, 1)] = KEY_9, + [KEYMAP_INDEX(0, 2)] = 229, /* SOFT1 */ + [KEYMAP_INDEX(0, 3)] = KEY_6, + [KEYMAP_INDEX(0, 4)] = KEY_LEFT, + + [KEYMAP_INDEX(1, 0)] = KEY_0, + [KEYMAP_INDEX(1, 1)] = KEY_RIGHT, + [KEYMAP_INDEX(1, 2)] = KEY_1, + [KEYMAP_INDEX(1, 3)] = 228, /* KEY_SHARP */ + [KEYMAP_INDEX(1, 4)] = KEY_SEND, + + [KEYMAP_INDEX(2, 0)] = KEY_VOLUMEUP, + [KEYMAP_INDEX(2, 1)] = KEY_HOME, /* FA */ + [KEYMAP_INDEX(2, 2)] = KEY_F8, /* QCHT */ + [KEYMAP_INDEX(2, 3)] = KEY_F6, /* R+ */ + [KEYMAP_INDEX(2, 4)] = KEY_F7, /* R- */ + + [KEYMAP_INDEX(3, 0)] = KEY_UP, + [KEYMAP_INDEX(3, 1)] = KEY_CLEAR, + [KEYMAP_INDEX(3, 2)] = KEY_4, + [KEYMAP_INDEX(3, 3)] = KEY_MUTE, /* SPKR */ + [KEYMAP_INDEX(3, 4)] = KEY_2, + + [KEYMAP_INDEX(4, 0)] = 230, /* SOFT2 */ + [KEYMAP_INDEX(4, 1)] = 232, /* KEY_CENTER */ + [KEYMAP_INDEX(4, 2)] = KEY_DOWN, + [KEYMAP_INDEX(4, 3)] = KEY_BACK, /* FB */ + [KEYMAP_INDEX(4, 4)] = KEY_8, + + [KEYMAP_INDEX(5, 0)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(5, 1)] = 227, /* KEY_STAR */ + [KEYMAP_INDEX(5, 2)] = KEY_MAIL, /* MESG */ + [KEYMAP_INDEX(5, 3)] = KEY_3, + [KEYMAP_INDEX(5, 4)] = KEY_7, + +#if SCAN_FUNCTION_KEYS + [KEYMAP_INDEX(6, 0)] = KEY_F5, + [KEYMAP_INDEX(6, 1)] = KEY_F4, + [KEYMAP_INDEX(6, 2)] = KEY_F3, + [KEYMAP_INDEX(6, 3)] = KEY_F2, + [KEYMAP_INDEX(6, 4)] = KEY_F1 +#endif +}; + +static const unsigned short keypad_keymap_ffa[ARRAY_SIZE(keypad_col_gpios) * + ARRAY_SIZE(keypad_row_gpios)] = { + /*[KEYMAP_INDEX(0, 0)] = ,*/ + /*[KEYMAP_INDEX(0, 1)] = ,*/ + [KEYMAP_INDEX(0, 2)] = KEY_1, + [KEYMAP_INDEX(0, 3)] = KEY_SEND, + [KEYMAP_INDEX(0, 4)] = KEY_LEFT, + + [KEYMAP_INDEX(1, 0)] = KEY_3, + [KEYMAP_INDEX(1, 1)] = KEY_RIGHT, + [KEYMAP_INDEX(1, 2)] = KEY_VOLUMEUP, + /*[KEYMAP_INDEX(1, 3)] = ,*/ + [KEYMAP_INDEX(1, 4)] = KEY_6, + + [KEYMAP_INDEX(2, 0)] = KEY_HOME, /* A */ + [KEYMAP_INDEX(2, 1)] = KEY_BACK, /* B */ + [KEYMAP_INDEX(2, 2)] = KEY_0, + [KEYMAP_INDEX(2, 3)] = 228, /* KEY_SHARP */ + [KEYMAP_INDEX(2, 4)] = KEY_9, + + [KEYMAP_INDEX(3, 0)] = KEY_UP, + [KEYMAP_INDEX(3, 1)] = 232, /* KEY_CENTER */ /* i */ + [KEYMAP_INDEX(3, 2)] = KEY_4, + /*[KEYMAP_INDEX(3, 3)] = ,*/ + [KEYMAP_INDEX(3, 4)] = KEY_2, + + [KEYMAP_INDEX(4, 0)] = KEY_VOLUMEDOWN, + [KEYMAP_INDEX(4, 1)] = KEY_SOUND, + [KEYMAP_INDEX(4, 2)] = KEY_DOWN, + [KEYMAP_INDEX(4, 3)] = KEY_8, + [KEYMAP_INDEX(4, 4)] = KEY_5, + + /*[KEYMAP_INDEX(5, 0)] = ,*/ + [KEYMAP_INDEX(5, 1)] = 227, /* KEY_STAR */ + [KEYMAP_INDEX(5, 2)] = 230, /*SOFT2*/ /* 2 */ + [KEYMAP_INDEX(5, 3)] = KEY_MENU, /* 1 */ + [KEYMAP_INDEX(5, 4)] = KEY_7, +}; + +#define QSD8x50_FFA_KEYMAP_SIZE (ARRAY_SIZE(keypad_col_gpios_8k_ffa) * \ + ARRAY_SIZE(keypad_row_gpios_8k_ffa)) + +static const unsigned short keypad_keymap_8k_ffa[QSD8x50_FFA_KEYMAP_SIZE] = { + + [FFA_8K_KEYMAP_INDEX(0, 0)] = KEY_VOLUMEDOWN, + /*[KEYMAP_INDEX(0, 1)] = ,*/ + [FFA_8K_KEYMAP_INDEX(0, 2)] = KEY_DOWN, + [FFA_8K_KEYMAP_INDEX(0, 3)] = KEY_8, + [FFA_8K_KEYMAP_INDEX(0, 4)] = KEY_5, + + [FFA_8K_KEYMAP_INDEX(1, 0)] = KEY_UP, + [FFA_8K_KEYMAP_INDEX(1, 1)] = KEY_CLEAR, + [FFA_8K_KEYMAP_INDEX(1, 2)] = KEY_4, + /*[KEYMAP_INDEX(1, 3)] = ,*/ + [FFA_8K_KEYMAP_INDEX(1, 4)] = KEY_2, + + [FFA_8K_KEYMAP_INDEX(2, 0)] = KEY_HOME, /* A */ + [FFA_8K_KEYMAP_INDEX(2, 1)] = KEY_BACK, /* B */ + [FFA_8K_KEYMAP_INDEX(2, 2)] = KEY_0, + [FFA_8K_KEYMAP_INDEX(2, 3)] = 228, /* KEY_SHARP */ + [FFA_8K_KEYMAP_INDEX(2, 4)] = KEY_9, + + [FFA_8K_KEYMAP_INDEX(3, 0)] = KEY_3, + [FFA_8K_KEYMAP_INDEX(3, 1)] = KEY_RIGHT, + [FFA_8K_KEYMAP_INDEX(3, 2)] = KEY_VOLUMEUP, + /*[KEYMAP_INDEX(3, 3)] = ,*/ + [FFA_8K_KEYMAP_INDEX(3, 4)] = KEY_6, + + [FFA_8K_KEYMAP_INDEX(4, 0)] = 232, /* OK */ + [FFA_8K_KEYMAP_INDEX(4, 1)] = KEY_SOUND, + [FFA_8K_KEYMAP_INDEX(4, 2)] = KEY_1, + [FFA_8K_KEYMAP_INDEX(4, 3)] = KEY_SEND, + [FFA_8K_KEYMAP_INDEX(4, 4)] = KEY_LEFT, + + /*[KEYMAP_INDEX(5, 0)] = ,*/ + [FFA_8K_KEYMAP_INDEX(5, 1)] = 227, /* KEY_STAR */ + [FFA_8K_KEYMAP_INDEX(5, 2)] = 230, /*SOFT2*/ /* 2 */ + [FFA_8K_KEYMAP_INDEX(5, 3)] = 229, /* 1 */ + [FFA_8K_KEYMAP_INDEX(5, 4)] = KEY_7, +}; + +/* SURF keypad platform device information */ +static struct gpio_event_matrix_info surf_keypad_matrix_info = { + .info.func = gpio_event_matrix_func, + .keymap = keypad_keymap_surf, + .output_gpios = keypad_row_gpios, + .input_gpios = keypad_col_gpios, + .noutputs = ARRAY_SIZE(keypad_row_gpios), + .ninputs = ARRAY_SIZE(keypad_col_gpios), + .settle_time.tv_nsec = 40 * NSEC_PER_USEC, + .poll_time.tv_nsec = 20 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_DRIVE_INACTIVE | + GPIOKPF_PRINT_UNMAPPED_KEYS +}; + +static struct gpio_event_info *surf_keypad_info[] = { + &surf_keypad_matrix_info.info +}; + +static struct gpio_event_platform_data surf_keypad_data = { + .name = "surf_keypad", + .info = surf_keypad_info, + .info_count = ARRAY_SIZE(surf_keypad_info) +}; + +struct platform_device keypad_device_surf = { + .name = GPIO_EVENT_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &surf_keypad_data, + }, +}; + +/* 8k FFA keypad platform device information */ +static struct gpio_event_matrix_info keypad_matrix_info_8k_ffa = { + .info.func = gpio_event_matrix_func, + .keymap = keypad_keymap_8k_ffa, + .output_gpios = keypad_row_gpios_8k_ffa, + .input_gpios = keypad_col_gpios_8k_ffa, + .noutputs = ARRAY_SIZE(keypad_row_gpios_8k_ffa), + .ninputs = ARRAY_SIZE(keypad_col_gpios_8k_ffa), + .settle_time.tv_nsec = 40 * NSEC_PER_USEC, + .poll_time.tv_nsec = 20 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_DRIVE_INACTIVE | + GPIOKPF_PRINT_UNMAPPED_KEYS +}; + +static struct gpio_event_info *keypad_info_8k_ffa[] = { + &keypad_matrix_info_8k_ffa.info +}; + +static struct gpio_event_platform_data keypad_data_8k_ffa = { + .name = "8k_ffa_keypad", + .info = keypad_info_8k_ffa, + .info_count = ARRAY_SIZE(keypad_info_8k_ffa) +}; + +struct platform_device keypad_device_8k_ffa = { + .name = GPIO_EVENT_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &keypad_data_8k_ffa, + }, +}; + +/* 7k FFA keypad platform device information */ +static struct gpio_event_matrix_info keypad_matrix_info_7k_ffa = { + .info.func = gpio_event_matrix_func, + .keymap = keypad_keymap_ffa, + .output_gpios = keypad_row_gpios, + .input_gpios = keypad_col_gpios, + .noutputs = ARRAY_SIZE(keypad_row_gpios), + .ninputs = ARRAY_SIZE(keypad_col_gpios), + .settle_time.tv_nsec = 40 * NSEC_PER_USEC, + .poll_time.tv_nsec = 20 * NSEC_PER_MSEC, + .flags = GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_DRIVE_INACTIVE | + GPIOKPF_PRINT_UNMAPPED_KEYS +}; + +static struct gpio_event_info *keypad_info_7k_ffa[] = { + &keypad_matrix_info_7k_ffa.info +}; + +static struct gpio_event_platform_data keypad_data_7k_ffa = { + .name = "7k_ffa_keypad", + .info = keypad_info_7k_ffa, + .info_count = ARRAY_SIZE(keypad_info_7k_ffa) +}; + +struct platform_device keypad_device_7k_ffa = { + .name = GPIO_EVENT_DEV_NAME, + .id = -1, + .dev = { + .platform_data = &keypad_data_7k_ffa, + }, +}; + diff --git a/arch/arm/mach-msm/mdm.c b/arch/arm/mach-msm/mdm.c new file mode 100644 index 00000000000..47e88eec96c --- /dev/null +++ b/arch/arm/mach-msm/mdm.c @@ -0,0 +1,464 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_watchdog.h" +#include "devices.h" +#include "clock.h" + +#define CHARM_MODEM_TIMEOUT 6000 +#define CHARM_HOLD_TIME 4000 +#define CHARM_MODEM_DELTA 100 + +static void (*power_on_charm)(void); +static void (*power_down_charm)(void); + +static int charm_debug_on; +static int charm_status_irq; +static int charm_errfatal_irq; +static int charm_ready; +static enum charm_boot_type boot_type = CHARM_NORMAL_BOOT; +static int charm_boot_status; +static int charm_ram_dump_status; +static struct workqueue_struct *charm_queue; + +#define CHARM_DBG(...) do { if (charm_debug_on) \ + pr_info(__VA_ARGS__); \ + } while (0); + + +DECLARE_COMPLETION(charm_needs_reload); +DECLARE_COMPLETION(charm_boot); +DECLARE_COMPLETION(charm_ram_dumps); + +static void charm_disable_irqs(void) +{ + disable_irq_nosync(charm_errfatal_irq); + disable_irq_nosync(charm_status_irq); + +} + +static void charm_enable_irqs(void) +{ + enable_irq(charm_errfatal_irq); + enable_irq(charm_status_irq); +} + +static int charm_subsys_shutdown(const struct subsys_data *crashed_subsys) +{ + charm_disable_irqs(); + power_down_charm(); + charm_ready = 0; + return 0; +} + +static int charm_subsys_powerup(const struct subsys_data *crashed_subsys) +{ + power_on_charm(); + boot_type = CHARM_NORMAL_BOOT; + complete(&charm_needs_reload); + wait_for_completion(&charm_boot); + pr_info("%s: charm modem has been restarted\n", __func__); + INIT_COMPLETION(charm_boot); + charm_enable_irqs(); + return charm_boot_status; +} + +static int charm_subsys_ramdumps(int want_dumps, + const struct subsys_data *crashed_subsys) +{ + charm_ram_dump_status = 0; + if (want_dumps) { + boot_type = CHARM_RAM_DUMPS; + complete(&charm_needs_reload); + wait_for_completion(&charm_ram_dumps); + INIT_COMPLETION(charm_ram_dumps); + power_down_charm(); + } + return charm_ram_dump_status; +} + +static struct subsys_data charm_subsystem = { + .shutdown = charm_subsys_shutdown, + .ramdump = charm_subsys_ramdumps, + .powerup = charm_subsys_powerup, + .name = "external_modem", +}; + +static int charm_panic_prep(struct notifier_block *this, + unsigned long event, void *ptr) +{ + CHARM_DBG("%s: setting AP2MDM_ERRFATAL high for a non graceful reset\n", + __func__); + charm_disable_irqs(); + gpio_set_value(AP2MDM_ERRFATAL, 1); + return NOTIFY_DONE; +} + +static struct notifier_block charm_panic_blk = { + .notifier_call = charm_panic_prep, +}; + + +static long charm_modem_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + + int status, ret = 0; + + if (_IOC_TYPE(cmd) != CHARM_CODE) { + pr_err("%s: invalid ioctl code\n", __func__); + return -EINVAL; + } + + CHARM_DBG("%s: Entering ioctl cmd = %d\n", __func__, _IOC_NR(cmd)); + switch (cmd) { + case WAKE_CHARM: + CHARM_DBG("%s: Powering on\n", __func__); + power_on_charm(); + break; + case CHECK_FOR_BOOT: + if (gpio_get_value(MDM2AP_STATUS) == 0) + put_user(1, (unsigned long __user *) arg); + else + put_user(0, (unsigned long __user *) arg); + break; + case NORMAL_BOOT_DONE: + CHARM_DBG("%s: check if charm is booted up\n", __func__); + get_user(status, (unsigned long __user *) arg); + if (status) + charm_boot_status = -EIO; + else + charm_boot_status = 0; + complete(&charm_boot); + break; + case RAM_DUMP_DONE: + CHARM_DBG("%s: charm done collecting RAM dumps\n", __func__); + get_user(status, (unsigned long __user *) arg); + if (status) + charm_ram_dump_status = -EIO; + else + charm_ram_dump_status = 0; + complete(&charm_ram_dumps); + break; + case WAIT_FOR_RESTART: + CHARM_DBG("%s: wait for charm to need images reloaded\n", + __func__); + ret = wait_for_completion_interruptible(&charm_needs_reload); + if (!ret) + put_user(boot_type, (unsigned long __user *) arg); + INIT_COMPLETION(charm_needs_reload); + break; + default: + pr_err("%s: invalid ioctl cmd = %d\n", __func__, _IOC_NR(cmd)); + ret = -EINVAL; + break; + } + + return ret; +} + +static int charm_modem_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations charm_modem_fops = { + .owner = THIS_MODULE, + .open = charm_modem_open, + .unlocked_ioctl = charm_modem_ioctl, +}; + + +struct miscdevice charm_modem_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mdm", + .fops = &charm_modem_fops +}; + + + +static void charm_status_fn(struct work_struct *work) +{ + pr_info("Reseting the charm because status changed\n"); + subsystem_restart("external_modem"); +} + +static DECLARE_WORK(charm_status_work, charm_status_fn); + +static void charm_fatal_fn(struct work_struct *work) +{ + pr_info("Reseting the charm due to an errfatal\n"); + subsystem_restart("external_modem"); +} + +static DECLARE_WORK(charm_fatal_work, charm_fatal_fn); + +static irqreturn_t charm_errfatal(int irq, void *dev_id) +{ + CHARM_DBG("%s: charm got errfatal interrupt\n", __func__); + if (charm_ready) { + CHARM_DBG("%s: scheduling work now\n", __func__); + queue_work(charm_queue, &charm_fatal_work); + } + return IRQ_HANDLED; +} + +static irqreturn_t charm_status_change(int irq, void *dev_id) +{ + CHARM_DBG("%s: charm sent status change interrupt\n", __func__); + if ((gpio_get_value(MDM2AP_STATUS) == 0) && charm_ready) { + CHARM_DBG("%s: scheduling work now\n", __func__); + queue_work(charm_queue, &charm_status_work); + } else if (gpio_get_value(MDM2AP_STATUS) == 1) { + CHARM_DBG("%s: charm is now ready\n", __func__); + charm_ready = 1; + } + return IRQ_HANDLED; +} + +static int charm_debug_on_set(void *data, u64 val) +{ + charm_debug_on = val; + return 0; +} + +static int charm_debug_on_get(void *data, u64 *val) +{ + *val = charm_debug_on; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(charm_debug_on_fops, + charm_debug_on_get, + charm_debug_on_set, "%llu\n"); + +static int charm_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("charm_dbg", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debugfs_create_file("debug_on", 0644, dent, NULL, + &charm_debug_on_fops); + return 0; +} + +static int gsbi9_uart_notifier_cb(struct notifier_block *this, + unsigned long code, void *_cmd) +{ + switch (code) { + case SUBSYS_AFTER_SHUTDOWN: + platform_device_unregister(msm_device_uart_gsbi9); + msm_device_uart_gsbi9 = msm_add_gsbi9_uart(); + if (IS_ERR(msm_device_uart_gsbi9)) + pr_err("%s(): Failed to create uart gsbi9 device\n", + __func__); + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block gsbi9_nb = { + .notifier_call = gsbi9_uart_notifier_cb, +}; + +static int __init charm_modem_probe(struct platform_device *pdev) +{ + int ret, irq; + struct charm_platform_data *d = pdev->dev.platform_data; + + gpio_request(AP2MDM_STATUS, "AP2MDM_STATUS"); + gpio_request(AP2MDM_ERRFATAL, "AP2MDM_ERRFATAL"); + gpio_request(AP2MDM_KPDPWR_N, "AP2MDM_KPDPWR_N"); + gpio_request(AP2MDM_PMIC_RESET_N, "AP2MDM_PMIC_RESET_N"); + gpio_request(MDM2AP_STATUS, "MDM2AP_STATUS"); + gpio_request(MDM2AP_ERRFATAL, "MDM2AP_ERRFATAL"); + + gpio_direction_output(AP2MDM_STATUS, 1); + gpio_direction_output(AP2MDM_ERRFATAL, 0); + gpio_direction_input(MDM2AP_STATUS); + gpio_direction_input(MDM2AP_ERRFATAL); + + power_on_charm = d->charm_modem_on; + power_down_charm = d->charm_modem_off; + + charm_queue = create_singlethread_workqueue("charm_queue"); + if (!charm_queue) { + pr_err("%s: could not create workqueue. All charm \ + functionality will be disabled\n", + __func__); + ret = -ENOMEM; + goto fatal_err; + } + + atomic_notifier_chain_register(&panic_notifier_list, &charm_panic_blk); + charm_debugfs_init(); + + ssr_register_subsystem(&charm_subsystem); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + pr_err("%s: could not get MDM2AP_ERRFATAL IRQ resource. \ + error=%d No IRQ will be generated on errfatal.", + __func__, irq); + goto errfatal_err; + } + + ret = request_irq(irq, charm_errfatal, + IRQF_TRIGGER_RISING , "charm errfatal", NULL); + + if (ret < 0) { + pr_err("%s: MDM2AP_ERRFATAL IRQ#%d request failed with error=%d\ + . No IRQ will be generated on errfatal.", + __func__, irq, ret); + goto errfatal_err; + } + charm_errfatal_irq = irq; + +errfatal_err: + + irq = platform_get_irq(pdev, 1); + if (irq < 0) { + pr_err("%s: could not get MDM2AP_STATUS IRQ resource. \ + error=%d No IRQ will be generated on status change.", + __func__, irq); + goto status_err; + } + + ret = request_threaded_irq(irq, NULL, charm_status_change, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "charm status", NULL); + + if (ret < 0) { + pr_err("%s: MDM2AP_STATUS IRQ#%d request failed with error=%d\ + . No IRQ will be generated on status change.", + __func__, irq, ret); + goto status_err; + } + charm_status_irq = irq; + +status_err: + subsys_notif_register_notifier("external_modem", &gsbi9_nb); + + pr_info("%s: Registering charm modem\n", __func__); + + return misc_register(&charm_modem_misc); + +fatal_err: + gpio_free(AP2MDM_STATUS); + gpio_free(AP2MDM_ERRFATAL); + gpio_free(AP2MDM_KPDPWR_N); + gpio_free(AP2MDM_PMIC_RESET_N); + gpio_free(MDM2AP_STATUS); + gpio_free(MDM2AP_ERRFATAL); + return ret; + +} + + +static int __devexit charm_modem_remove(struct platform_device *pdev) +{ + gpio_free(AP2MDM_STATUS); + gpio_free(AP2MDM_ERRFATAL); + gpio_free(AP2MDM_KPDPWR_N); + gpio_free(AP2MDM_PMIC_RESET_N); + gpio_free(MDM2AP_STATUS); + gpio_free(MDM2AP_ERRFATAL); + + return misc_deregister(&charm_modem_misc); +} + +static void charm_modem_shutdown(struct platform_device *pdev) +{ + int i; + + CHARM_DBG("%s: setting AP2MDM_STATUS low for a graceful restart\n", + __func__); + + charm_disable_irqs(); + + gpio_set_value(AP2MDM_STATUS, 0); + + for (i = CHARM_MODEM_TIMEOUT; i > 0; i -= CHARM_MODEM_DELTA) { + pet_watchdog(); + msleep(CHARM_MODEM_DELTA); + if (gpio_get_value(MDM2AP_STATUS) == 0) + break; + } + + if (i <= 0) { + pr_err("%s: MDM2AP_STATUS never went low.\n", + __func__); + gpio_direction_output(AP2MDM_PMIC_RESET_N, 1); + for (i = CHARM_HOLD_TIME; i > 0; i -= CHARM_MODEM_DELTA) { + pet_watchdog(); + msleep(CHARM_MODEM_DELTA); + } + gpio_direction_output(AP2MDM_PMIC_RESET_N, 0); + } +} + +static struct platform_driver charm_modem_driver = { + .remove = charm_modem_remove, + .shutdown = charm_modem_shutdown, + .driver = { + .name = "charm_modem", + .owner = THIS_MODULE + }, +}; + +static int __init charm_modem_init(void) +{ + return platform_driver_probe(&charm_modem_driver, charm_modem_probe); +} + +static void __exit charm_modem_exit(void) +{ + platform_driver_unregister(&charm_modem_driver); +} + +module_init(charm_modem_init); +module_exit(charm_modem_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("msm8660 charm modem driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("charm_modem"); diff --git a/arch/arm/mach-msm/memory.c b/arch/arm/mach-msm/memory.c new file mode 100644 index 00000000000..c38071ca3ca --- /dev/null +++ b/arch/arm/mach-msm/memory.c @@ -0,0 +1,403 @@ +/* arch/arm/mach-msm/memory.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_MSM_NPA_REMOTE) +#include "npa_remote.h" +#include +#include +#endif +#include +#include +#include +#include <../../mm/mm.h> + +int arch_io_remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, + unsigned long pfn, unsigned long size, pgprot_t prot) +{ + unsigned long pfn_addr = pfn << PAGE_SHIFT; + if ((pfn_addr >= 0x88000000) && (pfn_addr < 0xD0000000)) { + prot = pgprot_device(prot); + pr_debug("remapping device %lx\n", prot); + } + return remap_pfn_range(vma, addr, pfn, size, prot); +} + +void *strongly_ordered_page; +char strongly_ordered_mem[PAGE_SIZE*2-4]; + +/* + * The trick of making the zero page strongly ordered no longer + * works. We no longer want to make a second alias to the zero + * page that is strongly ordered. Manually changing the bits + * in the page table for the zero page would have side effects + * elsewhere that aren't necessary. The result is that we need + * to get a page from else where. Given when the first call + * to write_to_strongly_ordered_memory occurs, using bootmem + * to get a page makes the most sense. + */ +void map_page_strongly_ordered(void) +{ +#if defined(CONFIG_ARCH_MSM7X27) && !defined(CONFIG_ARCH_MSM7X27A) + long unsigned int phys; + struct map_desc map; + + if (strongly_ordered_page) + return; + + strongly_ordered_page = (void*)PFN_ALIGN((int)&strongly_ordered_mem); + phys = __pa(strongly_ordered_page); + + map.pfn = __phys_to_pfn(phys); + map.virtual = MSM_STRONGLY_ORDERED_PAGE; + map.length = PAGE_SIZE; + map.type = MT_DEVICE_STRONGLY_ORDERED; + create_mapping(&map); + + printk(KERN_ALERT "Initialized strongly ordered page successfully\n"); +#endif +} +EXPORT_SYMBOL(map_page_strongly_ordered); + +void write_to_strongly_ordered_memory(void) +{ +#if defined(CONFIG_ARCH_MSM7X27) && !defined(CONFIG_ARCH_MSM7X27A) + if (!strongly_ordered_page) { + if (!in_interrupt()) + map_page_strongly_ordered(); + else { + printk(KERN_ALERT "Cannot map strongly ordered page in " + "Interrupt Context\n"); + /* capture it here before the allocation fails later */ + BUG(); + } + } + *(int *)MSM_STRONGLY_ORDERED_PAGE = 0; +#endif +} +EXPORT_SYMBOL(write_to_strongly_ordered_memory); + +void flush_axi_bus_buffer(void) +{ +#if defined(CONFIG_ARCH_MSM7X27) && !defined(CONFIG_ARCH_MSM7X27A) + __asm__ __volatile__ ("mcr p15, 0, %0, c7, c10, 5" \ + : : "r" (0) : "memory"); + write_to_strongly_ordered_memory(); +#endif +} + +#define CACHE_LINE_SIZE 32 + +/* These cache related routines make the assumption that the associated + * physical memory is contiguous. They will operate on all (L1 + * and L2 if present) caches. + */ +void clean_and_invalidate_caches(unsigned long vstart, + unsigned long length, unsigned long pstart) +{ + unsigned long vaddr; + + for (vaddr = vstart; vaddr < vstart + length; vaddr += CACHE_LINE_SIZE) + asm ("mcr p15, 0, %0, c7, c14, 1" : : "r" (vaddr)); +#ifdef CONFIG_OUTER_CACHE + outer_flush_range(pstart, pstart + length); +#endif + asm ("mcr p15, 0, %0, c7, c10, 4" : : "r" (0)); + asm ("mcr p15, 0, %0, c7, c5, 0" : : "r" (0)); + + flush_axi_bus_buffer(); +} + +void clean_caches(unsigned long vstart, + unsigned long length, unsigned long pstart) +{ + unsigned long vaddr; + + for (vaddr = vstart; vaddr < vstart + length; vaddr += CACHE_LINE_SIZE) + asm ("mcr p15, 0, %0, c7, c10, 1" : : "r" (vaddr)); +#ifdef CONFIG_OUTER_CACHE + outer_clean_range(pstart, pstart + length); +#endif + asm ("mcr p15, 0, %0, c7, c10, 4" : : "r" (0)); + asm ("mcr p15, 0, %0, c7, c5, 0" : : "r" (0)); + + flush_axi_bus_buffer(); +} + +void invalidate_caches(unsigned long vstart, + unsigned long length, unsigned long pstart) +{ + unsigned long vaddr; + + for (vaddr = vstart; vaddr < vstart + length; vaddr += CACHE_LINE_SIZE) + asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (vaddr)); +#ifdef CONFIG_OUTER_CACHE + outer_inv_range(pstart, pstart + length); +#endif + asm ("mcr p15, 0, %0, c7, c10, 4" : : "r" (0)); + asm ("mcr p15, 0, %0, c7, c5, 0" : : "r" (0)); + + flush_axi_bus_buffer(); +} + +void *alloc_bootmem_aligned(unsigned long size, unsigned long alignment) +{ + void *unused_addr = NULL; + unsigned long addr, tmp_size, unused_size; + + /* Allocate maximum size needed, see where it ends up. + * Then free it -- in this path there are no other allocators + * so we can depend on getting the same address back + * when we allocate a smaller piece that is aligned + * at the end (if necessary) and the piece we really want, + * then free the unused first piece. + */ + + tmp_size = size + alignment - PAGE_SIZE; + addr = (unsigned long)alloc_bootmem(tmp_size); + free_bootmem(__pa(addr), tmp_size); + + unused_size = alignment - (addr % alignment); + if (unused_size) + unused_addr = alloc_bootmem(unused_size); + + addr = (unsigned long)alloc_bootmem(size); + if (unused_size) + free_bootmem(__pa(unused_addr), unused_size); + + return (void *)addr; +} + +int platform_physical_remove_pages(unsigned long start_pfn, + unsigned long nr_pages) +{ + return 1; +} + +int platform_physical_active_pages(unsigned long start_pfn, + unsigned long nr_pages) +{ + return 1; +} + +int platform_physical_low_power_pages(unsigned long start_pfn, + unsigned long nr_pages) +{ + return 1; +} + + +char *memtype_name[] = { + "SMI_KERNEL", + "SMI", + "EBI0", + "EBI1" +}; + +struct reserve_info *reserve_info; + +static void __init calculate_reserve_limits(void) +{ + int i; + struct membank *mb; + int memtype; + struct memtype_reserve *mt; + + for (i = 0, mb = &meminfo.bank[0]; i < meminfo.nr_banks; i++, mb++) { + memtype = reserve_info->paddr_to_memtype(mb->start); + if (memtype == MEMTYPE_NONE) { + pr_warning("unknown memory type for bank at %lx\n", + (long unsigned int)mb->start); + continue; + } + mt = &reserve_info->memtype_reserve_table[memtype]; + mt->limit = max(mt->limit, mb->size); + } +} + +static void __init adjust_reserve_sizes(void) +{ + int i; + struct memtype_reserve *mt; + + mt = &reserve_info->memtype_reserve_table[0]; + for (i = 0; i < MEMTYPE_MAX; i++, mt++) { + if (mt->flags & MEMTYPE_FLAGS_1M_ALIGN) + mt->size = (mt->size + SECTION_SIZE - 1) & SECTION_MASK; + if (mt->size > mt->limit) { + pr_warning("%lx size for %s too large, setting to %lx\n", + mt->size, memtype_name[i], mt->limit); + mt->size = mt->limit; + } + } +} + +static void __init reserve_memory_for_mempools(void) +{ + int i, memtype, membank_type; + struct memtype_reserve *mt; + struct membank *mb; + int ret; + + mt = &reserve_info->memtype_reserve_table[0]; + for (memtype = 0; memtype < MEMTYPE_MAX; memtype++, mt++) { + if (mt->flags & MEMTYPE_FLAGS_FIXED || !mt->size) + continue; + + /* We know we will find a memory bank of the proper size + * as we have limited the size of the memory pool for + * each memory type to the size of the largest memory + * bank. Choose the memory bank with the highest physical + * address which is large enough, so that we will not + * take memory from the lowest memory bank which the kernel + * is in (and cause boot problems) and so that we might + * be able to steal memory that would otherwise become + * highmem. + */ + for (i = meminfo.nr_banks - 1; i >= 0; i--) { + mb = &meminfo.bank[i]; + membank_type = + reserve_info->paddr_to_memtype(mb->start); + if (memtype != membank_type) + continue; + if (mb->size >= mt->size) { + mt->start = mb->start + mb->size - mt->size; + ret = memblock_remove(mt->start, mt->size); + BUG_ON(ret); + break; + } + } + } +} + +static void __init initialize_mempools(void) +{ + struct mem_pool *mpool; + int memtype; + struct memtype_reserve *mt; + + mt = &reserve_info->memtype_reserve_table[0]; + for (memtype = 0; memtype < MEMTYPE_MAX; memtype++, mt++) { + if (!mt->size) + continue; + mpool = initialize_memory_pool(mt->start, mt->size, memtype); + if (!mpool) + pr_warning("failed to create %s mempool\n", + memtype_name[memtype]); + } +} + +void __init msm_reserve(void) +{ + memory_pool_init(); + reserve_info->calculate_reserve_sizes(); + calculate_reserve_limits(); + adjust_reserve_sizes(); + reserve_memory_for_mempools(); + initialize_mempools(); +} + +static int get_ebi_memtype(void) +{ + /* on 7x30 and 8x55 "EBI1 kernel PMEM" is really on EBI0 */ + if (cpu_is_msm7x30() || cpu_is_msm8x55()) + return MEMTYPE_EBI0; + return MEMTYPE_EBI1; +} + +void *allocate_contiguous_ebi(unsigned long size, + unsigned long align, int cached) +{ + return allocate_contiguous_memory(size, get_ebi_memtype(), + align, cached); +} +EXPORT_SYMBOL(allocate_contiguous_ebi); + +unsigned long allocate_contiguous_ebi_nomap(unsigned long size, + unsigned long align) +{ + return allocate_contiguous_memory_nomap(size, get_ebi_memtype(), align); +} +EXPORT_SYMBOL(allocate_contiguous_ebi_nomap); + +/* emulation of the deprecated pmem_kalloc and pmem_kfree */ +int32_t pmem_kalloc(const size_t size, const uint32_t flags) +{ + int pmem_memtype; + int memtype = MEMTYPE_NONE; + int ebi1_memtype = MEMTYPE_EBI1; + unsigned int align; + int32_t paddr; + + switch (flags & PMEM_ALIGNMENT_MASK) { + case PMEM_ALIGNMENT_4K: + align = SZ_4K; + break; + case PMEM_ALIGNMENT_1M: + align = SZ_1M; + break; + default: + pr_alert("Invalid alignment %x\n", + (flags & PMEM_ALIGNMENT_MASK)); + return -EINVAL; + } + + /* on 7x30 and 8x55 "EBI1 kernel PMEM" is really on EBI0 */ + if (cpu_is_msm7x30() || cpu_is_msm8x55()) + ebi1_memtype = MEMTYPE_EBI0; + + pmem_memtype = flags & PMEM_MEMTYPE_MASK; + if (pmem_memtype == PMEM_MEMTYPE_EBI1) + memtype = ebi1_memtype; + else if (pmem_memtype == PMEM_MEMTYPE_SMI) + memtype = MEMTYPE_SMI_KERNEL; + else { + pr_alert("Invalid memory type %x\n", + flags & PMEM_MEMTYPE_MASK); + return -EINVAL; + } + + paddr = allocate_contiguous_memory_nomap(size, memtype, align); + if (!paddr && pmem_memtype == PMEM_MEMTYPE_SMI) + paddr = allocate_contiguous_memory_nomap(size, + ebi1_memtype, align); + + if (!paddr) + return -ENOMEM; + return paddr; +} +EXPORT_SYMBOL(pmem_kalloc); + +int pmem_kfree(const int32_t physaddr) +{ + free_contiguous_memory_by_paddr(physaddr); + + return 0; +} +EXPORT_SYMBOL(pmem_kfree); diff --git a/arch/arm/mach-msm/memory_topology.c b/arch/arm/mach-msm/memory_topology.c new file mode 100644 index 00000000000..866907c7cc9 --- /dev/null +++ b/arch/arm/mach-msm/memory_topology.c @@ -0,0 +1,35 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include + +/* Return the number of chipselects populated with a memory bank */ +/* This is 7x30 only and will be extended in the future */ + +unsigned int get_num_populated_chipselects() +{ + /* Currently, Linux cannot determine the memory toplogy of a target */ + /* This is a kludge until all this info is figured out from smem */ + + /* There is atleast one chipselect populated for hosting the 1st bank */ + unsigned int num_chipselects = 1; + int i; + for (i = 0; i < meminfo.nr_banks; i++) { + struct membank *bank = &meminfo.bank[i]; + if (bank->start == EBI1_PHYS_OFFSET) + num_chipselects++; + } + return num_chipselects; +} + diff --git a/arch/arm/mach-msm/mkrpcsym.pl b/arch/arm/mach-msm/mkrpcsym.pl new file mode 100644 index 00000000000..f4abb5fc36c --- /dev/null +++ b/arch/arm/mach-msm/mkrpcsym.pl @@ -0,0 +1,162 @@ +#!/usr/bin/perl +# +# Generate the smd_rpc_sym.c symbol file for ONCRPC SMEM Logging +# +# Copyright (c) 2009, Code Aurora Forum. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Code Aurora Forum, Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use strict; +use POSIX; + +my $base_fn = "smd_rpc_sym"; +my %prog_table; +my ($in, $out) = @ARGV; +my $max_table_size = 1024; + +my $header = <<"EOF"; +/* Autogenerated by mkrpcsym.pl. Do not edit */ +EOF + +sub smd_rpc_gen_files() { + my $c_fp; + my $h_fp; + my @table; + my $tbl_index; + my $num_undefined=0; + + # Process the input hash table into an array + # Any duplicate items will be combined, missing items will + # become "UNKNOWN" We end-up with a fully-qualified table + # from 0 to n. + + $prog_table{"UNDEFINED"}{'name'}="UNDEFINED"; + $prog_table{"UNDEFINED"}{'prog'}=-1; + my $hex_num = 0xFFFF; + foreach my $api_prog (sort {$a cmp $b} keys %prog_table ) { + $tbl_index = hex($api_prog) & hex("0000FFFF"); + if($prog_table{$api_prog}{'prog'} >= 0) { + if($tbl_index < $max_table_size) { + $table[$tbl_index]=$prog_table{$api_prog}; + } else { + print "Skipping table item $tbl_index, larger ", + "than max:$max_table_size \n"; + } + } + } + for (my $i=0; $i<=$#table; $i++) { + if (!exists $table[$i]) { + $table[$i]=$prog_table{"UNDEFINED"}; + $num_undefined++; + } + } + + + open($c_fp, ">", $out) or die $!; + print $c_fp $header; + print $c_fp "\n\n\n"; + print $c_fp <<"EOF"; +#include +#include +#include +#include + +struct sym { + const char *str; +}; + +EOF + +# Each API is named starts with "CB " to allow both the forward and +# callback names of the API to be returned from a common database. +# By convention, program names starting with 0x30 are forward APIS, +# API names starting with 0x31 are callback apis. + print $c_fp "const char *smd_rpc_syms[] = {\n"; + + for (my $i=0; $i<= $#table; $i++) { + my $l = length($table[$i]{'name'}); + my $t = floor((45 - $l - 4)/8); + print $c_fp "\t\"CB ".uc($table[$i]{'name'})."\","; + if($table[$i]{'name'} ne "UNDEFINED") { + for (my $i=0;$i<$t;$i++) { + print $c_fp "\t"; + } + print $c_fp "/*".$table[$i]{'prog'}."*/\n"; + } else { + print $c_fp "\n"; + } + } + + print $c_fp "};\n"; + print $c_fp <<"EOF"; + +static struct sym_tbl { + const char **data; + int size; +} tbl = { smd_rpc_syms, ARRAY_SIZE(smd_rpc_syms)}; + +const char *smd_rpc_get_sym(uint32_t val) +{ + int idx = val & 0xFFFF; + if (idx < tbl.size) { + if (val & 0x01000000) + return tbl.data[idx]; + else + return tbl.data[idx] + 3; + } + return 0; +} +EXPORT_SYMBOL(smd_rpc_get_sym); + +EOF + close $c_fp; +} + +sub read_smd_rpc_table() { + my $fp; + my $line; + open($fp, "<", $in) or die "$! File:$in"; + while ($line = <$fp>) { + chomp($line); + if($line =~ /([^\s]+)\s+([\w]+)$/) { + if(defined $prog_table{$1}) { + print "Error entry already defined $1,", + " in $prog_table{$1}{name} \n"; + } else { + $prog_table{$1}{'name'}=$2; + $prog_table{$1}{'prog'}=$1; + } + } else { + if($line =~ /\w/) { + print "Error parsing error >>$line<< \n"; + } + } + } + close $fp; +} + +read_smd_rpc_table(); +smd_rpc_gen_files(); diff --git a/arch/arm/mach-msm/modem-8960.c b/arch/arm/mach-msm/modem-8960.c new file mode 100644 index 00000000000..907a1cf60ab --- /dev/null +++ b/arch/arm/mach-msm/modem-8960.c @@ -0,0 +1,227 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "smd_private.h" +#include "modem_notifier.h" +#include "ramdump.h" + +static int crash_shutdown; + +static void modem_sw_fatal_fn(struct work_struct *work) +{ + uint32_t panic_smsm_states = SMSM_RESET | SMSM_SYSTEM_DOWNLOAD; + uint32_t reset_smsm_states = SMSM_SYSTEM_REBOOT_USR | + SMSM_SYSTEM_PWRDWN_USR; + uint32_t modem_state; + + pr_err("Watchdog bite received from modem SW!\n"); + + modem_state = smsm_get_state(SMSM_MODEM_STATE); + + if (modem_state & panic_smsm_states) { + + pr_err("Modem SMSM state changed to SMSM_RESET.\n" + "Probable err_fatal on the modem. " + "Calling subsystem restart...\n"); + subsystem_restart("modem"); + + } else if (modem_state & reset_smsm_states) { + + pr_err("%s: User-invoked system reset/powerdown. " + "Resetting the SoC now.\n", + __func__); + kernel_restart(NULL); + } else { + /* TODO: Bus unlock code/sequence goes _here_ */ + subsystem_restart("modem"); + } +} + +static void modem_fw_fatal_fn(struct work_struct *work) +{ + pr_err("Watchdog bite received from modem FW!\n"); + subsystem_restart("modem"); +} + +static DECLARE_WORK(modem_sw_fatal_work, modem_sw_fatal_fn); +static DECLARE_WORK(modem_fw_fatal_work, modem_fw_fatal_fn); + +static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state) +{ + /* Ignore if we're the one that set SMSM_RESET */ + if (crash_shutdown) + return; + + if (new_state & SMSM_RESET) { + pr_err("Modem SMSM state changed to SMSM_RESET.\n" + "Probable err_fatal on the modem. " + "Calling subsystem restart...\n"); + subsystem_restart("modem"); + } +} + +static int modem_shutdown(const struct subsys_data *subsys) +{ + /* TODO: Call into PIL to shutdown the modem */ + return 0; +} + +static int modem_powerup(const struct subsys_data *subsys) +{ + /* TODO: Call into PIL to powerup the modem */ + return 0; +} + +void modem_crash_shutdown(const struct subsys_data *subsys) +{ + crash_shutdown = 1; + smsm_reset_modem(SMSM_RESET); +} + +int modem_ramdump(int enable, const struct subsys_data *subsys) +{ + return 0; +} + +static irqreturn_t modem_wdog_bite_irq(int irq, void *dev_id) +{ + int ret; + + switch (irq) { + + case Q6SW_WDOG_EXPIRED_IRQ: + ret = schedule_work(&modem_sw_fatal_work); + disable_irq_nosync(Q6SW_WDOG_EXPIRED_IRQ); + break; + case Q6FW_WDOG_EXPIRED_IRQ: + ret = schedule_work(&modem_fw_fatal_work); + disable_irq_nosync(Q6FW_WDOG_EXPIRED_IRQ); + break; + break; + + default: + pr_err("%s: Unknown IRQ!\n", __func__); + } + + return IRQ_HANDLED; +} + +static struct subsys_data modem_8960 = { + .name = "modem", + .shutdown = modem_shutdown, + .powerup = modem_powerup, + .ramdump = modem_ramdump, + .crash_shutdown = modem_crash_shutdown +}; + +static int modem_subsystem_restart_init(void) +{ + return ssr_register_subsystem(&modem_8960); +} + +static int modem_debug_set(void *data, u64 val) +{ + if (val == 1) { + pr_info("%s: Intentionally setting the SMSM_RESET bit.\n", + __func__); + smsm_reset_modem(SMSM_RESET); + } + + return 0; +} + +static int modem_debug_get(void *data, u64 *val) +{ + *val = 0; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(modem_debug_fops, modem_debug_get, modem_debug_set, + "%llu\n"); + +static int modem_debugfs_init(void) +{ + struct dentry *dent; + dent = debugfs_create_dir("modem_debug", 0); + + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debugfs_create_file("reset_modem", 0644, dent, NULL, + &modem_debug_fops); + return 0; +} + +static int __init modem_8960_init(void) +{ + int ret; + + ret = smsm_state_cb_register(SMSM_MODEM_STATE, SMSM_RESET, + smsm_state_cb, 0); + + if (ret < 0) + pr_err("%s: Unable to register SMSM callback! (%d)\n", + __func__, ret); + + ret = request_irq(Q6FW_WDOG_EXPIRED_IRQ, modem_wdog_bite_irq, + IRQF_TRIGGER_RISING, "modem_wdog_fw", NULL); + + if (ret < 0) { + pr_err("%s: Unable to request q6fw watchdog IRQ. (%d)\n", + __func__, ret); + goto out; + } + + ret = request_irq(Q6SW_WDOG_EXPIRED_IRQ, modem_wdog_bite_irq, + IRQF_TRIGGER_RISING, "modem_wdog_sw", NULL); + + if (ret < 0) { + pr_err("%s: Unable to request q6sw watchdog IRQ. (%d)\n", + __func__, ret); + disable_irq_nosync(Q6FW_WDOG_EXPIRED_IRQ); + goto out; + } + + ret = modem_subsystem_restart_init(); + + if (ret < 0) { + pr_err("%s: Unable to reg with subsystem restart. (%d)\n", + __func__, ret); + goto out; + } + + ret = modem_debugfs_init(); + + pr_info("%s: 8960 modem fatal driver init'ed.\n", __func__); +out: + return ret; +} + +module_init(modem_8960_init); diff --git a/arch/arm/mach-msm/modem_notifier.c b/arch/arm/mach-msm/modem_notifier.c new file mode 100644 index 00000000000..d92098b6948 --- /dev/null +++ b/arch/arm/mach-msm/modem_notifier.c @@ -0,0 +1,213 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Modem Restart Notifier -- Provides notification + * of modem restart events. + */ + +#include +#include +#include +#include +#include + +#include "modem_notifier.h" + +#define DEBUG + +static struct srcu_notifier_head modem_notifier_list; +static struct workqueue_struct *modem_notifier_wq; + +static void notify_work_smsm_init(struct work_struct *work) +{ + modem_notify(0, MODEM_NOTIFIER_SMSM_INIT); +} +static DECLARE_WORK(modem_notifier_smsm_init_work, ¬ify_work_smsm_init); + +void modem_queue_smsm_init_notify(void) +{ + int ret; + + ret = queue_work(modem_notifier_wq, &modem_notifier_smsm_init_work); + + if (!ret) + printk(KERN_ERR "%s\n", __func__); +} +EXPORT_SYMBOL(modem_queue_smsm_init_notify); + +static void notify_work_start_reset(struct work_struct *work) +{ + modem_notify(0, MODEM_NOTIFIER_START_RESET); +} +static DECLARE_WORK(modem_notifier_start_reset_work, ¬ify_work_start_reset); + +void modem_queue_start_reset_notify(void) +{ + int ret; + + ret = queue_work(modem_notifier_wq, &modem_notifier_start_reset_work); + + if (!ret) + printk(KERN_ERR "%s\n", __func__); +} +EXPORT_SYMBOL(modem_queue_start_reset_notify); + +static void notify_work_end_reset(struct work_struct *work) +{ + modem_notify(0, MODEM_NOTIFIER_END_RESET); +} +static DECLARE_WORK(modem_notifier_end_reset_work, ¬ify_work_end_reset); + +void modem_queue_end_reset_notify(void) +{ + int ret; + + ret = queue_work(modem_notifier_wq, &modem_notifier_end_reset_work); + + if (!ret) + printk(KERN_ERR "%s\n", __func__); +} +EXPORT_SYMBOL(modem_queue_end_reset_notify); + +int modem_register_notifier(struct notifier_block *nb) +{ + int ret; + + ret = srcu_notifier_chain_register( + &modem_notifier_list, nb); + + return ret; +} +EXPORT_SYMBOL(modem_register_notifier); + +int modem_unregister_notifier(struct notifier_block *nb) +{ + int ret; + + ret = srcu_notifier_chain_unregister( + &modem_notifier_list, nb); + + return ret; +} +EXPORT_SYMBOL(modem_unregister_notifier); + +void modem_notify(void *data, unsigned int state) +{ + srcu_notifier_call_chain(&modem_notifier_list, state, data); +} +EXPORT_SYMBOL(modem_notify); + +#if defined(CONFIG_DEBUG_FS) +static int debug_reset_start(const char __user *buf, int count) +{ + modem_queue_start_reset_notify(); + return 0; +} + +static int debug_reset_end(const char __user *buf, int count) +{ + modem_queue_end_reset_notify(); + return 0; +} + +static ssize_t debug_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int (*fling)(const char __user *buf, int max) = file->private_data; + fling(buf, count); + return count; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations debug_ops = { + .write = debug_write, + .open = debug_open, +}; + +static void debug_create(const char *name, mode_t mode, + struct dentry *dent, + int (*fling)(const char __user *buf, int max)) +{ + debugfs_create_file(name, mode, dent, fling, &debug_ops); +} + +static void modem_notifier_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("modem_notifier", 0); + if (IS_ERR(dent)) + return; + + debug_create("reset_start", 0444, dent, debug_reset_start); + debug_create("reset_end", 0444, dent, debug_reset_end); +} +#else +static void modem_notifier_debugfs_init(void) {} +#endif + +#if defined(DEBUG) +static int modem_notifier_test_call(struct notifier_block *this, + unsigned long code, + void *_cmd) +{ + switch (code) { + case MODEM_NOTIFIER_START_RESET: + printk(KERN_ERR "Notify: start reset\n"); + break; + case MODEM_NOTIFIER_END_RESET: + printk(KERN_ERR "Notify: end reset\n"); + break; + case MODEM_NOTIFIER_SMSM_INIT: + printk(KERN_ERR "Notify: smsm init\n"); + break; + default: + printk(KERN_ERR "Notify: general\n"); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block nb = { + .notifier_call = modem_notifier_test_call, +}; + +static void register_test_notifier(void) +{ + modem_register_notifier(&nb); +} +#endif + +static int __init init_modem_notifier_list(void) +{ + srcu_init_notifier_head(&modem_notifier_list); + modem_notifier_debugfs_init(); +#if defined(DEBUG) + register_test_notifier(); +#endif + + /* Create the workqueue */ + modem_notifier_wq = create_singlethread_workqueue("modem_notifier"); + if (!modem_notifier_wq) { + srcu_cleanup_notifier_head(&modem_notifier_list); + return -ENOMEM; + } + + return 0; +} +module_init(init_modem_notifier_list); diff --git a/arch/arm/mach-msm/modem_notifier.h b/arch/arm/mach-msm/modem_notifier.h new file mode 100644 index 00000000000..1bd2d6d9580 --- /dev/null +++ b/arch/arm/mach-msm/modem_notifier.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Modem Restart Notifier API + * + */ + +#ifndef _MODEM_NOTIFIER_H +#define _MODEM_NOTIFIER_H + +#include + +#define MODEM_NOTIFIER_START_RESET 0x1 +#define MODEM_NOTIFIER_END_RESET 0x2 +#define MODEM_NOTIFIER_SMSM_INIT 0x3 + +extern int modem_register_notifier(struct notifier_block *nb); +extern int modem_unregister_notifier(struct notifier_block *nb); +extern void modem_notify(void *data, unsigned int state); +extern void modem_queue_start_reset_notify(void); +extern void modem_queue_end_reset_notify(void); +extern void modem_queue_smsm_init_notify(void); + +#endif /* _MODEM_NOTIFIER_H */ diff --git a/arch/arm/mach-msm/mpm.c b/arch/arm/mach-msm/mpm.c new file mode 100644 index 00000000000..040e1260489 --- /dev/null +++ b/arch/arm/mach-msm/mpm.c @@ -0,0 +1,518 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mpm.h" + +/****************************************************************************** + * Debug Definitions + *****************************************************************************/ + +enum { + MSM_MPM_DEBUG_NON_DETECTABLE_IRQ = BIT(0), + MSM_MPM_DEBUG_PENDING_IRQ = BIT(1), + MSM_MPM_DEBUG_WRITE = BIT(2), + MSM_MPM_DEBUG_NON_DETECTABLE_IRQ_IDLE = BIT(3), +}; + +static int msm_mpm_debug_mask = 1; +module_param_named( + debug_mask, msm_mpm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); + +/****************************************************************************** + * Request and Status Definitions + *****************************************************************************/ + +enum { + MSM_MPM_REQUEST_REG_ENABLE, + MSM_MPM_REQUEST_REG_DETECT_CTL, + MSM_MPM_REQUEST_REG_POLARITY, + MSM_MPM_REQUEST_REG_CLEAR, +}; + +enum { + MSM_MPM_STATUS_REG_PENDING, +}; + +/****************************************************************************** + * IRQ Mapping Definitions + *****************************************************************************/ + +#define MSM_MPM_NR_APPS_IRQS (NR_MSM_IRQS + NR_GPIO_IRQS) + +#define MSM_MPM_REG_WIDTH DIV_ROUND_UP(MSM_MPM_NR_MPM_IRQS, 32) +#define MSM_MPM_IRQ_INDEX(irq) (irq / 32) +#define MSM_MPM_IRQ_MASK(irq) BIT(irq % 32) + +static uint8_t msm_mpm_irqs_a2m[MSM_MPM_NR_APPS_IRQS]; + +static DEFINE_SPINLOCK(msm_mpm_lock); + +/* + * Note: the following two bitmaps only mark irqs that are _not_ + * mappable to MPM. + */ +static DECLARE_BITMAP(msm_mpm_enabled_apps_irqs, MSM_MPM_NR_APPS_IRQS); +static DECLARE_BITMAP(msm_mpm_wake_apps_irqs, MSM_MPM_NR_APPS_IRQS); + +static DECLARE_BITMAP(msm_mpm_gpio_irqs_mask, MSM_MPM_NR_APPS_IRQS); + +static uint32_t msm_mpm_enabled_irq[MSM_MPM_REG_WIDTH]; +static uint32_t msm_mpm_wake_irq[MSM_MPM_REG_WIDTH]; +static uint32_t msm_mpm_detect_ctl[MSM_MPM_REG_WIDTH]; +static uint32_t msm_mpm_polarity[MSM_MPM_REG_WIDTH]; + + +/****************************************************************************** + * Low Level Functions for Accessing MPM + *****************************************************************************/ + +static inline uint32_t msm_mpm_read( + unsigned int reg, unsigned int subreg_index) +{ + unsigned int offset = reg * MSM_MPM_REG_WIDTH + subreg_index; + return __raw_readl(msm_mpm_dev_data.mpm_status_reg_base + offset * 4); +} + +static inline void msm_mpm_write( + unsigned int reg, unsigned int subreg_index, uint32_t value) +{ + unsigned int offset = reg * MSM_MPM_REG_WIDTH + subreg_index; + __raw_writel(value, msm_mpm_dev_data.mpm_request_reg_base + offset * 4); + + if (MSM_MPM_DEBUG_WRITE & msm_mpm_debug_mask) + pr_info("%s: reg %u.%u: 0x%08x\n", + __func__, reg, subreg_index, value); +} + +static inline void msm_mpm_send_interrupt(void) +{ + __raw_writel(msm_mpm_dev_data.mpm_apps_ipc_val, + msm_mpm_dev_data.mpm_apps_ipc_reg); + /* Ensure the write is complete before returning. */ + mb(); +} + +static irqreturn_t msm_mpm_irq(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} + +/****************************************************************************** + * MPM Access Functions + *****************************************************************************/ + +static void msm_mpm_set(bool wakeset) +{ + uint32_t *irqs; + unsigned int reg; + int i; + + irqs = wakeset ? msm_mpm_wake_irq : msm_mpm_enabled_irq; + for (i = 0; i < MSM_MPM_REG_WIDTH; i++) { + reg = MSM_MPM_REQUEST_REG_ENABLE; + msm_mpm_write(reg, i, irqs[i]); + + reg = MSM_MPM_REQUEST_REG_DETECT_CTL; + msm_mpm_write(reg, i, msm_mpm_detect_ctl[i]); + + reg = MSM_MPM_REQUEST_REG_POLARITY; + msm_mpm_write(reg, i, msm_mpm_polarity[i]); + + reg = MSM_MPM_REQUEST_REG_CLEAR; + msm_mpm_write(reg, i, 0xffffffff); + } + + /* Ensure that the set operation is complete before sending the + * interrupt + */ + mb(); + msm_mpm_send_interrupt(); +} + +static void msm_mpm_clear(void) +{ + int i; + + for (i = 0; i < MSM_MPM_REG_WIDTH; i++) { + msm_mpm_write(MSM_MPM_REQUEST_REG_ENABLE, i, 0); + msm_mpm_write(MSM_MPM_REQUEST_REG_CLEAR, i, 0xffffffff); + } + + /* Ensure the clear is complete before sending the interrupt */ + mb(); + msm_mpm_send_interrupt(); +} + +/****************************************************************************** + * Interrupt Mapping Functions + *****************************************************************************/ + +static inline bool msm_mpm_is_valid_apps_irq(unsigned int irq) +{ + return irq < ARRAY_SIZE(msm_mpm_irqs_a2m); +} + +static inline uint8_t msm_mpm_get_irq_a2m(unsigned int irq) +{ + return msm_mpm_irqs_a2m[irq]; +} + +static inline void msm_mpm_set_irq_a2m(unsigned int apps_irq, + unsigned int mpm_irq) +{ + msm_mpm_irqs_a2m[apps_irq] = (uint8_t) mpm_irq; +} + +static inline bool msm_mpm_is_valid_mpm_irq(unsigned int irq) +{ + return irq < msm_mpm_dev_data.irqs_m2a_size; +} + +static inline uint16_t msm_mpm_get_irq_m2a(unsigned int irq) +{ + return msm_mpm_dev_data.irqs_m2a[irq]; +} + +static bool msm_mpm_bypass_apps_irq(unsigned int irq) +{ + int i; + + for (i = 0; i < msm_mpm_dev_data.bypassed_apps_irqs_size; i++) + if (irq == msm_mpm_dev_data.bypassed_apps_irqs[i]) + return true; + + return false; +} + +static int msm_mpm_enable_irq_exclusive( + unsigned int irq, bool enable, bool wakeset) +{ + uint32_t mpm_irq; + + if (!msm_mpm_is_valid_apps_irq(irq)) + return -EINVAL; + + if (msm_mpm_bypass_apps_irq(irq)) + return 0; + + mpm_irq = msm_mpm_get_irq_a2m(irq); + if (mpm_irq) { + uint32_t *mpm_irq_masks = wakeset ? + msm_mpm_wake_irq : msm_mpm_enabled_irq; + uint32_t index = MSM_MPM_IRQ_INDEX(mpm_irq); + uint32_t mask = MSM_MPM_IRQ_MASK(mpm_irq); + + if (enable) + mpm_irq_masks[index] |= mask; + else + mpm_irq_masks[index] &= ~mask; + } else { + unsigned long *apps_irq_bitmap = wakeset ? + msm_mpm_wake_apps_irqs : msm_mpm_enabled_apps_irqs; + + if (enable) + __set_bit(irq, apps_irq_bitmap); + else + __clear_bit(irq, apps_irq_bitmap); + } + + return 0; +} + +static int msm_mpm_set_irq_type_exclusive( + unsigned int irq, unsigned int flow_type) +{ + uint32_t mpm_irq; + + if (!msm_mpm_is_valid_apps_irq(irq)) + return -EINVAL; + + if (msm_mpm_bypass_apps_irq(irq)) + return 0; + + mpm_irq = msm_mpm_get_irq_a2m(irq); + if (mpm_irq) { + uint32_t index = MSM_MPM_IRQ_INDEX(mpm_irq); + uint32_t mask = MSM_MPM_IRQ_MASK(mpm_irq); + + if (flow_type & IRQ_TYPE_EDGE_BOTH) + msm_mpm_detect_ctl[index] |= mask; + else + msm_mpm_detect_ctl[index] &= ~mask; + + if (flow_type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH)) + msm_mpm_polarity[index] |= mask; + else + msm_mpm_polarity[index] &= ~mask; + } + + return 0; +} + +static int __msm_mpm_enable_irq(unsigned int irq, unsigned int enable) +{ + unsigned long flags; + int rc; + + spin_lock_irqsave(&msm_mpm_lock, flags); + rc = msm_mpm_enable_irq_exclusive(irq, (bool)enable, false); + spin_unlock_irqrestore(&msm_mpm_lock, flags); + + return rc; +} + +static void msm_mpm_enable_irq(struct irq_data *d) +{ + __msm_mpm_enable_irq(d->irq, 1); +} + +static void msm_mpm_disable_irq(struct irq_data *d) +{ + __msm_mpm_enable_irq(d->irq, 0); +} + +static int msm_mpm_set_irq_wake(struct irq_data *d, unsigned int on) +{ + unsigned long flags; + int rc; + + spin_lock_irqsave(&msm_mpm_lock, flags); + rc = msm_mpm_enable_irq_exclusive(d->irq, (bool)on, true); + spin_unlock_irqrestore(&msm_mpm_lock, flags); + + return rc; +} + +static int msm_mpm_set_irq_type(struct irq_data *d, unsigned int flow_type) +{ + unsigned long flags; + int rc; + + spin_lock_irqsave(&msm_mpm_lock, flags); + rc = msm_mpm_set_irq_type_exclusive(d->irq, flow_type); + spin_unlock_irqrestore(&msm_mpm_lock, flags); + + return rc; +} + +/****************************************************************************** + * Public functions + *****************************************************************************/ +int msm_mpm_enable_pin(enum msm_mpm_pin pin, unsigned int enable) +{ + uint32_t index = MSM_MPM_IRQ_INDEX(pin); + uint32_t mask = MSM_MPM_IRQ_MASK(pin); + unsigned long flags; + + spin_lock_irqsave(&msm_mpm_lock, flags); + + if (enable) + msm_mpm_enabled_irq[index] |= mask; + else + msm_mpm_enabled_irq[index] &= ~mask; + + spin_unlock_irqrestore(&msm_mpm_lock, flags); + return 0; +} + +int msm_mpm_set_pin_wake(enum msm_mpm_pin pin, unsigned int on) +{ + uint32_t index = MSM_MPM_IRQ_INDEX(pin); + uint32_t mask = MSM_MPM_IRQ_MASK(pin); + unsigned long flags; + + spin_lock_irqsave(&msm_mpm_lock, flags); + + if (on) + msm_mpm_wake_irq[index] |= mask; + else + msm_mpm_wake_irq[index] &= ~mask; + + spin_unlock_irqrestore(&msm_mpm_lock, flags); + return 0; +} + +int msm_mpm_set_pin_type(enum msm_mpm_pin pin, unsigned int flow_type) +{ + uint32_t index = MSM_MPM_IRQ_INDEX(pin); + uint32_t mask = MSM_MPM_IRQ_MASK(pin); + unsigned long flags; + + spin_lock_irqsave(&msm_mpm_lock, flags); + + if (flow_type & IRQ_TYPE_EDGE_BOTH) + msm_mpm_detect_ctl[index] |= mask; + else + msm_mpm_detect_ctl[index] &= ~mask; + + if (flow_type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_LEVEL_HIGH)) + msm_mpm_polarity[index] |= mask; + else + msm_mpm_polarity[index] &= ~mask; + + spin_unlock_irqrestore(&msm_mpm_lock, flags); + return 0; +} + +bool msm_mpm_irqs_detectable(bool from_idle) +{ + unsigned long *apps_irq_bitmap; + int debug_mask; + + if (from_idle) { + apps_irq_bitmap = msm_mpm_enabled_apps_irqs; + debug_mask = msm_mpm_debug_mask & + MSM_MPM_DEBUG_NON_DETECTABLE_IRQ_IDLE; + } else { + apps_irq_bitmap = msm_mpm_wake_apps_irqs; + debug_mask = msm_mpm_debug_mask & + MSM_MPM_DEBUG_NON_DETECTABLE_IRQ; + } + + if (debug_mask) { + static char buf[DIV_ROUND_UP(MSM_MPM_NR_APPS_IRQS, 32)*9+1]; + + bitmap_scnprintf(buf, sizeof(buf), apps_irq_bitmap, + MSM_MPM_NR_APPS_IRQS); + buf[sizeof(buf) - 1] = '\0'; + + pr_info("%s: cannot monitor %s", __func__, buf); + } + + return (bool)__bitmap_empty(apps_irq_bitmap, MSM_MPM_NR_APPS_IRQS); +} + +bool msm_mpm_gpio_irqs_detectable(bool from_idle) +{ + unsigned long *apps_irq_bitmap = from_idle ? + msm_mpm_enabled_apps_irqs : msm_mpm_wake_apps_irqs; + + return !__bitmap_intersects(msm_mpm_gpio_irqs_mask, apps_irq_bitmap, + MSM_MPM_NR_APPS_IRQS); +} + +void msm_mpm_enter_sleep(bool from_idle) +{ + msm_mpm_set(!from_idle); +} + +void msm_mpm_exit_sleep(bool from_idle) +{ + unsigned long pending; + int i; + int k; + + for (i = 0; i < MSM_MPM_REG_WIDTH; i++) { + pending = msm_mpm_read(MSM_MPM_STATUS_REG_PENDING, i); + + if (MSM_MPM_DEBUG_PENDING_IRQ & msm_mpm_debug_mask) + pr_info("%s: pending.%d: 0x%08lx", __func__, + i, pending); + + k = find_first_bit(&pending, 32); + while (k < 32) { + unsigned int mpm_irq = 32 * i + k; + unsigned int apps_irq = msm_mpm_get_irq_m2a(mpm_irq); + struct irq_desc *desc = apps_irq ? + irq_to_desc(apps_irq) : NULL; + + if (desc && !irqd_is_level_type(&desc->irq_data)) { + irq_set_pending(apps_irq); + if (from_idle) + check_irq_resend(desc, apps_irq); + } + + k = find_next_bit(&pending, 32, k + 1); + } + } + + msm_mpm_clear(); +} + +static int __init msm_mpm_early_init(void) +{ + uint8_t mpm_irq; + uint16_t apps_irq; + + for (mpm_irq = 0; msm_mpm_is_valid_mpm_irq(mpm_irq); mpm_irq++) { + apps_irq = msm_mpm_get_irq_m2a(mpm_irq); + if (apps_irq && msm_mpm_is_valid_apps_irq(apps_irq)) + msm_mpm_set_irq_a2m(apps_irq, mpm_irq); + } + + return 0; +} +core_initcall(msm_mpm_early_init); + +void msm_mpm_irq_extn_init(void) +{ + gic_arch_extn.irq_mask = msm_mpm_disable_irq; + gic_arch_extn.irq_unmask = msm_mpm_enable_irq; + gic_arch_extn.irq_disable = msm_mpm_disable_irq; + gic_arch_extn.irq_set_type = msm_mpm_set_irq_type; + gic_arch_extn.irq_set_wake = msm_mpm_set_irq_wake; + + msm_gpio_irq_extn.irq_mask = msm_mpm_disable_irq; + msm_gpio_irq_extn.irq_unmask = msm_mpm_enable_irq; + msm_gpio_irq_extn.irq_disable = msm_mpm_disable_irq; + msm_gpio_irq_extn.irq_set_type = msm_mpm_set_irq_type; + msm_gpio_irq_extn.irq_set_wake = msm_mpm_set_irq_wake; + + bitmap_set(msm_mpm_gpio_irqs_mask, NR_MSM_IRQS, NR_GPIO_IRQS); +} + +static int __init msm_mpm_init(void) +{ + unsigned int irq = msm_mpm_dev_data.mpm_ipc_irq; + int rc; + + rc = request_irq(irq, msm_mpm_irq, + IRQF_TRIGGER_RISING, "mpm_drv", msm_mpm_irq); + + if (rc) { + pr_err("%s: failed to request irq %u: %d\n", + __func__, irq, rc); + goto init_bail; + } + + rc = irq_set_irq_wake(irq, 1); + if (rc) { + pr_err("%s: failed to set wakeup irq %u: %d\n", + __func__, irq, rc); + goto init_free_bail; + } + + return 0; + +init_free_bail: + free_irq(irq, msm_mpm_irq); + +init_bail: + return rc; +} +device_initcall(msm_mpm_init); diff --git a/arch/arm/mach-msm/mpm.h b/arch/arm/mach-msm/mpm.h new file mode 100644 index 00000000000..88e369c8174 --- /dev/null +++ b/arch/arm/mach-msm/mpm.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_MPM_H +#define __ARCH_ARM_MACH_MSM_MPM_H + +#include +#include + +enum msm_mpm_pin { + MSM_MPM_PIN_SDC3_DAT1 = 21, + MSM_MPM_PIN_SDC3_DAT3 = 22, + MSM_MPM_PIN_SDC4_DAT1 = 23, + MSM_MPM_PIN_SDC4_DAT3 = 24, +}; + +#define MSM_MPM_NR_MPM_IRQS 64 + +struct msm_mpm_device_data { + uint16_t *irqs_m2a; + unsigned int irqs_m2a_size; + uint16_t *bypassed_apps_irqs; + unsigned int bypassed_apps_irqs_size; + void __iomem *mpm_request_reg_base; + void __iomem *mpm_status_reg_base; + void __iomem *mpm_apps_ipc_reg; + unsigned int mpm_apps_ipc_val; + unsigned int mpm_ipc_irq; +}; + +#ifdef CONFIG_MSM_MPM +extern struct msm_mpm_device_data msm_mpm_dev_data; + +int msm_mpm_enable_pin(enum msm_mpm_pin pin, unsigned int enable); +int msm_mpm_set_pin_wake(enum msm_mpm_pin pin, unsigned int on); +int msm_mpm_set_pin_type(enum msm_mpm_pin pin, unsigned int flow_type); +bool msm_mpm_irqs_detectable(bool from_idle); +bool msm_mpm_gpio_irqs_detectable(bool from_idle); +void msm_mpm_enter_sleep(bool from_idle); +void msm_mpm_exit_sleep(bool from_idle); +void msm_mpm_irq_extn_init(void); +#else + +int msm_mpm_enable_irq(unsigned int irq, unsigned int enable) +{ return -ENODEV; } +int msm_mpm_set_irq_wake(unsigned int irq, unsigned int on) +{ return -ENODEV; } +int msm_mpm_set_irq_type(unsigned int irq, unsigned int flow_type) +{ return -ENODEV; } +int msm_mpm_enable_pin(enum msm_mpm_pin pin, unsigned int enable) +{ return -ENODEV; } +int msm_mpm_set_pin_wake(enum msm_mpm_pin pin, unsigned int on) +{ return -ENODEV; } +int msm_mpm_set_pin_type(enum msm_mpm_pin pin, unsigned int flow_type) +{ return -ENODEV; } +bool msm_mpm_irqs_detectable(bool from_idle) +{ return false; } +bool msm_mpm_gpio_irqs_detectable(bool from_idle) +{ return false; } +void msm_mpm_enter_sleep(bool from_idle) {} +void msm_mpm_exit_sleep(bool from_idle) {} +void msm_mpm_irq_extn_init(void) {} +#endif + + + +#endif /* __ARCH_ARM_MACH_MSM_MPM_H */ diff --git a/arch/arm/mach-msm/mpp.c b/arch/arm/mach-msm/mpp.c new file mode 100644 index 00000000000..2637c3e2693 --- /dev/null +++ b/arch/arm/mach-msm/mpp.c @@ -0,0 +1,96 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* Qualcomm PMIC Multi-Purpose Pin Configurations */ + +#include +#include +#include +#include + +#include + +#include "proc_comm.h" + +int mpp_config_digital_out(unsigned mpp, unsigned config) +{ + int err; + err = msm_proc_comm(PCOM_PM_MPP_CONFIG, &mpp, &config); + if (err) + pr_err("%s: msm_proc_comm(PCOM_PM_MPP_CONFIG) failed\n", + __func__); + return err; +} +EXPORT_SYMBOL(mpp_config_digital_out); + +int mpp_config_digital_in(unsigned mpp, unsigned config) +{ + int err; + err = msm_proc_comm(PCOM_PM_MPP_CONFIG_DIGITAL_INPUT, &mpp, &config); + if (err) + pr_err("%s: msm_proc_comm(PCOM_PM_MPP_CONFIG) failed\n", + __func__); + return err; +} +EXPORT_SYMBOL(mpp_config_digital_in); + +#if defined(CONFIG_DEBUG_FS) +static int test_result; + +static int mpp_debug_set(void *data, u64 val) +{ + unsigned mpp = (unsigned) data; + + test_result = mpp_config_digital_out(mpp, (unsigned)val); + if (test_result) { + printk(KERN_ERR + "%s: mpp_config_digital_out \ + [mpp(%d) = 0x%x] failed (err=%d)\n", + __func__, mpp, (unsigned)val, test_result); + } + return 0; +} + +static int mpp_debug_get(void *data, u64 *val) +{ + if (!test_result) + *val = 0; + else + *val = 1; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(mpp_fops, mpp_debug_get, mpp_debug_set, "%llu\n"); + +static int __init mpp_debug_init(void) +{ + struct dentry *dent; + int n; + char file_name[16]; + + dent = debugfs_create_dir("mpp", 0); + if (IS_ERR(dent)) + return 0; + + for (n = 0; n < MPPS; n++) { + snprintf(file_name, sizeof(file_name), "mpp%d", n + 1); + debugfs_create_file(file_name, 0644, dent, + (void *)n, &mpp_fops); + } + + return 0; +} + +device_initcall(mpp_debug_init); +#endif + diff --git a/arch/arm/mach-msm/msm-keypad-devices.h b/arch/arm/mach-msm/msm-keypad-devices.h new file mode 100644 index 00000000000..469564a754a --- /dev/null +++ b/arch/arm/mach-msm/msm-keypad-devices.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _MSM_KEYPAD_DEVICES_H +#define _MSM_KEYPAD_DEVICES_H + +extern struct platform_device keypad_device_7k_ffa; +extern struct platform_device keypad_device_8k_ffa; +extern struct platform_device keypad_device_surf; + +#endif diff --git a/arch/arm/mach-msm/msm-krait-l2-accessors.c b/arch/arm/mach-msm/msm-krait-l2-accessors.c new file mode 100644 index 00000000000..7ba19ab1e56 --- /dev/null +++ b/arch/arm/mach-msm/msm-krait-l2-accessors.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +DEFINE_RAW_SPINLOCK(l2_access_lock); + +u32 set_get_l2_indirect_reg(u32 reg_addr, u32 val) +{ + unsigned long flags; + u32 ret_val; + + raw_spin_lock_irqsave(&l2_access_lock, flags); + + asm volatile ("mcr p15, 3, %0, c15, c0, 6" : : "r" (reg_addr)); + + asm volatile ("mcr p15, 3, %0, c15, c0, 7" : : "r" (val)); + + /* Ensure the value took */ + asm volatile ("mrc p15, 3, %0, c15, c0, 7" : "=r" (ret_val)); + + raw_spin_unlock_irqrestore(&l2_access_lock, flags); + + return ret_val; +} + +void set_l2_indirect_reg(u32 reg_addr, u32 val) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&l2_access_lock, flags); + + asm volatile ("mcr p15, 3, %0, c15, c0, 6" : : "r" (reg_addr)); + + asm volatile ("mcr p15, 3, %0, c15, c0, 7" : : "r" (val)); + + raw_spin_unlock_irqrestore(&l2_access_lock, flags); +} + +u32 get_l2_indirect_reg(u32 reg_addr) +{ + u32 val; + unsigned long flags; + + raw_spin_lock_irqsave(&l2_access_lock, flags); + + asm volatile ("mcr p15, 3, %0, c15, c0, 6" : : "r" (reg_addr)); + + asm volatile ("mrc p15, 3, %0, c15, c0, 7" : "=r" (val)); + + raw_spin_unlock_irqrestore(&l2_access_lock, flags); + + return val; +} diff --git a/arch/arm/mach-msm/msm_bus/Makefile b/arch/arm/mach-msm/msm_bus/Makefile new file mode 100644 index 00000000000..c8600d54bf7 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for msm-bus driver specific files +# +obj-y += msm_bus_core.o msm_bus_fabric.o msm_bus_config.o msm_bus_arb.o msm_bus_rpm.o +obj-$(CONFIG_ARCH_MSM8X60) += msm_bus_board_8660.o +obj-$(CONFIG_ARCH_MSM8960) += msm_bus_board_8960.o +obj-$(CONFIG_DEBUG_FS) += msm_bus_dbg.o diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_arb.c b/arch/arm/mach-msm/msm_bus/msm_bus_arb.c new file mode 100644 index 00000000000..bb53dd373ab --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_arb.c @@ -0,0 +1,655 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" + +#define INDEX_MASK 0x0000FFFF +#define PNODE_MASK 0xFFFF0000 +#define SHIFT_VAL 16 +#define CREATE_PNODE_ID(n, i) (((n) << SHIFT_VAL) | (i)) +#define GET_INDEX(n) ((n) & INDEX_MASK) +#define GET_NODE(n) ((n) >> SHIFT_VAL) +#define IS_NODE(n) ((n) % FABRIC_ID_KEY) +#define SEL_FAB_CLK 1 +#define SEL_SLAVE_CLK 0 + +#define BW_TO_CLK_FREQ_HZ(width, bw) ((unsigned long)((bw) / (width))) +#define IS_MASTER_VALID(mas) \ + (((mas >= MSM_BUS_MASTER_FIRST) && (mas <= MSM_BUS_MASTER_LAST)) \ + ? 1 : 0) + +#define IS_SLAVE_VALID(slv) \ + (((slv >= MSM_BUS_SLAVE_FIRST) && (slv <= MSM_BUS_SLAVE_LAST)) ? 1 : 0) + +static DEFINE_MUTEX(msm_bus_lock); + +/** + * add_path_node: Adds the path information to the current node + * @info: Internal node info structure + * @next: Combination of the id and index of the next node + * Function returns: Number of pnodes (path_nodes) on success, + * error on failure. + * + * Every node maintains the list of path nodes. A path node is + * reached by finding the node-id and index stored at the current + * node. This makes updating the paths with requested bw and clock + * values efficient, as it avoids lookup for each update-path request. + */ +static int add_path_node(struct msm_bus_inode_info *info, int next) +{ + struct path_node *pnode; + int i; + if (!info) { + MSM_BUS_ERR("Cannot find node info!: id :%d\n", + info->node_info->priv_id); + return -ENXIO; + } + + for (i = 0; i <= info->num_pnodes; i++) { + if (info->pnode[i].next == -2) { + MSM_BUS_DBG("Reusing pnode for info: %d at index: %d\n", + info->node_info->priv_id, i); + info->pnode[i].clk[DUAL_CTX] = 0; + info->pnode[i].clk[ACTIVE_CTX] = 0; + info->pnode[i].bw[DUAL_CTX] = 0; + info->pnode[i].bw[ACTIVE_CTX] = 0; + info->pnode[i].next = next; + MSM_BUS_DBG("%d[%d] : (%d, %d)\n", + info->node_info->priv_id, i, GET_NODE(next), + GET_INDEX(next)); + return i; + } + } + + info->num_pnodes++; + pnode = krealloc(info->pnode, + ((info->num_pnodes + 1) * sizeof(struct path_node)) + , GFP_KERNEL); + if (IS_ERR(pnode)) { + MSM_BUS_ERR("Error creating path node!\n"); + info->num_pnodes--; + return -ENOMEM; + } + info->pnode = pnode; + info->pnode[info->num_pnodes].clk[DUAL_CTX] = 0; + info->pnode[info->num_pnodes].clk[ACTIVE_CTX] = 0; + info->pnode[info->num_pnodes].bw[DUAL_CTX] = 0; + info->pnode[info->num_pnodes].bw[ACTIVE_CTX] = 0; + info->pnode[info->num_pnodes].next = next; + MSM_BUS_DBG("%d[%d] : (%d, %d)\n", info->node_info->priv_id, + info->num_pnodes, GET_NODE(next), GET_INDEX(next)); + return info->num_pnodes; +} + +static int clearvisitedflag(struct device *dev, void *data) +{ + struct msm_bus_fabric_device *fabdev = to_msm_bus_fabric_device(dev); + fabdev->visited = false; + return 0; +} + +/** + * getpath() - Finds the path from the topology between src and dest + * @src: Source. This is the master from which the request originates + * @dest: Destination. This is the slave to which we're trying to reach + * + * Function returns: next_pnode_id. The higher 16 bits of the next_pnode_id + * represent the src id of the next node on path. The lower 16 bits of the + * next_pnode_id represent the "index", which is the next entry in the array + * of pnodes for that node to fill in clk and bw values. This is created using + * CREATE_PNODE_ID. The return value is stored in ret_pnode, and this is added + * to the list of path nodes. + * + * This function recursively finds the path by updating the src to the + * closest possible node to dest. + */ +static int getpath(int src, int dest) +{ + int pnode_num = -1, i; + struct msm_bus_fabnodeinfo *fabnodeinfo; + struct msm_bus_fabric_device *fabdev; + int next_pnode_id = -1; + struct msm_bus_inode_info *info = NULL; + int _src = src/FABRIC_ID_KEY; + int _dst = dest/FABRIC_ID_KEY; + int ret_pnode = -1; + int fabid = GET_FABID(src); + + /* Find the location of fabric for the src */ + MSM_BUS_DBG("%d --> %d\n", src, dest); + + fabdev = msm_bus_get_fabric_device(fabid); + if (!fabdev) { + MSM_BUS_WARN("Fabric Not yet registered. Try again\n"); + return -ENXIO; + } + + /* Are we there yet? */ + if (src == dest) { + info = fabdev->algo->find_node(fabdev, src); + for (i = 0; i <= info->num_pnodes; i++) { + if (info->pnode[i].next == -2) { + MSM_BUS_DBG("src = dst Reusing pnode for" + " info: %d at index: %d\n", + info->node_info->priv_id, i); + next_pnode_id = CREATE_PNODE_ID(src, i); + info->pnode[i].clk[DUAL_CTX] = 0; + info->pnode[i].bw[DUAL_CTX] = 0; + info->pnode[i].next = next_pnode_id; + MSM_BUS_DBG("returning: %d, %d\n", GET_NODE + (next_pnode_id), GET_INDEX(next_pnode_id)); + return next_pnode_id; + } + } + next_pnode_id = CREATE_PNODE_ID(src, (info->num_pnodes + 1)); + pnode_num = add_path_node(info, next_pnode_id); + if (pnode_num < 0) { + MSM_BUS_DBG("error adding path node\n"); + return -ENXIO; + } + MSM_BUS_DBG("returning: %d, %d\n", GET_NODE(next_pnode_id), + GET_INDEX(next_pnode_id)); + return next_pnode_id; + } else if (_src == _dst) { + /* + * src and dest belong to same fabric, find the destination + * from the radix tree + */ + info = fabdev->algo->find_node(fabdev, dest); + ret_pnode = getpath(info->node_info->priv_id, dest); + next_pnode_id = ret_pnode; + } else { + /* find the dest fabric */ + int trynextgw = true; + struct list_head *gateways = fabdev->algo->get_gw_list(fabdev); + list_for_each_entry(fabnodeinfo, gateways, list) { + /* see if the destination is at a connected fabric */ + if (_dst == (fabnodeinfo->info->node_info->priv_id / + FABRIC_ID_KEY) && !(fabdev->visited)) { + /* Found the fab on which the device exists */ + info = fabnodeinfo->info; + trynextgw = false; + ret_pnode = getpath(info->node_info->priv_id, + dest); + pnode_num = add_path_node(info, ret_pnode); + if (pnode_num < 0) { + MSM_BUS_DBG("Error adding path node\n"); + return -ENXIO; + } + next_pnode_id = CREATE_PNODE_ID( + info->node_info->priv_id, pnode_num); + break; + } + } + + /* find the gateway */ + if (trynextgw) { + gateways = fabdev->algo->get_gw_list(fabdev); + list_for_each_entry(fabnodeinfo, gateways, list) { + struct msm_bus_fabric_device *gwfab = + msm_bus_get_fabric_device(fabnodeinfo-> + info->node_info->priv_id); + if (!gwfab->visited) { + MSM_BUS_DBG("VISITED ID: %d\n", + gwfab->id); + gwfab->visited = true; + info = fabnodeinfo->info; + ret_pnode = getpath(info-> + node_info->priv_id, dest); + if (ret_pnode >= 0) { + pnode_num = add_path_node(info, + ret_pnode); + if (pnode_num < 0) { + MSM_BUS_ERR("Malloc" + "failure in adding" + "path node\n"); + return -ENXIO; + } + next_pnode_id = CREATE_PNODE_ID( + info->node_info->priv_id, + pnode_num); + break; + } + } + } + if (next_pnode_id < 0) + return -EPERM; + } + } + + if (!IS_NODE(src)) { + MSM_BUS_DBG("Returning next_pnode_id:%d[%d]\n", GET_NODE( + next_pnode_id), GET_INDEX(next_pnode_id)); + return next_pnode_id; + } + info = fabdev->algo->find_node(fabdev, src); + if (!info) { + MSM_BUS_ERR("Node info not found.\n"); + return -EPERM; + } + pnode_num = add_path_node(info, next_pnode_id); + MSM_BUS_DBG(" Last: %d[%d] = (%d, %d)\n", + src, info->num_pnodes, GET_NODE(next_pnode_id), + GET_INDEX(next_pnode_id)); + MSM_BUS_DBG("returning: %d, %d\n", src, pnode_num); + return CREATE_PNODE_ID(src, pnode_num); +} + +/** + * update_path() - Update the path with the bandwidth and clock values, as + * requested by the client. + * + * @curr: Current source node, as specified in the client vector (master) + * @pnode: The first-hop node on the path, stored in the internal client struct + * @req_clk: Requested clock value from the vector + * @req_bw: Requested bandwidth value from the vector + * @curr_clk: Current clock frequency + * @curr_bw: Currently allocated bandwidth + * + * This function updates the nodes on the path calculated using getpath(), with + * clock and bandwidth values. The sum of bandwidths, and the max of clock + * frequencies is calculated at each node on the path. Commit data to be sent + * to RPM for each master and slave is also calculated here. + */ +static int update_path(int curr, int pnode, unsigned long req_clk, unsigned + long req_bw, unsigned long curr_clk, unsigned long curr_bw, + unsigned int ctx, unsigned int cl_active_flag) +{ + int index, ret = 0; + struct msm_bus_inode_info *info; + int next_pnode; + long int add_bw = req_bw - curr_bw; + unsigned bwsum = 0; + unsigned req_clk_hz, curr_clk_hz, bwsum_hz; + int *master_tiers; + struct msm_bus_fabric_device *fabdev = msm_bus_get_fabric_device + (GET_FABID(curr)); + + MSM_BUS_DBG("args: %d %d %d %lu %lu %lu %lu %u\n", + curr, GET_NODE(pnode), GET_INDEX(pnode), req_clk, req_bw, + curr_clk, curr_bw, ctx); + index = GET_INDEX(pnode); + MSM_BUS_DBG("Client passed index :%d\n", index); + info = fabdev->algo->find_node(fabdev, curr); + if (!info) { + MSM_BUS_ERR("Cannot find node info!\n"); + return -ENXIO; + } + + info->link_info.sel_bw = &info->link_info.bw[ctx]; + info->link_info.sel_clk = &info->link_info.clk[ctx]; + *info->link_info.sel_bw += add_bw; + + info->pnode[index].sel_bw = &info->pnode[index].bw[ctx]; + info->pnode[index].sel_clk = &info->pnode[index].clk[ctx]; + *info->pnode[index].sel_bw += add_bw; + + info->link_info.num_tiers = info->node_info->num_tiers; + info->link_info.tier = info->node_info->tier; + master_tiers = info->node_info->tier; + + do { + struct msm_bus_inode_info *hop; + fabdev = msm_bus_get_fabric_device(GET_FABID(curr)); + if (!fabdev) { + MSM_BUS_ERR("Fabric not found\n"); + return -ENXIO; + } + MSM_BUS_DBG("id: %d\n", info->node_info->priv_id); + + /* find next node and index */ + next_pnode = info->pnode[index].next; + curr = GET_NODE(next_pnode); + index = GET_INDEX(next_pnode); + MSM_BUS_DBG("id:%d, next: %d\n", info-> + node_info->priv_id, curr); + + /* Get hop */ + /* check if we are here as gateway, or does the hop belong to + * this fabric */ + if (IS_NODE(curr)) + hop = fabdev->algo->find_node(fabdev, curr); + else + hop = fabdev->algo->find_gw_node(fabdev, curr); + if (!hop) { + MSM_BUS_ERR("Null Info found for hop\n"); + return -ENXIO; + } + + hop->link_info.sel_bw = &hop->link_info.bw[ctx]; + hop->link_info.sel_clk = &hop->link_info.clk[ctx]; + *hop->link_info.sel_bw += add_bw; + + hop->pnode[index].sel_bw = &hop->pnode[index].bw[ctx]; + hop->pnode[index].sel_clk = &hop->pnode[index].clk[ctx]; + + if (!hop->node_info->buswidth) { + MSM_BUS_WARN("No bus width found. Using default\n"); + hop->node_info->buswidth = 8; + } + *hop->pnode[index].sel_clk = BW_TO_CLK_FREQ_HZ(hop->node_info-> + buswidth, req_clk); + *hop->pnode[index].sel_bw += add_bw; + MSM_BUS_DBG("fabric: %d slave: %d, slave-width: %d info: %d\n", + fabdev->id, hop->node_info->priv_id, hop->node_info-> + buswidth, info->node_info->priv_id); + /* Update Bandwidth */ + fabdev->algo->update_bw(fabdev, hop, info, add_bw, + master_tiers, ctx); + bwsum = (uint16_t)*hop->link_info.sel_bw; + /* Update Fabric clocks */ + curr_clk_hz = BW_TO_CLK_FREQ_HZ(hop->node_info->buswidth, + curr_clk); + req_clk_hz = BW_TO_CLK_FREQ_HZ(hop->node_info->buswidth, + req_clk); + bwsum_hz = BW_TO_CLK_FREQ_HZ(hop->node_info->buswidth, + bwsum); + MSM_BUS_DBG("Calling update-clks: curr_hz: %lu, req_hz: %lu," + " bw_hz %u\n", curr_clk, req_clk, bwsum_hz); + ret = fabdev->algo->update_clks(fabdev, hop, index, + curr_clk_hz, req_clk_hz, bwsum_hz, SEL_FAB_CLK, + ctx, cl_active_flag); + if (ret) + MSM_BUS_WARN("Failed to update clk\n"); + info = hop; + } while (GET_NODE(info->pnode[index].next) != info->node_info->priv_id); + + /* Update BW, clk after exiting the loop for the last one */ + if (!info) { + MSM_BUS_ERR("Cannot find node info!\n"); + return -ENXIO; + } + /* Update slave clocks */ + ret = fabdev->algo->update_clks(fabdev, info, index, curr_clk_hz, + req_clk_hz, bwsum_hz, SEL_SLAVE_CLK, ctx, cl_active_flag); + if (ret) + MSM_BUS_ERR("Failed to update clk\n"); + return ret; +} + +/** + * msm_bus_commit_fn() - Commits the data for fabric to rpm + * @dev: fabric device + * @data: NULL + */ +static int msm_bus_commit_fn(struct device *dev, void *data) +{ + int ret = 0; + struct msm_bus_fabric_device *fabdev = to_msm_bus_fabric_device(dev); + MSM_BUS_DBG("Committing: fabid: %d\n", fabdev->id); + ret = fabdev->algo->commit(fabdev, (int)data); + return ret; +} + +/** + * msm_bus_scale_register_client() - Register the clients with the msm bus + * driver + * @pdata: Platform data of the client, containing src, dest, ab, ib + * + * Client data contains the vectors specifying arbitrated bandwidth (ab) + * and instantaneous bandwidth (ib) requested between a particular + * src and dest. + */ +uint32_t msm_bus_scale_register_client(struct msm_bus_scale_pdata *pdata) +{ + struct msm_bus_client *client = NULL; + int i; + int src, dest, nfab; + nfab = msm_bus_get_num_fab(); + if (nfab < NUM_FAB) { + MSM_BUS_ERR("Can't register client!\n" + "Num of fabrics up: %d\n", + nfab); + return 0; + } + + if ((!pdata) || (pdata->usecase->num_paths == 0) || IS_ERR(pdata)) { + MSM_BUS_ERR("Cannot register client with null data\n"); + return 0; + } + + client = kzalloc(sizeof(struct msm_bus_client), GFP_KERNEL); + if (!client) { + MSM_BUS_ERR("Error allocating client\n"); + return 0; + } + + mutex_lock(&msm_bus_lock); + client->pdata = pdata; + client->curr = -1; + for (i = 0; i < pdata->usecase->num_paths; i++) { + int *pnode; + struct msm_bus_fabric_device *srcfab; + pnode = krealloc(client->src_pnode, ((i + 1) * sizeof(int)), + GFP_KERNEL); + if (!IS_ERR(pnode)) + client->src_pnode = pnode; + else { + MSM_BUS_ERR("Invalid Pnode ptr!\n"); + continue; + } + + if (!IS_MASTER_VALID(pdata->usecase->vectors[i].src)) { + MSM_BUS_ERR("Invalid Master ID %d in request!\n", + pdata->usecase->vectors[i].src); + goto err; + } + + if (!IS_SLAVE_VALID(pdata->usecase->vectors[i].dst)) { + MSM_BUS_ERR("Invalid Slave ID %d in request!\n", + pdata->usecase->vectors[i].dst); + goto err; + } + + src = msm_bus_board_get_iid(pdata->usecase->vectors[i].src); + dest = msm_bus_board_get_iid(pdata->usecase->vectors[i].dst); + srcfab = msm_bus_get_fabric_device(GET_FABID(src)); + srcfab->visited = true; + pnode[i] = getpath(src, dest); + bus_for_each_dev(&msm_bus_type, NULL, NULL, clearvisitedflag); + if (pnode[i] < 0) { + MSM_BUS_ERR("Cannot register client now! Try again!\n"); + goto err; + } + } + msm_bus_dbg_client_data(client->pdata, MSM_BUS_DBG_REGISTER, + (uint32_t)client); + mutex_unlock(&msm_bus_lock); + MSM_BUS_DBG("ret: %u num_paths: %d\n", (uint32_t)client, + pdata->usecase->num_paths); + return (uint32_t)(client); +err: + kfree(client->src_pnode); + kfree(client); + mutex_unlock(&msm_bus_lock); + return 0; +} +EXPORT_SYMBOL(msm_bus_scale_register_client); + +/** + * msm_bus_scale_client_update_request() - Update the request for bandwidth + * from a particular client + * + * cl: Handle to the client + * index: Index into the vector, to which the bw and clock values need to be + * updated + */ +int msm_bus_scale_client_update_request(uint32_t cl, unsigned index) +{ + int i, ret = 0; + struct msm_bus_scale_pdata *pdata; + int pnode, src, curr, ctx; + unsigned long req_clk, req_bw, curr_clk, curr_bw; + struct msm_bus_client *client = (struct msm_bus_client *)cl; + if (IS_ERR(client)) { + MSM_BUS_ERR("msm_bus_scale_client update req error %d\n", + (uint32_t)client); + ret = -ENXIO; + goto err; + } + + mutex_lock(&msm_bus_lock); + if (client->curr == index) + goto err; + + curr = client->curr; + pdata = client->pdata; + + if ((index < 0) || (index > pdata->num_usecases)) { + MSM_BUS_ERR("Client %u passed invalid index: %d\n", + (uint32_t)client, index); + ret = -ENXIO; + goto err; + } + + MSM_BUS_DBG("cl: %u index: %d curr: %d" + " num_paths: %d\n", cl, index, client->curr, + client->pdata->usecase->num_paths); + + for (i = 0; i < pdata->usecase->num_paths; i++) { + src = msm_bus_board_get_iid(client->pdata->usecase[index]. + vectors[i].src); + pnode = client->src_pnode[i]; + req_clk = client->pdata->usecase[index].vectors[i].ib; + req_bw = client->pdata->usecase[index].vectors[i].ab; + if (curr < 0) { + curr_clk = 0; + curr_bw = 0; + } else { + curr_clk = client->pdata->usecase[curr].vectors[i].ib; + curr_bw = client->pdata->usecase[curr].vectors[i].ab; + MSM_BUS_DBG("ab: %lu ib: %lu\n", curr_bw, curr_clk); + } + + if (!pdata->active_only) { + ret = update_path(src, pnode, req_clk, req_bw, + curr_clk, curr_bw, 0, pdata->active_only); + if (ret) { + MSM_BUS_ERR("Update path failed! %d\n", ret); + goto err; + } + } + + ret = update_path(src, pnode, req_clk, req_bw, curr_clk, + curr_bw, ACTIVE_CTX, pdata->active_only); + if (ret) { + MSM_BUS_ERR("Update Path failed! %d\n", ret); + goto err; + } + } + + client->curr = index; + ctx = ACTIVE_CTX; + msm_bus_dbg_client_data(client->pdata, index, cl); + bus_for_each_dev(&msm_bus_type, NULL, (void *)ctx, msm_bus_commit_fn); + +err: + mutex_unlock(&msm_bus_lock); + return ret; +} +EXPORT_SYMBOL(msm_bus_scale_client_update_request); + +int reset_pnodes(int curr, int pnode) +{ + struct msm_bus_inode_info *info; + struct msm_bus_fabric_device *fabdev; + int index, next_pnode; + fabdev = msm_bus_get_fabric_device(GET_FABID(curr)); + index = GET_INDEX(pnode); + info = fabdev->algo->find_node(fabdev, curr); + if (!info) { + MSM_BUS_ERR("Cannot find node info!\n"); + return -ENXIO; + } + MSM_BUS_DBG("Starting the loop--remove\n"); + do { + struct msm_bus_inode_info *hop; + fabdev = msm_bus_get_fabric_device(GET_FABID(curr)); + if (!fabdev) { + MSM_BUS_ERR("Fabric not found\n"); + return -ENXIO; + } + + next_pnode = info->pnode[index].next; + info->pnode[index].next = -2; + curr = GET_NODE(next_pnode); + index = GET_INDEX(next_pnode); + if (IS_NODE(curr)) + hop = fabdev->algo->find_node(fabdev, curr); + else + hop = fabdev->algo->find_gw_node(fabdev, curr); + if (!hop) { + MSM_BUS_ERR("Null Info found for hop\n"); + return -ENXIO; + } + + MSM_BUS_DBG("%d[%d] = %d\n", info->node_info->priv_id, index, + info->pnode[index].next); + MSM_BUS_DBG("num_pnodes: %d: %d\n", info->node_info->priv_id, + info->num_pnodes); + info = hop; + } while (GET_NODE(info->pnode[index].next) != info->node_info->priv_id); + + info->pnode[index].next = -2; + MSM_BUS_DBG("%d[%d] = %d\n", info->node_info->priv_id, index, + info->pnode[index].next); + MSM_BUS_DBG("num_pnodes: %d: %d\n", info->node_info->priv_id, + info->num_pnodes); + return 0; +} + +void msm_bus_scale_client_reset_pnodes(uint32_t cl) +{ + int i, src, pnode, index; + struct msm_bus_client *client = (struct msm_bus_client *)(cl); + if (IS_ERR(client)) { + MSM_BUS_ERR("msm_bus_scale_reset_pnodes error\n"); + return; + } + index = 0; + for (i = 0; i < client->pdata->usecase->num_paths; i++) { + src = msm_bus_board_get_iid( + client->pdata->usecase[index].vectors[i].src); + pnode = client->src_pnode[i]; + MSM_BUS_DBG("(%d, %d)\n", GET_NODE(pnode), GET_INDEX(pnode)); + reset_pnodes(src, pnode); + } +} + +/** + * msm_bus_scale_unregister_client() - Unregister the client from the bus driver + * @cl: Handle to the client + */ +void msm_bus_scale_unregister_client(uint32_t cl) +{ + struct msm_bus_client *client = (struct msm_bus_client *)(cl); + if (IS_ERR(client) || (!client)) + return; + if (client->curr != 0) + msm_bus_scale_client_update_request(cl, 0); + MSM_BUS_DBG("Unregistering client %d\n", cl); + mutex_lock(&msm_bus_lock); + msm_bus_scale_client_reset_pnodes(cl); + msm_bus_dbg_client_data(client->pdata, MSM_BUS_DBG_UNREGISTER, cl); + mutex_unlock(&msm_bus_lock); + kfree(client->src_pnode); + kfree(client); +} +EXPORT_SYMBOL(msm_bus_scale_unregister_client); + diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_board_8660.c b/arch/arm/mach-msm/msm_bus/msm_bus_board_8660.c new file mode 100644 index 00000000000..ddf56c8986f --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_board_8660.c @@ -0,0 +1,908 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" + +#define NMASTERS 39 +#define NSLAVES 67 + +enum msm_bus_fabric_tiered_slave_type { + MSM_BUS_SYSTEM_TIERED_SLAVE_FAB_APPSS = 1, + MSM_BUS_TIERED_SLAVE_SYSTEM_IMEM, + + MSM_BUS_TIERED_SLAVE_SMI = 1, + MSM_BUS_MMSS_TIERED_SLAVE_FAB_APPS, + MSM_BUS_TIERED_SLAVE_MM_IMEM, + + MSM_BUS_TIERED_SLAVE_EBI_CH0 = 1, + MSM_BUS_TIERED_SLAVE_SMPSS_L2, +}; + +enum msm_bus_8660_master_ports_type { + MSM_BUS_SYSTEM_MASTER_PORT_APPSS_FAB = 0, + MSM_BUS_MASTER_PORT_SPS, + MSM_BUS_MASTER_PORT_ADM0_PORT0, + MSM_BUS_MASTER_PORT_ADM0_PORT1, + MSM_BUS_MASTER_PORT_ADM1_PORT0, + MSM_BUS_MASTER_PORT_ADM1_PORT1, + MSM_BUS_MASTER_PORT_LPASS_PROC, + MSM_BUS_MASTER_PORT_MSS_PROCI, + MSM_BUS_MASTER_PORT_MSM_MSS_PROCD, + MSM_BUS_MASTER_PORT_MSM_MDM_PORT0, + MSM_BUS_MASTER_PORT_LPASS, + MSM_BUS_SYSTEM_MASTER_PORT_CPSS_FPB, + MSM_BUS_MASTER_PORT_SYSTEM_FPB, + MSM_BUS_MASTER_PORT_MMSS_FPB, + MSM_BUS_MASTER_PORT_ADM1_AHB_CI, + MSM_BUS_MASTER_PORT_ADM0_AHB_CI, + MSM_BUS_MASTER_PORT_MSS_MDM_PORT1, + + MSM_BUS_MASTER_PORT_MDP_PORT0 = 0, + MSM_BUS_MASTER_PORT_MDP_PORT1, + MSM_BUS_MMSS_MASTER_PORT_ADM1_PORT0, + MSM_BUS_MASTER_PORT_ROTATOR, + MSM_BUS_MASTER_PORT_GRAPHICS_3D, + MSM_BUS_MASTER_PORT_JPEG_DEC, + MSM_BUS_MASTER_PORT_GRAPHICS_2D_CORE0, + MSM_BUS_MASTER_PORT_VFE, + MSM_BUS_MASTER_PORT_VPE, + MSM_BUS_MASTER_PORT_JPEG_ENC, + MSM_BUS_MASTER_PORT_GRAPHICS_2D_CORE1, + MSM_BUS_MMSS_MASTER_PORT_APPS_FAB, + MSM_BUS_MASTER_PORT_HD_CODEC_PORT0, + MSM_BUS_MASTER_PORT_HD_CODEC_PORT1, + + MSM_BUS_MASTER_PORT_SMPSS_M0 = 0, + MSM_BUS_MASTER_PORT_SMPSS_M1, + MSM_BUS_APPSS_MASTER_PORT_FAB_MMSS, + MSM_BUS_APPSS_MASTER_PORT_FAB_SYSTEM, + +}; + +static int tier2[] = {MSM_BUS_BW_TIER2,}; + +enum msm_bus_8660_slave_ports_type { + MSM_BUS_SLAVE_PORT_SMI = 0, + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_0, + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_1, + MSM_BUS_SLAVE_PORT_MM_IMEM, + + MSM_BUS_SLAVE_PORT_EBI_CH0 = 0, + MSM_BUS_SLAVE_PORT_SMPSS_L2, + MSM_BUS_APPSS_SLAVE_PORT_MMSS_FAB, + MSM_BUS_SLAVE_PORT_SYSTEM_FAB, + + MSM_BUS_SYSTEM_SLAVE_PORT_APPSS_FAB = 0, + MSM_BUS_SLAVE_PORT_SPS, + MSM_BUS_SLAVE_PORT_SYSTEM_IMEM, + MSM_BUS_SLAVE_PORT_SMPSS, + MSM_BUS_SLAVE_PORT_MSS, + MSM_BUS_SLAVE_PORT_LPASS, + MSM_BUS_SYSTEM_SLAVE_PORT_CPSS_FPB, + MSM_BUS_SYSTEM_SLAVE_PORT_SYSTEM_FPB, + MSM_BUS_SLAVE_PORT_MMSS_FPB, +}; + +static uint32_t master_iids[NMASTERS]; +static uint32_t slave_iids[NSLAVES]; + +static int ampss_m0_mports[] = {MSM_BUS_MASTER_PORT_SMPSS_M0,}; +static int ampss_m1_mports[] = {MSM_BUS_MASTER_PORT_SMPSS_M1,}; +static int mmss_mport_apps_fab[] = {MSM_BUS_MMSS_MASTER_PORT_APPS_FAB,}; +static int system_mport_appss_fab[] = {MSM_BUS_SYSTEM_MASTER_PORT_APPSS_FAB,}; + +static int sport_ebi_ch0[] = {MSM_BUS_SLAVE_PORT_EBI_CH0,}; +static int sport_smpss_l2[] = {MSM_BUS_SLAVE_PORT_SMPSS_L2,}; +static int appss_sport_mmss_fab[] = {MSM_BUS_APPSS_SLAVE_PORT_MMSS_FAB,}; +static int sport_system_fab[] = {MSM_BUS_SLAVE_PORT_SYSTEM_FAB,}; + +static int tiered_slave_ebi[] = {MSM_BUS_TIERED_SLAVE_EBI_CH0,}; +static int tiered_slave_smpss[] = {MSM_BUS_TIERED_SLAVE_SMPSS_L2,}; + +static struct msm_bus_node_info apps_fabric_info[] = { + { + .id = MSM_BUS_MASTER_AMPSS_M0, + .masterp = ampss_m0_mports, + .num_mports = ARRAY_SIZE(ampss_m0_mports), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_AMPSS_M1, + .masterp = ampss_m1_mports, + .num_mports = ARRAY_SIZE(ampss_m1_mports), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_SLAVE_EBI_CH0, + .slavep = sport_ebi_ch0, + .num_sports = ARRAY_SIZE(sport_ebi_ch0), + .tier = tiered_slave_ebi, + .num_tiers = ARRAY_SIZE(tiered_slave_ebi), + .buswidth = 8, + .slaveclk[DUAL_CTX] = "ebi1_msmbus_clk", + .slaveclk[ACTIVE_CTX] = "ebi1_a_clk", + }, + { + .id = MSM_BUS_SLAVE_AMPSS_L2, + .slavep = sport_smpss_l2, + .num_sports = ARRAY_SIZE(sport_smpss_l2), + .tier = tiered_slave_smpss, + .num_tiers = ARRAY_SIZE(tiered_slave_smpss), + .buswidth = 8, + }, + { + .id = MSM_BUS_FAB_MMSS, + .gateway = 1, + .slavep = appss_sport_mmss_fab, + .num_sports = ARRAY_SIZE(appss_sport_mmss_fab), + .masterp = mmss_mport_apps_fab, + .num_mports = ARRAY_SIZE(mmss_mport_apps_fab), + .buswidth = 8, + }, + { + .id = MSM_BUS_FAB_SYSTEM, + .gateway = 1, + .slavep = sport_system_fab, + .num_sports = ARRAY_SIZE(sport_system_fab), + .masterp = system_mport_appss_fab, + .num_mports = ARRAY_SIZE(system_mport_appss_fab), + .buswidth = 8, + }, +}; + +static int mport_sps[] = {MSM_BUS_MASTER_PORT_SPS,}; +static int mport_adm0_port0[] = {MSM_BUS_MASTER_PORT_ADM0_PORT0,}; +static int mport_adm0_port1[] = {MSM_BUS_MASTER_PORT_ADM0_PORT1,}; +static int mport_adm1_port0[] = {MSM_BUS_MASTER_PORT_ADM1_PORT0,}; +static int mport_adm1_port1[] = {MSM_BUS_MASTER_PORT_ADM1_PORT1,}; +static int mport_lpass_proc[] = {MSM_BUS_MASTER_PORT_LPASS_PROC,}; +static int mport_mss_proci[] = {MSM_BUS_MASTER_PORT_MSS_PROCI,}; +static int mport_msm_mss_procd[] = {MSM_BUS_MASTER_PORT_MSM_MSS_PROCD,}; +static int mport_mss_mdm_port0[] = {MSM_BUS_MASTER_PORT_MSM_MDM_PORT0,}; +static int mport_lpass[] = {MSM_BUS_MASTER_PORT_LPASS,}; +static int mport_mmss_fpb[] = {MSM_BUS_MASTER_PORT_MMSS_FPB,}; +static int mport_adm1_ahb_ci[] = {MSM_BUS_MASTER_PORT_ADM1_AHB_CI,}; +static int mport_adm0_ahb_ci[] = {MSM_BUS_MASTER_PORT_ADM0_AHB_CI,}; +static int mport_mss_mdm_port1[] = {MSM_BUS_MASTER_PORT_MSS_MDM_PORT1,}; +static int appss_mport_fab_system[] = {MSM_BUS_APPSS_MASTER_PORT_FAB_SYSTEM,}; +static int mport_system_fpb[] = {MSM_BUS_MASTER_PORT_SYSTEM_FPB,}; +static int system_mport_cpss_fpb[] = {MSM_BUS_SYSTEM_MASTER_PORT_CPSS_FPB,}; + +static int system_sport_appss_fab[] = {MSM_BUS_SYSTEM_SLAVE_PORT_APPSS_FAB,}; +static int system_sport_system_fpb[] = {MSM_BUS_SYSTEM_SLAVE_PORT_SYSTEM_FPB,}; +static int system_sport_cpss_fpb[] = {MSM_BUS_SYSTEM_SLAVE_PORT_CPSS_FPB,}; +static int sport_sps[] = {MSM_BUS_SLAVE_PORT_SPS,}; +static int sport_system_imem[] = {MSM_BUS_SLAVE_PORT_SYSTEM_IMEM,}; +static int sport_smpss[] = {MSM_BUS_SLAVE_PORT_SMPSS,}; +static int sport_mss[] = {MSM_BUS_SLAVE_PORT_MSS,}; +static int sport_lpass[] = {MSM_BUS_SLAVE_PORT_LPASS,}; +static int sport_mmss_fpb[] = {MSM_BUS_SLAVE_PORT_MMSS_FPB,}; + +static int tiered_slave_system_imem[] = {MSM_BUS_TIERED_SLAVE_SYSTEM_IMEM,}; +static int system_tiered_slave_fab_appss[] = { + MSM_BUS_SYSTEM_TIERED_SLAVE_FAB_APPSS,}; + +static struct msm_bus_node_info system_fabric_info[] = { + { + .id = MSM_BUS_MASTER_SPS, + .masterp = mport_sps, + .num_mports = ARRAY_SIZE(mport_sps), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ADM_PORT0, + .masterp = mport_adm0_port0, + .num_mports = ARRAY_SIZE(mport_adm0_port0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ADM_PORT1, + .masterp = mport_adm0_port1, + .num_mports = ARRAY_SIZE(mport_adm0_port1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_SYSTEM_MASTER_ADM1_PORT0, + .masterp = mport_adm1_port0, + .num_mports = ARRAY_SIZE(mport_adm1_port0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ADM1_PORT1, + .masterp = mport_adm1_port1, + .num_mports = ARRAY_SIZE(mport_adm1_port1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_LPASS_PROC, + .masterp = mport_lpass_proc, + .num_mports = ARRAY_SIZE(mport_lpass_proc), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MSS_PROCI, + .masterp = mport_mss_proci, + .num_mports = ARRAY_SIZE(mport_mss_proci), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MSS_PROCD, + .masterp = mport_msm_mss_procd, + .num_mports = ARRAY_SIZE(mport_msm_mss_procd), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MSS_MDM_PORT0, + .masterp = mport_mss_mdm_port0, + .num_mports = ARRAY_SIZE(mport_mss_mdm_port0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_LPASS, + .masterp = mport_lpass, + .num_mports = ARRAY_SIZE(mport_lpass), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_SYSTEM_MASTER_MMSS_FPB, + .masterp = mport_mmss_fpb, + .num_mports = ARRAY_SIZE(mport_mmss_fpb), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ADM1_CI, + .masterp = mport_adm1_ahb_ci, + .num_mports = ARRAY_SIZE(mport_adm1_ahb_ci), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ADM0_CI, + .masterp = mport_adm0_ahb_ci, + .num_mports = ARRAY_SIZE(mport_adm0_ahb_ci), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MSS_MDM_PORT1, + .masterp = mport_mss_mdm_port1, + .num_mports = ARRAY_SIZE(mport_mss_mdm_port1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_FAB_APPSS, + .gateway = 1, + .slavep = system_sport_appss_fab, + .num_sports = ARRAY_SIZE(system_sport_appss_fab), + .masterp = appss_mport_fab_system, + .num_mports = ARRAY_SIZE(appss_mport_fab_system), + .tier = system_tiered_slave_fab_appss, + .num_tiers = ARRAY_SIZE(system_tiered_slave_fab_appss), + .buswidth = 8, + }, + { + .id = MSM_BUS_FAB_SYSTEM_FPB, + .gateway = 1, + .slavep = system_sport_system_fpb, + .num_sports = ARRAY_SIZE(system_sport_system_fpb), + .masterp = mport_system_fpb, + .num_mports = ARRAY_SIZE(mport_system_fpb), + .buswidth = 4, + }, + { + .id = MSM_BUS_FAB_CPSS_FPB, + .gateway = 1, + .slavep = system_sport_cpss_fpb, + .num_sports = ARRAY_SIZE(system_sport_cpss_fpb), + .masterp = system_mport_cpss_fpb, + .num_mports = ARRAY_SIZE(system_mport_cpss_fpb), + .buswidth = 4, + }, + { + .id = MSM_BUS_SLAVE_SPS, + .slavep = sport_sps, + .num_sports = ARRAY_SIZE(sport_sps), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_SYSTEM_IMEM, + .slavep = sport_system_imem, + .num_sports = ARRAY_SIZE(sport_system_imem), + .tier = tiered_slave_system_imem, + .num_tiers = ARRAY_SIZE(tiered_slave_system_imem), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_AMPSS, + .slavep = sport_smpss, + .num_sports = ARRAY_SIZE(sport_smpss), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_MSS, + .slavep = sport_mss, + .num_sports = ARRAY_SIZE(sport_mss), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_LPASS, + .slavep = sport_lpass, + .num_sports = ARRAY_SIZE(sport_lpass), + .buswidth = 8, + }, + { + .id = MSM_BUS_SYSTEM_SLAVE_MMSS_FPB, + .slavep = sport_mmss_fpb, + .num_sports = ARRAY_SIZE(sport_mmss_fpb), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, +}; + +static int mport_mdp_port0[] = {MSM_BUS_MASTER_PORT_MDP_PORT0,}; +static int mport_mdp_port1[] = {MSM_BUS_MASTER_PORT_MDP_PORT1,}; +static int mmss_mport_adm1_port0[] = {MSM_BUS_MMSS_MASTER_PORT_ADM1_PORT0,}; +static int mport_rotator[] = {MSM_BUS_MASTER_PORT_ROTATOR,}; +static int mport_graphics_3d[] = {MSM_BUS_MASTER_PORT_GRAPHICS_3D,}; +static int mport_jpeg_dec[] = {MSM_BUS_MASTER_PORT_JPEG_DEC,}; +static int mport_graphics_2d_core0[] = {MSM_BUS_MASTER_PORT_GRAPHICS_2D_CORE0,}; +static int mport_vfe[] = {MSM_BUS_MASTER_PORT_VFE,}; +static int mport_vpe[] = {MSM_BUS_MASTER_PORT_VPE,}; +static int mport_jpeg_enc[] = {MSM_BUS_MASTER_PORT_JPEG_ENC,}; +static int mport_graphics_2d_core1[] = {MSM_BUS_MASTER_PORT_GRAPHICS_2D_CORE1,}; +static int mport_hd_codec_port0[] = {MSM_BUS_MASTER_PORT_HD_CODEC_PORT0,}; +static int mport_hd_codec_port1[] = {MSM_BUS_MASTER_PORT_HD_CODEC_PORT1,}; +static int appss_mport_fab_mmss[] = {MSM_BUS_APPSS_MASTER_PORT_FAB_MMSS,}; + +static int sport_smi[] = {MSM_BUS_SLAVE_PORT_SMI,}; +static int mmss_sport_apps_fab_0[] = {MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_0,}; +static int sport_mm_imem[] = {MSM_BUS_SLAVE_PORT_MM_IMEM,}; + +static int tiered_slave_smi[] = {MSM_BUS_TIERED_SLAVE_SMI,}; +static int mmss_tiered_slave_fab_apps[] = {MSM_BUS_MMSS_TIERED_SLAVE_FAB_APPS,}; +static int tiered_slave_mm_imem[] = {MSM_BUS_TIERED_SLAVE_MM_IMEM,}; + +static struct msm_bus_node_info mmss_fabric_info[] = { + { + .id = MSM_BUS_MASTER_MDP_PORT0, + .masterp = mport_mdp_port0, + .num_mports = ARRAY_SIZE(mport_mdp_port0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MDP_PORT1, + .masterp = mport_mdp_port1, + .num_mports = ARRAY_SIZE(mport_mdp_port1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MMSS_MASTER_ADM1_PORT0, + .masterp = mmss_mport_adm1_port0, + .num_mports = ARRAY_SIZE(mmss_mport_adm1_port0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ROTATOR, + .masterp = mport_rotator, + .num_mports = ARRAY_SIZE(mport_rotator), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_GRAPHICS_3D, + .masterp = mport_graphics_3d, + .num_mports = ARRAY_SIZE(mport_graphics_3d), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_JPEG_DEC, + .masterp = mport_jpeg_dec, + .num_mports = ARRAY_SIZE(mport_jpeg_dec), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, + .masterp = mport_graphics_2d_core0, + .num_mports = ARRAY_SIZE(mport_graphics_2d_core0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_VFE, + .masterp = mport_vfe, + .num_mports = ARRAY_SIZE(mport_vfe), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_VPE, + .masterp = mport_vpe, + .num_mports = ARRAY_SIZE(mport_vpe), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_JPEG_ENC, + .masterp = mport_jpeg_enc, + .num_mports = ARRAY_SIZE(mport_jpeg_enc), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + /* This port has been added for V2. It is absent in V1 */ + { + .id = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, + .masterp = mport_graphics_2d_core1, + .num_mports = ARRAY_SIZE(mport_graphics_2d_core1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_HD_CODEC_PORT0, + .masterp = mport_hd_codec_port0, + .num_mports = ARRAY_SIZE(mport_hd_codec_port0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_HD_CODEC_PORT1, + .masterp = mport_hd_codec_port1, + .num_mports = ARRAY_SIZE(mport_hd_codec_port1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_SLAVE_SMI, + .slavep = sport_smi, + .num_sports = ARRAY_SIZE(sport_smi), + .tier = tiered_slave_smi, + .num_tiers = ARRAY_SIZE(tiered_slave_smi), + .buswidth = 16, + .slaveclk[DUAL_CTX] = "smi_clk", + .slaveclk[ACTIVE_CTX] = "smi_a_clk", + }, + { + .id = MSM_BUS_MMSS_SLAVE_FAB_APPS_1, + .slavep = mmss_sport_apps_fab_0, + .num_sports = ARRAY_SIZE(mmss_sport_apps_fab_0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, + { + .id = MSM_BUS_FAB_APPSS, + .gateway = 1, + .slavep = mmss_sport_apps_fab_0, + .num_sports = ARRAY_SIZE(mmss_sport_apps_fab_0), + .masterp = appss_mport_fab_mmss, + .num_mports = ARRAY_SIZE(appss_mport_fab_mmss), + .tier = mmss_tiered_slave_fab_apps, + .num_tiers = ARRAY_SIZE(mmss_tiered_slave_fab_apps), + .buswidth = 16, + }, + { + .id = MSM_BUS_SLAVE_MM_IMEM, + .slavep = sport_mm_imem, + .num_sports = ARRAY_SIZE(sport_mm_imem), + .tier = tiered_slave_mm_imem, + .num_tiers = ARRAY_SIZE(tiered_slave_mm_imem), + .buswidth = 8, + }, +}; + +static struct msm_bus_node_info sys_fpb_fabric_info[] = { + { + .id = MSM_BUS_FAB_SYSTEM, + .gateway = 1, + .slavep = system_sport_system_fpb, + .num_sports = ARRAY_SIZE(system_sport_system_fpb), + .masterp = mport_system_fpb, + .num_mports = ARRAY_SIZE(mport_system_fpb), + .buswidth = 4, + .ahb = 1, + }, + { + .id = MSM_BUS_MASTER_SPDM, + .ahb = 1, + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_RPM, + .ahb = 1, + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_SLAVE_SPDM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_RPM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_RPM_MSG_RAM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MPM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC1_SSBI1_A, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC1_SSBI1_B, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC1_SSBI1_C, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC2_SSBI2_A, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC2_SSBI2_B, + .buswidth = 4, + .ahb = 1, + }, +}; + +static struct msm_bus_node_info cpss_fpb_fabric_info[] = { + { + .id = MSM_BUS_FAB_SYSTEM, + .gateway = 1, + .slavep = system_sport_cpss_fpb, + .num_sports = ARRAY_SIZE(system_sport_cpss_fpb), + .masterp = system_mport_cpss_fpb, + .num_mports = ARRAY_SIZE(system_mport_cpss_fpb), + .buswidth = 4, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI1_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI2_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI3_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI4_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI5_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI6_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI7_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI8_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI9_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI10_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI11_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI12_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI1_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI2_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI3_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI4_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI5_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI6_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI7_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI8_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI9_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI10_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI11_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI12_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_NAND, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS0, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS1, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS2, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS3, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS4, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS5, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_USB_FS1, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_USB_FS2, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_TSIF, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_TSSC, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_PDM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_DIMEM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_TCSR, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_PRNG, + .buswidth = 4, + .ahb = 1, + }, +}; + +struct msm_bus_fabric_registration msm_bus_apps_fabric_pdata = { + .id = MSM_BUS_FAB_APPSS, + .name = "msm_apps_fab", + .info = apps_fabric_info, + .len = ARRAY_SIZE(apps_fabric_info), + .ahb = 0, + .fabclk[DUAL_CTX] = "afab_clk", + .fabclk[ACTIVE_CTX] = "afab_a_clk", + .haltid = MSM_RPM_ID_APPS_FABRIC_HALT_0, + .offset = MSM_RPM_ID_APPS_FABRIC_ARB_0, + .nmasters = 4, + .nslaves = 4, + .ntieredslaves = 2, +}; + +struct msm_bus_fabric_registration msm_bus_sys_fabric_pdata = { + .id = MSM_BUS_FAB_SYSTEM, + .name = "msm_sys_fab", + system_fabric_info, + ARRAY_SIZE(system_fabric_info), + .ahb = 0, + .fabclk[DUAL_CTX] = "sfab_clk", + .fabclk[ACTIVE_CTX] = "sfab_a_clk", + .haltid = MSM_RPM_ID_SYSTEM_FABRIC_HALT_0, + .offset = MSM_RPM_ID_SYSTEM_FABRIC_ARB_0, + .nmasters = 17, + .nslaves = 9, + .ntieredslaves = 2, +}; + +struct msm_bus_fabric_registration msm_bus_mm_fabric_pdata = { + .id = MSM_BUS_FAB_MMSS, + .name = "msm_mm_fab", + mmss_fabric_info, + ARRAY_SIZE(mmss_fabric_info), + .ahb = 0, + .fabclk[DUAL_CTX] = "mmfab_clk", + .fabclk[ACTIVE_CTX] = "mmfab_a_clk", + .haltid = MSM_RPM_ID_MM_FABRIC_HALT_0, + .offset = MSM_RPM_ID_MM_FABRIC_ARB_0, + .nmasters = 14, + .nslaves = 4, + .ntieredslaves = 3, +}; + +struct msm_bus_fabric_registration msm_bus_sys_fpb_pdata = { + .id = MSM_BUS_FAB_SYSTEM_FPB, + .name = "msm_sys_fpb", + sys_fpb_fabric_info, + ARRAY_SIZE(sys_fpb_fabric_info), + .ahb = 1, + .fabclk[DUAL_CTX] = "sfpb_clk", + .fabclk[ACTIVE_CTX] = "sfpb_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, +}; + +struct msm_bus_fabric_registration msm_bus_cpss_fpb_pdata = { + .id = MSM_BUS_FAB_CPSS_FPB, + .name = "msm_cpss_fpb", + cpss_fpb_fabric_info, + ARRAY_SIZE(cpss_fpb_fabric_info), + .ahb = 1, + .fabclk[DUAL_CTX] = "cfpb_clk", + .fabclk[ACTIVE_CTX] = "cfpb_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, +}; + +static void msm_bus_board_get_ids( + struct msm_bus_fabric_registration *fabreg, + int fabid) +{ + int i; + for (i = 0; i < fabreg->len; i++) { + if (!fabreg->info[i].gateway) { + fabreg->info[i].priv_id = fabid + fabreg->info[i].id; + if (fabreg->info[i].id < SLAVE_ID_KEY) + master_iids[fabreg->info[i].id] = + fabreg->info[i].priv_id; + else + slave_iids[fabreg->info[i].id - (SLAVE_ID_KEY)] + = fabreg->info[i].priv_id; + } else + fabreg->info[i].priv_id = fabreg->info[i].id; + } +} + +void msm_bus_board_assign_iids(struct msm_bus_fabric_registration *fabreg, + int fabid) +{ + msm_bus_board_get_ids(fabreg, fabid); +} +int msm_bus_board_get_iid(int id) +{ + return ((id < SLAVE_ID_KEY) ? master_iids[id] : slave_iids[id - + SLAVE_ID_KEY]); +} + diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_board_8960.c b/arch/arm/mach-msm/msm_bus/msm_bus_board_8960.c new file mode 100644 index 00000000000..151058c38d8 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_board_8960.c @@ -0,0 +1,931 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" + +#define NMASTERS 45 +#define NSLAVES 75 + +enum msm_bus_fabric_tiered_slave_type { + MSM_BUS_SYSTEM_TIERED_SLAVE_FAB_APPSS_0 = 1, + MSM_BUS_SYSTEM_TIERED_SLAVE_FAB_APPSS_1, + MSM_BUS_TIERED_SLAVE_SYSTEM_IMEM, + + MSM_BUS_MMSS_TIERED_SLAVE_FAB_APPS_0 = 1, + MSM_BUS_MMSS_TIERED_SLAVE_FAB_APPS_1, + MSM_BUS_TIERED_SLAVE_MM_IMEM, + + MSM_BUS_TIERED_SLAVE_EBI1_CH0 = 1, + MSM_BUS_TIERED_SLAVE_EBI1_CH1, + MSM_BUS_TIERED_SLAVE_KMPSS_L2, +}; + +enum msm_bus_8960_master_ports_type { + MSM_BUS_SYSTEM_MASTER_PORT_APPSS_FAB = 0, + MSM_BUS_MASTER_PORT_SPS, + MSM_BUS_MASTER_PORT_ADM_PORT0, + MSM_BUS_MASTER_PORT_ADM_PORT1, + MSM_BUS_MASTER_PORT_LPASS_PROC, + MSM_BUS_MASTER_PORT_MSS, + MSM_BUS_SYSTEM_MASTER_PORT_UNUSED_6, + MSM_BUS_MASTER_PORT_RIVA, + MSM_BUS_MASTER_PORT_MSS_SW_PROC, + MSM_BUS_MASTER_PORT_MSS_FW_PROC, + MSM_BUS_MASTER_PORT_LPASS, + MSM_BUS_SYSTEM_MASTER_PORT_CPSS_FPB, + MSM_BUS_SYSTEM_MASTER_PORT_SYSTEM_FPB, + MSM_BUS_SYSTEM_MASTER_PORT_MMSS_FPB, + MSM_BUS_SYSTEM_MASTER_PORT_ADM_AHB_CI, + + MSM_BUS_MASTER_PORT_MDP_PORT0 = 0, + MSM_BUS_MASTER_PORT_MDP_PORT1, + MSM_BUS_MMSS_MASTER_PORT_UNUSED_2, + MSM_BUS_MASTER_PORT_ROTATOR, + MSM_BUS_MASTER_PORT_GRAPHICS_3D, + MSM_BUS_MASTER_PORT_JPEG_DEC, + MSM_BUS_MASTER_PORT_GRAPHICS_2D_CORE0, + MSM_BUS_MASTER_PORT_VFE, + MSM_BUS_MASTER_PORT_VPE, + MSM_BUS_MASTER_PORT_JPEG_ENC, + MSM_BUS_MASTER_PORT_GRAPHICS_2D_CORE1, + MSM_BUS_MMSS_MASTER_PORT_APPS_FAB, + MSM_BUS_MASTER_PORT_HD_CODEC_PORT0, + MSM_BUS_MASTER_PORT_HD_CODEC_PORT1, + + MSM_BUS_MASTER_PORT_KMPSS_M0 = 0, + MSM_BUS_MASTER_PORT_KMPSS_M1, + MSM_BUS_APPSS_MASTER_PORT_FAB_MMSS_0, + MSM_BUS_APPSS_MASTER_PORT_FAB_MMSS_1, + MSM_BUS_APPSS_MASTER_PORT_FAB_SYSTEM_0, + MSM_BUS_APPSS_MASTER_PORT_FAB_SYSTEM_1, + +}; + +enum msm_bus_8660_slave_ports_type { + MSM_BUS_MMSS_SLAVE_PORT_UNUSED_0 = 0, + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_0, + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_1, + MSM_BUS_SLAVE_PORT_MM_IMEM, + + MSM_BUS_SLAVE_PORT_EBI1_CH0 = 0, + MSM_BUS_SLAVE_PORT_EBI1_CH1, + MSM_BUS_SLAVE_PORT_KMPSS_L2, + MSM_BUS_APPSS_SLAVE_PORT_MMSS_FAB, + MSM_BUS_SLAVE_PORT_SYSTEM_FAB, + + MSM_BUS_SYSTEM_SLAVE_PORT_APPSS_FAB_0 = 0, + MSM_BUS_SYSTEM_SLAVE_PORT_APPSS_FAB_1, + MSM_BUS_SLAVE_PORT_SPS, + MSM_BUS_SLAVE_PORT_SYSTEM_IMEM, + MSM_BUS_SLAVE_PORT_CORESIGHT, + MSM_BUS_SLAVE_PORT_KMPSS, + MSM_BUS_SLAVE_PORT_MSS, + MSM_BUS_SLAVE_PORT_LPASS, + MSM_BUS_SYSTEM_SLAVE_PORT_CPSS_FPB, + MSM_BUS_SYSTEM_SLAVE_PORT_SYSTEM_FPB, + MSM_BUS_SYSTEM_SLAVE_PORT_MMSS_FPB, + MSM_BUS_SLAVE_PORT_RIVA, +}; + +static int tier2[] = {MSM_BUS_BW_TIER2,}; +static int tier1[] = {MSM_BUS_BW_TIER1,}; +static uint32_t master_iids[NMASTERS]; +static uint32_t slave_iids[NSLAVES]; + +static int mport_kmpss_m0[] = {MSM_BUS_MASTER_PORT_KMPSS_M0,}; +static int mport_kmpss_m1[] = {MSM_BUS_MASTER_PORT_KMPSS_M1,}; + +static int mmss_mport_apps_fab[] = {MSM_BUS_MMSS_MASTER_PORT_APPS_FAB,}; +static int system_mport_appss_fab[] = {MSM_BUS_SYSTEM_MASTER_PORT_APPSS_FAB,}; +static int sport_ebi1_ch0[] = {MSM_BUS_SLAVE_PORT_EBI1_CH0,}; +static int sport_ebi1_ch1[] = {MSM_BUS_SLAVE_PORT_EBI1_CH1,}; +static int sport_kmpss_l2[] = {MSM_BUS_SLAVE_PORT_KMPSS_L2,}; +static int appss_sport_mmss_fab[] = {MSM_BUS_APPSS_SLAVE_PORT_MMSS_FAB,}; +static int sport_system_fab[] = {MSM_BUS_SLAVE_PORT_SYSTEM_FAB,}; + +static int tiered_slave_ebi1_ch0[] = {MSM_BUS_TIERED_SLAVE_EBI1_CH0,}; +static int tiered_slave_ebi1_ch1[] = {MSM_BUS_TIERED_SLAVE_EBI1_CH1,}; + +static int tiered_slave_kmpss[] = {MSM_BUS_TIERED_SLAVE_KMPSS_L2,}; + +static struct msm_bus_node_info apps_fabric_info[] = { + { + .id = MSM_BUS_MASTER_AMPSS_M0, + .masterp = mport_kmpss_m0, + .num_mports = ARRAY_SIZE(mport_kmpss_m0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_AMPSS_M1, + .masterp = mport_kmpss_m1, + .num_mports = ARRAY_SIZE(mport_kmpss_m1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_SLAVE_EBI_CH0, + .slavep = sport_ebi1_ch0, + .num_sports = ARRAY_SIZE(sport_ebi1_ch0), + .tier = tiered_slave_ebi1_ch0, + .num_tiers = ARRAY_SIZE(tiered_slave_ebi1_ch0), + .buswidth = 8, + .slaveclk[DUAL_CTX] = "ebi1_msmbus_clk", + .slaveclk[ACTIVE_CTX] = "ebi1_a_clk", + }, + { + .id = MSM_BUS_SLAVE_EBI_CH1, + .slavep = sport_ebi1_ch1, + .num_sports = ARRAY_SIZE(sport_ebi1_ch1), + .tier = tiered_slave_ebi1_ch1, + .num_tiers = ARRAY_SIZE(tiered_slave_ebi1_ch1), + .buswidth = 8, + .slaveclk[DUAL_CTX] = "ebi1_msmbus_clk", + .slaveclk[ACTIVE_CTX] = "ebi1_a_clk", + }, + { + .id = MSM_BUS_SLAVE_AMPSS_L2, + .slavep = sport_kmpss_l2, + .num_sports = ARRAY_SIZE(sport_kmpss_l2), + .tier = tiered_slave_kmpss, + .num_tiers = ARRAY_SIZE(tiered_slave_kmpss), + .buswidth = 8, + }, + { + .id = MSM_BUS_FAB_MMSS, + .gateway = 1, + .slavep = appss_sport_mmss_fab, + .num_sports = ARRAY_SIZE(appss_sport_mmss_fab), + .masterp = mmss_mport_apps_fab, + .num_mports = ARRAY_SIZE(mmss_mport_apps_fab), + .buswidth = 8, + }, + { + .id = MSM_BUS_FAB_SYSTEM, + .gateway = 1, + .slavep = sport_system_fab, + .num_sports = ARRAY_SIZE(sport_system_fab), + .masterp = system_mport_appss_fab, + .num_mports = ARRAY_SIZE(system_mport_appss_fab), + .buswidth = 8, + }, +}; + +static int mport_sps[] = {MSM_BUS_MASTER_PORT_SPS,}; +static int mport_adm_port0[] = {MSM_BUS_MASTER_PORT_ADM_PORT0,}; +static int mport_adm_port1[] = {MSM_BUS_MASTER_PORT_ADM_PORT1,}; +static int mport_mss[] = {MSM_BUS_MASTER_PORT_MSS,}; +static int mport_lpass_proc[] = {MSM_BUS_MASTER_PORT_LPASS_PROC,}; +static int system_mport_unused_6[] = {MSM_BUS_SYSTEM_MASTER_PORT_UNUSED_6,}; +static int mport_riva[] = {MSM_BUS_MASTER_PORT_RIVA,}; +static int mport_mss_sw_proc[] = {MSM_BUS_MASTER_PORT_MSS_SW_PROC,}; +static int mport_mss_fw_proc[] = {MSM_BUS_MASTER_PORT_MSS_FW_PROC,}; +static int mport_lpass[] = {MSM_BUS_MASTER_PORT_LPASS,}; +static int system_mport_mmss_fpb[] = {MSM_BUS_SYSTEM_MASTER_PORT_MMSS_FPB,}; +static int system_mport_adm_ahb_ci[] = {MSM_BUS_SYSTEM_MASTER_PORT_ADM_AHB_CI,}; +static int appss_mport_fab_system[] = { + MSM_BUS_APPSS_MASTER_PORT_FAB_SYSTEM_0, + MSM_BUS_APPSS_MASTER_PORT_FAB_SYSTEM_1 +}; +static int mport_system_fpb[] = {MSM_BUS_SYSTEM_MASTER_PORT_SYSTEM_FPB,}; +static int system_mport_cpss_fpb[] = {MSM_BUS_SYSTEM_MASTER_PORT_CPSS_FPB,}; + +static int system_sport_appss_fab[] = { + MSM_BUS_SYSTEM_SLAVE_PORT_APPSS_FAB_0, + MSM_BUS_SYSTEM_SLAVE_PORT_APPSS_FAB_1 +}; +static int system_sport_system_fpb[] = {MSM_BUS_SYSTEM_SLAVE_PORT_SYSTEM_FPB,}; +static int system_sport_cpss_fpb[] = {MSM_BUS_SYSTEM_SLAVE_PORT_CPSS_FPB,}; +static int sport_sps[] = {MSM_BUS_SLAVE_PORT_SPS,}; +static int sport_system_imem[] = {MSM_BUS_SLAVE_PORT_SYSTEM_IMEM,}; +static int sport_coresight[] = {MSM_BUS_SLAVE_PORT_CORESIGHT,}; +static int sport_riva[] = {MSM_BUS_SLAVE_PORT_RIVA,}; +static int sport_kmpss[] = {MSM_BUS_SLAVE_PORT_KMPSS,}; +static int sport_mss[] = {MSM_BUS_SLAVE_PORT_MSS,}; +static int sport_lpass[] = {MSM_BUS_SLAVE_PORT_LPASS,}; +static int sport_mmss_fpb[] = {MSM_BUS_SYSTEM_SLAVE_PORT_MMSS_FPB,}; + +static int tiered_slave_system_imem[] = {MSM_BUS_TIERED_SLAVE_SYSTEM_IMEM,}; +static int system_tiered_slave_fab_appss[] = { + MSM_BUS_SYSTEM_TIERED_SLAVE_FAB_APPSS_0, + MSM_BUS_SYSTEM_TIERED_SLAVE_FAB_APPSS_1, +}; + +static struct msm_bus_node_info system_fabric_info[] = { + { + .id = MSM_BUS_MASTER_SPS, + .masterp = mport_sps, + .num_mports = ARRAY_SIZE(mport_sps), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ADM_PORT0, + .masterp = mport_adm_port0, + .num_mports = ARRAY_SIZE(mport_adm_port0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ADM_PORT1, + .masterp = mport_adm_port1, + .num_mports = ARRAY_SIZE(mport_adm_port1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_LPASS_PROC, + .masterp = mport_lpass_proc, + .num_mports = ARRAY_SIZE(mport_lpass_proc), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MSS, + .masterp = mport_mss, + .num_mports = ARRAY_SIZE(mport_mss), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_SYSTEM_MASTER_UNUSED_6, + .masterp = system_mport_unused_6, + .num_mports = ARRAY_SIZE(system_mport_unused_6), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_RIVA, + .masterp = mport_riva, + .num_mports = ARRAY_SIZE(mport_riva), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MSS_SW_PROC, + .masterp = mport_mss_sw_proc, + .num_mports = ARRAY_SIZE(mport_mss_sw_proc), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_MSS_FW_PROC, + .masterp = mport_mss_fw_proc, + .num_mports = ARRAY_SIZE(mport_mss_fw_proc), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_LPASS, + .masterp = mport_lpass, + .num_mports = ARRAY_SIZE(mport_lpass), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_SYSTEM_MASTER_MMSS_FPB, + .masterp = system_mport_mmss_fpb, + .num_mports = ARRAY_SIZE(system_mport_mmss_fpb), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ADM0_CI, + .masterp = system_mport_adm_ahb_ci, + .num_mports = ARRAY_SIZE(system_mport_adm_ahb_ci), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_FAB_APPSS, + .gateway = 1, + .slavep = system_sport_appss_fab, + .num_sports = ARRAY_SIZE(system_sport_appss_fab), + .masterp = appss_mport_fab_system, + .num_mports = ARRAY_SIZE(appss_mport_fab_system), + .tier = system_tiered_slave_fab_appss, + .num_tiers = ARRAY_SIZE(system_tiered_slave_fab_appss), + .buswidth = 8, + }, + { + .id = MSM_BUS_FAB_SYSTEM_FPB, + .gateway = 1, + .slavep = system_sport_system_fpb, + .num_sports = ARRAY_SIZE(system_sport_system_fpb), + .masterp = mport_system_fpb, + .num_mports = ARRAY_SIZE(mport_system_fpb), + .buswidth = 4, + }, + { + .id = MSM_BUS_FAB_CPSS_FPB, + .gateway = 1, + .slavep = system_sport_cpss_fpb, + .num_sports = ARRAY_SIZE(system_sport_cpss_fpb), + .masterp = system_mport_cpss_fpb, + .num_mports = ARRAY_SIZE(system_mport_cpss_fpb), + .buswidth = 4, + }, + { + .id = MSM_BUS_SLAVE_SPS, + .slavep = sport_sps, + .num_sports = ARRAY_SIZE(sport_sps), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_SYSTEM_IMEM, + .slavep = sport_system_imem, + .num_sports = ARRAY_SIZE(sport_system_imem), + .tier = tiered_slave_system_imem, + .num_tiers = ARRAY_SIZE(tiered_slave_system_imem), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_CORESIGHT, + .slavep = sport_coresight, + .num_sports = ARRAY_SIZE(sport_coresight), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_RIVA, + .slavep = sport_riva, + .num_sports = ARRAY_SIZE(sport_riva), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_AMPSS, + .slavep = sport_kmpss, + .num_sports = ARRAY_SIZE(sport_kmpss), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_MSS, + .slavep = sport_mss, + .num_sports = ARRAY_SIZE(sport_mss), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, + { + .id = MSM_BUS_SLAVE_LPASS, + .slavep = sport_lpass, + .num_sports = ARRAY_SIZE(sport_lpass), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, + { + .id = MSM_BUS_SYSTEM_SLAVE_MMSS_FPB, + .slavep = sport_mmss_fpb, + .num_sports = ARRAY_SIZE(sport_mmss_fpb), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + .buswidth = 8, + }, +}; + +static int mport_mdp[] = { + MSM_BUS_MASTER_PORT_MDP_PORT0, + MSM_BUS_MASTER_PORT_MDP_PORT1, +}; + +static int mmss_mport_unused_2[] = {MSM_BUS_MMSS_MASTER_PORT_UNUSED_2,}; +static int mport_rotator[] = {MSM_BUS_MASTER_PORT_ROTATOR,}; +static int mport_graphics_3d[] = {MSM_BUS_MASTER_PORT_GRAPHICS_3D,}; +static int mport_jpeg_dec[] = {MSM_BUS_MASTER_PORT_JPEG_DEC,}; +static int mport_graphics_2d_core0[] = {MSM_BUS_MASTER_PORT_GRAPHICS_2D_CORE0,}; +static int mport_vfe[] = {MSM_BUS_MASTER_PORT_VFE,}; +static int mport_vpe[] = {MSM_BUS_MASTER_PORT_VPE,}; +static int mport_jpeg_enc[] = {MSM_BUS_MASTER_PORT_JPEG_ENC,}; +static int mport_graphics_2d_core1[] = {MSM_BUS_MASTER_PORT_GRAPHICS_2D_CORE1,}; +static int mport_hd_codec_port0[] = {MSM_BUS_MASTER_PORT_HD_CODEC_PORT0,}; +static int mport_hd_codec_port1[] = {MSM_BUS_MASTER_PORT_HD_CODEC_PORT1,}; +static int appss_mport_fab_mmss[] = { + MSM_BUS_APPSS_MASTER_PORT_FAB_MMSS_0, + MSM_BUS_APPSS_MASTER_PORT_FAB_MMSS_1 +}; + +static int mmss_sport_apps_fab[] = { + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_0, + MSM_BUS_MMSS_SLAVE_PORT_APPS_FAB_1 +}; +static int sport_mm_imem[] = {MSM_BUS_SLAVE_PORT_MM_IMEM,}; + +static int mmss_tiered_slave_fab_apps[] = { + MSM_BUS_MMSS_TIERED_SLAVE_FAB_APPS_0, + MSM_BUS_MMSS_TIERED_SLAVE_FAB_APPS_1, +}; +static int tiered_slave_mm_imem[] = {MSM_BUS_TIERED_SLAVE_MM_IMEM,}; + + +static struct msm_bus_node_info mmss_fabric_info[] = { + { + .id = MSM_BUS_MASTER_MDP_PORT0, + .masterp = mport_mdp, + .num_mports = ARRAY_SIZE(mport_mdp), + .tier = tier1, + .num_tiers = ARRAY_SIZE(tier1), + }, + { + .id = MSM_BUS_MMSS_MASTER_UNUSED_2, + .masterp = mmss_mport_unused_2, + .num_mports = ARRAY_SIZE(mmss_mport_unused_2), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_ROTATOR, + .masterp = mport_rotator, + .num_mports = ARRAY_SIZE(mport_rotator), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_GRAPHICS_3D, + .masterp = mport_graphics_3d, + .num_mports = ARRAY_SIZE(mport_graphics_3d), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_JPEG_DEC, + .masterp = mport_jpeg_dec, + .num_mports = ARRAY_SIZE(mport_jpeg_dec), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_GRAPHICS_2D_CORE0, + .masterp = mport_graphics_2d_core0, + .num_mports = ARRAY_SIZE(mport_graphics_2d_core0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_VFE, + .masterp = mport_vfe, + .num_mports = ARRAY_SIZE(mport_vfe), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_VPE, + .masterp = mport_vpe, + .num_mports = ARRAY_SIZE(mport_vpe), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_JPEG_ENC, + .masterp = mport_jpeg_enc, + .num_mports = ARRAY_SIZE(mport_jpeg_enc), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + /* This port has been added for V2. It is absent in V1 */ + { + .id = MSM_BUS_MASTER_GRAPHICS_2D_CORE1, + .masterp = mport_graphics_2d_core1, + .num_mports = ARRAY_SIZE(mport_graphics_2d_core1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_HD_CODEC_PORT0, + .masterp = mport_hd_codec_port0, + .num_mports = ARRAY_SIZE(mport_hd_codec_port0), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_HD_CODEC_PORT1, + .masterp = mport_hd_codec_port1, + .num_mports = ARRAY_SIZE(mport_hd_codec_port1), + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_FAB_APPSS, + .gateway = 1, + .slavep = mmss_sport_apps_fab, + .num_sports = ARRAY_SIZE(mmss_sport_apps_fab), + .masterp = appss_mport_fab_mmss, + .num_mports = ARRAY_SIZE(appss_mport_fab_mmss), + .tier = mmss_tiered_slave_fab_apps, + .num_tiers = ARRAY_SIZE(mmss_tiered_slave_fab_apps), + .buswidth = 16, + }, + { + .id = MSM_BUS_SLAVE_MM_IMEM, + .slavep = sport_mm_imem, + .num_sports = ARRAY_SIZE(sport_mm_imem), + .tier = tiered_slave_mm_imem, + .num_tiers = ARRAY_SIZE(tiered_slave_mm_imem), + .buswidth = 8, + }, +}; + +static struct msm_bus_node_info sys_fpb_fabric_info[] = { + { + .id = MSM_BUS_FAB_SYSTEM, + .gateway = 1, + .slavep = system_sport_system_fpb, + .num_sports = ARRAY_SIZE(system_sport_system_fpb), + .masterp = mport_system_fpb, + .num_mports = ARRAY_SIZE(mport_system_fpb), + .buswidth = 4, + .ahb = 1, + }, + { + .id = MSM_BUS_MASTER_SPDM, + .ahb = 1, + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_MASTER_RPM, + .ahb = 1, + .tier = tier2, + .num_tiers = ARRAY_SIZE(tier2), + }, + { + .id = MSM_BUS_SLAVE_SPDM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_RPM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_RPM_MSG_RAM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MPM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC1_SSBI1_A, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC1_SSBI1_B, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC1_SSBI1_C, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC2_SSBI2_A, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_PMIC2_SSBI2_B, + .buswidth = 4, + .ahb = 1, + }, +}; + +static struct msm_bus_node_info cpss_fpb_fabric_info[] = { + { + .id = MSM_BUS_FAB_SYSTEM, + .gateway = 1, + .slavep = system_sport_cpss_fpb, + .num_sports = ARRAY_SIZE(system_sport_cpss_fpb), + .masterp = system_mport_cpss_fpb, + .num_mports = ARRAY_SIZE(system_mport_cpss_fpb), + .buswidth = 4, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI1_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI2_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI3_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI4_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI5_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI6_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI7_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI8_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI9_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI10_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI11_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI12_UART, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI1_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI2_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI3_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI4_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI5_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI6_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI7_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI8_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI9_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI10_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI11_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_GSBI12_QUP, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_NAND, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS0, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS1, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS2, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS3, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS4, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_EBI2_CS5, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_USB_FS1, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_USB_FS2, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_TSIF, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_TSSC, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_PDM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_DIMEM, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_TCSR, + .buswidth = 8, + .ahb = 1, + }, + { + .id = MSM_BUS_SLAVE_MSM_PRNG, + .buswidth = 4, + .ahb = 1, + }, +}; + +struct msm_bus_fabric_registration msm_bus_apps_fabric_pdata = { + .id = MSM_BUS_FAB_APPSS, + .name = "msm_apps_fab", + .info = apps_fabric_info, + .len = ARRAY_SIZE(apps_fabric_info), + .ahb = 0, + .fabclk[DUAL_CTX] = "afab_clk", + .fabclk[ACTIVE_CTX] = "afab_a_clk", + .haltid = MSM_RPM_ID_APPS_FABRIC_CFG_HALT_0, + .offset = MSM_RPM_ID_APPS_FABRIC_ARB_0, + .nmasters = 6, + .nslaves = 5, + .ntieredslaves = 3, +}; + +struct msm_bus_fabric_registration msm_bus_sys_fabric_pdata = { + .id = MSM_BUS_FAB_SYSTEM, + .name = "msm_sys_fab", + system_fabric_info, + ARRAY_SIZE(system_fabric_info), + .ahb = 0, + .fabclk[DUAL_CTX] = "sfab_clk", + .fabclk[ACTIVE_CTX] = "sfab_a_clk", + .haltid = MSM_RPM_ID_SYS_FABRIC_CFG_HALT_0, + .offset = MSM_RPM_ID_SYSTEM_FABRIC_ARB_0, + .nmasters = 15, + .nslaves = 12, + .ntieredslaves = 3, +}; + +struct msm_bus_fabric_registration msm_bus_mm_fabric_pdata = { + .id = MSM_BUS_FAB_MMSS, + .name = "msm_mm_fab", + mmss_fabric_info, + ARRAY_SIZE(mmss_fabric_info), + .ahb = 0, + .fabclk[DUAL_CTX] = "mmfab_clk", + .fabclk[ACTIVE_CTX] = "mmfab_a_clk", + .haltid = MSM_RPM_ID_MMSS_FABRIC_CFG_HALT_0, + .offset = MSM_RPM_ID_MM_FABRIC_ARB_0, + .nmasters = 14, + .nslaves = 4, + .ntieredslaves = 3, +}; + +struct msm_bus_fabric_registration msm_bus_sys_fpb_pdata = { + .id = MSM_BUS_FAB_SYSTEM_FPB, + .name = "msm_sys_fpb", + sys_fpb_fabric_info, + ARRAY_SIZE(sys_fpb_fabric_info), + .ahb = 1, + .fabclk[DUAL_CTX] = "sfpb_clk", + .fabclk[ACTIVE_CTX] = "sfpb_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, +}; + +struct msm_bus_fabric_registration msm_bus_cpss_fpb_pdata = { + .id = MSM_BUS_FAB_CPSS_FPB, + .name = "msm_cpss_fpb", + cpss_fpb_fabric_info, + ARRAY_SIZE(cpss_fpb_fabric_info), + .ahb = 1, + .fabclk[DUAL_CTX] = "cfpb_clk", + .fabclk[ACTIVE_CTX] = "cfpb_a_clk", + .nmasters = 0, + .nslaves = 0, + .ntieredslaves = 0, +}; + +static void msm_bus_board_get_ids( + struct msm_bus_fabric_registration *fabreg, + int fabid) +{ + int i; + for (i = 0; i < fabreg->len; i++) { + if (!fabreg->info[i].gateway) { + fabreg->info[i].priv_id = fabid + fabreg->info[i].id; + if (fabreg->info[i].id < SLAVE_ID_KEY) + master_iids[fabreg->info[i].id] = + fabreg->info[i].priv_id; + else + slave_iids[fabreg->info[i].id - (SLAVE_ID_KEY)] + = fabreg->info[i].priv_id; + } else + fabreg->info[i].priv_id = fabreg->info[i].id; + } +} + +void msm_bus_board_assign_iids(struct msm_bus_fabric_registration *fabreg, + int fabid) +{ + msm_bus_board_get_ids(fabreg, fabid); +} +int msm_bus_board_get_iid(int id) +{ + return ((id < SLAVE_ID_KEY) ? master_iids[id] : slave_iids[id - + SLAVE_ID_KEY]); +} + diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_config.c b/arch/arm/mach-msm/msm_bus/msm_bus_config.c new file mode 100644 index 00000000000..7d9d18f40a8 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_config.c @@ -0,0 +1,75 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" + +static DEFINE_MUTEX(msm_bus_config_lock); + +/** + * msm_bus_axi_porthalt() - Halt the given axi master port + * @master_port: AXI Master port to be halted + */ +int msm_bus_axi_porthalt(int master_port) +{ + int ret = 0; + int priv_id; + struct msm_bus_fabric_device *fabdev; + + priv_id = msm_bus_board_get_iid(master_port); + MSM_BUS_DBG("master_port: %d iid: %d fabid%d\n", + master_port, priv_id, GET_FABID(priv_id)); + fabdev = msm_bus_get_fabric_device(GET_FABID(priv_id)); + if (IS_ERR(fabdev)) { + MSM_BUS_ERR("Fabric device not found for mport: %d\n", + master_port); + return -ENODEV; + } + mutex_lock(&msm_bus_config_lock); + ret = fabdev->algo->port_halt(fabdev, priv_id); + mutex_unlock(&msm_bus_config_lock); + return ret; +} +EXPORT_SYMBOL(msm_bus_axi_porthalt); + +/** + * msm_bus_axi_portunhalt() - Unhalt the given axi master port + * @master_port: AXI Master port to be unhalted + */ +int msm_bus_axi_portunhalt(int master_port) +{ + int ret = 0; + int priv_id; + struct msm_bus_fabric_device *fabdev; + + priv_id = msm_bus_board_get_iid(master_port); + MSM_BUS_DBG("master_port: %d iid: %d fabid: %d\n", + master_port, priv_id, GET_FABID(priv_id)); + fabdev = msm_bus_get_fabric_device(GET_FABID(priv_id)); + if (IS_ERR(fabdev)) { + MSM_BUS_ERR("Fabric device not found for mport: %d\n", + master_port); + return -ENODEV; + } + mutex_lock(&msm_bus_config_lock); + ret = fabdev->algo->port_unhalt(fabdev, priv_id); + mutex_unlock(&msm_bus_config_lock); + return ret; +} +EXPORT_SYMBOL(msm_bus_axi_portunhalt); diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_core.c b/arch/arm/mach-msm/msm_bus/msm_bus_core.c new file mode 100644 index 00000000000..9fe05b7264b --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_core.c @@ -0,0 +1,114 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" + +static atomic_t num_fab = ATOMIC_INIT(0); + +int msm_bus_get_num_fab(void) +{ + return atomic_read(&num_fab); +} + +int msm_bus_device_match(struct device *dev, void* id) +{ + struct msm_bus_fabric_device *fabdev = to_msm_bus_fabric_device(dev); + + if (!fabdev) { + MSM_BUS_WARN("Fabric %p returning 0\n", fabdev); + return 0; + } + return (fabdev->id == (int)id); +} + +struct bus_type msm_bus_type = { + .name = "msm-bus-type", +}; +EXPORT_SYMBOL(msm_bus_type); + +/** + * msm_bus_get_fabric_device() - This function is used to search for + * the fabric device on the bus + * @fabid: Fabric id + * Function returns: Pointer to the fabric device + */ +struct msm_bus_fabric_device *msm_bus_get_fabric_device(int fabid) +{ + struct device *dev; + struct msm_bus_fabric_device *fabric; + dev = bus_find_device(&msm_bus_type, NULL, (void *)fabid, + msm_bus_device_match); + fabric = to_msm_bus_fabric_device(dev); + return fabric; +} + +/** + * msm_bus_fabric_device_register() - Registers a fabric on msm bus + * @fabdev: Fabric device to be registered + */ +int msm_bus_fabric_device_register(struct msm_bus_fabric_device *fabdev) +{ + int ret = 0; + fabdev->dev.bus = &msm_bus_type; + ret = dev_set_name(&fabdev->dev, fabdev->name); + if (ret) { + MSM_BUS_ERR("error setting dev name\n"); + goto err; + } + ret = device_register(&fabdev->dev); + if (ret < 0) { + MSM_BUS_ERR("error registering device%d %s\n", + ret, fabdev->name); + goto err; + } + atomic_inc(&num_fab); +err: + return ret; +} + +/** + * msm_bus_fabric_device_unregister() - Unregisters the fabric + * devices from the msm bus + */ +void msm_bus_fabric_device_unregister(struct msm_bus_fabric_device *fabdev) +{ + device_unregister(&fabdev->dev); + atomic_dec(&num_fab); +} + +static void __exit msm_bus_exit(void) +{ + bus_unregister(&msm_bus_type); +} + +static int __init msm_bus_init(void) +{ + int retval = 0; + retval = bus_register(&msm_bus_type); + if (retval) + MSM_BUS_ERR("bus_register error! %d\n", + retval); + return retval; +} +postcore_initcall(msm_bus_init); +module_exit(msm_bus_exit); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.2"); +MODULE_ALIAS("platform:msm_bus"); diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_core.h b/arch/arm/mach-msm/msm_bus/msm_bus_core.h new file mode 100644 index 00000000000..4ac2bc75fde --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_core.h @@ -0,0 +1,191 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _ARCH_ARM_MACH_MSM_BUS_CORE_H +#define _ARCH_ARM_MACH_MSM_BUS_CORE_H + +#include +#include +#include +#include +#include +#include + +#if defined DEBUG + +#define MSM_BUS_DBG(msg, ...) \ + printk(KERN_DEBUG "AXI: %s(): " msg, __func__, ## __VA_ARGS__) +#define MSM_FAB_DBG(msg, ...) \ + dev_dbg(&fabric->fabdev.dev, "AXI: %s(): " msg, __func__, ## \ + __VA_ARGS__) + +#else +#define MSM_BUS_DBG(msg, ...) +#define MSM_FAB_DBG(msg, ...) +#endif + +#define MSM_BUS_ERR(msg, ...) \ + printk(KERN_ERR "AXI: %s(): " msg, __func__, ## __VA_ARGS__) +#define MSM_BUS_WARN(msg, ...) \ + printk(KERN_WARNING "AXI: %s(): " msg, __func__, ## __VA_ARGS__) +#define MSM_FAB_ERR(msg, ...) \ + dev_err(&fabric->fabdev.dev, "AXI: %s(): " msg, __func__, ## \ + __VA_ARGS__) + +enum msm_bus_dbg_op_type { + MSM_BUS_DBG_UNREGISTER = -2, + MSM_BUS_DBG_REGISTER, + MSM_BUS_DBG_OP = 1, +}; + +extern struct bus_type msm_bus_type; + +struct msm_bus_node_info { + unsigned int id; + unsigned int priv_id; + int gateway; + int *masterp; + int num_mports; + int *slavep; + int num_sports; + int *tier; + int num_tiers; + int ahb; + const char *slaveclk[NUM_CTX]; + const char *memclk; + unsigned int buswidth; +}; + +struct path_node { + unsigned long clk[NUM_CTX]; + unsigned long bw[NUM_CTX]; + unsigned long *sel_clk; + unsigned long *sel_bw; + int next; +}; + +struct msm_bus_link_info { + unsigned long clk[NUM_CTX]; + unsigned long *sel_clk; + unsigned long memclk; + long bw[NUM_CTX]; + long *sel_bw; + int *tier; + int num_tiers; +}; + +struct nodeclk { + struct clk *clk; + unsigned long rate; + bool dirty; + bool enable; +}; + +struct msm_bus_inode_info { + struct msm_bus_node_info *node_info; + unsigned long max_bw; + unsigned long max_clk; + struct msm_bus_link_info link_info; + int num_pnodes; + struct path_node *pnode; + int commit_index; + struct nodeclk nodeclk[NUM_CTX]; + struct nodeclk memclk; +}; + +struct msm_bus_fabric_device { + int id; + const char *name; + struct device dev; + const struct msm_bus_fab_algorithm *algo; + int visited; +}; +#define to_msm_bus_fabric_device(d) container_of(d, \ + struct msm_bus_fabric_device, d) + + +struct msm_bus_fab_algorithm { + int (*update_clks)(struct msm_bus_fabric_device *fabdev, + struct msm_bus_inode_info *pme, int index, + unsigned long curr_clk, unsigned long req_clk, + unsigned long bwsum, int flag, int ctx, + unsigned int cl_active_flag); + int (*port_halt)(struct msm_bus_fabric_device *fabdev, int portid); + int (*port_unhalt)(struct msm_bus_fabric_device *fabdev, int portid); + int (*commit)(struct msm_bus_fabric_device *fabdev, + int active_only); + struct msm_bus_inode_info *(*find_node)(struct msm_bus_fabric_device + *fabdev, int id); + struct msm_bus_inode_info *(*find_gw_node)(struct msm_bus_fabric_device + *fabdev, int id); + struct list_head *(*get_gw_list)(struct msm_bus_fabric_device *fabdev); + void (*update_bw)(struct msm_bus_fabric_device *fabdev, struct + msm_bus_inode_info * hop, struct msm_bus_inode_info *info, + long int add_bw, int *master_tiers, int ctx); +}; + +/** + * Used to store the list of fabrics and other info to be + * maintained outside the fabric structure. + * Used while calculating path, and to find fabric ptrs + */ +struct msm_bus_fabnodeinfo { + struct list_head list; + struct msm_bus_inode_info *info; +}; + +struct msm_bus_client { + int id; + struct msm_bus_scale_pdata *pdata; + int *src_pnode; + int curr; +}; + +int msm_bus_fabric_device_register(struct msm_bus_fabric_device *fabric); +void msm_bus_fabric_device_unregister(struct msm_bus_fabric_device *fabric); +struct msm_bus_fabric_device *msm_bus_get_fabric_device(int fabid); +int msm_bus_get_num_fab(void); + +int allocate_commit_data(struct msm_bus_fabric_registration *fab_pdata, + void **cdata); +struct msm_rpm_iv_pair *allocate_rpm_data(struct msm_bus_fabric_registration + *fab_pdata); +int msm_bus_rpm_commit(struct msm_bus_fabric_registration + *fab_pdata, int ctx, struct msm_rpm_iv_pair *rpm_data, + void *cdata); +void free_commit_data(void *cdata); +void msm_bus_rpm_update_bw(struct msm_bus_inode_info *hop, + struct msm_bus_inode_info *info, + struct msm_bus_fabric_registration *fab_pdata, + void *sel_cdata, int *master_tiers, + long int add_bw); +void msm_bus_rpm_fill_cdata_buffer(int *curr, char *buf, const int max_size, + void *cdata, int nmasters, int nslaves, int ntslaves); + +#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_MSM_BUS_SCALING) +void msm_bus_dbg_client_data(struct msm_bus_scale_pdata *pdata, int index, + uint32_t cl); +void msm_bus_dbg_commit_data(const char *fabname, void *cdata, + int nmasters, int nslaves, int ntslaves, int op); +#else +static inline void msm_bus_dbg_client_data(struct msm_bus_scale_pdata *pdata, + int index, uint32_t cl) +{ +} +static inline void msm_bus_dbg_commit_data(const char *fabname, + void *cdata, int nmasters, int nslaves, int ntslaves, + int op) +{ +} +#endif + +#endif /*_ARCH_ARM_MACH_MSM_BUS_CORE_H*/ diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_dbg.c b/arch/arm/mach-msm/msm_bus/msm_bus_dbg.c new file mode 100644 index 00000000000..04348f98037 --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_dbg.c @@ -0,0 +1,709 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" + +#define MAX_BUFF_SIZE 4096 +#define FILL_LIMIT 128 + +static struct dentry *clients; +static struct dentry *dir; +static DEFINE_MUTEX(msm_bus_dbg_fablist_lock); +struct msm_bus_dbg_state { + uint32_t cl; + uint8_t enable; + uint8_t current_index; +} clstate; + +struct msm_bus_cldata { + const struct msm_bus_scale_pdata *pdata; + int index; + uint32_t clid; + int size; + struct dentry *file; + struct list_head list; + char buffer[MAX_BUFF_SIZE]; +}; + +struct msm_bus_fab_list { + const char *name; + int size; + struct dentry *file; + struct list_head list; + char buffer[MAX_BUFF_SIZE]; +}; + +LIST_HEAD(fabdata_list); +LIST_HEAD(cl_list); + +/** + * The following structures and funtions are used for + * the test-client which can be created at run-time. + */ + +static struct msm_bus_vectors init_vectors[1]; +static struct msm_bus_vectors current_vectors[1]; +static struct msm_bus_vectors requested_vectors[1]; + +static struct msm_bus_paths shell_client_usecases[] = { + { + .num_paths = ARRAY_SIZE(init_vectors), + .vectors = init_vectors, + }, + { + .num_paths = ARRAY_SIZE(current_vectors), + .vectors = current_vectors, + }, + { + .num_paths = ARRAY_SIZE(requested_vectors), + .vectors = requested_vectors, + }, +}; + +static struct msm_bus_scale_pdata shell_client = { + .usecase = shell_client_usecases, + .num_usecases = ARRAY_SIZE(shell_client_usecases), + .name = "test-client", +}; + +static void msm_bus_dbg_init_vectors(void) +{ + init_vectors[0].src = -1; + init_vectors[0].dst = -1; + init_vectors[0].ab = 0; + init_vectors[0].ib = 0; + current_vectors[0].src = -1; + current_vectors[0].dst = -1; + current_vectors[0].ab = 0; + current_vectors[0].ib = 0; + requested_vectors[0].src = -1; + requested_vectors[0].dst = -1; + requested_vectors[0].ab = 0; + requested_vectors[0].ib = 0; + clstate.enable = 0; + clstate.current_index = 0; +} + +static int msm_bus_dbg_update_cl_request(uint32_t cl) +{ + int ret = 0; + + if (clstate.current_index < 2) + clstate.current_index = 2; + else { + clstate.current_index = 1; + current_vectors[0].ab = requested_vectors[0].ab; + current_vectors[0].ib = requested_vectors[0].ib; + } + + if (clstate.enable) { + MSM_BUS_DBG("Updating request for shell client, index: %d\n", + clstate.current_index); + ret = msm_bus_scale_client_update_request(clstate.cl, + clstate.current_index); + } else + MSM_BUS_DBG("Enable bit not set. Skipping update request\n"); + + return ret; +} + +static void msm_bus_dbg_unregister_client(uint32_t cl) +{ + MSM_BUS_DBG("Unregistering shell client\n"); + msm_bus_scale_unregister_client(clstate.cl); + clstate.cl = 0; +} + +static uint32_t msm_bus_dbg_register_client(void) +{ + int ret = 0; + + if (init_vectors[0].src != requested_vectors[0].src) { + MSM_BUS_DBG("Shell client master changed. Unregistering\n"); + msm_bus_dbg_unregister_client(clstate.cl); + } + if (init_vectors[0].dst != requested_vectors[0].dst) { + MSM_BUS_DBG("Shell client slave changed. Unregistering\n"); + msm_bus_dbg_unregister_client(clstate.cl); + } + + if (!clstate.enable) { + MSM_BUS_DBG("Enable bit not set, skipping registration: cl " + "%d\n", clstate.cl); + return 0; + } + + if (clstate.cl) { + MSM_BUS_DBG("Client registered, skipping registration\n"); + return 0; + } + + current_vectors[0].src = init_vectors[0].src; + requested_vectors[0].src = init_vectors[0].src; + current_vectors[0].dst = init_vectors[0].dst; + requested_vectors[0].dst = init_vectors[0].dst; + MSM_BUS_DBG("Registering shell client\n"); + ret = msm_bus_scale_register_client(&shell_client); + return ret; +} + +static int msm_bus_dbg_mas_get(void *data, u64 *val) +{ + *val = init_vectors[0].src; + MSM_BUS_DBG("Get master: %llu\n", *val); + return 0; +} + +static int msm_bus_dbg_mas_set(void *data, u64 val) +{ + init_vectors[0].src = val; + MSM_BUS_DBG("Set master: %llu\n", val); + clstate.cl = msm_bus_dbg_register_client(); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(shell_client_mas_fops, msm_bus_dbg_mas_get, + msm_bus_dbg_mas_set, "%llu\n"); + +static int msm_bus_dbg_slv_get(void *data, u64 *val) +{ + *val = init_vectors[0].dst; + MSM_BUS_DBG("Get slave: %llu\n", *val); + return 0; +} + +static int msm_bus_dbg_slv_set(void *data, u64 val) +{ + init_vectors[0].dst = val; + MSM_BUS_DBG("Set slave: %llu\n", val); + clstate.cl = msm_bus_dbg_register_client(); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(shell_client_slv_fops, msm_bus_dbg_slv_get, + msm_bus_dbg_slv_set, "%llu\n"); + +static int msm_bus_dbg_ab_get(void *data, u64 *val) +{ + *val = requested_vectors[0].ab; + MSM_BUS_DBG("Get ab: %llu\n", *val); + return 0; +} + +static int msm_bus_dbg_ab_set(void *data, u64 val) +{ + requested_vectors[0].ab = val; + MSM_BUS_DBG("Set ab: %llu\n", val); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(shell_client_ab_fops, msm_bus_dbg_ab_get, + msm_bus_dbg_ab_set, "%llu\n"); + +static int msm_bus_dbg_ib_get(void *data, u64 *val) +{ + *val = requested_vectors[0].ib; + MSM_BUS_DBG("Get ib: %llu\n", *val); + return 0; +} + +static int msm_bus_dbg_ib_set(void *data, u64 val) +{ + requested_vectors[0].ib = val; + MSM_BUS_DBG("Set ib: %llu\n", val); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(shell_client_ib_fops, msm_bus_dbg_ib_get, + msm_bus_dbg_ib_set, "%llu\n"); + +static int msm_bus_dbg_en_get(void *data, u64 *val) +{ + *val = clstate.enable; + MSM_BUS_DBG("Get enable: %llu\n", *val); + return 0; +} + +static int msm_bus_dbg_en_set(void *data, u64 val) +{ + int ret = 0; + + clstate.enable = val; + if (clstate.enable) { + if (!clstate.cl) { + MSM_BUS_DBG("client: %u\n", clstate.cl); + clstate.cl = msm_bus_dbg_register_client(); + if (clstate.cl) + ret = msm_bus_dbg_update_cl_request(clstate.cl); + } else { + MSM_BUS_DBG("update request for cl: %u\n", clstate.cl); + ret = msm_bus_dbg_update_cl_request(clstate.cl); + } + } + + MSM_BUS_DBG("Set enable: %llu\n", val); + return ret; +} +DEFINE_SIMPLE_ATTRIBUTE(shell_client_en_fops, msm_bus_dbg_en_get, + msm_bus_dbg_en_set, "%llu\n"); + +/** + * The following funtions are used for viewing the client data + * and changing the client request at run-time + */ + +static ssize_t client_data_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int bsize = 0; + uint32_t cl = (uint32_t)file->private_data; + struct msm_bus_cldata *cldata = NULL; + + list_for_each_entry(cldata, &cl_list, list) { + if (cldata->clid == cl) + break; + } + bsize = cldata->size; + return simple_read_from_buffer(buf, count, ppos, + cldata->buffer, bsize); +} + +static int client_data_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations client_data_fops = { + .open = client_data_open, + .read = client_data_read, +}; + +struct dentry *msm_bus_dbg_create(const char *name, mode_t mode, + struct dentry *dent, uint32_t clid) +{ + if (dent == NULL) { + MSM_BUS_DBG("debugfs not ready yet\n"); + return NULL; + } + return debugfs_create_file(name, mode, dent, (void *)clid, + &client_data_fops); +} + +static int msm_bus_dbg_record_client(const struct msm_bus_scale_pdata *pdata, + int index, uint32_t clid, struct dentry *file) +{ + struct msm_bus_cldata *cldata; + + cldata = kmalloc(sizeof(struct msm_bus_cldata), GFP_KERNEL); + if (!cldata) { + MSM_BUS_DBG("Failed to allocate memory for client data\n"); + return -ENOMEM; + } + cldata->pdata = pdata; + cldata->index = index; + cldata->clid = clid; + cldata->file = file; + cldata->size = 0; + list_add_tail(&cldata->list, &cl_list); + return 0; +} + +static void msm_bus_dbg_free_client(uint32_t clid) +{ + struct msm_bus_cldata *cldata = NULL; + + list_for_each_entry(cldata, &cl_list, list) { + if (cldata->clid == clid) { + debugfs_remove(cldata->file); + list_del(&cldata->list); + kfree(cldata); + break; + } + } +} + +static int msm_bus_dbg_fill_cl_buffer(const struct msm_bus_scale_pdata *pdata, + int index, uint32_t clid) +{ + int i = 0, j; + char *buf = NULL; + struct msm_bus_cldata *cldata = NULL; + struct timespec ts; + + list_for_each_entry(cldata, &cl_list, list) { + if (cldata->clid == clid) + break; + } + if (cldata->file == NULL) { + if (pdata->name == NULL) { + MSM_BUS_DBG("Client doesn't have a name\n"); + return -EINVAL; + } + cldata->file = msm_bus_dbg_create(pdata->name, S_IRUGO, + clients, clid); + } + + if (cldata->size < (MAX_BUFF_SIZE - FILL_LIMIT)) + i = cldata->size; + else { + i = 0; + cldata->size = 0; + } + buf = cldata->buffer; + ts = ktime_to_timespec(ktime_get()); + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "\n%d.%d\n", + (int)ts.tv_sec, (int)ts.tv_nsec); + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "curr : %d\n", index); + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "masters: "); + + for (j = 0; j < pdata->usecase->num_paths; j++) + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "%d ", + pdata->usecase[index].vectors[j].src); + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "\nslaves : "); + for (j = 0; j < pdata->usecase->num_paths; j++) + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "%d ", + pdata->usecase[index].vectors[j].dst); + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "\nab : "); + for (j = 0; j < pdata->usecase->num_paths; j++) + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "%u ", + pdata->usecase[index].vectors[j].ab); + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "\nib : "); + for (j = 0; j < pdata->usecase->num_paths; j++) + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "%u ", + pdata->usecase[index].vectors[j].ib); + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "\n"); + + cldata->size = i; + return i; +} + +static int msm_bus_dbg_update_request(struct msm_bus_cldata *cldata, int index) +{ + int ret = 0; + + if ((index < 0) || (index > cldata->pdata->num_usecases)) { + MSM_BUS_DBG("Invalid index!\n"); + return -EINVAL; + } + ret = msm_bus_scale_client_update_request(cldata->clid, index); + return ret; +} + +static ssize_t msm_bus_dbg_update_request_write(struct file *file, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + struct msm_bus_cldata *cldata; + unsigned long index = 0; + int ret = 0; + char *chid; + char *buf = kmalloc((sizeof(char) * (cnt + 1)), GFP_KERNEL); + + if (!buf || IS_ERR(buf)) { + MSM_BUS_ERR("Memory allocation for buffer failed\n"); + return -ENOMEM; + } + if (cnt == 0) + return 0; + if (copy_from_user(buf, ubuf, cnt)) + return -EFAULT; + buf[cnt] = '\0'; + chid = buf; + MSM_BUS_DBG("buffer: %s\n size: %d\n", buf, sizeof(ubuf)); + + list_for_each_entry(cldata, &cl_list, list) { + if (strstr(chid, cldata->pdata->name)) { + cldata = cldata; + strsep(&chid, " "); + if (chid) { + ret = strict_strtoul(chid, 10, &index); + if (ret) { + MSM_BUS_DBG("Index conversion" + " failed\n"); + return -EFAULT; + } + } else + MSM_BUS_DBG("Error parsing input. Index not" + " found\n"); + break; + } + } + + msm_bus_dbg_update_request(cldata, index); + kfree(buf); + return cnt; +} + +/** + * The following funtions are used for viewing the commit data + * for each fabric + */ +static ssize_t fabric_data_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct msm_bus_fab_list *fablist = NULL; + int bsize = 0; + ssize_t ret; + const char *name = file->private_data; + + mutex_lock(&msm_bus_dbg_fablist_lock); + list_for_each_entry(fablist, &fabdata_list, list) { + if (strcmp(fablist->name, name) == 0) + break; + } + bsize = fablist->size; + ret = simple_read_from_buffer(buf, count, ppos, + fablist->buffer, bsize); + mutex_unlock(&msm_bus_dbg_fablist_lock); + return ret; +} + +static const struct file_operations fabric_data_fops = { + .open = client_data_open, + .read = fabric_data_read, +}; + +static int msm_bus_dbg_record_fabric(const char *fabname, struct dentry *file) +{ + struct msm_bus_fab_list *fablist; + int ret = 0; + + mutex_lock(&msm_bus_dbg_fablist_lock); + fablist = kmalloc(sizeof(struct msm_bus_fab_list), GFP_KERNEL); + if (!fablist) { + MSM_BUS_DBG("Failed to allocate memory for commit data\n"); + ret = -ENOMEM; + goto err; + } + + fablist->name = fabname; + fablist->size = 0; + list_add_tail(&fablist->list, &fabdata_list); +err: + mutex_unlock(&msm_bus_dbg_fablist_lock); + return ret; +} + +static void msm_bus_dbg_free_fabric(const char *fabname) +{ + struct msm_bus_fab_list *fablist = NULL; + + mutex_lock(&msm_bus_dbg_fablist_lock); + list_for_each_entry(fablist, &fabdata_list, list) { + if (strcmp(fablist->name, fabname) == 0) { + debugfs_remove(fablist->file); + list_del(&fablist->list); + kfree(fablist); + break; + } + } + mutex_unlock(&msm_bus_dbg_fablist_lock); +} + +static int msm_bus_dbg_fill_fab_buffer(const char *fabname, + void *cdata, int nmasters, int nslaves, + int ntslaves) +{ + int i; + char *buf = NULL; + struct msm_bus_fab_list *fablist = NULL; + struct timespec ts; + + mutex_lock(&msm_bus_dbg_fablist_lock); + list_for_each_entry(fablist, &fabdata_list, list) { + if (strcmp(fablist->name, fabname) == 0) + break; + } + if (fablist->file == NULL) { + MSM_BUS_DBG("Fabric dbg entry does not exist\n"); + mutex_unlock(&msm_bus_dbg_fablist_lock); + return -EFAULT; + } + + if (fablist->size < MAX_BUFF_SIZE - 256) + i = fablist->size; + else { + i = 0; + fablist->size = 0; + } + buf = fablist->buffer; + mutex_unlock(&msm_bus_dbg_fablist_lock); + ts = ktime_to_timespec(ktime_get()); + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "\n%d.%d\n", + (int)ts.tv_sec, (int)ts.tv_nsec); + + msm_bus_rpm_fill_cdata_buffer(&i, buf + i, MAX_BUFF_SIZE, cdata, + nmasters, nslaves, ntslaves); + i += scnprintf(buf + i, MAX_BUFF_SIZE - i, "\n"); + mutex_lock(&msm_bus_dbg_fablist_lock); + fablist->size = i; + mutex_unlock(&msm_bus_dbg_fablist_lock); + return 0; +} + +static const struct file_operations msm_bus_dbg_update_request_fops = { + .open = client_data_open, + .write = msm_bus_dbg_update_request_write, +}; + +/** + * msm_bus_dbg_client_data() - Add debug data for clients + * @pdata: Platform data of the client + * @index: The current index or operation to be performed + * @clid: Client handle obtained during registration + */ +void msm_bus_dbg_client_data(struct msm_bus_scale_pdata *pdata, int index, + uint32_t clid) +{ + struct dentry *file = NULL; + + if (index == MSM_BUS_DBG_REGISTER) { + msm_bus_dbg_record_client(pdata, index, clid, file); + if (!pdata->name) { + MSM_BUS_DBG("Cannot create debugfs entry. Null name\n"); + return; + } + } else if (index == MSM_BUS_DBG_UNREGISTER) { + msm_bus_dbg_free_client(clid); + MSM_BUS_DBG("Client %d unregistered\n", clid); + } else + msm_bus_dbg_fill_cl_buffer(pdata, index, clid); +} +EXPORT_SYMBOL(msm_bus_dbg_client_data); + +/** + * msm_bus_dbg_commit_data() - Add commit data from fabrics + * @fabname: Fabric name specified in platform data + * @cdata: Commit Data + * @nmasters: Number of masters attached to fabric + * @nslaves: Number of slaves attached to fabric + * @ntslaves: Number of tiered slaves attached to fabric + * @op: Operation to be performed + */ +void msm_bus_dbg_commit_data(const char *fabname, void *cdata, + int nmasters, int nslaves, int ntslaves, int op) +{ + struct dentry *file = NULL; + + if (op == MSM_BUS_DBG_REGISTER) + msm_bus_dbg_record_fabric(fabname, file); + else if (op == MSM_BUS_DBG_UNREGISTER) + msm_bus_dbg_free_fabric(fabname); + else + msm_bus_dbg_fill_fab_buffer(fabname, cdata, nmasters, + nslaves, ntslaves); +} +EXPORT_SYMBOL(msm_bus_dbg_commit_data); + +static int __init msm_bus_debugfs_init(void) +{ + struct dentry *commit, *shell_client; + struct msm_bus_fab_list *fablist; + struct msm_bus_cldata *cldata = NULL; + uint64_t val = 0; + + dir = debugfs_create_dir("msm-bus-dbg", NULL); + if ((!dir) || IS_ERR(dir)) { + MSM_BUS_ERR("Couldn't create msm-bus-dbg\n"); + goto err; + } + + clients = debugfs_create_dir("client-data", dir); + if ((!dir) || IS_ERR(dir)) { + MSM_BUS_ERR("Couldn't create clients\n"); + goto err; + } + + shell_client = debugfs_create_dir("shell-client", dir); + if ((!dir) || IS_ERR(dir)) { + MSM_BUS_ERR("Couldn't create clients\n"); + goto err; + } + + commit = debugfs_create_dir("commit-data", dir); + if ((!dir) || IS_ERR(dir)) { + MSM_BUS_ERR("Couldn't create commit\n"); + goto err; + } + + if (debugfs_create_file("update_request", S_IRUGO | S_IWUSR, + shell_client, &val, &shell_client_en_fops) == NULL) + goto err; + if (debugfs_create_file("ib", S_IRUGO | S_IWUSR, shell_client, &val, + &shell_client_ib_fops) == NULL) + goto err; + if (debugfs_create_file("ab", S_IRUGO | S_IWUSR, shell_client, &val, + &shell_client_ab_fops) == NULL) + goto err; + if (debugfs_create_file("slv", S_IRUGO | S_IWUSR, shell_client, + &val, &shell_client_slv_fops) == NULL) + goto err; + if (debugfs_create_file("mas", S_IRUGO | S_IWUSR, shell_client, + &val, &shell_client_mas_fops) == NULL) + goto err; + if (debugfs_create_file("update-request", S_IRUGO | S_IWUSR, + clients, NULL, &msm_bus_dbg_update_request_fops) == NULL) + goto err; + + list_for_each_entry(cldata, &cl_list, list) { + if (cldata->pdata->name == NULL) { + MSM_BUS_DBG("Client name not found\n"); + continue; + } + cldata->file = msm_bus_dbg_create(cldata-> + pdata->name, S_IRUGO, clients, cldata->clid); + } + + mutex_lock(&msm_bus_dbg_fablist_lock); + list_for_each_entry(fablist, &fabdata_list, list) { + fablist->file = debugfs_create_file(fablist->name, S_IRUGO, + commit, (void *)fablist->name, &fabric_data_fops); + if (fablist->file == NULL) { + MSM_BUS_DBG("Cannot create files for commit data\n"); + goto err; + } + } + mutex_unlock(&msm_bus_dbg_fablist_lock); + + msm_bus_dbg_init_vectors(); + return 0; +err: + debugfs_remove_recursive(dir); + return -ENODEV; +} +late_initcall(msm_bus_debugfs_init); + +static void __exit msm_bus_dbg_teardown(void) +{ + struct msm_bus_fab_list *fablist = NULL; + struct msm_bus_cldata *cldata = NULL; + + debugfs_remove_recursive(dir); + list_for_each_entry(cldata, &cl_list, list) { + kfree(cldata); + } + mutex_lock(&msm_bus_dbg_fablist_lock); + list_for_each_entry(fablist, &fabdata_list, list) { + kfree(fablist); + } + mutex_unlock(&msm_bus_dbg_fablist_lock); +} +module_exit(msm_bus_dbg_teardown); +MODULE_DESCRIPTION("Debugfs for msm bus scaling client"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Gagan Mac "); diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_fabric.c b/arch/arm/mach-msm/msm_bus/msm_bus_fabric.c new file mode 100644 index 00000000000..e8d529bf45c --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_fabric.c @@ -0,0 +1,758 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" + +#define GET_RATE(clk, nports) \ + ((clk % nports) ? ((clk + nports - 1) / nports) : (clk / nports)) + +enum { + SLAVE_NODE, + MASTER_NODE, +}; + +enum { + DISABLE, + ENABLE, +}; + +struct msm_bus_fabric { + struct msm_bus_fabric_device fabdev; + int ahb; + void *cdata[NUM_CTX]; + bool arb_dirty; + bool clk_dirty; + struct radix_tree_root fab_tree; + int num_nodes; + struct list_head gateways; + struct msm_bus_inode_info info; + const struct msm_bus_fab_algorithm *algo; + struct msm_bus_fabric_registration *pdata; + struct msm_rpm_iv_pair *rpm_data; +}; +#define to_msm_bus_fabric(d) container_of(d, \ + struct msm_bus_fabric, d) + +/** + * msm_bus_fabric_add_node() - Add a node to the fabric structure + * @fabric: Fabric device to which the node should be added + * @info: The node to be added + */ +static int msm_bus_fabric_add_node(struct msm_bus_fabric *fabric, + struct msm_bus_inode_info *info) +{ + int status = -ENOMEM; + MSM_FAB_DBG("msm_bus_fabric_add_node: ID %d Gw: %d\n", + info->node_info->priv_id, info->node_info->gateway); + status = radix_tree_preload(GFP_ATOMIC); + if (status) + goto out; + status = radix_tree_insert(&fabric->fab_tree, info->node_info->priv_id, + info); + if (IS_SLAVE(info->node_info->priv_id)) + radix_tree_tag_set(&fabric->fab_tree, info->node_info->priv_id, + SLAVE_NODE); + + if (info->node_info->slaveclk[DUAL_CTX]) { + info->nodeclk[DUAL_CTX].clk = clk_get(NULL, + info->node_info->slaveclk[DUAL_CTX]); + if (IS_ERR(info->nodeclk[DUAL_CTX].clk)) { + MSM_BUS_ERR("Could not get clock for %s\n", + info->node_info->slaveclk[DUAL_CTX]); + status = -EINVAL; + goto out; + } + info->nodeclk[DUAL_CTX].enable = false; + info->nodeclk[DUAL_CTX].dirty = false; + } + radix_tree_preload_end(); +out: + return status; +} + +/** + * msm_bus_add_fab() - Add a fabric (gateway) to the current fabric + * @fabric: Fabric device to which the gateway info should be added + * @info: Gateway node to be added to the fabric + */ +static int msm_bus_fabric_add_fab(struct msm_bus_fabric *fabric, + struct msm_bus_inode_info *info) +{ + int status = 0; + struct msm_bus_fabnodeinfo *fabnodeinfo; + MSM_FAB_DBG("msm_bus_fabric_add_fab: ID %d Gw: %d\n", + info->node_info->priv_id, info->node_info->gateway); + fabnodeinfo = kzalloc(sizeof(struct msm_bus_fabnodeinfo), GFP_KERNEL); + if (!fabnodeinfo) { + MSM_FAB_ERR("msm_bus_fabric_add_fab: " + "No Node Info\n"); + MSM_FAB_ERR("axi: Cannot register fabric!\n"); + status = -ENOMEM; + } + + fabnodeinfo->info = info; + fabnodeinfo->info->num_pnodes = -1; + list_add_tail(&fabnodeinfo->list, &fabric->gateways); + return status; +} + +/** + * register_fabric_info() - Create the internal fabric structure and + * build the topology tree from platform specific data + * + * @fabric: Fabric to which the gateways, nodes should be added + * + * This function is called from probe. Iterates over the platform data, + * and builds the topology + */ +static int register_fabric_info(struct msm_bus_fabric *fabric) +{ + int i, ret = 0, err = 0; + + MSM_FAB_DBG("id:%d pdata-id: %d len: %d\n", fabric->fabdev.id, + fabric->pdata->id, fabric->pdata->len); + + for (i = 0; i < fabric->pdata->len; i++) { + struct msm_bus_inode_info *info; + int ctx; + + info = kzalloc(sizeof(struct msm_bus_inode_info), GFP_KERNEL); + info->node_info = fabric->pdata->info + i; + info->commit_index = -1; + info->num_pnodes = -1; + + for (ctx = 0; ctx < NUM_CTX; ctx++) { + if (info->node_info->slaveclk[ctx]) { + info->nodeclk[ctx].clk = clk_get(NULL, + info->node_info->slaveclk[ctx]); + if (IS_ERR(info->nodeclk[ctx].clk)) { + MSM_BUS_ERR("Couldn't get clk %s\n", + info->node_info->slaveclk[ctx]); + err = -EINVAL; + } + info->nodeclk[ctx].enable = false; + info->nodeclk[ctx].dirty = false; + } + } + if (info->node_info->memclk) { + info->memclk.clk = clk_get(NULL, + info->node_info->memclk); + if (IS_ERR(info->memclk.clk)) { + MSM_BUS_ERR("Couldn't get clk %s\n", + info->node_info->memclk); + err = -EINVAL; + } + info->memclk.enable = false; + info->memclk.dirty = false; + } + + ret = info->node_info->gateway ? + msm_bus_fabric_add_fab(fabric, info) : + msm_bus_fabric_add_node(fabric, info); + if (ret) { + MSM_BUS_ERR("Unable to add node info, ret: %d\n", ret); + kfree(info); + goto error; + } + } + + fabric->rpm_data = allocate_rpm_data(fabric->pdata); + + MSM_FAB_DBG("Fabric: %d nmasters: %d nslaves: %d\n" + " ntieredslaves: %d, rpm_enabled: %d\n", + fabric->fabdev.id, fabric->pdata->nmasters, + fabric->pdata->nslaves, fabric->pdata->ntieredslaves, + fabric->pdata->rpm_enabled); + MSM_FAB_DBG("msm_bus_register_fabric_info i: %d\n", i); + fabric->num_nodes = fabric->pdata->len; +error: + fabric->num_nodes = i; + msm_bus_dbg_commit_data(fabric->fabdev.name, NULL, 0, 0, 0, + MSM_BUS_DBG_REGISTER); + return ret | err; +} + +/** + * msm_bus_fabric_update_clks() - Set the clocks for fabrics and slaves + * @fabric: Fabric for which the clocks need to be updated + * @slave: The node for which the clocks need to be updated + * @index: The index for which the current clocks are set + * @curr_clk_hz:Current clock value + * @req_clk_hz: Requested clock value + * @bwsum: Bandwidth Sum + * @clk_flag: Flag determining whether fabric clock or the slave clock has to + * be set. If clk_flag is set, fabric clock is set, else slave clock is set. + */ +static int msm_bus_fabric_update_clks(struct msm_bus_fabric_device *fabdev, + struct msm_bus_inode_info *slave, int index, + unsigned long curr_clk_hz, unsigned long req_clk_hz, + unsigned long bwsum_hz, int clk_flag, int ctx, + unsigned int cl_active_flag) +{ + int i, status = 0; + unsigned long max_pclk = 0, rate; + unsigned long *pclk = NULL; + struct msm_bus_fabric *fabric = to_msm_bus_fabric(fabdev); + struct nodeclk *nodeclk; + + /* Maximum for this gateway */ + for (i = 0; i <= slave->num_pnodes; i++) { + if (i == index && (req_clk_hz < curr_clk_hz)) + continue; + slave->pnode[i].sel_clk = &slave->pnode[i].clk[ctx]; + max_pclk = max(max_pclk, *slave->pnode[i].sel_clk); + } + + *slave->link_info.sel_clk = + max(max_pclk, max(bwsum_hz, req_clk_hz)); + /* Is this gateway or slave? */ + if (clk_flag && (!fabric->ahb)) { + struct msm_bus_fabnodeinfo *fabgw = NULL; + struct msm_bus_inode_info *info = NULL; + /* Maximum of all gateways set at fabric */ + list_for_each_entry(fabgw, &fabric->gateways, list) { + info = fabgw->info; + if (!info) + continue; + info->link_info.sel_clk = &info->link_info.clk[ctx]; + max_pclk = max(max_pclk, *info->link_info.sel_clk); + } + MSM_FAB_DBG("max_pclk from gateways: %lu\n", max_pclk); + + /* Maximum of all slave clocks. */ + + for (i = 0; i < fabric->pdata->len; i++) { + if (fabric->pdata->info[i].gateway || + (fabric->pdata->info[i].id < SLAVE_ID_KEY)) + continue; + info = radix_tree_lookup(&fabric->fab_tree, + fabric->pdata->info[i].priv_id); + if (!info) + continue; + info->link_info.sel_clk = &info->link_info.clk[ctx]; + max_pclk = max(max_pclk, *info->link_info.sel_clk); + } + + + MSM_FAB_DBG("max_pclk from slaves & gws: %lu\n", max_pclk); + fabric->info.link_info.sel_clk = + &fabric->info.link_info.clk[ctx]; + pclk = fabric->info.link_info.sel_clk; + } else { + slave->link_info.sel_clk = &slave->link_info.clk[ctx]; + pclk = slave->link_info.sel_clk; + } + + + *pclk = max(max_pclk, max(bwsum_hz, req_clk_hz)); + + if (!fabric->pdata->rpm_enabled) + goto skip_set_clks; + + if (clk_flag) { + nodeclk = &fabric->info.nodeclk[ctx]; + /** + * Send a clock request only when the client requests in active + * context and the ACTIVE_CTX clock rate is selected OR the + * client request is in normal context and normal clock rate + * is selected. + */ + if (nodeclk->clk && (!((ctx == ACTIVE_CTX) ^ cl_active_flag))) { + MSM_BUS_DBG("clks: id: %d set-clk: %lu bwsum_hz:%lu\n", + fabric->fabdev.id, *pclk, bwsum_hz); + if (nodeclk->rate != *pclk) { + nodeclk->dirty = true; + nodeclk->rate = *pclk; + } + fabric->clk_dirty = true; + } + } else { + nodeclk = &slave->nodeclk[ctx]; + if (nodeclk->clk && (!((ctx == ACTIVE_CTX) ^ cl_active_flag))) { + rate = GET_RATE(*pclk, slave->node_info->num_sports); + MSM_BUS_DBG("AXI_clks: id: %d set-clk: %lu " + "bwsum_hz: %lu\n" , slave->node_info->priv_id, rate, + bwsum_hz); + if (nodeclk->rate != *pclk) { + nodeclk->dirty = true; + nodeclk->rate = rate; + } + } + if (!status && slave->memclk.clk && + (!((ctx == ACTIVE_CTX) ^ cl_active_flag))) { + rate = GET_RATE(*slave->link_info.sel_clk, + slave->node_info->num_sports); + if (slave->memclk.rate != rate) { + slave->memclk.rate = rate; + slave->memclk.dirty = true; + } + slave->memclk.rate = rate; + fabric->clk_dirty = true; + } + } +skip_set_clks: + return status; +} + +void msm_bus_fabric_update_bw(struct msm_bus_fabric_device *fabdev, + struct msm_bus_inode_info *hop, struct msm_bus_inode_info *info, + long int add_bw, int *master_tiers, int ctx) +{ + struct msm_bus_fabric *fabric = to_msm_bus_fabric(fabdev); + void *sel_cdata; + + sel_cdata = fabric->cdata[ctx]; + + /* If it's an ahb fabric, don't calculate arb values */ + if (fabric->ahb) { + MSM_FAB_DBG("AHB fabric, skipping bw calculation\n"); + return; + } + if (!add_bw) { + MSM_BUS_DBG("No bandwidth delta. Skipping commit\n"); + return; + } + + msm_bus_rpm_update_bw(hop, info, fabric->pdata, sel_cdata, + master_tiers, add_bw); + fabric->arb_dirty = true; +} + +static int msm_bus_fabric_clk_set(int enable, struct msm_bus_inode_info *info) +{ + int i, status = 0; + for (i = 0; i < NUM_CTX; i++) + if (info->nodeclk[i].dirty) { + status = clk_set_min_rate(info->nodeclk[i].clk, info-> + nodeclk[i].rate); + if (enable && !(info->nodeclk[i].enable)) { + clk_enable(info->nodeclk[i].clk); + info->nodeclk[i].dirty = false; + info->nodeclk[i].enable = true; + } else if ((info->nodeclk[i].rate == 0) && (!enable) + && (info->nodeclk[i].enable)) { + clk_disable(info->nodeclk[i].clk); + info->nodeclk[i].dirty = false; + info->nodeclk[i].enable = false; + } + } + + if (info->memclk.dirty) { + status = clk_set_min_rate(info->memclk.clk, info->memclk.rate); + if (enable && !(info->memclk.enable)) { + clk_enable(info->memclk.clk); + info->memclk.dirty = false; + info->memclk.enable = true; + } else if (info->memclk.rate == 0 && (!enable) && + (info->memclk.enable)) { + clk_disable(info->memclk.clk); + info->memclk.dirty = false; + info->memclk.enable = false; + } + } + + return status; +} + +/** + * msm_bus_fabric_clk_commit() - Call clock enable and update clock + * values. +*/ +static int msm_bus_fabric_clk_commit(int enable, struct msm_bus_fabric *fabric) +{ + unsigned int i, nfound = 0, status = 0; + struct msm_bus_inode_info *info[fabric->pdata->nslaves]; + + if (fabric->clk_dirty == false) { + MSM_BUS_DBG("No clocks have been touched for fabric: %d\n", + fabric->fabdev.id); + goto out; + } else + status = msm_bus_fabric_clk_set(enable, &fabric->info); + + if (status) + MSM_BUS_WARN("Error setting clocks on fabric: %d\n", + fabric->fabdev.id); + + nfound = radix_tree_gang_lookup_tag(&fabric->fab_tree, (void **)&info, + fabric->fabdev.id, fabric->pdata->nslaves, SLAVE_NODE); + if (nfound == 0) { + MSM_BUS_DBG("No slaves found for fabric: %d\n", + fabric->fabdev.id); + goto out; + } + + for (i = 0; i < nfound; i++) { + status = msm_bus_fabric_clk_set(enable, info[i]); + if (status) + MSM_BUS_WARN("Error setting clocks for node: %d\n", + info[i]->node_info->id); + } + +out: + return status; +} + +/** + * msm_bus_fabric_rpm_commit() - Commit the arbitration data to RPM + * @fabric: Fabric for which the data should be committed + * */ +static int msm_bus_fabric_rpm_commit(struct msm_bus_fabric_device *fabdev, + int ctx) + +{ + int status = 0; + struct msm_bus_fabric *fabric = to_msm_bus_fabric(fabdev); + void *cdata; + + /* + * For a non-zero bandwidth request, clocks should be enabled before + * sending the arbitration data to RPM, but should be disabled only + * after commiting the data. + */ + status = msm_bus_fabric_clk_commit(ENABLE, fabric); + if (status) + MSM_BUS_DBG("Error setting clocks on fabric: %d\n", + fabric->fabdev.id); + + if (!fabric->arb_dirty) { + MSM_BUS_DBG("Not committing as fabric not arb_dirty\n"); + goto skip_arb; + } + + cdata = fabric->cdata[ctx]; + status = msm_bus_rpm_commit(fabric->pdata, ctx, + fabric->rpm_data, cdata); + if (status) + MSM_BUS_DBG("Error committing arb data for fabric: %d\n", + fabric->fabdev.id); + + fabric->arb_dirty = false; +skip_arb: + /* + * If the bandwidth request is 0 for a fabric, the clocks + * should be disabled after arbitration data is committed. + */ + status = msm_bus_fabric_clk_commit(DISABLE, fabric); + if (status) + MSM_BUS_DBG("Error disabling clocks on fabric: %d\n", + fabric->fabdev.id); + + fabric->clk_dirty = false; + return status; +} + +/** + * msm_bus_fabric_port_halt() - Used to halt a master port + * @fabric: Fabric on which the current master node is present + * @portid: Port id of the master + */ +int msm_bus_fabric_port_halt(struct msm_bus_fabric_device *fabdev, int iid) +{ + struct msm_bus_halt_vector hvector = {0, 0}; + struct msm_rpm_iv_pair rpm_data[2]; + struct msm_bus_inode_info *info = NULL; + uint8_t mport, i; + uint32_t haltid = 0; + int status = 0; + struct msm_bus_fabric *fabric = to_msm_bus_fabric(fabdev); + + info = fabdev->algo->find_node(fabdev, iid); + if (!info) { + MSM_BUS_ERR("Error: Info not found for id: %u", iid); + return -EINVAL; + } + + haltid = fabric->pdata->haltid; + for (i = 0; i < info->node_info->num_mports; i++) { + mport = info->node_info->masterp[i]; + MSM_BUS_MASTER_HALT(hvector.haltmask, hvector.haltval, mport); + rpm_data[0].id = haltid; + rpm_data[0].value = hvector.haltval; + rpm_data[1].id = haltid + 1; + rpm_data[1].value = hvector.haltmask; + + MSM_FAB_DBG("ctx: %d, id: %d, value: %d\n", + MSM_RPM_CTX_SET_0, + rpm_data[0].id, rpm_data[0].value); + MSM_FAB_DBG("ctx: %d, id: %d, value: %d\n", + MSM_RPM_CTX_SET_0, + rpm_data[1].id, rpm_data[1].value); + + if (fabric->pdata->rpm_enabled) + status = msm_rpm_set(MSM_RPM_CTX_SET_0, rpm_data, 2); + if (status) + MSM_BUS_ERR("msm_rpm_set returned: %d\n", status); + } + return status; +} + +/** + * msm_bus_fabric_port_unhalt() - Used to unhalt a master port + * @fabric: Fabric on which the current master node is present + * @portid: Port id of the master + */ +int msm_bus_fabric_port_unhalt(struct msm_bus_fabric_device *fabdev, int iid) +{ + struct msm_bus_halt_vector hvector = {0, 0}; + struct msm_rpm_iv_pair rpm_data[2]; + struct msm_bus_inode_info *info = NULL; + uint8_t mport, i; + uint32_t haltid = 0; + int status = 0; + struct msm_bus_fabric *fabric = to_msm_bus_fabric(fabdev); + + info = fabdev->algo->find_node(fabdev, iid); + if (!info) { + MSM_BUS_ERR("Error: Info not found for id: %u", iid); + return -EINVAL; + } + + haltid = fabric->pdata->haltid; + for (i = 0; i < info->node_info->num_mports; i++) { + mport = info->node_info->masterp[i]; + MSM_BUS_MASTER_UNHALT(hvector.haltmask, hvector.haltval, + mport); + rpm_data[0].id = haltid; + rpm_data[0].value = hvector.haltval; + rpm_data[1].id = haltid + 1; + rpm_data[1].value = hvector.haltmask; + + MSM_FAB_DBG("unalt: ctx: %d, id: %d, value: %d\n", + MSM_RPM_CTX_SET_SLEEP, + rpm_data[0].id, rpm_data[0].value); + MSM_FAB_DBG("unhalt: ctx: %d, id: %d, value: %d\n", + MSM_RPM_CTX_SET_SLEEP, + rpm_data[1].id, rpm_data[1].value); + + if (fabric->pdata->rpm_enabled) + status = msm_rpm_set(MSM_RPM_CTX_SET_0, rpm_data, 2); + if (status) + MSM_BUS_ERR("msm_rpm_set returned: %d\n", status); + } + + return status; +} + +/** + * msm_bus_fabric_find_gw_node() - This function finds the gateway node + * attached on a given fabric + * @id: ID of the gateway node + * @fabric: Fabric to find the gateway node on + * Function returns: Pointer to the gateway node + */ +static struct msm_bus_inode_info *msm_bus_fabric_find_gw_node(struct + msm_bus_fabric_device * fabdev, int id) +{ + struct msm_bus_inode_info *info = NULL; + struct msm_bus_fabnodeinfo *fab; + struct msm_bus_fabric *fabric; + if (!fabdev) { + MSM_BUS_ERR("No fabric device found!\n"); + return NULL; + } + + fabric = to_msm_bus_fabric(fabdev); + if (!fabric || IS_ERR(fabric)) { + MSM_BUS_ERR("No fabric type found!\n"); + return NULL; + } + list_for_each_entry(fab, &fabric->gateways, list) { + if (fab->info->node_info->priv_id == id) { + info = fab->info; + break; + } + } + + return info; +} + +static struct msm_bus_inode_info *msm_bus_fabric_find_node(struct + msm_bus_fabric_device * fabdev, int id) +{ + struct msm_bus_inode_info *info = NULL; + struct msm_bus_fabric *fabric = to_msm_bus_fabric(fabdev); + info = radix_tree_lookup(&fabric->fab_tree, id); + if (!info) + MSM_FAB_DBG("Null info found for id %d\n", id); + return info; +} + +static struct list_head *msm_bus_fabric_get_gw_list(struct msm_bus_fabric_device + *fabdev) +{ + struct msm_bus_fabric *fabric = to_msm_bus_fabric(fabdev); + if (!fabric || IS_ERR(fabric)) { + MSM_BUS_ERR("No fabric found from fabdev\n"); + return NULL; + } + return &fabric->gateways; + +} +static struct msm_bus_fab_algorithm msm_bus_algo = { + .update_clks = msm_bus_fabric_update_clks, + .update_bw = msm_bus_fabric_update_bw, + .port_halt = msm_bus_fabric_port_halt, + .port_unhalt = msm_bus_fabric_port_unhalt, + .commit = msm_bus_fabric_rpm_commit, + .find_node = msm_bus_fabric_find_node, + .find_gw_node = msm_bus_fabric_find_gw_node, + .get_gw_list = msm_bus_fabric_get_gw_list, +}; + +static int msm_bus_fabric_probe(struct platform_device *pdev) +{ + int ctx, ret = 0; + struct msm_bus_fabric *fabric; + struct msm_bus_fabric_registration *pdata; + + fabric = kzalloc(sizeof(struct msm_bus_fabric), GFP_KERNEL); + if (!fabric) { + MSM_BUS_ERR("Fabric alloc failed\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&fabric->gateways); + INIT_RADIX_TREE(&fabric->fab_tree, GFP_ATOMIC); + fabric->num_nodes = 0; + fabric->fabdev.id = pdev->id; + fabric->fabdev.visited = false; + + fabric->info.node_info = kzalloc(sizeof(struct msm_bus_node_info), + GFP_KERNEL); + if (!fabric->info.node_info) { + MSM_BUS_ERR("Fabric node info alloc failed\n"); + kfree(fabric); + return -ENOMEM; + } + fabric->info.node_info->priv_id = fabric->fabdev.id; + fabric->info.node_info->id = fabric->fabdev.id; + fabric->info.num_pnodes = -1; + fabric->info.link_info.clk[DUAL_CTX] = 0; + fabric->info.link_info.bw[DUAL_CTX] = 0; + fabric->info.link_info.clk[ACTIVE_CTX] = 0; + fabric->info.link_info.bw[ACTIVE_CTX] = 0; + + fabric->fabdev.id = pdev->id; + pdata = (struct msm_bus_fabric_registration *)pdev->dev.platform_data; + fabric->fabdev.name = pdata->name; + fabric->fabdev.algo = &msm_bus_algo; + fabric->ahb = pdata->ahb; + fabric->pdata = pdata; + msm_bus_board_assign_iids(fabric->pdata, fabric->fabdev.id); + + for (ctx = 0; ctx < NUM_CTX; ctx++) { + if (pdata->fabclk[ctx]) { + fabric->info.nodeclk[ctx].clk = clk_get(NULL, + pdata->fabclk[ctx]); + if (IS_ERR(fabric->info.nodeclk[ctx].clk)) { + MSM_BUS_ERR("Couldn't get clock %s\n", + pdata->fabclk[ctx]); + ret = -EINVAL; + goto err; + } + fabric->info.nodeclk[ctx].enable = false; + fabric->info.nodeclk[ctx].dirty = false; + } + } + + /* Find num. of slaves, masters, populate gateways, radix tree */ + ret = register_fabric_info(fabric); + if (ret) { + MSM_BUS_ERR("Could not register fabric %d info, ret: %d\n", + fabric->fabdev.id, ret); + goto err; + } + if (!fabric->ahb) { + /* Allocate memory for commit data */ + for (ctx = 0; ctx < NUM_CTX; ctx++) { + ret = allocate_commit_data(fabric->pdata, &fabric-> + cdata[ctx]); + if (ret) { + MSM_BUS_ERR("Failed to alloc commit data for " + "fab: %d, ret = %d\n", + fabric->fabdev.id, ret); + goto err; + } + } + } + /* + * clk and bw for fabric->info will contain the max bw and clk + * it will allow. This info will come from the boards file. + */ + ret = msm_bus_fabric_device_register(&fabric->fabdev); + if (ret) { + MSM_BUS_ERR("Error registering fabric %d ret %d\n", + fabric->fabdev.id, ret); + goto err; + } + + return ret; +err: + kfree(fabric->info.node_info); + kfree(fabric); + return ret; +} + +static int msm_bus_fabric_remove(struct platform_device *pdev) +{ + struct msm_bus_fabric_device *fabdev = NULL; + struct msm_bus_fabric *fabric; + int i; + int ret = 0; + + fabdev = platform_get_drvdata(pdev); + msm_bus_fabric_device_unregister(fabdev); + fabric = to_msm_bus_fabric(fabdev); + msm_bus_dbg_commit_data(fabric->fabdev.name, NULL, 0, 0, 0, + MSM_BUS_DBG_UNREGISTER); + for (i = 0; i < fabric->pdata->nmasters; i++) + radix_tree_delete(&fabric->fab_tree, fabric->fabdev.id + i); + for (i = (fabric->fabdev.id + SLAVE_ID_KEY); i < + fabric->pdata->nslaves; i++) + radix_tree_delete(&fabric->fab_tree, i); + if (!fabric->ahb) { + free_commit_data(fabric->cdata[DUAL_CTX]); + free_commit_data(fabric->cdata[ACTIVE_CTX]); + } + + kfree(fabric->info.node_info); + kfree(fabric->rpm_data); + kfree(fabric); + return ret; +} + +static struct platform_driver msm_bus_fabric_driver = { + .probe = msm_bus_fabric_probe, + .remove = msm_bus_fabric_remove, + .driver = { + .name = "msm_bus_fabric", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_bus_fabric_init_driver(void) +{ + MSM_BUS_ERR("msm_bus_fabric_init_driver\n"); + return platform_driver_register(&msm_bus_fabric_driver); +} +postcore_initcall(msm_bus_fabric_init_driver); diff --git a/arch/arm/mach-msm/msm_bus/msm_bus_rpm.c b/arch/arm/mach-msm/msm_bus/msm_bus_rpm.c new file mode 100644 index 00000000000..bfefe9ebd2d --- /dev/null +++ b/arch/arm/mach-msm/msm_bus/msm_bus_rpm.c @@ -0,0 +1,326 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_bus_core.h" + +struct commit_data { + uint16_t *bwsum; + uint16_t *arb; + unsigned long *actarb; +}; + + +/* + * The following macros are used for various operations on commit data. + * Commit data is an array of 32 bit integers. The size of arrays is unique + * to the fabric. Commit arrays are allocated at run-time based on the number + * of masters, slaves and tiered-slaves registered. + */ + +#define MSM_BUS_GET_BW_INFO(val, type, bw) \ + do { \ + (type) = MSM_BUS_GET_BW_TYPE(val); \ + (bw) = MSM_BUS_GET_BW(val); \ + } while (0) + + +#define MSM_BUS_GET_BW_INFO_BYTES (val, type, bw) \ + do { \ + (type) = MSM_BUS_GET_BW_TYPE(val); \ + (bw) = msm_bus_get_bw_bytes(val); \ + } while (0) + +#define ROUNDED_BW_VAL_FROM_BYTES(bw) \ + ((((bw) >> 17) + 1) & 0x8000 ? 0x7FFF : (((bw) >> 17) + 1)) + +#define BW_VAL_FROM_BYTES(bw) \ + ((((bw) >> 17) & 0x8000) ? 0x7FFF : ((bw) >> 17)) + +uint32_t msm_bus_set_bw_bytes(unsigned long bw) +{ + return ((((bw) & 0x1FFFF) && (((bw) >> 17) == 0)) ? + ROUNDED_BW_VAL_FROM_BYTES(bw) : BW_VAL_FROM_BYTES(bw)); + +} + +uint64_t msm_bus_get_bw_bytes(unsigned long val) +{ + return ((val) & 0x7FFF) << 17; +} + +uint16_t msm_bus_get_bw(unsigned long val) +{ + return (val)&0x7FFF; +} + +uint16_t msm_bus_create_bw_tier_pair_bytes(uint8_t type, unsigned long bw) +{ + return ((((type) == MSM_BUS_BW_TIER1 ? 1 : 0) << 15) | + (msm_bus_set_bw_bytes(bw))); +}; + +uint16_t msm_bus_create_bw_tier_pair(uint8_t type, unsigned long bw) +{ + return (((type) == MSM_BUS_BW_TIER1 ? 1 : 0) << 15) | ((bw) & 0x7FFF); +} + +void msm_bus_rpm_fill_cdata_buffer(int *curr, char *buf, const int max_size, + void *cdata, int nmasters, int nslaves, int ntslaves) +{ + int j, c; + struct commit_data *cd = (struct commit_data *)cdata; + + *curr += scnprintf(buf + *curr, max_size - *curr, "BWSum:\n"); + for (c = 0; c < nslaves; c++) + *curr += scnprintf(buf + *curr, max_size - *curr, + "0x%x\t", cd->bwsum[c]); + *curr += scnprintf(buf + *curr, max_size - *curr, "\nArb:"); + for (c = 0; c < ntslaves; c++) { + *curr += scnprintf(buf + *curr, max_size - *curr, + "\nTSlave %d:\n", c); + for (j = 0; j < nmasters; j++) + *curr += scnprintf(buf + *curr, max_size - *curr, + " 0x%x\t", cd->arb[(c * nmasters) + j]); + } +} + +/** + * allocate_commit_data() - Allocate the data for commit array in the + * format specified by RPM + * @fabric: Fabric device for which commit data is allocated + */ +int allocate_commit_data(struct msm_bus_fabric_registration *fab_pdata, + void **cdata) +{ + struct commit_data **cd = (struct commit_data **)cdata; + *cd = kzalloc(sizeof(struct commit_data), GFP_KERNEL); + if (!*cd) { + MSM_FAB_DBG("Couldn't alloc mem for cdata\n"); + return -ENOMEM; + } + (*cd)->bwsum = kzalloc((sizeof(uint16_t) * fab_pdata->nslaves), + GFP_KERNEL); + if (!(*cd)->bwsum) { + MSM_FAB_DBG("Couldn't alloc mem for slaves\n"); + kfree(*cd); + return -ENOMEM; + } + (*cd)->arb = kzalloc(((sizeof(uint16_t *)) * + (fab_pdata->ntieredslaves * fab_pdata->nmasters) + 1), + GFP_KERNEL); + if (!(*cd)->arb) { + MSM_FAB_DBG("Couldn't alloc memory for" + " slaves\n"); + kfree((*cd)->bwsum); + kfree(*cd); + return -ENOMEM; + } + (*cd)->actarb = kzalloc(((sizeof(unsigned long *)) * + (fab_pdata->ntieredslaves * fab_pdata->nmasters) + 1), + GFP_KERNEL); + if (!(*cd)->actarb) { + MSM_FAB_DBG("Couldn't alloc memory for" + " slaves\n"); + kfree((*cd)->bwsum); + kfree((*cd)->arb); + kfree(*cd); + return -ENOMEM; + } + + return 0; +} + +void free_commit_data(void *cdata) +{ + struct commit_data *cd = (struct commit_data *)cdata; + + kfree(cd->bwsum); + kfree(cd->arb); + kfree(cd->actarb); + kfree(cd); +} + +/** + * allocate_rpm_data() - Allocate the id-value pairs to be + * sent to RPM + */ +struct msm_rpm_iv_pair *allocate_rpm_data(struct msm_bus_fabric_registration + *fab_pdata) +{ + struct msm_rpm_iv_pair *rpm_data; + uint16_t count = ((fab_pdata->nmasters * fab_pdata->ntieredslaves) + + fab_pdata->nslaves + 1)/2; + + rpm_data = kmalloc((sizeof(struct msm_rpm_iv_pair) * count), + GFP_KERNEL); + return rpm_data; +} + +#define BWMASK 0x7FFF +#define TIERMASK 0x8000 +#define GET_TIER(n) (((n) & TIERMASK) >> 15) + +void msm_bus_rpm_update_bw(struct msm_bus_inode_info *hop, + struct msm_bus_inode_info *info, + struct msm_bus_fabric_registration *fab_pdata, + void *sel_cdata, int *master_tiers, + long int add_bw) +{ + int index, i, j; + struct commit_data *sel_cd = (struct commit_data *)sel_cdata; + + for (i = 0; i < hop->node_info->num_tiers; i++) { + for (j = 0; j < info->node_info->num_mports; j++) { + + uint16_t hop_tier; + if (!hop->node_info->tier) + hop_tier = MSM_BUS_BW_TIER2 - 1; + else + hop_tier = hop->node_info->tier[i] - 1; + index = ((hop_tier * fab_pdata->nmasters) + + (info->node_info->masterp[j])); + /* If there is tier, calculate arb for commit */ + if (hop->node_info->tier) { + uint16_t tier; + unsigned long tieredbw = sel_cd->actarb[index]; + if (GET_TIER(sel_cd->arb[index])) + tier = MSM_BUS_BW_TIER1; + else if (master_tiers) + /* + * By default master is only in the + * tier specified by default. + * To change the default tier, client + * needs to explicitly request for a + * different supported tier */ + tier = master_tiers[0]; + else + tier = MSM_BUS_BW_TIER2; + tieredbw += add_bw/info->node_info->num_mports; + /* If bw is 0, update tier to default */ + if (!tieredbw) + tier = MSM_BUS_BW_TIER2; + /* Update Arb for fab,get HW Mport from enum */ + sel_cd->arb[index] = + msm_bus_create_bw_tier_pair_bytes(tier, + tieredbw); + sel_cd->actarb[index] = tieredbw; + MSM_BUS_DBG("tier:%d mport: %d tiered_bw:%ld " + "bwsum: %ld\n", hop_tier, info->node_info-> + masterp[i], tieredbw, *hop->link_info.sel_bw); + } + } + } + + /* Update bwsum for slaves on fabric */ + for (i = 0; i < hop->node_info->num_sports; i++) { + sel_cd->bwsum[hop->node_info->slavep[i]] + = (uint16_t)msm_bus_create_bw_tier_pair_bytes(0, + (*hop->link_info.sel_bw/hop->node_info->num_sports)); + MSM_BUS_DBG("slavep:%d, link_bw: %ld\n", + hop->node_info->slavep[i], (*hop->link_info.sel_bw/ + hop->node_info->num_sports)); + } +} + +#define RPM_SHIFT_VAL 16 +#define RPM_SHIFT(n) ((n) << RPM_SHIFT_VAL) +/** + * msm_bus_rpm_commit() - Commit the arbitration data to RPM + * @fabric: Fabric for which the data should be committed + * */ +int msm_bus_rpm_commit(struct msm_bus_fabric_registration + *fab_pdata, int ctx, struct msm_rpm_iv_pair *rpm_data, + void *cdata) +{ + int i, j, offset = 0, status = 0, count, index = 0; + struct commit_data *cd = (struct commit_data *)cdata; + /* + * count is the number of 2-byte words required to commit the + * data to rpm. This is calculated by the following formula. + * Commit data is split into two arrays: + * 1. arb[nmasters * ntieredslaves] + * 2. bwsum[nslaves] + */ + count = ((fab_pdata->nmasters * fab_pdata->ntieredslaves) + + (fab_pdata->nslaves) + 1)/2; + + offset = fab_pdata->offset; + + /* + * Copy bwsum to rpm data + * Since bwsum is uint16, the values need to be adjusted to + * be copied to value field of rpm-data, which is 32 bits. + */ + for (i = 0; i < fab_pdata->nslaves; i += 2) { + rpm_data[index].id = offset + index; + rpm_data[index].value = RPM_SHIFT(*(cd->bwsum + i + 1)) | + *(cd->bwsum + i); + index++; + } + /* Account for odd number of slaves */ + if (fab_pdata->nslaves & 1) { + rpm_data[index].id = offset + index; + rpm_data[index].value = *(cd->arb); + rpm_data[index].value = RPM_SHIFT(rpm_data[index].value) | + *(cd->bwsum + i); + index++; + i = 1; + } else + i = 0; + + /* Copy arb values to rpm data */ + for (; i < (fab_pdata->ntieredslaves * fab_pdata->nmasters); + i += 2) { + rpm_data[index].id = offset + index; + rpm_data[index].value = RPM_SHIFT(*(cd->arb + i + 1)) | + *(cd->arb + i); + index++; + } + + MSM_FAB_DBG("rpm data for fab: %d\n", fab_pdata->id); + for (i = 0; i < count; i++) + MSM_FAB_DBG("%d %x\n", rpm_data[i].id, rpm_data[i].value); + + MSM_FAB_DBG("Commit Data: Fab: %d BWSum:\n", fab_pdata->id); + for (i = 0; i < fab_pdata->nslaves; i++) + MSM_FAB_DBG("fab_slaves:0x%x\n", cd->bwsum[i]); + MSM_FAB_DBG("Commit Data: Fab: %d Arb:\n", fab_pdata->id); + for (i = 0; i < fab_pdata->ntieredslaves; i++) { + MSM_FAB_DBG("tiered-slave: %d\n", i); + for (j = 0; j < fab_pdata->nmasters; j++) + MSM_FAB_DBG(" 0x%x\n", + cd->arb[(i * fab_pdata->nmasters) + j]); + } + + MSM_FAB_DBG("calling msm_rpm_set: %d\n", status); + msm_bus_dbg_commit_data(fab_pdata->name, cd, fab_pdata-> + nmasters, fab_pdata->nslaves, fab_pdata->ntieredslaves, + MSM_BUS_DBG_OP); + if (fab_pdata->rpm_enabled) { + if (ctx == ACTIVE_CTX) + status = msm_rpm_set(MSM_RPM_CTX_SET_0, rpm_data, + count); + } + + MSM_FAB_DBG("msm_rpm_set returned: %d\n", status); + return status; +} + diff --git a/arch/arm/mach-msm/msm_dsps.c b/arch/arm/mach-msm/msm_dsps.c new file mode 100644 index 00000000000..031f5e1c3ac --- /dev/null +++ b/arch/arm/mach-msm/msm_dsps.c @@ -0,0 +1,732 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * msm_dsps - control DSPS clocks, gpios and vregs. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DRV_NAME "msm_dsps" +#define DRV_VERSION "2.00" + +#define PPSS_PAUSE_REG 0x1804 + +#define PPSS_TIMER0_32KHZ_REG 0x1004 +#define PPSS_TIMER0_20MHZ_REG 0x0804 + +/** + * Driver Context + * + * @dev_class - device class. + * @dev_num - device major & minor number. + * @dev - the device. + * @cdev - character device for user interface. + * @pdata - platform data. + * @pil - handle to DSPS Firmware loader. + * @is_on - DSPS is on. + * @ref_count - open/close reference count. + * @ppss_base - ppss registers virtual base address. + */ +struct dsps_drv { + + struct class *dev_class; + dev_t dev_num; + struct device *dev; + struct cdev *cdev; + + struct msm_dsps_platform_data *pdata; + + void *pil; + + int is_on; + int ref_count; + + void __iomem *ppss_base; +}; + +/** + * Driver context. + */ +static struct dsps_drv *drv; + +/** + * Load DSPS Firmware. + */ +static int dsps_load(const char *name) +{ + pr_debug("%s.\n", __func__); + + drv->pil = pil_get(name); + + if (IS_ERR(drv->pil)) { + pr_err("%s: fail to load DSPS firmware %s.\n", __func__, name); + return -ENODEV; + } + + return 0; +} + +/** + * Unload DSPS Firmware. + */ +static void dsps_unload(void) +{ + pr_debug("%s.\n", __func__); + + pil_put(drv->pil); +} + +/** + * Suspend DSPS CPU. + */ +static void dsps_suspend(void) +{ + pr_debug("%s.\n", __func__); + + writel_relaxed(1, drv->ppss_base + PPSS_PAUSE_REG); + mb(); /* Make sure write commited before ioctl returns. */ +} + +/** + * Resume DSPS CPU. + */ +static void dsps_resume(void) +{ + pr_debug("%s.\n", __func__); + + writel_relaxed(0, drv->ppss_base + PPSS_PAUSE_REG); + mb(); /* Make sure write commited before ioctl returns. */ +} + +/** + * Read DSPS slow timer. + */ +static u32 dsps_read_slow_timer(void) +{ + u32 val; + + val = readl_relaxed(drv->ppss_base + PPSS_TIMER0_32KHZ_REG); + rmb(); /* order reads from the user output buffer */ + + pr_debug("%s.count=%d.\n", __func__, val); + + return val; +} + +/** + * Read DSPS fast timer. + */ +static u32 dsps_read_fast_timer(void) +{ + u32 val; + + val = readl_relaxed(drv->ppss_base + PPSS_TIMER0_20MHZ_REG); + rmb(); /* order reads from the user output buffer */ + + pr_debug("%s.count=%d.\n", __func__, val); + + return val; +} + +/** + * Power on request. + * + * Set clocks to ON. + * Set sensors chip-select GPIO to non-reset (on) value. + * + */ +static int dsps_power_on_handler(void) +{ + int ret = 0; + int i, ci, gi, ri; + + pr_debug("%s.\n", __func__); + + if (drv->is_on) { + pr_debug("%s: already ON.\n", __func__); + return 0; + } + + for (ci = 0; ci < drv->pdata->clks_num; ci++) { + const char *name = drv->pdata->clks[ci].name; + u32 rate = drv->pdata->clks[ci].rate; + struct clk *clock = drv->pdata->clks[ci].clock; + + if (clock == NULL) + continue; + + if (rate > 0) { + ret = clk_set_rate(clock, rate); + pr_debug("%s: clk %s set rate %d.", + __func__, name, rate); + if (ret) { + pr_err("%s: clk %s set rate %d. err=%d.", + __func__, name, rate, ret); + goto clk_err; + } + + } + + ret = clk_enable(clock); + if (ret) { + pr_err("%s: enable clk %s err %d.", + __func__, name, ret); + goto clk_err; + } + } + + for (gi = 0; gi < drv->pdata->gpios_num; gi++) { + const char *name = drv->pdata->gpios[gi].name; + int num = drv->pdata->gpios[gi].num; + int val = drv->pdata->gpios[gi].on_val; + int is_owner = drv->pdata->gpios[gi].is_owner; + + if (!is_owner) + continue; + + ret = gpio_direction_output(num, val); + if (ret) { + pr_err("%s: set GPIO %s num %d to %d err %d.", + __func__, name, num, val, ret); + goto gpio_err; + } + } + + for (ri = 0; ri < drv->pdata->regs_num; ri++) { + const char *name = drv->pdata->regs[ri].name; + struct regulator *reg = drv->pdata->regs[ri].reg; + int volt = drv->pdata->regs[ri].volt; + + if (reg == NULL) + continue; + + pr_debug("%s: set regulator %s.", __func__, name); + + ret = regulator_set_voltage(reg, volt, volt); + + if (ret) { + pr_err("%s: set regulator %s voltage %d err = %d.\n", + __func__, name, volt, ret); + goto reg_err; + } + + ret = regulator_enable(reg); + if (ret) { + pr_err("%s: enable regulator %s err = %d.\n", + __func__, name, ret); + goto reg_err; + } + } + + drv->is_on = true; + + return 0; + + /* + * If failling to set ANY clock/gpio/regulator to ON then we set + * them back to OFF to avoid consuming power for unused + * clocks/gpios/regulators. + */ +reg_err: + for (i = 0; i < ri; i++) { + struct regulator *reg = drv->pdata->regs[ri].reg; + + if (reg == NULL) + continue; + + regulator_disable(reg); + } + +gpio_err: + for (i = 0; i < gi; i++) { + int num = drv->pdata->gpios[i].num; + int val = drv->pdata->gpios[i].off_val; + int is_owner = drv->pdata->gpios[i].is_owner; + + if (!is_owner) + continue; + + ret = gpio_direction_output(num, val); + } + +clk_err: + for (i = 0; i < ci; i++) { + struct clk *clock = drv->pdata->clks[i].clock; + + if (clock == NULL) + continue; + + clk_disable(clock); + } + + return -ENODEV; +} + +/** + * Power off request. + * + * Set clocks to OFF. + * Set sensors chip-select GPIO to reset (off) value. + * + */ +static int dsps_power_off_handler(void) +{ + int ret; + int i; + + pr_debug("%s.\n", __func__); + + if (!drv->is_on) { + pr_debug("%s: already OFF.\n", __func__); + return 0; + } + + for (i = 0; i < drv->pdata->clks_num; i++) + if (drv->pdata->clks[i].clock) { + const char *name = drv->pdata->clks[i].name; + + pr_debug("%s: set clk %s off.", __func__, name); + clk_disable(drv->pdata->clks[i].clock); + } + + for (i = 0; i < drv->pdata->regs_num; i++) + if (drv->pdata->regs[i].reg) { + const char *name = drv->pdata->regs[i].name; + + pr_debug("%s: set regulator %s off.", __func__, name); + regulator_disable(drv->pdata->regs[i].reg); + } + + /* Clocks on/off has reference count but GPIOs don't. */ + drv->is_on = false; + + for (i = 0; i < drv->pdata->gpios_num; i++) { + const char *name = drv->pdata->gpios[i].name; + int num = drv->pdata->gpios[i].num; + int val = drv->pdata->gpios[i].off_val; + + pr_debug("%s: set gpio %s off.", __func__, name); + + ret = gpio_direction_output(num, val); + if (ret) { + pr_err("%s: set GPIO %s err %d.", __func__, name, ret); + return ret; + } + } + + return 0; +} + +/** + * IO Control - handle commands from client. + * + */ +static long dsps_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + u32 val = 0; + + pr_debug("%s.\n", __func__); + + switch (cmd) { + case DSPS_IOCTL_ON: + ret = dsps_power_on_handler(); + dsps_resume(); + break; + case DSPS_IOCTL_OFF: + if (!drv->pdata->dsps_pwr_ctl_en) { + dsps_suspend(); + ret = dsps_power_off_handler(); + } + break; + case DSPS_IOCTL_READ_SLOW_TIMER: + val = dsps_read_slow_timer(); + ret = put_user(val, (u32 __user *) arg); + break; + case DSPS_IOCTL_READ_FAST_TIMER: + val = dsps_read_fast_timer(); + ret = put_user(val, (u32 __user *) arg); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/** + * allocate resources. + * @pdev - pointer to platform device. + */ +static int dsps_alloc_resources(struct platform_device *pdev) +{ + int ret = -ENODEV; + struct resource *ppss_res; + int i; + + pr_debug("%s.\n", __func__); + + if ((drv->pdata->signature != DSPS_SIGNATURE)) { + pr_err("%s: invalid signature for pdata.", __func__); + return -EINVAL; + } + + ppss_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "ppss_reg"); + if (!ppss_res) { + pr_err("%s: failed to get ppss_reg resource.\n", __func__); + return -EINVAL; + } + + for (i = 0; i < drv->pdata->clks_num; i++) { + const char *name = drv->pdata->clks[i].name; + struct clk *clock; + + drv->pdata->clks[i].clock = NULL; + + pr_debug("%s: get clk %s.", __func__, name); + + clock = clk_get(drv->dev, name); + if (IS_ERR(clock)) { + pr_err("%s: can't get clk %s.", __func__, name); + goto clk_err; + } + drv->pdata->clks[i].clock = clock; + } + + for (i = 0; i < drv->pdata->gpios_num; i++) { + const char *name = drv->pdata->gpios[i].name; + int num = drv->pdata->gpios[i].num; + + drv->pdata->gpios[i].is_owner = false; + + pr_debug("%s: get gpio %s.", __func__, name); + + ret = gpio_request(num, name); + if (ret) { + pr_err("%s: request GPIO %s err %d.", + __func__, name, ret); + goto gpio_err; + } + + drv->pdata->gpios[i].is_owner = true; + + } + + for (i = 0; i < drv->pdata->regs_num; i++) { + const char *name = drv->pdata->regs[i].name; + + drv->pdata->regs[i].reg = NULL; + + pr_debug("%s: get regulator %s.", __func__, name); + + drv->pdata->regs[i].reg = regulator_get(drv->dev, name); + if (IS_ERR(drv->pdata->regs[i].reg)) { + pr_err("%s: get regulator %s failed.", + __func__, name); + goto reg_err; + } + } + + drv->ppss_base = ioremap(ppss_res->start, + resource_size(ppss_res)); + + return 0; + +reg_err: + for (i = 0; i < drv->pdata->regs_num; i++) { + if (drv->pdata->regs[i].reg) { + regulator_put(drv->pdata->regs[i].reg); + drv->pdata->regs[i].reg = NULL; + } + } + +gpio_err: + for (i = 0; i < drv->pdata->gpios_num; i++) + if (drv->pdata->gpios[i].is_owner) { + gpio_free(drv->pdata->gpios[i].num); + drv->pdata->gpios[i].is_owner = false; + } +clk_err: + for (i = 0; i < drv->pdata->clks_num; i++) + if (drv->pdata->clks[i].clock) { + clk_put(drv->pdata->clks[i].clock); + drv->pdata->clks[i].clock = NULL; + } + + return ret; +} + +/** + * Open File. + * + */ +static int dsps_open(struct inode *ip, struct file *fp) +{ + int ret = 0; + + pr_debug("%s.\n", __func__); + + if (drv->ref_count == 0) { + + /* clocks must be ON before loading.*/ + ret = dsps_power_on_handler(); + if (ret) + return ret; + + ret = dsps_load(drv->pdata->pil_name); + + if (ret) { + dsps_power_off_handler(); + return ret; + } + + dsps_resume(); + } + drv->ref_count++; + + return ret; +} + +/** + * free resources. + * + */ +static void dsps_free_resources(void) +{ + int i; + + pr_debug("%s.\n", __func__); + + for (i = 0; i < drv->pdata->clks_num; i++) + if (drv->pdata->clks[i].clock) { + clk_put(drv->pdata->clks[i].clock); + drv->pdata->clks[i].clock = NULL; + } + + for (i = 0; i < drv->pdata->gpios_num; i++) + if (drv->pdata->gpios[i].is_owner) { + gpio_free(drv->pdata->gpios[i].num); + drv->pdata->gpios[i].is_owner = false; + } + + for (i = 0; i < drv->pdata->regs_num; i++) { + if (drv->pdata->regs[i].reg) { + regulator_put(drv->pdata->regs[i].reg); + drv->pdata->regs[i].reg = NULL; + } + } + + iounmap(drv->ppss_base); +} + +/** + * Close File. + * + * The client shall close and re-open the file for re-loading the DSPS + * firmware. + * The file system will close the file if the user space app has crashed. + * + * If the DSPS is running, then we must reset DSPS CPU & HW before + * setting the clocks off. + * The DSPS reset should be done as part of the pil_put(). + * The DSPS reset should be used for error recovery if the DSPS firmware + * has crashed and re-loading the firmware is required. + */ +static int dsps_release(struct inode *inode, struct file *file) +{ + pr_debug("%s.\n", __func__); + + drv->ref_count--; + + if (drv->ref_count == 0) { + if (!drv->pdata->dsps_pwr_ctl_en) { + dsps_suspend(); + + dsps_unload(); + + dsps_power_off_handler(); + } + } + + return 0; +} + +const struct file_operations dsps_fops = { + .owner = THIS_MODULE, + .open = dsps_open, + .release = dsps_release, + .unlocked_ioctl = dsps_ioctl, +}; + +/** + * platform driver + * + */ +static int __devinit dsps_probe(struct platform_device *pdev) +{ + int ret; + + pr_debug("%s.\n", __func__); + + if (pdev->dev.platform_data == NULL) { + pr_err("%s: platform data is NULL.\n", __func__); + return -ENODEV; + } + + drv = kzalloc(sizeof(*drv), GFP_KERNEL); + if (drv == NULL) { + pr_err("%s: kzalloc fail.\n", __func__); + goto alloc_err; + } + drv->pdata = pdev->dev.platform_data; + + ret = dsps_alloc_resources(pdev); + if (ret) { + pr_err("%s: failed to allocate dsps resources.\n", __func__); + goto res_err; + } + + drv->dev_class = class_create(THIS_MODULE, DRV_NAME); + if (drv->dev_class == NULL) { + pr_err("%s: class_create fail.\n", __func__); + goto res_err; + } + + ret = alloc_chrdev_region(&drv->dev_num, 0, 1, DRV_NAME); + if (ret) { + pr_err("%s: alloc_chrdev_region fail.\n", __func__); + goto alloc_chrdev_region_err; + } + + drv->dev = device_create(drv->dev_class, NULL, + drv->dev_num, + drv, DRV_NAME); + if (IS_ERR(drv->dev)) { + pr_err("%s: device_create fail.\n", __func__); + goto device_create_err; + } + + drv->cdev = cdev_alloc(); + if (drv->cdev == NULL) { + pr_err("%s: cdev_alloc fail.\n", __func__); + goto cdev_alloc_err; + } + cdev_init(drv->cdev, &dsps_fops); + drv->cdev->owner = THIS_MODULE; + + ret = cdev_add(drv->cdev, drv->dev_num, 1); + if (ret) { + pr_err("%s: cdev_add fail.\n", __func__); + goto cdev_add_err; + } + + return 0; + +cdev_add_err: + kfree(drv->cdev); +cdev_alloc_err: + device_destroy(drv->dev_class, drv->dev_num); +device_create_err: + unregister_chrdev_region(drv->dev_num, 1); +alloc_chrdev_region_err: + class_destroy(drv->dev_class); +res_err: + kfree(drv); + drv = NULL; +alloc_err: + return -ENODEV; +} + +static int __devexit dsps_remove(struct platform_device *pdev) +{ + pr_debug("%s.\n", __func__); + + dsps_power_off_handler(); + dsps_free_resources(); + + cdev_del(drv->cdev); + kfree(drv->cdev); + drv->cdev = NULL; + device_destroy(drv->dev_class, drv->dev_num); + unregister_chrdev_region(drv->dev_num, 1); + class_destroy(drv->dev_class); + kfree(drv); + drv = NULL; + + return 0; +} + +static struct platform_driver dsps_driver = { + .probe = dsps_probe, + .remove = __exit_p(dsps_remove), + .driver = { + .name = "msm_dsps", + }, +}; + +/** + * Module Init. + */ +static int __init dsps_init(void) +{ + int ret; + + pr_info("%s driver version %s.\n", DRV_NAME, DRV_VERSION); + + ret = platform_driver_register(&dsps_driver); + + if (ret) + pr_err("dsps_init.err=%d.\n", ret); + + return ret; +} + +/** + * Module Exit. + */ +static void __exit dsps_exit(void) +{ + pr_debug("%s.\n", __func__); + + platform_driver_unregister(&dsps_driver); +} + +module_init(dsps_init); +module_exit(dsps_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Dedicated Sensors Processor Subsystem (DSPS) driver"); +MODULE_AUTHOR("Amir Samuelov "); + diff --git a/arch/arm/mach-msm/msm_fault_handlers.c b/arch/arm/mach-msm/msm_fault_handlers.c new file mode 100644 index 00000000000..c97585605b4 --- /dev/null +++ b/arch/arm/mach-msm/msm_fault_handlers.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) 1995 Linus Torvalds + * Modifications for ARM processor (c) 1995-2004 Russell King + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include "acpuclock.h" + +#define __str(x) #x +#define MRC(x, v1, v2, v4, v5, v6) do { \ + unsigned int __##x; \ + asm("mrc " __str(v1) ", " __str(v2) ", %0, " __str(v4) ", " \ + __str(v5) ", " __str(v6) "\n" \ + : "=r" (__##x)); \ + pr_info("%s: %s = 0x%.8x\n", __func__, #x, __##x); \ +} while (0) + +static int msm_imp_ext_abort(unsigned long addr, unsigned int fsr, + struct pt_regs *regs) +{ + int cpu; + unsigned int regval; + static unsigned char flush_toggle; + + asm("mrc p15, 7, %0, c15, c0, 1\n" /* read EFSR for fault status */ + : "=r" (regval)); + if (regval == 0x2) { + /* Fault was caused by icache parity error. Alternate + * simply retrying the access and flushing the icache. */ + flush_toggle ^= 1; + if (flush_toggle) + asm("mcr p15, 0, %0, c7, c5, 0\n" + : + : "r" (regval)); /* input value is ignored */ + /* Clear fault in EFSR. */ + asm("mcr p15, 7, %0, c15, c0, 1\n" + : + : "r" (regval)); + /* Clear fault in ADFSR. */ + regval = 0; + asm("mcr p15, 0, %0, c5, c1, 0\n" + : + : "r" (regval)); + return 0; + } + + MRC(ADFSR, p15, 0, c5, c1, 0); + MRC(DFSR, p15, 0, c5, c0, 0); + MRC(ACTLR, p15, 0, c1, c0, 1); + MRC(EFSR, p15, 7, c15, c0, 1); + MRC(L2SR, p15, 3, c15, c1, 0); + MRC(L2CR0, p15, 3, c15, c0, 1); + MRC(L2CPUESR, p15, 3, c15, c1, 1); + MRC(L2CPUCR, p15, 3, c15, c0, 2); + MRC(SPESR, p15, 1, c9, c7, 0); + MRC(SPCR, p15, 0, c9, c7, 0); + MRC(DMACHSR, p15, 1, c11, c0, 0); + MRC(DMACHESR, p15, 1, c11, c0, 1); + MRC(DMACHCR, p15, 0, c11, c0, 2); + for_each_online_cpu(cpu) + pr_info("cpu %d, acpuclk rate: %lu kHz\n", cpu, + acpuclk_get_rate(cpu)); + + return 1; +} + +static int __init msm_register_fault_handlers(void) +{ + /* hook in our handler for imprecise abort for when we get + i-cache parity errors */ + hook_fault_code(22, msm_imp_ext_abort, SIGBUS, 0, + "imprecise external abort"); + + return 0; +} +arch_initcall(msm_register_fault_handlers); diff --git a/arch/arm/mach-msm/msm_kexec.c b/arch/arm/mach-msm/msm_kexec.c new file mode 100644 index 00000000000..4597cf0da81 --- /dev/null +++ b/arch/arm/mach-msm/msm_kexec.c @@ -0,0 +1,30 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#ifdef CONFIG_MSM_WATCHDOG +#include + +#define WDT0_EN (MSM_TMR_BASE + 0x40) +#endif + +void arch_kexec(void) +{ +#ifdef CONFIG_MSM_WATCHDOG + /* Prevent watchdog from resetting SoC */ + writel(0, WDT0_EN); + pr_crit("KEXEC: MSM Watchdog Exit - Deactivated\n"); +#endif + return; +} diff --git a/arch/arm/mach-msm/msm_pm_qos.c b/arch/arm/mach-msm/msm_pm_qos.c new file mode 100644 index 00000000000..698d72e5d16 --- /dev/null +++ b/arch/arm/mach-msm/msm_pm_qos.c @@ -0,0 +1,69 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +struct pm_qos_request_list { + struct list_head list; + union { + s32 value; + s32 usec; + s32 kbps; + }; + int pm_qos_class; +}; + + +struct pm_qos_object { + struct pm_qos_request_list requests; + struct blocking_notifier_head *notifiers; + struct miscdevice pm_qos_power_miscdev; + char *name; + s32 default_value; + atomic_t target_value; + s32 (*comparitor)(s32, s32); +}; + + +int msm_pm_qos_add(struct pm_qos_object *class, char *request_name, + s32 value, void **request_data) +{ + char *resource_name = class->name; + + /* Non-default requirements are not allowed since, if the resource + * isn't available yet, the request can't be honoured. */ + BUG_ON(value != class->default_value); + + /* Create an client for a resource. Store the client pointer + * where request_data points when the the resource is available. */ + *request_data = msm_req_add(resource_name, request_name); + if (IS_ERR(*request_data)) + return PTR_ERR(*request_data); + + return 0; +} + +int msm_pm_qos_update(struct pm_qos_object *class, char *request_name, + s32 value, void **request_data) +{ + return msm_req_update(*request_data, value); +} + +int msm_pm_qos_remove(struct pm_qos_object *class, char *request_name, + s32 value, void **request_data) +{ + return msm_req_remove(*request_data); +} + diff --git a/arch/arm/mach-msm/msm_pm_qos.h b/arch/arm/mach-msm/msm_pm_qos.h new file mode 100644 index 00000000000..43acf3bb63d --- /dev/null +++ b/arch/arm/mach-msm/msm_pm_qos.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_PM_QOS_H__ +#define __MSM_PM_QOS_H__ + +#include + +struct pm_qos_object; + +int msm_pm_qos_add(struct pm_qos_object *class, char *request_name, + s32 value, void **request_data); +int msm_pm_qos_update(struct pm_qos_object *class, char *request_name, + s32 value, void **request_data); +int msm_pm_qos_remove(struct pm_qos_object *class, char *request_name, + s32 value, void **request_data); + +#endif /* __MSM_PM_QOS_H__ */ + diff --git a/arch/arm/mach-msm/msm_reqs.c b/arch/arm/mach-msm/msm_reqs.c new file mode 100644 index 00000000000..be42d2e44a8 --- /dev/null +++ b/arch/arm/mach-msm/msm_reqs.c @@ -0,0 +1,125 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include "npa_remote.h" +#include + +struct request { + atomic_t creation_pending; + struct npa_client *client; + char *client_name; + char *resource_name; +}; + +/** + * npa_res_available_cb - NPA resource-available callback. + * @u_data: Pointer to request struct. + * @t,d,n: Unused + */ +static void npa_res_available_cb(void *u_data, unsigned t, void *d, unsigned n) +{ + struct request *request = u_data; + + /* Make sure client is still needed. */ + if (!atomic_dec_and_test(&request->creation_pending)) { + kfree(request); + return; + } + + /* Create NPA 'required' client. */ + request->client = npa_create_sync_client(request->resource_name, + request->client_name, NPA_CLIENT_REQUIRED); + if (IS_ERR(request->client)) { + pr_crit("npa_req: Failed to create NPA client '%s' " + "for resource '%s'. (Error %ld)\n", + request->client_name, request->resource_name, + PTR_ERR(request->client)); + BUG(); + } + + return; +} + +/** + * msm_req_add - Creates an NPA request and returns a handle. Non-blocking. + * @req_name: Name of the request + * @res_name: Name of the NPA resource the request is for + */ +void *msm_req_add(char *res_name, char *req_name) +{ + struct request *request; + + request = kmalloc(sizeof(*request), GFP_KERNEL); + if (!request) + return ERR_PTR(-ENOMEM); + + /* Populate request data. */ + request->client_name = req_name; + request->resource_name = res_name; + + /* Mark client creation as pending. */ + atomic_set(&request->creation_pending, 1); + + /* Create NPA client when the resource becomes available. */ + npa_resource_available(res_name, npa_res_available_cb, request); + + return request; +} + +/** + * msm_req_update - Updates an existing NPA request. May block. + * @req: Request handle + * @value: Request value + */ +int msm_req_update(void *req, s32 value) +{ + struct request *request = req; + int rc = 0; + + if (atomic_read(&request->creation_pending)) { + pr_err("%s: Error: No client '%s' for resource '%s'.\n", + __func__, request->client_name, request->resource_name); + return -ENXIO; + } + + if (value == MSM_REQ_DEFAULT_VALUE) + npa_complete_request(request->client); + else + rc = npa_issue_required_request(request->client, value); + + return rc; +} + +/** + * msm_req_remove - Removes an existing NPA request. May block. + * @req: Request handle + */ +int msm_req_remove(void *req) +{ + struct request *request = req; + + /* Remove client if it's been created. */ + if (!atomic_dec_and_test(&request->creation_pending)) { + npa_cancel_request(request->client); + npa_destroy_client(request->client); + kfree(request); + } + + return 0; +} + diff --git a/arch/arm/mach-msm/msm_rq_stats.c b/arch/arm/mach-msm/msm_rq_stats.c new file mode 100644 index 00000000000..81c8ad59817 --- /dev/null +++ b/arch/arm/mach-msm/msm_rq_stats.c @@ -0,0 +1,264 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm MSM Runqueue Stats Interface for Userspace + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct rq_data { + unsigned int rq_avg; + unsigned int rq_poll_ms; + unsigned int def_timer_ms; + unsigned int def_interval; + int64_t last_time; + int64_t total_time; + int64_t def_start_time; + struct delayed_work rq_work; + struct attribute_group *attr_group; + struct kobject *kobj; + struct delayed_work def_timer_work; +}; + +static struct rq_data rq_info; +static DEFINE_SPINLOCK(rq_lock); +static struct workqueue_struct *rq_wq; + +static void rq_work_fn(struct work_struct *work) +{ + int64_t time_diff = 0; + int64_t rq_avg = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&rq_lock, flags); + + if (!rq_info.last_time) + rq_info.last_time = ktime_to_ns(ktime_get()); + if (!rq_info.rq_avg) + rq_info.total_time = 0; + + rq_avg = nr_running() * 10; + time_diff = ktime_to_ns(ktime_get()) - rq_info.last_time; + do_div(time_diff, (1000 * 1000)); + + if (time_diff && rq_info.total_time) { + rq_avg = (rq_avg * time_diff) + + (rq_info.rq_avg * rq_info.total_time); + do_div(rq_avg, rq_info.total_time + time_diff); + } + + rq_info.rq_avg = (unsigned int)rq_avg; + + /* Set the next poll */ + if (rq_info.rq_poll_ms) + queue_delayed_work(rq_wq, &rq_info.rq_work, + msecs_to_jiffies(rq_info.rq_poll_ms)); + + rq_info.total_time += time_diff; + rq_info.last_time = ktime_to_ns(ktime_get()); + + spin_unlock_irqrestore(&rq_lock, flags); +} + +static void def_work_fn(struct work_struct *work) +{ + int64_t diff; + + diff = ktime_to_ns(ktime_get()) - rq_info.def_start_time; + do_div(diff, 1000 * 1000); + rq_info.def_interval = (unsigned int) diff; + + /* Notify polling threads on change of value */ + sysfs_notify(rq_info.kobj, NULL, "def_timer_ms"); +} + +static ssize_t show_run_queue_avg(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + unsigned int val = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&rq_lock, flags); + /* rq avg currently available only on one core */ + val = rq_info.rq_avg; + rq_info.rq_avg = 0; + spin_unlock_irqrestore(&rq_lock, flags); + + return sprintf(buf, "%d.%d\n", val/10, val%10); +} + +static ssize_t show_run_queue_poll_ms(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret = 0; + unsigned long flags = 0; + + spin_lock_irqsave(&rq_lock, flags); + ret = sprintf(buf, "%u\n", rq_info.rq_poll_ms); + spin_unlock_irqrestore(&rq_lock, flags); + + return ret; +} + +static ssize_t store_run_queue_poll_ms(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int val = 0; + unsigned long flags = 0; + static DEFINE_MUTEX(lock_poll_ms); + + mutex_lock(&lock_poll_ms); + + spin_lock_irqsave(&rq_lock, flags); + sscanf(buf, "%u", &val); + rq_info.rq_poll_ms = val; + spin_unlock_irqrestore(&rq_lock, flags); + + if (val <= 0) + cancel_delayed_work(&rq_info.rq_work); + else + queue_delayed_work(rq_wq, &rq_info.rq_work, + msecs_to_jiffies(val)); + + mutex_unlock(&lock_poll_ms); + + return count; +} + +static ssize_t show_def_timer_ms(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", rq_info.def_interval); +} + +static ssize_t store_def_timer_ms(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + unsigned int val = 0; + + sscanf(buf, "%u", &val); + rq_info.def_timer_ms = val; + + if (val <= 0) + cancel_delayed_work(&rq_info.def_timer_work); + else { + rq_info.def_start_time = ktime_to_ns(ktime_get()); + queue_delayed_work(rq_wq, &rq_info.def_timer_work, + msecs_to_jiffies(val)); + } + + return count; +} + +#define MSM_RQ_STATS_RO_ATTRIB(att) ({ \ + struct attribute *attrib = NULL; \ + struct kobj_attribute *ptr = NULL; \ + ptr = kzalloc(sizeof(struct kobj_attribute), GFP_KERNEL); \ + if (ptr) { \ + ptr->attr.name = #att; \ + ptr->attr.mode = S_IRUGO; \ + ptr->show = show_##att; \ + ptr->store = NULL; \ + attrib = &ptr->attr; \ + } \ + attrib; }) + +#define MSM_RQ_STATS_RW_ATTRIB(att) ({ \ + struct attribute *attrib = NULL; \ + struct kobj_attribute *ptr = NULL; \ + ptr = kzalloc(sizeof(struct kobj_attribute), GFP_KERNEL); \ + if (ptr) { \ + ptr->attr.name = #att; \ + ptr->attr.mode = S_IWUSR|S_IRUSR; \ + ptr->show = show_##att; \ + ptr->store = store_##att; \ + attrib = &ptr->attr; \ + } \ + attrib; }) + +static int init_rq_attribs(void) +{ + int i; + int err = 0; + const int attr_count = 4; + + struct attribute **attribs = + kzalloc(sizeof(struct attribute *) * attr_count, GFP_KERNEL); + + if (!attribs) + goto rel; + + rq_info.rq_avg = 0; + rq_info.rq_poll_ms = 0; + + attribs[0] = MSM_RQ_STATS_RW_ATTRIB(def_timer_ms); + attribs[1] = MSM_RQ_STATS_RO_ATTRIB(run_queue_avg); + attribs[2] = MSM_RQ_STATS_RW_ATTRIB(run_queue_poll_ms); + attribs[3] = NULL; + + for (i = 0; i < attr_count - 1 ; i++) { + if (!attribs[i]) + goto rel; + } + + rq_info.attr_group = kzalloc(sizeof(struct attribute_group), + GFP_KERNEL); + if (!rq_info.attr_group) + goto rel; + rq_info.attr_group->attrs = attribs; + + /* Create /sys/devices/system/cpu/cpu0/rq-stats/... */ + rq_info.kobj = kobject_create_and_add("rq-stats", + &get_cpu_sysdev(0)->kobj); + if (!rq_info.kobj) + goto rel; + + err = sysfs_create_group(rq_info.kobj, rq_info.attr_group); + if (err) + kobject_put(rq_info.kobj); + else + kobject_uevent(rq_info.kobj, KOBJ_ADD); + + if (!err) + return err; + +rel: + for (i = 0; i < attr_count - 1 ; i++) + kfree(attribs[i]); + kfree(attribs); + kfree(rq_info.attr_group); + kfree(rq_info.kobj); + + return -ENOMEM; +} + +static int __init msm_rq_stats_init(void) +{ + rq_wq = create_singlethread_workqueue("rq_stats"); + BUG_ON(!rq_wq); + INIT_DELAYED_WORK_DEFERRABLE(&rq_info.rq_work, rq_work_fn); + INIT_DELAYED_WORK_DEFERRABLE(&rq_info.def_timer_work, def_work_fn); + return init_rq_attribs(); +} +late_initcall(msm_rq_stats_init); diff --git a/arch/arm/mach-msm/msm_show_resume_irq.c b/arch/arm/mach-msm/msm_show_resume_irq.c new file mode 100644 index 00000000000..82093673c49 --- /dev/null +++ b/arch/arm/mach-msm/msm_show_resume_irq.c @@ -0,0 +1,22 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +int msm_show_resume_irq_mask; + +module_param_named( + debug_mask, msm_show_resume_irq_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); diff --git a/arch/arm/mach-msm/msm_vibrator.c b/arch/arm/mach-msm/msm_vibrator.c new file mode 100644 index 00000000000..94d5c42789e --- /dev/null +++ b/arch/arm/mach-msm/msm_vibrator.c @@ -0,0 +1,161 @@ +/* include/asm/mach-msm/htc_pwrsink.h + * + * Copyright (C) 2008 HTC Corporation. + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2011 Code Aurora Forum. 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 +#include +#include +#include +#include +#include "pmic.h" +#include "timed_output.h" + +#include + +#define PM_LIBPROG 0x30000061 +#if (CONFIG_MSM_AMSS_VERSION == 6220) || (CONFIG_MSM_AMSS_VERSION == 6225) +#define PM_LIBVERS 0xfb837d0b +#else +#define PM_LIBVERS 0x10001 +#endif + +#define HTC_PROCEDURE_SET_VIB_ON_OFF 21 +#define PMIC_VIBRATOR_LEVEL (3000) + +static struct work_struct work_vibrator_on; +static struct work_struct work_vibrator_off; +static struct hrtimer vibe_timer; + +#ifdef CONFIG_PM8XXX_RPC_VIBRATOR +static void set_pmic_vibrator(int on) +{ + int rc; + + rc = pmic_vib_mot_set_mode(PM_VIB_MOT_MODE__MANUAL); + if (rc) { + pr_err("%s: Vibrator set mode failed", __func__); + return; + } + + if (on) + rc = pmic_vib_mot_set_volt(PMIC_VIBRATOR_LEVEL); + else + rc = pmic_vib_mot_set_volt(0); + + if (rc) + pr_err("%s: Vibrator set voltage level failed", __func__); +} +#else +static void set_pmic_vibrator(int on) +{ + static struct msm_rpc_endpoint *vib_endpoint; + struct set_vib_on_off_req { + struct rpc_request_hdr hdr; + uint32_t data; + } req; + + if (!vib_endpoint) { + vib_endpoint = msm_rpc_connect(PM_LIBPROG, PM_LIBVERS, 0); + if (IS_ERR(vib_endpoint)) { + printk(KERN_ERR "init vib rpc failed!\n"); + vib_endpoint = 0; + return; + } + } + + + if (on) + req.data = cpu_to_be32(PMIC_VIBRATOR_LEVEL); + else + req.data = cpu_to_be32(0); + + msm_rpc_call(vib_endpoint, HTC_PROCEDURE_SET_VIB_ON_OFF, &req, + sizeof(req), 5 * HZ); +} +#endif + +static void pmic_vibrator_on(struct work_struct *work) +{ + set_pmic_vibrator(1); +} + +static void pmic_vibrator_off(struct work_struct *work) +{ + set_pmic_vibrator(0); +} + +static void timed_vibrator_on(struct timed_output_dev *sdev) +{ + schedule_work(&work_vibrator_on); +} + +static void timed_vibrator_off(struct timed_output_dev *sdev) +{ + schedule_work(&work_vibrator_off); +} + +static void vibrator_enable(struct timed_output_dev *dev, int value) +{ + hrtimer_cancel(&vibe_timer); + + if (value == 0) + timed_vibrator_off(dev); + else { + value = (value > 15000 ? 15000 : value); + + timed_vibrator_on(dev); + + hrtimer_start(&vibe_timer, + ktime_set(value / 1000, (value % 1000) * 1000000), + HRTIMER_MODE_REL); + } +} + +static int vibrator_get_time(struct timed_output_dev *dev) +{ + if (hrtimer_active(&vibe_timer)) { + ktime_t r = hrtimer_get_remaining(&vibe_timer); + struct timeval t = ktime_to_timeval(r); + return t.tv_sec * 1000 + t.tv_usec / 1000; + } + return 0; +} + +static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer) +{ + timed_vibrator_off(NULL); + return HRTIMER_NORESTART; +} + +static struct timed_output_dev pmic_vibrator = { + .name = "vibrator", + .get_time = vibrator_get_time, + .enable = vibrator_enable, +}; + +void __init msm_init_pmic_vibrator(void) +{ + INIT_WORK(&work_vibrator_on, pmic_vibrator_on); + INIT_WORK(&work_vibrator_off, pmic_vibrator_off); + + hrtimer_init(&vibe_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + vibe_timer.function = vibrator_timer_func; + + timed_output_dev_register(&pmic_vibrator); +} + +MODULE_DESCRIPTION("timed output pmic vibrator device"); +MODULE_LICENSE("GPL"); + diff --git a/arch/arm/mach-msm/msm_watchdog.c b/arch/arm/mach-msm/msm_watchdog.c new file mode 100644 index 00000000000..40fa37e5bba --- /dev/null +++ b/arch/arm/mach-msm/msm_watchdog.c @@ -0,0 +1,340 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_watchdog.h" + +#define TCSR_WDT_CFG 0x30 + +#define WDT0_RST (MSM_TMR0_BASE + 0x38) +#define WDT0_EN (MSM_TMR0_BASE + 0x40) +#define WDT0_BARK_TIME (MSM_TMR0_BASE + 0x4C) +#define WDT0_BITE_TIME (MSM_TMR0_BASE + 0x5C) + +/* Watchdog pet interval in ms */ +#define PET_DELAY 3000 +static unsigned long delay_time; +static unsigned long long last_pet; + +/* + * On the kernel command line specify + * msm_watchdog.enable=1 to enable the watchdog + * By default watchdog is turned on + */ +static int enable = 1; +module_param(enable, int, 0); + +/* + * If the watchdog is enabled at bootup (enable=1), + * the runtime_disable sysfs node at + * /sys/module/msm_watchdog/runtime_disable + * can be used to deactivate the watchdog. + * This is a one-time setting. The watchdog + * cannot be re-enabled once it is disabled. + */ +static int runtime_disable; +static DEFINE_MUTEX(disable_lock); +static int wdog_enable_set(const char *val, struct kernel_param *kp); +module_param_call(runtime_disable, wdog_enable_set, param_get_int, + &runtime_disable, 0644); + +/* + * On the kernel command line specify msm_watchdog.appsbark=1 to handle + * watchdog barks in Linux. By default barks are processed by the secure side. + */ +static int appsbark; +module_param(appsbark, int, 0); + +/* + * Use /sys/module/msm_watchdog/parameters/print_all_stacks + * to control whether stacks of all running + * processes are printed when a wdog bark is received. + */ +static int print_all_stacks = 1; +module_param(print_all_stacks, int, S_IRUGO | S_IWUSR); + +/* Area for context dump in secure mode */ +static void *scm_regsave; + +static void pet_watchdog_work(struct work_struct *work); +static void init_watchdog_work(struct work_struct *work); +static DECLARE_DELAYED_WORK(dogwork_struct, pet_watchdog_work); +static DECLARE_WORK(init_dogwork_struct, init_watchdog_work); + +static int msm_watchdog_suspend(void) +{ + __raw_writel(1, WDT0_RST); + __raw_writel(0, WDT0_EN); + mb(); + return NOTIFY_DONE; +} +static int msm_watchdog_resume(void) +{ + __raw_writel(1, WDT0_EN); + __raw_writel(1, WDT0_RST); + return NOTIFY_DONE; +} + +static int msm_watchdog_power_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + switch (event) { + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + return msm_watchdog_resume(); + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + return msm_watchdog_suspend(); + default: + return NOTIFY_DONE; + } +} + +static int panic_wdog_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + if (panic_timeout == 0) { + __raw_writel(0, WDT0_EN); + mb(); + secure_writel(0, MSM_TCSR_BASE + TCSR_WDT_CFG); + } else { + __raw_writel(32768 * (panic_timeout + 4), WDT0_BARK_TIME); + __raw_writel(32768 * (panic_timeout + 4), WDT0_BITE_TIME); + __raw_writel(1, WDT0_RST); + } + return NOTIFY_DONE; +} + +static struct notifier_block panic_blk = { + .notifier_call = panic_wdog_handler, +}; + +static struct notifier_block msm_watchdog_power_notifier = { + .notifier_call = msm_watchdog_power_event, +}; + +static int wdog_enable_set(const char *val, struct kernel_param *kp) +{ + int ret = 0; + int old_val = runtime_disable; + + mutex_lock(&disable_lock); + + if (!enable) { + printk(KERN_INFO "MSM Watchdog is not active.\n"); + ret = -EINVAL; + goto done; + } + + ret = param_set_int(val, kp); + + if (ret) + goto done; + + switch (runtime_disable) { + + case 1: + if (!old_val) { + __raw_writel(0, WDT0_EN); + unregister_pm_notifier(&msm_watchdog_power_notifier); + + /* may be suspended after the first write above */ + __raw_writel(0, WDT0_EN); + mb(); + secure_writel(0, MSM_TCSR_BASE + TCSR_WDT_CFG); + free_irq(WDT0_ACCSCSSNBARK_INT, 0); + enable = 0; + atomic_notifier_chain_unregister(&panic_notifier_list, + &panic_blk); + cancel_delayed_work(&dogwork_struct); + printk(KERN_INFO "MSM Watchdog deactivated.\n"); + } + break; + + default: + runtime_disable = old_val; + ret = -EINVAL; + break; + + } + +done: + mutex_unlock(&disable_lock); + return ret; +} + +void pet_watchdog(void) +{ + __raw_writel(1, WDT0_RST); + last_pet = sched_clock(); +} + +static void pet_watchdog_work(struct work_struct *work) +{ + pet_watchdog(); + + if (enable) + schedule_delayed_work_on(0, &dogwork_struct, delay_time); +} + +static void __exit exit_watchdog(void) +{ + if (enable) { + __raw_writel(0, WDT0_EN); + unregister_pm_notifier(&msm_watchdog_power_notifier); + __raw_writel(0, WDT0_EN); /* In case we got suspended + * mid-exit */ + mb(); + secure_writel(0, MSM_TCSR_BASE + TCSR_WDT_CFG); + free_irq(WDT0_ACCSCSSNBARK_INT, 0); + enable = 0; + } + printk(KERN_INFO "MSM Watchdog Exit - Deactivated\n"); +} + +static irqreturn_t wdog_bark_handler(int irq, void *dev_id) +{ + unsigned long nanosec_rem; + unsigned long long t = sched_clock(); + struct task_struct *tsk; + + nanosec_rem = do_div(t, 1000000000); + printk(KERN_INFO "Watchdog bark! Now = %lu.%06lu\n", (unsigned long) t, + nanosec_rem / 1000); + + nanosec_rem = do_div(last_pet, 1000000000); + printk(KERN_INFO "Watchdog last pet at %lu.%06lu\n", (unsigned long) + last_pet, nanosec_rem / 1000); + + if (print_all_stacks) { + + /* Suspend wdog until all stacks are printed */ + msm_watchdog_suspend(); + + printk(KERN_INFO "Stack trace dump:\n"); + + for_each_process(tsk) { + printk(KERN_INFO "\nPID: %d, Name: %s\n", + tsk->pid, tsk->comm); + show_stack(tsk, NULL); + } + + msm_watchdog_resume(); + } + + panic("Apps watchdog bark received!"); + return IRQ_HANDLED; +} + +#define SCM_SET_REGSAVE_CMD 0x2 + +static void init_watchdog_work(struct work_struct *work) +{ + int ret; + struct { + unsigned addr; + int len; + } cmd_buf; + + if (!enable) { + printk(KERN_INFO "MSM Watchdog Not Initialized\n"); + return; + } + + /* Must request irq before sending scm command */ + ret = request_irq(WDT0_ACCSCSSNBARK_INT, wdog_bark_handler, 0, + "apps_wdog_bark", NULL); + if (ret) + return; + +#ifdef CONFIG_MSM_SCM + if (!appsbark) { + scm_regsave = (void *)__get_free_page(GFP_KERNEL); + + if (scm_regsave) { + cmd_buf.addr = __pa(scm_regsave); + cmd_buf.len = PAGE_SIZE; + + ret = scm_call(SCM_SVC_UTIL, SCM_SET_REGSAVE_CMD, + &cmd_buf, sizeof(cmd_buf), NULL, 0); + if (ret) + pr_err("Setting register save address failed.\n" + "Registers won't be dumped on a dog " + "bite\n"); + } else + pr_err("Allocating register save space failed\n" + "Registers won't be dumped on a dog bite\n"); + /* + * No need to bail if allocation fails. Simply don't + * send the command, and the secure side will reset + * without saving registers. + */ + } +#endif + secure_writel(1, MSM_TCSR_BASE + TCSR_WDT_CFG); + delay_time = msecs_to_jiffies(PET_DELAY); + + /* 32768 ticks = 1 second */ + if (machine_is_msm8960_sim()) { + __raw_writel(32768*8, WDT0_BARK_TIME); + __raw_writel(32768*10, WDT0_BITE_TIME); + } else { + __raw_writel(32768*4, WDT0_BARK_TIME); + __raw_writel(32768*5, WDT0_BITE_TIME); + } + + ret = register_pm_notifier(&msm_watchdog_power_notifier); + if (ret) { + free_irq(WDT0_ACCSCSSNBARK_INT, NULL); + return; + } + + schedule_delayed_work_on(0, &dogwork_struct, delay_time); + + atomic_notifier_chain_register(&panic_notifier_list, + &panic_blk); + + __raw_writel(1, WDT0_EN); + __raw_writel(1, WDT0_RST); + last_pet = sched_clock(); + + printk(KERN_INFO "MSM Watchdog Initialized\n"); + + return; +} + +static int __init init_watchdog(void) +{ + schedule_work_on(0, &init_dogwork_struct); + return 0; +} + +late_initcall(init_watchdog); +module_exit(exit_watchdog); +MODULE_DESCRIPTION("MSM Watchdog Driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/msm_watchdog.h b/arch/arm/mach-msm/msm_watchdog.h new file mode 100644 index 00000000000..23ff60e361c --- /dev/null +++ b/arch/arm/mach-msm/msm_watchdog.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ARCH_ARM_MACH_MSM_MSM_WATCHDOG_H +#define __ARCH_ARM_MACH_MSM_MSM_WATCHDOG_H + +#ifdef CONFIG_MSM_WATCHDOG +void pet_watchdog(void); +#else +static inline void pet_watchdog(void) { } +#endif + +#endif diff --git a/arch/arm/mach-msm/msm_xo.c b/arch/arm/mach-msm/msm_xo.c new file mode 100644 index 00000000000..5b66b96d8a8 --- /dev/null +++ b/arch/arm/mach-msm/msm_xo.c @@ -0,0 +1,301 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rpm_resources.h" + +static DEFINE_SPINLOCK(msm_xo_lock); + +struct msm_xo { + unsigned votes[NUM_MSM_XO_MODES]; + unsigned mode; + struct list_head voters; +}; + +struct msm_xo_voter { + const char *name; + unsigned mode; + struct msm_xo *xo; + struct list_head list; +}; + +static struct msm_xo msm_xo_sources[NUM_MSM_XO_IDS]; + +#ifdef CONFIG_DEBUG_FS +static const char *msm_xo_mode_to_str(unsigned mode) +{ + switch (mode) { + case MSM_XO_MODE_ON: + return "ON"; + case MSM_XO_MODE_PIN_CTRL: + return "PIN"; + case MSM_XO_MODE_OFF: + return "OFF"; + default: + return "ERR"; + } +} + +static void msm_xo_dump_xo(struct seq_file *m, struct msm_xo *xo, + const char *name) +{ + struct msm_xo_voter *voter; + + seq_printf(m, "%-20s%s\n", name, msm_xo_mode_to_str(xo->mode)); + list_for_each_entry(voter, &xo->voters, list) + seq_printf(m, " %s %-16s %s\n", + xo->mode == voter->mode ? "*" : " ", + voter->name, + msm_xo_mode_to_str(voter->mode)); +} + +static int msm_xo_show_voters(struct seq_file *m, void *v) +{ + unsigned long flags; + + spin_lock_irqsave(&msm_xo_lock, flags); + msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_D0], "TCXO D0"); + msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_D1], "TCXO D1"); + msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_A0], "TCXO A0"); + msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_A1], "TCXO A1"); + msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_TCXO_A2], "TCXO A2"); + msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_CORE], "TCXO Core"); + msm_xo_dump_xo(m, &msm_xo_sources[MSM_XO_PXO], "PXO during sleep"); + spin_unlock_irqrestore(&msm_xo_lock, flags); + + return 0; +} + +static int msm_xo_voters_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_xo_show_voters, inode->i_private); +} + +static const struct file_operations msm_xo_voters_ops = { + .open = msm_xo_voters_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __init msm_xo_debugfs_init(void) +{ + struct dentry *entry; + + entry = debugfs_create_file("xo_voters", S_IRUGO, NULL, NULL, + &msm_xo_voters_ops); + return IS_ERR(entry) ? PTR_ERR(entry) : 0; +} +late_initcall(msm_xo_debugfs_init); +#endif + +static int msm_xo_update_vote(struct msm_xo *xo) +{ + int ret; + unsigned vote, prev_vote = xo->mode; + struct msm_rpm_iv_pair cmd; + + if (xo->votes[MSM_XO_MODE_ON]) + vote = MSM_XO_MODE_ON; + else if (xo->votes[MSM_XO_MODE_PIN_CTRL]) + vote = MSM_XO_MODE_PIN_CTRL; + else + vote = MSM_XO_MODE_OFF; + + if (vote == prev_vote) + return 0; + + /* + * Change the vote here to simplify the TCXO logic. If the RPM + * command fails we'll rollback. + */ + xo->mode = vote; + + if (xo == &msm_xo_sources[MSM_XO_PXO]) { + cmd.id = MSM_RPM_ID_PXO_CLK; + cmd.value = msm_xo_sources[MSM_XO_PXO].mode ? 1 : 0; + ret = msm_rpmrs_set_noirq(MSM_RPM_CTX_SET_SLEEP, &cmd, 1); + } else { + cmd.id = MSM_RPM_ID_CXO_BUFFERS; + cmd.value = (msm_xo_sources[MSM_XO_TCXO_D0].mode << 0) | + (msm_xo_sources[MSM_XO_TCXO_D1].mode << 8) | + (msm_xo_sources[MSM_XO_TCXO_A0].mode << 16) | + (msm_xo_sources[MSM_XO_TCXO_A1].mode << 24) | + (msm_xo_sources[MSM_XO_TCXO_A2].mode << 28) | + ((msm_xo_sources[MSM_XO_CORE].mode ? 1 : 0) << 20); + ret = msm_rpm_set_noirq(MSM_RPM_CTX_SET_0, &cmd, 1); + } + + if (ret) + xo->mode = prev_vote; + + return ret; +} + +static int __msm_xo_mode_vote(struct msm_xo_voter *xo_voter, unsigned mode) +{ + int ret; + struct msm_xo *xo = xo_voter->xo; + + if (xo_voter->mode == mode) + return 0; + + xo->votes[mode]++; + xo->votes[xo_voter->mode]--; + ret = msm_xo_update_vote(xo); + if (ret) { + xo->votes[xo_voter->mode]++; + xo->votes[mode]--; + goto out; + } + xo_voter->mode = mode; +out: + return ret; +} + +/** + * msm_xo_mode_vote() - Vote for an XO to be ON, OFF, or under PIN_CTRL + * @xo_voter - Valid handle returned from msm_xo_get() + * @mode - Mode to vote for (ON, OFF, PIN_CTRL) + * + * Vote for an XO to be either ON, OFF, or under PIN_CTRL. Votes are + * aggregated with ON taking precedence over PIN_CTRL taking precedence + * over OFF. + * + * This function returns 0 on success or a negative error code on failure. + */ +int msm_xo_mode_vote(struct msm_xo_voter *xo_voter, enum msm_xo_modes mode) +{ + int ret; + unsigned long flags; + + if (mode >= NUM_MSM_XO_MODES) + return -EINVAL; + + spin_lock_irqsave(&msm_xo_lock, flags); + ret = __msm_xo_mode_vote(xo_voter, mode); + spin_unlock_irqrestore(&msm_xo_lock, flags); + + return ret; +} +EXPORT_SYMBOL(msm_xo_mode_vote); + +/** + * msm_xo_get() - Get a voting handle for an XO + * @xo_id - XO identifier + * @voter - Debug string to identify users + * + * XO voters vote for OFF by default. This function returns a pointer + * indicating success. An ERR_PTR is returned on failure. + * + * If XO voting is disabled, %NULL is returned. + */ +struct msm_xo_voter *msm_xo_get(enum msm_xo_ids xo_id, const char *voter) +{ + int ret; + unsigned long flags; + struct msm_xo_voter *xo_voter; + + if (xo_id >= NUM_MSM_XO_IDS) { + ret = -EINVAL; + goto err; + } + + xo_voter = kzalloc(sizeof(*xo_voter), GFP_KERNEL); + if (!xo_voter) { + ret = -ENOMEM; + goto err; + } + + xo_voter->name = kstrdup(voter, GFP_KERNEL); + if (!xo_voter->name) { + ret = -ENOMEM; + goto err_name; + } + + xo_voter->xo = &msm_xo_sources[xo_id]; + + /* Voters vote for OFF by default */ + spin_lock_irqsave(&msm_xo_lock, flags); + xo_voter->xo->votes[MSM_XO_MODE_OFF]++; + list_add(&xo_voter->list, &xo_voter->xo->voters); + spin_unlock_irqrestore(&msm_xo_lock, flags); + + return xo_voter; + +err_name: + kfree(xo_voter); +err: + return ERR_PTR(ret); +} +EXPORT_SYMBOL(msm_xo_get); + +/** + * msm_xo_put() - Release a voting handle + * @xo_voter - Valid handle returned from msm_xo_get() + * + * Release a reference to an XO voting handle. This also removes the voter's + * vote, therefore calling msm_xo_mode_vote(xo_voter, MSM_XO_MODE_OFF) + * beforehand is unnecessary. + */ +void msm_xo_put(struct msm_xo_voter *xo_voter) +{ + unsigned long flags; + + spin_lock_irqsave(&msm_xo_lock, flags); + __msm_xo_mode_vote(xo_voter, MSM_XO_MODE_OFF); + xo_voter->xo->votes[MSM_XO_MODE_OFF]--; + list_del(&xo_voter->list); + spin_unlock_irqrestore(&msm_xo_lock, flags); + + kfree(xo_voter->name); + kfree(xo_voter); +} +EXPORT_SYMBOL(msm_xo_put); + +int __init msm_xo_init(void) +{ + int i; + int ret; + struct msm_rpm_iv_pair cmd[2]; + + for (i = 0; i < ARRAY_SIZE(msm_xo_sources); i++) + INIT_LIST_HEAD(&msm_xo_sources[i].voters); + + cmd[0].id = MSM_RPM_ID_PXO_CLK; + cmd[0].value = 1; + cmd[1].id = MSM_RPM_ID_CXO_BUFFERS; + cmd[1].value = 0; + ret = msm_rpmrs_set(MSM_RPM_CTX_SET_0, cmd, 2); + if (ret) + goto out; + + cmd[0].id = MSM_RPM_ID_PXO_CLK; + cmd[0].value = 0; + ret = msm_rpmrs_set(MSM_RPM_CTX_SET_SLEEP, cmd, 1); + if (ret) + goto out; +out: + return ret; +} diff --git a/arch/arm/mach-msm/nand_partitions.c b/arch/arm/mach-msm/nand_partitions.c new file mode 100644 index 00000000000..d448aed5cfa --- /dev/null +++ b/arch/arm/mach-msm/nand_partitions.c @@ -0,0 +1,187 @@ +/* arch/arm/mach-msm/nand_partitions.c + * + * Code to extract partition information from ATAG set up by the + * bootloader. + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +#include +#include "smd_private.h" + +/* configuration tags specific to msm */ + +#define ATAG_MSM_PARTITION 0x4d534D70 /* MSMp */ + +struct msm_ptbl_entry { + char name[16]; + __u32 offset; + __u32 size; + __u32 flags; +}; + +#define MSM_MAX_PARTITIONS 8 + +static struct mtd_partition msm_nand_partitions[MSM_MAX_PARTITIONS]; +static char msm_nand_names[MSM_MAX_PARTITIONS * 16]; + +extern struct flash_platform_data msm_nand_data; + +static int __init parse_tag_msm_partition(const struct tag *tag) +{ + struct mtd_partition *ptn = msm_nand_partitions; + char *name = msm_nand_names; + struct msm_ptbl_entry *entry = (void *) &tag->u; + unsigned count, n; + + count = (tag->hdr.size - 2) / + (sizeof(struct msm_ptbl_entry) / sizeof(__u32)); + + if (count > MSM_MAX_PARTITIONS) + count = MSM_MAX_PARTITIONS; + + for (n = 0; n < count; n++) { + memcpy(name, entry->name, 15); + name[15] = 0; + + ptn->name = name; + ptn->offset = entry->offset; + ptn->size = entry->size; + + printk(KERN_INFO "Partition (from atag) %s " + "-- Offset:%llx Size:%llx\n", + ptn->name, ptn->offset, ptn->size); + + name += 16; + entry++; + ptn++; + } + + msm_nand_data.nr_parts = count; + msm_nand_data.parts = msm_nand_partitions; + + return 0; +} + +__tagtable(ATAG_MSM_PARTITION, parse_tag_msm_partition); + +#define FLASH_PART_MAGIC1 0x55EE73AA +#define FLASH_PART_MAGIC2 0xE35EBDDB +#define FLASH_PARTITION_VERSION 0x3 + +#define LINUX_FS_PARTITION_NAME "0:EFS2APPS" + +struct flash_partition_entry { + char name[16]; + u32 offset; /* Offset in blocks from beginning of device */ + u32 length; /* Length of the partition in blocks */ + u8 attrib1; + u8 attrib2; + u8 attrib3; + u8 which_flash; /* Numeric ID (first = 0, second = 1) */ +}; +struct flash_partition_table { + u32 magic1; + u32 magic2; + u32 version; + u32 numparts; + struct flash_partition_entry part_entry[16]; +}; + +static int get_nand_partitions(void) +{ + struct flash_partition_table *partition_table; + struct flash_partition_entry *part_entry; + struct mtd_partition *ptn = msm_nand_partitions; + char *name = msm_nand_names; + int part; + + if (msm_nand_data.nr_parts) + return 0; + + partition_table = (struct flash_partition_table *) + smem_alloc(SMEM_AARM_PARTITION_TABLE, + sizeof(struct flash_partition_table)); + + if (!partition_table) { + printk(KERN_WARNING "%s: no flash partition table in shared " + "memory\n", __func__); + return -ENOENT; + } + + if ((partition_table->magic1 != (u32) FLASH_PART_MAGIC1) || + (partition_table->magic2 != (u32) FLASH_PART_MAGIC2) || + (partition_table->version != (u32) FLASH_PARTITION_VERSION)) { + printk(KERN_WARNING "%s: version mismatch -- magic1=%#x, " + "magic2=%#x, version=%#x\n", __func__, + partition_table->magic1, + partition_table->magic2, + partition_table->version); + return -EFAULT; + } + + msm_nand_data.nr_parts = 0; + + /* Get the LINUX FS partition info */ + for (part = 0; part < partition_table->numparts; part++) { + part_entry = &partition_table->part_entry[part]; + + /* Find a match for the Linux file system partition */ + if (strcmp(part_entry->name, LINUX_FS_PARTITION_NAME) == 0) { + strcpy(name, part_entry->name); + ptn->name = name; + + /*TODO: Get block count and size info */ + ptn->offset = part_entry->offset; + + /* For SMEM, -1 indicates remaining space in flash, + * but for MTD it is 0 + */ + if (part_entry->length == (u32)-1) + ptn->size = 0; + else + ptn->size = part_entry->length; + + msm_nand_data.nr_parts = 1; + msm_nand_data.parts = msm_nand_partitions; + + printk(KERN_INFO "Partition(from smem) %s " + "-- Offset:%llx Size:%llx\n", + ptn->name, ptn->offset, ptn->size); + + return 0; + } + } + + printk(KERN_WARNING "%s: no partition table found!", __func__); + + return -ENODEV; +} + +device_initcall(get_nand_partitions); diff --git a/arch/arm/mach-msm/no-pm.c b/arch/arm/mach-msm/no-pm.c new file mode 100644 index 00000000000..e34abb10dab --- /dev/null +++ b/arch/arm/mach-msm/no-pm.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include "cpuidle.h" +#include "idle.h" +#include "pm.h" + +void arch_idle(void) +{ } + +void msm_pm_set_platform_data(struct msm_pm_platform_data *data, int count) +{ } + +void msm_pm_set_max_sleep_time(int64_t max_sleep_time_ns) { } +EXPORT_SYMBOL(msm_pm_set_max_sleep_time); + +int platform_cpu_disable(unsigned int cpu) +{ + return -ENOSYS; +} + +int platform_cpu_kill(unsigned int cpu) +{ + return -ENOSYS; +} + +void platform_cpu_die(unsigned int cpu) +{ } diff --git a/arch/arm/mach-msm/nohlt.c b/arch/arm/mach-msm/nohlt.c new file mode 100644 index 00000000000..532d57d6eb7 --- /dev/null +++ b/arch/arm/mach-msm/nohlt.c @@ -0,0 +1,40 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * MSM architecture driver to control arm halt behavior + */ + +#include +#include +#include +#include + +static int set_nohalt(void *data, u64 val) +{ + if (val) + disable_hlt(); + else + enable_hlt(); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(nohalt_ops, NULL, set_nohalt, "%llu\n"); + +static int __init init_hlt_debug(void) +{ + debugfs_create_file("nohlt", 0200, NULL, NULL, &nohalt_ops); + + return 0; +} + +late_initcall(init_hlt_debug); diff --git a/arch/arm/mach-msm/oem_rapi_client.c b/arch/arm/mach-msm/oem_rapi_client.c new file mode 100644 index 00000000000..f2b93cefc33 --- /dev/null +++ b/arch/arm/mach-msm/oem_rapi_client.c @@ -0,0 +1,355 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * OEM RAPI CLIENT Driver source file + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OEM_RAPI_PROG 0x3000006B +#define OEM_RAPI_VERS 0x00010001 + +#define OEM_RAPI_NULL_PROC 0 +#define OEM_RAPI_RPC_GLUE_CODE_INFO_REMOTE_PROC 1 +#define OEM_RAPI_STREAMING_FUNCTION_PROC 2 + +#define OEM_RAPI_CLIENT_MAX_OUT_BUFF_SIZE 128 + +static struct msm_rpc_client *rpc_client; +static uint32_t open_count; +static DEFINE_MUTEX(oem_rapi_client_lock); + +/* TODO: check where to allocate memory for return */ +static int oem_rapi_client_cb(struct msm_rpc_client *client, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + uint32_t cb_id, accept_status; + int rc; + void *cb_func; + uint32_t temp; + + struct oem_rapi_client_streaming_func_cb_arg arg; + struct oem_rapi_client_streaming_func_cb_ret ret; + + arg.input = NULL; + ret.out_len = NULL; + ret.output = NULL; + + xdr_recv_uint32(xdr, &cb_id); /* cb_id */ + xdr_recv_uint32(xdr, &arg.event); /* enum */ + xdr_recv_uint32(xdr, (uint32_t *)(&arg.handle)); /* handle */ + xdr_recv_uint32(xdr, &arg.in_len); /* in_len */ + xdr_recv_bytes(xdr, (void **)&arg.input, &temp); /* input */ + xdr_recv_uint32(xdr, &arg.out_len_valid); /* out_len */ + if (arg.out_len_valid) { + ret.out_len = kmalloc(sizeof(*ret.out_len), GFP_KERNEL); + if (!ret.out_len) { + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + goto oem_rapi_send_ack; + } + } + + xdr_recv_uint32(xdr, &arg.output_valid); /* out */ + if (arg.output_valid) { + xdr_recv_uint32(xdr, &arg.output_size); /* ouput_size */ + + ret.output = kmalloc(arg.output_size, GFP_KERNEL); + if (!ret.output) { + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + goto oem_rapi_send_ack; + } + } + + cb_func = msm_rpc_get_cb_func(client, cb_id); + if (cb_func) { + rc = ((int (*)(struct oem_rapi_client_streaming_func_cb_arg *, + struct oem_rapi_client_streaming_func_cb_ret *)) + cb_func)(&arg, &ret); + if (rc) + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + else + accept_status = RPC_ACCEPTSTAT_SUCCESS; + } else + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + + oem_rapi_send_ack: + xdr_start_accepted_reply(xdr, accept_status); + + if (accept_status == RPC_ACCEPTSTAT_SUCCESS) { + uint32_t temp = sizeof(uint32_t); + xdr_send_pointer(xdr, (void **)&(ret.out_len), temp, + xdr_send_uint32); + + /* output */ + if (ret.output && ret.out_len) + xdr_send_bytes(xdr, (const void **)&ret.output, + ret.out_len); + else { + temp = 0; + xdr_send_uint32(xdr, &temp); + } + } + rc = xdr_send_msg(xdr); + if (rc) + pr_err("%s: sending reply failed: %d\n", __func__, rc); + + kfree(arg.input); + kfree(ret.out_len); + kfree(ret.output); + + return 0; +} + +static int oem_rapi_client_streaming_function_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, + void *data) +{ + int cb_id; + struct oem_rapi_client_streaming_func_arg *arg = data; + + cb_id = msm_rpc_add_cb_func(client, (void *)arg->cb_func); + if ((cb_id < 0) && (cb_id != MSM_RPC_CLIENT_NULL_CB_ID)) + return cb_id; + + xdr_send_uint32(xdr, &arg->event); /* enum */ + xdr_send_uint32(xdr, &cb_id); /* cb_id */ + xdr_send_uint32(xdr, (uint32_t *)(&arg->handle)); /* handle */ + xdr_send_uint32(xdr, &arg->in_len); /* in_len */ + xdr_send_bytes(xdr, (const void **)&arg->input, + &arg->in_len); /* input */ + xdr_send_uint32(xdr, &arg->out_len_valid); /* out_len */ + xdr_send_uint32(xdr, &arg->output_valid); /* output */ + + /* output_size */ + if (arg->output_valid) + xdr_send_uint32(xdr, &arg->output_size); + + return 0; +} + +static int oem_rapi_client_streaming_function_ret(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, + void *data) +{ + struct oem_rapi_client_streaming_func_ret *ret = data; + uint32_t temp; + + /* out_len */ + xdr_recv_pointer(xdr, (void **)&(ret->out_len), sizeof(uint32_t), + xdr_recv_uint32); + + /* output */ + if (ret->out_len && *ret->out_len) + xdr_recv_bytes(xdr, (void **)&ret->output, &temp); + + return 0; +} + +int oem_rapi_client_streaming_function( + struct msm_rpc_client *client, + struct oem_rapi_client_streaming_func_arg *arg, + struct oem_rapi_client_streaming_func_ret *ret) +{ + return msm_rpc_client_req2(client, + OEM_RAPI_STREAMING_FUNCTION_PROC, + oem_rapi_client_streaming_function_arg, arg, + oem_rapi_client_streaming_function_ret, + ret, -1); +} +EXPORT_SYMBOL(oem_rapi_client_streaming_function); + +int oem_rapi_client_close(void) +{ + mutex_lock(&oem_rapi_client_lock); + if (--open_count == 0) { + msm_rpc_unregister_client(rpc_client); + pr_info("%s: disconnected from remote oem rapi server\n", + __func__); + } + mutex_unlock(&oem_rapi_client_lock); + return 0; +} +EXPORT_SYMBOL(oem_rapi_client_close); + +struct msm_rpc_client *oem_rapi_client_init(void) +{ + mutex_lock(&oem_rapi_client_lock); + if (open_count == 0) { + rpc_client = msm_rpc_register_client2("oemrapiclient", + OEM_RAPI_PROG, + OEM_RAPI_VERS, 0, + oem_rapi_client_cb); + if (!IS_ERR(rpc_client)) + open_count++; + } + mutex_unlock(&oem_rapi_client_lock); + return rpc_client; +} +EXPORT_SYMBOL(oem_rapi_client_init); + +#if defined(CONFIG_DEBUG_FS) + +static struct dentry *dent; +static int oem_rapi_client_test_res; + +static int oem_rapi_client_null(struct msm_rpc_client *client, + void *arg, void *ret) +{ + return msm_rpc_client_req2(client, OEM_RAPI_NULL_PROC, + NULL, NULL, NULL, NULL, -1); +} + +static int oem_rapi_client_test_streaming_cb_func( + struct oem_rapi_client_streaming_func_cb_arg *arg, + struct oem_rapi_client_streaming_func_cb_ret *ret) +{ + uint32_t size; + + size = (arg->in_len < OEM_RAPI_CLIENT_MAX_OUT_BUFF_SIZE) ? + arg->in_len : OEM_RAPI_CLIENT_MAX_OUT_BUFF_SIZE; + + if (ret->out_len != 0) + *ret->out_len = size; + + if (ret->output != 0) + memcpy(ret->output, arg->input, size); + + return 0; +} + +static ssize_t debug_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + char _buf[16]; + + snprintf(_buf, sizeof(_buf), "%i\n", oem_rapi_client_test_res); + + return simple_read_from_buffer(buf, count, pos, _buf, strlen(_buf)); +} + +static ssize_t debug_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + char input[OEM_RAPI_CLIENT_MAX_OUT_BUFF_SIZE]; + struct oem_rapi_client_streaming_func_arg arg; + struct oem_rapi_client_streaming_func_ret ret; + + unsigned char cmd[64]; + int len; + + if (count < 1) + return 0; + + len = count > 63 ? 63 : count; + + if (copy_from_user(cmd, buf, len)) + return -EFAULT; + + cmd[len] = 0; + + if (cmd[len-1] == '\n') { + cmd[len-1] = 0; + len--; + } + + if (!strncmp(cmd, "null", 64)) { + oem_rapi_client_test_res = oem_rapi_client_null(rpc_client, + NULL, NULL); + } else if (!strncmp(cmd, "streaming_func", 64)) { + memset(input, 5, 16); + arg.event = 0; + arg.cb_func = oem_rapi_client_test_streaming_cb_func; + arg.handle = (void *)20; + arg.in_len = 16; + arg.input = input; + arg.out_len_valid = 1; + arg.output_valid = 1; + arg.output_size = OEM_RAPI_CLIENT_MAX_OUT_BUFF_SIZE; + ret.out_len = NULL; + ret.output = NULL; + + oem_rapi_client_test_res = oem_rapi_client_streaming_function( + rpc_client, &arg, &ret); + + kfree(ret.out_len); + kfree(ret.output); + + } else + oem_rapi_client_test_res = -EINVAL; + + if (oem_rapi_client_test_res) + pr_err("oem rapi client test fail %d\n", + oem_rapi_client_test_res); + else + pr_info("oem rapi client test passed\n"); + + return count; +} + +static int debug_release(struct inode *ip, struct file *fp) +{ + return oem_rapi_client_close(); +} + +static int debug_open(struct inode *ip, struct file *fp) +{ + struct msm_rpc_client *client; + client = oem_rapi_client_init(); + if (IS_ERR(client)) { + pr_err("%s: couldn't open oem rapi client\n", __func__); + return PTR_ERR(client); + } else + pr_info("%s: connected to remote oem rapi server\n", __func__); + + return 0; +} + +static const struct file_operations debug_ops = { + .owner = THIS_MODULE, + .open = debug_open, + .release = debug_release, + .read = debug_read, + .write = debug_write, +}; + +static void __exit oem_rapi_client_mod_exit(void) +{ + debugfs_remove(dent); +} + +static int __init oem_rapi_client_mod_init(void) +{ + dent = debugfs_create_file("oem_rapi", 0444, 0, NULL, &debug_ops); + open_count = 0; + oem_rapi_client_test_res = -1; + return 0; +} + +module_init(oem_rapi_client_mod_init); +module_exit(oem_rapi_client_mod_exit); + +#endif + +MODULE_DESCRIPTION("OEM RAPI CLIENT Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/peripheral-loader.c b/arch/arm/mach-msm/peripheral-loader.c new file mode 100644 index 00000000000..af6f5966076 --- /dev/null +++ b/arch/arm/mach-msm/peripheral-loader.c @@ -0,0 +1,439 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "peripheral-loader.h" + +static DEFINE_MUTEX(pil_list_lock); +static LIST_HEAD(pil_list); + +static struct pil_device *__find_peripheral(const char *str) +{ + struct pil_device *dev; + + list_for_each_entry(dev, &pil_list, list) + if (!strcmp(dev->name, str)) + return dev; + return NULL; +} + +static struct pil_device *find_peripheral(const char *str) +{ + struct pil_device *dev; + + if (!str) + return NULL; + + mutex_lock(&pil_list_lock); + dev = __find_peripheral(str); + mutex_unlock(&pil_list_lock); + + return dev; +} + +#define IOMAP_SIZE SZ_4M + +static int load_segment(const struct elf32_phdr *phdr, unsigned num, + struct pil_device *pil) +{ + int ret, count, paddr; + char fw_name[30]; + const struct firmware *fw = NULL; + const u8 *data; + + if (memblock_is_region_memory(phdr->p_paddr, phdr->p_memsz)) { + dev_err(&pil->pdev.dev, "Kernel memory would be overwritten"); + return -EPERM; + } + + if (phdr->p_filesz) { + snprintf(fw_name, ARRAY_SIZE(fw_name), "%s.b%02d", pil->name, + num); + ret = request_firmware(&fw, fw_name, &pil->pdev.dev); + if (ret) { + dev_err(&pil->pdev.dev, "Failed to locate blob %s\n", + fw_name); + return ret; + } + + if (fw->size != phdr->p_filesz) { + dev_err(&pil->pdev.dev, + "Blob size %u doesn't match %u\n", + fw->size, phdr->p_filesz); + ret = -EPERM; + goto release_fw; + } + } + + /* Load the segment into memory */ + count = phdr->p_filesz; + paddr = phdr->p_paddr; + data = fw ? fw->data : NULL; + while (count > 0) { + int size; + u8 __iomem *buf; + + size = min_t(size_t, IOMAP_SIZE, count); + buf = ioremap(paddr, size); + if (!buf) { + dev_err(&pil->pdev.dev, "Failed to map memory\n"); + ret = -ENOMEM; + goto release_fw; + } + memcpy(buf, data, size); + iounmap(buf); + + count -= size; + paddr += size; + data += size; + } + + /* Zero out trailing memory */ + count = phdr->p_memsz - phdr->p_filesz; + while (count > 0) { + int size; + u8 __iomem *buf; + + size = min_t(size_t, IOMAP_SIZE, count); + buf = ioremap(paddr, size); + if (!buf) { + dev_err(&pil->pdev.dev, "Failed to map memory\n"); + ret = -ENOMEM; + goto release_fw; + } + memset(buf, 0, size); + iounmap(buf); + + count -= size; + paddr += size; + } + + ret = pil->ops->verify_blob(phdr->p_paddr, phdr->p_memsz); + if (ret) + dev_err(&pil->pdev.dev, "Blob %u failed verification\n", num); + +release_fw: + release_firmware(fw); + return ret; +} + +#define segment_is_hash(flag) (((flag) & (0x7 << 24)) == (0x2 << 24)) + +static int segment_is_loadable(const struct elf32_phdr *p) +{ + return (p->p_type & PT_LOAD) && !segment_is_hash(p->p_flags); +} + +static int load_image(struct pil_device *pil) +{ + int i, ret; + char fw_name[30]; + struct elf32_hdr *ehdr; + const struct elf32_phdr *phdr; + const struct firmware *fw; + + snprintf(fw_name, sizeof(fw_name), "%s.mdt", pil->name); + ret = request_firmware(&fw, fw_name, &pil->pdev.dev); + if (ret) { + dev_err(&pil->pdev.dev, "Failed to locate %s\n", fw_name); + goto out; + } + + if (fw->size < sizeof(*ehdr)) { + dev_err(&pil->pdev.dev, "Not big enough to be an elf header\n"); + ret = -EIO; + goto release_fw; + } + + ehdr = (struct elf32_hdr *)fw->data; + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) { + dev_err(&pil->pdev.dev, "Not an elf header\n"); + ret = -EIO; + goto release_fw; + } + + if (ehdr->e_phnum == 0) { + dev_err(&pil->pdev.dev, "No loadable segments\n"); + ret = -EIO; + goto release_fw; + } + if (ehdr->e_phoff > fw->size) { + dev_err(&pil->pdev.dev, "Program header beyond size of mdt\n"); + ret = -EIO; + goto release_fw; + } + + ret = pil->ops->init_image(fw->data, fw->size); + if (ret) { + dev_err(&pil->pdev.dev, "Invalid firmware metadata\n"); + goto release_fw; + } + + phdr = (const struct elf32_phdr *)(fw->data + ehdr->e_phoff); + for (i = 0; i < ehdr->e_phnum; i++, phdr++) { + if (!segment_is_loadable(phdr)) + continue; + + ret = load_segment(phdr, i, pil); + if (ret) { + dev_err(&pil->pdev.dev, "Failed to load segment %d\n", + i); + goto release_fw; + } + } + + ret = pil->ops->auth_and_reset(); + if (ret) { + dev_err(&pil->pdev.dev, "Failed to bring out of reset\n"); + goto release_fw; + } + +release_fw: + release_firmware(fw); +out: + return ret; +} + +/** + * pil_get() - Load a peripheral into memory and take it out of reset + * @name: pointer to a string containing the name of the peripheral to load + * + * This function returns a pointer if it succeeds. If an error occurs an + * ERR_PTR is returned. + * + * If PIL is not enabled in the kernel, the value %NULL will be returned. + */ +void *pil_get(const char *name) +{ + int ret; + struct pil_device *pil; + struct pil_device *pil_d; + void *retval; + + pil = retval = find_peripheral(name); + if (!pil) + return ERR_PTR(-ENODEV); + + pil_d = find_peripheral(pil->depends_on); + if (pil_d) { + void *p = pil_get(pil_d->name); + if (IS_ERR(p)) + return p; + } + + mutex_lock(&pil->lock); + if (pil->count) { + pil->count++; + goto unlock; + } + + ret = load_image(pil); + if (ret) { + retval = ERR_PTR(ret); + goto unlock; + } + + pil->count++; +unlock: + mutex_unlock(&pil->lock); + return retval; +} +EXPORT_SYMBOL(pil_get); + +/** + * pil_put() - Inform PIL the peripheral no longer needs to be active + * @peripheral_handle: pointer from a previous call to pil_get() + * + * This doesn't imply that a peripheral is shutdown or in reset since another + * driver could be using the peripheral. + */ +void pil_put(void *peripheral_handle) +{ + struct pil_device *pil_d; + struct pil_device *pil = peripheral_handle; + if (!pil || IS_ERR(pil)) { + WARN(1, "Invalid peripheral handle\n"); + return; + } + + mutex_lock(&pil->lock); + WARN(!pil->count, "%s: Reference count mismatch\n", __func__); + /* TODO: Peripheral shutdown support */ + if (pil->count == 1) + goto unlock; + if (pil->count) + pil->count--; + if (pil->count == 0) + pil->ops->shutdown(); +unlock: + mutex_unlock(&pil->lock); + + pil_d = find_peripheral(pil->depends_on); + if (pil_d) + pil_put(pil_d); +} +EXPORT_SYMBOL(pil_put); + +void pil_force_shutdown(const char *name) +{ + struct pil_device *pil; + + pil = find_peripheral(name); + if (!pil) + return; + + mutex_lock(&pil->lock); + if (!WARN(!pil->count, "%s: Reference count mismatch\n", __func__)) + pil->ops->shutdown(); + mutex_unlock(&pil->lock); +} +EXPORT_SYMBOL(pil_force_shutdown); + +int pil_force_boot(const char *name) +{ + int ret = -EINVAL; + struct pil_device *pil; + + pil = find_peripheral(name); + if (!pil) + return -EINVAL; + + mutex_lock(&pil->lock); + if (!WARN(!pil->count, "%s: Reference count mismatch\n", __func__)) + ret = load_image(pil); + mutex_unlock(&pil->lock); + + return ret; +} +EXPORT_SYMBOL(pil_force_boot); + +#ifdef CONFIG_DEBUG_FS +int msm_pil_debugfs_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static ssize_t msm_pil_debugfs_read(struct file *filp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + int r; + char buf[40]; + struct pil_device *pil = filp->private_data; + + mutex_lock(&pil->lock); + r = snprintf(buf, sizeof(buf), "%d\n", pil->count); + mutex_unlock(&pil->lock); + return simple_read_from_buffer(ubuf, cnt, ppos, buf, r); +} + +static ssize_t msm_pil_debugfs_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + struct pil_device *pil = filp->private_data; + char buf[4]; + + if (cnt > sizeof(buf)) + return -EINVAL; + + if (copy_from_user(&buf, ubuf, cnt)) + return -EFAULT; + + if (!strncmp(buf, "get", 3)) { + if (IS_ERR(pil_get(pil->name))) + return -EIO; + } else if (!strncmp(buf, "put", 3)) + pil_put(pil); + else + return -EINVAL; + + return cnt; +} + +static const struct file_operations msm_pil_debugfs_fops = { + .open = msm_pil_debugfs_open, + .read = msm_pil_debugfs_read, + .write = msm_pil_debugfs_write, +}; + +static struct dentry *pil_base_dir; + +static int msm_pil_debugfs_init(void) +{ + pil_base_dir = debugfs_create_dir("pil", NULL); + if (!pil_base_dir) { + pil_base_dir = NULL; + return -ENOMEM; + } + + return 0; +} +arch_initcall(msm_pil_debugfs_init); + +static int msm_pil_debugfs_add(struct pil_device *pil) +{ + if (!pil_base_dir) + return -ENOMEM; + + if (!debugfs_create_file(pil->name, S_IRUGO | S_IWUSR, pil_base_dir, + pil, &msm_pil_debugfs_fops)) + return -ENOMEM; + return 0; +} +#else +static int msm_pil_debugfs_add(struct pil_device *pil) { return 0; } +#endif + +static int msm_pil_shutdown_at_boot(void) +{ + struct pil_device *pil; + + mutex_lock(&pil_list_lock); + list_for_each_entry(pil, &pil_list, list) + pil->ops->shutdown(); + mutex_unlock(&pil_list_lock); + + return 0; +} +late_initcall(msm_pil_shutdown_at_boot); + +int msm_pil_add_device(struct pil_device *pil) +{ + int ret; + ret = platform_device_register(&pil->pdev); + if (ret) + return ret; + + mutex_init(&pil->lock); + + mutex_lock(&pil_list_lock); + list_add(&pil->list, &pil_list); + mutex_unlock(&pil_list_lock); + + msm_pil_debugfs_add(pil); + return 0; +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Load peripheral images and bring peripherals out of reset"); diff --git a/arch/arm/mach-msm/peripheral-loader.h b/arch/arm/mach-msm/peripheral-loader.h new file mode 100644 index 00000000000..e0dc3a69af0 --- /dev/null +++ b/arch/arm/mach-msm/peripheral-loader.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __MACH_PERIPHERAL_LOADER_H +#define __MACH_PERIPHERAL_LOADER_H + +#include +#include +#include + +struct pil_reset_ops { + int (*init_image)(const u8 *metadata, size_t size); + int (*verify_blob)(u32 phy_addr, size_t size); + int (*auth_and_reset)(void); + int (*shutdown)(void); +}; + +struct pil_device { + const char *name; + const char *depends_on; + int count; + struct mutex lock; + struct platform_device pdev; + struct list_head list; + struct pil_reset_ops *ops; +}; + +extern int msm_pil_add_device(struct pil_device *pil); + +#endif diff --git a/arch/arm/mach-msm/peripheral-reset-8960.c b/arch/arm/mach-msm/peripheral-reset-8960.c new file mode 100644 index 00000000000..19fe1c5e09d --- /dev/null +++ b/arch/arm/mach-msm/peripheral-reset-8960.c @@ -0,0 +1,653 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "peripheral-loader.h" + +#define MSM_FW_QDSP6SS_PHYS 0x08800000 +#define MSM_SW_QDSP6SS_PHYS 0x08900000 +#define MSM_LPASS_QDSP6SS_PHYS 0x28800000 +#define MSM_MSS_ENABLE_PHYS 0x08B00000 + +#define QDSP6SS_RST_EVB 0x0 +#define QDSP6SS_RESET 0x04 +#define QDSP6SS_CGC_OVERRIDE 0x18 +#define QDSP6SS_STRAP_TCM 0x1C +#define QDSP6SS_STRAP_AHB 0x20 +#define QDSP6SS_GFMUX_CTL 0x30 +#define QDSP6SS_PWR_CTL 0x38 + +#define MSS_S_HCLK_CTL (MSM_CLK_CTL_BASE + 0x2C70) +#define MSS_SLP_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C60) +#define SFAB_MSS_M_ACLK_CTL (MSM_CLK_CTL_BASE + 0x2340) +#define SFAB_MSS_S_HCLK_CTL (MSM_CLK_CTL_BASE + 0x2C00) +#define SFAB_MSS_Q6_FW_ACLK_CTL (MSM_CLK_CTL_BASE + 0x2044) +#define SFAB_MSS_Q6_SW_ACLK_CTL (MSM_CLK_CTL_BASE + 0x2040) +#define SFAB_LPASS_Q6_ACLK_CTL (MSM_CLK_CTL_BASE + 0x23A0) +#define MSS_Q6FW_JTAG_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C6C) +#define MSS_Q6SW_JTAG_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C68) +#define MSS_RESET (MSM_CLK_CTL_BASE + 0x2C64) + +#define Q6SS_SS_ARES BIT(0) +#define Q6SS_CORE_ARES BIT(1) +#define Q6SS_ISDB_ARES BIT(2) +#define Q6SS_ETM_ARES BIT(3) +#define Q6SS_STOP_CORE_ARES BIT(4) +#define Q6SS_PRIV_ARES BIT(5) + +#define Q6SS_L2DATA_SLP_NRET_N BIT(0) +#define Q6SS_SLP_RET_N BIT(1) +#define Q6SS_L1TCM_SLP_NRET_N BIT(2) +#define Q6SS_L2TAG_SLP_NRET_N BIT(3) +#define Q6SS_ETB_SLEEP_NRET_N BIT(4) +#define Q6SS_ARR_STBY_N BIT(5) +#define Q6SS_CLAMP_IO BIT(6) + +#define Q6SS_CLK_ENA BIT(1) +#define Q6SS_SRC_SWITCH_CLK_OVR BIT(8) +#define Q6SS_AXIS_ACLK_EN BIT(9) + +#define MSM_RIVA_PHYS 0x03204000 +#define RIVA_PMU_A2XB_CFG (msm_riva_base + 0xB8) +#define RIVA_PMU_A2XB_CFG_EN BIT(0) + +#define RIVA_PMU_CFG (msm_riva_base + 0x28) +#define RIVA_PMU_CFG_WARM_BOOT BIT(0) +#define RIVA_PMU_CFG_IRIS_XO_MODE 0x6 +#define RIVA_PMU_CFG_IRIS_XO_MODE_48 (3 << 1) + +#define RIVA_PMU_OVRD_VAL (msm_riva_base + 0x30) +#define RIVA_PMU_OVRD_VAL_CCPU_RESET BIT(0) +#define RIVA_PMU_OVRD_VAL_CCPU_CLK BIT(1) + +#define RIVA_PMU_CCPU_CTL (msm_riva_base + 0x9C) +#define RIVA_PMU_CCPU_CTL_HIGH_IVT BIT(0) +#define RIVA_PMU_CCPU_CTL_REMAP_EN BIT(2) + +#define RIVA_PMU_CCPU_BOOT_REMAP_ADDR (msm_riva_base + 0xA0) + +#define RIVA_PLL_MODE (MSM_CLK_CTL_BASE + 0x31A0) +#define PLL_MODE_OUTCTRL BIT(0) +#define PLL_MODE_BYPASSNL BIT(1) +#define PLL_MODE_RESET_N BIT(2) +#define PLL_MODE_REF_XO_SEL 0x30 +#define PLL_MODE_REF_XO_SEL_CXO (2 << 4) +#define PLL_MODE_REF_XO_SEL_RF (3 << 4) +#define RIVA_PLL_L_VAL (MSM_CLK_CTL_BASE + 0x31A4) +#define RIVA_PLL_M_VAL (MSM_CLK_CTL_BASE + 0x31A8) +#define RIVA_PLL_N_VAL (MSM_CLK_CTL_BASE + 0x31Ac) +#define RIVA_PLL_CONFIG (MSM_CLK_CTL_BASE + 0x31B4) +#define RIVA_PLL_STATUS (MSM_CLK_CTL_BASE + 0x31B8) + +#define RIVA_PMU_ROOT_CLK_SEL (msm_riva_base + 0xC8) +#define RIVA_PMU_ROOT_CLK_SEL_3 BIT(2) + +#define RIVA_PMU_CLK_ROOT3 (msm_riva_base + 0x78) +#define RIVA_PMU_CLK_ROOT3_ENA BIT(0) +#define RIVA_PMU_CLK_ROOT3_SRC0_DIV 0x3C +#define RIVA_PMU_CLK_ROOT3_SRC0_DIV_2 (1 << 2) +#define RIVA_PMU_CLK_ROOT3_SRC0_SEL 0x1C0 +#define RIVA_PMU_CLK_ROOT3_SRC0_SEL_RIVA (1 << 6) +#define RIVA_PMU_CLK_ROOT3_SRC1_DIV 0x1E00 +#define RIVA_PMU_CLK_ROOT3_SRC1_DIV_2 (1 << 9) +#define RIVA_PMU_CLK_ROOT3_SRC1_SEL 0xE000 +#define RIVA_PMU_CLK_ROOT3_SRC1_SEL_RIVA (1 << 13) + +#define PPSS_RESET (MSM_CLK_CTL_BASE + 0x2594) +#define PPSS_PROC_CLK_CTL (MSM_CLK_CTL_BASE + 0x2588) +#define PPSS_HCLK_CTL (MSM_CLK_CTL_BASE + 0x2580) + +struct q6_data { + const unsigned strap_tcm_base; + const unsigned strap_ahb_upper; + const unsigned strap_ahb_lower; + void __iomem *reg_base; + void __iomem *aclk_reg; + void __iomem *jtag_clk_reg; + int start_addr; + struct regulator *vreg; + bool vreg_enabled; + const char *name; +}; + +static struct q6_data q6_lpass = { + .strap_tcm_base = (0x146 << 16), + .strap_ahb_upper = (0x029 << 16), + .strap_ahb_lower = (0x028 << 4), + .aclk_reg = SFAB_LPASS_Q6_ACLK_CTL, + .name = "q6_lpass", +}; + +static struct q6_data q6_modem_fw = { + .strap_tcm_base = (0x40 << 16), + .strap_ahb_upper = (0x09 << 16), + .strap_ahb_lower = (0x08 << 4), + .aclk_reg = SFAB_MSS_Q6_FW_ACLK_CTL, + .jtag_clk_reg = MSS_Q6FW_JTAG_CLK_CTL, + .name = "q6_modem_fw", +}; + +static struct q6_data q6_modem_sw = { + .strap_tcm_base = (0x42 << 16), + .strap_ahb_upper = (0x09 << 16), + .strap_ahb_lower = (0x08 << 4), + .aclk_reg = SFAB_MSS_Q6_SW_ACLK_CTL, + .jtag_clk_reg = MSS_Q6SW_JTAG_CLK_CTL, + .name = "q6_modem_sw", +}; + +static void __iomem *mss_enable_reg; +static void __iomem *msm_riva_base; +static unsigned long riva_start; + +static int init_image_lpass_q6_untrusted(const u8 *metadata, size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + q6_lpass.start_addr = ehdr->e_entry; + return 0; +} + +static int init_image_modem_fw_q6_untrusted(const u8 *metadata, size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + q6_modem_fw.start_addr = ehdr->e_entry; + return 0; +} + +static int init_image_modem_sw_q6_untrusted(const u8 *metadata, size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + q6_modem_sw.start_addr = ehdr->e_entry; + return 0; +} + +static int verify_blob(u32 phy_addr, size_t size) +{ + return 0; +} + +static int reset_q6_untrusted(struct q6_data *q6) +{ + u32 reg, err = 0; + + err = regulator_set_voltage(q6->vreg, 1050000, 1050000); + if (err) { + pr_err("Failed to set %s regulator's voltage.\n", q6->name); + goto out; + } + err = regulator_enable(q6->vreg); + if (err) { + pr_err("Failed to enable %s's regulator.\n", q6->name); + goto out; + } + q6->vreg_enabled = true; + + /* Enable Q6 ACLK */ + writel_relaxed(0x10, q6->aclk_reg); + + if (q6 == &q6_modem_fw || q6 == &q6_modem_sw) { + /* Enable MSS clocks */ + writel_relaxed(0x10, SFAB_MSS_M_ACLK_CTL); + writel_relaxed(0x10, SFAB_MSS_S_HCLK_CTL); + writel_relaxed(0x10, MSS_S_HCLK_CTL); + writel_relaxed(0x10, MSS_SLP_CLK_CTL); + /* Wait for clocks to enable */ + mb(); + udelay(10); + + /* Enable JTAG clocks */ + /* TODO: Remove if/when Q6 software enables them? */ + writel_relaxed(0x10, q6->jtag_clk_reg); + + /* De-assert MSS reset */ + writel_relaxed(0x0, MSS_RESET); + mb(); + udelay(10); + + /* Enable MSS */ + writel_relaxed(0x7, mss_enable_reg); + } + + /* + * Assert AXIS_ACLK_EN override to allow for correct updating of the + * QDSP6_CORE_STATE status bit. This is mandatory only for the SW Q6 + * in 8960v1 and optional elsewhere. + */ + reg = readl_relaxed(q6->reg_base + QDSP6SS_CGC_OVERRIDE); + reg |= Q6SS_AXIS_ACLK_EN; + writel_relaxed(reg, q6->reg_base + QDSP6SS_CGC_OVERRIDE); + + /* Deassert Q6SS_SS_ARES */ + reg = readl_relaxed(q6->reg_base + QDSP6SS_RESET); + reg &= ~(Q6SS_SS_ARES); + writel_relaxed(reg, q6->reg_base + QDSP6SS_RESET); + + /* Program boot address */ + writel_relaxed((q6->start_addr >> 8) & 0xFFFFFF, + q6->reg_base + QDSP6SS_RST_EVB); + + /* Program TCM and AHB address ranges */ + writel_relaxed(q6->strap_tcm_base, q6->reg_base + QDSP6SS_STRAP_TCM); + writel_relaxed(q6->strap_ahb_upper | q6->strap_ahb_lower, + q6->reg_base + QDSP6SS_STRAP_AHB); + + /* Turn off Q6 core clock */ + writel_relaxed(Q6SS_SRC_SWITCH_CLK_OVR, + q6->reg_base + QDSP6SS_GFMUX_CTL); + + /* Put memories to sleep */ + writel_relaxed(Q6SS_CLAMP_IO, q6->reg_base + QDSP6SS_PWR_CTL); + + /* Assert resets */ + reg = readl_relaxed(q6->reg_base + QDSP6SS_RESET); + reg |= (Q6SS_CORE_ARES | Q6SS_ISDB_ARES | Q6SS_ETM_ARES + | Q6SS_STOP_CORE_ARES); + writel_relaxed(reg, q6->reg_base + QDSP6SS_RESET); + + /* Wait 8 AHB cycles for Q6 to be fully reset (AHB = 1.5Mhz) */ + mb(); + usleep_range(20, 30); + + /* Turn on Q6 memories */ + reg = Q6SS_L2DATA_SLP_NRET_N | Q6SS_SLP_RET_N | Q6SS_L1TCM_SLP_NRET_N + | Q6SS_L2TAG_SLP_NRET_N | Q6SS_ETB_SLEEP_NRET_N | Q6SS_ARR_STBY_N + | Q6SS_CLAMP_IO; + writel_relaxed(reg, q6->reg_base + QDSP6SS_PWR_CTL); + + /* Turn on Q6 core clock */ + reg = Q6SS_CLK_ENA | Q6SS_SRC_SWITCH_CLK_OVR; + writel_relaxed(reg, q6->reg_base + QDSP6SS_GFMUX_CTL); + + /* Remove Q6SS_CLAMP_IO */ + reg = readl_relaxed(q6->reg_base + QDSP6SS_PWR_CTL); + reg &= ~Q6SS_CLAMP_IO; + writel_relaxed(reg, q6->reg_base + QDSP6SS_PWR_CTL); + + /* Bring Q6 core out of reset and start execution. */ + writel_relaxed(0x0, q6->reg_base + QDSP6SS_RESET); + + /* + * Re-enable auto-gating of AXIS_ACLK at lease one AXI clock cycle + * after resets are de-asserted. + */ + mb(); + usleep_range(1, 10); + reg = readl_relaxed(q6->reg_base + QDSP6SS_CGC_OVERRIDE); + reg &= ~Q6SS_AXIS_ACLK_EN; + writel_relaxed(reg, q6->reg_base + QDSP6SS_CGC_OVERRIDE); + +out: + return err; +} + +static int reset_lpass_q6_untrusted(void) +{ + return reset_q6_untrusted(&q6_lpass); +} + +static int reset_modem_fw_q6_untrusted(void) +{ + return reset_q6_untrusted(&q6_modem_fw); +} + +static int reset_modem_sw_q6_untrusted(void) +{ + return reset_q6_untrusted(&q6_modem_sw); +} + +static int shutdown_q6_untrusted(struct q6_data *q6) +{ + u32 reg; + + /* Turn off Q6 core clock */ + writel_relaxed(Q6SS_SRC_SWITCH_CLK_OVR, + q6->reg_base + QDSP6SS_GFMUX_CTL); + + /* Assert resets */ + reg = (Q6SS_SS_ARES | Q6SS_CORE_ARES | Q6SS_ISDB_ARES + | Q6SS_ETM_ARES | Q6SS_STOP_CORE_ARES | Q6SS_PRIV_ARES); + writel_relaxed(reg, q6->reg_base + QDSP6SS_RESET); + + /* Turn off Q6 memories */ + writel_relaxed(Q6SS_CLAMP_IO, q6->reg_base + QDSP6SS_PWR_CTL); + + /* Put Modem Subsystem back into reset when shutting down FWQ6 */ + if (q6 == &q6_modem_fw) + writel_relaxed(0x1, MSS_RESET); + + if (q6->vreg_enabled) { + regulator_disable(q6->vreg); + q6->vreg_enabled = false; + } + + return 0; +} + +static int shutdown_lpass_q6_untrusted(void) +{ + return shutdown_q6_untrusted(&q6_lpass); +} + +static int shutdown_modem_fw_q6_untrusted(void) +{ + return shutdown_q6_untrusted(&q6_modem_fw); +} + +static int shutdown_modem_sw_q6_untrusted(void) +{ + return shutdown_q6_untrusted(&q6_modem_sw); +} + +static int init_image_riva_untrusted(const u8 *metadata, size_t size) +{ + const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + riva_start = ehdr->e_entry; + return 0; +} + +static int reset_riva_untrusted(void) +{ + u32 reg; + bool xo; + + /* Enable A2XB bridge */ + reg = readl(RIVA_PMU_A2XB_CFG); + reg |= RIVA_PMU_A2XB_CFG_EN; + writel(reg, RIVA_PMU_A2XB_CFG); + + /* Determine which XO to use */ + reg = readl(RIVA_PMU_CFG); + xo = (reg & RIVA_PMU_CFG_IRIS_XO_MODE) == RIVA_PMU_CFG_IRIS_XO_MODE_48; + + /* Program PLL 13 to 960 MHz */ + reg = readl(RIVA_PLL_MODE); + reg &= ~(PLL_MODE_BYPASSNL | PLL_MODE_OUTCTRL | PLL_MODE_RESET_N); + writel(reg, RIVA_PLL_MODE); + + if (xo) + writel(0x40000C00 | 40, RIVA_PLL_L_VAL); + else + writel(0x40000C00 | 50, RIVA_PLL_L_VAL); + writel(0, RIVA_PLL_M_VAL); + writel(1, RIVA_PLL_N_VAL); + writel_relaxed(0x01495227, RIVA_PLL_CONFIG); + + reg = readl(RIVA_PLL_MODE); + reg &= ~(PLL_MODE_REF_XO_SEL); + reg |= xo ? PLL_MODE_REF_XO_SEL_RF : PLL_MODE_REF_XO_SEL_CXO; + writel(reg, RIVA_PLL_MODE); + + /* Enable PLL 13 */ + reg |= PLL_MODE_BYPASSNL; + writel(reg, RIVA_PLL_MODE); + + usleep_range(10, 20); + + reg |= PLL_MODE_RESET_N; + writel(reg, RIVA_PLL_MODE); + reg |= PLL_MODE_OUTCTRL; + writel(reg, RIVA_PLL_MODE); + + /* Wait for PLL to settle */ + usleep_range(50, 100); + + /* Configure cCPU for 240 MHz */ + reg = readl(RIVA_PMU_CLK_ROOT3); + if (readl(RIVA_PMU_ROOT_CLK_SEL) & RIVA_PMU_ROOT_CLK_SEL_3) { + reg &= ~(RIVA_PMU_CLK_ROOT3_SRC0_SEL | + RIVA_PMU_CLK_ROOT3_SRC0_DIV); + reg |= RIVA_PMU_CLK_ROOT3_SRC0_SEL_RIVA | + RIVA_PMU_CLK_ROOT3_SRC0_DIV_2; + } else { + reg &= ~(RIVA_PMU_CLK_ROOT3_SRC1_SEL | + RIVA_PMU_CLK_ROOT3_SRC1_DIV); + reg |= RIVA_PMU_CLK_ROOT3_SRC1_SEL_RIVA | + RIVA_PMU_CLK_ROOT3_SRC1_DIV_2; + } + writel(reg, RIVA_PMU_CLK_ROOT3); + reg |= RIVA_PMU_CLK_ROOT3_ENA; + writel(reg, RIVA_PMU_CLK_ROOT3); + reg = readl(RIVA_PMU_ROOT_CLK_SEL); + reg ^= RIVA_PMU_ROOT_CLK_SEL_3; + writel(reg, RIVA_PMU_ROOT_CLK_SEL); + + /* Use the high vector table */ + reg = readl(RIVA_PMU_CCPU_CTL); + reg |= RIVA_PMU_CCPU_CTL_HIGH_IVT | RIVA_PMU_CCPU_CTL_REMAP_EN; + writel(reg, RIVA_PMU_CCPU_CTL); + + /* Set base memory address */ + writel_relaxed(riva_start >> 16, RIVA_PMU_CCPU_BOOT_REMAP_ADDR); + + /* Clear warmboot bit indicating this is a cold boot */ + reg = readl(RIVA_PMU_CFG); + reg &= ~(RIVA_PMU_CFG_WARM_BOOT); + writel(reg, RIVA_PMU_CFG); + + /* Enable the cCPU clock */ + reg = readl(RIVA_PMU_OVRD_VAL); + reg |= RIVA_PMU_OVRD_VAL_CCPU_CLK; + writel(reg, RIVA_PMU_OVRD_VAL); + + /* Take cCPU out of reset */ + reg |= RIVA_PMU_OVRD_VAL_CCPU_RESET; + writel(reg, RIVA_PMU_OVRD_VAL); + + return 0; +} + +static int shutdown_riva_untrusted(void) +{ + u32 reg; + /* Put riva into reset */ + reg = readl(RIVA_PMU_OVRD_VAL); + reg &= ~(RIVA_PMU_OVRD_VAL_CCPU_RESET | RIVA_PMU_OVRD_VAL_CCPU_CLK); + writel(reg, RIVA_PMU_OVRD_VAL); + return 0; +} + +static int init_image_dsps_untrusted(const u8 *metadata, size_t size) +{ + /* Bring memory and bus interface out of reset */ + writel_relaxed(0x2, PPSS_RESET); + writel_relaxed(0x10, PPSS_HCLK_CTL); + return 0; +} + +static int reset_dsps_untrusted(void) +{ + writel_relaxed(0x10, PPSS_PROC_CLK_CTL); + /* Bring DSPS out of reset */ + writel_relaxed(0x0, PPSS_RESET); + return 0; +} + +static int shutdown_dsps_untrusted(void) +{ + writel_relaxed(0x2, PPSS_RESET); + writel_relaxed(0x0, PPSS_PROC_CLK_CTL); + return 0; +} + +static struct pil_reset_ops pil_modem_fw_q6_ops = { + .init_image = init_image_modem_fw_q6_untrusted, + .verify_blob = verify_blob, + .auth_and_reset = reset_modem_fw_q6_untrusted, + .shutdown = shutdown_modem_fw_q6_untrusted, +}; + +static struct pil_reset_ops pil_modem_sw_q6_ops = { + .init_image = init_image_modem_sw_q6_untrusted, + .verify_blob = verify_blob, + .auth_and_reset = reset_modem_sw_q6_untrusted, + .shutdown = shutdown_modem_sw_q6_untrusted, +}; + +static struct pil_reset_ops pil_lpass_q6_ops = { + .init_image = init_image_lpass_q6_untrusted, + .verify_blob = verify_blob, + .auth_and_reset = reset_lpass_q6_untrusted, + .shutdown = shutdown_lpass_q6_untrusted, +}; + +static struct pil_reset_ops pil_riva_ops = { + .init_image = init_image_riva_untrusted, + .verify_blob = verify_blob, + .auth_and_reset = reset_riva_untrusted, + .shutdown = shutdown_riva_untrusted, +}; + +struct pil_reset_ops pil_dsps_ops = { + .init_image = init_image_dsps_untrusted, + .verify_blob = verify_blob, + .auth_and_reset = reset_dsps_untrusted, + .shutdown = shutdown_dsps_untrusted, +}; + +static struct pil_device peripherals[] = { + { + .name = "q6", + .pdev = { + .name = "pil_lpass_q6", + .id = -1, + }, + .ops = &pil_lpass_q6_ops, + }, + { + .name = "modem_fw", + .depends_on = "q6", + .pdev = { + .name = "pil_modem_fw_q6", + .id = -1, + }, + .ops = &pil_modem_fw_q6_ops, + }, + { + .name = "modem", + .depends_on = "modem_fw", + .pdev = { + .name = "pil_modem_sw_q6", + .id = -1, + }, + .ops = &pil_modem_sw_q6_ops, + }, + { + .name = "wcnss", + .pdev = { + .name = "pil_riva", + .id = -1, + }, + .ops = &pil_riva_ops, + }, + { + .name = "dsps", + .pdev = { + .name = "pil_dsps", + .id = -1, + }, + .ops = &pil_dsps_ops, + }, +}; + +static int __init msm_peripheral_reset_init(void) +{ + unsigned i; + int err; + + /* + * Don't initialize PIL on simulated targets, as some + * subsystems may not be emulated on them. + */ + if (machine_is_msm8960_sim() || machine_is_msm8960_rumi3()) + return 0; + + mss_enable_reg = ioremap(MSM_MSS_ENABLE_PHYS, 1); + if (!mss_enable_reg) { + err = -ENOMEM; + goto err_map_mss; + } + + q6_lpass.reg_base = ioremap(MSM_LPASS_QDSP6SS_PHYS, SZ_256); + if (!q6_lpass.reg_base) { + err = -ENOMEM; + goto err_map_lpass_q6; + } + + q6_modem_fw.reg_base = ioremap(MSM_FW_QDSP6SS_PHYS, SZ_256); + if (!q6_modem_fw.reg_base) { + err = -ENOMEM; + goto err_map_modem_fw_q6; + } + + q6_modem_sw.reg_base = ioremap(MSM_SW_QDSP6SS_PHYS, SZ_256); + if (!q6_modem_sw.reg_base) { + err = -ENOMEM; + goto err_map_modem_sw_q6; + } + + msm_riva_base = ioremap(MSM_RIVA_PHYS, SZ_256); + if (!msm_riva_base) { + err = -ENOMEM; + goto err_map_riva; + } + + q6_lpass.vreg = regulator_get(NULL, "lpass_q6"); + if (IS_ERR(q6_lpass.vreg)) { + err = PTR_ERR(q6_lpass.vreg); + goto err_vreg_lpass; + } + + q6_modem_fw.vreg = regulator_get(NULL, "modem_fw_q6"); + if (IS_ERR(q6_modem_fw.vreg)) { + err = PTR_ERR(q6_modem_fw.vreg); + goto err_vreg_modem_fw_q6; + } + + q6_modem_sw.vreg = regulator_get(NULL, "modem_sw_q6"); + if (IS_ERR(q6_modem_sw.vreg)) { + err = PTR_ERR(q6_modem_sw.vreg); + goto err_vreg_modem_sw_q6; + } + + for (i = 0; i < ARRAY_SIZE(peripherals); i++) + msm_pil_add_device(&peripherals[i]); + + return 0; + +err_vreg_modem_sw_q6: + regulator_put(q6_modem_fw.vreg); +err_vreg_modem_fw_q6: + regulator_put(q6_lpass.vreg); +err_vreg_lpass: + iounmap(msm_riva_base); +err_map_riva: + iounmap(q6_modem_sw.reg_base); +err_map_modem_sw_q6: + iounmap(q6_modem_fw.reg_base); +err_map_modem_fw_q6: + iounmap(q6_lpass.reg_base); +err_map_lpass_q6: + iounmap(mss_enable_reg); +err_map_mss: + return err; +} +arch_initcall(msm_peripheral_reset_init); diff --git a/arch/arm/mach-msm/peripheral-reset.c b/arch/arm/mach-msm/peripheral-reset.c new file mode 100644 index 00000000000..58097b2fdbc --- /dev/null +++ b/arch/arm/mach-msm/peripheral-reset.c @@ -0,0 +1,694 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "peripheral-loader.h" + +#define PROXY_VOTE_TIMEOUT 10000 + +#define MSM_MMS_REGS_BASE 0x10200000 +#define MSM_LPASS_QDSP6SS_BASE 0x28800000 + +#define MARM_RESET (MSM_CLK_CTL_BASE + 0x2BD4) +#define MARM_BOOT_CONTROL (msm_mms_regs_base + 0x0010) +#define MAHB0_SFAB_PORT_RESET (MSM_CLK_CTL_BASE + 0x2304) +#define MARM_CLK_BRANCH_ENA_VOTE (MSM_CLK_CTL_BASE + 0x3000) +#define MARM_CLK_SRC0_NS (MSM_CLK_CTL_BASE + 0x2BC0) +#define MARM_CLK_SRC1_NS (MSM_CLK_CTL_BASE + 0x2BC4) +#define MARM_CLK_SRC_CTL (MSM_CLK_CTL_BASE + 0x2BC8) +#define MARM_CLK_CTL (MSM_CLK_CTL_BASE + 0x2BCC) +#define SFAB_MSS_S_HCLK_CTL (MSM_CLK_CTL_BASE + 0x2C00) +#define MSS_MODEM_CXO_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C44) +#define MSS_SLP_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C60) +#define MSS_MARM_SYS_REF_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C64) +#define MAHB0_CLK_CTL (MSM_CLK_CTL_BASE + 0x2300) +#define MAHB1_CLK_CTL (MSM_CLK_CTL_BASE + 0x2BE4) +#define MAHB2_CLK_CTL (MSM_CLK_CTL_BASE + 0x2C20) +#define MAHB1_NS (MSM_CLK_CTL_BASE + 0x2BE0) +#define MARM_CLK_FS (MSM_CLK_CTL_BASE + 0x2BD0) +#define MAHB2_CLK_FS (MSM_CLK_CTL_BASE + 0x2C24) +#define PLL_ENA_MARM (MSM_CLK_CTL_BASE + 0x3500) +#define PLL8_STATUS (MSM_CLK_CTL_BASE + 0x3158) +#define CLK_HALT_MSS_SMPSS_MISC_STATE (MSM_CLK_CTL_BASE + 0x2FDC) + +#define LCC_Q6_FUNC (MSM_LPASS_CLK_CTL_BASE + 0x001C) +#define QDSP6SS_RST_EVB (msm_lpass_qdsp6ss_base + 0x0000) +#define QDSP6SS_STRAP_TCM (msm_lpass_qdsp6ss_base + 0x001C) +#define QDSP6SS_STRAP_AHB (msm_lpass_qdsp6ss_base + 0x0020) + +#define PPSS_RESET (MSM_CLK_CTL_BASE + 0x2594) +#define PPSS_PROC_CLK_CTL (MSM_CLK_CTL_BASE + 0x2588) +#define CLK_HALT_DFAB_STATE (MSM_CLK_CTL_BASE + 0x2FC8) + +#define PAS_MODEM 0 +#define PAS_Q6 1 +#define PAS_DSPS 2 +#define PAS_PLAYREADY 3 + +#define PAS_INIT_IMAGE_CMD 1 +#define PAS_MEM_CMD 2 +#define PAS_AUTH_AND_RESET_CMD 5 +#define PAS_SHUTDOWN_CMD 6 + +struct pas_init_image_req { + u32 proc; + u32 image_addr; +}; + +struct pas_init_image_resp { + u32 image_valid; +}; + +struct pas_auth_image_req { + u32 proc; +}; + +struct pas_auth_image_resp { + u32 reset_initiated; +}; + +struct pas_shutdown_req { + u32 proc; +}; + +struct pas_shutdown_resp { + u32 success; +}; + +static int modem_start, q6_start, dsps_start; +static void __iomem *msm_mms_regs_base; +static void __iomem *msm_lpass_qdsp6ss_base; + +static int init_image_trusted(int id, const u8 *metadata, size_t size) +{ + int ret; + struct pas_init_image_req request; + struct pas_init_image_resp resp = {0}; + void *mdata_buf; + + /* Make memory physically contiguous */ + mdata_buf = kmemdup(metadata, size, GFP_KERNEL); + if (!mdata_buf) + return -ENOMEM; + + request.proc = id; + request.image_addr = virt_to_phys(mdata_buf); + + ret = scm_call(SCM_SVC_PIL, PAS_INIT_IMAGE_CMD, &request, + sizeof(request), &resp, sizeof(resp)); + kfree(mdata_buf); + + if (ret) + return ret; + return resp.image_valid; +} + +static int init_image_modem_trusted(const u8 *metadata, size_t size) +{ + return init_image_trusted(PAS_MODEM, metadata, size); +} + +static int init_image_modem_untrusted(const u8 *metadata, size_t size) +{ + struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + modem_start = ehdr->e_entry; + return 0; +} + +static int init_image_q6_trusted(const u8 *metadata, size_t size) +{ + return init_image_trusted(PAS_Q6, metadata, size); +} + +static int init_image_q6_untrusted(const u8 *metadata, size_t size) +{ + struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + q6_start = ehdr->e_entry; + return 0; +} + +static int init_image_dsps_trusted(const u8 *metadata, size_t size) +{ + return init_image_trusted(PAS_DSPS, metadata, size); +} + +static int init_image_dsps_untrusted(const u8 *metadata, size_t size) +{ + struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata; + dsps_start = ehdr->e_entry; + /* Bring memory and bus interface out of reset */ + __raw_writel(0x2, PPSS_RESET); + mb(); + return 0; +} + +static int verify_blob(u32 phy_addr, size_t size) +{ + return 0; +} + +static int auth_and_reset_trusted(int id) +{ + int ret; + struct pas_auth_image_req request; + struct pas_auth_image_resp resp = {0}; + + request.proc = id; + ret = scm_call(SCM_SVC_PIL, PAS_AUTH_AND_RESET_CMD, &request, + sizeof(request), &resp, sizeof(resp)); + if (ret) + return ret; + + return resp.reset_initiated; +} + +static struct msm_xo_voter *pxo; +static void remove_modem_proxy_votes(unsigned long data) +{ + msm_xo_mode_vote(pxo, MSM_XO_MODE_OFF); +} +static DEFINE_TIMER(modem_timer, remove_modem_proxy_votes, 0, 0); + +static void make_modem_proxy_votes(void) +{ + /* Make proxy votes for modem and set up timer to disable it. */ + msm_xo_mode_vote(pxo, MSM_XO_MODE_ON); + mod_timer(&modem_timer, jiffies + msecs_to_jiffies(PROXY_VOTE_TIMEOUT)); +} + +static void remove_modem_proxy_votes_now(void) +{ + /* + * If the modem proxy vote hasn't been removed yet, them remove the + * votes immediately. + */ + if (del_timer(&modem_timer)) + remove_modem_proxy_votes(0); +} + +static int reset_modem_untrusted(void) +{ + u32 reg; + + make_modem_proxy_votes(); + + /* Put modem AHB0,1,2 clocks into reset */ + __raw_writel(BIT(0) | BIT(1), MAHB0_SFAB_PORT_RESET); + __raw_writel(BIT(7), MAHB1_CLK_CTL); + __raw_writel(BIT(7), MAHB2_CLK_CTL); + + /* Vote for pll8 on behalf of the modem */ + reg = __raw_readl(PLL_ENA_MARM); + reg |= BIT(8); + __raw_writel(reg, PLL_ENA_MARM); + + /* Wait for PLL8 to enable */ + while (!(__raw_readl(PLL8_STATUS) & BIT(16))) + cpu_relax(); + + /* Set MAHB1 divider to Div-5 to run MAHB1,2 and sfab at 79.8 Mhz*/ + __raw_writel(0x4, MAHB1_NS); + + /* Vote for modem AHB1 and 2 clocks to be on on behalf of the modem */ + reg = __raw_readl(MARM_CLK_BRANCH_ENA_VOTE); + reg |= BIT(0) | BIT(1); + __raw_writel(reg, MARM_CLK_BRANCH_ENA_VOTE); + + /* Source marm_clk off of PLL8 */ + reg = __raw_readl(MARM_CLK_SRC_CTL); + if ((reg & 0x1) == 0) { + __raw_writel(0x3, MARM_CLK_SRC1_NS); + reg |= 0x1; + } else { + __raw_writel(0x3, MARM_CLK_SRC0_NS); + reg &= ~0x1; + } + __raw_writel(reg | 0x2, MARM_CLK_SRC_CTL); + + /* + * Force core on and periph on signals to remain active during halt + * for marm_clk and mahb2_clk + */ + __raw_writel(0x6F, MARM_CLK_FS); + __raw_writel(0x6F, MAHB2_CLK_FS); + + /* + * Enable all of the marm_clk branches, cxo sourced marm branches, + * and sleep clock branches + */ + __raw_writel(0x10, MARM_CLK_CTL); + __raw_writel(0x10, MAHB0_CLK_CTL); + __raw_writel(0x10, SFAB_MSS_S_HCLK_CTL); + __raw_writel(0x10, MSS_MODEM_CXO_CLK_CTL); + __raw_writel(0x10, MSS_SLP_CLK_CTL); + __raw_writel(0x10, MSS_MARM_SYS_REF_CLK_CTL); + + /* Wait for above clocks to be turned on */ + while (__raw_readl(CLK_HALT_MSS_SMPSS_MISC_STATE) & (BIT(7) | BIT(8) | + BIT(9) | BIT(10) | BIT(4) | BIT(6))) + cpu_relax(); + + /* Take MAHB0,1,2 clocks out of reset */ + __raw_writel(0x0, MAHB2_CLK_CTL); + __raw_writel(0x0, MAHB1_CLK_CTL); + __raw_writel(0x0, MAHB0_SFAB_PORT_RESET); + + /* Setup exception vector table base address */ + __raw_writel(modem_start | 0x1, MARM_BOOT_CONTROL); + + /* Wait for vector table to be setup */ + mb(); + + /* Bring modem out of reset */ + __raw_writel(0x0, MARM_RESET); + + return 0; +} + +static int reset_modem_trusted(void) +{ + int ret; + + make_modem_proxy_votes(); + + ret = auth_and_reset_trusted(PAS_MODEM); + if (ret) + remove_modem_proxy_votes_now(); + + return ret; +} + +static int shutdown_trusted(int id) +{ + int ret; + struct pas_shutdown_req request; + struct pas_shutdown_resp resp = {0}; + + request.proc = id; + ret = scm_call(SCM_SVC_PIL, PAS_SHUTDOWN_CMD, &request, sizeof(request), + &resp, sizeof(resp)); + if (ret) + return ret; + + return resp.success; +} + +static int shutdown_modem_untrusted(void) +{ + u32 reg; + + /* Put modem into reset */ + __raw_writel(0x1, MARM_RESET); + mb(); + + /* Put modem AHB0,1,2 clocks into reset */ + __raw_writel(BIT(0) | BIT(1), MAHB0_SFAB_PORT_RESET); + __raw_writel(BIT(7), MAHB1_CLK_CTL); + __raw_writel(BIT(7), MAHB2_CLK_CTL); + mb(); + + /* + * Disable all of the marm_clk branches, cxo sourced marm branches, + * and sleep clock branches + */ + __raw_writel(0x0, MARM_CLK_CTL); + __raw_writel(0x0, MAHB0_CLK_CTL); + __raw_writel(0x0, SFAB_MSS_S_HCLK_CTL); + __raw_writel(0x0, MSS_MODEM_CXO_CLK_CTL); + __raw_writel(0x0, MSS_SLP_CLK_CTL); + __raw_writel(0x0, MSS_MARM_SYS_REF_CLK_CTL); + + /* Disable marm_clk */ + reg = __raw_readl(MARM_CLK_SRC_CTL); + reg &= ~0x2; + __raw_writel(reg, MARM_CLK_SRC_CTL); + + /* Clear modem's votes for ahb clocks */ + __raw_writel(0x0, MARM_CLK_BRANCH_ENA_VOTE); + + /* Clear modem's votes for PLLs */ + __raw_writel(0x0, PLL_ENA_MARM); + + remove_modem_proxy_votes_now(); + + return 0; +} + +static int shutdown_modem_trusted(void) +{ + int ret; + + ret = shutdown_trusted(PAS_MODEM); + if (ret) + return ret; + + remove_modem_proxy_votes_now(); + + return 0; +} + +#define LV_EN BIT(27) +#define STOP_CORE BIT(26) +#define CLAMP_IO BIT(25) +#define Q6SS_PRIV_ARES BIT(24) +#define Q6SS_SS_ARES BIT(23) +#define Q6SS_ISDB_ARES BIT(22) +#define Q6SS_ETM_ARES BIT(21) +#define Q6_JTAG_CRC_EN BIT(20) +#define Q6_JTAG_INV_EN BIT(19) +#define Q6_JTAG_CXC_EN BIT(18) +#define Q6_PXO_CRC_EN BIT(17) +#define Q6_PXO_INV_EN BIT(16) +#define Q6_PXO_CXC_EN BIT(15) +#define Q6_PXO_SLEEP_EN BIT(14) +#define Q6_SLP_CRC_EN BIT(13) +#define Q6_SLP_INV_EN BIT(12) +#define Q6_SLP_CXC_EN BIT(11) +#define CORE_ARES BIT(10) +#define CORE_L1_MEM_CORE_EN BIT(9) +#define CORE_TCM_MEM_CORE_EN BIT(8) +#define CORE_TCM_MEM_PERPH_EN BIT(7) +#define CORE_GFM4_CLK_EN BIT(2) +#define CORE_GFM4_RES BIT(1) +#define RAMP_PLL_SRC_SEL BIT(0) + +#define Q6_STRAP_AHB_UPPER (0x290 << 12) +#define Q6_STRAP_AHB_LOWER 0x280 +#define Q6_STRAP_TCM_BASE (0x28C << 15) +#define Q6_STRAP_TCM_CONFIG 0x28B + +static struct clk *pll4; + +static void remove_q6_proxy_votes(unsigned long data) +{ + clk_disable(pll4); +} +static DEFINE_TIMER(q6_timer, remove_q6_proxy_votes, 0, 0); + +static void make_q6_proxy_votes(void) +{ + /* Make proxy votes for Q6 and set up timer to disable it. */ + clk_enable(pll4); + mod_timer(&q6_timer, jiffies + msecs_to_jiffies(PROXY_VOTE_TIMEOUT)); +} + +static void remove_q6_proxy_votes_now(void) +{ + /* + * If the Q6 proxy vote hasn't been removed yet, them remove the + * votes immediately. + */ + if (del_timer(&q6_timer)) + remove_q6_proxy_votes(0); +} + +static int reset_q6_untrusted(void) +{ + u32 reg; + + make_q6_proxy_votes(); + + /* Put Q6 into reset */ + reg = __raw_readl(LCC_Q6_FUNC); + reg |= Q6SS_SS_ARES | Q6SS_ISDB_ARES | Q6SS_ETM_ARES | STOP_CORE | + CORE_ARES; + reg &= ~CORE_GFM4_CLK_EN; + __raw_writel(reg, LCC_Q6_FUNC); + + /* Wait 8 AHB cycles for Q6 to be fully reset (AHB = 1.5Mhz) */ + usleep_range(20, 30); + + /* Turn on Q6 memory */ + reg |= CORE_GFM4_CLK_EN | CORE_L1_MEM_CORE_EN | CORE_TCM_MEM_CORE_EN | + CORE_TCM_MEM_PERPH_EN; + __raw_writel(reg, LCC_Q6_FUNC); + + /* Turn on Q6 core clocks and take core out of reset */ + reg &= ~(CLAMP_IO | Q6SS_SS_ARES | Q6SS_ISDB_ARES | Q6SS_ETM_ARES | + CORE_ARES); + __raw_writel(reg, LCC_Q6_FUNC); + + /* Wait for clocks to be enabled */ + mb(); + /* Program boot address */ + __raw_writel((q6_start >> 12) & 0xFFFFF, QDSP6SS_RST_EVB); + + __raw_writel(Q6_STRAP_TCM_CONFIG | Q6_STRAP_TCM_BASE, + QDSP6SS_STRAP_TCM); + __raw_writel(Q6_STRAP_AHB_UPPER | Q6_STRAP_AHB_LOWER, + QDSP6SS_STRAP_AHB); + + /* Wait for addresses to be programmed before starting Q6 */ + mb(); + + /* Start Q6 instruction execution */ + reg &= ~STOP_CORE; + __raw_writel(reg, LCC_Q6_FUNC); + + return 0; +} + +static int reset_q6_trusted(void) +{ + make_q6_proxy_votes(); + + return auth_and_reset_trusted(PAS_Q6); +} + +static int shutdown_q6_untrusted(void) +{ + u32 reg; + + /* Put Q6 into reset */ + reg = __raw_readl(LCC_Q6_FUNC); + reg |= Q6SS_SS_ARES | Q6SS_ISDB_ARES | Q6SS_ETM_ARES | STOP_CORE | + CORE_ARES; + reg &= ~CORE_GFM4_CLK_EN; + __raw_writel(reg, LCC_Q6_FUNC); + + /* Wait 8 AHB cycles for Q6 to be fully reset (AHB = 1.5Mhz) */ + usleep_range(20, 30); + + /* Turn off Q6 memory */ + reg &= ~(CORE_L1_MEM_CORE_EN | CORE_TCM_MEM_CORE_EN | + CORE_TCM_MEM_PERPH_EN); + __raw_writel(reg, LCC_Q6_FUNC); + + reg |= CLAMP_IO; + __raw_writel(reg, LCC_Q6_FUNC); + + remove_q6_proxy_votes_now(); + + return 0; +} + +static int shutdown_q6_trusted(void) +{ + int ret; + + ret = shutdown_trusted(PAS_Q6); + if (ret) + return ret; + + remove_q6_proxy_votes_now(); + + return 0; +} + +static int reset_dsps_untrusted(void) +{ + __raw_writel(0x10, PPSS_PROC_CLK_CTL); + while (__raw_readl(CLK_HALT_DFAB_STATE) & BIT(18)) + cpu_relax(); + + /* Bring DSPS out of reset */ + __raw_writel(0x0, PPSS_RESET); + return 0; +} + +static int reset_dsps_trusted(void) +{ + return auth_and_reset_trusted(PAS_DSPS); +} + +static int shutdown_dsps_trusted(void) +{ + return shutdown_trusted(PAS_DSPS); +} + +static int shutdown_dsps_untrusted(void) +{ + __raw_writel(0x2, PPSS_RESET); + __raw_writel(0x0, PPSS_PROC_CLK_CTL); + return 0; +} + +static int init_image_playready(const u8 *metadata, size_t size) +{ + return init_image_trusted(PAS_PLAYREADY, metadata, size); +} + +static int reset_playready(void) +{ + return auth_and_reset_trusted(PAS_PLAYREADY); +} + +static int shutdown_playready(void) +{ + return shutdown_trusted(PAS_PLAYREADY); +} + +struct pil_reset_ops pil_modem_ops = { + .init_image = init_image_modem_untrusted, + .verify_blob = verify_blob, + .auth_and_reset = reset_modem_untrusted, + .shutdown = shutdown_modem_untrusted, +}; + +struct pil_reset_ops pil_q6_ops = { + .init_image = init_image_q6_untrusted, + .verify_blob = verify_blob, + .auth_and_reset = reset_q6_untrusted, + .shutdown = shutdown_q6_untrusted, +}; + +struct pil_reset_ops pil_dsps_ops = { + .init_image = init_image_dsps_untrusted, + .verify_blob = verify_blob, + .auth_and_reset = reset_dsps_untrusted, + .shutdown = shutdown_dsps_untrusted, +}; + +struct pil_reset_ops pil_playready_ops = { + .init_image = init_image_playready, + .verify_blob = verify_blob, + .auth_and_reset = reset_playready, + .shutdown = shutdown_playready, +}; + +static struct pil_device peripherals[] = { + { + .name = "modem", + .depends_on = "q6", + .pdev = { + .name = "pil_modem", + .id = -1, + }, + .ops = &pil_modem_ops, + }, + { + .name = "q6", + .pdev = { + .name = "pil_q6", + .id = -1, + }, + .ops = &pil_q6_ops, + }, + { + .name = "playrdy", + .pdev = { + .name = "pil_playready", + .id = -1, + }, + .ops = &pil_playready_ops, + }, +}; + +struct pil_device peripheral_dsps = { + .name = "dsps", + .pdev = { + .name = "pil_dsps", + .id = -1, + }, + .ops = &pil_dsps_ops, +}; + +#ifdef CONFIG_MSM_SECURE_PIL +#define SECURE_PIL 1 +#else +#define SECURE_PIL 0 +#endif + +static int __init msm_peripheral_reset_init(void) +{ + unsigned i; + + msm_mms_regs_base = ioremap(MSM_MMS_REGS_BASE, SZ_256); + if (!msm_mms_regs_base) + goto err; + + msm_lpass_qdsp6ss_base = ioremap(MSM_LPASS_QDSP6SS_BASE, SZ_256); + if (!msm_lpass_qdsp6ss_base) + goto err_lpass; + + pxo = msm_xo_get(MSM_XO_PXO, "pil"); + if (IS_ERR(pxo)) + goto err_pxo; + + pll4 = clk_get_sys("peripheral-reset", "pll4"); + if (IS_ERR(pll4)) + goto err_clk; + + if (SECURE_PIL) { + pil_modem_ops.init_image = init_image_modem_trusted; + pil_modem_ops.auth_and_reset = reset_modem_trusted; + pil_modem_ops.shutdown = shutdown_modem_trusted; + + pil_q6_ops.init_image = init_image_q6_trusted; + pil_q6_ops.auth_and_reset = reset_q6_trusted; + pil_q6_ops.shutdown = shutdown_q6_trusted; + + pil_dsps_ops.init_image = init_image_dsps_trusted; + pil_dsps_ops.auth_and_reset = reset_dsps_trusted; + pil_dsps_ops.shutdown = shutdown_dsps_trusted; + } + + for (i = 0; i < ARRAY_SIZE(peripherals); i++) + msm_pil_add_device(&peripherals[i]); + + return 0; + +err_clk: + msm_xo_put(pxo); +err_pxo: + iounmap(msm_lpass_qdsp6ss_base); +err_lpass: + iounmap(msm_mms_regs_base); +err: + return -ENOMEM; +} + +static void __exit msm_peripheral_reset_exit(void) +{ + iounmap(msm_mms_regs_base); + iounmap(msm_lpass_qdsp6ss_base); +} + +arch_initcall(msm_peripheral_reset_init); +module_exit(msm_peripheral_reset_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Validate and bring peripherals out of reset"); diff --git a/arch/arm/mach-msm/ping_apps_server.c b/arch/arm/mach-msm/ping_apps_server.c new file mode 100644 index 00000000000..0a856003f96 --- /dev/null +++ b/arch/arm/mach-msm/ping_apps_server.c @@ -0,0 +1,605 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * PING APPS SERVER Driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* ping server definitions */ + +#define PING_APPS_PROG 0x30000082 +#define PING_APPS_VERS 0x00010001 + +#define PING_APPS_NULL 0 +#define PING_APPS_DATA 4 +#define PING_APPS_REG 2 +#define PING_APPS_UNREG 3 +#define PING_APPS_DATA_CB_REG 6 +#define PING_APPS_DATA_CB_UNREG 5 + +#define PING_APPS_REG_CB 2 +#define PING_APPS_DATA_CB 1 + +static LIST_HEAD(cb_entry_list); +static DEFINE_MUTEX(cb_entry_list_lock); + +static struct task_struct *server_thread; + +struct ping_apps_register_arg { + uint32_t cb_id; + int32_t num; +}; + +struct ping_apps_unregister_arg { + uint32_t cb_id; +}; + +struct ping_apps_register_cb_arg { + uint32_t cb_id; + int32_t num; +}; + +struct ping_apps_register_ret { + uint32_t result; +}; + +struct ping_apps_unregister_ret { + uint32_t result; +}; + +struct ping_apps_data_cb_reg_arg { + uint32_t cb_id; + uint32_t num; + uint32_t size; + uint32_t interval_ms; + uint32_t num_tasks; +}; + +struct ping_apps_data_cb_unreg_arg { + uint32_t cb_id; +}; + +struct ping_apps_data_cb_arg { + uint32_t cb_id; + uint32_t *data; + uint32_t size; + uint32_t sum; +}; + +struct ping_apps_data_cb_reg_ret { + uint32_t result; +}; + +struct ping_apps_data_cb_unreg_ret { + uint32_t result; +}; + +struct ping_apps_data_cb_ret { + uint32_t result; +}; + +struct ping_apps_data_arg { + uint32_t *data; + uint32_t size; +}; + +struct ping_apps_data_ret { + uint32_t result; +}; + +struct ping_apps_data_cb_info { + void *cb_func; + uint32_t size; + uint32_t num_tasks; +}; + +struct ping_apps_cb_entry { + struct list_head list; + + struct msm_rpc_client_info clnt_info; + void *cb_info; + uint32_t cb_id; + int32_t num; + uint32_t interval_ms; + uint32_t time_to_next_cb; + void (*cb_func)(struct ping_apps_cb_entry *); +}; + +static int handle_rpc_call(struct msm_rpc_server *server, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr); + +static int ping_apps_data_cb(struct msm_rpc_server *server, + struct msm_rpc_client_info *clnt_info, + struct ping_apps_data_cb_arg *arg, + struct ping_apps_data_cb_ret *ret); + +static int ping_apps_register_cb(struct msm_rpc_server *server, + struct msm_rpc_client_info *clnt_info, + struct ping_apps_register_cb_arg *arg, + void *ret); + +static struct msm_rpc_server rpc_server = { + .prog = PING_APPS_PROG, + .vers = PING_APPS_VERS, + .rpc_call2 = handle_rpc_call, +}; + +static void handle_ping_apps_data_cb(struct ping_apps_cb_entry *cb_entry) +{ + struct ping_apps_data_cb_arg arg; + struct ping_apps_data_cb_ret ret; + uint32_t my_sum = 0; + uint32_t *my_data; + int i; + + if (cb_entry->num > 0) { + cb_entry->num--; + arg.cb_id = cb_entry->cb_id; + arg.size = ((struct ping_apps_data_cb_info *) + (cb_entry->cb_info))->size; + + my_data = kmalloc((arg.size * sizeof(uint32_t)), GFP_KERNEL); + if (!my_data) + return; + + for (i = 0; i < arg.size; i++) { + my_data[i] = (42 + i); + my_sum ^= (42 + i); + } + arg.data = my_data; + arg.sum = my_sum; + + ((int (*)(struct msm_rpc_server *, + struct msm_rpc_client_info *, + struct ping_apps_data_cb_arg *, + struct ping_apps_data_cb_ret *)) + ((struct ping_apps_data_cb_info *) + (cb_entry->cb_info))->cb_func)(&rpc_server, + &cb_entry->clnt_info, + &arg, &ret); + pr_info("%s: cb_id = %d, ret = %d\n", + __func__, arg.cb_id, ret.result); + kfree(my_data); + } +} + +static void handle_ping_apps_register_cb(struct ping_apps_cb_entry *cb_entry) +{ + struct ping_apps_register_cb_arg arg; + + if (cb_entry->num > 0) { + cb_entry->num--; + arg.cb_id = cb_entry->cb_id; + arg.num = cb_entry->num; + + pr_info("%s: cb_id = %d, num = %d\n", + __func__, arg.cb_id, arg.num); + ((int (*)(struct msm_rpc_server *, + struct msm_rpc_client_info *, + struct ping_apps_register_cb_arg *, + void *))cb_entry->cb_info)(&rpc_server, + &cb_entry->clnt_info, + &arg, NULL); + } + +} + +static int ping_apps_cb_process_thread(void *data) +{ + struct ping_apps_cb_entry *cb_entry; + uint32_t sleep_time; + uint32_t time_slept = 0; + + pr_info("%s: thread started\n", __func__); + for (;;) { + sleep_time = 1000; + mutex_lock(&cb_entry_list_lock); + list_for_each_entry(cb_entry, &cb_entry_list, list) { + if (cb_entry->time_to_next_cb <= time_slept) { + cb_entry->cb_func(cb_entry); + cb_entry->time_to_next_cb = + cb_entry->interval_ms; + } else + cb_entry->time_to_next_cb -= time_slept; + + if (cb_entry->time_to_next_cb < sleep_time) + sleep_time = cb_entry->time_to_next_cb; + } + mutex_unlock(&cb_entry_list_lock); + + msleep(sleep_time); + time_slept = sleep_time; + } + + do_exit(0); +} + +static int ping_apps_data_register(struct ping_apps_data_arg *arg, + struct ping_apps_data_ret *ret) +{ + int i; + + ret->result = 0; + for (i = 0; i < arg->size; i++) + ret->result ^= arg->data[i]; + + return 0; +} + +static int ping_apps_data_cb_reg(struct ping_apps_data_cb_reg_arg *arg, + struct ping_apps_data_cb_reg_ret *ret) +{ + struct ping_apps_cb_entry *cb_entry; + struct ping_apps_data_cb_info *cb_info; + + cb_entry = kmalloc(sizeof(*cb_entry), GFP_KERNEL); + if (!cb_entry) + return -ENOMEM; + + cb_entry->cb_info = kmalloc(sizeof(struct ping_apps_data_cb_info), + GFP_KERNEL); + if (!cb_entry->cb_info) { + kfree(cb_entry); + return -ENOMEM; + } + cb_info = (struct ping_apps_data_cb_info *)cb_entry->cb_info; + + INIT_LIST_HEAD(&cb_entry->list); + cb_entry->cb_func = handle_ping_apps_data_cb; + cb_entry->cb_id = arg->cb_id; + cb_entry->num = arg->num; + cb_entry->interval_ms = arg->interval_ms; + cb_entry->time_to_next_cb = arg->interval_ms; + cb_info->cb_func = ping_apps_data_cb; + cb_info->size = arg->size; + cb_info->num_tasks = arg->num_tasks; + + mutex_lock(&cb_entry_list_lock); + list_add_tail(&cb_entry->list, &cb_entry_list); + mutex_unlock(&cb_entry_list_lock); + + msm_rpc_server_get_requesting_client(&cb_entry->clnt_info); + + if (IS_ERR(server_thread)) + server_thread = kthread_run(ping_apps_cb_process_thread, + NULL, "kpingrpccbprocessd"); + if (IS_ERR(server_thread)) { + kfree(cb_entry); + return PTR_ERR(server_thread); + } + + ret->result = 1; + return 0; +} + +static int ping_apps_data_cb_unreg(struct ping_apps_data_cb_unreg_arg *arg, + struct ping_apps_data_cb_unreg_ret *ret) +{ + struct ping_apps_cb_entry *cb_entry, *tmp_cb_entry; + + mutex_lock(&cb_entry_list_lock); + list_for_each_entry_safe(cb_entry, tmp_cb_entry, + &cb_entry_list, list) { + if (cb_entry->cb_id == arg->cb_id) { + list_del(&cb_entry->list); + kfree(cb_entry->cb_info); + kfree(cb_entry); + break; + } + } + mutex_unlock(&cb_entry_list_lock); + + ret->result = 1; + return 0; +} + +static int ping_apps_register(struct ping_apps_register_arg *arg, + struct ping_apps_register_ret *ret) +{ + struct ping_apps_cb_entry *cb_entry; + + cb_entry = kmalloc(sizeof(*cb_entry), GFP_KERNEL); + if (!cb_entry) + return -ENOMEM; + + INIT_LIST_HEAD(&cb_entry->list); + cb_entry->cb_func = handle_ping_apps_register_cb; + cb_entry->cb_info = ping_apps_register_cb; + cb_entry->cb_id = arg->cb_id; + cb_entry->num = arg->num; + cb_entry->interval_ms = 100; + cb_entry->time_to_next_cb = 100; + + mutex_lock(&cb_entry_list_lock); + list_add_tail(&cb_entry->list, &cb_entry_list); + mutex_unlock(&cb_entry_list_lock); + + msm_rpc_server_get_requesting_client(&cb_entry->clnt_info); + + if (IS_ERR(server_thread)) + server_thread = kthread_run(ping_apps_cb_process_thread, + NULL, "kpingrpccbprocessd"); + if (IS_ERR(server_thread)) { + kfree(cb_entry); + return PTR_ERR(server_thread); + } + + ret->result = 1; + return 0; +} + +static int ping_apps_unregister(struct ping_apps_unregister_arg *arg, + struct ping_apps_unregister_ret *ret) +{ + struct ping_apps_cb_entry *cb_entry, *tmp_cb_entry; + + mutex_lock(&cb_entry_list_lock); + list_for_each_entry_safe(cb_entry, tmp_cb_entry, + &cb_entry_list, list) { + if (cb_entry->cb_id == arg->cb_id) { + list_del(&cb_entry->list); + kfree(cb_entry); + break; + } + } + mutex_unlock(&cb_entry_list_lock); + + ret->result = 1; + return 0; +} + +static int ping_apps_data_cb_arg_func(struct msm_rpc_server *server, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_apps_data_cb_arg *arg = data; + + xdr_send_uint32(xdr, &arg->cb_id); + xdr_send_array(xdr, (void **)&arg->data, &arg->size, 64, + sizeof(uint32_t), (void *)xdr_send_uint32); + xdr_send_uint32(xdr, &arg->size); + xdr_send_uint32(xdr, &arg->sum); + + return 0; +} + +static int ping_apps_data_cb_ret_func(struct msm_rpc_server *server, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_apps_data_cb_ret *ret = data; + + xdr_recv_uint32(xdr, &ret->result); + + return 0; +} + +static int ping_apps_data_cb(struct msm_rpc_server *server, + struct msm_rpc_client_info *clnt_info, + struct ping_apps_data_cb_arg *arg, + struct ping_apps_data_cb_ret *ret) +{ + return msm_rpc_server_cb_req2(server, clnt_info, + PING_APPS_DATA_CB, + ping_apps_data_cb_arg_func, arg, + ping_apps_data_cb_ret_func, ret, -1); +} + +static int ping_apps_register_cb_arg(struct msm_rpc_server *server, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_apps_register_cb_arg *arg = data; + + xdr_send_uint32(xdr, &arg->cb_id); + xdr_send_int32(xdr, &arg->num); + + return 0; +} + +static int ping_apps_register_cb(struct msm_rpc_server *server, + struct msm_rpc_client_info *clnt_info, + struct ping_apps_register_cb_arg *arg, + void *ret) +{ + return msm_rpc_server_cb_req2(server, clnt_info, + PING_APPS_REG_CB, + ping_apps_register_cb_arg, + arg, NULL, NULL, -1); +} + +static int handle_ping_apps_data_register(struct msm_rpc_server *server, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + uint32_t rc; + struct ping_apps_data_arg arg; + struct ping_apps_data_ret ret; + + pr_info("%s: request received\n", __func__); + + xdr_recv_array(xdr, (void **)&arg.data, &arg.size, 64, + sizeof(uint32_t), (void *)xdr_recv_uint32); + xdr_recv_uint32(xdr, &arg.size); + + rc = ping_apps_data_register(&arg, &ret); + if (rc < 0) + goto free_and_return; + + xdr_start_accepted_reply(xdr, RPC_ACCEPTSTAT_SUCCESS); + xdr_send_uint32(xdr, &ret.result); + rc = xdr_send_msg(xdr); + if (rc < 0) + pr_info("%s: sending reply failed\n", __func__); + else + rc = 1; + + free_and_return: + kfree(arg.data); + return rc; +} + +static int handle_ping_apps_data_cb_reg(struct msm_rpc_server *server, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + uint32_t rc; + struct ping_apps_data_cb_reg_arg arg; + struct ping_apps_data_cb_reg_ret ret; + + pr_info("%s: request received\n", __func__); + + xdr_recv_uint32(xdr, &arg.cb_id); + xdr_recv_uint32(xdr, &arg.num); + xdr_recv_uint32(xdr, &arg.size); + xdr_recv_uint32(xdr, &arg.interval_ms); + xdr_recv_uint32(xdr, &arg.num_tasks); + + rc = ping_apps_data_cb_reg(&arg, &ret); + if (rc < 0) + return rc; + + xdr_start_accepted_reply(xdr, RPC_ACCEPTSTAT_SUCCESS); + xdr_send_uint32(xdr, &ret.result); + rc = xdr_send_msg(xdr); + if (rc < 0) + pr_info("%s: sending reply failed\n", __func__); + else + rc = 1; + + return rc; +} + +static int handle_ping_apps_data_cb_unreg(struct msm_rpc_server *server, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + uint32_t rc; + struct ping_apps_data_cb_unreg_arg arg; + struct ping_apps_data_cb_unreg_ret ret; + + pr_info("%s: request received\n", __func__); + + xdr_recv_uint32(xdr, &arg.cb_id); + + rc = ping_apps_data_cb_unreg(&arg, &ret); + if (rc < 0) + return rc; + + xdr_start_accepted_reply(xdr, RPC_ACCEPTSTAT_SUCCESS); + xdr_send_uint32(xdr, &ret.result); + rc = xdr_send_msg(xdr); + if (rc < 0) + pr_info("%s: sending reply failed\n", __func__); + else + rc = 1; + + return rc; +} + +static int handle_ping_apps_register(struct msm_rpc_server *server, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + uint32_t rc; + struct ping_apps_register_arg arg; + struct ping_apps_register_ret ret; + + pr_info("%s: request received\n", __func__); + + xdr_recv_uint32(xdr, &arg.cb_id); + xdr_recv_int32(xdr, &arg.num); + + rc = ping_apps_register(&arg, &ret); + if (rc < 0) + return rc; + + xdr_start_accepted_reply(xdr, RPC_ACCEPTSTAT_SUCCESS); + xdr_send_uint32(xdr, &ret.result); + rc = xdr_send_msg(xdr); + if (rc < 0) + pr_info("%s: sending reply failed\n", __func__); + else + rc = 1; + + return rc; +} + +static int handle_ping_apps_unregister(struct msm_rpc_server *server, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + uint32_t rc; + struct ping_apps_unregister_arg arg; + struct ping_apps_unregister_ret ret; + + pr_info("%s: request received\n", __func__); + + xdr_recv_uint32(xdr, &arg.cb_id); + + rc = ping_apps_unregister(&arg, &ret); + if (rc < 0) + return rc; + + xdr_start_accepted_reply(xdr, RPC_ACCEPTSTAT_SUCCESS); + xdr_send_uint32(xdr, &ret.result); + rc = xdr_send_msg(xdr); + if (rc < 0) + pr_info("%s: sending reply failed\n", __func__); + else + rc = 1; + + return rc; +} + +static int handle_rpc_call(struct msm_rpc_server *server, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + switch (req->procedure) { + case PING_APPS_NULL: + pr_info("%s: null procedure request received\n", __func__); + return 0; + case PING_APPS_DATA: + return handle_ping_apps_data_register(server, req, xdr); + case PING_APPS_REG: + return handle_ping_apps_register(server, req, xdr); + case PING_APPS_UNREG: + return handle_ping_apps_unregister(server, req, xdr); + case PING_APPS_DATA_CB_REG: + return handle_ping_apps_data_cb_reg(server, req, xdr); + case PING_APPS_DATA_CB_UNREG: + return handle_ping_apps_data_cb_unreg(server, req, xdr); + default: + return -ENODEV; + } +} + +static int __init ping_apps_server_init(void) +{ + INIT_LIST_HEAD(&cb_entry_list); + server_thread = ERR_PTR(-1); + return msm_rpc_create_server2(&rpc_server); +} + +module_init(ping_apps_server_init); + +MODULE_DESCRIPTION("PING APPS SERVER Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/ping_mdm_rpc_client.c b/arch/arm/mach-msm/ping_mdm_rpc_client.c new file mode 100644 index 00000000000..041430ea02b --- /dev/null +++ b/arch/arm/mach-msm/ping_mdm_rpc_client.c @@ -0,0 +1,812 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * SMD RPC PING MODEM Driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PING_TEST_BASE 0x31 + +#define PTIOC_NULL_TEST _IO(PING_TEST_BASE, 1) +#define PTIOC_REG_TEST _IO(PING_TEST_BASE, 2) +#define PTIOC_DATA_REG_TEST _IO(PING_TEST_BASE, 3) +#define PTIOC_DATA_CB_REG_TEST _IO(PING_TEST_BASE, 4) + +#define PING_MDM_PROG 0x30000081 +#define PING_MDM_VERS 0x00010001 +#define PING_MDM_CB_PROG 0x31000081 +#define PING_MDM_CB_VERS 0x00010001 + +#define PING_MDM_NULL_PROC 0 +#define PING_MDM_RPC_GLUE_CODE_INFO_REMOTE_PROC 1 +#define PING_MDM_REGISTER_PROC 2 +#define PING_MDM_UNREGISTER_PROC 3 +#define PING_MDM_REGISTER_DATA_PROC 4 +#define PING_MDM_UNREGISTER_DATA_CB_PROC 5 +#define PING_MDM_REGISTER_DATA_CB_PROC 6 + +#define PING_MDM_DATA_CB_PROC 1 +#define PING_MDM_CB_PROC 2 + +#define PING_MAX_RETRY 5 + +static struct msm_rpc_client *rpc_client; +static uint32_t open_count; +static DEFINE_MUTEX(ping_mdm_lock); + +struct ping_mdm_register_cb_arg { + uint32_t cb_id; + int val; +}; + +struct ping_mdm_register_data_cb_cb_arg { + uint32_t cb_id; + uint32_t *data; + uint32_t size; + uint32_t sum; +}; + +struct ping_mdm_register_data_cb_cb_ret { + uint32_t result; +}; + +static struct dentry *dent; +static uint32_t test_res; +static int reg_cb_num, reg_cb_num_req; +static int data_cb_num, data_cb_num_req; +static int reg_done_flag, data_cb_done_flag; +static DECLARE_WAIT_QUEUE_HEAD(reg_test_wait); +static DECLARE_WAIT_QUEUE_HEAD(data_cb_test_wait); + +enum { + PING_MODEM_NOT_IN_RESET = 0, + PING_MODEM_IN_RESET, + PING_LEAVING_RESET, + PING_MODEM_REGISTER_CB +}; +static int fifo_event; +static DEFINE_MUTEX(event_fifo_lock); +static DEFINE_KFIFO(event_fifo, int, sizeof(int)*16); + +static int ping_mdm_register_cb(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr) +{ + int rc; + uint32_t accept_status; + struct ping_mdm_register_cb_arg arg; + void *cb_func; + + xdr_recv_uint32(xdr, &arg.cb_id); /* cb_id */ + xdr_recv_int32(xdr, &arg.val); /* val */ + + cb_func = msm_rpc_get_cb_func(client, arg.cb_id); + if (cb_func) { + rc = ((int (*)(struct ping_mdm_register_cb_arg *, void *)) + cb_func)(&arg, NULL); + if (rc) + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + else + accept_status = RPC_ACCEPTSTAT_SUCCESS; + } else + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + + xdr_start_accepted_reply(xdr, accept_status); + rc = xdr_send_msg(xdr); + if (rc) + pr_err("%s: send accepted reply failed: %d\n", __func__, rc); + + return rc; +} + +static int ping_mdm_data_cb(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr) +{ + int rc; + void *cb_func; + uint32_t size, accept_status; + struct ping_mdm_register_data_cb_cb_arg arg; + struct ping_mdm_register_data_cb_cb_ret ret; + + xdr_recv_uint32(xdr, &arg.cb_id); /* cb_id */ + + /* data */ + xdr_recv_array(xdr, (void **)(&(arg.data)), &size, 64, + sizeof(uint32_t), (void *)xdr_recv_uint32); + + xdr_recv_uint32(xdr, &arg.size); /* size */ + xdr_recv_uint32(xdr, &arg.sum); /* sum */ + + cb_func = msm_rpc_get_cb_func(client, arg.cb_id); + if (cb_func) { + rc = ((int (*) + (struct ping_mdm_register_data_cb_cb_arg *, + struct ping_mdm_register_data_cb_cb_ret *)) + cb_func)(&arg, &ret); + if (rc) + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + else + accept_status = RPC_ACCEPTSTAT_SUCCESS; + } else + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + + xdr_start_accepted_reply(xdr, accept_status); + + if (accept_status == RPC_ACCEPTSTAT_SUCCESS) + xdr_send_uint32(xdr, &ret.result); /* result */ + + rc = xdr_send_msg(xdr); + if (rc) + pr_err("%s: send accepted reply failed: %d\n", __func__, rc); + + kfree(arg.data); + return rc; +} + +static int ping_mdm_cb_func(struct msm_rpc_client *client, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + int rc = 0; + + switch (req->procedure) { + case PING_MDM_CB_PROC: + rc = ping_mdm_register_cb(client, xdr); + break; + case PING_MDM_DATA_CB_PROC: + rc = ping_mdm_data_cb(client, xdr); + break; + default: + pr_err("%s: procedure not supported %d\n", + __func__, req->procedure); + xdr_start_accepted_reply(xdr, RPC_ACCEPTSTAT_PROC_UNAVAIL); + rc = xdr_send_msg(xdr); + if (rc) + pr_err("%s: sending reply failed: %d\n", __func__, rc); + break; + } + return rc; +} + +struct ping_mdm_unregister_data_cb_arg { + int (*cb_func)( + struct ping_mdm_register_data_cb_cb_arg *arg, + struct ping_mdm_register_data_cb_cb_ret *ret); +}; + +struct ping_mdm_register_data_cb_arg { + int (*cb_func)( + struct ping_mdm_register_data_cb_cb_arg *arg, + struct ping_mdm_register_data_cb_cb_ret *ret); + uint32_t num; + uint32_t size; + uint32_t interval_ms; + uint32_t num_tasks; +}; + +struct ping_mdm_register_data_cb_ret { + uint32_t result; +}; + +struct ping_mdm_unregister_data_cb_ret { + uint32_t result; +}; + +static int ping_mdm_data_cb_register_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_mdm_register_data_cb_arg *arg = data; + int cb_id; + + cb_id = msm_rpc_add_cb_func(client, (void *)arg->cb_func); + if ((cb_id < 0) && (cb_id != MSM_RPC_CLIENT_NULL_CB_ID)) + return cb_id; + + xdr_send_uint32(xdr, &cb_id); /* cb_id */ + xdr_send_uint32(xdr, &arg->num); /* num */ + xdr_send_uint32(xdr, &arg->size); /* size */ + xdr_send_uint32(xdr, &arg->interval_ms); /* interval_ms */ + xdr_send_uint32(xdr, &arg->num_tasks); /* num_tasks */ + + return 0; +} + +static int ping_mdm_data_cb_unregister_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_mdm_unregister_data_cb_arg *arg = data; + int cb_id; + + cb_id = msm_rpc_add_cb_func(client, (void *)arg->cb_func); + if ((cb_id < 0) && (cb_id != MSM_RPC_CLIENT_NULL_CB_ID)) + return cb_id; + + xdr_send_uint32(xdr, &cb_id); /* cb_id */ + + return 0; +} + +static int ping_mdm_data_cb_register_ret(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_mdm_register_data_cb_ret *ret = data; + + xdr_recv_uint32(xdr, &ret->result); /* result */ + + return 0; +} + +static int ping_mdm_register_data_cb( + struct msm_rpc_client *client, + struct ping_mdm_register_data_cb_arg *arg, + struct ping_mdm_register_data_cb_ret *ret) +{ + return msm_rpc_client_req2(client, + PING_MDM_REGISTER_DATA_CB_PROC, + ping_mdm_data_cb_register_arg, arg, + ping_mdm_data_cb_register_ret, ret, -1); +} + +static int ping_mdm_unregister_data_cb( + struct msm_rpc_client *client, + struct ping_mdm_unregister_data_cb_arg *arg, + struct ping_mdm_unregister_data_cb_ret *ret) +{ + return msm_rpc_client_req2(client, + PING_MDM_UNREGISTER_DATA_CB_PROC, + ping_mdm_data_cb_unregister_arg, arg, + ping_mdm_data_cb_register_ret, ret, -1); +} + +struct ping_mdm_data_arg { + uint32_t *data; + uint32_t size; +}; + +struct ping_mdm_data_ret { + uint32_t result; +}; + +static int ping_mdm_data_register_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_mdm_data_arg *arg = data; + + /* data */ + xdr_send_array(xdr, (void **)&arg->data, &arg->size, 64, + sizeof(uint32_t), (void *)xdr_send_uint32); + + xdr_send_uint32(xdr, &arg->size); /* size */ + + return 0; +} + +static int ping_mdm_data_register_ret(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_mdm_data_ret *ret = data; + + xdr_recv_uint32(xdr, &ret->result); /* result */ + + return 0; +} + +static int ping_mdm_data_register( + struct msm_rpc_client *client, + struct ping_mdm_data_arg *arg, + struct ping_mdm_data_ret *ret) +{ + return msm_rpc_client_req2(client, + PING_MDM_REGISTER_DATA_PROC, + ping_mdm_data_register_arg, arg, + ping_mdm_data_register_ret, ret, -1); +} + +struct ping_mdm_register_arg { + int (*cb_func)(struct ping_mdm_register_cb_arg *, void *); + int num; +}; + +struct ping_mdm_unregister_arg { + int (*cb_func)(struct ping_mdm_register_cb_arg *, void *); +}; + +struct ping_mdm_register_ret { + uint32_t result; +}; + +struct ping_mdm_unregister_ret { + uint32_t result; +}; + +static int ping_mdm_register_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_mdm_register_arg *arg = data; + int cb_id; + + cb_id = msm_rpc_add_cb_func(client, (void *)arg->cb_func); + if ((cb_id < 0) && (cb_id != MSM_RPC_CLIENT_NULL_CB_ID)) + return cb_id; + + xdr_send_uint32(xdr, &cb_id); /* cb_id */ + xdr_send_uint32(xdr, &arg->num); /* num */ + + return 0; +} + +static int ping_mdm_unregister_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_mdm_unregister_arg *arg = data; + int cb_id; + + cb_id = msm_rpc_add_cb_func(client, (void *)arg->cb_func); + if ((cb_id < 0) && (cb_id != MSM_RPC_CLIENT_NULL_CB_ID)) + return cb_id; + + xdr_send_uint32(xdr, &cb_id); /* cb_id */ + + return 0; +} + +static int ping_mdm_register_ret(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct ping_mdm_register_ret *ret = data; + + xdr_recv_uint32(xdr, &ret->result); /* result */ + + return 0; +} + +static int ping_mdm_register( + struct msm_rpc_client *client, + struct ping_mdm_register_arg *arg, + struct ping_mdm_register_ret *ret) +{ + return msm_rpc_client_req2(client, + PING_MDM_REGISTER_PROC, + ping_mdm_register_arg, arg, + ping_mdm_register_ret, ret, -1); +} + +static int ping_mdm_unregister( + struct msm_rpc_client *client, + struct ping_mdm_unregister_arg *arg, + struct ping_mdm_unregister_ret *ret) +{ + return msm_rpc_client_req2(client, + PING_MDM_UNREGISTER_PROC, + ping_mdm_unregister_arg, arg, + ping_mdm_register_ret, ret, -1); +} + +static int ping_mdm_null(struct msm_rpc_client *client, + void *arg, void *ret) +{ + return msm_rpc_client_req2(client, PING_MDM_NULL_PROC, + NULL, NULL, NULL, NULL, -1); +} + +static int ping_mdm_close(void) +{ + mutex_lock(&ping_mdm_lock); + if (--open_count == 0) { + msm_rpc_unregister_client(rpc_client); + pr_info("%s: disconnected from remote ping server\n", + __func__); + } + mutex_unlock(&ping_mdm_lock); + return 0; +} + +static void handle_restart_teardown(struct msm_rpc_client *client) +{ + int event = PING_MODEM_IN_RESET; + + pr_info("%s: modem in reset\n", __func__); + + mutex_lock(&event_fifo_lock); + kfifo_in(&event_fifo, &event, sizeof(event)); + fifo_event = 1; + mutex_unlock(&event_fifo_lock); + + wake_up(&data_cb_test_wait); +} + +static void handle_restart_setup(struct msm_rpc_client *client) +{ + int event = PING_LEAVING_RESET; + + pr_info("%s: modem leaving reset\n", __func__); + + mutex_lock(&event_fifo_lock); + kfifo_in(&event_fifo, &event, sizeof(event)); + fifo_event = 1; + mutex_unlock(&event_fifo_lock); + + wake_up(&data_cb_test_wait); +} + +static struct msm_rpc_client *ping_mdm_init(void) +{ + mutex_lock(&ping_mdm_lock); + if (open_count == 0) { + rpc_client = msm_rpc_register_client2("pingdef", + PING_MDM_PROG, + PING_MDM_VERS, 1, + ping_mdm_cb_func); + if (!IS_ERR(rpc_client)) { + open_count++; + msm_rpc_register_reset_callbacks(rpc_client, + handle_restart_teardown, + handle_restart_setup); + } + } + mutex_unlock(&ping_mdm_lock); + return rpc_client; +} + +static int ping_mdm_data_register_test(void) +{ + int i, rc = 0; + uint32_t my_data[64]; + uint32_t my_sum = 0; + struct ping_mdm_data_arg data_arg; + struct ping_mdm_data_ret data_ret; + + for (i = 0; i < 64; i++) { + my_data[i] = (42 + i); + my_sum ^= (42 + i); + } + + data_arg.data = my_data; + data_arg.size = 64; + + rc = ping_mdm_data_register(rpc_client, &data_arg, &data_ret); + if (rc) + return rc; + + if (my_sum != data_ret.result) { + pr_err("%s: sum mismatch %d %d\n", + __func__, my_sum, data_ret.result); + rc = -1; + } + + return rc; +} + +static int ping_mdm_test_register_data_cb( + struct ping_mdm_register_data_cb_cb_arg *arg, + struct ping_mdm_register_data_cb_cb_ret *ret) +{ + uint32_t i, sum = 0; + + data_cb_num++; + + pr_info("%s: received cb_id %d, size = %d, sum = %u, num = %u of %u\n", + __func__, arg->cb_id, arg->size, arg->sum, data_cb_num, + data_cb_num_req); + + if (arg->data) + for (i = 0; i < arg->size; i++) + sum ^= arg->data[i]; + + if (sum != arg->sum) + pr_err("%s: sum mismatch %u %u\n", __func__, sum, arg->sum); + + if (data_cb_num == data_cb_num_req) { + data_cb_done_flag = 1; + wake_up(&data_cb_test_wait); + } + + ret->result = 1; + return 0; +} + +static int ping_mdm_data_cb_register( + struct ping_mdm_register_data_cb_ret *reg_ret) +{ + int rc; + struct ping_mdm_register_data_cb_arg reg_arg; + + reg_arg.cb_func = ping_mdm_test_register_data_cb; + reg_arg.num = data_cb_num_req - data_cb_num; + reg_arg.size = 64; + reg_arg.interval_ms = 10; + reg_arg.num_tasks = 1; + + pr_info("%s: registering callback\n", __func__); + rc = ping_mdm_register_data_cb(rpc_client, ®_arg, reg_ret); + if (rc) + pr_err("%s: failed to register callback %d\n", __func__, rc); + + return rc; +} + + +static void retry_timer_cb(unsigned long data) +{ + int event = (int)data; + + pr_info("%s: retry timer triggered\n", __func__); + + mutex_lock(&event_fifo_lock); + kfifo_in(&event_fifo, &event, sizeof(event)); + fifo_event = 1; + mutex_unlock(&event_fifo_lock); + + wake_up(&data_cb_test_wait); +} + +static int ping_mdm_data_cb_register_test(void) +{ + int rc; + int event; + int retry_count = 0; + struct ping_mdm_register_data_cb_ret reg_ret; + struct ping_mdm_unregister_data_cb_arg unreg_arg; + struct ping_mdm_unregister_data_cb_ret unreg_ret; + struct timer_list retry_timer; + + mutex_init(&event_fifo_lock); + init_timer(&retry_timer); + + data_cb_done_flag = 0; + data_cb_num = 0; + if (!data_cb_num_req) + data_cb_num_req = 10; + + rc = ping_mdm_data_cb_register(®_ret); + if (rc) + return rc; + + pr_info("%s: data_cb_register result: 0x%x\n", + __func__, reg_ret.result); + + while (!data_cb_done_flag) { + wait_event(data_cb_test_wait, data_cb_done_flag || fifo_event); + fifo_event = 0; + + for (;;) { + mutex_lock(&event_fifo_lock); + + if (kfifo_is_empty(&event_fifo)) { + mutex_unlock(&event_fifo_lock); + break; + } + rc = kfifo_out(&event_fifo, &event, sizeof(event)); + mutex_unlock(&event_fifo_lock); + BUG_ON(rc != sizeof(event)); + + pr_info("%s: processing event data_cb_done_flag=%d,event=%d\n", + __func__, data_cb_done_flag, event); + + if (event == PING_MODEM_IN_RESET) { + pr_info("%s: modem entering reset\n", __func__); + retry_count = 0; + } else if (event == PING_LEAVING_RESET) { + pr_info("%s: modem exiting reset - " + "re-registering cb\n", __func__); + + rc = ping_mdm_data_cb_register(®_ret); + if (rc) { + retry_count++; + if (retry_count < PING_MAX_RETRY) { + pr_info("%s: retry %d failed\n", + __func__, retry_count); + + retry_timer.expires = jiffies + + msecs_to_jiffies(1000); + retry_timer.data = + PING_LEAVING_RESET; + retry_timer.function = + retry_timer_cb; + add_timer(&retry_timer); + } else { + pr_err("%s: max retries exceeded, aborting\n", + __func__); + return -ENETRESET; + } + } else + pr_info("%s: data_cb_register result: 0x%x\n", + __func__, reg_ret.result); + } + } + } + + while (del_timer(&retry_timer)) + ; + + unreg_arg.cb_func = ping_mdm_test_register_data_cb; + rc = ping_mdm_unregister_data_cb(rpc_client, &unreg_arg, &unreg_ret); + if (rc) + return rc; + + pr_info("%s: data_cb_unregister result: 0x%x\n", + __func__, unreg_ret.result); + + pr_info("%s: Test completed\n", __func__); + + return 0; +} + +static int ping_mdm_test_register_cb( + struct ping_mdm_register_cb_arg *arg, void *ret) +{ + pr_info("%s: received cb_id %d, val = %d\n", + __func__, arg->cb_id, arg->val); + + reg_cb_num++; + if (reg_cb_num == reg_cb_num_req) { + reg_done_flag = 1; + wake_up(®_test_wait); + } + return 0; +} + +static int ping_mdm_register_test(void) +{ + int rc = 0; + struct ping_mdm_register_arg reg_arg; + struct ping_mdm_unregister_arg unreg_arg; + struct ping_mdm_register_ret reg_ret; + struct ping_mdm_unregister_ret unreg_ret; + + reg_cb_num = 0; + reg_cb_num_req = 10; + reg_done_flag = 0; + + reg_arg.num = 10; + reg_arg.cb_func = ping_mdm_test_register_cb; + + rc = ping_mdm_register(rpc_client, ®_arg, ®_ret); + if (rc) + return rc; + + pr_info("%s: register result: 0x%x\n", + __func__, reg_ret.result); + + wait_event(reg_test_wait, reg_done_flag); + + unreg_arg.cb_func = ping_mdm_test_register_cb; + rc = ping_mdm_unregister(rpc_client, &unreg_arg, &unreg_ret); + if (rc) + return rc; + + pr_info("%s: unregister result: 0x%x\n", + __func__, unreg_ret.result); + + return 0; +} + +static int ping_mdm_null_test(void) +{ + return ping_mdm_null(rpc_client, NULL, NULL); +} + +static int ping_test_release(struct inode *ip, struct file *fp) +{ + return ping_mdm_close(); +} + +static int ping_test_open(struct inode *ip, struct file *fp) +{ + struct msm_rpc_client *client; + + client = ping_mdm_init(); + if (IS_ERR(client)) { + pr_err("%s: couldn't open ping client\n", __func__); + return PTR_ERR(client); + } else + pr_info("%s: connected to remote ping server\n", + __func__); + + return 0; +} + +static ssize_t ping_test_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + char _buf[16]; + + snprintf(_buf, sizeof(_buf), "%i\n", test_res); + + return simple_read_from_buffer(buf, count, pos, _buf, strlen(_buf)); +} + +static ssize_t ping_test_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + unsigned char cmd[64]; + int len; + + if (count < 1) + return 0; + + len = count > 63 ? 63 : count; + + if (copy_from_user(cmd, buf, len)) + return -EFAULT; + + cmd[len] = 0; + + /* lazy */ + if (cmd[len-1] == '\n') { + cmd[len-1] = 0; + len--; + } + + if (!strncmp(cmd, "null_test", 64)) + test_res = ping_mdm_null_test(); + else if (!strncmp(cmd, "reg_test", 64)) + test_res = ping_mdm_register_test(); + else if (!strncmp(cmd, "data_reg_test", 64)) + test_res = ping_mdm_data_register_test(); + else if (!strncmp(cmd, "data_cb_reg_test", 64)) + test_res = ping_mdm_data_cb_register_test(); + else if (!strncmp(cmd, "count=", 6)) { + long tmp; + + if (strict_strtol(cmd + 6, 0, &tmp) == 0) { + data_cb_num_req = tmp; + pr_info("Set repetition count to %d\n", + data_cb_num_req); + } else { + data_cb_num_req = 10; + pr_err("invalid number %s, defaulting to %d\n", + cmd + 6, data_cb_num_req); + } + } + else + test_res = -EINVAL; + + return count; +} + +static const struct file_operations debug_ops = { + .owner = THIS_MODULE, + .open = ping_test_open, + .read = ping_test_read, + .write = ping_test_write, + .release = ping_test_release, +}; + +static void __exit ping_test_exit(void) +{ + debugfs_remove(dent); +} + +static int __init ping_test_init(void) +{ + dent = debugfs_create_file("ping_mdm", 0444, 0, NULL, &debug_ops); + test_res = 0; + open_count = 0; + return 0; +} + +module_init(ping_test_init); +module_exit(ping_test_exit); + +MODULE_DESCRIPTION("PING TEST Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/platsmp.c b/arch/arm/mach-msm/platsmp.c index 2034098cf01..c2008d6c72e 100644 --- a/arch/arm/mach-msm/platsmp.c +++ b/arch/arm/mach-msm/platsmp.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2002 ARM Ltd. * All Rights Reserved - * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2010-2011, Code Aurora Forum. 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 @@ -9,160 +9,167 @@ */ #include -#include +#include #include -#include -#include -#include +#include #include #include #include +#include #include +#include +#include +#include #include +#include "pm.h" #include "scm-boot.h" +#include "acpuclock.h" -#define VDD_SC1_ARRAY_CLAMP_GFS_CTL 0x15A0 -#define SCSS_CPU1CORE_RESET 0xD80 -#define SCSS_DBG_STATUS_CORE_PWRDUP 0xE64 +#define SECONDARY_CPU_WAIT_MS 10 -/* Mask for edge trigger PPIs except AVS_SVICINT and AVS_SVICINTSWDONE */ -#define GIC_PPI_EDGE_MASK 0xFFFFD7FF +int pen_release = -1; -extern void msm_secondary_startup(void); -/* - * control for which core is the next to come out of the secondary - * boot "holding pen". - */ -volatile int pen_release = -1; +/* Initialize the present map (cpu_set(i, cpu_present_map)). */ +void __init platform_smp_prepare_cpus(unsigned int max_cpus) +{ + int i; -static DEFINE_SPINLOCK(boot_lock); + for (i = 0; i < max_cpus; i++) + cpu_set(i, cpu_present_map); +} -void __cpuinit platform_secondary_init(unsigned int cpu) +void __init smp_init_cpus(void) { - /* Configure edge-triggered PPIs */ - writel(GIC_PPI_EDGE_MASK, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); + unsigned int i, ncores = get_core_count(); - /* - * if any interrupts are already enabled for the primary - * core (e.g. timer irq), then they will not have been enabled - * for us: do so - */ - gic_secondary_init(0); - - /* - * let the primary processor know we're out of the - * pen, then head off into the C entry point - */ - pen_release = -1; - smp_wmb(); + for (i = 0; i < ncores; i++) + cpu_set(i, cpu_possible_map); - /* - * Synchronise with the boot thread. - */ - spin_lock(&boot_lock); - spin_unlock(&boot_lock); + set_smp_cross_call(gic_raise_softirq); } -static __cpuinit void prepare_cold_cpu(unsigned int cpu) +static void __cpuinit release_secondary(unsigned int cpu) { - int ret; - ret = scm_set_boot_addr(virt_to_phys(msm_secondary_startup), - SCM_FLAG_COLDBOOT_CPU1); - if (ret == 0) { - void *sc1_base_ptr; - sc1_base_ptr = ioremap_nocache(0x00902000, SZ_4K*2); - if (sc1_base_ptr) { - writel(0, sc1_base_ptr + VDD_SC1_ARRAY_CLAMP_GFS_CTL); - writel(0, sc1_base_ptr + SCSS_CPU1CORE_RESET); - writel(3, sc1_base_ptr + SCSS_DBG_STATUS_CORE_PWRDUP); - iounmap(sc1_base_ptr); + void *base_ptr; + + BUG_ON(cpu >= get_core_count()); + + /* KraitMP or ScorpionMP ? */ + if ((read_cpuid_id() & 0xFF0) >> 4 != 0x2D) { + base_ptr = ioremap_nocache(0x02098000, SZ_4K); + if (base_ptr) { + if (machine_is_msm8960_sim() || + machine_is_msm8960_rumi3()) { + writel_relaxed(0x10, base_ptr+0x04); + writel_relaxed(0x80, base_ptr+0x04); + } else if (get_core_count() == 2) { + writel_relaxed(0x109, base_ptr+0x04); + writel_relaxed(0x101, base_ptr+0x04); + ndelay(300); + + writel_relaxed(0x121, base_ptr+0x04); + udelay(2); + + writel_relaxed(0x020, base_ptr+0x04); + udelay(2); + + writel_relaxed(0x000, base_ptr+0x04); + udelay(100); + + writel_relaxed(0x080, base_ptr+0x04); + } + mb(); + iounmap(base_ptr); } - } else - printk(KERN_DEBUG "Failed to set secondary core boot " - "address\n"); + } else { + base_ptr = ioremap_nocache(0x00902000, SZ_4K*2); + if (base_ptr) { + writel_relaxed(0x0, base_ptr+0x15A0); + dmb(); + writel_relaxed(0x0, base_ptr+0xD80); + writel_relaxed(0x3, base_ptr+0xE64); + mb(); + iounmap(base_ptr); + } + } } +/* Executed by primary CPU, brings other CPUs out of reset. Called at boot + as well as when a CPU is coming out of shutdown induced by echo 0 > + /sys/devices/.../cpuX. +*/ int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle) { - unsigned long timeout; static int cold_boot_done; + int cnt = 0; + int ret; + + pr_debug("Starting secondary CPU %d\n", cpu); + + /* Set preset_lpj to avoid subsequent lpj recalculations */ + preset_lpj = loops_per_jiffy; - /* Only need to bring cpu out of reset this way once */ if (cold_boot_done == false) { - prepare_cold_cpu(cpu); + ret = scm_set_boot_addr((void *) + virt_to_phys(msm_secondary_startup), + SCM_FLAG_COLDBOOT_CPU1); + if (ret == 0) + release_secondary(cpu); + else + printk(KERN_DEBUG "Failed to set secondary core boot " + "address\n"); cold_boot_done = true; } - /* - * set synchronisation state between this boot processor - * and the secondary one - */ - spin_lock(&boot_lock); - - /* - * The secondary processor is waiting to be released from - * the holding pen - release it, then wait for it to flag - * that it has been released by resetting pen_release. - * - * Note that "pen_release" is the hardware CPU ID, whereas - * "cpu" is Linux's internal ID. - */ pen_release = cpu; - __cpuc_flush_dcache_area((void *)&pen_release, sizeof(pen_release)); - outer_clean_range(__pa(&pen_release), __pa(&pen_release + 1)); + dmac_flush_range((void *)&pen_release, + (void *)(&pen_release + sizeof(pen_release))); + __asm__("sev"); + mb(); - /* - * Send the secondary CPU a soft interrupt, thereby causing - * the boot monitor to read the system wide flags register, - * and branch to the address found there. + /* Use smp_cross_call() to send a soft interrupt to wake up + * the other core. */ gic_raise_softirq(cpumask_of(cpu), 1); - timeout = jiffies + (1 * HZ); - while (time_before(jiffies, timeout)) { - smp_rmb(); - if (pen_release == -1) + while (pen_release != 0xFFFFFFFF) { + dmac_inv_range((void *)&pen_release, + (void *)(&pen_release+sizeof(pen_release))); + msleep_interruptible(1); + if (cnt++ >= SECONDARY_CPU_WAIT_MS) break; - - udelay(10); } - /* - * now the secondary core is starting up let it run its - * calibrations, then wait for it to finish - */ - spin_unlock(&boot_lock); - - return pen_release != -1 ? -ENOSYS : 0; + return 0; } -/* - * Initialise the CPU possible map early - this describes the CPUs - * which may be present or become present in the system. The msm8x60 - * does not support the ARM SCU, so just set the possible cpu mask to - * NR_CPUS. - */ -void __init smp_init_cpus(void) +/* Initialization routine for secondary CPUs after they are brought out of + * reset. +*/ +void __cpuinit platform_secondary_init(unsigned int cpu) { - unsigned int i; + pr_debug("CPU%u: Booted secondary processor\n", cpu); - for (i = 0; i < NR_CPUS; i++) - set_cpu_possible(i, true); +#ifdef CONFIG_HOTPLUG_CPU + WARN_ON(msm_pm_platform_secondary_init(cpu)); +#endif - set_smp_cross_call(gic_raise_softirq); -} + trace_hardirqs_off(); -void __init platform_smp_prepare_cpus(unsigned int max_cpus) -{ - int i; + /* Edge trigger PPIs except AVS_SVICINT and AVS_SVICINTSWDONE */ + writel(0xFFFFD7FF, MSM_QGIC_DIST_BASE + GIC_DIST_CONFIG + 4); - /* - * Initialise the present map, which describes the set of CPUs - * actually populated at the present time. + /* RUMI does not adhere to GIC spec by enabling STIs by default. + * Enable/clear is supposed to be RO for STIs, but is RW on RUMI. */ - for (i = 0; i < max_cpus; i++) - set_cpu_present(i, true); + if (!machine_is_msm8x60_sim()) + writel(0x0000FFFF, MSM_QGIC_DIST_BASE + GIC_DIST_ENABLE_SET); + + gic_secondary_init(0); + + /* Setup acpuclock for non-primary CPU. */ + acpuclock_secondary_init(); } diff --git a/arch/arm/mach-msm/pm-8x60.c b/arch/arm/mach-msm/pm-8x60.c new file mode 100644 index 00000000000..91cbcad1fc0 --- /dev/null +++ b/arch/arm/mach-msm/pm-8x60.c @@ -0,0 +1,1222 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_VFP +#include +#endif + +#include "acpuclock.h" +#include "clock.h" +#include "avs.h" +#include "cpuidle.h" +#include "idle.h" +#include "pm.h" +#include "rpm_resources.h" +#include "scm-boot.h" +#include "spm.h" +#include "timer.h" + +/****************************************************************************** + * Debug Definitions + *****************************************************************************/ + +enum { + MSM_PM_DEBUG_SUSPEND = BIT(0), + MSM_PM_DEBUG_POWER_COLLAPSE = BIT(1), + MSM_PM_DEBUG_SUSPEND_LIMITS = BIT(2), + MSM_PM_DEBUG_CLOCK = BIT(3), + MSM_PM_DEBUG_RESET_VECTOR = BIT(4), + MSM_PM_DEBUG_IDLE = BIT(6), + MSM_PM_DEBUG_IDLE_LIMITS = BIT(7), + MSM_PM_DEBUG_HOTPLUG = BIT(8), +}; + +static int msm_pm_debug_mask = 1; +module_param_named( + debug_mask, msm_pm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); + + +/****************************************************************************** + * Sleep Modes and Parameters + *****************************************************************************/ + +static struct msm_pm_platform_data *msm_pm_modes; +static int rpm_cpu0_wakeup_irq; + +void __init msm_pm_set_platform_data( + struct msm_pm_platform_data *data, int count) +{ + BUG_ON(MSM_PM_SLEEP_MODE_NR * num_possible_cpus() > count); + msm_pm_modes = data; +} + +void __init msm_pm_set_rpm_wakeup_irq(unsigned int irq) +{ + rpm_cpu0_wakeup_irq = irq; +} + +enum { + MSM_PM_MODE_ATTR_SUSPEND, + MSM_PM_MODE_ATTR_IDLE, + MSM_PM_MODE_ATTR_NR, +}; + +static char *msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_NR] = { + [MSM_PM_MODE_ATTR_SUSPEND] = "suspend_enabled", + [MSM_PM_MODE_ATTR_IDLE] = "idle_enabled", +}; + +struct msm_pm_kobj_attribute { + unsigned int cpu; + struct kobj_attribute ka; +}; + +#define GET_CPU_OF_ATTR(attr) \ + (container_of(attr, struct msm_pm_kobj_attribute, ka)->cpu) + +struct msm_pm_sysfs_sleep_mode { + struct kobject *kobj; + struct attribute_group attr_group; + struct attribute *attrs[MSM_PM_MODE_ATTR_NR + 1]; + struct msm_pm_kobj_attribute kas[MSM_PM_MODE_ATTR_NR]; +}; + +static char *msm_pm_sleep_mode_labels[MSM_PM_SLEEP_MODE_NR] = { + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = "power_collapse", + [MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT] = "wfi", + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] = + "standalone_power_collapse", +}; + +/* + * Write out the attribute. + */ +static ssize_t msm_pm_mode_attr_show( + struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int ret = -EINVAL; + int i; + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + struct kernel_param kp; + unsigned int cpu; + struct msm_pm_platform_data *mode; + + if (msm_pm_sleep_mode_labels[i] == NULL) + continue; + + if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i])) + continue; + + cpu = GET_CPU_OF_ATTR(attr); + mode = &msm_pm_modes[MSM_PM_MODE(cpu, i)]; + + if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_SUSPEND])) { + u32 arg = mode->suspend_enabled; + kp.arg = &arg; + ret = param_get_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_IDLE])) { + u32 arg = mode->idle_enabled; + kp.arg = &arg; + ret = param_get_ulong(buf, &kp); + } + + break; + } + + if (ret > 0) { + strcat(buf, "\n"); + ret++; + } + + return ret; +} + +/* + * Read in the new attribute value. + */ +static ssize_t msm_pm_mode_attr_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret = -EINVAL; + int i; + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + struct kernel_param kp; + unsigned int cpu; + struct msm_pm_platform_data *mode; + + if (msm_pm_sleep_mode_labels[i] == NULL) + continue; + + if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i])) + continue; + + cpu = GET_CPU_OF_ATTR(attr); + mode = &msm_pm_modes[MSM_PM_MODE(cpu, i)]; + + if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_SUSPEND])) { + kp.arg = &mode->suspend_enabled; + ret = param_set_byte(buf, &kp); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_IDLE])) { + kp.arg = &mode->idle_enabled; + ret = param_set_byte(buf, &kp); + } + + break; + } + + return ret ? ret : count; +} + +/* + * Add sysfs entries for one cpu. + */ +static int __init msm_pm_mode_sysfs_add_cpu( + unsigned int cpu, struct kobject *modes_kobj) +{ + char cpu_name[8]; + struct kobject *cpu_kobj; + struct msm_pm_sysfs_sleep_mode *mode; + int i, j, k; + int ret; + + snprintf(cpu_name, sizeof(cpu_name), "cpu%u", cpu); + cpu_kobj = kobject_create_and_add(cpu_name, modes_kobj); + if (!cpu_kobj) { + pr_err("%s: cannot create %s kobject\n", __func__, cpu_name); + ret = -ENOMEM; + goto mode_sysfs_add_cpu_exit; + } + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + int idx = MSM_PM_MODE(cpu, i); + + if ((!msm_pm_modes[idx].suspend_supported) + && (!msm_pm_modes[idx].idle_supported)) + continue; + + mode = kzalloc(sizeof(*mode), GFP_KERNEL); + if (!mode) { + pr_err("%s: cannot allocate memory for attributes\n", + __func__); + ret = -ENOMEM; + goto mode_sysfs_add_cpu_exit; + } + + mode->kobj = kobject_create_and_add( + msm_pm_sleep_mode_labels[i], cpu_kobj); + if (!mode->kobj) { + pr_err("%s: cannot create kobject\n", __func__); + ret = -ENOMEM; + goto mode_sysfs_add_cpu_exit; + } + + for (k = 0, j = 0; k < MSM_PM_MODE_ATTR_NR; k++) { + if ((k == MSM_PM_MODE_ATTR_IDLE) && + !msm_pm_modes[idx].idle_supported) + continue; + if ((k == MSM_PM_MODE_ATTR_SUSPEND) && + !msm_pm_modes[idx].suspend_supported) + continue; + mode->kas[j].cpu = cpu; + mode->kas[j].ka.attr.mode = 0644; + mode->kas[j].ka.show = msm_pm_mode_attr_show; + mode->kas[j].ka.store = msm_pm_mode_attr_store; + mode->kas[j].ka.attr.name = msm_pm_mode_attr_labels[k]; + mode->attrs[j] = &mode->kas[j].ka.attr; + j++; + } + mode->attrs[j] = NULL; + + mode->attr_group.attrs = mode->attrs; + ret = sysfs_create_group(mode->kobj, &mode->attr_group); + if (ret) { + pr_err("%s: cannot create kobject attribute group\n", + __func__); + goto mode_sysfs_add_cpu_exit; + } + } + + ret = 0; + +mode_sysfs_add_cpu_exit: + return ret; +} + +/* + * Add sysfs entries for the sleep modes. + */ +static int __init msm_pm_mode_sysfs_add(void) +{ + struct kobject *module_kobj; + struct kobject *modes_kobj; + unsigned int cpu; + int ret; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + pr_err("%s: cannot find kobject for module %s\n", + __func__, KBUILD_MODNAME); + ret = -ENOENT; + goto mode_sysfs_add_exit; + } + + modes_kobj = kobject_create_and_add("modes", module_kobj); + if (!modes_kobj) { + pr_err("%s: cannot create modes kobject\n", __func__); + ret = -ENOMEM; + goto mode_sysfs_add_exit; + } + + for_each_possible_cpu(cpu) { + ret = msm_pm_mode_sysfs_add_cpu(cpu, modes_kobj); + if (ret) + goto mode_sysfs_add_exit; + } + + ret = 0; + +mode_sysfs_add_exit: + return ret; +} + +/****************************************************************************** + * CONFIG_MSM_IDLE_STATS + *****************************************************************************/ + +#ifdef CONFIG_MSM_IDLE_STATS +enum msm_pm_time_stats_id { + MSM_PM_STAT_REQUESTED_IDLE, + MSM_PM_STAT_IDLE_WFI, + MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE, + MSM_PM_STAT_IDLE_POWER_COLLAPSE, + MSM_PM_STAT_SUSPEND, + MSM_PM_STAT_COUNT +}; + +struct msm_pm_time_stats { + const char *name; + int64_t first_bucket_time; + int bucket[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; + int64_t min_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; + int64_t max_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; + int count; + int64_t total_time; +}; + +struct msm_pm_cpu_time_stats { + struct msm_pm_time_stats stats[MSM_PM_STAT_COUNT]; +}; + +static DEFINE_SPINLOCK(msm_pm_stats_lock); +static DEFINE_PER_CPU_SHARED_ALIGNED( + struct msm_pm_cpu_time_stats, msm_pm_stats); + +/* + * Add the given time data to the statistics collection. + */ +static void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t) +{ + unsigned long flags; + struct msm_pm_time_stats *stats; + int64_t bt; + int i; + + spin_lock_irqsave(&msm_pm_stats_lock, flags); + stats = __get_cpu_var(msm_pm_stats).stats; + + stats[id].total_time += t; + stats[id].count++; + + bt = t; + do_div(bt, stats[id].first_bucket_time); + + if (bt < 1ULL << (CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT * + (CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1))) + i = DIV_ROUND_UP(fls((uint32_t)bt), + CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT); + else + i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; + + stats[id].bucket[i]++; + + if (t < stats[id].min_time[i] || !stats[id].max_time[i]) + stats[id].min_time[i] = t; + if (t > stats[id].max_time[i]) + stats[id].max_time[i] = t; + + spin_unlock_irqrestore(&msm_pm_stats_lock, flags); +} + +/* + * Helper function of snprintf where buf is auto-incremented, size is auto- + * decremented, and there is no return value. + * + * NOTE: buf and size must be l-values (e.g. variables) + */ +#define SNPRINTF(buf, size, format, ...) \ + do { \ + if (size > 0) { \ + int ret; \ + ret = snprintf(buf, size, format, ## __VA_ARGS__); \ + if (ret > size) { \ + buf += size; \ + size = 0; \ + } else { \ + buf += ret; \ + size -= ret; \ + } \ + } \ + } while (0) + +/* + * Write out the power management statistics. + */ +static int msm_pm_read_proc + (char *page, char **start, off_t off, int count, int *eof, void *data) +{ + unsigned int cpu = off / MSM_PM_STAT_COUNT; + int id = off % MSM_PM_STAT_COUNT; + char *p = page; + + if (count < 1024) { + *start = (char *) 0; + *eof = 0; + return 0; + } + + if (cpu < num_possible_cpus()) { + unsigned long flags; + struct msm_pm_time_stats *stats; + int i; + int64_t bucket_time; + int64_t s; + uint32_t ns; + + spin_lock_irqsave(&msm_pm_stats_lock, flags); + stats = per_cpu(msm_pm_stats, cpu).stats; + + s = stats[id].total_time; + ns = do_div(s, NSEC_PER_SEC); + SNPRINTF(p, count, + "[cpu %u] %s:\n" + " count: %7d\n" + " total_time: %lld.%09u\n", + cpu, stats[id].name, + stats[id].count, + s, ns); + + bucket_time = stats[id].first_bucket_time; + for (i = 0; i < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; i++) { + s = bucket_time; + ns = do_div(s, NSEC_PER_SEC); + SNPRINTF(p, count, + " <%6lld.%09u: %7d (%lld-%lld)\n", + s, ns, stats[id].bucket[i], + stats[id].min_time[i], + stats[id].max_time[i]); + + bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT; + } + + SNPRINTF(p, count, " >=%6lld.%09u: %7d (%lld-%lld)\n", + s, ns, stats[id].bucket[i], + stats[id].min_time[i], + stats[id].max_time[i]); + + *start = (char *) 1; + *eof = (off + 1 >= MSM_PM_STAT_COUNT * num_possible_cpus()); + + spin_unlock_irqrestore(&msm_pm_stats_lock, flags); + } + + return p - page; +} +#undef SNPRINTF + +#define MSM_PM_STATS_RESET "reset" + +/* + * Reset the power management statistics values. + */ +static int msm_pm_write_proc(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char buf[sizeof(MSM_PM_STATS_RESET)]; + int ret; + unsigned long flags; + unsigned int cpu; + + if (count < strlen(MSM_PM_STATS_RESET)) { + ret = -EINVAL; + goto write_proc_failed; + } + + if (copy_from_user(buf, buffer, strlen(MSM_PM_STATS_RESET))) { + ret = -EFAULT; + goto write_proc_failed; + } + + if (memcmp(buf, MSM_PM_STATS_RESET, strlen(MSM_PM_STATS_RESET))) { + ret = -EINVAL; + goto write_proc_failed; + } + + spin_lock_irqsave(&msm_pm_stats_lock, flags); + for_each_possible_cpu(cpu) { + struct msm_pm_time_stats *stats; + int i; + + stats = per_cpu(msm_pm_stats, cpu).stats; + for (i = 0; i < MSM_PM_STAT_COUNT; i++) { + memset(stats[i].bucket, + 0, sizeof(stats[i].bucket)); + memset(stats[i].min_time, + 0, sizeof(stats[i].min_time)); + memset(stats[i].max_time, + 0, sizeof(stats[i].max_time)); + stats[i].count = 0; + stats[i].total_time = 0; + } + } + + spin_unlock_irqrestore(&msm_pm_stats_lock, flags); + return count; + +write_proc_failed: + return ret; +} +#undef MSM_PM_STATS_RESET +#endif /* CONFIG_MSM_IDLE_STATS */ + + +/****************************************************************************** + * Configure Hardware before/after Low Power Mode + *****************************************************************************/ + +/* + * Configure hardware registers in preparation for Apps power down. + */ +static void msm_pm_config_hw_before_power_down(void) +{ + return; +} + +/* + * Clear hardware registers after Apps powers up. + */ +static void msm_pm_config_hw_after_power_up(void) +{ + return; +} + +/* + * Configure hardware registers in preparation for SWFI. + */ +static void msm_pm_config_hw_before_swfi(void) +{ + return; +} + + +/****************************************************************************** + * Suspend Max Sleep Time + *****************************************************************************/ + +#ifdef CONFIG_MSM_SLEEP_TIME_OVERRIDE +static int msm_pm_sleep_time_override; +module_param_named(sleep_time_override, + msm_pm_sleep_time_override, int, S_IRUGO | S_IWUSR | S_IWGRP); +#endif + +#define SCLK_HZ (32768) +#define MSM_PM_SLEEP_TICK_LIMIT (0x6DDD000) + +static uint32_t msm_pm_max_sleep_time; + +/* + * Convert time from nanoseconds to slow clock ticks, then cap it to the + * specified limit + */ +static int64_t msm_pm_convert_and_cap_time(int64_t time_ns, int64_t limit) +{ + do_div(time_ns, NSEC_PER_SEC / SCLK_HZ); + return (time_ns > limit) ? limit : time_ns; +} + +/* + * Set the sleep time for suspend. 0 means infinite sleep time. + */ +void msm_pm_set_max_sleep_time(int64_t max_sleep_time_ns) +{ + if (max_sleep_time_ns == 0) { + msm_pm_max_sleep_time = 0; + } else { + msm_pm_max_sleep_time = (uint32_t)msm_pm_convert_and_cap_time( + max_sleep_time_ns, MSM_PM_SLEEP_TICK_LIMIT); + + if (msm_pm_max_sleep_time == 0) + msm_pm_max_sleep_time = 1; + } + + if (msm_pm_debug_mask & MSM_PM_DEBUG_SUSPEND) + pr_info("%s: Requested %lld ns Giving %u sclk ticks\n", + __func__, max_sleep_time_ns, msm_pm_max_sleep_time); +} +EXPORT_SYMBOL(msm_pm_set_max_sleep_time); + + +/****************************************************************************** + * + *****************************************************************************/ + +struct msm_pm_device { + unsigned int cpu; +#ifdef CONFIG_HOTPLUG_CPU + struct completion cpu_killed; + unsigned int warm_boot; +#endif +}; + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_pm_device, msm_pm_devices); +static struct msm_rpmrs_limits *msm_pm_idle_rs_limits; + +static void msm_pm_swfi(void) +{ + msm_pm_config_hw_before_swfi(); + msm_arch_idle(); +} + +static void msm_pm_spm_power_collapse( + struct msm_pm_device *dev, bool from_idle, bool notify_rpm) +{ + void *entry; + int collapsed = 0; + int ret; + + if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask) + pr_info("CPU%u: %s: notify_rpm %d\n", + dev->cpu, __func__, (int) notify_rpm); + + ret = msm_spm_set_low_power_mode( + MSM_SPM_MODE_POWER_COLLAPSE, notify_rpm); + WARN_ON(ret); + + entry = (!dev->cpu || from_idle) ? + msm_pm_collapse_exit : msm_secondary_startup; + msm_pm_write_boot_vector(dev->cpu, virt_to_phys(entry)); + + if (MSM_PM_DEBUG_RESET_VECTOR & msm_pm_debug_mask) + pr_info("CPU%u: %s: program vector to %p\n", + dev->cpu, __func__, entry); + +#ifdef CONFIG_VFP + vfp_flush_context(); +#endif + + collapsed = msm_pm_collapse(); + + if (collapsed) { +#ifdef CONFIG_VFP + vfp_reinit(); +#endif + cpu_init(); + writel(0xF0, MSM_QGIC_CPU_BASE + GIC_CPU_PRIMASK); + writel(1, MSM_QGIC_CPU_BASE + GIC_CPU_CTRL); + local_fiq_enable(); + } + + if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask) + pr_info("CPU%u: %s: msm_pm_collapse returned, collapsed %d\n", + dev->cpu, __func__, collapsed); + + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false); + WARN_ON(ret); +} + +static void msm_pm_power_collapse_standalone(bool from_idle) +{ + struct msm_pm_device *dev = &__get_cpu_var(msm_pm_devices); + unsigned int avsdscr_setting; + + avsdscr_setting = avs_get_avsdscr(); + avs_disable(); + msm_pm_spm_power_collapse(dev, from_idle, false); + avs_reset_delays(avsdscr_setting); +} + +static void msm_pm_power_collapse(bool from_idle) +{ + struct msm_pm_device *dev = &__get_cpu_var(msm_pm_devices); + unsigned long saved_acpuclk_rate; + unsigned int avsdscr_setting; + + if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask) + pr_info("CPU%u: %s: idle %d\n", + dev->cpu, __func__, (int)from_idle); + + msm_pm_config_hw_before_power_down(); + if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask) + pr_info("CPU%u: %s: pre power down\n", dev->cpu, __func__); + + avsdscr_setting = avs_get_avsdscr(); + avs_disable(); + + if (cpu_online(dev->cpu)) + saved_acpuclk_rate = acpuclk_power_collapse(); + else + saved_acpuclk_rate = 0; + + if (MSM_PM_DEBUG_CLOCK & msm_pm_debug_mask) + pr_info("CPU%u: %s: change clock rate (old rate = %lu)\n", + dev->cpu, __func__, saved_acpuclk_rate); + + msm_pm_spm_power_collapse(dev, from_idle, true); + + if (MSM_PM_DEBUG_CLOCK & msm_pm_debug_mask) + pr_info("CPU%u: %s: restore clock rate to %lu\n", + dev->cpu, __func__, saved_acpuclk_rate); + if (acpuclk_set_rate(dev->cpu, saved_acpuclk_rate, SETRATE_PC) < 0) + pr_err("CPU%u: %s: failed to restore clock rate(%lu)\n", + dev->cpu, __func__, saved_acpuclk_rate); + + avs_reset_delays(avsdscr_setting); + msm_pm_config_hw_after_power_up(); + if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask) + pr_info("CPU%u: %s: post power up\n", dev->cpu, __func__); + + if (MSM_PM_DEBUG_POWER_COLLAPSE & msm_pm_debug_mask) + pr_info("CPU%u: %s: return\n", dev->cpu, __func__); +} + +static irqreturn_t msm_pm_rpm_wakeup_interrupt(int irq, void *dev_id) +{ + if (dev_id != &msm_pm_rpm_wakeup_interrupt) + return IRQ_NONE; + + return IRQ_HANDLED; +} + + +/****************************************************************************** + * External Idle/Suspend Functions + *****************************************************************************/ + +void arch_idle(void) +{ + return; +} + +int msm_pm_idle_prepare(struct cpuidle_device *dev) +{ + uint32_t latency_us; + uint32_t sleep_us; + int i; + + latency_us = (uint32_t) pm_qos_request(PM_QOS_CPU_DMA_LATENCY); + sleep_us = (uint32_t) ktime_to_ns(tick_nohz_get_sleep_length()); + sleep_us = DIV_ROUND_UP(sleep_us, 1000); + + for (i = 0; i < dev->state_count; i++) { + struct cpuidle_state *state = &dev->states[i]; + enum msm_pm_sleep_mode mode; + bool allow; + struct msm_rpmrs_limits *rs_limits = NULL; + int idx; + + mode = (enum msm_pm_sleep_mode) state->driver_data; + idx = MSM_PM_MODE(dev->cpu, mode); + + allow = msm_pm_modes[idx].idle_enabled && + msm_pm_modes[idx].idle_supported; + + switch (mode) { + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: + if (!allow) + break; + + if (num_online_cpus() > 1) { + allow = false; + break; + } +#ifdef CONFIG_HAS_WAKELOCK + if (has_wake_lock(WAKE_LOCK_IDLE)) { + allow = false; + break; + } +#endif + /* fall through */ + + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE: + if (!allow) + break; + + if (!dev->cpu && + msm_rpm_local_request_is_outstanding()) { + allow = false; + break; + } + /* fall through */ + + case MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT: + if (!allow) + break; + + rs_limits = msm_rpmrs_lowest_limits(true, + mode, latency_us, sleep_us); + + if (MSM_PM_DEBUG_IDLE & msm_pm_debug_mask) + pr_info("CPU%u: %s: %s, latency %uus, " + "sleep %uus, limit %p\n", + dev->cpu, __func__, state->desc, + latency_us, sleep_us, rs_limits); + + if ((MSM_PM_DEBUG_IDLE_LIMITS & msm_pm_debug_mask) && + rs_limits) + pr_info("CPU%u: %s: limit %p: " + "pxo %d, l2_cache %d, " + "vdd_mem %d, vdd_dig %d\n", + dev->cpu, __func__, rs_limits, + rs_limits->pxo, + rs_limits->l2_cache, + rs_limits->vdd_mem, + rs_limits->vdd_dig); + + if (!rs_limits) + allow = false; + break; + + default: + allow = false; + break; + } + + if (MSM_PM_DEBUG_IDLE & msm_pm_debug_mask) + pr_info("CPU%u: %s: allow %s: %d\n", + dev->cpu, __func__, state->desc, (int)allow); + + if (allow) { + state->flags &= ~CPUIDLE_FLAG_IGNORE; + state->target_residency = 0; + state->exit_latency = 0; + state->power_usage = rs_limits->power[dev->cpu]; + + if (MSM_PM_SLEEP_MODE_POWER_COLLAPSE == mode) + msm_pm_idle_rs_limits = rs_limits; + } else { + state->flags |= CPUIDLE_FLAG_IGNORE; + } + } + + return 0; +} + +int msm_pm_idle_enter(enum msm_pm_sleep_mode sleep_mode) +{ + int64_t time; +#ifdef CONFIG_MSM_IDLE_STATS + int exit_stat; +#endif + + if (MSM_PM_DEBUG_IDLE & msm_pm_debug_mask) + pr_info("CPU%u: %s: mode %d\n", + smp_processor_id(), __func__, sleep_mode); + + time = ktime_to_ns(ktime_get()); + + switch (sleep_mode) { + case MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT: + msm_pm_swfi(); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_WFI; +#endif + break; + + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE: + msm_pm_power_collapse_standalone(true); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE; +#endif + break; + + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: { + int64_t timer_expiration = msm_timer_enter_idle(); + bool timer_halted = false; + uint32_t sleep_delay; + int ret; + int notify_rpm = + (sleep_mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE); + + sleep_delay = (uint32_t) msm_pm_convert_and_cap_time( + timer_expiration, MSM_PM_SLEEP_TICK_LIMIT); + if (sleep_delay == 0) /* 0 would mean infinite time */ + sleep_delay = 1; + + ret = msm_rpmrs_enter_sleep( + sleep_delay, msm_pm_idle_rs_limits, true, notify_rpm); + if (!ret) { + msm_pm_power_collapse(true); + timer_halted = true; + + msm_rpmrs_exit_sleep(msm_pm_idle_rs_limits, true, + notify_rpm); + } + + msm_timer_exit_idle((int) timer_halted); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_POWER_COLLAPSE; +#endif + break; + } + + default: + __WARN(); + goto cpuidle_enter_bail; + } + + time = ktime_to_ns(ktime_get()) - time; +#ifdef CONFIG_MSM_IDLE_STATS + msm_pm_add_stat(exit_stat, time); +#endif + + do_div(time, 1000); + return (int) time; + +cpuidle_enter_bail: + return 0; +} + +static int msm_pm_enter(suspend_state_t state) +{ + bool allow[MSM_PM_SLEEP_MODE_NR]; + int i; + +#ifdef CONFIG_MSM_IDLE_STATS + int64_t period = 0; + int64_t time = msm_timer_get_sclk_time(&period); +#endif + + if (MSM_PM_DEBUG_SUSPEND & msm_pm_debug_mask) + pr_info("%s\n", __func__); + + if (smp_processor_id()) { + __WARN(); + goto enter_exit; + } + + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + struct msm_pm_platform_data *mode; + + mode = &msm_pm_modes[MSM_PM_MODE(0, i)]; + allow[i] = mode->suspend_supported && mode->suspend_enabled; + } + + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]) { + struct msm_rpmrs_limits *rs_limits; + int ret; + + if (MSM_PM_DEBUG_SUSPEND & msm_pm_debug_mask) + pr_info("%s: power collapse\n", __func__); + + clock_debug_print_enabled(); + +#ifdef CONFIG_MSM_SLEEP_TIME_OVERRIDE + if (msm_pm_sleep_time_override > 0) { + int64_t ns = NSEC_PER_SEC * + (int64_t) msm_pm_sleep_time_override; + msm_pm_set_max_sleep_time(ns); + msm_pm_sleep_time_override = 0; + } +#endif /* CONFIG_MSM_SLEEP_TIME_OVERRIDE */ + + if (MSM_PM_DEBUG_SUSPEND_LIMITS & msm_pm_debug_mask) + msm_rpmrs_show_resources(); + + rs_limits = msm_rpmrs_lowest_limits(false, + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, -1, -1); + + if ((MSM_PM_DEBUG_SUSPEND_LIMITS & msm_pm_debug_mask) && + rs_limits) + pr_info("%s: limit %p: pxo %d, l2_cache %d, " + "vdd_mem %d, vdd_dig %d\n", + __func__, rs_limits, + rs_limits->pxo, rs_limits->l2_cache, + rs_limits->vdd_mem, rs_limits->vdd_dig); + + if (rs_limits) { + ret = msm_rpmrs_enter_sleep( + msm_pm_max_sleep_time, rs_limits, false, true); + if (!ret) { + msm_pm_power_collapse(false); + msm_rpmrs_exit_sleep(rs_limits, false, true); + } + } else { + pr_err("%s: cannot find the lowest power limit\n", + __func__); + } + +#ifdef CONFIG_MSM_IDLE_STATS + if (time != 0) { + int64_t end_time = msm_timer_get_sclk_time(NULL); + if (end_time != 0) { + time = end_time - time; + if (time < 0) + time += period; + } else + time = 0; + } + + msm_pm_add_stat(MSM_PM_STAT_SUSPEND, time); +#endif /* CONFIG_MSM_IDLE_STATS */ + } else if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) { + if (MSM_PM_DEBUG_SUSPEND & msm_pm_debug_mask) + pr_info("%s: standalone power collapse\n", __func__); + msm_pm_power_collapse_standalone(false); + } else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) { + if (MSM_PM_DEBUG_SUSPEND & msm_pm_debug_mask) + pr_info("%s: swfi\n", __func__); + msm_pm_swfi(); + } + + +enter_exit: + if (MSM_PM_DEBUG_SUSPEND & msm_pm_debug_mask) + pr_info("%s: return\n", __func__); + + return 0; +} + +static struct platform_suspend_ops msm_pm_ops = { + .enter = msm_pm_enter, + .valid = suspend_valid_only_mem, +}; + +#ifdef CONFIG_HOTPLUG_CPU +int platform_cpu_disable(unsigned int cpu) +{ + return cpu == 0 ? -EPERM : 0; +} + +int platform_cpu_kill(unsigned int cpu) +{ + struct completion *killed = &per_cpu(msm_pm_devices, cpu).cpu_killed; + return wait_for_completion_timeout(killed, HZ * 5); +} + +void platform_cpu_die(unsigned int cpu) +{ + bool allow[MSM_PM_SLEEP_MODE_NR]; + int i; + + if (unlikely(cpu != smp_processor_id())) { + pr_crit("%s: running on %u, should be %u\n", + __func__, smp_processor_id(), cpu); + BUG(); + } + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + struct msm_pm_platform_data *mode; + + mode = &msm_pm_modes[MSM_PM_MODE(cpu, i)]; + allow[i] = mode->suspend_supported && mode->suspend_enabled; + } + + if (MSM_PM_DEBUG_HOTPLUG & msm_pm_debug_mask) + pr_notice("CPU%u: %s: shutting down cpu\n", cpu, __func__); + complete(&__get_cpu_var(msm_pm_devices).cpu_killed); + + flush_cache_all(); + + for (;;) { + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]) + msm_pm_power_collapse(false); + else if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) + msm_pm_power_collapse_standalone(false); + else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) + msm_pm_swfi(); + + if (pen_release == cpu) { + /* OK, proper wakeup, we're done */ + break; + } + } + + pen_release = -1; + pr_notice("CPU%u: %s: normal wakeup\n", cpu, __func__); +} + +int msm_pm_platform_secondary_init(unsigned int cpu) +{ + int ret; + struct msm_pm_device *dev = &__get_cpu_var(msm_pm_devices); + + if (!dev->warm_boot) { + dev->warm_boot = 1; + return 0; + } +#ifdef CONFIG_VFP + vfp_reinit(); +#endif + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false); + + return ret; +} +#endif /* CONFIG_HOTPLUG_CPU */ + +/****************************************************************************** + * Initialization routine + *****************************************************************************/ + +static int __init msm_pm_init(void) +{ + pgd_t *pc_pgd; + pmd_t *pmd; + unsigned long pmdval; + unsigned int cpu; +#ifdef CONFIG_MSM_IDLE_STATS + struct proc_dir_entry *d_entry; +#endif + int ret; + + /* Page table for cores to come back up safely. */ + pc_pgd = pgd_alloc(&init_mm); + if (!pc_pgd) + return -ENOMEM; + + pmd = pmd_offset(pc_pgd + + pgd_index(virt_to_phys(msm_pm_collapse_exit)), + virt_to_phys(msm_pm_collapse_exit)); + pmdval = (virt_to_phys(msm_pm_collapse_exit) & PGDIR_MASK) | + PMD_TYPE_SECT | PMD_SECT_AP_WRITE; + pmd[0] = __pmd(pmdval); + pmd[1] = __pmd(pmdval + (1 << (PGDIR_SHIFT - 1))); + + /* It is remotely possible that the code in msm_pm_collapse_exit() + * which turns on the MMU with this mapping is in the + * next even-numbered megabyte beyond the + * start of msm_pm_collapse_exit(). + * Map this megabyte in as well. + */ + pmd[2] = __pmd(pmdval + (2 << (PGDIR_SHIFT - 1))); + flush_pmd_entry(pmd); + msm_pm_pc_pgd = virt_to_phys(pc_pgd); + + ret = request_irq(rpm_cpu0_wakeup_irq, + msm_pm_rpm_wakeup_interrupt, IRQF_TRIGGER_RISING, + "pm_drv", msm_pm_rpm_wakeup_interrupt); + if (ret) { + pr_err("%s: failed to request irq %u: %d\n", + __func__, rpm_cpu0_wakeup_irq, ret); + return ret; + } + + ret = irq_set_irq_wake(rpm_cpu0_wakeup_irq, 1); + if (ret) { + pr_err("%s: failed to set wakeup irq %u: %d\n", + __func__, rpm_cpu0_wakeup_irq, ret); + return ret; + } + + for_each_possible_cpu(cpu) { + struct msm_pm_device *dev = &per_cpu(msm_pm_devices, cpu); + + dev->cpu = cpu; +#ifdef CONFIG_HOTPLUG_CPU + init_completion(&dev->cpu_killed); +#endif + } + + ret = scm_set_boot_addr((void *)virt_to_phys(msm_pm_boot_entry), + SCM_FLAG_WARMBOOT_CPU0 | SCM_FLAG_WARMBOOT_CPU1); + if (ret) { + pr_err("%s: failed to set up scm boot addr: %d\n", + __func__, ret); + return ret; + } + +#ifdef CONFIG_MSM_IDLE_STATS + for_each_possible_cpu(cpu) { + struct msm_pm_time_stats *stats = + per_cpu(msm_pm_stats, cpu).stats; + + stats[MSM_PM_STAT_REQUESTED_IDLE].name = "idle-request"; + stats[MSM_PM_STAT_REQUESTED_IDLE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; + + stats[MSM_PM_STAT_IDLE_WFI].name = "idle-wfi"; + stats[MSM_PM_STAT_IDLE_WFI].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; + + stats[MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE].name = + "idle-standalone-power-collapse"; + stats[MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE]. + first_bucket_time = CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; + + stats[MSM_PM_STAT_IDLE_POWER_COLLAPSE].name = + "idle-power-collapse"; + stats[MSM_PM_STAT_IDLE_POWER_COLLAPSE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; + + stats[MSM_PM_STAT_SUSPEND].name = "suspend"; + stats[MSM_PM_STAT_SUSPEND].first_bucket_time = + CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET; + } + + d_entry = create_proc_entry("msm_pm_stats", + S_IRUGO | S_IWUSR | S_IWGRP, NULL); + if (d_entry) { + d_entry->read_proc = msm_pm_read_proc; + d_entry->write_proc = msm_pm_write_proc; + d_entry->data = NULL; + } +#endif /* CONFIG_MSM_IDLE_STATS */ + + msm_pm_mode_sysfs_add(); + msm_spm_allow_x_cpu_set_vdd(false); + + suspend_set_ops(&msm_pm_ops); + msm_cpuidle_init(); + + return 0; +} + +late_initcall(msm_pm_init); diff --git a/arch/arm/mach-msm/pm.c b/arch/arm/mach-msm/pm.c new file mode 100644 index 00000000000..9e19d401b25 --- /dev/null +++ b/arch/arm/mach-msm/pm.c @@ -0,0 +1,919 @@ +/* arch/arm/mach-msm/pm.c + * + * MSM Power Management Routines + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HAS_WAKELOCK +#include +#endif + +#include "smd_private.h" +#include "smd_rpcrouter.h" +#include "acpuclock.h" +#include "clock.h" +#include "proc_comm.h" +#include "idle.h" +#include "irq.h" +#include "gpio.h" +#include "timer.h" +#include "pm.h" + +enum { + MSM_PM_DEBUG_SUSPEND = 1U << 0, + MSM_PM_DEBUG_POWER_COLLAPSE = 1U << 1, + MSM_PM_DEBUG_STATE = 1U << 2, + MSM_PM_DEBUG_CLOCK = 1U << 3, + MSM_PM_DEBUG_RESET_VECTOR = 1U << 4, + MSM_PM_DEBUG_SMSM_STATE = 1U << 5, + MSM_PM_DEBUG_IDLE = 1U << 6, +}; +static int msm_pm_debug_mask; +module_param_named(debug_mask, msm_pm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#ifdef CONFIG_MSM_SLEEP_TIME_OVERRIDE +static int msm_pm_sleep_time_override; +module_param_named(sleep_time_override, + msm_pm_sleep_time_override, int, S_IRUGO | S_IWUSR | S_IWGRP); +#endif + +static int msm_pm_sleep_mode = CONFIG_MSM7X00A_SLEEP_MODE; +module_param_named(sleep_mode, msm_pm_sleep_mode, int, S_IRUGO | S_IWUSR | S_IWGRP); +static int msm_pm_idle_sleep_mode = CONFIG_MSM7X00A_IDLE_SLEEP_MODE; +module_param_named(idle_sleep_mode, msm_pm_idle_sleep_mode, int, S_IRUGO | S_IWUSR | S_IWGRP); +static int msm_pm_idle_sleep_min_time = CONFIG_MSM7X00A_IDLE_SLEEP_MIN_TIME; +module_param_named(idle_sleep_min_time, msm_pm_idle_sleep_min_time, int, S_IRUGO | S_IWUSR | S_IWGRP); +static int msm_pm_idle_spin_time = CONFIG_MSM7X00A_IDLE_SPIN_TIME; +module_param_named(idle_spin_time, msm_pm_idle_spin_time, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define A11S_CLK_SLEEP_EN (MSM_CSR_BASE + 0x11c) +#define A11S_PWRDOWN (MSM_CSR_BASE + 0x440) +#define A11S_STANDBY_CTL (MSM_CSR_BASE + 0x108) +#define A11RAMBACKBIAS (MSM_CSR_BASE + 0x508) + +enum { + SLEEP_LIMIT_NONE = 0, + SLEEP_LIMIT_NO_TCXO_SHUTDOWN = 2 +}; + +static atomic_t msm_pm_init_done = ATOMIC_INIT(0); +struct smsm_interrupt_info_ext { + uint32_t aArm_en_mask; + uint32_t aArm_interrupts_pending; + uint32_t aArm_wakeup_reason; + uint32_t aArm_rpc_prog; + uint32_t aArm_rpc_proc; + char aArm_smd_port_name[20]; + uint32_t aArm_gpio_info; +}; +static struct msm_pm_smem_addr_t { + uint32_t *sleep_delay; + uint32_t *limit_sleep; + struct smsm_interrupt_info *int_info; + struct smsm_interrupt_info_ext *int_info_ext; +} msm_pm_sma; + +static uint32_t *msm_pm_reset_vector; +static uint32_t msm_pm_max_sleep_time; +static struct msm_pm_platform_data *msm_pm_modes; + +#ifdef CONFIG_MSM_IDLE_STATS +enum msm_pm_time_stats_id { + MSM_PM_STAT_REQUESTED_IDLE, + MSM_PM_STAT_IDLE_SPIN, + MSM_PM_STAT_IDLE_WFI, + MSM_PM_STAT_IDLE_SLEEP, + MSM_PM_STAT_IDLE_FAILED_SLEEP, + MSM_PM_STAT_IDLE_POWER_COLLAPSE, + MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE, + MSM_PM_STAT_SUSPEND, + MSM_PM_STAT_FAILED_SUSPEND, + MSM_PM_STAT_NOT_IDLE, + MSM_PM_STAT_COUNT +}; + +static struct msm_pm_time_stats { + const char *name; + int64_t first_bucket_time; + int bucket[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; + int64_t min_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; + int64_t max_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; + int count; + int64_t total_time; +} msm_pm_stats[MSM_PM_STAT_COUNT] = { + [MSM_PM_STAT_REQUESTED_IDLE].name = "idle-request", + [MSM_PM_STAT_REQUESTED_IDLE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_SPIN].name = "idle-spin", + [MSM_PM_STAT_IDLE_SPIN].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_WFI].name = "idle-wfi", + [MSM_PM_STAT_IDLE_WFI].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_SLEEP].name = "idle-sleep", + [MSM_PM_STAT_IDLE_SLEEP].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_FAILED_SLEEP].name = "idle-failed-sleep", + [MSM_PM_STAT_IDLE_FAILED_SLEEP].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_POWER_COLLAPSE].name = "idle-power-collapse", + [MSM_PM_STAT_IDLE_POWER_COLLAPSE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].name = + "idle-failed-power-collapse", + [MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_SUSPEND].name = "suspend", + [MSM_PM_STAT_SUSPEND].first_bucket_time = + CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_FAILED_SUSPEND].name = "failed-suspend", + [MSM_PM_STAT_FAILED_SUSPEND].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_NOT_IDLE].name = "not-idle", + [MSM_PM_STAT_NOT_IDLE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, +}; + +static void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t) +{ + int i; + int64_t bt; + msm_pm_stats[id].total_time += t; + msm_pm_stats[id].count++; + bt = t; + do_div(bt, msm_pm_stats[id].first_bucket_time); + if (bt < 1ULL << (CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT * + (CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1))) + i = DIV_ROUND_UP(fls((uint32_t)bt), + CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT); + else + i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; + msm_pm_stats[id].bucket[i]++; + if (t < msm_pm_stats[id].min_time[i] || !msm_pm_stats[id].max_time[i]) + msm_pm_stats[id].min_time[i] = t; + if (t > msm_pm_stats[id].max_time[i]) + msm_pm_stats[id].max_time[i] = t; +} + +static uint32_t msm_pm_sleep_limit = SLEEP_LIMIT_NONE; +#endif + +static int +msm_pm_wait_state(uint32_t wait_state_all_set, uint32_t wait_state_all_clear, + uint32_t wait_state_any_set, uint32_t wait_state_any_clear) +{ + int i; + uint32_t state; + + for (i = 0; i < 2000000; i++) { + state = smsm_get_state(SMSM_MODEM_STATE); + if (((state & wait_state_all_set) == wait_state_all_set) && + ((~state & wait_state_all_clear) == wait_state_all_clear) && + (wait_state_any_set == 0 || (state & wait_state_any_set) || + wait_state_any_clear == 0 || (state & wait_state_any_clear))) + return 0; + } + printk(KERN_ERR "msm_pm_wait_state(%x, %x, %x, %x) failed %x\n", + wait_state_all_set, wait_state_all_clear, + wait_state_any_set, wait_state_any_clear, state); + return -ETIMEDOUT; +} + +/* + * Respond to timing out waiting for Modem + * + * NOTE: The function never returns. + */ +static void msm_pm_timeout(void) +{ +#if defined(CONFIG_MSM_PM_TIMEOUT_RESET_CHIP) + printk(KERN_EMERG "%s(): resetting chip\n", __func__); + msm_proc_comm(PCOM_RESET_CHIP_IMM, NULL, NULL); +#elif defined(CONFIG_MSM_PM_TIMEOUT_RESET_MODEM) + printk(KERN_EMERG "%s(): resetting modem\n", __func__); + msm_proc_comm_reset_modem_now(); +#elif defined(CONFIG_MSM_PM_TIMEOUT_HALT) + printk(KERN_EMERG "%s(): halting\n", __func__); +#endif + for (;;) + ; +} + +static int msm_sleep(int sleep_mode, uint32_t sleep_delay, + uint32_t sleep_limit, int from_idle) +{ + uint32_t saved_vector[2]; + int collapsed; + uint32_t enter_state; + uint32_t enter_wait_set = 0; + uint32_t enter_wait_clear = 0; + uint32_t exit_state; + uint32_t exit_wait_clear = 0; + uint32_t exit_wait_set = 0; + unsigned long pm_saved_acpu_clk_rate = 0; + int ret; + int rv = -EINTR; + + if (msm_pm_debug_mask & MSM_PM_DEBUG_SUSPEND) + printk(KERN_INFO "msm_sleep(): " + "mode %d delay %u limit %u idle %d\n", + sleep_mode, sleep_delay, sleep_limit, from_idle); + + switch (sleep_mode) { + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: + enter_state = SMSM_PWRC; + enter_wait_set = SMSM_RSA; + exit_state = SMSM_WFPI; + exit_wait_clear = SMSM_RSA; + break; + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND: + enter_state = SMSM_PWRC_SUSPEND; + enter_wait_set = SMSM_RSA; + exit_state = SMSM_WFPI; + exit_wait_clear = SMSM_RSA; + break; + case MSM_PM_SLEEP_MODE_APPS_SLEEP: + enter_state = SMSM_SLEEP; + exit_state = SMSM_SLEEPEXIT; + exit_wait_set = SMSM_SLEEPEXIT; + break; + default: + enter_state = 0; + exit_state = 0; + } + + if (enter_state && !(smsm_get_state(SMSM_MODEM_STATE) & SMSM_RUN)) { + if ((MSM_PM_DEBUG_POWER_COLLAPSE | MSM_PM_DEBUG_SUSPEND) & + msm_pm_debug_mask) + printk(KERN_INFO "msm_sleep(): modem not ready\n"); + rv = -EBUSY; + goto check_failed; + } + + memset(msm_pm_sma.int_info, 0, sizeof(*msm_pm_sma.int_info)); + msm_irq_enter_sleep1(!!enter_state, from_idle, + &msm_pm_sma.int_info->aArm_en_mask); + msm_gpio_enter_sleep(from_idle); + + if (enter_state) { + if (sleep_delay == 0 && sleep_mode >= MSM_PM_SLEEP_MODE_APPS_SLEEP) + sleep_delay = 192000*5; /* APPS_SLEEP does not allow infinite timeout */ + + *msm_pm_sma.sleep_delay = sleep_delay; + *msm_pm_sma.limit_sleep = sleep_limit; + ret = smsm_change_state(SMSM_APPS_STATE, SMSM_RUN, enter_state); + if (ret) { + printk(KERN_ERR "msm_sleep(): smsm_change_state %x failed\n", enter_state); + enter_state = 0; + exit_state = 0; + } + ret = msm_pm_wait_state(enter_wait_set, enter_wait_clear, 0, 0); + if (ret) { + printk(KERN_EMERG "msm_sleep(): power collapse entry " + "timed out waiting for Modem's response\n"); + msm_pm_timeout(); + } + } + if (msm_irq_enter_sleep2(!!enter_state, from_idle)) + goto enter_failed; + + if (enter_state) { + __raw_writel(0x1f, A11S_CLK_SLEEP_EN); + __raw_writel(1, A11S_PWRDOWN); + + __raw_writel(0, A11S_STANDBY_CTL); + __raw_writel(0, A11RAMBACKBIAS); + + if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE) + printk(KERN_INFO "msm_sleep(): enter " + "A11S_CLK_SLEEP_EN %x, A11S_PWRDOWN %x, " + "smsm_get_state %x\n", + __raw_readl(A11S_CLK_SLEEP_EN), + __raw_readl(A11S_PWRDOWN), + smsm_get_state(SMSM_MODEM_STATE)); + } + + if (sleep_mode <= MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT) { + pm_saved_acpu_clk_rate = acpuclk_power_collapse(); + if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK) + printk(KERN_INFO "msm_sleep(): %ld enter power collapse" + "\n", pm_saved_acpu_clk_rate); + if (pm_saved_acpu_clk_rate == 0) + goto ramp_down_failed; + } + if (sleep_mode < MSM_PM_SLEEP_MODE_APPS_SLEEP) { + if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE) + smsm_print_sleep_info(*msm_pm_sma.sleep_delay, + *msm_pm_sma.limit_sleep, + msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); + saved_vector[0] = msm_pm_reset_vector[0]; + saved_vector[1] = msm_pm_reset_vector[1]; + msm_pm_reset_vector[0] = 0xE51FF004; /* ldr pc, 4 */ + msm_pm_reset_vector[1] = virt_to_phys(msm_pm_collapse_exit); + if (msm_pm_debug_mask & MSM_PM_DEBUG_RESET_VECTOR) + printk(KERN_INFO "msm_sleep(): vector %x %x -> " + "%x %x\n", saved_vector[0], saved_vector[1], + msm_pm_reset_vector[0], msm_pm_reset_vector[1]); + collapsed = msm_pm_collapse(); + msm_pm_reset_vector[0] = saved_vector[0]; + msm_pm_reset_vector[1] = saved_vector[1]; + if (collapsed) { + cpu_init(); + local_fiq_enable(); + rv = 0; + } + if (msm_pm_debug_mask & MSM_PM_DEBUG_POWER_COLLAPSE) + printk(KERN_INFO "msm_pm_collapse(): returned %d\n", + collapsed); + if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE) + smsm_print_sleep_info(*msm_pm_sma.sleep_delay, + *msm_pm_sma.limit_sleep, + msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); + } else { + msm_arch_idle(); + rv = 0; + } + + if (sleep_mode <= MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT) { + if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK) + printk(KERN_INFO "msm_sleep(): exit power collapse %ld" + "\n", pm_saved_acpu_clk_rate); + if (acpuclk_set_rate(smp_processor_id(), + pm_saved_acpu_clk_rate, SETRATE_PC) < 0) + printk(KERN_ERR "msm_sleep(): clk_set_rate %ld " + "failed\n", pm_saved_acpu_clk_rate); + } + if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE) + printk(KERN_INFO "msm_sleep(): exit A11S_CLK_SLEEP_EN %x, " + "A11S_PWRDOWN %x, smsm_get_state %x\n", + __raw_readl(A11S_CLK_SLEEP_EN), + __raw_readl(A11S_PWRDOWN), + smsm_get_state(SMSM_MODEM_STATE)); +ramp_down_failed: + msm_irq_exit_sleep1(msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); +enter_failed: + if (enter_state) { + __raw_writel(0x00, A11S_CLK_SLEEP_EN); + __raw_writel(0, A11S_PWRDOWN); + smsm_change_state(SMSM_APPS_STATE, enter_state, exit_state); + if (msm_pm_wait_state(exit_wait_set, exit_wait_clear, 0, 0)) { + printk(KERN_EMERG "msm_sleep(): power collapse exit " + "timed out waiting for Modem's response\n"); + msm_pm_timeout(); + } + if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE) + printk(KERN_INFO "msm_sleep(): sleep exit " + "A11S_CLK_SLEEP_EN %x, A11S_PWRDOWN %x, " + "smsm_get_state %x\n", + __raw_readl(A11S_CLK_SLEEP_EN), + __raw_readl(A11S_PWRDOWN), + smsm_get_state(SMSM_MODEM_STATE)); + if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE) + smsm_print_sleep_info(*msm_pm_sma.sleep_delay, + *msm_pm_sma.limit_sleep, + msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); + } + msm_irq_exit_sleep2(msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); + if (enter_state) { + smsm_change_state(SMSM_APPS_STATE, exit_state, SMSM_RUN); + if (msm_pm_debug_mask & MSM_PM_DEBUG_STATE) + printk(KERN_INFO "msm_sleep(): sleep exit " + "A11S_CLK_SLEEP_EN %x, A11S_PWRDOWN %x, " + "smsm_get_state %x\n", + __raw_readl(A11S_CLK_SLEEP_EN), + __raw_readl(A11S_PWRDOWN), + smsm_get_state(SMSM_MODEM_STATE)); + } + msm_irq_exit_sleep3(msm_pm_sma.int_info->aArm_en_mask, + msm_pm_sma.int_info->aArm_wakeup_reason, + msm_pm_sma.int_info->aArm_interrupts_pending); + msm_gpio_exit_sleep(); + smd_sleep_exit(); + +check_failed: + return rv; +} + +void msm_pm_set_max_sleep_time(int64_t max_sleep_time_ns) +{ + int64_t max_sleep_time_bs = max_sleep_time_ns; + + /* Convert from ns -> BS units */ + do_div(max_sleep_time_bs, NSEC_PER_SEC / 32768); + + if (max_sleep_time_bs > 0x6DDD000) + msm_pm_max_sleep_time = (uint32_t) 0x6DDD000; + else + msm_pm_max_sleep_time = (uint32_t) max_sleep_time_bs; + + if (msm_pm_debug_mask & MSM_PM_DEBUG_SUSPEND) + printk(KERN_INFO "%s: Requested %lldns (%lldbs), Giving %ubs\n", + __func__, max_sleep_time_ns, + max_sleep_time_bs, + msm_pm_max_sleep_time); +} +EXPORT_SYMBOL(msm_pm_set_max_sleep_time); + +void arch_idle(void) +{ + int ret; + int spin; + int64_t sleep_time; + int low_power = 0; + struct msm_pm_platform_data *mode; +#ifdef CONFIG_MSM_IDLE_STATS + int64_t t1; + static int64_t t2; + int exit_stat; +#endif + int latency_qos = pm_qos_request(PM_QOS_CPU_DMA_LATENCY); + uint32_t sleep_limit = SLEEP_LIMIT_NONE; + int allow_sleep = + msm_pm_idle_sleep_mode < MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT && +#ifdef CONFIG_HAS_WAKELOCK + !has_wake_lock(WAKE_LOCK_IDLE) && +#endif + msm_irq_idle_sleep_allowed(); + + if (!atomic_read(&msm_pm_init_done)) + return; + + sleep_time = msm_timer_enter_idle(); + +#ifdef CONFIG_MSM_IDLE_STATS + t1 = ktime_to_ns(ktime_get()); + msm_pm_add_stat(MSM_PM_STAT_NOT_IDLE, t1 - t2); + msm_pm_add_stat(MSM_PM_STAT_REQUESTED_IDLE, sleep_time); +#endif + + mode = &msm_pm_modes[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]; + if (mode->latency >= latency_qos) + sleep_limit = SLEEP_LIMIT_NO_TCXO_SHUTDOWN; + + mode = &msm_pm_modes[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]; + if (mode->latency >= latency_qos) + allow_sleep = false; + + mode = &msm_pm_modes[ + MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT]; + if (mode->latency >= latency_qos) { + /* no time even for SWFI */ + while (!msm_irq_pending()) + udelay(1); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif + goto abort_idle; + } + + if (msm_pm_debug_mask & MSM_PM_DEBUG_IDLE) + printk(KERN_INFO "arch_idle: sleep time %llu, allow_sleep %d\n", + sleep_time, allow_sleep); + spin = msm_pm_idle_spin_time >> 10; + while (spin-- > 0) { + if (msm_irq_pending()) { +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif + goto abort_idle; + } + udelay(1); + } + if (sleep_time < msm_pm_idle_sleep_min_time || !allow_sleep) { + unsigned long saved_rate; + saved_rate = acpuclk_wait_for_irq(); + if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK) + printk(KERN_DEBUG "arch_idle: clk %ld -> swfi\n", + saved_rate); + if (saved_rate) { + msm_arch_idle(); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_WFI; +#endif + } else { + while (!msm_irq_pending()) + udelay(1); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif + } + if (msm_pm_debug_mask & MSM_PM_DEBUG_CLOCK) + printk(KERN_DEBUG "msm_sleep: clk swfi -> %ld\n", + saved_rate); + if (saved_rate + && acpuclk_set_rate(smp_processor_id(), + saved_rate, SETRATE_SWFI) < 0) + printk(KERN_ERR "msm_sleep(): clk_set_rate %ld " + "failed\n", saved_rate); + } else { + low_power = 1; + do_div(sleep_time, NSEC_PER_SEC / 32768); + if (sleep_time > 0x6DDD000) { + printk("sleep_time too big %lld\n", sleep_time); + sleep_time = 0x6DDD000; + } + ret = msm_sleep(msm_pm_idle_sleep_mode, sleep_time, + sleep_limit, 1); +#ifdef CONFIG_MSM_IDLE_STATS + switch (msm_pm_idle_sleep_mode) { + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND: + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: + if (ret) + exit_stat = + MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE; + else { + exit_stat = MSM_PM_STAT_IDLE_POWER_COLLAPSE; + msm_pm_sleep_limit = sleep_limit; + } + break; + case MSM_PM_SLEEP_MODE_APPS_SLEEP: + if (ret) + exit_stat = MSM_PM_STAT_IDLE_FAILED_SLEEP; + else + exit_stat = MSM_PM_STAT_IDLE_SLEEP; + break; + default: + exit_stat = MSM_PM_STAT_IDLE_WFI; + } +#endif + } +abort_idle: + msm_timer_exit_idle(low_power); +#ifdef CONFIG_MSM_IDLE_STATS + t2 = ktime_to_ns(ktime_get()); + msm_pm_add_stat(exit_stat, t2 - t1); +#endif +} + +static int msm_pm_enter(suspend_state_t state) +{ + uint32_t sleep_limit = SLEEP_LIMIT_NONE; + int ret; +#ifdef CONFIG_MSM_IDLE_STATS + int64_t period = 0; + int64_t time = 0; + + time = msm_timer_get_sclk_time(&period); +#endif + + clock_debug_print_enabled(); + +#ifdef CONFIG_MSM_SLEEP_TIME_OVERRIDE + if (msm_pm_sleep_time_override > 0) { + int64_t ns = NSEC_PER_SEC * (int64_t)msm_pm_sleep_time_override; + msm_pm_set_max_sleep_time(ns); + msm_pm_sleep_time_override = 0; + } +#endif + + ret = msm_sleep(msm_pm_sleep_mode, + msm_pm_max_sleep_time, sleep_limit, 0); + +#ifdef CONFIG_MSM_IDLE_STATS + if (msm_pm_sleep_mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND || + msm_pm_sleep_mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE) { + enum msm_pm_time_stats_id id; + int64_t end_time; + + if (ret) + id = MSM_PM_STAT_FAILED_SUSPEND; + else { + id = MSM_PM_STAT_SUSPEND; + msm_pm_sleep_limit = sleep_limit; + } + + if (time != 0) { + end_time = msm_timer_get_sclk_time(NULL); + if (end_time != 0) { + time = end_time - time; + if (time < 0) + time += period; + } else + time = 0; + } + + msm_pm_add_stat(id, time); + } +#endif + + return 0; +} + +static struct platform_suspend_ops msm_pm_ops = { + .enter = msm_pm_enter, + .valid = suspend_valid_only_mem, +}; + +static uint32_t restart_reason = 0x776655AA; + +static void msm_pm_power_off(void) +{ + msm_rpcrouter_close(); + msm_proc_comm(PCOM_POWER_DOWN, 0, 0); + for (;;) ; +} + +static void msm_pm_restart(char str, const char *cmd) +{ + msm_rpcrouter_close(); + msm_proc_comm(PCOM_RESET_CHIP, &restart_reason, 0); + + for (;;) ; +} + +static int msm_reboot_call(struct notifier_block *this, unsigned long code, void *_cmd) +{ + if((code == SYS_RESTART) && _cmd) { + char *cmd = _cmd; + if (!strcmp(cmd, "bootloader")) { + restart_reason = 0x77665500; + } else if (!strcmp(cmd, "recovery")) { + restart_reason = 0x77665502; + } else if (!strcmp(cmd, "eraseflash")) { + restart_reason = 0x776655EF; + } else if (!strncmp(cmd, "oem-", 4)) { + unsigned code = simple_strtoul(cmd + 4, 0, 16) & 0xff; + restart_reason = 0x6f656d00 | code; + } else { + restart_reason = 0x77665501; + } + } + return NOTIFY_DONE; +} + +static struct notifier_block msm_reboot_notifier = { + .notifier_call = msm_reboot_call, +}; + +#ifdef CONFIG_MSM_IDLE_STATS +/* + * Helper function of snprintf where buf is auto-incremented, size is auto- + * decremented, and there is no return value. + * + * NOTE: buf and size must be l-values (e.g. variables) + */ +#define SNPRINTF(buf, size, format, ...) \ + do { \ + if (size > 0) { \ + int ret; \ + ret = snprintf(buf, size, format, ## __VA_ARGS__); \ + if (ret > size) { \ + buf += size; \ + size = 0; \ + } else { \ + buf += ret; \ + size -= ret; \ + } \ + } \ + } while (0) + +/* + * Write out the power management statistics. + */ +static int msm_pm_read_proc( + char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int i; + char *p = page; + + if (count < 1024) { + *start = (char *) 0; + *eof = 0; + return 0; + } + + if (!off) { + SNPRINTF(p, count, "Last power collapse voted "); + if (msm_pm_sleep_limit == SLEEP_LIMIT_NONE) + SNPRINTF(p, count, "for TCXO shutdown\n\n"); + else + SNPRINTF(p, count, "against TCXO shutdown\n\n"); + + *start = (char *) 1; + *eof = 0; + } else if (--off < ARRAY_SIZE(msm_pm_stats)) { + int64_t bucket_time; + int64_t s; + uint32_t ns; + + s = msm_pm_stats[off].total_time; + ns = do_div(s, NSEC_PER_SEC); + SNPRINTF(p, count, + "%s:\n" + " count: %7d\n" + " total_time: %lld.%09u\n", + msm_pm_stats[off].name, + msm_pm_stats[off].count, + s, ns); + + bucket_time = msm_pm_stats[off].first_bucket_time; + for (i = 0; i < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; i++) { + s = bucket_time; + ns = do_div(s, NSEC_PER_SEC); + SNPRINTF(p, count, + " <%6lld.%09u: %7d (%lld-%lld)\n", + s, ns, msm_pm_stats[off].bucket[i], + msm_pm_stats[off].min_time[i], + msm_pm_stats[off].max_time[i]); + + bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT; + } + + SNPRINTF(p, count, " >=%6lld.%09u: %7d (%lld-%lld)\n", + s, ns, msm_pm_stats[off].bucket[i], + msm_pm_stats[off].min_time[i], + msm_pm_stats[off].max_time[i]); + + *start = (char *) 1; + *eof = (off + 1 >= ARRAY_SIZE(msm_pm_stats)); + } + + return p - page; +} +#undef SNPRINTF + +#define MSM_PM_STATS_RESET "reset" + +/* + * Reset the power management statistics values. + */ +static int msm_pm_write_proc(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char buf[sizeof(MSM_PM_STATS_RESET)]; + int ret; + unsigned long flags; + int i; + + if (count < strlen(MSM_PM_STATS_RESET)) { + ret = -EINVAL; + goto write_proc_failed; + } + + if (copy_from_user(buf, buffer, strlen(MSM_PM_STATS_RESET))) { + ret = -EFAULT; + goto write_proc_failed; + } + + if (memcmp(buf, MSM_PM_STATS_RESET, strlen(MSM_PM_STATS_RESET))) { + ret = -EINVAL; + goto write_proc_failed; + } + + local_irq_save(flags); + for (i = 0; i < ARRAY_SIZE(msm_pm_stats); i++) { + memset(msm_pm_stats[i].bucket, + 0, sizeof(msm_pm_stats[i].bucket)); + memset(msm_pm_stats[i].min_time, + 0, sizeof(msm_pm_stats[i].min_time)); + memset(msm_pm_stats[i].max_time, + 0, sizeof(msm_pm_stats[i].max_time)); + msm_pm_stats[i].count = 0; + msm_pm_stats[i].total_time = 0; + } + + msm_pm_sleep_limit = SLEEP_LIMIT_NONE; + local_irq_restore(flags); + + return count; + +write_proc_failed: + return ret; +} +#undef MSM_PM_STATS_RESET +#endif /* CONFIG_MSM_IDLE_STATS */ + +static int __init msm_pm_init(void) +{ +#ifdef CONFIG_MSM_IDLE_STATS + struct proc_dir_entry *d_entry; +#endif + int ret; + + pm_power_off = msm_pm_power_off; + arm_pm_restart = msm_pm_restart; + msm_pm_max_sleep_time = 0; + + register_reboot_notifier(&msm_reboot_notifier); + + msm_pm_sma.sleep_delay = smem_alloc(SMEM_SMSM_SLEEP_DELAY, + sizeof(*msm_pm_sma.sleep_delay)); + if (msm_pm_sma.sleep_delay == NULL) { + printk(KERN_ERR "msm_pm_init: failed get SLEEP_DELAY\n"); + return -ENODEV; + } + + msm_pm_sma.limit_sleep = smem_alloc(SMEM_SMSM_LIMIT_SLEEP, + sizeof(*msm_pm_sma.limit_sleep)); + if (msm_pm_sma.limit_sleep == NULL) { + printk(KERN_ERR "msm_pm_init: failed get LIMIT_SLEEP\n"); + return -ENODEV; + } + + msm_pm_sma.int_info_ext = smem_alloc(SMEM_SMSM_INT_INFO, + sizeof(*msm_pm_sma.int_info_ext)); + + if (msm_pm_sma.int_info_ext) + msm_pm_sma.int_info = (struct smsm_interrupt_info *) + msm_pm_sma.int_info_ext; + else + msm_pm_sma.int_info = smem_alloc(SMEM_SMSM_INT_INFO, + sizeof(*msm_pm_sma.int_info)); + + if (msm_pm_sma.int_info == NULL) { + printk(KERN_ERR "msm_pm_init: failed get INT_INFO\n"); + return -ENODEV; + } + +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) + /* The bootloader is responsible for initializing many of Scorpion's + * coprocessor registers for things like cache timing. The state of + * these coprocessor registers is lost on reset, so part of the + * bootloader must be re-executed. Do not overwrite the reset vector + * or bootloader area. + */ + msm_pm_reset_vector = (uint32_t *) PAGE_OFFSET; +#else + msm_pm_reset_vector = ioremap(0, PAGE_SIZE); + if (msm_pm_reset_vector == NULL) { + printk(KERN_ERR "msm_pm_init: failed to map reset vector\n"); + return -ENODEV; + } +#endif /* CONFIG_ARCH_MSM_SCORPION */ + + ret = msm_timer_init_time_sync(msm_pm_timeout); + if (ret) + return ret; + + BUG_ON(msm_pm_modes == NULL); + + atomic_set(&msm_pm_init_done, 1); + suspend_set_ops(&msm_pm_ops); + +#ifdef CONFIG_MSM_IDLE_STATS + d_entry = create_proc_entry("msm_pm_stats", + S_IRUGO | S_IWUSR | S_IWGRP, NULL); + if (d_entry) { + d_entry->read_proc = msm_pm_read_proc; + d_entry->write_proc = msm_pm_write_proc; + d_entry->data = NULL; + } +#endif + + return 0; +} + +void __init msm_pm_set_platform_data( + struct msm_pm_platform_data *data, int count) +{ + BUG_ON(MSM_PM_SLEEP_MODE_NR != count); + msm_pm_modes = data; +} + +late_initcall(msm_pm_init); diff --git a/arch/arm/mach-msm/pm.h b/arch/arm/mach-msm/pm.h new file mode 100644 index 00000000000..ad0bc7d0be0 --- /dev/null +++ b/arch/arm/mach-msm/pm.h @@ -0,0 +1,67 @@ +/* arch/arm/mach-msm/pm.h + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Author: San Mehat + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_PM_H +#define __ARCH_ARM_MACH_MSM_PM_H + +#include +#include + +#ifdef CONFIG_SMP +extern int pen_release; +extern void msm_secondary_startup(void); +#else +#define msm_secondary_startup NULL +#endif + +enum msm_pm_sleep_mode { + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND, + MSM_PM_SLEEP_MODE_POWER_COLLAPSE, + MSM_PM_SLEEP_MODE_APPS_SLEEP, + MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT, + MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT, + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN, + MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE, + MSM_PM_SLEEP_MODE_NR +}; + +#define MSM_PM_MODE(cpu, mode_nr) ((cpu) * MSM_PM_SLEEP_MODE_NR + (mode_nr)) + +struct msm_pm_platform_data { + u8 idle_supported; /* Allow device to enter mode during idle */ + u8 suspend_supported; /* Allow device to enter mode during suspend */ + u8 suspend_enabled; /* enabled for suspend */ + u8 idle_enabled; /* enabled for idle low power */ + u32 latency; /* interrupt latency in microseconds when entering + and exiting the low power mode */ + u32 residency; /* time threshold in microseconds beyond which + staying in the low power mode saves power */ +}; + +void msm_pm_set_platform_data(struct msm_pm_platform_data *data, int count); +int msm_pm_idle_prepare(struct cpuidle_device *dev); +int msm_pm_idle_enter(enum msm_pm_sleep_mode sleep_mode); + +#ifdef CONFIG_PM +void msm_pm_set_rpm_wakeup_irq(unsigned int irq); +int msm_pm_platform_secondary_init(unsigned int cpu); +#else +static inline void msm_pm_set_rpm_wakeup_irq(unsigned int irq) {} +static inline int msm_pm_platform_secondary_init(unsigned int cpu) +{ return -ENOSYS; } +#endif +#endif /* __ARCH_ARM_MACH_MSM_PM_H */ diff --git a/arch/arm/mach-msm/pm2.c b/arch/arm/mach-msm/pm2.c new file mode 100644 index 00000000000..6b24a020df1 --- /dev/null +++ b/arch/arm/mach-msm/pm2.c @@ -0,0 +1,1894 @@ +/* arch/arm/mach-msm/pm2.c + * + * MSM Power Management Routines + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011 Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_HAS_WAKELOCK +#include +#endif +#include +#include +#ifdef CONFIG_CPU_V7 +#include +#include +#endif +#ifdef CONFIG_CACHE_L2X0 +#include +#endif +#ifdef CONFIG_VFP +#include +#endif + +#ifdef CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_DEEP_POWER_DOWN +#include +#endif + +#include "smd_private.h" +#include "smd_rpcrouter.h" +#include "acpuclock.h" +#include "clock.h" +#include "proc_comm.h" +#include "idle.h" +#include "irq.h" +#include "gpio.h" +#include "timer.h" +#include "pm.h" +#include "spm.h" +#include "sirc.h" + +/****************************************************************************** + * Debug Definitions + *****************************************************************************/ + +enum { + MSM_PM_DEBUG_SUSPEND = 1U << 0, + MSM_PM_DEBUG_POWER_COLLAPSE = 1U << 1, + MSM_PM_DEBUG_STATE = 1U << 2, + MSM_PM_DEBUG_CLOCK = 1U << 3, + MSM_PM_DEBUG_RESET_VECTOR = 1U << 4, + MSM_PM_DEBUG_SMSM_STATE = 1U << 5, + MSM_PM_DEBUG_IDLE = 1U << 6, +}; + +static int msm_pm_debug_mask; +module_param_named( + debug_mask, msm_pm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); + +#define MSM_PM_DPRINTK(mask, level, message, ...) \ + do { \ + if ((mask) & msm_pm_debug_mask) \ + printk(level message, ## __VA_ARGS__); \ + } while (0) + +#define MSM_PM_DEBUG_PRINT_STATE(tag) \ + do { \ + MSM_PM_DPRINTK(MSM_PM_DEBUG_STATE, \ + KERN_INFO, "%s: " \ + "APPS_CLK_SLEEP_EN %x, APPS_PWRDOWN %x, " \ + "SMSM_POWER_MASTER_DEM %x, SMSM_MODEM_STATE %x, " \ + "SMSM_APPS_DEM %x\n", \ + tag, \ + __raw_readl(APPS_CLK_SLEEP_EN), \ + __raw_readl(APPS_PWRDOWN), \ + smsm_get_state(SMSM_POWER_MASTER_DEM), \ + smsm_get_state(SMSM_MODEM_STATE), \ + smsm_get_state(SMSM_APPS_DEM)); \ + } while (0) + +#define MSM_PM_DEBUG_PRINT_SLEEP_INFO() \ + do { \ + if (msm_pm_debug_mask & MSM_PM_DEBUG_SMSM_STATE) \ + smsm_print_sleep_info(msm_pm_smem_data->sleep_time, \ + msm_pm_smem_data->resources_used, \ + msm_pm_smem_data->irq_mask, \ + msm_pm_smem_data->wakeup_reason, \ + msm_pm_smem_data->pending_irqs); \ + } while (0) + + +/****************************************************************************** + * Sleep Modes and Parameters + *****************************************************************************/ + +static int msm_pm_sleep_mode = CONFIG_MSM7X00A_SLEEP_MODE; +module_param_named( + sleep_mode, msm_pm_sleep_mode, + int, S_IRUGO | S_IWUSR | S_IWGRP +); + +static int msm_pm_idle_sleep_mode = CONFIG_MSM7X00A_IDLE_SLEEP_MODE; +module_param_named( + idle_sleep_mode, msm_pm_idle_sleep_mode, + int, S_IRUGO | S_IWUSR | S_IWGRP +); + +static int msm_pm_idle_sleep_min_time = CONFIG_MSM7X00A_IDLE_SLEEP_MIN_TIME; +module_param_named( + idle_sleep_min_time, msm_pm_idle_sleep_min_time, + int, S_IRUGO | S_IWUSR | S_IWGRP +); + +enum { + MSM_PM_MODE_ATTR_SUSPEND, + MSM_PM_MODE_ATTR_IDLE, + MSM_PM_MODE_ATTR_LATENCY, + MSM_PM_MODE_ATTR_RESIDENCY, + MSM_PM_MODE_ATTR_NR, +}; + +static char *msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_NR] = { + [MSM_PM_MODE_ATTR_SUSPEND] = "suspend_enabled", + [MSM_PM_MODE_ATTR_IDLE] = "idle_enabled", + [MSM_PM_MODE_ATTR_LATENCY] = "latency", + [MSM_PM_MODE_ATTR_RESIDENCY] = "residency", +}; + +static char *msm_pm_sleep_mode_labels[MSM_PM_SLEEP_MODE_NR] = { + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND] = " ", + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = "power_collapse", + [MSM_PM_SLEEP_MODE_APPS_SLEEP] = "apps_sleep", + [MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT] = + "ramp_down_and_wfi", + [MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT] = "wfi", + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = + "power_collapse_no_xo_shutdown", + [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] = + "standalone_power_collapse", +}; + +static struct msm_pm_platform_data *msm_pm_modes; + +static struct kobject *msm_pm_mode_kobjs[MSM_PM_SLEEP_MODE_NR]; +static struct attribute_group *msm_pm_mode_attr_group[MSM_PM_SLEEP_MODE_NR]; +static struct attribute **msm_pm_mode_attrs[MSM_PM_SLEEP_MODE_NR]; +static struct kobj_attribute *msm_pm_mode_kobj_attrs[MSM_PM_SLEEP_MODE_NR]; + +/* + * Write out the attribute. + */ +static ssize_t msm_pm_mode_attr_show( + struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int ret = -EINVAL; + int i; + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + struct kernel_param kp; + + if (msm_pm_sleep_mode_labels[i] == NULL) + continue; + + if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i])) + continue; + + if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_SUSPEND])) { + u32 arg = msm_pm_modes[i].suspend_enabled; + kp.arg = &arg; + ret = param_get_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_IDLE])) { + u32 arg = msm_pm_modes[i].idle_enabled; + kp.arg = &arg; + ret = param_get_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_LATENCY])) { + kp.arg = &msm_pm_modes[i].latency; + ret = param_get_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_RESIDENCY])) { + kp.arg = &msm_pm_modes[i].residency; + ret = param_get_ulong(buf, &kp); + } + + break; + } + + if (ret > 0) { + strcat(buf, "\n"); + ret++; + } + + return ret; +} + +/* + * Read in the new attribute value. + */ +static ssize_t msm_pm_mode_attr_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret = -EINVAL; + int i; + + for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { + struct kernel_param kp; + + if (msm_pm_sleep_mode_labels[i] == NULL) + continue; + + if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i])) + continue; + + if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_SUSPEND])) { + kp.arg = &msm_pm_modes[i].suspend_enabled; + ret = param_set_byte(buf, &kp); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_IDLE])) { + kp.arg = &msm_pm_modes[i].idle_enabled; + ret = param_set_byte(buf, &kp); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_LATENCY])) { + kp.arg = &msm_pm_modes[i].latency; + ret = param_set_ulong(buf, &kp); + } else if (!strcmp(attr->attr.name, + msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_RESIDENCY])) { + kp.arg = &msm_pm_modes[i].residency; + ret = param_set_ulong(buf, &kp); + } + + break; + } + + return ret ? ret : count; +} + +/* + * Add sysfs entries for the sleep modes. + */ +static int __init msm_pm_mode_sysfs_add(void) +{ + struct kobject *module_kobj = NULL; + struct kobject *modes_kobj = NULL; + + struct kobject *kobj; + struct attribute_group *attr_group; + struct attribute **attrs; + struct kobj_attribute *kobj_attrs; + + int i, j, k; + int ret; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + printk(KERN_ERR "%s: cannot find kobject for module %s\n", + __func__, KBUILD_MODNAME); + ret = -ENOENT; + goto mode_sysfs_add_cleanup; + } + + modes_kobj = kobject_create_and_add("modes", module_kobj); + if (!modes_kobj) { + printk(KERN_ERR "%s: cannot create modes kobject\n", __func__); + ret = -ENOMEM; + goto mode_sysfs_add_cleanup; + } + + for (i = 0; i < ARRAY_SIZE(msm_pm_mode_kobjs); i++) { + if (!msm_pm_modes[i].suspend_supported && + !msm_pm_modes[i].idle_supported) + continue; + + kobj = kobject_create_and_add( + msm_pm_sleep_mode_labels[i], modes_kobj); + attr_group = kzalloc(sizeof(*attr_group), GFP_KERNEL); + attrs = kzalloc(sizeof(*attrs) * (MSM_PM_MODE_ATTR_NR + 1), + GFP_KERNEL); + kobj_attrs = kzalloc(sizeof(*kobj_attrs) * MSM_PM_MODE_ATTR_NR, + GFP_KERNEL); + + if (!kobj || !attr_group || !attrs || !kobj_attrs) { + printk(KERN_ERR + "%s: cannot create kobject or attributes\n", + __func__); + ret = -ENOMEM; + goto mode_sysfs_add_abort; + } + + for (k = 0, j = 0; k < MSM_PM_MODE_ATTR_NR; k++) { + if ((k == MSM_PM_MODE_ATTR_SUSPEND) && + (!msm_pm_modes[i].suspend_supported)) + continue; + if ((k == MSM_PM_MODE_ATTR_IDLE) && + (!msm_pm_modes[i].idle_supported)) + continue; + + kobj_attrs[j].attr.mode = 0644; + kobj_attrs[j].show = msm_pm_mode_attr_show; + kobj_attrs[j].store = msm_pm_mode_attr_store; + kobj_attrs[j].attr.name = msm_pm_mode_attr_labels[k]; + attrs[j] = &kobj_attrs[j].attr; + j++; + } + attrs[j] = NULL; + + attr_group->attrs = attrs; + ret = sysfs_create_group(kobj, attr_group); + if (ret) { + printk(KERN_ERR + "%s: cannot create kobject attribute group\n", + __func__); + goto mode_sysfs_add_abort; + } + + msm_pm_mode_kobjs[i] = kobj; + msm_pm_mode_attr_group[i] = attr_group; + msm_pm_mode_attrs[i] = attrs; + msm_pm_mode_kobj_attrs[i] = kobj_attrs; + } + + return 0; + +mode_sysfs_add_abort: + kfree(kobj_attrs); + kfree(attrs); + kfree(attr_group); + kobject_put(kobj); + +mode_sysfs_add_cleanup: + for (i = ARRAY_SIZE(msm_pm_mode_kobjs) - 1; i >= 0; i--) { + if (!msm_pm_mode_kobjs[i]) + continue; + + sysfs_remove_group( + msm_pm_mode_kobjs[i], msm_pm_mode_attr_group[i]); + + kfree(msm_pm_mode_kobj_attrs[i]); + kfree(msm_pm_mode_attrs[i]); + kfree(msm_pm_mode_attr_group[i]); + kobject_put(msm_pm_mode_kobjs[i]); + } + + return ret; +} + +void __init msm_pm_set_platform_data( + struct msm_pm_platform_data *data, int count) +{ + BUG_ON(MSM_PM_SLEEP_MODE_NR != count); + msm_pm_modes = data; +} + + +/****************************************************************************** + * Sleep Limitations + *****************************************************************************/ +enum { + SLEEP_LIMIT_NONE = 0, + SLEEP_LIMIT_NO_TCXO_SHUTDOWN = 2, + SLEEP_LIMIT_MASK = 0x03, +}; + +#ifdef CONFIG_MSM_MEMORY_LOW_POWER_MODE +enum { + SLEEP_RESOURCE_MEMORY_BIT0 = 0x0200, + SLEEP_RESOURCE_MEMORY_BIT1 = 0x0010, +}; +#endif + + +/****************************************************************************** + * Configure Hardware for Power Down/Up + *****************************************************************************/ + +#if defined(CONFIG_ARCH_MSM7X30) +#define APPS_CLK_SLEEP_EN (MSM_GCC_BASE + 0x020) +#define APPS_PWRDOWN (MSM_ACC_BASE + 0x01c) +#define APPS_SECOP (MSM_TCSR_BASE + 0x038) +#else /* defined(CONFIG_ARCH_MSM7X30) */ +#define APPS_CLK_SLEEP_EN (MSM_CSR_BASE + 0x11c) +#define APPS_PWRDOWN (MSM_CSR_BASE + 0x440) +#define APPS_STANDBY_CTL (MSM_CSR_BASE + 0x108) +#endif /* defined(CONFIG_ARCH_MSM7X30) */ + +/* + * Configure hardware registers in preparation for Apps power down. + */ +static void msm_pm_config_hw_before_power_down(void) +{ +#if defined(CONFIG_ARCH_MSM7X30) + __raw_writel(1, APPS_PWRDOWN); + mb(); + __raw_writel(4, APPS_SECOP); + mb(); +#elif defined(CONFIG_ARCH_MSM7X27) + __raw_writel(0x1f, APPS_CLK_SLEEP_EN); + mb(); + __raw_writel(1, APPS_PWRDOWN); + mb(); +#elif defined(CONFIG_ARCH_MSM7x27A) + __raw_writel(0x7, APPS_CLK_SLEEP_EN); + mb(); + __raw_writel(1, APPS_PWRDOWN); + mb(); +#else + __raw_writel(0x1f, APPS_CLK_SLEEP_EN); + mb(); + __raw_writel(1, APPS_PWRDOWN); + mb(); + __raw_writel(0, APPS_STANDBY_CTL); + mb(); +#endif +} + +/* + * Clear hardware registers after Apps powers up. + */ +static void msm_pm_config_hw_after_power_up(void) +{ +#if defined(CONFIG_ARCH_MSM7X30) + __raw_writel(0, APPS_SECOP); + mb(); + __raw_writel(0, APPS_PWRDOWN); + mb(); + msm_spm_reinit(); +#elif defined(CONFIG_ARCH_MSM7x27A) + __raw_writel(0, APPS_PWRDOWN); + mb(); + __raw_writel(0, APPS_CLK_SLEEP_EN); + mb(); +#else + __raw_writel(0, APPS_PWRDOWN); + mb(); + __raw_writel(0, APPS_CLK_SLEEP_EN); + mb(); +#endif +} + +/* + * Configure hardware registers in preparation for SWFI. + */ +static void msm_pm_config_hw_before_swfi(void) +{ +#if defined(CONFIG_ARCH_QSD8X50) + __raw_writel(0x1f, APPS_CLK_SLEEP_EN); + mb(); +#elif defined(CONFIG_ARCH_MSM7X27) + __raw_writel(0x0f, APPS_CLK_SLEEP_EN); + mb(); +#elif defined(CONFIG_ARCH_MSM7X27A) + __raw_writel(0x7, APPS_CLK_SLEEP_EN); + mb(); +#endif +} + +/* + * Respond to timing out waiting for Modem + * + * NOTE: The function never returns. + */ +static void msm_pm_timeout(void) +{ +#if defined(CONFIG_MSM_PM_TIMEOUT_RESET_CHIP) + printk(KERN_EMERG "%s(): resetting chip\n", __func__); + msm_proc_comm(PCOM_RESET_CHIP_IMM, NULL, NULL); +#elif defined(CONFIG_MSM_PM_TIMEOUT_RESET_MODEM) + printk(KERN_EMERG "%s(): resetting modem\n", __func__); + msm_proc_comm_reset_modem_now(); +#elif defined(CONFIG_MSM_PM_TIMEOUT_HALT) + printk(KERN_EMERG "%s(): halting\n", __func__); +#endif + for (;;) + ; +} + + +/****************************************************************************** + * State Polling Definitions + *****************************************************************************/ + +struct msm_pm_polled_group { + uint32_t group_id; + + uint32_t bits_all_set; + uint32_t bits_all_clear; + uint32_t bits_any_set; + uint32_t bits_any_clear; + + uint32_t value_read; +}; + +/* + * Return true if all bits indicated by flag are set in source. + */ +static inline bool msm_pm_all_set(uint32_t source, uint32_t flag) +{ + return (source & flag) == flag; +} + +/* + * Return true if any bit indicated by flag are set in source. + */ +static inline bool msm_pm_any_set(uint32_t source, uint32_t flag) +{ + return !flag || (source & flag); +} + +/* + * Return true if all bits indicated by flag are cleared in source. + */ +static inline bool msm_pm_all_clear(uint32_t source, uint32_t flag) +{ + return (~source & flag) == flag; +} + +/* + * Return true if any bit indicated by flag are cleared in source. + */ +static inline bool msm_pm_any_clear(uint32_t source, uint32_t flag) +{ + return !flag || (~source & flag); +} + +/* + * Poll the shared memory states as indicated by the poll groups. + * + * nr_grps: number of groups in the array + * grps: array of groups + * + * The function returns when conditions specified by any of the poll + * groups become true. The conditions specified by a poll group are + * deemed true when 1) at least one bit from bits_any_set is set OR one + * bit from bits_any_clear is cleared; and 2) all bits in bits_all_set + * are set; and 3) all bits in bits_all_clear are cleared. + * + * Return value: + * >=0: index of the poll group whose conditions have become true + * -ETIMEDOUT: timed out + */ +static int msm_pm_poll_state(int nr_grps, struct msm_pm_polled_group *grps) +{ + int i, k; + + for (i = 0; i < 50000; i++) { + for (k = 0; k < nr_grps; k++) { + bool all_set, all_clear; + bool any_set, any_clear; + + grps[k].value_read = smsm_get_state(grps[k].group_id); + + all_set = msm_pm_all_set(grps[k].value_read, + grps[k].bits_all_set); + all_clear = msm_pm_all_clear(grps[k].value_read, + grps[k].bits_all_clear); + any_set = msm_pm_any_set(grps[k].value_read, + grps[k].bits_any_set); + any_clear = msm_pm_any_clear(grps[k].value_read, + grps[k].bits_any_clear); + + if (all_set && all_clear && (any_set || any_clear)) + return k; + } + udelay(50); + } + + printk(KERN_ERR "%s failed:\n", __func__); + for (k = 0; k < nr_grps; k++) + printk(KERN_ERR "(%x, %x, %x, %x) %x\n", + grps[k].bits_all_set, grps[k].bits_all_clear, + grps[k].bits_any_set, grps[k].bits_any_clear, + grps[k].value_read); + + return -ETIMEDOUT; +} + + +/****************************************************************************** + * Suspend Max Sleep Time + *****************************************************************************/ + +#define SCLK_HZ (32768) +#define MSM_PM_SLEEP_TICK_LIMIT (0x6DDD000) + +#ifdef CONFIG_MSM_SLEEP_TIME_OVERRIDE +static int msm_pm_sleep_time_override; +module_param_named(sleep_time_override, + msm_pm_sleep_time_override, int, S_IRUGO | S_IWUSR | S_IWGRP); +#endif + +static uint32_t msm_pm_max_sleep_time; + +/* + * Convert time from nanoseconds to slow clock ticks, then cap it to the + * specified limit + */ +static int64_t msm_pm_convert_and_cap_time(int64_t time_ns, int64_t limit) +{ + do_div(time_ns, NSEC_PER_SEC / SCLK_HZ); + return (time_ns > limit) ? limit : time_ns; +} + +/* + * Set the sleep time for suspend. 0 means infinite sleep time. + */ +void msm_pm_set_max_sleep_time(int64_t max_sleep_time_ns) +{ + unsigned long flags; + + local_irq_save(flags); + if (max_sleep_time_ns == 0) { + msm_pm_max_sleep_time = 0; + } else { + msm_pm_max_sleep_time = (uint32_t)msm_pm_convert_and_cap_time( + max_sleep_time_ns, MSM_PM_SLEEP_TICK_LIMIT); + + if (msm_pm_max_sleep_time == 0) + msm_pm_max_sleep_time = 1; + } + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND, KERN_INFO, + "%s(): Requested %lld ns Giving %u sclk ticks\n", __func__, + max_sleep_time_ns, msm_pm_max_sleep_time); + local_irq_restore(flags); +} +EXPORT_SYMBOL(msm_pm_set_max_sleep_time); + + +/****************************************************************************** + * CONFIG_MSM_IDLE_STATS + *****************************************************************************/ + +#ifdef CONFIG_MSM_IDLE_STATS +enum msm_pm_time_stats_id { + MSM_PM_STAT_REQUESTED_IDLE, + MSM_PM_STAT_IDLE_SPIN, + MSM_PM_STAT_IDLE_WFI, + MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE, + MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE, + MSM_PM_STAT_IDLE_SLEEP, + MSM_PM_STAT_IDLE_FAILED_SLEEP, + MSM_PM_STAT_IDLE_POWER_COLLAPSE, + MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE, + MSM_PM_STAT_SUSPEND, + MSM_PM_STAT_FAILED_SUSPEND, + MSM_PM_STAT_NOT_IDLE, + MSM_PM_STAT_COUNT +}; + +static struct msm_pm_time_stats { + const char *name; + int64_t first_bucket_time; + int bucket[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; + int64_t min_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; + int64_t max_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; + int count; + int64_t total_time; +} msm_pm_stats[MSM_PM_STAT_COUNT] = { + [MSM_PM_STAT_REQUESTED_IDLE].name = "idle-request", + [MSM_PM_STAT_REQUESTED_IDLE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_SPIN].name = "idle-spin", + [MSM_PM_STAT_IDLE_SPIN].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_WFI].name = "idle-wfi", + [MSM_PM_STAT_IDLE_WFI].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE].name = + "idle-standalone-power-collapse", + [MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE].name = + "idle-failed-standalone-power-collapse", + [MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_SLEEP].name = "idle-sleep", + [MSM_PM_STAT_IDLE_SLEEP].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_FAILED_SLEEP].name = "idle-failed-sleep", + [MSM_PM_STAT_IDLE_FAILED_SLEEP].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_POWER_COLLAPSE].name = "idle-power-collapse", + [MSM_PM_STAT_IDLE_POWER_COLLAPSE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].name = + "idle-failed-power-collapse", + [MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_SUSPEND].name = "suspend", + [MSM_PM_STAT_SUSPEND].first_bucket_time = + CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_FAILED_SUSPEND].name = "failed-suspend", + [MSM_PM_STAT_FAILED_SUSPEND].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, + + [MSM_PM_STAT_NOT_IDLE].name = "not-idle", + [MSM_PM_STAT_NOT_IDLE].first_bucket_time = + CONFIG_MSM_IDLE_STATS_FIRST_BUCKET, +}; + +static uint32_t msm_pm_sleep_limit = SLEEP_LIMIT_NONE; + +/* + * Add the given time data to the statistics collection. + */ +static void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t) +{ + int i; + int64_t bt; + + msm_pm_stats[id].total_time += t; + msm_pm_stats[id].count++; + + bt = t; + do_div(bt, msm_pm_stats[id].first_bucket_time); + + if (bt < 1ULL << (CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT * + (CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1))) + i = DIV_ROUND_UP(fls((uint32_t)bt), + CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT); + else + i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; + + msm_pm_stats[id].bucket[i]++; + + if (t < msm_pm_stats[id].min_time[i] || !msm_pm_stats[id].max_time[i]) + msm_pm_stats[id].min_time[i] = t; + if (t > msm_pm_stats[id].max_time[i]) + msm_pm_stats[id].max_time[i] = t; +} + +/* + * Helper function of snprintf where buf is auto-incremented, size is auto- + * decremented, and there is no return value. + * + * NOTE: buf and size must be l-values (e.g. variables) + */ +#define SNPRINTF(buf, size, format, ...) \ + do { \ + if (size > 0) { \ + int ret; \ + ret = snprintf(buf, size, format, ## __VA_ARGS__); \ + if (ret > size) { \ + buf += size; \ + size = 0; \ + } else { \ + buf += ret; \ + size -= ret; \ + } \ + } \ + } while (0) + +/* + * Write out the power management statistics. + */ +static int msm_pm_read_proc + (char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int i; + char *p = page; + + if (count < 1024) { + *start = (char *) 0; + *eof = 0; + return 0; + } + + if (!off) { + SNPRINTF(p, count, "Last power collapse voted "); + if ((msm_pm_sleep_limit & SLEEP_LIMIT_MASK) == + SLEEP_LIMIT_NONE) + SNPRINTF(p, count, "for TCXO shutdown\n\n"); + else + SNPRINTF(p, count, "against TCXO shutdown\n\n"); + + *start = (char *) 1; + *eof = 0; + } else if (--off < ARRAY_SIZE(msm_pm_stats)) { + int64_t bucket_time; + int64_t s; + uint32_t ns; + + s = msm_pm_stats[off].total_time; + ns = do_div(s, NSEC_PER_SEC); + SNPRINTF(p, count, + "%s:\n" + " count: %7d\n" + " total_time: %lld.%09u\n", + msm_pm_stats[off].name, + msm_pm_stats[off].count, + s, ns); + + bucket_time = msm_pm_stats[off].first_bucket_time; + for (i = 0; i < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; i++) { + s = bucket_time; + ns = do_div(s, NSEC_PER_SEC); + SNPRINTF(p, count, + " <%6lld.%09u: %7d (%lld-%lld)\n", + s, ns, msm_pm_stats[off].bucket[i], + msm_pm_stats[off].min_time[i], + msm_pm_stats[off].max_time[i]); + + bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT; + } + + SNPRINTF(p, count, " >=%6lld.%09u: %7d (%lld-%lld)\n", + s, ns, msm_pm_stats[off].bucket[i], + msm_pm_stats[off].min_time[i], + msm_pm_stats[off].max_time[i]); + + *start = (char *) 1; + *eof = (off + 1 >= ARRAY_SIZE(msm_pm_stats)); + } + + return p - page; +} +#undef SNPRINTF + +#define MSM_PM_STATS_RESET "reset" + +/* + * Reset the power management statistics values. + */ +static int msm_pm_write_proc(struct file *file, const char __user *buffer, + unsigned long count, void *data) +{ + char buf[sizeof(MSM_PM_STATS_RESET)]; + int ret; + unsigned long flags; + int i; + + if (count < strlen(MSM_PM_STATS_RESET)) { + ret = -EINVAL; + goto write_proc_failed; + } + + if (copy_from_user(buf, buffer, strlen(MSM_PM_STATS_RESET))) { + ret = -EFAULT; + goto write_proc_failed; + } + + if (memcmp(buf, MSM_PM_STATS_RESET, strlen(MSM_PM_STATS_RESET))) { + ret = -EINVAL; + goto write_proc_failed; + } + + local_irq_save(flags); + for (i = 0; i < ARRAY_SIZE(msm_pm_stats); i++) { + memset(msm_pm_stats[i].bucket, + 0, sizeof(msm_pm_stats[i].bucket)); + memset(msm_pm_stats[i].min_time, + 0, sizeof(msm_pm_stats[i].min_time)); + memset(msm_pm_stats[i].max_time, + 0, sizeof(msm_pm_stats[i].max_time)); + msm_pm_stats[i].count = 0; + msm_pm_stats[i].total_time = 0; + } + + msm_pm_sleep_limit = SLEEP_LIMIT_NONE; + local_irq_restore(flags); + + return count; + +write_proc_failed: + return ret; +} +#undef MSM_PM_STATS_RESET +#endif /* CONFIG_MSM_IDLE_STATS */ + + +/****************************************************************************** + * Shared Memory Bits + *****************************************************************************/ + +#define DEM_MASTER_BITS_PER_CPU 6 + +/* Power Master State Bits - Per CPU */ +#define DEM_MASTER_SMSM_RUN \ + (0x01UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) +#define DEM_MASTER_SMSM_RSA \ + (0x02UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) +#define DEM_MASTER_SMSM_PWRC_EARLY_EXIT \ + (0x04UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) +#define DEM_MASTER_SMSM_SLEEP_EXIT \ + (0x08UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) +#define DEM_MASTER_SMSM_READY \ + (0x10UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) +#define DEM_MASTER_SMSM_SLEEP \ + (0x20UL << (DEM_MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) + +/* Power Slave State Bits */ +#define DEM_SLAVE_SMSM_RUN (0x0001) +#define DEM_SLAVE_SMSM_PWRC (0x0002) +#define DEM_SLAVE_SMSM_PWRC_DELAY (0x0004) +#define DEM_SLAVE_SMSM_PWRC_EARLY_EXIT (0x0008) +#define DEM_SLAVE_SMSM_WFPI (0x0010) +#define DEM_SLAVE_SMSM_SLEEP (0x0020) +#define DEM_SLAVE_SMSM_SLEEP_EXIT (0x0040) +#define DEM_SLAVE_SMSM_MSGS_REDUCED (0x0080) +#define DEM_SLAVE_SMSM_RESET (0x0100) +#define DEM_SLAVE_SMSM_PWRC_SUSPEND (0x0200) + + +/****************************************************************************** + * Shared Memory Data + *****************************************************************************/ + +#define DEM_MAX_PORT_NAME_LEN (20) + +struct msm_pm_smem_t { + uint32_t sleep_time; + uint32_t irq_mask; + uint32_t resources_used; + uint32_t reserved1; + + uint32_t wakeup_reason; + uint32_t pending_irqs; + uint32_t rpc_prog; + uint32_t rpc_proc; + char smd_port_name[DEM_MAX_PORT_NAME_LEN]; + uint32_t reserved2; +}; + + +/****************************************************************************** + * + *****************************************************************************/ +static struct msm_pm_smem_t *msm_pm_smem_data; +static uint32_t *msm_pm_reset_vector; +static atomic_t msm_pm_init_done = ATOMIC_INIT(0); + +static int msm_pm_modem_busy(void) +{ + if (!(smsm_get_state(SMSM_POWER_MASTER_DEM) & DEM_MASTER_SMSM_READY)) { + MSM_PM_DPRINTK(MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, "%s(): master not ready\n", __func__); + return -EBUSY; + } + + return 0; +} + +/* + * Power collapse the Apps processor. This function executes the handshake + * protocol with Modem. + * + * Return value: + * -EAGAIN: modem reset occurred or early exit from power collapse + * -EBUSY: modem not ready for our power collapse -- no power loss + * -ETIMEDOUT: timed out waiting for modem's handshake -- no power loss + * 0: success + */ +static int msm_pm_power_collapse + (bool from_idle, uint32_t sleep_delay, uint32_t sleep_limit) +{ + struct msm_pm_polled_group state_grps[2]; + unsigned long saved_acpuclk_rate; + uint32_t saved_vector[2]; + int collapsed = 0; + int ret; + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, "%s(): idle %d, delay %u, limit %u\n", __func__, + (int)from_idle, sleep_delay, sleep_limit); + + if (!(smsm_get_state(SMSM_POWER_MASTER_DEM) & DEM_MASTER_SMSM_READY)) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND | MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, "%s(): master not ready\n", __func__); + ret = -EBUSY; + goto power_collapse_bail; + } + + memset(msm_pm_smem_data, 0, sizeof(*msm_pm_smem_data)); + + msm_irq_enter_sleep1(true, from_idle, &msm_pm_smem_data->irq_mask); + msm_sirc_enter_sleep(); + msm_gpio_enter_sleep(from_idle); + + msm_pm_smem_data->sleep_time = sleep_delay; + msm_pm_smem_data->resources_used = sleep_limit; + + /* Enter PWRC/PWRC_SUSPEND */ + + if (from_idle) + smsm_change_state(SMSM_APPS_DEM, DEM_SLAVE_SMSM_RUN, + DEM_SLAVE_SMSM_PWRC); + else + smsm_change_state(SMSM_APPS_DEM, DEM_SLAVE_SMSM_RUN, + DEM_SLAVE_SMSM_PWRC | DEM_SLAVE_SMSM_PWRC_SUSPEND); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): PWRC"); + MSM_PM_DEBUG_PRINT_SLEEP_INFO(); + + memset(state_grps, 0, sizeof(state_grps)); + state_grps[0].group_id = SMSM_POWER_MASTER_DEM; + state_grps[0].bits_all_set = DEM_MASTER_SMSM_RSA; + state_grps[1].group_id = SMSM_MODEM_STATE; + state_grps[1].bits_all_set = SMSM_RESET; + + ret = msm_pm_poll_state(ARRAY_SIZE(state_grps), state_grps); + + if (ret < 0) { + printk(KERN_EMERG "%s(): power collapse entry " + "timed out waiting for Modem's response\n", __func__); + msm_pm_timeout(); + } + + if (ret == 1) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_poll_state detected Modem reset\n", + __func__); + goto power_collapse_early_exit; + } + + /* DEM Master in RSA */ + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): PWRC RSA"); + + ret = msm_irq_enter_sleep2(true, from_idle); + if (ret < 0) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_irq_enter_sleep2 aborted, %d\n", __func__, + ret); + goto power_collapse_early_exit; + } + + msm_pm_config_hw_before_power_down(); + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): pre power down"); + + saved_acpuclk_rate = acpuclk_power_collapse(); + MSM_PM_DPRINTK(MSM_PM_DEBUG_CLOCK, KERN_INFO, + "%s(): change clock rate (old rate = %lu)\n", __func__, + saved_acpuclk_rate); + + if (saved_acpuclk_rate == 0) { + msm_pm_config_hw_after_power_up(); + goto power_collapse_early_exit; + } + + saved_vector[0] = msm_pm_reset_vector[0]; + saved_vector[1] = msm_pm_reset_vector[1]; + msm_pm_reset_vector[0] = 0xE51FF004; /* ldr pc, 4 */ + msm_pm_reset_vector[1] = virt_to_phys(msm_pm_collapse_exit); + + MSM_PM_DPRINTK(MSM_PM_DEBUG_RESET_VECTOR, KERN_INFO, + "%s(): vector %x %x -> %x %x\n", __func__, + saved_vector[0], saved_vector[1], + msm_pm_reset_vector[0], msm_pm_reset_vector[1]); + +#ifdef CONFIG_VFP + if (from_idle) + vfp_flush_context(); +#endif + +#ifdef CONFIG_CACHE_L2X0 + l2x0_suspend(); +#endif + + collapsed = msm_pm_collapse(); + +#ifdef CONFIG_CACHE_L2X0 + l2x0_resume(collapsed); +#endif + + msm_pm_reset_vector[0] = saved_vector[0]; + msm_pm_reset_vector[1] = saved_vector[1]; + + if (collapsed) { +#ifdef CONFIG_VFP + if (from_idle) + vfp_reinit(); +#endif + cpu_init(); + local_fiq_enable(); + } + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND | MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_collapse returned %d\n", __func__, collapsed); + + MSM_PM_DPRINTK(MSM_PM_DEBUG_CLOCK, KERN_INFO, + "%s(): restore clock rate to %lu\n", __func__, + saved_acpuclk_rate); + if (acpuclk_set_rate(smp_processor_id(), saved_acpuclk_rate, + SETRATE_PC) < 0) + printk(KERN_ERR "%s(): failed to restore clock rate(%lu)\n", + __func__, saved_acpuclk_rate); + + msm_irq_exit_sleep1(msm_pm_smem_data->irq_mask, + msm_pm_smem_data->wakeup_reason, + msm_pm_smem_data->pending_irqs); + + msm_pm_config_hw_after_power_up(); + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): post power up"); + + memset(state_grps, 0, sizeof(state_grps)); + state_grps[0].group_id = SMSM_POWER_MASTER_DEM; + state_grps[0].bits_any_set = + DEM_MASTER_SMSM_RSA | DEM_MASTER_SMSM_PWRC_EARLY_EXIT; + state_grps[1].group_id = SMSM_MODEM_STATE; + state_grps[1].bits_all_set = SMSM_RESET; + + ret = msm_pm_poll_state(ARRAY_SIZE(state_grps), state_grps); + + if (ret < 0) { + printk(KERN_EMERG "%s(): power collapse exit " + "timed out waiting for Modem's response\n", __func__); + msm_pm_timeout(); + } + + if (ret == 1) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_poll_state detected Modem reset\n", + __func__); + goto power_collapse_early_exit; + } + + /* Sanity check */ + if (collapsed) { + BUG_ON(!(state_grps[0].value_read & DEM_MASTER_SMSM_RSA)); + } else { + BUG_ON(!(state_grps[0].value_read & + DEM_MASTER_SMSM_PWRC_EARLY_EXIT)); + goto power_collapse_early_exit; + } + + /* Enter WFPI */ + + smsm_change_state(SMSM_APPS_DEM, + DEM_SLAVE_SMSM_PWRC | DEM_SLAVE_SMSM_PWRC_SUSPEND, + DEM_SLAVE_SMSM_WFPI); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): WFPI"); + + memset(state_grps, 0, sizeof(state_grps)); + state_grps[0].group_id = SMSM_POWER_MASTER_DEM; + state_grps[0].bits_all_set = DEM_MASTER_SMSM_RUN; + state_grps[1].group_id = SMSM_MODEM_STATE; + state_grps[1].bits_all_set = SMSM_RESET; + + ret = msm_pm_poll_state(ARRAY_SIZE(state_grps), state_grps); + + if (ret < 0) { + printk(KERN_EMERG "%s(): power collapse WFPI " + "timed out waiting for Modem's response\n", __func__); + msm_pm_timeout(); + } + + if (ret == 1) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_poll_state detected Modem reset\n", + __func__); + ret = -EAGAIN; + goto power_collapse_restore_gpio_bail; + } + + /* DEM Master == RUN */ + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): WFPI RUN"); + MSM_PM_DEBUG_PRINT_SLEEP_INFO(); + + msm_irq_exit_sleep2(msm_pm_smem_data->irq_mask, + msm_pm_smem_data->wakeup_reason, + msm_pm_smem_data->pending_irqs); + msm_irq_exit_sleep3(msm_pm_smem_data->irq_mask, + msm_pm_smem_data->wakeup_reason, + msm_pm_smem_data->pending_irqs); + msm_gpio_exit_sleep(); + msm_sirc_exit_sleep(); + + smsm_change_state(SMSM_APPS_DEM, + DEM_SLAVE_SMSM_WFPI, DEM_SLAVE_SMSM_RUN); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): RUN"); + + smd_sleep_exit(); + return 0; + +power_collapse_early_exit: + /* Enter PWRC_EARLY_EXIT */ + + smsm_change_state(SMSM_APPS_DEM, + DEM_SLAVE_SMSM_PWRC | DEM_SLAVE_SMSM_PWRC_SUSPEND, + DEM_SLAVE_SMSM_PWRC_EARLY_EXIT); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): EARLY_EXIT"); + + memset(state_grps, 0, sizeof(state_grps)); + state_grps[0].group_id = SMSM_POWER_MASTER_DEM; + state_grps[0].bits_all_set = DEM_MASTER_SMSM_PWRC_EARLY_EXIT; + state_grps[1].group_id = SMSM_MODEM_STATE; + state_grps[1].bits_all_set = SMSM_RESET; + + ret = msm_pm_poll_state(ARRAY_SIZE(state_grps), state_grps); + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): EARLY_EXIT EE"); + + if (ret < 0) { + printk(KERN_EMERG "%s(): power collapse EARLY_EXIT " + "timed out waiting for Modem's response\n", __func__); + msm_pm_timeout(); + } + + if (ret == 1) { + MSM_PM_DPRINTK( + MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_poll_state detected Modem reset\n", + __func__); + } + + /* DEM Master == RESET or PWRC_EARLY_EXIT */ + + ret = -EAGAIN; + +power_collapse_restore_gpio_bail: + msm_gpio_exit_sleep(); + msm_sirc_exit_sleep(); + + /* Enter RUN */ + smsm_change_state(SMSM_APPS_DEM, + DEM_SLAVE_SMSM_PWRC | DEM_SLAVE_SMSM_PWRC_SUSPEND | + DEM_SLAVE_SMSM_PWRC_EARLY_EXIT, DEM_SLAVE_SMSM_RUN); + + MSM_PM_DEBUG_PRINT_STATE("msm_pm_power_collapse(): RUN"); + + if (collapsed) + smd_sleep_exit(); + +power_collapse_bail: + return ret; +} + +/* + * Power collapse the Apps processor without involving Modem. + * + * Return value: + * 0: success + */ +static int msm_pm_power_collapse_standalone(void) +{ + uint32_t saved_vector[2]; + int collapsed = 0; + int ret; + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND|MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, "%s()\n", __func__); + + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_POWER_COLLAPSE, false); + WARN_ON(ret); + + saved_vector[0] = msm_pm_reset_vector[0]; + saved_vector[1] = msm_pm_reset_vector[1]; + msm_pm_reset_vector[0] = 0xE51FF004; /* ldr pc, 4 */ + msm_pm_reset_vector[1] = virt_to_phys(msm_pm_collapse_exit); + + MSM_PM_DPRINTK(MSM_PM_DEBUG_RESET_VECTOR, KERN_INFO, + "%s(): vector %x %x -> %x %x\n", __func__, + saved_vector[0], saved_vector[1], + msm_pm_reset_vector[0], msm_pm_reset_vector[1]); + +#ifdef CONFIG_VFP + vfp_flush_context(); +#endif + +#ifdef CONFIG_CACHE_L2X0 + l2x0_suspend(); +#endif + + collapsed = msm_pm_collapse(); + +#ifdef CONFIG_CACHE_L2X0 + l2x0_resume(collapsed); +#endif + + msm_pm_reset_vector[0] = saved_vector[0]; + msm_pm_reset_vector[1] = saved_vector[1]; + + if (collapsed) { +#ifdef CONFIG_VFP + vfp_reinit(); +#endif + cpu_init(); + local_fiq_enable(); + } + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND | MSM_PM_DEBUG_POWER_COLLAPSE, + KERN_INFO, + "%s(): msm_pm_collapse returned %d\n", __func__, collapsed); + + ret = msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING, false); + WARN_ON(ret); + + return 0; +} + +/* + * Apps-sleep the Apps processor. This function execute the handshake + * protocol with Modem. + * + * Return value: + * -ENOSYS: function not implemented yet + */ +static int msm_pm_apps_sleep(uint32_t sleep_delay, uint32_t sleep_limit) +{ + return -ENOSYS; +} + +/* + * Bring the Apps processor to SWFI. + * + * Return value: + * -EIO: could not ramp Apps processor clock + * 0: success + */ +static int msm_pm_swfi(bool ramp_acpu) +{ + unsigned long saved_acpuclk_rate = 0; + + if (ramp_acpu) { + saved_acpuclk_rate = acpuclk_wait_for_irq(); + MSM_PM_DPRINTK(MSM_PM_DEBUG_CLOCK, KERN_INFO, + "%s(): change clock rate (old rate = %lu)\n", __func__, + saved_acpuclk_rate); + + if (!saved_acpuclk_rate) + return -EIO; + } + + msm_pm_config_hw_before_swfi(); + msm_arch_idle(); + + if (ramp_acpu) { + MSM_PM_DPRINTK(MSM_PM_DEBUG_CLOCK, KERN_INFO, + "%s(): restore clock rate to %lu\n", __func__, + saved_acpuclk_rate); + if (acpuclk_set_rate(smp_processor_id(), saved_acpuclk_rate, + SETRATE_SWFI) < 0) + printk(KERN_ERR + "%s(): failed to restore clock rate(%lu)\n", + __func__, saved_acpuclk_rate); + } + + return 0; +} + + +/****************************************************************************** + * External Idle/Suspend Functions + *****************************************************************************/ + +/* + * Put CPU in low power mode. + */ +void arch_idle(void) +{ + bool allow[MSM_PM_SLEEP_MODE_NR]; + uint32_t sleep_limit = SLEEP_LIMIT_NONE; + + int latency_qos; + int64_t timer_expiration; + + int low_power; + int ret; + int i; + +#ifdef CONFIG_MSM_IDLE_STATS + int64_t t1; + static int64_t t2; + int exit_stat; +#endif /* CONFIG_MSM_IDLE_STATS */ + + if (!atomic_read(&msm_pm_init_done)) + return; + + latency_qos = pm_qos_request(PM_QOS_CPU_DMA_LATENCY); + timer_expiration = msm_timer_enter_idle(); + +#ifdef CONFIG_MSM_IDLE_STATS + t1 = ktime_to_ns(ktime_get()); + msm_pm_add_stat(MSM_PM_STAT_NOT_IDLE, t1 - t2); + msm_pm_add_stat(MSM_PM_STAT_REQUESTED_IDLE, timer_expiration); +#endif /* CONFIG_MSM_IDLE_STATS */ + + for (i = 0; i < ARRAY_SIZE(allow); i++) + allow[i] = true; + + switch (msm_pm_idle_sleep_mode) { + case MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT: + allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT] = + false; + /* fall through */ + case MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT: + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE: + allow[MSM_PM_SLEEP_MODE_APPS_SLEEP] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_APPS_SLEEP: + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = false; + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND: + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: + break; + default: + printk(KERN_ERR "idle sleep mode is invalid: %d\n", + msm_pm_idle_sleep_mode); +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif /* CONFIG_MSM_IDLE_STATS */ + low_power = 0; + goto arch_idle_exit; + } + + if ((timer_expiration < msm_pm_idle_sleep_min_time) || +#ifdef CONFIG_HAS_WAKELOCK + has_wake_lock(WAKE_LOCK_IDLE) || +#endif + !msm_irq_idle_sleep_allowed()) { + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false; + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = false; + allow[MSM_PM_SLEEP_MODE_APPS_SLEEP] = false; + } + + for (i = 0; i < ARRAY_SIZE(allow); i++) { + struct msm_pm_platform_data *mode = &msm_pm_modes[i]; + if (!mode->idle_supported || !mode->idle_enabled || + mode->latency >= latency_qos || + mode->residency * 1000ULL >= timer_expiration) + allow[i] = false; + } + + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] || + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]) { + uint32_t wait_us = CONFIG_MSM_IDLE_WAIT_ON_MODEM; + while (msm_pm_modem_busy() && wait_us) { + if (wait_us > 100) { + udelay(100); + wait_us -= 100; + } else { + udelay(wait_us); + wait_us = 0; + } + } + + if (msm_pm_modem_busy()) { + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false; + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] + = false; + } + } + + MSM_PM_DPRINTK(MSM_PM_DEBUG_IDLE, KERN_INFO, + "%s(): latency qos %d, next timer %lld, sleep limit %u\n", + __func__, latency_qos, timer_expiration, sleep_limit); + + for (i = 0; i < ARRAY_SIZE(allow); i++) + MSM_PM_DPRINTK(MSM_PM_DEBUG_IDLE, KERN_INFO, + "%s(): allow %s: %d\n", __func__, + msm_pm_sleep_mode_labels[i], (int)allow[i]); + + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] || + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]) { + uint32_t sleep_delay; + + sleep_delay = (uint32_t) msm_pm_convert_and_cap_time( + timer_expiration, MSM_PM_SLEEP_TICK_LIMIT); + if (sleep_delay == 0) /* 0 would mean infinite time */ + sleep_delay = 1; + + if (!allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]) + sleep_limit = SLEEP_LIMIT_NO_TCXO_SHUTDOWN; + +#if defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_IDLE_ACTIVE) + sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT1; +#elif defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_IDLE_RETENTION) + sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT0; +#endif + + ret = msm_pm_power_collapse(true, sleep_delay, sleep_limit); + low_power = (ret != -EBUSY && ret != -ETIMEDOUT); + +#ifdef CONFIG_MSM_IDLE_STATS + if (ret) + exit_stat = MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE; + else { + exit_stat = MSM_PM_STAT_IDLE_POWER_COLLAPSE; + msm_pm_sleep_limit = sleep_limit; + } +#endif /* CONFIG_MSM_IDLE_STATS */ + } else if (allow[MSM_PM_SLEEP_MODE_APPS_SLEEP]) { + uint32_t sleep_delay; + + sleep_delay = (uint32_t) msm_pm_convert_and_cap_time( + timer_expiration, MSM_PM_SLEEP_TICK_LIMIT); + if (sleep_delay == 0) /* 0 would mean infinite time */ + sleep_delay = 1; + + ret = msm_pm_apps_sleep(sleep_delay, sleep_limit); + low_power = 0; + +#ifdef CONFIG_MSM_IDLE_STATS + if (ret) + exit_stat = MSM_PM_STAT_IDLE_FAILED_SLEEP; + else + exit_stat = MSM_PM_STAT_IDLE_SLEEP; +#endif /* CONFIG_MSM_IDLE_STATS */ + } else if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) { + ret = msm_pm_power_collapse_standalone(); + low_power = 0; +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = ret ? + MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE : + MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE; +#endif /* CONFIG_MSM_IDLE_STATS */ + } else if (allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT]) { + ret = msm_pm_swfi(true); + if (ret) + while (!msm_irq_pending()) + udelay(1); + low_power = 0; +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = ret ? MSM_PM_STAT_IDLE_SPIN : MSM_PM_STAT_IDLE_WFI; +#endif /* CONFIG_MSM_IDLE_STATS */ + } else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) { + msm_pm_swfi(false); + low_power = 0; +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_WFI; +#endif /* CONFIG_MSM_IDLE_STATS */ + } else { + while (!msm_irq_pending()) + udelay(1); + low_power = 0; +#ifdef CONFIG_MSM_IDLE_STATS + exit_stat = MSM_PM_STAT_IDLE_SPIN; +#endif /* CONFIG_MSM_IDLE_STATS */ + } + +arch_idle_exit: + msm_timer_exit_idle(low_power); + +#ifdef CONFIG_MSM_IDLE_STATS + t2 = ktime_to_ns(ktime_get()); + msm_pm_add_stat(exit_stat, t2 - t1); +#endif /* CONFIG_MSM_IDLE_STATS */ +} + +/* + * Suspend the Apps processor. + * + * Return value: + * -EAGAIN: modem reset occurred or early exit from suspend + * -EBUSY: modem not ready for our suspend + * -EINVAL: invalid sleep mode + * -EIO: could not ramp Apps processor clock + * -ETIMEDOUT: timed out waiting for modem's handshake + * 0: success + */ +static int msm_pm_enter(suspend_state_t state) +{ + bool allow[MSM_PM_SLEEP_MODE_NR]; + uint32_t sleep_limit = SLEEP_LIMIT_NONE; + int ret; + int i; + +#ifdef CONFIG_MSM_IDLE_STATS + int64_t period = 0; + int64_t time = 0; + + time = msm_timer_get_sclk_time(&period); +#endif + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND, KERN_INFO, + "%s(): sleep limit %u\n", __func__, sleep_limit); + + for (i = 0; i < ARRAY_SIZE(allow); i++) + allow[i] = true; + + switch (msm_pm_sleep_mode) { + case MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT: + allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT] = + false; + /* fall through */ + case MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT: + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE: + allow[MSM_PM_SLEEP_MODE_APPS_SLEEP] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_APPS_SLEEP: + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN] = false; + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = false; + /* fall through */ + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND: + case MSM_PM_SLEEP_MODE_POWER_COLLAPSE: + break; + default: + printk(KERN_ERR "suspend sleep mode is invalid: %d\n", + msm_pm_sleep_mode); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(allow); i++) { + struct msm_pm_platform_data *mode = &msm_pm_modes[i]; + if (!mode->suspend_supported || !mode->suspend_enabled) + allow[i] = false; + } + + ret = 0; + + if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE] || + allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_NO_XO_SHUTDOWN]) { +#ifdef CONFIG_MSM_IDLE_STATS + enum msm_pm_time_stats_id id; + int64_t end_time; +#endif + + clock_debug_print_enabled(); + +#ifdef CONFIG_MSM_SLEEP_TIME_OVERRIDE + if (msm_pm_sleep_time_override > 0) { + int64_t ns; + ns = NSEC_PER_SEC * (int64_t)msm_pm_sleep_time_override; + msm_pm_set_max_sleep_time(ns); + msm_pm_sleep_time_override = 0; + } +#endif + if (!allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE]) + sleep_limit = SLEEP_LIMIT_NO_TCXO_SHUTDOWN; + +#if defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_ACTIVE) + sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT1; +#elif defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_RETENTION) + sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT0; +#elif defined(CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_DEEP_POWER_DOWN) + if (get_msm_migrate_pages_status() != MEM_OFFLINE) + sleep_limit |= SLEEP_RESOURCE_MEMORY_BIT0; +#endif + + for (i = 0; i < 30 && msm_pm_modem_busy(); i++) + udelay(500); + + ret = msm_pm_power_collapse( + false, msm_pm_max_sleep_time, sleep_limit); + +#ifdef CONFIG_MSM_IDLE_STATS + if (ret) + id = MSM_PM_STAT_FAILED_SUSPEND; + else { + id = MSM_PM_STAT_SUSPEND; + msm_pm_sleep_limit = sleep_limit; + } + + if (time != 0) { + end_time = msm_timer_get_sclk_time(NULL); + if (end_time != 0) { + time = end_time - time; + if (time < 0) + time += period; + } else + time = 0; + } + + msm_pm_add_stat(id, time); +#endif + } else if (allow[MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE]) { + ret = msm_pm_power_collapse_standalone(); + } else if (allow[MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT]) { + ret = msm_pm_swfi(true); + if (ret) + while (!msm_irq_pending()) + udelay(1); + } else if (allow[MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT]) { + msm_pm_swfi(false); + } + + MSM_PM_DPRINTK(MSM_PM_DEBUG_SUSPEND, KERN_INFO, + "%s(): return %d\n", __func__, ret); + + return ret; +} + +static struct platform_suspend_ops msm_pm_ops = { + .enter = msm_pm_enter, + .valid = suspend_valid_only_mem, +}; + + +/****************************************************************************** + * Restart Definitions + *****************************************************************************/ + +static uint32_t restart_reason = 0x776655AA; + +static void msm_pm_power_off(void) +{ + msm_rpcrouter_close(); + msm_proc_comm(PCOM_POWER_DOWN, 0, 0); + for (;;) + ; +} + +static void msm_pm_restart(char str, const char *cmd) +{ + msm_rpcrouter_close(); + msm_proc_comm(PCOM_RESET_CHIP, &restart_reason, 0); + + for (;;) + ; +} + +static int msm_reboot_call + (struct notifier_block *this, unsigned long code, void *_cmd) +{ + if ((code == SYS_RESTART) && _cmd) { + char *cmd = _cmd; + if (!strcmp(cmd, "bootloader")) { + restart_reason = 0x77665500; + } else if (!strcmp(cmd, "recovery")) { + restart_reason = 0x77665502; + } else if (!strcmp(cmd, "eraseflash")) { + restart_reason = 0x776655EF; + } else if (!strncmp(cmd, "oem-", 4)) { + unsigned code = simple_strtoul(cmd + 4, 0, 16) & 0xff; + restart_reason = 0x6f656d00 | code; + } else { + restart_reason = 0x77665501; + } + } + return NOTIFY_DONE; +} + +static struct notifier_block msm_reboot_notifier = { + .notifier_call = msm_reboot_call, +}; + + +/****************************************************************************** + * + *****************************************************************************/ + +/* + * Initialize the power management subsystem. + * + * Return value: + * -ENODEV: initialization failed + * 0: success + */ +static int __init msm_pm_init(void) +{ +#ifdef CONFIG_MSM_IDLE_STATS + struct proc_dir_entry *d_entry; +#endif + int ret; +#ifdef CONFIG_CPU_V7 + pgd_t *pc_pgd; + pmd_t *pmd; + unsigned long pmdval; + + /* Page table for cores to come back up safely. */ + pc_pgd = pgd_alloc(&init_mm); + if (!pc_pgd) + return -ENOMEM; + pmd = pmd_offset(pc_pgd + + pgd_index(virt_to_phys(msm_pm_collapse_exit)), + virt_to_phys(msm_pm_collapse_exit)); + pmdval = (virt_to_phys(msm_pm_collapse_exit) & PGDIR_MASK) | + PMD_TYPE_SECT | PMD_SECT_AP_WRITE; + pmd[0] = __pmd(pmdval); + pmd[1] = __pmd(pmdval + (1 << (PGDIR_SHIFT - 1))); + + /* It is remotely possible that the code in msm_pm_collapse_exit() + * which turns on the MMU with this mapping is in the + * next even-numbered megabyte beyond the + * start of msm_pm_collapse_exit(). + * Map this megabyte in as well. + */ + pmd[2] = __pmd(pmdval + (2 << (PGDIR_SHIFT - 1))); + flush_pmd_entry(pmd); + msm_pm_pc_pgd = virt_to_phys(pc_pgd); +#endif + + pm_power_off = msm_pm_power_off; + arm_pm_restart = msm_pm_restart; + register_reboot_notifier(&msm_reboot_notifier); + + msm_pm_smem_data = smem_alloc(SMEM_APPS_DEM_SLAVE_DATA, + sizeof(*msm_pm_smem_data)); + if (msm_pm_smem_data == NULL) { + printk(KERN_ERR "%s: failed to get smsm_data\n", __func__); + return -ENODEV; + } +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) + /* The bootloader is responsible for initializing many of Scorpion's + * coprocessor registers for things like cache timing. The state of + * these coprocessor registers is lost on reset, so part of the + * bootloader must be re-executed. Do not overwrite the reset vector + * or bootloader area. + */ + msm_pm_reset_vector = (uint32_t *) PAGE_OFFSET; +#else + msm_pm_reset_vector = ioremap(0, PAGE_SIZE); + if (msm_pm_reset_vector == NULL) { + printk(KERN_ERR "%s: failed to map reset vector\n", __func__); + return -ENODEV; + } +#endif /* CONFIG_ARCH_MSM_SCORPION */ + + ret = msm_timer_init_time_sync(msm_pm_timeout); + if (ret) + return ret; + + ret = smsm_change_intr_mask(SMSM_POWER_MASTER_DEM, 0xFFFFFFFF, 0); + if (ret) { + printk(KERN_ERR "%s: failed to clear interrupt mask, %d\n", + __func__, ret); + return ret; + } + +#ifdef CONFIG_MSM_MEMORY_LOW_POWER_MODE + /* The wakeup_reason field is overloaded during initialization time + to signal Modem that Apps will control the low power modes of + the memory. + */ + msm_pm_smem_data->wakeup_reason = 1; + smsm_change_state(SMSM_APPS_DEM, 0, DEM_SLAVE_SMSM_RUN); +#endif + + BUG_ON(msm_pm_modes == NULL); + + atomic_set(&msm_pm_init_done, 1); + suspend_set_ops(&msm_pm_ops); + + msm_pm_mode_sysfs_add(); +#ifdef CONFIG_MSM_IDLE_STATS + d_entry = create_proc_entry("msm_pm_stats", + S_IRUGO | S_IWUSR | S_IWGRP, NULL); + if (d_entry) { + d_entry->read_proc = msm_pm_read_proc; + d_entry->write_proc = msm_pm_write_proc; + d_entry->data = NULL; + } +#endif + + return 0; +} + +late_initcall_sync(msm_pm_init); diff --git a/arch/arm/mach-msm/pmic.c b/arch/arm/mach-msm/pmic.c new file mode 100644 index 00000000000..6227a788f9a --- /dev/null +++ b/arch/arm/mach-msm/pmic.c @@ -0,0 +1,1286 @@ +/* Copyright (c) 2009, 2011 Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "smd_rpcrouter.h" + +#define TRACE_PMIC 0 + +#if TRACE_PMIC +#define PMIC(x...) printk(KERN_INFO "[PMIC] " x) +#else +#define PMIC(x...) do {} while (0) +#endif + + +#define LIB_NULL_PROC 0 +#define LIB_RPC_GLUE_CODE_INFO_REMOTE_PROC 1 +#define LP_MODE_CONTROL_PROC 2 +#define VREG_SET_LEVEL_PROC 3 +#define VREG_PULL_DOWN_SWITCH_PROC 4 +#define SECURE_MPP_CONFIG_DIGITAL_OUTPUT_PROC 5 +#define SECURE_MPP_CONFIG_I_SINK_PROC 6 +#define RTC_START_PROC 7 +#define RTC_STOP_PROC 8 +#define RTC_GET_TIME_PROC 9 +#define RTC_ENABLE_ALARM_PROC 10 +#define RTC_DISABLE_ALARM_PROC 11 +#define RTC_GET_ALARM_TIME_PROC 12 +#define RTC_GET_ALARM_STATUS_PROC 13 +#define RTC_SET_TIME_ADJUST_PROC 14 +#define RTC_GET_TIME_ADJUST_PROC 15 +#define SET_LED_INTENSITY_PROC 16 +#define FLASH_LED_SET_CURRENT_PROC 17 +#define FLASH_LED_SET_MODE_PROC 18 +#define FLASH_LED_SET_POLARITY_PROC 19 +#define SPEAKER_CMD_PROC 20 +#define SET_SPEAKER_GAIN_PROC 21 +#define VIB_MOT_SET_VOLT_PROC 22 +#define VIB_MOT_SET_MODE_PROC 23 +#define VIB_MOT_SET_POLARITY_PROC 24 +#define VID_EN_PROC 25 +#define VID_IS_EN_PROC 26 +#define VID_LOAD_DETECT_EN_PROC 27 +#define MIC_EN_PROC 28 +#define MIC_IS_EN_PROC 29 +#define MIC_SET_VOLT_PROC 30 +#define MIC_GET_VOLT_PROC 31 +#define SPKR_EN_RIGHT_CHAN_PROC 32 +#define SPKR_IS_RIGHT_CHAN_EN_PROC 33 +#define SPKR_EN_LEFT_CHAN_PROC 34 +#define SPKR_IS_LEFT_CHAN_EN_PROC 35 +#define SET_SPKR_CONFIGURATION_PROC 36 +#define GET_SPKR_CONFIGURATION_PROC 37 +#define SPKR_GET_GAIN_PROC 38 +#define SPKR_IS_EN_PROC 39 +#define SPKR_EN_MUTE_PROC 40 +#define SPKR_IS_MUTE_EN_PROC 41 +#define SPKR_SET_DELAY_PROC 42 +#define SPKR_GET_DELAY_PROC 43 +#define SECURE_MPP_CONFIG_DIGITAL_INPUT_PROC 44 +#define SET_SPEAKER_DELAY_PROC 45 +#define SPEAKER_1K6_ZIN_ENABLE_PROC 46 +#define SPKR_SET_MUX_HPF_CORNER_FREQ_PROC 47 +#define SPKR_GET_MUX_HPF_CORNER_FREQ_PROC 48 +#define SPKR_IS_RIGHT_LEFT_CHAN_ADDED_PROC 49 +#define SPKR_EN_STEREO_PROC 50 +#define SPKR_IS_STEREO_EN_PROC 51 +#define SPKR_SELECT_USB_WITH_HPF_20HZ_PROC 52 +#define SPKR_IS_USB_WITH_HPF_20HZ_PROC 53 +#define SPKR_BYPASS_MUX_PROC 54 +#define SPKR_IS_MUX_BYPASSED_PROC 55 +#define SPKR_EN_HPF_PROC 56 +#define SPKR_IS_HPF_EN_PROC 57 +#define SPKR_EN_SINK_CURR_FROM_REF_VOLT_CIR_PROC 58 +#define SPKR_IS_SINK_CURR_FROM_REF_VOLT_CIR_EN_PROC 59 +#define SPKR_ADD_RIGHT_LEFT_CHAN_PROC 60 +#define SPKR_SET_GAIN_PROC 61 +#define SPKR_EN_PROC 62 +#define HSED_SET_PERIOD_PROC 63 +#define HSED_SET_HYSTERESIS_PROC 64 +#define HSED_SET_CURRENT_THRESHOLD_PROC 65 +#define HSED_ENABLE_PROC 66 +#define HIGH_CURRENT_LED_SET_CURRENT_PROC 67 +#define HIGH_CURRENT_LED_SET_POLARITY_PROC 68 +#define HIGH_CURRENT_LED_SET_MODE_PROC 69 +#define LP_FORCE_LPM_CONTROL_PROC 70 +#define LOW_CURRENT_LED_SET_EXT_SIGNAL_PROC 71 +#define LOW_CURRENT_LED_SET_CURRENT_PROC 72 +#define SPKR_SET_VSEL_LDO_PROC 86 +#define HP_SPKR_CTRL_AUX_GAIN_INPUT_PROC 87 +#define HP_SPKR_MSTR_EN_PROC 88 +#define SPKR_SET_BOOST_PROC 89 +#define HP_SPKR_PRM_IN_EN_PROC 90 +#define HP_SPKR_CTRL_PRM_GAIN_INPUT_PROC 91 +#define HP_SPKR_MUTE_EN_PROC 92 +#define SPKR_BYPASS_EN_PROC 93 +#define HP_SPKR_AUX_IN_EN_PROC 94 +#define XO_CORE_FORCE_ENABLE 96 +#define GPIO_SET_CURRENT_SOURCE_PULLS_PROC 97 +#define GPIO_SET_GPIO_DIRECTION_INPUT_PROC 98 +#define GPIO_SET_EXT_PIN_CONFIG_PROC 99 +#define GPIO_SET_GPIO_CONFIG_PROC 100 +#define GPIO_CONFIG_DIGITAL_OUTPUT_PROC 101 +#define GPIO_GET_GPIO_DIRECTION_PROC 102 +#define GPIO_SET_SLEEP_CLK_CONFIG_PROC 103 +#define GPIO_CONFIG_DIGITAL_INPUT_PROC 104 +#define GPIO_SET_OUTPUT_BUFFER_CONFIGURATION_PROC 105 +#define GPIO_SET_PROC 106 +#define GPIO_CONFIG_MODE_SELECTION_PROC 107 +#define GPIO_SET_INVERSION_CONFIGURATION_PROC 108 +#define GPIO_SET_GPIO_DIRECTION_OUTPUT_PROC 109 +#define GPIO_SET_SOURCE_CONFIGURATION_PROC 110 +#define GPIO_GET_PROC 111 +#define GPIO_SET_VOLTAGE_SOURCE_PROC 112 +#define GPIO_SET_OUTPUT_BUFFER_DRIVE_STRENGTH_PROC 113 + +/* rpc related */ +#define PMIC_RPC_TIMEOUT (5*HZ) + +#define PMIC_PDEV_NAME "rs00010001:00000000" +#define PMIC_RPC_PROG 0x30000061 +#define PMIC_RPC_VER_1_1 0x00010001 +#define PMIC_RPC_VER_2_1 0x00020001 +#define PMIC_RPC_VER_3_1 0x00030001 +#define PMIC_RPC_VER_5_1 0x00050001 +#define PMIC_RPC_VER_6_1 0x00060001 + +/* error bit flags defined by modem side */ +#define PM_ERR_FLAG__PAR1_OUT_OF_RANGE (0x0001) +#define PM_ERR_FLAG__PAR2_OUT_OF_RANGE (0x0002) +#define PM_ERR_FLAG__PAR3_OUT_OF_RANGE (0x0004) +#define PM_ERR_FLAG__PAR4_OUT_OF_RANGE (0x0008) +#define PM_ERR_FLAG__PAR5_OUT_OF_RANGE (0x0010) + +#define PM_ERR_FLAG__ALL_PARMS_OUT_OF_RANGE (0x001F) /* all 5 previous */ + +#define PM_ERR_FLAG__SBI_OPT_ERR (0x0080) +#define PM_ERR_FLAG__FEATURE_NOT_SUPPORTED (0x0100) + +#define PMIC_BUFF_SIZE 256 + +struct pmic_buf { + char *start; /* buffer start addr */ + char *end; /* buffer end addr */ + int size; /* buffer size */ + char *data; /* payload begin addr */ + int len; /* payload len */ +}; + +static DEFINE_MUTEX(pmic_mtx); + +struct pmic_ctrl { + int inited; + struct pmic_buf tbuf; + struct pmic_buf rbuf; + struct msm_rpc_endpoint *endpoint; +}; + +static struct pmic_ctrl pmic_ctrl = { + .inited = -1, +}; + +/* Add newer versions at the top of array */ +static const unsigned int rpc_vers[] = { + PMIC_RPC_VER_6_1, + PMIC_RPC_VER_5_1, + PMIC_RPC_VER_3_1, + PMIC_RPC_VER_2_1, + PMIC_RPC_VER_1_1, +}; + +static int pmic_rpc_req_reply(struct pmic_buf *tbuf, + struct pmic_buf *rbuf, int proc); +static int pmic_rpc_set_only(uint data0, uint data1, uint data2, + uint data3, int num, int proc); +static int pmic_rpc_set_struct(int, uint, uint *data, uint size, int proc); +static int pmic_rpc_set_get(uint setdata, uint *getdata, int size, int proc); +static int pmic_rpc_get_only(uint *getdata, int size, int proc); + +static int pmic_buf_init(void) +{ + struct pmic_ctrl *pm = &pmic_ctrl; + + memset(&pmic_ctrl, 0, sizeof(pmic_ctrl)); + + pm->tbuf.start = kmalloc(PMIC_BUFF_SIZE, GFP_KERNEL); + if (pm->tbuf.start == NULL) { + printk(KERN_ERR "%s:%u\n", __func__, __LINE__); + return -ENOMEM; + } + + pm->tbuf.data = pm->tbuf.start; + pm->tbuf.size = PMIC_BUFF_SIZE; + pm->tbuf.end = pm->tbuf.start + PMIC_BUFF_SIZE; + pm->tbuf.len = 0; + + pm->rbuf.start = kmalloc(PMIC_BUFF_SIZE, GFP_KERNEL); + if (pm->rbuf.start == NULL) { + kfree(pm->tbuf.start); + printk(KERN_ERR "%s:%u\n", __func__, __LINE__); + return -ENOMEM; + } + pm->rbuf.data = pm->rbuf.start; + pm->rbuf.size = PMIC_BUFF_SIZE; + pm->rbuf.end = pm->rbuf.start + PMIC_BUFF_SIZE; + pm->rbuf.len = 0; + + pm->inited = 1; + + return 0; +} + +static inline void pmic_buf_reserve(struct pmic_buf *bp, int len) +{ + bp->data += len; +} + +static inline void pmic_buf_reset(struct pmic_buf *bp) +{ + bp->data = bp->start; + bp->len = 0; +} + +static int modem_to_linux_err(uint err) +{ + if (err == 0) + return 0; + + if (err & PM_ERR_FLAG__ALL_PARMS_OUT_OF_RANGE) + return -EINVAL; /* PM_ERR_FLAG__PAR[1..5]_OUT_OF_RANGE */ + + if (err & PM_ERR_FLAG__SBI_OPT_ERR) + return -EIO; + + if (err & PM_ERR_FLAG__FEATURE_NOT_SUPPORTED) + return -ENOSYS; + + return -EPERM; +} + +static int pmic_put_tx_data(struct pmic_buf *tp, uint datav) +{ + uint *lp; + + if ((tp->size - tp->len) < sizeof(datav)) { + printk(KERN_ERR "%s: OVERFLOW size=%d len=%d\n", + __func__, tp->size, tp->len); + return -1; + } + + lp = (uint *)tp->data; + *lp = cpu_to_be32(datav); + tp->data += sizeof(datav); + tp->len += sizeof(datav); + + return sizeof(datav); +} + +static int pmic_pull_rx_data(struct pmic_buf *rp, uint *datap) +{ + uint *lp; + + if (rp->len < sizeof(*datap)) { + printk(KERN_ERR "%s: UNDERRUN len=%d\n", __func__, rp->len); + return -1; + } + lp = (uint *)rp->data; + *datap = be32_to_cpu(*lp); + rp->data += sizeof(*datap); + rp->len -= sizeof(*datap); + + return sizeof(*datap); +} + + +/* + * + * +-------------------+ + * | PROC cmd layer | + * +-------------------+ + * | RPC layer | + * +-------------------+ + * + * 1) network byte order + * 2) RPC request header(40 bytes) and RPC reply header (24 bytes) + * 3) each transaction consists of a request and reply + * 3) PROC (comamnd) layer has its own sub-protocol defined + * 4) sub-protocol can be grouped to follwoing 7 cases: + * a) set one argument, no get + * b) set two argument, no get + * c) set three argument, no get + * d) set a struct, no get + * e) set a argument followed by a struct, no get + * f) set a argument, get a argument + * g) no set, get either a argument or a struct + */ + +/** + * pmic_rpc_req_reply() - send request and wait for reply + * @tbuf: buffer contains arguments + * @rbuf: buffer to be filled with arguments at reply + * @proc: command/request id + * + * This function send request to modem and wait until reply received + */ +static int pmic_rpc_req_reply(struct pmic_buf *tbuf, struct pmic_buf *rbuf, + int proc) +{ + struct pmic_ctrl *pm = &pmic_ctrl; + int ans, len, i; + + + if ((pm->endpoint == NULL) || IS_ERR(pm->endpoint)) { + for (i = 0; i < ARRAY_SIZE(rpc_vers); i++) { + pm->endpoint = msm_rpc_connect_compatible(PMIC_RPC_PROG, + rpc_vers[i], 0); + + if (IS_ERR(pm->endpoint)) { + ans = PTR_ERR(pm->endpoint); + printk(KERN_ERR "%s: init rpc failed! ans = %d" + " for 0x%x version, fallback\n", + __func__, ans, rpc_vers[i]); + } else { + printk(KERN_DEBUG "%s: successfully connected" + " to 0x%x rpc version\n", + __func__, rpc_vers[i]); + break; + } + } + } + + if (IS_ERR(pm->endpoint)) { + ans = PTR_ERR(pm->endpoint); + return ans; + } + + /* + * data is point to next available space at this moment, + * move it back to beginning of request header and increase + * the length + */ + tbuf->data = tbuf->start; + tbuf->len += sizeof(struct rpc_request_hdr); + + len = msm_rpc_call_reply(pm->endpoint, proc, + tbuf->data, tbuf->len, + rbuf->data, rbuf->size, + PMIC_RPC_TIMEOUT); + + if (len <= 0) { + printk(KERN_ERR "%s: rpc failed! len = %d\n", __func__, len); + pm->endpoint = NULL; /* re-connect later ? */ + return len; + } + + rbuf->len = len; + /* strip off rpc_reply_hdr */ + rbuf->data += sizeof(struct rpc_reply_hdr); + rbuf->len -= sizeof(struct rpc_reply_hdr); + + return rbuf->len; +} + +/** + * pmic_rpc_set_only() - set arguments and no get + * @data0: first argumrnt + * @data1: second argument + * @data2: third argument + * @data3: fourth argument + * @num: number of argument + * @proc: command/request id + * + * This function covers case a, b, and c + */ +static int pmic_rpc_set_only(uint data0, uint data1, uint data2, uint data3, + int num, int proc) +{ + struct pmic_ctrl *pm = &pmic_ctrl; + struct pmic_buf *tp; + struct pmic_buf *rp; + int stat; + + + if (mutex_lock_interruptible(&pmic_mtx)) + return -ERESTARTSYS; + + if (pm->inited <= 0) { + stat = pmic_buf_init(); + if (stat < 0) { + mutex_unlock(&pmic_mtx); + return stat; + } + } + + tp = &pm->tbuf; + rp = &pm->rbuf; + + pmic_buf_reset(tp); + pmic_buf_reserve(tp, sizeof(struct rpc_request_hdr)); + pmic_buf_reset(rp); + + if (num > 0) + pmic_put_tx_data(tp, data0); + + if (num > 1) + pmic_put_tx_data(tp, data1); + + if (num > 2) + pmic_put_tx_data(tp, data2); + + if (num > 3) + pmic_put_tx_data(tp, data3); + + stat = pmic_rpc_req_reply(tp, rp, proc); + if (stat < 0) { + mutex_unlock(&pmic_mtx); + return stat; + } + + pmic_pull_rx_data(rp, &stat); /* result from server */ + + mutex_unlock(&pmic_mtx); + + return modem_to_linux_err(stat); +} + +/** + * pmic_rpc_set_struct() - set the whole struct + * @xflag: indicates an extra argument + * @xdata: the extra argument + * @*data: starting address of struct + * @size: size of struct + * @proc: command/request id + * + * This fucntion covers case d and e + */ +static int pmic_rpc_set_struct(int xflag, uint xdata, uint *data, uint size, + int proc) +{ + struct pmic_ctrl *pm = &pmic_ctrl; + struct pmic_buf *tp; + struct pmic_buf *rp; + int i, stat, more_data; + + + if (mutex_lock_interruptible(&pmic_mtx)) + return -ERESTARTSYS; + + if (pm->inited <= 0) { + stat = pmic_buf_init(); + if (stat < 0) { + mutex_unlock(&pmic_mtx); + return stat; + } + } + + tp = &pm->tbuf; + rp = &pm->rbuf; + + pmic_buf_reset(tp); + pmic_buf_reserve(tp, sizeof(struct rpc_request_hdr)); + pmic_buf_reset(rp); + + if (xflag) + pmic_put_tx_data(tp, xdata); + + more_data = 1; /* tell server there have more data followed */ + pmic_put_tx_data(tp, more_data); + + size >>= 2; + for (i = 0; i < size; i++) { + pmic_put_tx_data(tp, *data); + data++; + } + + stat = pmic_rpc_req_reply(tp, rp, proc); + if (stat < 0) { + mutex_unlock(&pmic_mtx); + return stat; + } + + pmic_pull_rx_data(rp, &stat); /* result from server */ + + mutex_unlock(&pmic_mtx); + + return modem_to_linux_err(stat); +} + +/** + * pmic_rpc_set_get() - set one argument and get one argument + * @setdata: set argument + * @*getdata: memory to store argumnet + * @size: size of memory + * @proc: command/request id + * + * This function covers case f + */ +static int pmic_rpc_set_get(uint setdata, uint *getdata, int size, int proc) +{ + struct pmic_ctrl *pm = &pmic_ctrl; + struct pmic_buf *tp; + struct pmic_buf *rp; + unsigned int *lp; + int i, stat, more_data; + + + if (mutex_lock_interruptible(&pmic_mtx)) + return -ERESTARTSYS; + + if (pm->inited <= 0) { + stat = pmic_buf_init(); + if (stat < 0) { + mutex_unlock(&pmic_mtx); + return stat; + } + } + + tp = &pm->tbuf; + rp = &pm->rbuf; + + pmic_buf_reset(tp); + pmic_buf_reserve(tp, sizeof(struct rpc_request_hdr)); + pmic_buf_reset(rp); + + pmic_put_tx_data(tp, setdata); + + /* + * more_data = TRUE to ask server reply with requested datum + * otherwise, server will reply without datum + */ + more_data = (getdata != NULL); + pmic_put_tx_data(tp, more_data); + + stat = pmic_rpc_req_reply(tp, rp, proc); + if (stat < 0) { + mutex_unlock(&pmic_mtx); + return stat; + } + + pmic_pull_rx_data(rp, &stat); /* result from server */ + pmic_pull_rx_data(rp, &more_data); + + if (more_data) { /* more data followed */ + size >>= 2; + lp = getdata; + for (i = 0; i < size; i++) { + if (pmic_pull_rx_data(rp, lp++) < 0) + break; /* not supposed to happen */ + } + } + + mutex_unlock(&pmic_mtx); + + return modem_to_linux_err(stat); +} + +/** + * pmic_rpc_get_only() - get one or more than one arguments + * @*getdata: memory to store arguments + * @size: size of mmory + * @proc: command/request id + * + * This function covers case g + */ +static int pmic_rpc_get_only(uint *getdata, int size, int proc) +{ + struct pmic_ctrl *pm = &pmic_ctrl; + struct pmic_buf *tp; + struct pmic_buf *rp; + unsigned int *lp; + int i, stat, more_data; + + + if (mutex_lock_interruptible(&pmic_mtx)) + return -ERESTARTSYS; + + if (pm->inited <= 0) { + stat = pmic_buf_init(); + if (stat < 0) { + mutex_unlock(&pmic_mtx); + return stat; + } + } + + tp = &pm->tbuf; + rp = &pm->rbuf; + + pmic_buf_reset(tp); + pmic_buf_reserve(tp, sizeof(struct rpc_request_hdr)); + pmic_buf_reset(rp); + + /* + * more_data = TRUE to ask server reply with requested datum + * otherwise, server will reply without datum + */ + more_data = (getdata != NULL); + pmic_put_tx_data(tp, more_data); + + stat = pmic_rpc_req_reply(tp, rp, proc); + if (stat < 0) { + mutex_unlock(&pmic_mtx); + return stat; + } + + pmic_pull_rx_data(rp, &stat); /* result from server */ + pmic_pull_rx_data(rp, &more_data); + + if (more_data) { /* more data followed */ + size >>= 2; + lp = getdata; + for (i = 0; i < size; i++) { + if (pmic_pull_rx_data(rp, lp++) < 0) + break; /* not supposed to happen */ + } + } + + mutex_unlock(&pmic_mtx); + + return modem_to_linux_err(stat); +} + + +int pmic_lp_mode_control(enum switch_cmd cmd, enum vreg_lp_id id) +{ + return pmic_rpc_set_only(cmd, id, 0, 0, 2, LP_MODE_CONTROL_PROC); +} +EXPORT_SYMBOL(pmic_lp_mode_control); + +int pmic_vreg_set_level(enum vreg_id vreg, int level) +{ + return pmic_rpc_set_only(vreg, level, 0, 0, 2, VREG_SET_LEVEL_PROC); +} +EXPORT_SYMBOL(pmic_vreg_set_level); + +int pmic_vreg_pull_down_switch(enum switch_cmd cmd, enum vreg_pdown_id id) +{ + return pmic_rpc_set_only(cmd, id, 0, 0, 2, VREG_PULL_DOWN_SWITCH_PROC); +} +EXPORT_SYMBOL(pmic_vreg_pull_down_switch); + +int pmic_secure_mpp_control_digital_output(enum mpp_which which, + enum mpp_dlogic_level level, + enum mpp_dlogic_out_ctrl out) +{ + return pmic_rpc_set_only(which, level, out, 0, 3, + SECURE_MPP_CONFIG_DIGITAL_OUTPUT_PROC); +} +EXPORT_SYMBOL(pmic_secure_mpp_control_digital_output); + +int pmic_secure_mpp_config_i_sink(enum mpp_which which, + enum mpp_i_sink_level level, + enum mpp_i_sink_switch onoff) +{ + return pmic_rpc_set_only(which, level, onoff, 0, 3, + SECURE_MPP_CONFIG_I_SINK_PROC); +} +EXPORT_SYMBOL(pmic_secure_mpp_config_i_sink); + +int pmic_secure_mpp_config_digital_input(enum mpp_which which, + enum mpp_dlogic_level level, + enum mpp_dlogic_in_dbus dbus) +{ + return pmic_rpc_set_only(which, level, dbus, 0, 3, + SECURE_MPP_CONFIG_DIGITAL_INPUT_PROC); +} +EXPORT_SYMBOL(pmic_secure_mpp_config_digital_input); + +int pmic_rtc_start(struct rtc_time *time) +{ + return pmic_rpc_set_struct(0, 0, (uint *)time, sizeof(*time), + RTC_START_PROC); +} +EXPORT_SYMBOL(pmic_rtc_start); + +int pmic_rtc_stop(void) +{ + return pmic_rpc_set_only(0, 0, 0, 0, 0, RTC_STOP_PROC); +} +EXPORT_SYMBOL(pmic_rtc_stop); + +int pmic_rtc_get_time(struct rtc_time *time) +{ + return pmic_rpc_get_only((uint *)time, sizeof(*time), + RTC_GET_TIME_PROC); +} +EXPORT_SYMBOL(pmic_rtc_get_time); + +int pmic_rtc_enable_alarm(enum rtc_alarm alarm, + struct rtc_time *time) +{ + return pmic_rpc_set_struct(1, alarm, (uint *)time, sizeof(*time), + RTC_ENABLE_ALARM_PROC); +} +EXPORT_SYMBOL(pmic_rtc_enable_alarm); + +int pmic_rtc_disable_alarm(enum rtc_alarm alarm) +{ + return pmic_rpc_set_only(alarm, 0, 0, 0, 1, RTC_DISABLE_ALARM_PROC); +} +EXPORT_SYMBOL(pmic_rtc_disable_alarm); + +int pmic_rtc_get_alarm_time(enum rtc_alarm alarm, + struct rtc_time *time) +{ + return pmic_rpc_set_get(alarm, (uint *)time, sizeof(*time), + RTC_GET_ALARM_TIME_PROC); +} +EXPORT_SYMBOL(pmic_rtc_get_alarm_time); + +int pmic_rtc_get_alarm_status(uint *status) +{ + return pmic_rpc_get_only(status, sizeof(*status), + RTC_GET_ALARM_STATUS_PROC); +} +EXPORT_SYMBOL(pmic_rtc_get_alarm_status); + +int pmic_rtc_set_time_adjust(uint adjust) +{ + return pmic_rpc_set_only(adjust, 0, 0, 0, 1, + RTC_SET_TIME_ADJUST_PROC); +} +EXPORT_SYMBOL(pmic_rtc_set_time_adjust); + +int pmic_rtc_get_time_adjust(uint *adjust) +{ + return pmic_rpc_get_only(adjust, sizeof(*adjust), + RTC_GET_TIME_ADJUST_PROC); +} +EXPORT_SYMBOL(pmic_rtc_get_time_adjust); + +/* + * generic speaker + */ +int pmic_speaker_cmd(const enum spkr_cmd cmd) +{ + return pmic_rpc_set_only(cmd, 0, 0, 0, 1, SPEAKER_CMD_PROC); +} +EXPORT_SYMBOL(pmic_speaker_cmd); + +int pmic_set_spkr_configuration(struct spkr_config_mode *cfg) +{ + return pmic_rpc_set_struct(0, 0, (uint *)cfg, sizeof(*cfg), + SET_SPKR_CONFIGURATION_PROC); +} +EXPORT_SYMBOL(pmic_set_spkr_configuration); + +int pmic_get_spkr_configuration(struct spkr_config_mode *cfg) +{ + return pmic_rpc_get_only((uint *)cfg, sizeof(*cfg), + GET_SPKR_CONFIGURATION_PROC); +} +EXPORT_SYMBOL(pmic_get_spkr_configuration); + +int pmic_spkr_en_right_chan(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, SPKR_EN_RIGHT_CHAN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_en_right_chan); + +int pmic_spkr_is_right_chan_en(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), + SPKR_IS_RIGHT_CHAN_EN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_right_chan_en); + +int pmic_spkr_en_left_chan(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, SPKR_EN_LEFT_CHAN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_en_left_chan); + +int pmic_spkr_is_left_chan_en(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), + SPKR_IS_LEFT_CHAN_EN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_left_chan_en); + +int pmic_set_speaker_gain(enum spkr_gain gain) +{ + return pmic_rpc_set_only(gain, 0, 0, 0, 1, SET_SPEAKER_GAIN_PROC); +} +EXPORT_SYMBOL(pmic_set_speaker_gain); + +int pmic_set_speaker_delay(enum spkr_dly delay) +{ + return pmic_rpc_set_only(delay, 0, 0, 0, 1, SET_SPEAKER_DELAY_PROC); +} +EXPORT_SYMBOL(pmic_set_speaker_delay); + +int pmic_speaker_1k6_zin_enable(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, + SPEAKER_1K6_ZIN_ENABLE_PROC); +} +EXPORT_SYMBOL(pmic_speaker_1k6_zin_enable); + +int pmic_spkr_set_mux_hpf_corner_freq(enum spkr_hpf_corner_freq freq) +{ + return pmic_rpc_set_only(freq, 0, 0, 0, 1, + SPKR_SET_MUX_HPF_CORNER_FREQ_PROC); +} +EXPORT_SYMBOL(pmic_spkr_set_mux_hpf_corner_freq); + +int pmic_spkr_get_mux_hpf_corner_freq(enum spkr_hpf_corner_freq *freq) +{ + return pmic_rpc_get_only(freq, sizeof(*freq), + SPKR_GET_MUX_HPF_CORNER_FREQ_PROC); +} +EXPORT_SYMBOL(pmic_spkr_get_mux_hpf_corner_freq); + +int pmic_spkr_select_usb_with_hpf_20hz(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, + SPKR_SELECT_USB_WITH_HPF_20HZ_PROC); +} +EXPORT_SYMBOL(pmic_spkr_select_usb_with_hpf_20hz); + +int pmic_spkr_is_usb_with_hpf_20hz(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), + SPKR_IS_USB_WITH_HPF_20HZ_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_usb_with_hpf_20hz); + +int pmic_spkr_bypass_mux(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, SPKR_BYPASS_MUX_PROC); +} +EXPORT_SYMBOL(pmic_spkr_bypass_mux); + +int pmic_spkr_is_mux_bypassed(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), + SPKR_IS_MUX_BYPASSED_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_mux_bypassed); + +int pmic_spkr_en_hpf(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, SPKR_EN_HPF_PROC); +} +EXPORT_SYMBOL(pmic_spkr_en_hpf); + +int pmic_spkr_is_hpf_en(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), + SPKR_IS_HPF_EN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_hpf_en); + +int pmic_spkr_en_sink_curr_from_ref_volt_cir(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, + SPKR_EN_SINK_CURR_FROM_REF_VOLT_CIR_PROC); +} +EXPORT_SYMBOL(pmic_spkr_en_sink_curr_from_ref_volt_cir); + +int pmic_spkr_is_sink_curr_from_ref_volt_cir_en(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), + SPKR_IS_SINK_CURR_FROM_REF_VOLT_CIR_EN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_sink_curr_from_ref_volt_cir_en); + +/* + * speaker indexed by left_right + */ +int pmic_spkr_en(enum spkr_left_right left_right, uint enable) +{ + return pmic_rpc_set_only(left_right, enable, 0, 0, 2, SPKR_EN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_en); + +int pmic_spkr_is_en(enum spkr_left_right left_right, uint *enabled) +{ + return pmic_rpc_set_get(left_right, enabled, sizeof(*enabled), + SPKR_IS_EN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_en); + +int pmic_spkr_set_gain(enum spkr_left_right left_right, enum spkr_gain gain) +{ + return pmic_rpc_set_only(left_right, gain, 0, 0, 2, SPKR_SET_GAIN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_set_gain); + +int pmic_spkr_get_gain(enum spkr_left_right left_right, enum spkr_gain *gain) +{ + return pmic_rpc_set_get(left_right, gain, sizeof(*gain), + SPKR_GET_GAIN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_get_gain); + +int pmic_spkr_set_delay(enum spkr_left_right left_right, enum spkr_dly delay) +{ + return pmic_rpc_set_only(left_right, delay, 0, 0, 2, + SPKR_SET_DELAY_PROC); +} +EXPORT_SYMBOL(pmic_spkr_set_delay); + +int pmic_spkr_get_delay(enum spkr_left_right left_right, enum spkr_dly *delay) +{ + return pmic_rpc_set_get(left_right, delay, sizeof(*delay), + SPKR_GET_DELAY_PROC); +} +EXPORT_SYMBOL(pmic_spkr_get_delay); + +int pmic_spkr_en_mute(enum spkr_left_right left_right, uint enabled) +{ + return pmic_rpc_set_only(left_right, enabled, 0, 0, 2, + SPKR_EN_MUTE_PROC); +} +EXPORT_SYMBOL(pmic_spkr_en_mute); + +int pmic_spkr_is_mute_en(enum spkr_left_right left_right, uint *enabled) +{ + return pmic_rpc_set_get(left_right, enabled, sizeof(*enabled), + SPKR_IS_MUTE_EN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_mute_en); + +int pmic_spkr_set_vsel_ldo(enum spkr_left_right left_right, + enum spkr_ldo_v_sel vlt_cntrl) +{ + return pmic_rpc_set_only(left_right, vlt_cntrl, 0, 0, 2, + SPKR_SET_VSEL_LDO_PROC); +} +EXPORT_SYMBOL(pmic_spkr_set_vsel_ldo); + +int pmic_spkr_set_boost(enum spkr_left_right left_right, uint enable) +{ + return pmic_rpc_set_only(left_right, enable, 0, 0, 2, + SPKR_SET_BOOST_PROC); +} +EXPORT_SYMBOL(pmic_spkr_set_boost); + +int pmic_spkr_bypass_en(enum spkr_left_right left_right, uint enable) +{ + return pmic_rpc_set_only(left_right, enable, 0, 0, 2, + SPKR_BYPASS_EN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_bypass_en); + +/* + * mic + */ +int pmic_mic_en(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, MIC_EN_PROC); +} +EXPORT_SYMBOL(pmic_mic_en); + +int pmic_mic_is_en(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), MIC_IS_EN_PROC); +} +EXPORT_SYMBOL(pmic_mic_is_en); + +int pmic_mic_set_volt(enum mic_volt vol) +{ + return pmic_rpc_set_only(vol, 0, 0, 0, 1, MIC_SET_VOLT_PROC); +} +EXPORT_SYMBOL(pmic_mic_set_volt); + +int pmic_mic_get_volt(enum mic_volt *voltage) +{ + return pmic_rpc_get_only(voltage, sizeof(*voltage), MIC_GET_VOLT_PROC); +} +EXPORT_SYMBOL(pmic_mic_get_volt); + +int pmic_vib_mot_set_volt(uint vol) +{ + return pmic_rpc_set_only(vol, 0, 0, 0, 1, VIB_MOT_SET_VOLT_PROC); +} +EXPORT_SYMBOL(pmic_vib_mot_set_volt); + +int pmic_vib_mot_set_mode(enum pm_vib_mot_mode mode) +{ + return pmic_rpc_set_only(mode, 0, 0, 0, 1, VIB_MOT_SET_MODE_PROC); +} +EXPORT_SYMBOL(pmic_vib_mot_set_mode); + +int pmic_vib_mot_set_polarity(enum pm_vib_mot_pol pol) +{ + return pmic_rpc_set_only(pol, 0, 0, 0, 1, VIB_MOT_SET_POLARITY_PROC); +} +EXPORT_SYMBOL(pmic_vib_mot_set_polarity); + +int pmic_vid_en(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, VID_EN_PROC); +} +EXPORT_SYMBOL(pmic_vid_en); + +int pmic_vid_is_en(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), VID_IS_EN_PROC); +} +EXPORT_SYMBOL(pmic_vid_is_en); + +int pmic_vid_load_detect_en(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, VID_LOAD_DETECT_EN_PROC); +} +EXPORT_SYMBOL(pmic_vid_load_detect_en); + +int pmic_set_led_intensity(enum ledtype type, int level) +{ + return pmic_rpc_set_only(type, level, 0, 0, 2, SET_LED_INTENSITY_PROC); +} +EXPORT_SYMBOL(pmic_set_led_intensity); + +int pmic_flash_led_set_current(const uint16_t milliamps) +{ + return pmic_rpc_set_only(milliamps, 0, 0, 0, 1, + FLASH_LED_SET_CURRENT_PROC); +} +EXPORT_SYMBOL(pmic_flash_led_set_current); + +int pmic_flash_led_set_mode(enum flash_led_mode mode) +{ + return pmic_rpc_set_only((int)mode, 0, 0, 0, 1, + FLASH_LED_SET_MODE_PROC); +} +EXPORT_SYMBOL(pmic_flash_led_set_mode); + +int pmic_flash_led_set_polarity(enum flash_led_pol pol) +{ + return pmic_rpc_set_only((int)pol, 0, 0, 0, 1, + FLASH_LED_SET_POLARITY_PROC); +} +EXPORT_SYMBOL(pmic_flash_led_set_polarity); + +int pmic_spkr_add_right_left_chan(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, + SPKR_ADD_RIGHT_LEFT_CHAN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_add_right_left_chan); + +int pmic_spkr_is_right_left_chan_added(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), + SPKR_IS_RIGHT_LEFT_CHAN_ADDED_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_right_left_chan_added); + +int pmic_spkr_en_stereo(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, SPKR_EN_STEREO_PROC); +} +EXPORT_SYMBOL(pmic_spkr_en_stereo); + +int pmic_spkr_is_stereo_en(uint *enabled) +{ + return pmic_rpc_get_only(enabled, sizeof(*enabled), + SPKR_IS_STEREO_EN_PROC); +} +EXPORT_SYMBOL(pmic_spkr_is_stereo_en); + +int pmic_hsed_set_period( + enum hsed_controller controller, + enum hsed_period_pre_div period_pre_div, + enum hsed_period_time period_time +) +{ + return pmic_rpc_set_only(controller, period_pre_div, period_time, 0, + 3, + HSED_SET_PERIOD_PROC); +} +EXPORT_SYMBOL(pmic_hsed_set_period); + +int pmic_hsed_set_hysteresis( + enum hsed_controller controller, + enum hsed_hyst_pre_div hyst_pre_div, + enum hsed_hyst_time hyst_time +) +{ + return pmic_rpc_set_only(controller, hyst_pre_div, hyst_time, 0, + 3, + HSED_SET_HYSTERESIS_PROC); +} +EXPORT_SYMBOL(pmic_hsed_set_hysteresis); + +int pmic_hsed_set_current_threshold( + enum hsed_controller controller, + enum hsed_switch switch_hsed, + uint32_t current_threshold +) +{ + return pmic_rpc_set_only(controller, switch_hsed, current_threshold, 0, + 3, + HSED_SET_CURRENT_THRESHOLD_PROC); +} +EXPORT_SYMBOL(pmic_hsed_set_current_threshold); + +int pmic_hsed_enable( + enum hsed_controller controller, + enum hsed_enable enable_hsed +) +{ + return pmic_rpc_set_only(controller, enable_hsed, 0, 0, + 2, + HSED_ENABLE_PROC); +} +EXPORT_SYMBOL(pmic_hsed_enable); + +int pmic_high_current_led_set_current(enum high_current_led led, + uint16_t milliamps) +{ + return pmic_rpc_set_only(led, milliamps, 0, 0, + 2, + HIGH_CURRENT_LED_SET_CURRENT_PROC); +} +EXPORT_SYMBOL(pmic_high_current_led_set_current); + +int pmic_high_current_led_set_polarity(enum high_current_led led, + enum flash_led_pol polarity) +{ + return pmic_rpc_set_only(led, polarity, 0, 0, + 2, + HIGH_CURRENT_LED_SET_POLARITY_PROC); +} +EXPORT_SYMBOL(pmic_high_current_led_set_polarity); + +int pmic_high_current_led_set_mode(enum high_current_led led, + enum flash_led_mode mode) +{ + return pmic_rpc_set_only(led, mode, 0, 0, + 2, + HIGH_CURRENT_LED_SET_MODE_PROC); +} +EXPORT_SYMBOL(pmic_high_current_led_set_mode); + +int pmic_lp_force_lpm_control(enum switch_cmd cmd, + enum vreg_lpm_id vreg) +{ + return pmic_rpc_set_only(cmd, vreg, 0, 0, + 2, + LP_FORCE_LPM_CONTROL_PROC); +} +EXPORT_SYMBOL(pmic_lp_force_lpm_control); + +int pmic_low_current_led_set_ext_signal(enum low_current_led led, + enum ext_signal sig) +{ + return pmic_rpc_set_only(led, sig, 0, 0, + 2, + LOW_CURRENT_LED_SET_EXT_SIGNAL_PROC); +} +EXPORT_SYMBOL(pmic_low_current_led_set_ext_signal); + +int pmic_low_current_led_set_current(enum low_current_led led, + uint16_t milliamps) +{ + return pmic_rpc_set_only(led, milliamps, 0, 0, + 2, + LOW_CURRENT_LED_SET_CURRENT_PROC); +} +EXPORT_SYMBOL(pmic_low_current_led_set_current); + +/* + * Head phone speaker + */ +int pmic_hp_spkr_mstr_en(enum hp_spkr_left_right left_right, uint enable) +{ + return pmic_rpc_set_only(left_right, enable, 0, 0, 2, + HP_SPKR_MSTR_EN_PROC); +} +EXPORT_SYMBOL(pmic_hp_spkr_mstr_en); + +int pmic_hp_spkr_mute_en(enum hp_spkr_left_right left_right, uint enable) +{ + return pmic_rpc_set_only(left_right, enable, 0, 0, 2, + HP_SPKR_MUTE_EN_PROC); +} +EXPORT_SYMBOL(pmic_hp_spkr_mute_en); + +int pmic_hp_spkr_prm_in_en(enum hp_spkr_left_right left_right, uint enable) +{ + return pmic_rpc_set_only(left_right, enable, 0, 0, 2, + HP_SPKR_PRM_IN_EN_PROC); +} +EXPORT_SYMBOL(pmic_hp_spkr_prm_in_en); + +int pmic_hp_spkr_aux_in_en(enum hp_spkr_left_right left_right, uint enable) +{ + return pmic_rpc_set_only(left_right, enable, 0, 0, 2, + HP_SPKR_AUX_IN_EN_PROC); +} +EXPORT_SYMBOL(pmic_hp_spkr_aux_in_en); + +int pmic_hp_spkr_ctrl_prm_gain_input(enum hp_spkr_left_right left_right, + uint prm_gain_ctl) +{ + return pmic_rpc_set_only(left_right, prm_gain_ctl, 0, 0, 2, + HP_SPKR_CTRL_PRM_GAIN_INPUT_PROC); +} +EXPORT_SYMBOL(pmic_hp_spkr_ctrl_prm_gain_input); + +int pmic_hp_spkr_ctrl_aux_gain_input(enum hp_spkr_left_right left_right, + uint aux_gain_ctl) +{ + return pmic_rpc_set_only(left_right, aux_gain_ctl, 0, 0, 2, + HP_SPKR_CTRL_AUX_GAIN_INPUT_PROC); +} +EXPORT_SYMBOL(pmic_hp_spkr_ctrl_aux_gain_input); + +int pmic_xo_core_force_enable(uint enable) +{ + return pmic_rpc_set_only(enable, 0, 0, 0, 1, XO_CORE_FORCE_ENABLE); +} +EXPORT_SYMBOL(pmic_xo_core_force_enable); + +int pmic_gpio_direction_input(unsigned gpio) +{ + return pmic_rpc_set_only(gpio, 0, 0, 0, 1, + GPIO_SET_GPIO_DIRECTION_INPUT_PROC); +} +EXPORT_SYMBOL(pmic_gpio_direction_input); + +int pmic_gpio_direction_output(unsigned gpio) +{ + return pmic_rpc_set_only(gpio, 0, 0, 0, 1, + GPIO_SET_GPIO_DIRECTION_OUTPUT_PROC); +} +EXPORT_SYMBOL(pmic_gpio_direction_output); + +int pmic_gpio_set_value(unsigned gpio, int value) +{ + return pmic_rpc_set_only(gpio, value, 0, 0, 2, GPIO_SET_PROC); +} +EXPORT_SYMBOL(pmic_gpio_set_value); + +int pmic_gpio_get_value(unsigned gpio) +{ + uint value; + int ret; + + ret = pmic_rpc_set_get(gpio, &value, sizeof(value), GPIO_GET_PROC); + if (ret < 0) + return ret; + return value ? 1 : 0; +} +EXPORT_SYMBOL(pmic_gpio_get_value); + +int pmic_gpio_get_direction(unsigned gpio) +{ + enum pmic_direction_mode dir; + int ret; + + ret = pmic_rpc_set_get(gpio, &dir, sizeof(dir), + GPIO_GET_GPIO_DIRECTION_PROC); + if (ret < 0) + return ret; + return dir; +} +EXPORT_SYMBOL(pmic_gpio_get_direction); + +int pmic_gpio_config(struct pm8xxx_gpio_rpc_cfg *param) +{ + return pmic_rpc_set_struct(0, 0, (uint *)param, sizeof(*param), + GPIO_SET_GPIO_CONFIG_PROC); +} +EXPORT_SYMBOL(pmic_gpio_config); diff --git a/arch/arm/mach-msm/pmic.h b/arch/arm/mach-msm/pmic.h new file mode 100644 index 00000000000..dcd5912626d --- /dev/null +++ b/arch/arm/mach-msm/pmic.h @@ -0,0 +1,295 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_PMIC_H +#define __ARCH_ARM_MACH_PMIC_H + +#include "proc_comm.h" + +enum spkr_left_right { + LEFT_SPKR, + RIGHT_SPKR, +}; + +enum spkr_gain { + SPKR_GAIN_MINUS16DB, /* -16 db */ + SPKR_GAIN_MINUS12DB, /* -12 db */ + SPKR_GAIN_MINUS08DB, /* -08 db */ + SPKR_GAIN_MINUS04DB, /* -04 db */ + SPKR_GAIN_00DB, /* 00 db */ + SPKR_GAIN_PLUS04DB, /* +04 db */ + SPKR_GAIN_PLUS08DB, /* +08 db */ + SPKR_GAIN_PLUS12DB, /* +12 db */ +}; + +enum spkr_dly { + SPKR_DLY_10MS, /* ~10 ms delay */ + SPKR_DLY_100MS, /* ~100 ms delay */ +}; + +enum spkr_hpf_corner_freq { + SPKR_FREQ_1_39KHZ, /* 1.39 kHz */ + SPKR_FREQ_0_64KHZ, /* 0.64 kHz */ + SPKR_FREQ_0_86KHZ, /* 0.86 kHz */ + SPKR_FREQ_0_51KHZ, /* 0.51 kHz */ + SPKR_FREQ_1_06KHZ, /* 1.06 kHz */ + SPKR_FREQ_0_57KHZ, /* 0.57 kHz */ + SPKR_FREQ_0_73KHZ, /* 0.73 kHz */ + SPKR_FREQ_0_47KHZ, /* 0.47 kHz */ + SPKR_FREQ_1_20KHZ, /* 1.20 kHz */ + SPKR_FREQ_0_60KHZ, /* 0.60 kHz */ + SPKR_FREQ_0_76KHZ, /* 0.76 kHz */ + SPKR_FREQ_0_49KHZ, /* 0.49 kHz */ + SPKR_FREQ_0_95KHZ, /* 0.95 kHz */ + SPKR_FREQ_0_54KHZ, /* 0.54 kHz */ + SPKR_FREQ_0_68KHZ, /* 0.68 kHz */ + SPKR_FREQ_0_45KHZ, /* 0.45 kHz */ +}; + +/* Turn the speaker on or off and enables or disables mute.*/ +enum spkr_cmd { + SPKR_DISABLE, /* Enable Speaker */ + SPKR_ENABLE, /* Disable Speaker */ + SPKR_MUTE_OFF, /* turn speaker mute off, SOUND ON */ + SPKR_MUTE_ON, /* turn speaker mute on, SOUND OFF */ + SPKR_OFF, /* turn speaker OFF (speaker disable and mute on) */ + SPKR_ON, /* turn speaker ON (speaker enable and mute off) */ + SPKR_SET_FREQ_CMD, /* set speaker frequency */ + SPKR_GET_FREQ_CMD, /* get speaker frequency */ + SPKR_SET_GAIN_CMD, /* set speaker gain */ + SPKR_GET_GAIN_CMD, /* get speaker gain */ + SPKR_SET_DELAY_CMD, /* set speaker delay */ + SPKR_GET_DELAY_CMD, /* get speaker delay */ + SPKR_SET_PDM_MODE, + SPKR_SET_PWM_MODE, +}; + +struct spkr_config_mode { + uint32_t is_right_chan_en; + uint32_t is_left_chan_en; + uint32_t is_right_left_chan_added; + uint32_t is_stereo_en; + uint32_t is_usb_with_hpf_20hz; + uint32_t is_mux_bypassed; + uint32_t is_hpf_en; + uint32_t is_sink_curr_from_ref_volt_cir_en; +}; + +enum mic_volt { + MIC_VOLT_2_00V, /* 2.00 V */ + MIC_VOLT_1_93V, /* 1.93 V */ + MIC_VOLT_1_80V, /* 1.80 V */ + MIC_VOLT_1_73V, /* 1.73 V */ +}; + +enum ledtype { + LED_LCD, + LED_KEYPAD, +}; + +enum flash_led_mode { + FLASH_LED_MODE__MANUAL, + FLASH_LED_MODE__DBUS1, + FLASH_LED_MODE__DBUS2, + FLASH_LED_MODE__DBUS3, +}; + +enum flash_led_pol { + FLASH_LED_POL__ACTIVE_HIGH, + FLASH_LED_POL__ACTIVE_LOW, +}; + +enum switch_cmd { + OFF_CMD, + ON_CMD +}; + +enum vreg_lp_id { + PM_VREG_LP_MSMA_ID, + PM_VREG_LP_MSMP_ID, + PM_VREG_LP_MSME1_ID, + PM_VREG_LP_GP3_ID, + PM_VREG_LP_MSMC_ID, + PM_VREG_LP_MSME2_ID, + PM_VREG_LP_GP4_ID, + PM_VREG_LP_GP1_ID, + PM_VREG_LP_RFTX_ID, + PM_VREG_LP_RFRX1_ID, + PM_VREG_LP_RFRX2_ID, + PM_VREG_LP_WLAN_ID, + PM_VREG_LP_MMC_ID, + PM_VREG_LP_RUIM_ID, + PM_VREG_LP_MSMC0_ID, + PM_VREG_LP_GP2_ID, + PM_VREG_LP_GP5_ID, + PM_VREG_LP_GP6_ID, + PM_VREG_LP_MPLL_ID, + PM_VREG_LP_RFUBM_ID, + PM_VREG_LP_RFA_ID, + PM_VREG_LP_CDC2_ID, + PM_VREG_LP_RFTX2_ID, + PM_VREG_LP_USIM_ID, + PM_VREG_LP_USB2P6_ID, + PM_VREG_LP_TCXO_ID, + PM_VREG_LP_USB3P3_ID, + + PM_VREG_LP_MSME_ID = PM_VREG_LP_MSME1_ID, + /* backward compatible enums only */ + PM_VREG_LP_CAM_ID = PM_VREG_LP_GP1_ID, + PM_VREG_LP_MDDI_ID = PM_VREG_LP_GP2_ID, + PM_VREG_LP_RUIM2_ID = PM_VREG_LP_GP3_ID, + PM_VREG_LP_AUX_ID = PM_VREG_LP_GP4_ID, + PM_VREG_LP_AUX2_ID = PM_VREG_LP_GP5_ID, + PM_VREG_LP_BT_ID = PM_VREG_LP_GP6_ID, + PM_VREG_LP_MSMC_LDO_ID = PM_VREG_LP_MSMC_ID, + PM_VREG_LP_MSME1_LDO_ID = PM_VREG_LP_MSME1_ID, + PM_VREG_LP_MSME2_LDO_ID = PM_VREG_LP_MSME2_ID, + PM_VREG_LP_RFA1_ID = PM_VREG_LP_RFRX2_ID, + PM_VREG_LP_RFA2_ID = PM_VREG_LP_RFTX2_ID, + PM_VREG_LP_XO_ID = PM_VREG_LP_TCXO_ID +}; + +enum mpp_which { + PM_MPP_1, + PM_MPP_2, + PM_MPP_3, + PM_MPP_4, + PM_MPP_5, + PM_MPP_6, + PM_MPP_7, + PM_MPP_8, + PM_MPP_9, + PM_MPP_10, + PM_MPP_11, + PM_MPP_12, + PM_MPP_13, + PM_MPP_14, + PM_MPP_15, + PM_MPP_16, + PM_MPP_17, + PM_MPP_18, + PM_MPP_19, + PM_MPP_20, + PM_MPP_21, + PM_MPP_22, + + PM_NUM_MPP_HAN = PM_MPP_4 + 1, + PM_NUM_MPP_KIP = PM_MPP_4 + 1, + PM_NUM_MPP_EPIC = PM_MPP_4 + 1, + PM_NUM_MPP_PM7500 = PM_MPP_22 + 1, + PM_NUM_MPP_PM6650 = PM_MPP_12 + 1, + PM_NUM_MPP_PM6658 = PM_MPP_12 + 1, + PM_NUM_MPP_PANORAMIX = PM_MPP_2 + 1, + PM_NUM_MPP_PM6640 = PM_NUM_MPP_PANORAMIX, + PM_NUM_MPP_PM6620 = PM_NUM_MPP_PANORAMIX +}; + +enum mpp_dlogic_level { + PM_MPP__DLOGIC__LVL_MSME, + PM_MPP__DLOGIC__LVL_MSMP, + PM_MPP__DLOGIC__LVL_RUIM, + PM_MPP__DLOGIC__LVL_MMC, + PM_MPP__DLOGIC__LVL_VDD, +}; + +enum mpp_dlogic_in_dbus { + PM_MPP__DLOGIC_IN__DBUS_NONE, + PM_MPP__DLOGIC_IN__DBUS1, + PM_MPP__DLOGIC_IN__DBUS2, + PM_MPP__DLOGIC_IN__DBUS3, +}; + +enum mpp_dlogic_out_ctrl { + PM_MPP__DLOGIC_OUT__CTRL_LOW, + PM_MPP__DLOGIC_OUT__CTRL_HIGH, + PM_MPP__DLOGIC_OUT__CTRL_MPP, + PM_MPP__DLOGIC_OUT__CTRL_NOT_MPP, +}; + +enum mpp_i_sink_level { + PM_MPP__I_SINK__LEVEL_5mA, + PM_MPP__I_SINK__LEVEL_10mA, + PM_MPP__I_SINK__LEVEL_15mA, + PM_MPP__I_SINK__LEVEL_20mA, + PM_MPP__I_SINK__LEVEL_25mA, + PM_MPP__I_SINK__LEVEL_30mA, + PM_MPP__I_SINK__LEVEL_35mA, + PM_MPP__I_SINK__LEVEL_40mA, +}; + +enum mpp_i_sink_switch { + PM_MPP__I_SINK__SWITCH_DIS, + PM_MPP__I_SINK__SWITCH_ENA, + PM_MPP__I_SINK__SWITCH_ENA_IF_MPP_HIGH, + PM_MPP__I_SINK__SWITCH_ENA_IF_MPP_LOW, +}; + +enum pm_vib_mot_mode { + PM_VIB_MOT_MODE__MANUAL, + PM_VIB_MOT_MODE__DBUS1, + PM_VIB_MOT_MODE__DBUS2, + PM_VIB_MOT_MODE__DBUS3, +}; + +enum pm_vib_mot_pol { + PM_VIB_MOT_POL__ACTIVE_HIGH, + PM_VIB_MOT_POL__ACTIVE_LOW, +}; + +struct rtc_time { + uint sec; +}; + +enum rtc_alarm { + PM_RTC_ALARM_1, +}; + + +int pmic_lp_mode_control(enum switch_cmd cmd, enum vreg_lp_id id); +int pmic_secure_mpp_control_digital_output(enum mpp_which which, + enum mpp_dlogic_level level, enum mpp_dlogic_out_ctrl out); +int pmic_secure_mpp_config_i_sink(enum mpp_which which, + enum mpp_i_sink_level level, enum mpp_i_sink_switch onoff); +int pmic_secure_mpp_config_digital_input(enum mpp_which which, + enum mpp_dlogic_level level, enum mpp_dlogic_in_dbus dbus); +int pmic_speaker_cmd(const enum spkr_cmd cmd); +int pmic_set_spkr_configuration(struct spkr_config_mode *cfg); +int pmic_spkr_en_right_chan(uint enable); +int pmic_spkr_en_left_chan(uint enable); +int pmic_spkr_en(enum spkr_left_right left_right, uint enabled); +int pmic_spkr_set_gain(enum spkr_left_right left_right, enum spkr_gain gain); +int pmic_set_speaker_gain(enum spkr_gain gain); +int pmic_set_speaker_delay(enum spkr_dly delay); +int pmic_speaker_1k6_zin_enable(uint enable); +int pmic_spkr_set_mux_hpf_corner_freq(enum spkr_hpf_corner_freq freq); +int pmic_spkr_select_usb_with_hpf_20hz(uint enable); +int pmic_spkr_bypass_mux(uint enable); +int pmic_spkr_en_hpf(uint enable); +int pmic_spkr_en_sink_curr_from_ref_volt_cir(uint enable); +int pmic_spkr_set_delay(enum spkr_left_right left_right, enum spkr_dly delay); +int pmic_spkr_en_mute(enum spkr_left_right left_right, uint enabled); +int pmic_mic_en(uint enable); +int pmic_mic_set_volt(enum mic_volt vol); +int pmic_set_led_intensity(enum ledtype type, int level); +int pmic_flash_led_set_current(uint16_t milliamps); +int pmic_flash_led_set_mode(enum flash_led_mode mode); +int pmic_flash_led_set_polarity(enum flash_led_pol pol); +int pmic_spkr_add_right_left_chan(uint enable); +int pmic_spkr_en_stereo(uint enable); +int pmic_vib_mot_set_volt(uint vol); +int pmic_vib_mot_set_mode(enum pm_vib_mot_mode mode); +int pmic_vib_mot_set_polarity(enum pm_vib_mot_pol pol); +int pmic_vid_en(uint enable); +int pmic_vid_load_detect_en(uint enable); + +#endif diff --git a/arch/arm/mach-msm/pmic8058-gpio.c b/arch/arm/mach-msm/pmic8058-gpio.c new file mode 100644 index 00000000000..63a26dedb4a --- /dev/null +++ b/arch/arm/mach-msm/pmic8058-gpio.c @@ -0,0 +1,705 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * Qualcomm PMIC8058 GPIO driver + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef CONFIG_GPIOLIB +#include "gpio_chip.h" +#endif + +/* GPIO registers */ +#define SSBI_REG_ADDR_GPIO_BASE 0x150 +#define SSBI_REG_ADDR_GPIO(n) (SSBI_REG_ADDR_GPIO_BASE + n) + +/* GPIO */ +#define PM8058_GPIO_BANK_MASK 0x70 +#define PM8058_GPIO_BANK_SHIFT 4 +#define PM8058_GPIO_WRITE 0x80 + +/* Bank 0 */ +#define PM8058_GPIO_VIN_MASK 0x0E +#define PM8058_GPIO_VIN_SHIFT 1 +#define PM8058_GPIO_MODE_ENABLE 0x01 + +/* Bank 1 */ +#define PM8058_GPIO_MODE_MASK 0x0C +#define PM8058_GPIO_MODE_SHIFT 2 +#define PM8058_GPIO_OUT_BUFFER 0x02 +#define PM8058_GPIO_OUT_INVERT 0x01 + +#define PM8058_GPIO_MODE_OFF 3 +#define PM8058_GPIO_MODE_OUTPUT 2 +#define PM8058_GPIO_MODE_INPUT 0 +#define PM8058_GPIO_MODE_BOTH 1 + +/* Bank 2 */ +#define PM8058_GPIO_PULL_MASK 0x0E +#define PM8058_GPIO_PULL_SHIFT 1 + +/* Bank 3 */ +#define PM8058_GPIO_OUT_STRENGTH_MASK 0x0C +#define PM8058_GPIO_OUT_STRENGTH_SHIFT 2 +#define PM8058_GPIO_PIN_ENABLE 0x00 +#define PM8058_GPIO_PIN_DISABLE 0x01 + +/* Bank 4 */ +#define PM8058_GPIO_FUNC_MASK 0x0E +#define PM8058_GPIO_FUNC_SHIFT 1 + +/* Bank 5 */ +#define PM8058_GPIO_NON_INT_POL_INV 0x08 +#define PM8058_GPIO_BANKS 6 + +struct pm8058_gpio_chip { + struct gpio_chip gpio_chip; + struct pm8058_chip *pm_chip; + struct mutex pm_lock; + u8 bank1[PM8058_GPIOS]; +}; + +static int pm8058_gpio_get(struct pm8058_gpio_chip *chip, unsigned gpio) +{ + struct pm8058_gpio_platform_data *pdata; + int mode; + + if (gpio >= PM8058_GPIOS || chip == NULL) + return -EINVAL; + + pdata = chip->gpio_chip.dev->platform_data; + + /* Get gpio value from config bank 1 if output gpio. + Get gpio value from IRQ RT status register for all other gpio modes. + */ + mode = (chip->bank1[gpio] & PM8058_GPIO_MODE_MASK) >> + PM8058_GPIO_MODE_SHIFT; + if (mode == PM8058_GPIO_MODE_OUTPUT) + return chip->bank1[gpio] & PM8058_GPIO_OUT_INVERT; + else + return pm8058_irq_get_rt_status(chip->pm_chip, + pdata->irq_base + gpio); +} + +static int pm8058_gpio_set(struct pm8058_gpio_chip *chip, + unsigned gpio, int value) +{ + int rc; + u8 bank1; + + if (gpio >= PM8058_GPIOS || chip == NULL) + return -EINVAL; + + mutex_lock(&chip->pm_lock); + bank1 = chip->bank1[gpio] & ~PM8058_GPIO_OUT_INVERT; + + if (value) + bank1 |= PM8058_GPIO_OUT_INVERT; + + chip->bank1[gpio] = bank1; + rc = pm8058_write(chip->pm_chip, SSBI_REG_ADDR_GPIO(gpio), &bank1, 1); + mutex_unlock(&chip->pm_lock); + + if (rc) + pr_err("%s: FAIL pm8058_write(): rc=%d. " + "(gpio=%d, value=%d)\n", + __func__, rc, gpio, value); + + return rc; +} + +static int pm8058_gpio_set_direction(struct pm8058_gpio_chip *chip, + unsigned gpio, int direction) +{ + int rc; + u8 bank1; + static int dir_map[] = { + PM8058_GPIO_MODE_OFF, + PM8058_GPIO_MODE_OUTPUT, + PM8058_GPIO_MODE_INPUT, + PM8058_GPIO_MODE_BOTH, + }; + + if (!direction || chip == NULL) + return -EINVAL; + + mutex_lock(&chip->pm_lock); + bank1 = chip->bank1[gpio] & ~PM8058_GPIO_MODE_MASK; + + bank1 |= ((dir_map[direction] << PM8058_GPIO_MODE_SHIFT) + & PM8058_GPIO_MODE_MASK); + + chip->bank1[gpio] = bank1; + rc = pm8058_write(chip->pm_chip, SSBI_REG_ADDR_GPIO(gpio), &bank1, 1); + mutex_unlock(&chip->pm_lock); + + if (rc) + pr_err("%s: Failed on pm8058_write(): rc=%d (GPIO config)\n", + __func__, rc); + + return rc; +} + +static int pm8058_gpio_init_bank1(struct pm8058_gpio_chip *chip) +{ + int i, rc; + u8 bank; + + for (i = 0; i < PM8058_GPIOS; i++) { + bank = 1 << PM8058_GPIO_BANK_SHIFT; + rc = pm8058_write(chip->pm_chip, + SSBI_REG_ADDR_GPIO(i), + &bank, 1); + if (rc) { + pr_err("%s: error setting bank\n", __func__); + return rc; + } + + rc = pm8058_read(chip->pm_chip, + SSBI_REG_ADDR_GPIO(i), + &chip->bank1[i], 1); + if (rc) { + pr_err("%s: error reading bank 1\n", __func__); + return rc; + } + } + return 0; +} + +#ifndef CONFIG_GPIOLIB +static int pm8058_gpio_configure(struct gpio_chip *chip, + unsigned int gpio, + unsigned long flags) +{ + int rc = 0, direction; + struct pm8058_gpio_chip *gpio_chip; + + gpio -= chip->start; + + if (flags & (GPIOF_INPUT | GPIOF_DRIVE_OUTPUT)) { + direction = 0; + if (flags & GPIOF_INPUT) + direction |= PM_GPIO_DIR_IN; + if (flags & GPIOF_DRIVE_OUTPUT) + direction |= PM_GPIO_DIR_OUT; + + gpio_chip = dev_get_drvdata(chip->dev); + + if (flags & (GPIOF_OUTPUT_LOW | GPIOF_OUTPUT_HIGH)) { + if (flags & GPIOF_OUTPUT_HIGH) + rc = pm8058_gpio_set(gpio_chip, + gpio, 1); + else + rc = pm8058_gpio_set(gpio_chip, + gpio, 0); + + if (rc) { + pr_err("%s: FAIL pm8058_gpio_set(): rc=%d.\n", + __func__, rc); + goto bail_out; + } + } + + rc = pm8058_gpio_set_direction(gpio_chip, + gpio, direction); + if (rc) + pr_err("%s: FAIL pm8058_gpio_config(): rc=%d.\n", + __func__, rc); + } + +bail_out: + return rc; +} + +static int pm8058_gpio_get_irq_num(struct gpio_chip *chip, + unsigned int gpio, + unsigned int *irqp, + unsigned long *irqnumflagsp) +{ + struct pm8058_gpio_platform_data *pdata; + + pdata = chip->dev->platform_data; + gpio -= chip->start; + *irqp = pdata->irq_base + gpio; + if (irqnumflagsp) + *irqnumflagsp = 0; + return 0; +} + +static int pm8058_gpio_read(struct gpio_chip *chip, unsigned n) +{ + struct pm8058_gpio_chip *gpio_chip; + + n -= chip->start; + gpio_chip = dev_get_drvdata(chip->dev); + return pm8058_gpio_get(gpio_chip, n); +} + +static int pm8058_gpio_write(struct gpio_chip *chip, unsigned n, unsigned on) +{ + struct pm8058_gpio_chip *gpio_chip; + + n -= chip->start; + gpio_chip = dev_get_drvdata(chip->dev); + return pm8058_gpio_set(gpio_chip, n, on); +} + +static struct pm8058_gpio_chip pm8058_gpio_chip = { + .gpio_chip = { + .configure = pm8058_gpio_configure, + .get_irq_num = pm8058_gpio_get_irq_num, + .read = pm8058_gpio_read, + .write = pm8058_gpio_write, + }, +}; + +static int __devinit pm8058_gpio_probe(struct platform_device *pdev) +{ + int rc = 0; + struct pm8058_gpio_platform_data *pdata = pdev->dev.platform_data; + + mutex_init(&pm8058_gpio_chip.pm_lock); + pm8058_gpio_chip.gpio_chip.dev = &pdev->dev; + pm8058_gpio_chip.gpio_chip.start = pdata->gpio_base; + pm8058_gpio_chip.gpio_chip.end = pdata->gpio_base + + PM8058_GPIOS - 1; + pm8058_gpio_chip.pm_chip = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, &pm8058_gpio_chip); + + rc = register_gpio_chip(&pm8058_gpio_chip.gpio_chip); + if (!rc) + goto bail; + + rc = pm8058_gpio_init_bank1(&pm8058_gpio_chip); + if (rc) + goto bail; + + if (pdata->init) + rc = pdata->init(); + +bail: + if (rc) + platform_set_drvdata(pdev, pm8058_gpio_chip.pm_chip); + + pr_info("%s: register_gpio_chip(): rc=%d\n", __func__, rc); + return rc; +} + +static int __devexit pm8058_gpio_remove(struct platform_device *pdev) +{ + return 0; +} + +#else + +static int pm8058_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pm8058_gpio_platform_data *pdata; + pdata = chip->dev->platform_data; + return pdata->irq_base + offset; +} + +static int pm8058_gpio_read(struct gpio_chip *chip, unsigned offset) +{ + struct pm8058_gpio_chip *gpio_chip; + gpio_chip = dev_get_drvdata(chip->dev); + return pm8058_gpio_get(gpio_chip, offset); +} + +static void pm8058_gpio_write(struct gpio_chip *chip, + unsigned offset, int val) +{ + struct pm8058_gpio_chip *gpio_chip; + gpio_chip = dev_get_drvdata(chip->dev); + pm8058_gpio_set(gpio_chip, offset, val); +} + +static int pm8058_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + struct pm8058_gpio_chip *gpio_chip; + gpio_chip = dev_get_drvdata(chip->dev); + return pm8058_gpio_set_direction(gpio_chip, offset, PM_GPIO_DIR_IN); +} + +static int pm8058_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, + int val) +{ + struct pm8058_gpio_chip *gpio_chip; + int ret; + + gpio_chip = dev_get_drvdata(chip->dev); + ret = pm8058_gpio_set_direction(gpio_chip, offset, PM_GPIO_DIR_OUT); + if (!ret) + ret = pm8058_gpio_set(gpio_chip, offset, val); + + return ret; +} + +static void pm8058_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + static const char *cmode[] = { "in", "in/out", "out", "off" }; + struct pm8058_gpio_chip *gpio_chip = dev_get_drvdata(chip->dev); + u8 mode, state, bank; + const char *label; + int i, j; + + for (i = 0; i < PM8058_GPIOS; i++) { + label = gpiochip_is_requested(chip, i); + mode = (gpio_chip->bank1[i] & PM8058_GPIO_MODE_MASK) >> + PM8058_GPIO_MODE_SHIFT; + state = pm8058_gpio_get(gpio_chip, i); + seq_printf(s, "gpio-%-3d (%-12.12s) %-10.10s" + " %s", + chip->base + i, + label ? label : "--", + cmode[mode], + state ? "hi" : "lo"); + for (j = 0; j < PM8058_GPIO_BANKS; j++) { + bank = j << PM8058_GPIO_BANK_SHIFT; + pm8058_write(gpio_chip->pm_chip, + SSBI_REG_ADDR_GPIO(i), + &bank, 1); + pm8058_read(gpio_chip->pm_chip, + SSBI_REG_ADDR_GPIO(i), + &bank, 1); + seq_printf(s, " 0x%02x", bank); + } + seq_printf(s, "\n"); + } +} + +static struct pm8058_gpio_chip pm8058_gpio_chip = { + .gpio_chip = { + .label = "pm8058-gpio", + .direction_input = pm8058_gpio_direction_input, + .direction_output = pm8058_gpio_direction_output, + .to_irq = pm8058_gpio_to_irq, + .get = pm8058_gpio_read, + .set = pm8058_gpio_write, + .dbg_show = pm8058_gpio_dbg_show, + .ngpio = PM8058_GPIOS, + .can_sleep = 1, + }, +}; + +static int __devinit pm8058_gpio_probe(struct platform_device *pdev) +{ + int ret; + struct pm8058_gpio_platform_data *pdata = pdev->dev.platform_data; + + mutex_init(&pm8058_gpio_chip.pm_lock); + pm8058_gpio_chip.gpio_chip.dev = &pdev->dev; + pm8058_gpio_chip.gpio_chip.base = pdata->gpio_base; + pm8058_gpio_chip.pm_chip = dev_get_drvdata(pdev->dev.parent); + platform_set_drvdata(pdev, &pm8058_gpio_chip); + + ret = gpiochip_add(&pm8058_gpio_chip.gpio_chip); + if (ret) + goto unset_drvdata; + + ret = pm8058_gpio_init_bank1(&pm8058_gpio_chip); + if (ret) + goto remove_chip; + + if (pdata->init) + ret = pdata->init(); + if (!ret) + goto ok; + +remove_chip: + if (gpiochip_remove(&pm8058_gpio_chip.gpio_chip)) + pr_err("%s: failed to remove gpio chip\n", __func__); +unset_drvdata: + platform_set_drvdata(pdev, pm8058_gpio_chip.pm_chip); +ok: + pr_info("%s: gpiochip_add(): rc=%d\n", __func__, ret); + + return ret; +} + +static int __devexit pm8058_gpio_remove(struct platform_device *pdev) +{ + return gpiochip_remove(&pm8058_gpio_chip.gpio_chip); +} + +#endif + +int pm8058_gpio_config(int gpio, struct pm8058_gpio *param) +{ + int rc; + u8 bank[8]; + static int dir_map[] = { + PM8058_GPIO_MODE_OFF, + PM8058_GPIO_MODE_OUTPUT, + PM8058_GPIO_MODE_INPUT, + PM8058_GPIO_MODE_BOTH, + }; + + if (param == NULL) + return -EINVAL; + + /* Select banks and configure the gpio */ + bank[0] = PM8058_GPIO_WRITE | + ((param->vin_sel << PM8058_GPIO_VIN_SHIFT) & + PM8058_GPIO_VIN_MASK) | + PM8058_GPIO_MODE_ENABLE; + bank[1] = PM8058_GPIO_WRITE | + ((1 << PM8058_GPIO_BANK_SHIFT) & + PM8058_GPIO_BANK_MASK) | + ((dir_map[param->direction] << + PM8058_GPIO_MODE_SHIFT) & + PM8058_GPIO_MODE_MASK) | + ((param->direction & PM_GPIO_DIR_OUT) ? + ((param->output_buffer & 1) ? + PM8058_GPIO_OUT_BUFFER : 0) : 0) | + ((param->direction & PM_GPIO_DIR_OUT) ? + param->output_value & 0x01 : 0); + bank[2] = PM8058_GPIO_WRITE | + ((2 << PM8058_GPIO_BANK_SHIFT) & + PM8058_GPIO_BANK_MASK) | + ((param->pull << PM8058_GPIO_PULL_SHIFT) & + PM8058_GPIO_PULL_MASK); + bank[3] = PM8058_GPIO_WRITE | + ((3 << PM8058_GPIO_BANK_SHIFT) & + PM8058_GPIO_BANK_MASK) | + ((param->out_strength << + PM8058_GPIO_OUT_STRENGTH_SHIFT) & + PM8058_GPIO_OUT_STRENGTH_MASK) | + (param->disable_pin ? + PM8058_GPIO_PIN_DISABLE : PM8058_GPIO_PIN_ENABLE); + bank[4] = PM8058_GPIO_WRITE | + ((4 << PM8058_GPIO_BANK_SHIFT) & + PM8058_GPIO_BANK_MASK) | + ((param->function << PM8058_GPIO_FUNC_SHIFT) & + PM8058_GPIO_FUNC_MASK); + bank[5] = PM8058_GPIO_WRITE | + ((5 << PM8058_GPIO_BANK_SHIFT) & PM8058_GPIO_BANK_MASK) | + (param->inv_int_pol ? 0 : PM8058_GPIO_NON_INT_POL_INV); + + mutex_lock(&pm8058_gpio_chip.pm_lock); + /* Remember bank1 for later use */ + pm8058_gpio_chip.bank1[gpio] = bank[1]; + rc = pm8058_write(pm8058_gpio_chip.pm_chip, + SSBI_REG_ADDR_GPIO(gpio), bank, 6); + mutex_unlock(&pm8058_gpio_chip.pm_lock); + + if (rc) + pr_err("%s: Failed on pm8058_write(): rc=%d (GPIO config)\n", + __func__, rc); + + return rc; +} +EXPORT_SYMBOL(pm8058_gpio_config); + +static struct platform_driver pm8058_gpio_driver = { + .probe = pm8058_gpio_probe, + .remove = __devexit_p(pm8058_gpio_remove), + .driver = { + .name = "pm8058-gpio", + .owner = THIS_MODULE, + }, +}; + +#if defined(CONFIG_DEBUG_FS) + +#define DEBUG_MAX_RW_BUF 128 +#define DEBUG_MAX_FNAME 8 + +static struct dentry *debug_dent; + +static char debug_read_buf[DEBUG_MAX_RW_BUF]; +static char debug_write_buf[DEBUG_MAX_RW_BUF]; + +static int debug_gpios[PM8058_GPIOS]; + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static int debug_read_gpio_bank(int gpio, int bank, u8 *data) +{ + int rc; + + mutex_lock(&pm8058_gpio_chip.pm_lock); + + *data = bank << PM8058_GPIO_BANK_SHIFT; + rc = pm8058_write(pm8058_gpio_chip.pm_chip, + SSBI_REG_ADDR_GPIO(gpio), data, 1); + if (rc) + goto bail_out; + + *data = bank << PM8058_GPIO_BANK_SHIFT; + rc = pm8058_read(pm8058_gpio_chip.pm_chip, + SSBI_REG_ADDR_GPIO(gpio), data, 1); + +bail_out: + mutex_unlock(&pm8058_gpio_chip.pm_lock); + + return rc; +} + +static ssize_t debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int gpio = *((int *) file->private_data); + int len = 0; + int rc = -EINVAL; + u8 bank[PM8058_GPIO_BANKS]; + int val = -1; + int mode; + int i; + + for (i = 0; i < PM8058_GPIO_BANKS; i++) { + rc = debug_read_gpio_bank(gpio, i, &bank[i]); + if (rc) + pr_err("pmic failed to read bank %d\n", i); + } + + if (rc) { + len = snprintf(debug_read_buf, DEBUG_MAX_RW_BUF - 1, "-1\n"); + goto bail_out; + } + + val = pm8058_gpio_get(&pm8058_gpio_chip, gpio); + + /* print the mode and the value */ + mode = (bank[1] & PM8058_GPIO_MODE_MASK) >> PM8058_GPIO_MODE_SHIFT; + if (mode == PM8058_GPIO_MODE_BOTH) + len = snprintf(debug_read_buf, DEBUG_MAX_RW_BUF - 1, + "BOTH %d ", val); + else if (mode == PM8058_GPIO_MODE_INPUT) + len = snprintf(debug_read_buf, DEBUG_MAX_RW_BUF - 1, + "IN %d ", val); + else if (mode == PM8058_GPIO_MODE_OUTPUT) + len = snprintf(debug_read_buf, DEBUG_MAX_RW_BUF - 1, + "OUT %d ", val); + else + len = snprintf(debug_read_buf, DEBUG_MAX_RW_BUF - 1, + "OFF %d ", val); + + /* print the control register values */ + len += snprintf(debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + "[0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x]\n", + bank[0], bank[1], bank[2], bank[3], bank[4], bank[5]); + +bail_out: + rc = simple_read_from_buffer((void __user *) buf, len, + ppos, (void *) debug_read_buf, len); + + return rc; +} + +static ssize_t debug_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int gpio = *((int *) file->private_data); + unsigned long val; + int mode, rc; + + mode = (pm8058_gpio_chip.bank1[gpio] & PM8058_GPIO_MODE_MASK) >> + PM8058_GPIO_MODE_SHIFT; + if (mode == PM8058_GPIO_MODE_OFF || mode == PM8058_GPIO_MODE_INPUT) + return count; + + if (count > sizeof(debug_write_buf)) + return -EFAULT; + + if (copy_from_user(debug_write_buf, buf, count)) { + pr_err("failed to copy from user\n"); + return -EFAULT; + } + debug_write_buf[count] = '\0'; + + rc = strict_strtoul(debug_write_buf, 10, &val); + if (rc) + return rc; + + if (pm8058_gpio_set(&pm8058_gpio_chip, gpio, val)) { + pr_err("gpio write failed\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations debug_ops = { + .open = debug_open, + .read = debug_read, + .write = debug_write, +}; + +static void debug_init(void) +{ + int i; + char name[DEBUG_MAX_FNAME]; + + debug_dent = debugfs_create_dir("pm_gpio", NULL); + if (IS_ERR(debug_dent)) { + pr_err("pmic8058 debugfs_create_dir fail, error %ld\n", + PTR_ERR(debug_dent)); + return; + } + + for (i = 0; i < PM8058_GPIOS; i++) { + snprintf(name, DEBUG_MAX_FNAME-1, "%d", i+1); + debug_gpios[i] = i; + if (debugfs_create_file(name, 0644, debug_dent, + &debug_gpios[i], &debug_ops) == NULL) { + pr_err("pmic8058 debugfs_create_file %s failed\n", + name); + } + } +} + +static void debug_exit(void) +{ + debugfs_remove_recursive(debug_dent); +} + +#else +static void debug_init(void) { } +static void debug_exit(void) { } +#endif + +static int __init pm8058_gpio_init(void) +{ + int rc = platform_driver_register(&pm8058_gpio_driver); + if (!rc) + debug_init(); + return rc; +} + +static void __exit pm8058_gpio_exit(void) +{ + platform_driver_unregister(&pm8058_gpio_driver); + debug_exit(); +} + +subsys_initcall(pm8058_gpio_init); +module_exit(pm8058_gpio_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 GPIO driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8058-gpio"); diff --git a/arch/arm/mach-msm/pmic8058-mpp.c b/arch/arm/mach-msm/pmic8058-mpp.c new file mode 100644 index 00000000000..78a132a525f --- /dev/null +++ b/arch/arm/mach-msm/pmic8058-mpp.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8058 MPP driver + * + */ + +#include +#include +#include +#include +#include + +#ifndef CONFIG_GPIOLIB +#include "gpio_chip.h" +#endif + +/* MPP Control Registers */ +#define SSBI_MPP_CNTRL_BASE 0x50 +#define SSBI_MPP_CNTRL(n) (SSBI_MPP_CNTRL_BASE + (n)) + +/* MPP Type */ +#define PM8058_MPP_TYPE_MASK 0xE0 +#define PM8058_MPP_TYPE_SHIFT 5 + +/* MPP Config Level */ +#define PM8058_MPP_CONFIG_LVL_MASK 0x1C +#define PM8058_MPP_CONFIG_LVL_SHIFT 2 + +/* MPP Config Control */ +#define PM8058_MPP_CONFIG_CTL_MASK 0x03 + +static int pm8058_mpp_get(struct gpio_chip *chip, unsigned mpp) +{ + struct pm8058_gpio_platform_data *pdata; + struct pm8058_chip *pm_chip; + + if (mpp >= PM8058_MPPS || chip == NULL) + return -EINVAL; + + pdata = chip->dev->platform_data; + pm_chip = dev_get_drvdata(chip->dev->parent); + + return pm8058_irq_get_rt_status(pm_chip, + pdata->irq_base + mpp); +} + +#ifndef CONFIG_GPIOLIB +static int pm8058_mpp_get_irq_num(struct gpio_chip *chip, + unsigned int gpio, + unsigned int *irqp, + unsigned long *irqnumflagsp) +{ + struct pm8058_gpio_platform_data *pdata; + + pdata = chip->dev->platform_data; + gpio -= chip->start; + *irqp = pdata->irq_base + gpio; + if (irqnumflagsp) + *irqnumflagsp = 0; + return 0; +} + +static int pm8058_mpp_read(struct gpio_chip *chip, unsigned n) +{ + n -= chip->start; + return pm8058_mpp_get(chip, n); +} + +struct msm_gpio_chip pm8058_mpp_chip = { + .chip = { + .get_irq_num = pm8058_mpp_get_irq_num, + .read = pm8058_mpp_read, + } +}; + +int pm8058_mpp_config(unsigned mpp, unsigned type, unsigned level, + unsigned control) +{ + u8 config; + int rc; + struct pm8058_chip *pm_chip; + + if (mpp >= PM8058_MPPS) + return -EINVAL; + + pm_chip = dev_get_drvdata(pm8058_mpp_chip->dev->parent); + + config = (type << PM8058_MPP_TYPE_SHIFT) & PM8058_MPP_TYPE_MASK; + config |= (level << PM8058_MPP_CONFIG_LVL_SHIFT) & + PM8058_MPP_CONFIG_LVL_MASK; + config |= control & PM8058_MPP_CONFIG_CTL_MASK; + + rc = pm8058_write(pm_chip, SSBI_MPP_CNTRL(mpp), &config, 1); + if (rc) + pr_err("%s: pm8058_write(): rc=%d\n", __func__, rc); + + return rc; +} +EXPORT_SYMBOL(pm8058_mpp_config); + +static int __devinit pm8058_mpp_probe(struct platform_device *pdev) +{ + int rc; + struct pm8058_gpio_platform_data *pdata = pdev->dev.platform_data; + + pm8058_mpp_chip.chip.dev = &pdev->dev; + pm8058_mpp_chip.chip.start = pdata->gpio_base; + pm8058_mpp_chip.chip.end = pdata->gpio_base + PM8058_MPPS - 1; + rc = register_gpio_chip(&pm8058_mpp_chip.chip); + if (!rc) { + if (pdata->init) + ret = pdata->init(); + } + pr_info("%s: register_gpio_chip(): rc=%d\n", __func__, rc); + + return rc; +} + +static int __devexit pm8058_mpp_remove(struct platform_device *pdev) +{ + return 0; +} + +#else + +static int pm8058_mpp_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pm8058_gpio_platform_data *pdata; + pdata = chip->dev->platform_data; + return pdata->irq_base + offset; +} + +static int pm8058_mpp_read(struct gpio_chip *chip, unsigned offset) +{ + return pm8058_mpp_get(chip, offset); +} + +static void pm8058_mpp_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + static const char *ctype[] = { "d_in", "d_out", "bi_dir", "a_in", + "a_out", "sink", "dtest_sink", "dtest_out" }; + struct pm8058_chip *pm_chip = dev_get_drvdata(chip->dev->parent); + u8 type, state, ctrl; + const char *label; + int i; + + for (i = 0; i < PM8058_MPPS; i++) { + pm8058_read(pm_chip, SSBI_MPP_CNTRL(i), &ctrl, 1); + label = gpiochip_is_requested(chip, i); + type = (ctrl & PM8058_MPP_TYPE_MASK) >> + PM8058_MPP_TYPE_SHIFT; + state = pm8058_mpp_get(chip, i); + seq_printf(s, "gpio-%-3d (%-12.12s) %-10.10s" + " %s 0x%02x\n", + chip->base + i, + label ? label : "--", + ctype[type], + state ? "hi" : "lo", + ctrl); + } +} + +static struct gpio_chip pm8058_mpp_chip = { + .label = "pm8058-mpp", + .to_irq = pm8058_mpp_to_irq, + .get = pm8058_mpp_read, + .dbg_show = pm8058_mpp_dbg_show, + .ngpio = PM8058_MPPS, + .can_sleep = 1, +}; + +int pm8058_mpp_config(unsigned mpp, unsigned type, unsigned level, + unsigned control) +{ + u8 config; + int rc; + struct pm8058_chip *pm_chip; + + if (mpp >= PM8058_MPPS) + return -EINVAL; + + pm_chip = dev_get_drvdata(pm8058_mpp_chip.dev->parent); + + config = (type << PM8058_MPP_TYPE_SHIFT) & PM8058_MPP_TYPE_MASK; + config |= (level << PM8058_MPP_CONFIG_LVL_SHIFT) & + PM8058_MPP_CONFIG_LVL_MASK; + config |= control & PM8058_MPP_CONFIG_CTL_MASK; + + rc = pm8058_write(pm_chip, SSBI_MPP_CNTRL(mpp), &config, 1); + if (rc) + pr_err("%s: pm8058_write(): rc=%d\n", __func__, rc); + + return rc; +} +EXPORT_SYMBOL(pm8058_mpp_config); + +static int __devinit pm8058_mpp_probe(struct platform_device *pdev) +{ + int ret; + struct pm8058_gpio_platform_data *pdata = pdev->dev.platform_data; + + pm8058_mpp_chip.dev = &pdev->dev; + pm8058_mpp_chip.base = pdata->gpio_base; + ret = gpiochip_add(&pm8058_mpp_chip); + if (!ret) { + if (pdata->init) + ret = pdata->init(); + } + + pr_info("%s: gpiochip_add(): ret=%d\n", __func__, ret); + return ret; +} + +static int __devexit pm8058_mpp_remove(struct platform_device *pdev) +{ + return gpiochip_remove(&pm8058_mpp_chip); +} + +#endif + +static struct platform_driver pm8058_mpp_driver = { + .probe = pm8058_mpp_probe, + .remove = __devexit_p(pm8058_mpp_remove), + .driver = { + .name = "pm8058-mpp", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_mpp_init(void) +{ + return platform_driver_register(&pm8058_mpp_driver); +} + +static void __exit pm8058_mpp_exit(void) +{ + platform_driver_unregister(&pm8058_mpp_driver); +} + +subsys_initcall(pm8058_mpp_init); +module_exit(pm8058_mpp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 MPP driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8058-mpp"); + diff --git a/arch/arm/mach-msm/pmic_debugfs.c b/arch/arm/mach-msm/pmic_debugfs.c new file mode 100644 index 00000000000..c52cf9b14c2 --- /dev/null +++ b/arch/arm/mach-msm/pmic_debugfs.c @@ -0,0 +1,1156 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include + +#include + + +static int debug_lp_mode_control(char *buf, int size) +{ + enum switch_cmd cmd; + enum vreg_lp_id id; + int cnt; + + + cnt = sscanf(buf, "%u %u", &cmd, &id); + if (cnt < 2) { + printk(KERN_ERR "%s: sscanf failed cnt=%d", __func__, cnt); + return -EINVAL; + } + + if (pmic_lp_mode_control(cmd, id) < 0) + return -EFAULT; + + return size; +} + +static int debug_vreg_set_level(char *buf, int size) +{ + enum vreg_id vreg; + int level; + int cnt; + + cnt = sscanf(buf, "%u %u", &vreg, &level); + if (cnt < 2) { + printk(KERN_ERR "%s: sscanf failed cnt=%d", __func__, cnt); + return -EINVAL; + } + if (pmic_vreg_set_level(vreg, level) < 0) + return -EFAULT; + + return size; +} + +static int debug_vreg_pull_down_switch(char *buf, int size) +{ + enum switch_cmd cmd; + enum vreg_pdown_id id; + int cnt; + + cnt = sscanf(buf, "%u %u", &cmd, &id); + if (cnt < 2) { + printk(KERN_ERR "%s: sscanf failed cnt=%d", __func__, cnt); + return -EINVAL; + } + if (pmic_vreg_pull_down_switch(cmd, id) < 0) + return -EFAULT; + + return size; +} + +static int debug_secure_mpp_control_digital_output(char *buf, int size) +{ + enum mpp_which which; + enum mpp_dlogic_level level; + enum mpp_dlogic_out_ctrl out; + int cnt; + + cnt = sscanf(buf, "%u %u %u", &which, &level, &out); + if (cnt < 3) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + + if (pmic_secure_mpp_control_digital_output(which, level, out) < 0) + return -EFAULT; + + return size; +} + +static int debug_secure_mpp_config_i_sink(char *buf, int size) +{ + enum mpp_which which; + enum mpp_i_sink_level level; + enum mpp_i_sink_switch onoff; + int cnt; + + cnt = sscanf(buf, "%u %u %u", &which, &level, &onoff); + if (cnt < 3) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + + if (pmic_secure_mpp_config_i_sink(which, level, onoff) < 0) + return -EFAULT; + + return size; +} + +static int debug_secure_mpp_config_digital_input(char *buf, int size) +{ + enum mpp_which which; + enum mpp_dlogic_level level; + enum mpp_dlogic_in_dbus dbus; + int cnt; + + cnt = sscanf(buf, "%u %u %u", &which, &level, &dbus); + if (cnt < 3) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_secure_mpp_config_digital_input(which, level, dbus) < 0) + return -EFAULT; + + return size; +} + +static int debug_rtc_start(char *buf, int size) +{ + uint time; + struct rtc_time *hal; + int cnt; + + cnt = sscanf(buf, "%d", &time); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + hal = (struct rtc_time *)&time; + if (pmic_rtc_start(hal) < 0) + return -EFAULT; + + return size; +} + +static int debug_rtc_stop(char *buf, int size) +{ + if (pmic_rtc_stop() < 0) + return -EFAULT; + + return size; +} + +static int debug_rtc_get_time(char *buf, int size) +{ + uint time; + struct rtc_time *hal; + + hal = (struct rtc_time *)&time; + if (pmic_rtc_get_time(hal) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", time); +} + +static int debug_rtc_alarm_ndx; + +int debug_rtc_enable_alarm(char *buf, int size) +{ + enum rtc_alarm alarm; + struct rtc_time *hal; + uint time; + int cnt; + + + cnt = sscanf(buf, "%u %u", &alarm, &time); + if (cnt < 2) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + hal = (struct rtc_time *)&time; + + if (pmic_rtc_enable_alarm(alarm, hal) < 0) + return -EFAULT; + + debug_rtc_alarm_ndx = alarm; + return size; +} + +static int debug_rtc_disable_alarm(char *buf, int size) +{ + + enum rtc_alarm alarm; + int cnt; + + cnt = sscanf(buf, "%u", &alarm); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_rtc_disable_alarm(alarm) < 0) + return -EFAULT; + + return size; +} + +static int debug_rtc_get_alarm_time(char *buf, int size) +{ + uint time; + struct rtc_time *hal; + + hal = (struct rtc_time *)&time; + if (pmic_rtc_get_alarm_time(debug_rtc_alarm_ndx, hal) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", time); +} +static int debug_rtc_get_alarm_status(char *buf, int size) +{ + int status;; + + if (pmic_rtc_get_alarm_status(&status) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", status); + +} + +static int debug_rtc_set_time_adjust(char *buf, int size) +{ + uint adjust; + int cnt; + + cnt = sscanf(buf, "%d", &adjust); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_rtc_set_time_adjust(adjust) < 0) + return -EFAULT; + + return size; +} + +static int debug_rtc_get_time_adjust(char *buf, int size) +{ + int adjust;; + + if (pmic_rtc_get_time_adjust(&adjust) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", adjust); +} + +static int debug_set_led_intensity(char *buf, int size) +{ + enum ledtype type; + int level; + int cnt; + + cnt = sscanf(buf, "%u %d", &type, &level); + if (cnt < 2) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_set_led_intensity(type, level) < 0) + return -EFAULT; + + return size; +} + +static int debug_flash_led_set_current(char *buf, int size) +{ + int milliamps; + int cnt; + + cnt = sscanf(buf, "%d", &milliamps); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_flash_led_set_current(milliamps) < 0) + return -EFAULT; + + return size; +} +static int debug_flash_led_set_mode(char *buf, int size) +{ + + uint mode; + int cnt; + + cnt = sscanf(buf, "%d", &mode); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_flash_led_set_mode(mode) < 0) + return -EFAULT; + + return size; +} + +static int debug_flash_led_set_polarity(char *buf, int size) +{ + int pol; + int cnt; + + cnt = sscanf(buf, "%d", &pol); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_flash_led_set_polarity(pol) < 0) + return -EFAULT; + + return size; +} + +static int debug_speaker_cmd(char *buf, int size) +{ + int cmd; + int cnt; + + cnt = sscanf(buf, "%d", &cmd); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_speaker_cmd(cmd) < 0) + return -EFAULT; + + return size; +} +static int debug_set_speaker_gain(char *buf, int size) +{ + int gain; + int cnt; + + cnt = sscanf(buf, "%d", &gain); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_set_speaker_gain(gain) < 0) + return -EFAULT; + + return size; +} + +static int debug_mic_en(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_mic_en(enable) < 0) + return -EFAULT; + + return size; +} + +static int debug_mic_is_en(char *buf, int size) +{ + int enabled; + + if (pmic_mic_is_en(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_mic_set_volt(char *buf, int size) +{ + int vol; + int cnt; + + cnt = sscanf(buf, "%d", &vol); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_mic_set_volt(vol) < 0) + return -EFAULT; + + return size; +} + +static int debug_mic_get_volt(char *buf, int size) +{ + uint vol; + + if (pmic_mic_get_volt(&vol) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", vol); +} + +static int debug_spkr_en_right_chan(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_en_right_chan(enable) < 0) + return -EFAULT; + + return size; +} + +static int debug_spkr_is_right_chan_en(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_right_chan_en(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} +static int debug_spkr_en_left_chan(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_en_left_chan(enable) < 0) + return -EFAULT; + + return size; +} + +static int debug_spkr_is_left_chan_en(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_left_chan_en(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_set_spkr_configuration(char *buf, int size) +{ + + struct spkr_config_mode cfg; + int cnt; + + cnt = sscanf(buf, "%d %d %d %d %d %d %d %d", + &cfg.is_right_chan_en, + &cfg.is_left_chan_en, + &cfg.is_right_left_chan_added, + &cfg.is_stereo_en, + &cfg.is_usb_with_hpf_20hz, + &cfg.is_mux_bypassed, + &cfg.is_hpf_en, + &cfg.is_sink_curr_from_ref_volt_cir_en); + + if (cnt < 8) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + + if (pmic_set_spkr_configuration(&cfg) < 0) + return -EFAULT; + + return size; +} + +static int debug_get_spkr_configuration(char *buf, int size) +{ + struct spkr_config_mode cfg; + + if (pmic_get_spkr_configuration(&cfg) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d %d %d %d %d %d %d %d\n", + cfg.is_right_chan_en, + cfg.is_left_chan_en, + cfg.is_right_left_chan_added, + cfg.is_stereo_en, + cfg.is_usb_with_hpf_20hz, + cfg.is_mux_bypassed, + cfg.is_hpf_en, + cfg.is_sink_curr_from_ref_volt_cir_en); + +} + +static int debug_set_speaker_delay(char *buf, int size) +{ + int delay; + int cnt; + + cnt = sscanf(buf, "%d", &delay); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_set_speaker_delay(delay) < 0) + return -EFAULT; + + return size; +} + +static int debug_speaker_1k6_zin_enable(char *buf, int size) +{ + uint enable; + int cnt; + + cnt = sscanf(buf, "%u", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_speaker_1k6_zin_enable(enable) < 0) + return -EFAULT; + + return size; +} + +static int debug_spkr_set_mux_hpf_corner_freq(char *buf, int size) +{ + int freq; + int cnt; + + cnt = sscanf(buf, "%d", &freq); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_set_mux_hpf_corner_freq(freq) < 0) + return -EFAULT; + + return size; +} + +static int debug_spkr_get_mux_hpf_corner_freq(char *buf, int size) +{ + uint freq; + + if (pmic_spkr_get_mux_hpf_corner_freq(&freq) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", freq); +} + +static int debug_spkr_add_right_left_chan(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_add_right_left_chan(enable) < 0) + return -EFAULT; + + return size; +} + +static int debug_spkr_is_right_left_chan_added(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_right_left_chan_added(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_spkr_en_stereo(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_en_stereo(enable) < 0) + return -EFAULT; + + return size; +} +static int debug_spkr_is_stereo_en(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_stereo_en(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_spkr_select_usb_with_hpf_20hz(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_select_usb_with_hpf_20hz(enable) < 0) + return -EFAULT; + + return size; +} +static int debug_spkr_is_usb_with_hpf_20hz(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_usb_with_hpf_20hz(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_spkr_bypass_mux(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_bypass_mux(enable) < 0) + return -EFAULT; + + return size; +} +static int debug_spkr_is_mux_bypassed(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_mux_bypassed(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_spkr_en_hpf(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_en_hpf(enable) < 0) + return -EFAULT; + + return size; +} +static int debug_spkr_is_hpf_en(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_hpf_en(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_spkr_en_sink_curr_from_ref_volt_cir(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_en_sink_curr_from_ref_volt_cir(enable) < 0) + return -EFAULT; + + return size; +} + +static int debug_spkr_is_sink_curr_from_ref_volt_cir_en(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_sink_curr_from_ref_volt_cir_en(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_vib_mot_set_volt(char *buf, int size) +{ + int vol; + int cnt; + + cnt = sscanf(buf, "%d", &vol); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_vib_mot_set_volt(vol) < 0) + return -EFAULT; + + return size; +} +static int debug_vib_mot_set_mode(char *buf, int size) +{ + int mode; + int cnt; + + cnt = sscanf(buf, "%d", &mode); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_vib_mot_set_mode(mode) < 0) + return -EFAULT; + + return size; +} + +static int debug_vib_mot_set_polarity(char *buf, int size) +{ + int pol; + int cnt; + + cnt = sscanf(buf, "%d", &pol); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_vib_mot_set_polarity(pol) < 0) + return -EFAULT; + + return size; +} +static int debug_vid_en(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_vid_en(enable) < 0) + return -EFAULT; + + return size; +} +static int debug_vid_is_en(char *buf, int size) +{ + int enabled; + + if (pmic_vid_is_en(&enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_vid_load_detect_en(char *buf, int size) +{ + int enable; + int cnt; + + cnt = sscanf(buf, "%d", &enable); + if (cnt < 1) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_vid_load_detect_en(enable) < 0) + return -EFAULT; + + return size; +} + +/************************************************** + * speaker indexed by left_right +**************************************************/ +static enum spkr_left_right debug_spkr_left_right = LEFT_SPKR; + +static int debug_spkr_en(char *buf, int size) +{ + int left_right; + int enable; + int cnt; + + cnt = sscanf(buf, "%d %d", &left_right, &enable); + if (cnt < 2) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_en(left_right, enable) >= 0) { + debug_spkr_left_right = left_right; + return size; + } + return -EFAULT; +} + +static int debug_spkr_is_en(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_en(debug_spkr_left_right, &enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +static int debug_spkr_set_gain(char *buf, int size) +{ + int left_right; + int enable; + int cnt; + + cnt = sscanf(buf, "%d %d", &left_right, &enable); + if (cnt < 2) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_set_gain(left_right, enable) >= 0) { + debug_spkr_left_right = left_right; + return size; + } + return -EFAULT; +} + +static int debug_spkr_get_gain(char *buf, int size) +{ + uint gain; + + if (pmic_spkr_get_gain(debug_spkr_left_right, &gain) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", gain); +} +static int debug_spkr_set_delay(char *buf, int size) +{ + int left_right; + int delay; + int cnt; + + cnt = sscanf(buf, "%d %d", &left_right, &delay); + if (cnt < 2) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_set_delay(left_right, delay) >= 0) { + debug_spkr_left_right = left_right; + return size; + } + return -EFAULT; +} + +static int debug_spkr_get_delay(char *buf, int size) +{ + uint delay; + + if (pmic_spkr_get_delay(debug_spkr_left_right, &delay) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", delay); +} + +static int debug_spkr_en_mute(char *buf, int size) +{ + int left_right; + int enable; + int cnt; + + cnt = sscanf(buf, "%d %d", &left_right, &enable); + if (cnt < 2) { + printk(KERN_ERR "%s: sscanf failed cnt=%d" , __func__, cnt); + return -EINVAL; + } + if (pmic_spkr_en_mute(left_right, enable) >= 0) { + debug_spkr_left_right = left_right; + return size; + } + return -EFAULT; +} + +static int debug_spkr_is_mute_en(char *buf, int size) +{ + int enabled; + + if (pmic_spkr_is_mute_en(debug_spkr_left_right, &enabled) < 0) + return -EFAULT; + + return snprintf(buf, size, "%d\n", enabled); +} + +/******************************************************************* + * debug function table +*******************************************************************/ + +struct pmic_debug_desc { + int (*get) (char *, int); + int (*set) (char *, int); +}; + +struct pmic_debug_desc pmic_debug[] = { + {NULL, NULL}, /*LIB_NULL_PROC */ + {NULL, NULL}, /* LIB_RPC_GLUE_CODE_INFO_REMOTE_PROC */ + {NULL, debug_lp_mode_control}, /* LP_MODE_CONTROL_PROC */ + {NULL, debug_vreg_set_level}, /*VREG_SET_LEVEL_PROC */ + {NULL, debug_vreg_pull_down_switch}, /*VREG_PULL_DOWN_SWITCH_PROC */ + {NULL, debug_secure_mpp_control_digital_output}, + /* SECURE_MPP_CONFIG_DIGITAL_OUTPUT_PROC */ + /*SECURE_MPP_CONFIG_I_SINK_PROC */ + {NULL, debug_secure_mpp_config_i_sink}, + {NULL, debug_rtc_start}, /*RTC_START_PROC */ + {NULL, debug_rtc_stop}, /* RTC_STOP_PROC */ + {debug_rtc_get_time, NULL}, /* RTC_GET_TIME_PROC */ + {NULL, debug_rtc_enable_alarm}, /* RTC_ENABLE_ALARM_PROC */ + {NULL , debug_rtc_disable_alarm}, /*RTC_DISABLE_ALARM_PROC */ + {debug_rtc_get_alarm_time, NULL}, /* RTC_GET_ALARM_TIME_PROC */ + {debug_rtc_get_alarm_status, NULL}, /* RTC_GET_ALARM_STATUS_PROC */ + {NULL, debug_rtc_set_time_adjust}, /* RTC_SET_TIME_ADJUST_PROC */ + {debug_rtc_get_time_adjust, NULL}, /* RTC_GET_TIME_ADJUST_PROC */ + {NULL, debug_set_led_intensity}, /* SET_LED_INTENSITY_PROC */ + {NULL, debug_flash_led_set_current}, /* FLASH_LED_SET_CURRENT_PROC */ + {NULL, debug_flash_led_set_mode}, /* FLASH_LED_SET_MODE_PROC */ + {NULL, debug_flash_led_set_polarity}, /* FLASH_LED_SET_POLARITY_PROC */ + {NULL, debug_speaker_cmd}, /* SPEAKER_CMD_PROC */ + {NULL, debug_set_speaker_gain}, /* SET_SPEAKER_GAIN_PROC */ + {NULL, debug_vib_mot_set_volt}, /* VIB_MOT_SET_VOLT_PROC */ + {NULL, debug_vib_mot_set_mode}, /* VIB_MOT_SET_MODE_PROC */ + {NULL, debug_vib_mot_set_polarity}, /* VIB_MOT_SET_POLARITY_PROC */ + {NULL, debug_vid_en}, /* VID_EN_PROC */ + {debug_vid_is_en, NULL}, /* VID_IS_EN_PROC */ + {NULL, debug_vid_load_detect_en}, /* VID_LOAD_DETECT_EN_PROC */ + {NULL, debug_mic_en}, /* MIC_EN_PROC */ + {debug_mic_is_en, NULL}, /* MIC_IS_EN_PROC */ + {NULL, debug_mic_set_volt}, /* MIC_SET_VOLT_PROC */ + {debug_mic_get_volt, NULL}, /* MIC_GET_VOLT_PROC */ + {NULL, debug_spkr_en_right_chan}, /* SPKR_EN_RIGHT_CHAN_PROC */ + {debug_spkr_is_right_chan_en, NULL}, /* SPKR_IS_RIGHT_CHAN_EN_PROC */ + {NULL, debug_spkr_en_left_chan}, /* SPKR_EN_LEFT_CHAN_PROC */ + {debug_spkr_is_left_chan_en, NULL}, /* SPKR_IS_LEFT_CHAN_EN_PROC */ + {NULL, debug_set_spkr_configuration}, /* SET_SPKR_CONFIGURATION_PROC */ + {debug_get_spkr_configuration, NULL}, /* GET_SPKR_CONFIGURATION_PROC */ + {debug_spkr_get_gain, NULL}, /* SPKR_GET_GAIN_PROC */ + {debug_spkr_is_en, NULL}, /* SPKR_IS_EN_PROC */ + {NULL, debug_spkr_en_mute}, /* SPKR_EN_MUTE_PROC */ + {debug_spkr_is_mute_en, NULL}, /* SPKR_IS_MUTE_EN_PROC */ + {NULL, debug_spkr_set_delay}, /* SPKR_SET_DELAY_PROC */ + {debug_spkr_get_delay, NULL}, /* SPKR_GET_DELAY_PROC */ + /* SECURE_MPP_CONFIG_DIGITAL_INPUT_PROC */ + {NULL, debug_secure_mpp_config_digital_input}, + {NULL, debug_set_speaker_delay}, /* SET_SPEAKER_DELAY_PROC */ + {NULL, debug_speaker_1k6_zin_enable}, /* SPEAKER_1K6_ZIN_ENABLE_PROC */ + /* SPKR_SET_MUX_HPF_CORNER_FREQ_PROC */ + {NULL, debug_spkr_set_mux_hpf_corner_freq}, + /* SPKR_GET_MUX_HPF_CORNER_FREQ_PROC */ + {debug_spkr_get_mux_hpf_corner_freq, NULL}, + /* SPKR_IS_RIGHT_LEFT_CHAN_ADDED_PROC */ + {debug_spkr_is_right_left_chan_added, NULL}, + {NULL, debug_spkr_en_stereo}, /* SPKR_EN_STEREO_PROC */ + {debug_spkr_is_stereo_en, NULL}, /* SPKR_IS_STEREO_EN_PROC */ + /* SPKR_SELECT_USB_WITH_HPF_20HZ_PROC */ + {NULL, debug_spkr_select_usb_with_hpf_20hz}, + /* SPKR_IS_USB_WITH_HPF_20HZ_PROC */ + {debug_spkr_is_usb_with_hpf_20hz, NULL}, + {NULL, debug_spkr_bypass_mux}, /* SPKR_BYPASS_MUX_PROC */ + {debug_spkr_is_mux_bypassed, NULL}, /* SPKR_IS_MUX_BYPASSED_PROC */ + {NULL, debug_spkr_en_hpf}, /* SPKR_EN_HPF_PROC */ + { debug_spkr_is_hpf_en, NULL}, /* SPKR_IS_HPF_EN_PROC */ + /* SPKR_EN_SINK_CURR_FROM_REF_VOLT_CIR_PROC */ + {NULL, debug_spkr_en_sink_curr_from_ref_volt_cir}, + /* SPKR_IS_SINK_CURR_FROM_REF_VOLT_CIR_EN_PROC */ + {debug_spkr_is_sink_curr_from_ref_volt_cir_en, NULL}, + /* SPKR_ADD_RIGHT_LEFT_CHAN_PROC */ + {NULL, debug_spkr_add_right_left_chan}, + {NULL, debug_spkr_set_gain}, /* SPKR_SET_GAIN_PROC */ + {NULL , debug_spkr_en}, /* SPKR_EN_PROC */ +}; + +/***********************************************************************/ + +#define PROC_END (sizeof(pmic_debug)/sizeof(struct pmic_debug_desc)) + + +#define PMIC_DEBUG_BUF 512 + +static int debug_proc; /* PROC's index */ + +static char debug_buf[PMIC_DEBUG_BUF]; + +static int proc_index_set(void *data, u64 val) +{ + int ndx; + + ndx = (int)val; + + if (ndx >= 0 && ndx <= PROC_END) + debug_proc = ndx; + + return 0; +} + +static int proc_index_get(void *data, u64 *val) +{ + *val = (u64)debug_proc; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE( + proc_index_fops, + proc_index_get, + proc_index_set, + "%llu\n"); + + +static int pmic_debugfs_open(struct inode *inode, struct file *file) +{ + /* non-seekable */ + file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); + return 0; +} + +static int pmic_debugfs_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t pmic_debugfs_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + struct pmic_debug_desc *pd; + int len = 0; + + printk(KERN_INFO "%s: proc=%d count=%d *ppos=%d\n", + __func__, debug_proc, count, (uint)*ppos); + + if (count > sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + + debug_buf[count] = 0; /* end of string */ + + pd = &pmic_debug[debug_proc]; + + if (pd->set) { + len = pd->set(debug_buf, count); + printk(KERN_INFO "%s: len=%d\n", __func__, len); + return len; + } + + return 0; +} + +static ssize_t pmic_debugfs_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + struct pmic_debug_desc *pd; + int len = 0; + + printk(KERN_INFO "%s: proc=%d count=%d *ppos=%d\n", + __func__, debug_proc, count, (uint)*ppos); + + pd = &pmic_debug[debug_proc]; + + if (*ppos) + return 0; /* the end */ + + if (pd->get) { + len = pd->get(debug_buf, sizeof(debug_buf)); + if (len > 0) { + if (len > count) + len = count; + if (copy_to_user(buff, debug_buf, len)) + return -EFAULT; + } + } + + printk(KERN_INFO "%s: len=%d\n", __func__, len); + + if (len < 0) + return 0; + + *ppos += len; /* increase offset */ + + return len; +} + +static const struct file_operations pmic_debugfs_fops = { + .open = pmic_debugfs_open, + .release = pmic_debugfs_release, + .read = pmic_debugfs_read, + .write = pmic_debugfs_write, +}; + +static int __init pmic_debugfs_init(void) +{ + struct dentry *dent = debugfs_create_dir("pmic", NULL); + + if (IS_ERR(dent)) { + printk(KERN_ERR "%s(%d): debugfs_create_dir fail, error %ld\n", + __FILE__, __LINE__, PTR_ERR(dent)); + return -1; + } + + if (debugfs_create_file("index", 0644, dent, 0, &proc_index_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: index fail\n", + __FILE__, __LINE__); + return -1; + } + + if (debugfs_create_file("debug", 0644, dent, 0, &pmic_debugfs_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: debug fail\n", + __FILE__, __LINE__); + return -1; + } + + debug_proc = 0; + debug_rtc_alarm_ndx = 0; + + return 0; +} + +late_initcall(pmic_debugfs_init); diff --git a/arch/arm/mach-msm/pmu.c b/arch/arm/mach-msm/pmu.c new file mode 100644 index 00000000000..81d4e5beb5d --- /dev/null +++ b/arch/arm/mach-msm/pmu.c @@ -0,0 +1,58 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +static struct resource cpu_pmu_resource = { + .start = INT_ARMQC_PERFMON, + .end = INT_ARMQC_PERFMON, + .flags = IORESOURCE_IRQ, +}; + +#ifdef CONFIG_CPU_HAS_L2_PMU +static struct resource l2_pmu_resource = { + .start = SC_SICL2PERFMONIRPTREQ, + .end = SC_SICL2PERFMONIRPTREQ, + .flags = IORESOURCE_IRQ, +}; + +static struct platform_device l2_pmu_device = { + .name = "l2-arm-pmu", + .id = ARM_PMU_DEVICE_L2, + .resource = &l2_pmu_resource, + .num_resources = 1, +}; + +#endif + +static struct platform_device cpu_pmu_device = { + .name = "cpu-arm-pmu", + .id = ARM_PMU_DEVICE_CPU, + .resource = &cpu_pmu_resource, + .num_resources = 1, +}; + +static struct platform_device *pmu_devices[] = { + &cpu_pmu_device, +#ifdef CONFIG_CPU_HAS_L2_PMU + &l2_pmu_device, +#endif +}; + +static int __init msm_pmu_init(void) +{ + return platform_add_devices(pmu_devices, ARRAY_SIZE(pmu_devices)); +} + +arch_initcall(msm_pmu_init); diff --git a/arch/arm/mach-msm/proc_comm.c b/arch/arm/mach-msm/proc_comm.c index 67e701c7f18..6f92b276f47 100644 --- a/arch/arm/mach-msm/proc_comm.c +++ b/arch/arm/mach-msm/proc_comm.c @@ -1,6 +1,7 @@ /* arch/arm/mach-msm/proc_comm.c * * Copyright (C) 2007-2008 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -18,25 +19,26 @@ #include #include #include +#include #include #include #include "proc_comm.h" +#include "smd_private.h" -static inline void msm_a2m_int(uint32_t irq) +static inline void notify_other_proc_comm(void) { + /* Make sure the write completes before interrupt */ + wmb(); #if defined(CONFIG_ARCH_MSM7X30) - writel(1 << irq, MSM_GCC_BASE + 0x8); + __raw_writel(1 << 6, MSM_GCC_BASE + 0x8); +#elif defined(CONFIG_ARCH_MSM8X60) + __raw_writel(1 << 5, MSM_GCC_BASE + 0x8); #else - writel(1, MSM_CSR_BASE + 0x400 + (irq * 4)); + __raw_writel(1, MSM_CSR_BASE + 0x400 + (6) * 4); #endif } -static inline void notify_other_proc_comm(void) -{ - msm_a2m_int(6); -} - #define APP_COMMAND 0x00 #define APP_STATUS 0x04 #define APP_DATA1 0x08 @@ -48,83 +50,109 @@ static inline void notify_other_proc_comm(void) #define MDM_DATA2 0x1C static DEFINE_SPINLOCK(proc_comm_lock); - -/* The higher level SMD support will install this to - * provide a way to check for and handle modem restart. - */ -int (*msm_check_for_modem_crash)(void); +static int msm_proc_comm_disable; /* Poll for a state change, checking for possible * modem crashes along the way (so we don't wait - * forever while the ARM9 is blowing up). + * forever while the ARM9 is blowing up. * * Return an error in the event of a modem crash and * restart so the msm_proc_comm() routine can restart * the operation from the beginning. */ -static int proc_comm_wait_for(void __iomem *addr, unsigned value) +static int proc_comm_wait_for(unsigned addr, unsigned value) { - for (;;) { - if (readl(addr) == value) + while (1) { + /* Barrier here prevents excessive spinning */ + mb(); + if (readl_relaxed(addr) == value) return 0; - if (msm_check_for_modem_crash) - if (msm_check_for_modem_crash()) - return -EAGAIN; + if (smsm_check_for_modem_crash()) + return -EAGAIN; + + udelay(5); } } +void msm_proc_comm_reset_modem_now(void) +{ + unsigned base = (unsigned)MSM_SHARED_RAM_BASE; + unsigned long flags; + + spin_lock_irqsave(&proc_comm_lock, flags); + +again: + if (proc_comm_wait_for(base + MDM_STATUS, PCOM_READY)) + goto again; + + writel_relaxed(PCOM_RESET_MODEM, base + APP_COMMAND); + writel_relaxed(0, base + APP_DATA1); + writel_relaxed(0, base + APP_DATA2); + + spin_unlock_irqrestore(&proc_comm_lock, flags); + + /* Make sure the writes complete before notifying the other side */ + wmb(); + notify_other_proc_comm(); + + return; +} +EXPORT_SYMBOL(msm_proc_comm_reset_modem_now); + int msm_proc_comm(unsigned cmd, unsigned *data1, unsigned *data2) { - void __iomem *base = MSM_SHARED_RAM_BASE; + unsigned base = (unsigned)MSM_SHARED_RAM_BASE; unsigned long flags; int ret; spin_lock_irqsave(&proc_comm_lock, flags); - for (;;) { - if (proc_comm_wait_for(base + MDM_STATUS, PCOM_READY)) - continue; + if (msm_proc_comm_disable) { + ret = -EIO; + goto end; + } - writel(cmd, base + APP_COMMAND); - writel(data1 ? *data1 : 0, base + APP_DATA1); - writel(data2 ? *data2 : 0, base + APP_DATA2); - notify_other_proc_comm(); +again: + if (proc_comm_wait_for(base + MDM_STATUS, PCOM_READY)) + goto again; - if (proc_comm_wait_for(base + APP_COMMAND, PCOM_CMD_DONE)) - continue; + writel_relaxed(cmd, base + APP_COMMAND); + writel_relaxed(data1 ? *data1 : 0, base + APP_DATA1); + writel_relaxed(data2 ? *data2 : 0, base + APP_DATA2); - if (readl(base + APP_STATUS) != PCOM_CMD_FAIL) { - if (data1) - *data1 = readl(base + APP_DATA1); - if (data2) - *data2 = readl(base + APP_DATA2); - ret = 0; - } else { - ret = -EIO; - } - break; + /* Make sure the writes complete before notifying the other side */ + wmb(); + notify_other_proc_comm(); + + if (proc_comm_wait_for(base + APP_COMMAND, PCOM_CMD_DONE)) + goto again; + + if (readl_relaxed(base + APP_STATUS) == PCOM_CMD_SUCCESS) { + if (data1) + *data1 = readl_relaxed(base + APP_DATA1); + if (data2) + *data2 = readl_relaxed(base + APP_DATA2); + ret = 0; + } else { + ret = -EIO; } - writel(PCOM_CMD_IDLE, base + APP_COMMAND); + writel_relaxed(PCOM_CMD_IDLE, base + APP_COMMAND); + switch (cmd) { + case PCOM_RESET_CHIP: + case PCOM_RESET_CHIP_IMM: + case PCOM_RESET_APPS: + msm_proc_comm_disable = 1; + printk(KERN_ERR "msm: proc_comm: proc comm disabled\n"); + break; + } +end: + /* Make sure the writes complete before returning */ + wmb(); spin_unlock_irqrestore(&proc_comm_lock, flags); - return ret; } - -/* - * We need to wait for the ARM9 to at least partially boot - * up before we can continue. Since the ARM9 does resource - * allocation, if we dont' wait we could end up crashing or in - * and unknown state. This function should be called early to - * wait on the ARM9. - */ -void __init proc_comm_boot_wait(void) -{ - void __iomem *base = MSM_SHARED_RAM_BASE; - - proc_comm_wait_for(base + MDM_STATUS, PCOM_READY); - -} +EXPORT_SYMBOL(msm_proc_comm); diff --git a/arch/arm/mach-msm/proc_comm.h b/arch/arm/mach-msm/proc_comm.h index 12da4cacd4a..6f1bc1539f8 100644 --- a/arch/arm/mach-msm/proc_comm.h +++ b/arch/arm/mach-msm/proc_comm.h @@ -1,6 +1,6 @@ /* arch/arm/mach-msm/proc_comm.h * - * Copyright (c) 2007 QUALCOMM Incorporated + * Copyright (c) 2007-2009,2011 Code Aurora Forum. 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 @@ -13,10 +13,8 @@ * */ -#ifndef _ARCH_ARM_MACH_MSM_PROC_COMM_H_ -#define _ARCH_ARM_MACH_MSM_PROC_COMM_H_ - -#include +#ifndef _ARCH_ARM_MACH_MSM_MSM_PROC_COMM_H_ +#define _ARCH_ARM_MACH_MSM_MSM_PROC_COMM_H_ enum { PCOM_CMD_IDLE = 0x0, @@ -137,7 +135,19 @@ enum { PCOM_CLKCTL_RPC_RAIL_DISABLE, PCOM_CLKCTL_RPC_RAIL_CONTROL, PCOM_CLKCTL_RPC_MIN_MSMC1, - PCOM_NUM_CMDS, + PCOM_CLKCTL_RPC_SRC_REQUEST, + PCOM_NPA_INIT, + PCOM_NPA_ISSUE_REQUIRED_REQUEST, + PCOM_CLKCTL_RPC_SET_EXT_CONFIG, +}; + +enum { + PCOM_OEM_FIRST_CMD = 0x10000000, + PCOM_OEM_TEST_CMD = PCOM_OEM_FIRST_CMD, + + /* add OEM PROC COMM commands here */ + + PCOM_OEM_LAST = PCOM_OEM_TEST_CMD, }; enum { @@ -157,102 +167,15 @@ enum { PCOM_CMD_FAIL_SMSM_NOT_INIT, PCOM_CMD_FAIL_PROC_COMM_BUSY, PCOM_CMD_FAIL_PROC_COMM_NOT_INIT, - -}; - -/* List of VREGs that support the Pull Down Resistor setting. */ -enum vreg_pdown_id { - PM_VREG_PDOWN_MSMA_ID, - PM_VREG_PDOWN_MSMP_ID, - PM_VREG_PDOWN_MSME1_ID, /* Not supported in Panoramix */ - PM_VREG_PDOWN_MSMC1_ID, /* Not supported in PM6620 */ - PM_VREG_PDOWN_MSMC2_ID, /* Supported in PM7500 only */ - PM_VREG_PDOWN_GP3_ID, /* Supported in PM7500 only */ - PM_VREG_PDOWN_MSME2_ID, /* Supported in PM7500 and Panoramix only */ - PM_VREG_PDOWN_GP4_ID, /* Supported in PM7500 only */ - PM_VREG_PDOWN_GP1_ID, /* Supported in PM7500 only */ - PM_VREG_PDOWN_TCXO_ID, - PM_VREG_PDOWN_PA_ID, - PM_VREG_PDOWN_RFTX_ID, - PM_VREG_PDOWN_RFRX1_ID, - PM_VREG_PDOWN_RFRX2_ID, - PM_VREG_PDOWN_SYNT_ID, - PM_VREG_PDOWN_WLAN_ID, - PM_VREG_PDOWN_USB_ID, - PM_VREG_PDOWN_MMC_ID, - PM_VREG_PDOWN_RUIM_ID, - PM_VREG_PDOWN_MSMC0_ID, /* Supported in PM6610 only */ - PM_VREG_PDOWN_GP2_ID, /* Supported in PM7500 only */ - PM_VREG_PDOWN_GP5_ID, /* Supported in PM7500 only */ - PM_VREG_PDOWN_GP6_ID, /* Supported in PM7500 only */ - PM_VREG_PDOWN_RF_ID, - PM_VREG_PDOWN_RF_VCO_ID, - PM_VREG_PDOWN_MPLL_ID, - PM_VREG_PDOWN_S2_ID, - PM_VREG_PDOWN_S3_ID, - PM_VREG_PDOWN_RFUBM_ID, - - /* new for HAN */ - PM_VREG_PDOWN_RF1_ID, - PM_VREG_PDOWN_RF2_ID, - PM_VREG_PDOWN_RFA_ID, - PM_VREG_PDOWN_CDC2_ID, - PM_VREG_PDOWN_RFTX2_ID, - PM_VREG_PDOWN_USIM_ID, - PM_VREG_PDOWN_USB2P6_ID, - PM_VREG_PDOWN_USB3P3_ID, - PM_VREG_PDOWN_INVALID_ID, - - /* backward compatible enums only */ - PM_VREG_PDOWN_CAM_ID = PM_VREG_PDOWN_GP1_ID, - PM_VREG_PDOWN_MDDI_ID = PM_VREG_PDOWN_GP2_ID, - PM_VREG_PDOWN_RUIM2_ID = PM_VREG_PDOWN_GP3_ID, - PM_VREG_PDOWN_AUX_ID = PM_VREG_PDOWN_GP4_ID, - PM_VREG_PDOWN_AUX2_ID = PM_VREG_PDOWN_GP5_ID, - PM_VREG_PDOWN_BT_ID = PM_VREG_PDOWN_GP6_ID, - - PM_VREG_PDOWN_MSME_ID = PM_VREG_PDOWN_MSME1_ID, - PM_VREG_PDOWN_MSMC_ID = PM_VREG_PDOWN_MSMC1_ID, - PM_VREG_PDOWN_RFA1_ID = PM_VREG_PDOWN_RFRX2_ID, - PM_VREG_PDOWN_RFA2_ID = PM_VREG_PDOWN_RFTX2_ID, - PM_VREG_PDOWN_XO_ID = PM_VREG_PDOWN_TCXO_ID -}; - -enum { - PCOM_CLKRGM_APPS_RESET_USB_PHY = 34, - PCOM_CLKRGM_APPS_RESET_USBH = 37, }; -/* gpio info for PCOM_RPC_GPIO_TLMM_CONFIG_EX */ - -#define GPIO_ENABLE 0 -#define GPIO_DISABLE 1 - -#define GPIO_INPUT 0 -#define GPIO_OUTPUT 1 - -#define GPIO_NO_PULL 0 -#define GPIO_PULL_DOWN 1 -#define GPIO_KEEPER 2 -#define GPIO_PULL_UP 3 - -#define GPIO_2MA 0 -#define GPIO_4MA 1 -#define GPIO_6MA 2 -#define GPIO_8MA 3 -#define GPIO_10MA 4 -#define GPIO_12MA 5 -#define GPIO_14MA 6 -#define GPIO_16MA 7 - -#define PCOM_GPIO_CFG(gpio, func, dir, pull, drvstr) \ - ((((gpio) & 0x3FF) << 4) | \ - ((func) & 0xf) | \ - (((dir) & 0x1) << 14) | \ - (((pull) & 0x3) << 15) | \ - (((drvstr) & 0xF) << 17)) - +#ifdef CONFIG_MSM_PROC_COMM +void msm_proc_comm_reset_modem_now(void); int msm_proc_comm(unsigned cmd, unsigned *data1, unsigned *data2); -void __init proc_comm_boot_wait(void); +#else +static inline void msm_proc_comm_reset_modem_now(void) { } +static inline int msm_proc_comm(unsigned cmd, unsigned *data1, unsigned *data2) +{ return 0; } +#endif #endif diff --git a/arch/arm/mach-msm/proc_comm_test.c b/arch/arm/mach-msm/proc_comm_test.c new file mode 100644 index 00000000000..8228f3a2bc4 --- /dev/null +++ b/arch/arm/mach-msm/proc_comm_test.c @@ -0,0 +1,125 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * PROC COMM TEST Driver source file + */ + +#include +#include +#include +#include +#include "proc_comm.h" + +static struct dentry *dent; +static int proc_comm_test_res; + +static int proc_comm_reverse_test(void) +{ + uint32_t data1, data2; + int rc; + + data1 = 10; + data2 = 20; + + rc = msm_proc_comm(PCOM_OEM_TEST_CMD, &data1, &data2); + if (rc) + return rc; + + if ((data1 != 20) || (data2 != 10)) + return -1; + + return 0; +} + +static ssize_t debug_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + char _buf[16]; + + snprintf(_buf, sizeof(_buf), "%i\n", proc_comm_test_res); + + return simple_read_from_buffer(buf, count, pos, _buf, strlen(_buf)); +} + +static ssize_t debug_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + + unsigned char cmd[64]; + int len; + + if (count < 1) + return 0; + + len = count > 63 ? 63 : count; + + if (copy_from_user(cmd, buf, len)) + return -EFAULT; + + cmd[len] = 0; + + if (cmd[len-1] == '\n') { + cmd[len-1] = 0; + len--; + } + + if (!strncmp(cmd, "reverse_test", 64)) + proc_comm_test_res = proc_comm_reverse_test(); + else + proc_comm_test_res = -EINVAL; + + if (proc_comm_test_res) + pr_err("proc comm test fail %d\n", + proc_comm_test_res); + else + pr_info("proc comm test passed\n"); + + return count; +} + +static int debug_release(struct inode *ip, struct file *fp) +{ + return 0; +} + +static int debug_open(struct inode *ip, struct file *fp) +{ + return 0; +} + +static const struct file_operations debug_ops = { + .owner = THIS_MODULE, + .open = debug_open, + .release = debug_release, + .read = debug_read, + .write = debug_write, +}; + +static void __exit proc_comm_test_mod_exit(void) +{ + debugfs_remove(dent); +} + +static int __init proc_comm_test_mod_init(void) +{ + dent = debugfs_create_file("proc_comm", 0444, 0, NULL, &debug_ops); + proc_comm_test_res = -1; + return 0; +} + +module_init(proc_comm_test_mod_init); +module_exit(proc_comm_test_mod_exit); + +MODULE_DESCRIPTION("PROC COMM TEST Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5/Makefile b/arch/arm/mach-msm/qdsp5/Makefile new file mode 100644 index 00000000000..d8e7bf3b469 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/Makefile @@ -0,0 +1,17 @@ +obj-y += adsp.o adsp_driver.o adsp_info.o adsp_rm.o +obj-y += adsp_video_verify_cmd.o +obj-y += adsp_videoenc_verify_cmd.o +obj-y += adsp_jpeg_verify_cmd.o adsp_jpeg_patch_event.o +obj-y += adsp_vfe_verify_cmd.o adsp_vfe_patch_event.o +obj-y += adsp_lpm_verify_cmd.o +ifdef CONFIG_MSM7X27A_AUDIO +obj-y += audio_pcm_in.o +else +obj-y += audio_in.o +endif +obj-$(CONFIG_MSM7X27A_AUDIO) += audio_evrc_in.o audio_qcelp_in.o +obj-y += audio_out.o audio_mp3.o audmgr.o audpp.o audrec.o audpreproc.o +obj-y += audio_evrc.o audio_qcelp.o audio_amrnb.o audio_aac.o audio_amrnb_in.o +obj-y += audio_wma.o audio_voicememo.o audio_pcm.o audio_amrwb.o audio_wmapro.o +obj-y += snd.o snd_adie.o +obj-$(CONFIG_ARCH_MSM7X27A) += audio_fm.o diff --git a/arch/arm/mach-msm/qdsp5/adsp.c b/arch/arm/mach-msm/qdsp5/adsp.c new file mode 100644 index 00000000000..d392cce9d1a --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp.c @@ -0,0 +1,1397 @@ +/* arch/arm/mach-msm/qdsp5/adsp.c + * + * Register/Interrupt access for userspace aDSP library. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Author: Iliyan Malchev + * + * 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. + * + */ + +/* TODO: + * - move shareable rpc code outside of adsp.c + * - general solution for virt->phys patchup + * - queue IDs should be relative to modules + * - disallow access to non-associated queues + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_DEBUG_FS +static struct dentry *dentry_adsp; +static struct dentry *dentry_wdata; +static struct dentry *dentry_rdata; +static int wdump, rdump; +#endif /* CONFIG_DEBUG_FS */ +static struct wake_lock adsp_wake_lock; +static inline void prevent_suspend(void) +{ + wake_lock(&adsp_wake_lock); +} +static inline void allow_suspend(void) +{ + wake_unlock(&adsp_wake_lock); +} + +#include +#include +#include +#include +#include "adsp.h" + +#define INT_ADSP INT_ADSP_A9_A11 + +static struct adsp_info adsp_info; +static struct msm_rpc_endpoint *rpc_cb_server_client; +static struct msm_adsp_module *adsp_modules; +static int adsp_open_count; + +static uint32_t rpc_adsp_rtos_atom_prog; +static uint32_t rpc_adsp_rtos_atom_vers; +static uint32_t rpc_adsp_rtos_atom_vers_comp; +static uint32_t rpc_adsp_rtos_mtoa_prog; +static uint32_t rpc_adsp_rtos_mtoa_vers; +static uint32_t rpc_adsp_rtos_mtoa_vers_comp; +static DEFINE_MUTEX(adsp_open_lock); + +/* protect interactions with the ADSP command/message queue */ +static spinlock_t adsp_cmd_lock; +static spinlock_t adsp_write_lock; + +static uint32_t current_image = -1; + +void adsp_set_image(struct adsp_info *info, uint32_t image) +{ + current_image = image; +} + +/* + * Checks whether the module_id is available in the + * module_entries table.If module_id is available returns `0`. + * If module_id is not available returns `-ENXIO`. + */ +static int32_t adsp_validate_module(uint32_t module_id) +{ + uint32_t *ptr; + uint32_t module_index; + uint32_t num_mod_entries; + + ptr = adsp_info.init_info_ptr->module_entries; + num_mod_entries = adsp_info.init_info_ptr->module_table_size; + + for (module_index = 0; module_index < num_mod_entries; module_index++) + if (module_id == ptr[module_index]) + return 0; + + return -ENXIO; +} + +static int32_t adsp_validate_queue(uint32_t mod_id, unsigned q_idx, + uint32_t size) +{ + int32_t i; + struct adsp_rtos_mp_mtoa_init_info_type *sptr; + + sptr = adsp_info.init_info_ptr; + for (i = 0; i < sptr->mod_to_q_entries; i++) + if (mod_id == sptr->mod_to_q_tbl[i].module) + if (q_idx == sptr->mod_to_q_tbl[i].q_type) { + if (size <= sptr->mod_to_q_tbl[i].q_max_len) + return 0; + MM_ERR("q_idx: %d is not a valid queue \ + for module %x\n", q_idx, mod_id); + return -EINVAL; + } + MM_ERR("cmd_buf size is more than allowed size\n"); + return -EINVAL; +} + +uint32_t adsp_get_module(struct adsp_info *info, uint32_t task) +{ + return info->task_to_module[current_image][task]; +} + +uint32_t adsp_get_queue_offset(struct adsp_info *info, uint32_t queue_id) +{ + return info->queue_offset[current_image][queue_id]; +} + +static int rpc_adsp_rtos_app_to_modem(uint32_t cmd, uint32_t module, + struct msm_adsp_module *adsp_module) +{ + int rc; + struct rpc_adsp_rtos_app_to_modem_args_t rpc_req; + struct rpc_reply_hdr rpc_rsp; + + rpc_req.gotit = cpu_to_be32(1); + rpc_req.cmd = cpu_to_be32(cmd); + rpc_req.proc_id = cpu_to_be32(RPC_ADSP_RTOS_PROC_APPS); + rpc_req.module = cpu_to_be32(module); + rc = msm_rpc_call_reply(adsp_module->rpc_client, + RPC_ADSP_RTOS_APP_TO_MODEM_PROC, + &rpc_req, sizeof(rpc_req), + &rpc_rsp, sizeof(rpc_rsp), + 5 * HZ); + + if (rc < 0) { + MM_ERR("error receiving RPC reply: %d (%d)\n", + rc, -ERESTARTSYS); + return rc; + } + + if (be32_to_cpu(rpc_rsp.reply_stat) != RPCMSG_REPLYSTAT_ACCEPTED) { + MM_ERR("RPC call was denied!\n"); + return -EPERM; + } + + if (be32_to_cpu(rpc_rsp.data.acc_hdr.accept_stat) != + RPC_ACCEPTSTAT_SUCCESS) { + MM_ERR("RPC call was not successful (%d)\n", + be32_to_cpu(rpc_rsp.data.acc_hdr.accept_stat)); + return -EINVAL; + } + + return 0; +} + +static int get_module_index(uint32_t id) +{ + int mod_idx; + for (mod_idx = 0; mod_idx < adsp_info.module_count; mod_idx++) + if (adsp_info.module[mod_idx].id == id) + return mod_idx; + + return -ENXIO; +} + +static struct msm_adsp_module *find_adsp_module_by_id( + struct adsp_info *info, uint32_t id) +{ + int mod_idx; + + if (id > info->max_module_id) { + return NULL; + } else { + mod_idx = get_module_index(id); + if (mod_idx < 0) + return NULL; + return info->id_to_module[mod_idx]; + } +} + +static struct msm_adsp_module *find_adsp_module_by_name( + struct adsp_info *info, const char *name) +{ + unsigned n; + for (n = 0; n < info->module_count; n++) + if (!strcmp(name, adsp_modules[n].name)) + return adsp_modules + n; + return NULL; +} + +static int adsp_rpc_init(struct msm_adsp_module *adsp_module) +{ + /* remove the original connect once compatible support is complete */ + adsp_module->rpc_client = msm_rpc_connect( + rpc_adsp_rtos_atom_prog, + rpc_adsp_rtos_atom_vers, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(adsp_module->rpc_client)) + adsp_module->rpc_client = msm_rpc_connect_compatible( + rpc_adsp_rtos_atom_prog, + rpc_adsp_rtos_atom_vers_comp, + MSM_RPC_UNINTERRUPTIBLE); + + if (IS_ERR(adsp_module->rpc_client)) { + int rc = PTR_ERR(adsp_module->rpc_client); + adsp_module->rpc_client = 0; + MM_ERR("could not open rpc client: %d\n", rc); + return rc; + } + + return 0; +} + +/* + * Send RPC_ADSP_RTOS_CMD_GET_INIT_INFO cmd to ARM9 and get + * queue offsets and module entries (init info) as part of the event. + */ +static void msm_get_init_info(void) +{ + int rc; + struct rpc_adsp_rtos_app_to_modem_args_t rpc_req; + struct rpc_reply_hdr rpc_rsp; + + adsp_info.init_info_rpc_client = msm_rpc_connect( + rpc_adsp_rtos_atom_prog, + rpc_adsp_rtos_atom_vers, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(adsp_info.init_info_rpc_client)) { + adsp_info.init_info_rpc_client = msm_rpc_connect_compatible( + rpc_adsp_rtos_atom_prog, + rpc_adsp_rtos_atom_vers_comp, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(adsp_info.init_info_rpc_client)) { + rc = PTR_ERR(adsp_info.init_info_rpc_client); + adsp_info.init_info_rpc_client = 0; + MM_ERR("could not open rpc client: %d\n", rc); + return; + } + } + + rpc_req.gotit = cpu_to_be32(1); + rpc_req.cmd = cpu_to_be32(RPC_ADSP_RTOS_CMD_GET_INIT_INFO); + rpc_req.proc_id = cpu_to_be32(RPC_ADSP_RTOS_PROC_APPS); + rpc_req.module = 0; + + rc = msm_rpc_call_reply(adsp_info.init_info_rpc_client, + RPC_ADSP_RTOS_APP_TO_MODEM_PROC, + &rpc_req, sizeof(rpc_req), + &rpc_rsp, sizeof(rpc_rsp), + 5 * HZ); + + if (rc < 0) + MM_ERR("could not send RPC request: %d\n", rc); +} + +int msm_adsp_get(const char *name, struct msm_adsp_module **out, + struct msm_adsp_ops *ops, void *driver_data) +{ + struct msm_adsp_module *module; + int rc = 0; + static uint32_t init_info_cmd_sent; + + if (!init_info_cmd_sent) { + init_waitqueue_head(&adsp_info.init_info_wait); + msm_get_init_info(); + rc = wait_event_timeout(adsp_info.init_info_wait, + adsp_info.init_info_state == ADSP_STATE_INIT_INFO, + 5 * HZ); + if (!rc) { + MM_ERR("INIT_INFO failed\n"); + return -ETIMEDOUT; + } + init_info_cmd_sent++; + } + + module = find_adsp_module_by_name(&adsp_info, name); + if (!module) + return -ENODEV; + + mutex_lock(&module->lock); + MM_INFO("opening module %s\n", module->name); + + if (module->ops) { + rc = -EBUSY; + goto done; + } + + rc = adsp_rpc_init(module); + if (rc) + goto done; + + module->ops = ops; + module->driver_data = driver_data; + *out = module; + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_REGISTER_APP, + module->id, module); + if (rc) { + module->ops = NULL; + module->driver_data = NULL; + *out = NULL; + MM_ERR("REGISTER_APP failed\n"); + goto done; + } + + MM_DBG("module %s has been registered\n", module->name); + +done: + mutex_unlock(&module->lock); + return rc; +} +EXPORT_SYMBOL(msm_adsp_get); + +static int msm_adsp_disable_locked(struct msm_adsp_module *module); + +void msm_adsp_put(struct msm_adsp_module *module) +{ + unsigned long flags; + + mutex_lock(&module->lock); + if (module->ops) { + MM_INFO("closing module %s\n", module->name); + + /* lock to ensure a dsp event cannot be delivered + * during or after removal of the ops and driver_data + */ + spin_lock_irqsave(&adsp_cmd_lock, flags); + module->ops = NULL; + module->driver_data = NULL; + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + + if (module->state != ADSP_STATE_DISABLED) { + MM_INFO("disabling module %s\n", module->name); + msm_adsp_disable_locked(module); + } + + msm_rpc_close(module->rpc_client); + module->rpc_client = 0; + } else { + MM_INFO("module %s is already closed\n", module->name); + } + mutex_unlock(&module->lock); +} +EXPORT_SYMBOL(msm_adsp_put); + +/* this should be common code with rpc_servers.c */ +static int rpc_send_accepted_void_reply(struct msm_rpc_endpoint *client, + uint32_t xid, uint32_t accept_status) +{ + int rc = 0; + uint8_t reply_buf[sizeof(struct rpc_reply_hdr)]; + struct rpc_reply_hdr *reply = (struct rpc_reply_hdr *)reply_buf; + + reply->xid = cpu_to_be32(xid); + reply->type = cpu_to_be32(1); /* reply */ + reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + + reply->data.acc_hdr.accept_stat = cpu_to_be32(accept_status); + reply->data.acc_hdr.verf_flavor = 0; + reply->data.acc_hdr.verf_length = 0; + + rc = msm_rpc_write(rpc_cb_server_client, reply_buf, sizeof(reply_buf)); + if (rc < 0) + MM_ERR("could not write RPC response: %d\n", rc); + return rc; +} + +int __msm_adsp_write(struct msm_adsp_module *module, unsigned dsp_queue_addr, + void *cmd_buf, size_t cmd_size) +{ + uint32_t ctrl_word; + uint32_t dsp_q_addr; + uint32_t dsp_addr; + uint32_t cmd_id = 0; + int cnt = 0; + int ret_status = 0; + unsigned long flags; + struct adsp_info *info; + + if (!module || !cmd_buf) { + MM_ERR("Called with NULL parameters\n"); + return -EINVAL; + } + info = module->info; + spin_lock_irqsave(&adsp_write_lock, flags); + + if (module->state != ADSP_STATE_ENABLED) { + spin_unlock_irqrestore(&adsp_write_lock, flags); + MM_ERR("module %s not enabled before write\n", module->name); + return -ENODEV; + } + if (adsp_validate_module(module->id)) { + spin_unlock_irqrestore(&adsp_write_lock, flags); + MM_ERR("module id validation failed %s %d\n", + module->name, module->id); + return -ENXIO; + } + if (dsp_queue_addr >= QDSP_MAX_NUM_QUEUES) { + spin_unlock_irqrestore(&adsp_write_lock, flags); + MM_ERR("Invalid Queue Index: %d\n", dsp_queue_addr); + return -ENXIO; + } + if (adsp_validate_queue(module->id, dsp_queue_addr, cmd_size)) { + spin_unlock_irqrestore(&adsp_write_lock, flags); + return -EINVAL; + } + dsp_q_addr = adsp_get_queue_offset(info, dsp_queue_addr); + dsp_q_addr &= ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M; + + /* Poll until the ADSP is ready to accept a command. + * Wait for 100us, return error if it's not responding. + * If this returns an error, we need to disable ALL modules and + * then retry. + */ + while (((ctrl_word = readl(info->write_ctrl)) & + ADSP_RTOS_WRITE_CTRL_WORD_READY_M) != + ADSP_RTOS_WRITE_CTRL_WORD_READY_V) { + if (cnt > 50) { + MM_ERR("timeout waiting for DSP write ready\n"); + ret_status = -EIO; + goto fail; + } + MM_DBG("waiting for DSP write ready\n"); + udelay(2); + cnt++; + } + + /* Set the mutex bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M); + ctrl_word |= ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V; + + /* Clear the command bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_CMD_M); + + /* Set the queue address bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M); + ctrl_word |= dsp_q_addr; + + writel(ctrl_word, info->write_ctrl); + + /* Generate an interrupt to the DSP. This notifies the DSP that + * we are about to send a command on this particular queue. The + * DSP will in response change its state. + */ + writel(1, info->send_irq); + + /* Poll until the adsp responds to the interrupt; this does not + * generate an interrupt from the adsp. This should happen within + * 5ms. + */ + cnt = 0; + while ((readl(info->write_ctrl) & + ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M) == + ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V) { + if (cnt > 2500) { + MM_ERR("timeout waiting for adsp ack\n"); + ret_status = -EIO; + goto fail; + } + udelay(2); + cnt++; + } + + /* Read the ctrl word */ + ctrl_word = readl(info->write_ctrl); + + if ((ctrl_word & ADSP_RTOS_WRITE_CTRL_WORD_STATUS_M) != + ADSP_RTOS_WRITE_CTRL_WORD_NO_ERR_V) { + ret_status = -EAGAIN; + goto fail; + } else { + /* No error */ + /* Get the DSP buffer address */ + dsp_addr = (ctrl_word & ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M) + + (uint32_t)MSM_AD5_BASE; + + if (dsp_addr < (uint32_t)(MSM_AD5_BASE + QDSP_RAMC_OFFSET)) { + uint16_t *buf_ptr = (uint16_t *) cmd_buf; + uint16_t *dsp_addr16 = (uint16_t *)dsp_addr; + cmd_size /= sizeof(uint16_t); + + /* Save the command ID */ + cmd_id = (uint32_t) buf_ptr[0]; + + /* Copy the command to DSP memory */ + cmd_size++; + while (--cmd_size) + *dsp_addr16++ = *buf_ptr++; + } else { + uint32_t *buf_ptr = (uint32_t *) cmd_buf; + uint32_t *dsp_addr32 = (uint32_t *)dsp_addr; + cmd_size /= sizeof(uint32_t); + + /* Save the command ID */ + cmd_id = buf_ptr[0]; + + cmd_size++; + while (--cmd_size) + *dsp_addr32++ = *buf_ptr++; + } + + /* Set the mutex bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M); + ctrl_word |= ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V; + + /* Set the command bits to write done */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_CMD_M); + ctrl_word |= ADSP_RTOS_WRITE_CTRL_WORD_CMD_WRITE_DONE_V; + + /* Set the queue address bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M); + ctrl_word |= dsp_q_addr; + + writel(ctrl_word, info->write_ctrl); + + /* Generate an interrupt to the DSP. It does not respond with + * an interrupt, and we do not need to wait for it to + * acknowledge, because it will hold the mutex lock until it's + * ready to receive more commands again. + */ + writel(1, info->send_irq); + + module->num_commands++; + } /* Ctrl word status bits were 00, no error in the ctrl word */ + +fail: + spin_unlock_irqrestore(&adsp_write_lock, flags); + return ret_status; +} +EXPORT_SYMBOL(msm_adsp_write); + +int msm_adsp_write(struct msm_adsp_module *module, unsigned dsp_queue_addr, + void *cmd_buf, size_t cmd_size) +{ + int rc, retries = 0; +#ifdef CONFIG_DEBUG_FS + uint16_t *ptr; + int ii; + + if (wdump > 0) { + ptr = cmd_buf; + pr_info("A->D:%x\n", module->id); + pr_info("adsp: %x %d\n", dsp_queue_addr, cmd_size); + for (ii = 0; ii < cmd_size/2; ii++) + pr_info("%x ", ptr[ii]); + pr_info("\n"); + } +#endif /* CONFIG_DEBUG_FS */ + do { + rc = __msm_adsp_write(module, dsp_queue_addr, cmd_buf, + cmd_size); + if (rc == -EAGAIN) + udelay(10); + } while (rc == -EAGAIN && retries++ < 300); + if (retries > 50) + MM_ERR("adsp: %s command took %d attempts: rc %d\n", + module->name, retries, rc); + return rc; +} + +static void *event_addr; +static void read_event(void *buf, size_t len) +{ + uint32_t dptr[3]; + struct rpc_adsp_rtos_modem_to_app_args_t *sptr; + struct adsp_rtos_mp_mtoa_type *pkt_ptr; + + sptr = event_addr; + pkt_ptr = &sptr->mtoa_pkt.adsp_rtos_mp_mtoa_data.mp_mtoa_packet; + + dptr[0] = be32_to_cpu(sptr->mtoa_pkt.mp_mtoa_header.event); + dptr[1] = be32_to_cpu(pkt_ptr->module); + dptr[2] = be32_to_cpu(pkt_ptr->image); + + if (len > EVENT_LEN) + len = EVENT_LEN; + + memcpy(buf, dptr, len); +} + +static void handle_adsp_rtos_mtoa_app(struct rpc_request_hdr *req) +{ + struct rpc_adsp_rtos_modem_to_app_args_t *args = + (struct rpc_adsp_rtos_modem_to_app_args_t *)req; + uint32_t event; + uint32_t proc_id; + uint32_t module_id; + uint32_t image; + struct msm_adsp_module *module; + struct adsp_rtos_mp_mtoa_type *pkt_ptr; + struct queue_to_offset_type *qptr; + struct queue_to_offset_type *qtbl; + struct mod_to_queue_offsets *mqptr; + struct mod_to_queue_offsets *mqtbl; + uint32_t *mptr; + uint32_t *mtbl; + uint32_t q_idx; + uint32_t num_entries; + uint32_t entries_per_image; + struct adsp_rtos_mp_mtoa_init_info_type *iptr; + struct adsp_rtos_mp_mtoa_init_info_type *sptr; + int32_t i_no, e_idx; + + event = be32_to_cpu(args->mtoa_pkt.mp_mtoa_header.event); + proc_id = be32_to_cpu(args->mtoa_pkt.mp_mtoa_header.proc_id); + + if (event == RPC_ADSP_RTOS_INIT_INFO) { + MM_INFO("INIT_INFO Event\n"); + sptr = &args->mtoa_pkt.adsp_rtos_mp_mtoa_data.mp_mtoa_init_packet; + + iptr = adsp_info.init_info_ptr; + iptr->image_count = be32_to_cpu(sptr->image_count); + if (iptr->image_count > IMG_MAX) + iptr->image_count = IMG_MAX; + iptr->num_queue_offsets = be32_to_cpu(sptr->num_queue_offsets); + num_entries = iptr->num_queue_offsets; + if (num_entries > ENTRIES_MAX) { + num_entries = ENTRIES_MAX; + iptr->num_queue_offsets = ENTRIES_MAX; + } + qptr = &sptr->queue_offsets_tbl[0][0]; + for (i_no = 0; i_no < iptr->image_count; i_no++) { + qtbl = &iptr->queue_offsets_tbl[i_no][0]; + for (e_idx = 0; e_idx < num_entries; e_idx++) { + qtbl[e_idx].offset = be32_to_cpu(qptr->offset); + qtbl[e_idx].queue = be32_to_cpu(qptr->queue); + q_idx = be32_to_cpu(qptr->queue); + iptr->queue_offsets[i_no][q_idx] = qtbl[e_idx].offset; + qptr++; + } + } + + num_entries = be32_to_cpu(sptr->num_task_module_entries); + if (num_entries > ENTRIES_MAX) + num_entries = ENTRIES_MAX; + iptr->num_task_module_entries = num_entries; + entries_per_image = num_entries / iptr->image_count; + mptr = &sptr->task_to_module_tbl[0][0]; + for (i_no = 0; i_no < iptr->image_count; i_no++) { + mtbl = &iptr->task_to_module_tbl[i_no][0]; + for (e_idx = 0; e_idx < entries_per_image; e_idx++) { + mtbl[e_idx] = be32_to_cpu(*mptr); + mptr++; + } + } + + iptr->module_table_size = be32_to_cpu(sptr->module_table_size); +#if CONFIG_ADSP_RPC_VER > 0x30001 + if (iptr->module_table_size > MODULES_MAX) + iptr->module_table_size = MODULES_MAX; +#else + if (iptr->module_table_size > ENTRIES_MAX) + iptr->module_table_size = ENTRIES_MAX; +#endif + mptr = &sptr->module_entries[0]; + for (i_no = 0; i_no < iptr->module_table_size; i_no++) + iptr->module_entries[i_no] = be32_to_cpu(mptr[i_no]); + + mqptr = &sptr->mod_to_q_tbl[0]; + mqtbl = &iptr->mod_to_q_tbl[0]; + iptr->mod_to_q_entries = be32_to_cpu(sptr->mod_to_q_entries); + if (iptr->mod_to_q_entries > ENTRIES_MAX) + iptr->mod_to_q_entries = ENTRIES_MAX; + for (e_idx = 0; e_idx < iptr->mod_to_q_entries; e_idx++) { + mqtbl[e_idx].module = be32_to_cpu(mqptr->module); + mqtbl[e_idx].q_type = be32_to_cpu(mqptr->q_type); + mqtbl[e_idx].q_max_len = be32_to_cpu(mqptr->q_max_len); + mqptr++; + } + + adsp_info.init_info_state = ADSP_STATE_INIT_INFO; + rpc_send_accepted_void_reply(rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_SUCCESS); + wake_up(&adsp_info.init_info_wait); + + return; + } + + pkt_ptr = &args->mtoa_pkt.adsp_rtos_mp_mtoa_data.mp_mtoa_packet; + module_id = be32_to_cpu(pkt_ptr->module); + image = be32_to_cpu(pkt_ptr->image); + + MM_DBG("rpc event=%d, proc_id=%d, module=%d, image=%d\n", + event, proc_id, module_id, image); + + module = find_adsp_module_by_id(&adsp_info, module_id); + if (!module) { + MM_ERR("module %d is not supported!\n", module_id); + rpc_send_accepted_void_reply(rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_GARBAGE_ARGS); + return; + } + + mutex_lock(&module->lock); + switch (event) { + case RPC_ADSP_RTOS_MOD_READY: + MM_INFO("module %s: READY\n", module->name); + module->state = ADSP_STATE_ENABLED; + wake_up(&module->state_wait); + adsp_set_image(module->info, image); + break; + case RPC_ADSP_RTOS_MOD_DISABLE: + MM_INFO("module %s: DISABLED\n", module->name); + module->state = ADSP_STATE_DISABLED; + wake_up(&module->state_wait); + break; + case RPC_ADSP_RTOS_SERVICE_RESET: + MM_INFO("module %s: SERVICE_RESET\n", module->name); + module->state = ADSP_STATE_DISABLED; + wake_up(&module->state_wait); + break; + case RPC_ADSP_RTOS_CMD_SUCCESS: + MM_INFO("module %s: CMD_SUCCESS\n", module->name); + break; + case RPC_ADSP_RTOS_CMD_FAIL: + MM_INFO("module %s: CMD_FAIL\n", module->name); + break; + case RPC_ADSP_RTOS_DISABLE_FAIL: + MM_INFO("module %s: DISABLE_FAIL\n", module->name); + break; + default: + MM_ERR("unknown event %d\n", event); + rpc_send_accepted_void_reply(rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_GARBAGE_ARGS); + mutex_unlock(&module->lock); + return; + } + rpc_send_accepted_void_reply(rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_SUCCESS); +#ifdef CONFIG_MSM_ADSP_REPORT_EVENTS + event_addr = (uint32_t *)req; + module->ops->event(module->driver_data, + EVENT_MSG_ID, + EVENT_LEN, + read_event); +#endif + mutex_unlock(&module->lock); +} + +static int handle_adsp_rtos_mtoa(struct rpc_request_hdr *req) +{ + switch (req->procedure) { + case RPC_ADSP_RTOS_MTOA_NULL_PROC: + rpc_send_accepted_void_reply(rpc_cb_server_client, + req->xid, + RPC_ACCEPTSTAT_SUCCESS); + break; +#if CONFIG_ADSP_RPC_VER > 0x30001 + case RPC_ADSP_RTOS_MTOA_INIT_INFO_PROC: + case RPC_ADSP_RTOS_MTOA_EVENT_INFO_PROC: +#else + case RPC_ADSP_RTOS_MODEM_TO_APP_PROC: +#endif + handle_adsp_rtos_mtoa_app(req); + break; + default: + MM_ERR("unknowned proc %d\n", req->procedure); + rpc_send_accepted_void_reply( + rpc_cb_server_client, req->xid, + RPC_ACCEPTSTAT_PROC_UNAVAIL); + break; + } + return 0; +} + +/* this should be common code with rpc_servers.c */ +static int adsp_rpc_thread(void *data) +{ + void *buffer; + struct rpc_request_hdr *req; + int rc, exit = 0; + + do { + rc = msm_rpc_read(rpc_cb_server_client, &buffer, -1, -1); + if (rc < 0) { + MM_ERR("could not read rpc: %d\n", rc); + break; + } + req = (struct rpc_request_hdr *)buffer; + + req->type = be32_to_cpu(req->type); + req->xid = be32_to_cpu(req->xid); + req->rpc_vers = be32_to_cpu(req->rpc_vers); + req->prog = be32_to_cpu(req->prog); + req->vers = be32_to_cpu(req->vers); + req->procedure = be32_to_cpu(req->procedure); + + if (req->type != 0) + goto bad_rpc; + if (req->rpc_vers != 2) + goto bad_rpc; + if (req->prog != rpc_adsp_rtos_mtoa_prog) + goto bad_rpc; + if (!msm_rpc_is_compatible_version(rpc_adsp_rtos_mtoa_vers, + req->vers)) + goto bad_rpc; + + handle_adsp_rtos_mtoa(req); + kfree(buffer); + continue; + +bad_rpc: + MM_ERR("bogus rpc from modem\n"); + kfree(buffer); + } while (!exit); + do_exit(0); +} + +static size_t read_event_size; +static void *read_event_addr; + +static void read_event_16(void *buf, size_t len) +{ + uint16_t *dst = buf; + uint16_t *src = read_event_addr; + len /= 2; + if (len > read_event_size) + len = read_event_size; + while (len--) + *dst++ = *src++; +} + +static void read_event_32(void *buf, size_t len) +{ + uint32_t *dst = buf; + uint32_t *src = read_event_addr; + len /= 2; + if (len > read_event_size) + len = read_event_size; + while (len--) + *dst++ = *src++; +} + +static int adsp_rtos_read_ctrl_word_cmd_tast_to_h_v( + struct adsp_info *info, void *dsp_addr) +{ + struct msm_adsp_module *module; + unsigned rtos_task_id; + unsigned msg_id; + unsigned msg_length; +#ifdef CONFIG_DEBUG_FS + uint16_t *ptr; + int ii; +#endif /* CONFIG_DEBUG_FS */ + void (*func)(void *, size_t); + + if (dsp_addr >= (void *)(MSM_AD5_BASE + QDSP_RAMC_OFFSET)) { + uint32_t *dsp_addr32 = dsp_addr; + uint32_t tmp = *dsp_addr32++; + rtos_task_id = (tmp & ADSP_RTOS_READ_CTRL_WORD_TASK_ID_M) >> 8; + msg_id = (tmp & ADSP_RTOS_READ_CTRL_WORD_MSG_ID_M); + read_event_size = tmp >> 16; + read_event_addr = dsp_addr32; + msg_length = read_event_size * sizeof(uint32_t); + func = read_event_32; + } else { + uint16_t *dsp_addr16 = dsp_addr; + uint16_t tmp = *dsp_addr16++; + rtos_task_id = (tmp & ADSP_RTOS_READ_CTRL_WORD_TASK_ID_M) >> 8; + msg_id = tmp & ADSP_RTOS_READ_CTRL_WORD_MSG_ID_M; + read_event_size = *dsp_addr16++; + read_event_addr = dsp_addr16; + msg_length = read_event_size * sizeof(uint16_t); + func = read_event_16; + } + + if (rtos_task_id > info->max_task_id) { + MM_ERR("bogus task id %d\n", rtos_task_id); + return 0; + } + module = find_adsp_module_by_id(info, + adsp_get_module(info, rtos_task_id)); + + if (!module) { + MM_ERR("no module for task id %d\n", rtos_task_id); + return 0; + } + + module->num_events++; + + if (!module->ops) { + MM_ERR("module %s is not open\n", module->name); + return 0; + } +#ifdef CONFIG_DEBUG_FS + if (rdump > 0) { + ptr = read_event_addr; + pr_info("D->A\n"); + pr_info("m_id = %x id = %x\n", module->id, msg_id); + for (ii = 0; ii < msg_length/2; ii++) + pr_info("%x ", ptr[ii]); + pr_info("\n"); + } +#endif /* CONFIG_DEBUG_FS */ + + module->ops->event(module->driver_data, msg_id, msg_length, func); + return 0; +} + +static int adsp_get_event(struct adsp_info *info) +{ + uint32_t ctrl_word; + uint32_t ready; + void *dsp_addr; + uint32_t cmd_type; + int cnt; + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&adsp_cmd_lock, flags); + + /* Whenever the DSP has a message, it updates this control word + * and generates an interrupt. When we receive the interrupt, we + * read this register to find out what ADSP task the command is + * comming from. + * + * The ADSP should *always* be ready on the first call, but the + * irq handler calls us in a loop (to handle back-to-back command + * processing), so we give the DSP some time to return to the + * ready state. The DSP will not issue another IRQ for events + * pending between the first IRQ and the event queue being drained, + * unfortunately. + */ + + for (cnt = 0; cnt < 50; cnt++) { + ctrl_word = readl(info->read_ctrl); + + if ((ctrl_word & ADSP_RTOS_READ_CTRL_WORD_FLAG_M) == + ADSP_RTOS_READ_CTRL_WORD_FLAG_UP_CONT_V) + goto ready; + + udelay(2); + } + MM_ERR("not ready after 100uS\n"); + rc = -EBUSY; + goto done; + +ready: + /* Here we check to see if there are pending messages. If there are + * none, we siply return -EAGAIN to indicate that there are no more + * messages pending. + */ + ready = ctrl_word & ADSP_RTOS_READ_CTRL_WORD_READY_M; + if ((ready != ADSP_RTOS_READ_CTRL_WORD_READY_V) && + (ready != ADSP_RTOS_READ_CTRL_WORD_CONT_V)) { + rc = -EAGAIN; + goto done; + } + + /* DSP says that there are messages waiting for the host to read */ + + /* Get the Command Type */ + cmd_type = ctrl_word & ADSP_RTOS_READ_CTRL_WORD_CMD_TYPE_M; + + /* Get the DSP buffer address */ + dsp_addr = (void *)((ctrl_word & + ADSP_RTOS_READ_CTRL_WORD_DSP_ADDR_M) + + (uint32_t)MSM_AD5_BASE); + + /* We can only handle Task-to-Host messages */ + if (cmd_type != ADSP_RTOS_READ_CTRL_WORD_CMD_TASK_TO_H_V) { + MM_ERR("unknown dsp cmd_type %d\n", cmd_type); + rc = -EIO; + goto done; + } + + adsp_rtos_read_ctrl_word_cmd_tast_to_h_v(info, dsp_addr); + + ctrl_word = readl(info->read_ctrl); + ctrl_word &= ~ADSP_RTOS_READ_CTRL_WORD_READY_M; + + /* Write ctrl word to the DSP */ + writel(ctrl_word, info->read_ctrl); + + /* Generate an interrupt to the DSP */ + writel(1, info->send_irq); + +done: + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + return rc; +} + +static irqreturn_t adsp_irq_handler(int irq, void *data) +{ + struct adsp_info *info = &adsp_info; + int cnt = 0; + for (cnt = 0; cnt < 15; cnt++) + if (adsp_get_event(info) < 0) + break; + if (cnt > info->event_backlog_max) + info->event_backlog_max = cnt; + info->events_received += cnt; + if (cnt == 15) + MM_ERR("too many (%d) events for single irq!\n", cnt); + return IRQ_HANDLED; +} + +int adsp_set_clkrate(struct msm_adsp_module *module, unsigned long clk_rate) +{ + if (module->clk && clk_rate) + return clk_set_min_rate(module->clk, clk_rate); + + return -EINVAL; +} + +int msm_adsp_enable(struct msm_adsp_module *module) +{ + int rc = 0; + + MM_INFO("enable '%s'state[%d] id[%d]\n", + module->name, module->state, module->id); + + mutex_lock(&module->lock); + switch (module->state) { + case ADSP_STATE_DISABLED: + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_ENABLE, + module->id, module); + if (rc) + break; + module->state = ADSP_STATE_ENABLING; + mutex_unlock(&module->lock); + rc = wait_event_timeout(module->state_wait, + module->state != ADSP_STATE_ENABLING, + 1 * HZ); + mutex_lock(&module->lock); + if (module->state == ADSP_STATE_ENABLED) { + rc = 0; + } else { + MM_ERR("module '%s' enable timed out\n", module->name); + rc = -ETIMEDOUT; + } + if (module->open_count++ == 0 && module->clk) + clk_enable(module->clk); + + mutex_lock(&adsp_open_lock); + if (adsp_open_count++ == 0) { + enable_irq(INT_ADSP); + prevent_suspend(); + } + mutex_unlock(&adsp_open_lock); + break; + case ADSP_STATE_ENABLING: + MM_DBG("module '%s' enable in progress\n", module->name); + break; + case ADSP_STATE_ENABLED: + MM_DBG("module '%s' already enabled\n", module->name); + break; + case ADSP_STATE_DISABLING: + MM_ERR("module '%s' disable in progress\n", module->name); + rc = -EBUSY; + break; + } + mutex_unlock(&module->lock); + return rc; +} +EXPORT_SYMBOL(msm_adsp_enable); + +int msm_adsp_disable_event_rsp(struct msm_adsp_module *module) +{ + int rc = 0; + + mutex_lock(&module->lock); + + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_DISABLE_EVENT_RSP, + module->id, module); + mutex_unlock(&module->lock); + + return rc; +} +EXPORT_SYMBOL(msm_adsp_disable_event_rsp); + +static int msm_adsp_disable_locked(struct msm_adsp_module *module) +{ + int rc = 0; + + switch (module->state) { + case ADSP_STATE_DISABLED: + MM_DBG("module '%s' already disabled\n", module->name); + break; + case ADSP_STATE_ENABLING: + case ADSP_STATE_ENABLED: + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_DISABLE, + module->id, module); + module->state = ADSP_STATE_DISABLED; + if (--module->open_count == 0 && module->clk) + clk_disable(module->clk); + mutex_lock(&adsp_open_lock); + if (--adsp_open_count == 0) { + disable_irq(INT_ADSP); + allow_suspend(); + MM_DBG("disable interrupt\n"); + } + mutex_unlock(&adsp_open_lock); + } + return rc; +} + +int msm_adsp_disable(struct msm_adsp_module *module) +{ + int rc; + MM_INFO("disable '%s'\n", module->name); + mutex_lock(&module->lock); + rc = msm_adsp_disable_locked(module); + mutex_unlock(&module->lock); + return rc; +} +EXPORT_SYMBOL(msm_adsp_disable); + +static int msm_adsp_probe(struct platform_device *pdev) +{ + unsigned count; + int rc, i; + + if (pdev->id != (rpc_adsp_rtos_atom_vers & RPC_VERSION_MAJOR_MASK)) + return -EINVAL; + + wake_lock_init(&adsp_wake_lock, WAKE_LOCK_SUSPEND, "adsp"); + adsp_info.init_info_ptr = kzalloc( + (sizeof(struct adsp_rtos_mp_mtoa_init_info_type)), GFP_KERNEL); + if (!adsp_info.init_info_ptr) + return -ENOMEM; + + rc = adsp_init_info(&adsp_info); + if (rc) + return rc; + adsp_info.send_irq += (uint32_t) MSM_AD5_BASE; + adsp_info.read_ctrl += (uint32_t) MSM_AD5_BASE; + adsp_info.write_ctrl += (uint32_t) MSM_AD5_BASE; + count = adsp_info.module_count; + + adsp_modules = kzalloc( + (sizeof(struct msm_adsp_module) + sizeof(void *)) * + count, GFP_KERNEL); + if (!adsp_modules) + return -ENOMEM; + + adsp_info.id_to_module = (void *) (adsp_modules + count); + + spin_lock_init(&adsp_cmd_lock); + spin_lock_init(&adsp_write_lock); + + rc = request_irq(INT_ADSP, adsp_irq_handler, IRQF_TRIGGER_RISING, + "adsp", 0); + if (rc < 0) + goto fail_request_irq; + disable_irq(INT_ADSP); + + rpc_cb_server_client = msm_rpc_open(); + if (IS_ERR(rpc_cb_server_client)) { + rpc_cb_server_client = NULL; + rc = PTR_ERR(rpc_cb_server_client); + MM_ERR("could not create rpc server (%d)\n", rc); + goto fail_rpc_open; + } + + rc = msm_rpc_register_server(rpc_cb_server_client, + rpc_adsp_rtos_mtoa_prog, + rpc_adsp_rtos_mtoa_vers); + if (rc) { + MM_ERR("could not register callback server (%d)\n", rc); + goto fail_rpc_register; + } + + /* start the kernel thread to process the callbacks */ + kthread_run(adsp_rpc_thread, NULL, "kadspd"); + + for (i = 0; i < count; i++) { + struct msm_adsp_module *mod = adsp_modules + i; + mutex_init(&mod->lock); + init_waitqueue_head(&mod->state_wait); + mod->info = &adsp_info; + mod->name = adsp_info.module[i].name; + mod->id = adsp_info.module[i].id; + if (adsp_info.module[i].clk_name) + mod->clk = clk_get(NULL, adsp_info.module[i].clk_name); + else + mod->clk = NULL; + if (mod->clk && adsp_info.module[i].clk_rate) + clk_set_min_rate(mod->clk, + adsp_info.module[i].clk_rate); + mod->verify_cmd = adsp_info.module[i].verify_cmd; + mod->patch_event = adsp_info.module[i].patch_event; + INIT_HLIST_HEAD(&mod->pmem_regions); + mod->pdev.name = adsp_info.module[i].pdev_name; + mod->pdev.id = -1; + adsp_info.id_to_module[i] = mod; + platform_device_register(&mod->pdev); + } + + msm_adsp_publish_cdevs(adsp_modules, count); + rmtask_init(); + + return 0; + +fail_rpc_register: + msm_rpc_close(rpc_cb_server_client); + rpc_cb_server_client = NULL; +fail_rpc_open: + enable_irq(INT_ADSP); + free_irq(INT_ADSP, 0); +fail_request_irq: + kfree(adsp_modules); + kfree(adsp_info.init_info_ptr); + return rc; +} +#ifdef CONFIG_DEBUG_FS +static int get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (strict_strtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } + else + return -EINVAL; + } + return 0; +} + + +static ssize_t adsp_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + pr_debug("adsp debugfs opened\n"); + return 0; +} +static ssize_t adsp_debug_write(struct file *file, const char __user *buf, + size_t cnt, loff_t *ppos) +{ + char *access_str = file->private_data; + char lbuf[32]; + int rc; + long int param[5]; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + rc = copy_from_user(lbuf, buf, cnt); + if (rc) { + pr_info("Unable to copy data from user space\n"); + return -EFAULT; + } + lbuf[cnt] = '\0'; + + if (!strcmp(access_str, "write_log")) { + if (get_parameters(lbuf, param, 1) == 0) { + switch (param[0]) { + case 1: + if (wdump <= 0) + wdump = 1; + pr_debug("write cmd to DSP(A->D) dump \ + started:%d\n", wdump); + break; + case 0: + if (wdump > 0) + wdump = 0; + pr_debug("Stop write cmd to \ + DSP(A->D):%d\n", wdump); + break; + default: + rc = -EINVAL; + break; + } + } else + rc = -EINVAL; + } else if (!strcmp(access_str, "read_log")) { + if (get_parameters(lbuf, param, 1) == 0) { + switch (param[0]) { + case 1: + if (rdump <= 0) + rdump = 1; + pr_debug("write cmd from DSP(D->A) dump \ + started:%d\n", wdump); + break; + case 0: + if (rdump > 0) + rdump = 0; + pr_debug("Stop write cmd from \ + DSP(D->A):%d\n", wdump); + break; + default: + rc = -EINVAL; + break; + } + } else + rc = -EINVAL; + } else { + rc = -EINVAL; + } + if (rc == 0) + rc = cnt; + else { + pr_err("%s: rc = %d\n", __func__, rc); + pr_info("\nWrong command: Use =>\n"); + pr_info("-------------------------\n"); + pr_info("To Start A->D:: echo \"1\">/sys/kernel/debug/ \ + adsp_cmd/write_log\n"); + pr_info("To Start D->A:: echo \"1\">/sys/kernel/debug/ \ + adsp_cmd/read_log\n"); + pr_info("To Stop A->D:: echo \"0\">/sys/kernel/debug/ \ + adsp_cmd/write_log\n"); + pr_info("To Stop D->A:: echo \"0\">/sys/kernel/debug/ \ + adsp_cmd/read_log\n"); + pr_info("------------------------\n"); + } + + return rc; +} +#endif + +static struct platform_driver msm_adsp_driver = { + .probe = msm_adsp_probe, + .driver = { + .owner = THIS_MODULE, + }, +}; + +static char msm_adsp_driver_name[] = "rs00000000"; + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations adsp_debug_fops = { + .write = adsp_debug_write, + .open = adsp_debug_open, +}; +#endif + +static int __init adsp_init(void) +{ + int rc; + +#ifdef CONFIG_DEBUG_FS + dentry_adsp = debugfs_create_dir("adsp_cmd", 0); + if (!IS_ERR(dentry_adsp)) { + dentry_wdata = debugfs_create_file("write_log", \ + S_IFREG | S_IRUGO, dentry_adsp, + (void *) "write_log" , &adsp_debug_fops); + dentry_rdata = debugfs_create_file("read_log", \ + S_IFREG | S_IRUGO, dentry_adsp, + (void *) "read_log", &adsp_debug_fops); + } + rdump = 0; + wdump = 0; +#endif /* CONFIG_DEBUG_FS */ + + rpc_adsp_rtos_atom_prog = 0x3000000a; + rpc_adsp_rtos_atom_vers = 0x10001; + rpc_adsp_rtos_atom_vers_comp = 0x00010001; + rpc_adsp_rtos_mtoa_prog = 0x3000000b; +#if CONFIG_ADSP_RPC_VER > 0x30001 + rpc_adsp_rtos_mtoa_vers = 0x30002; + rpc_adsp_rtos_mtoa_vers_comp = 0x00030002; +#else + rpc_adsp_rtos_mtoa_vers = 0x30001; + rpc_adsp_rtos_mtoa_vers_comp = 0x00030001; +#endif + + snprintf(msm_adsp_driver_name, sizeof(msm_adsp_driver_name), + "rs%08x", + rpc_adsp_rtos_atom_prog); + msm_adsp_driver.driver.name = msm_adsp_driver_name; + rc = platform_driver_register(&msm_adsp_driver); + MM_INFO("%s -- %d\n", msm_adsp_driver_name, rc); + return rc; +} + +device_initcall(adsp_init); diff --git a/arch/arm/mach-msm/qdsp5/adsp.h b/arch/arm/mach-msm/qdsp5/adsp.h new file mode 100644 index 00000000000..a15925a1db4 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp.h @@ -0,0 +1,352 @@ +/* arch/arm/mach-msm/qdsp5/adsp.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * Author: Iliyan Malchev + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_ADSP_H +#define _ARCH_ARM_MACH_MSM_ADSP_H + +#include +#include +#include +#include + +int adsp_pmem_fixup(struct msm_adsp_module *module, void **addr, + unsigned long len); +int adsp_pmem_fixup_kvaddr(struct msm_adsp_module *module, void **addr, + unsigned long *kvaddr, unsigned long len, + struct file **filp, unsigned long *offset); +int adsp_pmem_paddr_fixup(struct msm_adsp_module *module, void **addr); + +int adsp_vfe_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); +int adsp_jpeg_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); +int adsp_lpm_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); +int adsp_video_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); +int adsp_videoenc_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); + + +struct adsp_event; + +int adsp_vfe_patch_event(struct msm_adsp_module *module, + struct adsp_event *event); + +int adsp_jpeg_patch_event(struct msm_adsp_module *module, + struct adsp_event *event); + + +struct adsp_module_info { + const char *name; + const char *pdev_name; + uint32_t id; + const char *clk_name; + unsigned long clk_rate; + int (*verify_cmd) (struct msm_adsp_module*, unsigned int, void *, + size_t); + int (*patch_event) (struct msm_adsp_module*, struct adsp_event *); +}; + +#define ADSP_EVENT_MAX_SIZE 496 +#define EVENT_LEN 12 +#define EVENT_MSG_ID ((uint16_t)~0) + +struct adsp_event { + struct list_head list; + uint32_t size; /* always in bytes */ + uint16_t msg_id; + uint16_t type; /* 0 for msgs (from aDSP), -1 for events (from ARM9) */ + int is16; /* always 0 (msg is 32-bit) when the event type is 1(ARM9) */ + union { + uint16_t msg16[ADSP_EVENT_MAX_SIZE / 2]; + uint32_t msg32[ADSP_EVENT_MAX_SIZE / 4]; + } data; +}; + +struct adsp_info { + uint32_t send_irq; + uint32_t read_ctrl; + uint32_t write_ctrl; + + uint32_t max_msg16_size; + uint32_t max_msg32_size; + + uint32_t max_task_id; + uint32_t max_module_id; + uint32_t max_queue_id; + uint32_t max_image_id; + + /* for each image id, a map of queue id to offset */ + uint32_t **queue_offset; + + /* for each image id, a map of task id to module id */ + uint32_t **task_to_module; + + /* for each module id, map of module id to module */ + struct msm_adsp_module **id_to_module; + + uint32_t module_count; + struct adsp_module_info *module; + + /* stats */ + uint32_t events_received; + uint32_t event_backlog_max; + + /* rpc_client for init_info */ + struct msm_rpc_endpoint *init_info_rpc_client; + struct adsp_rtos_mp_mtoa_init_info_type *init_info_ptr; + wait_queue_head_t init_info_wait; + unsigned init_info_state; +}; + +#define RPC_ADSP_RTOS_ATOM_NULL_PROC 0 +#define RPC_ADSP_RTOS_MTOA_NULL_PROC 0 +#define RPC_ADSP_RTOS_APP_TO_MODEM_PROC 2 +#define RPC_ADSP_RTOS_MODEM_TO_APP_PROC 2 +#define RPC_ADSP_RTOS_MTOA_EVENT_INFO_PROC 3 +#define RPC_ADSP_RTOS_MTOA_INIT_INFO_PROC 4 + +enum rpc_adsp_rtos_proc_type { + RPC_ADSP_RTOS_PROC_NONE = 0, + RPC_ADSP_RTOS_PROC_MODEM = 1, + RPC_ADSP_RTOS_PROC_APPS = 2, +}; + +enum { + RPC_ADSP_RTOS_CMD_REGISTER_APP, + RPC_ADSP_RTOS_CMD_ENABLE, + RPC_ADSP_RTOS_CMD_DISABLE, + RPC_ADSP_RTOS_CMD_KERNEL_COMMAND, + RPC_ADSP_RTOS_CMD_16_COMMAND, + RPC_ADSP_RTOS_CMD_32_COMMAND, + RPC_ADSP_RTOS_CMD_DISABLE_EVENT_RSP, + RPC_ADSP_RTOS_CMD_REMOTE_EVENT, + RPC_ADSP_RTOS_CMD_SET_STATE, + RPC_ADSP_RTOS_CMD_REMOTE_INIT_INFO_EVENT, + RPC_ADSP_RTOS_CMD_GET_INIT_INFO, +}; + +enum rpc_adsp_rtos_mod_status_type { + RPC_ADSP_RTOS_MOD_READY, + RPC_ADSP_RTOS_MOD_DISABLE, + RPC_ADSP_RTOS_SERVICE_RESET, + RPC_ADSP_RTOS_CMD_FAIL, + RPC_ADSP_RTOS_CMD_SUCCESS, + RPC_ADSP_RTOS_INIT_INFO, + RPC_ADSP_RTOS_DISABLE_FAIL, +}; + +struct rpc_adsp_rtos_app_to_modem_args_t { + struct rpc_request_hdr hdr; + uint32_t gotit; /* if 1, the next elements are present */ + uint32_t cmd; /* e.g., RPC_ADSP_RTOS_CMD_REGISTER_APP */ + uint32_t proc_id; /* e.g., RPC_ADSP_RTOS_PROC_APPS */ + uint32_t module; /* e.g., QDSP_MODULE_AUDPPTASK */ +}; + +enum qdsp_image_type { + QDSP_IMAGE_COMBO, + QDSP_IMAGE_GAUDIO, + QDSP_IMAGE_QTV_LP, + QDSP_IMAGE_MAX, + /* DO NOT USE: Force this enum to be a 32bit type to improve speed */ + QDSP_IMAGE_32BIT_DUMMY = 0x10000 +}; + +struct adsp_rtos_mp_mtoa_header_type { + enum rpc_adsp_rtos_mod_status_type event; + enum rpc_adsp_rtos_proc_type proc_id; +}; + +/* ADSP RTOS MP Communications - Modem to APP's Event Info*/ +struct adsp_rtos_mp_mtoa_type { + uint32_t module; + uint32_t image; + uint32_t apps_okts; +}; + +/* ADSP RTOS MP Communications - Modem to APP's Init Info */ +#if CONFIG_ADSP_RPC_VER > 0x30001 +#define IMG_MAX 2 +#define ENTRIES_MAX 36 +#define MODULES_MAX 64 +#else +#define IMG_MAX 6 +#define ENTRIES_MAX 48 +#endif +#define QUEUES_MAX 64 + +struct queue_to_offset_type { + uint32_t queue; + uint32_t offset; +}; + +struct mod_to_queue_offsets { + uint32_t module; + uint32_t q_type; + uint32_t q_max_len; +}; + +struct adsp_rtos_mp_mtoa_init_info_type { + uint32_t image_count; + uint32_t num_queue_offsets; + struct queue_to_offset_type queue_offsets_tbl[IMG_MAX][ENTRIES_MAX]; + uint32_t num_task_module_entries; + uint32_t task_to_module_tbl[IMG_MAX][ENTRIES_MAX]; + + uint32_t module_table_size; +#if CONFIG_ADSP_RPC_VER > 0x30001 + uint32_t module_entries[MODULES_MAX]; +#else + uint32_t module_entries[ENTRIES_MAX]; +#endif + uint32_t mod_to_q_entries; + struct mod_to_queue_offsets mod_to_q_tbl[ENTRIES_MAX]; + /* + * queue_offsets[] is to store only queue_offsets + */ + uint32_t queue_offsets[IMG_MAX][QUEUES_MAX]; +}; + +struct adsp_rtos_mp_mtoa_s_type { + struct adsp_rtos_mp_mtoa_header_type mp_mtoa_header; +#if CONFIG_ADSP_RPC_VER == 0x30001 + uint32_t desc_field; +#endif + union { + struct adsp_rtos_mp_mtoa_init_info_type mp_mtoa_init_packet; + struct adsp_rtos_mp_mtoa_type mp_mtoa_packet; + } adsp_rtos_mp_mtoa_data; +}; + +struct rpc_adsp_rtos_modem_to_app_args_t { + struct rpc_request_hdr hdr; + uint32_t gotit; /* if 1, the next elements are present */ + struct adsp_rtos_mp_mtoa_s_type mtoa_pkt; +}; + +#define ADSP_STATE_DISABLED 0 +#define ADSP_STATE_ENABLING 1 +#define ADSP_STATE_ENABLED 2 +#define ADSP_STATE_DISABLING 3 +#define ADSP_STATE_INIT_INFO 4 + +struct msm_adsp_module { + struct mutex lock; + const char *name; + unsigned id; + struct adsp_info *info; + + struct msm_rpc_endpoint *rpc_client; + struct msm_adsp_ops *ops; + void *driver_data; + + /* statistics */ + unsigned num_commands; + unsigned num_events; + + wait_queue_head_t state_wait; + unsigned state; + + struct platform_device pdev; + struct clk *clk; + int open_count; + + struct mutex pmem_regions_lock; + struct hlist_head pmem_regions; + int (*verify_cmd) (struct msm_adsp_module*, unsigned int, void *, + size_t); + int (*patch_event) (struct msm_adsp_module*, struct adsp_event *); +}; + +extern void msm_adsp_publish_cdevs(struct msm_adsp_module *, unsigned); +extern int adsp_init_info(struct adsp_info *info); +extern void rmtask_init(void); + +/* Value to indicate that a queue is not defined for a particular image */ +#define QDSP_RTOS_NO_QUEUE 0xfffffffe + +/* + * Constants used to communicate with the ADSP RTOS + */ +#define ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M 0x80000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V 0x80000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_AVAIL_V 0x00000000U + +#define ADSP_RTOS_WRITE_CTRL_WORD_CMD_M 0x70000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_CMD_WRITE_REQ_V 0x00000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_CMD_WRITE_DONE_V 0x10000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_CMD_NO_CMD_V 0x70000000U + +#define ADSP_RTOS_WRITE_CTRL_WORD_STATUS_M 0x0E000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_NO_ERR_V 0x00000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_NO_FREE_BUF_V 0x02000000U + +#define ADSP_RTOS_WRITE_CTRL_WORD_KERNEL_FLG_M 0x01000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_HTOD_MSG_WRITE_V 0x00000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_HTOD_CMD_V 0x01000000U + +#define ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M 0x00FFFFFFU +#define ADSP_RTOS_WRITE_CTRL_WORD_HTOD_CMD_ID_M 0x00FFFFFFU + +/* Combination of MUTEX and CMD bits to check if the DSP is busy */ +#define ADSP_RTOS_WRITE_CTRL_WORD_READY_M 0xF0000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_READY_V 0x70000000U + +/* RTOS to Host processor command mask values */ +#define ADSP_RTOS_READ_CTRL_WORD_FLAG_M 0x80000000U +#define ADSP_RTOS_READ_CTRL_WORD_FLAG_UP_WAIT_V 0x00000000U +#define ADSP_RTOS_READ_CTRL_WORD_FLAG_UP_CONT_V 0x80000000U + +#define ADSP_RTOS_READ_CTRL_WORD_CMD_M 0x60000000U +#define ADSP_RTOS_READ_CTRL_WORD_READ_DONE_V 0x00000000U +#define ADSP_RTOS_READ_CTRL_WORD_READ_REQ_V 0x20000000U +#define ADSP_RTOS_READ_CTRL_WORD_NO_CMD_V 0x60000000U + +/* Combination of FLAG and COMMAND bits to check if MSG ready */ +#define ADSP_RTOS_READ_CTRL_WORD_READY_M 0xE0000000U +#define ADSP_RTOS_READ_CTRL_WORD_READY_V 0xA0000000U +#define ADSP_RTOS_READ_CTRL_WORD_CONT_V 0xC0000000U +#define ADSP_RTOS_READ_CTRL_WORD_DONE_V 0xE0000000U + +#define ADSP_RTOS_READ_CTRL_WORD_STATUS_M 0x18000000U +#define ADSP_RTOS_READ_CTRL_WORD_NO_ERR_V 0x00000000U + +#define ADSP_RTOS_READ_CTRL_WORD_IN_PROG_M 0x04000000U +#define ADSP_RTOS_READ_CTRL_WORD_NO_READ_IN_PROG_V 0x00000000U +#define ADSP_RTOS_READ_CTRL_WORD_READ_IN_PROG_V 0x04000000U + +#define ADSP_RTOS_READ_CTRL_WORD_CMD_TYPE_M 0x03000000U +#define ADSP_RTOS_READ_CTRL_WORD_CMD_TASK_TO_H_V 0x00000000U +#define ADSP_RTOS_READ_CTRL_WORD_CMD_KRNL_TO_H_V 0x01000000U +#define ADSP_RTOS_READ_CTRL_WORD_CMD_H_TO_KRNL_CFM_V 0x02000000U + +#define ADSP_RTOS_READ_CTRL_WORD_DSP_ADDR_M 0x00FFFFFFU + +#define ADSP_RTOS_READ_CTRL_WORD_MSG_ID_M 0x000000FFU +#define ADSP_RTOS_READ_CTRL_WORD_TASK_ID_M 0x0000FF00U + +/* Base address of DSP and DSP hardware registers */ +#define QDSP_RAMC_OFFSET 0x400000 + +#endif diff --git a/arch/arm/mach-msm/qdsp5/adsp_6210.c b/arch/arm/mach-msm/qdsp5/adsp_6210.c new file mode 100644 index 00000000000..322ba68cb36 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_6210.c @@ -0,0 +1,283 @@ +/* arch/arm/mach-msm/qdsp5/adsp_6210.h + * + * Copyright (c) 2008-2009, Code Aurora Forum. 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 "adsp.h" + +/* Firmware modules */ +typedef enum { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_AUDPLAY1TASK, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_VIDEOTASK, + QDSP_MODULE_VIDEO_AAC_VOC, + QDSP_MODULE_PCM_DEC, + QDSP_MODULE_AUDIO_DEC_MP3, + QDSP_MODULE_AUDIO_DEC_AAC, + QDSP_MODULE_AUDIO_DEC_WMA, + QDSP_MODULE_HOSTPCM, + QDSP_MODULE_DTMF, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_SBC_ENC, + QDSP_MODULE_VOC, + QDSP_MODULE_VOC_PCM, + QDSP_MODULE_VOCENCTASK, + QDSP_MODULE_VOCDECTASK, + QDSP_MODULE_VOICEPROCTASK, + QDSP_MODULE_VIDEOENCTASK, + QDSP_MODULE_VFETASK, + QDSP_MODULE_WAV_ENC, + QDSP_MODULE_AACLC_ENC, + QDSP_MODULE_VIDEO_AMR, + QDSP_MODULE_VOC_AMR, + QDSP_MODULE_VOC_EVRC, + QDSP_MODULE_VOC_13K, + QDSP_MODULE_VOC_FGV, + QDSP_MODULE_DIAGTASK, + QDSP_MODULE_JPEGTASK, + QDSP_MODULE_LPMTASK, + QDSP_MODULE_QCAMTASK, + QDSP_MODULE_MODMATHTASK, + QDSP_MODULE_AUDPLAY2TASK, + QDSP_MODULE_AUDPLAY3TASK, + QDSP_MODULE_AUDPLAY4TASK, + QDSP_MODULE_GRAPHICSTASK, + QDSP_MODULE_MIDI, + QDSP_MODULE_GAUDIO, + QDSP_MODULE_VDEC_LP_MODE, + QDSP_MODULE_MAX, +} qdsp_module_type; + +#define QDSP_RTOS_MAX_TASK_ID 19U + +/* Table of modules indexed by task ID for the GAUDIO image */ +static qdsp_module_type qdsp_gaudio_task_to_module_table[] = { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_AUDPLAY1TASK, + QDSP_MODULE_AUDPLAY2TASK, + QDSP_MODULE_AUDPLAY3TASK, + QDSP_MODULE_AUDPLAY4TASK, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_GRAPHICSTASK, + QDSP_MODULE_MAX +}; + +/* Queue offset table indexed by queue ID for the GAUDIO image */ +static uint32_t qdsp_gaudio_queue_offset_table[] = { + QDSP_RTOS_NO_QUEUE, /* QDSP_lpmCommandQueue */ + 0x3be, /* QDSP_mpuAfeQueue */ + 0x3ee, /* QDSP_mpuGraphicsCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuModmathCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVDecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVEncCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_txMpuEncQueue */ + 0x3c2, /* QDSP_uPAudPPCmd1Queue */ + 0x3c6, /* QDSP_uPAudPPCmd2Queue */ + 0x3ca, /* QDSP_uPAudPPCmd3Queue */ + 0x3da, /* QDSP_uPAudPlay0BitStreamCtrlQueue */ + 0x3de, /* QDSP_uPAudPlay1BitStreamCtrlQueue */ + 0x3e2, /* QDSP_uPAudPlay2BitStreamCtrlQueue */ + 0x3e6, /* QDSP_uPAudPlay3BitStreamCtrlQueue */ + 0x3ea, /* QDSP_uPAudPlay4BitStreamCtrlQueue */ + 0x3ce, /* QDSP_uPAudPreProcCmdQueue */ + 0x3d6, /* QDSP_uPAudRecBitStreamQueue */ + 0x3d2, /* QDSP_uPAudRecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegActionCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegCfgCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPVocProcQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandScaleQueue */ + QDSP_RTOS_NO_QUEUE /* QDSP_vfeCommandTableQueue */ +}; + +/* Table of modules indexed by task ID for the COMBO image */ +static qdsp_module_type qdsp_combo_task_to_module_table[] = { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_VOCDECTASK, + QDSP_MODULE_VOCENCTASK, + QDSP_MODULE_VIDEOTASK, + QDSP_MODULE_VIDEOENCTASK, + QDSP_MODULE_VOICEPROCTASK, + QDSP_MODULE_VFETASK, + QDSP_MODULE_JPEGTASK, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_AUDPLAY1TASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_LPMTASK, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_MODMATHTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX +}; + +/* Queue offset table indexed by queue ID for the COMBO image */ +static uint32_t qdsp_combo_queue_offset_table[] = { + 0x585, /* QDSP_lpmCommandQueue */ + 0x52d, /* QDSP_mpuAfeQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuGraphicsCmdQueue */ + 0x541, /* QDSP_mpuModmathCmdQueue */ + 0x555, /* QDSP_mpuVDecCmdQueue */ + 0x559, /* QDSP_mpuVDecPktQueue */ + 0x551, /* QDSP_mpuVEncCmdQueue */ + 0x535, /* QDSP_rxMpuDecCmdQueue */ + 0x539, /* QDSP_rxMpuDecPktQueue */ + 0x53d, /* QDSP_txMpuEncQueue */ + 0x55d, /* QDSP_uPAudPPCmd1Queue */ + 0x561, /* QDSP_uPAudPPCmd2Queue */ + 0x565, /* QDSP_uPAudPPCmd3Queue */ + 0x575, /* QDSP_uPAudPlay0BitStreamCtrlQueue */ + 0x579, /* QDSP_uPAudPlay1BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay2BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay3BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay4BitStreamCtrlQueue */ + 0x569, /* QDSP_uPAudPreProcCmdQueue */ + 0x571, /* QDSP_uPAudRecBitStreamQueue */ + 0x56d, /* QDSP_uPAudRecCmdQueue */ + 0x581, /* QDSP_uPJpegActionCmdQueue */ + 0x57d, /* QDSP_uPJpegCfgCmdQueue */ + 0x531, /* QDSP_uPVocProcQueue */ + 0x545, /* QDSP_vfeCommandQueue */ + 0x54d, /* QDSP_vfeCommandScaleQueue */ + 0x549 /* QDSP_vfeCommandTableQueue */ +}; + +/* Table of modules indexed by task ID for the QTV_LP image */ +static qdsp_module_type qdsp_qtv_lp_task_to_module_table[] = { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_VIDEOTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX +}; + +/* Queue offset table indexed by queue ID for the QTV_LP image */ +static uint32_t qdsp_qtv_lp_queue_offset_table[] = { + QDSP_RTOS_NO_QUEUE, /* QDSP_lpmCommandQueue */ + 0x40c, /* QDSP_mpuAfeQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuGraphicsCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuModmathCmdQueue */ + 0x410, /* QDSP_mpuVDecCmdQueue */ + 0x414, /* QDSP_mpuVDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVEncCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_txMpuEncQueue */ + 0x41c, /* QDSP_uPAudPPCmd1Queue */ + 0x420, /* QDSP_uPAudPPCmd2Queue */ + 0x424, /* QDSP_uPAudPPCmd3Queue */ + 0x430, /* QDSP_uPAudPlay0BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay1BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay2BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay3BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay4BitStreamCtrlQueue */ + 0x418, /* QDSP_uPAudPreProcCmdQueue */ + 0x42c, /* QDSP_uPAudRecBitStreamQueue */ + 0x428, /* QDSP_uPAudRecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegActionCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegCfgCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPVocProcQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandScaleQueue */ + QDSP_RTOS_NO_QUEUE /* QDSP_vfeCommandTableQueue */ +}; + +/* Tables to convert tasks to modules */ +static uint32_t *qdsp_task_to_module[] = { + qdsp_combo_task_to_module_table, + qdsp_gaudio_task_to_module_table, + qdsp_qtv_lp_task_to_module_table, +}; + +/* Tables to retrieve queue offsets */ +static uint32_t *qdsp_queue_offset_table[] = { + qdsp_combo_queue_offset_table, + qdsp_gaudio_queue_offset_table, + qdsp_qtv_lp_queue_offset_table, +}; + +#define QDSP_MODULE(n) \ + { .name = #n, .pdev_name = "adsp_" #n, .id = QDSP_MODULE_##n } + +static struct adsp_module_info module_info[] = { + QDSP_MODULE(AUDPPTASK), + QDSP_MODULE(AUDRECTASK), + QDSP_MODULE(AUDPREPROCTASK), + QDSP_MODULE(VFETASK), + QDSP_MODULE(QCAMTASK), + QDSP_MODULE(LPMTASK), + QDSP_MODULE(JPEGTASK), + QDSP_MODULE(VIDEOTASK), + QDSP_MODULE(VDEC_LP_MODE), +}; + +int adsp_init_info(struct adsp_info *info) +{ + info->send_irq = 0x00c00200; + info->read_ctrl = 0x00400038; + info->write_ctrl = 0x00400034; + + info->max_msg16_size = 193; + info->max_msg32_size = 8; + + info->max_task_id = 16; + info->max_module_id = QDSP_MODULE_MAX - 1; + info->max_queue_id = QDSP_QUEUE_MAX; + info->max_image_id = 2; + info->queue_offset = qdsp_queue_offset_table; + info->task_to_module = qdsp_task_to_module; + + info->module_count = ARRAY_SIZE(module_info); + info->module = module_info; + return 0; +} diff --git a/arch/arm/mach-msm/qdsp5/adsp_6220.c b/arch/arm/mach-msm/qdsp5/adsp_6220.c new file mode 100644 index 00000000000..f947cd7a86d --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_6220.c @@ -0,0 +1,284 @@ +/* arch/arm/mach-msm/qdsp5/adsp_6220.h + * + * Copyright (c) 2008-2009, Code Aurora Forum. 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 "adsp.h" + +/* Firmware modules */ +typedef enum { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_AUDPLAY1TASK, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_VIDEOTASK, + QDSP_MODULE_VIDEO_AAC_VOC, + QDSP_MODULE_PCM_DEC, + QDSP_MODULE_AUDIO_DEC_MP3, + QDSP_MODULE_AUDIO_DEC_AAC, + QDSP_MODULE_AUDIO_DEC_WMA, + QDSP_MODULE_HOSTPCM, + QDSP_MODULE_DTMF, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_SBC_ENC, + QDSP_MODULE_VOC, + QDSP_MODULE_VOC_PCM, + QDSP_MODULE_VOCENCTASK, + QDSP_MODULE_VOCDECTASK, + QDSP_MODULE_VOICEPROCTASK, + QDSP_MODULE_VIDEOENCTASK, + QDSP_MODULE_VFETASK, + QDSP_MODULE_WAV_ENC, + QDSP_MODULE_AACLC_ENC, + QDSP_MODULE_VIDEO_AMR, + QDSP_MODULE_VOC_AMR, + QDSP_MODULE_VOC_EVRC, + QDSP_MODULE_VOC_13K, + QDSP_MODULE_VOC_FGV, + QDSP_MODULE_DIAGTASK, + QDSP_MODULE_JPEGTASK, + QDSP_MODULE_LPMTASK, + QDSP_MODULE_QCAMTASK, + QDSP_MODULE_MODMATHTASK, + QDSP_MODULE_AUDPLAY2TASK, + QDSP_MODULE_AUDPLAY3TASK, + QDSP_MODULE_AUDPLAY4TASK, + QDSP_MODULE_GRAPHICSTASK, + QDSP_MODULE_MIDI, + QDSP_MODULE_GAUDIO, + QDSP_MODULE_VDEC_LP_MODE, + QDSP_MODULE_MAX, +} qdsp_module_type; + +#define QDSP_RTOS_MAX_TASK_ID 19U + +/* Table of modules indexed by task ID for the GAUDIO image */ +static qdsp_module_type qdsp_gaudio_task_to_module_table[] = { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_AUDPLAY1TASK, + QDSP_MODULE_AUDPLAY2TASK, + QDSP_MODULE_AUDPLAY3TASK, + QDSP_MODULE_AUDPLAY4TASK, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_GRAPHICSTASK, + QDSP_MODULE_MAX +}; + +/* Queue offset table indexed by queue ID for the GAUDIO image */ +static uint32_t qdsp_gaudio_queue_offset_table[] = { + QDSP_RTOS_NO_QUEUE, /* QDSP_lpmCommandQueue */ + 0x3f0, /* QDSP_mpuAfeQueue */ + 0x420, /* QDSP_mpuGraphicsCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuModmathCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVDecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVEncCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_txMpuEncQueue */ + 0x3f4, /* QDSP_uPAudPPCmd1Queue */ + 0x3f8, /* QDSP_uPAudPPCmd2Queue */ + 0x3fc, /* QDSP_uPAudPPCmd3Queue */ + 0x40c, /* QDSP_uPAudPlay0BitStreamCtrlQueue */ + 0x410, /* QDSP_uPAudPlay1BitStreamCtrlQueue */ + 0x414, /* QDSP_uPAudPlay2BitStreamCtrlQueue */ + 0x418, /* QDSP_uPAudPlay3BitStreamCtrlQueue */ + 0x41c, /* QDSP_uPAudPlay4BitStreamCtrlQueue */ + 0x400, /* QDSP_uPAudPreProcCmdQueue */ + 0x408, /* QDSP_uPAudRecBitStreamQueue */ + 0x404, /* QDSP_uPAudRecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegActionCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegCfgCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPVocProcQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandScaleQueue */ + QDSP_RTOS_NO_QUEUE /* QDSP_vfeCommandTableQueue */ +}; + +/* Table of modules indexed by task ID for the COMBO image */ +static qdsp_module_type qdsp_combo_task_to_module_table[] = { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_VOCDECTASK, + QDSP_MODULE_VOCENCTASK, + QDSP_MODULE_VIDEOTASK, + QDSP_MODULE_VIDEOENCTASK, + QDSP_MODULE_VOICEPROCTASK, + QDSP_MODULE_VFETASK, + QDSP_MODULE_JPEGTASK, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_AUDPLAY1TASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_LPMTASK, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_MODMATHTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX +}; + +/* Queue offset table indexed by queue ID for the COMBO image */ +static uint32_t qdsp_combo_queue_offset_table[] = { + 0x6f2, /* QDSP_lpmCommandQueue */ + 0x69e, /* QDSP_mpuAfeQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuGraphicsCmdQueue */ + 0x6b2, /* QDSP_mpuModmathCmdQueue */ + 0x6c6, /* QDSP_mpuVDecCmdQueue */ + 0x6ca, /* QDSP_mpuVDecPktQueue */ + 0x6c2, /* QDSP_mpuVEncCmdQueue */ + 0x6a6, /* QDSP_rxMpuDecCmdQueue */ + 0x6aa, /* QDSP_rxMpuDecPktQueue */ + 0x6ae, /* QDSP_txMpuEncQueue */ + 0x6ce, /* QDSP_uPAudPPCmd1Queue */ + 0x6d2, /* QDSP_uPAudPPCmd2Queue */ + 0x6d6, /* QDSP_uPAudPPCmd3Queue */ + 0x6e6, /* QDSP_uPAudPlay0BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay1BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay2BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay3BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay4BitStreamCtrlQueue */ + 0x6da, /* QDSP_uPAudPreProcCmdQueue */ + 0x6e2, /* QDSP_uPAudRecBitStreamQueue */ + 0x6de, /* QDSP_uPAudRecCmdQueue */ + 0x6ee, /* QDSP_uPJpegActionCmdQueue */ + 0x6ea, /* QDSP_uPJpegCfgCmdQueue */ + 0x6a2, /* QDSP_uPVocProcQueue */ + 0x6b6, /* QDSP_vfeCommandQueue */ + 0x6be, /* QDSP_vfeCommandScaleQueue */ + 0x6ba /* QDSP_vfeCommandTableQueue */ +}; + +/* Table of modules indexed by task ID for the QTV_LP image */ +static qdsp_module_type qdsp_qtv_lp_task_to_module_table[] = { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_VIDEOTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX +}; + +/* Queue offset table indexed by queue ID for the QTV_LP image */ +static uint32_t qdsp_qtv_lp_queue_offset_table[] = { + QDSP_RTOS_NO_QUEUE, /* QDSP_lpmCommandQueue */ + 0x430, /* QDSP_mpuAfeQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuGraphicsCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuModmathCmdQueue */ + 0x434, /* QDSP_mpuVDecCmdQueue */ + 0x438, /* QDSP_mpuVDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVEncCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_txMpuEncQueue */ + 0x440, /* QDSP_uPAudPPCmd1Queue */ + 0x444, /* QDSP_uPAudPPCmd2Queue */ + 0x448, /* QDSP_uPAudPPCmd3Queue */ + 0x454, /* QDSP_uPAudPlay0BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay1BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay2BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay3BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay4BitStreamCtrlQueue */ + 0x43c, /* QDSP_uPAudPreProcCmdQueue */ + 0x450, /* QDSP_uPAudRecBitStreamQueue */ + 0x44c, /* QDSP_uPAudRecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegActionCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegCfgCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPVocProcQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandScaleQueue */ + QDSP_RTOS_NO_QUEUE /* QDSP_vfeCommandTableQueue */ +}; + +/* Tables to convert tasks to modules */ +static qdsp_module_type *qdsp_task_to_module[] = { + qdsp_combo_task_to_module_table, + qdsp_gaudio_task_to_module_table, + qdsp_qtv_lp_task_to_module_table, +}; + +/* Tables to retrieve queue offsets */ +static uint32_t *qdsp_queue_offset_table[] = { + qdsp_combo_queue_offset_table, + qdsp_gaudio_queue_offset_table, + qdsp_qtv_lp_queue_offset_table, +}; + +#define QDSP_MODULE(n) \ + { .name = #n, .pdev_name = "adsp_" #n, .id = QDSP_MODULE_##n } + +static struct adsp_module_info module_info[] = { + QDSP_MODULE(AUDPLAY0TASK), + QDSP_MODULE(AUDPPTASK), + QDSP_MODULE(AUDPREPROCTASK), + QDSP_MODULE(AUDRECTASK), + QDSP_MODULE(VFETASK), + QDSP_MODULE(QCAMTASK), + QDSP_MODULE(LPMTASK), + QDSP_MODULE(JPEGTASK), + QDSP_MODULE(VIDEOTASK), + QDSP_MODULE(VDEC_LP_MODE), +}; + +int adsp_init_info(struct adsp_info *info) +{ + info->send_irq = 0x00c00200; + info->read_ctrl = 0x00400038; + info->write_ctrl = 0x00400034; + + info->max_msg16_size = 193; + info->max_msg32_size = 8; + + info->max_task_id = 16; + info->max_module_id = QDSP_MODULE_MAX - 1; + info->max_queue_id = QDSP_QUEUE_MAX; + info->max_image_id = 2; + info->queue_offset = qdsp_queue_offset_table; + info->task_to_module = qdsp_task_to_module; + + info->module_count = ARRAY_SIZE(module_info); + info->module = module_info; + return 0; +} diff --git a/arch/arm/mach-msm/qdsp5/adsp_6225.c b/arch/arm/mach-msm/qdsp5/adsp_6225.c new file mode 100644 index 00000000000..6f8d3f43bf2 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_6225.c @@ -0,0 +1,328 @@ +/* arch/arm/mach-msm/qdsp5/adsp_6225.h + * + * Copyright (c) 2008-2009, Code Aurora Forum. 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 "adsp.h" + +/* Firmware modules */ +typedef enum { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_AUDPLAY1TASK, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_VIDEOTASK, + QDSP_MODULE_VIDEO_AAC_VOC, + QDSP_MODULE_PCM_DEC, + QDSP_MODULE_AUDIO_DEC_MP3, + QDSP_MODULE_AUDIO_DEC_AAC, + QDSP_MODULE_AUDIO_DEC_WMA, + QDSP_MODULE_HOSTPCM, + QDSP_MODULE_DTMF, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_SBC_ENC, + QDSP_MODULE_VOC_UMTS, + QDSP_MODULE_VOC_CDMA, + QDSP_MODULE_VOC_PCM, + QDSP_MODULE_VOCENCTASK, + QDSP_MODULE_VOCDECTASK, + QDSP_MODULE_VOICEPROCTASK, + QDSP_MODULE_VIDEOENCTASK, + QDSP_MODULE_VFETASK, + QDSP_MODULE_WAV_ENC, + QDSP_MODULE_AACLC_ENC, + QDSP_MODULE_VIDEO_AMR, + QDSP_MODULE_VOC_AMR, + QDSP_MODULE_VOC_EVRC, + QDSP_MODULE_VOC_13K, + QDSP_MODULE_VOC_FGV, + QDSP_MODULE_DIAGTASK, + QDSP_MODULE_JPEGTASK, + QDSP_MODULE_LPMTASK, + QDSP_MODULE_QCAMTASK, + QDSP_MODULE_MODMATHTASK, + QDSP_MODULE_AUDPLAY2TASK, + QDSP_MODULE_AUDPLAY3TASK, + QDSP_MODULE_AUDPLAY4TASK, + QDSP_MODULE_GRAPHICSTASK, + QDSP_MODULE_MIDI, + QDSP_MODULE_GAUDIO, + QDSP_MODULE_VDEC_LP_MODE, + QDSP_MODULE_MAX, +} qdsp_module_type; + +#define QDSP_RTOS_MAX_TASK_ID 30U + +/* Table of modules indexed by task ID for the GAUDIO image */ +static qdsp_module_type qdsp_gaudio_task_to_module_table[] = { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_AUDPLAY1TASK, + QDSP_MODULE_AUDPLAY2TASK, + QDSP_MODULE_AUDPLAY3TASK, + QDSP_MODULE_AUDPLAY4TASK, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_GRAPHICSTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, +}; + +/* Queue offset table indexed by queue ID for the GAUDIO image */ +static uint32_t qdsp_gaudio_queue_offset_table[] = { + QDSP_RTOS_NO_QUEUE, /* QDSP_lpmCommandQueue */ + 0x3f0, /* QDSP_mpuAfeQueue */ + 0x420, /* QDSP_mpuGraphicsCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuModmathCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVDecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVEncCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_txMpuEncQueue */ + 0x3f4, /* QDSP_uPAudPPCmd1Queue */ + 0x3f8, /* QDSP_uPAudPPCmd2Queue */ + 0x3fc, /* QDSP_uPAudPPCmd3Queue */ + 0x40c, /* QDSP_uPAudPlay0BitStreamCtrlQueue */ + 0x410, /* QDSP_uPAudPlay1BitStreamCtrlQueue */ + 0x414, /* QDSP_uPAudPlay2BitStreamCtrlQueue */ + 0x418, /* QDSP_uPAudPlay3BitStreamCtrlQueue */ + 0x41c, /* QDSP_uPAudPlay4BitStreamCtrlQueue */ + 0x400, /* QDSP_uPAudPreProcCmdQueue */ + 0x408, /* QDSP_uPAudRecBitStreamQueue */ + 0x404, /* QDSP_uPAudRecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegActionCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegCfgCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPVocProcQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandScaleQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandTableQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPDiagQueue */ +}; + +/* Table of modules indexed by task ID for the COMBO image */ +static qdsp_module_type qdsp_combo_task_to_module_table[] = { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_VOCDECTASK, + QDSP_MODULE_VOCENCTASK, + QDSP_MODULE_VIDEOTASK, + QDSP_MODULE_VIDEOENCTASK, + QDSP_MODULE_VOICEPROCTASK, + QDSP_MODULE_VFETASK, + QDSP_MODULE_JPEGTASK, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_AUDPLAY1TASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_LPMTASK, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_MODMATHTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_DIAGTASK, + QDSP_MODULE_MAX, +}; + +/* Queue offset table indexed by queue ID for the COMBO image */ +static uint32_t qdsp_combo_queue_offset_table[] = { + 0x714, /* QDSP_lpmCommandQueue */ + 0x6bc, /* QDSP_mpuAfeQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuGraphicsCmdQueue */ + 0x6d0, /* QDSP_mpuModmathCmdQueue */ + 0x6e8, /* QDSP_mpuVDecCmdQueue */ + 0x6ec, /* QDSP_mpuVDecPktQueue */ + 0x6e4, /* QDSP_mpuVEncCmdQueue */ + 0x6c4, /* QDSP_rxMpuDecCmdQueue */ + 0x6c8, /* QDSP_rxMpuDecPktQueue */ + 0x6cc, /* QDSP_txMpuEncQueue */ + 0x6f0, /* QDSP_uPAudPPCmd1Queue */ + 0x6f4, /* QDSP_uPAudPPCmd2Queue */ + 0x6f8, /* QDSP_uPAudPPCmd3Queue */ + 0x708, /* QDSP_uPAudPlay0BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay1BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay2BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay3BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay4BitStreamCtrlQueue */ + 0x6fc, /* QDSP_uPAudPreProcCmdQueue */ + 0x704, /* QDSP_uPAudRecBitStreamQueue */ + 0x700, /* QDSP_uPAudRecCmdQueue */ + 0x710, /* QDSP_uPJpegActionCmdQueue */ + 0x70c, /* QDSP_uPJpegCfgCmdQueue */ + 0x6c0, /* QDSP_uPVocProcQueue */ + 0x6d8, /* QDSP_vfeCommandQueue */ + 0x6e0, /* QDSP_vfeCommandScaleQueue */ + 0x6dc, /* QDSP_vfeCommandTableQueue */ + 0x6d4, /* QDSP_uPDiagQueue */ +}; + +/* Table of modules indexed by task ID for the QTV_LP image */ +static qdsp_module_type qdsp_qtv_lp_task_to_module_table[] = { + QDSP_MODULE_KERNEL, + QDSP_MODULE_AFETASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_VIDEOTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDPPTASK, + QDSP_MODULE_AUDPLAY0TASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_AUDRECTASK, + QDSP_MODULE_AUDPREPROCTASK, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, + QDSP_MODULE_MAX, +}; + +/* Queue offset table indexed by queue ID for the QTV_LP image */ +static uint32_t qdsp_qtv_lp_queue_offset_table[] = { + QDSP_RTOS_NO_QUEUE, /* QDSP_lpmCommandQueue */ + 0x3fe, /* QDSP_mpuAfeQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuGraphicsCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuModmathCmdQueue */ + 0x402, /* QDSP_mpuVDecCmdQueue */ + 0x406, /* QDSP_mpuVDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_mpuVEncCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_rxMpuDecPktQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_txMpuEncQueue */ + 0x40e, /* QDSP_uPAudPPCmd1Queue */ + 0x412, /* QDSP_uPAudPPCmd2Queue */ + 0x416, /* QDSP_uPAudPPCmd3Queue */ + 0x422, /* QDSP_uPAudPlay0BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay1BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay2BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay3BitStreamCtrlQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPAudPlay4BitStreamCtrlQueue */ + 0x40a, /* QDSP_uPAudPreProcCmdQueue */ + 0x41e, /* QDSP_uPAudRecBitStreamQueue */ + 0x41a, /* QDSP_uPAudRecCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegActionCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPJpegCfgCmdQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPVocProcQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandScaleQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_vfeCommandTableQueue */ + QDSP_RTOS_NO_QUEUE, /* QDSP_uPDiagQueue */ +}; + +/* Tables to convert tasks to modules */ +static qdsp_module_type *qdsp_task_to_module[] = { + qdsp_combo_task_to_module_table, + qdsp_gaudio_task_to_module_table, + qdsp_qtv_lp_task_to_module_table, +}; + +/* Tables to retrieve queue offsets */ +static uint32_t *qdsp_queue_offset_table[] = { + qdsp_combo_queue_offset_table, + qdsp_gaudio_queue_offset_table, + qdsp_qtv_lp_queue_offset_table, +}; + +#define QDSP_MODULE(n, clkname, clkrate, verify_cmd_func, patch_event_func) \ + { .name = #n, .pdev_name = "adsp_" #n, .id = QDSP_MODULE_##n, \ + .clk_name = clkname, .clk_rate = clkrate, \ + .verify_cmd = verify_cmd_func, .patch_event = patch_event_func } + +static struct adsp_module_info module_info[] = { + QDSP_MODULE(AUDPLAY0TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPPTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDRECTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPREPROCTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(VFETASK, "vfe_clk", 0, adsp_vfe_verify_cmd, + adsp_vfe_patch_event), + QDSP_MODULE(QCAMTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(LPMTASK, NULL, 0, adsp_lpm_verify_cmd, NULL), + QDSP_MODULE(JPEGTASK, "vdc_clk", 0, adsp_jpeg_verify_cmd, + adsp_jpeg_patch_event), + QDSP_MODULE(VIDEOTASK, "vdc_clk", 96000000, + adsp_video_verify_cmd, NULL), + QDSP_MODULE(VDEC_LP_MODE, NULL, 0, NULL, NULL), + QDSP_MODULE(VIDEOENCTASK, "vdc_clk", 96000000, + adsp_videoenc_verify_cmd, NULL), +}; + +int adsp_init_info(struct adsp_info *info) +{ + info->send_irq = 0x00c00200; + info->read_ctrl = 0x00400038; + info->write_ctrl = 0x00400034; + + info->max_msg16_size = 193; + info->max_msg32_size = 8; + + info->max_task_id = 16; + info->max_module_id = QDSP_MODULE_MAX - 1; + info->max_queue_id = QDSP_QUEUE_MAX; + info->max_image_id = 2; + info->queue_offset = qdsp_queue_offset_table; + info->task_to_module = qdsp_task_to_module; + + info->module_count = ARRAY_SIZE(module_info); + info->module = module_info; + return 0; +} diff --git a/arch/arm/mach-msm/qdsp5/adsp_driver.c b/arch/arm/mach-msm/qdsp5/adsp_driver.c new file mode 100644 index 00000000000..1cb1c9b3109 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_driver.c @@ -0,0 +1,667 @@ +/* arch/arm/mach-msm/qdsp5/adsp_driver.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Author: Iliyan Malchev + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "adsp.h" + +#include +#include +#include + +struct adsp_pmem_info { + int fd; + void *vaddr; +}; + +struct adsp_pmem_region { + struct hlist_node list; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + struct file *file; +}; + +struct adsp_device { + struct msm_adsp_module *module; + + spinlock_t event_queue_lock; + wait_queue_head_t event_wait; + struct list_head event_queue; + int abort; + + const char *name; + struct device *device; + struct cdev cdev; +}; + +static struct adsp_device *inode_to_device(struct inode *inode); + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = __v >= __r->vaddr && \ + __e <= __r->vaddr + __r->len; \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +static int adsp_pmem_check(struct msm_adsp_module *module, + void *vaddr, unsigned long len) +{ + struct adsp_pmem_region *region_elt; + struct hlist_node *node; + struct adsp_pmem_region t = { .vaddr = vaddr, .len = len }; + + hlist_for_each_entry(region_elt, node, &module->pmem_regions, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + MM_ERR("module %s:" + " region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + module->name, + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, + region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int adsp_pmem_add(struct msm_adsp_module *module, + struct adsp_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct adsp_pmem_region *region; + int rc = -EINVAL; + + mutex_lock(&module->pmem_regions_lock); + region = kmalloc(sizeof(*region), GFP_KERNEL); + if (!region) { + rc = -ENOMEM; + goto end; + } + INIT_HLIST_NODE(®ion->list); + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = adsp_pmem_check(module, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + + hlist_add_head(®ion->list, &module->pmem_regions); +end: + mutex_unlock(&module->pmem_regions_lock); + return rc; +} + +static int adsp_pmem_lookup_vaddr(struct msm_adsp_module *module, void **addr, + unsigned long len, struct adsp_pmem_region **region) +{ + struct hlist_node *node; + void *vaddr = *addr; + struct adsp_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + hlist_for_each_entry(region_elt, node, &module->pmem_regions, list) { + if (vaddr >= region_elt->vaddr && + vaddr < region_elt->vaddr + region_elt->len && + vaddr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + MM_ERR("module %s: " + "multiple hits for vaddr %p, len %ld\n", + module->name, vaddr, len); + hlist_for_each_entry(region_elt, node, + &module->pmem_regions, list) { + if (vaddr >= region_elt->vaddr && + vaddr < region_elt->vaddr + region_elt->len && + vaddr + len <= region_elt->vaddr + region_elt->len) + MM_ERR("%p, %ld --> %p\n", + region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +int adsp_pmem_fixup_kvaddr(struct msm_adsp_module *module, void **addr, + unsigned long *kvaddr, unsigned long len, + struct file **filp, unsigned long *offset) +{ + struct adsp_pmem_region *region; + void *vaddr = *addr; + unsigned long *paddr = (unsigned long *)addr; + int ret; + + ret = adsp_pmem_lookup_vaddr(module, addr, len, ®ion); + if (ret) { + MM_ERR("not patching %s (paddr & kvaddr)," + " lookup (%p, %ld) failed\n", + module->name, vaddr, len); + return ret; + } + *paddr = region->paddr + (vaddr - region->vaddr); + *kvaddr = region->kvaddr + (vaddr - region->vaddr); + if (filp) + *filp = region->file; + if (offset) + *offset = vaddr - region->vaddr; + return 0; +} + +int adsp_pmem_fixup(struct msm_adsp_module *module, void **addr, + unsigned long len) +{ + struct adsp_pmem_region *region; + void *vaddr = *addr; + unsigned long *paddr = (unsigned long *)addr; + int ret; + + ret = adsp_pmem_lookup_vaddr(module, addr, len, ®ion); + if (ret) { + MM_ERR("not patching %s, lookup (%p, %ld) failed\n", + module->name, vaddr, len); + return ret; + } + + *paddr = region->paddr + (vaddr - region->vaddr); + return 0; +} + +static int adsp_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size) +{ + /* call the per module verifier */ + if (module->verify_cmd) + return module->verify_cmd(module, queue_id, cmd_data, + cmd_size); + else + MM_INFO("no packet verifying function " + "for task %s\n", module->name); + return 0; +} + +static long adsp_write_cmd(struct adsp_device *adev, void __user *arg) +{ + struct adsp_command_t cmd; + unsigned char buf[256]; + void *cmd_data; + long rc; + + if (copy_from_user(&cmd, (void __user *)arg, sizeof(cmd))) + return -EFAULT; + + if (cmd.len > 256) { + cmd_data = kmalloc(cmd.len, GFP_USER); + if (!cmd_data) + return -ENOMEM; + } else { + cmd_data = buf; + } + + if (copy_from_user(cmd_data, (void __user *)(cmd.data), cmd.len)) { + rc = -EFAULT; + goto end; + } + + mutex_lock(&adev->module->pmem_regions_lock); + if (adsp_verify_cmd(adev->module, cmd.queue, cmd_data, cmd.len)) { + MM_ERR("module %s: verify failed.\n", adev->module->name); + rc = -EINVAL; + goto end; + } + /* complete the writes to the buffer */ + wmb(); + rc = msm_adsp_write(adev->module, cmd.queue, cmd_data, cmd.len); +end: + mutex_unlock(&adev->module->pmem_regions_lock); + + if (cmd.len > 256) + kfree(cmd_data); + + return rc; +} + +static int adsp_events_pending(struct adsp_device *adev) +{ + unsigned long flags; + int yes; + spin_lock_irqsave(&adev->event_queue_lock, flags); + yes = !list_empty(&adev->event_queue); + spin_unlock_irqrestore(&adev->event_queue_lock, flags); + return yes || adev->abort; +} + +static int adsp_pmem_lookup_paddr(struct msm_adsp_module *module, void **addr, + struct adsp_pmem_region **region) +{ + struct hlist_node *node; + unsigned long paddr = (unsigned long)(*addr); + struct adsp_pmem_region *region_elt; + + hlist_for_each_entry(region_elt, node, &module->pmem_regions, list) { + if (paddr >= region_elt->paddr && + paddr < region_elt->paddr + region_elt->len) { + *region = region_elt; + return 0; + } + } + return -1; +} + +int adsp_pmem_paddr_fixup(struct msm_adsp_module *module, void **addr) +{ + struct adsp_pmem_region *region; + unsigned long paddr = (unsigned long)(*addr); + unsigned long *vaddr = (unsigned long *)addr; + int ret; + + ret = adsp_pmem_lookup_paddr(module, addr, ®ion); + if (ret) { + MM_ERR("not patching %s, paddr %p lookup failed\n", + module->name, vaddr); + return ret; + } + + *vaddr = (unsigned long)region->vaddr + (paddr - region->paddr); + return 0; +} + +static int adsp_patch_event(struct msm_adsp_module *module, + struct adsp_event *event) +{ + /* call the per-module msg verifier */ + if (module->patch_event) + return module->patch_event(module, event); + return 0; +} + +static long adsp_get_event(struct adsp_device *adev, void __user *arg) +{ + unsigned long flags; + struct adsp_event *data = NULL; + struct adsp_event_t evt; + int timeout; + long rc = 0; + + if (copy_from_user(&evt, arg, sizeof(struct adsp_event_t))) + return -EFAULT; + + timeout = (int)evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + adev->event_wait, adsp_events_pending(adev), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + adev->event_wait, adsp_events_pending(adev)); + } + if (rc < 0) + return rc; + + if (adev->abort) + return -ENODEV; + + spin_lock_irqsave(&adev->event_queue_lock, flags); + if (!list_empty(&adev->event_queue)) { + data = list_first_entry(&adev->event_queue, + struct adsp_event, list); + list_del(&data->list); + } + spin_unlock_irqrestore(&adev->event_queue_lock, flags); + + if (!data) + return -EAGAIN; + + /* DSP messages are type 0; they may contain physical addresses */ + if (data->type == 0) + adsp_patch_event(adev->module, data); + + /* map adsp_event --> adsp_event_t */ + if (evt.len < data->size) { + rc = -ETOOSMALL; + goto end; + } + /* order the reads to the buffer */ + rmb(); + if (data->msg_id != EVENT_MSG_ID) { + if (copy_to_user((void *)(evt.data), data->data.msg16, + data->size)) { + rc = -EFAULT; + goto end; + } + } else { + if (copy_to_user((void *)(evt.data), data->data.msg32, + data->size)) { + rc = -EFAULT; + goto end; + } + } + + evt.type = data->type; /* 0 --> from aDSP, 1 --> from ARM9 */ + evt.msg_id = data->msg_id; + evt.flags = data->is16; + evt.len = data->size; + if (copy_to_user(arg, &evt, sizeof(evt))) + rc = -EFAULT; +end: + kfree(data); + return rc; +} + +static int adsp_pmem_del(struct msm_adsp_module *module) +{ + struct hlist_node *node, *tmp; + struct adsp_pmem_region *region; + + mutex_lock(&module->pmem_regions_lock); + hlist_for_each_safe(node, tmp, &module->pmem_regions) { + region = hlist_entry(node, struct adsp_pmem_region, list); + hlist_del(node); + put_pmem_file(region->file); + kfree(region); + } + mutex_unlock(&module->pmem_regions_lock); + BUG_ON(!hlist_empty(&module->pmem_regions)); + + return 0; +} + +static long adsp_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct adsp_device *adev = filp->private_data; + + switch (cmd) { + case ADSP_IOCTL_ENABLE: + return msm_adsp_enable(adev->module); + + case ADSP_IOCTL_DISABLE: + return msm_adsp_disable(adev->module); + + case ADSP_IOCTL_DISABLE_EVENT_RSP: + return msm_adsp_disable_event_rsp(adev->module); + + case ADSP_IOCTL_DISABLE_ACK: + MM_ERR("ADSP_IOCTL_DISABLE_ACK is not implemented\n"); + break; + + case ADSP_IOCTL_WRITE_COMMAND: + return adsp_write_cmd(adev, (void __user *) arg); + + case ADSP_IOCTL_GET_EVENT: + return adsp_get_event(adev, (void __user *) arg); + + case ADSP_IOCTL_SET_CLKRATE: { + unsigned long clk_rate; + if (copy_from_user(&clk_rate, (void *) arg, sizeof(clk_rate))) + return -EFAULT; + return adsp_set_clkrate(adev->module, clk_rate); + } + + case ADSP_IOCTL_REGISTER_PMEM: { + struct adsp_pmem_info info; + if (copy_from_user(&info, (void *) arg, sizeof(info))) + return -EFAULT; + return adsp_pmem_add(adev->module, &info); + } + + case ADSP_IOCTL_ABORT_EVENT_READ: + adev->abort = 1; + wake_up(&adev->event_wait); + break; + + case ADSP_IOCTL_UNREGISTER_PMEM: + return adsp_pmem_del(adev->module); + + default: + break; + } + return -EINVAL; +} + +static int adsp_release(struct inode *inode, struct file *filp) +{ + struct adsp_device *adev = filp->private_data; + struct msm_adsp_module *module = adev->module; + int rc = 0; + + MM_INFO("release '%s'\n", adev->name); + + /* clear module before putting it to avoid race with open() */ + adev->module = NULL; + + rc = adsp_pmem_del(module); + + msm_adsp_put(module); + return rc; +} + +static void adsp_event(void *driver_data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct adsp_device *adev = driver_data; + struct adsp_event *event; + unsigned long flags; + + if (len > ADSP_EVENT_MAX_SIZE) { + MM_ERR("event too large (%d bytes)\n", len); + return; + } + + event = kmalloc(sizeof(*event), GFP_ATOMIC); + if (!event) { + MM_ERR("cannot allocate buffer\n"); + return; + } + + if (id != EVENT_MSG_ID) { + event->type = 0; + event->is16 = 0; + event->msg_id = id; + event->size = len; + + getevent(event->data.msg16, len); + } else { + event->type = 1; + event->is16 = 1; + event->msg_id = id; + event->size = len; + getevent(event->data.msg32, len); + } + + spin_lock_irqsave(&adev->event_queue_lock, flags); + list_add_tail(&event->list, &adev->event_queue); + spin_unlock_irqrestore(&adev->event_queue_lock, flags); + wake_up(&adev->event_wait); +} + +static struct msm_adsp_ops adsp_ops = { + .event = adsp_event, +}; + +static int adsp_open(struct inode *inode, struct file *filp) +{ + struct adsp_device *adev; + int rc; + + rc = nonseekable_open(inode, filp); + if (rc < 0) + return rc; + + adev = inode_to_device(inode); + if (!adev) + return -ENODEV; + + MM_INFO("open '%s'\n", adev->name); + + rc = msm_adsp_get(adev->name, &adev->module, &adsp_ops, adev); + if (rc) + return rc; + + MM_INFO("opened module '%s' adev %p\n", adev->name, adev); + filp->private_data = adev; + adev->abort = 0; + INIT_HLIST_HEAD(&adev->module->pmem_regions); + mutex_init(&adev->module->pmem_regions_lock); + + return 0; +} + +static unsigned adsp_device_count; +static struct adsp_device *adsp_devices; + +static struct adsp_device *inode_to_device(struct inode *inode) +{ + unsigned n = MINOR(inode->i_rdev); + if (n < adsp_device_count) { + if (adsp_devices[n].device) + return adsp_devices + n; + } + return NULL; +} + +static dev_t adsp_devno; +static struct class *adsp_class; + +static struct file_operations adsp_fops = { + .owner = THIS_MODULE, + .open = adsp_open, + .unlocked_ioctl = adsp_ioctl, + .release = adsp_release, +}; + +static void adsp_create(struct adsp_device *adev, const char *name, + struct device *parent, dev_t devt) +{ + struct device *dev; + int rc; + + dev = device_create(adsp_class, parent, devt, "%s", name); + if (IS_ERR(dev)) + return; + + init_waitqueue_head(&adev->event_wait); + INIT_LIST_HEAD(&adev->event_queue); + spin_lock_init(&adev->event_queue_lock); + + cdev_init(&adev->cdev, &adsp_fops); + adev->cdev.owner = THIS_MODULE; + + rc = cdev_add(&adev->cdev, devt, 1); + if (rc < 0) { + device_destroy(adsp_class, devt); + } else { + adev->device = dev; + adev->name = name; + } +} + +void msm_adsp_publish_cdevs(struct msm_adsp_module *modules, unsigned n) +{ + int rc; + + adsp_devices = kzalloc(sizeof(struct adsp_device) * n, GFP_KERNEL); + if (!adsp_devices) + return; + + adsp_class = class_create(THIS_MODULE, "adsp"); + if (IS_ERR(adsp_class)) + goto fail_create_class; + + rc = alloc_chrdev_region(&adsp_devno, 0, n, "adsp"); + if (rc < 0) + goto fail_alloc_region; + + adsp_device_count = n; + for (n = 0; n < adsp_device_count; n++) { + adsp_create(adsp_devices + n, + modules[n].name, &modules[n].pdev.dev, + MKDEV(MAJOR(adsp_devno), n)); + } + + return; + +fail_alloc_region: + class_unregister(adsp_class); +fail_create_class: + kfree(adsp_devices); +} diff --git a/arch/arm/mach-msm/qdsp5/adsp_info.c b/arch/arm/mach-msm/qdsp5/adsp_info.c new file mode 100644 index 00000000000..62e385dd6e0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_info.c @@ -0,0 +1,144 @@ +/* arch/arm/mach-msm/adsp_info.c + * + * Copyright (c) 2008-2009, 2011 Code Aurora Forum. 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 "adsp.h" + +/* Firmware modules */ +#define QDSP_MODULE_KERNEL 0x0106dd4e +#define QDSP_MODULE_AFETASK 0x0106dd6f +#define QDSP_MODULE_AUDPLAY0TASK 0x0106dd70 +#define QDSP_MODULE_AUDPLAY1TASK 0x0106dd71 +#define QDSP_MODULE_AUDPPTASK 0x0106dd72 +#define QDSP_MODULE_VIDEOTASK 0x0106dd73 +#define QDSP_MODULE_VIDEO_AAC_VOC 0x0106dd74 +#define QDSP_MODULE_PCM_DEC 0x0106dd75 +#define QDSP_MODULE_AUDIO_DEC_MP3 0x0106dd76 +#define QDSP_MODULE_AUDIO_DEC_AAC 0x0106dd77 +#define QDSP_MODULE_AUDIO_DEC_WMA 0x0106dd78 +#define QDSP_MODULE_HOSTPCM 0x0106dd79 +#define QDSP_MODULE_DTMF 0x0106dd7a +#define QDSP_MODULE_AUDRECTASK 0x0106dd7b +#define QDSP_MODULE_AUDPREPROCTASK 0x0106dd7c +#define QDSP_MODULE_SBC_ENC 0x0106dd7d +#define QDSP_MODULE_VOC_UMTS 0x0106dd9a +#define QDSP_MODULE_VOC_CDMA 0x0106dd98 +#define QDSP_MODULE_VOC_PCM 0x0106dd7f +#define QDSP_MODULE_VOCENCTASK 0x0106dd80 +#define QDSP_MODULE_VOCDECTASK 0x0106dd81 +#define QDSP_MODULE_VOICEPROCTASK 0x0106dd82 +#define QDSP_MODULE_VIDEOENCTASK 0x0106dd83 +#define QDSP_MODULE_VFETASK 0x0106dd84 +#define QDSP_MODULE_WAV_ENC 0x0106dd85 +#define QDSP_MODULE_AACLC_ENC 0x0106dd86 +#define QDSP_MODULE_VIDEO_AMR 0x0106dd87 +#define QDSP_MODULE_VOC_AMR 0x0106dd88 +#define QDSP_MODULE_VOC_EVRC 0x0106dd89 +#define QDSP_MODULE_VOC_13K 0x0106dd8a +#define QDSP_MODULE_VOC_FGV 0x0106dd8b +#define QDSP_MODULE_DIAGTASK 0x0106dd8c +#define QDSP_MODULE_JPEGTASK 0x0106dd8d +#define QDSP_MODULE_LPMTASK 0x0106dd8e +#define QDSP_MODULE_QCAMTASK 0x0106dd8f +#define QDSP_MODULE_MODMATHTASK 0x0106dd90 +#define QDSP_MODULE_AUDPLAY2TASK 0x0106dd91 +#define QDSP_MODULE_AUDPLAY3TASK 0x0106dd92 +#define QDSP_MODULE_AUDPLAY4TASK 0x0106dd93 +#define QDSP_MODULE_GRAPHICSTASK 0x0106dd94 +#define QDSP_MODULE_MIDI 0x0106dd95 +#define QDSP_MODULE_GAUDIO 0x0106dd96 +#define QDSP_MODULE_VDEC_LP_MODE 0x0106dd97 +#define QDSP_MODULE_VIDEO_AAC_VOC_TURBO 0x01089f77 +#define QDSP_MODULE_VIDEO_AMR_TURBO 0x01089f78 +#define QDSP_MODULE_WM_TURBO_MODE 0x01089f79 +#define QDSP_MODULE_VDEC_LP_MODE_TURBO 0x01089f7a +#define QDSP_MODULE_AUDREC0TASK 0x0109696f +#define QDSP_MODULE_AUDREC1TASK 0x01096970 +#define QDSP_MODULE_RMTASK 0x01090f8e +#define QDSP_MODULE_MAX 0x7fffffff + + /* DO NOT USE: Force this enum to be a 32bit type to improve speed */ +#define QDSP_MODULE_32BIT_DUMMY 0x10000 + +static uint32_t *qdsp_task_to_module[IMG_MAX]; +static uint32_t *qdsp_queue_offset_table[IMG_MAX]; + +#define QDSP_MODULE(n, clkname, clkrate, verify_cmd_func, patch_event_func) \ + { .name = #n, .pdev_name = "adsp_" #n, .id = QDSP_MODULE_##n, \ + .clk_name = clkname, .clk_rate = clkrate, \ + .verify_cmd = verify_cmd_func, .patch_event = patch_event_func } + +static struct adsp_module_info module_info[] = { + QDSP_MODULE(AUDPLAY0TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPLAY1TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPLAY2TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPLAY3TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPPTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPREPROCTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(RMTASK, NULL, 0, NULL, NULL), +#if !defined(CONFIG_ARCH_MSM7X30) + QDSP_MODULE(AUDRECTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(VFETASK, "vfe_clk", 0, adsp_vfe_verify_cmd, + adsp_vfe_patch_event), + QDSP_MODULE(QCAMTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(LPMTASK, NULL, 0, adsp_lpm_verify_cmd, NULL), + QDSP_MODULE(JPEGTASK, "vdc_clk", 96000000, adsp_jpeg_verify_cmd, + adsp_jpeg_patch_event), + QDSP_MODULE(VIDEOTASK, "vdc_clk", 96000000, + adsp_video_verify_cmd, NULL), + QDSP_MODULE(VDEC_LP_MODE, NULL, 0, NULL, NULL), + QDSP_MODULE(VIDEOENCTASK, "vdc_clk", 96000000, + adsp_videoenc_verify_cmd, NULL), + QDSP_MODULE(VIDEO_AAC_VOC_TURBO, NULL, 0, NULL, NULL), + QDSP_MODULE(VIDEO_AMR_TURBO, NULL, 0, NULL, NULL), + QDSP_MODULE(WM_TURBO_MODE, NULL, 0, NULL, NULL), + QDSP_MODULE(VDEC_LP_MODE_TURBO, NULL, 0, NULL, NULL), +#if defined(CONFIG_MSM7X27A_AUDIO) + QDSP_MODULE(AUDREC1TASK, NULL, 0, NULL, NULL), +#endif +#else + QDSP_MODULE(AFETASK , NULL, 0, NULL, NULL), + QDSP_MODULE(AUDREC0TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDREC1TASK, NULL, 0, NULL, NULL), +#endif +}; + +int adsp_init_info(struct adsp_info *info) +{ + uint32_t img_num; + + info->send_irq = 0x00c00200; + info->read_ctrl = 0x00400038; + info->write_ctrl = 0x00400034; + + info->max_msg16_size = 193; + info->max_msg32_size = 8; + for (img_num = 0; img_num < IMG_MAX; img_num++) + qdsp_queue_offset_table[img_num] = + &info->init_info_ptr->queue_offsets[img_num][0]; + + for (img_num = 0; img_num < IMG_MAX; img_num++) + qdsp_task_to_module[img_num] = + &info->init_info_ptr->task_to_module_tbl[img_num][0]; + info->max_task_id = 30; + info->max_module_id = QDSP_MODULE_MAX - 1; + info->max_queue_id = QDSP_MAX_NUM_QUEUES; + info->max_image_id = 2; + info->queue_offset = qdsp_queue_offset_table; + info->task_to_module = qdsp_task_to_module; + + info->module_count = ARRAY_SIZE(module_info); + info->module = module_info; + return 0; +} diff --git a/arch/arm/mach-msm/qdsp5/adsp_jpeg_patch_event.c b/arch/arm/mach-msm/qdsp5/adsp_jpeg_patch_event.c new file mode 100644 index 00000000000..8fb2e06c79c --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_jpeg_patch_event.c @@ -0,0 +1,39 @@ +/* arch/arm/mach-msm/qdsp5/adsp_jpeg_patch_event.c + * + * Verification code for aDSP JPEG events. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. 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 +#include "adsp.h" + +int adsp_jpeg_patch_event(struct msm_adsp_module *module, + struct adsp_event *event) +{ + if (event->msg_id == JPEG_MSG_ENC_OP_PRODUCED) { + jpeg_msg_enc_op_produced *op = (jpeg_msg_enc_op_produced *)event->data.msg16; + return adsp_pmem_paddr_fixup(module, (void **)&op->op_buf_addr); + } + if (event->msg_id == JPEG_MSG_DEC_OP_PRODUCED) { + jpeg_msg_dec_op_produced *op = (jpeg_msg_dec_op_produced *) + event->data.msg16; + return adsp_pmem_paddr_fixup(module, + (void **)&op->luma_op_buf_addr) || + adsp_pmem_paddr_fixup(module, + (void **)&op->chroma_op_buf_addr); + } + + return 0; +} diff --git a/arch/arm/mach-msm/qdsp5/adsp_jpeg_verify_cmd.c b/arch/arm/mach-msm/qdsp5/adsp_jpeg_verify_cmd.c new file mode 100644 index 00000000000..87d5dc389df --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_jpeg_verify_cmd.c @@ -0,0 +1,201 @@ +/* arch/arm/mach-msm/qdsp5/adsp_jpeg_verify_cmd.c + * + * Verification code for aDSP JPEG packets from userspace. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. 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 +#include "adsp.h" +#include + +static uint32_t dec_fmt; + +static inline void get_sizes(jpeg_cmd_enc_cfg *cmd, uint32_t *luma_size, + uint32_t *chroma_size) +{ + uint32_t fmt, luma_width, luma_height; + + fmt = cmd->process_cfg & JPEG_CMD_ENC_PROCESS_CFG_IP_DATA_FORMAT_M; + luma_width = (cmd->ip_size_cfg & JPEG_CMD_IP_SIZE_CFG_LUMA_WIDTH_M) + >> 16; + luma_height = cmd->frag_cfg & JPEG_CMD_FRAG_SIZE_LUMA_HEIGHT_M; + *luma_size = luma_width * luma_height; + if (fmt == JPEG_CMD_ENC_PROCESS_CFG_IP_DATA_FORMAT_H2V2) + *chroma_size = *luma_size/2; + else + *chroma_size = *luma_size; +} + +static inline int verify_jpeg_cmd_enc_cfg(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + jpeg_cmd_enc_cfg *cmd = (jpeg_cmd_enc_cfg *)cmd_data; + uint32_t luma_size, chroma_size; + int i, num_frags; + + if (cmd_size != sizeof(jpeg_cmd_enc_cfg)) { + MM_ERR("module %s: JPEG ENC CFG invalid \ + cmd_size %d\n", module->name, cmd_size); + return -1; + } + + get_sizes(cmd, &luma_size, &chroma_size); + num_frags = (cmd->process_cfg >> 10) & 0xf; + num_frags = ((num_frags == 1) ? num_frags : num_frags * 2); + for (i = 0; i < num_frags; i += 2) { + if (adsp_pmem_fixup(module, (void **)(&cmd->frag_cfg_part[i]), luma_size) || + adsp_pmem_fixup(module, (void **)(&cmd->frag_cfg_part[i+1]), chroma_size)) + return -1; + } + + if (adsp_pmem_fixup(module, (void **)&cmd->op_buf_0_cfg_part1, + cmd->op_buf_0_cfg_part2) || + adsp_pmem_fixup(module, (void **)&cmd->op_buf_1_cfg_part1, + cmd->op_buf_1_cfg_part2)) + return -1; + return 0; +} + +static inline int verify_jpeg_cmd_dec_cfg(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + jpeg_cmd_dec_cfg *cmd = (jpeg_cmd_dec_cfg *)cmd_data; + uint32_t div; + + if (cmd_size != sizeof(jpeg_cmd_dec_cfg)) { + MM_ERR("module %s: JPEG DEC CFG invalid \ + cmd_size %d\n", module->name, cmd_size); + return -1; + } + + if (adsp_pmem_fixup(module, (void **)&cmd->ip_stream_buf_cfg_part1, + cmd->ip_stream_buf_cfg_part2) || + adsp_pmem_fixup(module, (void **)&cmd->op_stream_buf_0_cfg_part1, + cmd->op_stream_buf_0_cfg_part2) || + adsp_pmem_fixup(module, (void **)&cmd->op_stream_buf_1_cfg_part1, + cmd->op_stream_buf_1_cfg_part2)) + return -1; + dec_fmt = cmd->op_data_format & + JPEG_CMD_DEC_OP_DATA_FORMAT_M; + div = (dec_fmt == JPEG_CMD_DEC_OP_DATA_FORMAT_H2V2) ? 2 : 1; + if (adsp_pmem_fixup(module, (void **)&cmd->op_stream_buf_0_cfg_part3, + cmd->op_stream_buf_0_cfg_part2 / div) || + adsp_pmem_fixup(module, (void **)&cmd->op_stream_buf_1_cfg_part3, + cmd->op_stream_buf_1_cfg_part2 / div)) + return -1; + return 0; +} + +static int verify_jpeg_cfg_cmd(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + uint32_t cmd_id = ((uint32_t *)cmd_data)[0]; + switch(cmd_id) { + case JPEG_CMD_ENC_CFG: + return verify_jpeg_cmd_enc_cfg(module, cmd_data, cmd_size); + case JPEG_CMD_DEC_CFG: + return verify_jpeg_cmd_dec_cfg(module, cmd_data, cmd_size); + default: + if (cmd_id > 1) { + MM_ERR("module %s: invalid JPEG CFG cmd_id %d\n", + module->name, cmd_id); + return -1; + } + } + return 0; +} + +static int verify_jpeg_action_cmd(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + uint32_t cmd_id = ((uint32_t *)cmd_data)[0]; + switch (cmd_id) { + case JPEG_CMD_ENC_OP_CONSUMED: + { + jpeg_cmd_enc_op_consumed *cmd = + (jpeg_cmd_enc_op_consumed *)cmd_data; + + if (cmd_size != sizeof(jpeg_cmd_enc_op_consumed)) { + MM_ERR("module %s: JPEG_CMD_ENC_OP_CONSUMED \ + invalid size %d\n", module->name, cmd_size); + return -1; + } + + if (adsp_pmem_fixup(module, (void **)&cmd->op_buf_addr, + cmd->op_buf_size)) + return -1; + } + break; + case JPEG_CMD_DEC_OP_CONSUMED: + { + uint32_t div; + jpeg_cmd_dec_op_consumed *cmd = + (jpeg_cmd_dec_op_consumed *)cmd_data; + + if (cmd_size != sizeof(jpeg_cmd_dec_op_consumed)) { + MM_ERR("module %s: JPEG_CMD_DEC_OP_CONSUMED \ + invalid size %d\n", module->name, cmd_size); + return -1; + } + + div = (dec_fmt == JPEG_CMD_DEC_OP_DATA_FORMAT_H2V2) ? 2 : 1; + if (adsp_pmem_fixup(module, (void **)&cmd->luma_op_buf_addr, + cmd->luma_op_buf_size) || + adsp_pmem_fixup(module, (void **)&cmd->chroma_op_buf_addr, + cmd->luma_op_buf_size / div)) + return -1; + } + break; + + case JPEG_CMD_DEC_IP: + { + jpeg_cmd_dec_ip *cmd = + (jpeg_cmd_dec_ip *)cmd_data; + + if (cmd_size != sizeof(jpeg_cmd_dec_ip)) { + MM_ERR("module %s: JPEG_CMD_DEC_IP invalid \ + size %d\n", module->name, cmd_size); + return -1; + } + if (adsp_pmem_fixup(module, (void **)&cmd->ip_buf_addr, + cmd->ip_buf_size)) + return -1; + } + break; + + default: + if (cmd_id > 7) { + MM_ERR("module %s: invalid cmd_id %d\n", + module->name, cmd_id); + return -1; + } + } + return 0; +} + +int adsp_jpeg_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size) +{ + switch(queue_id) { + case QDSP_uPJpegCfgCmdQueue: + return verify_jpeg_cfg_cmd(module, cmd_data, cmd_size); + case QDSP_uPJpegActionCmdQueue: + return verify_jpeg_action_cmd(module, cmd_data, cmd_size); + default: + return -1; + } +} + diff --git a/arch/arm/mach-msm/qdsp5/adsp_lpm_verify_cmd.c b/arch/arm/mach-msm/qdsp5/adsp_lpm_verify_cmd.c new file mode 100644 index 00000000000..06b70de38ed --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_lpm_verify_cmd.c @@ -0,0 +1,66 @@ +/* arch/arm/mach-msm/qdsp5/adsp_lpm_verify_cmd.c + * + * Verificion code for aDSP LPM packets from userspace. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. 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 +#include "adsp.h" +#include + +int adsp_lpm_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size) +{ + uint32_t cmd_id, col_height, input_row_incr, output_row_incr, + input_size, output_size; + uint32_t size_mask = 0x0fff; + lpm_cmd_start *cmd; + + if (queue_id != QDSP_lpmCommandQueue) { + MM_ERR("module %s: wrong queue id %d\n", + module->name, queue_id); + return -1; + } + + cmd = (lpm_cmd_start *)cmd_data; + cmd_id = cmd->cmd_id; + + if (cmd_id == LPM_CMD_START) { + if (cmd_size != sizeof(lpm_cmd_start)) { + MM_ERR("module %s: wrong size %d, \ + expect %d\n", module->name, + cmd_size, sizeof(lpm_cmd_start)); + return -1; + } + col_height = cmd->ip_data_cfg_part1 & size_mask; + input_row_incr = cmd->ip_data_cfg_part2 & size_mask; + output_row_incr = cmd->op_data_cfg_part1 & size_mask; + input_size = col_height * input_row_incr; + output_size = col_height * output_row_incr; + if ((cmd->ip_data_cfg_part4 && adsp_pmem_fixup(module, + (void **)(&cmd->ip_data_cfg_part4), + input_size)) || + (cmd->op_data_cfg_part3 && adsp_pmem_fixup(module, + (void **)(&cmd->op_data_cfg_part3), + output_size))) + return -1; + } else if (cmd_id > 1) { + MM_ERR("module %s: invalid cmd_id %d\n", module->name, cmd_id); + return -1; + } + return 0; +} + diff --git a/arch/arm/mach-msm/qdsp5/adsp_rm.c b/arch/arm/mach-msm/qdsp5/adsp_rm.c new file mode 100644 index 00000000000..678dd81572b --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_rm.c @@ -0,0 +1,192 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "adsp.h" + +#define MAX_CLIENTS 5 +#define MAX_AUDIO_CLIENTS 5 +#define MAX_RM_CLIENTS MAX_AUDIO_CLIENTS + +static char *rm_errs[] = { + "", + "PCM Blocks not Sufficient", + "TASK is already occupied", + "Concurrency not supported", + "MIPS not sufficient" + }; +static struct client { + wait_queue_head_t wait; + unsigned int wait_state; + struct aud_codec_config_ack cfg_msg; +} rmclient[MAX_RM_CLIENTS]; + +static struct rm { + struct msm_adsp_module *mod; + int cnt; + int state; + + struct aud_codec_config_ack cfg_msg; + struct mutex lock; +} rmtask; + +static void rm_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)); +static struct msm_adsp_ops rm_ops = { + .event = rm_dsp_event, +}; + +int32_t get_adsp_resource(unsigned short client_id, + void *cmd_buf, size_t cmd_size) +{ + int rc = 0; + int client_idx; + + client_idx = ((client_id >> 8) * MAX_CLIENTS) + (client_id & 0xFF); + if (client_idx >= MAX_RM_CLIENTS) + return -EINVAL; + + mutex_lock(&rmtask.lock); + if (rmtask.state != ADSP_STATE_ENABLED) { + rc = msm_adsp_get("RMTASK", &rmtask.mod, &rm_ops, NULL); + if (rc) { + MM_ERR("Failed to get module RMTASK\n"); + mutex_unlock(&rmtask.lock); + return rc; + } + rc = msm_adsp_enable(rmtask.mod); + if (rc) { + MM_ERR("RMTASK enable Failed\n"); + msm_adsp_put(rmtask.mod); + mutex_unlock(&rmtask.lock); + return rc; + } + rmtask.state = ADSP_STATE_ENABLED; + } + rmclient[client_idx].wait_state = -1; + mutex_unlock(&rmtask.lock); + msm_adsp_write(rmtask.mod, QDSP_apuRmtQueue, cmd_buf, cmd_size); + rc = wait_event_interruptible_timeout(rmclient[client_idx].wait, + rmclient[client_idx].wait_state != -1, 5 * HZ); + mutex_lock(&rmtask.lock); + if (unlikely(rc < 0)) { + if (rc == -ERESTARTSYS) + MM_ERR("wait_event_interruptible " + "returned -ERESTARTSYS\n"); + else + MM_ERR("wait_event_interruptible " + "returned error\n"); + if (!rmtask.cnt) + goto disable_rm; + goto unlock; + } else if (rc == 0) { + MM_ERR("RMTASK Msg not received\n"); + rc = -ETIMEDOUT; + if (!rmtask.cnt) + goto disable_rm; + goto unlock; + } + if (!(rmclient[client_idx].cfg_msg.enable)) { + MM_ERR("Reason for failure: %s\n", + rm_errs[rmclient[client_idx].cfg_msg.reason]); + rc = -EBUSY; + if (!rmtask.cnt) + goto disable_rm; + goto unlock; + } + rmtask.cnt++; + mutex_unlock(&rmtask.lock); + return 0; + +disable_rm: + msm_adsp_disable(rmtask.mod); + msm_adsp_put(rmtask.mod); + rmtask.state = ADSP_STATE_DISABLED; +unlock: + mutex_unlock(&rmtask.lock); + return rc; +} +EXPORT_SYMBOL(get_adsp_resource); + +int32_t put_adsp_resource(unsigned short client_id, void *cmd_buf, + size_t cmd_size) +{ + mutex_lock(&rmtask.lock); + if (rmtask.state != ADSP_STATE_ENABLED) { + mutex_unlock(&rmtask.lock); + return 0; + } + + msm_adsp_write(rmtask.mod, QDSP_apuRmtQueue, cmd_buf, cmd_size); + rmtask.cnt--; + if (!rmtask.cnt) { + msm_adsp_disable(rmtask.mod); + msm_adsp_put(rmtask.mod); + rmtask.state = ADSP_STATE_DISABLED; + } + mutex_unlock(&rmtask.lock); + return 0; +} +EXPORT_SYMBOL(put_adsp_resource); + +static void rm_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + unsigned short client_id; + int client_idx; + + MM_DBG("Msg ID = %d\n", id); + + switch (id) { + case RMT_CODEC_CONFIG_ACK: { + getevent(&rmtask.cfg_msg, sizeof(rmtask.cfg_msg)); + client_id = ((rmtask.cfg_msg.client_id << 8) | + rmtask.cfg_msg.task_id); + client_idx = ((client_id >> 8) * MAX_CLIENTS) + + (client_id & 0xFF); + memcpy(&rmclient[client_idx].cfg_msg, &rmtask.cfg_msg, + sizeof(rmtask.cfg_msg)); + rmclient[client_idx].wait_state = 1; + wake_up(&rmclient[client_idx].wait); + break; + } + case RMT_DSP_OUT_OF_MIPS: { + struct rmt_dsp_out_of_mips msg; + getevent(&msg, sizeof(msg)); + MM_ERR("RMT_DSP_OUT_OF_MIPS: Not enough resorces in ADSP \ + to handle all sessions :%hx\n", msg.dec_info); + break; + } + default: + MM_DBG("Unknown Msg Id\n"); + break; + } +} + +void rmtask_init(void) +{ + int i; + + for (i = 0; i < MAX_RM_CLIENTS; i++) + init_waitqueue_head(&rmclient[i].wait); + mutex_init(&rmtask.lock); +} diff --git a/arch/arm/mach-msm/qdsp5/adsp_vfe_patch_event.c b/arch/arm/mach-msm/qdsp5/adsp_vfe_patch_event.c new file mode 100644 index 00000000000..68ae3802ecd --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_vfe_patch_event.c @@ -0,0 +1,54 @@ +/* arch/arm/mach-msm/qdsp5/adsp_vfe_patch_event.c + * + * Verification code for aDSP VFE packets from userspace. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. 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 +#include "adsp.h" + +static int patch_op_event(struct msm_adsp_module *module, + struct adsp_event *event) +{ + vfe_msg_op1 *op = (vfe_msg_op1 *)event->data.msg16; + if (adsp_pmem_paddr_fixup(module, (void **)&op->op1_buf_y_addr) || + adsp_pmem_paddr_fixup(module, (void **)&op->op1_buf_cbcr_addr)) + return -1; + return 0; +} + +static int patch_af_wb_event(struct msm_adsp_module *module, + struct adsp_event *event) +{ + vfe_msg_stats_wb_exp *af = (vfe_msg_stats_wb_exp *)event->data.msg16; + return adsp_pmem_paddr_fixup(module, (void **)&af->wb_exp_stats_op_buf); +} + +int adsp_vfe_patch_event(struct msm_adsp_module *module, + struct adsp_event *event) +{ + switch(event->msg_id) { + case VFE_MSG_OP1: + case VFE_MSG_OP2: + return patch_op_event(module, event); + case VFE_MSG_STATS_AF: + case VFE_MSG_STATS_WB_EXP: + return patch_af_wb_event(module, event); + default: + break; + } + + return 0; +} diff --git a/arch/arm/mach-msm/qdsp5/adsp_vfe_verify_cmd.c b/arch/arm/mach-msm/qdsp5/adsp_vfe_verify_cmd.c new file mode 100644 index 00000000000..dcd3d968af8 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_vfe_verify_cmd.c @@ -0,0 +1,244 @@ +/* arch/arm/mach-msm/qdsp5/adsp_vfe_verify_cmd.c + * + * Verification code for aDSP VFE packets from userspace. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. 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 +#include "adsp.h" +#include + +static uint32_t size1_y, size2_y, size1_cbcr, size2_cbcr; +static uint32_t af_size = 4228; +static uint32_t awb_size = 8196; + +static inline int verify_cmd_op_ack(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + vfe_cmd_op1_ack *cmd = (vfe_cmd_op1_ack *)cmd_data; + void **addr_y = (void **)&cmd->op1_buf_y_addr; + void **addr_cbcr = (void **)(&cmd->op1_buf_cbcr_addr); + + if (cmd_size != sizeof(vfe_cmd_op1_ack)) + return -1; + if ((*addr_y && adsp_pmem_fixup(module, addr_y, size1_y)) || + (*addr_cbcr && adsp_pmem_fixup(module, addr_cbcr, size1_cbcr))) + return -1; + return 0; +} + +static inline int verify_cmd_stats_autofocus_cfg(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + int i; + vfe_cmd_stats_autofocus_cfg *cmd = + (vfe_cmd_stats_autofocus_cfg *)cmd_data; + + if (cmd_size != sizeof(vfe_cmd_stats_autofocus_cfg)) + return -1; + + for (i = 0; i < 3; i++) { + void **addr = (void **)(&cmd->af_stats_op_buf[i]); + if (*addr && adsp_pmem_fixup(module, addr, af_size)) + return -1; + } + return 0; +} + +static inline int verify_cmd_stats_wb_exp_cfg(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + vfe_cmd_stats_wb_exp_cfg *cmd = + (vfe_cmd_stats_wb_exp_cfg *)cmd_data; + int i; + + if (cmd_size != sizeof(vfe_cmd_stats_wb_exp_cfg)) + return -1; + + for (i = 0; i < 3; i++) { + void **addr = (void **)(&cmd->wb_exp_stats_op_buf[i]); + if (*addr && adsp_pmem_fixup(module, addr, awb_size)) + return -1; + } + return 0; +} + +static inline int verify_cmd_stats_af_ack(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + vfe_cmd_stats_af_ack *cmd = (vfe_cmd_stats_af_ack *)cmd_data; + void **addr = (void **)&cmd->af_stats_op_buf; + + if (cmd_size != sizeof(vfe_cmd_stats_af_ack)) + return -1; + + if (*addr && adsp_pmem_fixup(module, addr, af_size)) + return -1; + return 0; +} + +static inline int verify_cmd_stats_wb_exp_ack(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + vfe_cmd_stats_wb_exp_ack *cmd = + (vfe_cmd_stats_wb_exp_ack *)cmd_data; + void **addr = (void **)&cmd->wb_exp_stats_op_buf; + + if (cmd_size != sizeof(vfe_cmd_stats_wb_exp_ack)) + return -1; + + if (*addr && adsp_pmem_fixup(module, addr, awb_size)) + return -1; + return 0; +} + +static int verify_vfe_command(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + uint32_t cmd_id = ((uint32_t *)cmd_data)[0]; + switch (cmd_id) { + case VFE_CMD_OP1_ACK: + return verify_cmd_op_ack(module, cmd_data, cmd_size); + case VFE_CMD_OP2_ACK: + return verify_cmd_op_ack(module, cmd_data, cmd_size); + case VFE_CMD_STATS_AUTOFOCUS_CFG: + return verify_cmd_stats_autofocus_cfg(module, cmd_data, + cmd_size); + case VFE_CMD_STATS_WB_EXP_CFG: + return verify_cmd_stats_wb_exp_cfg(module, cmd_data, cmd_size); + case VFE_CMD_STATS_AF_ACK: + return verify_cmd_stats_af_ack(module, cmd_data, cmd_size); + case VFE_CMD_STATS_WB_EXP_ACK: + return verify_cmd_stats_wb_exp_ack(module, cmd_data, cmd_size); + default: + if (cmd_id > 29) { + MM_ERR("module %s: invalid VFE command id %d\n", + module->name, cmd_id); + return -1; + } + } + return 0; +} + +static int verify_vfe_command_scale(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + uint32_t cmd_id = ((uint32_t *)cmd_data)[0]; + // FIXME: check the size + if (cmd_id > 1) { + MM_ERR("module %s: invalid VFE SCALE command id %d\n", + module->name, cmd_id); + return -1; + } + return 0; +} + + +static uint32_t get_size(uint32_t hw) +{ + uint32_t height, width; + uint32_t height_mask = 0x3ffc; + uint32_t width_mask = 0x3ffc000; + + height = (hw & height_mask) >> 2; + width = (hw & width_mask) >> 14 ; + return height * width; +} + +static int verify_vfe_command_table(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + uint32_t cmd_id = ((uint32_t *)cmd_data)[0]; + int i; + + switch (cmd_id) { + case VFE_CMD_AXI_IP_CFG: + { + vfe_cmd_axi_ip_cfg *cmd = (vfe_cmd_axi_ip_cfg *)cmd_data; + uint32_t size; + if (cmd_size != sizeof(vfe_cmd_axi_ip_cfg)) { + MM_ERR("module %s: invalid VFE TABLE \ + (VFE_CMD_AXI_IP_CFG) command size %d\n", + module->name, cmd_size); + return -1; + } + size = get_size(cmd->ip_cfg_part2); + + for (i = 0; i < 8; i++) { + void **addr = (void **) + &cmd->ip_buf_addr[i]; + if (*addr && adsp_pmem_fixup(module, addr, size)) + return -1; + } + } + case VFE_CMD_AXI_OP_CFG: + { + vfe_cmd_axi_op_cfg *cmd = (vfe_cmd_axi_op_cfg *)cmd_data; + void **addr1_y, **addr2_y, **addr1_cbcr, **addr2_cbcr; + + if (cmd_size != sizeof(vfe_cmd_axi_op_cfg)) { + MM_ERR("module %s: invalid VFE TABLE \ + (VFE_CMD_AXI_OP_CFG) command size %d\n", + module->name, cmd_size); + return -1; + } + size1_y = get_size(cmd->op1_y_cfg_part2); + size1_cbcr = get_size(cmd->op1_cbcr_cfg_part2); + size2_y = get_size(cmd->op2_y_cfg_part2); + size2_cbcr = get_size(cmd->op2_cbcr_cfg_part2); + for (i = 0; i < 8; i++) { + addr1_y = (void **)(&cmd->op1_buf1_addr[2*i]); + addr1_cbcr = (void **)(&cmd->op1_buf1_addr[2*i+1]); + addr2_y = (void **)(&cmd->op2_buf1_addr[2*i]); + addr2_cbcr = (void **)(&cmd->op2_buf1_addr[2*i+1]); +/* + printk("module %s: [%d] %p %p %p %p\n", + module->name, i, + *addr1_y, *addr1_cbcr, *addr2_y, *addr2_cbcr); +*/ + if ((*addr1_y && adsp_pmem_fixup(module, addr1_y, size1_y)) || + (*addr1_cbcr && adsp_pmem_fixup(module, addr1_cbcr, size1_cbcr)) || + (*addr2_y && adsp_pmem_fixup(module, addr2_y, size2_y)) || + (*addr2_cbcr && adsp_pmem_fixup(module, addr2_cbcr, size2_cbcr))) + return -1; + } + } + default: + if (cmd_id > 4) { + MM_ERR("module %s: invalid VFE TABLE command \ + id %d\n", module->name, cmd_id); + return -1; + } + } + return 0; +} + +int adsp_vfe_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size) +{ + switch (queue_id) { + case QDSP_vfeCommandQueue: + return verify_vfe_command(module, cmd_data, cmd_size); + case QDSP_vfeCommandScaleQueue: + return verify_vfe_command_scale(module, cmd_data, cmd_size); + case QDSP_vfeCommandTableQueue: + return verify_vfe_command_table(module, cmd_data, cmd_size); + default: + MM_ERR("module %s: unknown queue id %d\n", + module->name, queue_id); + return -1; + } +} diff --git a/arch/arm/mach-msm/qdsp5/adsp_video_verify_cmd.c b/arch/arm/mach-msm/qdsp5/adsp_video_verify_cmd.c new file mode 100644 index 00000000000..f884a29d69a --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_video_verify_cmd.c @@ -0,0 +1,233 @@ +/* arch/arm/mach-msm/qdsp5/adsp_video_verify_cmd.c + * + * Verificion code for aDSP VDEC packets from userspace. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2010, Code Aurora Forum. 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 +#include + +#include +#include "adsp.h" +#include + +static inline void *high_low_short_to_ptr(unsigned short high, + unsigned short low) +{ + return (void *)((((unsigned long)high) << 16) | ((unsigned long)low)); +} + +static inline void ptr_to_high_low_short(void *ptr, unsigned short *high, + unsigned short *low) +{ + *high = (unsigned short)((((unsigned long)ptr) >> 16) & 0xffff); + *low = (unsigned short)((unsigned long)ptr & 0xffff); +} + +static int pmem_fixup_high_low(unsigned short *high, + unsigned short *low, + unsigned short size_high, + unsigned short size_low, + struct msm_adsp_module *module, + unsigned long *addr, unsigned long *size, + struct file **filp, unsigned long *offset) +{ + void *phys_addr; + unsigned long phys_size; + unsigned long kvaddr; + + phys_addr = high_low_short_to_ptr(*high, *low); + phys_size = (unsigned long)high_low_short_to_ptr(size_high, size_low); + MM_DBG("virt %x %x\n", (unsigned int)phys_addr, + (unsigned int)phys_size); + if (phys_addr) { + if (adsp_pmem_fixup_kvaddr(module, &phys_addr, + &kvaddr, phys_size, filp, offset)) { + MM_ERR("ah%x al%x sh%x sl%x addr %x size %x\n", + *high, *low, size_high, + size_low, (unsigned int)phys_addr, + (unsigned int)phys_size); + return -EINVAL; + } + } + ptr_to_high_low_short(phys_addr, high, low); + MM_DBG("phys %x %x\n", (unsigned int)phys_addr, + (unsigned int)phys_size); + if (addr) + *addr = kvaddr; + if (size) + *size = phys_size; + return 0; +} + +static int verify_vdec_pkt_cmd(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + unsigned short cmd_id = ((unsigned short *)cmd_data)[0]; + viddec_cmd_subframe_pkt *pkt; + unsigned long subframe_pkt_addr; + unsigned long subframe_pkt_size; + unsigned short *frame_header_pkt; + int i, num_addr, skip, start_pos = 0, xdim_pos = 1, ydim_pos = 2; + unsigned short *frame_buffer_high, *frame_buffer_low; + unsigned long frame_buffer_size; + unsigned short frame_buffer_size_high, frame_buffer_size_low; + struct file *filp = NULL; + unsigned long offset = 0; + struct pmem_addr pmem_addr; + unsigned long Codec_Id = 0; + + MM_DBG("cmd_size %d cmd_id %d cmd_data %x\n", cmd_size, cmd_id, + (unsigned int)cmd_data); + if (cmd_id != VIDDEC_CMD_SUBFRAME_PKT) { + MM_INFO("adsp_video: unknown video packet %u\n", cmd_id); + return 0; + } + if (cmd_size < sizeof(viddec_cmd_subframe_pkt)) + return -1; + + pkt = (viddec_cmd_subframe_pkt *)cmd_data; + + if (pmem_fixup_high_low(&(pkt->subframe_packet_high), + &(pkt->subframe_packet_low), + pkt->subframe_packet_size_high, + pkt->subframe_packet_size_low, + module, + &subframe_pkt_addr, + &subframe_pkt_size, + &filp, &offset)) + return -1; + Codec_Id = pkt->codec_selection_word; + + /* deref those ptrs and check if they are a frame header packet */ + frame_header_pkt = (unsigned short *)subframe_pkt_addr; + switch (frame_header_pkt[0]) { + case 0xB201: /* h.264 vld in dsp */ + if (Codec_Id == 0x8) { + num_addr = 16; + skip = 0; + start_pos = 5; + } else { + num_addr = 33; + skip = 0; + start_pos = 6; + } + break; + case 0x8201: /* h.264 vld in arm */ + num_addr = 16; + skip = 0; + start_pos = 6; + break; + case 0x4D01: /* mpeg-4 and h.263 vld in arm */ + num_addr = 3; + skip = 0; + start_pos = 5; + break; + case 0x9201: /*For Real Decoder*/ + num_addr = 2; + skip = 0; + start_pos = 5; + break; + case 0xBD01: /* mpeg-4 and h.263 vld in dsp */ + num_addr = 3; + skip = 0; + start_pos = 6; + if (((frame_header_pkt[5] & 0x000c) >> 2) == 0x2) /* B-frame */ + start_pos = 8; + break; + case 0x0001: /* wmv */ + num_addr = 2; + skip = 0; + start_pos = 5; + break; + case 0xC201: /*WMV main profile*/ + num_addr = 3; + skip = 0; + start_pos = 6; + break; + case 0xDD01: /* VP6 */ + num_addr = 3; + skip = 0; + start_pos = 10; + break; + case 0xFD01: /* VP8 */ + num_addr = 3; + skip = 0; + start_pos = 24; + break; + default: + return 0; + } + + frame_buffer_high = &frame_header_pkt[start_pos]; + frame_buffer_low = &frame_header_pkt[start_pos + 1]; + frame_buffer_size = (frame_header_pkt[xdim_pos] * + frame_header_pkt[ydim_pos] * 3) / 2; + ptr_to_high_low_short((void *)frame_buffer_size, + &frame_buffer_size_high, + &frame_buffer_size_low); + for (i = 0; i < num_addr; i++) { + if (frame_buffer_high && frame_buffer_low) { + if (pmem_fixup_high_low(frame_buffer_high, + frame_buffer_low, + frame_buffer_size_high, + frame_buffer_size_low, + module, + NULL, NULL, NULL, NULL)) + return -EINVAL; + } + frame_buffer_high += 2; + frame_buffer_low += 2; + } + /* Patch the output buffer. */ + frame_buffer_high += 2*skip; + frame_buffer_low += 2*skip; + if (frame_buffer_high && frame_buffer_low) { + if (pmem_fixup_high_low(frame_buffer_high, + frame_buffer_low, + frame_buffer_size_high, + frame_buffer_size_low, + module, + NULL, NULL, NULL, NULL)) + return -EINVAL; + } + if (filp) { + pmem_addr.vaddr = subframe_pkt_addr; + pmem_addr.length = ((subframe_pkt_size + 31) & (~31)) + 32; + pmem_addr.offset = offset; + if (pmem_cache_maint (filp, PMEM_CLEAN_CACHES, &pmem_addr)) { + MM_ERR("Cache operation failed for phys addr high %x" + " addr low %x\n", pkt->subframe_packet_high, + pkt->subframe_packet_low); + return -1; + } + } + + return 0; +} + +int adsp_video_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size) +{ + switch (queue_id) { + case QDSP_mpuVDecPktQueue: + return verify_vdec_pkt_cmd(module, cmd_data, cmd_size); + default: + MM_INFO("unknown video queue %u\n", queue_id); + return 0; + } +} + diff --git a/arch/arm/mach-msm/qdsp5/adsp_videoenc_verify_cmd.c b/arch/arm/mach-msm/qdsp5/adsp_videoenc_verify_cmd.c new file mode 100644 index 00000000000..936b7af81bb --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/adsp_videoenc_verify_cmd.c @@ -0,0 +1,235 @@ +/* arch/arm/mach-msm/qdsp5/adsp_video_verify_cmd.c + * + * Verificion code for aDSP VENC packets from userspace. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. 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 + +#include +#include "adsp.h" +#include + + +static unsigned short x_dimension, y_dimension; + +static inline void *high_low_short_to_ptr(unsigned short high, + unsigned short low) +{ + return (void *)((((unsigned long)high) << 16) | ((unsigned long)low)); +} + +static inline void ptr_to_high_low_short(void *ptr, unsigned short *high, + unsigned short *low) +{ + *high = (unsigned short)((((unsigned long)ptr) >> 16) & 0xffff); + *low = (unsigned short)((unsigned long)ptr & 0xffff); +} + +static int pmem_fixup_high_low(unsigned short *high, + unsigned short *low, + unsigned short size_high, + unsigned short size_low, + struct msm_adsp_module *module, + unsigned long *addr, unsigned long *size) +{ + void *phys_addr; + unsigned long phys_size; + unsigned long kvaddr; + + phys_addr = high_low_short_to_ptr(*high, *low); + phys_size = (unsigned long)high_low_short_to_ptr(size_high, size_low); + MM_DBG("virt %x %x\n", (unsigned int)phys_addr, + (unsigned int)phys_size); + if (adsp_pmem_fixup_kvaddr(module, &phys_addr, &kvaddr, phys_size, + NULL, NULL)) { + MM_ERR("ah%x al%x sh%x sl%x addr %x size %x\n", + *high, *low, size_high, + size_low, (unsigned int)phys_addr, + (unsigned int) phys_size); + return -1; + } + ptr_to_high_low_short(phys_addr, high, low); + MM_DBG("phys %x %x\n", (unsigned int)phys_addr, + (unsigned int)phys_size); + if (addr) + *addr = kvaddr; + if (size) + *size = phys_size; + return 0; +} + +static int verify_venc_cmd(struct msm_adsp_module *module, + void *cmd_data, size_t cmd_size) +{ + unsigned short cmd_id = ((unsigned short *)cmd_data)[0]; + unsigned long frame_buf_size, luma_buf_size, chroma_buf_size; + unsigned short frame_buf_size_high, frame_buf_size_low; + unsigned short luma_buf_size_high, luma_buf_size_low; + unsigned short chroma_buf_size_high, chroma_buf_size_low; + videnc_cmd_cfg *config_cmd; + videnc_cmd_frame_start *frame_cmd; + videnc_cmd_dis *dis_cmd; + + MM_DBG("cmd_size %d cmd_id %d cmd_data %x\n", + cmd_size, cmd_id, (unsigned int)cmd_data); + switch (cmd_id) { + case VIDENC_CMD_ACTIVE: + if (cmd_size < sizeof(videnc_cmd_active)) + return -1; + break; + case VIDENC_CMD_IDLE: + if (cmd_size < sizeof(videnc_cmd_idle)) + return -1; + x_dimension = y_dimension = 0; + break; + case VIDENC_CMD_STATUS_QUERY: + if (cmd_size < sizeof(videnc_cmd_status_query)) + return -1; + break; + case VIDENC_CMD_RC_CFG: + if (cmd_size < sizeof(videnc_cmd_rc_cfg)) + return -1; + break; + case VIDENC_CMD_INTRA_REFRESH: + if (cmd_size < sizeof(videnc_cmd_intra_refresh)) + return -1; + break; + case VIDENC_CMD_DIGITAL_ZOOM: + if (cmd_size < sizeof(videnc_cmd_digital_zoom)) + return -1; + break; + case VIDENC_CMD_DIS_CFG: + if (cmd_size < sizeof(videnc_cmd_dis_cfg)) + return -1; + break; + case VIDENC_CMD_VENC_CLOCK: + if (cmd_size < sizeof(struct videnc_cmd_venc_clock)) + return -1; + break; + case VIDENC_CMD_CFG: + if (cmd_size < sizeof(videnc_cmd_cfg)) + return -1; + config_cmd = (videnc_cmd_cfg *)cmd_data; + x_dimension = ((config_cmd->venc_frame_dim) & 0xFF00)>>8; + x_dimension = x_dimension*16; + y_dimension = (config_cmd->venc_frame_dim) & 0xFF; + y_dimension = y_dimension * 16; + break; + case VIDENC_CMD_FRAME_START: + if (cmd_size < sizeof(videnc_cmd_frame_start)) + return -1; + frame_cmd = (videnc_cmd_frame_start *)cmd_data; + luma_buf_size = x_dimension * y_dimension; + chroma_buf_size = luma_buf_size>>1; + frame_buf_size = luma_buf_size + chroma_buf_size; + ptr_to_high_low_short((void *)luma_buf_size, + &luma_buf_size_high, + &luma_buf_size_low); + ptr_to_high_low_short((void *)chroma_buf_size, + &chroma_buf_size_high, + &chroma_buf_size_low); + ptr_to_high_low_short((void *)frame_buf_size, + &frame_buf_size_high, + &frame_buf_size_low); + /* Address of raw Y data. */ + if (pmem_fixup_high_low(&frame_cmd->input_luma_addr_high, + &frame_cmd->input_luma_addr_low, + luma_buf_size_high, + luma_buf_size_low, + module, + NULL, NULL)) + return -1; + /* Address of raw CbCr data */ + if (pmem_fixup_high_low(&frame_cmd->input_chroma_addr_high, + &frame_cmd->input_chroma_addr_low, + chroma_buf_size_high, + chroma_buf_size_low, + module, + NULL, NULL)) + return -1; + /* Reference VOP */ + if (pmem_fixup_high_low(&frame_cmd->ref_vop_buf_ptr_high, + &frame_cmd->ref_vop_buf_ptr_low, + frame_buf_size_high, + frame_buf_size_low, + module, + NULL, NULL)) + return -1; + /* Encoded Packet Address */ + if (pmem_fixup_high_low(&frame_cmd->enc_pkt_buf_ptr_high, + &frame_cmd->enc_pkt_buf_ptr_low, + frame_cmd->enc_pkt_buf_size_high, + frame_cmd->enc_pkt_buf_size_low, + module, + NULL, NULL)) + return -1; + /* Unfiltered VOP Buffer Address */ + if (pmem_fixup_high_low( + &frame_cmd->unfilt_recon_vop_buf_ptr_high, + &frame_cmd->unfilt_recon_vop_buf_ptr_low, + frame_buf_size_high, + frame_buf_size_low, + module, + NULL, NULL)) + return -1; + /* Filtered VOP Buffer Address */ + if (pmem_fixup_high_low(&frame_cmd->filt_recon_vop_buf_ptr_high, + &frame_cmd->filt_recon_vop_buf_ptr_low, + frame_buf_size_high, + frame_buf_size_low, + module, + NULL, NULL)) + return -1; + break; + case VIDENC_CMD_DIS: + if (cmd_size < sizeof(videnc_cmd_dis)) + return -1; + dis_cmd = (videnc_cmd_dis *)cmd_data; + luma_buf_size = x_dimension * y_dimension; + ptr_to_high_low_short((void *)luma_buf_size, + &luma_buf_size_high, + &luma_buf_size_low); + /* Prev VFE Luma Output Address */ + if (pmem_fixup_high_low(&dis_cmd->vfe_out_prev_luma_addr_high, + &dis_cmd->vfe_out_prev_luma_addr_low, + luma_buf_size_high, + luma_buf_size_low, + module, + NULL, NULL)) + return -1; + break; + default: + MM_INFO("adsp_video:unknown encoder video cmd %u\n", cmd_id); + return 0; + } + + return 0; +} + + +int adsp_videoenc_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size) +{ + switch (queue_id) { + case QDSP_mpuVEncCmdQueue: + return verify_venc_cmd(module, cmd_data, cmd_size); + default: + MM_INFO("unknown video queue %u\n", queue_id); + return 0; + } +} + diff --git a/arch/arm/mach-msm/qdsp5/audio_aac.c b/arch/arm/mach-msm/qdsp5/audio_aac.c new file mode 100644 index 00000000000..556a5f11d15 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_aac.c @@ -0,0 +1,1899 @@ +/* arch/arm/mach-msm/qdsp5/audio_aac.c + * + * aac audio decoder device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, 2011 Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSZ 32768 +#define DMASZ (BUFSZ * 2) +#define BUFSZ_MIN 4096 +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_AAC 5 + +#define PCM_BUFSZ_MIN 9600 /* Hold one stereo AAC frame */ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDAAC_METAFIELD_MASK 0xFFFF0000 +#define AUDAAC_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDAAC_EOS_FLG_MASK 0x01 +#define AUDAAC_EOS_NONE 0x0 /* No EOS detected */ +#define AUDAAC_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDAAC_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audaac_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audaac_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + struct msm_audio_aac_config aac_config; + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int eos_in_progress; + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int rmt_resource_released; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audaac_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + struct msm_audio_bitstream_info stream_info; + + int eq_enable; + int eq_needs_commit; + audpp_cmd_cfg_object_params_eqalizer eq; + audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audplay_config_hostpcm(struct audio *audio); +static void audplay_buffer_refresh(struct audio *audio); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audaac_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); + +static int rmt_put_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_DISABLE; + cmd.dec_type = AUDDEC_DEC_AAC; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return put_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +static int rmt_get_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_ENABLE; + cmd.dec_type = AUDDEC_DEC_AAC; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return get_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + if (audio->rmt_resource_released == 1) { + audio->rmt_resource_released = 0; + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for AAC \ + session 0x%08x on decoder: %d\n Ignoring \ + error and going ahead with the playback\n", + (int)audio, audio->dec_id); + } + } + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_PLAYBACK; + cfg.codec = RPC_AUD_DEF_CODEC_AAC; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + } + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + rmt_put_resource(audio); + audio->rmt_resource_released = 1; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audio_update_pcm_buf_entry(struct audio *audio, uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audplay_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + +} +static void audaac_update_stream_info(struct audio *audio, uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload e_payload; + + /* get stream info from DSP msg */ + spin_lock_irqsave(&audio->dsp_lock, flags); + + audio->stream_info.codec_type = AUDIO_CODEC_TYPE_AAC; + audio->stream_info.chan_info = (0x0000FFFF & payload[1]); + audio->stream_info.sample_rate = (0x0000FFFF & payload[2]); + audio->stream_info.bit_stream_info = (0x0000FFFF & payload[3]); + audio->stream_info.bit_rate = payload[4]; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + MM_DBG("chan_info=%d, sample_rate=%d, bit_stream_info=%d\n", + audio->stream_info.chan_info, + audio->stream_info.sample_rate, + audio->stream_info.bit_stream_info); + + /* send event to ARM to notify steam info coming */ + e_payload.stream_info = audio->stream_info; + audaac_post_event(audio, AUDIO_EVENT_STREAM_INFO, e_payload); +} +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audplay_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audio_update_pcm_buf_entry(audio, msg); + break; + + case AUDPLAY_UP_STREAM_INFO: + audaac_update_stream_info(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status: sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + if (audio->pcm_feedback) { + audplay_config_hostpcm(audio); + audplay_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status \n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq); + audpp_avsync(audio->dec_id, 22050); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audpp_avsync(audio->dec_id, 0); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audplay_buffer_refresh(audio); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_aac = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id,\ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + u16 cfg_dec_cmd[AUDPP_CMD_CFG_DEC_TYPE_LEN / sizeof(unsigned short)]; + + memset(cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + cfg_dec_cmd[0] = AUDPP_CMD_CFG_DEC_TYPE; + + if (enable) + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_AAC; + else + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + audpp_cmd_cfg_adec_params_aac cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_AAC_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + cmd.format = audio->aac_config.format; + cmd.audio_object = audio->aac_config.audio_object; + cmd.ep_config = audio->aac_config.ep_config; + cmd.aac_section_data_resilience_flag = + audio->aac_config.aac_section_data_resilience_flag; + cmd.aac_scalefactor_data_resilience_flag = + audio->aac_config.aac_scalefactor_data_resilience_flag; + cmd.aac_spectral_data_resilience_flag = + audio->aac_config.aac_spectral_data_resilience_flag; + cmd.sbr_on_flag = audio->aac_config.sbr_on_flag; + cmd.sbr_ps_on_flag = audio->aac_config.sbr_ps_on_flag; + cmd.channel_configuration = audio->aac_config.channel_configuration; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDAAC_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + /* complete all the writes to the input buffer */ + wmb(); + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audplay_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + /* AAC frame size */ + refresh_cmd.buf0_length = audio->in[audio->fill_next].size - + (audio->in[audio->fill_next].size % 1024) + + (audio->mfield ? 24 : 0); + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audplay_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } else if ((audio->out[0].used == 0) && + (audio->out[1].used == 0) && + (audio->eos_in_progress)) { + wake_up(&audio->write_wait); + } + + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static int audaac_validate_usr_config(struct msm_audio_aac_config *config) +{ + int ret_val = -1; + + if (config->format != AUDIO_AAC_FORMAT_ADTS && + config->format != AUDIO_AAC_FORMAT_RAW && + config->format != AUDIO_AAC_FORMAT_PSUEDO_RAW && + config->format != AUDIO_AAC_FORMAT_LOAS) + goto done; + + if (config->audio_object != AUDIO_AAC_OBJECT_LC && + config->audio_object != AUDIO_AAC_OBJECT_LTP && + config->audio_object != AUDIO_AAC_OBJECT_BSAC && + config->audio_object != AUDIO_AAC_OBJECT_ERLC) + goto done; + + if (config->audio_object == AUDIO_AAC_OBJECT_ERLC) { + if (config->ep_config > 3) + goto done; + if (config->aac_scalefactor_data_resilience_flag != + AUDIO_AAC_SCA_DATA_RES_OFF && + config->aac_scalefactor_data_resilience_flag != + AUDIO_AAC_SCA_DATA_RES_ON) + goto done; + if (config->aac_section_data_resilience_flag != + AUDIO_AAC_SEC_DATA_RES_OFF && + config->aac_section_data_resilience_flag != + AUDIO_AAC_SEC_DATA_RES_ON) + goto done; + if (config->aac_spectral_data_resilience_flag != + AUDIO_AAC_SPEC_DATA_RES_OFF && + config->aac_spectral_data_resilience_flag != + AUDIO_AAC_SPEC_DATA_RES_ON) + goto done; + } else { + config->aac_section_data_resilience_flag = + AUDIO_AAC_SEC_DATA_RES_OFF; + config->aac_scalefactor_data_resilience_flag = + AUDIO_AAC_SCA_DATA_RES_OFF; + config->aac_spectral_data_resilience_flag = + AUDIO_AAC_SPEC_DATA_RES_OFF; + } + +#ifndef CONFIG_AUDIO_AAC_PLUS + if (AUDIO_AAC_SBR_ON_FLAG_OFF != config->sbr_on_flag) + goto done; +#else + if (config->sbr_on_flag != AUDIO_AAC_SBR_ON_FLAG_OFF && + config->sbr_on_flag != AUDIO_AAC_SBR_ON_FLAG_ON) + goto done; +#endif + +#ifndef CONFIG_AUDIO_ENHANCED_AAC_PLUS + if (AUDIO_AAC_SBR_PS_ON_FLAG_OFF != config->sbr_ps_on_flag) + goto done; +#else + if (config->sbr_ps_on_flag != AUDIO_AAC_SBR_PS_ON_FLAG_OFF && + config->sbr_ps_on_flag != AUDIO_AAC_SBR_PS_ON_FLAG_ON) + goto done; +#endif + + if (config->dual_mono_mode > AUDIO_AAC_DUAL_MONO_PL_SR) + goto done; + + if (config->channel_configuration > 2) + goto done; + + ret_val = 0; + done: + return ret_val; +} + +static void audio_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); +} + +static int audaac_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audaac_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audaac_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audaac_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audaac_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audaac_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audaac_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq); + audio->eq_needs_commit = 0; + } + return 0; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = audpp_avsync_byte_count(audio->dec_id); + stats.sample_count = audpp_avsync_sample_count(audio->dec_id); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audaac_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH running=%d\n", audio->running); + audio->rflush = 1; + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + + if (config.channel_count == 1) { + config.channel_count = + AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = + AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + audio->mfield = config.meta_field; + rc = 0; + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == + AUDPP_CMD_PCM_INTF_MONO_V) { + config.channel_count = 1; + } else { + config.channel_count = 2; + } + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_AAC_CONFIG:{ + if (copy_to_user((void *)arg, &audio->aac_config, + sizeof(audio->aac_config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_AAC_CONFIG:{ + struct msm_audio_aac_config usr_config; + + if (copy_from_user + (&usr_config, (void *)arg, + sizeof(usr_config))) { + rc = -EFAULT; + break; + } + + if (audaac_validate_usr_config(&usr_config) == 0) { + audio->aac_config = usr_config; + rc = 0; + } else + rc = -EINVAL; + + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if (config.pcm_feedback) { + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; + } + rc = 0; + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + case AUDIO_GET_STREAM_INFO:{ + if (audio->stream_info.sample_rate == 0) { + /* haven't received DSP stream event, + the stream info is not updated */ + rc = -EPERM; + break; + } + if (copy_to_user((void *)arg, &audio->stream_info, + sizeof(struct msm_audio_bitstream_info))) + rc = -EFAULT; + else + rc = 0; + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} +/* Only useful in tunnel-mode */ +static int audaac_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audplay_send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("to read %d \n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next]. + used > 0) || (audio->stopped) + || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver + does not know frame size, read count must be greater + or equal to size of PCM samples */ + MM_DBG("no partial frame done reading\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + /* order reads to the output buffer */ + rmb(); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x\n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + /* + * Force to exit while loop + * to prevent output thread + * sleep too long if data is not + * ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audplay_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audaac_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + char *buf_ptr; + int rc = 0; + unsigned long flags = 0; + + MM_DBG("signal input EOS reserved=%d\n", audio->reserved); + if (audio->reserved) { + MM_DBG("Pass reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + audio->reserved = 0; + frame->used = 2; + audplay_send_data(audio, 0); + } + MM_DBG("Now signal input EOS after reserved bytes %d %d %d\n", + audio->out[0].used, audio->out[1].used, audio->out_needed); + frame = audio->out + audio->out_head; + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->eos_in_progress = 1; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->eos_in_progress = 0; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audplay_send_data(audio, 0); +done: + return rc; +} +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDAAC_EOS_NONE; + unsigned dsize; + + unsigned short mfield_size = 0; + MM_DBG("cnt=%d\n", count); + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDAAC_EOS_FLG_OFFSET] & + AUDAAC_EOS_FLG_MASK) { + MM_DBG("eos set\n"); + eos_condition = AUDAAC_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDAAC_EOS_FLG_OFFSET] &= + ~AUDAAC_EOS_FLG_MASK; + } + /* Check EOS to see if */ + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", + audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + (frame->size - mfield_size) - 1 : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audplay_send_data(audio, 0); + } + } + MM_DBG("eos_condition %x buf[0x%x] start[0x%x]\n", eos_condition, + (int) buf, (int) start); + if (eos_condition == AUDAAC_EOS_SET) + rc = audaac_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + audio_disable(audio); + if (audio->rmt_resource_released == 0) + rmt_put_resource(audio); + audio_flush(audio); + audio_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audaac_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audaac_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audaac_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audaac_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audaac_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audaac_suspend(struct early_suspend *h) +{ + struct audaac_suspend_ctl *ctl = + container_of(h, struct audaac_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audaac_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audaac_resume(struct early_suspend *h) +{ + struct audaac_suspend_ctl *ctl = + container_of(h, struct audaac_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audaac_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audaac_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audaac_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].used %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audaac_debug_fops = { + .read = audaac_debug_read, + .open = audaac_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, index, offset = 0; + unsigned pmem_sz = DMASZ; + struct audaac_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_aac_" + 5]; +#endif + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_AAC; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d \n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr \ + 0x%08x\n", audio->phys, (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + + audio->read_phys = pmem_kalloc(PCM_BUFSZ_MIN * PCM_BUF_MAX_COUNT, + PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + MM_ERR("could not allocate read buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->read_data = ioremap(audio->read_phys, + PCM_BUFSZ_MIN * PCM_BUF_MAX_COUNT); + if (!audio->read_data) { + MM_ERR("could not allocate read buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + iounmap(audio->data); + pmem_kfree(audio->phys); + pmem_kfree(audio->read_phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("read buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->read_phys, (int)audio->read_data); + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + rc = audmgr_open(&audio->audmgr); + if (rc) { + MM_ERR("audmgr open failed, freeing instance \ + 0x%08x\n", (int)audio); + goto err; + } + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_aac, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + goto err; + } + + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for AAC session \ + 0x%08x on decoder: %d\n", (int)audio, audio->dec_id); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + msm_adsp_put(audio->audplay); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = audio->out_dma_sz >> 1; + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + + audio->pcm_buf_count = PCM_BUF_MAX_COUNT; + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) { + audio->in[index].data = audio->read_data + offset; + audio->in[index].addr = audio->read_phys + offset; + audio->in[index].size = PCM_BUFSZ_MIN; + audio->in[index].used = 0; + offset += PCM_BUFSZ_MIN; + } + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + audio->aac_config.format = AUDIO_AAC_FORMAT_ADTS; + audio->aac_config.audio_object = AUDIO_AAC_OBJECT_LC; + audio->aac_config.ep_config = 0; + audio->aac_config.aac_section_data_resilience_flag = + AUDIO_AAC_SEC_DATA_RES_OFF; + audio->aac_config.aac_scalefactor_data_resilience_flag = + AUDIO_AAC_SCA_DATA_RES_OFF; + audio->aac_config.aac_spectral_data_resilience_flag = + AUDIO_AAC_SPEC_DATA_RES_OFF; +#ifdef CONFIG_AUDIO_AAC_PLUS + audio->aac_config.sbr_on_flag = AUDIO_AAC_SBR_ON_FLAG_ON; +#else + audio->aac_config.sbr_on_flag = AUDIO_AAC_SBR_ON_FLAG_OFF; +#endif +#ifdef CONFIG_AUDIO_ENHANCED_AAC_PLUS + audio->aac_config.sbr_ps_on_flag = AUDIO_AAC_SBR_PS_ON_FLAG_ON; +#else + audio->aac_config.sbr_ps_on_flag = AUDIO_AAC_SBR_PS_ON_FLAG_OFF; +#endif + audio->aac_config.dual_mono_mode = AUDIO_AAC_DUAL_MONO_PL_SR; + audio->aac_config.channel_configuration = 2; + audio->vol_pan.volume = 0x2000; + + audio_flush(audio); + + file->private_data = audio; + audio->opened = 1; +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_aac_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, + &audaac_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audaac_resume; + audio->suspend_ctl.node.suspend = audaac_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (index = 0; index < AUDAAC_EVENT_NUM; index++) { + e_node = kmalloc(sizeof(struct audaac_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } + memset(&audio->stream_info, 0, sizeof(struct msm_audio_bitstream_info)); +done: + return rc; +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_aac_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audaac_fsync +}; + +struct miscdevice audio_aac_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac", + .fops = &audio_aac_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_aac_misc); +} + +static void __exit audio_exit(void) +{ + misc_deregister(&audio_aac_misc); +} + +module_init(audio_init); +module_exit(audio_exit); + +MODULE_DESCRIPTION("MSM AAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5/audio_amrnb.c b/arch/arm/mach-msm/qdsp5/audio_amrnb.c new file mode 100644 index 00000000000..f430b0cb107 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_amrnb.c @@ -0,0 +1,1609 @@ +/* linux/arch/arm/mach-msm/qdsp5/audio_amrnb.c + * + * amrnb audio decoder device + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include + +#define BUFSZ 1024 /* Hold minimum 700ms voice data and 14 bytes of meta in*/ +#define DMASZ (BUFSZ * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_AMRNB 10 + +#define PCM_BUFSZ_MIN 1624 /* 100ms worth of data and 24 bytes of meta out*/ +#define AMRNB_DECODED_FRSZ 320 /* AMR-NB 20ms 8KHz mono PCM size */ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDAMRNB_METAFIELD_MASK 0xFFFF0000 +#define AUDAMRNB_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDAMRNB_EOS_FLG_MASK 0x01 +#define AUDAMRNB_EOS_NONE 0x0 /* No EOS detected */ +#define AUDAMRNB_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDAMRNB_EVENT_NUM 10 /* Default number of pre-allocated event pkts */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audamrnb_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audamrnb_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + uint8_t opened:1; + uint8_t enabled:1; + uint8_t running:1; + uint8_t stopped:1; /* set when stopped, cleared on flush */ + uint8_t pcm_feedback:1; + uint8_t buf_refresh:1; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int rmt_resource_released; + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audamrnb_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + int eq_enable; + int eq_needs_commit; + audpp_cmd_cfg_object_params_eqalizer eq; + audpp_cmd_cfg_object_params_volume vol_pan; +}; + +struct audpp_cmd_cfg_adec_params_amrnb { + audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; +} __attribute__((packed)) ; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audamrnb_send_data(struct audio *audio, unsigned needed); +static void audamrnb_config_hostpcm(struct audio *audio); +static void audamrnb_buffer_refresh(struct audio *audio); +static void audamrnb_dsp_event(void *private, unsigned id, uint16_t *msg); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audamrnb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +static int rmt_put_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_DISABLE; + cmd.dec_type = AUDDEC_DEC_AMRNB; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return put_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +static int rmt_get_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_ENABLE; + cmd.dec_type = AUDDEC_DEC_AMRNB; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return get_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audamrnb_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + if (audio->rmt_resource_released == 1) { + audio->rmt_resource_released = 0; + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for AMRNB \ + session 0x%08x on decoder: %d\n Ignoring \ + error and going ahead with the playback\n", + (int)audio, audio->dec_id); + } + } + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_PLAYBACK; + cfg.codec = RPC_AUD_DEF_CODEC_AMR_NB; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + } + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audamrnb_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audamrnb_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + rmt_put_resource(audio); + audio->rmt_resource_released = 1; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audamrnb_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audamrnb_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audamrnb_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audamrnb_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder\n"); + } +} + +static void audamrnb_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + if (audio->pcm_feedback) { + audamrnb_config_hostpcm(audio); + audamrnb_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status \n"); + break; + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq); + audpp_avsync(audio->dec_id, 22050); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audpp_avsync(audio->dec_id, 0); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audamrnb_buffer_refresh(audio); + break; + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_amrnb = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + u16 cfg_dec_cmd[AUDPP_CMD_CFG_DEC_TYPE_LEN / sizeof(unsigned short)]; + + memset(cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + cfg_dec_cmd[0] = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_AMRNB; + else + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_amrnb cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_V13K_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = 8000; + cmd.stereo_cfg = AUDPP_CMD_PCM_INTF_MONO_V; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDAMRNB_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + /* complete writes to the input buffer */ + wmb(); + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audamrnb_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size - + (audio->in[audio->fill_next].size % AMRNB_DECODED_FRSZ) + + (audio->mfield ? 24 : 0); + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audamrnb_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audamrnb_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audamrnb_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audamrnb_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audamrnb_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audamrnb_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audamrnb_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); +} + +static int audamrnb_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audamrnb_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audamrnb_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audamrnb_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audamrnb_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audamrnb_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audamrnb_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audamrnb_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audamrnb_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audamrnb_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq); + audio->eq_needs_commit = 0; + } + return 0; +} + +static long audamrnb_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = audpp_avsync_byte_count(audio->dec_id); + stats.sample_count = audpp_avsync_sample_count(audio->dec_id); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audamrnb_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audamrnb_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audamrnb_disable(audio); + audio->stopped = 1; + audamrnb_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audamrnb_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + audio->mfield = config.meta_field; + rc = 0; + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = 8000; + config.channel_count = 1; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buf %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("no mem for read buf\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr 0x%08x kernel \ + addr 0x%08x\n", audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audamrnb_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audamrnb_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d \n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver does + * not know frame size, read count must be greater or + * equal to size of PCM samples + */ + MM_DBG("read stop - partial frame\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + /* order reads from the output buffer */ + rmb(); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audamrnb_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audamrnb_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audamrnb_send_data(audio, 0); + +done: + return rc; +} + +static ssize_t audamrnb_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDAMRNB_EOS_NONE; + unsigned short mfield_size = 0; + + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + + MM_DBG("buffer available\n"); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer + * contains just meta field + */ + if (cpy_ptr[AUDAMRNB_EOS_FLG_OFFSET] & + AUDAMRNB_EOS_FLG_MASK) { + MM_DBG("eos set\n"); + eos_condition = AUDAMRNB_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDAMRNB_EOS_FLG_OFFSET] &= + ~AUDAMRNB_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + frame->used = (xfer + mfield_size); + audio->out_head ^= 1; + count -= xfer; + buf += xfer; + + audamrnb_send_data(audio, 0); + + } + if (eos_condition == AUDAMRNB_EOS_SET) + rc = audamrnb_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audamrnb_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + audamrnb_disable(audio); + if (audio->rmt_resource_released == 0) + rmt_put_resource(audio); + audamrnb_flush(audio); + audamrnb_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audamrnb_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audamrnb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audamrnb_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audamrnb_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audamrnb_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static void audamrnb_suspend(struct early_suspend *h) +{ + struct audamrnb_suspend_ctl *ctl = + container_of(h, struct audamrnb_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audamrnb_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audamrnb_resume(struct early_suspend *h) +{ + struct audamrnb_suspend_ctl *ctl = + container_of(h, struct audamrnb_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audamrnb_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audamrnb_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audamrnb_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].used %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audamrnb_debug_fops = { + .read = audamrnb_debug_read, + .open = audamrnb_debug_open, +}; +#endif + +static int audamrnb_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + struct audamrnb_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_amrnb_" + 5]; +#endif + + /* Allocate Mem for audio instance */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_AMRNB; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->phys)) { + MM_ERR("could not allocate write buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x freeing\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr \ + 0x%08x\n", audio->phys, (int)audio->data); + } + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + rc = audmgr_open(&audio->audmgr); + if (rc) { + MM_ERR("audmgr open failed, freeing instance \ + 0x%08x\n", (int)audio); + goto err; + } + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_amrnb, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + goto err; + } + + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for AMRNB session \ + 0x%08x on decoder: %d\n", (int)audio, audio->dec_id); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + msm_adsp_put(audio->audplay); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x2000; + + audamrnb_flush(audio); + + file->private_data = audio; + audio->opened = 1; +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_amrnb_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audamrnb_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audamrnb_resume; + audio->suspend_ctl.node.suspend = audamrnb_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDAMRNB_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audamrnb_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_amrnb_fops = { + .owner = THIS_MODULE, + .open = audamrnb_open, + .release = audamrnb_release, + .read = audamrnb_read, + .write = audamrnb_write, + .unlocked_ioctl = audamrnb_ioctl, + .fsync = audamrnb_fsync, +}; + +struct miscdevice audio_amrnb_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrnb", + .fops = &audio_amrnb_fops, +}; + +static int __init audamrnb_init(void) +{ + return misc_register(&audio_amrnb_misc); +} + +static void __exit audamrnb_exit(void) +{ + misc_deregister(&audio_amrnb_misc); +} + +module_init(audamrnb_init); +module_exit(audamrnb_exit); + +MODULE_DESCRIPTION("MSM AMR-NB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5/audio_amrnb_in.c b/arch/arm/mach-msm/qdsp5/audio_amrnb_in.c new file mode 100644 index 00000000000..8d99a3d4fe3 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_amrnb_in.c @@ -0,0 +1,669 @@ +/* arch/arm/mach-msm/qdsp5/audio_amrnb_in.c + * + * amrnb encoder device + * + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * This code is based in part on arch/arm/mach-msm/qdsp5/audio_in.c, which is + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "audmgr.h" + +#include +#include +#include +#include +#include + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (8) +#define FRAME_SIZE (22 * 2) +#define DMASZ (FRAME_SIZE * FRAME_NUM) + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; +}; + +struct audio_amrnb_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + + uint16_t audrec_obj_idx; + + /* configuration to use on enable */ + uint32_t buffer_size; + uint32_t enc_type; /* 0 for WAV ,1 for AAC,10 for AMRNB */ + struct msm_audio_amrnb_enc_config amrnb_enc_cfg; + + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + +}; + +static int audio_amrnb_in_dsp_enable(struct audio_amrnb_in *audio, int enable); +static int audio_amrnb_in_encmem_config(struct audio_amrnb_in *audio); +static int audio_amrnb_in_encparam_config(struct audio_amrnb_in *audio); +static int audio_amrnb_in_dsp_read_buffer(struct audio_amrnb_in *audio, + uint32_t read_cnt); +static void audio_amrnb_in_flush(struct audio_amrnb_in *audio); +static void audio_amrnb_in_dsp_event(void *data, unsigned id, uint16_t *msg); + +static struct audio_amrnb_in the_audio_amrnb_in; + +/* must be called with audio->lock held */ +static int audio_amrnb_in_enable(struct audio_amrnb_in *audio) +{ + struct audmgr_config cfg; + int rc; + + if (audio->enabled) + return 0; + + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_8000; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; + cfg.codec = RPC_AUD_DEF_CODEC_AMR_NB; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + + if (audrectask_enable(audio->enc_type, audio_amrnb_in_dsp_event, + audio)) { + audmgr_disable(&audio->audmgr); + MM_ERR("audrec_enable failed\n"); + return -ENODEV; + } + + audio->enabled = 1; + audio_amrnb_in_dsp_enable(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audio_amrnb_in_disable(struct audio_amrnb_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + audio_amrnb_in_dsp_enable(audio, 0); + wake_up(&audio->wait); + audrectask_disable(audio->enc_type, audio); + audmgr_disable(&audio->audmgr); + } + return 0; +} + +/* ------------------- dsp --------------------- */ +struct audio_amrnb_in_frame { + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + unsigned char raw_bitstream[]; +} __attribute__((packed)); + +static void audio_amrnb_in_get_dsp_frames(struct audio_amrnb_in *audio) +{ + struct audio_amrnb_in_frame *frame; + uint32_t index; + unsigned long flags; + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - + sizeof(*frame)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = FRAME_SIZE - (sizeof(*frame)); /* Send + Complete Transcoded Data, not actual frame part */ + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + else + audio->in_count++; + + audio_amrnb_in_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +static void audio_amrnb_in_dsp_event(void *data, unsigned id, uint16_t *msg) +{ + struct audio_amrnb_in *audio = data; + + switch (id) { + case AUDREC_MSG_CMD_CFG_DONE_MSG: + MM_DBG("CFG_DONE_MSG\n"); + if (msg[0] & AUDREC_MSG_CFG_DONE_ENC_ENA) { + audio->audrec_obj_idx = msg[1]; + audio_amrnb_in_encmem_config(audio); + } else { + audio->running = 0; + } + break; + case AUDREC_MSG_CMD_AREC_MEM_CFG_DONE_MSG: { + MM_DBG("AREC_MEM_CFG_DONE_MSG\n"); + if (msg[0] == audio->audrec_obj_idx) + audio_amrnb_in_encparam_config(audio); + else + MM_ERR("AREC_MEM_CFG_DONE_MSG ERR\n"); + break; + } + case AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG: { + MM_DBG("AREC_PARAM_CFG_DONE_MSG\n"); + if (msg[0] == audio->audrec_obj_idx) + audio->running = 1; + else + MM_ERR("AREC_PARAM_CFG_DONE_MSG ERR\n"); + break; + } + case AUDREC_MSG_PACKET_READY_MSG: { + MM_DBG("AUDREC_MSG_PACKET_READY_MSG\n"); + if (msg[0] == audio->audrec_obj_idx) + audio_amrnb_in_get_dsp_frames(audio); + else + MM_ERR("PACKET_READY_MSG ERR\n"); + break; + } + case AUDREC_MSG_FATAL_ERR_MSG: { + MM_ERR("FATAL_ERR_MSG %x\n", msg[0]); + break; + } + default: + MM_ERR("unknown event %d\n", id); + } +} + +static int audio_amrnb_in_dsp_enable(struct audio_amrnb_in *audio, int enable) +{ + struct audrec_cmd_enc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_ENC_CFG; + cmd.audrec_enc_type = (audio->enc_type) | + (enable ? AUDREC_CMD_ENC_ENA : AUDREC_CMD_ENC_DIS); + /* Don't care on enable, required on disable */ + cmd.audrec_obj_idx = audio->audrec_obj_idx; + + return audrectask_send_cmdqueue(&cmd, sizeof(cmd)); +} + +static int audio_amrnb_in_encmem_config(struct audio_amrnb_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + unsigned cnt; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.cmd_id = AUDREC_CMD_ARECMEM_CFG; + cmd.audrec_obj_idx = audio->audrec_obj_idx; + /* Rate at which packet complete message comes */ + cmd.audrec_up_pkt_intm_cnt = 1; + cmd.audrec_extpkt_buffer_msw = audio->phys >> 16; + cmd.audrec_extpkt_buffer_lsw = audio->phys; + /* Max Buffer no available for frames */ + cmd.audrec_extpkt_buffer_num = FRAME_NUM; + + /* prepare buffer pointers: + * 4 halfword header + Frame Raw Packet (20ms data) + */ + for (cnt = 0; cnt < FRAME_NUM; cnt++) { + audio->in[cnt].data = data + 4; /* Pointer to Raw Packet part*/ + MM_DBG(" audio->in[%d].data = %x \n", cnt, + (unsigned int)audio->in[cnt].data); + data += 22; /* Point to next Frame buffer */ + } + + return audrectask_send_cmdqueue(&cmd, sizeof(cmd)); +} + +static int audio_amrnb_in_encparam_config(struct audio_amrnb_in *audio) +{ + struct audrec_cmd_arecparam_amrnb_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.common.cmd_id = AUDREC_CMD_ARECPARAM_CFG; + cmd.common.audrec_obj_idx = audio->audrec_obj_idx; + cmd.samp_rate_idx = 0xb; /* 8k Sampling rate */ + cmd.voicememoencweight1 = audio->amrnb_enc_cfg.voicememoencweight1; + cmd.voicememoencweight2 = audio->amrnb_enc_cfg.voicememoencweight2; + cmd.voicememoencweight3 = audio->amrnb_enc_cfg.voicememoencweight3; + cmd.voicememoencweight4 = audio->amrnb_enc_cfg.voicememoencweight4; + cmd.update_mode = 0x8000 | 0x0000; + cmd.dtx_mode = audio->amrnb_enc_cfg.dtx_mode_enable; + cmd.test_mode = audio->amrnb_enc_cfg.test_mode_enable; + cmd.used_mode = audio->amrnb_enc_cfg.enc_mode; + + MM_DBG("cmd.common.cmd_id = 0x%4x\n", cmd.common.cmd_id); + MM_DBG("cmd.common.audrec_obj_idx = 0x%4x\n", + cmd.common.audrec_obj_idx); + MM_DBG("cmd.samp_rate_idx = 0x%4x\n", cmd.samp_rate_idx); + MM_DBG("cmd.voicememoencweight1 = 0x%4x\n", + cmd.voicememoencweight1); + MM_DBG("cmd.voicememoencweight2 = 0x%4x\n", + cmd.voicememoencweight2); + MM_DBG("cmd.voicememoencweight3 = 0x%4x\n", + cmd.voicememoencweight3); + MM_DBG("cmd.voicememoencweight4 = 0x%4x\n", + cmd.voicememoencweight4); + MM_DBG("cmd.update_mode = 0x%4x\n", cmd.update_mode); + MM_DBG("cmd.dtx_mode = 0x%4x\n", cmd.dtx_mode); + MM_DBG("cmd.test_mode = 0x%4x\n", cmd.test_mode); + MM_DBG("cmd.used_mode = 0x%4x\n", cmd.used_mode); + + return audrectask_send_cmdqueue(&cmd, sizeof(cmd)); +} + +static int audio_amrnb_in_dsp_read_buffer(struct audio_amrnb_in *audio, + uint32_t read_cnt) +{ + audrec_cmd_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PACKET_EXT_PTR; + cmd.type = audio->audrec_obj_idx; + cmd.curr_rec_count_msw = read_cnt >> 16; + cmd.curr_rec_count_lsw = read_cnt; + + return audrectask_send_bitstreamqueue(&cmd, sizeof(cmd)); +} + +static void audio_amrnb_in_flush(struct audio_amrnb_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + for (i = 0; i < FRAME_NUM; i++) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } +} + + +/* ------------------- device --------------------- */ +static long audio_amrnb_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_amrnb_in *audio = file->private_data; + int rc; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + rc = audio_amrnb_in_enable(audio); + break; + case AUDIO_STOP: + rc = audio_amrnb_in_disable(audio); + audio->stopped = 1; + break; + case AUDIO_FLUSH: + if (audio->stopped) { + /* Make sure we're stopped and we wake any threads + * that might be blocked holding the read_lock. + * While audio->stopped read threads will always + * exit immediately. + */ + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audio_amrnb_in_flush(audio); + mutex_unlock(&audio->read_lock); + } + case AUDIO_SET_CONFIG: { + rc = -EINVAL; /* Buffer size better to come from upper */ + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + cfg.sample_rate = 8000; + cfg.channel_count = 1; + cfg.type = 10; + cfg.unused[0] = 0; + cfg.unused[1] = 0; + cfg.unused[2] = 0; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_AMRNB_ENC_CONFIG: { + if (copy_to_user((void *)arg, &audio->amrnb_enc_cfg, + sizeof(audio->amrnb_enc_cfg))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_AMRNB_ENC_CONFIG: { + struct msm_audio_amrnb_enc_config cfg; + if (copy_from_user + (&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + } else + rc = 0; + audio->amrnb_enc_cfg.voicememoencweight1 = + cfg.voicememoencweight1; + audio->amrnb_enc_cfg.voicememoencweight2 = + cfg.voicememoencweight2; + audio->amrnb_enc_cfg.voicememoencweight3 = + cfg.voicememoencweight3; + audio->amrnb_enc_cfg.voicememoencweight4 = + cfg.voicememoencweight4; + audio->amrnb_enc_cfg.dtx_mode_enable = cfg.dtx_mode_enable; + audio->amrnb_enc_cfg.test_mode_enable = cfg.test_mode_enable; + audio->amrnb_enc_cfg.enc_mode = cfg.enc_mode; + /* Run time change of Param */ + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audio_amrnb_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_amrnb_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped); + if (rc < 0) + break; + + if (audio->stopped) { + rc = -EBUSY; + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + if (count >= size) { + dma_coherent_post_ops(); + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is invalid + and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, + flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + } else { + MM_ERR("short read\n"); + break; + } + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static ssize_t audio_amrnb_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + return -EINVAL; +} + +static int audio_amrnb_in_release(struct inode *inode, struct file *file) +{ + struct audio_amrnb_in *audio = file->private_data; + + mutex_lock(&audio->lock); + audio_amrnb_in_disable(audio); + audio_amrnb_in_flush(audio); + audio->opened = 0; + if (audio->data) { + dma_free_coherent(NULL, DMASZ, audio->data, audio->phys); + audio->data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + + +static int audio_amrnb_in_open(struct inode *inode, struct file *file) +{ + struct audio_amrnb_in *audio = &the_audio_amrnb_in; + int rc; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + + if (!audio->data) { + audio->data = dma_alloc_coherent(NULL, DMASZ, + &audio->phys, GFP_KERNEL); + if (!audio->data) { + rc = -ENOMEM; + goto done; + } + } + + rc = audmgr_open(&audio->audmgr); + if (rc) + goto err; + audio->buffer_size = FRAME_SIZE - 8; + audio->enc_type = 10; + audio->amrnb_enc_cfg.voicememoencweight1 = 0x0000; + audio->amrnb_enc_cfg.voicememoencweight2 = 0x0000; + audio->amrnb_enc_cfg.voicememoencweight3 = 0x4000; + audio->amrnb_enc_cfg.voicememoencweight4 = 0x0000; + audio->amrnb_enc_cfg.dtx_mode_enable = 0; + audio->amrnb_enc_cfg.test_mode_enable = 0; + audio->amrnb_enc_cfg.enc_mode = 7; + audio->dsp_cnt = 0; + audio->stopped = 0; + + audio_amrnb_in_flush(audio); + + file->private_data = audio; + audio->opened = 1; + rc = 0; + goto done; + +err: + dma_free_coherent(NULL, DMASZ, audio->data, audio->phys); +done: + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_fops = { + .owner = THIS_MODULE, + .open = audio_amrnb_in_open, + .release = audio_amrnb_in_release, + .read = audio_amrnb_in_read, + .write = audio_amrnb_in_write, + .unlocked_ioctl = audio_amrnb_in_ioctl, +}; + +struct miscdevice audio_amrnb_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrnb_in", + .fops = &audio_fops, +}; + +#ifdef CONFIG_DEBUG_FS +static ssize_t audamrnb_in_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audamrnb_in_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio_amrnb_in *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "audrec_obj_idx %d\n", audio->audrec_obj_idx); + n += scnprintf(buffer + n, debug_bufmax - n, + "dsp_cnt %d \n", audio->dsp_cnt); + n += scnprintf(buffer + n, debug_bufmax - n, + "in_count %d \n", audio->in_count); + for (i = 0; i < FRAME_NUM; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "audio->in[%d].size %d \n", i, audio->in[i].size); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when record halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_size %d \n", audio->buffer_size); + n += scnprintf(buffer + n, debug_bufmax - n, + "in_head %d \n", audio->in_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "in_tail %d \n", audio->in_tail); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audamrnb_in_debug_fops = { + .read = audamrnb_in_debug_read, + .open = audamrnb_in_debug_open, +}; +#endif + +static int __init audio_amrnb_in_init(void) +{ +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + mutex_init(&the_audio_amrnb_in.lock); + mutex_init(&the_audio_amrnb_in.read_lock); + spin_lock_init(&the_audio_amrnb_in.dsp_lock); + init_waitqueue_head(&the_audio_amrnb_in.wait); + +#ifdef CONFIG_DEBUG_FS + dentry = debugfs_create_file("msm_amrnb_in", S_IFREG | S_IRUGO, NULL, + (void *) &the_audio_amrnb_in, &audamrnb_in_debug_fops); + + if (IS_ERR(dentry)) + MM_ERR("debugfs_create_file failed\n"); +#endif + return misc_register(&audio_amrnb_in_misc); +} + +static void __exit audio_amrnb_in_exit(void) +{ + misc_deregister(&audio_amrnb_in_misc); +} + +module_init(audio_amrnb_in_init); +module_exit(audio_amrnb_in_exit); + +MODULE_DESCRIPTION("MSM AMRNB Encoder driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5/audio_amrwb.c b/arch/arm/mach-msm/qdsp5/audio_amrwb.c new file mode 100644 index 00000000000..dcd3c05104f --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_amrwb.c @@ -0,0 +1,1680 @@ +/* linux/arch/arm/mach-msm/qdsp5/audio_amrwb.c + * + * amrwb audio decoder device + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include + +#define BUFSZ 4110 /* Hold minimum 700ms voice data and 14 bytes of meta in*/ +#define DMASZ (BUFSZ * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_AMRWB 11 + +#define PCM_BUFSZ_MIN 8216 /* 100ms worth of data and 24 bytes of meta out*/ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDAMRWB_METAFIELD_MASK 0xFFFF0000 +#define AUDAMRWB_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDAMRWB_EOS_FLG_MASK 0x01 +#define AUDAMRWB_EOS_NONE 0x0 /* No EOS detected */ +#define AUDAMRWB_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDAMRWB_EVENT_NUM 10 /* Default number of pre-allocated event pkts */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audamrwb_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audamrwb_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + struct audmgr audmgr; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int rmt_resource_released; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audamrwb_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + int eq_enable; + int eq_needs_commit; + audpp_cmd_cfg_object_params_eqalizer eq; + audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audamrwb_send_data(struct audio *audio, unsigned needed); +static void audamrwb_config_hostpcm(struct audio *audio); +static void audamrwb_buffer_refresh(struct audio *audio); +static void audamrwb_dsp_event(void *private, unsigned id, uint16_t *msg); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audamrwb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +static int rmt_put_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_DISABLE; + cmd.dec_type = AUDDEC_DEC_AMRWB; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return put_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +static int rmt_get_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_ENABLE; + cmd.dec_type = AUDDEC_DEC_AMRWB; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return get_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audamrwb_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + if (audio->rmt_resource_released == 1) { + audio->rmt_resource_released = 0; + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for AMRWB \ + session 0x%08x on decoder: %d\n Ignoring \ + error and going ahead with the playback\n", + (int)audio, audio->dec_id); + } + } + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_PLAYBACK; + cfg.codec = RPC_AUD_DEF_CODEC_AMR_WB; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + } + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audamrwb_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audamrwb_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + rmt_put_resource(audio); + audio->rmt_resource_released = 1; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audamrwb_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audamrwb_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audamrwb_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audamrwb_update_pcm_buf_entry(audio, msg); + break; + + default: + MM_ERR("unexpected message from decoder\n"); + } +} + +static void audamrwb_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason=0x%04x\n", + reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init\n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg\n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play\n"); + if (audio->pcm_feedback) { + audamrwb_config_hostpcm(audio); + audamrwb_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_DBG("unknown decoder status\n"); + break; + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq); + audpp_avsync(audio->dec_id, 22050); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audpp_avsync(audio->dec_id, 0); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audamrwb_buffer_refresh(audio); + break; + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + default: + MM_DBG("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_amrwb = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + u16 cfg_dec_cmd[AUDPP_CMD_CFG_DEC_TYPE_LEN / sizeof(unsigned short)]; + + memset(cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + cfg_dec_cmd[0] = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_AMRWB; + else + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_amrwb cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_AMRWB_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + cmd.stereo_cfg = audio->out_channel_mode; + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDAMRWB_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audamrwb_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audamrwb_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audamrwb_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audamrwb_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audamrwb_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audamrwb_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audamrwb_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audamrwb_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); +} + +static int audamrwb_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audamrwb_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audamrwb_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audamrwb_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audamrwb_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audamrwb_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audamrwb_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audamrwb_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audamrwb_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audamrwb_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq); + audio->eq_needs_commit = 0; + } + return 0; +} + +static long audamrwb_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = audpp_avsync_byte_count(audio->dec_id); + stats.sample_count = audpp_avsync_sample_count(audio->dec_id); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audamrwb_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audamrwb_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audamrwb_disable(audio); + audio->stopped = 1; + audamrwb_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG(" AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audamrwb_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) + config.channel_count = + AUDPP_CMD_PCM_INTF_MONO_V; + else if (config.channel_count == 2) + config.channel_count = + AUDPP_CMD_PCM_INTF_STEREO_V; + else + rc = -EINVAL; + audio->out_channel_mode = config.channel_count; + audio->out_sample_rate = config.sample_rate; + audio->mfield = config.meta_field; + rc = 0; + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == + AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = 0; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buf %d\n", config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("no mem for read buf\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->pcm_feedback = 1; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr 0x%08x \ + kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audamrwb_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audamrwb_send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audamrwb_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("count %d\n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver does + * not know frame size, read count must be greater or + * equal to size of PCM samples + */ + MM_DBG("read stop - partial frame\n"); + break; + } else { + MM_DBG("read from in[%d]\n", + audio->read_next); + + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_ERR("kick start pcm feedback again\n"); + audamrwb_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audamrwb_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + char *buf_ptr; + int rc = 0; + + MM_DBG("signal input EOS reserved=%d\n", audio->reserved); + if (audio->reserved) { + MM_DBG("Pass reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + audio->reserved = 0; + frame->used = 2; + audamrwb_send_data(audio, 0); + } + + MM_DBG("Now signal input EOS after reserved bytes %d %d %d\n", + audio->out[0].used, audio->out[1].used, audio->out_needed); + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audamrwb_send_data(audio, 0); + +done: + return rc; +} + +static ssize_t audamrwb_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDAMRWB_EOS_NONE; + unsigned short mfield_size = 0; + unsigned dsize; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + + MM_DBG("buffer available\n"); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer + * contains just meta field + */ + if (cpy_ptr[AUDAMRWB_EOS_FLG_OFFSET] & + AUDAMRWB_EOS_FLG_MASK) { + MM_DBG("eos set\n"); + eos_condition = AUDAMRWB_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDAMRWB_EOS_FLG_OFFSET] &= + ~AUDAMRWB_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + ((frame->size - mfield_size) - 1) : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audamrwb_send_data(audio, 0); + } + } + MM_DBG("eos_condition %x buf[0x%x] start[0x%x]\n", eos_condition, + (int) buf, (int) start); + if (eos_condition == AUDAMRWB_EOS_SET) + rc = audamrwb_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audamrwb_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + audamrwb_disable(audio); + if (audio->rmt_resource_released == 0) + rmt_put_resource(audio); + audamrwb_flush(audio); + audamrwb_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audamrwb_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audamrwb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audamrwb_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audamrwb_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audamrwb_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static void audamrwb_suspend(struct early_suspend *h) +{ + struct audamrwb_suspend_ctl *ctl = + container_of(h, struct audamrwb_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audamrwb_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audamrwb_resume(struct early_suspend *h) +{ + struct audamrwb_suspend_ctl *ctl = + container_of(h, struct audamrwb_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audamrwb_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audamrwb_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audamrwb_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].used %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audamrwb_debug_fops = { + .read = audamrwb_debug_read, + .open = audamrwb_debug_open, +}; +#endif + +static int audamrwb_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + struct audamrwb_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_amrwb_" + 5]; +#endif + + /* Allocate Mem for audio instance */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("No memory to allocate audio instance\n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_AMRWB; + if (file->f_mode & FMODE_READ) + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + else + dec_attrb |= MSM_AUD_MODE_TUNNEL; + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->phys)) { + MM_ERR("could not allocate write buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + } + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + rc = audmgr_open(&audio->audmgr); + if (rc) { + MM_ERR("audmgr open failed, freeing instance \ + 0x%08x\n", (int)audio); + goto err; + } + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_amrwb, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + goto err; + } + + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for AMRWB session \ + 0x%08x on decoder: %d\n", (int)audio, audio->dec_id); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + msm_adsp_put(audio->audplay); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x2000; + audio->vol_pan.pan = 0x0; + audio->eq_enable = 0; + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + + audamrwb_flush(audio); + + file->private_data = audio; + audio->opened = 1; + audio->event_abort = 0; +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_amrwb_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audamrwb_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audamrwb_resume; + audio->suspend_ctl.node.suspend = audamrwb_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDAMRWB_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audamrwb_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_amrwb_fops = { + .owner = THIS_MODULE, + .open = audamrwb_open, + .release = audamrwb_release, + .read = audamrwb_read, + .write = audamrwb_write, + .unlocked_ioctl = audamrwb_ioctl, + .fsync = audamrwb_fsync, +}; + +struct miscdevice audio_amrwb_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrwb", + .fops = &audio_amrwb_fops, +}; + +static int __init audamrwb_init(void) +{ + return misc_register(&audio_amrwb_misc); +} + +static void __exit audamrwb_exit(void) +{ + misc_deregister(&audio_amrwb_misc); +} + +module_init(audamrwb_init); +module_exit(audamrwb_exit); + +MODULE_DESCRIPTION("MSM AMR-WB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5/audio_evrc.c b/arch/arm/mach-msm/qdsp5/audio_evrc.c new file mode 100644 index 00000000000..eaa652835d8 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_evrc.c @@ -0,0 +1,1603 @@ +/* arch/arm/mach-msm/audio_evrc.c + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * This code also borrows from audio_aac.c, which is + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include + +/* Hold 30 packets of 24 bytes each and 14 bytes of meta in */ +#define BUFSZ 734 +#define DMASZ (BUFSZ * 2) + +#define AUDDEC_DEC_EVRC 12 + +#define PCM_BUFSZ_MIN 1624 /* 100ms worth of data and + and 24 bytes of meta out */ +#define PCM_BUF_MAX_COUNT 5 +/* DSP only accepts 5 buffers at most + * but support 2 buffers currently + */ +#define EVRC_DECODED_FRSZ 320 /* EVRC 20ms 8KHz mono PCM size */ + +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDEVRC_METAFIELD_MASK 0xFFFF0000 +#define AUDEVRC_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDEVRC_EOS_FLG_MASK 0x01 +#define AUDEVRC_EOS_NONE 0x0 /* No EOS detected */ +#define AUDEVRC_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDEVRC_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audevrc_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audevrc_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + uint8_t opened:1; + uint8_t enabled:1; + uint8_t running:1; + uint8_t stopped:1; /* set when stopped, cleared on flush */ + uint8_t pcm_feedback:1; + uint8_t buf_refresh:1; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int rmt_resource_released; + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audevrc_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + int eq_enable; + int eq_needs_commit; + audpp_cmd_cfg_object_params_eqalizer eq; + audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audevrc_send_data(struct audio *audio, unsigned needed); +static void audevrc_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audevrc_config_hostpcm(struct audio *audio); +static void audevrc_buffer_refresh(struct audio *audio); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audevrc_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +static int rmt_put_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_DISABLE; + cmd.dec_type = AUDDEC_DEC_EVRC; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return put_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +static int rmt_get_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_ENABLE; + cmd.dec_type = AUDDEC_DEC_EVRC; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return get_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audevrc_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + if (audio->enabled) + return 0; + + if (audio->rmt_resource_released == 1) { + audio->rmt_resource_released = 0; + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for EVRC \ + session 0x%08x on decoder: %d\n Ignoring \ + error and going ahead with the playback\n", + (int)audio, audio->dec_id); + } + } + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_PLAYBACK; + cfg.codec = RPC_AUD_DEF_CODEC_EVRC; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + } + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audevrc_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audevrc_disable(struct audio *audio) +{ + int rc = 0; + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + rmt_put_resource(audio); + audio->rmt_resource_released = 1; + } + return rc; +} + +/* ------------------- dsp --------------------- */ + +static void audevrc_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr + == payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audevrc_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audevrc_send_data(audio, 1); + break; + case AUDPLAY_MSG_BUFFER_UPDATE: + MM_DBG("\n"); /* Macro prints the file name and function */ + audevrc_update_pcm_buf_entry(audio, msg); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + default: + MM_ERR("unexpected message from decoder \n"); + } +} + +static void audevrc_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + if (audio->pcm_feedback) { + audevrc_config_hostpcm(audio); + audevrc_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status \n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq); + audpp_avsync(audio->dec_id, 22050); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audpp_avsync(audio->dec_id, 0); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK\n"); + audpp_cmd_cfg_adec_params(audio); + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audevrc_buffer_refresh(audio); + break; + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_evrc = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + u16 cfg_dec_cmd[AUDPP_CMD_CFG_DEC_TYPE_LEN / sizeof(unsigned short)]; + + memset(cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd[0] = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_EVRC; + else + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_evrc cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = sizeof(cmd); + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = 8000; + cmd.stereo_cfg = AUDPP_CMD_PCM_INTF_MONO_V; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDEVRC_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + /* complete writes to the input buffer */ + wmb(); + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audevrc_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audevrc_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = 1; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audevrc_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audevrc_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audevrc_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audevrc_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audevrc_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audevrc_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); +} + +static int audevrc_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audevrc_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audevrc_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audevrc_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audevrc_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + + +static long audevrc_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audevrc_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audevrc_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audevrc_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audevrc_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq); + audio->eq_needs_commit = 0; + } + return 0; +} + +static long audevrc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = audpp_avsync_byte_count(audio->dec_id); + stats.sample_count = audpp_avsync_sample_count(audio->dec_id); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audevrc_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audevrc_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audevrc_disable(audio); + audio->stopped = 1; + audevrc_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audevrc_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + audio->mfield = config.meta_field; + rc = 0; + MM_DBG("AUDIO_SET_CONFIG applicable only \ + for meta field configuration\n"); + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = 8000; + config.channel_count = 1; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buf %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("no mem for read buf\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; + index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr \ + 0x%08x kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audevrc_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audevrc_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + if (!audio->pcm_feedback) { + return 0; + /* PCM feedback is not enabled. Nothing to read */ + } + mutex_lock(&audio->read_lock); + MM_DBG("\n"); /* Macro prints the file name and function */ + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + MM_DBG("wait terminated \n"); + if (rc < 0) + break; + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver does + * not know frame size, read count must be greater or + * equal to size of PCM samples + */ + MM_DBG("read stop - partial frame\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + /* order reads from the output buffer */ + rmb(); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", + (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment + */ + + } + } + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audevrc_buffer_refresh(audio); + } + mutex_unlock(&audio->read_lock); + if (buf > start) + rc = buf - start; + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audevrc_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audevrc_send_data(audio, 0); + +done: + return rc; +} + +static ssize_t audevrc_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + unsigned short mfield_size = 0; + int rc = 0, eos_condition = AUDEVRC_EOS_NONE; + + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, + mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDEVRC_EOS_FLG_OFFSET] & + AUDEVRC_EOS_FLG_MASK) { + MM_DBG("eos set\n"); + eos_condition = AUDEVRC_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDEVRC_EOS_FLG_OFFSET] &= + ~AUDEVRC_EOS_FLG_MASK; + } + /* Check EOS to see if */ + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + frame->used = xfer + mfield_size; + audio->out_head ^= 1; + count -= xfer; + buf += xfer; + audevrc_send_data(audio, 0); + } + if (eos_condition == AUDEVRC_EOS_SET) + rc = audevrc_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audevrc_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + audevrc_disable(audio); + if (audio->rmt_resource_released == 0) + rmt_put_resource(audio); + audevrc_flush(audio); + audevrc_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audevrc_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audevrc_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audevrc_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audevrc_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audevrc_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static void audevrc_suspend(struct early_suspend *h) +{ + struct audevrc_suspend_ctl *ctl = + container_of(h, struct audevrc_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audevrc_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audevrc_resume(struct early_suspend *h) +{ + struct audevrc_suspend_ctl *ctl = + container_of(h, struct audevrc_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audevrc_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audevrc_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audevrc_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audevrc_debug_fops = { + .read = audevrc_debug_read, + .open = audevrc_debug_open, +}; +#endif + +static int audevrc_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + struct audevrc_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_evrc_" + 5]; +#endif + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance\n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_EVRC; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->phys)) { + MM_ERR("could not allocate write buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + } + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + rc = audmgr_open(&audio->audmgr); + if (rc) { + MM_ERR("audmgr open failed, freeing instance \ + 0x%08x\n", (int)audio); + goto err; + } + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_evrc, audio); + + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + goto err; + } + + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for EVRC session \ + 0x%08x on decoder: %d\n", (int)audio, audio->dec_id); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + msm_adsp_put(audio->audplay); + goto err; + } + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x3FFF; + + audevrc_flush(audio); + + file->private_data = audio; + audio->opened = 1; +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_evrc_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audevrc_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audevrc_resume; + audio->suspend_ctl.node.suspend = audevrc_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDEVRC_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audevrc_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_evrc_fops = { + .owner = THIS_MODULE, + .open = audevrc_open, + .release = audevrc_release, + .read = audevrc_read, + .write = audevrc_write, + .unlocked_ioctl = audevrc_ioctl, + .fsync = audevrc_fsync, +}; + +struct miscdevice audio_evrc_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_evrc", + .fops = &audio_evrc_fops, +}; + +static int __init audevrc_init(void) +{ + return misc_register(&audio_evrc_misc); + +} + +static void __exit audevrc_exit(void) +{ + misc_deregister(&audio_evrc_misc); +} + +module_init(audevrc_init); +module_exit(audevrc_exit); + +MODULE_DESCRIPTION("MSM EVRC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5/audio_evrc_in.c b/arch/arm/mach-msm/qdsp5/audio_evrc_in.c new file mode 100644 index 00000000000..a640b28c49c --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_evrc_in.c @@ -0,0 +1,1375 @@ +/* arch/arm/mach-msm/qdsp5/audio_evrc_in.c + * + * evrc audio input device + * + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This code is based in part on arch/arm/mach-msm/qdsp5v2/audio_evrc_in.c, + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include + +#define FRAME_HEADER_SIZE 8 /* 8 bytes frame header */ +#define NT_FRAME_HEADER_SIZE 24 /* 24 bytes frame header */ +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM 8 +#define EVRC_FRAME_SIZE 36 /* 36 bytes data */ +/*Tunnel mode : 36 bytes data + 8 byte header*/ +#define FRAME_SIZE (EVRC_FRAME_SIZE + FRAME_HEADER_SIZE) + /* 36 bytes data + 24 meta field*/ +#define NT_FRAME_SIZE (EVRC_FRAME_SIZE + NT_FRAME_HEADER_SIZE) +#define DMASZ (FRAME_SIZE * FRAME_NUM) +#define NT_DMASZ (NT_FRAME_SIZE * FRAME_NUM) +#define OUT_FRAME_NUM 2 +#define OUT_BUFFER_SIZE (4 * 1024 + NT_FRAME_HEADER_SIZE) +#define BUFFER_SIZE (OUT_BUFFER_SIZE * OUT_FRAME_NUM) + +#define AUDPREPROC_EVRC_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer*/ +#define AUDPREPROC_EVRC_EOS_FLG_MASK 0x01 +#define AUDPREPROC_EVRC_EOS_NONE 0x0 /* No EOS detected */ +#define AUDPREPROC_EVRC_EOS_SET 0x1 /* EOS set in meta field */ + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; + uint32_t used; + uint32_t mfield_sz; +}; + +struct audio_evrc_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + atomic_t in_samples; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + wait_queue_head_t wait_enable; + /*write section*/ + struct buffer out[OUT_FRAME_NUM]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + uint32_t out_count; + + struct mutex write_lock; + wait_queue_head_t write_wait; + int32_t out_phys; /* physical address of write buffer */ + char *out_data; + int mfield; /* meta field embedded in data */ + int wflush; /*write flush */ + int rflush; /*read flush*/ + int out_frame_cnt; + + struct msm_adsp_module *audrec; + struct msm_adsp_module *audpre; + + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* Frame size (36 bytes) */ + uint32_t enc_type; /* 11 for EVRC */ + uint32_t mode; /* T or NT Mode*/ + + struct msm_audio_evrc_enc_config cfg; + + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + uint32_t eos_ack; + uint32_t flush_ack; + + const char *module_name; + unsigned queue_ids; + uint16_t enc_id; /* Session Id */ + + unsigned short samp_rate_index; + uint32_t audrec_obj_idx ; + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ +}; + +struct audio_frame { + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + unsigned char raw_bitstream[]; +} __packed; + +struct audio_frame_nt { + uint16_t metadata_len; + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + uint16_t reserved; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; + unsigned char raw_bitstream[]; /* samples */ +} __packed; + +struct evrc_encoded_meta_out { + uint16_t metadata_len; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; +}; + +/* Audrec Queue command sent macro's */ +#define audio_send_queue_pre(audio, cmd, len) \ + msm_adsp_write(audio->audpre, QDSP_uPAudPreProcCmdQueue, cmd, len) + +#define audio_send_queue_recbs(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_ids & 0xFFFF0000) >> 16),\ + cmd, len) +#define audio_send_queue_rec(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_ids & 0x0000FFFF),\ + cmd, len) + +static int audevrc_in_dsp_enable(struct audio_evrc_in *audio, int enable); +static int audevrc_in_encparam_config(struct audio_evrc_in *audio); +static int audevrc_in_encmem_config(struct audio_evrc_in *audio); +static int audevrc_in_dsp_read_buffer(struct audio_evrc_in *audio, + uint32_t read_cnt); +static void audevrc_in_flush(struct audio_evrc_in *audio); + +static void audevrc_in_get_dsp_frames(struct audio_evrc_in *audio); +static int audpcm_config(struct audio_evrc_in *audio); +static void audevrc_out_flush(struct audio_evrc_in *audio); +static int audevrc_in_routing_mode_config(struct audio_evrc_in *audio); +static void audrec_pcm_send_data(struct audio_evrc_in *audio, unsigned needed); +static void audevrc_nt_in_get_dsp_frames(struct audio_evrc_in *audio); +static void audevrc_in_flush(struct audio_evrc_in *audio); + +static unsigned convert_samp_index(unsigned index) +{ + switch (index) { + case RPC_AUD_DEF_SAMPLE_RATE_48000: return 48000; + case RPC_AUD_DEF_SAMPLE_RATE_44100: return 44100; + case RPC_AUD_DEF_SAMPLE_RATE_32000: return 32000; + case RPC_AUD_DEF_SAMPLE_RATE_24000: return 24000; + case RPC_AUD_DEF_SAMPLE_RATE_22050: return 22050; + case RPC_AUD_DEF_SAMPLE_RATE_16000: return 16000; + case RPC_AUD_DEF_SAMPLE_RATE_12000: return 12000; + case RPC_AUD_DEF_SAMPLE_RATE_11025: return 11025; + case RPC_AUD_DEF_SAMPLE_RATE_8000: return 8000; + default: return 11025; + } +} + +/* must be called with audio->lock held */ +static int audevrc_in_enable(struct audio_evrc_in *audio) +{ + struct audmgr_config cfg; + int rc; + + if (audio->enabled) + return 0; + + cfg.tx_rate = audio->samp_rate; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; + cfg.codec = RPC_AUD_DEF_CODEC_EVRC; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + + if (msm_adsp_enable(audio->audpre)) { + audmgr_disable(&audio->audmgr); + MM_ERR("msm_adsp_enable(audpre) failed\n"); + return -ENODEV; + } + } + if (msm_adsp_enable(audio->audrec)) { + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + audmgr_disable(&audio->audmgr); + msm_adsp_disable(audio->audpre); + } + MM_ERR("msm_adsp_enable(audrec) failed\n"); + return -ENODEV; + } + + audio->enabled = 1; + audevrc_in_dsp_enable(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audevrc_in_disable(struct audio_evrc_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + + audevrc_in_dsp_enable(audio, 0); + + wake_up(&audio->wait); + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + msm_adsp_disable(audio->audrec); + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + msm_adsp_disable(audio->audpre); + audmgr_disable(&audio->audmgr); + } + } + return 0; +} + +/* ------------------- dsp --------------------- */ +static void audpre_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + uint16_t msg[2]; + getevent(msg, sizeof(msg)); + + switch (id) { + case AUDPREPROC_MSG_CMD_CFG_DONE_MSG: + MM_DBG("type %d, status_flag %d\n", msg[0], msg[1]); + break; + case AUDPREPROC_MSG_ERROR_MSG_ID: + MM_ERR("err_index %d\n", msg[0]); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audpreproctask)\n"); + break; + default: + MM_ERR("unknown event %d\n", id); + } +} + +static void audevrc_in_get_dsp_frames(struct audio_evrc_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - + sizeof(*frame)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = frame->frame_length; + + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) { + MM_ERR("Error! not able to keep up the read\n"); + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + MM_ERR("in_count = %d\n", audio->in_count); + } else + audio->in_count++; + + audevrc_in_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +static void audevrc_nt_in_get_dsp_frames(struct audio_evrc_in *audio) +{ + struct audio_frame_nt *nt_frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + nt_frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(struct audio_frame_nt)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = nt_frame->frame_length; + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + MM_DBG("Error! not able to keep up the read\n"); + else + audio->in_count++; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + wake_up(&audio->wait); +} + +static int audrec_pcm_buffer_ptr_refresh(struct audio_evrc_in *audio, + unsigned idx, unsigned len) +{ + struct audrec_cmd_pcm_buffer_ptr_refresh_arm_enc cmd; + + if (len == NT_FRAME_HEADER_SIZE) + len = len / 2; + else + len = (len + NT_FRAME_HEADER_SIZE) / 2; + MM_DBG("len = %d\n", len); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_BUFFER_PTR_REFRESH_ARM_TO_ENC; + cmd.num_buffers = 1; + if (cmd.num_buffers == 1) { + cmd.buf_address_length[0] = (audio->out[idx].addr & + 0xffff0000) >> 16; + cmd.buf_address_length[1] = (audio->out[idx].addr & + 0x0000ffff); + cmd.buf_address_length[2] = (len & 0xffff0000) >> 16; + cmd.buf_address_length[3] = (len & 0x0000ffff); + } + audio->out_frame_cnt++; + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audpcm_config(struct audio_evrc_in *audio) +{ + struct audrec_cmd_pcm_cfg_arm_to_enc cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_CFG_ARM_TO_ENC; + cmd.config_update_flag = AUDREC_PCM_CONFIG_UPDATE_FLAG_ENABLE; + cmd.enable_flag = AUDREC_ENABLE_FLAG_VALUE; + cmd.sampling_freq = convert_samp_index(audio->samp_rate); + if (!audio->channel_mode) + cmd.channels = 1; + else + cmd.channels = 2; + cmd.frequency_of_intimation = 1; + cmd.max_number_of_buffers = OUT_FRAME_NUM; + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + + +static int audevrc_in_routing_mode_config(struct audio_evrc_in *audio) +{ + struct audrec_cmd_routing_mode cmd; + + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_ROUTING_MODE; + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + cmd.routing_mode = 1; + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_evrc_in *audio = NULL; + + if (data) + audio = data; + else { + MM_ERR("invalid data for event %x\n", id); + return; + } + + switch (id) { + case AUDREC_MSG_CMD_CFG_DONE_MSG: { + struct audrec_msg_cmd_cfg_done_msg cmd_cfg_done_msg; + getevent(&cmd_cfg_done_msg, AUDREC_MSG_CMD_CFG_DONE_MSG_LEN); + if (cmd_cfg_done_msg.audrec_enc_type & \ + AUDREC_MSG_CFG_DONE_ENC_ENA) { + audio->audrec_obj_idx = cmd_cfg_done_msg.audrec_obj_idx; + MM_DBG("CFG ENABLED\n"); + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + MM_DBG("routing command\n"); + audevrc_in_routing_mode_config(audio); + } else { + audevrc_in_encmem_config(audio); + } + } else { + MM_DBG("CFG SLEEP\n"); + audio->running = 0; + wake_up(&audio->wait_enable); + } + break; + } + case AUDREC_MSG_CMD_ROUTING_MODE_DONE_MSG: { + struct audrec_msg_cmd_routing_mode_done_msg \ + routing_msg; + getevent(&routing_msg, AUDREC_MSG_CMD_ROUTING_MODE_DONE_MSG); + MM_DBG("AUDREC_MSG_CMD_ROUTING_MODE_DONE_MSG"); + if (routing_msg.configuration == 0) { + MM_ERR("routing configuration failed\n"); + audio->running = 0; + wake_up(&audio->wait_enable); + } else + audevrc_in_encmem_config(audio); + break; + } + case AUDREC_MSG_CMD_AREC_MEM_CFG_DONE_MSG: { + MM_DBG("AREC_MEM_CFG_DONE_MSG\n"); + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + audevrc_in_encparam_config(audio); + else + audpcm_config(audio); + break; + } + case AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG: { + MM_DBG("AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG"); + audevrc_in_encparam_config(audio); + break; + } + case AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG: { + MM_DBG("AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG\n"); + audio->running = 1; + wake_up(&audio->wait_enable); + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + audrec_pcm_send_data(audio, 1); + break; + } + case AUDREC_CMD_PCM_BUFFER_PTR_UPDATE_ARM_TO_ENC_MSG: { + MM_DBG("ptr_update recieved from DSP\n"); + audrec_pcm_send_data(audio, 1); + break; + } + case AUDREC_MSG_NO_EXT_PKT_AVAILABLE_MSG: { + struct audrec_msg_no_ext_pkt_avail_msg err_msg; + getevent(&err_msg, AUDREC_MSG_NO_EXT_PKT_AVAILABLE_MSG_LEN); + MM_DBG("NO_EXT_PKT_AVAILABLE_MSG %x\n",\ + err_msg.audrec_err_id); + break; + } + case AUDREC_MSG_PACKET_READY_MSG: { + struct audrec_msg_packet_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_MSG_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt msw %d \ + write cnt lsw %d read cnt msw %d read cnt lsw %d \n",\ + pkt_ready_msg.pkt_counter_msw, \ + pkt_ready_msg.pkt_counter_lsw, \ + pkt_ready_msg.pkt_read_cnt_msw, \ + pkt_ready_msg.pkt_read_cnt_lsw); + + audevrc_in_get_dsp_frames(audio); + break; + } + case AUDREC_UP_NT_PACKET_READY_MSG: { + struct audrec_up_nt_packet_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_NT_PACKET_READY_MSG_LEN); + MM_DBG("UP_NT_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packetwrite_cnt_lsw, \ + pkt_ready_msg.audrec_packetwrite_cnt_msw, \ + pkt_ready_msg.audrec_upprev_readcount_lsw, \ + pkt_ready_msg.audrec_upprev_readcount_msw); + + audevrc_nt_in_get_dsp_frames(audio); + break; + } + case AUDREC_CMD_FLUSH_DONE_MSG: { + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 1; + wake_up(&audio->write_wait); + MM_DBG("flush ack recieved\n"); + break; + } + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module \ + enable/disable(audrectask)\n"); + break; + default: + MM_ERR("unknown event %d\n", id); + } +} + +static struct msm_adsp_ops audpre_evrc_adsp_ops = { + .event = audpre_dsp_event, +}; + +static struct msm_adsp_ops audrec_evrc_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int audevrc_in_dsp_enable(struct audio_evrc_in *audio, int enable) +{ + struct audrec_cmd_enc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_ENC_CFG; + cmd.audrec_enc_type = (audio->enc_type & 0xFF) | + (enable ? AUDREC_CMD_ENC_ENA : AUDREC_CMD_ENC_DIS); + /* Don't care */ + cmd.audrec_obj_idx = audio->audrec_obj_idx; + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audevrc_in_encmem_config(struct audio_evrc_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + int n; + int header_len = 0; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.cmd_id = AUDREC_CMD_ARECMEM_CFG; + cmd.audrec_obj_idx = audio->audrec_obj_idx; + /* Rate at which packet complete message comes */ + cmd.audrec_up_pkt_intm_cnt = 1; + cmd.audrec_extpkt_buffer_msw = audio->phys >> 16; + cmd.audrec_extpkt_buffer_lsw = audio->phys; + /* Max Buffer no available for frames */ + cmd.audrec_extpkt_buffer_num = FRAME_NUM; + + /* prepare buffer pointers: + * T:36 bytes evrc packet + 4 halfword header + * NT:36 bytes evrc packet + 12 halfword header + */ + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + header_len = FRAME_HEADER_SIZE/2; + else + header_len = NT_FRAME_HEADER_SIZE/2; + + for (n = 0; n < FRAME_NUM; n++) { + audio->in[n].data = data + header_len; + data += (EVRC_FRAME_SIZE/2) + header_len; + MM_DBG("0x%8x\n", (int)(audio->in[n].data - header_len*2)); + } + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audevrc_in_encparam_config(struct audio_evrc_in *audio) +{ + struct audrec_cmd_arecparam_evrc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDREC_CMD_ARECPARAM_CFG; + cmd.common.audrec_obj_idx = audio->audrec_obj_idx; + cmd.enc_min_rate = audio->cfg.min_bit_rate; + cmd.enc_max_rate = audio->cfg.max_bit_rate; + cmd.rate_modulation_cmd = 0; /* Default set to 0 */ + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audevrc_flush_command(struct audio_evrc_in *audio) +{ + struct audrec_cmd_flush cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_FLUSH; + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audevrc_in_dsp_read_buffer(struct audio_evrc_in *audio, + uint32_t read_cnt) +{ + audrec_cmd_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PACKET_EXT_PTR; + cmd.type = audio->audrec_obj_idx; + cmd.curr_rec_count_msw = read_cnt >> 16; + cmd.curr_rec_count_lsw = read_cnt; + + return audio_send_queue_recbs(audio, &cmd, sizeof(cmd)); +} + +/* ------------------- device --------------------- */ + +static void audevrc_ioport_reset(struct audio_evrc_in *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audevrc_in_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audevrc_out_flush(audio); + mutex_unlock(&audio->read_lock); +} + +static void audevrc_in_flush(struct audio_evrc_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + audio->eos_ack = 0; + for (i = FRAME_NUM-1; i >= 0; i--) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } + MM_DBG("in_bytes %d\n", atomic_read(&audio->in_bytes)); + MM_DBG("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); +} + +static void audevrc_out_flush(struct audio_evrc_in *audio) +{ + int i; + + audio->out_head = 0; + audio->out_tail = 0; + audio->out_count = 0; + for (i = OUT_FRAME_NUM-1; i >= 0; i--) { + audio->out[i].size = 0; + audio->out[i].read = 0; + audio->out[i].used = 0; + } +} + +/* ------------------- device --------------------- */ +static long audevrc_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_evrc_in *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + rc = audevrc_in_enable(audio); + if (!rc) { + rc = + wait_event_interruptible_timeout(audio->wait_enable, + audio->running != 0, 1*HZ); + MM_DBG("state %d rc = %d\n", audio->running, rc); + + if (audio->running == 0) + rc = -ENODEV; + else + rc = 0; + } + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + rc = audevrc_in_disable(audio); + audio->stopped = 1; + break; + } + case AUDIO_FLUSH: { + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audevrc_ioport_reset(audio); + if (audio->running) { + audevrc_flush_command(audio); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = OUT_BUFFER_SIZE; + cfg.buffer_count = OUT_FRAME_NUM; + cfg.sample_rate = convert_samp_index(audio->samp_rate); + cfg.channel_count = 1; + cfg.type = 0; + cfg.unused[0] = 0; + cfg.unused[1] = 0; + cfg.unused[2] = 0; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + /* Allow only single frame */ + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if (cfg.buffer_size != (FRAME_SIZE - 8)) + rc = -EINVAL; + break; + } else { + if (cfg.buffer_size != (EVRC_FRAME_SIZE + 14)) + rc = -EINVAL; + break; + } + audio->buffer_size = cfg.buffer_size; + break; + } + case AUDIO_GET_EVRC_ENC_CONFIG: { + if (copy_to_user((void *) arg, &audio->cfg, sizeof(audio->cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_EVRC_ENC_CONFIG: { + struct msm_audio_evrc_enc_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + MM_DBG("0X%8x, 0x%8x, 0x%8x\n", cfg.min_bit_rate, + cfg.max_bit_rate, cfg.cdma_rate); + if (cfg.min_bit_rate > CDMA_RATE_FULL || \ + cfg.min_bit_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid min bitrate\n"); + rc = -EFAULT; + break; + } + if (cfg.max_bit_rate > CDMA_RATE_FULL || \ + cfg.max_bit_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid max bitrate\n"); + rc = -EFAULT; + break; + } + /* Recording Does not support Erase and Blank */ + if (cfg.cdma_rate > CDMA_RATE_FULL || + cfg.cdma_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid qcelp cdma rate\n"); + rc = -EFAULT; + break; + } + memcpy(&audio->cfg, &cfg, sizeof(cfg)); + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audevrc_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_evrc_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + struct evrc_encoded_meta_out meta_field; + struct audio_frame_nt *nt_frame; + MM_DBG("count = %d\n", count); + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped || + audio->rflush); + if (rc < 0) + break; + + if (audio->rflush) { + rc = -EBUSY; + break; + } + if (audio->stopped && !audio->in_count) { + MM_DBG("Driver in stop state, No more buffer to read"); + rc = 0;/* End of File */ + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + nt_frame = (struct audio_frame_nt *)(data - + sizeof(struct audio_frame_nt)); + memcpy((char *)&meta_field.time_stamp_dword_lsw, + (char *)&nt_frame->time_stamp_dword_lsw, + (sizeof(struct evrc_encoded_meta_out) - \ + sizeof(uint16_t))); + meta_field.metadata_len = + sizeof(struct evrc_encoded_meta_out); + if (copy_to_user((char *)start, (char *)&meta_field, + sizeof(struct evrc_encoded_meta_out))) { + rc = -EFAULT; + break; + } + if (nt_frame->nflag_lsw & 0x0001) { + MM_ERR("recieved EOS in read call\n"); + audio->eos_ack = 1; + } + buf += sizeof(struct evrc_encoded_meta_out); + count -= sizeof(struct evrc_encoded_meta_out); + } + if (count >= size) { + /* order the reads on the buffer */ + dma_coherent_post_ops(); + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is + * invalid and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + if ((audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL)) { + if (!audio->eos_ack) { + MM_DBG("sending read ptr command \ + %d %d\n", + audio->dsp_cnt, + audio->in_tail); + audevrc_in_dsp_read_buffer(audio, + audio->dsp_cnt++); + } + } + } else { + MM_ERR("short read\n"); + break; + } + break; + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static void audrec_pcm_send_data(struct audio_evrc_in *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + MM_DBG("\n"); + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + audrec_pcm_buffer_ptr_refresh(audio, + audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static int audevrc_in_fsync(struct file *file, int datasync) + +{ + struct audio_evrc_in *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (!audio->running || (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + audio->wflush); + MM_DBG("waked on by some event audio->wflush = %d\n", audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; + +} + +int audrec_evrc_process_eos(struct audio_evrc_in *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + int rc = 0; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + MM_DBG("copying meta_out frame->used = %d\n", frame->used); + audrec_pcm_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audevrc_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_evrc_in *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + char *cpy_ptr; + int rc = 0, eos_condition = AUDPREPROC_EVRC_EOS_NONE; + unsigned short mfield_size = 0; + int write_count = 0; + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + if (audio->mode != MSM_AUD_ENC_MODE_NONTUNNEL) + return -EINVAL; + + mutex_lock(&audio->write_lock); + frame = audio->out + audio->out_head; + /* if supplied count is more than driver buffer size + * then only copy driver buffer size + */ + if (count > frame->size) + count = frame->size; + + write_count = count; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto error; + + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto error; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + goto error; + } else if (mfield_size > count) { + rc = -EINVAL; + goto error; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + goto error; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDPREPROC_EVRC_EOS_FLG_OFFSET] & + AUDPREPROC_EVRC_EOS_FLG_MASK) { + eos_condition = AUDPREPROC_EVRC_EOS_SET; + MM_DBG("EOS SET\n"); + if (mfield_size == count) { + buf += mfield_size; + eos_condition = 0; + goto exit; + } else + cpy_ptr[AUDPREPROC_EVRC_EOS_FLG_OFFSET] &= + ~AUDPREPROC_EVRC_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + MM_DBG("copying the stream count = %d\n", count); + if (copy_from_user(cpy_ptr, buf, count)) { + rc = -EFAULT; + goto error; + } +exit: + frame->used = count; + audio->out_head ^= 1; + if (!audio->flush_ack) + audrec_pcm_send_data(audio, 0); + else { + audrec_pcm_send_data(audio, 1); + audio->flush_ack = 0; + } + if (eos_condition == AUDPREPROC_EVRC_EOS_SET) + rc = audrec_evrc_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + return write_count; +error: + mutex_unlock(&audio->write_lock); + return rc; +} + +static int audevrc_in_release(struct inode *inode, struct file *file) +{ + struct audio_evrc_in *audio = file->private_data; + + mutex_lock(&audio->lock); + audevrc_in_disable(audio); + audevrc_in_flush(audio); + msm_adsp_put(audio->audrec); + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + msm_adsp_put(audio->audpre); + + audpreproc_aenc_free(audio->enc_id); + audio->audrec = NULL; + audio->audpre = NULL; + audio->opened = 0; + if ((audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) && \ + (audio->out_data)) { + free_contiguous_memory(audio->out_data); + audio->out_data = NULL; + } + if (audio->data) { + free_contiguous_memory(audio->data); + audio->data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +static struct audio_evrc_in the_audio_evrc_in; + +static int audevrc_in_open(struct inode *inode, struct file *file) +{ + struct audio_evrc_in *audio = &the_audio_evrc_in; + int rc; + int encid; + int dma_size = 0; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_NONTUNNEL; + dma_size = NT_DMASZ; + MM_DBG("Opened for non tunnel mode encoding\n"); + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_TUNNEL; + dma_size = DMASZ; + MM_DBG("Opened for tunnel mode encoding\n"); + } else { + MM_ERR("Invalid mode\n"); + rc = -EACCES; + goto done; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->samp_rate = RPC_AUD_DEF_SAMPLE_RATE_8000, + audio->samp_rate_index = AUDREC_CMD_SAMP_RATE_INDX_8000; + audio->channel_mode = AUDREC_CMD_STEREO_MODE_MONO; + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + audio->buffer_size = (EVRC_FRAME_SIZE + 14); + else + audio->buffer_size = EVRC_FRAME_SIZE; + audio->enc_type = AUDREC_CMD_TYPE_0_INDEX_EVRC | audio->mode; + + audio->cfg.cdma_rate = CDMA_RATE_FULL; + audio->cfg.min_bit_rate = CDMA_RATE_FULL; + audio->cfg.max_bit_rate = CDMA_RATE_FULL; + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + rc = audmgr_open(&audio->audmgr); + if (rc) + goto done; + } + + encid = audpreproc_aenc_alloc(audio->enc_type, &audio->module_name, + &audio->queue_ids); + if (encid < 0) { + MM_ERR("No free encoder available\n"); + rc = -ENODEV; + goto done; + } + audio->enc_id = encid; + + rc = msm_adsp_get(audio->module_name, &audio->audrec, + &audrec_evrc_adsp_ops, audio); + if (rc) { + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + rc = msm_adsp_get("AUDPREPROCTASK", &audio->audpre, + &audpre_evrc_adsp_ops, audio); + if (rc) { + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + goto done; + } + } + + audio->dsp_cnt = 0; + audio->stopped = 0; + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 0; + + audevrc_in_flush(audio); + audevrc_out_flush(audio); + + audio->data = allocate_contiguous_memory(dma_size, MEMTYPE_EBI1, + SZ_4K, 0); + if (!audio->data) { + MM_ERR("could not allocate read buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->phys = memory_pool_node_paddr(audio->data); + if (!audio->phys) { + MM_ERR("could not get physical address\n"); + rc = -ENOMEM; + free_contiguous_memory(audio->data); + goto evt_error; + } + MM_DBG("read buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + } + audio->out_data = NULL; + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + audio->out_data = allocate_contiguous_memory(BUFFER_SIZE, + MEMTYPE_EBI1, + SZ_4K, 0); + if (!audio->out_data) { + MM_ERR("could not allocate read buffers\n"); + rc = -ENOMEM; + free_contiguous_memory(audio->data); + goto evt_error; + } else { + audio->out_phys = memory_pool_node_paddr( + audio->out_data); + if (!audio->out_phys) { + MM_ERR("could not get physical address\n"); + rc = -ENOMEM; + free_contiguous_memory(audio->data); + free_contiguous_memory(audio->out_data); + goto evt_error; + } + MM_DBG("write buf:phy addr 0x%08x kernel addr 0x%08x\n", + audio->out_phys, (int)audio->out_data); + } + + /* Initialize buffer */ + audio->out[0].data = audio->out_data + 0; + audio->out[0].addr = audio->out_phys + 0; + audio->out[0].size = OUT_BUFFER_SIZE; + + audio->out[1].data = audio->out_data + OUT_BUFFER_SIZE; + audio->out[1].addr = audio->out_phys + OUT_BUFFER_SIZE; + audio->out[1].size = OUT_BUFFER_SIZE; + + MM_DBG("audio->out[0].data = %d audio->out[1].data = %d", + (unsigned int)audio->out[0].data, + (unsigned int)audio->out[1].data); + audio->mfield = NT_FRAME_HEADER_SIZE; + audio->out_frame_cnt++; + } + file->private_data = audio; + audio->opened = 1; + +done: + mutex_unlock(&audio->lock); + return rc; +evt_error: + msm_adsp_put(audio->audrec); + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + msm_adsp_put(audio->audpre); + + audpreproc_aenc_free(audio->enc_id); + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_evrc_in_fops = { + .owner = THIS_MODULE, + .open = audevrc_in_open, + .release = audevrc_in_release, + .read = audevrc_in_read, + .write = audevrc_in_write, + .fsync = audevrc_in_fsync, + .unlocked_ioctl = audevrc_in_ioctl, +}; + +static struct miscdevice audevrc_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_evrc_in", + .fops = &audio_evrc_in_fops, +}; + +static int __init audevrc_in_init(void) +{ + mutex_init(&the_audio_evrc_in.lock); + mutex_init(&the_audio_evrc_in.read_lock); + spin_lock_init(&the_audio_evrc_in.dsp_lock); + init_waitqueue_head(&the_audio_evrc_in.wait); + init_waitqueue_head(&the_audio_evrc_in.wait_enable); + mutex_init(&the_audio_evrc_in.write_lock); + init_waitqueue_head(&the_audio_evrc_in.write_wait); + return misc_register(&audevrc_in_misc); +} +device_initcall(audevrc_in_init); diff --git a/arch/arm/mach-msm/qdsp5/audio_fm.c b/arch/arm/mach-msm/qdsp5/audio_fm.c new file mode 100644 index 00000000000..2ab7cadfdeb --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_fm.c @@ -0,0 +1,169 @@ +/* arch/arm/mach-msm/qdsp5/audio_fm.c + * + * pcm audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2011, Code Aurora Forum. 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 +#include +#include +#include +#include + +#include "audmgr.h" + +struct audio { + struct mutex lock; + int opened; + int enabled; + int running; + struct audmgr audmgr; + uint16_t volume; +}; + +static struct audio fm_audio; + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_HOST_PCM; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_VOICE; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + + audio->enabled = 1; + return rc; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audmgr_disable(&audio->audmgr); + } + return 0; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + + MM_DBG("cmd %d", cmd); + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->running = 0; + audio->enabled = 0; + break; + + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_DBG("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + audio_disable(audio); + audio->running = 0; + audio->enabled = 0; + audio->opened = 0; + mutex_unlock(&audio->lock); + return 0; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = &fm_audio; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + mutex_lock(&audio->lock); + + if (audio->opened) { + MM_ERR("busy\n"); + rc = -EBUSY; + goto done; + } + + rc = audmgr_open(&audio->audmgr); + + if (rc) { + MM_ERR("%s: failed to register listnet\n", __func__); + goto done; + } + + file->private_data = audio; + audio->opened = 1; + +done: + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_fm_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .unlocked_ioctl = audio_ioctl, +}; + +struct miscdevice audio_fm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_fm", + .fops = &audio_fm_fops, +}; + +static int __init audio_init(void) +{ + struct audio *audio = &fm_audio; + + mutex_init(&audio->lock); + return misc_register(&audio_fm_misc); +} + +device_initcall(audio_init); + +MODULE_DESCRIPTION("MSM FM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5/audio_in.c b/arch/arm/mach-msm/qdsp5/audio_in.c new file mode 100644 index 00000000000..6fc5d6bf299 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_in.c @@ -0,0 +1,996 @@ +/* arch/arm/mach-msm/qdsp5/audio_in.c + * + * pcm audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include "audmgr.h" + +#include +#include +#include +#include +#include + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (8) +#define FRAME_SIZE (2052 * 2) +#define MONO_DATA_SIZE (2048) +#define STEREO_DATA_SIZE (MONO_DATA_SIZE * 2) +#define DMASZ (FRAME_SIZE * FRAME_NUM) + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; +}; + +struct audio_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + + struct msm_adsp_module *audpre; + struct msm_adsp_module *audrec; + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* 2048 for mono, 4096 for stereo */ + uint32_t type; /* 0 for PCM ,1 for AAC */ + uint32_t bit_rate; /* bit rate for AAC */ + uint32_t record_quality; /* record quality (bits/sample/channel) + for AAC*/ + uint32_t buffer_cfg_ioctl; /* to allow any one of buffer set ioctl */ + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + unsigned short samp_rate_index; + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + + /* audpre settings */ + int tx_agc_enable; + audpreproc_cmd_cfg_agc_params tx_agc_cfg; + int ns_enable; + audpreproc_cmd_cfg_ns_params ns_cfg; + /* For different sample rate, the coeff might be different. * + * All the coeff should be passed from user space */ + int iir_enable; + audpreproc_cmd_cfg_iir_tuning_filter_params iir_cfg; +}; + +static int audio_in_dsp_enable(struct audio_in *audio, int enable); +static int audio_in_encoder_config(struct audio_in *audio); +static int audio_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt); +static void audio_flush(struct audio_in *audio); +static int audio_dsp_set_tx_agc(struct audio_in *audio); +static int audio_dsp_set_ns(struct audio_in *audio); +static int audio_dsp_set_iir(struct audio_in *audio); + +static unsigned convert_dsp_samp_index(unsigned index) +{ + switch (index) { + case 48000: return AUDREC_CMD_SAMP_RATE_INDX_48000; + case 44100: return AUDREC_CMD_SAMP_RATE_INDX_44100; + case 32000: return AUDREC_CMD_SAMP_RATE_INDX_32000; + case 24000: return AUDREC_CMD_SAMP_RATE_INDX_24000; + case 22050: return AUDREC_CMD_SAMP_RATE_INDX_22050; + case 16000: return AUDREC_CMD_SAMP_RATE_INDX_16000; + case 12000: return AUDREC_CMD_SAMP_RATE_INDX_12000; + case 11025: return AUDREC_CMD_SAMP_RATE_INDX_11025; + case 8000: return AUDREC_CMD_SAMP_RATE_INDX_8000; + default: return AUDREC_CMD_SAMP_RATE_INDX_11025; + } +} + +static unsigned convert_samp_rate(unsigned hz) +{ + switch (hz) { + case 48000: return RPC_AUD_DEF_SAMPLE_RATE_48000; + case 44100: return RPC_AUD_DEF_SAMPLE_RATE_44100; + case 32000: return RPC_AUD_DEF_SAMPLE_RATE_32000; + case 24000: return RPC_AUD_DEF_SAMPLE_RATE_24000; + case 22050: return RPC_AUD_DEF_SAMPLE_RATE_22050; + case 16000: return RPC_AUD_DEF_SAMPLE_RATE_16000; + case 12000: return RPC_AUD_DEF_SAMPLE_RATE_12000; + case 11025: return RPC_AUD_DEF_SAMPLE_RATE_11025; + case 8000: return RPC_AUD_DEF_SAMPLE_RATE_8000; + default: return RPC_AUD_DEF_SAMPLE_RATE_11025; + } +} + +static unsigned convert_samp_index(unsigned index) +{ + switch (index) { + case RPC_AUD_DEF_SAMPLE_RATE_48000: return 48000; + case RPC_AUD_DEF_SAMPLE_RATE_44100: return 44100; + case RPC_AUD_DEF_SAMPLE_RATE_32000: return 32000; + case RPC_AUD_DEF_SAMPLE_RATE_24000: return 24000; + case RPC_AUD_DEF_SAMPLE_RATE_22050: return 22050; + case RPC_AUD_DEF_SAMPLE_RATE_16000: return 16000; + case RPC_AUD_DEF_SAMPLE_RATE_12000: return 12000; + case RPC_AUD_DEF_SAMPLE_RATE_11025: return 11025; + case RPC_AUD_DEF_SAMPLE_RATE_8000: return 8000; + default: return 11025; + } +} + +/* must be called with audio->lock held */ +static int audio_in_enable(struct audio_in *audio) +{ + struct audmgr_config cfg; + int rc; + + if (audio->enabled) + return 0; + + cfg.tx_rate = audio->samp_rate; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; + if (audio->type == AUDREC_CMD_TYPE_0_INDEX_WAV) + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + else + cfg.codec = RPC_AUD_DEF_CODEC_AAC; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + + if (msm_adsp_enable(audio->audpre)) { + MM_ERR("msm_adsp_enable(audpre) failed\n"); + return -ENODEV; + } + if (msm_adsp_enable(audio->audrec)) { + MM_ERR("msm_adsp_enable(audrec) failed\n"); + return -ENODEV; + } + + audio->enabled = 1; + audio_in_dsp_enable(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audio_in_disable(struct audio_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + + audio_in_dsp_enable(audio, 0); + + wake_up(&audio->wait); + + msm_adsp_disable(audio->audrec); + msm_adsp_disable(audio->audpre); + audmgr_disable(&audio->audmgr); + } + return 0; +} + +/* ------------------- dsp --------------------- */ +static void audpre_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + uint16_t msg[2]; + getevent(msg, sizeof(msg)); + + switch (id) { + case AUDPREPROC_MSG_CMD_CFG_DONE_MSG: + MM_INFO("type %d, status_flag %d\n", msg[0], msg[1]); + break; + case AUDPREPROC_MSG_ERROR_MSG_ID: + MM_INFO("err_index %d\n", msg[0]); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audpreproctask)\n"); + break; + default: + MM_ERR("unknown event %d\n", id); + } +} + +struct audio_frame { + uint16_t count_low; + uint16_t count_high; + uint16_t bytes; + uint16_t unknown; + unsigned char samples[]; +} __attribute__((packed)); + +static void audio_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + + /* XXX check for bogus frame size? */ + + frame = (void *) (((char *)audio->in[index].data) - + sizeof(*frame)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = frame->bytes; + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + else + audio->in_count++; + + audio_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_in *audio = data; + uint16_t msg[3]; + getevent(msg, sizeof(msg)); + + switch (id) { + case AUDREC_MSG_CMD_CFG_DONE_MSG: + if (msg[0] & AUDREC_MSG_CFG_DONE_TYPE_0_UPDATE) { + if (msg[0] & AUDREC_MSG_CFG_DONE_TYPE_0_ENA) { + MM_INFO("CFG ENABLED\n"); + audio_in_encoder_config(audio); + } else { + MM_INFO("CFG SLEEP\n"); + audio->running = 0; + audio->tx_agc_enable = 0; + audio->ns_enable = 0; + audio->iir_enable = 0; + } + } else { + MM_INFO("CMD_CFG_DONE %x\n", msg[0]); + } + break; + case AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG: { + MM_INFO("PARAM CFG DONE\n"); + audio->running = 1; + audio_dsp_set_tx_agc(audio); + audio_dsp_set_ns(audio); + audio_dsp_set_iir(audio); + break; + } + case AUDREC_MSG_FATAL_ERR_MSG: + MM_ERR("ERROR %x\n", msg[0]); + break; + case AUDREC_MSG_PACKET_READY_MSG: +/* REC_DBG("type %x, count %d", msg[0], (msg[1] | (msg[2] << 16))); */ + audio_in_get_dsp_frames(audio); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module \ + enable/disable(audrectask)\n"); + break; + default: + MM_ERR("unknown event %d\n", id); + } +} + +struct msm_adsp_ops audpre_adsp_ops = { + .event = audpre_dsp_event, +}; + +struct msm_adsp_ops audrec_adsp_ops = { + .event = audrec_dsp_event, +}; + + +#define audio_send_queue_pre(audio, cmd, len) \ + msm_adsp_write(audio->audpre, QDSP_uPAudPreProcCmdQueue, cmd, len) +#define audio_send_queue_recbs(audio, cmd, len) \ + msm_adsp_write(audio->audrec, QDSP_uPAudRecBitStreamQueue, cmd, len) +#define audio_send_queue_rec(audio, cmd, len) \ + msm_adsp_write(audio->audrec, \ + QDSP_uPAudRecCmdQueue, cmd, len) + +/* Convert Bit Rate to Record Quality field of DSP */ +static unsigned int bitrate_to_record_quality(unsigned int sample_rate, + unsigned int channel, unsigned int bit_rate) { + unsigned int temp; + + temp = sample_rate * channel; + MM_DBG(" sample rate * channel = %d \n", temp); + /* To represent in Q12 fixed format */ + temp = (bit_rate * 4096) / temp; + MM_DBG(" Record Quality = 0x%8x \n", temp); + return temp; +} + +static int audio_dsp_set_tx_agc(struct audio_in *audio) +{ + audpreproc_cmd_cfg_agc_params cmd; + + memset(&cmd, 0, sizeof(cmd)); + + audio->tx_agc_cfg.cmd_id = AUDPREPROC_CMD_CFG_AGC_PARAMS; + if (audio->tx_agc_enable) { + /* cmd.tx_agc_param_mask = 0xFE00 from sample code */ + audio->tx_agc_cfg.tx_agc_param_mask = + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_SLOPE) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_TH) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_EXP_SLOPE) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_EXP_TH) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_AIG_FLAG) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_STATIC_GAIN) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_TX_AGC_ENA_FLAG); + audio->tx_agc_cfg.tx_agc_enable_flag = + AUDPREPROC_CMD_TX_AGC_ENA_FLAG_ENA; + /* cmd.param_mask = 0xFFF0 from sample code */ + audio->tx_agc_cfg.param_mask = + (1 << AUDPREPROC_CMD_PARAM_MASK_RMS_TAY) | + (1 << AUDPREPROC_CMD_PARAM_MASK_RELEASEK) | + (1 << AUDPREPROC_CMD_PARAM_MASK_DELAY) | + (1 << AUDPREPROC_CMD_PARAM_MASK_ATTACKK) | + (1 << AUDPREPROC_CMD_PARAM_MASK_LEAKRATE_SLOW) | + (1 << AUDPREPROC_CMD_PARAM_MASK_LEAKRATE_FAST) | + (1 << AUDPREPROC_CMD_PARAM_MASK_AIG_RELEASEK) | + (1 << AUDPREPROC_CMD_PARAM_MASK_AIG_MIN) | + (1 << AUDPREPROC_CMD_PARAM_MASK_AIG_MAX) | + (1 << AUDPREPROC_CMD_PARAM_MASK_LEAK_UP) | + (1 << AUDPREPROC_CMD_PARAM_MASK_LEAK_DOWN) | + (1 << AUDPREPROC_CMD_PARAM_MASK_AIG_ATTACKK); + } else { + audio->tx_agc_cfg.tx_agc_param_mask = + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_TX_AGC_ENA_FLAG); + audio->tx_agc_cfg.tx_agc_enable_flag = + AUDPREPROC_CMD_TX_AGC_ENA_FLAG_DIS; + } + cmd = audio->tx_agc_cfg; + + return audio_send_queue_pre(audio, &cmd, sizeof(cmd)); +} + +static int audio_enable_tx_agc(struct audio_in *audio, int enable) +{ + if (audio->tx_agc_enable != enable) { + audio->tx_agc_enable = enable; + if (audio->running) + audio_dsp_set_tx_agc(audio); + } + return 0; +} + +static int audio_dsp_set_ns(struct audio_in *audio) +{ + audpreproc_cmd_cfg_ns_params cmd; + + memset(&cmd, 0, sizeof(cmd)); + + audio->ns_cfg.cmd_id = AUDPREPROC_CMD_CFG_NS_PARAMS; + + if (audio->ns_enable) { + /* cmd.ec_mode_new is fixed as 0x0064 when enable + * from sample code */ + audio->ns_cfg.ec_mode_new = + AUDPREPROC_CMD_EC_MODE_NEW_NS_ENA | + AUDPREPROC_CMD_EC_MODE_NEW_HB_ENA | + AUDPREPROC_CMD_EC_MODE_NEW_VA_ENA; + } else { + audio->ns_cfg.ec_mode_new = + AUDPREPROC_CMD_EC_MODE_NEW_NLMS_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_DES_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_NS_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_CNI_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_NLES_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_HB_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_VA_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_PCD_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_FEHI_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_NEHI_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_NLPP_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_FNE_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_PRENLMS_DIS; + } + cmd = audio->ns_cfg; + + return audio_send_queue_pre(audio, &cmd, sizeof(cmd)); +} + +static int audio_enable_ns(struct audio_in *audio, int enable) +{ + if (audio->ns_enable != enable) { + audio->ns_enable = enable; + if (audio->running) + audio_dsp_set_ns(audio); + } + return 0; +} + +static int audio_dsp_set_iir(struct audio_in *audio) +{ + audpreproc_cmd_cfg_iir_tuning_filter_params cmd; + + memset(&cmd, 0, sizeof(cmd)); + + audio->iir_cfg.cmd_id = AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS; + + if (audio->iir_enable) + /* cmd.active_flag is 0xFFFF from sample code but 0x0001 here */ + audio->iir_cfg.active_flag = AUDPREPROC_CMD_IIR_ACTIVE_FLAG_ENA; + else + audio->iir_cfg.active_flag = AUDPREPROC_CMD_IIR_ACTIVE_FLAG_DIS; + + cmd = audio->iir_cfg; + + return audio_send_queue_pre(audio, &cmd, sizeof(cmd)); +} + +static int audio_enable_iir(struct audio_in *audio, int enable) +{ + if (audio->iir_enable != enable) { + audio->iir_enable = enable; + if (audio->running) + audio_dsp_set_iir(audio); + } + return 0; +} + +static int audio_in_dsp_enable(struct audio_in *audio, int enable) +{ + audrec_cmd_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_CFG; + cmd.type_0 = enable ? AUDREC_CMD_TYPE_0_ENA : AUDREC_CMD_TYPE_0_DIS; + cmd.type_0 |= (AUDREC_CMD_TYPE_0_UPDATE | audio->type); + cmd.type_1 = 0; + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audio_in_encoder_config(struct audio_in *audio) +{ + audrec_cmd_arec0param_cfg cmd; + uint16_t *data = (void *) audio->data; + unsigned n; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_AREC0PARAM_CFG; + cmd.ptr_to_extpkt_buffer_msw = audio->phys >> 16; + cmd.ptr_to_extpkt_buffer_lsw = audio->phys; + cmd.buf_len = FRAME_NUM; /* Both WAV and AAC use 8 frames */ + cmd.samp_rate_index = audio->samp_rate_index; + cmd.stereo_mode = audio->channel_mode; /* 0 for mono, 1 for stereo */ + + /* cmd.rec_quality is based on user set bit rate / sample rate / + * channel + */ + cmd.rec_quality = audio->record_quality; + + /* prepare buffer pointers: + * Mono: 1024 samples + 4 halfword header + * Stereo: 2048 samples + 4 halfword header + * AAC + * Mono/Stere: 768 + 4 halfword header + */ + for (n = 0; n < FRAME_NUM; n++) { + audio->in[n].data = data + 4; + if (audio->type == AUDREC_CMD_TYPE_0_INDEX_WAV) + data += (4 + (audio->channel_mode ? 2048 : 1024)); + else if (audio->type == AUDREC_CMD_TYPE_0_INDEX_AAC) + data += (4 + 768); + } + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audio_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt) +{ + audrec_cmd_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PACKET_EXT_PTR; + /* Both WAV and AAC use AUDREC_CMD_TYPE_0 */ + cmd.type = AUDREC_CMD_TYPE_0; + cmd.curr_rec_count_msw = read_cnt >> 16; + cmd.curr_rec_count_lsw = read_cnt; + + return audio_send_queue_recbs(audio, &cmd, sizeof(cmd)); +} + +/* ------------------- device --------------------- */ + +static void audio_flush(struct audio_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + for (i = 0; i < FRAME_NUM; i++) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } +} + +static long audio_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_in *audio = file->private_data; + int rc; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + rc = audio_in_enable(audio); + break; + case AUDIO_STOP: + rc = audio_in_disable(audio); + audio->stopped = 1; + break; + case AUDIO_FLUSH: + if (audio->stopped) { + /* Make sure we're stopped and we wake any threads + * that might be blocked holding the read_lock. + * While audio->stopped read threads will always + * exit immediately. + */ + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audio_flush(audio); + mutex_unlock(&audio->read_lock); + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config cfg; + /* The below code is to make mutual exclusive between + * AUDIO_SET_CONFIG and AUDIO_SET_STREAM_CONFIG. + * Allow any one IOCTL. + */ + if (audio->buffer_cfg_ioctl == AUDIO_SET_STREAM_CONFIG) { + rc = -EINVAL; + break; + } + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.channel_count == 1) { + cfg.channel_count = AUDREC_CMD_STEREO_MODE_MONO; + } else if (cfg.channel_count == 2) { + cfg.channel_count = AUDREC_CMD_STEREO_MODE_STEREO; + } else { + rc = -EINVAL; + break; + } + + if (cfg.type == 0) { + cfg.type = AUDREC_CMD_TYPE_0_INDEX_WAV; + } else if (cfg.type == 1) { + cfg.type = AUDREC_CMD_TYPE_0_INDEX_AAC; + } else { + rc = -EINVAL; + break; + } + audio->samp_rate = convert_samp_rate(cfg.sample_rate); + audio->samp_rate_index = + convert_dsp_samp_index(cfg.sample_rate); + audio->channel_mode = cfg.channel_count; + audio->buffer_size = + audio->channel_mode ? STEREO_DATA_SIZE + : MONO_DATA_SIZE; + audio->type = cfg.type; + audio->buffer_cfg_ioctl = AUDIO_SET_CONFIG; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + cfg.sample_rate = convert_samp_index(audio->samp_rate); + if (audio->channel_mode == AUDREC_CMD_STEREO_MODE_MONO) + cfg.channel_count = 1; + else + cfg.channel_count = 2; + if (audio->type == AUDREC_CMD_TYPE_0_INDEX_WAV) + cfg.type = 0; + else + cfg.type = 1; + cfg.unused[0] = 0; + cfg.unused[1] = 0; + cfg.unused[2] = 0; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + /* The below code is to make mutual exclusive between + * AUDIO_SET_CONFIG and AUDIO_SET_STREAM_CONFIG. + * Allow any one IOCTL. + */ + if (audio->buffer_cfg_ioctl == AUDIO_SET_CONFIG) { + rc = -EINVAL; + break; + } + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } else + rc = 0; + audio->buffer_size = cfg.buffer_size; + /* The IOCTL is only of AAC, set the encoder as AAC */ + audio->type = 1; + audio->buffer_cfg_ioctl = AUDIO_SET_STREAM_CONFIG; + break; + } + case AUDIO_GET_AAC_ENC_CONFIG: { + struct msm_audio_aac_enc_config cfg; + if (audio->channel_mode == AUDREC_CMD_STEREO_MODE_MONO) + cfg.channels = 1; + else + cfg.channels = 2; + cfg.sample_rate = convert_samp_index(audio->samp_rate); + cfg.bit_rate = audio->bit_rate; + cfg.stream_format = AUDIO_AAC_FORMAT_RAW; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_AAC_ENC_CONFIG: { + struct msm_audio_aac_enc_config cfg; + unsigned int record_quality; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.stream_format != AUDIO_AAC_FORMAT_RAW) { + MM_ERR("unsupported AAC format\n"); + rc = -EINVAL; + break; + } + record_quality = bitrate_to_record_quality(cfg.sample_rate, + cfg.channels, cfg.bit_rate); + /* Range of Record Quality Supported by DSP, Q12 format */ + if ((record_quality < 0x800) || (record_quality > 0x4000)) { + MM_ERR("Unsupported bit rate \n"); + rc = -EINVAL; + break; + } + if (cfg.channels == 1) { + cfg.channels = AUDREC_CMD_STEREO_MODE_MONO; + } else if (cfg.channels == 2) { + cfg.channels = AUDREC_CMD_STEREO_MODE_STEREO; + } else { + rc = -EINVAL; + break; + } + audio->samp_rate = convert_samp_rate(cfg.sample_rate); + audio->samp_rate_index = + convert_dsp_samp_index(cfg.sample_rate); + audio->channel_mode = cfg.channels; + audio->bit_rate = cfg.bit_rate; + audio->record_quality = record_quality; + MM_DBG(" Record Quality = 0x%8x \n", audio->record_quality); + rc = 0; + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audio_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped); + if (rc < 0) + break; + + if (audio->stopped && !audio->in_count) { + rc = 0;/* End of File */ + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + if (count >= size) { + /* order the reads on the buffer */ + dma_coherent_post_ops(); + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is invalid and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + } else { + MM_ERR("short read\n"); + break; + } + if (audio->type == AUDREC_CMD_TYPE_0_INDEX_AAC) + break; /* AAC only read one frame */ + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static ssize_t audio_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + return -EINVAL; +} + +static int audio_in_release(struct inode *inode, struct file *file) +{ + struct audio_in *audio = file->private_data; + + mutex_lock(&audio->lock); + audio_in_disable(audio); + audio_flush(audio); + msm_adsp_put(audio->audrec); + msm_adsp_put(audio->audpre); + audio->audrec = NULL; + audio->audpre = NULL; + audio->opened = 0; + mutex_unlock(&audio->lock); + return 0; +} + +struct audio_in the_audio_in; + +static int audio_in_open(struct inode *inode, struct file *file) +{ + struct audio_in *audio = &the_audio_in; + int rc; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->samp_rate = RPC_AUD_DEF_SAMPLE_RATE_11025; + audio->samp_rate_index = AUDREC_CMD_SAMP_RATE_INDX_11025; + audio->channel_mode = AUDREC_CMD_STEREO_MODE_MONO; + audio->buffer_size = MONO_DATA_SIZE; + audio->type = AUDREC_CMD_TYPE_0_INDEX_WAV; + + /* For AAC, bit rate hard coded, default settings is + * sample rate (11025) x channel count (1) x recording quality (1.75) + * = 19293 bps */ + audio->bit_rate = 19293; + audio->record_quality = 0x1c00; + + rc = audmgr_open(&audio->audmgr); + if (rc) + goto done; + rc = msm_adsp_get("AUDPREPROCTASK", &audio->audpre, + &audpre_adsp_ops, audio); + if (rc) + goto done; + rc = msm_adsp_get("AUDRECTASK", &audio->audrec, + &audrec_adsp_ops, audio); + if (rc) + goto done; + + audio->dsp_cnt = 0; + audio->stopped = 0; + audio->buffer_cfg_ioctl = 0; /* No valid ioctl set */ + + audio_flush(audio); + + file->private_data = audio; + audio->opened = 1; + rc = 0; +done: + mutex_unlock(&audio->lock); + return rc; +} + +static long audpre_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio_in *audio = file->private_data; + int rc = 0, enable; + uint16_t enable_mask; + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_ENABLE_AUDPRE: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + enable = (enable_mask & AGC_ENABLE) ? 1 : 0; + audio_enable_tx_agc(audio, enable); + enable = (enable_mask & NS_ENABLE) ? 1 : 0; + audio_enable_ns(audio, enable); + enable = (enable_mask & TX_IIR_ENABLE) ? 1 : 0; + audio_enable_iir(audio, enable); + break; + + case AUDIO_SET_AGC: + if (copy_from_user(&audio->tx_agc_cfg, (void *) arg, + sizeof(audio->tx_agc_cfg))) + rc = -EFAULT; + break; + + case AUDIO_SET_NS: + if (copy_from_user(&audio->ns_cfg, (void *) arg, + sizeof(audio->ns_cfg))) + rc = -EFAULT; + break; + + case AUDIO_SET_TX_IIR: + if (copy_from_user(&audio->iir_cfg, (void *) arg, + sizeof(audio->iir_cfg))) + rc = -EFAULT; + break; + + default: + rc = -EINVAL; + } + + mutex_unlock(&audio->lock); + return rc; +} + +static int audpre_open(struct inode *inode, struct file *file) +{ + struct audio_in *audio = &the_audio_in; + + file->private_data = audio; + + return 0; +} + +static struct file_operations audio_fops = { + .owner = THIS_MODULE, + .open = audio_in_open, + .release = audio_in_release, + .read = audio_in_read, + .write = audio_in_write, + .unlocked_ioctl = audio_in_ioctl, +}; + +struct miscdevice audio_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_in", + .fops = &audio_fops, +}; + +static const struct file_operations audpre_fops = { + .owner = THIS_MODULE, + .open = audpre_open, + .unlocked_ioctl = audpre_ioctl, +}; + +struct miscdevice audpre_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_preproc_ctl", + .fops = &audpre_fops, +}; + +static int __init audio_in_init(void) +{ + the_audio_in.data = dma_alloc_coherent(NULL, DMASZ, + &the_audio_in.phys, GFP_KERNEL); + if (!the_audio_in.data) { + MM_ERR("Unable to allocate DMA buffer\n"); + return -ENOMEM; + } + + mutex_init(&the_audio_in.lock); + mutex_init(&the_audio_in.read_lock); + spin_lock_init(&the_audio_in.dsp_lock); + init_waitqueue_head(&the_audio_in.wait); + return misc_register(&audio_in_misc) || misc_register(&audpre_misc); +} + +device_initcall(audio_in_init); diff --git a/arch/arm/mach-msm/qdsp5/audio_mp3.c b/arch/arm/mach-msm/qdsp5/audio_mp3.c new file mode 100644 index 00000000000..a4f2d183e9e --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_mp3.c @@ -0,0 +1,2337 @@ +/* arch/arm/mach-msm/qdsp5/audio_mp3.c + * + * mp3 audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include + +#define ADRV_STATUS_AIO_INTF 0x00000001 +#define ADRV_STATUS_OBUF_GIVEN 0x00000002 +#define ADRV_STATUS_IBUF_GIVEN 0x00000004 +#define ADRV_STATUS_FSYNC 0x00000008 + +/* Size must be power of 2 */ +#define BUFSZ_MAX 32768 +#define BUFSZ_MIN 4096 +#define DMASZ_MAX (BUFSZ_MAX * 2) +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_MP3 2 + +#define PCM_BUFSZ_MIN 4800 /* Hold one stereo MP3 frame */ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDMP3_METAFIELD_MASK 0xFFFF0000 +#define AUDMP3_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDMP3_EOS_FLG_MASK 0x01 +#define AUDMP3_EOS_NONE 0x0 /* No EOS detected */ +#define AUDMP3_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDMP3_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +struct audio; + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audmp3_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audmp3_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audmp3_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audmp3_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; +}; + +struct audmp3_drv_operations { + void (*pcm_buf_update)(struct audio *, uint32_t *); + void (*buffer_refresh)(struct audio *); + void (*send_data)(struct audio *, unsigned); + void (*out_flush)(struct audio *); + void (*in_flush)(struct audio *); + int (*fsync)(struct audio *); +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + struct list_head out_queue; /* queue to retain output buffers */ + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + struct list_head in_queue; /* queue to retain input buffers */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + uint32_t drv_status; + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int rmt_resource_released; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audmp3_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + struct list_head pmem_region_queue; /* protected by lock */ + struct audmp3_drv_operations drv_ops; + + int eq_enable; + int eq_needs_commit; + audpp_cmd_cfg_object_params_eqalizer eq; + audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audplay_config_hostpcm(struct audio *audio); +static void audplay_buffer_refresh(struct audio *audio); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audmp3_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +static unsigned long audmp3_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up); + +static int rmt_put_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_DISABLE; + cmd.dec_type = AUDDEC_DEC_MP3; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return put_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +static int rmt_get_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_ENABLE; + cmd.dec_type = AUDDEC_DEC_MP3; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return get_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + if (audio->rmt_resource_released == 1) { + audio->rmt_resource_released = 0; + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for MP3 \ + session 0x%08x on decoder: %d\n Ignoring \ + error and going ahead with the playback\n", + (int)audio, audio->dec_id); + } + } + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_PLAYBACK; + cfg.codec = RPC_AUD_DEF_CODEC_MP3; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + } + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + rmt_put_resource(audio); + audio->rmt_resource_released = 1; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audmp3_async_pcm_buf_update(struct audio *audio, uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audmp3_buffer_node *filled_buf; + uint8_t index; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + BUG_ON(list_empty(&audio->in_queue)); + filled_buf = list_first_entry(&audio->in_queue, + struct audmp3_buffer_node, list); + if (filled_buf->paddr == payload[2 + index * 2]) { + list_del(&filled_buf->list); + event_payload.aio_buf = filled_buf->buf; + event_payload.aio_buf.data_len = + payload[3 + index * 2]; + MM_DBG("pcm buf %p data_len %d\n", filled_buf, + event_payload.aio_buf.data_len); + audmp3_post_event(audio, AUDIO_EVENT_READ_DONE, + event_payload); + kfree(filled_buf); + } else { + MM_ERR("expected=%lx ret=%x\n", filled_buf->paddr, + payload[2 + index * 2]); + break; + } + } + + audio->drv_status &= ~ADRV_STATUS_IBUF_GIVEN; + audio->drv_ops.buffer_refresh(audio); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + +} + +static void audio_update_pcm_buf_entry(struct audio *audio, uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[2 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audio->drv_ops.buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audio->drv_ops.send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audio->drv_ops.pcm_buf_update(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status: sleep reason=0x%04x\n", + reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + if (audio->pcm_feedback) { + audplay_config_hostpcm(audio); + audio->drv_ops.buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status \n"); + break; + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq); + audpp_avsync(audio->dec_id, 22050); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audpp_avsync(audio->dec_id, 0); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audio->drv_ops.buffer_refresh(audio); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + + +struct msm_adsp_ops audplay_adsp_ops = { + .event = audplay_dsp_event, +}; + + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + u16 cfg_dec_cmd[AUDPP_CMD_CFG_DEC_TYPE_LEN / sizeof(unsigned short)]; + + memset(cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd[0] = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_MP3; + else + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + audpp_cmd_cfg_adec_params_mp3 cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_MP3_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDMP3_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len/2; + cmd.partition_number = 0; + /* complete all the writes to the input buffer */ + wmb(); + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +/* Caller holds irq_lock */ +static void audmp3_async_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + struct audmp3_buffer_node *next_buf; + + if (!audio->running || + audio->drv_status & ADRV_STATUS_IBUF_GIVEN) + return; + + if (!list_empty(&audio->in_queue)) { + next_buf = list_first_entry(&audio->in_queue, + struct audmp3_buffer_node, list); + if (!next_buf) + return; + MM_DBG("next buf %p phy %lx len %d\n", next_buf, + next_buf->paddr, next_buf->buf.buf_len); + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = next_buf->paddr; + refresh_cmd.buf0_length = next_buf->buf.buf_len - + (next_buf->buf.buf_len % 576) + + (audio->mfield ? 24 : 0); /* Mp3 frame size */ + refresh_cmd.buf_read_count = 0; + audio->drv_status |= ADRV_STATUS_IBUF_GIVEN; + (void) audplay_send_queue0(audio, &refresh_cmd, + sizeof(refresh_cmd)); + } + +} + +static void audplay_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size - + (audio->in[audio->fill_next].size % 576) + + (audio->mfield ? 24 : 0); /* Mp3 frame size */ + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audplay_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = 1; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audmp3_async_send_data(struct audio *audio, unsigned needed) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + audio->out_needed = 1; + if (audio->drv_status & ADRV_STATUS_OBUF_GIVEN) { + /* pop one node out of queue */ + union msm_audio_event_payload payload; + struct audmp3_buffer_node *used_buf; + + MM_DBG("consumed\n"); + BUG_ON(list_empty(&audio->out_queue)); + used_buf = list_first_entry(&audio->out_queue, + struct audmp3_buffer_node, list); + list_del(&used_buf->list); + payload.aio_buf = used_buf->buf; + audmp3_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(used_buf); + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + } + + } + + if (audio->out_needed) { + struct audmp3_buffer_node *next_buf; + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + if (!list_empty(&audio->out_queue)) { + next_buf = list_first_entry(&audio->out_queue, + struct audmp3_buffer_node, list); + MM_DBG("next_buf %p\n", next_buf); + if (next_buf) { + MM_DBG("next buf phy %lx len %d\n", + next_buf->paddr, + next_buf->buf.data_len); + + cmd.cmd_id = + AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDMP3_METAFIELD_MASK | + (next_buf->buf.mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = (unsigned) next_buf->paddr; + cmd.buf_size = next_buf->buf.data_len >> 1; + cmd.partition_number = 0; + /* complete the writes to the input buffer */ + wmb(); + audplay_send_queue0(audio, &cmd, sizeof(cmd)); + audio->out_needed = 0; + audio->drv_status |= ADRV_STATUS_OBUF_GIVEN; + } + } + } + +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ +static void audmp3_async_flush(struct audio *audio) +{ + struct audmp3_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audmp3_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audmp3_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(buf_node); + } + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audmp3_async_flush_pcm_buf(struct audio *audio) +{ + struct audmp3_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + list_for_each_safe(ptr, next, &audio->in_queue) { + buf_node = list_entry(ptr, struct audmp3_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = 0; + audmp3_post_event(audio, AUDIO_EVENT_READ_DONE, + payload); + kfree(buf_node); + } + audio->drv_status &= ~ADRV_STATUS_IBUF_GIVEN; + +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audio_ioport_reset(struct audio *audio) +{ + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + /* If fsync is in progress, make sure + * return value of fsync indicates + * abort due to flush + */ + if (audio->drv_status & ADRV_STATUS_FSYNC) { + MM_DBG("fsync in progress\n"); + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio->drv_ops.out_flush(audio); + mutex_unlock(&audio->write_lock); + } else + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + } else { + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio->drv_ops.out_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio->drv_ops.in_flush(audio); + mutex_unlock(&audio->read_lock); + } +} + +static int audmp3_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audmp3_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audmp3_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audmp3_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audmp3_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audmp3_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audmp3_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audmp3_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audmp3_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audmp3_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (drv_evt->event_type == AUDIO_EVENT_WRITE_DONE || + drv_evt->event_type == AUDIO_EVENT_READ_DONE) { + mutex_lock(&audio->lock); + audmp3_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0); + mutex_unlock(&audio->lock); + } + + /* order reads from the output buffer */ + if (drv_evt->event_type == AUDIO_EVENT_READ_DONE) + rmb(); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audmp3_pmem_check(struct audio *audio, + void *vaddr, unsigned long len) +{ + struct audmp3_pmem_region *region_elt; + struct audmp3_pmem_region t = { .vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + MM_ERR("region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, + region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audmp3_pmem_add(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audmp3_pmem_region *region; + int rc = -EINVAL; + + MM_DBG("\n"); /* Macro prints the file name and function */ + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = audmp3_pmem_check(audio, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + MM_DBG("add region paddr %lx vaddr %p, len %lu\n", region->paddr, + region->vaddr, region->len); + list_add_tail(®ion->list, &audio->pmem_region_queue); +end: + return rc; +} + +static int audmp3_pmem_remove(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audmp3_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + MM_DBG("info fd %d vaddr %p\n", info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audmp3_pmem_region, list); + + if ((region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + MM_DBG("region %p in use ref_cnt %d\n", + region, region->ref_cnt); + break; + } + MM_DBG("remove region fd %d vaddr %p \n", + info->fd, info->vaddr); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +static int audmp3_pmem_lookup_vaddr(struct audio *audio, void *addr, + unsigned long len, struct audmp3_pmem_region **region) +{ + struct audmp3_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + MM_ERR("multiple hits for vaddr %p, len %ld\n", addr, len); + list_for_each_entry(region_elt, + &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + MM_ERR("\t%p, %ld --> %p\n", region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +unsigned long audmp3_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up) +{ + struct audmp3_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audmp3_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + MM_ERR("lookup (%p, %ld) failed\n", addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + MM_DBG("found region %p ref_cnt %d\n", region, region->ref_cnt); + paddr = region->paddr + (addr - region->vaddr); + return paddr; +} + +/* audio -> lock must be held at this point */ +static int audmp3_aio_buf_add(struct audio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audmp3_buffer_node *buf_node; + + buf_node = kmalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + MM_DBG("node %p dir %x buf_addr %p buf_len %d data_len \ + %d\n", buf_node, dir, + buf_node->buf.buf_addr, buf_node->buf.buf_len, + buf_node->buf.data_len); + + buf_node->paddr = audmp3_pmem_fixup( + audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1); + + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.data_len & 0x1) || + (!audio->pcm_feedback && + !buf_node->buf.data_len)) { + kfree(buf_node); + return -EINVAL; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + list_add_tail(&buf_node->list, &audio->out_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + audio->drv_ops.send_data(audio, 0); + } else { + /* read */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.buf_len < PCM_BUFSZ_MIN)) { + kfree(buf_node); + return -EINVAL; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + list_add_tail(&buf_node->list, &audio->in_queue); + audio->drv_ops.buffer_refresh(audio); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + + MM_DBG("Add buf_node %p paddr %lx\n", buf_node, buf_node->paddr); + + return 0; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq); + audio->eq_needs_commit = 0; + } + return 0; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = audpp_avsync_byte_count(audio->dec_id); + stats.sample_count = audpp_avsync_sample_count(audio->dec_id); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG(" AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audmp3_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + audio->mfield = config.meta_field; + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) { + config.channel_count = 1; + } else { + config.channel_count = 2; + } + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) { + rc = -EFAULT; + } else { + rc = 0; + } + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + rc = 0; + break; + } + + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buffer %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("malloc read buf failed\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; + index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + rc = 0; + MM_DBG("read buf: phy addr \ + 0x%08x kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audmp3_pmem_add(audio, &info); + break; + } + + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_DEREGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audmp3_pmem_remove(audio, &info); + break; + } + case AUDIO_ASYNC_WRITE: + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else + rc = audmp3_aio_buf_add(audio, 1, (void __user *) arg); + break; + + case AUDIO_ASYNC_READ: + if (audio->pcm_feedback) + rc = audmp3_aio_buf_add(audio, 0, (void __user *) arg); + else + rc = -EPERM; + break; + + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +int audmp3_async_fsync(struct audio *audio) +{ + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + mutex_lock(&audio->write_lock); + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + (audio->teos && audio->out_needed && + list_empty(&audio->out_queue)) + || audio->wflush || audio->stopped); + + if (audio->stopped || audio->wflush) + rc = -EBUSY; + + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +int audmp3_sync_fsync(struct audio *audio) +{ + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audio->drv_ops.send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + return rc; +} + +int audmp3_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + + if (!audio->running || audio->pcm_feedback) + return -EINVAL; + + return audio->drv_ops.fsync(audio); +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (audio->drv_status & ADRV_STATUS_AIO_INTF) + return -EPERM; + else if (!audio->pcm_feedback) + return 0; /* PCM feedback disabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d \n", count); + while (count > 0) { + rc = wait_event_interruptible( + audio->read_wait, + (audio->in[audio->read_next]. + used > 0) || (audio->stopped) + || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since + * driver does not know frame size, read count + * must be greater or equal + * to size of PCM samples + */ + MM_DBG("no partial frame done reading\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + /* order reads from the output buffer */ + rmb(); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audio->drv_ops.buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audmp3_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + char *buf_ptr; + + if (audio->reserved) { + MM_DBG("flush reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + frame->used = 2; + audio->reserved = 0; + audio->drv_ops.send_data(audio, 0); + } + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audio->drv_ops.send_data(audio, 0); +done: + return rc; +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDMP3_EOS_NONE; + unsigned dsize; + unsigned short mfield_size = 0; + + if (audio->drv_status & ADRV_STATUS_AIO_INTF) + return -EPERM; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDMP3_EOS_FLG_OFFSET] & + AUDMP3_EOS_FLG_MASK) { + MM_DBG("EOS SET\n"); + eos_condition = AUDMP3_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDMP3_EOS_FLG_OFFSET] + &= ~AUDMP3_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + (frame->size - mfield_size) - 1 : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audio->drv_ops.send_data(audio, 0); + } + } + if (eos_condition == AUDMP3_EOS_SET) + rc = audmp3_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static void audmp3_reset_pmem_region(struct audio *audio) +{ + struct audmp3_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audmp3_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + audio_disable(audio); + if (audio->rmt_resource_released == 0) + rmt_put_resource(audio); + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + audmp3_reset_pmem_region(audio); + + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->opened = 0; + audio->event_abort = 1; + wake_up(&audio->event_wait); + audmp3_reset_event_queue(audio); + MM_DBG("pmem area = 0x%8x\n", (unsigned int)audio->data); + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + } + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audmp3_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audmp3_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audmp3_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audmp3_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audmp3_suspend(struct early_suspend *h) +{ + struct audmp3_suspend_ctl *ctl = + container_of(h, struct audmp3_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audmp3_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audmp3_resume(struct early_suspend *h) +{ + struct audmp3_suspend_ctl *ctl = + container_of(h, struct audmp3_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audmp3_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audmp3_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audmp3_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audmp3_debug_fops = { + .read = audmp3_debug_read, + .open = audmp3_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + + struct audio *audio = NULL; + int rc, i, dec_attrb, decid; + struct audmp3_event *e_node = NULL; + unsigned pmem_sz = DMASZ_MAX; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_mp3_" + 5]; +#endif + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_MP3; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + /* Non AIO interface */ + if (!(file->f_flags & O_NONBLOCK)) { + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d \n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write \ + buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr\ + 0x%08x\n", audio->phys,\ + (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + } + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + rc = audmgr_open(&audio->audmgr); + if (rc) { + MM_ERR("audmgr open failed, freeing instance \ + 0x%08x\n", (int)audio); + goto err; + } + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops, audio); + + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + goto err; + } + + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for MP3 session \ + 0x%08x on decoder: %d\n", (int)audio, audio->dec_id); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + msm_adsp_put(audio->audplay); + goto err; + } + + if (file->f_flags & O_NONBLOCK) { + MM_DBG("set to aio interface\n"); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + audio->drv_ops.pcm_buf_update = audmp3_async_pcm_buf_update; + audio->drv_ops.buffer_refresh = audmp3_async_buffer_refresh; + audio->drv_ops.send_data = audmp3_async_send_data; + audio->drv_ops.out_flush = audmp3_async_flush; + audio->drv_ops.in_flush = audmp3_async_flush_pcm_buf; + audio->drv_ops.fsync = audmp3_async_fsync; + } else { + MM_DBG("set to std io interface\n"); + audio->drv_ops.pcm_buf_update = audio_update_pcm_buf_entry; + audio->drv_ops.buffer_refresh = audplay_buffer_refresh; + audio->drv_ops.send_data = audplay_send_data; + audio->drv_ops.out_flush = audio_flush; + audio->drv_ops.in_flush = audio_flush_pcm_buf; + audio->drv_ops.fsync = audmp3_sync_fsync; + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = (audio->out_dma_sz >> 1); + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + } + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->in_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + audio->vol_pan.volume = 0x2000; + + audio->drv_ops.out_flush(audio); + + file->private_data = audio; + audio->opened = 1; +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_mp3_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audmp3_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audmp3_resume; + audio->suspend_ctl.node.suspend = audmp3_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDMP3_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audmp3_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +err: + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + } + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_mp3_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audmp3_fsync, +}; + +struct miscdevice audio_mp3_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_mp3", + .fops = &audio_mp3_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_mp3_misc); +} + +static void __exit audio_exit(void) +{ + misc_deregister(&audio_mp3_misc); +} + +module_init(audio_init); +module_exit(audio_exit); + +MODULE_DESCRIPTION("MSM MP3 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5/audio_out.c b/arch/arm/mach-msm/qdsp5/audio_out.c new file mode 100644 index 00000000000..7c560373f3e --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_out.c @@ -0,0 +1,1020 @@ +/* arch/arm/mach-msm/qdsp5/audio_out.c + * + * pcm audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "audmgr.h" + +#include +#include + +#include +#include + +#include "evlog.h" + +#define LOG_AUDIO_EVENTS 1 +#define LOG_AUDIO_FAULTS 0 + +enum { + EV_NULL, + EV_OPEN, + EV_WRITE, + EV_RETURN, + EV_IOCTL, + EV_WRITE_WAIT, + EV_WAIT_EVENT, + EV_FILL_BUFFER, + EV_SEND_BUFFER, + EV_DSP_EVENT, + EV_ENABLE, +}; + +#if (LOG_AUDIO_EVENTS != 1) +static inline void LOG(unsigned id, unsigned arg) {} +#else +static const char *pcm_log_strings[] = { + "NULL", + "OPEN", + "WRITE", + "RETURN", + "IOCTL", + "WRITE_WAIT", + "WAIT_EVENT", + "FILL_BUFFER", + "SEND_BUFFER", + "DSP_EVENT", + "ENABLE", +}; + +DECLARE_LOG(pcm_log, 64, pcm_log_strings); + +static int __init _pcm_log_init(void) +{ + return ev_log_init(&pcm_log); +} +module_init(_pcm_log_init); + +#define LOG(id,arg) ev_log_write(&pcm_log, id, arg) +#endif + + + + + +#define BUFSZ (960 * 5) +#define DMASZ (BUFSZ * 2) + +#define COMMON_OBJ_ID 6 + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t wait; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_weight; + uint32_t out_buffer_size; + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int teos; /* valid only if tunnel mode & no data left for decoder */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + + struct wake_lock wakelock; + struct wake_lock idlelock; + + audpp_cmd_cfg_object_params_volume vol_pan; +}; + +struct audio_copp { + int mbadrc_enable; + int mbadrc_needs_commit; + char *mbadrc_data; + dma_addr_t mbadrc_phys; + + audpp_cmd_cfg_object_params_mbadrc mbadrc; + + int eq_enable; + int eq_needs_commit; + audpp_cmd_cfg_object_params_eqalizer eq; + + int rx_iir_enable; + int rx_iir_needs_commit; + audpp_cmd_cfg_object_params_pcm iir; + + audpp_cmd_cfg_object_params_volume vol_pan; + + int qconcert_plus_enable; + int qconcert_plus_needs_commit; + audpp_cmd_cfg_object_params_qconcert qconcert_plus; + + int status; + int opened; + struct mutex lock; + + struct audpp_event_callback ecb; +} the_audio_copp; + +static void audio_prevent_sleep(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + wake_lock(&audio->wakelock); + wake_lock(&audio->idlelock); +} + +static void audio_allow_sleep(struct audio *audio) +{ + wake_unlock(&audio->wakelock); + wake_unlock(&audio->idlelock); + MM_DBG("\n"); /* Macro prints the file name and function */ +} + +static int audio_dsp_out_enable(struct audio *audio, int yes); +static int audio_dsp_send_buffer(struct audio *audio, unsigned id, unsigned len); + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + /* refuse to start if we're not ready */ + if (!audio->out[0].used || !audio->out[1].used) + return -EIO; + + /* we start buffers 0 and 1, so buffer 0 will be the + * next one the dsp will want + */ + audio->out_tail = 0; + audio->out_needed = 0; + + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_HOST_PCM; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + audio_prevent_sleep(audio); + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) { + audio_allow_sleep(audio); + return rc; + } + + if (audpp_enable(-1, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + audmgr_disable(&audio->audmgr); + audio_allow_sleep(audio); + return -ENODEV; + } + + audio->enabled = 1; + htc_pwrsink_set(PWRSINK_AUDIO, 100); + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio_dsp_out_enable(audio, 0); + + audpp_disable(-1, audio); + + wake_up(&audio->wait); + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + audio_allow_sleep(audio); + } + return 0; +} + +void audio_commit_pending_pp_params(void *priv, unsigned id, uint16_t *msg) +{ + struct audio_copp *audio_copp = priv; + + if (AUDPP_MSG_CFG_MSG == id && msg[0] == AUDPP_MSG_ENA_DIS) + return; + + if (!audio_copp->status) + return; + + audpp_dsp_set_mbadrc(COMMON_OBJ_ID, audio_copp->mbadrc_enable, + &audio_copp->mbadrc); + + audpp_dsp_set_eq(COMMON_OBJ_ID, audio_copp->eq_enable, + &audio_copp->eq); + + audpp_dsp_set_rx_iir(COMMON_OBJ_ID, audio_copp->rx_iir_enable, + &audio_copp->iir); + audpp_dsp_set_vol_pan(COMMON_OBJ_ID, &audio_copp->vol_pan); + + audpp_dsp_set_qconcert_plus(COMMON_OBJ_ID, + audio_copp->qconcert_plus_enable, + &audio_copp->qconcert_plus); +} +EXPORT_SYMBOL(audio_commit_pending_pp_params); + +/* ------------------- dsp --------------------- */ +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + struct buffer *frame; + unsigned long flags; + + LOG(EV_DSP_EVENT, id); + switch (id) { + case AUDPP_MSG_HOST_PCM_INTF_MSG: { + unsigned id = msg[2]; + unsigned idx = msg[3] - 1; + + /* MM_INFO("HOST_PCM id %d idx %d\n", id, idx); */ + if (id != AUDPP_MSG_HOSTPCM_ID_ARM_RX) { + MM_ERR("bogus id\n"); + break; + } + if (idx > 1) { + MM_ERR("bogus buffer idx\n"); + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (audio->running) { + atomic_add(audio->out[idx].used, &audio->out_bytes); + audio->out[idx].used = 0; + + frame = audio->out + audio->out_tail; + if (frame->used) { + audio_dsp_send_buffer( + audio, audio->out_tail, frame->used); + audio->out_tail ^= 1; + } else { + audio->out_needed++; + } + wake_up(&audio->wait); + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + break; + } + case AUDPP_MSG_PCMDMAMISSED: + MM_INFO("PCMDMAMISSED %d\n", msg[0]); + audio->teos = 1; + wake_up(&audio->wait); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + LOG(EV_ENABLE, 1); + MM_DBG("CFG_MSG ENABLE\n"); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(5, &audio->vol_pan); + audio_dsp_out_enable(audio, 1); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + LOG(EV_ENABLE, 0); + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_ERR("CFG_MSG %d?\n", msg[0]); + } + break; + default: + MM_ERR("UNKNOWN (%d)\n", id); + } +} + +static int audio_dsp_out_enable(struct audio *audio, int yes) +{ + audpp_cmd_pcm_intf cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_PCM_INTF_2; + cmd.object_num = AUDPP_CMD_PCM_INTF_OBJECT_NUM; + cmd.config = AUDPP_CMD_PCM_INTF_CONFIG_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + + if (yes) { + cmd.write_buf1LSW = audio->out[0].addr; + cmd.write_buf1MSW = audio->out[0].addr >> 16; + if (audio->out[0].used) + cmd.write_buf1_len = audio->out[0].used; + else + cmd.write_buf1_len = audio->out[0].size; + cmd.write_buf2LSW = audio->out[1].addr; + cmd.write_buf2MSW = audio->out[1].addr >> 16; + if (audio->out[1].used) + cmd.write_buf2_len = audio->out[1].used; + else + cmd.write_buf2_len = audio->out[1].size; + cmd.arm_to_rx_flag = AUDPP_CMD_PCM_INTF_ENA_V; + cmd.weight_decoder_to_rx = audio->out_weight; + cmd.weight_arm_to_rx = 1; + cmd.partition_number_arm_to_dsp = 0; + cmd.sample_rate = audio->out_sample_rate; + cmd.channel_mode = audio->out_channel_mode; + } + + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static int audio_dsp_send_buffer(struct audio *audio, unsigned idx, unsigned len) +{ + audpp_cmd_pcm_intf_send_buffer cmd; + + cmd.cmd_id = AUDPP_CMD_PCM_INTF_2; + cmd.host_pcm_object = AUDPP_CMD_PCM_INTF_OBJECT_NUM; + cmd.config = AUDPP_CMD_PCM_INTF_BUFFER_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + cmd.dsp_to_arm_buf_id = 0; + cmd.arm_to_dsp_buf_id = idx + 1; + cmd.arm_to_dsp_buf_len = len; + + LOG(EV_SEND_BUFFER, idx); + dma_coherent_pre_ops(); + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +/* ------------------- device --------------------- */ + +static int audio_enable_mbadrc(struct audio_copp *audio_copp, int enable) +{ + if (audio_copp->mbadrc_enable == enable && + !audio_copp->mbadrc_needs_commit) + return 0; + + audio_copp->mbadrc_enable = enable; + if (is_audpp_enable()) { + audpp_dsp_set_mbadrc(COMMON_OBJ_ID, enable, + &audio_copp->mbadrc); + audio_copp->mbadrc_needs_commit = 0; + } + + return 0; +} + +static int audio_enable_eq(struct audio_copp *audio_copp, int enable) +{ + if (audio_copp->eq_enable == enable && + !audio_copp->eq_needs_commit) + return 0; + + audio_copp->eq_enable = enable; + + if (is_audpp_enable()) { + audpp_dsp_set_eq(COMMON_OBJ_ID, enable, &audio_copp->eq); + audio_copp->eq_needs_commit = 0; + } + return 0; +} + +static int audio_enable_rx_iir(struct audio_copp *audio_copp, int enable) +{ + if (audio_copp->rx_iir_enable == enable && + !audio_copp->rx_iir_needs_commit) + return 0; + + audio_copp->rx_iir_enable = enable; + + if (is_audpp_enable()) { + audpp_dsp_set_rx_iir(COMMON_OBJ_ID, enable, &audio_copp->iir); + audio_copp->rx_iir_needs_commit = 0; + } + return 0; +} + +static int audio_enable_vol_pan(struct audio_copp *audio_copp) +{ + if (is_audpp_enable()) + audpp_dsp_set_vol_pan(COMMON_OBJ_ID, &audio_copp->vol_pan); + return 0; +} + +static int audio_enable_qconcert_plus(struct audio_copp *audio_copp, int enable) +{ + if (audio_copp->qconcert_plus_enable == enable && + !audio_copp->qconcert_plus_needs_commit) + return 0; + + audio_copp->qconcert_plus_enable = enable; + + if (is_audpp_enable()) { + audpp_dsp_set_qconcert_plus(COMMON_OBJ_ID, enable, + &audio_copp->qconcert_plus); + audio_copp->qconcert_plus_needs_commit = 0; + } + return 0; +} + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->stopped = 0; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->out_bytes); + if (copy_to_user((void*) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(5, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + return 0; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(5, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + return 0; + } + + LOG(EV_IOCTL, cmd); + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + rc = audio_enable(audio); + break; + case AUDIO_STOP: + rc = audio_disable(audio); + audio->stopped = 1; + break; + case AUDIO_FLUSH: + if (audio->stopped) { + /* Make sure we're stopped and we wake any threads + * that might be blocked holding the write_lock. + * While audio->stopped write threads will always + * exit immediately. + */ + wake_up(&audio->wait); + mutex_lock(&audio->write_lock); + audio_flush(audio); + mutex_unlock(&audio->write_lock); + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void*) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count= AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) { + config.channel_count = 1; + } else { + config.channel_count = 2; + } + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void*) arg, &config, sizeof(config))) { + rc = -EFAULT; + } else { + rc = 0; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audio_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + int rc = 0; + + if (!audio->running) + return -EINVAL; + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->wait, + (!audio->out[0].used && + !audio->out[1].used)); + + if (rc < 0) + goto done; + + /* pcm dmamiss message is sent continously when + * decoder is starved so no race condition concern + */ + + audio->teos = 0; + + rc = wait_event_interruptible(audio->wait, + audio->teos); + +done: + mutex_unlock(&audio->write_lock); + return rc; +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, loff_t *pos) +{ + return -EINVAL; +} + +static inline int rt_policy(int policy) +{ + if (unlikely(policy == SCHED_FIFO) || unlikely(policy == SCHED_RR)) + return 1; + return 0; +} + +static inline int task_has_rt_policy(struct task_struct *p) +{ + return rt_policy(p->policy); +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct sched_param s = { .sched_priority = 1 }; + struct audio *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + int old_prio = current->rt_priority; + int old_policy = current->policy; + int cap_nice = cap_raised(current_cap(), CAP_SYS_NICE); + int rc = 0; + + LOG(EV_WRITE, count | (audio->running << 28) | (audio->stopped << 24)); + + /* just for this write, set us real-time */ + if (!task_has_rt_policy(current)) { + struct cred *new = prepare_creds(); + cap_raise(new->cap_effective, CAP_SYS_NICE); + commit_creds(new); + if ((sched_setscheduler(current, SCHED_RR, &s)) < 0) + MM_ERR("sched_setscheduler failed\n"); + } + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + + LOG(EV_WAIT_EVENT, 0); + rc = wait_event_interruptible(audio->wait, + (frame->used == 0) || (audio->stopped)); + LOG(EV_WAIT_EVENT, 1); + + if (rc < 0) + break; + if (audio->stopped) { + rc = -EBUSY; + break; + } + xfer = count > frame->size ? frame->size : count; + if (copy_from_user(frame->data, buf, xfer)) { + rc = -EFAULT; + break; + } + frame->used = xfer; + audio->out_head ^= 1; + count -= xfer; + buf += xfer; + + spin_lock_irqsave(&audio->dsp_lock, flags); + LOG(EV_FILL_BUFFER, audio->out_head ^ 1); + frame = audio->out + audio->out_tail; + if (frame->used && audio->out_needed) { + audio_dsp_send_buffer(audio, audio->out_tail, frame->used); + audio->out_tail ^= 1; + audio->out_needed--; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + + mutex_unlock(&audio->write_lock); + + /* restore scheduling policy and priority */ + if (!rt_policy(old_policy)) { + struct sched_param v = { .sched_priority = old_prio }; + if ((sched_setscheduler(current, old_policy, &v)) < 0) + MM_ERR("sched_setscheduler failed\n"); + if (likely(!cap_nice)) { + struct cred *new = prepare_creds(); + cap_lower(new->cap_effective, CAP_SYS_NICE); + commit_creds(new); + } + } + + LOG(EV_RETURN,(buf > start) ? (buf - start) : rc); + if (buf > start) + return buf - start; + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + LOG(EV_OPEN, 0); + mutex_lock(&audio->lock); + audio_disable(audio); + audio_flush(audio); + audio->opened = 0; + mutex_unlock(&audio->lock); + htc_pwrsink_set(PWRSINK_AUDIO, 0); + return 0; +} + +struct audio the_audio; + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = &the_audio; + int rc; + + mutex_lock(&audio->lock); + + if (audio->opened) { + MM_ERR("busy\n"); + rc = -EBUSY; + goto done; + } + + if (!audio->data) { + audio->data = dma_alloc_coherent(NULL, DMASZ, + &audio->phys, GFP_KERNEL); + if (!audio->data) { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + goto done; + } + } + + rc = audmgr_open(&audio->audmgr); + if (rc) + goto done; + + audio->out_buffer_size = BUFSZ; + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + audio->out_weight = 100; + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x2000; + audio->vol_pan.pan = 0x0; + + audio_flush(audio); + + file->private_data = audio; + audio->opened = 1; + rc = 0; + LOG(EV_OPEN, 1); +done: + mutex_unlock(&audio->lock); + return rc; +} + +static long audpp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio_copp *audio_copp = file->private_data; + int rc = 0, enable; + uint16_t enable_mask; + int prev_state; + + mutex_lock(&audio_copp->lock); + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + enable = ((enable_mask & ADRC_ENABLE) || + (enable_mask & MBADRC_ENABLE)) ? 1 : 0; + audio_enable_mbadrc(audio_copp, enable); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio_copp, enable); + enable = (enable_mask & IIR_ENABLE) ? 1 : 0; + audio_enable_rx_iir(audio_copp, enable); + enable = (enable_mask & QCONCERT_PLUS_ENABLE) ? 1 : 0; + audio_enable_qconcert_plus(audio_copp, enable); + break; + + case AUDIO_SET_MBADRC: { + uint32_t mbadrc_coeff_buf; + prev_state = audio_copp->mbadrc_enable; + audio_copp->mbadrc_enable = 0; + if (copy_from_user(&audio_copp->mbadrc.num_bands, (void *) arg, + sizeof(audio_copp->mbadrc) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) + rc = -EFAULT; + else if (audio_copp->mbadrc.ext_buf_size) { + mbadrc_coeff_buf = (uint32_t) ((char *) arg + + sizeof(audio_copp->mbadrc) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2)); + if ((copy_from_user(audio_copp->mbadrc_data, + (void *) mbadrc_coeff_buf, + AUDPP_MBADRC_EXTERNAL_BUF_SIZE * 2))) { + rc = -EFAULT; + break; + } + audio_copp->mbadrc.ext_buf_lsw = + audio_copp->mbadrc_phys & 0xFFFF; + audio_copp->mbadrc.ext_buf_msw = + ((audio_copp->mbadrc_phys & 0xFFFF0000) >> 16); + } + audio_copp->mbadrc_enable = prev_state; + if (!rc) + audio_copp->mbadrc_needs_commit = 1; + break; + } + + case AUDIO_SET_ADRC: { + struct audpp_cmd_cfg_object_params_adrc adrc; + prev_state = audio_copp->mbadrc_enable; + audio_copp->mbadrc_enable = 0; + if (copy_from_user(&adrc.compression_th, (void *) arg, + sizeof(adrc) - 2)) { + rc = -EFAULT; + audio_copp->mbadrc_enable = prev_state; + break; + } + audio_copp->mbadrc.num_bands = 1; + audio_copp->mbadrc.down_samp_level = 8; + audio_copp->mbadrc.adrc_delay = adrc.adrc_delay; + audio_copp->mbadrc.ext_buf_size = 0; + audio_copp->mbadrc.ext_partition = 0; + audio_copp->mbadrc.adrc_band[0].subband_enable = 1; + audio_copp->mbadrc.adrc_band[0].adrc_sub_mute = 0; + audio_copp->mbadrc.adrc_band[0].rms_time = + adrc.rms_time; + audio_copp->mbadrc.adrc_band[0].compression_th = + adrc.compression_th; + audio_copp->mbadrc.adrc_band[0].compression_slope = + adrc.compression_slope; + audio_copp->mbadrc.adrc_band[0].attack_const_lsw = + adrc.attack_const_lsw; + audio_copp->mbadrc.adrc_band[0].attack_const_msw = + adrc.attack_const_msw; + audio_copp->mbadrc.adrc_band[0].release_const_lsw = + adrc.release_const_lsw; + audio_copp->mbadrc.adrc_band[0].release_const_msw = + adrc.release_const_msw; + audio_copp->mbadrc.adrc_band[0].makeup_gain = 0x2000; + audio_copp->mbadrc_enable = prev_state; + audio_copp->mbadrc_needs_commit = 1; + break; + } + + case AUDIO_SET_EQ: + prev_state = audio_copp->eq_enable; + audio_copp->eq_enable = 0; + if (copy_from_user(&audio_copp->eq.num_bands, (void *) arg, + sizeof(audio_copp->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) + rc = -EFAULT; + audio_copp->eq_enable = prev_state; + audio_copp->eq_needs_commit = 1; + break; + + case AUDIO_SET_RX_IIR: + prev_state = audio_copp->rx_iir_enable; + audio_copp->rx_iir_enable = 0; + if (copy_from_user(&audio_copp->iir.num_bands, (void *) arg, + sizeof(audio_copp->iir) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) + rc = -EFAULT; + audio_copp->rx_iir_enable = prev_state; + audio_copp->rx_iir_needs_commit = 1; + break; + + case AUDIO_SET_VOLUME: + audio_copp->vol_pan.volume = arg; + audio_enable_vol_pan(audio_copp); + break; + + case AUDIO_SET_PAN: + audio_copp->vol_pan.pan = arg; + audio_enable_vol_pan(audio_copp); + break; + + case AUDIO_SET_QCONCERT_PLUS: + prev_state = audio_copp->qconcert_plus_enable; + audio_copp->qconcert_plus_enable = 0; + if (copy_from_user(&audio_copp->qconcert_plus.op_mode, + (void *) arg, + sizeof(audio_copp->qconcert_plus) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) + rc = -EFAULT; + audio_copp->qconcert_plus_enable = prev_state; + audio_copp->qconcert_plus_needs_commit = 1; + break; + + default: + rc = -EINVAL; + } + + mutex_unlock(&audio_copp->lock); + return rc; +} + +static int audpp_open(struct inode *inode, struct file *file) +{ + struct audio_copp *audio_copp = &the_audio_copp; + int rc; + + mutex_lock(&audio_copp->lock); + if (audio_copp->opened) { + mutex_unlock(&audio_copp->lock); + return -EBUSY; + } + + audio_copp->opened = 1; + + if (!audio_copp->status) { + audio_copp->ecb.fn = audio_commit_pending_pp_params; + audio_copp->ecb.private = audio_copp; + rc = audpp_register_event_callback(&audio_copp->ecb); + if (rc) { + audio_copp->opened = 0; + mutex_unlock(&audio_copp->lock); + return rc; + } + audio_copp->mbadrc_data = dma_alloc_coherent(NULL, + AUDPP_MBADRC_EXTERNAL_BUF_SIZE * 2, + &audio_copp->mbadrc_phys, GFP_KERNEL); + if (!audio_copp->mbadrc_data) { + MM_ERR("could not allocate DMA buffers\n"); + audio_copp->opened = 0; + audpp_unregister_event_callback(&audio_copp->ecb); + mutex_unlock(&audio_copp->lock); + return -ENOMEM; + } + audio_copp->vol_pan.volume = 0x2000; + audio_copp->vol_pan.pan = 0x0; + audio_copp->status = 1; + } + + file->private_data = audio_copp; + mutex_unlock(&audio_copp->lock); + + return 0; +} + +static int audpp_release(struct inode *inode, struct file *file) +{ + struct audio_copp *audio_copp = &the_audio_copp; + + audio_copp->opened = 0; + + return 0; +} + +static struct file_operations audio_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_fsync, +}; + +static struct file_operations audpp_fops = { + .owner = THIS_MODULE, + .open = audpp_open, + .release = audpp_release, + .unlocked_ioctl = audpp_ioctl, +}; + +struct miscdevice audio_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_out", + .fops = &audio_fops, +}; + +struct miscdevice audpp_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_ctl", + .fops = &audpp_fops, +}; + +static int __init audio_init(void) +{ + mutex_init(&the_audio.lock); + mutex_init(&the_audio.write_lock); + mutex_init(&the_audio_copp.lock); + spin_lock_init(&the_audio.dsp_lock); + init_waitqueue_head(&the_audio.wait); + wake_lock_init(&the_audio.wakelock, WAKE_LOCK_SUSPEND, "audio_pcm"); + wake_lock_init(&the_audio.idlelock, WAKE_LOCK_IDLE, "audio_pcm_idle"); + return (misc_register(&audio_misc) || misc_register(&audpp_misc)); +} + +device_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5/audio_pcm.c b/arch/arm/mach-msm/qdsp5/audio_pcm.c new file mode 100644 index 00000000000..7a26b50ec33 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_pcm.c @@ -0,0 +1,1692 @@ +/* audio_pcm.c - pcm audio decoder driver + * + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 decoder driver in arch/arm/mach-msm/qdsp5/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include + +/* for queue ids - should be relative to module number*/ +#include "adsp.h" + +#define ADRV_STATUS_AIO_INTF 0x00000001 +#define ADRV_STATUS_OBUF_GIVEN 0x00000002 +#define ADRV_STATUS_IBUF_GIVEN 0x00000004 +#define ADRV_STATUS_FSYNC 0x00000008 + +/* Size must be power of 2 */ +#define BUFSZ_MAX 32768 +#define BUFSZ_MIN 4096 +#define DMASZ_MAX (BUFSZ_MAX * 2) +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDDEC_DEC_PCM 0 + +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDPCM_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +struct audio; + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audpcm_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audpcm_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audpcm_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr_ref; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audpcm_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; +}; + +struct audpcm_drv_operations { + void (*send_data)(struct audio *, unsigned); + void (*out_flush)(struct audio *); + int (*fsync)(struct audio *); +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + struct list_head out_queue; /* queue to retain output buffers */ + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_bits; /* bits per sample */ + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + int32_t phys; + + uint32_t drv_status; + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int teos; /* valid only if tunnel mode & no data left for decoder */ + int rmt_resource_released; + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + + unsigned volume; + + uint16_t dec_id; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audpcm_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + struct list_head pmem_region_queue; + struct audpcm_drv_operations drv_ops; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audpcm_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +static unsigned long audpcm_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up); + +static int rmt_put_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_DISABLE; + cmd.dec_type = AUDDEC_DEC_PCM; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return put_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +static int rmt_get_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_ENABLE; + cmd.dec_type = AUDDEC_DEC_PCM; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return get_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + if (audio->rmt_resource_released == 1) { + audio->rmt_resource_released = 0; + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for PCM \ + session 0x%08x on decoder: %d\n Ignoring \ + error and going ahead with the playback\n", + (int)audio, audio->dec_id); + } + } + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_PLAYBACK; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + rmt_put_resource(audio); + audio->rmt_resource_released = 1; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audio->drv_ops.send_data(audio, 1); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + default: + MM_ERR("unexpected message from decoder \n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status: sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init\n"); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status \n"); + break; + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_set_volume_and_pan(audio->dec_id, audio->volume, + 0); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_ERR("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + + +struct msm_adsp_ops audpcmdec_adsp_ops = { + .event = audplay_dsp_event, +}; + + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + u16 cfg_dec_cmd[AUDPP_CMD_CFG_DEC_TYPE_LEN / sizeof(unsigned short)]; + + memset(cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd[0] = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_PCM; + else + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + audpp_cmd_cfg_adec_params_wav cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_WAV_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + cmd.stereo_cfg = audio->out_channel_mode; + cmd.pcm_width = audio->out_bits; + cmd.sign = 0; + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + audplay_cmd_bitstream_data_avail cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL; + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len/2; + cmd.partition_number = 0; + /* complete writes to the input buffer */ + wmb(); + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audpcm_async_send_data(struct audio *audio, unsigned needed) +{ + unsigned long flags; + + if (!audio->running) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + + if (needed && !audio->wflush) { + audio->out_needed = 1; + if (audio->drv_status & ADRV_STATUS_OBUF_GIVEN) { + /* pop one node out of queue */ + union msm_audio_event_payload payload; + struct audpcm_buffer_node *used_buf; + + MM_DBG("consumed\n"); + + BUG_ON(list_empty(&audio->out_queue)); + used_buf = list_first_entry(&audio->out_queue, + struct audpcm_buffer_node, list); + list_del(&used_buf->list); + payload.aio_buf = used_buf->buf; + audpcm_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(used_buf); + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + } + } + if (audio->out_needed) { + struct audpcm_buffer_node *next_buf; + audplay_cmd_bitstream_data_avail cmd; + if (!list_empty(&audio->out_queue)) { + next_buf = list_first_entry(&audio->out_queue, + struct audpcm_buffer_node, list); + MM_DBG("next_buf %p\n", next_buf); + if (next_buf) { + MM_DBG("next buf phy %lx len %d\n", + next_buf->paddr, next_buf->buf.data_len); + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL; + if (next_buf->buf.data_len) + cmd.decoder_id = audio->dec_id; + else { + cmd.decoder_id = -1; + MM_DBG("input EOS signaled\n"); + } + cmd.buf_ptr = (unsigned) next_buf->paddr; + cmd.buf_size = next_buf->buf.data_len >> 1; + cmd.partition_number = 0; + /* complete writes to the input buffer */ + wmb(); + audplay_send_queue0(audio, &cmd, sizeof(cmd)); + audio->out_needed = 0; + audio->drv_status |= ADRV_STATUS_OBUF_GIVEN; + } + } + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + if (!audio->running) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ +static void audpcm_async_flush(struct audio *audio) +{ + struct audpcm_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audpcm_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audpcm_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(buf_node); + } + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_ioport_reset(struct audio *audio) +{ + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + /* If fsync is in progress, make sure + * return value of fsync indicates + * abort due to flush + */ + if (audio->drv_status & ADRV_STATUS_FSYNC) { + MM_DBG("fsync in progress\n"); + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio->drv_ops.out_flush(audio); + mutex_unlock(&audio->write_lock); + } else + audio->drv_ops.out_flush(audio); + } else { + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio->drv_ops.out_flush(audio); + mutex_unlock(&audio->write_lock); + } +} + +static int audpcm_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audpcm_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audpcm_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audpcm_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audpcm_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audpcm_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audpcm_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audpcm_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audpcm_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audpcm_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (drv_evt && drv_evt->event_type == AUDIO_EVENT_WRITE_DONE) { + mutex_lock(&audio->lock); + audpcm_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0); + mutex_unlock(&audio->lock); + } + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audpcm_pmem_check(struct audio *audio, + void *vaddr, unsigned long len) +{ + struct audpcm_pmem_region *region_elt; + struct audpcm_pmem_region t = { .vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + MM_ERR("region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, + region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audpcm_pmem_add(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audpcm_pmem_region *region; + struct vm_area_struct *vma; + int rc = -EINVAL; + + MM_DBG("\n"); /* Macro prints the file name and function */ + region = kmalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + return -EINVAL; + } + + vma = find_vma_intersection(current->active_mm, + (unsigned long) info->vaddr, (unsigned long) info->vaddr+1); + + if (vma && ((vma->vm_end - vma->vm_start) == len)) { + rc = audpcm_pmem_check(audio, (void *) vma->vm_start, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + return rc; + } + region->vaddr = (void *) vma->vm_start; + region->vaddr_ref = info->vaddr; + MM_DBG("Valid VMA region vma->vm_start = 0x%8x \ + vma->vm_end = 0x%8x\n", (int) vma->vm_start, + (int) vma->vm_end); + } else { + MM_ERR("No valid VMA region found\n"); + put_pmem_file(file); + kfree(region); + return rc; + } + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + MM_DBG("add region paddr %lx vaddr %p, len %lu\n", region->paddr, + region->vaddr, region->len); + list_add_tail(®ion->list, &audio->pmem_region_queue); + return rc; +} + +static int audpcm_pmem_remove(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audpcm_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + MM_DBG("info fd %d vaddr %p\n", info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audpcm_pmem_region, list); + + if ((region->fd == info->fd) && + (region->vaddr_ref == info->vaddr)) { + if (region->ref_cnt) { + MM_DBG("region %p in use ref_cnt %d\n", + region, region->ref_cnt); + break; + } + MM_DBG("remove region fd %d vaddr %p \n", info->fd, + info->vaddr); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +static int audpcm_pmem_lookup_vaddr(struct audio *audio, void *addr, + unsigned long len, struct audpcm_pmem_region **region) +{ + struct audpcm_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + MM_ERR("multiple hits for vaddr %p, len %ld\n", addr, len); + list_for_each_entry(region_elt, + &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + MM_ERR("\t%p, %ld --> %p\n", region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +static unsigned long audpcm_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up) +{ + struct audpcm_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audpcm_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + MM_ERR("lookup (%p, %ld) failed\n", addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + MM_DBG("found region %p ref_cnt %d\n", region, region->ref_cnt); + paddr = region->paddr + (addr - region->vaddr); + return paddr; +} + +/* audio -> lock must be held at this point */ +static int audpcm_aio_buf_add(struct audio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audpcm_buffer_node *buf_node; + + buf_node = kmalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + MM_DBG("node %p dir %x buf_addr %p buf_len %d data_len %d\n", + buf_node, dir, buf_node->buf.buf_addr, + buf_node->buf.buf_len, buf_node->buf.data_len); + + buf_node->paddr = audpcm_pmem_fixup( + audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1); + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.data_len & 0x1) || + (!buf_node->buf.data_len)) { + kfree(buf_node); + return -EINVAL; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + list_add_tail(&buf_node->list, &audio->out_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + audio->drv_ops.send_data(audio, 0); + } + + MM_DBG("Add buf_node %p paddr %lx\n", buf_node, buf_node->paddr); + + return 0; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = 0; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = audpp_avsync_byte_count(audio->dec_id); + stats.sample_count = audpp_avsync_sample_count(audio->dec_id); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + if (cmd == AUDIO_SET_VOLUME) { + unsigned long flags; + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->volume = arg; + if (audio->running) + audpp_set_volume_and_pan(audio->dec_id, arg, 0); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + return 0; + } + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audpcm_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->wflush = 0; + } + break; + + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + if (config.bits == 8) + config.bits = AUDPP_CMD_WAV_PCM_WIDTH_8; + else if (config.bits == 16) + config.bits = AUDPP_CMD_WAV_PCM_WIDTH_16; + else if (config.bits == 24) + config.bits = AUDPP_CMD_WAV_PCM_WIDTH_24; + else { + rc = -EINVAL; + break; + } + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + audio->out_bits = config.bits; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + if (audio->out_bits == AUDPP_CMD_WAV_PCM_WIDTH_8) + config.bits = 8; + else if (audio->out_bits == AUDPP_CMD_WAV_PCM_WIDTH_24) + config.bits = 24; + else + config.bits = 16; + config.unused[0] = 0; + config.unused[1] = 0; + + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audpcm_pmem_add(audio, &info); + break; + } + + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_DEREGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audpcm_pmem_remove(audio, &info); + break; + } + + case AUDIO_ASYNC_WRITE: + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else + rc = audpcm_aio_buf_add(audio, 1, (void __user *) arg); + break; + + case AUDIO_ASYNC_READ: + MM_ERR("AUDIO_ASYNC_READ not supported\n"); + rc = -EPERM; + break; + + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +int audpcm_async_fsync(struct audio *audio) +{ + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + mutex_lock(&audio->write_lock); + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + (audio->teos && audio->out_needed && + list_empty(&audio->out_queue)) + || audio->wflush || audio->stopped); + + if (audio->stopped || audio->wflush) + rc = -EBUSY; + + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +int audpcm_sync_fsync(struct audio *audio) +{ + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audio->drv_ops.send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + return rc; +} + +int audpcm_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + + if (!audio->running) + return -EINVAL; + + return audio->drv_ops.fsync(audio); +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0; + unsigned dsize; + + if (audio->drv_status & ADRV_STATUS_AIO_INTF) + return -EPERM; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > (frame->size - 1)) ? + frame->size - 1 : count; + cpy_ptr++; + dsize = 1; + audio->reserved = 0; + } else + xfer = (count > frame->size) ? frame->size : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audio->drv_ops.send_data(audio, 0); + } + } + mutex_unlock(&audio->write_lock); + if (buf > start) + return buf - start; + + return rc; +} + +static void audpcm_reset_pmem_region(struct audio *audio) +{ + struct audpcm_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audpcm_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_DBG("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + audio_disable(audio); + if (audio->rmt_resource_released == 0) + rmt_put_resource(audio); + audio->drv_ops.out_flush(audio); + audpcm_reset_pmem_region(audio); + + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->opened = 0; + audio->event_abort = 1; + wake_up(&audio->event_wait); + audpcm_reset_event_queue(audio); + MM_DBG("pmem area = 0x%8x\n", (unsigned int)audio->data); + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audpcm_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audpcm_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audpcm_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audpcm_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audpcm_suspend(struct early_suspend *h) +{ + struct audpcm_suspend_ctl *ctl = + container_of(h, struct audpcm_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audpcm_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audpcm_resume(struct early_suspend *h) +{ + struct audpcm_suspend_ctl *ctl = + container_of(h, struct audpcm_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audpcm_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audpcm_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audpcm_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audpcm_debug_fops = { + .read = audpcm_debug_read, + .open = audpcm_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, i, dec_attrb, decid; + struct audpcm_event *e_node = NULL; + unsigned pmem_sz = DMASZ_MAX; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_pcm_dec_" + 5]; +#endif + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_DBG("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_PCM; + if (file->f_mode & FMODE_READ) { + MM_ERR("Non-Tunneled mode not supported\n"); + rc = -EPERM; + kfree(audio); + goto done; + } else + dec_attrb |= MSM_AUD_MODE_TUNNEL; + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + if (decid < 0) { + MM_ERR("No free decoder available\n"); + rc = -ENODEV; + MM_DBG("audio instance 0x%08x freeing\n", (int)audio); + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + /* Non AIO interface */ + if (!(file->f_flags & O_NONBLOCK)) { + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d \n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write\ + buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + MM_DBG("audio instance 0x%08x\ + freeing\n", (int)audio); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr\ + 0x%08x\n", audio->phys,\ + (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + MM_DBG("audio instance 0x%08x freeing\n",\ + (int)audio); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + } + + rc = audmgr_open(&audio->audmgr); + if (rc) + goto err; + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audpcmdec_adsp_ops, audio); + if (rc) { + MM_ERR("failed to get %s module\n", audio->module_name); + audmgr_close(&audio->audmgr); + goto err; + } + + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for PCM session \ + 0x%08x on decoder: %d\n", (int)audio, audio->dec_id); + audmgr_close(&audio->audmgr); + msm_adsp_put(audio->audplay); + goto err; + } + + if (file->f_flags & O_NONBLOCK) { + MM_DBG("set to aio interface\n"); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + audio->drv_ops.send_data = audpcm_async_send_data; + audio->drv_ops.out_flush = audpcm_async_flush; + audio->drv_ops.fsync = audpcm_async_fsync; + } else { + MM_DBG("set to std io interface\n"); + audio->drv_ops.send_data = audplay_send_data; + audio->drv_ops.out_flush = audio_flush; + audio->drv_ops.fsync = audpcm_sync_fsync; + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = (audio->out_dma_sz >> 1); + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + } + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + audio->out_bits = AUDPP_CMD_WAV_PCM_WIDTH_16; + audio->volume = 0x2000; + audio->drv_ops.out_flush(audio); + + file->private_data = audio; + audio->opened = 1; + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_pcm_dec_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audpcm_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audpcm_resume; + audio->suspend_ctl.node.suspend = audpcm_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDPCM_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audpcm_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +err: + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + } + audpp_adec_free(audio->dec_id); + MM_DBG("audio instance 0x%08x freeing\n", (int)audio); + kfree(audio); + return rc; +} + +static const struct file_operations audio_pcm_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audpcm_fsync, +}; + +struct miscdevice audio_pcm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_dec", + .fops = &audio_pcm_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_pcm_misc); +} + +device_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5/audio_pcm_in.c b/arch/arm/mach-msm/qdsp5/audio_pcm_in.c new file mode 100644 index 00000000000..0b35f1a0962 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_pcm_in.c @@ -0,0 +1,950 @@ +/* arch/arm/mach-msm/qdsp5/audio_pcm_in.c + * + * pcm audio input device + * + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This code is based in part on arch/arm/mach-msm/qdsp5v2/audio_pcm_in.c, + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (8) +#define FRAME_SIZE (2052 * 2) +#define MONO_DATA_SIZE (2048) +#define STEREO_DATA_SIZE (MONO_DATA_SIZE * 2) +#define DMASZ (FRAME_SIZE * FRAME_NUM) + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; +}; + +struct audio_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + + struct msm_adsp_module *audpre; + struct msm_adsp_module *audrec; + const char *module_name; + unsigned queue_ids; + uint16_t enc_id; /* Session Id */ + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* 2048 for mono, 4096 for stereo */ + uint32_t enc_type; /* 0 for PCM */ + uint32_t mode; /* Tunnel for PCM */ + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + unsigned short samp_rate_index; + uint32_t audrec_obj_idx ; + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + + /* audpre settings */ + int tx_agc_enable; + audpreproc_cmd_cfg_agc_params tx_agc_cfg; + int ns_enable; + audpreproc_cmd_cfg_ns_params ns_cfg; + /* For different sample rate, the coeff might be different. * + * All the coeff should be passed from user space */ + int iir_enable; + audpreproc_cmd_cfg_iir_tuning_filter_params iir_cfg; +}; + +static int audpcm_in_dsp_enable(struct audio_in *audio, int enable); +static int audpcm_in_encmem_config(struct audio_in *audio); +static int audpcm_in_encparam_config(struct audio_in *audio); +static int audpcm_in_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt); +static void audpcm_in_flush(struct audio_in *audio); +static int audio_dsp_set_tx_agc(struct audio_in *audio); +static int audio_dsp_set_ns(struct audio_in *audio); +static int audio_dsp_set_iir(struct audio_in *audio); + +static unsigned convert_dsp_samp_index(unsigned index) +{ + switch (index) { + case 48000: return AUDREC_CMD_SAMP_RATE_INDX_48000; + case 44100: return AUDREC_CMD_SAMP_RATE_INDX_44100; + case 32000: return AUDREC_CMD_SAMP_RATE_INDX_32000; + case 24000: return AUDREC_CMD_SAMP_RATE_INDX_24000; + case 22050: return AUDREC_CMD_SAMP_RATE_INDX_22050; + case 16000: return AUDREC_CMD_SAMP_RATE_INDX_16000; + case 12000: return AUDREC_CMD_SAMP_RATE_INDX_12000; + case 11025: return AUDREC_CMD_SAMP_RATE_INDX_11025; + case 8000: return AUDREC_CMD_SAMP_RATE_INDX_8000; + default: return AUDREC_CMD_SAMP_RATE_INDX_11025; + } +} + +static unsigned convert_samp_rate(unsigned hz) +{ + switch (hz) { + case 48000: return RPC_AUD_DEF_SAMPLE_RATE_48000; + case 44100: return RPC_AUD_DEF_SAMPLE_RATE_44100; + case 32000: return RPC_AUD_DEF_SAMPLE_RATE_32000; + case 24000: return RPC_AUD_DEF_SAMPLE_RATE_24000; + case 22050: return RPC_AUD_DEF_SAMPLE_RATE_22050; + case 16000: return RPC_AUD_DEF_SAMPLE_RATE_16000; + case 12000: return RPC_AUD_DEF_SAMPLE_RATE_12000; + case 11025: return RPC_AUD_DEF_SAMPLE_RATE_11025; + case 8000: return RPC_AUD_DEF_SAMPLE_RATE_8000; + default: return RPC_AUD_DEF_SAMPLE_RATE_11025; + } +} + +static unsigned convert_samp_index(unsigned index) +{ + switch (index) { + case RPC_AUD_DEF_SAMPLE_RATE_48000: return 48000; + case RPC_AUD_DEF_SAMPLE_RATE_44100: return 44100; + case RPC_AUD_DEF_SAMPLE_RATE_32000: return 32000; + case RPC_AUD_DEF_SAMPLE_RATE_24000: return 24000; + case RPC_AUD_DEF_SAMPLE_RATE_22050: return 22050; + case RPC_AUD_DEF_SAMPLE_RATE_16000: return 16000; + case RPC_AUD_DEF_SAMPLE_RATE_12000: return 12000; + case RPC_AUD_DEF_SAMPLE_RATE_11025: return 11025; + case RPC_AUD_DEF_SAMPLE_RATE_8000: return 8000; + default: return 11025; + } +} + +/* must be called with audio->lock held */ +static int audpcm_in_enable(struct audio_in *audio) +{ + struct audmgr_config cfg; + int rc; + + if (audio->enabled) + return 0; + + cfg.tx_rate = audio->samp_rate; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + + if (msm_adsp_enable(audio->audpre)) { + MM_ERR("msm_adsp_enable(audpre) failed\n"); + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + if (msm_adsp_enable(audio->audrec)) { + audmgr_disable(&audio->audmgr); + msm_adsp_disable(audio->audpre); + MM_ERR("msm_adsp_enable(audrec) failed\n"); + return -ENODEV; + } + + audio->enabled = 1; + audpcm_in_dsp_enable(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audpcm_in_disable(struct audio_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + + audpcm_in_dsp_enable(audio, 0); + + wake_up(&audio->wait); + + msm_adsp_disable(audio->audrec); + msm_adsp_disable(audio->audpre); + audmgr_disable(&audio->audmgr); + } + return 0; +} + +/* ------------------- dsp --------------------- */ +static void audpre_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + uint16_t msg[2]; + getevent(msg, sizeof(msg)); + + switch (id) { + case AUDPREPROC_MSG_CMD_CFG_DONE_MSG: + MM_INFO("type %d, status_flag %d\n", msg[0], msg[1]); + break; + case AUDPREPROC_MSG_ERROR_MSG_ID: + MM_ERR("err_index %d\n", msg[0]); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audpreproctask)\n"); + break; + default: + MM_ERR("unknown event %d\n", id); + } +} + +struct audio_frame { + uint16_t count_low; + uint16_t count_high; + uint16_t bytes; + uint16_t unknown; + unsigned char samples[]; +} __packed; + +static void audpcm_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - + sizeof(*frame)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = frame->bytes; + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) { + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + MM_ERR("Error! not able to keep up the read\n"); + } else + audio->in_count++; + + audpcm_in_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_in *audio = NULL; + uint16_t msg[3]; + + if (data) + audio = data; + else { + MM_ERR("invalid data for event %x\n", id); + return; + } + + getevent(msg, sizeof(msg)); + + switch (id) { + case AUDREC_MSG_CMD_CFG_DONE_MSG: { + if (msg[0] & AUDREC_MSG_CFG_DONE_ENC_ENA) { + audio->audrec_obj_idx = msg[1]; + MM_INFO("CFG ENABLED\n"); + audpcm_in_encmem_config(audio); + } else { + MM_INFO("CFG SLEEP\n"); + audio->running = 0; + audio->tx_agc_enable = 0; + audio->ns_enable = 0; + audio->iir_enable = 0; + } + break; + } + case AUDREC_MSG_CMD_AREC_MEM_CFG_DONE_MSG: { + MM_DBG("AREC_MEM_CFG_DONE_MSG\n"); + audpcm_in_encparam_config(audio); + break; + } + case AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG: { + MM_INFO("PARAM CFG DONE\n"); + audio->running = 1; + audio_dsp_set_tx_agc(audio); + audio_dsp_set_ns(audio); + audio_dsp_set_iir(audio); + break; + } + case AUDREC_MSG_NO_EXT_PKT_AVAILABLE_MSG: { + MM_DBG("ERROR %x\n", msg[0]); + break; + } + case AUDREC_MSG_PACKET_READY_MSG: { + struct audrec_msg_packet_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_MSG_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt msw %d \ + write cnt lsw %d read cnt msw %d read cnt lsw %d \n",\ + pkt_ready_msg.pkt_counter_msw, \ + pkt_ready_msg.pkt_counter_lsw, \ + pkt_ready_msg.pkt_read_cnt_msw, \ + pkt_ready_msg.pkt_read_cnt_lsw); + + audpcm_in_get_dsp_frames(audio); + break; + } + case ADSP_MESSAGE_ID: { + MM_DBG("Received ADSP event: module \ + enable/disable(audrectask)\n"); + break; + } + default: + MM_ERR("unknown event %d\n", id); + } +} + +static struct msm_adsp_ops audpre_adsp_ops = { + .event = audpre_dsp_event, +}; + +static struct msm_adsp_ops audrec_adsp_ops = { + .event = audrec_dsp_event, +}; + + +#define audio_send_queue_pre(audio, cmd, len) \ + msm_adsp_write(audio->audpre, QDSP_uPAudPreProcCmdQueue, cmd, len) + +#define audio_send_queue_recbs(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_ids & 0xFFFF0000) >> 16),\ + cmd, len) +#define audio_send_queue_rec(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_ids & 0x0000FFFF),\ + cmd, len) + +static int audio_dsp_set_tx_agc(struct audio_in *audio) +{ + audpreproc_cmd_cfg_agc_params cmd; + + memset(&cmd, 0, sizeof(cmd)); + + audio->tx_agc_cfg.cmd_id = AUDPREPROC_CMD_CFG_AGC_PARAMS; + if (audio->tx_agc_enable) { + /* cmd.tx_agc_param_mask = 0xFE00 from sample code */ + audio->tx_agc_cfg.tx_agc_param_mask = + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_SLOPE) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_TH) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_EXP_SLOPE) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_EXP_TH) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_AIG_FLAG) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_COMP_STATIC_GAIN) | + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_TX_AGC_ENA_FLAG); + audio->tx_agc_cfg.tx_agc_enable_flag = + AUDPREPROC_CMD_TX_AGC_ENA_FLAG_ENA; + /* cmd.param_mask = 0xFFF0 from sample code */ + audio->tx_agc_cfg.param_mask = + (1 << AUDPREPROC_CMD_PARAM_MASK_RMS_TAY) | + (1 << AUDPREPROC_CMD_PARAM_MASK_RELEASEK) | + (1 << AUDPREPROC_CMD_PARAM_MASK_DELAY) | + (1 << AUDPREPROC_CMD_PARAM_MASK_ATTACKK) | + (1 << AUDPREPROC_CMD_PARAM_MASK_LEAKRATE_SLOW) | + (1 << AUDPREPROC_CMD_PARAM_MASK_LEAKRATE_FAST) | + (1 << AUDPREPROC_CMD_PARAM_MASK_AIG_RELEASEK) | + (1 << AUDPREPROC_CMD_PARAM_MASK_AIG_MIN) | + (1 << AUDPREPROC_CMD_PARAM_MASK_AIG_MAX) | + (1 << AUDPREPROC_CMD_PARAM_MASK_LEAK_UP) | + (1 << AUDPREPROC_CMD_PARAM_MASK_LEAK_DOWN) | + (1 << AUDPREPROC_CMD_PARAM_MASK_AIG_ATTACKK); + } else { + audio->tx_agc_cfg.tx_agc_param_mask = + (1 << AUDPREPROC_CMD_TX_AGC_PARAM_MASK_TX_AGC_ENA_FLAG); + audio->tx_agc_cfg.tx_agc_enable_flag = + AUDPREPROC_CMD_TX_AGC_ENA_FLAG_DIS; + } + cmd = audio->tx_agc_cfg; + + return audio_send_queue_pre(audio, &cmd, sizeof(cmd)); +} + +static int audio_enable_tx_agc(struct audio_in *audio, int enable) +{ + if (audio->tx_agc_enable != enable) { + audio->tx_agc_enable = enable; + if (audio->running) + audio_dsp_set_tx_agc(audio); + } + return 0; +} + +static int audio_dsp_set_ns(struct audio_in *audio) +{ + audpreproc_cmd_cfg_ns_params cmd; + + memset(&cmd, 0, sizeof(cmd)); + + audio->ns_cfg.cmd_id = AUDPREPROC_CMD_CFG_NS_PARAMS; + + if (audio->ns_enable) { + /* cmd.ec_mode_new is fixed as 0x0064 when enable + * from sample code */ + audio->ns_cfg.ec_mode_new = + AUDPREPROC_CMD_EC_MODE_NEW_NS_ENA | + AUDPREPROC_CMD_EC_MODE_NEW_HB_ENA | + AUDPREPROC_CMD_EC_MODE_NEW_VA_ENA; + } else { + audio->ns_cfg.ec_mode_new = + AUDPREPROC_CMD_EC_MODE_NEW_NLMS_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_DES_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_NS_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_CNI_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_NLES_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_HB_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_VA_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_PCD_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_FEHI_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_NEHI_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_NLPP_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_FNE_DIS | + AUDPREPROC_CMD_EC_MODE_NEW_PRENLMS_DIS; + } + cmd = audio->ns_cfg; + + return audio_send_queue_pre(audio, &cmd, sizeof(cmd)); +} + +static int audio_enable_ns(struct audio_in *audio, int enable) +{ + if (audio->ns_enable != enable) { + audio->ns_enable = enable; + if (audio->running) + audio_dsp_set_ns(audio); + } + return 0; +} + +static int audio_dsp_set_iir(struct audio_in *audio) +{ + audpreproc_cmd_cfg_iir_tuning_filter_params cmd; + + memset(&cmd, 0, sizeof(cmd)); + + audio->iir_cfg.cmd_id = AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS; + + if (audio->iir_enable) + /* cmd.active_flag is 0xFFFF from sample code but 0x0001 here */ + audio->iir_cfg.active_flag = AUDPREPROC_CMD_IIR_ACTIVE_FLAG_ENA; + else + audio->iir_cfg.active_flag = AUDPREPROC_CMD_IIR_ACTIVE_FLAG_DIS; + + cmd = audio->iir_cfg; + + return audio_send_queue_pre(audio, &cmd, sizeof(cmd)); +} + +static int audio_enable_iir(struct audio_in *audio, int enable) +{ + if (audio->iir_enable != enable) { + audio->iir_enable = enable; + if (audio->running) + audio_dsp_set_iir(audio); + } + return 0; +} + +static int audpcm_in_dsp_enable(struct audio_in *audio, int enable) +{ + struct audrec_cmd_enc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_ENC_CFG; + + cmd.audrec_enc_type = (audio->enc_type & 0xFF) | + (enable ? AUDREC_CMD_ENC_ENA : AUDREC_CMD_ENC_DIS); + /* Don't care */ + cmd.audrec_obj_idx = audio->audrec_obj_idx; + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audpcm_in_encmem_config(struct audio_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t cnt = 0; + uint16_t *data = (void *) audio->data; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.cmd_id = AUDREC_CMD_ARECMEM_CFG; + cmd.audrec_obj_idx = audio->audrec_obj_idx; + /* Rate at which packet complete message comes */ + cmd.audrec_up_pkt_intm_cnt = 1; + cmd.audrec_extpkt_buffer_msw = audio->phys >> 16; + cmd.audrec_extpkt_buffer_lsw = audio->phys; + /* Max Buffer no available for frames */ + cmd.audrec_extpkt_buffer_num = FRAME_NUM; + + /* prepare buffer pointers: + * Mono: 1024 samples + 4 halfword header + * Stereo: 2048 samples + 4 halfword header + */ + for (cnt = 0; cnt < FRAME_NUM; cnt++) { + audio->in[cnt].data = data + 4; + data += (4 + (audio->channel_mode ? 2048 : 1024)); + } + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audpcm_in_encparam_config(struct audio_in *audio) +{ + struct audrec_cmd_arecparam_wav_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDREC_CMD_ARECPARAM_CFG; + cmd.common.audrec_obj_idx = audio->audrec_obj_idx; + cmd.samp_rate_idx = audio->samp_rate_index; + cmd.stereo_mode = audio->channel_mode; /* 0 for mono, 1 for stereo */ + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audpcm_in_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt) +{ + audrec_cmd_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PACKET_EXT_PTR; + cmd.type = audio->audrec_obj_idx; + cmd.curr_rec_count_msw = read_cnt >> 16; + cmd.curr_rec_count_lsw = read_cnt; + + return audio_send_queue_recbs(audio, &cmd, sizeof(cmd)); +} + +/* ------------------- device --------------------- */ + +static void audpcm_in_flush(struct audio_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + for (i = FRAME_NUM-1; i <= 0; i--) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } +} + +static long audpcm_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_in *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + rc = audpcm_in_enable(audio); + audio->stopped = 0; + break; + } + case AUDIO_STOP: + rc = audpcm_in_disable(audio); + audio->stopped = 1; + break; + case AUDIO_FLUSH: + if (audio->stopped) { + /* Make sure we're stopped and we wake any threads + * that might be blocked holding the read_lock. + * While audio->stopped read threads will always + * exit immediately. + */ + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audpcm_in_flush(audio); + mutex_unlock(&audio->read_lock); + } + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config cfg; + + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.channel_count == 1) { + cfg.channel_count = AUDREC_CMD_STEREO_MODE_MONO; + } else if (cfg.channel_count == 2) { + cfg.channel_count = AUDREC_CMD_STEREO_MODE_STEREO; + } else { + rc = -EINVAL; + break; + } + + audio->samp_rate = convert_samp_rate(cfg.sample_rate); + audio->samp_rate_index = + convert_dsp_samp_index(cfg.sample_rate); + audio->channel_mode = cfg.channel_count; + audio->buffer_size = + audio->channel_mode ? STEREO_DATA_SIZE + : MONO_DATA_SIZE; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + cfg.sample_rate = convert_samp_index(audio->samp_rate); + if (audio->channel_mode == AUDREC_CMD_STEREO_MODE_MONO) + cfg.channel_count = 1; + else + cfg.channel_count = 2; + cfg.type = 0; + cfg.unused[0] = 0; + cfg.unused[1] = 0; + cfg.unused[2] = 0; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audpcm_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped); + if (rc < 0) + break; + + if (audio->stopped && !audio->in_count) { + rc = 0;/* End of File */ + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + if (count >= size) { + /* order the reads on the buffer */ + dma_coherent_post_ops(); + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is invalid and we need to + * retry + */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + } else { + MM_ERR("short read\n"); + break; + } + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static ssize_t audpcm_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + return -EINVAL; +} + +static int audpcm_in_release(struct inode *inode, struct file *file) +{ + struct audio_in *audio = file->private_data; + + mutex_lock(&audio->lock); + audpcm_in_disable(audio); + audpcm_in_flush(audio); + audpreproc_aenc_free(audio->enc_id); + msm_adsp_put(audio->audrec); + msm_adsp_put(audio->audpre); + audio->audrec = NULL; + audio->audpre = NULL; + audio->opened = 0; + if (audio->data) { + free_contiguous_memory((void *)audio->data); + audio->data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +static struct audio_in the_audio_in; + +static int audpcm_in_open(struct inode *inode, struct file *file) +{ + struct audio_in *audio = &the_audio_in; + int rc; + + int encid; + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->mode = MSM_AUD_ENC_MODE_TUNNEL; + audio->samp_rate = RPC_AUD_DEF_SAMPLE_RATE_11025; + audio->samp_rate_index = AUDREC_CMD_SAMP_RATE_INDX_11025; + audio->channel_mode = AUDREC_CMD_STEREO_MODE_MONO; + audio->buffer_size = MONO_DATA_SIZE; + audio->enc_type = AUDREC_CMD_TYPE_0_INDEX_WAV | audio->mode; + + rc = audmgr_open(&audio->audmgr); + if (rc) + goto done; + encid = audpreproc_aenc_alloc(audio->enc_type, &audio->module_name, + &audio->queue_ids); + if (encid < 0) { + MM_ERR("No free encoder available\n"); + rc = -ENODEV; + goto done; + } + audio->enc_id = encid; + + rc = msm_adsp_get(audio->module_name, &audio->audrec, + &audrec_adsp_ops, audio); + if (rc) { + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + rc = msm_adsp_get("AUDPREPROCTASK", &audio->audpre, + &audpre_adsp_ops, audio); + if (rc) { + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + audio->dsp_cnt = 0; + audio->stopped = 0; + + audpcm_in_flush(audio); + + audio->data = allocate_contiguous_memory(DMASZ, MEMTYPE_EBI1, + SZ_4K, 0); + if (!audio->data) { + MM_ERR("could not allocate read buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->phys = memory_pool_node_paddr(audio->data); + if (!audio->phys) { + MM_ERR("could not get physical address\n"); + rc = -ENOMEM; + free_contiguous_memory(audio->data); + goto evt_error; + } + MM_DBG("read buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + } + + file->private_data = audio; + audio->opened = 1; + rc = 0; +done: + mutex_unlock(&audio->lock); + return rc; +evt_error: + msm_adsp_put(audio->audrec); + msm_adsp_put(audio->audpre); + audpreproc_aenc_free(audio->enc_id); + mutex_unlock(&audio->lock); + return rc; +} + +static long audpre_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio_in *audio = file->private_data; + int rc = 0, enable; + uint16_t enable_mask; + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_ENABLE_AUDPRE: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + enable = (enable_mask & AGC_ENABLE) ? 1 : 0; + audio_enable_tx_agc(audio, enable); + enable = (enable_mask & NS_ENABLE) ? 1 : 0; + audio_enable_ns(audio, enable); + enable = (enable_mask & TX_IIR_ENABLE) ? 1 : 0; + audio_enable_iir(audio, enable); + break; + + case AUDIO_SET_AGC: + if (copy_from_user(&audio->tx_agc_cfg, (void *) arg, + sizeof(audio->tx_agc_cfg))) + rc = -EFAULT; + break; + + case AUDIO_SET_NS: + if (copy_from_user(&audio->ns_cfg, (void *) arg, + sizeof(audio->ns_cfg))) + rc = -EFAULT; + break; + + case AUDIO_SET_TX_IIR: + if (copy_from_user(&audio->iir_cfg, (void *) arg, + sizeof(audio->iir_cfg))) + rc = -EFAULT; + break; + + default: + rc = -EINVAL; + } + + mutex_unlock(&audio->lock); + return rc; +} + +static int audpre_open(struct inode *inode, struct file *file) +{ + struct audio_in *audio = &the_audio_in; + + file->private_data = audio; + + return 0; +} + +static const struct file_operations audio_fops = { + .owner = THIS_MODULE, + .open = audpcm_in_open, + .release = audpcm_in_release, + .read = audpcm_in_read, + .write = audpcm_in_write, + .unlocked_ioctl = audpcm_in_ioctl, +}; + +static struct miscdevice audpcm_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_in", + .fops = &audio_fops, +}; + +static const struct file_operations audpre_fops = { + .owner = THIS_MODULE, + .open = audpre_open, + .unlocked_ioctl = audpre_ioctl, +}; + +static struct miscdevice audpre_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_preproc_ctl", + .fops = &audpre_fops, +}; + +static int __init audpcm_in_init(void) +{ + + mutex_init(&the_audio_in.lock); + mutex_init(&the_audio_in.read_lock); + spin_lock_init(&the_audio_in.dsp_lock); + init_waitqueue_head(&the_audio_in.wait); + return misc_register(&audpcm_in_misc) || misc_register(&audpre_misc); +} +device_initcall(audpcm_in_init); diff --git a/arch/arm/mach-msm/qdsp5/audio_qcelp.c b/arch/arm/mach-msm/qdsp5/audio_qcelp.c new file mode 100644 index 00000000000..5b0a9cdb275 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_qcelp.c @@ -0,0 +1,1602 @@ +/* arch/arm/mach-msm/qdsp5/audio_qcelp.c + * + * qcelp 13k audio decoder device + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * This code is based in part on audio_mp3.c, which is + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audmgr.h" + +#define BUFSZ 1094 /* QCELP 13K Hold 600ms packet data = 36 * 30 and + 14 bytes of meta in */ +#define BUF_COUNT 2 +#define DMASZ (BUFSZ * BUF_COUNT) + +#define PCM_BUFSZ_MIN 1624 /* 100ms worth of data and + 24 bytes of meta out */ +#define PCM_BUF_MAX_COUNT 5 + +#define AUDDEC_DEC_QCELP 9 + +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDQCELP_METAFIELD_MASK 0xFFFF0000 +#define AUDQCELP_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDQCELP_EOS_FLG_MASK 0x01 +#define AUDQCELP_EOS_NONE 0x0 /* No EOS detected */ +#define AUDQCELP_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDQCELP_EVENT_NUM 10 /* Default number of pre-allocated event pkts */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audqcelp_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audqcelp_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[BUF_COUNT]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section - START */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* Host PCM section - END */ + + struct msm_adsp_module *audplay; + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + uint8_t opened:1; + uint8_t enabled:1; + uint8_t running:1; + uint8_t stopped:1; /* set when stopped, cleared on flush */ + uint8_t pcm_feedback:1; /* set when non-tunnel mode */ + uint8_t buf_refresh:1; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int rmt_resource_released; + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audqcelp_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + int eq_enable; + int eq_needs_commit; + audpp_cmd_cfg_object_params_eqalizer eq; + audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audqcelp_send_data(struct audio *audio, unsigned needed); +static void audqcelp_config_hostpcm(struct audio *audio); +static void audqcelp_buffer_refresh(struct audio *audio); +static void audqcelp_dsp_event(void *private, unsigned id, uint16_t *msg); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audqcelp_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +static int rmt_put_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_DISABLE; + cmd.dec_type = AUDDEC_DEC_QCELP; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return put_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +static int rmt_get_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_ENABLE; + cmd.dec_type = AUDDEC_DEC_QCELP; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return get_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audqcelp_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + if (audio->rmt_resource_released == 1) { + audio->rmt_resource_released = 0; + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for QCELP \ + session 0x%08x on decoder: %d\n Ignoring \ + error and going ahead with the playback\n", + (int)audio, audio->dec_id); + } + } + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_PLAYBACK; + cfg.codec = RPC_AUD_DEF_CODEC_13K; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + } + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audqcelp_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audqcelp_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + rmt_put_resource(audio); + audio->rmt_resource_released = 1; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audqcelp_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audqcelp_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audqcelp_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audqcelp_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + } +} + +static void audqcelp_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + if (audio->pcm_feedback) { + audqcelp_config_hostpcm(audio); + audqcelp_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status\n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq); + audpp_avsync(audio->dec_id, 22050); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audpp_avsync(audio->dec_id, 0); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audqcelp_buffer_refresh(audio); + break; + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_qcelp = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + u16 cfg_dec_cmd[AUDPP_CMD_CFG_DEC_TYPE_LEN / sizeof(unsigned short)]; + + memset(cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd[0] = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_QCELP; + else + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_v13k cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_V13K_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = 8000; + cmd.stereo_cfg = AUDPP_CMD_PCM_INTF_MONO_V; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDQCELP_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + /* complete writes to the input buffer */ + wmb(); + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audqcelp_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audqcelp_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = 1; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); +} + +static void audqcelp_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audqcelp_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->out_needed = 0; +} + +static void audqcelp_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audqcelp_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audqcelp_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audqcelp_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); +} + +static int audqcelp_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audqcelp_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audqcelp_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audqcelp_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audqcelp_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + + +static long audqcelp_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audqcelp_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audqcelp_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audqcelp_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audqcelp_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq); + audio->eq_needs_commit = 0; + } + return 0; +} + +static long audqcelp_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = audpp_avsync_byte_count(audio->dec_id); + stats.sample_count = audpp_avsync_sample_count(audio->dec_id); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audqcelp_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audqcelp_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audqcelp_disable(audio); + audio->stopped = 1; + audqcelp_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audqcelp_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + if (copy_from_user(&config, (void *)arg, + sizeof(config))) { + rc = -EFAULT; + break; + } + audio->mfield = config.meta_field; + MM_DBG("AUDIO_SET_CONFIG applicable \ + for metafield configuration\n"); + rc = 0; + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = BUF_COUNT; + config.sample_rate = 8000; + config.channel_count = 1; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + + if (copy_from_user(&config, (void *)arg, + sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buf %d\n", + config.buffer_count * config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("no mem for read buf\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr 0x%08x \ + kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audqcelp_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audqcelp_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d\n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver does + not know frame size, read count must be greater or equal + to size of PCM samples */ + MM_DBG("read stop - partial frame\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + /* order reads from the output buffer */ + rmb(); + if (copy_to_user(buf, + audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x\n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audqcelp_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audqcelp_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + int rc = 0; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audqcelp_send_data(audio, 0); + +done: + return rc; +} + +static ssize_t audqcelp_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDQCELP_EOS_NONE; + unsigned short mfield_size = 0; + + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + MM_DBG("buffer available\n"); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDQCELP_EOS_FLG_OFFSET] & + AUDQCELP_EOS_FLG_MASK) { + MM_DBG("EOS SET\n"); + eos_condition = AUDQCELP_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDQCELP_EOS_FLG_OFFSET] &= + ~AUDQCELP_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + frame->used = xfer + mfield_size; + audio->out_head ^= 1; + count -= xfer; + buf += xfer; + audqcelp_send_data(audio, 0); + } + if (eos_condition == AUDQCELP_EOS_SET) + rc = audqcelp_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audqcelp_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int) audio); + mutex_lock(&audio->lock); + audqcelp_disable(audio); + if (audio->rmt_resource_released == 0) + rmt_put_resource(audio); + audqcelp_flush(audio); + audqcelp_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->opened = 0; + audio->event_abort = 1; + wake_up(&audio->event_wait); + audqcelp_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audqcelp_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audqcelp_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audqcelp_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audqcelp_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static void audqcelp_suspend(struct early_suspend *h) +{ + struct audqcelp_suspend_ctl *ctl = + container_of(h, struct audqcelp_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audqcelp_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audqcelp_resume(struct early_suspend *h) +{ + struct audqcelp_suspend_ctl *ctl = + container_of(h, struct audqcelp_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audqcelp_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audqcelp_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audqcelp_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audqcelp_debug_fops = { + .read = audqcelp_debug_read, + .open = audqcelp_debug_open, +}; +#endif + +static int audqcelp_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + struct audqcelp_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_qcelp_" + 5]; +#endif + + /* Create audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance\n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_QCELP; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->phys)) { + MM_ERR("could not allocate write buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + } + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + rc = audmgr_open(&audio->audmgr); + if (rc) { + MM_ERR("audmgr open failed, freeing instance \ + 0x%08x\n", (int)audio); + goto err; + } + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_qcelp, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + goto err; + } + + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for QCELP session \ + 0x%08x on decoder: %d\n", (int)audio, audio->dec_id); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + msm_adsp_put(audio->audplay); + goto err; + } + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + + /* Initialize buffer */ + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x2000; + + audqcelp_flush(audio); + + file->private_data = audio; + audio->opened = 1; +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_qcelp_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audqcelp_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audqcelp_resume; + audio->suspend_ctl.node.suspend = audqcelp_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDQCELP_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audqcelp_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_qcelp_fops = { + .owner = THIS_MODULE, + .open = audqcelp_open, + .release = audqcelp_release, + .read = audqcelp_read, + .write = audqcelp_write, + .unlocked_ioctl = audqcelp_ioctl, + .fsync = audqcelp_fsync, +}; + +struct miscdevice audio_qcelp_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_qcelp", + .fops = &audio_qcelp_fops, +}; + +static int __init audqcelp_init(void) +{ + return misc_register(&audio_qcelp_misc); +} + +static void __exit audqcelp_exit(void) +{ + misc_deregister(&audio_qcelp_misc); +} + +module_init(audqcelp_init); +module_exit(audqcelp_exit); + +MODULE_DESCRIPTION("MSM QCELP 13K driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5/audio_qcelp_in.c b/arch/arm/mach-msm/qdsp5/audio_qcelp_in.c new file mode 100644 index 00000000000..31edc835c47 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_qcelp_in.c @@ -0,0 +1,1380 @@ +/* arch/arm/mach-msm/qdsp5/audio_qcelp_in.c + * + * qcelp audio input device + * + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This code is based in part on arch/arm/mach-msm/qdsp5v2/audio_qcelp_in.c, + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include + +#define FRAME_HEADER_SIZE 8 /* 8 bytes frame header */ +#define NT_FRAME_HEADER_SIZE 24 /* 24 bytes frame header */ +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM 8 +#define QCELP_FRAME_SIZE 36 /* 36 bytes data */ +/*Tunnel mode : 36 bytes data + 8 byte header*/ +#define FRAME_SIZE (QCELP_FRAME_SIZE + FRAME_HEADER_SIZE) + /* 36 bytes data + 24 meta field*/ +#define NT_FRAME_SIZE (QCELP_FRAME_SIZE + NT_FRAME_HEADER_SIZE) +#define DMASZ (FRAME_SIZE * FRAME_NUM) +#define NT_DMASZ (NT_FRAME_SIZE * FRAME_NUM) +#define OUT_FRAME_NUM 2 +#define OUT_BUFFER_SIZE (4 * 1024 + NT_FRAME_HEADER_SIZE) +#define BUFFER_SIZE (OUT_BUFFER_SIZE * OUT_FRAME_NUM) + +/* Offset from beginning of buffer*/ +#define AUDPREPROC_QCELP_EOS_FLG_OFFSET 0x0A +#define AUDPREPROC_QCELP_EOS_FLG_MASK 0x01 +#define AUDPREPROC_QCELP_EOS_NONE 0x0 /* No EOS detected */ +#define AUDPREPROC_QCELP_EOS_SET 0x1 /* EOS set in meta field */ + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; + uint32_t used; + uint32_t mfield_sz; +}; + +struct audio_qcelp_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + atomic_t in_samples; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + wait_queue_head_t wait_enable; + /*write section*/ + struct buffer out[OUT_FRAME_NUM]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + uint32_t out_count; + + struct mutex write_lock; + wait_queue_head_t write_wait; + int32_t out_phys; /* physical address of write buffer */ + char *out_data; + int mfield; /* meta field embedded in data */ + int wflush; /*write flush */ + int rflush; /*read flush*/ + int out_frame_cnt; + + struct msm_adsp_module *audrec; + struct msm_adsp_module *audpre; + + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* Frame size (36 bytes) */ + uint32_t enc_type; /* 11 for QCELP */ + uint32_t mode; /* T or NT Mode*/ + + struct msm_audio_qcelp_enc_config cfg; + + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + uint32_t eos_ack; + uint32_t flush_ack; + + const char *module_name; + unsigned queue_ids; + uint16_t enc_id; /* Session Id */ + + unsigned short samp_rate_index; + uint32_t audrec_obj_idx ; + + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ +}; + +struct audio_frame { + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + unsigned char raw_bitstream[]; +} __packed; + +struct audio_frame_nt { + uint16_t metadata_len; + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + uint16_t reserved; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; + unsigned char raw_bitstream[]; /* samples */ +} __packed; + +struct qcelp_encoded_meta_out { + uint16_t metadata_len; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; +}; + +/* Audrec Queue command sent macro's */ +#define audio_send_queue_pre(audio, cmd, len) \ + msm_adsp_write(audio->audpre, QDSP_uPAudPreProcCmdQueue, cmd, len) + +#define audio_send_queue_recbs(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_ids & 0xFFFF0000) >> 16),\ + cmd, len) +#define audio_send_queue_rec(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_ids & 0x0000FFFF),\ + cmd, len) + +static int audqcelp_in_dsp_enable(struct audio_qcelp_in *audio, int enable); +static int audqcelp_in_encparam_config(struct audio_qcelp_in *audio); +static int audqcelp_in_encmem_config(struct audio_qcelp_in *audio); +static int audqcelp_in_dsp_read_buffer(struct audio_qcelp_in *audio, + uint32_t read_cnt); +static void audqcelp_in_flush(struct audio_qcelp_in *audio); + +static void audqcelp_in_get_dsp_frames(struct audio_qcelp_in *audio); +static int audpcm_config(struct audio_qcelp_in *audio); +static void audqcelp_out_flush(struct audio_qcelp_in *audio); +static int audqcelp_in_routing_mode_config(struct audio_qcelp_in *audio); +static void audrec_pcm_send_data(struct audio_qcelp_in *audio, unsigned needed); +static void audqcelp_nt_in_get_dsp_frames(struct audio_qcelp_in *audio); +static void audqcelp_in_flush(struct audio_qcelp_in *audio); + +static unsigned convert_samp_index(unsigned index) +{ + switch (index) { + case RPC_AUD_DEF_SAMPLE_RATE_48000: return 48000; + case RPC_AUD_DEF_SAMPLE_RATE_44100: return 44100; + case RPC_AUD_DEF_SAMPLE_RATE_32000: return 32000; + case RPC_AUD_DEF_SAMPLE_RATE_24000: return 24000; + case RPC_AUD_DEF_SAMPLE_RATE_22050: return 22050; + case RPC_AUD_DEF_SAMPLE_RATE_16000: return 16000; + case RPC_AUD_DEF_SAMPLE_RATE_12000: return 12000; + case RPC_AUD_DEF_SAMPLE_RATE_11025: return 11025; + case RPC_AUD_DEF_SAMPLE_RATE_8000: return 8000; + default: return 11025; + } +} + +/* must be called with audio->lock held */ +static int audqcelp_in_enable(struct audio_qcelp_in *audio) +{ + struct audmgr_config cfg; + int rc; + + if (audio->enabled) + return 0; + + cfg.tx_rate = audio->samp_rate; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; + cfg.codec = RPC_AUD_DEF_CODEC_13K; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + + if (msm_adsp_enable(audio->audpre)) { + audmgr_disable(&audio->audmgr); + MM_ERR("msm_adsp_enable(audpre) failed\n"); + return -ENODEV; + } + } + if (msm_adsp_enable(audio->audrec)) { + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + audmgr_disable(&audio->audmgr); + msm_adsp_disable(audio->audpre); + } + MM_ERR("msm_adsp_enable(audrec) failed\n"); + return -ENODEV; + } + + audio->enabled = 1; + audqcelp_in_dsp_enable(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audqcelp_in_disable(struct audio_qcelp_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + + audqcelp_in_dsp_enable(audio, 0); + + wake_up(&audio->wait); + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + msm_adsp_disable(audio->audrec); + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + msm_adsp_disable(audio->audpre); + audmgr_disable(&audio->audmgr); + } + } + return 0; +} + +/* ------------------- dsp --------------------- */ +static void audpre_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + uint16_t msg[2]; + getevent(msg, sizeof(msg)); + + switch (id) { + case AUDPREPROC_MSG_CMD_CFG_DONE_MSG: + MM_DBG("type %d, status_flag %d\n", msg[0], msg[1]); + break; + case AUDPREPROC_MSG_ERROR_MSG_ID: + MM_ERR("err_index %d\n", msg[0]); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audpreproctask)\n"); + break; + default: + MM_ERR("unknown event %d\n", id); + } +} + +static void audqcelp_in_get_dsp_frames(struct audio_qcelp_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - + sizeof(*frame)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = frame->frame_length; + + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) { + MM_ERR("Error! not able to keep up the read\n"); + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + MM_ERR("in_count = %d\n", audio->in_count); + } else + audio->in_count++; + + audqcelp_in_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +static void audqcelp_nt_in_get_dsp_frames(struct audio_qcelp_in *audio) +{ + struct audio_frame_nt *nt_frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + nt_frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(struct audio_frame_nt)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = nt_frame->frame_length; + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + MM_DBG("Error! not able to keep up the read\n"); + else + audio->in_count++; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + wake_up(&audio->wait); +} + +static int audrec_pcm_buffer_ptr_refresh(struct audio_qcelp_in *audio, + unsigned idx, unsigned len) +{ + struct audrec_cmd_pcm_buffer_ptr_refresh_arm_enc cmd; + + if (len == NT_FRAME_HEADER_SIZE) + len = len / 2; + else + len = (len + NT_FRAME_HEADER_SIZE) / 2; + MM_DBG("len = %d\n", len); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_BUFFER_PTR_REFRESH_ARM_TO_ENC; + cmd.num_buffers = 1; + if (cmd.num_buffers == 1) { + cmd.buf_address_length[0] = (audio->out[idx].addr & + 0xffff0000) >> 16; + cmd.buf_address_length[1] = (audio->out[idx].addr & + 0x0000ffff); + cmd.buf_address_length[2] = (len & 0xffff0000) >> 16; + cmd.buf_address_length[3] = (len & 0x0000ffff); + } + audio->out_frame_cnt++; + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audpcm_config(struct audio_qcelp_in *audio) +{ + struct audrec_cmd_pcm_cfg_arm_to_enc cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_CFG_ARM_TO_ENC; + cmd.config_update_flag = AUDREC_PCM_CONFIG_UPDATE_FLAG_ENABLE; + cmd.enable_flag = AUDREC_ENABLE_FLAG_VALUE; + cmd.sampling_freq = convert_samp_index(audio->samp_rate); + if (!audio->channel_mode) + cmd.channels = 1; + else + cmd.channels = 2; + cmd.frequency_of_intimation = 1; + cmd.max_number_of_buffers = OUT_FRAME_NUM; + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + + +static int audqcelp_in_routing_mode_config(struct audio_qcelp_in *audio) +{ + struct audrec_cmd_routing_mode cmd; + + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_ROUTING_MODE; + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + cmd.routing_mode = 1; + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_qcelp_in *audio = NULL; + + if (data) + audio = data; + else { + MM_ERR("invalid data for event %x\n", id); + return; + } + + switch (id) { + case AUDREC_MSG_CMD_CFG_DONE_MSG: { + struct audrec_msg_cmd_cfg_done_msg cmd_cfg_done_msg; + getevent(&cmd_cfg_done_msg, AUDREC_MSG_CMD_CFG_DONE_MSG_LEN); + if (cmd_cfg_done_msg.audrec_enc_type & \ + AUDREC_MSG_CFG_DONE_ENC_ENA) { + audio->audrec_obj_idx = cmd_cfg_done_msg.audrec_obj_idx; + MM_DBG("CFG ENABLED\n"); + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + MM_DBG("routing command\n"); + audqcelp_in_routing_mode_config(audio); + } else { + audqcelp_in_encmem_config(audio); + } + } else { + MM_DBG("CFG SLEEP\n"); + audio->running = 0; + wake_up(&audio->wait_enable); + } + break; + } + case AUDREC_MSG_CMD_ROUTING_MODE_DONE_MSG: { + struct audrec_msg_cmd_routing_mode_done_msg \ + routing_msg; + getevent(&routing_msg, AUDREC_MSG_CMD_ROUTING_MODE_DONE_MSG); + MM_DBG("AUDREC_MSG_CMD_ROUTING_MODE_DONE_MSG"); + if (routing_msg.configuration == 0) { + MM_ERR("routing configuration failed\n"); + audio->running = 0; + wake_up(&audio->wait_enable); + } else + audqcelp_in_encmem_config(audio); + break; + } + case AUDREC_MSG_CMD_AREC_MEM_CFG_DONE_MSG: { + MM_DBG("AREC_MEM_CFG_DONE_MSG\n"); + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + audqcelp_in_encparam_config(audio); + else + audpcm_config(audio); + break; + } + case AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG: { + MM_DBG("AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG"); + audqcelp_in_encparam_config(audio); + break; + } + case AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG: { + MM_DBG("AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG\n"); + audio->running = 1; + wake_up(&audio->wait_enable); + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + audrec_pcm_send_data(audio, 1); + break; + } + case AUDREC_CMD_PCM_BUFFER_PTR_UPDATE_ARM_TO_ENC_MSG: { + MM_DBG("ptr_update recieved from DSP\n"); + audrec_pcm_send_data(audio, 1); + break; + } + case AUDREC_MSG_NO_EXT_PKT_AVAILABLE_MSG: { + struct audrec_msg_no_ext_pkt_avail_msg err_msg; + getevent(&err_msg, AUDREC_MSG_NO_EXT_PKT_AVAILABLE_MSG_LEN); + MM_DBG("NO_EXT_PKT_AVAILABLE_MSG %x\n",\ + err_msg.audrec_err_id); + break; + } + case AUDREC_MSG_PACKET_READY_MSG: { + struct audrec_msg_packet_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_MSG_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt msw %d \ + write cnt lsw %d read cnt msw %d read cnt lsw %d \n",\ + pkt_ready_msg.pkt_counter_msw, \ + pkt_ready_msg.pkt_counter_lsw, \ + pkt_ready_msg.pkt_read_cnt_msw, \ + pkt_ready_msg.pkt_read_cnt_lsw); + + audqcelp_in_get_dsp_frames(audio); + break; + } + case AUDREC_UP_NT_PACKET_READY_MSG: { + struct audrec_up_nt_packet_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_NT_PACKET_READY_MSG_LEN); + MM_DBG("UP_NT_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packetwrite_cnt_lsw, \ + pkt_ready_msg.audrec_packetwrite_cnt_msw, \ + pkt_ready_msg.audrec_upprev_readcount_lsw, \ + pkt_ready_msg.audrec_upprev_readcount_msw); + + audqcelp_nt_in_get_dsp_frames(audio); + break; + } + case AUDREC_CMD_FLUSH_DONE_MSG: { + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 1; + wake_up(&audio->write_wait); + MM_DBG("flush ack recieved\n"); + break; + } + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module \ + enable/disable(audrectask)\n"); + break; + default: + MM_ERR("unknown event %d\n", id); + } +} + +static struct msm_adsp_ops audpre_qcelp_adsp_ops = { + .event = audpre_dsp_event, +}; + +static struct msm_adsp_ops audrec_qcelp_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int audqcelp_in_dsp_enable(struct audio_qcelp_in *audio, int enable) +{ + struct audrec_cmd_enc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_ENC_CFG; + cmd.audrec_enc_type = (audio->enc_type & 0xFF) | + (enable ? AUDREC_CMD_ENC_ENA : AUDREC_CMD_ENC_DIS); + /* Don't care */ + cmd.audrec_obj_idx = audio->audrec_obj_idx; + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audqcelp_in_encmem_config(struct audio_qcelp_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + int n; + int header_len = 0; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.cmd_id = AUDREC_CMD_ARECMEM_CFG; + cmd.audrec_obj_idx = audio->audrec_obj_idx; + /* Rate at which packet complete message comes */ + cmd.audrec_up_pkt_intm_cnt = 1; + cmd.audrec_extpkt_buffer_msw = audio->phys >> 16; + cmd.audrec_extpkt_buffer_lsw = audio->phys; + /* Max Buffer no available for frames */ + cmd.audrec_extpkt_buffer_num = FRAME_NUM; + + /* prepare buffer pointers: + * T:36 bytes qcelp packet + 4 halfword header + * NT:36 bytes qcelp packet + 12 halfword header + */ + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + header_len = FRAME_HEADER_SIZE/2; + else + header_len = NT_FRAME_HEADER_SIZE/2; + + for (n = 0; n < FRAME_NUM; n++) { + audio->in[n].data = data + header_len; + data += (QCELP_FRAME_SIZE/2) + header_len; + MM_DBG("0x%8x\n", (int)(audio->in[n].data - header_len*2)); + } + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audqcelp_in_encparam_config(struct audio_qcelp_in *audio) +{ + struct audrec_cmd_arecparam_qcelp_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDREC_CMD_ARECPARAM_CFG; + cmd.common.audrec_obj_idx = audio->audrec_obj_idx; + cmd.enc_min_rate = audio->cfg.min_bit_rate; + cmd.enc_max_rate = audio->cfg.max_bit_rate; + cmd.rate_modulation_cmd = 0; /* Default set to 0 */ + cmd.reduced_rate_level = 0; /* Default set to 0 */ + + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audqcelp_flush_command(struct audio_qcelp_in *audio) +{ + struct audrec_cmd_flush cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_FLUSH; + return audio_send_queue_rec(audio, &cmd, sizeof(cmd)); +} + +static int audqcelp_in_dsp_read_buffer(struct audio_qcelp_in *audio, + uint32_t read_cnt) +{ + audrec_cmd_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PACKET_EXT_PTR; + cmd.type = audio->audrec_obj_idx; + cmd.curr_rec_count_msw = read_cnt >> 16; + cmd.curr_rec_count_lsw = read_cnt; + + return audio_send_queue_recbs(audio, &cmd, sizeof(cmd)); +} + +/* ------------------- device --------------------- */ + +static void audqcelp_ioport_reset(struct audio_qcelp_in *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audqcelp_in_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audqcelp_out_flush(audio); + mutex_unlock(&audio->read_lock); +} + +static void audqcelp_in_flush(struct audio_qcelp_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + audio->eos_ack = 0; + for (i = FRAME_NUM-1; i >= 0; i--) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } + MM_DBG("in_bytes %d\n", atomic_read(&audio->in_bytes)); + MM_DBG("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); +} + +static void audqcelp_out_flush(struct audio_qcelp_in *audio) +{ + int i; + + audio->out_head = 0; + audio->out_tail = 0; + audio->out_count = 0; + for (i = OUT_FRAME_NUM-1; i >= 0; i--) { + audio->out[i].size = 0; + audio->out[i].read = 0; + audio->out[i].used = 0; + } +} + +/* ------------------- device --------------------- */ +static long audqcelp_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_qcelp_in *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + rc = audqcelp_in_enable(audio); + if (!rc) { + rc = + wait_event_interruptible_timeout(audio->wait_enable, + audio->running != 0, 1*HZ); + MM_DBG("state %d rc = %d\n", audio->running, rc); + + if (audio->running == 0) + rc = -ENODEV; + else + rc = 0; + } + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + rc = audqcelp_in_disable(audio); + audio->stopped = 1; + break; + } + case AUDIO_FLUSH: { + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audqcelp_ioport_reset(audio); + if (audio->running) { + audqcelp_flush_command(audio); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = OUT_BUFFER_SIZE; + cfg.buffer_count = OUT_FRAME_NUM; + cfg.sample_rate = convert_samp_index(audio->samp_rate); + cfg.channel_count = 1; + cfg.type = 0; + cfg.unused[0] = 0; + cfg.unused[1] = 0; + cfg.unused[2] = 0; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + /* Allow only single frame */ + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if (cfg.buffer_size != (FRAME_SIZE - 8)) + rc = -EINVAL; + break; + } else { + if (cfg.buffer_size != (QCELP_FRAME_SIZE + 14)) + rc = -EINVAL; + break; + } + audio->buffer_size = cfg.buffer_size; + break; + } + case AUDIO_GET_QCELP_ENC_CONFIG: { + if (copy_to_user((void *) arg, &audio->cfg, sizeof(audio->cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_QCELP_ENC_CONFIG: { + struct msm_audio_qcelp_enc_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + MM_DBG("0X%8x, 0x%8x, 0x%8x\n", cfg.min_bit_rate, + cfg.max_bit_rate, cfg.cdma_rate); + if (cfg.min_bit_rate > CDMA_RATE_FULL || \ + cfg.min_bit_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid min bitrate\n"); + rc = -EFAULT; + break; + } + if (cfg.max_bit_rate > CDMA_RATE_FULL || \ + cfg.max_bit_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid max bitrate\n"); + rc = -EFAULT; + break; + } + /* Recording Does not support Erase and Blank */ + if (cfg.cdma_rate > CDMA_RATE_FULL || + cfg.cdma_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid qcelp cdma rate\n"); + rc = -EFAULT; + break; + } + memcpy(&audio->cfg, &cfg, sizeof(cfg)); + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audqcelp_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_qcelp_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + struct qcelp_encoded_meta_out meta_field; + struct audio_frame_nt *nt_frame; + MM_DBG("count = %d\n", count); + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped || + audio->rflush); + if (rc < 0) + break; + + if (audio->rflush) { + rc = -EBUSY; + break; + } + if (audio->stopped && !audio->in_count) { + MM_DBG("Driver in stop state, No more buffer to read"); + rc = 0;/* End of File */ + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + nt_frame = (struct audio_frame_nt *)(data - + sizeof(struct audio_frame_nt)); + memcpy((char *)&meta_field.time_stamp_dword_lsw, + (char *)&nt_frame->time_stamp_dword_lsw, + (sizeof(struct qcelp_encoded_meta_out) - \ + sizeof(uint16_t))); + meta_field.metadata_len = + sizeof(struct qcelp_encoded_meta_out); + if (copy_to_user((char *)start, (char *)&meta_field, + sizeof(struct qcelp_encoded_meta_out))) { + rc = -EFAULT; + break; + } + if (nt_frame->nflag_lsw & 0x0001) { + MM_ERR("recieved EOS in read call\n"); + audio->eos_ack = 1; + } + buf += sizeof(struct qcelp_encoded_meta_out); + count -= sizeof(struct qcelp_encoded_meta_out); + } + if (count >= size) { + /* order the reads on the buffer */ + dma_coherent_post_ops(); + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is + * invalid and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + if ((audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL)) { + if (!audio->eos_ack) { + MM_DBG("sending read ptr command \ + %d %d\n", + audio->dsp_cnt, + audio->in_tail); + audqcelp_in_dsp_read_buffer(audio, + audio->dsp_cnt++); + } + } + } else { + MM_ERR("short read\n"); + break; + } + break; + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static void audrec_pcm_send_data(struct audio_qcelp_in *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + MM_DBG("\n"); + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + audrec_pcm_buffer_ptr_refresh(audio, + audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static int audqcelp_in_fsync(struct file *file, int datasync) + +{ + struct audio_qcelp_in *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (!audio->running || (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + audio->wflush); + MM_DBG("waked on by some event audio->wflush = %d\n", audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; + +} + +int audrec_qcelp_process_eos(struct audio_qcelp_in *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + int rc = 0; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + MM_DBG("copying meta_out frame->used = %d\n", frame->used); + audrec_pcm_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audqcelp_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_qcelp_in *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + char *cpy_ptr; + int rc = 0, eos_condition = AUDPREPROC_QCELP_EOS_NONE; + unsigned short mfield_size = 0; + int write_count = 0; + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + if (audio->mode != MSM_AUD_ENC_MODE_NONTUNNEL) + return -EINVAL; + + mutex_lock(&audio->write_lock); + frame = audio->out + audio->out_head; + /* if supplied count is more than driver buffer size + * then only copy driver buffer size + */ + if (count > frame->size) + count = frame->size; + + write_count = count; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto error; + + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto error; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + goto error; + } else if (mfield_size > count) { + rc = -EINVAL; + goto error; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + goto error; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDPREPROC_QCELP_EOS_FLG_OFFSET] & + AUDPREPROC_QCELP_EOS_FLG_MASK) { + eos_condition = AUDPREPROC_QCELP_EOS_SET; + MM_DBG("EOS SET\n"); + if (mfield_size == count) { + buf += mfield_size; + eos_condition = 0; + goto exit; + } else + cpy_ptr[AUDPREPROC_QCELP_EOS_FLG_OFFSET] &= + ~AUDPREPROC_QCELP_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + MM_DBG("copying the stream count = %d\n", count); + if (copy_from_user(cpy_ptr, buf, count)) { + rc = -EFAULT; + goto error; + } +exit: + frame->used = count; + audio->out_head ^= 1; + if (!audio->flush_ack) + audrec_pcm_send_data(audio, 0); + else { + audrec_pcm_send_data(audio, 1); + audio->flush_ack = 0; + } + if (eos_condition == AUDPREPROC_QCELP_EOS_SET) + rc = audrec_qcelp_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + return write_count; +error: + mutex_unlock(&audio->write_lock); + return rc; +} + +static int audqcelp_in_release(struct inode *inode, struct file *file) +{ + struct audio_qcelp_in *audio = file->private_data; + + mutex_lock(&audio->lock); + audqcelp_in_disable(audio); + audqcelp_in_flush(audio); + msm_adsp_put(audio->audrec); + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + msm_adsp_put(audio->audpre); + + audpreproc_aenc_free(audio->enc_id); + audio->audrec = NULL; + audio->audpre = NULL; + audio->opened = 0; + + if ((audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) && \ + (audio->out_data)) { + free_contiguous_memory(audio->out_data); + audio->out_data = NULL; + } + + if (audio->data) { + free_contiguous_memory(audio->data); + audio->data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +static struct audio_qcelp_in the_audio_qcelp_in; + +static int audqcelp_in_open(struct inode *inode, struct file *file) +{ + struct audio_qcelp_in *audio = &the_audio_qcelp_in; + int rc; + int encid; + int dma_size = 0; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_NONTUNNEL; + dma_size = NT_DMASZ; + MM_DBG("Opened for non tunnel mode encoding\n"); + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_TUNNEL; + dma_size = DMASZ; + MM_DBG("Opened for tunnel mode encoding\n"); + } else { + MM_ERR("Invalid mode\n"); + rc = -EACCES; + goto done; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->samp_rate = RPC_AUD_DEF_SAMPLE_RATE_8000, + audio->samp_rate_index = AUDREC_CMD_SAMP_RATE_INDX_8000; + audio->channel_mode = AUDREC_CMD_STEREO_MODE_MONO; + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + audio->buffer_size = (QCELP_FRAME_SIZE + 14); + else + audio->buffer_size = QCELP_FRAME_SIZE; + audio->enc_type = AUDREC_CMD_TYPE_0_INDEX_QCELP | audio->mode; + + audio->cfg.cdma_rate = CDMA_RATE_FULL; + audio->cfg.min_bit_rate = CDMA_RATE_FULL; + audio->cfg.max_bit_rate = CDMA_RATE_FULL; + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + rc = audmgr_open(&audio->audmgr); + if (rc) + goto done; + } + + encid = audpreproc_aenc_alloc(audio->enc_type, &audio->module_name, + &audio->queue_ids); + if (encid < 0) { + MM_ERR("No free encoder available\n"); + rc = -ENODEV; + goto done; + } + audio->enc_id = encid; + + rc = msm_adsp_get(audio->module_name, &audio->audrec, + &audrec_qcelp_adsp_ops, audio); + if (rc) { + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + rc = msm_adsp_get("AUDPREPROCTASK", &audio->audpre, + &audpre_qcelp_adsp_ops, audio); + if (rc) { + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + goto done; + } + } + + audio->dsp_cnt = 0; + audio->stopped = 0; + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 0; + + audqcelp_in_flush(audio); + audqcelp_out_flush(audio); + + audio->data = allocate_contiguous_memory(dma_size, MEMTYPE_EBI1, + SZ_4K, 0); + if (!audio->data) { + MM_ERR("could not allocate read buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->phys = memory_pool_node_paddr(audio->data); + if (!audio->phys) { + MM_ERR("could not get physical address\n"); + rc = -ENOMEM; + free_contiguous_memory(audio->data); + goto evt_error; + } + MM_DBG("read buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + } + + audio->out_data = NULL; + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + audio->out_data = allocate_contiguous_memory(BUFFER_SIZE, + MEMTYPE_EBI1, + SZ_4K, 0); + if (!audio->out_data) { + MM_ERR("could not allocate read buffers\n"); + rc = -ENOMEM; + free_contiguous_memory(audio->data); + goto evt_error; + } else { + audio->out_phys = memory_pool_node_paddr( + audio->out_data); + if (!audio->out_phys) { + MM_ERR("could not get physical address\n"); + rc = -ENOMEM; + free_contiguous_memory(audio->data); + free_contiguous_memory(audio->out_data); + goto evt_error; + } + MM_DBG("write buf:phy addr 0x%08x kernel addr 0x%08x\n", + audio->out_phys, (int)audio->out_data); + } + + /* Initialize buffer */ + audio->out[0].data = audio->out_data + 0; + audio->out[0].addr = audio->out_phys + 0; + audio->out[0].size = OUT_BUFFER_SIZE; + + audio->out[1].data = audio->out_data + OUT_BUFFER_SIZE; + audio->out[1].addr = audio->out_phys + OUT_BUFFER_SIZE; + audio->out[1].size = OUT_BUFFER_SIZE; + + MM_DBG("audio->out[0].data = %d audio->out[1].data = %d", + (unsigned int)audio->out[0].data, + (unsigned int)audio->out[1].data); + audio->mfield = NT_FRAME_HEADER_SIZE; + audio->out_frame_cnt++; + } + file->private_data = audio; + audio->opened = 1; + +done: + mutex_unlock(&audio->lock); + return rc; +evt_error: + msm_adsp_put(audio->audrec); + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + msm_adsp_put(audio->audpre); + + audpreproc_aenc_free(audio->enc_id); + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_qcelp_in_fops = { + .owner = THIS_MODULE, + .open = audqcelp_in_open, + .release = audqcelp_in_release, + .read = audqcelp_in_read, + .write = audqcelp_in_write, + .fsync = audqcelp_in_fsync, + .unlocked_ioctl = audqcelp_in_ioctl, +}; + +static struct miscdevice audqcelp_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_qcelp_in", + .fops = &audio_qcelp_in_fops, +}; + +static int __init audqcelp_in_init(void) +{ + mutex_init(&the_audio_qcelp_in.lock); + mutex_init(&the_audio_qcelp_in.read_lock); + spin_lock_init(&the_audio_qcelp_in.dsp_lock); + init_waitqueue_head(&the_audio_qcelp_in.wait); + init_waitqueue_head(&the_audio_qcelp_in.wait_enable); + mutex_init(&the_audio_qcelp_in.write_lock); + init_waitqueue_head(&the_audio_qcelp_in.write_wait); + return misc_register(&audqcelp_in_misc); +} +device_initcall(audqcelp_in_init); diff --git a/arch/arm/mach-msm/qdsp5/audio_voicememo.c b/arch/arm/mach-msm/qdsp5/audio_voicememo.c new file mode 100644 index 00000000000..b7e8e1c2574 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_voicememo.c @@ -0,0 +1,960 @@ +/* arch/arm/mach-msm/qdsp5/audio_voicememo.c + * + * Voice Memo device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * This code is based in part on arch/arm/mach-msm/qdsp5/audio_mp3.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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "audmgr.h" + +#define SND_PROG_VERS "rs30000002:0x00020001" +#define SND_PROG 0x30000002 +#define SND_VERS_COMP 0x00020001 +#define SND_VERS2_COMP 0x00030001 + +#define SND_VOC_REC_START_PROC 19 +#define SND_VOC_REC_STOP_PROC 20 +#define SND_VOC_REC_PAUSE_PROC 21 +#define SND_VOC_REC_RESUME_PROC 22 +#define SND_VOC_REC_PUT_BUF_PROC 23 + +#define SND_VOC_REC_AV_SYNC_CB_PTR_PROC 9 +#define SND_VOC_REC_CB_FUNC_TYPE_PROC 10 + +#define REC_CLIENT_DATA 0x11223344 +#define DATA_CB_FUNC_ID 0x12345678 +#define AV_SYNC_CB_FUNC_ID 0x87654321 +#define CLIENT_DATA 0xaabbccdd + +#define RPC_TYPE_REQUEST 0 +#define RPC_TYPE_REPLY 1 + +#define RPC_STATUS_FAILURE 0 +#define RPC_STATUS_SUCCESS 1 + +#define RPC_VERSION 2 + +#define RPC_COMMON_HDR_SZ (sizeof(uint32_t) * 2) +#define RPC_REQUEST_HDR_SZ (sizeof(struct rpc_request_hdr)) +#define RPC_REPLY_HDR_SZ (sizeof(uint32_t) * 3) +#define RPC_REPLY_SZ (sizeof(uint32_t) * 6) + +#define MAX_FRAME_SIZE 36 /* QCELP - 36, AMRNB - 32, EVRC - 24 */ +#define MAX_REC_BUF_COUNT 5 /* Maximum supported voc rec buffers */ +#define MAX_REC_BUF_SIZE (MAX_FRAME_SIZE * 10) +#define MAX_VOICEMEMO_BUF_SIZE \ + ((MAX_REC_BUF_SIZE)*MAX_REC_BUF_COUNT) /* 5 buffers for 200ms frame */ +#define MSM_AUD_BUFFER_UPDATE_WAIT_MS 2000 + +enum rpc_voc_rec_status_type { + RPC_VOC_REC_STAT_SUCCESS = 1, + RPC_VOC_REC_STAT_DONE = 2, + RPC_VOC_REC_STAT_AUTO_STOP = 4, + RPC_VOC_REC_STAT_PAUSED = 8, + RPC_VOC_REC_STAT_RESUMED = 16, + RPC_VOC_REC_STAT_ERROR = 32, + RPC_VOC_REC_STAT_BUFFER_ERROR = 64, + RPC_VOC_REC_STAT_INVALID_PARAM = 128, + RPC_VOC_REC_STAT_INT_TIME = 256, + RPC_VOC_REC_STAT_DATA = 512, + RPC_VOC_REC_STAT_NOT_READY = 1024, + RPC_VOC_REC_STAT_INFORM_EVRC = 2048, + RPC_VOC_REC_STAT_INFORM_13K = 4096, + RPC_VOC_REC_STAT_INFORM_AMR = 8192, + RPC_VOC_REC_STAT_INFORM_MAX = 65535 +}; + +struct rpc_snd_voc_rec_start_args { + uint32_t param_status; /* 1 = valid, 0 = not valid */ + uint32_t rec_type; + uint32_t rec_interval_ms; + uint32_t auto_stop_ms; + uint32_t capability; + uint32_t max_rate; + uint32_t min_rate; + uint32_t frame_format; + uint32_t dtx_enable; + uint32_t data_req_ms; + uint32_t rec_client_data; + + uint32_t cb_func_id; + uint32_t sync_cb_func_id; + uint32_t client_data; +}; + +struct rpc_snd_voc_rec_put_buf_args { + uint32_t buf; + uint32_t num_bytes; +}; + +struct snd_voc_rec_start_msg { + struct rpc_request_hdr hdr; + struct rpc_snd_voc_rec_start_args args; +}; + +struct snd_voc_rec_put_buf_msg { + struct rpc_request_hdr hdr; + struct rpc_snd_voc_rec_put_buf_args args; +}; + +struct snd_voc_rec_av_sync_cb_func_data { + uint32_t sync_cb_func_id; + uint32_t status; /* Pointer status (1 = valid, 0 = invalid) */ + uint32_t num_samples; + uint32_t time_stamp[2]; + uint32_t lost_samples; + uint32_t frame_index; + uint32_t client_data; +}; + +struct snd_voc_rec_cb_func_fw_data { + uint32_t fw_ptr_status; /* FW Pointer status (1=valid,0=invalid) */ + uint32_t rec_buffer_size; + uint32_t data[MAX_REC_BUF_SIZE/4]; + uint32_t rec_buffer_size_copy; + uint32_t rec_num_frames; /* Number of voice frames */ + uint32_t rec_length; /* Valid data in record buffer = + * data_req_ms amount of data */ + uint32_t client_data; /* A11 rec buffer pointer */ + uint32_t rw_ptr_status; /* RW Pointer status (1=valid,0=invalid) */ +}; + +struct snd_voc_rec_cb_func_rw_data { + uint32_t fw_ptr_status; /* FW Pointer status (1=valid,0=invalid) */ + uint32_t rw_ptr_status; /* RW Pointer status (1=valid,0=invalid) */ + uint32_t rec_buffer_size; + uint32_t data[MAX_REC_BUF_SIZE/4]; + uint32_t rec_buffer_size_copy; + uint32_t rec_num_frames; /* Number of voice frames */ + uint32_t rec_length; /* Valid data in record buffer = + * data_req_ms amount of data */ + uint32_t client_data; /* A11 rec buffer pointer */ +}; + +struct snd_voc_rec_data_cb_func_data { + uint32_t cb_func_id; + uint32_t status; /* Pointer status (1 = valid, 0 = invalid) */ + uint32_t rec_status; + + union { + struct snd_voc_rec_cb_func_fw_data fw_data; + struct snd_voc_rec_cb_func_rw_data rw_data; + } pkt; +}; + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Usage actual recorded data */ + unsigned addr; + unsigned numframes; +}; + +struct audio_voicememo { + uint32_t byte_count; /* Pass statistics to user space for + * time stamping */ + uint32_t frame_count; + + int opened; + int enabled; + int running; + int stopped; + int pause_resume; + + uint32_t rpc_prog; + uint32_t rpc_ver; + uint32_t rpc_xid; + uint32_t rpc_status; + + struct mutex lock; + struct mutex read_lock; + struct mutex dsp_lock; + wait_queue_head_t read_wait; + wait_queue_head_t wait; + + struct buffer in[MAX_REC_BUF_COUNT]; + char *rec_buf_ptr; + dma_addr_t phys; + uint32_t rec_buf_size; + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that should be filled as + * data comes from A9 */ + + struct audmgr audmgr; + + struct msm_audio_voicememo_config voicememo_cfg; + + struct msm_rpc_endpoint *sndept; + struct task_struct *task; +}; + +static struct audio_voicememo the_audio_voicememo; + +static int audvoicememo_validate_usr_config( + struct msm_audio_voicememo_config *config) +{ + int rc = -1; /* error */ + + if (config->rec_type != RPC_VOC_REC_FORWARD && + config->rec_type != RPC_VOC_REC_REVERSE && + config->rec_type != RPC_VOC_REC_BOTH) + goto done; + + /* QCELP, EVRC, AMR-NB only */ + if (config->capability != RPC_VOC_CAP_IS733 && + config->capability != RPC_VOC_CAP_IS127 && + config->capability != RPC_VOC_CAP_AMR) + goto done; + + /* QCP, AMR format supported */ + if ((config->frame_format != RPC_VOC_PB_NATIVE_QCP) && + (config->frame_format != RPC_VOC_PB_AMR)) + goto done; + + if ((config->frame_format == RPC_VOC_PB_AMR) && + (config->capability != RPC_VOC_CAP_AMR)) + goto done; + + /* To make sure, max kernel buf size matches + * with max data request time */ + if (config->data_req_ms > ((MAX_REC_BUF_SIZE/MAX_FRAME_SIZE)*20)) + goto done; + + rc = 0; +done: + return rc; +} + +static void audvoicememo_flush_buf(struct audio_voicememo *audio) +{ + uint8_t index; + + for (index = 0; index < MAX_REC_BUF_COUNT; index++) + audio->in[index].used = 0; + + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audvoicememo_ioport_reset(struct audio_voicememo *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audvoicememo_flush_buf(audio); + mutex_unlock(&audio->read_lock); +} + +/* must be called with audio->lock held */ +static int audvoicememo_enable(struct audio_voicememo *audio) +{ + struct audmgr_config cfg; + struct snd_voc_rec_put_buf_msg bmsg; + struct snd_voc_rec_start_msg msg; + uint8_t index; + uint32_t offset = 0; + int rc; + + if (audio->enabled) + return 0; + + /* Codec / method configure to audmgr client */ + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_8000; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; + + if (audio->voicememo_cfg.capability == RPC_VOC_CAP_IS733) + cfg.codec = RPC_AUD_DEF_CODEC_VOC_13K; + else if (audio->voicememo_cfg.capability == RPC_VOC_CAP_IS127) + cfg.codec = RPC_AUD_DEF_CODEC_VOC_EVRC; + else + cfg.codec = RPC_AUD_DEF_CODEC_VOC_AMR; /* RPC_VOC_CAP_AMR */ + + cfg.snd_method = RPC_SND_METHOD_VOICE; + rc = audmgr_enable(&audio->audmgr, &cfg); + + if (rc < 0) + return rc; + + /* Configure VOC Rec buffer */ + for (index = 0; index < MAX_REC_BUF_COUNT; index++) { + audio->in[index].data = audio->rec_buf_ptr + offset; + audio->in[index].addr = audio->phys + offset; + audio->in[index].size = audio->rec_buf_size; + audio->in[index].used = 0; + audio->in[index].numframes = 0; + offset += audio->rec_buf_size; + bmsg.args.buf = (uint32_t) audio->in[index].data; + bmsg.args.num_bytes = cpu_to_be32(audio->in[index].size); + MM_DBG("rec_buf_ptr=0x%8x, rec_buf_size = 0x%8x\n", + bmsg.args.buf, bmsg.args.num_bytes); + + msm_rpc_setup_req(&bmsg.hdr, audio->rpc_prog, audio->rpc_ver, + SND_VOC_REC_PUT_BUF_PROC); + audio->rpc_xid = bmsg.hdr.xid; + audio->rpc_status = RPC_STATUS_FAILURE; + msm_rpc_write(audio->sndept, &bmsg, sizeof(bmsg)); + rc = wait_event_timeout(audio->wait, + audio->rpc_status != RPC_STATUS_FAILURE, 1 * HZ); + if (rc == 0) + goto err; + } + + + /* Start Recording */ + msg.args.param_status = cpu_to_be32(0x00000001); + msg.args.rec_type = cpu_to_be32(audio->voicememo_cfg.rec_type); + msg.args.rec_interval_ms = + cpu_to_be32(audio->voicememo_cfg.rec_interval_ms); + msg.args.auto_stop_ms = cpu_to_be32(audio->voicememo_cfg.auto_stop_ms); + msg.args.capability = cpu_to_be32(audio->voicememo_cfg.capability); + msg.args.max_rate = cpu_to_be32(audio->voicememo_cfg.max_rate); + msg.args.min_rate = cpu_to_be32(audio->voicememo_cfg.min_rate); + msg.args.frame_format = cpu_to_be32(audio->voicememo_cfg.frame_format); + msg.args.dtx_enable = cpu_to_be32(audio->voicememo_cfg.dtx_enable); + msg.args.data_req_ms = cpu_to_be32(audio->voicememo_cfg.data_req_ms); + msg.args.rec_client_data = cpu_to_be32(REC_CLIENT_DATA); + msg.args.cb_func_id = cpu_to_be32(DATA_CB_FUNC_ID); + msg.args.sync_cb_func_id = cpu_to_be32(AV_SYNC_CB_FUNC_ID); + msg.args.client_data = cpu_to_be32(CLIENT_DATA); + + msm_rpc_setup_req(&msg.hdr, audio->rpc_prog, audio->rpc_ver, + SND_VOC_REC_START_PROC); + + audio->rpc_xid = msg.hdr.xid; + audio->rpc_status = RPC_STATUS_FAILURE; + msm_rpc_write(audio->sndept, &msg, sizeof(msg)); + rc = wait_event_timeout(audio->wait, + audio->rpc_status != RPC_STATUS_FAILURE, 1 * HZ); + if (rc == 0) + goto err; + + audio->rpc_xid = 0; + audio->enabled = 1; + return 0; + +err: + audio->rpc_xid = 0; + audmgr_disable(&audio->audmgr); + MM_ERR("Fail\n"); + return -1; +} + +/* must be called with audio->lock held */ +static int audvoicememo_disable(struct audio_voicememo *audio) +{ + struct rpc_request_hdr rhdr; + int rc = 0; + if (audio->enabled) { + msm_rpc_setup_req(&rhdr, audio->rpc_prog, audio->rpc_ver, + SND_VOC_REC_STOP_PROC); + rc = msm_rpc_write(audio->sndept, &rhdr, sizeof(rhdr)); + wait_event_timeout(audio->wait, audio->stopped == 0, + 1 * HZ); + wake_up(&audio->read_wait); + audmgr_disable(&audio->audmgr); + audio->enabled = 0; + } + return 0; +} + +/* RPC Reply Generator */ +static void rpc_reply(struct msm_rpc_endpoint *ept, uint32_t xid) +{ + int rc = 0; + uint8_t reply_buf[sizeof(struct rpc_reply_hdr)]; + struct rpc_reply_hdr *reply = (struct rpc_reply_hdr *)reply_buf; + + MM_DBG("inside\n"); + reply->xid = cpu_to_be32(xid); + reply->type = cpu_to_be32(RPC_TYPE_REPLY); /* reply */ + reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + + reply->data.acc_hdr.accept_stat = cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); + reply->data.acc_hdr.verf_flavor = 0; + reply->data.acc_hdr.verf_length = 0; + + rc = msm_rpc_write(ept, reply_buf, sizeof(reply_buf)); + if (rc < 0) + MM_ERR("could not write RPC response: %d\n", rc); +} + +static void process_rpc_request(uint32_t proc, uint32_t xid, + void *data, int len, void *private) +{ + struct audio_voicememo *audio = private; + + MM_DBG("inside\n"); + /* Sending Ack before processing the request + * to make sure A9 get response immediate + * However, if there is validation of request planned + * may be move this reply Ack at the end */ + rpc_reply(audio->sndept, xid); + switch (proc) { + case SND_VOC_REC_AV_SYNC_CB_PTR_PROC: { + MM_DBG("AV Sync CB:func_id=0x%8x,status=0x%x\n", + be32_to_cpu(( \ + (struct snd_voc_rec_av_sync_cb_func_data *)\ + data)->sync_cb_func_id),\ + be32_to_cpu(( \ + (struct snd_voc_rec_av_sync_cb_func_data *)\ + data)->status)); + break; + } + case SND_VOC_REC_CB_FUNC_TYPE_PROC: { + struct snd_voc_rec_data_cb_func_data *datacb_data + = (void *)(data); + struct snd_voc_rec_put_buf_msg bmsg; + uint32_t rec_status = be32_to_cpu(datacb_data->rec_status); + + MM_DBG("Data CB:func_id=0x%8x,status=0x%x,\ + rec_status=0x%x\n", + be32_to_cpu(datacb_data->cb_func_id),\ + be32_to_cpu(datacb_data->status),\ + be32_to_cpu(datacb_data->rec_status)); + + /* Data recorded */ + if ((rec_status == RPC_VOC_REC_STAT_DATA) || + (rec_status == RPC_VOC_REC_STAT_DONE)) { + if (datacb_data->pkt.fw_data.fw_ptr_status && + be32_to_cpu(datacb_data->pkt.fw_data.rec_length)) { + + MM_DBG("Copy FW link:rec_buf_size \ + = 0x%08x, rec_length=0x%08x\n", + be32_to_cpu( \ + datacb_data->pkt.fw_data. \ + rec_buffer_size_copy),\ + be32_to_cpu(datacb_data->pkt.fw_data. \ + rec_length)); + + mutex_lock(&audio->dsp_lock); + memcpy(audio->in[audio->fill_next].data, \ + &(datacb_data->pkt.fw_data.data[0]), \ + be32_to_cpu( + datacb_data->pkt.fw_data.rec_length)); + audio->in[audio->fill_next].used = + be32_to_cpu( + datacb_data->pkt.fw_data.rec_length); + audio->in[audio->fill_next].numframes = + be32_to_cpu( + datacb_data->pkt.fw_data.rec_num_frames); + mutex_unlock(&audio->dsp_lock); + } else if (datacb_data->pkt.rw_data.rw_ptr_status && + be32_to_cpu(datacb_data->pkt.rw_data.rec_length)) { + MM_DBG("Copy RW link:rec_buf_size \ + =0x%08x, rec_length=0x%08x\n", + be32_to_cpu( \ + datacb_data->pkt.rw_data. \ + rec_buffer_size_copy),\ + be32_to_cpu(datacb_data->pkt.rw_data. \ + rec_length)); + + mutex_lock(&audio->dsp_lock); + memcpy(audio->in[audio->fill_next].data, \ + &(datacb_data->pkt.rw_data.data[0]), \ + be32_to_cpu( + datacb_data->pkt.rw_data.rec_length)); + audio->in[audio->fill_next].used = + be32_to_cpu( + datacb_data->pkt.rw_data.rec_length); + audio->in[audio->fill_next].numframes = + be32_to_cpu( + datacb_data->pkt.rw_data.rec_num_frames); + mutex_unlock(&audio->dsp_lock); + } + if (rec_status != RPC_VOC_REC_STAT_DONE) { + /* Not end of record */ + bmsg.args.buf = \ + (uint32_t) audio->in[audio->fill_next].data; + bmsg.args.num_bytes = \ + be32_to_cpu(audio->in[audio->fill_next].size); + + if (++audio->fill_next == MAX_REC_BUF_COUNT) + audio->fill_next = 0; + + msm_rpc_setup_req(&bmsg.hdr, audio->rpc_prog, + audio->rpc_ver, SND_VOC_REC_PUT_BUF_PROC); + + msm_rpc_write(audio->sndept, &bmsg, + sizeof(bmsg)); + + wake_up(&audio->read_wait); + } else { + /* Indication record stopped gracefully */ + MM_DBG("End Of Voice Record\n"); + wake_up(&audio->wait); + } + } else if (rec_status == RPC_VOC_REC_STAT_PAUSED) { + MM_DBG(" Voice Record PAUSED\n"); + audio->pause_resume = 1; + } else if (rec_status == RPC_VOC_REC_STAT_RESUMED) { + MM_DBG(" Voice Record RESUMED\n"); + audio->pause_resume = 0; + } else if ((rec_status == RPC_VOC_REC_STAT_ERROR) || + (rec_status == RPC_VOC_REC_STAT_INVALID_PARAM) || + (rec_status == RPC_VOC_REC_STAT_BUFFER_ERROR)) + MM_ERR("error recording =0x%8x\n", + rec_status); + else if (rec_status == RPC_VOC_REC_STAT_INT_TIME) + MM_DBG("Frames recorded matches interval \ + callback time\n"); + else if (rec_status == RPC_VOC_REC_STAT_AUTO_STOP) { + MM_DBG(" Voice Record AUTO STOP\n"); + wake_up(&audio->read_wait); + audmgr_disable(&audio->audmgr); + audio->stopped = 1; + audvoicememo_ioport_reset(audio); + audio->stopped = 0; + audio->enabled = 0; + } + break; + } + default: + MM_ERR("UNKNOWN PROC , proc = 0x%8x \n", proc); + } +} + +static int voicememo_rpc_thread(void *data) +{ + struct audio_voicememo *audio = data; + struct rpc_request_hdr *hdr = NULL; + uint32_t type; + int len; + + MM_DBG("start\n"); + + while (!kthread_should_stop()) { + kfree(hdr); + hdr = NULL; + + len = msm_rpc_read(audio->sndept, (void **) &hdr, -1, -1); + MM_DBG("rpc_read len = 0x%x\n", len); + if (len < 0) { + MM_ERR("rpc read failed (%d)\n", len); + break; + } + if (len < RPC_COMMON_HDR_SZ) + continue; + type = be32_to_cpu(hdr->type); + if (type == RPC_TYPE_REPLY) { + struct rpc_reply_hdr *rep = (void *) hdr; + uint32_t status; + if (len < RPC_REPLY_HDR_SZ) + continue; + status = be32_to_cpu(rep->reply_stat); + if (status == RPCMSG_REPLYSTAT_ACCEPTED) { + status = + be32_to_cpu(rep->data.acc_hdr.accept_stat); + + /* Confirm major RPC success during open*/ + if ((audio->enabled == 0) && + (status == RPC_ACCEPTSTAT_SUCCESS) && + (audio->rpc_xid == rep->xid)) { + audio->rpc_status = \ + RPC_STATUS_SUCCESS; + wake_up(&audio->wait); + } + MM_DBG("rpc_reply status 0x%8x\n", status); + } else { + MM_ERR("rpc_reply denied!\n"); + } + /* process reply */ + continue; + } else if (type == RPC_TYPE_REQUEST) { + if (len < RPC_REQUEST_HDR_SZ) + continue; + process_rpc_request(be32_to_cpu(hdr->procedure), + be32_to_cpu(hdr->xid), + (void *) (hdr + 1), + len - sizeof(*hdr), + audio); + } else + MM_ERR("Unexpected type (%d)\n", type); + } + MM_DBG("stop\n"); + kfree(hdr); + hdr = NULL; + + return 0; +} + +/* ------------------- device --------------------- */ +static long audio_voicememo_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_voicememo *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + mutex_lock(&audio->dsp_lock); + stats.byte_count = audio->byte_count; + stats.sample_count = audio->frame_count; + mutex_unlock(&audio->dsp_lock); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + MM_DBG("AUDIO_START\n"); + audio->byte_count = 0; + audio->frame_count = 0; + if (audio->voicememo_cfg.rec_type != RPC_VOC_REC_NONE) + rc = audvoicememo_enable(audio); + else + rc = -EINVAL; + MM_DBG("AUDIO_START rc %d\n", rc); + break; + } + case AUDIO_STOP: { + MM_DBG("AUDIO_STOP\n"); + rc = audvoicememo_disable(audio); + audio->stopped = 1; + audvoicememo_ioport_reset(audio); + audio->stopped = 0; + MM_DBG("AUDIO_STOP rc %d\n", rc); + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + MM_DBG("AUDIO_GET_CONFIG\n"); + cfg.buffer_size = audio->rec_buf_size; + cfg.buffer_count = MAX_REC_BUF_COUNT; + cfg.sample_rate = 8000; /* Voice Encoder works on 8k, + * Mono */ + cfg.channel_count = 1; + cfg.type = 0; + cfg.unused[0] = 0; + cfg.unused[1] = 0; + cfg.unused[2] = 0; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + else + rc = 0; + MM_DBG("AUDIO_GET_CONFIG rc %d\n", rc); + break; + } + case AUDIO_GET_VOICEMEMO_CONFIG: { + MM_DBG("AUDIO_GET_VOICEMEMO_CONFIG\n"); + if (copy_to_user((void *)arg, &audio->voicememo_cfg, + sizeof(audio->voicememo_cfg))) + rc = -EFAULT; + else + rc = 0; + MM_DBG("AUDIO_GET_VOICEMEMO_CONFIG rc %d\n", rc); + break; + } + case AUDIO_SET_VOICEMEMO_CONFIG: { + struct msm_audio_voicememo_config usr_config; + MM_DBG("AUDIO_SET_VOICEMEMO_CONFIG\n"); + if (copy_from_user + (&usr_config, (void *)arg, + sizeof(usr_config))) { + rc = -EFAULT; + break; + } + if (audvoicememo_validate_usr_config(&usr_config) + == 0) { + audio->voicememo_cfg = usr_config; + rc = 0; + } else + rc = -EINVAL; + MM_DBG("AUDIO_SET_VOICEMEMO_CONFIG rc %d\n", rc); + break; + } + case AUDIO_PAUSE: { + struct rpc_request_hdr rhdr; + MM_DBG("AUDIO_PAUSE\n"); + if (arg == 1) + msm_rpc_setup_req(&rhdr, audio->rpc_prog, + audio->rpc_ver, SND_VOC_REC_PAUSE_PROC); + else + msm_rpc_setup_req(&rhdr, audio->rpc_prog, + audio->rpc_ver, SND_VOC_REC_RESUME_PROC); + + rc = msm_rpc_write(audio->sndept, &rhdr, sizeof(rhdr)); + MM_DBG("AUDIO_PAUSE exit %d\n", rc); + break; + } + default: + MM_ERR("IOCTL %d not supported\n", cmd); + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audio_voicememo_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_voicememo *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + mutex_lock(&audio->read_lock); + + MM_DBG("buff read =0x%8x \n", count); + + while (count > 0) { + rc = wait_event_interruptible_timeout(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped), + msecs_to_jiffies(MSM_AUD_BUFFER_UPDATE_WAIT_MS)); + + if (rc == 0) { + rc = -ETIMEDOUT; + break; + } else if (rc < 0) + break; + + if (audio->stopped) { + rc = -EBUSY; + break; + } + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver does + * not split frames, read count must be greater or + * equal to size of existing frames to copy + */ + MM_DBG("read not in frame boundary\n"); + break; + } else { + mutex_lock(&audio->dsp_lock); + dma_coherent_post_ops(); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + mutex_unlock(&audio->dsp_lock); + break; + } + count -= audio->in[audio->read_next].used; + audio->byte_count += audio->in[audio->read_next].used; + audio->frame_count += + audio->in[audio->read_next].numframes; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + mutex_unlock(&audio->dsp_lock); + if ((++audio->read_next) == MAX_REC_BUF_COUNT) + audio->read_next = 0; + if (audio->in[audio->read_next].used == 0) + break; /* No data ready at this moment + * Exit while loop to prevent + * output thread sleep too long + */ + } + } + mutex_unlock(&audio->read_lock); + if (buf > start) + rc = buf - start; + MM_DBG("exit return =0x%8x\n", rc); + return rc; +} + +static ssize_t audio_voicememo_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + return -EINVAL; +} + +static int audio_voicememo_release(struct inode *inode, struct file *file) +{ + struct audio_voicememo *audio = file->private_data; + + mutex_lock(&audio->lock); + audvoicememo_disable(audio); + audvoicememo_flush_buf(audio); + audio->opened = 0; + mutex_unlock(&audio->lock); + return 0; +} + +static int audio_voicememo_open(struct inode *inode, struct file *file) +{ + struct audio_voicememo *audio = &the_audio_voicememo; + int rc; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + + rc = audmgr_open(&audio->audmgr); + + if (rc) + goto done; + + /*Set default param to None*/ + memset(&audio->voicememo_cfg, 0, sizeof(audio->voicememo_cfg)); + + file->private_data = audio; + audio->opened = 1; + rc = 0; +done: + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_fops = { + .owner = THIS_MODULE, + .open = audio_voicememo_open, + .release = audio_voicememo_release, + .read = audio_voicememo_read, + .write = audio_voicememo_write, + .unlocked_ioctl = audio_voicememo_ioctl, +}; + +struct miscdevice audio_voicememo_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_voicememo", + .fops = &audio_fops, +}; + +static int audio_voicememo_probe(struct platform_device *pdev) +{ + int rc; + + if ((pdev->id != (SND_VERS_COMP & RPC_VERSION_MAJOR_MASK)) && + (pdev->id != (SND_VERS2_COMP & RPC_VERSION_MAJOR_MASK))) + return -EINVAL; + + mutex_init(&the_audio_voicememo.lock); + mutex_init(&the_audio_voicememo.read_lock); + mutex_init(&the_audio_voicememo.dsp_lock); + init_waitqueue_head(&the_audio_voicememo.read_wait); + init_waitqueue_head(&the_audio_voicememo.wait); + + the_audio_voicememo.rec_buf_ptr = dma_alloc_coherent(NULL, + MAX_VOICEMEMO_BUF_SIZE, + &the_audio_voicememo.phys, GFP_KERNEL); + if (the_audio_voicememo.rec_buf_ptr == NULL) { + MM_ERR("error allocating memory\n"); + rc = -ENOMEM; + return rc; + } + the_audio_voicememo.rec_buf_size = MAX_REC_BUF_SIZE; + MM_DBG("rec_buf_ptr = 0x%8x, phys = 0x%8x \n", + (uint32_t) the_audio_voicememo.rec_buf_ptr, \ + the_audio_voicememo.phys); + + the_audio_voicememo.sndept = msm_rpc_connect_compatible(SND_PROG, + SND_VERS_COMP, MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(the_audio_voicememo.sndept)) { + MM_DBG("connect failed with VERS \ + = %x, trying again with another API\n", + SND_VERS_COMP); + the_audio_voicememo.sndept = msm_rpc_connect_compatible( + SND_PROG, SND_VERS2_COMP, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(the_audio_voicememo.sndept)) { + rc = PTR_ERR(the_audio_voicememo.sndept); + the_audio_voicememo.sndept = NULL; + MM_ERR("Failed to connect to snd svc\n"); + goto err; + } + the_audio_voicememo.rpc_ver = SND_VERS2_COMP; + } else + the_audio_voicememo.rpc_ver = SND_VERS_COMP; + + the_audio_voicememo.task = kthread_run(voicememo_rpc_thread, + &the_audio_voicememo, "voicememo_rpc"); + if (IS_ERR(the_audio_voicememo.task)) { + rc = PTR_ERR(the_audio_voicememo.task); + the_audio_voicememo.task = NULL; + msm_rpc_close(the_audio_voicememo.sndept); + the_audio_voicememo.sndept = NULL; + MM_ERR("Failed to create voicememo_rpc task\n"); + goto err; + } + the_audio_voicememo.rpc_prog = SND_PROG; + + return misc_register(&audio_voicememo_misc); +err: + dma_free_coherent(NULL, MAX_VOICEMEMO_BUF_SIZE, + the_audio_voicememo.rec_buf_ptr, + the_audio_voicememo.phys); + the_audio_voicememo.rec_buf_ptr = NULL; + return rc; +} + +static void __exit audio_voicememo_exit(void) +{ + /* Close the RPC connection to make thread to comeout */ + msm_rpc_close(the_audio_voicememo.sndept); + the_audio_voicememo.sndept = NULL; + kthread_stop(the_audio_voicememo.task); + the_audio_voicememo.task = NULL; + if (the_audio_voicememo.rec_buf_ptr) + dma_free_coherent(NULL, MAX_VOICEMEMO_BUF_SIZE, + the_audio_voicememo.rec_buf_ptr, + the_audio_voicememo.phys); + the_audio_voicememo.rec_buf_ptr = NULL; + misc_deregister(&audio_voicememo_misc); +} + +static char audio_voicememo_rpc_name[] = "rs00000000"; + +static struct platform_driver audio_voicememo_driver = { + .probe = audio_voicememo_probe, + .driver = { + .owner = THIS_MODULE, + }, + }; + +static int __init audio_voicememo_init(void) +{ + snprintf(audio_voicememo_rpc_name, sizeof(audio_voicememo_rpc_name), + "rs%08x", SND_PROG); + audio_voicememo_driver.driver.name = audio_voicememo_rpc_name; + return platform_driver_register(&audio_voicememo_driver); +} + +module_init(audio_voicememo_init); +module_exit(audio_voicememo_exit); + +MODULE_DESCRIPTION("MSM Voice Memo driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("QUALCOMM"); diff --git a/arch/arm/mach-msm/qdsp5/audio_wma.c b/arch/arm/mach-msm/qdsp5/audio_wma.c new file mode 100644 index 00000000000..11f051f1e29 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_wma.c @@ -0,0 +1,1760 @@ +/* audio_wma.c - wma audio decoder driver + * + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include +#include + +/* Size must be power of 2 */ +#define BUFSZ_MAX 2062 /* Includes meta in size */ +#define BUFSZ_MIN 1038 /* Includes meta in size */ +#define DMASZ_MAX (BUFSZ_MAX * 2) +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_WMA 4 + +#define PCM_BUFSZ_MIN 8216 /* Hold one stereo WMA frame and meta out*/ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDWMA_METAFIELD_MASK 0xFFFF0000 +#define AUDWMA_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDWMA_EOS_FLG_MASK 0x01 +#define AUDWMA_EOS_NONE 0x0 /* No EOS detected */ +#define AUDWMA_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDWMA_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audwma_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audwma_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + + struct msm_audio_wma_config wma_config; + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int rmt_resource_released; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audwma_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + int eq_enable; + int eq_needs_commit; + audpp_cmd_cfg_object_params_eqalizer eq; + audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audplay_config_hostpcm(struct audio *audio); +static void audplay_buffer_refresh(struct audio *audio); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audwma_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +static int rmt_put_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_DISABLE; + cmd.dec_type = AUDDEC_DEC_WMA; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return put_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +static int rmt_get_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_ENABLE; + cmd.dec_type = AUDDEC_DEC_WMA; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return get_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + if (audio->rmt_resource_released == 1) { + audio->rmt_resource_released = 0; + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for WMA \ + session 0x%08x on decoder: %d\n Ignoring \ + error and going ahead with the playback\n", + (int)audio, audio->dec_id); + } + } + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_PLAYBACK; + cfg.codec = RPC_AUD_DEF_CODEC_WMA; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + } + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + rmt_put_resource(audio); + audio->rmt_resource_released = 1; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audio_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("audio_update_pcm_buf_entry: \ + in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + } else { + MM_ERR("audio_update_pcm_buf_entry: \ + expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audplay_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audplay_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audio_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init\n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg\n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play\n"); + if (audio->pcm_feedback) { + audplay_config_hostpcm(audio); + audplay_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status\n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq); + audpp_avsync(audio->dec_id, 22050); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audpp_avsync(audio->dec_id, 0); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audplay_buffer_refresh(audio); + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +static struct msm_adsp_ops audplay_adsp_ops_wma = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + u16 cfg_dec_cmd[AUDPP_CMD_CFG_DEC_TYPE_LEN / sizeof(unsigned short)]; + + memset(cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + cfg_dec_cmd[0] = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_WMA; + else + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_wma cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_WMA_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + + /* + * Test done for sample with the following configuration + * armdatareqthr = 1262 + * channelsdecoded = 1(MONO)/2(STEREO) + * wmabytespersec = Tested with 6003 Bytes per sec + * wmasamplingfreq = 44100 + * wmaencoderopts = 31 + */ + + cmd.armdatareqthr = audio->wma_config.armdatareqthr; + cmd.channelsdecoded = audio->wma_config.channelsdecoded; + cmd.wmabytespersec = audio->wma_config.wmabytespersec; + cmd.wmasamplingfreq = audio->wma_config.wmasamplingfreq; + cmd.wmaencoderopts = audio->wma_config.wmaencoderopts; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static void audplay_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + refresh_cmd.buf_read_count = 0; + + MM_DBG("buf0_addr=%x buf0_len=%d\n", + refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audplay_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); +} + + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDWMA_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len/2; + cmd.partition_number = 0; + /* complete writes to the input buffer */ + wmb(); + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (audio->wflush) { + audio->out_needed = 1; + goto done; + } + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + MM_DBG("\n"); /* Macro prints the file name and function */ + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audio_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); +} + +static int audwma_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audwma_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audwma_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwma_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audwma_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audwma_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audwma_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audwma_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audwma_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwma_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq); + audio->eq_needs_commit = 0; + } + return 0; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = audpp_avsync_byte_count(audio->dec_id); + stats.sample_count = audpp_avsync_sample_count(audio->dec_id); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audwma_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + audio->mfield = config.meta_field; + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_WMA_CONFIG:{ + if (copy_to_user((void *)arg, &audio->wma_config, + sizeof(audio->wma_config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_WMA_CONFIG:{ + struct msm_audio_wma_config usr_config; + + if (copy_from_user + (&usr_config, (void *)arg, + sizeof(usr_config))) { + rc = -EFAULT; + break; + } + + audio->wma_config = usr_config; + rc = 0; + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buffer %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("read buf alloc fail\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; + index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr \ + 0x%08x kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audio_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audplay_send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d \n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver + does not know frame size, read count must be greater + or equal to size of PCM samples */ + MM_DBG("audio_read: no partial frame done reading\n"); + break; + } else { + MM_DBG("audio_read: read from in[%d]\n", + audio->read_next); + /* order reads from the output buffer */ + rmb(); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audplay_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audwma_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + char *buf_ptr; + + if (audio->reserved) { + MM_DBG("flush reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + frame->used = 2; + audio->reserved = 0; + audplay_send_data(audio, 0); + } + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audplay_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDWMA_EOS_NONE; + unsigned dsize; + unsigned short mfield_size = 0; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("audio_write: mf offset_val %x\n", + mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDWMA_EOS_FLG_OFFSET] & + AUDWMA_EOS_FLG_MASK) { + MM_DBG("audio_write: EOS SET\n"); + eos_condition = AUDWMA_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDWMA_EOS_FLG_OFFSET] + &= ~AUDWMA_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("audio_write: continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + (frame->size - mfield_size) - 1 : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audplay_send_data(audio, 0); + } + } + if (eos_condition == AUDWMA_EOS_SET) + rc = audwma_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + audio_disable(audio); + if (audio->rmt_resource_released == 0) + rmt_put_resource(audio); + audio_flush(audio); + audio_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audwma_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audwma_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audwma_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audwma_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audwma_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static void audwma_suspend(struct early_suspend *h) +{ + struct audwma_suspend_ctl *ctl = + container_of(h, struct audwma_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audwma_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audwma_resume(struct early_suspend *h) +{ + struct audwma_suspend_ctl *ctl = + container_of(h, struct audwma_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audwma_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audwma_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audwma_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audwma_debug_fops = { + .read = audwma_debug_read, + .open = audwma_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + unsigned pmem_sz = DMASZ_MAX; + struct audwma_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_wma_" + 5]; +#endif + + /* Allocate Mem for audio instance */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_WMA; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d\n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr \ + 0x%08x\n", audio->phys, (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) { + rc = audmgr_open(&audio->audmgr); + if (rc) { + MM_ERR("audmgr open failed, freeing instance \ + 0x%08x\n", (int)audio); + goto err; + } + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_wma, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + goto err; + } + + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for WMA session \ + 0x%08x on decoder: %d\n", (int)audio, audio->dec_id); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + msm_adsp_put(audio->audplay); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = audio->out_dma_sz >> 1; + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + + audio->wma_config.armdatareqthr = 1262; + audio->wma_config.channelsdecoded = 2; + audio->wma_config.wmabytespersec = 6003; + audio->wma_config.wmasamplingfreq = 44100; + audio->wma_config.wmaencoderopts = 31; + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + + audio->vol_pan.volume = 0x2000; + + audio_flush(audio); + + file->private_data = audio; + audio->opened = 1; +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_wma_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, + &audwma_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audwma_resume; + audio->suspend_ctl.node.suspend = audwma_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDWMA_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audwma_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_wma_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_fsync, +}; + +struct miscdevice audio_wma_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_wma", + .fops = &audio_wma_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_wma_misc); +} + +device_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5/audio_wmapro.c b/arch/arm/mach-msm/qdsp5/audio_wmapro.c new file mode 100644 index 00000000000..b5ddf5fe209 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audio_wmapro.c @@ -0,0 +1,1748 @@ +/* audio_wmapro.c - wmapro audio decoder driver + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "audmgr.h" + +#include +#include +#include +#include +#include +#include +#include + +/* Size must be power of 2 */ +#define BUFSZ_MAX 8206 /* Includes meta in size */ +#define BUFSZ_MIN 2062 /* Includes meta in size */ +#define DMASZ_MAX (BUFSZ_MAX * 2) +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_WMAPRO 13 + +#define PCM_BUFSZ_MIN 8216 /* Hold one stereo WMAPRO frame and meta out*/ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDWMAPRO_METAFIELD_MASK 0xFFFF0000 +#define AUDWMAPRO_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDWMAPRO_EOS_FLG_MASK 0x01 +#define AUDWMAPRO_EOS_NONE 0x0 /* No EOS detected */ +#define AUDWMAPRO_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDWMAPRO_EVENT_NUM 10 /* Default no. of pre-allocated event packets */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audwmapro_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audwmapro_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + + struct msm_audio_wmapro_config wmapro_config; + struct audmgr audmgr; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int rmt_resource_released; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audwmapro_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + int eq_enable; + int eq_needs_commit; + audpp_cmd_cfg_object_params_eqalizer eq; + audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audplay_config_hostpcm(struct audio *audio); +static void audplay_buffer_refresh(struct audio *audio); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audwmapro_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +#endif + +static int rmt_put_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_DISABLE; + cmd.dec_type = AUDDEC_DEC_WMAPRO; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return put_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +static int rmt_get_resource(struct audio *audio) +{ + struct aud_codec_config_cmd cmd; + unsigned short client_idx; + + cmd.cmd_id = RM_CMD_AUD_CODEC_CFG; + cmd.client_id = RM_AUD_CLIENT_ID; + cmd.task_id = audio->dec_id; + cmd.enable = RMT_ENABLE; + cmd.dec_type = AUDDEC_DEC_WMAPRO; + client_idx = ((cmd.client_id << 8) | cmd.task_id); + + return get_adsp_resource(client_idx, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + struct audmgr_config cfg; + int rc; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + if (audio->rmt_resource_released == 1) { + audio->rmt_resource_released = 0; + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for WMAPRO \ + session 0x%08x on decoder: %d\n Ignoring \ + error and going ahead with the playback\n", + (int)audio, audio->dec_id); + } + } + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + cfg.tx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_PLAYBACK; + cfg.codec = RPC_AUD_DEF_CODEC_WMA; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&audio->audmgr, &cfg); + if (rc < 0) + return rc; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + audmgr_disable(&audio->audmgr); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audmgr_disable(&audio->audmgr); + audio->out_needed = 0; + rmt_put_resource(audio); + audio->rmt_resource_released = 1; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audio_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("audio_update_pcm_buf_entry: \ + in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + } else { + MM_ERR("audio_update_pcm_buf_entry: \ + expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audplay_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audplay_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audio_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init\n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg\n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play\n"); + if (audio->pcm_feedback) { + audplay_config_hostpcm(audio); + audplay_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status\n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq); + audpp_avsync(audio->dec_id, 22050); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audpp_avsync(audio->dec_id, 0); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audplay_buffer_refresh(audio); + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +static struct msm_adsp_ops audplay_adsp_ops_wmapro = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + u16 cfg_dec_cmd[AUDPP_CMD_CFG_DEC_TYPE_LEN / sizeof(unsigned short)]; + + memset(cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + cfg_dec_cmd[0] = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_WMAPRO; + else + cfg_dec_cmd[1 + audio->dec_id] = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_wmapro cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_WMAPRO_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + + cmd.armdatareqthr = audio->wmapro_config.armdatareqthr; + cmd.numchannels = audio->wmapro_config.numchannels; + cmd.validbitspersample = audio->wmapro_config.validbitspersample; + cmd.formattag = audio->wmapro_config.formattag; + cmd.samplingrate = audio->wmapro_config.samplingrate; + cmd.avgbytespersecond = audio->wmapro_config.avgbytespersecond; + cmd.asfpacketlength = audio->wmapro_config.asfpacketlength; + cmd.channelmask = audio->wmapro_config.channelmask; + cmd.encodeopt = audio->wmapro_config.encodeopt; + cmd.advancedencodeopt = audio->wmapro_config.advancedencodeopt; + cmd.advancedencodeopt2 = audio->wmapro_config.advancedencodeopt2; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static void audplay_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + refresh_cmd.buf_read_count = 0; + + MM_DBG("buf0_addr=%x buf0_len=%d\n", + refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audplay_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); +} + + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDWMAPRO_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len/2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (audio->wflush) { + audio->out_needed = 1; + goto done; + } + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + MM_DBG("\n"); /* Macro prints the file name and function */ + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audio_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); +} + +static int audwmapro_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audwmapro_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audwmapro_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwmapro_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audwmapro_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audwmapro_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audwmapro_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout(audio->event_wait, + audwmapro_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audwmapro_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwmapro_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq); + audio->eq_needs_commit = 0; + } + return 0; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = audpp_avsync_byte_count(audio->dec_id); + stats.sample_count = audpp_avsync_sample_count(audio->dec_id); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audwmapro_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + audio->mfield = config.meta_field; + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_WMAPRO_CONFIG:{ + if (copy_to_user((void *)arg, &audio->wmapro_config, + sizeof(audio->wmapro_config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_WMAPRO_CONFIG:{ + struct msm_audio_wmapro_config usr_config; + + if (copy_from_user + (&usr_config, (void *)arg, + sizeof(usr_config))) { + rc = -EFAULT; + break; + } + + audio->wmapro_config = usr_config; + + /* Need to swap the first and last words of advancedencodeopt2 + * as DSP cannot read 32-bit variable at a time. Need to be + * split into two 16-bit and swap them as required by DSP */ + + audio->wmapro_config.advancedencodeopt2 = + ((audio->wmapro_config.advancedencodeopt2 & 0xFFFF0000) + >> 16) | ((audio->wmapro_config.advancedencodeopt2 + << 16) & 0xFFFF0000); + rc = 0; + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buffer %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("read buf alloc fail\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->pcm_feedback = 1; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; + index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr \ + 0x%08x kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audio_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audplay_send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d \n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver + does not know frame size, read count must be greater + or equal to size of PCM samples */ + MM_DBG("audio_read: no partial frame done reading\n"); + break; + } else { + MM_DBG("audio_read: read from in[%d]\n", + audio->read_next); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audplay_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audwmapro_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + char *buf_ptr; + + if (audio->reserved) { + MM_DBG("flush reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + frame->used = 2; + audio->reserved = 0; + audplay_send_data(audio, 0); + } + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audplay_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDWMAPRO_EOS_NONE; + unsigned dsize; + unsigned short mfield_size = 0; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("audio_write: mf offset_val %x\n", + mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDWMAPRO_EOS_FLG_OFFSET] & + AUDWMAPRO_EOS_FLG_MASK) { + MM_DBG("audio_write: EOS SET\n"); + eos_condition = AUDWMAPRO_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDWMAPRO_EOS_FLG_OFFSET] + &= ~AUDWMAPRO_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("audio_write: continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + (frame->size - mfield_size) - 1 : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audplay_send_data(audio, 0); + } + } + if (eos_condition == AUDWMAPRO_EOS_SET) + rc = audwmapro_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + audio_disable(audio); + if (audio->rmt_resource_released == 0) + rmt_put_resource(audio); + audio_flush(audio); + audio_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audwmapro_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audwmapro_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audwmapro_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audwmapro_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audwmapro_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static void audwmapro_suspend(struct early_suspend *h) +{ + struct audwmapro_suspend_ctl *ctl = + container_of(h, struct audwmapro_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audwmapro_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audwmapro_resume(struct early_suspend *h) +{ + struct audwmapro_suspend_ctl *ctl = + container_of(h, struct audwmapro_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audwmapro_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audwmapro_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audwmapro_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audwmapro_debug_fops = { + .read = audwmapro_debug_read, + .open = audwmapro_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + unsigned pmem_sz = DMASZ_MAX; + struct audwmapro_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_wmapro_" + 5]; +#endif + + /* Allocate Mem for audio instance */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_WMAPRO; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d\n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr \ + 0x%08x\n", audio->phys, (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + + rc = audmgr_open(&audio->audmgr); + if (rc) { + MM_ERR("audmgr open failed, freeing instance 0x%08x\n", + (int)audio); + goto err; + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_wmapro, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + audmgr_close(&audio->audmgr); + goto err; + } + + rc = rmt_get_resource(audio); + if (rc) { + MM_ERR("ADSP resources are not available for WMAPRO session \ + 0x%08x on decoder: %d\n", (int)audio, audio->dec_id); + if (audio->pcm_feedback == TUNNEL_MODE_PLAYBACK) + audmgr_close(&audio->audmgr); + msm_adsp_put(audio->audplay); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = audio->out_dma_sz >> 1; + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + + audio->vol_pan.volume = 0x2000; + + audio_flush(audio); + + file->private_data = audio; + audio->opened = 1; +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_wmapro_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, + &audwmapro_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audwmapro_resume; + audio->suspend_ctl.node.suspend = audwmapro_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDWMAPRO_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audwmapro_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_wmapro_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_fsync, +}; + +struct miscdevice audio_wmapro_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_wmapro", + .fops = &audio_wmapro_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_wmapro_misc); +} + +device_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5/audmgr.c b/arch/arm/mach-msm/qdsp5/audmgr.c new file mode 100644 index 00000000000..231a28cdde8 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audmgr.c @@ -0,0 +1,365 @@ +/* arch/arm/mach-msm/qdsp5/audmgr.c + * + * interface to "audmgr" service on the baseband cpu + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include +#include +#include +#include + +#include +#include + +#include "audmgr.h" +#include + +#define STATE_CLOSED 0 +#define STATE_DISABLED 1 +#define STATE_ENABLING 2 +#define STATE_ENABLED 3 +#define STATE_DISABLING 4 +#define STATE_ERROR 5 + +/* store information used across complete audmgr sessions */ +struct audmgr_global { + struct mutex *lock; + struct msm_rpc_endpoint *ept; + struct task_struct *task; + uint32_t rpc_version; +}; +static DEFINE_MUTEX(audmgr_lock); + +static struct audmgr_global the_audmgr_state = { + .lock = &audmgr_lock, +}; + +static void rpc_ack(struct msm_rpc_endpoint *ept, uint32_t xid) +{ + uint32_t rep[6]; + + rep[0] = cpu_to_be32(xid); + rep[1] = cpu_to_be32(1); + rep[2] = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + rep[3] = cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); + rep[4] = 0; + rep[5] = 0; + + msm_rpc_write(ept, rep, sizeof(rep)); +} + +static void process_audmgr_callback(struct audmgr_global *amg, + struct rpc_audmgr_cb_func_ptr *args, + int len) +{ + struct audmgr *am; + + /* Allow only if complete arguments recevied */ + if (len < (sizeof(struct rpc_audmgr_cb_func_ptr))) + return; + + /* Allow only if valid argument */ + if (be32_to_cpu(args->set_to_one) != 1) + return; + + am = (struct audmgr *) be32_to_cpu(args->client_data); + + if (!am) + return; + + switch (be32_to_cpu(args->status)) { + case RPC_AUDMGR_STATUS_READY: + am->handle = be32_to_cpu(args->u.handle); + MM_INFO("rpc READY handle=0x%08x\n", am->handle); + break; + case RPC_AUDMGR_STATUS_CODEC_CONFIG: { + uint32_t volume; + volume = be32_to_cpu(args->u.volume); + MM_INFO("rpc CODEC_CONFIG volume=0x%08x\n", volume); + am->state = STATE_ENABLED; + wake_up(&am->wait); + break; + } + case RPC_AUDMGR_STATUS_PENDING: + MM_ERR("PENDING?\n"); + break; + case RPC_AUDMGR_STATUS_SUSPEND: + MM_ERR("SUSPEND?\n"); + break; + case RPC_AUDMGR_STATUS_FAILURE: + MM_ERR("FAILURE\n"); + break; + case RPC_AUDMGR_STATUS_VOLUME_CHANGE: + MM_ERR("VOLUME_CHANGE?\n"); + break; + case RPC_AUDMGR_STATUS_DISABLED: + MM_ERR("DISABLED\n"); + am->state = STATE_DISABLED; + wake_up(&am->wait); + break; + case RPC_AUDMGR_STATUS_ERROR: + MM_ERR("ERROR?\n"); + am->state = STATE_ERROR; + wake_up(&am->wait); + break; + default: + break; + } +} + +static void process_rpc_request(uint32_t proc, uint32_t xid, + void *data, int len, void *private) +{ + struct audmgr_global *amg = private; + + if (proc == AUDMGR_CB_FUNC_PTR) + process_audmgr_callback(amg, data, len); + else + MM_ERR("unknown rpc proc %d\n", proc); + rpc_ack(amg->ept, xid); +} + +#define RPC_TYPE_REQUEST 0 +#define RPC_TYPE_REPLY 1 + +#define RPC_VERSION 2 + +#define RPC_COMMON_HDR_SZ (sizeof(uint32_t) * 2) +#define RPC_REQUEST_HDR_SZ (sizeof(struct rpc_request_hdr)) +#define RPC_REPLY_HDR_SZ (sizeof(uint32_t) * 3) +#define RPC_REPLY_SZ (sizeof(uint32_t) * 6) + +static int audmgr_rpc_thread(void *data) +{ + struct audmgr_global *amg = data; + struct rpc_request_hdr *hdr = NULL; + uint32_t type; + int len; + + MM_INFO("start\n"); + + while (!kthread_should_stop()) { + if (hdr) { + kfree(hdr); + hdr = NULL; + } + len = msm_rpc_read(amg->ept, (void **) &hdr, -1, -1); + if (len < 0) { + MM_ERR("rpc read failed (%d)\n", len); + break; + } + if (len < RPC_COMMON_HDR_SZ) + continue; + + type = be32_to_cpu(hdr->type); + if (type == RPC_TYPE_REPLY) { + struct rpc_reply_hdr *rep = (void *) hdr; + uint32_t status; + if (len < RPC_REPLY_HDR_SZ) + continue; + status = be32_to_cpu(rep->reply_stat); + if (status == RPCMSG_REPLYSTAT_ACCEPTED) { + status = be32_to_cpu(rep->data.acc_hdr.accept_stat); + MM_INFO("rpc_reply status %d\n", status); + } else { + MM_INFO("rpc_reply denied!\n"); + } + /* process reply */ + continue; + } + + if (len < RPC_REQUEST_HDR_SZ) + continue; + + process_rpc_request(be32_to_cpu(hdr->procedure), + be32_to_cpu(hdr->xid), + (void *) (hdr + 1), + len - sizeof(*hdr), + data); + } + MM_INFO("exit\n"); + if (hdr) { + kfree(hdr); + hdr = NULL; + } + amg->task = NULL; + return 0; +} + +struct audmgr_enable_msg { + struct rpc_request_hdr hdr; + struct rpc_audmgr_enable_client_args args; +}; + +struct audmgr_disable_msg { + struct rpc_request_hdr hdr; + uint32_t handle; +}; + +int audmgr_open(struct audmgr *am) +{ + struct audmgr_global *amg = &the_audmgr_state; + int rc; + + if (am->state != STATE_CLOSED) + return 0; + + mutex_lock(amg->lock); + + /* connect to audmgr end point and polling thread only once */ + if (amg->ept == NULL) { + amg->ept = msm_rpc_connect_compatible(AUDMGR_PROG, + AUDMGR_VERS_COMP_VER3, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(amg->ept)) { + MM_ERR("connect failed with current VERS \ + = %x, trying again with another API\n", + AUDMGR_VERS_COMP_VER3); + amg->ept = msm_rpc_connect_compatible(AUDMGR_PROG, + AUDMGR_VERS_COMP_VER2, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(amg->ept)) { + MM_ERR("connect failed with current VERS \ + = %x, trying again with another API\n", + AUDMGR_VERS_COMP_VER2); + amg->ept = msm_rpc_connect_compatible( + AUDMGR_PROG, + AUDMGR_VERS_COMP, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(amg->ept)) { + MM_ERR("connect failed with current \ + VERS=%x, trying again with another \ + API\n", AUDMGR_VERS_COMP); + amg->ept = msm_rpc_connect(AUDMGR_PROG, + AUDMGR_VERS, + MSM_RPC_UNINTERRUPTIBLE); + amg->rpc_version = AUDMGR_VERS; + } else + amg->rpc_version = AUDMGR_VERS_COMP; + } else + amg->rpc_version = AUDMGR_VERS_COMP_VER2; + } else + amg->rpc_version = AUDMGR_VERS_COMP_VER3; + + if (IS_ERR(amg->ept)) { + rc = PTR_ERR(amg->ept); + amg->ept = NULL; + MM_ERR("failed to connect to audmgr svc\n"); + goto done; + } + + amg->task = kthread_run(audmgr_rpc_thread, amg, "audmgr_rpc"); + if (IS_ERR(amg->task)) { + rc = PTR_ERR(amg->task); + amg->task = NULL; + msm_rpc_close(amg->ept); + amg->ept = NULL; + goto done; + } + } + + /* Initialize session parameters */ + init_waitqueue_head(&am->wait); + am->state = STATE_DISABLED; + rc = 0; +done: + mutex_unlock(amg->lock); + return rc; +} +EXPORT_SYMBOL(audmgr_open); + +int audmgr_close(struct audmgr *am) +{ + return -EBUSY; +} +EXPORT_SYMBOL(audmgr_close); + +int audmgr_enable(struct audmgr *am, struct audmgr_config *cfg) +{ + struct audmgr_global *amg = &the_audmgr_state; + struct audmgr_enable_msg msg; + int rc; + + if (am->state == STATE_ENABLED) + return 0; + + if (am->state == STATE_DISABLING) + MM_ERR("state is DISABLING in enable?\n"); + am->state = STATE_ENABLING; + + MM_INFO("session 0x%08x\n", (int) am); + msg.args.set_to_one = cpu_to_be32(1); + msg.args.tx_sample_rate = cpu_to_be32(cfg->tx_rate); + msg.args.rx_sample_rate = cpu_to_be32(cfg->rx_rate); + msg.args.def_method = cpu_to_be32(cfg->def_method); + msg.args.codec_type = cpu_to_be32(cfg->codec); + msg.args.snd_method = cpu_to_be32(cfg->snd_method); + msg.args.cb_func = cpu_to_be32(0x11111111); + msg.args.client_data = cpu_to_be32((int)am); + + msm_rpc_setup_req(&msg.hdr, AUDMGR_PROG, amg->rpc_version, + AUDMGR_ENABLE_CLIENT); + + rc = msm_rpc_write(amg->ept, &msg, sizeof(msg)); + if (rc < 0) + return rc; + + rc = wait_event_timeout(am->wait, am->state != STATE_ENABLING, 15 * HZ); + if (rc == 0) { + MM_ERR("ARM9 did not reply to RPC am->state = %d\n", am->state); + } + if (am->state == STATE_ENABLED) + return 0; + + MM_ERR("unexpected state %d while enabling?!\n", am->state); + return -ENODEV; +} +EXPORT_SYMBOL(audmgr_enable); + +int audmgr_disable(struct audmgr *am) +{ + struct audmgr_global *amg = &the_audmgr_state; + struct audmgr_disable_msg msg; + int rc; + + if (am->state == STATE_DISABLED) + return 0; + + MM_INFO("session 0x%08x\n", (int) am); + msg.handle = cpu_to_be32(am->handle); + msm_rpc_setup_req(&msg.hdr, AUDMGR_PROG, amg->rpc_version, + AUDMGR_DISABLE_CLIENT); + + am->state = STATE_DISABLING; + + rc = msm_rpc_write(amg->ept, &msg, sizeof(msg)); + if (rc < 0) + return rc; + + rc = wait_event_timeout(am->wait, am->state != STATE_DISABLING, 15 * HZ); + if (rc == 0) { + MM_ERR("ARM9 did not reply to RPC am->state = %d\n", am->state); + } + + if (am->state == STATE_DISABLED) + return 0; + + MM_ERR("unexpected state %d while disabling?!\n", am->state); + return -ENODEV; +} +EXPORT_SYMBOL(audmgr_disable); diff --git a/arch/arm/mach-msm/qdsp5/audmgr.h b/arch/arm/mach-msm/qdsp5/audmgr.h new file mode 100644 index 00000000000..225beef6284 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audmgr.h @@ -0,0 +1,266 @@ +/* arch/arm/mach-msm/qdsp5/audmgr.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _AUDIO_RPC_H_ +#define _AUDIO_RPC_H_ + +#include + +enum rpc_aud_def_sample_rate_type { + RPC_AUD_DEF_SAMPLE_RATE_NONE, + RPC_AUD_DEF_SAMPLE_RATE_8000, + RPC_AUD_DEF_SAMPLE_RATE_11025, + RPC_AUD_DEF_SAMPLE_RATE_12000, + RPC_AUD_DEF_SAMPLE_RATE_16000, + RPC_AUD_DEF_SAMPLE_RATE_22050, + RPC_AUD_DEF_SAMPLE_RATE_24000, + RPC_AUD_DEF_SAMPLE_RATE_32000, + RPC_AUD_DEF_SAMPLE_RATE_44100, + RPC_AUD_DEF_SAMPLE_RATE_48000, + RPC_AUD_DEF_SAMPLE_RATE_MAX, +}; + +enum rpc_aud_def_method_type { + RPC_AUD_DEF_METHOD_NONE, + RPC_AUD_DEF_METHOD_KEY_BEEP, + RPC_AUD_DEF_METHOD_PLAYBACK, + RPC_AUD_DEF_METHOD_VOICE, + RPC_AUD_DEF_METHOD_RECORD, + RPC_AUD_DEF_METHOD_HOST_PCM, + RPC_AUD_DEF_METHOD_MIDI_OUT, + RPC_AUD_DEF_METHOD_RECORD_SBC, + RPC_AUD_DEF_METHOD_DTMF_RINGER, + RPC_AUD_DEF_METHOD_MAX, +}; + +enum rpc_aud_def_codec_type { + RPC_AUD_DEF_CODEC_NONE, + RPC_AUD_DEF_CODEC_DTMF, + RPC_AUD_DEF_CODEC_MIDI, + RPC_AUD_DEF_CODEC_MP3, + RPC_AUD_DEF_CODEC_PCM, + RPC_AUD_DEF_CODEC_AAC, + RPC_AUD_DEF_CODEC_WMA, + RPC_AUD_DEF_CODEC_RA, + RPC_AUD_DEF_CODEC_ADPCM, + RPC_AUD_DEF_CODEC_GAUDIO, + RPC_AUD_DEF_CODEC_VOC_EVRC, + RPC_AUD_DEF_CODEC_VOC_13K, + RPC_AUD_DEF_CODEC_VOC_4GV_NB, + RPC_AUD_DEF_CODEC_VOC_AMR, + RPC_AUD_DEF_CODEC_VOC_EFR, + RPC_AUD_DEF_CODEC_VOC_FR, + RPC_AUD_DEF_CODEC_VOC_HR, + RPC_AUD_DEF_CODEC_VOC_CDMA, + RPC_AUD_DEF_CODEC_VOC_CDMA_WB, + RPC_AUD_DEF_CODEC_VOC_UMTS, + RPC_AUD_DEF_CODEC_VOC_UMTS_WB, + RPC_AUD_DEF_CODEC_SBC, + RPC_AUD_DEF_CODEC_VOC_PCM, + RPC_AUD_DEF_CODEC_AMR_WB, + RPC_AUD_DEF_CODEC_AMR_WB_PLUS, + RPC_AUD_DEF_CODEC_AAC_BSAC, + RPC_AUD_DEF_CODEC_MAX, + RPC_AUD_DEF_CODEC_AMR_NB, + RPC_AUD_DEF_CODEC_13K, + RPC_AUD_DEF_CODEC_EVRC, + RPC_AUD_DEF_CODEC_MAX_002, +}; + +enum rpc_snd_method_type { + RPC_SND_METHOD_VOICE = 0, + RPC_SND_METHOD_KEY_BEEP, + RPC_SND_METHOD_MESSAGE, + RPC_SND_METHOD_RING, + RPC_SND_METHOD_MIDI, + RPC_SND_METHOD_AUX, + RPC_SND_METHOD_MAX, +}; + +enum rpc_voc_codec_type { + RPC_VOC_CODEC_DEFAULT, + RPC_VOC_CODEC_ON_CHIP_0 = RPC_VOC_CODEC_DEFAULT, + RPC_VOC_CODEC_ON_CHIP_1, + RPC_VOC_CODEC_STEREO_HEADSET, + RPC_VOC_CODEC_ON_CHIP_AUX, + RPC_VOC_CODEC_BT_OFF_BOARD, + RPC_VOC_CODEC_BT_A2DP, + RPC_VOC_CODEC_OFF_BOARD, + RPC_VOC_CODEC_SDAC, + RPC_VOC_CODEC_RX_EXT_SDAC_TX_INTERNAL, + RPC_VOC_CODEC_IN_STEREO_SADC_OUT_MONO_HANDSET, + RPC_VOC_CODEC_IN_STEREO_SADC_OUT_STEREO_HEADSET, + RPC_VOC_CODEC_TX_INT_SADC_RX_EXT_AUXPCM, + RPC_VOC_CODEC_EXT_STEREO_SADC_OUT_MONO_HANDSET, + RPC_VOC_CODEC_EXT_STEREO_SADC_OUT_STEREO_HEADSET, + RPC_VOC_CODEC_TTY_ON_CHIP_1, + RPC_VOC_CODEC_TTY_OFF_BOARD, + RPC_VOC_CODEC_TTY_VCO, + RPC_VOC_CODEC_TTY_HCO, + RPC_VOC_CODEC_ON_CHIP_0_DUAL_MIC, + RPC_VOC_CODEC_MAX, + RPC_VOC_CODEC_NONE, +}; + +enum rpc_audmgr_status_type { + RPC_AUDMGR_STATUS_READY, + RPC_AUDMGR_STATUS_CODEC_CONFIG, + RPC_AUDMGR_STATUS_PENDING, + RPC_AUDMGR_STATUS_SUSPEND, + RPC_AUDMGR_STATUS_FAILURE, + RPC_AUDMGR_STATUS_VOLUME_CHANGE, + RPC_AUDMGR_STATUS_DISABLED, + RPC_AUDMGR_STATUS_ERROR, +}; + +struct rpc_audmgr_enable_client_args { + uint32_t set_to_one; + uint32_t tx_sample_rate; + uint32_t rx_sample_rate; + uint32_t def_method; + uint32_t codec_type; + uint32_t snd_method; + + uint32_t cb_func; + uint32_t client_data; +}; + +#define AUDMGR_ENABLE_CLIENT 2 +#define AUDMGR_DISABLE_CLIENT 3 +#define AUDMGR_SUSPEND_EVENT_RSP 4 +#define AUDMGR_REGISTER_OPERATION_LISTENER 5 +#define AUDMGR_UNREGISTER_OPERATION_LISTENER 6 +#define AUDMGR_REGISTER_CODEC_LISTENER 7 +#define AUDMGR_GET_RX_SAMPLE_RATE 8 +#define AUDMGR_GET_TX_SAMPLE_RATE 9 +#define AUDMGR_SET_DEVICE_MODE 10 + +#define AUDMGR_PROG_VERS "rs30000013:0x7feccbff" +#define AUDMGR_PROG 0x30000013 +#define AUDMGR_VERS 0x7feccbff +#define AUDMGR_VERS_COMP 0x00010001 +#define AUDMGR_VERS_COMP_VER2 0x00020001 +#define AUDMGR_VERS_COMP_VER3 0x00030001 + +struct rpc_audmgr_cb_func_ptr { + uint32_t cb_id; /* cb_func */ + uint32_t status; /* Audmgr status */ + uint32_t set_to_one; /* Pointer status (1 = valid, 0 = invalid) */ + uint32_t disc; + /* disc = AUDMGR_STATUS_READY => data=handle + disc = AUDMGR_STATUS_CODEC_CONFIG => data = volume + disc = AUDMGR_STATUS_DISABLED => data =status_disabled + disc = AUDMGR_STATUS_VOLUME_CHANGE => data = volume_change */ + union { + uint32_t handle; + uint32_t volume; + uint32_t status_disabled; + uint32_t volume_change; + } u; + uint32_t client_data; +}; + +#define AUDMGR_CB_FUNC_PTR 1 +#define AUDMGR_OPR_LSTNR_CB_FUNC_PTR 2 +#define AUDMGR_CODEC_LSTR_FUNC_PTR 3 + +#define AUDMGR_CB_PROG_VERS "rs31000013:0xf8e3e2d9" +#define AUDMGR_CB_PROG 0x31000013 +#define AUDMGR_CB_VERS 0xf8e3e2d9 + +struct audmgr { + wait_queue_head_t wait; + uint32_t handle; + int state; +}; + +struct audmgr_config { + uint32_t tx_rate; + uint32_t rx_rate; + uint32_t def_method; + uint32_t codec; + uint32_t snd_method; +}; + +int audmgr_open(struct audmgr *am); +int audmgr_close(struct audmgr *am); +int audmgr_enable(struct audmgr *am, struct audmgr_config *cfg); +int audmgr_disable(struct audmgr *am); + +typedef void (*audpp_event_func)(void *private, unsigned id, uint16_t *msg); +typedef void (*audrec_event_func)(void *private, unsigned id, uint16_t *msg); + +/* worst case delay of 1sec for response */ +#define MSM_AUD_DECODER_WAIT_MS 1000 +#define MSM_AUD_MODE_TUNNEL 0x00000100 +#define MSM_AUD_MODE_NONTUNNEL 0x00000200 +#define MSM_AUD_DECODER_MASK 0x0000FFFF +#define MSM_AUD_OP_MASK 0xFFFF0000 + +/*Playback mode*/ +#define NON_TUNNEL_MODE_PLAYBACK 1 +#define TUNNEL_MODE_PLAYBACK 0 + +enum msm_aud_decoder_state { + MSM_AUD_DECODER_STATE_NONE = 0, + MSM_AUD_DECODER_STATE_FAILURE = 1, + MSM_AUD_DECODER_STATE_SUCCESS = 2, + MSM_AUD_DECODER_STATE_CLOSE = 3, +}; + +int audpp_adec_alloc(unsigned dec_attrb, const char **module_name, + unsigned *queueid); +void audpp_adec_free(int decid); + +struct audpp_event_callback { + audpp_event_func fn; + void *private; +}; + +int audpp_register_event_callback(struct audpp_event_callback *eh); +int audpp_unregister_event_callback(struct audpp_event_callback *eh); +int is_audpp_enable(void); + +int audpp_enable(int id, audpp_event_func func, void *private); +void audpp_disable(int id, void *private); + +int audpp_send_queue1(void *cmd, unsigned len); +int audpp_send_queue2(void *cmd, unsigned len); +int audpp_send_queue3(void *cmd, unsigned len); + +int audpp_set_volume_and_pan(unsigned id, unsigned volume, int pan); +int audpp_pause(unsigned id, int pause); +int audpp_flush(unsigned id); +void audpp_avsync(int id, unsigned rate); +unsigned audpp_avsync_sample_count(int id); +unsigned audpp_avsync_byte_count(int id); +int audpp_dsp_set_mbadrc(unsigned id, unsigned enable, + audpp_cmd_cfg_object_params_mbadrc *mbadrc); +int audpp_dsp_set_eq(unsigned id, unsigned enable, + audpp_cmd_cfg_object_params_eqalizer *eq); +int audpp_dsp_set_rx_iir(unsigned id, unsigned enable, + audpp_cmd_cfg_object_params_pcm *iir); +int audpp_dsp_set_vol_pan(unsigned id, + audpp_cmd_cfg_object_params_volume *vol_pan); +int audpp_dsp_set_qconcert_plus(unsigned id, unsigned enable, + audpp_cmd_cfg_object_params_qconcert *qconcert_plus); +int audrectask_enable(unsigned enc_type, audrec_event_func func, void *private); +void audrectask_disable(unsigned enc_type, void *private); + +int audrectask_send_cmdqueue(void *cmd, unsigned len); +int audrectask_send_bitstreamqueue(void *cmd, unsigned len); + +#endif diff --git a/arch/arm/mach-msm/qdsp5/audmgr_new.h b/arch/arm/mach-msm/qdsp5/audmgr_new.h new file mode 100644 index 00000000000..3604405b39c --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audmgr_new.h @@ -0,0 +1,213 @@ +/* arch/arm/mach-msm/qdsp5/audmgr.h + * + * Copyright 2008 (c) Code Aurora Forum. All rights reserved. + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_AUDMGR_NEW_H +#define _ARCH_ARM_MACH_MSM_AUDMGR_NEW_H + +enum rpc_aud_def_sample_rate_type { + RPC_AUD_DEF_SAMPLE_RATE_NONE, + RPC_AUD_DEF_SAMPLE_RATE_8000, + RPC_AUD_DEF_SAMPLE_RATE_11025, + RPC_AUD_DEF_SAMPLE_RATE_12000, + RPC_AUD_DEF_SAMPLE_RATE_16000, + RPC_AUD_DEF_SAMPLE_RATE_22050, + RPC_AUD_DEF_SAMPLE_RATE_24000, + RPC_AUD_DEF_SAMPLE_RATE_32000, + RPC_AUD_DEF_SAMPLE_RATE_44100, + RPC_AUD_DEF_SAMPLE_RATE_48000, + RPC_AUD_DEF_SAMPLE_RATE_MAX, +}; + +enum rpc_aud_def_method_type { + RPC_AUD_DEF_METHOD_NONE, + RPC_AUD_DEF_METHOD_KEY_BEEP, + RPC_AUD_DEF_METHOD_PLAYBACK, + RPC_AUD_DEF_METHOD_VOICE, + RPC_AUD_DEF_METHOD_RECORD, + RPC_AUD_DEF_METHOD_HOST_PCM, + RPC_AUD_DEF_METHOD_MIDI_OUT, + RPC_AUD_DEF_METHOD_RECORD_SBC, + RPC_AUD_DEF_METHOD_DTMF_RINGER, + RPC_AUD_DEF_METHOD_MAX, +}; + +enum rpc_aud_def_codec_type { + RPC_AUD_DEF_CODEC_NONE, + RPC_AUD_DEF_CODEC_DTMF, + RPC_AUD_DEF_CODEC_MIDI, + RPC_AUD_DEF_CODEC_MP3, + RPC_AUD_DEF_CODEC_PCM, + RPC_AUD_DEF_CODEC_AAC, + RPC_AUD_DEF_CODEC_WMA, + RPC_AUD_DEF_CODEC_RA, + RPC_AUD_DEF_CODEC_ADPCM, + RPC_AUD_DEF_CODEC_GAUDIO, + RPC_AUD_DEF_CODEC_VOC_EVRC, + RPC_AUD_DEF_CODEC_VOC_13K, + RPC_AUD_DEF_CODEC_VOC_4GV_NB, + RPC_AUD_DEF_CODEC_VOC_AMR, + RPC_AUD_DEF_CODEC_VOC_EFR, + RPC_AUD_DEF_CODEC_VOC_FR, + RPC_AUD_DEF_CODEC_VOC_HR, + RPC_AUD_DEF_CODEC_VOC_CDMA, + RPC_AUD_DEF_CODEC_VOC_CDMA_WB, + RPC_AUD_DEF_CODEC_VOC_UMTS, + RPC_AUD_DEF_CODEC_VOC_UMTS_WB, + RPC_AUD_DEF_CODEC_SBC, + RPC_AUD_DEF_CODEC_VOC_PCM, + RPC_AUD_DEF_CODEC_AMR_WB, + RPC_AUD_DEF_CODEC_AMR_WB_PLUS, + RPC_AUD_DEF_CODEC_AAC_BSAC, + RPC_AUD_DEF_CODEC_MAX, + RPC_AUD_DEF_CODEC_AMR_NB, + RPC_AUD_DEF_CODEC_13K, + RPC_AUD_DEF_CODEC_EVRC, + RPC_AUD_DEF_CODEC_MAX_002, +}; + +enum rpc_snd_method_type { + RPC_SND_METHOD_VOICE = 0, + RPC_SND_METHOD_KEY_BEEP, + RPC_SND_METHOD_MESSAGE, + RPC_SND_METHOD_RING, + RPC_SND_METHOD_MIDI, + RPC_SND_METHOD_AUX, + RPC_SND_METHOD_MAX, +}; + +enum rpc_voc_codec_type { + RPC_VOC_CODEC_DEFAULT, + RPC_VOC_CODEC_ON_CHIP_0 = RPC_VOC_CODEC_DEFAULT, + RPC_VOC_CODEC_ON_CHIP_1, + RPC_VOC_CODEC_STEREO_HEADSET, + RPC_VOC_CODEC_ON_CHIP_AUX, + RPC_VOC_CODEC_BT_OFF_BOARD, + RPC_VOC_CODEC_BT_A2DP, + RPC_VOC_CODEC_OFF_BOARD, + RPC_VOC_CODEC_SDAC, + RPC_VOC_CODEC_RX_EXT_SDAC_TX_INTERNAL, + RPC_VOC_CODEC_IN_STEREO_SADC_OUT_MONO_HANDSET, + RPC_VOC_CODEC_IN_STEREO_SADC_OUT_STEREO_HEADSET, + RPC_VOC_CODEC_TX_INT_SADC_RX_EXT_AUXPCM, + RPC_VOC_CODEC_EXT_STEREO_SADC_OUT_MONO_HANDSET, + RPC_VOC_CODEC_EXT_STEREO_SADC_OUT_STEREO_HEADSET, + RPC_VOC_CODEC_TTY_ON_CHIP_1, + RPC_VOC_CODEC_TTY_OFF_BOARD, + RPC_VOC_CODEC_TTY_VCO, + RPC_VOC_CODEC_TTY_HCO, + RPC_VOC_CODEC_ON_CHIP_0_DUAL_MIC, + RPC_VOC_CODEC_MAX, + RPC_VOC_CODEC_NONE, +}; + +enum rpc_audmgr_status_type { + RPC_AUDMGR_STATUS_READY, + RPC_AUDMGR_STATUS_CODEC_CONFIG, + RPC_AUDMGR_STATUS_PENDING, + RPC_AUDMGR_STATUS_SUSPEND, + RPC_AUDMGR_STATUS_FAILURE, + RPC_AUDMGR_STATUS_VOLUME_CHANGE, + RPC_AUDMGR_STATUS_DISABLED, + RPC_AUDMGR_STATUS_ERROR, +}; + +struct rpc_audmgr_enable_client_args { + uint32_t set_to_one; + uint32_t tx_sample_rate; + uint32_t rx_sample_rate; + uint32_t def_method; + uint32_t codec_type; + uint32_t snd_method; + + uint32_t cb_func; + uint32_t client_data; +}; + +#define AUDMGR_ENABLE_CLIENT 2 +#define AUDMGR_DISABLE_CLIENT 3 +#define AUDMGR_SUSPEND_EVENT_RSP 4 +#define AUDMGR_REGISTER_OPERATION_LISTENER 5 +#define AUDMGR_UNREGISTER_OPERATION_LISTENER 6 +#define AUDMGR_REGISTER_CODEC_LISTENER 7 +#define AUDMGR_GET_RX_SAMPLE_RATE 8 +#define AUDMGR_GET_TX_SAMPLE_RATE 9 +#define AUDMGR_SET_DEVICE_MODE 10 + +#define AUDMGR_PROG 0x30000013 +#define AUDMGR_VERS MSM_RPC_VERS(1,0) + +struct rpc_audmgr_cb_func_ptr { + uint32_t cb_id; + uint32_t status; /* Audmgr status */ + uint32_t set_to_one; /* Pointer status (1 = valid, 0 = invalid) */ + uint32_t disc; + /* disc = AUDMGR_STATUS_READY => data=handle + disc = AUDMGR_STATUS_CODEC_CONFIG => data = handle + disc = AUDMGR_STATUS_DISABLED => data =status_disabled + disc = AUDMGR_STATUS_VOLUME_CHANGE => data = volume-change */ + union { + uint32_t handle; + uint32_t volume; + uint32_t status_disabled; + uint32_t volume_change; + } u; +}; + +#define AUDMGR_CB_FUNC_PTR 1 +#define AUDMGR_OPR_LSTNR_CB_FUNC_PTR 2 +#define AUDMGR_CODEC_LSTR_FUNC_PTR 3 + +#define AUDMGR_CB_PROG 0x31000013 +#define AUDMGR_CB_VERS 0xf8e3e2d9 + +struct audmgr { + wait_queue_head_t wait; + uint32_t handle; + struct msm_rpc_endpoint *ept; + struct task_struct *task; + int state; +}; + +struct audmgr_config { + uint32_t tx_rate; + uint32_t rx_rate; + uint32_t def_method; + uint32_t codec; + uint32_t snd_method; +}; + +int audmgr_open(struct audmgr *am); +int audmgr_close(struct audmgr *am); +int audmgr_enable(struct audmgr *am, struct audmgr_config *cfg); +int audmgr_disable(struct audmgr *am); + +typedef void (*audpp_event_func)(void *private, unsigned id, uint16_t *msg); + +int audpp_enable(int id, audpp_event_func func, void *private); +void audpp_disable(int id, void *private); + +int audpp_send_queue1(void *cmd, unsigned len); +int audpp_send_queue2(void *cmd, unsigned len); +int audpp_send_queue3(void *cmd, unsigned len); + +int audpp_set_volume_and_pan(unsigned id, unsigned volume, int pan); +int audpp_pause(unsigned id, int pause); +int audpp_flush(unsigned id); +void audpp_avsync(int id, unsigned rate); +unsigned audpp_avsync_sample_count(int id); +unsigned audpp_avsync_byte_count(int id); + +#endif diff --git a/arch/arm/mach-msm/qdsp5/audpp.c b/arch/arm/mach-msm/qdsp5/audpp.c new file mode 100644 index 00000000000..3e834d86f19 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audpp.c @@ -0,0 +1,873 @@ + +/* arch/arm/mach-msm/qdsp5/audpp.c + * + * common code to deal with the AUDPP dsp task (audio postproc) + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "audmgr.h" + +#include +#include +#include + +#include "evlog.h" + +enum { + EV_NULL, + EV_ENABLE, + EV_DISABLE, + EV_EVENT, + EV_DATA, +}; + +static const char *dsp_log_strings[] = { + "NULL", + "ENABLE", + "DISABLE", + "EVENT", + "DATA", +}; + +DECLARE_LOG(dsp_log, 64, dsp_log_strings); + +static int __init _dsp_log_init(void) +{ + return ev_log_init(&dsp_log); +} + +module_init(_dsp_log_init); +#define LOG(id,arg) ev_log_write(&dsp_log, id, arg) + +static DEFINE_MUTEX(audpp_lock); +static DEFINE_MUTEX(audpp_dec_lock); + +#define CH_COUNT 5 +#define AUDPP_CLNT_MAX_COUNT 6 +#define AUDPP_AVSYNC_INFO_SIZE 7 + +#define AUDPP_CMD_CFG_OBJ_UPDATE 0x8000 +#define AUDPP_CMD_EQ_FLAG_DIS 0x0000 +#define AUDPP_CMD_EQ_FLAG_ENA -1 +#define AUDPP_CMD_IIR_FLAG_DIS 0x0000 +#define AUDPP_CMD_IIR_FLAG_ENA -1 + +#define AUDPP_CMD_VOLUME_PAN 0 +#define AUDPP_CMD_IIR_TUNING_FILTER 1 +#define AUDPP_CMD_EQUALIZER 2 +#define AUDPP_CMD_ADRC 3 +#define AUDPP_CMD_SPECTROGRAM 4 +#define AUDPP_CMD_QCONCERT 5 +#define AUDPP_CMD_SIDECHAIN_TUNING_FILTER 6 +#define AUDPP_CMD_SAMPLING_FREQUENCY 7 +#define AUDPP_CMD_QAFX 8 +#define AUDPP_CMD_QRUMBLE 9 +#define AUDPP_CMD_MBADRC 10 + +#define MAX_EVENT_CALLBACK_CLIENTS 1 + +#define AUDPP_CONCURRENCY_DEFAULT 6 /* All non tunnel mode */ +#define AUDPP_MAX_DECODER_CNT 5 +#define AUDPP_CODEC_MASK 0x000000FF +#define AUDPP_MODE_MASK 0x00000F00 +#define AUDPP_OP_MASK 0xF0000000 + +struct audpp_decoder_info { + unsigned int codec; + pid_t pid; +}; + +struct audpp_state { + struct msm_adsp_module *mod; + audpp_event_func func[AUDPP_CLNT_MAX_COUNT]; + void *private[AUDPP_CLNT_MAX_COUNT]; + struct mutex *lock; + unsigned open_count; + unsigned enabled; + + /* Related to decoder allocation */ + struct mutex *lock_dec; + struct msm_adspdec_database *dec_database; + struct audpp_decoder_info dec_info_table[AUDPP_MAX_DECODER_CNT]; + unsigned dec_inuse; + unsigned long concurrency; + + /* which channels are actually enabled */ + unsigned avsync_mask; + + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[CH_COUNT * AUDPP_CLNT_MAX_COUNT + 1]; + struct audpp_event_callback *cb_tbl[MAX_EVENT_CALLBACK_CLIENTS]; + + wait_queue_head_t event_wait; +}; + +struct audpp_state the_audpp_state = { + .lock = &audpp_lock, + .lock_dec = &audpp_dec_lock, +}; + +int audpp_send_queue1(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpp_state.mod, + QDSP_uPAudPPCmd1Queue, cmd, len); +} +EXPORT_SYMBOL(audpp_send_queue1); + +int audpp_send_queue2(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpp_state.mod, + QDSP_uPAudPPCmd2Queue, cmd, len); +} +EXPORT_SYMBOL(audpp_send_queue2); + +int audpp_send_queue3(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpp_state.mod, + QDSP_uPAudPPCmd3Queue, cmd, len); +} +EXPORT_SYMBOL(audpp_send_queue3); + +static int audpp_dsp_config(int enable) +{ + audpp_cmd_cfg cmd; + + cmd.cmd_id = AUDPP_CMD_CFG; + cmd.cfg = enable ? AUDPP_CMD_CFG_ENABLE : AUDPP_CMD_CFG_SLEEP; + + return audpp_send_queue1(&cmd, sizeof(cmd)); +} + +int is_audpp_enable(void) +{ + struct audpp_state *audpp = &the_audpp_state; + + return audpp->enabled; +} +EXPORT_SYMBOL(is_audpp_enable); + +int audpp_register_event_callback(struct audpp_event_callback *ecb) +{ + struct audpp_state *audpp = &the_audpp_state; + int i; + + for (i = 0; i < MAX_EVENT_CALLBACK_CLIENTS; ++i) { + if (NULL == audpp->cb_tbl[i]) { + audpp->cb_tbl[i] = ecb; + return 0; + } + } + return -1; +} +EXPORT_SYMBOL(audpp_register_event_callback); + +int audpp_unregister_event_callback(struct audpp_event_callback *ecb) +{ + struct audpp_state *audpp = &the_audpp_state; + int i; + + for (i = 0; i < MAX_EVENT_CALLBACK_CLIENTS; ++i) { + if (ecb == audpp->cb_tbl[i]) { + audpp->cb_tbl[i] = NULL; + return 0; + } + } + return -1; +} +EXPORT_SYMBOL(audpp_unregister_event_callback); + +static void audpp_broadcast(struct audpp_state *audpp, unsigned id, + uint16_t *msg) +{ + unsigned n; + for (n = 0; n < AUDPP_CLNT_MAX_COUNT; n++) { + if (audpp->func[n]) + audpp->func[n] (audpp->private[n], id, msg); + } + + for (n = 0; n < MAX_EVENT_CALLBACK_CLIENTS; ++n) + if (audpp->cb_tbl[n] && audpp->cb_tbl[n]->fn) + audpp->cb_tbl[n]->fn(audpp->cb_tbl[n]->private, id, + msg); +} + +static void audpp_notify_clnt(struct audpp_state *audpp, unsigned clnt_id, + unsigned id, uint16_t *msg) +{ + if (clnt_id < AUDPP_CLNT_MAX_COUNT && audpp->func[clnt_id]) + audpp->func[clnt_id] (audpp->private[clnt_id], id, msg); +} + +static void audpp_handle_pcmdmamiss(struct audpp_state *audpp, + uint16_t bit_mask) +{ + uint8_t b_index; + + for (b_index = 0; b_index < AUDPP_CLNT_MAX_COUNT; b_index++) { + if (bit_mask & (0x1 << b_index)) + if (audpp->func[b_index]) + audpp->func[b_index] (audpp->private[b_index], + AUDPP_MSG_PCMDMAMISSED, + &bit_mask); + } +} + +static void audpp_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audpp_state *audpp = data; + uint16_t msg[8]; + + if (id == AUDPP_MSG_AVSYNC_MSG) { + getevent(audpp->avsync, sizeof(audpp->avsync)); + + /* mask off any channels we're not watching to avoid + * cases where we might get one last update after + * disabling avsync and end up in an odd state when + * we next read... + */ + audpp->avsync[0] &= audpp->avsync_mask; + return; + } + + getevent(msg, sizeof(msg)); + + LOG(EV_EVENT, (id << 16) | msg[0]); + LOG(EV_DATA, (msg[1] << 16) | msg[2]); + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned cid = msg[0]; + MM_DBG("status %d %d %d\n", cid, msg[1], msg[2]); + if ((cid < 5) && audpp->func[cid]) + audpp->func[cid] (audpp->private[cid], id, msg); + break; + } + case AUDPP_MSG_HOST_PCM_INTF_MSG: + if (audpp->func[5]) + audpp->func[5] (audpp->private[5], id, msg); + break; + case AUDPP_MSG_PCMDMAMISSED: + audpp_handle_pcmdmamiss(audpp, msg[0]); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_INFO("ENABLE\n"); + audpp->enabled = 1; + audpp_broadcast(audpp, id, msg); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_INFO("DISABLE\n"); + audpp->enabled = 0; + wake_up(&audpp->event_wait); + audpp_broadcast(audpp, id, msg); + } else { + MM_ERR("invalid config msg %d\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + audpp_notify_clnt(audpp, msg[0], id, msg); + break; + case AUDPP_MSG_FLUSH_ACK: + audpp_notify_clnt(audpp, msg[0], id, msg); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable/disable(audpptask)"); + break; + default: + MM_ERR("unhandled msg id %x\n", id); + } +} + +static struct msm_adsp_ops adsp_ops = { + .event = audpp_dsp_event, +}; + +static void audpp_fake_event(struct audpp_state *audpp, int id, + unsigned event, unsigned arg) +{ + uint16_t msg[1]; + msg[0] = arg; + audpp->func[id] (audpp->private[id], event, msg); +} + +int audpp_enable(int id, audpp_event_func func, void *private) +{ + struct audpp_state *audpp = &the_audpp_state; + int res = 0; + + if (id < -1 || id > 4) + return -EINVAL; + + if (id == -1) + id = 5; + + mutex_lock(audpp->lock); + if (audpp->func[id]) { + res = -EBUSY; + goto out; + } + + audpp->func[id] = func; + audpp->private[id] = private; + + LOG(EV_ENABLE, 1); + if (audpp->open_count++ == 0) { + MM_DBG("enable\n"); + res = msm_adsp_get("AUDPPTASK", &audpp->mod, &adsp_ops, audpp); + if (res < 0) { + MM_ERR("cannot open AUDPPTASK\n"); + audpp->open_count = 0; + audpp->func[id] = NULL; + audpp->private[id] = NULL; + goto out; + } + LOG(EV_ENABLE, 2); + msm_adsp_enable(audpp->mod); + audpp_dsp_config(1); + } else { + unsigned long flags; + local_irq_save(flags); + if (audpp->enabled) + audpp_fake_event(audpp, id, + AUDPP_MSG_CFG_MSG, AUDPP_MSG_ENA_ENA); + local_irq_restore(flags); + } + + res = 0; +out: + mutex_unlock(audpp->lock); + return res; +} +EXPORT_SYMBOL(audpp_enable); + +void audpp_disable(int id, void *private) +{ + struct audpp_state *audpp = &the_audpp_state; + unsigned long flags; + int rc; + + if (id < -1 || id > 4) + return; + + if (id == -1) + id = 5; + + mutex_lock(audpp->lock); + LOG(EV_DISABLE, 1); + if (!audpp->func[id]) + goto out; + if (audpp->private[id] != private) + goto out; + + local_irq_save(flags); + audpp_fake_event(audpp, id, AUDPP_MSG_CFG_MSG, AUDPP_MSG_ENA_DIS); + audpp->func[id] = NULL; + audpp->private[id] = NULL; + local_irq_restore(flags); + + if (--audpp->open_count == 0) { + MM_DBG("disable\n"); + LOG(EV_DISABLE, 2); + audpp_dsp_config(0); + rc = wait_event_interruptible(audpp->event_wait, + (audpp->enabled == 0)); + if (audpp->enabled == 0) + MM_INFO("Received CFG_MSG_DISABLE from ADSP\n"); + else + MM_ERR("Didn't receive CFG_MSG DISABLE \ + message from ADSP\n"); + msm_adsp_disable(audpp->mod); + msm_adsp_put(audpp->mod); + audpp->mod = NULL; + } +out: + mutex_unlock(audpp->lock); +} +EXPORT_SYMBOL(audpp_disable); + +#define BAD_ID(id) ((id < 0) || (id >= CH_COUNT)) + +void audpp_avsync(int id, unsigned rate) +{ + unsigned long flags; + audpp_cmd_avsync cmd; + + if (BAD_ID(id)) + return; + + local_irq_save(flags); + if (rate) + the_audpp_state.avsync_mask |= (1 << id); + else + the_audpp_state.avsync_mask &= (~(1 << id)); + the_audpp_state.avsync[0] &= the_audpp_state.avsync_mask; + local_irq_restore(flags); + + cmd.cmd_id = AUDPP_CMD_AVSYNC; + cmd.object_number = id; + cmd.interrupt_interval_lsw = rate; + cmd.interrupt_interval_msw = rate >> 16; + audpp_send_queue1(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_avsync); + +unsigned audpp_avsync_sample_count(int id) +{ + uint16_t *avsync = the_audpp_state.avsync; + unsigned val; + unsigned long flags; + unsigned mask; + + if (BAD_ID(id)) + return 0; + + mask = 1 << id; + id = id * AUDPP_AVSYNC_INFO_SIZE + 2; + local_irq_save(flags); + if (avsync[0] & mask) + val = (avsync[id] << 16) | avsync[id + 1]; + else + val = 0; + local_irq_restore(flags); + + return val; +} +EXPORT_SYMBOL(audpp_avsync_sample_count); + +unsigned audpp_avsync_byte_count(int id) +{ + uint16_t *avsync = the_audpp_state.avsync; + unsigned val; + unsigned long flags; + unsigned mask; + + if (BAD_ID(id)) + return 0; + + mask = 1 << id; + id = id * AUDPP_AVSYNC_INFO_SIZE + 5; + local_irq_save(flags); + if (avsync[0] & mask) + val = (avsync[id] << 16) | avsync[id + 1]; + else + val = 0; + local_irq_restore(flags); + + return val; +} +EXPORT_SYMBOL(audpp_avsync_byte_count); + +int audpp_set_volume_and_pan(unsigned id, unsigned volume, int pan) +{ + /* cmd, obj_cfg[7], cmd_type, volume, pan */ + uint16_t cmd[11]; + + if (id > 6) + return -EINVAL; + + memset(cmd, 0, sizeof(cmd)); + cmd[0] = AUDPP_CMD_CFG_OBJECT_PARAMS; + cmd[1 + id] = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd[8] = AUDPP_CMD_VOLUME_PAN; + cmd[9] = volume; + cmd[10] = pan; + + return audpp_send_queue3(cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_set_volume_and_pan); + +/* Implementation of COPP features */ +int audpp_dsp_set_mbadrc(unsigned id, unsigned enable, + audpp_cmd_cfg_object_params_mbadrc *mbadrc) +{ + audpp_cmd_cfg_object_params_mbadrc cmd; + + if (id != 6) + return -EINVAL; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_CMD_MBADRC; + + if (enable) { + memcpy(&cmd.num_bands, &mbadrc->num_bands, + sizeof(*mbadrc) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2)); + cmd.enable = AUDPP_CMD_ADRC_FLAG_ENA; + } else + cmd.enable = AUDPP_CMD_ADRC_FLAG_DIS; + + /*order the writes to mbadrc */ + dma_coherent_pre_ops(); + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_mbadrc); + +int audpp_dsp_set_qconcert_plus(unsigned id, unsigned enable, + audpp_cmd_cfg_object_params_qconcert * + qconcert_plus) +{ + audpp_cmd_cfg_object_params_qconcert cmd; + if (id != 6) + return -EINVAL; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_CMD_QCONCERT; + + if (enable) { + memcpy(&cmd.op_mode, &qconcert_plus->op_mode, + sizeof(audpp_cmd_cfg_object_params_qconcert) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2)); + cmd.enable_flag = AUDPP_CMD_ADRC_FLAG_ENA; + } else + cmd.enable_flag = AUDPP_CMD_ADRC_FLAG_DIS; + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} + +int audpp_dsp_set_rx_iir(unsigned id, unsigned enable, + audpp_cmd_cfg_object_params_pcm *iir) +{ + audpp_cmd_cfg_object_params_pcm cmd; + + if (id != 6) + return -EINVAL; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.comman_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_CMD_IIR_TUNING_FILTER; + + if (enable) { + cmd.active_flag = AUDPP_CMD_IIR_FLAG_ENA; + cmd.num_bands = iir->num_bands; + memcpy(&cmd.params_filter, &iir->params_filter, + sizeof(iir->params_filter)); + } else + cmd.active_flag = AUDPP_CMD_IIR_FLAG_DIS; + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_rx_iir); + +/* Implementation Of COPP + POPP */ +int audpp_dsp_set_eq(unsigned id, unsigned enable, + audpp_cmd_cfg_object_params_eqalizer *eq) +{ + audpp_cmd_cfg_object_params_eqalizer cmd; + unsigned short *id_ptr = (unsigned short *)&cmd; + + if (id > 6 || id == 5) + return -EINVAL; + + memset(&cmd, 0, sizeof(cmd)); + id_ptr[1 + id] = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_CMD_EQUALIZER; + + if (enable) { + cmd.eq_flag = AUDPP_CMD_EQ_FLAG_ENA; + cmd.num_bands = eq->num_bands; + memcpy(&cmd.eq_coeff, &eq->eq_coeff, sizeof(eq->eq_coeff)); + } else + cmd.eq_flag = AUDPP_CMD_EQ_FLAG_DIS; + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_eq); + +int audpp_dsp_set_vol_pan(unsigned id, + audpp_cmd_cfg_object_params_volume *vol_pan) +{ + audpp_cmd_cfg_object_params_volume cmd; + unsigned short *id_ptr = (unsigned short *)&cmd; + + if (id > 6) + return -EINVAL; + + memset(&cmd, 0, sizeof(cmd)); + id_ptr[1 + id] = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_CMD_VOLUME_PAN; + + cmd.volume = vol_pan->volume; + cmd.pan = vol_pan->pan; + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_vol_pan); + +int audpp_pause(unsigned id, int pause) +{ + /* pause 1 = pause 0 = resume */ + u16 pause_cmd[AUDPP_CMD_DEC_CTRL_LEN / sizeof(unsigned short)]; + + if (id >= CH_COUNT) + return -EINVAL; + + memset(pause_cmd, 0, sizeof(pause_cmd)); + + pause_cmd[0] = AUDPP_CMD_DEC_CTRL; + if (pause == 1) + pause_cmd[1 + id] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_PAUSE_V; + else if (pause == 0) + pause_cmd[1 + id] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_RESUME_V; + else + return -EINVAL; + + return audpp_send_queue1(pause_cmd, sizeof(pause_cmd)); +} +EXPORT_SYMBOL(audpp_pause); + +int audpp_flush(unsigned id) +{ + u16 flush_cmd[AUDPP_CMD_DEC_CTRL_LEN / sizeof(unsigned short)]; + + if (id >= CH_COUNT) + return -EINVAL; + + memset(flush_cmd, 0, sizeof(flush_cmd)); + + flush_cmd[0] = AUDPP_CMD_DEC_CTRL; + flush_cmd[1 + id] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_FLUSH_V; + + return audpp_send_queue1(flush_cmd, sizeof(flush_cmd)); +} +EXPORT_SYMBOL(audpp_flush); + +/* dec_attrb = 7:0, 0 - No Decoder, else supported decoder * + * like mp3, aac, wma etc ... * + * = 15:8, bit[8] = 1 - Tunnel, bit[9] = 1 - NonTunnel * + * = 31:16, reserved */ +int audpp_adec_alloc(unsigned dec_attrb, const char **module_name, + unsigned *queueid) +{ + struct audpp_state *audpp = &the_audpp_state; + int decid = -1, idx, lidx, mode, codec; + int codecs_supported, min_codecs_supported; + unsigned int *concurrency_entry; + mutex_lock(audpp->lock_dec); + /* Represents in bit mask */ + mode = ((dec_attrb & AUDPP_MODE_MASK) << 16); + codec = (1 << (dec_attrb & AUDPP_CODEC_MASK)); + /* Point to Last entry of the row */ + concurrency_entry = ((audpp->dec_database->dec_concurrency_table + + ((audpp->concurrency + 1) * + (audpp->dec_database->num_dec))) - 1); + + lidx = audpp->dec_database->num_dec; + min_codecs_supported = sizeof(unsigned int) * 8; + + MM_DBG("mode = 0x%08x codec = 0x%08x\n", mode, codec); + + for (idx = lidx; idx > 0; idx--, concurrency_entry--) { + if (!(audpp->dec_inuse & (1 << (idx - 1)))) { + if ((mode & *concurrency_entry) && + (codec & *concurrency_entry)) { + /* Check supports minimum number codecs */ + codecs_supported = + audpp->dec_database->dec_info_list[idx - + 1]. + nr_codec_support; + if (codecs_supported < min_codecs_supported) { + lidx = idx - 1; + min_codecs_supported = codecs_supported; + } + } + } + } + + if (lidx < audpp->dec_database->num_dec) { + audpp->dec_inuse |= (1 << lidx); + *module_name = + audpp->dec_database->dec_info_list[lidx].module_name; + *queueid = + audpp->dec_database->dec_info_list[lidx].module_queueid; + decid = audpp->dec_database->dec_info_list[lidx].module_decid; + audpp->dec_info_table[lidx].codec = + (dec_attrb & AUDPP_CODEC_MASK); + audpp->dec_info_table[lidx].pid = current->pid; + /* point to row to get supported operation */ + concurrency_entry = + ((audpp->dec_database->dec_concurrency_table + + ((audpp->concurrency) * (audpp->dec_database->num_dec))) + + lidx); + decid |= ((*concurrency_entry & AUDPP_OP_MASK) >> 12); + MM_INFO("decid =0x%08x module_name=%s, queueid=%d \n", + decid, *module_name, *queueid); + } + mutex_unlock(audpp->lock_dec); + return decid; + +} +EXPORT_SYMBOL(audpp_adec_alloc); + +void audpp_adec_free(int decid) +{ + struct audpp_state *audpp = &the_audpp_state; + int idx; + mutex_lock(audpp->lock_dec); + for (idx = audpp->dec_database->num_dec; idx > 0; idx--) { + if (audpp->dec_database->dec_info_list[idx - 1].module_decid == + decid) { + audpp->dec_inuse &= ~(1 << (idx - 1)); + audpp->dec_info_table[idx - 1].codec = -1; + audpp->dec_info_table[idx - 1].pid = 0; + MM_INFO("free decid =%d \n", decid); + break; + } + } + mutex_unlock(audpp->lock_dec); + return; + +} +EXPORT_SYMBOL(audpp_adec_free); + +static ssize_t concurrency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct audpp_state *audpp = &the_audpp_state; + int rc; + mutex_lock(audpp->lock_dec); + rc = sprintf(buf, "%ld\n", audpp->concurrency); + mutex_unlock(audpp->lock_dec); + return rc; +} + +static ssize_t concurrency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct audpp_state *audpp = &the_audpp_state; + unsigned long concurrency; + int rc = -1; + mutex_lock(audpp->lock_dec); + if (audpp->dec_inuse) { + MM_ERR("Can not change profile, while playback in progress\n"); + goto done; + } + rc = strict_strtoul(buf, 10, &concurrency); + if (!rc && + (concurrency < audpp->dec_database->num_concurrency_support)) { + audpp->concurrency = concurrency; + MM_DBG("Concurrency case %ld\n", audpp->concurrency); + rc = count; + } else { + MM_ERR("Not a valid Concurrency case\n"); + rc = -EINVAL; + } +done: + mutex_unlock(audpp->lock_dec); + return rc; +} + +static ssize_t decoder_info_show(struct device *dev, + struct device_attribute *attr, char *buf); +static struct device_attribute dev_attr_decoder[AUDPP_MAX_DECODER_CNT] = { + __ATTR(decoder0, S_IRUGO, decoder_info_show, NULL), + __ATTR(decoder1, S_IRUGO, decoder_info_show, NULL), + __ATTR(decoder2, S_IRUGO, decoder_info_show, NULL), + __ATTR(decoder3, S_IRUGO, decoder_info_show, NULL), + __ATTR(decoder4, S_IRUGO, decoder_info_show, NULL), +}; + +static ssize_t decoder_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int cpy_sz = 0; + struct audpp_state *audpp = &the_audpp_state; + const ptrdiff_t off = attr - dev_attr_decoder; /* decoder number */ + mutex_lock(audpp->lock_dec); + cpy_sz += scnprintf(buf + cpy_sz, PAGE_SIZE - cpy_sz, "%d:", + audpp->dec_info_table[off].codec); + cpy_sz += scnprintf(buf + cpy_sz, PAGE_SIZE - cpy_sz, "%d\n", + audpp->dec_info_table[off].pid); + mutex_unlock(audpp->lock_dec); + return cpy_sz; +} + +static DEVICE_ATTR(concurrency, S_IWUSR | S_IRUGO, concurrency_show, + concurrency_store); +static int audpp_probe(struct platform_device *pdev) +{ + int rc, idx; + struct audpp_state *audpp = &the_audpp_state; + audpp->concurrency = AUDPP_CONCURRENCY_DEFAULT; + audpp->dec_database = + (struct msm_adspdec_database *)pdev->dev.platform_data; + + MM_INFO("Number of decoder supported %d\n", + audpp->dec_database->num_dec); + MM_INFO("Number of concurrency supported %d\n", + audpp->dec_database->num_concurrency_support); + + init_waitqueue_head(&audpp->event_wait); + + for (idx = 0; idx < audpp->dec_database->num_dec; idx++) { + audpp->dec_info_table[idx].codec = -1; + audpp->dec_info_table[idx].pid = 0; + MM_INFO("module_name:%s\n", + audpp->dec_database->dec_info_list[idx].module_name); + MM_INFO("queueid:%d\n", + audpp->dec_database->dec_info_list[idx].module_queueid); + MM_INFO("decid:%d\n", + audpp->dec_database->dec_info_list[idx].module_decid); + MM_INFO("nr_codec_support:%d\n", + audpp->dec_database->dec_info_list[idx]. + nr_codec_support); + } + + for (idx = 0; idx < audpp->dec_database->num_dec; idx++) { + rc = device_create_file(&pdev->dev, &dev_attr_decoder[idx]); + if (rc) + goto err; + } + rc = device_create_file(&pdev->dev, &dev_attr_concurrency); + if (rc) + goto err; + else + goto done; +err: + while (idx--) + device_remove_file(&pdev->dev, &dev_attr_decoder[idx]); +done: + return rc; +} + +static struct platform_driver audpp_plat_driver = { + .probe = audpp_probe, + .driver = { + .name = "msm_adspdec", + .owner = THIS_MODULE, + }, +}; + +static int __init audpp_init(void) +{ + return platform_driver_register(&audpp_plat_driver); +} + +device_initcall(audpp_init); diff --git a/arch/arm/mach-msm/qdsp5/audpreproc.c b/arch/arm/mach-msm/qdsp5/audpreproc.c new file mode 100644 index 00000000000..230429f141d --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audpreproc.c @@ -0,0 +1,169 @@ +/* + * Common code to deal with the AUDPREPROC dsp task (audio preprocessing) + * + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * Based on the audpp layer in arch/arm/mach-msm/qdsp5/audpp.c + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include +#include +#include +#include +#include +#include + +static DEFINE_MUTEX(audpreproc_lock); + +struct msm_adspenc_info { + const char *module_name; + unsigned module_queueids; + int module_encid; /* streamid */ + int enc_formats; /* supported formats */ + int nr_codec_support; /* number of codec suported */ +}; + +#define ENC_MODULE_INFO(name, queueids, encid, formats, nr_codec) \ + {.module_name = name, .module_queueids = queueids, \ + .module_encid = encid, .enc_formats = formats, \ + .nr_codec_support = nr_codec } + +#ifdef CONFIG_MSM7X27A_AUDIO +#define ENC0_FORMAT ((1<lock); + /* Represents in bit mask */ + mode = ((enc_type & AUDPREPROC_MODE_MASK) << 16); + codec = (1 << (enc_type & AUDPREPROC_CODEC_MASK)); + + lidx = msm_enc_database.num_enc; + min_codecs_supported = sizeof(unsigned int) * 8; + MM_DBG("mode = 0x%08x codec = 0x%08x\n", mode, codec); + + for (idx = lidx-1; idx >= 0; idx--) { + /* encoder free and supports the format */ + if (!(audpreproc->enc_inuse & (1 << (idx))) && + ((mode & msm_enc_database.enc_info_list[idx].enc_formats) + == mode) && ((codec & + msm_enc_database.enc_info_list[idx].enc_formats) + == codec)){ + /* Check supports minimum number codecs */ + codecs_supported = + msm_enc_database.enc_info_list[idx].nr_codec_support; + if (codecs_supported < min_codecs_supported) { + lidx = idx; + min_codecs_supported = codecs_supported; + } + } + } + + if (lidx < msm_enc_database.num_enc) { + audpreproc->enc_inuse |= (1 << lidx); + *module_name = + msm_enc_database.enc_info_list[lidx].module_name; + *queue_ids = + msm_enc_database.enc_info_list[lidx].module_queueids; + encid = msm_enc_database.enc_info_list[lidx].module_encid; + } + + mutex_unlock(audpreproc->lock); + return encid; +} +EXPORT_SYMBOL(audpreproc_aenc_alloc); + +void audpreproc_aenc_free(int enc_id) +{ + struct audpreproc_state *audpreproc = &the_audpreproc_state; + int idx; + + mutex_lock(audpreproc->lock); + for (idx = 0; idx < msm_enc_database.num_enc; idx++) { + if (msm_enc_database.enc_info_list[idx].module_encid == + enc_id) { + audpreproc->enc_inuse &= ~(1 << idx); + break; + } + } + mutex_unlock(audpreproc->lock); + return; + +} +EXPORT_SYMBOL(audpreproc_aenc_free); diff --git a/arch/arm/mach-msm/qdsp5/audrec.c b/arch/arm/mach-msm/qdsp5/audrec.c new file mode 100644 index 00000000000..d5cb168b98e --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/audrec.c @@ -0,0 +1,272 @@ +/* arch/arm/mach-msm/qdsp5/audrec.c + * + * common code to deal with the AUDREC dsp task (audio recording) + * + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Based on the audpp layer in arch/arm/mach-msm/qdsp5/audpp.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "audmgr.h" +#include + +static DEFINE_MUTEX(audrec_lock); + +#define MAX_ENC_COUNT 8 /* Max encoder supported */ + +#define ENC_SESSION_FREE 0 +#define ENC_SESSION_ACTIVE 1 + +struct enc_session { + unsigned enc_type; /* Param to identify type of encoder */ + unsigned audrec_obj_idx; /* Param to identify REC_OBJ or Session ID */ + audrec_event_func event_func; /* Event Call back + routine for the encoder */ + void *private; /* private data element passed as + part of Event Call back routine */ + unsigned state; /* Current state of the encoder session , + free, active*/ +}; + +struct audrec_state { + struct msm_adsp_module *audrec_mod; + struct enc_session enc_session[MAX_ENC_COUNT]; + struct mutex *lock; + unsigned enc_count; +}; + +struct audrec_state the_audrec_state = { + .lock = &audrec_lock, +}; + +int audrectask_send_cmdqueue(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audrec_state.audrec_mod, + QDSP_uPAudRecCmdQueue, cmd, len); +} +EXPORT_SYMBOL(audrectask_send_cmdqueue); + +int audrectask_send_bitstreamqueue(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audrec_state.audrec_mod, + QDSP_uPAudRecBitStreamQueue, cmd, len); +} +EXPORT_SYMBOL(audrectask_send_bitstreamqueue); + +static void audrectask_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audrec_state *audrec = data; + int cnt; + uint16_t msg[5]; /* Max size of message */ + getevent(msg, len); + + switch (id) { + case AUDREC_MSG_CMD_CFG_DONE_MSG: { + MM_DBG("CMD CFG DONE %x\n", msg[1]); + if (msg[0] & AUDREC_MSG_CFG_DONE_ENC_ENA) { + for (cnt = 0; cnt < MAX_ENC_COUNT ; cnt++) { + if (audrec->enc_session[cnt].enc_type == + (msg[0] & AUDREC_CMD_ENC_TYPE_MASK)) { + audrec->enc_session[cnt].audrec_obj_idx + = msg[1]; + audrec->enc_session[cnt].event_func( + audrec->enc_session[cnt].private, id, + msg); + break; + } + } + } else { + for (cnt = 0; cnt < MAX_ENC_COUNT ; cnt++) { + if (audrec->enc_session[cnt].enc_type == + (msg[0] & AUDREC_CMD_ENC_TYPE_MASK)) { + audrec->enc_session[cnt].event_func( + audrec->enc_session[cnt].private, id, + msg); + audrec->enc_session[cnt].audrec_obj_idx + = 0xFFFFFFFF; + audrec->enc_session[cnt].state + = ENC_SESSION_FREE; + audrec->enc_session[cnt].enc_type + = 0xFFFFFFFF; + audrec->enc_session[cnt].event_func + = NULL; + audrec->enc_session[cnt].private + = NULL; + break; + } + } + } + break; + } + case AUDREC_MSG_CMD_AREC_MEM_CFG_DONE_MSG: { + MM_DBG("CMD AREC MEM CFG DONE %x\n", msg[0]); + for (cnt = 0; cnt < MAX_ENC_COUNT ; cnt++) { + if (audrec->enc_session[cnt].audrec_obj_idx == + msg[0]) { + audrec->enc_session[cnt].event_func( + audrec->enc_session[cnt].private, id, msg); + break; + } + } + break; + } + case AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG: { + MM_DBG("CMD AREC PARAM CFG DONE %x\n", msg[0]); + for (cnt = 0; cnt < MAX_ENC_COUNT ; cnt++) { + if (audrec->enc_session[cnt].audrec_obj_idx == + msg[0]) { + audrec->enc_session[cnt].event_func( + audrec->enc_session[cnt].private, id, msg); + break; + } + } + break; + } + case AUDREC_MSG_PACKET_READY_MSG: { + MM_DBG("PCK READY %x\n", msg[0]); + for (cnt = 0; cnt < MAX_ENC_COUNT ; cnt++) { + if (audrec->enc_session[cnt].audrec_obj_idx == + msg[0]) { + audrec->enc_session[cnt].event_func( + audrec->enc_session[cnt].private, id, msg); + break; + } + } + break; + } + case AUDREC_MSG_FATAL_ERR_MSG: { + MM_ERR("ERROR %x\n", msg[0]); + if (msg[1] & AUDREC_MSG_FATAL_ERR_TYPE_0) { + for (cnt = 0; cnt < MAX_ENC_COUNT ; cnt++) { + if (audrec->enc_session[cnt].audrec_obj_idx == + msg[0]) { + audrec->enc_session[cnt].event_func( + audrec->enc_session[cnt].private, id, + msg); + break; + } + } + } else if (msg[1] & AUDREC_MSG_FATAL_ERR_TYPE_1) { + cnt = audrec->enc_count-1; + if (audrec->enc_session[cnt].event_func) + audrec->enc_session[cnt].event_func( + audrec->enc_session[cnt].private, id, + msg); + } + break; + } + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module \ + enable/disable(audrectask)\n"); + break; + default: + MM_ERR("unknown event %d\n", id); + } +} + +static struct msm_adsp_ops adsp_ops = { + .event = audrectask_dsp_event, +}; + +int audrectask_enable(unsigned enc_type, audrec_event_func func, void *private) +{ + struct audrec_state *audrec = &the_audrec_state; + int cnt, rc = 0; + + mutex_lock(audrec->lock); + + if (audrec->enc_count++ == 0) { + MM_DBG("enable\n"); + for (cnt = 0; cnt < MAX_ENC_COUNT ; cnt++) { + if (audrec->enc_session[cnt].state == + ENC_SESSION_FREE) { + audrec->enc_session[cnt].state = + ENC_SESSION_ACTIVE; + audrec->enc_session[cnt].enc_type = enc_type; + audrec->enc_session[cnt].event_func = func; + audrec->enc_session[cnt].private = private; + break; + } + } + rc = msm_adsp_get("AUDRECTASK", &audrec->audrec_mod, &adsp_ops, + audrec); + if (rc < 0) { + MM_ERR("cannot open AUDRECTASK\n"); + audrec->enc_count = 0; + audrec->enc_session[cnt].state = ENC_SESSION_FREE; + audrec->enc_session[cnt].enc_type = 0xFFFFFFFF; + audrec->enc_session[cnt].event_func = NULL; + audrec->enc_session[cnt].private = NULL; + goto out; + } + msm_adsp_enable(audrec->audrec_mod); + } else { + for (cnt = 0; cnt < MAX_ENC_COUNT ; cnt++) { + if (audrec->enc_session[cnt].state == + ENC_SESSION_FREE) { + audrec->enc_session[cnt].state = + ENC_SESSION_ACTIVE; + audrec->enc_session[cnt].enc_type = enc_type; + audrec->enc_session[cnt].event_func = func; + audrec->enc_session[cnt].private = private; + break; + } + } + } + if (cnt == MAX_ENC_COUNT) + rc = -EBUSY; + else + rc = 0; + +out: + mutex_unlock(audrec->lock); + return rc; +} +EXPORT_SYMBOL(audrectask_enable); + +void audrectask_disable(unsigned enc_type, void *private) +{ + struct audrec_state *audrec = &the_audrec_state; + + mutex_lock(audrec->lock); + + if (--audrec->enc_count == 0) { + MM_DBG("\n"); /* Macro prints the file name and function */ + msm_adsp_disable(audrec->audrec_mod); + msm_adsp_put(audrec->audrec_mod); + audrec->audrec_mod = NULL; + } + + mutex_unlock(audrec->lock); +} +EXPORT_SYMBOL(audrectask_disable); + diff --git a/arch/arm/mach-msm/qdsp5/evlog.h b/arch/arm/mach-msm/qdsp5/evlog.h new file mode 100644 index 00000000000..1f0f16bada2 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/evlog.h @@ -0,0 +1,125 @@ +/* arch/arm/mach-msm/qdsp5/evlog.h + * + * simple event log debugging facility + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#define EV_LOG_ENTRY_NAME(n) n##_entry + +#define DECLARE_LOG(_name, _size, _str) \ +static struct ev_entry EV_LOG_ENTRY_NAME(_name)[_size]; \ +static struct ev_log _name = { \ + .name = #_name, \ + .strings = _str, \ + .num_strings = ARRAY_SIZE(_str), \ + .entry = EV_LOG_ENTRY_NAME(_name), \ + .max = ARRAY_SIZE(EV_LOG_ENTRY_NAME(_name)), \ +} + +struct ev_entry { + struct timespec when; + uint32_t id; + uint32_t arg; +}; + +struct ev_log { + struct ev_entry *entry; + unsigned max; + unsigned next; + unsigned fault; + const char **strings; + unsigned num_strings; + const char *name; +}; + +static char ev_buf[4096]; + +static ssize_t ev_log_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct ev_log *log = file->private_data; + struct ev_entry *entry; + unsigned long flags; + int size = 0; + unsigned n, id, max; + struct timespec now, t; + + max = log->max; + getnstimeofday(&now); + local_irq_save(flags); + n = (log->next - 1) & (max - 1); + entry = log->entry; + while (n != log->next) { + t = timespec_sub(now, entry[n].when); + id = entry[n].id; + if (id) { + const char *str; + if (id < log->num_strings) + str = log->strings[id]; + else + str = "UNKNOWN"; + size += scnprintf(ev_buf + size, 4096 - size, + "%lu.%03lu %08x %s\n", + t.tv_sec, t.tv_nsec / 1000000, + entry[n].arg, str); + } + n = (n - 1) & (max - 1); + } + log->fault = 0; + local_irq_restore(flags); + return simple_read_from_buffer(buf, count, ppos, ev_buf, size); +} + +static void ev_log_write(struct ev_log *log, unsigned id, unsigned arg) +{ + struct ev_entry *entry; + unsigned long flags; + local_irq_save(flags); + + if (log->fault) { + if (log->fault == 1) + goto done; + log->fault--; + } + + entry = log->entry + log->next; + getnstimeofday(&entry->when); + entry->id = id; + entry->arg = arg; + log->next = (log->next + 1) & (log->max - 1); +done: + local_irq_restore(flags); +} + +static int ev_log_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations ev_log_ops = { + .read = ev_log_read, + .open = ev_log_open, +}; + +static int ev_log_init(struct ev_log *log) +{ + debugfs_create_file(log->name, 0444, 0, log, &ev_log_ops); + return 0; +} + diff --git a/arch/arm/mach-msm/qdsp5/snd.c b/arch/arm/mach-msm/qdsp5/snd.c new file mode 100644 index 00000000000..f1db012b0a0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/snd.c @@ -0,0 +1,675 @@ +/* arch/arm/mach-msm/qdsp5/snd.c + * + * interface to "snd" service on the baseband cpu + * + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct snd_ctxt { + struct mutex lock; + int opened; + struct msm_rpc_endpoint *ept; + struct msm_snd_endpoints *snd_epts; +}; + +struct snd_sys_ctxt { + struct mutex lock; + struct msm_rpc_endpoint *ept; +}; + +static struct snd_sys_ctxt the_snd_sys; + +static struct snd_ctxt the_snd; + +#define RPC_SND_PROG 0x30000002 +#define RPC_SND_CB_PROG 0x31000002 + +#define RPC_SND_VERS 0x00020001 +#define RPC_SND_VERS2 0x00030001 + +#define SND_SET_DEVICE_PROC 2 +#define SND_SET_VOLUME_PROC 3 +#define SND_AVC_CTL_PROC 29 +#define SND_AGC_CTL_PROC 30 + +struct rpc_snd_set_device_args { + uint32_t device; + uint32_t ear_mute; + uint32_t mic_mute; + + uint32_t cb_func; + uint32_t client_data; +}; + +struct rpc_snd_set_volume_args { + uint32_t device; + uint32_t method; + uint32_t volume; + + uint32_t cb_func; + uint32_t client_data; +}; + +struct rpc_snd_avc_ctl_args { + uint32_t avc_ctl; + uint32_t cb_func; + uint32_t client_data; +}; + +struct rpc_snd_agc_ctl_args { + uint32_t agc_ctl; + uint32_t cb_func; + uint32_t client_data; +}; + +struct snd_set_device_msg { + struct rpc_request_hdr hdr; + struct rpc_snd_set_device_args args; +}; + +struct snd_set_volume_msg { + struct rpc_request_hdr hdr; + struct rpc_snd_set_volume_args args; +}; + +struct snd_avc_ctl_msg { + struct rpc_request_hdr hdr; + struct rpc_snd_avc_ctl_args args; +}; + +struct snd_agc_ctl_msg { + struct rpc_request_hdr hdr; + struct rpc_snd_agc_ctl_args args; +}; + +struct snd_endpoint *get_snd_endpoints(int *size); + +static inline int check_mute(int mute) +{ + return (mute == SND_MUTE_MUTED || + mute == SND_MUTE_UNMUTED) ? 0 : -EINVAL; +} + +static int get_endpoint(struct snd_ctxt *snd, unsigned long arg) +{ + int rc = 0, index; + struct msm_snd_endpoint ept; + + if (copy_from_user(&ept, (void __user *)arg, sizeof(ept))) { + MM_ERR("snd_ioctl get endpoint: invalid read pointer\n"); + return -EFAULT; + } + + index = ept.id; + if (index < 0 || index >= snd->snd_epts->num) { + MM_ERR("snd_ioctl get endpoint: invalid index!\n"); + return -EINVAL; + } + + ept.id = snd->snd_epts->endpoints[index].id; + strncpy(ept.name, + snd->snd_epts->endpoints[index].name, + sizeof(ept.name)); + + if (copy_to_user((void __user *)arg, &ept, sizeof(ept))) { + MM_ERR("snd_ioctl get endpoint: invalid write pointer\n"); + rc = -EFAULT; + } + + return rc; +} + +static long snd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_set_device_msg dmsg; + struct snd_set_volume_msg vmsg; + struct snd_avc_ctl_msg avc_msg; + struct snd_agc_ctl_msg agc_msg; + + struct msm_snd_device_config dev; + struct msm_snd_volume_config vol; + struct snd_ctxt *snd = file->private_data; + int rc = 0; + + uint32_t avc, agc; + + mutex_lock(&snd->lock); + switch (cmd) { + case SND_SET_DEVICE: + if (copy_from_user(&dev, (void __user *) arg, sizeof(dev))) { + MM_ERR("set device: invalid pointer\n"); + rc = -EFAULT; + break; + } + + dmsg.args.device = cpu_to_be32(dev.device); + dmsg.args.ear_mute = cpu_to_be32(dev.ear_mute); + dmsg.args.mic_mute = cpu_to_be32(dev.mic_mute); + if (check_mute(dev.ear_mute) < 0 || + check_mute(dev.mic_mute) < 0) { + MM_ERR("set device: invalid mute status\n"); + rc = -EINVAL; + break; + } + dmsg.args.cb_func = -1; + dmsg.args.client_data = 0; + + MM_INFO("snd_set_device %d %d %d\n", dev.device, + dev.ear_mute, dev.mic_mute); + + rc = msm_rpc_call(snd->ept, + SND_SET_DEVICE_PROC, + &dmsg, sizeof(dmsg), 5 * HZ); + break; + + case SND_SET_VOLUME: + if (copy_from_user(&vol, (void __user *) arg, sizeof(vol))) { + MM_ERR("set volume: invalid pointer\n"); + rc = -EFAULT; + break; + } + + vmsg.args.device = cpu_to_be32(vol.device); + vmsg.args.method = cpu_to_be32(vol.method); + if (vol.method != SND_METHOD_VOICE) { + MM_ERR("set volume: invalid method\n"); + rc = -EINVAL; + break; + } + + vmsg.args.volume = cpu_to_be32(vol.volume); + vmsg.args.cb_func = -1; + vmsg.args.client_data = 0; + + MM_INFO("snd_set_volume %d %d %d\n", vol.device, + vol.method, vol.volume); + + rc = msm_rpc_call(snd->ept, + SND_SET_VOLUME_PROC, + &vmsg, sizeof(vmsg), 5 * HZ); + break; + + case SND_AVC_CTL: + if (get_user(avc, (uint32_t __user *) arg)) { + rc = -EFAULT; + break; + } else if ((avc != 1) && (avc != 0)) { + rc = -EINVAL; + break; + } + + avc_msg.args.avc_ctl = cpu_to_be32(avc); + avc_msg.args.cb_func = -1; + avc_msg.args.client_data = 0; + + MM_INFO("snd_avc_ctl %d\n", avc); + + rc = msm_rpc_call(snd->ept, + SND_AVC_CTL_PROC, + &avc_msg, sizeof(avc_msg), 5 * HZ); + break; + + case SND_AGC_CTL: + if (get_user(agc, (uint32_t __user *) arg)) { + rc = -EFAULT; + break; + } else if ((agc != 1) && (agc != 0)) { + rc = -EINVAL; + break; + } + agc_msg.args.agc_ctl = cpu_to_be32(agc); + agc_msg.args.cb_func = -1; + agc_msg.args.client_data = 0; + + MM_INFO("snd_agc_ctl %d\n", agc); + + rc = msm_rpc_call(snd->ept, + SND_AGC_CTL_PROC, + &agc_msg, sizeof(agc_msg), 5 * HZ); + break; + + case SND_GET_NUM_ENDPOINTS: + if (copy_to_user((void __user *)arg, + &snd->snd_epts->num, sizeof(unsigned))) { + MM_ERR("get endpoint: invalid pointer\n"); + rc = -EFAULT; + } + break; + + case SND_GET_ENDPOINT: + rc = get_endpoint(snd, arg); + break; + + default: + MM_ERR("unknown command\n"); + rc = -EINVAL; + break; + } + mutex_unlock(&snd->lock); + + return rc; +} + +static int snd_release(struct inode *inode, struct file *file) +{ + struct snd_ctxt *snd = file->private_data; + int rc; + + mutex_lock(&snd->lock); + rc = msm_rpc_close(snd->ept); + if (rc < 0) + MM_ERR("msm_rpc_close failed\n"); + snd->ept = NULL; + snd->opened = 0; + mutex_unlock(&snd->lock); + return 0; +} +static int snd_sys_release(void) +{ + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + int rc = 0; + + mutex_lock(&snd_sys->lock); + rc = msm_rpc_close(snd_sys->ept); + if (rc < 0) + MM_ERR("msm_rpc_close failed\n"); + snd_sys->ept = NULL; + mutex_unlock(&snd_sys->lock); + return rc; +} +static int snd_open(struct inode *inode, struct file *file) +{ + struct snd_ctxt *snd = &the_snd; + int rc = 0; + + mutex_lock(&snd->lock); + if (snd->opened == 0) { + if (snd->ept == NULL) { + snd->ept = msm_rpc_connect_compatible(RPC_SND_PROG, + RPC_SND_VERS, 0); + if (IS_ERR(snd->ept)) { + MM_DBG("connect failed with current VERS \ + = %x, trying again with another API\n", + RPC_SND_VERS2); + snd->ept = + msm_rpc_connect_compatible(RPC_SND_PROG, + RPC_SND_VERS2, 0); + } + if (IS_ERR(snd->ept)) { + rc = PTR_ERR(snd->ept); + snd->ept = NULL; + MM_ERR("failed to connect snd svc\n"); + goto err; + } + } + file->private_data = snd; + snd->opened = 1; + } else { + MM_ERR("snd already opened\n"); + rc = -EBUSY; + } + +err: + mutex_unlock(&snd->lock); + return rc; +} +static int snd_sys_open(void) +{ + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + int rc = 0; + + mutex_lock(&snd_sys->lock); + if (snd_sys->ept == NULL) { + snd_sys->ept = msm_rpc_connect_compatible(RPC_SND_PROG, + RPC_SND_VERS, 0); + if (IS_ERR(snd_sys->ept)) { + MM_DBG("connect failed with current VERS \ + = %x, trying again with another API\n", + RPC_SND_VERS2); + snd_sys->ept = msm_rpc_connect_compatible(RPC_SND_PROG, + RPC_SND_VERS2, 0); + } + if (IS_ERR(snd_sys->ept)) { + rc = PTR_ERR(snd_sys->ept); + snd_sys->ept = NULL; + MM_ERR("failed to connect snd svc\n"); + goto err; + } + } else + MM_DBG("snd already opened\n"); + +err: + mutex_unlock(&snd_sys->lock); + return rc; +} + +static struct file_operations snd_fops = { + .owner = THIS_MODULE, + .open = snd_open, + .release = snd_release, + .unlocked_ioctl = snd_ioctl, +}; + +struct miscdevice snd_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_snd", + .fops = &snd_fops, +}; + +static long snd_agc_enable(unsigned long arg) +{ + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + struct snd_agc_ctl_msg agc_msg; + int rc = 0; + + if ((arg != 1) && (arg != 0)) + return -EINVAL; + + agc_msg.args.agc_ctl = cpu_to_be32(arg); + agc_msg.args.cb_func = -1; + agc_msg.args.client_data = 0; + + MM_DBG("snd_agc_ctl %ld,%d\n", arg, agc_msg.args.agc_ctl); + + rc = msm_rpc_call(snd_sys->ept, + SND_AGC_CTL_PROC, + &agc_msg, sizeof(agc_msg), 5 * HZ); + return rc; +} + +static long snd_avc_enable(unsigned long arg) +{ + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + struct snd_avc_ctl_msg avc_msg; + int rc = 0; + + if ((arg != 1) && (arg != 0)) + return -EINVAL; + + avc_msg.args.avc_ctl = cpu_to_be32(arg); + + avc_msg.args.cb_func = -1; + avc_msg.args.client_data = 0; + + MM_DBG("snd_avc_ctl %ld,%d\n", arg, avc_msg.args.avc_ctl); + + rc = msm_rpc_call(snd_sys->ept, + SND_AVC_CTL_PROC, + &avc_msg, sizeof(avc_msg), 5 * HZ); + return rc; +} + +static ssize_t snd_agc_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + ssize_t status; + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + int rc = 0; + + rc = snd_sys_open(); + if (rc) + return rc; + + mutex_lock(&snd_sys->lock); + + if (sysfs_streq(buf, "enable")) + status = snd_agc_enable(1); + else if (sysfs_streq(buf, "disable")) + status = snd_agc_enable(0); + else + status = -EINVAL; + + mutex_unlock(&snd_sys->lock); + rc = snd_sys_release(); + if (rc) + return rc; + + return status ? : size; +} + +static ssize_t snd_avc_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + ssize_t status; + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + int rc = 0; + + rc = snd_sys_open(); + if (rc) + return rc; + + mutex_lock(&snd_sys->lock); + + if (sysfs_streq(buf, "enable")) + status = snd_avc_enable(1); + else if (sysfs_streq(buf, "disable")) + status = snd_avc_enable(0); + else + status = -EINVAL; + + mutex_unlock(&snd_sys->lock); + rc = snd_sys_release(); + if (rc) + return rc; + + return status ? : size; +} + +static long snd_vol_enable(const char *arg) +{ + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + struct snd_set_volume_msg vmsg; + struct msm_snd_volume_config vol; + int rc = 0; + + rc = sscanf(arg, "%d %d %d", &vol.device, &vol.method, &vol.volume); + if (rc != 3) { + MM_ERR("Invalid arguments. Usage: \ + \n"); + rc = -EINVAL; + return rc; + } + + vmsg.args.device = cpu_to_be32(vol.device); + vmsg.args.method = cpu_to_be32(vol.method); + if (vol.method != SND_METHOD_VOICE) { + MM_ERR("snd_ioctl set volume: invalid method\n"); + rc = -EINVAL; + return rc; + } + + vmsg.args.volume = cpu_to_be32(vol.volume); + vmsg.args.cb_func = -1; + vmsg.args.client_data = 0; + + MM_DBG("snd_set_volume %d %d %d\n", vol.device, vol.method, + vol.volume); + + rc = msm_rpc_call(snd_sys->ept, + SND_SET_VOLUME_PROC, + &vmsg, sizeof(vmsg), 5 * HZ); + return rc; +} + +static long snd_dev_enable(const char *arg) +{ + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + struct snd_set_device_msg dmsg; + struct msm_snd_device_config dev; + int rc = 0; + + rc = sscanf(arg, "%d %d %d", &dev.device, &dev.ear_mute, &dev.mic_mute); + if (rc != 3) { + MM_ERR("Invalid arguments. Usage: \ + \n"); + rc = -EINVAL; + return rc; + } + dmsg.args.device = cpu_to_be32(dev.device); + dmsg.args.ear_mute = cpu_to_be32(dev.ear_mute); + dmsg.args.mic_mute = cpu_to_be32(dev.mic_mute); + if (check_mute(dev.ear_mute) < 0 || + check_mute(dev.mic_mute) < 0) { + MM_ERR("snd_ioctl set device: invalid mute status\n"); + rc = -EINVAL; + return rc; + } + dmsg.args.cb_func = -1; + dmsg.args.client_data = 0; + + MM_INFO("snd_set_device %d %d %d\n", dev.device, dev.ear_mute, + dev.mic_mute); + + rc = msm_rpc_call(snd_sys->ept, + SND_SET_DEVICE_PROC, + &dmsg, sizeof(dmsg), 5 * HZ); + return rc; +} + +static ssize_t snd_dev_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + ssize_t status; + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + int rc = 0; + + rc = snd_sys_open(); + if (rc) + return rc; + + mutex_lock(&snd_sys->lock); + status = snd_dev_enable(buf); + mutex_unlock(&snd_sys->lock); + + rc = snd_sys_release(); + if (rc) + return rc; + + return status ? : size; +} + +static ssize_t snd_vol_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + ssize_t status; + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + int rc = 0; + + rc = snd_sys_open(); + if (rc) + return rc; + + mutex_lock(&snd_sys->lock); + status = snd_vol_enable(buf); + mutex_unlock(&snd_sys->lock); + + rc = snd_sys_release(); + if (rc) + return rc; + + return status ? : size; +} + +static DEVICE_ATTR(agc, S_IWUSR | S_IRUGO, + NULL, snd_agc_store); + +static DEVICE_ATTR(avc, S_IWUSR | S_IRUGO, + NULL, snd_avc_store); + +static DEVICE_ATTR(device, S_IWUSR | S_IRUGO, + NULL, snd_dev_store); + +static DEVICE_ATTR(volume, S_IWUSR | S_IRUGO, + NULL, snd_vol_store); + +static int snd_probe(struct platform_device *pdev) +{ + struct snd_ctxt *snd = &the_snd; + struct snd_sys_ctxt *snd_sys = &the_snd_sys; + int rc = 0; + + mutex_init(&snd->lock); + mutex_init(&snd_sys->lock); + snd_sys->ept = NULL; + snd->snd_epts = (struct msm_snd_endpoints *)pdev->dev.platform_data; + rc = misc_register(&snd_misc); + if (rc) + return rc; + + rc = device_create_file(snd_misc.this_device, &dev_attr_agc); + if (rc) { + misc_deregister(&snd_misc); + return rc; + } + + rc = device_create_file(snd_misc.this_device, &dev_attr_avc); + if (rc) { + device_remove_file(snd_misc.this_device, + &dev_attr_agc); + misc_deregister(&snd_misc); + return rc; + } + + rc = device_create_file(snd_misc.this_device, &dev_attr_device); + if (rc) { + device_remove_file(snd_misc.this_device, + &dev_attr_agc); + device_remove_file(snd_misc.this_device, + &dev_attr_avc); + misc_deregister(&snd_misc); + return rc; + } + + rc = device_create_file(snd_misc.this_device, &dev_attr_volume); + if (rc) { + device_remove_file(snd_misc.this_device, + &dev_attr_agc); + device_remove_file(snd_misc.this_device, + &dev_attr_avc); + device_remove_file(snd_misc.this_device, + &dev_attr_device); + misc_deregister(&snd_misc); + } + + return rc; +} + +static struct platform_driver snd_plat_driver = { + .probe = snd_probe, + .driver = { + .name = "msm_snd", + .owner = THIS_MODULE, + }, +}; + +static int __init snd_init(void) +{ + return platform_driver_register(&snd_plat_driver); +} + +module_init(snd_init); diff --git a/arch/arm/mach-msm/qdsp5/snd_adie.c b/arch/arm/mach-msm/qdsp5/snd_adie.c new file mode 100644 index 00000000000..ba7efc30f2f --- /dev/null +++ b/arch/arm/mach-msm/qdsp5/snd_adie.c @@ -0,0 +1,480 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct adie_svc_client adie_client[ADIE_SVC_MAX_CLIENTS]; +static DEFINE_MUTEX(adie_client_lock); + +static int adie_svc_process_cb(struct msm_rpc_client *client, + void *buffer, int in_size) +{ + int rc, id; + uint32_t accept_status; + struct rpc_request_hdr *req; + struct adie_svc_client_register_cb_cb_args arg, *buf_ptr; + + req = (struct rpc_request_hdr *)buffer; + for (id = 0; id < ADIE_SVC_MAX_CLIENTS; id++) { + if (adie_client[id].rpc_client == client) + break; + } + if (id == ADIE_SVC_MAX_CLIENTS) { + MM_ERR("RPC reply with invalid rpc client\n"); + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + goto err; + } + + buf_ptr = (struct adie_svc_client_register_cb_cb_args *)(req + 1); + arg.cb_id = be32_to_cpu(buf_ptr->cb_id); + arg.size = be32_to_cpu(buf_ptr->size); + arg.client_id = be32_to_cpu(buf_ptr->client_id); + arg.adie_block = be32_to_cpu(buf_ptr->adie_block); + arg.status = be32_to_cpu(buf_ptr->status); + arg.client_operation = be32_to_cpu(buf_ptr->client_operation); + + if (arg.cb_id != adie_client[id].cb_id) { + MM_ERR("RPC reply with invalid invalid cb_id\n"); + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + goto err; + } + + mutex_lock(&adie_client[id].lock); + switch (arg.client_operation) { + case ADIE_SVC_REGISTER_CLIENT: + MM_DBG("ADIE_SVC_REGISTER_CLIENT callback\n"); + adie_client[id].client_id = arg.client_id; + break; + case ADIE_SVC_DEREGISTER_CLIENT: + MM_DBG("ADIE_SVC_DEREGISTER_CLIENT callback\n"); + break; + case ADIE_SVC_CONFIG_ADIE_BLOCK: + MM_DBG("ADIE_SVC_CONFIG_ADIE_BLOCK callback\n"); + if (adie_client[id].client_id != arg.client_id) { + mutex_unlock(&adie_client[id].lock); + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + goto err; + } + break; + default: + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + goto err; + } + + adie_client[id].status = arg.status; + adie_client[id].adie_svc_cb_done = 1; + mutex_unlock(&adie_client[id].lock); + wake_up(&adie_client[id].wq); + accept_status = RPC_ACCEPTSTAT_SUCCESS; + +err: + msm_rpc_start_accepted_reply(client, be32_to_cpu(req->xid), + accept_status); + rc = msm_rpc_send_accepted_reply(client, 0); + if (rc) + MM_ERR("%s: send accepted reply failed: %d\n", __func__, rc); + + return rc; +} + +static int adie_svc_rpc_cb_func(struct msm_rpc_client *client, + void *buffer, int in_size) +{ + int rc = 0; + struct rpc_request_hdr *req; + + req = (struct rpc_request_hdr *)buffer; + + MM_DBG("procedure received to rpc cb %d\n", + be32_to_cpu(req->procedure)); + switch (be32_to_cpu(req->procedure)) { + case ADIE_SVC_CLIENT_STATUS_FUNC_PTR_TYPE_PROC: + rc = adie_svc_process_cb(client, buffer, in_size); + break; + default: + MM_ERR("%s: procedure not supported %d\n", __func__, + be32_to_cpu(req->procedure)); + msm_rpc_start_accepted_reply(client, be32_to_cpu(req->xid), + RPC_ACCEPTSTAT_PROC_UNAVAIL); + rc = msm_rpc_send_accepted_reply(client, 0); + if (rc) + MM_ERR("%s: sending reply failed: %d\n", __func__, rc); + break; + } + return rc; +} + +static int adie_svc_client_register_arg(struct msm_rpc_client *client, + void *buf, void *data) +{ + struct adie_svc_client_register_cb_args *arg; + + arg = (struct adie_svc_client_register_cb_args *)data; + + *((int *)buf) = cpu_to_be32((int)arg->cb_id); + return sizeof(int); +} + +static int adie_svc_client_deregister_arg(struct msm_rpc_client *client, + void *buf, void *data) +{ + struct adie_svc_client_deregister_cb_args *arg; + + arg = (struct adie_svc_client_deregister_cb_args *)data; + + *((int *)buf) = cpu_to_be32(arg->client_id); + return sizeof(int); +} + +static int adie_svc_config_adie_block_arg(struct msm_rpc_client *client, + void *buf, void *data) +{ + struct adie_svc_config_adie_block_cb_args *arg; + int size = 0; + + arg = (struct adie_svc_config_adie_block_cb_args *)data; + + *((int *)buf) = cpu_to_be32(arg->client_id); + size += sizeof(int); + buf += sizeof(int); + + *((int *)buf) = cpu_to_be32(arg->adie_block); + size += sizeof(int); + buf += sizeof(int); + + *((int *)buf) = cpu_to_be32(arg->config); + size += sizeof(int); + + return size; +} + +/* Returns : client id on success + * and -1 on failure + */ +int adie_svc_get(void) +{ + int id, rc = 0; + struct adie_svc_client_register_cb_args arg; + + mutex_lock(&adie_client_lock); + for (id = 0; id < ADIE_SVC_MAX_CLIENTS; id++) { + if (adie_client[id].client_id == -1 && + adie_client[id].rpc_client == NULL) + break; + } + if (id == ADIE_SVC_MAX_CLIENTS) { + mutex_unlock(&adie_client_lock); + return -1; + } + + mutex_lock(&adie_client[id].lock); + adie_client[id].rpc_client = msm_rpc_register_client("adie_client", + ADIE_SVC_PROG, + ADIE_SVC_VERS, 1, + adie_svc_rpc_cb_func); + if (IS_ERR(adie_client[id].rpc_client)) { + MM_ERR("Failed to register RPC client\n"); + adie_client[id].rpc_client = NULL; + mutex_unlock(&adie_client[id].lock); + mutex_unlock(&adie_client_lock); + return -1; + } + mutex_unlock(&adie_client_lock); + + adie_client[id].adie_svc_cb_done = 0; + arg.cb_id = id; + adie_client[id].cb_id = arg.cb_id; + mutex_unlock(&adie_client[id].lock); + rc = msm_rpc_client_req(adie_client[id].rpc_client, + SND_ADIE_SVC_CLIENT_REGISTER_PROC, + adie_svc_client_register_arg, &arg, + NULL, NULL, -1); + if (!rc) { + rc = wait_event_interruptible(adie_client[id].wq, + adie_client[id].adie_svc_cb_done); + mutex_lock(&adie_client[id].lock); + if (unlikely(rc < 0)) { + if (rc == -ERESTARTSYS) + MM_ERR("wait_event_interruptible " + "returned -ERESTARTSYS\n"); + else + MM_ERR("wait_event_interruptible " + "returned error\n"); + rc = -1; + goto err; + } + MM_DBG("Status %d received from CB function, id %d rc %d\n", + adie_client[id].status, adie_client[id].client_id, rc); + rc = id; + if (adie_client[id].status == ADIE_SVC_STATUS_FAILURE) { + MM_ERR("Received failed status for register request\n"); + rc = -1; + } else + goto done; + } else { + MM_ERR("Failed to send register client request\n"); + rc = -1; + mutex_lock(&adie_client[id].lock); + } +err: + msm_rpc_unregister_client(adie_client[id].rpc_client); + adie_client[id].rpc_client = NULL; + adie_client[id].client_id = -1; + adie_client[id].cb_id = MSM_RPC_CLIENT_NULL_CB_ID; + adie_client[id].adie_svc_cb_done = 0; +done: + mutex_unlock(&adie_client[id].lock); + return rc; +} +EXPORT_SYMBOL(adie_svc_get); + +/* Returns: 0 on succes and + * -1 on failure + */ +int adie_svc_put(int id) +{ + int rc = 0; + struct adie_svc_client_deregister_cb_args arg; + + if (id < 0 || id >= ADIE_SVC_MAX_CLIENTS) + return -1; + + mutex_lock(&adie_client[id].lock); + if (adie_client[id].client_id == -1 || + adie_client[id].rpc_client == NULL) { + mutex_unlock(&adie_client[id].lock); + return -1; + } + arg.client_id = adie_client[id].client_id; + adie_client[id].adie_svc_cb_done = 0; + mutex_unlock(&adie_client[id].lock); + rc = msm_rpc_client_req(adie_client[id].rpc_client, + SND_ADIE_SVC_CLIENT_DEREGISTER_PROC, + adie_svc_client_deregister_arg, &arg, + NULL, NULL, -1); + if (!rc) { + rc = wait_event_interruptible(adie_client[id].wq, + adie_client[id].adie_svc_cb_done); + if (unlikely(rc < 0)) { + if (rc == -ERESTARTSYS) + MM_ERR("wait_event_interruptible " + "returned -ERESTARTSYS\n"); + else + MM_ERR("wait_event_interruptible " + "returned error\n"); + rc = -1; + goto err; + } + MM_DBG("Status received from CB function\n"); + mutex_lock(&adie_client[id].lock); + if (adie_client[id].status == ADIE_SVC_STATUS_FAILURE) { + rc = -1; + } else { + msm_rpc_unregister_client(adie_client[id].rpc_client); + adie_client[id].rpc_client = NULL; + adie_client[id].client_id = -1; + adie_client[id].cb_id = MSM_RPC_CLIENT_NULL_CB_ID; + adie_client[id].adie_svc_cb_done = 0; + } + mutex_unlock(&adie_client[id].lock); + } else { + MM_ERR("Failed to send deregister client request\n"); + rc = -1; + } +err: + return rc; +} +EXPORT_SYMBOL(adie_svc_put); + +/* Returns: 0 on success + * 2 already in use + * -1 on failure + */ +int adie_svc_config_adie_block(int id, + enum adie_block_enum_type adie_block_type, bool enable) +{ + int rc = 0; + struct adie_svc_config_adie_block_cb_args arg; + + if (id < 0 || id >= ADIE_SVC_MAX_CLIENTS) + return -1; + + mutex_lock(&adie_client[id].lock); + if (adie_client[id].client_id == -1 || + adie_client[id].rpc_client == NULL) { + mutex_unlock(&adie_client[id].lock); + return -1; + } + arg.client_id = adie_client[id].client_id; + arg.adie_block = adie_block_type; + arg.config = (enum adie_config_enum_type)enable; + adie_client[id].adie_svc_cb_done = 0; + mutex_unlock(&adie_client[id].lock); + rc = msm_rpc_client_req(adie_client[id].rpc_client, + SND_ADIE_SVC_CONFIG_ADIE_BLOCK_PROC, + adie_svc_config_adie_block_arg, &arg, + NULL, NULL, -1); + if (!rc) { + rc = wait_event_interruptible(adie_client[id].wq, + adie_client[id].adie_svc_cb_done); + if (unlikely(rc < 0)) { + if (rc == -ERESTARTSYS) + MM_ERR("wait_event_interruptible " + "returned -ERESTARTSYS\n"); + else + MM_ERR("wait_event_interruptible " + "returned error\n"); + rc = -1; + goto err; + } + MM_DBG("Status received from CB function\n"); + mutex_lock(&adie_client[id].lock); + if (adie_client[id].status == ADIE_SVC_STATUS_FAILURE) + rc = -1; + else + rc = adie_client[id].status; + mutex_unlock(&adie_client[id].lock); + } else { + MM_ERR("Failed to send adie block config request\n"); + rc = -1; + } +err: + return rc; +} +EXPORT_SYMBOL(adie_svc_config_adie_block); + +#ifdef CONFIG_DEBUG_FS + +struct dentry *dentry; + +static ssize_t snd_adie_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t snd_adie_debug_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int rc = 0, op = 0; + int id = 0, adie_block = 0, config = 1; + + sscanf(buf, "%d %d %d %d", &op, &id, &adie_block, &config); + MM_INFO("\nUser input: op %d id %d block %d config %d\n", op, id, + adie_block, config); + switch (op) { + case ADIE_SVC_REGISTER_CLIENT: + MM_INFO("ADIE_SVC_REGISTER_CLIENT\n"); + rc = adie_svc_get(); + if (rc >= 0) + MM_INFO("Client registered: %d\n", rc); + else + MM_ERR("Failed registering client\n"); + break; + case ADIE_SVC_DEREGISTER_CLIENT: + MM_INFO("ADIE_SVC_DEREGISTER_CLIENT: %d\n", id); + rc = adie_svc_put(id); + if (!rc) + MM_INFO("Client %d deregistered\n", id); + else + MM_ERR("Failed unregistering the client: %d\n", id); + break; + case ADIE_SVC_CONFIG_ADIE_BLOCK: + MM_INFO("ADIE_SVC_CONFIG_ADIE_BLOCK: id %d adie_block %d \ + config %d\n", id, adie_block, config); + rc = adie_svc_config_adie_block(id, + (enum adie_block_enum_type)adie_block, (bool)config); + if (!rc) + MM_INFO("ADIE block %d %s", adie_block, + config ? "enabled\n" : "disabled\n"); + else if (rc == 2) + MM_INFO("ADIE block %d already in use\n", adie_block); + else + MM_ERR("ERROR configuring the ADIE block\n"); + break; + default: + MM_INFO("Invalid operation\n"); + } + return count; +} + +static ssize_t snd_adie_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + static char buffer[1024]; + const int debug_bufmax = sizeof(buffer); + int id, n = 0; + + n += scnprintf(buffer + n, debug_bufmax - n, + "LIST OF CLIENTS\n"); + for (id = 0; id < ADIE_SVC_MAX_CLIENTS ; id++) { + if (adie_client[id].client_id != -1 && + adie_client[id].rpc_client != NULL) { + n += scnprintf(buffer + n, debug_bufmax - n, + "id %d rpc client 0x%08x\n", id, + (uint32_t)adie_client[id].rpc_client); + } + } + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations snd_adie_debug_fops = { + .read = snd_adie_debug_read, + .open = snd_adie_debug_open, + .write = snd_adie_debug_write, +}; +#endif + +static void __exit snd_adie_exit(void) +{ +#ifdef CONFIG_DEBUG_FS + if (dentry) + debugfs_remove(dentry); +#endif +} + +static int __init snd_adie_init(void) +{ + int id; +#ifdef CONFIG_DEBUG_FS + char name[sizeof "msm_snd_adie"]; + + snprintf(name, sizeof name, "msm_snd_adie"); + dentry = debugfs_create_file(name, S_IFREG | S_IRUGO | S_IWUGO, + NULL, NULL, &snd_adie_debug_fops); + if (IS_ERR(dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif + for (id = 0; id < ADIE_SVC_MAX_CLIENTS; id++) { + adie_client[id].client_id = -1; + adie_client[id].cb_id = MSM_RPC_CLIENT_NULL_CB_ID; + adie_client[id].status = 0; + adie_client[id].adie_svc_cb_done = 0; + mutex_init(&adie_client[id].lock); + init_waitqueue_head(&adie_client[id].wq); + adie_client[id].rpc_client = NULL; + } + return 0; +} + +module_init(snd_adie_init); +module_exit(snd_adie_exit); diff --git a/arch/arm/mach-msm/qdsp5v2/Makefile b/arch/arm/mach-msm/qdsp5v2/Makefile new file mode 100755 index 00000000000..3ae3c1b1fb4 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/Makefile @@ -0,0 +1,22 @@ +obj-y += afe.o audio_interct.o mi2s.o audio_dev_ctl.o voice.o + +ifeq ($(CONFIG_TIMPANI_CODEC), y) +obj-y += snddev_icodec.o +else ifeq ($(CONFIG_MARIMBA_CODEC), y) +obj-y += snddev_icodec.o +endif + +obj-$(CONFIG_MARIMBA_CODEC) += snddev_data_marimba.o +obj-$(CONFIG_TIMPANI_CODEC) += snddev_data_timpani.o + +obj-y += audio_pcm.o audpp.o audio_mp3.o audio_wma.o audio_aac.o audio_amrnb.o +obj-y += audio_amrwb.o audio_wmapro.o audio_adpcm.o audio_evrc.o audio_qcelp.o +obj-y += aux_pcm.o snddev_ecodec.o audio_out.o +obj-y += audio_lpa.o mp3_funcs.o pcm_funcs.o +obj-y += audpreproc.o audio_pcm_in.o audio_aac_in.o audio_amrnb_in.o audio_a2dp_in.o +obj-y += audio_evrc_in.o audio_qcelp_in.o +obj-y += adsp.o adsp_driver.o adsp_info.o +obj-y += audio_acdb.o snddev_virtual.o +obj-y += audio_fm.o +obj-y += lpa.o snddev_mi2s.o +obj-y += audio_mvs.o \ No newline at end of file diff --git a/arch/arm/mach-msm/qdsp5v2/adsp.c b/arch/arm/mach-msm/qdsp5v2/adsp.c new file mode 100644 index 00000000000..6d4d074738a --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/adsp.c @@ -0,0 +1,1229 @@ +/* + * Register/Interrupt access for userspace aDSP library. + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009,2011 Code Aurora Forum. All rights reserved. + * Author: Iliyan Malchev + * + * 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. + * + */ + +/* TODO: + * - move shareable rpc code outside of adsp.c + * - general solution for virt->phys patchup + * - queue IDs should be relative to modules + * - disallow access to non-associated queues + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "adsp.h" +#include +#include + +#ifdef CONFIG_DEBUG_FS +static struct dentry *dentry_adsp; +static struct dentry *dentry_wdata; +static struct dentry *dentry_rdata; +static int wdump, rdump; +#endif /* CONFIG_DEBUG_FS */ + +#define INT_ADSP INT_ADSP_A9_A11 + +static struct adsp_info adsp_info; +static struct msm_adsp_module *adsp_modules; +static int adsp_open_count; + +static DEFINE_MUTEX(adsp_open_lock); + +/* protect interactions with the ADSP command/message queue */ +static spinlock_t adsp_cmd_lock; +static spinlock_t adsp_write_lock; + +static uint32_t current_image = -1; + +void adsp_set_image(struct adsp_info *info, uint32_t image) +{ + current_image = image; +} + +/* + * Checks whether the module_id is available in the + * module_entries table.If module_id is available returns `0`. + * If module_id is not available returns `-ENXIO`. + */ +static int32_t adsp_validate_module(uint32_t module_id) +{ + uint32_t *ptr; + uint32_t module_index; + uint32_t num_mod_entries; + + ptr = adsp_info.init_info_ptr->module_entries; + num_mod_entries = adsp_info.init_info_ptr->module_table_size; + + for (module_index = 0; module_index < num_mod_entries; module_index++) + if (module_id == ptr[module_index]) + return 0; + + return -ENXIO; +} + +static int32_t adsp_validate_queue(uint32_t mod_id, unsigned q_idx, + uint32_t size) +{ + int32_t i; + struct adsp_rtos_mp_mtoa_init_info_type *sptr; + + sptr = adsp_info.init_info_ptr; + for (i = 0; i < sptr->mod_to_q_entries; i++) + if (mod_id == sptr->mod_to_q_tbl[i].module) + if (q_idx == sptr->mod_to_q_tbl[i].q_type) { + if (size <= sptr->mod_to_q_tbl[i].q_max_len) + return 0; + MM_ERR("q_idx: %d is not a valid queue \ + for module %x\n", q_idx, mod_id); + return -EINVAL; + } + MM_ERR("cmd_buf size is more than allowed size\n"); + return -EINVAL; +} + +uint32_t adsp_get_module(struct adsp_info *info, uint32_t task) +{ + return info->task_to_module[current_image][task]; +} + +uint32_t adsp_get_queue_offset(struct adsp_info *info, uint32_t queue_id) +{ + return info->queue_offset[current_image][queue_id]; +} + +static int rpc_adsp_rtos_app_to_modem(uint32_t cmd, uint32_t module, + struct msm_adsp_module *adsp_module) +{ + struct adsp_rtos_atom_cmd adspsvc_cmd; + int err; + + adspsvc_cmd.cmd = cmd; + adspsvc_cmd.proc_id = RPC_ADSP_RTOS_PROC_APPS; + adspsvc_cmd.module = module; + adspsvc_cmd.cb_handle = adsp_info.cb_handle; + + err = dalrpc_fcn_5(DALDEVICE_ADSP_CMD_IDX | 0x80000000, + adsp_info.handle, + &adspsvc_cmd, sizeof(adspsvc_cmd)); + if (err < 0) + MM_ERR("ADSP command send Failed\n"); + + return 0; +} + +static int get_module_index(uint32_t id) +{ + int mod_idx; + for (mod_idx = 0; mod_idx < adsp_info.module_count; mod_idx++) + if (adsp_info.module[mod_idx].id == id) + return mod_idx; + + return -ENXIO; +} + +static struct msm_adsp_module *find_adsp_module_by_id( + struct adsp_info *info, uint32_t id) +{ + int mod_idx; + + if (id > info->max_module_id) { + return NULL; + } else { + mod_idx = get_module_index(id); + if (mod_idx < 0) + return NULL; + return info->id_to_module[mod_idx]; + } +} + +static struct msm_adsp_module *find_adsp_module_by_name( + struct adsp_info *info, const char *name) +{ + unsigned n; + for (n = 0; n < info->module_count; n++) + if (!strcmp(name, adsp_modules[n].name)) + return adsp_modules + n; + return NULL; +} + +/* + * Send RPC_ADSP_RTOS_CMD_GET_INIT_INFO cmd to ARM9 and get + * queue offsets and module entries (init info) as part of the event. + */ +static void msm_get_init_info(void) +{ + struct adsp_rtos_atom_cmd cmd; + int err; + + cmd.cmd = RPC_ADSP_RTOS_CMD_GET_INIT_INFO; + cmd.proc_id = RPC_ADSP_RTOS_PROC_APPS; + cmd.module = 0; + cmd.cb_handle = adsp_info.cb_handle; + + err = dalrpc_fcn_5(DALDEVICE_ADSP_CMD_IDX | 0x80000000, + adsp_info.handle, + &cmd, sizeof(cmd)); + if (err < 0) + MM_ERR("INIT_INFO command send Failed\n"); +} + +int msm_adsp_get(const char *name, struct msm_adsp_module **out, + struct msm_adsp_ops *ops, void *driver_data) +{ + struct msm_adsp_module *module; + int rc = 0; + + module = find_adsp_module_by_name(&adsp_info, name); + if (!module) + return -ENODEV; + + mutex_lock(&module->lock); + MM_DBG("opening module %s\n", module->name); + + if (module->ops) { + rc = -EBUSY; + mutex_unlock(&module->lock); + goto done; + } + + module->ops = ops; + module->driver_data = driver_data; + *out = module; + mutex_unlock(&module->lock); + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_REGISTER_APP, + module->id, module); + if (rc) { + mutex_lock(&module->lock); + module->ops = NULL; + module->driver_data = NULL; + *out = NULL; + MM_ERR("REGISTER_APP failed\n"); + mutex_unlock(&module->lock); + goto done; + } + + MM_INFO("module %s has been registered\n", module->name); + +done: + return rc; +} +EXPORT_SYMBOL(msm_adsp_get); + +void msm_adsp_put(struct msm_adsp_module *module) +{ + unsigned long flags; + + mutex_lock(&module->lock); + if (module->ops) { + MM_INFO("closing module %s\n", module->name); + + /* lock to ensure a dsp event cannot be delivered + * during or after removal of the ops and driver_data + */ + spin_lock_irqsave(&adsp_cmd_lock, flags); + module->ops = NULL; + module->driver_data = NULL; + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + + if (module->state != ADSP_STATE_DISABLED) { + MM_INFO("disabling module %s\n", module->name); + mutex_unlock(&module->lock); + msm_adsp_disable(module); + return; + } + } else { + MM_INFO("module %s is already closed\n", module->name); + } + mutex_unlock(&module->lock); +} +EXPORT_SYMBOL(msm_adsp_put); + +int __msm_adsp_write(struct msm_adsp_module *module, unsigned dsp_queue_addr, + void *cmd_buf, size_t cmd_size) +{ + uint32_t ctrl_word; + uint32_t dsp_q_addr; + uint32_t dsp_addr; + uint32_t cmd_id = 0; + int cnt = 0; + int ret_status = 0; + unsigned long flags; + struct adsp_info *info; + + if (!module || !cmd_buf) { + MM_ERR("Called with NULL parameters\n"); + return -EINVAL; + } + info = module->info; + spin_lock_irqsave(&adsp_write_lock, flags); + + if (module->state != ADSP_STATE_ENABLED) { + spin_unlock_irqrestore(&adsp_write_lock, flags); + MM_ERR("module %s not enabled before write\n", module->name); + return -ENODEV; + } + if (adsp_validate_module(module->id)) { + spin_unlock_irqrestore(&adsp_write_lock, flags); + MM_ERR("module id validation failed %s %d\n", + module->name, module->id); + return -ENXIO; + } + if (dsp_queue_addr >= QDSP_MAX_NUM_QUEUES) { + spin_unlock_irqrestore(&adsp_write_lock, flags); + MM_ERR("Invalid Queue Index: %d\n", dsp_queue_addr); + return -ENXIO; + } + if (adsp_validate_queue(module->id, dsp_queue_addr, cmd_size)) { + spin_unlock_irqrestore(&adsp_write_lock, flags); + return -EINVAL; + } + dsp_q_addr = adsp_get_queue_offset(info, dsp_queue_addr); + dsp_q_addr &= ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M; + + /* Poll until the ADSP is ready to accept a command. + * Wait for 100us, return error if it's not responding. + * If this returns an error, we need to disable ALL modules and + * then retry. + */ + while (((ctrl_word = readl(info->write_ctrl)) & + ADSP_RTOS_WRITE_CTRL_WORD_READY_M) != + ADSP_RTOS_WRITE_CTRL_WORD_READY_V) { + if (cnt > 50) { + MM_ERR("timeout waiting for DSP write ready\n"); + ret_status = -EIO; + goto fail; + } + MM_DBG("waiting for DSP write ready\n"); + udelay(2); + cnt++; + } + + /* Set the mutex bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M); + ctrl_word |= ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V; + + /* Clear the command bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_CMD_M); + + /* Set the queue address bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M); + ctrl_word |= dsp_q_addr; + + writel(ctrl_word, info->write_ctrl); + + /* Generate an interrupt to the DSP. This notifies the DSP that + * we are about to send a command on this particular queue. The + * DSP will in response change its state. + */ + writel(1, info->send_irq); + + /* Poll until the adsp responds to the interrupt; this does not + * generate an interrupt from the adsp. This should happen within + * 5ms. + */ + cnt = 0; + while ((readl(info->write_ctrl) & + ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M) == + ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V) { + if (cnt > 2500) { + MM_ERR("timeout waiting for adsp ack\n"); + ret_status = -EIO; + goto fail; + } + udelay(2); + cnt++; + } + + /* Read the ctrl word */ + ctrl_word = readl(info->write_ctrl); + + if ((ctrl_word & ADSP_RTOS_WRITE_CTRL_WORD_STATUS_M) != + ADSP_RTOS_WRITE_CTRL_WORD_NO_ERR_V) { + ret_status = -EAGAIN; + goto fail; + } else { + /* No error */ + /* Get the DSP buffer address */ + dsp_addr = (ctrl_word & ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M) + + (uint32_t)MSM_AD5_BASE; + + if (dsp_addr < (uint32_t)(MSM_AD5_BASE + QDSP_RAMC_OFFSET)) { + uint16_t *buf_ptr = (uint16_t *) cmd_buf; + uint16_t *dsp_addr16 = (uint16_t *)dsp_addr; + cmd_size /= sizeof(uint16_t); + + /* Save the command ID */ + cmd_id = (uint32_t) buf_ptr[0]; + + /* Copy the command to DSP memory */ + cmd_size++; + while (--cmd_size) + *dsp_addr16++ = *buf_ptr++; + } else { + uint32_t *buf_ptr = (uint32_t *) cmd_buf; + uint32_t *dsp_addr32 = (uint32_t *)dsp_addr; + cmd_size /= sizeof(uint32_t); + + /* Save the command ID */ + cmd_id = buf_ptr[0]; + + cmd_size++; + while (--cmd_size) + *dsp_addr32++ = *buf_ptr++; + } + + /* Set the mutex bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M); + ctrl_word |= ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V; + + /* Set the command bits to write done */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_CMD_M); + ctrl_word |= ADSP_RTOS_WRITE_CTRL_WORD_CMD_WRITE_DONE_V; + + /* Set the queue address bits */ + ctrl_word &= ~(ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M); + ctrl_word |= dsp_q_addr; + + writel(ctrl_word, info->write_ctrl); + + /* Generate an interrupt to the DSP. It does not respond with + * an interrupt, and we do not need to wait for it to + * acknowledge, because it will hold the mutex lock until it's + * ready to receive more commands again. + */ + writel(1, info->send_irq); + + module->num_commands++; + } /* Ctrl word status bits were 00, no error in the ctrl word */ + +fail: + spin_unlock_irqrestore(&adsp_write_lock, flags); + return ret_status; +} +EXPORT_SYMBOL(msm_adsp_write); + +int msm_adsp_write(struct msm_adsp_module *module, unsigned dsp_queue_addr, + void *cmd_buf, size_t cmd_size) +{ + int rc, retries = 0; +#ifdef CONFIG_DEBUG_FS + uint16_t *ptr; + int ii; + + if (wdump > 0) { + ptr = cmd_buf; + pr_info("A->D:%x\n", module->id); + pr_info("adsp: %x %d\n", dsp_queue_addr, cmd_size); + for (ii = 0; ii < cmd_size/2; ii++) + pr_info("%x ", ptr[ii]); + pr_info("\n"); + } +#endif /* CONFIG_DEBUG_FS */ + do { + rc = __msm_adsp_write(module, dsp_queue_addr, cmd_buf, + cmd_size); + if (rc == -EAGAIN) + udelay(50); + } while (rc == -EAGAIN && retries++ < 300); + if (retries > 20) + MM_INFO("%s command took %d attempts: rc %d\n", + module->name, retries, rc); + return rc; +} + +#ifdef CONFIG_MSM_ADSP_REPORT_EVENTS +static void *event_addr; +static void read_event(void *buf, size_t len) +{ + uint32_t dptr[3]; + struct adsp_rtos_mp_mtoa_s_type *sptr; + struct adsp_rtos_mp_mtoa_type *pkt_ptr; + + sptr = event_addr; + pkt_ptr = &sptr->adsp_rtos_mp_mtoa_data.mp_mtoa_packet; + + dptr[0] = sptr->mp_mtoa_header.event; + dptr[1] = pkt_ptr->module; + dptr[2] = pkt_ptr->image; + + if (len > EVENT_LEN) + len = EVENT_LEN; + + memcpy(buf, dptr, len); +} +#endif + +static void adsp_rtos_mtoa_cb(void *context, uint32_t param, + void *evt_buf, uint32_t len) +{ + struct adsp_rtos_mp_mtoa_s_type *args = NULL; + uint32_t event = 0; + uint32_t proc_id = 0; + uint32_t module_id; + uint32_t image; + struct msm_adsp_module *module; + struct adsp_rtos_mp_mtoa_type *pkt_ptr; + struct queue_to_offset_type *qptr; + struct queue_to_offset_type *qtbl; + struct mod_to_queue_offsets *mqptr; + struct mod_to_queue_offsets *mqtbl; + uint32_t *mptr; + uint32_t *mtbl; + uint32_t q_idx; + uint32_t num_entries; + uint32_t entries_per_image; + struct adsp_rtos_mp_mtoa_init_info_type *iptr; + struct adsp_rtos_mp_mtoa_init_info_type *sptr; + int32_t i_no, e_idx; + static uint32_t init_info_completed; + static uint32_t init_info_len = + sizeof(struct adsp_rtos_mp_mtoa_header_type); + static uint32_t next_init_info_byte; + static uint32_t expected_byte = 1; + uint32_t hdr_len = sizeof(struct adsp_rtos_mp_mtoa_header_type); + + if (len) { + args = (struct adsp_rtos_mp_mtoa_s_type *) evt_buf; + event = args->mp_mtoa_header.event; + proc_id = args->mp_mtoa_header.proc_id; + } + + if (!init_info_completed && event == RPC_ADSP_RTOS_INIT_INFO) { + memcpy(((char *)adsp_info.raw_event) + init_info_len, + (char *)evt_buf + hdr_len + 4, + len - ((hdr_len + 4))); + init_info_len += (len - (hdr_len + 4)); + evt_buf += hdr_len; + next_init_info_byte = *(uint32_t *) evt_buf; + expected_byte += len; + if (next_init_info_byte && + (expected_byte != next_init_info_byte)) { + MM_ERR("INIT_INFO - expecting next byte to be %d\n" + "\tbut ADSPSVC indicated next byte to be %d\n", + expected_byte, next_init_info_byte); + return; + } + if (!next_init_info_byte) { + args = adsp_info.raw_event; + args->mp_mtoa_header.event = event; + args->mp_mtoa_header.proc_id = proc_id; + init_info_completed = 1; + } else + return; + } + + if (event == RPC_ADSP_RTOS_INIT_INFO) { + MM_INFO("INIT_INFO Event\n"); + sptr = &args->adsp_rtos_mp_mtoa_data.mp_mtoa_init_packet; + + iptr = adsp_info.init_info_ptr; + iptr->image_count = sptr->image_count; + if (iptr->image_count > IMG_MAX) + iptr->image_count = IMG_MAX; + iptr->num_queue_offsets = sptr->num_queue_offsets; + num_entries = iptr->num_queue_offsets; + if (num_entries > ENTRIES_MAX) { + num_entries = ENTRIES_MAX; + iptr->num_queue_offsets = ENTRIES_MAX; + } + qptr = &sptr->queue_offsets_tbl[0][0]; + for (i_no = 0; i_no < iptr->image_count; i_no++) { + qtbl = &iptr->queue_offsets_tbl[i_no][0]; + for (e_idx = 0; e_idx < num_entries; e_idx++) { + qtbl[e_idx].offset = qptr->offset; + qtbl[e_idx].queue = qptr->queue; + q_idx = qptr->queue; + iptr->queue_offsets[i_no][q_idx] = + qtbl[e_idx].offset; + qptr++; + } + } + + num_entries = sptr->num_task_module_entries; + if (num_entries > ENTRIES_MAX) + num_entries = ENTRIES_MAX; + iptr->num_task_module_entries = num_entries; + entries_per_image = num_entries / iptr->image_count; + mptr = &sptr->task_to_module_tbl[0][0]; + for (i_no = 0; i_no < iptr->image_count; i_no++) { + mtbl = &iptr->task_to_module_tbl[i_no][0]; + for (e_idx = 0; e_idx < entries_per_image; e_idx++) { + mtbl[e_idx] = *mptr; + mptr++; + } + } + + iptr->module_table_size = sptr->module_table_size; + if (iptr->module_table_size > MODULES_MAX) + iptr->module_table_size = MODULES_MAX; + mptr = &sptr->module_entries[0]; + for (i_no = 0; i_no < iptr->module_table_size; i_no++) + iptr->module_entries[i_no] = mptr[i_no]; + + mqptr = &sptr->mod_to_q_tbl[0]; + mqtbl = &iptr->mod_to_q_tbl[0]; + iptr->mod_to_q_entries = sptr->mod_to_q_entries; + if (iptr->mod_to_q_entries > ENTRIES_MAX) + iptr->mod_to_q_entries = ENTRIES_MAX; + for (e_idx = 0; e_idx < iptr->mod_to_q_entries; e_idx++) { + mqtbl[e_idx].module = mqptr->module; + mqtbl[e_idx].q_type = mqptr->q_type; + mqtbl[e_idx].q_max_len = mqptr->q_max_len; + mqptr++; + } + + adsp_info.init_info_state = ADSP_STATE_INIT_INFO; + kfree(adsp_info.raw_event); + wake_up(&adsp_info.init_info_wait); + return; + } + pkt_ptr = &args->adsp_rtos_mp_mtoa_data.mp_mtoa_packet; + module_id = pkt_ptr->module; + image = pkt_ptr->image; + + MM_INFO("rpc event=%d, proc_id=%d, module=%d, image=%d\n", + event, proc_id, module_id, image); + + module = find_adsp_module_by_id(&adsp_info, module_id); + if (!module) { + MM_ERR("module %d is not supported!\n", module_id); + return; + } + + mutex_lock(&module->lock); + switch (event) { + case RPC_ADSP_RTOS_MOD_READY: + MM_INFO("module %s: READY\n", module->name); + module->state = ADSP_STATE_ENABLED; + wake_up(&module->state_wait); + adsp_set_image(module->info, image); + break; + case RPC_ADSP_RTOS_MOD_DISABLE: + MM_INFO("module %s: DISABLED\n", module->name); + module->state = ADSP_STATE_DISABLED; + wake_up(&module->state_wait); + break; + case RPC_ADSP_RTOS_SERVICE_RESET: + MM_INFO("module %s: SERVICE_RESET\n", module->name); + module->state = ADSP_STATE_DISABLED; + wake_up(&module->state_wait); + break; + case RPC_ADSP_RTOS_CMD_SUCCESS: + MM_INFO("module %s: CMD_SUCCESS\n", module->name); + break; + case RPC_ADSP_RTOS_CMD_FAIL: + MM_INFO("module %s: CMD_FAIL\n", module->name); + break; + case RPC_ADSP_RTOS_DISABLE_FAIL: + MM_INFO("module %s: DISABLE_FAIL\n", module->name); + break; + default: + MM_ERR("unknown event %d\n", event); + mutex_unlock(&module->lock); + return; + } +#ifdef CONFIG_MSM_ADSP_REPORT_EVENTS + event_addr = (uint32_t *)evt_buf; + if (module->ops) + module->ops->event(module->driver_data, + EVENT_MSG_ID, + EVENT_LEN, + read_event); +#endif + mutex_unlock(&module->lock); +} + +static size_t read_event_size; +static void *read_event_addr; + +static void read_event_16(void *buf, size_t len) +{ + uint16_t *dst = buf; + uint16_t *src = read_event_addr; + len /= 2; + if (len > read_event_size) + len = read_event_size; + while (len--) + *dst++ = *src++; +} + +static void read_event_32(void *buf, size_t len) +{ + uint32_t *dst = buf; + uint32_t *src = read_event_addr; + len /= 2; + if (len > read_event_size) + len = read_event_size; + while (len--) + *dst++ = *src++; +} + +static int adsp_rtos_read_ctrl_word_cmd_tast_to_h_v( + struct adsp_info *info, void *dsp_addr) +{ + struct msm_adsp_module *module; + unsigned rtos_task_id; + unsigned msg_id; + unsigned msg_length; +#ifdef CONFIG_DEBUG_FS + uint16_t *ptr; + int ii; +#endif /* CONFIG_DEBUG_FS */ + void (*func)(void *, size_t); + + if (dsp_addr >= (void *)(MSM_AD5_BASE + QDSP_RAMC_OFFSET)) { + uint32_t *dsp_addr32 = dsp_addr; + uint32_t tmp = *dsp_addr32++; + rtos_task_id = (tmp & ADSP_RTOS_READ_CTRL_WORD_TASK_ID_M) >> 8; + msg_id = (tmp & ADSP_RTOS_READ_CTRL_WORD_MSG_ID_M); + read_event_size = tmp >> 16; + read_event_addr = dsp_addr32; + msg_length = read_event_size * sizeof(uint32_t); + func = read_event_32; + } else { + uint16_t *dsp_addr16 = dsp_addr; + uint16_t tmp = *dsp_addr16++; + rtos_task_id = (tmp & ADSP_RTOS_READ_CTRL_WORD_TASK_ID_M) >> 8; + msg_id = tmp & ADSP_RTOS_READ_CTRL_WORD_MSG_ID_M; + read_event_size = *dsp_addr16++; + read_event_addr = dsp_addr16; + msg_length = read_event_size * sizeof(uint16_t); + func = read_event_16; + } + + if (rtos_task_id > info->max_task_id) { + MM_ERR("bogus task id %d\n", rtos_task_id); + return 0; + } + module = find_adsp_module_by_id(info, + adsp_get_module(info, rtos_task_id)); + + if (!module) { + MM_ERR("no module for task id %d\n", rtos_task_id); + return 0; + } + + module->num_events++; + + if (!module->ops) { + MM_ERR("module %s is not open\n", module->name); + return 0; + } +#ifdef CONFIG_DEBUG_FS + if (rdump > 0) { + ptr = read_event_addr; + pr_info("D->A\n"); + pr_info("m_id = %x id = %x\n", module->id, msg_id); + for (ii = 0; ii < msg_length/2; ii++) + pr_info("%x ", ptr[ii]); + pr_info("\n"); + } +#endif /* CONFIG_DEBUG_FS */ + + module->ops->event(module->driver_data, msg_id, msg_length, func); + return 0; +} + +static int adsp_get_event(struct adsp_info *info) +{ + uint32_t ctrl_word; + uint32_t ready; + void *dsp_addr; + uint32_t cmd_type; + int cnt; + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&adsp_cmd_lock, flags); + + /* Whenever the DSP has a message, it updates this control word + * and generates an interrupt. When we receive the interrupt, we + * read this register to find out what ADSP task the command is + * comming from. + * + * The ADSP should *always* be ready on the first call, but the + * irq handler calls us in a loop (to handle back-to-back command + * processing), so we give the DSP some time to return to the + * ready state. The DSP will not issue another IRQ for events + * pending between the first IRQ and the event queue being drained, + * unfortunately. + */ + + for (cnt = 0; cnt < 50; cnt++) { + ctrl_word = readl(info->read_ctrl); + + if ((ctrl_word & ADSP_RTOS_READ_CTRL_WORD_FLAG_M) == + ADSP_RTOS_READ_CTRL_WORD_FLAG_UP_CONT_V) + goto ready; + + udelay(2); + } + MM_ERR("not ready after 100uS\n"); + rc = -EBUSY; + goto done; + +ready: + /* Here we check to see if there are pending messages. If there are + * none, we siply return -EAGAIN to indicate that there are no more + * messages pending. + */ + ready = ctrl_word & ADSP_RTOS_READ_CTRL_WORD_READY_M; + if ((ready != ADSP_RTOS_READ_CTRL_WORD_READY_V) && + (ready != ADSP_RTOS_READ_CTRL_WORD_CONT_V)) { + rc = -EAGAIN; + goto done; + } + + /* DSP says that there are messages waiting for the host to read */ + + /* Get the Command Type */ + cmd_type = ctrl_word & ADSP_RTOS_READ_CTRL_WORD_CMD_TYPE_M; + + /* Get the DSP buffer address */ + dsp_addr = (void *)((ctrl_word & + ADSP_RTOS_READ_CTRL_WORD_DSP_ADDR_M) + + (uint32_t)MSM_AD5_BASE); + + /* We can only handle Task-to-Host messages */ + if (cmd_type != ADSP_RTOS_READ_CTRL_WORD_CMD_TASK_TO_H_V) { + MM_ERR("unknown dsp cmd_type %d\n", cmd_type); + rc = -EIO; + goto done; + } + + adsp_rtos_read_ctrl_word_cmd_tast_to_h_v(info, dsp_addr); + + ctrl_word = readl(info->read_ctrl); + ctrl_word &= ~ADSP_RTOS_READ_CTRL_WORD_READY_M; + + /* Write ctrl word to the DSP */ + writel(ctrl_word, info->read_ctrl); + + /* Generate an interrupt to the DSP */ + writel(1, info->send_irq); + +done: + spin_unlock_irqrestore(&adsp_cmd_lock, flags); + return rc; +} + +static irqreturn_t adsp_irq_handler(int irq, void *data) +{ + struct adsp_info *info = &adsp_info; + int cnt = 0; + for (cnt = 0; cnt < 15; cnt++) + if (adsp_get_event(info) < 0) + break; + if (cnt > info->event_backlog_max) + info->event_backlog_max = cnt; + info->events_received += cnt; + if (cnt == 15) + MM_ERR("too many (%d) events for single irq!\n", cnt); + return IRQ_HANDLED; +} + +int adsp_set_clkrate(struct msm_adsp_module *module, unsigned long clk_rate) +{ + if (module->clk && clk_rate) + return clk_set_min_rate(module->clk, clk_rate); + + return -EINVAL; +} + +int msm_adsp_enable(struct msm_adsp_module *module) +{ + int rc = 0; + + MM_INFO("enable '%s'state[%d] id[%d]\n", + module->name, module->state, module->id); + + mutex_lock(&module->lock); + switch (module->state) { + case ADSP_STATE_DISABLED: + module->state = ADSP_STATE_ENABLING; + mutex_unlock(&module->lock); + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_ENABLE, + module->id, module); + if (rc) { + mutex_lock(&module->lock); + module->state = ADSP_STATE_DISABLED; + break; + } + rc = wait_event_timeout(module->state_wait, + module->state != ADSP_STATE_ENABLING, + 1 * HZ); + mutex_lock(&module->lock); + if (module->state == ADSP_STATE_ENABLED) { + rc = 0; + } else { + MM_ERR("module '%s' enable timed out\n", module->name); + rc = -ETIMEDOUT; + } + if (module->open_count++ == 0 && module->clk) + clk_enable(module->clk); + + mutex_lock(&adsp_open_lock); + if (adsp_open_count++ == 0) + enable_irq(INT_ADSP); + mutex_unlock(&adsp_open_lock); + break; + case ADSP_STATE_ENABLING: + MM_DBG("module '%s' enable in progress\n", module->name); + break; + case ADSP_STATE_ENABLED: + MM_DBG("module '%s' already enabled\n", module->name); + break; + case ADSP_STATE_DISABLING: + MM_ERR("module '%s' disable in progress\n", module->name); + rc = -EBUSY; + break; + } + mutex_unlock(&module->lock); + return rc; +} +EXPORT_SYMBOL(msm_adsp_enable); + +int msm_adsp_disable_event_rsp(struct msm_adsp_module *module) +{ + int rc = 0; + + mutex_lock(&module->lock); + + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_DISABLE_EVENT_RSP, + module->id, module); + mutex_unlock(&module->lock); + + return rc; +} +EXPORT_SYMBOL(msm_adsp_disable_event_rsp); + +int msm_adsp_disable(struct msm_adsp_module *module) +{ + int rc = 0; + + mutex_lock(&module->lock); + switch (module->state) { + case ADSP_STATE_DISABLED: + MM_DBG("module '%s' already disabled\n", module->name); + mutex_unlock(&module->lock); + break; + case ADSP_STATE_ENABLING: + case ADSP_STATE_ENABLED: + mutex_unlock(&module->lock); + rc = rpc_adsp_rtos_app_to_modem(RPC_ADSP_RTOS_CMD_DISABLE, + module->id, module); + mutex_lock(&module->lock); + module->state = ADSP_STATE_DISABLED; + if (--module->open_count == 0 && module->clk) + clk_disable(module->clk); + mutex_unlock(&module->lock); + mutex_lock(&adsp_open_lock); + if (--adsp_open_count == 0) { + disable_irq(INT_ADSP); + MM_INFO("disable interrupt\n"); + } + mutex_unlock(&adsp_open_lock); + break; + } + return rc; +} +EXPORT_SYMBOL(msm_adsp_disable); + +static int msm_adsp_probe(struct platform_device *pdev) +{ + unsigned count; + int rc, i; + + adsp_info.init_info_ptr = kzalloc( + (sizeof(struct adsp_rtos_mp_mtoa_init_info_type)), GFP_KERNEL); + if (!adsp_info.init_info_ptr) + return -ENOMEM; + + adsp_info.raw_event = kzalloc( + (sizeof(struct adsp_rtos_mp_mtoa_s_type)), GFP_KERNEL); + if (!adsp_info.raw_event) { + kfree(adsp_info.init_info_ptr); + return -ENOMEM; + } + + rc = adsp_init_info(&adsp_info); + if (rc) { + kfree(adsp_info.init_info_ptr); + kfree(adsp_info.raw_event); + return rc; + } + adsp_info.send_irq += (uint32_t) MSM_AD5_BASE; + adsp_info.read_ctrl += (uint32_t) MSM_AD5_BASE; + adsp_info.write_ctrl += (uint32_t) MSM_AD5_BASE; + count = adsp_info.module_count; + + adsp_modules = kzalloc( + (sizeof(struct msm_adsp_module) + sizeof(void *)) * + count, GFP_KERNEL); + if (!adsp_modules) { + kfree(adsp_info.init_info_ptr); + kfree(adsp_info.raw_event); + return -ENOMEM; + } + + adsp_info.id_to_module = (void *) (adsp_modules + count); + + spin_lock_init(&adsp_cmd_lock); + spin_lock_init(&adsp_write_lock); + + rc = request_irq(INT_ADSP, adsp_irq_handler, + IRQF_TRIGGER_RISING, "adsp", 0); + if (rc < 0) + goto fail_request_irq; + disable_irq(INT_ADSP); + + for (i = 0; i < count; i++) { + struct msm_adsp_module *mod = adsp_modules + i; + mutex_init(&mod->lock); + init_waitqueue_head(&mod->state_wait); + mod->info = &adsp_info; + mod->name = adsp_info.module[i].name; + mod->id = adsp_info.module[i].id; + if (adsp_info.module[i].clk_name) + mod->clk = clk_get(NULL, adsp_info.module[i].clk_name); + else + mod->clk = NULL; + if (mod->clk && adsp_info.module[i].clk_rate) + clk_set_min_rate(mod->clk, + adsp_info.module[i].clk_rate); + mod->verify_cmd = adsp_info.module[i].verify_cmd; + mod->patch_event = adsp_info.module[i].patch_event; + INIT_HLIST_HEAD(&mod->pmem_regions); + mod->pdev.name = adsp_info.module[i].pdev_name; + mod->pdev.id = -1; + adsp_info.id_to_module[i] = mod; + platform_device_register(&mod->pdev); + } + + msm_adsp_publish_cdevs(adsp_modules, count); + + rc = daldevice_attach(DALRPC_ADSPSVC_DEVICEID, DALRPC_ADSPSVC_PORT, + DALRPC_ADSPSVC_DEST, &adsp_info.handle); + if (rc) { + MM_ERR("adsp attach failed : %d\n", rc); + goto fail_dal_attach; + } + + adsp_info.cb_handle = dalrpc_alloc_cb(adsp_info.handle, + adsp_rtos_mtoa_cb, NULL); + if (adsp_info.cb_handle == NULL) { + MM_ERR("Callback registration failed\n"); + goto fail_allocate_cb; + } + + /* Get INIT_INFO */ + init_waitqueue_head(&adsp_info.init_info_wait); + msm_get_init_info(); + rc = wait_event_timeout(adsp_info.init_info_wait, + adsp_info.init_info_state == ADSP_STATE_INIT_INFO, + 10 * HZ); + if (!rc) { + MM_ERR("INIT_INFO failed\n"); + rc = -ETIMEDOUT; + } else + return 0; + +fail_allocate_cb: + daldevice_detach(adsp_info.handle); + adsp_info.handle = NULL; +fail_dal_attach: + enable_irq(INT_ADSP); + free_irq(INT_ADSP, 0); +fail_request_irq: + kfree(adsp_modules); + kfree(adsp_info.init_info_ptr); + kfree(adsp_info.raw_event); + return rc; +} + +#ifdef CONFIG_DEBUG_FS +static int get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (strict_strtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } + else + return -EINVAL; + } + return 0; +} + +static ssize_t adsp_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + pr_debug("adsp debugfs opened\n"); + return 0; +} +static ssize_t adsp_debug_write(struct file *file, const char __user *buf, + size_t cnt, loff_t *ppos) +{ + char *access_str = file->private_data; + char lbuf[32]; + int rc; + long int param[5]; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + rc = copy_from_user(lbuf, buf, cnt); + if (rc) { + pr_info("Unable to copy data from user space\n"); + return -EFAULT; + } + lbuf[cnt] = '\0'; + + if (!strncmp(access_str, "write_log", 9)) { + if (get_parameters(lbuf, param, 1) == 0) { + switch (param[0]) { + case 1: + if (wdump <= 0) + wdump = 1; + pr_debug("write cmd to DSP(A->D) dump \ + started:%d\n", wdump); + break; + case 0: + if (wdump > 0) + wdump = 0; + pr_debug("Stop write cmd to \ + DSP(A->D):%d\n", wdump); + break; + default: + rc = -EINVAL; + break; + } + } else + rc = -EINVAL; + } else if (!strncmp(access_str, "read_log", 8)) { + if (get_parameters(lbuf, param, 1) == 0) { + switch (param[0]) { + case 1: + if (rdump <= 0) + rdump = 1; + pr_debug("write cmd from DSP(D->A) dump \ + started:%d\n", wdump); + break; + case 0: + if (rdump > 0) + rdump = 0; + pr_debug("Stop write cmd from \ + DSP(D->A):%d\n", wdump); + break; + default: + rc = -EINVAL; + break; + } + } else + rc = -EINVAL; + } else { + rc = -EINVAL; + } + if (rc == 0) + rc = cnt; + else { + pr_err("%s: rc = %d\n", __func__, rc); + pr_info("\nWrong command: Use =>\n"); + pr_info("-------------------------\n"); + pr_info("To Start A->D:: echo \"1\">/sys/kernel/debug/ \ + adsp_cmd/write_log\n"); + pr_info("To Start D->A:: echo \"1\">/sys/kernel/debug/ \ + adsp_cmd/read_log\n"); + pr_info("To Stop A->D:: echo \"0\">/sys/kernel/debug/ \ + adsp_cmd/write_log\n"); + pr_info("To Stop D->A:: echo \"0\">/sys/kernel/debug/ \ + adsp_cmd/read_log\n"); + pr_info("------------------------\n"); + } + + return rc; +} +#endif + +static struct platform_driver msm_adsp_driver = { + .probe = msm_adsp_probe, + .driver = { + .owner = THIS_MODULE, + }, +}; + +struct platform_device msm_adsp_device = { + .name = "msm_adsp", + .id = -1, +}; + +static char msm_adsp_driver_name[] = "msm_adsp"; + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations adsp_debug_fops = { + .write = adsp_debug_write, + .open = adsp_debug_open, +}; +#endif + +static int __init adsp_init(void) +{ + int rc; + +#ifdef CONFIG_DEBUG_FS + dentry_adsp = debugfs_create_dir("adsp_cmd", 0); + if (!IS_ERR(dentry_adsp)) { + dentry_wdata = debugfs_create_file("write_log", \ + S_IFREG | S_IRUGO, dentry_adsp, + (void *) "write_log" , &adsp_debug_fops); + dentry_rdata = debugfs_create_file("read_log", \ + S_IFREG | S_IRUGO, dentry_adsp, + (void *) "read_log", &adsp_debug_fops); + } +#endif /* CONFIG_DEBUG_FS */ + + msm_adsp_driver.driver.name = msm_adsp_driver_name; + rc = platform_device_register(&msm_adsp_device); + rc = platform_driver_register(&msm_adsp_driver); + MM_INFO("%s -- %d\n", msm_adsp_driver_name, rc); + return rc; +} + +device_initcall(adsp_init); diff --git a/arch/arm/mach-msm/qdsp5v2/adsp.h b/arch/arm/mach-msm/qdsp5v2/adsp.h new file mode 100644 index 00000000000..18f40461797 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/adsp.h @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Author: Iliyan Malchev + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_ADSP_H +#define _ARCH_ARM_MACH_MSM_ADSP_H + +#include +#include +#include +#include +#include + +int adsp_pmem_fixup(struct msm_adsp_module *module, void **addr, + unsigned long len); +int adsp_pmem_fixup_kvaddr(struct msm_adsp_module *module, void **addr, + unsigned long *kvaddr, unsigned long len); +int adsp_pmem_paddr_fixup(struct msm_adsp_module *module, void **addr); + +int adsp_vfe_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); +int adsp_jpeg_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); +int adsp_lpm_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); +int adsp_video_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); +int adsp_videoenc_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size); + + +struct adsp_event; + +int adsp_vfe_patch_event(struct msm_adsp_module *module, + struct adsp_event *event); + +int adsp_jpeg_patch_event(struct msm_adsp_module *module, + struct adsp_event *event); + + +struct adsp_module_info { + const char *name; + const char *pdev_name; + uint32_t id; + const char *clk_name; + unsigned long clk_rate; + int (*verify_cmd) (struct msm_adsp_module*, unsigned int, void *, + size_t); + int (*patch_event) (struct msm_adsp_module*, struct adsp_event *); +}; + +#define ADSP_EVENT_MAX_SIZE 496 +#define EVENT_LEN 12 +#define EVENT_MSG_ID ((uint16_t)~0) + +struct adsp_event { + struct list_head list; + uint32_t size; /* always in bytes */ + uint16_t msg_id; + uint16_t type; /* 0 for msgs (from aDSP), -1 for events (from ARM9) */ + int is16; /* always 0 (msg is 32-bit) when the event type is 1(ARM9) */ + union { + uint16_t msg16[ADSP_EVENT_MAX_SIZE / 2]; + uint32_t msg32[ADSP_EVENT_MAX_SIZE / 4]; + } data; +}; + +#define DALRPC_ADSPSVC_DEVICEID 0x0200009A +#define DALRPC_ADSPSVC_DEST SMD_APPS_MODEM +#define DALRPC_ADSPSVC_PORT "DAL00" + +enum { + DALDEVICE_ADSP_CMD_IDX = DALDEVICE_FIRST_DEVICE_API_IDX, +}; + +struct adsp_rtos_atom_cmd { + uint32_t cmd; + uint32_t proc_id; + uint32_t module; + void *cb_handle; +}; + +enum rpc_adsp_rtos_proc_type { + RPC_ADSP_RTOS_PROC_NONE = 0, + RPC_ADSP_RTOS_PROC_MODEM = 1, + RPC_ADSP_RTOS_PROC_APPS = 2, +}; + +enum { + RPC_ADSP_RTOS_CMD_REGISTER_APP, + RPC_ADSP_RTOS_CMD_ENABLE, + RPC_ADSP_RTOS_CMD_DISABLE, + RPC_ADSP_RTOS_CMD_KERNEL_COMMAND, + RPC_ADSP_RTOS_CMD_16_COMMAND, + RPC_ADSP_RTOS_CMD_32_COMMAND, + RPC_ADSP_RTOS_CMD_DISABLE_EVENT_RSP, + RPC_ADSP_RTOS_CMD_REMOTE_EVENT, + RPC_ADSP_RTOS_CMD_SET_STATE, + RPC_ADSP_RTOS_CMD_REMOTE_INIT_INFO_EVENT, + RPC_ADSP_RTOS_CMD_GET_INIT_INFO, +}; + +enum rpc_adsp_rtos_mod_status_type { + RPC_ADSP_RTOS_MOD_READY, + RPC_ADSP_RTOS_MOD_DISABLE, + RPC_ADSP_RTOS_SERVICE_RESET, + RPC_ADSP_RTOS_CMD_FAIL, + RPC_ADSP_RTOS_CMD_SUCCESS, + RPC_ADSP_RTOS_INIT_INFO, + RPC_ADSP_RTOS_DISABLE_FAIL, +}; + +enum qdsp_image_type { + QDSP_IMAGE_COMBO, + QDSP_IMAGE_GAUDIO, + QDSP_IMAGE_QTV_LP, + QDSP_IMAGE_MAX, + /* DO NOT USE: Force this enum to be a 32bit type to improve speed */ + QDSP_IMAGE_32BIT_DUMMY = 0x10000 +}; + +struct adsp_rtos_mp_mtoa_header_type { + enum rpc_adsp_rtos_mod_status_type event; + uint32_t version; + enum rpc_adsp_rtos_proc_type proc_id; +}; + +/* ADSP RTOS MP Communications - Modem to APP's Event Info*/ +struct adsp_rtos_mp_mtoa_type { + uint32_t module; + uint32_t image; + uint32_t apps_okts; +}; + +/* ADSP RTOS MP Communications - Modem to APP's Init Info */ +#define IMG_MAX 2 +#define ENTRIES_MAX 36 +#define MODULES_MAX 64 +#define QUEUES_MAX 64 + +struct queue_to_offset_type { + uint32_t queue; + uint32_t offset; +}; + +struct mod_to_queue_offsets { + uint32_t module; + uint32_t q_type; + uint32_t q_max_len; +}; + +struct adsp_rtos_mp_mtoa_init_info_type { + uint32_t image_count; + uint32_t num_queue_offsets; + struct queue_to_offset_type queue_offsets_tbl[IMG_MAX][ENTRIES_MAX]; + uint32_t num_task_module_entries; + uint32_t task_to_module_tbl[IMG_MAX][ENTRIES_MAX]; + + uint32_t module_table_size; + uint32_t module_entries[MODULES_MAX]; + uint32_t mod_to_q_entries; + struct mod_to_queue_offsets mod_to_q_tbl[ENTRIES_MAX]; + /* + * queue_offsets[] is to store only queue_offsets + */ + uint32_t queue_offsets[IMG_MAX][QUEUES_MAX]; +}; + +struct adsp_rtos_mp_mtoa_s_type { + struct adsp_rtos_mp_mtoa_header_type mp_mtoa_header; + + union { + struct adsp_rtos_mp_mtoa_init_info_type mp_mtoa_init_packet; + struct adsp_rtos_mp_mtoa_type mp_mtoa_packet; + } adsp_rtos_mp_mtoa_data; +}; + +struct adsp_info { + uint32_t send_irq; + uint32_t read_ctrl; + uint32_t write_ctrl; + + uint32_t max_msg16_size; + uint32_t max_msg32_size; + + uint32_t max_task_id; + uint32_t max_module_id; + uint32_t max_queue_id; + uint32_t max_image_id; + + /* for each image id, a map of queue id to offset */ + uint32_t **queue_offset; + + /* for each image id, a map of task id to module id */ + uint32_t **task_to_module; + + /* for each module id, map of module id to module */ + struct msm_adsp_module **id_to_module; + + uint32_t module_count; + struct adsp_module_info *module; + + /* stats */ + uint32_t events_received; + uint32_t event_backlog_max; + + /* rpc_client for init_info */ + struct adsp_rtos_mp_mtoa_init_info_type *init_info_ptr; + struct adsp_rtos_mp_mtoa_s_type *raw_event; + wait_queue_head_t init_info_wait; + unsigned init_info_state; + + void *handle; + void *cb_handle; +}; + +#define ADSP_STATE_DISABLED 0 +#define ADSP_STATE_ENABLING 1 +#define ADSP_STATE_ENABLED 2 +#define ADSP_STATE_DISABLING 3 +#define ADSP_STATE_INIT_INFO 4 + +struct msm_adsp_module { + struct mutex lock; + const char *name; + unsigned id; + struct adsp_info *info; + + struct msm_adsp_ops *ops; + void *driver_data; + + /* statistics */ + unsigned num_commands; + unsigned num_events; + + wait_queue_head_t state_wait; + unsigned state; + + struct platform_device pdev; + struct clk *clk; + int open_count; + + struct mutex pmem_regions_lock; + struct hlist_head pmem_regions; + int (*verify_cmd) (struct msm_adsp_module*, unsigned int, void *, + size_t); + int (*patch_event) (struct msm_adsp_module*, struct adsp_event *); +}; + +extern void msm_adsp_publish_cdevs(struct msm_adsp_module *, unsigned); +extern int adsp_init_info(struct adsp_info *info); + +/* Value to indicate that a queue is not defined for a particular image */ +#define QDSP_RTOS_NO_QUEUE 0xfffffffe + +/* + * Constants used to communicate with the ADSP RTOS + */ +#define ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_M 0x80000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_NAVAIL_V 0x80000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_MUTEX_AVAIL_V 0x00000000U + +#define ADSP_RTOS_WRITE_CTRL_WORD_CMD_M 0x70000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_CMD_WRITE_REQ_V 0x00000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_CMD_WRITE_DONE_V 0x10000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_CMD_NO_CMD_V 0x70000000U + +#define ADSP_RTOS_WRITE_CTRL_WORD_STATUS_M 0x0E000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_NO_ERR_V 0x00000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_NO_FREE_BUF_V 0x02000000U + +#define ADSP_RTOS_WRITE_CTRL_WORD_KERNEL_FLG_M 0x01000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_HTOD_MSG_WRITE_V 0x00000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_HTOD_CMD_V 0x01000000U + +#define ADSP_RTOS_WRITE_CTRL_WORD_DSP_ADDR_M 0x00FFFFFFU +#define ADSP_RTOS_WRITE_CTRL_WORD_HTOD_CMD_ID_M 0x00FFFFFFU + +/* Combination of MUTEX and CMD bits to check if the DSP is busy */ +#define ADSP_RTOS_WRITE_CTRL_WORD_READY_M 0xF0000000U +#define ADSP_RTOS_WRITE_CTRL_WORD_READY_V 0x70000000U + +/* RTOS to Host processor command mask values */ +#define ADSP_RTOS_READ_CTRL_WORD_FLAG_M 0x80000000U +#define ADSP_RTOS_READ_CTRL_WORD_FLAG_UP_WAIT_V 0x00000000U +#define ADSP_RTOS_READ_CTRL_WORD_FLAG_UP_CONT_V 0x80000000U + +#define ADSP_RTOS_READ_CTRL_WORD_CMD_M 0x60000000U +#define ADSP_RTOS_READ_CTRL_WORD_READ_DONE_V 0x00000000U +#define ADSP_RTOS_READ_CTRL_WORD_READ_REQ_V 0x20000000U +#define ADSP_RTOS_READ_CTRL_WORD_NO_CMD_V 0x60000000U + +/* Combination of FLAG and COMMAND bits to check if MSG ready */ +#define ADSP_RTOS_READ_CTRL_WORD_READY_M 0xE0000000U +#define ADSP_RTOS_READ_CTRL_WORD_READY_V 0xA0000000U +#define ADSP_RTOS_READ_CTRL_WORD_CONT_V 0xC0000000U +#define ADSP_RTOS_READ_CTRL_WORD_DONE_V 0xE0000000U + +#define ADSP_RTOS_READ_CTRL_WORD_STATUS_M 0x18000000U +#define ADSP_RTOS_READ_CTRL_WORD_NO_ERR_V 0x00000000U + +#define ADSP_RTOS_READ_CTRL_WORD_IN_PROG_M 0x04000000U +#define ADSP_RTOS_READ_CTRL_WORD_NO_READ_IN_PROG_V 0x00000000U +#define ADSP_RTOS_READ_CTRL_WORD_READ_IN_PROG_V 0x04000000U + +#define ADSP_RTOS_READ_CTRL_WORD_CMD_TYPE_M 0x03000000U +#define ADSP_RTOS_READ_CTRL_WORD_CMD_TASK_TO_H_V 0x00000000U +#define ADSP_RTOS_READ_CTRL_WORD_CMD_KRNL_TO_H_V 0x01000000U +#define ADSP_RTOS_READ_CTRL_WORD_CMD_H_TO_KRNL_CFM_V 0x02000000U + +#define ADSP_RTOS_READ_CTRL_WORD_DSP_ADDR_M 0x00FFFFFFU + +#define ADSP_RTOS_READ_CTRL_WORD_MSG_ID_M 0x000000FFU +#define ADSP_RTOS_READ_CTRL_WORD_TASK_ID_M 0x0000FF00U + +/* Base address of DSP and DSP hardware registers */ +#define QDSP_RAMC_OFFSET 0x400000 + +#endif diff --git a/arch/arm/mach-msm/qdsp5v2/adsp_driver.c b/arch/arm/mach-msm/qdsp5v2/adsp_driver.c new file mode 100644 index 00000000000..28f9dd61a58 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/adsp_driver.c @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Author: Iliyan Malchev + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "adsp.h" +#include +#include + +struct adsp_pmem_info { + int fd; + void *vaddr; +}; + +struct adsp_pmem_region { + struct hlist_node list; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + struct file *file; +}; + +struct adsp_device { + struct msm_adsp_module *module; + + spinlock_t event_queue_lock; + wait_queue_head_t event_wait; + struct list_head event_queue; + int abort; + + const char *name; + struct device *device; + struct cdev cdev; +}; + +static struct adsp_device *inode_to_device(struct inode *inode); + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = __v >= __r->vaddr && \ + __e <= __r->vaddr + __r->len; \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +static int adsp_pmem_check(struct msm_adsp_module *module, + void *vaddr, unsigned long len) +{ + struct adsp_pmem_region *region_elt; + struct hlist_node *node; + struct adsp_pmem_region t = { .vaddr = vaddr, .len = len }; + + hlist_for_each_entry(region_elt, node, &module->pmem_regions, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + MM_ERR("module %s:" + " region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + module->name, + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, + region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int adsp_pmem_add(struct msm_adsp_module *module, + struct adsp_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct adsp_pmem_region *region; + int rc = -EINVAL; + + mutex_lock(&module->pmem_regions_lock); + region = kmalloc(sizeof(*region), GFP_KERNEL); + if (!region) { + rc = -ENOMEM; + goto end; + } + INIT_HLIST_NODE(®ion->list); + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = adsp_pmem_check(module, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + + hlist_add_head(®ion->list, &module->pmem_regions); +end: + mutex_unlock(&module->pmem_regions_lock); + return rc; +} + +static int adsp_pmem_lookup_vaddr(struct msm_adsp_module *module, void **addr, + unsigned long len, struct adsp_pmem_region **region) +{ + struct hlist_node *node; + void *vaddr = *addr; + struct adsp_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + hlist_for_each_entry(region_elt, node, &module->pmem_regions, list) { + if (vaddr >= region_elt->vaddr && + vaddr < region_elt->vaddr + region_elt->len && + vaddr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + MM_ERR("module %s: " + "multiple hits for vaddr %p, len %ld\n", + module->name, vaddr, len); + hlist_for_each_entry(region_elt, node, + &module->pmem_regions, list) { + if (vaddr >= region_elt->vaddr && + vaddr < region_elt->vaddr + region_elt->len && + vaddr + len <= region_elt->vaddr + region_elt->len) + MM_ERR("%p, %ld --> %p\n", + region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +int adsp_pmem_fixup_kvaddr(struct msm_adsp_module *module, void **addr, + unsigned long *kvaddr, unsigned long len) +{ + struct adsp_pmem_region *region; + void *vaddr = *addr; + unsigned long *paddr = (unsigned long *)addr; + int ret; + + ret = adsp_pmem_lookup_vaddr(module, addr, len, ®ion); + if (ret) { + MM_ERR("not patching %s (paddr & kvaddr)," + " lookup (%p, %ld) failed\n", + module->name, vaddr, len); + return ret; + } + *paddr = region->paddr + (vaddr - region->vaddr); + *kvaddr = region->kvaddr + (vaddr - region->vaddr); + return 0; +} + +int adsp_pmem_fixup(struct msm_adsp_module *module, void **addr, + unsigned long len) +{ + struct adsp_pmem_region *region; + void *vaddr = *addr; + unsigned long *paddr = (unsigned long *)addr; + int ret; + + ret = adsp_pmem_lookup_vaddr(module, addr, len, ®ion); + if (ret) { + MM_ERR("not patching %s, lookup (%p, %ld) failed\n", + module->name, vaddr, len); + return ret; + } + + *paddr = region->paddr + (vaddr - region->vaddr); + return 0; +} + +static int adsp_verify_cmd(struct msm_adsp_module *module, + unsigned int queue_id, void *cmd_data, + size_t cmd_size) +{ + /* call the per module verifier */ + if (module->verify_cmd) + return module->verify_cmd(module, queue_id, cmd_data, + cmd_size); + else + MM_INFO("no packet verifying function " + "for task %s\n", module->name); + return 0; +} + +static long adsp_write_cmd(struct adsp_device *adev, void __user *arg) +{ + struct adsp_command_t cmd; + unsigned char buf[256]; + void *cmd_data; + long rc; + + if (copy_from_user(&cmd, (void __user *)arg, sizeof(cmd))) + return -EFAULT; + + if (cmd.len > 256) { + cmd_data = kmalloc(cmd.len, GFP_USER); + if (!cmd_data) + return -ENOMEM; + } else { + cmd_data = buf; + } + + if (copy_from_user(cmd_data, (void __user *)(cmd.data), cmd.len)) { + rc = -EFAULT; + goto end; + } + + mutex_lock(&adev->module->pmem_regions_lock); + if (adsp_verify_cmd(adev->module, cmd.queue, cmd_data, cmd.len)) { + MM_ERR("module %s: verify failed.\n", adev->module->name); + rc = -EINVAL; + goto end; + } + rc = msm_adsp_write(adev->module, cmd.queue, cmd_data, cmd.len); +end: + mutex_unlock(&adev->module->pmem_regions_lock); + + if (cmd.len > 256) + kfree(cmd_data); + + return rc; +} + +static int adsp_events_pending(struct adsp_device *adev) +{ + unsigned long flags; + int yes; + spin_lock_irqsave(&adev->event_queue_lock, flags); + yes = !list_empty(&adev->event_queue); + spin_unlock_irqrestore(&adev->event_queue_lock, flags); + return yes || adev->abort; +} + +static int adsp_pmem_lookup_paddr(struct msm_adsp_module *module, void **addr, + struct adsp_pmem_region **region) +{ + struct hlist_node *node; + unsigned long paddr = (unsigned long)(*addr); + struct adsp_pmem_region *region_elt; + + hlist_for_each_entry(region_elt, node, &module->pmem_regions, list) { + if (paddr >= region_elt->paddr && + paddr < region_elt->paddr + region_elt->len) { + *region = region_elt; + return 0; + } + } + return -1; +} + +int adsp_pmem_paddr_fixup(struct msm_adsp_module *module, void **addr) +{ + struct adsp_pmem_region *region; + unsigned long paddr = (unsigned long)(*addr); + unsigned long *vaddr = (unsigned long *)addr; + int ret; + + ret = adsp_pmem_lookup_paddr(module, addr, ®ion); + if (ret) { + MM_ERR("not patching %s, paddr %p lookup failed\n", + module->name, vaddr); + return ret; + } + + *vaddr = (unsigned long)region->vaddr + (paddr - region->paddr); + return 0; +} + +static int adsp_patch_event(struct msm_adsp_module *module, + struct adsp_event *event) +{ + /* call the per-module msg verifier */ + if (module->patch_event) + return module->patch_event(module, event); + return 0; +} + +static long adsp_get_event(struct adsp_device *adev, void __user *arg) +{ + unsigned long flags; + struct adsp_event *data = NULL; + struct adsp_event_t evt; + int timeout; + long rc = 0; + + if (copy_from_user(&evt, arg, sizeof(struct adsp_event_t))) + return -EFAULT; + + timeout = (int)evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + adev->event_wait, adsp_events_pending(adev), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + adev->event_wait, adsp_events_pending(adev)); + } + if (rc < 0) + return rc; + + if (adev->abort) + return -ENODEV; + + spin_lock_irqsave(&adev->event_queue_lock, flags); + if (!list_empty(&adev->event_queue)) { + data = list_first_entry(&adev->event_queue, + struct adsp_event, list); + list_del(&data->list); + } + spin_unlock_irqrestore(&adev->event_queue_lock, flags); + + if (!data) + return -EAGAIN; + + /* DSP messages are type 0; they may contain physical addresses */ + if (data->type == 0) + adsp_patch_event(adev->module, data); + + /* map adsp_event --> adsp_event_t */ + if (evt.len < data->size) { + rc = -ETOOSMALL; + goto end; + } + if (data->msg_id != EVENT_MSG_ID) { + if (copy_to_user((void *)(evt.data), data->data.msg16, + data->size)) { + rc = -EFAULT; + goto end; + } + } else { + if (copy_to_user((void *)(evt.data), data->data.msg32, + data->size)) { + rc = -EFAULT; + goto end; + } + } + + evt.type = data->type; /* 0 --> from aDSP, 1 --> from ARM9 */ + evt.msg_id = data->msg_id; + evt.flags = data->is16; + evt.len = data->size; + if (copy_to_user(arg, &evt, sizeof(evt))) + rc = -EFAULT; +end: + kfree(data); + return rc; +} + +static int adsp_pmem_del(struct msm_adsp_module *module) +{ + struct hlist_node *node, *tmp; + struct adsp_pmem_region *region; + + mutex_lock(&module->pmem_regions_lock); + hlist_for_each_safe(node, tmp, &module->pmem_regions) { + region = hlist_entry(node, struct adsp_pmem_region, list); + hlist_del(node); + put_pmem_file(region->file); + kfree(region); + } + mutex_unlock(&module->pmem_regions_lock); + BUG_ON(!hlist_empty(&module->pmem_regions)); + + return 0; +} + +static long adsp_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct adsp_device *adev = filp->private_data; + + switch (cmd) { + case ADSP_IOCTL_ENABLE: + return msm_adsp_enable(adev->module); + + case ADSP_IOCTL_DISABLE: + return msm_adsp_disable(adev->module); + + case ADSP_IOCTL_DISABLE_EVENT_RSP: + return msm_adsp_disable_event_rsp(adev->module); + + case ADSP_IOCTL_DISABLE_ACK: + MM_ERR("ADSP_IOCTL_DISABLE_ACK is not implemented\n"); + break; + + case ADSP_IOCTL_WRITE_COMMAND: + return adsp_write_cmd(adev, (void __user *) arg); + + case ADSP_IOCTL_GET_EVENT: + return adsp_get_event(adev, (void __user *) arg); + + case ADSP_IOCTL_SET_CLKRATE: { + unsigned long clk_rate; + if (copy_from_user(&clk_rate, (void *) arg, sizeof(clk_rate))) + return -EFAULT; + return adsp_set_clkrate(adev->module, clk_rate); + } + + case ADSP_IOCTL_REGISTER_PMEM: { + struct adsp_pmem_info info; + if (copy_from_user(&info, (void *) arg, sizeof(info))) + return -EFAULT; + return adsp_pmem_add(adev->module, &info); + } + + case ADSP_IOCTL_ABORT_EVENT_READ: + adev->abort = 1; + wake_up(&adev->event_wait); + break; + + case ADSP_IOCTL_UNREGISTER_PMEM: + return adsp_pmem_del(adev->module); + + default: + break; + } + return -EINVAL; +} + +static int adsp_release(struct inode *inode, struct file *filp) +{ + struct adsp_device *adev = filp->private_data; + struct msm_adsp_module *module = adev->module; + int rc = 0; + + MM_INFO("release '%s'\n", adev->name); + + /* clear module before putting it to avoid race with open() */ + adev->module = NULL; + + rc = adsp_pmem_del(module); + + msm_adsp_put(module); + return rc; +} + +static void adsp_event(void *driver_data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct adsp_device *adev = driver_data; + struct adsp_event *event; + unsigned long flags; + + if (len > ADSP_EVENT_MAX_SIZE) { + MM_ERR("event too large (%d bytes)\n", len); + return; + } + + event = kmalloc(sizeof(*event), GFP_ATOMIC); + if (!event) { + MM_ERR("cannot allocate buffer\n"); + return; + } + + if (id != EVENT_MSG_ID) { + event->type = 0; + event->is16 = 0; + event->msg_id = id; + event->size = len; + + getevent(event->data.msg16, len); + } else { + event->type = 1; + event->is16 = 1; + event->msg_id = id; + event->size = len; + getevent(event->data.msg32, len); + } + + spin_lock_irqsave(&adev->event_queue_lock, flags); + list_add_tail(&event->list, &adev->event_queue); + spin_unlock_irqrestore(&adev->event_queue_lock, flags); + wake_up(&adev->event_wait); +} + +static struct msm_adsp_ops adsp_ops = { + .event = adsp_event, +}; + +static int adsp_open(struct inode *inode, struct file *filp) +{ + struct adsp_device *adev; + int rc; + + rc = nonseekable_open(inode, filp); + if (rc < 0) + return rc; + + adev = inode_to_device(inode); + if (!adev) + return -ENODEV; + + MM_INFO("open '%s'\n", adev->name); + + rc = msm_adsp_get(adev->name, &adev->module, &adsp_ops, adev); + if (rc) + return rc; + + MM_INFO("opened module '%s' adev %p\n", adev->name, adev); + filp->private_data = adev; + adev->abort = 0; + INIT_HLIST_HEAD(&adev->module->pmem_regions); + mutex_init(&adev->module->pmem_regions_lock); + + return 0; +} + +static unsigned adsp_device_count; +static struct adsp_device *adsp_devices; + +static struct adsp_device *inode_to_device(struct inode *inode) +{ + unsigned n = MINOR(inode->i_rdev); + if (n < adsp_device_count) { + if (adsp_devices[n].device) + return adsp_devices + n; + } + return NULL; +} + +static dev_t adsp_devno; +static struct class *adsp_class; + +static const struct file_operations adsp_fops = { + .owner = THIS_MODULE, + .open = adsp_open, + .unlocked_ioctl = adsp_ioctl, + .release = adsp_release, +}; + +static void adsp_create(struct adsp_device *adev, const char *name, + struct device *parent, dev_t devt) +{ + struct device *dev; + int rc; + + dev = device_create(adsp_class, parent, devt, "%s", name); + if (IS_ERR(dev)) + return; + + init_waitqueue_head(&adev->event_wait); + INIT_LIST_HEAD(&adev->event_queue); + spin_lock_init(&adev->event_queue_lock); + + cdev_init(&adev->cdev, &adsp_fops); + adev->cdev.owner = THIS_MODULE; + + rc = cdev_add(&adev->cdev, devt, 1); + if (rc < 0) { + device_destroy(adsp_class, devt); + } else { + adev->device = dev; + adev->name = name; + } +} + +void msm_adsp_publish_cdevs(struct msm_adsp_module *modules, unsigned n) +{ + int rc; + + adsp_devices = kzalloc(sizeof(struct adsp_device) * n, GFP_KERNEL); + if (!adsp_devices) + return; + + adsp_class = class_create(THIS_MODULE, "adsp"); + if (IS_ERR(adsp_class)) + goto fail_create_class; + + rc = alloc_chrdev_region(&adsp_devno, 0, n, "adsp"); + if (rc < 0) + goto fail_alloc_region; + + adsp_device_count = n; + for (n = 0; n < adsp_device_count; n++) { + adsp_create(adsp_devices + n, + modules[n].name, &modules[n].pdev.dev, + MKDEV(MAJOR(adsp_devno), n)); + } + + return; + +fail_alloc_region: + class_unregister(adsp_class); +fail_create_class: + kfree(adsp_devices); +} diff --git a/arch/arm/mach-msm/qdsp5v2/adsp_info.c b/arch/arm/mach-msm/qdsp5v2/adsp_info.c new file mode 100644 index 00000000000..40263673eb2 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/adsp_info.c @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2008-2010, Code Aurora Forum. 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 "adsp.h" + +/* Firmware modules */ +#define QDSP_MODULE_KERNEL 0x0106dd4e +#define QDSP_MODULE_AFETASK 0x0106dd6f +#define QDSP_MODULE_AUDPLAY0TASK 0x0106dd70 +#define QDSP_MODULE_AUDPLAY1TASK 0x0106dd71 +#define QDSP_MODULE_AUDPPTASK 0x0106dd72 +#define QDSP_MODULE_VIDEOTASK 0x0106dd73 +#define QDSP_MODULE_VIDEO_AAC_VOC 0x0106dd74 +#define QDSP_MODULE_PCM_DEC 0x0106dd75 +#define QDSP_MODULE_AUDIO_DEC_MP3 0x0106dd76 +#define QDSP_MODULE_AUDIO_DEC_AAC 0x0106dd77 +#define QDSP_MODULE_AUDIO_DEC_WMA 0x0106dd78 +#define QDSP_MODULE_HOSTPCM 0x0106dd79 +#define QDSP_MODULE_DTMF 0x0106dd7a +#define QDSP_MODULE_AUDRECTASK 0x0106dd7b +#define QDSP_MODULE_AUDPREPROCTASK 0x0106dd7c +#define QDSP_MODULE_SBC_ENC 0x0106dd7d +#define QDSP_MODULE_VOC_UMTS 0x0106dd9a +#define QDSP_MODULE_VOC_CDMA 0x0106dd98 +#define QDSP_MODULE_VOC_PCM 0x0106dd7f +#define QDSP_MODULE_VOCENCTASK 0x0106dd80 +#define QDSP_MODULE_VOCDECTASK 0x0106dd81 +#define QDSP_MODULE_VOICEPROCTASK 0x0106dd82 +#define QDSP_MODULE_VIDEOENCTASK 0x0106dd83 +#define QDSP_MODULE_VFETASK 0x0106dd84 +#define QDSP_MODULE_WAV_ENC 0x0106dd85 +#define QDSP_MODULE_AACLC_ENC 0x0106dd86 +#define QDSP_MODULE_VIDEO_AMR 0x0106dd87 +#define QDSP_MODULE_VOC_AMR 0x0106dd88 +#define QDSP_MODULE_VOC_EVRC 0x0106dd89 +#define QDSP_MODULE_VOC_13K 0x0106dd8a +#define QDSP_MODULE_VOC_FGV 0x0106dd8b +#define QDSP_MODULE_DIAGTASK 0x0106dd8c +#define QDSP_MODULE_JPEGTASK 0x0106dd8d +#define QDSP_MODULE_LPMTASK 0x0106dd8e +#define QDSP_MODULE_QCAMTASK 0x0106dd8f +#define QDSP_MODULE_MODMATHTASK 0x0106dd90 +#define QDSP_MODULE_AUDPLAY2TASK 0x0106dd91 +#define QDSP_MODULE_AUDPLAY3TASK 0x0106dd92 +#define QDSP_MODULE_AUDPLAY4TASK 0x0106dd93 +#define QDSP_MODULE_GRAPHICSTASK 0x0106dd94 +#define QDSP_MODULE_MIDI 0x0106dd95 +#define QDSP_MODULE_GAUDIO 0x0106dd96 +#define QDSP_MODULE_VDEC_LP_MODE 0x0106dd97 +#define QDSP_MODULE_VIDEO_AAC_VOC_TURBO 0x01089f77 +#define QDSP_MODULE_VIDEO_AMR_TURBO 0x01089f78 +#define QDSP_MODULE_WM_TURBO_MODE 0x01089f79 +#define QDSP_MODULE_VDEC_LP_MODE_TURBO 0x01089f7a +#define QDSP_MODULE_AUDREC0TASK 0x0109696f +#define QDSP_MODULE_AUDREC1TASK 0x01096970 +#define QDSP_MODULE_AUDREC2TASK 0x010a2f59 +#define QDSP_MODULE_MAX 0x7fffffff + + /* DO NOT USE: Force this enum to be a 32bit type to improve speed */ +#define QDSP_MODULE_32BIT_DUMMY 0x10000 + +static uint32_t *qdsp_task_to_module[IMG_MAX]; +static uint32_t *qdsp_queue_offset_table[IMG_MAX]; + +#define QDSP_MODULE(n, clkname, clkrate, verify_cmd_func, patch_event_func) \ + { .name = #n, .pdev_name = "adsp_" #n, .id = QDSP_MODULE_##n, \ + .clk_name = clkname, .clk_rate = clkrate, \ + .verify_cmd = verify_cmd_func, .patch_event = patch_event_func } + +static struct adsp_module_info module_info[] = { + QDSP_MODULE(AUDPLAY0TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPLAY1TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPLAY2TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPLAY3TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPPTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDPREPROCTASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AFETASK , NULL, 0, NULL, NULL), + QDSP_MODULE(AUDREC0TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDREC1TASK, NULL, 0, NULL, NULL), + QDSP_MODULE(AUDREC2TASK, NULL, 0, NULL, NULL), +}; + +int adsp_init_info(struct adsp_info *info) +{ + uint32_t img_num; + + info->send_irq = 0x00c00200; + info->read_ctrl = 0x00400038; + info->write_ctrl = 0x00400034; + + info->max_msg16_size = 193; + info->max_msg32_size = 8; + for (img_num = 0; img_num < IMG_MAX; img_num++) + qdsp_queue_offset_table[img_num] = + &info->init_info_ptr->queue_offsets[img_num][0]; + + for (img_num = 0; img_num < IMG_MAX; img_num++) + qdsp_task_to_module[img_num] = + &info->init_info_ptr->task_to_module_tbl[img_num][0]; + info->max_task_id = ENTRIES_MAX; + info->max_module_id = QDSP_MODULE_MAX - 1; + info->max_queue_id = QDSP_MAX_NUM_QUEUES; + info->max_image_id = 0; + info->queue_offset = qdsp_queue_offset_table; + info->task_to_module = qdsp_task_to_module; + + info->module_count = ARRAY_SIZE(module_info); + info->module = module_info; + return 0; +} diff --git a/arch/arm/mach-msm/qdsp5v2/afe.c b/arch/arm/mach-msm/qdsp5v2/afe.c new file mode 100644 index 00000000000..20c9898ef8f --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/afe.c @@ -0,0 +1,534 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AFE_MAX_TIMEOUT 500 /* 500 ms */ +#define AFE_MAX_CLNT 6 /* 6 HW path defined so far */ +#define GETDEVICEID(x) ((x) - 1) + +struct msm_afe_state { + struct msm_adsp_module *mod; + struct msm_adsp_ops adsp_ops; + struct mutex lock; + u8 in_use; + u8 codec_config[AFE_MAX_CLNT]; + wait_queue_head_t wait; + u8 aux_conf_flag; +}; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_afelb; +#endif + + +static struct msm_afe_state the_afe_state; + +#define afe_send_queue(afe, cmd, len) \ + msm_adsp_write(afe->mod, QDSP_apuAfeQueue, \ + cmd, len) + +static void afe_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct msm_afe_state *afe = data; + + MM_DBG("msg_id %d \n", id); + + switch (id) { + case AFE_APU_MSG_CODEC_CONFIG_ACK: { + struct afe_msg_codec_config_ack afe_ack; + getevent(&afe_ack, AFE_APU_MSG_CODEC_CONFIG_ACK_LEN); + MM_DBG("%s: device_id: %d device activity: %d\n", __func__, + afe_ack.device_id, afe_ack.device_activity); + if (afe_ack.device_activity == AFE_MSG_CODEC_CONFIG_DISABLED) + afe->codec_config[GETDEVICEID(afe_ack.device_id)] = 0; + else + afe->codec_config[GETDEVICEID(afe_ack.device_id)] = + afe_ack.device_activity; + + wake_up(&afe->wait); + break; + } + case AFE_APU_MSG_VOC_TIMING_SUCCESS: + MM_INFO("Received VOC_TIMING_SUCCESS message from AFETASK\n"); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable/disable(audpptask)"); + break; + default: + MM_ERR("unexpected message from afe \n"); + } + + return; +} + +static void afe_dsp_codec_config(struct msm_afe_state *afe, + u8 path_id, u8 enable, struct msm_afe_config *config) +{ + struct afe_cmd_codec_config cmd; + + MM_DBG("%s() %p\n", __func__, config); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AFE_CMD_CODEC_CONFIG_CMD; + cmd.device_id = path_id; + cmd.activity = enable; + if (config) { + MM_DBG("%s: sample_rate %x ch mode %x vol %x\n", + __func__, config->sample_rate, + config->channel_mode, config->volume); + cmd.sample_rate = config->sample_rate; + cmd.channel_mode = config->channel_mode; + cmd.volume = config->volume; + } + afe_send_queue(afe, &cmd, sizeof(cmd)); +} +/* Function is called after afe module been enabled */ +void afe_loopback(int enable) +{ + struct afe_cmd_loopback cmd; + struct msm_afe_state *afe; + + afe = &the_afe_state; + MM_DBG("enable %d\n", enable); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AFE_CMD_LOOPBACK; + if (enable) + cmd.enable_flag = AFE_LOOPBACK_ENABLE_COMMAND; + + afe_send_queue(afe, &cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(afe_loopback); + +void afe_ext_loopback(int enable, int rx_copp_id, int tx_copp_id) +{ + struct afe_cmd_ext_loopback cmd; + struct msm_afe_state *afe; + + afe = &the_afe_state; + MM_DBG("enable %d\n", enable); + if ((rx_copp_id == 0) && (tx_copp_id == 0)) { + afe_loopback(enable); + } else { + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AFE_CMD_EXT_LOOPBACK; + cmd.source_id = tx_copp_id; + cmd.dst_id = rx_copp_id; + if (enable) + cmd.enable_flag = AFE_LOOPBACK_ENABLE_COMMAND; + + afe_send_queue(afe, &cmd, sizeof(cmd)); + } +} +EXPORT_SYMBOL(afe_ext_loopback); + +void afe_device_volume_ctrl(u16 device_id, u16 device_volume) +{ + struct afe_cmd_device_volume_ctrl cmd; + struct msm_afe_state *afe; + + afe = &the_afe_state; + MM_DBG("device 0x%4x volume 0x%4x\n", device_id, device_volume); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AFE_CMD_DEVICE_VOLUME_CTRL; + cmd.device_id = device_id; + cmd.device_volume = device_volume; + afe_send_queue(afe, &cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(afe_device_volume_ctrl); + +int afe_enable(u8 path_id, struct msm_afe_config *config) +{ + struct msm_afe_state *afe = &the_afe_state; + int rc; + + MM_DBG("%s: path %d\n", __func__, path_id); + if ((GETDEVICEID(path_id) < 0) || (GETDEVICEID(path_id) > 5)) { + MM_ERR("Invalid path_id: %d\n", path_id); + return -EINVAL; + } + mutex_lock(&afe->lock); + if (!afe->in_use && !afe->aux_conf_flag) { + /* enable afe */ + rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); + if (rc < 0) { + MM_ERR("%s: failed to get AFETASK module\n", __func__); + goto error_adsp_get; + } + rc = msm_adsp_enable(afe->mod); + if (rc < 0) + goto error_adsp_enable; + } + /* Issue codec config command */ + afe_dsp_codec_config(afe, path_id, 1, config); + rc = wait_event_timeout(afe->wait, + afe->codec_config[GETDEVICEID(path_id)], + msecs_to_jiffies(AFE_MAX_TIMEOUT)); + if (!rc) { + MM_ERR("AFE failed to respond within %d ms\n", AFE_MAX_TIMEOUT); + rc = -ENODEV; + if (!afe->in_use) { + if (!afe->aux_conf_flag || + (afe->aux_conf_flag && + (path_id == AFE_HW_PATH_AUXPCM_RX || + path_id == AFE_HW_PATH_AUXPCM_TX))) { + /* clean up if there is no client */ + msm_adsp_disable(afe->mod); + msm_adsp_put(afe->mod); + afe->aux_conf_flag = 0; + afe->mod = NULL; + } + } + + } else { + rc = 0; + afe->in_use++; + } + + mutex_unlock(&afe->lock); + return rc; + +error_adsp_enable: + msm_adsp_put(afe->mod); + afe->mod = NULL; +error_adsp_get: + mutex_unlock(&afe->lock); + return rc; +} +EXPORT_SYMBOL(afe_enable); + +int afe_config_fm_codec(int fm_enable, uint16_t source) +{ + struct afe_cmd_fm_codec_config cmd; + struct msm_afe_state *afe = &the_afe_state; + int rc = 0; + int i = 0; + unsigned short *ptrmem = (unsigned short *)&cmd; + + MM_INFO(" configure fm codec\n"); + mutex_lock(&afe->lock); + if (!afe->in_use) { + /* enable afe */ + rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); + if (rc < 0) { + MM_ERR("%s: failed to get AFETASK module\n", __func__); + goto error_adsp_get; + } + rc = msm_adsp_enable(afe->mod); + if (rc < 0) + goto error_adsp_enable; + } + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AFE_CMD_FM_RX_ROUTING_CMD; + cmd.enable = fm_enable; + cmd.device_id = source; + + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + afe_send_queue(afe, &cmd, sizeof(cmd)); + + mutex_unlock(&afe->lock); + return rc; +error_adsp_enable: + msm_adsp_put(afe->mod); + afe->mod = NULL; +error_adsp_get: + mutex_unlock(&afe->lock); + return rc; +} +EXPORT_SYMBOL(afe_config_fm_codec); + +int afe_config_fm_volume(uint16_t volume) +{ + struct afe_cmd_fm_volume_config cmd; + struct msm_afe_state *afe = &the_afe_state; + int rc = 0; + + MM_INFO(" configure fm volume\n"); + mutex_lock(&afe->lock); + if (!afe->in_use) { + /* enable afe */ + rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); + if (rc < 0) { + MM_ERR("%s: failed to get AFETASK module\n", __func__); + goto error_adsp_get; + } + rc = msm_adsp_enable(afe->mod); + if (rc < 0) + goto error_adsp_enable; + } + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AFE_CMD_FM_PLAYBACK_VOLUME_CMD; + cmd.volume = volume; + + afe_send_queue(afe, &cmd, sizeof(cmd)); + + mutex_unlock(&afe->lock); + return rc; +error_adsp_enable: + msm_adsp_put(afe->mod); + afe->mod = NULL; +error_adsp_get: + mutex_unlock(&afe->lock); + return rc; +} +EXPORT_SYMBOL(afe_config_fm_volume); + +int afe_config_fm_calibration_gain(uint16_t device_id, + uint16_t calibration_gain) +{ + struct afe_cmd_fm_calibgain_config cmd; + struct msm_afe_state *afe = &the_afe_state; + int rc = 0; + + MM_INFO("Configure for rx device = 0x%4x, gain = 0x%4x\n", device_id, + calibration_gain); + mutex_lock(&afe->lock); + if (!afe->in_use) { + /* enable afe */ + rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); + if (rc < 0) { + MM_ERR("%s: failed to get AFETASK module\n", __func__); + goto error_adsp_get; + } + rc = msm_adsp_enable(afe->mod); + if (rc < 0) + goto error_adsp_enable; + } + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AFE_CMD_FM_CALIBRATION_GAIN_CMD; + cmd.device_id = device_id; + cmd.calibration_gain = calibration_gain; + + afe_send_queue(afe, &cmd, sizeof(cmd)); + + mutex_unlock(&afe->lock); + return rc; +error_adsp_enable: + msm_adsp_put(afe->mod); + afe->mod = NULL; +error_adsp_get: + mutex_unlock(&afe->lock); + return rc; +} +EXPORT_SYMBOL(afe_config_fm_calibration_gain); + +int afe_config_aux_codec(int pcm_ctl_value, int aux_codec_intf_value, + int data_format_pad) +{ + struct afe_cmd_aux_codec_config cmd; + struct msm_afe_state *afe = &the_afe_state; + int rc = 0; + + MM_DBG(" configure aux codec \n"); + mutex_lock(&afe->lock); + if (!afe->in_use && !afe->aux_conf_flag) { + /* enable afe */ + rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); + if (rc < 0) { + MM_ERR("%s: failed to get AFETASK module\n", __func__); + goto error_adsp_get; + } + rc = msm_adsp_enable(afe->mod); + if (rc < 0) + goto error_adsp_enable; + } + afe->aux_conf_flag = 1; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AFE_CMD_AUX_CODEC_CONFIG_CMD; + cmd.dma_path_ctl = 0; + cmd.pcm_ctl = pcm_ctl_value; + cmd.eight_khz_int_mode = 0; + cmd.aux_codec_intf_ctl = aux_codec_intf_value; + cmd.data_format_padding_info = data_format_pad; + + afe_send_queue(afe, &cmd, sizeof(cmd)); + + mutex_unlock(&afe->lock); + return rc; +error_adsp_enable: + msm_adsp_put(afe->mod); + afe->mod = NULL; +error_adsp_get: + mutex_unlock(&afe->lock); + return rc; +} +EXPORT_SYMBOL(afe_config_aux_codec); + +int afe_config_rmc_block(struct acdb_rmc_block *acdb_rmc) +{ + struct afe_cmd_cfg_rmc cmd; + struct msm_afe_state *afe = &the_afe_state; + int rc = 0; + int i = 0; + unsigned short *ptrmem = (unsigned short *)&cmd; + + MM_DBG(" configure rmc block\n"); + mutex_lock(&afe->lock); + if (!afe->in_use && !afe->mod) { + /* enable afe */ + rc = msm_adsp_get("AFETASK", &afe->mod, &afe->adsp_ops, afe); + if (rc < 0) { + MM_DBG("%s: failed to get AFETASK module\n", __func__); + goto error_adsp_get; + } + rc = msm_adsp_enable(afe->mod); + if (rc < 0) + goto error_adsp_enable; + } + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AFE_CMD_CFG_RMC_PARAMS; + + cmd.rmc_mode = acdb_rmc->rmc_enable; + cmd.rmc_ipw_length_ms = acdb_rmc->rmc_ipw_length_ms; + cmd.rmc_peak_length_ms = acdb_rmc->rmc_peak_length_ms; + cmd.rmc_init_pulse_length_ms = acdb_rmc->rmc_init_pulse_length_ms; + cmd.rmc_total_int_length_ms = acdb_rmc->rmc_total_int_length_ms; + cmd.rmc_rampupdn_length_ms = acdb_rmc->rmc_rampupdn_length_ms; + cmd.rmc_delay_length_ms = acdb_rmc->rmc_delay_length_ms; + cmd.rmc_detect_start_threshdb = acdb_rmc->rmc_detect_start_threshdb; + cmd.rmc_init_pulse_threshdb = acdb_rmc->rmc_init_pulse_threshdb; + + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + afe_send_queue(afe, &cmd, sizeof(cmd)); + + mutex_unlock(&afe->lock); + return rc; +error_adsp_enable: + msm_adsp_put(afe->mod); + afe->mod = NULL; +error_adsp_get: + mutex_unlock(&afe->lock); + return rc; +} +EXPORT_SYMBOL(afe_config_rmc_block); + +int afe_disable(u8 path_id) +{ + struct msm_afe_state *afe = &the_afe_state; + int rc; + + mutex_lock(&afe->lock); + + BUG_ON(!afe->in_use); + MM_DBG("%s() path_id:%d codec state:%d\n", __func__, path_id, + afe->codec_config[GETDEVICEID(path_id)]); + afe_dsp_codec_config(afe, path_id, 0, NULL); + rc = wait_event_timeout(afe->wait, + !afe->codec_config[GETDEVICEID(path_id)], + msecs_to_jiffies(AFE_MAX_TIMEOUT)); + if (!rc) { + MM_ERR("AFE failed to respond within %d ms\n", AFE_MAX_TIMEOUT); + rc = -1; + } else + rc = 0; + afe->in_use--; + MM_DBG("%s() in_use:%d \n", __func__, afe->in_use); + if (!afe->in_use) { + msm_adsp_disable(afe->mod); + msm_adsp_put(afe->mod); + afe->aux_conf_flag = 0; + afe->mod = NULL; + } + mutex_unlock(&afe->lock); + return rc; +} +EXPORT_SYMBOL(afe_disable); + + +#ifdef CONFIG_DEBUG_FS +static int afe_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + MM_INFO("debug intf %s\n", (char *) file->private_data); + return 0; +} + +static ssize_t afe_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *lb_str = filp->private_data; + char cmd; + + if (get_user(cmd, ubuf)) + return -EFAULT; + + MM_INFO("%s %c\n", lb_str, cmd); + + if (!strcmp(lb_str, "afe_loopback")) { + switch (cmd) { + case '1': + afe_loopback(1); + break; + case '0': + afe_loopback(0); + break; + } + } + + return cnt; +} + +static const struct file_operations afe_debug_fops = { + .open = afe_debug_open, + .write = afe_debug_write +}; +#endif + +static int __init afe_init(void) +{ + struct msm_afe_state *afe = &the_afe_state; + + MM_INFO("AFE driver init\n"); + + memset(afe, 0, sizeof(struct msm_afe_state)); + afe->adsp_ops.event = afe_dsp_event; + mutex_init(&afe->lock); + init_waitqueue_head(&afe->wait); + +#ifdef CONFIG_DEBUG_FS + debugfs_afelb = debugfs_create_file("afe_loopback", + S_IFREG | S_IWUGO, NULL, (void *) "afe_loopback", + &afe_debug_fops); +#endif + + return 0; +} + +static void __exit afe_exit(void) +{ + MM_INFO("AFE driver exit\n"); +#ifdef CONFIG_DEBUG_FS + if (debugfs_afelb) + debugfs_remove(debugfs_afelb); +#endif + if (the_afe_state.mod) + msm_adsp_put(the_afe_state.mod); + return; +} + +module_init(afe_init); +module_exit(afe_exit); + +MODULE_DESCRIPTION("MSM AFE driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_a2dp_in.c b/arch/arm/mach-msm/qdsp5v2/audio_a2dp_in.c new file mode 100644 index 00000000000..bb3404a7122 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_a2dp_in.c @@ -0,0 +1,977 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * + * sbc/pcm audio input driver + * Based on the pcm input driver in arch/arm/mach-msm/qdsp5v2/audio_pcm_in.c + * + * Copyright (C) 2008 HTC Corporation + * Copyright (C) 2008 Google, Inc. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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 software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (8) +#define FRAME_SIZE (2052 * 2) +#define FRAME_SIZE_SBC (768 * 2) +#define MONO_DATA_SIZE (2048) +#define STEREO_DATA_SIZE (MONO_DATA_SIZE * 2) +#define DMASZ (FRAME_SIZE * FRAME_NUM) + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; + uint32_t frame_num; + uint32_t frame_len; +}; + +struct audio_a2dp_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + atomic_t in_samples; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + wait_queue_head_t wait_enable; + + struct msm_adsp_module *audrec; + + struct audrec_session_info session_info; /*audrec session info*/ + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* 2048 for mono, 4096 for stereo */ + uint32_t enc_type; + struct msm_audio_sbc_enc_config cfg; + + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + uint32_t mode; + + const char *module_name; + unsigned queue_ids; + uint16_t enc_id; /* Session Id */ + + uint16_t source; /* Encoding source bit mask */ + uint32_t device_events; /* device events interested in */ + uint32_t dev_cnt; + spinlock_t dev_lock; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int abort; /* set when error, like sample rate mismatch */ +}; + +static struct audio_a2dp_in the_audio_a2dp_in; + +struct wav_frame { + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_a2dp; + unsigned char raw_bitstream[]; /* samples */ +}; + +struct sbc_frame { + uint16_t bit_rate_msw; + uint16_t bit_rate_lsw; + uint16_t frame_length; + uint16_t frame_num; + unsigned char raw_bitstream[]; /* samples */ +}; + +struct audio_frame { + union { + struct wav_frame wav; + struct sbc_frame sbc; + } a2dp; +} __attribute__((packed)); + +/* Audrec Queue command sent macro's */ +#define audrec_send_bitstreamqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_ids & 0xFFFF0000) >> 16),\ + cmd, len) + +#define audrec_send_audrecqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_ids & 0x0000FFFF),\ + cmd, len) + +/* DSP command send functions */ +static int auda2dp_in_enc_config(struct audio_a2dp_in *audio, int enable); +static int auda2dp_in_param_config(struct audio_a2dp_in *audio); +static int auda2dp_in_mem_config(struct audio_a2dp_in *audio); +static int auda2dp_in_record_config(struct audio_a2dp_in *audio, int enable); +static int auda2dp_dsp_read_buffer(struct audio_a2dp_in *audio, + uint32_t read_cnt); + +static void auda2dp_in_get_dsp_frames(struct audio_a2dp_in *audio); + +static void auda2dp_in_flush(struct audio_a2dp_in *audio); + +static void a2dp_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio_a2dp_in *audio = (struct audio_a2dp_in *) private_data; + unsigned long flags; + + MM_DBG("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: { + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt++; + audio->source |= (0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((audio->running == 1) && (audio->enabled == 1)) + auda2dp_in_record_config(audio, 1); + + break; + } + case AUDDEV_EVT_DEV_RLS: { + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt--; + audio->source &= ~(0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if (!audio->running || !audio->enabled) + break; + + /* Turn of as per source */ + if (audio->source) + auda2dp_in_record_config(audio, 1); + else + /* Turn off all */ + auda2dp_in_record_config(audio, 0); + + break; + } + case AUDDEV_EVT_FREQ_CHG: { + MM_DBG("Encoder Driver got sample rate change event\n"); + MM_DBG("sample rate %d\n", evt_payload->freq_info.sample_rate); + MM_DBG("dev_type %d\n", evt_payload->freq_info.dev_type); + MM_DBG("acdb_dev_id %d\n", evt_payload->freq_info.acdb_dev_id); + if (audio->running == 1) { + /* Stop Recording sample rate does not match + with device sample rate */ + if (evt_payload->freq_info.sample_rate != + audio->samp_rate) { + auda2dp_in_record_config(audio, 0); + audio->abort = 1; + wake_up(&audio->wait); + } + } + break; + } + default: + MM_ERR("wrong event %d\n", evt_id); + break; + } +} + +/* ------------------- dsp preproc event handler--------------------- */ +static void audpreproc_dsp_event(void *data, unsigned id, void *msg) +{ + struct audio_a2dp_in *audio = data; + + switch (id) { + case AUDPREPROC_ERROR_MSG: { + struct audpreproc_err_msg *err_msg = msg; + + MM_ERR("ERROR_MSG: stream id %d err idx %d\n", + err_msg->stream_id, err_msg->aud_preproc_err_idx); + /* Error case */ + wake_up(&audio->wait_enable); + break; + } + case AUDPREPROC_CMD_CFG_DONE_MSG: { + MM_DBG("CMD_CFG_DONE_MSG \n"); + break; + } + case AUDPREPROC_CMD_ENC_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_cfg_done_msg *enc_cfg_msg = msg; + + MM_DBG("CMD_ENC_CFG_DONE_MSG: stream id %d enc type \ + 0x%8x\n", enc_cfg_msg->stream_id, + enc_cfg_msg->rec_enc_type); + /* Encoder enable success */ + if (enc_cfg_msg->rec_enc_type & ENCODE_ENABLE) + auda2dp_in_param_config(audio); + else { /* Encoder disable success */ + audio->running = 0; + auda2dp_in_record_config(audio, 0); + } + break; + } + case AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: { + MM_DBG("CMD_ENC_PARAM_CFG_DONE_MSG \n"); + auda2dp_in_mem_config(audio); + break; + } + case AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: { + MM_DBG("AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG \n"); + wake_up(&audio->wait_enable); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +/* ------------------- dsp audrec event handler--------------------- */ +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_a2dp_in *audio = data; + + switch (id) { + case AUDREC_CMD_MEM_CFG_DONE_MSG: { + MM_DBG("CMD_MEM_CFG_DONE MSG DONE\n"); + audio->running = 1; + if (audio->dev_cnt > 0) + auda2dp_in_record_config(audio, 1); + break; + } + case AUDREC_FATAL_ERR_MSG: { + struct audrec_fatal_err_msg fatal_err_msg; + + getevent(&fatal_err_msg, AUDREC_FATAL_ERR_MSG_LEN); + MM_ERR("FATAL_ERR_MSG: err id %d\n", + fatal_err_msg.audrec_err_id); + /* Error stop the encoder */ + audio->stopped = 1; + wake_up(&audio->wait); + break; + } + case AUDREC_UP_PACKET_READY_MSG: { + struct audrec_up_pkt_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packet_write_cnt_lsw, \ + pkt_ready_msg.audrec_packet_write_cnt_msw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_lsw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_msw); + + auda2dp_in_get_dsp_frames(audio); + break; + } + case ADSP_MESSAGE_ID: { + MM_DBG("Received ADSP event: module audrectask\n"); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +static void auda2dp_in_get_dsp_frames(struct audio_a2dp_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(*frame)); + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (audio->enc_type == ENC_TYPE_WAV) + audio->in[index].size = frame->a2dp.wav.frame_length; + else if (audio->enc_type == ENC_TYPE_SBC) { + audio->in[index].size = frame->a2dp.sbc.frame_length * + frame->a2dp.sbc.frame_num; + audio->in[index].frame_num = frame->a2dp.sbc.frame_num; + audio->in[index].frame_len = frame->a2dp.sbc.frame_length; + } + + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + else + audio->in_count++; + + auda2dp_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + wake_up(&audio->wait); +} + +static struct msm_adsp_ops audrec_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int auda2dp_in_enc_config(struct audio_a2dp_in *audio, int enable) +{ + struct audpreproc_audrec_cmd_enc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + cmd.stream_id = audio->enc_id; + + if (enable) + cmd.audrec_enc_type = audio->enc_type | ENCODE_ENABLE; + else + cmd.audrec_enc_type &= ~(ENCODE_ENABLE); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int auda2dp_in_param_config(struct audio_a2dp_in *audio) +{ + if (audio->enc_type == ENC_TYPE_WAV) { + struct audpreproc_audrec_cmd_parm_cfg_wav cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPREPROC_AUDREC_CMD_PARAM_CFG; + cmd.common.stream_id = audio->enc_id; + + cmd.aud_rec_samplerate_idx = audio->samp_rate; + cmd.aud_rec_stereo_mode = audio->channel_mode; + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); + } else if (audio->enc_type == ENC_TYPE_SBC) { + struct audpreproc_audrec_cmd_parm_cfg_sbc cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPREPROC_AUDREC_CMD_PARAM_CFG; + cmd.common.stream_id = audio->enc_id; + cmd.aud_rec_sbc_enc_param = + (audio->cfg.number_of_blocks << + AUDREC_SBC_ENC_PARAM_NUM_SUB_BLOCKS_MASK) | + (audio->cfg.number_of_subbands << + AUDREC_SBC_ENC_PARAM_NUM_SUB_BANDS_MASK) | + (audio->cfg.mode << + AUDREC_SBC_ENC_PARAM_MODE_MASK) | + (audio->cfg.bit_allocation << + AUDREC_SBC_ENC_PARAM_BIT_ALLOC_MASK); + cmd.aud_rec_sbc_bit_rate_msw = + (audio->cfg.bit_rate & 0xFFFF0000) >> 16; + cmd.aud_rec_sbc_bit_rate_lsw = + (audio->cfg.bit_rate & 0xFFFF); + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); + } + return 0; +} + +/* To Do: msm_snddev_route_enc(audio->enc_id); */ +static int auda2dp_in_record_config(struct audio_a2dp_in *audio, int enable) +{ + struct audpreproc_afe_cmd_audio_record_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG; + cmd.stream_id = audio->enc_id; + if (enable) + cmd.destination_activity = AUDIO_RECORDING_TURN_ON; + else + cmd.destination_activity = AUDIO_RECORDING_TURN_OFF; + + cmd.source_mix_mask = audio->source; + if (audio->enc_id == 2) { + if ((cmd.source_mix_mask & + INTERNAL_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & AUX_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_UL_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_DL_SOURCE_MIX_MASK)) { + cmd.pipe_id = SOURCE_PIPE_1; + } + if (cmd.source_mix_mask & + AUDPP_A2DP_PIPE_SOURCE_MIX_MASK) + cmd.pipe_id |= SOURCE_PIPE_0; + } + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int auda2dp_in_mem_config(struct audio_a2dp_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + int n; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_MEM_CFG_CMD; + cmd.audrec_up_pkt_intm_count = 1; + cmd.audrec_ext_pkt_start_addr_msw = audio->phys >> 16; + cmd.audrec_ext_pkt_start_addr_lsw = audio->phys; + cmd.audrec_ext_pkt_buf_number = FRAME_NUM; + + /* prepare buffer pointers: + * Wav: + * Mono: 1024 samples + 4 halfword header + * Stereo: 2048 samples + 4 halfword header + * SBC: + * 768 + 4 halfword header + */ + if (audio->enc_type == ENC_TYPE_SBC) { + for (n = 0; n < FRAME_NUM; n++) { + audio->in[n].data = data + 4; + data += (4 + (FRAME_SIZE_SBC/2)); + MM_DBG("0x%8x\n", (int)(audio->in[n].data - 8)); + } + } else if (audio->enc_type == ENC_TYPE_WAV) { + for (n = 0; n < FRAME_NUM; n++) { + audio->in[n].data = data + 4; + data += (4 + (audio->channel_mode ? 2048 : 1024)); + MM_DBG("0x%8x\n", (int)(audio->in[n].data - 8)); + } + } + + return audrec_send_audrecqueue(audio, &cmd, sizeof(cmd)); +} + +static int auda2dp_dsp_read_buffer(struct audio_a2dp_in *audio, + uint32_t read_cnt) +{ + struct up_audrec_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = UP_AUDREC_PACKET_EXT_PTR; + cmd.audrec_up_curr_read_count_msw = read_cnt >> 16; + cmd.audrec_up_curr_read_count_lsw = read_cnt; + + return audrec_send_bitstreamqueue(audio, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int auda2dp_in_enable(struct audio_a2dp_in *audio) +{ + if (audio->enabled) + return 0; + + if (audpreproc_enable(audio->enc_id, &audpreproc_dsp_event, audio)) { + MM_ERR("msm_adsp_enable(audpreproc) failed\n"); + return -ENODEV; + } + + if (msm_adsp_enable(audio->audrec)) { + MM_ERR("msm_adsp_enable(audrec) failed\n"); + audpreproc_disable(audio->enc_id, audio); + return -ENODEV; + } + audio->enabled = 1; + auda2dp_in_enc_config(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int auda2dp_in_disable(struct audio_a2dp_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + auda2dp_in_enc_config(audio, 0); + wake_up(&audio->wait); + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + msm_adsp_disable(audio->audrec); + audpreproc_disable(audio->enc_id, audio); + } + return 0; +} + +static void auda2dp_in_flush(struct audio_a2dp_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + for (i = 0; i < FRAME_NUM; i++) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } + MM_DBG("in_bytes %d\n", atomic_read(&audio->in_bytes)); + MM_DBG("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); +} + +/* ------------------- device --------------------- */ +static long auda2dp_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_a2dp_in *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + uint32_t freq; + /* Poll at 48KHz always */ + freq = 48000; + MM_DBG("AUDIO_START\n"); + rc = msm_snddev_request_freq(&freq, audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("sample rate configured %d sample rate requested %d\n", + freq, audio->samp_rate); + if (rc < 0) { + MM_DBG("sample rate can not be set, return code %d\n",\ + rc); + msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + break; + } + /*update aurec session info in audpreproc layer*/ + audio->session_info.session_id = audio->enc_id; + audio->session_info.sampling_freq = audio->samp_rate; + audpreproc_update_audrec_info(&audio->session_info); + rc = auda2dp_in_enable(audio); + if (!rc) { + rc = + wait_event_interruptible_timeout(audio->wait_enable, + audio->running != 0, 1*HZ); + MM_DBG("state %d rc = %d\n", audio->running, rc); + + if (audio->running == 0) { + rc = -ENODEV; + msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + } else + rc = 0; + } + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + rc = auda2dp_in_disable(audio); + rc = msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + audio->stopped = 1; + audio->abort = 0; + break; + } + case AUDIO_FLUSH: { + if (audio->stopped) { + /* Make sure we're stopped and we wake any threads + * that might be blocked holding the read_lock. + * While audio->stopped read threads will always + * exit immediately. + */ + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + auda2dp_in_flush(audio); + mutex_unlock(&audio->read_lock); + } + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + /* Allow only single frame */ + if ((audio->enc_type == ENC_TYPE_SBC) && + (cfg.buffer_size != FRAME_SIZE_SBC)) + rc = -EINVAL; + else + audio->buffer_size = cfg.buffer_size; + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + if (audio->enc_type == ENC_TYPE_SBC) + cfg.buffer_size = FRAME_SIZE_SBC; + else + cfg.buffer_size = MONO_DATA_SIZE; + cfg.buffer_count = FRAME_NUM; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_SBC_ENC_CONFIG: { + if (copy_from_user(&audio->cfg, (void *) arg, + sizeof(audio->cfg))) { + rc = -EFAULT; + break; + } + audio->samp_rate = audio->cfg.sample_rate; + audio->channel_mode = audio->cfg.channels; + audio->enc_type = ENC_TYPE_SBC; + break; + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.channel_count == 1) { + cfg.channel_count = AUDREC_CMD_MODE_MONO; + audio->buffer_size = MONO_DATA_SIZE; + } else if (cfg.channel_count == 2) { + cfg.channel_count = AUDREC_CMD_MODE_STEREO; + audio->buffer_size = STEREO_DATA_SIZE; + } else { + rc = -EINVAL; + break; + } + audio->samp_rate = cfg.sample_rate; + audio->channel_mode = cfg.channel_count; + audio->enc_type = ENC_TYPE_WAV; + break; + } + case AUDIO_GET_SBC_ENC_CONFIG: { + struct msm_audio_sbc_enc_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.bit_allocation = audio->cfg.bit_allocation; + cfg.mode = audio->cfg.mode; + cfg.number_of_subbands = audio->cfg.number_of_subbands; + cfg.number_of_blocks = audio->cfg.number_of_blocks; + cfg.sample_rate = audio->samp_rate; + cfg.channels = audio->channel_mode; + cfg.bit_rate = audio->cfg.bit_rate; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_count = FRAME_NUM; + cfg.sample_rate = audio->samp_rate; + if (audio->channel_mode == AUDREC_CMD_MODE_MONO) { + cfg.channel_count = 1; + cfg.buffer_size = MONO_DATA_SIZE; + } else { + cfg.channel_count = 2; + cfg.buffer_size = STEREO_DATA_SIZE; + } + cfg.type = ENC_TYPE_WAV; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &audio->enc_id, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t auda2dp_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_a2dp_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + uint32_t f_len = 0, f_num = 0; + int i = 0; + + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped || + audio->abort); + + if (rc < 0) + break; + + if (audio->stopped && !audio->in_count) { + MM_DBG("Driver in stop state, No more buffer to read"); + rc = 0;/* End of File */ + break; + } + + if (audio->abort) { + rc = -EPERM; /* Not permitted due to abort */ + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + if (count >= size) { + if (audio->enc_type == ENC_TYPE_SBC && + (audio->in[index].frame_len % 2)) { + f_len = audio->in[index].frame_len; + f_num = audio->in[index].frame_num; + for (i = 0; i < f_num; i++) { + if (copy_to_user(&buf[i * f_len], + (uint8_t *) (data + (i * (f_len + 1))), + f_len)) { + rc = -EFAULT; + break; + } + } + } else { + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is + * invalid and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + } else { + MM_ERR("short read\n"); + break; + } + } + mutex_unlock(&audio->read_lock); + if (buf > start) + return buf - start; + + return rc; +} + +static ssize_t auda2dp_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + return -EINVAL; +} + +static int auda2dp_in_release(struct inode *inode, struct file *file) +{ + struct audio_a2dp_in *audio = file->private_data; + + mutex_lock(&audio->lock); + /* with draw frequency for session + incase not stopped the driver */ + msm_snddev_withdraw_freq(audio->enc_id, SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, audio->enc_id); + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + auda2dp_in_disable(audio); + auda2dp_in_flush(audio); + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + audio->audrec = NULL; + audio->opened = 0; + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + audio->data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +static int auda2dp_in_open(struct inode *inode, struct file *file) +{ + struct audio_a2dp_in *audio = &the_audio_a2dp_in; + int rc; + int encid; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->phys); + goto done; + } + } else { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + goto done; + } + MM_DBG("Memory addr = 0x%8x phy addr = 0x%8x\n",\ + (int) audio->data, (int) audio->phys); + + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + rc = -EACCES; + MM_ERR("Non tunnel encoding is not supported\n"); + goto done; + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_TUNNEL; + MM_DBG("Opened for Tunnel mode encoding\n"); + } else { + rc = -EACCES; + goto done; + } + /* Settings will be re-config at AUDIO_SET_CONFIG/SBC_ENC_CONFIG, + * but at least we need to have initial config + */ + audio->channel_mode = AUDREC_CMD_MODE_MONO; + audio->buffer_size = FRAME_SIZE_SBC; + audio->samp_rate = 48000; + audio->enc_type = ENC_TYPE_SBC | audio->mode; + audio->cfg.bit_allocation = AUDIO_SBC_BA_SNR; + audio->cfg.mode = AUDIO_SBC_MODE_JSTEREO; + audio->cfg.number_of_subbands = AUDIO_SBC_BANDS_8; + audio->cfg.number_of_blocks = AUDIO_SBC_BLOCKS_16; + audio->cfg.bit_rate = 320000; /* max 512kbps(mono), 320kbs(others) */ + + encid = audpreproc_aenc_alloc(audio->enc_type, &audio->module_name, + &audio->queue_ids); + if (encid < 0) { + MM_ERR("No free encoder available\n"); + rc = -ENODEV; + goto done; + } + audio->enc_id = encid; + + rc = msm_adsp_get(audio->module_name, &audio->audrec, + &audrec_adsp_ops, audio); + + if (rc) { + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + audio->stopped = 0; + audio->source = 0; + audio->abort = 0; + auda2dp_in_flush(audio); + audio->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_FREQ_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_ENC, audio->enc_id, + a2dp_in_listener, (void *) audio); + if (rc) { + MM_ERR("failed to register device event listener\n"); + goto evt_error; + } + file->private_data = audio; + audio->opened = 1; + rc = 0; +done: + mutex_unlock(&audio->lock); + return rc; +evt_error: + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_a2dp_in_fops = { + .owner = THIS_MODULE, + .open = auda2dp_in_open, + .release = auda2dp_in_release, + .read = auda2dp_in_read, + .write = auda2dp_in_write, + .unlocked_ioctl = auda2dp_in_ioctl, +}; + +struct miscdevice audio_a2dp_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_a2dp_in", + .fops = &audio_a2dp_in_fops, +}; + +static int __init auda2dp_in_init(void) +{ + mutex_init(&the_audio_a2dp_in.lock); + mutex_init(&the_audio_a2dp_in.read_lock); + spin_lock_init(&the_audio_a2dp_in.dsp_lock); + spin_lock_init(&the_audio_a2dp_in.dev_lock); + init_waitqueue_head(&the_audio_a2dp_in.wait); + init_waitqueue_head(&the_audio_a2dp_in.wait_enable); + return misc_register(&audio_a2dp_in_misc); +} + +device_initcall(auda2dp_in_init); + +MODULE_DESCRIPTION("MSM SBC encode driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_aac.c b/arch/arm/mach-msm/qdsp5v2/audio_aac.c new file mode 100644 index 00000000000..d75dc92ccca --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_aac.c @@ -0,0 +1,2025 @@ +/* + * aac audio decoder device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSZ 32768 +#define DMASZ (BUFSZ * 2) +#define BUFSZ_MIN 4096 +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_AAC 5 + +#define PCM_BUFSZ_MIN 9600 /* Hold one stereo AAC frame */ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDAAC_METAFIELD_MASK 0xFFFF0000 +#define AUDAAC_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDAAC_EOS_FLG_MASK 0x01 +#define AUDAAC_EOS_NONE 0x0 /* No EOS detected */ +#define AUDAAC_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDAAC_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +#define BITSTREAM_ERROR_THRESHOLD_VALUE 0x1 /* DEFAULT THRESHOLD VALUE */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audaac_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audaac_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + struct msm_audio_aac_config aac_config; + + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audaac_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + uint32_t device_events; + + struct msm_audio_bitstream_info stream_info; + struct msm_audio_bitstream_error_info bitstream_error_info; + uint32_t bitstream_error_threshold_value; + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audplay_error_threshold_config(struct audio *audio); +static void audplay_config_hostpcm(struct audio *audio); +static void audplay_buffer_refresh(struct audio *audio); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audaac_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +static void aac_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + default: + MM_ERR(":ERROR:wrong event\n"); + break; + } +} +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audio_update_pcm_buf_entry(struct audio *audio, uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audplay_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + +} + +static void audaac_bitstream_error_info(struct audio *audio, uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload e_payload; + + if (payload[0] != AUDDEC_DEC_AAC) { + MM_ERR("Unexpected bitstream error info from DSP:\ + Invalid decoder\n"); + return; + } + + /* get stream info from DSP msg */ + spin_lock_irqsave(&audio->dsp_lock, flags); + + audio->bitstream_error_info.dec_id = payload[0]; + audio->bitstream_error_info.err_msg_indicator = payload[1]; + audio->bitstream_error_info.err_type = payload[2]; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + MM_ERR("bit_stream_error_type=%d error_count=%d\n", + audio->bitstream_error_info.err_type, (0x0000FFFF & + audio->bitstream_error_info.err_msg_indicator)); + + /* send event to ARM to notify error info coming */ + e_payload.error_info = audio->bitstream_error_info; + audaac_post_event(audio, AUDIO_EVENT_BITSTREAM_ERROR_INFO, e_payload); +} + +static void audaac_update_stream_info(struct audio *audio, uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload e_payload; + + /* get stream info from DSP msg */ + spin_lock_irqsave(&audio->dsp_lock, flags); + + audio->stream_info.codec_type = AUDIO_CODEC_TYPE_AAC; + audio->stream_info.chan_info = (0x0000FFFF & payload[1]); + audio->stream_info.sample_rate = (0x0000FFFF & payload[2]); + audio->stream_info.bit_stream_info = (0x0000FFFF & payload[3]); + audio->stream_info.bit_rate = payload[4]; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + MM_DBG("chan_info=%d, sample_rate=%d, bit_stream_info=%d\n", + audio->stream_info.chan_info, + audio->stream_info.sample_rate, + audio->stream_info.bit_stream_info); + + /* send event to ARM to notify steam info coming */ + e_payload.stream_info = audio->stream_info; + audaac_post_event(audio, AUDIO_EVENT_STREAM_INFO, e_payload); +} +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audplay_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audio_update_pcm_buf_entry(audio, msg); + break; + + case AUDPLAY_UP_STREAM_INFO: + if ((msg[1] & AUDPLAY_STREAM_INFO_MSG_MASK) == + AUDPLAY_STREAM_INFO_MSG_MASK) { + audaac_bitstream_error_info(audio, msg); + } else { + audaac_update_stream_info(audio, msg); + } + break; + + case AUDPLAY_UP_OUTPORT_FLUSH_ACK: + MM_DBG("OUTPORT_FLUSH_ACK\n"); + audio->rflush = 0; + wake_up(&audio->read_wait); + if (audio->pcm_feedback) + audplay_buffer_refresh(audio); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status: sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + /* send mixer command */ + audpp_route_stream(audio->dec_id, + audio->source); + if (audio->pcm_feedback) { + audplay_error_threshold_config(audio); + audplay_config_hostpcm(audio); + audplay_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status \n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audplay_buffer_refresh(audio); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AUDPP_MSG_AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_aac = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id,\ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_AAC; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_aac cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_AAC_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + cmd.format = audio->aac_config.format; + cmd.audio_object = audio->aac_config.audio_object; + cmd.ep_config = audio->aac_config.ep_config; + cmd.aac_section_data_resilience_flag = + audio->aac_config.aac_section_data_resilience_flag; + cmd.aac_scalefactor_data_resilience_flag = + audio->aac_config.aac_scalefactor_data_resilience_flag; + cmd.aac_spectral_data_resilience_flag = + audio->aac_config.aac_spectral_data_resilience_flag; + cmd.sbr_on_flag = audio->aac_config.sbr_on_flag; + cmd.sbr_ps_on_flag = audio->aac_config.sbr_ps_on_flag; + cmd.channel_configuration = audio->aac_config.channel_configuration; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDAAC_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audplay_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + /* AAC frame size */ + refresh_cmd.buf0_length = audio->in[audio->fill_next].size - + (audio->in[audio->fill_next].size % 1024) + + (audio->mfield ? 24 : 0); + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audplay_outport_flush(struct audio *audio) +{ + struct audplay_cmd_outport_flush op_flush_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + op_flush_cmd.cmd_id = AUDPLAY_CMD_OUTPORT_FLUSH; + (void)audplay_send_queue0(audio, &op_flush_cmd, sizeof(op_flush_cmd)); +} + +static void audplay_error_threshold_config(struct audio *audio) +{ + union audplay_cmd_channel_info ch_cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + ch_cfg_cmd.thr_update.cmd_id = AUDPLAY_CMD_CHANNEL_INFO; + ch_cfg_cmd.thr_update.threshold_update = AUDPLAY_ERROR_THRESHOLD_ENABLE; + ch_cfg_cmd.thr_update.threshold_value = + audio->bitstream_error_threshold_value; + (void)audplay_send_queue0(audio, &ch_cfg_cmd, sizeof(ch_cfg_cmd)); +} + +static void audplay_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static int audaac_validate_usr_config(struct msm_audio_aac_config *config) +{ + int ret_val = -1; + + if (config->format != AUDIO_AAC_FORMAT_ADTS && + config->format != AUDIO_AAC_FORMAT_RAW && + config->format != AUDIO_AAC_FORMAT_PSUEDO_RAW && + config->format != AUDIO_AAC_FORMAT_LOAS) + goto done; + + if (config->audio_object != AUDIO_AAC_OBJECT_LC && + config->audio_object != AUDIO_AAC_OBJECT_LTP && + config->audio_object != AUDIO_AAC_OBJECT_BSAC && + config->audio_object != AUDIO_AAC_OBJECT_ERLC) + goto done; + + if (config->audio_object == AUDIO_AAC_OBJECT_ERLC) { + if (config->ep_config > 3) + goto done; + if (config->aac_scalefactor_data_resilience_flag != + AUDIO_AAC_SCA_DATA_RES_OFF && + config->aac_scalefactor_data_resilience_flag != + AUDIO_AAC_SCA_DATA_RES_ON) + goto done; + if (config->aac_section_data_resilience_flag != + AUDIO_AAC_SEC_DATA_RES_OFF && + config->aac_section_data_resilience_flag != + AUDIO_AAC_SEC_DATA_RES_ON) + goto done; + if (config->aac_spectral_data_resilience_flag != + AUDIO_AAC_SPEC_DATA_RES_OFF && + config->aac_spectral_data_resilience_flag != + AUDIO_AAC_SPEC_DATA_RES_ON) + goto done; + } else { + config->aac_section_data_resilience_flag = + AUDIO_AAC_SEC_DATA_RES_OFF; + config->aac_scalefactor_data_resilience_flag = + AUDIO_AAC_SCA_DATA_RES_OFF; + config->aac_spectral_data_resilience_flag = + AUDIO_AAC_SPEC_DATA_RES_OFF; + } + +#ifndef CONFIG_AUDIO_AAC_PLUS + if (AUDIO_AAC_SBR_ON_FLAG_OFF != config->sbr_on_flag) + goto done; +#else + if (config->sbr_on_flag != AUDIO_AAC_SBR_ON_FLAG_OFF && + config->sbr_on_flag != AUDIO_AAC_SBR_ON_FLAG_ON) + goto done; +#endif + +#ifndef CONFIG_AUDIO_ENHANCED_AAC_PLUS + if (AUDIO_AAC_SBR_PS_ON_FLAG_OFF != config->sbr_ps_on_flag) + goto done; +#else + if (config->sbr_ps_on_flag != AUDIO_AAC_SBR_PS_ON_FLAG_OFF && + config->sbr_ps_on_flag != AUDIO_AAC_SBR_PS_ON_FLAG_ON) + goto done; +#endif + + if (config->dual_mono_mode > AUDIO_AAC_DUAL_MONO_PL_SR) + goto done; + + if (config->channel_configuration > 2) + goto done; + + ret_val = 0; + done: + return ret_val; +} + +static void audio_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + +} + +static int audaac_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audaac_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audaac_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audaac_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audaac_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audaac_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audaac_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audaac_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH running=%d\n", audio->running); + audio->rflush = 1; + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + + case AUDIO_OUTPORT_FLUSH: + MM_DBG("AUDIO_OUTPORT_FLUSH\n"); + audio->rflush = 1; + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); + audplay_outport_flush(audio); + rc = wait_event_interruptible(audio->read_wait, + !audio->rflush); + if (rc < 0) { + MM_ERR("AUDPLAY_OUTPORT_FLUSH interrupted\n"); + rc = -EINTR; + } + break; + + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + + if (config.channel_count == 1) { + config.channel_count = + AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = + AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + audio->mfield = config.meta_field; + rc = 0; + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == + AUDPP_CMD_PCM_INTF_MONO_V) { + config.channel_count = 1; + } else { + config.channel_count = 2; + } + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_AAC_CONFIG:{ + if (copy_to_user((void *)arg, &audio->aac_config, + sizeof(audio->aac_config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_AAC_CONFIG:{ + struct msm_audio_aac_config usr_config; + + if (copy_from_user + (&usr_config, (void *)arg, + sizeof(usr_config))) { + rc = -EFAULT; + break; + } + + if (audaac_validate_usr_config(&usr_config) == 0) { + audio->aac_config = usr_config; + rc = 0; + } else + rc = -EINVAL; + + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if (config.pcm_feedback) { + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; + } + rc = 0; + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + case AUDIO_GET_STREAM_INFO:{ + if (audio->stream_info.sample_rate == 0) { + /* haven't received DSP stream event, + the stream info is not updated */ + rc = -EPERM; + break; + } + if (copy_to_user((void *)arg, &audio->stream_info, + sizeof(struct msm_audio_bitstream_info))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_BITSTREAM_ERROR_INFO:{ + if ((audio->bitstream_error_info.err_msg_indicator & + AUDPLAY_STREAM_INFO_MSG_MASK) == + AUDPLAY_STREAM_INFO_MSG_MASK) { + /* haven't received bitstream error info event, + the bitstream error info is not updated */ + rc = -EPERM; + break; + } + if (copy_to_user((void *)arg, &audio->bitstream_error_info, + sizeof(struct msm_audio_bitstream_error_info))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + case AUDIO_SET_ERR_THRESHOLD_VALUE: + if (copy_from_user(&audio->bitstream_error_threshold_value, + (void *)arg, sizeof(uint32_t))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} +/* Only useful in tunnel-mode */ +static int audaac_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audplay_send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("to read %d \n", count); + while (count > 0) { + rc = wait_event_interruptible_timeout(audio->read_wait, + (audio->in[audio->read_next]. + used > 0) || (audio->stopped) + || (audio->rflush), + msecs_to_jiffies(MSM_AUD_BUFFER_UPDATE_WAIT_MS)); + + if (rc == 0) { + rc = -ETIMEDOUT; + break; + } else if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver + does not know frame size, read count must be greater + or equal to size of PCM samples */ + MM_DBG("no partial frame done reading\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x\n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + /* + * Force to exit while loop + * to prevent output thread + * sleep too long if data is not + * ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audplay_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audaac_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + char *buf_ptr; + int rc = 0; + + MM_DBG("signal input EOS reserved=%d\n", audio->reserved); + if (audio->reserved) { + MM_DBG("Pass reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + audio->reserved = 0; + frame->used = 2; + audplay_send_data(audio, 0); + } + MM_DBG("Now signal input EOS after reserved bytes %d %d %d\n", + audio->out[0].used, audio->out[1].used, audio->out_needed); + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audplay_send_data(audio, 0); +done: + return rc; +} +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDAAC_EOS_NONE; + unsigned dsize; + + unsigned short mfield_size = 0; + MM_DBG("cnt=%d\n", count); + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDAAC_EOS_FLG_OFFSET] & + AUDAAC_EOS_FLG_MASK) { + MM_DBG("eos set\n"); + eos_condition = AUDAAC_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDAAC_EOS_FLG_OFFSET] &= + ~AUDAAC_EOS_FLG_MASK; + } + /* Check EOS to see if */ + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", + audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + (frame->size - mfield_size) - 1 : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audplay_send_data(audio, 0); + } + } + MM_DBG("eos_condition %x buf[0x%x] start[0x%x]\n", eos_condition, + (int) buf, (int) start); + if (eos_condition == AUDAAC_EOS_SET) + rc = audaac_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audio_disable(audio); + audio_flush(audio); + audio_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audaac_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audaac_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audaac_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audaac_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audaac_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audaac_suspend(struct early_suspend *h) +{ + struct audaac_suspend_ctl *ctl = + container_of(h, struct audaac_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audaac_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audaac_resume(struct early_suspend *h) +{ + struct audaac_suspend_ctl *ctl = + container_of(h, struct audaac_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audaac_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audaac_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audaac_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].used %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audaac_debug_fops = { + .read = audaac_debug_read, + .open = audaac_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, index, offset = 0; + unsigned pmem_sz = DMASZ; + struct audaac_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_aac_" + 5]; +#endif + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_AAC; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d \n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr \ + 0x%08x\n", audio->phys, (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + + audio->read_phys = pmem_kalloc(PCM_BUFSZ_MIN * PCM_BUF_MAX_COUNT, + PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + MM_ERR("could not allocate read buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + audio->read_data = ioremap(audio->read_phys, + PCM_BUFSZ_MIN * PCM_BUF_MAX_COUNT); + if (!audio->read_data) { + MM_ERR("could not allocate read buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + iounmap(audio->data); + pmem_kfree(audio->phys); + pmem_kfree(audio->read_phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("read buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->read_phys, (int)audio->read_data); + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_aac, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + init_waitqueue_head(&audio->avsync_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = audio->out_dma_sz >> 1; + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + + audio->pcm_buf_count = PCM_BUF_MAX_COUNT; + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) { + audio->in[index].data = audio->read_data + offset; + audio->in[index].addr = audio->read_phys + offset; + audio->in[index].size = PCM_BUFSZ_MIN; + audio->in[index].used = 0; + offset += PCM_BUFSZ_MIN; + } + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + audio->aac_config.format = AUDIO_AAC_FORMAT_ADTS; + audio->aac_config.audio_object = AUDIO_AAC_OBJECT_LC; + audio->aac_config.ep_config = 0; + audio->aac_config.aac_section_data_resilience_flag = + AUDIO_AAC_SEC_DATA_RES_OFF; + audio->aac_config.aac_scalefactor_data_resilience_flag = + AUDIO_AAC_SCA_DATA_RES_OFF; + audio->aac_config.aac_spectral_data_resilience_flag = + AUDIO_AAC_SPEC_DATA_RES_OFF; +#ifdef CONFIG_AUDIO_AAC_PLUS + audio->aac_config.sbr_on_flag = AUDIO_AAC_SBR_ON_FLAG_ON; +#else + audio->aac_config.sbr_on_flag = AUDIO_AAC_SBR_ON_FLAG_OFF; +#endif +#ifdef CONFIG_AUDIO_ENHANCED_AAC_PLUS + audio->aac_config.sbr_ps_on_flag = AUDIO_AAC_SBR_PS_ON_FLAG_ON; +#else + audio->aac_config.sbr_ps_on_flag = AUDIO_AAC_SBR_PS_ON_FLAG_OFF; +#endif + audio->aac_config.dual_mono_mode = AUDIO_AAC_DUAL_MONO_PL_SR; + audio->aac_config.channel_configuration = 2; + audio->vol_pan.volume = 0x2000; + audio->bitstream_error_threshold_value = + BITSTREAM_ERROR_THRESHOLD_VALUE; + + audio_flush(audio); + + file->private_data = audio; + audio->opened = 1; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + aac_listner, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listner\n", __func__); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_aac_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, + &audaac_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audaac_resume; + audio->suspend_ctl.node.suspend = audaac_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (index = 0; index < AUDAAC_EVENT_NUM; index++) { + e_node = kmalloc(sizeof(struct audaac_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } + memset(&audio->stream_info, 0, sizeof(struct msm_audio_bitstream_info)); + memset(&audio->bitstream_error_info, 0, + sizeof(struct msm_audio_bitstream_info)); +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_aac_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audaac_fsync +}; + +struct miscdevice audio_aac_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac", + .fops = &audio_aac_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_aac_misc); +} + +static void __exit audio_exit(void) +{ + misc_deregister(&audio_aac_misc); +} + +module_init(audio_init); +module_exit(audio_exit); + +MODULE_DESCRIPTION("MSM AAC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_aac_in.c b/arch/arm/mach-msm/qdsp5v2/audio_aac_in.c new file mode 100644 index 00000000000..610dbae346c --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_aac_in.c @@ -0,0 +1,1465 @@ +/* + * aac audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (8) +#define FRAME_SIZE (772 * 2) /* 1536 bytes data */ +#define NT_FRAME_SIZE (780 * 2) /* 1536 bytes data + 24 meta field*/ +#define AAC_FRAME_SIZE 1536 +#define DMASZ (FRAME_SIZE * FRAME_NUM) +#define OUT_FRAME_NUM (2) +#define META_OUT_SIZE (24) +#define META_IN_SIZE (14) +#define OUT_BUFFER_SIZE (32 * 1024 + META_OUT_SIZE) +#define BUFFER_SIZE (OUT_BUFFER_SIZE * OUT_FRAME_NUM) + +#define AUDPREPROC_AAC_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDPREPROC_AAC_EOS_FLG_MASK 0x01 +#define AUDPREPROC_AAC_EOS_NONE 0x0 /* No EOS detected */ +#define AUDPREPROC_AAC_EOS_SET 0x1 /* EOS set in meta field */ + +#define PCM_CONFIG_UPDATE_FLAG_ENABLE -1 +#define PCM_CONFIG_UPDATE_FLAG_DISABLE 0 + +#define ENABLE_FLAG_VALUE -1 +#define DISABLE_FLAG_VALUE 0 + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; + uint32_t used; + uint32_t mfield_sz; +}; + +struct audio_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + atomic_t in_samples; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + wait_queue_head_t wait_enable; + /*write section*/ + struct buffer out[OUT_FRAME_NUM]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + uint32_t out_count; + + struct mutex write_lock; + wait_queue_head_t write_wait; + int32_t out_phys; /* physical address of write buffer */ + char *out_data; + int mfield; /* meta field embedded in data */ + int wflush; /*write flush */ + int rflush; /*read flush*/ + int out_frame_cnt; + + struct msm_adsp_module *audrec; + + /* configuration to use on next enable */ + uint32_t buffer_size; /* Frame size (36 bytes) */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t bit_rate; /* bit rate for AAC */ + uint32_t record_quality; /* record quality (bits/sample/channel) */ + uint32_t enc_type; + + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + uint32_t mode; + uint32_t eos_ack; + uint32_t flush_ack; + + const char *module_name; + unsigned queue_ids; + uint16_t enc_id; + + struct audrec_session_info session_info; /*audrec session info*/ + uint16_t source; /* Encoding source bit mask */ + uint32_t device_events; /* device events interested in */ + uint32_t dev_cnt; + spinlock_t dev_lock; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int abort; /* set when error, like sample rate mismatch */ +}; + +struct audio_frame { + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + unsigned char raw_bitstream[]; /* samples */ +} __attribute__((packed)); + +struct audio_frame_nt { + uint16_t metadata_len; + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + uint16_t reserved; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; + unsigned char raw_bitstream[]; /* samples */ +} __attribute__((packed)); + +struct aac_encoded_meta_in { + uint16_t metadata_len; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; +}; + +/* Audrec Queue command sent macro's */ +#define audrec_send_bitstreamqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_ids & 0xFFFF0000) >> 16),\ + cmd, len) + +#define audrec_send_audrecqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_ids & 0x0000FFFF),\ + cmd, len) + +/* DSP command send functions */ +static int audaac_in_enc_config(struct audio_in *audio, int enable); +static int audaac_in_param_config(struct audio_in *audio); +static int audaac_in_mem_config(struct audio_in *audio); +static int audaac_in_record_config(struct audio_in *audio, int enable); +static int audaac_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt); + +static void audaac_in_get_dsp_frames(struct audio_in *audio); +static int audpcm_config(struct audio_in *audio); +static void audaac_out_flush(struct audio_in *audio); +static int audpreproc_cmd_cfg_routing_mode(struct audio_in *audio); +static void audpreproc_pcm_send_data(struct audio_in *audio, unsigned needed); +static void audaac_nt_in_get_dsp_frames(struct audio_in *audio); + +static void audaac_in_flush(struct audio_in *audio); + +static void aac_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio_in *audio = (struct audio_in *) private_data; + unsigned long flags; + + MM_DBG("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: { + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt++; + audio->source |= (0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((audio->running == 1) && (audio->enabled == 1) && + (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) + audaac_in_record_config(audio, 1); + + break; + } + case AUDDEV_EVT_DEV_RLS: { + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt--; + audio->source &= ~(0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((!audio->running) || (!audio->enabled)) + break; + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + /* Turn of as per source */ + if (audio->source) + audaac_in_record_config(audio, 1); + else + /* Turn off all */ + audaac_in_record_config(audio, 0); + } + break; + } + case AUDDEV_EVT_FREQ_CHG: { + MM_DBG("Encoder Driver got sample rate change event\n"); + MM_DBG("sample rate %d\n", evt_payload->freq_info.sample_rate); + MM_DBG("dev_type %d\n", evt_payload->freq_info.dev_type); + MM_DBG("acdb_dev_id %d\n", evt_payload->freq_info.acdb_dev_id); + if ((audio->running == 1) && (audio->enabled == 1)) { + /* Stop Recording sample rate does not match + with device sample rate */ + if (evt_payload->freq_info.sample_rate != + audio->samp_rate) { + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + audaac_in_record_config(audio, 0); + audio->abort = 1; + wake_up(&audio->wait); + } + } + break; + } + default: + MM_ERR("wrong event %d\n", evt_id); + break; + } +} + +/* Convert Bit Rate to Record Quality field of DSP */ +static unsigned int bitrate_to_record_quality(unsigned int sample_rate, + unsigned int channel, unsigned int bit_rate) { + unsigned int temp; + + temp = sample_rate * channel; + MM_DBG(" sample rate * channel = %d \n", temp); + /* To represent in Q12 fixed format */ + temp = (bit_rate * 4096) / temp; + MM_DBG(" Record Quality = 0x%8x \n", temp); + return temp; +} + +/* ------------------- dsp preproc event handler--------------------- */ +static void audpreproc_dsp_event(void *data, unsigned id, void *msg) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDPREPROC_ERROR_MSG: { + struct audpreproc_err_msg *err_msg = msg; + + MM_ERR("ERROR_MSG: stream id %d err idx %d\n", + err_msg->stream_id, err_msg->aud_preproc_err_idx); + /* Error case */ + wake_up(&audio->wait_enable); + break; + } + case AUDPREPROC_CMD_CFG_DONE_MSG: { + MM_DBG("CMD_CFG_DONE_MSG \n"); + break; + } + case AUDPREPROC_CMD_ENC_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_cfg_done_msg *enc_cfg_msg = msg; + + MM_DBG("CMD_ENC_CFG_DONE_MSG: stream id %d enc type \ + 0x%8x\n", enc_cfg_msg->stream_id, + enc_cfg_msg->rec_enc_type); + /* Encoder enable success */ + if (enc_cfg_msg->rec_enc_type & ENCODE_ENABLE) { + if(audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + MM_DBG("routing command\n"); + audpreproc_cmd_cfg_routing_mode(audio); + } else { + audaac_in_param_config(audio); + } + } else { /* Encoder disable success */ + audio->running = 0; + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + audaac_in_record_config(audio, 0); + else + wake_up(&audio->wait_enable); + } + break; + } + case AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: { + MM_DBG("CMD_ENC_PARAM_CFG_DONE_MSG\n"); + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + audaac_in_mem_config(audio); + else + audpcm_config(audio); + break; + } + case AUDPREPROC_CMD_ROUTING_MODE_DONE_MSG: { + struct audpreproc_cmd_routing_mode_done\ + *routing_cfg_done_msg = msg; + if (routing_cfg_done_msg->configuration == 0) { + MM_INFO("routing configuration failed\n"); + audio->running = 0; + } else + audaac_in_param_config(audio); + break; + } + case AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: { + MM_DBG("AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG\n"); + wake_up(&audio->wait_enable); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +/* ------------------- dsp audrec event handler--------------------- */ +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDREC_CMD_MEM_CFG_DONE_MSG: { + MM_DBG("CMD_MEM_CFG_DONE MSG DONE\n"); + audio->running = 1; + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if (audio->dev_cnt > 0) + audaac_in_record_config(audio, 1); + } else { + audpreproc_pcm_send_data(audio, 1); + wake_up(&audio->wait_enable); + } + break; + } + case AUDREC_FATAL_ERR_MSG: { + struct audrec_fatal_err_msg fatal_err_msg; + + getevent(&fatal_err_msg, AUDREC_FATAL_ERR_MSG_LEN); + MM_ERR("FATAL_ERR_MSG: err id %d\n", + fatal_err_msg.audrec_err_id); + /* Error stop the encoder */ + audio->stopped = 1; + wake_up(&audio->wait); + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + wake_up(&audio->write_wait); + break; + } + case AUDREC_UP_PACKET_READY_MSG: { + struct audrec_up_pkt_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packet_write_cnt_lsw, \ + pkt_ready_msg.audrec_packet_write_cnt_msw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_lsw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_msw); + + audaac_in_get_dsp_frames(audio); + break; + } + case AUDREC_CMD_PCM_BUFFER_PTR_UPDATE_ARM_TO_ENC_MSG: { + MM_DBG("ptr_update recieved from DSP\n"); + audpreproc_pcm_send_data(audio, 1); + break; + } + case AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG: { + MM_ERR("AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG"); + audaac_in_mem_config(audio); + break; + } + case AUDREC_UP_NT_PACKET_READY_MSG: { + struct audrec_up_nt_packet_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_NT_PACKET_READY_MSG_LEN); + MM_DBG("UP_NT_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packetwrite_cnt_lsw, \ + pkt_ready_msg.audrec_packetwrite_cnt_msw, \ + pkt_ready_msg.audrec_upprev_readcount_lsw, \ + pkt_ready_msg.audrec_upprev_readcount_msw); + + audaac_nt_in_get_dsp_frames(audio); + break; + } + case AUDREC_CMD_EOS_ACK_MSG: { + MM_DBG("eos ack recieved\n"); + break; + } + case AUDREC_CMD_FLUSH_DONE_MSG: { + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 1; + wake_up(&audio->write_wait); + MM_DBG("flush ack recieved\n"); + break; + } + case ADSP_MESSAGE_ID: { + MM_DBG("Received ADSP event:module audrectask\n"); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +static void audaac_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + MM_DBG("head = %d\n", audio->in_head); + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(*frame)); + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = frame->frame_length; + + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) { + MM_ERR("Error! not able to keep up the read\n"); + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + } else + audio->in_count++; + + audaac_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +static void audaac_nt_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame_nt *nt_frame; + uint32_t index; + unsigned long flags; + MM_DBG("head = %d\n", audio->in_head); + index = audio->in_head; + nt_frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(struct audio_frame_nt)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = nt_frame->frame_length; + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + MM_DBG("Error! not able to keep up the read\n"); + else + audio->in_count++; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + wake_up(&audio->wait); +} + + +struct msm_adsp_ops audrec_aac_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int audpreproc_pcm_buffer_ptr_refresh(struct audio_in *audio, + unsigned idx, unsigned len) +{ + struct audrec_cmd_pcm_buffer_ptr_refresh_arm_enc cmd; + + if (len == META_OUT_SIZE) + len = len / 2; + else + len = (len + META_OUT_SIZE) / 2; + MM_DBG("len = %d\n", len); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_BUFFER_PTR_REFRESH_ARM_TO_ENC; + cmd.num_buffers = 1; + if (cmd.num_buffers == 1) { + cmd.buf_address_length[0] = (audio->out[idx].addr & + 0xffff0000) >> 16; + cmd.buf_address_length[1] = (audio->out[idx].addr & + 0x0000ffff); + cmd.buf_address_length[2] = (len & 0xffff0000) >> 16; + cmd.buf_address_length[3] = (len & 0x0000ffff); + } + audio->out_frame_cnt++; + return audrec_send_audrecqueue(audio, (void *)&cmd, + (unsigned int)sizeof(cmd)); +} + + +static int audpcm_config(struct audio_in *audio) +{ + struct audrec_cmd_pcm_cfg_arm_to_enc cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_CFG_ARM_TO_ENC; + cmd.config_update_flag = PCM_CONFIG_UPDATE_FLAG_ENABLE; + cmd.enable_flag = ENABLE_FLAG_VALUE; + cmd.sampling_freq = audio->samp_rate; + if (!audio->channel_mode) + cmd.channels = 1; + else + cmd.channels = 2; + cmd.frequency_of_intimation = 1; + cmd.max_number_of_buffers = OUT_FRAME_NUM; + return audrec_send_audrecqueue(audio, (void *)&cmd, + (unsigned int)sizeof(cmd)); +} + + +static int audpreproc_cmd_cfg_routing_mode(struct audio_in *audio) +{ + struct audpreproc_audrec_cmd_routing_mode cmd; + + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ROUTING_MODE; + cmd.stream_id = audio->enc_id; + if (audio->mode == MSM_ADSP_ENC_MODE_NON_TUNNEL) + cmd.routing_mode = 1; + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + + + +static int audaac_in_enc_config(struct audio_in *audio, int enable) +{ + struct audpreproc_audrec_cmd_enc_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + cmd.stream_id = audio->enc_id; + + if (enable) + cmd.audrec_enc_type = audio->enc_type | ENCODE_ENABLE; + else + cmd.audrec_enc_type &= ~(ENCODE_ENABLE); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audaac_in_param_config(struct audio_in *audio) +{ + struct audpreproc_audrec_cmd_parm_cfg_aac cmd; + + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPREPROC_AUDREC_CMD_PARAM_CFG; + cmd.common.stream_id = audio->enc_id; + + cmd.aud_rec_samplerate_idx = audio->samp_rate; + cmd.aud_rec_stereo_mode = audio->channel_mode; + cmd.recording_quality = audio->record_quality; + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +/* To Do: msm_snddev_route_enc(audio->enc_id); */ +static int audaac_in_record_config(struct audio_in *audio, int enable) +{ + struct audpreproc_afe_cmd_audio_record_cfg cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG; + cmd.stream_id = audio->enc_id; + if (enable) + cmd.destination_activity = AUDIO_RECORDING_TURN_ON; + else + cmd.destination_activity = AUDIO_RECORDING_TURN_OFF; + + cmd.source_mix_mask = audio->source; + if (audio->enc_id == 2) { + if ((cmd.source_mix_mask & INTERNAL_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & AUX_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_UL_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_DL_SOURCE_MIX_MASK)) { + cmd.pipe_id = SOURCE_PIPE_1; + } + if (cmd.source_mix_mask & + AUDPP_A2DP_PIPE_SOURCE_MIX_MASK) + cmd.pipe_id |= SOURCE_PIPE_0; + } + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audaac_in_mem_config(struct audio_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + int n; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_MEM_CFG_CMD; + cmd.audrec_up_pkt_intm_count = 1; + cmd.audrec_ext_pkt_start_addr_msw = audio->phys >> 16; + cmd.audrec_ext_pkt_start_addr_lsw = audio->phys; + cmd.audrec_ext_pkt_buf_number = FRAME_NUM; + MM_DBG("audio->phys = %x\n", audio->phys); + /* prepare buffer pointers: + * 1536 bytes aac packet + 4 halfword header + */ + for (n = 0; n < FRAME_NUM; n++) { + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + audio->in[n].data = data + 4; + data += (FRAME_SIZE/2); + MM_DBG("0x%8x\n", (int)(audio->in[n].data - 8)); + } else { + audio->in[n].data = data + 12; + data += ((AAC_FRAME_SIZE) / 2) + 12; + MM_DBG("0x%8x\n", (int)(audio->in[n].data - 24)); + } + } + return audrec_send_audrecqueue(audio, &cmd, sizeof(cmd)); +} + +static int audaac_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt) +{ + struct up_audrec_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = UP_AUDREC_PACKET_EXT_PTR; + cmd.audrec_up_curr_read_count_msw = read_cnt >> 16; + cmd.audrec_up_curr_read_count_lsw = read_cnt; + + return audrec_send_bitstreamqueue(audio, &cmd, sizeof(cmd)); +} +static int audaac_flush_command(struct audio_in *audio) +{ + struct audrec_cmd_flush cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_FLUSH; + return audrec_send_audrecqueue(audio, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audaac_in_enable(struct audio_in *audio) +{ + if (audio->enabled) + return 0; + + if (audpreproc_enable(audio->enc_id, &audpreproc_dsp_event, audio)) { + MM_ERR("msm_adsp_enable(audpreproc) failed\n"); + return -ENODEV; + } + + if (msm_adsp_enable(audio->audrec)) { + MM_ERR("msm_adsp_enable(audrec) failed\n"); + audpreproc_disable(audio->enc_id, audio); + return -ENODEV; + } + audio->enabled = 1; + audaac_in_enc_config(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audaac_in_disable(struct audio_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + audaac_in_enc_config(audio, 0); + wake_up(&audio->wait); + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + msm_adsp_disable(audio->audrec); + audpreproc_disable(audio->enc_id, audio); + } + return 0; +} + +static void audaac_ioport_reset(struct audio_in *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audaac_in_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audaac_out_flush(audio); + mutex_unlock(&audio->read_lock); +} + +static void audaac_in_flush(struct audio_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + audio->eos_ack = 0; + for (i = 0; i < FRAME_NUM; i++) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } + MM_DBG("in_bytes %d\n", atomic_read(&audio->in_bytes)); + MM_DBG("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); +} + +static void audaac_out_flush(struct audio_in *audio) +{ + int i; + + audio->out_head = 0; + audio->out_tail = 0; + audio->out_count = 0; + for (i = 0; i < OUT_FRAME_NUM; i++) { + audio->out[i].size = 0; + audio->out[i].read = 0; + audio->out[i].used = 0; + } +} + +/* ------------------- device --------------------- */ +static long audaac_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_in *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + uint32_t freq; + /* Poll at 48KHz always */ + freq = 48000; + MM_DBG("AUDIO_START\n"); + rc = msm_snddev_request_freq(&freq, audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("sample rate configured %d sample rate requested %d\n", + freq, audio->samp_rate); + if (rc < 0) { + MM_DBG(" Sample rate can not be set, return code %d\n", + rc); + msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + break; + } + /*update aurec session info in audpreproc layer*/ + audio->session_info.session_id = audio->enc_id; + audio->session_info.sampling_freq = audio->samp_rate; + audpreproc_update_audrec_info(&audio->session_info); + rc = audaac_in_enable(audio); + if (!rc) { + rc = + wait_event_interruptible_timeout(audio->wait_enable, + audio->running != 0, 1*HZ); + MM_DBG("state %d rc = %d\n", audio->running, rc); + + if (audio->running == 0) + rc = -ENODEV; + else + rc = 0; + } + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + rc = audaac_in_disable(audio); + rc = msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + audio->stopped = 1; + audio->abort = 0; + break; + } + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audaac_ioport_reset(audio); + if (audio->running) { + audaac_flush_command(audio); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + /* Allow only single frame */ + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if (cfg.buffer_size != (FRAME_SIZE - 8)) { + rc = -EINVAL; + break; + } + } else { + if (cfg.buffer_size != (NT_FRAME_SIZE - 24)) { + rc = -EINVAL; + break; + } + } + audio->buffer_size = cfg.buffer_size; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_pcm_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = OUT_BUFFER_SIZE; + cfg.buffer_count = OUT_FRAME_NUM; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_AAC_ENC_CONFIG: { + struct msm_audio_aac_enc_config cfg; + if (audio->channel_mode == AUDREC_CMD_MODE_MONO) + cfg.channels = 1; + else + cfg.channels = 2; + cfg.sample_rate = audio->samp_rate; + cfg.bit_rate = audio->bit_rate; + cfg.stream_format = AUDIO_AAC_FORMAT_RAW; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_AAC_ENC_CONFIG: { + struct msm_audio_aac_enc_config cfg; + unsigned int record_quality; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.stream_format != AUDIO_AAC_FORMAT_RAW) { + MM_ERR("unsupported AAC format\n"); + rc = -EINVAL; + break; + } + record_quality = bitrate_to_record_quality(cfg.sample_rate, + cfg.channels, cfg.bit_rate); + /* Range of Record Quality Supported by DSP, Q12 format */ + if ((record_quality < 0x800) || (record_quality > 0x4000)) { + MM_ERR("Unsupported bit rate \n"); + rc = -EINVAL; + break; + } + MM_DBG("channels = %d\n", cfg.channels); + if (cfg.channels == 1) { + cfg.channels = AUDREC_CMD_MODE_MONO; + } else if (cfg.channels == 2) { + cfg.channels = AUDREC_CMD_MODE_STEREO; + } else { + rc = -EINVAL; + break; + } + MM_DBG("channels = %d\n", cfg.channels); + audio->samp_rate = cfg.sample_rate; + audio->channel_mode = cfg.channels; + audio->bit_rate = cfg.bit_rate; + audio->record_quality = record_quality; + MM_DBG(" Record Quality = 0x%8x \n", audio->record_quality); + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &audio->enc_id, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audaac_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + struct aac_encoded_meta_in meta_field; + struct audio_frame_nt *nt_frame; + MM_DBG(" count = %d\n", count); + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped || + audio->abort || audio->rflush); + + if (rc < 0) + break; + + if (audio->rflush) { + rc = -EBUSY; + break; + } + + if (audio->stopped && !audio->in_count) { + MM_DBG("Driver in stop state, No more buffer to read"); + rc = 0;/* End of File */ + break; + } + + if (audio->abort) { + rc = -EPERM; /* Not permitted due to abort */ + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + nt_frame = (struct audio_frame_nt *)(data - + sizeof(struct audio_frame_nt)); + memcpy((char *)&meta_field.time_stamp_dword_lsw, + (char *)&nt_frame->time_stamp_dword_lsw, 12); + meta_field.metadata_len = + sizeof(struct aac_encoded_meta_in); + if (copy_to_user((char *)start, (char *)&meta_field, + sizeof(struct aac_encoded_meta_in))) { + rc = -EFAULT; + break; + } + if (nt_frame->nflag_lsw & 0x0001) { + MM_ERR("recieved EOS in read call\n"); + audio->eos_ack = 1; + } + buf += sizeof(struct aac_encoded_meta_in); + count -= sizeof(struct aac_encoded_meta_in); + } + if (count >= size) { + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is + * invalid and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + if ((audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) && + (!audio->eos_ack)) { + MM_DBG("sending read ptr command %d %d\n", + audio->dsp_cnt, + audio->in_tail); + audaac_dsp_read_buffer(audio, + audio->dsp_cnt++); + break; + } + } else { + MM_ERR("short read\n"); + break; + } + break; + } + mutex_unlock(&audio->read_lock); + if (buf > start) + return buf - start; + + return rc; +} + +static void audpreproc_pcm_send_data(struct audio_in *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + MM_DBG("\n"); + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audpreproc_pcm_buffer_ptr_refresh(audio, + audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + + +static int audaac_in_fsync(struct file *file, int datasync) + +{ + struct audio_in *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (!audio->running || (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + audio->wflush); + MM_DBG("waked on by some event audio->wflush = %d\n", audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; + +} + + int audpreproc_aac_process_eos(struct audio_in *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + int rc = 0; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + MM_DBG("copying meta_out frame->used = %d\n", frame->used); + audpreproc_pcm_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audaac_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + char *cpy_ptr; + int rc = 0, eos_condition = AUDPREPROC_AAC_EOS_NONE; + unsigned short mfield_size = 0; + int write_count = count; + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + mutex_lock(&audio->write_lock); + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto error; + + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto error; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + goto error; + } else if (mfield_size > count) { + rc = -EINVAL; + goto error; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + goto error; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDPREPROC_AAC_EOS_FLG_OFFSET] & + AUDPREPROC_AAC_EOS_FLG_MASK) { + MM_DBG("EOS SET\n"); + eos_condition = AUDPREPROC_AAC_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + if (audio->mode == + MSM_AUD_ENC_MODE_NONTUNNEL) { + eos_condition = 0; + goto exit; + } + goto error; + } else + cpy_ptr[AUDPREPROC_AAC_EOS_FLG_OFFSET] &= + ~AUDPREPROC_AAC_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + MM_DBG("copying the stream count = %d\n", count); + if (copy_from_user(cpy_ptr, buf, count)) { + rc = -EFAULT; + goto error; + } +exit: + frame->used = count; + audio->out_head ^= 1; + if (!audio->flush_ack) + audpreproc_pcm_send_data(audio, 0); + else { + audpreproc_pcm_send_data(audio, 1); + audio->flush_ack = 0; + } + if (eos_condition == AUDPREPROC_AAC_EOS_SET) + rc = audpreproc_aac_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + return write_count; +error: + mutex_unlock(&audio->write_lock); + return rc; +} + +static int audaac_in_release(struct inode *inode, struct file *file) +{ + struct audio_in *audio = file->private_data; + + mutex_lock(&audio->lock); + /* with draw frequency for session + incase not stopped the driver */ + msm_snddev_withdraw_freq(audio->enc_id, SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, audio->enc_id); + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + audaac_in_disable(audio); + audaac_in_flush(audio); + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + audio->audrec = NULL; + audio->opened = 0; + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + audio->data = NULL; + } + if (audio->out_data) { + iounmap(audio->out_data); + pmem_kfree(audio->out_phys); + audio->out_data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +struct audio_in the_audio_aac_in; + +static int audaac_in_open(struct inode *inode, struct file *file) +{ + struct audio_in *audio = &the_audio_aac_in; + int rc; + int encid; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->phys); + goto done; + } + } else { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + goto done; + } + MM_DBG("Memory addr = 0x%8x phy addr = 0x%8x\n",\ + (int) audio->data, (int) audio->phys); + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_NONTUNNEL; + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_TUNNEL; + MM_DBG("Opened for tunnel mode encoding\n"); + } else { + rc = -EACCES; + goto done; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + audio->buffer_size = (NT_FRAME_SIZE - 24); + else + audio->buffer_size = (FRAME_SIZE - 8); + audio->enc_type = ENC_TYPE_AAC | audio->mode; + audio->samp_rate = 8000; + audio->channel_mode = AUDREC_CMD_MODE_MONO; + /* For AAC, bit rate hard coded, default settings is + * sample rate (8000) x channel count (1) x recording quality (1.75) + * = 14000 bps */ + audio->bit_rate = 14000; + audio->record_quality = 0x1c00; + MM_DBG("enc_type = %x\n", audio->enc_type); + encid = audpreproc_aenc_alloc(audio->enc_type, &audio->module_name, + &audio->queue_ids); + if (encid < 0) { + MM_ERR("No free encoder available\n"); + rc = -ENODEV; + goto done; + } + audio->enc_id = encid; + + rc = msm_adsp_get(audio->module_name, &audio->audrec, + &audrec_aac_adsp_ops, audio); + + if (rc) { + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + audio->stopped = 0; + audio->source = 0; + audio->abort = 0; + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 0; + + audaac_in_flush(audio); + audaac_out_flush(audio); + + audio->out_phys = pmem_kalloc(BUFFER_SIZE, + PMEM_MEMTYPE_EBI1 | PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->out_phys)) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->out_data = ioremap(audio->out_phys, BUFFER_SIZE); + if (!audio->out_data) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->out_phys); + goto evt_error; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->out_phys, (int)audio->out_data); + } + + /* Initialize buffer */ + audio->out[0].data = audio->out_data + 0; + audio->out[0].addr = audio->out_phys + 0; + audio->out[0].size = OUT_BUFFER_SIZE; + + audio->out[1].data = audio->out_data + OUT_BUFFER_SIZE; + audio->out[1].addr = audio->out_phys + OUT_BUFFER_SIZE; + audio->out[1].size = OUT_BUFFER_SIZE; + + MM_DBG("audio->out[0].data = %d audio->out[1].data = %d", + (unsigned int)audio->out[0].data, + (unsigned int)audio->out[1].data); + audio->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_FREQ_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_ENC, audio->enc_id, + aac_in_listener, (void *) audio); + if (rc) { + MM_ERR("failed to register device event listener\n"); + iounmap(audio->out_data); + pmem_kfree(audio->out_phys); + goto evt_error; + } + audio->mfield = META_OUT_SIZE; + file->private_data = audio; + audio->opened = 1; + audio->out_frame_cnt++; + rc = 0; +done: + mutex_unlock(&audio->lock); + return rc; +evt_error: + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_in_fops = { + .owner = THIS_MODULE, + .open = audaac_in_open, + .release = audaac_in_release, + .read = audaac_in_read, + .write = audaac_in_write, + .fsync = audaac_in_fsync, + .unlocked_ioctl = audaac_in_ioctl, +}; + +struct miscdevice audio_aac_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac_in", + .fops = &audio_in_fops, +}; + +static int __init audaac_in_init(void) +{ + mutex_init(&the_audio_aac_in.lock); + mutex_init(&the_audio_aac_in.read_lock); + spin_lock_init(&the_audio_aac_in.dsp_lock); + spin_lock_init(&the_audio_aac_in.dev_lock); + init_waitqueue_head(&the_audio_aac_in.wait); + init_waitqueue_head(&the_audio_aac_in.wait_enable); + mutex_init(&the_audio_aac_in.write_lock); + init_waitqueue_head(&the_audio_aac_in.write_wait); + + return misc_register(&audio_aac_in_misc); +} + +device_initcall(audaac_in_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_acdb.c b/arch/arm/mach-msm/qdsp5v2/audio_acdb.c new file mode 100644 index 00000000000..84406cd1ea1 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_acdb.c @@ -0,0 +1,3407 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* this is the ACDB device ID */ +#define DALDEVICEID_ACDB 0x02000069 +#define ACDB_PORT_NAME "DAL00" +#define ACDB_CPU SMD_APPS_MODEM +#define ACDB_BUF_SIZE 4096 +#define PBE_BUF_SIZE (33*1024) +#define FLUENCE_BUF_SIZE 498 + +#define ACDB_VALUES_NOT_FILLED 0 +#define ACDB_VALUES_FILLED 1 +#define MAX_RETRY 10 + +/*below macro is used to align the session info received from +Devctl driver with the state mentioned as not to alter the +Existing code*/ +#define AUDREC_OFFSET 2 +/* rpc table index */ +enum { + ACDB_DalACDB_ioctl = DALDEVICE_FIRST_DEVICE_API_IDX +}; + +enum { + CAL_DATA_READY = 0x1, + AUDPP_READY = 0x2, + AUDREC0_READY = 0x4, + AUDREC1_READY = 0x8, + AUDREC2_READY = 0x10, +}; + + +struct acdb_data { + void *handle; + + u32 phys_addr; + u8 *virt_addr; + + struct task_struct *cb_thread_task; + struct auddev_evt_audcal_info *device_info; + + u32 acdb_state; + struct audpp_event_callback audpp_cb; + struct audpreproc_event_callback audpreproc_cb; + + struct audpp_cmd_cfg_object_params_pcm *pp_iir; + struct audpp_cmd_cfg_cal_gain *calib_gain_rx; + struct audpp_cmd_cfg_pbe *pbe_block; + struct audpp_cmd_cfg_object_params_mbadrc *pp_mbadrc; + struct audpreproc_cmd_cfg_agc_params *preproc_agc; + struct audpreproc_cmd_cfg_iir_tuning_filter_params *preproc_iir; + struct audpreproc_cmd_cfg_cal_gain *calib_gain_tx; + struct acdb_mbadrc_block mbadrc_block; + struct audpreproc_cmd_cfg_lvnv_param preproc_lvnv; + + wait_queue_head_t wait; + struct mutex acdb_mutex; + u32 device_cb_compl; + u32 audpp_cb_compl; + u32 preproc_cb_compl; + u8 preproc_stream_id; + u8 audrec_applied; + u32 multiple_sessions; + u32 cur_tx_session; + struct acdb_result acdb_result; + u16 *pbe_extbuff; + u16 *pbe_enable_flag; + u32 fluence_extbuff; + u8 *fluence_extbuff_virt; + + struct acdb_pbe_block *pbe_blk; + + spinlock_t dsp_lock; + int dec_id; + struct audpp_cmd_cfg_object_params_eqalizer eq; + /*status to enable or disable the fluence*/ + int fleuce_feature_status[MAX_AUDREC_SESSIONS]; + struct audrec_session_info session_info; + /*pmem info*/ + int pmem_fd; + unsigned long paddr; + unsigned long kvaddr; + unsigned long pmem_len; + struct file *file; + /* pmem for get acdb blk */ + unsigned long get_blk_paddr; + u8 *get_blk_kvaddr; +}; + +static struct acdb_data acdb_data; + +struct acdb_cache_node { + u32 node_status; + s32 stream_id; + u32 phys_addr_acdb_values; + u8 *virt_addr_acdb_values; + struct auddev_evt_audcal_info device_info; +}; + +/*for RX devices acdb values are applied based on copp ID so +the depth of tx cache is MAX number of COPP supported in the system*/ +struct acdb_cache_node acdb_cache_rx[MAX_COPP_NODE_SUPPORTED]; + +/*for TX devices acdb values are applied based on AUDREC session and +the depth of the tx cache is define by number of AUDREC sessions supported*/ +struct acdb_cache_node acdb_cache_tx[MAX_AUDREC_SESSIONS]; + +/*Audrec session info includes Attributes Sampling frequency and enc_id */ +struct audrec_session_info session_info[MAX_AUDREC_SESSIONS]; +#ifdef CONFIG_DEBUG_FS + +#define RTC_MAX_TIMEOUT 500 /* 500 ms */ +#define PMEM_RTC_ACDB_QUERY_MEM 4096 +#define EXTRACT_HIGH_WORD(x) ((x & 0xFFFF0000)>>16) +#define EXTRACT_LOW_WORD(x) (0x0000FFFF & x) +#define ACDB_RTC_TX 0xF1 +#define ACDB_RTC_RX 0x1F + + +static u32 acdb_audpp_entry[][4] = { + + { ABID_AUDIO_RTC_VOLUME_PAN_RX,\ + IID_AUDIO_RTC_VOLUME_PAN_PARAMETERS,\ + AUDPP_CMD_VOLUME_PAN,\ + ACDB_RTC_RX + }, + { ABID_AUDIO_IIR_RX,\ + IID_AUDIO_IIR_COEFF,\ + AUDPP_CMD_IIR_TUNING_FILTER, + ACDB_RTC_RX + }, + { ABID_AUDIO_RTC_EQUALIZER_PARAMETERS,\ + IID_AUDIO_RTC_EQUALIZER_PARAMETERS,\ + AUDPP_CMD_EQUALIZER,\ + ACDB_RTC_RX + }, + { ABID_AUDIO_RTC_SPA,\ + IID_AUDIO_RTC_SPA_PARAMETERS,\ + AUDPP_CMD_SPECTROGRAM, + ACDB_RTC_RX + }, + { ABID_AUDIO_STF_RX,\ + IID_AUDIO_IIR_COEFF,\ + AUDPP_CMD_SIDECHAIN_TUNING_FILTER,\ + ACDB_RTC_RX + }, + { + ABID_AUDIO_MBADRC_RX,\ + IID_AUDIO_RTC_MBADRC_PARAMETERS,\ + AUDPP_CMD_MBADRC,\ + ACDB_RTC_RX + }, + { + ABID_AUDIO_AGC_TX,\ + IID_AUDIO_AGC_PARAMETERS,\ + AUDPREPROC_CMD_CFG_AGC_PARAMS,\ + ACDB_RTC_TX + }, + { + ABID_AUDIO_AGC_TX,\ + IID_AUDIO_RTC_AGC_PARAMETERS,\ + AUDPREPROC_CMD_CFG_AGC_PARAMS,\ + ACDB_RTC_TX + }, + { + ABID_AUDIO_NS_TX,\ + IID_NS_PARAMETERS,\ + AUDPREPROC_CMD_CFG_NS_PARAMS,\ + ACDB_RTC_TX + }, + { + ABID_AUDIO_IIR_TX,\ + IID_AUDIO_RTC_TX_IIR_COEFF,\ + AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS,\ + ACDB_RTC_TX + }, + { + ABID_AUDIO_IIR_TX,\ + IID_AUDIO_IIR_COEFF,\ + AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS,\ + ACDB_RTC_TX + } + /*Any new entries should be added here*/ +}; + +static struct dentry *get_set_abid_dentry; +static struct dentry *get_set_abid_data_dentry; + +struct rtc_acdb_pmem { + u8 *viraddr; + int32_t phys; +}; + +struct rtc_acdb_data { + u32 acdb_id; + u32 cmd_id; + u32 set_abid; + u32 set_iid; + u32 abid; + u32 err; + bool valid_abid; + u32 tx_rx_ctl; + struct rtc_acdb_pmem rtc_read; + struct rtc_acdb_pmem rtc_write; + wait_queue_head_t wait; +}; + +struct get_abid { + u32 cmd_id; + u32 acdb_id; + u32 set_abid; + u32 set_iid; +}; + +struct acdb_block_mbadrc_rtc { + u16 enable; + u16 num_bands; + u16 down_samp_level; + u16 adrc_delay; + u16 ext_buf_size; + u16 ext_partition; + u16 ext_buf_msw; + u16 ext_buf_lsw; + struct adrc_config adrc_band[AUDPP_MAX_MBADRC_BANDS]; + signed int ExtBuff[196]; +} __attribute__((packed)); + +enum { + ACDB_RTC_SUCCESS, + ACDB_RTC_ERR_INVALID_DEVICE, + ACDB_RTC_ERR_DEVICE_INACTIVE, + ACDB_RTC_ERR_INVALID_ABID, + ACDB_RTC_DSP_FAILURE, + ACDB_RTC_DSP_FEATURE_NOT_AVAILABLE, + ACDB_RTC_ERR_INVALID_LEN, + ACDB_RTC_ERR_UNKNOWN_FAILURE, + ACDB_RTC_PENDING_RESPONSE, + ACDB_RTC_INIT_FAILURE, +}; + +static struct rtc_acdb_data rtc_acdb; + +static int rtc_getsetabid_dbg_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + MM_INFO("GET-SET ABID Open debug intf %s\n", + (char *) file->private_data); + return 0; +} + +static bool get_feature_id(u32 set_abid, u32 iid, unsigned short *feature_id) +{ + bool ret_value = false; + int i = 0; + + for (; i < (sizeof(acdb_audpp_entry) / sizeof(acdb_audpp_entry[0]));\ + i++) { + if (acdb_audpp_entry[i][0] == set_abid && + acdb_audpp_entry[i][1] == iid) { + *feature_id = acdb_audpp_entry[i][2]; + rtc_acdb.tx_rx_ctl = acdb_audpp_entry[i][3]; + ret_value = true; + break; + } + } + return ret_value; +} +static ssize_t rtc_getsetabid_dbg_write(struct file *filp, + const char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct get_abid write_abid; + unsigned short feat_id = 0; + rtc_acdb.valid_abid = false; + + if (copy_from_user(&write_abid, \ + (void *)ubuf, sizeof(struct get_abid))) { + MM_ERR("ACDB DATA WRITE - INVALID READ LEN\n"); + rtc_acdb.err = ACDB_RTC_ERR_INVALID_LEN; + return cnt; + } + MM_INFO("SET ABID : Cmd ID: %d Device:%d ABID:%d IID : %d cnt: %d\n",\ + write_abid.cmd_id, write_abid.acdb_id, + write_abid.set_abid, write_abid.set_iid, cnt); + if (write_abid.acdb_id > ACDB_ID_MAX || + write_abid.acdb_id < ACDB_ID_HANDSET_SPKR){ + rtc_acdb.err = ACDB_RTC_ERR_INVALID_DEVICE; + return cnt; + } + if (!is_dev_opened(write_abid.acdb_id)) { + rtc_acdb.err = ACDB_RTC_ERR_DEVICE_INACTIVE; + return cnt; + } + rtc_acdb.err = ACDB_RTC_ERR_INVALID_ABID; + rtc_acdb.abid = write_abid.set_abid; + if (get_feature_id(write_abid.set_abid, \ + write_abid.set_iid, &feat_id)) { + rtc_acdb.err = ACDB_RTC_SUCCESS; + rtc_acdb.cmd_id = write_abid.cmd_id; + rtc_acdb.acdb_id = write_abid.acdb_id; + rtc_acdb.set_abid = feat_id; + rtc_acdb.valid_abid = true; + rtc_acdb.set_iid = write_abid.set_iid; + } + return cnt; +} +static ssize_t rtc_getsetabid_dbg_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + static char buffer[1024]; + int n = 0; + u32 msg = rtc_acdb.err; + memcpy(buffer, &rtc_acdb.cmd_id, sizeof(struct get_abid)); + memcpy(buffer+16, &msg, 4); + n = 20; + MM_INFO("SET ABID : Cmd ID: %x Device:%x ABID:%x IID : %x Err: %d\n",\ + rtc_acdb.cmd_id, rtc_acdb.acdb_id, rtc_acdb.set_abid,\ + rtc_acdb.set_iid, rtc_acdb.err); + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static int rtc_getsetabid_data_dbg_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + MM_INFO("GET-SET ABID DATA Open debug intf %s\n", + (char *) file->private_data); + return 0; +} + +void acdb_rtc_set_err(u32 ErrCode) +{ + if (rtc_acdb.err == ACDB_RTC_PENDING_RESPONSE) { + if (ErrCode == 0xFFFF) { + rtc_acdb.err = ACDB_RTC_SUCCESS; + MM_INFO("RTC READ SUCCESS---\n"); + } else if (ErrCode == 0) { + rtc_acdb.err = ACDB_RTC_DSP_FAILURE; + MM_INFO("RTC READ FAIL---\n"); + } else if (ErrCode == 1) { + rtc_acdb.err = ACDB_RTC_DSP_FEATURE_NOT_AVAILABLE; + MM_INFO("RTC READ FEAT UNAVAILABLE---\n"); + } else { + rtc_acdb.err = ACDB_RTC_DSP_FAILURE; + MM_ERR("RTC Err CODE---\n"); + } + } else { + rtc_acdb.err = ACDB_RTC_DSP_FAILURE; + MM_ERR("RTC Err code Invalid State\n"); + } + wake_up(&rtc_acdb.wait); +} +static ssize_t rtc_getsetabid_data_dbg_read(struct file *file, + char __user *buf, size_t count, + loff_t *ppos) +{ + static char buffer[PMEM_RTC_ACDB_QUERY_MEM]; + int rc, n = 0; + int counter = 0; + struct rtc_acdb_pmem *rtc_read = &rtc_acdb.rtc_read; + memset(&buffer, 0, PMEM_RTC_ACDB_QUERY_MEM); + + if (rtc_acdb.valid_abid != true) { + MM_ERR("ACDB DATA READ ---INVALID ABID\n"); + n = 0; + rtc_acdb.err = ACDB_RTC_ERR_INVALID_ABID; + } else { + if (PMEM_RTC_ACDB_QUERY_MEM < count) { + MM_ERR("ACDB DATA READ ---\ + INVALID READ LEN %x\n", count); + n = 0; + rtc_acdb.err = ACDB_RTC_ERR_INVALID_LEN; + } else { + rtc_acdb.err = ACDB_RTC_PENDING_RESPONSE; + if (rtc_read->viraddr != NULL) { + memset(rtc_read->viraddr, + 0, PMEM_RTC_ACDB_QUERY_MEM); + } + if (rtc_acdb.tx_rx_ctl == ACDB_RTC_RX) { + struct rtc_audpp_read_data rtc_read_cmd; + rtc_read_cmd.cmd_id = + AUDPP_CMD_PP_FEAT_QUERY_PARAMS; + rtc_read_cmd.obj_id = + AUDPP_CMD_COPP_STREAM; + rtc_read_cmd.route_id = + acdb_data.device_info->dev_id; + rtc_read_cmd.feature_id = rtc_acdb.set_abid; + rtc_read_cmd.extbufsizemsw = + EXTRACT_HIGH_WORD(\ + PMEM_RTC_ACDB_QUERY_MEM); + rtc_read_cmd.extbufsizelsw = + EXTRACT_LOW_WORD(\ + PMEM_RTC_ACDB_QUERY_MEM); + rtc_read_cmd.extpart = 0x0000; + rtc_read_cmd.extbufstartmsw = + EXTRACT_HIGH_WORD(rtc_read->phys); + rtc_read_cmd.extbufstartlsw = + EXTRACT_LOW_WORD(rtc_read->phys); + rc = audpp_send_queue2(&rtc_read_cmd, + sizeof(rtc_read_cmd)); + MM_INFO("ACDB READ Command RC --->%x\ + Route ID=%x\n", rc,\ + acdb_data.device_info->dev_id); + } else if (rtc_acdb.tx_rx_ctl == ACDB_RTC_TX) { + struct rtc_audpreproc_read_data rtc_audpreproc; + rtc_audpreproc.cmd_id = + AUDPREPROC_CMD_FEAT_QUERY_PARAMS; + rtc_audpreproc.stream_id = + acdb_data.preproc_stream_id; + rtc_audpreproc.feature_id = rtc_acdb.set_abid; + rtc_audpreproc.extbufsizemsw = + EXTRACT_HIGH_WORD(\ + PMEM_RTC_ACDB_QUERY_MEM); + rtc_audpreproc.extbufsizelsw = + EXTRACT_LOW_WORD(\ + PMEM_RTC_ACDB_QUERY_MEM); + rtc_audpreproc.extpart = 0x0000; + rtc_audpreproc.extbufstartmsw = + EXTRACT_HIGH_WORD(rtc_read->phys); + rtc_audpreproc.extbufstartlsw = + EXTRACT_LOW_WORD(rtc_read->phys); + rc = audpreproc_send_preproccmdqueue( + &rtc_audpreproc,\ + sizeof(rtc_audpreproc)); + MM_INFO("ACDB READ Command RC --->%x,\ + stream_id %x\n", rc,\ + acdb_data.preproc_stream_id); + } + rc = wait_event_timeout(rtc_acdb.wait, + (rtc_acdb.err != + ACDB_RTC_PENDING_RESPONSE), + msecs_to_jiffies(RTC_MAX_TIMEOUT)); + MM_INFO("ACDB READ ACK Count = %x Err = %x\n", + count, rtc_acdb.err); + { + if (rtc_acdb.err == ACDB_RTC_SUCCESS + && rtc_read->viraddr != NULL) { + memcpy(buffer, rtc_read->viraddr, count); + n = count; + while (counter < count) { + MM_DBG("%x", \ + rtc_read->viraddr[counter]); + counter++; + } + } + } + } + } + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static bool acdb_set_tx_rtc(const char *ubuf, size_t writecount) +{ + struct audpreproc_cmd_cfg_iir_tuning_filter_params *preproc_iir; + struct audpreproc_cmd_cfg_agc_params *preproc_agc; + struct audpreproc_cmd_cfg_ns_params *preproc_ns; + s32 result = 0; + bool retval = false; + unsigned short iircmdsize = + sizeof(struct audpreproc_cmd_cfg_iir_tuning_filter_params); + unsigned short iircmdid = AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS; + + rtc_acdb.err = ACDB_RTC_ERR_UNKNOWN_FAILURE; + + switch (rtc_acdb.set_abid) { + + case AUDPREPROC_CMD_CFG_AGC_PARAMS: + case AUDPREPROC_CMD_CFG_AGC_PARAMS_2: + { + preproc_agc = kmalloc(sizeof(\ + struct audpreproc_cmd_cfg_agc_params),\ + GFP_KERNEL); + if ((sizeof(struct audpreproc_cmd_cfg_agc_params) -\ + (2*sizeof(unsigned short))) + < writecount) { + MM_ERR("ACDB DATA WRITE --\ + AGC TX writecount > DSP struct\n"); + } else { + if (preproc_agc != NULL) { + char *base; unsigned short offset; + unsigned short *offset_addr; + base = (char *)preproc_agc; + offset = offsetof(struct \ + audpreproc_cmd_cfg_agc_params,\ + tx_agc_param_mask); + offset_addr = (unsigned short *)(base + offset); + if ((copy_from_user(offset_addr,\ + (void *)ubuf, writecount)) == 0x00) { + preproc_agc->cmd_id = + AUDPREPROC_CMD_CFG_AGC_PARAMS; + preproc_agc->stream_id = + acdb_data.preproc_stream_id; + result = audpreproc_dsp_set_agc( + preproc_agc, + sizeof(struct \ + audpreproc_cmd_cfg_agc_params)); + if (result) { + MM_ERR("ACDB=> Failed to \ + send AGC data to \ + preproc)\n"); + } else { + retval = true; + } + } else { + MM_ERR("ACDB DATA WRITE ---\ + GC Tx copy_from_user Fail\n"); + } + } else { + MM_ERR("ACDB DATA WRITE --\ + AGC TX kalloc Failed LEN\n"); + } + } + if (preproc_agc != NULL) + kfree(preproc_agc); + break; + } + case AUDPREPROC_CMD_CFG_NS_PARAMS: + { + + preproc_ns = kmalloc(sizeof(struct \ + audpreproc_cmd_cfg_ns_params),\ + GFP_KERNEL); + if ((sizeof(struct audpreproc_cmd_cfg_ns_params) -\ + (2 * sizeof(unsigned short))) + < writecount) { + MM_ERR("ACDB DATA WRITE --\ + NS TX writecount > DSP struct\n"); + } else { + if (preproc_ns != NULL) { + char *base; unsigned short offset; + unsigned short *offset_addr; + base = (char *)preproc_ns; + offset = offsetof(struct \ + audpreproc_cmd_cfg_ns_params,\ + ec_mode_new); + offset_addr = (unsigned short *)(base + offset); + if ((copy_from_user(offset_addr,\ + (void *)ubuf, writecount)) == 0x00) { + preproc_ns->cmd_id = + AUDPREPROC_CMD_CFG_NS_PARAMS; + preproc_ns->stream_id = + acdb_data.preproc_stream_id; + result = audpreproc_dsp_set_ns( + preproc_ns, + sizeof(struct \ + audpreproc_cmd_cfg_ns_params)); + if (result) { + MM_ERR("ACDB=> Failed to send \ + NS data to preproc\n"); + } else { + retval = true; + } + } else { + MM_ERR("ACDB DATA WRITE ---NS Tx \ + copy_from_user Fail\n"); + } + } else { + MM_ERR("ACDB DATA WRITE --NS TX\ + kalloc Failed LEN\n"); + } + } + if (preproc_ns != NULL) + kfree(preproc_ns); + break; + } + case AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS: + { + + preproc_iir = kmalloc(sizeof(struct \ + audpreproc_cmd_cfg_iir_tuning_filter_params),\ + GFP_KERNEL); + if ((sizeof(struct \ + audpreproc_cmd_cfg_iir_tuning_filter_params)-\ + (2 * sizeof(unsigned short))) + < writecount) { + MM_ERR("ACDB DATA WRITE --IIR TX writecount\ + > DSP struct\n"); + } else { + if (preproc_iir != NULL) { + char *base; unsigned short offset; + unsigned short *offset_addr; + base = (char *)preproc_iir; + offset = offsetof(struct \ + audpreproc_cmd_cfg_iir_tuning_filter_params,\ + active_flag); + offset_addr = (unsigned short *)(base + \ + offset); + if ((copy_from_user(offset_addr,\ + (void *)ubuf, writecount)) == 0x00) { + preproc_iir->cmd_id = iircmdid; + preproc_iir->stream_id = + acdb_data.preproc_stream_id; + result = audpreproc_dsp_set_iir(\ + preproc_iir, + iircmdsize); + if (result) { + MM_ERR("ACDB=> Failed to send\ + IIR data to preproc\n"); + } else { + retval = true; + } + } else { + MM_ERR("ACDB DATA WRITE ---IIR Tx \ + copy_from_user Fail\n"); + } + } else { + MM_ERR("ACDB DATA WRITE --IIR TX kalloc \ + Failed LEN\n"); + } + } + if (preproc_iir != NULL) + kfree(preproc_iir); + break; + } + } + return retval; +} + +static bool acdb_set_rx_rtc(const char *ubuf, size_t writecount) +{ + + struct audpp_cmd_cfg_object_params_volpan *volpan_config; + struct audpp_cmd_cfg_object_params_mbadrc *mbadrc_config; + struct acdb_block_mbadrc_rtc *acdb_mbadrc_rtc; + struct audpp_cmd_cfg_object_params_sidechain *stf_config; + struct audpp_cmd_cfg_object_params_spectram *spa_config; + struct audpp_cmd_cfg_object_params_eqalizer *eq_config; + struct audpp_cmd_cfg_object_params_pcm *iir_config; + unsigned short temp_spa[34]; + struct rtc_acdb_pmem *rtc_write = &rtc_acdb.rtc_write; + s32 result = 0; + bool retval = false; + + switch (rtc_acdb.set_abid) { + case AUDPP_CMD_VOLUME_PAN: + { + volpan_config = kmalloc(sizeof(struct \ + audpp_cmd_cfg_object_params_volpan),\ + GFP_KERNEL); + if ((sizeof(struct audpp_cmd_cfg_object_params_volpan) -\ + sizeof(struct audpp_cmd_cfg_object_params_common)) + < writecount) { + MM_ERR("ACDB DATA WRITE --\ + VolPan writecount > DSP struct\n"); + } else { + if (volpan_config != NULL) { + char *base; unsigned short offset; + unsigned short *offset_addr; + base = (char *)volpan_config; + offset = offsetof(struct \ + audpp_cmd_cfg_object_params_volpan,\ + volume); + offset_addr = (unsigned short *)(base+offset); + if ((copy_from_user(offset_addr,\ + (void *)ubuf, writecount)) == 0x00) { + MM_ERR("ACDB RX WRITE DATA:\ + AUDPP_CMD_VOLUME_PAN\n"); + result = audpp_set_volume_and_pan( + acdb_data.device_info->dev_id,\ + volpan_config->volume, + volpan_config->pan, + COPP); + if (result) { + MM_ERR("ACDB=> Failed to \ + send VOLPAN data to" + " postproc\n"); + } else { + retval = true; + } + } else { + MM_ERR("ACDB DATA WRITE ---\ + copy_from_user Fail\n"); + } + } else { + MM_ERR("ACDB DATA WRITE --\ + Vol Pan kalloc Failed LEN\n"); + } + } + if (volpan_config != NULL) + kfree(volpan_config); + break; + } + + case AUDPP_CMD_IIR_TUNING_FILTER: + { + iir_config = kmalloc(sizeof(struct \ + audpp_cmd_cfg_object_params_pcm),\ + GFP_KERNEL); + if ((sizeof(struct audpp_cmd_cfg_object_params_pcm) -\ + sizeof(struct audpp_cmd_cfg_object_params_common)) + < writecount) { + MM_ERR("ACDB DATA WRITE --\ + IIR RX writecount > DSP struct\n"); + } else { + if (iir_config != NULL) { + char *base; unsigned short offset; + unsigned short *offset_addr; + base = (char *)iir_config; + offset = offsetof(struct \ + audpp_cmd_cfg_object_params_pcm,\ + active_flag); + offset_addr = (unsigned short *)(base+offset); + if ((copy_from_user(offset_addr,\ + (void *)ubuf, writecount)) == 0x00) { + + iir_config->common.cmd_id = + AUDPP_CMD_CFG_OBJECT_PARAMS; + iir_config->common.stream = + AUDPP_CMD_COPP_STREAM; + iir_config->common.stream_id = 0; + iir_config->common.obj_cfg = + AUDPP_CMD_OBJ0_UPDATE; + iir_config->common.command_type = 0; + MM_ERR("ACDB RX WRITE DATA:\ + AUDPP_CMD_IIR_TUNING_FILTER\n"); + result = audpp_dsp_set_rx_iir( + acdb_data.device_info->dev_id, + iir_config->active_flag,\ + iir_config, COPP); + if (result) { + MM_ERR("ACDB=> Failed to send\ + IIR data to\ + postproc\n"); + } else { + retval = true; + } + } else { + MM_ERR("ACDB DATA WRITE ---\ + IIR Rx copy_from_user Fail\n"); + } + } else { + MM_ERR("ACDB DATA WRITE --\ + acdb_iir_block kalloc Failed LEN\n"); + } + } + if (iir_config != NULL) + kfree(iir_config); + break; + } + case AUDPP_CMD_EQUALIZER: + { + eq_config = kmalloc(sizeof(struct \ + audpp_cmd_cfg_object_params_eqalizer),\ + GFP_KERNEL); + if ((sizeof(struct audpp_cmd_cfg_object_params_eqalizer) -\ + sizeof(struct audpp_cmd_cfg_object_params_common)) + < writecount) { + MM_ERR("ACDB DATA WRITE --\ + EQ RX writecount > DSP struct\n"); + } else { + if (eq_config != NULL) { + char *base; unsigned short offset; + unsigned short *offset_addr; + base = (char *)eq_config; + offset = offsetof(struct \ + audpp_cmd_cfg_object_params_eqalizer,\ + eq_flag); + offset_addr = (unsigned short *)(base+offset); + if ((copy_from_user(offset_addr,\ + (void *)ubuf, writecount)) == 0x00) { + eq_config->common.cmd_id = + AUDPP_CMD_CFG_OBJECT_PARAMS; + eq_config->common.stream = + AUDPP_CMD_COPP_STREAM; + eq_config->common.stream_id = 0; + eq_config->common.obj_cfg = + AUDPP_CMD_OBJ0_UPDATE; + eq_config->common.command_type = 0; + MM_ERR("ACDB RX WRITE\ + DATA:AUDPP_CMD_EQUALIZER\n"); + result = audpp_dsp_set_eq( + acdb_data.device_info->dev_id, + eq_config->eq_flag,\ + eq_config, + COPP); + if (result) { + MM_ERR("ACDB=> Failed to \ + send EQ data to postproc\n"); + } else { + retval = true; + } + } else { + MM_ERR("ACDB DATA WRITE ---\ + EQ Rx copy_from_user Fail\n"); + } + } else { + MM_ERR("ACDB DATA WRITE --\ + EQ kalloc Failed LEN\n"); + } + } + if (eq_config != NULL) + kfree(eq_config); + break; + } + + case AUDPP_CMD_SPECTROGRAM: + { + spa_config = kmalloc(sizeof(struct \ + audpp_cmd_cfg_object_params_spectram),\ + GFP_KERNEL); + if ((sizeof(struct audpp_cmd_cfg_object_params_spectram)-\ + sizeof(struct \ + audpp_cmd_cfg_object_params_common)) + < (2 * sizeof(unsigned short))) { + MM_ERR("ACDB DATA WRITE --SPA \ + RX writecount > DSP struct\n"); + } else { + if (spa_config != NULL) { + if ((copy_from_user(&temp_spa[0],\ + (void *)ubuf, + (34 * sizeof(unsigned short)))) + == 0x00) { + spa_config->common.cmd_id = + AUDPP_CMD_CFG_OBJECT_PARAMS; + spa_config->common.stream = + AUDPP_CMD_COPP_STREAM; + spa_config->common.stream_id = 0; + spa_config->common.obj_cfg = + AUDPP_CMD_OBJ0_UPDATE; + spa_config->common.command_type = 0; + spa_config->sample_interval = + temp_spa[0]; + spa_config->num_coeff = temp_spa[1]; + MM_ERR("ACDB RX WRITE DATA:\ + AUDPP_CMD_SPECTROGRAM\n"); + result = audpp_dsp_set_spa( + acdb_data.device_info->dev_id,\ + spa_config, COPP); + if (result) { + MM_ERR("ACDB=> Failed to \ + send SPA data \ + to postproc\n"); + } else { + retval = true; + } + } else { + MM_ERR("ACDB DATA WRITE \ + ---SPA Rx copy_from_user\ + Fail\n"); + } + } else { + MM_ERR("ACDB DATA WRITE --\ + SPA kalloc Failed LEN\n"); + } + } + if (spa_config != NULL) + kfree(spa_config); + break; + } + case AUDPP_CMD_MBADRC: + { + acdb_mbadrc_rtc = kmalloc(sizeof(struct \ + acdb_block_mbadrc_rtc),\ + GFP_KERNEL); + mbadrc_config = kmalloc(sizeof(struct \ + audpp_cmd_cfg_object_params_mbadrc),\ + GFP_KERNEL); + if (mbadrc_config != NULL && acdb_mbadrc_rtc != NULL) { + if ((copy_from_user(acdb_mbadrc_rtc,\ + (void *)ubuf, + sizeof(struct acdb_block_mbadrc_rtc))) + == 0x00) { + mbadrc_config->common.cmd_id = + AUDPP_CMD_CFG_OBJECT_PARAMS; + mbadrc_config->common.stream = + AUDPP_CMD_COPP_STREAM; + mbadrc_config->common.stream_id = 0; + mbadrc_config->common.obj_cfg = + AUDPP_CMD_OBJ0_UPDATE; + mbadrc_config->common.command_type = 0; + mbadrc_config->enable = + acdb_mbadrc_rtc->enable; + mbadrc_config->num_bands = + acdb_mbadrc_rtc->num_bands; + mbadrc_config->down_samp_level = + acdb_mbadrc_rtc->down_samp_level; + mbadrc_config->adrc_delay = + acdb_mbadrc_rtc->adrc_delay; + memcpy(mbadrc_config->adrc_band,\ + acdb_mbadrc_rtc->adrc_band,\ + AUDPP_MAX_MBADRC_BANDS *\ + sizeof(struct adrc_config)); + if (mbadrc_config->num_bands > 1) { + mbadrc_config->ext_buf_size = + (97 * 2) + (33 * 2 * \ + (mbadrc_config->num_bands - 2)); + } + mbadrc_config->ext_partition = 0; + mbadrc_config->ext_buf_lsw = + (u16) EXTRACT_LOW_WORD(\ + rtc_write->phys); + mbadrc_config->ext_buf_msw = + (u16) EXTRACT_HIGH_WORD(\ + rtc_write->phys); + memcpy(rtc_write->viraddr, + acdb_mbadrc_rtc->ExtBuff, + (196*sizeof(signed int))); + result = audpp_dsp_set_mbadrc( + acdb_data.device_info->dev_id, + mbadrc_config->enable, + mbadrc_config, COPP); + if (result) { + MM_ERR("ACDB=> Failed to \ + Send MBADRC data \ + to postproc\n"); + } else { + retval = true; + } + } else { + MM_ERR("ACDB DATA WRITE ---\ + MBADRC Rx copy_from_user Fail\n"); + } + } else { + MM_ERR("ACDB DATA WRITE --MBADRC kalloc Failed LEN\n"); + } + if (mbadrc_config != NULL) + kfree(mbadrc_config); + if (acdb_mbadrc_rtc != NULL) + kfree(acdb_mbadrc_rtc); + break; + } + case AUDPP_CMD_SIDECHAIN_TUNING_FILTER: + { + stf_config = kmalloc(sizeof(struct \ + audpp_cmd_cfg_object_params_sidechain),\ + GFP_KERNEL); + if ((sizeof(struct audpp_cmd_cfg_object_params_sidechain) -\ + sizeof(struct audpp_cmd_cfg_object_params_common)) + < writecount) { + MM_ERR("ACDB DATA WRITE --\ + STF RX writecount > DSP struct\n"); + } else { + if (stf_config != NULL) { + char *base; unsigned short offset; + unsigned short *offset_addr; + base = (char *)stf_config; + offset = offsetof(struct \ + audpp_cmd_cfg_object_params_sidechain,\ + active_flag); + offset_addr = (unsigned short *)(base+offset); + if ((copy_from_user(offset_addr,\ + (void *)ubuf, writecount)) == 0x00) { + stf_config->common.cmd_id = + AUDPP_CMD_CFG_OBJECT_PARAMS; + stf_config->common.stream = + AUDPP_CMD_COPP_STREAM; + stf_config->common.stream_id = 0; + stf_config->common.obj_cfg = + AUDPP_CMD_OBJ0_UPDATE; + stf_config->common.command_type = 0; + MM_ERR("ACDB RX WRITE DATA:\ + AUDPP_CMD_SIDECHAIN_TUNING_FILTER\n"); + result = audpp_dsp_set_stf( + acdb_data.device_info->dev_id,\ + stf_config->active_flag,\ + stf_config, COPP); + if (result) { + MM_ERR("ACDB=> Failed to send \ + STF data to postproc\n"); + } else { + retval = true; + } + } else { + MM_ERR("ACDB DATA WRITE ---\ + STF Rx copy_from_user Fail\n"); + } + } else { + MM_ERR("ACDB DATA WRITE \ + STF kalloc Failed LEN\n"); + } + } + if (stf_config != NULL) + kfree(stf_config); + break; + } + } + return retval; +} +static ssize_t rtc_getsetabid_data_dbg_write(struct file *filp, + const char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + if (rtc_acdb.valid_abid != true) { + MM_INFO("ACDB DATA READ ---INVALID ABID\n"); + rtc_acdb.err = ACDB_RTC_ERR_INVALID_ABID; + } else { + if (rtc_acdb.tx_rx_ctl == ACDB_RTC_RX) { + if (acdb_set_rx_rtc(ubuf, cnt)) { + rtc_acdb.err = ACDB_RTC_SUCCESS; + } else { + rtc_acdb.err = ACDB_RTC_ERR_UNKNOWN_FAILURE; + cnt = 0; + } + } else if (rtc_acdb.tx_rx_ctl == ACDB_RTC_TX) { + if (acdb_set_tx_rtc(ubuf, cnt)) { + rtc_acdb.err = ACDB_RTC_SUCCESS; + } else { + rtc_acdb.err = ACDB_RTC_ERR_UNKNOWN_FAILURE; + cnt = 0; + } + } + } + return cnt; +} + + +static const struct file_operations rtc_acdb_data_debug_fops = { + .open = rtc_getsetabid_data_dbg_open, + .write = rtc_getsetabid_data_dbg_write, + .read = rtc_getsetabid_data_dbg_read +}; + +static const struct file_operations rtc_acdb_debug_fops = { + .open = rtc_getsetabid_dbg_open, + .write = rtc_getsetabid_dbg_write, + .read = rtc_getsetabid_dbg_read +}; + +static void rtc_acdb_deinit(void) +{ + struct rtc_acdb_pmem *rtc_read = &rtc_acdb.rtc_read; + struct rtc_acdb_pmem *rtc_write = &rtc_acdb.rtc_write; + if (get_set_abid_dentry) { + MM_DBG("GetSet ABID remove debugfs\n"); + debugfs_remove(get_set_abid_dentry); + } + + if (get_set_abid_data_dentry) { + MM_DBG("GetSet ABID remove debugfs\n"); + debugfs_remove(get_set_abid_data_dentry); + } + rtc_acdb.abid = 0; + rtc_acdb.acdb_id = 0; + rtc_acdb.cmd_id = 0; + rtc_acdb.err = 1; + rtc_acdb.set_abid = 0; + rtc_acdb.set_iid = 0; + rtc_acdb.tx_rx_ctl = 0; + rtc_acdb.valid_abid = false; + + if (rtc_read->viraddr != NULL || ((void *)rtc_read->phys) != NULL) { + iounmap(rtc_read->viraddr); + pmem_kfree(rtc_read->phys); + } + if (rtc_write->viraddr != NULL || ((void *)rtc_write->phys) != NULL) { + iounmap(rtc_write->viraddr); + pmem_kfree(rtc_write->phys); + } +} + +static bool rtc_acdb_init(void) +{ + struct rtc_acdb_pmem *rtc_read = &rtc_acdb.rtc_read; + struct rtc_acdb_pmem *rtc_write = &rtc_acdb.rtc_write; + s32 result = 0; + char name[sizeof "get_set_abid"+1]; + char name1[sizeof "get_set_abid_data"+1]; + rtc_acdb.abid = 0; + rtc_acdb.acdb_id = 0; + rtc_acdb.cmd_id = 0; + rtc_acdb.err = 1; + rtc_acdb.set_abid = 0; + rtc_acdb.set_iid = 0; + rtc_acdb.valid_abid = false; + rtc_acdb.tx_rx_ctl = 0; + snprintf(name, sizeof name, "get_set_abid"); + get_set_abid_dentry = debugfs_create_file(name, + S_IFREG | S_IRUGO | S_IWUGO, + NULL, NULL, &rtc_acdb_debug_fops); + if (IS_ERR(get_set_abid_dentry)) { + MM_ERR("SET GET ABID debugfs_create_file failed\n"); + return false; + } + + snprintf(name1, sizeof name1, "get_set_abid_data"); + get_set_abid_data_dentry = debugfs_create_file(name1, + S_IFREG | S_IRUGO | S_IWUGO, + NULL, NULL, + &rtc_acdb_data_debug_fops); + if (IS_ERR(get_set_abid_data_dentry)) { + MM_ERR("SET GET ABID DATA debugfs_create_file failed\n"); + return false; + } + + rtc_read->phys = pmem_kalloc(PMEM_RTC_ACDB_QUERY_MEM, + PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + + if (IS_ERR((void *)rtc_read->phys)) { + MM_ERR("ACDB Cannot allocate physical memory\n"); + result = -ENOMEM; + goto error; + } + rtc_read->viraddr = ioremap(rtc_read->phys, PMEM_RTC_ACDB_QUERY_MEM); + + if (rtc_read->viraddr == NULL) { + MM_ERR("ACDB Could not map physical address\n"); + result = -ENOMEM; + goto error; + } + memset(rtc_read->viraddr, 0, PMEM_RTC_ACDB_QUERY_MEM); + + rtc_write->phys = pmem_kalloc(PMEM_RTC_ACDB_QUERY_MEM, + PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + + if (IS_ERR((void *)rtc_write->phys)) { + MM_ERR("ACDB Cannot allocate physical memory\n"); + result = -ENOMEM; + goto error; + } + rtc_write->viraddr = ioremap(rtc_write->phys, PMEM_RTC_ACDB_QUERY_MEM); + + if (rtc_write->viraddr == NULL) { + MM_ERR("ACDB Could not map physical address\n"); + result = -ENOMEM; + goto error; + } + memset(rtc_write->viraddr, 0, PMEM_RTC_ACDB_QUERY_MEM); + init_waitqueue_head(&rtc_acdb.wait); + return true; +error: + MM_DBG("INIT RTC FAILED REMOVING RTC DEBUG FS\n"); + if (get_set_abid_dentry) { + MM_DBG("GetSet ABID remove debugfs\n"); + debugfs_remove(get_set_abid_dentry); + } + + if (get_set_abid_data_dentry) { + MM_DBG("GetSet ABID remove debugfs\n"); + debugfs_remove(get_set_abid_data_dentry); + } + if (rtc_read->viraddr != NULL || ((void *)rtc_read->phys) != NULL) { + iounmap(rtc_read->viraddr); + pmem_kfree(rtc_read->phys); + } + if (rtc_write->viraddr != NULL || ((void *)rtc_write->phys) != NULL) { + iounmap(rtc_write->viraddr); + pmem_kfree(rtc_write->phys); + } + return false; +} +#endif /*CONFIG_DEBUG_FS*/ +static s32 acdb_set_calibration_blk(unsigned long arg) +{ + struct acdb_cmd_device acdb_cmd; + s32 result = 0; + + MM_DBG("acdb_set_calibration_blk\n"); + if (copy_from_user(&acdb_cmd, (struct acdb_cmd_device *)arg, + sizeof(acdb_cmd))) { + MM_ERR("Failed copy command struct from user in" + "acdb_set_calibration_blk\n"); + return -EFAULT; + } + acdb_cmd.phys_buf = (u32 *)acdb_data.paddr; + + MM_DBG("acdb_cmd.phys_buf %x\n", (u32)acdb_cmd.phys_buf); + + result = dalrpc_fcn_8(ACDB_DalACDB_ioctl, acdb_data.handle, + (const void *)&acdb_cmd, sizeof(acdb_cmd), + &acdb_data.acdb_result, + sizeof(acdb_data.acdb_result)); + + if (result < 0) { + MM_ERR("ACDB=> Device Set RPC failure" + " result = %d\n", result); + return -EINVAL; + } else { + MM_ERR("ACDB=> Device Set RPC success\n"); + if (acdb_data.acdb_result.result == ACDB_RES_SUCCESS) + MM_DBG("ACDB_SET_DEVICE Success\n"); + else if (acdb_data.acdb_result.result == ACDB_RES_FAILURE) + MM_ERR("ACDB_SET_DEVICE Failure\n"); + else if (acdb_data.acdb_result.result == ACDB_RES_BADPARM) + MM_ERR("ACDB_SET_DEVICE BadParams\n"); + else + MM_ERR("Unknown error\n"); + } + return result; +} + +static s32 acdb_get_calibration_blk(unsigned long arg) +{ + s32 result = 0; + struct acdb_cmd_device acdb_cmd; + + MM_DBG("acdb_get_calibration_blk\n"); + + if (copy_from_user(&acdb_cmd, (struct acdb_cmd_device *)arg, + sizeof(acdb_cmd))) { + MM_ERR("Failed copy command struct from user in" + "acdb_get_calibration_blk\n"); + return -EFAULT; + } + acdb_cmd.phys_buf = (u32 *)acdb_data.paddr; + MM_ERR("acdb_cmd.phys_buf %x\n", (u32)acdb_cmd.phys_buf); + + result = dalrpc_fcn_8(ACDB_DalACDB_ioctl, acdb_data.handle, + (const void *)&acdb_cmd, sizeof(acdb_cmd), + &acdb_data.acdb_result, + sizeof(acdb_data.acdb_result)); + + if (result < 0) { + MM_ERR("ACDB=> Device Get RPC failure" + " result = %d\n", result); + return -EINVAL; + } else { + MM_ERR("ACDB=> Device Get RPC Success\n"); + if (acdb_data.acdb_result.result == ACDB_RES_SUCCESS) + MM_DBG("ACDB_GET_DEVICE Success\n"); + else if (acdb_data.acdb_result.result == ACDB_RES_FAILURE) + MM_ERR("ACDB_GET_DEVICE Failure\n"); + else if (acdb_data.acdb_result.result == ACDB_RES_BADPARM) + MM_ERR("ACDB_GET_DEVICE BadParams\n"); + else + MM_ERR("Unknown error\n"); + } + return result; +} + +static int audio_acdb_open(struct inode *inode, struct file *file) +{ + MM_DBG("%s\n", __func__); + return 0; +} +static int audio_acdb_release(struct inode *inode, struct file *file) +{ + MM_DBG("%s\n", __func__); + return 0; +} + +static long audio_acdb_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int rc = 0; + unsigned long flags = 0; + struct msm_audio_pmem_info info; + + MM_DBG("%s\n", __func__); + + switch (cmd) { + case AUDIO_SET_EQ: + MM_DBG("IOCTL SET_EQ_CONFIG\n"); + if (copy_from_user(&acdb_data.eq.num_bands, (void *) arg, + sizeof(acdb_data.eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&acdb_data.dsp_lock, flags); + acdb_data.dec_id = 0; + rc = audpp_dsp_set_eq(acdb_data.dec_id, 1, + &acdb_data.eq, COPP); + if (rc < 0) + MM_ERR("AUDPP returned err =%d\n", rc); + spin_unlock_irqrestore(&acdb_data.dsp_lock, flags); + break; + case AUDIO_REGISTER_PMEM: + MM_DBG("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) { + MM_ERR("Cannot copy from user\n"); + return -EFAULT; + } + rc = get_pmem_file(info.fd, &acdb_data.paddr, + &acdb_data.kvaddr, + &acdb_data.pmem_len, + &acdb_data.file); + if (rc == 0) + acdb_data.pmem_fd = info.fd; + break; + case AUDIO_DEREGISTER_PMEM: + if (acdb_data.pmem_fd) + put_pmem_file(acdb_data.file); + break; + case AUDIO_SET_ACDB_BLK: + MM_DBG("IOCTL AUDIO_SET_ACDB_BLK\n"); + rc = acdb_set_calibration_blk(arg); + break; + case AUDIO_GET_ACDB_BLK: + MM_DBG("IOiCTL AUDIO_GET_ACDB_BLK\n"); + rc = acdb_get_calibration_blk(arg); + break; + default: + MM_DBG("Unknown IOCTL%d\n", cmd); + rc = -EINVAL; + } + return rc; +} + +static const struct file_operations acdb_fops = { + .owner = THIS_MODULE, + .open = audio_acdb_open, + .release = audio_acdb_release, + .llseek = no_llseek, + .unlocked_ioctl = audio_acdb_ioctl +}; + +struct miscdevice acdb_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_acdb", + .fops = &acdb_fops, +}; + +static s32 acdb_get_calibration(void) +{ + struct acdb_cmd_get_device_table acdb_cmd; + s32 result = 0; + u32 iterations = 0; + + MM_DBG("acdb state = %d\n", acdb_data.acdb_state); + + acdb_cmd.command_id = ACDB_GET_DEVICE_TABLE; + acdb_cmd.device_id = acdb_data.device_info->acdb_id; + acdb_cmd.network_id = 0x0108B153; + acdb_cmd.sample_rate_id = acdb_data.device_info->sample_rate; + acdb_cmd.total_bytes = ACDB_BUF_SIZE; + acdb_cmd.phys_buf = (u32 *)acdb_data.phys_addr; + MM_DBG("device_id = %d, sampling_freq = %d\n", + acdb_cmd.device_id, acdb_cmd.sample_rate_id); + + do { + result = dalrpc_fcn_8(ACDB_DalACDB_ioctl, acdb_data.handle, + (const void *)&acdb_cmd, sizeof(acdb_cmd), + &acdb_data.acdb_result, + sizeof(acdb_data.acdb_result)); + + if (result < 0) { + MM_ERR("ACDB=> Device table RPC failure" + " result = %d\n", result); + goto error; + } + /*following check is introduced to handle boot up race + condition between AUDCAL SW peers running on apps + and modem (ACDB_RES_BADSTATE indicates modem AUDCAL SW is + not in initialized sate) we need to retry to get ACDB + values*/ + if (acdb_data.acdb_result.result == ACDB_RES_BADSTATE) { + msleep(500); + iterations++; + } else if (acdb_data.acdb_result.result == ACDB_RES_SUCCESS) { + MM_DBG("Modem query for acdb values is successful" + " (iterations = %d)\n", iterations); + acdb_data.acdb_state |= CAL_DATA_READY; + return result; + } else { + MM_ERR("ACDB=> modem failed to fill acdb values," + " reuslt = %d, (iterations = %d)\n", + acdb_data.acdb_result.result, + iterations); + goto error; + } + } while (iterations < MAX_RETRY); + MM_ERR("ACDB=> AUDCAL SW on modem is not in intiailized state (%d)\n", + acdb_data.acdb_result.result); +error: + result = -EINVAL; + return result; +} + +s32 acdb_get_calibration_data(struct acdb_get_block *get_block) +{ + s32 result = -EINVAL; + struct acdb_cmd_device acdb_cmd; + struct acdb_result acdb_result; + + MM_DBG("acdb_get_calibration_data\n"); + + acdb_cmd.command_id = ACDB_GET_DEVICE; + acdb_cmd.network_id = 0x0108B153; + acdb_cmd.device_id = get_block->acdb_id; + acdb_cmd.sample_rate_id = get_block->sample_rate_id; + acdb_cmd.interface_id = get_block->interface_id; + acdb_cmd.algorithm_block_id = get_block->algorithm_block_id; + acdb_cmd.total_bytes = get_block->total_bytes; + acdb_cmd.phys_buf = (u32 *)acdb_data.get_blk_paddr; + + result = dalrpc_fcn_8(ACDB_DalACDB_ioctl, acdb_data.handle, + (const void *)&acdb_cmd, sizeof(acdb_cmd), + &acdb_result, + sizeof(acdb_result)); + + if (result < 0) { + MM_ERR("ACDB=> Device Get RPC failure" + " result = %d\n", result); + goto err_state; + } else { + MM_DBG("ACDB=> Device Get RPC Success\n"); + if (acdb_result.result == ACDB_RES_SUCCESS) { + MM_DBG("ACDB_GET_DEVICE Success\n"); + result = 0; + memcpy(get_block->buf_ptr, acdb_data.get_blk_kvaddr, + get_block->total_bytes); + } else if (acdb_result.result == ACDB_RES_FAILURE) + MM_ERR("ACDB_GET_DEVICE Failure\n"); + else if (acdb_result.result == ACDB_RES_BADPARM) + MM_ERR("ACDB_GET_DEVICE BadParams\n"); + else + MM_ERR("Unknown error\n"); + } +err_state: + return result; +} +EXPORT_SYMBOL(acdb_get_calibration_data); + +static u8 check_device_info_already_present( + struct auddev_evt_audcal_info audcal_info, + struct acdb_cache_node *acdb_cache_free_node) +{ + if ((audcal_info.dev_id == + acdb_cache_free_node->device_info.dev_id) && + (audcal_info.sample_rate == + acdb_cache_free_node->device_info.\ + sample_rate) && + (audcal_info.acdb_id == + acdb_cache_free_node->device_info.acdb_id)) { + MM_DBG("acdb values are already present\n"); + /*if acdb state is not set for CAL_DATA_READY and node status + is filled, acdb state should be updated with CAL_DATA_READY + state*/ + acdb_data.acdb_state |= CAL_DATA_READY; + /*checking for cache node status if it is not filled then the + acdb values are not cleaned from node so update node status + with acdb value filled*/ + if ((acdb_cache_free_node->node_status != ACDB_VALUES_FILLED) && + ((acdb_data.device_info->dev_type & RX_DEVICE) == 1)) { + MM_DBG("device was released earlier\n"); + acdb_cache_free_node->node_status = ACDB_VALUES_FILLED; + return 2; /*node is presnet but status as not filled*/ + } + return 1; /*node is present but status as filled*/ + } + MM_DBG("copying device info into node\n"); + /*as device information is not present in cache copy + the current device information into the node*/ + memcpy(&acdb_cache_free_node->device_info, + &audcal_info, sizeof(audcal_info)); + return 0; /*cant find the node*/ +} + +static struct acdb_iir_block *get_audpp_irr_block(void) +{ + struct header *prs_hdr; + u32 index = 0; + + while (index < acdb_data.acdb_result.used_bytes) { + prs_hdr = (struct header *)(acdb_data.virt_addr + index); + if (prs_hdr->dbor_signature == DBOR_SIGNATURE) { + if (prs_hdr->abid == ABID_AUDIO_IIR_RX) { + if (prs_hdr->iid == IID_AUDIO_IIR_COEFF) + return (struct acdb_iir_block *) + (acdb_data.virt_addr + index + + sizeof(struct header)); + } else { + index += prs_hdr->data_len + + sizeof(struct header); + } + } else { + break; + } + } + return NULL; +} + + +static s32 acdb_fill_audpp_iir(void) +{ + struct acdb_iir_block *acdb_iir; + s32 i = 0; + + acdb_iir = get_audpp_irr_block(); + if (acdb_iir == NULL) { + MM_ERR("unable to find audpp iir block returning\n"); + return -1; + } + memset(acdb_data.pp_iir, 0, sizeof(*acdb_data.pp_iir)); + + acdb_data.pp_iir->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + acdb_data.pp_iir->common.stream = AUDPP_CMD_COPP_STREAM; + acdb_data.pp_iir->common.stream_id = 0; + acdb_data.pp_iir->common.obj_cfg = AUDPP_CMD_OBJ0_UPDATE; + acdb_data.pp_iir->common.command_type = 0; + + acdb_data.pp_iir->active_flag = acdb_iir->enable_flag; + acdb_data.pp_iir->num_bands = acdb_iir->stage_count; + for (; i < acdb_iir->stage_count; i++) { + acdb_data.pp_iir->params_filter.filter_4_params. + numerator_filter[i].numerator_b0_filter_lsw = + acdb_iir->stages[i].b0_lo; + acdb_data.pp_iir->params_filter.filter_4_params. + numerator_filter[i].numerator_b0_filter_msw = + acdb_iir->stages[i].b0_hi; + acdb_data.pp_iir->params_filter.filter_4_params. + numerator_filter[i].numerator_b1_filter_lsw = + acdb_iir->stages[i].b1_lo; + acdb_data.pp_iir->params_filter.filter_4_params. + numerator_filter[i].numerator_b1_filter_msw = + acdb_iir->stages[i].b1_hi; + acdb_data.pp_iir->params_filter.filter_4_params. + numerator_filter[i].numerator_b2_filter_lsw = + acdb_iir->stages[i].b2_lo; + acdb_data.pp_iir->params_filter.filter_4_params. + numerator_filter[i].numerator_b2_filter_msw = + acdb_iir->stages[i].b2_hi; + acdb_data.pp_iir->params_filter.filter_4_params. + denominator_filter[i].denominator_a0_filter_lsw = + acdb_iir->stages_a[i].a1_lo; + acdb_data.pp_iir->params_filter.filter_4_params. + denominator_filter[i].denominator_a0_filter_msw = + acdb_iir->stages_a[i].a1_hi; + acdb_data.pp_iir->params_filter.filter_4_params. + denominator_filter[i].denominator_a1_filter_lsw = + acdb_iir->stages_a[i].a2_lo; + acdb_data.pp_iir->params_filter.filter_4_params. + denominator_filter[i].denominator_a1_filter_msw = + acdb_iir->stages_a[i].a2_hi; + acdb_data.pp_iir->params_filter.filter_4_params. + shift_factor_filter[i].shift_factor_0 = + acdb_iir->shift_factor[i]; + acdb_data.pp_iir->params_filter.filter_4_params.pan_filter[i]. + pan_filter_0 = acdb_iir->pan[i]; + } + return 0; +} + +static void extract_mbadrc(u32 *phy_addr, struct header *prs_hdr, u32 *index) +{ + if (prs_hdr->iid == IID_MBADRC_EXT_BUFF) { + MM_DBG("Got IID = IID_MBADRC_EXT_BUFF\n"); + *phy_addr = acdb_data.phys_addr + *index + + sizeof(struct header); + memcpy(acdb_data.mbadrc_block.ext_buf, + (acdb_data.virt_addr + *index + + sizeof(struct header)), 196*2); + MM_DBG("phy_addr = %x\n", *phy_addr); + *index += prs_hdr->data_len + sizeof(struct header); + } else if (prs_hdr->iid == IID_MBADRC_BAND_CONFIG) { + MM_DBG("Got IID == IID_MBADRC_BAND_CONFIG\n"); + memcpy(acdb_data.mbadrc_block.band_config, (acdb_data.virt_addr + + *index + sizeof(struct header)), + sizeof(struct mbadrc_band_config_type) * + acdb_data.mbadrc_block.parameters.\ + mbadrc_num_bands); + *index += prs_hdr->data_len + sizeof(struct header); + } else if (prs_hdr->iid == IID_MBADRC_PARAMETERS) { + struct mbadrc_parameter *tmp; + tmp = (struct mbadrc_parameter *)(acdb_data.virt_addr + *index + + sizeof(struct header)); + MM_DBG("Got IID == IID_MBADRC_PARAMETERS\n"); + acdb_data.mbadrc_block.parameters.mbadrc_enable = + tmp->mbadrc_enable; + acdb_data.mbadrc_block.parameters.mbadrc_num_bands = + tmp->mbadrc_num_bands; + acdb_data.mbadrc_block.parameters.mbadrc_down_sample_level = + tmp->mbadrc_down_sample_level; + acdb_data.mbadrc_block.parameters.mbadrc_delay = + tmp->mbadrc_delay; + *index += prs_hdr->data_len + sizeof(struct header); + } +} + +static void get_audpp_mbadrc_block(u32 *phy_addr) +{ + struct header *prs_hdr; + u32 index = 0; + + while (index < acdb_data.acdb_result.used_bytes) { + prs_hdr = (struct header *)(acdb_data.virt_addr + index); + + if (prs_hdr->dbor_signature == DBOR_SIGNATURE) { + if (prs_hdr->abid == ABID_AUDIO_MBADRC_RX) { + if ((prs_hdr->iid == IID_MBADRC_EXT_BUFF) + || (prs_hdr->iid == + IID_MBADRC_BAND_CONFIG) + || (prs_hdr->iid == + IID_MBADRC_PARAMETERS)) { + extract_mbadrc(phy_addr, prs_hdr, + &index); + } + } else { + index += prs_hdr->data_len + + sizeof(struct header); + } + } else { + break; + } + } +} + +static s32 acdb_fill_audpp_mbadrc(void) +{ + u32 mbadrc_phys_addr = -1; + get_audpp_mbadrc_block(&mbadrc_phys_addr); + if (IS_ERR_VALUE(mbadrc_phys_addr)) { + MM_ERR("failed to get mbadrc block\n"); + return -1; + } + + memset(acdb_data.pp_mbadrc, 0, sizeof(*acdb_data.pp_mbadrc)); + + acdb_data.pp_mbadrc->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + acdb_data.pp_mbadrc->common.stream = AUDPP_CMD_COPP_STREAM; + acdb_data.pp_mbadrc->common.stream_id = 0; + acdb_data.pp_mbadrc->common.obj_cfg = AUDPP_CMD_OBJ0_UPDATE; + acdb_data.pp_mbadrc->common.command_type = 0; + + acdb_data.pp_mbadrc->enable = acdb_data.mbadrc_block.\ + parameters.mbadrc_enable; + acdb_data.pp_mbadrc->num_bands = + acdb_data.mbadrc_block.\ + parameters.mbadrc_num_bands; + acdb_data.pp_mbadrc->down_samp_level = + acdb_data.mbadrc_block.parameters.\ + mbadrc_down_sample_level; + acdb_data.pp_mbadrc->adrc_delay = + acdb_data.mbadrc_block.parameters.\ + mbadrc_delay; + + if (acdb_data.mbadrc_block.parameters.mbadrc_num_bands > 1) + acdb_data.pp_mbadrc->ext_buf_size = (97 * 2) + + (33 * 2 * (acdb_data.mbadrc_block.parameters.\ + mbadrc_num_bands - 2)); + + acdb_data.pp_mbadrc->ext_partition = 0; + acdb_data.pp_mbadrc->ext_buf_lsw = (u16)(mbadrc_phys_addr\ + & 0xFFFF); + acdb_data.pp_mbadrc->ext_buf_msw = (u16)((mbadrc_phys_addr\ + & 0xFFFF0000) >> 16); + memcpy(acdb_data.pp_mbadrc->adrc_band, acdb_data.mbadrc_block.\ + band_config, + sizeof(struct mbadrc_band_config_type) * + acdb_data.mbadrc_block.parameters.mbadrc_num_bands); + return 0; +} + +static struct acdb_calib_gain_rx *get_audpp_cal_gain(void) +{ + struct header *prs_hdr; + u32 index = 0; + + while (index < acdb_data.acdb_result.used_bytes) { + prs_hdr = (struct header *)(acdb_data.virt_addr + index); + if (prs_hdr->dbor_signature == DBOR_SIGNATURE) { + if (prs_hdr->abid == ABID_AUDIO_CALIBRATION_GAIN_RX) { + if (prs_hdr->iid == + IID_AUDIO_CALIBRATION_GAIN_RX) { + MM_DBG("Got audpp_calib_gain_rx" + " block\n"); + return (struct acdb_calib_gain_rx *) + (acdb_data.virt_addr + index + + sizeof(struct header)); + } + } else { + index += prs_hdr->data_len + + sizeof(struct header); + } + } else { + break; + } + } + return NULL; +} + +static s32 acdb_fill_audpp_cal_gain(void) +{ + struct acdb_calib_gain_rx *acdb_calib_gain_rx = NULL; + + acdb_calib_gain_rx = get_audpp_cal_gain(); + if (acdb_calib_gain_rx == NULL) { + MM_ERR("unable to find audpp" + " calibration gain block returning\n"); + return -1; + } + MM_DBG("Calibration value" + " for calib_gain_rx %d\n", acdb_calib_gain_rx->audppcalgain); + memset(acdb_data.calib_gain_rx, 0, sizeof(*acdb_data.calib_gain_rx)); + + acdb_data.calib_gain_rx->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + acdb_data.calib_gain_rx->common.stream = AUDPP_CMD_COPP_STREAM; + acdb_data.calib_gain_rx->common.stream_id = 0; + acdb_data.calib_gain_rx->common.obj_cfg = AUDPP_CMD_OBJ0_UPDATE; + acdb_data.calib_gain_rx->common.command_type = 0; + + acdb_data.calib_gain_rx->audppcalgain = + acdb_calib_gain_rx->audppcalgain; + return 0; +} + +static void extract_pbe_block(struct header *prs_hdr, u32 *index) +{ + if (prs_hdr->iid == IID_AUDIO_PBE_RX_ENABLE_FLAG) { + MM_DBG("Got IID = IID_AUDIO_PBE_RX_ENABLE\n"); + acdb_data.pbe_enable_flag = (u16 *)(acdb_data.virt_addr + + *index + + sizeof(struct header)); + *index += prs_hdr->data_len + sizeof(struct header); + } else if (prs_hdr->iid == IID_PBE_CONFIG_PARAMETERS) { + MM_DBG("Got IID == IID_PBE_CONFIG_PARAMETERS\n"); + acdb_data.pbe_blk = (struct acdb_pbe_block *) + (acdb_data.virt_addr + *index + + sizeof(struct header)); + *index += prs_hdr->data_len + sizeof(struct header); + } +} + +static s32 get_audpp_pbe_block(void) +{ + struct header *prs_hdr; + u32 index = 0; + s32 result = -1; + + while (index < acdb_data.acdb_result.used_bytes) { + prs_hdr = (struct header *)(acdb_data.virt_addr + index); + + if (prs_hdr->dbor_signature == DBOR_SIGNATURE) { + if (prs_hdr->abid == ABID_AUDIO_PBE_RX) { + if ((prs_hdr->iid == IID_PBE_CONFIG_PARAMETERS) + || (prs_hdr->iid == + IID_AUDIO_PBE_RX_ENABLE_FLAG)) { + extract_pbe_block(prs_hdr, &index); + result = 0; + } + } else { + index += prs_hdr->data_len + + sizeof(struct header); + } + } else { + break; + } + } + return result; +} + +static s32 acdb_fill_audpp_pbe(void) +{ + s32 result = -1; + + result = get_audpp_pbe_block(); + if (IS_ERR_VALUE(result)) + return result; + memset(acdb_data.pbe_block, 0, sizeof(*acdb_data.pbe_block)); + + acdb_data.pbe_block->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + acdb_data.pbe_block->common.stream = AUDPP_CMD_COPP_STREAM; + acdb_data.pbe_block->common.stream_id = 0; + acdb_data.pbe_block->common.obj_cfg = AUDPP_CMD_OBJ0_UPDATE; + acdb_data.pbe_block->common.command_type = 0; + acdb_data.pbe_block->pbe_enable = *acdb_data.pbe_enable_flag; + + acdb_data.pbe_block->realbassmix = acdb_data.pbe_blk->realbassmix; + acdb_data.pbe_block->basscolorcontrol = + acdb_data.pbe_blk->basscolorcontrol; + acdb_data.pbe_block->mainchaindelay = acdb_data.pbe_blk->mainchaindelay; + acdb_data.pbe_block->xoverfltorder = acdb_data.pbe_blk->xoverfltorder; + acdb_data.pbe_block->bandpassfltorder = + acdb_data.pbe_blk->bandpassfltorder; + acdb_data.pbe_block->adrcdelay = acdb_data.pbe_blk->adrcdelay; + acdb_data.pbe_block->downsamplelevel = + acdb_data.pbe_blk->downsamplelevel; + acdb_data.pbe_block->comprmstav = acdb_data.pbe_blk->comprmstav; + acdb_data.pbe_block->expthreshold = acdb_data.pbe_blk->expthreshold; + acdb_data.pbe_block->expslope = acdb_data.pbe_blk->expslope; + acdb_data.pbe_block->compthreshold = acdb_data.pbe_blk->compthreshold; + acdb_data.pbe_block->compslope = acdb_data.pbe_blk->compslope; + acdb_data.pbe_block->cpmpattack_lsw = acdb_data.pbe_blk->cpmpattack_lsw; + acdb_data.pbe_block->compattack_msw = acdb_data.pbe_blk->compattack_msw; + acdb_data.pbe_block->comprelease_lsw = + acdb_data.pbe_blk->comprelease_lsw; + acdb_data.pbe_block->comprelease_msw = + acdb_data.pbe_blk->comprelease_msw; + acdb_data.pbe_block->compmakeupgain = acdb_data.pbe_blk->compmakeupgain; + acdb_data.pbe_block->baselimthreshold = + acdb_data.pbe_blk->baselimthreshold; + acdb_data.pbe_block->highlimthreshold = + acdb_data.pbe_blk->highlimthreshold; + acdb_data.pbe_block->basslimmakeupgain = + acdb_data.pbe_blk->basslimmakeupgain; + acdb_data.pbe_block->highlimmakeupgain = + acdb_data.pbe_blk->highlimmakeupgain; + acdb_data.pbe_block->limbassgrc = acdb_data.pbe_blk->limbassgrc; + acdb_data.pbe_block->limhighgrc = acdb_data.pbe_blk->limhighgrc; + acdb_data.pbe_block->limdelay = acdb_data.pbe_blk->limdelay; + memcpy(acdb_data.pbe_block->filter_coeffs, + acdb_data.pbe_blk->filter_coeffs, sizeof(u16)*90); + acdb_data.pbe_block->extpartition = 0; + acdb_data.pbe_block->extbuffsize_lsw = PBE_BUF_SIZE; + acdb_data.pbe_block->extbuffsize_msw = 0; + acdb_data.pbe_block->extbuffstart_lsw = ((u32)acdb_data.pbe_extbuff + & 0xFFFF); + acdb_data.pbe_block->extbuffstart_msw = (((u32)acdb_data.pbe_extbuff + & 0xFFFF0000) >> 16); + return 0; +} + + +static s32 acdb_calibrate_audpp(void) +{ + s32 result = 0; + + result = acdb_fill_audpp_iir(); + if (!IS_ERR_VALUE(result)) { + result = audpp_dsp_set_rx_iir(acdb_data.device_info->dev_id, + acdb_data.pp_iir->active_flag, + acdb_data.pp_iir, COPP); + if (result) { + MM_ERR("ACDB=> Failed to send IIR data to postproc\n"); + result = -EINVAL; + goto done; + } else + MM_DBG("AUDPP is calibrated with IIR parameters" + " for COPP ID %d\n", + acdb_data.device_info->dev_id); + } + result = acdb_fill_audpp_mbadrc(); + if (!IS_ERR_VALUE(result)) { + result = audpp_dsp_set_mbadrc(acdb_data.device_info->dev_id, + acdb_data.pp_mbadrc->enable, + acdb_data.pp_mbadrc, COPP); + if (result) { + MM_ERR("ACDB=> Failed to send MBADRC data to" + " postproc\n"); + result = -EINVAL; + goto done; + } else + MM_DBG("AUDPP is calibrated with MBADRC parameters" + " for COPP ID %d\n", + acdb_data.device_info->dev_id); + } + result = acdb_fill_audpp_cal_gain(); + if (!(IS_ERR_VALUE(result))) { + result = audpp_dsp_set_gain_rx(acdb_data.device_info->dev_id, + acdb_data.calib_gain_rx, COPP); + if (result) { + MM_ERR("ACDB=> Failed to send gain_rx" + " data to postproc\n"); + result = -EINVAL; + goto done; + } else + MM_DBG("AUDPP is calibrated with calib_gain_rx\n"); + } + result = acdb_fill_audpp_pbe(); + if (!(IS_ERR_VALUE(result))) { + result = audpp_dsp_set_pbe(acdb_data.device_info->dev_id, + acdb_data.pbe_block->pbe_enable, + acdb_data.pbe_block, COPP); + if (result) { + MM_ERR("ACDB=> Failed to send pbe block" + "data to postproc\n"); + result = -EINVAL; + goto done; + } + MM_DBG("AUDPP is calibarted with PBE\n"); + } +done: + return result; +} + +static struct acdb_agc_block *get_audpreproc_agc_block(void) +{ + struct header *prs_hdr; + u32 index = 0; + + while (index < acdb_data.acdb_result.used_bytes) { + prs_hdr = (struct header *)(acdb_data.virt_addr + index); + if (prs_hdr->dbor_signature == DBOR_SIGNATURE) { + if (prs_hdr->abid == ABID_AUDIO_AGC_TX) { + if (prs_hdr->iid == IID_AUDIO_AGC_PARAMETERS) { + MM_DBG("GOT ABID_AUDIO_AGC_TX\n"); + return (struct acdb_agc_block *) + (acdb_data.virt_addr + index + + sizeof(struct header)); + } + } else { + index += prs_hdr->data_len + + sizeof(struct header); + } + } else { + break; + } + } + return NULL; +} + +static s32 acdb_fill_audpreproc_agc(void) +{ + struct acdb_agc_block *acdb_agc; + + acdb_agc = get_audpreproc_agc_block(); + if (!acdb_agc) { + MM_DBG("unable to find preproc agc parameters winding up\n"); + return -1; + } + memset(acdb_data.preproc_agc, 0, sizeof(*acdb_data.preproc_agc)); + acdb_data.preproc_agc->cmd_id = AUDPREPROC_CMD_CFG_AGC_PARAMS; + acdb_data.preproc_agc->stream_id = acdb_data.preproc_stream_id; + /* 0xFE00 to configure all parameters */ + acdb_data.preproc_agc->tx_agc_param_mask = 0xFFFF; + + if (acdb_agc->enable_status) + acdb_data.preproc_agc->tx_agc_enable_flag = + AUDPREPROC_CMD_TX_AGC_ENA_FLAG_ENA; + else + acdb_data.preproc_agc->tx_agc_enable_flag = + AUDPREPROC_CMD_TX_AGC_ENA_FLAG_DIS; + + acdb_data.preproc_agc->comp_rlink_static_gain = + acdb_agc->comp_rlink_static_gain; + acdb_data.preproc_agc->comp_rlink_aig_flag = + acdb_agc->comp_rlink_aig_flag; + acdb_data.preproc_agc->expander_rlink_th = + acdb_agc->exp_rlink_threshold; + acdb_data.preproc_agc->expander_rlink_slope = + acdb_agc->exp_rlink_slope; + acdb_data.preproc_agc->compressor_rlink_th = + acdb_agc->comp_rlink_threshold; + acdb_data.preproc_agc->compressor_rlink_slope = + acdb_agc->comp_rlink_slope; + + /* 0xFFF0 to configure all parameters */ + acdb_data.preproc_agc->tx_adc_agc_param_mask = 0xFFFF; + + acdb_data.preproc_agc->comp_rlink_aig_attackk = + acdb_agc->comp_rlink_aig_attack_k; + acdb_data.preproc_agc->comp_rlink_aig_leak_down = + acdb_agc->comp_rlink_aig_leak_down; + acdb_data.preproc_agc->comp_rlink_aig_leak_up = + acdb_agc->comp_rlink_aig_leak_up; + acdb_data.preproc_agc->comp_rlink_aig_max = + acdb_agc->comp_rlink_aig_max; + acdb_data.preproc_agc->comp_rlink_aig_min = + acdb_agc->comp_rlink_aig_min; + acdb_data.preproc_agc->comp_rlink_aig_releasek = + acdb_agc->comp_rlink_aig_release_k; + acdb_data.preproc_agc->comp_rlink_aig_leakrate_fast = + acdb_agc->comp_rlink_aig_sm_leak_rate_fast; + acdb_data.preproc_agc->comp_rlink_aig_leakrate_slow = + acdb_agc->comp_rlink_aig_sm_leak_rate_slow; + acdb_data.preproc_agc->comp_rlink_attackk_msw = + acdb_agc->comp_rlink_attack_k_msw; + acdb_data.preproc_agc->comp_rlink_attackk_lsw = + acdb_agc->comp_rlink_attack_k_lsw; + acdb_data.preproc_agc->comp_rlink_delay = + acdb_agc->comp_rlink_delay; + acdb_data.preproc_agc->comp_rlink_releasek_msw = + acdb_agc->comp_rlink_release_k_msw; + acdb_data.preproc_agc->comp_rlink_releasek_lsw = + acdb_agc->comp_rlink_release_k_lsw; + acdb_data.preproc_agc->comp_rlink_rms_tav = + acdb_agc->comp_rlink_rms_trav; + return 0; +} + +static struct acdb_iir_block *get_audpreproc_irr_block(void) +{ + + struct header *prs_hdr; + u32 index = 0; + + while (index < acdb_data.acdb_result.used_bytes) { + prs_hdr = (struct header *)(acdb_data.virt_addr + index); + + if (prs_hdr->dbor_signature == DBOR_SIGNATURE) { + if (prs_hdr->abid == ABID_AUDIO_IIR_TX) { + if (prs_hdr->iid == IID_AUDIO_IIR_COEFF) + return (struct acdb_iir_block *) + (acdb_data.virt_addr + index + + sizeof(struct header)); + } else { + index += prs_hdr->data_len + + sizeof(struct header); + } + } else { + break; + } + } + return NULL; +} + + +static s32 acdb_fill_audpreproc_iir(void) +{ + struct acdb_iir_block *acdb_iir; + + + acdb_iir = get_audpreproc_irr_block(); + if (!acdb_iir) { + MM_DBG("unable to find preproc iir parameters winding up\n"); + return -1; + } + memset(acdb_data.preproc_iir, 0, sizeof(*acdb_data.preproc_iir)); + + acdb_data.preproc_iir->cmd_id = + AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS; + acdb_data.preproc_iir->stream_id = acdb_data.preproc_stream_id; + acdb_data.preproc_iir->active_flag = acdb_iir->enable_flag; + acdb_data.preproc_iir->num_bands = acdb_iir->stage_count; + + acdb_data.preproc_iir->numerator_coeff_b0_filter0_lsw = + acdb_iir->stages[0].b0_lo; + acdb_data.preproc_iir->numerator_coeff_b0_filter0_msw = + acdb_iir->stages[0].b0_hi; + acdb_data.preproc_iir->numerator_coeff_b1_filter0_lsw = + acdb_iir->stages[0].b1_lo; + acdb_data.preproc_iir->numerator_coeff_b1_filter0_msw = + acdb_iir->stages[0].b1_hi; + acdb_data.preproc_iir->numerator_coeff_b2_filter0_lsw = + acdb_iir->stages[0].b2_lo; + acdb_data.preproc_iir->numerator_coeff_b2_filter0_msw = + acdb_iir->stages[0].b2_hi; + + acdb_data.preproc_iir->numerator_coeff_b0_filter1_lsw = + acdb_iir->stages[1].b0_lo; + acdb_data.preproc_iir->numerator_coeff_b0_filter1_msw = + acdb_iir->stages[1].b0_hi; + acdb_data.preproc_iir->numerator_coeff_b1_filter1_lsw = + acdb_iir->stages[1].b1_lo; + acdb_data.preproc_iir->numerator_coeff_b1_filter1_msw = + acdb_iir->stages[1].b1_hi; + acdb_data.preproc_iir->numerator_coeff_b2_filter1_lsw = + acdb_iir->stages[1].b2_lo; + acdb_data.preproc_iir->numerator_coeff_b2_filter1_msw = + acdb_iir->stages[1].b2_hi; + + acdb_data.preproc_iir->numerator_coeff_b0_filter2_lsw = + acdb_iir->stages[2].b0_lo; + acdb_data.preproc_iir->numerator_coeff_b0_filter2_msw = + acdb_iir->stages[2].b0_hi; + acdb_data.preproc_iir->numerator_coeff_b1_filter2_lsw = + acdb_iir->stages[2].b1_lo; + acdb_data.preproc_iir->numerator_coeff_b1_filter2_msw = + acdb_iir->stages[2].b1_hi; + acdb_data.preproc_iir->numerator_coeff_b2_filter2_lsw = + acdb_iir->stages[2].b2_lo; + acdb_data.preproc_iir->numerator_coeff_b2_filter2_msw = + acdb_iir->stages[2].b2_hi; + + acdb_data.preproc_iir->numerator_coeff_b0_filter3_lsw = + acdb_iir->stages[3].b0_lo; + acdb_data.preproc_iir->numerator_coeff_b0_filter3_msw = + acdb_iir->stages[3].b0_hi; + acdb_data.preproc_iir->numerator_coeff_b1_filter3_lsw = + acdb_iir->stages[3].b1_lo; + acdb_data.preproc_iir->numerator_coeff_b1_filter3_msw = + acdb_iir->stages[3].b1_hi; + acdb_data.preproc_iir->numerator_coeff_b2_filter3_lsw = + acdb_iir->stages[3].b2_lo; + acdb_data.preproc_iir->numerator_coeff_b2_filter3_msw = + acdb_iir->stages[3].b2_hi; + + acdb_data.preproc_iir->denominator_coeff_a0_filter0_lsw = + acdb_iir->stages_a[0].a1_lo; + acdb_data.preproc_iir->denominator_coeff_a0_filter0_msw = + acdb_iir->stages_a[0].a1_hi; + acdb_data.preproc_iir->denominator_coeff_a1_filter0_lsw = + acdb_iir->stages_a[0].a2_lo; + acdb_data.preproc_iir->denominator_coeff_a1_filter0_msw = + acdb_iir->stages_a[0].a2_hi; + + acdb_data.preproc_iir->denominator_coeff_a0_filter1_lsw = + acdb_iir->stages_a[1].a1_lo; + acdb_data.preproc_iir->denominator_coeff_a0_filter1_msw = + acdb_iir->stages_a[1].a1_hi; + acdb_data.preproc_iir->denominator_coeff_a1_filter1_lsw = + acdb_iir->stages_a[1].a2_lo; + acdb_data.preproc_iir->denominator_coeff_a1_filter1_msw = + acdb_iir->stages_a[1].a2_hi; + + acdb_data.preproc_iir->denominator_coeff_a0_filter2_lsw = + acdb_iir->stages_a[2].a1_lo; + acdb_data.preproc_iir->denominator_coeff_a0_filter2_msw = + acdb_iir->stages_a[2].a1_hi; + acdb_data.preproc_iir->denominator_coeff_a1_filter2_lsw = + acdb_iir->stages_a[2].a2_lo; + acdb_data.preproc_iir->denominator_coeff_a1_filter2_msw = + acdb_iir->stages_a[2].a2_hi; + + acdb_data.preproc_iir->denominator_coeff_a0_filter3_lsw = + acdb_iir->stages_a[3].a1_lo; + acdb_data.preproc_iir->denominator_coeff_a0_filter3_msw = + acdb_iir->stages_a[3].a1_hi; + acdb_data.preproc_iir->denominator_coeff_a1_filter3_lsw = + acdb_iir->stages_a[3].a2_lo; + acdb_data.preproc_iir->denominator_coeff_a1_filter3_msw = + acdb_iir->stages_a[3].a2_hi; + + acdb_data.preproc_iir->shift_factor_filter0 = + acdb_iir->shift_factor[0]; + acdb_data.preproc_iir->shift_factor_filter1 = + acdb_iir->shift_factor[1]; + acdb_data.preproc_iir->shift_factor_filter2 = + acdb_iir->shift_factor[2]; + acdb_data.preproc_iir->shift_factor_filter3 = + acdb_iir->shift_factor[3]; + + acdb_data.preproc_iir->pan_of_filter0 = + acdb_iir->pan[0]; + acdb_data.preproc_iir->pan_of_filter1 = + acdb_iir->pan[1]; + acdb_data.preproc_iir->pan_of_filter2 = + acdb_iir->pan[2]; + acdb_data.preproc_iir->pan_of_filter3 = + acdb_iir->pan[3]; + return 0; +} + +static struct acdb_calib_gain_tx *get_audpreproc_cal_gain(void) +{ + struct header *prs_hdr; + u32 index = 0; + + while (index < acdb_data.acdb_result.used_bytes) { + prs_hdr = (struct header *)(acdb_data.virt_addr + index); + if (prs_hdr->dbor_signature == DBOR_SIGNATURE) { + if (prs_hdr->abid == ABID_AUDIO_CALIBRATION_GAIN_TX) { + if (prs_hdr->iid == + IID_AUDIO_CALIBRATION_GAIN_TX) { + MM_DBG("Got audpreproc_calib_gain_tx" + " block\n"); + return (struct acdb_calib_gain_tx *) + (acdb_data.virt_addr + index + + sizeof(struct header)); + } + } else { + index += prs_hdr->data_len + + sizeof(struct header); + } + } else { + break; + } + } + return NULL; +} + +static s32 acdb_fill_audpreproc_cal_gain(void) +{ + struct acdb_calib_gain_tx *acdb_calib_gain_tx = NULL; + + acdb_calib_gain_tx = get_audpreproc_cal_gain(); + if (acdb_calib_gain_tx == NULL) { + MM_ERR("unable to find audpreproc" + " calibration block returning\n"); + return -1; + } + MM_DBG("Calibration value" + " for calib_gain_tx %d\n", acdb_calib_gain_tx->audprecalgain); + memset(acdb_data.calib_gain_tx, 0, sizeof(*acdb_data.calib_gain_tx)); + + acdb_data.calib_gain_tx->cmd_id = + AUDPREPROC_CMD_CFG_CAL_GAIN_PARAMS; + acdb_data.calib_gain_tx->stream_id = acdb_data.preproc_stream_id; + acdb_data.calib_gain_tx->audprecalgain = + acdb_calib_gain_tx->audprecalgain; + return 0; +} + +static struct acdb_rmc_block *get_rmc_blk(void) +{ + struct header *prs_hdr; + u32 index = 0; + + while (index < acdb_data.acdb_result.used_bytes) { + prs_hdr = (struct header *)(acdb_data.virt_addr + index); + if (prs_hdr->dbor_signature == DBOR_SIGNATURE) { + if (prs_hdr->abid == ABID_AUDIO_RMC_TX) { + if (prs_hdr->iid == + IID_AUDIO_RMC_PARAM) { + MM_DBG("Got afe_rmc block\n"); + return (struct acdb_rmc_block *) + (acdb_data.virt_addr + index + + sizeof(struct header)); + } + } else { + index += prs_hdr->data_len + + sizeof(struct header); + } + } else { + break; + } + } + return NULL; +} + +struct acdb_fluence_block *get_audpp_fluence_block(void) +{ + struct header *prs_hdr; + u32 index = 0; + + while (index < acdb_data.acdb_result.used_bytes) { + prs_hdr = (struct header *)(acdb_data.virt_addr + index); + + if (prs_hdr->dbor_signature == DBOR_SIGNATURE) { + if (prs_hdr->abid == ABID_AUDIO_FLUENCE_TX) { + if (prs_hdr->iid == IID_AUDIO_FLUENCE_TX) { + MM_DBG("got fluence block\n"); + return (struct acdb_fluence_block *) + (acdb_data.virt_addr + index + + sizeof(struct header)); + } + } else { + index += prs_hdr->data_len + + sizeof(struct header); + } + } else { + break; + } + } + return NULL; +} + +static s32 acdb_fill_audpreproc_fluence(void) +{ + struct acdb_fluence_block *fluence_block = NULL; + fluence_block = get_audpp_fluence_block(); + if (!fluence_block) { + MM_ERR("error in finding fluence block\n"); + return -EPERM; + } + memset(&acdb_data.preproc_lvnv, 0, sizeof( + struct audpreproc_cmd_cfg_lvnv_param)); + memcpy(acdb_data.fluence_extbuff_virt, + &fluence_block->cs_tuningMode, + (sizeof(struct acdb_fluence_block) - + sizeof(fluence_block->csmode))); + acdb_data.preproc_lvnv.cmd_id = AUDPREPROC_CMD_CFG_LVNV_PARMS; + acdb_data.preproc_lvnv.stream_id = acdb_data.preproc_stream_id; + acdb_data.preproc_lvnv.cs_mode = fluence_block->csmode; + acdb_data.preproc_lvnv.lvnv_ext_buf_size = FLUENCE_BUF_SIZE; + acdb_data.preproc_lvnv.lvnv_ext_buf_start_lsw =\ + ((u32)(acdb_data.fluence_extbuff)\ + & 0x0000FFFF); + acdb_data.preproc_lvnv.lvnv_ext_buf_start_msw =\ + (((u32)acdb_data.fluence_extbuff\ + & 0xFFFF0000) >> 16); + return 0; +} + +s32 acdb_calibrate_audpreproc(void) +{ + s32 result = 0; + struct acdb_rmc_block *acdb_rmc = NULL; + + result = acdb_fill_audpreproc_agc(); + if (!IS_ERR_VALUE(result)) { + result = audpreproc_dsp_set_agc(acdb_data.preproc_agc, sizeof( + struct audpreproc_cmd_cfg_agc_params)); + if (result) { + MM_ERR("ACDB=> Failed to send AGC data to preproc)\n"); + result = -EINVAL; + goto done; + } else + MM_DBG("AUDPREC is calibrated with AGC parameters" + " for COPP ID %d and AUDREC session %d\n", + acdb_data.device_info->dev_id, + acdb_data.preproc_stream_id); + } + result = acdb_fill_audpreproc_iir(); + if (!IS_ERR_VALUE(result)) { + result = audpreproc_dsp_set_iir(acdb_data.preproc_iir, + sizeof(struct\ + audpreproc_cmd_cfg_iir_tuning_filter_params)); + if (result) { + MM_ERR("ACDB=> Failed to send IIR data to preproc\n"); + result = -EINVAL; + goto done; + } else + MM_DBG("audpreproc is calibrated with iir parameters" + " for COPP ID %d and AUREC session %d\n", + acdb_data.device_info->dev_id, + acdb_data.preproc_stream_id); + } + result = acdb_fill_audpreproc_cal_gain(); + if (!(IS_ERR_VALUE(result))) { + result = audpreproc_dsp_set_gain_tx(acdb_data.calib_gain_tx, + sizeof(struct audpreproc_cmd_cfg_cal_gain)); + if (result) { + MM_ERR("ACDB=> Failed to send calib_gain_tx" + " data to preproc\n"); + result = -EINVAL; + goto done; + } else + MM_DBG("AUDPREPROC is calibrated" + " with calib_gain_tx\n"); + } + acdb_rmc = get_rmc_blk(); + if (acdb_rmc != NULL) { + result = afe_config_rmc_block(acdb_rmc); + if (result) { + MM_ERR("ACDB=> Failed to send rmc" + " data to afe\n"); + result = -EINVAL; + goto done; + } else + MM_DBG("AFE is calibrated with rmc params\n"); + } else + MM_DBG("RMC block was not found\n"); + if (!acdb_data.fleuce_feature_status[acdb_data.preproc_stream_id]) { + result = acdb_fill_audpreproc_fluence(); + if (!(IS_ERR_VALUE(result))) { + result = audpreproc_dsp_set_lvnv( + &acdb_data.preproc_lvnv, + sizeof(struct\ + audpreproc_cmd_cfg_lvnv_param)); + if (result) { + MM_ERR("ACDB=> Failed to send lvnv " + "data to preproc\n"); + result = -EINVAL; + goto done; + } else + MM_DBG("AUDPREPROC is calibrated" + " with lvnv parameters\n"); + } else + MM_ERR("fluence block is not found\n"); + } else + MM_DBG("fluence block override\n"); +done: + return result; +} + +static s32 acdb_send_calibration(void) +{ + s32 result = 0; + + if ((acdb_data.device_info->dev_type & RX_DEVICE) == 1) { + result = acdb_calibrate_audpp(); + if (result) + goto done; + } else if ((acdb_data.device_info->dev_type & TX_DEVICE) == 2) { + result = acdb_calibrate_audpreproc(); + if (result) + goto done; + if (acdb_data.preproc_stream_id == 0) + acdb_data.audrec_applied |= AUDREC0_READY; + else if (acdb_data.preproc_stream_id == 1) + acdb_data.audrec_applied |= AUDREC1_READY; + else if (acdb_data.preproc_stream_id == 2) + acdb_data.audrec_applied |= AUDREC2_READY; + MM_DBG("acdb_data.audrec_applied = %x\n", + acdb_data.audrec_applied); + } +done: + return result; +} + +static u8 check_tx_acdb_values_cached(void) +{ + u8 stream_id = acdb_data.preproc_stream_id; + + if ((acdb_data.device_info->dev_id == + acdb_cache_tx[stream_id].device_info.dev_id) && + (acdb_data.device_info->sample_rate == + acdb_cache_tx[stream_id].device_info.sample_rate) && + (acdb_data.device_info->acdb_id == + acdb_cache_tx[stream_id].device_info.acdb_id) && + (acdb_cache_tx[stream_id].node_status == + ACDB_VALUES_FILLED)) + return 0; + else + return 1; +} + +static void handle_tx_device_ready_callback(void) +{ + u8 i = 0; + u8 ret = 0; + u8 acdb_value_apply = 0; + u8 result = 0; + u8 stream_id = acdb_data.preproc_stream_id; + + if (acdb_data.multiple_sessions) { + for (i = 0; i < MAX_AUDREC_SESSIONS; i++) { + /*check is to exclude copying acdb values in the + current node pointed by acdb_data structure*/ + if (acdb_cache_tx[i].phys_addr_acdb_values != + acdb_data.phys_addr) { + ret = check_device_info_already_present(\ + *acdb_data.device_info, + &acdb_cache_tx[i]); + if (ret) { + memcpy((char *)acdb_cache_tx[i].\ + virt_addr_acdb_values, + (char *)acdb_data.virt_addr, + ACDB_BUF_SIZE); + acdb_cache_tx[i].node_status = + ACDB_VALUES_FILLED; + } + } + } + acdb_data.multiple_sessions = 0; + } + /*check wheather AUDREC enabled before device call backs*/ + if ((acdb_data.acdb_state & AUDREC0_READY) && + !(acdb_data.audrec_applied & AUDREC0_READY)) { + MM_DBG("AUDREC0 already enabled apply acdb values\n"); + acdb_value_apply |= AUDREC0_READY; + } else if ((acdb_data.acdb_state & AUDREC1_READY) && + !(acdb_data.audrec_applied & AUDREC1_READY)) { + MM_DBG("AUDREC1 already enabled apply acdb values\n"); + acdb_value_apply |= AUDREC1_READY; + } else if ((acdb_data.acdb_state & AUDREC2_READY) && + !(acdb_data.audrec_applied & AUDREC2_READY)) { + MM_DBG("AUDREC2 already enabled apply acdb values\n"); + acdb_value_apply |= AUDREC2_READY; + } + if (acdb_value_apply) { + if (session_info[stream_id].sampling_freq) + acdb_data.device_info->sample_rate = + session_info[stream_id].sampling_freq; + result = check_tx_acdb_values_cached(); + if (result) { + result = acdb_get_calibration(); + if (result < 0) { + MM_ERR("Not able to get calibration" + " data continue\n"); + return; + } + } + acdb_cache_tx[stream_id].node_status = ACDB_VALUES_FILLED; + acdb_send_calibration(); + } +} + +static struct acdb_cache_node *get_acdb_values_from_cache_tx(u32 stream_id) +{ + MM_DBG("searching node with stream_id %d\n", stream_id); + if ((acdb_cache_tx[stream_id].stream_id == stream_id) && + (acdb_cache_tx[stream_id].node_status == + ACDB_VALUES_NOT_FILLED)) { + return &acdb_cache_tx[stream_id]; + } + MM_DBG("Error! in finding node\n"); + return NULL; +} + +static void update_acdb_data_struct(struct acdb_cache_node *cur_node) +{ + if (cur_node) { + acdb_data.device_info = &cur_node->device_info; + acdb_data.virt_addr = cur_node->virt_addr_acdb_values; + acdb_data.phys_addr = cur_node->phys_addr_acdb_values; + } else + MM_ERR("error in curent node\n"); +} + +static void send_acdb_values_for_active_devices(void) +{ + u32 i = 0; + for (i = 0; i < MAX_COPP_NODE_SUPPORTED; i++) { + if (acdb_cache_rx[i].node_status == + ACDB_VALUES_FILLED) { + update_acdb_data_struct(&acdb_cache_rx[i]); + if (acdb_data.acdb_state & CAL_DATA_READY) + acdb_send_calibration(); + } + } +} + +static s32 initialize_rpc(void) +{ + s32 result = 0; + + result = daldevice_attach(DALDEVICEID_ACDB, ACDB_PORT_NAME, + ACDB_CPU, &acdb_data.handle); + + if (result) { + MM_ERR("ACDB=> Device Attach failed\n"); + result = -ENODEV; + goto done; + } +done: + return result; +} + +static u32 allocate_memory_acdb_cache_tx(void) +{ + u32 result = 0; + u32 i = 0; + u32 err = 0; + /*initialize local cache */ + for (i = 0; i < MAX_AUDREC_SESSIONS; i++) { + acdb_cache_tx[i].phys_addr_acdb_values = + pmem_kalloc(ACDB_BUF_SIZE, + (PMEM_MEMTYPE_EBI1 + | PMEM_ALIGNMENT_4K)); + + if (IS_ERR((void *)acdb_cache_tx[i].phys_addr_acdb_values)) { + MM_ERR("ACDB=> Cannot allocate physical memory\n"); + result = -ENOMEM; + goto error; + } + acdb_cache_tx[i].virt_addr_acdb_values = + ioremap( + acdb_cache_tx[i].phys_addr_acdb_values, + ACDB_BUF_SIZE); + if (acdb_cache_tx[i].virt_addr_acdb_values == NULL) { + MM_ERR("ACDB=> Could not map physical address\n"); + result = -ENOMEM; + pmem_kfree(acdb_cache_tx[i].phys_addr_acdb_values); + goto error; + } + memset(acdb_cache_tx[i].virt_addr_acdb_values, 0, + ACDB_BUF_SIZE); + } + return result; +error: + for (err = 0; err < i; err++) { + iounmap(acdb_cache_tx[i].virt_addr_acdb_values); + pmem_kfree(acdb_cache_tx[i].phys_addr_acdb_values); + + } + return result; +} + +static u32 allocate_memory_acdb_cache_rx(void) +{ + u32 result = 0; + u32 i = 0; + u32 err = 0; + + /*initialize local cache */ + for (i = 0; i < MAX_COPP_NODE_SUPPORTED; i++) { + acdb_cache_rx[i].phys_addr_acdb_values = + pmem_kalloc(ACDB_BUF_SIZE, + (PMEM_MEMTYPE_EBI1 + | PMEM_ALIGNMENT_4K)); + + if (IS_ERR((void *)acdb_cache_rx[i].phys_addr_acdb_values)) { + MM_ERR("ACDB=> Can not allocate physical memory\n"); + result = -ENOMEM; + goto error; + } + acdb_cache_rx[i].virt_addr_acdb_values = + ioremap( + acdb_cache_rx[i].phys_addr_acdb_values, + ACDB_BUF_SIZE); + if (acdb_cache_rx[i].virt_addr_acdb_values == NULL) { + MM_ERR("ACDB=> Could not map physical address\n"); + result = -ENOMEM; + pmem_kfree(acdb_cache_rx[i].phys_addr_acdb_values); + goto error; + } + memset(acdb_cache_rx[i].virt_addr_acdb_values, 0, + ACDB_BUF_SIZE); + } + return result; +error: + for (err = 0; err < i; err++) { + iounmap(acdb_cache_rx[i].virt_addr_acdb_values); + pmem_kfree(acdb_cache_rx[i].phys_addr_acdb_values); + + } + return result; +} + +static u32 allocate_memory_acdb_get_blk(void) +{ + u32 result = 0; + acdb_data.get_blk_paddr = pmem_kalloc(ACDB_BUF_SIZE, + (PMEM_MEMTYPE_EBI1 + | PMEM_ALIGNMENT_4K)); + if (IS_ERR((void *)acdb_data.get_blk_paddr)) { + MM_ERR("ACDB=> Cannot allocate physical memory\n"); + result = -ENOMEM; + goto error; + } + acdb_data.get_blk_kvaddr = ioremap(acdb_data.get_blk_paddr, + ACDB_BUF_SIZE); + if (acdb_data.get_blk_kvaddr == NULL) { + MM_ERR("ACDB=> Could not map physical address\n"); + result = -ENOMEM; + pmem_kfree(acdb_data.get_blk_paddr); + goto error; + } + memset(acdb_data.get_blk_kvaddr, 0, ACDB_BUF_SIZE); +error: + return result; +} + +static void free_memory_acdb_cache_rx(void) +{ + u32 i = 0; + + for (i = 0; i < MAX_COPP_NODE_SUPPORTED; i++) { + iounmap(acdb_cache_rx[i].virt_addr_acdb_values); + pmem_kfree(acdb_cache_rx[i].phys_addr_acdb_values); + } +} + +static void free_memory_acdb_cache_tx(void) +{ + u32 i = 0; + + for (i = 0; i < MAX_AUDREC_SESSIONS; i++) { + iounmap(acdb_cache_tx[i].virt_addr_acdb_values); + pmem_kfree(acdb_cache_tx[i].phys_addr_acdb_values); + } +} + +static void free_memory_acdb_get_blk(void) +{ + iounmap(acdb_data.get_blk_kvaddr); + pmem_kfree(acdb_data.get_blk_paddr); +} + +static s32 initialize_memory(void) +{ + s32 result = 0; + + result = allocate_memory_acdb_get_blk(); + if (result < 0) { + MM_ERR("memory allocation for get blk failed\n"); + goto done; + } + + result = allocate_memory_acdb_cache_rx(); + if (result < 0) { + MM_ERR("memory allocation for rx cache is failed\n"); + free_memory_acdb_get_blk(); + goto done; + } + result = allocate_memory_acdb_cache_tx(); + if (result < 0) { + MM_ERR("memory allocation for tx cache is failed\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + goto done; + } + acdb_data.pp_iir = kmalloc(sizeof(*acdb_data.pp_iir), + GFP_KERNEL); + if (acdb_data.pp_iir == NULL) { + MM_ERR("ACDB=> Could not allocate postproc iir memory\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + result = -ENOMEM; + goto done; + } + + acdb_data.pp_mbadrc = kmalloc(sizeof(*acdb_data.pp_mbadrc), GFP_KERNEL); + if (acdb_data.pp_mbadrc == NULL) { + MM_ERR("ACDB=> Could not allocate postproc mbadrc memory\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + kfree(acdb_data.pp_iir); + result = -ENOMEM; + goto done; + } + acdb_data.calib_gain_rx = kmalloc(sizeof(*acdb_data.calib_gain_rx), + GFP_KERNEL); + if (acdb_data.calib_gain_rx == NULL) { + MM_ERR("ACDB=> Could not allocate" + " postproc calib_gain_rx memory\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + kfree(acdb_data.pp_iir); + kfree(acdb_data.pp_mbadrc); + result = -ENOMEM; + goto done; + } + + acdb_data.preproc_agc = kmalloc(sizeof(*acdb_data.preproc_agc), + GFP_KERNEL); + if (acdb_data.preproc_agc == NULL) { + MM_ERR("ACDB=> Could not allocate preproc agc memory\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + kfree(acdb_data.pp_iir); + kfree(acdb_data.pp_mbadrc); + kfree(acdb_data.calib_gain_rx); + result = -ENOMEM; + goto done; + } + + acdb_data.preproc_iir = kmalloc(sizeof(*acdb_data.preproc_iir), + GFP_KERNEL); + if (acdb_data.preproc_iir == NULL) { + MM_ERR("ACDB=> Could not allocate preproc iir memory\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + kfree(acdb_data.pp_iir); + kfree(acdb_data.pp_mbadrc); + kfree(acdb_data.calib_gain_rx); + kfree(acdb_data.preproc_agc); + result = -ENOMEM; + goto done; + } + acdb_data.calib_gain_tx = kmalloc(sizeof(*acdb_data.calib_gain_tx), + GFP_KERNEL); + if (acdb_data.calib_gain_tx == NULL) { + MM_ERR("ACDB=> Could not allocate" + " preproc calib_gain_tx memory\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + kfree(acdb_data.pp_iir); + kfree(acdb_data.pp_mbadrc); + kfree(acdb_data.calib_gain_rx); + kfree(acdb_data.preproc_agc); + kfree(acdb_data.preproc_iir); + result = -ENOMEM; + goto done; + } + acdb_data.pbe_block = kmalloc(sizeof(*acdb_data.pbe_block), + GFP_KERNEL); + if (acdb_data.pbe_block == NULL) { + MM_ERR("ACDB=> Could not allocate pbe_block memory\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + kfree(acdb_data.pp_iir); + kfree(acdb_data.pp_mbadrc); + kfree(acdb_data.calib_gain_rx); + kfree(acdb_data.preproc_agc); + kfree(acdb_data.preproc_iir); + kfree(acdb_data.calib_gain_tx); + result = -ENOMEM; + goto done; + } + acdb_data.pbe_extbuff = (u16 *)(pmem_kalloc(PBE_BUF_SIZE, + (PMEM_MEMTYPE_EBI1 | + PMEM_ALIGNMENT_4K))); + if (IS_ERR((void *)acdb_data.pbe_extbuff)) { + MM_ERR("ACDB=> Cannot allocate physical memory\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + kfree(acdb_data.pp_iir); + kfree(acdb_data.pp_mbadrc); + kfree(acdb_data.calib_gain_rx); + kfree(acdb_data.preproc_agc); + kfree(acdb_data.preproc_iir); + kfree(acdb_data.calib_gain_tx); + kfree(acdb_data.pbe_block); + result = -ENOMEM; + goto done; + } + acdb_data.fluence_extbuff = pmem_kalloc(FLUENCE_BUF_SIZE, + (PMEM_MEMTYPE_EBI1 | + PMEM_ALIGNMENT_4K)); + if (IS_ERR((void *)acdb_data.fluence_extbuff)) { + MM_ERR("ACDB=> cannot allocate physical memory for " + "fluence block\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + kfree(acdb_data.pp_iir); + kfree(acdb_data.pp_mbadrc); + kfree(acdb_data.calib_gain_rx); + kfree(acdb_data.preproc_agc); + kfree(acdb_data.preproc_iir); + kfree(acdb_data.calib_gain_tx); + kfree(acdb_data.pbe_block); + pmem_kfree((int32_t)acdb_data.pbe_extbuff); + result = -ENOMEM; + goto done; + } + acdb_data.fluence_extbuff_virt = + ioremap( + acdb_data.fluence_extbuff, + FLUENCE_BUF_SIZE); + if (acdb_data.fluence_extbuff_virt == NULL) { + MM_ERR("ACDB=> Could not map physical address\n"); + free_memory_acdb_get_blk(); + free_memory_acdb_cache_rx(); + free_memory_acdb_cache_tx(); + kfree(acdb_data.pp_iir); + kfree(acdb_data.pp_mbadrc); + kfree(acdb_data.calib_gain_rx); + kfree(acdb_data.preproc_agc); + kfree(acdb_data.preproc_iir); + kfree(acdb_data.calib_gain_tx); + kfree(acdb_data.pbe_block); + pmem_kfree((int32_t)acdb_data.pbe_extbuff); + pmem_kfree((int32_t)acdb_data.fluence_extbuff); + result = -ENOMEM; + goto done; + } +done: + return result; +} + +static u32 free_acdb_cache_node(union auddev_evt_data *evt) +{ + u32 session_id; + if ((evt->audcal_info.dev_type & TX_DEVICE) == 2) { + /*Second argument to find_first_bit should be maximum number + of bits interested + */ + session_id = find_first_bit( + (unsigned long *)&(evt->audcal_info.sessions), + sizeof(evt->audcal_info.sessions) * 8); + MM_DBG("freeing node %d for tx device", session_id); + acdb_cache_tx[session_id]. + node_status = ACDB_VALUES_NOT_FILLED; + } else { + if (--(acdb_cache_rx[evt->audcal_info.dev_id].stream_id) <= 0) { + MM_DBG("freeing rx cache node %d\n", + evt->audcal_info.dev_id); + acdb_cache_rx[evt->audcal_info.dev_id]. + node_status = ACDB_VALUES_NOT_FILLED; + acdb_cache_rx[evt->audcal_info.dev_id].stream_id = 0; + } + } + return 0; +} + +static u8 check_device_change(struct auddev_evt_audcal_info audcal_info) +{ + if (!acdb_data.device_info) { + MM_ERR("not pointing to previous valid device detail\n"); + return 1; /*device info will not be pointing to*/ + /* valid device when acdb driver comes up*/ + } + if ((audcal_info.dev_id == acdb_data.device_info->dev_id) && + (audcal_info.sample_rate == + acdb_data.device_info->sample_rate) && + (audcal_info.acdb_id == acdb_data.device_info->acdb_id)) { + return 0; + } + return 1; +} + +static void device_cb(u32 evt_id, union auddev_evt_data *evt, void *private) +{ + struct auddev_evt_audcal_info audcal_info; + struct acdb_cache_node *acdb_cache_free_node = NULL; + u32 stream_id = 0; + u8 ret = 0; + u8 count = 0; + u8 i = 0; + u8 device_change = 0; + + if (!((evt_id == AUDDEV_EVT_DEV_RDY) || + (evt_id == AUDDEV_EVT_DEV_RLS))) { + goto done; + } + /*if session value is zero it indicates that device call back is for + voice call we will drop the request as acdb values for voice call is + not applied from acdb driver*/ + if (!evt->audcal_info.sessions) { + MM_DBG("no active sessions and call back is for" + " voice call\n"); + goto done; + } + if (evt_id == AUDDEV_EVT_DEV_RLS) { + MM_DBG("got release command for dev %d\n", + evt->audcal_info.dev_id); + acdb_data.acdb_state &= ~CAL_DATA_READY; + free_acdb_cache_node(evt); + /*reset the applied flag for the session routed to the device*/ + acdb_data.audrec_applied &= ~(evt->audcal_info.sessions + << AUDREC_OFFSET); + goto done; + } + if (((evt->audcal_info.dev_type & RX_DEVICE) == 1) && + (evt->audcal_info.acdb_id == PSEUDO_ACDB_ID)) { + MM_INFO("device cb is for rx device with pseudo acdb id\n"); + goto done; + } + audcal_info = evt->audcal_info; + MM_DBG("dev_id = %d\n", audcal_info.dev_id); + MM_DBG("sample_rate = %d\n", audcal_info.sample_rate); + MM_DBG("acdb_id = %d\n", audcal_info.acdb_id); + MM_DBG("sessions = %d\n", audcal_info.sessions); + MM_DBG("acdb_state = %x\n", acdb_data.acdb_state); + mutex_lock(&acdb_data.acdb_mutex); + device_change = check_device_change(audcal_info); + if (!device_change) { + if ((audcal_info.dev_type & TX_DEVICE) == 2) { + if (!(acdb_data.acdb_state & AUDREC0_READY)) + acdb_data.audrec_applied &= ~AUDREC0_READY; + if (!(acdb_data.acdb_state & AUDREC1_READY)) + acdb_data.audrec_applied &= ~AUDREC1_READY; + if (!(acdb_data.acdb_state & AUDREC2_READY)) + acdb_data.audrec_applied &= ~AUDREC2_READY; + acdb_data.acdb_state &= ~CAL_DATA_READY; + goto update_cache; + } + } else + /* state is updated to querry the modem for values */ + acdb_data.acdb_state &= ~CAL_DATA_READY; + +update_cache: + if ((audcal_info.dev_type & TX_DEVICE) == 2) { + /*loop is to take care of use case:- multiple Audrec + sessions are routed before enabling the device in this use + case we will get the sessions value as bits set for all the + sessions routed before device enable, so we should take care + of copying device info to all the sessions*/ + for (i = 0; i < MAX_AUDREC_SESSIONS; i++) { + stream_id = ((audcal_info.sessions >> i) & 0x01); + if (stream_id) { + acdb_cache_free_node = &acdb_cache_tx[i]; + ret = check_device_info_already_present( + audcal_info, + acdb_cache_free_node); + acdb_cache_free_node->stream_id = i; + acdb_data.cur_tx_session = i; + count++; + } + } + if (count > 1) + acdb_data.multiple_sessions = 1; + } else { + acdb_cache_free_node = &acdb_cache_rx[audcal_info.dev_id]; + ret = check_device_info_already_present(audcal_info, + acdb_cache_free_node); + if (ret == 1) { + MM_DBG("got device ready call back for another " + "audplay task sessions on same COPP\n"); + /*stream_id is used to keep track of number of active*/ + /*sessions active on this device*/ + acdb_cache_free_node->stream_id++; + mutex_unlock(&acdb_data.acdb_mutex); + goto done; + } + acdb_cache_free_node->stream_id++; + } + update_acdb_data_struct(acdb_cache_free_node); + acdb_data.device_cb_compl = 1; + mutex_unlock(&acdb_data.acdb_mutex); + wake_up(&acdb_data.wait); +done: + return; +} + + +static s32 register_device_cb(void) +{ + s32 result = 0; + + result = auddev_register_evt_listner((AUDDEV_EVT_DEV_RDY + | AUDDEV_EVT_DEV_RLS), + AUDDEV_CLNT_AUDIOCAL, 0, device_cb, (void *)&acdb_data); + + if (result) { + MM_ERR("ACDB=> Could not register device callback\n"); + result = -ENODEV; + goto done; + } +done: + return result; +} + +static void audpp_cb(void *private, u32 id, u16 *msg) +{ + MM_DBG("\n"); + if (id != AUDPP_MSG_CFG_MSG) + goto done; + + if (msg[0] == AUDPP_MSG_ENA_DIS) { + acdb_data.acdb_state &= ~AUDPP_READY; + MM_DBG("AUDPP_MSG_ENA_DIS\n"); + goto done; + } + + acdb_data.acdb_state |= AUDPP_READY; + acdb_data.audpp_cb_compl = 1; + wake_up(&acdb_data.wait); +done: + return; +} + +static s8 handle_audpreproc_cb(void) +{ + struct acdb_cache_node *acdb_cached_values; + s8 result = 0; + u8 stream_id = acdb_data.preproc_stream_id; + acdb_data.preproc_cb_compl = 0; + acdb_cached_values = get_acdb_values_from_cache_tx(stream_id); + if (acdb_cached_values == NULL) { + MM_DBG("ERROR: to get chached acdb values\n"); + return -EPERM; + } + update_acdb_data_struct(acdb_cached_values); + if (acdb_data.device_info->dev_id == PSEUDO_ACDB_ID) { + MM_INFO("audpreproc is routed to pseudo device\n"); + return result; + } + if (session_info[stream_id].sampling_freq) + acdb_data.device_info->sample_rate = + session_info[stream_id].sampling_freq; + if (!(acdb_data.acdb_state & CAL_DATA_READY)) { + result = check_tx_acdb_values_cached(); + if (result) { + result = acdb_get_calibration(); + if (result < 0) { + MM_ERR("failed to get calibration data\n"); + return result; + } + } + acdb_cached_values->node_status = ACDB_VALUES_FILLED; + } + return result; +} + +void fluence_feature_update(int enable, int stream_id) +{ + MM_INFO("Fluence feature over ride with = %d\n", enable); + acdb_data.fleuce_feature_status[stream_id] = enable; +} +EXPORT_SYMBOL(fluence_feature_update); + +static void audpreproc_cb(void *private, u32 id, void *msg) +{ + struct audpreproc_cmd_enc_cfg_done_msg *tmp; + u8 result = 0; + int stream_id = 0; + if (id != AUDPREPROC_CMD_ENC_CFG_DONE_MSG) + goto done; + + tmp = (struct audpreproc_cmd_enc_cfg_done_msg *)msg; + acdb_data.preproc_stream_id = tmp->stream_id; + stream_id = acdb_data.preproc_stream_id; + get_audrec_session_info(stream_id, &session_info[stream_id]); + MM_DBG("rec_enc_type = %x\n", tmp->rec_enc_type); + if ((tmp->rec_enc_type & 0x8000) == + AUD_PREPROC_CONFIG_DISABLED) { + if (acdb_data.preproc_stream_id == 0) { + acdb_data.acdb_state &= ~AUDREC0_READY; + acdb_data.audrec_applied &= ~AUDREC0_READY; + } else if (acdb_data.preproc_stream_id == 1) { + acdb_data.acdb_state &= ~AUDREC1_READY; + acdb_data.audrec_applied &= ~AUDREC1_READY; + } else if (acdb_data.preproc_stream_id == 2) { + acdb_data.acdb_state &= ~AUDREC2_READY; + acdb_data.audrec_applied &= ~AUDREC2_READY; + } + acdb_data.fleuce_feature_status[stream_id] = 0; + acdb_cache_tx[tmp->stream_id].node_status =\ + ACDB_VALUES_NOT_FILLED; + acdb_data.acdb_state &= ~CAL_DATA_READY; + goto done; + } + /*Following check is added to make sure that device info + is updated. audpre proc layer enabled without device + callback at this scenario we should not access + device information + */ + if (acdb_data.device_info && + session_info[stream_id].sampling_freq) { + acdb_data.device_info->sample_rate = + session_info[stream_id].sampling_freq; + result = check_tx_acdb_values_cached(); + if (!result) { + MM_INFO("acdb values for the stream is" \ + " querried from modem"); + acdb_data.acdb_state |= CAL_DATA_READY; + } else { + acdb_data.acdb_state &= ~CAL_DATA_READY; + } + } + if (acdb_data.preproc_stream_id == 0) + acdb_data.acdb_state |= AUDREC0_READY; + else if (acdb_data.preproc_stream_id == 1) + acdb_data.acdb_state |= AUDREC1_READY; + else if (acdb_data.preproc_stream_id == 2) + acdb_data.acdb_state |= AUDREC2_READY; + acdb_data.preproc_cb_compl = 1; + MM_DBG("acdb_data.acdb_state = %x\n", acdb_data.acdb_state); + wake_up(&acdb_data.wait); +done: + return; +} + +static s32 register_audpp_cb(void) +{ + s32 result = 0; + + acdb_data.audpp_cb.fn = audpp_cb; + acdb_data.audpp_cb.private = NULL; + result = audpp_register_event_callback(&acdb_data.audpp_cb); + if (result) { + MM_ERR("ACDB=> Could not register audpp callback\n"); + result = -ENODEV; + goto done; + } +done: + return result; +} + +static s32 register_audpreproc_cb(void) +{ + s32 result = 0; + + acdb_data.audpreproc_cb.fn = audpreproc_cb; + acdb_data.audpreproc_cb.private = NULL; + result = audpreproc_register_event_callback(&acdb_data.audpreproc_cb); + if (result) { + MM_ERR("ACDB=> Could not register audpreproc callback\n"); + result = -ENODEV; + goto done; + } + +done: + return result; +} + +static s32 acdb_initialize_data(void) +{ + s32 result = 0; + + mutex_init(&acdb_data.acdb_mutex); + + result = initialize_rpc(); + if (result) + goto err; + + result = initialize_memory(); + if (result) + goto err1; + + result = register_device_cb(); + if (result) + goto err2; + + result = register_audpp_cb(); + if (result) + goto err3; + + result = register_audpreproc_cb(); + if (result) + goto err4; + + return result; + +err4: + result = audpreproc_unregister_event_callback(&acdb_data.audpreproc_cb); + if (result) + MM_ERR("ACDB=> Could not unregister audpreproc callback\n"); +err3: + result = audpp_unregister_event_callback(&acdb_data.audpp_cb); + if (result) + MM_ERR("ACDB=> Could not unregister audpp callback\n"); +err2: + result = auddev_unregister_evt_listner(AUDDEV_CLNT_AUDIOCAL, 0); + if (result) + MM_ERR("ACDB=> Could not unregister device callback\n"); +err1: + daldevice_detach(acdb_data.handle); + acdb_data.handle = NULL; +err: + return result; +} + +static s32 initialize_modem_acdb(void) +{ + struct acdb_cmd_init_adie acdb_cmd; + u8 codec_type = -1; + s32 result = 0; + u8 iterations = 0; + + codec_type = adie_get_detected_codec_type(); + if (codec_type == MARIMBA_ID) + acdb_cmd.adie_type = ACDB_CURRENT_ADIE_MODE_MARIMBA; + else if (codec_type == TIMPANI_ID) + acdb_cmd.adie_type = ACDB_CURRENT_ADIE_MODE_TIMPANI; + else + acdb_cmd.adie_type = ACDB_CURRENT_ADIE_MODE_UNKNOWN; + acdb_cmd.command_id = ACDB_CMD_INITIALIZE_FOR_ADIE; + do { + /*Initialize ACDB software on modem based on codec type*/ + result = dalrpc_fcn_8(ACDB_DalACDB_ioctl, acdb_data.handle, + (const void *)&acdb_cmd, sizeof(acdb_cmd), + &acdb_data.acdb_result, + sizeof(acdb_data.acdb_result)); + if (result < 0) { + MM_ERR("ACDB=> RPC failure result = %d\n", result); + goto error; + } + /*following check is introduced to handle boot up race + condition between AUDCAL SW peers running on apps + and modem (ACDB_RES_BADSTATE indicates modem AUDCAL SW is + not in initialized sate) we need to retry to get ACDB + initialized*/ + if (acdb_data.acdb_result.result == ACDB_RES_BADSTATE) { + msleep(500); + iterations++; + } else if (acdb_data.acdb_result.result == ACDB_RES_SUCCESS) { + MM_DBG("Modem ACDB SW initialized ((iterations = %d)\n", + iterations); + return result; + } else { + MM_ERR("ACDB=> Modem ACDB SW failed to initialize" + " reuslt = %d, (iterations = %d)\n", + acdb_data.acdb_result.result, + iterations); + goto error; + } + } while (iterations < MAX_RETRY); + MM_ERR("ACDB=> AUDCAL SW on modem is not in intiailized state (%d)\n", + acdb_data.acdb_result.result); +error: + result = -EINVAL; + return result; +} + +static s32 acdb_calibrate_device(void *data) +{ + s32 result = 0; + + /* initialize driver */ + result = acdb_initialize_data(); + if (result) + goto done; + + result = initialize_modem_acdb(); + if (result < 0) + MM_ERR("failed to initialize modem ACDB\n"); + + while (!kthread_should_stop()) { + MM_DBG("Waiting for call back events\n"); + wait_event_interruptible(acdb_data.wait, + (acdb_data.device_cb_compl + | acdb_data.audpp_cb_compl + | acdb_data.preproc_cb_compl)); + mutex_lock(&acdb_data.acdb_mutex); + if (acdb_data.device_cb_compl) { + acdb_data.device_cb_compl = 0; + if (!(acdb_data.acdb_state & CAL_DATA_READY)) { + if ((acdb_data.device_info->dev_type + & RX_DEVICE) == 1) { + /*we need to get calibration values + only for RX device as resampler + moved to start of the pre - proc chain + tx calibration value will be based on + sampling frequency what audrec is + configured, calibration values for tx + device are fetch in audpreproc + callback*/ + result = acdb_get_calibration(); + if (result < 0) { + mutex_unlock( + &acdb_data.acdb_mutex); + MM_ERR("Not able to get " + "calibration " + "data continue\n"); + continue; + } + } + } + MM_DBG("acdb state = %d\n", + acdb_data.acdb_state); + if ((acdb_data.device_info->dev_type & TX_DEVICE) == 2) + handle_tx_device_ready_callback(); + else { + acdb_cache_rx[acdb_data.device_info->dev_id]\ + .node_status = + ACDB_VALUES_FILLED; + if (acdb_data.acdb_state & + AUDPP_READY) { + MM_DBG("AUDPP already enabled " + "apply acdb values\n"); + goto apply; + } + } + } + + if (!(acdb_data.audpp_cb_compl || + acdb_data.preproc_cb_compl)) { + MM_DBG("need to wait for either AUDPP / AUDPREPROC " + "Event\n"); + mutex_unlock(&acdb_data.acdb_mutex); + continue; + } else { + MM_DBG("got audpp / preproc call back\n"); + if (acdb_data.audpp_cb_compl) { + send_acdb_values_for_active_devices(); + acdb_data.audpp_cb_compl = 0; + mutex_unlock(&acdb_data.acdb_mutex); + continue; + } else { + result = handle_audpreproc_cb(); + if (result < 0) { + mutex_unlock(&acdb_data.acdb_mutex); + continue; + } + } + } +apply: + if (acdb_data.acdb_state & CAL_DATA_READY) + result = acdb_send_calibration(); + + mutex_unlock(&acdb_data.acdb_mutex); + } +done: + return 0; +} + +static int __init acdb_init(void) +{ + + s32 result = 0; + + memset(&acdb_data, 0, sizeof(acdb_data)); + spin_lock_init(&acdb_data.dsp_lock); + acdb_data.cb_thread_task = kthread_run(acdb_calibrate_device, + NULL, "acdb_cb_thread"); + + if (IS_ERR(acdb_data.cb_thread_task)) { + MM_ERR("ACDB=> Could not register cb thread\n"); + result = -ENODEV; + goto err; + } +#ifdef CONFIG_DEBUG_FS + /*This is RTC specific INIT used only with debugfs*/ + if (!rtc_acdb_init()) + MM_ERR("RTC ACDB=>INIT Failure\n"); + +#endif + init_waitqueue_head(&acdb_data.wait); + + return misc_register(&acdb_misc); +err: + return result; +} + +static void __exit acdb_exit(void) +{ + s32 result = 0; + u32 i = 0; + + result = auddev_unregister_evt_listner(AUDDEV_CLNT_AUDIOCAL, 0); + if (result) + MM_ERR("ACDB=> Could not unregister device callback\n"); + + result = audpp_unregister_event_callback(&acdb_data.audpp_cb); + if (result) + MM_ERR("ACDB=> Could not unregister audpp callback\n"); + + result = audpreproc_unregister_event_callback(&acdb_data.\ + audpreproc_cb); + if (result) + MM_ERR("ACDB=> Could not unregister audpreproc callback\n"); + + result = kthread_stop(acdb_data.cb_thread_task); + if (result) + MM_ERR("ACDB=> Could not stop kthread\n"); + + free_memory_acdb_get_blk(); + + for (i = 0; i < MAX_COPP_NODE_SUPPORTED; i++) { + if (i < MAX_AUDREC_SESSIONS) { + iounmap(acdb_cache_tx[i].virt_addr_acdb_values); + pmem_kfree(acdb_cache_tx[i].phys_addr_acdb_values); + } + iounmap(acdb_cache_rx[i].virt_addr_acdb_values); + pmem_kfree(acdb_cache_rx[i].phys_addr_acdb_values); + } + kfree(acdb_data.device_info); + kfree(acdb_data.pp_iir); + kfree(acdb_data.pp_mbadrc); + kfree(acdb_data.preproc_agc); + kfree(acdb_data.preproc_iir); + pmem_kfree((int32_t)acdb_data.pbe_extbuff); + iounmap(acdb_data.fluence_extbuff_virt); + pmem_kfree((int32_t)acdb_data.fluence_extbuff); + mutex_destroy(&acdb_data.acdb_mutex); + memset(&acdb_data, 0, sizeof(acdb_data)); + #ifdef CONFIG_DEBUG_FS + rtc_acdb_deinit(); + #endif +} + +late_initcall(acdb_init); +module_exit(acdb_exit); + +MODULE_DESCRIPTION("MSM 7x30 Audio ACDB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_adpcm.c b/arch/arm/mach-msm/qdsp5v2/audio_adpcm.c new file mode 100644 index 00000000000..8d767c91901 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_adpcm.c @@ -0,0 +1,1741 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5v2/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Size must be power of 2 */ +#define BUFSZ_MAX 32768 /* Includes meta in size */ +#define BUFSZ_MIN 4096 /* Includes meta in size */ +#define DMASZ_MAX (BUFSZ_MAX * 2) +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_ADPCM 1 + +#define PCM_BUFSZ_MIN 8216 /* Hold one stereo ADPCM frame and meta out*/ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDADPCM_METAFIELD_MASK 0xFFFF0000 +#define AUDADPCM_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDADPCM_EOS_FLG_MASK 0x01 +#define AUDADPCM_EOS_NONE 0x0 /* No EOS detected */ +#define AUDADPCM_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDADPCM_EVENT_NUM 10 /* Default no. of pre-allocated event packets */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audadpcm_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audadpcm_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_block_size; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audadpcm_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + uint32_t device_events; + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audplay_config_hostpcm(struct audio *audio); +static void audplay_buffer_refresh(struct audio *audio); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audadpcm_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +static void adpcm_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + if (audio->dec_state == MSM_AUD_DECODER_STATE_SUCCESS && + audio->enabled == 1) + audpp_route_stream(audio->dec_id, + msm_snddev_route_dec(audio->dec_id)); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + default: + MM_ERR(":ERROR:wrong event\n"); + break; + } +} +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audio_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("audio_update_pcm_buf_entry: \ + in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + } else { + MM_ERR("audio_update_pcm_buf_entry: \ + expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audplay_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audplay_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audio_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init\n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg\n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + /* send mixer command */ + audpp_route_stream(audio->dec_id, + audio->source); + if (audio->pcm_feedback) { + audplay_config_hostpcm(audio); + audplay_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status\n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audplay_buffer_refresh(audio); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AUDPP_MSG_AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +static struct msm_adsp_ops audplay_adsp_ops_adpcm = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_ADPCM; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_adpcm cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_ADPCM_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + + cmd.stereo_cfg = audio->out_channel_mode; + cmd.block_size = audio->out_block_size; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static void audplay_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + refresh_cmd.buf_read_count = 0; + + MM_DBG("buf0_addr=%x buf0_len=%d\n", + refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audplay_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); +} + + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDADPCM_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len/2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (audio->wflush) { + audio->out_needed = 1; + goto done; + } + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + MM_DBG("\n"); /* Macro prints the file name and function */ + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audio_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); +} + +static int audadpcm_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audadpcm_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audadpcm_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audadpcm_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audadpcm_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audadpcm_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audadpcm_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audadpcm_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audadpcm_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audadpcm_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audadpcm_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + audio->mfield = config.meta_field; + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + audio->out_block_size = config.bits; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buffer %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("read buf alloc fail\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; + index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr \ + 0x%08x kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audio_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audplay_send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d \n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver + does not know frame size, read count must be greater + or equal to size of PCM samples */ + MM_DBG("audio_read: no partial frame done reading\n"); + break; + } else { + MM_DBG("audio_read: read from in[%d]\n", + audio->read_next); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audplay_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audadpcm_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + char *buf_ptr; + + if (audio->reserved) { + MM_DBG("flush reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + frame->used = 2; + audio->reserved = 0; + audplay_send_data(audio, 0); + } + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audplay_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDADPCM_EOS_NONE; + unsigned dsize; + unsigned short mfield_size = 0; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("audio_write: mf offset_val %x\n", + mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDADPCM_EOS_FLG_OFFSET] & + AUDADPCM_EOS_FLG_MASK) { + MM_DBG("audio_write: EOS SET\n"); + eos_condition = AUDADPCM_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDADPCM_EOS_FLG_OFFSET] + &= ~AUDADPCM_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("audio_write: continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + (frame->size - mfield_size) - 1 : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audplay_send_data(audio, 0); + } + } + if (eos_condition == AUDADPCM_EOS_SET) + rc = audadpcm_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audio_disable(audio); + audio_flush(audio); + audio_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audadpcm_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audadpcm_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audadpcm_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audadpcm_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audadpcm_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audadpcm_suspend(struct early_suspend *h) +{ + struct audadpcm_suspend_ctl *ctl = + container_of(h, struct audadpcm_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audadpcm_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audadpcm_resume(struct early_suspend *h) +{ + struct audadpcm_suspend_ctl *ctl = + container_of(h, struct audadpcm_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audadpcm_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audadpcm_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audadpcm_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audadpcm_debug_fops = { + .read = audadpcm_debug_read, + .open = audadpcm_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + unsigned pmem_sz = DMASZ_MAX; + struct audadpcm_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_adpcm_" + 5]; +#endif + + /* Allocate Mem for audio instance */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_ADPCM; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d\n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr \ + 0x%08x\n", audio->phys, (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_adpcm, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->avsync_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = audio->out_dma_sz >> 1; + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + + audio->vol_pan.volume = 0x2000; + + audio_flush(audio); + + file->private_data = audio; + audio->opened = 1; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + adpcm_listner, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listner\n", __func__); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_adpcm_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, + &audadpcm_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audadpcm_resume; + audio->suspend_ctl.node.suspend = audadpcm_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDADPCM_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audadpcm_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_adpcm_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_fsync, +}; + +struct miscdevice audio_adpcm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_adpcm", + .fops = &audio_adpcm_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_adpcm_misc); +} + +device_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_amrnb.c b/arch/arm/mach-msm/qdsp5v2/audio_amrnb.c new file mode 100644 index 00000000000..53a8850f53e --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_amrnb.c @@ -0,0 +1,1633 @@ +/* + * amrnb audio decoder device + * + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSZ 1024 /* Hold minimum 700ms voice data and 14 bytes of meta in*/ +#define DMASZ (BUFSZ * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_AMRNB 10 + +#define PCM_BUFSZ_MIN 1624 /* 100ms worth of data and 24 bytes of meta out*/ +#define AMRNB_DECODED_FRSZ 320 /* AMR-NB 20ms 8KHz mono PCM size */ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDAMRNB_METAFIELD_MASK 0xFFFF0000 +#define AUDAMRNB_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDAMRNB_EOS_FLG_MASK 0x01 +#define AUDAMRNB_EOS_NONE 0x0 /* No EOS detected */ +#define AUDAMRNB_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDAMRNB_EVENT_NUM 10 /* Default number of pre-allocated event pkts */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audamrnb_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audamrnb_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + uint8_t opened:1; + uint8_t enabled:1; + uint8_t running:1; + uint8_t stopped:1; /* set when stopped, cleared on flush */ + uint8_t pcm_feedback:1; + uint8_t buf_refresh:1; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audamrnb_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + uint32_t device_events; + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +struct audpp_cmd_cfg_adec_params_amrnb { + struct audpp_cmd_cfg_adec_params_common common; + unsigned short stereo_cfg; +} __attribute__((packed)) ; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audamrnb_send_data(struct audio *audio, unsigned needed); +static void audamrnb_config_hostpcm(struct audio *audio); +static void audamrnb_buffer_refresh(struct audio *audio); +static void audamrnb_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audamrnb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); + +/* must be called with audio->lock held */ +static int audamrnb_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audamrnb_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +static void amrnb_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + default: + MM_ERR(":ERROR:wrong event\n"); + break; + } +} +/* must be called with audio->lock held */ +static int audamrnb_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audamrnb_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audamrnb_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audamrnb_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audamrnb_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder\n"); + } +} + +static void audamrnb_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + /* send mixer command */ + audpp_route_stream(audio->dec_id, + audio->source); + if (audio->pcm_feedback) { + audamrnb_config_hostpcm(audio); + audamrnb_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status \n"); + break; + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audamrnb_buffer_refresh(audio); + break; + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AUDPP_MSG_AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_amrnb = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_AMRNB; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_amrnb cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_V13K_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = 8000; + cmd.stereo_cfg = AUDPP_CMD_PCM_INTF_MONO_V; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDAMRNB_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audamrnb_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size - + (audio->in[audio->fill_next].size % AMRNB_DECODED_FRSZ) + + (audio->mfield ? 24 : 0); + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audamrnb_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audamrnb_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audamrnb_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audamrnb_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audamrnb_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audamrnb_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audamrnb_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); +} + +static int audamrnb_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audamrnb_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audamrnb_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audamrnb_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audamrnb_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audamrnb_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audamrnb_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audamrnb_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audamrnb_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audamrnb_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audamrnb_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audamrnb_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audamrnb_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audamrnb_disable(audio); + audio->stopped = 1; + audamrnb_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audamrnb_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + audio->mfield = config.meta_field; + rc = 0; + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = 8000; + config.channel_count = 1; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buf %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("no mem for read buf\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr 0x%08x kernel \ + addr 0x%08x\n", audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audamrnb_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audamrnb_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d \n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver does + * not know frame size, read count must be greater or + * equal to size of PCM samples + */ + MM_DBG("read stop - partial frame\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audamrnb_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audamrnb_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audamrnb_send_data(audio, 0); + +done: + return rc; +} + +static ssize_t audamrnb_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDAMRNB_EOS_NONE; + unsigned short mfield_size = 0; + + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + + MM_DBG("buffer available\n"); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer + * contains just meta field + */ + if (cpy_ptr[AUDAMRNB_EOS_FLG_OFFSET] & + AUDAMRNB_EOS_FLG_MASK) { + MM_DBG("eos set\n"); + eos_condition = AUDAMRNB_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDAMRNB_EOS_FLG_OFFSET] &= + ~AUDAMRNB_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + frame->used = (xfer + mfield_size); + audio->out_head ^= 1; + count -= xfer; + buf += xfer; + + audamrnb_send_data(audio, 0); + + } + if (eos_condition == AUDAMRNB_EOS_SET) + rc = audamrnb_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audamrnb_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audamrnb_disable(audio); + audamrnb_flush(audio); + audamrnb_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audamrnb_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audamrnb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audamrnb_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audamrnb_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audamrnb_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audamrnb_suspend(struct early_suspend *h) +{ + struct audamrnb_suspend_ctl *ctl = + container_of(h, struct audamrnb_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audamrnb_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audamrnb_resume(struct early_suspend *h) +{ + struct audamrnb_suspend_ctl *ctl = + container_of(h, struct audamrnb_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audamrnb_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audamrnb_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audamrnb_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].used %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audamrnb_debug_fops = { + .read = audamrnb_debug_read, + .open = audamrnb_debug_open, +}; +#endif + +static int audamrnb_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + struct audamrnb_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_amrnb_" + 5]; +#endif + + /* Allocate Mem for audio instance */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_AMRNB; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->phys)) { + MM_ERR("could not allocate write buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr \ + 0x%08x\n", audio->phys, (int)audio->data); + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_amrnb, audio); + if (rc) { + MM_ERR("failed to get %s module freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + init_waitqueue_head(&audio->avsync_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x2000; + + audamrnb_flush(audio); + + file->private_data = audio; + audio->opened = 1; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + amrnb_listner, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listner\n", __func__); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_amrnb_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audamrnb_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audamrnb_resume; + audio->suspend_ctl.node.suspend = audamrnb_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDAMRNB_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audamrnb_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_amrnb_fops = { + .owner = THIS_MODULE, + .open = audamrnb_open, + .release = audamrnb_release, + .read = audamrnb_read, + .write = audamrnb_write, + .unlocked_ioctl = audamrnb_ioctl, + .fsync = audamrnb_fsync, +}; + +struct miscdevice audio_amrnb_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrnb", + .fops = &audio_amrnb_fops, +}; + +static int __init audamrnb_init(void) +{ + return misc_register(&audio_amrnb_misc); +} + +static void __exit audamrnb_exit(void) +{ + misc_deregister(&audio_amrnb_misc); +} + +module_init(audamrnb_init); +module_exit(audamrnb_exit); + +MODULE_DESCRIPTION("MSM AMR-NB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_amrnb_in.c b/arch/arm/mach-msm/qdsp5v2/audio_amrnb_in.c new file mode 100644 index 00000000000..a685af5ee79 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_amrnb_in.c @@ -0,0 +1,887 @@ +/* + * amrnb audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (8) +#define FRAME_SIZE (22 * 2) /* 36 bytes data */ +#define DMASZ (FRAME_SIZE * FRAME_NUM) + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; +}; + +struct audio_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + atomic_t in_samples; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + wait_queue_head_t wait_enable; + + struct msm_adsp_module *audrec; + struct audrec_session_info session_info; /*audrec session info*/ + + /* configuration to use on next enable */ + uint32_t buffer_size; /* Frame size (36 bytes) */ + uint32_t enc_type; + + int dtx_mode; + uint32_t frame_format; + uint32_t used_mode; + uint32_t rec_mode; + + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + uint32_t mode; + + const char *module_name; + unsigned queue_ids; + uint16_t enc_id; + + uint16_t source; /* Encoding source bit mask */ + uint32_t device_events; + uint32_t in_call; + uint32_t dev_cnt; + int voice_state; + spinlock_t dev_lock; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ +}; + +struct audio_frame { + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + unsigned char raw_bitstream[]; /* samples */ +} __attribute__((packed)); + +/* Audrec Queue command sent macro's */ +#define audrec_send_bitstreamqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_ids & 0xFFFF0000) >> 16),\ + cmd, len) + +#define audrec_send_audrecqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_ids & 0x0000FFFF),\ + cmd, len) + +struct audio_in the_audio_amrnb_in; + +/* DSP command send functions */ +static int audamrnb_in_enc_config(struct audio_in *audio, int enable); +static int audamrnb_in_param_config(struct audio_in *audio); +static int audamrnb_in_mem_config(struct audio_in *audio); +static int audamrnb_in_record_config(struct audio_in *audio, int enable); +static int audamrnb_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt); + +static void audamrnb_in_get_dsp_frames(struct audio_in *audio); + +static void audamrnb_in_flush(struct audio_in *audio); + +static void amrnb_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio_in *audio = (struct audio_in *) private_data; + unsigned long flags; + + MM_DBG("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: { + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt++; + if (!audio->in_call) + audio->source |= (0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((audio->running == 1) && (audio->enabled == 1)) + audamrnb_in_record_config(audio, 1); + + break; + } + case AUDDEV_EVT_DEV_RLS: { + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt--; + if (!audio->in_call) + audio->source &= ~(0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((!audio->running) || (!audio->enabled)) + break; + + /* Turn of as per source */ + if (audio->source) + audamrnb_in_record_config(audio, 1); + else + /* Turn off all */ + audamrnb_in_record_config(audio, 0); + + break; + } + case AUDDEV_EVT_VOICE_STATE_CHG: { + MM_DBG("AUDDEV_EVT_VOICE_STATE_CHG, state = %d\n", + evt_payload->voice_state); + audio->voice_state = evt_payload->voice_state; + if (audio->in_call && audio->running) { + if (audio->voice_state == VOICE_STATE_INCALL) + audamrnb_in_record_config(audio, 1); + else if (audio->voice_state == VOICE_STATE_OFFCALL) { + audamrnb_in_record_config(audio, 0); + wake_up(&audio->wait); + } + } + + break; + } + default: + MM_ERR("wrong event %d\n", evt_id); + break; + } +} + +/* ------------------- dsp preproc event handler--------------------- */ +static void audpreproc_dsp_event(void *data, unsigned id, void *msg) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDPREPROC_ERROR_MSG: { + struct audpreproc_err_msg *err_msg = msg; + + MM_ERR("ERROR_MSG: stream id %d err idx %d\n", + err_msg->stream_id, err_msg->aud_preproc_err_idx); + /* Error case */ + wake_up(&audio->wait_enable); + break; + } + case AUDPREPROC_CMD_CFG_DONE_MSG: { + MM_DBG("CMD_CFG_DONE_MSG \n"); + break; + } + case AUDPREPROC_CMD_ENC_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_cfg_done_msg *enc_cfg_msg = msg; + + MM_DBG("CMD_ENC_CFG_DONE_MSG: stream id %d enc type \ + 0x%8x\n", enc_cfg_msg->stream_id, + enc_cfg_msg->rec_enc_type); + /* Encoder enable success */ + if (enc_cfg_msg->rec_enc_type & ENCODE_ENABLE) + audamrnb_in_param_config(audio); + else { /* Encoder disable success */ + audio->running = 0; + audamrnb_in_record_config(audio, 0); + } + break; + } + case AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: { + MM_DBG("CMD_ENC_PARAM_CFG_DONE_MSG \n"); + audamrnb_in_mem_config(audio); + break; + } + case AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: { + MM_DBG("AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG \n"); + wake_up(&audio->wait_enable); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +/* ------------------- dsp audrec event handler--------------------- */ +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDREC_CMD_MEM_CFG_DONE_MSG: { + MM_DBG("CMD_MEM_CFG_DONE MSG DONE\n"); + audio->running = 1; + if ((!audio->in_call && (audio->dev_cnt > 0)) || + (audio->in_call && + (audio->voice_state == VOICE_STATE_INCALL))) + audamrnb_in_record_config(audio, 1); + break; + } + case AUDREC_FATAL_ERR_MSG: { + struct audrec_fatal_err_msg fatal_err_msg; + + getevent(&fatal_err_msg, AUDREC_FATAL_ERR_MSG_LEN); + MM_ERR("FATAL_ERR_MSG: err id %d\n", + fatal_err_msg.audrec_err_id); + /* Error stop the encoder */ + audio->stopped = 1; + wake_up(&audio->wait); + break; + } + case AUDREC_UP_PACKET_READY_MSG: { + struct audrec_up_pkt_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packet_write_cnt_lsw, \ + pkt_ready_msg.audrec_packet_write_cnt_msw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_lsw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_msw); + + audamrnb_in_get_dsp_frames(audio); + break; + } + case ADSP_MESSAGE_ID: { + MM_DBG("Received ADSP event:module audrectask\n"); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +static void audamrnb_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(*frame)); + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = frame->frame_length; + + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + else + audio->in_count++; + + audamrnb_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} +struct msm_adsp_ops audrec_amrnb_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int audamrnb_in_enc_config(struct audio_in *audio, int enable) +{ + struct audpreproc_audrec_cmd_enc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + cmd.stream_id = audio->enc_id; + + if (enable) + cmd.audrec_enc_type = audio->enc_type | ENCODE_ENABLE; + else + cmd.audrec_enc_type &= ~(ENCODE_ENABLE); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audamrnb_in_param_config(struct audio_in *audio) +{ + struct audpreproc_audrec_cmd_parm_cfg_amrnb cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPREPROC_AUDREC_CMD_PARAM_CFG; + cmd.common.stream_id = audio->enc_id; + + cmd.dtx_mode = audio->dtx_mode; + cmd.test_mode = -1; /* Default set to -1 */ + cmd.used_mode = audio->used_mode; + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +/* To Do: msm_snddev_route_enc(audio->enc_id); */ +static int audamrnb_in_record_config(struct audio_in *audio, int enable) +{ + struct audpreproc_afe_cmd_audio_record_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG; + cmd.stream_id = audio->enc_id; + if (enable) + cmd.destination_activity = AUDIO_RECORDING_TURN_ON; + else + cmd.destination_activity = AUDIO_RECORDING_TURN_OFF; + + cmd.source_mix_mask = audio->source; + if (audio->enc_id == 2) { + if ((cmd.source_mix_mask & + INTERNAL_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & AUX_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_UL_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_DL_SOURCE_MIX_MASK)) { + cmd.pipe_id = SOURCE_PIPE_1; + } + if (cmd.source_mix_mask & + AUDPP_A2DP_PIPE_SOURCE_MIX_MASK) + cmd.pipe_id |= SOURCE_PIPE_0; + } + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audamrnb_in_mem_config(struct audio_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + int n; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_MEM_CFG_CMD; + cmd.audrec_up_pkt_intm_count = 1; + cmd.audrec_ext_pkt_start_addr_msw = audio->phys >> 16; + cmd.audrec_ext_pkt_start_addr_lsw = audio->phys; + cmd.audrec_ext_pkt_buf_number = FRAME_NUM; + + /* prepare buffer pointers: + * 36 bytes amrnb packet + 4 halfword header + */ + for (n = 0; n < FRAME_NUM; n++) { + audio->in[n].data = data + 4; + data += (FRAME_SIZE/2); /* word increment */ + MM_DBG("0x%8x\n", (int)(audio->in[n].data - 8)); + } + + return audrec_send_audrecqueue(audio, &cmd, sizeof(cmd)); +} + +static int audamrnb_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt) +{ + struct up_audrec_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = UP_AUDREC_PACKET_EXT_PTR; + cmd.audrec_up_curr_read_count_msw = read_cnt >> 16; + cmd.audrec_up_curr_read_count_lsw = read_cnt; + + return audrec_send_bitstreamqueue(audio, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audamrnb_in_enable(struct audio_in *audio) +{ + if (audio->enabled) + return 0; + + if (audpreproc_enable(audio->enc_id, &audpreproc_dsp_event, audio)) { + MM_ERR("msm_adsp_enable(audpreproc) failed\n"); + return -ENODEV; + } + + if (msm_adsp_enable(audio->audrec)) { + MM_ERR("msm_adsp_enable(audrec) failed\n"); + audpreproc_disable(audio->enc_id, audio); + return -ENODEV; + } + audio->enabled = 1; + audamrnb_in_enc_config(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audamrnb_in_disable(struct audio_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + audamrnb_in_enc_config(audio, 0); + wake_up(&audio->wait); + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + msm_adsp_disable(audio->audrec); + audpreproc_disable(audio->enc_id, audio); + } + return 0; +} + +static void audamrnb_in_flush(struct audio_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + for (i = 0; i < FRAME_NUM; i++) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } + MM_DBG("in_bytes %d\n", atomic_read(&audio->in_bytes)); + MM_DBG("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); +} + +/* ------------------- device --------------------- */ +static long audamrnb_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_in *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + uint32_t freq; + freq = 48000; + MM_DBG("AUDIO_START\n"); + if (audio->in_call && (audio->voice_state != + VOICE_STATE_INCALL)) { + rc = -EPERM; + break; + } + rc = msm_snddev_request_freq(&freq, audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("sample rate configured %d\n", freq); + if (rc < 0) { + MM_DBG(" Sample rate can not be set, return code %d\n", + rc); + msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + break; + } + /*update aurec session info in audpreproc layer*/ + audio->session_info.session_id = audio->enc_id; + /*amrnb works only on 8KHz*/ + audio->session_info.sampling_freq = 8000; + audpreproc_update_audrec_info(&audio->session_info); + rc = audamrnb_in_enable(audio); + if (!rc) { + rc = + wait_event_interruptible_timeout(audio->wait_enable, + audio->running != 0, 1*HZ); + MM_DBG("state %d rc = %d\n", audio->running, rc); + + if (audio->running == 0) + rc = -ENODEV; + else + rc = 0; + } + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + rc = audamrnb_in_disable(audio); + rc = msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + audio->stopped = 1; + break; + } + case AUDIO_FLUSH: { + if (audio->stopped) { + /* Make sure we're stopped and we wake any threads + * that might be blocked holding the read_lock. + * While audio->stopped read threads will always + * exit immediately. + */ + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audamrnb_in_flush(audio); + mutex_unlock(&audio->read_lock); + } + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + /* Allow only single frame */ + if (cfg.buffer_size != (FRAME_SIZE - 8)) + rc = -EINVAL; + else + audio->buffer_size = cfg.buffer_size; + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_AMRNB_ENC_CONFIG_V2: { + struct msm_audio_amrnb_enc_config_v2 cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.dtx_enable = ((audio->dtx_mode == -1) ? 1 : 0); + cfg.band_mode = audio->used_mode; + cfg.frame_format = audio->frame_format; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_AMRNB_ENC_CONFIG_V2: { + struct msm_audio_amrnb_enc_config_v2 cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + /* DSP does not support any other than default format */ + if (audio->frame_format != cfg.frame_format) { + rc = -EINVAL; + break; + } + if (cfg.dtx_enable == 0) + audio->dtx_mode = 0; + else if (cfg.dtx_enable == 1) + audio->dtx_mode = -1; + else { + rc = -EINVAL; + break; + } + audio->used_mode = cfg.band_mode; + break; + } + case AUDIO_SET_INCALL: { + struct msm_voicerec_mode cfg; + unsigned long flags; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.rec_mode != VOC_REC_BOTH && + cfg.rec_mode != VOC_REC_UPLINK && + cfg.rec_mode != VOC_REC_DOWNLINK) { + MM_ERR("invalid rec_mode\n"); + rc = -EINVAL; + break; + } else { + spin_lock_irqsave(&audio->dev_lock, flags); + if (cfg.rec_mode == VOC_REC_UPLINK) + audio->source = VOICE_UL_SOURCE_MIX_MASK; + else if (cfg.rec_mode == VOC_REC_DOWNLINK) + audio->source = VOICE_DL_SOURCE_MIX_MASK; + else + audio->source = VOICE_DL_SOURCE_MIX_MASK | + VOICE_UL_SOURCE_MIX_MASK ; + audio->in_call = 1; + spin_unlock_irqrestore(&audio->dev_lock, flags); + } + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &audio->enc_id, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audamrnb_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped + || (audio->in_call && audio->running && + (audio->voice_state == VOICE_STATE_OFFCALL))); + if (rc < 0) + break; + + if (!audio->in_count) { + if (audio->stopped) { + rc = 0;/* End of File */ + break; + } else if (audio->in_call && audio->running && + (audio->voice_state == VOICE_STATE_OFFCALL)) { + MM_DBG("Not Permitted Voice Terminated\n"); + rc = -EPERM; /* Voice Call stopped */ + break; + } + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + if (count >= size) { + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is + * invalid and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + } else { + MM_ERR("short read\n"); + break; + } + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static ssize_t audamrnb_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + return -EINVAL; +} + +static int audamrnb_in_release(struct inode *inode, struct file *file) +{ + struct audio_in *audio = file->private_data; + + MM_DBG("\n"); + mutex_lock(&audio->lock); + audio->in_call = 0; + /* with draw frequency for session + incase not stopped the driver */ + msm_snddev_withdraw_freq(audio->enc_id, SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, audio->enc_id); + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + audamrnb_in_disable(audio); + audamrnb_in_flush(audio); + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + audio->audrec = NULL; + audio->opened = 0; + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + audio->data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +static int audamrnb_in_open(struct inode *inode, struct file *file) +{ + struct audio_in *audio = &the_audio_amrnb_in; + int rc; + int encid; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->phys); + goto done; + } + } else { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + goto done; + } + MM_DBG("Memory addr = 0x%8x phy addr = 0x%8x\n",\ + (int) audio->data, (int) audio->phys); + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + rc = -EACCES; + MM_ERR("Non tunnel encoding is not supported\n"); + goto done; + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_TUNNEL; + MM_DBG("Opened for tunnel mode encoding\n"); + } else { + rc = -EACCES; + goto done; + } + + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->buffer_size = (FRAME_SIZE - 8); + audio->enc_type = ENC_TYPE_AMRNB | audio->mode; + audio->dtx_mode = -1; + audio->frame_format = 0; + audio->used_mode = 7; /* Bit Rate 12.2 kbps MR122 */ + + encid = audpreproc_aenc_alloc(audio->enc_type, &audio->module_name, + &audio->queue_ids); + if (encid < 0) { + MM_ERR("No free encoder available\n"); + rc = -ENODEV; + goto done; + } + audio->enc_id = encid; + + rc = msm_adsp_get(audio->module_name, &audio->audrec, + &audrec_amrnb_adsp_ops, audio); + + if (rc) { + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + audio->stopped = 0; + audio->source = 0; + + audamrnb_in_flush(audio); + + audio->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_VOICE_STATE_CHG; + + audio->voice_state = msm_get_voice_state(); + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_ENC, audio->enc_id, + amrnb_in_listener, (void *) audio); + if (rc) { + MM_ERR("failed to register device event listener\n"); + goto evt_error; + } + file->private_data = audio; + audio->opened = 1; +done: + mutex_unlock(&audio->lock); + return rc; +evt_error: + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_in_fops = { + .owner = THIS_MODULE, + .open = audamrnb_in_open, + .release = audamrnb_in_release, + .read = audamrnb_in_read, + .write = audamrnb_in_write, + .unlocked_ioctl = audamrnb_in_ioctl, +}; + +struct miscdevice audio_amrnb_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrnb_in", + .fops = &audio_in_fops, +}; + +static int __init audamrnb_in_init(void) +{ + mutex_init(&the_audio_amrnb_in.lock); + mutex_init(&the_audio_amrnb_in.read_lock); + spin_lock_init(&the_audio_amrnb_in.dsp_lock); + spin_lock_init(&the_audio_amrnb_in.dev_lock); + init_waitqueue_head(&the_audio_amrnb_in.wait); + init_waitqueue_head(&the_audio_amrnb_in.wait_enable); + return misc_register(&audio_amrnb_in_misc); +} + +device_initcall(audamrnb_in_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_amrwb.c b/arch/arm/mach-msm/qdsp5v2/audio_amrwb.c new file mode 100644 index 00000000000..4793e6ed481 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_amrwb.c @@ -0,0 +1,1712 @@ +/* amrwb audio decoder device + * + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5v2/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSZ 4110 /* Hold minimum 700ms voice data and 14 bytes of meta in*/ +#define DMASZ (BUFSZ * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_AMRWB 11 + +#define PCM_BUFSZ_MIN 8216 /* 100ms worth of data and 24 bytes of meta out*/ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDAMRWB_METAFIELD_MASK 0xFFFF0000 +#define AUDAMRWB_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDAMRWB_EOS_FLG_MASK 0x01 +#define AUDAMRWB_EOS_NONE 0x0 /* No EOS detected */ +#define AUDAMRWB_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDAMRWB_EVENT_NUM 10 /* Default number of pre-allocated event pkts */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audamrwb_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audamrwb_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audamrwb_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + uint32_t device_events; + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audamrwb_send_data(struct audio *audio, unsigned needed); +static void audamrwb_config_hostpcm(struct audio *audio); +static void audamrwb_buffer_refresh(struct audio *audio); +static void audamrwb_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audamrwb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); + +/* must be called with audio->lock held */ +static int audamrwb_enable(struct audio *audio) +{ + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audamrwb_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +static void amrwb_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG("AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + default: + MM_ERR("ERROR:wrong event\n"); + break; + } +} + +/* must be called with audio->lock held */ +static int audamrwb_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audamrwb_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("audamrwb_update_pcm_buf_entry: \ + in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audamrwb_buffer_refresh(audio); + } else { + MM_DBG("audamrwb_update_pcm_buf_entry: \ + read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("audplay_dsp_event: msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audamrwb_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audamrwb_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event:module audplaytask\n"); + break; + + default: + MM_DBG("unexpected message from decoder\n"); + } +} + +static void audamrwb_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason=0x%04x\n", + reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init\n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg\n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play\n"); + /* send mixer command */ + audpp_route_stream(audio->dec_id, + audio->source); + if (audio->pcm_feedback) { + audamrwb_config_hostpcm(audio); + audamrwb_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_DBG("unknown decoder status\n"); + break; + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audamrwb_buffer_refresh(audio); + break; + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AUDPP_MSG_AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_DBG("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_amrwb = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_AMRWB; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_amrwb cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_AMRWB_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + cmd.stereo_cfg = audio->out_channel_mode; + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDAMRWB_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audamrwb_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audamrwb_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audamrwb_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audamrwb_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audamrwb_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audamrwb_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audamrwb_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audamrwb_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); +} + +static int audamrwb_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audamrwb_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audamrwb_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audamrwb_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audamrwb_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audamrwb_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audamrwb_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audamrwb_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audamrwb_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audamrwb_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audamrwb_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audamrwb_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audamrwb_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audamrwb_disable(audio); + audio->stopped = 1; + audamrwb_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audamrwb_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) + config.channel_count = + AUDPP_CMD_PCM_INTF_MONO_V; + else if (config.channel_count == 2) + config.channel_count = + AUDPP_CMD_PCM_INTF_STEREO_V; + else + rc = -EINVAL; + audio->out_channel_mode = config.channel_count; + audio->out_sample_rate = config.sample_rate; + audio->mfield = config.meta_field; + rc = 0; + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == + AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = 0; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buf %d\n", config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("no mem for read buf\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->pcm_feedback = 1; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr 0x%08x \ + kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audamrwb_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audamrwb_send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audamrwb_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("count %d\n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver does + * not know frame size, read count must be greater or + * equal to size of PCM samples + */ + MM_DBG("read stop - partial frame\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x\n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audamrwb_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audamrwb_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + char *buf_ptr; + int rc = 0; + + MM_DBG("signal input EOS reserved=%d\n", audio->reserved); + if (audio->reserved) { + MM_DBG("Pass reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + audio->reserved = 0; + frame->used = 2; + audamrwb_send_data(audio, 0); + } + + MM_DBG("Now signal input EOS after reserved bytes %d %d %d\n", + audio->out[0].used, audio->out[1].used, audio->out_needed); + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audamrwb_send_data(audio, 0); + +done: + return rc; +} + +static ssize_t audamrwb_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDAMRWB_EOS_NONE; + unsigned short mfield_size = 0; + unsigned dsize; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + + MM_DBG("buffer available\n"); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer + * contains just meta field + */ + if (cpy_ptr[AUDAMRWB_EOS_FLG_OFFSET] & + AUDAMRWB_EOS_FLG_MASK) { + MM_DBG("eos set\n"); + eos_condition = AUDAMRWB_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDAMRWB_EOS_FLG_OFFSET] &= + ~AUDAMRWB_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + ((frame->size - mfield_size) - 1) : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audamrwb_send_data(audio, 0); + } + } + MM_DBG("eos_condition %x buf[0x%x] start[0x%x]\n", eos_condition, + (int) buf, (int) start); + if (eos_condition == AUDAMRWB_EOS_SET) + rc = audamrwb_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audamrwb_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audamrwb_disable(audio); + audamrwb_flush(audio); + audamrwb_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audamrwb_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audamrwb_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audamrwb_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audamrwb_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audamrwb_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audamrwb_suspend(struct early_suspend *h) +{ + struct audamrwb_suspend_ctl *ctl = + container_of(h, struct audamrwb_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audamrwb_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audamrwb_resume(struct early_suspend *h) +{ + struct audamrwb_suspend_ctl *ctl = + container_of(h, struct audamrwb_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audamrwb_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audamrwb_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audamrwb_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].used %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audamrwb_debug_fops = { + .read = audamrwb_debug_read, + .open = audamrwb_debug_open, +}; +#endif + +static int audamrwb_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + struct audamrwb_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_amrwb_" + 5]; +#endif + + /* Allocate Mem for audio instance */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance\n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_AMRWB; + if (file->f_mode & FMODE_READ) + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + else + dec_attrb |= MSM_AUD_MODE_TUNNEL; + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->phys)) { + MM_ERR("could not allocate write buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_amrwb, audio); + if (rc) { + MM_ERR("failed to get %s module freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + init_waitqueue_head(&audio->avsync_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x2000; + audio->vol_pan.pan = 0x0; + audio->eq_enable = 0; + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + + audamrwb_flush(audio); + + file->private_data = audio; + audio->opened = 1; + audio->event_abort = 0; + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + amrwb_listner, + (void *)audio); + if (rc) { + MM_ERR("failed to register listner\n"); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_amrwb_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audamrwb_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audamrwb_resume; + audio->suspend_ctl.node.suspend = audamrwb_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDAMRWB_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audamrwb_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_amrwb_fops = { + .owner = THIS_MODULE, + .open = audamrwb_open, + .release = audamrwb_release, + .read = audamrwb_read, + .write = audamrwb_write, + .unlocked_ioctl = audamrwb_ioctl, + .fsync = audamrwb_fsync, +}; + +struct miscdevice audio_amrwb_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrwb", + .fops = &audio_amrwb_fops, +}; + +static int __init audamrwb_init(void) +{ + return misc_register(&audio_amrwb_misc); +} + +static void __exit audamrwb_exit(void) +{ + misc_deregister(&audio_amrwb_misc); +} + +module_init(audamrwb_init); +module_exit(audamrwb_exit); + +MODULE_DESCRIPTION("MSM AMR-WB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_dev_ctl.c b/arch/arm/mach-msm/qdsp5v2/audio_dev_ctl.c new file mode 100644 index 00000000000..b6d6e5e4346 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_dev_ctl.c @@ -0,0 +1,1328 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + + +static DEFINE_MUTEX(session_lock); + +struct audio_dev_ctrl_state { + struct msm_snddev_info *devs[AUDIO_DEV_CTL_MAX_DEV]; + u32 num_dev; + atomic_t opened; + struct msm_snddev_info *voice_rx_dev; + struct msm_snddev_info *voice_tx_dev; + wait_queue_head_t wait; +}; + +static struct audio_dev_ctrl_state audio_dev_ctrl; +struct event_listner event; +#define MAX_DEC_SESSIONS 7 +#define MAX_ENC_SESSIONS 3 + +struct session_freq { + int freq; + int evt; +}; + + +struct audio_routing_info { + unsigned short mixer_mask[MAX_DEC_SESSIONS]; + unsigned short audrec_mixer_mask[MAX_ENC_SESSIONS]; + struct session_freq dec_freq[MAX_DEC_SESSIONS]; + struct session_freq enc_freq[MAX_ENC_SESSIONS]; + int dual_mic_setting[MAX_ENC_SESSIONS]; + int voice_tx_dev_id; + int voice_rx_dev_id; + int voice_tx_sample_rate; + int voice_rx_sample_rate; + signed int voice_tx_vol; + signed int voice_rx_vol; + int tx_mute; + int rx_mute; + int voice_state; +}; + +static struct audio_routing_info routing_info; + +#ifdef CONFIG_DEBUG_FS + +static struct dentry *dentry; +static int rtc_getdevice_dbg_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + MM_INFO("debug intf %s\n", (char *) file->private_data); + return 0; +} +bool is_dev_opened(u32 adb_id) +{ + + int dev_id = 0; + struct msm_snddev_info *dev_info = NULL; + + for (dev_id = 0; dev_id < audio_dev_ctrl.num_dev; dev_id++) { + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id %d\n", dev_id); + return false; + } + if (dev_info->opened && (dev_info->acdb_id == adb_id)) + return true; + } + + return false; +} +static ssize_t rtc_getdevice_dbg_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + static char buffer[1024]; + static char swap_buf[1024]; + const int debug_bufmax = sizeof(buffer); + int n = 0; + int swap_count = 0; + int rc = 0; + int dev_count = 0; + int dev_id = 0; + struct msm_snddev_info *dev_info = NULL; + + + if (audio_dev_ctrl.num_dev <= 0) { + MM_ERR("Invalid no Device present\n"); + dev_count = 0; + n = scnprintf(buffer, debug_bufmax, "DEV_NO:0x%x\n", dev_count); + } else { + for (dev_id = 0; dev_id < audio_dev_ctrl.num_dev; dev_id++) { + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id %d\n", dev_id); + rc = PTR_ERR(dev_info); + return rc; + } + if (dev_info->opened) { + n += scnprintf(swap_buf + n, debug_bufmax - n, + "ACDB_ID:0x%x;CAPB:0x%x\n", + dev_info->acdb_id, + dev_info->capability); + dev_count++; + MM_DBG("RTC Get Device %x COPP %x Session Mask \ + %x Capb %x Dev Count %x\n", + dev_id , dev_info->copp_id, dev_info->sessions, + dev_info->capability, dev_count); + + } + } + + swap_count = scnprintf(buffer, debug_bufmax, \ + "DEV_NO:0x%x\n", dev_count); + + memcpy(buffer+swap_count, swap_buf, n*sizeof(char)); + n = n+swap_count; + + buffer[n] = 0; + } + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations rtc_acdb_debug_fops = { + .open = rtc_getdevice_dbg_open, + .read = rtc_getdevice_dbg_read +}; +#endif +int msm_reset_all_device(void) +{ + int rc = 0; + int dev_id = 0; + struct msm_snddev_info *dev_info = NULL; + + for (dev_id = 0; dev_id < audio_dev_ctrl.num_dev; dev_id++) { + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id %d\n", dev_id); + rc = PTR_ERR(dev_info); + return rc; + } + if (!dev_info->opened) + continue; + MM_DBG("Resetting device %d active on COPP %d" + "with 0x%08x as routing\n", + dev_id, dev_info->copp_id, dev_info->sessions); + broadcast_event(AUDDEV_EVT_REL_PENDING, + dev_id, + SESSION_IGNORE); + rc = dev_info->dev_ops.close(dev_info); + if (rc < 0) { + MM_ERR("Snd device %d failed close!\n", dev_id); + return rc; + } else { + dev_info->opened = 0; + broadcast_event(AUDDEV_EVT_DEV_RLS, + dev_id, + SESSION_IGNORE); + } + dev_info->sessions = 0; + } + return 0; +} +EXPORT_SYMBOL(msm_reset_all_device); + +int msm_set_dual_mic_config(int enc_session_id, int config) +{ + int i; + if (enc_session_id >= MAX_ENC_SESSIONS) + return -EINVAL; + /*config is set(1) dual mic recording is selected */ + /*config is reset (0) dual mic recording is not selected*/ + routing_info.dual_mic_setting[enc_session_id] = config; + for (i = 0; i < MAX_ENC_SESSIONS; i++) + MM_DBG("dual_mic_setting[%d] = %d\n", + i, routing_info.dual_mic_setting[i]); + return 0; +} +EXPORT_SYMBOL(msm_set_dual_mic_config); + +int msm_get_dual_mic_config(int enc_session_id) +{ + if (enc_session_id >= MAX_ENC_SESSIONS) + return -EINVAL; + return routing_info.dual_mic_setting[enc_session_id]; +} +EXPORT_SYMBOL(msm_get_dual_mic_config); + +int msm_get_voice_state(void) +{ + MM_DBG("voice state %d\n", routing_info.voice_state); + return routing_info.voice_state; +} +EXPORT_SYMBOL(msm_get_voice_state); + +int msm_set_voice_mute(int dir, int mute) +{ + MM_DBG("dir %x mute %x\n", dir, mute); + if (!audio_dev_ctrl.voice_rx_dev + || !audio_dev_ctrl.voice_tx_dev) + return -EPERM; + if (dir == DIR_TX) { + routing_info.tx_mute = mute; + broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, + routing_info.voice_tx_dev_id, SESSION_IGNORE); + } else + return -EPERM; + return 0; +} +EXPORT_SYMBOL(msm_set_voice_mute); + +int msm_set_voice_vol(int dir, s32 volume) +{ + if (!audio_dev_ctrl.voice_rx_dev + || !audio_dev_ctrl.voice_tx_dev) + return -EPERM; + if (dir == DIR_TX) { + routing_info.voice_tx_vol = volume; + broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, + routing_info.voice_tx_dev_id, + SESSION_IGNORE); + } else if (dir == DIR_RX) { + routing_info.voice_rx_vol = volume; + broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, + routing_info.voice_rx_dev_id, + SESSION_IGNORE); + } else + return -EINVAL; + return 0; +} +EXPORT_SYMBOL(msm_set_voice_vol); + +void msm_snddev_register(struct msm_snddev_info *dev_info) +{ + mutex_lock(&session_lock); + if (audio_dev_ctrl.num_dev < AUDIO_DEV_CTL_MAX_DEV) { + audio_dev_ctrl.devs[audio_dev_ctrl.num_dev] = dev_info; + dev_info->dev_volume = 50; /* 50% */ + dev_info->sessions = 0x0; + dev_info->usage_count = 0; + dev_info->set_sample_rate = 0; + audio_dev_ctrl.num_dev++; + } else + MM_ERR("%s: device registry max out\n", __func__); + mutex_unlock(&session_lock); +} +EXPORT_SYMBOL(msm_snddev_register); + +int msm_snddev_devcount(void) +{ + return audio_dev_ctrl.num_dev; +} +EXPORT_SYMBOL(msm_snddev_devcount); + +int msm_snddev_query(int dev_id) +{ + if (dev_id <= audio_dev_ctrl.num_dev) + return 0; + return -ENODEV; +} +EXPORT_SYMBOL(msm_snddev_query); + +int msm_snddev_is_set(int popp_id, int copp_id) +{ + return routing_info.mixer_mask[popp_id] & (0x1 << copp_id); +} +EXPORT_SYMBOL(msm_snddev_is_set); + +unsigned short msm_snddev_route_enc(int enc_id) +{ + if (enc_id >= MAX_ENC_SESSIONS) + return -EINVAL; + return routing_info.audrec_mixer_mask[enc_id]; +} +EXPORT_SYMBOL(msm_snddev_route_enc); + +unsigned short msm_snddev_route_dec(int popp_id) +{ + if (popp_id >= MAX_DEC_SESSIONS) + return -EINVAL; + return routing_info.mixer_mask[popp_id]; +} +EXPORT_SYMBOL(msm_snddev_route_dec); + +int msm_snddev_set_dec(int popp_id, int copp_id, int set) +{ + if (set) + routing_info.mixer_mask[popp_id] |= (0x1 << copp_id); + else + routing_info.mixer_mask[popp_id] &= ~(0x1 << copp_id); + + return 0; +} +EXPORT_SYMBOL(msm_snddev_set_dec); + +int msm_snddev_set_enc(int popp_id, int copp_id, int set) +{ + if (set) + routing_info.audrec_mixer_mask[popp_id] |= (0x1 << copp_id); + else + routing_info.audrec_mixer_mask[popp_id] &= ~(0x1 << copp_id); + return 0; +} +EXPORT_SYMBOL(msm_snddev_set_enc); + +int msm_device_is_voice(int dev_id) +{ + if ((dev_id == routing_info.voice_rx_dev_id) + || (dev_id == routing_info.voice_tx_dev_id)) + return 0; + else + return -EINVAL; +} +EXPORT_SYMBOL(msm_device_is_voice); + +int msm_set_voc_route(struct msm_snddev_info *dev_info, + int stream_type, int dev_id) +{ + int rc = 0; + u32 session_mask = 0; + + mutex_lock(&session_lock); + switch (stream_type) { + case AUDIO_ROUTE_STREAM_VOICE_RX: + if (audio_dev_ctrl.voice_rx_dev) + audio_dev_ctrl.voice_rx_dev->sessions &= ~0xFF; + + if (!(dev_info->capability & SNDDEV_CAP_RX) | + !(dev_info->capability & SNDDEV_CAP_VOICE)) { + rc = -EINVAL; + break; + } + audio_dev_ctrl.voice_rx_dev = dev_info; + if (audio_dev_ctrl.voice_rx_dev) { + session_mask = + 0x1 << (8 * ((int)AUDDEV_CLNT_VOC-1)); + audio_dev_ctrl.voice_rx_dev->sessions |= + session_mask; + } + routing_info.voice_rx_dev_id = dev_id; + break; + case AUDIO_ROUTE_STREAM_VOICE_TX: + if (audio_dev_ctrl.voice_tx_dev) + audio_dev_ctrl.voice_tx_dev->sessions &= ~0xFF; + + if (!(dev_info->capability & SNDDEV_CAP_TX) | + !(dev_info->capability & SNDDEV_CAP_VOICE)) { + rc = -EINVAL; + break; + } + + audio_dev_ctrl.voice_tx_dev = dev_info; + if (audio_dev_ctrl.voice_rx_dev) { + session_mask = + 0x1 << (8 * ((int)AUDDEV_CLNT_VOC-1)); + audio_dev_ctrl.voice_tx_dev->sessions |= + session_mask; + } + routing_info.voice_tx_dev_id = dev_id; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&session_lock); + return rc; +} +EXPORT_SYMBOL(msm_set_voc_route); + +void msm_release_voc_thread(void) +{ + wake_up(&audio_dev_ctrl.wait); +} +EXPORT_SYMBOL(msm_release_voc_thread); + +int msm_snddev_get_enc_freq(session_id) +{ + return routing_info.enc_freq[session_id].freq; +} +EXPORT_SYMBOL(msm_snddev_get_enc_freq); + +int msm_get_voc_freq(int *tx_freq, int *rx_freq) +{ + *tx_freq = routing_info.voice_tx_sample_rate; + *rx_freq = routing_info.voice_rx_sample_rate; + return 0; +} +EXPORT_SYMBOL(msm_get_voc_freq); + +int msm_get_voc_route(u32 *rx_id, u32 *tx_id) +{ + int rc = 0; + + if (!rx_id || !tx_id) + return -EINVAL; + + mutex_lock(&session_lock); + if (!audio_dev_ctrl.voice_rx_dev || !audio_dev_ctrl.voice_tx_dev) { + rc = -ENODEV; + mutex_unlock(&session_lock); + return rc; + } + + *rx_id = audio_dev_ctrl.voice_rx_dev->acdb_id; + *tx_id = audio_dev_ctrl.voice_tx_dev->acdb_id; + + mutex_unlock(&session_lock); + + return rc; +} +EXPORT_SYMBOL(msm_get_voc_route); + +struct msm_snddev_info *audio_dev_ctrl_find_dev(u32 dev_id) +{ + struct msm_snddev_info *info; + + if ((audio_dev_ctrl.num_dev - 1) < dev_id) { + info = ERR_PTR(-ENODEV); + goto error; + } + + info = audio_dev_ctrl.devs[dev_id]; +error: + return info; + +} +EXPORT_SYMBOL(audio_dev_ctrl_find_dev); + +int snddev_voice_set_volume(int vol, int path) +{ + if (audio_dev_ctrl.voice_rx_dev + && audio_dev_ctrl.voice_tx_dev) { + if (path) + audio_dev_ctrl.voice_tx_dev->dev_volume = vol; + else + audio_dev_ctrl.voice_rx_dev->dev_volume = vol; + } else + return -ENODEV; + return 0; +} +EXPORT_SYMBOL(snddev_voice_set_volume); + +static int audio_dev_ctrl_get_devices(struct audio_dev_ctrl_state *dev_ctrl, + void __user *arg) +{ + int rc = 0; + u32 index; + struct msm_snd_device_list work_list; + struct msm_snd_device_info *work_tbl; + + if (copy_from_user(&work_list, arg, sizeof(work_list))) { + rc = -EFAULT; + goto error; + } + + if (work_list.num_dev > dev_ctrl->num_dev) { + rc = -EINVAL; + goto error; + } + + work_tbl = kmalloc(work_list.num_dev * + sizeof(struct msm_snd_device_info), GFP_KERNEL); + if (!work_tbl) { + rc = -ENOMEM; + goto error; + } + + for (index = 0; index < dev_ctrl->num_dev; index++) { + work_tbl[index].dev_id = index; + work_tbl[index].dev_cap = dev_ctrl->devs[index]->capability; + strlcpy(work_tbl[index].dev_name, dev_ctrl->devs[index]->name, + 64); + } + + if (copy_to_user((void *) (work_list.list), work_tbl, + work_list.num_dev * sizeof(struct msm_snd_device_info))) + rc = -EFAULT; + kfree(work_tbl); +error: + return rc; +} + + +int auddev_register_evt_listner(u32 evt_id, u32 clnt_type, u32 clnt_id, + void (*listner)(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data), + void *private_data) +{ + int rc; + struct msm_snd_evt_listner *callback = NULL; + struct msm_snd_evt_listner *new_cb; + + new_cb = kzalloc(sizeof(struct msm_snd_evt_listner), GFP_KERNEL); + if (!new_cb) { + MM_ERR("No memory to add new listener node\n"); + return -ENOMEM; + } + + mutex_lock(&session_lock); + new_cb->cb_next = NULL; + new_cb->auddev_evt_listener = listner; + new_cb->evt_id = evt_id; + new_cb->clnt_type = clnt_type; + new_cb->clnt_id = clnt_id; + new_cb->private_data = private_data; + if (event.cb == NULL) { + event.cb = new_cb; + new_cb->cb_prev = NULL; + } else { + callback = event.cb; + for (; ;) { + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + callback->cb_next = new_cb; + new_cb->cb_prev = callback; + } + event.num_listner++; + mutex_unlock(&session_lock); + rc = 0; + return rc; +} +EXPORT_SYMBOL(auddev_register_evt_listner); + +int auddev_unregister_evt_listner(u32 clnt_type, u32 clnt_id) +{ + struct msm_snd_evt_listner *callback = event.cb; + struct msm_snddev_info *info; + u32 session_mask = 0; + int i = 0; + + mutex_lock(&session_lock); + while (callback != NULL) { + if ((callback->clnt_type == clnt_type) + && (callback->clnt_id == clnt_id)) + break; + callback = callback->cb_next; + } + if (callback == NULL) { + mutex_unlock(&session_lock); + return -EINVAL; + } + + if ((callback->cb_next == NULL) && (callback->cb_prev == NULL)) + event.cb = NULL; + else if (callback->cb_next == NULL) + callback->cb_prev->cb_next = NULL; + else if (callback->cb_prev == NULL) { + callback->cb_next->cb_prev = NULL; + event.cb = callback->cb_next; + } else { + callback->cb_prev->cb_next = callback->cb_next; + callback->cb_next->cb_prev = callback->cb_prev; + } + kfree(callback); + + session_mask = (0x1 << (clnt_id)) << (8 * ((int)clnt_type-1)); + for (i = 0; i < audio_dev_ctrl.num_dev; i++) { + info = audio_dev_ctrl.devs[i]; + info->sessions &= ~session_mask; + } + if (clnt_type == AUDDEV_CLNT_ENC) + msm_set_dual_mic_config(clnt_id, 0); + mutex_unlock(&session_lock); + return 0; +} +EXPORT_SYMBOL(auddev_unregister_evt_listner); + +int msm_snddev_withdraw_freq(u32 session_id, u32 capability, u32 clnt_type) +{ + int i = 0; + struct msm_snddev_info *info; + u32 session_mask = 0; + + if ((clnt_type == AUDDEV_CLNT_VOC) && (session_id != 0)) + return -EINVAL; + if ((clnt_type == AUDDEV_CLNT_DEC) + && (session_id >= MAX_DEC_SESSIONS)) + return -EINVAL; + if ((clnt_type == AUDDEV_CLNT_ENC) + && (session_id >= MAX_ENC_SESSIONS)) + return -EINVAL; + + session_mask = (0x1 << (session_id)) << (8 * ((int)clnt_type-1)); + + for (i = 0; i < audio_dev_ctrl.num_dev; i++) { + info = audio_dev_ctrl.devs[i]; + if ((info->sessions & session_mask) + && (info->capability & capability)) { + if (!(info->sessions & ~(session_mask))) + info->set_sample_rate = 0; + } + } + if (clnt_type == AUDDEV_CLNT_DEC) + routing_info.dec_freq[session_id].freq + = 0; + else if (clnt_type == AUDDEV_CLNT_ENC) + routing_info.enc_freq[session_id].freq + = 0; + else if (capability == SNDDEV_CAP_TX) + routing_info.voice_tx_sample_rate = 0; + else + routing_info.voice_rx_sample_rate = 48000; + return 0; +} + +int msm_snddev_request_freq(int *freq, u32 session_id, + u32 capability, u32 clnt_type) +{ + int i = 0; + int rc = 0; + struct msm_snddev_info *info; + u32 set_freq; + u32 session_mask = 0; + u32 clnt_type_mask = 0; + + MM_DBG(": clnt_type 0x%08x\n", clnt_type); + + if ((clnt_type == AUDDEV_CLNT_VOC) && (session_id != 0)) + return -EINVAL; + if ((clnt_type == AUDDEV_CLNT_DEC) + && (session_id >= MAX_DEC_SESSIONS)) + return -EINVAL; + if ((clnt_type == AUDDEV_CLNT_ENC) + && (session_id >= MAX_ENC_SESSIONS)) + return -EINVAL; + session_mask = ((0x1 << session_id)) << (8 * (clnt_type-1)); + clnt_type_mask = (0xFF << (8 * (clnt_type-1))); + if (!(*freq == 8000) && !(*freq == 11025) && + !(*freq == 12000) && !(*freq == 16000) && + !(*freq == 22050) && !(*freq == 24000) && + !(*freq == 32000) && !(*freq == 44100) && + !(*freq == 48000)) + return -EINVAL; + + for (i = 0; i < audio_dev_ctrl.num_dev; i++) { + info = audio_dev_ctrl.devs[i]; + if ((info->sessions & session_mask) + && (info->capability & capability)) { + rc = 0; + if ((info->sessions & ~clnt_type_mask) + && ((*freq != 8000) && (*freq != 16000) + && (*freq != 48000))) { + if (clnt_type == AUDDEV_CLNT_ENC) { + routing_info.enc_freq[session_id].freq + = 0; + return -EPERM; + } else if (clnt_type == AUDDEV_CLNT_DEC) { + routing_info.dec_freq[session_id].freq + = 0; + return -EPERM; + } + } + if (*freq == info->set_sample_rate) { + rc = info->set_sample_rate; + continue; + } + set_freq = MAX(*freq, info->set_sample_rate); + + + if (clnt_type == AUDDEV_CLNT_DEC) + routing_info.dec_freq[session_id].freq + = set_freq; + else if (clnt_type == AUDDEV_CLNT_ENC) + routing_info.enc_freq[session_id].freq + = set_freq; + else if (capability == SNDDEV_CAP_TX) + routing_info.voice_tx_sample_rate = set_freq; + + rc = set_freq; + *freq = set_freq; + /* There is difference in device sample rate to + * requested sample rate. So update device sample rate + * and propagate sample rate change event to active + * sessions of the device. + */ + if (info->set_sample_rate != set_freq) { + info->set_sample_rate = set_freq; + if (info->opened) { + /* Ignore propagating sample rate + * change event to requested client + * session + */ + if (clnt_type == AUDDEV_CLNT_DEC) + routing_info.\ + dec_freq[session_id].evt = 1; + else if (clnt_type == AUDDEV_CLNT_ENC) + routing_info.\ + enc_freq[session_id].evt = 1; + broadcast_event(AUDDEV_EVT_FREQ_CHG, i, + SESSION_IGNORE); + set_freq = info->dev_ops.set_freq(info, + set_freq); + broadcast_event(AUDDEV_EVT_DEV_RDY, i, + SESSION_IGNORE); + } + } + } + MM_DBG("info->set_sample_rate = %d\n", info->set_sample_rate); + MM_DBG("routing_info.enc_freq.freq = %d\n", + routing_info.enc_freq[session_id].freq); + } + return rc; +} +EXPORT_SYMBOL(msm_snddev_request_freq); + +int msm_snddev_enable_sidetone(u32 dev_id, u32 enable) +{ + int rc; + struct msm_snddev_info *dev_info; + + MM_DBG("dev_id %d enable %d\n", dev_id, enable); + + dev_info = audio_dev_ctrl_find_dev(dev_id); + + if (IS_ERR(dev_info)) { + MM_ERR("bad dev_id %d\n", dev_id); + rc = -EINVAL; + } else if (!dev_info->dev_ops.enable_sidetone) { + MM_DBG("dev %d no sidetone support\n", dev_id); + rc = -EPERM; + } else + rc = dev_info->dev_ops.enable_sidetone(dev_info, enable); + + return rc; +} +EXPORT_SYMBOL(msm_snddev_enable_sidetone); + +static long audio_dev_ctrl_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int rc = 0; + struct audio_dev_ctrl_state *dev_ctrl = file->private_data; + + mutex_lock(&session_lock); + switch (cmd) { + case AUDIO_GET_NUM_SND_DEVICE: + rc = put_user(dev_ctrl->num_dev, (uint32_t __user *) arg); + break; + case AUDIO_GET_SND_DEVICES: + rc = audio_dev_ctrl_get_devices(dev_ctrl, (void __user *) arg); + break; + case AUDIO_ENABLE_SND_DEVICE: { + struct msm_snddev_info *dev_info; + u32 dev_id; + + if (get_user(dev_id, (u32 __user *) arg)) { + rc = -EFAULT; + break; + } + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) + rc = PTR_ERR(dev_info); + else { + rc = dev_info->dev_ops.open(dev_info); + if (!rc) + dev_info->opened = 1; + wake_up(&audio_dev_ctrl.wait); + } + break; + + } + + case AUDIO_DISABLE_SND_DEVICE: { + struct msm_snddev_info *dev_info; + u32 dev_id; + + if (get_user(dev_id, (u32 __user *) arg)) { + rc = -EFAULT; + break; + } + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) + rc = PTR_ERR(dev_info); + else { + rc = dev_info->dev_ops.close(dev_info); + dev_info->opened = 0; + } + break; + } + + case AUDIO_ROUTE_STREAM: { + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + + if (copy_from_user(&route_cfg, (void __user *) arg, + sizeof(struct msm_audio_route_config))) { + rc = -EFAULT; + break; + } + MM_DBG("%s: route cfg %d %d type\n", __func__, + route_cfg.dev_id, route_cfg.stream_type); + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + MM_ERR("%s: pass invalid dev_id\n", __func__); + rc = PTR_ERR(dev_info); + break; + } + + switch (route_cfg.stream_type) { + + case AUDIO_ROUTE_STREAM_VOICE_RX: + if (!(dev_info->capability & SNDDEV_CAP_RX) | + !(dev_info->capability & SNDDEV_CAP_VOICE)) { + rc = -EINVAL; + break; + } + dev_ctrl->voice_rx_dev = dev_info; + break; + case AUDIO_ROUTE_STREAM_VOICE_TX: + if (!(dev_info->capability & SNDDEV_CAP_TX) | + !(dev_info->capability & SNDDEV_CAP_VOICE)) { + rc = -EINVAL; + break; + } + dev_ctrl->voice_tx_dev = dev_info; + break; + } + break; + } + + default: + rc = -EINVAL; + } + mutex_unlock(&session_lock); + return rc; +} + +static int audio_dev_ctrl_open(struct inode *inode, struct file *file) +{ + MM_DBG("open audio_dev_ctrl\n"); + atomic_inc(&audio_dev_ctrl.opened); + file->private_data = &audio_dev_ctrl; + return 0; +} + +static int audio_dev_ctrl_release(struct inode *inode, struct file *file) +{ + MM_DBG("release audio_dev_ctrl\n"); + atomic_dec(&audio_dev_ctrl.opened); + return 0; +} + +static const struct file_operations audio_dev_ctrl_fops = { + .owner = THIS_MODULE, + .open = audio_dev_ctrl_open, + .release = audio_dev_ctrl_release, + .unlocked_ioctl = audio_dev_ctrl_ioctl, +}; + + +struct miscdevice audio_dev_ctrl_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_audio_dev_ctrl", + .fops = &audio_dev_ctrl_fops, +}; + +/* session id is 32 bit routing mask per device + * 0-7 for voice clients + * 8-15 for Decoder clients + * 16-23 for Encoder clients + * 24-31 Do not care + */ +void broadcast_event(u32 evt_id, u32 dev_id, u32 session_id) +{ + int clnt_id = 0, i; + union auddev_evt_data *evt_payload; + struct msm_snd_evt_listner *callback; + struct msm_snddev_info *dev_info = NULL; + u32 session_mask = 0; + static int pending_sent; + + MM_DBG(": evt_id = %d\n", evt_id); + + if ((evt_id != AUDDEV_EVT_START_VOICE) + && (evt_id != AUDDEV_EVT_END_VOICE) + && (evt_id != AUDDEV_EVT_STREAM_VOL_CHG) + && (evt_id != AUDDEV_EVT_VOICE_STATE_CHG)) { + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id\n"); + return; + } + } + + if (event.cb != NULL) + callback = event.cb; + else + return; + + evt_payload = kzalloc(sizeof(union auddev_evt_data), + GFP_KERNEL); + if (evt_payload == NULL) { + MM_ERR("Memory allocation for event payload failed\n"); + return; + } + + mutex_lock(&session_lock); + + if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + routing_info.voice_state = dev_id; + + for (; ;) { + if (!(evt_id & callback->evt_id)) { + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + clnt_id = callback->clnt_id; + memset(evt_payload, 0, sizeof(union auddev_evt_data)); + + if ((evt_id == AUDDEV_EVT_START_VOICE) + || (evt_id == AUDDEV_EVT_END_VOICE)) + goto skip_check; + if (callback->clnt_type == AUDDEV_CLNT_AUDIOCAL) + goto aud_cal; + + session_mask = (0x1 << (clnt_id)) + << (8 * ((int)callback->clnt_type-1)); + + if ((evt_id == AUDDEV_EVT_STREAM_VOL_CHG) || \ + (evt_id == AUDDEV_EVT_VOICE_STATE_CHG)) { + MM_DBG("AUDDEV_EVT_STREAM_VOL_CHG or\ + AUDDEV_EVT_VOICE_STATE_CHG\n"); + goto volume_strm; + } + + MM_DBG("dev_info->sessions = %08x\n", dev_info->sessions); + + if ((!session_id && !(dev_info->sessions & session_mask)) || + (session_id && ((dev_info->sessions & session_mask) != + session_id))) { + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + if (evt_id == AUDDEV_EVT_DEV_CHG_VOICE) + goto voc_events; + +volume_strm: + if (callback->clnt_type == AUDDEV_CLNT_DEC) { + MM_DBG("AUDDEV_CLNT_DEC\n"); + if (evt_id == AUDDEV_EVT_STREAM_VOL_CHG) { + MM_DBG("clnt_id = %d, session_id = 0x%8x\n", + clnt_id, session_id); + if (session_mask != session_id) + goto sent_dec; + else + evt_payload->session_vol = + msm_vol_ctl.volume; + } else if (evt_id == AUDDEV_EVT_FREQ_CHG) { + if (routing_info.dec_freq[clnt_id].evt) { + routing_info.dec_freq[clnt_id].evt + = 0; + goto sent_dec; + } else if (routing_info.dec_freq[clnt_id].freq + == dev_info->set_sample_rate) + goto sent_dec; + else { + evt_payload->freq_info.sample_rate + = dev_info->set_sample_rate; + evt_payload->freq_info.dev_type + = dev_info->capability; + evt_payload->freq_info.acdb_dev_id + = dev_info->acdb_id; + } + /* Propogate device information to client */ + } else if (evt_id == AUDDEV_EVT_DEVICE_INFO) { + evt_payload->devinfo.dev_id + = dev_info->copp_id; + evt_payload->devinfo.acdb_id + = dev_info->acdb_id; + evt_payload->devinfo.dev_type = + (dev_info->capability & SNDDEV_CAP_TX) ? + SNDDEV_CAP_TX : SNDDEV_CAP_RX; + evt_payload->devinfo.sample_rate + = dev_info->sample_rate; + if (session_id == SESSION_IGNORE) + evt_payload->devinfo.sessions + = dev_info->sessions; + else + evt_payload->devinfo.sessions + = session_id; + evt_payload->devinfo.sessions = + (evt_payload->devinfo.sessions >> + ((AUDDEV_CLNT_DEC-1) * 8)); + } else if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + evt_payload->voice_state = + routing_info.voice_state; + else + evt_payload->routing_id = dev_info->copp_id; + callback->auddev_evt_listener( + evt_id, + evt_payload, + callback->private_data); +sent_dec: + if ((evt_id != AUDDEV_EVT_STREAM_VOL_CHG) && + (evt_id != AUDDEV_EVT_VOICE_STATE_CHG)) + routing_info.dec_freq[clnt_id].freq + = dev_info->set_sample_rate; + + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + if (callback->clnt_type == AUDDEV_CLNT_ENC) { + + MM_DBG("AUDDEV_CLNT_ENC\n"); + if (evt_id == AUDDEV_EVT_FREQ_CHG) { + if (routing_info.enc_freq[clnt_id].evt) { + routing_info.enc_freq[clnt_id].evt + = 0; + goto sent_enc; + } else { + evt_payload->freq_info.sample_rate + = dev_info->set_sample_rate; + evt_payload->freq_info.dev_type + = dev_info->capability; + evt_payload->freq_info.acdb_dev_id + = dev_info->acdb_id; + } + /* Propogate device information to client */ + } else if (evt_id == AUDDEV_EVT_DEVICE_INFO) { + evt_payload->devinfo.dev_id + = dev_info->copp_id; + evt_payload->devinfo.acdb_id + = dev_info->acdb_id; + evt_payload->devinfo.dev_type = + (dev_info->capability & SNDDEV_CAP_TX) ? + SNDDEV_CAP_TX : SNDDEV_CAP_RX; + evt_payload->devinfo.sample_rate + = dev_info->sample_rate; + if (session_id == SESSION_IGNORE) + evt_payload->devinfo.sessions + = dev_info->sessions; + else + evt_payload->devinfo.sessions + = session_id; + evt_payload->devinfo.sessions = + (evt_payload->devinfo.sessions >> + ((AUDDEV_CLNT_ENC-1) * 8)); + } else if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + evt_payload->voice_state = + routing_info.voice_state; + else + evt_payload->routing_id = dev_info->copp_id; + callback->auddev_evt_listener( + evt_id, + evt_payload, + callback->private_data); +sent_enc: + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } +aud_cal: + if (callback->clnt_type == AUDDEV_CLNT_AUDIOCAL) { + int temp_sessions; + MM_DBG("AUDDEV_CLNT_AUDIOCAL\n"); + if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + evt_payload->voice_state = + routing_info.voice_state; + else if (!dev_info->sessions) + goto sent_aud_cal; + else { + evt_payload->audcal_info.dev_id = + dev_info->copp_id; + evt_payload->audcal_info.acdb_id = + dev_info->acdb_id; + evt_payload->audcal_info.dev_type = + (dev_info->capability & SNDDEV_CAP_TX) ? + SNDDEV_CAP_TX : SNDDEV_CAP_RX; + evt_payload->audcal_info.sample_rate = + dev_info->set_sample_rate ? + dev_info->set_sample_rate : + dev_info->sample_rate; + } + if (evt_payload->audcal_info.dev_type == + SNDDEV_CAP_TX) { + if (session_id == SESSION_IGNORE) + temp_sessions = dev_info->sessions; + else + temp_sessions = session_id; + evt_payload->audcal_info.sessions = + (temp_sessions >> + ((AUDDEV_CLNT_ENC-1) * 8)); + } else { + if (session_id == SESSION_IGNORE) + temp_sessions = dev_info->sessions; + else + temp_sessions = session_id; + evt_payload->audcal_info.sessions = + (temp_sessions >> + ((AUDDEV_CLNT_DEC-1) * 8)); + } + callback->auddev_evt_listener( + evt_id, + evt_payload, + callback->private_data); + +sent_aud_cal: + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } +skip_check: +voc_events: + if (callback->clnt_type == AUDDEV_CLNT_VOC) { + MM_DBG("AUDDEV_CLNT_VOC\n"); + if (evt_id == AUDDEV_EVT_DEV_RLS) { + if (!pending_sent) + goto sent_voc; + else + pending_sent = 0; + } + if (evt_id == AUDDEV_EVT_REL_PENDING) + pending_sent = 1; + + if (evt_id == AUDDEV_EVT_DEVICE_VOL_MUTE_CHG) { + if (dev_info->capability & SNDDEV_CAP_TX) { + evt_payload->voc_vm_info.dev_type = + SNDDEV_CAP_TX; + evt_payload->voc_vm_info.acdb_dev_id = + dev_info->acdb_id; + evt_payload-> + voc_vm_info.dev_vm_val.mute = + routing_info.tx_mute; + } else { + evt_payload->voc_vm_info.dev_type = + SNDDEV_CAP_RX; + evt_payload->voc_vm_info.acdb_dev_id = + dev_info->acdb_id; + evt_payload-> + voc_vm_info.dev_vm_val.vol = + routing_info.voice_rx_vol; + } + } else if ((evt_id == AUDDEV_EVT_START_VOICE) + || (evt_id == AUDDEV_EVT_END_VOICE)) + memset(evt_payload, 0, + sizeof(union auddev_evt_data)); + else if (evt_id == AUDDEV_EVT_FREQ_CHG) { + if (routing_info.voice_tx_sample_rate + != dev_info->set_sample_rate) { + routing_info.voice_tx_sample_rate + = dev_info->set_sample_rate; + evt_payload->freq_info.sample_rate + = dev_info->set_sample_rate; + evt_payload->freq_info.dev_type + = dev_info->capability; + evt_payload->freq_info.acdb_dev_id + = dev_info->acdb_id; + } else + goto sent_voc; + } else if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + evt_payload->voice_state = + routing_info.voice_state; + else { + evt_payload->voc_devinfo.dev_type = + (dev_info->capability & SNDDEV_CAP_TX) ? + SNDDEV_CAP_TX : SNDDEV_CAP_RX; + evt_payload->voc_devinfo.acdb_dev_id = + dev_info->acdb_id; + evt_payload->voc_devinfo.dev_sample = + dev_info->set_sample_rate ? + dev_info->set_sample_rate : + dev_info->sample_rate; + evt_payload->voc_devinfo.dev_id = dev_id; + if (dev_info->capability & SNDDEV_CAP_RX) { + for (i = 0; i < VOC_RX_VOL_ARRAY_NUM; + i++) { + evt_payload-> + voc_devinfo.max_rx_vol[i] = + dev_info->max_voc_rx_vol[i]; + evt_payload + ->voc_devinfo.min_rx_vol[i] = + dev_info->min_voc_rx_vol[i]; + } + } + } + callback->auddev_evt_listener( + evt_id, + evt_payload, + callback->private_data); + if (evt_id == AUDDEV_EVT_DEV_RLS) + dev_info->sessions &= ~(0xFF); +sent_voc: + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + } + kfree(evt_payload); + mutex_unlock(&session_lock); +} +EXPORT_SYMBOL(broadcast_event); + + +void mixer_post_event(u32 evt_id, u32 id) +{ + + MM_DBG("evt_id = %d\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_CHG_VOICE: /* Called from Voice_route */ + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, id, SESSION_IGNORE); + break; + case AUDDEV_EVT_DEV_RDY: + broadcast_event(AUDDEV_EVT_DEV_RDY, id, SESSION_IGNORE); + break; + case AUDDEV_EVT_DEV_RLS: + broadcast_event(AUDDEV_EVT_DEV_RLS, id, SESSION_IGNORE); + break; + case AUDDEV_EVT_REL_PENDING: + broadcast_event(AUDDEV_EVT_REL_PENDING, id, SESSION_IGNORE); + break; + case AUDDEV_EVT_DEVICE_VOL_MUTE_CHG: + broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, id, + SESSION_IGNORE); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + broadcast_event(AUDDEV_EVT_STREAM_VOL_CHG, id, + SESSION_IGNORE); + break; + case AUDDEV_EVT_START_VOICE: + broadcast_event(AUDDEV_EVT_START_VOICE, + id, SESSION_IGNORE); + break; + case AUDDEV_EVT_END_VOICE: + broadcast_event(AUDDEV_EVT_END_VOICE, + id, SESSION_IGNORE); + break; + case AUDDEV_EVT_FREQ_CHG: + broadcast_event(AUDDEV_EVT_FREQ_CHG, id, SESSION_IGNORE); + break; + default: + break; + } +} +EXPORT_SYMBOL(mixer_post_event); + +static int __init audio_dev_ctrl_init(void) +{ +#ifdef CONFIG_DEBUG_FS + char name[sizeof "rtc_get_device"+1]; +#endif + + init_waitqueue_head(&audio_dev_ctrl.wait); + + event.cb = NULL; + + atomic_set(&audio_dev_ctrl.opened, 0); + audio_dev_ctrl.num_dev = 0; + audio_dev_ctrl.voice_tx_dev = NULL; + audio_dev_ctrl.voice_rx_dev = NULL; + routing_info.voice_state = VOICE_STATE_INVALID; +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "rtc_get_device"); + dentry = debugfs_create_file(name, S_IFREG | S_IRUGO | S_IWUGO, + NULL, NULL, &rtc_acdb_debug_fops); + if (IS_ERR(dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif + + return misc_register(&audio_dev_ctrl_misc); +} + +static void __exit audio_dev_ctrl_exit(void) +{ +#ifdef CONFIG_DEBUG_FS + if (dentry) + debugfs_remove(dentry); +#endif + +} +module_init(audio_dev_ctrl_init); +module_exit(audio_dev_ctrl_exit); + +MODULE_DESCRIPTION("MSM 7K Audio Device Control driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_evrc.c b/arch/arm/mach-msm/qdsp5v2/audio_evrc.c new file mode 100644 index 00000000000..8e7db903d2f --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_evrc.c @@ -0,0 +1,1624 @@ +/* + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * + * This code also borrows from audio_aac.c, which is + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Hold 30 packets of 24 bytes each and 14 bytes of meta in */ +#define BUFSZ 734 +#define DMASZ (BUFSZ * 2) + +#define AUDDEC_DEC_EVRC 12 + +#define PCM_BUFSZ_MIN 1624 /* 100ms worth of data and + and 24 bytes of meta out */ +#define PCM_BUF_MAX_COUNT 5 +/* DSP only accepts 5 buffers at most + * but support 2 buffers currently + */ +#define EVRC_DECODED_FRSZ 320 /* EVRC 20ms 8KHz mono PCM size */ + +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDEVRC_METAFIELD_MASK 0xFFFF0000 +#define AUDEVRC_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDEVRC_EOS_FLG_MASK 0x01 +#define AUDEVRC_EOS_NONE 0x0 /* No EOS detected */ +#define AUDEVRC_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDEVRC_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audevrc_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audevrc_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + uint8_t opened:1; + uint8_t enabled:1; + uint8_t running:1; + uint8_t stopped:1; /* set when stopped, cleared on flush */ + uint8_t pcm_feedback:1; + uint8_t buf_refresh:1; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audevrc_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + uint32_t device_events; + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audevrc_send_data(struct audio *audio, unsigned needed); +static void audevrc_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audevrc_config_hostpcm(struct audio *audio); +static void audevrc_buffer_refresh(struct audio *audio); +static void audevrc_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); + +/* must be called with audio->lock held */ +static int audevrc_enable(struct audio *audio) +{ + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audevrc_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +static void evrc_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + default: + MM_ERR(":ERROR:wrong event\n"); + break; + } +} +/* must be called with audio->lock held */ +static int audevrc_disable(struct audio *audio) +{ + int rc = 0; + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ + +static void audevrc_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr + == payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audevrc_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audevrc_send_data(audio, 1); + break; + case AUDPLAY_MSG_BUFFER_UPDATE: + MM_DBG("\n"); /* Macro prints the file name and function */ + audevrc_update_pcm_buf_entry(audio, msg); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + default: + MM_ERR("unexpected message from decoder \n"); + } +} + +static void audevrc_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + audpp_route_stream(audio->dec_id, + audio->source); + if (audio->pcm_feedback) { + audevrc_config_hostpcm(audio); + audevrc_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status \n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK\n"); + audpp_cmd_cfg_adec_params(audio); + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audevrc_buffer_refresh(audio); + break; + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AUDPP_MSG_AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_evrc = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_EVRC; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_evrc cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = sizeof(cmd); + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = 8000; + cmd.stereo_cfg = AUDPP_CMD_PCM_INTF_MONO_V; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDEVRC_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audevrc_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audevrc_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = 1; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audevrc_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audevrc_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audevrc_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audevrc_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audevrc_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audevrc_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); +} + +static int audevrc_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audevrc_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audevrc_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audevrc_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audevrc_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + + +static long audevrc_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audevrc_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audevrc_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audevrc_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audevrc_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audevrc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audevrc_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audevrc_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audevrc_disable(audio); + audio->stopped = 1; + audevrc_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audevrc_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + audio->mfield = config.meta_field; + rc = 0; + MM_DBG("AUDIO_SET_CONFIG applicable only \ + for meta field configuration\n"); + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = 8000; + config.channel_count = 1; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buf %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("no mem for read buf\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; + index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr \ + 0x%08x kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audevrc_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audevrc_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + if (!audio->pcm_feedback) { + return 0; + /* PCM feedback is not enabled. Nothing to read */ + } + mutex_lock(&audio->read_lock); + MM_DBG("\n"); /* Macro prints the file name and function */ + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + MM_DBG("wait terminated \n"); + if (rc < 0) + break; + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver does + * not know frame size, read count must be greater or + * equal to size of PCM samples + */ + MM_DBG("read stop - partial frame\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", + (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment + */ + + } + } + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audevrc_buffer_refresh(audio); + } + mutex_unlock(&audio->read_lock); + if (buf > start) + rc = buf - start; + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audevrc_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audevrc_send_data(audio, 0); + +done: + return rc; +} + +static ssize_t audevrc_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + unsigned short mfield_size = 0; + int rc = 0, eos_condition = AUDEVRC_EOS_NONE; + + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, + mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDEVRC_EOS_FLG_OFFSET] & + AUDEVRC_EOS_FLG_MASK) { + MM_DBG("eos set\n"); + eos_condition = AUDEVRC_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDEVRC_EOS_FLG_OFFSET] &= + ~AUDEVRC_EOS_FLG_MASK; + } + /* Check EOS to see if */ + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + frame->used = xfer + mfield_size; + audio->out_head ^= 1; + count -= xfer; + buf += xfer; + audevrc_send_data(audio, 0); + } + if (eos_condition == AUDEVRC_EOS_SET) + rc = audevrc_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audevrc_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audevrc_disable(audio); + audevrc_flush(audio); + audevrc_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audevrc_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audevrc_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audevrc_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audevrc_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audevrc_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audevrc_suspend(struct early_suspend *h) +{ + struct audevrc_suspend_ctl *ctl = + container_of(h, struct audevrc_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audevrc_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audevrc_resume(struct early_suspend *h) +{ + struct audevrc_suspend_ctl *ctl = + container_of(h, struct audevrc_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audevrc_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audevrc_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audevrc_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audevrc_debug_fops = { + .read = audevrc_debug_read, + .open = audevrc_debug_open, +}; +#endif + +static int audevrc_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + struct audevrc_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_evrc_" + 5]; +#endif + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance\n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_EVRC; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->phys)) { + MM_ERR("could not allocate write buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_evrc, audio); + + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->avsync_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x3FFF; + + audevrc_flush(audio); + + file->private_data = audio; + audio->opened = 1; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + evrc_listner, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listner\n", __func__); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_evrc_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audevrc_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audevrc_resume; + audio->suspend_ctl.node.suspend = audevrc_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDEVRC_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audevrc_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_evrc_fops = { + .owner = THIS_MODULE, + .open = audevrc_open, + .release = audevrc_release, + .read = audevrc_read, + .write = audevrc_write, + .unlocked_ioctl = audevrc_ioctl, + .fsync = audevrc_fsync, +}; + +struct miscdevice audio_evrc_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_evrc", + .fops = &audio_evrc_fops, +}; + +static int __init audevrc_init(void) +{ + return misc_register(&audio_evrc_misc); + +} + +static void __exit audevrc_exit(void) +{ + misc_deregister(&audio_evrc_misc); +} + +module_init(audevrc_init); +module_exit(audevrc_exit); + +MODULE_DESCRIPTION("MSM EVRC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_evrc_in.c b/arch/arm/mach-msm/qdsp5v2/audio_evrc_in.c new file mode 100644 index 00000000000..763856eca09 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_evrc_in.c @@ -0,0 +1,1490 @@ +/* + * evrc audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#define META_OUT_SIZE 24 +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM 8 +#define EVRC_FRAME_SIZE 36 /* 36 bytes data */ +#define FRAME_SIZE (22 * 2) /* 36 bytes data */ + /* 36 bytes data + 24 meta field*/ +#define NT_FRAME_SIZE (EVRC_FRAME_SIZE + META_OUT_SIZE) +#define DMASZ (NT_FRAME_SIZE * FRAME_NUM) +#define OUT_FRAME_NUM 2 +#define OUT_BUFFER_SIZE (4 * 1024 + META_OUT_SIZE) +#define BUFFER_SIZE (OUT_BUFFER_SIZE * OUT_FRAME_NUM) + +#define AUDPREPROC_EVRC_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer*/ +#define AUDPREPROC_EVRC_EOS_FLG_MASK 0x01 +#define AUDPREPROC_EVRC_EOS_NONE 0x0 /* No EOS detected */ +#define AUDPREPROC_EVRC_EOS_SET 0x1 /* EOS set in meta field */ + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; + uint32_t used; + uint32_t mfield_sz; +}; + +struct audio_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + atomic_t in_samples; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + wait_queue_head_t wait_enable; + /*write section*/ + struct buffer out[OUT_FRAME_NUM]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + uint32_t out_count; + + struct mutex write_lock; + wait_queue_head_t write_wait; + int32_t out_phys; /* physical address of write buffer */ + char *out_data; + int mfield; /* meta field embedded in data */ + int wflush; /*write flush */ + int rflush; /*read flush*/ + int out_frame_cnt; + + struct msm_adsp_module *audrec; + + struct audrec_session_info session_info; /*audrec session info*/ + + /* configuration to use on next enable */ + uint32_t buffer_size; /* Frame size (36 bytes) */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t enc_type; + + struct msm_audio_evrc_enc_config cfg; + + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + uint32_t mode; + uint32_t eos_ack; + uint32_t flush_ack; + + const char *module_name; + unsigned queue_ids; + uint16_t enc_id; + + uint16_t source; /* Encoding source bit mask */ + uint32_t device_events; + uint32_t in_call; + uint32_t dev_cnt; + int voice_state; + spinlock_t dev_lock; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ +}; + +struct audio_frame { + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + unsigned char raw_bitstream[]; /* samples */ +} __attribute__((packed)); + +struct audio_frame_nt { + uint16_t metadata_len; + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + uint16_t reserved; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; + unsigned char raw_bitstream[]; /* samples */ +} __attribute__((packed)); + +struct evrc_encoded_meta_out { + uint16_t metadata_len; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; +}; + +/* Audrec Queue command sent macro's */ +#define audrec_send_bitstreamqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_ids & 0xFFFF0000) >> 16),\ + cmd, len) + +#define audrec_send_audrecqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_ids & 0x0000FFFF),\ + cmd, len) + +/* DSP command send functions */ +static int audevrc_in_enc_config(struct audio_in *audio, int enable); +static int audevrc_in_param_config(struct audio_in *audio); +static int audevrc_in_mem_config(struct audio_in *audio); +static int audevrc_in_record_config(struct audio_in *audio, int enable); +static int audevrc_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt); + +static void audevrc_in_get_dsp_frames(struct audio_in *audio); +static int audpcm_config(struct audio_in *audio); +static void audevrc_out_flush(struct audio_in *audio); +static int audpreproc_cmd_cfg_routing_mode(struct audio_in *audio); +static void audpreproc_pcm_send_data(struct audio_in *audio, unsigned needed); +static void audevrc_nt_in_get_dsp_frames(struct audio_in *audio); + +static void audevrc_in_flush(struct audio_in *audio); + +static void evrc_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio_in *audio = (struct audio_in *) private_data; + unsigned long flags; + + MM_DBG("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: { + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt++; + if (!audio->in_call) + audio->source |= (0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((audio->running == 1) && (audio->enabled == 1) && + (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) + audevrc_in_record_config(audio, 1); + } + break; + case AUDDEV_EVT_DEV_RLS: { + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt--; + if (!audio->in_call) + audio->source &= ~(0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((!audio->running) || (!audio->enabled)) + break; + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + /* Turn of as per source */ + if (audio->source) + audevrc_in_record_config(audio, 1); + else + /* Turn off all */ + audevrc_in_record_config(audio, 0); + } + } + break; + case AUDDEV_EVT_VOICE_STATE_CHG: { + MM_DBG("AUDDEV_EVT_VOICE_STATE_CHG, state = %d\n", + evt_payload->voice_state); + audio->voice_state = evt_payload->voice_state; + if (audio->in_call && audio->running && + (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) { + if (audio->voice_state == VOICE_STATE_INCALL) + audevrc_in_record_config(audio, 1); + else if (audio->voice_state == VOICE_STATE_OFFCALL) { + audevrc_in_record_config(audio, 0); + wake_up(&audio->wait); + } + } + + break; + } + default: + MM_ERR("wrong event %d\n", evt_id); + break; + } +} + +/* ------------------- dsp preproc event handler--------------------- */ +static void audpreproc_dsp_event(void *data, unsigned id, void *msg) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDPREPROC_ERROR_MSG: { + struct audpreproc_err_msg *err_msg = msg; + + MM_ERR("ERROR_MSG: stream id %d err idx %d\n", + err_msg->stream_id, err_msg->aud_preproc_err_idx); + /* Error case */ + wake_up(&audio->wait_enable); + break; + } + case AUDPREPROC_CMD_CFG_DONE_MSG: { + MM_DBG("CMD_CFG_DONE_MSG \n"); + break; + } + case AUDPREPROC_CMD_ENC_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_cfg_done_msg *enc_cfg_msg = msg; + + MM_DBG("CMD_ENC_CFG_DONE_MSG: stream id %d enc type \ + 0x%8x\n", enc_cfg_msg->stream_id, + enc_cfg_msg->rec_enc_type); + /* Encoder enable success */ + if (enc_cfg_msg->rec_enc_type & ENCODE_ENABLE) { + if(audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + MM_DBG("routing command\n"); + audpreproc_cmd_cfg_routing_mode(audio); + } else { + audevrc_in_param_config(audio); + } + } else { /* Encoder disable success */ + audio->running = 0; + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + audevrc_in_record_config(audio, 0); + else + wake_up(&audio->wait_enable); + } + break; + } + case AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: { + MM_DBG("CMD_ENC_PARAM_CFG_DONE_MSG\n"); + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + audevrc_in_mem_config(audio); + else + audpcm_config(audio); + break; + } + case AUDPREPROC_CMD_ROUTING_MODE_DONE_MSG: { + struct audpreproc_cmd_routing_mode_done\ + *routing_cfg_done_msg = msg; + if (routing_cfg_done_msg->configuration == 0) { + MM_INFO("routing configuration failed\n"); + audio->running = 0; + } else + audevrc_in_param_config(audio); + break; + } + case AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: { + MM_DBG("AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG \n"); + wake_up(&audio->wait_enable); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +/* ------------------- dsp audrec event handler--------------------- */ +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDREC_CMD_MEM_CFG_DONE_MSG: { + MM_DBG("CMD_MEM_CFG_DONE MSG DONE\n"); + audio->running = 1; + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if ((!audio->in_call && (audio->dev_cnt > 0)) || + (audio->in_call && + (audio->voice_state \ + == VOICE_STATE_INCALL))) + audevrc_in_record_config(audio, 1); + } else { + audpreproc_pcm_send_data(audio, 1); + wake_up(&audio->wait_enable); + } + break; + } + case AUDREC_FATAL_ERR_MSG: { + struct audrec_fatal_err_msg fatal_err_msg; + + getevent(&fatal_err_msg, AUDREC_FATAL_ERR_MSG_LEN); + MM_ERR("FATAL_ERR_MSG: err id %d\n", + fatal_err_msg.audrec_err_id); + /* Error stop the encoder */ + audio->stopped = 1; + wake_up(&audio->wait); + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + wake_up(&audio->write_wait); + break; + } + case AUDREC_UP_PACKET_READY_MSG: { + struct audrec_up_pkt_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packet_write_cnt_lsw, \ + pkt_ready_msg.audrec_packet_write_cnt_msw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_lsw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_msw); + + audevrc_in_get_dsp_frames(audio); + break; + } + case AUDREC_CMD_PCM_BUFFER_PTR_UPDATE_ARM_TO_ENC_MSG: { + MM_DBG("ptr_update recieved from DSP\n"); + audpreproc_pcm_send_data(audio, 1); + break; + } + case AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG: { + MM_ERR("AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG"); + audevrc_in_mem_config(audio); + break; + } + case AUDREC_UP_NT_PACKET_READY_MSG: { + struct audrec_up_nt_packet_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_NT_PACKET_READY_MSG_LEN); + MM_DBG("UP_NT_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packetwrite_cnt_lsw, \ + pkt_ready_msg.audrec_packetwrite_cnt_msw, \ + pkt_ready_msg.audrec_upprev_readcount_lsw, \ + pkt_ready_msg.audrec_upprev_readcount_msw); + + audevrc_nt_in_get_dsp_frames(audio); + break; + } + case AUDREC_CMD_EOS_ACK_MSG: { + MM_DBG("eos ack recieved\n"); + break; + } + case AUDREC_CMD_FLUSH_DONE_MSG: { + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 1; + wake_up(&audio->write_wait); + MM_DBG("flush ack recieved\n"); + break; + } + case ADSP_MESSAGE_ID: { + MM_DBG("Received ADSP event:module audrectask\n"); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +static void audevrc_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(*frame)); + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = frame->frame_length; + + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) { + MM_ERR("Error! not able to keep up the read\n"); + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + MM_ERR("in_count = %d\n", audio->in_count); + } else + audio->in_count++; + + audevrc_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +static void audevrc_nt_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame_nt *nt_frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + nt_frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(struct audio_frame_nt)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = nt_frame->frame_length; + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + MM_DBG("Error! not able to keep up the read\n"); + else + audio->in_count++; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + wake_up(&audio->wait); +} + + +struct msm_adsp_ops audrec_evrc_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int audpreproc_pcm_buffer_ptr_refresh(struct audio_in *audio, + unsigned idx, unsigned len) +{ + struct audrec_cmd_pcm_buffer_ptr_refresh_arm_enc cmd; + + if (len == META_OUT_SIZE) + len = len / 2; + else + len = (len + META_OUT_SIZE) / 2; + MM_DBG("len = %d\n", len); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_BUFFER_PTR_REFRESH_ARM_TO_ENC; + cmd.num_buffers = 1; + if (cmd.num_buffers == 1) { + cmd.buf_address_length[0] = (audio->out[idx].addr & + 0xffff0000) >> 16; + cmd.buf_address_length[1] = (audio->out[idx].addr & + 0x0000ffff); + cmd.buf_address_length[2] = (len & 0xffff0000) >> 16; + cmd.buf_address_length[3] = (len & 0x0000ffff); + } + audio->out_frame_cnt++; + return audrec_send_audrecqueue(audio, (void *)&cmd, + (unsigned int)sizeof(cmd)); +} + + +static int audpcm_config(struct audio_in *audio) +{ + struct audrec_cmd_pcm_cfg_arm_to_enc cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_CFG_ARM_TO_ENC; + cmd.config_update_flag = AUDREC_PCM_CONFIG_UPDATE_FLAG_ENABLE; + cmd.enable_flag = AUDREC_ENABLE_FLAG_VALUE; + cmd.sampling_freq = audio->samp_rate; + if (!audio->channel_mode) + cmd.channels = 1; + else + cmd.channels = 2; + cmd.frequency_of_intimation = 1; + cmd.max_number_of_buffers = OUT_FRAME_NUM; + return audrec_send_audrecqueue(audio, (void *)&cmd, + (unsigned int)sizeof(cmd)); +} + + +static int audpreproc_cmd_cfg_routing_mode(struct audio_in *audio) +{ + struct audpreproc_audrec_cmd_routing_mode cmd; + + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ROUTING_MODE; + cmd.stream_id = audio->enc_id; + if (audio->mode == MSM_ADSP_ENC_MODE_NON_TUNNEL) + cmd.routing_mode = 1; + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + + + +static int audevrc_in_enc_config(struct audio_in *audio, int enable) +{ + struct audpreproc_audrec_cmd_enc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + cmd.stream_id = audio->enc_id; + + if (enable) + cmd.audrec_enc_type = audio->enc_type | ENCODE_ENABLE; + else + cmd.audrec_enc_type &= ~(ENCODE_ENABLE); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audevrc_in_param_config(struct audio_in *audio) +{ + struct audpreproc_audrec_cmd_parm_cfg_evrc cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPREPROC_AUDREC_CMD_PARAM_CFG; + cmd.common.stream_id = audio->enc_id; + + cmd.enc_min_rate = audio->cfg.min_bit_rate; + cmd.enc_max_rate = audio->cfg.max_bit_rate; + cmd.rate_modulation_cmd = 0; /* Default set to 0 */ + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +/* To Do: msm_snddev_route_enc(audio->enc_id); */ +static int audevrc_in_record_config(struct audio_in *audio, int enable) +{ + struct audpreproc_afe_cmd_audio_record_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG; + cmd.stream_id = audio->enc_id; + if (enable) + cmd.destination_activity = AUDIO_RECORDING_TURN_ON; + else + cmd.destination_activity = AUDIO_RECORDING_TURN_OFF; + + cmd.source_mix_mask = audio->source; + if (audio->enc_id == 2) { + if ((cmd.source_mix_mask & + INTERNAL_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & AUX_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_UL_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_DL_SOURCE_MIX_MASK)) { + cmd.pipe_id = SOURCE_PIPE_1; + } + if (cmd.source_mix_mask & + AUDPP_A2DP_PIPE_SOURCE_MIX_MASK) + cmd.pipe_id |= SOURCE_PIPE_0; + } + MM_DBG("stream_id %x destination_activity %x \ + source_mix_mask %x pipe_id %x",\ + cmd.stream_id, cmd.destination_activity, + cmd.source_mix_mask, cmd.pipe_id); + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audevrc_in_mem_config(struct audio_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + int n; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_MEM_CFG_CMD; + cmd.audrec_up_pkt_intm_count = 1; + cmd.audrec_ext_pkt_start_addr_msw = audio->phys >> 16; + cmd.audrec_ext_pkt_start_addr_lsw = audio->phys; + cmd.audrec_ext_pkt_buf_number = FRAME_NUM; + MM_DBG("audio->phys = %x\n", audio->phys); + /* prepare buffer pointers: + * T:36 bytes evrc packet + 4 halfword header + * NT:36 bytes evrc packet + 12 halfword header + */ + for (n = 0; n < FRAME_NUM; n++) { + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + audio->in[n].data = data + 4; + data += (FRAME_SIZE/2); + MM_DBG("0x%8x\n", (int)(audio->in[n].data - 8)); + } else { + audio->in[n].data = data + 12; + data += ((EVRC_FRAME_SIZE) / 2) + 12; + MM_DBG("0x%8x\n", (int)(audio->in[n].data - 24)); + } + } + return audrec_send_audrecqueue(audio, &cmd, sizeof(cmd)); +} + +static int audevrc_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt) +{ + struct up_audrec_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = UP_AUDREC_PACKET_EXT_PTR; + cmd.audrec_up_curr_read_count_msw = read_cnt >> 16; + cmd.audrec_up_curr_read_count_lsw = read_cnt; + + return audrec_send_bitstreamqueue(audio, &cmd, sizeof(cmd)); +} +static int audevrc_flush_command(struct audio_in *audio) +{ + struct audrec_cmd_flush cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_FLUSH; + return audrec_send_audrecqueue(audio, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audevrc_in_enable(struct audio_in *audio) +{ + if (audio->enabled) + return 0; + + if (audpreproc_enable(audio->enc_id, &audpreproc_dsp_event, audio)) { + MM_ERR("msm_adsp_enable(audpreproc) failed\n"); + return -ENODEV; + } + + if (msm_adsp_enable(audio->audrec)) { + MM_ERR("msm_adsp_enable(audrec) failed\n"); + audpreproc_disable(audio->enc_id, audio); + return -ENODEV; + } + audio->enabled = 1; + audevrc_in_enc_config(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audevrc_in_disable(struct audio_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + audevrc_in_enc_config(audio, 0); + wake_up(&audio->wait); + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + msm_adsp_disable(audio->audrec); + audpreproc_disable(audio->enc_id, audio); + } + return 0; +} + +static void audevrc_ioport_reset(struct audio_in *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audevrc_in_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audevrc_out_flush(audio); + mutex_unlock(&audio->read_lock); +} + +static void audevrc_in_flush(struct audio_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + audio->eos_ack = 0; + for (i = 0; i < FRAME_NUM; i++) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } + MM_DBG("in_bytes %d\n", atomic_read(&audio->in_bytes)); + MM_DBG("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); +} + +static void audevrc_out_flush(struct audio_in *audio) +{ + int i; + + audio->out_head = 0; + audio->out_tail = 0; + audio->out_count = 0; + for (i = 0; i < OUT_FRAME_NUM; i++) { + audio->out[i].size = 0; + audio->out[i].read = 0; + audio->out[i].used = 0; + } +} + +/* ------------------- device --------------------- */ +static long audevrc_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_in *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + uint32_t freq; + freq = 48000; + MM_DBG("AUDIO_START\n"); + if (audio->in_call && (audio->voice_state != + VOICE_STATE_INCALL)) { + rc = -EPERM; + break; + } + rc = msm_snddev_request_freq(&freq, audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("sample rate configured %d\n", freq); + if (rc < 0) { + MM_DBG(" Sample rate can not be set, return code %d\n", + rc); + msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + break; + } + /*update aurec session info in audpreproc layer*/ + audio->session_info.session_id = audio->enc_id; + audio->session_info.sampling_freq = audio->samp_rate; + audpreproc_update_audrec_info(&audio->session_info); + rc = audevrc_in_enable(audio); + if (!rc) { + rc = + wait_event_interruptible_timeout(audio->wait_enable, + audio->running != 0, 1*HZ); + MM_DBG("state %d rc = %d\n", audio->running, rc); + + if (audio->running == 0) + rc = -ENODEV; + else + rc = 0; + } + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + rc = audevrc_in_disable(audio); + rc = msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + audio->stopped = 1; + break; + } + case AUDIO_FLUSH: { + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audevrc_ioport_reset(audio); + if (audio->running) { + audevrc_flush_command(audio); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + /* Allow only single frame */ + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if (cfg.buffer_size != (FRAME_SIZE - 8)) { + rc = -EINVAL; + break; + } + } else { + if (cfg.buffer_size != (EVRC_FRAME_SIZE + 14)) { + rc = -EINVAL; + break; + } + } + audio->buffer_size = cfg.buffer_size; + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_EVRC_ENC_CONFIG: { + if (copy_to_user((void *) arg, &audio->cfg, sizeof(audio->cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_EVRC_ENC_CONFIG: { + struct msm_audio_evrc_enc_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + MM_DBG("0X%8x, 0x%8x, 0x%8x\n", cfg.min_bit_rate, + cfg.max_bit_rate, cfg.cdma_rate); + if (cfg.min_bit_rate > CDMA_RATE_FULL || \ + cfg.min_bit_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid min bitrate\n"); + rc = -EFAULT; + break; + } + if (cfg.max_bit_rate > CDMA_RATE_FULL || \ + cfg.max_bit_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid max bitrate\n"); + rc = -EFAULT; + break; + } + /* Recording Does not support Erase and Blank */ + if (cfg.cdma_rate > CDMA_RATE_FULL || + cfg.cdma_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid qcelp cdma rate\n"); + rc = -EFAULT; + break; + } + memcpy(&audio->cfg, &cfg, sizeof(cfg)); + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = OUT_BUFFER_SIZE; + cfg.buffer_count = OUT_FRAME_NUM; + cfg.sample_rate = audio->samp_rate; + cfg.channel_count = audio->channel_mode; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_INCALL: { + struct msm_voicerec_mode cfg; + unsigned long flags; + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.rec_mode != VOC_REC_BOTH && + cfg.rec_mode != VOC_REC_UPLINK && + cfg.rec_mode != VOC_REC_DOWNLINK) { + MM_ERR("invalid rec_mode\n"); + rc = -EINVAL; + break; + } else { + spin_lock_irqsave(&audio->dev_lock, flags); + if (cfg.rec_mode == VOC_REC_UPLINK) + audio->source = \ + VOICE_UL_SOURCE_MIX_MASK; + else if (cfg.rec_mode == VOC_REC_DOWNLINK) + audio->source = \ + VOICE_DL_SOURCE_MIX_MASK; + else + audio->source = \ + VOICE_DL_SOURCE_MIX_MASK | + VOICE_UL_SOURCE_MIX_MASK ; + audio->in_call = 1; + spin_unlock_irqrestore(&audio->dev_lock, flags); + } + } + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &audio->enc_id, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audevrc_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + struct evrc_encoded_meta_out meta_field; + struct audio_frame_nt *nt_frame; + MM_DBG("count = %d\n", count); + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped || + audio->rflush || + ((audio->mode == MSM_AUD_ENC_MODE_TUNNEL) && + audio->in_call && audio->running && + (audio->voice_state == VOICE_STATE_OFFCALL))); + if (rc < 0) + break; + + if (audio->rflush) { + rc = -EBUSY; + break; + } + if (audio->stopped && !audio->in_count) { + MM_DBG("Driver in stop state, No more buffer to read"); + rc = 0;/* End of File */ + break; + } else if ((audio->mode == MSM_AUD_ENC_MODE_TUNNEL) && + audio->in_call && audio->running && + (audio->voice_state \ + == VOICE_STATE_OFFCALL)) { + MM_DBG("Not Permitted Voice Terminated\n"); + rc = -EPERM; /* Voice Call stopped */ + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + nt_frame = (struct audio_frame_nt *)(data - + sizeof(struct audio_frame_nt)); + memcpy((char *)&meta_field.time_stamp_dword_lsw, + (char *)&nt_frame->time_stamp_dword_lsw, + (sizeof(struct evrc_encoded_meta_out) - \ + sizeof(uint16_t))); + meta_field.metadata_len = + sizeof(struct evrc_encoded_meta_out); + if (copy_to_user((char *)start, (char *)&meta_field, + sizeof(struct evrc_encoded_meta_out))) { + rc = -EFAULT; + break; + } + if (nt_frame->nflag_lsw & 0x0001) { + MM_ERR("recieved EOS in read call\n"); + audio->eos_ack = 1; + } + buf += sizeof(struct evrc_encoded_meta_out); + count -= sizeof(struct evrc_encoded_meta_out); + } + if (count >= size) { + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is + * invalid and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + if ((audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL)) { + if (!audio->eos_ack) { + MM_DBG("sending read ptr command \ + %d %d\n", + audio->dsp_cnt, + audio->in_tail); + audevrc_dsp_read_buffer(audio, + audio->dsp_cnt++); + } + } + } else { + MM_ERR("short read\n"); + break; + } + break; + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static void audpreproc_pcm_send_data(struct audio_in *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + MM_DBG("\n"); + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + audpreproc_pcm_buffer_ptr_refresh(audio, + audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + + +static int audevrc_in_fsync(struct file *file, int datasync) + +{ + struct audio_in *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (!audio->running || (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + audio->wflush); + MM_DBG("waked on by some event audio->wflush = %d\n", audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; + +} + + int audpreproc_evrc_process_eos(struct audio_in *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + int rc = 0; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + MM_DBG("copying meta_out frame->used = %d\n", frame->used); + audpreproc_pcm_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audevrc_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + char *cpy_ptr; + int rc = 0, eos_condition = AUDPREPROC_EVRC_EOS_NONE; + unsigned short mfield_size = 0; + int write_count = 0; + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + if (audio->mode != MSM_AUD_ENC_MODE_NONTUNNEL) + return -EINVAL; + + mutex_lock(&audio->write_lock); + frame = audio->out + audio->out_head; + /* if supplied count is more than driver buffer size + * then only copy driver buffer size + */ + if (count > frame->size) + count = frame->size; + + write_count = count; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto error; + + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto error; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + goto error; + } else if (mfield_size > count) { + rc = -EINVAL; + goto error; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + goto error; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDPREPROC_EVRC_EOS_FLG_OFFSET] & + AUDPREPROC_EVRC_EOS_FLG_MASK) { + eos_condition = AUDPREPROC_EVRC_EOS_SET; + MM_DBG("EOS SET\n"); + if (mfield_size == count) { + buf += mfield_size; + eos_condition = 0; + goto exit; + } else + cpy_ptr[AUDPREPROC_EVRC_EOS_FLG_OFFSET] &= + ~AUDPREPROC_EVRC_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + MM_DBG("copying the stream count = %d\n", count); + if (copy_from_user(cpy_ptr, buf, count)) { + rc = -EFAULT; + goto error; + } +exit: + frame->used = count; + audio->out_head ^= 1; + if (!audio->flush_ack) + audpreproc_pcm_send_data(audio, 0); + else { + audpreproc_pcm_send_data(audio, 1); + audio->flush_ack = 0; + } + if (eos_condition == AUDPREPROC_EVRC_EOS_SET) + rc = audpreproc_evrc_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + return write_count; +error: + mutex_unlock(&audio->write_lock); + return rc; +} + +static int audevrc_in_release(struct inode *inode, struct file *file) +{ + struct audio_in *audio = file->private_data; + + mutex_lock(&audio->lock); + audio->in_call = 0; + /* with draw frequency for session + incase not stopped the driver */ + msm_snddev_withdraw_freq(audio->enc_id, SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, audio->enc_id); + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + audevrc_in_disable(audio); + audevrc_in_flush(audio); + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + audio->audrec = NULL; + audio->opened = 0; + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + audio->data = NULL; + } + if (audio->out_data) { + iounmap(audio->out_data); + pmem_kfree(audio->out_phys); + audio->out_data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +struct audio_in the_audio_evrc_in; +static int audevrc_in_open(struct inode *inode, struct file *file) +{ + struct audio_in *audio = &the_audio_evrc_in; + int rc; + int encid; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->phys); + goto done; + } + } else { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + goto done; + } + MM_DBG("Memory addr = 0x%8x phy addr = 0x%8x\n",\ + (int) audio->data, (int) audio->phys); + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_NONTUNNEL; + MM_DBG("Opened for non tunnel mode encoding\n"); + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_TUNNEL; + MM_DBG("Opened for tunnel mode encoding\n"); + } else { + MM_ERR("Invalid mode\n"); + rc = -EACCES; + goto done; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + audio->buffer_size = (EVRC_FRAME_SIZE + 14); + else + audio->buffer_size = (FRAME_SIZE - 8); + audio->enc_type = ENC_TYPE_EVRC | audio->mode; + audio->samp_rate = 8000; + audio->channel_mode = AUDREC_CMD_MODE_MONO; + audio->cfg.cdma_rate = CDMA_RATE_FULL; + audio->cfg.min_bit_rate = CDMA_RATE_FULL; + audio->cfg.max_bit_rate = CDMA_RATE_FULL; + + encid = audpreproc_aenc_alloc(audio->enc_type, &audio->module_name, + &audio->queue_ids); + if (encid < 0) { + MM_ERR("No free encoder available\n"); + rc = -ENODEV; + goto done; + } + audio->enc_id = encid; + + rc = msm_adsp_get(audio->module_name, &audio->audrec, + &audrec_evrc_adsp_ops, audio); + + if (rc) { + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + audio->stopped = 0; + audio->source = 0; + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 0; + + audevrc_in_flush(audio); + audevrc_out_flush(audio); + + audio->out_phys = pmem_kalloc(BUFFER_SIZE, + PMEM_MEMTYPE_EBI1 | PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->out_phys)) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->out_data = ioremap(audio->out_phys, BUFFER_SIZE); + if (!audio->out_data) { + MM_ERR("could map write buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->out_phys); + goto evt_error; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->out_phys, (int)audio->out_data); + } + + /* Initialize buffer */ + audio->out[0].data = audio->out_data + 0; + audio->out[0].addr = audio->out_phys + 0; + audio->out[0].size = OUT_BUFFER_SIZE; + + audio->out[1].data = audio->out_data + OUT_BUFFER_SIZE; + audio->out[1].addr = audio->out_phys + OUT_BUFFER_SIZE; + audio->out[1].size = OUT_BUFFER_SIZE; + + MM_DBG("audio->out[0].data = %d audio->out[1].data = %d", + (unsigned int)audio->out[0].data, + (unsigned int)audio->out[1].data); + audio->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_VOICE_STATE_CHG; + + audio->voice_state = msm_get_voice_state(); + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_ENC, audio->enc_id, + evrc_in_listener, (void *) audio); + if (rc) { + MM_ERR("failed to register device event listener\n"); + iounmap(audio->out_data); + pmem_kfree(audio->out_phys); + goto evt_error; + } + audio->mfield = META_OUT_SIZE; + file->private_data = audio; + audio->opened = 1; + audio->out_frame_cnt++; +done: + mutex_unlock(&audio->lock); + return rc; +evt_error: + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_in_fops = { + .owner = THIS_MODULE, + .open = audevrc_in_open, + .release = audevrc_in_release, + .read = audevrc_in_read, + .write = audevrc_in_write, + .fsync = audevrc_in_fsync, + .unlocked_ioctl = audevrc_in_ioctl, +}; + +struct miscdevice audio_evrc_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_evrc_in", + .fops = &audio_in_fops, +}; + +static int __init audevrc_in_init(void) +{ + mutex_init(&the_audio_evrc_in.lock); + mutex_init(&the_audio_evrc_in.read_lock); + spin_lock_init(&the_audio_evrc_in.dsp_lock); + spin_lock_init(&the_audio_evrc_in.dev_lock); + init_waitqueue_head(&the_audio_evrc_in.wait); + init_waitqueue_head(&the_audio_evrc_in.wait_enable); + mutex_init(&the_audio_evrc_in.write_lock); + init_waitqueue_head(&the_audio_evrc_in.write_wait); + return misc_register(&audio_evrc_in_misc); +} + +device_initcall(audevrc_in_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_fm.c b/arch/arm/mach-msm/qdsp5v2/audio_fm.c new file mode 100644 index 00000000000..af65c802587 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_fm.c @@ -0,0 +1,358 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5v2/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SESSION_ID_FM 6 +#define FM_ENABLE 0xFFFF +#define FM_DISABLE 0x0 +#define FM_COPP 0x2 +/* Macro specifies maximum FM routing + possible */ +#define FM_MAX_RX_ROUTE 0x2 + +struct fm_rx_calib_gain { + uint16_t device_id; + struct auddev_evt_devinfo dev_details; + struct acdb_calib_gain_rx calib_rx; +}; + +struct audio { + struct mutex lock; + + int opened; + int enabled; + int running; + + uint16_t dec_id; + uint16_t source; + uint16_t fm_source; + uint16_t fm_mask; + uint32_t device_events; + uint16_t volume; + struct fm_rx_calib_gain fm_calibration_rx[FM_MAX_RX_ROUTE]; +}; + +static struct audio fm_audio; + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + int rc = 0; + if (audio->enabled) + return 0; + + MM_DBG("fm mask= %08x fm_source = %08x\n", + audio->fm_mask, audio->fm_source); + if (audio->fm_mask && audio->fm_source) { + rc = afe_config_fm_codec(FM_ENABLE, audio->fm_mask); + if (!rc) + audio->running = 1; + /* Routed to icodec rx path */ + if ((audio->fm_mask & AFE_HW_PATH_CODEC_RX) == + AFE_HW_PATH_CODEC_RX) { + afe_config_fm_calibration_gain( + audio->fm_calibration_rx[0].device_id, + audio->fm_calibration_rx[0].calib_rx.audppcalgain); + } + /* Routed to aux codec rx path */ + if ((audio->fm_mask & AFE_HW_PATH_AUXPCM_RX) == + AFE_HW_PATH_AUXPCM_RX){ + afe_config_fm_calibration_gain( + audio->fm_calibration_rx[1].device_id, + audio->fm_calibration_rx[1].calib_rx.audppcalgain); + } + } + + audio->enabled = 1; + return rc; +} + +static void fm_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + struct auddev_evt_devinfo *devinfo = + (struct auddev_evt_devinfo *)evt_payload; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + if (evt_payload->routing_id == FM_COPP) + audio->fm_source = 1; + else + audio->source = (0x1 << evt_payload->routing_id); + + if (audio->source & 0x1) + audio->fm_mask = 0x1; + else if (audio->source & 0x2) + audio->fm_mask = 0x3; + else + audio->fm_mask = 0x0; + + if (!audio->enabled + || !audio->fm_mask + || !audio->fm_source) + break; + else { + afe_config_fm_codec(FM_ENABLE, audio->fm_mask); + audio->running = 1; + } + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + if (evt_payload->routing_id == FM_COPP) + audio->fm_source = 0; + else + audio->source &= ~(0x1 << evt_payload->routing_id); + + if (audio->source & 0x1) + audio->fm_mask = 0x1; + else if (audio->source & 0x2) + audio->fm_mask = 0x3; + else + audio->fm_mask = 0x0; + + if (audio->running + && (!audio->fm_mask || !audio->fm_source)) { + afe_config_fm_codec(FM_DISABLE, audio->fm_mask); + audio->running = 0; + } + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol \n"); + audio->volume = evt_payload->session_vol; + afe_config_fm_volume(audio->volume); + break; + case AUDDEV_EVT_DEVICE_INFO:{ + struct acdb_get_block get_block; + int rc = 0; + MM_DBG(":AUDDEV_EVT_DEVICE_INFO\n"); + MM_DBG("sample_rate = %d\n", devinfo->sample_rate); + MM_DBG("acdb_id = %d\n", devinfo->acdb_id); + /* Applucable only for icodec rx and aux codec rx path + and fm stream routed to it */ + if (((devinfo->dev_id == 0x00) || (devinfo->dev_id == 0x01)) && + (devinfo->sessions && (1 << audio->dec_id))) { + /* Query ACDB driver for calib gain, only if difference + in device */ + if ((audio->fm_calibration_rx[devinfo->dev_id]. + dev_details.acdb_id != devinfo->acdb_id) || + (audio->fm_calibration_rx[devinfo->dev_id]. + dev_details.sample_rate != + devinfo->sample_rate)) { + audio->fm_calibration_rx[devinfo->dev_id]. + dev_details.dev_id = devinfo->dev_id; + audio->fm_calibration_rx[devinfo->dev_id]. + dev_details.sample_rate = + devinfo->sample_rate; + audio->fm_calibration_rx[devinfo->dev_id]. + dev_details.dev_type = + devinfo->dev_type; + audio->fm_calibration_rx[devinfo->dev_id]. + dev_details.sessions = + devinfo->sessions; + /* Query ACDB driver for calibration gain */ + get_block.acdb_id = devinfo->acdb_id; + get_block.sample_rate_id = devinfo->sample_rate; + get_block.interface_id = + IID_AUDIO_CALIBRATION_GAIN_RX; + get_block.algorithm_block_id = + ABID_AUDIO_CALIBRATION_GAIN_RX; + get_block.total_bytes = + sizeof(struct acdb_calib_gain_rx); + get_block.buf_ptr = (u32 *) + &audio->fm_calibration_rx[devinfo->dev_id]. + calib_rx; + + rc = acdb_get_calibration_data(&get_block); + if (rc < 0) { + MM_ERR("Unable to get calibration"\ + "gain\n"); + /* Set to unity incase of error */ + audio->\ + fm_calibration_rx[devinfo->dev_id]. + calib_rx.audppcalgain = 0x2000; + } else + MM_DBG("calibration gain = 0x%8x\n", + *(get_block.buf_ptr)); + } + if (audio->running) { + afe_config_fm_calibration_gain( + audio->fm_calibration_rx[devinfo->dev_id]. + device_id, + audio->fm_calibration_rx[devinfo->dev_id]. + calib_rx.audppcalgain); + } + } + break; + } + default: + MM_DBG(":ERROR:wrong event\n"); + break; + } +} +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + return afe_config_fm_codec(FM_DISABLE, audio->source); +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + + MM_DBG("cmd = %d\n", cmd); + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->running = 0; + audio->enabled = 0; + break; + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_DBG("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audio_disable(audio); + audio->running = 0; + audio->enabled = 0; + audio->opened = 0; + mutex_unlock(&audio->lock); + return 0; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = &fm_audio; + int rc = 0; + + + if (audio->opened) + return -EPERM; + + /* Allocate the decoder */ + audio->dec_id = SESSION_ID_FM; + + audio->running = 0; + audio->fm_source = 0; + audio->fm_mask = 0; + + /* Initialize the calibration gain structure */ + audio->fm_calibration_rx[0].device_id = AFE_HW_PATH_CODEC_RX; + audio->fm_calibration_rx[1].device_id = AFE_HW_PATH_AUXPCM_RX; + audio->fm_calibration_rx[0].calib_rx.audppcalgain = 0x2000; + audio->fm_calibration_rx[1].calib_rx.audppcalgain = 0x2000; + audio->fm_calibration_rx[0].dev_details.acdb_id = PSEUDO_ACDB_ID; + audio->fm_calibration_rx[1].dev_details.acdb_id = PSEUDO_ACDB_ID; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG| + AUDDEV_EVT_DEVICE_INFO; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + fm_listner, + (void *)audio); + + if (rc) { + MM_ERR("%s: failed to register listnet\n", __func__); + goto event_err; + } + + audio->opened = 1; + file->private_data = audio; + +event_err: + return rc; +} + +static const struct file_operations audio_fm_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .unlocked_ioctl = audio_ioctl, +}; + +struct miscdevice audio_fm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_fm", + .fops = &audio_fm_fops, +}; + +static int __init audio_init(void) +{ + struct audio *audio = &fm_audio; + + mutex_init(&audio->lock); + return misc_register(&audio_fm_misc); +} + +device_initcall(audio_init); + +MODULE_DESCRIPTION("MSM FM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_interct.c b/arch/arm/mach-msm/qdsp5v2/audio_interct.c new file mode 100644 index 00000000000..785ed8ee5d6 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_interct.c @@ -0,0 +1,124 @@ +/* Copyright (c) 2009, 2011 Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include + +#define AUDIO_INTERCT_ADSPLPA_WBRX_SEL_BMSK 0x4 +#define AUDIO_INTERCT_ADSPLPA_WBRX_SEL_SHFT 0x2 +#define AUDIO_INTERCT_ADSPAV_RPCMI2SRX_SEL_BMSK 0x10 +#define AUDIO_INTERCT_ADSPAV_RPCMI2SRX_SEL_SHFT 0x4 +#define AUDIO_INTERCT_ADSPAV_TPCMI2STX_SEL_BMSK 0x40 +#define AUDIO_INTERCT_ADSPAV_TPCMI2STX_SEL_SHFT 0x6 +#define AUDIO_INTERCT_ADSPAV_AUX_REGSEL_BMSK 0x100 +#define AUDIO_INTERCT_ADSPAV_AUX_REGSEL_SHFT 0x8 + +/* Should look to protect this register */ +void __iomem *aictl_reg; + +void audio_interct_codec(u32 source) +{ + u32 reg_val; + + reg_val = readl(aictl_reg); + reg_val = (reg_val & ~AUDIO_INTERCT_ADSPLPA_WBRX_SEL_BMSK) | + (source << AUDIO_INTERCT_ADSPLPA_WBRX_SEL_SHFT); + writel(reg_val, aictl_reg); + mb(); +} +EXPORT_SYMBOL(audio_interct_codec); + +void audio_interct_aux_regsel(u32 source) +{ + u32 reg_val; + + reg_val = readl(aictl_reg); + reg_val = (reg_val & ~AUDIO_INTERCT_ADSPAV_AUX_REGSEL_BMSK) | + (source << AUDIO_INTERCT_ADSPAV_AUX_REGSEL_SHFT); + writel(reg_val, aictl_reg); + mb(); +} +EXPORT_SYMBOL(audio_interct_aux_regsel); + +void audio_interct_tpcm_source(u32 source) +{ + u32 reg_val; + + reg_val = readl(aictl_reg); + reg_val = (reg_val & ~AUDIO_INTERCT_ADSPAV_TPCMI2STX_SEL_BMSK) | + (source << AUDIO_INTERCT_ADSPAV_TPCMI2STX_SEL_SHFT); + writel(reg_val, aictl_reg); + mb(); +} +EXPORT_SYMBOL(audio_interct_tpcm_source); + +void audio_interct_rpcm_source(u32 source) +{ + u32 reg_val; + + reg_val = readl(aictl_reg); + reg_val = (reg_val & ~AUDIO_INTERCT_ADSPAV_RPCMI2SRX_SEL_BMSK) | + (source << AUDIO_INTERCT_ADSPAV_RPCMI2SRX_SEL_SHFT); + writel(reg_val, aictl_reg); + mb(); +} +EXPORT_SYMBOL(audio_interct_rpcm_source); + +static int audio_interct_probe(struct platform_device *pdev) +{ + int rc = 0; + struct resource *aictl_mem; + + aictl_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!aictl_mem) { + rc = -ENODEV; + goto error; + } + aictl_reg = ioremap(aictl_mem->start, + (aictl_mem->end - aictl_mem->start) + 1); +error: + return rc; +} + + +static int audio_interct_remove(struct platform_device *pdev) +{ + iounmap(aictl_reg); + return 0; +} + +static struct platform_driver audio_interct_driver = { + .probe = audio_interct_probe, + .remove = audio_interct_remove, + .driver = { + .name = "audio_interct", + .owner = THIS_MODULE, + }, +}; + +static int __init audio_interct_init(void) +{ + return platform_driver_register(&audio_interct_driver); +} + +static void __exit audio_interct_exit(void) +{ + platform_driver_unregister(&audio_interct_driver); +} + +module_init(audio_interct_init); +module_exit(audio_interct_exit); + +MODULE_DESCRIPTION("MSM Audio Interconnect driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_lpa.c b/arch/arm/mach-msm/qdsp5v2/audio_lpa.c new file mode 100644 index 00000000000..da4ae7342da --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_lpa.c @@ -0,0 +1,1700 @@ +/* low power audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADRV_STATUS_AIO_INTF 0x00000001 +#define ADRV_STATUS_OBUF_GIVEN 0x00000002 +#define ADRV_STATUS_IBUF_GIVEN 0x00000004 +#define ADRV_STATUS_FSYNC 0x00000008 +#define ADRV_STATUS_PAUSE 0x00000010 + +#define DEVICE_SWITCH_STATE_NONE 0 +#define DEVICE_SWITCH_STATE_PENDING 1 +#define DEVICE_SWITCH_STATE_READY 2 +#define DEVICE_SWITCH_STATE_COMPLETE 3 + +#define AUDDEC_DEC_PCM 0 +#define AUDDEC_DEC_MP3 2 + +#define PCM_BUFSZ_MIN 4800 /* Hold one stereo MP3 frame */ + +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDMP3_METAFIELD_MASK 0xFFFF0000 +#define AUDMP3_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDMP3_EOS_FLG_MASK 0x01 +#define AUDMP3_EOS_NONE 0x0 /* No EOS detected */ +#define AUDMP3_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDLPA_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +#define MASK_32BITS 0xFFFFFFFF + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +/* payload[7]; -1 indicates error, 0 indicates no error */ +#define CHECK_ERROR(v) (!v[7]) + +/* calculates avsync_info from payload */ +#define CALCULATE_AVSYNC_FROM_PAYLOAD(v) ((uint64_t)((((uint64_t)v[10]) \ + << 32) | (v[11] & MASK_32BITS))) + +/* calculates avsync_info from avsync_info stored in audio */ +#define CALCULATE_AVSYNC(v) \ + ((uint64_t)((((uint64_t)v[4]) << 32) | \ + (v[5] << 16) | (v[6]))) + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audlpa_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audlpa_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audlpa_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audlpa_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; +}; + +struct audlpa_dec { + char *name; + int dec_attrb; + long (*ioctl)(struct file *, unsigned int, unsigned long); + void (*adec_params)(struct audio *); +}; + +struct audlpa_dec audlpa_decs[] = { + {"msm_mp3_lp", AUDDEC_DEC_MP3, &mp3_ioctl, &audpp_cmd_cfg_mp3_params}, + {"msm_pcm_lp_dec", AUDDEC_DEC_PCM, &pcm_ioctl, + &audpp_cmd_cfg_pcm_params}, +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audlpa_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +static unsigned long audlpa_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up); +static void audlpa_async_send_data(struct audio *audio, unsigned needed, + uint32_t *payload); + +static void lpa_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY routing id = %d\n", + evt_payload->routing_id); + /* Do not select HLB path for icodec, if there is already COPP3 + * routing exists. DSP can not support concurrency of HLB path + * and COPP3 routing as it involves different buffer Path */ + if (((0x1 << evt_payload->routing_id) == AUDPP_MIXER_ICODEC) && + !(audio->source & AUDPP_MIXER_3)) { + audio->source |= AUDPP_MIXER_HLB; + MM_DBG("mixer_mask modified for low-power audio\n"); + } else + audio->source |= (0x1 << evt_payload->routing_id); + + MM_DBG("running = %d, enabled = %d, source = 0x%x\n", + audio->running, audio->enabled, audio->source); + if (audio->running == 1 && audio->enabled == 1) { + audpp_route_stream(audio->dec_id, audio->source); + if (audio->source & AUDPP_MIXER_HLB) + audpp_dsp_set_vol_pan( + AUDPP_CMD_CFG_DEV_MIXER_ID_4, + &audio->vol_pan, + COPP); + else if (audio->source & AUDPP_MIXER_NONHLB) + audpp_dsp_set_vol_pan( + audio->dec_id, &audio->vol_pan, POPP); + if (audio->device_switch == DEVICE_SWITCH_STATE_READY) { + audio->wflush = 1; + audio->device_switch = + DEVICE_SWITCH_STATE_COMPLETE; + audpp_flush(audio->dec_id); + if (wait_event_interruptible(audio->write_wait, + !audio->wflush) < 0) + MM_DBG("AUDIO_FLUSH interrupted\n"); + + if (audio->wflush == 0) { + if (audio->drv_status & + ADRV_STATUS_PAUSE) { + if (audpp_pause(audio->dec_id, + 1)) + MM_DBG("audpp_pause" + "failed\n"); + } + } + } + } + break; + case AUDDEV_EVT_REL_PENDING: + MM_DBG(":AUDDEV_EVT_REL_PENDING\n"); + /* If route to multiple devices like COPP3, not need to + * handle device switch */ + if ((audio->running == 1) && (audio->enabled == 1) && + !(audio->source & AUDPP_MIXER_3)) { + if (audio->device_switch == DEVICE_SWITCH_STATE_NONE) { + if (!(audio->drv_status & ADRV_STATUS_PAUSE)) { + if (audpp_pause(audio->dec_id, 1)) + MM_DBG("audpp pause failed\n"); + } + audio->device_switch = + DEVICE_SWITCH_STATE_PENDING; + audio->avsync_flag = 0; + if (audpp_query_avsync(audio->dec_id) < 0) + MM_DBG("query avsync failed\n"); + + if (wait_event_interruptible_timeout + (audio->avsync_wait, audio->avsync_flag, + msecs_to_jiffies(AVSYNC_EVENT_TIMEOUT)) < 0) + MM_DBG("AV sync timeout failed\n"); + if (audio->avsync_flag == 1) { + if (audio->device_switch == + DEVICE_SWITCH_STATE_PENDING) + audio->device_switch = + DEVICE_SWITCH_STATE_READY; + } + } + } + break; + case AUDDEV_EVT_DEV_RLS: + /* If there is already COPP3 routing exists. icodec route + * was not having HLB path. */ + MM_DBG(":AUDDEV_EVT_DEV_RLS routing id = %d\n", + evt_payload->routing_id); + if (((0x1 << evt_payload->routing_id) == AUDPP_MIXER_ICODEC) && + !(audio->source & AUDPP_MIXER_3)) + audio->source &= ~AUDPP_MIXER_HLB; + else + audio->source &= ~(0x1 << evt_payload->routing_id); + MM_DBG("running = %d, enabled = %d, source = 0x%x\n", + audio->running, audio->enabled, audio->source); + + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG("\n:AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n" + "running = %d, enabled = %d, source = 0x%x", + audio->vol_pan.volume, audio->running, + audio->enabled, audio->source); + if (audio->running == 1 && audio->enabled == 1) { + if (audio->source & AUDPP_MIXER_HLB) + audpp_dsp_set_vol_pan( + AUDPP_CMD_CFG_DEV_MIXER_ID_4, + &audio->vol_pan, COPP); + else if (audio->source & AUDPP_MIXER_NONHLB) + audpp_dsp_set_vol_pan( + audio->dec_id, &audio->vol_pan, POPP); + } + break; + default: + MM_ERR(":ERROR:wrong event\n"); + break; + } +} + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audlpa_async_send_data(audio, 1, msg); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + default: + MM_ERR("unexpected message from decoder\n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status: sleep reason=0x%04x\n", + reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init\n"); + audio->codec_ops.adec_params(audio); + break; + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg\n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play\n"); + /* send mixer command */ + audpp_route_stream(audio->dec_id, + audio->source); + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + case AUDPP_DEC_STATUS_EOS: + MM_DBG("decoder status: EOS\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + default: + MM_ERR("unknown decoder status\n"); + break; + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + MM_DBG("source = 0x%x\n", audio->source); + if (audio->source & AUDPP_MIXER_HLB) + audpp_dsp_set_vol_pan( + AUDPP_CMD_CFG_DEV_MIXER_ID_4, + &audio->vol_pan, + COPP); + else if (audio->source & AUDPP_MIXER_NONHLB) + audpp_dsp_set_vol_pan( + audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audio->codec_ops.adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_lpa = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | + audlpa_decs[audio->minor_no].dec_attrb; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audlpa_async_send_buffer(struct audio *audio) +{ + int found = 0; + uint64_t temp = 0; + struct audplay_cmd_bitstream_data_avail cmd; + struct audlpa_buffer_node *next_buf = NULL; + + temp = audio->bytecount_head; + if (audio->device_switch == DEVICE_SWITCH_STATE_NONE) { + list_for_each_entry(next_buf, &audio->out_queue, list) { + if (temp == audio->bytecount_given) { + found = 1; + break; + } else + temp += next_buf->buf.data_len; + } + if (next_buf && found) { + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL; + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = (unsigned) next_buf->paddr; + cmd.buf_size = next_buf->buf.data_len >> 1; + cmd.partition_number = 0; + audio->bytecount_given += next_buf->buf.data_len; + wmb(); + audplay_send_queue0(audio, &cmd, sizeof(cmd)); + audio->out_needed = 0; + audio->drv_status |= ADRV_STATUS_OBUF_GIVEN; + } + } else if (audio->device_switch == DEVICE_SWITCH_STATE_COMPLETE) { + audio->device_switch = DEVICE_SWITCH_STATE_NONE; + next_buf = list_first_entry(&audio->out_queue, + struct audlpa_buffer_node, list); + if (next_buf) { + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL; + cmd.decoder_id = audio->dec_id; + temp = audio->bytecount_head + + next_buf->buf.data_len - + audio->bytecount_consumed; + if (audpp_restore_avsync(audio->dec_id, + &audio->avsync[0])) + MM_DBG("audpp_restore_avsync failed\n"); + + if ((signed)(temp >= 0) && + ((signed)(next_buf->buf.data_len - temp) >= 0)) { + cmd.buf_ptr = (unsigned) (next_buf->paddr + + (next_buf->buf.data_len - + temp)); + cmd.buf_size = temp >> 1; + cmd.partition_number = 0; + audio->bytecount_given = + audio->bytecount_consumed + temp; + wmb(); + audplay_send_queue0(audio, &cmd, sizeof(cmd)); + audio->out_needed = 0; + audio->drv_status |= ADRV_STATUS_OBUF_GIVEN; + } + } + } +} + +static void audlpa_async_send_data(struct audio *audio, unsigned needed, + uint32_t *payload) +{ + unsigned long flags; + uint64_t temp = 0; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + audio->out_needed = 1; + if (audio->drv_status & ADRV_STATUS_OBUF_GIVEN) { + union msm_audio_event_payload evt_payload; + struct audlpa_buffer_node *used_buf = NULL; + + if (CHECK_ERROR(payload)) + audio->bytecount_consumed = + CALCULATE_AVSYNC_FROM_PAYLOAD(payload); + + if ((audio->device_switch == + DEVICE_SWITCH_STATE_COMPLETE) && + (audio->avsync_flag == 1)) { + audio->avsync_flag = 0; + audio->bytecount_consumed = + CALCULATE_AVSYNC(audio->avsync); + } + BUG_ON(list_empty(&audio->out_queue)); + temp = audio->bytecount_head; + used_buf = list_first_entry(&audio->out_queue, + struct audlpa_buffer_node, list); + if ((audio->bytecount_head + used_buf->buf.data_len) < + audio->bytecount_consumed) { + audio->bytecount_head += used_buf->buf.data_len; + temp = audio->bytecount_head; + list_del(&used_buf->list); + evt_payload.aio_buf = used_buf->buf; + audlpa_post_event(audio, AUDIO_EVENT_WRITE_DONE, + evt_payload); + kfree(used_buf); + } + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + } + } + if (audio->out_needed) { + if (!list_empty(&audio->out_queue)) + audlpa_async_send_buffer(audio); + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ +static void audlpa_async_flush(struct audio *audio) +{ + struct audlpa_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audlpa_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + if ((buf_node->paddr != 0xFFFFFFFF) && + (buf_node->buf.data_len != 0)) + audlpa_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(buf_node); + } + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + audio->out_needed = 0; + audio->bytecount_consumed = 0; + audio->bytecount_head = 0; + audio->bytecount_given = 0; + audio->device_switch = DEVICE_SWITCH_STATE_NONE; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_ioport_reset(struct audio *audio) +{ + /* If fsync is in progress, make sure + * return value of fsync indicates + * abort due to flush + */ + if (audio->drv_status & ADRV_STATUS_FSYNC) { + MM_DBG("fsync in progress\n"); + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audlpa_async_flush(audio); + mutex_unlock(&audio->write_lock); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + } else + audlpa_async_flush(audio); +} + +static int audlpa_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audlpa_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audlpa_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audlpa_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audlpa_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audlpa_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audlpa_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audlpa_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audlpa_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audlpa_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (drv_evt->event_type == AUDIO_EVENT_WRITE_DONE || + drv_evt->event_type == AUDIO_EVENT_READ_DONE) { + mutex_lock(&audio->lock); + audlpa_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0); + mutex_unlock(&audio->lock); + } + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audlpa_pmem_check(struct audio *audio, + void *vaddr, unsigned long len) +{ + struct audlpa_pmem_region *region_elt; + struct audlpa_pmem_region t = { .vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + MM_ERR("region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, + region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audlpa_pmem_add(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audlpa_pmem_region *region; + int rc = -EINVAL; + + MM_DBG("\n"); /* Macro prints the file name and function */ + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = audlpa_pmem_check(audio, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + MM_DBG("add region paddr %lx vaddr %p, len %lu\n", region->paddr, + region->vaddr, region->len); + list_add_tail(®ion->list, &audio->pmem_region_queue); +end: + return rc; +} + +static int audlpa_pmem_remove(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audlpa_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + MM_DBG("info fd %d vaddr %p\n", info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audlpa_pmem_region, list); + + if ((region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + MM_DBG("region %p in use ref_cnt %d\n", + region, region->ref_cnt); + break; + } + MM_DBG("remove region fd %d vaddr %p\n", + info->fd, info->vaddr); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +static int audlpa_pmem_lookup_vaddr(struct audio *audio, void *addr, + unsigned long len, struct audlpa_pmem_region **region) +{ + struct audlpa_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + MM_ERR("multiple hits for vaddr %p, len %ld\n", addr, len); + list_for_each_entry(region_elt, + &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + MM_ERR("\t%p, %ld --> %p\n", region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +unsigned long audlpa_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up) +{ + struct audlpa_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audlpa_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + MM_ERR("lookup (%p, %ld) failed\n", addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + MM_DBG("found region %p ref_cnt %d\n", region, region->ref_cnt); + paddr = region->paddr + (addr - region->vaddr); + return paddr; +} + +/* audio -> lock must be held at this point */ +static int audlpa_aio_buf_add(struct audio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audlpa_buffer_node *buf_node; + + buf_node = kmalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + MM_DBG("node %p dir %x buf_addr %p buf_len %d data_len" + "%d\n", buf_node, dir, + buf_node->buf.buf_addr, buf_node->buf.buf_len, + buf_node->buf.data_len); + + buf_node->paddr = audlpa_pmem_fixup( + audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1); + + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.data_len & 0x1)) { + kfree(buf_node); + return -EINVAL; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + list_add_tail(&buf_node->list, &audio->out_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + audlpa_async_send_data(audio, 0, 0); + } else { + /* read */ + } + + MM_DBG("Add buf_node %p paddr %lx\n", buf_node, buf_node->paddr); + + return 0; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("audio_ioctl() cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(AUDPP_CMD_CFG_DEV_MIXER_ID_4, + &audio->vol_pan, + COPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(AUDPP_CMD_CFG_DEV_MIXER_ID_4, + &audio->vol_pan, + COPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG(" AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audlpa_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_DBG("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + audio->drv_status &= ~ADRV_STATUS_PAUSE; + break; + + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->wflush = 0; + } + break; + + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + MM_INFO("AUDIO_SET_CONFIG\n"); + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + MM_INFO("ERROR: copy from user\n"); + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + MM_INFO("ERROR: config.channel_count == %d\n", + config.channel_count); + break; + } + + if (config.bits == 8) + config.bits = AUDPP_CMD_WAV_PCM_WIDTH_8; + else if (config.bits == 16) + config.bits = AUDPP_CMD_WAV_PCM_WIDTH_16; + else if (config.bits == 24) + config.bits = AUDPP_CMD_WAV_PCM_WIDTH_24; + else { + rc = -EINVAL; + MM_INFO("ERROR: config.bits == %d\n", config.bits); + break; + } + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + audio->out_bits = config.bits; + MM_DBG("AUDIO_SET_CONFIG: config.bits = %d\n", config.bits); + rc = 0; + break; + } + + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + if (audio->out_bits == AUDPP_CMD_WAV_PCM_WIDTH_8) + config.bits = 8; + else if (audio->out_bits == AUDPP_CMD_WAV_PCM_WIDTH_24) + config.bits = 24; + else + config.bits = 16; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + MM_DBG("AUDIO_GET_CONFIG: config.bits = %d\n", config.bits); + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + if (arg == 1) + audio->drv_status |= ADRV_STATUS_PAUSE; + else if (arg == 0) + audio->drv_status &= ~ADRV_STATUS_PAUSE; + break; + + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audlpa_pmem_add(audio, &info); + break; + } + + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_DEREGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audlpa_pmem_remove(audio, &info); + break; + } + case AUDIO_ASYNC_WRITE: + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else + rc = audlpa_aio_buf_add(audio, 1, (void __user *) arg); + break; + + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = audio->codec_ops.ioctl(file, cmd, arg); + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +int audlpa_async_fsync(struct audio *audio) +{ + int rc = 0, empty = 0; + struct audlpa_buffer_node *buf_node; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + mutex_lock(&audio->write_lock); + audio->teos = 0; + empty = list_empty(&audio->out_queue); + buf_node = kmalloc(sizeof(*buf_node), GFP_KERNEL); + if (!buf_node) + goto done; + + buf_node->paddr = 0xFFFFFFFF; + buf_node->buf.data_len = 0; + buf_node->buf.buf_addr = NULL; + buf_node->buf.buf_len = 0; + buf_node->buf.private_data = NULL; + list_add_tail(&buf_node->list, &audio->out_queue); + if ((empty != 0) && (audio->out_needed == 1)) + audlpa_async_send_data(audio, 0, 0); + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush || + audio->stopped); + + if (rc < 0) + goto done; + + if (audio->teos == 1) { + /* Releasing all the pending buffers to user */ + audio->teos = 0; + audlpa_async_flush(audio); + } + + if (audio->stopped || audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +int audlpa_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + + if (!audio->running) + return -EINVAL; + + return audlpa_async_fsync(audio); +} + +static void audlpa_reset_pmem_region(struct audio *audio) +{ + struct audlpa_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audlpa_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audio_disable(audio); + audlpa_async_flush(audio); + audlpa_reset_pmem_region(audio); + + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->opened = 0; + audio->event_abort = 1; + wake_up(&audio->event_wait); + audlpa_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audlpa_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audlpa_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audlpa_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audlpa_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audlpa_suspend(struct early_suspend *h) +{ + struct audlpa_suspend_ctl *ctl = + container_of(h, struct audlpa_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audlpa_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audlpa_resume(struct early_suspend *h) +{ + struct audlpa_suspend_ctl *ctl = + container_of(h, struct audlpa_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audlpa_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audlpa_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audlpa_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x\n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d\n", + audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d\n", + audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d\n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d\n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d\n", audio->out_needed); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audlpa_debug_fops = { + .read = audlpa_debug_read, + .open = audlpa_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, i, dec_attrb = 0, decid; + struct audlpa_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_lpa_" + 5]; +#endif + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance\n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + if ((file->f_mode & FMODE_WRITE) && !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + /* Allocate the decoder based on inode minor number*/ + audio->minor_no = iminor(inode); + dec_attrb |= audlpa_decs[audio->minor_no].dec_attrb; + audio->codec_ops.ioctl = audlpa_decs[audio->minor_no].ioctl; + audio->codec_ops.adec_params = audlpa_decs[audio->minor_no].adec_params; + + dec_attrb |= MSM_AUD_MODE_LP; + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + if (decid < 0) { + MM_ERR("No free decoder available\n"); + rc = -ENODEV; + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + MM_DBG("set to aio interface\n"); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_lpa, audio); + + if (rc) { + MM_ERR("failed to get %s module\n", audio->module_name); + goto err; + } + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->avsync_wait); + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + audio->out_bits = AUDPP_CMD_WAV_PCM_WIDTH_16; + audio->vol_pan.volume = 0x2000; + + audlpa_async_flush(audio); + + file->private_data = audio; + audio->opened = 1; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS | AUDDEV_EVT_REL_PENDING + |AUDDEV_EVT_STREAM_VOL_CHG; + audio->device_switch = DEVICE_SWITCH_STATE_NONE; + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->bytecount_consumed = 0; + audio->bytecount_head = 0; + audio->bytecount_given = 0; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + lpa_listner, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listnet\n", __func__); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_lpa_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audlpa_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audlpa_resume; + audio->suspend_ctl.node.suspend = audlpa_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDLPA_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audlpa_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + kfree(audio); + return rc; +} + +static const struct file_operations audio_lpa_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audlpa_fsync, +}; + +static dev_t audlpa_devno; +static struct class *audlpa_class; +struct audlpa_device { + const char *name; + struct device *device; + struct cdev cdev; +}; + +static struct audlpa_device *audlpa_devices; + +static void audlpa_create(struct audlpa_device *adev, const char *name, + struct device *parent, dev_t devt) +{ + struct device *dev; + int rc; + + dev = device_create(audlpa_class, parent, devt, "%s", name); + if (IS_ERR(dev)) + return; + + cdev_init(&adev->cdev, &audio_lpa_fops); + adev->cdev.owner = THIS_MODULE; + + rc = cdev_add(&adev->cdev, devt, 1); + if (rc < 0) { + device_destroy(audlpa_class, devt); + } else { + adev->device = dev; + adev->name = name; + } +} + +static int __init audio_init(void) +{ + int rc; + int n = ARRAY_SIZE(audlpa_decs); + + audlpa_devices = kzalloc(sizeof(struct audlpa_device) * n, GFP_KERNEL); + if (!audlpa_devices) + return -ENOMEM; + + audlpa_class = class_create(THIS_MODULE, "audlpa"); + if (IS_ERR(audlpa_class)) + goto fail_create_class; + + rc = alloc_chrdev_region(&audlpa_devno, 0, n, "msm_audio_lpa"); + if (rc < 0) + goto fail_alloc_region; + + for (n = 0; n < ARRAY_SIZE(audlpa_decs); n++) { + audlpa_create(audlpa_devices + n, + audlpa_decs[n].name, NULL, + MKDEV(MAJOR(audlpa_devno), n)); + } + + return 0; + +fail_alloc_region: + class_unregister(audlpa_class); + return rc; +fail_create_class: + kfree(audlpa_devices); + return -ENOMEM; +} + +static void __exit audio_exit(void) +{ + class_unregister(audlpa_class); + kfree(audlpa_devices); +} + +module_init(audio_init); +module_exit(audio_exit); + +MODULE_DESCRIPTION("MSM LPA driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_mp3.c b/arch/arm/mach-msm/qdsp5v2/audio_mp3.c new file mode 100644 index 00000000000..7c0cde89f34 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_mp3.c @@ -0,0 +1,2505 @@ +/* mp3 audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#define ADRV_STATUS_AIO_INTF 0x00000001 +#define ADRV_STATUS_OBUF_GIVEN 0x00000002 +#define ADRV_STATUS_IBUF_GIVEN 0x00000004 +#define ADRV_STATUS_FSYNC 0x00000008 + +/* Size must be power of 2 */ +#define BUFSZ_MAX 32768 +#define BUFSZ_MIN 4096 +#define DMASZ_MAX (BUFSZ_MAX * 2) +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_MP3 2 + +#define PCM_BUFSZ_MIN 4800 /* Hold one stereo MP3 frame */ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDMP3_METAFIELD_MASK 0xFFFF0000 +#define AUDMP3_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDMP3_EOS_FLG_MASK 0x01 +#define AUDMP3_EOS_NONE 0x0 /* No EOS detected */ +#define AUDMP3_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDMP3_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +#define BITSTREAM_ERROR_THRESHOLD_VALUE 0x1 /* DEFAULT THRESHOLD VALUE */ + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audmp3_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audmp3_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audmp3_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audmp3_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; +}; + +struct audmp3_drv_operations { + void (*pcm_buf_update)(struct audio *, uint32_t *); + void (*buffer_refresh)(struct audio *); + void (*send_data)(struct audio *, unsigned); + void (*out_flush)(struct audio *); + void (*in_flush)(struct audio *); + int (*fsync)(struct audio *); +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + struct list_head out_queue; /* queue to retain output buffers */ + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + struct list_head in_queue; /* queue to retain input buffers */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + uint32_t drv_status; + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audmp3_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + uint32_t device_events; + + struct list_head pmem_region_queue; /* protected by lock */ + struct audmp3_drv_operations drv_ops; + + struct msm_audio_bitstream_info stream_info; + struct msm_audio_bitstream_error_info bitstream_error_info; + uint32_t bitstream_error_threshold_value; + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audplay_error_threshold_config(struct audio *audio); +static void audplay_config_hostpcm(struct audio *audio); +static void audplay_buffer_refresh(struct audio *audio); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audmp3_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +static unsigned long audmp3_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up); + +static void mp3_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + default: + MM_ERR(":ERROR:wrong event\n"); + break; + } +} +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audmp3_async_pcm_buf_update(struct audio *audio, uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audmp3_buffer_node *filled_buf; + uint8_t index; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + BUG_ON(list_empty(&audio->in_queue)); + filled_buf = list_first_entry(&audio->in_queue, + struct audmp3_buffer_node, list); + if (filled_buf->paddr == payload[2 + index * 2]) { + list_del(&filled_buf->list); + event_payload.aio_buf = filled_buf->buf; + event_payload.aio_buf.data_len = + payload[3 + index * 2]; + MM_DBG("pcm buf %p data_len %d\n", filled_buf, + event_payload.aio_buf.data_len); + audmp3_post_event(audio, AUDIO_EVENT_READ_DONE, + event_payload); + kfree(filled_buf); + } else { + MM_ERR("expected=%lx ret=%x\n", filled_buf->paddr, + payload[2 + index * 2]); + break; + } + } + + audio->drv_status &= ~ADRV_STATUS_IBUF_GIVEN; + audio->drv_ops.buffer_refresh(audio); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + +} + +static void audio_update_pcm_buf_entry(struct audio *audio, uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[2 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audio->drv_ops.buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + +} + +static void audmp3_bitstream_error_info(struct audio *audio, uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload e_payload; + + if (payload[0] != AUDDEC_DEC_MP3) { + MM_ERR("Unexpected bitstream error info from DSP:\ + Invalid decoder\n"); + return; + } + + /* get stream info from DSP msg */ + spin_lock_irqsave(&audio->dsp_lock, flags); + + audio->bitstream_error_info.dec_id = payload[0]; + audio->bitstream_error_info.err_msg_indicator = payload[1]; + audio->bitstream_error_info.err_type = payload[2]; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + MM_ERR("bit_stream_error_type=%d error_count=%d\n", + audio->bitstream_error_info.err_type, (0x0000FFFF & + audio->bitstream_error_info.err_msg_indicator)); + + /* send event to ARM to notify error info coming */ + e_payload.error_info = audio->bitstream_error_info; + audmp3_post_event(audio, AUDIO_EVENT_BITSTREAM_ERROR_INFO, e_payload); +} + +static void audmp3_update_stream_info(struct audio *audio, uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload e_payload; + + /* get stream info from DSP msg */ + spin_lock_irqsave(&audio->dsp_lock, flags); + + audio->stream_info.codec_type = AUDIO_CODEC_TYPE_MP3; + audio->stream_info.chan_info = (0x0000FFFF & payload[1]); + audio->stream_info.sample_rate = (0x0000FFFF & payload[2]); + audio->stream_info.bit_stream_info = (0x0000FFFF & payload[3]); + audio->stream_info.bit_rate = payload[4]; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + MM_DBG("chan_info=%d, sample_rate=%d, bit_stream_info=%d\n", + audio->stream_info.chan_info, + audio->stream_info.sample_rate, + audio->stream_info.bit_stream_info); + + /* send event to ARM to notify steam info coming */ + e_payload.stream_info = audio->stream_info; + audmp3_post_event(audio, AUDIO_EVENT_STREAM_INFO, e_payload); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audio->drv_ops.send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audio->drv_ops.pcm_buf_update(audio, msg); + break; + + case AUDPLAY_UP_STREAM_INFO: + if ((msg[1] & AUDPLAY_STREAM_INFO_MSG_MASK) == + AUDPLAY_STREAM_INFO_MSG_MASK) { + audmp3_bitstream_error_info(audio, msg); + } else { + audmp3_update_stream_info(audio, msg); + } + break; + + case AUDPLAY_UP_OUTPORT_FLUSH_ACK: + MM_DBG("OUTPORT_FLUSH_ACK\n"); + audio->rflush = 0; + wake_up(&audio->read_wait); + if (audio->pcm_feedback) + audio->drv_ops.buffer_refresh(audio); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status: sleep reason=0x%04x\n", + reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + /* send mixer command */ + audpp_route_stream(audio->dec_id, + audio->source); + if (audio->pcm_feedback) { + audplay_error_threshold_config(audio); + audplay_config_hostpcm(audio); + audio->drv_ops.buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status \n"); + break; + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audio->drv_ops.buffer_refresh(audio); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AUDPP_MSG_AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + + +struct msm_adsp_ops audplay_adsp_ops = { + .event = audplay_dsp_event, +}; + + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_MP3; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_mp3 cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_MP3_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDMP3_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len/2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} +/* Caller holds irq_lock */ +static void audmp3_async_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + struct audmp3_buffer_node *next_buf; + + if (!audio->running || + audio->drv_status & ADRV_STATUS_IBUF_GIVEN) + return; + + if (!list_empty(&audio->in_queue)) { + next_buf = list_first_entry(&audio->in_queue, + struct audmp3_buffer_node, list); + if (!next_buf) + return; + MM_DBG("next buf %p phy %lx len %d\n", next_buf, + next_buf->paddr, next_buf->buf.buf_len); + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = next_buf->paddr; + refresh_cmd.buf0_length = next_buf->buf.buf_len - + (next_buf->buf.buf_len % 576) + + (audio->mfield ? 24 : 0); /* Mp3 frame size */ + refresh_cmd.buf_read_count = 0; + audio->drv_status |= ADRV_STATUS_IBUF_GIVEN; + (void) audplay_send_queue0(audio, &refresh_cmd, + sizeof(refresh_cmd)); + } + +} + +static void audplay_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size - + (audio->in[audio->fill_next].size % 576) + + (audio->mfield ? 24 : 0); /* Mp3 frame size */ + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audplay_error_threshold_config(struct audio *audio) +{ + union audplay_cmd_channel_info ch_cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + ch_cfg_cmd.thr_update.cmd_id = AUDPLAY_CMD_CHANNEL_INFO; + ch_cfg_cmd.thr_update.threshold_update = AUDPLAY_ERROR_THRESHOLD_ENABLE; + ch_cfg_cmd.thr_update.threshold_value = + audio->bitstream_error_threshold_value; + (void)audplay_send_queue0(audio, &ch_cfg_cmd, sizeof(ch_cfg_cmd)); +} + +static void audplay_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = 1; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); + +} + +static void audplay_outport_flush(struct audio *audio) +{ + struct audplay_cmd_outport_flush op_flush_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + op_flush_cmd.cmd_id = AUDPLAY_CMD_OUTPORT_FLUSH; + (void)audplay_send_queue0(audio, &op_flush_cmd, sizeof(op_flush_cmd)); +} + +static void audmp3_async_send_data(struct audio *audio, unsigned needed) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + audio->out_needed = 1; + if (audio->drv_status & ADRV_STATUS_OBUF_GIVEN) { + /* pop one node out of queue */ + union msm_audio_event_payload payload; + struct audmp3_buffer_node *used_buf; + + MM_DBG("consumed\n"); + BUG_ON(list_empty(&audio->out_queue)); + used_buf = list_first_entry(&audio->out_queue, + struct audmp3_buffer_node, list); + list_del(&used_buf->list); + payload.aio_buf = used_buf->buf; + audmp3_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(used_buf); + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + } + + } + + if (audio->out_needed) { + struct audmp3_buffer_node *next_buf; + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + if (!list_empty(&audio->out_queue)) { + next_buf = list_first_entry(&audio->out_queue, + struct audmp3_buffer_node, list); + MM_DBG("next_buf %p\n", next_buf); + if (next_buf) { + MM_DBG("next buf phy %lx len %d\n", + next_buf->paddr, + next_buf->buf.data_len); + + cmd.cmd_id = + AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDMP3_METAFIELD_MASK | + (next_buf->buf.mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = (unsigned) next_buf->paddr; + cmd.buf_size = next_buf->buf.data_len >> 1; + cmd.partition_number = 0; + audplay_send_queue0(audio, &cmd, sizeof(cmd)); + audio->out_needed = 0; + audio->drv_status |= ADRV_STATUS_OBUF_GIVEN; + } + } + } + +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ +static void audmp3_async_flush(struct audio *audio) +{ + struct audmp3_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audmp3_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audmp3_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(buf_node); + } + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audmp3_async_flush_pcm_buf(struct audio *audio) +{ + struct audmp3_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + list_for_each_safe(ptr, next, &audio->in_queue) { + buf_node = list_entry(ptr, struct audmp3_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = 0; + audmp3_post_event(audio, AUDIO_EVENT_READ_DONE, + payload); + kfree(buf_node); + } + audio->drv_status &= ~ADRV_STATUS_IBUF_GIVEN; + +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audio_ioport_reset(struct audio *audio) +{ + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + /* If fsync is in progress, make sure + * return value of fsync indicates + * abort due to flush + */ + if (audio->drv_status & ADRV_STATUS_FSYNC) { + MM_DBG("fsync in progress\n"); + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio->drv_ops.out_flush(audio); + mutex_unlock(&audio->write_lock); + } else + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + } else { + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio->drv_ops.out_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio->drv_ops.in_flush(audio); + mutex_unlock(&audio->read_lock); + } + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); +} + +static int audmp3_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audmp3_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audmp3_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audmp3_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audmp3_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audmp3_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audmp3_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audmp3_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audmp3_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audmp3_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (drv_evt->event_type == AUDIO_EVENT_WRITE_DONE || + drv_evt->event_type == AUDIO_EVENT_READ_DONE) { + mutex_lock(&audio->lock); + audmp3_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0); + mutex_unlock(&audio->lock); + } + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audmp3_pmem_check(struct audio *audio, + void *vaddr, unsigned long len) +{ + struct audmp3_pmem_region *region_elt; + struct audmp3_pmem_region t = { .vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + MM_ERR("region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, + region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audmp3_pmem_add(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audmp3_pmem_region *region; + int rc = -EINVAL; + + MM_DBG("\n"); /* Macro prints the file name and function */ + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = audmp3_pmem_check(audio, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + MM_DBG("add region paddr %lx vaddr %p, len %lu\n", region->paddr, + region->vaddr, region->len); + list_add_tail(®ion->list, &audio->pmem_region_queue); +end: + return rc; +} + +static int audmp3_pmem_remove(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audmp3_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + MM_DBG("info fd %d vaddr %p\n", info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audmp3_pmem_region, list); + + if ((region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + MM_DBG("region %p in use ref_cnt %d\n", + region, region->ref_cnt); + break; + } + MM_DBG("remove region fd %d vaddr %p \n", + info->fd, info->vaddr); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +static int audmp3_pmem_lookup_vaddr(struct audio *audio, void *addr, + unsigned long len, struct audmp3_pmem_region **region) +{ + struct audmp3_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + MM_ERR("multiple hits for vaddr %p, len %ld\n", addr, len); + list_for_each_entry(region_elt, + &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + MM_ERR("\t%p, %ld --> %p\n", region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +unsigned long audmp3_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up) +{ + struct audmp3_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audmp3_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + MM_ERR("lookup (%p, %ld) failed\n", addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + MM_DBG("found region %p ref_cnt %d\n", region, region->ref_cnt); + paddr = region->paddr + (addr - region->vaddr); + return paddr; +} + +/* audio -> lock must be held at this point */ +static int audmp3_aio_buf_add(struct audio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audmp3_buffer_node *buf_node; + + buf_node = kmalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + MM_DBG("node %p dir %x buf_addr %p buf_len %d data_len \ + %d\n", buf_node, dir, + buf_node->buf.buf_addr, buf_node->buf.buf_len, + buf_node->buf.data_len); + + buf_node->paddr = audmp3_pmem_fixup( + audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1); + + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.data_len & 0x1) || + (!audio->pcm_feedback && + !buf_node->buf.data_len)) { + kfree(buf_node); + return -EINVAL; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + list_add_tail(&buf_node->list, &audio->out_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + audio->drv_ops.send_data(audio, 0); + } else { + /* read */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.buf_len < PCM_BUFSZ_MIN)) { + kfree(buf_node); + return -EINVAL; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + list_add_tail(&buf_node->list, &audio->in_queue); + audio->drv_ops.buffer_refresh(audio); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + + MM_DBG("Add buf_node %p paddr %lx\n", buf_node, buf_node->paddr); + + return 0; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG(" AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audmp3_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_OUTPORT_FLUSH: + MM_DBG("AUDIO_OUTPORT_FLUSH\n"); + audio->rflush = 1; + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + audio->drv_ops.in_flush(audio); + } else { + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio->drv_ops.in_flush(audio); + mutex_unlock(&audio->read_lock); + } + audplay_outport_flush(audio); + rc = wait_event_interruptible(audio->read_wait, + !audio->rflush); + if (rc < 0) { + MM_ERR("AUDPLAY_OUTPORT_FLUSH interrupted\n"); + rc = -EINTR; + } + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + audio->mfield = config.meta_field; + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + rc = 0; + break; + } + + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buffer %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("malloc read buf failed\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; + index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + rc = 0; + MM_DBG("read buf: phy addr \ + 0x%08x kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + + case AUDIO_GET_STREAM_INFO:{ + if (audio->stream_info.sample_rate == 0) { + /* haven't received DSP stream event, + the stream info is not updated */ + rc = -EPERM; + break; + } + if (copy_to_user((void *)arg, &audio->stream_info, + sizeof(struct msm_audio_bitstream_info))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_BITSTREAM_ERROR_INFO:{ + if ((audio->bitstream_error_info.err_msg_indicator & + AUDPLAY_STREAM_INFO_MSG_MASK) == + AUDPLAY_STREAM_INFO_MSG_MASK) { + /* haven't received bitstream error info event, + the bitstream error info is not updated */ + rc = -EPERM; + break; + } + if (copy_to_user((void *)arg, &audio->bitstream_error_info, + sizeof(struct msm_audio_bitstream_error_info))) + rc = -EFAULT; + else + rc = 0; + break; + } + + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audmp3_pmem_add(audio, &info); + break; + } + + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_DEREGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audmp3_pmem_remove(audio, &info); + break; + } + case AUDIO_ASYNC_WRITE: + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else + rc = audmp3_aio_buf_add(audio, 1, (void __user *) arg); + break; + + case AUDIO_ASYNC_READ: + if (audio->pcm_feedback) + rc = audmp3_aio_buf_add(audio, 0, (void __user *) arg); + else + rc = -EPERM; + break; + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + case AUDIO_SET_ERR_THRESHOLD_VALUE: + if (copy_from_user(&audio->bitstream_error_threshold_value, + (void *)arg, sizeof(uint32_t))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +int audmp3_async_fsync(struct audio *audio) +{ + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + mutex_lock(&audio->write_lock); + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + (audio->teos && audio->out_needed && + list_empty(&audio->out_queue)) + || audio->wflush || audio->stopped); + + if (audio->stopped || audio->wflush) + rc = -EBUSY; + + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +int audmp3_sync_fsync(struct audio *audio) +{ + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audio->drv_ops.send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + return rc; +} + +int audmp3_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + + if (!audio->running || audio->pcm_feedback) + return -EINVAL; + + return audio->drv_ops.fsync(audio); +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (audio->drv_status & ADRV_STATUS_AIO_INTF) + return -EPERM; + else if (!audio->pcm_feedback) + return 0; /* PCM feedback disabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d \n", count); + while (count > 0) { + rc = wait_event_interruptible_timeout( + audio->read_wait, + (audio->in[audio->read_next]. + used > 0) || (audio->stopped) + || (audio->rflush), + msecs_to_jiffies(MSM_AUD_BUFFER_UPDATE_WAIT_MS)); + + if (rc == 0) { + rc = -ETIMEDOUT; + break; + } else if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since + * driver does not know frame size, read count + * must be greater or equal + * to size of PCM samples + */ + MM_DBG("no partial frame done reading\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audio->drv_ops.buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audmp3_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + char *buf_ptr; + + if (audio->reserved) { + MM_DBG("flush reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + frame->used = 2; + audio->reserved = 0; + audio->drv_ops.send_data(audio, 0); + } + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audio->drv_ops.send_data(audio, 0); +done: + return rc; +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDMP3_EOS_NONE; + unsigned dsize; + unsigned short mfield_size = 0; + + if (audio->drv_status & ADRV_STATUS_AIO_INTF) + return -EPERM; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDMP3_EOS_FLG_OFFSET] & + AUDMP3_EOS_FLG_MASK) { + MM_DBG("EOS SET\n"); + eos_condition = AUDMP3_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDMP3_EOS_FLG_OFFSET] + &= ~AUDMP3_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + (frame->size - mfield_size) - 1 : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audio->drv_ops.send_data(audio, 0); + } + } + if (eos_condition == AUDMP3_EOS_SET) + rc = audmp3_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static void audmp3_reset_pmem_region(struct audio *audio) +{ + struct audmp3_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audmp3_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audio_disable(audio); + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + audmp3_reset_pmem_region(audio); + + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->opened = 0; + audio->event_abort = 1; + wake_up(&audio->event_wait); + audmp3_reset_event_queue(audio); + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + } + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audmp3_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audmp3_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audmp3_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audmp3_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audmp3_suspend(struct early_suspend *h) +{ + struct audmp3_suspend_ctl *ctl = + container_of(h, struct audmp3_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audmp3_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audmp3_resume(struct early_suspend *h) +{ + struct audmp3_suspend_ctl *ctl = + container_of(h, struct audmp3_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audmp3_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audmp3_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audmp3_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audmp3_debug_fops = { + .read = audmp3_debug_read, + .open = audmp3_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + + struct audio *audio = NULL; + int rc, i, dec_attrb, decid; + struct audmp3_event *e_node = NULL; + unsigned pmem_sz = DMASZ_MAX; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_mp3_" + 5]; +#endif + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_MP3; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + /* AIO interface */ + if (file->f_flags & O_NONBLOCK) { + MM_DBG("set to aio interface \n"); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + audio->drv_ops.pcm_buf_update = audmp3_async_pcm_buf_update; + audio->drv_ops.buffer_refresh = audmp3_async_buffer_refresh; + audio->drv_ops.send_data = audmp3_async_send_data; + audio->drv_ops.out_flush = audmp3_async_flush; + audio->drv_ops.in_flush = audmp3_async_flush_pcm_buf; + audio->drv_ops.fsync = audmp3_async_fsync; + } else { + MM_DBG("set to std io interface \n"); + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d \n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write \ + buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr\ + 0x%08x\n", audio->phys,\ + (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + audio->drv_ops.pcm_buf_update = audio_update_pcm_buf_entry; + audio->drv_ops.buffer_refresh = audplay_buffer_refresh; + audio->drv_ops.send_data = audplay_send_data; + audio->drv_ops.out_flush = audio_flush; + audio->drv_ops.in_flush = audio_flush_pcm_buf; + audio->drv_ops.fsync = audmp3_sync_fsync; + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = (audio->out_dma_sz >> 1); + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops, audio); + + if (rc) { + MM_ERR("failed to get %s module freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->in_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->avsync_wait); + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + audio->vol_pan.volume = 0x2000; + audio->bitstream_error_threshold_value = + BITSTREAM_ERROR_THRESHOLD_VALUE; + + audio->drv_ops.out_flush(audio); + + file->private_data = audio; + audio->opened = 1; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + mp3_listner, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listner\n", __func__); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_mp3_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audmp3_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audmp3_resume; + audio->suspend_ctl.node.suspend = audmp3_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDMP3_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audmp3_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } + memset(&audio->stream_info, 0, sizeof(struct msm_audio_bitstream_info)); + memset(&audio->bitstream_error_info, 0, + sizeof(struct msm_audio_bitstream_info)); +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + } + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_mp3_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audmp3_fsync, +}; + +struct miscdevice audio_mp3_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_mp3", + .fops = &audio_mp3_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_mp3_misc); +} + +static void __exit audio_exit(void) +{ + misc_deregister(&audio_mp3_misc); +} + +module_init(audio_init); +module_exit(audio_exit); + +MODULE_DESCRIPTION("MSM MP3 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_mvs.c b/arch/arm/mach-msm/qdsp5v2/audio_mvs.c new file mode 100644 index 00000000000..dc41bf41de4 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_mvs.c @@ -0,0 +1,1748 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MVS_PROG 0x30000014 +#define MVS_VERS 0x00030001 +#define MVS_VERS_COMP_VER4 0x00040001 +#define MVS_VERS_COMP_VER5 0x00050001 + +#define MVS_CLIENT_ID_VOIP 0x00000003 + +#define MVS_ACQUIRE_PROC 4 +#define MVS_ENABLE_PROC 5 +#define MVS_RELEASE_PROC 6 +#define MVS_AMR_SET_AMR_MODE_PROC 7 +#define MVS_AMR_SET_AWB_MODE_PROC 8 +#define MVS_VOC_SET_FRAME_RATE_PROC 10 +#define MVS_GSM_SET_DTX_MODE_PROC 11 +#define MVS_G729A_SET_MODE_PROC 12 +#define MVS_G711_GET_MODE_PROC 14 +#define MVS_G711_SET_MODE_PROC 15 +#define MVS_G711A_GET_MODE_PROC 16 +#define MVS_G711A_SET_MODE_PROC 17 +#define MVS_G722_SET_MODE_PROC 20 +#define MVS_G722_GET_MODE_PROC 21 +#define MVS_SET_DTX_MODE_PROC 22 + +#define MVS_EVENT_CB_TYPE_PROC 1 +#define MVS_PACKET_UL_FN_TYPE_PROC 2 +#define MVS_PACKET_DL_FN_TYPE_PROC 3 + +#define MVS_CB_FUNC_ID 0xAAAABBBB +#define MVS_UL_CB_FUNC_ID 0xBBBBCCCC +#define MVS_DL_CB_FUNC_ID 0xCCCCDDDD + +#define MVS_FRAME_MODE_VOC_TX 1 +#define MVS_FRAME_MODE_VOC_RX 2 +#define MVS_FRAME_MODE_AMR_UL 3 +#define MVS_FRAME_MODE_AMR_DL 4 +#define MVS_FRAME_MODE_GSM_UL 5 +#define MVS_FRAME_MODE_GSM_DL 6 +#define MVS_FRAME_MODE_HR_UL 7 +#define MVS_FRAME_MODE_HR_DL 8 +#define MVS_FRAME_MODE_G711_UL 9 +#define MVS_FRAME_MODE_G711_DL 10 +#define MVS_FRAME_MODE_PCM_UL 13 +#define MVS_FRAME_MODE_PCM_DL 14 +#define MVS_FRAME_MODE_G729A_UL 17 +#define MVS_FRAME_MODE_G729A_DL 18 +#define MVS_FRAME_MODE_G711A_UL 19 +#define MVS_FRAME_MODE_G711A_DL 20 +#define MVS_FRAME_MODE_G722_UL 21 +#define MVS_FRAME_MODE_G722_DL 22 + + + +#define MVS_PKT_CONTEXT_ISR 0x00000001 + +#define RPC_TYPE_REQUEST 0 +#define RPC_TYPE_REPLY 1 + +#define RPC_STATUS_FAILURE 0 +#define RPC_STATUS_SUCCESS 1 +#define RPC_STATUS_REJECT 1 + +#define RPC_COMMON_HDR_SZ (sizeof(uint32_t) * 2) +#define RPC_REQUEST_HDR_SZ (sizeof(struct rpc_request_hdr)) +#define RPC_REPLY_HDR_SZ (sizeof(uint32_t) * 3) + +enum audio_mvs_state_type { + AUDIO_MVS_CLOSED, + AUDIO_MVS_OPENED, + AUDIO_MVS_STARTED, + AUDIO_MVS_STOPPED +}; + +enum audio_mvs_event_type { + AUDIO_MVS_COMMAND, + AUDIO_MVS_MODE, + AUDIO_MVS_NOTIFY +}; + +enum audio_mvs_cmd_status_type { + AUDIO_MVS_CMD_FAILURE, + AUDIO_MVS_CMD_BUSY, + AUDIO_MVS_CMD_SUCCESS +}; + +enum audio_mvs_mode_status_type { + AUDIO_MVS_MODE_NOT_AVAIL, + AUDIO_MVS_MODE_INIT, + AUDIO_MVS_MODE_READY +}; + +enum audio_mvs_pkt_status_type { + AUDIO_MVS_PKT_NORMAL, + AUDIO_MVS_PKT_FAST, + AUDIO_MVS_PKT_SLOW +}; + +/* Parameters required for MVS acquire. */ +struct rpc_audio_mvs_acquire_args { + uint32_t client_id; + uint32_t cb_func_id; +}; + +struct audio_mvs_acquire_msg { + struct rpc_request_hdr rpc_hdr; + struct rpc_audio_mvs_acquire_args acquire_args; +}; + +/* Parameters required for MVS enable. */ +struct rpc_audio_mvs_enable_args { + uint32_t client_id; + uint32_t mode; + uint32_t ul_cb_func_id; + uint32_t dl_cb_func_id; + uint32_t context; +}; + +struct audio_mvs_enable_msg { + struct rpc_request_hdr rpc_hdr; + struct rpc_audio_mvs_enable_args enable_args; +}; + +/* Parameters required for MVS release. */ +struct audio_mvs_release_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t client_id; +}; + +/* Parameters required for setting AMR mode. */ +struct audio_mvs_set_amr_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t amr_mode; +}; + +/* Parameters required for setting DTX. */ +struct audio_mvs_set_dtx_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t dtx_mode; +}; + +/* Parameters required for setting EVRC mode. */ +struct audio_mvs_set_voc_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t max_rate; + uint32_t min_rate; +}; + +/* Parameters for G711 mode */ +struct audio_mvs_set_g711_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t g711_mode; +}; + +/* Parameters for G729 mode */ +struct audio_mvs_set_g729_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t g729_mode; +}; + +/* Parameters for G722 mode */ +struct audio_mvs_set_g722_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t g722_mode; +}; + + +/* Parameters for G711A mode */ +struct audio_mvs_set_g711A_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t g711A_mode; +}; + +/* Parameters for EFR FR and HR mode */ +struct audio_mvs_set_efr_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t efr_mode; +}; + +union audio_mvs_event_data { + struct mvs_ev_command_type { + uint32_t event; + uint32_t client_id; + uint32_t cmd_status; + } mvs_ev_command_type; + + struct mvs_ev_mode_type { + uint32_t event; + uint32_t client_id; + uint32_t mode_status; + uint32_t mode; + } mvs_ev_mode_type; + + struct mvs_ev_notify_type { + uint32_t event; + uint32_t client_id; + uint32_t buf_dir; + uint32_t max_frames; + } mvs_ev_notify_type; +}; + +struct audio_mvs_cb_func_args { + uint32_t cb_func_id; + uint32_t valid_ptr; + uint32_t event; + union audio_mvs_event_data event_data; +}; + +struct audio_mvs_frame_info_hdr { + uint32_t frame_mode; + uint32_t mvs_mode; + uint16_t buf_free_cnt; +}; + +struct audio_mvs_ul_reply { + struct rpc_reply_hdr reply_hdr; + uint32_t valid_pkt_status_ptr; + uint32_t pkt_status; +}; + +struct audio_mvs_dl_cb_func_args { + uint32_t cb_func_id; + + uint32_t valid_ptr; + uint32_t frame_mode; + uint32_t frame_mode_ignore; + + struct audio_mvs_frame_info_hdr frame_info_hdr; + + uint32_t amr_frame; + uint32_t amr_mode; +}; +/*general codec parameters includes AMR, G711A, PCM +G729, VOC and HR vocoders +*/ +struct gnr_cdc_param { + uint32_t param1; + uint32_t param2; + uint32_t valid_pkt_status_ptr; + uint32_t pkt_status; +}; +/*G711 codec parameter*/ +struct g711_param { + uint32_t param1; + uint32_t valid_pkt_status_ptr; + uint32_t pkt_status; +}; + +union codec_param { + struct gnr_cdc_param gnr_arg; + struct g711_param g711_arg; +}; + +struct audio_mvs_dl_reply { + struct rpc_reply_hdr reply_hdr; + + uint32_t voc_pkt[MVS_MAX_VOC_PKT_SIZE/4]; + + uint32_t valid_frame_info_ptr; + uint32_t frame_mode; + uint32_t frame_mode_again; + + struct audio_mvs_frame_info_hdr frame_info_hdr; + union codec_param cdc_param; +}; + +struct audio_mvs_buf_node { + struct list_head list; + struct msm_audio_mvs_frame frame; +}; + +/* Each buffer is 20 ms, queue holds 200 ms of data. */ +#define MVS_MAX_Q_LEN 10 + +struct audio_mvs_info_type { + enum audio_mvs_state_type state; + uint32_t frame_mode; + uint32_t mvs_mode; + uint32_t buf_free_cnt; + uint32_t rate_type; + uint32_t dtx_mode; + + struct msm_rpc_endpoint *rpc_endpt; + uint32_t rpc_prog; + uint32_t rpc_ver; + uint32_t rpc_status; + + uint8_t *mem_chunk; + + struct list_head in_queue; + struct list_head free_in_queue; + + struct list_head out_queue; + struct list_head free_out_queue; + + struct task_struct *task; + + wait_queue_head_t wait; + wait_queue_head_t mode_wait; + wait_queue_head_t out_wait; + + struct mutex lock; + struct mutex in_lock; + struct mutex out_lock; + + struct wake_lock suspend_lock; + struct wake_lock idle_lock; +}; + +static struct audio_mvs_info_type audio_mvs_info; + +static int audio_mvs_setup_mode(struct audio_mvs_info_type *audio) +{ + int rc = 0; + + pr_debug("%s:\n", __func__); + + switch (audio->mvs_mode) { + case MVS_MODE_AMR: + case MVS_MODE_AMR_WB: { + struct audio_mvs_set_amr_mode_msg set_amr_mode_msg; + struct audio_mvs_set_dtx_mode_msg set_dtx_mode_msg; + + /* Set AMR mode. */ + memset(&set_amr_mode_msg, 0, sizeof(set_amr_mode_msg)); + set_amr_mode_msg.amr_mode = cpu_to_be32(audio->rate_type); + + if (audio->mvs_mode == MVS_MODE_AMR) { + msm_rpc_setup_req(&set_amr_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_AMR_SET_AMR_MODE_PROC); + } else { + msm_rpc_setup_req(&set_amr_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_AMR_SET_AWB_MODE_PROC); + } + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &set_amr_mode_msg, + sizeof(set_amr_mode_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for set amr mode done\n", + __func__); + + /* Save the MVS configuration information. */ + audio->frame_mode = MVS_FRAME_MODE_AMR_DL; + + /* Disable DTX. */ + memset(&set_dtx_mode_msg, 0, sizeof(set_dtx_mode_msg)); + set_dtx_mode_msg.dtx_mode = cpu_to_be32(0); + + msm_rpc_setup_req(&set_dtx_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_SET_DTX_MODE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &set_dtx_mode_msg, + sizeof(set_dtx_mode_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for set dtx done\n", + __func__); + + rc = 0; + } + } else { + pr_err("%s: RPC write for set amr mode failed %d\n", + __func__, rc); + } + break; + } + case MVS_MODE_PCM: + case MVS_MODE_LINEAR_PCM: { + /* PCM does not have any params to be set. + Save the MVS configuration information. */ + audio->rate_type = MVS_AMR_MODE_UNDEF; + audio->frame_mode = MVS_FRAME_MODE_PCM_DL; + break; + } + case MVS_MODE_IS127: + case MVS_MODE_IS733: + case MVS_MODE_4GV_NB: + case MVS_MODE_4GV_WB: { + struct audio_mvs_set_voc_mode_msg set_voc_mode_msg; + + /* Set EVRC mode. */ + memset(&set_voc_mode_msg, 0, sizeof(set_voc_mode_msg)); + set_voc_mode_msg.min_rate = cpu_to_be32(audio->rate_type); + set_voc_mode_msg.max_rate = cpu_to_be32(audio->rate_type); + + msm_rpc_setup_req(&set_voc_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_VOC_SET_FRAME_RATE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &set_voc_mode_msg, + sizeof(set_voc_mode_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for set voc mode done\n", + __func__); + + /* Save the MVS configuration information. */ + audio->frame_mode = MVS_FRAME_MODE_VOC_RX; + + rc = 0; + } else { + pr_err("%s: RPC write for set voc mode failed %d\n", + __func__, rc); + } + break; + } + case MVS_MODE_G711: { + struct audio_mvs_set_g711_mode_msg set_g711_mode_msg; + + /* Set G711 mode. */ + memset(&set_g711_mode_msg, 0, sizeof(set_g711_mode_msg)); + set_g711_mode_msg.g711_mode = cpu_to_be32(audio->rate_type); + + pr_debug("%s: mode of g711:%d\n", + __func__, set_g711_mode_msg.g711_mode); + + msm_rpc_setup_req(&set_g711_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_G711_SET_MODE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &set_g711_mode_msg, + sizeof(set_g711_mode_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for set g711 mode done\n", + __func__); + /* Save the MVS configuration information. */ + audio->frame_mode = MVS_FRAME_MODE_G711_DL; + + rc = 0; + } else { + pr_err("%s: RPC write for set g711 mode failed %d\n", + __func__, rc); + } + break; + } + case MVS_MODE_G729A: { + struct audio_mvs_set_g729_mode_msg set_g729_mode_msg; + + /* Set G729 mode. */ + memset(&set_g729_mode_msg, 0, sizeof(set_g729_mode_msg)); + set_g729_mode_msg.g729_mode = cpu_to_be32(audio->dtx_mode); + + pr_debug("%s: mode of g729:%d\n", + __func__, set_g729_mode_msg.g729_mode); + + msm_rpc_setup_req(&set_g729_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_G729A_SET_MODE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &set_g729_mode_msg, + sizeof(set_g729_mode_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for set g729 mode done\n", + __func__); + + /* Save the MVS configuration information. */ + audio->frame_mode = MVS_FRAME_MODE_G729A_DL; + + rc = 0; + } else { + pr_err("%s: RPC write for set g729 mode failed %d\n", + __func__, rc); + } + break; + } + case MVS_MODE_G722: { + struct audio_mvs_set_g722_mode_msg set_g722_mode_msg; + + /* Set G722 mode. */ + memset(&set_g722_mode_msg, 0, sizeof(set_g722_mode_msg)); + set_g722_mode_msg.g722_mode = cpu_to_be32(audio->rate_type); + + pr_debug("%s: mode of g722:%d\n", + __func__, set_g722_mode_msg.g722_mode); + + msm_rpc_setup_req(&set_g722_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_G722_SET_MODE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &set_g722_mode_msg, + sizeof(set_g722_mode_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for set g722 mode done\n", + __func__); + + /* Save the MVS configuration information. */ + audio->frame_mode = MVS_FRAME_MODE_G722_DL; + + rc = 0; + } + break; + } + case MVS_MODE_G711A: { + struct audio_mvs_set_g711A_mode_msg set_g711A_mode_msg; + struct audio_mvs_set_dtx_mode_msg set_dtx_mode_msg; + + /* Set G711A mode. */ + memset(&set_g711A_mode_msg, 0, sizeof(set_g711A_mode_msg)); + set_g711A_mode_msg.g711A_mode = cpu_to_be32(audio->rate_type); + + pr_debug("%s: mode of g711A:%d\n", + __func__, set_g711A_mode_msg.g711A_mode); + + msm_rpc_setup_req(&set_g711A_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_G711A_SET_MODE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &set_g711A_mode_msg, + sizeof(set_g711A_mode_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for set g711A mode done\n", + __func__); + + /* Save the MVS configuration information. */ + audio->frame_mode = MVS_FRAME_MODE_G711A_DL; + /* Set DTX MODE. */ + memset(&set_dtx_mode_msg, 0, sizeof(set_dtx_mode_msg)); + set_dtx_mode_msg.dtx_mode = + cpu_to_be32((audio->dtx_mode)); + + msm_rpc_setup_req(&set_dtx_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_SET_DTX_MODE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &set_dtx_mode_msg, + sizeof(set_dtx_mode_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for set dtx done\n", + __func__); + + rc = 0; + } + rc = 0; + } else { + pr_err("%s: RPC write for set g711A mode failed %d\n", + __func__, rc); + } + break; + } + case MVS_MODE_EFR: + case MVS_MODE_FR: + case MVS_MODE_HR: { + struct audio_mvs_set_efr_mode_msg set_efr_mode_msg; + + /* Set G729 mode. */ + memset(&set_efr_mode_msg, 0, sizeof(set_efr_mode_msg)); + set_efr_mode_msg.efr_mode = cpu_to_be32(audio->dtx_mode); + + pr_debug("%s: mode of EFR, FR and HR:%d\n", + __func__, set_efr_mode_msg.efr_mode); + + msm_rpc_setup_req(&set_efr_mode_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_GSM_SET_DTX_MODE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &set_efr_mode_msg, + sizeof(set_efr_mode_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for set EFR, FR and HR mode done\n", + __func__); + + /* Save the MVS configuration information. */ + if ((audio->mvs_mode == MVS_MODE_EFR) || + (audio->mvs_mode == MVS_MODE_FR)) + audio->frame_mode = MVS_FRAME_MODE_GSM_DL; + if (audio->mvs_mode == MVS_MODE_HR) + audio->frame_mode = MVS_FRAME_MODE_HR_DL; + + rc = 0; + } else { + pr_err("%s: RPC write for set EFR, FR and HR mode failed %d\n", + __func__, rc); + } + break; + } + default: + rc = -EINVAL; + pr_err("Default case\n"); + } + return rc; +} + +static int audio_mvs_setup(struct audio_mvs_info_type *audio) +{ + int rc = 0; + struct audio_mvs_enable_msg enable_msg; + + pr_debug("%s:\n", __func__); + + /* Enable MVS. */ + memset(&enable_msg, 0, sizeof(enable_msg)); + enable_msg.enable_args.client_id = cpu_to_be32(MVS_CLIENT_ID_VOIP); + enable_msg.enable_args.mode = cpu_to_be32(audio->mvs_mode); + enable_msg.enable_args.ul_cb_func_id = cpu_to_be32(MVS_UL_CB_FUNC_ID); + enable_msg.enable_args.dl_cb_func_id = cpu_to_be32(MVS_DL_CB_FUNC_ID); + enable_msg.enable_args.context = cpu_to_be32(MVS_PKT_CONTEXT_ISR); + + msm_rpc_setup_req(&enable_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_ENABLE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, &enable_msg, sizeof(enable_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for enable done\n", __func__); + + rc = wait_event_timeout(audio->mode_wait, + (audio->rpc_status != RPC_STATUS_FAILURE), + 10 * HZ); + + if (rc > 0) { + pr_debug("%s: Wait event for enable succeeded\n", + __func__); + rc = audio_mvs_setup_mode(audio); + if (rc < 0) { + pr_err("%s: Unknown MVS mode %d\n", + __func__, audio->mvs_mode); + } + pr_err("rc value after mode setup: %d\n", rc); + } else { + pr_err("%s: Wait event for enable failed %d\n", + __func__, rc); + } + } else { + pr_err("%s: RPC write for enable failed %d\n", __func__, rc); + } + + return rc; +} + +static int audio_mvs_start(struct audio_mvs_info_type *audio) +{ + int rc = 0; + struct audio_mvs_acquire_msg acquire_msg; + + pr_info("%s:\n", __func__); + + /* Prevent sleep. */ + wake_lock(&audio->suspend_lock); + wake_lock(&audio->idle_lock); + + /* Acquire MVS. */ + memset(&acquire_msg, 0, sizeof(acquire_msg)); + acquire_msg.acquire_args.client_id = cpu_to_be32(MVS_CLIENT_ID_VOIP); + acquire_msg.acquire_args.cb_func_id = cpu_to_be32(MVS_CB_FUNC_ID); + + msm_rpc_setup_req(&acquire_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_ACQUIRE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &acquire_msg, + sizeof(acquire_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for acquire done\n", __func__); + + rc = wait_event_timeout(audio->wait, + (audio->rpc_status != RPC_STATUS_FAILURE), + 1 * HZ); + + if (rc > 0) { + + rc = audio_mvs_setup(audio); + + if (rc == 0) + audio->state = AUDIO_MVS_STARTED; + + } else { + pr_err("%s: Wait event for acquire failed %d\n", + __func__, rc); + + rc = -EBUSY; + } + } else { + pr_err("%s: RPC write for acquire failed %d\n", __func__, rc); + + rc = -EBUSY; + } + + return rc; +} + +static int audio_mvs_stop(struct audio_mvs_info_type *audio) +{ + int rc = 0; + struct audio_mvs_release_msg release_msg; + + pr_info("%s:\n", __func__); + + /* Release MVS. */ + memset(&release_msg, 0, sizeof(release_msg)); + release_msg.client_id = cpu_to_be32(MVS_CLIENT_ID_VOIP); + + msm_rpc_setup_req(&release_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_RELEASE_PROC); + + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, &release_msg, sizeof(release_msg)); + + if (rc >= 0) { + pr_debug("%s: RPC write for release done\n", __func__); + + rc = wait_event_timeout(audio->mode_wait, + (audio->rpc_status != RPC_STATUS_FAILURE), + 1 * HZ); + + if (rc > 0) { + pr_debug("%s: Wait event for release succeeded\n", + __func__); + + audio->state = AUDIO_MVS_STOPPED; + + /* Un-block read in case it is waiting for data. */ + wake_up(&audio->out_wait); + rc = 0; + } else { + pr_err("%s: Wait event for release failed %d\n", + __func__, rc); + } + } else { + pr_err("%s: RPC write for release failed %d\n", __func__, rc); + } + + /* Allow sleep. */ + wake_unlock(&audio->suspend_lock); + wake_unlock(&audio->idle_lock); + + return rc; +} + +static void audio_mvs_process_rpc_request(uint32_t procedure, + uint32_t xid, + void *data, + uint32_t length, + struct audio_mvs_info_type *audio) +{ + int rc = 0; + + pr_debug("%s:\n", __func__); + + switch (procedure) { + case MVS_EVENT_CB_TYPE_PROC: { + struct audio_mvs_cb_func_args *args = data; + struct rpc_reply_hdr reply_hdr; + + pr_debug("%s: MVS CB CB_FUNC_ID 0x%x\n", + __func__, be32_to_cpu(args->cb_func_id)); + + if (be32_to_cpu(args->valid_ptr)) { + uint32_t event_type = be32_to_cpu(args->event); + + pr_debug("%s: MVS CB event type %d\n", + __func__, be32_to_cpu(args->event)); + + if (event_type == AUDIO_MVS_COMMAND) { + uint32_t cmd_status = be32_to_cpu( + args->event_data.mvs_ev_command_type.cmd_status); + + pr_debug("%s: MVS CB command status %d\n", + __func__, cmd_status); + + if (cmd_status == AUDIO_MVS_CMD_SUCCESS) { + audio->rpc_status = RPC_STATUS_SUCCESS; + wake_up(&audio->wait); + } + + } else if (event_type == AUDIO_MVS_MODE) { + uint32_t mode_status = be32_to_cpu( + args->event_data.mvs_ev_mode_type.mode_status); + + pr_debug("%s: MVS CB mode status %d\n", + __func__, mode_status); + + if (mode_status == AUDIO_MVS_MODE_READY) { + audio->rpc_status = RPC_STATUS_SUCCESS; + wake_up(&audio->mode_wait); + } + } else { + pr_err("%s: MVS CB unknown event type %d\n", + __func__, event_type); + } + } else { + pr_err("%s: MVS CB event pointer not valid\n", + __func__); + } + + /* Send ack to modem. */ + memset(&reply_hdr, 0, sizeof(reply_hdr)); + reply_hdr.xid = cpu_to_be32(xid); + reply_hdr.type = cpu_to_be32(RPC_TYPE_REPLY); + reply_hdr.reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + + reply_hdr.data.acc_hdr.accept_stat = cpu_to_be32( + RPC_ACCEPTSTAT_SUCCESS); + reply_hdr.data.acc_hdr.verf_flavor = 0; + reply_hdr.data.acc_hdr.verf_length = 0; + + rc = msm_rpc_write(audio->rpc_endpt, + &reply_hdr, + sizeof(reply_hdr)); + + if (rc < 0) + pr_err("%s: RPC write for response failed %d\n", + __func__, rc); + + break; + } + + case MVS_PACKET_UL_FN_TYPE_PROC: { + uint32_t *args = data; + uint32_t pkt_len; + uint32_t frame_mode; + struct audio_mvs_ul_reply ul_reply; + struct audio_mvs_buf_node *buf_node = NULL; + + pr_debug("%s: MVS UL CB_FUNC_ID 0x%x\n", + __func__, be32_to_cpu(*args)); + args++; + + pkt_len = be32_to_cpu(*args); + pr_debug("%s: UL pkt_len %d\n", __func__, pkt_len); + args++; + + /* Copy the vocoder packets. */ + mutex_lock(&audio->out_lock); + + if (!list_empty(&audio->free_out_queue)) { + buf_node = list_first_entry(&audio->free_out_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + memcpy(&buf_node->frame.voc_pkt[0], args, pkt_len); + buf_node->frame.len = pkt_len; + pkt_len = ALIGN(pkt_len, 4); + args = args + pkt_len/4; + + pr_debug("%s: UL valid_ptr 0x%x\n", + __func__, be32_to_cpu(*args)); + args++; + + frame_mode = be32_to_cpu(*args); + pr_debug("%s: UL frame_mode %d\n", + __func__, frame_mode); + args++; + + pr_debug("%s: UL frame_mode %d\n", + __func__, be32_to_cpu(*args)); + args++; + + pr_debug("%s: UL frame_mode %d\n", + __func__, be32_to_cpu(*args)); + args++; + + pr_debug("%s: UL mvs_mode %d\n", + __func__, be32_to_cpu(*args)); + args++; + + pr_debug("%s: UL buf_free_cnt %d\n", + __func__, be32_to_cpu(*args)); + args++; + + if (frame_mode == MVS_FRAME_MODE_AMR_UL) { + /* Extract AMR frame type. */ + buf_node->frame.frame_type = be32_to_cpu(*args); + + pr_debug("%s: UL AMR frame_type %d\n", + __func__, be32_to_cpu(*args)); + } else if ((frame_mode == MVS_FRAME_MODE_PCM_UL) || + (frame_mode == MVS_FRAME_MODE_VOC_TX)) { + /* PCM and EVRC don't have frame_type */ + buf_node->frame.frame_type = 0; + } else if (frame_mode == MVS_FRAME_MODE_G711_UL) { + /* Extract G711 frame type. */ + buf_node->frame.frame_type = be32_to_cpu(*args); + + pr_debug("%s: UL G711 frame_type %d\n", + __func__, be32_to_cpu(*args)); + } else if (frame_mode == MVS_FRAME_MODE_G729A_UL) { + /* Extract G729 frame type. */ + buf_node->frame.frame_type = be32_to_cpu(*args); + + pr_debug("%s: UL G729 frame_type %d\n", + __func__, be32_to_cpu(*args)); + } else if (frame_mode == MVS_FRAME_MODE_G722_UL) { + /* Extract G722 frame type. */ + buf_node->frame.frame_type = be32_to_cpu(*args); + + pr_debug("%s: UL G722 frame_type %d\n", + __func__, be32_to_cpu(*args)); + } else if (frame_mode == MVS_FRAME_MODE_G711A_UL) { + /* Extract G711A frame type. */ + buf_node->frame.frame_type = be32_to_cpu(*args); + + pr_debug("%s: UL G711A frame_type %d\n", + __func__, be32_to_cpu(*args)); + } else if ((frame_mode == MVS_FRAME_MODE_GSM_UL) || + (frame_mode == MVS_FRAME_MODE_HR_UL)) { + /* Extract EFR, FR and HR frame type. */ + buf_node->frame.frame_type = be32_to_cpu(*args); + + pr_debug("%s: UL EFR,FR,HR frame_type %d\n", + __func__, be32_to_cpu(*args)); + } else { + pr_debug("%s: UL Unknown frame mode %d\n", + __func__, frame_mode); + } + + list_add_tail(&buf_node->list, &audio->out_queue); + } else { + pr_err("%s: UL data dropped, read is slow\n", __func__); + } + + mutex_unlock(&audio->out_lock); + + wake_up(&audio->out_wait); + + /* Send UL message accept to modem. */ + memset(&ul_reply, 0, sizeof(ul_reply)); + ul_reply.reply_hdr.xid = cpu_to_be32(xid); + ul_reply.reply_hdr.type = cpu_to_be32(RPC_TYPE_REPLY); + ul_reply.reply_hdr.reply_stat = cpu_to_be32( + RPCMSG_REPLYSTAT_ACCEPTED); + + ul_reply.reply_hdr.data.acc_hdr.accept_stat = cpu_to_be32( + RPC_ACCEPTSTAT_SUCCESS); + ul_reply.reply_hdr.data.acc_hdr.verf_flavor = 0; + ul_reply.reply_hdr.data.acc_hdr.verf_length = 0; + + ul_reply.valid_pkt_status_ptr = cpu_to_be32(0x00000001); + ul_reply.pkt_status = cpu_to_be32(0x00000000); + + rc = msm_rpc_write(audio->rpc_endpt, + &ul_reply, + sizeof(ul_reply)); + + if (rc < 0) + pr_err("%s: RPC write for UL response failed %d\n", + __func__, rc); + + break; + } + + case MVS_PACKET_DL_FN_TYPE_PROC: { + struct audio_mvs_dl_cb_func_args *args = data; + struct audio_mvs_dl_reply dl_reply; + uint32_t frame_mode; + struct audio_mvs_buf_node *buf_node = NULL; + + pr_debug("%s: MVS DL CB CB_FUNC_ID 0x%x\n", + __func__, be32_to_cpu(args->cb_func_id)); + + frame_mode = be32_to_cpu(args->frame_mode); + pr_debug("%s: DL frame_mode %d\n", __func__, frame_mode); + + /* Prepare and send the DL packets to modem. */ + memset(&dl_reply, 0, sizeof(dl_reply)); + dl_reply.reply_hdr.xid = cpu_to_be32(xid); + dl_reply.reply_hdr.type = cpu_to_be32(RPC_TYPE_REPLY); + dl_reply.reply_hdr.reply_stat = cpu_to_be32( + RPCMSG_REPLYSTAT_ACCEPTED); + + dl_reply.reply_hdr.data.acc_hdr.accept_stat = cpu_to_be32( + RPC_ACCEPTSTAT_SUCCESS); + dl_reply.reply_hdr.data.acc_hdr.verf_flavor = 0; + dl_reply.reply_hdr.data.acc_hdr.verf_length = 0; + + mutex_lock(&audio->in_lock); + + if (!list_empty(&audio->in_queue)) { + buf_node = list_first_entry(&audio->in_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + memcpy(&dl_reply.voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + + pr_debug("%s:frame mode %d\n", __func__, frame_mode); + if (frame_mode == MVS_FRAME_MODE_AMR_DL) { + dl_reply.cdc_param.gnr_arg.param1 = cpu_to_be32( + buf_node->frame.frame_type); + dl_reply.cdc_param.gnr_arg.param2 = + cpu_to_be32(audio->rate_type); + dl_reply.cdc_param.\ + gnr_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.gnr_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + } else if (frame_mode == MVS_FRAME_MODE_PCM_DL) { + dl_reply.cdc_param.gnr_arg.param1 = 0; + dl_reply.cdc_param.gnr_arg.param2 = 0; + dl_reply.cdc_param.\ + gnr_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.gnr_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + } else if (frame_mode == MVS_FRAME_MODE_VOC_RX) { + dl_reply.cdc_param.gnr_arg.param1 = + cpu_to_be32(audio->rate_type); + dl_reply.cdc_param.gnr_arg.param2 = 0; + dl_reply.cdc_param.\ + gnr_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.gnr_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + } else if (frame_mode == MVS_FRAME_MODE_G711_DL) { + dl_reply.cdc_param.g711_arg.param1 = + cpu_to_be32(buf_node->frame.frame_type); + dl_reply.cdc_param.\ + g711_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.g711_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + } else if (frame_mode == MVS_FRAME_MODE_G729A_DL) { + dl_reply.cdc_param.gnr_arg.param1 = cpu_to_be32( + buf_node->frame.frame_type); + dl_reply.cdc_param.gnr_arg.param2 = + cpu_to_be32(audio->rate_type); + dl_reply.cdc_param.\ + gnr_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.gnr_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + } else if (frame_mode == MVS_FRAME_MODE_G722_DL) { + dl_reply.cdc_param.gnr_arg.param1 = cpu_to_be32( + buf_node->frame.frame_type); + dl_reply.cdc_param.gnr_arg.param2 = + cpu_to_be32(audio->rate_type); + dl_reply.cdc_param.\ + gnr_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.gnr_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + } else if (frame_mode == MVS_FRAME_MODE_G711A_DL) { + dl_reply.cdc_param.gnr_arg.param1 = cpu_to_be32( + buf_node->frame.frame_type); + dl_reply.cdc_param.gnr_arg.param2 = + cpu_to_be32(audio->rate_type); + dl_reply.cdc_param.\ + gnr_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.gnr_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + } else if ((frame_mode == MVS_FRAME_MODE_GSM_DL) || + (frame_mode == MVS_FRAME_MODE_HR_DL)) { + dl_reply.cdc_param.gnr_arg.param1 = cpu_to_be32( + buf_node->frame.frame_type); + dl_reply.cdc_param.gnr_arg.param2 = + cpu_to_be32(audio->rate_type); + dl_reply.cdc_param.\ + gnr_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.gnr_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + } else { + pr_err("%s: DL Unknown frame mode %d\n", + __func__, frame_mode); + } + list_add_tail(&buf_node->list, &audio->free_in_queue); + } else { + pr_debug("%s: No DL data available to send to MVS\n", + __func__); + if (frame_mode == MVS_FRAME_MODE_G711_DL) { + dl_reply.cdc_param.\ + g711_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.g711_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_SLOW); + } else { + dl_reply.cdc_param.\ + gnr_arg.valid_pkt_status_ptr = + cpu_to_be32(0x00000001); + dl_reply.cdc_param.gnr_arg.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_SLOW); + } + } + + mutex_unlock(&audio->in_lock); + + dl_reply.valid_frame_info_ptr = cpu_to_be32(0x00000001); + + dl_reply.frame_mode = cpu_to_be32(audio->frame_mode); + dl_reply.frame_mode_again = cpu_to_be32(audio->frame_mode); + + dl_reply.frame_info_hdr.frame_mode = + cpu_to_be32(audio->frame_mode); + dl_reply.frame_info_hdr.mvs_mode = cpu_to_be32(audio->mvs_mode); + dl_reply.frame_info_hdr.buf_free_cnt = 0; + + rc = msm_rpc_write(audio->rpc_endpt, + &dl_reply, + sizeof(dl_reply)); + + if (rc < 0) + pr_err("%s: RPC write for DL response failed %d\n", + __func__, rc); + + break; + } + + default: + pr_err("%s: Unknown CB type %d\n", __func__, procedure); + } +} + +static int audio_mvs_thread(void *data) +{ + struct audio_mvs_info_type *audio = data; + struct rpc_request_hdr *rpc_hdr = NULL; + + pr_info("%s:\n", __func__); + + while (!kthread_should_stop()) { + + int rpc_hdr_len = msm_rpc_read(audio->rpc_endpt, + (void **) &rpc_hdr, + -1, + -1); + + if (rpc_hdr_len < 0) { + pr_err("%s: RPC read failed %d\n", + __func__, rpc_hdr_len); + + break; + } else if (rpc_hdr_len < RPC_COMMON_HDR_SZ) { + continue; + } else { + uint32_t rpc_type = be32_to_cpu(rpc_hdr->type); + if (rpc_type == RPC_TYPE_REPLY) { + struct rpc_reply_hdr *rpc_reply = + (void *) rpc_hdr; + uint32_t reply_status; + + if (rpc_hdr_len < RPC_REPLY_HDR_SZ) + continue; + + reply_status = + be32_to_cpu(rpc_reply->reply_stat); + + if (reply_status != RPCMSG_REPLYSTAT_ACCEPTED) { + /* If the command is not accepted, there + * will be no response callback. Wake + * the caller and report error. */ + audio->rpc_status = RPC_STATUS_REJECT; + + wake_up(&audio->wait); + + pr_err("%s: RPC reply status denied\n", + __func__); + } + } else if (rpc_type == RPC_TYPE_REQUEST) { + if (rpc_hdr_len < RPC_REQUEST_HDR_SZ) + continue; + + audio_mvs_process_rpc_request( + be32_to_cpu(rpc_hdr->procedure), + be32_to_cpu(rpc_hdr->xid), + (void *) (rpc_hdr + 1), + (rpc_hdr_len - sizeof(*rpc_hdr)), + audio); + } else { + pr_err("%s: Unexpected RPC type %d\n", + __func__, rpc_type); + } + } + + kfree(rpc_hdr); + rpc_hdr = NULL; + } + + pr_info("%s: MVS thread stopped\n", __func__); + + return 0; +} + +static int audio_mvs_alloc_buf(struct audio_mvs_info_type *audio) +{ + int i = 0; + struct audio_mvs_buf_node *buf_node = NULL; + struct list_head *ptr = NULL; + struct list_head *next = NULL; + + pr_debug("%s:\n", __func__); + + /* Allocate input buffers. */ + for (i = 0; i < MVS_MAX_Q_LEN; i++) { + buf_node = kmalloc(sizeof(struct audio_mvs_buf_node), + GFP_KERNEL); + + if (buf_node != NULL) { + list_add_tail(&buf_node->list, + &audio->free_in_queue); + } else { + pr_err("%s: No memory for IO buffers\n", + __func__); + goto err; + } + buf_node = NULL; + } + + /* Allocate output buffers. */ + for (i = 0; i < MVS_MAX_Q_LEN; i++) { + buf_node = kmalloc(sizeof(struct audio_mvs_buf_node), + GFP_KERNEL); + + if (buf_node != NULL) { + list_add_tail(&buf_node->list, + &audio->free_out_queue); + } else { + pr_err("%s: No memory for IO buffers\n", + __func__); + goto err; + } + buf_node = NULL; + } + + return 0; + +err: + list_for_each_safe(ptr, next, &audio->free_in_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + kfree(buf_node); + buf_node = NULL; + } + + ptr = next = NULL; + list_for_each_safe(ptr, next, &audio->free_out_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + kfree(buf_node); + buf_node = NULL; + } + + return -ENOMEM; +} + +static void audio_mvs_free_buf(struct audio_mvs_info_type *audio) +{ + struct list_head *ptr = NULL; + struct list_head *next = NULL; + struct audio_mvs_buf_node *buf_node = NULL; + + pr_debug("%s:\n", __func__); + + mutex_lock(&audio->in_lock); + /* Free input buffers. */ + list_for_each_safe(ptr, next, &audio->in_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + kfree(buf_node); + buf_node = NULL; + } + + ptr = next = NULL; + /* Free free_input buffers. */ + list_for_each_safe(ptr, next, &audio->free_in_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + kfree(buf_node); + buf_node = NULL; + } + mutex_unlock(&audio->in_lock); + + mutex_lock(&audio->out_lock); + ptr = next = NULL; + /* Free output buffers. */ + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + kfree(buf_node); + buf_node = NULL; + } + + /* Free free_ioutput buffers. */ + ptr = next = NULL; + list_for_each_safe(ptr, next, &audio->free_out_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + kfree(buf_node); + buf_node = NULL; + } + mutex_unlock(&audio->out_lock); +} + +static int audio_mvs_open(struct inode *inode, struct file *file) +{ + int rc = 0; + + pr_info("%s:\n", __func__); + + mutex_lock(&audio_mvs_info.lock); + + if (audio_mvs_info.state == AUDIO_MVS_CLOSED) { + + if (audio_mvs_info.task != NULL || + audio_mvs_info.rpc_endpt != NULL) { + rc = audio_mvs_alloc_buf(&audio_mvs_info); + + if (rc == 0) { + audio_mvs_info.state = AUDIO_MVS_OPENED; + file->private_data = &audio_mvs_info; + } + } else { + pr_err("%s: MVS thread and RPC end point do not exist\n", + __func__); + + rc = -ENODEV; + } + } else { + pr_err("%s: MVS driver exists, state %d\n", + __func__, audio_mvs_info.state); + + rc = -EBUSY; + } + + mutex_unlock(&audio_mvs_info.lock); + + return rc; +} + +static int audio_mvs_release(struct inode *inode, struct file *file) +{ + + struct audio_mvs_info_type *audio = file->private_data; + + pr_info("%s:\n", __func__); + + mutex_lock(&audio->lock); + if (audio->state == AUDIO_MVS_STARTED) + audio_mvs_stop(audio); + audio_mvs_free_buf(audio); + audio->state = AUDIO_MVS_CLOSED; + mutex_unlock(&audio->lock); + + pr_debug("%s: Release done\n", __func__); + return 0; +} + +static ssize_t audio_mvs_read(struct file *file, + char __user *buf, + size_t count, + loff_t *pos) +{ + int rc = 0; + struct audio_mvs_buf_node *buf_node = NULL; + struct audio_mvs_info_type *audio = file->private_data; + + pr_debug("%s:\n", __func__); + + rc = wait_event_interruptible_timeout(audio->out_wait, + (!list_empty(&audio->out_queue) || + audio->state == AUDIO_MVS_STOPPED), + 1 * HZ); + + if (rc > 0) { + mutex_lock(&audio->out_lock); + if ((audio->state == AUDIO_MVS_STARTED) && + (!list_empty(&audio->out_queue))) { + + if (count >= sizeof(struct msm_audio_mvs_frame)) { + buf_node = list_first_entry(&audio->out_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + rc = copy_to_user(buf, + &buf_node->frame, + sizeof(struct msm_audio_mvs_frame)); + + if (rc == 0) { + rc = buf_node->frame.len + + sizeof(buf_node->frame.frame_type) + + sizeof(buf_node->frame.len); + } else { + pr_err("%s: Copy to user retuned %d", + __func__, rc); + + rc = -EFAULT; + } + + list_add_tail(&buf_node->list, + &audio->free_out_queue); + } else { + pr_err("%s: Read count %d < sizeof(frame) %d", + __func__, count, + sizeof(struct msm_audio_mvs_frame)); + + rc = -ENOMEM; + } + } else { + pr_err("%s: Read performed in state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + mutex_unlock(&audio->out_lock); + + } else if (rc == 0) { + pr_err("%s: No UL data available\n", __func__); + + rc = -ETIMEDOUT; + } else { + pr_err("%s: Read was interrupted\n", __func__); + + rc = -ERESTARTSYS; + } + + return rc; +} + +static ssize_t audio_mvs_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *pos) +{ + int rc = 0; + struct audio_mvs_buf_node *buf_node = NULL; + struct audio_mvs_info_type *audio = file->private_data; + + pr_debug("%s:\n", __func__); + + mutex_lock(&audio->in_lock); + if (audio->state == AUDIO_MVS_STARTED) { + if (count <= sizeof(struct msm_audio_mvs_frame)) { + if (!list_empty(&audio->free_in_queue)) { + buf_node = + list_first_entry(&audio->free_in_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + rc = copy_from_user(&buf_node->frame, + buf, + count); + + list_add_tail(&buf_node->list, + &audio->in_queue); + } else { + pr_err("%s: No free DL buffs\n", __func__); + } + } else { + pr_err("%s: Write count %d < sizeof(frame) %d", + __func__, count, + sizeof(struct msm_audio_mvs_frame)); + + rc = -ENOMEM; + } + } else { + pr_err("%s: Write performed in invalid state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + mutex_unlock(&audio->in_lock); + + return rc; +} + +static long audio_mvs_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + int rc = 0; + + struct audio_mvs_info_type *audio = file->private_data; + + pr_info("%s:\n", __func__); + + switch (cmd) { + case AUDIO_GET_MVS_CONFIG: { + struct msm_audio_mvs_config config; + + pr_debug("%s: IOCTL GET_MVS_CONFIG\n", __func__); + + mutex_lock(&audio->lock); + config.mvs_mode = audio->mvs_mode; + config.rate_type = audio->rate_type; + mutex_unlock(&audio->lock); + + rc = copy_to_user((void *)arg, &config, sizeof(config)); + if (rc == 0) + rc = sizeof(config); + else + pr_err("%s: Config copy failed %d\n", __func__, rc); + + break; + } + + case AUDIO_SET_MVS_CONFIG: { + struct msm_audio_mvs_config config; + + pr_debug("%s: IOCTL SET_MVS_CONFIG\n", __func__); + + rc = copy_from_user(&config, (void *)arg, sizeof(config)); + if (rc == 0) { + mutex_lock(&audio->lock); + + if (audio->state == AUDIO_MVS_OPENED) { + audio->mvs_mode = config.mvs_mode; + audio->rate_type = config.rate_type; + audio->dtx_mode = config.dtx_mode; + } else { + pr_err("%s: Set confg called in state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->lock); + } else { + pr_err("%s: Config copy failed %d\n", __func__, rc); + } + + break; + } + + case AUDIO_START: { + pr_debug("%s: IOCTL START\n", __func__); + + mutex_lock(&audio->lock); + + if (audio->state == AUDIO_MVS_OPENED || + audio->state == AUDIO_MVS_STOPPED) { + rc = audio_mvs_start(audio); + + if (rc != 0) + audio_mvs_stop(audio); + } else { + pr_err("%s: Start called in invalid state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->lock); + + break; + } + + case AUDIO_STOP: { + pr_debug("%s: IOCTL STOP\n", __func__); + + mutex_lock(&audio->lock); + + if (audio->state == AUDIO_MVS_STARTED) { + rc = audio_mvs_stop(audio); + } else { + pr_err("%s: Stop called in invalid state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->lock); + break; + } + + default: { + pr_err("%s: Unknown IOCTL %d\n", __func__, cmd); + } + } + + return rc; +} + +static const struct file_operations audio_mvs_fops = { + .owner = THIS_MODULE, + .open = audio_mvs_open, + .release = audio_mvs_release, + .read = audio_mvs_read, + .write = audio_mvs_write, + .unlocked_ioctl = audio_mvs_ioctl +}; + +struct miscdevice audio_mvs_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_mvs", + .fops = &audio_mvs_fops +}; + +static int __init audio_mvs_init(void) +{ + int rc; + + pr_info("%s:\n", __func__); + + memset(&audio_mvs_info, 0, sizeof(audio_mvs_info)); + mutex_init(&audio_mvs_info.lock); + mutex_init(&audio_mvs_info.in_lock); + mutex_init(&audio_mvs_info.out_lock); + + init_waitqueue_head(&audio_mvs_info.wait); + init_waitqueue_head(&audio_mvs_info.mode_wait); + init_waitqueue_head(&audio_mvs_info.out_wait); + + INIT_LIST_HEAD(&audio_mvs_info.in_queue); + INIT_LIST_HEAD(&audio_mvs_info.free_in_queue); + INIT_LIST_HEAD(&audio_mvs_info.out_queue); + INIT_LIST_HEAD(&audio_mvs_info.free_out_queue); + + wake_lock_init(&audio_mvs_info.suspend_lock, + WAKE_LOCK_SUSPEND, + "audio_mvs_suspend"); + wake_lock_init(&audio_mvs_info.idle_lock, + WAKE_LOCK_IDLE, + "audio_mvs_idle"); + + audio_mvs_info.rpc_endpt = msm_rpc_connect_compatible(MVS_PROG, + MVS_VERS_COMP_VER5, + MSM_RPC_UNINTERRUPTIBLE); + + if (IS_ERR(audio_mvs_info.rpc_endpt)) { + pr_err("%s: MVS RPC connect failed ver 0x%x\n", __func__, + MVS_VERS_COMP_VER5); + audio_mvs_info.rpc_endpt = msm_rpc_connect_compatible(MVS_PROG, + MVS_VERS_COMP_VER4, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(audio_mvs_info.rpc_endpt)) { + pr_err("%s: MVS RPC connect failed ver 0x%x\n", + __func__, MVS_VERS_COMP_VER4); + audio_mvs_info.rpc_endpt = + msm_rpc_connect_compatible(MVS_PROG, + MVS_VERS, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(audio_mvs_info.rpc_endpt)) { + pr_err("%s: MVS RPC connect failed ver 0x%x\n", + __func__, MVS_VERS); + rc = PTR_ERR(audio_mvs_info.rpc_endpt); + audio_mvs_info.rpc_endpt = NULL; + goto done; + } else { + pr_debug("%s: MVS RPC connect succeeded ver\ + 0x%x\n", __func__, MVS_VERS); + audio_mvs_info.rpc_prog = MVS_PROG; + audio_mvs_info.rpc_ver = MVS_VERS; + } + } else { + pr_debug("%s: MVS RPC connect succeeded ver 0x%x\n", + __func__, MVS_VERS_COMP_VER4); + audio_mvs_info.rpc_prog = MVS_PROG; + audio_mvs_info.rpc_ver = MVS_VERS_COMP_VER4; + } + } else { + pr_debug("%s: MVS RPC connect succeeded ver 0x%x\n", __func__, + MVS_VERS_COMP_VER5); + audio_mvs_info.rpc_prog = MVS_PROG; + audio_mvs_info.rpc_ver = MVS_VERS_COMP_VER5; + } + audio_mvs_info.task = kthread_run(audio_mvs_thread, + &audio_mvs_info, + "audio_mvs"); + if (IS_ERR(audio_mvs_info.task)) { + pr_err("%s: MVS thread create failed\n", __func__); + rc = PTR_ERR(audio_mvs_info.task); + audio_mvs_info.task = NULL; + msm_rpc_close(audio_mvs_info.rpc_endpt); + audio_mvs_info.rpc_endpt = NULL; + goto done; + } + + rc = misc_register(&audio_mvs_misc); +done: + return rc; +} + +static void __exit audio_mvs_exit(void) +{ + pr_info("%s:\n", __func__); + + misc_deregister(&audio_mvs_misc); +} + +module_init(audio_mvs_init); +module_exit(audio_mvs_exit); + +MODULE_DESCRIPTION("MSM MVS driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/arch/arm/mach-msm/qdsp5v2/audio_out.c b/arch/arm/mach-msm/qdsp5v2/audio_out.c new file mode 100644 index 00000000000..7c1e5ca9eac --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_out.c @@ -0,0 +1,722 @@ +/* + * pcm audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BUFSZ (960 * 5) +#define DMASZ (BUFSZ * 2) + +#define HOSTPCM_STREAM_ID 5 + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t wait; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_weight; + uint32_t out_buffer_size; + uint32_t device_events; + int16_t source; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int teos; /* valid only if tunnel mode & no data left for decoder */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + uint16_t dec_id; + int voice_state; + + struct wake_lock wakelock; + struct wake_lock idlelock; + + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static void audio_out_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + case AUDDEV_EVT_VOICE_STATE_CHG: + MM_DBG("AUDDEV_EVT_VOICE_STATE_CHG, state = %d\n", + evt_payload->voice_state); + audio->voice_state = evt_payload->voice_state; + /* Voice uplink Rx case */ + if (audio->running && + (audio->source & AUDPP_MIXER_UPLINK_RX) && + (audio->voice_state == VOICE_STATE_OFFCALL)) { + MM_DBG("Voice is terminated, Wake up write: %x %x\n", + audio->voice_state, audio->source); + wake_up(&audio->wait); + } + break; + default: + MM_ERR("ERROR:wrong event\n"); + break; + } +} + +static void audio_prevent_sleep(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + wake_lock(&audio->wakelock); + wake_lock(&audio->idlelock); +} + +static void audio_allow_sleep(struct audio *audio) +{ + wake_unlock(&audio->wakelock); + wake_unlock(&audio->idlelock); + MM_DBG("\n"); /* Macro prints the file name and function */ +} + +static int audio_dsp_out_enable(struct audio *audio, int yes); +static int audio_dsp_send_buffer(struct audio *audio, unsigned id, + unsigned len); + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + /* refuse to start if we're not ready */ + if (!audio->out[0].used || !audio->out[1].used) + return -EIO; + + /* we start buffers 0 and 1, so buffer 0 will be the + * next one the dsp will want + */ + audio->out_tail = 0; + audio->out_needed = 0; + + audio_prevent_sleep(audio); + + if (audpp_enable(-1, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + audio_allow_sleep(audio); + return -ENODEV; + } + + audio->enabled = 1; + htc_pwrsink_set(PWRSINK_AUDIO, 100); + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio_dsp_out_enable(audio, 0); + + audpp_disable(-1, audio); + + wake_up(&audio->wait); + audio->out_needed = 0; + audio_allow_sleep(audio); + } + return 0; +} + +/* ------------------- dsp --------------------- */ +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + struct buffer *frame; + unsigned long flags; + static unsigned long pcmdmamsd_time; + + switch (id) { + case AUDPP_MSG_HOST_PCM_INTF_MSG: { + unsigned id = msg[3]; + unsigned idx = msg[4] - 1; + + MM_DBG("HOST_PCM id %d idx %d\n", id, idx); + if (id != AUDPP_MSG_HOSTPCM_ID_ARM_RX) { + MM_ERR("bogus id\n"); + break; + } + if (idx > 1) { + MM_ERR("bogus buffer idx\n"); + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (audio->running) { + atomic_add(audio->out[idx].used, &audio->out_bytes); + audio->out[idx].used = 0; + frame = audio->out + audio->out_tail; + if (frame->used) { + /* Reset teos flag to avoid stale + * PCMDMAMISS been considered + */ + audio->teos = 0; + audio_dsp_send_buffer( + audio, audio->out_tail, frame->used); + audio->out_tail ^= 1; + } else { + audio->out_needed++; + } + wake_up(&audio->wait); + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + break; + } + case AUDPP_MSG_PCMDMAMISSED: + /* prints only if 1 second is elapsed since the last time + * this message has been printed */ + if (printk_timed_ratelimit(&pcmdmamsd_time, 1000)) + printk(KERN_INFO "[%s:%s] PCMDMAMISSED %d\n", + __MM_FILE__, __func__, msg[0]); + audio->teos++; + MM_DBG("PCMDMAMISSED Count per Buffer %d\n", audio->teos); + wake_up(&audio->wait); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_route_stream(audio->dec_id, audio->source); + audio_dsp_out_enable(audio, 1); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_ERR("CFG_MSG %d?\n", msg[0]); + } + break; + default: + MM_ERR("UNKNOWN (%d)\n", id); + } +} + +static int audio_dsp_out_enable(struct audio *audio, int yes) +{ + struct audpp_cmd_pcm_intf cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_PCM_INTF; + cmd.stream = AUDPP_CMD_POPP_STREAM; + cmd.stream_id = audio->dec_id; + cmd.config = AUDPP_CMD_PCM_INTF_CONFIG_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + + if (yes) { + cmd.write_buf1LSW = audio->out[0].addr; + cmd.write_buf1MSW = audio->out[0].addr >> 16; + if (audio->out[0].used) + cmd.write_buf1_len = audio->out[0].used; + else + cmd.write_buf1_len = audio->out[0].size; + cmd.write_buf2LSW = audio->out[1].addr; + cmd.write_buf2MSW = audio->out[1].addr >> 16; + if (audio->out[1].used) + cmd.write_buf2_len = audio->out[1].used; + else + cmd.write_buf2_len = audio->out[1].size; + cmd.arm_to_rx_flag = AUDPP_CMD_PCM_INTF_ENA_V; + cmd.weight_decoder_to_rx = audio->out_weight; + cmd.weight_arm_to_rx = 1; + cmd.partition_number_arm_to_dsp = 0; + cmd.sample_rate = audio->out_sample_rate; + cmd.channel_mode = audio->out_channel_mode; + } + + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static int audio_dsp_send_buffer(struct audio *audio, unsigned idx, + unsigned len) +{ + struct audpp_cmd_pcm_intf_send_buffer cmd; + + cmd.cmd_id = AUDPP_CMD_PCM_INTF; + cmd.stream = AUDPP_CMD_POPP_STREAM; + cmd.stream_id = audio->dec_id; + cmd.config = AUDPP_CMD_PCM_INTF_BUFFER_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + cmd.dsp_to_arm_buf_id = 0; + cmd.arm_to_dsp_buf_id = idx + 1; + cmd.arm_to_dsp_buf_len = len; + + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +/* ------------------- device --------------------- */ +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->stopped = 0; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->out_bytes); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + return 0; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + if ((audio->voice_state != VOICE_STATE_INCALL) + && (audio->source & AUDPP_MIXER_UPLINK_RX)) { + MM_ERR("Unable to Start : state %d source %d\n", + audio->voice_state, audio->source); + rc = -EPERM; + break; + } + rc = audio_enable(audio); + break; + case AUDIO_STOP: + rc = audio_disable(audio); + audio->stopped = 1; + break; + case AUDIO_FLUSH: + if (audio->stopped) { + /* Make sure we're stopped and we wake any threads + * that might be blocked holding the write_lock. + * While audio->stopped write threads will always + * exit immediately. + */ + wake_up(&audio->wait); + mutex_lock(&audio->write_lock); + audio_flush(audio); + mutex_unlock(&audio->write_lock); + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + else if (config.channel_count == 2) + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + else { + rc = -EINVAL; + break; + } + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + return -EFAULT; + rc = 0; + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audio_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + int rc = 0; + + if (!audio->running) + return -EINVAL; + + mutex_lock(&audio->write_lock); + + /* PCM DMAMISS message is sent only once in + * hpcm interface. So, wait for buffer complete + * and teos flag. + */ + rc = wait_event_interruptible(audio->wait, + (!audio->out[0].used && + !audio->out[1].used)); + + if (rc < 0) + goto done; + + rc = wait_event_interruptible(audio->wait, + audio->teos); +done: + mutex_unlock(&audio->write_lock); + return rc; +} + +static ssize_t audio_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + return -EINVAL; +} + +static inline int rt_policy(int policy) +{ + if (unlikely(policy == SCHED_FIFO) || unlikely(policy == SCHED_RR)) + return 1; + return 0; +} + +static inline int task_has_rt_policy(struct task_struct *p) +{ + return rt_policy(p->policy); +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct sched_param s = { .sched_priority = 1 }; + struct audio *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + int old_prio = current->rt_priority; + int old_policy = current->policy; + int cap_nice = cap_raised(current_cap(), CAP_SYS_NICE); + int rc = 0; + + + if ((audio->voice_state == VOICE_STATE_OFFCALL) + && (audio->source & AUDPP_MIXER_UPLINK_RX) && + audio->running) { + MM_ERR("Not Permitted Voice Terminated: state %d source %x \ + running %d\n", + audio->voice_state, audio->source, audio->running); + return -EPERM; + } + /* just for this write, set us real-time */ + if (!task_has_rt_policy(current)) { + struct cred *new = prepare_creds(); + cap_raise(new->cap_effective, CAP_SYS_NICE); + commit_creds(new); + if ((sched_setscheduler(current, SCHED_RR, &s)) < 0) + MM_ERR("sched_setscheduler failed\n"); + } + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->wait, + (frame->used == 0) || (audio->stopped) || + ((audio->voice_state == VOICE_STATE_OFFCALL) && + (audio->source & AUDPP_MIXER_UPLINK_RX))); + + if (rc < 0) + break; + if (audio->stopped) { + rc = -EBUSY; + break; + } else if ((audio->voice_state == VOICE_STATE_OFFCALL) && + (audio->source & AUDPP_MIXER_UPLINK_RX)) { + MM_ERR("Not Permitted Voice Terminated: %d\n", + audio->voice_state); + rc = -EPERM; + break; + } + + xfer = count > frame->size ? frame->size : count; + if (copy_from_user(frame->data, buf, xfer)) { + rc = -EFAULT; + break; + } + frame->used = xfer; + audio->out_head ^= 1; + count -= xfer; + buf += xfer; + + spin_lock_irqsave(&audio->dsp_lock, flags); + frame = audio->out + audio->out_tail; + if (frame->used && audio->out_needed) { + /* Reset teos flag to avoid stale + * PCMDMAMISS been considered + */ + audio->teos = 0; + audio_dsp_send_buffer(audio, audio->out_tail, + frame->used); + audio->out_tail ^= 1; + audio->out_needed--; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + + mutex_unlock(&audio->write_lock); + + /* restore scheduling policy and priority */ + if (!rt_policy(old_policy)) { + struct sched_param v = { .sched_priority = old_prio }; + if ((sched_setscheduler(current, old_policy, &v)) < 0) + MM_ERR("sched_setscheduler failed\n"); + if (likely(!cap_nice)) { + struct cred *new = prepare_creds(); + cap_lower(new->cap_effective, CAP_SYS_NICE); + commit_creds(new); + } + } + + if (buf > start) + return buf - start; + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audio_disable(audio); + audio_flush(audio); + audio->opened = 0; + mutex_unlock(&audio->lock); + htc_pwrsink_set(PWRSINK_AUDIO, 0); + return 0; +} + +static struct audio the_audio; + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = &the_audio; + int rc; + + mutex_lock(&audio->lock); + + if (audio->opened) { + MM_ERR("busy\n"); + rc = -EBUSY; + goto done; + } + + + audio->dec_id = HOSTPCM_STREAM_ID; + + audio->out_buffer_size = BUFSZ; + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + audio->out_weight = 100; + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x2000; + audio->vol_pan.pan = 0x0; + audio->source = 0x0; + + audio_flush(audio); + audio->voice_state = msm_get_voice_state(); + MM_DBG("voice_state = %x\n", audio->voice_state); + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG| + AUDDEV_EVT_VOICE_STATE_CHG; + + MM_DBG("register for event callback pdata %p\n", audio); + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + audio_out_listener, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listener\n", __func__); + goto done; + } + + file->private_data = audio; + audio->opened = 1; + rc = 0; +done: + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_fsync, +}; + +struct miscdevice audio_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_out", + .fops = &audio_fops, +}; + +static int __init audio_init(void) +{ + the_audio.phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)the_audio.phys)) { + the_audio.data = ioremap(the_audio.phys, DMASZ); + if (!the_audio.data) { + MM_ERR("could not map pmem buffers\n"); + pmem_kfree(the_audio.phys); + return -ENOMEM; + } + } else { + MM_ERR("could not allocate pmem buffers\n"); + return -ENOMEM; + } + MM_DBG("Memory addr = 0x%8x phy addr = 0x%8x\n",\ + (int) the_audio.data, (int) the_audio.phys); + mutex_init(&the_audio.lock); + mutex_init(&the_audio.write_lock); + spin_lock_init(&the_audio.dsp_lock); + init_waitqueue_head(&the_audio.wait); + wake_lock_init(&the_audio.wakelock, WAKE_LOCK_SUSPEND, "audio_pcm"); + wake_lock_init(&the_audio.idlelock, WAKE_LOCK_IDLE, "audio_pcm_idle"); + return misc_register(&audio_misc); +} + +late_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_pcm.c b/arch/arm/mach-msm/qdsp5v2/audio_pcm.c new file mode 100644 index 00000000000..3115d52cea3 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_pcm.c @@ -0,0 +1,1697 @@ +/* arch/arm/mach-msm/qdsp5v2/audio_pcm.c + * + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define ADRV_STATUS_AIO_INTF 0x00000001 +#define ADRV_STATUS_OBUF_GIVEN 0x00000002 +#define ADRV_STATUS_IBUF_GIVEN 0x00000004 +#define ADRV_STATUS_FSYNC 0x00000008 + +/* Size must be power of 2 */ +#define BUFSZ_MAX 32768 +#define BUFSZ_MIN 4096 +#define DMASZ_MAX (BUFSZ_MAX * 2) +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDDEC_DEC_PCM 0 + +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDPCM_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audpcm_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audpcm_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audpcm_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audpcm_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; +}; + +struct audpcm_drv_operations { + void (*send_data)(struct audio *, unsigned); + void (*out_flush)(struct audio *); + int (*fsync)(struct audio *); +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + struct list_head out_queue; /* queue to retain output buffers */ + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_bits; /* bits per sample */ + + /* data allocated for various buffers */ + char *data; + int32_t phys; + + uint32_t drv_status; + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint32_t device_events; + + unsigned volume; + + uint16_t dec_id; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audpcm_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + struct list_head pmem_region_queue; + struct audpcm_drv_operations drv_ops; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audpcm_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +static unsigned long audpcm_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up); + +static void pcm_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->volume = evt_payload->session_vol; + MM_DBG("AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->volume); + if (audio->running) + audpp_set_volume_and_pan(audio->dec_id, audio->volume, + 0, POPP); + break; + default: + MM_ERR("ERROR:wrong event\n"); + break; + } +} +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audio->drv_ops.send_data(audio, 1); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event:module audplaytask\n"); + break; + + default: + MM_ERR("unexpected message from decoder\n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason=0x%04x\n", + reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + audpp_route_stream(audio->dec_id, + audio->source); + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status\n"); + break; + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_set_volume_and_pan(audio->dec_id, audio->volume, + 0, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_ERR("audio_dsp_event: CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + pr_info("%s: AVSYNC_MSG\n", __func__); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_DBG("audio_dsp_event: UNKNOWN (%d)\n", id); + } + +} + + +struct msm_adsp_ops audpcmdec_adsp_ops = { + .event = audplay_dsp_event, +}; + + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_PCM; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_wav cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_WAV_LEN >> 1; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + cmd.stereo_cfg = audio->out_channel_mode; + cmd.pcm_width = audio->out_bits; + cmd.sign = 0; + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL; + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len/2; + cmd.partition_number = 0; + /* complete writes to the input buffer */ + wmb(); + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audpcm_async_send_data(struct audio *audio, unsigned needed) +{ + unsigned long flags; + + if (!audio->running) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + + if (needed && !audio->wflush) { + audio->out_needed = 1; + if (audio->drv_status & ADRV_STATUS_OBUF_GIVEN) { + /* pop one node out of queue */ + union msm_audio_event_payload payload; + struct audpcm_buffer_node *used_buf; + + MM_DBG("consumed\n"); + + BUG_ON(list_empty(&audio->out_queue)); + used_buf = list_first_entry(&audio->out_queue, + struct audpcm_buffer_node, list); + list_del(&used_buf->list); + payload.aio_buf = used_buf->buf; + audpcm_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(used_buf); + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + } + } + if (audio->out_needed) { + struct audpcm_buffer_node *next_buf; + struct audplay_cmd_bitstream_data_avail cmd; + if (!list_empty(&audio->out_queue)) { + next_buf = list_first_entry(&audio->out_queue, + struct audpcm_buffer_node, list); + MM_DBG("next_buf %p\n", next_buf); + if (next_buf) { + MM_DBG("next buf phy %lx len %d\n", + next_buf->paddr, next_buf->buf.data_len); + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL; + if (next_buf->buf.data_len) + cmd.decoder_id = audio->dec_id; + else { + cmd.decoder_id = -1; + MM_DBG("input EOS signaled\n"); + } + cmd.buf_ptr = (unsigned) next_buf->paddr; + cmd.buf_size = next_buf->buf.data_len >> 1; + cmd.partition_number = 0; + /* complete writes to the input buffer */ + wmb(); + audplay_send_queue0(audio, &cmd, sizeof(cmd)); + audio->out_needed = 0; + audio->drv_status |= ADRV_STATUS_OBUF_GIVEN; + } + } + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + if (!audio->running) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ +static void audpcm_async_flush(struct audio *audio) +{ + struct audpcm_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audpcm_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audpcm_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(buf_node); + } + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + audio->out_needed = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_ioport_reset(struct audio *audio) +{ + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + /* If fsync is in progress, make sure + * return value of fsync indicates + * abort due to flush + */ + if (audio->drv_status & ADRV_STATUS_FSYNC) { + MM_DBG("fsync in progress\n"); + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio->drv_ops.out_flush(audio); + mutex_unlock(&audio->write_lock); + } else + audio->drv_ops.out_flush(audio); + } else { + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio->drv_ops.out_flush(audio); + mutex_unlock(&audio->write_lock); + } + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); +} + +static int audpcm_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audpcm_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audpcm_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audpcm_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audpcm_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audpcm_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audpcm_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audpcm_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audpcm_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audpcm_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (drv_evt && drv_evt->event_type == AUDIO_EVENT_WRITE_DONE) { + mutex_lock(&audio->lock); + audpcm_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0); + mutex_unlock(&audio->lock); + } + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audpcm_pmem_check(struct audio *audio, + void *vaddr, unsigned long len) +{ + struct audpcm_pmem_region *region_elt; + struct audpcm_pmem_region t = { .vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + MM_ERR("region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, + region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audpcm_pmem_add(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audpcm_pmem_region *region; + int rc = -EINVAL; + + MM_DBG("\n"); /* Macro prints the file name and function */ + region = kmalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + return -EINVAL; + } + + rc = audpcm_pmem_check(audio, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + return rc; + } + + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + MM_DBG("add region paddr %lx vaddr %p, len %lu\n", region->paddr, + region->vaddr, region->len); + list_add_tail(®ion->list, &audio->pmem_region_queue); + return rc; +} + +static int audpcm_pmem_remove(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audpcm_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + MM_DBG("info fd %d vaddr %p\n", info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audpcm_pmem_region, list); + + if ((region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + MM_DBG("region %p in use ref_cnt %d\n", region, + region->ref_cnt); + break; + } + MM_DBG("remove region fd %d vaddr %p \n", info->fd, + info->vaddr); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +static int audpcm_pmem_lookup_vaddr(struct audio *audio, void *addr, + unsigned long len, struct audpcm_pmem_region **region) +{ + struct audpcm_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + MM_ERR("multiple hits for vaddr %p, len %ld\n", addr, len); + list_for_each_entry(region_elt, + &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + MM_ERR("\t%p, %ld --> %p\n", + region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +static unsigned long audpcm_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up) +{ + struct audpcm_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audpcm_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + MM_ERR("lookup (%p, %ld) failed\n", addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + MM_DBG("found region %p ref_cnt %d\n", region, region->ref_cnt); + paddr = region->paddr + (addr - region->vaddr); + return paddr; +} + +/* audio -> lock must be held at this point */ +static int audpcm_aio_buf_add(struct audio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audpcm_buffer_node *buf_node; + + buf_node = kmalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + MM_DBG("node %p dir %x buf_addr %p buf_len %d data_len %d\n", + buf_node, dir, buf_node->buf.buf_addr, + buf_node->buf.buf_len, buf_node->buf.data_len); + + buf_node->paddr = audpcm_pmem_fixup( + audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1); + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.data_len & 0x1) || + (!buf_node->buf.data_len)) { + kfree(buf_node); + return -EINVAL; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + list_add_tail(&buf_node->list, &audio->out_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + audio->drv_ops.send_data(audio, 0); + } + + MM_DBG("Add buf_node %p paddr %lx\n", buf_node, buf_node->paddr); + + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = 0; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + if (cmd == AUDIO_SET_VOLUME) { + unsigned long flags; + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->volume = arg; + if (audio->running) + audpp_set_volume_and_pan(audio->dec_id, arg, 0, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + return 0; + } + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audpcm_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->wflush = 0; + } + break; + + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + if (config.bits == 8) + config.bits = AUDPP_CMD_WAV_PCM_WIDTH_8; + else if (config.bits == 16) + config.bits = AUDPP_CMD_WAV_PCM_WIDTH_16; + else if (config.bits == 24) + config.bits = AUDPP_CMD_WAV_PCM_WIDTH_24; + else { + rc = -EINVAL; + break; + } + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + audio->out_bits = config.bits; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + if (audio->out_bits == AUDPP_CMD_WAV_PCM_WIDTH_8) + config.bits = 8; + else if (audio->out_bits == AUDPP_CMD_WAV_PCM_WIDTH_24) + config.bits = 24; + else + config.bits = 16; + config.unused[0] = 0; + config.unused[1] = 0; + + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audpcm_pmem_add(audio, &info); + break; + } + + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + MM_DBG("AUDIO_DEREGISTER_PMEM\n"); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audpcm_pmem_remove(audio, &info); + break; + } + + case AUDIO_ASYNC_WRITE: + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else + rc = audpcm_aio_buf_add(audio, 1, (void __user *) arg); + break; + + case AUDIO_ASYNC_READ: + MM_ERR("AUDIO_ASYNC_READ not supported\n"); + rc = -EPERM; + break; + + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + return -EFAULT; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +int audpcm_async_fsync(struct audio *audio) +{ + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + mutex_lock(&audio->write_lock); + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + (audio->teos && audio->out_needed && + list_empty(&audio->out_queue)) + || audio->wflush || audio->stopped); + + if (audio->stopped || audio->wflush) + rc = -EBUSY; + + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +int audpcm_sync_fsync(struct audio *audio) +{ + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audio->drv_ops.send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + return rc; +} + +int audpcm_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + + if (!audio->running) + return -EINVAL; + + return audio->drv_ops.fsync(audio); +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0; + unsigned dsize; + + if (audio->drv_status & ADRV_STATUS_AIO_INTF) + return -EPERM; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > (frame->size - 1)) ? + frame->size - 1 : count; + cpy_ptr++; + dsize = 1; + audio->reserved = 0; + } else + xfer = (count > frame->size) ? frame->size : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audio->drv_ops.send_data(audio, 0); + } + } + mutex_unlock(&audio->write_lock); + if (buf > start) + return buf - start; + + return rc; +} + +static void audpcm_reset_pmem_region(struct audio *audio) +{ + struct audpcm_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audpcm_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audio_disable(audio); + audio->drv_ops.out_flush(audio); + audpcm_reset_pmem_region(audio); + + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->opened = 0; + audio->event_abort = 1; + wake_up(&audio->event_wait); + audpcm_reset_event_queue(audio); + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audpcm_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audpcm_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audpcm_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audpcm_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audpcm_suspend(struct early_suspend *h) +{ + struct audpcm_suspend_ctl *ctl = + container_of(h, struct audpcm_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audpcm_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audpcm_resume(struct early_suspend *h) +{ + struct audpcm_suspend_ctl *ctl = + container_of(h, struct audpcm_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audpcm_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audpcm_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audpcm_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audpcm_debug_fops = { + .read = audpcm_debug_read, + .open = audpcm_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, i, dec_attrb, decid; + struct audpcm_event *e_node = NULL; + unsigned pmem_sz = DMASZ_MAX; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_pcm_dec_" + 5]; +#endif + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_PCM; + if (file->f_mode & FMODE_READ) { + MM_ERR("Non-Tunneled mode not supported\n"); + rc = -EPERM; + kfree(audio); + goto done; + } else + dec_attrb |= MSM_AUD_MODE_TUNNEL; + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + /* AIO interface */ + if (file->f_flags & O_NONBLOCK) { + MM_DBG("set to aio interface\n"); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + audio->drv_ops.send_data = audpcm_async_send_data; + audio->drv_ops.out_flush = audpcm_async_flush; + audio->drv_ops.fsync = audpcm_async_fsync; + } else { + MM_DBG("set to std io interface\n"); + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d\n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write \ + buffers freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x \ + kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers \ + freeing instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + audio->drv_ops.send_data = audplay_send_data; + audio->drv_ops.out_flush = audio_flush; + audio->drv_ops.fsync = audpcm_sync_fsync; + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = (audio->out_dma_sz >> 1); + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audpcmdec_adsp_ops, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->avsync_wait); + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + audio->out_bits = AUDPP_CMD_WAV_PCM_WIDTH_16; + audio->volume = 0x7FFF; + audio->drv_ops.out_flush(audio); + + file->private_data = audio; + audio->opened = 1; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + pcm_listner, + (void *)audio); + if (rc) { + MM_ERR("failed to register listnet\n"); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_pcm_dec_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audpcm_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_ERR("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audpcm_resume; + audio->suspend_ctl.node.suspend = audpcm_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDPCM_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audpcm_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + } + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_pcm_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audpcm_fsync, +}; + +struct miscdevice audio_pcm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_dec", + .fops = &audio_pcm_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_pcm_misc); +} + +device_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_pcm_in.c b/arch/arm/mach-msm/qdsp5v2/audio_pcm_in.c new file mode 100644 index 00000000000..71a07e5b0e1 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_pcm_in.c @@ -0,0 +1,940 @@ +/* + * pcm audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (8) +#define FRAME_HEADER_SIZE (8) /*4 half words*/ +/* size of a mono frame with 256 samples */ +#define MONO_DATA_SIZE_256 (512) /* in bytes*/ +/*size of a mono frame with 512 samples */ +#define MONO_DATA_SIZE_512 (1024) /* in bytes*/ +/*size of a mono frame with 1024 samples */ +#define MONO_DATA_SIZE_1024 (2048) /* in bytes */ + +/*size of a stereo frame with 256 samples per channel */ +#define STEREO_DATA_SIZE_256 (1024) /* in bytes*/ +/*size of a stereo frame with 512 samples per channel */ +#define STEREO_DATA_SIZE_512 (2048) /* in bytes*/ +/*size of a stereo frame with 1024 samples per channel */ +#define STEREO_DATA_SIZE_1024 (4096) /* in bytes */ + +#define MAX_FRAME_SIZE ((STEREO_DATA_SIZE_1024) + FRAME_HEADER_SIZE) +#define DMASZ (MAX_FRAME_SIZE * FRAME_NUM) + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; +}; + +struct audio_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + atomic_t in_samples; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + wait_queue_head_t wait_enable; + + struct msm_adsp_module *audrec; + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* 2048 for mono, 4096 for stereo */ + uint32_t enc_type; + + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + uint32_t mode; + + const char *module_name; + unsigned queue_ids; + uint16_t enc_id; /* Session Id */ + + uint16_t source; /* Encoding source bit mask */ + uint32_t device_events; /* device events interested in */ + uint32_t in_call; + uint32_t dev_cnt; + int voice_state; + spinlock_t dev_lock; + + struct audrec_session_info session_info; /*audrec session info*/ + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int abort; /* set when error, like sample rate mismatch */ + int dual_mic_config; +}; + +static struct audio_in the_audio_in; + +struct audio_frame { + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + unsigned char raw_bitstream[]; /* samples */ +} __attribute__((packed)); + +/* Audrec Queue command sent macro's */ +#define audrec_send_bitstreamqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_ids & 0xFFFF0000) >> 16),\ + cmd, len) + +#define audrec_send_audrecqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_ids & 0x0000FFFF),\ + cmd, len) + +/* DSP command send functions */ +static int audpcm_in_enc_config(struct audio_in *audio, int enable); +static int audpcm_in_param_config(struct audio_in *audio); +static int audpcm_in_mem_config(struct audio_in *audio); +static int audpcm_in_record_config(struct audio_in *audio, int enable); +static int audpcm_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt); + +static void audpcm_in_get_dsp_frames(struct audio_in *audio); + +static void audpcm_in_flush(struct audio_in *audio); + +static void pcm_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio_in *audio = (struct audio_in *) private_data; + unsigned long flags; + + MM_DBG("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: { + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt++; + if (!audio->in_call) + audio->source |= (0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((audio->running == 1) && (audio->enabled == 1)) + audpcm_in_record_config(audio, 1); + + break; + } + case AUDDEV_EVT_DEV_RLS: { + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt--; + if (!audio->in_call) + audio->source &= ~(0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if (!audio->running || !audio->enabled) + break; + + /* Turn of as per source */ + if (audio->source) + audpcm_in_record_config(audio, 1); + else + /* Turn off all */ + audpcm_in_record_config(audio, 0); + + break; + } + case AUDDEV_EVT_VOICE_STATE_CHG: { + MM_DBG("AUDDEV_EVT_VOICE_STATE_CHG, state = %d\n", + evt_payload->voice_state); + audio->voice_state = evt_payload->voice_state; + if (audio->in_call && audio->running) { + if (audio->voice_state == VOICE_STATE_INCALL) + audpcm_in_record_config(audio, 1); + else if (audio->voice_state == VOICE_STATE_OFFCALL) { + audpcm_in_record_config(audio, 0); + wake_up(&audio->wait); + } + } + break; + } + case AUDDEV_EVT_FREQ_CHG: { + MM_DBG("Encoder Driver got sample rate change event\n"); + MM_DBG("sample rate %d\n", evt_payload->freq_info.sample_rate); + MM_DBG("dev_type %d\n", evt_payload->freq_info.dev_type); + MM_DBG("acdb_dev_id %d\n", evt_payload->freq_info.acdb_dev_id); + if (audio->running == 1) { + /* Stop Recording sample rate does not match + with device sample rate */ + if (evt_payload->freq_info.sample_rate != + audio->samp_rate) { + audpcm_in_record_config(audio, 0); + audio->abort = 1; + wake_up(&audio->wait); + } + } + break; + } + default: + MM_ERR("wrong event %d\n", evt_id); + break; + } +} + +/* ------------------- dsp preproc event handler--------------------- */ +static void audpreproc_dsp_event(void *data, unsigned id, void *msg) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDPREPROC_ERROR_MSG: { + struct audpreproc_err_msg *err_msg = msg; + + MM_ERR("ERROR_MSG: stream id %d err idx %d\n", + err_msg->stream_id, err_msg->aud_preproc_err_idx); + /* Error case */ + wake_up(&audio->wait_enable); + break; + } + case AUDPREPROC_CMD_CFG_DONE_MSG: { + MM_DBG("CMD_CFG_DONE_MSG \n"); + break; + } + case AUDPREPROC_CMD_ENC_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_cfg_done_msg *enc_cfg_msg = msg; + + MM_DBG("CMD_ENC_CFG_DONE_MSG: stream id %d enc type \ + 0x%8x\n", enc_cfg_msg->stream_id, + enc_cfg_msg->rec_enc_type); + /* Encoder enable success */ + if (enc_cfg_msg->rec_enc_type & ENCODE_ENABLE) + audpcm_in_param_config(audio); + else { /* Encoder disable success */ + audio->running = 0; + audpcm_in_record_config(audio, 0); + } + break; + } + case AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: { + MM_DBG("CMD_ENC_PARAM_CFG_DONE_MSG \n"); + audpcm_in_mem_config(audio); + break; + } + case AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: { + MM_DBG("AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG \n"); + wake_up(&audio->wait_enable); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +/* ------------------- dsp audrec event handler--------------------- */ +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDREC_CMD_MEM_CFG_DONE_MSG: { + MM_DBG("CMD_MEM_CFG_DONE MSG DONE\n"); + audio->running = 1; + if ((!audio->in_call && (audio->dev_cnt > 0)) || + (audio->in_call && + (audio->voice_state == VOICE_STATE_INCALL))) + audpcm_in_record_config(audio, 1); + break; + } + case AUDREC_FATAL_ERR_MSG: { + struct audrec_fatal_err_msg fatal_err_msg; + + getevent(&fatal_err_msg, AUDREC_FATAL_ERR_MSG_LEN); + MM_ERR("FATAL_ERR_MSG: err id %d\n", + fatal_err_msg.audrec_err_id); + /* Error stop the encoder */ + audio->stopped = 1; + wake_up(&audio->wait); + break; + } + case AUDREC_UP_PACKET_READY_MSG: { + struct audrec_up_pkt_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packet_write_cnt_lsw, \ + pkt_ready_msg.audrec_packet_write_cnt_msw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_lsw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_msw); + + audpcm_in_get_dsp_frames(audio); + break; + } + case ADSP_MESSAGE_ID: { + MM_DBG("Received ADSP event :module audrectask\n"); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +static void audpcm_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(*frame)); + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = frame->frame_length; + + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + else + audio->in_count++; + + audpcm_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +struct msm_adsp_ops audrec_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int audpcm_in_enc_config(struct audio_in *audio, int enable) +{ + struct audpreproc_audrec_cmd_enc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + cmd.stream_id = audio->enc_id; + + if (enable) + cmd.audrec_enc_type = audio->enc_type | ENCODE_ENABLE; + else + cmd.audrec_enc_type &= ~(ENCODE_ENABLE); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audpcm_in_param_config(struct audio_in *audio) +{ + struct audpreproc_audrec_cmd_parm_cfg_wav cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPREPROC_AUDREC_CMD_PARAM_CFG; + cmd.common.stream_id = audio->enc_id; + + cmd.aud_rec_samplerate_idx = audio->samp_rate; + if (audio->dual_mic_config) + cmd.aud_rec_stereo_mode = DUAL_MIC_STEREO_RECORDING; + else + cmd.aud_rec_stereo_mode = audio->channel_mode; + + if (audio->channel_mode == AUDREC_CMD_MODE_MONO) + cmd.aud_rec_frame_size = audio->buffer_size/2; + else + cmd.aud_rec_frame_size = audio->buffer_size/4; + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +/* To Do: msm_snddev_route_enc(audio->enc_id); */ +static int audpcm_in_record_config(struct audio_in *audio, int enable) +{ + struct audpreproc_afe_cmd_audio_record_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG; + cmd.stream_id = audio->enc_id; + if (enable) + cmd.destination_activity = AUDIO_RECORDING_TURN_ON; + else + cmd.destination_activity = AUDIO_RECORDING_TURN_OFF; + + cmd.source_mix_mask = audio->source; + if (audio->enc_id == 2) { + if ((cmd.source_mix_mask & + INTERNAL_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & AUX_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_UL_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_DL_SOURCE_MIX_MASK)) { + cmd.pipe_id = SOURCE_PIPE_1; + } + if (cmd.source_mix_mask & + AUDPP_A2DP_PIPE_SOURCE_MIX_MASK) + cmd.pipe_id |= SOURCE_PIPE_0; + } + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audpcm_in_mem_config(struct audio_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + int n; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_MEM_CFG_CMD; + cmd.audrec_up_pkt_intm_count = 1; + cmd.audrec_ext_pkt_start_addr_msw = audio->phys >> 16; + cmd.audrec_ext_pkt_start_addr_lsw = audio->phys; + cmd.audrec_ext_pkt_buf_number = FRAME_NUM; + + /* prepare buffer pointers: + * Mono: 1024 samples + 4 halfword header + * Stereo: 2048 samples + 4 halfword header + */ + for (n = 0; n < FRAME_NUM; n++) { + /* word increment*/ + audio->in[n].data = data + (FRAME_HEADER_SIZE/2); + data += ((FRAME_HEADER_SIZE/2) + (audio->buffer_size/2)); + MM_DBG("0x%8x\n", (int)(audio->in[n].data - FRAME_HEADER_SIZE)); + } + + return audrec_send_audrecqueue(audio, &cmd, sizeof(cmd)); +} + +static int audpcm_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt) +{ + struct up_audrec_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = UP_AUDREC_PACKET_EXT_PTR; + cmd.audrec_up_curr_read_count_msw = read_cnt >> 16; + cmd.audrec_up_curr_read_count_lsw = read_cnt; + + return audrec_send_bitstreamqueue(audio, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audpcm_in_enable(struct audio_in *audio) +{ + if (audio->enabled) + return 0; + + if (audpreproc_enable(audio->enc_id, &audpreproc_dsp_event, audio)) { + MM_ERR("msm_adsp_enable(audpreproc) failed\n"); + return -ENODEV; + } + + if (msm_adsp_enable(audio->audrec)) { + MM_ERR("msm_adsp_enable(audrec) failed\n"); + audpreproc_disable(audio->enc_id, audio); + return -ENODEV; + } + audio->enabled = 1; + audpcm_in_enc_config(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audpcm_in_disable(struct audio_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + audpcm_in_enc_config(audio, 0); + wake_up(&audio->wait); + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + msm_adsp_disable(audio->audrec); + audpreproc_disable(audio->enc_id, audio); + } + return 0; +} + +static void audpcm_in_flush(struct audio_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + for (i = 0; i < FRAME_NUM; i++) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } + MM_DBG("in_bytes %d\n", atomic_read(&audio->in_bytes)); + MM_DBG("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); +} + +/* ------------------- device --------------------- */ +static long audpcm_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_in *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + uint32_t freq; + /* Poll at 48KHz always */ + freq = 48000; + MM_DBG("AUDIO_START\n"); + if (audio->in_call && (audio->voice_state != + VOICE_STATE_INCALL)) { + rc = -EPERM; + break; + } + rc = msm_snddev_request_freq(&freq, audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("sample rate configured %d sample rate requested %d\n", + freq, audio->samp_rate); + if (rc < 0) { + MM_DBG("sample rate can not be set, return code %d\n",\ + rc); + msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + break; + } + audio->dual_mic_config = msm_get_dual_mic_config(audio->enc_id); + /*DSP supports fluence block and by default ACDB layer will + applies the fluence pre-processing feature, if dual MIC config + is enabled implies client want to record pure dual MIC sample + for this we need to over ride the fluence pre processing + feature at ACDB layer to not to apply if fluence preprocessing + feature supported*/ + if (audio->dual_mic_config) { + MM_INFO("dual MIC config = %d, over ride the fluence " + "feature\n", audio->dual_mic_config); + fluence_feature_update(audio->dual_mic_config, + audio->enc_id); + } + /*update aurec session info in audpreproc layer*/ + audio->session_info.session_id = audio->enc_id; + audio->session_info.sampling_freq = audio->samp_rate; + audpreproc_update_audrec_info(&audio->session_info); + rc = audpcm_in_enable(audio); + if (!rc) { + rc = + wait_event_interruptible_timeout(audio->wait_enable, + audio->running != 0, 1*HZ); + MM_DBG("state %d rc = %d\n", audio->running, rc); + + if (audio->running == 0) + rc = -ENODEV; + else + rc = 0; + } + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + rc = audpcm_in_disable(audio); + rc = msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + audio->stopped = 1; + audio->abort = 0; + break; + } + case AUDIO_FLUSH: { + if (audio->stopped) { + /* Make sure we're stopped and we wake any threads + * that might be blocked holding the read_lock. + * While audio->stopped read threads will always + * exit immediately. + */ + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audpcm_in_flush(audio); + mutex_unlock(&audio->read_lock); + } + break; + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.channel_count == 1) { + cfg.channel_count = AUDREC_CMD_MODE_MONO; + if ((cfg.buffer_size == MONO_DATA_SIZE_256) || + (cfg.buffer_size == MONO_DATA_SIZE_512) || + (cfg.buffer_size == MONO_DATA_SIZE_1024)) { + audio->buffer_size = cfg.buffer_size; + } else { + rc = -EINVAL; + break; + } + } else if (cfg.channel_count == 2) { + cfg.channel_count = AUDREC_CMD_MODE_STEREO; + if ((cfg.buffer_size == STEREO_DATA_SIZE_256) || + (cfg.buffer_size == STEREO_DATA_SIZE_512) || + (cfg.buffer_size == STEREO_DATA_SIZE_1024)) { + audio->buffer_size = cfg.buffer_size; + } else { + rc = -EINVAL; + break; + } + } else { + rc = -EINVAL; + break; + } + audio->samp_rate = cfg.sample_rate; + audio->channel_mode = cfg.channel_count; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + cfg.sample_rate = audio->samp_rate; + if (audio->channel_mode == AUDREC_CMD_MODE_MONO) + cfg.channel_count = 1; + else + cfg.channel_count = 2; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_INCALL: { + struct msm_voicerec_mode cfg; + unsigned long flags; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.rec_mode != VOC_REC_BOTH && + cfg.rec_mode != VOC_REC_UPLINK && + cfg.rec_mode != VOC_REC_DOWNLINK) { + MM_ERR("invalid rec_mode\n"); + rc = -EINVAL; + break; + } else { + spin_lock_irqsave(&audio->dev_lock, flags); + if (cfg.rec_mode == VOC_REC_UPLINK) + audio->source = VOICE_UL_SOURCE_MIX_MASK; + else if (cfg.rec_mode == VOC_REC_DOWNLINK) + audio->source = VOICE_DL_SOURCE_MIX_MASK; + else + audio->source = VOICE_DL_SOURCE_MIX_MASK | + VOICE_UL_SOURCE_MIX_MASK ; + audio->in_call = 1; + spin_unlock_irqrestore(&audio->dev_lock, flags); + } + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &audio->enc_id, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audpcm_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped || + audio->abort || (audio->in_call && audio->running && + (audio->voice_state == VOICE_STATE_OFFCALL))); + if (rc < 0) + break; + + if (!audio->in_count) { + if (audio->stopped) { + MM_DBG("Driver in stop state, No more \ + buffer to read"); + rc = 0;/* End of File */ + break; + } else if (audio->in_call && audio->running && + (audio->voice_state == VOICE_STATE_OFFCALL)) { + MM_DBG("Not Permitted Voice Terminated\n"); + rc = -EPERM; /* Voice Call stopped */ + break; + } + } + + if (audio->abort) { + rc = -EPERM; /* Not permitted due to abort */ + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + if (count >= size) { + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is + * invalid and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + } else { + MM_ERR("short read count %d\n", count); + break; + } + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static ssize_t audpcm_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + return -EINVAL; +} + +static int audpcm_in_release(struct inode *inode, struct file *file) +{ + struct audio_in *audio = file->private_data; + + mutex_lock(&audio->lock); + audio->in_call = 0; + /* with draw frequency for session + incase not stopped the driver */ + msm_snddev_withdraw_freq(audio->enc_id, SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, audio->enc_id); + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + audpcm_in_disable(audio); + audpcm_in_flush(audio); + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + audio->audrec = NULL; + audio->opened = 0; + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + audio->data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +static int audpcm_in_open(struct inode *inode, struct file *file) +{ + struct audio_in *audio = &the_audio_in; + int rc; + int encid; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate read buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->phys); + goto done; + } + } else { + MM_ERR("could not allocate read buffers\n"); + rc = -ENOMEM; + goto done; + } + MM_DBG("Memory addr = 0x%8x phy addr = 0x%8x\n",\ + (int) audio->data, (int) audio->phys); + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + rc = -EACCES; + MM_ERR("Non tunnel encoding is not supported\n"); + goto done; + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_TUNNEL; + MM_DBG("Opened for tunnel mode encoding\n"); + } else { + rc = -EACCES; + goto done; + } + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->channel_mode = AUDREC_CMD_MODE_MONO; + audio->buffer_size = MONO_DATA_SIZE_1024; + audio->samp_rate = 8000; + audio->enc_type = ENC_TYPE_EXT_WAV | audio->mode; + + encid = audpreproc_aenc_alloc(audio->enc_type, &audio->module_name, + &audio->queue_ids); + if (encid < 0) { + MM_ERR("No free encoder available\n"); + rc = -ENODEV; + goto done; + } + audio->enc_id = encid; + + rc = msm_adsp_get(audio->module_name, &audio->audrec, + &audrec_adsp_ops, audio); + + if (rc) { + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + audio->stopped = 0; + audio->source = 0; + audio->abort = 0; + audpcm_in_flush(audio); + audio->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_FREQ_CHG | + AUDDEV_EVT_VOICE_STATE_CHG; + + audio->voice_state = msm_get_voice_state(); + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_ENC, audio->enc_id, + pcm_in_listener, (void *) audio); + if (rc) { + MM_ERR("failed to register device event listener\n"); + goto evt_error; + } + file->private_data = audio; + audio->opened = 1; + rc = 0; +done: + mutex_unlock(&audio->lock); + return rc; +evt_error: + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_in_fops = { + .owner = THIS_MODULE, + .open = audpcm_in_open, + .release = audpcm_in_release, + .read = audpcm_in_read, + .write = audpcm_in_write, + .unlocked_ioctl = audpcm_in_ioctl, +}; + +struct miscdevice audio_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_in", + .fops = &audio_in_fops, +}; + +static int __init audpcm_in_init(void) +{ + mutex_init(&the_audio_in.lock); + mutex_init(&the_audio_in.read_lock); + spin_lock_init(&the_audio_in.dsp_lock); + spin_lock_init(&the_audio_in.dev_lock); + init_waitqueue_head(&the_audio_in.wait); + init_waitqueue_head(&the_audio_in.wait_enable); + return misc_register(&audio_in_misc); +} + +device_initcall(audpcm_in_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_qcelp.c b/arch/arm/mach-msm/qdsp5v2/audio_qcelp.c new file mode 100644 index 00000000000..b57d72f65f6 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_qcelp.c @@ -0,0 +1,1625 @@ +/* + * qcelp 13k audio decoder device + * + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * + * This code is based in part on audio_mp3.c, which is + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSZ 1094 /* QCELP 13K Hold 600ms packet data = 36 * 30 and + 14 bytes of meta in */ +#define BUF_COUNT 2 +#define DMASZ (BUFSZ * BUF_COUNT) + +#define PCM_BUFSZ_MIN 1624 /* 100ms worth of data and + 24 bytes of meta out */ +#define PCM_BUF_MAX_COUNT 5 + +#define AUDDEC_DEC_QCELP 9 + +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDQCELP_METAFIELD_MASK 0xFFFF0000 +#define AUDQCELP_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDQCELP_EOS_FLG_MASK 0x01 +#define AUDQCELP_EOS_NONE 0x0 /* No EOS detected */ +#define AUDQCELP_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDQCELP_EVENT_NUM 10 /* Default number of pre-allocated event pkts */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audqcelp_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audqcelp_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[BUF_COUNT]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section - START */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* Host PCM section - END */ + + struct msm_adsp_module *audplay; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + uint8_t opened:1; + uint8_t enabled:1; + uint8_t running:1; + uint8_t stopped:1; /* set when stopped, cleared on flush */ + uint8_t pcm_feedback:1; /* set when non-tunnel mode */ + uint8_t buf_refresh:1; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audqcelp_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + uint32_t device_events; + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audqcelp_send_data(struct audio *audio, unsigned needed); +static void audqcelp_config_hostpcm(struct audio *audio); +static void audqcelp_buffer_refresh(struct audio *audio); +static void audqcelp_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audqcelp_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); + +/* must be called with audio->lock held */ +static int audqcelp_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audqcelp_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + audio->enabled = 1; + return 0; +} + +static void qcelp_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + default: + MM_ERR(":ERROR:wrong event\n"); + break; + } +} +/* must be called with audio->lock held */ +static int audqcelp_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audqcelp_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + } else { + MM_ERR("expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audqcelp_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audqcelp_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audqcelp_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + } +} + +static void audqcelp_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init \n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg \n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + /* send mixer command */ + audpp_route_stream(audio->dec_id, + audio->source); + if (audio->pcm_feedback) { + audqcelp_config_hostpcm(audio); + audqcelp_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status\n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audqcelp_buffer_refresh(audio); + break; + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AUDPP_MSG_AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +struct msm_adsp_ops audplay_adsp_ops_qcelp = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_QCELP; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_v13k cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_V13K_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = 8000; + cmd.stereo_cfg = AUDPP_CMD_PCM_INTF_MONO_V; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDQCELP_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len / 2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audqcelp_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + refresh_cmd.buf_read_count = 0; + MM_DBG("buf0_addr=%x buf0_len=%d\n", refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audqcelp_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = 1; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); +} + +static void audqcelp_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audqcelp_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->out_needed = 0; +} + +static void audqcelp_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audqcelp_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audqcelp_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audqcelp_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); +} + +static int audqcelp_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audqcelp_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audqcelp_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audqcelp_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audqcelp_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + + +static long audqcelp_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audqcelp_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audqcelp_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audqcelp_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audqcelp_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audqcelp_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audqcelp_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audqcelp_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audqcelp_disable(audio); + audio->stopped = 1; + audqcelp_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audqcelp_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + if (copy_from_user(&config, (void *)arg, + sizeof(config))) { + rc = -EFAULT; + break; + } + audio->mfield = config.meta_field; + MM_DBG("AUDIO_SET_CONFIG applicable \ + for metafield configuration\n"); + rc = 0; + break; + } + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = BUF_COUNT; + config.sample_rate = 8000; + config.channel_count = 1; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + + if (copy_from_user(&config, (void *)arg, + sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buf %d\n", + config.buffer_count * config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("no mem for read buf\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr 0x%08x \ + kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audqcelp_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audqcelp_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d\n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver does + not know frame size, read count must be greater or equal + to size of PCM samples */ + MM_DBG("read stop - partial frame\n"); + break; + } else { + MM_DBG("read from in[%d]\n", audio->read_next); + + if (copy_to_user(buf, + audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x\n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; + /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audqcelp_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audqcelp_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + int rc = 0; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audqcelp_send_data(audio, 0); + +done: + return rc; +} + +static ssize_t audqcelp_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDQCELP_EOS_NONE; + unsigned short mfield_size = 0; + + MM_DBG("cnt=%d\n", count); + + if (count & 1) + return -EINVAL; + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + MM_DBG("buffer available\n"); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDQCELP_EOS_FLG_OFFSET] & + AUDQCELP_EOS_FLG_MASK) { + MM_DBG("EOS SET\n"); + eos_condition = AUDQCELP_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDQCELP_EOS_FLG_OFFSET] &= + ~AUDQCELP_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + frame->used = xfer + mfield_size; + audio->out_head ^= 1; + count -= xfer; + buf += xfer; + audqcelp_send_data(audio, 0); + } + if (eos_condition == AUDQCELP_EOS_SET) + rc = audqcelp_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audqcelp_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int) audio); + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audqcelp_disable(audio); + audqcelp_flush(audio); + audqcelp_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->opened = 0; + audio->event_abort = 1; + wake_up(&audio->event_wait); + audqcelp_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audqcelp_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audqcelp_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audqcelp_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audqcelp_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audqcelp_suspend(struct early_suspend *h) +{ + struct audqcelp_suspend_ctl *ctl = + container_of(h, struct audqcelp_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audqcelp_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audqcelp_resume(struct early_suspend *h) +{ + struct audqcelp_suspend_ctl *ctl = + container_of(h, struct audqcelp_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audqcelp_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audqcelp_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audqcelp_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 1024; + static char buffer[1024]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audqcelp_debug_fops = { + .read = audqcelp_debug_read, + .open = audqcelp_debug_open, +}; +#endif + +static int audqcelp_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + struct audqcelp_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_qcelp_" + 5]; +#endif + + /* Create audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance\n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_QCELP; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->phys)) { + MM_ERR("could not allocate write buffers, freeing instance \ + 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->phys, (int)audio->data); + } + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_qcelp, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->avsync_wait); + + /* Initialize buffer */ + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = BUFSZ; + + audio->out[1].data = audio->data + BUFSZ; + audio->out[1].addr = audio->phys + BUFSZ; + audio->out[1].size = BUFSZ; + + audio->vol_pan.volume = 0x2000; + + audqcelp_flush(audio); + + file->private_data = audio; + audio->opened = 1; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + qcelp_listner, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listnet\n", __func__); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_qcelp_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audqcelp_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audqcelp_resume; + audio->suspend_ctl.node.suspend = audqcelp_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDQCELP_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audqcelp_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_qcelp_fops = { + .owner = THIS_MODULE, + .open = audqcelp_open, + .release = audqcelp_release, + .read = audqcelp_read, + .write = audqcelp_write, + .unlocked_ioctl = audqcelp_ioctl, + .fsync = audqcelp_fsync, +}; + +struct miscdevice audio_qcelp_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_qcelp", + .fops = &audio_qcelp_fops, +}; + +static int __init audqcelp_init(void) +{ + return misc_register(&audio_qcelp_misc); +} + +static void __exit audqcelp_exit(void) +{ + misc_deregister(&audio_qcelp_misc); +} + +module_init(audqcelp_init); +module_exit(audqcelp_exit); + +MODULE_DESCRIPTION("MSM QCELP 13K driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_qcelp_in.c b/arch/arm/mach-msm/qdsp5v2/audio_qcelp_in.c new file mode 100644 index 00000000000..47bd589ff1b --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_qcelp_in.c @@ -0,0 +1,1497 @@ +/* + * qcelp audio input device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#define META_OUT_SIZE 24 +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM 8 +#define QCELP_FRAME_SIZE 36 /* 36 bytes data */ +#define FRAME_SIZE (22 * 2) /* 36 bytes data */ + /* 36 bytes data + 24 meta field*/ +#define NT_FRAME_SIZE (QCELP_FRAME_SIZE + META_OUT_SIZE) +#define DMASZ (NT_FRAME_SIZE * FRAME_NUM) +#define OUT_FRAME_NUM (2) +#define OUT_BUFFER_SIZE (4 * 1024 + META_OUT_SIZE) +#define BUFFER_SIZE (OUT_BUFFER_SIZE * OUT_FRAME_NUM) + + +#define AUDPREPROC_QCELP_EOS_FLG_OFFSET 0x0A +#define AUDPREPROC_QCELP_EOS_FLG_MASK 0x01 +#define AUDPREPROC_QCELP_EOS_NONE 0x0 /* No EOS detected */ +#define AUDPREPROC_QCELP_EOS_SET 0x1 /* EOS set in meta field */ + +struct buffer { + void *data; + uint32_t size; + uint32_t read; + uint32_t addr; + uint32_t used; + uint32_t mfield_sz; +}; + +struct audio_in { + struct buffer in[FRAME_NUM]; + + spinlock_t dsp_lock; + + atomic_t in_bytes; + atomic_t in_samples; + + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + wait_queue_head_t wait_enable; + /*write section*/ + struct buffer out[OUT_FRAME_NUM]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + uint32_t out_count; + + struct mutex write_lock; + wait_queue_head_t write_wait; + int32_t out_phys; /* physical address of write buffer */ + char *out_data; + int mfield; /* meta field embedded in data */ + int wflush; /*write flush */ + int rflush; /*read flush*/ + int out_frame_cnt; + + struct msm_adsp_module *audrec; + + struct audrec_session_info session_info; /*audrec session info*/ + + /* configuration to use on next enable */ + uint32_t buffer_size; /* Frame size (36 bytes) */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t enc_type; + + struct msm_audio_qcelp_enc_config cfg; + uint32_t rec_mode; + + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + uint32_t mode; + uint32_t eos_ack; + uint32_t flush_ack; + + const char *module_name; + unsigned queue_ids; + uint16_t enc_id; + + uint16_t source; /* Encoding source bit mask */ + uint32_t device_events; + uint32_t in_call; + uint32_t dev_cnt; + int voice_state; + spinlock_t dev_lock; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ +}; + +struct audio_frame { + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + unsigned char raw_bitstream[]; /* samples */ +} __attribute__((packed)); + +struct audio_frame_nt { + uint16_t metadata_len; + uint16_t frame_count_lsw; + uint16_t frame_count_msw; + uint16_t frame_length; + uint16_t erased_pcm; + uint16_t reserved; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; + unsigned char raw_bitstream[]; /* samples */ +} __attribute__((packed)); + +struct qcelp_encoded_meta_out { + uint16_t metadata_len; + uint16_t time_stamp_dword_lsw; + uint16_t time_stamp_dword_msw; + uint16_t time_stamp_lsw; + uint16_t time_stamp_msw; + uint16_t nflag_lsw; + uint16_t nflag_msw; +}; + +/* Audrec Queue command sent macro's */ +#define audrec_send_bitstreamqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_ids & 0xFFFF0000) >> 16),\ + cmd, len) + +#define audrec_send_audrecqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_ids & 0x0000FFFF),\ + cmd, len) + +/* DSP command send functions */ +static int audqcelp_in_enc_config(struct audio_in *audio, int enable); +static int audqcelp_in_param_config(struct audio_in *audio); +static int audqcelp_in_mem_config(struct audio_in *audio); +static int audqcelp_in_record_config(struct audio_in *audio, int enable); +static int audqcelp_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt); + +static void audqcelp_in_get_dsp_frames(struct audio_in *audio); +static int audpcm_config(struct audio_in *audio); +static void audqcelp_out_flush(struct audio_in *audio); +static int audpreproc_cmd_cfg_routing_mode(struct audio_in *audio); +static void audpreproc_pcm_send_data(struct audio_in *audio, unsigned needed); +static void audqcelp_nt_in_get_dsp_frames(struct audio_in *audio); + +static void audqcelp_in_flush(struct audio_in *audio); + +static void qcelp_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio_in *audio = (struct audio_in *) private_data; + unsigned long flags; + + MM_DBG("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: { + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt++; + if (!audio->in_call) + audio->source |= (0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((audio->running == 1) && (audio->enabled == 1) && + (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) + audqcelp_in_record_config(audio, 1); + } + break; + case AUDDEV_EVT_DEV_RLS: { + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + spin_lock_irqsave(&audio->dev_lock, flags); + audio->dev_cnt--; + if (!audio->in_call) + audio->source &= ~(0x1 << evt_payload->routing_id); + spin_unlock_irqrestore(&audio->dev_lock, flags); + + if ((!audio->running) || (!audio->enabled)) + break; + + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + /* Turn of as per source */ + if (audio->source) + audqcelp_in_record_config(audio, 1); + else + /* Turn off all */ + audqcelp_in_record_config(audio, 0); + } + } + break; + case AUDDEV_EVT_VOICE_STATE_CHG: { + MM_DBG("AUDDEV_EVT_VOICE_STATE_CHG, state = %d\n", + evt_payload->voice_state); + audio->voice_state = evt_payload->voice_state; + if (audio->in_call && audio->running && + (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) { + if (audio->voice_state == VOICE_STATE_INCALL) + audqcelp_in_record_config(audio, 1); + else if (audio->voice_state == VOICE_STATE_OFFCALL) { + audqcelp_in_record_config(audio, 0); + wake_up(&audio->wait); + } + } + + break; + } + default: + MM_ERR("wrong event %d\n", evt_id); + break; + } +} + +/* ------------------- dsp preproc event handler--------------------- */ +static void audpreproc_dsp_event(void *data, unsigned id, void *msg) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDPREPROC_ERROR_MSG: { + struct audpreproc_err_msg *err_msg = msg; + + MM_ERR("ERROR_MSG: stream id %d err idx %d\n", + err_msg->stream_id, err_msg->aud_preproc_err_idx); + /* Error case */ + wake_up(&audio->wait_enable); + break; + } + case AUDPREPROC_CMD_CFG_DONE_MSG: { + MM_DBG("CMD_CFG_DONE_MSG \n"); + break; + } + case AUDPREPROC_CMD_ENC_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_cfg_done_msg *enc_cfg_msg = msg; + + MM_DBG("CMD_ENC_CFG_DONE_MSG: stream id %d enc type \ + 0x%8x\n", enc_cfg_msg->stream_id, + enc_cfg_msg->rec_enc_type); + /* Encoder enable success */ + if (enc_cfg_msg->rec_enc_type & ENCODE_ENABLE) { + if(audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + MM_DBG("routing command\n"); + audpreproc_cmd_cfg_routing_mode(audio); + } else { + audqcelp_in_param_config(audio); + } + } else { /* Encoder disable success */ + audio->running = 0; + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + audqcelp_in_record_config(audio, 0); + else + wake_up(&audio->wait_enable); + } + break; + } + case AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: { + MM_DBG("CMD_ENC_PARAM_CFG_DONE_MSG\n"); + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) + audqcelp_in_mem_config(audio); + else + audpcm_config(audio); + break; + } + case AUDPREPROC_CMD_ROUTING_MODE_DONE_MSG: { + struct audpreproc_cmd_routing_mode_done\ + *routing_cfg_done_msg = msg; + if (routing_cfg_done_msg->configuration == 0) { + MM_INFO("routing configuration failed\n"); + audio->running = 0; + } else + audqcelp_in_param_config(audio); + break; + } + case AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: { + MM_DBG("AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG \n"); + wake_up(&audio->wait_enable); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +/* ------------------- dsp audrec event handler--------------------- */ +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + struct audio_in *audio = data; + + switch (id) { + case AUDREC_CMD_MEM_CFG_DONE_MSG: { + MM_DBG("CMD_MEM_CFG_DONE MSG DONE\n"); + audio->running = 1; + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if ((!audio->in_call && (audio->dev_cnt > 0)) || + (audio->in_call && + (audio->voice_state \ + == VOICE_STATE_INCALL))) + audqcelp_in_record_config(audio, 1); + } else { + audpreproc_pcm_send_data(audio, 1); + wake_up(&audio->wait_enable); + } + break; + } + case AUDREC_FATAL_ERR_MSG: { + struct audrec_fatal_err_msg fatal_err_msg; + + getevent(&fatal_err_msg, AUDREC_FATAL_ERR_MSG_LEN); + MM_ERR("FATAL_ERR_MSG: err id %d\n", + fatal_err_msg.audrec_err_id); + /* Error stop the encoder */ + audio->stopped = 1; + wake_up(&audio->wait); + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + wake_up(&audio->write_wait); + break; + } + case AUDREC_UP_PACKET_READY_MSG: { + struct audrec_up_pkt_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packet_write_cnt_lsw, \ + pkt_ready_msg.audrec_packet_write_cnt_msw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_lsw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_msw); + + audqcelp_in_get_dsp_frames(audio); + break; + } + case AUDREC_CMD_PCM_BUFFER_PTR_UPDATE_ARM_TO_ENC_MSG: { + MM_DBG("ptr_update recieved from DSP\n"); + audpreproc_pcm_send_data(audio, 1); + break; + } + case AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG: { + MM_ERR("AUDREC_CMD_PCM_CFG_ARM_TO_ENC_DONE_MSG"); + audqcelp_in_mem_config(audio); + break; + } + case AUDREC_UP_NT_PACKET_READY_MSG: { + struct audrec_up_nt_packet_ready_msg pkt_ready_msg; + + getevent(&pkt_ready_msg, AUDREC_UP_NT_PACKET_READY_MSG_LEN); + MM_DBG("UP_NT_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packetwrite_cnt_lsw, \ + pkt_ready_msg.audrec_packetwrite_cnt_msw, \ + pkt_ready_msg.audrec_upprev_readcount_lsw, \ + pkt_ready_msg.audrec_upprev_readcount_msw); + + audqcelp_nt_in_get_dsp_frames(audio); + break; + } + case AUDREC_CMD_EOS_ACK_MSG: { + MM_DBG("eos ack recieved\n"); + break; + } + case AUDREC_CMD_FLUSH_DONE_MSG: { + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 1; + wake_up(&audio->write_wait); + MM_DBG("flush ack recieved\n"); + break; + } + case ADSP_MESSAGE_ID: { + MM_DBG("Received ADSP event:module audrectask\n"); + break; + } + default: + MM_ERR("Unknown Event id %d\n", id); + } +} + +static void audqcelp_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame *frame; + uint32_t index; + unsigned long flags; + + MM_DBG("head = %d\n", audio->in_head); + index = audio->in_head; + + frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(*frame)); + + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = frame->frame_length; + + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) { + MM_ERR("Error! not able to keep up the read\n"); + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + MM_ERR("in_count = %d\n", audio->in_count); + } else + audio->in_count++; + + audqcelp_dsp_read_buffer(audio, audio->dsp_cnt++); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + wake_up(&audio->wait); +} + +static void audqcelp_nt_in_get_dsp_frames(struct audio_in *audio) +{ + struct audio_frame_nt *nt_frame; + uint32_t index; + unsigned long flags; + MM_DBG("head = %d\n", audio->in_head); + index = audio->in_head; + nt_frame = (void *) (((char *)audio->in[index].data) - \ + sizeof(struct audio_frame_nt)); + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->in[index].size = nt_frame->frame_length; + /* statistics of read */ + atomic_add(audio->in[index].size, &audio->in_bytes); + atomic_add(1, &audio->in_samples); + + audio->in_head = (audio->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (audio->in_head == audio->in_tail) + MM_DBG("Error! not able to keep up the read\n"); + else + audio->in_count++; + + spin_unlock_irqrestore(&audio->dsp_lock, flags); + wake_up(&audio->wait); +} + + +struct msm_adsp_ops audrec_qcelp_adsp_ops = { + .event = audrec_dsp_event, +}; + +static int audpreproc_pcm_buffer_ptr_refresh(struct audio_in *audio, + unsigned idx, unsigned len) +{ + struct audrec_cmd_pcm_buffer_ptr_refresh_arm_enc cmd; + + if (len == META_OUT_SIZE) + len = len / 2; + else + len = (len + META_OUT_SIZE) / 2; + MM_DBG("len = %d\n", len); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_BUFFER_PTR_REFRESH_ARM_TO_ENC; + cmd.num_buffers = 1; + if (cmd.num_buffers == 1) { + cmd.buf_address_length[0] = (audio->out[idx].addr & + 0xffff0000) >> 16; + cmd.buf_address_length[1] = (audio->out[idx].addr & + 0x0000ffff); + cmd.buf_address_length[2] = (len & 0xffff0000) >> 16; + cmd.buf_address_length[3] = (len & 0x0000ffff); + } + audio->out_frame_cnt++; + return audrec_send_audrecqueue(audio, (void *)&cmd, + (unsigned int)sizeof(cmd)); +} + + +static int audpcm_config(struct audio_in *audio) +{ + struct audrec_cmd_pcm_cfg_arm_to_enc cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PCM_CFG_ARM_TO_ENC; + cmd.config_update_flag = AUDREC_PCM_CONFIG_UPDATE_FLAG_ENABLE; + cmd.enable_flag = AUDREC_ENABLE_FLAG_VALUE; + cmd.sampling_freq = audio->samp_rate; + if (!audio->channel_mode) + cmd.channels = 1; + else + cmd.channels = 2; + cmd.frequency_of_intimation = 1; + cmd.max_number_of_buffers = OUT_FRAME_NUM; + return audrec_send_audrecqueue(audio, (void *)&cmd, + (unsigned int)sizeof(cmd)); +} + + +static int audpreproc_cmd_cfg_routing_mode(struct audio_in *audio) +{ + struct audpreproc_audrec_cmd_routing_mode cmd; + + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ROUTING_MODE; + cmd.stream_id = audio->enc_id; + if (audio->mode == MSM_ADSP_ENC_MODE_NON_TUNNEL) + cmd.routing_mode = 1; + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + + + +static int audqcelp_in_enc_config(struct audio_in *audio, int enable) +{ + struct audpreproc_audrec_cmd_enc_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG_2; + cmd.stream_id = audio->enc_id; + + if (enable) + cmd.audrec_enc_type = audio->enc_type | ENCODE_ENABLE; + else + cmd.audrec_enc_type &= ~(ENCODE_ENABLE); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audqcelp_in_param_config(struct audio_in *audio) +{ + struct audpreproc_audrec_cmd_parm_cfg_qcelp13k cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPREPROC_AUDREC_CMD_PARAM_CFG; + cmd.common.stream_id = audio->enc_id; + + cmd.enc_min_rate = audio->cfg.min_bit_rate; + cmd.enc_max_rate = audio->cfg.max_bit_rate; + cmd.rate_modulation_cmd = 0; /* Default set to 0 */ + cmd.reduced_rate_level = 0; /* Default set to 0 */ + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +/* To Do: msm_snddev_route_enc(audio->enc_id); */ +static int audqcelp_in_record_config(struct audio_in *audio, int enable) +{ + struct audpreproc_afe_cmd_audio_record_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG; + cmd.stream_id = audio->enc_id; + if (enable) + cmd.destination_activity = AUDIO_RECORDING_TURN_ON; + else + cmd.destination_activity = AUDIO_RECORDING_TURN_OFF; + + cmd.source_mix_mask = audio->source; + if (audio->enc_id == 2) { + if ((cmd.source_mix_mask & + INTERNAL_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & AUX_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_UL_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_DL_SOURCE_MIX_MASK)) { + cmd.pipe_id = SOURCE_PIPE_1; + } + if (cmd.source_mix_mask & + AUDPP_A2DP_PIPE_SOURCE_MIX_MASK) + cmd.pipe_id |= SOURCE_PIPE_0; + } + MM_DBG("stream_id %x destination_activity %x \ + source_mix_mask %x pipe_id %x",\ + cmd.stream_id, cmd.destination_activity, + cmd.source_mix_mask, cmd.pipe_id); + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int audqcelp_in_mem_config(struct audio_in *audio) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) audio->data; + int n; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_MEM_CFG_CMD; + cmd.audrec_up_pkt_intm_count = 1; + cmd.audrec_ext_pkt_start_addr_msw = audio->phys >> 16; + cmd.audrec_ext_pkt_start_addr_lsw = audio->phys; + cmd.audrec_ext_pkt_buf_number = FRAME_NUM; + MM_DBG("audio->phys = %x\n", audio->phys); + /* prepare buffer pointers: + * T:36 bytes qcelp ppacket + 4 halfword header + * NT:36 bytes qcelp packet + 12 halfword header + */ + for (n = 0; n < FRAME_NUM; n++) { + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + audio->in[n].data = data + 4; + data += (FRAME_SIZE/2); + MM_DBG("0x%8x\n", (int)(audio->in[n].data - 8)); + } else { + audio->in[n].data = data + 12; + data += ((QCELP_FRAME_SIZE) / 2) + 12; + MM_DBG("0x%8x\n", (int)(audio->in[n].data - 24)); + } + } + return audrec_send_audrecqueue(audio, &cmd, sizeof(cmd)); +} + +static int audqcelp_dsp_read_buffer(struct audio_in *audio, uint32_t read_cnt) +{ + struct up_audrec_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = UP_AUDREC_PACKET_EXT_PTR; + cmd.audrec_up_curr_read_count_msw = read_cnt >> 16; + cmd.audrec_up_curr_read_count_lsw = read_cnt; + + return audrec_send_bitstreamqueue(audio, &cmd, sizeof(cmd)); +} +static int audqcelp_flush_command(struct audio_in *audio) +{ + struct audrec_cmd_flush cmd; + MM_DBG("\n"); + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_FLUSH; + return audrec_send_audrecqueue(audio, &cmd, sizeof(cmd)); +} + +/* must be called with audio->lock held */ +static int audqcelp_in_enable(struct audio_in *audio) +{ + if (audio->enabled) + return 0; + + if (audpreproc_enable(audio->enc_id, &audpreproc_dsp_event, audio)) { + MM_ERR("msm_adsp_enable(audpreproc) failed\n"); + return -ENODEV; + } + + if (msm_adsp_enable(audio->audrec)) { + MM_ERR("msm_adsp_enable(audrec) failed\n"); + audpreproc_disable(audio->enc_id, audio); + return -ENODEV; + } + audio->enabled = 1; + audqcelp_in_enc_config(audio, 1); + + return 0; +} + +/* must be called with audio->lock held */ +static int audqcelp_in_disable(struct audio_in *audio) +{ + if (audio->enabled) { + audio->enabled = 0; + audqcelp_in_enc_config(audio, 0); + wake_up(&audio->wait); + wait_event_interruptible_timeout(audio->wait_enable, + audio->running == 0, 1*HZ); + msm_adsp_disable(audio->audrec); + audpreproc_disable(audio->enc_id, audio); + } + return 0; +} + +static void audqcelp_ioport_reset(struct audio_in *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audqcelp_in_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->wait); + mutex_lock(&audio->read_lock); + audqcelp_out_flush(audio); + mutex_unlock(&audio->read_lock); +} + +static void audqcelp_in_flush(struct audio_in *audio) +{ + int i; + + audio->dsp_cnt = 0; + audio->in_head = 0; + audio->in_tail = 0; + audio->in_count = 0; + audio->eos_ack = 0; + for (i = 0; i < FRAME_NUM; i++) { + audio->in[i].size = 0; + audio->in[i].read = 0; + } + MM_DBG("in_bytes %d\n", atomic_read(&audio->in_bytes)); + MM_DBG("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); +} + +static void audqcelp_out_flush(struct audio_in *audio) +{ + int i; + + audio->out_head = 0; + audio->out_tail = 0; + audio->out_count = 0; + for (i = 0; i < OUT_FRAME_NUM; i++) { + audio->out[i].size = 0; + audio->out[i].read = 0; + audio->out[i].used = 0; + } +} + +/* ------------------- device --------------------- */ +static long audqcelp_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct audio_in *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + uint32_t freq; + freq = 48000; + MM_DBG("AUDIO_START\n"); + if (audio->in_call && (audio->voice_state != + VOICE_STATE_INCALL)) { + rc = -EPERM; + break; + } + rc = msm_snddev_request_freq(&freq, audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("sample rate configured %d\n", freq); + if (rc < 0) { + MM_DBG(" Sample rate can not be set, return code %d\n", + rc); + msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + break; + } + /*update aurec session info in audpreproc layer*/ + audio->session_info.session_id = audio->enc_id; + audio->session_info.sampling_freq = audio->samp_rate; + audpreproc_update_audrec_info(&audio->session_info); + rc = audqcelp_in_enable(audio); + if (!rc) { + rc = + wait_event_interruptible_timeout(audio->wait_enable, + audio->running != 0, 1*HZ); + MM_DBG("state %d rc = %d\n", audio->running, rc); + + if (audio->running == 0) + rc = -ENODEV; + else + rc = 0; + } + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + rc = audqcelp_in_disable(audio); + rc = msm_snddev_withdraw_freq(audio->enc_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + audio->stopped = 1; + break; + } + case AUDIO_FLUSH: { + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audqcelp_ioport_reset(audio); + if (audio->running) { + audqcelp_flush_command(audio); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + /* Allow only single frame */ + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if (cfg.buffer_size != (FRAME_SIZE - 8)) { + rc = -EINVAL; + break; + } + } else { + if (cfg.buffer_size != (QCELP_FRAME_SIZE + 14)) { + rc = -EINVAL; + break; + } + } + audio->buffer_size = cfg.buffer_size; + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->buffer_size; + cfg.buffer_count = FRAME_NUM; + if (copy_to_user((void *) arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_QCELP_ENC_CONFIG: { + if (copy_to_user((void *) arg, &audio->cfg, sizeof(audio->cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_QCELP_ENC_CONFIG: { + struct msm_audio_qcelp_enc_config cfg; + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + MM_DBG("0X%8x, 0x%8x, 0x%8x\n", cfg.min_bit_rate, \ + cfg.max_bit_rate, cfg.cdma_rate); + if (cfg.min_bit_rate > CDMA_RATE_FULL || \ + cfg.min_bit_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid min bitrate\n"); + rc = -EFAULT; + break; + } + if (cfg.max_bit_rate > CDMA_RATE_FULL || \ + cfg.max_bit_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid max bitrate\n"); + rc = -EFAULT; + break; + } + /* Recording Does not support Erase and Blank */ + if (cfg.cdma_rate > CDMA_RATE_FULL || + cfg.cdma_rate < CDMA_RATE_EIGHTH) { + MM_ERR("invalid qcelp cdma rate\n"); + rc = -EFAULT; + break; + } + memcpy(&audio->cfg, &cfg, sizeof(cfg)); + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = OUT_BUFFER_SIZE; + cfg.buffer_count = OUT_FRAME_NUM; + cfg.sample_rate = audio->samp_rate; + cfg.channel_count = audio->channel_mode; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_INCALL: { + struct msm_voicerec_mode cfg; + unsigned long flags; + if (audio->mode == MSM_AUD_ENC_MODE_TUNNEL) { + if (copy_from_user(&cfg, (void *) arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (cfg.rec_mode != VOC_REC_BOTH && + cfg.rec_mode != VOC_REC_UPLINK && + cfg.rec_mode != VOC_REC_DOWNLINK) { + MM_ERR("invalid rec_mode\n"); + rc = -EINVAL; + break; + } else { + spin_lock_irqsave(&audio->dev_lock, flags); + if (cfg.rec_mode == VOC_REC_UPLINK) + audio->source = \ + VOICE_UL_SOURCE_MIX_MASK; + else if (cfg.rec_mode == VOC_REC_DOWNLINK) + audio->source = \ + VOICE_DL_SOURCE_MIX_MASK; + else + audio->source = \ + VOICE_DL_SOURCE_MIX_MASK | + VOICE_UL_SOURCE_MIX_MASK ; + audio->in_call = 1; + spin_unlock_irqrestore(&audio->dev_lock, flags); + } + } + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &audio->enc_id, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static ssize_t audqcelp_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + unsigned long flags; + const char __user *start = buf; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + struct qcelp_encoded_meta_out meta_field; + struct audio_frame_nt *nt_frame; + MM_DBG(" count = %d\n", count); + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->wait, (audio->in_count > 0) || audio->stopped || + audio->rflush || + ((audio->mode == MSM_AUD_ENC_MODE_TUNNEL) && + audio->in_call && audio->running && + (audio->voice_state == VOICE_STATE_OFFCALL))); + if (rc < 0) + break; + + if (audio->rflush) { + rc = -EBUSY; + break; + } + if (audio->stopped && !audio->in_count) { + MM_DBG("Driver in stop state, No more buffer to read"); + rc = 0;/* End of File */ + break; + } else if ((audio->mode == MSM_AUD_ENC_MODE_TUNNEL) && + audio->in_call && audio->running && + (audio->voice_state \ + == VOICE_STATE_OFFCALL)) { + MM_DBG("Not Permitted Voice Terminated\n"); + rc = -EPERM; /* Voice Call stopped */ + break; + } + + index = audio->in_tail; + data = (uint8_t *) audio->in[index].data; + size = audio->in[index].size; + + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) { + nt_frame = (struct audio_frame_nt *)(data - + sizeof(struct audio_frame_nt)); + memcpy((char *)&meta_field.time_stamp_dword_lsw, + (char *)&nt_frame->time_stamp_dword_lsw, + (sizeof(struct qcelp_encoded_meta_out) - \ + sizeof(uint16_t))); + meta_field.metadata_len = + sizeof(struct qcelp_encoded_meta_out); + if (copy_to_user((char *)start, + (char *)&meta_field, + sizeof(struct qcelp_encoded_meta_out))) { + rc = -EFAULT; + break; + } + if (nt_frame->nflag_lsw & 0x0001) { + MM_ERR("recieved EOS in read call\n"); + audio->eos_ack = 1; + } + buf += sizeof(struct qcelp_encoded_meta_out); + count -= sizeof(struct qcelp_encoded_meta_out); + } + if (count >= size) { + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&audio->dsp_lock, flags); + if (index != audio->in_tail) { + /* overrun -- data is + * invalid and we need to retry */ + spin_unlock_irqrestore(&audio->dsp_lock, flags); + continue; + } + audio->in[index].size = 0; + audio->in_tail = (audio->in_tail + 1) & (FRAME_NUM - 1); + audio->in_count--; + spin_unlock_irqrestore(&audio->dsp_lock, flags); + count -= size; + buf += size; + if ((audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL)) { + if (!audio->eos_ack) { + MM_DBG("sending read ptr command\ + %d %d\n", + audio->dsp_cnt, + audio->in_tail); + audqcelp_dsp_read_buffer(audio, + audio->dsp_cnt++); + } + } + } else { + MM_ERR("short read\n"); + break; + } + break; + } + mutex_unlock(&audio->read_lock); + + if (buf > start) + return buf - start; + + return rc; +} + +static void audpreproc_pcm_send_data(struct audio_in *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + MM_DBG("\n"); + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + audpreproc_pcm_buffer_ptr_refresh(audio, + audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } + done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + + +static int audqcelp_in_fsync(struct file *file, int datasync) + +{ + struct audio_in *audio = file->private_data; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + if (!audio->running || (audio->mode == MSM_AUD_ENC_MODE_TUNNEL)) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + audio->wflush); + MM_DBG("waked on by some event audio->wflush = %d\n", audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; + +} + + int audpreproc_qcelp_process_eos(struct audio_in *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + struct buffer *frame; + int rc = 0; + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + MM_DBG("copying meta_out frame->used = %d\n", frame->used); + audpreproc_pcm_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audqcelp_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_in *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + char *cpy_ptr; + int rc = 0, eos_condition = AUDPREPROC_QCELP_EOS_NONE; + unsigned short mfield_size = 0; + int write_count = 0; + + MM_DBG("cnt=%d\n", count); + if (count & 1) + return -EINVAL; + + if (audio->mode != MSM_AUD_ENC_MODE_NONTUNNEL) + return -EINVAL; + + mutex_lock(&audio->write_lock); + frame = audio->out + audio->out_head; + /* if supplied count is more than driver buffer size + * then only copy driver buffer size + */ + if (count > frame->size) + count = frame->size; + + write_count = count; + cpy_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto error; + + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto error; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + goto error; + } else if (mfield_size > count) { + rc = -EINVAL; + goto error; + } + MM_DBG("mf offset_val %x\n", mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + goto error; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDPREPROC_QCELP_EOS_FLG_OFFSET] & + AUDPREPROC_QCELP_EOS_FLG_MASK) { + eos_condition = AUDPREPROC_QCELP_EOS_SET; + MM_DBG("EOS SET\n"); + if (mfield_size == count) { + buf += mfield_size; + eos_condition = 0; + goto exit; + } else + cpy_ptr[AUDPREPROC_QCELP_EOS_FLG_OFFSET] &= + ~AUDPREPROC_QCELP_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + MM_DBG("copying the stream count = %d\n", count); + if (copy_from_user(cpy_ptr, buf, count)) { + rc = -EFAULT; + goto error; + } +exit: + frame->used = count; + audio->out_head ^= 1; + if (!audio->flush_ack) + audpreproc_pcm_send_data(audio, 0); + else { + audpreproc_pcm_send_data(audio, 1); + audio->flush_ack = 0; + } + if (eos_condition == AUDPREPROC_QCELP_EOS_SET) + rc = audpreproc_qcelp_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + return write_count; +error: + mutex_unlock(&audio->write_lock); + return rc; +} + +static int audqcelp_in_release(struct inode *inode, struct file *file) +{ + struct audio_in *audio = file->private_data; + + mutex_lock(&audio->lock); + audio->in_call = 0; + /* with draw frequency for session + incase not stopped the driver */ + msm_snddev_withdraw_freq(audio->enc_id, SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, audio->enc_id); + /*reset the sampling frequency information at audpreproc layer*/ + audio->session_info.sampling_freq = 0; + audpreproc_update_audrec_info(&audio->session_info); + audqcelp_in_disable(audio); + audqcelp_in_flush(audio); + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + audio->audrec = NULL; + audio->opened = 0; + if (audio->data) { + iounmap(audio->data); + pmem_kfree(audio->phys); + audio->data = NULL; + } + if (audio->out_data) { + iounmap(audio->out_data); + pmem_kfree(audio->out_phys); + audio->out_data = NULL; + } + mutex_unlock(&audio->lock); + return 0; +} + +struct audio_in the_audio_qcelp_in; +static int audqcelp_in_open(struct inode *inode, struct file *file) +{ + struct audio_in *audio = &the_audio_qcelp_in; + int rc; + int encid; + + mutex_lock(&audio->lock); + if (audio->opened) { + rc = -EBUSY; + goto done; + } + audio->phys = pmem_kalloc(DMASZ, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, DMASZ); + if (!audio->data) { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->phys); + goto done; + } + } else { + MM_ERR("could not allocate DMA buffers\n"); + rc = -ENOMEM; + goto done; + } + MM_DBG("Memory addr = 0x%8x phy addr = 0x%8x\n",\ + (int) audio->data, (int) audio->phys); + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_NONTUNNEL; + MM_DBG("Opened for non tunnel mode encoding\n"); + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->mode = MSM_AUD_ENC_MODE_TUNNEL; + MM_DBG("Opened for tunnel mode encoding\n"); + } else { + MM_ERR("Invalid mode\n"); + rc = -EACCES; + goto done; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + if (audio->mode == MSM_AUD_ENC_MODE_NONTUNNEL) + audio->buffer_size = (QCELP_FRAME_SIZE + 14); + else + audio->buffer_size = (FRAME_SIZE - 8); + audio->enc_type = ENC_TYPE_V13K | audio->mode; + audio->samp_rate = 8000; + audio->channel_mode = AUDREC_CMD_MODE_MONO; + audio->cfg.cdma_rate = CDMA_RATE_FULL; + audio->cfg.min_bit_rate = CDMA_RATE_FULL; + audio->cfg.max_bit_rate = CDMA_RATE_FULL; + audio->source = INTERNAL_CODEC_TX_SOURCE_MIX_MASK; + audio->rec_mode = VOC_REC_UPLINK; + + encid = audpreproc_aenc_alloc(audio->enc_type, &audio->module_name, + &audio->queue_ids); + if (encid < 0) { + MM_ERR("No free encoder available\n"); + rc = -ENODEV; + goto done; + } + audio->enc_id = encid; + + rc = msm_adsp_get(audio->module_name, &audio->audrec, + &audrec_qcelp_adsp_ops, audio); + + if (rc) { + audpreproc_aenc_free(audio->enc_id); + goto done; + } + + audio->stopped = 0; + audio->source = 0; + audio->wflush = 0; + audio->rflush = 0; + audio->flush_ack = 0; + + audqcelp_in_flush(audio); + audqcelp_out_flush(audio); + + audio->out_phys = pmem_kalloc(BUFFER_SIZE, + PMEM_MEMTYPE_EBI1 | PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->out_phys)) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + goto evt_error; + } else { + audio->out_data = ioremap(audio->out_phys, BUFFER_SIZE); + if (!audio->out_data) { + MM_ERR("could not allocate write buffers\n"); + rc = -ENOMEM; + pmem_kfree(audio->out_phys); + goto evt_error; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + audio->out_phys, (int)audio->out_data); + } + + /* Initialize buffer */ + audio->out[0].data = audio->out_data + 0; + audio->out[0].addr = audio->out_phys + 0; + audio->out[0].size = OUT_BUFFER_SIZE; + + audio->out[1].data = audio->out_data + OUT_BUFFER_SIZE; + audio->out[1].addr = audio->out_phys + OUT_BUFFER_SIZE; + audio->out[1].size = OUT_BUFFER_SIZE; + + MM_DBG("audio->out[0].data = %d audio->out[1].data = %d", + (unsigned int)audio->out[0].data, + (unsigned int)audio->out[1].data); + audio->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_VOICE_STATE_CHG; + + audio->voice_state = msm_get_voice_state(); + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_ENC, audio->enc_id, + qcelp_in_listener, (void *) audio); + if (rc) { + MM_ERR("failed to register device event listener\n"); + iounmap(audio->out_data); + pmem_kfree(audio->out_phys); + goto evt_error; + } + audio->mfield = META_OUT_SIZE; + file->private_data = audio; + audio->opened = 1; + audio->out_frame_cnt++; +done: + mutex_unlock(&audio->lock); + return rc; +evt_error: + msm_adsp_put(audio->audrec); + audpreproc_aenc_free(audio->enc_id); + mutex_unlock(&audio->lock); + return rc; +} + +static const struct file_operations audio_in_fops = { + .owner = THIS_MODULE, + .open = audqcelp_in_open, + .release = audqcelp_in_release, + .read = audqcelp_in_read, + .write = audqcelp_in_write, + .fsync = audqcelp_in_fsync, + .unlocked_ioctl = audqcelp_in_ioctl, +}; + +struct miscdevice audio_qcelp_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_qcelp_in", + .fops = &audio_in_fops, +}; + +static int __init audqcelp_in_init(void) +{ + mutex_init(&the_audio_qcelp_in.lock); + mutex_init(&the_audio_qcelp_in.read_lock); + spin_lock_init(&the_audio_qcelp_in.dsp_lock); + spin_lock_init(&the_audio_qcelp_in.dev_lock); + init_waitqueue_head(&the_audio_qcelp_in.wait); + init_waitqueue_head(&the_audio_qcelp_in.wait_enable); + mutex_init(&the_audio_qcelp_in.write_lock); + init_waitqueue_head(&the_audio_qcelp_in.write_wait); + return misc_register(&audio_qcelp_in_misc); +} + +device_initcall(audqcelp_in_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_wma.c b/arch/arm/mach-msm/qdsp5v2/audio_wma.c new file mode 100644 index 00000000000..2695b833c80 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_wma.c @@ -0,0 +1,1785 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5v2/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Size must be power of 2 */ +#define BUFSZ_MAX 4110 /* Includes meta in size */ +#define BUFSZ_MIN 1038 /* Includes meta in size */ +#define DMASZ_MAX (BUFSZ_MAX * 2) +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_WMA 4 + +#define PCM_BUFSZ_MIN 8216 /* Hold one stereo WMA frame and meta out*/ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDWMA_METAFIELD_MASK 0xFFFF0000 +#define AUDWMA_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDWMA_EOS_FLG_MASK 0x01 +#define AUDWMA_EOS_NONE 0x0 /* No EOS detected */ +#define AUDWMA_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDWMA_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audwma_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audwma_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + + struct msm_audio_wma_config wma_config; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audwma_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + uint32_t device_events; + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audplay_config_hostpcm(struct audio *audio); +static void audplay_buffer_refresh(struct audio *audio); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audwma_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +static void wma_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + default: + MM_ERR(":ERROR:wrong event\n"); + break; + } +} +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audio_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("audio_update_pcm_buf_entry: \ + in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + } else { + MM_ERR("audio_update_pcm_buf_entry: \ + expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audplay_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audplay_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audio_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init\n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg\n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + /* send mixer command */ + audpp_route_stream(audio->dec_id, + audio->source); + if (audio->pcm_feedback) { + audplay_config_hostpcm(audio); + audplay_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status\n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audplay_buffer_refresh(audio); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AUDPP_MSG_AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +static struct msm_adsp_ops audplay_adsp_ops_wma = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_WMA; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_wma cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_WMA_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + + /* + * Test done for sample with the following configuration + * armdatareqthr = 1262 + * channelsdecoded = 1(MONO)/2(STEREO) + * wmabytespersec = Tested with 6003 Bytes per sec + * wmasamplingfreq = 44100 + * wmaencoderopts = 31 + */ + + cmd.armdatareqthr = audio->wma_config.armdatareqthr; + cmd.channelsdecoded = audio->wma_config.channelsdecoded; + cmd.wmabytespersec = audio->wma_config.wmabytespersec; + cmd.wmasamplingfreq = audio->wma_config.wmasamplingfreq; + cmd.wmaencoderopts = audio->wma_config.wmaencoderopts; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static void audplay_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + refresh_cmd.buf_read_count = 0; + + MM_DBG("buf0_addr=%x buf0_len=%d\n", + refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audplay_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); +} + + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (!audio->pcm_feedback) + cmd.decoder_id = 0; + else { + if (audio->mfield) + cmd.decoder_id = AUDWMA_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + } + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len/2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (audio->wflush) { + audio->out_needed = 1; + goto done; + } + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + MM_DBG("\n"); /* Macro prints the file name and function */ + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audio_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); +} + +static int audwma_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audwma_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audwma_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwma_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audwma_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audwma_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audwma_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audwma_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audwma_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwma_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audwma_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + audio->mfield = config.meta_field; + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_WMA_CONFIG:{ + if (copy_to_user((void *)arg, &audio->wma_config, + sizeof(audio->wma_config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_WMA_CONFIG:{ + struct msm_audio_wma_config usr_config; + + if (copy_from_user + (&usr_config, (void *)arg, + sizeof(usr_config))) { + rc = -EFAULT; + break; + } + + audio->wma_config = usr_config; + rc = 0; + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buffer %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("read buf alloc fail\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; + index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr \ + 0x%08x kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audio_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audplay_send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d \n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver + does not know frame size, read count must be greater + or equal to size of PCM samples */ + MM_DBG("audio_read: no partial frame done reading\n"); + break; + } else { + MM_DBG("audio_read: read from in[%d]\n", + audio->read_next); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audplay_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audwma_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + char *buf_ptr; + + if (audio->reserved) { + MM_DBG("flush reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + frame->used = 2; + audio->reserved = 0; + audplay_send_data(audio, 0); + } + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audplay_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDWMA_EOS_NONE; + unsigned dsize; + unsigned short mfield_size = 0; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("audio_write: mf offset_val %x\n", + mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDWMA_EOS_FLG_OFFSET] & + AUDWMA_EOS_FLG_MASK) { + MM_DBG("audio_write: EOS SET\n"); + eos_condition = AUDWMA_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDWMA_EOS_FLG_OFFSET] + &= ~AUDWMA_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("audio_write: continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + (frame->size - mfield_size) - 1 : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audplay_send_data(audio, 0); + } + } + if (eos_condition == AUDWMA_EOS_SET) + rc = audwma_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audio_disable(audio); + audio_flush(audio); + audio_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audwma_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audwma_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audwma_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audwma_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audwma_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audwma_suspend(struct early_suspend *h) +{ + struct audwma_suspend_ctl *ctl = + container_of(h, struct audwma_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audwma_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audwma_resume(struct early_suspend *h) +{ + struct audwma_suspend_ctl *ctl = + container_of(h, struct audwma_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audwma_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audwma_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audwma_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audwma_debug_fops = { + .read = audwma_debug_read, + .open = audwma_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + unsigned pmem_sz = DMASZ_MAX; + struct audwma_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_wma_" + 5]; +#endif + + /* Allocate Mem for audio instance */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_WMA; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d\n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr \ + 0x%08x\n", audio->phys, (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_wma, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->avsync_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = audio->out_dma_sz >> 1; + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + + audio->wma_config.armdatareqthr = 1262; + audio->wma_config.channelsdecoded = 2; + audio->wma_config.wmabytespersec = 6003; + audio->wma_config.wmasamplingfreq = 44100; + audio->wma_config.wmaencoderopts = 31; + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + + audio->vol_pan.volume = 0x2000; + + audio_flush(audio); + + file->private_data = audio; + audio->opened = 1; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + wma_listner, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listner\n", __func__); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_wma_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, + &audwma_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audwma_resume; + audio->suspend_ctl.node.suspend = audwma_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDWMA_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audwma_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_wma_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_fsync, +}; + +struct miscdevice audio_wma_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_wma", + .fops = &audio_wma_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_wma_misc); +} + +device_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audio_wmapro.c b/arch/arm/mach-msm/qdsp5v2/audio_wmapro.c new file mode 100644 index 00000000000..e9837be2dcb --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audio_wmapro.c @@ -0,0 +1,1801 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5v2/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Size must be power of 2 */ +#define BUFSZ_MAX 4110 /* Includes meta in size */ +#define BUFSZ_MIN 2062 /* Includes meta in size */ +#define DMASZ_MAX (BUFSZ_MAX * 2) +#define DMASZ_MIN (BUFSZ_MIN * 2) + +#define AUDPLAY_INVALID_READ_PTR_OFFSET 0xFFFF +#define AUDDEC_DEC_WMAPRO 13 + +#define PCM_BUFSZ_MIN 8216 /* Hold one stereo WMAPRO frame and meta out*/ +#define PCM_BUF_MAX_COUNT 5 /* DSP only accepts 5 buffers at most + but support 2 buffers currently */ +#define ROUTING_MODE_FTRT 1 +#define ROUTING_MODE_RT 2 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +#define AUDWMAPRO_METAFIELD_MASK 0xFFFF0000 +#define AUDWMAPRO_EOS_FLG_OFFSET 0x0A /* Offset from beginning of buffer */ +#define AUDWMAPRO_EOS_FLG_MASK 0x01 +#define AUDWMAPRO_EOS_NONE 0x0 /* No EOS detected */ +#define AUDWMAPRO_EOS_SET 0x1 /* EOS set in meta field */ + +#define AUDWMAPRO_EVENT_NUM 10 /* Default no. of pre-allocated event packets */ + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audwmapro_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct audwmapro_event{ + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audio { + struct buffer out[2]; + + spinlock_t dsp_lock; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + unsigned out_dma_sz; + + atomic_t out_bytes; + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + /* Host PCM section */ + struct buffer in[PCM_BUF_MAX_COUNT]; + struct mutex read_lock; + wait_queue_head_t read_wait; /* Wait queue for read */ + char *read_data; /* pointer to reader buffer */ + int32_t read_phys; /* physical address of reader buffer */ + uint8_t read_next; /* index to input buffers to be read next */ + uint8_t fill_next; /* index to buffer that DSP should be filling */ + uint8_t pcm_buf_count; /* number of pcm buffer allocated */ + /* ---- End of Host PCM section */ + + struct msm_adsp_module *audplay; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + + struct msm_audio_wmapro_config wmapro_config; + + /* data allocated for various buffers */ + char *data; + int32_t phys; /* physical address of write buffer */ + + int mfield; /* meta field embedded in data */ + int rflush; /* Read flush */ + int wflush; /* Write flush */ + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int pcm_feedback; + int buf_refresh; + int teos; /* valid only if tunnel mode & no data left for decoder */ + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + int reserved; /* A byte is being reserved */ + char rsv_byte; /* Handle odd length user data */ + + const char *module_name; + unsigned queue_id; + uint16_t dec_id; + uint32_t read_ptr_offset; + int16_t source; + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audwmapro_suspend_ctl suspend_ctl; +#endif + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + /* AV sync Info */ + int avsync_flag; /* Flag to indicate feedback from DSP */ + wait_queue_head_t avsync_wait;/* Wait queue for AV Sync Message */ + /* flags, 48 bits sample/bytes counter per channel */ + uint16_t avsync[AUDPP_AVSYNC_CH_COUNT * AUDPP_AVSYNC_NUM_WORDS + 1]; + + uint32_t device_events; + + int eq_enable; + int eq_needs_commit; + struct audpp_cmd_cfg_object_params_eqalizer eq; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + +static int auddec_dsp_config(struct audio *audio, int enable); +static void audpp_cmd_cfg_adec_params(struct audio *audio); +static void audpp_cmd_cfg_routing_mode(struct audio *audio); +static void audplay_send_data(struct audio *audio, unsigned needed); +static void audplay_config_hostpcm(struct audio *audio); +static void audplay_buffer_refresh(struct audio *audio); +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg); +static void audwmapro_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) + return 0; + + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + audio->out_tail = 0; + audio->out_needed = 0; + + if (msm_adsp_enable(audio->audplay)) { + MM_ERR("msm_adsp_enable(audplay) failed\n"); + return -ENODEV; + } + + if (audpp_enable(audio->dec_id, audio_dsp_event, audio)) { + MM_ERR("audpp_enable() failed\n"); + msm_adsp_disable(audio->audplay); + return -ENODEV; + } + + audio->enabled = 1; + return 0; +} + +static void wmapro_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG(":AUDDEV_EVT_DEV_RDY\n"); + audio->source |= (0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG(":AUDDEV_EVT_DEV_RLS\n"); + audio->source &= ~(0x1 << evt_payload->routing_id); + if (audio->running == 1 && audio->enabled == 1) + audpp_route_stream(audio->dec_id, audio->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->vol_pan.volume = evt_payload->session_vol; + MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + audio->vol_pan.volume); + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + break; + default: + MM_ERR(":ERROR:wrong event\n"); + break; + } +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + MM_DBG("\n"); /* Macro prints the file name and function */ + if (audio->enabled) { + audio->enabled = 0; + audio->dec_state = MSM_AUD_DECODER_STATE_NONE; + auddec_dsp_config(audio, 0); + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + if (rc == 0) + rc = -ETIMEDOUT; + else if (audio->dec_state != MSM_AUD_DECODER_STATE_CLOSE) + rc = -EFAULT; + else + rc = 0; + wake_up(&audio->write_wait); + wake_up(&audio->read_wait); + msm_adsp_disable(audio->audplay); + audpp_disable(audio->dec_id, audio); + audio->out_needed = 0; + } + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audio_update_pcm_buf_entry(struct audio *audio, + uint32_t *payload) +{ + uint8_t index; + unsigned long flags; + + if (audio->rflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + for (index = 0; index < payload[1]; index++) { + if (audio->in[audio->fill_next].addr == + payload[2 + index * 2]) { + MM_DBG("audio_update_pcm_buf_entry: \ + in[%d] ready\n", audio->fill_next); + audio->in[audio->fill_next].used = + payload[3 + index * 2]; + if ((++audio->fill_next) == audio->pcm_buf_count) + audio->fill_next = 0; + } else { + MM_ERR("audio_update_pcm_buf_entry: \ + expected=%x ret=%x\n", + audio->in[audio->fill_next].addr, + payload[1 + index * 2]); + break; + } + } + if (audio->in[audio->fill_next].used == 0) { + audplay_buffer_refresh(audio); + } else { + MM_DBG("read cannot keep up\n"); + audio->buf_refresh = 1; + } + wake_up(&audio->read_wait); + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static void audplay_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audio *audio = data; + uint32_t msg[28]; + + getevent(msg, sizeof(msg)); + + MM_DBG("msg_id=%x\n", id); + + switch (id) { + case AUDPLAY_MSG_DEC_NEEDS_DATA: + audplay_send_data(audio, 1); + break; + + case AUDPLAY_MSG_BUFFER_UPDATE: + audio_update_pcm_buf_entry(audio, msg); + break; + + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable(audplaytask)\n"); + break; + + default: + MM_ERR("unexpected message from decoder \n"); + break; + } +} + +static void audio_dsp_event(void *private, unsigned id, uint16_t *msg) +{ + struct audio *audio = private; + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned status = msg[1]; + + switch (status) { + case AUDPP_DEC_STATUS_SLEEP: { + uint16_t reason = msg[2]; + MM_DBG("decoder status:sleep reason = \ + 0x%04x\n", reason); + if ((reason == AUDPP_MSG_REASON_MEM) + || (reason == + AUDPP_MSG_REASON_NODECODER)) { + audio->dec_state = + MSM_AUD_DECODER_STATE_FAILURE; + wake_up(&audio->wait); + } else if (reason == AUDPP_MSG_REASON_NONE) { + /* decoder is in disable state */ + audio->dec_state = + MSM_AUD_DECODER_STATE_CLOSE; + wake_up(&audio->wait); + } + break; + } + case AUDPP_DEC_STATUS_INIT: + MM_DBG("decoder status: init\n"); + if (audio->pcm_feedback) + audpp_cmd_cfg_routing_mode(audio); + else + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_DEC_STATUS_CFG: + MM_DBG("decoder status: cfg\n"); + break; + case AUDPP_DEC_STATUS_PLAY: + MM_DBG("decoder status: play \n"); + audpp_route_stream(audio->dec_id, + audio->source); + if (audio->pcm_feedback) { + audplay_config_hostpcm(audio); + audplay_buffer_refresh(audio); + } + audio->dec_state = + MSM_AUD_DECODER_STATE_SUCCESS; + wake_up(&audio->wait); + break; + default: + MM_ERR("unknown decoder status\n"); + } + break; + } + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + auddec_dsp_config(audio, 1); + audio->out_needed = 0; + audio->running = 1; + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + audpp_dsp_set_eq(audio->dec_id, audio->eq_enable, + &audio->eq, POPP); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + audio->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + MM_DBG("ROUTING_ACK mode=%d\n", msg[1]); + audpp_cmd_cfg_adec_params(audio); + break; + + case AUDPP_MSG_FLUSH_ACK: + MM_DBG("FLUSH_ACK\n"); + audio->wflush = 0; + audio->rflush = 0; + wake_up(&audio->write_wait); + if (audio->pcm_feedback) + audplay_buffer_refresh(audio); + break; + + case AUDPP_MSG_PCMDMAMISSED: + MM_DBG("PCMDMAMISSED\n"); + audio->teos = 1; + wake_up(&audio->write_wait); + break; + + case AUDPP_MSG_AVSYNC_MSG: + MM_DBG("AUDPP_MSG_AVSYNC_MSG\n"); + memcpy(&audio->avsync[0], msg, sizeof(audio->avsync)); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); + break; + + default: + MM_ERR("UNKNOWN (%d)\n", id); + } + +} + +static struct msm_adsp_ops audplay_adsp_ops_wmapro = { + .event = audplay_dsp_event, +}; + +#define audplay_send_queue0(audio, cmd, len) \ + msm_adsp_write(audio->audplay, audio->queue_id, \ + cmd, len) + +static int auddec_dsp_config(struct audio *audio, int enable) +{ + struct audpp_cmd_cfg_dec_type cfg_dec_cmd; + + memset(&cfg_dec_cmd, 0, sizeof(cfg_dec_cmd)); + + cfg_dec_cmd.cmd_id = AUDPP_CMD_CFG_DEC_TYPE; + if (enable) + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_ENA_DEC_V | AUDDEC_DEC_WMAPRO; + else + cfg_dec_cmd.dec_cfg = AUDPP_CMD_UPDATDE_CFG_DEC | + AUDPP_CMD_DIS_DEC_V; + cfg_dec_cmd.dm_mode = 0x0; + cfg_dec_cmd.stream_id = audio->dec_id; + return audpp_send_queue1(&cfg_dec_cmd, sizeof(cfg_dec_cmd)); +} + +static void audpp_cmd_cfg_adec_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_wmapro cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_WMAPRO_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + + /* + * Test done for sample with the following configuration + * armdatareqthr = 1262 + * channelsdecoded = 1(MONO)/2(STEREO) + * wmaprobytespersec = Tested with 6003 Bytes per sec + * wmaprosamplingfreq = 44100 + * wmaproencoderopts = 31 + */ + + cmd.armdatareqthr = audio->wmapro_config.armdatareqthr; + cmd.numchannels = audio->wmapro_config.numchannels; + cmd.validbitspersample = audio->wmapro_config.validbitspersample; + cmd.formattag = audio->wmapro_config.formattag; + cmd.samplingrate = audio->wmapro_config.samplingrate; + cmd.avgbytespersecond = audio->wmapro_config.avgbytespersecond; + cmd.asfpacketlength = audio->wmapro_config.asfpacketlength; + cmd.channelmask = audio->wmapro_config.channelmask; + cmd.encodeopt = audio->wmapro_config.encodeopt; + cmd.advancedencodeopt = audio->wmapro_config.advancedencodeopt; + cmd.advancedencodeopt2 = audio->wmapro_config.advancedencodeopt2; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static void audpp_cmd_cfg_routing_mode(struct audio *audio) +{ + struct audpp_cmd_routing_mode cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_ROUTING_MODE; + cmd.object_number = audio->dec_id; + if (audio->pcm_feedback) + cmd.routing_mode = ROUTING_MODE_FTRT; + else + cmd.routing_mode = ROUTING_MODE_RT; + + audpp_send_queue1(&cmd, sizeof(cmd)); +} + +static void audplay_buffer_refresh(struct audio *audio) +{ + struct audplay_cmd_buffer_refresh refresh_cmd; + + refresh_cmd.cmd_id = AUDPLAY_CMD_BUFFER_REFRESH; + refresh_cmd.num_buffers = 1; + refresh_cmd.buf0_address = audio->in[audio->fill_next].addr; + refresh_cmd.buf0_length = audio->in[audio->fill_next].size; + refresh_cmd.buf_read_count = 0; + + MM_DBG("buf0_addr=%x buf0_len=%d\n", + refresh_cmd.buf0_address, + refresh_cmd.buf0_length); + + (void)audplay_send_queue0(audio, &refresh_cmd, sizeof(refresh_cmd)); +} + +static void audplay_config_hostpcm(struct audio *audio) +{ + struct audplay_cmd_hpcm_buf_cfg cfg_cmd; + + MM_DBG("\n"); /* Macro prints the file name and function */ + cfg_cmd.cmd_id = AUDPLAY_CMD_HPCM_BUF_CFG; + cfg_cmd.max_buffers = audio->pcm_buf_count; + cfg_cmd.byte_swap = 0; + cfg_cmd.hostpcm_config = (0x8000) | (0x4000); + cfg_cmd.feedback_frequency = 1; + cfg_cmd.partition_number = 0; + + (void)audplay_send_queue0(audio, &cfg_cmd, sizeof(cfg_cmd)); +} + + +static int audplay_dsp_send_data_avail(struct audio *audio, + unsigned idx, unsigned len) +{ + struct audplay_cmd_bitstream_data_avail_nt2 cmd; + + cmd.cmd_id = AUDPLAY_CMD_BITSTREAM_DATA_AVAIL_NT2; + if (audio->mfield) + cmd.decoder_id = AUDWMAPRO_METAFIELD_MASK | + (audio->out[idx].mfield_sz >> 1); + else + cmd.decoder_id = audio->dec_id; + cmd.buf_ptr = audio->out[idx].addr; + cmd.buf_size = len/2; + cmd.partition_number = 0; + return audplay_send_queue0(audio, &cmd, sizeof(cmd)); +} + +static void audplay_send_data(struct audio *audio, unsigned needed) +{ + struct buffer *frame; + unsigned long flags; + + spin_lock_irqsave(&audio->dsp_lock, flags); + if (!audio->running) + goto done; + + if (audio->wflush) { + audio->out_needed = 1; + goto done; + } + + if (needed && !audio->wflush) { + /* We were called from the callback because the DSP + * requested more data. Note that the DSP does want + * more data, and if a buffer was in-flight, mark it + * as available (since the DSP must now be done with + * it). + */ + audio->out_needed = 1; + frame = audio->out + audio->out_tail; + if (frame->used == 0xffffffff) { + MM_DBG("frame %d free\n", audio->out_tail); + frame->used = 0; + audio->out_tail ^= 1; + wake_up(&audio->write_wait); + } + } + + if (audio->out_needed) { + /* If the DSP currently wants data and we have a + * buffer available, we will send it and reset + * the needed flag. We'll mark the buffer as in-flight + * so that it won't be recycled until the next buffer + * is requested + */ + + MM_DBG("\n"); /* Macro prints the file name and function */ + frame = audio->out + audio->out_tail; + if (frame->used) { + BUG_ON(frame->used == 0xffffffff); + MM_DBG("frame %d busy\n", audio->out_tail); + audplay_dsp_send_data_avail(audio, audio->out_tail, + frame->used); + frame->used = 0xffffffff; + audio->out_needed = 0; + } + } +done: + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ + +static void audio_flush(struct audio *audio) +{ + audio->out[0].used = 0; + audio->out[1].used = 0; + audio->out_head = 0; + audio->out_tail = 0; + audio->reserved = 0; + atomic_set(&audio->out_bytes, 0); +} + +static void audio_flush_pcm_buf(struct audio *audio) +{ + uint8_t index; + + for (index = 0; index < PCM_BUF_MAX_COUNT; index++) + audio->in[index].used = 0; + audio->buf_refresh = 0; + audio->read_next = 0; + audio->fill_next = 0; +} + +static void audio_ioport_reset(struct audio *audio) +{ + /* Make sure read/write thread are free from + * sleep and knowing that system is not able + * to process io request at the moment + */ + wake_up(&audio->write_wait); + mutex_lock(&audio->write_lock); + audio_flush(audio); + mutex_unlock(&audio->write_lock); + wake_up(&audio->read_wait); + mutex_lock(&audio->read_lock); + audio_flush_pcm_buf(audio); + mutex_unlock(&audio->read_lock); + audio->avsync_flag = 1; + wake_up(&audio->avsync_wait); +} + +static int audwmapro_events_pending(struct audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audwmapro_reset_event_queue(struct audio *audio) +{ + unsigned long flags; + struct audwmapro_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwmapro_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audwmapro_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audwmapro_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audwmapro_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout(audio->event_wait, + audwmapro_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audwmapro_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwmapro_event, list); + list_del(&drv_evt->list); + } + + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audio_enable_eq(struct audio *audio, int enable) +{ + if (audio->eq_enable == enable && !audio->eq_needs_commit) + return 0; + + audio->eq_enable = enable; + + if (audio->running) { + audpp_dsp_set_eq(audio->dec_id, enable, &audio->eq, POPP); + audio->eq_needs_commit = 0; + } + return 0; +} + +static int audio_get_avsync_data(struct audio *audio, + struct msm_audio_stats *stats) +{ + int rc = -EINVAL; + unsigned long flags; + + local_irq_save(flags); + if (audio->dec_id == audio->avsync[0] && audio->avsync_flag) { + /* av_sync sample count */ + stats->sample_count = (audio->avsync[2] << 16) | + (audio->avsync[3]); + + /* av_sync byte_count */ + stats->byte_count = (audio->avsync[5] << 16) | + (audio->avsync[6]); + + audio->avsync_flag = 0; + rc = 0; + } + local_irq_restore(flags); + return rc; + +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + unsigned long flags = 0; + uint16_t enable_mask; + int enable; + int prev_state; + + MM_DBG("cmd = %d\n", cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + audio->avsync_flag = 0; + memset(&stats, 0, sizeof(stats)); + if (audpp_query_avsync(audio->dec_id) < 0) + return rc; + + rc = wait_event_interruptible_timeout(audio->avsync_wait, + (audio->avsync_flag == 1), + msecs_to_jiffies(AUDPP_AVSYNC_EVENT_TIMEOUT)); + + if (rc < 0) + return rc; + else if ((rc > 0) || ((rc == 0) && (audio->avsync_flag == 1))) { + if (audio_get_avsync_data(audio, &stats) < 0) + return rc; + + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } else + return -EAGAIN; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + + spin_lock_irqsave(&audio->dsp_lock, flags); + enable = (enable_mask & EQ_ENABLE) ? 1 : 0; + audio_enable_eq(audio, enable); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + case AUDIO_SET_VOLUME: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.volume = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_PAN: + spin_lock_irqsave(&audio->dsp_lock, flags); + audio->vol_pan.pan = arg; + if (audio->running) + audpp_dsp_set_vol_pan(audio->dec_id, &audio->vol_pan, + POPP); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + rc = 0; + break; + + case AUDIO_SET_EQ: + prev_state = audio->eq_enable; + audio->eq_enable = 0; + if (copy_from_user(&audio->eq.num_bands, (void *) arg, + sizeof(audio->eq) - + (AUDPP_CMD_CFG_OBJECT_PARAMS_COMMON_LEN + 2))) { + rc = -EFAULT; + break; + } + audio->eq_enable = prev_state; + audio->eq_needs_commit = 1; + rc = 0; + break; + } + + if (-EINVAL != rc) + return rc; + + if (cmd == AUDIO_GET_EVENT) { + MM_DBG("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audwmapro_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + MM_DBG("AUDIO_START\n"); + rc = audio_enable(audio); + if (!rc) { + rc = wait_event_interruptible_timeout(audio->wait, + audio->dec_state != MSM_AUD_DECODER_STATE_NONE, + msecs_to_jiffies(MSM_AUD_DECODER_WAIT_MS)); + MM_INFO("dec_state %d rc = %d\n", audio->dec_state, rc); + + if (audio->dec_state != MSM_AUD_DECODER_STATE_SUCCESS) + rc = -ENODEV; + else + rc = 0; + } + break; + case AUDIO_STOP: + MM_DBG("AUDIO_STOP\n"); + rc = audio_disable(audio); + audio->stopped = 1; + audio_ioport_reset(audio); + audio->stopped = 0; + break; + case AUDIO_FLUSH: + MM_DBG("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + audio_ioport_reset(audio); + if (audio->running) { + audpp_flush(audio->dec_id); + rc = wait_event_interruptible(audio->write_wait, + !audio->wflush); + if (rc < 0) { + MM_ERR("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } + } else { + audio->rflush = 0; + audio->wflush = 0; + } + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count == 1) { + config.channel_count = AUDPP_CMD_PCM_INTF_MONO_V; + } else if (config.channel_count == 2) { + config.channel_count = AUDPP_CMD_PCM_INTF_STEREO_V; + } else { + rc = -EINVAL; + break; + } + audio->mfield = config.meta_field; + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = (audio->out_dma_sz >> 1); + config.buffer_count = 2; + config.sample_rate = audio->out_sample_rate; + if (audio->out_channel_mode == AUDPP_CMD_PCM_INTF_MONO_V) + config.channel_count = 1; + else + config.channel_count = 2; + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + + break; + } + case AUDIO_GET_WMAPRO_CONFIG:{ + if (copy_to_user((void *)arg, &audio->wmapro_config, + sizeof(audio->wmapro_config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_WMAPRO_CONFIG:{ + struct msm_audio_wmapro_config usr_config; + + if (copy_from_user + (&usr_config, (void *)arg, + sizeof(usr_config))) { + rc = -EFAULT; + break; + } + + audio->wmapro_config = usr_config; + + /* Need to swap the first and last words of advancedencodeopt2 + * as DSP cannot read 32-bit variable at a time. Need to be + * split into two 16-bit and swap them as required by DSP */ + + audio->wmapro_config.advancedencodeopt2 = + ((audio->wmapro_config.advancedencodeopt2 & 0xFFFF0000) + >> 16) | ((audio->wmapro_config.advancedencodeopt2 + << 16) & 0xFFFF0000); + rc = 0; + break; + } + case AUDIO_GET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + config.pcm_feedback = audio->pcm_feedback; + config.buffer_count = PCM_BUF_MAX_COUNT; + config.buffer_size = PCM_BUFSZ_MIN; + if (copy_to_user((void *)arg, &config, + sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + case AUDIO_SET_PCM_CONFIG:{ + struct msm_audio_pcm_config config; + if (copy_from_user + (&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.pcm_feedback != audio->pcm_feedback) { + MM_ERR("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_MAX_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_MAX_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + /* Check if pcm feedback is required */ + if ((config.pcm_feedback) && (!audio->read_data)) { + MM_DBG("allocate PCM buffer %d\n", + config.buffer_count * + config.buffer_size); + audio->read_phys = pmem_kalloc( + config.buffer_size * + config.buffer_count, + PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)audio->read_phys)) { + rc = -ENOMEM; + break; + } + audio->read_data = ioremap(audio->read_phys, + config.buffer_size * + config.buffer_count); + if (!audio->read_data) { + MM_ERR("read buf alloc fail\n"); + rc = -ENOMEM; + pmem_kfree(audio->read_phys); + } else { + uint8_t index; + uint32_t offset = 0; + audio->pcm_feedback = 1; + audio->buf_refresh = 0; + audio->pcm_buf_count = + config.buffer_count; + audio->read_next = 0; + audio->fill_next = 0; + + for (index = 0; + index < config.buffer_count; + index++) { + audio->in[index].data = + audio->read_data + offset; + audio->in[index].addr = + audio->read_phys + offset; + audio->in[index].size = + config.buffer_size; + audio->in[index].used = 0; + offset += config.buffer_size; + } + MM_DBG("read buf: phy addr \ + 0x%08x kernel addr 0x%08x\n", + audio->read_phys, + (int)audio->read_data); + rc = 0; + } + } else { + rc = 0; + } + break; + } + case AUDIO_PAUSE: + MM_DBG("AUDIO_PAUSE %ld\n", arg); + rc = audpp_pause(audio->dec_id, (int) arg); + break; + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +static int audio_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + struct buffer *frame; + int rc = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + if (!audio->running || audio->pcm_feedback) { + rc = -EINVAL; + goto done_nolock; + } + + mutex_lock(&audio->write_lock); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (audio->reserved) { + MM_DBG("send reserved byte\n"); + frame = audio->out + audio->out_tail; + ((char *) frame->data)[0] = audio->rsv_byte; + ((char *) frame->data)[1] = 0; + frame->used = 2; + audplay_send_data(audio, 0); + + rc = wait_event_interruptible(audio->write_wait, + (!audio->out[0].used && + !audio->out[1].used && + audio->out_needed) || audio->wflush); + + if (rc < 0) + goto done; + else if (audio->wflush) { + rc = -EBUSY; + goto done; + } + } + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + audio->teos || audio->wflush); + + if (audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); +done_nolock: + return rc; +} + +static ssize_t audio_read(struct file *file, char __user *buf, size_t count, + loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + int rc = 0; + + if (!audio->pcm_feedback) + return 0; /* PCM feedback is not enabled. Nothing to read */ + + mutex_lock(&audio->read_lock); + MM_DBG("%d \n", count); + while (count > 0) { + rc = wait_event_interruptible(audio->read_wait, + (audio->in[audio->read_next].used > 0) || + (audio->stopped) || (audio->rflush)); + + if (rc < 0) + break; + + if (audio->stopped || audio->rflush) { + rc = -EBUSY; + break; + } + + if (count < audio->in[audio->read_next].used) { + /* Read must happen in frame boundary. Since driver + does not know frame size, read count must be greater + or equal to size of PCM samples */ + MM_DBG("audio_read: no partial frame done reading\n"); + break; + } else { + MM_DBG("audio_read: read from in[%d]\n", + audio->read_next); + if (copy_to_user + (buf, audio->in[audio->read_next].data, + audio->in[audio->read_next].used)) { + MM_ERR("invalid addr %x \n", (unsigned int)buf); + rc = -EFAULT; + break; + } + count -= audio->in[audio->read_next].used; + buf += audio->in[audio->read_next].used; + audio->in[audio->read_next].used = 0; + if ((++audio->read_next) == audio->pcm_buf_count) + audio->read_next = 0; + break; /* Force to exit while loop + * to prevent output thread + * sleep too long if data is + * not ready at this moment. + */ + } + } + + /* don't feed output buffer to HW decoder during flushing + * buffer refresh command will be sent once flush completes + * send buf refresh command here can confuse HW decoder + */ + if (audio->buf_refresh && !audio->rflush) { + audio->buf_refresh = 0; + MM_DBG("kick start pcm feedback again\n"); + audplay_buffer_refresh(audio); + } + + mutex_unlock(&audio->read_lock); + + if (buf > start) + rc = buf - start; + + MM_DBG("read %d bytes\n", rc); + return rc; +} + +static int audwmapro_process_eos(struct audio *audio, + const char __user *buf_start, unsigned short mfield_size) +{ + int rc = 0; + struct buffer *frame; + char *buf_ptr; + + if (audio->reserved) { + MM_DBG("flush reserve byte\n"); + frame = audio->out + audio->out_head; + buf_ptr = frame->data; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + buf_ptr[0] = audio->rsv_byte; + buf_ptr[1] = 0; + audio->out_head ^= 1; + frame->mfield_sz = 0; + frame->used = 2; + audio->reserved = 0; + audplay_send_data(audio, 0); + } + + frame = audio->out + audio->out_head; + + rc = wait_event_interruptible(audio->write_wait, + (audio->out_needed && + audio->out[0].used == 0 && + audio->out[1].used == 0) + || (audio->stopped) + || (audio->wflush)); + + if (rc < 0) + goto done; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + goto done; + } + + if (copy_from_user(frame->data, buf_start, mfield_size)) { + rc = -EFAULT; + goto done; + } + + frame->mfield_sz = mfield_size; + audio->out_head ^= 1; + frame->used = mfield_size; + audplay_send_data(audio, 0); +done: + return rc; +} + +static ssize_t audio_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct audio *audio = file->private_data; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + char *cpy_ptr; + int rc = 0, eos_condition = AUDWMAPRO_EOS_NONE; + unsigned dsize; + unsigned short mfield_size = 0; + + MM_DBG("cnt=%d\n", count); + + mutex_lock(&audio->write_lock); + while (count > 0) { + frame = audio->out + audio->out_head; + cpy_ptr = frame->data; + dsize = 0; + rc = wait_event_interruptible(audio->write_wait, + (frame->used == 0) + || (audio->stopped) + || (audio->wflush)); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + rc = -EBUSY; + break; + } + if (audio->mfield) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (__get_user(mfield_size, + (unsigned short __user *) buf)) { + rc = -EFAULT; + break; + } else if (mfield_size > count) { + rc = -EINVAL; + break; + } + MM_DBG("audio_write: mf offset_val %x\n", + mfield_size); + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + if (cpy_ptr[AUDWMAPRO_EOS_FLG_OFFSET] & + AUDWMAPRO_EOS_FLG_MASK) { + MM_DBG("audio_write: EOS SET\n"); + eos_condition = AUDWMAPRO_EOS_SET; + if (mfield_size == count) { + buf += mfield_size; + break; + } else + cpy_ptr[AUDWMAPRO_EOS_FLG_OFFSET] + &= ~AUDWMAPRO_EOS_FLG_MASK; + } + cpy_ptr += mfield_size; + count -= mfield_size; + dsize += mfield_size; + buf += mfield_size; + } else { + mfield_size = 0; + MM_DBG("audio_write: continuous buffer\n"); + } + frame->mfield_sz = mfield_size; + } + + if (audio->reserved) { + MM_DBG("append reserved byte %x\n", audio->rsv_byte); + *cpy_ptr = audio->rsv_byte; + xfer = (count > ((frame->size - mfield_size) - 1)) ? + (frame->size - mfield_size) - 1 : count; + cpy_ptr++; + dsize += 1; + audio->reserved = 0; + } else + xfer = (count > (frame->size - mfield_size)) ? + (frame->size - mfield_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + + dsize += xfer; + if (dsize & 1) { + audio->rsv_byte = ((char *) frame->data)[dsize - 1]; + MM_DBG("odd length buf reserve last byte %x\n", + audio->rsv_byte); + audio->reserved = 1; + dsize--; + } + count -= xfer; + buf += xfer; + + if (dsize > 0) { + audio->out_head ^= 1; + frame->used = dsize; + audplay_send_data(audio, 0); + } + } + if (eos_condition == AUDWMAPRO_EOS_SET) + rc = audwmapro_process_eos(audio, start, mfield_size); + mutex_unlock(&audio->write_lock); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + MM_INFO("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + audio_disable(audio); + audio_flush(audio); + audio_flush_pcm_buf(audio); + msm_adsp_put(audio->audplay); + audpp_adec_free(audio->dec_id); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->event_abort = 1; + wake_up(&audio->event_wait); + audwmapro_reset_event_queue(audio); + iounmap(audio->data); + pmem_kfree(audio->phys); + if (audio->read_data) { + iounmap(audio->read_data); + pmem_kfree(audio->read_phys); + } + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audwmapro_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audwmapro_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audwmapro_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audwmapro_event), GFP_ATOMIC); + if (!e_node) { + MM_ERR("No mem to post event %d\n", type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audwmapro_suspend(struct early_suspend *h) +{ + struct audwmapro_suspend_ctl *ctl = + container_of(h, struct audwmapro_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audwmapro_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audwmapro_resume(struct early_suspend *h) +{ + struct audwmapro_suspend_ctl *ctl = + container_of(h, struct audwmapro_suspend_ctl, node); + union msm_audio_event_payload payload; + + MM_DBG("\n"); /* Macro prints the file name and function */ + audwmapro_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audwmapro_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audwmapro_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0, i; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_feedback %d\n", audio->pcm_feedback); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_buf_sz %d\n", audio->out[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_count %d \n", audio->pcm_buf_count); + n += scnprintf(buffer + n, debug_bufmax - n, + "pcm_buf_sz %d \n", audio->in[0].size); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x \n", audio->vol_pan.volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d \n", audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d \n", audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d \n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "dec state %d \n", audio->dec_state); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d \n", audio->out_needed); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_head %d \n", audio->out_head); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_tail %d \n", audio->out_tail); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[0].used %d \n", audio->out[0].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "out[1].used %d \n", audio->out[1].used); + n += scnprintf(buffer + n, debug_bufmax - n, + "buffer_refresh %d \n", audio->buf_refresh); + n += scnprintf(buffer + n, debug_bufmax - n, + "read_next %d \n", audio->read_next); + n += scnprintf(buffer + n, debug_bufmax - n, + "fill_next %d \n", audio->fill_next); + for (i = 0; i < audio->pcm_buf_count; i++) + n += scnprintf(buffer + n, debug_bufmax - n, + "in[%d].size %d \n", i, audio->in[i].used); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audwmapro_debug_fops = { + .read = audwmapro_debug_read, + .open = audwmapro_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, dec_attrb, decid, i; + unsigned pmem_sz = DMASZ_MAX; + struct audwmapro_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_wmapro_" + 5]; +#endif + + /* Allocate Mem for audio instance */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + MM_ERR("no memory to allocate audio instance \n"); + rc = -ENOMEM; + goto done; + } + MM_INFO("audio instance 0x%08x created\n", (int)audio); + + /* Allocate the decoder */ + dec_attrb = AUDDEC_DEC_WMAPRO; + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_NONTUNNEL; + audio->pcm_feedback = NON_TUNNEL_MODE_PLAYBACK; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + dec_attrb |= MSM_AUD_MODE_TUNNEL; + audio->pcm_feedback = TUNNEL_MODE_PLAYBACK; + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + decid = audpp_adec_alloc(dec_attrb, &audio->module_name, + &audio->queue_id); + + if (decid < 0) { + MM_ERR("No free decoder available, freeing instance 0x%08x\n", + (int)audio); + rc = -ENODEV; + kfree(audio); + goto done; + } + audio->dec_id = decid & MSM_AUD_DECODER_MASK; + + while (pmem_sz >= DMASZ_MIN) { + MM_DBG("pmemsz = %d\n", pmem_sz); + audio->phys = pmem_kalloc(pmem_sz, PMEM_MEMTYPE_EBI1| + PMEM_ALIGNMENT_4K); + if (!IS_ERR((void *)audio->phys)) { + audio->data = ioremap(audio->phys, pmem_sz); + if (!audio->data) { + MM_ERR("could not allocate write buffers, \ + freeing instance 0x%08x\n", + (int)audio); + rc = -ENOMEM; + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } + MM_DBG("write buf: phy addr 0x%08x kernel addr \ + 0x%08x\n", audio->phys, (int)audio->data); + break; + } else if (pmem_sz == DMASZ_MIN) { + MM_ERR("could not allocate write buffers, freeing \ + instance 0x%08x\n", (int)audio); + rc = -ENOMEM; + audpp_adec_free(audio->dec_id); + kfree(audio); + goto done; + } else + pmem_sz >>= 1; + } + audio->out_dma_sz = pmem_sz; + + rc = msm_adsp_get(audio->module_name, &audio->audplay, + &audplay_adsp_ops_wmapro, audio); + if (rc) { + MM_ERR("failed to get %s module, freeing instance 0x%08x\n", + audio->module_name, (int)audio); + goto err; + } + + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->read_wait); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->avsync_wait); + + audio->out[0].data = audio->data + 0; + audio->out[0].addr = audio->phys + 0; + audio->out[0].size = audio->out_dma_sz >> 1; + + audio->out[1].data = audio->data + audio->out[0].size; + audio->out[1].addr = audio->phys + audio->out[0].size; + audio->out[1].size = audio->out[0].size; + + /*audio->wmapro_config.armdatareqthr = 1268; + audio->wmapro_config.numchannels = 2; + audio->wmapro_config.avgbytespersecond = 6003; + audio->wmapro_config.samplingrate = 44100; + audio->wmapro_config.encodeopt = 224; + audio->wmapro_config.validbitspersample = 16; + audio->wmapro_config.formattag = 354; + audio->wmapro_config.asfpacketlength = 2230; + audio->wmapro_config.channelmask = 3; + audio->wmapro_config.advancedencodeopt = 32834; + audio->wmapro_config.advancedencodeopt2 = 0;*/ + + audio->out_sample_rate = 44100; + audio->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + + audio->vol_pan.volume = 0x2000; + + audio_flush(audio); + + file->private_data = audio; + audio->opened = 1; + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + wmapro_listner, + (void *)audio); + if (rc) { + MM_ERR("%s: failed to register listner\n", __func__); + goto event_err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_wmapro_%04x", audio->dec_id); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, + &audwmapro_debug_fops); + + if (IS_ERR(audio->dentry)) + MM_DBG("debugfs_create_file failed\n"); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audwmapro_resume; + audio->suspend_ctl.node.suspend = audwmapro_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDWMAPRO_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audwmapro_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + MM_ERR("event pkt alloc failed\n"); + break; + } + } +done: + return rc; +event_err: + msm_adsp_put(audio->audplay); +err: + iounmap(audio->data); + pmem_kfree(audio->phys); + audpp_adec_free(audio->dec_id); + kfree(audio); + return rc; +} + +static const struct file_operations audio_wmapro_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .read = audio_read, + .write = audio_write, + .unlocked_ioctl = audio_ioctl, + .fsync = audio_fsync, +}; + +struct miscdevice audio_wmapro_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_wmapro", + .fops = &audio_wmapro_fops, +}; + +static int __init audio_init(void) +{ + return misc_register(&audio_wmapro_misc); +} + +device_initcall(audio_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audpp.c b/arch/arm/mach-msm/qdsp5v2/audpp.c new file mode 100644 index 00000000000..be274c7fcb4 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audpp.c @@ -0,0 +1,1133 @@ +/* arch/arm/mach-msm/qdsp5/audpp.c + * + * common code to deal with the AUDPP dsp task (audio postproc) + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../qdsp5/evlog.h" +#include + +enum { + EV_NULL, + EV_ENABLE, + EV_DISABLE, + EV_EVENT, + EV_DATA, +}; + +static const char *dsp_log_strings[] = { + "NULL", + "ENABLE", + "DISABLE", + "EVENT", + "DATA", +}; + +DECLARE_LOG(dsp_log, 64, dsp_log_strings); + +static int __init _dsp_log_init(void) +{ + return ev_log_init(&dsp_log); +} + +module_init(_dsp_log_init); +#define LOG(id, arg) ev_log_write(&dsp_log, id, arg) + +static DEFINE_MUTEX(audpp_lock); +static DEFINE_MUTEX(audpp_dec_lock); +static struct wake_lock audpp_wake_lock; + +#define CH_COUNT 5 +#define AUDPP_CLNT_MAX_COUNT 6 + +#define AUDPP_CMD_CFG_OBJ_UPDATE 0x8000 +#define AUDPP_CMD_EQ_FLAG_DIS 0x0000 +#define AUDPP_CMD_EQ_FLAG_ENA -1 +#define AUDPP_CMD_IIR_FLAG_DIS 0x0000 +#define AUDPP_CMD_IIR_FLAG_ENA -1 +#define AUDPP_CMD_STF_FLAG_ENA -1 +#define AUDPP_CMD_STF_FLAG_DIS 0x0000 + +#define MAX_EVENT_CALLBACK_CLIENTS 1 + +#define AUDPP_CONCURRENCY_DEFAULT 0 /* Set default to LPA mode */ +#define AUDPP_MAX_DECODER_CNT 5 +#define AUDPP_CODEC_MASK 0x000000FF +#define AUDPP_MODE_MASK 0x00000F00 +#define AUDPP_OP_MASK 0xF0000000 + +struct audpp_decoder_info { + unsigned int codec; + pid_t pid; +}; + +struct audpp_state { + struct msm_adsp_module *mod; + audpp_event_func func[AUDPP_CLNT_MAX_COUNT]; + void *private[AUDPP_CLNT_MAX_COUNT]; + struct mutex *lock; + unsigned open_count; + unsigned enabled; + + /* Related to decoder allocation */ + struct mutex *lock_dec; + struct msm_adspdec_database *dec_database; + struct audpp_decoder_info dec_info_table[AUDPP_MAX_DECODER_CNT]; + unsigned dec_inuse; + unsigned long concurrency; + + struct audpp_event_callback *cb_tbl[MAX_EVENT_CALLBACK_CLIENTS]; + + /* Related to decoder instances */ + uint8_t op_mode; /* Specifies Turbo/Non Turbo mode */ + uint8_t decoder_count; /* No. of decoders active running */ + uint8_t codec_max_instances; /* Max codecs allowed currently */ + uint8_t codec_cnt[MSM_MAX_DEC_CNT]; /* Nr of each codec + type enabled */ + + wait_queue_head_t event_wait; +}; + +struct audpp_state the_audpp_state = { + .lock = &audpp_lock, + .lock_dec = &audpp_dec_lock, +}; + +static inline void prevent_suspend(void) +{ + wake_lock(&audpp_wake_lock); +} +static inline void allow_suspend(void) +{ + wake_unlock(&audpp_wake_lock); +} + +int audpp_send_queue1(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpp_state.mod, + QDSP_uPAudPPCmd1Queue, cmd, len); +} +EXPORT_SYMBOL(audpp_send_queue1); + +int audpp_send_queue2(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpp_state.mod, + QDSP_uPAudPPCmd2Queue, cmd, len); +} +EXPORT_SYMBOL(audpp_send_queue2); + +int audpp_send_queue3(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpp_state.mod, + QDSP_uPAudPPCmd3Queue, cmd, len); +} +EXPORT_SYMBOL(audpp_send_queue3); + +static int audpp_dsp_config(int enable) +{ + struct audpp_cmd_cfg cmd; + + cmd.cmd_id = AUDPP_CMD_CFG; + cmd.cfg = enable ? AUDPP_CMD_CFG_ENABLE : AUDPP_CMD_CFG_SLEEP; + + return audpp_send_queue1(&cmd, sizeof(cmd)); +} + +void audpp_route_stream(unsigned short dec_id, unsigned short mixer_mask) +{ + struct audpp_cmd_cfg_dev_mixer_params mixer_params_cmd; + + memset(&mixer_params_cmd, 0, sizeof(mixer_params_cmd)); + + mixer_params_cmd.cmd_id = AUDPP_CMD_CFG_DEV_MIXER; + mixer_params_cmd.stream_id = dec_id; + mixer_params_cmd.mixer_cmd = mixer_mask; + audpp_send_queue1(&mixer_params_cmd, sizeof(mixer_params_cmd)); + +} +EXPORT_SYMBOL(audpp_route_stream); + +int is_audpp_enable(void) +{ + struct audpp_state *audpp = &the_audpp_state; + + return audpp->enabled; +} +EXPORT_SYMBOL(is_audpp_enable); + +int audpp_register_event_callback(struct audpp_event_callback *ecb) +{ + struct audpp_state *audpp = &the_audpp_state; + int i; + + for (i = 0; i < MAX_EVENT_CALLBACK_CLIENTS; ++i) { + if (NULL == audpp->cb_tbl[i]) { + audpp->cb_tbl[i] = ecb; + return 0; + } + } + return -1; +} +EXPORT_SYMBOL(audpp_register_event_callback); + + +int audpp_unregister_event_callback(struct audpp_event_callback *ecb) +{ + struct audpp_state *audpp = &the_audpp_state; + int i; + + for (i = 0; i < MAX_EVENT_CALLBACK_CLIENTS; ++i) { + if (ecb == audpp->cb_tbl[i]) { + audpp->cb_tbl[i] = NULL; + return 0; + } + } + return -1; +} +EXPORT_SYMBOL(audpp_unregister_event_callback); + +static void audpp_broadcast(struct audpp_state *audpp, unsigned id, + uint16_t *msg) +{ + unsigned n; + for (n = 0; n < AUDPP_CLNT_MAX_COUNT; n++) { + if (audpp->func[n]) + audpp->func[n] (audpp->private[n], id, msg); + } + + for (n = 0; n < MAX_EVENT_CALLBACK_CLIENTS; ++n) + if (audpp->cb_tbl[n] && audpp->cb_tbl[n]->fn) + audpp->cb_tbl[n]->fn(audpp->cb_tbl[n]->private, id, + msg); +} + +static void audpp_notify_clnt(struct audpp_state *audpp, unsigned clnt_id, + unsigned id, uint16_t *msg) +{ + if (clnt_id < AUDPP_CLNT_MAX_COUNT && audpp->func[clnt_id]) + audpp->func[clnt_id] (audpp->private[clnt_id], id, msg); +} + +static void audpp_handle_pcmdmamiss(struct audpp_state *audpp, + uint16_t bit_mask) +{ + uint8_t b_index; + + for (b_index = 0; b_index < AUDPP_CLNT_MAX_COUNT; b_index++) { + if (bit_mask & (0x1 << b_index)) + if (audpp->func[b_index]) + audpp->func[b_index] (audpp->private[b_index], + AUDPP_MSG_PCMDMAMISSED, + &bit_mask); + } +} + +static void audpp_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct audpp_state *audpp = data; + uint16_t msg[8]; + + getevent(msg, sizeof(msg)); + + LOG(EV_EVENT, (id << 16) | msg[0]); + LOG(EV_DATA, (msg[1] << 16) | msg[2]); + + switch (id) { + case AUDPP_MSG_STATUS_MSG:{ + unsigned cid = msg[0]; + MM_DBG("status %d %d %d\n", cid, msg[1], msg[2]); + + if ((cid < 5) && audpp->func[cid]) + audpp->func[cid] (audpp->private[cid], id, msg); + break; + } + case AUDPP_MSG_HOST_PCM_INTF_MSG: + if (audpp->func[5]) + audpp->func[5] (audpp->private[5], id, msg); + break; + case AUDPP_MSG_PCMDMAMISSED: + audpp_handle_pcmdmamiss(audpp, msg[0]); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_INFO("ENABLE\n"); + audpp->enabled = 1; + audpp_broadcast(audpp, id, msg); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_INFO("DISABLE\n"); + audpp->enabled = 0; + wake_up(&audpp->event_wait); + audpp_broadcast(audpp, id, msg); + } else { + MM_ERR("invalid config msg %d\n", msg[0]); + } + break; + case AUDPP_MSG_ROUTING_ACK: + audpp_notify_clnt(audpp, msg[0], id, msg); + break; + case AUDPP_MSG_FLUSH_ACK: + audpp_notify_clnt(audpp, msg[0], id, msg); + break; + case ADSP_MESSAGE_ID: + MM_DBG("Received ADSP event: module enable/disable \ + (audpptask)"); + break; + case AUDPP_MSG_AVSYNC_MSG: + audpp_notify_clnt(audpp, msg[0], id, msg); + break; +#ifdef CONFIG_DEBUG_FS + case AUDPP_MSG_FEAT_QUERY_DM_DONE: + MM_INFO(" RTC ACK --> %x %x %x %x %x %x %x %x\n", msg[0],\ + msg[1], msg[2], msg[3], msg[4], \ + msg[5], msg[6], msg[7]); + acdb_rtc_set_err(msg[3]); + break; +#endif + default: + MM_INFO("unhandled msg id %x\n", id); + } +} + +static struct msm_adsp_ops adsp_ops = { + .event = audpp_dsp_event, +}; + +static void audpp_fake_event(struct audpp_state *audpp, int id, + unsigned event, unsigned arg) +{ + uint16_t msg[1]; + msg[0] = arg; + audpp->func[id] (audpp->private[id], event, msg); +} + +int audpp_enable(int id, audpp_event_func func, void *private) +{ + struct audpp_state *audpp = &the_audpp_state; + int res = 0; + + if (id < -1 || id > 4) + return -EINVAL; + + if (id == -1) + id = 5; + + mutex_lock(audpp->lock); + if (audpp->func[id]) { + res = -EBUSY; + goto out; + } + + audpp->func[id] = func; + audpp->private[id] = private; + + LOG(EV_ENABLE, 1); + if (audpp->open_count++ == 0) { + MM_DBG("enable\n"); + res = msm_adsp_get("AUDPPTASK", &audpp->mod, &adsp_ops, audpp); + if (res < 0) { + MM_ERR("audpp: cannot open AUDPPTASK\n"); + audpp->open_count = 0; + audpp->func[id] = NULL; + audpp->private[id] = NULL; + goto out; + } + LOG(EV_ENABLE, 2); + prevent_suspend(); + msm_adsp_enable(audpp->mod); + audpp_dsp_config(1); + } else { + unsigned long flags; + local_irq_save(flags); + if (audpp->enabled) + audpp_fake_event(audpp, id, + AUDPP_MSG_CFG_MSG, AUDPP_MSG_ENA_ENA); + local_irq_restore(flags); + } + + res = 0; +out: + mutex_unlock(audpp->lock); + return res; +} +EXPORT_SYMBOL(audpp_enable); + +void audpp_disable(int id, void *private) +{ + struct audpp_state *audpp = &the_audpp_state; + unsigned long flags; + int rc; + + if (id < -1 || id > 4) + return; + + if (id == -1) + id = 5; + + mutex_lock(audpp->lock); + LOG(EV_DISABLE, 1); + if (!audpp->func[id]) + goto out; + if (audpp->private[id] != private) + goto out; + + local_irq_save(flags); + audpp_fake_event(audpp, id, AUDPP_MSG_CFG_MSG, AUDPP_MSG_ENA_DIS); + audpp->func[id] = NULL; + audpp->private[id] = NULL; + local_irq_restore(flags); + + if (--audpp->open_count == 0) { + MM_DBG("disable\n"); + LOG(EV_DISABLE, 2); + audpp_dsp_config(0); + rc = wait_event_interruptible(audpp->event_wait, + (audpp->enabled == 0)); + if (audpp->enabled == 0) + MM_INFO("Received CFG_MSG_DISABLE from ADSP\n"); + else + MM_ERR("Didn't receive CFG_MSG DISABLE \ + message from ADSP\n"); + msm_adsp_disable(audpp->mod); + msm_adsp_put(audpp->mod); + audpp->mod = NULL; + allow_suspend(); + } +out: + mutex_unlock(audpp->lock); +} +EXPORT_SYMBOL(audpp_disable); + +#define BAD_ID(id) ((id < 0) || (id >= CH_COUNT)) + +int audpp_restore_avsync(int id, uint16_t *avsync) +{ + struct audpp_cmd_avsync cmd; + + if (BAD_ID(id)) + return -1; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_AVSYNC; + cmd.stream_id = id; + cmd.interrupt_interval = 0; /* Setting it to Zero as there won't be + periodic update */ + cmd.sample_counter_dlsw = avsync[3]; + cmd.sample_counter_dmsw = avsync[2]; + cmd.sample_counter_msw = avsync[1]; + cmd.byte_counter_dlsw = avsync[6]; + cmd.byte_counter_dmsw = avsync[5]; + cmd.byte_counter_msw = avsync[4]; + + return audpp_send_queue1(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_restore_avsync); + +int audpp_query_avsync(int id) +{ + struct audpp_cmd_query_avsync cmd; + + if (BAD_ID(id)) + return -EINVAL; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_QUERY_AVSYNC; + cmd.stream_id = id; + return audpp_send_queue1(&cmd, sizeof(cmd)); + +} +EXPORT_SYMBOL(audpp_query_avsync); + +int audpp_set_volume_and_pan(unsigned id, unsigned volume, int pan, + enum obj_type objtype) +{ + /* cmd, obj_cfg[7], cmd_type, volume, pan */ + uint16_t cmd[7]; + + if (objtype) { + if (id > 5) { + MM_ERR("Wrong POPP decoder id: %d\n", id); + return -EINVAL; + } + } else { + if (id > 3) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + + memset(cmd, 0, sizeof(cmd)); + cmd[0] = AUDPP_CMD_CFG_OBJECT_PARAMS; + if (objtype) + cmd[1] = AUDPP_CMD_POPP_STREAM; + else + cmd[1] = AUDPP_CMD_COPP_STREAM; + cmd[2] = id; + cmd[3] = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd[4] = AUDPP_CMD_VOLUME_PAN; + cmd[5] = volume; + cmd[6] = pan; + + return audpp_send_queue3(cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_set_volume_and_pan); + +/* Implementation of COPP features */ +int audpp_dsp_set_mbadrc(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_mbadrc *mbadrc, + enum obj_type objtype) +{ + if (objtype) { + if (id > 5) { + MM_ERR("Wrong POPP decoder id: %d\n", id); + return -EINVAL; + } + } else { + if (id > 3) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + + mbadrc->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + if (objtype) + mbadrc->common.stream = AUDPP_CMD_POPP_STREAM; + else + mbadrc->common.stream = AUDPP_CMD_COPP_STREAM; + + mbadrc->common.stream_id = id; + mbadrc->common.obj_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + mbadrc->common.command_type = AUDPP_CMD_MBADRC; + + if (enable) + mbadrc->enable = AUDPP_CMD_ADRC_FLAG_ENA; + else + mbadrc->enable = AUDPP_CMD_ADRC_FLAG_DIS; + + return audpp_send_queue3(mbadrc, + sizeof(struct audpp_cmd_cfg_object_params_mbadrc)); +} +EXPORT_SYMBOL(audpp_dsp_set_mbadrc); + +int audpp_dsp_set_qconcert_plus(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_qconcert *qconcert_plus, + enum obj_type objtype) +{ + if (objtype) { + if (id > 5) { + MM_ERR("Wrong POPP decoder id: %d\n", id); + return -EINVAL; + } + } else { + if (id > 3) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + + qconcert_plus->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + if (objtype) + qconcert_plus->common.stream = AUDPP_CMD_POPP_STREAM; + else + qconcert_plus->common.stream = AUDPP_CMD_COPP_STREAM; + + qconcert_plus->common.stream_id = id; + qconcert_plus->common.obj_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + qconcert_plus->common.command_type = AUDPP_CMD_QCONCERT; + + if (enable) + qconcert_plus->enable_flag = AUDPP_CMD_ADRC_FLAG_ENA; + else + qconcert_plus->enable_flag = AUDPP_CMD_ADRC_FLAG_DIS; + + return audpp_send_queue3(qconcert_plus, + sizeof(struct audpp_cmd_cfg_object_params_qconcert)); +} +EXPORT_SYMBOL(audpp_dsp_set_qconcert_plus); + +int audpp_dsp_set_rx_iir(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_pcm *iir, + enum obj_type objtype) +{ + + if (objtype) { + if (id > 5) { + MM_ERR("Wrong POPP decoder id: %d\n", id); + return -EINVAL; + } + } else { + if (id > 3) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + + iir->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + if (objtype) + iir->common.stream = AUDPP_CMD_POPP_STREAM; + else + iir->common.stream = AUDPP_CMD_COPP_STREAM; + + iir->common.stream_id = id; + iir->common.obj_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + iir->common.command_type = AUDPP_CMD_IIR_TUNING_FILTER; + + if (enable) + iir->active_flag = AUDPP_CMD_IIR_FLAG_ENA; + else + iir->active_flag = AUDPP_CMD_IIR_FLAG_DIS; + + return audpp_send_queue3(iir, + sizeof(struct audpp_cmd_cfg_object_params_pcm)); +} +EXPORT_SYMBOL(audpp_dsp_set_rx_iir); + +int audpp_dsp_set_gain_rx(unsigned id, + struct audpp_cmd_cfg_cal_gain *calib_gain_rx, + enum obj_type objtype) +{ + if (objtype) { + return -EINVAL; + } else { + if (id > 3) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + calib_gain_rx->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + calib_gain_rx->common.stream = AUDPP_CMD_COPP_STREAM; + + calib_gain_rx->common.stream_id = id; + calib_gain_rx->common.obj_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + calib_gain_rx->common.command_type = AUDPP_CMD_CALIB_GAIN_RX; + + return audpp_send_queue3(calib_gain_rx, + sizeof(struct audpp_cmd_cfg_cal_gain)); +} +EXPORT_SYMBOL(audpp_dsp_set_gain_rx); + +int audpp_dsp_set_pbe(unsigned id, unsigned enable, + struct audpp_cmd_cfg_pbe *pbe_block, + enum obj_type objtype) +{ + if (objtype) { + if (id > 5) { + MM_ERR("Wrong POPP decoder id: %d\n", id); + return -EINVAL; + } + } else { + if (id > 3) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + + pbe_block->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + if (objtype) + pbe_block->common.stream = AUDPP_CMD_POPP_STREAM; + else + pbe_block->common.stream = AUDPP_CMD_COPP_STREAM; + + pbe_block->common.stream_id = id; + pbe_block->common.obj_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + pbe_block->common.command_type = AUDPP_CMD_PBE; + + if (enable) + pbe_block->pbe_enable = AUDPP_CMD_PBE_FLAG_ENA; + else + pbe_block->pbe_enable = AUDPP_CMD_PBE_FLAG_DIS; + + return audpp_send_queue3(pbe_block, + sizeof(struct audpp_cmd_cfg_pbe)); +} +EXPORT_SYMBOL(audpp_dsp_set_pbe); + +int audpp_dsp_set_spa(unsigned id, + struct audpp_cmd_cfg_object_params_spectram *spa, + enum obj_type objtype){ + struct audpp_cmd_cfg_object_params_spectram cmd; + + if (objtype) { + if (id > 5) { + MM_ERR("Wrong POPP decoder id: %d\n", id); + return -EINVAL; + } + } else { + if (id > 3) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + + memset(&cmd, 0, sizeof(cmd)); + if (objtype) + cmd.common.stream = AUDPP_CMD_POPP_STREAM; + else + cmd.common.stream = AUDPP_CMD_COPP_STREAM; + + cmd.common.stream_id = id; + cmd.common.obj_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_CMD_SPECTROGRAM; + cmd.sample_interval = spa->sample_interval; + cmd.num_coeff = spa->num_coeff; + return audpp_send_queue3(&cmd, sizeof(cmd)); + +} +EXPORT_SYMBOL(audpp_dsp_set_spa); + +int audpp_dsp_set_stf(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_sidechain *stf, + enum obj_type objtype){ + if (objtype) { + if (id > 5) { + MM_ERR("Wrong POPP decoder id: %d\n", id); + return -EINVAL; + } + } else { + if (id > 3) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + + stf->common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + if (objtype) + stf->common.stream = AUDPP_CMD_POPP_STREAM; + else + stf->common.stream = AUDPP_CMD_COPP_STREAM; + + stf->common.stream_id = id; + stf->common.obj_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + stf->common.command_type = AUDPP_CMD_SIDECHAIN_TUNING_FILTER; + + if (enable) + stf->active_flag = AUDPP_CMD_STF_FLAG_ENA; + else + stf->active_flag = AUDPP_CMD_STF_FLAG_DIS; + return audpp_send_queue3(stf, + sizeof(struct audpp_cmd_cfg_object_params_sidechain)); +} +EXPORT_SYMBOL(audpp_dsp_set_stf); + +/* Implementation Of COPP + POPP */ +int audpp_dsp_set_eq(unsigned id, unsigned enable, + struct audpp_cmd_cfg_object_params_eqalizer *eq, + enum obj_type objtype) +{ + struct audpp_cmd_cfg_object_params_eqalizer cmd; + + if (objtype) { + if (id > 5) { + MM_ERR("Wrong POPP decoder id: %d\n", id); + return -EINVAL; + } + } else { + if (id > 3) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + + memset(&cmd, 0, sizeof(cmd)); + if (objtype) + cmd.common.stream = AUDPP_CMD_POPP_STREAM; + else + cmd.common.stream = AUDPP_CMD_COPP_STREAM; + + cmd.common.stream_id = id; + cmd.common.obj_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_CMD_EQUALIZER; + if (enable) { + cmd.eq_flag = AUDPP_CMD_EQ_FLAG_ENA; + cmd.num_bands = eq->num_bands; + memcpy(&cmd.eq_coeff, &eq->eq_coeff, sizeof(eq->eq_coeff)); + } else + cmd.eq_flag = AUDPP_CMD_EQ_FLAG_DIS; + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_eq); + +int audpp_dsp_set_vol_pan(unsigned id, + struct audpp_cmd_cfg_object_params_volume *vol_pan, + enum obj_type objtype) +{ + struct audpp_cmd_cfg_object_params_volume cmd; + + if (objtype) { + if (id > 5) { + MM_ERR("Wrong POPP decoder id: %d\n", id); + return -EINVAL; + } + } else { + if (id > AUDPP_MAX_COPP_DEVICES) { + MM_ERR("Wrong COPP decoder id: %d\n", id); + return -EINVAL; + } + } + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_OBJECT_PARAMS; + if (objtype) + cmd.common.stream = AUDPP_CMD_POPP_STREAM; + else + cmd.common.stream = AUDPP_CMD_COPP_STREAM; + + cmd.common.stream_id = id; + cmd.common.obj_cfg = AUDPP_CMD_CFG_OBJ_UPDATE; + cmd.common.command_type = AUDPP_CMD_VOLUME_PAN; + + cmd.volume = vol_pan->volume; + cmd.pan = vol_pan->pan; + + return audpp_send_queue3(&cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(audpp_dsp_set_vol_pan); + +int audpp_pause(unsigned id, int pause) +{ + /* pause 1 = pause 0 = resume */ + u16 pause_cmd[AUDPP_CMD_DEC_CTRL_LEN / sizeof(unsigned short)]; + + if (id >= CH_COUNT) + return -EINVAL; + + memset(pause_cmd, 0, sizeof(pause_cmd)); + + pause_cmd[0] = AUDPP_CMD_DEC_CTRL; + pause_cmd[1] = id; + if (pause == 1) + pause_cmd[2] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_PAUSE_V; + else if (pause == 0) + pause_cmd[2] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_RESUME_V; + else + return -EINVAL; + + return audpp_send_queue1(pause_cmd, sizeof(pause_cmd)); +} +EXPORT_SYMBOL(audpp_pause); + +int audpp_flush(unsigned id) +{ + u16 flush_cmd[AUDPP_CMD_DEC_CTRL_LEN / sizeof(unsigned short)]; + + if (id >= CH_COUNT) + return -EINVAL; + + memset(flush_cmd, 0, sizeof(flush_cmd)); + + flush_cmd[0] = AUDPP_CMD_DEC_CTRL; + flush_cmd[1] = id; + flush_cmd[2] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_FLUSH_V; + + return audpp_send_queue1(flush_cmd, sizeof(flush_cmd)); +} +EXPORT_SYMBOL(audpp_flush); + +/* dec_attrb = 7:0, 0 - No Decoder, else supported decoder * + * like mp3, aac, wma etc ... * + * = 15:8, bit[8] = 1 - Tunnel, bit[9] = 1 - NonTunnel * + * = 31:16, reserved */ +int audpp_adec_alloc(unsigned dec_attrb, const char **module_name, + unsigned *queueid) +{ + struct audpp_state *audpp = &the_audpp_state; + int decid = -1, idx, lidx, mode, codec; + int codecs_supported, min_codecs_supported; + unsigned int *concurrency_entry; + u8 max_instance, codec_type; + + struct dec_instance_table *dec_instance_list; + dec_instance_list = (struct dec_instance_table *) + (audpp->dec_database->dec_instance_list); + + mutex_lock(audpp->lock_dec); + /* Represents in bit mask */ + mode = ((dec_attrb & AUDPP_MODE_MASK) << 16); + codec = (1 << (dec_attrb & AUDPP_CODEC_MASK)); + codec_type = (dec_attrb & AUDPP_CODEC_MASK); + + /* Find whether same/different codec instances are running */ + audpp->decoder_count++; + audpp->codec_cnt[codec_type]++; + max_instance = 0; + + /*if different instance of codec*/ + if (audpp->codec_cnt[codec_type] < audpp->decoder_count) { + max_instance = audpp->codec_max_instances; + /* Get the maximum no. of instances that can be supported */ + for (idx = 0; idx < MSM_MAX_DEC_CNT; idx++) { + if (audpp->codec_cnt[idx]) { + if ((dec_instance_list + + audpp->op_mode * MSM_MAX_DEC_CNT + + idx)-> + max_instances_diff_dec < + max_instance) { + max_instance = + (dec_instance_list + + audpp->op_mode * + MSM_MAX_DEC_CNT + + idx)-> + max_instances_diff_dec; + } + } + } + /* if different codec type, should not cross maximum other + supported */ + if (audpp->decoder_count > (max_instance + 1)) { + MM_ERR("Can not support, already reached max\n"); + audpp->decoder_count--; + audpp->codec_cnt[codec_type]--; + goto done; + } + audpp->codec_max_instances = max_instance; + MM_DBG("different codec running\n"); + } else { + max_instance = (dec_instance_list + audpp->op_mode * + MSM_MAX_DEC_CNT + + codec_type)-> + max_instances_same_dec; + /* if same codec type, should not cross maximum supported */ + if (audpp->decoder_count > max_instance) { + MM_ERR("Can not support, already reached max\n"); + audpp->decoder_count--; + audpp->codec_cnt[codec_type]--; + goto done; + } + audpp->codec_max_instances = max_instance; + MM_DBG("same codec running\n"); + } + + /* Point to Last entry of the row */ + concurrency_entry = ((audpp->dec_database->dec_concurrency_table + + ((audpp->concurrency + 1) * + (audpp->dec_database->num_dec))) - 1); + + lidx = audpp->dec_database->num_dec; + min_codecs_supported = sizeof(unsigned int) * 8; + + MM_DBG("mode = 0x%08x codec = 0x%08x\n", mode, codec); + + for (idx = lidx; idx > 0; idx--, concurrency_entry--) { + if (!(audpp->dec_inuse & (1 << (idx - 1)))) { + if (((mode & *concurrency_entry) == mode) && + (codec & *concurrency_entry)) { + /* Check supports minimum number codecs */ + codecs_supported = + audpp->dec_database->dec_info_list[idx - + 1]. + nr_codec_support; + if (codecs_supported < min_codecs_supported) { + lidx = idx - 1; + min_codecs_supported = codecs_supported; + } + } + } + } + + if (lidx < audpp->dec_database->num_dec) { + audpp->dec_inuse |= (1 << lidx); + *module_name = + audpp->dec_database->dec_info_list[lidx].module_name; + *queueid = + audpp->dec_database->dec_info_list[lidx].module_queueid; + decid = audpp->dec_database->dec_info_list[lidx].module_decid; + audpp->dec_info_table[lidx].codec = + (dec_attrb & AUDPP_CODEC_MASK); + audpp->dec_info_table[lidx].pid = current->pid; + /* point to row to get supported operation */ + concurrency_entry = + ((audpp->dec_database->dec_concurrency_table + + ((audpp->concurrency) * (audpp->dec_database->num_dec))) + + lidx); + decid |= ((*concurrency_entry & AUDPP_OP_MASK) >> 12); + MM_INFO("decid =0x%08x module_name=%s, queueid=%d \n", decid, + *module_name, *queueid); + } +done: + mutex_unlock(audpp->lock_dec); + return decid; + +} +EXPORT_SYMBOL(audpp_adec_alloc); + +void audpp_adec_free(int decid) +{ + struct audpp_state *audpp = &the_audpp_state; + int idx; + mutex_lock(audpp->lock_dec); + for (idx = audpp->dec_database->num_dec; idx > 0; idx--) { + if (audpp->dec_database->dec_info_list[idx - 1].module_decid == + decid) { + audpp->decoder_count--; + audpp->\ + codec_cnt[audpp->dec_info_table[idx - 1].codec]--; + audpp->dec_inuse &= ~(1 << (idx - 1)); + audpp->dec_info_table[idx - 1].codec = -1; + audpp->dec_info_table[idx - 1].pid = 0; + MM_INFO("free decid =%d \n", decid); + break; + } + } + mutex_unlock(audpp->lock_dec); + return; + +} +EXPORT_SYMBOL(audpp_adec_free); + +static ssize_t concurrency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct audpp_state *audpp = &the_audpp_state; + int rc; + mutex_lock(audpp->lock_dec); + rc = sprintf(buf, "%ld\n", audpp->concurrency); + mutex_unlock(audpp->lock_dec); + return rc; +} + +static ssize_t concurrency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct audpp_state *audpp = &the_audpp_state; + unsigned long concurrency; + int rc = -1; + mutex_lock(audpp->lock_dec); + if (audpp->dec_inuse) { + MM_ERR("Can not change profile, while playback in progress\n"); + goto done; + } + rc = strict_strtoul(buf, 10, &concurrency); + if (!rc && + (concurrency < audpp->dec_database->num_concurrency_support)) { + audpp->concurrency = concurrency; + MM_DBG("Concurrency case %ld\n", audpp->concurrency); + rc = count; + } else { + MM_ERR("Not a valid Concurrency case\n"); + rc = -EINVAL; + } +done: + mutex_unlock(audpp->lock_dec); + return rc; +} + +static ssize_t decoder_info_show(struct device *dev, + struct device_attribute *attr, char *buf); +static struct device_attribute dev_attr_decoder[AUDPP_MAX_DECODER_CNT] = { + __ATTR(decoder0, S_IRUGO, decoder_info_show, NULL), + __ATTR(decoder1, S_IRUGO, decoder_info_show, NULL), + __ATTR(decoder2, S_IRUGO, decoder_info_show, NULL), + __ATTR(decoder3, S_IRUGO, decoder_info_show, NULL), + __ATTR(decoder4, S_IRUGO, decoder_info_show, NULL), +}; + +static ssize_t decoder_info_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int cpy_sz = 0; + struct audpp_state *audpp = &the_audpp_state; + const ptrdiff_t off = attr - dev_attr_decoder; /* decoder number */ + mutex_lock(audpp->lock_dec); + cpy_sz += scnprintf(buf + cpy_sz, PAGE_SIZE - cpy_sz, "%d:", + audpp->dec_info_table[off].codec); + cpy_sz += scnprintf(buf + cpy_sz, PAGE_SIZE - cpy_sz, "%d\n", + audpp->dec_info_table[off].pid); + mutex_unlock(audpp->lock_dec); + return cpy_sz; +} + +static DEVICE_ATTR(concurrency, S_IWUSR | S_IRUGO, concurrency_show, + concurrency_store); +static int audpp_probe(struct platform_device *pdev) +{ + int rc, idx; + struct audpp_state *audpp = &the_audpp_state; + audpp->concurrency = AUDPP_CONCURRENCY_DEFAULT; + audpp->dec_database = + (struct msm_adspdec_database *)pdev->dev.platform_data; + + MM_INFO("Number of decoder supported %d\n", + audpp->dec_database->num_dec); + MM_INFO("Number of concurrency supported %d\n", + audpp->dec_database->num_concurrency_support); + init_waitqueue_head(&audpp->event_wait); + for (idx = 0; idx < audpp->dec_database->num_dec; idx++) { + audpp->dec_info_table[idx].codec = -1; + audpp->dec_info_table[idx].pid = 0; + MM_INFO("module_name:%s\n", + audpp->dec_database->dec_info_list[idx].module_name); + MM_INFO("queueid:%d\n", + audpp->dec_database->dec_info_list[idx].module_queueid); + MM_INFO("decid:%d\n", + audpp->dec_database->dec_info_list[idx].module_decid); + MM_INFO("nr_codec_support:%d\n", + audpp->dec_database->dec_info_list[idx]. + nr_codec_support); + } + + wake_lock_init(&audpp_wake_lock, WAKE_LOCK_SUSPEND, "audpp"); + for (idx = 0; idx < audpp->dec_database->num_dec; idx++) { + rc = device_create_file(&pdev->dev, &dev_attr_decoder[idx]); + if (rc) + goto err; + } + rc = device_create_file(&pdev->dev, &dev_attr_concurrency); + audpp->op_mode = 0; /* Consider as non turbo mode */ + if (rc) + goto err; + else + goto done; +err: + while (idx--) + device_remove_file(&pdev->dev, &dev_attr_decoder[idx]); +done: + return rc; +} + +static struct platform_driver audpp_plat_driver = { + .probe = audpp_probe, + .driver = { + .name = "msm_adspdec", + .owner = THIS_MODULE, + }, +}; + +static int __init audpp_init(void) +{ + return platform_driver_register(&audpp_plat_driver); +} + +device_initcall(audpp_init); diff --git a/arch/arm/mach-msm/qdsp5v2/audpreproc.c b/arch/arm/mach-msm/qdsp5v2/audpreproc.c new file mode 100644 index 00000000000..09611a552ce --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/audpreproc.c @@ -0,0 +1,523 @@ +/* + * Common code to deal with the AUDPREPROC dsp task (audio preprocessing) + * + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * + * Based on the audpp layer in arch/arm/mach-msm/qdsp5/audpp.c + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_MUTEX(audpreproc_lock); +static struct wake_lock audpre_wake_lock; +static struct wake_lock audpre_idle_wake_lock; + +struct msm_adspenc_info { + const char *module_name; + unsigned module_queueids; + int module_encid; /* streamid */ + int enc_formats; /* supported formats */ + int nr_codec_support; /* number of codec suported */ +}; + +#define ENC_MODULE_INFO(name, queueids, encid, formats, nr_codec) \ + {.module_name = name, .module_queueids = queueids, \ + .module_encid = encid, .enc_formats = formats, \ + .nr_codec_support = nr_codec } + +#define MAX_EVENT_CALLBACK_CLIENTS 1 + +#define ENC0_FORMAT ((1<func[cfg_done_msg.stream_id]) + audpreproc->func[cfg_done_msg.stream_id]( + audpreproc->private[cfg_done_msg.stream_id], id, + &cfg_done_msg); + break; + } + case AUDPREPROC_ERROR_MSG: { + struct audpreproc_err_msg err_msg; + + getevent(&err_msg, AUDPREPROC_ERROR_MSG_LEN); + MM_DBG("AUDPREPROC_ERROR_MSG: stream id %d err idx %d\n", + err_msg.stream_id, err_msg.aud_preproc_err_idx); + if ((err_msg.stream_id < MAX_ENC_COUNT) && + audpreproc->func[err_msg.stream_id]) + audpreproc->func[err_msg.stream_id]( + audpreproc->private[err_msg.stream_id], id, + &err_msg); + break; + } + case AUDPREPROC_CMD_ENC_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_cfg_done_msg enc_cfg_msg; + + getevent(&enc_cfg_msg, AUDPREPROC_CMD_ENC_CFG_DONE_MSG_LEN); + MM_DBG("AUDPREPROC_CMD_ENC_CFG_DONE_MSG: stream id %d enc type \ + %d\n", enc_cfg_msg.stream_id, enc_cfg_msg.rec_enc_type); + if ((enc_cfg_msg.stream_id < MAX_ENC_COUNT) && + audpreproc->func[enc_cfg_msg.stream_id]) + audpreproc->func[enc_cfg_msg.stream_id]( + audpreproc->private[enc_cfg_msg.stream_id], id, + &enc_cfg_msg); + for (n = 0; n < MAX_EVENT_CALLBACK_CLIENTS; ++n) { + if (audpreproc->cb_tbl[n] && + audpreproc->cb_tbl[n]->fn) { + audpreproc->cb_tbl[n]->fn( \ + audpreproc->cb_tbl[n]->private, \ + id, (void *) &enc_cfg_msg); + } + } + break; + } + case AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_param_cfg_done_msg enc_param_msg; + + getevent(&enc_param_msg, + AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG_LEN); + MM_DBG("AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: stream id %d\n", + enc_param_msg.stream_id); + if ((enc_param_msg.stream_id < MAX_ENC_COUNT) && + audpreproc->func[enc_param_msg.stream_id]) + audpreproc->func[enc_param_msg.stream_id]( + audpreproc->private[enc_param_msg.stream_id], id, + &enc_param_msg); + break; + } + case AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: { + struct audpreproc_afe_cmd_audio_record_cfg_done + record_cfg_done; + getevent(&record_cfg_done, + AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG_LEN); + MM_DBG("AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: \ + stream id %d\n", record_cfg_done.stream_id); + if ((record_cfg_done.stream_id < MAX_ENC_COUNT) && + audpreproc->func[record_cfg_done.stream_id]) + audpreproc->func[record_cfg_done.stream_id]( + audpreproc->private[record_cfg_done.stream_id], id, + &record_cfg_done); + break; + } + case AUDPREPROC_CMD_ROUTING_MODE_DONE_MSG: { + struct audpreproc_cmd_routing_mode_done routing_mode_done; + + getevent(&routing_mode_done, + AUDPREPROC_CMD_ROUTING_MODE_DONE_MSG_LEN); + MM_DBG("AUDPREPROC_CMD_ROUTING_MODE_DONE_MSG: \ + stream id %d\n", routing_mode_done.stream_id); + if ((routing_mode_done.stream_id < MAX_ENC_COUNT) && + audpreproc->func[routing_mode_done.stream_id]) + audpreproc->func[routing_mode_done.stream_id]( + audpreproc->private[routing_mode_done.stream_id], id, + &routing_mode_done); + break; + } +#ifdef CONFIG_DEBUG_FS + case AUDPREPROC_MSG_FEAT_QUERY_DM_DONE: + { + uint16_t msg[3]; + getevent(msg, sizeof(msg)); + MM_INFO("RTC ACK --> %x %x %x\n", msg[0], msg[1], msg[2]); + acdb_rtc_set_err(msg[2]); + } + break; +#endif + case ADSP_MESSAGE_ID: { + MM_DBG("Received ADSP event:module audpreproctask\n"); + break; + } + default: + MM_ERR("Unknown Event %d\n", id); + } + return; +} + +static struct msm_adsp_ops adsp_ops = { + .event = audpreproc_dsp_event, +}; + +/* EXPORTED API's */ +int audpreproc_enable(int enc_id, audpreproc_event_func func, void *private) +{ + struct audpreproc_state *audpreproc = &the_audpreproc_state; + int res = 0; + + if (enc_id < 0 || enc_id > (MAX_ENC_COUNT - 1)) + return -EINVAL; + + mutex_lock(audpreproc->lock); + if (audpreproc->func[enc_id]) { + res = -EBUSY; + goto out; + } + + audpreproc->func[enc_id] = func; + audpreproc->private[enc_id] = private; + + /* First client to enable preproc task */ + if (audpreproc->open_count++ == 0) { + MM_DBG("Get AUDPREPROCTASK\n"); + res = msm_adsp_get("AUDPREPROCTASK", &audpreproc->mod, + &adsp_ops, audpreproc); + if (res < 0) { + MM_ERR("Can not get AUDPREPROCTASK\n"); + audpreproc->open_count = 0; + audpreproc->func[enc_id] = NULL; + audpreproc->private[enc_id] = NULL; + goto out; + } + prevent_suspend(); + if (msm_adsp_enable(audpreproc->mod)) { + MM_ERR("Can not enable AUDPREPROCTASK\n"); + audpreproc->open_count = 0; + audpreproc->func[enc_id] = NULL; + audpreproc->private[enc_id] = NULL; + msm_adsp_put(audpreproc->mod); + audpreproc->mod = NULL; + res = -ENODEV; + allow_suspend(); + goto out; + } + } + res = 0; +out: + mutex_unlock(audpreproc->lock); + return res; +} +EXPORT_SYMBOL(audpreproc_enable); + +int audpreproc_update_audrec_info( + struct audrec_session_info *audrec_session_info) +{ + if (!audrec_session_info) { + MM_ERR("error in audrec session info address\n"); + return -EINVAL; + } + if (audrec_session_info->session_id < MAX_ENC_COUNT) { + memcpy(&session_info[audrec_session_info->session_id], + audrec_session_info, + sizeof(struct audrec_session_info)); + return 0; + } + return -EINVAL; +} +EXPORT_SYMBOL(audpreproc_update_audrec_info); + +void audpreproc_disable(int enc_id, void *private) +{ + struct audpreproc_state *audpreproc = &the_audpreproc_state; + + if (enc_id < 0 || enc_id > (MAX_ENC_COUNT - 1)) + return; + + mutex_lock(audpreproc->lock); + if (!audpreproc->func[enc_id]) + goto out; + if (audpreproc->private[enc_id] != private) + goto out; + + audpreproc->func[enc_id] = NULL; + audpreproc->private[enc_id] = NULL; + + /* Last client then disable preproc task */ + if (--audpreproc->open_count == 0) { + msm_adsp_disable(audpreproc->mod); + MM_DBG("Put AUDPREPROCTASK\n"); + msm_adsp_put(audpreproc->mod); + audpreproc->mod = NULL; + allow_suspend(); + } +out: + mutex_unlock(audpreproc->lock); + return; +} +EXPORT_SYMBOL(audpreproc_disable); + + +int audpreproc_register_event_callback(struct audpreproc_event_callback *ecb) +{ + struct audpreproc_state *audpreproc = &the_audpreproc_state; + int i; + + for (i = 0; i < MAX_EVENT_CALLBACK_CLIENTS; ++i) { + if (NULL == audpreproc->cb_tbl[i]) { + audpreproc->cb_tbl[i] = ecb; + return 0; + } + } + return -1; +} +EXPORT_SYMBOL(audpreproc_register_event_callback); + +int audpreproc_unregister_event_callback(struct audpreproc_event_callback *ecb) +{ + struct audpreproc_state *audpreproc = &the_audpreproc_state; + int i; + + for (i = 0; i < MAX_EVENT_CALLBACK_CLIENTS; ++i) { + if (ecb == audpreproc->cb_tbl[i]) { + audpreproc->cb_tbl[i] = NULL; + return 0; + } + } + return -1; +} +EXPORT_SYMBOL(audpreproc_unregister_event_callback); + + +/* enc_type = supported encode format * + * like pcm, aac, sbc, evrc, qcelp, amrnb etc ... * + */ +int audpreproc_aenc_alloc(unsigned enc_type, const char **module_name, + unsigned *queue_ids) +{ + struct audpreproc_state *audpreproc = &the_audpreproc_state; + int encid = -1, idx, lidx, mode, codec; + int codecs_supported, min_codecs_supported; + static int wakelock_init; + + mutex_lock(audpreproc->lock); + /* Represents in bit mask */ + mode = ((enc_type & AUDPREPROC_MODE_MASK) << 16); + codec = (1 << (enc_type & AUDPREPROC_CODEC_MASK)); + + lidx = msm_enc_database.num_enc; + min_codecs_supported = sizeof(unsigned int) * 8; + MM_DBG("mode = 0x%08x codec = 0x%08x\n", mode, codec); + + for (idx = lidx-1; idx >= 0; idx--) { + /* encoder free and supports the format */ + if (!(audpreproc->enc_inuse & (1 << (idx))) && + ((mode & msm_enc_database.enc_info_list[idx].enc_formats) + == mode) && ((codec & + msm_enc_database.enc_info_list[idx].enc_formats) + == codec)){ + /* Check supports minimum number codecs */ + codecs_supported = + msm_enc_database.enc_info_list[idx].nr_codec_support; + if (codecs_supported < min_codecs_supported) { + lidx = idx; + min_codecs_supported = codecs_supported; + } + } + } + + if (lidx < msm_enc_database.num_enc) { + audpreproc->enc_inuse |= (1 << lidx); + *module_name = + msm_enc_database.enc_info_list[lidx].module_name; + *queue_ids = + msm_enc_database.enc_info_list[lidx].module_queueids; + encid = msm_enc_database.enc_info_list[lidx].module_encid; + } + + if (!wakelock_init) { + wake_lock_init(&audpre_wake_lock, WAKE_LOCK_SUSPEND, "audpre"); + wake_lock_init(&audpre_idle_wake_lock, WAKE_LOCK_IDLE, + "audpre_idle"); + wakelock_init = 1; + } + + mutex_unlock(audpreproc->lock); + return encid; +} +EXPORT_SYMBOL(audpreproc_aenc_alloc); + +void audpreproc_aenc_free(int enc_id) +{ + struct audpreproc_state *audpreproc = &the_audpreproc_state; + int idx; + + mutex_lock(audpreproc->lock); + for (idx = 0; idx < msm_enc_database.num_enc; idx++) { + if (msm_enc_database.enc_info_list[idx].module_encid == + enc_id) { + audpreproc->enc_inuse &= ~(1 << idx); + break; + } + } + mutex_unlock(audpreproc->lock); + return; + +} +EXPORT_SYMBOL(audpreproc_aenc_free); + +int audpreproc_send_preproccmdqueue(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpreproc_state.mod, + QDSP_uPAudPreProcCmdQueue, cmd, len); +} +EXPORT_SYMBOL(audpreproc_send_preproccmdqueue); + +int audpreproc_send_audreccmdqueue(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpreproc_state.mod, + QDSP_uPAudPreProcAudRecCmdQueue, cmd, len); +} +EXPORT_SYMBOL(audpreproc_send_audreccmdqueue); + +int audpreproc_send_audrec2cmdqueue(void *cmd, unsigned len) +{ + return msm_adsp_write(the_audpreproc_state.mod, + QDSP_uPAudRec2CmdQueue, cmd, len); +} +EXPORT_SYMBOL(audpreproc_send_audrec2cmdqueue); + +int audpreproc_dsp_set_agc(struct audpreproc_cmd_cfg_agc_params *agc, + unsigned len) +{ + return msm_adsp_write(the_audpreproc_state.mod, + QDSP_uPAudPreProcCmdQueue, agc, len); +} +EXPORT_SYMBOL(audpreproc_dsp_set_agc); + +int audpreproc_dsp_set_agc2(struct audpreproc_cmd_cfg_agc_params_2 *agc2, + unsigned len) +{ + return msm_adsp_write(the_audpreproc_state.mod, + QDSP_uPAudPreProcCmdQueue, agc2, len); +} +EXPORT_SYMBOL(audpreproc_dsp_set_agc2); + +int audpreproc_dsp_set_ns(struct audpreproc_cmd_cfg_ns_params *ns, + unsigned len) +{ + return msm_adsp_write(the_audpreproc_state.mod, + QDSP_uPAudPreProcCmdQueue, ns, len); +} +EXPORT_SYMBOL(audpreproc_dsp_set_ns); + +int audpreproc_dsp_set_iir( +struct audpreproc_cmd_cfg_iir_tuning_filter_params *iir, unsigned len) +{ + return msm_adsp_write(the_audpreproc_state.mod, + QDSP_uPAudPreProcCmdQueue, iir, len); +} +EXPORT_SYMBOL(audpreproc_dsp_set_iir); + +int audpreproc_dsp_set_gain_tx( + struct audpreproc_cmd_cfg_cal_gain *calib_gain_tx, unsigned len) +{ + return msm_adsp_write(the_audpreproc_state.mod, + QDSP_uPAudPreProcCmdQueue, calib_gain_tx, len); +} +EXPORT_SYMBOL(audpreproc_dsp_set_gain_tx); + +void get_audrec_session_info(int id, struct audrec_session_info *info) +{ + if (id >= MAX_ENC_COUNT) { + MM_ERR("invalid session id = %d\n", id); + return; + } + memcpy(info, &session_info[id], sizeof(struct audrec_session_info)); +} +EXPORT_SYMBOL(get_audrec_session_info); + +int audpreproc_dsp_set_lvnv( + struct audpreproc_cmd_cfg_lvnv_param *preproc_lvnv, unsigned len) +{ + return msm_adsp_write(the_audpreproc_state.mod, + QDSP_uPAudPreProcCmdQueue, preproc_lvnv, len); +} +EXPORT_SYMBOL(audpreproc_dsp_set_lvnv); + diff --git a/arch/arm/mach-msm/qdsp5v2/aux_pcm.c b/arch/arm/mach-msm/qdsp5v2/aux_pcm.c new file mode 100644 index 00000000000..712766d1e7b --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/aux_pcm.c @@ -0,0 +1,280 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*---------------------------------------------------------------------------- + * Preprocessor Definitions and Constants + * -------------------------------------------------------------------------*/ + +/* define offset of registers here, may put them into platform data */ +#define AUX_CODEC_CTL_OFFSET 0x00 +#define PCM_PATH_CTL_OFFSET 0x04 +#define AUX_CODEC_CTL_OUT_OFFSET 0x08 + +/* define some bit values in PCM_PATH_CTL register */ +#define PCM_PATH_CTL__ADSP_CTL_EN_BMSK 0x8 + +/* mask and shift */ +#define AUX_CODEC_CTL_ADSP_CODEC_CTL_EN_BMSK 0x800 +#define AUX_CODEC_CTL_PCM_SYNC_LONG_BMSK 0x400 +#define AUX_CODEC_CTL_PCM_SYNC_SHORT_BMSK 0x200 +#define AUX_CODEC_CTL_I2S_SAMPLE_CLK_SRC_BMSK 0x80 +#define AUX_CODEC_CTL_I2S_SAMPLE_CLK_MODE_BMSK 0x40 +#define AUX_CODEC_CTL_I2S_RX_MODE_BMSK 0x20 +#define AUX_CODEC_CTL_I2S_CLK_MODE_BMSK 0x10 +#define AUX_CODEC_CTL_AUX_PCM_MODE_BMSK 0x0b +#define AUX_CODEC_CTL_AUX_CODEC_MODE_BMSK 0x02 + +/* AUX PCM MODE */ +#define MASTER_PRIM_PCM_SHORT 0 +#define MASTER_AUX_PCM_LONG 1 +#define SLAVE_PRIM_PCM_SHORT 2 + +struct aux_pcm_state { + void __iomem *aux_pcm_base; /* configure aux pcm through Scorpion */ + int dout; + int din; + int syncout; + int clkin_a; +}; + +static struct aux_pcm_state the_aux_pcm_state; + +static void __iomem *get_base_addr(struct aux_pcm_state *aux_pcm) +{ + return aux_pcm->aux_pcm_base; +} + +/* Set who control aux pcm : adsp or MSM */ +void aux_codec_adsp_codec_ctl_en(bool msm_adsp_en) +{ + void __iomem *baddr = get_base_addr(&the_aux_pcm_state); + uint32_t val; + + if (!IS_ERR(baddr)) { + val = readl(baddr + AUX_CODEC_CTL_OFFSET); + if (msm_adsp_en) { /* adsp */ + writel( + ((val & ~AUX_CODEC_CTL_ADSP_CODEC_CTL_EN_BMSK) | + AUX_CODEC_CTL__ADSP_CODEC_CTL_EN__ADSP_V), + baddr + AUX_CODEC_CTL_OFFSET); + } else { /* MSM */ + writel( + ((val & ~AUX_CODEC_CTL_ADSP_CODEC_CTL_EN_BMSK) | + AUX_CODEC_CTL__ADSP_CODEC_CTL_EN__MSM_V), + baddr + AUX_CODEC_CTL_OFFSET); + } + } + mb(); +} + +/* Set who control aux pcm path: adsp or MSM */ +void aux_codec_pcm_path_ctl_en(bool msm_adsp_en) +{ + void __iomem *baddr = get_base_addr(&the_aux_pcm_state); + uint32_t val; + + if (!IS_ERR(baddr)) { + val = readl(baddr + PCM_PATH_CTL_OFFSET); + if (msm_adsp_en) { /* adsp */ + writel( + ((val & ~PCM_PATH_CTL__ADSP_CTL_EN_BMSK) | + PCM_PATH_CTL__ADSP_CTL_EN__ADSP_V), + baddr + PCM_PATH_CTL_OFFSET); + } else { /* MSM */ + writel( + ((val & ~PCM_PATH_CTL__ADSP_CTL_EN_BMSK) | + PCM_PATH_CTL__ADSP_CTL_EN__MSM_V), + baddr + PCM_PATH_CTL_OFFSET); + } + } + mb(); + return; +} +EXPORT_SYMBOL(aux_codec_pcm_path_ctl_en); + +int aux_pcm_gpios_request(void) +{ + int rc = 0; + + MM_DBG("aux_pcm_gpios_request\n"); + rc = gpio_request(the_aux_pcm_state.dout, "AUX PCM DOUT"); + if (rc) { + MM_ERR("GPIO request for AUX PCM DOUT failed\n"); + return rc; + } + + rc = gpio_request(the_aux_pcm_state.din, "AUX PCM DIN"); + if (rc) { + MM_ERR("GPIO request for AUX PCM DIN failed\n"); + gpio_free(the_aux_pcm_state.dout); + return rc; + } + + rc = gpio_request(the_aux_pcm_state.syncout, "AUX PCM SYNC OUT"); + if (rc) { + MM_ERR("GPIO request for AUX PCM SYNC OUT failed\n"); + gpio_free(the_aux_pcm_state.dout); + gpio_free(the_aux_pcm_state.din); + return rc; + } + + rc = gpio_request(the_aux_pcm_state.clkin_a, "AUX PCM CLKIN A"); + if (rc) { + MM_ERR("GPIO request for AUX PCM CLKIN A failed\n"); + gpio_free(the_aux_pcm_state.dout); + gpio_free(the_aux_pcm_state.din); + gpio_free(the_aux_pcm_state.syncout); + return rc; + } + + return rc; +} +EXPORT_SYMBOL(aux_pcm_gpios_request); + + +void aux_pcm_gpios_free(void) +{ + MM_DBG(" aux_pcm_gpios_free \n"); + + /* + * Feed silence frames before close to prevent buzzing sound in BT at + * call end. This fix is applicable only to Marimba BT. + */ + gpio_tlmm_config(GPIO_CFG(the_aux_pcm_state.dout, 0, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_2MA), GPIO_CFG_ENABLE); + gpio_set_value(the_aux_pcm_state.dout, 0); + msleep(20); + gpio_tlmm_config(GPIO_CFG(the_aux_pcm_state.dout, 1, GPIO_CFG_OUTPUT, + GPIO_CFG_NO_PULL, GPIO_CFG_2MA), GPIO_CFG_ENABLE); + + gpio_free(the_aux_pcm_state.dout); + gpio_free(the_aux_pcm_state.din); + gpio_free(the_aux_pcm_state.syncout); + gpio_free(the_aux_pcm_state.clkin_a); +} +EXPORT_SYMBOL(aux_pcm_gpios_free); + + +static int get_aux_pcm_gpios(struct platform_device *pdev) +{ + int rc = 0; + struct resource *res; + + /* Claim all of the GPIOs. */ + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "aux_pcm_dout"); + if (!res) { + MM_ERR("%s: failed to get gpio AUX PCM DOUT\n", __func__); + return -ENODEV; + } + + the_aux_pcm_state.dout = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "aux_pcm_din"); + if (!res) { + MM_ERR("%s: failed to get gpio AUX PCM DIN\n", __func__); + return -ENODEV; + } + + the_aux_pcm_state.din = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "aux_pcm_syncout"); + if (!res) { + MM_ERR("%s: failed to get gpio AUX PCM SYNC OUT\n", __func__); + return -ENODEV; + } + + the_aux_pcm_state.syncout = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "aux_pcm_clkin_a"); + if (!res) { + MM_ERR("%s: failed to get gpio AUX PCM CLKIN A\n", __func__); + return -ENODEV; + } + + the_aux_pcm_state.clkin_a = res->start; + + return rc; +} +static int aux_pcm_probe(struct platform_device *pdev) +{ + int rc = 0; + struct resource *mem_src; + + MM_DBG("aux_pcm_probe \n"); + mem_src = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "aux_codec_reg_addr"); + if (!mem_src) { + rc = -ENODEV; + goto done; + } + + the_aux_pcm_state.aux_pcm_base = ioremap(mem_src->start, + (mem_src->end - mem_src->start) + 1); + if (!the_aux_pcm_state.aux_pcm_base) { + rc = -ENOMEM; + goto done; + } + rc = get_aux_pcm_gpios(pdev); + if (rc) { + MM_ERR("GPIO configuration failed\n"); + rc = -ENODEV; + } + +done: return rc; + +} + +static int aux_pcm_remove(struct platform_device *pdev) +{ + iounmap(the_aux_pcm_state.aux_pcm_base); + return 0; +} + +static struct platform_driver aux_pcm_driver = { + .probe = aux_pcm_probe, + .remove = aux_pcm_remove, + .driver = { + .name = "msm_aux_pcm", + .owner = THIS_MODULE, + }, +}; + +static int __init aux_pcm_init(void) +{ + + return platform_driver_register(&aux_pcm_driver); +} + +static void __exit aux_pcm_exit(void) +{ + platform_driver_unregister(&aux_pcm_driver); +} + +module_init(aux_pcm_init); +module_exit(aux_pcm_exit); + +MODULE_DESCRIPTION("MSM AUX PCM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/lpa.c b/arch/arm/mach-msm/qdsp5v2/lpa.c new file mode 100644 index 00000000000..c4e0feeaa0a --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/lpa.c @@ -0,0 +1,608 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LPA_REG_WRITEL(drv, val, reg) writel(val, drv->baseaddr + reg) +#define LPA_REG_READL(drv, reg) readl(drv->baseaddr + reg) + +/* bit 2:0 is reserved because watermarks have to be 64-bit aligned */ +#define LLB_WATERMARK_VAL_MASK 0x00000003 + +#define LPA_STATUS_SBUF_EN 0x01 + +struct lpa_drv { + void __iomem *baseaddr; + u32 obuf_hlb_size; + u32 dsp_proc_id; + u32 app_proc_id; + struct lpa_mem_config nosb_config; + struct lpa_mem_config sb_config; + u32 status; + u32 watermark_bytes; + u32 watermark_aheadtime; + u32 sample_boundary; +}; + +struct lpa_state { + struct lpa_drv lpa_drv; /* One instance for now */ + u32 assigned; + struct mutex lpa_lock; +}; + +struct lpa_state the_lpa_state; + +static void lpa_enable_codec(struct lpa_drv *lpa, bool enable) +{ + u32 val; + + val = LPA_REG_READL(lpa, LPA_OBUF_CODEC); + val = enable ? (val | LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK) : + (val & ~LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK); + val |= LPA_OBUF_CODEC_LOAD_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_CODEC); + mb(); +} + +static void lpa_reset(struct lpa_drv *lpa) +{ + u32 status; + struct clk *adsp_clk; + /* Need to make sure not disable clock while other device is enabled */ + adsp_clk = clk_get(NULL, "adsp_clk"); + if (!adsp_clk) { + MM_ERR("failed to get adsp clk\n"); + goto error; + } + clk_enable(adsp_clk); + lpa_enable_codec(lpa, 0); + LPA_REG_WRITEL(lpa, (LPA_OBUF_RESETS_MISR_RESET | + LPA_OBUF_RESETS_OVERALL_RESET), LPA_OBUF_RESETS); + do { + status = LPA_REG_READL(lpa, LPA_OBUF_STATUS); + } while (!(status & LPA_OBUF_STATUS_RESET_DONE)); + + LPA_REG_WRITEL(lpa, LPA_OBUF_ACK_RESET_DONE_BMSK, LPA_OBUF_ACK); + mb(); + clk_disable(adsp_clk); + clk_put(adsp_clk); +error: + return; +} + +static void lpa_config_hlb_addr(struct lpa_drv *lpa) +{ + u32 val, min_addr = 0, max_addr = min_addr + lpa->obuf_hlb_size; + + val = (min_addr & LPA_OBUF_HLB_MIN_ADDR_SEG_BMSK) | + LPA_OBUF_HLB_MIN_ADDR_LOAD_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_HLB_MIN_ADDR); + val = max_addr & LPA_OBUF_HLB_MAX_ADDR_SEG_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_HLB_MAX_ADDR); +} + +static void lpa_powerup_mem_bank(struct lpa_drv *lpa, + struct lpa_mem_bank_select *bank) +{ + u32 status, val; + + status = LPA_REG_READL(lpa, LPA_OBUF_MEMORY_CONTROL); + val = ((*((u32 *) bank)) << LPA_OBUF_MEM_CTL_PWRUP_SHFT) & + LPA_OBUF_MEM_CTL_PWRUP_BMSK; + val |= status; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_MEMORY_CONTROL); +} + +static void lpa_enable_interrupt(struct lpa_drv *lpa, u32 proc_id) +{ + u32 val; + + proc_id &= LPA_OBUF_INTR_EN_BMSK; + val = 0x1 << proc_id; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_INTR_ENABLE); +} + +static void lpa_config_llb_addr(struct lpa_drv *lpa, u32 min_addr, u32 max_addr) +{ + u32 val; + + val = (min_addr & LPA_OBUF_LLB_MIN_ADDR_SEG_BMSK) | + LPA_OBUF_LLB_MIN_ADDR_LOAD_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_LLB_MIN_ADDR); + val = max_addr & LPA_OBUF_LLB_MAX_ADDR_SEG_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_LLB_MAX_ADDR); +} + +static void lpa_config_sb_addr(struct lpa_drv *lpa, u32 min_addr, u32 max_addr) +{ + u32 val; + + val = (min_addr & LPA_OBUF_SB_MIN_ADDR_SEG_BMSK) | + LPA_OBUF_SB_MIN_ADDR_LOAD_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_SB_MIN_ADDR); + val = max_addr & LPA_OBUF_SB_MAX_ADDR_SEG_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_SB_MAX_ADDR); +} + +static void lpa_switch_sb(struct lpa_drv *lpa) +{ + if (lpa->status & LPA_STATUS_SBUF_EN) { + lpa_config_llb_addr(lpa, lpa->sb_config.llb_min_addr, + lpa->sb_config.llb_max_addr); + lpa_config_sb_addr(lpa, lpa->sb_config.sb_min_addr, + lpa->sb_config.sb_max_addr); + } else { + lpa_config_llb_addr(lpa, lpa->nosb_config.llb_min_addr, + lpa->nosb_config.llb_max_addr); + lpa_config_sb_addr(lpa, lpa->nosb_config.sb_min_addr, + lpa->nosb_config.sb_max_addr); + } +} + +static u8 lpa_req_wmark_id(struct lpa_drv *lpa) +{ + return (u8) (LPA_REG_READL(lpa, LPA_OBUF_WMARK_ASSIGN) & + LPA_OBUF_WMARK_ASSIGN_BMSK); +} + +static void lpa_enable_llb_wmark(struct lpa_drv *lpa, u32 wmark_ctrl, + u32 wmark_id, u32 cpu_id) +{ + u32 val; + + wmark_id = (wmark_id > 3) ? 0 : wmark_id; + val = LPA_REG_READL(lpa, LPA_OBUF_WMARK_n_LLB_ADDR(wmark_id)); + val &= ~LPA_OBUF_LLB_WMARK_CTRL_BMSK; + val &= ~LPA_OBUF_LLB_WMARK_MAP_BMSK; + val |= (wmark_ctrl << LPA_OBUF_LLB_WMARK_CTRL_SHFT) & + LPA_OBUF_LLB_WMARK_CTRL_BMSK; + val |= (cpu_id << LPA_OBUF_LLB_WMARK_MAP_SHFT) & + LPA_OBUF_LLB_WMARK_MAP_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_WMARK_n_LLB_ADDR(wmark_id)); +} + +static void lpa_enable_sb_wmark(struct lpa_drv *lpa, u32 wmark_ctrl, + u32 cpu_id) +{ + u32 val; + + val = LPA_REG_READL(lpa, LPA_OBUF_WMARK_SB); + val &= ~LPA_OBUF_SB_WMARK_CTRL_BMSK; + val &= ~LPA_OBUF_SB_WMARK_MAP_BMSK; + val |= (wmark_ctrl << LPA_OBUF_SB_WMARK_CTRL_SHFT) & + LPA_OBUF_SB_WMARK_CTRL_BMSK; + val |= (cpu_id << LPA_OBUF_SB_WMARK_MAP_SHFT) & + LPA_OBUF_SB_WMARK_MAP_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_WMARK_SB); +} +static void lpa_enable_hlb_wmark(struct lpa_drv *lpa, u32 wmark_ctrl, + u32 cpu_id) +{ + u32 val; + + val = LPA_REG_READL(lpa, LPA_OBUF_WMARK_HLB); + val &= ~LPA_OBUF_HLB_WMARK_CTRL_BMSK; + val &= ~LPA_OBUF_HLB_WMARK_MAP_BMSK; + val |= (wmark_ctrl << LPA_OBUF_HLB_WMARK_CTRL_SHFT) & + LPA_OBUF_HLB_WMARK_CTRL_BMSK; + val |= (cpu_id << LPA_OBUF_HLB_WMARK_MAP_SHFT) & + LPA_OBUF_HLB_WMARK_MAP_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_WMARK_HLB); +} + +static void lpa_enable_utc(struct lpa_drv *lpa, bool enable, u32 cpu_id) +{ + u32 val; + + val = (cpu_id << LPA_OBUF_UTC_CONFIG_MAP_SHFT) & + LPA_OBUF_UTC_CONFIG_MAP_BMSK; + enable = (enable ? 1 : 0); + val = (enable << LPA_OBUF_UTC_CONFIG_EN_SHFT) & + LPA_OBUF_UTC_CONFIG_EN_BMSK; + LPA_REG_WRITEL(lpa, val, LPA_OBUF_UTC_CONFIG); +} + +static void lpa_enable_mixing(struct lpa_drv *lpa, bool enable) +{ + u32 val; + + val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL); + val = (enable ? val | LPA_OBUF_CONTROL_LLB_EN_BMSK : + val & ~LPA_OBUF_CONTROL_LLB_EN_BMSK); + LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL); +} + +static void lpa_enable_mixer_saturation(struct lpa_drv *lpa, u32 buf_id, + bool enable) +{ + u32 val; + + val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL); + + switch (buf_id) { + case LPA_BUF_ID_LLB: + val = enable ? (val | LPA_OBUF_CONTROL_LLB_SAT_EN_BMSK) : + (val & ~LPA_OBUF_CONTROL_LLB_SAT_EN_BMSK); + break; + + case LPA_BUF_ID_SB: + val = enable ? (val | LPA_OBUF_CONTROL_SB_SAT_EN_BMSK) : + (val & ~LPA_OBUF_CONTROL_SB_SAT_EN_BMSK); + break; + } + + LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL); +} + +static void lpa_enable_obuf(struct lpa_drv *lpa, u32 buf_id, bool enable) +{ + u32 val; + + val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL); + + switch (buf_id) { + case LPA_BUF_ID_HLB: + val = enable ? (val | LPA_OBUF_CONTROL_HLB_EN_BMSK) : + (val & ~LPA_OBUF_CONTROL_HLB_EN_BMSK); + break; + + case LPA_BUF_ID_LLB: + val = enable ? (val | LPA_OBUF_CONTROL_LLB_EN_BMSK) : + (val & ~LPA_OBUF_CONTROL_LLB_EN_BMSK); + break; + + case LPA_BUF_ID_SB: + val = enable ? (val | LPA_OBUF_CONTROL_SB_EN_BMSK) : + (val & ~LPA_OBUF_CONTROL_SB_EN_BMSK); + break; + } + LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL); +} + +struct lpa_drv *lpa_get(void) +{ + struct lpa_mem_bank_select mem_bank; + struct lpa_drv *ret_lpa = &the_lpa_state.lpa_drv; + + mutex_lock(&the_lpa_state.lpa_lock); + if (the_lpa_state.assigned) { + MM_ERR("LPA HW accupied\n"); + ret_lpa = ERR_PTR(-EBUSY); + goto error; + } + /* perform initialization */ + lpa_reset(ret_lpa); + /* Config adec param */ + /* Initialize LLB/SB min/max address */ + lpa_switch_sb(ret_lpa); + /* Config HLB minx/max address */ + lpa_config_hlb_addr(ret_lpa); + + /* Power up all memory bank for now */ + mem_bank.b0 = 1; + mem_bank.b1 = 1; + mem_bank.b2 = 1; + mem_bank.b3 = 1; + mem_bank.b4 = 1; + mem_bank.b5 = 1; + mem_bank.b6 = 1; + mem_bank.b7 = 1; + mem_bank.b8 = 1; + mem_bank.b9 = 1; + mem_bank.b10 = 1; + mem_bank.llb = 1; + lpa_powerup_mem_bank(ret_lpa, &mem_bank); + + while + (lpa_req_wmark_id(ret_lpa) != LPA_OBUF_WMARK_ASSIGN_DONE); + + lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 0, + ret_lpa->dsp_proc_id); + lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 1, + ret_lpa->dsp_proc_id); + lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 2, + ret_lpa->app_proc_id); + lpa_enable_llb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, 3, + ret_lpa->app_proc_id); + lpa_enable_hlb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, + ret_lpa->dsp_proc_id); + lpa_enable_sb_wmark(ret_lpa, LPA_WMARK_CTL_DISABLED, + ret_lpa->dsp_proc_id); + lpa_enable_utc(ret_lpa, 0, LPA_OBUF_UTC_CONFIG_NO_INTR); + + lpa_enable_mixing(ret_lpa, 1); + lpa_enable_mixer_saturation(ret_lpa, LPA_BUF_ID_LLB, 1); + + lpa_enable_obuf(ret_lpa, LPA_BUF_ID_HLB, 0); + lpa_enable_obuf(ret_lpa, LPA_BUF_ID_LLB, 1); + if (ret_lpa->status & LPA_STATUS_SBUF_EN) { + lpa_enable_mixer_saturation(ret_lpa, LPA_BUF_ID_SB, 1); + lpa_enable_obuf(ret_lpa, LPA_BUF_ID_SB, 1); + } + + lpa_enable_interrupt(ret_lpa, ret_lpa->dsp_proc_id); + mb(); + the_lpa_state.assigned++; +error: + mutex_unlock(&the_lpa_state.lpa_lock); + return ret_lpa; +} +EXPORT_SYMBOL(lpa_get); + +void lpa_put(struct lpa_drv *lpa) +{ + + mutex_lock(&the_lpa_state.lpa_lock); + if (!lpa || &the_lpa_state.lpa_drv != lpa) { + MM_ERR("invalid arg\n"); + goto error; + } + /* Deinitialize */ + the_lpa_state.assigned--; +error: + mutex_unlock(&the_lpa_state.lpa_lock); +} +EXPORT_SYMBOL(lpa_put); + +int lpa_cmd_codec_config(struct lpa_drv *lpa, + struct lpa_codec_config *config_ptr) +{ + u32 sample_rate; + u32 num_channels; + u32 width; + u32 val = 0; + + if (!lpa || !config_ptr) { + MM_ERR("invalid parameters\n"); + return -EINVAL; + } + + switch (config_ptr->num_channels) { + case 8: + num_channels = LPA_NUM_CHAN_7P1; + break; + case 6: + num_channels = LPA_NUM_CHAN_5P1; + break; + case 4: + num_channels = LPA_NUM_CHAN_4_CHANNEL; + break; + case 2: + num_channels = LPA_NUM_CHAN_STEREO; + break; + case 1: + num_channels = LPA_NUM_CHAN_MONO; + break; + default: + MM_ERR("unsupported number of channel\n"); + goto error; + } + val |= (num_channels << LPA_OBUF_CODEC_NUM_CHAN_SHFT) & + LPA_OBUF_CODEC_NUM_CHAN_BMSK; + + switch (config_ptr->sample_rate) { + case 96000: + sample_rate = LPA_SAMPLE_RATE_96KHZ; + break; + case 64000: + sample_rate = LPA_SAMPLE_RATE_64KHZ; + break; + case 48000: + sample_rate = LPA_SAMPLE_RATE_48KHZ; + break; + case 44100: + sample_rate = LPA_SAMPLE_RATE_44P1KHZ; + break; + case 32000: + sample_rate = LPA_SAMPLE_RATE_32KHZ; + break; + case 22050: + sample_rate = LPA_SAMPLE_RATE_22P05KHZ; + break; + case 16000: + sample_rate = LPA_SAMPLE_RATE_16KHZ; + break; + case 11025: + sample_rate = LPA_SAMPLE_RATE_11P025KHZ; + break; + case 8000: + sample_rate = LPA_SAMPLE_RATE_8KHZ; + break; + default: + MM_ERR("unsupported sample rate \n"); + goto error; + } + val |= (sample_rate << LPA_OBUF_CODEC_SAMP_SHFT) & + LPA_OBUF_CODEC_SAMP_BMSK; + switch (config_ptr->sample_width) { + case 32: + width = LPA_BITS_PER_CHAN_32BITS; + break; + case 24: + width = LPA_BITS_PER_CHAN_24BITS; + break; + case 16: + width = LPA_BITS_PER_CHAN_16BITS; + break; + default: + MM_ERR("unsupported sample width \n"); + goto error; + } + val |= (width << LPA_OBUF_CODEC_BITS_PER_CHAN_SHFT) & + LPA_OBUF_CODEC_BITS_PER_CHAN_BMSK; + + val |= LPA_OBUF_CODEC_LOAD_BMSK; + val |= (config_ptr->output_interface << LPA_OBUF_CODEC_INTF_SHFT) & + LPA_OBUF_CODEC_INTF_BMSK; + + LPA_REG_WRITEL(lpa, val, LPA_OBUF_CODEC); + mb(); + + return 0; +error: + return -EINVAL; +} +EXPORT_SYMBOL(lpa_cmd_codec_config); + +static int lpa_check_llb_clear(struct lpa_drv *lpa) +{ + u32 val; + val = LPA_REG_READL(lpa, LPA_OBUF_STATUS); + + return !(val & LPA_OBUF_STATUS_LLB_CLR_BMSK); +} + +static void lpa_clear_llb(struct lpa_drv *lpa) +{ + u32 val; + + val = LPA_REG_READL(lpa, LPA_OBUF_CONTROL); + LPA_REG_WRITEL(lpa, (val | LPA_OBUF_CONTROL_LLB_CLR_CMD_BMSK), + LPA_OBUF_CONTROL); + lpa_enable_obuf(lpa, LPA_BUF_ID_LLB, 0); + + while (!lpa_check_llb_clear(lpa)) + udelay(100); + LPA_REG_WRITEL(lpa, val, LPA_OBUF_CONTROL); +} + +int lpa_cmd_enable_codec(struct lpa_drv *lpa, bool enable) +{ + u32 val; + struct lpa_mem_bank_select mem_bank; + + MM_DBG(" %s\n", (enable ? "enable" : "disable")); + + if (!lpa) + return -EINVAL; + + val = LPA_REG_READL(lpa, LPA_OBUF_CODEC); + + if (enable) { + if (val & LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK) + return -EBUSY; + /* Power up all memory bank for now */ + mem_bank.b0 = 1; + mem_bank.b1 = 1; + mem_bank.b2 = 1; + mem_bank.b3 = 1; + mem_bank.b4 = 1; + mem_bank.b5 = 1; + mem_bank.b6 = 1; + mem_bank.b7 = 1; + mem_bank.b8 = 1; + mem_bank.b9 = 1; + mem_bank.b10 = 1; + mem_bank.llb = 1; + lpa_powerup_mem_bank(lpa, &mem_bank); + + /*clear LLB*/ + lpa_clear_llb(lpa); + + lpa_enable_codec(lpa, 1); + MM_DBG("LPA codec is enabled\n"); + } else { + if (val & LPA_OBUF_CODEC_CODEC_INTF_EN_BMSK) { + lpa_enable_codec(lpa, 0); + MM_DBG("LPA codec is disabled\n"); + } else + MM_ERR("LPA codec is already disable\n"); + } + mb(); + return 0; +} +EXPORT_SYMBOL(lpa_cmd_enable_codec); + +static int lpa_probe(struct platform_device *pdev) +{ + int rc = 0; + struct resource *mem_src; + struct msm_lpa_platform_data *pdata; + + MM_INFO("lpa probe\n"); + + if (!pdev || !pdev->dev.platform_data) { + MM_ERR("no plaform data\n"); + rc = -ENODEV; + goto error; + } + + mem_src = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpa"); + if (!mem_src) { + MM_ERR("LPA base address undefined\n"); + rc = -ENODEV; + goto error; + } + + pdata = pdev->dev.platform_data; + the_lpa_state.lpa_drv.baseaddr = ioremap(mem_src->start, + (mem_src->end - mem_src->start) + 1); + if (!the_lpa_state.lpa_drv.baseaddr) { + rc = -ENOMEM; + goto error; + } + + the_lpa_state.lpa_drv.obuf_hlb_size = pdata->obuf_hlb_size; + the_lpa_state.lpa_drv.dsp_proc_id = pdata->dsp_proc_id; + the_lpa_state.lpa_drv.app_proc_id = pdata->app_proc_id; + the_lpa_state.lpa_drv.nosb_config = pdata->nosb_config; + the_lpa_state.lpa_drv.sb_config = pdata->sb_config; + /* default to enable summing buffer */ + the_lpa_state.lpa_drv.status = LPA_STATUS_SBUF_EN; + +error: + return rc; + +} + +static int lpa_remove(struct platform_device *pdev) +{ + iounmap(the_lpa_state.lpa_drv.baseaddr); + return 0; +} + +static struct platform_driver lpa_driver = { + .probe = lpa_probe, + .remove = lpa_remove, + .driver = { + .name = "lpa", + .owner = THIS_MODULE, + }, +}; + +static int __init lpa_init(void) +{ + the_lpa_state.assigned = 0; + mutex_init(&the_lpa_state.lpa_lock); + return platform_driver_register(&lpa_driver); +} + +static void __exit lpa_exit(void) +{ + platform_driver_unregister(&lpa_driver); +} + +module_init(lpa_init); +module_exit(lpa_exit); + +MODULE_DESCRIPTION("MSM LPA driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/mi2s.c b/arch/arm/mach-msm/qdsp5v2/mi2s.c new file mode 100644 index 00000000000..e38f164eb12 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/mi2s.c @@ -0,0 +1,885 @@ +/* Copyright (c) 2009,2011 Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define DEBUG +#ifdef DEBUG +#define dprintk(format, arg...) \ +printk(KERN_DEBUG format, ## arg) +#else +#define dprintk(format, arg...) do {} while (0) +#endif + +/*---------------------------------------------------------------------------- + * Preprocessor Definitions and Constants + * -------------------------------------------------------------------------*/ + +/* Device Types */ +#define HDMI 0 +#define CODEC_RX 1 +#define CODEC_TX 2 + +/* Static offset for now. If different target have different + * offset, update to platform data model + */ +#define MI2S_RESET_OFFSET 0x0 +#define MI2S_MODE_OFFSET 0x4 +#define MI2S_TX_MODE_OFFSET 0x8 +#define MI2S_RX_MODE_OFFSET 0xc + +#define MI2S_SD_N_EN_MASK 0xF0 +#define MI2S_TX_RX_N_MASK 0x0F + +#define MI2S_RESET__MI2S_RESET__RESET 0x1 +#define MI2S_RESET__MI2S_RESET__ACTIVE 0x0 +#define MI2S_MODE__MI2S_MASTER__MASTER 0x1 +#define MI2S_MODE__MI2S_MASTER__SLAVE 0x0 +#define MI2S_MODE__MI2S_TX_RX_WORD_TYPE__16_BIT 0x1 +#define MI2S_MODE__MI2S_TX_RX_WORD_TYPE__24_BIT 0x2 +#define MI2S_MODE__MI2S_TX_RX_WORD_TYPE__32_BIT 0x3 +#define MI2S_TX_MODE__MI2S_TX_CODEC_16_MONO_MODE__RAW 0x0 +#define MI2S_TX_MODE__MI2S_TX_CODEC_16_MONO_MODE__PACKED 0x1 +#define MI2S_TX_MODE__MI2S_TX_STEREO_MODE__MONO_SAMPLE 0x0 +#define MI2S_TX_MODE__MI2S_TX_STEREO_MODE__STEREO_SAMPLE 0x1 +#define MI2S_TX_MODE__MI2S_TX_CH_TYPE__2_CHANNEL 0x0 +#define MI2S_TX_MODE__MI2S_TX_CH_TYPE__4_CHANNEL 0x1 +#define MI2S_TX_MODE__MI2S_TX_CH_TYPE__6_CHANNEL 0x2 +#define MI2S_TX_MODE__MI2S_TX_CH_TYPE__8_CHANNEL 0x3 +#define MI2S_TX_MODE__MI2S_TX_DMA_ACK_SYNCH_EN__SYNC_ENABLE 0x1 +#define MI2S_RX_MODE__MI2S_RX_CODEC_16_MONO_MODE__RAW 0x0 +#define MI2S_RX_MODE__MI2S_RX_CODEC_16_MONO_MODE__PACKED 0x1 +#define MI2S_RX_MODE__MI2S_RX_STEREO_MODE__MONO_SAMPLE 0x0 +#define MI2S_RX_MODE__MI2S_RX_STEREO_MODE__STEREO_SAMPLE 0x1 +#define MI2S_RX_MODE__MI2S_RX_CH_TYPE__2_CH 0x0 +#define MI2S_RX_MODE__MI2S_RX_DMA_ACK_SYNCH_EN__SYNC_ENABLE 0x1 + +#define HWIO_AUDIO1_MI2S_MODE_MI2S_MASTER_BMSK 0x1000 +#define HWIO_AUDIO1_MI2S_MODE_MI2S_MASTER_SHFT 0xC +#define HWIO_AUDIO1_MI2S_MODE_MI2S_TX_RX_WORD_TYPE_BMSK 0x300 +#define HWIO_AUDIO1_MI2S_MODE_MI2S_TX_RX_WORD_TYPE_SHFT 0x8 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_BMSK 0x4 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_SHFT 0x2 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_P_MONO_BMSK 0x2 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_P_MONO_SHFT 0x1 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_BMSK 0x18 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_SHFT 0x3 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_4_0_CH_MAP_BMSK 0x80 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_4_0_CH_MAP_SHFT 0x7 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_2_0_CH_MAP_BMSK 0x60 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_2_0_CH_MAP_SHFT 0x5 +#define HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_DMA_ACK_SYNCH_EN_BMSK 0x1 +#define HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_I2S_LINE_BMSK 0x60 +#define HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_I2S_LINE_SHFT 0x5 +#define HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_BMSK 0x4 +#define HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_SHFT 0x2 +#define HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CODEC_P_MONO_BMSK 0x2 +#define HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CODEC_P_MONO_SHFT 0x1 +#define HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CH_TYPE_BMSK 0x18 +#define HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CH_TYPE_SHFT 0x3 +#define HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_DMA_ACK_SYNCH_EN_BMSK 0x1 + +/* Max number of channels */ +#define MAX_NUM_CHANNELS_OUT 8 +#define MAX_NUM_CHANNELS_IN 2 + +/* Num of SD Lines */ +#define MAX_SD_LINES 4 + +#define MI2S_SD_0_EN_MAP 0x10 +#define MI2S_SD_1_EN_MAP 0x20 +#define MI2S_SD_2_EN_MAP 0x40 +#define MI2S_SD_3_EN_MAP 0x80 +#define MI2S_SD_0_TX_MAP 0x01 +#define MI2S_SD_1_TX_MAP 0x02 +#define MI2S_SD_2_TX_MAP 0x04 +#define MI2S_SD_3_TX_MAP 0x08 + +struct mi2s_state { + void __iomem *mi2s_hdmi_base; + void __iomem *mi2s_rx_base; + void __iomem *mi2s_tx_base; + struct mutex mutex_lock; + +}; + +static struct mi2s_state the_mi2s_state; + +static void __iomem *get_base_addr(struct mi2s_state *mi2s, uint8_t dev_id) +{ + switch (dev_id) { + case HDMI: + return mi2s->mi2s_hdmi_base; + case CODEC_RX: + return mi2s->mi2s_rx_base; + case CODEC_TX: + return mi2s->mi2s_tx_base; + default: + break; + } + return ERR_PTR(-ENODEV); +} + +static void mi2s_reset(struct mi2s_state *mi2s, uint8_t dev_id) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + if (!IS_ERR(baddr)) + writel(MI2S_RESET__MI2S_RESET__RESET, + baddr + MI2S_RESET_OFFSET); +} + +static void mi2s_release(struct mi2s_state *mi2s, uint8_t dev_id) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + if (!IS_ERR(baddr)) + writel(MI2S_RESET__MI2S_RESET__ACTIVE, + baddr + MI2S_RESET_OFFSET); +} + +static void mi2s_master(struct mi2s_state *mi2s, uint8_t dev_id, bool master) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_MODE_OFFSET); + if (master) { + writel( + ((val & ~HWIO_AUDIO1_MI2S_MODE_MI2S_MASTER_BMSK) | + (MI2S_MODE__MI2S_MASTER__MASTER << + HWIO_AUDIO1_MI2S_MODE_MI2S_MASTER_SHFT)), + baddr + MI2S_MODE_OFFSET); + } else { + writel( + ((val & ~HWIO_AUDIO1_MI2S_MODE_MI2S_MASTER_BMSK) | + (MI2S_MODE__MI2S_MASTER__SLAVE << + HWIO_AUDIO1_MI2S_MODE_MI2S_MASTER_SHFT)), + baddr + MI2S_MODE_OFFSET); + } + } +} + +static void mi2s_set_word_type(struct mi2s_state *mi2s, uint8_t dev_id, + uint8_t size) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_MODE_OFFSET); + switch (size) { + case WT_16_BIT: + writel( + ((val & + ~HWIO_AUDIO1_MI2S_MODE_MI2S_TX_RX_WORD_TYPE_BMSK) | + (MI2S_MODE__MI2S_TX_RX_WORD_TYPE__16_BIT << + HWIO_AUDIO1_MI2S_MODE_MI2S_TX_RX_WORD_TYPE_SHFT)), + baddr + MI2S_MODE_OFFSET); + break; + case WT_24_BIT: + writel( + ((val & + ~HWIO_AUDIO1_MI2S_MODE_MI2S_TX_RX_WORD_TYPE_BMSK) | + (MI2S_MODE__MI2S_TX_RX_WORD_TYPE__24_BIT << + HWIO_AUDIO1_MI2S_MODE_MI2S_TX_RX_WORD_TYPE_SHFT)), + baddr + MI2S_MODE_OFFSET); + break; + case WT_32_BIT: + writel( + ((val & + ~HWIO_AUDIO1_MI2S_MODE_MI2S_TX_RX_WORD_TYPE_BMSK) | + (MI2S_MODE__MI2S_TX_RX_WORD_TYPE__32_BIT << + HWIO_AUDIO1_MI2S_MODE_MI2S_TX_RX_WORD_TYPE_SHFT)), + baddr + MI2S_MODE_OFFSET); + break; + default: + break; + } + } +} + +static void mi2s_set_sd(struct mi2s_state *mi2s, uint8_t dev_id, uint8_t sd_map) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_MODE_OFFSET) & + ~(MI2S_SD_N_EN_MASK | MI2S_TX_RX_N_MASK); + writel(val | sd_map, baddr + MI2S_MODE_OFFSET); + } +} + +static void mi2s_set_output_num_channels(struct mi2s_state *mi2s, + uint8_t dev_id, uint8_t channels) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_TX_MODE_OFFSET); + if (channels == MI2S_CHAN_MONO_RAW) { + val = (val & + ~(HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_P_MONO_BMSK)) | + ((MI2S_TX_MODE__MI2S_TX_STEREO_MODE__MONO_SAMPLE << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_SHFT) | + (MI2S_TX_MODE__MI2S_TX_CODEC_16_MONO_MODE__RAW << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_P_MONO_SHFT)); + } else if (channels == MI2S_CHAN_MONO_PACKED) { + val = (val & + ~(HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_P_MONO_BMSK)) | + ((MI2S_TX_MODE__MI2S_TX_STEREO_MODE__MONO_SAMPLE << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_SHFT) | + (MI2S_TX_MODE__MI2S_TX_CODEC_16_MONO_MODE__PACKED << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_P_MONO_SHFT)); + } else if (channels == MI2S_CHAN_STEREO) { + val = (val & + ~(HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_BMSK)) | + ((MI2S_TX_MODE__MI2S_TX_STEREO_MODE__STEREO_SAMPLE << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_SHFT) | + (MI2S_TX_MODE__MI2S_TX_CH_TYPE__2_CHANNEL << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_SHFT)); + } else if (channels == MI2S_CHAN_4CHANNELS) { + val = (val & + ~(HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_BMSK)) | + ((MI2S_TX_MODE__MI2S_TX_STEREO_MODE__STEREO_SAMPLE << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_SHFT) | + (MI2S_TX_MODE__MI2S_TX_CH_TYPE__4_CHANNEL << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_SHFT)); + } else if (channels == MI2S_CHAN_6CHANNELS) { + val = (val & + ~(HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_BMSK)) | + ((MI2S_TX_MODE__MI2S_TX_STEREO_MODE__STEREO_SAMPLE << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_SHFT) | + (MI2S_TX_MODE__MI2S_TX_CH_TYPE__6_CHANNEL << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_SHFT)); + } else if (channels == MI2S_CHAN_8CHANNELS) { + val = (val & + ~(HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_BMSK)) | + ((MI2S_TX_MODE__MI2S_TX_STEREO_MODE__STEREO_SAMPLE << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_STEREO_MODE_SHFT) | + (MI2S_TX_MODE__MI2S_TX_CH_TYPE__8_CHANNEL << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_CH_TYPE_SHFT)); + } + writel(val, baddr + MI2S_TX_MODE_OFFSET); + } +} + +static void mi2s_set_output_4ch_map(struct mi2s_state *mi2s, uint8_t dev_id, + bool high_low) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_TX_MODE_OFFSET); + val = (val & ~HWIO_AUDIO1_MI2S_TX_MODE_MI2S_4_0_CH_MAP_BMSK) | + (high_low << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_4_0_CH_MAP_SHFT); + writel(val, baddr + MI2S_TX_MODE_OFFSET); + } +} + +static void mi2s_set_output_2ch_map(struct mi2s_state *mi2s, uint8_t dev_id, + uint8_t sd_line) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_TX_MODE_OFFSET); + if (sd_line < 4) { + val = (val & + ~HWIO_AUDIO1_MI2S_TX_MODE_MI2S_2_0_CH_MAP_BMSK) | + (sd_line << + HWIO_AUDIO1_MI2S_TX_MODE_MI2S_2_0_CH_MAP_SHFT); + writel(val, baddr + MI2S_TX_MODE_OFFSET); + } + } +} + +static void mi2s_set_output_clk_synch(struct mi2s_state *mi2s, uint8_t dev_id) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_TX_MODE_OFFSET); + writel(((val & + ~HWIO_AUDIO1_MI2S_TX_MODE_MI2S_TX_DMA_ACK_SYNCH_EN_BMSK) | + MI2S_TX_MODE__MI2S_TX_DMA_ACK_SYNCH_EN__SYNC_ENABLE), + baddr + MI2S_TX_MODE_OFFSET); + } +} + +static void mi2s_set_input_sd_line(struct mi2s_state *mi2s, uint8_t dev_id, + uint8_t sd_line) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_RX_MODE_OFFSET); + if (sd_line < 4) { + val = (val & + ~HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_I2S_LINE_BMSK) | + (sd_line << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_I2S_LINE_SHFT); + writel(val, baddr + MI2S_RX_MODE_OFFSET); + } + } +} + +static void mi2s_set_input_num_channels(struct mi2s_state *mi2s, uint8_t dev_id, + uint8_t channels) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_RX_MODE_OFFSET); + if (channels == MI2S_CHAN_MONO_RAW) { + val = (val & + ~(HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CODEC_P_MONO_BMSK)) | + ((MI2S_RX_MODE__MI2S_RX_STEREO_MODE__MONO_SAMPLE << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_SHFT) | + (MI2S_RX_MODE__MI2S_RX_CODEC_16_MONO_MODE__RAW << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CODEC_P_MONO_SHFT)); + } else if (channels == MI2S_CHAN_MONO_PACKED) { + val = (val & + ~(HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CODEC_P_MONO_BMSK)) | + ((MI2S_RX_MODE__MI2S_RX_STEREO_MODE__MONO_SAMPLE << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_SHFT) | + (MI2S_RX_MODE__MI2S_RX_CODEC_16_MONO_MODE__PACKED << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CODEC_P_MONO_SHFT)); + } else if (channels == MI2S_CHAN_STEREO) { + + if (dev_id == HDMI) + val = (val & + ~(HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CH_TYPE_BMSK)) | + ((MI2S_RX_MODE__MI2S_RX_STEREO_MODE__STEREO_SAMPLE << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_SHFT) | + (MI2S_RX_MODE__MI2S_RX_CH_TYPE__2_CH << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CH_TYPE_SHFT)); + + else + val = (val & + ~(HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_BMSK | + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CH_TYPE_BMSK)) | + ((MI2S_RX_MODE__MI2S_RX_STEREO_MODE__STEREO_SAMPLE << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_STEREO_MODE_SHFT) | + (MI2S_RX_MODE__MI2S_RX_CODEC_16_MONO_MODE__PACKED << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CODEC_P_MONO_SHFT) | + (MI2S_RX_MODE__MI2S_RX_CH_TYPE__2_CH << + HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_CH_TYPE_SHFT)); + + + } + writel(val, baddr + MI2S_RX_MODE_OFFSET); + } +} + +static void mi2s_set_input_clk_synch(struct mi2s_state *mi2s, uint8_t dev_id) +{ + void __iomem *baddr = get_base_addr(mi2s, dev_id); + uint32_t val; + + if (!IS_ERR(baddr)) { + val = readl(baddr + MI2S_RX_MODE_OFFSET); + writel( + ((val & + ~HWIO_AUDIO1_MI2S_RX_MODE_MI2S_RX_DMA_ACK_SYNCH_EN_BMSK) | + MI2S_RX_MODE__MI2S_RX_DMA_ACK_SYNCH_EN__SYNC_ENABLE), + baddr + MI2S_RX_MODE_OFFSET); + } +} + + +static u8 num_of_bits_set(u8 sd_line_mask) +{ + u8 num_bits_set = 0; + + while (sd_line_mask) { + + if (sd_line_mask & 1) + num_bits_set++; + sd_line_mask = sd_line_mask >> 1; + } + return num_bits_set; +} + + +bool mi2s_set_hdmi_output_path(uint8_t channels, uint8_t size, + uint8_t sd_line_mask) +{ + bool ret_val = MI2S_TRUE; + struct mi2s_state *mi2s = &the_mi2s_state; + u8 sd_line, num_of_sd_lines = 0; + void __iomem *baddr; + uint32_t val; + + pr_debug("%s: channels = %u size = %u sd_line_mask = 0x%x\n", __func__, + channels, size, sd_line_mask); + + if ((channels == 0) || (channels > MAX_NUM_CHANNELS_OUT) || + ((channels != 1) && (channels % 2 != 0))) { + + pr_err("%s: invalid number of channels. channels = %u\n", + __func__, channels); + return MI2S_FALSE; + } + + sd_line_mask &= MI2S_SD_LINE_MASK; + + if (!sd_line_mask) { + pr_err("%s: Did not set any data lines to use " + " sd_line_mask =0x%x\n", __func__, sd_line_mask); + return MI2S_FALSE; + } + + mutex_lock(&mi2s->mutex_lock); + /* Put device in reset */ + mi2s_reset(mi2s, HDMI); + + mi2s_master(mi2s, HDMI, 1); + + /* Set word type */ + if (size <= WT_MAX) + mi2s_set_word_type(mi2s, HDMI, size); + else + ret_val = MI2S_FALSE; + + /* Enable clock crossing synchronization of RD DMA ACK */ + mi2s_set_output_clk_synch(mi2s, HDMI); + + mi2s_set_output_num_channels(mi2s, HDMI, channels); + + num_of_sd_lines = num_of_bits_set(sd_line_mask); + /*Second argument to find_first_bit should be maximum number of + bit*/ + + sd_line = find_first_bit((unsigned long *)&sd_line_mask, + sizeof(sd_line_mask) * 8); + pr_debug("sd_line = %d\n", sd_line); + + if (channels == 1) { + + if (num_of_sd_lines != 1) { + pr_err("%s: for one channel only one SD lines is" + " needed. num_of_sd_lines = %u\n", + __func__, num_of_sd_lines); + + ret_val = MI2S_FALSE; + goto error; + } + + if (sd_line != 0) { + pr_err("%s: for one channel tx, need to use SD_0 " + "sd_line = %u\n", __func__, sd_line); + + ret_val = MI2S_FALSE; + goto error; + } + + /* Enable SD line 0 for Tx (only option for + * mono audio) + */ + mi2s_set_sd(mi2s, HDMI, MI2S_SD_0_EN_MAP | MI2S_SD_0_TX_MAP); + + } else if (channels == 2) { + + if (num_of_sd_lines != 1) { + pr_err("%s: for two channel only one SD lines is" + " needed. num_of_sd_lines = %u\n", + __func__, num_of_sd_lines); + ret_val = MI2S_FALSE; + goto error; + } + + /* Enable single SD line for Tx */ + mi2s_set_sd(mi2s, HDMI, (MI2S_SD_0_EN_MAP << sd_line) | + (MI2S_SD_0_TX_MAP << sd_line)); + + /* Set 2-channel mapping */ + mi2s_set_output_2ch_map(mi2s, HDMI, sd_line); + + } else if (channels == 4) { + + if (num_of_sd_lines != 2) { + pr_err("%s: for 4 channels two SD lines are" + " needed. num_of_sd_lines = %u\\n", + __func__, num_of_sd_lines); + ret_val = MI2S_FALSE; + goto error; + } + + if ((sd_line_mask && MI2S_SD_0) && + (sd_line_mask && MI2S_SD_1)) { + + mi2s_set_sd(mi2s, HDMI, (MI2S_SD_0_EN_MAP | + MI2S_SD_1_EN_MAP) | (MI2S_SD_0_TX_MAP | + MI2S_SD_1_TX_MAP)); + mi2s_set_output_4ch_map(mi2s, HDMI, MI2S_FALSE); + + } else if ((sd_line_mask && MI2S_SD_2) && + (sd_line_mask && MI2S_SD_3)) { + + mi2s_set_sd(mi2s, HDMI, (MI2S_SD_2_EN_MAP | + MI2S_SD_3_EN_MAP) | (MI2S_SD_2_TX_MAP | + MI2S_SD_3_TX_MAP)); + + mi2s_set_output_4ch_map(mi2s, HDMI, MI2S_TRUE); + } else { + + pr_err("%s: for 4 channels invalid SD lines usage" + " sd_line_mask = 0x%x\n", + __func__, sd_line_mask); + ret_val = MI2S_FALSE; + goto error; + } + } else if (channels == 6) { + + if (num_of_sd_lines != 3) { + pr_err("%s: for 6 channels three SD lines are" + " needed. num_of_sd_lines = %u\n", + __func__, num_of_sd_lines); + ret_val = MI2S_FALSE; + goto error; + } + + if ((sd_line_mask && MI2S_SD_0) && + (sd_line_mask && MI2S_SD_1) && + (sd_line_mask && MI2S_SD_2)) { + + mi2s_set_sd(mi2s, HDMI, (MI2S_SD_0_EN_MAP | + MI2S_SD_1_EN_MAP | MI2S_SD_2_EN_MAP) | + (MI2S_SD_0_TX_MAP | MI2S_SD_1_TX_MAP | + MI2S_SD_2_TX_MAP)); + + } else if ((sd_line_mask && MI2S_SD_1) && + (sd_line_mask && MI2S_SD_2) && + (sd_line_mask && MI2S_SD_3)) { + + mi2s_set_sd(mi2s, HDMI, (MI2S_SD_1_EN_MAP | + MI2S_SD_2_EN_MAP | MI2S_SD_3_EN_MAP) | + (MI2S_SD_1_TX_MAP | MI2S_SD_2_TX_MAP | + MI2S_SD_3_TX_MAP)); + + } else { + + pr_err("%s: for 6 channels invalid SD lines usage" + " sd_line_mask = 0x%x\n", + __func__, sd_line_mask); + ret_val = MI2S_FALSE; + goto error; + } + } else if (channels == 8) { + + if (num_of_sd_lines != 4) { + pr_err("%s: for 8 channels four SD lines are" + " needed. num_of_sd_lines = %u\n", + __func__, num_of_sd_lines); + ret_val = MI2S_FALSE; + goto error; + } + + mi2s_set_sd(mi2s, HDMI, (MI2S_SD_0_EN_MAP | + MI2S_SD_1_EN_MAP | MI2S_SD_2_EN_MAP | + MI2S_SD_3_EN_MAP) | (MI2S_SD_0_TX_MAP | + MI2S_SD_1_TX_MAP | MI2S_SD_2_TX_MAP | + MI2S_SD_3_TX_MAP)); + } else { + pr_err("%s: invalid number channels = %u\n", + __func__, channels); + ret_val = MI2S_FALSE; + goto error; + } + + baddr = get_base_addr(mi2s, HDMI); + + val = readl(baddr + MI2S_MODE_OFFSET); + pr_debug("%s(): MI2S_MODE = 0x%x\n", __func__, val); + + val = readl(baddr + MI2S_TX_MODE_OFFSET); + pr_debug("%s(): MI2S_TX_MODE = 0x%x\n", __func__, val); + + +error: + /* Release device from reset */ + mi2s_release(mi2s, HDMI); + + mutex_unlock(&mi2s->mutex_lock); + mb(); + return ret_val; +} +EXPORT_SYMBOL(mi2s_set_hdmi_output_path); + +bool mi2s_set_hdmi_input_path(uint8_t channels, uint8_t size, + uint8_t sd_line_mask) +{ + bool ret_val = MI2S_TRUE; + struct mi2s_state *mi2s = &the_mi2s_state; + u8 sd_line, num_of_sd_lines = 0; + void __iomem *baddr; + uint32_t val; + + pr_debug("%s: channels = %u size = %u sd_line_mask = 0x%x\n", __func__, + channels, size, sd_line_mask); + + if ((channels != 1) && (channels != MAX_NUM_CHANNELS_IN)) { + + pr_err("%s: invalid number of channels. channels = %u\n", + __func__, channels); + return MI2S_FALSE; + } + + if (size > WT_MAX) { + + pr_err("%s: mi2s word size can not be greater than 32 bits\n", + __func__); + return MI2S_FALSE; + } + + sd_line_mask &= MI2S_SD_LINE_MASK; + + if (!sd_line_mask) { + pr_err("%s: Did not set any data lines to use " + " sd_line_mask =0x%x\n", __func__, sd_line_mask); + return MI2S_FALSE; + } + + num_of_sd_lines = num_of_bits_set(sd_line_mask); + + if (num_of_sd_lines != 1) { + pr_err("%s: for two channel input only one SD lines is" + " needed. num_of_sd_lines = %u sd_line_mask = 0x%x\n", + __func__, num_of_sd_lines, sd_line_mask); + return MI2S_FALSE; + } + + /*Second argument to find_first_bit should be maximum number of + bits interested*/ + sd_line = find_first_bit((unsigned long *)&sd_line_mask, + sizeof(sd_line_mask) * 8); + pr_debug("sd_line = %d\n", sd_line); + + /* Ensure sd_line parameter is valid (0-max) */ + if (sd_line > MAX_SD_LINES) { + pr_err("%s: Line number can not be greater than = %u\n", + __func__, MAX_SD_LINES); + return MI2S_FALSE; + } + + mutex_lock(&mi2s->mutex_lock); + /* Put device in reset */ + mi2s_reset(mi2s, HDMI); + + mi2s_master(mi2s, HDMI, 1); + + /* Set word type */ + mi2s_set_word_type(mi2s, HDMI, size); + + /* Enable clock crossing synchronization of WR DMA ACK */ + mi2s_set_input_clk_synch(mi2s, HDMI); + + /* Ensure channels parameter is valid (non-zero, less than max, + * and even or mono) + */ + mi2s_set_input_num_channels(mi2s, HDMI, channels); + + mi2s_set_input_sd_line(mi2s, HDMI, sd_line); + + mi2s_set_sd(mi2s, HDMI, (MI2S_SD_0_EN_MAP << sd_line)); + + baddr = get_base_addr(mi2s, HDMI); + + val = readl(baddr + MI2S_MODE_OFFSET); + pr_debug("%s(): MI2S_MODE = 0x%x\n", __func__, val); + + val = readl(baddr + MI2S_RX_MODE_OFFSET); + pr_debug("%s(): MI2S_RX_MODE = 0x%x\n", __func__, val); + + /* Release device from reset */ + mi2s_release(mi2s, HDMI); + + mutex_unlock(&mi2s->mutex_lock); + mb(); + return ret_val; +} +EXPORT_SYMBOL(mi2s_set_hdmi_input_path); + +bool mi2s_set_codec_output_path(uint8_t channels, uint8_t size) +{ + bool ret_val = MI2S_TRUE; + struct mi2s_state *mi2s = &the_mi2s_state; + + mutex_lock(&mi2s->mutex_lock); + /* Put device in reset */ + mi2s_reset(mi2s, CODEC_TX); + + mi2s_master(mi2s, CODEC_TX, 1); + + /* Enable clock crossing synchronization of RD DMA ACK */ + mi2s_set_output_clk_synch(mi2s, CODEC_TX); + + /* Set word type */ + if (size <= WT_MAX) + mi2s_set_word_type(mi2s, CODEC_TX, size); + else + ret_val = MI2S_FALSE; + + mi2s_set_output_num_channels(mi2s, CODEC_TX, channels); + + /* Enable SD line */ + mi2s_set_sd(mi2s, CODEC_TX, MI2S_SD_0_EN_MAP | MI2S_SD_0_TX_MAP); + + /* Release device from reset */ + mi2s_release(mi2s, CODEC_TX); + + mutex_unlock(&mi2s->mutex_lock); + mb(); + return ret_val; +} +EXPORT_SYMBOL(mi2s_set_codec_output_path); + +bool mi2s_set_codec_input_path(uint8_t channels, uint8_t size) +{ + bool ret_val = MI2S_TRUE; + struct mi2s_state *mi2s = &the_mi2s_state; + + mutex_lock(&the_mi2s_state.mutex_lock); + /* Put device in reset */ + mi2s_reset(mi2s, CODEC_RX); + + mi2s_master(mi2s, CODEC_RX, 1); + + /* Enable clock crossing synchronization of WR DMA ACK */ + mi2s_set_input_clk_synch(mi2s, CODEC_RX); + + /* Set word type */ + if (size <= WT_MAX) + mi2s_set_word_type(mi2s, CODEC_RX, size); + else + ret_val = MI2S_FALSE; + + mi2s_set_input_num_channels(mi2s, CODEC_RX, channels); + + /* Enable SD line */ + mi2s_set_sd(mi2s, CODEC_RX, MI2S_SD_0_EN_MAP); + + /* Release device from reset */ + mi2s_release(mi2s, CODEC_RX); + + mutex_unlock(&mi2s->mutex_lock); + mb(); + return ret_val; +} +EXPORT_SYMBOL(mi2s_set_codec_input_path); + + +static int mi2s_probe(struct platform_device *pdev) +{ + int rc = 0; + struct resource *mem_src; + + mem_src = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi"); + if (!mem_src) { + rc = -ENODEV; + goto error_hdmi; + } + the_mi2s_state.mi2s_hdmi_base = ioremap(mem_src->start, + (mem_src->end - mem_src->start) + 1); + if (!the_mi2s_state.mi2s_hdmi_base) { + rc = -ENOMEM; + goto error_hdmi; + } + mem_src = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "codec_rx"); + if (!mem_src) { + rc = -ENODEV; + goto error_codec_rx; + } + the_mi2s_state.mi2s_rx_base = ioremap(mem_src->start, + (mem_src->end - mem_src->start) + 1); + if (!the_mi2s_state.mi2s_rx_base) { + rc = -ENOMEM; + goto error_codec_rx; + } + mem_src = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "codec_tx"); + if (!mem_src) { + rc = -ENODEV; + goto error_codec_tx; + } + the_mi2s_state.mi2s_tx_base = ioremap(mem_src->start, + (mem_src->end - mem_src->start) + 1); + if (!the_mi2s_state.mi2s_tx_base) { + rc = -ENOMEM; + goto error_codec_tx; + } + mutex_init(&the_mi2s_state.mutex_lock); + + return rc; + +error_codec_tx: + iounmap(the_mi2s_state.mi2s_rx_base); +error_codec_rx: + iounmap(the_mi2s_state.mi2s_hdmi_base); +error_hdmi: + return rc; + +} + +static int mi2s_remove(struct platform_device *pdev) +{ + iounmap(the_mi2s_state.mi2s_tx_base); + iounmap(the_mi2s_state.mi2s_rx_base); + iounmap(the_mi2s_state.mi2s_hdmi_base); + return 0; +} + +static struct platform_driver mi2s_driver = { + .probe = mi2s_probe, + .remove = mi2s_remove, + .driver = { + .name = "mi2s", + .owner = THIS_MODULE, + }, +}; + +static int __init mi2s_init(void) +{ + return platform_driver_register(&mi2s_driver); +} + +static void __exit mi2s_exit(void) +{ + platform_driver_unregister(&mi2s_driver); +} + +module_init(mi2s_init); +module_exit(mi2s_exit); + +MODULE_DESCRIPTION("MSM MI2S driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/mp3_funcs.c b/arch/arm/mach-msm/qdsp5v2/mp3_funcs.c new file mode 100644 index 00000000000..0b20be0b279 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/mp3_funcs.c @@ -0,0 +1,45 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +long mp3_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + MM_DBG("mp3_ioctl() cmd = %d\b", cmd); + + return -EINVAL; +} + +void audpp_cmd_cfg_mp3_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_mp3 cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_MP3_LEN; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + + audpp_send_queue2(&cmd, sizeof(cmd)); +} diff --git a/arch/arm/mach-msm/qdsp5v2/pcm_funcs.c b/arch/arm/mach-msm/qdsp5v2/pcm_funcs.c new file mode 100644 index 00000000000..d7935a727c9 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/pcm_funcs.c @@ -0,0 +1,47 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +long pcm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + MM_DBG("pcm_ioctl() cmd = %d\n", cmd); + + return -EINVAL; +} + +void audpp_cmd_cfg_pcm_params(struct audio *audio) +{ + struct audpp_cmd_cfg_adec_params_wav cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPP_CMD_CFG_ADEC_PARAMS; + cmd.common.length = AUDPP_CMD_CFG_ADEC_PARAMS_WAV_LEN >> 1; + cmd.common.dec_id = audio->dec_id; + cmd.common.input_sampling_frequency = audio->out_sample_rate; + cmd.stereo_cfg = audio->out_channel_mode; + cmd.pcm_width = audio->out_bits; + cmd.sign = 0; + audpp_send_queue2(&cmd, sizeof(cmd)); +} diff --git a/arch/arm/mach-msm/qdsp5v2/snddev_data_marimba.c b/arch/arm/mach-msm/qdsp5v2/snddev_data_marimba.c new file mode 100644 index 00000000000..b15d4c461b7 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/snddev_data_marimba.c @@ -0,0 +1,1537 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* define the value for BT_SCO */ +#define BT_SCO_PCM_CTL_VAL (PCM_CTL__RPCM_WIDTH__LINEAR_V |\ + PCM_CTL__TPCM_WIDTH__LINEAR_V) +#define BT_SCO_DATA_FORMAT_PADDING (DATA_FORMAT_PADDING_INFO__RPCM_FORMAT_V |\ + DATA_FORMAT_PADDING_INFO__TPCM_FORMAT_V) +#define BT_SCO_AUX_CODEC_INTF AUX_CODEC_INTF_CTL__PCMINTF_DATA_EN_V + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_hsed_config; +static void snddev_hsed_config_modify_setting(int type); +static void snddev_hsed_config_restore_setting(void); +#endif + +static struct adie_codec_action_unit iearpiece_48KHz_osr256_actions[] = + HANDSET_RX_48000_OSR_256; + +static struct adie_codec_hwsetting_entry iearpiece_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = iearpiece_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(iearpiece_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile iearpiece_profile = { + .path_type = ADIE_CODEC_RX, + .settings = iearpiece_settings, + .setting_sz = ARRAY_SIZE(iearpiece_settings), +}; + +static struct snddev_icodec_data snddev_iearpiece_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "handset_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_SPKR, + .profile = &iearpiece_profile, + .channel_mode = 1, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, + .property = SIDE_TONE_MASK, + .max_voice_rx_vol[VOC_NB_INDEX] = -200, + .min_voice_rx_vol[VOC_NB_INDEX] = -1700, + .max_voice_rx_vol[VOC_WB_INDEX] = -200, + .min_voice_rx_vol[VOC_WB_INDEX] = -1700 +}; + +static struct platform_device msm_iearpiece_device = { + .name = "snddev_icodec", + .id = 0, + .dev = { .platform_data = &snddev_iearpiece_data }, +}; + +static struct adie_codec_action_unit imic_8KHz_osr256_actions[] = + HANDSET_TX_8000_OSR_256; + +static struct adie_codec_action_unit imic_16KHz_osr256_actions[] = + HANDSET_TX_16000_OSR_256; + +static struct adie_codec_action_unit imic_48KHz_osr256_actions[] = + HANDSET_TX_48000_OSR_256; + +static struct adie_codec_hwsetting_entry imic_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = imic_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(imic_8KHz_osr256_actions), + }, + { + .freq_plan = 16000, + .osr = 256, + .actions = imic_16KHz_osr256_actions, + .action_sz = ARRAY_SIZE(imic_16KHz_osr256_actions), + }, + { + .freq_plan = 48000, + .osr = 256, + .actions = imic_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(imic_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile imic_profile = { + .path_type = ADIE_CODEC_TX, + .settings = imic_settings, + .setting_sz = ARRAY_SIZE(imic_settings), +}; + +static enum hsed_controller imic_pmctl_id[] = {PM_HSED_CONTROLLER_0}; + +static struct snddev_icodec_data snddev_imic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_MIC, + .profile = &imic_profile, + .channel_mode = 1, + .pmctl_id = imic_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(imic_pmctl_id), + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_imic_device = { + .name = "snddev_icodec", + .id = 1, + .dev = { .platform_data = &snddev_imic_data }, +}; + +static struct adie_codec_action_unit ihs_stereo_rx_48KHz_osr256_actions[] = + HEADSET_STEREO_RX_LEGACY_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ihs_stereo_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ihs_stereo_rx_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ihs_stereo_rx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ihs_stereo_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ihs_stereo_rx_settings, + .setting_sz = ARRAY_SIZE(ihs_stereo_rx_settings), +}; + +static struct snddev_icodec_data snddev_ihs_stereo_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_stereo_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_SPKR_STEREO, + .profile = &ihs_stereo_rx_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, + .property = SIDE_TONE_MASK, + .max_voice_rx_vol[VOC_NB_INDEX] = -700, + .min_voice_rx_vol[VOC_NB_INDEX] = -2200, + .max_voice_rx_vol[VOC_WB_INDEX] = -900, + .min_voice_rx_vol[VOC_WB_INDEX] = -2400 +}; + +static struct platform_device msm_ihs_stereo_rx_device = { + .name = "snddev_icodec", + .id = 2, + .dev = { .platform_data = &snddev_ihs_stereo_rx_data }, +}; + +static struct adie_codec_action_unit ihs_mono_rx_48KHz_osr256_actions[] = + HEADSET_RX_LEGACY_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ihs_mono_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ihs_mono_rx_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ihs_mono_rx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ihs_mono_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ihs_mono_rx_settings, + .setting_sz = ARRAY_SIZE(ihs_mono_rx_settings), +}; + +static struct snddev_icodec_data snddev_ihs_mono_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_mono_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_SPKR_MONO, + .profile = &ihs_mono_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, + .property = SIDE_TONE_MASK, + .max_voice_rx_vol[VOC_NB_INDEX] = -700, + .min_voice_rx_vol[VOC_NB_INDEX] = -2200, + .max_voice_rx_vol[VOC_WB_INDEX] = -900, + .min_voice_rx_vol[VOC_WB_INDEX] = -2400, + +}; + +static struct platform_device msm_ihs_mono_rx_device = { + .name = "snddev_icodec", + .id = 3, + .dev = { .platform_data = &snddev_ihs_mono_rx_data }, +}; + +static struct adie_codec_action_unit ihs_ffa_stereo_rx_48KHz_osr256_actions[] = + HEADSET_STEREO_RX_CAPLESS_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ihs_ffa_stereo_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ihs_ffa_stereo_rx_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ihs_ffa_stereo_rx_48KHz_osr256_actions), + } +}; + +#ifdef CONFIG_DEBUG_FS +static struct adie_codec_action_unit + ihs_ffa_stereo_rx_class_d_legacy_48KHz_osr256_actions[] = + HEADSET_STEREO_RX_CLASS_D_LEGACY_48000_OSR_256; + +static struct adie_codec_hwsetting_entry + ihs_ffa_stereo_rx_class_d_legacy_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = + ihs_ffa_stereo_rx_class_d_legacy_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE + (ihs_ffa_stereo_rx_class_d_legacy_48KHz_osr256_actions), + } +}; + +static struct adie_codec_action_unit + ihs_ffa_stereo_rx_class_ab_legacy_48KHz_osr256_actions[] = + HEADSET_STEREO_RX_LEGACY_48000_OSR_256; + +static struct adie_codec_hwsetting_entry + ihs_ffa_stereo_rx_class_ab_legacy_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = + ihs_ffa_stereo_rx_class_ab_legacy_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE + (ihs_ffa_stereo_rx_class_ab_legacy_48KHz_osr256_actions), + } +}; +#endif + +static struct adie_codec_dev_profile ihs_ffa_stereo_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ihs_ffa_stereo_rx_settings, + .setting_sz = ARRAY_SIZE(ihs_ffa_stereo_rx_settings), +}; + +static struct snddev_icodec_data snddev_ihs_ffa_stereo_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_stereo_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_SPKR_STEREO, + .profile = &ihs_ffa_stereo_rx_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_hsed_voltage_on, + .voltage_off = msm_snddev_hsed_voltage_off, + .max_voice_rx_vol[VOC_NB_INDEX] = -700, + .min_voice_rx_vol[VOC_NB_INDEX] = -2200, + .max_voice_rx_vol[VOC_WB_INDEX] = -900, + .min_voice_rx_vol[VOC_WB_INDEX] = -2400, +}; + +static struct platform_device msm_ihs_ffa_stereo_rx_device = { + .name = "snddev_icodec", + .id = 4, + .dev = { .platform_data = &snddev_ihs_ffa_stereo_rx_data }, +}; + +static struct adie_codec_action_unit ihs_ffa_mono_rx_48KHz_osr256_actions[] = + HEADSET_RX_CAPLESS_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ihs_ffa_mono_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ihs_ffa_mono_rx_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ihs_ffa_mono_rx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ihs_ffa_mono_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ihs_ffa_mono_rx_settings, + .setting_sz = ARRAY_SIZE(ihs_ffa_mono_rx_settings), +}; + +static struct snddev_icodec_data snddev_ihs_ffa_mono_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_mono_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_SPKR_MONO, + .profile = &ihs_ffa_mono_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_hsed_voltage_on, + .pamp_off = msm_snddev_hsed_voltage_off, + .max_voice_rx_vol[VOC_NB_INDEX] = -700, + .min_voice_rx_vol[VOC_NB_INDEX] = -2200, + .max_voice_rx_vol[VOC_WB_INDEX] = -900, + .min_voice_rx_vol[VOC_WB_INDEX] = -2400, +}; + +static struct platform_device msm_ihs_ffa_mono_rx_device = { + .name = "snddev_icodec", + .id = 5, + .dev = { .platform_data = &snddev_ihs_ffa_mono_rx_data }, +}; + +static struct adie_codec_action_unit ihs_mono_tx_8KHz_osr256_actions[] = + HEADSET_MONO_TX_8000_OSR_256; + +static struct adie_codec_action_unit ihs_mono_tx_16KHz_osr256_actions[] = + HEADSET_MONO_TX_16000_OSR_256; + +static struct adie_codec_action_unit ihs_mono_tx_48KHz_osr256_actions[] = + HEADSET_MONO_TX_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ihs_mono_tx_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = ihs_mono_tx_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ihs_mono_tx_8KHz_osr256_actions), + }, + { + .freq_plan = 16000, + .osr = 256, + .actions = ihs_mono_tx_16KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ihs_mono_tx_16KHz_osr256_actions), + }, + { + .freq_plan = 48000, + .osr = 256, + .actions = ihs_mono_tx_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ihs_mono_tx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ihs_mono_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ihs_mono_tx_settings, + .setting_sz = ARRAY_SIZE(ihs_mono_tx_settings), +}; + +static struct snddev_icodec_data snddev_ihs_mono_tx_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "headset_mono_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_MIC, + .profile = &ihs_mono_tx_profile, + .channel_mode = 1, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_tx_route_config, + .pamp_off = msm_snddev_tx_route_deconfig, +}; + +static struct platform_device msm_ihs_mono_tx_device = { + .name = "snddev_icodec", + .id = 6, + .dev = { .platform_data = &snddev_ihs_mono_tx_data }, +}; + +static struct adie_codec_action_unit ifmradio_handset_osr64_actions[] = + FM_HANDSET_OSR_64; + +static struct adie_codec_hwsetting_entry ifmradio_handset_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = ifmradio_handset_osr64_actions, + .action_sz = ARRAY_SIZE(ifmradio_handset_osr64_actions), + } +}; + +static struct adie_codec_dev_profile ifmradio_handset_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ifmradio_handset_settings, + .setting_sz = ARRAY_SIZE(ifmradio_handset_settings), +}; + +static struct snddev_icodec_data snddev_ifmradio_handset_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_FM), + .name = "fmradio_handset_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_LP_FM_SPKR_PHONE_STEREO_RX, + .profile = &ifmradio_handset_profile, + .channel_mode = 1, + .default_sample_rate = 8000, + .pamp_on = NULL, + .pamp_off = NULL, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device msm_ifmradio_handset_device = { + .name = "snddev_icodec", + .id = 7, + .dev = { .platform_data = &snddev_ifmradio_handset_data }, +}; + + +static struct adie_codec_action_unit ispeaker_rx_48KHz_osr256_actions[] = + SPEAKER_STEREO_RX_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ispeaker_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ispeaker_rx_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispeaker_rx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ispeaker_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ispeaker_rx_settings, + .setting_sz = ARRAY_SIZE(ispeaker_rx_settings), +}; + +static struct snddev_icodec_data snddev_ispeaker_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "speaker_stereo_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_STEREO, + .profile = &ispeaker_rx_profile, + .channel_mode = 2, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .default_sample_rate = 48000, + .pamp_on = &msm_snddev_poweramp_on, + .pamp_off = &msm_snddev_poweramp_off, + .max_voice_rx_vol[VOC_NB_INDEX] = 1000, + .min_voice_rx_vol[VOC_NB_INDEX] = -500, + .max_voice_rx_vol[VOC_WB_INDEX] = 1000, + .min_voice_rx_vol[VOC_WB_INDEX] = -500, +}; + +static struct platform_device msm_ispeaker_rx_device = { + .name = "snddev_icodec", + .id = 8, + .dev = { .platform_data = &snddev_ispeaker_rx_data }, + +}; + +static struct adie_codec_action_unit ifmradio_speaker_osr64_actions[] = + FM_SPEAKER_OSR_64; + +static struct adie_codec_hwsetting_entry ifmradio_speaker_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = ifmradio_speaker_osr64_actions, + .action_sz = ARRAY_SIZE(ifmradio_speaker_osr64_actions), + } +}; + +static struct adie_codec_dev_profile ifmradio_speaker_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ifmradio_speaker_settings, + .setting_sz = ARRAY_SIZE(ifmradio_speaker_settings), +}; + +static struct snddev_icodec_data snddev_ifmradio_speaker_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_FM), + .name = "fmradio_speaker_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_LP_FM_SPKR_PHONE_STEREO_RX, + .profile = &ifmradio_speaker_profile, + .channel_mode = 1, + .default_sample_rate = 8000, + .pamp_on = &msm_snddev_poweramp_on, + .pamp_off = &msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device msm_ifmradio_speaker_device = { + .name = "snddev_icodec", + .id = 9, + .dev = { .platform_data = &snddev_ifmradio_speaker_data }, +}; + +static struct adie_codec_action_unit ifmradio_headset_osr64_actions[] = + FM_HEADSET_STEREO_CLASS_D_LEGACY_OSR_64; + +static struct adie_codec_hwsetting_entry ifmradio_headset_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = ifmradio_headset_osr64_actions, + .action_sz = ARRAY_SIZE(ifmradio_headset_osr64_actions), + } +}; + +static struct adie_codec_dev_profile ifmradio_headset_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ifmradio_headset_settings, + .setting_sz = ARRAY_SIZE(ifmradio_headset_settings), +}; + +static struct snddev_icodec_data snddev_ifmradio_headset_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_FM), + .name = "fmradio_headset_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_LP_FM_HEADSET_SPKR_STEREO_RX, + .profile = &ifmradio_headset_profile, + .channel_mode = 1, + .default_sample_rate = 8000, + .pamp_on = NULL, + .pamp_off = NULL, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device msm_ifmradio_headset_device = { + .name = "snddev_icodec", + .id = 10, + .dev = { .platform_data = &snddev_ifmradio_headset_data }, +}; + + +static struct adie_codec_action_unit ifmradio_ffa_headset_osr64_actions[] = + FM_HEADSET_CLASS_AB_STEREO_CAPLESS_OSR_64; + +static struct adie_codec_hwsetting_entry ifmradio_ffa_headset_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = ifmradio_ffa_headset_osr64_actions, + .action_sz = ARRAY_SIZE(ifmradio_ffa_headset_osr64_actions), + } +}; + +static struct adie_codec_dev_profile ifmradio_ffa_headset_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ifmradio_ffa_headset_settings, + .setting_sz = ARRAY_SIZE(ifmradio_ffa_headset_settings), +}; + +static struct snddev_icodec_data snddev_ifmradio_ffa_headset_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_FM), + .name = "fmradio_headset_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_LP_FM_HEADSET_SPKR_STEREO_RX, + .profile = &ifmradio_ffa_headset_profile, + .channel_mode = 1, + .default_sample_rate = 8000, + .pamp_on = msm_snddev_hsed_voltage_on, + .pamp_off = msm_snddev_hsed_voltage_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device msm_ifmradio_ffa_headset_device = { + .name = "snddev_icodec", + .id = 11, + .dev = { .platform_data = &snddev_ifmradio_ffa_headset_data }, +}; + +static struct snddev_ecodec_data snddev_bt_sco_earpiece_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "bt_sco_rx", + .copp_id = 1, + .acdb_id = ACDB_ID_BT_SCO_SPKR, + .channel_mode = 1, + .conf_pcm_ctl_val = BT_SCO_PCM_CTL_VAL, + .conf_aux_codec_intf = BT_SCO_AUX_CODEC_INTF, + .conf_data_format_padding_val = BT_SCO_DATA_FORMAT_PADDING, + .max_voice_rx_vol[VOC_NB_INDEX] = 400, + .min_voice_rx_vol[VOC_NB_INDEX] = -1100, + .max_voice_rx_vol[VOC_WB_INDEX] = 400, + .min_voice_rx_vol[VOC_WB_INDEX] = -1100, +}; + +static struct snddev_ecodec_data snddev_bt_sco_mic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "bt_sco_tx", + .copp_id = 1, + .acdb_id = ACDB_ID_BT_SCO_MIC, + .channel_mode = 1, + .conf_pcm_ctl_val = BT_SCO_PCM_CTL_VAL, + .conf_aux_codec_intf = BT_SCO_AUX_CODEC_INTF, + .conf_data_format_padding_val = BT_SCO_DATA_FORMAT_PADDING, +}; + +struct platform_device msm_bt_sco_earpiece_device = { + .name = "msm_snddev_ecodec", + .id = 0, + .dev = { .platform_data = &snddev_bt_sco_earpiece_data }, +}; + +struct platform_device msm_bt_sco_mic_device = { + .name = "msm_snddev_ecodec", + .id = 1, + .dev = { .platform_data = &snddev_bt_sco_mic_data }, +}; + +static struct adie_codec_action_unit idual_mic_endfire_8KHz_osr256_actions[] = + MIC1_LEFT_LINE_IN_RIGHT_8000_OSR_256; + +static struct adie_codec_hwsetting_entry idual_mic_endfire_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = idual_mic_endfire_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_endfire_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16KHz */ + { + .freq_plan = 16000, + .osr = 256, + .actions = idual_mic_endfire_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_endfire_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 48KHz */ + { + .freq_plan = 48000, + .osr = 256, + .actions = idual_mic_endfire_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_endfire_8KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile idual_mic_endfire_profile = { + .path_type = ADIE_CODEC_TX, + .settings = idual_mic_endfire_settings, + .setting_sz = ARRAY_SIZE(idual_mic_endfire_settings), +}; + +static enum hsed_controller idual_mic_endfire_pmctl_id[] = { + PM_HSED_CONTROLLER_0, PM_HSED_CONTROLLER_2 +}; + +static struct snddev_icodec_data snddev_idual_mic_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_endfire_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_MIC_ENDFIRE, + .profile = &idual_mic_endfire_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_endfire_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_endfire_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_idual_mic_endfire_device = { + .name = "snddev_icodec", + .id = 12, + .dev = { .platform_data = &snddev_idual_mic_endfire_data }, +}; + + +static struct snddev_icodec_data\ + snddev_idual_mic_endfire_real_stereo_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_endfire_tx_real_stereo", + .copp_id = 0, + .acdb_id = PSEUDO_ACDB_ID, + .profile = &idual_mic_endfire_profile, + .channel_mode = REAL_STEREO_CHANNEL_MODE, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_endfire_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_endfire_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_real_stereo_tx_device = { + .name = "snddev_icodec", + .id = 26, + .dev = { .platform_data = + &snddev_idual_mic_endfire_real_stereo_data }, +}; + +static struct adie_codec_action_unit idual_mic_bs_8KHz_osr256_actions[] = + MIC1_LEFT_AUX_IN_RIGHT_8000_OSR_256; + +static struct adie_codec_hwsetting_entry idual_mic_broadside_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = idual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_bs_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16KHz */ + { + .freq_plan = 16000, + .osr = 256, + .actions = idual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_bs_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16KHz */ + { + .freq_plan = 48000, + .osr = 256, + .actions = idual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_bs_8KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile idual_mic_broadside_profile = { + .path_type = ADIE_CODEC_TX, + .settings = idual_mic_broadside_settings, + .setting_sz = ARRAY_SIZE(idual_mic_broadside_settings), +}; + +static enum hsed_controller idual_mic_broadside_pmctl_id[] = { + PM_HSED_CONTROLLER_0, PM_HSED_CONTROLLER_2 +}; + +static struct snddev_icodec_data snddev_idual_mic_broadside_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_broadside_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_MIC_BROADSIDE, + .profile = &idual_mic_broadside_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_broadside_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_broadside_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_idual_mic_broadside_device = { + .name = "snddev_icodec", + .id = 13, + .dev = { .platform_data = &snddev_idual_mic_broadside_data }, +}; + +static struct adie_codec_action_unit ispk_dual_mic_ef_8KHz_osr256_actions[] = + SPEAKER_MIC1_LEFT_LINE_IN_RIGHT_8000_OSR_256; + +static struct adie_codec_hwsetting_entry ispk_dual_mic_ef_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = ispk_dual_mic_ef_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispk_dual_mic_ef_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16Khz */ + { + .freq_plan = 16000, + .osr = 256, + .actions = ispk_dual_mic_ef_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispk_dual_mic_ef_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 48KHz */ + { + .freq_plan = 48000, + .osr = 256, + .actions = ispk_dual_mic_ef_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispk_dual_mic_ef_8KHz_osr256_actions), + }, +}; + +static struct adie_codec_dev_profile ispk_dual_mic_ef_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ispk_dual_mic_ef_settings, + .setting_sz = ARRAY_SIZE(ispk_dual_mic_ef_settings), +}; + +static struct snddev_icodec_data snddev_spk_idual_mic_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_dual_mic_endfire_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_MIC_ENDFIRE, + .profile = &ispk_dual_mic_ef_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_endfire_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_endfire_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_spk_idual_mic_endfire_device = { + .name = "snddev_icodec", + .id = 14, + .dev = { .platform_data = &snddev_spk_idual_mic_endfire_data }, +}; + +static struct adie_codec_action_unit ispk_dual_mic_bs_8KHz_osr256_actions[] = + SPEAKER_MIC1_LEFT_AUX_IN_RIGHT_8000_OSR_256; + +static struct adie_codec_hwsetting_entry ispk_dual_mic_bs_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = ispk_dual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispk_dual_mic_bs_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16Khz */ + { + .freq_plan = 16000, + .osr = 256, + .actions = ispk_dual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispk_dual_mic_bs_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 48KHz */ + { + .freq_plan = 48000, + .osr = 256, + .actions = ispk_dual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispk_dual_mic_bs_8KHz_osr256_actions), + }, +}; + +static struct adie_codec_dev_profile ispk_dual_mic_bs_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ispk_dual_mic_bs_settings, + .setting_sz = ARRAY_SIZE(ispk_dual_mic_bs_settings), +}; +static struct snddev_icodec_data snddev_spk_idual_mic_broadside_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_dual_mic_broadside_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_MIC_BROADSIDE, + .profile = &ispk_dual_mic_bs_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_broadside_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_broadside_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_spk_idual_mic_broadside_device = { + .name = "snddev_icodec", + .id = 15, + .dev = { .platform_data = &snddev_spk_idual_mic_broadside_data }, +}; + +static struct adie_codec_action_unit itty_hs_mono_tx_8KHz_osr256_actions[] = + TTY_HEADSET_MONO_TX_8000_OSR_256; + +static struct adie_codec_hwsetting_entry itty_hs_mono_tx_settings[] = { + /* 8KHz, 16KHz, 48KHz TTY Tx devices can shared same set of actions */ + { + .freq_plan = 8000, + .osr = 256, + .actions = itty_hs_mono_tx_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(itty_hs_mono_tx_8KHz_osr256_actions), + }, + { + .freq_plan = 16000, + .osr = 256, + .actions = itty_hs_mono_tx_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(itty_hs_mono_tx_8KHz_osr256_actions), + }, + { + .freq_plan = 48000, + .osr = 256, + .actions = itty_hs_mono_tx_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(itty_hs_mono_tx_8KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile itty_hs_mono_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = itty_hs_mono_tx_settings, + .setting_sz = ARRAY_SIZE(itty_hs_mono_tx_settings), +}; + +static struct snddev_icodec_data snddev_itty_hs_mono_tx_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE | SNDDEV_CAP_TTY), + .name = "tty_headset_mono_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_TTY_HEADSET_MIC, + .profile = &itty_hs_mono_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_itty_hs_mono_tx_device = { + .name = "snddev_icodec", + .id = 16, + .dev = { .platform_data = &snddev_itty_hs_mono_tx_data }, +}; + +static struct adie_codec_action_unit itty_hs_mono_rx_8KHz_osr256_actions[] = + TTY_HEADSET_MONO_RX_CLASS_D_8000_OSR_256; + +static struct adie_codec_action_unit itty_hs_mono_rx_16KHz_osr256_actions[] = + TTY_HEADSET_MONO_RX_CLASS_D_16000_OSR_256; + +static struct adie_codec_action_unit itty_hs_mono_rx_48KHz_osr256_actions[] = + TTY_HEADSET_MONO_RX_CLASS_D_48000_OSR_256; + +static struct adie_codec_hwsetting_entry itty_hs_mono_rx_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = itty_hs_mono_rx_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(itty_hs_mono_rx_8KHz_osr256_actions), + }, + { + .freq_plan = 16000, + .osr = 256, + .actions = itty_hs_mono_rx_16KHz_osr256_actions, + .action_sz = ARRAY_SIZE(itty_hs_mono_rx_16KHz_osr256_actions), + }, + { + .freq_plan = 48000, + .osr = 256, + .actions = itty_hs_mono_rx_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(itty_hs_mono_rx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile itty_hs_mono_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = itty_hs_mono_rx_settings, + .setting_sz = ARRAY_SIZE(itty_hs_mono_rx_settings), +}; + +static struct snddev_icodec_data snddev_itty_hs_mono_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE | SNDDEV_CAP_TTY), + .name = "tty_headset_mono_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_TTY_HEADSET_SPKR, + .profile = &itty_hs_mono_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, + .max_voice_rx_vol[VOC_NB_INDEX] = 0, + .min_voice_rx_vol[VOC_NB_INDEX] = 0, + .max_voice_rx_vol[VOC_WB_INDEX] = 0, + .min_voice_rx_vol[VOC_WB_INDEX] = 0, +}; + +static struct platform_device msm_itty_hs_mono_rx_device = { + .name = "snddev_icodec", + .id = 17, + .dev = { .platform_data = &snddev_itty_hs_mono_rx_data }, +}; + +static struct adie_codec_action_unit ispeaker_tx_8KHz_osr256_actions[] = + SPEAKER_TX_8000_OSR_256; + +static struct adie_codec_action_unit ispeaker_tx_48KHz_osr256_actions[] = + SPEAKER_TX_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ispeaker_tx_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = ispeaker_tx_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispeaker_tx_8KHz_osr256_actions), + }, + { /* 8KHz profile is good for 16KHz */ + .freq_plan = 16000, + .osr = 256, + .actions = ispeaker_tx_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispeaker_tx_8KHz_osr256_actions), + }, + { + .freq_plan = 48000, + .osr = 256, + .actions = ispeaker_tx_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispeaker_tx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ispeaker_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ispeaker_tx_settings, + .setting_sz = ARRAY_SIZE(ispeaker_tx_settings), +}; + +static enum hsed_controller ispk_pmctl_id[] = {PM_HSED_CONTROLLER_0}; + +static struct snddev_icodec_data snddev_ispeaker_tx_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_mono_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_MIC, + .profile = &ispeaker_tx_profile, + .channel_mode = 1, + .pmctl_id = ispk_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(ispk_pmctl_id), + .default_sample_rate = 48000, + .pamp_on = msm_snddev_tx_route_config, + .pamp_off = msm_snddev_tx_route_deconfig, +}; + +static struct platform_device msm_ispeaker_tx_device = { + .name = "snddev_icodec", + .id = 18, + .dev = { .platform_data = &snddev_ispeaker_tx_data }, +}; + +static struct adie_codec_action_unit iearpiece_ffa_48KHz_osr256_actions[] = + HANDSET_RX_48000_OSR_256_FFA; + +static struct adie_codec_hwsetting_entry iearpiece_ffa_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = iearpiece_ffa_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(iearpiece_ffa_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile iearpiece_ffa_profile = { + .path_type = ADIE_CODEC_RX, + .settings = iearpiece_ffa_settings, + .setting_sz = ARRAY_SIZE(iearpiece_ffa_settings), +}; + +static struct snddev_icodec_data snddev_iearpiece_ffa_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "handset_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_SPKR, + .profile = &iearpiece_ffa_profile, + .channel_mode = 1, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, + .max_voice_rx_vol[VOC_NB_INDEX] = -700, + .min_voice_rx_vol[VOC_NB_INDEX] = -2200, + .max_voice_rx_vol[VOC_WB_INDEX] = -1400, + .min_voice_rx_vol[VOC_WB_INDEX] = -2900, +}; + +static struct platform_device msm_iearpiece_ffa_device = { + .name = "snddev_icodec", + .id = 19, + .dev = { .platform_data = &snddev_iearpiece_ffa_data }, +}; + +static struct adie_codec_action_unit imic_ffa_8KHz_osr256_actions[] = + HANDSET_TX_8000_OSR_256_FFA; + +static struct adie_codec_action_unit imic_ffa_16KHz_osr256_actions[] = + HANDSET_TX_16000_OSR_256_FFA; + +static struct adie_codec_action_unit imic_ffa_48KHz_osr256_actions[] = + HANDSET_TX_48000_OSR_256_FFA; + +static struct adie_codec_hwsetting_entry imic_ffa_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = imic_ffa_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(imic_ffa_8KHz_osr256_actions), + }, + { + .freq_plan = 16000, + .osr = 256, + .actions = imic_ffa_16KHz_osr256_actions, + .action_sz = ARRAY_SIZE(imic_ffa_16KHz_osr256_actions), + }, + { + .freq_plan = 48000, + .osr = 256, + .actions = imic_ffa_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(imic_ffa_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile imic_ffa_profile = { + .path_type = ADIE_CODEC_TX, + .settings = imic_ffa_settings, + .setting_sz = ARRAY_SIZE(imic_ffa_settings), +}; + +static struct snddev_icodec_data snddev_imic_ffa_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_MIC, + .profile = &imic_ffa_profile, + .channel_mode = 1, + .pmctl_id = imic_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(imic_pmctl_id), + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_imic_ffa_device = { + .name = "snddev_icodec", + .id = 20, + .dev = { .platform_data = &snddev_imic_ffa_data }, +}; + + +static struct adie_codec_action_unit + ihs_stereo_speaker_stereo_rx_48KHz_osr256_actions[] = + HEADSET_STEREO_SPEAKER_STEREO_RX_CAPLESS_48000_OSR_256; + + +static struct adie_codec_hwsetting_entry + ihs_stereo_speaker_stereo_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ihs_stereo_speaker_stereo_rx_48KHz_osr256_actions, + .action_sz = + ARRAY_SIZE(ihs_stereo_speaker_stereo_rx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ihs_stereo_speaker_stereo_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ihs_stereo_speaker_stereo_rx_settings, + .setting_sz = ARRAY_SIZE(ihs_stereo_speaker_stereo_rx_settings), +}; + +static struct snddev_icodec_data snddev_ihs_stereo_speaker_stereo_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_stereo_speaker_stereo_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_STEREO_PLUS_SPKR_STEREO_RX, + .profile = &ihs_stereo_speaker_stereo_rx_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .voltage_on = msm_snddev_hsed_voltage_on, + .voltage_off = msm_snddev_hsed_voltage_off, + .max_voice_rx_vol[VOC_NB_INDEX] = -500, + .min_voice_rx_vol[VOC_NB_INDEX] = -2000, + .max_voice_rx_vol[VOC_WB_INDEX] = -500, + .min_voice_rx_vol[VOC_WB_INDEX] = -2000, +}; + +static struct platform_device msm_ihs_stereo_speaker_stereo_rx_device = { + .name = "snddev_icodec", + .id = 21, + .dev = { .platform_data = &snddev_ihs_stereo_speaker_stereo_rx_data }, +}; + +static struct snddev_mi2s_data snddev_mi2s_stereo_rx_data = { + .capability = SNDDEV_CAP_RX , + .name = "hdmi_stereo_rx", + .copp_id = 3, + .acdb_id = ACDB_ID_HDMI, + .channel_mode = 2, + .sd_lines = MI2S_SD_0, + .route = msm_snddev_tx_route_config, + .deroute = msm_snddev_tx_route_deconfig, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_snddev_mi2s_stereo_rx_device = { + .name = "snddev_mi2s", + .id = 0, + .dev = { .platform_data = &snddev_mi2s_stereo_rx_data }, +}; + + +static struct snddev_mi2s_data snddev_mi2s_fm_tx_data = { + .capability = SNDDEV_CAP_TX , + .name = "fmradio_stereo_tx", + .copp_id = 2, + .acdb_id = ACDB_ID_FM_TX, + .channel_mode = 2, + .sd_lines = MI2S_SD_3, + .route = NULL, + .deroute = NULL, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_snddev_mi2s_fm_tx_device = { + .name = "snddev_mi2s", + .id = 1, + .dev = { .platform_data = &snddev_mi2s_fm_tx_data}, +}; + +static struct snddev_icodec_data snddev_fluid_imic_tx_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_MIC, + .profile = &ispeaker_tx_profile, + .channel_mode = 1, + .pmctl_id = ispk_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(ispk_pmctl_id), + .default_sample_rate = 48000, + .pamp_on = msm_snddev_tx_route_config, + .pamp_off = msm_snddev_tx_route_deconfig, +}; + +static struct platform_device msm_fluid_imic_tx_device = { + .name = "snddev_icodec", + .id = 22, + .dev = { .platform_data = &snddev_fluid_imic_tx_data }, +}; + +static struct snddev_icodec_data snddev_fluid_iearpiece_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "handset_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_STEREO, + .profile = &ispeaker_rx_profile, + .channel_mode = 2, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .default_sample_rate = 48000, + .pamp_on = &msm_snddev_poweramp_on, + .pamp_off = &msm_snddev_poweramp_off, + .max_voice_rx_vol[VOC_NB_INDEX] = -500, + .min_voice_rx_vol[VOC_NB_INDEX] = -1000, + .max_voice_rx_vol[VOC_WB_INDEX] = -500, + .min_voice_rx_vol[VOC_WB_INDEX] = -1000, +}; + +static struct platform_device msm_fluid_iearpeice_rx_device = { + .name = "snddev_icodec", + .id = 23, + .dev = { .platform_data = &snddev_fluid_iearpiece_rx_data }, +}; + +static struct adie_codec_action_unit fluid_idual_mic_ef_8KHz_osr256_actions[] = + MIC1_LEFT_AUX_IN_RIGHT_8000_OSR_256; + +static struct adie_codec_hwsetting_entry fluid_idual_mic_endfire_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = fluid_idual_mic_ef_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(fluid_idual_mic_ef_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16KHz */ + { + .freq_plan = 16000, + .osr = 256, + .actions = fluid_idual_mic_ef_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(fluid_idual_mic_ef_8KHz_osr256_actions), + }, /* 8KHz profile can also be used for 48KHz */ + { + .freq_plan = 48000, + .osr = 256, + .actions = fluid_idual_mic_ef_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(fluid_idual_mic_ef_8KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile fluid_idual_mic_endfire_profile = { + .path_type = ADIE_CODEC_TX, + .settings = fluid_idual_mic_endfire_settings, + .setting_sz = ARRAY_SIZE(fluid_idual_mic_endfire_settings), +}; + +static enum hsed_controller fluid_idual_mic_endfire_pmctl_id[] = { + PM_HSED_CONTROLLER_0, PM_HSED_CONTROLLER_2 +}; + +static struct snddev_icodec_data snddev_fluid_idual_mic_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_endfire_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_MIC_ENDFIRE, + .profile = &fluid_idual_mic_endfire_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = fluid_idual_mic_endfire_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(fluid_idual_mic_endfire_pmctl_id), + .pamp_on = msm_snddev_tx_route_config, + .pamp_off = msm_snddev_tx_route_deconfig, +}; + +static struct platform_device msm_fluid_idual_mic_endfire_device = { + .name = "snddev_icodec", + .id = 24, + .dev = { .platform_data = &snddev_fluid_idual_mic_endfire_data }, +}; + +static struct snddev_icodec_data snddev_fluid_spk_idual_mic_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_dual_mic_endfire_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_MIC_ENDFIRE, + .profile = &fluid_idual_mic_endfire_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = fluid_idual_mic_endfire_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(fluid_idual_mic_endfire_pmctl_id), + .pamp_on = msm_snddev_tx_route_config, + .pamp_off = msm_snddev_tx_route_deconfig, +}; + +static struct platform_device msm_fluid_spk_idual_mic_endfire_device = { + .name = "snddev_icodec", + .id = 25, + .dev = { .platform_data = &snddev_fluid_spk_idual_mic_endfire_data }, +}; + +static struct snddev_virtual_data snddev_a2dp_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "a2dp_tx", + .copp_id = 5, + .acdb_id = PSEUDO_ACDB_ID, +}; + +static struct snddev_virtual_data snddev_a2dp_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "a2dp_rx", + .copp_id = 2, + .acdb_id = PSEUDO_ACDB_ID, +}; + +static struct platform_device msm_a2dp_rx_device = { + .name = "snddev_virtual", + .id = 0, + .dev = { .platform_data = &snddev_a2dp_rx_data }, +}; + +static struct platform_device msm_a2dp_tx_device = { + .name = "snddev_virtual", + .id = 1, + .dev = { .platform_data = &snddev_a2dp_tx_data }, +}; + +static struct snddev_virtual_data snddev_uplink_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "uplink_rx", + .copp_id = 5, + .acdb_id = PSEUDO_ACDB_ID, +}; + +static struct platform_device msm_uplink_rx_device = { + .name = "snddev_virtual", + .id = 2, + .dev = { .platform_data = &snddev_uplink_rx_data }, +}; + +static struct platform_device *snd_devices_ffa[] __initdata = { + &msm_iearpiece_ffa_device, + &msm_imic_ffa_device, + &msm_ifmradio_handset_device, + &msm_ihs_ffa_stereo_rx_device, + &msm_ihs_ffa_mono_rx_device, + &msm_ihs_mono_tx_device, + &msm_bt_sco_earpiece_device, + &msm_bt_sco_mic_device, + &msm_ispeaker_rx_device, + &msm_ifmradio_speaker_device, + &msm_ifmradio_ffa_headset_device, + &msm_idual_mic_endfire_device, + &msm_idual_mic_broadside_device, + &msm_spk_idual_mic_endfire_device, + &msm_spk_idual_mic_broadside_device, + &msm_itty_hs_mono_tx_device, + &msm_itty_hs_mono_rx_device, + &msm_ispeaker_tx_device, + &msm_ihs_stereo_speaker_stereo_rx_device, + &msm_a2dp_rx_device, + &msm_a2dp_tx_device, + &msm_snddev_mi2s_stereo_rx_device, + &msm_snddev_mi2s_fm_tx_device, + &msm_uplink_rx_device, + &msm_real_stereo_tx_device, +}; + +static struct platform_device *snd_devices_surf[] __initdata = { + &msm_iearpiece_device, + &msm_imic_device, + &msm_ihs_stereo_rx_device, + &msm_ihs_mono_rx_device, + &msm_ihs_mono_tx_device, + &msm_bt_sco_earpiece_device, + &msm_bt_sco_mic_device, + &msm_ifmradio_handset_device, + &msm_ispeaker_rx_device, + &msm_ifmradio_speaker_device, + &msm_ifmradio_headset_device, + &msm_itty_hs_mono_tx_device, + &msm_itty_hs_mono_rx_device, + &msm_ispeaker_tx_device, + &msm_ihs_stereo_speaker_stereo_rx_device, + &msm_a2dp_rx_device, + &msm_a2dp_tx_device, + &msm_snddev_mi2s_stereo_rx_device, + &msm_snddev_mi2s_fm_tx_device, + &msm_uplink_rx_device, +}; + +static struct platform_device *snd_devices_fluid[] __initdata = { + &msm_ihs_stereo_rx_device, + &msm_ihs_mono_rx_device, + &msm_ihs_mono_tx_device, + &msm_ispeaker_rx_device, + &msm_ispeaker_tx_device, + &msm_fluid_imic_tx_device, + &msm_fluid_iearpeice_rx_device, + &msm_fluid_idual_mic_endfire_device, + &msm_fluid_spk_idual_mic_endfire_device, + &msm_a2dp_rx_device, + &msm_a2dp_tx_device, + &msm_snddev_mi2s_stereo_rx_device, + &msm_uplink_rx_device, + &msm_ifmradio_speaker_device, + &msm_ifmradio_headset_device, +}; + +#ifdef CONFIG_DEBUG_FS +static void snddev_hsed_config_modify_setting(int type) +{ + struct platform_device *device; + struct snddev_icodec_data *icodec_data; + + device = &msm_ihs_ffa_stereo_rx_device; + icodec_data = (struct snddev_icodec_data *)device->dev.platform_data; + + if (icodec_data) { + if (type == 1) { + icodec_data->voltage_on = NULL; + icodec_data->voltage_off = NULL; + icodec_data->profile->settings = + ihs_ffa_stereo_rx_class_d_legacy_settings; + icodec_data->profile->setting_sz = + ARRAY_SIZE(ihs_ffa_stereo_rx_class_d_legacy_settings); + } else if (type == 2) { + icodec_data->voltage_on = NULL; + icodec_data->voltage_off = NULL; + icodec_data->profile->settings = + ihs_ffa_stereo_rx_class_ab_legacy_settings; + icodec_data->profile->setting_sz = + ARRAY_SIZE(ihs_ffa_stereo_rx_class_ab_legacy_settings); + } + } +} + +static void snddev_hsed_config_restore_setting(void) +{ + struct platform_device *device; + struct snddev_icodec_data *icodec_data; + + device = &msm_ihs_ffa_stereo_rx_device; + icodec_data = (struct snddev_icodec_data *)device->dev.platform_data; + + if (icodec_data) { + icodec_data->voltage_on = msm_snddev_hsed_voltage_on; + icodec_data->voltage_off = msm_snddev_hsed_voltage_off; + icodec_data->profile->settings = ihs_ffa_stereo_rx_settings; + icodec_data->profile->setting_sz = + ARRAY_SIZE(ihs_ffa_stereo_rx_settings); + } +} + +static ssize_t snddev_hsed_config_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *lb_str = filp->private_data; + char cmd; + + if (get_user(cmd, ubuf)) + return -EFAULT; + + if (!strcmp(lb_str, "msm_hsed_config")) { + switch (cmd) { + case '0': + snddev_hsed_config_restore_setting(); + break; + + case '1': + snddev_hsed_config_modify_setting(1); + break; + + case '2': + snddev_hsed_config_modify_setting(2); + break; + + default: + break; + } + } + return cnt; +} + +static int snddev_hsed_config_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations snddev_hsed_config_debug_fops = { + .open = snddev_hsed_config_debug_open, + .write = snddev_hsed_config_debug_write +}; +#endif + +void __ref msm_snddev_init(void) +{ + if (machine_is_msm7x30_ffa() || machine_is_msm8x55_ffa() || + machine_is_msm8x55_svlte_ffa()) { + platform_add_devices(snd_devices_ffa, + ARRAY_SIZE(snd_devices_ffa)); +#ifdef CONFIG_DEBUG_FS + debugfs_hsed_config = debugfs_create_file("msm_hsed_config", + S_IFREG | S_IRUGO, NULL, + (void *) "msm_hsed_config", &snddev_hsed_config_debug_fops); +#endif + } else if (machine_is_msm7x30_surf() || machine_is_msm8x55_surf() || + machine_is_msm8x55_svlte_surf()) + platform_add_devices(snd_devices_surf, + ARRAY_SIZE(snd_devices_surf)); + else if (machine_is_msm7x30_fluid()) + platform_add_devices(snd_devices_fluid, + ARRAY_SIZE(snd_devices_fluid)); + else + pr_err("%s: Unknown machine type\n", __func__); +} diff --git a/arch/arm/mach-msm/qdsp5v2/snddev_data_timpani.c b/arch/arm/mach-msm/qdsp5v2/snddev_data_timpani.c new file mode 100644 index 00000000000..c0a48c826a5 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/snddev_data_timpani.c @@ -0,0 +1,1006 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "timpani_profile_7x30.h" +#include + +/* define the value for BT_SCO */ +#define BT_SCO_PCM_CTL_VAL (PCM_CTL__RPCM_WIDTH__LINEAR_V |\ + PCM_CTL__TPCM_WIDTH__LINEAR_V) +#define BT_SCO_DATA_FORMAT_PADDING (DATA_FORMAT_PADDING_INFO__RPCM_FORMAT_V |\ + DATA_FORMAT_PADDING_INFO__TPCM_FORMAT_V) +#define BT_SCO_AUX_CODEC_INTF AUX_CODEC_INTF_CTL__PCMINTF_DATA_EN_V + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_hsed_config; +static void snddev_hsed_config_modify_setting(int type); +static void snddev_hsed_config_restore_setting(void); +#endif + +static struct adie_codec_action_unit iearpiece_ffa_48KHz_osr256_actions[] = + EAR_PRI_MONO_8000_OSR_256; /* 8000 profile also works for 48k */ + +static struct adie_codec_hwsetting_entry iearpiece_ffa_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = iearpiece_ffa_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(iearpiece_ffa_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile iearpiece_ffa_profile = { + .path_type = ADIE_CODEC_RX, + .settings = iearpiece_ffa_settings, + .setting_sz = ARRAY_SIZE(iearpiece_ffa_settings), +}; + +static struct snddev_icodec_data snddev_iearpiece_ffa_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "handset_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_SPKR, + .profile = &iearpiece_ffa_profile, + .channel_mode = 1, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, + .property = SIDE_TONE_MASK, + .max_voice_rx_vol[VOC_NB_INDEX] = -700, + .min_voice_rx_vol[VOC_NB_INDEX] = -2200, + .max_voice_rx_vol[VOC_WB_INDEX] = -1400, + .min_voice_rx_vol[VOC_WB_INDEX] = -2900, +}; + +static struct platform_device msm_iearpiece_ffa_device = { + .name = "snddev_icodec", + .id = 19, + .dev = { .platform_data = &snddev_iearpiece_ffa_data }, +}; + +static struct adie_codec_action_unit imic_ffa_48KHz_osr256_actions[] = + AMIC_PRI_MONO_8000_OSR_256; /* 8000 profile also works for 48k */ + +static struct adie_codec_hwsetting_entry imic_ffa_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = imic_ffa_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(imic_ffa_48KHz_osr256_actions), + } +}; + +static enum hsed_controller imic_pmctl_id[] = {PM_HSED_CONTROLLER_0}; + +static struct adie_codec_dev_profile imic_ffa_profile = { + .path_type = ADIE_CODEC_TX, + .settings = imic_ffa_settings, + .setting_sz = ARRAY_SIZE(imic_ffa_settings), +}; + +static struct snddev_icodec_data snddev_imic_ffa_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_MIC, + .profile = &imic_ffa_profile, + .channel_mode = 1, + .pmctl_id = imic_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(imic_pmctl_id), + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_imic_ffa_device = { + .name = "snddev_icodec", + .id = 20, + .dev = { .platform_data = &snddev_imic_ffa_data }, +}; + +static struct adie_codec_action_unit ispkr_stereo_48KHz_osr256_actions[] = + SPEAKER_PRI_STEREO_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ispkr_stereo_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ispkr_stereo_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispkr_stereo_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ispkr_stereo_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ispkr_stereo_settings, + .setting_sz = ARRAY_SIZE(ispkr_stereo_settings), +}; + +static struct snddev_icodec_data snddev_ispkr_stereo_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "speaker_stereo_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_STEREO, + .profile = &ispkr_stereo_profile, + .channel_mode = 2, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .max_voice_rx_vol[VOC_NB_INDEX] = 1000, + .min_voice_rx_vol[VOC_NB_INDEX] = -500, + .max_voice_rx_vol[VOC_WB_INDEX] = 1000, + .min_voice_rx_vol[VOC_WB_INDEX] = -500 +}; + +static struct platform_device msm_ispkr_stereo_device = { + .name = "snddev_icodec", + .id = 8, + .dev = { .platform_data = &snddev_ispkr_stereo_data }, +}; + +static struct adie_codec_action_unit iheadset_mic_tx_osr256_actions[] = + AMIC1_HEADSET_TX_MONO_PRIMARY_OSR256; + +static struct adie_codec_hwsetting_entry iheadset_mic_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = iheadset_mic_tx_osr256_actions, + .action_sz = ARRAY_SIZE(iheadset_mic_tx_osr256_actions), + } +}; + +static struct adie_codec_dev_profile iheadset_mic_profile = { + .path_type = ADIE_CODEC_TX, + .settings = iheadset_mic_tx_settings, + .setting_sz = ARRAY_SIZE(iheadset_mic_tx_settings), +}; + +static struct snddev_icodec_data snddev_headset_mic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "headset_mono_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_MIC, + .profile = &iheadset_mic_profile, + .channel_mode = 1, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_tx_route_config, + .pamp_off = msm_snddev_tx_route_deconfig, +}; + +static struct platform_device msm_headset_mic_device = { + .name = "snddev_icodec", + .id = 6, + .dev = { .platform_data = &snddev_headset_mic_data }, +}; + +static struct snddev_mi2s_data snddev_mi2s_fm_tx_data = { + .capability = SNDDEV_CAP_TX , + .name = "fmradio_stereo_tx", + .copp_id = 2, + .acdb_id = ACDB_ID_FM_TX, + .channel_mode = 2, + .sd_lines = MI2S_SD_3, + .route = NULL, + .deroute = NULL, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_snddev_mi2s_fm_tx_device = { + .name = "snddev_mi2s", + .id = 1, + .dev = { .platform_data = &snddev_mi2s_fm_tx_data}, +}; + +static struct snddev_mi2s_data snddev_mi2s_fm_rx_data = { + .capability = SNDDEV_CAP_RX , + .name = "fmradio_stereo_rx", + .copp_id = 3, + .acdb_id = ACDB_ID_FM_RX, + .channel_mode = 2, + .sd_lines = MI2S_SD_3, + .route = NULL, + .deroute = NULL, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_snddev_mi2s_fm_rx_device = { + .name = "snddev_mi2s", + .id = 2, + .dev = { .platform_data = &snddev_mi2s_fm_rx_data}, +}; + +static struct snddev_ecodec_data snddev_bt_sco_earpiece_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "bt_sco_rx", + .copp_id = 1, + .acdb_id = ACDB_ID_BT_SCO_SPKR, + .channel_mode = 1, + .conf_pcm_ctl_val = BT_SCO_PCM_CTL_VAL, + .conf_aux_codec_intf = BT_SCO_AUX_CODEC_INTF, + .conf_data_format_padding_val = BT_SCO_DATA_FORMAT_PADDING, + .max_voice_rx_vol[VOC_NB_INDEX] = 400, + .min_voice_rx_vol[VOC_NB_INDEX] = -1100, + .max_voice_rx_vol[VOC_WB_INDEX] = 400, + .min_voice_rx_vol[VOC_WB_INDEX] = -1100, +}; + +static struct snddev_ecodec_data snddev_bt_sco_mic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "bt_sco_tx", + .copp_id = 1, + .acdb_id = ACDB_ID_BT_SCO_MIC, + .channel_mode = 1, + .conf_pcm_ctl_val = BT_SCO_PCM_CTL_VAL, + .conf_aux_codec_intf = BT_SCO_AUX_CODEC_INTF, + .conf_data_format_padding_val = BT_SCO_DATA_FORMAT_PADDING, +}; + +static struct platform_device msm_bt_sco_earpiece_device = { + .name = "msm_snddev_ecodec", + .id = 0, + .dev = { .platform_data = &snddev_bt_sco_earpiece_data }, +}; + +static struct platform_device msm_bt_sco_mic_device = { + .name = "msm_snddev_ecodec", + .id = 1, + .dev = { .platform_data = &snddev_bt_sco_mic_data }, +}; + +static struct adie_codec_action_unit headset_ab_cpls_48KHz_osr256_actions[] = + HEADSET_AB_CPLS_48000_OSR_256; + +static struct adie_codec_hwsetting_entry headset_ab_cpls_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = headset_ab_cpls_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(headset_ab_cpls_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile headset_ab_cpls_profile = { + .path_type = ADIE_CODEC_RX, + .settings = headset_ab_cpls_settings, + .setting_sz = ARRAY_SIZE(headset_ab_cpls_settings), +}; + +static struct snddev_icodec_data snddev_ihs_stereo_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_stereo_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_SPKR_STEREO, + .profile = &headset_ab_cpls_profile, + .channel_mode = 2, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, + .property = SIDE_TONE_MASK, + .voltage_on = msm_snddev_hsed_voltage_on, + .voltage_off = msm_snddev_hsed_voltage_off, + .max_voice_rx_vol[VOC_NB_INDEX] = -700, + .min_voice_rx_vol[VOC_NB_INDEX] = -2200, + .max_voice_rx_vol[VOC_WB_INDEX] = -900, + .min_voice_rx_vol[VOC_WB_INDEX] = -2400, +}; + +static struct platform_device msm_headset_stereo_device = { + .name = "snddev_icodec", + .id = 2, + .dev = { .platform_data = &snddev_ihs_stereo_rx_data }, +}; + +/*debug FS interface is exposed to test Class D and class AB mode + * amplifers for headset device folloowing options are supported + * 0 -> settings will be restored + * 1 -> Cladd D mode is selected + * 2 -> Class AB mode is selected +*/ +#ifdef CONFIG_DEBUG_FS +static struct adie_codec_action_unit + ihs_stereo_rx_class_d_legacy_48KHz_osr256_actions[] = + HPH_PRI_D_LEG_STEREO; + +static struct adie_codec_hwsetting_entry + ihs_stereo_rx_class_d_legacy_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = + ihs_stereo_rx_class_d_legacy_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE + (ihs_stereo_rx_class_d_legacy_48KHz_osr256_actions), + } +}; + +static struct adie_codec_action_unit + ihs_stereo_rx_class_ab_legacy_48KHz_osr256_actions[] = + HPH_PRI_AB_LEG_STEREO; + +static struct adie_codec_hwsetting_entry + ihs_stereo_rx_class_ab_legacy_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = + ihs_stereo_rx_class_ab_legacy_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE + (ihs_stereo_rx_class_ab_legacy_48KHz_osr256_actions), + } +}; + +static void snddev_hsed_config_modify_setting(int type) +{ + struct platform_device *device; + struct snddev_icodec_data *icodec_data; + + device = &msm_headset_stereo_device; + icodec_data = (struct snddev_icodec_data *)device->dev.platform_data; + + if (icodec_data) { + if (type == 1) { + icodec_data->voltage_on = NULL; + icodec_data->voltage_off = NULL; + icodec_data->profile->settings = + ihs_stereo_rx_class_d_legacy_settings; + icodec_data->profile->setting_sz = + ARRAY_SIZE(ihs_stereo_rx_class_d_legacy_settings); + } else if (type == 2) { + icodec_data->voltage_on = NULL; + icodec_data->voltage_off = NULL; + icodec_data->profile->settings = + ihs_stereo_rx_class_ab_legacy_settings; + icodec_data->profile->setting_sz = + ARRAY_SIZE(ihs_stereo_rx_class_ab_legacy_settings); + } + } +} + +static void snddev_hsed_config_restore_setting(void) +{ + struct platform_device *device; + struct snddev_icodec_data *icodec_data; + + device = &msm_headset_stereo_device; + icodec_data = device->dev.platform_data; + + if (icodec_data) { + icodec_data->voltage_on = msm_snddev_hsed_voltage_on; + icodec_data->voltage_off = msm_snddev_hsed_voltage_off; + icodec_data->profile->settings = headset_ab_cpls_settings; + icodec_data->profile->setting_sz = + ARRAY_SIZE(headset_ab_cpls_settings); + } +} + +static ssize_t snddev_hsed_config_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *lb_str = filp->private_data; + char cmd; + + if (get_user(cmd, ubuf)) + return -EFAULT; + + if (!strcmp(lb_str, "msm_hsed_config")) { + switch (cmd) { + case '0': + snddev_hsed_config_restore_setting(); + break; + + case '1': + snddev_hsed_config_modify_setting(1); + break; + + case '2': + snddev_hsed_config_modify_setting(2); + break; + + default: + break; + } + } + return cnt; +} + +static int snddev_hsed_config_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations snddev_hsed_config_debug_fops = { + .open = snddev_hsed_config_debug_open, + .write = snddev_hsed_config_debug_write +}; +#endif + +static enum hsed_controller ispk_pmctl_id[] = {PM_HSED_CONTROLLER_0}; + +static struct snddev_icodec_data snddev_ispkr_mic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_mono_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_MIC, + .profile = &imic_ffa_profile, + .channel_mode = 1, + .pmctl_id = ispk_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(ispk_pmctl_id), + .default_sample_rate = 48000, + .pamp_on = msm_snddev_tx_route_config, + .pamp_off = msm_snddev_tx_route_deconfig, +}; + +static struct platform_device msm_ispkr_mic_device = { + .name = "snddev_icodec", + .id = 18, + .dev = { .platform_data = &snddev_ispkr_mic_data }, +}; + +static struct adie_codec_action_unit idual_mic_endfire_8KHz_osr256_actions[] = + AMIC_DUAL_8000_OSR_256; + +static struct adie_codec_hwsetting_entry idual_mic_endfire_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = idual_mic_endfire_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_endfire_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16KHz */ + { + .freq_plan = 16000, + .osr = 256, + .actions = idual_mic_endfire_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_endfire_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 48KHz */ + { + .freq_plan = 48000, + .osr = 256, + .actions = idual_mic_endfire_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_endfire_8KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile idual_mic_endfire_profile = { + .path_type = ADIE_CODEC_TX, + .settings = idual_mic_endfire_settings, + .setting_sz = ARRAY_SIZE(idual_mic_endfire_settings), +}; + +static enum hsed_controller idual_mic_endfire_pmctl_id[] = { + PM_HSED_CONTROLLER_0, PM_HSED_CONTROLLER_2 +}; + +static struct snddev_icodec_data snddev_idual_mic_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_endfire_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_MIC_ENDFIRE, + .profile = &idual_mic_endfire_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_endfire_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_endfire_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_idual_mic_endfire_device = { + .name = "snddev_icodec", + .id = 12, + .dev = { .platform_data = &snddev_idual_mic_endfire_data }, +}; + +static struct snddev_icodec_data snddev_spk_idual_mic_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_dual_mic_endfire_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_MIC_ENDFIRE, + .profile = &idual_mic_endfire_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_endfire_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_endfire_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_spk_idual_mic_endfire_device = { + .name = "snddev_icodec", + .id = 14, + .dev = { .platform_data = &snddev_spk_idual_mic_endfire_data }, +}; + +static struct adie_codec_action_unit itty_mono_tx_actions[] = + TTY_HEADSET_MONO_TX_8000_OSR_256; + +static struct adie_codec_hwsetting_entry itty_mono_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = itty_mono_tx_actions, + .action_sz = ARRAY_SIZE(itty_mono_tx_actions), + }, +}; + +static struct adie_codec_dev_profile itty_mono_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = itty_mono_tx_settings, + .setting_sz = ARRAY_SIZE(itty_mono_tx_settings), +}; + +static struct snddev_icodec_data snddev_itty_mono_tx_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE | SNDDEV_CAP_TTY), + .name = "tty_headset_mono_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_TTY_HEADSET_MIC, + .profile = &itty_mono_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pmctl_id = NULL, + .pmctl_id_sz = 0, + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_itty_mono_tx_device = { + .name = "snddev_icodec", + .id = 16, + .dev = { .platform_data = &snddev_itty_mono_tx_data }, +}; + +static struct adie_codec_action_unit itty_mono_rx_actions[] = + TTY_HEADSET_MONO_RX_8000_OSR_256; + +static struct adie_codec_hwsetting_entry itty_mono_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = itty_mono_rx_actions, + .action_sz = ARRAY_SIZE(itty_mono_rx_actions), + }, +}; + +static struct adie_codec_dev_profile itty_mono_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = itty_mono_rx_settings, + .setting_sz = ARRAY_SIZE(itty_mono_rx_settings), +}; + +static struct snddev_icodec_data snddev_itty_mono_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE | SNDDEV_CAP_TTY), + .name = "tty_headset_mono_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_TTY_HEADSET_SPKR, + .profile = &itty_mono_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = NULL, + .pamp_off = NULL, + .max_voice_rx_vol[VOC_NB_INDEX] = 0, + .min_voice_rx_vol[VOC_NB_INDEX] = 0, + .max_voice_rx_vol[VOC_WB_INDEX] = 0, + .min_voice_rx_vol[VOC_WB_INDEX] = 0, +}; + +static struct platform_device msm_itty_mono_rx_device = { + .name = "snddev_icodec", + .id = 17, + .dev = { .platform_data = &snddev_itty_mono_rx_data }, +}; + +static struct snddev_virtual_data snddev_a2dp_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "a2dp_tx", + .copp_id = 5, + .acdb_id = PSEUDO_ACDB_ID, +}; + +static struct snddev_virtual_data snddev_a2dp_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "a2dp_rx", + .copp_id = 2, + .acdb_id = PSEUDO_ACDB_ID, +}; + +static struct platform_device msm_a2dp_rx_device = { + .name = "snddev_virtual", + .id = 0, + .dev = { .platform_data = &snddev_a2dp_rx_data }, +}; + +static struct platform_device msm_a2dp_tx_device = { + .name = "snddev_virtual", + .id = 1, + .dev = { .platform_data = &snddev_a2dp_tx_data }, +}; + +static struct snddev_virtual_data snddev_uplink_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "uplink_rx", + .copp_id = 5, + .acdb_id = PSEUDO_ACDB_ID, +}; + +static struct platform_device msm_uplink_rx_device = { + .name = "snddev_virtual", + .id = 2, + .dev = { .platform_data = &snddev_uplink_rx_data }, +}; + +static struct snddev_icodec_data\ + snddev_idual_mic_endfire_real_stereo_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_endfire_tx_real_stereo", + .copp_id = 0, + .acdb_id = PSEUDO_ACDB_ID, + .profile = &idual_mic_endfire_profile, + .channel_mode = REAL_STEREO_CHANNEL_MODE, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_endfire_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_endfire_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_real_stereo_tx_device = { + .name = "snddev_icodec", + .id = 26, + .dev = { .platform_data = + &snddev_idual_mic_endfire_real_stereo_data }, +}; + +static struct adie_codec_action_unit ihs_ffa_mono_rx_48KHz_osr256_actions[] = + HEADSET_RX_CAPLESS_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ihs_ffa_mono_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ihs_ffa_mono_rx_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ihs_ffa_mono_rx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ihs_ffa_mono_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ihs_ffa_mono_rx_settings, + .setting_sz = ARRAY_SIZE(ihs_ffa_mono_rx_settings), +}; + +static struct snddev_icodec_data snddev_ihs_ffa_mono_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_mono_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_SPKR_MONO, + .profile = &ihs_ffa_mono_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_hsed_voltage_on, + .pamp_off = msm_snddev_hsed_voltage_off, + .max_voice_rx_vol[VOC_NB_INDEX] = -700, + .min_voice_rx_vol[VOC_NB_INDEX] = -2200, + .max_voice_rx_vol[VOC_WB_INDEX] = -900, + .min_voice_rx_vol[VOC_WB_INDEX] = -2400, + .property = SIDE_TONE_MASK, +}; + +static struct platform_device msm_ihs_ffa_mono_rx_device = { + .name = "snddev_icodec", + .id = 5, + .dev = { .platform_data = &snddev_ihs_ffa_mono_rx_data }, +}; + +static struct adie_codec_action_unit + ihs_stereo_speaker_stereo_rx_48KHz_osr256_actions[] = + HEADSET_STEREO_SPEAKER_STEREO_RX_CAPLESS_48000_OSR_256; + + +static struct adie_codec_hwsetting_entry + ihs_stereo_speaker_stereo_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ihs_stereo_speaker_stereo_rx_48KHz_osr256_actions, + .action_sz = + ARRAY_SIZE(ihs_stereo_speaker_stereo_rx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ihs_stereo_speaker_stereo_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ihs_stereo_speaker_stereo_rx_settings, + .setting_sz = ARRAY_SIZE(ihs_stereo_speaker_stereo_rx_settings), +}; + +static struct snddev_icodec_data snddev_ihs_stereo_speaker_stereo_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_stereo_speaker_stereo_rx", + .copp_id = 0, + .acdb_id = ACDB_ID_HEADSET_STEREO_PLUS_SPKR_STEREO_RX, + .profile = &ihs_stereo_speaker_stereo_rx_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .voltage_on = msm_snddev_hsed_voltage_on, + .voltage_off = msm_snddev_hsed_voltage_off, + .max_voice_rx_vol[VOC_NB_INDEX] = -500, + .min_voice_rx_vol[VOC_NB_INDEX] = -2000, + .max_voice_rx_vol[VOC_WB_INDEX] = -900, + .min_voice_rx_vol[VOC_WB_INDEX] = -2400, +}; + +static struct platform_device msm_ihs_stereo_speaker_stereo_rx_device = { + .name = "snddev_icodec", + .id = 21, + .dev = { .platform_data = &snddev_ihs_stereo_speaker_stereo_rx_data }, +}; + +static struct adie_codec_action_unit ispk_dual_mic_bs_8KHz_osr256_actions[] = + HS_DMIC2_STEREO_8000_OSR_256; + +static struct adie_codec_hwsetting_entry ispk_dual_mic_bs_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = ispk_dual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispk_dual_mic_bs_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16Khz */ + { + .freq_plan = 16000, + .osr = 256, + .actions = ispk_dual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispk_dual_mic_bs_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 48KHz */ + { + .freq_plan = 48000, + .osr = 256, + .actions = ispk_dual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispk_dual_mic_bs_8KHz_osr256_actions), + }, +}; + +static enum hsed_controller idual_mic_broadside_pmctl_id[] = { + PM_HSED_CONTROLLER_0, PM_HSED_CONTROLLER_2 +}; + +static struct adie_codec_dev_profile ispk_dual_mic_bs_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ispk_dual_mic_bs_settings, + .setting_sz = ARRAY_SIZE(ispk_dual_mic_bs_settings), +}; +static struct snddev_icodec_data snddev_spk_idual_mic_broadside_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_dual_mic_broadside_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_SPKR_PHONE_MIC_BROADSIDE, + .profile = &ispk_dual_mic_bs_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_broadside_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_broadside_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_spk_idual_mic_broadside_device = { + .name = "snddev_icodec", + .id = 15, + .dev = { .platform_data = &snddev_spk_idual_mic_broadside_data }, +}; + +static struct adie_codec_action_unit idual_mic_bs_8KHz_osr256_actions[] = + HS_DMIC2_STEREO_8000_OSR_256; + +static struct adie_codec_hwsetting_entry idual_mic_broadside_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = idual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_bs_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16KHz */ + { + .freq_plan = 16000, + .osr = 256, + .actions = idual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_bs_8KHz_osr256_actions), + }, /* 8KHz profile can be used for 16KHz */ + { + .freq_plan = 48000, + .osr = 256, + .actions = idual_mic_bs_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idual_mic_bs_8KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile idual_mic_broadside_profile = { + .path_type = ADIE_CODEC_TX, + .settings = idual_mic_broadside_settings, + .setting_sz = ARRAY_SIZE(idual_mic_broadside_settings), +}; + +static struct snddev_icodec_data snddev_idual_mic_broadside_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_broadside_tx", + .copp_id = 0, + .acdb_id = ACDB_ID_HANDSET_MIC_BROADSIDE, + .profile = &idual_mic_broadside_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pmctl_id = idual_mic_broadside_pmctl_id, + .pmctl_id_sz = ARRAY_SIZE(idual_mic_broadside_pmctl_id), + .pamp_on = NULL, + .pamp_off = NULL, +}; + +static struct platform_device msm_idual_mic_broadside_device = { + .name = "snddev_icodec", + .id = 13, + .dev = { .platform_data = &snddev_idual_mic_broadside_data }, +}; + +static struct snddev_mi2s_data snddev_mi2s_stereo_rx_data = { + .capability = SNDDEV_CAP_RX , + .name = "hdmi_stereo_rx", + .copp_id = 3, + .acdb_id = ACDB_ID_HDMI, + .channel_mode = 2, + .sd_lines = MI2S_SD_0, + .route = msm_snddev_tx_route_config, + .deroute = msm_snddev_tx_route_deconfig, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_snddev_mi2s_stereo_rx_device = { + .name = "snddev_mi2s", + .id = 0, + .dev = { .platform_data = &snddev_mi2s_stereo_rx_data }, +}; + +static struct adie_codec_action_unit auxpga_lb_lo_actions[] = + LB_AUXPGA_LO_STEREO; + +static struct adie_codec_hwsetting_entry auxpga_lb_lo_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = auxpga_lb_lo_actions, + .action_sz = ARRAY_SIZE(auxpga_lb_lo_actions), + }, +}; + +static struct adie_codec_dev_profile auxpga_lb_lo_profile = { + .path_type = ADIE_CODEC_LB, + .settings = auxpga_lb_lo_settings, + .setting_sz = ARRAY_SIZE(auxpga_lb_lo_settings), +}; + +static struct snddev_icodec_data snddev_auxpga_lb_lo_data = { + .capability = SNDDEV_CAP_LB, + .name = "auxpga_loopback_lo", + .copp_id = 0, + .acdb_id = PSEUDO_ACDB_ID, + .profile = &auxpga_lb_lo_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_ANALOG, +}; + +static struct platform_device msm_auxpga_lb_lo_device = { + .name = "snddev_icodec", + .id = 27, + .dev = { .platform_data = &snddev_auxpga_lb_lo_data }, +}; + +static struct adie_codec_action_unit auxpga_lb_hs_actions[] = + LB_AUXPGA_HPH_AB_CPLS_STEREO; + +static struct adie_codec_hwsetting_entry auxpga_lb_hs_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = auxpga_lb_hs_actions, + .action_sz = ARRAY_SIZE(auxpga_lb_hs_actions), + }, +}; + +static struct adie_codec_dev_profile auxpga_lb_hs_profile = { + .path_type = ADIE_CODEC_LB, + .settings = auxpga_lb_hs_settings, + .setting_sz = ARRAY_SIZE(auxpga_lb_hs_settings), +}; + +static struct snddev_icodec_data snddev_auxpga_lb_hs_data = { + .capability = SNDDEV_CAP_LB, + .name = "auxpga_loopback_hs", + .copp_id = 0, + .acdb_id = PSEUDO_ACDB_ID, + .profile = &auxpga_lb_hs_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_hsed_voltage_on, + .voltage_off = msm_snddev_hsed_voltage_off, + .dev_vol_type = SNDDEV_DEV_VOL_ANALOG, +}; + +static struct platform_device msm_auxpga_lb_hs_device = { + .name = "snddev_icodec", + .id = 25, + .dev = { .platform_data = &snddev_auxpga_lb_hs_data }, +}; + +static struct platform_device *snd_devices_ffa[] __initdata = { + &msm_iearpiece_ffa_device, + &msm_imic_ffa_device, + &msm_ispkr_stereo_device, + &msm_headset_mic_device, + &msm_ihs_ffa_mono_rx_device, + &msm_snddev_mi2s_fm_rx_device, + &msm_snddev_mi2s_fm_tx_device, + &msm_bt_sco_earpiece_device, + &msm_bt_sco_mic_device, + &msm_ispkr_mic_device, + &msm_headset_stereo_device, + &msm_idual_mic_endfire_device, + &msm_spk_idual_mic_endfire_device, + &msm_itty_mono_tx_device, + &msm_itty_mono_rx_device, + &msm_a2dp_rx_device, + &msm_a2dp_tx_device, + &msm_uplink_rx_device, + &msm_real_stereo_tx_device, + &msm_ihs_stereo_speaker_stereo_rx_device, + &msm_spk_idual_mic_broadside_device, + &msm_idual_mic_broadside_device, + &msm_snddev_mi2s_stereo_rx_device, + &msm_auxpga_lb_hs_device, + &msm_auxpga_lb_lo_device, +}; + +void __ref msm_snddev_init_timpani(void) +{ + platform_add_devices(snd_devices_ffa, + ARRAY_SIZE(snd_devices_ffa)); +#ifdef CONFIG_DEBUG_FS + debugfs_hsed_config = debugfs_create_file("msm_hsed_config", + S_IFREG | S_IWUGO, NULL, + (void *) "msm_hsed_config", &snddev_hsed_config_debug_fops); + if (!debugfs_hsed_config) + pr_err("failed to create msm_head_config debug fs entry\n"); +#endif + +} diff --git a/arch/arm/mach-msm/qdsp5v2/snddev_ecodec.c b/arch/arm/mach-msm/qdsp5v2/snddev_ecodec.c new file mode 100644 index 00000000000..a5da912b29b --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/snddev_ecodec.c @@ -0,0 +1,484 @@ +/* Copyright (c) 2009,2011 Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Context for each external codec device */ +struct snddev_ecodec_state { + struct snddev_ecodec_data *data; + u32 sample_rate; + bool enabled; +}; + +/* Global state for the driver */ +struct snddev_ecodec_drv_state { + struct mutex dev_lock; + u32 rx_active; /* ensure one rx device at a time */ + u32 tx_active; /* ensure one tx device at a time */ + struct clk *lpa_core_clk; + struct clk *ecodec_clk; +}; + +#define ADSP_CTL 1 + +static struct snddev_ecodec_drv_state snddev_ecodec_drv; + +static int snddev_ecodec_open_rx(struct snddev_ecodec_state *ecodec) +{ + int rc = 0; + struct snddev_ecodec_drv_state *drv = &snddev_ecodec_drv; + struct msm_afe_config afe_config; + int ret = 0; + + MM_DBG("snddev_ecodec_open_rx\n"); + + if (!drv->tx_active) { + /* request GPIO */ + rc = aux_pcm_gpios_request(); + if (rc) { + MM_ERR("GPIO enable failed\n"); + goto done; + } + /* config clocks */ + clk_enable(drv->lpa_core_clk); + + /*if long sync is selected in aux PCM interface + ecodec clock is updated to work with 128KHz, + if short sync is selected ecodec clock is updated to + work with 2.048MHz frequency, actual clock output is + different than the SW configuration by factor of two*/ + if (!(ecodec->data->conf_aux_codec_intf & + AUX_CODEC_CTL__AUX_CODEC_MODE__I2S_V)) { + if (ecodec->data->conf_aux_codec_intf & + AUX_CODEC_CTL__AUX_PCM_MODE__AUX_MASTER_V) { + MM_DBG("Update ecodec clock to 128 KHz, long " + "sync in master mode is selected\n"); + ret = clk_set_rate(drv->ecodec_clk, 256000); + if (ret < 0) + MM_ERR("Error updating ecodec clock" + " to 128KHz\n"); + } else if (ecodec->data->conf_aux_codec_intf & + AUX_CODEC_CTL__AUX_PCM_MODE__PRIM_SLAVE_V) { + MM_DBG("Update ecodec clock to 2 MHz, short" + " sync in slave mode is selected\n"); + ret = clk_set_rate(drv->ecodec_clk, 4096000); + if (ret < 0) + MM_ERR("Error updating ecodec clock" + " to 2.048MHz\n"); + } else { + MM_DBG("Update ecodec clock to 2 MHz, short" + " sync in master mode is selected\n"); + ret = clk_set_rate(drv->ecodec_clk, 4096000); + if (ret < 0) + MM_ERR("Error updating ecodec clock" + " to 2.048MHz\n"); + } + } + + /* enable ecodec clk */ + clk_enable(drv->ecodec_clk); + + /* let ADSP confiure AUX PCM regs */ + aux_codec_adsp_codec_ctl_en(ADSP_CTL); + + /* let adsp configure pcm path */ + aux_codec_pcm_path_ctl_en(ADSP_CTL); + + /* choose ADSP_A */ + audio_interct_aux_regsel(AUDIO_ADSP_A); + audio_interct_tpcm_source(AUDIO_ADSP_A); + audio_interct_rpcm_source(AUDIO_ADSP_A); + + clk_disable(drv->lpa_core_clk); + + /* send AUX_CODEC_CONFIG to AFE */ + rc = afe_config_aux_codec(ecodec->data->conf_pcm_ctl_val, + ecodec->data->conf_aux_codec_intf, + ecodec->data->conf_data_format_padding_val); + if (IS_ERR_VALUE(rc)) + goto error; + } + /* send CODEC CONFIG to AFE */ + afe_config.sample_rate = ecodec->sample_rate / 1000; + afe_config.channel_mode = ecodec->data->channel_mode; + afe_config.volume = AFE_VOLUME_UNITY; + rc = afe_enable(AFE_HW_PATH_AUXPCM_RX, &afe_config); + if (IS_ERR_VALUE(rc)) { + if (!drv->tx_active) { + aux_pcm_gpios_free(); + clk_disable(drv->ecodec_clk); + } + goto done; + } + + ecodec->enabled = 1; + return 0; + +error: + aux_pcm_gpios_free(); + clk_disable(drv->ecodec_clk); +done: + return rc; +} + +static int snddev_ecodec_close_rx(struct snddev_ecodec_state *ecodec) +{ + struct snddev_ecodec_drv_state *drv = &snddev_ecodec_drv; + + /* free GPIO */ + if (!drv->tx_active) { + aux_pcm_gpios_free(); + clk_disable(drv->ecodec_clk); + } + + /* disable AFE */ + afe_disable(AFE_HW_PATH_AUXPCM_RX); + + ecodec->enabled = 0; + + return 0; +} + +static int snddev_ecodec_open_tx(struct snddev_ecodec_state *ecodec) +{ + int rc = 0; + struct snddev_ecodec_drv_state *drv = &snddev_ecodec_drv; + struct msm_afe_config afe_config; + int ret = 0; + + MM_DBG("snddev_ecodec_open_tx\n"); + + /* request GPIO */ + if (!drv->rx_active) { + rc = aux_pcm_gpios_request(); + if (rc) { + MM_ERR("GPIO enable failed\n"); + goto done; + } + /* config clocks */ + clk_enable(drv->lpa_core_clk); + + /*if long sync is selected in aux PCM interface + ecodec clock is updated to work with 128KHz, + if short sync is selected ecodec clock is updated to + work with 2.048MHz frequency, actual clock output is + different than the SW configuration by factor of two*/ + if (!(ecodec->data->conf_aux_codec_intf & + AUX_CODEC_CTL__AUX_CODEC_MODE__I2S_V)) { + if (ecodec->data->conf_aux_codec_intf & + AUX_CODEC_CTL__AUX_PCM_MODE__AUX_MASTER_V) { + MM_DBG("Update ecodec clock to 128 KHz, long " + "sync in master mode is selected\n"); + ret = clk_set_rate(drv->ecodec_clk, 256000); + if (ret < 0) + MM_ERR("Error updating ecodec clock" + " to 128KHz\n"); + } else if (ecodec->data->conf_aux_codec_intf & + AUX_CODEC_CTL__AUX_PCM_MODE__PRIM_SLAVE_V) { + MM_DBG("Update ecodec clock to 2 MHz, short" + " sync in slave mode is selected\n"); + ret = clk_set_rate(drv->ecodec_clk, 4096000); + if (ret < 0) + MM_ERR("Error updating ecodec clock" + " to 2.048MHz\n"); + } else { + MM_DBG("Update ecodec clock to 2 MHz, short" + " sync in master mode is selected\n"); + ret = clk_set_rate(drv->ecodec_clk, 4096000); + if (ret < 0) + MM_ERR("Error updating ecodec clock" + " to 2.048MHz\n"); + } + } + + /* enable ecodec clk */ + clk_enable(drv->ecodec_clk); + + /* let ADSP confiure AUX PCM regs */ + aux_codec_adsp_codec_ctl_en(ADSP_CTL); + + /* let adsp configure pcm path */ + aux_codec_pcm_path_ctl_en(ADSP_CTL); + + /* choose ADSP_A */ + audio_interct_aux_regsel(AUDIO_ADSP_A); + audio_interct_tpcm_source(AUDIO_ADSP_A); + audio_interct_rpcm_source(AUDIO_ADSP_A); + + clk_disable(drv->lpa_core_clk); + + /* send AUX_CODEC_CONFIG to AFE */ + rc = afe_config_aux_codec(ecodec->data->conf_pcm_ctl_val, + ecodec->data->conf_aux_codec_intf, + ecodec->data->conf_data_format_padding_val); + if (IS_ERR_VALUE(rc)) + goto error; + } + /* send CODEC CONFIG to AFE */ + afe_config.sample_rate = ecodec->sample_rate / 1000; + afe_config.channel_mode = ecodec->data->channel_mode; + afe_config.volume = AFE_VOLUME_UNITY; + rc = afe_enable(AFE_HW_PATH_AUXPCM_TX, &afe_config); + if (IS_ERR_VALUE(rc)) { + if (!drv->rx_active) { + aux_pcm_gpios_free(); + clk_disable(drv->ecodec_clk); + } + goto done; + } + + ecodec->enabled = 1; + return 0; + +error: + clk_disable(drv->ecodec_clk); + aux_pcm_gpios_free(); +done: + return rc; +} + +static int snddev_ecodec_close_tx(struct snddev_ecodec_state *ecodec) +{ + struct snddev_ecodec_drv_state *drv = &snddev_ecodec_drv; + + /* free GPIO */ + if (!drv->rx_active) { + aux_pcm_gpios_free(); + clk_disable(drv->ecodec_clk); + } + + /* disable AFE */ + afe_disable(AFE_HW_PATH_AUXPCM_TX); + + ecodec->enabled = 0; + + return 0; +} + + +static int snddev_ecodec_open(struct msm_snddev_info *dev_info) +{ + int rc = 0; + struct snddev_ecodec_state *ecodec; + struct snddev_ecodec_drv_state *drv = &snddev_ecodec_drv; + + if (!dev_info) { + rc = -EINVAL; + goto error; + } + + ecodec = dev_info->private_data; + + if (ecodec->data->capability & SNDDEV_CAP_RX) { + mutex_lock(&drv->dev_lock); + if (drv->rx_active) { + mutex_unlock(&drv->dev_lock); + rc = -EBUSY; + goto error; + } + rc = snddev_ecodec_open_rx(ecodec); + if (!IS_ERR_VALUE(rc)) + drv->rx_active = 1; + mutex_unlock(&drv->dev_lock); + } else { + mutex_lock(&drv->dev_lock); + if (drv->tx_active) { + mutex_unlock(&drv->dev_lock); + rc = -EBUSY; + goto error; + } + rc = snddev_ecodec_open_tx(ecodec); + if (!IS_ERR_VALUE(rc)) + drv->tx_active = 1; + mutex_unlock(&drv->dev_lock); + } +error: + return rc; +} + +static int snddev_ecodec_close(struct msm_snddev_info *dev_info) +{ + int rc = 0; + struct snddev_ecodec_state *ecodec; + struct snddev_ecodec_drv_state *drv = &snddev_ecodec_drv; + if (!dev_info) { + rc = -EINVAL; + goto error; + } + + ecodec = dev_info->private_data; + + if (ecodec->data->capability & SNDDEV_CAP_RX) { + mutex_lock(&drv->dev_lock); + if (!drv->rx_active) { + mutex_unlock(&drv->dev_lock); + rc = -EPERM; + goto error; + } + rc = snddev_ecodec_close_rx(ecodec); + if (!IS_ERR_VALUE(rc)) + drv->rx_active = 0; + mutex_unlock(&drv->dev_lock); + } else { + mutex_lock(&drv->dev_lock); + if (!drv->tx_active) { + mutex_unlock(&drv->dev_lock); + rc = -EPERM; + goto error; + } + rc = snddev_ecodec_close_tx(ecodec); + if (!IS_ERR_VALUE(rc)) + drv->tx_active = 0; + mutex_unlock(&drv->dev_lock); + } + +error: + return rc; +} + +static int snddev_ecodec_set_freq(struct msm_snddev_info *dev_info, u32 rate) +{ + int rc = 0; + + if (!dev_info) { + rc = -EINVAL; + goto error; + } + return 8000; + +error: + return rc; +} + +static int snddev_ecodec_probe(struct platform_device *pdev) +{ + int rc = 0, i; + struct snddev_ecodec_data *pdata; + struct msm_snddev_info *dev_info; + struct snddev_ecodec_state *ecodec; + + if (!pdev || !pdev->dev.platform_data) { + printk(KERN_ALERT "Invalid caller \n"); + rc = -1; + goto error; + } + pdata = pdev->dev.platform_data; + + ecodec = kzalloc(sizeof(struct snddev_ecodec_state), GFP_KERNEL); + if (!ecodec) { + rc = -ENOMEM; + goto error; + } + + dev_info = kzalloc(sizeof(struct msm_snddev_info), GFP_KERNEL); + if (!dev_info) { + kfree(ecodec); + rc = -ENOMEM; + goto error; + } + + dev_info->name = pdata->name; + dev_info->copp_id = pdata->copp_id; + dev_info->acdb_id = pdata->acdb_id; + dev_info->private_data = (void *) ecodec; + dev_info->dev_ops.open = snddev_ecodec_open; + dev_info->dev_ops.close = snddev_ecodec_close; + dev_info->dev_ops.set_freq = snddev_ecodec_set_freq; + dev_info->dev_ops.enable_sidetone = NULL; + dev_info->capability = pdata->capability; + dev_info->opened = 0; + + msm_snddev_register(dev_info); + ecodec->data = pdata; + ecodec->sample_rate = 8000; /* Default to 8KHz */ + if (pdata->capability & SNDDEV_CAP_RX) { + for (i = 0; i < VOC_RX_VOL_ARRAY_NUM; i++) { + dev_info->max_voc_rx_vol[i] = + pdata->max_voice_rx_vol[i]; + dev_info->min_voc_rx_vol[i] = + pdata->min_voice_rx_vol[i]; + } + } +error: + return rc; +} + +static int snddev_ecodec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver snddev_ecodec_driver = { + .probe = snddev_ecodec_probe, + .remove = snddev_ecodec_remove, + .driver = { .name = "msm_snddev_ecodec" } +}; + +static int __init snddev_ecodec_init(void) +{ + int rc = 0; + struct snddev_ecodec_drv_state *ecodec_drv = &snddev_ecodec_drv; + + MM_INFO("snddev_ecodec_init\n"); + rc = platform_driver_register(&snddev_ecodec_driver); + if (IS_ERR_VALUE(rc)) + goto error_platform_driver; + ecodec_drv->ecodec_clk = clk_get(NULL, "ecodec_clk"); + if (IS_ERR(ecodec_drv->ecodec_clk)) + goto error_ecodec_clk; + ecodec_drv->lpa_core_clk = clk_get(NULL, "lpa_core_clk"); + if (IS_ERR(ecodec_drv->lpa_core_clk)) + goto error_lpa_core_clk; + + + mutex_init(&ecodec_drv->dev_lock); + ecodec_drv->rx_active = 0; + ecodec_drv->tx_active = 0; + return 0; + +error_lpa_core_clk: + clk_put(ecodec_drv->ecodec_clk); +error_ecodec_clk: + platform_driver_unregister(&snddev_ecodec_driver); +error_platform_driver: + + MM_ERR("encounter error\n"); + return -ENODEV; +} + +static void __exit snddev_ecodec_exit(void) +{ + struct snddev_ecodec_drv_state *ecodec_drv = &snddev_ecodec_drv; + + platform_driver_unregister(&snddev_ecodec_driver); + clk_put(ecodec_drv->ecodec_clk); + + return; +} + +module_init(snddev_ecodec_init); +module_exit(snddev_ecodec_exit); + +MODULE_DESCRIPTION("ECodec Sound Device driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/snddev_icodec.c b/arch/arm/mach-msm/qdsp5v2/snddev_icodec.c new file mode 100644 index 00000000000..dbeda82afa2 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/snddev_icodec.c @@ -0,0 +1,1211 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SMPS_AUDIO_PLAYBACK_ID "AUPB" +#define SMPS_AUDIO_RECORD_ID "AURC" + +#define SNDDEV_ICODEC_PCM_SZ 32 /* 16 bit / sample stereo mode */ +#define SNDDEV_ICODEC_MUL_FACTOR 3 /* Multi by 8 Shift by 3 */ +#define SNDDEV_ICODEC_CLK_RATE(freq) \ + (((freq) * (SNDDEV_ICODEC_PCM_SZ)) << (SNDDEV_ICODEC_MUL_FACTOR)) + +#ifdef CONFIG_DEBUG_FS +static struct adie_codec_action_unit debug_rx_actions[] = + HANDSET_RX_8000_OSR_256; + +static struct adie_codec_action_unit debug_tx_lb_actions[] = { + { ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF }, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x01)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x80, 0x01, 0x00) }, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x8A, 0x30, 0x30)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x11, 0xfc, 0xfc)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x13, 0xfc, 0x58)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x14, 0xff, 0x65)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x15, 0xff, 0x64)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x82, 0xff, 0x5C)}, + { ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY }, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x0D, 0xF0, 0xd0)}, + { ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x14)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x86, 0xff, 0x00)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x8A, 0x50, 0x40)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x91, 0xFF, 0x01)}, /* Start loop back */ + { ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x8A, 0x10, 0x30)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x83, 0x14, 0x00)}, + { ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, + { ADIE_CODEC_ACTION_ENTRY, + ADIE_CODEC_PACK_ENTRY(0x11, 0xff, 0x00)} +}; + +static struct adie_codec_action_unit debug_tx_actions[] = + HANDSET_TX_8000_OSR_256; + +static struct adie_codec_hwsetting_entry debug_rx_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = debug_rx_actions, + .action_sz = ARRAY_SIZE(debug_rx_actions), + } +}; + +static struct adie_codec_hwsetting_entry debug_tx_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = debug_tx_actions, + .action_sz = ARRAY_SIZE(debug_tx_actions), + } +}; + +static struct adie_codec_hwsetting_entry debug_tx_lb_settings[] = { + { + .freq_plan = 8000, + .osr = 256, + .actions = debug_tx_lb_actions, + .action_sz = ARRAY_SIZE(debug_tx_lb_actions), + } +}; + +static struct adie_codec_dev_profile debug_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = debug_rx_settings, + .setting_sz = ARRAY_SIZE(debug_rx_settings), +}; + +static struct adie_codec_dev_profile debug_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = debug_tx_settings, + .setting_sz = ARRAY_SIZE(debug_tx_settings), +}; + +static struct adie_codec_dev_profile debug_tx_lb_profile = { + .path_type = ADIE_CODEC_TX, + .settings = debug_tx_lb_settings, + .setting_sz = ARRAY_SIZE(debug_tx_lb_settings), +}; +#endif /* CONFIG_DEBUG_FS */ + +/* Context for each internal codec sound device */ +struct snddev_icodec_state { + struct snddev_icodec_data *data; + struct adie_codec_path *adie_path; + u32 sample_rate; + u32 enabled; +}; + +/* Global state for the driver */ +struct snddev_icodec_drv_state { + struct mutex rx_lock; + struct mutex lb_lock; + struct mutex tx_lock; + u32 rx_active; /* ensure one rx device at a time */ + u32 tx_active; /* ensure one tx device at a time */ + struct clk *rx_mclk; + struct clk *rx_sclk; + struct clk *tx_mclk; + struct clk *tx_sclk; + struct clk *lpa_codec_clk; + struct clk *lpa_core_clk; + struct clk *lpa_p_clk; + struct lpa_drv *lpa; + + struct wake_lock rx_idlelock; + struct wake_lock tx_idlelock; +}; + +static struct snddev_icodec_drv_state snddev_icodec_drv; + +static int snddev_icodec_open_rx(struct snddev_icodec_state *icodec) +{ + int trc, err; + int smps_mode = PMAPP_SMPS_MODE_VOTE_PWM; + struct msm_afe_config afe_config; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + struct lpa_codec_config lpa_config; + + wake_lock(&drv->rx_idlelock); + + if ((icodec->data->acdb_id == ACDB_ID_HEADSET_SPKR_MONO) || + (icodec->data->acdb_id == ACDB_ID_HEADSET_SPKR_STEREO)) { + /* Vote PMAPP_SMPS_MODE_VOTE_PFM for headset */ + smps_mode = PMAPP_SMPS_MODE_VOTE_PFM; + MM_DBG("snddev_icodec_open_rx: PMAPP_SMPS_MODE_VOTE_PFM \n"); + } else + MM_DBG("snddev_icodec_open_rx: PMAPP_SMPS_MODE_VOTE_PWM \n"); + + /* Vote for SMPS mode*/ + err = pmapp_smps_mode_vote(SMPS_AUDIO_PLAYBACK_ID, + PMAPP_VREG_S4, smps_mode); + if (err != 0) + MM_ERR("pmapp_smps_mode_vote error %d\n", err); + + /* enable MI2S RX master block */ + /* enable MI2S RX bit clock */ + trc = clk_set_rate(drv->rx_mclk, + SNDDEV_ICODEC_CLK_RATE(icodec->sample_rate)); + if (IS_ERR_VALUE(trc)) + goto error_invalid_freq; + clk_enable(drv->rx_mclk); + clk_enable(drv->rx_sclk); + /* clk_set_rate(drv->lpa_codec_clk, 1); */ /* Remove if use pcom */ + clk_enable(drv->lpa_p_clk); + clk_enable(drv->lpa_codec_clk); + clk_enable(drv->lpa_core_clk); + + /* Enable LPA sub system + */ + drv->lpa = lpa_get(); + if (!drv->lpa) + goto error_lpa; + lpa_config.sample_rate = icodec->sample_rate; + lpa_config.sample_width = 16; + lpa_config.output_interface = LPA_OUTPUT_INTF_WB_CODEC; + lpa_config.num_channels = icodec->data->channel_mode; + lpa_cmd_codec_config(drv->lpa, &lpa_config); + + /* Set audio interconnect reg to LPA */ + audio_interct_codec(AUDIO_INTERCT_LPA); + + /* Set MI2S */ + mi2s_set_codec_output_path((icodec->data->channel_mode == 2 ? + MI2S_CHAN_STEREO : MI2S_CHAN_MONO_PACKED), WT_16_BIT); + + if (icodec->data->voltage_on) + icodec->data->voltage_on(); + + /* Configure ADIE */ + trc = adie_codec_open(icodec->data->profile, &icodec->adie_path); + if (IS_ERR_VALUE(trc)) + goto error_adie; + /* OSR default to 256, can be changed for power optimization + * If OSR is to be changed, need clock API for setting the divider + */ + adie_codec_setpath(icodec->adie_path, icodec->sample_rate, 256); + /* Start AFE */ + afe_config.sample_rate = icodec->sample_rate / 1000; + afe_config.channel_mode = icodec->data->channel_mode; + afe_config.volume = AFE_VOLUME_UNITY; + trc = afe_enable(AFE_HW_PATH_CODEC_RX, &afe_config); + if (IS_ERR_VALUE(trc)) + goto error_afe; + lpa_cmd_enable_codec(drv->lpa, 1); + /* Enable ADIE */ + adie_codec_proceed_stage(icodec->adie_path, ADIE_CODEC_DIGITAL_READY); + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_ANALOG_READY); + + /* Enable power amplifier */ + if (icodec->data->pamp_on) + icodec->data->pamp_on(); + + icodec->enabled = 1; + + wake_unlock(&drv->rx_idlelock); + return 0; + +error_afe: + adie_codec_close(icodec->adie_path); + icodec->adie_path = NULL; +error_adie: + lpa_put(drv->lpa); +error_lpa: + clk_disable(drv->lpa_p_clk); + clk_disable(drv->lpa_codec_clk); + clk_disable(drv->lpa_core_clk); + clk_disable(drv->rx_sclk); + clk_disable(drv->rx_mclk); +error_invalid_freq: + + MM_ERR("encounter error\n"); + + wake_unlock(&drv->rx_idlelock); + return -ENODEV; +} + +static int snddev_icodec_open_tx(struct snddev_icodec_state *icodec) +{ + int trc; + int i, err; + struct msm_afe_config afe_config; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;; + + wake_lock(&drv->tx_idlelock); + + /* Vote for PWM mode*/ + err = pmapp_smps_mode_vote(SMPS_AUDIO_RECORD_ID, + PMAPP_VREG_S4, PMAPP_SMPS_MODE_VOTE_PWM); + if (err != 0) + MM_ERR("pmapp_smps_mode_vote error %d\n", err); + + /* Reuse pamp_on for TX platform-specific setup */ + if (icodec->data->pamp_on) + icodec->data->pamp_on(); + + for (i = 0; i < icodec->data->pmctl_id_sz; i++) { + pmic_hsed_enable(icodec->data->pmctl_id[i], + PM_HSED_ENABLE_PWM_TCXO); + } + + /* enable MI2S TX master block */ + /* enable MI2S TX bit clock */ + trc = clk_set_rate(drv->tx_mclk, + SNDDEV_ICODEC_CLK_RATE(icodec->sample_rate)); + if (IS_ERR_VALUE(trc)) + goto error_invalid_freq; + clk_enable(drv->tx_mclk); + clk_enable(drv->tx_sclk); + + /* Set MI2S */ + mi2s_set_codec_input_path((icodec->data->channel_mode == + REAL_STEREO_CHANNEL_MODE ? MI2S_CHAN_STEREO : + (icodec->data->channel_mode == 2 ? + MI2S_CHAN_STEREO : MI2S_CHAN_MONO_RAW)), + WT_16_BIT); + /* Configure ADIE */ + trc = adie_codec_open(icodec->data->profile, &icodec->adie_path); + if (IS_ERR_VALUE(trc)) + goto error_adie; + /* Enable ADIE */ + adie_codec_setpath(icodec->adie_path, icodec->sample_rate, 256); + adie_codec_proceed_stage(icodec->adie_path, ADIE_CODEC_DIGITAL_READY); + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_ANALOG_READY); + + /* Start AFE */ + afe_config.sample_rate = icodec->sample_rate / 1000; + afe_config.channel_mode = icodec->data->channel_mode; + afe_config.volume = AFE_VOLUME_UNITY; + trc = afe_enable(AFE_HW_PATH_CODEC_TX, &afe_config); + if (IS_ERR_VALUE(trc)) + goto error_afe; + + + icodec->enabled = 1; + + wake_unlock(&drv->tx_idlelock); + return 0; + +error_afe: + adie_codec_close(icodec->adie_path); + icodec->adie_path = NULL; +error_adie: + clk_disable(drv->tx_sclk); + clk_disable(drv->tx_mclk); +error_invalid_freq: + + /* Disable mic bias */ + for (i = 0; i < icodec->data->pmctl_id_sz; i++) { + pmic_hsed_enable(icodec->data->pmctl_id[i], + PM_HSED_ENABLE_OFF); + } + + if (icodec->data->pamp_off) + icodec->data->pamp_off(); + + MM_ERR("encounter error\n"); + + wake_unlock(&drv->tx_idlelock); + return -ENODEV; +} + +static int snddev_icodec_close_lb(struct snddev_icodec_state *icodec) +{ + /* Disable power amplifier */ + if (icodec->data->pamp_off) + icodec->data->pamp_off(); + + if (icodec->adie_path) { + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(icodec->adie_path); + icodec->adie_path = NULL; + } + if (icodec->data->voltage_off) + icodec->data->voltage_off(); + + return 0; +} + +static int snddev_icodec_close_rx(struct snddev_icodec_state *icodec) +{ + int err; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + wake_lock(&drv->rx_idlelock); + + /* Remove the vote for SMPS mode*/ + err = pmapp_smps_mode_vote(SMPS_AUDIO_PLAYBACK_ID, + PMAPP_VREG_S4, PMAPP_SMPS_MODE_VOTE_DONTCARE); + if (err != 0) + MM_ERR("pmapp_smps_mode_vote error %d\n", err); + + /* Disable power amplifier */ + if (icodec->data->pamp_off) + icodec->data->pamp_off(); + + /* Disable ADIE */ + adie_codec_proceed_stage(icodec->adie_path, ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(icodec->adie_path); + icodec->adie_path = NULL; + + afe_disable(AFE_HW_PATH_CODEC_RX); + + if (icodec->data->voltage_off) + icodec->data->voltage_off(); + + /* Disable LPA Sub system */ + lpa_cmd_enable_codec(drv->lpa, 0); + lpa_put(drv->lpa); + + /* Disable LPA clocks */ + clk_disable(drv->lpa_p_clk); + clk_disable(drv->lpa_codec_clk); + clk_disable(drv->lpa_core_clk); + + /* Disable MI2S RX master block */ + /* Disable MI2S RX bit clock */ + clk_disable(drv->rx_sclk); + clk_disable(drv->rx_mclk); + + icodec->enabled = 0; + + wake_unlock(&drv->rx_idlelock); + return 0; +} + +static int snddev_icodec_close_tx(struct snddev_icodec_state *icodec) +{ + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + int i, err; + + wake_lock(&drv->tx_idlelock); + + /* Remove the vote for SMPS mode*/ + err = pmapp_smps_mode_vote(SMPS_AUDIO_RECORD_ID, + PMAPP_VREG_S4, PMAPP_SMPS_MODE_VOTE_DONTCARE); + if (err != 0) + MM_ERR("pmapp_smps_mode_vote error %d\n", err); + + afe_disable(AFE_HW_PATH_CODEC_TX); + + /* Disable ADIE */ + adie_codec_proceed_stage(icodec->adie_path, ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(icodec->adie_path); + icodec->adie_path = NULL; + + /* Disable MI2S TX master block */ + /* Disable MI2S TX bit clock */ + clk_disable(drv->tx_sclk); + clk_disable(drv->tx_mclk); + + /* Disable mic bias */ + for (i = 0; i < icodec->data->pmctl_id_sz; i++) { + pmic_hsed_enable(icodec->data->pmctl_id[i], + PM_HSED_ENABLE_OFF); + } + + /* Reuse pamp_off for TX platform-specific setup */ + if (icodec->data->pamp_off) + icodec->data->pamp_off(); + + icodec->enabled = 0; + + wake_unlock(&drv->tx_idlelock); + return 0; +} + +static int snddev_icodec_open_lb(struct snddev_icodec_state *icodec) +{ + int trc; + trc = adie_codec_open(icodec->data->profile, &icodec->adie_path); + if (IS_ERR_VALUE(trc)) + pr_err("%s: adie codec open failed\n", __func__); + else + adie_codec_setpath(icodec->adie_path, + icodec->sample_rate, 256); + + if (icodec->adie_path) + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_ANALOG_READY); + if (icodec->data->pamp_on) + icodec->data->pamp_on(); + + icodec->enabled = 1; + return 0; +} + +static int snddev_icodec_set_device_volume_impl( + struct msm_snddev_info *dev_info, u32 volume) +{ + struct snddev_icodec_state *icodec; + u8 afe_path_id; + + int rc = 0; + + icodec = dev_info->private_data; + + if (icodec->data->capability & SNDDEV_CAP_RX) + afe_path_id = AFE_HW_PATH_CODEC_RX; + else + afe_path_id = AFE_HW_PATH_CODEC_TX; + + if (icodec->data->dev_vol_type & SNDDEV_DEV_VOL_DIGITAL) { + + rc = adie_codec_set_device_digital_volume(icodec->adie_path, + icodec->data->channel_mode == + REAL_STEREO_CHANNEL_MODE ? + 2 : icodec->data->channel_mode, volume); + if (rc < 0) { + MM_ERR("unable to set_device_digital_volume for" + "%s volume in percentage = %u\n", + dev_info->name, volume); + return rc; + } + + } else if (icodec->data->dev_vol_type & SNDDEV_DEV_VOL_ANALOG) { + rc = adie_codec_set_device_analog_volume(icodec->adie_path, + icodec->data->channel_mode == + REAL_STEREO_CHANNEL_MODE ? + 2 : icodec->data->channel_mode, volume); + if (rc < 0) { + MM_ERR("unable to set_device_analog_volume for" + "%s volume in percentage = %u\n", + dev_info->name, volume); + return rc; + } + } + else { + MM_ERR("Invalid device volume control\n"); + return -EPERM; + } + return rc; +} + +static int snddev_icodec_close(struct msm_snddev_info *dev_info) +{ + int rc = 0; + struct snddev_icodec_state *icodec; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + if (!dev_info) { + rc = -EINVAL; + goto error; + } + + icodec = dev_info->private_data; + + if (icodec->data->capability & SNDDEV_CAP_RX) { + mutex_lock(&drv->rx_lock); + if (!drv->rx_active) { + mutex_unlock(&drv->rx_lock); + rc = -EPERM; + goto error; + } + rc = snddev_icodec_close_rx(icodec); + if (!IS_ERR_VALUE(rc)) + drv->rx_active = 0; + mutex_unlock(&drv->rx_lock); + } else if (icodec->data->capability & SNDDEV_CAP_LB) { + mutex_lock(&drv->lb_lock); + rc = snddev_icodec_close_lb(icodec); + mutex_unlock(&drv->lb_lock); + } else { + mutex_lock(&drv->tx_lock); + if (!drv->tx_active) { + mutex_unlock(&drv->tx_lock); + rc = -EPERM; + goto error; + } + rc = snddev_icodec_close_tx(icodec); + if (!IS_ERR_VALUE(rc)) + drv->tx_active = 0; + mutex_unlock(&drv->tx_lock); + } + +error: + return rc; +} + +static int snddev_icodec_open(struct msm_snddev_info *dev_info) +{ + int rc = 0; + struct snddev_icodec_state *icodec; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + if (!dev_info) { + rc = -EINVAL; + goto error; + } + + icodec = dev_info->private_data; + + if (icodec->data->capability & SNDDEV_CAP_RX) { + mutex_lock(&drv->rx_lock); + if (drv->rx_active) { + mutex_unlock(&drv->rx_lock); + rc = -EBUSY; + goto error; + } + rc = snddev_icodec_open_rx(icodec); + + if (!IS_ERR_VALUE(rc)) { + drv->rx_active = 1; + if ((icodec->data->dev_vol_type & ( + SNDDEV_DEV_VOL_DIGITAL | + SNDDEV_DEV_VOL_ANALOG))) + rc = snddev_icodec_set_device_volume_impl( + dev_info, dev_info->dev_volume); + if (IS_ERR_VALUE(rc)) { + MM_ERR("Failed to set device volume" + " impl for rx device\n"); + snddev_icodec_close(dev_info); + mutex_unlock(&drv->rx_lock); + goto error; + } + } + mutex_unlock(&drv->rx_lock); + } else if (icodec->data->capability & SNDDEV_CAP_LB) { + mutex_lock(&drv->lb_lock); + rc = snddev_icodec_open_lb(icodec); + if (!IS_ERR_VALUE(rc)) { + if ((icodec->data->dev_vol_type & ( + SNDDEV_DEV_VOL_DIGITAL | + SNDDEV_DEV_VOL_ANALOG))) + rc = snddev_icodec_set_device_volume_impl( + dev_info, + dev_info->dev_volume); + if (rc < 0) + MM_ERR("failed to set device volume\n"); + } + mutex_unlock(&drv->lb_lock); + } else { + mutex_lock(&drv->tx_lock); + if (drv->tx_active) { + mutex_unlock(&drv->tx_lock); + rc = -EBUSY; + goto error; + } + rc = snddev_icodec_open_tx(icodec); + + if (!IS_ERR_VALUE(rc)) { + drv->tx_active = 1; + if ((icodec->data->dev_vol_type & ( + SNDDEV_DEV_VOL_DIGITAL | + SNDDEV_DEV_VOL_ANALOG))) + rc = snddev_icodec_set_device_volume_impl( + dev_info, dev_info->dev_volume); + if (IS_ERR_VALUE(rc)) { + MM_ERR("Failed to set device volume" + " impl for tx device\n"); + snddev_icodec_close(dev_info); + mutex_unlock(&drv->tx_lock); + goto error; + } + } + mutex_unlock(&drv->tx_lock); + } +error: + return rc; +} + +static int snddev_icodec_check_freq(u32 req_freq) +{ + int rc = -EINVAL; + + if ((req_freq != 0) && (req_freq >= 8000) && (req_freq <= 48000)) { + if ((req_freq == 8000) || (req_freq == 11025) || + (req_freq == 12000) || (req_freq == 16000) || + (req_freq == 22050) || (req_freq == 24000) || + (req_freq == 32000) || (req_freq == 44100) || + (req_freq == 48000)) { + rc = 0; + } else + MM_INFO("Unsupported Frequency:%d\n", req_freq); + } + return rc; +} + +static int snddev_icodec_set_freq(struct msm_snddev_info *dev_info, u32 rate) +{ + int rc; + struct snddev_icodec_state *icodec; + + if (!dev_info) { + rc = -EINVAL; + goto error; + } + + icodec = dev_info->private_data; + if (adie_codec_freq_supported(icodec->data->profile, rate) != 0) { + rc = -EINVAL; + goto error; + } else { + if (snddev_icodec_check_freq(rate) != 0) { + rc = -EINVAL; + goto error; + } else + icodec->sample_rate = rate; + } + + if (icodec->enabled) { + snddev_icodec_close(dev_info); + snddev_icodec_open(dev_info); + } + + return icodec->sample_rate; + +error: + return rc; +} + +static int snddev_icodec_enable_sidetone(struct msm_snddev_info *dev_info, + u32 enable) +{ + int rc = 0; + struct snddev_icodec_state *icodec; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + if (!dev_info) { + MM_ERR("invalid dev_info\n"); + rc = -EINVAL; + goto error; + } + + icodec = dev_info->private_data; + + if (icodec->data->capability & SNDDEV_CAP_RX) { + mutex_lock(&drv->rx_lock); + if (!drv->rx_active || !dev_info->opened) { + MM_ERR("dev not active\n"); + rc = -EPERM; + mutex_unlock(&drv->rx_lock); + goto error; + } + rc = adie_codec_enable_sidetone(icodec->adie_path, enable); + mutex_unlock(&drv->rx_lock); + } else { + rc = -EINVAL; + MM_ERR("rx device only\n"); + } + +error: + return rc; + +} + +int snddev_icodec_set_device_volume(struct msm_snddev_info *dev_info, + u32 volume) +{ + struct snddev_icodec_state *icodec; + struct mutex *lock; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + int rc = -EPERM; + + if (!dev_info) { + MM_INFO("device not intilized.\n"); + return -EINVAL; + } + + icodec = dev_info->private_data; + + if (!(icodec->data->dev_vol_type & (SNDDEV_DEV_VOL_DIGITAL + | SNDDEV_DEV_VOL_ANALOG))) { + + MM_INFO("device %s does not support device volume " + "control.", dev_info->name); + return -EPERM; + } + dev_info->dev_volume = volume; + + if (icodec->data->capability & SNDDEV_CAP_RX) + lock = &drv->rx_lock; + else if (icodec->data->capability & SNDDEV_CAP_LB) + lock = &drv->lb_lock; + else + lock = &drv->tx_lock; + + mutex_lock(lock); + + rc = snddev_icodec_set_device_volume_impl(dev_info, + dev_info->dev_volume); + mutex_unlock(lock); + return rc; +} + +static int snddev_icodec_probe(struct platform_device *pdev) +{ + int rc = 0, i; + struct snddev_icodec_data *pdata; + struct msm_snddev_info *dev_info; + struct snddev_icodec_state *icodec; + + if (!pdev || !pdev->dev.platform_data) { + printk(KERN_ALERT "Invalid caller \n"); + rc = -1; + goto error; + } + pdata = pdev->dev.platform_data; + if ((pdata->capability & SNDDEV_CAP_RX) && + (pdata->capability & SNDDEV_CAP_TX)) { + MM_ERR("invalid device data either RX or TX\n"); + goto error; + } + icodec = kzalloc(sizeof(struct snddev_icodec_state), GFP_KERNEL); + if (!icodec) { + rc = -ENOMEM; + goto error; + } + dev_info = kmalloc(sizeof(struct msm_snddev_info), GFP_KERNEL); + if (!dev_info) { + kfree(icodec); + rc = -ENOMEM; + goto error; + } + + dev_info->name = pdata->name; + dev_info->copp_id = pdata->copp_id; + dev_info->acdb_id = pdata->acdb_id; + dev_info->private_data = (void *) icodec; + dev_info->dev_ops.open = snddev_icodec_open; + dev_info->dev_ops.close = snddev_icodec_close; + dev_info->dev_ops.set_freq = snddev_icodec_set_freq; + dev_info->dev_ops.set_device_volume = snddev_icodec_set_device_volume; + dev_info->capability = pdata->capability; + dev_info->opened = 0; + msm_snddev_register(dev_info); + icodec->data = pdata; + icodec->sample_rate = pdata->default_sample_rate; + dev_info->sample_rate = pdata->default_sample_rate; + if (pdata->capability & SNDDEV_CAP_RX) { + for (i = 0; i < VOC_RX_VOL_ARRAY_NUM; i++) { + dev_info->max_voc_rx_vol[i] = + pdata->max_voice_rx_vol[i]; + dev_info->min_voc_rx_vol[i] = + pdata->min_voice_rx_vol[i]; + } + /*sidetone is enabled only for the device which + property set for side tone*/ + if (pdata->property & SIDE_TONE_MASK) + dev_info->dev_ops.enable_sidetone = + snddev_icodec_enable_sidetone; + else + dev_info->dev_ops.enable_sidetone = NULL; + } else { + dev_info->dev_ops.enable_sidetone = NULL; + } + +error: + return rc; +} + +static int snddev_icodec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver snddev_icodec_driver = { + .probe = snddev_icodec_probe, + .remove = snddev_icodec_remove, + .driver = { .name = "snddev_icodec" } +}; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_sdev_dent; +static struct dentry *debugfs_afelb; +static struct dentry *debugfs_adielb; +static struct adie_codec_path *debugfs_rx_adie; +static struct adie_codec_path *debugfs_tx_adie; + +static int snddev_icodec_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + MM_INFO("snddev_icodec: debug intf %s\n", (char *) file->private_data); + return 0; +} + +static void debugfs_adie_loopback(u32 loop) +{ + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + if (loop) { + + /* enable MI2S RX master block */ + /* enable MI2S RX bit clock */ + clk_set_rate(drv->rx_mclk, + SNDDEV_ICODEC_CLK_RATE(8000)); + clk_enable(drv->rx_mclk); + clk_enable(drv->rx_sclk); + + MM_INFO("configure ADIE RX path\n"); + /* Configure ADIE */ + adie_codec_open(&debug_rx_profile, &debugfs_rx_adie); + adie_codec_setpath(debugfs_rx_adie, 8000, 256); + adie_codec_proceed_stage(debugfs_rx_adie, + ADIE_CODEC_DIGITAL_ANALOG_READY); + + MM_INFO("Enable Handset Mic bias\n"); + pmic_hsed_enable(PM_HSED_CONTROLLER_0, PM_HSED_ENABLE_PWM_TCXO); + /* enable MI2S TX master block */ + /* enable MI2S TX bit clock */ + clk_set_rate(drv->tx_mclk, + SNDDEV_ICODEC_CLK_RATE(8000)); + clk_enable(drv->tx_mclk); + clk_enable(drv->tx_sclk); + + MM_INFO("configure ADIE TX path\n"); + /* Configure ADIE */ + adie_codec_open(&debug_tx_lb_profile, &debugfs_tx_adie); + adie_codec_setpath(debugfs_tx_adie, 8000, 256); + adie_codec_proceed_stage(debugfs_tx_adie, + ADIE_CODEC_DIGITAL_ANALOG_READY); + } else { + /* Disable ADIE */ + adie_codec_proceed_stage(debugfs_rx_adie, + ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(debugfs_rx_adie); + adie_codec_proceed_stage(debugfs_tx_adie, + ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(debugfs_tx_adie); + + pmic_hsed_enable(PM_HSED_CONTROLLER_0, PM_HSED_ENABLE_OFF); + + /* Disable MI2S RX master block */ + /* Disable MI2S RX bit clock */ + clk_disable(drv->rx_sclk); + clk_disable(drv->rx_mclk); + + /* Disable MI2S TX master block */ + /* Disable MI2S TX bit clock */ + clk_disable(drv->tx_sclk); + clk_disable(drv->tx_mclk); + } +} + +static void debugfs_afe_loopback(u32 loop) +{ + int trc; + struct msm_afe_config afe_config; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + struct lpa_codec_config lpa_config; + + if (loop) { + /* Vote for SMPS mode*/ + pmapp_smps_mode_vote(SMPS_AUDIO_PLAYBACK_ID, + PMAPP_VREG_S4, PMAPP_SMPS_MODE_VOTE_PWM); + + /* enable MI2S RX master block */ + /* enable MI2S RX bit clock */ + trc = clk_set_rate(drv->rx_mclk, + SNDDEV_ICODEC_CLK_RATE(8000)); + if (IS_ERR_VALUE(trc)) + MM_ERR("failed to set clk rate\n"); + clk_enable(drv->rx_mclk); + clk_enable(drv->rx_sclk); + clk_enable(drv->lpa_p_clk); + clk_enable(drv->lpa_codec_clk); + clk_enable(drv->lpa_core_clk); + /* Enable LPA sub system + */ + drv->lpa = lpa_get(); + if (!drv->lpa) + MM_ERR("failed to enable lpa\n"); + lpa_config.sample_rate = 8000; + lpa_config.sample_width = 16; + lpa_config.output_interface = LPA_OUTPUT_INTF_WB_CODEC; + lpa_config.num_channels = 1; + lpa_cmd_codec_config(drv->lpa, &lpa_config); + /* Set audio interconnect reg to LPA */ + audio_interct_codec(AUDIO_INTERCT_LPA); + mi2s_set_codec_output_path(MI2S_CHAN_MONO_PACKED, WT_16_BIT); + MM_INFO("configure ADIE RX path\n"); + /* Configure ADIE */ + adie_codec_open(&debug_rx_profile, &debugfs_rx_adie); + adie_codec_setpath(debugfs_rx_adie, 8000, 256); + lpa_cmd_enable_codec(drv->lpa, 1); + + /* Start AFE for RX */ + afe_config.sample_rate = 0x8; + afe_config.channel_mode = 1; + afe_config.volume = AFE_VOLUME_UNITY; + MM_INFO("enable afe\n"); + trc = afe_enable(AFE_HW_PATH_CODEC_RX, &afe_config); + if (IS_ERR_VALUE(trc)) + MM_ERR("fail to enable afe RX\n"); + adie_codec_proceed_stage(debugfs_rx_adie, + ADIE_CODEC_DIGITAL_READY); + adie_codec_proceed_stage(debugfs_rx_adie, + ADIE_CODEC_DIGITAL_ANALOG_READY); + + /* Vote for PWM mode*/ + pmapp_smps_mode_vote(SMPS_AUDIO_RECORD_ID, + PMAPP_VREG_S4, PMAPP_SMPS_MODE_VOTE_PWM); + + MM_INFO("Enable Handset Mic bias\n"); + pmic_hsed_enable(PM_HSED_CONTROLLER_0, PM_HSED_ENABLE_PWM_TCXO); + + /* enable MI2S TX master block */ + /* enable MI2S TX bit clock */ + clk_set_rate(drv->tx_mclk, + SNDDEV_ICODEC_CLK_RATE(8000)); + clk_enable(drv->tx_mclk); + clk_enable(drv->tx_sclk); + /* Set MI2S */ + mi2s_set_codec_input_path(MI2S_CHAN_MONO_PACKED, WT_16_BIT); + MM_INFO("configure ADIE TX path\n"); + /* Configure ADIE */ + adie_codec_open(&debug_tx_profile, &debugfs_tx_adie); + adie_codec_setpath(debugfs_tx_adie, 8000, 256); + adie_codec_proceed_stage(debugfs_tx_adie, + ADIE_CODEC_DIGITAL_READY); + adie_codec_proceed_stage(debugfs_tx_adie, + ADIE_CODEC_DIGITAL_ANALOG_READY); + /* Start AFE for TX */ + afe_config.sample_rate = 0x8; + afe_config.channel_mode = 1; + afe_config.volume = AFE_VOLUME_UNITY; + trc = afe_enable(AFE_HW_PATH_CODEC_TX, &afe_config); + if (IS_ERR_VALUE(trc)) + MM_ERR("failed to enable AFE TX\n"); + /* Set the volume level to non unity, to avoid + loopback effect */ + afe_device_volume_ctrl(AFE_HW_PATH_CODEC_RX, 0x0500); + + /* enable afe loopback */ + afe_loopback(1); + MM_INFO("AFE loopback enabled\n"); + } else { + /* disable afe loopback */ + afe_loopback(0); + /* Remove the vote for SMPS mode*/ + pmapp_smps_mode_vote(SMPS_AUDIO_PLAYBACK_ID, + PMAPP_VREG_S4, PMAPP_SMPS_MODE_VOTE_DONTCARE); + + /* Disable ADIE */ + adie_codec_proceed_stage(debugfs_rx_adie, + ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(debugfs_rx_adie); + /* Disable AFE for RX */ + afe_disable(AFE_HW_PATH_CODEC_RX); + + /* Disable LPA Sub system */ + lpa_cmd_enable_codec(drv->lpa, 0); + lpa_put(drv->lpa); + + /* Disable LPA clocks */ + clk_disable(drv->lpa_p_clk); + clk_disable(drv->lpa_codec_clk); + clk_disable(drv->lpa_core_clk); + + /* Disable MI2S RX master block */ + /* Disable MI2S RX bit clock */ + clk_disable(drv->rx_sclk); + clk_disable(drv->rx_mclk); + + pmapp_smps_mode_vote(SMPS_AUDIO_RECORD_ID, + PMAPP_VREG_S4, PMAPP_SMPS_MODE_VOTE_DONTCARE); + + /* Disable AFE for TX */ + afe_disable(AFE_HW_PATH_CODEC_TX); + + /* Disable ADIE */ + adie_codec_proceed_stage(debugfs_tx_adie, + ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(debugfs_tx_adie); + /* Disable MI2S TX master block */ + /* Disable MI2S TX bit clock */ + clk_disable(drv->tx_sclk); + clk_disable(drv->tx_mclk); + pmic_hsed_enable(PM_HSED_CONTROLLER_0, PM_HSED_ENABLE_OFF); + MM_INFO("AFE loopback disabled\n"); + } +} + +static ssize_t snddev_icodec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *lb_str = filp->private_data; + char cmd; + + if (get_user(cmd, ubuf)) + return -EFAULT; + + MM_INFO("%s %c\n", lb_str, cmd); + + if (!strcmp(lb_str, "adie_loopback")) { + switch (cmd) { + case '1': + debugfs_adie_loopback(1); + break; + case '0': + debugfs_adie_loopback(0); + break; + } + } else if (!strcmp(lb_str, "afe_loopback")) { + switch (cmd) { + case '1': + debugfs_afe_loopback(1); + break; + case '0': + debugfs_afe_loopback(0); + break; + } + } + + return cnt; +} + +static const struct file_operations snddev_icodec_debug_fops = { + .open = snddev_icodec_debug_open, + .write = snddev_icodec_debug_write +}; +#endif + +static int __init snddev_icodec_init(void) +{ + s32 rc; + struct snddev_icodec_drv_state *icodec_drv = &snddev_icodec_drv; + + rc = platform_driver_register(&snddev_icodec_driver); + if (IS_ERR_VALUE(rc)) + goto error_platform_driver; + icodec_drv->rx_mclk = clk_get(NULL, "mi2s_codec_rx_m_clk"); + if (IS_ERR(icodec_drv->rx_mclk)) + goto error_rx_mclk; + icodec_drv->rx_sclk = clk_get(NULL, "mi2s_codec_rx_s_clk"); + if (IS_ERR(icodec_drv->rx_sclk)) + goto error_rx_sclk; + icodec_drv->tx_mclk = clk_get(NULL, "mi2s_codec_tx_m_clk"); + if (IS_ERR(icodec_drv->tx_mclk)) + goto error_tx_mclk; + icodec_drv->tx_sclk = clk_get(NULL, "mi2s_codec_tx_s_clk"); + if (IS_ERR(icodec_drv->tx_sclk)) + goto error_tx_sclk; + icodec_drv->lpa_codec_clk = clk_get(NULL, "lpa_codec_clk"); + if (IS_ERR(icodec_drv->lpa_codec_clk)) + goto error_lpa_codec_clk; + icodec_drv->lpa_core_clk = clk_get(NULL, "lpa_core_clk"); + if (IS_ERR(icodec_drv->lpa_core_clk)) + goto error_lpa_core_clk; + icodec_drv->lpa_p_clk = clk_get(NULL, "lpa_pclk"); + if (IS_ERR(icodec_drv->lpa_p_clk)) + goto error_lpa_p_clk; + +#ifdef CONFIG_DEBUG_FS + debugfs_sdev_dent = debugfs_create_dir("snddev_icodec", 0); + if (debugfs_sdev_dent) { + debugfs_afelb = debugfs_create_file("afe_loopback", + S_IFREG | S_IWUGO, debugfs_sdev_dent, + (void *) "afe_loopback", &snddev_icodec_debug_fops); + debugfs_adielb = debugfs_create_file("adie_loopback", + S_IFREG | S_IWUGO, debugfs_sdev_dent, + (void *) "adie_loopback", &snddev_icodec_debug_fops); + } +#endif + mutex_init(&icodec_drv->rx_lock); + mutex_init(&icodec_drv->lb_lock); + mutex_init(&icodec_drv->tx_lock); + icodec_drv->rx_active = 0; + icodec_drv->tx_active = 0; + icodec_drv->lpa = NULL; + wake_lock_init(&icodec_drv->tx_idlelock, WAKE_LOCK_IDLE, + "snddev_tx_idle"); + wake_lock_init(&icodec_drv->rx_idlelock, WAKE_LOCK_IDLE, + "snddev_rx_idle"); + return 0; + +error_lpa_p_clk: + clk_put(icodec_drv->lpa_core_clk); +error_lpa_core_clk: + clk_put(icodec_drv->lpa_codec_clk); +error_lpa_codec_clk: + clk_put(icodec_drv->tx_sclk); +error_tx_sclk: + clk_put(icodec_drv->tx_mclk); +error_tx_mclk: + clk_put(icodec_drv->rx_sclk); +error_rx_sclk: + clk_put(icodec_drv->rx_mclk); +error_rx_mclk: + platform_driver_unregister(&snddev_icodec_driver); +error_platform_driver: + + MM_ERR("encounter error\n"); + return -ENODEV; +} + +static void __exit snddev_icodec_exit(void) +{ + struct snddev_icodec_drv_state *icodec_drv = &snddev_icodec_drv; + +#ifdef CONFIG_DEBUG_FS + if (debugfs_afelb) + debugfs_remove(debugfs_afelb); + if (debugfs_adielb) + debugfs_remove(debugfs_adielb); + if (debugfs_sdev_dent) + debugfs_remove(debugfs_sdev_dent); +#endif + platform_driver_unregister(&snddev_icodec_driver); + + clk_put(icodec_drv->rx_sclk); + clk_put(icodec_drv->rx_mclk); + clk_put(icodec_drv->tx_sclk); + clk_put(icodec_drv->tx_mclk); + return; +} + +module_init(snddev_icodec_init); +module_exit(snddev_icodec_exit); + +MODULE_DESCRIPTION("ICodec Sound Device driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/snddev_mi2s.c b/arch/arm/mach-msm/qdsp5v2/snddev_mi2s.c new file mode 100644 index 00000000000..939cc8b3848 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/snddev_mi2s.c @@ -0,0 +1,405 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global state for the driver */ +struct snddev_mi2s_drv_state { + struct clk *mclk; + struct clk *sclk; + struct mutex lock; + u8 sd_lines_used; + u8 clocks_enabled; +}; + +static struct snddev_mi2s_drv_state snddev_mi2s_drv; + +static int snddev_mi2s_open_tx(struct msm_snddev_info *dev_info) +{ + u8 channels; + struct msm_afe_config afe_config; + int rc; + struct snddev_mi2s_data *snddev_mi2s_data = dev_info->private_data; + + MM_DBG("%s: channel_mode = %u sd_line_mask = 0x%x " + "default_sample_rate = %u\n", __func__, + snddev_mi2s_data->channel_mode, snddev_mi2s_data->sd_lines, + snddev_mi2s_data->default_sample_rate); + + if (snddev_mi2s_data->channel_mode == 2) { + channels = MI2S_CHAN_STEREO; + } else { + MM_ERR("%s: Invalid number of channels = %u\n", __func__, + snddev_mi2s_data->channel_mode); + return -EINVAL; + } + + /* Set MI2S */ + mi2s_set_hdmi_input_path(channels, WT_16_BIT, + snddev_mi2s_data->sd_lines); + + afe_config.sample_rate = snddev_mi2s_data->default_sample_rate / 1000; + afe_config.channel_mode = snddev_mi2s_data->channel_mode; + afe_config.volume = AFE_VOLUME_UNITY; + rc = afe_enable(AFE_HW_PATH_MI2S_TX, &afe_config); + + if (IS_ERR_VALUE(rc)) { + MM_ERR("%s: afe_enable failed for AFE_HW_PATH_MI2S_TX " + "rc = %d\n", __func__, rc); + return -ENODEV; + } + + /* Enable audio path */ + if (snddev_mi2s_data->route) + snddev_mi2s_data->route(); + + return 0; +} + +static int snddev_mi2s_open_rx(struct msm_snddev_info *dev_info) +{ + int rc; + struct msm_afe_config afe_config; + u8 channels; + struct snddev_mi2s_data *snddev_mi2s_data = dev_info->private_data; + + MM_DBG("%s: channel_mode = %u sd_line_mask = 0x%x " + "default_sample_rate = %u\n", __func__, + snddev_mi2s_data->channel_mode, snddev_mi2s_data->sd_lines, + snddev_mi2s_data->default_sample_rate); + + if (snddev_mi2s_data->channel_mode == 2) + channels = MI2S_CHAN_STEREO; + else if (snddev_mi2s_data->channel_mode == 4) + channels = MI2S_CHAN_4CHANNELS; + else if (snddev_mi2s_data->channel_mode == 6) + channels = MI2S_CHAN_6CHANNELS; + else if (snddev_mi2s_data->channel_mode == 8) + channels = MI2S_CHAN_8CHANNELS; + else + channels = MI2S_CHAN_MONO_RAW; + + /* Set MI2S */ + mi2s_set_hdmi_output_path(channels, WT_16_BIT, + snddev_mi2s_data->sd_lines); + + /* Start AFE */ + afe_config.sample_rate = snddev_mi2s_data->default_sample_rate / 1000; + afe_config.channel_mode = snddev_mi2s_data->channel_mode; + afe_config.volume = AFE_VOLUME_UNITY; + rc = afe_enable(AFE_HW_PATH_MI2S_RX, &afe_config); + + if (IS_ERR_VALUE(rc)) { + MM_ERR("%s: encounter error\n", __func__); + return -ENODEV; + } + + /* Enable audio path */ + if (snddev_mi2s_data->route) + snddev_mi2s_data->route(); + + MM_DBG("%s: enabled %s \n", __func__, snddev_mi2s_data->name); + + return 0; +} + +static int snddev_mi2s_open(struct msm_snddev_info *dev_info) +{ + int rc = 0; + struct snddev_mi2s_drv_state *drv = &snddev_mi2s_drv; + u32 dir; + struct snddev_mi2s_data *snddev_mi2s_data = dev_info->private_data; + + if (!dev_info) { + MM_ERR("%s: msm_snddev_info is null \n", __func__); + return -EINVAL; + } + + mutex_lock(&drv->lock); + + if (drv->sd_lines_used & snddev_mi2s_data->sd_lines) { + MM_ERR("%s: conflict in SD data line. can not use the device\n", + __func__); + mutex_unlock(&drv->lock); + return -EBUSY; + } + + if (!drv->clocks_enabled) { + + rc = mi2s_config_clk_gpio(); + if (rc) { + MM_ERR("%s: mi2s GPIO config failed for %s\n", + __func__, snddev_mi2s_data->name); + mutex_unlock(&drv->lock); + return -EIO; + } + clk_enable(drv->mclk); + clk_enable(drv->sclk); + drv->clocks_enabled = 1; + MM_DBG("%s: clks enabled \n", __func__); + } else + MM_DBG("%s: clks already enabled \n", __func__); + + if (snddev_mi2s_data->capability & SNDDEV_CAP_RX) { + + dir = DIR_RX; + rc = mi2s_config_data_gpio(dir, snddev_mi2s_data->sd_lines); + + if (rc) { + rc = -EIO; + MM_ERR("%s: mi2s GPIO config failed for %s\n", + __func__, snddev_mi2s_data->name); + goto mi2s_data_gpio_failure; + } + + MM_DBG("%s: done gpio config rx SD lines\n", __func__); + + rc = snddev_mi2s_open_rx(dev_info); + + if (IS_ERR_VALUE(rc)) { + MM_ERR(" snddev_mi2s_open_rx failed \n"); + goto mi2s_cleanup_open; + } + + drv->sd_lines_used |= snddev_mi2s_data->sd_lines; + + MM_DBG("%s: sd_lines_used = 0x%x\n", __func__, + drv->sd_lines_used); + mutex_unlock(&drv->lock); + + } else { + dir = DIR_TX; + rc = mi2s_config_data_gpio(dir, snddev_mi2s_data->sd_lines); + + if (rc) { + rc = -EIO; + MM_ERR("%s: mi2s GPIO config failed for %s\n", + __func__, snddev_mi2s_data->name); + goto mi2s_data_gpio_failure; + } + MM_DBG("%s: done data line gpio config for %s\n", + __func__, snddev_mi2s_data->name); + + rc = snddev_mi2s_open_tx(dev_info); + + if (IS_ERR_VALUE(rc)) { + MM_ERR(" snddev_mi2s_open_tx failed \n"); + goto mi2s_cleanup_open; + } + + drv->sd_lines_used |= snddev_mi2s_data->sd_lines; + MM_DBG("%s: sd_lines_used = 0x%x\n", __func__, + drv->sd_lines_used); + mutex_unlock(&drv->lock); + } + + return 0; + +mi2s_cleanup_open: + mi2s_unconfig_data_gpio(dir, snddev_mi2s_data->sd_lines); + + /* Disable audio path */ + if (snddev_mi2s_data->deroute) + snddev_mi2s_data->deroute(); + +mi2s_data_gpio_failure: + if (!drv->sd_lines_used) { + clk_disable(drv->sclk); + clk_disable(drv->mclk); + drv->clocks_enabled = 0; + mi2s_unconfig_clk_gpio(); + } + mutex_unlock(&drv->lock); + return rc; +} + +static int snddev_mi2s_close(struct msm_snddev_info *dev_info) +{ + struct snddev_mi2s_drv_state *drv = &snddev_mi2s_drv; + int dir; + struct snddev_mi2s_data *snddev_mi2s_data = dev_info->private_data; + + if (!dev_info) { + MM_ERR("%s: msm_snddev_info is null \n", __func__); + return -EINVAL; + } + + if (!dev_info->opened) { + MM_ERR(" %s: calling close device with out opening the" + " device \n", __func__); + return -EIO; + } + + mutex_lock(&drv->lock); + + drv->sd_lines_used &= ~snddev_mi2s_data->sd_lines; + + MM_DBG("%s: sd_lines in use = 0x%x\n", __func__, drv->sd_lines_used); + + if (snddev_mi2s_data->capability & SNDDEV_CAP_RX) { + dir = DIR_RX; + afe_disable(AFE_HW_PATH_MI2S_RX); + } else { + dir = DIR_TX; + afe_disable(AFE_HW_PATH_MI2S_TX); + } + + mi2s_unconfig_data_gpio(dir, snddev_mi2s_data->sd_lines); + + if (!drv->sd_lines_used) { + clk_disable(drv->sclk); + clk_disable(drv->mclk); + drv->clocks_enabled = 0; + mi2s_unconfig_clk_gpio(); + } + + /* Disable audio path */ + if (snddev_mi2s_data->deroute) + snddev_mi2s_data->deroute(); + + mutex_unlock(&drv->lock); + + return 0; +} + +static int snddev_mi2s_set_freq(struct msm_snddev_info *dev_info, u32 req_freq) +{ + if (req_freq != 48000) { + MM_DBG("%s: Unsupported Frequency:%d\n", __func__, req_freq); + return -EINVAL; + } + return 48000; +} + +static int snddev_mi2s_probe(struct platform_device *pdev) +{ + int rc = 0; + struct snddev_mi2s_data *pdata; + struct msm_snddev_info *dev_info; + + if (!pdev || !pdev->dev.platform_data) { + printk(KERN_ALERT "Invalid caller \n"); + return -ENODEV; + } + + pdata = pdev->dev.platform_data; + if ((pdata->capability & SNDDEV_CAP_RX) && + (pdata->capability & SNDDEV_CAP_TX)) { + MM_ERR("%s: invalid device data either RX or TX\n", __func__); + return -ENODEV; + } + + dev_info = kzalloc(sizeof(struct msm_snddev_info), GFP_KERNEL); + if (!dev_info) { + MM_ERR("%s: uneable to allocate memeory for msm_snddev_info \n", + __func__); + + return -ENOMEM; + } + + dev_info->name = pdata->name; + dev_info->copp_id = pdata->copp_id; + dev_info->acdb_id = pdata->acdb_id; + dev_info->private_data = (void *)pdata; + dev_info->dev_ops.open = snddev_mi2s_open; + dev_info->dev_ops.close = snddev_mi2s_close; + dev_info->dev_ops.set_freq = snddev_mi2s_set_freq; + dev_info->capability = pdata->capability; + dev_info->opened = 0; + msm_snddev_register(dev_info); + dev_info->sample_rate = pdata->default_sample_rate; + + MM_DBG("%s: probe done for %s\n", __func__, pdata->name); + return rc; +} + +static int snddev_mi2s_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver snddev_mi2s_driver = { + .probe = snddev_mi2s_probe, + .remove = snddev_mi2s_remove, + .driver = {.name = "snddev_mi2s"} +}; + +static int __init snddev_mi2s_init(void) +{ + s32 rc; + struct snddev_mi2s_drv_state *drv = &snddev_mi2s_drv; + + rc = platform_driver_register(&snddev_mi2s_driver); + if (IS_ERR_VALUE(rc)) { + + MM_ERR("%s: platform_driver_register failed \n", __func__); + goto error_platform_driver; + } + + drv->mclk = clk_get(NULL, "mi2s_m_clk"); + if (IS_ERR(drv->mclk)) { + MM_ERR("%s: clk_get mi2s_mclk failed \n", __func__); + goto error_mclk; + } + + drv->sclk = clk_get(NULL, "mi2s_s_clk"); + if (IS_ERR(drv->sclk)) { + MM_ERR("%s: clk_get mi2s_sclk failed \n", __func__); + + goto error_sclk; + } + + mutex_init(&drv->lock); + + MM_DBG("snddev_mi2s_init : done \n"); + + return 0; + +error_sclk: + clk_put(drv->mclk); +error_mclk: + platform_driver_unregister(&snddev_mi2s_driver); +error_platform_driver: + + MM_ERR("%s: encounter error\n", __func__); + return -ENODEV; +} + +static void __exit snddev_mi2s_exit(void) +{ + struct snddev_mi2s_drv_state *drv = &snddev_mi2s_drv; + + platform_driver_unregister(&snddev_mi2s_driver); + + clk_put(drv->sclk); + clk_put(drv->mclk); + return; +} + +module_init(snddev_mi2s_init); +module_exit(snddev_mi2s_exit); + +MODULE_DESCRIPTION("mi2s Sound Device driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/snddev_virtual.c b/arch/arm/mach-msm/qdsp5v2/snddev_virtual.c new file mode 100644 index 00000000000..cd93345d601 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/snddev_virtual.c @@ -0,0 +1,122 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static int snddev_virtual_open(struct msm_snddev_info *dev_info) +{ + int rc = 0; + + if (!dev_info) + rc = -EINVAL; + return rc; +} + +static int snddev_virtual_close(struct msm_snddev_info *dev_info) +{ + int rc = 0; + + if (!dev_info) + rc = -EINVAL; + return rc; +} + +static int snddev_virtual_set_freq(struct msm_snddev_info *dev_info, u32 rate) +{ + int rc = 0; + + if (!dev_info) + rc = -EINVAL; + return rate; +} + +static int snddev_virtual_probe(struct platform_device *pdev) +{ + int rc = 0; + struct snddev_virtual_data *pdata; + struct msm_snddev_info *dev_info; + + if (!pdev || !pdev->dev.platform_data) { + MM_ERR("Invalid caller\n"); + rc = -EPERM; + goto error; + } + pdata = pdev->dev.platform_data; + + dev_info = kmalloc(sizeof(struct msm_snddev_info), GFP_KERNEL); + if (!dev_info) { + rc = -ENOMEM; + goto error; + } + + dev_info->name = pdata->name; + dev_info->copp_id = pdata->copp_id; + dev_info->acdb_id = pdata->acdb_id; + dev_info->private_data = (void *) NULL; + dev_info->dev_ops.open = snddev_virtual_open; + dev_info->dev_ops.close = snddev_virtual_close; + dev_info->dev_ops.set_freq = snddev_virtual_set_freq; + dev_info->capability = pdata->capability; + dev_info->sample_rate = 8000; + dev_info->opened = 0; + dev_info->sessions = 0; + + msm_snddev_register(dev_info); + +error: + return rc; +} + +static int snddev_virtual_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver snddev_virtual_driver = { + .probe = snddev_virtual_probe, + .remove = snddev_virtual_remove, + .driver = { .name = "snddev_virtual" } +}; + +static int __init snddev_virtual_init(void) +{ + int rc = 0; + + MM_DBG(" snddev_virtual_init \n"); + rc = platform_driver_register(&snddev_virtual_driver); + if (IS_ERR_VALUE(rc)) { + MM_ERR("platform driver register failure\n"); + return -ENODEV; + } + return 0; +} + +static void __exit snddev_virtual_exit(void) +{ + platform_driver_unregister(&snddev_virtual_driver); + + return; +} + +module_init(snddev_virtual_init); +module_exit(snddev_virtual_exit); + +MODULE_DESCRIPTION("Virtual Sound Device driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp5v2/timpani_profile_7x30.h b/arch/arm/mach-msm/qdsp5v2/timpani_profile_7x30.h new file mode 100644 index 00000000000..f336597176c --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/timpani_profile_7x30.h @@ -0,0 +1,622 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MACH_QDSP5_V2_TIMPANI_PROFILE_H__ +#define __MACH_QDSP5_V2_MTIMPANI_PROFILE_H__ + +/* + * TX Device Profiles + */ + +/* Analog MIC */ +/* AMIC Primary mono */ +#define AMIC_PRI_MONO_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xD0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xE6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* Headset MIC */ +#define AMIC1_HEADSET_TX_MONO_PRIMARY_OSR256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xC8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xE7)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)} } + +/* + * RX Device Profiles + */ + +/* RX EAR */ +#define EAR_PRI_MONO_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* RX SPEAKER */ +#define SPEAKER_PRI_STEREO_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} }; + +/* + * RX HPH PRIMARY + */ + +/* RX HPH CLASS AB CAPLESS */ + +#define HEADSET_AB_CPLS_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFE, 0xC8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* AMIC dual */ +#define AMIC_DUAL_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xD0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8b, 0xff, 0xCE)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8c, 0x03, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* TTY RX */ +#define TTY_HEADSET_MONO_RX_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x06)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x45)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xC5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFE, 0xC8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* TTY TX */ +#define TTY_HEADSET_MONO_TX_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_RX_CAPLESS_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x4e)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x04, 0xff, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x25, 0x0F, 0x0B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xfc, 0xfc)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xff, 0xa2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0xFF, 0xab)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0xf0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x23, 0xff, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xff, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0f, 0x0c)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8a, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3b, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3c, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x34, 0xf0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HEADSET_STEREO_SPEAKER_STEREO_RX_CAPLESS_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HS_DMIC2_STEREO_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0x1F, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0x3F, 0x19)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0x3F, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x39, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HPH_PRI_AB_LEG_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xF9)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x27)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HPH_PRI_D_LEG_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x21, 0xFF, 0x60)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x22, 0xFF, 0xE1)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xFF, 0xD0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2D, 0xFF, 0x6F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2E, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xBB)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xF7, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xFF)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xF7, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xFF)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4A, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0x8C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x0A)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_AUXPGA_HPH_AB_CPLS_STEREO \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2F, 0xFF, 0x44)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x30, 0xFF, 0x92)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xAA)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x90, 0x90)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_AUXPGA_LO_STEREO \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2F, 0xFF, 0x44)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x30, 0xFF, 0x92)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xAA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x90, 0x90)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0xF0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x90, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#endif diff --git a/arch/arm/mach-msm/qdsp5v2/voice.c b/arch/arm/mach-msm/qdsp5v2/voice.c new file mode 100644 index 00000000000..c4560ed2a37 --- /dev/null +++ b/arch/arm/mach-msm/qdsp5v2/voice.c @@ -0,0 +1,748 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct voice_data { + void *handle; /* DALRPC handle */ + void *cb_handle; /* DALRPC callback handle */ + int network; /* Network information */ + int dev_state;/*READY, CHANGE, REL_DONE,INIT*/ + int voc_state;/*INIT, CHANGE, RELEASE, ACQUIRE */ + struct mutex voc_lock; + struct mutex vol_lock; + int voc_event; + int dev_event; + atomic_t rel_start_flag; + atomic_t acq_start_flag; + atomic_t chg_start_flag; + struct task_struct *task; + struct completion complete; + wait_queue_head_t dev_wait; + wait_queue_head_t voc_wait; + uint32_t device_events; + /* cache the values related to Rx and Tx */ + struct device_data dev_rx; + struct device_data dev_tx; + /* these default values are for all devices */ + uint32_t default_mute_val; + uint32_t default_vol_val; + uint32_t default_sample_val; + /* call status */ + int v_call_status; /* Start or End */ + s32 max_rx_vol[VOC_RX_VOL_ARRAY_NUM]; /* [0] is for NB, [1] for WB */ + s32 min_rx_vol[VOC_RX_VOL_ARRAY_NUM]; +}; + +static struct voice_data voice; + +static int voice_cmd_device_info(struct voice_data *); +static int voice_cmd_acquire_done(struct voice_data *); +static void voice_auddev_cb_function(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data); + +static int voice_cmd_change(void) +{ + + struct voice_header hdr; + struct voice_data *v = &voice; + int err; + + hdr.id = CMD_DEVICE_CHANGE; + hdr.data_len = 0; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + err = dalrpc_fcn_5(VOICE_DALRPC_CMD, v->handle, &hdr, + sizeof(struct voice_header)); + + if (err) + MM_ERR("Voice change command failed\n"); + return err; +} + +static void voice_auddev_cb_function(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data) +{ + struct voice_data *v = &voice; + int rc = 0, i; + + MM_INFO("auddev_cb_function, evt_id=%d, dev_state=%d, voc_state=%d\n", + evt_id, v->dev_state, v->voc_state); + if ((evt_id != AUDDEV_EVT_START_VOICE) || + (evt_id != AUDDEV_EVT_END_VOICE)) { + if (evt_payload == NULL) { + MM_ERR(" evt_payload is NULL pointer\n"); + return; + } + } + switch (evt_id) { + case AUDDEV_EVT_START_VOICE: + if ((v->dev_state == DEV_INIT) || + (v->dev_state == DEV_REL_DONE)) { + v->v_call_status = VOICE_CALL_START; + if ((v->dev_rx.enabled == VOICE_DEV_ENABLED) + && (v->dev_tx.enabled == VOICE_DEV_ENABLED)) { + v->dev_state = DEV_READY; + MM_DBG("dev_state into ready\n"); + wake_up(&v->dev_wait); + } + } + break; + case AUDDEV_EVT_DEV_CHG_VOICE: + if (v->dev_state == DEV_READY) { + v->dev_rx.enabled = VOICE_DEV_DISABLED; + v->dev_tx.enabled = VOICE_DEV_DISABLED; + v->dev_state = DEV_CHANGE; + mutex_lock(&voice.voc_lock); + if (v->voc_state == VOICE_ACQUIRE) { + /* send device change to modem */ + voice_cmd_change(); + mutex_unlock(&voice.voc_lock); + msm_snddev_enable_sidetone(v->dev_rx.dev_id, + 0); + /* block to wait for CHANGE_START */ + rc = wait_event_interruptible( + v->voc_wait, (v->voc_state == VOICE_CHANGE) + || (atomic_read(&v->chg_start_flag) == 1) + || (atomic_read(&v->rel_start_flag) == 1)); + } else { + mutex_unlock(&voice.voc_lock); + MM_ERR(" Voice is not at ACQUIRE state\n"); + } + } else if ((v->dev_state == DEV_INIT) || + (v->dev_state == DEV_REL_DONE)) { + v->dev_rx.enabled = VOICE_DEV_DISABLED; + v->dev_tx.enabled = VOICE_DEV_DISABLED; + } else + MM_ERR(" device is not at proper state\n"); + break; + case AUDDEV_EVT_DEV_RDY: + /* update the dev info */ + if (evt_payload->voc_devinfo.dev_type == DIR_RX) { + for (i = 0; i < VOC_RX_VOL_ARRAY_NUM; i++) { + v->max_rx_vol[i] = + evt_payload->voc_devinfo.max_rx_vol[i]; + v->min_rx_vol[i] = + evt_payload->voc_devinfo.min_rx_vol[i]; + } + } + if (v->dev_state == DEV_CHANGE) { + if (evt_payload->voc_devinfo.dev_type == DIR_RX) { + v->dev_rx.dev_acdb_id = + evt_payload->voc_devinfo.acdb_dev_id; + v->dev_rx.sample = + evt_payload->voc_devinfo.dev_sample; + v->dev_rx.dev_id = + evt_payload->voc_devinfo.dev_id; + v->dev_rx.enabled = VOICE_DEV_ENABLED; + } else { + v->dev_tx.dev_acdb_id = + evt_payload->voc_devinfo.acdb_dev_id; + v->dev_tx.sample = + evt_payload->voc_devinfo.dev_sample; + v->dev_tx.enabled = VOICE_DEV_ENABLED; + v->dev_tx.dev_id = + evt_payload->voc_devinfo.dev_id; + } + if ((v->dev_rx.enabled == VOICE_DEV_ENABLED) && + (v->dev_tx.enabled == VOICE_DEV_ENABLED)) { + v->dev_state = DEV_READY; + MM_DBG("dev state into ready\n"); + voice_cmd_device_info(v); + wake_up(&v->dev_wait); + mutex_lock(&voice.voc_lock); + if (v->voc_state == VOICE_CHANGE) { + v->dev_event = DEV_CHANGE_READY; + complete(&v->complete); + } + mutex_unlock(&voice.voc_lock); + } + } else if ((v->dev_state == DEV_INIT) || + (v->dev_state == DEV_REL_DONE)) { + if (evt_payload->voc_devinfo.dev_type == DIR_RX) { + v->dev_rx.dev_acdb_id = + evt_payload->voc_devinfo.acdb_dev_id; + v->dev_rx.sample = + evt_payload->voc_devinfo.dev_sample; + v->dev_rx.dev_id = + evt_payload->voc_devinfo.dev_id; + v->dev_rx.enabled = VOICE_DEV_ENABLED; + } else { + v->dev_tx.dev_acdb_id = + evt_payload->voc_devinfo.acdb_dev_id; + v->dev_tx.sample = + evt_payload->voc_devinfo.dev_sample; + v->dev_tx.dev_id = + evt_payload->voc_devinfo.dev_id; + v->dev_tx.enabled = VOICE_DEV_ENABLED; + } + if ((v->dev_rx.enabled == VOICE_DEV_ENABLED) && + (v->dev_tx.enabled == VOICE_DEV_ENABLED) && + (v->v_call_status == VOICE_CALL_START)) { + v->dev_state = DEV_READY; + MM_DBG("dev state into ready\n"); + voice_cmd_device_info(v); + wake_up(&v->dev_wait); + mutex_lock(&voice.voc_lock); + if (v->voc_state == VOICE_CHANGE) { + v->dev_event = DEV_CHANGE_READY; + complete(&v->complete); + } + mutex_unlock(&voice.voc_lock); + } + } else + MM_ERR("Receive READY not at the proper state =%d\n", + v->dev_state); + break; + case AUDDEV_EVT_DEVICE_VOL_MUTE_CHG: + if (evt_payload->voc_devinfo.dev_type == DIR_TX) + v->dev_tx.mute = + evt_payload->voc_vm_info.dev_vm_val.mute; + else + v->dev_rx.volume = evt_payload-> + voc_vm_info.dev_vm_val.vol; + /* send device info */ + voice_cmd_device_info(v); + break; + case AUDDEV_EVT_REL_PENDING: + /* recover the tx mute and rx volume to the default values */ + if (v->dev_state == DEV_READY) { + if (atomic_read(&v->rel_start_flag)) { + atomic_dec(&v->rel_start_flag); + if (evt_payload->voc_devinfo.dev_type == DIR_RX) + v->dev_rx.enabled = VOICE_DEV_DISABLED; + else + v->dev_tx.enabled = VOICE_DEV_DISABLED; + v->dev_state = DEV_REL_DONE; + wake_up(&v->dev_wait); + break; + } + mutex_lock(&voice.voc_lock); + if ((v->voc_state == VOICE_RELEASE) || + (v->voc_state == VOICE_INIT)) { + if (evt_payload->voc_devinfo.dev_type + == DIR_RX) { + v->dev_rx.enabled = VOICE_DEV_DISABLED; + } else { + v->dev_tx.enabled = VOICE_DEV_DISABLED; + } + v->dev_state = DEV_REL_DONE; + mutex_unlock(&voice.voc_lock); + wake_up(&v->dev_wait); + } else { + /* send device change to modem */ + voice_cmd_change(); + mutex_unlock(&voice.voc_lock); + rc = wait_event_interruptible( + v->voc_wait, (v->voc_state == VOICE_CHANGE) + || (atomic_read(&v->chg_start_flag) == 1) + || (atomic_read(&v->rel_start_flag) == 1)); + if (atomic_read(&v->rel_start_flag) == 1) + atomic_dec(&v->rel_start_flag); + /* clear Rx/Tx to Disable */ + if (evt_payload->voc_devinfo.dev_type == DIR_RX) + v->dev_rx.enabled = VOICE_DEV_DISABLED; + else + v->dev_tx.enabled = VOICE_DEV_DISABLED; + v->dev_state = DEV_REL_DONE; + wake_up(&v->dev_wait); + } + } else if ((v->dev_state == DEV_INIT) || + (v->dev_state == DEV_REL_DONE)) { + if (evt_payload->voc_devinfo.dev_type == DIR_RX) + v->dev_rx.enabled = VOICE_DEV_DISABLED; + else + v->dev_tx.enabled = VOICE_DEV_DISABLED; + } + break; + case AUDDEV_EVT_END_VOICE: + /* recover the tx mute and rx volume to the default values */ + v->dev_tx.mute = v->default_mute_val; + v->dev_rx.volume = v->default_vol_val; + + if (v->dev_rx.enabled == VOICE_DEV_ENABLED) + msm_snddev_enable_sidetone(v->dev_rx.dev_id, 0); + + if ((v->dev_state == DEV_READY) || + (v->dev_state == DEV_CHANGE)) { + if (atomic_read(&v->rel_start_flag)) { + atomic_dec(&v->rel_start_flag); + v->v_call_status = VOICE_CALL_END; + v->dev_state = DEV_REL_DONE; + wake_up(&v->dev_wait); + break; + } + mutex_lock(&voice.voc_lock); + if ((v->voc_state == VOICE_RELEASE) || + (v->voc_state == VOICE_INIT)) { + v->v_call_status = VOICE_CALL_END; + v->dev_state = DEV_REL_DONE; + mutex_unlock(&voice.voc_lock); + wake_up(&v->dev_wait); + } else { + /* send mute and default volume value to MCAD */ + voice_cmd_device_info(v); + /* send device change to modem */ + voice_cmd_change(); + mutex_unlock(&voice.voc_lock); + /* block to wait for RELEASE_START + or CHANGE_START */ + rc = wait_event_interruptible( + v->voc_wait, (v->voc_state == VOICE_CHANGE) + || (atomic_read(&v->chg_start_flag) == 1) + || (atomic_read(&v->rel_start_flag) == 1)); + if (atomic_read(&v->rel_start_flag) == 1) + atomic_dec(&v->rel_start_flag); + /* set voice call to END state */ + v->v_call_status = VOICE_CALL_END; + v->dev_state = DEV_REL_DONE; + wake_up(&v->dev_wait); + } + } else + v->v_call_status = VOICE_CALL_END; + break; + case AUDDEV_EVT_FREQ_CHG: + MM_DBG("Voice Driver got sample rate change Event\n"); + MM_DBG("sample rate %d\n", evt_payload->freq_info.sample_rate); + MM_DBG("dev_type %d\n", evt_payload->freq_info.dev_type); + MM_DBG("acdb_dev_id %d\n", evt_payload->freq_info.acdb_dev_id); + if (v->dev_state == DEV_READY) { + v->dev_tx.enabled = VOICE_DEV_DISABLED; + v->dev_state = DEV_CHANGE; + mutex_lock(&voice.voc_lock); + if (v->voc_state == VOICE_ACQUIRE) { + msm_snddev_enable_sidetone(v->dev_rx.dev_id, + 0); + /* send device change to modem */ + voice_cmd_change(); + mutex_unlock(&voice.voc_lock); + /* block to wait for CHANGE_START */ + rc = wait_event_interruptible( + v->voc_wait, (v->voc_state == VOICE_CHANGE) + || (atomic_read(&v->chg_start_flag) == 1) + || (atomic_read(&v->rel_start_flag) == 1)); + } else { + mutex_unlock(&voice.voc_lock); + MM_ERR(" Voice is not at ACQUIRE state\n"); + } + } else if ((v->dev_state == DEV_INIT) || + (v->dev_state == DEV_REL_DONE)) { + v->dev_tx.enabled = VOICE_DEV_DISABLED; + } else + MM_ERR("Event not at the proper state =%d\n", + v->dev_state); + break; + default: + MM_ERR("UNKNOWN EVENT\n"); + } + return; +} +EXPORT_SYMBOL(voice_auddev_cb_function); + +static void remote_cb_function(void *context, u32 param, + void *evt_buf, u32 len) +{ + struct voice_header *hdr; + struct voice_data *v = context; + + hdr = (struct voice_header *)evt_buf; + + MM_INFO("len=%d id=%d\n", len, hdr->id); + + if (len <= 0) { + MM_ERR("unexpected event with length %d \n", len); + return; + } + + switch (hdr->id) { + case EVENT_ACQUIRE_START: + atomic_inc(&v->acq_start_flag); + wake_up(&v->dev_wait); + v->voc_event = VOICE_ACQUIRE_START; + v->network = ((struct voice_network *)evt_buf)->network_info; + complete(&v->complete); + break; + case EVENT_RELEASE_START: + /* If ACQUIRED come in before the RELEASE, + * will only services the RELEASE */ + atomic_inc(&v->rel_start_flag); + wake_up(&v->voc_wait); + wake_up(&v->dev_wait); + v->voc_event = VOICE_RELEASE_START; + complete(&v->complete); + break; + case EVENT_CHANGE_START: + atomic_inc(&v->chg_start_flag); + wake_up(&v->voc_wait); + v->voc_event = VOICE_CHANGE_START; + complete(&v->complete); + break; + case EVENT_NETWORK_RECONFIG: + /* send network change to audio_dev, + if sample rate is less than 16k, + otherwise, send acquire done */ + v->voc_event = VOICE_NETWORK_RECONFIG; + v->network = ((struct voice_network *)evt_buf)->network_info; + complete(&v->complete); + break; + default: + MM_ERR("Undefined event %d \n", hdr->id); + } + +} + +static int voice_cmd_init(struct voice_data *v) +{ + + struct voice_init cmd; + int err; + + MM_DBG("\n"); /* Macro prints the file name and function */ + + cmd.hdr.id = CMD_VOICE_INIT; + cmd.hdr.data_len = sizeof(struct voice_init) - + sizeof(struct voice_header); + cmd.cb_handle = v->cb_handle; + + err = dalrpc_fcn_5(VOICE_DALRPC_CMD, v->handle, &cmd, + sizeof(struct voice_init)); + + if (err) + MM_ERR("Voice init command failed\n"); + return err; +} + +static int voice_cmd_acquire_done(struct voice_data *v) +{ + struct voice_header hdr; + int err; + + hdr.id = CMD_ACQUIRE_DONE; + hdr.data_len = 0; + + MM_INFO("\n"); /* Macro prints the file name and function */ + + /* Enable HW sidetone if device supports it */ + msm_snddev_enable_sidetone(v->dev_rx.dev_id, 1); + + err = dalrpc_fcn_5(VOICE_DALRPC_CMD, v->handle, &hdr, + sizeof(struct voice_header)); + + if (err) + MM_ERR("Voice acquire done command failed\n"); + return err; +} + +static int voice_cmd_device_info(struct voice_data *v) +{ + struct voice_device cmd; + int err, vol; + + MM_INFO("tx_dev=%d, rx_dev=%d, tx_sample=%d, tx_mute=%d\n", + v->dev_tx.dev_acdb_id, v->dev_rx.dev_acdb_id, + v->dev_tx.sample, v->dev_tx.mute); + + mutex_lock(&voice.vol_lock); + + cmd.hdr.id = CMD_DEVICE_INFO; + cmd.hdr.data_len = sizeof(struct voice_device) - + sizeof(struct voice_header); + cmd.tx_device = v->dev_tx.dev_acdb_id; + cmd.rx_device = v->dev_rx.dev_acdb_id; + if (v->network == NETWORK_WCDMA_WB) + vol = v->min_rx_vol[VOC_WB_INDEX] + + ((v->max_rx_vol[VOC_WB_INDEX] - + v->min_rx_vol[VOC_WB_INDEX]) * v->dev_rx.volume)/100; + else + vol = v->min_rx_vol[VOC_NB_INDEX] + + ((v->max_rx_vol[VOC_NB_INDEX] - + v->min_rx_vol[VOC_NB_INDEX]) * v->dev_rx.volume)/100; + cmd.rx_volume = (u32)vol; /* in mb */ + cmd.rx_mute = 0; + cmd.tx_mute = v->dev_tx.mute; + cmd.rx_sample = v->dev_rx.sample/1000; + cmd.tx_sample = v->dev_tx.sample/1000; + + MM_DBG("rx_vol=%d, rx_sample=%d\n", cmd.rx_volume, v->dev_rx.sample); + + err = dalrpc_fcn_5(VOICE_DALRPC_CMD, v->handle, &cmd, + sizeof(struct voice_device)); + + mutex_unlock(&voice.vol_lock); + + if (err) + MM_ERR("Voice device command failed\n"); + return err; +} +EXPORT_SYMBOL(voice_cmd_device_info); + +void voice_change_sample_rate(struct voice_data *v) +{ + int freq = 48000; + int rc = 0; + + MM_DBG("network =%d, vote freq=%d\n", v->network, freq); + if (freq != v->dev_tx.sample) { + rc = msm_snddev_request_freq(&freq, 0, + SNDDEV_CAP_TX, AUDDEV_CLNT_VOC); + if (rc >= 0) { + v->dev_tx.sample = freq; + MM_DBG(" vote for freq=%d successfully \n", freq); + } else + MM_ERR(" voting for freq=%d failed.\n", freq); + } +} + +static int voice_thread(void *data) +{ + struct voice_data *v = (struct voice_data *)data; + int rc = 0; + + MM_INFO("voice_thread() start\n"); + + while (!kthread_should_stop()) { + wait_for_completion(&v->complete); + init_completion(&v->complete); + + MM_DBG(" voc_event=%d, voice state =%d, dev_event=%d\n", + v->voc_event, v->voc_state, v->dev_event); + switch (v->voc_event) { + case VOICE_ACQUIRE_START: + /* check if dev_state = READY */ + /* if ready, send device_info and acquire_done */ + /* if not ready, block to wait the dev_state = READY */ + if ((v->voc_state == VOICE_INIT) || + (v->voc_state == VOICE_RELEASE)) { + if (v->dev_state == DEV_READY) { + mutex_lock(&voice.voc_lock); + voice_change_sample_rate(v); + rc = voice_cmd_device_info(v); + rc = voice_cmd_acquire_done(v); + v->voc_state = VOICE_ACQUIRE; + mutex_unlock(&voice.voc_lock); + broadcast_event( + AUDDEV_EVT_VOICE_STATE_CHG, + VOICE_STATE_INCALL, SESSION_IGNORE); + } else { + rc = wait_event_interruptible( + v->dev_wait, + (v->dev_state == DEV_READY) + || (atomic_read(&v->rel_start_flag) + == 1)); + if (atomic_read(&v->rel_start_flag) + == 1) { + v->voc_state = VOICE_RELEASE; + atomic_dec(&v->rel_start_flag); + msm_snddev_withdraw_freq(0, + SNDDEV_CAP_TX, AUDDEV_CLNT_VOC); + broadcast_event( + AUDDEV_EVT_VOICE_STATE_CHG, + VOICE_STATE_OFFCALL, + SESSION_IGNORE); + } else { + mutex_lock(&voice.voc_lock); + voice_change_sample_rate(v); + rc = voice_cmd_device_info(v); + rc = voice_cmd_acquire_done(v); + v->voc_state = VOICE_ACQUIRE; + mutex_unlock(&voice.voc_lock); + broadcast_event( + AUDDEV_EVT_VOICE_STATE_CHG, + VOICE_STATE_INCALL, + SESSION_IGNORE); + } + } + } else + MM_ERR("Get this event at the wrong state\n"); + if (atomic_read(&v->acq_start_flag)) + atomic_dec(&v->acq_start_flag); + break; + case VOICE_RELEASE_START: + MM_DBG("broadcast voice call end\n"); + broadcast_event(AUDDEV_EVT_VOICE_STATE_CHG, + VOICE_STATE_OFFCALL, SESSION_IGNORE); + if ((v->dev_state == DEV_REL_DONE) || + (v->dev_state == DEV_INIT)) { + v->voc_state = VOICE_RELEASE; + msm_snddev_withdraw_freq(0, SNDDEV_CAP_TX, + AUDDEV_CLNT_VOC); + } else { + /* wait for the dev_state = RELEASE */ + rc = wait_event_interruptible(v->dev_wait, + (v->dev_state == DEV_REL_DONE) + || (atomic_read(&v->acq_start_flag) == 1)); + if (atomic_read(&v->acq_start_flag) == 1) + atomic_dec(&v->acq_start_flag); + v->voc_state = VOICE_RELEASE; + msm_snddev_withdraw_freq(0, SNDDEV_CAP_TX, + AUDDEV_CLNT_VOC); + } + if (atomic_read(&v->rel_start_flag)) + atomic_dec(&v->rel_start_flag); + break; + case VOICE_CHANGE_START: + if (v->voc_state == VOICE_ACQUIRE) + v->voc_state = VOICE_CHANGE; + else + MM_ERR("Get this event at the wrong state\n"); + wake_up(&v->voc_wait); + if (atomic_read(&v->chg_start_flag)) + atomic_dec(&v->chg_start_flag); + break; + case VOICE_NETWORK_RECONFIG: + if ((v->voc_state == VOICE_ACQUIRE) + || (v->voc_state == VOICE_CHANGE)) { + voice_change_sample_rate(v); + rc = voice_cmd_device_info(v); + rc = voice_cmd_acquire_done(v); + } + break; + default: + break; + } + + switch (v->dev_event) { + case DEV_CHANGE_READY: + if (v->voc_state == VOICE_CHANGE) { + mutex_lock(&voice.voc_lock); + msm_snddev_enable_sidetone(v->dev_rx.dev_id, + 1); + /* update voice state */ + v->voc_state = VOICE_ACQUIRE; + v->dev_event = 0; + mutex_unlock(&voice.voc_lock); + broadcast_event(AUDDEV_EVT_VOICE_STATE_CHG, + VOICE_STATE_INCALL, SESSION_IGNORE); + } else { + mutex_lock(&voice.voc_lock); + v->dev_event = 0; + mutex_unlock(&voice.voc_lock); + MM_ERR("Get this event at the wrong state\n"); + } + break; + default: + mutex_lock(&voice.voc_lock); + v->dev_event = 0; + mutex_unlock(&voice.voc_lock); + break; + } + } + return 0; +} + +static int __init voice_init(void) +{ + int rc, i; + struct voice_data *v = &voice; + MM_INFO("\n"); /* Macro prints the file name and function */ + + mutex_init(&voice.voc_lock); + mutex_init(&voice.vol_lock); + v->handle = NULL; + v->cb_handle = NULL; + + /* set default value */ + v->default_mute_val = 1; /* default is mute */ + v->default_vol_val = 0; + v->default_sample_val = 8000; + for (i = 0; i < VOC_RX_VOL_ARRAY_NUM; i++) { + v->max_rx_vol[i] = 0; + v->min_rx_vol[i] = 0; + } + v->network = NETWORK_GSM; + + /* initialize dev_rx and dev_tx */ + memset(&v->dev_tx, 0, sizeof(struct device_data)); + memset(&v->dev_rx, 0, sizeof(struct device_data)); + v->dev_rx.volume = v->default_vol_val; + v->dev_tx.mute = v->default_mute_val; + + v->dev_state = DEV_INIT; + v->voc_state = VOICE_INIT; + atomic_set(&v->rel_start_flag, 0); + atomic_set(&v->acq_start_flag, 0); + v->dev_event = 0; + v->voc_event = 0; + init_completion(&voice.complete); + init_waitqueue_head(&v->dev_wait); + init_waitqueue_head(&v->voc_wait); + + /* get device handle */ + rc = daldevice_attach(VOICE_DALRPC_DEVICEID, + VOICE_DALRPC_PORT_NAME, + VOICE_DALRPC_CPU, + &v->handle); + if (rc) { + MM_ERR("Voc DALRPC call to Modem attach failed\n"); + goto done; + } + + /* Allocate the callback handle */ + v->cb_handle = dalrpc_alloc_cb(v->handle, remote_cb_function, v); + if (v->cb_handle == NULL) { + MM_ERR("Allocate Callback failure\n"); + goto err; + } + + /* setup the callback */ + rc = voice_cmd_init(v); + if (rc) + goto err1; + + v->device_events = AUDDEV_EVT_DEV_CHG_VOICE | + AUDDEV_EVT_DEV_RDY | + AUDDEV_EVT_REL_PENDING | + AUDDEV_EVT_START_VOICE | + AUDDEV_EVT_END_VOICE | + AUDDEV_EVT_DEVICE_VOL_MUTE_CHG | + AUDDEV_EVT_FREQ_CHG; + + MM_DBG(" to register call back \n"); + /* register callback to auddev */ + auddev_register_evt_listner(v->device_events, AUDDEV_CLNT_VOC, + 0, voice_auddev_cb_function, v); + + /* create and start thread */ + v->task = kthread_run(voice_thread, v, "voice"); + if (IS_ERR(v->task)) { + rc = PTR_ERR(v->task); + v->task = NULL; + } else + goto done; + +err1: dalrpc_dealloc_cb(v->handle, v->cb_handle); +err: + daldevice_detach(v->handle); + v->handle = NULL; +done: + return rc; +} + +late_initcall(voice_init); diff --git a/arch/arm/mach-msm/qdsp6/Makefile b/arch/arm/mach-msm/qdsp6/Makefile new file mode 100644 index 00000000000..9a5561261bf --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/Makefile @@ -0,0 +1,19 @@ +obj-y += dal.o +obj-y += q6audio.o +obj-y += analog_audio.o +obj-y += pcm_out.o +obj-y += pcm_in.o +obj-y += auxpcm_lb_out.o +obj-y += auxpcm_lb_in.o +obj-y += aac_in.o +obj-y += qcelp_in.o +obj-y += evrc_in.o +obj-y += amrnb_in.o +obj-y += mp3.o +obj-y += dtmf.o +obj-y += routing.o +obj-y += audio_ctl.o +obj-y += msm_q6vdec.o +obj-y += msm_q6venc.o +obj-y += dsp_debug.o +obj-$(CONFIG_QSD_AUDIO) += audiov2/ diff --git a/arch/arm/mach-msm/qdsp6/aac_in.c b/arch/arm/mach-msm/qdsp6/aac_in.c new file mode 100644 index 00000000000..9e1d5b6cdfc --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/aac_in.c @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define AAC_FC_BUFF_CNT 10 +#define AAC_READ_TIMEOUT 2000 +struct aac_fc_buff { + struct mutex lock; + int empty; + void *data; + int size; + int actual_size; +}; + +struct aac_fc { + struct task_struct *task; + wait_queue_head_t fc_wq; + struct aac_fc_buff fc_buff[AAC_FC_BUFF_CNT]; + int buff_index; +}; +struct aac { + struct mutex lock; + struct msm_audio_aac_enc_config cfg; + struct msm_audio_stream_config str_cfg; + struct audio_client *audio_client; + struct msm_voicerec_mode voicerec_mode; + struct aac_fc *aac_fc; +}; + +static int q6_aac_flowcontrol(void *data) +{ + struct audio_client *ac; + struct audio_buffer *ab; + struct aac *aac = data; + int buff_index = 0; + int xfer = 0; + struct aac_fc *fc; + + + ac = aac->audio_client; + fc = aac->aac_fc; + if (!ac) { + pr_err("[%s:%s] audio_client is NULL\n", __MM_FILE__, __func__); + return 0; + } + + while (!kthread_should_stop()) { + ab = ac->buf + ac->cpu_buf; + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + pr_debug("[%s:%s] ab->data = %p, cpu_buf = %d\n", __MM_FILE__, + __func__, ab->data, ac->cpu_buf); + xfer = ab->actual_size; + + mutex_lock(&(fc->fc_buff[buff_index].lock)); + if (!fc->fc_buff[buff_index].empty) { + pr_err("[%s:%s] flow control buffer[%d] not read!\n", + __MM_FILE__, __func__, buff_index); + } + + if (fc->fc_buff[buff_index].size < xfer) { + pr_err("[%s:%s] buffer %d too small\n", __MM_FILE__, + __func__, buff_index); + memcpy(fc->fc_buff[buff_index].data, + ab->data, fc->fc_buff[buff_index].size); + fc->fc_buff[buff_index].empty = 0; + fc->fc_buff[buff_index].actual_size = + fc->fc_buff[buff_index].size; + } else { + memcpy(fc->fc_buff[buff_index].data, ab->data, xfer); + fc->fc_buff[buff_index].empty = 0; + fc->fc_buff[buff_index].actual_size = xfer; + } + mutex_unlock(&(fc->fc_buff[buff_index].lock)); + /*wake up client, if any*/ + wake_up(&fc->fc_wq); + + buff_index++; + if (buff_index >= AAC_FC_BUFF_CNT) + buff_index = 0; + + ab->used = 1; + + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } + + return 0; +} +static long q6_aac_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct aac *aac = file->private_data; + int rc = 0; + int i = 0; + struct aac_fc *fc; + int size = 0; + + mutex_lock(&aac->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: + break; + case AUDIO_GET_STATS: + { + struct msm_audio_stats stats; + pr_debug("[%s:%s] GET_STATS\n", __MM_FILE__, __func__); + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + case AUDIO_START: + { + uint32_t acdb_id; + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + if (arg == 0) { + acdb_id = 0; + } else { + if (copy_from_user(&acdb_id, (void *) arg, + sizeof(acdb_id))) { + rc = -EFAULT; + break; + } + } + if (aac->audio_client) { + rc = -EBUSY; + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + break; + } else { + aac->audio_client = q6audio_open_aac( + aac->str_cfg.buffer_size, + aac->cfg.sample_rate, + aac->cfg.channels, + aac->cfg.bit_rate, + aac->cfg.stream_format, + aac->voicerec_mode.rec_mode, acdb_id); + + if (aac->audio_client < 0) { + pr_err("[%s:%s] aac open session failed\n", + __MM_FILE__, __func__); + rc = -ENOMEM; + break; + } + } + + /*allocate flow control buffers*/ + fc = aac->aac_fc; + size = ((aac->str_cfg.buffer_size < 1543) ? 1543 : + aac->str_cfg.buffer_size); + for (i = 0; i < AAC_FC_BUFF_CNT; ++i) { + mutex_init(&(fc->fc_buff[i].lock)); + fc->fc_buff[i].empty = 1; + fc->fc_buff[i].data = kmalloc(size, GFP_KERNEL); + if (fc->fc_buff[i].data == NULL) { + pr_err("[%s:%s] No memory for FC buffers\n", + __MM_FILE__, __func__); + rc = -ENOMEM; + goto fc_fail; + } + fc->fc_buff[i].size = size; + fc->fc_buff[i].actual_size = 0; + } + + /*create flow control thread*/ + fc->task = kthread_run(q6_aac_flowcontrol, + aac, "aac_flowcontrol"); + if (IS_ERR(fc->task)) { + rc = PTR_ERR(fc->task); + pr_err("[%s:%s] error creating flow control thread\n", + __MM_FILE__, __func__); + goto fc_fail; + } + break; +fc_fail: + /*free flow control buffers*/ + --i; + for (; i >= 0; i--) { + kfree(fc->fc_buff[i].data); + fc->fc_buff[i].data = NULL; + } + break; + } + case AUDIO_STOP: + pr_debug("[%s:%s] AUDIO_STOP\n", __MM_FILE__, __func__); + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_INCALL: { + pr_debug("[%s:%s] SET_INCALL\n", __MM_FILE__, __func__); + if (copy_from_user(&aac->voicerec_mode, + (void *)arg, sizeof(struct msm_voicerec_mode))) + rc = -EFAULT; + + if (aac->voicerec_mode.rec_mode != AUDIO_FLAG_READ + && aac->voicerec_mode.rec_mode != + AUDIO_FLAG_INCALL_MIXED) { + aac->voicerec_mode.rec_mode = AUDIO_FLAG_READ; + pr_err("[%s:%s] Invalid rec_mode\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + break; + } + case AUDIO_GET_STREAM_CONFIG: + if (copy_to_user((void *)arg, &aac->str_cfg, + sizeof(struct msm_audio_stream_config))) + rc = -EFAULT; + pr_debug("[%s:%s] GET_STREAM_CONFIG: buffsz=%d, buffcnt=%d\n", + __MM_FILE__, __func__, aac->str_cfg.buffer_size, + aac->str_cfg.buffer_count); + break; + case AUDIO_SET_STREAM_CONFIG: + if (copy_from_user(&aac->str_cfg, (void *)arg, + sizeof(struct msm_audio_stream_config))) { + rc = -EFAULT; + break; + } + pr_debug("[%s:%s] SET_STREAM_CONFIG: buffsz=%d, buffcnt=%d\n", + __MM_FILE__, __func__, aac->str_cfg.buffer_size, + aac->str_cfg.buffer_count); + if (aac->str_cfg.buffer_size < 1543) { + pr_err("[%s:%s] Buffer size too small\n", __MM_FILE__, + __func__); + rc = -EINVAL; + break; + } + if (aac->str_cfg.buffer_count != 2) + pr_info("[%s:%s] Buffer count set to 2\n", __MM_FILE__, + __func__); + + break; + case AUDIO_SET_AAC_ENC_CONFIG: + if (copy_from_user(&aac->cfg, (void *) arg, + sizeof(struct msm_audio_aac_enc_config))) { + rc = -EFAULT; + } + pr_debug("[%s:%s] SET_AAC_ENC_CONFIG: channels=%d, rate=%d\n", + __MM_FILE__, __func__, aac->cfg.channels, + aac->cfg.sample_rate); + if (aac->cfg.channels < 1 || aac->cfg.channels > 2) { + pr_err("[%s:%s]invalid number of channels\n", + __MM_FILE__, __func__); + rc = -EINVAL; + } + if (aac->cfg.sample_rate != 48000) { + pr_err("[%s:%s] only 48KHz is supported\n", + __MM_FILE__, __func__); + rc = -EINVAL; + } + if (aac->cfg.stream_format != AUDIO_AAC_FORMAT_RAW && + aac->cfg.stream_format != AUDIO_AAC_FORMAT_ADTS) { + pr_err("[%s:%s] unsupported AAC format\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + break; + case AUDIO_GET_AAC_ENC_CONFIG: + if (copy_to_user((void *) arg, &aac->cfg, + sizeof(struct msm_audio_aac_enc_config))) { + rc = -EFAULT; + } + pr_debug("[%s:%s] GET_AAC_ENC_CONFIG: channels=%d, rate=%d\n", + __MM_FILE__, __func__, aac->cfg.channels, + aac->cfg.sample_rate); + break; + default: + rc = -EINVAL; + } + + mutex_unlock(&aac->lock); + pr_debug("[%s:%s] rc = %d\n", __MM_FILE__, __func__, rc); + return rc; +} + +static int q6_aac_in_open(struct inode *inode, struct file *file) +{ + + struct aac *aac; + struct aac_fc *fc; + int i; + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + aac = kmalloc(sizeof(struct aac), GFP_KERNEL); + if (aac == NULL) { + pr_err("[%s:%s] Could not allocate memory for aac driver\n", + __MM_FILE__, __func__); + return -ENOMEM; + } + + mutex_init(&aac->lock); + file->private_data = aac; + aac->audio_client = NULL; + aac->str_cfg.buffer_size = 1543; + aac->str_cfg.buffer_count = 2; + aac->cfg.channels = 1; + aac->cfg.bit_rate = 192000; + aac->cfg.stream_format = AUDIO_AAC_FORMAT_ADTS; + aac->cfg.sample_rate = 48000; + aac->voicerec_mode.rec_mode = AUDIO_FLAG_READ; + + aac->aac_fc = kmalloc(sizeof(struct aac_fc), GFP_KERNEL); + if (aac->aac_fc == NULL) { + pr_err("[%s:%s] Could not allocate memory for aac_fc\n", + __MM_FILE__, __func__); + kfree(aac); + return -ENOMEM; + } + fc = aac->aac_fc; + fc->task = NULL; + fc->buff_index = 0; + for (i = 0; i < AAC_FC_BUFF_CNT; ++i) { + fc->fc_buff[i].data = NULL; + fc->fc_buff[i].size = 0; + fc->fc_buff[i].actual_size = 0; + } + /*initialize wait queue head*/ + init_waitqueue_head(&fc->fc_wq); + return 0; +} + +static ssize_t q6_aac_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + const char __user *start = buf; + struct aac *aac = file->private_data; + struct aac_fc *fc; + int xfer = 0; + int res = 0; + + pr_debug("[%s:%s] count = %d\n", __MM_FILE__, __func__, count); + mutex_lock(&aac->lock); + ac = aac->audio_client; + + if (!ac) { + res = -ENODEV; + goto fail; + } + fc = aac->aac_fc; + + /*wait for buffer to full*/ + if (fc->fc_buff[fc->buff_index].empty != 0) { + res = wait_event_interruptible_timeout(fc->fc_wq, + (fc->fc_buff[fc->buff_index].empty == 0), + msecs_to_jiffies(AAC_READ_TIMEOUT)); + + pr_debug("[%s:%s] buff_index = %d\n", __MM_FILE__, + __func__, fc->buff_index); + if (res == 0) { + pr_err("[%s:%s] Timeout!\n", __MM_FILE__, __func__); + res = -ETIMEDOUT; + goto fail; + } else if (res < 0) { + pr_err("[%s:%s] Returning on Interrupt\n", __MM_FILE__, + __func__); + goto fail; + } + } + /*lock the buffer*/ + mutex_lock(&(fc->fc_buff[fc->buff_index].lock)); + xfer = fc->fc_buff[fc->buff_index].actual_size; + + if (xfer > count) { + mutex_unlock(&(fc->fc_buff[fc->buff_index].lock)); + pr_err("[%s:%s] read failed! byte count too small\n", + __MM_FILE__, __func__); + res = -EINVAL; + goto fail; + } + + if (copy_to_user(buf, fc->fc_buff[fc->buff_index].data, xfer)) { + mutex_unlock(&(fc->fc_buff[fc->buff_index].lock)); + pr_err("[%s:%s] copy_to_user failed at index %d\n", + __MM_FILE__, __func__, fc->buff_index); + res = -EFAULT; + goto fail; + } + + buf += xfer; + + fc->fc_buff[fc->buff_index].empty = 1; + fc->fc_buff[fc->buff_index].actual_size = 0; + + mutex_unlock(&(fc->fc_buff[fc->buff_index].lock)); + ++(fc->buff_index); + if (fc->buff_index >= AAC_FC_BUFF_CNT) + fc->buff_index = 0; + + res = buf - start; +fail: + mutex_unlock(&aac->lock); + + return res; +} + +static int q6_aac_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct aac *aac = file->private_data; + int i = 0; + struct aac_fc *fc; + + mutex_lock(&aac->lock); + fc = aac->aac_fc; + kthread_stop(fc->task); + fc->task = NULL; + + /*free flow control buffers*/ + for (i = 0; i < AAC_FC_BUFF_CNT; ++i) { + kfree(fc->fc_buff[i].data); + fc->fc_buff[i].data = NULL; + } + kfree(fc); + if (aac->audio_client) + rc = q6audio_close(aac->audio_client); + mutex_unlock(&aac->lock); + kfree(aac); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return rc; +} + +static const struct file_operations q6_aac_in_fops = { + .owner = THIS_MODULE, + .open = q6_aac_in_open, + .read = q6_aac_in_read, + .release = q6_aac_in_release, + .unlocked_ioctl = q6_aac_in_ioctl, +}; + +struct miscdevice q6_aac_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac_in", + .fops = &q6_aac_in_fops, +}; + +static int __init q6_aac_in_init(void) +{ + return misc_register(&q6_aac_in_misc); +} + +device_initcall(q6_aac_in_init); diff --git a/arch/arm/mach-msm/qdsp6/amrnb_in.c b/arch/arm/mach-msm/qdsp6/amrnb_in.c new file mode 100644 index 00000000000..e7756e14831 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/amrnb_in.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "dal_audio_format.h" +#include + +struct amrnb { + struct mutex lock; + struct msm_audio_amrnb_enc_config_v2 cfg; + struct msm_audio_stream_config str_cfg; + struct audio_client *audio_client; + struct msm_voicerec_mode voicerec_mode; +}; + + +static long q6_amrnb_in_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct amrnb *amrnb = file->private_data; + int rc = 0; + + mutex_lock(&amrnb->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: + pr_debug("[%s:%s] SET_VOLUME\n", __MM_FILE__, __func__); + break; + case AUDIO_GET_STATS: + { + struct msm_audio_stats stats; + pr_debug("[%s:%s] GET_STATS\n", __MM_FILE__, __func__); + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + case AUDIO_START: + { + uint32_t acdb_id; + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + if (arg == 0) { + acdb_id = 0; + } else { + if (copy_from_user(&acdb_id, (void *) arg, + sizeof(acdb_id))) { + rc = -EFAULT; + break; + } + } + if (amrnb->audio_client) { + rc = -EBUSY; + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + break; + } else { + amrnb->audio_client = q6audio_open_amrnb( + amrnb->str_cfg.buffer_size, + amrnb->cfg.band_mode, + amrnb->cfg.dtx_enable, + amrnb->voicerec_mode.rec_mode, + acdb_id); + if (!amrnb->audio_client) { + pr_err("[%s:%s] amrnb open session failed\n", + __MM_FILE__, __func__); + kfree(amrnb); + rc = -ENOMEM; + break; + } + } + break; + } + case AUDIO_STOP: + pr_debug("[%s:%s] AUDIO_STOP\n", __MM_FILE__, __func__); + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_INCALL: { + pr_debug("[%s:%s] SET_INCALL\n", __MM_FILE__, __func__); + if (copy_from_user(&amrnb->voicerec_mode, + (void *)arg, sizeof(struct msm_voicerec_mode))) + rc = -EFAULT; + + if (amrnb->voicerec_mode.rec_mode != AUDIO_FLAG_READ + && amrnb->voicerec_mode.rec_mode != + AUDIO_FLAG_INCALL_MIXED) { + amrnb->voicerec_mode.rec_mode = AUDIO_FLAG_READ; + pr_err("[%s:%s] Invalid rec_mode\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + break; + } + case AUDIO_GET_STREAM_CONFIG: + if (copy_to_user((void *)arg, &amrnb->str_cfg, + sizeof(struct msm_audio_stream_config))) + rc = -EFAULT; + pr_debug("[%s:%s] GET_STREAM_CONFIG: buffsz=%d, buffcnt = %d\n", + __MM_FILE__, __func__, amrnb->str_cfg.buffer_size, + amrnb->str_cfg.buffer_count); + break; + case AUDIO_SET_STREAM_CONFIG: + if (copy_from_user(&amrnb->str_cfg, (void *)arg, + sizeof(struct msm_audio_stream_config))) { + rc = -EFAULT; + break; + } + pr_debug("[%s:%s] SET_STREAM_CONFIG: buffsz=%d, buffcnt = %d\n", + __MM_FILE__, __func__, amrnb->str_cfg.buffer_size, + amrnb->str_cfg.buffer_count); + + if (amrnb->str_cfg.buffer_size < 768) { + pr_err("[%s:%s] Buffer size too small\n", __MM_FILE__, + __func__); + rc = -EINVAL; + break; + } + + if (amrnb->str_cfg.buffer_count != 2) + pr_info("[%s:%s] Buffer count set to 2\n", __MM_FILE__, + __func__); + break; + case AUDIO_SET_AMRNB_ENC_CONFIG: + if (copy_from_user(&amrnb->cfg, (void *) arg, + sizeof(struct msm_audio_amrnb_enc_config_v2))) + rc = -EFAULT; + pr_debug("[%s:%s] SET_AMRNB_ENC_CONFIG\n", __MM_FILE__, + __func__); + break; + case AUDIO_GET_AMRNB_ENC_CONFIG: + if (copy_to_user((void *) arg, &amrnb->cfg, + sizeof(struct msm_audio_amrnb_enc_config_v2))) + rc = -EFAULT; + pr_debug("[%s:%s] GET_AMRNB_ENC_CONFIG\n", __MM_FILE__, + __func__); + break; + + default: + rc = -EINVAL; + } + + mutex_unlock(&amrnb->lock); + pr_debug("[%s:%s] rc= %d\n", __MM_FILE__, __func__, rc); + return rc; +} + +static int q6_amrnb_in_open(struct inode *inode, struct file *file) +{ + struct amrnb *amrnb; + + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + amrnb = kmalloc(sizeof(struct amrnb), GFP_KERNEL); + if (amrnb == NULL) { + pr_err("[%s:%s] Could not allocate memory for amrnb driver\n", + __MM_FILE__, __func__); + return -ENOMEM; + } + + mutex_init(&amrnb->lock); + file->private_data = amrnb; + amrnb->audio_client = NULL; + amrnb->str_cfg.buffer_size = 768; + amrnb->str_cfg.buffer_count = 2; + amrnb->cfg.band_mode = 7; + amrnb->cfg.dtx_enable = 3; + amrnb->cfg.frame_format = ADSP_AUDIO_FORMAT_AMRNB_FS; + amrnb->voicerec_mode.rec_mode = AUDIO_FLAG_READ; + + return 0; +} + +static ssize_t q6_amrnb_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + struct amrnb *amrnb = file->private_data; + int xfer = 0; + int res; + + pr_debug("[%s:%s] count = %d\n", __MM_FILE__, __func__, count); + mutex_lock(&amrnb->lock); + ac = amrnb->audio_client; + if (!ac) { + res = -ENODEV; + goto fail; + } + while (count > xfer) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + pr_debug("[%s:%s] ab->data = %p, cpu_buf = %d\n", __MM_FILE__, + __func__, ab->data, ac->cpu_buf); + xfer = ab->actual_size; + + if (copy_to_user(buf, ab->data, xfer)) { + pr_err("[%s:%s] copy_to_user failed\n", + __MM_FILE__, __func__); + res = -EFAULT; + goto fail; + } + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } + + res = buf - start; +fail: + mutex_unlock(&amrnb->lock); + + return res; +} + +static int q6_amrnb_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct amrnb *amrnb = file->private_data; + + mutex_lock(&amrnb->lock); + if (amrnb->audio_client) + rc = q6audio_close(amrnb->audio_client); + mutex_unlock(&amrnb->lock); + kfree(amrnb); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return rc; +} + +static const struct file_operations q6_amrnb_in_fops = { + .owner = THIS_MODULE, + .open = q6_amrnb_in_open, + .read = q6_amrnb_in_read, + .release = q6_amrnb_in_release, + .unlocked_ioctl = q6_amrnb_in_ioctl, +}; + +struct miscdevice q6_amrnb_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amr_in", + .fops = &q6_amrnb_in_fops, +}; + +static int __init q6_amrnb_in_init(void) +{ + return misc_register(&q6_amrnb_in_misc); +} + +device_initcall(q6_amrnb_in_init); diff --git a/arch/arm/mach-msm/qdsp6/analog_audio.c b/arch/arm/mach-msm/qdsp6/analog_audio.c new file mode 100644 index 00000000000..688f57e3410 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/analog_audio.c @@ -0,0 +1,94 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPIO_HEADSET_AMP 157 +#define GPIO_SPEAKER_AMP 39 +#define GPIO_HEADSET_SHDN_N 48 + +void analog_init(void) +{ + /* stereo pmic init */ + pmic_spkr_set_gain(LEFT_SPKR, SPKR_GAIN_PLUS12DB); + pmic_spkr_set_gain(RIGHT_SPKR, SPKR_GAIN_PLUS12DB); + pmic_mic_set_volt(MIC_VOLT_1_80V); + gpio_direction_output(GPIO_HEADSET_AMP, 1); + gpio_set_value(GPIO_HEADSET_AMP, 0); +} + +void analog_headset_enable(int en) +{ + pr_debug("[%s:%s] en = %d\n", __MM_FILE__, __func__, en); + /* enable audio amp */ + gpio_set_value(GPIO_HEADSET_AMP, !!en); +} + +void analog_speaker_enable(int en) +{ + struct spkr_config_mode scm; + memset(&scm, 0, sizeof(scm)); + + pr_debug("[%s:%s] en = %d\n", __MM_FILE__, __func__, en); + if (en) { + scm.is_right_chan_en = 1; + scm.is_left_chan_en = 1; + scm.is_stereo_en = 1; + scm.is_hpf_en = 1; + pmic_spkr_en_mute(LEFT_SPKR, 0); + pmic_spkr_en_mute(RIGHT_SPKR, 0); + pmic_set_spkr_configuration(&scm); + pmic_spkr_en(LEFT_SPKR, 1); + pmic_spkr_en(RIGHT_SPKR, 1); + + /* unmute */ + pmic_spkr_en_mute(LEFT_SPKR, 1); + pmic_spkr_en_mute(RIGHT_SPKR, 1); + } else { + pmic_spkr_en_mute(LEFT_SPKR, 0); + pmic_spkr_en_mute(RIGHT_SPKR, 0); + + pmic_spkr_en(LEFT_SPKR, 0); + pmic_spkr_en(RIGHT_SPKR, 0); + + pmic_set_spkr_configuration(&scm); + } +} + +void analog_mic_enable(int en) +{ + pr_debug("[%s:%s] en = %d\n", __MM_FILE__, __func__, en); + pmic_mic_en(en); +} + +static struct q6audio_analog_ops ops = { + .init = analog_init, + .speaker_enable = analog_speaker_enable, + .headset_enable = analog_headset_enable, + .int_mic_enable = analog_mic_enable, + .ext_mic_enable = analog_mic_enable, +}; + +static int __init init(void) +{ + q6audio_register_analog_ops(&ops); + return 0; +} + +device_initcall(init); diff --git a/arch/arm/mach-msm/qdsp6/audio_ctl.c b/arch/arm/mach-msm/qdsp6/audio_ctl.c new file mode 100644 index 00000000000..ab1df3992e4 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audio_ctl.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define BUFSZ (0) + +static DEFINE_MUTEX(voice_lock); +static int voice_started; + +static struct audio_client *voc_tx_clnt; +static struct audio_client *voc_rx_clnt; + +static int q6_voice_start(void) +{ + int rc = 0; + + mutex_lock(&voice_lock); + + if (voice_started) { + pr_err("[%s:%s] busy\n", __MM_FILE__, __func__); + rc = -EBUSY; + goto done; + } + + voc_tx_clnt = q6voice_open(AUDIO_FLAG_WRITE); + if (!voc_tx_clnt) { + pr_err("[%s:%s] open voice tx failed.\n", __MM_FILE__, + __func__); + rc = -ENOMEM; + goto done; + } + + voc_rx_clnt = q6voice_open(AUDIO_FLAG_READ); + if (!voc_rx_clnt) { + pr_err("[%s:%s] open voice rx failed.\n", __MM_FILE__, + __func__); + q6voice_close(voc_tx_clnt); + rc = -ENOMEM; + } + + voice_started = 1; +done: + mutex_unlock(&voice_lock); + return rc; +} + +static int q6_voice_stop(void) +{ + mutex_lock(&voice_lock); + if (voice_started) { + q6voice_close(voc_tx_clnt); + q6voice_close(voc_rx_clnt); + voice_started = 0; + } + mutex_unlock(&voice_lock); + return 0; +} + +static int q6_open(struct inode *inode, struct file *file) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + return 0; +} + +static long q6_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int rc; + uint32_t n; + uint32_t id[2]; + uint32_t mute_status; + + switch (cmd) { + case AUDIO_SWITCH_DEVICE: + rc = copy_from_user(&id, (void *)arg, sizeof(id)); + pr_info("[%s:%s] SWITCH_DEV: id[0] = 0x%x, id[1] = 0x%x", + __MM_FILE__, __func__, id[0], id[1]); + if (!rc) + rc = q6audio_do_routing(id[0], id[1]); + break; + case AUDIO_SET_VOLUME: + rc = copy_from_user(&n, (void *)arg, sizeof(n)); + pr_debug("[%s:%s] SET_VOLUME: vol = %d\n", __MM_FILE__, + __func__, n); + if (!rc) + rc = q6audio_set_rx_volume(n); + break; + case AUDIO_SET_MUTE: + rc = copy_from_user(&n, (void *)arg, sizeof(n)); + if (!rc) { + if (voice_started) { + if (n == 1) + mute_status = STREAM_MUTE; + else + mute_status = STREAM_UNMUTE; + } else { + if (n == 1) + mute_status = DEVICE_MUTE; + else + mute_status = DEVICE_UNMUTE; + } + + pr_debug("[%s:%s] SET_MUTE: mute_status = %d\n", + __MM_FILE__, __func__, mute_status); + rc = q6audio_set_tx_mute(mute_status); + } + break; + case AUDIO_UPDATE_ACDB: + rc = copy_from_user(&id, (void *)arg, sizeof(id)); + pr_debug("[%s:%s] UPDATE_ACDB: id[0] = 0x%x, id[1] = 0x%x\n", + __MM_FILE__, __func__, id[0], id[1]); + if (!rc) + rc = q6audio_update_acdb(id[0], 0); + break; + case AUDIO_START_VOICE: + pr_debug("[%s:%s] START_VOICE\n", __MM_FILE__, __func__); + rc = q6_voice_start(); + break; + case AUDIO_STOP_VOICE: + pr_debug("[%s:%s] STOP_VOICE\n", __MM_FILE__, __func__); + rc = q6_voice_stop(); + break; + case AUDIO_REINIT_ACDB: + pr_debug("[%s:%s] REINIT_ACDB\n", __MM_FILE__, __func__); + rc = 0; + break; + default: + rc = -EINVAL; + } + + return rc; +} + + +static int q6_release(struct inode *inode, struct file *file) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + return 0; +} + +static struct file_operations q6_dev_fops = { + .owner = THIS_MODULE, + .open = q6_open, + .unlocked_ioctl = q6_ioctl, + .release = q6_release, +}; + +struct miscdevice q6_control_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_audio_ctl", + .fops = &q6_dev_fops, +}; + + +static int __init q6_audio_ctl_init(void) { + return misc_register(&q6_control_device); +} + +device_initcall(q6_audio_ctl_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/Makefile b/arch/arm/mach-msm/qdsp6/audiov2/Makefile new file mode 100644 index 00000000000..86ab9aeab71 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/Makefile @@ -0,0 +1,12 @@ +obj-y += q6audio.o +obj-y += aac_in.o +obj-y += voice.o +obj-y += pcm_out.o +obj-y += pcm_in.o +obj-y += mp3.o +obj-y += audio_ctl.o +obj-y += analog_audio.o +obj-y += routing.o +obj-y += evrc_in.o +obj-y += qcelp_in.o +obj-y += amrnb_in.o diff --git a/arch/arm/mach-msm/qdsp6/audiov2/aac_in.c b/arch/arm/mach-msm/qdsp6/audiov2/aac_in.c new file mode 100644 index 00000000000..fe6c049a35f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/aac_in.c @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "dal_audio.h" +#include "dal_audio_format.h" + +struct aac { + struct mutex lock; + struct msm_audio_aac_enc_config cfg; + struct msm_audio_stream_config str_cfg; + struct audio_client *audio_client; +}; + +static long q6_aac_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct aac *aac = file->private_data; + struct adsp_open_command rpc; + + int sample_rate; + int audio_object_type; + int index = sizeof(u32); + int rc = 0; + u32 *aac_type = NULL; + + + mutex_lock(&aac->lock); + switch (cmd) { + + case AUDIO_START: + if (aac->audio_client) { + rc = -EBUSY; + break; + } else { + tx_clk_freq = 48000; + aac->audio_client = q6audio_open(AUDIO_FLAG_READ, + aac->str_cfg.buffer_size); + + if (aac->audio_client < 0) { + + tx_clk_freq = 8000; + rc = -ENOMEM; + break; + } + } + memset(&rpc, 0, sizeof(rpc)); + + rpc.format_block.binary.format = ADSP_AUDIO_FORMAT_MPEG4_AAC; + /* only 48k sample rate is supported */ + sample_rate = 3; + + /* AAC OBJECT LC */ + audio_object_type = 2; + + aac_type = (u32 *)rpc.format_block.binary.data; + switch (aac->cfg.stream_format) { + + case AUDIO_AAC_FORMAT_ADTS: + /* AAC Encoder expect MPEG4_ADTS media type */ + *aac_type = ADSP_AUDIO_AAC_MPEG4_ADTS; + break; + case AUDIO_AAC_FORMAT_RAW: + /* for ADIF recording */ + *aac_type = ADSP_AUDIO_AAC_RAW; + break; + } + + rpc.format_block.binary.data[index++] = (u8)( + ((audio_object_type & 0x1F) << 3) | + ((sample_rate >> 1) & 0x7)); + rpc.format_block.binary.data[index] = (u8)( + ((sample_rate & 0x1) << 7) | + ((aac->cfg.channels & 0x7) << 3)); + + rpc.format_block.binary.num_bytes = index + 1; + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + rpc.buf_max_size = aac->str_cfg.buffer_size; + rpc.config.aac.bit_rate = aac->cfg.bit_rate; + rpc.config.aac.encoder_mode = ADSP_AUDIO_ENC_AAC_LC_ONLY_MODE; + q6audio_start(aac->audio_client, &rpc, sizeof(rpc)); + break; + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_VOLUME: + break; + case AUDIO_GET_STREAM_CONFIG: + if (copy_to_user((void *)arg, &aac->str_cfg, + sizeof(struct msm_audio_stream_config))) + rc = -EFAULT; + break; + case AUDIO_SET_STREAM_CONFIG: + if (copy_from_user(&aac->str_cfg, (void *)arg, + sizeof(struct msm_audio_stream_config))) { + rc = -EFAULT; + break; + } + if (aac->str_cfg.buffer_size < 519) { + pr_err("Buffer size too small\n"); + rc = -EINVAL; + break; + } + if (aac->str_cfg.buffer_count != 2) + pr_info("Buffer count set to 2\n"); + + break; + case AUDIO_SET_AAC_ENC_CONFIG: + if (copy_from_user(&aac->cfg, (void *) arg, + sizeof(struct msm_audio_aac_enc_config))) { + rc = -EFAULT; + } + if (aac->cfg.channels != 1) { + pr_err("only mono is supported\n"); + rc = -EINVAL; + } + if (aac->cfg.sample_rate != 48000) { + pr_err("only 48KHz is supported\n"); + rc = -EINVAL; + } + if (aac->cfg.stream_format != AUDIO_AAC_FORMAT_RAW && + aac->cfg.stream_format != AUDIO_AAC_FORMAT_ADTS) { + pr_err("unsupported AAC format\n"); + rc = -EINVAL; + } + break; + case AUDIO_GET_AAC_ENC_CONFIG: + if (copy_to_user((void *) arg, &aac->cfg, + sizeof(struct msm_audio_aac_enc_config))) { + rc = -EFAULT; + } + break; + default: + rc = -EINVAL; + } + + mutex_unlock(&aac->lock); + return rc; +} + +static int q6_aac_in_open(struct inode *inode, struct file *file) +{ + + struct aac *aac; + aac = kmalloc(sizeof(struct aac), GFP_KERNEL); + if (aac == NULL) { + pr_err("Could not allocate memory for aac driver\n"); + return -ENOMEM; + } + + mutex_init(&aac->lock); + file->private_data = aac; + aac->audio_client = NULL; + aac->str_cfg.buffer_size = 519; + aac->str_cfg.buffer_count = 2; + aac->cfg.channels = 1; + aac->cfg.bit_rate = 192000; + aac->cfg.stream_format = AUDIO_AAC_FORMAT_ADTS; + aac->cfg.sample_rate = 48000; + + return 0; +} + +static ssize_t q6_aac_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + struct aac *aac = file->private_data; + int xfer = 0; + int res; + + mutex_lock(&aac->lock); + ac = aac->audio_client; + if (!ac) { + res = -ENODEV; + goto fail; + } + while (count > xfer) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + xfer = ab->actual_size; + + if (copy_to_user(buf, ab->data, xfer)) { + res = -EFAULT; + goto fail; + } + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } + res = buf - start; +fail: + mutex_unlock(&aac->lock); + + return res; +} + +static int q6_aac_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct aac *aac = file->private_data; + + mutex_lock(&aac->lock); + if (aac->audio_client) + rc = q6audio_close(aac->audio_client); + mutex_unlock(&aac->lock); + kfree(aac); + tx_clk_freq = 8000; + return rc; +} + +static const struct file_operations q6_aac_in_fops = { + .owner = THIS_MODULE, + .open = q6_aac_in_open, + .read = q6_aac_in_read, + .release = q6_aac_in_release, + .unlocked_ioctl = q6_aac_in_ioctl, +}; + +struct miscdevice q6_aac_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac_in", + .fops = &q6_aac_in_fops, +}; + +static int __init q6_aac_in_init(void) +{ + return misc_register(&q6_aac_in_misc); +} + +device_initcall(q6_aac_in_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/amrnb_in.c b/arch/arm/mach-msm/qdsp6/audiov2/amrnb_in.c new file mode 100644 index 00000000000..b877977ac1f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/amrnb_in.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include "dal_audio.h" +#include "dal_audio_format.h" +#include + + +struct amrnb { + struct mutex lock; + struct msm_audio_amrnb_enc_config_v2 cfg; + struct msm_audio_stream_config str_cfg; + struct audio_client *audio_client; +}; + + +static long q6_amrnb_in_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct amrnb *amrnb = file->private_data; + struct adsp_open_command rpc; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&amrnb->lock); + switch (cmd) { + case AUDIO_START: + if (amrnb->audio_client) { + rc = -EBUSY; + break; + } else { + amrnb->audio_client = q6audio_open(AUDIO_FLAG_READ, + amrnb->str_cfg.buffer_size); + + if (!amrnb->audio_client) { + kfree(amrnb); + rc = -ENOMEM; + break; + } + } + + tx_clk_freq = 8000; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format_block.standard.format = ADSP_AUDIO_FORMAT_AMRNB_FS; + rpc.format_block.standard.channels = 1; + rpc.format_block.standard.bits_per_sample = 16; + rpc.format_block.standard.sampling_rate = 8000; + rpc.format_block.standard.is_signed = 1; + rpc.format_block.standard.is_interleaved = 0; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + rpc.buf_max_size = amrnb->str_cfg.buffer_size; + rpc.config.amr.mode = amrnb->cfg.band_mode; + rpc.config.amr.dtx_mode = amrnb->cfg.dtx_enable; + rpc.config.amr.enable = 1; + q6audio_start(amrnb->audio_client, &rpc, sizeof(rpc)); + break; + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_VOLUME: + break; + case AUDIO_GET_STREAM_CONFIG: + if (copy_to_user((void *)arg, &amrnb->str_cfg, + sizeof(struct msm_audio_stream_config))) + rc = -EFAULT; + break; + case AUDIO_SET_STREAM_CONFIG: + if (copy_from_user(&amrnb->str_cfg, (void *)arg, + sizeof(struct msm_audio_stream_config))) { + rc = -EFAULT; + break; + } + + if (amrnb->str_cfg.buffer_size < 768) { + pr_err("[%s:%s] Buffer size too small\n", __MM_FILE__, + __func__); + rc = -EINVAL; + break; + } + + if (amrnb->str_cfg.buffer_count != 2) + pr_info("[%s:%s] Buffer count set to 2\n", __MM_FILE__, + __func__); + break; + case AUDIO_SET_AMRNB_ENC_CONFIG: + if (copy_from_user(&amrnb->cfg, (void *) arg, + sizeof(struct msm_audio_amrnb_enc_config_v2))) + rc = -EFAULT; + break; + case AUDIO_GET_AMRNB_ENC_CONFIG: + if (copy_to_user((void *) arg, &amrnb->cfg, + sizeof(struct msm_audio_amrnb_enc_config_v2))) + rc = -EFAULT; + break; + + default: + rc = -EINVAL; + } + + mutex_unlock(&amrnb->lock); + return rc; +} + +static int q6_amrnb_in_open(struct inode *inode, struct file *file) +{ + struct amrnb *amrnb; + amrnb = kmalloc(sizeof(struct amrnb), GFP_KERNEL); + if (amrnb == NULL) { + pr_err("[%s:%s] Could not allocate memory for amrnb driver\n", + __MM_FILE__, __func__); + return -ENOMEM; + } + + mutex_init(&amrnb->lock); + file->private_data = amrnb; + amrnb->audio_client = NULL; + amrnb->str_cfg.buffer_size = 768; + amrnb->str_cfg.buffer_count = 2; + amrnb->cfg.band_mode = ADSP_AUDIO_AMR_MR475; + amrnb->cfg.dtx_enable = ADSP_AUDIO_AMR_DTX_MODE_ON_AUTO; + amrnb->cfg.frame_format = ADSP_AUDIO_FORMAT_AMRNB_FS; + return 0; +} + +static ssize_t q6_amrnb_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + struct amrnb *amrnb = file->private_data; + int xfer = 0; + int res; + + mutex_lock(&amrnb->lock); + ac = amrnb->audio_client; + if (!ac) { + res = -ENODEV; + goto fail; + } + while (count > xfer) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + xfer = ab->actual_size; + + if (copy_to_user(buf, ab->data, xfer)) { + res = -EFAULT; + goto fail; + } + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } + + res = buf - start; +fail: + mutex_unlock(&amrnb->lock); + + return res; +} + +static int q6_amrnb_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct amrnb *amrnb = file->private_data; + + mutex_lock(&amrnb->lock); + if (amrnb->audio_client) + rc = q6audio_close(amrnb->audio_client); + mutex_unlock(&amrnb->lock); + kfree(amrnb); + return rc; +} + +static const struct file_operations q6_amrnb_in_fops = { + .owner = THIS_MODULE, + .open = q6_amrnb_in_open, + .read = q6_amrnb_in_read, + .release = q6_amrnb_in_release, + .unlocked_ioctl = q6_amrnb_in_ioctl, +}; + +struct miscdevice q6_amrnb_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amr_in", + .fops = &q6_amrnb_in_fops, +}; + +static int __init q6_amrnb_in_init(void) +{ + return misc_register(&q6_amrnb_in_misc); +} + +device_initcall(q6_amrnb_in_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/analog_audio.c b/arch/arm/mach-msm/qdsp6/audiov2/analog_audio.c new file mode 100644 index 00000000000..1df4f5d2f67 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/analog_audio.c @@ -0,0 +1,85 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include + +#define GPIO_HEADSET_AMP 157 + +void analog_init(void) +{ + /* stereo pmic init */ + pmic_spkr_set_gain(LEFT_SPKR, SPKR_GAIN_PLUS12DB); + pmic_spkr_set_gain(RIGHT_SPKR, SPKR_GAIN_PLUS12DB); + pmic_mic_set_volt(MIC_VOLT_1_80V); + + gpio_direction_output(GPIO_HEADSET_AMP, 1); + gpio_set_value(GPIO_HEADSET_AMP, 0); +} + +void analog_headset_enable(int en) +{ + /* enable audio amp */ + gpio_set_value(GPIO_HEADSET_AMP, !!en); +} + +void analog_speaker_enable(int en) +{ + struct spkr_config_mode scm; + memset(&scm, 0, sizeof(scm)); + + if (en) { + scm.is_right_chan_en = 1; + scm.is_left_chan_en = 1; + scm.is_stereo_en = 1; + scm.is_hpf_en = 1; + pmic_spkr_en_mute(LEFT_SPKR, 0); + pmic_spkr_en_mute(RIGHT_SPKR, 0); + pmic_set_spkr_configuration(&scm); + pmic_spkr_en(LEFT_SPKR, 1); + pmic_spkr_en(RIGHT_SPKR, 1); + + /* unmute */ + pmic_spkr_en_mute(LEFT_SPKR, 1); + pmic_spkr_en_mute(RIGHT_SPKR, 1); + } else { + pmic_spkr_en_mute(LEFT_SPKR, 0); + pmic_spkr_en_mute(RIGHT_SPKR, 0); + + pmic_spkr_en(LEFT_SPKR, 0); + pmic_spkr_en(RIGHT_SPKR, 0); + + pmic_set_spkr_configuration(&scm); + } +} + +void analog_mic_enable(int en) +{ + pmic_mic_en(en); +} + +static struct q6audio_analog_ops ops = { + .init = analog_init, + .speaker_enable = analog_speaker_enable, + .headset_enable = analog_headset_enable, + .int_mic_enable = analog_mic_enable, +}; + +static int __init init(void) +{ + q6audio_register_analog_ops(&ops); + return 0; +} + +device_initcall(init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/audio_ctl.c b/arch/arm/mach-msm/qdsp6/audiov2/audio_ctl.c new file mode 100644 index 00000000000..286d85d8ce5 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/audio_ctl.c @@ -0,0 +1,140 @@ +/* arch/arm/mach-msm/qdsp6/audiov2/audio_ctrl.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include +#include +#include + +#include + +#define BUFSZ (0) + +static DEFINE_MUTEX(voice_lock); +static int voice_started; + +static struct audio_client *voc_clnt; + +static int q6_voice_start(void) +{ + int rc = 0; + + mutex_lock(&voice_lock); + + if (voice_started) { + pr_err("voice: busy\n"); + rc = -EBUSY; + goto done; + } + + voc_clnt = q6voice_open(); + if (!voc_clnt) { + pr_err("voice: open voice failed.\n"); + rc = -ENOMEM; + goto done; + } + + voice_started = 1; +done: + mutex_unlock(&voice_lock); + return rc; +} + +static int q6_voice_stop(void) +{ + mutex_lock(&voice_lock); + if (voice_started) { + q6voice_close(voc_clnt); + voice_started = 0; + } + mutex_unlock(&voice_lock); + return 0; +} + +static int q6_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int q6_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int rc; + uint32_t n; + uint32_t id[2]; + + switch (cmd) { + case AUDIO_SWITCH_DEVICE: + rc = copy_from_user(&n, (void *)arg, sizeof(n)); + if (!rc) + rc = q6audio_do_routing(n); + break; + case AUDIO_SET_VOLUME: + rc = copy_from_user(&n, (void *)arg, sizeof(n)); + if (!rc) + rc = q6audio_set_rx_volume(n); + break; + case AUDIO_SET_MUTE: + rc = copy_from_user(&n, (void *)arg, sizeof(n)); + if (!rc) + rc = q6audio_set_tx_mute(n); + break; + case AUDIO_UPDATE_ACDB: + rc = copy_from_user(&id, (void *)arg, sizeof(id)); + if (!rc) + rc = q6audio_update_acdb(id[0], id[1]); + break; + case AUDIO_START_VOICE: + rc = q6_voice_start(); + break; + case AUDIO_STOP_VOICE: + rc = q6_voice_stop(); + break; + default: + rc = -EINVAL; + } + + return rc; +} + + +static int q6_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations q6_dev_fops = { + .owner = THIS_MODULE, + .open = q6_open, + .ioctl = q6_ioctl, + .release = q6_release, +}; + +struct miscdevice q6_control_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_audio_ctl", + .fops = &q6_dev_fops, +}; + + +static int __init q6_audio_ctl_init(void) +{ + return misc_register(&q6_control_device); +} + +device_initcall(q6_audio_ctl_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/dal_acdb.h b/arch/arm/mach-msm/qdsp6/audiov2/dal_acdb.h new file mode 100644 index 00000000000..d88b7ad3662 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/dal_acdb.h @@ -0,0 +1,71 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define ACDB_DAL_DEVICE 0x02000069 +#define ACDB_DAL_PORT "DAL_AM_AUD" +#define ACDB_DAL_VERSION 0x00010000 + +#define ACDB_OP_IOCTL DAL_OP_FIRST_DEVICE_API + +/* ioctls */ +#define ACDB_GET_DEVICE 0x0108bb92 +#define ACDB_SET_DEVICE 0x0108bb93 +#define ACDB_GET_STREAM 0x0108bb95 +#define ACDB_SET_STREAM 0x0108bb96 +#define ACDB_GET_DEVICE_TABLE 0x0108bb97 +#define ACDB_GET_STREAM_TABLE 0x0108bb98 + +#define ACDB_RES_SUCCESS 0 +#define ACDB_RES_FAILURE -1 +#define ACDB_RES_BADPARM -2 +#define ACDB_RES_BADSTATE -3 + +struct acdb_cmd_device { + uint32_t size; + + uint32_t command_id; + uint32_t device_id; + uint32_t network_id; + uint32_t sample_rate_id; + uint32_t interface_id; + uint32_t algorithm_block_id; + + /* physical page aligned buffer */ + uint32_t total_bytes; + uint32_t unmapped_buf; +} __attribute__((packed)); + +struct acdb_cmd_device_table { + uint32_t size; + + uint32_t command_id; + uint32_t device_id; + uint32_t network_id; + uint32_t sample_rate_id; + + /* physical page aligned buffer */ + uint32_t total_bytes; + uint32_t unmapped_buf; + + uint32_t res_size; +} __attribute__((packed)); + +struct acdb_result { + uint32_t dal_status; + uint32_t size; + + uint32_t total_devices; + uint32_t unmapped_buf; + uint32_t used_bytes; + uint32_t result; +} __attribute__((packed)); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/dal_adie.h b/arch/arm/mach-msm/qdsp6/audiov2/dal_adie.h new file mode 100644 index 00000000000..e828e9c14a7 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/dal_adie.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _MACH_MSM_QDSP6_ADIE_ +#define _MACH_MSM_QDSP6_ADIE_ + +#include "../dal.h" + +#define ADIE_DAL_DEVICE 0x02000029 +#define ADIE_DAL_PORT "DAL_AM_AUD" +#define ADIE_DAL_VERSION 0x00010000 + +enum { + ADIE_OP_SET_PATH = DAL_OP_FIRST_DEVICE_API, + ADIE_OP_PROCEED_TO_STAGE, + ADIE_OP_IOCTL +}; + +/* Path IDs for normal operation. */ +#define ADIE_PATH_HANDSET_TX 0x010740f6 +#define ADIE_PATH_HANDSET_RX 0x010740f7 +#define ADIE_PATH_HEADSET_MONO_TX 0x010740f8 +#define ADIE_PATH_HEADSET_STEREO_TX 0x010740f9 +#define ADIE_PATH_HEADSET_MONO_RX 0x010740fa +#define ADIE_PATH_HEADSET_STEREO_RX 0x010740fb +#define ADIE_PATH_SPEAKER_TX 0x010740fc +#define ADIE_PATH_SPEAKER_RX 0x010740fd +#define ADIE_PATH_SPEAKER_STEREO_RX 0x01074101 + +/* Path IDs used for TTY */ +#define ADIE_PATH_TTY_HEADSET_TX 0x010740fe +#define ADIE_PATH_TTY_HEADSET_RX 0x010740ff + +/* Path IDs used by Factory Test Mode. */ +#define ADIE_PATH_FTM_MIC1_TX 0x01074108 +#define ADIE_PATH_FTM_MIC2_TX 0x01074107 +#define ADIE_PATH_FTM_HPH_L_RX 0x01074106 +#define ADIE_PATH_FTM_HPH_R_RX 0x01074104 +#define ADIE_PATH_FTM_EAR_RX 0x01074103 +#define ADIE_PATH_FTM_SPKR_RX 0x01074102 + +/* Path IDs for Loopback */ +/* Path IDs used for Line in -> AuxPGA -> Line Out Stereo Mode*/ +#define ADIE_PATH_AUXPGA_LINEOUT_STEREO_LB 0x01074100 +/* Line in -> AuxPGA -> LineOut Mono */ +#define ADIE_PATH_AUXPGA_LINEOUT_MONO_LB 0x01073d82 +/* Line in -> AuxPGA -> Stereo Headphone */ +#define ADIE_PATH_AUXPGA_HDPH_STEREO_LB 0x01074109 +/* Line in -> AuxPGA -> Mono Headphone */ +#define ADIE_PATH_AUXPGA_HDPH_MONO_LB 0x01073d85 +/* Line in -> AuxPGA -> Earpiece */ +#define ADIE_PATH_AUXPGA_EAP_LB 0x01073d81 +/* Line in -> AuxPGA -> AuxOut */ +#define ADIE_PATH_AUXPGA_AUXOUT_LB 0x01073d86 + +/* Concurrency Profiles */ +#define ADIE_PATH_SPKR_STEREO_HDPH_MONO_RX 0x01073d83 +#define ADIE_PATH_SPKR_MONO_HDPH_MONO_RX 0x01073d84 +#define ADIE_PATH_SPKR_MONO_HDPH_STEREO_RX 0x01073d88 +#define ADIE_PATH_SPKR_STEREO_HDPH_STEREO_RX 0x01073d89 + +/* stages */ +#define ADIE_STAGE_PATH_OFF 0x0050 +#define ADIE_STAGE_DIGITAL_READY 0x0100 +#define ADIE_STAGE_DIGITAL_ANALOG_READY 0x1000 +#define ADIE_STAGE_ANALOG_OFF 0x0750 +#define ADIE_STAGE_DIGITAL_OFF 0x0600 + +/* path types */ +#define ADIE_PATH_RX 0 +#define ADIE_PATH_TX 1 +#define ADIE_PATH_LOOPBACK 2 + +/* mute states */ +#define ADIE_MUTE_OFF 0 +#define ADIE_MUTE_ON 1 + + +#endif diff --git a/arch/arm/mach-msm/qdsp6/audiov2/dal_audio.h b/arch/arm/mach-msm/qdsp6/audiov2/dal_audio.h new file mode 100644 index 00000000000..52de785b011 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/dal_audio.h @@ -0,0 +1,546 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __DAL_AUDIO_H__ +#define __DAL_AUDIO_H__ + +#include "../dal.h" +#include "dal_audio_format.h" + +#define AUDIO_DAL_DEVICE 0x02000028 +#define AUDIO_DAL_PORT "DAL_AQ_AUD" +#define AUDIO_DAL_VERSION 0x00030001 + +enum { + AUDIO_OP_CONTROL = DAL_OP_FIRST_DEVICE_API, + AUDIO_OP_DATA, + AUDIO_OP_INIT, +}; + +/* ---- common audio structures ---- */ + +/* This flag, if set, indicates that the beginning of the data in the*/ +/* buffer is a synchronization point or key frame, meaning no data */ +/* before it in the stream is required in order to render the stream */ +/* from this point onward. */ +#define ADSP_AUDIO_BUFFER_FLAG_SYNC_POINT 0x01 + +/* This flag, if set, indicates that the buffer object is using valid */ +/* physical address used to store the media data */ +#define ADSP_AUDIO_BUFFER_FLAG_PHYS_ADDR 0x04 + +/* This flag, if set, indicates that a media start timestamp has been */ +/* set for a buffer. */ +#define ADSP_AUDIO_BUFFER_FLAG_START_SET 0x08 + +/* This flag, if set, indicates that a media stop timestamp has been set */ +/* for a buffer. */ +#define ADSP_AUDIO_BUFFER_FLAG_STOP_SET 0x10 + +/* This flag, if set, indicates that a preroll timestamp has been set */ +/* for a buffer. */ +#define ADSP_AUDIO_BUFFER_FLAG_PREROLL_SET 0x20 + +/* This flag, if set, indicates that the data in the buffer is a fragment of */ +/* a larger block of data, and will be continued by the data in the next */ +/* buffer to be delivered. */ +#define ADSP_AUDIO_BUFFER_FLAG_CONTINUATION 0x40 + +struct adsp_audio_buffer { + u32 addr; /* Physical Address of buffer */ + u32 max_size; /* Maximum size of buffer */ + u32 actual_size; /* Actual size of valid data in the buffer */ + u32 offset; /* Offset to the first valid byte */ + u32 flags; /* ADSP_AUDIO_BUFFER_FLAGs that has been set */ + s64 start; /* Start timestamp, if any */ + s64 stop; /* Stop timestamp, if any */ + s64 preroll; /* Preroll timestamp, if any */ +} __attribute__ ((packed)); + + + +/* ---- audio commands ---- */ + +/* Command/event response types */ +#define ADSP_AUDIO_RESPONSE_COMMAND 0 +#define ADSP_AUDIO_RESPONSE_ASYNC 1 + +struct adsp_command_hdr { + u32 size; /* sizeof(cmd) - sizeof(u32) */ + + u32 dest; + u32 src; + u32 opcode; + u32 response_type; + u32 seq_number; + + u32 context; /* opaque to DSP */ + u32 data; + u32 padding; +} __attribute__ ((packed)); + + +#define DOMAIN_APP 0 +#define DOMAIN_MODEM 1 +#define DOMAIN_DSP 2 + + +/* adsp audio addresses are (byte order) major, minor, domain */ +#define AUDIO_ADDR(dmn, maj, min) (((maj & 0xff) << 16) \ + | ((min & 0xff) << 24) | (dmn & 0xff)) + +/* AAC Encoder modes */ +#define ADSP_AUDIO_ENC_AAC_LC_ONLY_MODE 0 +#define ADSP_AUDIO_ENC_AAC_PLUS_MODE 1 +#define ADSP_AUDIO_ENC_ENHANCED_AAC_PLUS_MODE 2 + +struct adsp_audio_aac_enc_cfg { + u32 bit_rate; /* bits per second */ + u32 encoder_mode; /* ADSP_AUDIO_ENC_* */ +} __attribute__ ((packed)); + +#define ADSP_AUDIO_ENC_SBC_ALLOCATION_METHOD_LOUNDNESS 0 +#define ADSP_AUDIO_ENC_SBC_ALLOCATION_METHOD_SNR 1 + +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_MONO 1 +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_STEREO 2 +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_DUAL 8 +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_JOINT_STEREO 9 + +struct adsp_audio_sbc_encoder_cfg { + u32 num_subbands; + u32 block_len; + u32 channel_mode; + u32 allocation_method; + u32 bit_rate; +} __attribute__ ((packed)); + +/* AMR NB encoder modes */ +#define ADSP_AUDIO_AMR_MR475 0 +#define ADSP_AUDIO_AMR_MR515 1 +#define ADSP_AUDIO_AMR_MMR59 2 +#define ADSP_AUDIO_AMR_MMR67 3 +#define ADSP_AUDIO_AMR_MMR74 4 +#define ADSP_AUDIO_AMR_MMR795 5 +#define ADSP_AUDIO_AMR_MMR102 6 +#define ADSP_AUDIO_AMR_MMR122 7 + +/* The following are valid AMR NB DTX modes */ +#define ADSP_AUDIO_AMR_DTX_MODE_OFF 0 +#define ADSP_AUDIO_AMR_DTX_MODE_ON_VAD1 1 +#define ADSP_AUDIO_AMR_DTX_MODE_ON_VAD2 2 +#define ADSP_AUDIO_AMR_DTX_MODE_ON_AUTO 3 + +/* AMR Encoder configuration */ +struct adsp_audio_amr_enc_cfg { + u32 mode; /* ADSP_AUDIO_AMR_MR* */ + u32 dtx_mode; /* ADSP_AUDIO_AMR_DTX_MODE* */ + u32 enable; /* 1 = enable, 0 = disable */ +} __attribute__ ((packed)); + +struct adsp_audio_qcelp13k_enc_cfg { + u16 min_rate; + u16 max_rate; +} __attribute__ ((packed)); + +struct adsp_audio_evrc_enc_cfg { + u16 min_rate; + u16 max_rate; +} __attribute__ ((packed)); + +union adsp_audio_codec_config { + struct adsp_audio_amr_enc_cfg amr; + struct adsp_audio_aac_enc_cfg aac; + struct adsp_audio_qcelp13k_enc_cfg qcelp13k; + struct adsp_audio_evrc_enc_cfg evrc; + struct adsp_audio_sbc_encoder_cfg sbc; +} __attribute__ ((packed)); + + +/* This is the default value. */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_NONE 0x0000 + +/* This bit, if set, indicates that the AVSync mode is activated. */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_AVSYNC 0x0001 + +/* This bit, if set, indicates that the Sample Rate/Channel Mode */ +/* Change Notification mode is activated. */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_SR_CM_NOTIFY 0x0002 + +#define ADSP_AUDIO_OPEN_STREAM_MODE_ENABLE_SYNC_CLOCK 0x0004 + +#define ADSP_AUDIO_MAX_DEVICES 1 + +struct adsp_open_command { + struct adsp_command_hdr hdr; + u32 device; + u32 end_point; + u32 stream_context; + u32 mode; + u32 buf_max_size; + union adsp_audio_format format_block; + union adsp_audio_codec_config config; + +} __attribute__ ((packed)); + + +/* --- audio control and stream session ioctls ---- */ + +/* Opcode to open a device stream session to capture audio */ +#define ADSP_AUDIO_IOCTL_CMD_OPEN_READ 0x0108dd79 + +/* Opcode to open a device stream session to render audio */ +#define ADSP_AUDIO_IOCTL_CMD_OPEN_WRITE 0x0108dd7a + +/* Opcode to open a device session, must open a device */ +#define ADSP_AUDIO_IOCTL_CMD_OPEN_DEVICE 0x0108dd7b + +/* Close an existing stream or device */ +#define ADSP_AUDIO_IOCTL_CMD_CLOSE 0x0108d8bc + + + +/* A device switch requires three IOCTL */ +/* commands in the following sequence: PREPARE, STANDBY, COMMIT */ + +/* adsp_audio_device_switch_command structure is needed for */ +/* DEVICE_SWITCH_PREPARE */ + +/* Device switch protocol step #1. Pause old device and */ +/* generate silence for the old device. */ +#define ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_PREPARE 0x010815c4 + +/* Device switch protocol step #2. Release old device, */ +/* create new device and generate silence for the new device. */ + +/* When client receives ack for this IOCTL, the client can */ +/* start sending IOCTL commands to configure, calibrate and */ +/* change filter settings on the new device. */ +#define ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_STANDBY 0x010815c5 + +/* Device switch protocol step #3. Start normal operations on new device */ +#define ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_COMMIT 0x01075ee7 + +struct adsp_device_switch_command { + struct adsp_command_hdr hdr; + u32 old_device; + u32 new_device; + u8 device_class; /* 0 = i.rx, 1 = i.tx, 2 = e.rx, 3 = e.tx */ + u8 device_type; /* 0 = rx, 1 = tx, 2 = both */ +} __attribute__ ((packed)); + + + +/* --- audio control session ioctls ---- */ + +#define ADSP_PATH_RX 0 +#define ADSP_PATH_TX 1 +#define ADSP_PATH_BOTH 2 + +/* These commands will affect a logical device and all its associated */ +/* streams. */ + + +/* Set device volume. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_VOL 0x0107605c + +struct adsp_set_dev_volume_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 path; /* 0 = rx, 1 = tx, 2 = both */ + s32 volume; +} __attribute__ ((packed)); + +/* Set Device stereo volume. This command has data payload, */ +/* struct adsp_audio_set_dev_stereo_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_STEREO_VOL 0x0108df3e + +/* Set L, R cross channel gain for a Device. This command has */ +/* data payload, struct adsp_audio_set_dev_x_chan_gain_command. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_XCHAN_GAIN 0x0108df40 + +/* Set device mute state. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_MUTE 0x0107605f + +struct adsp_set_dev_mute_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 path; /* 0 = rx, 1 = tx, 2 = both */ + u32 mute; /* 1 = mute */ +} __attribute__ ((packed)); + +/* Configure Equalizer for a device. */ +/* This command has payload struct adsp_audio_set_dev_equalizer_command. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_EQ_CONFIG 0x0108b10e + +/* Set configuration data for an algorithm aspect of a device. */ +/* This command has payload struct adsp_audio_set_dev_cfg_command. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_CONFIG 0x0108b6cb + +struct adsp_set_dev_cfg_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 block_id; + u32 interface_id; + u32 phys_addr; + u32 phys_size; + u32 phys_used; +} __attribute__ ((packed)); + +/* Set configuration data for all interfaces of a device. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_CONFIG_TABLE 0x0108b6bf + +struct adsp_set_dev_cfg_table_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 phys_addr; + u32 phys_size; + u32 phys_used; +} __attribute__ ((packed)); + +/* ---- audio stream data commands ---- */ + +#define ADSP_AUDIO_IOCTL_CMD_DATA_TX 0x0108dd7f +#define ADSP_AUDIO_IOCTL_CMD_DATA_RX 0x0108dd80 + +struct adsp_buffer_command { + struct adsp_command_hdr hdr; + struct adsp_audio_buffer buffer; +} __attribute__ ((packed)); + + + +/* ---- audio stream ioctls (only affect a single stream in a session) ---- */ + +/* Stop stream for audio device. */ +#define ADSP_AUDIO_IOCTL_CMD_STREAM_STOP 0x01075c54 + +/* End of stream reached. Client will not send any more data. */ +#define ADSP_AUDIO_IOCTL_CMD_STREAM_EOS 0x0108b150 + +/* Do sample slipping/stuffing on AAC outputs. The payload of */ +/* this command is struct adsp_audio_slip_sample_command. */ +#define ADSP_AUDIO_IOCTL_CMD_STREAM_SLIPSAMPLE 0x0108d40e + +/* Set stream volume. */ +/* This command has data payload, struct adsp_audio_set_volume_command. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_STREAM_VOL 0x0108c0de + +/* Set stream stereo volume. This command has data payload, */ +/* struct adsp_audio_set_stereo_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_STEREO_VOL 0x0108dd7c + +/* Set L, R cross channel gain for a Stream. This command has */ +/* data payload, struct adsp_audio_set_x_chan_gain_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_XCHAN_GAIN 0x0108dd7d + +/* Set stream mute state. */ +/* This command has data payload, struct adsp_audio_set_stream_mute. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_STREAM_MUTE 0x0108c0df + +/* Reconfigure bit rate information. This command has data */ +/* payload, struct adsp_audio_set_bit_rate_command */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_BITRATE 0x0108ccf1 + +/* Set Channel Mapping. This command has data payload, struct */ +/* This command has data payload struct adsp_audio_set_channel_map_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_CHANNELMAP 0x0108d32a + +/* Enable/disable AACPlus SBR. */ +/* This command has data payload struct adsp_audio_set_sbr_command */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_SBR 0x0108d416 + +/* Enable/disable WMA Pro Chex and Fex. This command has data payload */ +/* struct adsp_audio_stream_set_wma_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_WMAPRO 0x0108d417 + + +/* ---- audio session ioctls (affect all streams in a session) --- */ + +/* Start stream for audio device. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_START 0x010815c6 + +/* Stop all stream(s) for audio session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_STOP 0x0108dd7e + +/* Pause the data flow for a session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_PAUSE 0x01075ee8 + +/* Resume the data flow for a session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_RESUME 0x01075ee9 + +/* Drop any unprocessed data buffers for a session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_FLUSH 0x01075eea + +/* Start Stream DTMF tone */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_DTMF_START 0x0108c0dd + +/* Stop Stream DTMF tone */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_DTMF_STOP 0x01087554 + +/* Set Session volume. */ +/* This command has data payload, struct adsp_audio_set_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_VOL 0x0108d8bd + +/* Set session stereo volume. This command has data payload, */ +/* struct adsp_audio_set_stereo_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_STEREO_VOL 0x0108df3d + +/* Set L, R cross channel gain for a session. This command has */ +/* data payload, struct adsp_audio_set_x_chan_gain_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_XCHAN_GAIN 0x0108df3f + +/* Set Session mute state. */ +/* This command has data payload, struct adsp_audio_set_mute_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_MUTE 0x0108d8be + +/* Configure Equalizer for a stream. */ +/* This command has payload struct adsp_audio_set_equalizer_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_EQ_CONFIG 0x0108c0e0 + +/* Set Audio Video sync information. */ +/* This command has data payload, struct adsp_audio_set_av_sync_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_AVSYNC 0x0108d1e2 + +/* Get Audio Media Session time. */ +/* This command returns the audioTime in adsp_audio_unsigned64_event */ +#define ADSP_AUDIO_IOCTL_CMD_GET_AUDIO_TIME 0x0108c26c + + +/* these command structures are used for both STREAM and SESSION ioctls */ + +struct adsp_set_volume_command { + struct adsp_command_hdr hdr; + s32 volume; +} __attribute__ ((packed)); + +struct adsp_set_mute_command { + struct adsp_command_hdr hdr; + u32 mute; /* 1 == mute */ +} __attribute__ ((packed)); + + + +/* ---- audio events ---- */ + +/* All IOCTL commands generate an event with the IOCTL opcode as the */ +/* event id after the IOCTL command has been executed. */ + +/* This event is generated after a media stream session is opened. */ +#define ADSP_AUDIO_EVT_STATUS_OPEN 0x0108c0d6 + +/* This event is generated after a media stream session is closed. */ +#define ADSP_AUDIO_EVT_STATUS_CLOSE 0x0108c0d7 + +/* Asyncronous buffer consumption. This event is generated after a */ +/* recived buffer is consumed during rendering or filled during */ +/* capture opeartion. */ +#define ADSP_AUDIO_EVT_STATUS_BUF_DONE 0x0108c0d8 + +/* This event is generated when rendering operation is starving for */ +/* data. In order to avoid audio loss at the end of a plauback, the */ +/* client should wait for this event before issuing the close command. */ +#define ADSP_AUDIO_EVT_STATUS_BUF_UNDERRUN 0x0108c0d9 + +/* This event is generated during capture operation when there are no */ +/* buffers available to copy the captured audio data */ +#define ADSP_AUDIO_EVT_STATUS_BUF_OVERFLOW 0x0108c0da + +/* This asynchronous event is generated as a result of an input */ +/* sample rate change and/or channel mode change detected by the */ +/* decoder. The event payload data is an array of 2 uint32 */ +/* values containing the sample rate in Hz and channel mode. */ +#define ADSP_AUDIO_EVT_SR_CM_CHANGE 0x0108d329 + +struct adsp_event_hdr { + u32 evt_handle; /* DAL common header */ + u32 evt_cookie; + u32 evt_length; + + u32 dest; + u32 src; + + u32 event_id; + u32 response_type; + u32 seq_number; + + u32 context; /* opaque to DSP */ + u32 data; + + u32 status; +} __attribute__ ((packed)); + +struct adsp_buffer_event { + struct adsp_event_hdr hdr; + struct adsp_audio_buffer buffer; +} __attribute__ ((packed)); + + +/* ---- audio device IDs ---- */ + +/* Device direction Rx/Tx flag */ +#define ADSP_AUDIO_RX_DEVICE 0x00 +#define ADSP_AUDIO_TX_DEVICE 0x01 + +#define ADSP_AUDIO_DEVICE_ID_DEFAULT 0x1081679 + +/* Default RX or TX device */ + +#define ADSP_AUDIO_DEVICE_ID_HANDSET_MIC 0x107ac8d +#define ADSP_AUDIO_DEVICE_ID_HANDSET_DUAL_MIC 0x108f9c3 +#define ADSP_AUDIO_DEVICE_ID_HEADSET_MIC 0x1081510 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MIC 0x1081512 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_DUAL_MIC 0x108f9c5 +#define ADSP_AUDIO_DEVICE_ID_BT_SCO_MIC 0x1081518 +#define ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_MIC 0x108151b +#define ADSP_AUDIO_DEVICE_ID_I2S_MIC 0x1089bf3 + +/* Special loopback pseudo device to be paired with an RX device */ +/* with usage ADSP_AUDIO_DEVICE_USAGE_MIXED_PCM_LOOPBACK */ +#define ADSP_AUDIO_DEVICE_ID_MIXED_PCM_LOOPBACK_TX 0x1089bf2 + +/* Sink (RX) devices */ +#define ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR 0x107ac88 +#define ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_MONO 0x1081511 +#define ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO 0x107ac8a +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO 0x1081513 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_MONO_HEADSET 0x108c508 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_STEREO_HEADSET 0x108c894 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO 0x1081514 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_MONO_HEADSET 0x108c895 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_STEREO_HEADSET 0x108c509 +#define ADSP_AUDIO_DEVICE_ID_BT_SCO_SPKR 0x1081519 +#define ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_SPKR 0x108151c +#define ADSP_AUDIO_DEVICE_ID_I2S_SPKR 0x1089bf4 +#define ADSP_AUDIO_DEVICE_ID_NULL_SINK 0x108e512 + +/* BT A2DP playback device. */ +/* This device must be paired with */ +/* ADSP_AUDIO_DEVICE_ID_MIXED_PCM_LOOPBACK_TX using */ +/* ADSP_AUDIO_DEVICE_USAGE_MIXED_PCM_LOOPBACK mode */ +#define ADSP_AUDIO_DEVICE_ID_BT_A2DP_SPKR 0x108151a + +/* Voice Destination identifier - specifically used for */ +/* controlling Voice module from the Device Control Session */ +#define ADSP_AUDIO_DEVICE_ID_VOICE 0x0108df3c + +/* Audio device usage types. */ +/* This is a bit mask to determine which topology to use in the */ +/* device session */ +#define ADSP_AUDIO_DEVICE_CONTEXT_VOICE 0x01 +#define ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK 0x02 +#define ADSP_AUDIO_DEVICE_CONTEXT_MIXED_RECORD 0x10 +#define ADSP_AUDIO_DEVICE_CONTEXT_RECORD 0x20 +#define ADSP_AUDIO_DEVICE_CONTEXT_PCM_LOOPBACK 0x40 + +#endif diff --git a/arch/arm/mach-msm/qdsp6/audiov2/dal_audio_format.h b/arch/arm/mach-msm/qdsp6/audiov2/dal_audio_format.h new file mode 100644 index 00000000000..348aad163ff --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/dal_audio_format.h @@ -0,0 +1,284 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ADSP_AUDIO_MEDIA_FORMAT_H +#define __ADSP_AUDIO_MEDIA_FORMAT_H + +/* Supported audio media formats */ + +/* format block in shmem */ +#define ADSP_AUDIO_FORMAT_SHAREDMEMORY 0x01091a78 + +/* adsp_audio_format_raw_pcm type */ +#define ADSP_AUDIO_FORMAT_PCM 0x0103d2fd + +/* adsp_audio_format_raw_pcm type */ +#define ADSP_AUDIO_FORMAT_DTMF 0x01087725 + +/* adsp_audio_format_adpcm type */ +#define ADSP_AUDIO_FORMAT_ADPCM 0x0103d2ff + +/* Yamaha PCM format */ +#define ADSP_AUDIO_FORMAT_YADPCM 0x0108dc07 + +/* ISO/IEC 11172 */ +#define ADSP_AUDIO_FORMAT_MP3 0x0103d308 + +/* ISO/IEC 14496 */ +#define ADSP_AUDIO_FORMAT_MPEG4_AAC 0x010422f1 + +/* AMR-NB audio in FS format */ +#define ADSP_AUDIO_FORMAT_AMRNB_FS 0x0105c16c + +/* AMR-WB audio in FS format */ +#define ADSP_AUDIO_FORMAT_AMRWB_FS 0x0105c16e + +/* QCELP 13k, IS733 */ +#define ADSP_AUDIO_FORMAT_V13K_FS 0x01080b8a + +/* EVRC 8k, IS127 */ +#define ADSP_AUDIO_FORMAT_EVRC_FS 0x01080b89 + +/* EVRC-B 8k, 4GV */ +#define ADSP_AUDIO_FORMAT_EVRCB_FS 0x0108f2a3 + +/* MIDI command stream */ +#define ADSP_AUDIO_FORMAT_MIDI 0x0103d300 + +/* A2DP SBC stream */ +#define ADSP_AUDIO_FORMAT_SBC 0x0108c4d8 + +/* Version 10 Professional */ +#define ADSP_AUDIO_FORMAT_WMA_V10PRO 0x0108aa92 + +/* Version 9 Starndard */ +#define ADSP_AUDIO_FORMAT_WMA_V9 0x0108d430 + +/* AMR WideBand Plus */ +#define ADSP_AUDIO_FORMAT_AMR_WB_PLUS 0x0108f3da + +/* AC3 Decoder */ +#define ADSP_AUDIO_FORMAT_AC3_DECODER 0x0108d5f9 + +/* Not yet supported audio media formats */ + +/* ISO/IEC 13818 */ +#define ADSP_AUDIO_FORMAT_MPEG2_AAC 0x0103d309 + +/* 3GPP TS 26.101 Sec 4.0 */ +#define ADSP_AUDIO_FORMAT_AMRNB_IF1 0x0103d305 + +/* 3GPP TS 26.101 Annex A */ +#define ADSP_AUDIO_FORMAT_AMRNB_IF2 0x01057b31 + +/* 3GPP TS 26.201 */ +#define ADSP_AUDIO_FORMAT_AMRWB_IF1 0x0103d306 + +/* 3GPP TS 26.201 */ +#define ADSP_AUDIO_FORMAT_AMRWB_IF2 0x0105c16d + +/* G.711 */ +#define ADSP_AUDIO_FORMAT_G711 0x0106201d + +/* QCELP 8k, IS96A */ +#define ADSP_AUDIO_FORMAT_V8K_FS 0x01081d29 + +/* Version 1 codec */ +#define ADSP_AUDIO_FORMAT_WMA_V1 0x01055b2b + +/* Version 2, 7 & 8 codec */ +#define ADSP_AUDIO_FORMAT_WMA_V8 0x01055b2c + +/* Version 9 Professional codec */ +#define ADSP_AUDIO_FORMAT_WMA_V9PRO 0x01055b2d + +/* Version 9 Voice codec */ +#define ADSP_AUDIO_FORMAT_WMA_SP1 0x01055b2e + +/* Version 9 Lossless codec */ +#define ADSP_AUDIO_FORMAT_WMA_LOSSLESS 0x01055b2f + +/* Real Media content, low-bitrate */ +#define ADSP_AUDIO_FORMAT_RA_SIPR 0x01042a0f + +/* Real Media content */ +#define ADSP_AUDIO_FORMAT_RA_COOK 0x01042a0e + + +/* For all of the audio formats, unless specified otherwise, */ +/* the following apply: */ +/* Format block bits are arranged in bytes and words in little-endian */ +/* order, i.e., least-significant bit first and least-significant */ +/* byte first. */ + + +/* AAC Format Block. */ + +/* AAC format block consist of a format identifier followed by */ +/* AudioSpecificConfig formatted according to ISO/IEC 14496-3 */ + +/* The following AAC format identifiers are supported */ +#define ADSP_AUDIO_AAC_ADTS 0x010619cf +#define ADSP_AUDIO_AAC_MPEG4_ADTS 0x010619d0 +#define ADSP_AUDIO_AAC_LOAS 0x010619d1 +#define ADSP_AUDIO_AAC_ADIF 0x010619d2 +#define ADSP_AUDIO_AAC_RAW 0x010619d3 +#define ADSP_AUDIO_AAC_FRAMED_RAW 0x0108c1fb + +struct adsp_audio_no_payload_format { + /* Media Format Code (must always be first element) */ + u32 format; + /* no payload for this format type */ +} __attribute__ ((packed)); + +/* Maxmum number of bytes allowed in a format block */ +#define ADSP_AUDIO_FORMAT_DATA_MAX 16 + +/* For convenience, to be used as a standard format block */ +/* for various media types that don't need a unique format block */ +/* ie. PCM, DTMF, etc. */ +struct adsp_audio_standard_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u16 channels; + u16 bits_per_sample; + u32 sampling_rate; + u8 is_signed; + u8 is_interleaved; +} __attribute__ ((packed)); + +/* ADPCM format block */ +struct adsp_audio_adpcm_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u16 channels; + u16 bits_per_sample; + u32 sampling_rate; + u8 is_signed; + u8 is_interleaved; + u32 block_size; +} __attribute__ ((packed)); + +/* MIDI format block */ +struct adsp_audio_midi_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u32 sampling_rate; + u16 channels; + u16 mode; +} __attribute__ ((packed)); + +#define ADSP_AUDIO_COMPANDING_ALAW 0x10619cd +#define ADSP_AUDIO_COMPANDING_MLAW 0x10619ce + +/* G711 format block */ +struct adsp_audio_g711_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u32 companding; +} __attribute__ ((packed)); + + +struct adsp_audio_wma_pro_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u16 format_tag; + u16 channels; + u32 samples_per_sec; + u32 avg_bytes_per_sec; + u16 block_align; + u16 valid_bits_per_sample; + u32 channel_mask; + u16 encode_opt; + u16 advanced_encode_opt; + u32 advanced_encode_opt2; + u32 drc_peak_reference; + u32 drc_peak_target; + u32 drc_average_reference; + u32 drc_average_target; +} __attribute__ ((packed)); + +struct adsp_audio_amrwb_plus_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u32 size; + u32 version; + u32 channels; + u32 amr_band_mode; + u32 amr_dtx_mode; + u32 amr_frame_format; + u32 amr_isf_index; +} __attribute__ ((packed)); + +/* Binary Byte Stream Format */ +/* Binary format type that defines a byte stream, */ +/* can be used to specify any format (ie. AAC) */ +struct adsp_audio_binary_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + /* number of bytes set in byte stream */ + u32 num_bytes; + /* Byte stream binary data */ + u8 data[ADSP_AUDIO_FORMAT_DATA_MAX]; +} __attribute__ ((packed)); + +struct adsp_audio_shared_memory_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* Number of bytes in shared memory */ + u32 len; + /* Phyisical address to data in shared memory */ + u32 address; +} __attribute__ ((packed)); + + +/* Union of all format types */ +union adsp_audio_format { + /* Basic format block with no payload */ + struct adsp_audio_no_payload_format no_payload; + /* Generic format block PCM, DTMF */ + struct adsp_audio_standard_format standard; + /* ADPCM format block */ + struct adsp_audio_adpcm_format adpcm; + /* MIDI format block */ + struct adsp_audio_midi_format midi; + /* G711 format block */ + struct adsp_audio_g711_format g711; + /* WmaPro format block */ + struct adsp_audio_wma_pro_format wma_pro; + /* WmaPro format block */ + struct adsp_audio_amrwb_plus_format amrwb_plus; + /* binary (byte stream) format block, used for AAC */ + struct adsp_audio_binary_format binary; + /* format block in shared memory */ + struct adsp_audio_shared_memory_format shared_mem; +}; + +#endif + + diff --git a/arch/arm/mach-msm/qdsp6/audiov2/dal_voice.h b/arch/arm/mach-msm/qdsp6/audiov2/dal_voice.h new file mode 100644 index 00000000000..62c1122b0e1 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/dal_voice.h @@ -0,0 +1,69 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __DAL_VOICE_H__ +#define __DAL_VOICE_H__ + +#define VOICE_DAL_DEVICE 0x02000075 +#define VOICE_DAL_PORT "DAL_AM_AUD" +#define VOICE_DAL_VERSION 0x00010000 + +#define APR_PKTV1_TYPE_EVENT_V 0 +#define APR_UNDEFINED -1 +#define APR_PKTV1_TYPE_MASK 0x00000010 +#define APR_PKTV1_TYPE_SHFT 4 + +#define APR_SET_BITMASK(mask, shift, value) \ + (((value) << (shift)) & (mask)) + +#define APR_SET_FIELD(field, value) \ + APR_SET_BITMASK((field##_MASK), (field##_SHFT), (value)) + + +enum { + VOICE_OP_INIT = DAL_OP_FIRST_DEVICE_API, + VOICE_OP_CONTROL, +}; + +struct apr_command_pkt { + uint32_t size; + uint32_t header; + uint16_t reserved1; + uint16_t src_addr; + uint16_t dst_addr; + uint16_t ret_addr; + uint32_t src_token; + uint32_t dst_token; + uint32_t ret_token; + uint32_t context; + uint32_t opcode; +} __attribute__ ((packed)); + + +#define APR_IBASIC_RSP_RESULT 0x00010000 + +#define APR_OP_CMD_CREATE 0x0001001B + +#define APR_OP_CMD_DESTROY 0x0001001C + +#define VOICE_OP_CMD_BRINGUP 0x0001001E + +#define VOICE_OP_CMD_TEARDOWN 0x0001001F + +#define VOICE_OP_CMD_SET_NETWORK 0x0001001D + +#define VOICE_OP_CMD_STREAM_SETUP 0x00010027 + +#define VOICE_OP_CMD_STREAM_TEARDOWN 0x00010028 + +#endif diff --git a/arch/arm/mach-msm/qdsp6/audiov2/evrc_in.c b/arch/arm/mach-msm/qdsp6/audiov2/evrc_in.c new file mode 100644 index 00000000000..88f19b70e5b --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/evrc_in.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include "dal_audio.h" +#include "dal_audio_format.h" +#include + + +struct evrc { + struct mutex lock; + struct msm_audio_evrc_enc_config cfg; + struct msm_audio_stream_config str_cfg; + struct audio_client *audio_client; +}; + + +static long q6_evrc_in_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct evrc *evrc = file->private_data; + struct adsp_open_command rpc; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&evrc->lock); + switch (cmd) { + case AUDIO_START: + if (evrc->audio_client) { + rc = -EBUSY; + break; + } else { + evrc->audio_client = q6audio_open(AUDIO_FLAG_READ, + evrc->str_cfg.buffer_size); + + if (!evrc->audio_client) { + kfree(evrc); + rc = -ENOMEM; + break; + } + } + + tx_clk_freq = 8000; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format_block.standard.format = ADSP_AUDIO_FORMAT_EVRC_FS; + rpc.format_block.standard.channels = 1; + rpc.format_block.standard.bits_per_sample = 16; + rpc.format_block.standard.sampling_rate = 8000; + rpc.format_block.standard.is_signed = 1; + rpc.format_block.standard.is_interleaved = 0; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + rpc.buf_max_size = evrc->str_cfg.buffer_size; + rpc.config.evrc.min_rate = evrc->cfg.min_bit_rate; + rpc.config.evrc.max_rate = evrc->cfg.max_bit_rate; + + q6audio_start(evrc->audio_client, &rpc, sizeof(rpc)); + break; + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_VOLUME: + break; + case AUDIO_GET_STREAM_CONFIG: + if (copy_to_user((void *)arg, &evrc->str_cfg, + sizeof(struct msm_audio_stream_config))) + rc = -EFAULT; + break; + case AUDIO_SET_STREAM_CONFIG: + if (copy_from_user(&evrc->str_cfg, (void *)arg, + sizeof(struct msm_audio_stream_config))) { + rc = -EFAULT; + break; + } + + if (evrc->str_cfg.buffer_size < 23) { + pr_err("[%s:%s] Buffer size too small\n", __MM_FILE__, + __func__); + rc = -EINVAL; + break; + } + + if (evrc->str_cfg.buffer_count != 2) + pr_info("[%s:%s] Buffer count set to 2\n", __MM_FILE__, + __func__); + break; + case AUDIO_SET_EVRC_ENC_CONFIG: + if (copy_from_user(&evrc->cfg, (void *) arg, + sizeof(struct msm_audio_evrc_enc_config))) + rc = -EFAULT; + + if (evrc->cfg.min_bit_rate > 4 || evrc->cfg.min_bit_rate < 1) { + pr_err("[%s:%s] invalid min bitrate\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + if (evrc->cfg.max_bit_rate > 4 || evrc->cfg.max_bit_rate < 1) { + pr_err("[%s:%s] invalid max bitrate\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + break; + case AUDIO_GET_EVRC_ENC_CONFIG: + if (copy_to_user((void *) arg, &evrc->cfg, + sizeof(struct msm_audio_evrc_enc_config))) + rc = -EFAULT; + break; + + default: + rc = -EINVAL; + } + + mutex_unlock(&evrc->lock); + return rc; +} + +static int q6_evrc_in_open(struct inode *inode, struct file *file) +{ + struct evrc *evrc; + evrc = kmalloc(sizeof(struct evrc), GFP_KERNEL); + if (evrc == NULL) { + pr_err("[%s:%s] Could not allocate memory for evrc driver\n", + __MM_FILE__, __func__); + return -ENOMEM; + } + + mutex_init(&evrc->lock); + file->private_data = evrc; + evrc->audio_client = NULL; + evrc->str_cfg.buffer_size = 23; + evrc->str_cfg.buffer_count = 2; + evrc->cfg.cdma_rate = CDMA_RATE_FULL; + evrc->cfg.min_bit_rate = 1; + evrc->cfg.max_bit_rate = 4; + + return 0; +} + +static ssize_t q6_evrc_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + struct evrc *evrc = file->private_data; + int xfer = 0; + int res; + + mutex_lock(&evrc->lock); + ac = evrc->audio_client; + if (!ac) { + res = -ENODEV; + goto fail; + } + while (count > xfer) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + xfer = ab->actual_size; + + if (copy_to_user(buf, ab->data, xfer)) { + res = -EFAULT; + goto fail; + } + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } + + res = buf - start; + +fail: + mutex_unlock(&evrc->lock); + + return res; +} + +static int q6_evrc_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct evrc *evrc = file->private_data; + + mutex_lock(&evrc->lock); + if (evrc->audio_client) + rc = q6audio_close(evrc->audio_client); + mutex_unlock(&evrc->lock); + kfree(evrc); + return rc; +} + +static const struct file_operations q6_evrc_in_fops = { + .owner = THIS_MODULE, + .open = q6_evrc_in_open, + .read = q6_evrc_in_read, + .release = q6_evrc_in_release, + .unlocked_ioctl = q6_evrc_in_ioctl, +}; + +struct miscdevice q6_evrc_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_evrc_in", + .fops = &q6_evrc_in_fops, +}; + +static int __init q6_evrc_in_init(void) +{ + return misc_register(&q6_evrc_in_misc); +} + +device_initcall(q6_evrc_in_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/mp3.c b/arch/arm/mach-msm/qdsp6/audiov2/mp3.c new file mode 100644 index 00000000000..0781eda6efd --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/mp3.c @@ -0,0 +1,205 @@ +/* arch/arm/mach-msm/qdsp6/audiov2/mp3.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "dal_audio.h" +#include "dal_audio_format.h" + +#define BUFSZ (8192) +#define DMASZ (BUFSZ * 2) + +struct mp3 { + struct mutex lock; + struct audio_client *ac; + struct msm_audio_config cfg; +}; + +static long mp3_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct mp3 *mp3 = file->private_data; + struct adsp_open_command rpc; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&mp3->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: + break; + case AUDIO_START: + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_WRITE; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.format_block.standard.format = ADSP_AUDIO_FORMAT_MP3; + rpc.format_block.standard.channels = mp3->cfg.channel_count; + rpc.format_block.standard.bits_per_sample = 16; + rpc.format_block.standard.sampling_rate = mp3->cfg.sample_rate; + rpc.format_block.standard.is_signed = 1; + rpc.format_block.standard.is_interleaved = 0; + rpc.buf_max_size = BUFSZ; + q6audio_start(mp3->ac, (void *) &rpc, sizeof(rpc)); + break; + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: + if (copy_from_user(&mp3->cfg, (void *) arg, + sizeof(struct msm_audio_config))) { + rc = -EFAULT; + break; + } + if (mp3->cfg.channel_count < 1 || mp3->cfg.channel_count > 2) { + rc = -EINVAL; + break; + } + break; + case AUDIO_GET_CONFIG: + if (copy_to_user((void *) arg, &mp3->cfg, + sizeof(struct msm_audio_config))) { + rc = -EFAULT; + } + break; + default: + rc = -EINVAL; + } + mutex_unlock(&mp3->lock); + return rc; +} + +static int mp3_open(struct inode *inode, struct file *file) +{ + + struct mp3 *mp3; + mp3 = kzalloc(sizeof(struct mp3), GFP_KERNEL); + + if (!mp3) + return -ENOMEM; + + mutex_init(&mp3->lock); + file->private_data = mp3; + mp3->ac = q6audio_open(AUDIO_FLAG_WRITE, BUFSZ); + if (!mp3->ac) { + kfree(mp3); + return -ENOMEM; + } + mp3->cfg.channel_count = 2; + mp3->cfg.buffer_count = 2; + mp3->cfg.buffer_size = BUFSZ; + mp3->cfg.unused[0] = 0; + mp3->cfg.unused[1] = 0; + mp3->cfg.unused[2] = 0; + mp3->cfg.sample_rate = 48000; + + return 0; +} + +static ssize_t mp3_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mp3 *mp3 = file->private_data; + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer; + + if (!mp3->ac) + mp3_ioctl(file, AUDIO_START, 0); + + ac = mp3->ac; + if (!ac) + return -ENODEV; + + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_from_user(ab->data, buf, xfer)) + return -EFAULT; + + buf += xfer; + count -= xfer; + + ab->used = xfer; + q6audio_write(ac, ab); + ac->cpu_buf ^= 1; + } + + return buf - start; +} + +static int mp3_fsync(struct file *f, int datasync) +{ + struct mp3 *mp3 = f->private_data; + if (mp3->ac) + return q6audio_async(mp3->ac); + return -ENODEV; +} + +static int mp3_release(struct inode *inode, struct file *file) +{ + struct mp3 *mp3 = file->private_data; + if (mp3->ac) + q6audio_close(mp3->ac); + kfree(mp3); + return 0; +} + +static const struct file_operations mp3_fops = { + .owner = THIS_MODULE, + .open = mp3_open, + .write = mp3_write, + .fsync = mp3_fsync, + .release = mp3_release, + .unlocked_ioctl = mp3_ioctl, +}; + +struct miscdevice mp3_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_mp3", + .fops = &mp3_fops, +}; + +static int __init mp3_init(void) +{ + return misc_register(&mp3_misc); +} + +device_initcall(mp3_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/pcm_in.c b/arch/arm/mach-msm/qdsp6/audiov2/pcm_in.c new file mode 100644 index 00000000000..6ef21952f25 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/pcm_in.c @@ -0,0 +1,208 @@ +/* arch/arm/mach-msm/qdsp6/audiov2/pcm_in.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "dal_audio.h" +#include "dal_audio_format.h" + +#define BUFSZ (4096) +#define DMASZ (BUFSZ * 2) + + +struct pcm { + struct mutex lock; + struct msm_audio_config cfg; + struct audio_client *audio_client; +}; + +static long q6_in_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct pcm *pcm = file->private_data; + struct adsp_open_command rpc; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&pcm->lock); + switch (cmd) { + + case AUDIO_START: + tx_clk_freq = pcm->cfg.sample_rate; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format_block.standard.format = ADSP_AUDIO_FORMAT_PCM; + rpc.format_block.standard.channels = pcm->cfg.channel_count; + rpc.format_block.standard.bits_per_sample = 16; + rpc.format_block.standard.sampling_rate = pcm->cfg.sample_rate; + rpc.format_block.standard.is_signed = 1; + rpc.format_block.standard.is_interleaved = 1; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + rpc.buf_max_size = BUFSZ; + q6audio_start(pcm->audio_client, &rpc, sizeof(rpc)); + break; + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_VOLUME: + break; + case AUDIO_SET_CONFIG: + if (copy_from_user(&pcm->cfg, (void *) arg, + sizeof(struct msm_audio_config))) { + rc = -EFAULT; + break; + } + break; + case AUDIO_GET_CONFIG: + if (copy_to_user((void *) arg, &pcm->cfg, + sizeof(struct msm_audio_config))) { + rc = -EFAULT; + } + break; + default: + rc = -EINVAL; + } + + mutex_unlock(&pcm->lock); + return rc; +} + +static int q6_in_open(struct inode *inode, struct file *file) +{ + + struct pcm *pcm; + pcm = kmalloc(sizeof(struct pcm), GFP_KERNEL); + if (pcm == NULL) { + pr_err("Could not allocate memory for pcm driver\n"); + return -ENOMEM; + } + mutex_init(&pcm->lock); + file->private_data = pcm; + pcm->audio_client = q6audio_open(AUDIO_FLAG_READ, BUFSZ); + if (!pcm->audio_client) { + kfree(pcm); + return -ENOMEM; + } + pcm->cfg.channel_count = 1; + pcm->cfg.buffer_count = 2; + pcm->cfg.buffer_size = BUFSZ; + pcm->cfg.unused[0] = 0; + pcm->cfg.unused[1] = 0; + pcm->cfg.unused[2] = 0; + pcm->cfg.sample_rate = 8000; + + return 0; +} + +static ssize_t q6_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + struct pcm *pcm = file->private_data; + int xfer; + int res; + + mutex_lock(&pcm->lock); + ac = pcm->audio_client; + if (!ac) { + res = -ENODEV; + goto fail; + } + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_to_user(buf, ab->data, xfer)) { + res = -EFAULT; + goto fail; + } + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } +fail: + res = buf - start; + mutex_unlock(&pcm->lock); + + return res; +} + +static int q6_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct pcm *pcm = file->private_data; + + mutex_lock(&pcm->lock); + if (pcm->audio_client) + rc = q6audio_close(pcm->audio_client); + mutex_unlock(&pcm->lock); + kfree(pcm); + return rc; +} + +static const struct file_operations q6_in_fops = { + .owner = THIS_MODULE, + .open = q6_in_open, + .read = q6_in_read, + .release = q6_in_release, + .unlocked_ioctl = q6_in_ioctl, +}; + +struct miscdevice q6_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_in", + .fops = &q6_in_fops, +}; + +static int __init q6_in_init(void) +{ + return misc_register(&q6_in_misc); +} + +device_initcall(q6_in_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/pcm_out.c b/arch/arm/mach-msm/qdsp6/audiov2/pcm_out.c new file mode 100644 index 00000000000..6743c6c7741 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/pcm_out.c @@ -0,0 +1,196 @@ +/* arch/arm/mach-msm/qdsp6/audiov2/pcm_out.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "dal_audio.h" +#include "dal_audio_format.h" + +#define BUFSZ (8192) +#define DMASZ (BUFSZ * 2) + +struct pcm { + struct mutex lock; + struct audio_client *ac; + struct msm_audio_config cfg; + +}; + +static long pcm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct pcm *pcm = file->private_data; + struct adsp_open_command rpc; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&pcm->lock); + switch (cmd) { + case AUDIO_START: + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_WRITE; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.format_block.standard.format = ADSP_AUDIO_FORMAT_PCM; + rpc.format_block.standard.channels = pcm->cfg.channel_count; + rpc.format_block.standard.bits_per_sample = 16; + rpc.format_block.standard.sampling_rate = pcm->cfg.sample_rate; + rpc.format_block.standard.is_signed = 1; + rpc.format_block.standard.is_interleaved = 1; + rpc.buf_max_size = BUFSZ; + q6audio_start(pcm->ac, (void *) &rpc, sizeof(rpc)); + break; + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: + if (copy_from_user(&pcm->cfg, (void *) arg, + sizeof(struct msm_audio_config))) { + rc = -EFAULT; + break; + } + if (pcm->cfg.channel_count < 1 || pcm->cfg.channel_count > 2) { + rc = -EINVAL; + break; + } + + break; + case AUDIO_GET_CONFIG: + if (copy_to_user((void *) arg, &pcm->cfg, + sizeof(struct msm_audio_config))) { + rc = -EFAULT; + } + break; + + default: + rc = -EINVAL; + } + + mutex_unlock(&pcm->lock); + return rc; +} + +static int pcm_open(struct inode *inode, struct file *file) +{ + struct pcm *pcm; + pcm = kzalloc(sizeof(struct pcm), GFP_KERNEL); + + if (!pcm) + return -ENOMEM; + + mutex_init(&pcm->lock); + file->private_data = pcm; + pcm->ac = q6audio_open(AUDIO_FLAG_WRITE, BUFSZ); + if (!pcm->ac) { + kfree(pcm); + return -ENOMEM; + } + pcm->cfg.channel_count = 2; + pcm->cfg.buffer_count = 2; + pcm->cfg.buffer_size = BUFSZ; + pcm->cfg.unused[0] = 0; + pcm->cfg.unused[1] = 0; + pcm->cfg.unused[2] = 0; + pcm->cfg.sample_rate = 48000; + + return 0; +} + +static ssize_t pcm_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct pcm *pcm = file->private_data; + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer; + + ac = pcm->ac; + if (!ac) + return -ENODEV; + + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_from_user(ab->data, buf, xfer)) + return -EFAULT; + + buf += xfer; + count -= xfer; + + ab->used = 1; + ab->actual_size = xfer; + q6audio_write(ac, ab); + ac->cpu_buf ^= 1; + } + + return buf - start; +} + +static int pcm_release(struct inode *inode, struct file *file) +{ + struct pcm *pcm = file->private_data; + if (pcm->ac) + q6audio_close(pcm->ac); + kfree(pcm); + return 0; +} + +static const struct file_operations pcm_fops = { + .owner = THIS_MODULE, + .open = pcm_open, + .write = pcm_write, + .release = pcm_release, + .unlocked_ioctl = pcm_ioctl, +}; + +struct miscdevice pcm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_out", + .fops = &pcm_fops, +}; + +static int __init pcm_init(void) +{ + return misc_register(&pcm_misc); +} + +device_initcall(pcm_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/q6audio.c b/arch/arm/mach-msm/qdsp6/audiov2/q6audio.c new file mode 100644 index 00000000000..f713e3dff01 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/q6audio.c @@ -0,0 +1,1312 @@ +/* arch/arm/mach-msm/qdsp6/audiov2/q6audio.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../dal.h" +#include "dal_audio.h" +#include "dal_audio_format.h" +#include "dal_acdb.h" +#include "dal_adie.h" +#include "q6audio_devices.h" + +struct q6_hw_info { + int min_gain; + int max_gain; +}; + +/* TODO: provide mechanism to configure from board file */ + +static struct q6_hw_info q6_audio_hw[Q6_HW_COUNT] = { + [Q6_HW_HANDSET] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_HEADSET] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_SPEAKER] = { + .min_gain = -1500, + .max_gain = 0, + }, + [Q6_HW_TTY] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_BT_SCO] = { + .min_gain = -2000, + .max_gain = 0, + }, + [Q6_HW_BT_A2DP] = { + .min_gain = -2000, + .max_gain = 0, + }, +}; + +static struct wake_lock idlelock; +static int idlecount; +static DEFINE_MUTEX(idlecount_lock); + +void audio_prevent_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (++idlecount == 1) + wake_lock(&idlelock); + mutex_unlock(&idlecount_lock); +} + +void audio_allow_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (--idlecount == 0) + wake_unlock(&idlelock); + mutex_unlock(&idlecount_lock); +} + +static struct clk *icodec_rx_clk; +static struct clk *icodec_tx_clk; +static struct clk *ecodec_clk; +static struct clk *sdac_clk; + +static struct q6audio_analog_ops default_analog_ops; +static struct q6audio_analog_ops *analog_ops = &default_analog_ops; +uint32_t tx_clk_freq = 8000; +static int tx_mute_status; + +void q6audio_register_analog_ops(struct q6audio_analog_ops *ops) +{ + analog_ops = ops; +} + +static struct q6_device_info *q6_lookup_device(uint32_t device_id) +{ + struct q6_device_info *di = q6_audio_devices; + for (;;) { + if (di->id == device_id) + return di; + if (di->id == 0) { + pr_err("q6_lookup_device: bogus id 0x%08x\n", + device_id); + return di; + } + di++; + } +} + +static uint32_t q6_device_to_codec(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->codec; +} + +static uint32_t q6_device_to_dir(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->dir; +} + +static uint32_t q6_device_to_cad_id(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->cad_id; +} + +static uint32_t q6_device_to_path(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->path; +} + +static uint32_t q6_device_to_rate(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + return di->rate; +} + +int q6_device_volume(uint32_t device_id, int level) +{ + struct q6_device_info *di = q6_lookup_device(device_id); + struct q6_hw_info *hw; + + hw = &q6_audio_hw[di->hw]; + + return hw->min_gain + ((hw->max_gain - hw->min_gain) * level) / 100; +} + +static inline int adie_open(struct dal_client *client) +{ + return dal_call_f0(client, DAL_OP_OPEN, 0); +} + +static inline int adie_close(struct dal_client *client) +{ + return dal_call_f0(client, DAL_OP_CLOSE, 0); +} + +static inline int adie_set_path(struct dal_client *client, + uint32_t *adie_params, uint32_t size) +{ + uint32_t tmp; + return dal_call(client, ADIE_OP_SET_PATH, 5, adie_params, size, + (void *)&tmp, sizeof(uint32_t)); + +} + +static inline int adie_proceed_to_stage(struct dal_client *client, + uint32_t path_type, uint32_t stage) +{ + return dal_call_f1(client, ADIE_OP_PROCEED_TO_STAGE, + path_type, stage); +} + +static int adie_refcount; + +static struct dal_client *adie; +static struct dal_client *adsp; +static struct dal_client *acdb; + +static int adie_enable(void) +{ + adie_refcount++; + if (adie_refcount == 1) + adie_open(adie); + return 0; +} + +static int adie_disable(void) +{ + adie_refcount--; + if (adie_refcount == 0) + adie_close(adie); + return 0; +} + +/* 4k DMA scratch page used for exchanging acdb device config tables + * and stream format descriptions with the DSP. + */ +char *audio_data; +int32_t audio_phys; + +#define SESSION_MIN 0 +#define SESSION_MAX 64 + +static DEFINE_MUTEX(session_lock); +static DEFINE_MUTEX(audio_lock); + +static struct audio_client *session[SESSION_MAX]; + +static int session_alloc(struct audio_client *ac) +{ + int n; + + mutex_lock(&session_lock); + for (n = SESSION_MIN; n < SESSION_MAX; n++) { + if (!session[n]) { + session[n] = ac; + mutex_unlock(&session_lock); + return n; + } + } + mutex_unlock(&session_lock); + return -ENOMEM; +} + +static void session_free(int n, struct audio_client *ac) +{ + mutex_lock(&session_lock); + if (session[n] == ac) + session[n] = 0; + mutex_unlock(&session_lock); +} + +static void audio_client_free(struct audio_client *ac) +{ + session_free(ac->session, ac); + + if (ac->buf[0].data) + pmem_kfree(ac->buf[0].phys); + if (ac->buf[1].data) + pmem_kfree(ac->buf[1].phys); + kfree(ac); +} + +static struct audio_client *audio_client_alloc(unsigned bufsz) +{ + struct audio_client *ac; + int n; + + ac = kzalloc(sizeof(*ac), GFP_KERNEL); + if (!ac) + return 0; + + n = session_alloc(ac); + if (n < 0) + goto fail_session; + ac->session = n; + + if (bufsz > 0) { + ac->buf[0].phys = pmem_kalloc(bufsz, + PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + ac->buf[0].data = ioremap(ac->buf[0].phys, bufsz); + if (!ac->buf[0].data) + goto fail; + + ac->buf[1].phys = pmem_kalloc(bufsz, + PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + ac->buf[1].data = ioremap(ac->buf[1].phys, bufsz); + if (!ac->buf[1].data) + goto fail; + + ac->buf[0].size = bufsz; + ac->buf[1].size = bufsz; + } + + init_waitqueue_head(&ac->wait); + ac->client = adsp; + + return ac; + +fail: + pr_err("pmem_kalloc failed\n"); + session_free(n, ac); +fail_session: + audio_client_free(ac); + return 0; +} + +static int audio_ioctl(struct audio_client *ac, void *ptr, uint32_t len) +{ + struct adsp_command_hdr *hdr = ptr; + uint32_t tmp; + int r; + + hdr->size = len - sizeof(u32); + hdr->dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + hdr->src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + hdr->context = ac->session; + ac->cb_status = -EBUSY; + r = dal_call(ac->client, AUDIO_OP_CONTROL, 5, ptr, len, + &tmp, sizeof(tmp)); + if (r != 4) + return -EIO; + wait_event(ac->wait, (ac->cb_status != -EBUSY)); + return tmp; +} + +static int audio_command(struct audio_client *ac, uint32_t cmd) +{ + struct adsp_command_hdr rpc; + memset(&rpc, 0, sizeof(rpc)); + rpc.opcode = cmd; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_open_control(struct audio_client *ac) +{ + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_DEVICE; + rpc.hdr.dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + rpc.hdr.src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + + +static int audio_close(struct audio_client *ac) +{ + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_STREAM_STOP); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_CLOSE); + return 0; +} + +static int audio_set_table(struct audio_client *ac, + uint32_t device_id, int size) +{ + struct adsp_set_dev_cfg_table_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_SET_DEVICE_CONFIG_TABLE; + rpc.hdr.dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + rpc.hdr.src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + rpc.device_id = device_id; + rpc.phys_addr = audio_phys; + rpc.phys_size = size; + rpc.phys_used = size; + + if (q6_device_to_dir(device_id) == Q6_TX) + rpc.hdr.data = tx_clk_freq; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +int q6audio_read(struct audio_client *ac, struct audio_buffer *ab) +{ + struct adsp_buffer_command rpc; + uint32_t res; + int r; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.size = sizeof(rpc) - sizeof(u32); + rpc.hdr.dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + rpc.hdr.src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + rpc.hdr.context = ac->session; + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_DATA_TX; + rpc.buffer.addr = ab->phys; + rpc.buffer.max_size = ab->size; + rpc.buffer.actual_size = ab->actual_size; + + r = dal_call(ac->client, AUDIO_OP_DATA, 5, &rpc, sizeof(rpc), + &res, sizeof(res)); + + if ((r == sizeof(res))) + return 0; + + return -EIO; + +} + +int q6audio_write(struct audio_client *ac, struct audio_buffer *ab) +{ + struct adsp_buffer_command rpc; + uint32_t res; + int r; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.size = sizeof(rpc) - sizeof(u32); + rpc.hdr.src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + rpc.hdr.dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + rpc.hdr.context = ac->session; + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_DATA_RX; + rpc.buffer.addr = ab->phys; + rpc.buffer.max_size = ab->size; + rpc.buffer.actual_size = ab->actual_size; + + r = dal_call(ac->client, AUDIO_OP_DATA, 5, &rpc, sizeof(rpc), + &res, sizeof(res)); + return 0; +} + +static int audio_rx_volume(struct audio_client *ac, uint32_t dev_id, + int32_t volume) +{ + struct adsp_set_dev_volume_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_VOL; + rpc.hdr.dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + rpc.hdr.src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_RX; + rpc.volume = volume; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_rx_mute(struct audio_client *ac, uint32_t dev_id, int mute) +{ + struct adsp_set_dev_mute_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_MUTE; + rpc.hdr.dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + rpc.hdr.src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_RX; + rpc.mute = !!mute; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_tx_volume(struct audio_client *ac, uint32_t dev_id, + int32_t volume) +{ + struct adsp_set_dev_volume_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_VOL; + rpc.hdr.dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + rpc.hdr.src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_TX; + rpc.volume = volume; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_tx_mute(struct audio_client *ac, uint32_t dev_id, int mute) +{ + struct adsp_set_dev_mute_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_MUTE; + rpc.hdr.dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + rpc.hdr.src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_TX; + rpc.mute = !!mute; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static void callback(void *data, int len, void *cookie) +{ + struct adsp_event_hdr *e = data; + struct audio_client *ac; + struct adsp_buffer_event *abe = data; + + if (e->context >= SESSION_MAX) { + pr_err("audio callback: bogus session %d\n", + e->context); + return; + } + ac = session[e->context]; + if (!ac) { + pr_err("audio callback: unknown session %d\n", + e->context); + return; + } + + if (e->event_id == ADSP_AUDIO_IOCTL_CMD_STREAM_EOS) { + pr_info("playback done\n"); + if (e->status) + pr_err("playback status %d\n", e->status); + if (ac->cb_status == -EBUSY) { + ac->cb_status = e->status; + wake_up(&ac->wait); + } + return; + } + + if (e->event_id == ADSP_AUDIO_EVT_STATUS_BUF_DONE) { + if (e->status) + pr_err("buffer status %d\n", e->status); + + ac->buf[ac->dsp_buf].actual_size = abe->buffer.actual_size; + ac->buf[ac->dsp_buf].used = 0; + ac->dsp_buf ^= 1; + wake_up(&ac->wait); + return; + } + + if (e->status) + pr_warning("audio_cb: s=%d e=%08x status=%d\n", + e->context, e->event_id, e->status); + + if (ac->cb_status == -EBUSY) { + ac->cb_status = e->status; + wake_up(&ac->wait); + } +} + +static void audio_init(struct dal_client *client) +{ + u32 tmp[3]; + + tmp[0] = 2 * sizeof(u32); + tmp[1] = 0; + tmp[2] = 0; + dal_call(client, AUDIO_OP_INIT, 5, tmp, sizeof(tmp), + tmp, sizeof(u32)); +} + +static struct audio_client *ac_control; + +static int q6audio_init(void) +{ + struct audio_client *ac = 0; + int res = -ENODEV; + + mutex_lock(&audio_lock); + if (ac_control) { + res = 0; + goto done; + } + + icodec_rx_clk = clk_get(0, "icodec_rx_clk"); + icodec_tx_clk = clk_get(0, "icodec_tx_clk"); + ecodec_clk = clk_get(0, "ecodec_clk"); + sdac_clk = clk_get(0, "sdac_clk"); + + tx_mute_status = 0; + audio_phys = pmem_kalloc(4096, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + audio_data = ioremap(audio_phys, 4096); + if (!audio_data) { + pr_err("pmem kalloc failed\n"); + res = -ENOMEM; + goto done; + } + + adsp = dal_attach(AUDIO_DAL_DEVICE, AUDIO_DAL_PORT, 1, + callback, 0); + if (!adsp) { + pr_err("audio_init: cannot attach to adsp\n"); + res = -ENODEV; + goto done; + } + if (check_version(adsp, AUDIO_DAL_VERSION) != 0) { + pr_err("Incompatible adsp version\n"); + res = -ENODEV; + goto done; + } + + audio_init(adsp); + + ac = audio_client_alloc(0); + if (!ac) { + pr_err("audio_init: cannot allocate client\n"); + res = -ENOMEM; + goto done; + } + + if (audio_open_control(ac)) { + pr_err("audio_init: cannot open control channel\n"); + res = -ENODEV; + goto done; + } + + acdb = dal_attach(ACDB_DAL_DEVICE, ACDB_DAL_PORT, 0, 0, 0); + if (!acdb) { + pr_err("audio_init: cannot attach to acdb channel\n"); + res = -ENODEV; + goto done; + } + if (check_version(acdb, ACDB_DAL_VERSION) != 0) { + pr_err("Incompatablie acdb version\n"); + res = -ENODEV; + goto done; + } + + + adie = dal_attach(ADIE_DAL_DEVICE, ADIE_DAL_PORT, 0, 0, 0); + if (!adie) { + pr_err("audio_init: cannot attach to adie\n"); + res = -ENODEV; + goto done; + } + if (check_version(adie, ADIE_DAL_VERSION) != 0) { + pr_err("Incompatablie adie version\n"); + res = -ENODEV; + goto done; + } + if (analog_ops->init) + analog_ops->init(); + + res = 0; + ac_control = ac; + + wake_lock_init(&idlelock, WAKE_LOCK_IDLE, "audio_pcm_idle"); +done: + if ((res < 0) && ac) + audio_client_free(ac); + mutex_unlock(&audio_lock); + + return res; +} + +static int acdb_get_config_table(uint32_t device_id, uint32_t sample_rate) +{ + struct acdb_cmd_device_table rpc; + struct acdb_result res; + int r; + + if (q6audio_init()) + return 0; + + memset(audio_data, 0, 4096); + memset(&rpc, 0, sizeof(rpc)); + + rpc.size = sizeof(rpc) - (2 * sizeof(uint32_t)); + rpc.command_id = ACDB_GET_DEVICE_TABLE; + rpc.device_id = q6_device_to_cad_id(device_id); + rpc.network_id = 0x00010023; + rpc.sample_rate_id = sample_rate; + rpc.total_bytes = 4096; + rpc.unmapped_buf = audio_phys; + rpc.res_size = sizeof(res) - (2 * sizeof(uint32_t)); + + r = dal_call(acdb, ACDB_OP_IOCTL, 8, &rpc, sizeof(rpc), + &res, sizeof(res)); + + if ((r == sizeof(res)) && (res.dal_status == 0)) + return res.used_bytes; + + return -EIO; +} + +static uint32_t audio_rx_path_id = ADIE_PATH_HANDSET_RX; +static uint32_t audio_rx_device_id = ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR; +static uint32_t audio_rx_device_group = -1; +static uint32_t audio_tx_path_id = ADIE_PATH_HANDSET_TX; +static uint32_t audio_tx_device_id = ADSP_AUDIO_DEVICE_ID_HANDSET_MIC; +static uint32_t audio_tx_device_group = -1; + +static int qdsp6_devchg_notify(struct audio_client *ac, + uint32_t dev_type, uint32_t dev_id) +{ + struct adsp_device_switch_command rpc; + + if (dev_type != ADSP_AUDIO_RX_DEVICE && + dev_type != ADSP_AUDIO_TX_DEVICE) + return -EINVAL; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_PREPARE; + rpc.hdr.dest = AUDIO_ADDR(DOMAIN_DSP, ac->session, 0); + rpc.hdr.src = AUDIO_ADDR(DOMAIN_APP, ac->session, 0); + + if (dev_type == ADSP_AUDIO_RX_DEVICE) { + rpc.old_device = audio_rx_device_id; + rpc.new_device = dev_id; + } else { + rpc.old_device = audio_tx_device_id; + rpc.new_device = dev_id; + } + rpc.device_class = 0; + rpc.device_type = dev_type; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int qdsp6_standby(struct audio_client *ac) +{ + return audio_command(ac, ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_STANDBY); +} + +static int qdsp6_start(struct audio_client *ac) +{ + return audio_command(ac, ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_COMMIT); +} + +static void audio_rx_analog_enable(int en) +{ + switch (audio_rx_device_id) { + case ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_MONO: + case ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO: + case ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_SPKR: + if (analog_ops->headset_enable) + analog_ops->headset_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_MONO_HEADSET: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_STEREO_HEADSET: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_MONO_HEADSET: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_STEREO_HEADSET: + if (analog_ops->headset_enable) + analog_ops->headset_enable(en); + if (analog_ops->speaker_enable) + analog_ops->speaker_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO: + if (analog_ops->speaker_enable) + analog_ops->speaker_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_BT_SCO_SPKR: + if (analog_ops->bt_sco_enable) + analog_ops->bt_sco_enable(en); + break; + } +} + +static void audio_tx_analog_enable(int en) +{ + switch (audio_tx_device_id) { + case ADSP_AUDIO_DEVICE_ID_HANDSET_MIC: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MIC: + if (analog_ops->int_mic_enable) + analog_ops->int_mic_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_HEADSET_MIC: + case ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_MIC: + if (analog_ops->ext_mic_enable) + analog_ops->ext_mic_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_BT_SCO_MIC: + if (analog_ops->bt_sco_enable) + analog_ops->bt_sco_enable(en); + break; + } +} + +static void _audio_rx_path_enable(void) +{ + uint32_t adev, sample_rate; + int sz; + uint32_t adie_params[5]; + + adev = audio_rx_device_id; + sample_rate = q6_device_to_rate(adev); + + sz = acdb_get_config_table(adev, sample_rate); + audio_set_table(ac_control, adev, sz); + + adie_params[0] = 4*sizeof(uint32_t); + adie_params[1] = audio_rx_path_id; + adie_params[2] = ADIE_PATH_RX; + adie_params[3] = 48000; + adie_params[4] = 256; + /*check for errors here*/ + if (!adie_set_path(adie, adie_params, sizeof(adie_params))) + pr_err("adie set rx path failed\n"); + + adie_proceed_to_stage(adie, ADIE_PATH_RX, + ADIE_STAGE_DIGITAL_READY); + adie_proceed_to_stage(adie, ADIE_PATH_RX, + ADIE_STAGE_DIGITAL_ANALOG_READY); + + audio_rx_analog_enable(1); + + audio_rx_mute(ac_control, adev, 0); + + audio_rx_volume(ac_control, adev, q6_device_volume(adev, 100)); +} + +static void _audio_tx_path_enable(void) +{ + uint32_t adev; + int sz; + uint32_t adie_params[5]; + + adev = audio_tx_device_id; + + pr_info("audiolib: load %08x cfg table\n", adev); + + if (tx_clk_freq > 16000) { + adie_params[3] = 48000; + sz = acdb_get_config_table(adev, 48000); + + } else if (tx_clk_freq > 8000) { + adie_params[3] = 16000; + sz = acdb_get_config_table(adev, 16000); + } else { + + adie_params[3] = 8000; + sz = acdb_get_config_table(adev, 8000); + } + + pr_info("cfg table is %d bytes\n", sz); + audio_set_table(ac_control, adev, sz); + + pr_info("audiolib: set adie tx path\n"); + + adie_params[0] = 4*sizeof(uint32_t); + adie_params[1] = audio_tx_path_id; + adie_params[2] = ADIE_PATH_TX; + adie_params[4] = 256; + + if (!adie_set_path(adie, adie_params, sizeof(adie_params))) + pr_err("adie set tx path failed\n"); + + adie_proceed_to_stage(adie, ADIE_PATH_TX, + ADIE_STAGE_DIGITAL_READY); + adie_proceed_to_stage(adie, ADIE_PATH_TX, + ADIE_STAGE_DIGITAL_ANALOG_READY); + + audio_tx_analog_enable(1); + audio_tx_mute(ac_control, adev, tx_mute_status); + + if (!tx_mute_status) + audio_tx_volume(ac_control, adev, q6_device_volume(adev, 100)); +} + +static void _audio_rx_path_disable(void) +{ + audio_rx_analog_enable(0); + + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_ANALOG_OFF); + adie_proceed_to_stage(adie, ADIE_PATH_RX, ADIE_STAGE_DIGITAL_OFF); +} + +static void _audio_tx_path_disable(void) +{ + audio_tx_analog_enable(0); + + adie_proceed_to_stage(adie, ADIE_PATH_TX, ADIE_STAGE_ANALOG_OFF); + adie_proceed_to_stage(adie, ADIE_PATH_TX, ADIE_STAGE_DIGITAL_OFF); +} + +static int icodec_rx_clk_refcount; +static int icodec_tx_clk_refcount; +static int ecodec_clk_refcount; +static int sdac_clk_refcount; + +static void _audio_rx_clk_enable(void) +{ + uint32_t device_group = q6_device_to_codec(audio_rx_device_id); + + switch (device_group) { + case Q6_ICODEC_RX: + icodec_rx_clk_refcount++; + if (icodec_rx_clk_refcount == 1) { + clk_set_rate(icodec_rx_clk, 12288000); + clk_enable(icodec_rx_clk); + } + break; + case Q6_ECODEC_RX: + ecodec_clk_refcount++; + if (ecodec_clk_refcount == 1) { + clk_set_rate(ecodec_clk, 2048000); + clk_enable(ecodec_clk); + } + break; + case Q6_SDAC_RX: + sdac_clk_refcount++; + if (sdac_clk_refcount == 1) { + clk_set_rate(sdac_clk, 12288000); + clk_enable(sdac_clk); + } + break; + default: + return; + } + audio_rx_device_group = device_group; +} + +static void _audio_tx_clk_enable(void) +{ + uint32_t device_group = q6_device_to_codec(audio_tx_device_id); + + switch (device_group) { + case Q6_ICODEC_TX: + icodec_tx_clk_refcount++; + if (icodec_tx_clk_refcount == 1) { + clk_set_rate(icodec_tx_clk, tx_clk_freq * 256); + clk_enable(icodec_tx_clk); + } + break; + case Q6_ECODEC_TX: + ecodec_clk_refcount++; + if (ecodec_clk_refcount == 1) { + clk_set_rate(ecodec_clk, 2048000); + clk_enable(ecodec_clk); + } + break; + case Q6_SDAC_TX: + /* TODO: In QCT BSP, clk rate was set to 20480000 */ + sdac_clk_refcount++; + if (sdac_clk_refcount == 1) { + clk_set_rate(sdac_clk, 12288000); + clk_enable(sdac_clk); + } + break; + default: + return; + } + audio_tx_device_group = device_group; +} + +static void _audio_rx_clk_disable(void) +{ + switch (audio_rx_device_group) { + case Q6_ICODEC_RX: + icodec_rx_clk_refcount--; + if (icodec_rx_clk_refcount == 0) { + clk_disable(icodec_rx_clk); + audio_rx_device_group = -1; + } + break; + case Q6_ECODEC_RX: + ecodec_clk_refcount--; + if (ecodec_clk_refcount == 0) { + clk_disable(ecodec_clk); + audio_rx_device_group = -1; + } + break; + case Q6_SDAC_RX: + sdac_clk_refcount--; + if (sdac_clk_refcount == 0) { + clk_disable(sdac_clk); + audio_rx_device_group = -1; + } + break; + default: + pr_err("audiolib: invalid rx device group %d\n", + audio_rx_device_group); + break; + } +} + +static void _audio_tx_clk_disable(void) +{ + switch (audio_tx_device_group) { + case Q6_ICODEC_TX: + icodec_tx_clk_refcount--; + if (icodec_tx_clk_refcount == 0) { + clk_disable(icodec_tx_clk); + audio_tx_device_group = -1; + } + break; + case Q6_ECODEC_TX: + ecodec_clk_refcount--; + if (ecodec_clk_refcount == 0) { + clk_disable(ecodec_clk); + audio_tx_device_group = -1; + } + break; + case Q6_SDAC_TX: + sdac_clk_refcount--; + if (sdac_clk_refcount == 0) { + clk_disable(sdac_clk); + audio_tx_device_group = -1; + } + break; + default: + pr_err("audiolib: invalid tx device group %d\n", + audio_tx_device_group); + break; + } +} + +static void _audio_rx_clk_reinit(uint32_t rx_device) +{ + uint32_t device_group = q6_device_to_codec(rx_device); + + if (device_group != audio_rx_device_group) + _audio_rx_clk_disable(); + + audio_rx_device_id = rx_device; + audio_rx_path_id = q6_device_to_path(rx_device); + + if (device_group != audio_rx_device_group) + _audio_rx_clk_enable(); + +} + +static void _audio_tx_clk_reinit(uint32_t tx_device) +{ + uint32_t device_group = q6_device_to_codec(tx_device); + + if (device_group != audio_tx_device_group) + _audio_tx_clk_disable(); + + audio_tx_device_id = tx_device; + audio_tx_path_id = q6_device_to_path(tx_device); + + if (device_group != audio_tx_device_group) + _audio_tx_clk_enable(); +} + +static DEFINE_MUTEX(audio_path_lock); +static int audio_rx_path_refcount; +static int audio_tx_path_refcount; + +static int audio_rx_path_enable(int en) +{ + mutex_lock(&audio_path_lock); + if (en) { + audio_rx_path_refcount++; + if (audio_rx_path_refcount == 1) { + adie_enable(); + _audio_rx_clk_enable(); + _audio_rx_path_enable(); + } + } else { + audio_rx_path_refcount--; + if (audio_rx_path_refcount == 0) { + _audio_rx_path_disable(); + _audio_rx_clk_disable(); + adie_disable(); + } + } + mutex_unlock(&audio_path_lock); + return 0; +} + +static int audio_tx_path_enable(int en) +{ + mutex_lock(&audio_path_lock); + if (en) { + audio_tx_path_refcount++; + if (audio_tx_path_refcount == 1) { + adie_enable(); + _audio_tx_clk_enable(); + _audio_tx_path_enable(); + } + } else { + audio_tx_path_refcount--; + if (audio_tx_path_refcount == 0) { + _audio_tx_path_disable(); + _audio_tx_clk_disable(); + adie_disable(); + } + } + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_update_acdb(uint32_t id_src, uint32_t id_dst) +{ + mutex_lock(&audio_path_lock); + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_tx_mute(int mute) +{ + uint32_t adev; + int rc; + + if (q6audio_init()) + return 0; + + mutex_lock(&audio_path_lock); + + if (mute == tx_mute_status) { + mutex_unlock(&audio_path_lock); + return 0; + } + + adev = audio_tx_device_id; + rc = audio_tx_mute(ac_control, adev, mute); + if (!rc) + tx_mute_status = mute; + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_rx_volume(int level) +{ + uint32_t adev; + int vol; + + if (q6audio_init()) + return 0; + + if (level < 0 || level > 100) + return -EINVAL; + + mutex_lock(&audio_path_lock); + adev = audio_rx_device_id; + vol = q6_device_volume(adev, level); + audio_rx_mute(ac_control, adev, 0); + audio_rx_volume(ac_control, adev, vol); + mutex_unlock(&audio_path_lock); + return 0; +} + +static void do_rx_routing(uint32_t device_id) +{ + int sz; + uint32_t sample_rate; + + if (device_id == audio_rx_device_id) + return; + + if (audio_rx_path_refcount > 0) { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, + device_id); + _audio_rx_path_disable(); + _audio_rx_clk_reinit(device_id); + _audio_rx_path_enable(); + } else { + sample_rate = q6_device_to_rate(device_id); + sz = acdb_get_config_table(device_id, sample_rate); + if (sz < 0) + pr_err("could not get ACDB config table\n"); + + audio_set_table(ac_control, device_id, sz); + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, + device_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + audio_rx_device_id = device_id; + audio_rx_path_id = q6_device_to_path(device_id); + } +} + +static void do_tx_routing(uint32_t device_id) +{ + int sz; + uint32_t sample_rate; + + if (device_id == audio_tx_device_id) + return; + + if (audio_tx_path_refcount > 0) { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, + device_id); + _audio_tx_path_disable(); + _audio_tx_clk_reinit(device_id); + _audio_tx_path_enable(); + } else { + sample_rate = q6_device_to_rate(device_id); + sz = acdb_get_config_table(device_id, sample_rate); + audio_set_table(ac_control, device_id, sz); + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, + device_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + audio_tx_device_id = device_id; + audio_tx_path_id = q6_device_to_path(device_id); + } +} + +int q6audio_do_routing(uint32_t device_id) +{ + if (q6audio_init()) + return 0; + + mutex_lock(&audio_path_lock); + + switch (q6_device_to_dir(device_id)) { + case Q6_RX: + do_rx_routing(device_id); + break; + case Q6_TX: + do_tx_routing(device_id); + break; + } + + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_route(const char *name) +{ + uint32_t route; + if (!strcmp(name, "speaker")) + route = ADIE_PATH_SPEAKER_STEREO_RX; + else if (!strcmp(name, "headphones")) + route = ADIE_PATH_HEADSET_STEREO_RX; + else if (!strcmp(name, "handset")) + route = ADIE_PATH_HANDSET_RX; + else + return -EINVAL; + + mutex_lock(&audio_path_lock); + if (route == audio_rx_path_id) + goto done; + + audio_rx_path_id = route; + + if (audio_rx_path_refcount > 0) { + _audio_rx_path_disable(); + _audio_rx_path_enable(); + } + if (audio_tx_path_refcount > 0) { + _audio_tx_path_disable(); + _audio_tx_path_enable(); + } +done: + mutex_unlock(&audio_path_lock); + return 0; +} + +struct audio_client *q6audio_open(uint32_t flags, uint32_t bufsz) +{ + struct audio_client *ac; + + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(bufsz); + if (!ac) + return 0; + + ac->flags = flags; + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(1); + else + audio_tx_path_enable(1); + + return ac; +} + +int q6audio_start(struct audio_client *ac, void *rpc, + uint32_t len) +{ + + audio_ioctl(ac, rpc, len); + + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + if (!(ac->flags & AUDIO_FLAG_WRITE)) { + ac->buf[0].used = 1; + ac->buf[1].used = 1; + q6audio_read(ac, &ac->buf[0]); + q6audio_read(ac, &ac->buf[1]); + } + + audio_prevent_sleep(); + return 0; +} + +int q6audio_close(struct audio_client *ac) +{ + audio_close(ac); + + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(0); + else + audio_tx_path_enable(0); + + audio_client_free(ac); + audio_allow_sleep(); + return 0; +} + +struct audio_client *q6voice_open(void) +{ + struct audio_client *ac; + + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(0); + if (!ac) + return 0; + + return ac; +} + +int q6voice_setup(void) +{ + audio_rx_path_enable(1); + tx_clk_freq = 8000; + audio_tx_path_enable(1); + + return 0; +} + +int q6voice_teardown(void) +{ + audio_rx_path_enable(0); + audio_tx_path_enable(0); + return 0; +} + + +int q6voice_close(struct audio_client *ac) +{ + audio_client_free(ac); + return 0; +} + +int q6audio_async(struct audio_client *ac) +{ + struct adsp_command_hdr rpc; + memset(&rpc, 0, sizeof(rpc)); + rpc.opcode = ADSP_AUDIO_IOCTL_CMD_STREAM_EOS; + rpc.response_type = ADSP_AUDIO_RESPONSE_ASYNC; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} diff --git a/arch/arm/mach-msm/qdsp6/audiov2/q6audio_devices.h b/arch/arm/mach-msm/qdsp6/audiov2/q6audio_devices.h new file mode 100644 index 00000000000..aa8a6990393 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/q6audio_devices.h @@ -0,0 +1,276 @@ +/* arch/arm/mach-msm/qdsp6/audiov2/q6audio_devices.h + * + * Copyright (C) 2009 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Author: Brian Swetland + * + * 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. + * + */ + +struct q6_device_info { + uint32_t id; + uint32_t cad_id; + uint32_t path; + uint32_t rate; + uint8_t dir; + uint8_t codec; + uint8_t hw; +}; + +#define Q6_ICODEC_RX 0 +#define Q6_ICODEC_TX 1 +#define Q6_ECODEC_RX 2 +#define Q6_ECODEC_TX 3 +#define Q6_SDAC_RX 6 +#define Q6_SDAC_TX 7 +#define Q6_CODEC_NONE 255 + +#define Q6_TX 1 +#define Q6_RX 2 +#define Q6_TX_RX 3 + +#define Q6_HW_HANDSET 0 +#define Q6_HW_HEADSET 1 +#define Q6_HW_SPEAKER 2 +#define Q6_HW_TTY 3 +#define Q6_HW_BT_SCO 4 +#define Q6_HW_BT_A2DP 5 + +#define Q6_HW_COUNT 6 + +#define CAD_HW_DEVICE_ID_HANDSET_MIC 0x01 +#define CAD_HW_DEVICE_ID_HANDSET_SPKR 0x02 +#define CAD_HW_DEVICE_ID_HEADSET_MIC 0x03 +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_MONO 0x04 +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_STEREO 0x05 +#define CAD_HW_DEVICE_ID_SPKR_PHONE_MIC 0x06 +#define CAD_HW_DEVICE_ID_SPKR_PHONE_MONO 0x07 +#define CAD_HW_DEVICE_ID_SPKR_PHONE_STEREO 0x08 +#define CAD_HW_DEVICE_ID_BT_SCO_MIC 0x09 +#define CAD_HW_DEVICE_ID_BT_SCO_SPKR 0x0A +#define CAD_HW_DEVICE_ID_BT_A2DP_SPKR 0x0B +#define CAD_HW_DEVICE_ID_TTY_HEADSET_MIC 0x0C +#define CAD_HW_DEVICE_ID_TTY_HEADSET_SPKR 0x0D + +#define CAD_HW_DEVICE_ID_DEFAULT_TX 0x0E +#define CAD_HW_DEVICE_ID_DEFAULT_RX 0x0F + +/* Logical Device to indicate A2DP routing */ +#define CAD_HW_DEVICE_ID_BT_A2DP_TX 0x10 +#define CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_MONO_RX 0x11 +#define CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_STEREO_RX 0x12 +#define CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_MONO_RX 0x13 +#define CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_STEREO_RX 0x14 + +#define CAD_HW_DEVICE_ID_VOICE 0x15 + +#define CAD_HW_DEVICE_ID_I2S_RX 0x20 +#define CAD_HW_DEVICE_ID_I2S_TX 0x21 + +/* AUXPGA */ +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_STEREO_LB 0x22 +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_MONO_LB 0x23 +#define CAD_HW_DEVICE_ID_SPEAKER_SPKR_STEREO_LB 0x24 +#define CAD_HW_DEVICE_ID_SPEAKER_SPKR_MONO_LB 0x25 + +#define CAD_HW_DEVICE_ID_NULL_RX 0x2A + +#define CAD_HW_DEVICE_ID_MAX_NUM 0x2F + +#define CAD_HW_DEVICE_ID_INVALID 0xFF + +#define CAD_RX_DEVICE 0x00 +#define CAD_TX_DEVICE 0x01 + +static struct q6_device_info q6_audio_devices[] = { + { + .id = ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR, + .cad_id = CAD_HW_DEVICE_ID_HANDSET_SPKR, + .path = ADIE_PATH_HANDSET_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HANDSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_MONO, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_SPKR_MONO, + .path = ADIE_PATH_HEADSET_MONO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_SPKR_STEREO, + .path = ADIE_PATH_HEADSET_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_MONO, + .path = ADIE_PATH_SPEAKER_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_STEREO, + .path = ADIE_PATH_SPEAKER_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_MONO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_MONO_RX, + .path = ADIE_PATH_SPKR_MONO_HDPH_MONO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_STEREO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_STEREO_RX, + .path = ADIE_PATH_SPKR_MONO_HDPH_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_MONO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_MONO_RX, + .path = ADIE_PATH_SPKR_STEREO_HDPH_MONO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_STEREO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_STEREO_RX, + .path = ADIE_PATH_SPKR_STEREO_HDPH_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_SPKR, + .cad_id = CAD_HW_DEVICE_ID_TTY_HEADSET_SPKR, + .path = ADIE_PATH_TTY_HEADSET_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_TTY, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HANDSET_MIC, + .cad_id = CAD_HW_DEVICE_ID_HANDSET_MIC, + .path = ADIE_PATH_HANDSET_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HANDSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HEADSET_MIC, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_MIC, + .path = ADIE_PATH_HEADSET_MONO_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MIC, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_MIC, + .path = ADIE_PATH_SPEAKER_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_MIC, + .cad_id = CAD_HW_DEVICE_ID_TTY_HEADSET_MIC, + .path = ADIE_PATH_TTY_HEADSET_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_BT_SCO_SPKR, + .cad_id = CAD_HW_DEVICE_ID_BT_SCO_SPKR, + .path = 0, /* XXX */ + .rate = 8000, + .dir = Q6_RX, + .codec = Q6_ECODEC_RX, + .hw = Q6_HW_BT_SCO, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_BT_A2DP_SPKR, + .cad_id = CAD_HW_DEVICE_ID_BT_A2DP_SPKR, + .path = 0, /* XXX */ + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ECODEC_RX, + .hw = Q6_HW_BT_A2DP, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_BT_SCO_MIC, + .cad_id = CAD_HW_DEVICE_ID_BT_SCO_MIC, + .path = 0, /* XXX */ + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ECODEC_TX, + .hw = Q6_HW_BT_SCO, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_I2S_SPKR, + .cad_id = CAD_HW_DEVICE_ID_I2S_RX, + .path = 0, /* XXX */ + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_SDAC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_I2S_MIC, + .cad_id = CAD_HW_DEVICE_ID_I2S_TX, + .path = 0, /* XXX */ + .rate = 16000, + .dir = Q6_TX, + .codec = Q6_SDAC_TX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = 0, + .cad_id = 0, + .path = 0, + .rate = 8000, + .dir = 0, + .codec = Q6_CODEC_NONE, + .hw = 0, + }, +}; + diff --git a/arch/arm/mach-msm/qdsp6/audiov2/qcelp_in.c b/arch/arm/mach-msm/qdsp6/audiov2/qcelp_in.c new file mode 100644 index 00000000000..a13084f6127 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/qcelp_in.c @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "dal_audio.h" +#include "dal_audio_format.h" +#include + + +struct qcelp { + struct mutex lock; + struct msm_audio_qcelp_enc_config cfg; + struct msm_audio_stream_config str_cfg; + struct audio_client *audio_client; +}; + + +static long q6_qcelp_in_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct qcelp *qcelp = file->private_data; + struct adsp_open_command rpc; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&qcelp->lock); + switch (cmd) { + case AUDIO_START: + if (qcelp->audio_client) { + rc = -EBUSY; + break; + } else { + qcelp->audio_client = q6audio_open(AUDIO_FLAG_READ, + qcelp->str_cfg.buffer_size); + + if (!qcelp->audio_client) { + kfree(qcelp); + rc = -ENOMEM; + break; + } + } + + tx_clk_freq = 8000; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format_block.standard.format = ADSP_AUDIO_FORMAT_V13K_FS; + rpc.format_block.standard.channels = 1; + rpc.format_block.standard.bits_per_sample = 16; + rpc.format_block.standard.sampling_rate = 8000; + rpc.format_block.standard.is_signed = 1; + rpc.format_block.standard.is_interleaved = 0; + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + rpc.buf_max_size = qcelp->str_cfg.buffer_size; + rpc.config.qcelp13k.min_rate = qcelp->cfg.min_bit_rate; + rpc.config.qcelp13k.max_rate = qcelp->cfg.max_bit_rate; + + q6audio_start(qcelp->audio_client, &rpc, sizeof(rpc)); + break; + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_VOLUME: + break; + case AUDIO_GET_STREAM_CONFIG: + if (copy_to_user((void *)arg, &qcelp->str_cfg, + sizeof(struct msm_audio_stream_config))) + rc = -EFAULT; + break; + case AUDIO_SET_STREAM_CONFIG: + if (copy_from_user(&qcelp->str_cfg, (void *)arg, + sizeof(struct msm_audio_stream_config))) { + rc = -EFAULT; + break; + } + + if (qcelp->str_cfg.buffer_size < 35) { + pr_err("[%s:%s] Buffer size too small\n", __MM_FILE__, + __func__); + rc = -EINVAL; + break; + } + + if (qcelp->str_cfg.buffer_count != 2) + pr_info("[%s:%s] Buffer count set to 2\n", __MM_FILE__, + __func__); + break; + case AUDIO_SET_QCELP_ENC_CONFIG: + if (copy_from_user(&qcelp->cfg, (void *) arg, + sizeof(struct msm_audio_qcelp_enc_config))) + rc = -EFAULT; + + if (qcelp->cfg.min_bit_rate > 4 || + qcelp->cfg.min_bit_rate < 1) { + + pr_err("[%s:%s] invalid min bitrate\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + if (qcelp->cfg.max_bit_rate > 4 || + qcelp->cfg.max_bit_rate < 1) { + + pr_err("[%s:%s] invalid max bitrate\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + + break; + case AUDIO_GET_QCELP_ENC_CONFIG: + if (copy_to_user((void *) arg, &qcelp->cfg, + sizeof(struct msm_audio_qcelp_enc_config))) + rc = -EFAULT; + break; + + default: + rc = -EINVAL; + } + + mutex_unlock(&qcelp->lock); + return rc; +} + +static int q6_qcelp_in_open(struct inode *inode, struct file *file) +{ + struct qcelp *qcelp; + qcelp = kmalloc(sizeof(struct qcelp), GFP_KERNEL); + if (qcelp == NULL) { + pr_err("[%s:%s] Could not allocate memory for qcelp driver\n", + __MM_FILE__, __func__); + return -ENOMEM; + } + + mutex_init(&qcelp->lock); + file->private_data = qcelp; + qcelp->audio_client = NULL; + qcelp->str_cfg.buffer_size = 35; + qcelp->str_cfg.buffer_count = 2; + qcelp->cfg.cdma_rate = CDMA_RATE_FULL; + qcelp->cfg.min_bit_rate = 1; + qcelp->cfg.max_bit_rate = 4; + return 0; +} + +static ssize_t q6_qcelp_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + struct qcelp *qcelp = file->private_data; + int xfer = 0; + int res; + + mutex_lock(&qcelp->lock); + ac = qcelp->audio_client; + if (!ac) { + res = -ENODEV; + goto fail; + } + while (count > xfer) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + xfer = ab->actual_size; + + if (copy_to_user(buf, ab->data, xfer)) { + res = -EFAULT; + goto fail; + } + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } + + res = buf - start; + +fail: + mutex_unlock(&qcelp->lock); + + return res; +} + +static int q6_qcelp_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct qcelp *qcelp = file->private_data; + + mutex_lock(&qcelp->lock); + if (qcelp->audio_client) + rc = q6audio_close(qcelp->audio_client); + mutex_unlock(&qcelp->lock); + kfree(qcelp); + return rc; +} + +static const struct file_operations q6_qcelp_in_fops = { + .owner = THIS_MODULE, + .open = q6_qcelp_in_open, + .read = q6_qcelp_in_read, + .release = q6_qcelp_in_release, + .unlocked_ioctl = q6_qcelp_in_ioctl, +}; + +struct miscdevice q6_qcelp_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_qcelp_in", + .fops = &q6_qcelp_in_fops, +}; + +static int __init q6_qcelp_in_init(void) +{ + return misc_register(&q6_qcelp_in_misc); +} + +device_initcall(q6_qcelp_in_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/routing.c b/arch/arm/mach-msm/qdsp6/audiov2/routing.c new file mode 100644 index 00000000000..1a2476b2ffc --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/routing.c @@ -0,0 +1,73 @@ +/* arch/arm/mach-msm/qdsp6/audiov2/routing.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +static int q6_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t q6_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + char cmd[32]; + + if (count >= sizeof(cmd)) + return -EINVAL; + if (copy_from_user(cmd, buf, count)) + return -EFAULT; + cmd[count] = 0; + + if ((count > 1) && (cmd[count-1] == '\n')) + cmd[count-1] = 0; + + q6audio_set_route(cmd); + + return count; +} + +static int q6_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations q6_fops = { + .owner = THIS_MODULE, + .open = q6_open, + .write = q6_write, + .release = q6_release, +}; + +static struct miscdevice q6_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_audio_route", + .fops = &q6_fops, +}; + + +static int __init q6_init(void) +{ + return misc_register(&q6_misc); +} + +device_initcall(q6_init); diff --git a/arch/arm/mach-msm/qdsp6/audiov2/voice.c b/arch/arm/mach-msm/qdsp6/audiov2/voice.c new file mode 100644 index 00000000000..906c5341a9c --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/audiov2/voice.c @@ -0,0 +1,188 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../dal.h" +#include "dal_voice.h" +#include + +struct voice_struct { + struct dal_client *cvd; + struct apr_command_pkt apr_pkt; + struct completion compl; +}; + +static struct voice_struct voice; + +static int cvd_send_response(void) +{ + struct apr_command_pkt *pkt; + uint16_t src_addr; + uint16_t src_token; + uint16_t dst_token; + uint16_t dst_addr; + + pkt = &voice.apr_pkt; + src_addr = pkt->dst_addr; + dst_addr = pkt->src_addr; + src_token = pkt->dst_token; + dst_token = pkt->src_token; + + pkt->header &= ~APR_PKTV1_TYPE_MASK; + pkt->header |= APR_SET_FIELD(APR_PKTV1_TYPE, APR_PKTV1_TYPE_EVENT_V); + pkt->src_addr = src_addr; + pkt->dst_addr = dst_addr; + pkt->src_token = src_token; + pkt->dst_token = dst_token; + pkt->opcode = APR_IBASIC_RSP_RESULT; + + dal_call(voice.cvd, VOICE_OP_CONTROL, 5, pkt, + sizeof(struct apr_command_pkt), + pkt, sizeof(u32)); + return 0; +} + +static int cvd_process_voice_setup(void) +{ + q6voice_setup(); + cvd_send_response(); + return 0; +} + +static int cvd_process_voice_teardown(void) +{ + q6voice_teardown(); + cvd_send_response(); + return 0; +} + +static int cvd_process_set_network(void) +{ + cvd_send_response(); + return 0; +} + +static int voice_thread(void *data) +{ + while (!kthread_should_stop()) { + wait_for_completion(&voice.compl); + init_completion(&voice.compl); + + switch (voice.apr_pkt.opcode) { + + case APR_OP_CMD_CREATE: + cvd_send_response(); + break; + case VOICE_OP_CMD_BRINGUP: + cvd_process_voice_setup(); + break; + case APR_OP_CMD_DESTROY: + cvd_send_response(); + break; + case VOICE_OP_CMD_TEARDOWN: + cvd_process_voice_teardown(); + break; + case VOICE_OP_CMD_SET_NETWORK: + cvd_process_set_network(); + break; + default: + pr_err("[%s:%s] Undefined event\n", __MM_FILE__, + __func__); + + } + } + return 0; +} + +static void remote_cb_function(void *data, int len, void *cookie) +{ + struct apr_command_pkt *apr = data + 2*sizeof(uint32_t); + + memcpy(&voice.apr_pkt, apr, sizeof(struct apr_command_pkt)); + + if (len <= 0) { + pr_err("[%s:%s] unexpected event with length %d\n", + __MM_FILE__, __func__, len); + return; + } + + pr_debug("[%s:%s] APR = %x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n", __MM_FILE__, + __func__, + apr->header, + apr->reserved1, + apr->src_addr, + apr->dst_addr, + apr->ret_addr, + apr->src_token, + apr->dst_token, + apr->ret_token, + apr->context, + apr->opcode); + + complete(&voice.compl); +} + +static int __init voice_init(void) +{ + int res = 0; + struct task_struct *task; + u32 tmp[2]; + + tmp[0] = sizeof(u32); + tmp[1] = 0; + + voice.cvd = dal_attach(VOICE_DAL_DEVICE, VOICE_DAL_PORT, 0, + remote_cb_function, 0); + + if (!voice.cvd) { + pr_err("[%s:%s] audio_init: cannot attach to cvd\n", + __MM_FILE__, __func__); + res = -ENODEV; + goto done; + } + + if (check_version(voice.cvd, VOICE_DAL_VERSION) != 0) { + pr_err("[%s:%s] Incompatible cvd version\n", + __MM_FILE__, __func__); + res = -ENODEV; + goto done; + } + dal_call(voice.cvd, VOICE_OP_INIT, 5, tmp, sizeof(tmp), + tmp, sizeof(u32)); + + init_completion(&voice.compl); + task = kthread_run(voice_thread, &voice, "voice_thread"); + + if (IS_ERR(task)) { + pr_err("[%s:%s] Cannot start the voice thread\n", __MM_FILE__, + __func__); + res = PTR_ERR(task); + task = NULL; + } else + goto done; + +done: + return res; +} + +late_initcall(voice_init); diff --git a/arch/arm/mach-msm/qdsp6/auxpcm_lb_in.c b/arch/arm/mach-msm/qdsp6/auxpcm_lb_in.c new file mode 100644 index 00000000000..4195454776c --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/auxpcm_lb_in.c @@ -0,0 +1,190 @@ +/* arch/arm/mach-msm/qdsp6/auxpcm_lb_in.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct auxpcm { + struct mutex lock; + struct audio_client *ac; + uint32_t sample_rate; + uint32_t channel_count; + int opened;; +}; + +static long auxpcmin_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct auxpcm *auxpcmin = file->private_data; + int rc = 0; + + mutex_lock(&auxpcmin->lock); + switch (cmd) { + case AUDIO_START: { + uint32_t acdb_id; + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, (void *) arg, + sizeof(acdb_id))) { + pr_info("[%s:%s] copy acdb_id from user failed\n", + __MM_FILE__, __func__); + rc = -EFAULT; + break; + } + if (auxpcmin->ac) { + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + rc = -EBUSY; + } else { + auxpcmin->ac = + q6audio_open_auxpcm(auxpcmin->sample_rate, + auxpcmin->channel_count, + AUDIO_FLAG_READ, acdb_id); + if (!auxpcmin->ac) { + pr_err("[%s:%s] auxpcm open session failed\n", + __MM_FILE__, __func__); + rc = -ENOMEM; + } + } + break; + } + case AUDIO_STOP: + pr_debug("[%s:%s] AUDIO_STOP\n", __MM_FILE__, __func__); + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (auxpcmin->ac) { + rc = -EBUSY; + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + break; + } + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + pr_debug("[%s:%s] SET_CONFIG: samplerate = %d, channels = %d\n", + __MM_FILE__, __func__, config.sample_rate, + config.channel_count); + if (config.channel_count != 1) { + rc = -EINVAL; + pr_err("[%s:%s] invalid channelcount %d\n", + __MM_FILE__, __func__, config.channel_count); + break; + } + if (config.sample_rate != 8000) { + rc = -EINVAL; + pr_err("[%s:%s] invalid samplerate %d\n", __MM_FILE__, + __func__, config.sample_rate); + break; + } + auxpcmin->sample_rate = config.sample_rate; + auxpcmin->channel_count = config.channel_count; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = 0; + config.buffer_count = 0; + config.sample_rate = auxpcmin->sample_rate; + config.channel_count = auxpcmin->channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + pr_debug("[%s:%s] GET_CONFIG: samplerate = %d, channels = %d\n", + __MM_FILE__, __func__, config.sample_rate, + config.channel_count); + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&auxpcmin->lock); + pr_debug("[%s:%s] rc = %d\n", __MM_FILE__, __func__, rc); + return rc; +} + +static struct auxpcm the_auxpcmin; + +static int auxpcmin_open(struct inode *inode, struct file *file) +{ + struct auxpcm *auxpcmin = &the_auxpcmin; + + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + mutex_lock(&auxpcmin->lock); + if (auxpcmin->opened) { + pr_err("aux pcm loopback tx already open!\n"); + mutex_unlock(&auxpcmin->lock); + return -EBUSY; + } + auxpcmin->channel_count = 1; + auxpcmin->sample_rate = 8000; + auxpcmin->opened = 1; + file->private_data = auxpcmin; + mutex_unlock(&auxpcmin->lock); + return 0; +} + +static int auxpcmin_release(struct inode *inode, struct file *file) +{ + struct auxpcm *auxpcmin = file->private_data; + mutex_lock(&auxpcmin->lock); + if (auxpcmin->ac) + q6audio_auxpcm_close(auxpcmin->ac); + auxpcmin->ac = NULL; + auxpcmin->opened = 0; + mutex_unlock(&auxpcmin->lock); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return 0; +} + +static const struct file_operations auxpcmin_fops = { + .owner = THIS_MODULE, + .open = auxpcmin_open, + .release = auxpcmin_release, + .unlocked_ioctl = auxpcmin_ioctl, +}; + +struct miscdevice auxpcmin_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aux_pcm_lb_in", + .fops = &auxpcmin_fops, +}; + +static int __init auxpcmin_init(void) +{ + mutex_init(&the_auxpcmin.lock); + return misc_register(&auxpcmin_misc); +} + +device_initcall(auxpcmin_init); diff --git a/arch/arm/mach-msm/qdsp6/auxpcm_lb_out.c b/arch/arm/mach-msm/qdsp6/auxpcm_lb_out.c new file mode 100644 index 00000000000..b6805973f36 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/auxpcm_lb_out.c @@ -0,0 +1,191 @@ +/* arch/arm/mach-msm/qdsp6/auxpcm_lb_out.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * Copyright (c) 2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct auxpcm { + struct mutex lock; + struct audio_client *ac; + uint32_t sample_rate; + uint32_t channel_count; + int opened;; +}; + +static long auxpcmout_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct auxpcm *auxpcmout = file->private_data; + int rc = 0; + + mutex_lock(&auxpcmout->lock); + switch (cmd) { + case AUDIO_START: { + uint32_t acdb_id; + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, (void *) arg, + sizeof(acdb_id))) { + pr_info("[%s:%s] copy acdb_id from user failed\n", + __MM_FILE__, __func__); + rc = -EFAULT; + break; + } + if (auxpcmout->ac) { + rc = -EBUSY; + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + } else { + auxpcmout->ac = + q6audio_open_auxpcm(auxpcmout->sample_rate, + auxpcmout->channel_count, + AUDIO_FLAG_WRITE, acdb_id); + if (!auxpcmout->ac) { + pr_err("[%s:%s] auxpcm open session failed\n", + __MM_FILE__, __func__); + rc = -ENOMEM; + } + } + break; + } + case AUDIO_STOP: + pr_debug("[%s:%s] AUDIO_STOP\n", __MM_FILE__, __func__); + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (auxpcmout->ac) { + rc = -EBUSY; + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + break; + } + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + pr_debug("[%s:%s] SET_CONFIG: samplerate = %d, channels = %d\n", + __MM_FILE__, __func__, config.sample_rate, + config.channel_count); + if (config.channel_count != 1) { + rc = -EINVAL; + pr_err("[%s:%s] invalid channelcount %d\n", + __MM_FILE__, __func__, config.channel_count); + break; + } + if (config.sample_rate != 8000) { + rc = -EINVAL; + pr_err("[%s:%s] invalid samplerate %d\n", __MM_FILE__, + __func__, config.sample_rate); + break; + } + auxpcmout->sample_rate = config.sample_rate; + auxpcmout->channel_count = config.channel_count; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = 0; + config.buffer_count = 0; + config.sample_rate = auxpcmout->sample_rate; + config.channel_count = auxpcmout->channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + pr_debug("[%s:%s] GET_CONFIG: samplerate = %d, channels= %d\n", + __MM_FILE__, __func__, config.sample_rate, + config.channel_count); + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&auxpcmout->lock); + pr_debug("[%s:%s] rc = %d\n", __MM_FILE__, __func__, rc); + return rc; +} + +static struct auxpcm the_auxpcmout; + +static int auxpcmout_open(struct inode *inode, struct file *file) +{ + struct auxpcm *auxpcmout = &the_auxpcmout; + + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + + mutex_lock(&auxpcmout->lock); + + if (auxpcmout->opened) { + pr_err("aux pcm loopback rx already open!\n"); + mutex_unlock(&auxpcmout->lock); + return -EBUSY; + } + auxpcmout->channel_count = 1; + auxpcmout->sample_rate = 8000; + auxpcmout->opened = 1; + file->private_data = auxpcmout; + mutex_unlock(&auxpcmout->lock); + return 0; +} + +static int auxpcmout_release(struct inode *inode, struct file *file) +{ + struct auxpcm *auxpcmout = file->private_data; + mutex_lock(&auxpcmout->lock); + if (auxpcmout->ac) + q6audio_auxpcm_close(auxpcmout->ac); + auxpcmout->ac = NULL; + auxpcmout->opened = 0; + mutex_unlock(&auxpcmout->lock); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return 0; +} + +static const struct file_operations auxpcmout_fops = { + .owner = THIS_MODULE, + .open = auxpcmout_open, + .release = auxpcmout_release, + .unlocked_ioctl = auxpcmout_ioctl, +}; + +struct miscdevice auxpcmout_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aux_pcm_lb_out", + .fops = &auxpcmout_fops, +}; + +static int __init auxpcmout_init(void) +{ + mutex_init(&the_auxpcmout.lock); + return misc_register(&auxpcmout_misc); +} + +device_initcall(auxpcmout_init); diff --git a/arch/arm/mach-msm/qdsp6/dal.c b/arch/arm/mach-msm/qdsp6/dal.c new file mode 100644 index 00000000000..378432b66c0 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/dal.c @@ -0,0 +1,727 @@ +/* arch/arm/mach-msm/qdsp6/dal.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "dal.h" + +#define DAL_TRACE 0 + +struct dal_hdr { + uint32_t length:16; /* message length (header inclusive) */ + uint32_t version:8; /* DAL protocol version */ + uint32_t priority:7; + uint32_t async:1; + uint32_t ddi:16; /* DDI method number */ + uint32_t prototype:8; /* DDI serialization format */ + uint32_t msgid:8; /* message id (DDI, ATTACH, DETACH, ...) */ + void *from; + void *to; +} __attribute__((packed)); + +#define TRACE_DATA_MAX 128 +#define TRACE_LOG_MAX 32 +#define TRACE_LOG_MASK (TRACE_LOG_MAX - 1) + +struct dal_trace { + unsigned timestamp; + struct dal_hdr hdr; + uint32_t data[TRACE_DATA_MAX]; +}; + +#define DAL_HDR_SIZE (sizeof(struct dal_hdr)) +#define DAL_DATA_MAX 512 +#define DAL_MSG_MAX (DAL_HDR_SIZE + DAL_DATA_MAX) + +#define DAL_VERSION 0x11 + +#define DAL_MSGID_DDI 0x00 +#define DAL_MSGID_ATTACH 0x01 +#define DAL_MSGID_DETACH 0x02 +#define DAL_MSGID_ASYNCH 0xC0 +#define DAL_MSGID_REPLY 0x80 + +struct dal_channel { + struct list_head list; + struct list_head clients; + + /* synchronization for changing channel state, + * adding/removing clients, smd callbacks, etc + */ + spinlock_t lock; + + struct smd_channel *sch; + char *name; + + /* events are delivered at IRQ context immediately, so + * we only need one assembly buffer for the entire channel + */ + struct dal_hdr hdr; + unsigned char data[DAL_DATA_MAX]; + + unsigned count; + void *ptr; + + /* client which the current inbound message is for */ + struct dal_client *active; +}; + +struct dal_client { + struct list_head list; + struct dal_channel *dch; + void *cookie; + dal_event_func_t event; + + /* opaque handle for the far side */ + void *remote; + + /* dal rpc calls are fully synchronous -- only one call may be + * active per client at a time + */ + struct mutex write_lock; + wait_queue_head_t wait; + + unsigned char data[DAL_DATA_MAX]; + + void *reply; + int reply_max; + int status; + unsigned msgid; /* msgid of expected reply */ + + spinlock_t tr_lock; + unsigned tr_head; + unsigned tr_tail; + struct dal_trace *tr_log; +}; + +static unsigned now(void) +{ + struct timespec ts; + ktime_get_ts(&ts); + return (ts.tv_nsec / 1000000) + (ts.tv_sec * 1000); +} + +void dal_trace(struct dal_client *c) +{ + if (c->tr_log) + return; + c->tr_log = kzalloc(sizeof(struct dal_trace) * TRACE_LOG_MAX, + GFP_KERNEL); +} + +void dal_trace_print(struct dal_hdr *hdr, unsigned *data, int len, unsigned when) +{ + int i; + printk("DAL %08x -> %08x L=%03x A=%d D=%04x P=%02x M=%02x T=%d", + (unsigned) hdr->from, (unsigned) hdr->to, + hdr->length, hdr->async, + hdr->ddi, hdr->prototype, hdr->msgid, + when); + len /= 4; + for (i = 0; i < len; i++) { + if (!(i & 7)) + printk("\n%03x", i * 4); + printk(" %08x", data[i]); + } + printk("\n"); +} + +void dal_trace_dump(struct dal_client *c) +{ + struct dal_trace *dt; + unsigned n, len; + + if (!c->tr_log) + return; + + for (n = c->tr_tail; n != c->tr_head; n = (n + 1) & TRACE_LOG_MASK) { + dt = c->tr_log + n; + len = dt->hdr.length - sizeof(dt->hdr); + if (len > TRACE_DATA_MAX) + len = TRACE_DATA_MAX; + dal_trace_print(&dt->hdr, dt->data, len, dt->timestamp); + } +} + +static void dal_trace_log(struct dal_client *c, + struct dal_hdr *hdr, void *data, unsigned len) +{ + unsigned long flags; + unsigned t, n; + struct dal_trace *dt; + + t = now(); + if (len > TRACE_DATA_MAX) + len = TRACE_DATA_MAX; + + spin_lock_irqsave(&c->tr_lock, flags); + n = (c->tr_head + 1) & TRACE_LOG_MASK; + if (c->tr_tail == n) + c->tr_tail = (c->tr_tail + 1) & TRACE_LOG_MASK; + dt = c->tr_log + n; + dt->timestamp = t; + memcpy(&dt->hdr, hdr, sizeof(struct dal_hdr)); + memcpy(dt->data, data, len); + c->tr_head = n; + + spin_unlock_irqrestore(&c->tr_lock, flags); +} + + +static void dal_channel_notify(void *priv, unsigned event) +{ + struct dal_channel *dch = priv; + struct dal_hdr *hdr = &dch->hdr; + struct dal_client *client; + unsigned long flags; + int len; + int r; + + spin_lock_irqsave(&dch->lock, flags); + +again: + if (dch->count == 0) { + if (smd_read_avail(dch->sch) < DAL_HDR_SIZE) + goto done; + + smd_read(dch->sch, hdr, DAL_HDR_SIZE); + + if (hdr->length < DAL_HDR_SIZE) + goto done; + + if (hdr->length > DAL_MSG_MAX) + panic("oversize message"); + + dch->count = hdr->length - DAL_HDR_SIZE; + + /* locate the client this message is targeted to */ + list_for_each_entry(client, &dch->clients, list) { + if (dch->hdr.to == client) { + dch->active = client; + dch->ptr = client->data; + goto check_data; + } + } + pr_err("[%s:%s] $$$ receiving unknown message len = %d $$$\n", + __MM_FILE__, __func__, dch->count); + dch->active = 0; + dch->ptr = dch->data; + } + +check_data: + len = dch->count; + if (len > 0) { + if (smd_read_avail(dch->sch) < len) + goto done; + + r = smd_read(dch->sch, dch->ptr, len); + if (r != len) + panic("invalid read"); + +#if DAL_TRACE + pr_info("[%s:%s] dal recv %p <- %p %02x:%04x:%02x %d\n", + __MM_FILE__, __func__, hdr->to, hdr->from, hdr->msgid, + hdr->ddi, hdr->prototype, hdr->length - sizeof(*hdr)); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, dch->ptr, len); +#endif + dch->count = 0; + + client = dch->active; + if (!client) { + pr_err("[%s:%s] message to %p discarded\n", + __MM_FILE__, __func__, dch->hdr.to); + goto again; + } + + if (client->tr_log) + dal_trace_log(client, hdr, dch->ptr, len); + + if (hdr->msgid == DAL_MSGID_ASYNCH) { + if (client->event) + client->event(dch->ptr, len, client->cookie); + else + pr_err("[%s:%s] client %p has no event \ + handler\n", __MM_FILE__, __func__, + client); + goto again; + } + + if (hdr->msgid == client->msgid) { + if (!client->remote) + client->remote = hdr->from; + if (len > client->reply_max) + len = client->reply_max; + memcpy(client->reply, client->data, len); + client->status = len; + wake_up(&client->wait); + goto again; + } + + pr_err("[%s:%s] cannot find client %p\n", __MM_FILE__, + __func__, dch->hdr.to); + goto again; + } + +done: + spin_unlock_irqrestore(&dch->lock, flags); +} + +static LIST_HEAD(dal_channel_list); +static DEFINE_MUTEX(dal_channel_list_lock); + +static struct dal_channel *dal_open_channel(const char *name, uint32_t cpu) +{ + struct dal_channel *dch; + + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + mutex_lock(&dal_channel_list_lock); + + list_for_each_entry(dch, &dal_channel_list, list) { + if (!strcmp(dch->name, name)) + goto found_it; + } + + dch = kzalloc(sizeof(*dch) + strlen(name) + 1, GFP_KERNEL); + if (!dch) + goto fail; + + dch->name = (char *) (dch + 1); + strcpy(dch->name, name); + spin_lock_init(&dch->lock); + INIT_LIST_HEAD(&dch->clients); + + list_add(&dch->list, &dal_channel_list); + +found_it: + if (!dch->sch) { + if (smd_named_open_on_edge(name, cpu, &dch->sch, + dch, dal_channel_notify)) { + pr_err("[%s:%s] smd open failed\n", __MM_FILE__, + __func__); + dch = NULL; + } + /* FIXME: wait for channel to open before returning */ + msleep(100); + } + +fail: + mutex_unlock(&dal_channel_list_lock); + + return dch; +} + +int dal_call_raw(struct dal_client *client, + struct dal_hdr *hdr, + void *data, int data_len, + void *reply, int reply_max) +{ + struct dal_channel *dch = client->dch; + unsigned long flags; + + client->reply = reply; + client->reply_max = reply_max; + client->msgid = hdr->msgid | DAL_MSGID_REPLY; + client->status = -EBUSY; + +#if DAL_TRACE + pr_info("[%s:%s:%x] dal send %p -> %p %02x:%04x:%02x %d\n", + __MM_FILE__, __func__, (unsigned int)client, hdr->from, hdr->to, + hdr->msgid, hdr->ddi, hdr->prototype, + hdr->length - sizeof(*hdr)); + print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, data, data_len); +#endif + + if (client->tr_log) + dal_trace_log(client, hdr, data, data_len); + + spin_lock_irqsave(&dch->lock, flags); + /* FIXME: ensure entire message is written or none. */ + smd_write(dch->sch, hdr, sizeof(*hdr)); + smd_write(dch->sch, data, data_len); + spin_unlock_irqrestore(&dch->lock, flags); + + if (!wait_event_timeout(client->wait, (client->status != -EBUSY), 5*HZ)) { + dal_trace_dump(client); + pr_err("[%s:%s] call timed out. dsp is probably dead.\n", + __MM_FILE__, __func__); + dal_trace_print(hdr, data, data_len, 0); + q6audio_dsp_not_responding(); + } + + return client->status; +} + +int dal_call(struct dal_client *client, + unsigned ddi, unsigned prototype, + void *data, int data_len, + void *reply, int reply_max) +{ + struct dal_hdr hdr; + int r; + + memset(&hdr, 0, sizeof(hdr)); + + hdr.length = data_len + sizeof(hdr); + hdr.version = DAL_VERSION; + hdr.msgid = DAL_MSGID_DDI; + hdr.ddi = ddi; + hdr.prototype = prototype; + hdr.from = client; + hdr.to = client->remote; + + if (hdr.length > DAL_MSG_MAX) + return -EINVAL; + + mutex_lock(&client->write_lock); + r = dal_call_raw(client, &hdr, data, data_len, reply, reply_max); + mutex_unlock(&client->write_lock); + + return r; +} + +struct dal_msg_attach { + uint32_t device_id; + char attach[64]; + char service_name[32]; +} __attribute__((packed)); + +struct dal_reply_attach { + uint32_t status; + char name[64]; +}; + +struct dal_client *dal_attach(uint32_t device_id, const char *name, + uint32_t cpu, dal_event_func_t func, void *cookie) +{ + struct dal_hdr hdr; + struct dal_msg_attach msg; + struct dal_reply_attach reply; + struct dal_channel *dch; + struct dal_client *client; + unsigned long flags; + int r; + + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + dch = dal_open_channel(name, cpu); + if (!dch) + return 0; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return 0; + + client->dch = dch; + client->event = func; + client->cookie = cookie; + mutex_init(&client->write_lock); + spin_lock_init(&client->tr_lock); + init_waitqueue_head(&client->wait); + + spin_lock_irqsave(&dch->lock, flags); + list_add(&client->list, &dch->clients); + spin_unlock_irqrestore(&dch->lock, flags); + + memset(&hdr, 0, sizeof(hdr)); + memset(&msg, 0, sizeof(msg)); + + hdr.length = sizeof(hdr) + sizeof(msg); + hdr.version = DAL_VERSION; + hdr.msgid = DAL_MSGID_ATTACH; + hdr.from = client; + msg.device_id = device_id; + + r = dal_call_raw(client, &hdr, &msg, sizeof(msg), + &reply, sizeof(reply)); + + if ((r == sizeof(reply)) && (reply.status == 0)) { + reply.name[63] = 0; + pr_info("[%s:%s] status = %d, name = '%s' dal_client %x\n", + __MM_FILE__, __func__, reply.status, + reply.name, (unsigned int)client); + return client; + } + + pr_err("[%s:%s] failure\n", __MM_FILE__, __func__); + + dal_detach(client); + return 0; +} + +int dal_detach(struct dal_client *client) +{ + struct dal_channel *dch; + unsigned long flags; + + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + mutex_lock(&client->write_lock); + if (client->remote) { + struct dal_hdr hdr; + uint32_t data; + + memset(&hdr, 0, sizeof(hdr)); + hdr.length = sizeof(hdr) + sizeof(data); + hdr.version = DAL_VERSION; + hdr.msgid = DAL_MSGID_DETACH; + hdr.from = client; + hdr.to = client->remote; + data = (uint32_t) client; + + dal_call_raw(client, &hdr, &data, sizeof(data), + &data, sizeof(data)); + } + + dch = client->dch; + spin_lock_irqsave(&dch->lock, flags); + if (dch->active == client) { + /* We have received a message header for this client + * but not the body of the message. Ensure that when + * the body arrives we don't write it into the now-closed + * client. In *theory* this should never happen. + */ + dch->active = 0; + dch->ptr = dch->data; + } + list_del(&client->list); + spin_unlock_irqrestore(&dch->lock, flags); + + mutex_unlock(&client->write_lock); + + kfree(client); + return 0; +} + +void *dal_get_remote_handle(struct dal_client *client) +{ + return client->remote; +} + +/* convenience wrappers */ + +int dal_call_f0(struct dal_client *client, uint32_t ddi, uint32_t arg1) +{ + uint32_t tmp = arg1; + int res; + res = dal_call(client, ddi, 0, &tmp, sizeof(tmp), &tmp, sizeof(tmp)); + if (res >= 4) + return (int) tmp; + return res; +} + +int dal_call_f1(struct dal_client *client, uint32_t ddi, uint32_t arg1, + uint32_t arg2) +{ + uint32_t tmp[2]; + int res; + tmp[0] = arg1; + tmp[1] = arg2; + res = dal_call(client, ddi, 1, tmp, sizeof(tmp), tmp, sizeof(uint32_t)); + if (res >= 4) + return (int) tmp[0]; + return res; +} + +int dal_call_f5(struct dal_client *client, uint32_t ddi, void *ibuf, uint32_t ilen) +{ + uint32_t tmp[128]; + int res; + int param_idx = 0; + + if (ilen + 4 > DAL_DATA_MAX) + return -EINVAL; + + tmp[param_idx] = ilen; + param_idx++; + + memcpy(&tmp[param_idx], ibuf, ilen); + param_idx += DIV_ROUND_UP(ilen, 4); + + res = dal_call(client, ddi, 5, tmp, param_idx * 4, tmp, sizeof(tmp)); + + if (res >= 4) + return (int) tmp[0]; + return res; +} + +int dal_call_f6(struct dal_client *client, uint32_t ddi, uint32_t s1, + void *ibuf, uint32_t ilen) +{ + uint32_t tmp[128]; + int res; + int param_idx = 0; + + if (ilen + 8 > DAL_DATA_MAX) + return -EINVAL; + + tmp[param_idx] = s1; + param_idx++; + tmp[param_idx] = ilen; + param_idx++; + memcpy(&tmp[param_idx], ibuf, ilen); + param_idx += DIV_ROUND_UP(ilen, 4); + + res = dal_call(client, ddi, 6, tmp, param_idx * 4, tmp, sizeof(tmp)); + + if (res >= 4) + return (int) tmp[0]; + + return res; +} + +int dal_call_f9(struct dal_client *client, uint32_t ddi, void *obuf, + uint32_t olen) +{ + uint32_t tmp[128]; + int res; + + if (olen > sizeof(tmp) - 8) + return -EINVAL; + tmp[0] = olen; + + res = dal_call(client, ddi, 9, tmp, sizeof(uint32_t), tmp, + sizeof(tmp)); + + if (res >= 4) + res = (int)tmp[0]; + + if (!res) { + if (tmp[1] > olen) + return -EIO; + memcpy(obuf, &tmp[2], tmp[1]); + } + return res; +} + +int dal_call_f11(struct dal_client *client, uint32_t ddi, uint32_t s1, + void *obuf, uint32_t olen) +{ + uint32_t tmp[DAL_DATA_MAX/4] = {0}; + int res; + int param_idx = 0; + int num_bytes = 4; + + num_bytes += (DIV_ROUND_UP(olen, 4)) * 4; + + if ((num_bytes > DAL_DATA_MAX - 12) || (olen > DAL_DATA_MAX - 8)) + return -EINVAL; + + tmp[param_idx] = s1; + param_idx++; + tmp[param_idx] = olen; + param_idx += DIV_ROUND_UP(olen, 4); + + res = dal_call(client, ddi, 11, tmp, param_idx * 4, tmp, sizeof(tmp)); + + if (res >= 4) + res = (int) tmp[0]; + if (!res) { + if (tmp[1] > olen) + return -EIO; + memcpy(obuf, &tmp[2], tmp[1]); + } + return res; +} + +int dal_call_f13(struct dal_client *client, uint32_t ddi, void *ibuf1, + uint32_t ilen1, void *ibuf2, uint32_t ilen2, void *obuf, + uint32_t olen) +{ + uint32_t tmp[DAL_DATA_MAX/4]; + int res; + int param_idx = 0; + int num_bytes = 0; + + num_bytes = (DIV_ROUND_UP(ilen1, 4)) * 4; + num_bytes += (DIV_ROUND_UP(ilen2, 4)) * 4; + + if ((num_bytes > DAL_DATA_MAX - 12) || (olen > DAL_DATA_MAX - 8) || + (ilen1 > DAL_DATA_MAX) || (ilen2 > DAL_DATA_MAX)) + return -EINVAL; + + tmp[param_idx] = ilen1; + param_idx++; + + memcpy(&tmp[param_idx], ibuf1, ilen1); + param_idx += DIV_ROUND_UP(ilen1, 4); + + tmp[param_idx++] = ilen2; + memcpy(&tmp[param_idx], ibuf2, ilen2); + param_idx += DIV_ROUND_UP(ilen2, 4); + + tmp[param_idx++] = olen; + res = dal_call(client, ddi, 13, tmp, param_idx * 4, tmp, + sizeof(tmp)); + + if (res >= 4) + res = (int)tmp[0]; + + if (!res) { + if (tmp[1] > olen) + return -EIO; + memcpy(obuf, &tmp[2], tmp[1]); + } + return res; +} +int dal_call_f14(struct dal_client *client, uint32_t ddi, void *ibuf, + uint32_t ilen, void *obuf1, uint32_t olen1, void *obuf2, + uint32_t olen2, uint32_t *oalen2) +{ + uint32_t tmp[128]; + int res; + int param_idx = 0; + + if (olen1 + olen2 + 8 > DAL_DATA_MAX || + ilen + 12 > DAL_DATA_MAX) + return -EINVAL; + + tmp[param_idx] = ilen; + param_idx++; + + memcpy(&tmp[param_idx], ibuf, ilen); + param_idx += DIV_ROUND_UP(ilen, 4); + + tmp[param_idx++] = olen1; + tmp[param_idx++] = olen2; + res = dal_call(client, ddi, 14, tmp, param_idx * 4, tmp, sizeof(tmp)); + + if (res >= 4) + res = (int)tmp[0]; + + if (!res) { + if (tmp[1] > olen1) + return -EIO; + param_idx = DIV_ROUND_UP(tmp[1], 4) + 2; + if (tmp[param_idx] > olen2) + return -EIO; + + memcpy(obuf1, &tmp[2], tmp[1]); + memcpy(obuf2, &tmp[param_idx+1], tmp[param_idx]); + *oalen2 = tmp[param_idx]; + } + return res; +} diff --git a/arch/arm/mach-msm/qdsp6/dal.h b/arch/arm/mach-msm/qdsp6/dal.h new file mode 100644 index 00000000000..1176eb9c102 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/dal.h @@ -0,0 +1,96 @@ +/* arch/arm/mach-msm/qdsp6/dal.h + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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_MSM_DAL_ +#define _MACH_MSM_DAL_ + +struct dal_client; + +struct dal_info { + uint32_t size; + uint32_t version; + char name[32]; +}; + +typedef void (*dal_event_func_t)(void *data, int len, void *cookie); + +struct dal_client *dal_attach(uint32_t device_id, const char *name, + uint32_t cpu, dal_event_func_t func, void *cookie); + +int dal_detach(struct dal_client *client); + +int dal_call(struct dal_client *client, + unsigned ddi, unsigned prototype, + void *data, int data_len, + void *reply, int reply_max); + +void dal_trace(struct dal_client *client); +void dal_trace_dump(struct dal_client *client); + +/* function to call before panic on stalled dal calls */ +void dal_set_oops(struct dal_client *client, void (*oops)(void)); + +/* convenience wrappers */ +int dal_call_f0(struct dal_client *client, uint32_t ddi, + uint32_t arg1); +int dal_call_f1(struct dal_client *client, uint32_t ddi, + uint32_t arg1, uint32_t arg2); +int dal_call_f5(struct dal_client *client, uint32_t ddi, + void *ibuf, uint32_t ilen); +int dal_call_f6(struct dal_client *client, uint32_t ddi, + uint32_t s1, void *ibuf, uint32_t ilen); +int dal_call_f9(struct dal_client *client, uint32_t ddi, + void *obuf, uint32_t olen); +int dal_call_f11(struct dal_client *client, uint32_t ddi, + uint32_t s1, void *obuf, uint32_t olen); +int dal_call_f13(struct dal_client *client, uint32_t ddi, void *ibuf1, + uint32_t ilen1, void *ibuf2, uint32_t ilen2, void *obuf, + uint32_t olen); +int dal_call_f14(struct dal_client *client, uint32_t ddi, void *ibuf, + uint32_t ilen, void *obuf1, uint32_t olen1, void *obuf2, + uint32_t olen2, uint32_t *oalen2); + +/* common DAL operations */ +enum { + DAL_OP_ATTACH = 0, + DAL_OP_DETACH, + DAL_OP_INIT, + DAL_OP_DEINIT, + DAL_OP_OPEN, + DAL_OP_CLOSE, + DAL_OP_INFO, + DAL_OP_POWEREVENT, + DAL_OP_SYSREQUEST, + DAL_OP_FIRST_DEVICE_API, +}; + +static inline int check_version(struct dal_client *client, uint32_t version) +{ + struct dal_info info; + int res; + + res = dal_call_f9(client, DAL_OP_INFO, &info, sizeof(struct dal_info)); + if (!res) { + if (((info.version & 0xFFFF0000) != (version & 0xFFFF0000)) || + ((info.version & 0x0000FFFF) < + (version & 0x0000FFFF))) { + res = -EINVAL; + } + } + return res; +} + +#endif diff --git a/arch/arm/mach-msm/qdsp6/dal_acdb.h b/arch/arm/mach-msm/qdsp6/dal_acdb.h new file mode 100644 index 00000000000..dfb1fefdae9 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/dal_acdb.h @@ -0,0 +1,69 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define ACDB_DAL_DEVICE 0x02000069 +#define ACDB_DAL_PORT "DAL_AM_AUD" + +#define ACDB_OP_IOCTL DAL_OP_FIRST_DEVICE_API + +/* ioctls */ +#define ACDB_GET_DEVICE 0x0108bb92 +#define ACDB_SET_DEVICE 0x0108bb93 +#define ACDB_GET_STREAM 0x0108bb95 +#define ACDB_SET_STREAM 0x0108bb96 +#define ACDB_GET_DEVICE_TABLE 0x0108bb97 +#define ACDB_GET_STREAM_TABLE 0x0108bb98 + +#define ACDB_RES_SUCCESS 0 +#define ACDB_RES_FAILURE -1 +#define ACDB_RES_BADPARM -2 +#define ACDB_RES_BADSTATE -3 + +struct acdb_cmd_device { + uint32_t size; + + uint32_t command_id; + uint32_t device_id; + uint32_t network_id; + uint32_t sample_rate_id; + uint32_t interface_id; + uint32_t algorithm_block_id; + + /* physical page aligned buffer */ + uint32_t total_bytes; + uint32_t unmapped_buf; +} __attribute__((packed)); + +struct acdb_cmd_device_table { + uint32_t size; + + uint32_t command_id; + uint32_t device_id; + uint32_t network_id; + uint32_t sample_rate_id; + + /* physical page aligned buffer */ + uint32_t total_bytes; + uint32_t unmapped_buf; + + uint32_t res_size; +} __attribute__((packed)); + +struct acdb_result { + uint32_t dal_status; + uint32_t size; + + uint32_t unmapped_buf; + uint32_t used_bytes; + uint32_t result; +} __attribute__((packed)); diff --git a/arch/arm/mach-msm/qdsp6/dal_adie.h b/arch/arm/mach-msm/qdsp6/dal_adie.h new file mode 100644 index 00000000000..6abc60c66ec --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/dal_adie.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _MACH_MSM_QDSP6_ADIE_ +#define _MACH_MSM_QDSP6_ADIE_ + +#include "dal.h" + +#define ADIE_DAL_DEVICE 0x02000029 +#define ADIE_DAL_PORT "DAL_AM_AUD" + +enum { + ADIE_OP_GET_NUM_PATHS = DAL_OP_FIRST_DEVICE_API, + ADIE_OP_GET_ALL_PATH_IDS, + ADIE_OP_SET_PATH, + ADIE_OP_GET_NUM_PATH_FREQUENCY_PLANS, + ADIE_OP_GET_PATH_FREQUENCY_PLANS, + ADIE_OP_SET_PATH_FREQUENCY_PLAN, + ADIE_OP_PROCEED_TO_STAGE, + ADIE_OP_MUTE_PATH +}; + +/* Path IDs for normal operation. */ +#define ADIE_PATH_HANDSET_TX 0x010740f6 +#define ADIE_PATH_HANDSET_RX 0x010740f7 +#define ADIE_PATH_HEADSET_MONO_TX 0x010740f8 +#define ADIE_PATH_HEADSET_STEREO_TX 0x010740f9 +#define ADIE_PATH_HEADSET_MONO_RX 0x010740fa +#define ADIE_PATH_HEADSET_STEREO_RX 0x010740fb +#define ADIE_PATH_SPEAKER_TX 0x010740fc +#define ADIE_PATH_SPEAKER_RX 0x010740fd +#define ADIE_PATH_SPEAKER_STEREO_RX 0x01074101 + +/* Path IDs used for TTY */ +#define ADIE_PATH_TTY_HEADSET_TX 0x010740fe +#define ADIE_PATH_TTY_HEADSET_RX 0x010740ff + +/* Path IDs used by Factory Test Mode. */ +#define ADIE_PATH_FTM_MIC1_TX 0x01074108 +#define ADIE_PATH_FTM_MIC2_TX 0x01074107 +#define ADIE_PATH_FTM_HPH_L_RX 0x01074106 +#define ADIE_PATH_FTM_HPH_R_RX 0x01074104 +#define ADIE_PATH_FTM_EAR_RX 0x01074103 +#define ADIE_PATH_FTM_SPKR_RX 0x01074102 + +/* Path IDs for Loopback */ +/* Path IDs used for Line in -> AuxPGA -> Line Out Stereo Mode*/ +#define ADIE_PATH_AUXPGA_LINEOUT_STEREO_LB 0x01074100 +/* Line in -> AuxPGA -> LineOut Mono */ +#define ADIE_PATH_AUXPGA_LINEOUT_MONO_LB 0x01073d82 +/* Line in -> AuxPGA -> Stereo Headphone */ +#define ADIE_PATH_AUXPGA_HDPH_STEREO_LB 0x01074109 +/* Line in -> AuxPGA -> Mono Headphone */ +#define ADIE_PATH_AUXPGA_HDPH_MONO_LB 0x01073d85 +/* Line in -> AuxPGA -> Earpiece */ +#define ADIE_PATH_AUXPGA_EAP_LB 0x01073d81 +/* Line in -> AuxPGA -> AuxOut */ +#define ADIE_PATH_AUXPGA_AUXOUT_LB 0x01073d86 + +/* Concurrency Profiles */ +#define ADIE_PATH_SPKR_STEREO_HDPH_MONO_RX 0x01073d83 +#define ADIE_PATH_SPKR_MONO_HDPH_MONO_RX 0x01073d84 +#define ADIE_PATH_SPKR_MONO_HDPH_STEREO_RX 0x01073d88 +#define ADIE_PATH_SPKR_STEREO_HDPH_STEREO_RX 0x01073d89 + + +/** Fluence Profiles **/ + +/* Broadside/Bowsetalk profile, + * For Handset and Speaker phone Tx*/ +#define ADIE_CODEC_HANDSET_SPKR_BS_TX 0x0108fafa +/* EndFire profile, + * For Handset and Speaker phone Tx*/ +#define ADIE_CODEC_HANDSET_SPKR_EF_TX 0x0108fafb + + +/* stages */ +#define ADIE_STAGE_PATH_OFF 0x0050 +#define ADIE_STAGE_DIGITAL_READY 0x0100 +#define ADIE_STAGE_DIGITAL_ANALOG_READY 0x1000 +#define ADIE_STAGE_ANALOG_OFF 0x0750 +#define ADIE_STAGE_DIGITAL_OFF 0x0600 + +/* path types */ +#define ADIE_PATH_RX 0 +#define ADIE_PATH_TX 1 +#define ADIE_PATH_LOOPBACK 2 + +/* mute states */ +#define ADIE_MUTE_OFF 0 +#define ADIE_MUTE_ON 1 + + +#endif diff --git a/arch/arm/mach-msm/qdsp6/dal_audio.h b/arch/arm/mach-msm/qdsp6/dal_audio.h new file mode 100644 index 00000000000..25d1e4f45e3 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/dal_audio.h @@ -0,0 +1,604 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __DAL_AUDIO_H__ +#define __DAL_AUDIO_H__ + +#include "dal_audio_format.h" + +#define AUDIO_DAL_DEVICE 0x02000028 +#define AUDIO_DAL_PORT "DAL_AQ_AUD" + +enum { + AUDIO_OP_CONTROL = DAL_OP_FIRST_DEVICE_API, + AUDIO_OP_DATA, + AUDIO_OP_INIT, +}; + +/* ---- common audio structures ---- */ + +/* This flag, if set, indicates that the beginning of the data in the*/ +/* buffer is a synchronization point or key frame, meaning no data */ +/* before it in the stream is required in order to render the stream */ +/* from this point onward. */ +#define ADSP_AUDIO_BUFFER_FLAG_SYNC_POINT 0x01 + +/* This flag, if set, indicates that the buffer object is using valid */ +/* physical address used to store the media data */ +#define ADSP_AUDIO_BUFFER_FLAG_PHYS_ADDR 0x04 + +/* This flag, if set, indicates that a media start timestamp has been */ +/* set for a buffer. */ +#define ADSP_AUDIO_BUFFER_FLAG_START_SET 0x08 + +/* This flag, if set, indicates that a media stop timestamp has been set */ +/* for a buffer. */ +#define ADSP_AUDIO_BUFFER_FLAG_STOP_SET 0x10 + +/* This flag, if set, indicates that a preroll timestamp has been set */ +/* for a buffer. */ +#define ADSP_AUDIO_BUFFER_FLAG_PREROLL_SET 0x20 + +/* This flag, if set, indicates that the data in the buffer is a fragment of */ +/* a larger block of data, and will be continued by the data in the next */ +/* buffer to be delivered. */ +#define ADSP_AUDIO_BUFFER_FLAG_CONTINUATION 0x40 + +struct adsp_audio_buffer { + u32 addr; /* Physical Address of buffer */ + u32 max_size; /* Maximum size of buffer */ + u32 actual_size; /* Actual size of valid data in the buffer */ + u32 offset; /* Offset to the first valid byte */ + u32 flags; /* ADSP_AUDIO_BUFFER_FLAGs that has been set */ + s64 start; /* Start timestamp, if any */ + s64 stop; /* Stop timestamp, if any */ + s64 preroll; /* Preroll timestamp, if any */ +} __attribute__ ((packed)); + + + +/* ---- audio commands ---- */ + +/* Command/event response types */ +#define ADSP_AUDIO_RESPONSE_COMMAND 0 +#define ADSP_AUDIO_RESPONSE_ASYNC 1 + +struct adsp_command_hdr { + u32 size; /* sizeof(cmd) - sizeof(u32) */ + + u32 dst; + u32 src; + + u32 opcode; + u32 response_type; + u32 seq_number; + + u32 context; /* opaque to DSP */ + u32 data; + + u32 padding; +} __attribute__ ((packed)); + + +#define AUDIO_DOMAIN_APP 0 +#define AUDIO_DOMAIN_MODEM 1 +#define AUDIO_DOMAIN_DSP 2 + +#define AUDIO_SERVICE_AUDIO 0 +#define AUDIO_SERVICE_VIDEO 1 /* really? */ + +/* adsp audio addresses are (byte order) domain, service, major, minor */ +//#define AUDIO_ADDR(maj,min) ( (((maj) & 0xff) << 16) | (((min) & 0xff) << 24) | (1) ) + +#define AUDIO_ADDR(maj,min,dom) ( (((min) & 0xff) << 24) | (((maj) & 0xff) << 16) | ((AUDIO_SERVICE_AUDIO) << 8) | (dom) ) + + +/* AAC Encoder modes */ +#define ADSP_AUDIO_ENC_AAC_LC_ONLY_MODE 0 +#define ADSP_AUDIO_ENC_AAC_PLUS_MODE 1 +#define ADSP_AUDIO_ENC_ENHANCED_AAC_PLUS_MODE 2 + +struct adsp_audio_aac_enc_cfg { + u32 bit_rate; /* bits per second */ + u32 encoder_mode; /* ADSP_AUDIO_ENC_* */ +} __attribute__ ((packed)); + +#define ADSP_AUDIO_ENC_SBC_ALLOCATION_METHOD_LOUNDNESS 0 +#define ADSP_AUDIO_ENC_SBC_ALLOCATION_METHOD_SNR 1 + +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_MONO 1 +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_STEREO 2 +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_DUAL 8 +#define ADSP_AUDIO_ENC_SBC_CHANNEL_MODE_JOINT_STEREO 9 + +struct adsp_audio_sbc_encoder_cfg { + u32 num_subbands; + u32 block_len; + u32 channel_mode; + u32 allocation_method; + u32 bit_rate; +} __attribute__ ((packed)); + +/* AMR NB encoder modes */ +#define ADSP_AUDIO_AMR_MR475 0 +#define ADSP_AUDIO_AMR_MR515 1 +#define ADSP_AUDIO_AMR_MMR59 2 +#define ADSP_AUDIO_AMR_MMR67 3 +#define ADSP_AUDIO_AMR_MMR74 4 +#define ADSP_AUDIO_AMR_MMR795 5 +#define ADSP_AUDIO_AMR_MMR102 6 +#define ADSP_AUDIO_AMR_MMR122 7 + +/* The following are valid AMR NB DTX modes */ +#define ADSP_AUDIO_AMR_DTX_MODE_OFF 0 +#define ADSP_AUDIO_AMR_DTX_MODE_ON_VAD1 1 +#define ADSP_AUDIO_AMR_DTX_MODE_ON_VAD2 2 +#define ADSP_AUDIO_AMR_DTX_MODE_ON_AUTO 3 + +/* AMR Encoder configuration */ +struct adsp_audio_amr_enc_cfg { + u32 mode; /* ADSP_AUDIO_AMR_MR* */ + u32 dtx_mode; /* ADSP_AUDIO_AMR_DTX_MODE* */ + u32 enable; /* 1 = enable, 0 = disable */ +} __attribute__ ((packed)); + +struct adsp_audio_qcelp13k_enc_cfg { + u16 min_rate; + u16 max_rate; +} __attribute__ ((packed)); + +struct adsp_audio_evrc_enc_cfg { + u16 min_rate; + u16 max_rate; +} __attribute__ ((packed)); + +union adsp_audio_codec_config { + struct adsp_audio_amr_enc_cfg amr; + struct adsp_audio_aac_enc_cfg aac; + struct adsp_audio_qcelp13k_enc_cfg qcelp13k; + struct adsp_audio_evrc_enc_cfg evrc; + struct adsp_audio_sbc_encoder_cfg sbc; +} __attribute__ ((packed)); + + +/* This is the default value. */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_NONE 0x0000 + +/* This bit, if set, indicates that the AVSync mode is activated. */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_AVSYNC 0x0001 + +/* This bit, if set, indicates that the Sample Rate/Channel Mode */ +/* Change Notification mode is activated. */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_SR_CM_NOTIFY 0x0002 + +/* This bit, if set, indicates that the sync clock is enabled */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_ENABLE_SYNC_CLOCK 0x0004 + +/* This bit, if set, indicates that the AUX PCM loopback is enabled */ +#define ADSP_AUDIO_OPEN_STREAM_MODE_AUX_PCM 0x0040 + +struct adsp_open_command { + struct adsp_command_hdr hdr; + + u32 device; + u32 endpoint; /* address */ + + u32 stream_context; + u32 mode; + + u32 buf_max_size; + + union adsp_audio_format format; + union adsp_audio_codec_config config; +} __attribute__ ((packed)); + + +/* --- audio control and stream session ioctls ---- */ + +/* Opcode to open a device stream session to capture audio */ +#define ADSP_AUDIO_IOCTL_CMD_OPEN_READ 0x0108dd79 + +/* Opcode to open a device stream session to render audio */ +#define ADSP_AUDIO_IOCTL_CMD_OPEN_WRITE 0x0108dd7a + +/* Opcode to open a device session, must open a device */ +#define ADSP_AUDIO_IOCTL_CMD_OPEN_DEVICE 0x0108dd7b + +/* Close an existing stream or device */ +#define ADSP_AUDIO_IOCTL_CMD_CLOSE 0x0108d8bc + + + +/* A device switch requires three IOCTL */ +/* commands in the following sequence: PREPARE, STANDBY, COMMIT */ + +/* adsp_audio_device_switch_command structure is needed for */ +/* DEVICE_SWITCH_PREPARE */ + +/* Device switch protocol step #1. Pause old device and */ +/* generate silence for the old device. */ +#define ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_PREPARE 0x010815c4 + +/* Device switch protocol step #2. Release old device, */ +/* create new device and generate silence for the new device. */ + +/* When client receives ack for this IOCTL, the client can */ +/* start sending IOCTL commands to configure, calibrate and */ +/* change filter settings on the new device. */ +#define ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_STANDBY 0x010815c5 + +/* Device switch protocol step #3. Start normal operations on new device */ +#define ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_COMMIT 0x01075ee7 + +struct adsp_device_switch_command { + struct adsp_command_hdr hdr; + u32 old_device; + u32 new_device; + u8 device_class; /* 0 = i.rx, 1 = i.tx, 2 = e.rx, 3 = e.tx */ + u8 device_type; /* 0 = rx, 1 = tx, 2 = both */ +} __attribute__ ((packed)); + + + +/* --- audio control session ioctls ---- */ + +#define ADSP_PATH_RX 0 +#define ADSP_PATH_TX 1 +#define ADSP_PATH_BOTH 2 +#define ADSP_PATH_TX_CNG_DIS 3 + +struct adsp_audio_dtmf_start_command { + struct adsp_command_hdr hdr; + u32 tone1_hz; + u32 tone2_hz; + u32 duration_usec; + s32 gain_mb; +} __attribute__ ((packed)); + +/* These commands will affect a logical device and all its associated */ +/* streams. */ + +#define ADSP_AUDIO_MAX_EQ_BANDS 12 + +struct adsp_audio_eq_band { + u16 band_idx; /* The band index, 0 .. 11 */ + u32 filter_type; /* Filter band type */ + u32 center_freq_hz; /* Filter band center frequency */ + s32 filter_gain; /* Filter band initial gain (dB) */ + /* Range is +12 dB to -12 dB with 1dB increments. */ + s32 q_factor; + /* Filter band quality factor expressed as q-8 number, */ + /* e.g. 3000/(2^8) */ +} __attribute__ ((packed)); + +struct adsp_audio_eq_stream_config { + uint32_t enable; /* Number of consequtive bands specified */ + uint32_t num_bands; + struct adsp_audio_eq_band eq_bands[ADSP_AUDIO_MAX_EQ_BANDS]; +} __attribute__ ((packed)); + +/* set device equalizer */ +struct adsp_set_dev_equalizer_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 enable; + u32 num_bands; + struct adsp_audio_eq_band eq_bands[ADSP_AUDIO_MAX_EQ_BANDS]; +} __attribute__ ((packed)); + +/* Set device volume. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_VOL 0x0107605c + +struct adsp_set_dev_volume_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 path; /* 0 = rx, 1 = tx, 2 = both */ + s32 volume; +} __attribute__ ((packed)); + +/* Set Device stereo volume. This command has data payload, */ +/* struct adsp_audio_set_dev_stereo_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_STEREO_VOL 0x0108df3e + +/* Set L, R cross channel gain for a Device. This command has */ +/* data payload, struct adsp_audio_set_dev_x_chan_gain_command. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_XCHAN_GAIN 0x0108df40 + +/* Set device mute state. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_MUTE 0x0107605f + +struct adsp_set_dev_mute_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 path; /* 0 = rx, 1 = tx, 2 = both */ + u32 mute; /* 1 = mute */ +} __attribute__ ((packed)); + +/* Configure Equalizer for a device. */ +/* This command has payload struct adsp_audio_set_dev_equalizer_command. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_EQ_CONFIG 0x0108b10e + +/* Set configuration data for an algorithm aspect of a device. */ +/* This command has payload struct adsp_audio_set_dev_cfg_command. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_CONFIG 0x0108b6cb + +struct adsp_set_dev_cfg_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 block_id; + u32 interface_id; + u32 phys_addr; + u32 phys_size; + u32 phys_used; +} __attribute__ ((packed)); + +/* Set configuration data for all interfaces of a device. */ +#define ADSP_AUDIO_IOCTL_SET_DEVICE_CONFIG_TABLE 0x0108b6bf + +struct adsp_set_dev_cfg_table_command { + struct adsp_command_hdr hdr; + u32 device_id; + u32 phys_addr; + u32 phys_size; + u32 phys_used; +} __attribute__ ((packed)); + +/* ---- audio stream data commands ---- */ + +#define ADSP_AUDIO_IOCTL_CMD_DATA_TX 0x0108dd7f +#define ADSP_AUDIO_IOCTL_CMD_DATA_RX 0x0108dd80 + +struct adsp_buffer_command { + struct adsp_command_hdr hdr; + struct adsp_audio_buffer buffer; +} __attribute__ ((packed)); + + + +/* ---- audio stream ioctls (only affect a single stream in a session) ---- */ + +/* Stop stream for audio device. */ +#define ADSP_AUDIO_IOCTL_CMD_STREAM_STOP 0x01075c54 + +/* End of stream reached. Client will not send any more data. */ +#define ADSP_AUDIO_IOCTL_CMD_STREAM_EOS 0x0108b150 + +/* Do sample slipping/stuffing on AAC outputs. The payload of */ +/* this command is struct adsp_audio_slip_sample_command. */ +#define ADSP_AUDIO_IOCTL_CMD_STREAM_SLIPSAMPLE 0x0108d40e + +/* Set stream volume. */ +/* This command has data payload, struct adsp_audio_set_volume_command. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_STREAM_VOL 0x0108c0de + +/* Set stream stereo volume. This command has data payload, */ +/* struct adsp_audio_set_stereo_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_STEREO_VOL 0x0108dd7c + +/* Set L, R cross channel gain for a Stream. This command has */ +/* data payload, struct adsp_audio_set_x_chan_gain_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_XCHAN_GAIN 0x0108dd7d + +/* Set stream mute state. */ +/* This command has data payload, struct adsp_audio_set_stream_mute. */ +#define ADSP_AUDIO_IOCTL_CMD_SET_STREAM_MUTE 0x0108c0df + +/* Reconfigure bit rate information. This command has data */ +/* payload, struct adsp_audio_set_bit_rate_command */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_BITRATE 0x0108ccf1 + +/* Set Channel Mapping. This command has data payload, struct */ +/* This command has data payload struct adsp_audio_set_channel_map_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_CHANNELMAP 0x0108d32a + +/* Enable/disable AACPlus SBR. */ +/* This command has data payload struct adsp_audio_set_sbr_command */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_SBR 0x0108d416 + +/* Enable/disable WMA Pro Chex and Fex. This command has data payload */ +/* struct adsp_audio_stream_set_wma_command. */ +#define ADSP_AUDIO_IOCTL_SET_STREAM_WMAPRO 0x0108d417 + + +/* ---- audio session ioctls (affect all streams in a session) --- */ + +/* Start stream for audio device. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_START 0x010815c6 + +/* Stop all stream(s) for audio session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_STOP 0x0108dd7e + +/* Pause the data flow for a session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_PAUSE 0x01075ee8 + +/* Resume the data flow for a session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_RESUME 0x01075ee9 + +/* Drop any unprocessed data buffers for a session as indicated by major id. */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_FLUSH 0x01075eea + +/* Start Stream DTMF tone */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_DTMF_START 0x0108c0dd + +/* Stop Stream DTMF tone */ +#define ADSP_AUDIO_IOCTL_CMD_SESSION_DTMF_STOP 0x01087554 + +/* Set Session volume. */ +/* This command has data payload, struct adsp_audio_set_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_VOL 0x0108d8bd + +/* Set session stereo volume. This command has data payload, */ +/* struct adsp_audio_set_stereo_volume_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_STEREO_VOL 0x0108df3d + +/* Set L, R cross channel gain for a session. This command has */ +/* data payload, struct adsp_audio_set_x_chan_gain_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_XCHAN_GAIN 0x0108df3f + +/* Set Session mute state. */ +/* This command has data payload, struct adsp_audio_set_mute_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_MUTE 0x0108d8be + +/* Configure Equalizer for a stream. */ +/* This command has payload struct adsp_audio_set_equalizer_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_EQ_CONFIG 0x0108c0e0 + +/* Set Audio Video sync information. */ +/* This command has data payload, struct adsp_audio_set_av_sync_command. */ +#define ADSP_AUDIO_IOCTL_SET_SESSION_AVSYNC 0x0108d1e2 + +/* Get Audio Media Session time. */ +/* This command returns the audioTime in adsp_audio_unsigned64_event */ +#define ADSP_AUDIO_IOCTL_CMD_GET_AUDIO_TIME 0x0108c26c + + +/* these command structures are used for both STREAM and SESSION ioctls */ + +struct adsp_set_volume_command { + struct adsp_command_hdr hdr; + s32 volume; +} __attribute__ ((packed)); + +struct adsp_set_mute_command { + struct adsp_command_hdr hdr; + u32 mute; /* 1 == mute */ +} __attribute__ ((packed)); + + +struct adsp_set_equalizer_command { + struct adsp_command_hdr hdr; + u32 enable; + u32 num_bands; + struct adsp_audio_eq_band eq_bands[ADSP_AUDIO_MAX_EQ_BANDS]; +} __attribute__ ((packed)); + +/* ---- audio events ---- */ + +/* All IOCTL commands generate an event with the IOCTL opcode as the */ +/* event id after the IOCTL command has been executed. */ + +/* This event is generated after a media stream session is opened. */ +#define ADSP_AUDIO_EVT_STATUS_OPEN 0x0108c0d6 + +/* This event is generated after a media stream session is closed. */ +#define ADSP_AUDIO_EVT_STATUS_CLOSE 0x0108c0d7 + +/* Asyncronous buffer consumption. This event is generated after a */ +/* recived buffer is consumed during rendering or filled during */ +/* capture opeartion. */ +#define ADSP_AUDIO_EVT_STATUS_BUF_DONE 0x0108c0d8 + +/* This event is generated when rendering operation is starving for */ +/* data. In order to avoid audio loss at the end of a plauback, the */ +/* client should wait for this event before issuing the close command. */ +#define ADSP_AUDIO_EVT_STATUS_BUF_UNDERRUN 0x0108c0d9 + +/* This event is generated during capture operation when there are no */ +/* buffers available to copy the captured audio data */ +#define ADSP_AUDIO_EVT_STATUS_BUF_OVERFLOW 0x0108c0da + +/* This asynchronous event is generated as a result of an input */ +/* sample rate change and/or channel mode change detected by the */ +/* decoder. The event payload data is an array of 2 uint32 */ +/* values containing the sample rate in Hz and channel mode. */ +#define ADSP_AUDIO_EVT_SR_CM_CHANGE 0x0108d329 + +struct adsp_event_hdr { + u32 evt_handle; /* DAL common header */ + u32 evt_cookie; + u32 evt_length; + + u32 src; /* "source" audio address */ + u32 dst; /* "destination" audio address */ + + u32 event_id; + u32 response_type; + u32 seq_number; + + u32 context; /* opaque to DSP */ + u32 data; + + u32 status; +} __attribute__ ((packed)); + +struct adsp_buffer_event { + struct adsp_event_hdr hdr; + struct adsp_audio_buffer buffer; +} __attribute__ ((packed)); + + +/* ---- audio device IDs ---- */ + +/* Device direction Rx/Tx flag */ +#define ADSP_AUDIO_RX_DEVICE 0x00 +#define ADSP_AUDIO_TX_DEVICE 0x01 + +/* Default RX or TX device */ +#define ADSP_AUDIO_DEVICE_ID_DEFAULT 0x1081679 + +/* Source (TX) devices */ +#define ADSP_AUDIO_DEVICE_ID_HANDSET_MIC 0x107ac8d +#define ADSP_AUDIO_DEVICE_ID_HEADSET_MIC 0x1081510 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MIC 0x1081512 +#define ADSP_AUDIO_DEVICE_ID_BT_SCO_MIC 0x1081518 +#define ADSP_AUDIO_DEVICE_ID_AUXPCM_TX 0x1081518 +#define ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_MIC 0x108151b +#define ADSP_AUDIO_DEVICE_ID_I2S_MIC 0x1089bf3 + +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_DUAL_MIC 0x108f9c5 +#define ADSP_AUDIO_DEVICE_ID_HANDSET_DUAL_MIC 0x108f9c3 + +/* Special loopback pseudo device to be paired with an RX device */ +/* with usage ADSP_AUDIO_DEVICE_USAGE_MIXED_PCM_LOOPBACK */ +#define ADSP_AUDIO_DEVICE_ID_MIXED_PCM_LOOPBACK_TX 0x1089bf2 + +/* Sink (RX) devices */ +#define ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR 0x107ac88 +#define ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_MONO 0x1081511 +#define ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO 0x107ac8a +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO 0x1081513 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_MONO_HEADSET 0x108c508 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_STEREO_HEADSET 0x108c894 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO 0x1081514 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_MONO_HEADSET 0x108c895 +#define ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_STEREO_HEADSET 0x108c509 +#define ADSP_AUDIO_DEVICE_ID_BT_SCO_SPKR 0x1081519 +#define ADSP_AUDIO_DEVICE_ID_AUXPCM_RX 0x1081519 +#define ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_SPKR 0x108151c +#define ADSP_AUDIO_DEVICE_ID_I2S_SPKR 0x1089bf4 +#define ADSP_AUDIO_DEVICE_ID_NULL_SINK 0x108e512 + +/* BT A2DP playback device. */ +/* This device must be paired with */ +/* ADSP_AUDIO_DEVICE_ID_MIXED_PCM_LOOPBACK_TX using */ +/* ADSP_AUDIO_DEVICE_USAGE_MIXED_PCM_LOOPBACK mode */ +#define ADSP_AUDIO_DEVICE_ID_BT_A2DP_SPKR 0x108151a + +/* Voice Destination identifier - specifically used for */ +/* controlling Voice module from the Device Control Session */ +#define ADSP_AUDIO_DEVICE_ID_VOICE 0x0108df3c + +/* Audio device usage types. */ +/* This is a bit mask to determine which topology to use in the */ +/* device session */ +#define ADSP_AUDIO_DEVICE_CONTEXT_VOICE 0x01 +#define ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK 0x02 +#define ADSP_AUDIO_DEVICE_CONTEXT_MIXED_RECORD 0x10 +#define ADSP_AUDIO_DEVICE_CONTEXT_RECORD 0x20 +#define ADSP_AUDIO_DEVICE_CONTEXT_PCM_LOOPBACK 0x40 + +/* ADSP audio driver return codes */ +#define ADSP_AUDIO_STATUS_SUCCESS 0 +#define ADSP_AUDIO_STATUS_EUNSUPPORTED 20 + +#endif diff --git a/arch/arm/mach-msm/qdsp6/dal_audio_format.h b/arch/arm/mach-msm/qdsp6/dal_audio_format.h new file mode 100644 index 00000000000..638269398ea --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/dal_audio_format.h @@ -0,0 +1,270 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ADSP_AUDIO_MEDIA_FORMAT_H +#define __ADSP_AUDIO_MEDIA_FORMAT_H + + + +/* Supported audio media formats */ + +/* format block in shmem */ +#define ADSP_AUDIO_FORMAT_SHAREDMEMORY 0x01091a78 +/* adsp_audio_format_raw_pcm type */ +#define ADSP_AUDIO_FORMAT_PCM 0x0103d2fd +/* adsp_audio_format_raw_pcm type */ +#define ADSP_AUDIO_FORMAT_DTMF 0x01087725 +/* adsp_audio_format_adpcm type */ +#define ADSP_AUDIO_FORMAT_ADPCM 0x0103d2ff +/* Yamaha PCM format */ +#define ADSP_AUDIO_FORMAT_YADPCM 0x0108dc07 +/* ISO/IEC 11172 */ +#define ADSP_AUDIO_FORMAT_MP3 0x0103d308 +/* ISO/IEC 14496 */ +#define ADSP_AUDIO_FORMAT_MPEG4_AAC 0x010422f1 +/* AMR-NB audio in FS format */ +#define ADSP_AUDIO_FORMAT_AMRNB_FS 0x0105c16c +/* AMR-WB audio in FS format */ +#define ADSP_AUDIO_FORMAT_AMRWB_FS 0x0105c16e +/* QCELP 13k, IS733 */ +#define ADSP_AUDIO_FORMAT_V13K_FS 0x01080b8a +/* EVRC 8k, IS127 */ +#define ADSP_AUDIO_FORMAT_EVRC_FS 0x01080b89 +/* EVRC-B 8k, 4GV */ +#define ADSP_AUDIO_FORMAT_EVRCB_FS 0x0108f2a3 +/* MIDI command stream */ +#define ADSP_AUDIO_FORMAT_MIDI 0x0103d300 +/* A2DP SBC stream */ +#define ADSP_AUDIO_FORMAT_SBC 0x0108c4d8 +/* Version 10 Professional */ +#define ADSP_AUDIO_FORMAT_WMA_V10PRO 0x0108aa92 +/* Version 9 Starndard */ +#define ADSP_AUDIO_FORMAT_WMA_V9 0x0108d430 +/* AMR WideBand Plus */ +#define ADSP_AUDIO_FORMAT_AMR_WB_PLUS 0x0108f3da +/* AC3 Decoder */ +#define ADSP_AUDIO_FORMAT_AC3_DECODER 0x0108d5f9 + + +/* Not yet supported audio media formats */ + + + +/* ISO/IEC 13818 */ +#define ADSP_AUDIO_FORMAT_MPEG2_AAC 0x0103d309 +/* 3GPP TS 26.101 Sec 4.0 */ +#define ADSP_AUDIO_FORMAT_AMRNB_IF1 0x0103d305 +/* 3GPP TS 26.101 Annex A */ +#define ADSP_AUDIO_FORMAT_AMRNB_IF2 0x01057b31 +/* 3GPP TS 26.201 */ +#define ADSP_AUDIO_FORMAT_AMRWB_IF1 0x0103d306 +/* 3GPP TS 26.201 */ +#define ADSP_AUDIO_FORMAT_AMRWB_IF2 0x0105c16d +/* G.711 */ +#define ADSP_AUDIO_FORMAT_G711 0x0106201d +/* QCELP 8k, IS96A */ +#define ADSP_AUDIO_FORMAT_V8K_FS 0x01081d29 +/* Version 1 codec */ +#define ADSP_AUDIO_FORMAT_WMA_V1 0x01055b2b +/* Version 2, 7 & 8 codec */ +#define ADSP_AUDIO_FORMAT_WMA_V8 0x01055b2c +/* Version 9 Professional codec */ +#define ADSP_AUDIO_FORMAT_WMA_V9PRO 0x01055b2d +/* Version 9 Voice codec */ +#define ADSP_AUDIO_FORMAT_WMA_SP1 0x01055b2e +/* Version 9 Lossless codec */ +#define ADSP_AUDIO_FORMAT_WMA_LOSSLESS 0x01055b2f +/* Real Media content, low-bitrate */ +#define ADSP_AUDIO_FORMAT_RA_SIPR 0x01042a0f +/* Real Media content */ +#define ADSP_AUDIO_FORMAT_RA_COOK 0x01042a0e + + +/* For all of the audio formats, unless specified otherwise, */ +/* the following apply: */ +/* Format block bits are arranged in bytes and words in little-endian */ +/* order, i.e., least-significant bit first and least-significant */ +/* byte first. */ + + + +/* AAC Format Block. */ + +/* AAC format block consist of a format identifier followed by */ +/* AudioSpecificConfig formatted according to ISO/IEC 14496-3 */ + +/* The following AAC format identifiers are supported */ +#define ADSP_AUDIO_AAC_ADTS 0x010619cf +#define ADSP_AUDIO_AAC_MPEG4_ADTS 0x010619d0 +#define ADSP_AUDIO_AAC_LOAS 0x010619d1 +#define ADSP_AUDIO_AAC_ADIF 0x010619d2 +#define ADSP_AUDIO_AAC_RAW 0x010619d3 +#define ADSP_AUDIO_AAC_FRAMED_RAW 0x0108c1fb + + +#define ADSP_AUDIO_COMPANDING_ALAW 0x10619cd +#define ADSP_AUDIO_COMPANDING_MLAW 0x10619ce + +/* Maxmum number of bytes allowed in a format block */ +#define ADSP_AUDIO_FORMAT_DATA_MAX 16 + + +struct adsp_audio_no_payload_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* no payload for this format type */ +} __attribute__ ((packed)); + + +/* For convenience, to be used as a standard format block */ +/* for various media types that don't need a unique format block */ +/* ie. PCM, DTMF, etc. */ +struct adsp_audio_standard_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u16 channels; + u16 bits_per_sample; + u32 sampling_rate; + u8 is_signed; + u8 is_interleaved; +} __attribute__ ((packed)); + + + +/* ADPCM format block */ +struct adsp_audio_adpcm_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u16 channels; + u16 bits_per_sample; + u32 sampling_rate; + u8 is_signed; + u8 is_interleaved; + u32 block_size; +} __attribute__ ((packed)); + + +/* MIDI format block */ +struct adsp_audio_midi_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u32 sampling_rate; + u16 channels; + u16 mode; +} __attribute__ ((packed)); + + +/* G711 format block */ +struct adsp_audio_g711_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u32 companding; +} __attribute__ ((packed)); + + +struct adsp_audio_wma_pro_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u16 format_tag; + u16 channels; + u32 samples_per_sec; + u32 avg_bytes_per_sec; + u16 block_align; + u16 valid_bits_per_sample; + u32 channel_mask; + u16 encode_opt; + u16 advanced_encode_opt; + u32 advanced_encode_opt2; + u32 drc_peak_reference; + u32 drc_peak_target; + u32 drc_average_reference; + u32 drc_average_target; +} __attribute__ ((packed)); + + +struct adsp_audio_amrwb_plus_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + u32 size; + u32 version; + u32 channels; + u32 amr_band_mode; + u32 amr_dtx_mode; + u32 amr_frame_format; + u32 amr_isf_index; +} __attribute__ ((packed)); + + +/* Binary Byte Stream Format */ +/* Binary format type that defines a byte stream, */ +/* can be used to specify any format (ie. AAC) */ +struct adsp_audio_binary_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* payload */ + /* number of bytes set in byte stream */ + u32 num_bytes; + /* Byte stream binary data */ + u8 data[ADSP_AUDIO_FORMAT_DATA_MAX]; +} __attribute__ ((packed)); + + +struct adsp_audio_shared_memory_format { + /* Media Format Code (must always be first element) */ + u32 format; + + /* Number of bytes in shared memory */ + u32 len; + /* Phyisical address to data in shared memory */ + u32 address; +} __attribute__ ((packed)); + + +/* Union of all format types */ +union adsp_audio_format { + /* Basic format block with no payload */ + struct adsp_audio_no_payload_format no_payload; + /* Generic format block PCM, DTMF */ + struct adsp_audio_standard_format standard; + /* ADPCM format block */ + struct adsp_audio_adpcm_format adpcm; + /* MIDI format block */ + struct adsp_audio_midi_format midi; + /* G711 format block */ + struct adsp_audio_g711_format g711; + /* WmaPro format block */ + struct adsp_audio_wma_pro_format wma_pro; + /* WmaPro format block */ + struct adsp_audio_amrwb_plus_format amrwb_plus; + /* binary (byte stream) format block, used for AAC */ + struct adsp_audio_binary_format binary; + /* format block in shared memory */ + struct adsp_audio_shared_memory_format shared_mem; +}; + +#endif + diff --git a/arch/arm/mach-msm/qdsp6/dsp_debug.c b/arch/arm/mach-msm/qdsp6/dsp_debug.c new file mode 100644 index 00000000000..fdf049ceef4 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/dsp_debug.c @@ -0,0 +1,179 @@ +/* arch/arm/mach-msm/qdsp6/dsp_dump.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../proc_comm.h" +#include + +static wait_queue_head_t dsp_wait; +static int dsp_has_crashed; +static int dsp_wait_count; + +static atomic_t dsp_crash_count = ATOMIC_INIT(0); + +void q6audio_dsp_not_responding(void) +{ + + if (atomic_add_return(1, &dsp_crash_count) != 1) { + pr_err("q6audio_dsp_not_responding() - parking additional crasher...\n"); + for (;;) + msleep(1000); + } + if (dsp_wait_count) { + dsp_has_crashed = 1; + wake_up(&dsp_wait); + + while (dsp_has_crashed != 2) + wait_event(dsp_wait, dsp_has_crashed == 2); + } else { + pr_err("q6audio_dsp_not_responding() - no waiter?\n"); + } + BUG(); +} + +static int dsp_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t dsp_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + char cmd[32]; + + if (count >= sizeof(cmd)) + return -EINVAL; + if (copy_from_user(cmd, buf, count)) + return -EFAULT; + cmd[count] = 0; + + if ((count > 1) && (cmd[count-1] == '\n')) + cmd[count-1] = 0; + + if (!strcmp(cmd, "wait-for-crash")) { + while (!dsp_has_crashed) { + int res; + dsp_wait_count++; + res = wait_event_interruptible(dsp_wait, dsp_has_crashed); + if (res < 0) { + dsp_wait_count--; + return res; + } + } +#if defined(CONFIG_MACH_MAHIMAHI) + /* assert DSP NMI */ + msm_proc_comm(PCOM_CUSTOMER_CMD1, 0, 0); + msleep(250); +#endif + } else if (!strcmp(cmd, "boom")) { + q6audio_dsp_not_responding(); + } else if (!strcmp(cmd, "continue-crash")) { + dsp_has_crashed = 2; + wake_up(&dsp_wait); + } else { + pr_err("[%s:%s] unknown dsp_debug command: %s\n", __MM_FILE__, + __func__, cmd); + } + + return count; +} + +#define DSP_RAM_BASE 0x2E800000 +#define DSP_RAM_SIZE 0x01800000 + +static unsigned copy_ok_count; + +static ssize_t dsp_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + size_t actual = 0; + size_t mapsize = PAGE_SIZE; + unsigned addr; + void __iomem *ptr; + + if (*pos >= DSP_RAM_SIZE) + return 0; + + if (*pos & (PAGE_SIZE - 1)) + return -EINVAL; + + addr = (*pos + DSP_RAM_BASE); + + /* don't blow up if we're unaligned */ + if (addr & (PAGE_SIZE - 1)) + mapsize *= 2; + + while (count >= PAGE_SIZE) { + ptr = ioremap(addr, mapsize); + if (!ptr) { + pr_err("[%s:%s] map error @ %x\n", __MM_FILE__, + __func__, addr); + return -EFAULT; + } + if (copy_to_user(buf, ptr, PAGE_SIZE)) { + iounmap(ptr); + pr_err("[%s:%s] copy error @ %p\n", __MM_FILE__, + __func__, buf); + return -EFAULT; + } + copy_ok_count += PAGE_SIZE; + iounmap(ptr); + addr += PAGE_SIZE; + buf += PAGE_SIZE; + actual += PAGE_SIZE; + count -= PAGE_SIZE; + } + + *pos += actual; + return actual; +} + +static int dsp_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations dsp_fops = { + .owner = THIS_MODULE, + .open = dsp_open, + .read = dsp_read, + .write = dsp_write, + .release = dsp_release, +}; + +static struct miscdevice dsp_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "dsp_debug", + .fops = &dsp_fops, +}; + + +static int __init dsp_init(void) +{ + init_waitqueue_head(&dsp_wait); + return misc_register(&dsp_misc); +} + +device_initcall(dsp_init); diff --git a/arch/arm/mach-msm/qdsp6/dtmf.c b/arch/arm/mach-msm/qdsp6/dtmf.c new file mode 100644 index 00000000000..cf2748807be --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/dtmf.c @@ -0,0 +1,126 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct dtmf { + struct mutex lock; + struct audio_client *ac; + struct msm_dtmf_config cfg; +}; + +static long dtmf_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct dtmf *dtmf = file->private_data; + int rc = 0; + + mutex_lock(&dtmf->lock); + switch (cmd) { + + case AUDIO_START: { + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + if (dtmf->ac) { + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + rc = -EBUSY; + } else { + dtmf->ac = q6audio_open_dtmf(48000, 2, 0); + if (!dtmf->ac) + rc = -ENOMEM; + } + break; + } + case AUDIO_PLAY_DTMF: { + rc = copy_from_user((void *)&dtmf->cfg, (void *)arg, + sizeof(struct msm_dtmf_config)); + + pr_debug("[%s:%s] PLAY_DTMF: high = %d, low = %d\n", + __MM_FILE__, __func__, dtmf->cfg.dtmf_hi, + dtmf->cfg.dtmf_low); + rc = q6audio_play_dtmf(dtmf->ac, dtmf->cfg.dtmf_hi, + dtmf->cfg.dtmf_low, dtmf->cfg.duration, + dtmf->cfg.rx_gain); + if (rc) { + pr_err("[%s:%s] DTMF_START failed\n", __MM_FILE__, + __func__); + break; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&dtmf->lock); + + pr_debug("[%s:%s] rc = %d\n", __MM_FILE__, __func__, rc) ; + return rc; +} + +static int dtmf_open(struct inode *inode, struct file *file) +{ + int rc = 0; + + struct dtmf *dtmf; + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + dtmf = kzalloc(sizeof(struct dtmf), GFP_KERNEL); + + if (!dtmf) + return -ENOMEM; + + mutex_init(&dtmf->lock); + + file->private_data = dtmf; + return rc; +} + +static int dtmf_release(struct inode *inode, struct file *file) +{ + struct dtmf *dtmf = file->private_data; + if (dtmf->ac) + q6audio_close(dtmf->ac); + kfree(dtmf); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return 0; +} + +static const struct file_operations dtmf_fops = { + .owner = THIS_MODULE, + .open = dtmf_open, + .release = dtmf_release, + .unlocked_ioctl = dtmf_ioctl, +}; + +struct miscdevice dtmf_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_dtmf", + .fops = &dtmf_fops, +}; + +static int __init dtmf_init(void) +{ + return misc_register(&dtmf_misc); +} + +device_initcall(dtmf_init); diff --git a/arch/arm/mach-msm/qdsp6/evrc_in.c b/arch/arm/mach-msm/qdsp6/evrc_in.c new file mode 100644 index 00000000000..9fc412bc81a --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/evrc_in.c @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2010, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "dal_audio_format.h" +#include + +#define EVRC_FC_BUFF_CNT 10 +#define EVRC_READ_TIMEOUT 2000 +struct evrc_fc_buff { + struct mutex lock; + int empty; + void *data; + int size; + int actual_size; +}; + +struct evrc_fc { + struct task_struct *task; + wait_queue_head_t fc_wq; + struct evrc_fc_buff fc_buff[EVRC_FC_BUFF_CNT]; + int buff_index; +}; + +struct evrc { + struct mutex lock; + struct msm_audio_evrc_enc_config cfg; + struct msm_audio_stream_config str_cfg; + struct audio_client *audio_client; + struct msm_voicerec_mode voicerec_mode; + struct evrc_fc *evrc_fc; +}; + + +static int q6_evrc_flowcontrol(void *data) +{ + struct audio_client *ac; + struct audio_buffer *ab; + struct evrc *evrc = data; + int buff_index = 0; + int xfer = 0; + struct evrc_fc *fc; + + + ac = evrc->audio_client; + fc = evrc->evrc_fc; + if (!ac) { + pr_err("[%s:%s] audio_client is NULL\n", __MM_FILE__, __func__); + return 0; + } + + while (!kthread_should_stop()) { + ab = ac->buf + ac->cpu_buf; + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + pr_debug("[%s:%s] ab->data = %p, cpu_buf = %d\n", __MM_FILE__, + __func__, ab->data, ac->cpu_buf); + xfer = ab->actual_size; + + + mutex_lock(&(fc->fc_buff[buff_index].lock)); + if (!fc->fc_buff[buff_index].empty) { + pr_err("[%s:%s] flow control buffer[%d] not read!\n", + __MM_FILE__, __func__, buff_index); + } + + if (fc->fc_buff[buff_index].size < xfer) { + pr_err("[%s:%s] buffer %d too small\n", __MM_FILE__, + __func__, buff_index); + memcpy(fc->fc_buff[buff_index].data, ab->data, + fc->fc_buff[buff_index].size); + fc->fc_buff[buff_index].empty = 0; + fc->fc_buff[buff_index].actual_size = + fc->fc_buff[buff_index].size; + } else { + memcpy(fc->fc_buff[buff_index].data, ab->data, xfer); + fc->fc_buff[buff_index].empty = 0; + fc->fc_buff[buff_index].actual_size = xfer; + } + mutex_unlock(&(fc->fc_buff[buff_index].lock)); + /*wake up client, if any*/ + wake_up(&fc->fc_wq); + + buff_index++; + if (buff_index >= EVRC_FC_BUFF_CNT) + buff_index = 0; + + ab->used = 1; + + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } + + return 0; +} +static long q6_evrc_in_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct evrc *evrc = file->private_data; + int rc = 0; + int i = 0; + struct evrc_fc *fc; + int size = 0; + + mutex_lock(&evrc->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: + pr_debug("[%s:%s] SET_VOLUME\n", __MM_FILE__, __func__); + break; + case AUDIO_GET_STATS: + { + struct msm_audio_stats stats; + pr_debug("[%s:%s] GET_STATS\n", __MM_FILE__, __func__); + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + case AUDIO_START: + { + uint32_t acdb_id; + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + if (arg == 0) { + acdb_id = 0; + } else { + if (copy_from_user(&acdb_id, (void *) arg, + sizeof(acdb_id))) { + rc = -EFAULT; + break; + } + } + if (evrc->audio_client) { + rc = -EBUSY; + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + break; + } else { + evrc->audio_client = q6audio_open_qcp( + evrc->str_cfg.buffer_size, + evrc->cfg.min_bit_rate, + evrc->cfg.max_bit_rate, + evrc->voicerec_mode.rec_mode, + ADSP_AUDIO_FORMAT_EVRC_FS, + acdb_id); + + if (!evrc->audio_client) { + pr_err("[%s:%s] evrc open session failed\n", + __MM_FILE__, __func__); + kfree(evrc); + rc = -ENOMEM; + break; + } + } + + /*allocate flow control buffers*/ + fc = evrc->evrc_fc; + size = evrc->str_cfg.buffer_size; + for (i = 0; i < EVRC_FC_BUFF_CNT; ++i) { + mutex_init(&(fc->fc_buff[i].lock)); + fc->fc_buff[i].empty = 1; + fc->fc_buff[i].data = kmalloc(size, GFP_KERNEL); + if (fc->fc_buff[i].data == NULL) { + pr_err("[%s:%s] No memory for FC buffers\n", + __MM_FILE__, __func__); + rc = -ENOMEM; + goto fc_fail; + } + fc->fc_buff[i].size = size; + fc->fc_buff[i].actual_size = 0; + } + + /*create flow control thread*/ + fc->task = kthread_run(q6_evrc_flowcontrol, + evrc, "evrc_flowcontrol"); + if (IS_ERR(fc->task)) { + rc = PTR_ERR(fc->task); + pr_err("[%s:%s] error creating flow control thread\n", + __MM_FILE__, __func__); + goto fc_fail; + } + break; +fc_fail: + /*free flow control buffers*/ + --i; + for (; i >= 0; i--) { + kfree(fc->fc_buff[i].data); + fc->fc_buff[i].data = NULL; + } + break; + } + case AUDIO_STOP: + pr_debug("[%s:%s] AUDIO_STOP\n", __MM_FILE__, __func__); + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_INCALL: { + pr_debug("[%s:%s] SET_INCALL\n", __MM_FILE__, __func__); + if (copy_from_user(&evrc->voicerec_mode, + (void *)arg, sizeof(struct msm_voicerec_mode))) + rc = -EFAULT; + + if (evrc->voicerec_mode.rec_mode != AUDIO_FLAG_READ + && evrc->voicerec_mode.rec_mode != + AUDIO_FLAG_INCALL_MIXED) { + evrc->voicerec_mode.rec_mode = AUDIO_FLAG_READ; + pr_err("[%s:%s] Invalid rec_mode\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + break; + } + case AUDIO_GET_STREAM_CONFIG: + if (copy_to_user((void *)arg, &evrc->str_cfg, + sizeof(struct msm_audio_stream_config))) + rc = -EFAULT; + + pr_debug("[%s:%s] GET_STREAM_CONFIG: buffsz=%d, buffcnt=%d\n", + __MM_FILE__, __func__, evrc->str_cfg.buffer_size, + evrc->str_cfg.buffer_count); + break; + case AUDIO_SET_STREAM_CONFIG: + if (copy_from_user(&evrc->str_cfg, (void *)arg, + sizeof(struct msm_audio_stream_config))) { + rc = -EFAULT; + break; + } + + pr_debug("[%s:%s] SET_STREAM_CONFIG: buffsz=%d, buffcnt=%d\n", + __MM_FILE__, __func__, evrc->str_cfg.buffer_size, + evrc->str_cfg.buffer_count); + + if (evrc->str_cfg.buffer_size < 23) { + pr_err("[%s:%s] Buffer size too small\n", __MM_FILE__, + __func__); + rc = -EINVAL; + break; + } + + if (evrc->str_cfg.buffer_count != 2) + pr_info("[%s:%s] Buffer count set to 2\n", __MM_FILE__, + __func__); + break; + case AUDIO_SET_EVRC_ENC_CONFIG: + if (copy_from_user(&evrc->cfg, (void *) arg, + sizeof(struct msm_audio_evrc_enc_config))) + rc = -EFAULT; + pr_debug("[%s:%s] SET_EVRC_ENC_CONFIG\n", __MM_FILE__, + __func__); + + if (evrc->cfg.min_bit_rate > 4 || evrc->cfg.min_bit_rate < 1) { + pr_err("[%s:%s] invalid min bitrate\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + if (evrc->cfg.max_bit_rate > 4 || evrc->cfg.max_bit_rate < 1) { + pr_err("[%s:%s] invalid max bitrate\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + break; + case AUDIO_GET_EVRC_ENC_CONFIG: + if (copy_to_user((void *) arg, &evrc->cfg, + sizeof(struct msm_audio_evrc_enc_config))) + rc = -EFAULT; + pr_debug("[%s:%s] GET_EVRC_ENC_CONFIG\n", __MM_FILE__, + __func__); + break; + + default: + rc = -EINVAL; + } + + mutex_unlock(&evrc->lock); + pr_debug("[%s:%s] rc = %d\n", __MM_FILE__, __func__, rc); + return rc; +} + +static int q6_evrc_in_open(struct inode *inode, struct file *file) +{ + struct evrc *evrc; + struct evrc_fc *fc; + int i; + + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + evrc = kmalloc(sizeof(struct evrc), GFP_KERNEL); + if (evrc == NULL) { + pr_err("[%s:%s] Could not allocate memory for evrc driver\n", + __MM_FILE__, __func__); + return -ENOMEM; + } + + mutex_init(&evrc->lock); + file->private_data = evrc; + evrc->audio_client = NULL; + evrc->str_cfg.buffer_size = 23; + evrc->str_cfg.buffer_count = 2; + evrc->cfg.cdma_rate = CDMA_RATE_FULL; + evrc->cfg.min_bit_rate = 1; + evrc->cfg.max_bit_rate = 4; + evrc->voicerec_mode.rec_mode = AUDIO_FLAG_READ; + + evrc->evrc_fc = kmalloc(sizeof(struct evrc_fc), GFP_KERNEL); + if (evrc->evrc_fc == NULL) { + pr_err("[%s:%s] Could not allocate memory for evrc_fc\n", + __MM_FILE__, __func__); + kfree(evrc); + return -ENOMEM; + } + fc = evrc->evrc_fc; + fc->task = NULL; + fc->buff_index = 0; + for (i = 0; i < EVRC_FC_BUFF_CNT; ++i) { + fc->fc_buff[i].data = NULL; + fc->fc_buff[i].size = 0; + fc->fc_buff[i].actual_size = 0; + } + /*initialize wait queue head*/ + init_waitqueue_head(&fc->fc_wq); + return 0; +} + +static ssize_t q6_evrc_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + const char __user *start = buf; + struct evrc *evrc = file->private_data; + struct evrc_fc *fc; + int xfer = 0; + int res = 0; + + pr_debug("[%s:%s] count = %d\n", __MM_FILE__, __func__, count); + mutex_lock(&evrc->lock); + ac = evrc->audio_client; + if (!ac) { + res = -ENODEV; + goto fail; + } + fc = evrc->evrc_fc; + while (count > xfer) { + /*wait for buffer to full*/ + if (fc->fc_buff[fc->buff_index].empty != 0) { + res = wait_event_interruptible_timeout(fc->fc_wq, + (fc->fc_buff[fc->buff_index].empty == 0), + msecs_to_jiffies(EVRC_READ_TIMEOUT)); + + pr_debug("[%s:%s] buff_index = %d\n", __MM_FILE__, + __func__, fc->buff_index); + if (res == 0) { + pr_err("[%s:%s] Timeout!\n", __MM_FILE__, + __func__); + res = -ETIMEDOUT; + goto fail; + } else if (res < 0) { + pr_err("[%s:%s] Returning on Interrupt\n", + __MM_FILE__, __func__); + goto fail; + } + } + /*lock the buffer*/ + mutex_lock(&(fc->fc_buff[fc->buff_index].lock)); + xfer = fc->fc_buff[fc->buff_index].actual_size; + + if (xfer > count) { + mutex_unlock(&(fc->fc_buff[fc->buff_index].lock)); + pr_err("[%s:%s] read failed! byte count too small\n", + __MM_FILE__, __func__); + res = -EINVAL; + goto fail; + } + + if (copy_to_user(buf, fc->fc_buff[fc->buff_index].data, xfer)) { + mutex_unlock(&(fc->fc_buff[fc->buff_index].lock)); + pr_err("[%s:%s] copy_to_user failed at index %d\n", + __MM_FILE__, __func__, fc->buff_index); + res = -EFAULT; + goto fail; + } + buf += xfer; + count -= xfer; + + fc->fc_buff[fc->buff_index].empty = 1; + fc->fc_buff[fc->buff_index].actual_size = 0; + + mutex_unlock(&(fc->fc_buff[fc->buff_index].lock)); + ++(fc->buff_index); + if (fc->buff_index >= EVRC_FC_BUFF_CNT) + fc->buff_index = 0; + } + res = buf - start; + +fail: + mutex_unlock(&evrc->lock); + + return res; +} + +static int q6_evrc_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct evrc *evrc = file->private_data; + int i = 0; + struct evrc_fc *fc; + + mutex_lock(&evrc->lock); + fc = evrc->evrc_fc; + kthread_stop(fc->task); + fc->task = NULL; + /*free flow control buffers*/ + for (i = 0; i < EVRC_FC_BUFF_CNT; ++i) { + kfree(fc->fc_buff[i].data); + fc->fc_buff[i].data = NULL; + } + kfree(fc); + if (evrc->audio_client) + rc = q6audio_close(evrc->audio_client); + mutex_unlock(&evrc->lock); + kfree(evrc); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return rc; +} + +static const struct file_operations q6_evrc_in_fops = { + .owner = THIS_MODULE, + .open = q6_evrc_in_open, + .read = q6_evrc_in_read, + .release = q6_evrc_in_release, + .unlocked_ioctl = q6_evrc_in_ioctl, +}; + +struct miscdevice q6_evrc_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_evrc_in", + .fops = &q6_evrc_in_fops, +}; + +static int __init q6_evrc_in_init(void) +{ + return misc_register(&q6_evrc_in_misc); +} + +device_initcall(q6_evrc_in_init); diff --git a/arch/arm/mach-msm/qdsp6/mp3.c b/arch/arm/mach-msm/qdsp6/mp3.c new file mode 100644 index 00000000000..16f6204febb --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/mp3.c @@ -0,0 +1,249 @@ +/* arch/arm/mach-msm/qdsp6/mp3.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define BUFSZ (8192) +#define DMASZ (BUFSZ * 2) + +struct mp3 { + struct mutex lock; + struct audio_client *ac; + uint32_t sample_rate; + uint32_t channel_count; +}; + +static long mp3_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct mp3 *mp3 = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void*) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&mp3->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: { + int vol; + pr_debug("[%s:%s] SET_VOLUME = %d\n", __MM_FILE__, + __func__, vol); + if (copy_from_user(&vol, (void*) arg, sizeof(vol))) { + rc = -EFAULT; + break; + } + rc = q6audio_set_stream_volume(mp3->ac, vol); + break; + } + case AUDIO_START: { + uint32_t acdb_id; + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, (void*) arg, sizeof(acdb_id))) { + pr_info("[%s:%s] copy acdb_id from user failed\n", + __MM_FILE__, __func__); + rc = -EFAULT; + break; + } + if (mp3->ac) { + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + rc = -EBUSY; + } else { + mp3->ac = q6audio_open_mp3(BUFSZ, + mp3->sample_rate, mp3->channel_count, acdb_id); + if (!mp3->ac) { + pr_err("[%s:%s] mp3 open session failed\n", + __MM_FILE__, __func__); + rc = -ENOMEM; + } + } + break; + } + case AUDIO_STOP: + pr_debug("[%s:%s] AUDIO_STOP\n", __MM_FILE__, __func__); + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (mp3->ac) { + rc = -EBUSY; + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + break; + } + if (copy_from_user(&config, (void*) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + pr_debug("[%s:%s] SET_CONFIG: buffsize = %d, samplerate = %d, \ + channelcount = %d\n", __MM_FILE__, __func__, + config.buffer_size, config.sample_rate, + config.channel_count); + if (config.channel_count < 1 || config.channel_count > 2) { + rc = -EINVAL; + pr_err("[%s:%s] invalid channelcount\n", __MM_FILE__, + __func__); + break; + } + mp3->sample_rate = config.sample_rate; + mp3->channel_count = config.channel_count; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = BUFSZ; + config.buffer_count = 2; + config.sample_rate = mp3->sample_rate; + config.channel_count = mp3->channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void*) arg, &config, sizeof(config))) { + rc = -EFAULT; + } + pr_debug("[%s:%s] GET_CONFIG: buffsize = %d, samplerate = %d, \ + channelcount = %d\n", __MM_FILE__, __func__, + config.buffer_size, config.sample_rate, + config.channel_count); + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&mp3->lock); + pr_debug("[%s:%s] rc = %d\n", __MM_FILE__, __func__, rc); + return rc; +} + +static int mp3_open(struct inode *inode, struct file *file) +{ + int rc = 0; + + struct mp3 *mp3; + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + mp3 = kzalloc(sizeof(struct mp3), GFP_KERNEL); + + if (!mp3) + return -ENOMEM; + + mutex_init(&mp3->lock); + mp3->channel_count = 2; + mp3->sample_rate = 44100; + + file->private_data = mp3; + return rc; +} + +static ssize_t mp3_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mp3 *mp3 = file->private_data; + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer; + + pr_debug("[%s:%s] count = %d\n", __MM_FILE__, __func__, count); + if (!mp3->ac) + mp3_ioctl(file, AUDIO_START, 0); + + ac = mp3->ac; + if (!ac) + return -ENODEV; + + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + pr_debug("[%s:%s] ab->data = %p, ac->cpu_buf = %d\n", + __MM_FILE__, __func__, ab->data, ac->cpu_buf); + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_from_user(ab->data, buf, xfer)) + return -EFAULT; + + buf += xfer; + count -= xfer; + + ab->used = xfer; + q6audio_write(ac, ab); + ac->cpu_buf ^= 1; + } + + return buf - start; +} + +static int mp3_fsync(struct file *f, int datasync) +{ + struct mp3 *mp3 = f->private_data; + if (mp3->ac) + return q6audio_async(mp3->ac); + return -ENODEV; +} + +static int mp3_release(struct inode *inode, struct file *file) +{ + struct mp3 *mp3 = file->private_data; + if (mp3->ac) + q6audio_mp3_close(mp3->ac); + kfree(mp3); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return 0; +} + +static struct file_operations mp3_fops = { + .owner = THIS_MODULE, + .open = mp3_open, + .write = mp3_write, + .fsync = mp3_fsync, + .release = mp3_release, + .unlocked_ioctl = mp3_ioctl, +}; + +struct miscdevice mp3_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_mp3", + .fops = &mp3_fops, +}; + +static int __init mp3_init(void) { + return misc_register(&mp3_misc); +} + +device_initcall(mp3_init); diff --git a/arch/arm/mach-msm/qdsp6/msm_q6vdec.c b/arch/arm/mach-msm/qdsp6/msm_q6vdec.c new file mode 100644 index 00000000000..c79f0c41116 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/msm_q6vdec.c @@ -0,0 +1,1505 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* +#define DEBUG_TRACE_VDEC +#define DEBUG +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "dal.h" + +#define DALDEVICEID_VDEC_DEVICE 0x02000026 +#define DALDEVICEID_VDEC_PORTNAME "DAL_AQ_VID" + +#define VDEC_INTERFACE_VERSION 0x00020000 + +#define MAJOR_MASK 0xFFFF0000 +#define MINOR_MASK 0x0000FFFF + +#define VDEC_GET_MAJOR_VERSION(version) (((version)&MAJOR_MASK)>>16) + +#define VDEC_GET_MINOR_VERSION(version) ((version)&MINOR_MASK) + +#ifdef DEBUG_TRACE_VDEC +#define TRACE(fmt,x...) \ + do { pr_debug("%s:%d " fmt, __func__, __LINE__, ##x); } while (0) +#else +#define TRACE(fmt,x...) do { } while (0) +#endif + +#define YAMATO_COLOR_FORMAT 0x02 +#define MAX_Q6_LOAD ((720*1280)/256) /* 720p */ +#define MAX_Q6_LOAD_YAMATO ((736*1280)/256) +#define MAX_Q6_LOAD_VP6 ((800*480)/256) + +#define VDEC_MAX_PORTS 4 + +/* + *why magic number 300? + + *the Maximum size of the DAL payload is 512 bytes according to DAL protocol + *Initialize call to QDSP6 from scorpion need to send sequence header as part of + *the DAL payload. DAL payload to initialize contains the following + + *1) configuration data- 52 bytes 2) length field of config data - 4 bytes + *3) sequence header data ( that is from the bit stream) + *4) length field for sequence header - 4 bytes + *5) length field for output structure - 4 bytes + + *that left with 512 - 68 = 448 bytes. It is unusual that we get a sequence + *header with such a big length unless the bit stream has multiple sequence + *headers.We estimated 300 is good enough which gives enough room for rest + *of the payload and even reserves some space for future payload. + */ + +#define VDEC_MAX_SEQ_HEADER_SIZE 300 + +char *Q6Portnames[] = { +"DAL_AQ_VID_0", +"DAL_AQ_VID_1", +"DAL_AQ_VID_2", +"DAL_AQ_VID_3" +}; + + + +#define DALDEVICEID_VDEC_DEVICE_0 0x020000D2 +#define DALDEVICEID_VDEC_DEVICE_1 0x020000D3 +#define DALDEVICEID_VDEC_DEVICE_2 0x020000D4 +#define DALDEVICEID_VDEC_DEVICE_3 0x020000D5 +#define DALDEVICEID_VDEC_DEVICE_4 0x020000D6 +#define DALDEVICEID_VDEC_DEVICE_5 0x020000D7 +#define DALDEVICEID_VDEC_DEVICE_6 0x020000D8 +#define DALDEVICEID_VDEC_DEVICE_7 0x020000D9 +#define DALDEVICEID_VDEC_DEVICE_8 0x020000DA +#define DALDEVICEID_VDEC_DEVICE_9 0x020000DB +#define DALDEVICEID_VDEC_DEVICE_10 0x020000DC +#define DALDEVICEID_VDEC_DEVICE_11 0x020000DD +#define DALDEVICEID_VDEC_DEVICE_12 0x020000DE +#define DALDEVICEID_VDEC_DEVICE_13 0x020000DF +#define DALDEVICEID_VDEC_DEVICE_14 0x020000E0 +#define DALDEVICEID_VDEC_DEVICE_15 0x020000E1 +#define DALDEVICEID_VDEC_DEVICE_16 0x020000E2 +#define DALDEVICEID_VDEC_DEVICE_17 0x020000E3 +#define DALDEVICEID_VDEC_DEVICE_18 0x020000E4 +#define DALDEVICEID_VDEC_DEVICE_19 0x020000E5 +#define DALDEVICEID_VDEC_DEVICE_20 0x020000E6 +#define DALDEVICEID_VDEC_DEVICE_21 0x020000E7 +#define DALDEVICEID_VDEC_DEVICE_22 0x020000E8 +#define DALDEVICEID_VDEC_DEVICE_23 0x020000E9 +#define DALDEVICEID_VDEC_DEVICE_24 0x020000EA +#define DALDEVICEID_VDEC_DEVICE_25 0x020000EB +#define DALDEVICEID_VDEC_DEVICE_26 0x020000EC +#define DALDEVICEID_VDEC_DEVICE_27 0x020000ED +#define DALDEVICEID_VDEC_DEVICE_28 0x020000EE +#define DALDEVICEID_VDEC_DEVICE_29 0x020000EF +#define DALDEVICEID_VDEC_DEVICE_30 0x020000F0 +#define DALDEVICEID_VDEC_DEVICE_31 0x020000F1 + +#define DALVDEC_MAX_DEVICE_IDS 32 + + +static int numOfPorts; + + +static char loadOnPorts[VDEC_MAX_PORTS]; + +static char deviceIdRegistry[DALVDEC_MAX_DEVICE_IDS]; + + +#define VDEC_DEVID_FREE 0 +#define VDEC_DEVID_OCCUPIED 1 + +#define MAX_SUPPORTED_INSTANCES 6 + +#define MAKEFOURCC(ch0, ch1, ch2, ch3) ((unsigned int)(unsigned char)(ch0) | \ + ((unsigned int)(unsigned char)(ch1) << 8) | \ + ((unsigned int)(unsigned char)(ch2) << 16) | \ + ((unsigned int)(unsigned char)(ch3) << 24)) + +#define FOURCC_MPEG4 MAKEFOURCC('m', 'p', '4', 'v') +#define FOURCC_H263 MAKEFOURCC('h', '2', '6', '3') +#define FOURCC_H264 MAKEFOURCC('h', '2', '6', '4') +#define FOURCC_VC1 MAKEFOURCC('w', 'm', 'v', '3') +#define FOURCC_DIVX MAKEFOURCC('D', 'I', 'V', 'X') +#define FOURCC_SPARK MAKEFOURCC('F', 'L', 'V', '1') +#define FOURCC_VP6 MAKEFOURCC('V', 'P', '6', '0') + +/* static struct vdec_data *multiInstances[MAX_SUPPORTED_INSTANCES];*/ + +static int totalPlaybackQ6load; +static int totalTnailQ6load; + +#define FLAG_THUMBNAIL_MODE 0x8 +#define MAX_TNAILS 3 + +#define TRUE 1 +#define FALSE 0 + +enum { + VDEC_DALRPC_INITIALIZE = DAL_OP_FIRST_DEVICE_API, + VDEC_DALRPC_SETBUFFERS, + VDEC_DALRPC_FREEBUFFERS, + VDEC_DALRPC_QUEUE, + VDEC_DALRPC_SIGEOFSTREAM, + VDEC_DALRPC_FLUSH, + VDEC_DALRPC_REUSEFRAMEBUFFER, + VDEC_DALRPC_GETDECATTRIBUTES, + VDEC_DALRPC_SUSPEND, + VDEC_DALRPC_RESUME, + VDEC_DALRPC_INITIALIZE_00, + VDEC_DALRPC_GETINTERNALBUFFERREQ, + VDEC_DALRPC_SETBUFFERS_00, + VDEC_DALRPC_FREEBUFFERS_00, + VDEC_DALRPC_GETPROPERTY, + VDEC_DALRPC_SETPROPERTY, + VDEC_DALRPC_GETDECATTRIBUTES_00, + VDEC_DALRPC_PERFORMANCE_CHANGE_REQUEST +}; + +enum { + VDEC_ASYNCMSG_DECODE_DONE = 0xdec0de00, + VDEC_ASYNCMSG_REUSE_FRAME, +}; + +struct vdec_init_cfg { + u32 decode_done_evt; + u32 reuse_frame_evt; + struct vdec_config cfg; +}; + +struct vdec_buffer_status { + u32 data; + u32 status; +}; + +#define VDEC_MSG_MAX 128 + +struct vdec_msg_list { + struct list_head list; + struct vdec_msg vdec_msg; +}; + +struct vdec_mem_info { + u32 buf_type; + u32 id; + unsigned long phys_addr; + unsigned long len; + struct file *file; +}; + +struct vdec_mem_list { + struct list_head list; + struct vdec_mem_info mem; +}; + +struct videoStreamDetails{ + int height; + int width; + unsigned int fourcc; + int Q6usage; + bool isThisTnail; + bool isTnailGranted; +}; + +struct vdec_data { + struct dal_client *vdec_handle; + unsigned int Q6deviceId; + struct videoStreamDetails streamDetails; + struct list_head vdec_msg_list_head; + struct list_head vdec_msg_list_free; + wait_queue_head_t vdec_msg_evt; + spinlock_t vdec_list_lock; + struct list_head vdec_mem_list_head; + spinlock_t vdec_mem_list_lock; + int mem_initialized; + int running; + int close_decode; +}; + +static struct class *driver_class; +static dev_t vdec_device_no; +static struct cdev vdec_cdev; +static int ref_cnt; +static DEFINE_MUTEX(vdec_ref_lock); + +static DEFINE_MUTEX(idlecount_lock); + +static DEFINE_MUTEX(vdec_rm_lock); + +static int idlecount; +static struct wake_lock wakelock; +static struct wake_lock idlelock; + +static void prevent_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (++idlecount == 1) { + wake_lock(&idlelock); + wake_lock(&wakelock); + } + mutex_unlock(&idlecount_lock); +} + +static void allow_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (--idlecount == 0) { + wake_unlock(&idlelock); + wake_unlock(&wakelock); + } + mutex_unlock(&idlecount_lock); +} + +static inline int vdec_check_version(u32 client, u32 server) +{ + int ret = -EINVAL; + if ((VDEC_GET_MAJOR_VERSION(client) == VDEC_GET_MAJOR_VERSION(server)) + && (VDEC_GET_MINOR_VERSION(client) <= + VDEC_GET_MINOR_VERSION(server))) + ret = 0; + return ret; +} + +static int vdec_get_msg(struct vdec_data *vd, void *msg) +{ + struct vdec_msg_list *l; + unsigned long flags; + int ret = 0; + + if (!vd->running) + return -EPERM; + + spin_lock_irqsave(&vd->vdec_list_lock, flags); + list_for_each_entry_reverse(l, &vd->vdec_msg_list_head, list) { + if (copy_to_user(msg, &l->vdec_msg, sizeof(struct vdec_msg))) + pr_err("vdec_get_msg failed to copy_to_user!\n"); + if (l->vdec_msg.id == VDEC_MSG_REUSEINPUTBUFFER) + TRACE("reuse_input_buffer %d\n", l->vdec_msg.buf_id); + else if (l->vdec_msg.id == VDEC_MSG_FRAMEDONE) + TRACE("frame_done (stat=%d)\n", + l->vdec_msg.vfr_info.status); + else + TRACE("unknown msg (msgid=%d)\n", l->vdec_msg.id); + list_del(&l->list); + list_add(&l->list, &vd->vdec_msg_list_free); + ret = 1; + break; + } + spin_unlock_irqrestore(&vd->vdec_list_lock, flags); + + if (vd->close_decode) + ret = 1; + + return ret; +} + +static void vdec_put_msg(struct vdec_data *vd, struct vdec_msg *msg) +{ + struct vdec_msg_list *l; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&vd->vdec_list_lock, flags); + list_for_each_entry(l, &vd->vdec_msg_list_free, list) { + memcpy(&l->vdec_msg, msg, sizeof(struct vdec_msg)); + list_del(&l->list); + list_add(&l->list, &vd->vdec_msg_list_head); + found = 1; + break; + } + spin_unlock_irqrestore(&vd->vdec_list_lock, flags); + + if (found) + wake_up(&vd->vdec_msg_evt); + else + pr_err("vdec_put_msg can't find free list!\n"); +} + +static struct vdec_mem_list *vdec_get_mem_from_list(struct vdec_data *vd, + u32 pmem_id, u32 buf_type) +{ + struct vdec_mem_list *l; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&vd->vdec_mem_list_lock, flags); + list_for_each_entry(l, &vd->vdec_mem_list_head, list) { + if (l->mem.buf_type == buf_type && l->mem.id == pmem_id) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&vd->vdec_mem_list_lock, flags); + + if (found) + return l; + else + return NULL; + +} +static int vdec_setproperty(struct vdec_data *vd, void *argp) +{ + struct vdec_property_info property; + int res; + + if (copy_from_user(&property, argp, sizeof(struct vdec_property_info))) + return -1; + + res = dal_call_f6(vd->vdec_handle, VDEC_DALRPC_SETPROPERTY, + property.id, &(property.property), sizeof(union vdec_property)); + if (res) + TRACE("Set Property failed"); + else + TRACE("Set Property succeeded"); + return res; +} +static int vdec_getproperty(struct vdec_data *vd, void *argp) +{ + int res; + union vdec_property property = {0}; + + res = dal_call_f11(vd->vdec_handle, VDEC_DALRPC_GETPROPERTY, + ((struct vdec_property_info *)argp)->id, &property, + sizeof(union vdec_property)); + + if (res) + TRACE("get Property failed"); + else + TRACE("get Property succeeded"); + + res = copy_to_user( + (&((struct vdec_property_info *)argp)->property), + &property, sizeof(property)); + + return res; +} +static int vdec_performance_change_request(struct vdec_data *vd, void* argp) +{ + u32 request_type; + int ret; + + ret = copy_from_user(&request_type, argp, sizeof(request_type)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + ret = dal_call_f0(vd->vdec_handle, + VDEC_DALRPC_PERFORMANCE_CHANGE_REQUEST, + request_type); + if (ret) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + return ret; + } + return ret; +} + +#ifdef TRACE_PORTS +static void printportsanddeviceids(void) +{ + int i; + + pr_err("\n\n%s:loadOnPorts", __func__); + for (i = 0; i < numOfPorts; i++) + pr_err("\t%d", loadOnPorts[i]); + + pr_err("\n\n"); + + pr_err("\n\n%s:Devids", __func__); + for (i = 0; i < DALVDEC_MAX_DEVICE_IDS; i++) + pr_err("Devid[%d]:%d\n", i, deviceIdRegistry[i]); + + + pr_err("\n\n"); +} +#endif /*TRACE_PORTS*/ + + +/* + * + * This method is used to get the number of ports supported on the Q6 + * + */ +static int vdec_get_numberofq6ports(void) +{ + struct dal_client *vdec_handle = NULL; + int retval = 0; + union vdec_property property = {0}; + + vdec_handle = dal_attach(DALDEVICEID_VDEC_DEVICE, + DALDEVICEID_VDEC_PORTNAME, 1, NULL, NULL); + if (!vdec_handle) { + pr_err("%s: failed to attach\n", __func__); + return 1;/* default setting */ + } + + retval = dal_call_f6(vdec_handle, VDEC_DALRPC_GETPROPERTY, + VDEC_NUM_DAL_PORTS, (void *)&property, sizeof(union vdec_property)); + if (retval) { + pr_err("%s: Q6get prperty failed\n", __func__); + return 1;/* default setting */ + } + + dal_detach(vdec_handle); + return property.num_dal_ports ; +} + + +/** + * This method is used to get the find the least loaded port and a corresponding + * free device id in that port. + * + * Prerequisite: vdec_open should have been called. + * + * @param[in] deviceid + * device id will be populated here. + * + * @param[in] portname + * portname will be populated here. + */ +static void vdec_get_next_portanddevid(int *deviceid, char **portname) +{ + + int i = 0; + int leastLoad = 0; + int leastLoadedIndex = 0; + + if (0 == numOfPorts) { + numOfPorts = vdec_get_numberofq6ports(); + pr_err("%s: Q6get numOfPorts %d\n", __func__, numOfPorts); + numOfPorts = 4; + /*fix: me currently hard coded to 4 as + *the Q6 getproperty is failing + */ + } + + if ((NULL == deviceid) || (NULL == portname)) + return; + else + *deviceid = 0; /* init value */ + + if (numOfPorts > 1) { + /* multi ports mode*/ + + /* find the least loaded port*/ + for (i = 1, leastLoad = loadOnPorts[0], leastLoadedIndex = 0; + i < numOfPorts; i++) { + if (leastLoad > loadOnPorts[i]) { + leastLoadedIndex = i; + leastLoad = loadOnPorts[i]; + } + } + + /* register the load */ + loadOnPorts[leastLoadedIndex]++; + *portname = Q6Portnames[leastLoadedIndex]; + + /* find a free device id corresponding to the port*/ + for (i = leastLoadedIndex; i < DALVDEC_MAX_DEVICE_IDS; + i += numOfPorts) { + if (VDEC_DEVID_FREE == deviceIdRegistry[i]) { + deviceIdRegistry[i] = VDEC_DEVID_OCCUPIED; + *deviceid = DALDEVICEID_VDEC_DEVICE_0 + i; + break; + } + } + +#ifdef TRACE_PORTS + printportsanddeviceids(); +#endif /*TRACE_PORTS*/ + } else if (1 == numOfPorts) { + /* single port mode */ + *deviceid = DALDEVICEID_VDEC_DEVICE; + *portname = DALDEVICEID_VDEC_PORTNAME; + } else if (numOfPorts <= 0) { + pr_err("%s: FATAL error numOfPorts cannot be \ + less than or equal to zero\n", __func__); + } + + +} + + +/** + * This method frees up the used dev id and decrements the port load. + * + */ + +static void vdec_freeup_portanddevid(int deviceid) +{ + + if (numOfPorts > 1) { + /* multi ports mode*/ + if (VDEC_DEVID_FREE == + deviceIdRegistry[deviceid - DALDEVICEID_VDEC_DEVICE_0]) + pr_err("device id cannot be already free\n"); + deviceIdRegistry[deviceid - DALDEVICEID_VDEC_DEVICE_0] = + VDEC_DEVID_FREE; + + loadOnPorts[(deviceid - DALDEVICEID_VDEC_DEVICE_0) + % numOfPorts]--; + + if (loadOnPorts[(deviceid - DALDEVICEID_VDEC_DEVICE_0) + % numOfPorts] < 0) + pr_err("Warning:load cannot be negative\n"); + + pr_err("dettaching on deviceid %x portname %s\n", deviceid, + Q6Portnames[(deviceid - DALDEVICEID_VDEC_DEVICE_0) + % numOfPorts]); + +#ifdef TRACE_PORTS + printportsanddeviceids(); +#endif /*TRACE_PORTS*/ + } else { + /*single port mode, nothing to be done here*/ + } + +} + + +/** + * This method validates whether a new instance can be houred or not. + * + */ +static int vdec_rm_checkWithRm(struct vdec_data *vdecInstance, + unsigned int color_format) +{ + + unsigned int maxQ6load = 0;/* in the units of macro blocks per second */ + unsigned int currentq6load = 0; + struct videoStreamDetails *streamDetails = &vdecInstance->streamDetails; + + + + if (streamDetails->isThisTnail) { + if (totalTnailQ6load < MAX_TNAILS) { + + totalTnailQ6load++; + streamDetails->isTnailGranted = TRUE; + pr_info("%s: thumbnail granted %d\n", __func__, + totalTnailQ6load); + return 0; + + } else { + + pr_err("%s: thumbnails load max this instance cannot \ + be supported\n", __func__); + streamDetails->isTnailGranted = FALSE; + return -ENOSPC; + + } + } + + /* calculate the Q6 percentage instance would need */ + if ((streamDetails->fourcc == FOURCC_MPEG4) || + (streamDetails->fourcc == FOURCC_H264) || + (streamDetails->fourcc == FOURCC_DIVX) || + (streamDetails->fourcc == FOURCC_VC1) || + (streamDetails->fourcc == FOURCC_SPARK) || + (streamDetails->fourcc == FOURCC_H263) + ){ + + /* is yamato color format, + Rounds the H & W --> mutiple of 32 */ + if (color_format == YAMATO_COLOR_FORMAT) + maxQ6load = MAX_Q6_LOAD_YAMATO; + else + maxQ6load = MAX_Q6_LOAD; /* 720p */ + + } else if (streamDetails->fourcc == FOURCC_VP6) { + + maxQ6load = MAX_Q6_LOAD_VP6; /* FWVGA */ + + } else { + + pr_err("%s: unknown fourcc %d maxQ6load %u\n", __func__, + streamDetails->fourcc, maxQ6load); + return -EINVAL; + + } + + currentq6load = ((streamDetails->height)*(streamDetails->width) / 256); + currentq6load = ((currentq6load * 100)/maxQ6load); + if ((currentq6load+totalPlaybackQ6load) > 100) { + /* reject this instance */ + pr_err("%s: too much Q6load [cur+tot] = [%d + %d] = %d", + __func__, currentq6load, totalPlaybackQ6load, + (currentq6load+totalPlaybackQ6load)); + pr_err("rejecting the instance,[WxH] = [%d x %d],color_fmt=0x%x\n", + streamDetails->width, streamDetails->height, color_format); + pr_err("VDEC_fmt=%s\n", (char *)(&streamDetails->fourcc)); + streamDetails->Q6usage = 0; + return -ENOSPC; + } + + totalPlaybackQ6load += currentq6load; + streamDetails->Q6usage = currentq6load; + + pr_info("%s: adding a load [%d%%] bringing total Q6load to [%d%%]\n", + __func__, currentq6load, totalPlaybackQ6load); + + return 0; +} + + +static int vdec_initialize(struct vdec_data *vd, void *argp) +{ + struct vdec_config_sps vdec_cfg_sps; + struct vdec_init_cfg vi_cfg; + struct vdec_buf_req vdec_buf_req; + struct u8 *header; + int ret = 0; + + ret = copy_from_user(&vdec_cfg_sps, + &((struct vdec_init *)argp)->sps_cfg, + sizeof(vdec_cfg_sps)); + + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + vi_cfg.decode_done_evt = VDEC_ASYNCMSG_DECODE_DONE; + vi_cfg.reuse_frame_evt = VDEC_ASYNCMSG_REUSE_FRAME; + memcpy(&vi_cfg.cfg, &vdec_cfg_sps.cfg, sizeof(struct vdec_config)); + + /* + * restricting the max value of the seq header + */ + if (vdec_cfg_sps.seq.len > VDEC_MAX_SEQ_HEADER_SIZE) + vdec_cfg_sps.seq.len = VDEC_MAX_SEQ_HEADER_SIZE; + + header = kmalloc(vdec_cfg_sps.seq.len, GFP_KERNEL); + if (!header) { + pr_err("%s: kmalloc failed\n", __func__); + return -ENOMEM; + } + + ret = copy_from_user(header, + ((struct vdec_init *)argp)->sps_cfg.seq.header, + vdec_cfg_sps.seq.len); + + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + kfree(header); + return ret; + } + + TRACE("vi_cfg: handle=%p fourcc=0x%x w=%d h=%d order=%d notify_en=%d " + "vc1_rb=%d h264_sd=%d h264_nls=%d pp_flag=%d fruc_en=%d\n", + vd->vdec_handle, vi_cfg.cfg.fourcc, vi_cfg.cfg.width, + vi_cfg.cfg.height, vi_cfg.cfg.order, vi_cfg.cfg.notify_enable, + vi_cfg.cfg.vc1_rowbase, vi_cfg.cfg.h264_startcode_detect, + vi_cfg.cfg.h264_nal_len_size, vi_cfg.cfg.postproc_flag, + vi_cfg.cfg.fruc_enable); + + vd->streamDetails.height = vi_cfg.cfg.height; + vd->streamDetails.width = vi_cfg.cfg.width; + vd->streamDetails.fourcc = vi_cfg.cfg.fourcc; + if (FLAG_THUMBNAIL_MODE == vi_cfg.cfg.postproc_flag) + vd->streamDetails.isThisTnail = TRUE; + else + vd->streamDetails.isThisTnail = FALSE; + + mutex_lock(&vdec_rm_lock); + ret = vdec_rm_checkWithRm(vd, vi_cfg.cfg.color_format); + mutex_unlock(&vdec_rm_lock); + if (ret) + return ret; + + ret = dal_call_f13(vd->vdec_handle, VDEC_DALRPC_INITIALIZE, + &vi_cfg, sizeof(vi_cfg), + header, vdec_cfg_sps.seq.len, + &vdec_buf_req, sizeof(vdec_buf_req)); + + kfree(header); + + if (ret) + pr_err("%s: remote function failed (%d)\n", __func__, ret); + else + ret = copy_to_user(((struct vdec_init *)argp)->buf_req, + &vdec_buf_req, sizeof(vdec_buf_req)); + + vd->close_decode = 0; + return ret; +} + +static void vdec_rm_freeupResources(struct vdec_data *vdecInstance) +{ + struct videoStreamDetails *streamDetails = &vdecInstance->streamDetails; + + + + if ((streamDetails->isThisTnail) && + (streamDetails->isTnailGranted)) { + + totalTnailQ6load--; + pr_info("%s: Thumbnail released %d\n", __func__, + totalTnailQ6load); + + } else if (streamDetails->Q6usage > 0) { + + totalPlaybackQ6load -= streamDetails->Q6usage; + if (totalPlaybackQ6load < 0) + pr_err("Warning:Q6load cannot be negative\n"); + + pr_info("%s:Releasing [%d%%] of Q6load from a total of [%d%%]\n" + , __func__, streamDetails->Q6usage, + (streamDetails->Q6usage+totalPlaybackQ6load)); + } + +} + +static int vdec_setbuffers(struct vdec_data *vd, void *argp) +{ + struct vdec_buffer vmem; + struct vdec_mem_list *l; + unsigned long vstart; + unsigned long flags; + struct { + uint32_t size; + struct vdec_buf_info buf; + } rpc; + uint32_t res; + + int ret = 0; + + vd->mem_initialized = 0; + + ret = copy_from_user(&vmem, argp, sizeof(vmem)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + l = kzalloc(sizeof(struct vdec_mem_list), GFP_KERNEL); + if (!l) { + pr_err("%s: kzalloc failed!\n", __func__); + return -ENOMEM; + } + + l->mem.id = vmem.pmem_id; + l->mem.buf_type = vmem.buf.buf_type; + + ret = get_pmem_file(l->mem.id, &l->mem.phys_addr, &vstart, + &l->mem.len, &l->mem.file); + if (ret) { + pr_err("%s: get_pmem_fd failed\n", __func__); + goto err_get_pmem_file; + } + + TRACE("pmem_id=%d (phys=0x%08lx len=0x%lx) buftype=%d num_buf=%d " + "islast=%d src_id=%d offset=0x%08x size=0x%x\n", + vmem.pmem_id, l->mem.phys_addr, l->mem.len, + vmem.buf.buf_type, vmem.buf.num_buf, vmem.buf.islast, + vmem.buf.region.src_id, vmem.buf.region.offset, + vmem.buf.region.size); + + /* input buffers */ + if ((vmem.buf.region.offset + vmem.buf.region.size) > l->mem.len) { + pr_err("%s: invalid input buffer offset!\n", __func__); + ret = -EINVAL; + goto err_bad_offset; + + } + vmem.buf.region.offset += l->mem.phys_addr; + + rpc.size = sizeof(vmem.buf); + memcpy(&rpc.buf, &vmem.buf, sizeof(struct vdec_buf_info)); + + + ret = dal_call(vd->vdec_handle, VDEC_DALRPC_SETBUFFERS, 5, + &rpc, sizeof(rpc), &res, sizeof(res)); + + if (ret < 4) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + ret = -EIO; + goto err_dal_call; + } + + spin_lock_irqsave(&vd->vdec_mem_list_lock, flags); + list_add(&l->list, &vd->vdec_mem_list_head); + spin_unlock_irqrestore(&vd->vdec_mem_list_lock, flags); + + vd->mem_initialized = 1; + return ret; + +err_dal_call: +err_bad_offset: + put_pmem_file(l->mem.file); +err_get_pmem_file: + kfree(l); + return ret; +} + +static int vdec_queue(struct vdec_data *vd, void *argp) +{ + struct { + uint32_t size; + struct vdec_input_buf_info buf_info; + uint32_t osize; + } rpc; + struct vdec_mem_list *l; + struct { + uint32_t result; + uint32_t size; + struct vdec_queue_status status; + } rpc_res; + + u32 pmem_id; + int ret = 0; + + if (!vd->mem_initialized) { + pr_err("%s: memory is not being initialized!\n", __func__); + return -EPERM; + } + + ret = copy_from_user(&rpc.buf_info, + &((struct vdec_input_buf *)argp)->buffer, + sizeof(rpc.buf_info)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + ret = copy_from_user(&pmem_id, + &((struct vdec_input_buf *)argp)->pmem_id, + sizeof(u32)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + l = vdec_get_mem_from_list(vd, pmem_id, VDEC_BUFFER_TYPE_INPUT); + + if (NULL == l) { + pr_err("%s: not able to find the buffer from list\n", __func__); + return -EPERM; + } + + if ((rpc.buf_info.size + rpc.buf_info.offset) >= l->mem.len) { + pr_err("%s: invalid queue buffer offset!\n", __func__); + return -EINVAL; + } + + rpc.buf_info.offset += l->mem.phys_addr; + rpc.size = sizeof(struct vdec_input_buf_info); + rpc.osize = sizeof(struct vdec_queue_status); + + /* complete the writes to the buffer */ + wmb(); + ret = dal_call(vd->vdec_handle, VDEC_DALRPC_QUEUE, 8, + &rpc, sizeof(rpc), &rpc_res, sizeof(rpc_res)); + if (ret < 4) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + ret = -EIO; + } + return ret; +} + +static int vdec_reuse_framebuffer(struct vdec_data *vd, void *argp) +{ + u32 buf_id; + int ret = 0; + + ret = copy_from_user(&buf_id, argp, sizeof(buf_id)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + ret = dal_call_f0(vd->vdec_handle, VDEC_DALRPC_REUSEFRAMEBUFFER, + buf_id); + if (ret) + pr_err("%s: remote function failed (%d)\n", __func__, ret); + + return ret; +} + +static int vdec_flush(struct vdec_data *vd, void *argp) +{ + u32 flush_type; + int ret = 0; + + if (!vd->mem_initialized) { + pr_err("%s: memory is not being initialized!\n", __func__); + return -EPERM; + } + + ret = copy_from_user(&flush_type, argp, sizeof(flush_type)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + TRACE("flush_type=%d\n", flush_type); + ret = dal_call_f0(vd->vdec_handle, VDEC_DALRPC_FLUSH, flush_type); + if (ret) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + return ret; + } + + return ret; +} + +static int vdec_close(struct vdec_data *vd, void *argp) +{ + struct vdec_mem_list *l; + int ret = 0; + + pr_info("q6vdec_close()\n"); + vd->close_decode = 1; + wake_up(&vd->vdec_msg_evt); + + ret = dal_call_f0(vd->vdec_handle, DAL_OP_CLOSE, 0); + if (ret) + pr_err("%s: failed to close daldevice (%d)\n", __func__, ret); + + if (vd->mem_initialized) { + list_for_each_entry(l, &vd->vdec_mem_list_head, list) + put_pmem_file(l->mem.file); + } + + return ret; +} +static int vdec_getdecattributes(struct vdec_data *vd, void *argp) +{ + struct { + uint32_t status; + uint32_t size; + struct vdec_dec_attributes dec_attr; + } rpc; + uint32_t inp; + int ret = 0; + inp = sizeof(struct vdec_dec_attributes); + + ret = dal_call(vd->vdec_handle, VDEC_DALRPC_GETDECATTRIBUTES, 9, + &inp, sizeof(inp), &rpc, sizeof(rpc)); + if (ret < 4 || rpc.size != sizeof(struct vdec_dec_attributes)) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + ret = -EIO; + } else + ret = + copy_to_user(((struct vdec_dec_attributes *)argp), + &rpc.dec_attr, sizeof(rpc.dec_attr)); + return ret; +} + +static int vdec_freebuffers(struct vdec_data *vd, void *argp) +{ + struct vdec_buffer vmem; + struct vdec_mem_list *l; + struct { + uint32_t size; + struct vdec_buf_info buf; + } rpc; + uint32_t res; + + int ret = 0; + + if (!vd->mem_initialized) { + pr_err("%s: memory is not being initialized!\n", __func__); + return -EPERM; + } + + ret = copy_from_user(&vmem, argp, sizeof(vmem)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + l = vdec_get_mem_from_list(vd, vmem.pmem_id, vmem.buf.buf_type); + + if (NULL == l) { + pr_err("%s: not able to find the buffer from list\n", __func__); + return -EPERM; + } + + /* input buffers */ + if ((vmem.buf.region.offset + vmem.buf.region.size) > l->mem.len) { + pr_err("%s: invalid input buffer offset!\n", __func__); + return -EINVAL; + + } + vmem.buf.region.offset += l->mem.phys_addr; + + rpc.size = sizeof(vmem.buf); + memcpy(&rpc.buf, &vmem.buf, sizeof(struct vdec_buf_info)); + + ret = dal_call(vd->vdec_handle, VDEC_DALRPC_FREEBUFFERS, 5, + &rpc, sizeof(rpc), &res, sizeof(res)); + if (ret < 4) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + } + + return ret; +} + +static int vdec_getversion(struct vdec_data *vd, void *argp) +{ + struct vdec_version ver_info; + int ret = 0; + + ver_info.major = VDEC_GET_MAJOR_VERSION(VDEC_INTERFACE_VERSION); + ver_info.minor = VDEC_GET_MINOR_VERSION(VDEC_INTERFACE_VERSION); + + ret = copy_to_user(((struct vdec_version *)argp), + &ver_info, sizeof(ver_info)); + + return ret; + +} + +static long vdec_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct vdec_data *vd = file->private_data; + void __user *argp = (void __user *)arg; + int ret = 0; + + if (!vd->running) + return -EPERM; + + switch (cmd) { + case VDEC_IOCTL_INITIALIZE: + ret = vdec_initialize(vd, argp); + break; + + case VDEC_IOCTL_SETBUFFERS: + ret = vdec_setbuffers(vd, argp); + break; + + case VDEC_IOCTL_QUEUE: + TRACE("VDEC_IOCTL_QUEUE (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_queue(vd, argp); + break; + + case VDEC_IOCTL_REUSEFRAMEBUFFER: + TRACE("VDEC_IOCTL_REUSEFRAMEBUFFER (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_reuse_framebuffer(vd, argp); + break; + + case VDEC_IOCTL_FLUSH: + TRACE("IOCTL flush\n"); + ret = vdec_flush(vd, argp); + break; + + case VDEC_IOCTL_EOS: + TRACE("VDEC_IOCTL_EOS (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = dal_call_f0(vd->vdec_handle, VDEC_DALRPC_SIGEOFSTREAM, 0); + if (ret) + pr_err("%s: remote function failed (%d)\n", + __func__, ret); + break; + + case VDEC_IOCTL_GETMSG: + TRACE("VDEC_IOCTL_GETMSG (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + wait_event_interruptible(vd->vdec_msg_evt, + vdec_get_msg(vd, argp)); + + if (vd->close_decode) + ret = -EINTR; + else + /* order the reads from the buffer */ + rmb(); + break; + + case VDEC_IOCTL_CLOSE: + ret = vdec_close(vd, argp); + break; + + case VDEC_IOCTL_GETDECATTRIBUTES: + TRACE("VDEC_IOCTL_GETDECATTRIBUTES (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_getdecattributes(vd, argp); + + if (ret) + pr_err("%s: remote function failed (%d)\n", + __func__, ret); + break; + + case VDEC_IOCTL_FREEBUFFERS: + TRACE("VDEC_IOCTL_FREEBUFFERS (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_freebuffers(vd, argp); + + if (ret) + pr_err("%s: remote function failed (%d)\n", + __func__, ret); + break; + case VDEC_IOCTL_GETVERSION: + TRACE("VDEC_IOCTL_GETVERSION (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_getversion(vd, argp); + + if (ret) + pr_err("%s: remote function failed (%d)\n", + __func__, ret); + break; + case VDEC_IOCTL_GETPROPERTY: + TRACE("VDEC_IOCTL_GETPROPERTY (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_getproperty(vd, argp); + break; + case VDEC_IOCTL_SETPROPERTY: + TRACE("VDEC_IOCTL_SETPROPERTY (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + ret = vdec_setproperty(vd, argp); + break; + case VDEC_IOCTL_PERFORMANCE_CHANGE_REQ: + ret = vdec_performance_change_request(vd, argp); + break; + default: + pr_err("%s: invalid ioctl!\n", __func__); + ret = -EINVAL; + break; + } + + TRACE("ioctl done (pid=%d tid=%d)\n", + current->group_leader->pid, current->pid); + + return ret; +} + +static void vdec_dcdone_handler(struct vdec_data *vd, void *frame, + uint32_t frame_size) +{ + struct vdec_msg msg; + struct vdec_mem_list *l; + unsigned long flags; + int found = 0; + + if (frame_size < sizeof(struct vdec_frame_info)) { + pr_warning("%s: msg size mismatch %d != %d\n", __func__, + frame_size, sizeof(struct vdec_frame_info)); + return; + } + + memcpy(&msg.vfr_info, (struct vdec_frame_info *)frame, + sizeof(struct vdec_frame_info)); + + if (msg.vfr_info.status == VDEC_FRAME_DECODE_OK) { + spin_lock_irqsave(&vd->vdec_mem_list_lock, flags); + list_for_each_entry(l, &vd->vdec_mem_list_head, list) { + if ((l->mem.buf_type == VDEC_BUFFER_TYPE_OUTPUT) && + (msg.vfr_info.offset >= l->mem.phys_addr) && + (msg.vfr_info.offset < + (l->mem.phys_addr + l->mem.len))) { + found = 1; + msg.vfr_info.offset -= l->mem.phys_addr; + msg.vfr_info.data2 = l->mem.id; + break; + } + } + spin_unlock_irqrestore(&vd->vdec_mem_list_lock, flags); + } + + if (found || (msg.vfr_info.status != VDEC_FRAME_DECODE_OK)) { + msg.id = VDEC_MSG_FRAMEDONE; + vdec_put_msg(vd, &msg); + } else { + pr_err("%s: invalid phys addr = 0x%x\n", + __func__, msg.vfr_info.offset); + } + +} + +static void vdec_reuseibuf_handler(struct vdec_data *vd, void *bufstat, + uint32_t bufstat_size) +{ + struct vdec_buffer_status *vdec_bufstat; + struct vdec_msg msg; + + /* TODO: how do we signal the client? If they are waiting on a + * message in an ioctl, they may block forever */ + if (bufstat_size != sizeof(struct vdec_buffer_status)) { + pr_warning("%s: msg size mismatch %d != %d\n", __func__, + bufstat_size, sizeof(struct vdec_buffer_status)); + return; + } + vdec_bufstat = (struct vdec_buffer_status *)bufstat; + msg.id = VDEC_MSG_REUSEINPUTBUFFER; + msg.buf_id = vdec_bufstat->data; + vdec_put_msg(vd, &msg); +} + +static void callback(void *data, int len, void *cookie) +{ + struct vdec_data *vd = (struct vdec_data *)cookie; + uint32_t *tmp = (uint32_t *) data; + + if (!vd->mem_initialized) { + pr_err("%s:memory not initialize but callback called!\n", + __func__); + return; + } + + TRACE("vdec_async: tmp=0x%08x 0x%08x 0x%08x\n", tmp[0], tmp[1], tmp[2]); + switch (tmp[0]) { + case VDEC_ASYNCMSG_DECODE_DONE: + vdec_dcdone_handler(vd, &tmp[3], tmp[2]); + break; + case VDEC_ASYNCMSG_REUSE_FRAME: + vdec_reuseibuf_handler(vd, &tmp[3], tmp[2]); + break; + default: + pr_err("%s: Unknown async message from DSP id=0x%08x sz=%u\n", + __func__, tmp[0], tmp[2]); + } +} + +static int vdec_open(struct inode *inode, struct file *file) +{ + int ret; + int i; + struct vdec_msg_list *l; + struct vdec_data *vd; + struct dal_info version_info; + char *portname = NULL; + + pr_info("q6vdec_open()\n"); + mutex_lock(&vdec_ref_lock); + if (ref_cnt >= MAX_SUPPORTED_INSTANCES) { + pr_err("%s: Max allowed instances exceeded \n", __func__); + mutex_unlock(&vdec_ref_lock); + return -EBUSY; + } + ref_cnt++; + mutex_unlock(&vdec_ref_lock); + + vd = kmalloc(sizeof(struct vdec_data), GFP_KERNEL); + if (!vd) { + pr_err("%s: kmalloc failed\n", __func__); + ret = -ENOMEM; + goto vdec_open_err_handle_vd; + } + file->private_data = vd; + + vd->mem_initialized = 0; + INIT_LIST_HEAD(&vd->vdec_msg_list_head); + INIT_LIST_HEAD(&vd->vdec_msg_list_free); + INIT_LIST_HEAD(&vd->vdec_mem_list_head); + init_waitqueue_head(&vd->vdec_msg_evt); + + spin_lock_init(&vd->vdec_list_lock); + spin_lock_init(&vd->vdec_mem_list_lock); + for (i = 0; i < VDEC_MSG_MAX; i++) { + l = kzalloc(sizeof(struct vdec_msg_list), GFP_KERNEL); + if (!l) { + pr_err("%s: kzalloc failed!\n", __func__); + ret = -ENOMEM; + goto vdec_open_err_handle_list; + } + list_add(&l->list, &vd->vdec_msg_list_free); + } + + memset(&vd->streamDetails, 0, sizeof(struct videoStreamDetails)); + + mutex_lock(&vdec_ref_lock); + vdec_get_next_portanddevid(&vd->Q6deviceId, &portname); + mutex_unlock(&vdec_ref_lock); + + if ((0 == vd->Q6deviceId) || (NULL == portname)) { + pr_err("%s: FATAL error portname %s or deviceId %d not picked properly\n", + __func__, portname, vd->Q6deviceId); + ret = -EIO; + goto vdec_open_err_handle_list; + } else { + pr_err("attaching on deviceid %x portname %s\n", + vd->Q6deviceId, portname); + vd->vdec_handle = dal_attach(vd->Q6deviceId, + portname, 1, callback, vd); + } + + if (!vd->vdec_handle) { + pr_err("%s: failed to attach\n", __func__); + ret = -EIO; + goto vdec_open_err_handle_list; + } + ret = dal_call_f9(vd->vdec_handle, DAL_OP_INFO, + &version_info, sizeof(struct dal_info)); + + if (ret) { + pr_err("%s: failed to get version \n", __func__); + goto vdec_open_err_handle_version; + } + + TRACE("q6vdec_open() interface version 0x%x\n", version_info.version); + if (vdec_check_version(VDEC_INTERFACE_VERSION, + version_info.version)) { + pr_err("%s: driver version mismatch !\n", __func__); + goto vdec_open_err_handle_version; + } + + vd->running = 1; + prevent_sleep(); + + return 0; +vdec_open_err_handle_version: + dal_detach(vd->vdec_handle); +vdec_open_err_handle_list: + { + struct vdec_msg_list *l, *n; + list_for_each_entry_safe(l, n, &vd->vdec_msg_list_free, list) { + list_del(&l->list); + kfree(l); + } + } +vdec_open_err_handle_vd: + mutex_lock(&vdec_ref_lock); + vdec_freeup_portanddevid(vd->Q6deviceId); + ref_cnt--; + mutex_unlock(&vdec_ref_lock); + kfree(vd); + return ret; +} + +static int vdec_release(struct inode *inode, struct file *file) +{ + int ret; + struct vdec_msg_list *l, *n; + struct vdec_mem_list *m, *k; + struct vdec_data *vd = file->private_data; + + vd->running = 0; + wake_up_all(&vd->vdec_msg_evt); + + if (!vd->close_decode) + vdec_close(vd, NULL); + + ret = dal_detach(vd->vdec_handle); + if (ret) + printk(KERN_INFO "%s: failed to detach (%d)\n", __func__, ret); + + list_for_each_entry_safe(l, n, &vd->vdec_msg_list_free, list) { + list_del(&l->list); + kfree(l); + } + + list_for_each_entry_safe(l, n, &vd->vdec_msg_list_head, list) { + list_del(&l->list); + kfree(l); + } + + list_for_each_entry_safe(m, k, &vd->vdec_mem_list_head, list) { + list_del(&m->list); + kfree(m); + } + mutex_lock(&vdec_ref_lock); + BUG_ON(ref_cnt <= 0); + ref_cnt--; + vdec_freeup_portanddevid(vd->Q6deviceId); + mutex_unlock(&vdec_ref_lock); + + mutex_lock(&vdec_rm_lock); + vdec_rm_freeupResources(vd); + mutex_unlock(&vdec_rm_lock); + + + kfree(vd); + allow_sleep(); + return 0; +} + +static const struct file_operations vdec_fops = { + .owner = THIS_MODULE, + .open = vdec_open, + .release = vdec_release, + .unlocked_ioctl = vdec_ioctl, +}; + +static int __init vdec_init(void) +{ + struct device *class_dev; + int rc = 0; + + wake_lock_init(&idlelock, WAKE_LOCK_IDLE, "vdec_idle"); + wake_lock_init(&wakelock, WAKE_LOCK_SUSPEND, "vdec_suspend"); + + rc = alloc_chrdev_region(&vdec_device_no, 0, 1, "vdec"); + if (rc < 0) { + pr_err("%s: alloc_chrdev_region failed %d\n", __func__, rc); + return rc; + } + + driver_class = class_create(THIS_MODULE, "vdec"); + if (IS_ERR(driver_class)) { + rc = -ENOMEM; + pr_err("%s: class_create failed %d\n", __func__, rc); + goto vdec_init_err_unregister_chrdev_region; + } + class_dev = device_create(driver_class, NULL, + vdec_device_no, NULL, "vdec"); + if (!class_dev) { + pr_err("%s: class_device_create failed %d\n", __func__, rc); + rc = -ENOMEM; + goto vdec_init_err_class_destroy; + } + + cdev_init(&vdec_cdev, &vdec_fops); + vdec_cdev.owner = THIS_MODULE; + rc = cdev_add(&vdec_cdev, MKDEV(MAJOR(vdec_device_no), 0), 1); + + if (rc < 0) { + pr_err("%s: cdev_add failed %d\n", __func__, rc); + goto vdec_init_err_class_device_destroy; + } + + memset(&deviceIdRegistry, 0, sizeof(deviceIdRegistry)); + memset(&loadOnPorts, 0, sizeof(loadOnPorts)); + numOfPorts = 0; + + return 0; + +vdec_init_err_class_device_destroy: + device_destroy(driver_class, vdec_device_no); +vdec_init_err_class_destroy: + class_destroy(driver_class); +vdec_init_err_unregister_chrdev_region: + unregister_chrdev_region(vdec_device_no, 1); + return rc; +} + +static void __exit vdec_exit(void) +{ + device_destroy(driver_class, vdec_device_no); + class_destroy(driver_class); + unregister_chrdev_region(vdec_device_no, 1); +} + +MODULE_DESCRIPTION("video decoder driver for QSD platform"); +MODULE_VERSION("2.00"); + +module_init(vdec_init); +module_exit(vdec_exit); diff --git a/arch/arm/mach-msm/qdsp6/msm_q6venc.c b/arch/arm/mach-msm/qdsp6/msm_q6venc.c new file mode 100644 index 00000000000..bd5d3f60b38 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/msm_q6venc.c @@ -0,0 +1,1195 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dal.h" + +#define DALDEVICEID_VENC_DEVICE 0x0200002D +#define DALDEVICEID_VENC_PORTNAME "DAL_AQ_VID" + +#define VENC_NAME "q6venc" +#define VENC_MSG_MAX 128 + +#define VENC_INTERFACE_VERSION 0x00020000 +#define MAJOR_MASK 0xFFFF0000 +#define MINOR_MASK 0x0000FFFF +#define VENC_GET_MAJOR_VERSION(version) ((version & MAJOR_MASK)>>16) +#define VENC_GET_MINOR_VERSION(version) (version & MINOR_MASK) + +enum { + VENC_BUFFER_TYPE_INPUT, + VENC_BUFFER_TYPE_OUTPUT, + VENC_BUFFER_TYPE_QDSP6, + VENC_BUFFER_TYPE_HDR +}; +enum { + VENC_DALRPC_GET_SYNTAX_HEADER = DAL_OP_FIRST_DEVICE_API, + VENC_DALRPC_UPDATE_INTRA_REFRESH, + VENC_DALRPC_UPDATE_FRAME_RATE, + VENC_DALRPC_UPDATE_BITRATE, + VENC_DALRPC_UPDATE_QP_RANGE, + VENC_DALRPC_UPDATE_INTRA_PERIOD, + VENC_DALRPC_REQUEST_IFRAME, + VENC_DALRPC_START, + VENC_DALRPC_STOP, + VENC_DALRPC_SUSPEND, + VENC_DALRPC_RESUME, + VENC_DALRPC_FLUSH, + VENC_DALRPC_QUEUE_INPUT, + VENC_DALRPC_QUEUE_OUTPUT +}; +struct venc_input_payload { + u32 data; +}; +struct venc_output_payload { + u32 size; + long long time_stamp; + u32 flags; + u32 data; + u32 client_data_from_input; +}; +union venc_payload { + struct venc_input_payload input_payload; + struct venc_output_payload output_payload; +}; +struct venc_msg_type { + u32 event; + u32 status; + union venc_payload payload; +}; +struct venc_input_buf { + struct venc_buf_type yuv_buf; + u32 data_size; + long long time_stamp; + u32 flags; + u32 dvs_offsetx; + u32 dvs_offsety; + u32 client_data; + u32 op_client_data; +}; +struct venc_output_buf { + struct venc_buf_type bit_stream_buf; + u32 client_data; +}; + +struct venc_msg_list { + struct list_head list; + struct venc_msg msg_data; +}; +struct venc_buf { + int fd; + u32 src; + u32 offset; + u32 size; + u32 btype; + unsigned long paddr; + struct file *file; +}; +struct venc_pmem_list { + struct list_head list; + struct venc_buf buf; +}; +struct venc_dev { + bool is_active; + bool pmem_freed; + enum venc_state_type state; + struct list_head venc_msg_list_head; + struct list_head venc_msg_list_free; + spinlock_t venc_msg_list_lock; + struct list_head venc_pmem_list_head; + spinlock_t venc_pmem_list_lock; + struct dal_client *q6_handle; + wait_queue_head_t venc_msg_evt; + struct device *class_devp; +}; + +#define DEBUG_VENC 0 +#if DEBUG_VENC +#define TRACE(fmt, x...) \ + do { pr_debug("%s:%d " fmt, __func__, __LINE__, ##x); } while (0) +#else +#define TRACE(fmt, x...) do { } while (0) +#endif + +static struct cdev cdev; +static dev_t venc_dev_num; +static struct class *venc_class; +static struct venc_dev *venc_device_p; +static int venc_ref; + +static DEFINE_MUTEX(idlecount_lock); +static int idlecount; +static struct wake_lock wakelock; +static struct wake_lock idlelock; + +static void prevent_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (++idlecount == 1) { + wake_lock(&idlelock); + wake_lock(&wakelock); + } + mutex_unlock(&idlecount_lock); +} + +static void allow_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (--idlecount == 0) { + wake_unlock(&idlelock); + wake_unlock(&wakelock); + } + mutex_unlock(&idlecount_lock); +} + +static inline int venc_check_version(u32 client, u32 server) +{ + int ret = -EINVAL; + + if ((VENC_GET_MAJOR_VERSION(client) == VENC_GET_MAJOR_VERSION(server)) + && (VENC_GET_MINOR_VERSION(client) <= + VENC_GET_MINOR_VERSION(server))) + ret = 0; + + return ret; +} + +static int venc_get_msg(struct venc_dev *dvenc, void *msg) +{ + struct venc_msg_list *l; + unsigned long flags; + int ret = 0; + struct venc_msg qdsp_msg; + + if (!dvenc->is_active) + return -EPERM; + spin_lock_irqsave(&dvenc->venc_msg_list_lock, flags); + list_for_each_entry_reverse(l, &dvenc->venc_msg_list_head, list) { + memcpy(&qdsp_msg, &l->msg_data, sizeof(struct venc_msg)); + list_del(&l->list); + list_add(&l->list, &dvenc->venc_msg_list_free); + ret = 1; + break; + } + spin_unlock_irqrestore(&dvenc->venc_msg_list_lock, flags); + if (copy_to_user(msg, &qdsp_msg, sizeof(struct venc_msg))) + pr_err("%s failed to copy_to_user\n", __func__); + return ret; +} + +static void venc_put_msg(struct venc_dev *dvenc, struct venc_msg *msg) +{ + struct venc_msg_list *l; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&dvenc->venc_msg_list_lock, flags); + list_for_each_entry(l, &dvenc->venc_msg_list_free, list) { + memcpy(&l->msg_data, msg, sizeof(struct venc_msg)); + list_del(&l->list); + list_add(&l->list, &dvenc->venc_msg_list_head); + found = 1; + break; + } + spin_unlock_irqrestore(&dvenc->venc_msg_list_lock, flags); + if (found) + wake_up(&dvenc->venc_msg_evt); + else + pr_err("%s: failed to find a free node\n", __func__); + +} + +static struct venc_pmem_list *venc_add_pmem_to_list(struct venc_dev *dvenc, + struct venc_pmem *mptr, + u32 btype) +{ + int ret = 0; + unsigned long flags; + unsigned long len; + unsigned long vaddr; + struct venc_pmem_list *plist = NULL; + + plist = kzalloc(sizeof(struct venc_pmem_list), GFP_KERNEL); + if (!plist) { + pr_err("%s: kzalloc failed\n", __func__); + return NULL; + } + + ret = get_pmem_file(mptr->fd, &(plist->buf.paddr), + &vaddr, &len, &(plist->buf.file)); + if (ret) { + pr_err("%s: get_pmem_file failed for fd=%d offset=%d\n", + __func__, mptr->fd, mptr->offset); + goto err_venc_add_pmem; + } else if (mptr->offset >= len) { + pr_err("%s: invalid offset (%d > %ld) for fd=%d\n", + __func__, mptr->offset, len, mptr->fd); + ret = -EINVAL; + goto err_venc_get_pmem; + } + + plist->buf.fd = mptr->fd; + plist->buf.paddr += mptr->offset; + plist->buf.size = mptr->size; + plist->buf.btype = btype; + plist->buf.offset = mptr->offset; + plist->buf.src = mptr->src; + + spin_lock_irqsave(&dvenc->venc_pmem_list_lock, flags); + list_add(&plist->list, &dvenc->venc_pmem_list_head); + spin_unlock_irqrestore(&dvenc->venc_pmem_list_lock, flags); + return plist; + +err_venc_get_pmem: + put_pmem_file(plist->buf.file); +err_venc_add_pmem: + kfree(plist); + return NULL; +} + +static struct venc_pmem_list *venc_get_pmem_from_list( + struct venc_dev *dvenc, u32 pmem_fd, + u32 offset, u32 btype) +{ + struct venc_pmem_list *plist; + unsigned long flags; + struct file *file; + int found = 0; + + file = fget(pmem_fd); + if (!file) { + pr_err("%s: invalid encoder buffer fd(%d)\n", __func__, + pmem_fd); + return NULL; + } + spin_lock_irqsave(&dvenc->venc_pmem_list_lock, flags); + list_for_each_entry(plist, &dvenc->venc_pmem_list_head, list) { + if (plist->buf.btype == btype && plist->buf.file == file && + plist->buf.offset == offset) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&dvenc->venc_pmem_list_lock, flags); + fput(file); + if (found) + return plist; + + else + return NULL; +} + +static int venc_set_buffer(struct venc_dev *dvenc, void *argp, + u32 btype) +{ + struct venc_pmem pmem; + struct venc_pmem_list *plist; + int ret = 0; + + ret = copy_from_user(&pmem, argp, sizeof(pmem)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + plist = venc_add_pmem_to_list(dvenc, &pmem, btype); + if (plist == NULL) { + pr_err("%s: buffer add_to_pmem_list failed\n", + __func__); + return -EPERM; + } + return ret; +} + +static int venc_assign_q6_buffers(struct venc_dev *dvenc, + struct venc_buffers *pbufs, + struct venc_nonio_buf_config *pcfg) +{ + int ret = 0; + struct venc_pmem_list *plist; + + plist = venc_add_pmem_to_list(dvenc, &(pbufs->recon_buf[0]), + VENC_BUFFER_TYPE_QDSP6); + if (plist == NULL) { + pr_err("%s: recon_buf0 failed to add_to_pmem_list\n", + __func__); + return -EPERM; + } + pcfg->recon_buf1.region = pbufs->recon_buf[0].src; + pcfg->recon_buf1.phys = plist->buf.paddr; + pcfg->recon_buf1.size = plist->buf.size; + pcfg->recon_buf1.offset = 0; + + plist = venc_add_pmem_to_list(dvenc, &(pbufs->recon_buf[1]), + VENC_BUFFER_TYPE_QDSP6); + if (plist == NULL) { + pr_err("%s: recons_buf1 failed to add_to_pmem_list\n", + __func__); + return -EPERM; + } + pcfg->recon_buf2.region = pbufs->recon_buf[1].src; + pcfg->recon_buf2.phys = plist->buf.paddr; + pcfg->recon_buf2.size = plist->buf.size; + pcfg->recon_buf2.offset = 0; + + plist = venc_add_pmem_to_list(dvenc, &(pbufs->wb_buf), + VENC_BUFFER_TYPE_QDSP6); + if (plist == NULL) { + pr_err("%s: wb_buf failed to add_to_pmem_list\n", + __func__); + return -EPERM; + } + pcfg->wb_buf.region = pbufs->wb_buf.src; + pcfg->wb_buf.phys = plist->buf.paddr; + pcfg->wb_buf.size = plist->buf.size; + pcfg->wb_buf.offset = 0; + + plist = venc_add_pmem_to_list(dvenc, &(pbufs->cmd_buf), + VENC_BUFFER_TYPE_QDSP6); + if (plist == NULL) { + pr_err("%s: cmd_buf failed to add_to_pmem_list\n", + __func__); + return -EPERM; + } + pcfg->cmd_buf.region = pbufs->cmd_buf.src; + pcfg->cmd_buf.phys = plist->buf.paddr; + pcfg->cmd_buf.size = plist->buf.size; + pcfg->cmd_buf.offset = 0; + + plist = venc_add_pmem_to_list(dvenc, &(pbufs->vlc_buf), + VENC_BUFFER_TYPE_QDSP6); + if (plist == NULL) { + pr_err("%s: vlc_buf failed to add_to_pmem_list" + " failed\n", __func__); + return -EPERM; + } + pcfg->vlc_buf.region = pbufs->vlc_buf.src; + pcfg->vlc_buf.phys = plist->buf.paddr; + pcfg->vlc_buf.size = plist->buf.size; + pcfg->vlc_buf.offset = 0; + + return ret; +} + +static int venc_start(struct venc_dev *dvenc, void *argp) +{ + int ret = 0; + struct venc_q6_config q6_config; + struct venc_init_config vconfig; + + dvenc->state = VENC_STATE_START; + ret = copy_from_user(&vconfig, argp, sizeof(struct venc_init_config)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + memcpy(&q6_config, &(vconfig.q6_config), sizeof(q6_config)); + ret = venc_assign_q6_buffers(dvenc, &(vconfig.q6_bufs), + &(q6_config.buf_params)); + if (ret != 0) { + pr_err("%s: assign_q6_buffers failed\n", __func__); + return -EPERM; + } + + q6_config.callback_event = dvenc->q6_handle; + TRACE("%s: parameters: handle:%p, config:%p, callback:%p \n", __func__, + dvenc->q6_handle, &q6_config, q6_config.callback_event); + TRACE("%s: parameters:recon1:0x%x, recon2:0x%x," + " wb_buf:0x%x, cmd:0x%x, vlc:0x%x\n", __func__, + q6_config.buf_params.recon_buf1.phys, + q6_config.buf_params.recon_buf2.phys, + q6_config.buf_params.wb_buf.phys, + q6_config.buf_params.cmd_buf.phys, + q6_config.buf_params.vlc_buf.phys); + TRACE("%s: size of param:%d \n", __func__, sizeof(q6_config)); + ret = dal_call_f5(dvenc->q6_handle, VENC_DALRPC_START, &q6_config, + sizeof(q6_config)); + if (ret != 0) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + return ret; + } + return ret; +} + +static int venc_encode_frame(struct venc_dev *dvenc, void *argp) +{ + int ret = 0; + struct venc_pmem buf; + struct venc_input_buf q6_input; + struct venc_pmem_list *plist; + struct venc_buffer input; + + ret = copy_from_user(&input, argp, sizeof(struct venc_buffer)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + ret = copy_from_user(&buf, + ((struct venc_buffer *)argp)->ptr_buffer, + sizeof(struct venc_pmem)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + plist = venc_get_pmem_from_list(dvenc, buf.fd, buf.offset, + VENC_BUFFER_TYPE_INPUT); + if (NULL == plist) { + plist = venc_add_pmem_to_list(dvenc, &buf, + VENC_BUFFER_TYPE_INPUT); + if (plist == NULL) { + pr_err("%s: buffer add_to_pmem_list failed\n", + __func__); + return -EPERM; + } + } + + q6_input.flags = 0; + if (input.flags & VENC_FLAG_EOS) + q6_input.flags |= 0x00000001; + q6_input.yuv_buf.region = plist->buf.src; + q6_input.yuv_buf.phys = plist->buf.paddr; + q6_input.yuv_buf.size = plist->buf.size; + q6_input.yuv_buf.offset = 0; + q6_input.data_size = plist->buf.size; + q6_input.client_data = (u32)input.client_data; + q6_input.time_stamp = input.time_stamp; + q6_input.dvs_offsetx = 0; + q6_input.dvs_offsety = 0; + + TRACE("Pushing down input phys=0x%x fd= %d, client_data: 0x%x," + " time_stamp:%lld \n", q6_input.yuv_buf.phys, plist->buf.fd, + input.client_data, input.time_stamp); + ret = dal_call_f5(dvenc->q6_handle, VENC_DALRPC_QUEUE_INPUT, + &q6_input, sizeof(q6_input)); + + if (ret != 0) + pr_err("%s: Q6 queue_input failed (%d)\n", __func__, + (int)ret); + return ret; +} + +static int venc_fill_output(struct venc_dev *dvenc, void *argp) +{ + int ret = 0; + struct venc_pmem buf; + struct venc_output_buf q6_output; + struct venc_pmem_list *plist; + struct venc_buffer output; + + ret = copy_from_user(&output, argp, sizeof(struct venc_buffer)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + ret = copy_from_user(&buf, + ((struct venc_buffer *)argp)->ptr_buffer, + sizeof(struct venc_pmem)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + plist = venc_get_pmem_from_list(dvenc, buf.fd, buf.offset, + VENC_BUFFER_TYPE_OUTPUT); + if (NULL == plist) { + plist = venc_add_pmem_to_list(dvenc, &buf, + VENC_BUFFER_TYPE_OUTPUT); + if (NULL == plist) { + pr_err("%s: output buffer failed to add_to_pmem_list" + "\n", __func__); + return -EPERM; + } + } + q6_output.bit_stream_buf.region = plist->buf.src; + q6_output.bit_stream_buf.phys = (u32)plist->buf.paddr; + q6_output.bit_stream_buf.size = plist->buf.size; + q6_output.bit_stream_buf.offset = 0; + q6_output.client_data = (u32)output.client_data; + ret = + dal_call_f5(dvenc->q6_handle, VENC_DALRPC_QUEUE_OUTPUT, &q6_output, + sizeof(q6_output)); + if (ret != 0) + pr_err("%s: remote function failed (%d)\n", __func__, ret); + return ret; +} + +static int venc_stop(struct venc_dev *dvenc) +{ + int ret = 0; + struct venc_msg msg; + + ret = dal_call_f0(dvenc->q6_handle, VENC_DALRPC_STOP, 1); + if (ret) { + pr_err("%s: remote runction failed (%d)\n", __func__, ret); + msg.msg_code = VENC_MSG_STOP; + msg.msg_data_size = 0; + msg.status_code = VENC_S_EFAIL; + venc_put_msg(dvenc, &msg); + } + return ret; +} + +static int venc_pause(struct venc_dev *dvenc) +{ + int ret = 0; + struct venc_msg msg; + + ret = dal_call_f0(dvenc->q6_handle, VENC_DALRPC_SUSPEND, 1); + if (ret) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + msg.msg_code = VENC_MSG_PAUSE; + msg.status_code = VENC_S_EFAIL; + msg.msg_data_size = 0; + venc_put_msg(dvenc, &msg); + } + return ret; +} + +static int venc_resume(struct venc_dev *dvenc) +{ + int ret = 0; + struct venc_msg msg; + + ret = dal_call_f0(dvenc->q6_handle, VENC_DALRPC_RESUME, 1); + if (ret) { + pr_err("%s: remote function failed (%d)\n", __func__, ret); + msg.msg_code = VENC_MSG_RESUME; + msg.msg_data_size = 0; + msg.status_code = VENC_S_EFAIL; + venc_put_msg(dvenc, &msg); + } + return ret; +} + +static int venc_flush(struct venc_dev *dvenc, void *argp) +{ + int ret = 0; + struct venc_msg msg; + union venc_msg_data smsg; + int status = VENC_S_SUCCESS; + struct venc_buffer_flush flush; + + if (copy_from_user(&flush, argp, sizeof(struct venc_buffer_flush))) + return -EFAULT; + if (flush.flush_mode == VENC_FLUSH_ALL) { + ret = dal_call_f0(dvenc->q6_handle, VENC_DALRPC_FLUSH, 1); + if (ret) + status = VENC_S_EFAIL; + } else + status = VENC_S_ENOTSUPP; + + if (status != VENC_S_SUCCESS) { + if ((flush.flush_mode == VENC_FLUSH_INPUT) || + (flush.flush_mode == VENC_FLUSH_ALL)) { + smsg.flush_ret.flush_mode = VENC_FLUSH_INPUT; + msg.msg_data = smsg; + msg.status_code = status; + msg.msg_code = VENC_MSG_FLUSH; + msg.msg_data_size = sizeof(union venc_msg_data); + venc_put_msg(dvenc, &msg); + } + if (flush.flush_mode == VENC_FLUSH_OUTPUT || + (flush.flush_mode == VENC_FLUSH_ALL)) { + smsg.flush_ret.flush_mode = VENC_FLUSH_OUTPUT; + msg.msg_data = smsg; + msg.status_code = status; + msg.msg_code = VENC_MSG_FLUSH; + msg.msg_data_size = sizeof(union venc_msg_data); + venc_put_msg(dvenc, &msg); + } + return -EIO; + } + return ret; +} + +static int venc_get_sequence_hdr(struct venc_dev *dvenc, void *argp) +{ + pr_err("%s not supported\n", __func__); + return -EIO; +} + +static int venc_set_qp_range(struct venc_dev *dvenc, void *argp) +{ + int ret = 0; + struct venc_qp_range qp; + + ret = copy_from_user(&qp, argp, sizeof(struct venc_qp_range)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + + if (dvenc->state == VENC_STATE_START || + dvenc->state == VENC_STATE_PAUSE) { + ret = + dal_call_f5(dvenc->q6_handle, VENC_DALRPC_UPDATE_QP_RANGE, + &qp, sizeof(struct venc_qp_range)); + if (ret) { + pr_err("%s: remote function failed (%d) \n", __func__, + ret); + return ret; + } + } + return ret; +} + +static int venc_set_intra_period(struct venc_dev *dvenc, void *argp) +{ + int ret = 0; + u32 pnum = 0; + + ret = copy_from_user(&pnum, argp, sizeof(int)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + if (dvenc->state == VENC_STATE_START || + dvenc->state == VENC_STATE_PAUSE) { + ret = dal_call_f0(dvenc->q6_handle, + VENC_DALRPC_UPDATE_INTRA_PERIOD, pnum); + if (ret) + pr_err("%s: remote function failed (%d)\n", __func__, + ret); + } + return ret; +} + +static int venc_set_intra_refresh(struct venc_dev *dvenc, void *argp) +{ + int ret = 0; + u32 mb_num = 0; + + ret = copy_from_user(&mb_num, argp, sizeof(int)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + if (dvenc->state == VENC_STATE_START || + dvenc->state == VENC_STATE_PAUSE) { + ret = dal_call_f0(dvenc->q6_handle, + VENC_DALRPC_UPDATE_INTRA_REFRESH, mb_num); + if (ret) + pr_err("%s: remote function failed (%d)\n", __func__, + ret); + } + return ret; +} + +static int venc_set_frame_rate(struct venc_dev *dvenc, void *argp) +{ + int ret = 0; + struct venc_frame_rate pdata; + ret = copy_from_user(&pdata, argp, sizeof(struct venc_frame_rate)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + if (dvenc->state == VENC_STATE_START || + dvenc->state == VENC_STATE_PAUSE) { + ret = dal_call_f5(dvenc->q6_handle, + VENC_DALRPC_UPDATE_FRAME_RATE, + (void *)&(pdata), + sizeof(struct venc_frame_rate)); + if (ret) + pr_err("%s: remote function failed (%d)\n", __func__, + ret); + } + return ret; +} + +static int venc_set_target_bitrate(struct venc_dev *dvenc, void *argp) +{ + int ret = 0; + u32 pdata = 0; + + ret = copy_from_user(&pdata, argp, sizeof(int)); + if (ret) { + pr_err("%s: copy_from_user failed\n", __func__); + return ret; + } + if (dvenc->state == VENC_STATE_START || + dvenc->state == VENC_STATE_PAUSE) { + ret = dal_call_f0(dvenc->q6_handle, + VENC_DALRPC_UPDATE_BITRATE, pdata); + if (ret) + pr_err("%s: remote function failed (%d)\n", __func__, + ret); + } + return ret; +} + +static int venc_request_iframe(struct venc_dev *dvenc) +{ + int ret = 0; + + if (dvenc->state != VENC_STATE_START) + return -EINVAL; + + ret = dal_call_f0(dvenc->q6_handle, VENC_DALRPC_REQUEST_IFRAME, 1); + if (ret) + pr_err("%s: remote function failed (%d)\n", __func__, ret); + return ret; +} + +static int venc_stop_read_msg(struct venc_dev *dvenc) +{ + struct venc_msg msg; + int ret = 0; + + msg.status_code = 0; + msg.msg_code = VENC_MSG_STOP_READING_MSG; + msg.msg_data_size = 0; + venc_put_msg(dvenc, &msg); + return ret; +} + +static int venc_q6_stop(struct venc_dev *dvenc) +{ + int ret = 0; + struct venc_pmem_list *plist; + unsigned long flags; + + wake_up(&dvenc->venc_msg_evt); + spin_lock_irqsave(&dvenc->venc_pmem_list_lock, flags); + if (!dvenc->pmem_freed) { + list_for_each_entry(plist, &dvenc->venc_pmem_list_head, list) + put_pmem_file(plist->buf.file); + dvenc->pmem_freed = 1; + } + spin_unlock_irqrestore(&dvenc->venc_pmem_list_lock, flags); + + dvenc->state = VENC_STATE_STOP; + return ret; +} + +static int venc_translate_error(enum venc_status_code q6_status) +{ + int ret = 0; + + switch (q6_status) { + case VENC_STATUS_SUCCESS: + ret = VENC_S_SUCCESS; + break; + case VENC_STATUS_ERROR: + ret = VENC_S_EFAIL; + break; + case VENC_STATUS_INVALID_STATE: + ret = VENC_S_EINVALSTATE; + break; + case VENC_STATUS_FLUSHING: + ret = VENC_S_EFLUSHED; + break; + case VENC_STATUS_INVALID_PARAM: + ret = VENC_S_EBADPARAM; + break; + case VENC_STATUS_CMD_QUEUE_FULL: + ret = VENC_S_ECMDQFULL; + break; + case VENC_STATUS_CRITICAL: + ret = VENC_S_EFATAL; + break; + case VENC_STATUS_INSUFFICIENT_RESOURCES: + ret = VENC_S_ENOHWRES; + break; + case VENC_STATUS_TIMEOUT: + ret = VENC_S_ETIMEOUT; + break; + } + if (q6_status != VENC_STATUS_SUCCESS) + pr_err("%s: Q6 failed (%d)", __func__, (int)q6_status); + return ret; +} + +static void venc_q6_callback(void *data, int len, void *cookie) +{ + int status = 0; + struct venc_dev *dvenc = (struct venc_dev *)cookie; + struct venc_msg_type *q6_msg = NULL; + struct venc_msg msg, msg1; + union venc_msg_data smsg1, smsg2; + unsigned long msg_code = 0; + struct venc_input_payload *pload1; + struct venc_output_payload *pload2; + uint32_t * tmp = (uint32_t *) data; + + if (dvenc == NULL) { + pr_err("%s: empty driver parameter\n", __func__); + return; + } + if (tmp[2] == sizeof(struct venc_msg_type)) { + q6_msg = (struct venc_msg_type *)&tmp[3]; + } else { + pr_err("%s: callback with empty message (%d, %d)\n", + __func__, tmp[2], sizeof(struct venc_msg_type)); + return; + } + msg.msg_data_size = 0; + status = venc_translate_error(q6_msg->status); + switch ((enum venc_event_type_enum)q6_msg->event) { + case VENC_EVENT_START_STATUS: + dvenc->state = VENC_STATE_START; + msg_code = VENC_MSG_START; + break; + case VENC_EVENT_STOP_STATUS: + venc_q6_stop(dvenc); + msg_code = VENC_MSG_STOP; + break; + case VENC_EVENT_SUSPEND_STATUS: + dvenc->state = VENC_STATE_PAUSE; + msg_code = VENC_MSG_PAUSE; + break; + case VENC_EVENT_RESUME_STATUS: + dvenc->state = VENC_STATE_START; + msg_code = VENC_MSG_RESUME; + break; + case VENC_EVENT_FLUSH_STATUS: + smsg1.flush_ret.flush_mode = VENC_FLUSH_INPUT; + msg1.status_code = status; + msg1.msg_code = VENC_MSG_FLUSH; + msg1.msg_data = smsg1; + msg1.msg_data_size = sizeof(union venc_msg_data); + venc_put_msg(dvenc, &msg1); + smsg2.flush_ret.flush_mode = VENC_FLUSH_OUTPUT; + msg_code = VENC_MSG_FLUSH; + msg.msg_data = smsg2; + msg.msg_data_size = sizeof(union venc_msg_data); + break; + case VENC_EVENT_RELEASE_INPUT: + pload1 = &((q6_msg->payload).input_payload); + TRACE("Release_input: data: 0x%x \n", pload1->data); + if (pload1 != NULL) { + msg.msg_data.buf.client_data = pload1->data; + msg_code = VENC_MSG_INPUT_BUFFER_DONE; + msg.msg_data_size = sizeof(union venc_msg_data); + } + break; + case VENC_EVENT_DELIVER_OUTPUT: + pload2 = &((q6_msg->payload).output_payload); + smsg1.buf.flags = 0; + if (pload2->flags & VENC_FLAG_SYNC_FRAME) + smsg1.buf.flags |= VENC_FLAG_SYNC_FRAME; + if (pload2->flags & VENC_FLAG_CODEC_CONFIG) + smsg1.buf.flags |= VENC_FLAG_CODEC_CONFIG; + if (pload2->flags & VENC_FLAG_END_OF_FRAME) + smsg1.buf.flags |= VENC_FLAG_END_OF_FRAME; + if (pload2->flags & VENC_FLAG_EOS) + smsg1.buf.flags |= VENC_FLAG_EOS; + smsg1.buf.len = pload2->size; + smsg1.buf.offset = 0; + smsg1.buf.time_stamp = pload2->time_stamp; + smsg1.buf.client_data = pload2->data; + msg_code = VENC_MSG_OUTPUT_BUFFER_DONE; + msg.msg_data = smsg1; + msg.msg_data_size = sizeof(union venc_msg_data); + break; + default: + pr_err("%s: invalid response from Q6 (%d)\n", __func__, + (int)q6_msg->event); + return; + } + msg.status_code = status; + msg.msg_code = msg_code; + venc_put_msg(dvenc, &msg); + return; +} + +static int venc_get_version(struct venc_dev *dvenc, void *argp) +{ + struct venc_version ver_info; + int ret = 0; + + ver_info.major = VENC_GET_MAJOR_VERSION(VENC_INTERFACE_VERSION); + ver_info.minor = VENC_GET_MINOR_VERSION(VENC_INTERFACE_VERSION); + + ret = copy_to_user(((struct venc_version *)argp), + &ver_info, sizeof(ver_info)); + if (ret) + pr_err("%s failed to copy_to_user\n", __func__); + + return ret; + +} + +static long q6venc_ioctl(struct file *file, u32 cmd, + unsigned long arg) +{ + long ret = 0; + void __user *argp = (void __user *)arg; + struct venc_dev *dvenc = file->private_data; + + if (!dvenc || !dvenc->is_active) + return -EPERM; + + switch (cmd) { + case VENC_IOCTL_SET_INPUT_BUFFER: + ret = venc_set_buffer(dvenc, argp, VENC_BUFFER_TYPE_INPUT); + break; + case VENC_IOCTL_SET_OUTPUT_BUFFER: + ret = venc_set_buffer(dvenc, argp, VENC_BUFFER_TYPE_OUTPUT); + break; + case VENC_IOCTL_GET_SEQUENCE_HDR: + ret = venc_get_sequence_hdr(dvenc, argp); + break; + case VENC_IOCTL_SET_QP_RANGE: + ret = venc_set_qp_range(dvenc, argp); + break; + case VENC_IOCTL_SET_INTRA_PERIOD: + ret = venc_set_intra_period(dvenc, argp); + break; + case VENC_IOCTL_SET_INTRA_REFRESH: + ret = venc_set_intra_refresh(dvenc, argp); + break; + case VENC_IOCTL_SET_FRAME_RATE: + ret = venc_set_frame_rate(dvenc, argp); + break; + case VENC_IOCTL_SET_TARGET_BITRATE: + ret = venc_set_target_bitrate(dvenc, argp); + break; + case VENC_IOCTL_CMD_REQUEST_IFRAME: + if (dvenc->state == VENC_STATE_START) + ret = venc_request_iframe(dvenc); + break; + case VENC_IOCTL_CMD_START: + ret = venc_start(dvenc, argp); + break; + case VENC_IOCTL_CMD_STOP: + ret = venc_stop(dvenc); + break; + case VENC_IOCTL_CMD_PAUSE: + ret = venc_pause(dvenc); + break; + case VENC_IOCTL_CMD_RESUME: + ret = venc_resume(dvenc); + break; + case VENC_IOCTL_CMD_ENCODE_FRAME: + ret = venc_encode_frame(dvenc, argp); + break; + case VENC_IOCTL_CMD_FILL_OUTPUT_BUFFER: + ret = venc_fill_output(dvenc, argp); + break; + case VENC_IOCTL_CMD_FLUSH: + ret = venc_flush(dvenc, argp); + break; + case VENC_IOCTL_CMD_READ_NEXT_MSG: + wait_event_interruptible(dvenc->venc_msg_evt, + venc_get_msg(dvenc, argp)); + break; + case VENC_IOCTL_CMD_STOP_READ_MSG: + ret = venc_stop_read_msg(dvenc); + break; + case VENC_IOCTL_GET_VERSION: + ret = venc_get_version(dvenc, argp); + break; + default: + pr_err("%s: invalid ioctl code (%d)\n", __func__, cmd); + ret = -ENOTTY; + break; + } + return ret; +} + +static int q6venc_open(struct inode *inode, struct file *file) +{ + int i; + int ret = 0; + struct venc_dev *dvenc; + struct venc_msg_list *plist, *tmp; + struct dal_info version_info; + + dvenc = kzalloc(sizeof(struct venc_dev), GFP_KERNEL); + if (!dvenc) { + pr_err("%s: unable to allocate memory for struct venc_dev\n", + __func__); + return -ENOMEM; + } + file->private_data = dvenc; + INIT_LIST_HEAD(&dvenc->venc_msg_list_head); + INIT_LIST_HEAD(&dvenc->venc_msg_list_free); + INIT_LIST_HEAD(&dvenc->venc_pmem_list_head); + init_waitqueue_head(&dvenc->venc_msg_evt); + spin_lock_init(&dvenc->venc_msg_list_lock); + spin_lock_init(&dvenc->venc_pmem_list_lock); + venc_ref++; + for (i = 0; i < VENC_MSG_MAX; i++) { + plist = kzalloc(sizeof(struct venc_msg_list), GFP_KERNEL); + if (!plist) { + pr_err("%s: kzalloc failed\n", __func__); + ret = -ENOMEM; + goto err_venc_create_msg_list; + } + list_add(&plist->list, &dvenc->venc_msg_list_free); + } + dvenc->q6_handle = + dal_attach(DALDEVICEID_VENC_DEVICE, DALDEVICEID_VENC_PORTNAME, 1, + venc_q6_callback, (void *)dvenc); + if (!(dvenc->q6_handle)) { + pr_err("%s: daldevice_attach failed (%d)\n", __func__, ret); + goto err_venc_dal_attach; + } + ret = dal_call_f9(dvenc->q6_handle, DAL_OP_INFO, &version_info, + sizeof(struct dal_info)); + if (ret) { + pr_err("%s: failed to get version\n", __func__); + goto err_venc_dal_open; + } + if (venc_check_version(VENC_INTERFACE_VERSION, version_info.version)) { + pr_err("%s: driver version mismatch\n", __func__); + goto err_venc_dal_open; + } + ret = dal_call_f0(dvenc->q6_handle, DAL_OP_OPEN, 1); + if (ret) { + pr_err("%s: dal_call_open failed (%d)\n", __func__, ret); + goto err_venc_dal_open; + } + dvenc->state = VENC_STATE_STOP; + dvenc->is_active = 1; + prevent_sleep(); + return ret; +err_venc_dal_open: + dal_detach(dvenc->q6_handle); +err_venc_dal_attach: + list_for_each_entry_safe(plist, tmp, &dvenc->venc_msg_list_free, list) { + list_del(&plist->list); + kfree(plist); + } +err_venc_create_msg_list: + kfree(dvenc); + venc_ref--; + return ret; +} + +static int q6venc_release(struct inode *inode, struct file *file) +{ + int ret = 0; + struct venc_msg_list *l, *n; + struct venc_pmem_list *plist, *m; + struct venc_dev *dvenc; + unsigned long flags; + + venc_ref--; + dvenc = file->private_data; + dvenc->is_active = 0; + wake_up_all(&dvenc->venc_msg_evt); + dal_call_f0(dvenc->q6_handle, VENC_DALRPC_STOP, 1); + dal_call_f0(dvenc->q6_handle, DAL_OP_CLOSE, 1); + dal_detach(dvenc->q6_handle); + list_for_each_entry_safe(l, n, &dvenc->venc_msg_list_free, list) { + list_del(&l->list); + kfree(l); + } + list_for_each_entry_safe(l, n, &dvenc->venc_msg_list_head, list) { + list_del(&l->list); + kfree(l); + } + spin_lock_irqsave(&dvenc->venc_pmem_list_lock, flags); + if (!dvenc->pmem_freed) { + list_for_each_entry(plist, &dvenc->venc_pmem_list_head, list) + put_pmem_file(plist->buf.file); + dvenc->pmem_freed = 1; + } + spin_unlock_irqrestore(&dvenc->venc_pmem_list_lock, flags); + + list_for_each_entry_safe(plist, m, &dvenc->venc_pmem_list_head, list) { + list_del(&plist->list); + kfree(plist); + } + kfree(dvenc); + allow_sleep(); + return ret; +} + +const struct file_operations q6venc_fops = { + .owner = THIS_MODULE, + .open = q6venc_open, + .release = q6venc_release, + .unlocked_ioctl = q6venc_ioctl, +}; + +static int __init q6venc_init(void) +{ + int ret = 0; + + wake_lock_init(&idlelock, WAKE_LOCK_IDLE, "venc_idle"); + wake_lock_init(&wakelock, WAKE_LOCK_SUSPEND, "venc_suspend"); + + venc_device_p = kzalloc(sizeof(struct venc_dev), GFP_KERNEL); + if (!venc_device_p) { + pr_err("%s: unable to allocate memory for venc_device_p\n", + __func__); + return -ENOMEM; + } + ret = alloc_chrdev_region(&venc_dev_num, 0, 1, VENC_NAME); + if (ret < 0) { + pr_err("%s: alloc_chrdev_region failed (%d)\n", __func__, + ret); + return ret; + } + venc_class = class_create(THIS_MODULE, VENC_NAME); + if (IS_ERR(venc_class)) { + ret = PTR_ERR(venc_class); + pr_err("%s: failed to create venc_class (%d)\n", + __func__, ret); + goto err_venc_class_create; + } + venc_device_p->class_devp = + device_create(venc_class, NULL, venc_dev_num, NULL, + VENC_NAME); + if (IS_ERR(venc_device_p->class_devp)) { + ret = PTR_ERR(venc_device_p->class_devp); + pr_err("%s: failed to create class_device (%d)\n", __func__, + ret); + goto err_venc_class_device_create; + } + cdev_init(&cdev, &q6venc_fops); + cdev.owner = THIS_MODULE; + ret = cdev_add(&cdev, venc_dev_num, 1); + if (ret < 0) { + pr_err("%s: cdev_add failed (%d)\n", __func__, ret); + goto err_venc_cdev_add; + } + init_waitqueue_head(&venc_device_p->venc_msg_evt); + return ret; + +err_venc_cdev_add: + device_destroy(venc_class, venc_dev_num); +err_venc_class_device_create: + class_destroy(venc_class); +err_venc_class_create: + unregister_chrdev_region(venc_dev_num, 1); + return ret; +} + +static void __exit q6venc_exit(void) +{ + cdev_del(&(cdev)); + device_destroy(venc_class, venc_dev_num); + class_destroy(venc_class); + unregister_chrdev_region(venc_dev_num, 1); +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Video encoder driver for QDSP6"); +MODULE_VERSION("2.0"); +module_init(q6venc_init); +module_exit(q6venc_exit); diff --git a/arch/arm/mach-msm/qdsp6/pcm_in.c b/arch/arm/mach-msm/qdsp6/pcm_in.c new file mode 100644 index 00000000000..c6bddb883ea --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/pcm_in.c @@ -0,0 +1,263 @@ +/* arch/arm/mach-msm/qdsp6/pcm_in.c + * + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct pcm { + struct audio_client *ac; + uint32_t sample_rate; + uint32_t channel_count; + uint32_t buffer_size; + uint32_t rec_mode; +}; + +#define BUFSZ (256) + +void audio_client_dump(struct audio_client *ac); + +static long q6_in_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct pcm *pcm = file->private_data; + int rc = 0; + + switch (cmd) { + case AUDIO_SET_VOLUME: + pr_debug("[%s:%s] SET_VOLUME\n", __MM_FILE__, __func__); + break; + case AUDIO_GET_STATS: { + struct msm_audio_stats stats; + pr_debug("[%s:%s] GET_STATS\n", __MM_FILE__, __func__); + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void*) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + case AUDIO_START: { + uint32_t acdb_id; + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + rc = 0; + + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, (void*) arg, sizeof(acdb_id))) { + rc = -EFAULT; + break; + } + + if (pcm->ac) { + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + rc = -EBUSY; + } else { + pcm->ac = q6audio_open_pcm(pcm->buffer_size, + pcm->sample_rate, pcm->channel_count, + pcm->rec_mode, acdb_id); + if (!pcm->ac) { + pr_err("[%s:%s] pcm open session failed\n", + __MM_FILE__, __func__); + rc = -ENOMEM; + } + } + break; + } + case AUDIO_STOP: + pr_debug("[%s:%s] AUDIO_STOP\n", __MM_FILE__, __func__); + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void*) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + pr_debug("[%s:%s] SET_CONFIG: samplerate = %d, channels = %d\n", + __MM_FILE__, __func__, config.sample_rate, + config.channel_count); + if (!config.channel_count || config.channel_count > 2) { + rc = -EINVAL; + pr_err("[%s:%s] invalid channelcount %d\n", + __MM_FILE__, __func__, config.channel_count); + break; + } + if (config.sample_rate < 8000 || config.sample_rate > 48000) { + rc = -EINVAL; + pr_err("[%s:%s] invalid samplerate %d\n", __MM_FILE__, + __func__, config.sample_rate); + break; + } + if (config.buffer_size < 128 || config.buffer_size > 8192) { + rc = -EINVAL; + pr_err("[%s:%s] invalid buffsize %d\n", __MM_FILE__, + __func__, config.buffer_size); + break; + } + + pcm->sample_rate = config.sample_rate; + pcm->channel_count = config.channel_count; + pcm->buffer_size = config.buffer_size; + break; + } + case AUDIO_SET_INCALL: { + struct msm_voicerec_mode voicerec_mode; + pr_debug("[%s:%s] SET_INCALL\n", __MM_FILE__, __func__); + if (copy_from_user(&voicerec_mode, (void *)arg, + sizeof(struct msm_voicerec_mode))) + return -EFAULT; + if (voicerec_mode.rec_mode != AUDIO_FLAG_READ && + voicerec_mode.rec_mode != AUDIO_FLAG_INCALL_MIXED) { + pcm->rec_mode = AUDIO_FLAG_READ; + pr_err("[%s:%s] invalid rec_mode\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } else + pcm->rec_mode = voicerec_mode.rec_mode; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = pcm->buffer_size; + config.buffer_count = 2; + config.sample_rate = pcm->sample_rate; + config.channel_count = pcm->channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void*) arg, &config, sizeof(config))) { + rc = -EFAULT; + } + pr_debug("[%s:%s] GET_CONFIG: samplerate = %d, channels = %d\n", + __MM_FILE__, __func__, config.sample_rate, + config.channel_count); + break; + } + default: + rc = -EINVAL; + } + pr_debug("[%s:%s] rc = %d\n", __MM_FILE__, __func__, rc); + return rc; +} + +static int q6_in_open(struct inode *inode, struct file *file) +{ + struct pcm *pcm; + + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + pcm = kzalloc(sizeof(struct pcm), GFP_KERNEL); + + if (!pcm) + return -ENOMEM; + + pcm->channel_count = 1; + pcm->sample_rate = 8000; + pcm->buffer_size = BUFSZ; + pcm->rec_mode = AUDIO_FLAG_READ; + file->private_data = pcm; + return 0; +} + +static ssize_t q6_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct pcm *pcm = file->private_data; + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer; + int res; + + pr_debug("[%s:%s] count = %d\n", __MM_FILE__, __func__, count); + ac = pcm->ac; + if (!ac) { + res = -ENODEV; + goto fail; + } + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + if (!wait_event_timeout(ac->wait, (ab->used == 0), 5*HZ)) { + audio_client_dump(ac); + pr_err("[%s:%s] timeout. dsp dead?\n", + __MM_FILE__, __func__); + q6audio_dsp_not_responding(); + } + pr_debug("[%s:%s] ab->data = %p, cpu_buf = %d", __MM_FILE__, + __func__, ab->data, ac->cpu_buf); + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_to_user(buf, ab->data, xfer)) { + res = -EFAULT; + goto fail; + } + + buf += xfer; + count -= xfer; + + ab->used = 1; + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } +fail: + res = buf - start; + return res; +} + +static int q6_in_release(struct inode *inode, struct file *file) +{ + + int rc = 0; + struct pcm *pcm = file->private_data; + if (pcm->ac) + rc = q6audio_close(pcm->ac); + kfree(pcm); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return rc; +} + +static struct file_operations q6_in_fops = { + .owner = THIS_MODULE, + .open = q6_in_open, + .read = q6_in_read, + .release = q6_in_release, + .unlocked_ioctl = q6_in_ioctl, +}; + +struct miscdevice q6_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_in", + .fops = &q6_in_fops, +}; + +static int __init q6_in_init(void) { + return misc_register(&q6_in_misc); +} + +device_initcall(q6_in_init); diff --git a/arch/arm/mach-msm/qdsp6/pcm_out.c b/arch/arm/mach-msm/qdsp6/pcm_out.c new file mode 100644 index 00000000000..2e91cb2fed8 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/pcm_out.c @@ -0,0 +1,276 @@ +/* arch/arm/mach-msm/qdsp6/pcm_out.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +void audio_client_dump(struct audio_client *ac); + +#define BUFSZ (3072) + +struct pcm { + struct mutex lock; + struct audio_client *ac; + uint32_t sample_rate; + uint32_t channel_count; + size_t buffer_size; +}; + +static long pcm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct pcm *pcm = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void*) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&pcm->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: { + int vol; + if (!pcm->ac) { + pr_err("%s: cannot set volume before AUDIO_START!\n", + __func__); + rc = -EINVAL; + break; + } + if (copy_from_user(&vol, (void*) arg, sizeof(vol))) { + rc = -EFAULT; + break; + } + pr_debug("[%s:%s] SET_VOLUME: vol = %d\n", __MM_FILE__, + __func__, vol); + rc = q6audio_set_stream_volume(pcm->ac, vol); + break; + } + case AUDIO_START: { + uint32_t acdb_id; + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + if (arg == 0) { + acdb_id = 0; + } else if (copy_from_user(&acdb_id, (void*) arg, sizeof(acdb_id))) { + pr_info("[%s:%s] copy acdb_id from user failed\n", + __MM_FILE__, __func__); + rc = -EFAULT; + break; + } + if (pcm->ac) { + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + rc = -EBUSY; + } else { + pcm->ac = q6audio_open_pcm(pcm->buffer_size, + pcm->sample_rate, + pcm->channel_count, + AUDIO_FLAG_WRITE, acdb_id); + if (!pcm->ac) { + pr_err("[%s:%s] pcm open session failed\n", + __MM_FILE__, __func__); + rc = -ENOMEM; + } + } + break; + } + case AUDIO_STOP: + pr_debug("[%s:%s] AUDIO_STOP\n", __MM_FILE__, __func__); + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (pcm->ac) { + rc = -EBUSY; + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + break; + } + if (copy_from_user(&config, (void*) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + pr_debug("[%s:%s] SET_CONFIG: samplerate = %d, channels = %d\n", + __MM_FILE__, __func__, config.sample_rate, + config.channel_count); + if (config.channel_count < 1 || config.channel_count > 2) { + rc = -EINVAL; + pr_err("[%s:%s] invalid channelcount %d\n", + __MM_FILE__, __func__, config.channel_count); + break; + } + if (config.sample_rate < 8000 || config.sample_rate > 48000) { + rc = -EINVAL; + pr_err("[%s:%s] invalid samplerate %d\n", __MM_FILE__, + __func__, config.sample_rate); + break; + } + if (config.buffer_size < 128 || config.buffer_size > 8192) { + rc = -EINVAL; + pr_err("[%s:%s] invalid buffsize %d\n", __MM_FILE__, + __func__, config.buffer_size); + break; + } + pcm->sample_rate = config.sample_rate; + pcm->channel_count = config.channel_count; + pcm->buffer_size = config.buffer_size; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = pcm->buffer_size; + config.buffer_count = 2; + config.sample_rate = pcm->sample_rate; + config.channel_count = pcm->channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void*) arg, &config, sizeof(config))) { + rc = -EFAULT; + } + pr_debug("[%s:%s] GET_CONFIG: samplerate = %d, channels = %d\n", + __MM_FILE__, __func__, config.sample_rate, + config.channel_count); + break; + } + case AUDIO_SET_EQ: { + struct msm_audio_eq_stream_config eq_config; + pr_debug("[%s:%s] SET_EQ\n", __MM_FILE__, __func__); + if (copy_from_user(&eq_config, (void *) arg, + sizeof(eq_config))) { + rc = -EFAULT; + break; + } + rc = q6audio_set_stream_eq_pcm(pcm->ac, (void *) &eq_config); + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&pcm->lock); + pr_debug("[%s:%s] rc = %d\n", __MM_FILE__, __func__, rc); + return rc; +} + +static int pcm_open(struct inode *inode, struct file *file) +{ + struct pcm *pcm; + + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + pcm = kzalloc(sizeof(struct pcm), GFP_KERNEL); + + if (!pcm) + return -ENOMEM; + + mutex_init(&pcm->lock); + pcm->channel_count = 2; + pcm->sample_rate = 44100; + pcm->buffer_size = BUFSZ; + file->private_data = pcm; + return 0; +} + +static ssize_t pcm_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct pcm *pcm = file->private_data; + struct audio_client *ac; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer; + + pr_debug("[%s:%s] count = %d\n", __MM_FILE__, __func__, count); + if (!pcm->ac) + pcm_ioctl(file, AUDIO_START, 0); + + ac = pcm->ac; + if (!ac) + return -ENODEV; + + while (count > 0) { + ab = ac->buf + ac->cpu_buf; + + if (ab->used) + if (!wait_event_timeout(ac->wait, (ab->used == 0), 5*HZ)) { + audio_client_dump(ac); + pr_err("[%s:%s] timeout. dsp dead?\n", + __MM_FILE__, __func__); + q6audio_dsp_not_responding(); + } + pr_debug("[%s:%s] ab->data = %p, cpu_buf = %d", __MM_FILE__, + __func__, ab->data, ac->cpu_buf); + xfer = count; + if (xfer > ab->size) + xfer = ab->size; + + if (copy_from_user(ab->data, buf, xfer)) + return -EFAULT; + + buf += xfer; + count -= xfer; + + ab->used = 1; + ab->actual_size = xfer; + q6audio_write(ac, ab); + ac->cpu_buf ^= 1; + } + + return buf - start; +} + +static int pcm_release(struct inode *inode, struct file *file) +{ + struct pcm *pcm = file->private_data; + if (pcm->ac) + q6audio_close(pcm->ac); + kfree(pcm); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return 0; +} + +static struct file_operations pcm_fops = { + .owner = THIS_MODULE, + .open = pcm_open, + .write = pcm_write, + .release = pcm_release, + .unlocked_ioctl = pcm_ioctl, +}; + +struct miscdevice pcm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_out", + .fops = &pcm_fops, +}; + +static int __init pcm_init(void) { + return misc_register(&pcm_misc); +} + +device_initcall(pcm_init); diff --git a/arch/arm/mach-msm/qdsp6/q6audio.c b/arch/arm/mach-msm/qdsp6/q6audio.c new file mode 100644 index 00000000000..bf6f115f91e --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/q6audio.c @@ -0,0 +1,2155 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dal.h" +#include "dal_audio.h" +#include "dal_audio_format.h" +#include "dal_acdb.h" +#include "dal_adie.h" +#include + +#include + +#include + +#include "q6audio_devices.h" +#include + + +struct q6_hw_info { + int min_gain; + int max_gain; +}; + +/* TODO: provide mechanism to configure from board file */ + +static struct q6_hw_info q6_audio_hw[Q6_HW_COUNT] = { + [Q6_HW_HANDSET] = { + .min_gain = -400, + .max_gain = 1100, + }, + [Q6_HW_HEADSET] = { + .min_gain = -1100, + .max_gain = 400, + }, + [Q6_HW_SPEAKER] = { + .min_gain = -1000, + .max_gain = 500, + }, + [Q6_HW_TTY] = { + .min_gain = 0, + .max_gain = 0, + }, + [Q6_HW_BT_SCO] = { + .min_gain = -1100, + .max_gain = 400, + }, + [Q6_HW_BT_A2DP] = { + .min_gain = -1100, + .max_gain = 400, + }, +}; + +static struct wake_lock wakelock; +static struct wake_lock idlelock; +static int idlecount; +static DEFINE_MUTEX(idlecount_lock); + +void audio_prevent_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (++idlecount == 1) { + wake_lock(&wakelock); + wake_lock(&idlelock); + } + mutex_unlock(&idlecount_lock); +} + +void audio_allow_sleep(void) +{ + mutex_lock(&idlecount_lock); + if (--idlecount == 0) { + wake_unlock(&idlelock); + wake_unlock(&wakelock); + } + mutex_unlock(&idlecount_lock); +} + +static struct clk *icodec_rx_clk; +static struct clk *icodec_tx_clk; +static struct clk *ecodec_clk; +static struct clk *sdac_clk; + +static struct q6audio_analog_ops default_analog_ops; +static struct q6audio_analog_ops *analog_ops = &default_analog_ops; +static uint32_t tx_clk_freq = 8000; +static int tx_mute_status = 0; +static int rx_vol_level = 100; +static uint32_t tx_acdb = 0; +static uint32_t rx_acdb = 0; + +void q6audio_register_analog_ops(struct q6audio_analog_ops *ops) +{ + analog_ops = ops; +} + +static struct q6_device_info *q6_lookup_device(uint32_t device_id, + uint32_t acdb_id) +{ + struct q6_device_info *di = q6_audio_devices; + + pr_debug("[%s:%s] device_id = 0x%x, acdb_id = %d\n", __MM_FILE__, + __func__, device_id, acdb_id); + if (acdb_id) { + for (;;) { + if (di->cad_id == acdb_id && di->id == device_id) + return di; + if (di->id == 0) { + pr_err("[%s:%s] bogus id 0x%08x\n", + __MM_FILE__, __func__, device_id); + return di; + } + di++; + } + } else { + for (;;) { + if (di->id == device_id) + return di; + if (di->id == 0) { + pr_err("[%s:%s] bogus id 0x%08x\n", + __MM_FILE__, __func__, device_id); + return di; + } + di++; + } + } +} + +static uint32_t q6_device_to_codec(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id, 0); + return di->codec; +} + +static uint32_t q6_device_to_dir(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id, 0); + return di->dir; +} + +static uint32_t q6_device_to_cad_id(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id, 0); + return di->cad_id; +} + +static uint32_t q6_device_to_path(uint32_t device_id, uint32_t acdb_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id, acdb_id); + return di->path; +} + +static uint32_t q6_device_to_rate(uint32_t device_id) +{ + struct q6_device_info *di = q6_lookup_device(device_id, 0); + return di->rate; +} + +int q6_device_volume(uint32_t device_id, int level) +{ + struct q6_device_info *di = q6_lookup_device(device_id, 0); + struct q6_hw_info *hw; + + hw = &q6_audio_hw[di->hw]; + + return hw->min_gain + ((hw->max_gain - hw->min_gain) * level) / 100; +} + +static inline int adie_open(struct dal_client *client) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + return dal_call_f0(client, DAL_OP_OPEN, 0); +} + +static inline int adie_close(struct dal_client *client) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + return dal_call_f0(client, DAL_OP_CLOSE, 0); +} + +static inline int adie_set_path(struct dal_client *client, + uint32_t id, uint32_t path_type) +{ + pr_debug("[%s:%s] id = 0x%x, path_type = %d\n", __MM_FILE__, + __func__, id, path_type); + return dal_call_f1(client, ADIE_OP_SET_PATH, id, path_type); +} + +static inline int adie_set_path_freq_plan(struct dal_client *client, + uint32_t path_type, uint32_t plan) +{ + pr_debug("[%s:%s] path_type = %d, plan = %d\n", __MM_FILE__, + __func__, path_type, plan); + return dal_call_f1(client, ADIE_OP_SET_PATH_FREQUENCY_PLAN, + path_type, plan); +} + +static inline int adie_proceed_to_stage(struct dal_client *client, + uint32_t path_type, uint32_t stage) +{ + pr_debug("[%s:%s] path_type = %d, stage = 0x%x\n", __MM_FILE__, + __func__, path_type, stage); + return dal_call_f1(client, ADIE_OP_PROCEED_TO_STAGE, + path_type, stage); +} + +static inline int adie_mute_path(struct dal_client *client, + uint32_t path_type, uint32_t mute_state) +{ + pr_debug("[%s:%s] path_type = %d, mute = %d\n", __MM_FILE__, __func__, + path_type, mute_state); + return dal_call_f1(client, ADIE_OP_MUTE_PATH, path_type, mute_state); +} + +static int adie_refcount; + +static struct dal_client *adie; +static struct dal_client *adsp; +static struct dal_client *acdb; + +static int adie_enable(void) +{ + adie_refcount++; + if (adie_refcount == 1) + adie_open(adie); + return 0; +} + +static int adie_disable(void) +{ + adie_refcount--; + if (adie_refcount == 0) + adie_close(adie); + return 0; +} + +/* 4k PMEM used for exchanging acdb device config tables + * and stream format descriptions with the DSP. + */ +static char *audio_data; +static int32_t audio_phys; + +#define SESSION_MIN 0 +#define SESSION_MAX 64 + +static DEFINE_MUTEX(session_lock); +static DEFINE_MUTEX(audio_lock); + +static struct audio_client *session[SESSION_MAX]; + +static int session_alloc(struct audio_client *ac) +{ + int n; + + mutex_lock(&session_lock); + for (n = SESSION_MIN; n < SESSION_MAX; n++) { + if (!session[n]) { + session[n] = ac; + mutex_unlock(&session_lock); + pr_debug("[%s:%s] session = %d\n", __MM_FILE__, + __func__, n); + return n; + } + } + mutex_unlock(&session_lock); + return -ENOMEM; +} + +static void session_free(int n, struct audio_client *ac) +{ + mutex_lock(&session_lock); + if (session[n] == ac) { + session[n] = 0; + pr_debug("[%s:%s] session = %d\n", __MM_FILE__, __func__, n); + } + mutex_unlock(&session_lock); +} + +static void audio_client_free(struct audio_client *ac) +{ + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + session_free(ac->session, ac); + + if (ac->buf[0].data) { + iounmap(ac->buf[0].data); + pmem_kfree(ac->buf[0].phys); + } + if (ac->buf[1].data) { + iounmap(ac->buf[1].data); + pmem_kfree(ac->buf[1].phys); + } + kfree(ac); +} + +static struct audio_client *audio_client_alloc(unsigned bufsz) +{ + struct audio_client *ac; + int n; + + pr_debug("[%s:%s] bufsz = %d\n", __MM_FILE__, __func__, bufsz); + ac = kzalloc(sizeof(*ac), GFP_KERNEL); + if (!ac) + return 0; + + n = session_alloc(ac); + if (n < 0) + goto fail_session; + ac->session = n; + + if (bufsz > 0) { + ac->buf[0].phys = pmem_kalloc(bufsz, + PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + ac->buf[0].data = ioremap(ac->buf[0].phys, bufsz); + if (!ac->buf[0].data) + goto fail; + ac->buf[1].phys = pmem_kalloc(bufsz, + PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + ac->buf[1].data = ioremap(ac->buf[1].phys, bufsz); + if (!ac->buf[1].data) + goto fail; + + ac->buf[0].size = bufsz; + ac->buf[1].size = bufsz; + } + + init_waitqueue_head(&ac->wait); + ac->client = adsp; + + return ac; + +fail: + session_free(n, ac); +fail_session: + audio_client_free(ac); + return 0; +} + +void audio_client_dump(struct audio_client *ac) +{ + dal_trace_dump(ac->client); +} + +static int audio_ioctl(struct audio_client *ac, void *ptr, uint32_t len) +{ + struct adsp_command_hdr *hdr = ptr; + uint32_t tmp; + int r; + + hdr->size = len - sizeof(u32); + hdr->dst = AUDIO_ADDR(ac->session, 0, AUDIO_DOMAIN_DSP); + hdr->src = AUDIO_ADDR(ac->session, 0, AUDIO_DOMAIN_APP); + hdr->context = ac->session; + ac->cb_status = -EBUSY; + r = dal_call(ac->client, AUDIO_OP_CONTROL, 5, ptr, len, &tmp, sizeof(tmp)); + if (r != 4) + return -EIO; + if (!wait_event_timeout(ac->wait, (ac->cb_status != -EBUSY), 5*HZ)) { + dal_trace_dump(ac->client); + pr_err("[%s:%s] timeout. dsp dead?\n", __MM_FILE__, __func__); + q6audio_dsp_not_responding(); + } + return ac->cb_status; +} + +static int audio_command(struct audio_client *ac, uint32_t cmd) +{ + struct adsp_command_hdr rpc; + memset(&rpc, 0, sizeof(rpc)); + rpc.opcode = cmd; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_open_control(struct audio_client *ac) +{ + struct adsp_open_command rpc; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_DEVICE; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_out_open(struct audio_client *ac, uint32_t bufsz, + uint32_t rate, uint32_t channels) +{ + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format.standard.format = ADSP_AUDIO_FORMAT_PCM; + rpc.format.standard.channels = channels; + rpc.format.standard.bits_per_sample = 16; + rpc.format.standard.sampling_rate = rate; + rpc.format.standard.is_signed = 1; + rpc.format.standard.is_interleaved = 1; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_WRITE; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK; + rpc.buf_max_size = bufsz; + + pr_debug("[%s:%s]ac = %p\n", __MM_FILE__, __func__, ac); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_in_open(struct audio_client *ac, uint32_t bufsz, + uint32_t flags, uint32_t rate, uint32_t channels) +{ + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format.standard.format = ADSP_AUDIO_FORMAT_PCM; + rpc.format.standard.channels = channels; + rpc.format.standard.bits_per_sample = 16; + rpc.format.standard.sampling_rate = rate; + rpc.format.standard.is_signed = 1; + rpc.format.standard.is_interleaved = 1; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + if (flags == AUDIO_FLAG_READ) + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + else + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_MIXED_RECORD; + + rpc.buf_max_size = bufsz; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_auxpcm_out_open(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format.standard.format = ADSP_AUDIO_FORMAT_PCM; + rpc.format.standard.channels = channels; + rpc.format.standard.bits_per_sample = 16; + rpc.format.standard.sampling_rate = rate; + rpc.format.standard.is_signed = 1; + rpc.format.standard.is_interleaved = 1; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.mode = ADSP_AUDIO_OPEN_STREAM_MODE_AUX_PCM; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_auxpcm_in_open(struct audio_client *ac, uint32_t rate, + uint32_t channels) +{ + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format.standard.format = ADSP_AUDIO_FORMAT_PCM; + rpc.format.standard.channels = channels; + rpc.format.standard.bits_per_sample = 16; + rpc.format.standard.sampling_rate = rate; + rpc.format.standard.is_signed = 1; + rpc.format.standard.is_interleaved = 1; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_WRITE; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.mode = ADSP_AUDIO_OPEN_STREAM_MODE_AUX_PCM; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_mp3_open(struct audio_client *ac, uint32_t bufsz, + uint32_t rate, uint32_t channels) +{ + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format.standard.format = ADSP_AUDIO_FORMAT_MP3; + rpc.format.standard.channels = channels; + rpc.format.standard.bits_per_sample = 16; + rpc.format.standard.sampling_rate = rate; + rpc.format.standard.is_signed = 1; + rpc.format.standard.is_interleaved = 0; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_WRITE; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK; + rpc.buf_max_size = bufsz; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_dtmf_open(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format.standard.format = ADSP_AUDIO_FORMAT_DTMF; + rpc.format.standard.channels = channels; + rpc.format.standard.bits_per_sample = 16; + rpc.format.standard.sampling_rate = rate; + rpc.format.standard.is_signed = 1; + rpc.format.standard.is_interleaved = 0; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_WRITE; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_PLAYBACK; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_aac_open(struct audio_client *ac, uint32_t bufsz, + uint32_t sample_rate, uint32_t channels, + uint32_t bit_rate, uint32_t flags, + uint32_t stream_format) +{ + struct adsp_open_command rpc; + int audio_object_type; + int index = sizeof(u32); + u32 *aac_type = NULL; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format.binary.format = ADSP_AUDIO_FORMAT_MPEG4_AAC; + /* only 48k sample rate is supported */ + sample_rate = 3; + /* AAC OBJECT LC */ + audio_object_type = 2; + + aac_type = (u32 *)rpc.format.binary.data; + switch (stream_format) { + case AUDIO_AAC_FORMAT_ADTS: + /* AAC Encoder expect MPEG4_ADTS media type */ + *aac_type = ADSP_AUDIO_AAC_MPEG4_ADTS; + break; + case AUDIO_AAC_FORMAT_RAW: + /* for ADIF recording */ + *aac_type = ADSP_AUDIO_AAC_RAW; + break; + } + + rpc.format.binary.data[index++] = (u8)( + ((audio_object_type & 0x1F) << 3) | + ((sample_rate >> 1) & 0x7)); + rpc.format.binary.data[index] = (u8)( + ((sample_rate & 0x1) << 7) | + ((channels & 0x7) << 3)); + rpc.format.binary.num_bytes = index + 1; + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + + if (flags == AUDIO_FLAG_READ) + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + else + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_MIXED_RECORD; + + rpc.buf_max_size = bufsz; + rpc.config.aac.bit_rate = bit_rate; + rpc.config.aac.encoder_mode = ADSP_AUDIO_ENC_AAC_LC_ONLY_MODE; + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_qcp_open(struct audio_client *ac, uint32_t bufsz, + uint32_t min_rate, uint32_t max_rate, + uint32_t flags, uint32_t format) +{ + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format.standard.format = format; + rpc.format.standard.channels = 1; + rpc.format.standard.bits_per_sample = 16; + rpc.format.standard.sampling_rate = 8000; + rpc.format.standard.is_signed = 1; + rpc.format.standard.is_interleaved = 0; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + + if (flags == AUDIO_FLAG_READ) + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + else + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_MIXED_RECORD; + rpc.buf_max_size = bufsz; + rpc.config.evrc.min_rate = min_rate; + rpc.config.evrc.max_rate = max_rate; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_amrnb_open(struct audio_client *ac, uint32_t bufsz, + uint32_t enc_mode, uint32_t flags, + uint32_t dtx_enable) +{ + struct adsp_open_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.format.standard.format = ADSP_AUDIO_FORMAT_AMRNB_FS; + rpc.format.standard.channels = 1; + rpc.format.standard.bits_per_sample = 16; + rpc.format.standard.sampling_rate = 8000; + rpc.format.standard.is_signed = 1; + rpc.format.standard.is_interleaved = 0; + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_OPEN_READ; + rpc.device = ADSP_AUDIO_DEVICE_ID_DEFAULT; + + if (flags == AUDIO_FLAG_READ) + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_RECORD; + else + rpc.stream_context = ADSP_AUDIO_DEVICE_CONTEXT_MIXED_RECORD; + + rpc.buf_max_size = bufsz; + rpc.config.amr.mode = enc_mode; + rpc.config.amr.dtx_mode = dtx_enable; + rpc.config.amr.enable = 1; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + + + +static int audio_close(struct audio_client *ac) +{ + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_STREAM_STOP); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_CLOSE); + return 0; +} + +static int audio_set_table(struct audio_client *ac, + uint32_t device_id, int size) +{ + struct adsp_set_dev_cfg_table_command rpc; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_SET_DEVICE_CONFIG_TABLE; + if (q6_device_to_dir(device_id) == Q6_TX) { + if (tx_clk_freq > 16000) + rpc.hdr.data = 48000; + else if (tx_clk_freq > 8000) + rpc.hdr.data = 16000; + else + rpc.hdr.data = 8000; + } + rpc.device_id = device_id; + rpc.phys_addr = audio_phys; + rpc.phys_size = size; + rpc.phys_used = size; + + pr_debug("[%s:%s] ac = %p, device_id = 0x%x, size = %d\n", __MM_FILE__, + __func__, ac, device_id, size); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +int q6audio_read(struct audio_client *ac, struct audio_buffer *ab) +{ + struct adsp_buffer_command rpc; + uint32_t res; + int r; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.size = sizeof(rpc) - sizeof(u32); + rpc.hdr.dst = AUDIO_ADDR(ac->session, 0, AUDIO_DOMAIN_DSP); + rpc.hdr.src = AUDIO_ADDR(ac->session, 0, AUDIO_DOMAIN_APP); + rpc.hdr.context = ac->session; + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_DATA_TX; + rpc.buffer.addr = ab->phys; + rpc.buffer.max_size = ab->size; + rpc.buffer.actual_size = ab->actual_size; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + r = dal_call(ac->client, AUDIO_OP_DATA, 5, &rpc, sizeof(rpc), + &res, sizeof(res)); + return 0; +} + +int q6audio_write(struct audio_client *ac, struct audio_buffer *ab) +{ + struct adsp_buffer_command rpc; + uint32_t res; + int r; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.size = sizeof(rpc) - sizeof(u32); + rpc.hdr.dst = AUDIO_ADDR(ac->session, 0, AUDIO_DOMAIN_DSP); + rpc.hdr.src = AUDIO_ADDR(ac->session, 0, AUDIO_DOMAIN_APP); + rpc.hdr.context = ac->session; + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_DATA_RX; + rpc.buffer.addr = ab->phys; + rpc.buffer.max_size = ab->size; + rpc.buffer.actual_size = ab->actual_size; + + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + r = dal_call(ac->client, AUDIO_OP_DATA, 5, &rpc, sizeof(rpc), + &res, sizeof(res)); + return 0; +} + +static int audio_rx_volume(struct audio_client *ac, uint32_t dev_id, int32_t volume) +{ + struct adsp_set_dev_volume_command rpc; + + pr_debug("[%s:%s] volume = %d\n", __MM_FILE__, __func__, volume); + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_VOL; + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_RX; + rpc.volume = volume; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_rx_mute(struct audio_client *ac, uint32_t dev_id, int mute) +{ + struct adsp_set_dev_mute_command rpc; + + pr_debug("[%s:%s] mute = %d, dev_id = 0x%x\n", __MM_FILE__, + __func__, mute, dev_id); + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_MUTE; + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_RX; + rpc.mute = !!mute; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_tx_mute(struct audio_client *ac, uint32_t dev_id, int mute) +{ + struct adsp_set_dev_mute_command rpc; + + pr_debug("[%s:%s] mute = %d\n", __MM_FILE__, __func__, mute); + if (mute < 0 || mute > 3) { + pr_err("[%s:%s] invalid mute status %d\n", __MM_FILE__, + __func__, mute); + return -EINVAL; + } + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SET_DEVICE_MUTE; + if ((mute == STREAM_UNMUTE) || (mute == STREAM_MUTE)) { + rpc.device_id = ADSP_AUDIO_DEVICE_ID_VOICE; + rpc.path = ADSP_PATH_TX_CNG_DIS; + } else { + rpc.device_id = dev_id; + rpc.path = ADSP_PATH_TX; + } + mute &= 0x01; + rpc.mute = !!mute; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int audio_stream_volume(struct audio_client *ac, int volume) +{ + struct adsp_set_volume_command rpc; + int rc; + + pr_debug("[%s:%s] volume = %d\n", __MM_FILE__, __func__, volume); + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SET_STREAM_VOL; + rpc.volume = volume; + rc = audio_ioctl(ac, &rpc, sizeof(rpc)); + return rc; +} + +static int audio_stream_mute(struct audio_client *ac, int mute) +{ + struct adsp_set_mute_command rpc; + int rc; + + pr_debug("[%s:%s] mute = %d\n", __MM_FILE__, __func__, mute); + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SET_STREAM_MUTE; + rpc.mute = mute; + rc = audio_ioctl(ac, &rpc, sizeof(rpc)); + return rc; +} + +static void callback(void *data, int len, void *cookie) +{ + struct adsp_event_hdr *e = data; + struct audio_client *ac; + struct adsp_buffer_event *abe = data; + + if (e->context >= SESSION_MAX) { + pr_err("[%s:%s] bogus session %d\n", __MM_FILE__, __func__, + e->context); + return; + } + ac = session[e->context]; + if (!ac) { + pr_err("[%s:%s] unknown session %d\n", __MM_FILE__, __func__, + e->context); + return; + } + + if (e->event_id == ADSP_AUDIO_IOCTL_CMD_STREAM_EOS) { + pr_debug("[%s:%s] CB Stream eos, ac = %p\n", + __MM_FILE__, __func__, ac); + if (e->status) + pr_err("[%s:%s] playback status %d\n", __MM_FILE__, + __func__, e->status); + if (ac->cb_status == -EBUSY) { + ac->cb_status = e->status; + wake_up(&ac->wait); + } + return; + } + + if (e->event_id == ADSP_AUDIO_EVT_STATUS_BUF_DONE) { + pr_debug("[%s:%s] CB done, ac = %p, status = %d\n", + __MM_FILE__, __func__, ac, e->status); + if (e->status) + pr_err("[%s:%s] buffer status %d\n", __MM_FILE__, + __func__, e->status); + + ac->buf[ac->dsp_buf].actual_size = abe->buffer.actual_size; + ac->buf[ac->dsp_buf].used = 0; + ac->dsp_buf ^= 1; + wake_up(&ac->wait); + return; + } + + pr_debug("[%s:%s] ac = %p, event_id = 0x%x, status = %d\n", + __MM_FILE__, __func__, ac, e->event_id, e->status); + if (e->status) + pr_warning("audio_cb: s=%d e=%08x status=%d\n", + e->context, e->event_id, e->status); + if (ac->cb_status == -EBUSY) { + ac->cb_status = e->status; + wake_up(&ac->wait); + } +} + +static void audio_init(struct dal_client *client) +{ + u32 tmp[3]; + + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + tmp[0] = 2 * sizeof(u32); + tmp[1] = 0; + tmp[2] = 0; + dal_call(client, AUDIO_OP_INIT, 5, tmp, sizeof(tmp), + tmp, sizeof(u32)); +} + +static struct audio_client *ac_control; + +static int q6audio_init(void) +{ + struct audio_client *ac = 0; + int res; + + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + mutex_lock(&audio_lock); + if (ac_control) { + res = 0; + goto done; + } + + pr_info("[%s:%s] codecs\n", __MM_FILE__, __func__); + icodec_rx_clk = clk_get(0, "icodec_rx_clk"); + icodec_tx_clk = clk_get(0, "icodec_tx_clk"); + ecodec_clk = clk_get(0, "ecodec_clk"); + sdac_clk = clk_get(0, "sdac_clk"); + audio_phys = pmem_kalloc(4096, PMEM_MEMTYPE_EBI1|PMEM_ALIGNMENT_4K); + audio_data = ioremap(audio_phys, 4096); + + pr_info("[%s:%s] attach ADSP\n", __MM_FILE__, __func__); + adsp = dal_attach(AUDIO_DAL_DEVICE, AUDIO_DAL_PORT, 1, + callback, 0); + if (!adsp) { + pr_err("[%s:%s] cannot attach to adsp\n", __MM_FILE__, + __func__); + res = -ENODEV; + goto done; + } + pr_info("[%s:%s] INIT\n", __MM_FILE__, __func__); + audio_init(adsp); + dal_trace(adsp); + + ac = audio_client_alloc(0); + if (!ac) { + pr_err("[%s:%s] cannot allocate client\n", + __MM_FILE__, __func__); + res = -ENOMEM; + goto done; + } + + pr_info("[%s:%s] OPEN control\n", __MM_FILE__, __func__); + if (audio_open_control(ac)) { + pr_err("[%s:%s] cannot open control channel\n", + __MM_FILE__, __func__); + res = -ENODEV; + goto done; + } + + pr_info("[%s:%s] attach ACDB\n", __MM_FILE__, __func__); + acdb = dal_attach(ACDB_DAL_DEVICE, ACDB_DAL_PORT, 0, 0, 0); + if (!acdb) { + pr_err("[%s:%s] cannot attach to acdb channel\n", + __MM_FILE__, __func__); + res = -ENODEV; + goto done; + } + + pr_info("[%s:%s] attach ADIE\n", __MM_FILE__, __func__); + adie = dal_attach(ADIE_DAL_DEVICE, ADIE_DAL_PORT, 0, 0, 0); + if (!adie) { + pr_err("[%s:%s] cannot attach to adie\n", + __MM_FILE__, __func__); + res = -ENODEV; + goto done; + } + if (analog_ops->init) + analog_ops->init(); + + res = 0; + ac_control = ac; + + wake_lock_init(&idlelock, WAKE_LOCK_IDLE, "audio_pcm_idle"); + wake_lock_init(&wakelock, WAKE_LOCK_SUSPEND, "audio_pcm_suspend"); +done: + if ((res < 0) && ac) + audio_client_free(ac); + mutex_unlock(&audio_lock); + + pr_debug("[%s:%s] res = %d\n", __MM_FILE__, __func__, res); + return res; +} + +struct audio_config_data { + uint32_t device_id; + uint32_t sample_rate; + uint32_t offset; + uint32_t length; +}; + +struct audio_config_database { + uint8_t magic[8]; + uint32_t entry_count; + uint32_t unused; + struct audio_config_data entry[0]; +}; + +void *acdb_data; +const struct firmware *acdb_fw; +extern struct miscdevice q6_control_device; + +static int acdb_get_config_table(uint32_t device_id, uint32_t sample_rate) +{ + struct acdb_cmd_device_table rpc; + struct acdb_result res; + int r; + + pr_debug("[%s:%s] device_id = 0x%x, samplerate = %d\n", __MM_FILE__, + __func__, device_id, sample_rate); + if (q6audio_init()) + return 0; + + memset(audio_data, 0, 4096); + memset(&rpc, 0, sizeof(rpc)); + + rpc.size = sizeof(rpc) - (2 * sizeof(uint32_t)); + rpc.command_id = ACDB_GET_DEVICE_TABLE; + rpc.device_id = device_id; + rpc.sample_rate_id = sample_rate; + rpc.total_bytes = 4096; + rpc.unmapped_buf = audio_phys; + rpc.res_size = sizeof(res) - (2 * sizeof(uint32_t)); + + r = dal_call(acdb, ACDB_OP_IOCTL, 8, &rpc, sizeof(rpc), + &res, sizeof(res)); + + if ((r == sizeof(res)) && (res.dal_status == 0)) + return res.used_bytes; + + return -EIO; +} + +static uint32_t audio_rx_path_id = ADIE_PATH_HANDSET_RX; +static uint32_t audio_rx_device_id = ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR; +static uint32_t audio_rx_device_group = -1; +static uint32_t audio_tx_path_id = ADIE_PATH_HANDSET_TX; +static uint32_t audio_tx_device_id = ADSP_AUDIO_DEVICE_ID_HANDSET_MIC; +static uint32_t audio_tx_device_group = -1; + +static int qdsp6_devchg_notify(struct audio_client *ac, + uint32_t dev_type, uint32_t dev_id) +{ + struct adsp_device_switch_command rpc; + + if (dev_type != ADSP_AUDIO_RX_DEVICE && + dev_type != ADSP_AUDIO_TX_DEVICE) + return -EINVAL; + + memset(&rpc, 0, sizeof(rpc)); + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_PREPARE; + if (dev_type == ADSP_AUDIO_RX_DEVICE) { + rpc.old_device = audio_rx_device_id; + rpc.new_device = dev_id; + } else { + rpc.old_device = audio_tx_device_id; + rpc.new_device = dev_id; + } + rpc.device_class = 0; + rpc.device_type = dev_type; + pr_debug("[%s:%s] dev_id = 0x%x\n", __MM_FILE__, __func__, dev_id); + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +static int qdsp6_standby(struct audio_client *ac) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + return audio_command(ac, ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_STANDBY); +} + +static int qdsp6_start(struct audio_client *ac) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + return audio_command(ac, ADSP_AUDIO_IOCTL_CMD_DEVICE_SWITCH_COMMIT); +} + +static void audio_rx_analog_enable(int en) +{ + pr_debug("[%s:%s] audio_rx_device_id = 0x%x, en = %d\n", __MM_FILE__, + __func__, audio_rx_device_id, en); + switch (audio_rx_device_id) { + case ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_MONO: + case ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO: + case ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_SPKR: + if (analog_ops->headset_enable) + analog_ops->headset_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_MONO_HEADSET: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_STEREO_HEADSET: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_MONO_HEADSET: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_STEREO_HEADSET: + if (analog_ops->headset_enable) + analog_ops->headset_enable(en); + if (analog_ops->speaker_enable) + analog_ops->speaker_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO: + if (analog_ops->speaker_enable) + analog_ops->speaker_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_BT_SCO_SPKR: + if (analog_ops->bt_sco_enable) + analog_ops->bt_sco_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR: + if (analog_ops->receiver_enable) + analog_ops->receiver_enable(en); + break; + } +} + +static void audio_tx_analog_enable(int en) +{ + pr_debug("[%s:%s] audio_tx_device_id = 0x%x, en = %d\n", __MM_FILE__, + __func__, audio_tx_device_id, en); + switch (audio_tx_device_id) { + case ADSP_AUDIO_DEVICE_ID_HANDSET_MIC: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MIC: + if (analog_ops->int_mic_enable) + analog_ops->int_mic_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_HEADSET_MIC: + case ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_MIC: + case ADSP_AUDIO_DEVICE_ID_HANDSET_DUAL_MIC: + case ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_DUAL_MIC: + if (analog_ops->ext_mic_enable) + analog_ops->ext_mic_enable(en); + break; + case ADSP_AUDIO_DEVICE_ID_BT_SCO_MIC: + if (analog_ops->bt_sco_enable) + analog_ops->bt_sco_enable(en); + break; + } +} + +static int audio_update_acdb(uint32_t adev, uint32_t acdb_id) +{ + uint32_t sample_rate; + int sz; + + pr_debug("[%s:%s] adev = 0x%x, acdb_id = 0x%x\n", __MM_FILE__, + __func__, adev, acdb_id); + if (q6_device_to_dir(adev) == Q6_RX) { + rx_acdb = acdb_id; + sample_rate = q6_device_to_rate(adev); + } else { + + tx_acdb = acdb_id; + if (tx_clk_freq > 16000) + sample_rate = 48000; + else if (tx_clk_freq > 8000) + sample_rate = 16000; + else + sample_rate = 8000; + } + + if (acdb_id == 0) + acdb_id = q6_device_to_cad_id(adev); + + sz = acdb_get_config_table(acdb_id, sample_rate); + audio_set_table(ac_control, adev, sz); + + return 0; +} + +static void adie_rx_path_enable(uint32_t acdb_id) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + if (audio_rx_path_id) { + adie_enable(); + adie_set_path(adie, audio_rx_path_id, ADIE_PATH_RX); + adie_set_path_freq_plan(adie, ADIE_PATH_RX, 48000); + + adie_proceed_to_stage(adie, ADIE_PATH_RX, + ADIE_STAGE_DIGITAL_READY); + adie_proceed_to_stage(adie, ADIE_PATH_RX, + ADIE_STAGE_DIGITAL_ANALOG_READY); + } +} + +static void q6_rx_path_enable(int reconf, uint32_t acdb_id) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + if (!reconf) + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, audio_rx_device_id); + audio_update_acdb(audio_rx_device_id, acdb_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); +} + +static void _audio_rx_path_enable(int reconf, uint32_t acdb_id) +{ + pr_debug("[%s:%s] reconf = %d\n", __MM_FILE__, __func__, reconf); + q6_rx_path_enable(reconf, acdb_id); + if (audio_rx_path_id) + adie_rx_path_enable(acdb_id); + audio_rx_analog_enable(1); +} + +static void _audio_tx_path_enable(int reconf, uint32_t acdb_id) +{ + pr_debug("[%s:%s] reconf = %d, tx_clk_freq = %d\n", __MM_FILE__, + __func__, reconf, tx_clk_freq); + audio_tx_analog_enable(1); + + if (audio_tx_path_id) { + adie_enable(); + adie_set_path(adie, audio_tx_path_id, ADIE_PATH_TX); + + if (tx_clk_freq > 16000) + adie_set_path_freq_plan(adie, ADIE_PATH_TX, 48000); + else if (tx_clk_freq > 8000) + adie_set_path_freq_plan(adie, ADIE_PATH_TX, 16000); + else + adie_set_path_freq_plan(adie, ADIE_PATH_TX, 8000); + + adie_proceed_to_stage(adie, ADIE_PATH_TX, + ADIE_STAGE_DIGITAL_READY); + adie_proceed_to_stage(adie, ADIE_PATH_TX, + ADIE_STAGE_DIGITAL_ANALOG_READY); + } + + + if (!reconf) + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, + audio_tx_device_id); + audio_update_acdb(audio_tx_device_id, acdb_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + + audio_tx_mute(ac_control, audio_tx_device_id, tx_mute_status); +} + +static void _audio_rx_path_disable(void) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + audio_rx_analog_enable(0); + + if (audio_rx_path_id) { + adie_proceed_to_stage(adie, ADIE_PATH_RX, + ADIE_STAGE_ANALOG_OFF); + adie_proceed_to_stage(adie, ADIE_PATH_RX, + ADIE_STAGE_DIGITAL_OFF); + adie_disable(); + } +} + +static void _audio_tx_path_disable(void) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + audio_tx_analog_enable(0); + + if (audio_tx_path_id) { + adie_proceed_to_stage(adie, ADIE_PATH_TX, + ADIE_STAGE_ANALOG_OFF); + adie_proceed_to_stage(adie, ADIE_PATH_TX, + ADIE_STAGE_DIGITAL_OFF); + adie_disable(); + } +} + +static int icodec_rx_clk_refcount; +static int icodec_tx_clk_refcount; +static int ecodec_clk_refcount; +static int sdac_clk_refcount; + +static void ecodec_clk_enable(void) +{ + ecodec_clk_refcount++; + if (ecodec_clk_refcount == 1) { + clk_set_rate(ecodec_clk, 2048000); + clk_enable(ecodec_clk); + } +} +static void ecodec_clk_disable(int group_reset, int path) +{ + ecodec_clk_refcount--; + if (ecodec_clk_refcount == 0) { + clk_disable(ecodec_clk); + if (group_reset) { + if (path == ADSP_PATH_TX) + audio_tx_device_group = -1; + else + audio_rx_device_group = -1; + } + } +} +static void _audio_rx_clk_enable(void) +{ + uint32_t device_group = q6_device_to_codec(audio_rx_device_id); + + pr_debug("[%s:%s] rx_clk_refcount = %d\n", __MM_FILE__, __func__, + icodec_rx_clk_refcount); + switch(device_group) { + case Q6_ICODEC_RX: + icodec_rx_clk_refcount++; + if (icodec_rx_clk_refcount == 1) { + clk_set_rate(icodec_rx_clk, 12288000); + clk_enable(icodec_rx_clk); + } + break; + case Q6_ECODEC_RX: + ecodec_clk_enable(); + break; + case Q6_SDAC_RX: + sdac_clk_refcount++; + if (sdac_clk_refcount == 1) { + clk_set_rate(sdac_clk, 12288000); + clk_enable(sdac_clk); + } + break; + default: + return; + } + audio_rx_device_group = device_group; +} + +static void _audio_tx_clk_enable(void) +{ + uint32_t device_group = q6_device_to_codec(audio_tx_device_id); + uint32_t icodec_tx_clk_rate; + + pr_debug("[%s:%s] tx_clk_refcount = %d\n", __MM_FILE__, __func__, + icodec_tx_clk_refcount); + switch (device_group) { + case Q6_ICODEC_TX: + icodec_tx_clk_refcount++; + if (icodec_tx_clk_refcount == 1) { + if (tx_clk_freq > 16000) + icodec_tx_clk_rate = 48000; + else if (tx_clk_freq > 8000) + icodec_tx_clk_rate = 16000; + else + icodec_tx_clk_rate = 8000; + + clk_set_rate(icodec_tx_clk, icodec_tx_clk_rate * 256); + clk_enable(icodec_tx_clk); + } + break; + case Q6_ECODEC_TX: + ecodec_clk_enable(); + break; + case Q6_SDAC_TX: + /* TODO: In QCT BSP, clk rate was set to 20480000 */ + sdac_clk_refcount++; + if (sdac_clk_refcount == 1) { + clk_set_rate(sdac_clk, 12288000); + clk_enable(sdac_clk); + } + break; + default: + return; + } + audio_tx_device_group = device_group; +} + +static void _audio_rx_clk_disable(void) +{ + pr_debug("[%s:%s] rx_clk_refcount = %d\n", __MM_FILE__, __func__, + icodec_rx_clk_refcount); + switch (audio_rx_device_group) { + case Q6_ICODEC_RX: + icodec_rx_clk_refcount--; + if (icodec_rx_clk_refcount == 0) { + clk_disable(icodec_rx_clk); + audio_rx_device_group = -1; + } + break; + case Q6_ECODEC_RX: + ecodec_clk_disable(1, ADSP_PATH_RX); + break; + case Q6_SDAC_RX: + sdac_clk_refcount--; + if (sdac_clk_refcount == 0) { + clk_disable(sdac_clk); + audio_rx_device_group = -1; + } + break; + default: + pr_err("[%s:%s] invalid rx device group %d\n", __MM_FILE__, + __func__, audio_rx_device_group); + break; + } +} + +static void _audio_tx_clk_disable(void) +{ + pr_debug("[%s:%s] tx_clk_refcount = %d\n", __MM_FILE__, __func__, + icodec_tx_clk_refcount); + switch (audio_tx_device_group) { + case Q6_ICODEC_TX: + icodec_tx_clk_refcount--; + if (icodec_tx_clk_refcount == 0) { + clk_disable(icodec_tx_clk); + audio_tx_device_group = -1; + } + break; + case Q6_ECODEC_TX: + ecodec_clk_disable(1, ADSP_PATH_TX); + break; + case Q6_SDAC_TX: + sdac_clk_refcount--; + if (sdac_clk_refcount == 0) { + clk_disable(sdac_clk); + audio_tx_device_group = -1; + } + break; + default: + pr_err("[%s:%s] invalid tx device group %d\n", + __MM_FILE__, __func__, audio_tx_device_group); + break; + } +} + +static void _audio_rx_clk_reinit(uint32_t rx_device, uint32_t acdb_id) +{ + uint32_t device_group = q6_device_to_codec(rx_device); + + pr_debug("[%s:%s] rx_device = 0x%x\n", __MM_FILE__, __func__, + rx_device); + if (device_group != audio_rx_device_group) + _audio_rx_clk_disable(); + + audio_rx_device_id = rx_device; + audio_rx_path_id = q6_device_to_path(rx_device, acdb_id); + + if (device_group != audio_rx_device_group) + _audio_rx_clk_enable(); + +} + +static void _audio_tx_clk_reinit(uint32_t tx_device, uint32_t acdb_id) +{ + uint32_t device_group = q6_device_to_codec(tx_device); + + pr_debug("[%s:%s] tx_device = 0x%x\n", __MM_FILE__, __func__, + tx_device); + if (device_group != audio_tx_device_group) + _audio_tx_clk_disable(); + + audio_tx_device_id = tx_device; + audio_tx_path_id = q6_device_to_path(tx_device, acdb_id); + + if (device_group != audio_tx_device_group) + _audio_tx_clk_enable(); +} + +static DEFINE_MUTEX(audio_path_lock); +static int audio_rx_path_refcount; +static int audio_tx_path_refcount; + +static int audio_rx_path_enable(int en, uint32_t acdb_id) +{ + pr_debug("[%s:%s] en = %d\n", __MM_FILE__, __func__, en); + mutex_lock(&audio_path_lock); + if (en) { + audio_rx_path_refcount++; + if (audio_rx_path_refcount == 1) { + _audio_rx_clk_enable(); + _audio_rx_path_enable(0, acdb_id); + } + } else { + audio_rx_path_refcount--; + if (audio_rx_path_refcount == 0) { + _audio_rx_path_disable(); + _audio_rx_clk_disable(); + } + } + mutex_unlock(&audio_path_lock); + return 0; +} + +static int audio_tx_path_enable(int en, uint32_t acdb_id) +{ + pr_debug("[%s:%s] en = %d\n", __MM_FILE__, __func__, en); + mutex_lock(&audio_path_lock); + if (en) { + audio_tx_path_refcount++; + if (audio_tx_path_refcount == 1) { + _audio_tx_clk_enable(); + _audio_tx_path_enable(0, acdb_id); + } + } else { + audio_tx_path_refcount--; + if (audio_tx_path_refcount == 0) { + _audio_tx_path_disable(); + _audio_tx_clk_disable(); + } + } + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_update_acdb(uint32_t id_src, uint32_t id_dst) +{ + int res; + + pr_debug("[%s:%s] id_src = 0x%x\n, id_dst = 0x%x\n", __MM_FILE__, + __func__, id_src, id_dst); + if (q6audio_init()) + return 0; + + mutex_lock(&audio_path_lock); + + if (q6_device_to_dir(id_dst) == Q6_RX) + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, id_dst); + else + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, id_dst); + res = audio_update_acdb(id_dst, id_src); + if (res) + goto done; + + qdsp6_standby(ac_control); + qdsp6_start(ac_control); +done: + mutex_unlock(&audio_path_lock); + return res; +} + +int q6audio_set_tx_mute(int mute) +{ + uint32_t adev; + int rc; + + if (q6audio_init()) + return 0; + + mutex_lock(&audio_path_lock); + + if (mute == tx_mute_status) { + mutex_unlock(&audio_path_lock); + return 0; + } + + adev = audio_tx_device_id; + rc = audio_tx_mute(ac_control, adev, mute); + + /* DSP caches the requested MUTE state when it cannot apply the state + immediately. In that case, it returns EUNSUPPORTED and applies the + cached state later */ + if ((rc == ADSP_AUDIO_STATUS_SUCCESS) || + (rc == ADSP_AUDIO_STATUS_EUNSUPPORTED)) { + pr_debug("[%s:%s] return status = %d\n", + __MM_FILE__, __func__, rc); + tx_mute_status = mute; + } + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_stream_volume(struct audio_client *ac, int vol) +{ + if (vol > 1200 || vol < -4000) { + pr_err("[%s:%s] unsupported volume level %d\n", __MM_FILE__, + __func__, vol); + return -EINVAL; + } + mutex_lock(&audio_path_lock); + audio_stream_mute(ac, 0); + audio_stream_volume(ac, vol); + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_rx_volume(int level) +{ + uint32_t adev; + int vol; + + pr_debug("[%s:%s] level = %d\n", __MM_FILE__, __func__, level); + if (q6audio_init()) + return 0; + + if (level < 0 || level > 100) + return -EINVAL; + + mutex_lock(&audio_path_lock); + adev = ADSP_AUDIO_DEVICE_ID_VOICE; + + if (level) { + vol = q6_device_volume(audio_rx_device_id, level); + audio_rx_mute(ac_control, adev, 0); + audio_rx_volume(ac_control, adev, vol); + } else + audio_rx_mute(ac_control, adev, 1); + + rx_vol_level = level; + mutex_unlock(&audio_path_lock); + return 0; +} + +static void do_rx_routing(uint32_t device_id, uint32_t acdb_id) +{ + pr_debug("[%s:%s] device_id = 0x%x, acdb_id = 0x%x\n", __MM_FILE__, + __func__, device_id, acdb_id); + if (device_id == audio_rx_device_id && + audio_rx_path_id == q6_device_to_path(device_id, acdb_id)) { + if (acdb_id != rx_acdb) { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, device_id); + audio_update_acdb(device_id, acdb_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + } + return; + } + + if (audio_rx_path_refcount > 0) { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, device_id); + _audio_rx_path_disable(); + _audio_rx_clk_reinit(device_id, acdb_id); + _audio_rx_path_enable(1, acdb_id); + } else { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_RX_DEVICE, + device_id); + audio_update_acdb(device_id, acdb_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + audio_rx_device_id = device_id; + audio_rx_path_id = q6_device_to_path(device_id, acdb_id); + } +} + +static void do_tx_routing(uint32_t device_id, uint32_t acdb_id) +{ + pr_debug("[%s:%s] device_id = 0x%x, acdb_id = 0x%x\n", __MM_FILE__, + __func__, device_id, acdb_id); + if (device_id == audio_tx_device_id && + audio_tx_path_id == q6_device_to_path(device_id, acdb_id)) { + if (acdb_id != tx_acdb) { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, + device_id); + audio_update_acdb(device_id, acdb_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + } + return; + } + + if (audio_tx_path_refcount > 0) { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, device_id); + _audio_tx_path_disable(); + _audio_tx_clk_reinit(device_id, acdb_id); + _audio_tx_path_enable(1, acdb_id); + } else { + qdsp6_devchg_notify(ac_control, ADSP_AUDIO_TX_DEVICE, + device_id); + audio_update_acdb(device_id, acdb_id); + qdsp6_standby(ac_control); + qdsp6_start(ac_control); + audio_tx_device_id = device_id; + audio_tx_path_id = q6_device_to_path(device_id, acdb_id); + tx_acdb = acdb_id; + } +} + +int q6audio_do_routing(uint32_t device_id, uint32_t acdb_id) +{ + if (q6audio_init()) + return 0; + + mutex_lock(&audio_path_lock); + + switch(q6_device_to_dir(device_id)) { + case Q6_RX: + do_rx_routing(device_id, acdb_id); + break; + case Q6_TX: + do_tx_routing(device_id, acdb_id); + break; + } + + mutex_unlock(&audio_path_lock); + return 0; +} + +int q6audio_set_route(const char *name) +{ + uint32_t route; + if (!strcmp(name, "speaker")) { + route = ADIE_PATH_SPEAKER_STEREO_RX; + } else if (!strcmp(name, "headphones")) { + route = ADIE_PATH_HEADSET_STEREO_RX; + } else if (!strcmp(name, "handset")) { + route = ADIE_PATH_HANDSET_RX; + } else { + return -EINVAL; + } + + mutex_lock(&audio_path_lock); + if (route == audio_rx_path_id) + goto done; + + audio_rx_path_id = route; + + if (audio_rx_path_refcount > 0) { + _audio_rx_path_disable(); + _audio_rx_path_enable(1, 0); + } + if (audio_tx_path_refcount > 0) { + _audio_tx_path_disable(); + _audio_tx_path_enable(1, 0); + } +done: + mutex_unlock(&audio_path_lock); + return 0; +} + +static int audio_stream_equalizer(struct audio_client *ac, void *eq_config) +{ + int i; + struct adsp_set_equalizer_command rpc; + struct adsp_audio_eq_stream_config *eq_cfg; + eq_cfg = (struct adsp_audio_eq_stream_config *) eq_config; + + memset(&rpc, 0, sizeof(rpc)); + + rpc.hdr.opcode = ADSP_AUDIO_IOCTL_SET_SESSION_EQ_CONFIG; + rpc.enable = eq_cfg->enable; + rpc.num_bands = eq_cfg->num_bands; + for (i = 0; i < eq_cfg->num_bands; i++) { + rpc.eq_bands[i].band_idx = eq_cfg->eq_bands[i].band_idx; + rpc.eq_bands[i].filter_type = eq_cfg->eq_bands[i].filter_type; + rpc.eq_bands[i].center_freq_hz = + eq_cfg->eq_bands[i].center_freq_hz; + rpc.eq_bands[i].filter_gain = eq_cfg->eq_bands[i].filter_gain; + rpc.eq_bands[i].q_factor = eq_cfg->eq_bands[i].q_factor; + } + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} + +int q6audio_set_stream_eq_pcm(struct audio_client *ac, void *eq_config) +{ + int rc = 0; + mutex_lock(&audio_path_lock); + rc = audio_stream_equalizer(ac, eq_config); + mutex_unlock(&audio_path_lock); + return rc; +} + +struct audio_client *q6audio_open_auxpcm(uint32_t rate, + uint32_t channels, uint32_t flags, uint32_t acdb_id) +{ + int rc, retry = 5; + struct audio_client *ac; + + pr_debug("[%s:%s] rate = %d, channels = %d\n", __MM_FILE__, __func__, + rate, channels); + if (q6audio_init()) + return NULL; + ac = audio_client_alloc(0); + if (!ac) + return NULL; + + ac->flags = flags; + + mutex_lock(&audio_path_lock); + + if (ac->flags & AUDIO_FLAG_WRITE) { + audio_tx_path_refcount++; + if (audio_tx_path_refcount == 1) { + tx_clk_freq = rate; + _audio_tx_clk_enable(); + _audio_tx_path_enable(0, acdb_id); + } + } else { + audio_rx_path_refcount++; + if (audio_rx_path_refcount == 1) { + _audio_rx_clk_enable(); + _audio_rx_path_enable(0, acdb_id); + } + } + + ecodec_clk_enable(); + + for (retry = 5;; retry--) { + if (ac->flags & AUDIO_FLAG_WRITE) + rc = audio_auxpcm_out_open(ac, rate, channels); + else + rc = audio_auxpcm_in_open(ac, rate, channels); + if (rc == 0) + break; + if (retry == 0) + q6audio_dsp_not_responding(); + + pr_err("[%s:%s] open pcm error %d, retrying\n", + __MM_FILE__, __func__, rc); + msleep(1); + } + + mutex_unlock(&audio_path_lock); + + for (retry = 5;; retry--) { + rc = audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + if (rc == 0) + break; + if (retry == 0) + q6audio_dsp_not_responding(); + + pr_err("[%s:%s] stream start error %d, retrying\n", + __MM_FILE__, __func__, rc); + } + audio_prevent_sleep(); + return ac; + +} + +struct audio_client *q6audio_open_pcm(uint32_t bufsz, uint32_t rate, + uint32_t channels, uint32_t flags, uint32_t acdb_id) +{ + int rc, retry = 5; + struct audio_client *ac; + + pr_debug("[%s:%s] bufsz = %d, rate = %d, channels = %d\n", __MM_FILE__, + __func__, bufsz, rate, channels); + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(bufsz); + if (!ac) + return 0; + + ac->flags = flags; + + mutex_lock(&audio_path_lock); + + if (ac->flags & AUDIO_FLAG_WRITE) { + audio_rx_path_refcount++; + if (audio_rx_path_refcount == 1) { + _audio_rx_clk_enable(); + q6_rx_path_enable(0, acdb_id); + adie_rx_path_enable(acdb_id); + } + } else { + /* TODO: consider concurrency with voice call */ + audio_tx_path_refcount++; + if (audio_tx_path_refcount == 1) { + tx_clk_freq = rate; + _audio_tx_clk_enable(); + _audio_tx_path_enable(0, acdb_id); + } + } + + for (retry = 5;;retry--) { + if (ac->flags & AUDIO_FLAG_WRITE) + rc = audio_out_open(ac, bufsz, rate, channels); + else + rc = audio_in_open(ac, bufsz, flags, rate, channels); + if (rc == 0) + break; + if (retry == 0) + q6audio_dsp_not_responding(); + + pr_err("[%s:%s] open pcm error %d, retrying\n", + __MM_FILE__, __func__, rc); + msleep(1); + } + + if (ac->flags & AUDIO_FLAG_WRITE) { + if (audio_rx_path_refcount == 1) + audio_rx_analog_enable(1); + } + mutex_unlock(&audio_path_lock); + + for (retry = 5;;retry--) { + rc = audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + if (rc == 0) + break; + if (retry == 0) + q6audio_dsp_not_responding(); + + pr_err("[%s:%s] stream start error %d, retrying\n", + __MM_FILE__, __func__, rc); + } + + if (!(ac->flags & AUDIO_FLAG_WRITE)) { + ac->buf[0].used = 1; + ac->buf[1].used = 1; + q6audio_read(ac, &ac->buf[0]); + q6audio_read(ac, &ac->buf[1]); + } + + audio_prevent_sleep(); + return ac; +} + +int q6audio_close(struct audio_client *ac) +{ + audio_close(ac); + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(0, 0); + else + audio_tx_path_enable(0, 0); + audio_client_free(ac); + audio_allow_sleep(); + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return 0; +} + +int q6audio_auxpcm_close(struct audio_client *ac) +{ + audio_close(ac); + if (ac->flags & AUDIO_FLAG_WRITE) { + audio_tx_path_enable(0, 0); + ecodec_clk_disable(0, ADSP_PATH_RX); + } else { + audio_rx_path_enable(0, 0); + ecodec_clk_disable(0, ADSP_PATH_TX); + } + + audio_client_free(ac); + audio_allow_sleep(); + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + return 0; +} +struct audio_client *q6voice_open(uint32_t flags) +{ + struct audio_client *ac; + + pr_debug("[%s:%s] flags = %d\n", __MM_FILE__, __func__, flags); + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(0); + if (!ac) + return 0; + + ac->flags = flags; + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(1, rx_acdb); + else { + if (!audio_tx_path_refcount) + tx_clk_freq = 8000; + audio_tx_path_enable(1, tx_acdb); + } + + return ac; +} + +int q6voice_close(struct audio_client *ac) +{ + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(0, 0); + else + audio_tx_path_enable(0, 0); + + tx_mute_status = 0; + audio_client_free(ac); + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + return 0; +} + +struct audio_client *q6audio_open_mp3(uint32_t bufsz, uint32_t rate, + uint32_t channels, uint32_t acdb_id) +{ + struct audio_client *ac; + + pr_debug("[%s:%s] bufsz = %d, rate = %d\n, channels = %d", + __MM_FILE__, __func__, bufsz, rate, channels); + + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(bufsz); + if (!ac) + return 0; + + ac->flags = AUDIO_FLAG_WRITE; + audio_rx_path_enable(1, acdb_id); + + audio_mp3_open(ac, bufsz, rate, channels); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + mutex_lock(&audio_path_lock); + audio_rx_mute(ac_control, audio_rx_device_id, 0); + audio_rx_volume(ac_control, audio_rx_device_id, + q6_device_volume(audio_rx_device_id, rx_vol_level)); + mutex_unlock(&audio_path_lock); + return ac; +} + +struct audio_client *q6audio_open_dtmf(uint32_t rate, + uint32_t channels, uint32_t acdb_id) +{ + struct audio_client *ac; + + pr_debug("[%s:%s] rate = %d\n, channels = %d", __MM_FILE__, __func__, + rate, channels); + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(0); + if (!ac) + return 0; + + ac->flags = AUDIO_FLAG_WRITE; + audio_rx_path_enable(1, acdb_id); + + audio_dtmf_open(ac, rate, channels); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + mutex_lock(&audio_path_lock); + audio_rx_mute(ac_control, audio_rx_device_id, 0); + audio_rx_volume(ac_control, audio_rx_device_id, + q6_device_volume(audio_rx_device_id, rx_vol_level)); + mutex_unlock(&audio_path_lock); + + return ac; +} + +int q6audio_play_dtmf(struct audio_client *ac, uint16_t dtmf_hi, + uint16_t dtmf_low, uint16_t duration, uint16_t rx_gain) +{ + struct adsp_audio_dtmf_start_command dtmf_cmd; + + pr_debug("[%s:%s] high = %d, low = %d\n", __MM_FILE__, __func__, + dtmf_hi, dtmf_low); + + dtmf_cmd.hdr.opcode = ADSP_AUDIO_IOCTL_CMD_SESSION_DTMF_START; + dtmf_cmd.hdr.response_type = ADSP_AUDIO_RESPONSE_COMMAND; + dtmf_cmd.tone1_hz = dtmf_hi; + dtmf_cmd.tone2_hz = dtmf_low; + dtmf_cmd.duration_usec = duration * 1000; + dtmf_cmd.gain_mb = rx_gain; + + return audio_ioctl(ac, &dtmf_cmd, + sizeof(struct adsp_audio_dtmf_start_command)); + +} + +int q6audio_mp3_close(struct audio_client *ac) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + audio_close(ac); + audio_rx_path_enable(0, 0); + audio_client_free(ac); + return 0; +} + + +struct audio_client *q6audio_open_aac(uint32_t bufsz, uint32_t samplerate, + uint32_t channels, uint32_t bitrate, + uint32_t stream_format, uint32_t flags, + uint32_t acdb_id) +{ + struct audio_client *ac; + + pr_debug("[%s:%s] bufsz = %d, samplerate = %d, channels = %d\n", + __MM_FILE__, __func__, bufsz, samplerate, channels); + + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(bufsz); + if (!ac) + return 0; + + ac->flags = flags; + + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(1, acdb_id); + else{ + if (!audio_tx_path_refcount) + tx_clk_freq = 48000; + audio_tx_path_enable(1, acdb_id); + } + + audio_aac_open(ac, bufsz, samplerate, channels, bitrate, flags, + stream_format); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + if (!(ac->flags & AUDIO_FLAG_WRITE)) { + ac->buf[0].used = 1; + ac->buf[1].used = 1; + q6audio_read(ac, &ac->buf[0]); + q6audio_read(ac, &ac->buf[1]); + } + audio_prevent_sleep(); + return ac; +} + + +struct audio_client *q6audio_open_qcp(uint32_t bufsz, uint32_t min_rate, + uint32_t max_rate, uint32_t flags, + uint32_t format, uint32_t acdb_id) +{ + struct audio_client *ac; + + pr_debug("[%s:%s] bufsz = %d\n", __MM_FILE__, __func__, bufsz); + + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(bufsz); + if (!ac) + return 0; + + ac->flags = flags; + + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(1, acdb_id); + else{ + if (!audio_tx_path_refcount) + tx_clk_freq = 8000; + audio_tx_path_enable(1, acdb_id); + } + + audio_qcp_open(ac, bufsz, min_rate, max_rate, flags, format); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + if (!(ac->flags & AUDIO_FLAG_WRITE)) { + ac->buf[0].used = 1; + ac->buf[1].used = 1; + q6audio_read(ac, &ac->buf[0]); + q6audio_read(ac, &ac->buf[1]); + } + audio_prevent_sleep(); + return ac; +} + +struct audio_client *q6audio_open_amrnb(uint32_t bufsz, uint32_t enc_mode, + uint32_t dtx_mode_enable, + uint32_t flags, uint32_t acdb_id) +{ + struct audio_client *ac; + + pr_debug("[%s:%s] bufsz = %d, dtx_mode = %d\n", __MM_FILE__, + __func__, bufsz, dtx_mode_enable); + + if (q6audio_init()) + return 0; + + ac = audio_client_alloc(bufsz); + if (!ac) + return 0; + + ac->flags = flags; + if (ac->flags & AUDIO_FLAG_WRITE) + audio_rx_path_enable(1, acdb_id); + else{ + if (!audio_tx_path_refcount) + tx_clk_freq = 8000; + audio_tx_path_enable(1, acdb_id); + } + + audio_amrnb_open(ac, bufsz, enc_mode, flags, dtx_mode_enable); + audio_command(ac, ADSP_AUDIO_IOCTL_CMD_SESSION_START); + + if (!(ac->flags & AUDIO_FLAG_WRITE)) { + ac->buf[0].used = 1; + ac->buf[1].used = 1; + q6audio_read(ac, &ac->buf[0]); + q6audio_read(ac, &ac->buf[1]); + } + audio_prevent_sleep(); + return ac; +} + +int q6audio_async(struct audio_client *ac) +{ + struct adsp_command_hdr rpc; + pr_debug("[%s:%s] ac = %p\n", __MM_FILE__, __func__, ac); + memset(&rpc, 0, sizeof(rpc)); + rpc.opcode = ADSP_AUDIO_IOCTL_CMD_STREAM_EOS; + rpc.response_type = ADSP_AUDIO_RESPONSE_ASYNC; + return audio_ioctl(ac, &rpc, sizeof(rpc)); +} diff --git a/arch/arm/mach-msm/qdsp6/q6audio_devices.h b/arch/arm/mach-msm/qdsp6/q6audio_devices.h new file mode 100644 index 00000000000..d316ab0e0de --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/q6audio_devices.h @@ -0,0 +1,334 @@ +/* arch/arm/mach-msm/qdsp6/q6audio_devices.h + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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. + * + */ + +struct q6_device_info { + uint32_t id; + uint32_t cad_id; + uint32_t path; + uint32_t rate; + uint8_t dir; + uint8_t codec; + uint8_t hw; +}; + +#define Q6_ICODEC_RX 0 +#define Q6_ICODEC_TX 1 +#define Q6_ECODEC_RX 2 +#define Q6_ECODEC_TX 3 +#define Q6_SDAC_RX 6 +#define Q6_SDAC_TX 7 +#define Q6_CODEC_NONE 255 + +#define Q6_TX 1 +#define Q6_RX 2 +#define Q6_TX_RX 3 + +#define Q6_HW_HANDSET 0 +#define Q6_HW_HEADSET 1 +#define Q6_HW_SPEAKER 2 +#define Q6_HW_TTY 3 +#define Q6_HW_BT_SCO 4 +#define Q6_HW_BT_A2DP 5 + +#define Q6_HW_COUNT 6 + +#define CAD_HW_DEVICE_ID_HANDSET_MIC 0x01 +#define CAD_HW_DEVICE_ID_HANDSET_SPKR 0x02 +#define CAD_HW_DEVICE_ID_HEADSET_MIC 0x03 +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_MONO 0x04 +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_STEREO 0x05 +#define CAD_HW_DEVICE_ID_SPKR_PHONE_MIC 0x06 +#define CAD_HW_DEVICE_ID_SPKR_PHONE_MONO 0x07 +#define CAD_HW_DEVICE_ID_SPKR_PHONE_STEREO 0x08 +#define CAD_HW_DEVICE_ID_BT_SCO_MIC 0x09 +#define CAD_HW_DEVICE_ID_BT_SCO_SPKR 0x0A +#define CAD_HW_DEVICE_ID_BT_A2DP_SPKR 0x0B +#define CAD_HW_DEVICE_ID_TTY_HEADSET_MIC 0x0C +#define CAD_HW_DEVICE_ID_TTY_HEADSET_SPKR 0x0D + +#define CAD_HW_DEVICE_ID_DEFAULT_TX 0x0E +#define CAD_HW_DEVICE_ID_DEFAULT_RX 0x0F + + +#define CAD_HW_DEVICE_ID_SPKR_PHONE_DUAL_MIC_BROADSIDE 0x2B +#define CAD_HW_DEVICE_ID_SPKR_PHONE_DUAL_MIC_ENDFIRE 0x2D +#define CAD_HW_DEVICE_ID_HANDSET_DUAL_MIC_BROADSIDE 0x2C +#define CAD_HW_DEVICE_ID_HANDSET_DUAL_MIC_ENDFIRE 0x2E + +/* Logical Device to indicate A2DP routing */ +#define CAD_HW_DEVICE_ID_BT_A2DP_TX 0x10 +#define CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_MONO_RX 0x11 +#define CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_STEREO_RX 0x12 +#define CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_MONO_RX 0x13 +#define CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_STEREO_RX 0x14 + +#define CAD_HW_DEVICE_ID_VOICE 0x15 + +#define CAD_HW_DEVICE_ID_I2S_RX 0x20 +#define CAD_HW_DEVICE_ID_I2S_TX 0x21 + +/* AUXPGA */ +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_STEREO_LB 0x22 +#define CAD_HW_DEVICE_ID_HEADSET_SPKR_MONO_LB 0x23 +#define CAD_HW_DEVICE_ID_SPEAKER_SPKR_STEREO_LB 0x24 +#define CAD_HW_DEVICE_ID_SPEAKER_SPKR_MONO_LB 0x25 + +#define CAD_HW_DEVICE_ID_NULL_RX 0x2A + +#define CAD_HW_DEVICE_ID_MAX_NUM 0x2F + +#define CAD_HW_DEVICE_ID_INVALID 0xFF + +#define CAD_RX_DEVICE 0x00 +#define CAD_TX_DEVICE 0x01 + +static struct q6_device_info q6_audio_devices[] = { + { + .id = ADSP_AUDIO_DEVICE_ID_HANDSET_SPKR, + .cad_id = CAD_HW_DEVICE_ID_HANDSET_SPKR, + .path = ADIE_PATH_HANDSET_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HANDSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_MONO, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_SPKR_MONO, + .path = ADIE_PATH_HEADSET_MONO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HEADSET_SPKR_STEREO, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_SPKR_STEREO, + .path = ADIE_PATH_HEADSET_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_MONO, + .path = ADIE_PATH_SPEAKER_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_STEREO, + .path = ADIE_PATH_SPEAKER_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_MONO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_MONO_RX, + .path = ADIE_PATH_SPKR_MONO_HDPH_MONO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MONO_W_STEREO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_MONO_RX, + .path = ADIE_PATH_SPKR_MONO_HDPH_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_MONO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_MONO_PLUS_SPKR_STEREO_RX, + .path = ADIE_PATH_SPKR_STEREO_HDPH_MONO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_STEREO_W_STEREO_HEADSET, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_STEREO_PLUS_SPKR_STEREO_RX, + .path = ADIE_PATH_SPKR_STEREO_HDPH_STEREO_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_SPKR, + .cad_id = CAD_HW_DEVICE_ID_TTY_HEADSET_SPKR, + .path = ADIE_PATH_TTY_HEADSET_RX, + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ICODEC_RX, + .hw = Q6_HW_TTY, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HANDSET_MIC, + .cad_id = CAD_HW_DEVICE_ID_HANDSET_MIC, + .path = ADIE_PATH_HANDSET_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HANDSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HEADSET_MIC, + .cad_id = CAD_HW_DEVICE_ID_HEADSET_MIC, + .path = ADIE_PATH_HEADSET_MONO_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_MIC, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_MIC, + .path = ADIE_PATH_SPEAKER_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HANDSET_DUAL_MIC, + .cad_id = CAD_HW_DEVICE_ID_HANDSET_DUAL_MIC_ENDFIRE, + .path = ADIE_CODEC_HANDSET_SPKR_EF_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HANDSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_HANDSET_DUAL_MIC, + .cad_id = CAD_HW_DEVICE_ID_HANDSET_DUAL_MIC_BROADSIDE, + .path = ADIE_CODEC_HANDSET_SPKR_BS_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HANDSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_DUAL_MIC, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_DUAL_MIC_ENDFIRE, + .path = ADIE_CODEC_HANDSET_SPKR_EF_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_SPKR_PHONE_DUAL_MIC, + .cad_id = CAD_HW_DEVICE_ID_SPKR_PHONE_DUAL_MIC_BROADSIDE, + .path = ADIE_CODEC_HANDSET_SPKR_BS_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_TTY_HEADSET_MIC, + .cad_id = CAD_HW_DEVICE_ID_TTY_HEADSET_MIC, + .path = ADIE_PATH_TTY_HEADSET_TX, + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ICODEC_TX, + .hw = Q6_HW_HEADSET, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_BT_SCO_SPKR, + .cad_id = CAD_HW_DEVICE_ID_BT_SCO_SPKR, + .path = 0, /* XXX */ + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ECODEC_RX, + .hw = Q6_HW_BT_SCO, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_BT_A2DP_SPKR, + .cad_id = CAD_HW_DEVICE_ID_BT_A2DP_SPKR, + .path = 0, /* XXX */ + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_ECODEC_RX, + .hw = Q6_HW_BT_A2DP, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_BT_SCO_MIC, + .cad_id = CAD_HW_DEVICE_ID_BT_SCO_MIC, + .path = 0, /* XXX */ + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ECODEC_TX, + .hw = Q6_HW_BT_SCO, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_I2S_SPKR, + .cad_id = CAD_HW_DEVICE_ID_I2S_RX, + .path = 0, /* XXX */ + .rate = 48000, + .dir = Q6_RX, + .codec = Q6_SDAC_RX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_I2S_MIC, + .cad_id = CAD_HW_DEVICE_ID_I2S_TX, + .path = 0, /* XXX */ + .rate = 16000, + .dir = Q6_TX, + .codec = Q6_SDAC_TX, + .hw = Q6_HW_SPEAKER, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_AUXPCM_RX, + .cad_id = CAD_HW_DEVICE_ID_BT_SCO_SPKR, + .path = 0, /* XXX */ + .rate = 8000, + .dir = Q6_RX, + .codec = Q6_ECODEC_RX, + .hw = Q6_HW_BT_SCO, + }, + { + .id = ADSP_AUDIO_DEVICE_ID_AUXPCM_TX, + .cad_id = CAD_HW_DEVICE_ID_BT_SCO_MIC, + .path = 0, /* XXX */ + .rate = 8000, + .dir = Q6_TX, + .codec = Q6_ECODEC_TX, + .hw = Q6_HW_BT_SCO, + }, + { + .id = 0, + .cad_id = 0, + .path = 0, + .rate = 8000, + .dir = 0, + .codec = Q6_CODEC_NONE, + .hw = 0, + }, +}; + diff --git a/arch/arm/mach-msm/qdsp6/qcelp_in.c b/arch/arm/mach-msm/qdsp6/qcelp_in.c new file mode 100644 index 00000000000..ca0ab1a5654 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/qcelp_in.c @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "dal_audio_format.h" +#include + +#define QCELP_FC_BUFF_CNT 10 +#define QCELP_READ_TIMEOUT 2000 +struct qcelp_fc_buff { + struct mutex lock; + int empty; + void *data; + int size; + int actual_size; +}; + +struct qcelp_fc { + struct task_struct *task; + wait_queue_head_t fc_wq; + struct qcelp_fc_buff fc_buff[QCELP_FC_BUFF_CNT]; + int buff_index; +}; + +struct qcelp { + struct mutex lock; + struct msm_audio_qcelp_enc_config cfg; + struct msm_audio_stream_config str_cfg; + struct audio_client *audio_client; + struct msm_voicerec_mode voicerec_mode; + struct qcelp_fc *qcelp_fc; +}; + + +static int q6_qcelp_flowcontrol(void *data) +{ + struct audio_client *ac; + struct audio_buffer *ab; + struct qcelp *qcelp = data; + int buff_index = 0; + int xfer = 0; + struct qcelp_fc *fc; + + + ac = qcelp->audio_client; + fc = qcelp->qcelp_fc; + if (!ac) { + pr_err("[%s:%s] audio_client is NULL\n", __MM_FILE__, __func__); + return 0; + } + + while (!kthread_should_stop()) { + ab = ac->buf + ac->cpu_buf; + if (ab->used) + wait_event(ac->wait, (ab->used == 0)); + + pr_debug("[%s:%s] ab->data = %p, cpu_buf = %d", __MM_FILE__, + __func__, ab->data, ac->cpu_buf); + xfer = ab->actual_size; + + + mutex_lock(&(fc->fc_buff[buff_index].lock)); + if (!fc->fc_buff[buff_index].empty) { + pr_err("[%s:%s] flow control buffer[%d] not read!\n", + __MM_FILE__, __func__, buff_index); + } + + if (fc->fc_buff[buff_index].size < xfer) { + pr_err("[%s:%s] buffer %d too small\n", __MM_FILE__, + __func__, buff_index); + memcpy(fc->fc_buff[buff_index].data, ab->data, + fc->fc_buff[buff_index].size); + fc->fc_buff[buff_index].empty = 0; + fc->fc_buff[buff_index].actual_size = + fc->fc_buff[buff_index].size; + } else { + memcpy(fc->fc_buff[buff_index].data, ab->data, xfer); + fc->fc_buff[buff_index].empty = 0; + fc->fc_buff[buff_index].actual_size = xfer; + } + mutex_unlock(&(fc->fc_buff[buff_index].lock)); + /*wake up client, if any*/ + wake_up(&fc->fc_wq); + + buff_index++; + if (buff_index >= QCELP_FC_BUFF_CNT) + buff_index = 0; + + ab->used = 1; + + q6audio_read(ac, ab); + ac->cpu_buf ^= 1; + } + + return 0; +} +static long q6_qcelp_in_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct qcelp *qcelp = file->private_data; + int rc = 0; + int i = 0; + struct qcelp_fc *fc; + int size = 0; + + mutex_lock(&qcelp->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: + pr_debug("[%s:%s] SET_VOLUME\n", __MM_FILE__, __func__); + break; + case AUDIO_GET_STATS: + { + struct msm_audio_stats stats; + pr_debug("[%s:%s] GET_STATS\n", __MM_FILE__, __func__); + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, + sizeof(stats))) + return -EFAULT; + return 0; + } + case AUDIO_START: + { + uint32_t acdb_id; + pr_debug("[%s:%s] AUDIO_START\n", __MM_FILE__, __func__); + if (arg == 0) { + acdb_id = 0; + } else { + if (copy_from_user(&acdb_id, + (void *) arg, sizeof(acdb_id))) { + rc = -EFAULT; + break; + } + } + if (qcelp->audio_client) { + pr_err("[%s:%s] active session already existing\n", + __MM_FILE__, __func__); + rc = -EBUSY; + break; + } else { + qcelp->audio_client = q6audio_open_qcp( + qcelp->str_cfg.buffer_size, + qcelp->cfg.min_bit_rate, + qcelp->cfg.max_bit_rate, + qcelp->voicerec_mode.rec_mode, + ADSP_AUDIO_FORMAT_V13K_FS, + acdb_id); + + if (!qcelp->audio_client) { + pr_err("[%s:%s] qcelp open session failed\n", + __MM_FILE__, __func__); + kfree(qcelp); + rc = -ENOMEM; + break; + } + } + + /*allocate flow control buffers*/ + fc = qcelp->qcelp_fc; + size = qcelp->str_cfg.buffer_size; + for (i = 0; i < QCELP_FC_BUFF_CNT; ++i) { + mutex_init(&(fc->fc_buff[i].lock)); + fc->fc_buff[i].empty = 1; + fc->fc_buff[i].data = kmalloc(size, GFP_KERNEL); + if (fc->fc_buff[i].data == NULL) { + pr_err("[%s:%s] No memory for FC buffers\n", + __MM_FILE__, __func__); + rc = -ENOMEM; + goto fc_fail; + } + fc->fc_buff[i].size = size; + fc->fc_buff[i].actual_size = 0; + } + + /*create flow control thread*/ + fc->task = kthread_run(q6_qcelp_flowcontrol, + qcelp, "qcelp_flowcontrol"); + if (IS_ERR(fc->task)) { + rc = PTR_ERR(fc->task); + pr_err("[%s:%s] error creating flow control thread\n", + __MM_FILE__, __func__); + goto fc_fail; + } + break; +fc_fail: + /*free flow control buffers*/ + --i; + for (; i >= 0; i--) { + kfree(fc->fc_buff[i].data); + fc->fc_buff[i].data = NULL; + } + break; + } + case AUDIO_STOP: + pr_debug("[%s:%s] AUDIO_STOP\n", __MM_FILE__, __func__); + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_INCALL: { + pr_debug("[%s:%s] SET_INCALL\n", __MM_FILE__, __func__); + if (copy_from_user(&qcelp->voicerec_mode, + (void *)arg, sizeof(struct msm_voicerec_mode))) + rc = -EFAULT; + + if (qcelp->voicerec_mode.rec_mode != AUDIO_FLAG_READ + && qcelp->voicerec_mode.rec_mode != + AUDIO_FLAG_INCALL_MIXED) { + qcelp->voicerec_mode.rec_mode = AUDIO_FLAG_READ; + pr_err("[%s:%s] Invalid rec_mode\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + break; + } + case AUDIO_GET_STREAM_CONFIG: + if (copy_to_user((void *)arg, &qcelp->str_cfg, + sizeof(struct msm_audio_stream_config))) + rc = -EFAULT; + pr_debug("[%s:%s] GET_STREAM_CONFIG: buffsz=%d, buffcnt=%d\n", + __MM_FILE__, __func__, qcelp->str_cfg.buffer_size, + qcelp->str_cfg.buffer_count); + break; + case AUDIO_SET_STREAM_CONFIG: + if (copy_from_user(&qcelp->str_cfg, (void *)arg, + sizeof(struct msm_audio_stream_config))) { + rc = -EFAULT; + break; + } + pr_debug("[%s:%s] SET_STREAM_CONFIG: buffsz=%d, buffcnt=%d\n", + __MM_FILE__, __func__, qcelp->str_cfg.buffer_size, + qcelp->str_cfg.buffer_count); + + if (qcelp->str_cfg.buffer_size < 35) { + pr_err("[%s:%s] Buffer size too small\n", __MM_FILE__, + __func__); + rc = -EINVAL; + break; + } + + if (qcelp->str_cfg.buffer_count != 2) + pr_info("[%s:%s] Buffer count set to 2\n", __MM_FILE__, + __func__); + break; + case AUDIO_SET_QCELP_ENC_CONFIG: + if (copy_from_user(&qcelp->cfg, (void *) arg, + sizeof(struct msm_audio_qcelp_enc_config))) + rc = -EFAULT; + pr_debug("[%s:%s] SET_QCELP_ENC_CONFIG\n", __MM_FILE__, + __func__); + + if (qcelp->cfg.min_bit_rate > 4 || + qcelp->cfg.min_bit_rate < 1) { + + pr_err("[%s:%s] invalid min bitrate\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + if (qcelp->cfg.max_bit_rate > 4 || + qcelp->cfg.max_bit_rate < 1) { + + pr_err("[%s:%s] invalid max bitrate\n", __MM_FILE__, + __func__); + rc = -EINVAL; + } + + break; + case AUDIO_GET_QCELP_ENC_CONFIG: + if (copy_to_user((void *) arg, &qcelp->cfg, + sizeof(struct msm_audio_qcelp_enc_config))) + rc = -EFAULT; + pr_debug("[%s:%s] GET_QCELP_ENC_CONFIG\n", __MM_FILE__, + __func__); + break; + + default: + rc = -EINVAL; + } + mutex_unlock(&qcelp->lock); + pr_debug("[%s:%s] rc = %d\n", __MM_FILE__, __func__, rc); + return rc; +} + +static int q6_qcelp_in_open(struct inode *inode, struct file *file) +{ + struct qcelp *qcelp; + struct qcelp_fc *fc; + int i; + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + qcelp = kmalloc(sizeof(struct qcelp), GFP_KERNEL); + if (qcelp == NULL) { + pr_err("[%s:%s] Could not allocate memory for qcelp driver\n", + __MM_FILE__, __func__); + return -ENOMEM; + } + + mutex_init(&qcelp->lock); + file->private_data = qcelp; + qcelp->audio_client = NULL; + qcelp->str_cfg.buffer_size = 35; + qcelp->str_cfg.buffer_count = 2; + qcelp->cfg.cdma_rate = CDMA_RATE_FULL; + qcelp->cfg.min_bit_rate = 1; + qcelp->cfg.max_bit_rate = 4; + qcelp->voicerec_mode.rec_mode = AUDIO_FLAG_READ; + + qcelp->qcelp_fc = kmalloc(sizeof(struct qcelp_fc), GFP_KERNEL); + if (qcelp->qcelp_fc == NULL) { + pr_err("[%s:%s] Could not allocate memory for qcelp_fc\n", + __MM_FILE__, __func__); + kfree(qcelp); + return -ENOMEM; + } + fc = qcelp->qcelp_fc; + fc->task = NULL; + fc->buff_index = 0; + for (i = 0; i < QCELP_FC_BUFF_CNT; ++i) { + fc->fc_buff[i].data = NULL; + fc->fc_buff[i].size = 0; + fc->fc_buff[i].actual_size = 0; + } + /*initialize wait queue head*/ + init_waitqueue_head(&fc->fc_wq); + return 0; +} + +static ssize_t q6_qcelp_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct audio_client *ac; + const char __user *start = buf; + struct qcelp *qcelp = file->private_data; + struct qcelp_fc *fc; + int xfer = 0; + int res = 0; + + pr_debug("[%s:%s] count = %d\n", __MM_FILE__, __func__, count); + mutex_lock(&qcelp->lock); + ac = qcelp->audio_client; + if (!ac) { + res = -ENODEV; + goto fail; + } + fc = qcelp->qcelp_fc; + while (count > xfer) { + /*wait for buffer to full*/ + if (fc->fc_buff[fc->buff_index].empty != 0) { + res = wait_event_interruptible_timeout(fc->fc_wq, + (fc->fc_buff[fc->buff_index].empty == 0), + msecs_to_jiffies(QCELP_READ_TIMEOUT)); + + pr_debug("[%s:%s] buff_index = %d\n", __MM_FILE__, + __func__, fc->buff_index); + if (res == 0) { + pr_err("[%s:%s] Timeout!\n", __MM_FILE__, + __func__); + res = -ETIMEDOUT; + goto fail; + } else if (res < 0) { + pr_err("[%s:%s] Returning on Interrupt\n", + __MM_FILE__, __func__); + goto fail; + } + } + /*lock the buffer*/ + mutex_lock(&(fc->fc_buff[fc->buff_index].lock)); + xfer = fc->fc_buff[fc->buff_index].actual_size; + + if (xfer > count) { + mutex_unlock(&(fc->fc_buff[fc->buff_index].lock)); + pr_err("[%s:%s] read failed! byte count too small\n", + __MM_FILE__, __func__); + res = -EINVAL; + goto fail; + } + + if (copy_to_user(buf, fc->fc_buff[fc->buff_index].data, xfer)) { + mutex_unlock(&(fc->fc_buff[fc->buff_index].lock)); + pr_err("[%s:%s] copy_to_user failed at index %d\n", + __MM_FILE__, __func__, fc->buff_index); + res = -EFAULT; + goto fail; + } + buf += xfer; + count -= xfer; + + fc->fc_buff[fc->buff_index].empty = 1; + fc->fc_buff[fc->buff_index].actual_size = 0; + + mutex_unlock(&(fc->fc_buff[fc->buff_index].lock)); + ++(fc->buff_index); + if (fc->buff_index >= QCELP_FC_BUFF_CNT) + fc->buff_index = 0; + } + res = buf - start; + +fail: + mutex_unlock(&qcelp->lock); + + return res; +} + +static int q6_qcelp_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct qcelp *qcelp = file->private_data; + int i = 0; + struct qcelp_fc *fc; + + mutex_lock(&qcelp->lock); + fc = qcelp->qcelp_fc; + kthread_stop(fc->task); + fc->task = NULL; + + /*free flow control buffers*/ + for (i = 0; i < QCELP_FC_BUFF_CNT; ++i) { + kfree(fc->fc_buff[i].data); + fc->fc_buff[i].data = NULL; + } + kfree(fc); + + if (qcelp->audio_client) + rc = q6audio_close(qcelp->audio_client); + mutex_unlock(&qcelp->lock); + kfree(qcelp); + pr_info("[%s:%s] release\n", __MM_FILE__, __func__); + return rc; +} + +static const struct file_operations q6_qcelp_in_fops = { + .owner = THIS_MODULE, + .open = q6_qcelp_in_open, + .read = q6_qcelp_in_read, + .release = q6_qcelp_in_release, + .unlocked_ioctl = q6_qcelp_in_ioctl, +}; + +struct miscdevice q6_qcelp_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_qcelp_in", + .fops = &q6_qcelp_in_fops, +}; + +static int __init q6_qcelp_in_init(void) +{ + return misc_register(&q6_qcelp_in_misc); +} + +device_initcall(q6_qcelp_in_init); diff --git a/arch/arm/mach-msm/qdsp6/routing.c b/arch/arm/mach-msm/qdsp6/routing.c new file mode 100644 index 00000000000..f6533a40c2b --- /dev/null +++ b/arch/arm/mach-msm/qdsp6/routing.c @@ -0,0 +1,78 @@ +/* arch/arm/mach-msm/qdsp6/routing.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +extern int q6audio_set_route(const char *name); + +static int q6_open(struct inode *inode, struct file *file) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + return 0; +} + +static ssize_t q6_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + char cmd[32]; + + pr_debug("[%s:%s] count = %d", __MM_FILE__, __func__, count); + if (count >= sizeof(cmd)) { + pr_err("[%s:%s] invalid count %d\n", __MM_FILE__, + __func__, count); + return -EINVAL; + } + if (copy_from_user(cmd, buf, count)) + return -EFAULT; + cmd[count] = 0; + + if ((count > 1) && (cmd[count-1] == '\n')) + cmd[count-1] = 0; + + q6audio_set_route(cmd); + + return count; +} + +static int q6_release(struct inode *inode, struct file *file) +{ + pr_debug("[%s:%s]\n", __MM_FILE__, __func__); + return 0; +} + +static struct file_operations q6_fops = { + .owner = THIS_MODULE, + .open = q6_open, + .write = q6_write, + .release = q6_release, +}; + +static struct miscdevice q6_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_audio_route", + .fops = &q6_fops, +}; + + +static int __init q6_init(void) { + return misc_register(&q6_misc); +} + +device_initcall(q6_init); diff --git a/arch/arm/mach-msm/qdsp6v2/Makefile b/arch/arm/mach-msm/qdsp6v2/Makefile new file mode 100644 index 00000000000..eaf9e69e5ec --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/Makefile @@ -0,0 +1,17 @@ +ifdef CONFIG_ARCH_MSM8X60 +obj-$(CONFIG_MSM8X60_RTAC) += rtac.o +obj-y += audio_dev_ctl.o +obj-y += board-msm8x60-audio.o +obj-$(CONFIG_TIMPANI_CODEC) += snddev_icodec.o +obj-y += snddev_ecodec.o snddev_mi2s.o snddev_virtual.o +obj-y += pcm_out.o pcm_in.o fm.o +obj-y += audio_lpa.o +obj-y += q6voice.o +obj-y += snddev_hdmi.o +obj-y += audio_mvs.o +obj-$(CONFIG_FB_MSM_HDMI_MSM_PANEL) += lpa_if_hdmi.o +endif +obj-$(CONFIG_MSM_QDSP6_APR) += apr.o apr_tal.o q6core.o dsp_debug.o +obj-y += audio_acdb.o +obj-y += aac_in.o qcelp_in.o evrc_in.o amrnb_in.o audio_utils.o +obj-y += audio_wma.o audio_wmapro.o audio_aac.o audio_multi_aac.o \ No newline at end of file diff --git a/arch/arm/mach-msm/qdsp6v2/aac_in.c b/arch/arm/mach-msm/qdsp6v2/aac_in.c new file mode 100644 index 00000000000..ff25d32dda9 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/aac_in.c @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audio_utils.h" + + +/* Buffer with meta*/ +#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in)) + +/* Maximum 5 frames in buffer with meta */ +#define FRAME_SIZE (1 + ((1536+sizeof(struct meta_out_dsp)) * 5)) + +#define AAC_FORMAT_ADTS 65535 + +void q6asm_aac_in_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6audio_in * audio = (struct q6audio_in *)priv; + unsigned long flags; + + pr_debug("%s:session id %d: opcode[0x%x]\n", __func__, + audio->ac->session, opcode); + + spin_lock_irqsave(&audio->dsp_lock, flags); + switch (opcode) { + case ASM_DATA_EVENT_READ_DONE: + audio_in_get_dsp_frames(audio, token, payload); + break; + case ASM_DATA_EVENT_WRITE_DONE: + atomic_inc(&audio->in_count); + wake_up(&audio->write_wait); + break; + case ASM_DATA_CMDRSP_EOS: + audio->eos_rsp = 1; + wake_up(&audio->read_wait); + break; + case ASM_STREAM_CMDRSP_GET_ENCDEC_PARAM: + break; + case ASM_STREAM_CMDRSP_GET_PP_PARAMS: + break; + case ASM_SESSION_EVENT_TX_OVERFLOW: + pr_err("%s:session id %d: ASM_SESSION_EVENT_TX_OVERFLOW\n", + __func__, audio->ac->session); + break; + default: + pr_debug("%s:session id %d: Ignore opcode[0x%x]\n", __func__, + audio->ac->session, opcode); + break; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} +/* ------------------- device --------------------- */ +static long aac_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct q6audio_in *audio = file->private_data; + int rc = 0; + int cnt = 0; + + switch (cmd) { + case AUDIO_START: { + struct msm_audio_aac_enc_config *enc_cfg; + struct msm_audio_aac_config *aac_config; + uint32_t aac_mode = AAC_ENC_MODE_AAC_LC; + + enc_cfg = audio->enc_cfg; + aac_config = audio->codec_cfg; + /* ENCODE CFG (after new set of API's are published )bharath*/ + pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__, + audio->ac->session, audio->buf_alloc); + if (audio->enabled == 1) { + pr_info("%s:AUDIO_START already over\n", __func__); + rc = 0; + break; + } + + rc = audio_in_buf_alloc(audio); + if (rc < 0) { + pr_err("%s:session id %d: buffer allocation failed\n", + __func__, audio->ac->session); + break; + } + + pr_debug("%s:sbr_ps_flag = %d, sbr_flag = %d\n", __func__, + aac_config->sbr_ps_on_flag, aac_config->sbr_on_flag); + if (aac_config->sbr_ps_on_flag) + aac_mode = AAC_ENC_MODE_EAAC_P; + else if (aac_config->sbr_on_flag) + aac_mode = AAC_ENC_MODE_AAC_P; + else + aac_mode = AAC_ENC_MODE_AAC_LC; + + rc = q6asm_enc_cfg_blk_aac(audio->ac, + audio->buf_cfg.frames_per_buf, + enc_cfg->sample_rate, + enc_cfg->channels, + enc_cfg->bit_rate, + aac_mode, + enc_cfg->stream_format); + if (rc < 0) { + pr_err("%s:session id %d: cmd media format block\ + failed\n", __func__, audio->ac->session); + break; + } + if (audio->feedback == NON_TUNNEL_MODE) { + rc = q6asm_media_format_block_pcm(audio->ac, + audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + if (rc < 0) { + pr_err("%s:session id %d: media format block\ + failed\n", __func__, audio->ac->session); + break; + } + } + rc = audio_in_enable(audio); + if (!rc) { + audio->enabled = 1; + } else { + audio->enabled = 0; + pr_err("%s:session id %d: Audio Start procedure\ + failed rc=%d\n", __func__, audio->ac->session, rc); + break; + } + while (cnt++ < audio->str_cfg.buffer_count) + q6asm_read(audio->ac); + pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n", + __func__, audio->ac->session, audio->enabled); + break; + } + case AUDIO_STOP: { + pr_debug("%s:session id %d: Rxed AUDIO_STOP\n", __func__, + audio->ac->session); + rc = audio_in_disable(audio); + if (rc < 0) { + pr_err("%s:session id %d: Audio Stop procedure failed\ + rc=%d\n", __func__, audio->ac->session, rc); + break; + } + break; + } + case AUDIO_GET_AAC_ENC_CONFIG: { + struct msm_audio_aac_enc_config cfg; + struct msm_audio_aac_enc_config *enc_cfg; + enc_cfg = audio->enc_cfg; + if (enc_cfg->channels == CH_MODE_MONO) + cfg.channels = 1; + else + cfg.channels = 2; + cfg.sample_rate = enc_cfg->sample_rate; + cfg.bit_rate = enc_cfg->bit_rate; + /* ADTS(-1) to ADTS(0x00), RAW(0x00) to RAW(0x03) */ + cfg.stream_format = ((enc_cfg->stream_format == \ + 0x00) ? AUDIO_AAC_FORMAT_ADTS : AUDIO_AAC_FORMAT_RAW); + pr_debug("%s:session id %d: Get-aac-cfg: format=%d sr=%d\ + bitrate=%d\n", __func__, audio->ac->session, + cfg.stream_format, cfg.sample_rate, cfg.bit_rate); + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_AAC_ENC_CONFIG: { + struct msm_audio_aac_enc_config cfg; + struct msm_audio_aac_enc_config *enc_cfg; + enc_cfg = audio->enc_cfg; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + pr_debug("%s:session id %d: Set-aac-cfg: stream=%d\n", __func__, + audio->ac->session, cfg.stream_format); + + if ((cfg.stream_format != AUDIO_AAC_FORMAT_RAW) && + (cfg.stream_format != AAC_FORMAT_ADTS)) { + pr_err("%s:session id %d: unsupported AAC format\n", + __func__, audio->ac->session); + rc = -EINVAL; + break; + } + + if (cfg.channels == 1) { + cfg.channels = CH_MODE_MONO; + } else if (cfg.channels == 2) { + cfg.channels = CH_MODE_STEREO; + } else { + rc = -EINVAL; + break; + } + if ((cfg.sample_rate < 8000) && (cfg.sample_rate > 48000)) { + pr_err("%s: ERROR in setting samplerate = %d\n", + __func__, cfg.sample_rate); + rc = -EINVAL; + break; + } + enc_cfg->sample_rate = cfg.sample_rate; + enc_cfg->channels = cfg.channels; + enc_cfg->bit_rate = cfg.bit_rate; + enc_cfg->stream_format = + ((cfg.stream_format == AUDIO_AAC_FORMAT_RAW) ? \ + 0x03 : 0x00); + pr_debug("%s:session id %d: Set-aac-cfg:SR= 0x%x ch=0x%x\ + bitrate=0x%x, format(adts/raw) = %d\n", + __func__, audio->ac->session, enc_cfg->sample_rate, + enc_cfg->channels, enc_cfg->bit_rate, + enc_cfg->stream_format); + break; + } + case AUDIO_GET_AAC_CONFIG: { + if (copy_to_user((void *)arg, &audio->codec_cfg, + sizeof(struct msm_audio_aac_config))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_SET_AAC_CONFIG: { + struct msm_audio_aac_config aac_cfg; + struct msm_audio_aac_config *audio_aac_cfg; + struct msm_audio_aac_enc_config *enc_cfg; + enc_cfg = audio->enc_cfg; + audio_aac_cfg = audio->codec_cfg; + + if (copy_from_user(&aac_cfg, (void *)arg, + sizeof(struct msm_audio_aac_config))) { + rc = -EFAULT; + break; + } + pr_debug("%s:session id %d: AUDIO_SET_AAC_CONFIG: sbr_flag = %d" + " sbr_ps_flag = %d\n", __func__, + audio->ac->session, aac_cfg.sbr_on_flag, + aac_cfg.sbr_ps_on_flag); + audio_aac_cfg->sbr_on_flag = aac_cfg.sbr_on_flag; + audio_aac_cfg->sbr_ps_on_flag = aac_cfg.sbr_ps_on_flag; + if ((audio_aac_cfg->sbr_on_flag == 1) || + (audio_aac_cfg->sbr_ps_on_flag == 1)) { + if (enc_cfg->sample_rate < 24000) { + pr_err("%s: ERROR in setting samplerate = %d" + "\n", __func__, enc_cfg->sample_rate); + rc = -EINVAL; + break; + } + } + break; + } + default: + rc = -EINVAL; + } + return rc; +} + +static int aac_in_open(struct inode *inode, struct file *file) +{ + struct q6audio_in *audio = NULL; + struct msm_audio_aac_enc_config *enc_cfg; + struct msm_audio_aac_config *aac_config; + int rc = 0; + + audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL); + + if (audio == NULL) { + pr_err("%s:session id %d: Could not allocate memory for aac\ + driver\n", __func__, audio->ac->session); + return -ENOMEM; + } + /* Allocate memory for encoder config param */ + audio->enc_cfg = kzalloc(sizeof(struct msm_audio_aac_enc_config), + GFP_KERNEL); + if (audio->enc_cfg == NULL) { + pr_err("%s:session id %d: Could not allocate memory for aac\ + config param\n", __func__, audio->ac->session); + kfree(audio); + return -ENOMEM; + } + enc_cfg = audio->enc_cfg; + + audio->codec_cfg = kzalloc(sizeof(struct msm_audio_aac_config), + GFP_KERNEL); + if (audio->codec_cfg == NULL) { + pr_err("%s:session id %d: Could not allocate memory for aac\ + config\n", __func__, audio->ac->session); + kfree(audio->enc_cfg); + kfree(audio); + return -ENOMEM; + } + aac_config = audio->codec_cfg; + + mutex_init(&audio->lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->write_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->write_wait); + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + audio->min_frame_size = 1536; + audio->max_frames_per_buf = 5; + enc_cfg->sample_rate = 8000; + enc_cfg->channels = 1; + enc_cfg->bit_rate = 16000; + enc_cfg->stream_format = 0x00;/* 0:ADTS, 3:RAW */ + audio->buf_cfg.meta_info_enable = 0x01; + audio->buf_cfg.frames_per_buf = 0x01; + audio->pcm_cfg.buffer_count = PCM_BUF_COUNT; + audio->pcm_cfg.buffer_size = PCM_BUF_SIZE; + aac_config->format = AUDIO_AAC_FORMAT_ADTS; + aac_config->audio_object = AUDIO_AAC_OBJECT_LC; + aac_config->sbr_on_flag = 0; + aac_config->sbr_ps_on_flag = 0; + aac_config->channel_configuration = 1; + + audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_aac_in_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("%s:session id %d: Could not allocate memory for\ + audio client\n", __func__, audio->ac->session); + kfree(audio->enc_cfg); + kfree(audio->codec_cfg); + kfree(audio); + return -ENOMEM; + } + /* open aac encoder in tunnel mode */ + audio->buf_cfg.frames_per_buf = 0x01; + + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->feedback = NON_TUNNEL_MODE; + rc = q6asm_open_read_write(audio->ac, FORMAT_MPEG4_AAC, + FORMAT_LINEAR_PCM); + + if (rc < 0) { + pr_err("%s:session id %d: NT Open failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + audio->buf_cfg.meta_info_enable = 0x01; + pr_info("%s:session id %d: NT mode encoder success\n", __func__, + audio->ac->session); + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->feedback = TUNNEL_MODE; + rc = q6asm_open_read(audio->ac, FORMAT_MPEG4_AAC); + + if (rc < 0) { + pr_err("%s:session id %d: Tunnel Open failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + /* register for tx overflow (valid for tunnel mode only) */ + rc = q6asm_reg_tx_overflow(audio->ac, 0x01); + if (rc < 0) { + pr_err("%s:session id %d: TX Overflow registration\ + failed rc=%d\n", __func__, + audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + audio->buf_cfg.meta_info_enable = 0x00; + pr_info("%s:session id %d: T mode encoder success\n", __func__, + audio->ac->session); + } else { + pr_err("%s:session id %d: Unexpected mode\n", __func__, + audio->ac->session); + rc = -EACCES; + goto fail; + } + audio->opened = 1; + atomic_set(&audio->in_count, PCM_BUF_COUNT); + atomic_set(&audio->out_count, 0x00); + audio->enc_ioctl = aac_in_ioctl; + file->private_data = audio; + + pr_info("%s:session id %d: success\n", __func__, audio->ac->session); + return 0; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio->enc_cfg); + kfree(audio->codec_cfg); + kfree(audio); + return rc; +} + +static const struct file_operations audio_in_fops = { + .owner = THIS_MODULE, + .open = aac_in_open, + .release = audio_in_release, + .read = audio_in_read, + .write = audio_in_write, + .unlocked_ioctl = audio_in_ioctl, +}; + +struct miscdevice audio_aac_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac_in", + .fops = &audio_in_fops, +}; + +static int __init aac_in_init(void) +{ + return misc_register(&audio_aac_in_misc); +} +device_initcall(aac_in_init); diff --git a/arch/arm/mach-msm/qdsp6v2/amrnb_in.c b/arch/arm/mach-msm/qdsp6v2/amrnb_in.c new file mode 100644 index 00000000000..ece44fdc5da --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/amrnb_in.c @@ -0,0 +1,325 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audio_utils.h" + +/* Buffer with meta*/ +#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in)) + +/* Maximum 10 frames in buffer with meta */ +#define FRAME_SIZE (1 + ((32+sizeof(struct meta_out_dsp)) * 10)) + +void q6asm_amrnb_in_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6audio_in * audio = (struct q6audio_in *)priv; + unsigned long flags; + + pr_debug("%s:session id %d: opcode - %d\n", __func__, + audio->ac->session, opcode); + + spin_lock_irqsave(&audio->dsp_lock, flags); + switch (opcode) { + case ASM_DATA_EVENT_READ_DONE: + audio_in_get_dsp_frames(audio, token, payload); + break; + case ASM_DATA_EVENT_WRITE_DONE: + atomic_inc(&audio->in_count); + wake_up(&audio->write_wait); + break; + case ASM_DATA_CMDRSP_EOS: + audio->eos_rsp = 1; + wake_up(&audio->read_wait); + break; + case ASM_STREAM_CMDRSP_GET_ENCDEC_PARAM: + break; + case ASM_STREAM_CMDRSP_GET_PP_PARAMS: + break; + case ASM_SESSION_EVENT_TX_OVERFLOW: + pr_err("%s:session id %d: ASM_SESSION_EVENT_TX_OVERFLOW\n", + __func__, audio->ac->session); + break; + default: + pr_err("%s:session id %d: Ignore opcode[0x%x]\n", __func__, + audio->ac->session, opcode); + break; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ +static long amrnb_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct q6audio_in *audio = file->private_data; + int rc = 0; + int cnt = 0; + + switch (cmd) { + case AUDIO_START: { + struct msm_audio_amrnb_enc_config_v2 *enc_cfg; + enc_cfg = audio->enc_cfg; + pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__, + audio->ac->session, audio->buf_alloc); + if (audio->enabled == 1) { + pr_info("%s:AUDIO_START already over\n", __func__); + rc = 0; + break; + } + rc = audio_in_buf_alloc(audio); + if (rc < 0) { + pr_err("%s:session id %d: buffer allocation failed\n", + __func__, audio->ac->session); + break; + } + + rc = q6asm_enc_cfg_blk_amrnb(audio->ac, + audio->buf_cfg.frames_per_buf, + enc_cfg->band_mode, + enc_cfg->dtx_enable); + + if (rc < 0) { + pr_err("%s:session id %d: cmd amrnb media format block\ + failed\n", __func__, audio->ac->session); + break; + } + if (audio->feedback == NON_TUNNEL_MODE) { + rc = q6asm_media_format_block_pcm(audio->ac, + audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + + if (rc < 0) { + pr_err("%s:session id %d: media format block\ + failed\n", __func__, audio->ac->session); + break; + } + } + pr_debug("%s:session id %d: AUDIO_START enable[%d]\n", + __func__, audio->ac->session, + audio->enabled); + rc = audio_in_enable(audio); + if (!rc) { + audio->enabled = 1; + } else { + audio->enabled = 0; + pr_err("%s:session id %d: Audio Start procedure failed\ + rc=%d\n", __func__, + audio->ac->session, rc); + break; + } + while (cnt++ < audio->str_cfg.buffer_count) + q6asm_read(audio->ac); /* Push buffer to DSP */ + rc = 0; + pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n", + __func__, audio->ac->session, audio->enabled); + break; + } + case AUDIO_STOP: { + pr_debug("%s:AUDIO_STOP\n", __func__); + rc = audio_in_disable(audio); + if (rc < 0) { + pr_err("%s:session id %d: Audio Stop procedure failed\ + rc=%d\n", __func__, + audio->ac->session, rc); + break; + } + break; + } + case AUDIO_GET_AMRNB_ENC_CONFIG_V2: { + if (copy_to_user((void *)arg, audio->enc_cfg, + sizeof(struct msm_audio_amrnb_enc_config_v2))) + rc = -EFAULT; + break; + } + case AUDIO_SET_AMRNB_ENC_CONFIG_V2: { + struct msm_audio_amrnb_enc_config_v2 cfg; + struct msm_audio_amrnb_enc_config_v2 *enc_cfg; + enc_cfg = audio->enc_cfg; + if (copy_from_user(&cfg, (void *) arg, + sizeof(struct msm_audio_amrnb_enc_config_v2))) { + rc = -EFAULT; + break; + } + if (cfg.band_mode > 8 || + cfg.band_mode < 1) { + pr_err("%s:session id %d: invalid band mode\n", + __func__, audio->ac->session); + rc = -EINVAL; + break; + } + /* AMR NB encoder accepts values between 0-7 + while openmax provides value between 1-8 + as per spec */ + enc_cfg->band_mode = (cfg.band_mode - 1); + enc_cfg->dtx_enable = (cfg.dtx_enable ? 1 : 0); + enc_cfg->frame_format = 0; + pr_debug("%s:session id %d: band_mode = 0x%x dtx_enable=0x%x\n", + __func__, audio->ac->session, + enc_cfg->band_mode, enc_cfg->dtx_enable); + break; + } + default: + rc = -EINVAL; + } + return rc; +} + +static int amrnb_in_open(struct inode *inode, struct file *file) +{ + struct q6audio_in *audio = NULL; + struct msm_audio_amrnb_enc_config_v2 *enc_cfg; + int rc = 0; + + audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL); + + if (audio == NULL) { + pr_err("%s:session id %d: Could not allocate memory for amrnb\ + driver\n", __func__, audio->ac->session); + return -ENOMEM; + } + /* Allocate memory for encoder config param */ + audio->enc_cfg = kzalloc(sizeof(struct msm_audio_amrnb_enc_config_v2), + GFP_KERNEL); + if (audio->enc_cfg == NULL) { + pr_err("%s:session id %d: Could not allocate memory for aac\ + config param\n", __func__, audio->ac->session); + kfree(audio); + return -ENOMEM; + } + enc_cfg = audio->enc_cfg; + + mutex_init(&audio->lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->write_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->write_wait); + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + audio->min_frame_size = 32; + audio->max_frames_per_buf = 10; + audio->pcm_cfg.buffer_size = PCM_BUF_SIZE; + audio->pcm_cfg.buffer_count = PCM_BUF_COUNT; + enc_cfg->band_mode = 7; + enc_cfg->dtx_enable = 0; + audio->pcm_cfg.channel_count = 1; + audio->pcm_cfg.sample_rate = 8000; + audio->buf_cfg.meta_info_enable = 0x01; + audio->buf_cfg.frames_per_buf = 0x01; + + audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_amrnb_in_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("%s:session id %d: Could not allocate memory for audio\ + client\n", __func__, audio->ac->session); + kfree(audio->enc_cfg); + kfree(audio); + return -ENOMEM; + } + + /* open amrnb encoder in T/NT mode */ + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->feedback = NON_TUNNEL_MODE; + rc = q6asm_open_read_write(audio->ac, FORMAT_AMRNB, + FORMAT_LINEAR_PCM); + if (rc < 0) { + pr_err("%s:session id %d: NT mode Open failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + pr_info("%s:session id %d: NT mode encoder success\n", + __func__, audio->ac->session); + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->feedback = TUNNEL_MODE; + rc = q6asm_open_read(audio->ac, FORMAT_AMRNB); + if (rc < 0) { + pr_err("%s:session id %d: T mode Open failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + /* register for tx overflow (valid for tunnel mode only) */ + rc = q6asm_reg_tx_overflow(audio->ac, 0x01); + if (rc < 0) { + pr_err("%s:session id %d: TX Overflow registration\ + failed rc=%d\n", __func__, audio->ac->session, + rc); + rc = -ENODEV; + goto fail; + } + pr_info("%s:session id %d: T mode encoder success\n", + __func__, audio->ac->session); + } else { + pr_err("%s:session id %d: Unexpected mode\n", __func__, + audio->ac->session); + rc = -EACCES; + goto fail; + } + + audio->opened = 1; + atomic_set(&audio->in_count, PCM_BUF_COUNT); + atomic_set(&audio->out_count, 0x00); + audio->enc_ioctl = amrnb_in_ioctl; + file->private_data = audio; + + pr_info("%s:session id %d: success\n", __func__, audio->ac->session); + return 0; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio->enc_cfg); + kfree(audio); + return rc; +} + +static const struct file_operations audio_in_fops = { + .owner = THIS_MODULE, + .open = amrnb_in_open, + .release = audio_in_release, + .read = audio_in_read, + .write = audio_in_write, + .unlocked_ioctl = audio_in_ioctl, +}; + +struct miscdevice audio_amrnb_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_amrnb_in", + .fops = &audio_in_fops, +}; + +static int __init amrnb_in_init(void) +{ + return misc_register(&audio_amrnb_in_misc); +} + +device_initcall(amrnb_in_init); diff --git a/arch/arm/mach-msm/qdsp6v2/apr.c b/arch/arm/mach-msm/qdsp6v2/apr.c new file mode 100644 index 00000000000..f4054577e15 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/apr.c @@ -0,0 +1,673 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct apr_q6 q6; +struct apr_client client[APR_DEST_MAX][APR_CLIENT_MAX]; +static atomic_t dsp_state; +static atomic_t modem_state; + +static wait_queue_head_t dsp_wait; +static wait_queue_head_t modem_wait; +/* Subsystem restart: QDSP6 data, functions */ +static struct workqueue_struct *apr_reset_workqueue; +static void apr_reset_deregister(struct work_struct *work); +struct apr_reset_work { + void *handle; + struct work_struct work; +}; + + +int apr_send_pkt(void *handle, uint32_t *buf) +{ + struct apr_svc *svc = handle; + struct apr_client *clnt; + struct apr_hdr *hdr; + uint16_t dest_id; + uint16_t client_id; + uint16_t w_len; + unsigned long flags; + + if (!handle || !buf) { + pr_err("APR: Wrong parameters\n"); + return -EINVAL; + } + if (svc->need_reset) { + pr_err("apr: send_pkt service need reset\n"); + return -ENETRESET; + } + + if ((svc->dest_id == APR_DEST_QDSP6) && + (atomic_read(&dsp_state) == 0)) { + pr_err("apr: Still dsp is not Up\n"); + return -ENETRESET; + } else if ((svc->dest_id == APR_DEST_MODEM) && + (atomic_read(&modem_state) == 0)) { + pr_err("apr: Still Modem is not Up\n"); + return -ENETRESET; + } + + + spin_lock_irqsave(&svc->w_lock, flags); + dest_id = svc->dest_id; + client_id = svc->client_id; + clnt = &client[dest_id][client_id]; + + if (!client[dest_id][client_id].handle) { + pr_err("APR: Still service is not yet opened\n"); + spin_unlock_irqrestore(&svc->w_lock, flags); + return -EINVAL; + } + hdr = (struct apr_hdr *)buf; + + hdr->src_domain = APR_DOMAIN_APPS; + hdr->src_svc = svc->id; + if (dest_id == APR_DEST_MODEM) + hdr->dest_domain = APR_DOMAIN_MODEM; + else if (dest_id == APR_DEST_QDSP6) + hdr->dest_domain = APR_DOMAIN_ADSP; + + hdr->dest_svc = svc->id; + + w_len = apr_tal_write(clnt->handle, buf, hdr->pkt_size); + if (w_len != hdr->pkt_size) + pr_err("Unable to write APR pkt successfully: %d\n", w_len); + spin_unlock_irqrestore(&svc->w_lock, flags); + + return w_len; +} + +static void apr_cb_func(void *buf, int len, void *priv) +{ + struct apr_client_data data; + struct apr_client *apr_client; + struct apr_svc *c_svc; + struct apr_hdr *hdr; + uint16_t hdr_size; + uint16_t msg_type; + uint16_t ver; + uint16_t src; + uint16_t svc; + uint16_t clnt; + int i; + int temp_port = 0; + uint32_t *ptr; + + pr_debug("APR2: len = %d\n", len); + ptr = buf; + pr_debug("\n*****************\n"); + for (i = 0; i < len/4; i++) + pr_debug("%x ", ptr[i]); + pr_debug("\n"); + pr_debug("\n*****************\n"); + + if (!buf || len <= APR_HDR_SIZE) { + pr_err("APR: Improper apr pkt received:%p %d\n", + buf, len); + return; + } + hdr = buf; + + ver = hdr->hdr_field; + ver = (ver & 0x000F); + if (ver > APR_PKT_VER + 1) { + pr_err("APR: Wrong version: %d\n", ver); + return; + } + + hdr_size = hdr->hdr_field; + hdr_size = ((hdr_size & 0x00F0) >> 0x4) * 4; + if (hdr_size < APR_HDR_SIZE) { + pr_err("APR: Wrong hdr size:%d\n", hdr_size); + return; + } + + if (hdr->pkt_size < APR_HDR_SIZE) { + pr_err("APR: Wrong paket size\n"); + return; + } + msg_type = hdr->hdr_field; + msg_type = (msg_type >> 0x08) & 0x0003; + if (msg_type >= APR_MSG_TYPE_MAX && + msg_type != APR_BASIC_RSP_RESULT) { + pr_err("APR: Wrong message type: %d\n", msg_type); + return; + } + + if (hdr->src_domain >= APR_DOMAIN_MAX || + hdr->dest_domain >= APR_DOMAIN_MAX || + hdr->src_svc >= APR_SVC_MAX || + hdr->dest_svc >= APR_SVC_MAX) { + pr_err("APR: Wrong APR header\n"); + return; + } + + svc = hdr->dest_svc; + if (hdr->src_domain == APR_DOMAIN_MODEM) { + src = APR_DEST_MODEM; + if (svc == APR_SVC_MVS || svc == APR_SVC_MVM || + svc == APR_SVC_CVS || svc == APR_SVC_CVP || + svc == APR_SVC_TEST_CLIENT) + clnt = APR_CLIENT_VOICE; + else { + pr_err("APR: Wrong svc :%d\n", svc); + return; + } + } else if (hdr->src_domain == APR_DOMAIN_ADSP) { + src = APR_DEST_QDSP6; + if (svc == APR_SVC_AFE || svc == APR_SVC_ASM || + svc == APR_SVC_VSM || svc == APR_SVC_VPM || + svc == APR_SVC_ADM || svc == APR_SVC_ADSP_CORE || + svc == APR_SVC_TEST_CLIENT || svc == APR_SVC_ADSP_MVM || + svc == APR_SVC_ADSP_CVS || svc == APR_SVC_ADSP_CVP) + clnt = APR_CLIENT_AUDIO; + else { + pr_err("APR: Wrong svc :%d\n", svc); + return; + } + } else { + pr_err("APR: Pkt from wrong source: %d\n", hdr->src_domain); + return; + } + + pr_debug("src =%d clnt = %d\n", src, clnt); + apr_client = &client[src][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) + if (apr_client->svc[i].id == svc) { + pr_debug("%d\n", apr_client->svc[i].id); + c_svc = &apr_client->svc[i]; + break; + } + + if (i == APR_SVC_MAX) { + pr_err("APR: service is not registered\n"); + return; + } + pr_debug("svc_idx = %d\n", i); + pr_debug("%x %x %x %p %p\n", c_svc->id, c_svc->dest_id, + c_svc->client_id, c_svc->fn, c_svc->priv); + data.payload_size = hdr->pkt_size - hdr_size; + data.opcode = hdr->opcode; + data.src = src; + data.src_port = hdr->src_port; + data.dest_port = hdr->dest_port; + data.token = hdr->token; + data.msg_type = msg_type; + if (data.payload_size > 0) + data.payload = (char *)hdr + hdr_size; + + temp_port = ((data.src_port >> 8) * 8) + (data.src_port & 0xFF); + pr_debug("port = %d t_port = %d\n", data.src_port, temp_port); + if (c_svc->port_cnt && c_svc->port_fn[temp_port]) + c_svc->port_fn[temp_port](&data, c_svc->port_priv[temp_port]); + else if (c_svc->fn) + c_svc->fn(&data, c_svc->priv); + else + pr_err("APR: Rxed a packet for NULL callback\n"); +} + +struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn, + uint32_t src_port, void *priv) +{ + int client_id = 0; + int svc_idx = 0; + int svc_id = 0; + int dest_id = 0; + int temp_port = 0; + struct apr_svc *svc = NULL; + int rc = 0; + + if (!dest || !svc_name || !svc_fn) + return NULL; + + if (!strcmp(dest, "ADSP")) + dest_id = APR_DEST_QDSP6; + else if (!strcmp(dest, "MODEM")) { + dest_id = APR_DEST_MODEM; + } else { + pr_err("APR: wrong destination\n"); + goto done; + } + + if ((dest_id == APR_DEST_QDSP6) && + (atomic_read(&dsp_state) == 0)) { + rc = wait_event_timeout(dsp_wait, + (atomic_read(&dsp_state) == 1), 5*HZ); + if (rc == 0) { + pr_err("apr: Still dsp is not Up\n"); + return NULL; + } + } else if ((dest_id == APR_DEST_MODEM) && + (atomic_read(&modem_state) == 0)) { + rc = wait_event_timeout(modem_wait, + (atomic_read(&modem_state) == 1), 5*HZ); + if (rc == 0) { + pr_err("apr: Still Modem is not Up\n"); + return NULL; + } + } + + if (!strcmp(svc_name, "AFE")) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 0; + svc_id = APR_SVC_AFE; + } else if (!strcmp(svc_name, "ASM")) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 1; + svc_id = APR_SVC_ASM; + } else if (!strcmp(svc_name, "ADM")) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 2; + svc_id = APR_SVC_ADM; + } else if (!strcmp(svc_name, "CORE")) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 3; + svc_id = APR_SVC_ADSP_CORE; + } else if (!strcmp(svc_name, "TEST")) { + if (dest_id == APR_DEST_QDSP6) { + client_id = APR_CLIENT_AUDIO; + svc_idx = 4; + } else { + client_id = APR_CLIENT_VOICE; + svc_idx = 7; + } + svc_id = APR_SVC_TEST_CLIENT; + } else if (!strcmp(svc_name, "VSM")) { + client_id = APR_CLIENT_VOICE; + svc_idx = 0; + svc_id = APR_SVC_VSM; + } else if (!strcmp(svc_name, "VPM")) { + client_id = APR_CLIENT_VOICE; + svc_idx = 1; + svc_id = APR_SVC_VPM; + } else if (!strcmp(svc_name, "MVS")) { + client_id = APR_CLIENT_VOICE; + svc_idx = 2; + svc_id = APR_SVC_MVS; + } else if (!strcmp(svc_name, "MVM")) { + if (dest_id == APR_DEST_MODEM) { + client_id = APR_CLIENT_VOICE; + svc_idx = 3; + svc_id = APR_SVC_MVM; + } else { + client_id = APR_CLIENT_AUDIO; + svc_idx = 5; + svc_id = APR_SVC_ADSP_MVM; + } + } else if (!strcmp(svc_name, "CVS")) { + if (dest_id == APR_DEST_MODEM) { + client_id = APR_CLIENT_VOICE; + svc_idx = 4; + svc_id = APR_SVC_CVS; + } else { + client_id = APR_CLIENT_AUDIO; + svc_idx = 6; + svc_id = APR_SVC_ADSP_CVS; + } + } else if (!strcmp(svc_name, "CVP")) { + if (dest_id == APR_DEST_MODEM) { + client_id = APR_CLIENT_VOICE; + svc_idx = 5; + svc_id = APR_SVC_CVP; + } else { + client_id = APR_CLIENT_AUDIO; + svc_idx = 7; + svc_id = APR_SVC_ADSP_CVP; + } + } else if (!strcmp(svc_name, "SRD")) { + client_id = APR_CLIENT_VOICE; + svc_idx = 6; + svc_id = APR_SVC_SRD; + } else { + pr_err("APR: Wrong svc name\n"); + goto done; + } + + pr_debug("svc name = %s c_id = %d dest_id = %d\n", + svc_name, client_id, dest_id); + mutex_lock(&q6.lock); + if (q6.state == APR_Q6_NOIMG) { + q6.pil = pil_get("q6"); + if (!q6.pil) { + pr_err("APR: Unable to load q6 image\n"); + mutex_unlock(&q6.lock); + return svc; + } + q6.state = APR_Q6_LOADED; + } + mutex_unlock(&q6.lock); + mutex_lock(&client[dest_id][client_id].m_lock); + if (!client[dest_id][client_id].handle) { + client[dest_id][client_id].handle = apr_tal_open(client_id, + dest_id, APR_DL_SMD, apr_cb_func, NULL); + if (!client[dest_id][client_id].handle) { + svc = NULL; + pr_err("APR: Unable to open handle\n"); + mutex_unlock(&client[dest_id][client_id].m_lock); + goto done; + } + } + mutex_unlock(&client[dest_id][client_id].m_lock); + svc = &client[dest_id][client_id].svc[svc_idx]; + mutex_lock(&svc->m_lock); + client[dest_id][client_id].id = client_id; + if (svc->need_reset) { + mutex_unlock(&svc->m_lock); + pr_err("APR: Service needs reset\n"); + goto done; + } + svc->priv = priv; + svc->id = svc_id; + svc->dest_id = dest_id; + svc->client_id = client_id; + if (src_port != 0xFFFFFFFF) { + temp_port = ((src_port >> 8) * 8) + (src_port & 0xFF); + pr_debug("port = %d t_port = %d\n", src_port, temp_port); + if (temp_port > APR_MAX_PORTS || temp_port < 0) { + pr_err("APR: temp_port out of bounds\n"); + mutex_unlock(&svc->m_lock); + return NULL; + } + if (!svc->port_cnt && !svc->svc_cnt) + client[dest_id][client_id].svc_cnt++; + svc->port_cnt++; + svc->port_fn[temp_port] = svc_fn; + svc->port_priv[temp_port] = priv; + } else { + if (!svc->fn) { + if (!svc->port_cnt && !svc->svc_cnt) + client[dest_id][client_id].svc_cnt++; + svc->fn = svc_fn; + if (svc->port_cnt) + svc->svc_cnt++; + } + } + + mutex_unlock(&svc->m_lock); +done: + return svc; +} + +static void apr_reset_deregister(struct work_struct *work) +{ + struct apr_svc *handle = NULL; + struct apr_reset_work *apr_reset = + container_of(work, struct apr_reset_work, work); + + handle = apr_reset->handle; + pr_debug("%s:handle[%p]\n", __func__, handle); + apr_deregister(handle); + kfree(apr_reset); +} + +int apr_deregister(void *handle) +{ + struct apr_svc *svc = handle; + struct apr_client *clnt; + uint16_t dest_id; + uint16_t client_id; + + if (!handle) + return -EINVAL; + + mutex_lock(&svc->m_lock); + dest_id = svc->dest_id; + client_id = svc->client_id; + clnt = &client[dest_id][client_id]; + + if (svc->port_cnt > 0 || svc->svc_cnt > 0) { + if (svc->port_cnt) + svc->port_cnt--; + else if (svc->svc_cnt) + svc->svc_cnt--; + if (!svc->port_cnt && !svc->svc_cnt) { + client[dest_id][client_id].svc_cnt--; + svc->need_reset = 0x0; + } + } else if (client[dest_id][client_id].svc_cnt > 0) { + client[dest_id][client_id].svc_cnt--; + if (!client[dest_id][client_id].svc_cnt) { + svc->need_reset = 0x0; + pr_debug("%s: service is reset %p\n", __func__, svc); + } + } + + if (!svc->port_cnt && !svc->svc_cnt) { + svc->priv = NULL; + svc->id = 0; + svc->fn = NULL; + svc->dest_id = 0; + svc->client_id = 0; + svc->need_reset = 0x0; + } + if (client[dest_id][client_id].handle && + !client[dest_id][client_id].svc_cnt) { + apr_tal_close(client[dest_id][client_id].handle); + client[dest_id][client_id].handle = NULL; + } + mutex_unlock(&svc->m_lock); + + return 0; +} + +void apr_reset(void *handle) +{ + struct apr_reset_work *apr_reset_worker = NULL; + + if (!handle) + return; + pr_debug("%s: handle[%p]\n", __func__, handle); + + apr_reset_worker = kzalloc(sizeof(struct apr_reset_work), + GFP_ATOMIC); + if (apr_reset_worker == NULL || apr_reset_workqueue == NULL) { + pr_err("%s: mem failure\n", __func__); + return; + } + apr_reset_worker->handle = handle; + INIT_WORK(&apr_reset_worker->work, apr_reset_deregister); + queue_work(apr_reset_workqueue, &apr_reset_worker->work); +} + +void change_q6_state(int state) +{ + mutex_lock(&q6.lock); + q6.state = state; + mutex_unlock(&q6.lock); +} + +int adsp_state(int state) +{ + pr_info("dsp state = %d\n", state); + return 0; +} + +/* Dispatch the Reset events to Modem and audio clients */ +void dispatch_event(unsigned long code, unsigned short proc) +{ + struct apr_client *apr_client; + struct apr_client_data data; + struct apr_svc *svc; + uint16_t clnt; + int i, j; + + data.opcode = RESET_EVENTS; + data.reset_event = code; + data.reset_proc = proc; + + clnt = APR_CLIENT_AUDIO; + apr_client = &client[proc][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) { + mutex_lock(&apr_client->svc[i].m_lock); + if (apr_client->svc[i].fn) { + apr_client->svc[i].need_reset = 0x1; + apr_client->svc[i].fn(&data, apr_client->svc[i].priv); + } + if (apr_client->svc[i].port_cnt) { + svc = &(apr_client->svc[i]); + svc->need_reset = 0x1; + for (j = 0; j < APR_MAX_PORTS; j++) + if (svc->port_fn[j]) + svc->port_fn[j](&data, + svc->port_priv[j]); + } + mutex_unlock(&apr_client->svc[i].m_lock); + } + + clnt = APR_CLIENT_VOICE; + apr_client = &client[proc][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) { + mutex_lock(&apr_client->svc[i].m_lock); + if (apr_client->svc[i].fn) { + apr_client->svc[i].need_reset = 0x1; + apr_client->svc[i].fn(&data, apr_client->svc[i].priv); + } + if (apr_client->svc[i].port_cnt) { + svc = &(apr_client->svc[i]); + svc->need_reset = 0x1; + for (j = 0; j < APR_MAX_PORTS; j++) + if (svc->port_fn[j]) + svc->port_fn[j](&data, + svc->port_priv[j]); + } + mutex_unlock(&apr_client->svc[i].m_lock); + } +} + +static int modem_notifier_cb(struct notifier_block *this, unsigned long code, + void *_cmd) +{ + switch (code) { + case SUBSYS_BEFORE_SHUTDOWN: + pr_debug("M-Notify: Shutdown started\n"); + atomic_set(&modem_state, 0); + dispatch_event(code, APR_DEST_MODEM); + break; + case SUBSYS_AFTER_SHUTDOWN: + pr_debug("M-Notify: Shutdown Completed\n"); + break; + case SUBSYS_BEFORE_POWERUP: + pr_debug("M-notify: Bootup started\n"); + break; + case SUBSYS_AFTER_POWERUP: + if (atomic_read(&modem_state) == 0) { + atomic_set(&modem_state, 1); + wake_up(&modem_wait); + } + pr_debug("M-Notify: Bootup Completed\n"); + break; + default: + pr_err("M-Notify: General: %lu\n", code); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block mnb = { + .notifier_call = modem_notifier_cb, +}; + +static int lpass_notifier_cb(struct notifier_block *this, unsigned long code, + void *_cmd) +{ + switch (code) { + case SUBSYS_BEFORE_SHUTDOWN: + pr_debug("L-Notify: Shutdown started\n"); + atomic_set(&dsp_state, 0); + dispatch_event(code, APR_DEST_QDSP6); + break; + case SUBSYS_AFTER_SHUTDOWN: + pr_debug("L-Notify: Shutdown Completed\n"); + break; + case SUBSYS_BEFORE_POWERUP: + pr_debug("L-notify: Bootup started\n"); + break; + case SUBSYS_AFTER_POWERUP: + if (atomic_read(&dsp_state) == 0) { + atomic_set(&dsp_state, 1); + wake_up(&dsp_wait); + } + pr_debug("L-Notify: Bootup Completed\n"); + break; + default: + pr_err("L-Notify: Generel: %lu\n", code); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block lnb = { + .notifier_call = lpass_notifier_cb, +}; + + +static int __init apr_init(void) +{ + int i, j, k; + + for (i = 0; i < APR_DEST_MAX; i++) + for (j = 0; j < APR_CLIENT_MAX; j++) { + mutex_init(&client[i][j].m_lock); + for (k = 0; k < APR_SVC_MAX; k++) { + mutex_init(&client[i][j].svc[k].m_lock); + spin_lock_init(&client[i][j].svc[k].w_lock); + } + } + mutex_init(&q6.lock); + dsp_debug_register(adsp_state); + apr_reset_workqueue = + create_singlethread_workqueue("apr_driver"); + if (!apr_reset_workqueue) + return -ENOMEM; + return 0; +} +device_initcall(apr_init); + +static int __init apr_late_init(void) +{ + void *ret; + init_waitqueue_head(&dsp_wait); + init_waitqueue_head(&modem_wait); + atomic_set(&dsp_state, 1); + atomic_set(&modem_state, 1); + ret = subsys_notif_register_notifier("modem", &mnb); + pr_debug("subsys_register_notifier: ret1 = %p\n", ret); + ret = subsys_notif_register_notifier("lpass", &lnb); + pr_debug("subsys_register_notifier: ret2 = %p\n", ret); + + return 0; +} +late_initcall(apr_late_init); diff --git a/arch/arm/mach-msm/qdsp6v2/apr_tal.c b/arch/arm/mach-msm/qdsp6v2/apr_tal.c new file mode 100644 index 00000000000..418da4dd594 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/apr_tal.c @@ -0,0 +1,279 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *svc_names[APR_DEST_MAX][APR_CLIENT_MAX] = { + { + "apr_audio_svc", + "apr_voice_svc", + }, + { + "apr_audio_svc", + "apr_voice_svc", + }, +}; + +struct apr_svc_ch_dev apr_svc_ch[APR_DL_MAX][APR_DEST_MAX][APR_CLIENT_MAX]; + +int __apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data, int len) +{ + int w_len; + unsigned long flags; + + + spin_lock_irqsave(&apr_ch->w_lock, flags); + if (smd_write_avail(apr_ch->ch) < len) { + spin_unlock_irqrestore(&apr_ch->w_lock, flags); + return -EAGAIN; + } + + w_len = smd_write(apr_ch->ch, data, len); + spin_unlock_irqrestore(&apr_ch->w_lock, flags); + pr_debug("apr_tal:w_len = %d\n", w_len); + + if (w_len != len) { + pr_err("apr_tal: Error in write\n"); + return -ENETRESET; + } + return w_len; +} + +int apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data, int len) +{ + int rc = 0, retries = 0; + + if (!apr_ch->ch) + return -EINVAL; + + do { + if (rc == -EAGAIN) + udelay(50); + + rc = __apr_tal_write(apr_ch, data, len); + } while (rc == -EAGAIN && retries++ < 300); + + if (rc == -EAGAIN) + pr_err("apr_tal: TIMEOUT for write\n"); + + return rc; +} + +static void apr_tal_notify(void *priv, unsigned event) +{ + struct apr_svc_ch_dev *apr_ch = priv; + int len, r_len, sz; + int pkt_cnt = 0; + unsigned long flags; + + pr_debug("event = %d\n", event); + switch (event) { + case SMD_EVENT_DATA: + pkt_cnt = 0; + spin_lock_irqsave(&apr_ch->lock, flags); +check_pending: + len = smd_read_avail(apr_ch->ch); + if (len < 0) { + pr_err("apr_tal: Invalid Read Event :%d\n", len); + spin_unlock_irqrestore(&apr_ch->lock, flags); + return; + } + sz = smd_cur_packet_size(apr_ch->ch); + if (sz < 0) { + pr_debug("pkt size is zero\n"); + spin_unlock_irqrestore(&apr_ch->lock, flags); + return; + } + if (!len && !sz && !pkt_cnt) + goto check_write_avail; + if (!len) { + pr_debug("len = %d pkt_cnt = %d\n", len, pkt_cnt); + spin_unlock_irqrestore(&apr_ch->lock, flags); + return; + } + r_len = smd_read_from_cb(apr_ch->ch, apr_ch->data, len); + if (len != r_len) { + pr_err("apr_tal: Invalid Read\n"); + spin_unlock_irqrestore(&apr_ch->lock, flags); + return; + } + pkt_cnt++; + pr_debug("%d %d %d\n", len, sz, pkt_cnt); + if (apr_ch->func) + apr_ch->func(apr_ch->data, r_len, apr_ch->priv); + goto check_pending; +check_write_avail: + if (smd_write_avail(apr_ch->ch)) + wake_up(&apr_ch->wait); + spin_unlock_irqrestore(&apr_ch->lock, flags); + break; + case SMD_EVENT_OPEN: + pr_info("apr_tal: SMD_EVENT_OPEN\n"); + apr_ch->smd_state = 1; + wake_up(&apr_ch->wait); + break; + case SMD_EVENT_CLOSE: + pr_info("apr_tal: SMD_EVENT_CLOSE\n"); + break; + } +} + +struct apr_svc_ch_dev *apr_tal_open(uint32_t svc, uint32_t dest, + uint32_t dl, apr_svc_cb_fn func, void *priv) +{ + int rc; + + if ((svc >= APR_CLIENT_MAX) || (dest >= APR_DEST_MAX) || + (dl >= APR_DL_MAX)) { + pr_err("apr_tal: Invalid params\n"); + return NULL; + } + + if (apr_svc_ch[dl][dest][svc].ch) { + pr_err("apr_tal: This channel alreday openend\n"); + return NULL; + } + + mutex_lock(&apr_svc_ch[dl][dest][svc].m_lock); + if (!apr_svc_ch[dl][dest][svc].dest_state) { + rc = wait_event_timeout(apr_svc_ch[dl][dest][svc].dest, + apr_svc_ch[dl][dest][svc].dest_state, + msecs_to_jiffies(APR_OPEN_TIMEOUT_MS)); + if (rc == 0) { + pr_debug("apr_tal:Open timeout\n"); + mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock); + return NULL; + } + pr_debug("apr_tal:Wakeup done\n"); + apr_svc_ch[dl][dest][svc].dest_state = 0; + } + rc = smd_named_open_on_edge(svc_names[dest][svc], dest, + &apr_svc_ch[dl][dest][svc].ch, + &apr_svc_ch[dl][dest][svc], + apr_tal_notify); + if (rc < 0) { + pr_err("apr_tal: smd_open failed %s\n", + svc_names[dest][svc]); + mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock); + return NULL; + } + rc = wait_event_timeout(apr_svc_ch[dl][dest][svc].wait, + (apr_svc_ch[dl][dest][svc].smd_state == 1), 5 * HZ); + if (rc == 0) { + pr_err("apr_tal:TIMEOUT for OPEN event\n"); + mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock); + return NULL; + } + if (!apr_svc_ch[dl][dest][svc].dest_state) { + apr_svc_ch[dl][dest][svc].dest_state = 1; + pr_debug("apr_tal:Waiting for apr svc init\n"); + msleep(200); + pr_debug("apr_tal:apr svc init done\n"); + } + apr_svc_ch[dl][dest][svc].smd_state = 0; + + apr_svc_ch[dl][dest][svc].func = func; + apr_svc_ch[dl][dest][svc].priv = priv; + mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock); + + return &apr_svc_ch[dl][dest][svc]; +} + +int apr_tal_close(struct apr_svc_ch_dev *apr_ch) +{ + int r; + + if (!apr_ch->ch) + return -EINVAL; + + mutex_lock(&apr_ch->m_lock); + r = smd_close(apr_ch->ch); + apr_ch->ch = NULL; + apr_ch->func = NULL; + apr_ch->priv = NULL; + mutex_unlock(&apr_ch->m_lock); + return r; +} + +static int apr_smd_probe(struct platform_device *pdev) +{ + int dest; + int clnt; + + if (pdev->id == APR_DEST_MODEM) { + pr_info("apr_tal:Modem Is Up\n"); + dest = APR_DEST_MODEM; + clnt = APR_CLIENT_VOICE; + apr_svc_ch[APR_DL_SMD][dest][clnt].dest_state = 1; + wake_up(&apr_svc_ch[APR_DL_SMD][dest][clnt].dest); + } else if (pdev->id == APR_DEST_QDSP6) { + pr_info("apr_tal:Q6 Is Up\n"); + dest = APR_DEST_QDSP6; + clnt = APR_CLIENT_AUDIO; + apr_svc_ch[APR_DL_SMD][dest][clnt].dest_state = 1; + wake_up(&apr_svc_ch[APR_DL_SMD][dest][clnt].dest); + } else + pr_err("apr_tal:Invalid Dest Id: %d\n", pdev->id); + + return 0; +} + +static struct platform_driver apr_q6_driver = { + .probe = apr_smd_probe, + .driver = { + .name = "apr_audio_svc", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver apr_modem_driver = { + .probe = apr_smd_probe, + .driver = { + .name = "apr_voice_svc", + .owner = THIS_MODULE, + }, +}; + +static int __init apr_tal_init(void) +{ + int i, j, k; + + for (i = 0; i < APR_DL_MAX; i++) + for (j = 0; j < APR_DEST_MAX; j++) + for (k = 0; k < APR_CLIENT_MAX; k++) { + init_waitqueue_head(&apr_svc_ch[i][j][k].wait); + init_waitqueue_head(&apr_svc_ch[i][j][k].dest); + spin_lock_init(&apr_svc_ch[i][j][k].lock); + spin_lock_init(&apr_svc_ch[i][j][k].w_lock); + mutex_init(&apr_svc_ch[i][j][k].m_lock); + } + platform_driver_register(&apr_q6_driver); + platform_driver_register(&apr_modem_driver); + return 0; +} +device_initcall(apr_tal_init); diff --git a/arch/arm/mach-msm/qdsp6v2/audio_aac.c b/arch/arm/mach-msm/qdsp6v2/audio_aac.c new file mode 100644 index 00000000000..4c9a9b49b42 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_aac.c @@ -0,0 +1,1674 @@ +/* aac audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2010-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADRV_STATUS_AIO_INTF 0x00000001 /* AIO interface */ +#define ADRV_STATUS_FSYNC 0x00000008 +#define ADRV_STATUS_PAUSE 0x00000010 + +#define TUNNEL_MODE 0x0000 +#define NON_TUNNEL_MODE 0x0001 +#define AUDAAC_EOS_SET 0x00000001 + +/* Default number of pre-allocated event packets */ +#define AUDAAC_EVENT_NUM 10 + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +struct timestamp { + unsigned long lowpart; + unsigned long highpart; +} __attribute__ ((packed)); + +struct meta_in { + unsigned char reserved[18]; + unsigned short offset; + struct timestamp ntimestamp; + unsigned int nflags; +} __attribute__ ((packed)); + +struct meta_out_dsp{ + u32 offset_to_frame; + u32 frame_size; + u32 encoded_pcm_samples; + u32 msw_ts; + u32 lsw_ts; + u32 nflags; +} __attribute__ ((packed)); + +struct dec_meta_out{ + unsigned int reserved[7]; + unsigned int num_of_frames; + struct meta_out_dsp meta_out_dsp[]; +} __attribute__ ((packed)); + +/* General meta field to store meta info +locally */ +union meta_data { + struct dec_meta_out meta_out; + struct meta_in meta_in; +} __attribute__ ((packed)); + +struct audaac_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audaac_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audaac_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; + unsigned long token; + void *kvaddr; + union meta_data meta_info; +}; + +struct q6audio; + +struct audaac_drv_operations { + void (*out_flush) (struct q6audio *); + void (*in_flush) (struct q6audio *); + int (*fsync)(struct q6audio *); +}; + +#define PCM_BUF_COUNT (2) +/* Buffer with meta */ +#define PCM_BUFSZ_MIN ((8192) + sizeof(struct dec_meta_out)) + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (2) +#define FRAME_SIZE ((4*1536) + sizeof(struct meta_in)) + +struct q6audio { + atomic_t in_bytes; + atomic_t in_samples; + + struct msm_audio_stream_config str_cfg; + struct msm_audio_buf_cfg buf_cfg; + struct msm_audio_config pcm_cfg; + struct msm_audio_aac_config aac_config; + + struct audio_client *ac; + + struct mutex lock; + struct mutex read_lock; + struct mutex write_lock; + struct mutex get_event_lock; + wait_queue_head_t cmd_wait; + wait_queue_head_t write_wait; + wait_queue_head_t event_wait; + spinlock_t dsp_lock; + spinlock_t event_queue_lock; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + struct list_head out_queue; /* queue to retain output buffers */ + struct list_head in_queue; /* queue to retain input buffers */ + struct list_head free_event_queue; + struct list_head event_queue; + struct list_head pmem_region_queue; /* protected by lock */ + struct audaac_drv_operations drv_ops; + union msm_audio_event_payload eos_write_payload; + + uint32_t drv_status; + int event_abort; + int eos_rsp; + int eos_flag; + int opened; + int enabled; + int stopped; + int feedback; + int rflush; /* Read flush */ + int wflush; /* Write flush */ +}; + +static int insert_eos_buf(struct q6audio *audio, + struct audaac_buffer_node *buf_node) { + struct dec_meta_out *eos_buf = buf_node->kvaddr; + eos_buf->num_of_frames = 0xFFFFFFFF; + eos_buf->meta_out_dsp[0].offset_to_frame = 0x0; + eos_buf->meta_out_dsp[0].nflags = AUDAAC_EOS_SET; + return sizeof(struct dec_meta_out) + + sizeof(eos_buf->meta_out_dsp[0]); +} + +/* Routine which updates read buffers of driver/dsp, + for flush operation as DSP output might not have proper + value set */ +static int insert_meta_data(struct q6audio *audio, + struct audaac_buffer_node *buf_node) { + struct dec_meta_out *meta_data = buf_node->kvaddr; + meta_data->num_of_frames = 0x0; + meta_data->meta_out_dsp[0].offset_to_frame = 0x0; + meta_data->meta_out_dsp[0].nflags = 0x0; + return sizeof(struct dec_meta_out) + + sizeof(meta_data->meta_out_dsp[0]); +} + +static void extract_meta_info(struct q6audio *audio, + struct audaac_buffer_node *buf_node, int dir) +{ + if (dir) { /* input buffer - Write */ + if (audio->buf_cfg.meta_info_enable) + memcpy(&buf_node->meta_info.meta_in, + (char *)buf_node->kvaddr, sizeof(struct meta_in)); + else + memset(&buf_node->meta_info.meta_in, + 0, sizeof(struct meta_in)); + pr_debug("i/p: msw_ts 0x%lx lsw_ts 0x%lx nflags 0x%8x\n", + buf_node->meta_info.meta_in.ntimestamp.highpart, + buf_node->meta_info.meta_in.ntimestamp.lowpart, + buf_node->meta_info.meta_in.nflags); + } else { /* output buffer - Read */ + memcpy((char *)buf_node->kvaddr, + &buf_node->meta_info.meta_out, + sizeof(struct dec_meta_out)); + pr_debug("o/p: msw_ts 0x%8x lsw_ts 0x%8x nflags 0x%8x," + "num_frames = %d\n", + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].msw_ts, + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].lsw_ts, + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].nflags, + ((struct dec_meta_out *)buf_node->kvaddr)->num_of_frames); + } +} + +static int audaac_pmem_lookup_vaddr(struct q6audio *audio, void *addr, + unsigned long len, struct audaac_pmem_region **region) +{ + struct audaac_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + pr_err("multiple hits for vaddr %p, len %ld\n", addr, len); + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + pr_err("\t%p, %ld --> %p\n", region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +static unsigned long audaac_pmem_fixup(struct q6audio *audio, void *addr, + unsigned long len, int ref_up, void **kvaddr) +{ + struct audaac_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audaac_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + pr_err("lookup (%p, %ld) failed\n", addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + pr_debug("found region %p ref_cnt %d\n", region, region->ref_cnt); + paddr = region->paddr + (addr - region->vaddr); + /* provide kernel virtual address for accessing meta information */ + if (kvaddr) + *kvaddr = (void *) (region->kvaddr + (addr - region->vaddr)); + return paddr; +} + +static void audaac_post_event(struct q6audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audaac_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audaac_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audaac_event), GFP_ATOMIC); + if (!e_node) { + pr_err("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static int audaac_enable(struct q6audio *audio) +{ + /* 2nd arg: 0 -> run immediately + 3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */ + return q6asm_run(audio->ac, 0x00, 0x00, 0x00); +} + +static int audaac_disable(struct q6audio *audio) +{ + int rc = 0; + if (audio->opened) { + audio->enabled = 0; + audio->opened = 0; + pr_debug("%s: inbytes[%d] insamples[%d]\n", __func__, + atomic_read(&audio->in_bytes), + atomic_read(&audio->in_samples)); + /* Close the session */ + rc = q6asm_cmd(audio->ac, CMD_CLOSE); + if (rc < 0) + pr_err("Failed to close the session rc=%d\n", rc); + audio->stopped = 1; + wake_up(&audio->write_wait); + wake_up(&audio->cmd_wait); + } + pr_debug("enabled[%d]\n", audio->enabled); + return rc; +} + +static int audaac_pause(struct q6audio *audio) +{ + int rc = 0; + + pr_debug("%s, enabled = %d\n", __func__, + audio->enabled); + if (audio->enabled) { + rc = q6asm_cmd(audio->ac, CMD_PAUSE); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, rc); + + } else + pr_err("%s: Driver not enabled\n", __func__); + return rc; +} + +static int audaac_flush(struct q6audio *audio) +{ + int rc; + + if (audio->enabled) { + /* Implicitly issue a pause to the decoder before flushing if + it is not in pause state */ + if (!(audio->drv_status & ADRV_STATUS_PAUSE)) { + rc = audaac_pause(audio); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, + rc); + else + audio->drv_status |= ADRV_STATUS_PAUSE; + } + rc = q6asm_cmd(audio->ac, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + /* Not in stop state, reenable the stream */ + if (audio->stopped == 0) { + rc = audaac_enable(audio); + if (rc) + pr_err("%s:audio re-enable failed\n", __func__); + else { + audio->enabled = 1; + if (audio->drv_status & ADRV_STATUS_PAUSE) + audio->drv_status &= ~ADRV_STATUS_PAUSE; + } + } + } + pr_debug("in_bytes %d\n", atomic_read(&audio->in_bytes)); + pr_debug("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); + return 0; +} + +static int audaac_outport_flush(struct q6audio *audio) +{ + int rc; + + rc = q6asm_cmd(audio->ac, CMD_OUT_FLUSH); + if (rc < 0) + pr_err("%s: output port flush cmd failed rc=%d\n", __func__, + rc); + return rc; +} + +static void audaac_async_read(struct q6audio *audio, + struct audaac_buffer_node *buf_node) +{ + struct audio_client *ac; + struct audio_aio_read_param param; + int rc; + + pr_debug("%s: Send read buff %p phy %lx len %d\n", __func__, buf_node, + buf_node->paddr, buf_node->buf.buf_len); + ac = audio->ac; + /* Provide address so driver can append nr frames information */ + param.paddr = buf_node->paddr + + sizeof(struct dec_meta_out); + param.len = buf_node->buf.buf_len - + sizeof(struct dec_meta_out); + param.uid = param.paddr; + /* Write command will populate paddr as token */ + buf_node->token = param.paddr; + rc = q6asm_async_read(ac, ¶m); + if (rc < 0) + pr_err("%s:failed\n", __func__); +} + +static void audaac_async_write(struct q6audio *audio, + struct audaac_buffer_node *buf_node) +{ + int rc; + struct audio_client *ac; + struct audio_aio_write_param param; + + pr_debug("%s: Send write buff %p phy %lx len %d, meta_enable = %d\n", + __func__, buf_node, buf_node->paddr, buf_node->buf.data_len, + audio->buf_cfg.meta_info_enable); + + ac = audio->ac; + /* Offset with appropriate meta */ + param.paddr = buf_node->paddr + sizeof(struct meta_in); + param.len = buf_node->buf.data_len - sizeof(struct meta_in); + param.msw_ts = buf_node->meta_info.meta_in.ntimestamp.highpart; + param.lsw_ts = buf_node->meta_info.meta_in.ntimestamp.lowpart; + /* If no meta_info enaled, indicate no time stamp valid */ + if (audio->buf_cfg.meta_info_enable) + param.flags = 0; + else + param.flags = 0xFF00; + param.uid = param.paddr; + /* Read command will populate paddr as token */ + buf_node->token = param.paddr; + rc = q6asm_async_write(ac, ¶m); + if (rc < 0) + pr_err("%s:failed\n", __func__); +} + +/* Write buffer to DSP / Handle Ack from DSP */ +static void audaac_async_write_ack(struct q6audio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audaac_buffer_node *used_buf; + + /* No active flush in progress */ + if (audio->wflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + BUG_ON(list_empty(&audio->out_queue)); + used_buf = list_first_entry(&audio->out_queue, + struct audaac_buffer_node, list); + if (token == used_buf->token) { + list_del(&used_buf->list); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + pr_debug("consumed buffer\n"); + event_payload.aio_buf = used_buf->buf; + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, + event_payload); + kfree(used_buf); + if (list_empty(&audio->out_queue) && + (audio->drv_status & ADRV_STATUS_FSYNC)) { + pr_debug("%s: list is empty, reached EOS in\ + Tunnel\n", __func__); + wake_up(&audio->write_wait); + } + } else { + pr_err("expected=%lx ret=%x\n", used_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +/* Read buffer from DSP / Handle Ack from DSP */ +static void audaac_async_read_ack(struct q6audio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audaac_buffer_node *filled_buf; + + /* No active flush in progress */ + if (audio->rflush) + return; + + /* Statistics of read */ + atomic_add(payload[2], &audio->in_bytes); + atomic_add(payload[7], &audio->in_samples); + + spin_lock_irqsave(&audio->dsp_lock, flags); + BUG_ON(list_empty(&audio->in_queue)); + filled_buf = list_first_entry(&audio->in_queue, + struct audaac_buffer_node, list); + if (token == (filled_buf->token)) { + list_del(&filled_buf->list); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + event_payload.aio_buf = filled_buf->buf; + /* Read done Buffer due to flush/normal condition + after EOS event, so append EOS buffer */ + if (audio->eos_rsp == 0x1) { + event_payload.aio_buf.data_len = + insert_eos_buf(audio, filled_buf); + /* Reset flag back to indicate eos intimated */ + audio->eos_rsp = 0; + } else { + filled_buf->meta_info.meta_out.num_of_frames = + payload[7]; + event_payload.aio_buf.data_len = payload[2] + \ + payload[3] + \ + sizeof(struct dec_meta_out); + pr_debug("nr of frames 0x%8x len=%d\n", + filled_buf->meta_info.meta_out.num_of_frames, + event_payload.aio_buf.data_len); + extract_meta_info(audio, filled_buf, 0); + audio->eos_rsp = 0; + } + audaac_post_event(audio, AUDIO_EVENT_READ_DONE, event_payload); + kfree(filled_buf); + } else { + pr_err("expected=%lx ret=%x\n", filled_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +static void q6_audaac_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6audio *audio = (struct q6audio *)priv; + union msm_audio_event_payload e_payload; + + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: + pr_debug("%s:ASM_DATA_EVENT_WRITE_DONE token = 0x%x\n", + __func__, token); + audaac_async_write_ack(audio, token, payload); + break; + case ASM_DATA_EVENT_READ_DONE: + pr_debug("%s:ASM_DATA_EVENT_READ_DONE token = 0x%x\n", + __func__, token); + audaac_async_read_ack(audio, token, payload); + break; + case ASM_DATA_CMDRSP_EOS: + /* EOS Handle */ + pr_debug("%s:ASM_DATA_CMDRSP_EOS\n", __func__); + if (audio->feedback) { /* Non-Tunnel mode */ + audio->eos_rsp = 1; + /* propagate input EOS i/p buffer, + after receiving DSP acknowledgement */ + if (audio->eos_flag && + (audio->eos_write_payload.aio_buf.buf_addr)) { + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, + audio->eos_write_payload); + memset(&audio->eos_write_payload , 0, + sizeof(union msm_audio_event_payload)); + audio->eos_flag = 0; + } + } else { /* Tunnel mode */ + audio->eos_rsp = 1; + wake_up(&audio->write_wait); + wake_up(&audio->cmd_wait); + } + break; + case ASM_DATA_CMD_MEDIA_FORMAT_UPDATE: + case ASM_STREAM_CMD_SET_ENCDEC_PARAM: + pr_debug("%s:payload0[%x] payloa1d[%x]opcode= 0x%x\n", + __func__, payload[0], payload[1], opcode); + break; + + case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY: + pr_debug("%s: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, " + "payload[0]-sr = %d, payload[1]-chl = %d, " + "payload[2] = %d, payload[3] = %d\n", __func__, + payload[0], payload[1], payload[2], + payload[3]); + pr_debug("%s: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, " + "sr(prev) = %d, chl(prev) = %d,", + __func__, audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + audio->pcm_cfg.sample_rate = payload[0]; + audio->pcm_cfg.channel_count = payload[1] & 0xFFFF; + e_payload.stream_info.chan_info = audio->pcm_cfg.channel_count; + e_payload.stream_info.sample_rate = audio->pcm_cfg.sample_rate; + audaac_post_event(audio, AUDIO_EVENT_STREAM_INFO, e_payload); + break; + default: + pr_debug("%s:Unhandled event = 0x%8x\n", __func__, opcode); + break; + } +} + +/* ------------------- device --------------------- */ +static void audaac_async_out_flush(struct q6audio *audio) +{ + struct audaac_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + unsigned long flags; + + pr_debug("%s\n", __func__); + /* EOS followed by flush, EOS response not guranteed, free EOS i/p + buffer */ + spin_lock_irqsave(&audio->dsp_lock, flags); + if (audio->eos_flag && (audio->eos_write_payload.aio_buf.buf_addr)) { + pr_debug("%s: EOS followed by flush received,acknowledge eos"\ + " i/p buffer immediately\n", __func__); + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, + audio->eos_write_payload); + memset(&audio->eos_write_payload , 0, + sizeof(union msm_audio_event_payload)); + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audaac_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, payload); + kfree(buf_node); + pr_debug("%s: Propagate WRITE_DONE during flush\n", __func__); + } +} + +static void audaac_async_in_flush(struct q6audio *audio) +{ + struct audaac_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + pr_debug("%s\n", __func__); + list_for_each_safe(ptr, next, &audio->in_queue) { + buf_node = list_entry(ptr, struct audaac_buffer_node, list); + list_del(&buf_node->list); + /* Forcefull send o/p eos buffer after flush, if no eos response + * received by dsp even after sending eos command */ + if ((audio->eos_rsp != 1) && audio->eos_flag) { + pr_debug("%s: send eos on o/p buffer during flush\n",\ + __func__); + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = + insert_eos_buf(audio, buf_node); + audio->eos_flag = 0; + } else { + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = + insert_meta_data(audio, buf_node); + } + audaac_post_event(audio, AUDIO_EVENT_READ_DONE, payload); + kfree(buf_node); + pr_debug("%s: Propagate READ_DONE during flush\n", __func__); + } +} + +static void audaac_ioport_reset(struct q6audio *audio) +{ + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + /* If fsync is in progress, make sure + * return value of fsync indicates + * abort due to flush + */ + if (audio->drv_status & ADRV_STATUS_FSYNC) { + pr_debug("fsync in progress\n"); + audio->drv_ops.out_flush(audio); + } else + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + } +} + +static int audaac_events_pending(struct q6audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audaac_reset_event_queue(struct q6audio *audio) +{ + unsigned long flags; + struct audaac_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audaac_process_event_req(struct q6audio *audio, void __user * arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audaac_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int)usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout(audio->event_wait, + audaac_events_pending + (audio), + msecs_to_jiffies + (timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible(audio->event_wait, + audaac_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else { + pr_err("Unexpected path\n"); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return -EPERM; + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (drv_evt->event_type == AUDIO_EVENT_WRITE_DONE) { + pr_debug("posted AUDIO_EVENT_WRITE_DONE to user\n"); + mutex_lock(&audio->write_lock); + audaac_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0, 0); + mutex_unlock(&audio->write_lock); + } else if (drv_evt->event_type == AUDIO_EVENT_READ_DONE) { + pr_debug("posted AUDIO_EVENT_READ_DONE to user\n"); + mutex_lock(&audio->read_lock); + audaac_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0, 0); + mutex_unlock(&audio->read_lock); + } + + /* Some read buffer might be held up in DSP, release all + * once EOS indicated + */ + if (audio->eos_rsp && !list_empty(&audio->in_queue)) { + pr_debug("Send flush command to release read buffers"\ + "held up in DSP\n"); + audaac_flush(audio); + } + + if (copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audaac_pmem_check(struct q6audio *audio, + void *vaddr, unsigned long len) +{ + struct audaac_pmem_region *region_elt; + struct audaac_pmem_region t = {.vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + pr_err("region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audaac_pmem_add(struct q6audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audaac_pmem_region *region; + int rc = -EINVAL; + + pr_debug("%s:\n", __func__); + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = audaac_pmem_check(audio, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + pr_debug("add region paddr %lx vaddr %p, len %lu kvaddr %lx\n", + region->paddr, region->vaddr, region->len, region->kvaddr); + list_add_tail(®ion->list, &audio->pmem_region_queue); + + rc = q6asm_memory_map(audio->ac, (uint32_t) paddr, IN, (uint32_t) len, + 1); + if (rc < 0) + pr_err("%s: memory map failed\n", __func__); +end: + return rc; +} + +static int audaac_pmem_remove(struct q6audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audaac_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + pr_debug("info fd %d vaddr %p\n", info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audaac_pmem_region, list); + + if ((region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + pr_debug("region %p in use ref_cnt %d\n", + region, region->ref_cnt); + break; + } + pr_debug("remove region fd %d vaddr %p\n", + info->fd, info->vaddr); + rc = q6asm_memory_unmap(audio->ac, + (uint32_t) region->paddr, IN); + if (rc < 0) + pr_err("%s: memory unmap failed\n", __func__); + + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +/* audio -> lock must be held at this point */ +static int audaac_aio_buf_add(struct q6audio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audaac_buffer_node *buf_node; + + buf_node = kzalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + pr_debug("node %p dir %x buf_addr %p buf_len %d data_len \ + %d\n", buf_node, dir, buf_node->buf.buf_addr, + buf_node->buf.buf_len, buf_node->buf.data_len); + + buf_node->paddr = audaac_pmem_fixup(audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1, + &buf_node->kvaddr); + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (!audio->feedback && !buf_node->buf.data_len)) { + kfree(buf_node); + return -EINVAL; + } + extract_meta_info(audio, buf_node, 1); + /* Not a EOS buffer */ + if (!(buf_node->meta_info.meta_in.nflags & AUDAAC_EOS_SET)) { + spin_lock_irqsave(&audio->dsp_lock, flags); + audaac_async_write(audio, buf_node); + /* EOS buffer handled in driver */ + list_add_tail(&buf_node->list, &audio->out_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + if (buf_node->meta_info.meta_in.nflags & AUDAAC_EOS_SET) { + if (!audio->wflush) { + pr_debug("%s:Send EOS cmd at i/p\n", __func__); + /* Driver will forcefully post writedone event + * once eos ack recived from DSP + */ + audio->eos_write_payload.aio_buf = + buf_node->buf; + audio->eos_flag = 1; + audio->eos_rsp = 0; + q6asm_cmd(audio->ac, CMD_EOS); + kfree(buf_node); + } else {/* Flush in progress, send back i/p + * EOS buffer as is + */ + union msm_audio_event_payload event_payload; + event_payload.aio_buf = buf_node->buf; + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, + event_payload); + kfree(buf_node); + } + + } + } else { + /* read */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.buf_len < PCM_BUFSZ_MIN)) { + kfree(buf_node); + return -EINVAL; + } + /* No EOS reached */ + if (!audio->eos_rsp) { + spin_lock_irqsave(&audio->dsp_lock, flags); + audaac_async_read(audio, buf_node); + /* EOS buffer handled in driver */ + list_add_tail(&buf_node->list, &audio->in_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } else { + /* EOS reached at input side fake all upcoming read buffer to + * indicate the same + */ + union msm_audio_event_payload event_payload; + event_payload.aio_buf = buf_node->buf; + event_payload.aio_buf.data_len = + insert_eos_buf(audio, buf_node); + pr_debug("%s: propagate READ_DONE as EOS done\n", + __func__); + audaac_post_event(audio, AUDIO_EVENT_READ_DONE, + event_payload); + kfree(buf_node); + } + } + return 0; +} + +/* TBD: Only useful in tunnel-mode */ +int audaac_async_fsync(struct q6audio *audio) +{ + int rc = 0; + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + pr_debug("%s:\n", __func__); + + mutex_lock(&audio->write_lock); + audio->eos_rsp = 0; + + rc = wait_event_interruptible(audio->write_wait, + (list_empty(&audio->out_queue)) || + audio->wflush || audio->stopped); + + if (rc < 0) { + pr_err("%s: wait event for list_empty failed, rc = %d\n", + __func__, rc); + goto done; + } + + rc = q6asm_cmd(audio->ac, CMD_EOS); + + if (rc < 0) + pr_err("%s: q6asm_cmd failed, rc = %d", __func__, rc); + + rc = wait_event_interruptible(audio->write_wait, + (audio->eos_rsp || audio->wflush || + audio->stopped)); + + if (rc < 0) { + pr_err("%s: wait event for eos_rsp failed, rc = %d\n", __func__, + rc); + goto done; + } + + if (audio->eos_rsp == 1) { + rc = audaac_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->enabled = 1; + } + } + + if (audio->stopped || audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +int audaac_fsync(struct file *file, int datasync) +{ + struct q6audio *audio = file->private_data; + + if (!audio->enabled || audio->feedback) + return -EINVAL; + + return audio->drv_ops.fsync(audio); +} + +static void audaac_reset_pmem_region(struct q6audio *audio) +{ + struct audaac_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audaac_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t audaac_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audaac_debug_read(struct file *file, char __user * buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0; + struct q6audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "feedback %d\n", audio->feedback); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "inqueue empty %d\n", list_empty(&audio->in_queue)); + n += scnprintf(buffer + n, debug_bufmax - n, + "outqueue empty %d\n", list_empty(&audio->out_queue)); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audaac_debug_fops = { + .read = audaac_debug_read, + .open = audaac_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + if (cmd == AUDIO_GET_EVENT) { + pr_debug("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audaac_process_event_req(audio, + (void __user *)arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ASYNC_WRITE) { + mutex_lock(&audio->write_lock); + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else { + if (audio->enabled) + rc = audaac_aio_buf_add(audio, 1, + (void __user *)arg); + else + rc = -EPERM; + } + mutex_unlock(&audio->write_lock); + return rc; + } + + if (cmd == AUDIO_ASYNC_READ) { + mutex_lock(&audio->read_lock); + if ((audio->feedback) && (audio->enabled)) + rc = audaac_aio_buf_add(audio, 0, + (void __user *)arg); + else + rc = -EPERM; + mutex_unlock(&audio->read_lock); + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + struct asm_aac_cfg aac_cfg; + uint32_t sbr_ps = 0x00; + if (audio->feedback == NON_TUNNEL_MODE) { + /* Configure PCM output block */ + rc = q6asm_enc_cfg_blk_pcm(audio->ac, + audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + if (rc < 0) { + pr_err("pcm output block config failed\n"); + break; + } + } + /* turn on both sbr and ps */ + rc = q6asm_enable_sbrps(audio->ac, sbr_ps); + if (rc < 0) + pr_err("sbr-ps enable failed\n"); + if (audio->aac_config.sbr_ps_on_flag) + aac_cfg.aot = AAC_ENC_MODE_EAAC_P; + else if (audio->aac_config.sbr_on_flag) + aac_cfg.aot = AAC_ENC_MODE_AAC_P; + else + aac_cfg.aot = AAC_ENC_MODE_AAC_LC; + + switch (audio->aac_config.format) { + case AUDIO_AAC_FORMAT_ADTS: + aac_cfg.format = 0x00; + break; + case AUDIO_AAC_FORMAT_LOAS: + aac_cfg.format = 0x01; + break; + /* ADIF, use it as RAW */ + default: + case AUDIO_AAC_FORMAT_RAW: + aac_cfg.format = 0x03; + } + aac_cfg.ep_config = audio->aac_config.ep_config; + aac_cfg.section_data_resilience = + audio->aac_config.aac_section_data_resilience_flag; + aac_cfg.scalefactor_data_resilience = + audio->aac_config.aac_scalefactor_data_resilience_flag; + aac_cfg.spectral_data_resilience = + audio->aac_config.aac_spectral_data_resilience_flag; + aac_cfg.ch_cfg = audio->pcm_cfg.channel_count; + aac_cfg.sample_rate = audio->pcm_cfg.sample_rate; + + pr_debug("%s:format=%x aot=%d ch=%d sr=%d\n", + __func__, aac_cfg.format, + aac_cfg.aot, aac_cfg.ch_cfg, + aac_cfg.sample_rate); + + /* Configure Media format block */ + rc = q6asm_media_format_block_aac(audio->ac, &aac_cfg); + if (rc < 0) { + pr_err("cmd media format block failed\n"); + break; + } + rc = audaac_enable(audio); + audio->eos_rsp = 0; + audio->eos_flag = 0; + if (!rc) { + audio->enabled = 1; + } else { + audio->enabled = 0; + pr_err("Audio Start procedure failed rc=%d\n", rc); + break; + } + pr_info("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__, + audio->ac->session, + audio->enabled); + if (audio->stopped == 1) + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + pr_debug("%s: AUDIO_STOP sessionid[%d]\n", __func__, + audio->ac->session); + audio->stopped = 1; + audaac_flush(audio); + audio->enabled = 0; + audio->drv_status &= ~ADRV_STATUS_PAUSE; + if (rc < 0) { + pr_err("Audio Stop procedure failed rc=%d\n", rc); + break; + } + break; + } + case AUDIO_PAUSE: { + pr_debug("AUDIO_PAUSE %ld\n", arg); + if (arg == 1) { + rc = audaac_pause(audio); + if (rc < 0) + pr_err("%s: pause FAILED rc=%d\n", __func__, + rc); + audio->drv_status |= ADRV_STATUS_PAUSE; + } else if (arg == 0) { + if (audio->drv_status & ADRV_STATUS_PAUSE) { + rc = audaac_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", + __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->enabled = 1; + } + } + } + break; + } + case AUDIO_FLUSH: { + pr_debug("%s: AUDIO_FLUSH sessionid[%d]\n", __func__, + audio->ac->session); + audio->rflush = 1; + audio->wflush = 1; + /* Flush DSP */ + rc = audaac_flush(audio); + /* Flush input / Output buffer in software*/ + audaac_ioport_reset(audio); + if (rc < 0) { + pr_err("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } else { + audio->rflush = 0; + audio->wflush = 0; + } + audio->eos_flag = 0; + audio->eos_rsp = 0; + break; + } + case AUDIO_OUTPORT_FLUSH: + { + pr_debug("AUDIO_OUTPORT_FLUSH\n"); + rc = audaac_outport_flush(audio); + if (rc < 0) { + pr_err("%s: AUDIO_OUTPORT_FLUSH failed\n", __func__); + rc = -EINTR; + } + break; + } + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audaac_pmem_add(audio, &info); + break; + } + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("AUDIO_DEREGISTER_PMEM\n"); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audaac_pmem_remove(audio, &info); + break; + } + case AUDIO_GET_AAC_CONFIG: { + if (copy_to_user((void *)arg, &audio->aac_config, + sizeof(struct msm_audio_aac_config))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_SET_AAC_CONFIG: { + if (copy_from_user(&audio->aac_config, (void *)arg, + sizeof(struct msm_audio_aac_config))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->str_cfg.buffer_size; + cfg.buffer_count = audio->str_cfg.buffer_count; + pr_debug("GET STREAM CFG %d %d\n", cfg.buffer_size, + cfg.buffer_count); + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + pr_debug("SET STREAM CONFIG\n"); + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + if (copy_to_user((void *)arg, &audio->pcm_cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (audio->feedback != NON_TUNNEL_MODE) { + pr_err("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + audio->pcm_cfg.buffer_count = config.buffer_count; + audio->pcm_cfg.buffer_size = config.buffer_size; + audio->pcm_cfg.channel_count = config.channel_count; + audio->pcm_cfg.sample_rate = config.sample_rate; + rc = 0; + break; + } + case AUDIO_SET_BUF_CFG: { + struct msm_audio_buf_cfg cfg; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if ((audio->feedback == NON_TUNNEL_MODE) && + !cfg.meta_info_enable) { + rc = -EFAULT; + break; + } + + audio->buf_cfg.meta_info_enable = cfg.meta_info_enable; + pr_debug("%s:session id %d: Set-buf-cfg: meta[%d]", __func__, + audio->ac->session, cfg.meta_info_enable); + break; + } + case AUDIO_GET_BUF_CFG: { + pr_debug("%s:session id %d: Get-buf-cfg: meta[%d]\ + framesperbuf[%d]\n", __func__, + audio->ac->session, audio->buf_cfg.meta_info_enable, + audio->buf_cfg.frames_per_buf); + + if (copy_to_user((void *)arg, &audio->buf_cfg, + sizeof(struct msm_audio_buf_cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *)arg, &audio->ac->session, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct q6audio *audio = file->private_data; + pr_debug("%s: aac dec\n", __func__); + mutex_lock(&audio->lock); + audaac_disable(audio); + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + audaac_reset_pmem_region(audio); + audio->event_abort = 1; + wake_up(&audio->event_wait); + audaac_reset_event_queue(audio); + q6asm_audio_client_free(audio->ac); + mutex_unlock(&audio->lock); + mutex_destroy(&audio->lock); + mutex_destroy(&audio->read_lock); + mutex_destroy(&audio->write_lock); + mutex_destroy(&audio->get_event_lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + pr_info("%s:aac dec success\n", __func__); + return 0; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio *audio = NULL; + int rc = 0; + int i; + struct audaac_event *e_node = NULL; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_aac_" + 5]; +#endif + audio = kzalloc(sizeof(struct q6audio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for aac decode driver\n"); + return -ENOMEM; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + audio->pcm_cfg.buffer_count = PCM_BUF_COUNT; + audio->pcm_cfg.sample_rate = 48000; + audio->pcm_cfg.channel_count = 2; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audaac_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio); + return -ENOMEM; + } + /* Only AIO interface */ + if (file->f_flags & O_NONBLOCK) { + pr_debug("set to aio interface\n"); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + audio->drv_ops.out_flush = audaac_async_out_flush; + audio->drv_ops.in_flush = audaac_async_in_flush; + audio->drv_ops.fsync = audaac_async_fsync; + q6asm_set_io_mode(audio->ac, ASYNC_IO_MODE); + } else { + pr_err("SIO interface not supported\n"); + rc = -EACCES; + goto fail; + } + + /* open in T/NT mode */ + if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) { + rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM, + FORMAT_MPEG4_AAC); + if (rc < 0) { + pr_err("NT mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = NON_TUNNEL_MODE; + /* open AAC decoder, expected frames is always 1 + audio->buf_cfg.frames_per_buf = 0x01;*/ + audio->buf_cfg.meta_info_enable = 0x01; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + rc = q6asm_open_write(audio->ac, FORMAT_MPEG4_AAC); + if (rc < 0) { + pr_err("T mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = TUNNEL_MODE; + audio->buf_cfg.meta_info_enable = 0x00; + } else { + pr_err("Not supported mode\n"); + rc = -EACCES; + goto fail; + } + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->cmd_wait); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->event_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->in_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + + audio->drv_ops.out_flush(audio); + audio->opened = 1; + file->private_data = audio; + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_aac_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *)audio, + &audaac_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + for (i = 0; i < AUDAAC_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audaac_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + pr_err("event pkt alloc failed\n"); + break; + } + } + pr_info("%s:aacdec success mode[%d]session[%d]\n", __func__, + audio->feedback, + audio->ac->session); + return 0; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio); + return rc; +} + +static const struct file_operations audio_aac_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audaac_fsync, +}; + +struct miscdevice audaac_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_aac", + .fops = &audio_aac_fops, +}; + +static int __init audio_aac_init(void) +{ + return misc_register(&audaac_misc); +} + +device_initcall(audio_aac_init); diff --git a/arch/arm/mach-msm/qdsp6v2/audio_acdb.c b/arch/arm/mach-msm/qdsp6v2/audio_acdb.c new file mode 100644 index 00000000000..7ce070ca84e --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_acdb.c @@ -0,0 +1,905 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + + +#define MAX_NETWORKS 9 +#define NUM_ACTIVE_NETWORKS 6 +#define VOCPROC_STREAM_OFFSET NUM_ACTIVE_NETWORKS +#define VOCPROC_VOL_OFFSET (NUM_ACTIVE_NETWORKS * 2) +#define NUM_VOCPROC_CAL_TYPES (NUM_ACTIVE_NETWORKS * 3) +#define NUM_AUDPROC_CAL_TYPES 3 +#define ACDB_BLOCK_SIZE 4096 +#define NUM_VOCPROC_BLOCKS 18 + +enum { + RX_CAL, + TX_CAL, + MAX_AUDPROC_TYPES +}; + +struct acdb_data { + struct mutex acdb_mutex; + + /* ANC Cal */ + struct acdb_cal_block anc_cal; + + /* AudProc Cal */ + uint32_t adm_topology; + uint32_t asm_topology; + struct acdb_cal_block audproc_cal[MAX_AUDPROC_TYPES]; + struct acdb_cal_block audstrm_cal[MAX_AUDPROC_TYPES]; + struct acdb_cal_block audvol_cal[MAX_AUDPROC_TYPES]; + + /* VocProc Cal */ + uint32_t voice_rx_topology; + uint32_t voice_tx_topology; + struct acdb_cal_block vocproc_cal[MAX_NETWORKS]; + struct acdb_cal_block vocstrm_cal[MAX_NETWORKS]; + struct acdb_cal_block vocvol_cal[MAX_NETWORKS]; + /* size of cal block tables above*/ + uint32_t vocproc_cal_size; + uint32_t vocstrm_cal_size; + uint32_t vocvol_cal_size; + /* Total size of cal data for all networks */ + uint32_t vocproc_total_cal_size; + uint32_t vocstrm_total_cal_size; + uint32_t vocvol_total_cal_size; + + /* Sidetone Cal */ + struct sidetone_cal sidetone_cal; + + /* PMEM information */ + int pmem_fd; + unsigned long paddr; + unsigned long kvaddr; + unsigned long pmem_len; + struct file *file; + +}; + +static struct acdb_data acdb_data; +static atomic_t usage_count; + +uint32_t get_voice_rx_topology(void) +{ + return acdb_data.voice_rx_topology; +} + +void store_voice_rx_topology(uint32_t topology) +{ + acdb_data.voice_rx_topology = topology; +} + +uint32_t get_voice_tx_topology(void) +{ + return acdb_data.voice_tx_topology; +} + +void store_voice_tx_topology(uint32_t topology) +{ + acdb_data.voice_tx_topology = topology; +} + +uint32_t get_adm_topology(void) +{ + return acdb_data.adm_topology; +} + +void store_adm_topology(uint32_t topology) +{ + acdb_data.adm_topology = topology; +} + +uint32_t get_asm_topology(void) +{ + return acdb_data.asm_topology; +} + +void store_asm_topology(uint32_t topology) +{ + acdb_data.asm_topology = topology; +} + +void get_all_voice_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = acdb_data.vocproc_cal[0].cal_kvaddr; + cal_block->cal_paddr = acdb_data.vocproc_cal[0].cal_paddr; + cal_block->cal_size = acdb_data.vocproc_total_cal_size + + acdb_data.vocstrm_total_cal_size + + acdb_data.vocvol_total_cal_size; +} + +void get_all_cvp_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = acdb_data.vocproc_cal[0].cal_kvaddr; + cal_block->cal_paddr = acdb_data.vocproc_cal[0].cal_paddr; + cal_block->cal_size = acdb_data.vocproc_total_cal_size + + acdb_data.vocvol_total_cal_size; +} + +void get_all_vocproc_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = acdb_data.vocproc_cal[0].cal_kvaddr; + cal_block->cal_paddr = acdb_data.vocproc_cal[0].cal_paddr; + cal_block->cal_size = acdb_data.vocproc_total_cal_size; +} + +void get_all_vocstrm_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = acdb_data.vocstrm_cal[0].cal_kvaddr; + cal_block->cal_paddr = acdb_data.vocstrm_cal[0].cal_paddr; + cal_block->cal_size = acdb_data.vocstrm_total_cal_size; +} + +void get_all_vocvol_cal(struct acdb_cal_block *cal_block) +{ + cal_block->cal_kvaddr = acdb_data.vocvol_cal[0].cal_kvaddr; + cal_block->cal_paddr = acdb_data.vocvol_cal[0].cal_paddr; + cal_block->cal_size = acdb_data.vocvol_total_cal_size; +} + +void get_anc_cal(struct acdb_cal_block *cal_block) +{ + pr_debug("%s\n", __func__); + + if (cal_block == NULL) { + pr_err("ACDB=> NULL pointer sent to %s\n", __func__); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + cal_block->cal_kvaddr = acdb_data.anc_cal.cal_kvaddr; + cal_block->cal_paddr = acdb_data.anc_cal.cal_paddr; + cal_block->cal_size = acdb_data.anc_cal.cal_size; + + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void store_anc_cal(struct cal_block *cal_block) +{ + pr_debug("%s,\n", __func__); + + if (cal_block->cal_offset > acdb_data.pmem_len) { + pr_err("%s: offset %d is > pmem_len %ld\n", + __func__, cal_block->cal_offset, + acdb_data.pmem_len); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + acdb_data.anc_cal.cal_kvaddr = + cal_block->cal_offset + acdb_data.kvaddr; + acdb_data.anc_cal.cal_paddr = + cal_block->cal_offset + acdb_data.paddr; + acdb_data.anc_cal.cal_size = + cal_block->cal_size; + + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void get_audproc_buffer_data(struct audproc_buffer_data *cal_buffers) +{ + int i; + pr_debug("%s\n", __func__); + + if (cal_buffers == NULL) { + pr_err("ACDB=> NULL pointer sent to %s\n", __func__); + goto done; + } + + for (i = 0; i < NUM_AUDPROC_BUFFERS; i++) { + cal_buffers->phys_addr[i] = (uint32_t) + (acdb_data.paddr + + (NUM_VOCPROC_BLOCKS + i) * ACDB_BLOCK_SIZE); + cal_buffers->buf_size[i] = ACDB_BLOCK_SIZE; + } +done: + return; +} + +void store_audproc_cal(int32_t path, struct cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + mutex_lock(&acdb_data.acdb_mutex); + + if (cal_block->cal_offset > acdb_data.pmem_len) { + pr_err("%s: offset %d is > pmem_len %ld\n", + __func__, cal_block->cal_offset, + acdb_data.pmem_len); + goto done; + } + if (path >= MAX_AUDPROC_TYPES) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + acdb_data.audproc_cal[path].cal_kvaddr = + cal_block->cal_offset + acdb_data.kvaddr; + acdb_data.audproc_cal[path].cal_paddr = + cal_block->cal_offset + acdb_data.paddr; + acdb_data.audproc_cal[path].cal_size = + cal_block->cal_size; + +done: + mutex_unlock(&acdb_data.acdb_mutex); + return; +} + +void get_audproc_cal(int32_t path, struct acdb_cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + if (cal_block == NULL) { + pr_err("ACDB=> NULL pointer sent to %s\n", __func__); + goto done; + } + if (path >= MAX_AUDPROC_TYPES) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + cal_block->cal_kvaddr = acdb_data.audproc_cal[path].cal_kvaddr; + cal_block->cal_paddr = acdb_data.audproc_cal[path].cal_paddr; + cal_block->cal_size = acdb_data.audproc_cal[path].cal_size; + + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void store_audstrm_cal(int32_t path, struct cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + mutex_lock(&acdb_data.acdb_mutex); + + if (cal_block->cal_offset > acdb_data.pmem_len) { + pr_err("%s: offset %d is > pmem_len %ld\n", + __func__, cal_block->cal_offset, + acdb_data.pmem_len); + goto done; + } + if (path >= MAX_AUDPROC_TYPES) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + acdb_data.audstrm_cal[path].cal_kvaddr = + cal_block->cal_offset + acdb_data.kvaddr; + acdb_data.audstrm_cal[path].cal_paddr = + cal_block->cal_offset + acdb_data.paddr; + acdb_data.audstrm_cal[path].cal_size = + cal_block->cal_size; + +done: + mutex_unlock(&acdb_data.acdb_mutex); + return; +} + +void get_audstrm_cal(int32_t path, struct acdb_cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + if (cal_block == NULL) { + pr_err("ACDB=> NULL pointer sent to %s\n", __func__); + goto done; + } + if (path >= MAX_AUDPROC_TYPES) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + cal_block->cal_kvaddr = acdb_data.audstrm_cal[path].cal_kvaddr; + cal_block->cal_paddr = acdb_data.audstrm_cal[path].cal_paddr; + cal_block->cal_size = acdb_data.audstrm_cal[path].cal_size; + + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void store_audvol_cal(int32_t path, struct cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + mutex_lock(&acdb_data.acdb_mutex); + + if (cal_block->cal_offset > acdb_data.pmem_len) { + pr_err("%s: offset %d is > pmem_len %ld\n", + __func__, cal_block->cal_offset, + acdb_data.pmem_len); + goto done; + } + if (path >= MAX_AUDPROC_TYPES) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + acdb_data.audvol_cal[path].cal_kvaddr = + cal_block->cal_offset + acdb_data.kvaddr; + acdb_data.audvol_cal[path].cal_paddr = + cal_block->cal_offset + acdb_data.paddr; + acdb_data.audvol_cal[path].cal_size = + cal_block->cal_size; + +done: + mutex_unlock(&acdb_data.acdb_mutex); + return; +} + +void get_audvol_cal(int32_t path, struct acdb_cal_block *cal_block) +{ + pr_debug("%s, path = %d\n", __func__, path); + + if (cal_block == NULL) { + pr_err("ACDB=> NULL pointer sent to %s\n", __func__); + goto done; + } + if (path > MAX_AUDPROC_TYPES || path < 0) { + pr_err("ACDB=> Bad path sent to %s, path: %d\n", + __func__, path); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + cal_block->cal_kvaddr = acdb_data.audvol_cal[path].cal_kvaddr; + cal_block->cal_paddr = acdb_data.audvol_cal[path].cal_paddr; + cal_block->cal_size = acdb_data.audvol_cal[path].cal_size; + + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + + +void store_vocproc_cal(int32_t len, struct cal_block *cal_blocks) +{ + int i; + pr_debug("%s\n", __func__); + + if (len > MAX_NETWORKS) { + pr_err("%s: Calibration sent for %d networks, only %d are " + "supported!\n", __func__, len, MAX_NETWORKS); + goto done; + } + + + mutex_lock(&acdb_data.acdb_mutex); + + acdb_data.vocproc_total_cal_size = 0; + for (i = 0; i < len; i++) { + if (cal_blocks[i].cal_offset > acdb_data.pmem_len) { + pr_err("%s: offset %d is > pmem_len %ld\n", + __func__, cal_blocks[i].cal_offset, + acdb_data.pmem_len); + acdb_data.vocproc_cal[i].cal_size = 0; + } else { + acdb_data.vocproc_total_cal_size += + cal_blocks[i].cal_size; + acdb_data.vocproc_cal[i].cal_size = + cal_blocks[i].cal_size; + acdb_data.vocproc_cal[i].cal_paddr = + cal_blocks[i].cal_offset + + acdb_data.paddr; + acdb_data.vocproc_cal[i].cal_kvaddr = + cal_blocks[i].cal_offset + + acdb_data.kvaddr; + } + } + acdb_data.vocproc_cal_size = len; + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void get_vocproc_cal(struct acdb_cal_data *cal_data) +{ + pr_debug("%s\n", __func__); + + if (cal_data == NULL) { + pr_err("ACDB=> NULL pointer sent to %s\n", __func__); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + cal_data->num_cal_blocks = acdb_data.vocproc_cal_size; + cal_data->cal_blocks = &acdb_data.vocproc_cal[0]; + + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void store_vocstrm_cal(int32_t len, struct cal_block *cal_blocks) +{ + int i; + pr_debug("%s\n", __func__); + + if (len > MAX_NETWORKS) { + pr_err("%s: Calibration sent for %d networks, only %d are " + "supported!\n", __func__, len, MAX_NETWORKS); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + acdb_data.vocstrm_total_cal_size = 0; + for (i = 0; i < len; i++) { + if (cal_blocks[i].cal_offset > acdb_data.pmem_len) { + pr_err("%s: offset %d is > pmem_len %ld\n", + __func__, cal_blocks[i].cal_offset, + acdb_data.pmem_len); + acdb_data.vocstrm_cal[i].cal_size = 0; + } else { + acdb_data.vocstrm_total_cal_size += + cal_blocks[i].cal_size; + acdb_data.vocstrm_cal[i].cal_size = + cal_blocks[i].cal_size; + acdb_data.vocstrm_cal[i].cal_paddr = + cal_blocks[i].cal_offset + + acdb_data.paddr; + acdb_data.vocstrm_cal[i].cal_kvaddr = + cal_blocks[i].cal_offset + + acdb_data.kvaddr; + } + } + acdb_data.vocstrm_cal_size = len; + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void get_vocstrm_cal(struct acdb_cal_data *cal_data) +{ + pr_debug("%s\n", __func__); + + if (cal_data == NULL) { + pr_err("ACDB=> NULL pointer sent to %s\n", __func__); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + cal_data->num_cal_blocks = acdb_data.vocstrm_cal_size; + cal_data->cal_blocks = &acdb_data.vocstrm_cal[0]; + + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void store_vocvol_cal(int32_t len, struct cal_block *cal_blocks) +{ + int i; + pr_debug("%s\n", __func__); + + if (len > MAX_NETWORKS) { + pr_err("%s: Calibration sent for %d networks, only %d are " + "supported!\n", __func__, len, MAX_NETWORKS); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + acdb_data.vocvol_total_cal_size = 0; + for (i = 0; i < len; i++) { + if (cal_blocks[i].cal_offset > acdb_data.pmem_len) { + pr_err("%s: offset %d is > pmem_len %ld\n", + __func__, cal_blocks[i].cal_offset, + acdb_data.pmem_len); + acdb_data.vocvol_cal[i].cal_size = 0; + } else { + acdb_data.vocvol_total_cal_size += + cal_blocks[i].cal_size; + acdb_data.vocvol_cal[i].cal_size = + cal_blocks[i].cal_size; + acdb_data.vocvol_cal[i].cal_paddr = + cal_blocks[i].cal_offset + + acdb_data.paddr; + acdb_data.vocvol_cal[i].cal_kvaddr = + cal_blocks[i].cal_offset + + acdb_data.kvaddr; + } + } + acdb_data.vocvol_cal_size = len; + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void get_vocvol_cal(struct acdb_cal_data *cal_data) +{ + pr_debug("%s\n", __func__); + + if (cal_data == NULL) { + pr_err("ACDB=> NULL pointer sent to %s\n", __func__); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + cal_data->num_cal_blocks = acdb_data.vocvol_cal_size; + cal_data->cal_blocks = &acdb_data.vocvol_cal[0]; + + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +void store_sidetone_cal(struct sidetone_cal *cal_data) +{ + pr_debug("%s\n", __func__); + + mutex_lock(&acdb_data.acdb_mutex); + + acdb_data.sidetone_cal.enable = cal_data->enable; + acdb_data.sidetone_cal.gain = cal_data->gain; + + mutex_unlock(&acdb_data.acdb_mutex); +} + + +void get_sidetone_cal(struct sidetone_cal *cal_data) +{ + pr_debug("%s\n", __func__); + + if (cal_data == NULL) { + pr_err("ACDB=> NULL pointer sent to %s\n", __func__); + goto done; + } + + mutex_lock(&acdb_data.acdb_mutex); + + cal_data->enable = acdb_data.sidetone_cal.enable; + cal_data->gain = acdb_data.sidetone_cal.gain; + + mutex_unlock(&acdb_data.acdb_mutex); +done: + return; +} + +static int acdb_open(struct inode *inode, struct file *f) +{ + s32 result = 0; + pr_info("%s\n", __func__); + + mutex_lock(&acdb_data.acdb_mutex); + if (acdb_data.pmem_fd) { + pr_info("%s: ACDB opened but PMEM allocated, using existing PMEM!\n", + __func__); + } + mutex_unlock(&acdb_data.acdb_mutex); + + atomic_inc(&usage_count); + return result; +} + +static int deregister_pmem(void) +{ + int result; + struct audproc_buffer_data buffer; + + get_audproc_buffer_data(&buffer); + + result = adm_memory_unmap_regions(buffer.phys_addr, + buffer.buf_size, NUM_AUDPROC_BUFFERS); + + if (result < 0) + pr_err("Audcal unmap did not work!\n"); + + if (acdb_data.pmem_fd) { + put_pmem_file(acdb_data.file); + acdb_data.pmem_fd = 0; + } + return result; +} + +static int register_pmem(void) +{ + int result; + struct audproc_buffer_data buffer; + + result = get_pmem_file(acdb_data.pmem_fd, &acdb_data.paddr, + &acdb_data.kvaddr, &acdb_data.pmem_len, + &acdb_data.file); + if (result != 0) { + acdb_data.pmem_fd = 0; + pr_err("%s: Could not register PMEM!!!\n", __func__); + goto done; + } + + pr_debug("AUDIO_REGISTER_PMEM done! paddr = 0x%lx, " + "kvaddr = 0x%lx, len = x%lx\n", acdb_data.paddr, + acdb_data.kvaddr, acdb_data.pmem_len); + get_audproc_buffer_data(&buffer); + result = adm_memory_map_regions(buffer.phys_addr, 0, + buffer.buf_size, + NUM_AUDPROC_BUFFERS); + if (result < 0) + pr_err("Audcal mmap did not work!\n"); + goto done; + +done: + return result; +} +static long acdb_ioctl(struct file *f, + unsigned int cmd, unsigned long arg) +{ + s32 result = 0; + s32 audproc_path; + s32 size; + u32 topology; + struct cal_block data[MAX_NETWORKS]; + pr_debug("%s\n", __func__); + + switch (cmd) { + case AUDIO_REGISTER_PMEM: + pr_debug("AUDIO_REGISTER_PMEM\n"); + mutex_lock(&acdb_data.acdb_mutex); + if (acdb_data.pmem_fd) { + deregister_pmem(); + pr_info("Remove the existing PMEM\n"); + } + + if (copy_from_user(&acdb_data.pmem_fd, (void *)arg, + sizeof(acdb_data.pmem_fd))) { + pr_err("%s: fail to copy pmem handle!\n", __func__); + result = -EFAULT; + } else { + result = register_pmem(); + } + mutex_unlock(&acdb_data.acdb_mutex); + goto done; + + case AUDIO_DEREGISTER_PMEM: + pr_debug("AUDIO_DEREGISTER_PMEM\n"); + mutex_lock(&acdb_data.acdb_mutex); + deregister_pmem(); + mutex_unlock(&acdb_data.acdb_mutex); + goto done; + case AUDIO_SET_VOICE_RX_TOPOLOGY: + if (copy_from_user(&topology, (void *)arg, + sizeof(topology))) { + pr_err("%s: fail to copy topology!\n", __func__); + result = -EFAULT; + } + store_voice_rx_topology(topology); + goto done; + case AUDIO_SET_VOICE_TX_TOPOLOGY: + if (copy_from_user(&topology, (void *)arg, + sizeof(topology))) { + pr_err("%s: fail to copy topology!\n", __func__); + result = -EFAULT; + } + store_voice_tx_topology(topology); + goto done; + case AUDIO_SET_ADM_TOPOLOGY: + if (copy_from_user(&topology, (void *)arg, + sizeof(topology))) { + pr_err("%s: fail to copy topology!\n", __func__); + result = -EFAULT; + } + store_adm_topology(topology); + goto done; + case AUDIO_SET_ASM_TOPOLOGY: + if (copy_from_user(&topology, (void *)arg, + sizeof(topology))) { + pr_err("%s: fail to copy topology!\n", __func__); + result = -EFAULT; + } + store_asm_topology(topology); + goto done; + } + + if (copy_from_user(&size, (void *) arg, sizeof(size))) { + + result = -EFAULT; + goto done; + } + + if (size <= 0) { + pr_err("%s: Invalid size sent to driver: %d\n", + __func__, size); + result = -EFAULT; + goto done; + } + + if (copy_from_user(data, (void *)(arg + sizeof(size)), size)) { + + pr_err("%s: fail to copy table size %d\n", __func__, size); + result = -EFAULT; + goto done; + } + + if (data == NULL) { + pr_err("%s: NULL pointer sent to driver!\n", __func__); + result = -EFAULT; + goto done; + } + + switch (cmd) { + case AUDIO_SET_AUDPROC_TX_CAL: + audproc_path = TX_CAL; + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audproc_cal(audproc_path, data); + break; + case AUDIO_SET_AUDPROC_RX_CAL: + audproc_path = RX_CAL; + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audproc_cal(audproc_path, data); + break; + case AUDIO_SET_AUDPROC_TX_STREAM_CAL: + audproc_path = TX_CAL; + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audstrm_cal(audproc_path, data); + break; + case AUDIO_SET_AUDPROC_RX_STREAM_CAL: + audproc_path = RX_CAL; + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audstrm_cal(audproc_path, data); + break; + case AUDIO_SET_AUDPROC_TX_VOL_CAL: + audproc_path = TX_CAL; + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audvol_cal(audproc_path, data); + case AUDIO_SET_AUDPROC_RX_VOL_CAL: + audproc_path = RX_CAL; + if (size > sizeof(struct cal_block)) + pr_err("%s: More Audproc Cal then expected, " + "size received: %d\n", __func__, size); + store_audvol_cal(audproc_path, data); + break; + case AUDIO_SET_VOCPROC_CAL: + store_vocproc_cal(size / sizeof(struct cal_block), data); + break; + case AUDIO_SET_VOCPROC_STREAM_CAL: + store_vocstrm_cal(size / sizeof(struct cal_block), data); + break; + case AUDIO_SET_VOCPROC_VOL_CAL: + store_vocvol_cal(size / sizeof(struct cal_block), data); + break; + case AUDIO_SET_SIDETONE_CAL: + if (size > sizeof(struct sidetone_cal)) + pr_err("%s: More sidetone cal then expected, " + "size received: %d\n", __func__, size); + store_sidetone_cal((struct sidetone_cal *)data); + break; + case AUDIO_SET_ANC_CAL: + store_anc_cal(data); + break; + default: + pr_err("ACDB=> ACDB ioctl not found!\n"); + } + +done: + return result; +} + +static int acdb_mmap(struct file *file, struct vm_area_struct *vma) +{ + int result = 0; + int size = vma->vm_end - vma->vm_start; + + pr_debug("%s\n", __func__); + + mutex_lock(&acdb_data.acdb_mutex); + if (acdb_data.pmem_fd) { + if (size <= acdb_data.pmem_len) { + vma->vm_page_prot = pgprot_noncached( + vma->vm_page_prot); + result = remap_pfn_range(vma, + vma->vm_start, + acdb_data.paddr >> PAGE_SHIFT, + size, + vma->vm_page_prot); + } else { + pr_err("%s: Not enough PMEM memory!\n", __func__); + result = -ENOMEM; + } + } else { + pr_err("%s: PMEM is not allocated, yet!\n", __func__); + result = -ENODEV; + } + mutex_unlock(&acdb_data.acdb_mutex); + + return result; +} + +static int acdb_release(struct inode *inode, struct file *f) +{ + s32 result = 0; + + atomic_dec(&usage_count); + atomic_read(&usage_count); + + pr_info("%s: ref count %d!\n", __func__, + atomic_read(&usage_count)); + + if (atomic_read(&usage_count) >= 1) { + result = -EBUSY; + } else { + mutex_lock(&acdb_data.acdb_mutex); + result = deregister_pmem(); + mutex_unlock(&acdb_data.acdb_mutex); + } + + return result; +} + +static const struct file_operations acdb_fops = { + .owner = THIS_MODULE, + .open = acdb_open, + .release = acdb_release, + .unlocked_ioctl = acdb_ioctl, + .mmap = acdb_mmap, +}; + +struct miscdevice acdb_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_acdb", + .fops = &acdb_fops, +}; + +static int __init acdb_init(void) +{ + memset(&acdb_data, 0, sizeof(acdb_data)); + mutex_init(&acdb_data.acdb_mutex); + atomic_set(&usage_count, 0); + return misc_register(&acdb_misc); +} + +static void __exit acdb_exit(void) +{ +} + +module_init(acdb_init); +module_exit(acdb_exit); + +MODULE_DESCRIPTION("MSM 8x60 Audio ACDB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/audio_dev_ctl.c b/arch/arm/mach-msm/qdsp6v2/audio_dev_ctl.c new file mode 100644 index 00000000000..08e5266a31a --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_dev_ctl.c @@ -0,0 +1,1701 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + + +static DEFINE_MUTEX(session_lock); + +struct audio_dev_ctrl_state { + struct msm_snddev_info *devs[AUDIO_DEV_CTL_MAX_DEV]; + u32 num_dev; + atomic_t opened; + struct msm_snddev_info *voice_rx_dev; + struct msm_snddev_info *voice_tx_dev; + wait_queue_head_t wait; +}; + +static struct audio_dev_ctrl_state audio_dev_ctrl; +struct event_listner event; + +#define PLAYBACK 0x1 +#define LIVE_RECORDING 0x2 +#define NON_LIVE_RECORDING 0x3 +#define MAX_COPP_DEVICES 4 + +struct session_freq { + int freq; + int evt; +}; + +struct audio_routing_info { + unsigned short mixer_mask[MAX_SESSIONS]; + unsigned short audrec_mixer_mask[MAX_SESSIONS]; + struct session_freq dec_freq[MAX_SESSIONS]; + struct session_freq enc_freq[MAX_SESSIONS]; + unsigned int copp_list[MAX_SESSIONS][AFE_MAX_PORTS]; + int voice_tx_dev_id; + int voice_rx_dev_id; + int voice_tx_sample_rate; + int voice_rx_sample_rate; + signed int voice_tx_vol; + signed int voice_rx_vol; + int tx_mute; + int rx_mute; + int voice_state; + struct mutex copp_list_mutex; + struct mutex adm_mutex; +}; + +static struct audio_routing_info routing_info; + +struct audio_copp_topology { + struct mutex lock; + int session_cnt; + int session_id[MAX_SESSIONS]; + int topolog_id[MAX_SESSIONS]; +}; +static struct audio_copp_topology adm_tx_topology_tbl; + +int msm_reset_all_device(void) +{ + int rc = 0; + int dev_id = 0; + struct msm_snddev_info *dev_info = NULL; + + for (dev_id = 0; dev_id < audio_dev_ctrl.num_dev; dev_id++) { + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dev_info); + return rc; + } + if (!dev_info->opened) + continue; + pr_debug("%s:Resetting device %d active on COPP %d" + "with %lld as routing\n", __func__, + dev_id, dev_info->copp_id, dev_info->sessions); + broadcast_event(AUDDEV_EVT_REL_PENDING, + dev_id, + SESSION_IGNORE); + rc = dev_info->dev_ops.close(dev_info); + if (rc < 0) { + pr_err("%s:Snd device failed close!\n", __func__); + return rc; + } else { + dev_info->opened = 0; + broadcast_event(AUDDEV_EVT_DEV_RLS, + dev_id, + SESSION_IGNORE); + + if (dev_info->copp_id == VOICE_PLAYBACK_TX) + voice_start_playback(0); + } + dev_info->sessions = 0; + } + msm_clear_all_session(); + return 0; +} +EXPORT_SYMBOL(msm_reset_all_device); + +int msm_set_copp_id(int session_id, int copp_id) +{ + int rc = 0; + int index; + + if (session_id < 1 || session_id > 8) + return -EINVAL; + if (afe_validate_port(copp_id) < 0) + return -EINVAL; + + index = afe_get_port_index(copp_id); + if (index < 0 || index > AFE_MAX_PORTS) + return -EINVAL; + pr_debug("%s: session[%d] copp_id[%d] index[%d]\n", __func__, + session_id, copp_id, index); + mutex_lock(&routing_info.copp_list_mutex); + if (routing_info.copp_list[session_id][index] == DEVICE_IGNORE) + routing_info.copp_list[session_id][index] = copp_id; + mutex_unlock(&routing_info.copp_list_mutex); + + return rc; +} +EXPORT_SYMBOL(msm_set_copp_id); + +int msm_clear_copp_id(int session_id, int copp_id) +{ + int rc = 0; + int index = afe_get_port_index(copp_id); + + if (session_id < 1 || session_id > 8) + return -EINVAL; + pr_debug("%s: session[%d] copp_id[%d] index[%d]\n", __func__, + session_id, copp_id, index); + mutex_lock(&routing_info.copp_list_mutex); + if (routing_info.copp_list[session_id][index] == copp_id) + routing_info.copp_list[session_id][index] = DEVICE_IGNORE; + mutex_unlock(&routing_info.copp_list_mutex); + + return rc; +} +EXPORT_SYMBOL(msm_clear_copp_id); + +int msm_clear_session_id(int session_id) +{ + int rc = 0; + int i = 0; + if (session_id < 1 || session_id > 8) + return -EINVAL; + pr_debug("%s: session[%d]\n", __func__, session_id); + mutex_lock(&routing_info.adm_mutex); + mutex_lock(&routing_info.copp_list_mutex); + for (i = 0; i < AFE_MAX_PORTS; i++) { + if (routing_info.copp_list[session_id][i] != DEVICE_IGNORE) { + rc = adm_close(routing_info.copp_list[session_id][i]); + if (rc < 0) { + pr_err("%s: adm close fail port[%d] rc[%d]\n", + __func__, + routing_info.copp_list[session_id][i], + rc); + continue; + } + routing_info.copp_list[session_id][i] = DEVICE_IGNORE; + rc = 0; + } + } + mutex_unlock(&routing_info.copp_list_mutex); + mutex_unlock(&routing_info.adm_mutex); + + return rc; +} +EXPORT_SYMBOL(msm_clear_session_id); + +int msm_clear_all_session() +{ + int rc = 0; + int i = 0, j = 0; + pr_info("%s:\n", __func__); + mutex_lock(&routing_info.adm_mutex); + mutex_lock(&routing_info.copp_list_mutex); + for (j = 1; j < MAX_SESSIONS; j++) { + for (i = 0; i < AFE_MAX_PORTS; i++) { + if (routing_info.copp_list[j][i] != DEVICE_IGNORE) { + rc = adm_close( + routing_info.copp_list[j][i]); + if (rc < 0) { + pr_err("%s: adm close fail copp[%d]" + "session[%d] rc[%d]\n", + __func__, + routing_info.copp_list[j][i], + j, rc); + continue; + } + routing_info.copp_list[j][i] = DEVICE_IGNORE; + rc = 0; + } + } + } + mutex_unlock(&routing_info.copp_list_mutex); + mutex_unlock(&routing_info.adm_mutex); + return rc; +} +EXPORT_SYMBOL(msm_clear_all_session); + +int msm_get_voice_state(void) +{ + pr_debug("voice state %d\n", routing_info.voice_state); + return routing_info.voice_state; +} +EXPORT_SYMBOL(msm_get_voice_state); + +int msm_set_voice_mute(int dir, int mute) +{ + pr_debug("dir %x mute %x\n", dir, mute); + if (dir == DIR_TX) { + routing_info.tx_mute = mute; + broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, + routing_info.voice_tx_dev_id, SESSION_IGNORE); + } else + return -EPERM; + return 0; +} +EXPORT_SYMBOL(msm_set_voice_mute); + +int msm_set_voice_vol(int dir, s32 volume) +{ + if (dir == DIR_TX) { + routing_info.voice_tx_vol = volume; + broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, + routing_info.voice_tx_dev_id, + SESSION_IGNORE); + } else if (dir == DIR_RX) { + routing_info.voice_rx_vol = volume; + broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, + routing_info.voice_rx_dev_id, + SESSION_IGNORE); + } else + return -EINVAL; + return 0; +} +EXPORT_SYMBOL(msm_set_voice_vol); + +void msm_snddev_register(struct msm_snddev_info *dev_info) +{ + mutex_lock(&session_lock); + if (audio_dev_ctrl.num_dev < AUDIO_DEV_CTL_MAX_DEV) { + audio_dev_ctrl.devs[audio_dev_ctrl.num_dev] = dev_info; + /* roughly 0 DB for digital gain + * If default gain is not desirable, it is expected that + * application sets desired gain before activating sound + * device + */ + dev_info->dev_volume = 75; + dev_info->sessions = 0x0; + dev_info->usage_count = 0; + audio_dev_ctrl.num_dev++; + } else + pr_err("%s: device registry max out\n", __func__); + mutex_unlock(&session_lock); +} +EXPORT_SYMBOL(msm_snddev_register); + +int msm_snddev_devcount(void) +{ + return audio_dev_ctrl.num_dev; +} +EXPORT_SYMBOL(msm_snddev_devcount); + +int msm_snddev_query(int dev_id) +{ + if (dev_id <= audio_dev_ctrl.num_dev) + return 0; + return -ENODEV; +} +EXPORT_SYMBOL(msm_snddev_query); + +int msm_snddev_is_set(int popp_id, int copp_id) +{ + return routing_info.mixer_mask[popp_id] & (0x1 << copp_id); +} +EXPORT_SYMBOL(msm_snddev_is_set); + +unsigned short msm_snddev_route_enc(int enc_id) +{ + if (enc_id >= MAX_SESSIONS) + return -EINVAL; + return routing_info.audrec_mixer_mask[enc_id]; +} +EXPORT_SYMBOL(msm_snddev_route_enc); + +unsigned short msm_snddev_route_dec(int popp_id) +{ + if (popp_id >= MAX_SESSIONS) + return -EINVAL; + return routing_info.mixer_mask[popp_id]; +} +EXPORT_SYMBOL(msm_snddev_route_dec); + +/*To check one->many case*/ +int msm_check_multicopp_per_stream(int session_id, + struct route_payload *payload) +{ + int i = 0; + int flag = 0; + pr_debug("%s: session_id=%d\n", __func__, session_id); + mutex_lock(&routing_info.copp_list_mutex); + for (i = 0; i < AFE_MAX_PORTS; i++) { + if (routing_info.copp_list[session_id][i] == DEVICE_IGNORE) + continue; + else { + pr_debug("Device enabled\n"); + payload->copp_ids[flag++] = + routing_info.copp_list[session_id][i]; + } + } + mutex_unlock(&routing_info.copp_list_mutex); + if (flag > 1) { + pr_debug("Multiple copp per stream case num_copps=%d\n", flag); + } else { + pr_debug("Stream routed to single copp\n"); + } + payload->num_copps = flag; + return flag; +} + +int msm_snddev_set_dec(int popp_id, int copp_id, int set, + int rate, int mode) +{ + int rc = 0, i = 0; + struct route_payload payload; + + if ((popp_id >= MAX_SESSIONS) || (popp_id <= 0)) { + pr_err("%s: Invalid session id %d\n", __func__, popp_id); + return 0; + } + + mutex_lock(&routing_info.adm_mutex); + if (set) { + rc = adm_open(copp_id, PLAYBACK, rate, mode, + DEFAULT_COPP_TOPOLOGY); + if (rc < 0) { + pr_err("%s: adm open fail rc[%d]\n", __func__, rc); + rc = -EINVAL; + mutex_unlock(&routing_info.adm_mutex); + return rc; + } + msm_set_copp_id(popp_id, copp_id); + pr_debug("%s:Session id=%d copp_id=%d\n", + __func__, popp_id, copp_id); + memset(payload.copp_ids, DEVICE_IGNORE, + (sizeof(unsigned int) * AFE_MAX_PORTS)); + rc = msm_check_multicopp_per_stream(popp_id, &payload); + /* Multiple streams per copp is handled, one stream at a time */ + rc = adm_matrix_map(popp_id, PLAYBACK, rc, + payload.copp_ids, copp_id); + if (rc < 0) { + pr_err("%s: matrix map failed rc[%d]\n", + __func__, rc); + adm_close(copp_id); + rc = -EINVAL; + mutex_unlock(&routing_info.adm_mutex); + return rc; + } + } else { + for (i = 0; i < AFE_MAX_PORTS; i++) { + if (routing_info.copp_list[popp_id][i] == copp_id) { + rc = adm_close(copp_id); + if (rc < 0) { + pr_err("%s: adm close fail copp[%d]" + "rc[%d]\n", + __func__, copp_id, rc); + rc = -EINVAL; + mutex_unlock(&routing_info.adm_mutex); + return rc; + } + msm_clear_copp_id(popp_id, copp_id); + break; + } + } + } + + if (copp_id == VOICE_PLAYBACK_TX) { + /* Signal uplink playback. */ + rc = voice_start_playback(set); + } + mutex_unlock(&routing_info.adm_mutex); + return rc; +} +EXPORT_SYMBOL(msm_snddev_set_dec); + + +static int check_tx_copp_topology(int session_id) +{ + int cnt; + int ret_val = -ENOENT; + + cnt = adm_tx_topology_tbl.session_cnt; + if (cnt) { + do { + if (adm_tx_topology_tbl.session_id[cnt-1] + == session_id) + ret_val = cnt-1; + } while (--cnt); + } + + return ret_val; +} + +static int add_to_tx_topology_lists(int session_id, int topology) +{ + int idx = 0, tbl_idx; + int ret_val = -ENOSPC; + + mutex_lock(&adm_tx_topology_tbl.lock); + + tbl_idx = check_tx_copp_topology(session_id); + if (tbl_idx == -ENOENT) { + while (adm_tx_topology_tbl.session_id[idx++]) + ; + tbl_idx = idx-1; + } + + if (tbl_idx < MAX_SESSIONS) { + adm_tx_topology_tbl.session_id[tbl_idx] = session_id; + adm_tx_topology_tbl.topolog_id[tbl_idx] = topology; + adm_tx_topology_tbl.session_cnt++; + + ret_val = 0; + } + mutex_unlock(&adm_tx_topology_tbl.lock); + return ret_val; +} + +static void remove_from_tx_topology_lists(int session_id) +{ + int tbl_idx; + + mutex_lock(&adm_tx_topology_tbl.lock); + tbl_idx = check_tx_copp_topology(session_id); + if (tbl_idx != -ENOENT) { + + adm_tx_topology_tbl.session_cnt--; + adm_tx_topology_tbl.session_id[tbl_idx] = 0; + adm_tx_topology_tbl.topolog_id[tbl_idx] = 0; + } + mutex_unlock(&adm_tx_topology_tbl.lock); +} + +int auddev_cfg_tx_copp_topology(int session_id, int cfg) +{ + int ret = 0; + + if (cfg == DEFAULT_COPP_TOPOLOGY) + remove_from_tx_topology_lists(session_id); + else { + switch (cfg) { + case VPM_TX_SM_ECNS_COPP_TOPOLOGY: + case VPM_TX_DM_FLUENCE_COPP_TOPOLOGY: + ret = add_to_tx_topology_lists(session_id, cfg); + break; + + default: + ret = -ENODEV; + break; + } + } + return ret; +} + +int msm_snddev_set_enc(int popp_id, int copp_id, int set, + int rate, int mode) +{ + int topology; + int tbl_idx; + int rc = 0, i = 0; + mutex_lock(&routing_info.adm_mutex); + if (set) { + mutex_lock(&adm_tx_topology_tbl.lock); + tbl_idx = check_tx_copp_topology(popp_id); + if (tbl_idx == -ENOENT) + topology = DEFAULT_COPP_TOPOLOGY; + else { + topology = adm_tx_topology_tbl.topolog_id[tbl_idx]; + rate = 16000; + } + mutex_unlock(&adm_tx_topology_tbl.lock); + rc = adm_open(copp_id, LIVE_RECORDING, rate, mode, topology); + if (rc < 0) { + pr_err("%s: adm open fail rc[%d]\n", __func__, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = adm_matrix_map(popp_id, LIVE_RECORDING, 1, + (unsigned int *)&copp_id, copp_id); + if (rc < 0) { + pr_err("%s: matrix map failed rc[%d]\n", __func__, rc); + adm_close(copp_id); + rc = -EINVAL; + goto fail_cmd; + } + msm_set_copp_id(popp_id, copp_id); + } else { + for (i = 0; i < AFE_MAX_PORTS; i++) { + if (routing_info.copp_list[popp_id][i] == copp_id) { + rc = adm_close(copp_id); + if (rc < 0) { + pr_err("%s: adm close fail copp[%d]" + "rc[%d]\n", + __func__, copp_id, rc); + rc = -EINVAL; + goto fail_cmd; + } + msm_clear_copp_id(popp_id, copp_id); + break; + } + } + } +fail_cmd: + mutex_unlock(&routing_info.adm_mutex); + return rc; +} +EXPORT_SYMBOL(msm_snddev_set_enc); + +int msm_device_is_voice(int dev_id) +{ + if ((dev_id == routing_info.voice_rx_dev_id) + || (dev_id == routing_info.voice_tx_dev_id)) + return 0; + else + return -EINVAL; +} +EXPORT_SYMBOL(msm_device_is_voice); + +int msm_set_voc_route(struct msm_snddev_info *dev_info, + int stream_type, int dev_id) +{ + int rc = 0; + u64 session_mask = 0; + + mutex_lock(&session_lock); + switch (stream_type) { + case AUDIO_ROUTE_STREAM_VOICE_RX: + if (audio_dev_ctrl.voice_rx_dev) + audio_dev_ctrl.voice_rx_dev->sessions &= ~0xFFFF; + + if (!(dev_info->capability & SNDDEV_CAP_RX) | + !(dev_info->capability & SNDDEV_CAP_VOICE)) { + rc = -EINVAL; + break; + } + audio_dev_ctrl.voice_rx_dev = dev_info; + if (audio_dev_ctrl.voice_rx_dev) { + session_mask = + ((u64)0x1) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_VOC-1)); + audio_dev_ctrl.voice_rx_dev->sessions |= + session_mask; + } + routing_info.voice_rx_dev_id = dev_id; + break; + case AUDIO_ROUTE_STREAM_VOICE_TX: + if (audio_dev_ctrl.voice_tx_dev) + audio_dev_ctrl.voice_tx_dev->sessions &= ~0xFFFF; + + if (!(dev_info->capability & SNDDEV_CAP_TX) | + !(dev_info->capability & SNDDEV_CAP_VOICE)) { + rc = -EINVAL; + break; + } + + audio_dev_ctrl.voice_tx_dev = dev_info; + if (audio_dev_ctrl.voice_rx_dev) { + session_mask = + ((u64)0x1) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_VOC-1)); + audio_dev_ctrl.voice_tx_dev->sessions |= + session_mask; + } + routing_info.voice_tx_dev_id = dev_id; + break; + default: + rc = -EINVAL; + } + mutex_unlock(&session_lock); + return rc; +} +EXPORT_SYMBOL(msm_set_voc_route); + +void msm_release_voc_thread(void) +{ + wake_up(&audio_dev_ctrl.wait); +} +EXPORT_SYMBOL(msm_release_voc_thread); + +int msm_snddev_get_enc_freq(session_id) +{ + return routing_info.enc_freq[session_id].freq; +} +EXPORT_SYMBOL(msm_snddev_get_enc_freq); + +int msm_get_voc_freq(int *tx_freq, int *rx_freq) +{ + *tx_freq = routing_info.voice_tx_sample_rate; + *rx_freq = routing_info.voice_rx_sample_rate; + return 0; +} +EXPORT_SYMBOL(msm_get_voc_freq); + +int msm_get_voc_route(u32 *rx_id, u32 *tx_id) +{ + int rc = 0; + + if (!rx_id || !tx_id) + return -EINVAL; + + mutex_lock(&session_lock); + if (!audio_dev_ctrl.voice_rx_dev || !audio_dev_ctrl.voice_tx_dev) { + rc = -ENODEV; + mutex_unlock(&session_lock); + return rc; + } + + *rx_id = audio_dev_ctrl.voice_rx_dev->acdb_id; + *tx_id = audio_dev_ctrl.voice_tx_dev->acdb_id; + + mutex_unlock(&session_lock); + + return rc; +} +EXPORT_SYMBOL(msm_get_voc_route); + +struct msm_snddev_info *audio_dev_ctrl_find_dev(u32 dev_id) +{ + struct msm_snddev_info *info; + + if ((audio_dev_ctrl.num_dev - 1) < dev_id) { + info = ERR_PTR(-ENODEV); + goto error; + } + + info = audio_dev_ctrl.devs[dev_id]; +error: + return info; + +} +EXPORT_SYMBOL(audio_dev_ctrl_find_dev); + +int snddev_voice_set_volume(int vol, int path) +{ + if (audio_dev_ctrl.voice_rx_dev + && audio_dev_ctrl.voice_tx_dev) { + if (path) + audio_dev_ctrl.voice_tx_dev->dev_volume = vol; + else + audio_dev_ctrl.voice_rx_dev->dev_volume = vol; + } else + return -ENODEV; + return 0; +} +EXPORT_SYMBOL(snddev_voice_set_volume); + +static int audio_dev_ctrl_get_devices(struct audio_dev_ctrl_state *dev_ctrl, + void __user *arg) +{ + int rc = 0; + u32 index; + struct msm_snd_device_list work_list; + struct msm_snd_device_info *work_tbl; + + if (copy_from_user(&work_list, arg, sizeof(work_list))) { + rc = -EFAULT; + goto error; + } + + if (work_list.num_dev > dev_ctrl->num_dev) { + rc = -EINVAL; + goto error; + } + + work_tbl = kmalloc(work_list.num_dev * + sizeof(struct msm_snd_device_info), GFP_KERNEL); + if (!work_tbl) { + rc = -ENOMEM; + goto error; + } + + for (index = 0; index < dev_ctrl->num_dev; index++) { + work_tbl[index].dev_id = index; + work_tbl[index].dev_cap = dev_ctrl->devs[index]->capability; + strlcpy(work_tbl[index].dev_name, dev_ctrl->devs[index]->name, + 64); + } + + if (copy_to_user((void *) (work_list.list), work_tbl, + work_list.num_dev * sizeof(struct msm_snd_device_info))) + rc = -EFAULT; + kfree(work_tbl); +error: + return rc; +} + + +int auddev_register_evt_listner(u32 evt_id, u32 clnt_type, u32 clnt_id, + void (*listner)(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data), + void *private_data) +{ + int rc; + struct msm_snd_evt_listner *callback = NULL; + struct msm_snd_evt_listner *new_cb; + + new_cb = kzalloc(sizeof(struct msm_snd_evt_listner), GFP_KERNEL); + if (!new_cb) { + pr_err("No memory to add new listener node\n"); + return -ENOMEM; + } + + mutex_lock(&session_lock); + new_cb->cb_next = NULL; + new_cb->auddev_evt_listener = listner; + new_cb->evt_id = evt_id; + new_cb->clnt_type = clnt_type; + new_cb->clnt_id = clnt_id; + new_cb->private_data = private_data; + if (event.cb == NULL) { + event.cb = new_cb; + new_cb->cb_prev = NULL; + } else { + callback = event.cb; + for (; ;) { + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + callback->cb_next = new_cb; + new_cb->cb_prev = callback; + } + event.num_listner++; + mutex_unlock(&session_lock); + rc = 0; + return rc; +} +EXPORT_SYMBOL(auddev_register_evt_listner); + +int auddev_unregister_evt_listner(u32 clnt_type, u32 clnt_id) +{ + struct msm_snd_evt_listner *callback = event.cb; + struct msm_snddev_info *info; + u64 session_mask = 0; + int i = 0; + + mutex_lock(&session_lock); + while (callback != NULL) { + if ((callback->clnt_type == clnt_type) + && (callback->clnt_id == clnt_id)) + break; + callback = callback->cb_next; + } + if (callback == NULL) { + mutex_unlock(&session_lock); + return -EINVAL; + } + + if ((callback->cb_next == NULL) && (callback->cb_prev == NULL)) + event.cb = NULL; + else if (callback->cb_next == NULL) + callback->cb_prev->cb_next = NULL; + else if (callback->cb_prev == NULL) { + callback->cb_next->cb_prev = NULL; + event.cb = callback->cb_next; + } else { + callback->cb_prev->cb_next = callback->cb_next; + callback->cb_next->cb_prev = callback->cb_prev; + } + kfree(callback); + + session_mask = (((u64)0x1) << clnt_id) << (MAX_BIT_PER_CLIENT * \ + ((int)clnt_type-1)); + for (i = 0; i < audio_dev_ctrl.num_dev; i++) { + info = audio_dev_ctrl.devs[i]; + info->sessions &= ~session_mask; + } + mutex_unlock(&session_lock); + return 0; +} +EXPORT_SYMBOL(auddev_unregister_evt_listner); + +int msm_snddev_withdraw_freq(u32 session_id, u32 capability, u32 clnt_type) +{ + int i = 0; + struct msm_snddev_info *info; + u64 session_mask = 0; + + if ((clnt_type == AUDDEV_CLNT_VOC) && (session_id != 0)) + return -EINVAL; + if ((clnt_type == AUDDEV_CLNT_DEC) + && (session_id >= MAX_SESSIONS)) + return -EINVAL; + if ((clnt_type == AUDDEV_CLNT_ENC) + && (session_id >= MAX_SESSIONS)) + return -EINVAL; + + session_mask = (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)clnt_type-1)); + + for (i = 0; i < audio_dev_ctrl.num_dev; i++) { + info = audio_dev_ctrl.devs[i]; + if ((info->sessions & session_mask) + && (info->capability & capability)) { + if (!(info->sessions & ~(session_mask))) + info->set_sample_rate = 0; + } + } + if (clnt_type == AUDDEV_CLNT_DEC) + routing_info.dec_freq[session_id].freq + = 0; + else if (clnt_type == AUDDEV_CLNT_ENC) + routing_info.enc_freq[session_id].freq + = 0; + else if (capability == SNDDEV_CAP_TX) + routing_info.voice_tx_sample_rate = 0; + else + routing_info.voice_rx_sample_rate = 48000; + return 0; +} + +int msm_snddev_request_freq(int *freq, u32 session_id, + u32 capability, u32 clnt_type) +{ + int i = 0; + int rc = 0; + struct msm_snddev_info *info; + u32 set_freq; + u64 session_mask = 0; + u64 clnt_type_mask = 0; + + pr_debug(": clnt_type 0x%08x\n", clnt_type); + + if ((clnt_type == AUDDEV_CLNT_VOC) && (session_id != 0)) + return -EINVAL; + if ((clnt_type == AUDDEV_CLNT_DEC) + && (session_id >= MAX_SESSIONS)) + return -EINVAL; + if ((clnt_type == AUDDEV_CLNT_ENC) + && (session_id >= MAX_SESSIONS)) + return -EINVAL; + session_mask = (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)clnt_type-1)); + clnt_type_mask = (0xFFFF << (MAX_BIT_PER_CLIENT * (clnt_type-1))); + if (!(*freq == 8000) && !(*freq == 11025) && + !(*freq == 12000) && !(*freq == 16000) && + !(*freq == 22050) && !(*freq == 24000) && + !(*freq == 32000) && !(*freq == 44100) && + !(*freq == 48000)) + return -EINVAL; + + for (i = 0; i < audio_dev_ctrl.num_dev; i++) { + info = audio_dev_ctrl.devs[i]; + if ((info->sessions & session_mask) + && (info->capability & capability)) { + rc = 0; + if ((info->sessions & ~clnt_type_mask) + && ((*freq != 8000) && (*freq != 16000) + && (*freq != 48000))) { + if (clnt_type == AUDDEV_CLNT_ENC) { + routing_info.enc_freq[session_id].freq + = 0; + return -EPERM; + } else if (clnt_type == AUDDEV_CLNT_DEC) { + routing_info.dec_freq[session_id].freq + = 0; + return -EPERM; + } + } + if (*freq == info->set_sample_rate) { + rc = info->set_sample_rate; + continue; + } + set_freq = MAX(*freq, info->set_sample_rate); + + + if (clnt_type == AUDDEV_CLNT_DEC) { + routing_info.dec_freq[session_id].evt = 1; + routing_info.dec_freq[session_id].freq + = set_freq; + } else if (clnt_type == AUDDEV_CLNT_ENC) { + routing_info.enc_freq[session_id].evt = 1; + routing_info.enc_freq[session_id].freq + = set_freq; + } else if (capability == SNDDEV_CAP_TX) + routing_info.voice_tx_sample_rate = set_freq; + + rc = set_freq; + info->set_sample_rate = set_freq; + *freq = info->set_sample_rate; + + if (info->opened) { + broadcast_event(AUDDEV_EVT_FREQ_CHG, i, + SESSION_IGNORE); + set_freq = info->dev_ops.set_freq(info, + set_freq); + broadcast_event(AUDDEV_EVT_DEV_RDY, i, + SESSION_IGNORE); + } + } + pr_debug("info->set_sample_rate = %d\n", info->set_sample_rate); + pr_debug("routing_info.enc_freq.freq = %d\n", + routing_info.enc_freq[session_id].freq); + } + return rc; +} +EXPORT_SYMBOL(msm_snddev_request_freq); + +int msm_snddev_enable_sidetone(u32 dev_id, u32 enable, uint16_t gain) +{ + int rc; + struct msm_snddev_info *dev_info; + + pr_debug("dev_id %d enable %d\n", dev_id, enable); + + dev_info = audio_dev_ctrl_find_dev(dev_id); + + if (IS_ERR(dev_info)) { + pr_err("bad dev_id %d\n", dev_id); + rc = -EINVAL; + } else if (!dev_info->dev_ops.enable_sidetone) { + pr_debug("dev %d no sidetone support\n", dev_id); + rc = -EPERM; + } else + rc = dev_info->dev_ops.enable_sidetone(dev_info, enable, gain); + + return rc; +} +EXPORT_SYMBOL(msm_snddev_enable_sidetone); + +int msm_enable_incall_recording(int popp_id, int rec_mode, int rate, + int channel_mode) +{ + int rc = 0; + unsigned int port_id[2]; + port_id[0] = VOICE_RECORD_TX; + port_id[1] = VOICE_RECORD_RX; + + pr_debug("%s: popp_id %d, rec_mode %d, rate %d, channel_mode %d\n", + __func__, popp_id, rec_mode, rate, channel_mode); + + mutex_lock(&routing_info.adm_mutex); + + if (rec_mode == VOC_REC_UPLINK) { + rc = afe_start_pseudo_port(port_id[0]); + if (rc < 0) { + pr_err("%s: Error %d in Tx pseudo port start\n", + __func__, rc); + + goto fail_cmd; + } + + rc = adm_open(port_id[0], LIVE_RECORDING, rate, channel_mode, + DEFAULT_COPP_TOPOLOGY); + if (rc < 0) { + pr_err("%s: Error %d in ADM open %d\n", + __func__, rc, port_id[0]); + + goto fail_cmd; + } + + rc = adm_matrix_map(popp_id, LIVE_RECORDING, 1, + &port_id[0], port_id[0]); + if (rc < 0) { + pr_err("%s: Error %d in ADM matrix map %d\n", + __func__, rc, port_id[0]); + + goto fail_cmd; + } + + msm_set_copp_id(popp_id, port_id[0]); + + } else if (rec_mode == VOC_REC_DOWNLINK) { + rc = afe_start_pseudo_port(port_id[1]); + if (rc < 0) { + pr_err("%s: Error %d in Rx pseudo port start\n", + __func__, rc); + + goto fail_cmd; + } + + rc = adm_open(port_id[1], LIVE_RECORDING, rate, channel_mode, + DEFAULT_COPP_TOPOLOGY); + if (rc < 0) { + pr_err("%s: Error %d in ADM open %d\n", + __func__, rc, port_id[1]); + + goto fail_cmd; + } + + rc = adm_matrix_map(popp_id, LIVE_RECORDING, 1, + &port_id[1], port_id[1]); + if (rc < 0) { + pr_err("%s: Error %d in ADM matrix map %d\n", + __func__, rc, port_id[1]); + + goto fail_cmd; + } + + msm_set_copp_id(popp_id, port_id[1]); + + } else if (rec_mode == VOC_REC_BOTH) { + rc = afe_start_pseudo_port(port_id[0]); + if (rc < 0) { + pr_err("%s: Error %d in Tx pseudo port start\n", + __func__, rc); + + goto fail_cmd; + } + + rc = adm_open(port_id[0], LIVE_RECORDING, rate, channel_mode, + DEFAULT_COPP_TOPOLOGY); + if (rc < 0) { + pr_err("%s: Error %d in ADM open %d\n", + __func__, rc, port_id[0]); + + goto fail_cmd; + } + + msm_set_copp_id(popp_id, port_id[0]); + + rc = afe_start_pseudo_port(port_id[1]); + if (rc < 0) { + pr_err("%s: Error %d in Rx pseudo port start\n", + __func__, rc); + + goto fail_cmd; + } + + rc = adm_open(port_id[1], LIVE_RECORDING, rate, channel_mode, + DEFAULT_COPP_TOPOLOGY); + if (rc < 0) { + pr_err("%s: Error %d in ADM open %d\n", + __func__, rc, port_id[0]); + + goto fail_cmd; + } + + rc = adm_matrix_map(popp_id, LIVE_RECORDING, 2, + &port_id[0], port_id[1]); + if (rc < 0) { + pr_err("%s: Error %d in ADM matrix map\n", + __func__, rc); + + goto fail_cmd; + } + + msm_set_copp_id(popp_id, port_id[1]); + } else { + pr_err("%s Unknown rec_mode %d\n", __func__, rec_mode); + + goto fail_cmd; + } + + rc = voice_start_record(rec_mode, 1); + +fail_cmd: + mutex_unlock(&routing_info.adm_mutex); + return rc; +} + +int msm_disable_incall_recording(uint32_t popp_id, uint32_t rec_mode) +{ + int rc = 0; + uint32_t port_id[2]; + port_id[0] = VOICE_RECORD_TX; + port_id[1] = VOICE_RECORD_RX; + + pr_debug("%s: popp_id %d, rec_mode %d\n", __func__, popp_id, rec_mode); + + mutex_lock(&routing_info.adm_mutex); + + rc = voice_start_record(rec_mode, 0); + if (rc < 0) { + pr_err("%s: Error %d stopping record\n", __func__, rc); + + goto fail_cmd; + } + + if (rec_mode == VOC_REC_UPLINK) { + rc = adm_close(port_id[0]); + if (rc < 0) { + pr_err("%s: Error %d in ADM close %d\n", + __func__, rc, port_id[0]); + + goto fail_cmd; + } + + msm_clear_copp_id(popp_id, port_id[0]); + + rc = afe_stop_pseudo_port(port_id[0]); + if (rc < 0) { + pr_err("%s: Error %d in Tx pseudo port stop\n", + __func__, rc); + goto fail_cmd; + } + + } else if (rec_mode == VOC_REC_DOWNLINK) { + rc = adm_close(port_id[1]); + if (rc < 0) { + pr_err("%s: Error %d in ADM close %d\n", + __func__, rc, port_id[1]); + + goto fail_cmd; + } + + msm_clear_copp_id(popp_id, port_id[1]); + + rc = afe_stop_pseudo_port(port_id[1]); + if (rc < 0) { + pr_err("%s: Error %d in Rx pseudo port stop\n", + __func__, rc); + goto fail_cmd; + } + } else if (rec_mode == VOC_REC_BOTH) { + rc = adm_close(port_id[0]); + if (rc < 0) { + pr_err("%s: Error %d in ADM close %d\n", + __func__, rc, port_id[0]); + + goto fail_cmd; + } + + msm_clear_copp_id(popp_id, port_id[0]); + + rc = afe_stop_pseudo_port(port_id[0]); + if (rc < 0) { + pr_err("%s: Error %d in Tx pseudo port stop\n", + __func__, rc); + goto fail_cmd; + } + + rc = adm_close(port_id[1]); + if (rc < 0) { + pr_err("%s: Error %d in ADM close %d\n", + __func__, rc, port_id[1]); + + goto fail_cmd; + } + + msm_clear_copp_id(popp_id, port_id[1]); + + rc = afe_stop_pseudo_port(port_id[1]); + if (rc < 0) { + pr_err("%s: Error %d in Rx pseudo port stop\n", + __func__, rc); + goto fail_cmd; + } + } else { + pr_err("%s Unknown rec_mode %d\n", __func__, rec_mode); + + goto fail_cmd; + } + +fail_cmd: + mutex_unlock(&routing_info.adm_mutex); + return rc; +} + +static long audio_dev_ctrl_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int rc = 0; + struct audio_dev_ctrl_state *dev_ctrl = file->private_data; + + mutex_lock(&session_lock); + switch (cmd) { + case AUDIO_GET_NUM_SND_DEVICE: + rc = put_user(dev_ctrl->num_dev, (uint32_t __user *) arg); + break; + case AUDIO_GET_SND_DEVICES: + rc = audio_dev_ctrl_get_devices(dev_ctrl, (void __user *) arg); + break; + case AUDIO_ENABLE_SND_DEVICE: { + struct msm_snddev_info *dev_info; + u32 dev_id; + + if (get_user(dev_id, (u32 __user *) arg)) { + rc = -EFAULT; + break; + } + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) + rc = PTR_ERR(dev_info); + else { + rc = dev_info->dev_ops.open(dev_info); + if (!rc) + dev_info->opened = 1; + wake_up(&audio_dev_ctrl.wait); + } + break; + + } + + case AUDIO_DISABLE_SND_DEVICE: { + struct msm_snddev_info *dev_info; + u32 dev_id; + + if (get_user(dev_id, (u32 __user *) arg)) { + rc = -EFAULT; + break; + } + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) + rc = PTR_ERR(dev_info); + else { + rc = dev_info->dev_ops.close(dev_info); + dev_info->opened = 0; + } + break; + } + + case AUDIO_ROUTE_STREAM: { + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + + if (copy_from_user(&route_cfg, (void __user *) arg, + sizeof(struct msm_audio_route_config))) { + rc = -EFAULT; + break; + } + pr_debug("%s: route cfg %d %d type\n", __func__, + route_cfg.dev_id, route_cfg.stream_type); + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + pr_err("%s: pass invalid dev_id\n", __func__); + rc = PTR_ERR(dev_info); + break; + } + + switch (route_cfg.stream_type) { + + case AUDIO_ROUTE_STREAM_VOICE_RX: + if (!(dev_info->capability & SNDDEV_CAP_RX) | + !(dev_info->capability & SNDDEV_CAP_VOICE)) { + rc = -EINVAL; + break; + } + dev_ctrl->voice_rx_dev = dev_info; + break; + case AUDIO_ROUTE_STREAM_VOICE_TX: + if (!(dev_info->capability & SNDDEV_CAP_TX) | + !(dev_info->capability & SNDDEV_CAP_VOICE)) { + rc = -EINVAL; + break; + } + dev_ctrl->voice_tx_dev = dev_info; + break; + } + break; + } + + default: + rc = -EINVAL; + } + mutex_unlock(&session_lock); + return rc; +} + +static int audio_dev_ctrl_open(struct inode *inode, struct file *file) +{ + pr_debug("open audio_dev_ctrl\n"); + atomic_inc(&audio_dev_ctrl.opened); + file->private_data = &audio_dev_ctrl; + return 0; +} + +static int audio_dev_ctrl_release(struct inode *inode, struct file *file) +{ + pr_debug("release audio_dev_ctrl\n"); + atomic_dec(&audio_dev_ctrl.opened); + return 0; +} + +static const struct file_operations audio_dev_ctrl_fops = { + .owner = THIS_MODULE, + .open = audio_dev_ctrl_open, + .release = audio_dev_ctrl_release, + .unlocked_ioctl = audio_dev_ctrl_ioctl, +}; + + +struct miscdevice audio_dev_ctrl_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_audio_dev_ctrl", + .fops = &audio_dev_ctrl_fops, +}; + +/* session id is 64 bit routing mask per device + * 0-15 for voice clients + * 16-31 for Decoder clients + * 32-47 for Encoder clients + * 48-63 Do not care + */ +void broadcast_event(u32 evt_id, u32 dev_id, u64 session_id) +{ + int clnt_id = 0, i; + union auddev_evt_data *evt_payload; + struct msm_snd_evt_listner *callback; + struct msm_snddev_info *dev_info = NULL; + u64 session_mask = 0; + static int pending_sent; + + pr_debug(": evt_id = %d\n", evt_id); + + if ((evt_id != AUDDEV_EVT_START_VOICE) + && (evt_id != AUDDEV_EVT_END_VOICE) + && (evt_id != AUDDEV_EVT_STREAM_VOL_CHG) + && (evt_id != AUDDEV_EVT_VOICE_STATE_CHG)) { + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) { + pr_err("%s: pass invalid dev_id(%d)\n", + __func__, dev_id); + return; + } + } + +#ifdef CONFIG_MSM8X60_RTAC + update_rtac(evt_id, dev_id, dev_info); +#endif + + if (event.cb != NULL) + callback = event.cb; + else + return; + mutex_lock(&session_lock); + + if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + routing_info.voice_state = dev_id; + + evt_payload = kzalloc(sizeof(union auddev_evt_data), + GFP_KERNEL); + + if (evt_payload == NULL) { + pr_err("broadcast_event: cannot allocate memory\n"); + mutex_unlock(&session_lock); + return; + } + for (; ;) { + if (!(evt_id & callback->evt_id)) { + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + clnt_id = callback->clnt_id; + memset(evt_payload, 0, sizeof(union auddev_evt_data)); + + if ((evt_id == AUDDEV_EVT_START_VOICE) + || (evt_id == AUDDEV_EVT_END_VOICE)) + goto skip_check; + if (callback->clnt_type == AUDDEV_CLNT_AUDIOCAL) + goto aud_cal; + + session_mask = (((u64)0x1) << clnt_id) + << (MAX_BIT_PER_CLIENT * \ + ((int)callback->clnt_type-1)); + + if ((evt_id == AUDDEV_EVT_STREAM_VOL_CHG) || \ + (evt_id == AUDDEV_EVT_VOICE_STATE_CHG)) { + pr_debug("AUDDEV_EVT_STREAM_VOL_CHG or\ + AUDDEV_EVT_VOICE_STATE_CHG\n"); + goto volume_strm; + } + + pr_debug("dev_info->sessions = %llu\n", dev_info->sessions); + + if ((!session_id && !(dev_info->sessions & session_mask)) || + (session_id && ((dev_info->sessions & session_mask) != + session_id))) { + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + if (evt_id == AUDDEV_EVT_DEV_CHG_VOICE) + goto voc_events; + +volume_strm: + if (callback->clnt_type == AUDDEV_CLNT_DEC) { + pr_debug("AUDDEV_CLNT_DEC\n"); + if (evt_id == AUDDEV_EVT_STREAM_VOL_CHG) { + pr_debug("clnt_id = %d, session_id = %llu\n", + clnt_id, session_id); + if (session_mask != session_id) + goto sent_dec; + else + evt_payload->session_vol = + msm_vol_ctl.volume; + } else if (evt_id == AUDDEV_EVT_FREQ_CHG) { + if (routing_info.dec_freq[clnt_id].evt) { + routing_info.dec_freq[clnt_id].evt + = 0; + goto sent_dec; + } else if (routing_info.dec_freq[clnt_id].freq + == dev_info->set_sample_rate) + goto sent_dec; + else { + evt_payload->freq_info.sample_rate + = dev_info->set_sample_rate; + evt_payload->freq_info.dev_type + = dev_info->capability; + evt_payload->freq_info.acdb_dev_id + = dev_info->acdb_id; + } + } else if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + evt_payload->voice_state = + routing_info.voice_state; + else + evt_payload->routing_id = dev_info->copp_id; + callback->auddev_evt_listener( + evt_id, + evt_payload, + callback->private_data); +sent_dec: + if ((evt_id != AUDDEV_EVT_STREAM_VOL_CHG) && + (evt_id != AUDDEV_EVT_VOICE_STATE_CHG)) + routing_info.dec_freq[clnt_id].freq + = dev_info->set_sample_rate; + + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + if (callback->clnt_type == AUDDEV_CLNT_ENC) { + pr_debug("AUDDEV_CLNT_ENC\n"); + if (evt_id == AUDDEV_EVT_FREQ_CHG) { + if (routing_info.enc_freq[clnt_id].evt) { + routing_info.enc_freq[clnt_id].evt + = 0; + goto sent_enc; + } else { + evt_payload->freq_info.sample_rate + = dev_info->set_sample_rate; + evt_payload->freq_info.dev_type + = dev_info->capability; + evt_payload->freq_info.acdb_dev_id + = dev_info->acdb_id; + } + } else if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + evt_payload->voice_state = + routing_info.voice_state; + else + evt_payload->routing_id = dev_info->copp_id; + callback->auddev_evt_listener( + evt_id, + evt_payload, + callback->private_data); +sent_enc: + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } +aud_cal: + if (callback->clnt_type == AUDDEV_CLNT_AUDIOCAL) { + pr_debug("AUDDEV_CLNT_AUDIOCAL\n"); + if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + evt_payload->voice_state = + routing_info.voice_state; + else if (!dev_info->sessions) + goto sent_aud_cal; + else { + evt_payload->audcal_info.dev_id = + dev_info->copp_id; + evt_payload->audcal_info.acdb_id = + dev_info->acdb_id; + evt_payload->audcal_info.dev_type = + (dev_info->capability & SNDDEV_CAP_TX) ? + SNDDEV_CAP_TX : SNDDEV_CAP_RX; + evt_payload->audcal_info.sample_rate = + dev_info->set_sample_rate ? + dev_info->set_sample_rate : + dev_info->sample_rate; + } + callback->auddev_evt_listener( + evt_id, + evt_payload, + callback->private_data); + +sent_aud_cal: + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } +skip_check: +voc_events: + if (callback->clnt_type == AUDDEV_CLNT_VOC) { + pr_debug("AUDDEV_CLNT_VOC\n"); + if (evt_id == AUDDEV_EVT_DEV_RLS) { + if (!pending_sent) + goto sent_voc; + else + pending_sent = 0; + } + if (evt_id == AUDDEV_EVT_REL_PENDING) + pending_sent = 1; + + if (evt_id == AUDDEV_EVT_DEVICE_VOL_MUTE_CHG) { + if (dev_info->capability & SNDDEV_CAP_TX) { + evt_payload->voc_vm_info.dev_type = + SNDDEV_CAP_TX; + evt_payload->voc_vm_info.acdb_dev_id = + dev_info->acdb_id; + evt_payload-> + voc_vm_info.dev_vm_val.mute = + routing_info.tx_mute; + } else { + evt_payload->voc_vm_info.dev_type = + SNDDEV_CAP_RX; + evt_payload->voc_vm_info.acdb_dev_id = + dev_info->acdb_id; + evt_payload-> + voc_vm_info.dev_vm_val.vol = + routing_info.voice_rx_vol; + } + } else if ((evt_id == AUDDEV_EVT_START_VOICE) + || (evt_id == AUDDEV_EVT_END_VOICE)) + memset(evt_payload, 0, + sizeof(union auddev_evt_data)); + else if (evt_id == AUDDEV_EVT_FREQ_CHG) { + if (routing_info.voice_tx_sample_rate + != dev_info->set_sample_rate) { + routing_info.voice_tx_sample_rate + = dev_info->set_sample_rate; + evt_payload->freq_info.sample_rate + = dev_info->set_sample_rate; + evt_payload->freq_info.dev_type + = dev_info->capability; + evt_payload->freq_info.acdb_dev_id + = dev_info->acdb_id; + } else + goto sent_voc; + } else if (evt_id == AUDDEV_EVT_VOICE_STATE_CHG) + evt_payload->voice_state = + routing_info.voice_state; + else { + evt_payload->voc_devinfo.dev_type = + (dev_info->capability & SNDDEV_CAP_TX) ? + SNDDEV_CAP_TX : SNDDEV_CAP_RX; + evt_payload->voc_devinfo.acdb_dev_id = + dev_info->acdb_id; + evt_payload->voc_devinfo.dev_port_id = + dev_info->copp_id; + evt_payload->voc_devinfo.dev_sample = + dev_info->set_sample_rate ? + dev_info->set_sample_rate : + dev_info->sample_rate; + evt_payload->voc_devinfo.dev_id = dev_id; + if (dev_info->capability & SNDDEV_CAP_RX) { + for (i = 0; i < VOC_RX_VOL_ARRAY_NUM; + i++) { + evt_payload-> + voc_devinfo.max_rx_vol[i] = + dev_info->max_voc_rx_vol[i]; + evt_payload + ->voc_devinfo.min_rx_vol[i] = + dev_info->min_voc_rx_vol[i]; + } + } + } + callback->auddev_evt_listener( + evt_id, + evt_payload, + callback->private_data); + if (evt_id == AUDDEV_EVT_DEV_RLS) + dev_info->sessions &= ~(0xFFFF); +sent_voc: + if (callback->cb_next == NULL) + break; + else { + callback = callback->cb_next; + continue; + } + } + } + kfree(evt_payload); + mutex_unlock(&session_lock); +} +EXPORT_SYMBOL(broadcast_event); + + +void mixer_post_event(u32 evt_id, u32 id) +{ + + pr_debug("evt_id = %d\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_CHG_VOICE: /* Called from Voice_route */ + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, id, SESSION_IGNORE); + break; + case AUDDEV_EVT_DEV_RDY: + broadcast_event(AUDDEV_EVT_DEV_RDY, id, SESSION_IGNORE); + break; + case AUDDEV_EVT_DEV_RLS: + broadcast_event(AUDDEV_EVT_DEV_RLS, id, SESSION_IGNORE); + break; + case AUDDEV_EVT_REL_PENDING: + broadcast_event(AUDDEV_EVT_REL_PENDING, id, SESSION_IGNORE); + break; + case AUDDEV_EVT_DEVICE_VOL_MUTE_CHG: + broadcast_event(AUDDEV_EVT_DEVICE_VOL_MUTE_CHG, id, + SESSION_IGNORE); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + broadcast_event(AUDDEV_EVT_STREAM_VOL_CHG, id, + SESSION_IGNORE); + break; + case AUDDEV_EVT_START_VOICE: + broadcast_event(AUDDEV_EVT_START_VOICE, + id, SESSION_IGNORE); + break; + case AUDDEV_EVT_END_VOICE: + broadcast_event(AUDDEV_EVT_END_VOICE, + id, SESSION_IGNORE); + break; + case AUDDEV_EVT_FREQ_CHG: + broadcast_event(AUDDEV_EVT_FREQ_CHG, id, SESSION_IGNORE); + break; + default: + break; + } +} +EXPORT_SYMBOL(mixer_post_event); + +static int __init audio_dev_ctrl_init(void) +{ + init_waitqueue_head(&audio_dev_ctrl.wait); + + event.cb = NULL; + + atomic_set(&audio_dev_ctrl.opened, 0); + audio_dev_ctrl.num_dev = 0; + audio_dev_ctrl.voice_tx_dev = NULL; + audio_dev_ctrl.voice_rx_dev = NULL; + routing_info.voice_state = VOICE_STATE_INVALID; + + mutex_init(&adm_tx_topology_tbl.lock); + mutex_init(&routing_info.copp_list_mutex); + mutex_init(&routing_info.adm_mutex); + + memset(routing_info.copp_list, DEVICE_IGNORE, + (sizeof(unsigned int) * MAX_SESSIONS * AFE_MAX_PORTS)); + return misc_register(&audio_dev_ctrl_misc); +} + +static void __exit audio_dev_ctrl_exit(void) +{ +} +module_init(audio_dev_ctrl_init); +module_exit(audio_dev_ctrl_exit); + +MODULE_DESCRIPTION("MSM 8K Audio Device Control driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/audio_lpa.c b/arch/arm/mach-msm/qdsp6v2/audio_lpa.c new file mode 100644 index 00000000000..52434bfcd94 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_lpa.c @@ -0,0 +1,1420 @@ +/* low power audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audio_lpa.h" + +#include +#include +#include + +#include +#include + +#define MAX_BUF 3 +#define BUFSZ (524288) + +#define AUDDEC_DEC_PCM 0 + +#define AUDLPA_EVENT_NUM 10 /* Default number of pre-allocated event packets */ + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +struct audlpa_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audlpa_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audlpa_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; +}; + +struct audlpa_dec { + char *name; + int dec_attrb; + long (*ioctl)(struct file *, unsigned int, unsigned long); + int (*set_params)(void *); +}; + +static void audlpa_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload); +static unsigned long audlpa_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up); +static void audlpa_async_send_data(struct audio *audio, unsigned needed, + uint32_t token); +static int audlpa_pause(struct audio *audio); +static void audlpa_unmap_pmem_region(struct audio *audio); +static long pcm_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +static int audlpa_set_pcm_params(void *data); + +struct audlpa_dec audlpa_decs[] = { + {"msm_pcm_lp_dec", AUDDEC_DEC_PCM, &pcm_ioctl, + &audlpa_set_pcm_params}, +}; + +static void lpa_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + int rc = 0; + + switch (evt_id) { + case AUDDEV_EVT_STREAM_VOL_CHG: + audio->volume = evt_payload->session_vol; + pr_debug("%s: AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d, " + "enabled = %d\n", __func__, audio->volume, + audio->out_enabled); + if (audio->out_enabled == 1) { + if (audio->ac) { + rc = q6asm_set_volume(audio->ac, audio->volume); + if (rc < 0) { + pr_err("%s: Send Volume command failed" + " rc=%d\n", __func__, rc); + } + } + } + break; + default: + pr_err("%s:ERROR:wrong event\n", __func__); + break; + } +} + +static void audlpa_prevent_sleep(struct audio *audio) +{ + pr_debug("%s:\n", __func__); + wake_lock(&audio->wakelock); +} + +static void audlpa_allow_sleep(struct audio *audio) +{ + pr_debug("%s:\n", __func__); + wake_unlock(&audio->wakelock); +} + +/* must be called with audio->lock held */ +static int audio_enable(struct audio *audio) +{ + pr_debug("%s\n", __func__); + + return q6asm_run(audio->ac, 0, 0, 0); + +} + +static void audlpa_async_flush(struct audio *audio) +{ + struct audlpa_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + int rc = 0; + + pr_debug("%s:out_enabled = %d, drv_status = 0x%x\n", __func__, + audio->out_enabled, audio->drv_status); + if (audio->out_enabled) { + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audlpa_buffer_node, + list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audlpa_post_event(audio, AUDIO_EVENT_WRITE_DONE, + payload); + kfree(buf_node); + } + /* Implicitly issue a pause to the decoder before flushing if + it is not in pause state */ + if (!(audio->drv_status & ADRV_STATUS_PAUSE)) { + rc = audlpa_pause(audio); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, + rc); + } + + rc = q6asm_cmd(audio->ac, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + audio->out_needed = 0; + + if (audio->stopped == 0) { + rc = audio_enable(audio); + if (rc < 0) + pr_err("%s: audio enable failed\n", __func__); + else { + audio->out_enabled = 1; + audio->out_needed = 1; + if (audio->drv_status & ADRV_STATUS_PAUSE) + audio->drv_status &= ~ADRV_STATUS_PAUSE; + } + } + wake_up(&audio->write_wait); + } +} + +/* must be called with audio->lock held */ +static int audio_disable(struct audio *audio) +{ + int rc = 0; + + pr_debug("%s:%d %d\n", __func__, audio->opened, audio->out_enabled); + + if (audio->opened) { + audio->out_enabled = 0; + audio->opened = 0; + rc = q6asm_cmd(audio->ac, CMD_CLOSE); + if (rc < 0) + pr_err("%s: CLOSE cmd failed\n", __func__); + else + pr_debug("%s: rxed CLOSE resp\n", __func__); + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + wake_up(&audio->write_wait); + audio->out_needed = 0; + } + return rc; +} +static int audlpa_pause(struct audio *audio) +{ + int rc = 0; + + pr_debug("%s, enabled = %d\n", __func__, + audio->out_enabled); + if (audio->out_enabled) { + rc = q6asm_cmd(audio->ac, CMD_PAUSE); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, rc); + + } else + pr_err("%s: Driver not enabled\n", __func__); + return rc; +} + +/* ------------------- dsp --------------------- */ +static void audlpa_async_send_data(struct audio *audio, unsigned needed, + uint32_t token) +{ + unsigned long flags; + struct audio_client *ac; + int rc = 0; + + pr_debug("%s:\n", __func__); + spin_lock_irqsave(&audio->dsp_lock, flags); + + pr_debug("%s: needed = %d, out_needed = %d, token = 0x%x\n", + __func__, needed, audio->out_needed, token); + if (needed && !audio->wflush) { + audio->out_needed = 1; + if (audio->drv_status & ADRV_STATUS_OBUF_GIVEN) { + /* pop one node out of queue */ + union msm_audio_event_payload evt_payload; + struct audlpa_buffer_node *used_buf; + + used_buf = list_first_entry(&audio->out_queue, + struct audlpa_buffer_node, list); + if (token == used_buf->paddr) { + pr_debug("%s, Release: addr: %lx," + " token = 0x%x\n", __func__, + used_buf->paddr, token); + list_del(&used_buf->list); + evt_payload.aio_buf = used_buf->buf; + audlpa_post_event(audio, AUDIO_EVENT_WRITE_DONE, + evt_payload); + kfree(used_buf); + audio->drv_status &= ~ADRV_STATUS_OBUF_GIVEN; + } + } + } + pr_debug("%s: out_needed = %d, stopped = %d, drv_status = 0x%x\n", + __func__, audio->out_needed, audio->stopped, + audio->drv_status); + if (audio->out_needed && (audio->stopped == 0)) { + struct audlpa_buffer_node *next_buf; + struct audio_aio_write_param param; + if (!list_empty(&audio->out_queue)) { + pr_debug("%s: list not empty\n", __func__); + next_buf = list_first_entry(&audio->out_queue, + struct audlpa_buffer_node, list); + if (next_buf) { + pr_debug("%s: Send: addr: %lx\n", __func__, + next_buf->paddr); + ac = audio->ac; + param.paddr = next_buf->paddr; + param.len = next_buf->buf.data_len; + param.msw_ts = 0; + param.lsw_ts = 0; + /* No time stamp valid */ + param.flags = NO_TIMESTAMP; + param.uid = next_buf->paddr; + rc = q6asm_async_write(ac, ¶m); + if (rc < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + audio->out_needed = 0; + audio->drv_status |= ADRV_STATUS_OBUF_GIVEN; + } + } else if (list_empty(&audio->out_queue) && + (audio->drv_status & ADRV_STATUS_FSYNC)) { + pr_debug("%s: list is empty, reached EOS\n", __func__); + wake_up(&audio->write_wait); + } + } + + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +static int audlpa_events_pending(struct audio *audio) +{ + int empty; + + spin_lock(&audio->event_queue_lock); + empty = !list_empty(&audio->event_queue); + spin_unlock(&audio->event_queue_lock); + return empty || audio->event_abort; +} + +static void audlpa_reset_event_queue(struct audio *audio) +{ + struct audlpa_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock(&audio->event_queue_lock); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audlpa_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audlpa_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock(&audio->event_queue_lock); + + return; +} + +static long audlpa_process_event_req(struct audio *audio, void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audlpa_event *drv_evt = NULL; + int timeout; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int) usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout( + audio->event_wait, audlpa_events_pending(audio), + msecs_to_jiffies(timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible( + audio->event_wait, audlpa_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock(&audio->event_queue_lock); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audlpa_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else + rc = -1; + spin_unlock(&audio->event_queue_lock); + + if (drv_evt->event_type == AUDIO_EVENT_WRITE_DONE || + drv_evt->event_type == AUDIO_EVENT_READ_DONE) { + pr_debug("%s: AUDIO_EVENT_WRITE_DONE completing\n", __func__); + mutex_lock(&audio->lock); + audlpa_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0); + mutex_unlock(&audio->lock); + } + if (!rc && copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audlpa_pmem_check(struct audio *audio, + void *vaddr, unsigned long len) +{ + struct audlpa_pmem_region *region_elt; + struct audlpa_pmem_region t = { .vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + pr_err("%s: region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + __func__, vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, + region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audlpa_pmem_add(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audlpa_pmem_region *region; + int rc = -EINVAL; + + pr_debug("%s:\n", __func__); + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = audlpa_pmem_check(audio, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + pr_debug("%s: add region paddr %lx vaddr %p, len %lu\n", __func__, + region->paddr, region->vaddr, + region->len); + list_add_tail(®ion->list, &audio->pmem_region_queue); + rc = q6asm_memory_map(audio->ac, (uint32_t)paddr, IN, (uint32_t)len, 1); + if (rc < 0) + pr_err("%s: memory map failed\n", __func__); +end: + return rc; +} + +static int audlpa_pmem_remove(struct audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audlpa_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audlpa_pmem_region, list); + + if ((region != NULL) && (region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + pr_debug("%s: region %p in use ref_cnt %d\n", + __func__, region, region->ref_cnt); + break; + } + rc = q6asm_memory_unmap(audio->ac, + (uint32_t)region->paddr, + IN); + if (rc < 0) + pr_err("%s: memory unmap failed\n", __func__); + + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +static int audlpa_pmem_lookup_vaddr(struct audio *audio, void *addr, + unsigned long len, struct audlpa_pmem_region **region) +{ + struct audlpa_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + pr_err("%s: multiple hits for vaddr %p, len %ld\n", __func__, + addr, len); + list_for_each_entry(region_elt, + &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + pr_err("%s: \t%p, %ld --> %p\n", __func__, + region_elt->vaddr, region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +unsigned long audlpa_pmem_fixup(struct audio *audio, void *addr, + unsigned long len, int ref_up) +{ + struct audlpa_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audlpa_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + pr_err("%s: lookup (%p, %ld) failed\n", __func__, addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + paddr = region->paddr + (addr - region->vaddr); + return paddr; +} + +/* audio -> lock must be held at this point */ +static int audlpa_aio_buf_add(struct audio *audio, unsigned dir, + void __user *arg) +{ + struct audlpa_buffer_node *buf_node; + + buf_node = kmalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + buf_node->paddr = audlpa_pmem_fixup( + audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1); + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.data_len & 0x1)) { + kfree(buf_node); + return -EINVAL; + } + list_add_tail(&buf_node->list, &audio->out_queue); + pr_debug("%s, Added to list: addr: %lx, length = %d\n", + __func__, buf_node->paddr, buf_node->buf.data_len); + audlpa_async_send_data(audio, 0, 0); + } else { + /* read */ + } + return 0; +} + +static int config(struct audio *audio) +{ + int rc = 0; + if (!audio->out_prefill) { + if (audio->codec_ops.set_params != NULL) { + rc = audio->codec_ops.set_params(audio); + audio->out_prefill = 1; + } + } + return rc; +} + +void q6_audlpa_out_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct audio *audio = (struct audio *) priv; + + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: + pr_debug("%s: ASM_DATA_EVENT_WRITE_DONE, token = 0x%x\n", + __func__, token); + audlpa_async_send_data(audio, 1, token); + break; + case ASM_DATA_EVENT_EOS: + case ASM_DATA_CMDRSP_EOS: + pr_debug("%s: ASM_DATA_CMDRSP_EOS, teos = %d\n", __func__, + audio->teos); + if (audio->teos == 0) { + audio->teos = 1; + wake_up(&audio->write_wait); + } + break; + case ASM_SESSION_CMDRSP_GET_SESSION_TIME: + break; + default: + break; + } +} + +static long pcm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + pr_debug("%s: cmd = %d\n", __func__, cmd); + return -EINVAL; +} + +static int audlpa_set_pcm_params(void *data) +{ + struct audio *audio = (struct audio *)data; + int rc; + + rc = q6asm_media_format_block_pcm(audio->ac, audio->out_sample_rate, + audio->out_channel_mode); + if (rc < 0) + pr_err("%s: Format block pcm failed\n", __func__); + return rc; +} + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + uint64_t timestamp; + uint64_t temp; + + pr_debug("%s: audio_ioctl() cmd = %d\n", __func__, cmd); + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + + pr_debug("%s: AUDIO_GET_STATS cmd\n", __func__); + memset(&stats, 0, sizeof(stats)); + timestamp = q6asm_get_session_time(audio->ac); + if (timestamp < 0) { + pr_err("%s: Get Session Time return value =%lld\n", + __func__, timestamp); + return -EAGAIN; + } + temp = (timestamp * 2 * audio->out_channel_mode); + temp = temp * (audio->out_sample_rate/1000); + temp = div_u64(temp, 1000); + audio->bytes_consumed = (uint32_t)(temp & 0xFFFFFFFF); + stats.byte_count = audio->bytes_consumed; + stats.unused[0] = (uint32_t)((temp >> 32) & 0xFFFFFFFF); + pr_debug("%s: bytes_consumed:lsb = %d, msb = %d," + "timestamp = %lld\n", __func__, + audio->bytes_consumed, stats.unused[0], timestamp); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + switch (cmd) { + case AUDIO_ENABLE_AUDPP: + break; + + case AUDIO_SET_VOLUME: + break; + + case AUDIO_SET_PAN: + break; + + case AUDIO_SET_EQ: + break; + } + + if (cmd == AUDIO_GET_EVENT) { + pr_debug("%s: AUDIO_GET_EVENT\n", __func__); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audlpa_process_event_req(audio, + (void __user *) arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + pr_info("%s: AUDIO_START: Session %d\n", __func__, + audio->ac->session); + if (!audio->opened) { + pr_err("%s: Driver not opened\n", __func__); + rc = -EFAULT; + goto fail; + } + rc = config(audio); + if (rc) { + pr_err("%s: Out Configuration failed\n", __func__); + rc = -EFAULT; + goto fail; + } + + rc = audio_enable(audio); + if (rc) { + pr_err("%s: audio enable failed\n", __func__); + rc = -EFAULT; + goto fail; + } else { + struct asm_softpause_params param = { + .enable = SOFT_PAUSE_ENABLE, + .period = SOFT_PAUSE_PERIOD, + .step = SOFT_PAUSE_STEP, + .rampingcurve = SOFT_PAUSE_CURVE_LINEAR, + }; + audio->out_enabled = 1; + audio->out_needed = 1; + rc = q6asm_set_volume(audio->ac, audio->volume); + if (rc < 0) + pr_err("%s: Send Volume command failed rc=%d\n", + __func__, rc); + rc = q6asm_set_softpause(audio->ac, ¶m); + if (rc < 0) + pr_err("%s: Send SoftPause Param failed rc=%d\n", + __func__, rc); + rc = q6asm_set_lrgain(audio->ac, 0x2000, 0x2000); + if (rc < 0) + pr_err("%s: Send channel gain failed rc=%d\n", + __func__, rc); + /* disable mute by default */ + rc = q6asm_set_mute(audio->ac, 0); + if (rc < 0) + pr_err("%s: Send mute command failed rc=%d\n", + __func__, rc); + if (!list_empty(&audio->out_queue)) + pr_err("%s: write_list is not empty!!!\n", + __func__); + if (audio->stopped == 1) + audio->stopped = 0; + audlpa_prevent_sleep(audio); + } + break; + + case AUDIO_STOP: + pr_info("%s: AUDIO_STOP: session_id:%d\n", __func__, + audio->ac->session); + audio->stopped = 1; + audlpa_async_flush(audio); + audio->out_enabled = 0; + audio->out_needed = 0; + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audlpa_allow_sleep(audio); + break; + + case AUDIO_FLUSH: + pr_debug("%s: AUDIO_FLUSH: session_id:%d\n", __func__, + audio->ac->session); + audio->wflush = 1; + if (audio->out_enabled) + audlpa_async_flush(audio); + else + audio->wflush = 0; + audio->wflush = 0; + break; + + case AUDIO_SET_CONFIG:{ + struct msm_audio_config config; + pr_debug("%s: AUDIO_SET_CONFIG\n", __func__); + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + pr_err("%s: ERROR: copy from user\n", __func__); + break; + } + if (!((config.channel_count == 1) || + (config.channel_count == 2))) { + rc = -EINVAL; + pr_err("%s: ERROR: config.channel_count == %d\n", + __func__, config.channel_count); + break; + } + + if (!((config.bits == 8) || (config.bits == 16) || + (config.bits == 24))) { + rc = -EINVAL; + pr_err("%s: ERROR: config.bits = %d\n", __func__, + config.bits); + break; + } + audio->out_sample_rate = config.sample_rate; + audio->out_channel_mode = config.channel_count; + audio->out_bits = config.bits; + audio->buffer_count = config.buffer_count; + audio->buffer_size = config.buffer_size; + rc = 0; + break; + } + + case AUDIO_GET_CONFIG:{ + struct msm_audio_config config; + config.buffer_count = audio->buffer_count; + config.buffer_size = audio->buffer_size; + config.sample_rate = audio->out_sample_rate; + config.channel_count = audio->out_channel_mode; + config.bits = audio->out_bits; + + config.meta_field = 0; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + else + rc = 0; + break; + } + + case AUDIO_PAUSE: + pr_debug("%s: AUDIO_PAUSE %ld\n", __func__, arg); + if (arg == 1) { + rc = audlpa_pause(audio); + if (rc < 0) + pr_err("%s: pause FAILED rc=%d\n", __func__, + rc); + audio->drv_status |= ADRV_STATUS_PAUSE; + } else if (arg == 0) { + if (audio->drv_status & ADRV_STATUS_PAUSE) { + rc = audio_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", + __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->out_enabled = 1; + } + } + } + break; + + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("%s: AUDIO_REGISTER_PMEM\n", __func__); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audlpa_pmem_add(audio, &info); + break; + } + + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("%s: AUDIO_DEREGISTER_PMEM\n", __func__); + if (copy_from_user(&info, (void *) arg, sizeof(info))) + rc = -EFAULT; + else + rc = audlpa_pmem_remove(audio, &info); + break; + } + case AUDIO_ASYNC_WRITE: + pr_debug("%s: AUDIO_ASYNC_WRITE\n", __func__); + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else + rc = audlpa_aio_buf_add(audio, 1, (void __user *) arg); + break; + + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->ac->session, + sizeof(unsigned short))) + return -EFAULT; + rc = 0; + break; + + default: + rc = audio->codec_ops.ioctl(file, cmd, arg); + } +fail: + mutex_unlock(&audio->lock); + return rc; +} + +/* Only useful in tunnel-mode */ +int audlpa_async_fsync(struct audio *audio) +{ + int rc = 0; + + pr_info("%s:Session %d\n", __func__, audio->ac->session); + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + mutex_lock(&audio->write_lock); + audio->teos = 0; + + rc = wait_event_interruptible(audio->write_wait, + ((list_empty(&audio->out_queue)) || + audio->wflush || audio->stopped)); + + if (audio->wflush || audio->stopped) + goto flush_event; + + if (rc < 0) { + pr_err("%s: wait event for list_empty failed, rc = %d\n", + __func__, rc); + goto done; + } + + rc = q6asm_cmd(audio->ac, CMD_EOS); + + if (rc < 0) { + pr_err("%s: q6asm_cmd failed, rc = %d", __func__, rc); + goto done; + } + rc = wait_event_interruptible_timeout(audio->write_wait, + (audio->teos || audio->wflush || + audio->stopped), 5*HZ); + + if (rc < 0) { + pr_err("%s: wait event for teos failed, rc = %d\n", __func__, + rc); + goto done; + } + + if (audio->teos == 1) { + rc = audio_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->out_enabled = 1; + audio->out_needed = 1; + } + } + +flush_event: + if (audio->stopped || audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +int audlpa_fsync(struct file *file, int datasync) +{ + struct audio *audio = file->private_data; + + return audlpa_async_fsync(audio); +} + +static void audlpa_reset_pmem_region(struct audio *audio) +{ + struct audlpa_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audlpa_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +static void audlpa_unmap_pmem_region(struct audio *audio) +{ + struct audlpa_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + pr_debug("%s:\n", __func__); + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audlpa_pmem_region, list); + pr_debug("%s: phy_address = 0x%lx\n", __func__, region->paddr); + if (region != NULL) { + rc = q6asm_memory_unmap(audio->ac, + (uint32_t)region->paddr, IN); + if (rc < 0) + pr_err("%s: memory unmap failed\n", __func__); + } + } +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + pr_info("%s: audio instance 0x%08x freeing, session %d\n", __func__, + (int)audio, audio->ac->session); + + mutex_lock(&audio->lock); + audio->wflush = 1; + if (audio->out_enabled) + audlpa_async_flush(audio); + audio->wflush = 0; + audlpa_unmap_pmem_region(audio); + audio_disable(audio); + msm_clear_session_id(audio->ac->session); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->ac->session); + q6asm_audio_client_free(audio->ac); + audlpa_reset_pmem_region(audio); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&audio->suspend_ctl.node); +#endif + audio->opened = 0; + audio->out_enabled = 0; + audio->out_prefill = 0; + audio->event_abort = 1; + wake_up(&audio->event_wait); + audlpa_reset_event_queue(audio); + pmem_kfree(audio->phys); + if (audio->stopped == 0) + audlpa_allow_sleep(audio); + wake_lock_destroy(&audio->wakelock); + + mutex_unlock(&audio->lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + return 0; +} + +static void audlpa_post_event(struct audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audlpa_event *e_node = NULL; + + spin_lock(&audio->event_queue_lock); + + pr_debug("%s:\n", __func__); + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audlpa_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audlpa_event), GFP_ATOMIC); + if (!e_node) { + pr_err("%s: No mem to post event %d\n", __func__, type); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock(&audio->event_queue_lock); + wake_up(&audio->event_wait); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void audlpa_suspend(struct early_suspend *h) +{ + struct audlpa_suspend_ctl *ctl = + container_of(h, struct audlpa_suspend_ctl, node); + union msm_audio_event_payload payload; + + pr_debug("%s:\n", __func__); + audlpa_post_event(ctl->audio, AUDIO_EVENT_SUSPEND, payload); +} + +static void audlpa_resume(struct early_suspend *h) +{ + struct audlpa_suspend_ctl *ctl = + container_of(h, struct audlpa_suspend_ctl, node); + union msm_audio_event_payload payload; + + pr_debug("%s:\n", __func__); + audlpa_post_event(ctl->audio, AUDIO_EVENT_RESUME, payload); +} +#endif + +#ifdef CONFIG_DEBUG_FS +static ssize_t audlpa_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audlpa_debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0; + struct audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_enabled %d\n", audio->out_enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "volume %x\n", audio->volume); + n += scnprintf(buffer + n, debug_bufmax - n, + "sample rate %d\n", + audio->out_sample_rate); + n += scnprintf(buffer + n, debug_bufmax - n, + "channel mode %d\n", + audio->out_channel_mode); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "running %d\n", audio->running); + n += scnprintf(buffer + n, debug_bufmax - n, + "out_needed %d\n", audio->out_needed); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audlpa_debug_fops = { + .read = audlpa_debug_read, + .open = audlpa_debug_open, +}; +#endif + +static int audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = NULL; + int rc, i, dec_attrb = 0; + struct audlpa_event *e_node = NULL; +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_lpa_" + 5]; +#endif + char wake_lock_name[24]; + + /* Allocate audio instance, set to zero */ + audio = kzalloc(sizeof(struct audio), GFP_KERNEL); + if (!audio) { + pr_err("%s: no memory to allocate audio instance\n", __func__); + rc = -ENOMEM; + goto done; + } + + if ((file->f_mode & FMODE_WRITE) && !(file->f_mode & FMODE_READ)) { + pr_debug("%s: Tunnel Mode playback\n", __func__); + } else { + kfree(audio); + rc = -EACCES; + goto done; + } + + /* Allocate the decoder based on inode minor number*/ + audio->minor_no = iminor(inode); + dec_attrb |= audlpa_decs[audio->minor_no].dec_attrb; + audio->codec_ops.ioctl = audlpa_decs[audio->minor_no].ioctl; + audio->codec_ops.set_params = audlpa_decs[audio->minor_no].set_params; + audio->buffer_size = BUFSZ; + audio->buffer_count = MAX_BUF; + + audio->ac = q6asm_audio_client_alloc((app_cb)q6_audlpa_out_cb, + (void *)audio); + if (!audio->ac) { + pr_err("%s: Could not allocate memory for lpa client\n", + __func__); + rc = -ENOMEM; + goto err; + } + rc = q6asm_open_write(audio->ac, FORMAT_LINEAR_PCM); + if (rc < 0) { + pr_err("%s: lpa out open failed\n", __func__); + goto err; + } + + pr_debug("%s: Set mode to AIO session[%d]\n", + __func__, + audio->ac->session); + rc = q6asm_set_io_mode(audio->ac, ASYNC_IO_MODE); + if (rc < 0) + pr_err("%s: Set IO mode failed\n", __func__); + + + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->write_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + init_waitqueue_head(&audio->wait); + init_waitqueue_head(&audio->event_wait); + spin_lock_init(&audio->event_queue_lock); + snprintf(wake_lock_name, sizeof wake_lock_name, "audio_lpa_%x", + audio->ac->session); + wake_lock_init(&audio->wakelock, WAKE_LOCK_SUSPEND, wake_lock_name); + + audio->out_sample_rate = 44100; + audio->out_channel_mode = 2; + audio->out_bits = 16; + audio->volume = 0x2000; + + file->private_data = audio; + audio->opened = 1; + audio->out_enabled = 0; + audio->out_prefill = 0; + audio->bytes_consumed = 0; + + audio->device_events = AUDDEV_EVT_STREAM_VOL_CHG; + audio->drv_status &= ~ADRV_STATUS_PAUSE; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->ac->session, + lpa_listner, + (void *)audio); + if (rc) { + pr_err("%s: failed to register listner\n", __func__); + goto err; + } + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_lpa_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *) audio, &audlpa_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_err("%s: debugfs_create_file failed\n", __func__); +#endif +#ifdef CONFIG_HAS_EARLYSUSPEND + audio->suspend_ctl.node.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + audio->suspend_ctl.node.resume = audlpa_resume; + audio->suspend_ctl.node.suspend = audlpa_suspend; + audio->suspend_ctl.audio = audio; + register_early_suspend(&audio->suspend_ctl.node); +#endif + for (i = 0; i < AUDLPA_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audlpa_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + pr_err("%s: event pkt alloc failed\n", __func__); + break; + } + } + pr_info("%s: audio instance 0x%08x created session[%d]\n", __func__, + (int)audio, + audio->ac->session); +done: + return rc; +err: + q6asm_audio_client_free(audio->ac); + pmem_kfree(audio->phys); + kfree(audio); + return rc; +} + +static const struct file_operations audio_lpa_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audlpa_fsync, +}; + +static dev_t audlpa_devno; +static struct class *audlpa_class; +struct audlpa_device { + const char *name; + struct device *device; + struct cdev cdev; +}; + +static struct audlpa_device *audlpa_devices; + +static void audlpa_create(struct audlpa_device *adev, const char *name, + struct device *parent, dev_t devt) +{ + struct device *dev; + int rc; + + dev = device_create(audlpa_class, parent, devt, "%s", name); + if (IS_ERR(dev)) + return; + + cdev_init(&adev->cdev, &audio_lpa_fops); + adev->cdev.owner = THIS_MODULE; + + rc = cdev_add(&adev->cdev, devt, 1); + if (rc < 0) { + device_destroy(audlpa_class, devt); + } else { + adev->device = dev; + adev->name = name; + } +} + +static int __init audio_init(void) +{ + int rc; + int n = ARRAY_SIZE(audlpa_decs); + + audlpa_devices = kzalloc(sizeof(struct audlpa_device) * n, GFP_KERNEL); + if (!audlpa_devices) + return -ENOMEM; + + audlpa_class = class_create(THIS_MODULE, "audlpa"); + if (IS_ERR(audlpa_class)) + goto fail_create_class; + + rc = alloc_chrdev_region(&audlpa_devno, 0, n, "msm_audio_lpa"); + if (rc < 0) + goto fail_alloc_region; + + for (n = 0; n < ARRAY_SIZE(audlpa_decs); n++) { + audlpa_create(audlpa_devices + n, + audlpa_decs[n].name, NULL, + MKDEV(MAJOR(audlpa_devno), n)); + } + + return 0; + +fail_alloc_region: + class_unregister(audlpa_class); + return rc; +fail_create_class: + kfree(audlpa_devices); + return -ENOMEM; +} + +static void __exit audio_exit(void) +{ + class_unregister(audlpa_class); + kfree(audlpa_devices); +} + +module_init(audio_init); +module_exit(audio_exit); + +MODULE_DESCRIPTION("MSM LPA driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/audio_lpa.h b/arch/arm/mach-msm/qdsp6v2/audio_lpa.h new file mode 100644 index 00000000000..3c99d0881fe --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_lpa.h @@ -0,0 +1,112 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef AUDIO_LPA_H +#define AUDIO_LPA_H + +#include +#include + +#define ADRV_STATUS_OBUF_GIVEN 0x00000001 +#define ADRV_STATUS_IBUF_GIVEN 0x00000002 +#define ADRV_STATUS_FSYNC 0x00000004 +#define ADRV_STATUS_PAUSE 0x00000008 + +#define SOFT_PAUSE_PERIOD 30 /* ramp up/down for 30ms */ +#define SOFT_PAUSE_STEP 2000 /* Step value 2ms or 2000us */ +enum { + SOFT_PAUSE_CURVE_LINEAR = 0, + SOFT_PAUSE_CURVE_EXP, + SOFT_PAUSE_CURVE_LOG, +}; + +struct buffer { + void *data; + unsigned size; + unsigned used; /* Input usage actual DSP produced PCM size */ + unsigned addr; +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct audlpa_suspend_ctl { + struct early_suspend node; + struct audio *audio; +}; +#endif + +struct codec_operations { + long (*ioctl)(struct file *, unsigned int, unsigned long); + int (*set_params)(void *); +}; + +struct audio { + spinlock_t dsp_lock; + + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + struct list_head out_queue; /* queue to retain output buffers */ + + struct mutex lock; + struct mutex write_lock; + wait_queue_head_t write_wait; + + struct audio_client *ac; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_bits; /* bits per sample (used by PCM decoder) */ + + int32_t phys; /* physical address of write buffer */ + + uint32_t drv_status; + int wflush; /* Write flush */ + int opened; + int out_enabled; + int out_prefill; + int running; + int stopped; /* set when stopped, cleared on flush */ + int buf_refresh; + int teos; /* valid only if tunnel mode & no data left for decoder */ + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct audlpa_suspend_ctl suspend_ctl; +#endif + + struct wake_lock wakelock; +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + + wait_queue_head_t wait; + struct list_head free_event_queue; + struct list_head event_queue; + wait_queue_head_t event_wait; + spinlock_t event_queue_lock; + struct mutex get_event_lock; + int event_abort; + + uint32_t device_events; + + struct list_head pmem_region_queue; /* protected by lock */ + + int eq_enable; + int eq_needs_commit; + uint32_t volume; + + unsigned int minor_no; + struct codec_operations codec_ops; + uint32_t buffer_size; + uint32_t buffer_count; + uint32_t bytes_consumed; +}; + +#endif /* !AUDIO_LPA_H */ diff --git a/arch/arm/mach-msm/qdsp6v2/audio_multi_aac.c b/arch/arm/mach-msm/qdsp6v2/audio_multi_aac.c new file mode 100644 index 00000000000..74239505e99 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_multi_aac.c @@ -0,0 +1,1673 @@ +/* aac audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADRV_STATUS_AIO_INTF 0x00000001 /* AIO interface */ +#define ADRV_STATUS_FSYNC 0x00000008 +#define ADRV_STATUS_PAUSE 0x00000010 + +#define TUNNEL_MODE 0x0000 +#define NON_TUNNEL_MODE 0x0001 +#define AUDAAC_EOS_SET 0x00000001 + +/* Default number of pre-allocated event packets */ +#define AUDAAC_EVENT_NUM 10 + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +struct timestamp { + unsigned long lowpart; + unsigned long highpart; +} __packed; + +struct meta_in { + unsigned char reserved[18]; + unsigned short offset; + struct timestamp ntimestamp; + unsigned int nflags; +} __packed; + +struct meta_out_dsp { + u32 offset_to_frame; + u32 frame_size; + u32 encoded_pcm_samples; + u32 msw_ts; + u32 lsw_ts; + u32 nflags; +} __packed; + +struct dec_meta_out { + unsigned int reserved[7]; + unsigned int num_of_frames; + struct meta_out_dsp meta_out_dsp[]; +} __packed; + +/* General meta field to store meta info +locally */ +union meta_data { + struct dec_meta_out meta_out; + struct meta_in meta_in; +} __packed; + +struct audaac_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audaac_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audaac_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; + unsigned long token; + void *kvaddr; + union meta_data meta_info; +}; + +struct q6audio; + +struct audaac_drv_operations { + void (*out_flush) (struct q6audio *); + void (*in_flush) (struct q6audio *); + int (*fsync)(struct q6audio *); +}; + +#define PCM_BUF_COUNT (2) +/* Buffer with meta */ +#define PCM_BUFSZ_MIN ((8192) + sizeof(struct dec_meta_out)) + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (2) +#define FRAME_SIZE ((4*1536) + sizeof(struct meta_in)) + +struct q6audio { + atomic_t in_bytes; + atomic_t in_samples; + + struct msm_audio_stream_config str_cfg; + struct msm_audio_buf_cfg buf_cfg; + struct msm_audio_config pcm_cfg; + struct msm_audio_aac_config aac_config; + + struct audio_client *ac; + + struct mutex lock; + struct mutex read_lock; + struct mutex write_lock; + struct mutex get_event_lock; + wait_queue_head_t cmd_wait; + wait_queue_head_t write_wait; + wait_queue_head_t event_wait; + spinlock_t dsp_lock; + spinlock_t event_queue_lock; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + struct list_head out_queue; /* queue to retain output buffers */ + struct list_head in_queue; /* queue to retain input buffers */ + struct list_head free_event_queue; + struct list_head event_queue; + struct list_head pmem_region_queue; /* protected by lock */ + struct audaac_drv_operations drv_ops; + union msm_audio_event_payload eos_write_payload; + + uint32_t drv_status; + int event_abort; + int eos_rsp; + int eos_flag; + int opened; + int enabled; + int stopped; + int feedback; + int rflush; /* Read flush */ + int wflush; /* Write flush */ +}; + +static int insert_eos_buf(struct q6audio *audio, + struct audaac_buffer_node *buf_node) { + struct dec_meta_out *eos_buf = buf_node->kvaddr; + eos_buf->num_of_frames = 0xFFFFFFFF; + eos_buf->meta_out_dsp[0].offset_to_frame = 0x0; + eos_buf->meta_out_dsp[0].nflags = AUDAAC_EOS_SET; + return sizeof(struct dec_meta_out) + + sizeof(eos_buf->meta_out_dsp[0]); +} + +/* Routine which updates read buffers of driver/dsp, + for flush operation as DSP output might not have proper + value set */ +static int insert_meta_data(struct q6audio *audio, + struct audaac_buffer_node *buf_node) { + struct dec_meta_out *meta_data = buf_node->kvaddr; + meta_data->num_of_frames = 0x0; + meta_data->meta_out_dsp[0].offset_to_frame = 0x0; + meta_data->meta_out_dsp[0].nflags = 0x0; + return sizeof(struct dec_meta_out) + + sizeof(meta_data->meta_out_dsp[0]); +} + +static void extract_meta_info(struct q6audio *audio, + struct audaac_buffer_node *buf_node, int dir) +{ + if (dir) { /* input buffer - Write */ + if (audio->buf_cfg.meta_info_enable) + memcpy(&buf_node->meta_info.meta_in, + (char *)buf_node->kvaddr, sizeof(struct meta_in)); + else + memset(&buf_node->meta_info.meta_in, + 0, sizeof(struct meta_in)); + pr_debug("i/p: msw_ts 0x%lx lsw_ts 0x%lx nflags 0x%8x\n", + buf_node->meta_info.meta_in.ntimestamp.highpart, + buf_node->meta_info.meta_in.ntimestamp.lowpart, + buf_node->meta_info.meta_in.nflags); + } else { /* output buffer - Read */ + memcpy((char *)buf_node->kvaddr, + &buf_node->meta_info.meta_out, + sizeof(struct dec_meta_out)); + pr_debug("o/p: msw_ts 0x%8x lsw_ts 0x%8x nflags 0x%8x," + "num_frames = %d\n", + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].msw_ts, + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].lsw_ts, + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].nflags, + ((struct dec_meta_out *)buf_node->kvaddr)->num_of_frames); + } +} + +static int audaac_pmem_lookup_vaddr(struct q6audio *audio, void *addr, + unsigned long len, struct audaac_pmem_region **region) +{ + struct audaac_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + pr_err("multiple hits for vaddr %p, len %ld\n", addr, len); + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + pr_err("\t%p, %ld --> %p\n", region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +static unsigned long audaac_pmem_fixup(struct q6audio *audio, void *addr, + unsigned long len, int ref_up, void **kvaddr) +{ + struct audaac_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audaac_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + pr_err("lookup (%p, %ld) failed\n", addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + pr_debug("found region %p ref_cnt %d\n", region, region->ref_cnt); + paddr = region->paddr + (addr - region->vaddr); + /* provide kernel virtual address for accessing meta information */ + if (kvaddr) + *kvaddr = (void *) (region->kvaddr + (addr - region->vaddr)); + return paddr; +} + +static void audaac_post_event(struct q6audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audaac_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audaac_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audaac_event), GFP_ATOMIC); + if (!e_node) { + pr_err("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static int audaac_enable(struct q6audio *audio) +{ + /* 2nd arg: 0 -> run immediately + 3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */ + return q6asm_run(audio->ac, 0x00, 0x00, 0x00); +} + +static int audaac_disable(struct q6audio *audio) +{ + int rc = 0; + if (audio->opened) { + audio->enabled = 0; + audio->opened = 0; + pr_debug("%s: inbytes[%d] insamples[%d]\n", __func__, + atomic_read(&audio->in_bytes), + atomic_read(&audio->in_samples)); + /* Close the session */ + rc = q6asm_cmd(audio->ac, CMD_CLOSE); + if (rc < 0) + pr_err("Failed to close the session rc=%d\n", rc); + audio->stopped = 1; + wake_up(&audio->write_wait); + wake_up(&audio->cmd_wait); + } + pr_debug("enabled[%d]\n", audio->enabled); + return rc; +} + +static int audaac_pause(struct q6audio *audio) +{ + int rc = 0; + + pr_debug("%s, enabled = %d\n", __func__, + audio->enabled); + if (audio->enabled) { + rc = q6asm_cmd(audio->ac, CMD_PAUSE); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, rc); + + } else + pr_err("%s: Driver not enabled\n", __func__); + return rc; +} + +static int audaac_flush(struct q6audio *audio) +{ + int rc; + + if (audio->enabled) { + /* Implicitly issue a pause to the decoder before flushing if + it is not in pause state */ + if (!(audio->drv_status & ADRV_STATUS_PAUSE)) { + rc = audaac_pause(audio); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, + rc); + else + audio->drv_status |= ADRV_STATUS_PAUSE; + } + rc = q6asm_cmd(audio->ac, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + /* Not in stop state, reenable the stream */ + if (audio->stopped == 0) { + rc = audaac_enable(audio); + if (rc) + pr_err("%s:audio re-enable failed\n", __func__); + else { + audio->enabled = 1; + if (audio->drv_status & ADRV_STATUS_PAUSE) + audio->drv_status &= ~ADRV_STATUS_PAUSE; + } + } + } + pr_debug("in_bytes %d\n", atomic_read(&audio->in_bytes)); + pr_debug("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); + return 0; +} + +static int audaac_outport_flush(struct q6audio *audio) +{ + int rc; + + rc = q6asm_cmd(audio->ac, CMD_OUT_FLUSH); + if (rc < 0) + pr_err("%s: output port flush cmd failed rc=%d\n", __func__, + rc); + return rc; +} + +static void audaac_async_read(struct q6audio *audio, + struct audaac_buffer_node *buf_node) +{ + struct audio_client *ac; + struct audio_aio_read_param param; + int rc; + + pr_debug("%s: Send read buff %p phy %lx len %d\n", __func__, buf_node, + buf_node->paddr, buf_node->buf.buf_len); + ac = audio->ac; + /* Provide address so driver can append nr frames information */ + param.paddr = buf_node->paddr + + sizeof(struct dec_meta_out); + param.len = buf_node->buf.buf_len - + sizeof(struct dec_meta_out); + param.uid = param.paddr; + /* Write command will populate paddr as token */ + buf_node->token = param.paddr; + rc = q6asm_async_read(ac, ¶m); + if (rc < 0) + pr_err("%s:failed\n", __func__); +} + +static void audaac_async_write(struct q6audio *audio, + struct audaac_buffer_node *buf_node) +{ + int rc; + struct audio_client *ac; + struct audio_aio_write_param param; + + pr_debug("%s: Send write buff %p phy %lx len %d, meta_enable = %d\n", + __func__, buf_node, buf_node->paddr, buf_node->buf.data_len, + audio->buf_cfg.meta_info_enable); + + ac = audio->ac; + /* Offset with appropriate meta */ + param.paddr = buf_node->paddr + sizeof(struct meta_in); + param.len = buf_node->buf.data_len - sizeof(struct meta_in); + param.msw_ts = buf_node->meta_info.meta_in.ntimestamp.highpart; + param.lsw_ts = buf_node->meta_info.meta_in.ntimestamp.lowpart; + /* If no meta_info enaled, indicate no time stamp valid */ + if (audio->buf_cfg.meta_info_enable) + param.flags = 0; + else + param.flags = 0xFF00; + param.uid = param.paddr; + /* Read command will populate paddr as token */ + buf_node->token = param.paddr; + rc = q6asm_async_write(ac, ¶m); + if (rc < 0) + pr_err("%s:failed\n", __func__); +} + +/* Write buffer to DSP / Handle Ack from DSP */ +static void audaac_async_write_ack(struct q6audio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audaac_buffer_node *used_buf; + + /* No active flush in progress */ + if (audio->wflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + BUG_ON(list_empty(&audio->out_queue)); + used_buf = list_first_entry(&audio->out_queue, + struct audaac_buffer_node, list); + if (token == used_buf->token) { + list_del(&used_buf->list); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + pr_debug("consumed buffer\n"); + event_payload.aio_buf = used_buf->buf; + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, + event_payload); + kfree(used_buf); + if (list_empty(&audio->out_queue) && + (audio->drv_status & ADRV_STATUS_FSYNC)) { + pr_debug("%s: list is empty, reached EOS in\ + Tunnel\n", __func__); + wake_up(&audio->write_wait); + } + } else { + pr_err("expected=%lx ret=%x\n", used_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +/* Read buffer from DSP / Handle Ack from DSP */ +static void audaac_async_read_ack(struct q6audio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audaac_buffer_node *filled_buf; + + /* No active flush in progress */ + if (audio->rflush) + return; + + /* Statistics of read */ + atomic_add(payload[2], &audio->in_bytes); + atomic_add(payload[7], &audio->in_samples); + + spin_lock_irqsave(&audio->dsp_lock, flags); + BUG_ON(list_empty(&audio->in_queue)); + filled_buf = list_first_entry(&audio->in_queue, + struct audaac_buffer_node, list); + if (token == (filled_buf->token)) { + list_del(&filled_buf->list); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + event_payload.aio_buf = filled_buf->buf; + /* Read done Buffer due to flush/normal condition + after EOS event, so append EOS buffer */ + if (audio->eos_rsp == 0x1) { + event_payload.aio_buf.data_len = + insert_eos_buf(audio, filled_buf); + /* Reset flag back to indicate eos intimated */ + audio->eos_rsp = 0; + } else { + filled_buf->meta_info.meta_out.num_of_frames = + payload[7]; + event_payload.aio_buf.data_len = payload[2] + \ + payload[3] + \ + sizeof(struct dec_meta_out); + pr_debug("nr of frames 0x%8x len=%d\n", + filled_buf->meta_info.meta_out.num_of_frames, + event_payload.aio_buf.data_len); + extract_meta_info(audio, filled_buf, 0); + audio->eos_rsp = 0; + } + audaac_post_event(audio, AUDIO_EVENT_READ_DONE, event_payload); + kfree(filled_buf); + } else { + pr_err("expected=%lx ret=%x\n", filled_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +static void q6_audaac_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6audio *audio = (struct q6audio *)priv; + union msm_audio_event_payload e_payload; + + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: + pr_debug("%s:ASM_DATA_EVENT_WRITE_DONE token = 0x%x\n", + __func__, token); + audaac_async_write_ack(audio, token, payload); + break; + case ASM_DATA_EVENT_READ_DONE: + pr_debug("%s:ASM_DATA_EVENT_READ_DONE token = 0x%x\n", + __func__, token); + audaac_async_read_ack(audio, token, payload); + break; + case ASM_DATA_CMDRSP_EOS: + /* EOS Handle */ + pr_debug("%s:ASM_DATA_CMDRSP_EOS\n", __func__); + if (audio->feedback) { /* Non-Tunnel mode */ + audio->eos_rsp = 1; + /* propagate input EOS i/p buffer, + after receiving DSP acknowledgement */ + if (audio->eos_flag && + (audio->eos_write_payload.aio_buf.buf_addr)) { + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, + audio->eos_write_payload); + memset(&audio->eos_write_payload , 0, + sizeof(union msm_audio_event_payload)); + audio->eos_flag = 0; + } + } else { /* Tunnel mode */ + audio->eos_rsp = 1; + wake_up(&audio->write_wait); + wake_up(&audio->cmd_wait); + } + break; + case ASM_DATA_CMD_MEDIA_FORMAT_UPDATE: + case ASM_STREAM_CMD_SET_ENCDEC_PARAM: + pr_debug("%s:payload0[%x] payloa1d[%x]opcode= 0x%x\n", + __func__, payload[0], payload[1], opcode); + break; + + case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY: + pr_debug("%s: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, " + "payload[0]-sr = %d, payload[1]-chl = %d, " + "payload[2] = %d, payload[3] = %d\n", __func__, + payload[0], payload[1], payload[2], + payload[3]); + pr_debug("%s: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, " + "sr(prev) = %d, chl(prev) = %d,", + __func__, audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + audio->pcm_cfg.sample_rate = payload[0]; + audio->pcm_cfg.channel_count = payload[1] & 0xFFFF; + e_payload.stream_info.chan_info = audio->pcm_cfg.channel_count; + e_payload.stream_info.sample_rate = audio->pcm_cfg.sample_rate; + audaac_post_event(audio, AUDIO_EVENT_STREAM_INFO, e_payload); + break; + default: + pr_debug("%s:Unhandled event = 0x%8x\n", __func__, opcode); + break; + } +} + +/* ------------------- device --------------------- */ +static void audaac_async_out_flush(struct q6audio *audio) +{ + struct audaac_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + unsigned long flags; + + pr_debug("%s\n", __func__); + /* EOS followed by flush, EOS response not guranteed, free EOS i/p + buffer */ + spin_lock_irqsave(&audio->dsp_lock, flags); + if (audio->eos_flag && (audio->eos_write_payload.aio_buf.buf_addr)) { + pr_debug("%s: EOS followed by flush received,acknowledge eos"\ + " i/p buffer immediately\n", __func__); + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, + audio->eos_write_payload); + memset(&audio->eos_write_payload , 0, + sizeof(union msm_audio_event_payload)); + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audaac_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, payload); + kfree(buf_node); + pr_debug("%s: Propagate WRITE_DONE during flush\n", __func__); + } +} + +static void audaac_async_in_flush(struct q6audio *audio) +{ + struct audaac_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + pr_debug("%s\n", __func__); + list_for_each_safe(ptr, next, &audio->in_queue) { + buf_node = list_entry(ptr, struct audaac_buffer_node, list); + list_del(&buf_node->list); + /* Forcefull send o/p eos buffer after flush, if no eos response + * received by dsp even after sending eos command */ + if ((audio->eos_rsp != 1) && audio->eos_flag) { + pr_debug("%s: send eos on o/p buffer during flush\n",\ + __func__); + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = + insert_eos_buf(audio, buf_node); + audio->eos_flag = 0; + } else { + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = + insert_meta_data(audio, buf_node); + } + audaac_post_event(audio, AUDIO_EVENT_READ_DONE, payload); + kfree(buf_node); + pr_debug("%s: Propagate READ_DONE during flush\n", __func__); + } +} + +static void audaac_ioport_reset(struct q6audio *audio) +{ + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + /* If fsync is in progress, make sure + * return value of fsync indicates + * abort due to flush + */ + if (audio->drv_status & ADRV_STATUS_FSYNC) { + pr_debug("fsync in progress\n"); + audio->drv_ops.out_flush(audio); + } else + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + } +} + +static int audaac_events_pending(struct q6audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audaac_reset_event_queue(struct q6audio *audio) +{ + unsigned long flags; + struct audaac_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audaac_process_event_req(struct q6audio *audio, void __user * arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audaac_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int)usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout(audio->event_wait, + audaac_events_pending + (audio), + msecs_to_jiffies + (timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible(audio->event_wait, + audaac_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audaac_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else { + pr_err("Unexpected path\n"); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return -EPERM; + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (drv_evt->event_type == AUDIO_EVENT_WRITE_DONE) { + pr_debug("posted AUDIO_EVENT_WRITE_DONE to user\n"); + mutex_lock(&audio->write_lock); + audaac_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0, 0); + mutex_unlock(&audio->write_lock); + } else if (drv_evt->event_type == AUDIO_EVENT_READ_DONE) { + pr_debug("posted AUDIO_EVENT_READ_DONE to user\n"); + mutex_lock(&audio->read_lock); + audaac_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0, 0); + mutex_unlock(&audio->read_lock); + } + + /* Some read buffer might be held up in DSP, release all + * once EOS indicated + */ + if (audio->eos_rsp && !list_empty(&audio->in_queue)) { + pr_debug("Send flush command to release read buffers"\ + "held up in DSP\n"); + audaac_flush(audio); + } + + if (copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audaac_pmem_check(struct q6audio *audio, + void *vaddr, unsigned long len) +{ + struct audaac_pmem_region *region_elt; + struct audaac_pmem_region t = {.vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + pr_err("region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audaac_pmem_add(struct q6audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audaac_pmem_region *region; + int rc = -EINVAL; + + pr_debug("%s:\n", __func__); + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = audaac_pmem_check(audio, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + pr_debug("add region paddr %lx vaddr %p, len %lu kvaddr %lx\n", + region->paddr, region->vaddr, region->len, region->kvaddr); + list_add_tail(®ion->list, &audio->pmem_region_queue); + + rc = q6asm_memory_map(audio->ac, (uint32_t) paddr, IN, (uint32_t) len, + 1); + if (rc < 0) + pr_err("%s: memory map failed\n", __func__); +end: + return rc; +} + +static int audaac_pmem_remove(struct q6audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audaac_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + pr_debug("info fd %d vaddr %p\n", info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audaac_pmem_region, list); + + if ((region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + pr_debug("region %p in use ref_cnt %d\n", + region, region->ref_cnt); + break; + } + pr_debug("remove region fd %d vaddr %p\n", + info->fd, info->vaddr); + rc = q6asm_memory_unmap(audio->ac, + (uint32_t) region->paddr, IN); + if (rc < 0) + pr_err("%s: memory unmap failed\n", __func__); + + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +/* audio -> lock must be held at this point */ +static int audaac_aio_buf_add(struct q6audio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audaac_buffer_node *buf_node; + + buf_node = kzalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + pr_debug("node %p dir %x buf_addr %p buf_len %d data_len \ + %d\n", buf_node, dir, buf_node->buf.buf_addr, + buf_node->buf.buf_len, buf_node->buf.data_len); + + buf_node->paddr = audaac_pmem_fixup(audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1, + &buf_node->kvaddr); + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (!audio->feedback && !buf_node->buf.data_len)) { + kfree(buf_node); + return -EINVAL; + } + extract_meta_info(audio, buf_node, 1); + /* Not a EOS buffer */ + if (!(buf_node->meta_info.meta_in.nflags & AUDAAC_EOS_SET)) { + spin_lock_irqsave(&audio->dsp_lock, flags); + audaac_async_write(audio, buf_node); + /* EOS buffer handled in driver */ + list_add_tail(&buf_node->list, &audio->out_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + if (buf_node->meta_info.meta_in.nflags & AUDAAC_EOS_SET) { + if (!audio->wflush) { + pr_debug("%s:Send EOS cmd at i/p\n", __func__); + /* Driver will forcefully post writedone event + * once eos ack recived from DSP + */ + audio->eos_write_payload.aio_buf = + buf_node->buf; + audio->eos_flag = 1; + audio->eos_rsp = 0; + q6asm_cmd(audio->ac, CMD_EOS); + kfree(buf_node); + } else {/* Flush in progress, send back i/p + * EOS buffer as is + */ + union msm_audio_event_payload event_payload; + event_payload.aio_buf = buf_node->buf; + audaac_post_event(audio, AUDIO_EVENT_WRITE_DONE, + event_payload); + kfree(buf_node); + } + + } + } else { + /* read */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.buf_len < PCM_BUFSZ_MIN)) { + kfree(buf_node); + return -EINVAL; + } + /* No EOS reached */ + if (!audio->eos_rsp) { + spin_lock_irqsave(&audio->dsp_lock, flags); + audaac_async_read(audio, buf_node); + /* EOS buffer handled in driver */ + list_add_tail(&buf_node->list, &audio->in_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } else { + /* EOS reached at input side fake all upcoming read buffer to + * indicate the same + */ + union msm_audio_event_payload event_payload; + event_payload.aio_buf = buf_node->buf; + event_payload.aio_buf.data_len = + insert_eos_buf(audio, buf_node); + pr_debug("%s: propagate READ_DONE as EOS done\n", + __func__); + audaac_post_event(audio, AUDIO_EVENT_READ_DONE, + event_payload); + kfree(buf_node); + } + } + return 0; +} + +/* TBD: Only useful in tunnel-mode */ +static int audmultiaac_async_fsync(struct q6audio *audio) +{ + int rc = 0; + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + pr_debug("%s:\n", __func__); + + mutex_lock(&audio->write_lock); + audio->eos_rsp = 0; + + rc = wait_event_interruptible(audio->write_wait, + (list_empty(&audio->out_queue)) || + audio->wflush || audio->stopped); + + if (rc < 0) { + pr_err("%s: wait event for list_empty failed, rc = %d\n", + __func__, rc); + goto done; + } + + rc = q6asm_cmd(audio->ac, CMD_EOS); + + if (rc < 0) + pr_err("%s: q6asm_cmd failed, rc = %d", __func__, rc); + + rc = wait_event_interruptible(audio->write_wait, + (audio->eos_rsp || audio->wflush || + audio->stopped)); + + if (rc < 0) { + pr_err("%s: wait event for eos_rsp failed, rc = %d\n", __func__, + rc); + goto done; + } + + if (audio->eos_rsp == 1) { + rc = audaac_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->enabled = 1; + } + } + + if (audio->stopped || audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +static int audmultiaac_fsync(struct file *file, int datasync) +{ + struct q6audio *audio = file->private_data; + + if (!audio->enabled || audio->feedback) + return -EINVAL; + + return audio->drv_ops.fsync(audio); +} + +static void audaac_reset_pmem_region(struct q6audio *audio) +{ + struct audaac_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audaac_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t audaac_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audaac_debug_read(struct file *file, char __user * buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0; + struct q6audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "feedback %d\n", audio->feedback); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "inqueue empty %d\n", list_empty(&audio->in_queue)); + n += scnprintf(buffer + n, debug_bufmax - n, + "outqueue empty %d\n", list_empty(&audio->out_queue)); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audaac_debug_fops = { + .read = audaac_debug_read, + .open = audaac_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + if (cmd == AUDIO_GET_EVENT) { + pr_debug("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audaac_process_event_req(audio, + (void __user *)arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ASYNC_WRITE) { + mutex_lock(&audio->write_lock); + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else { + if (audio->enabled) + rc = audaac_aio_buf_add(audio, 1, + (void __user *)arg); + else + rc = -EPERM; + } + mutex_unlock(&audio->write_lock); + return rc; + } + + if (cmd == AUDIO_ASYNC_READ) { + mutex_lock(&audio->read_lock); + if ((audio->feedback) && (audio->enabled)) + rc = audaac_aio_buf_add(audio, 0, + (void __user *)arg); + else + rc = -EPERM; + mutex_unlock(&audio->read_lock); + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + struct asm_aac_cfg aac_cfg; + uint32_t sbr_ps = 0x00; + if (audio->feedback == NON_TUNNEL_MODE) { + /* Configure PCM output block */ + rc = q6asm_enc_cfg_blk_pcm(audio->ac, + audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + if (rc < 0) { + pr_err("pcm output block config failed\n"); + break; + } + } + /* turn on both sbr and ps */ + rc = q6asm_enable_sbrps(audio->ac, sbr_ps); + if (rc < 0) + pr_err("sbr-ps enable failed\n"); + if (audio->aac_config.sbr_ps_on_flag) + aac_cfg.aot = AAC_ENC_MODE_EAAC_P; + else if (audio->aac_config.sbr_on_flag) + aac_cfg.aot = AAC_ENC_MODE_AAC_P; + else + aac_cfg.aot = AAC_ENC_MODE_AAC_LC; + + switch (audio->aac_config.format) { + case AUDIO_AAC_FORMAT_ADTS: + aac_cfg.format = 0x00; + break; + case AUDIO_AAC_FORMAT_LOAS: + aac_cfg.format = 0x01; + break; + /* ADIF, use it as RAW */ + default: + case AUDIO_AAC_FORMAT_RAW: + aac_cfg.format = 0x03; + } + aac_cfg.ep_config = audio->aac_config.ep_config; + aac_cfg.section_data_resilience = + audio->aac_config.aac_section_data_resilience_flag; + aac_cfg.scalefactor_data_resilience = + audio->aac_config.aac_scalefactor_data_resilience_flag; + aac_cfg.spectral_data_resilience = + audio->aac_config.aac_spectral_data_resilience_flag; + aac_cfg.ch_cfg = audio->aac_config.channel_configuration; + aac_cfg.sample_rate = audio->pcm_cfg.sample_rate; + + pr_debug("%s:format=%x aot=%d ch=%d sr=%d\n", + __func__, aac_cfg.format, + aac_cfg.aot, aac_cfg.ch_cfg, + aac_cfg.sample_rate); + + /* Configure Media format block */ + rc = q6asm_media_format_block_aac(audio->ac, &aac_cfg); + if (rc < 0) { + pr_err("cmd media format block failed\n"); + break; + } + rc = audaac_enable(audio); + audio->eos_rsp = 0; + audio->eos_flag = 0; + if (!rc) { + audio->enabled = 1; + } else { + audio->enabled = 0; + pr_err("Audio Start procedure failed rc=%d\n", rc); + break; + } + pr_info("%s: AUDIO_START sessionid[%d]enable[%d]\n", __func__, + audio->ac->session, + audio->enabled); + if (audio->stopped == 1) + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + pr_debug("%s: AUDIO_STOP sessionid[%d]\n", __func__, + audio->ac->session); + audio->stopped = 1; + audaac_flush(audio); + audio->enabled = 0; + audio->drv_status &= ~ADRV_STATUS_PAUSE; + if (rc < 0) { + pr_err("Audio Stop procedure failed rc=%d\n", rc); + break; + } + break; + } + case AUDIO_PAUSE: { + pr_debug("AUDIO_PAUSE %ld\n", arg); + if (arg == 1) { + rc = audaac_pause(audio); + if (rc < 0) + pr_err("%s: pause FAILED rc=%d\n", __func__, + rc); + audio->drv_status |= ADRV_STATUS_PAUSE; + } else if (arg == 0) { + if (audio->drv_status & ADRV_STATUS_PAUSE) { + rc = audaac_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", + __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->enabled = 1; + } + } + } + break; + } + case AUDIO_FLUSH: { + pr_debug("%s: AUDIO_FLUSH sessionid[%d]\n", __func__, + audio->ac->session); + audio->rflush = 1; + audio->wflush = 1; + /* Flush DSP */ + rc = audaac_flush(audio); + /* Flush input / Output buffer in software*/ + audaac_ioport_reset(audio); + if (rc < 0) { + pr_err("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } else { + audio->rflush = 0; + audio->wflush = 0; + } + audio->eos_flag = 0; + audio->eos_rsp = 0; + break; + } + case AUDIO_OUTPORT_FLUSH: + { + pr_debug("AUDIO_OUTPORT_FLUSH\n"); + rc = audaac_outport_flush(audio); + if (rc < 0) { + pr_err("%s: AUDIO_OUTPORT_FLUSH failed\n", __func__); + rc = -EINTR; + } + break; + } + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audaac_pmem_add(audio, &info); + break; + } + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("AUDIO_DEREGISTER_PMEM\n"); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audaac_pmem_remove(audio, &info); + break; + } + case AUDIO_GET_AAC_CONFIG: { + if (copy_to_user((void *)arg, &audio->aac_config, + sizeof(struct msm_audio_aac_config))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_SET_AAC_CONFIG: { + if (copy_from_user(&audio->aac_config, (void *)arg, + sizeof(struct msm_audio_aac_config))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->str_cfg.buffer_size; + cfg.buffer_count = audio->str_cfg.buffer_count; + pr_debug("GET STREAM CFG %d %d\n", cfg.buffer_size, + cfg.buffer_count); + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + pr_debug("SET STREAM CONFIG\n"); + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + if (copy_to_user((void *)arg, &audio->pcm_cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (audio->feedback != NON_TUNNEL_MODE) { + pr_err("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + audio->pcm_cfg.buffer_count = config.buffer_count; + audio->pcm_cfg.buffer_size = config.buffer_size; + audio->pcm_cfg.channel_count = config.channel_count; + audio->pcm_cfg.sample_rate = config.sample_rate; + rc = 0; + break; + } + case AUDIO_SET_BUF_CFG: { + struct msm_audio_buf_cfg cfg; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if ((audio->feedback == NON_TUNNEL_MODE) && + !cfg.meta_info_enable) { + rc = -EFAULT; + break; + } + + audio->buf_cfg.meta_info_enable = cfg.meta_info_enable; + pr_debug("%s:session id %d: Set-buf-cfg: meta[%d]", __func__, + audio->ac->session, cfg.meta_info_enable); + break; + } + case AUDIO_GET_BUF_CFG: { + pr_debug("%s:session id %d: Get-buf-cfg: meta[%d]\ + framesperbuf[%d]\n", __func__, + audio->ac->session, audio->buf_cfg.meta_info_enable, + audio->buf_cfg.frames_per_buf); + + if (copy_to_user((void *)arg, &audio->buf_cfg, + sizeof(struct msm_audio_buf_cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *)arg, &audio->ac->session, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct q6audio *audio = file->private_data; + pr_debug("%s: multi_aac dec\n", __func__); + mutex_lock(&audio->lock); + audaac_disable(audio); + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + audaac_reset_pmem_region(audio); + audio->event_abort = 1; + wake_up(&audio->event_wait); + audaac_reset_event_queue(audio); + q6asm_audio_client_free(audio->ac); + mutex_unlock(&audio->lock); + mutex_destroy(&audio->lock); + mutex_destroy(&audio->read_lock); + mutex_destroy(&audio->write_lock); + mutex_destroy(&audio->get_event_lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + pr_info("%s:multi_aac dec success\n", __func__); + return 0; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio *audio = NULL; + int rc = 0; + int i; + struct audaac_event *e_node = NULL; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_multi_aac_" + 5]; +#endif + audio = kzalloc(sizeof(struct q6audio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for aac decode driver\n"); + return -ENOMEM; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + audio->pcm_cfg.buffer_count = PCM_BUF_COUNT; + audio->pcm_cfg.sample_rate = 48000; + audio->pcm_cfg.channel_count = 2; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audaac_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio); + return -ENOMEM; + } + /* Only AIO interface */ + if (file->f_flags & O_NONBLOCK) { + pr_debug("set to aio interface\n"); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + audio->drv_ops.out_flush = audaac_async_out_flush; + audio->drv_ops.in_flush = audaac_async_in_flush; + audio->drv_ops.fsync = audmultiaac_async_fsync; + q6asm_set_io_mode(audio->ac, ASYNC_IO_MODE); + } else { + pr_err("SIO interface not supported\n"); + rc = -EACCES; + goto fail; + } + + /* open in T/NT mode */ + if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) { + rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM, + FORMAT_MPEG4_AAC); + if (rc < 0) { + pr_err("NT mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = NON_TUNNEL_MODE; + /* open AAC decoder, expected frames is always 1 + audio->buf_cfg.frames_per_buf = 0x01;*/ + audio->buf_cfg.meta_info_enable = 0x01; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + rc = q6asm_open_write(audio->ac, FORMAT_MPEG4_AAC); + if (rc < 0) { + pr_err("T mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = TUNNEL_MODE; + audio->buf_cfg.meta_info_enable = 0x00; + } else { + pr_err("Not supported mode\n"); + rc = -EACCES; + goto fail; + } + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->cmd_wait); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->event_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->in_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + + audio->drv_ops.out_flush(audio); + audio->opened = 1; + file->private_data = audio; + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_multi_aac_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *)audio, + &audaac_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + for (i = 0; i < AUDAAC_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audaac_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + pr_err("event pkt alloc failed\n"); + break; + } + } + pr_info("%s:AAC 5.1 Decoder OPEN success mode[%d]session[%d]\n", + __func__, audio->feedback, audio->ac->session); + return 0; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio); + return rc; +} + +static const struct file_operations audio_aac_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audmultiaac_fsync, +}; + +struct miscdevice audmultiaac_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_multi_aac", + .fops = &audio_aac_fops, +}; + +static int __init audio_aac_init(void) +{ + return misc_register(&audmultiaac_misc); +} + +device_initcall(audio_aac_init); diff --git a/arch/arm/mach-msm/qdsp6v2/audio_mvs.c b/arch/arm/mach-msm/qdsp6v2/audio_mvs.c new file mode 100644 index 00000000000..13ebaf465ad --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_mvs.c @@ -0,0 +1,992 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Each buffer is 20 ms, queue holds 200 ms of data. */ +#define MVS_MAX_Q_LEN 10 + +/* Length of the DSP frame info header added to the voc packet. */ +#define DSP_FRAME_HDR_LEN 1 + +enum audio_mvs_state_type { + AUDIO_MVS_CLOSED, + AUDIO_MVS_STARTED, + AUDIO_MVS_STOPPED +}; + +struct audio_mvs_buf_node { + struct list_head list; + struct msm_audio_mvs_frame frame; +}; + +struct audio_mvs_info_type { + enum audio_mvs_state_type state; + + uint32_t mvs_mode; + uint32_t rate_type; + uint32_t dtx_mode; + + struct list_head in_queue; + struct list_head free_in_queue; + + struct list_head out_queue; + struct list_head free_out_queue; + + wait_queue_head_t out_wait; + + struct mutex lock; + struct mutex in_lock; + struct mutex out_lock; + + spinlock_t dsp_lock; + + struct wake_lock suspend_lock; + struct wake_lock idle_lock; + + void *memory_chunk; +}; + +static struct audio_mvs_info_type audio_mvs_info; + +static uint32_t audio_mvs_get_rate(uint32_t mvs_mode, uint32_t rate_type) +{ + uint32_t cvs_rate; + + if (mvs_mode == MVS_MODE_AMR_WB) + cvs_rate = rate_type - MVS_AMR_MODE_0660; + else + cvs_rate = rate_type; + + pr_debug("%s: CVS rate is %d for MVS mode %d\n", + __func__, cvs_rate, mvs_mode); + + return cvs_rate; +} + +static void audio_mvs_process_ul_pkt(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data) +{ + struct audio_mvs_buf_node *buf_node = NULL; + struct audio_mvs_info_type *audio = private_data; + unsigned long dsp_flags; + + /* Copy up-link packet into out_queue. */ + spin_lock_irqsave(&audio->dsp_lock, dsp_flags); + + if (!list_empty(&audio->free_out_queue)) { + buf_node = list_first_entry(&audio->free_out_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + switch (audio->mvs_mode) { + case MVS_MODE_AMR: + case MVS_MODE_AMR_WB: { + /* Remove the DSP frame info header. Header format: + * Bits 0-3: Frame rate + * Bits 4-7: Frame type + */ + buf_node->frame.frame_type = ((*voc_pkt) & 0xF0) >> 4; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + buf_node->frame.len = pkt_len - DSP_FRAME_HDR_LEN; + + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + + list_add_tail(&buf_node->list, &audio->out_queue); + break; + } + + case MVS_MODE_IS127: { + buf_node->frame.frame_type = 0; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + buf_node->frame.len = pkt_len - DSP_FRAME_HDR_LEN; + + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + + list_add_tail(&buf_node->list, &audio->out_queue); + break; + } + + case MVS_MODE_G729A: { + /* G729 frames are 10ms each, but the DSP works with + * 20ms frames and sends two 10ms frames per buffer. + * Extract the two frames and put them in separate + * buffers. + */ + /* Remove the first DSP frame info header. + * Header format: + * Bits 0-1: Frame type + */ + buf_node->frame.frame_type = (*voc_pkt) & 0x03; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + /* There are two frames in the buffer. Length of the + * first frame: + */ + buf_node->frame.len = (pkt_len - + 2 * DSP_FRAME_HDR_LEN) / 2; + + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + voc_pkt = voc_pkt + buf_node->frame.len; + + list_add_tail(&buf_node->list, &audio->out_queue); + + /* Get another buffer from the free Q and fill in the + * second frame. + */ + if (!list_empty(&audio->free_out_queue)) { + buf_node = + list_first_entry(&audio->free_out_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + /* Remove the second DSP frame info header. + * Header format: + * Bits 0-1: Frame type + */ + buf_node->frame.frame_type = (*voc_pkt) & 0x03; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + /* There are two frames in the buffer. Length + * of the first frame: + */ + buf_node->frame.len = (pkt_len - + 2 * DSP_FRAME_HDR_LEN) / 2; + + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + + list_add_tail(&buf_node->list, + &audio->out_queue); + + } else { + /* Drop the second frame. */ + pr_err("%s: UL data dropped, read is slow\n", + __func__); + } + + break; + } + + case MVS_MODE_G711A: { + /* G711 frames are 10ms each, but the DSP works with + * 20ms frames and sends two 10ms frames per buffer. + * Extract the two frames and put them in separate + * buffers. + */ + /* Remove the first DSP frame info header. + * Header format: + * Bits 0-1: Frame type + * Bits 2-3: Frame rate + */ + buf_node->frame.frame_type = (*voc_pkt) & 0x03; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + /* There are two frames in the buffer. Length of the + * first frame: + */ + buf_node->frame.len = (pkt_len - + 2 * DSP_FRAME_HDR_LEN) / 2; + + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + voc_pkt = voc_pkt + buf_node->frame.len; + + list_add_tail(&buf_node->list, &audio->out_queue); + + /* Get another buffer from the free Q and fill in the + * second frame. + */ + if (!list_empty(&audio->free_out_queue)) { + buf_node = + list_first_entry(&audio->free_out_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + /* Remove the second DSP frame info header. + * Header format: + * Bits 0-1: Frame type + * Bits 2-3: Frame rate + */ + buf_node->frame.frame_type = (*voc_pkt) & 0x03; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + /* There are two frames in the buffer. Length + * of the second frame: + */ + buf_node->frame.len = (pkt_len - + 2 * DSP_FRAME_HDR_LEN) / 2; + + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + + list_add_tail(&buf_node->list, + &audio->out_queue); + } else { + /* Drop the second frame. */ + pr_err("%s: UL data dropped, read is slow\n", + __func__); + } + break; + } + + default: { + buf_node->frame.frame_type = 0; + + buf_node->frame.len = pkt_len; + + memcpy(&buf_node->frame.voc_pkt[0], + voc_pkt, + buf_node->frame.len); + + list_add_tail(&buf_node->list, &audio->out_queue); + } + } + } else { + pr_err("%s: UL data dropped, read is slow\n", __func__); + } + + spin_unlock_irqrestore(&audio->dsp_lock, dsp_flags); + + wake_up(&audio->out_wait); +} + +static void audio_mvs_process_dl_pkt(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data) +{ + struct audio_mvs_buf_node *buf_node = NULL; + struct audio_mvs_info_type *audio = private_data; + unsigned long dsp_flags; + + spin_lock_irqsave(&audio->dsp_lock, dsp_flags); + + if (!list_empty(&audio->in_queue)) { + uint32_t rate_type = audio_mvs_get_rate(audio->mvs_mode, + audio->rate_type); + + buf_node = list_first_entry(&audio->in_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + switch (audio->mvs_mode) { + case MVS_MODE_AMR: + case MVS_MODE_AMR_WB: { + /* Add the DSP frame info header. Header format: + * Bits 0-3: Frame rate + * Bits 4-7: Frame type + */ + *voc_pkt = ((buf_node->frame.frame_type & 0x0F) << 4) | + (rate_type & 0x0F); + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + + list_add_tail(&buf_node->list, &audio->free_in_queue); + break; + } + + case MVS_MODE_IS127: { + /* Add the DSP frame info header. Header format: + * Bits 0-3: Frame rate + * Bits 4-7: Frame type + */ + *voc_pkt = rate_type & 0x0F; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + + list_add_tail(&buf_node->list, &audio->free_in_queue); + break; + } + + case MVS_MODE_G729A: { + /* G729 frames are 10ms each but the DSP expects 20ms + * worth of data, so send two 10ms frames per buffer. + */ + /* Add the first DSP frame info header. Header format: + * Bits 0-1: Frame type + */ + *voc_pkt = buf_node->frame.frame_type & 0x03; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + voc_pkt = voc_pkt + buf_node->frame.len; + + list_add_tail(&buf_node->list, &audio->free_in_queue); + + if (!list_empty(&audio->in_queue)) { + /* Get the second buffer. */ + buf_node = list_first_entry(&audio->in_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + /* Add the second DSP frame info header. + * Header format: + * Bits 0-1: Frame type + */ + *voc_pkt = buf_node->frame.frame_type & 0x03; + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + *pkt_len = *pkt_len + + buf_node->frame.len + DSP_FRAME_HDR_LEN; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + + list_add_tail(&buf_node->list, + &audio->free_in_queue); + } else { + /* Only 10ms worth of data is available, signal + * erasure frame. + */ + *voc_pkt = MVS_G729A_ERASURE & 0x03; + + *pkt_len = *pkt_len + DSP_FRAME_HDR_LEN; + } + + break; + } + + case MVS_MODE_G711A: { + /* G711 frames are 10ms each but the DSP expects 20ms + * worth of data, so send two 10ms frames per buffer. + */ + /* Add the first DSP frame info header. Header format: + * Bits 0-1: Frame type + * Bits 2-3: Frame rate + */ + *voc_pkt = ((rate_type & 0x0F) << 2) | + (buf_node->frame.frame_type & 0x03); + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + *pkt_len = buf_node->frame.len + DSP_FRAME_HDR_LEN; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + voc_pkt = voc_pkt + buf_node->frame.len; + + list_add_tail(&buf_node->list, &audio->free_in_queue); + + if (!list_empty(&audio->in_queue)) { + /* Get the second buffer. */ + buf_node = list_first_entry(&audio->in_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + /* Add the second DSP frame info header. + * Header format: + * Bits 0-1: Frame type + * Bits 2-3: Frame rate + */ + *voc_pkt = ((rate_type & 0x0F) << 2) | + (buf_node->frame.frame_type & 0x03); + voc_pkt = voc_pkt + DSP_FRAME_HDR_LEN; + + *pkt_len = *pkt_len + + buf_node->frame.len + DSP_FRAME_HDR_LEN; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + + list_add_tail(&buf_node->list, + &audio->free_in_queue); + } else { + /* Only 10ms worth of data is available, signal + * erasure frame. + */ + *voc_pkt = ((rate_type & 0x0F) << 2) | + (MVS_G711A_ERASURE & 0x03); + + *pkt_len = *pkt_len + DSP_FRAME_HDR_LEN; + } + break; + } + + default: { + *pkt_len = buf_node->frame.len; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + + list_add_tail(&buf_node->list, &audio->free_in_queue); + } + } + } else { + *pkt_len = 0; + + pr_info("%s: No DL data available to send to MVS\n", __func__); + } + + spin_unlock_irqrestore(&audio->dsp_lock, dsp_flags); +} + +static uint32_t audio_mvs_get_media_type(uint32_t mvs_mode, uint32_t rate_type) +{ + uint32_t media_type; + + switch (mvs_mode) { + case MVS_MODE_IS127: + media_type = VSS_MEDIA_ID_EVRC_MODEM; + break; + + case MVS_MODE_AMR: + media_type = VSS_MEDIA_ID_AMR_NB_MODEM; + break; + + case MVS_MODE_LINEAR_PCM: + media_type = VSS_MEDIA_ID_PCM_NB; + break; + + case MVS_MODE_PCM: + media_type = VSS_MEDIA_ID_PCM_NB; + break; + + case MVS_MODE_AMR_WB: + media_type = VSS_MEDIA_ID_AMR_WB_MODEM; + break; + + case MVS_MODE_G729A: + media_type = VSS_MEDIA_ID_G729; + break; + + case MVS_MODE_G711A: + if (rate_type == MVS_G711A_MODE_MULAW) + media_type = VSS_MEDIA_ID_G711_MULAW; + else + media_type = VSS_MEDIA_ID_G711_ALAW; + break; + + default: + media_type = VSS_MEDIA_ID_PCM_NB; + } + + pr_debug("%s: media_type is 0x%x\n", __func__, media_type); + + return media_type; +} + +static uint32_t audio_mvs_get_network_type(uint32_t mvs_mode) +{ + uint32_t network_type; + + switch (mvs_mode) { + case MVS_MODE_IS127: + case MVS_MODE_AMR: + case MVS_MODE_LINEAR_PCM: + case MVS_MODE_PCM: + case MVS_MODE_G729A: + case MVS_MODE_G711A: + network_type = VSS_NETWORK_ID_VOIP_NB; + break; + + case MVS_MODE_AMR_WB: + network_type = VSS_NETWORK_ID_VOIP_WB; + break; + + default: + network_type = VSS_NETWORK_ID_DEFAULT; + } + + pr_debug("%s: network_type is 0x%x\n", __func__, network_type); + + return network_type; +} + +static int audio_mvs_start(struct audio_mvs_info_type *audio) +{ + int rc = 0; + + pr_info("%s\n", __func__); + + /* Prevent sleep. */ + wake_lock(&audio->suspend_lock); + wake_lock(&audio->idle_lock); + + rc = voice_set_voc_path_full(1); + + if (rc == 0) { + voice_register_mvs_cb(audio_mvs_process_ul_pkt, + audio_mvs_process_dl_pkt, + audio); + + voice_config_vocoder( + audio_mvs_get_media_type(audio->mvs_mode, audio->rate_type), + audio_mvs_get_rate(audio->mvs_mode, audio->rate_type), + audio_mvs_get_network_type(audio->mvs_mode), + audio->dtx_mode); + + audio->state = AUDIO_MVS_STARTED; + } else { + pr_err("%s: Error %d setting voc path to full\n", __func__, rc); + } + + return rc; +} + +static int audio_mvs_stop(struct audio_mvs_info_type *audio) +{ + int rc = 0; + + pr_info("%s\n", __func__); + + voice_set_voc_path_full(0); + + audio->state = AUDIO_MVS_STOPPED; + + /* Allow sleep. */ + wake_unlock(&audio->suspend_lock); + wake_unlock(&audio->idle_lock); + + return rc; +} + +static int audio_mvs_open(struct inode *inode, struct file *file) +{ + int rc = 0; + int i; + int offset = 0; + struct audio_mvs_buf_node *buf_node = NULL; + + pr_info("%s\n", __func__); + + mutex_lock(&audio_mvs_info.lock); + + /* Allocate input and output buffers. */ + audio_mvs_info.memory_chunk = kmalloc(2 * MVS_MAX_Q_LEN * + sizeof(struct audio_mvs_buf_node), + GFP_KERNEL); + + if (audio_mvs_info.memory_chunk != NULL) { + for (i = 0; i < MVS_MAX_Q_LEN; i++) { + buf_node = audio_mvs_info.memory_chunk + offset; + + list_add_tail(&buf_node->list, + &audio_mvs_info.free_in_queue); + + offset = offset + sizeof(struct audio_mvs_buf_node); + } + + for (i = 0; i < MVS_MAX_Q_LEN; i++) { + buf_node = audio_mvs_info.memory_chunk + offset; + + list_add_tail(&buf_node->list, + &audio_mvs_info.free_out_queue); + + offset = offset + sizeof(struct audio_mvs_buf_node); + } + + audio_mvs_info.state = AUDIO_MVS_STOPPED; + + file->private_data = &audio_mvs_info; + + } else { + pr_err("%s: No memory for IO buffers\n", __func__); + + rc = -ENOMEM; + } + + mutex_unlock(&audio_mvs_info.lock); + + return rc; +} + +static int audio_mvs_release(struct inode *inode, struct file *file) +{ + struct list_head *ptr = NULL; + struct list_head *next = NULL; + struct audio_mvs_buf_node *buf_node = NULL; + struct audio_mvs_info_type *audio = file->private_data; + + pr_info("%s\n", __func__); + + mutex_lock(&audio->lock); + + if (audio->state == AUDIO_MVS_STARTED) + audio_mvs_stop(audio); + + /* Free input and output memory. */ + mutex_lock(&audio->in_lock); + + list_for_each_safe(ptr, next, &audio->in_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + } + + list_for_each_safe(ptr, next, &audio->free_in_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + } + + mutex_unlock(&audio->in_lock); + + + mutex_lock(&audio->out_lock); + + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + } + + list_for_each_safe(ptr, next, &audio->free_out_queue) { + buf_node = list_entry(ptr, struct audio_mvs_buf_node, list); + list_del(&buf_node->list); + } + + mutex_unlock(&audio->out_lock); + + kfree(audio->memory_chunk); + audio->memory_chunk = NULL; + + audio->state = AUDIO_MVS_CLOSED; + + mutex_unlock(&audio->lock); + + return 0; +} + +static ssize_t audio_mvs_read(struct file *file, + char __user *buf, + size_t count, + loff_t *pos) +{ + int rc = 0; + struct audio_mvs_buf_node *buf_node = NULL; + struct audio_mvs_info_type *audio = file->private_data; + + pr_debug("%s:\n", __func__); + + rc = wait_event_interruptible_timeout(audio->out_wait, + (!list_empty(&audio->out_queue) || + audio->state == AUDIO_MVS_STOPPED), + 1 * HZ); + + if (rc > 0) { + mutex_lock(&audio->out_lock); + + if ((audio->state == AUDIO_MVS_STARTED) && + (!list_empty(&audio->out_queue))) { + + if (count >= sizeof(struct msm_audio_mvs_frame)) { + buf_node = list_first_entry(&audio->out_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + rc = copy_to_user(buf, + &buf_node->frame, + sizeof(struct msm_audio_mvs_frame)); + + if (rc == 0) { + rc = buf_node->frame.len + + sizeof(buf_node->frame.frame_type) + + sizeof(buf_node->frame.len); + } else { + pr_err("%s: Copy to user retuned %d", + __func__, rc); + + rc = -EFAULT; + } + + list_add_tail(&buf_node->list, + &audio->free_out_queue); + } else { + pr_err("%s: Read count %d < sizeof(frame) %d", + __func__, count, + sizeof(struct msm_audio_mvs_frame)); + + rc = -ENOMEM; + } + } else { + pr_err("%s: Read performed in state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->out_lock); + + } else if (rc == 0) { + pr_err("%s: No UL data available\n", __func__); + + rc = -ETIMEDOUT; + } else { + pr_err("%s: Read was interrupted\n", __func__); + + rc = -ERESTARTSYS; + } + + return rc; +} + +static ssize_t audio_mvs_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *pos) +{ + int rc = 0; + struct audio_mvs_buf_node *buf_node = NULL; + struct audio_mvs_info_type *audio = file->private_data; + + pr_debug("%s:\n", __func__); + + mutex_lock(&audio->in_lock); + + if (audio->state == AUDIO_MVS_STARTED) { + if (count <= sizeof(struct msm_audio_mvs_frame)) { + if (!list_empty(&audio->free_in_queue)) { + buf_node = + list_first_entry(&audio->free_in_queue, + struct audio_mvs_buf_node, + list); + list_del(&buf_node->list); + + rc = copy_from_user(&buf_node->frame, + buf, + count); + + list_add_tail(&buf_node->list, + &audio->in_queue); + } else { + pr_err("%s: No free DL buffs\n", __func__); + } + } else { + pr_err("%s: Write count %d < sizeof(frame) %d", + __func__, count, + sizeof(struct msm_audio_mvs_frame)); + + rc = -ENOMEM; + } + } else { + pr_err("%s: Write performed in invalid state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->in_lock); + + return rc; +} + +static long audio_mvs_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + int rc = 0; + struct audio_mvs_info_type *audio = file->private_data; + + pr_info("%s:\n", __func__); + + switch (cmd) { + case AUDIO_GET_MVS_CONFIG: { + struct msm_audio_mvs_config config; + + pr_info("%s: IOCTL GET_MVS_CONFIG\n", __func__); + + mutex_lock(&audio->lock); + + config.mvs_mode = audio->mvs_mode; + config.rate_type = audio->rate_type; + config.dtx_mode = audio->dtx_mode; + + mutex_unlock(&audio->lock); + + rc = copy_to_user((void *)arg, &config, sizeof(config)); + if (rc == 0) + rc = sizeof(config); + else + pr_err("%s: Config copy failed %d\n", __func__, rc); + + break; + } + + case AUDIO_SET_MVS_CONFIG: { + struct msm_audio_mvs_config config; + + pr_info("%s: IOCTL SET_MVS_CONFIG\n", __func__); + + rc = copy_from_user(&config, (void *)arg, sizeof(config)); + if (rc == 0) { + mutex_lock(&audio->lock); + + if (audio->state == AUDIO_MVS_STOPPED) { + audio->mvs_mode = config.mvs_mode; + audio->rate_type = config.rate_type; + audio->dtx_mode = config.dtx_mode; + } else { + pr_err("%s: Set confg called in state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->lock); + } else { + pr_err("%s: Config copy failed %d\n", __func__, rc); + } + + break; + } + + case AUDIO_START: { + pr_info("%s: IOCTL START\n", __func__); + + mutex_lock(&audio->lock); + + if (audio->state == AUDIO_MVS_STOPPED) { + rc = audio_mvs_start(audio); + + if (rc != 0) + audio_mvs_stop(audio); + } else { + pr_err("%s: Start called in invalid state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->lock); + + break; + } + + case AUDIO_STOP: { + pr_info("%s: IOCTL STOP\n", __func__); + + mutex_lock(&audio->lock); + + if (audio->state == AUDIO_MVS_STARTED) { + rc = audio_mvs_stop(audio); + } else { + pr_err("%s: Stop called in invalid state %d\n", + __func__, audio->state); + + rc = -EPERM; + } + + mutex_unlock(&audio->lock); + + break; + } + + default: { + pr_err("%s: Unknown IOCTL %d\n", __func__, cmd); + } + } + + return rc; +} + +static const struct file_operations audio_mvs_fops = { + .owner = THIS_MODULE, + .open = audio_mvs_open, + .release = audio_mvs_release, + .read = audio_mvs_read, + .write = audio_mvs_write, + .unlocked_ioctl = audio_mvs_ioctl +}; + +struct miscdevice audio_mvs_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_mvs", + .fops = &audio_mvs_fops +}; + +static int __init audio_mvs_init(void) +{ + int rc = 0; + + memset(&audio_mvs_info, 0, sizeof(audio_mvs_info)); + + init_waitqueue_head(&audio_mvs_info.out_wait); + + mutex_init(&audio_mvs_info.lock); + mutex_init(&audio_mvs_info.in_lock); + mutex_init(&audio_mvs_info.out_lock); + + spin_lock_init(&audio_mvs_info.dsp_lock); + + INIT_LIST_HEAD(&audio_mvs_info.in_queue); + INIT_LIST_HEAD(&audio_mvs_info.free_in_queue); + INIT_LIST_HEAD(&audio_mvs_info.out_queue); + INIT_LIST_HEAD(&audio_mvs_info.free_out_queue); + + wake_lock_init(&audio_mvs_info.suspend_lock, + WAKE_LOCK_SUSPEND, + "audio_mvs_suspend"); + wake_lock_init(&audio_mvs_info.idle_lock, + WAKE_LOCK_IDLE, + "audio_mvs_idle"); + + rc = misc_register(&audio_mvs_misc); + + return rc; +} + +static void __exit audio_mvs_exit(void){ + pr_info("%s:\n", __func__); + + misc_deregister(&audio_mvs_misc); +} + +module_init(audio_mvs_init); +module_exit(audio_mvs_exit); + +MODULE_DESCRIPTION("MSM MVS driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/audio_utils.c b/arch/arm/mach-msm/qdsp6v2/audio_utils.c new file mode 100644 index 00000000000..2e31ba02d8e --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_utils.c @@ -0,0 +1,639 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audio_utils.h" + +static int audio_in_pause(struct q6audio_in *audio) +{ + int rc; + + rc = q6asm_cmd(audio->ac, CMD_PAUSE); + if (rc < 0) + pr_err("%s:session id %d: pause cmd failed rc=%d\n", __func__, + audio->ac->session, rc); + + return rc; +} + +static int audio_in_flush(struct q6audio_in *audio) +{ + int rc; + + pr_debug("%s:session id %d: flush\n", __func__, audio->ac->session); + /* Implicitly issue a pause to the decoder before flushing */ + rc = audio_in_pause(audio); + if (rc < 0) { + pr_err("%s:session id %d: pause cmd failed rc=%d\n", __func__, + audio->ac->session, rc); + return rc; + } + + rc = q6asm_cmd(audio->ac, CMD_FLUSH); + if (rc < 0) { + pr_err("%s:session id %d: flush cmd failed rc=%d\n", __func__, + audio->ac->session, rc); + return rc; + } + audio->rflush = 1; + audio->wflush = 1; + memset(audio->out_frame_info, 0, sizeof(audio->out_frame_info)); + wake_up(&audio->read_wait); + /* get read_lock to ensure no more waiting read thread */ + mutex_lock(&audio->read_lock); + audio->rflush = 0; + mutex_unlock(&audio->read_lock); + wake_up(&audio->write_wait); + /* get write_lock to ensure no more waiting write thread */ + mutex_lock(&audio->write_lock); + audio->wflush = 0; + mutex_unlock(&audio->write_lock); + pr_debug("%s:session id %d: in_bytes %d\n", __func__, + audio->ac->session, atomic_read(&audio->in_bytes)); + pr_debug("%s:session id %d: in_samples %d\n", __func__, + audio->ac->session, atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); + return 0; +} + +void audio_in_get_dsp_frames(struct q6audio_in *audio, + uint32_t token, uint32_t *payload) +{ + uint32_t index; + + index = token; + pr_debug("%s:session id %d: index=%d nr frames=%d offset[%d]\n", + __func__, audio->ac->session, token, payload[7], + payload[3]); + pr_debug("%s:session id %d: timemsw=%d lsw=%d\n", __func__, + audio->ac->session, payload[4], payload[5]); + pr_debug("%s:session id %d: uflags=0x%8x uid=0x%8x\n", __func__, + audio->ac->session, payload[6], payload[8]); + pr_debug("%s:session id %d: enc frame size=0x%8x\n", __func__, + audio->ac->session, payload[2]); + + audio->out_frame_info[index][0] = payload[7]; + audio->out_frame_info[index][1] = payload[3]; + + /* statistics of read */ + atomic_add(payload[2], &audio->in_bytes); + atomic_add(payload[7], &audio->in_samples); + + if (atomic_read(&audio->out_count) <= audio->str_cfg.buffer_count) { + atomic_inc(&audio->out_count); + wake_up(&audio->read_wait); + } +} + +/* must be called with audio->lock held */ +int audio_in_enable(struct q6audio_in *audio) +{ + if (audio->enabled) + return 0; + + /* 2nd arg: 0 -> run immediately + 3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */ + return q6asm_run(audio->ac, 0x00, 0x00, 0x00); +} + +/* must be called with audio->lock held */ +int audio_in_disable(struct q6audio_in *audio) +{ + int rc = 0; + if (audio->opened) { + audio->enabled = 0; + audio->opened = 0; + pr_debug("%s:session id %d: inbytes[%d] insamples[%d]\n", + __func__, audio->ac->session, + atomic_read(&audio->in_bytes), + atomic_read(&audio->in_samples)); + + rc = q6asm_cmd(audio->ac, CMD_CLOSE); + if (rc < 0) + pr_err("%s:session id %d: Failed to close the\ + session rc=%d\n", __func__, audio->ac->session, + rc); + audio->stopped = 1; + memset(audio->out_frame_info, 0, + sizeof(audio->out_frame_info)); + wake_up(&audio->read_wait); + wake_up(&audio->write_wait); + } + pr_debug("%s:session id %d: enabled[%d]\n", __func__, + audio->ac->session, audio->enabled); + return rc; +} + +int audio_in_buf_alloc(struct q6audio_in *audio) +{ + int rc = 0; + + switch (audio->buf_alloc) { + case NO_BUF_ALLOC: + if (audio->feedback == NON_TUNNEL_MODE) { + rc = q6asm_audio_client_buf_alloc(IN, + audio->ac, + ALIGN_BUF_SIZE(audio->pcm_cfg.buffer_size), + audio->pcm_cfg.buffer_count); + if (rc < 0) { + pr_err("%s:session id %d: Buffer Alloc\ + failed\n", __func__, + audio->ac->session); + rc = -ENOMEM; + break; + } + audio->buf_alloc |= BUF_ALLOC_IN; + } + rc = q6asm_audio_client_buf_alloc(OUT, audio->ac, + ALIGN_BUF_SIZE(audio->str_cfg.buffer_size), + audio->str_cfg.buffer_count); + if (rc < 0) { + pr_err("%s:session id %d: Buffer Alloc failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENOMEM; + break; + } + audio->buf_alloc |= BUF_ALLOC_OUT; + break; + case BUF_ALLOC_IN: + rc = q6asm_audio_client_buf_alloc(OUT, audio->ac, + ALIGN_BUF_SIZE(audio->str_cfg.buffer_size), + audio->str_cfg.buffer_count); + if (rc < 0) { + pr_err("%s:session id %d: Buffer Alloc failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENOMEM; + break; + } + audio->buf_alloc |= BUF_ALLOC_OUT; + break; + case BUF_ALLOC_OUT: + if (audio->feedback == NON_TUNNEL_MODE) { + rc = q6asm_audio_client_buf_alloc(IN, audio->ac, + ALIGN_BUF_SIZE(audio->pcm_cfg.buffer_size), + audio->pcm_cfg.buffer_count); + if (rc < 0) { + pr_err("%s:session id %d: Buffer Alloc\ + failed\n", __func__, + audio->ac->session); + rc = -ENOMEM; + break; + } + audio->buf_alloc |= BUF_ALLOC_IN; + } + break; + default: + pr_debug("%s:session id %d: buf[%d]\n", __func__, + audio->ac->session, audio->buf_alloc); + } + + return rc; +} +/* ------------------- device --------------------- */ +long audio_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct q6audio_in *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_FLUSH: { + /* Make sure we're stopped and we wake any threads + * that might be blocked holding the read_lock. + * While audio->stopped read threads will always + * exit immediately. + */ + rc = audio_in_flush(audio); + if (rc < 0) + pr_err("%s:session id %d: Flush Fail rc=%d\n", + __func__, audio->ac->session, rc); + break; + } + case AUDIO_PAUSE: { + pr_debug("%s:session id %d: AUDIO_PAUSE\n", __func__, + audio->ac->session); + if (audio->enabled) + audio_in_pause(audio); + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->str_cfg.buffer_size; + cfg.buffer_count = audio->str_cfg.buffer_count; + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + pr_debug("%s:session id %d: AUDIO_GET_STREAM_CONFIG %d %d\n", + __func__, audio->ac->session, cfg.buffer_size, + cfg.buffer_count); + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + /* Minimum single frame size, + but with in maximum frames number */ + if ((cfg.buffer_size < (audio->min_frame_size+ \ + sizeof(struct meta_out_dsp))) || + (cfg.buffer_count < FRAME_NUM)) { + rc = -EINVAL; + break; + } + audio->str_cfg.buffer_size = cfg.buffer_size; + audio->str_cfg.buffer_count = cfg.buffer_count; + rc = q6asm_audio_client_buf_alloc(OUT, audio->ac, + ALIGN_BUF_SIZE(audio->str_cfg.buffer_size), + audio->str_cfg.buffer_count); + if (rc < 0) { + pr_err("%s: session id %d: Buffer Alloc failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENOMEM; + break; + } + audio->buf_alloc |= BUF_ALLOC_OUT; + rc = 0; + pr_debug("%s:session id %d: AUDIO_SET_STREAM_CONFIG %d %d\n", + __func__, audio->ac->session, + audio->str_cfg.buffer_size, + audio->str_cfg.buffer_count); + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &audio->ac->session, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + case AUDIO_SET_BUF_CFG: { + struct msm_audio_buf_cfg cfg; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if ((audio->feedback == NON_TUNNEL_MODE) && + !cfg.meta_info_enable) { + rc = -EFAULT; + break; + } + + /* Restrict the num of frames per buf to coincide with + * default buf size */ + if (cfg.frames_per_buf > audio->max_frames_per_buf) { + rc = -EFAULT; + break; + } + audio->buf_cfg.meta_info_enable = cfg.meta_info_enable; + audio->buf_cfg.frames_per_buf = cfg.frames_per_buf; + pr_debug("%s:session id %d: Set-buf-cfg: meta[%d]\ + framesperbuf[%d]\n", __func__, + audio->ac->session, cfg.meta_info_enable, + cfg.frames_per_buf); + break; + } + case AUDIO_GET_BUF_CFG: { + pr_debug("%s:session id %d: Get-buf-cfg: meta[%d]\ + framesperbuf[%d]\n", __func__, + audio->ac->session, audio->buf_cfg.meta_info_enable, + audio->buf_cfg.frames_per_buf); + + if (copy_to_user((void *)arg, &audio->buf_cfg, + sizeof(struct msm_audio_buf_cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_CONFIG: { + if (copy_to_user((void *)arg, &audio->pcm_cfg, + sizeof(struct msm_audio_config))) + rc = -EFAULT; + break; + + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config cfg; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if (audio->feedback != NON_TUNNEL_MODE) { + pr_err("%s:session id %d: Not sufficient permission to" + "change the record mode\n", __func__, + audio->ac->session); + rc = -EACCES; + break; + } + if ((cfg.buffer_count > PCM_BUF_COUNT) || + (cfg.buffer_count == 1)) + cfg.buffer_count = PCM_BUF_COUNT; + + audio->pcm_cfg.buffer_count = cfg.buffer_count; + audio->pcm_cfg.buffer_size = cfg.buffer_size; + audio->pcm_cfg.channel_count = cfg.channel_count; + audio->pcm_cfg.sample_rate = cfg.sample_rate; + rc = q6asm_audio_client_buf_alloc(IN, audio->ac, + ALIGN_BUF_SIZE(audio->pcm_cfg.buffer_size), + audio->pcm_cfg.buffer_count); + if (rc < 0) { + pr_err("%s:session id %d: Buffer Alloc failed\n", + __func__, audio->ac->session); + rc = -ENOMEM; + break; + } + audio->buf_alloc |= BUF_ALLOC_IN; + rc = 0; + pr_debug("%s:session id %d: AUDIO_SET_CONFIG %d %d\n", __func__, + audio->ac->session, audio->pcm_cfg.buffer_count, + audio->pcm_cfg.buffer_size); + break; + } + default: + /* call codec specific ioctl */ + rc = audio->enc_ioctl(file, cmd, arg); + } + mutex_unlock(&audio->lock); + return rc; +} + +ssize_t audio_in_read(struct file *file, + char __user *buf, + size_t count, loff_t *pos) +{ + struct q6audio_in *audio = file->private_data; + const char __user *start = buf; + unsigned char *data; + uint32_t offset = 0; + uint32_t size = 0; + int rc = 0; + uint32_t idx; + struct meta_out_dsp meta; + uint32_t bytes_to_copy = 0; + uint32_t mfield_size = (audio->buf_cfg.meta_info_enable == 0) ? 0 : + (sizeof(unsigned char) + + (sizeof(struct meta_out_dsp)*(audio->buf_cfg.frames_per_buf))); + + pr_debug("%s:session id %d: read - %d\n", __func__, audio->ac->session, + count); + if (!audio->enabled) + return -EFAULT; + mutex_lock(&audio->read_lock); + while (count > 0) { + rc = wait_event_interruptible( + audio->read_wait, + ((atomic_read(&audio->out_count) > 0) || + (audio->stopped) || + audio->rflush || audio->eos_rsp)); + + if (rc < 0) + break; + + if ((audio->stopped && !(atomic_read(&audio->out_count))) || + audio->rflush) { + pr_debug("%s:session id %d: driver in stop state or\ + flush,No more buf to read", __func__, + audio->ac->session); + rc = 0;/* End of File */ + break; + } + if (!(atomic_read(&audio->out_count)) && + (audio->eos_rsp == 1) && + (count >= (sizeof(unsigned char) + + sizeof(struct meta_out_dsp)))) { + unsigned char num_of_frames; + pr_info("%s:session id %d: eos %d at output\n", + __func__, audio->ac->session, audio->eos_rsp); + if (buf != start) + break; + num_of_frames = 0xFF; + if (copy_to_user(buf, &num_of_frames, + sizeof(unsigned char))) { + rc = -EFAULT; + break; + } + buf += sizeof(unsigned char); + meta.frame_size = 0xFFFF; + meta.encoded_pcm_samples = 0xFFFF; + meta.msw_ts = 0x00; + meta.lsw_ts = 0x00; + meta.nflags = AUD_EOS_SET; + audio->eos_rsp = 0; + if (copy_to_user(buf, &meta, sizeof(meta))) { + rc = -EFAULT; + break; + } + buf += sizeof(meta); + break; + } + data = (unsigned char *)q6asm_is_cpu_buf_avail(OUT, audio->ac, + &size, &idx); + if ((count >= (size + mfield_size)) && data) { + if (audio->buf_cfg.meta_info_enable) { + if (copy_to_user(buf, + &audio->out_frame_info[idx][0], + sizeof(unsigned char))) { + rc = -EFAULT; + break; + } + bytes_to_copy = + (size + audio->out_frame_info[idx][1]); + /* Number of frames information copied */ + buf += sizeof(unsigned char); + count -= sizeof(unsigned char); + } else { + offset = audio->out_frame_info[idx][1]; + bytes_to_copy = size; + } + + pr_debug("%s:session id %d: offset=%d nr of frames= %d\n", + __func__, audio->ac->session, + audio->out_frame_info[idx][1], + audio->out_frame_info[idx][0]); + + if (copy_to_user(buf, &data[offset], bytes_to_copy)) { + rc = -EFAULT; + break; + } + count -= bytes_to_copy; + buf += bytes_to_copy; + } else { + pr_err("%s:session id %d: short read data[%p]\ + bytesavail[%d]bytesrequest[%d]\n", __func__, + audio->ac->session, + data, size, count); + } + atomic_dec(&audio->out_count); + q6asm_read(audio->ac); + break; + } + mutex_unlock(&audio->read_lock); + + pr_debug("%s:session id %d: read: %d bytes\n", __func__, + audio->ac->session, (buf-start)); + if (buf > start) + return buf - start; + return rc; +} + +static int extract_meta_info(char *buf, unsigned long *msw_ts, + unsigned long *lsw_ts, unsigned int *flags) +{ + struct meta_in *meta = (struct meta_in *)buf; + *msw_ts = meta->ntimestamp.highpart; + *lsw_ts = meta->ntimestamp.lowpart; + *flags = meta->nflags; + return 0; +} + +ssize_t audio_in_write(struct file *file, + const char __user *buf, + size_t count, loff_t *pos) +{ + struct q6audio_in *audio = file->private_data; + const char __user *start = buf; + size_t xfer = 0; + char *cpy_ptr; + int rc = 0; + unsigned char *data; + uint32_t size = 0; + uint32_t idx = 0; + uint32_t nflags = 0; + unsigned long msw_ts = 0; + unsigned long lsw_ts = 0; + uint32_t mfield_size = (audio->buf_cfg.meta_info_enable == 0) ? 0 : + sizeof(struct meta_in); + + pr_debug("%s:session id %d: to write[%d]\n", __func__, + audio->ac->session, count); + if (!audio->enabled) + return -EFAULT; + mutex_lock(&audio->write_lock); + + while (count > 0) { + rc = wait_event_interruptible(audio->write_wait, + ((atomic_read(&audio->in_count) > 0) || + (audio->stopped) || + (audio->wflush))); + if (rc < 0) + break; + if (audio->stopped || audio->wflush) { + pr_debug("%s: session id %d: stop or flush\n", __func__, + audio->ac->session); + rc = -EBUSY; + break; + } + data = (unsigned char *)q6asm_is_cpu_buf_avail(IN, audio->ac, + &size, &idx); + if (!data) { + pr_debug("%s:session id %d: No buf available\n", + __func__, audio->ac->session); + continue; + } + cpy_ptr = data; + if (audio->buf_cfg.meta_info_enable) { + if (buf == start) { + /* Processing beginning of user buffer */ + if (copy_from_user(cpy_ptr, buf, mfield_size)) { + rc = -EFAULT; + break; + } + /* Check if EOS flag is set and buffer has + * contains just meta field + */ + extract_meta_info(cpy_ptr, &msw_ts, &lsw_ts, + &nflags); + buf += mfield_size; + if (count == mfield_size) { + /* send the EOS and return */ + pr_debug("%s:session id %d: send EOS\ + 0x%8x\n", __func__, + audio->ac->session, nflags); + break; + } + count -= mfield_size; + } else { + pr_debug("%s:session id %d: continuous\ + buffer\n", __func__, audio->ac->session); + } + } + xfer = (count > (audio->pcm_cfg.buffer_size)) ? + (audio->pcm_cfg.buffer_size) : count; + + if (copy_from_user(cpy_ptr, buf, xfer)) { + rc = -EFAULT; + break; + } + rc = q6asm_write(audio->ac, xfer, msw_ts, lsw_ts, 0x00); + if (rc < 0) { + rc = -EFAULT; + break; + } + atomic_dec(&audio->in_count); + count -= xfer; + buf += xfer; + } + mutex_unlock(&audio->write_lock); + pr_debug("%s:session id %d: eos_condition 0x%8x buf[0x%x]\ + start[0x%x]\n", __func__, audio->ac->session, + nflags, (int) buf, (int) start); + if (nflags & AUD_EOS_SET) { + rc = q6asm_cmd(audio->ac, CMD_EOS); + pr_info("%s:session id %d: eos %d at input\n", __func__, + audio->ac->session, audio->eos_rsp); + } + pr_debug("%s:session id %d: Written %d Avail Buf[%d]", __func__, + audio->ac->session, (buf - start - mfield_size), + atomic_read(&audio->in_count)); + if (!rc) { + if (buf > start) + return buf - start; + } + return rc; +} + +int audio_in_release(struct inode *inode, struct file *file) +{ + struct q6audio_in *audio = file->private_data; + pr_info("%s: session id %d\n", __func__, audio->ac->session); + mutex_lock(&audio->lock); + audio_in_disable(audio); + q6asm_audio_client_free(audio->ac); + mutex_unlock(&audio->lock); + kfree(audio->enc_cfg); + kfree(audio); + return 0; +} + diff --git a/arch/arm/mach-msm/qdsp6v2/audio_utils.h b/arch/arm/mach-msm/qdsp6v2/audio_utils.h new file mode 100644 index 00000000000..7a696cac01f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_utils.h @@ -0,0 +1,104 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ +#include + +#define FRAME_NUM (8) + +#define PCM_BUF_COUNT (2) + +#define AUD_EOS_SET 0x01 +#define TUNNEL_MODE 0x0000 +#define NON_TUNNEL_MODE 0x0001 + +#define NO_BUF_ALLOC 0x00 +#define BUF_ALLOC_IN 0x01 +#define BUF_ALLOC_OUT 0x02 +#define BUF_ALLOC_INOUT 0x03 +#define ALIGN_BUF_SIZE(size) ((size + 4095) & (~4095)) + +struct timestamp{ + unsigned long lowpart; + unsigned long highpart; +} __attribute__ ((packed)); + +struct meta_in{ + unsigned short offset; + struct timestamp ntimestamp; + unsigned int nflags; +} __attribute__ ((packed)); + +struct meta_out_dsp{ + u32 offset_to_frame; + u32 frame_size; + u32 encoded_pcm_samples; + u32 msw_ts; + u32 lsw_ts; + u32 nflags; +} __attribute__ ((packed)); + +struct meta_out{ + unsigned char num_of_frames; + struct meta_out_dsp meta_out_dsp[]; +} __attribute__ ((packed)); + +struct q6audio_in{ + spinlock_t dsp_lock; + atomic_t in_bytes; + atomic_t in_samples; + + struct mutex lock; + struct mutex read_lock; + struct mutex write_lock; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + struct audio_client *ac; + struct msm_audio_stream_config str_cfg; + void *enc_cfg; + struct msm_audio_buf_cfg buf_cfg; + struct msm_audio_config pcm_cfg; + void *codec_cfg; + + /* number of buffers available to read/write */ + atomic_t in_count; + atomic_t out_count; + + /* first idx: num of frames per buf, second idx: offset to frame */ + uint32_t out_frame_info[FRAME_NUM][2]; + int eos_rsp; + int opened; + int enabled; + int stopped; + int feedback; /* Flag indicates whether used + in Non Tunnel mode */ + int rflush; + int wflush; + int buf_alloc; + uint16_t min_frame_size; + uint16_t max_frames_per_buf; + long (*enc_ioctl)(struct file *, unsigned int, unsigned long); +}; + +void audio_in_get_dsp_frames(struct q6audio_in *audio, + uint32_t token, uint32_t *payload); +int audio_in_enable(struct q6audio_in *audio); +int audio_in_disable(struct q6audio_in *audio); +int audio_in_buf_alloc(struct q6audio_in *audio); +long audio_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg); +ssize_t audio_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos); +ssize_t audio_in_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos); +int audio_in_release(struct inode *inode, struct file *file); + diff --git a/arch/arm/mach-msm/qdsp6v2/audio_wma.c b/arch/arm/mach-msm/qdsp6v2/audio_wma.c new file mode 100644 index 00000000000..ec6466be066 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_wma.c @@ -0,0 +1,1585 @@ +/* wma audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADRV_STATUS_AIO_INTF 0x00000001 /* AIO interface */ +#define ADRV_STATUS_FSYNC 0x00000008 +#define ADRV_STATUS_PAUSE 0x00000010 + +#define TUNNEL_MODE 0x0000 +#define NON_TUNNEL_MODE 0x0001 +#define AUDWMA_EOS_SET 0x00000001 + +/* Default number of pre-allocated event packets */ +#define AUDWMA_EVENT_NUM 10 + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +struct timestamp { + unsigned long lowpart; + unsigned long highpart; +} __attribute__ ((packed)); + +struct meta_in { + unsigned char reserved[18]; + unsigned short offset; + struct timestamp ntimestamp; + unsigned int nflags; +} __attribute__ ((packed)); + +struct meta_out_dsp{ + u32 offset_to_frame; + u32 frame_size; + u32 encoded_pcm_samples; + u32 msw_ts; + u32 lsw_ts; + u32 nflags; +} __attribute__ ((packed)); + +struct dec_meta_out{ + unsigned int reserved[7]; + unsigned int num_of_frames; + struct meta_out_dsp meta_out_dsp[]; +} __attribute__ ((packed)); + +/* General meta field to store meta info +locally */ +union meta_data { + struct dec_meta_out meta_out; + struct meta_in meta_in; +} __attribute__ ((packed)); + +struct audwma_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audwma_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audwma_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; + unsigned long token; + void *kvaddr; + union meta_data meta_info; +}; + +struct q6audio; + +struct audwma_drv_operations { + void (*out_flush) (struct q6audio *); + void (*in_flush) (struct q6audio *); + int (*fsync)(struct q6audio *); +}; + +#define PCM_BUF_COUNT (2) +/* Buffer with meta */ +#define PCM_BUFSZ_MIN ((4*1024) + sizeof(struct dec_meta_out)) + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (2) +#define FRAME_SIZE ((4*1024) + sizeof(struct meta_in)) + +struct q6audio { + atomic_t in_bytes; + atomic_t in_samples; + + struct msm_audio_stream_config str_cfg; + struct msm_audio_buf_cfg buf_cfg; + struct msm_audio_config pcm_cfg; + struct msm_audio_wma_config_v2 wma_config; + + struct audio_client *ac; + + struct mutex lock; + struct mutex read_lock; + struct mutex write_lock; + struct mutex get_event_lock; + wait_queue_head_t cmd_wait; + wait_queue_head_t write_wait; + wait_queue_head_t event_wait; + spinlock_t dsp_lock; + spinlock_t event_queue_lock; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + struct list_head out_queue; /* queue to retain output buffers */ + struct list_head in_queue; /* queue to retain input buffers */ + struct list_head free_event_queue; + struct list_head event_queue; + struct list_head pmem_region_queue; /* protected by lock */ + struct audwma_drv_operations drv_ops; + union msm_audio_event_payload eos_write_payload; + + uint32_t drv_status; + int event_abort; + int eos_rsp; + int eos_flag; + int opened; + int enabled; + int stopped; + int feedback; + int rflush; /* Read flush */ + int wflush; /* Write flush */ +}; + +static int insert_eos_buf(struct q6audio *audio, + struct audwma_buffer_node *buf_node) { + struct dec_meta_out *eos_buf = buf_node->kvaddr; + eos_buf->num_of_frames = 0xFFFFFFFF; + eos_buf->meta_out_dsp[0].offset_to_frame = 0x0; + eos_buf->meta_out_dsp[0].nflags = AUDWMA_EOS_SET; + return sizeof(struct dec_meta_out) + + sizeof(eos_buf->meta_out_dsp[0]); +} + +/* Routine which updates read buffers of driver/dsp, + for flush operation as DSP output might not have proper + value set */ +static int insert_meta_data(struct q6audio *audio, + struct audwma_buffer_node *buf_node) { + struct dec_meta_out *meta_data = buf_node->kvaddr; + meta_data->num_of_frames = 0x0; + meta_data->meta_out_dsp[0].offset_to_frame = 0x0; + meta_data->meta_out_dsp[0].nflags = 0x0; + return sizeof(struct dec_meta_out) + + sizeof(meta_data->meta_out_dsp[0]); +} + +static void extract_meta_info(struct q6audio *audio, + struct audwma_buffer_node *buf_node, int dir) +{ + if (dir) { /* Read */ + if (audio->buf_cfg.meta_info_enable) + memcpy(&buf_node->meta_info.meta_in, + (char *)buf_node->kvaddr, sizeof(struct meta_in)); + else + memset(&buf_node->meta_info.meta_in, + 0, sizeof(struct meta_in)); + pr_debug("i/p: msw_ts 0x%lx lsw_ts 0x%lx nflags 0x%8x\n", + buf_node->meta_info.meta_in.ntimestamp.highpart, + buf_node->meta_info.meta_in.ntimestamp.lowpart, + buf_node->meta_info.meta_in.nflags); + } else { /* Write */ + memcpy((char *)buf_node->kvaddr, + &buf_node->meta_info.meta_out, + sizeof(struct dec_meta_out)); + pr_debug("o/p: msw_ts 0x%8x lsw_ts 0x%8x nflags 0x%8x\n", + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].msw_ts, + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].lsw_ts, + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].nflags); + } +} + +static int audwma_pmem_lookup_vaddr(struct q6audio *audio, void *addr, + unsigned long len, struct audwma_pmem_region **region) +{ + struct audwma_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + pr_err("multiple hits for vaddr %p, len %ld\n", addr, len); + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + pr_err("\t%p, %ld --> %p\n", region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +static unsigned long audwma_pmem_fixup(struct q6audio *audio, void *addr, + unsigned long len, int ref_up, void **kvaddr) +{ + struct audwma_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audwma_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + pr_err("lookup (%p, %ld) failed\n", addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + pr_debug("found region %p ref_cnt %d\n", region, region->ref_cnt); + paddr = region->paddr + (addr - region->vaddr); + /* provide kernel virtual address for accessing meta information */ + if (kvaddr) + *kvaddr = (void *) (region->kvaddr + (addr - region->vaddr)); + return paddr; +} + +static void audwma_post_event(struct q6audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audwma_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audwma_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audwma_event), GFP_ATOMIC); + if (!e_node) { + pr_err("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static int audwma_enable(struct q6audio *audio) +{ + /* 2nd arg: 0 -> run immediately + 3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */ + return q6asm_run(audio->ac, 0x00, 0x00, 0x00); +} + +static int audwma_disable(struct q6audio *audio) +{ + int rc = 0; + if (audio->opened) { + audio->enabled = 0; + audio->opened = 0; + pr_debug("%s: inbytes[%d] insamples[%d]\n", __func__, + atomic_read(&audio->in_bytes), + atomic_read(&audio->in_samples)); + /* Close the session */ + rc = q6asm_cmd(audio->ac, CMD_CLOSE); + if (rc < 0) + pr_err("Failed to close the session rc=%d\n", rc); + audio->stopped = 1; + wake_up(&audio->write_wait); + wake_up(&audio->cmd_wait); + } + pr_debug("enabled[%d]\n", audio->enabled); + return rc; +} + +static int audwma_pause(struct q6audio *audio) +{ + int rc = 0; + + pr_info("%s, enabled = %d\n", __func__, + audio->enabled); + if (audio->enabled) { + rc = q6asm_cmd(audio->ac, CMD_PAUSE); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, rc); + + } else + pr_err("%s: Driver not enabled\n", __func__); + return rc; +} + +static int audwma_flush(struct q6audio *audio) +{ + int rc; + + if (audio->enabled) { + /* Implicitly issue a pause to the decoder before flushing if + it is not in pause state */ + if (!(audio->drv_status & ADRV_STATUS_PAUSE)) { + rc = audwma_pause(audio); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, + rc); + else + audio->drv_status |= ADRV_STATUS_PAUSE; + } + rc = q6asm_cmd(audio->ac, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + /* Not in stop state, reenable the stream */ + if (audio->stopped == 0) { + rc = audwma_enable(audio); + if (rc) + pr_err("%s:audio re-enable failed\n", __func__); + else { + audio->enabled = 1; + if (audio->drv_status & ADRV_STATUS_PAUSE) + audio->drv_status &= ~ADRV_STATUS_PAUSE; + } + } + } + pr_debug("in_bytes %d\n", atomic_read(&audio->in_bytes)); + pr_debug("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); + return 0; +} + +static void audwma_async_read(struct q6audio *audio, + struct audwma_buffer_node *buf_node) +{ + struct audio_client *ac; + struct audio_aio_read_param param; + int rc; + + pr_debug("%s: Send read buff %p phy %lx len %d\n", __func__, buf_node, + buf_node->paddr, buf_node->buf.buf_len); + ac = audio->ac; + /* Provide address so driver can append nr frames information */ + param.paddr = buf_node->paddr + + sizeof(struct dec_meta_out); + param.len = buf_node->buf.buf_len - + sizeof(struct dec_meta_out); + param.uid = param.paddr; + /* Write command will populate paddr as token */ + buf_node->token = param.paddr; + rc = q6asm_async_read(ac, ¶m); + if (rc < 0) + pr_err("%s:failed\n", __func__); +} + +static void audwma_async_write(struct q6audio *audio, + struct audwma_buffer_node *buf_node) +{ + int rc; + struct audio_client *ac; + struct audio_aio_write_param param; + + pr_debug("%s: Send write buff %p phy %lx len %d\n", __func__, buf_node, + buf_node->paddr, buf_node->buf.data_len); + + ac = audio->ac; + /* Offset with appropriate meta */ + param.paddr = buf_node->paddr + sizeof(struct meta_in); + param.len = buf_node->buf.data_len - sizeof(struct meta_in); + param.msw_ts = buf_node->meta_info.meta_in.ntimestamp.highpart; + param.lsw_ts = buf_node->meta_info.meta_in.ntimestamp.lowpart; + /* If no meta_info enaled, indicate no time stamp valid */ + if (audio->buf_cfg.meta_info_enable) + param.flags = 0; + else + param.flags = 0xFF00; + param.uid = param.paddr; + /* Read command will populate paddr as token */ + buf_node->token = param.paddr; + rc = q6asm_async_write(ac, ¶m); + if (rc < 0) + pr_err("%s:failed\n", __func__); +} + +/* Write buffer to DSP / Handle Ack from DSP */ +static void audwma_async_write_ack(struct q6audio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audwma_buffer_node *used_buf; + + /* No active flush in progress */ + if (audio->wflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + BUG_ON(list_empty(&audio->out_queue)); + used_buf = list_first_entry(&audio->out_queue, + struct audwma_buffer_node, list); + if (token == used_buf->token) { + list_del(&used_buf->list); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + pr_debug("consumed buffer\n"); + event_payload.aio_buf = used_buf->buf; + audwma_post_event(audio, AUDIO_EVENT_WRITE_DONE, + event_payload); + kfree(used_buf); + if (list_empty(&audio->out_queue) && + (audio->drv_status & ADRV_STATUS_FSYNC)) { + pr_debug("%s: list is empty, reached EOS in\ + Tunnel\n", __func__); + wake_up(&audio->write_wait); + } + } else { + pr_err("expected=%lx ret=%x\n", used_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +/* Read buffer from DSP / Handle Ack from DSP */ +static void audwma_async_read_ack(struct q6audio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audwma_buffer_node *filled_buf; + + /* No active flush in progress */ + if (audio->rflush) + return; + + /* Statistics of read */ + atomic_add(payload[2], &audio->in_bytes); + atomic_add(payload[7], &audio->in_samples); + + spin_lock_irqsave(&audio->dsp_lock, flags); + BUG_ON(list_empty(&audio->in_queue)); + filled_buf = list_first_entry(&audio->in_queue, + struct audwma_buffer_node, list); + if (token == (filled_buf->token)) { + list_del(&filled_buf->list); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + event_payload.aio_buf = filled_buf->buf; + /* Read done Buffer due to flush/normal condition + after EOS event, so append EOS buffer */ + if (audio->eos_rsp == 0x1) { + event_payload.aio_buf.data_len = + insert_eos_buf(audio, filled_buf); + /* Reset flag back to indicate eos intimated */ + audio->eos_rsp = 0; + } else { + filled_buf->meta_info.meta_out.num_of_frames = + payload[7]; + pr_debug("nr of frames 0x%8x\n", + filled_buf->meta_info.meta_out.num_of_frames); + event_payload.aio_buf.data_len = payload[2] + \ + payload[3] + \ + sizeof(struct dec_meta_out); + extract_meta_info(audio, filled_buf, 0); + audio->eos_rsp = 0; + } + audwma_post_event(audio, AUDIO_EVENT_READ_DONE, event_payload); + kfree(filled_buf); + } else { + pr_err("expected=%lx ret=%x\n", filled_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +static void q6_audwma_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6audio *audio = (struct q6audio *)priv; + + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: + pr_debug("%s:ASM_DATA_EVENT_WRITE_DONE token = 0x%x\n", + __func__, token); + audwma_async_write_ack(audio, token, payload); + break; + case ASM_DATA_EVENT_READ_DONE: + pr_debug("%s:ASM_DATA_EVENT_READ_DONE token = 0x%x\n", + __func__, token); + audwma_async_read_ack(audio, token, payload); + break; + case ASM_DATA_CMDRSP_EOS: + /* EOS Handle */ + pr_debug("%s:ASM_DATA_CMDRSP_EOS\n", __func__); + if (audio->feedback) { /* Non-Tunnel mode */ + audio->eos_rsp = 1; + /* propagate input EOS i/p buffer, + after receiving DSP acknowledgement */ + if (audio->eos_flag && + (audio->eos_write_payload.aio_buf.buf_addr)) { + audwma_post_event(audio, AUDIO_EVENT_WRITE_DONE, + audio->eos_write_payload); + memset(&audio->eos_write_payload , 0, + sizeof(union msm_audio_event_payload)); + audio->eos_flag = 0; + } + } else { /* Tunnel mode */ + audio->eos_rsp = 1; + wake_up(&audio->write_wait); + wake_up(&audio->cmd_wait); + } + break; + default: + pr_debug("%s:Unhandled event = 0x%8x\n", __func__, opcode); + break; + } +} + +/* ------------------- device --------------------- */ +static void audwma_async_out_flush(struct q6audio *audio) +{ + struct audwma_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + unsigned long flags; + + pr_debug("%s\n", __func__); + /* EOS followed by flush, EOS response not guranteed, free EOS i/p + buffer */ + spin_lock_irqsave(&audio->dsp_lock, flags); + if (audio->eos_flag && (audio->eos_write_payload.aio_buf.buf_addr)) { + pr_debug("%s: EOS followed by flush received,acknowledge eos"\ + " i/p buffer immediately\n", __func__); + audwma_post_event(audio, AUDIO_EVENT_WRITE_DONE, + audio->eos_write_payload); + memset(&audio->eos_write_payload , 0, + sizeof(union msm_audio_event_payload)); + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audwma_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audwma_post_event(audio, AUDIO_EVENT_WRITE_DONE, payload); + kfree(buf_node); + pr_debug("%s: Propagate WRITE_DONE during flush\n", __func__); + } +} + +static void audwma_async_in_flush(struct q6audio *audio) +{ + struct audwma_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + pr_debug("%s\n", __func__); + list_for_each_safe(ptr, next, &audio->in_queue) { + buf_node = list_entry(ptr, struct audwma_buffer_node, list); + list_del(&buf_node->list); + /* Forcefull send o/p eos buffer after flush, if no eos response + * received by dsp even after sending eos command */ + if ((audio->eos_rsp != 1) && audio->eos_flag) { + pr_debug("%s: send eos on o/p buffer during flush\n",\ + __func__); + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = + insert_eos_buf(audio, buf_node); + audio->eos_flag = 0; + } else { + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = + insert_meta_data(audio, buf_node); + } + audwma_post_event(audio, AUDIO_EVENT_READ_DONE, payload); + kfree(buf_node); + pr_debug("%s: Propagate READ_DONE during flush\n", __func__); + } +} + +static void audwma_ioport_reset(struct q6audio *audio) +{ + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + /* If fsync is in progress, make sure + * return value of fsync indicates + * abort due to flush + */ + if (audio->drv_status & ADRV_STATUS_FSYNC) { + pr_debug("fsync in progress\n"); + audio->drv_ops.out_flush(audio); + } else + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + } +} + +static int audwma_events_pending(struct q6audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audwma_reset_event_queue(struct q6audio *audio) +{ + unsigned long flags; + struct audwma_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwma_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audwma_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audwma_process_event_req(struct q6audio *audio, void __user * arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audwma_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int)usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout(audio->event_wait, + audwma_events_pending + (audio), + msecs_to_jiffies + (timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible(audio->event_wait, + audwma_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwma_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else { + pr_err("Unexpected path\n"); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return -EPERM; + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (drv_evt->event_type == AUDIO_EVENT_WRITE_DONE) { + pr_debug("posted AUDIO_EVENT_WRITE_DONE to user\n"); + mutex_lock(&audio->write_lock); + audwma_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0, 0); + mutex_unlock(&audio->write_lock); + } else if (drv_evt->event_type == AUDIO_EVENT_READ_DONE) { + pr_debug("posted AUDIO_EVENT_READ_DONE to user\n"); + mutex_lock(&audio->read_lock); + audwma_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0, 0); + mutex_unlock(&audio->read_lock); + } + + /* Some read buffer might be held up in DSP,release all + Once EOS indicated*/ + if (audio->eos_rsp && !list_empty(&audio->in_queue)) { + pr_debug("Send flush command to release read buffers"\ + " held up in DSP\n"); + audwma_flush(audio); + } + + if (copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audwma_pmem_check(struct q6audio *audio, + void *vaddr, unsigned long len) +{ + struct audwma_pmem_region *region_elt; + struct audwma_pmem_region t = {.vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + pr_err("region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audwma_pmem_add(struct q6audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audwma_pmem_region *region; + int rc = -EINVAL; + + pr_debug("%s:\n", __func__); + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = audwma_pmem_check(audio, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + pr_debug("add region paddr %lx vaddr %p, len %lu kvaddr %lx\n", + region->paddr, region->vaddr, region->len, region->kvaddr); + list_add_tail(®ion->list, &audio->pmem_region_queue); + + rc = q6asm_memory_map(audio->ac, (uint32_t) paddr, IN, (uint32_t) len, + 1); + if (rc < 0) + pr_err("%s: memory map failed\n", __func__); +end: + return rc; +} + +static int audwma_pmem_remove(struct q6audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audwma_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + pr_debug("info fd %d vaddr %p\n", info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audwma_pmem_region, list); + + if ((region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + pr_debug("region %p in use ref_cnt %d\n", + region, region->ref_cnt); + break; + } + pr_debug("remove region fd %d vaddr %p\n", + info->fd, info->vaddr); + rc = q6asm_memory_unmap(audio->ac, + (uint32_t) region->paddr, IN); + if (rc < 0) + pr_err("%s: memory unmap failed\n", __func__); + + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +/* audio -> lock must be held at this point */ +static int audwma_aio_buf_add(struct q6audio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audwma_buffer_node *buf_node; + + buf_node = kzalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + pr_debug("node %p dir %x buf_addr %p buf_len %d data_len \ + %d\n", buf_node, dir, buf_node->buf.buf_addr, + buf_node->buf.buf_len, buf_node->buf.data_len); + + buf_node->paddr = audwma_pmem_fixup(audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1, + &buf_node->kvaddr); + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (!audio->feedback && !buf_node->buf.data_len)) { + kfree(buf_node); + return -EINVAL; + } + extract_meta_info(audio, buf_node, 1); + /* Not a EOS buffer */ + if (!(buf_node->meta_info.meta_in.nflags & AUDWMA_EOS_SET)) { + spin_lock_irqsave(&audio->dsp_lock, flags); + audwma_async_write(audio, buf_node); + /* EOS buffer handled in driver */ + list_add_tail(&buf_node->list, &audio->out_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + if (buf_node->meta_info.meta_in.nflags & AUDWMA_EOS_SET) { + if (!audio->wflush) { + pr_debug("%s:Send EOS cmd at i/p\n", __func__); + /* Driver will forcefully post writedone event + once eos ack recived from DSP*/ + audio->eos_write_payload.aio_buf =\ + buf_node->buf; + audio->eos_flag = 1; + audio->eos_rsp = 0; + q6asm_cmd(audio->ac, CMD_EOS); + kfree(buf_node); + } else { /* Flush in progress, send back i/p EOS buffer + as is */ + union msm_audio_event_payload event_payload; + event_payload.aio_buf = buf_node->buf; + audwma_post_event(audio, AUDIO_EVENT_WRITE_DONE, + event_payload); + kfree(buf_node); + } + } + } else { + /* read */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.buf_len < PCM_BUFSZ_MIN)) { + kfree(buf_node); + return -EINVAL; + } + /* No EOS reached */ + if (!audio->eos_rsp) { + spin_lock_irqsave(&audio->dsp_lock, flags); + audwma_async_read(audio, buf_node); + /* EOS buffer handled in driver */ + list_add_tail(&buf_node->list, &audio->in_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + /* EOS reached at input side fake all upcoming read buffer to + indicate the same */ + else { + union msm_audio_event_payload event_payload; + event_payload.aio_buf = buf_node->buf; + event_payload.aio_buf.data_len = + insert_eos_buf(audio, buf_node); + pr_debug("%s: propagate READ_DONE as EOS done\n",\ + __func__); + audwma_post_event(audio, AUDIO_EVENT_READ_DONE, + event_payload); + kfree(buf_node); + } + } + return 0; +} + +/* TBD: Only useful in tunnel-mode */ +int audwma_async_fsync(struct q6audio *audio) +{ + int rc = 0; + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + pr_info("%s:\n", __func__); + + mutex_lock(&audio->write_lock); + audio->eos_rsp = 0; + + rc = wait_event_interruptible(audio->write_wait, + (list_empty(&audio->out_queue)) || + audio->wflush || audio->stopped); + + if (rc < 0) { + pr_err("%s: wait event for list_empty failed, rc = %d\n", + __func__, rc); + goto done; + } + + rc = q6asm_cmd(audio->ac, CMD_EOS); + + if (rc < 0) + pr_err("%s: q6asm_cmd failed, rc = %d", __func__, rc); + + rc = wait_event_interruptible(audio->write_wait, + (audio->eos_rsp || audio->wflush || + audio->stopped)); + + if (rc < 0) { + pr_err("%s: wait event for eos_rsp failed, rc = %d\n", __func__, + rc); + goto done; + } + + if (audio->eos_rsp == 1) { + rc = audwma_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->enabled = 1; + } + } + + if (audio->stopped || audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +int audwma_fsync(struct file *file, int datasync) +{ + struct q6audio *audio = file->private_data; + + if (!audio->enabled || audio->feedback) + return -EINVAL; + + return audio->drv_ops.fsync(audio); +} + +static void audwma_reset_pmem_region(struct q6audio *audio) +{ + struct audwma_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audwma_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t audwma_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audwma_debug_read(struct file *file, char __user * buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0; + struct q6audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "feedback %d\n", audio->feedback); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "inqueue empty %d\n", list_empty(&audio->in_queue)); + n += scnprintf(buffer + n, debug_bufmax - n, + "outqueue empty %d\n", list_empty(&audio->out_queue)); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audwma_debug_fops = { + .read = audwma_debug_read, + .open = audwma_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + if (cmd == AUDIO_GET_EVENT) { + pr_debug("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audwma_process_event_req(audio, + (void __user *)arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ASYNC_WRITE) { + mutex_lock(&audio->write_lock); + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else { + if (audio->enabled) + rc = audwma_aio_buf_add(audio, 1, + (void __user *)arg); + else + rc = -EPERM; + } + mutex_unlock(&audio->write_lock); + return rc; + } + + if (cmd == AUDIO_ASYNC_READ) { + mutex_lock(&audio->read_lock); + if ((audio->feedback) && (audio->enabled)) + rc = audwma_aio_buf_add(audio, 0, + (void __user *)arg); + else + rc = -EPERM; + mutex_unlock(&audio->read_lock); + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + struct asm_wma_cfg wma_cfg; + if (audio->feedback == NON_TUNNEL_MODE) { + /* Configure PCM output block */ + rc = q6asm_enc_cfg_blk_pcm(audio->ac, + audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + if (rc < 0) { + pr_err("pcm output block config failed\n"); + break; + } + } + wma_cfg.format_tag = audio->wma_config.format_tag; + wma_cfg.ch_cfg = audio->wma_config.numchannels; + wma_cfg.sample_rate = audio->wma_config.samplingrate; + wma_cfg.avg_bytes_per_sec = audio->wma_config.avgbytespersecond; + wma_cfg.block_align = audio->wma_config.block_align; + wma_cfg.valid_bits_per_sample = + audio->wma_config.validbitspersample; + wma_cfg.ch_mask = audio->wma_config.channelmask; + wma_cfg.encode_opt = audio->wma_config.encodeopt; + /* Configure Media format block */ + rc = q6asm_media_format_block_wma(audio->ac, &wma_cfg); + if (rc < 0) { + pr_err("cmd media format block failed\n"); + break; + } + rc = audwma_enable(audio); + audio->eos_rsp = 0; + audio->eos_flag = 0; + if (!rc) { + audio->enabled = 1; + } else { + audio->enabled = 0; + pr_err("Audio Start procedure failed rc=%d\n", rc); + break; + } + pr_debug("AUDIO_START success enable[%d]\n", audio->enabled); + if (audio->stopped == 1) + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + pr_debug("AUDIO_STOP\n"); + audio->stopped = 1; + audwma_flush(audio); + audio->enabled = 0; + audio->drv_status &= ~ADRV_STATUS_PAUSE; + if (rc < 0) { + pr_err("Audio Stop procedure failed rc=%d\n", rc); + break; + } + break; + } + case AUDIO_PAUSE: { + pr_debug("AUDIO_PAUSE %ld\n", arg); + if (arg == 1) { + rc = audwma_pause(audio); + if (rc < 0) + pr_err("%s: pause FAILED rc=%d\n", __func__, + rc); + audio->drv_status |= ADRV_STATUS_PAUSE; + } else if (arg == 0) { + if (audio->drv_status & ADRV_STATUS_PAUSE) { + rc = audwma_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", + __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->enabled = 1; + } + } + } + break; + } + case AUDIO_FLUSH: { + pr_debug("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + /* Flush DSP */ + rc = audwma_flush(audio); + /* Flush input / Output buffer in software*/ + audwma_ioport_reset(audio); + if (rc < 0) { + pr_err("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } else { + audio->rflush = 0; + audio->wflush = 0; + } + audio->eos_flag = 0; + audio->eos_rsp = 0; + break; + } + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audwma_pmem_add(audio, &info); + break; + } + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("AUDIO_DEREGISTER_PMEM\n"); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audwma_pmem_remove(audio, &info); + break; + } + case AUDIO_GET_WMA_CONFIG_V2: { + if (copy_to_user((void *)arg, &audio->wma_config, + sizeof(struct msm_audio_wma_config_v2))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_SET_WMA_CONFIG_V2: { + if (copy_from_user(&audio->wma_config, (void *)arg, + sizeof(struct msm_audio_wma_config_v2))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->str_cfg.buffer_size; + cfg.buffer_count = audio->str_cfg.buffer_count; + pr_debug("GET STREAM CFG %d %d\n", cfg.buffer_size, + cfg.buffer_count); + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + pr_debug("SET STREAM CONFIG\n"); + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + if (copy_to_user((void *)arg, &audio->pcm_cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (audio->feedback != NON_TUNNEL_MODE) { + pr_err("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + audio->pcm_cfg.buffer_count = config.buffer_count; + audio->pcm_cfg.buffer_size = config.buffer_size; + audio->pcm_cfg.channel_count = config.channel_count; + audio->pcm_cfg.sample_rate = config.sample_rate; + rc = 0; + break; + } + case AUDIO_SET_BUF_CFG: { + struct msm_audio_buf_cfg cfg; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if ((audio->feedback == NON_TUNNEL_MODE) && + !cfg.meta_info_enable) { + rc = -EFAULT; + break; + } + + audio->buf_cfg.meta_info_enable = cfg.meta_info_enable; + pr_debug("%s:session id %d: Set-buf-cfg: meta[%d]", __func__, + audio->ac->session, cfg.meta_info_enable); + break; + } + case AUDIO_GET_BUF_CFG: { + pr_debug("%s:session id %d: Get-buf-cfg: meta[%d]\ + framesperbuf[%d]\n", __func__, + audio->ac->session, audio->buf_cfg.meta_info_enable, + audio->buf_cfg.frames_per_buf); + + if (copy_to_user((void *)arg, &audio->buf_cfg, + sizeof(struct msm_audio_buf_cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *)arg, &audio->ac->session, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct q6audio *audio = file->private_data; + mutex_lock(&audio->lock); + audwma_disable(audio); + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + audwma_reset_pmem_region(audio); + audio->event_abort = 1; + wake_up(&audio->event_wait); + audwma_reset_event_queue(audio); + q6asm_audio_client_free(audio->ac); + mutex_unlock(&audio->lock); + mutex_destroy(&audio->lock); + mutex_destroy(&audio->read_lock); + mutex_destroy(&audio->write_lock); + mutex_destroy(&audio->get_event_lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + pr_info("%s: wma_decoder success\n", __func__); + return 0; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio *audio = NULL; + int rc = 0; + int i; + struct audwma_event *e_node = NULL; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_wma_" + 5]; +#endif + audio = kzalloc(sizeof(struct q6audio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for wma decode driver\n"); + return -ENOMEM; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + audio->pcm_cfg.buffer_count = PCM_BUF_COUNT; + audio->pcm_cfg.sample_rate = 48000; + audio->pcm_cfg.channel_count = 2; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audwma_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio); + return -ENOMEM; + } + /* Only AIO interface */ + if (file->f_flags & O_NONBLOCK) { + pr_debug("set to aio interface\n"); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + audio->drv_ops.out_flush = audwma_async_out_flush; + audio->drv_ops.in_flush = audwma_async_in_flush; + audio->drv_ops.fsync = audwma_async_fsync; + q6asm_set_io_mode(audio->ac, ASYNC_IO_MODE); + } else { + pr_err("SIO interface not supported\n"); + rc = -EACCES; + goto fail; + } + + /* open in T/NT mode */ + if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) { + rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM, + FORMAT_WMA_V9); + if (rc < 0) { + pr_err("NT mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = NON_TUNNEL_MODE; + /* open WMA decoder, expected frames is always 1*/ + audio->buf_cfg.frames_per_buf = 0x01; + audio->buf_cfg.meta_info_enable = 0x01; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + rc = q6asm_open_write(audio->ac, FORMAT_WMA_V9); + if (rc < 0) { + pr_err("T mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = TUNNEL_MODE; + audio->buf_cfg.meta_info_enable = 0x00; + } else { + pr_err("Not supported mode\n"); + rc = -EACCES; + goto fail; + } + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->cmd_wait); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->event_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->in_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + + audio->drv_ops.out_flush(audio); + audio->opened = 1; + file->private_data = audio; + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_wma_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *)audio, + &audwma_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + for (i = 0; i < AUDWMA_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audwma_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + pr_err("event pkt alloc failed\n"); + break; + } + } + pr_info("%s:wma decoder open success, session_id = %d\n", __func__, + audio->ac->session); + return 0; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio); + return rc; +} + +static const struct file_operations audio_wma_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audwma_fsync, +}; + +struct miscdevice audwma_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_wma", + .fops = &audio_wma_fops, +}; + +static int __init audio_wma_init(void) +{ + return misc_register(&audwma_misc); +} + +device_initcall(audio_wma_init); diff --git a/arch/arm/mach-msm/qdsp6v2/audio_wmapro.c b/arch/arm/mach-msm/qdsp6v2/audio_wmapro.c new file mode 100644 index 00000000000..e26bec2025f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/audio_wmapro.c @@ -0,0 +1,1644 @@ +/* wmapro audio output device + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2009-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADRV_STATUS_AIO_INTF 0x00000001 /* AIO interface */ +#define ADRV_STATUS_FSYNC 0x00000008 +#define ADRV_STATUS_PAUSE 0x00000010 + +#define TUNNEL_MODE 0x0000 +#define NON_TUNNEL_MODE 0x0001 +#define AUDWMAPRO_EOS_SET 0x00000001 + +/* Default number of pre-allocated event packets */ +#define AUDWMAPRO_EVENT_NUM 10 + +#define __CONTAINS(r, v, l) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = ((__v >= __r->vaddr) && \ + (__e <= __r->vaddr + __r->len)); \ + res; \ +}) + +#define CONTAINS(r1, r2) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->vaddr, __r2->len); \ +}) + +#define IN_RANGE(r, v) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->vaddr) && \ + (__vv < (__r->vaddr + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->vaddr) __v = __r2->vaddr; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v) || IN_RANGE(__r1, __e)); \ + res; \ +}) + +struct timestamp { + unsigned long lowpart; + unsigned long highpart; +} __attribute__ ((packed)); + +struct meta_in { + unsigned char reserved[18]; + unsigned short offset; + struct timestamp ntimestamp; + unsigned int nflags; +} __attribute__ ((packed)); + +struct meta_out_dsp{ + u32 offset_to_frame; + u32 frame_size; + u32 encoded_pcm_samples; + u32 msw_ts; + u32 lsw_ts; + u32 nflags; +} __attribute__ ((packed)); + +struct dec_meta_out{ + unsigned int reserved[7]; + unsigned int num_of_frames; + struct meta_out_dsp meta_out_dsp[]; +} __attribute__ ((packed)); + +/* General meta field to store meta info +locally */ +union meta_data { + struct dec_meta_out meta_out; + struct meta_in meta_in; +} __attribute__ ((packed)); + +struct audwmapro_event { + struct list_head list; + int event_type; + union msm_audio_event_payload payload; +}; + +struct audwmapro_pmem_region { + struct list_head list; + struct file *file; + int fd; + void *vaddr; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + unsigned ref_cnt; +}; + +struct audwmapro_buffer_node { + struct list_head list; + struct msm_audio_aio_buf buf; + unsigned long paddr; + unsigned long token; + void *kvaddr; + union meta_data meta_info; +}; + +struct q6audio; + +struct audwmapro_drv_operations { + void (*out_flush) (struct q6audio *); + void (*in_flush) (struct q6audio *); + int (*fsync)(struct q6audio *); +}; + +#define PCM_BUF_COUNT (2) +/* Buffer with meta */ +#define PCM_BUFSZ_MIN ((4*1024) + sizeof(struct dec_meta_out)) + +/* FRAME_NUM must be a power of two */ +#define FRAME_NUM (2) +#define FRAME_SIZE ((4*1024) + sizeof(struct meta_in)) + +struct q6audio { + atomic_t in_bytes; + atomic_t in_samples; + + struct msm_audio_stream_config str_cfg; + struct msm_audio_buf_cfg buf_cfg; + struct msm_audio_config pcm_cfg; + struct msm_audio_wmapro_config wmapro_config; + + struct audio_client *ac; + + struct mutex lock; + struct mutex read_lock; + struct mutex write_lock; + struct mutex get_event_lock; + wait_queue_head_t cmd_wait; + wait_queue_head_t write_wait; + wait_queue_head_t event_wait; + spinlock_t dsp_lock; + spinlock_t event_queue_lock; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dentry; +#endif + struct list_head out_queue; /* queue to retain output buffers */ + struct list_head in_queue; /* queue to retain input buffers */ + struct list_head free_event_queue; + struct list_head event_queue; + struct list_head pmem_region_queue; /* protected by lock */ + struct audwmapro_drv_operations drv_ops; + union msm_audio_event_payload eos_write_payload; + + uint32_t drv_status; + int event_abort; + int eos_rsp; + int eos_flag; + int opened; + int enabled; + int stopped; + int feedback; + int rflush; /* Read flush */ + int wflush; /* Write flush */ +}; + +static int insert_eos_buf(struct q6audio *audio, + struct audwmapro_buffer_node *buf_node) { + struct dec_meta_out *eos_buf = buf_node->kvaddr; + eos_buf->num_of_frames = 0xFFFFFFFF; + eos_buf->meta_out_dsp[0].offset_to_frame = 0x0; + eos_buf->meta_out_dsp[0].nflags = AUDWMAPRO_EOS_SET; + return sizeof(struct dec_meta_out) + + sizeof(eos_buf->meta_out_dsp[0]); +} + +/* Routine which updates read buffers of driver/dsp, + for flush operation as DSP output might not have proper + value set */ +static int insert_meta_data(struct q6audio *audio, + struct audwmapro_buffer_node *buf_node) { + struct dec_meta_out *meta_data = buf_node->kvaddr; + meta_data->num_of_frames = 0x0; + meta_data->meta_out_dsp[0].offset_to_frame = 0x0; + meta_data->meta_out_dsp[0].nflags = 0x0; + return sizeof(struct dec_meta_out) + + sizeof(meta_data->meta_out_dsp[0]); +} + +static void extract_meta_info(struct q6audio *audio, + struct audwmapro_buffer_node *buf_node, int dir) +{ + if (dir) { /* Read */ + if (audio->buf_cfg.meta_info_enable) + memcpy(&buf_node->meta_info.meta_in, + (char *)buf_node->kvaddr, sizeof(struct meta_in)); + else + memset(&buf_node->meta_info.meta_in, + 0, sizeof(struct meta_in)); + pr_debug("i/p: msw_ts 0x%lx lsw_ts 0x%lx nflags 0x%8x\n", + buf_node->meta_info.meta_in.ntimestamp.highpart, + buf_node->meta_info.meta_in.ntimestamp.lowpart, + buf_node->meta_info.meta_in.nflags); + } else { /* Write */ + memcpy((char *)buf_node->kvaddr, + &buf_node->meta_info.meta_out, + sizeof(struct dec_meta_out)); + pr_debug("o/p: msw_ts 0x%8x lsw_ts 0x%8x nflags 0x%8x\n", + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].msw_ts, + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].lsw_ts, + ((struct dec_meta_out *)buf_node->kvaddr)->\ + meta_out_dsp[0].nflags); + } +} + +static int audwmapro_pmem_lookup_vaddr(struct q6audio *audio, void *addr, + unsigned long len, struct audwmapro_pmem_region **region) +{ + struct audwmapro_pmem_region *region_elt; + + int match_count = 0; + + *region = NULL; + + /* returns physical address or zero */ + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) { + /* offset since we could pass vaddr inside a registerd + * pmem buffer + */ + + match_count++; + if (!*region) + *region = region_elt; + } + } + + if (match_count > 1) { + pr_err("multiple hits for vaddr %p, len %ld\n", addr, len); + list_for_each_entry(region_elt, &audio->pmem_region_queue, + list) { + if (addr >= region_elt->vaddr && + addr < region_elt->vaddr + region_elt->len && + addr + len <= region_elt->vaddr + region_elt->len) + pr_err("\t%p, %ld --> %p\n", region_elt->vaddr, + region_elt->len, + (void *)region_elt->paddr); + } + } + + return *region ? 0 : -1; +} + +static unsigned long audwmapro_pmem_fixup(struct q6audio *audio, void *addr, + unsigned long len, int ref_up, void **kvaddr) +{ + struct audwmapro_pmem_region *region; + unsigned long paddr; + int ret; + + ret = audwmapro_pmem_lookup_vaddr(audio, addr, len, ®ion); + if (ret) { + pr_err("lookup (%p, %ld) failed\n", addr, len); + return 0; + } + if (ref_up) + region->ref_cnt++; + else + region->ref_cnt--; + pr_debug("found region %p ref_cnt %d\n", region, region->ref_cnt); + paddr = region->paddr + (addr - region->vaddr); + /* provide kernel virtual address for accessing meta information */ + if (kvaddr) + *kvaddr = (void *) (region->kvaddr + (addr - region->vaddr)); + return paddr; +} + +static void audwmapro_post_event(struct q6audio *audio, int type, + union msm_audio_event_payload payload) +{ + struct audwmapro_event *e_node = NULL; + unsigned long flags; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + + if (!list_empty(&audio->free_event_queue)) { + e_node = list_first_entry(&audio->free_event_queue, + struct audwmapro_event, list); + list_del(&e_node->list); + } else { + e_node = kmalloc(sizeof(struct audwmapro_event), GFP_ATOMIC); + if (!e_node) { + pr_err("No mem to post event %d\n", type); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return; + } + } + + e_node->event_type = type; + e_node->payload = payload; + + list_add_tail(&e_node->list, &audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + wake_up(&audio->event_wait); +} + +static int audwmapro_enable(struct q6audio *audio) +{ + /* 2nd arg: 0 -> run immediately + 3rd arg: 0 -> msw_ts, 4th arg: 0 ->lsw_ts */ + return q6asm_run(audio->ac, 0x00, 0x00, 0x00); +} + +static int audwmapro_disable(struct q6audio *audio) +{ + int rc = 0; + if (audio->opened) { + audio->enabled = 0; + audio->opened = 0; + pr_debug("%s: inbytes[%d] insamples[%d]\n", __func__, + atomic_read(&audio->in_bytes), + atomic_read(&audio->in_samples)); + /* Close the session */ + rc = q6asm_cmd(audio->ac, CMD_CLOSE); + if (rc < 0) + pr_err("Failed to close the session rc=%d\n", rc); + audio->stopped = 1; + wake_up(&audio->write_wait); + wake_up(&audio->cmd_wait); + } + pr_debug("enabled[%d]\n", audio->enabled); + return rc; +} + +static int audwmapro_pause(struct q6audio *audio) +{ + int rc = 0; + + pr_info("%s, enabled = %d\n", __func__, + audio->enabled); + if (audio->enabled) { + rc = q6asm_cmd(audio->ac, CMD_PAUSE); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, rc); + + } else + pr_err("%s: Driver not enabled\n", __func__); + return rc; +} + +static int audwmapro_flush(struct q6audio *audio) +{ + int rc; + + if (audio->enabled) { + /* Implicitly issue a pause to the decoder before flushing if + it is not in pause state */ + if (!(audio->drv_status & ADRV_STATUS_PAUSE)) { + rc = audwmapro_pause(audio); + if (rc < 0) + pr_err("%s: pause cmd failed rc=%d\n", __func__, + rc); + else + audio->drv_status |= ADRV_STATUS_PAUSE; + } + rc = q6asm_cmd(audio->ac, CMD_FLUSH); + if (rc < 0) + pr_err("%s: flush cmd failed rc=%d\n", __func__, rc); + /* Not in stop state, reenable the stream */ + if (audio->stopped == 0) { + rc = audwmapro_enable(audio); + if (rc) + pr_err("%s:audio re-enable failed\n", __func__); + else { + audio->enabled = 1; + if (audio->drv_status & ADRV_STATUS_PAUSE) + audio->drv_status &= ~ADRV_STATUS_PAUSE; + } + } + } + pr_debug("in_bytes %d\n", atomic_read(&audio->in_bytes)); + pr_debug("in_samples %d\n", atomic_read(&audio->in_samples)); + atomic_set(&audio->in_bytes, 0); + atomic_set(&audio->in_samples, 0); + return 0; +} + +static void audwmapro_async_read(struct q6audio *audio, + struct audwmapro_buffer_node *buf_node) +{ + struct audio_client *ac; + struct audio_aio_read_param param; + int rc; + + pr_debug("%s: Send read buff %p phy %lx len %d\n", __func__, buf_node, + buf_node->paddr, buf_node->buf.buf_len); + ac = audio->ac; + /* Provide address so driver can append nr frames information */ + param.paddr = buf_node->paddr + + sizeof(struct dec_meta_out); + param.len = buf_node->buf.buf_len - + sizeof(struct dec_meta_out); + param.uid = param.paddr; + /* Write command will populate paddr as token */ + buf_node->token = param.paddr; + rc = q6asm_async_read(ac, ¶m); + if (rc < 0) + pr_err("%s:failed\n", __func__); +} + +static void audwmapro_async_write(struct q6audio *audio, + struct audwmapro_buffer_node *buf_node) +{ + int rc; + struct audio_client *ac; + struct audio_aio_write_param param; + + pr_debug("%s: Send write buff %p phy %lx len %d\n", __func__, buf_node, + buf_node->paddr, buf_node->buf.data_len); + + ac = audio->ac; + /* Offset with appropriate meta */ + param.paddr = buf_node->paddr + sizeof(struct meta_in); + param.len = buf_node->buf.data_len - sizeof(struct meta_in); + param.msw_ts = buf_node->meta_info.meta_in.ntimestamp.highpart; + param.lsw_ts = buf_node->meta_info.meta_in.ntimestamp.lowpart; + /* If no meta_info enaled, indicate no time stamp valid */ + if (audio->buf_cfg.meta_info_enable) + param.flags = 0; + else + param.flags = 0xFF00; + param.uid = param.paddr; + /* Read command will populate paddr as token */ + buf_node->token = param.paddr; + rc = q6asm_async_write(ac, ¶m); + if (rc < 0) + pr_err("%s:failed\n", __func__); +} + +/* Write buffer to DSP / Handle Ack from DSP */ +static void audwmapro_async_write_ack(struct q6audio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audwmapro_buffer_node *used_buf; + + /* No active flush in progress */ + if (audio->wflush) + return; + + spin_lock_irqsave(&audio->dsp_lock, flags); + BUG_ON(list_empty(&audio->out_queue)); + used_buf = list_first_entry(&audio->out_queue, + struct audwmapro_buffer_node, list); + if (token == used_buf->token) { + list_del(&used_buf->list); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + pr_debug("consumed buffer\n"); + event_payload.aio_buf = used_buf->buf; + audwmapro_post_event(audio, AUDIO_EVENT_WRITE_DONE, + event_payload); + kfree(used_buf); + if (list_empty(&audio->out_queue) && + (audio->drv_status & ADRV_STATUS_FSYNC)) { + pr_debug("%s: list is empty, reached EOS in\ + Tunnel\n", __func__); + wake_up(&audio->write_wait); + } + } else { + pr_err("expected=%lx ret=%x\n", used_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +/* Read buffer from DSP / Handle Ack from DSP */ +static void audwmapro_async_read_ack(struct q6audio *audio, uint32_t token, + uint32_t *payload) +{ + unsigned long flags; + union msm_audio_event_payload event_payload; + struct audwmapro_buffer_node *filled_buf; + + /* No active flush in progress */ + if (audio->rflush) + return; + + /* Statistics of read */ + atomic_add(payload[2], &audio->in_bytes); + atomic_add(payload[7], &audio->in_samples); + + spin_lock_irqsave(&audio->dsp_lock, flags); + BUG_ON(list_empty(&audio->in_queue)); + filled_buf = list_first_entry(&audio->in_queue, + struct audwmapro_buffer_node, list); + if (token == (filled_buf->token)) { + list_del(&filled_buf->list); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + event_payload.aio_buf = filled_buf->buf; + /* Read done Buffer due to flush/normal condition + after EOS event, so append EOS buffer */ + if (audio->eos_rsp == 0x1) { + event_payload.aio_buf.data_len = + insert_eos_buf(audio, filled_buf); + /* Reset flag back to indicate eos intimated */ + audio->eos_rsp = 0; + } else { + filled_buf->meta_info.meta_out.num_of_frames = + payload[7]; + pr_debug("nr of frames 0x%8x\n", + filled_buf->meta_info.meta_out.num_of_frames); + event_payload.aio_buf.data_len = payload[2] + \ + payload[3] + \ + sizeof(struct dec_meta_out); + extract_meta_info(audio, filled_buf, 0); + audio->eos_rsp = 0; + } + audwmapro_post_event(audio, AUDIO_EVENT_READ_DONE, + event_payload); + kfree(filled_buf); + } else { + pr_err("expected=%lx ret=%x\n", filled_buf->token, token); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } +} + +static void q6_audwmapro_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6audio *audio = (struct q6audio *)priv; + + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: + pr_debug("%s:ASM_DATA_EVENT_WRITE_DONE token = 0x%x\n", + __func__, token); + audwmapro_async_write_ack(audio, token, payload); + break; + case ASM_DATA_EVENT_READ_DONE: + pr_debug("%s:ASM_DATA_EVENT_READ_DONE token = 0x%x\n", + __func__, token); + audwmapro_async_read_ack(audio, token, payload); + break; + case ASM_DATA_CMDRSP_EOS: + /* EOS Handle */ + pr_debug("%s:ASM_DATA_CMDRSP_EOS\n", __func__); + if (audio->feedback) { /* Non-Tunnel mode */ + audio->eos_rsp = 1; + /* propagate input EOS i/p buffer, + after receiving DSP acknowledgement */ + if (audio->eos_flag && + (audio->eos_write_payload.aio_buf.buf_addr)) { + audwmapro_post_event(audio, + AUDIO_EVENT_WRITE_DONE, + audio->eos_write_payload); + memset(&audio->eos_write_payload , 0, + sizeof(union msm_audio_event_payload)); + audio->eos_flag = 0; + } + } else { /* Tunnel mode */ + audio->eos_rsp = 1; + wake_up(&audio->write_wait); + wake_up(&audio->cmd_wait); + } + break; + default: + pr_debug("%s:Unhandled event = 0x%8x\n", __func__, opcode); + break; + } +} + +/* ------------------- device --------------------- */ +static void audwmapro_async_out_flush(struct q6audio *audio) +{ + struct audwmapro_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + unsigned long flags; + + pr_debug("%s\n", __func__); + /* EOS followed by flush, EOS response not guranteed, free EOS i/p + buffer */ + spin_lock_irqsave(&audio->dsp_lock, flags); + if (audio->eos_flag && (audio->eos_write_payload.aio_buf.buf_addr)) { + pr_debug("%s: EOS followed by flush received,acknowledge eos"\ + " i/p buffer immediately\n", __func__); + audwmapro_post_event(audio, AUDIO_EVENT_WRITE_DONE, + audio->eos_write_payload); + memset(&audio->eos_write_payload , 0, + sizeof(union msm_audio_event_payload)); + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); + list_for_each_safe(ptr, next, &audio->out_queue) { + buf_node = list_entry(ptr, struct audwmapro_buffer_node, list); + list_del(&buf_node->list); + payload.aio_buf = buf_node->buf; + audwmapro_post_event(audio, AUDIO_EVENT_WRITE_DONE, payload); + kfree(buf_node); + pr_debug("%s: Propagate WRITE_DONE during flush\n", __func__); + } +} + +static void audwmapro_async_in_flush(struct q6audio *audio) +{ + struct audwmapro_buffer_node *buf_node; + struct list_head *ptr, *next; + union msm_audio_event_payload payload; + + pr_debug("%s\n", __func__); + list_for_each_safe(ptr, next, &audio->in_queue) { + buf_node = list_entry(ptr, struct audwmapro_buffer_node, list); + list_del(&buf_node->list); + /* Forcefull send o/p eos buffer after flush, if no eos response + * received by dsp even after sending eos command */ + if ((audio->eos_rsp != 1) && audio->eos_flag) { + pr_debug("%s: send eos on o/p buffer during flush\n",\ + __func__); + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = + insert_eos_buf(audio, buf_node); + audio->eos_flag = 0; + } else { + payload.aio_buf = buf_node->buf; + payload.aio_buf.data_len = + insert_meta_data(audio, buf_node); + } + audwmapro_post_event(audio, AUDIO_EVENT_READ_DONE, payload); + kfree(buf_node); + pr_debug("%s: Propagate READ_DONE during flush\n", __func__); + } +} + +static void audwmapro_ioport_reset(struct q6audio *audio) +{ + if (audio->drv_status & ADRV_STATUS_AIO_INTF) { + /* If fsync is in progress, make sure + * return value of fsync indicates + * abort due to flush + */ + if (audio->drv_status & ADRV_STATUS_FSYNC) { + pr_debug("fsync in progress\n"); + audio->drv_ops.out_flush(audio); + } else + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + } +} + +static int audwmapro_events_pending(struct q6audio *audio) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + empty = !list_empty(&audio->event_queue); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return empty || audio->event_abort; +} + +static void audwmapro_reset_event_queue(struct q6audio *audio) +{ + unsigned long flags; + struct audwmapro_event *drv_evt; + struct list_head *ptr, *next; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + list_for_each_safe(ptr, next, &audio->event_queue) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwmapro_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + list_for_each_safe(ptr, next, &audio->free_event_queue) { + drv_evt = list_first_entry(&audio->free_event_queue, + struct audwmapro_event, list); + list_del(&drv_evt->list); + kfree(drv_evt); + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + return; +} + +static long audwmapro_process_event_req(struct q6audio *audio, + void __user *arg) +{ + long rc; + struct msm_audio_event usr_evt; + struct audwmapro_event *drv_evt = NULL; + int timeout; + unsigned long flags; + + if (copy_from_user(&usr_evt, arg, sizeof(struct msm_audio_event))) + return -EFAULT; + + timeout = (int)usr_evt.timeout_ms; + + if (timeout > 0) { + rc = wait_event_interruptible_timeout(audio->event_wait, + audwmapro_events_pending + (audio), + msecs_to_jiffies + (timeout)); + if (rc == 0) + return -ETIMEDOUT; + } else { + rc = wait_event_interruptible(audio->event_wait, + audwmapro_events_pending(audio)); + } + + if (rc < 0) + return rc; + + if (audio->event_abort) { + audio->event_abort = 0; + return -ENODEV; + } + + rc = 0; + + spin_lock_irqsave(&audio->event_queue_lock, flags); + if (!list_empty(&audio->event_queue)) { + drv_evt = list_first_entry(&audio->event_queue, + struct audwmapro_event, list); + list_del(&drv_evt->list); + } + if (drv_evt) { + usr_evt.event_type = drv_evt->event_type; + usr_evt.event_payload = drv_evt->payload; + list_add_tail(&drv_evt->list, &audio->free_event_queue); + } else { + pr_err("Unexpected path\n"); + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + return -EPERM; + } + spin_unlock_irqrestore(&audio->event_queue_lock, flags); + + if (drv_evt->event_type == AUDIO_EVENT_WRITE_DONE) { + pr_debug("posted AUDIO_EVENT_WRITE_DONE to user\n"); + mutex_lock(&audio->write_lock); + audwmapro_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0, 0); + mutex_unlock(&audio->write_lock); + } else if (drv_evt->event_type == AUDIO_EVENT_READ_DONE) { + pr_debug("posted AUDIO_EVENT_READ_DONE to user\n"); + mutex_lock(&audio->read_lock); + audwmapro_pmem_fixup(audio, drv_evt->payload.aio_buf.buf_addr, + drv_evt->payload.aio_buf.buf_len, 0, 0); + mutex_unlock(&audio->read_lock); + } + + /* Some read buffer might be held up in DSP,release all + Once EOS indicated*/ + if (audio->eos_rsp && !list_empty(&audio->in_queue)) { + pr_debug("Send flush command to release read buffers"\ + " held up in DSP\n"); + audwmapro_flush(audio); + } + + if (copy_to_user(arg, &usr_evt, sizeof(usr_evt))) + rc = -EFAULT; + + return rc; +} + +static int audwmapro_pmem_check(struct q6audio *audio, + void *vaddr, unsigned long len) +{ + struct audwmapro_pmem_region *region_elt; + struct audwmapro_pmem_region t = {.vaddr = vaddr, .len = len }; + + list_for_each_entry(region_elt, &audio->pmem_region_queue, list) { + if (CONTAINS(region_elt, &t) || CONTAINS(&t, region_elt) || + OVERLAPS(region_elt, &t)) { + pr_err("region (vaddr %p len %ld)" + " clashes with registered region" + " (vaddr %p paddr %p len %ld)\n", + vaddr, len, + region_elt->vaddr, + (void *)region_elt->paddr, region_elt->len); + return -EINVAL; + } + } + + return 0; +} + +static int audwmapro_pmem_add(struct q6audio *audio, + struct msm_audio_pmem_info *info) +{ + unsigned long paddr, kvaddr, len; + struct file *file; + struct audwmapro_pmem_region *region; + int rc = -EINVAL; + + pr_debug("%s\n", __func__); + region = kmalloc(sizeof(*region), GFP_KERNEL); + + if (!region) { + rc = -ENOMEM; + goto end; + } + + if (get_pmem_file(info->fd, &paddr, &kvaddr, &len, &file)) { + kfree(region); + goto end; + } + + rc = audwmapro_pmem_check(audio, info->vaddr, len); + if (rc < 0) { + put_pmem_file(file); + kfree(region); + goto end; + } + + region->vaddr = info->vaddr; + region->fd = info->fd; + region->paddr = paddr; + region->kvaddr = kvaddr; + region->len = len; + region->file = file; + region->ref_cnt = 0; + pr_debug("add region paddr %lx vaddr %p, len %lu kvaddr %lx\n", + region->paddr, region->vaddr, region->len, region->kvaddr); + list_add_tail(®ion->list, &audio->pmem_region_queue); + + rc = q6asm_memory_map(audio->ac, (uint32_t) paddr, IN, (uint32_t) len, + 1); + if (rc < 0) + pr_err("%s: memory map failed\n", __func__); +end: + return rc; +} + +static int audwmapro_pmem_remove(struct q6audio *audio, + struct msm_audio_pmem_info *info) +{ + struct audwmapro_pmem_region *region; + struct list_head *ptr, *next; + int rc = -EINVAL; + + pr_debug("info fd %d vaddr %p\n", info->fd, info->vaddr); + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audwmapro_pmem_region, list); + + if ((region->fd == info->fd) && + (region->vaddr == info->vaddr)) { + if (region->ref_cnt) { + pr_debug("region %p in use ref_cnt %d\n", + region, region->ref_cnt); + break; + } + pr_debug("remove region fd %d vaddr %p\n", + info->fd, info->vaddr); + rc = q6asm_memory_unmap(audio->ac, + (uint32_t) region->paddr, IN); + if (rc < 0) + pr_err("%s: memory unmap failed\n", __func__); + + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + rc = 0; + break; + } + } + + return rc; +} + +/* audio -> lock must be held at this point */ +static int audwmapro_aio_buf_add(struct q6audio *audio, unsigned dir, + void __user *arg) +{ + unsigned long flags; + struct audwmapro_buffer_node *buf_node; + + buf_node = kzalloc(sizeof(*buf_node), GFP_KERNEL); + + if (!buf_node) + return -ENOMEM; + + if (copy_from_user(&buf_node->buf, arg, sizeof(buf_node->buf))) { + kfree(buf_node); + return -EFAULT; + } + + pr_debug("node %p dir %x buf_addr %p buf_len %d data_len \ + %d\n", buf_node, dir, buf_node->buf.buf_addr, + buf_node->buf.buf_len, buf_node->buf.data_len); + + buf_node->paddr = audwmapro_pmem_fixup(audio, buf_node->buf.buf_addr, + buf_node->buf.buf_len, 1, + &buf_node->kvaddr); + if (dir) { + /* write */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (!audio->feedback && !buf_node->buf.data_len)) { + kfree(buf_node); + return -EINVAL; + } + extract_meta_info(audio, buf_node, 1); + /* Not a EOS buffer */ + if (!(buf_node->meta_info.meta_in.nflags & AUDWMAPRO_EOS_SET)) { + spin_lock_irqsave(&audio->dsp_lock, flags); + audwmapro_async_write(audio, buf_node); + /* EOS buffer handled in driver */ + list_add_tail(&buf_node->list, &audio->out_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + if (buf_node->meta_info.meta_in.nflags & AUDWMAPRO_EOS_SET) { + if (!audio->wflush) { + pr_debug("%s:Send EOS cmd at i/p\n", __func__); + /* Driver will forcefully post writedone event + once eos ack recived from DSP*/ + audio->eos_write_payload.aio_buf =\ + buf_node->buf; + audio->eos_flag = 1; + audio->eos_rsp = 0; + q6asm_cmd(audio->ac, CMD_EOS); + kfree(buf_node); + } else { /* Flush in progress, send back i/p EOS buffer + as is */ + union msm_audio_event_payload event_payload; + event_payload.aio_buf = buf_node->buf; + audwmapro_post_event(audio, + AUDIO_EVENT_WRITE_DONE, + event_payload); + kfree(buf_node); + } + } + } else { + /* read */ + if (!buf_node->paddr || + (buf_node->paddr & 0x1) || + (buf_node->buf.buf_len < PCM_BUFSZ_MIN)) { + kfree(buf_node); + return -EINVAL; + } + /* No EOS reached */ + if (!audio->eos_rsp) { + spin_lock_irqsave(&audio->dsp_lock, flags); + audwmapro_async_read(audio, buf_node); + /* EOS buffer handled in driver */ + list_add_tail(&buf_node->list, &audio->in_queue); + spin_unlock_irqrestore(&audio->dsp_lock, flags); + } + /* EOS reached at input side fake all upcoming read buffer to + indicate the same */ + else { + union msm_audio_event_payload event_payload; + event_payload.aio_buf = buf_node->buf; + event_payload.aio_buf.data_len = + insert_eos_buf(audio, buf_node); + pr_debug("%s: propagate READ_DONE as EOS done\n",\ + __func__); + audwmapro_post_event(audio, AUDIO_EVENT_READ_DONE, + event_payload); + kfree(buf_node); + } + } + return 0; +} + +/* TBD: Only useful in tunnel-mode */ +int audwmapro_async_fsync(struct q6audio *audio) +{ + int rc = 0; + + /* Blocking client sends more data */ + mutex_lock(&audio->lock); + audio->drv_status |= ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + pr_info("%s:\n", __func__); + + mutex_lock(&audio->write_lock); + audio->eos_rsp = 0; + + rc = wait_event_interruptible(audio->write_wait, + (list_empty(&audio->out_queue)) || + audio->wflush || audio->stopped); + + if (rc < 0) { + pr_err("%s: wait event for list_empty failed, rc = %d\n", + __func__, rc); + goto done; + } + + rc = q6asm_cmd(audio->ac, CMD_EOS); + + if (rc < 0) + pr_err("%s: q6asm_cmd failed, rc = %d", __func__, rc); + + rc = wait_event_interruptible(audio->write_wait, + (audio->eos_rsp || audio->wflush || + audio->stopped)); + + if (rc < 0) { + pr_err("%s: wait event for eos_rsp failed, rc = %d\n", __func__, + rc); + goto done; + } + + if (audio->eos_rsp == 1) { + rc = audwmapro_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->enabled = 1; + } + } + + if (audio->stopped || audio->wflush) + rc = -EBUSY; + +done: + mutex_unlock(&audio->write_lock); + mutex_lock(&audio->lock); + audio->drv_status &= ~ADRV_STATUS_FSYNC; + mutex_unlock(&audio->lock); + + return rc; +} + +int audwmapro_fsync(struct file *file, int datasync) +{ + struct q6audio *audio = file->private_data; + + if (!audio->enabled || audio->feedback) + return -EINVAL; + + return audio->drv_ops.fsync(audio); +} + +static void audwmapro_reset_pmem_region(struct q6audio *audio) +{ + struct audwmapro_pmem_region *region; + struct list_head *ptr, *next; + + list_for_each_safe(ptr, next, &audio->pmem_region_queue) { + region = list_entry(ptr, struct audwmapro_pmem_region, list); + list_del(®ion->list); + put_pmem_file(region->file); + kfree(region); + } + + return; +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t audwmapro_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t audwmapro_debug_read(struct file *file, char __user * buf, + size_t count, loff_t *ppos) +{ + const int debug_bufmax = 4096; + static char buffer[4096]; + int n = 0; + struct q6audio *audio = file->private_data; + + mutex_lock(&audio->lock); + n = scnprintf(buffer, debug_bufmax, "opened %d\n", audio->opened); + n += scnprintf(buffer + n, debug_bufmax - n, + "enabled %d\n", audio->enabled); + n += scnprintf(buffer + n, debug_bufmax - n, + "stopped %d\n", audio->stopped); + n += scnprintf(buffer + n, debug_bufmax - n, + "feedback %d\n", audio->feedback); + mutex_unlock(&audio->lock); + /* Following variables are only useful for debugging when + * when playback halts unexpectedly. Thus, no mutual exclusion + * enforced + */ + n += scnprintf(buffer + n, debug_bufmax - n, + "wflush %d\n", audio->wflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "rflush %d\n", audio->rflush); + n += scnprintf(buffer + n, debug_bufmax - n, + "inqueue empty %d\n", list_empty(&audio->in_queue)); + n += scnprintf(buffer + n, debug_bufmax - n, + "outqueue empty %d\n", list_empty(&audio->out_queue)); + buffer[n] = 0; + return simple_read_from_buffer(buf, count, ppos, buffer, n); +} + +static const struct file_operations audwmapro_debug_fops = { + .read = audwmapro_debug_read, + .open = audwmapro_debug_open, +}; +#endif + +static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct q6audio *audio = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + stats.byte_count = atomic_read(&audio->in_bytes); + stats.sample_count = atomic_read(&audio->in_samples); + if (copy_to_user((void *)arg, &stats, sizeof(stats))) + return -EFAULT; + return rc; + } + + if (cmd == AUDIO_GET_EVENT) { + pr_debug("AUDIO_GET_EVENT\n"); + if (mutex_trylock(&audio->get_event_lock)) { + rc = audwmapro_process_event_req(audio, + (void __user *)arg); + mutex_unlock(&audio->get_event_lock); + } else + rc = -EBUSY; + return rc; + } + + if (cmd == AUDIO_ASYNC_WRITE) { + mutex_lock(&audio->write_lock); + if (audio->drv_status & ADRV_STATUS_FSYNC) + rc = -EBUSY; + else { + if (audio->enabled) + rc = audwmapro_aio_buf_add(audio, 1, + (void __user *)arg); + else + rc = -EPERM; + } + mutex_unlock(&audio->write_lock); + return rc; + } + + if (cmd == AUDIO_ASYNC_READ) { + mutex_lock(&audio->read_lock); + if ((audio->feedback) && (audio->enabled)) + rc = audwmapro_aio_buf_add(audio, 0, + (void __user *)arg); + else + rc = -EPERM; + mutex_unlock(&audio->read_lock); + return rc; + } + + if (cmd == AUDIO_ABORT_GET_EVENT) { + audio->event_abort = 1; + wake_up(&audio->event_wait); + return 0; + } + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: { + struct asm_wmapro_cfg wmapro_cfg; + if (audio->feedback == NON_TUNNEL_MODE) { + /* Configure PCM output block */ + rc = q6asm_enc_cfg_blk_pcm(audio->ac, + audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + if (rc < 0) { + pr_err("pcm output block config failed\n"); + break; + } + } + if ((audio->wmapro_config.formattag == 0x162) || + (audio->wmapro_config.formattag == 0x166)) { + wmapro_cfg.format_tag = audio->wmapro_config.formattag; + } else { + pr_err("%s:AUDIO_START failed: formattag = %d\n", + __func__, audio->wmapro_config.formattag); + rc = -EINVAL; + break; + } + if ((audio->wmapro_config.numchannels == 1) || + (audio->wmapro_config.numchannels == 2)) { + wmapro_cfg.ch_cfg = audio->wmapro_config.numchannels; + } else { + pr_err("%s:AUDIO_START failed: channels = %d\n", + __func__, audio->wmapro_config.numchannels); + rc = -EINVAL; + break; + } + if ((audio->wmapro_config.samplingrate <= 48000) || + (audio->wmapro_config.samplingrate > 0)) { + wmapro_cfg.sample_rate = + audio->wmapro_config.samplingrate; + } else { + pr_err("%s:AUDIO_START failed: sample_rate = %d\n", + __func__, audio->wmapro_config.samplingrate); + rc = -EINVAL; + break; + } + wmapro_cfg.avg_bytes_per_sec = + audio->wmapro_config.avgbytespersecond; + if ((audio->wmapro_config.asfpacketlength <= 13376) || + (audio->wmapro_config.asfpacketlength > 0)) { + wmapro_cfg.block_align = + audio->wmapro_config.asfpacketlength; + } else { + pr_err("%s:AUDIO_START failed: block_align = %d\n", + __func__, audio->wmapro_config.asfpacketlength); + rc = -EINVAL; + break; + } + if (audio->wmapro_config.validbitspersample == 16) { + wmapro_cfg.valid_bits_per_sample = + audio->wmapro_config.validbitspersample; + } else { + pr_err("%s:AUDIO_START failed: bitspersample = %d\n", + __func__, + audio->wmapro_config.validbitspersample); + rc = -EINVAL; + break; + } + if ((audio->wmapro_config.channelmask == 4) || + (audio->wmapro_config.channelmask == 3)) { + wmapro_cfg.ch_mask = audio->wmapro_config.channelmask; + } else { + pr_err("%s:AUDIO_START failed: channel_mask = %d\n", + __func__, audio->wmapro_config.channelmask); + rc = -EINVAL; + break; + } + wmapro_cfg.encode_opt = audio->wmapro_config.encodeopt; + wmapro_cfg.adv_encode_opt = + audio->wmapro_config.advancedencodeopt; + wmapro_cfg.adv_encode_opt2 = + audio->wmapro_config.advancedencodeopt2; + /* Configure Media format block */ + rc = q6asm_media_format_block_wmapro(audio->ac, &wmapro_cfg); + if (rc < 0) { + pr_err("cmd media format block failed\n"); + break; + } + rc = audwmapro_enable(audio); + audio->eos_rsp = 0; + audio->eos_flag = 0; + if (!rc) { + audio->enabled = 1; + } else { + audio->enabled = 0; + pr_err("Audio Start procedure failed rc=%d\n", rc); + break; + } + pr_debug("AUDIO_START success enable[%d]\n", audio->enabled); + if (audio->stopped == 1) + audio->stopped = 0; + break; + } + case AUDIO_STOP: { + pr_debug("AUDIO_STOP\n"); + audio->stopped = 1; + audwmapro_flush(audio); + audio->enabled = 0; + audio->drv_status &= ~ADRV_STATUS_PAUSE; + if (rc < 0) { + pr_err("Audio Stop procedure failed rc=%d\n", rc); + break; + } + break; + } + case AUDIO_PAUSE: { + pr_debug("AUDIO_PAUSE %ld\n", arg); + if (arg == 1) { + rc = audwmapro_pause(audio); + if (rc < 0) + pr_err("%s: pause FAILED rc=%d\n", __func__, + rc); + audio->drv_status |= ADRV_STATUS_PAUSE; + } else if (arg == 0) { + if (audio->drv_status & ADRV_STATUS_PAUSE) { + rc = audwmapro_enable(audio); + if (rc) + pr_err("%s: audio enable failed\n", + __func__); + else { + audio->drv_status &= ~ADRV_STATUS_PAUSE; + audio->enabled = 1; + } + } + } + break; + } + case AUDIO_FLUSH: { + pr_debug("AUDIO_FLUSH\n"); + audio->rflush = 1; + audio->wflush = 1; + /* Flush DSP */ + rc = audwmapro_flush(audio); + /* Flush input / Output buffer in software*/ + audwmapro_ioport_reset(audio); + if (rc < 0) { + pr_err("AUDIO_FLUSH interrupted\n"); + rc = -EINTR; + } else { + audio->rflush = 0; + audio->wflush = 0; + } + audio->eos_flag = 0; + audio->eos_rsp = 0; + break; + } + case AUDIO_REGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("AUDIO_REGISTER_PMEM\n"); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audwmapro_pmem_add(audio, &info); + break; + } + case AUDIO_DEREGISTER_PMEM: { + struct msm_audio_pmem_info info; + pr_debug("AUDIO_DEREGISTER_PMEM\n"); + if (copy_from_user(&info, (void *)arg, sizeof(info))) + rc = -EFAULT; + else + rc = audwmapro_pmem_remove(audio, &info); + break; + } + case AUDIO_GET_WMAPRO_CONFIG: { + if (copy_to_user((void *)arg, &audio->wmapro_config, + sizeof(struct msm_audio_wmapro_config))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_SET_WMAPRO_CONFIG: { + if (copy_from_user(&audio->wmapro_config, (void *)arg, + sizeof(struct msm_audio_wmapro_config))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_GET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.buffer_size = audio->str_cfg.buffer_size; + cfg.buffer_count = audio->str_cfg.buffer_count; + pr_debug("GET STREAM CFG %d %d\n", cfg.buffer_size, + cfg.buffer_count); + if (copy_to_user((void *)arg, &cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_STREAM_CONFIG: { + struct msm_audio_stream_config cfg; + pr_debug("SET STREAM CONFIG\n"); + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + rc = 0; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config cfg; + if (copy_to_user((void *)arg, &audio->pcm_cfg, sizeof(cfg))) + rc = -EFAULT; + break; + } + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + if (copy_from_user(&config, (void *)arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (audio->feedback != NON_TUNNEL_MODE) { + pr_err("Not sufficient permission to" + "change the playback mode\n"); + rc = -EACCES; + break; + } + if ((config.buffer_count > PCM_BUF_COUNT) || + (config.buffer_count == 1)) + config.buffer_count = PCM_BUF_COUNT; + + if (config.buffer_size < PCM_BUFSZ_MIN) + config.buffer_size = PCM_BUFSZ_MIN; + + audio->pcm_cfg.buffer_count = config.buffer_count; + audio->pcm_cfg.buffer_size = config.buffer_size; + audio->pcm_cfg.channel_count = config.channel_count; + audio->pcm_cfg.sample_rate = config.sample_rate; + rc = 0; + break; + } + case AUDIO_SET_BUF_CFG: { + struct msm_audio_buf_cfg cfg; + if (copy_from_user(&cfg, (void *)arg, sizeof(cfg))) { + rc = -EFAULT; + break; + } + if ((audio->feedback == NON_TUNNEL_MODE) && + !cfg.meta_info_enable) { + rc = -EFAULT; + break; + } + + audio->buf_cfg.meta_info_enable = cfg.meta_info_enable; + pr_debug("%s:session id %d: Set-buf-cfg: meta[%d]", __func__, + audio->ac->session, cfg.meta_info_enable); + break; + } + case AUDIO_GET_BUF_CFG: { + pr_debug("%s:session id %d: Get-buf-cfg: meta[%d]\ + framesperbuf[%d]\n", __func__, + audio->ac->session, audio->buf_cfg.meta_info_enable, + audio->buf_cfg.frames_per_buf); + + if (copy_to_user((void *)arg, &audio->buf_cfg, + sizeof(struct msm_audio_buf_cfg))) + rc = -EFAULT; + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *)arg, &audio->ac->session, + sizeof(unsigned short))) { + rc = -EFAULT; + } + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&audio->lock); + return rc; +} + +static int audio_release(struct inode *inode, struct file *file) +{ + struct q6audio *audio = file->private_data; + mutex_lock(&audio->lock); + audwmapro_disable(audio); + audio->drv_ops.out_flush(audio); + audio->drv_ops.in_flush(audio); + audwmapro_reset_pmem_region(audio); + audio->event_abort = 1; + wake_up(&audio->event_wait); + audwmapro_reset_event_queue(audio); + q6asm_audio_client_free(audio->ac); + mutex_unlock(&audio->lock); + mutex_destroy(&audio->lock); + mutex_destroy(&audio->read_lock); + mutex_destroy(&audio->write_lock); + mutex_destroy(&audio->get_event_lock); +#ifdef CONFIG_DEBUG_FS + if (audio->dentry) + debugfs_remove(audio->dentry); +#endif + kfree(audio); + pr_info("%s: wmapro decoder success\n", __func__); + return 0; +} + +static int audio_open(struct inode *inode, struct file *file) +{ + struct q6audio *audio = NULL; + int rc = 0; + int i; + struct audwmapro_event *e_node = NULL; + +#ifdef CONFIG_DEBUG_FS + /* 4 bytes represents decoder number, 1 byte for terminate string */ + char name[sizeof "msm_wmapro_" + 5]; +#endif + audio = kzalloc(sizeof(struct q6audio), GFP_KERNEL); + + if (audio == NULL) { + pr_err("Could not allocate memory for wma decode driver\n"); + return -ENOMEM; + } + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + audio->pcm_cfg.buffer_size = PCM_BUFSZ_MIN; + audio->pcm_cfg.buffer_count = PCM_BUF_COUNT; + audio->pcm_cfg.sample_rate = 48000; + audio->pcm_cfg.channel_count = 2; + + audio->ac = q6asm_audio_client_alloc((app_cb) q6_audwmapro_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("Could not allocate memory for audio client\n"); + kfree(audio); + return -ENOMEM; + } + /* Only AIO interface */ + if (file->f_flags & O_NONBLOCK) { + pr_debug("set to aio interface\n"); + audio->drv_status |= ADRV_STATUS_AIO_INTF; + audio->drv_ops.out_flush = audwmapro_async_out_flush; + audio->drv_ops.in_flush = audwmapro_async_in_flush; + audio->drv_ops.fsync = audwmapro_async_fsync; + q6asm_set_io_mode(audio->ac, ASYNC_IO_MODE); + } else { + pr_err("SIO interface not supported\n"); + rc = -EACCES; + goto fail; + } + + /* open in T/NT mode */ + if ((file->f_mode & FMODE_WRITE) && (file->f_mode & FMODE_READ)) { + rc = q6asm_open_read_write(audio->ac, FORMAT_LINEAR_PCM, + FORMAT_WMA_V10PRO); + if (rc < 0) { + pr_err("NT mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = NON_TUNNEL_MODE; + /* open WMA decoder, expected frames is always 1*/ + audio->buf_cfg.frames_per_buf = 0x01; + audio->buf_cfg.meta_info_enable = 0x01; + } else if ((file->f_mode & FMODE_WRITE) && + !(file->f_mode & FMODE_READ)) { + rc = q6asm_open_write(audio->ac, FORMAT_WMA_V10PRO); + if (rc < 0) { + pr_err("T mode Open failed rc=%d\n", rc); + rc = -ENODEV; + goto fail; + } + audio->feedback = TUNNEL_MODE; + audio->buf_cfg.meta_info_enable = 0x00; + } else { + pr_err("Not supported mode\n"); + rc = -EACCES; + goto fail; + } + /* Initialize all locks of audio instance */ + mutex_init(&audio->lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->write_lock); + mutex_init(&audio->get_event_lock); + spin_lock_init(&audio->dsp_lock); + spin_lock_init(&audio->event_queue_lock); + init_waitqueue_head(&audio->cmd_wait); + init_waitqueue_head(&audio->write_wait); + init_waitqueue_head(&audio->event_wait); + INIT_LIST_HEAD(&audio->out_queue); + INIT_LIST_HEAD(&audio->in_queue); + INIT_LIST_HEAD(&audio->pmem_region_queue); + INIT_LIST_HEAD(&audio->free_event_queue); + INIT_LIST_HEAD(&audio->event_queue); + + audio->drv_ops.out_flush(audio); + audio->opened = 1; + file->private_data = audio; + +#ifdef CONFIG_DEBUG_FS + snprintf(name, sizeof name, "msm_wmapro_%04x", audio->ac->session); + audio->dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, + NULL, (void *)audio, + &audwmapro_debug_fops); + + if (IS_ERR(audio->dentry)) + pr_debug("debugfs_create_file failed\n"); +#endif + for (i = 0; i < AUDWMAPRO_EVENT_NUM; i++) { + e_node = kmalloc(sizeof(struct audwmapro_event), GFP_KERNEL); + if (e_node) + list_add_tail(&e_node->list, &audio->free_event_queue); + else { + pr_err("event pkt alloc failed\n"); + break; + } + } + pr_info("%s:wmapro decoder open success, session_id = %d\n", __func__, + audio->ac->session); + return 0; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio); + return rc; +} + +static const struct file_operations audio_wmapro_fops = { + .owner = THIS_MODULE, + .open = audio_open, + .release = audio_release, + .unlocked_ioctl = audio_ioctl, + .fsync = audwmapro_fsync, +}; + +struct miscdevice audwmapro_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_wmapro", + .fops = &audio_wmapro_fops, +}; + +static int __init audio_wmapro_init(void) +{ + return misc_register(&audwmapro_misc); +} + +device_initcall(audio_wmapro_init); diff --git a/arch/arm/mach-msm/qdsp6v2/board-msm8x60-audio.c b/arch/arm/mach-msm/qdsp6v2/board-msm8x60-audio.c new file mode 100644 index 00000000000..8bf7794a841 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/board-msm8x60-audio.c @@ -0,0 +1,2507 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "snddev_icodec.h" +#include "snddev_ecodec.h" +#include "timpani_profile_8x60.h" +#include "snddev_hdmi.h" +#include "snddev_mi2s.h" +#include "snddev_virtual.h" + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_hsed_config; +static void snddev_hsed_config_modify_setting(int type); +static void snddev_hsed_config_restore_setting(void); +#endif + +/* GPIO_CLASS_D0_EN */ +#define SNDDEV_GPIO_CLASS_D0_EN 227 + +/* GPIO_CLASS_D1_EN */ +#define SNDDEV_GPIO_CLASS_D1_EN 229 + +#define SNDDEV_GPIO_MIC2_ANCR_SEL 294 +#define SNDDEV_GPIO_MIC1_ANCL_SEL 295 + +static struct resource msm_cdcclk_ctl_resources[] = { + { + .name = "msm_snddev_tx_mclk", + .start = 108, + .end = 108, + .flags = IORESOURCE_IO, + }, + { + .name = "msm_snddev_rx_mclk", + .start = 109, + .end = 109, + .flags = IORESOURCE_IO, + }, +}; + +static struct platform_device msm_cdcclk_ctl_device = { + .name = "msm_cdcclk_ctl", + .num_resources = ARRAY_SIZE(msm_cdcclk_ctl_resources), + .resource = msm_cdcclk_ctl_resources, +}; + +static struct resource msm_aux_pcm_resources[] = { + + { + .name = "aux_pcm_dout", + .start = 111, + .end = 111, + .flags = IORESOURCE_IO, + }, + { + .name = "aux_pcm_din", + .start = 112, + .end = 112, + .flags = IORESOURCE_IO, + }, + { + .name = "aux_pcm_syncout", + .start = 113, + .end = 113, + .flags = IORESOURCE_IO, + }, + { + .name = "aux_pcm_clkin_a", + .start = 114, + .end = 114, + .flags = IORESOURCE_IO, + }, +}; + +static struct platform_device msm_aux_pcm_device = { + .name = "msm_aux_pcm", + .num_resources = ARRAY_SIZE(msm_aux_pcm_resources), + .resource = msm_aux_pcm_resources, +}; + +static struct resource msm_mi2s_gpio_resources[] = { + + { + .name = "mi2s_ws", + .start = 101, + .end = 101, + .flags = IORESOURCE_IO, + }, + { + .name = "mi2s_sclk", + .start = 102, + .end = 102, + .flags = IORESOURCE_IO, + }, + { + .name = "mi2s_mclk", + .start = 103, + .end = 103, + .flags = IORESOURCE_IO, + }, + { + .name = "fm_mi2s_sd", + .start = 107, + .end = 107, + .flags = IORESOURCE_IO, + }, +}; + +static struct platform_device msm_mi2s_device = { + .name = "msm_mi2s", + .num_resources = ARRAY_SIZE(msm_mi2s_gpio_resources), + .resource = msm_mi2s_gpio_resources, +}; + +/* Must be same size as msm_icodec_gpio_resources */ +static int msm_icodec_gpio_defaults[] = { + 0, + 0, +}; + +static struct resource msm_icodec_gpio_resources[] = { + { + .name = "msm_icodec_speaker_left", + .start = SNDDEV_GPIO_CLASS_D0_EN, + .end = SNDDEV_GPIO_CLASS_D0_EN, + .flags = IORESOURCE_IO, + }, + { + .name = "msm_icodec_speaker_right", + .start = SNDDEV_GPIO_CLASS_D1_EN, + .end = SNDDEV_GPIO_CLASS_D1_EN, + .flags = IORESOURCE_IO, + }, +}; + +static struct platform_device msm_icodec_gpio_device = { + .name = "msm_icodec_gpio", + .num_resources = ARRAY_SIZE(msm_icodec_gpio_resources), + .resource = msm_icodec_gpio_resources, + .dev = { .platform_data = &msm_icodec_gpio_defaults }, +}; + +static struct regulator *s3; +static struct regulator *mvs; + +static int msm_snddev_enable_dmic_power(void) +{ + int ret; + + s3 = regulator_get(NULL, "8058_s3"); + if (IS_ERR(s3)) { + ret = -EBUSY; + goto fail_get_s3; + } + + ret = regulator_set_voltage(s3, 1800000, 1800000); + if (ret) { + pr_err("%s: error setting voltage\n", __func__); + goto fail_s3; + } + + ret = regulator_enable(s3); + if (ret) { + pr_err("%s: error enabling regulator\n", __func__); + goto fail_s3; + } + + mvs = regulator_get(NULL, "8901_mvs0"); + if (IS_ERR(mvs)) + goto fail_mvs0_get; + + ret = regulator_enable(mvs); + if (ret) { + pr_err("%s: error setting regulator\n", __func__); + goto fail_mvs0_enable; + } + return ret; + +fail_mvs0_enable: + regulator_put(mvs); + mvs = NULL; +fail_mvs0_get: + regulator_disable(s3); +fail_s3: + regulator_put(s3); + s3 = NULL; +fail_get_s3: + return ret; +} + +static void msm_snddev_disable_dmic_power(void) +{ + int ret; + + if (mvs) { + ret = regulator_disable(mvs); + if (ret < 0) + pr_err("%s: error disabling vreg mvs\n", __func__); + regulator_put(mvs); + mvs = NULL; + } + + if (s3) { + ret = regulator_disable(s3); + if (ret < 0) + pr_err("%s: error disabling regulator s3\n", __func__); + regulator_put(s3); + s3 = NULL; + } +} + +#define PM8901_MPP_3 (2) /* PM8901 MPP starts from 0 */ + +static int config_class_d0_gpio(int enable) +{ + int rc; + + if (enable) { + rc = pm8901_mpp_config_digital_out(PM8901_MPP_3, + PM8901_MPP_DIG_LEVEL_MSMIO, 1); + + if (rc) { + pr_err("%s: CLASS_D0_EN failed\n", __func__); + return rc; + } + + rc = gpio_request(SNDDEV_GPIO_CLASS_D0_EN, "CLASSD0_EN"); + + if (rc) { + pr_err("%s: spkr pamp gpio pm8901 mpp3 request" + "failed\n", __func__); + pm8901_mpp_config_digital_out(PM8901_MPP_3, + PM8901_MPP_DIG_LEVEL_MSMIO, 0); + return rc; + } + + gpio_direction_output(SNDDEV_GPIO_CLASS_D0_EN, 1); + gpio_set_value(SNDDEV_GPIO_CLASS_D0_EN, 1); + + } else { + pm8901_mpp_config_digital_out(PM8901_MPP_3, + PM8901_MPP_DIG_LEVEL_MSMIO, 0); + gpio_set_value(SNDDEV_GPIO_CLASS_D0_EN, 0); + gpio_free(SNDDEV_GPIO_CLASS_D0_EN); + } + return 0; +} + +static atomic_t pamp_ref_cnt; + +static int msm_snddev_poweramp_on(void) +{ + int rc; + + if (atomic_inc_return(&pamp_ref_cnt) > 1) + return 0; + + pr_debug("%s: enable stereo spkr amp\n", __func__); + rc = config_class_d0_gpio(1); + if (rc) { + pr_err("%s: d0 gpio configuration failed\n", __func__); + goto config_gpio_fail; + } +config_gpio_fail: + return rc; +} + +static void msm_snddev_poweramp_off(void) +{ + if (atomic_dec_return(&pamp_ref_cnt) == 0) { + pr_debug("%s: disable stereo spkr amp\n", __func__); + config_class_d0_gpio(0); + msleep(30); + } +} + +/* Regulator 8058_l10 supplies regulator 8058_ncp. */ +static struct regulator *snddev_reg_ncp; +static struct regulator *snddev_reg_l10; + +static atomic_t preg_ref_cnt; + +static int msm_snddev_voltage_on(void) +{ + int rc; + pr_debug("%s\n", __func__); + + if (atomic_inc_return(&preg_ref_cnt) > 1) + return 0; + + snddev_reg_l10 = regulator_get(NULL, "8058_l10"); + if (IS_ERR(snddev_reg_l10)) { + pr_err("%s: regulator_get(%s) failed (%ld)\n", __func__, + "l10", PTR_ERR(snddev_reg_l10)); + return -EBUSY; + } + + rc = regulator_set_voltage(snddev_reg_l10, 2600000, 2600000); + if (rc < 0) + pr_err("%s: regulator_set_voltage(l10) failed (%d)\n", + __func__, rc); + + rc = regulator_enable(snddev_reg_l10); + if (rc < 0) + pr_err("%s: regulator_enable(l10) failed (%d)\n", __func__, rc); + + snddev_reg_ncp = regulator_get(NULL, "8058_ncp"); + if (IS_ERR(snddev_reg_ncp)) { + pr_err("%s: regulator_get(%s) failed (%ld)\n", __func__, + "ncp", PTR_ERR(snddev_reg_ncp)); + return -EBUSY; + } + + rc = regulator_set_voltage(snddev_reg_ncp, 1800000, 1800000); + if (rc < 0) { + pr_err("%s: regulator_set_voltage(ncp) failed (%d)\n", + __func__, rc); + goto regulator_fail; + } + + rc = regulator_enable(snddev_reg_ncp); + if (rc < 0) { + pr_err("%s: regulator_enable(ncp) failed (%d)\n", __func__, rc); + goto regulator_fail; + } + + return rc; + +regulator_fail: + regulator_put(snddev_reg_ncp); + snddev_reg_ncp = NULL; + return rc; +} + +static void msm_snddev_voltage_off(void) +{ + int rc; + pr_debug("%s\n", __func__); + + if (!snddev_reg_ncp) + goto done; + + if (atomic_dec_return(&preg_ref_cnt) == 0) { + rc = regulator_disable(snddev_reg_ncp); + if (rc < 0) + pr_err("%s: regulator_disable(ncp) failed (%d)\n", + __func__, rc); + regulator_put(snddev_reg_ncp); + + snddev_reg_ncp = NULL; + } + +done: + if (!snddev_reg_l10) + return; + + rc = regulator_disable(snddev_reg_l10); + if (rc < 0) + pr_err("%s: regulator_disable(l10) failed (%d)\n", + __func__, rc); + + regulator_put(snddev_reg_l10); + + snddev_reg_l10 = NULL; +} + +static int msm_snddev_enable_amic_power(void) +{ + int ret = 0; +#ifdef CONFIG_PMIC8058_OTHC + + if (machine_is_msm8x60_fluid()) { + + ret = pm8058_micbias_enable(OTHC_MICBIAS_0, + OTHC_SIGNAL_ALWAYS_ON); + if (ret) + pr_err("%s: Enabling amic power failed\n", __func__); + + ret = gpio_request(SNDDEV_GPIO_MIC2_ANCR_SEL, "MIC2_ANCR_SEL"); + if (ret) { + pr_err("%s: spkr pamp gpio %d request failed\n", + __func__, SNDDEV_GPIO_MIC2_ANCR_SEL); + return ret; + } + gpio_direction_output(SNDDEV_GPIO_MIC2_ANCR_SEL, 0); + + ret = gpio_request(SNDDEV_GPIO_MIC1_ANCL_SEL, "MIC1_ANCL_SEL"); + if (ret) { + pr_err("%s: mic1 ancl gpio %d request failed\n", + __func__, SNDDEV_GPIO_MIC1_ANCL_SEL); + gpio_free(SNDDEV_GPIO_MIC2_ANCR_SEL); + return ret; + } + gpio_direction_output(SNDDEV_GPIO_MIC1_ANCL_SEL, 0); + + } else { + ret = pm8058_micbias_enable(OTHC_MICBIAS_2, + OTHC_SIGNAL_ALWAYS_ON); + if (ret) + pr_err("%s: Enabling amic power failed\n", __func__); + } +#endif + return ret; +} + +static void msm_snddev_disable_amic_power(void) +{ +#ifdef CONFIG_PMIC8058_OTHC + int ret; + if (machine_is_msm8x60_fluid()) { + ret = pm8058_micbias_enable(OTHC_MICBIAS_0, + OTHC_SIGNAL_OFF); + gpio_free(SNDDEV_GPIO_MIC1_ANCL_SEL); + gpio_free(SNDDEV_GPIO_MIC2_ANCR_SEL); + } else + ret = pm8058_micbias_enable(OTHC_MICBIAS_2, OTHC_SIGNAL_OFF); + + if (ret) + pr_err("%s: Disabling amic power failed\n", __func__); +#endif +} + +static int msm_snddev_enable_anc_power(void) +{ + int ret = 0; +#ifdef CONFIG_PMIC8058_OTHC + ret = pm8058_micbias_enable(OTHC_MICBIAS_2, + OTHC_SIGNAL_ALWAYS_ON); + if (ret) + pr_err("%s: Enabling anc micbias 2 failed\n", __func__); + + if (machine_is_msm8x60_fluid()) { + + ret = pm8058_micbias_enable(OTHC_MICBIAS_0, + OTHC_SIGNAL_ALWAYS_ON); + if (ret) + pr_err("%s: Enabling anc micbias 0 failed\n", __func__); + + ret = gpio_request(SNDDEV_GPIO_MIC2_ANCR_SEL, "MIC2_ANCR_SEL"); + if (ret) { + pr_err("%s: mic2 ancr gpio %d request failed\n", + __func__, SNDDEV_GPIO_MIC2_ANCR_SEL); + return ret; + } + gpio_direction_output(SNDDEV_GPIO_MIC2_ANCR_SEL, 1); + + ret = gpio_request(SNDDEV_GPIO_MIC1_ANCL_SEL, "MIC1_ANCL_SEL"); + if (ret) { + pr_err("%s: mic1 ancl gpio %d request failed\n", + __func__, SNDDEV_GPIO_MIC1_ANCL_SEL); + gpio_free(SNDDEV_GPIO_MIC2_ANCR_SEL); + return ret; + } + gpio_direction_output(SNDDEV_GPIO_MIC1_ANCL_SEL, 1); + + } +#endif + return ret; +} + +static void msm_snddev_disable_anc_power(void) +{ +#ifdef CONFIG_PMIC8058_OTHC + int ret; + + ret = pm8058_micbias_enable(OTHC_MICBIAS_2, OTHC_SIGNAL_OFF); + + if (machine_is_msm8x60_fluid()) { + ret |= pm8058_micbias_enable(OTHC_MICBIAS_0, + OTHC_SIGNAL_OFF); + gpio_free(SNDDEV_GPIO_MIC2_ANCR_SEL); + gpio_free(SNDDEV_GPIO_MIC1_ANCL_SEL); + } + + if (ret) + pr_err("%s: Disabling anc power failed\n", __func__); +#endif +} + +static int msm_snddev_enable_dmic_sec_power(void) +{ + int ret; + + ret = msm_snddev_enable_dmic_power(); + if (ret) { + pr_err("%s: Error: Enabling dmic power failed\n", __func__); + return ret; + } +#ifdef CONFIG_PMIC8058_OTHC + ret = pm8058_micbias_enable(OTHC_MICBIAS_2, OTHC_SIGNAL_ALWAYS_ON); + if (ret) { + pr_err("%s: Error: Enabling micbias failed\n", __func__); + msm_snddev_disable_dmic_power(); + return ret; + } +#endif + return 0; +} + +static void msm_snddev_disable_dmic_sec_power(void) +{ + msm_snddev_disable_dmic_power(); + +#ifdef CONFIG_PMIC8058_OTHC + pm8058_micbias_enable(OTHC_MICBIAS_2, OTHC_SIGNAL_OFF); +#endif +} + +static struct adie_codec_action_unit iearpiece_48KHz_osr256_actions[] = + EAR_PRI_MONO_8000_OSR_256; + +static struct adie_codec_hwsetting_entry iearpiece_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = iearpiece_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(iearpiece_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile iearpiece_profile = { + .path_type = ADIE_CODEC_RX, + .settings = iearpiece_settings, + .setting_sz = ARRAY_SIZE(iearpiece_settings), +}; + +static struct snddev_icodec_data snddev_iearpiece_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "handset_rx", + .copp_id = 0, + .profile = &iearpiece_profile, + .channel_mode = 1, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_iearpiece_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_iearpiece_data }, +}; + +static struct adie_codec_action_unit imic_48KHz_osr256_actions[] = + AMIC_PRI_MONO_OSR_256; + +static struct adie_codec_hwsetting_entry imic_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = imic_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(imic_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile imic_profile = { + .path_type = ADIE_CODEC_TX, + .settings = imic_settings, + .setting_sz = ARRAY_SIZE(imic_settings), +}; + +static struct snddev_icodec_data snddev_imic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_tx", + .copp_id = 1, + .profile = &imic_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_amic_power, + .pamp_off = msm_snddev_disable_amic_power, +}; + +static struct platform_device msm_imic_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_imic_data }, +}; + +static struct snddev_icodec_data snddev_fluid_ispkr_mic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_mono_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &imic_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_amic_power, + .pamp_off = msm_snddev_disable_amic_power, +}; + +static struct platform_device msm_fluid_ispkr_mic_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_fluid_ispkr_mic_data }, +}; + + +static struct adie_codec_action_unit headset_ab_cpls_48KHz_osr256_actions[] = + HEADSET_AB_CPLS_48000_OSR_256; + +static struct adie_codec_hwsetting_entry headset_ab_cpls_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = headset_ab_cpls_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(headset_ab_cpls_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile headset_ab_cpls_profile = { + .path_type = ADIE_CODEC_RX, + .settings = headset_ab_cpls_settings, + .setting_sz = ARRAY_SIZE(headset_ab_cpls_settings), +}; + +static struct snddev_icodec_data snddev_ihs_stereo_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_stereo_rx", + .copp_id = 0, + .profile = &headset_ab_cpls_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, +}; + +static struct platform_device msm_headset_stereo_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_ihs_stereo_rx_data }, +}; + +static struct adie_codec_action_unit headset_anc_48KHz_osr256_actions[] = + ANC_HEADSET_CPLS_AMIC1_AUXL_RX1_48000_OSR_256; + +static struct adie_codec_hwsetting_entry headset_anc_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = headset_anc_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(headset_anc_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile headset_anc_profile = { + .path_type = ADIE_CODEC_RX, + .settings = headset_anc_settings, + .setting_sz = ARRAY_SIZE(headset_anc_settings), +}; + +static struct snddev_icodec_data snddev_anc_headset_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE | SNDDEV_CAP_ANC), + .name = "anc_headset_stereo_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &headset_anc_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_anc_power, + .pamp_off = msm_snddev_disable_anc_power, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, +}; + +static struct platform_device msm_anc_headset_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_anc_headset_data }, +}; + +static struct adie_codec_action_unit ispkr_stereo_48KHz_osr256_actions[] = + SPEAKER_PRI_STEREO_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ispkr_stereo_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ispkr_stereo_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(ispkr_stereo_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ispkr_stereo_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ispkr_stereo_settings, + .setting_sz = ARRAY_SIZE(ispkr_stereo_settings), +}; + +static struct snddev_icodec_data snddev_ispkr_stereo_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "speaker_stereo_rx", + .copp_id = 0, + .profile = &ispkr_stereo_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, +}; + +static struct platform_device msm_ispkr_stereo_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_ispkr_stereo_data }, +}; + +static struct adie_codec_action_unit idmic_mono_48KHz_osr256_actions[] = + DMIC1_PRI_MONO_OSR_256; + +static struct adie_codec_hwsetting_entry idmic_mono_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = idmic_mono_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(idmic_mono_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile idmic_mono_profile = { + .path_type = ADIE_CODEC_TX, + .settings = idmic_mono_settings, + .setting_sz = ARRAY_SIZE(idmic_mono_settings), +}; + +static struct snddev_icodec_data snddev_ispkr_mic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_mono_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &idmic_mono_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, +}; + +static struct platform_device msm_ispkr_mic_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_ispkr_mic_data }, +}; + +static struct adie_codec_action_unit iearpiece_ffa_48KHz_osr256_actions[] = + EAR_PRI_MONO_8000_OSR_256; + +static struct adie_codec_hwsetting_entry iearpiece_ffa_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = iearpiece_ffa_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE(iearpiece_ffa_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile iearpiece_ffa_profile = { + .path_type = ADIE_CODEC_RX, + .settings = iearpiece_ffa_settings, + .setting_sz = ARRAY_SIZE(iearpiece_ffa_settings), +}; + +static struct snddev_icodec_data snddev_iearpiece_ffa_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "handset_rx", + .copp_id = 0, + .profile = &iearpiece_ffa_profile, + .channel_mode = 1, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_iearpiece_ffa_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_iearpiece_ffa_data }, +}; + +static struct snddev_icodec_data snddev_imic_ffa_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &idmic_mono_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, +}; + +static struct platform_device msm_imic_ffa_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_imic_ffa_data }, +}; + +static struct adie_codec_action_unit dual_mic_endfire_8KHz_osr256_actions[] = + DMIC1_PRI_STEREO_OSR_256; + +static struct adie_codec_hwsetting_entry dual_mic_endfire_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = dual_mic_endfire_8KHz_osr256_actions, + .action_sz = ARRAY_SIZE(dual_mic_endfire_8KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile dual_mic_endfire_profile = { + .path_type = ADIE_CODEC_TX, + .settings = dual_mic_endfire_settings, + .setting_sz = ARRAY_SIZE(dual_mic_endfire_settings), +}; + +static struct snddev_icodec_data snddev_dual_mic_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_endfire_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &dual_mic_endfire_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, +}; + +static struct platform_device msm_hs_dual_mic_endfire_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_dual_mic_endfire_data }, +}; + +static struct snddev_icodec_data snddev_dual_mic_spkr_endfire_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_dual_mic_endfire_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &dual_mic_endfire_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, +}; + +static struct platform_device msm_spkr_dual_mic_endfire_device = { + .name = "snddev_icodec", + .id = 15, + .dev = { .platform_data = &snddev_dual_mic_spkr_endfire_data }, +}; + +static struct adie_codec_action_unit dual_mic_broadside_8osr256_actions[] = + HS_DMIC2_STEREO_OSR_256; + +static struct adie_codec_hwsetting_entry dual_mic_broadside_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = dual_mic_broadside_8osr256_actions, + .action_sz = ARRAY_SIZE(dual_mic_broadside_8osr256_actions), + } +}; + +static struct adie_codec_dev_profile dual_mic_broadside_profile = { + .path_type = ADIE_CODEC_TX, + .settings = dual_mic_broadside_settings, + .setting_sz = ARRAY_SIZE(dual_mic_broadside_settings), +}; + +static struct snddev_icodec_data snddev_hs_dual_mic_broadside_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_dual_mic_broadside_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &dual_mic_broadside_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_sec_power, + .pamp_off = msm_snddev_disable_dmic_sec_power, +}; + +static struct platform_device msm_hs_dual_mic_broadside_device = { + .name = "snddev_icodec", + .id = 21, + .dev = { .platform_data = &snddev_hs_dual_mic_broadside_data }, +}; + +static struct snddev_icodec_data snddev_spkr_dual_mic_broadside_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "speaker_dual_mic_broadside_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &dual_mic_broadside_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_sec_power, + .pamp_off = msm_snddev_disable_dmic_sec_power, +}; + +static struct platform_device msm_spkr_dual_mic_broadside_device = { + .name = "snddev_icodec", + .id = 18, + .dev = { .platform_data = &snddev_spkr_dual_mic_broadside_data }, +}; + +static struct snddev_hdmi_data snddev_hdmi_stereo_rx_data = { + .capability = SNDDEV_CAP_RX , + .name = "hdmi_stereo_rx", + .copp_id = HDMI_RX, + .channel_mode = 0, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_snddev_hdmi_stereo_rx_device = { + .name = "snddev_hdmi", + .dev = { .platform_data = &snddev_hdmi_stereo_rx_data }, +}; + +static struct snddev_mi2s_data snddev_mi2s_fm_tx_data = { + .capability = SNDDEV_CAP_TX , + .name = "fmradio_stereo_tx", + .copp_id = MI2S_TX, + .channel_mode = 2, /* stereo */ + .sd_lines = MI2S_SD3, /* sd3 */ + .sample_rate = 48000, +}; + +static struct platform_device msm_mi2s_fm_tx_device = { + .name = "snddev_mi2s", + .dev = { .platform_data = &snddev_mi2s_fm_tx_data }, +}; + +static struct snddev_mi2s_data snddev_mi2s_fm_rx_data = { + .capability = SNDDEV_CAP_RX , + .name = "fmradio_stereo_rx", + .copp_id = MI2S_RX, + .channel_mode = 2, /* stereo */ + .sd_lines = MI2S_SD3, /* sd3 */ + .sample_rate = 48000, +}; + +static struct platform_device msm_mi2s_fm_rx_device = { + .name = "snddev_mi2s", + .id = 1, + .dev = { .platform_data = &snddev_mi2s_fm_rx_data }, +}; + +static struct adie_codec_action_unit iheadset_mic_tx_osr256_actions[] = + HEADSET_AMIC2_TX_MONO_PRI_OSR_256; + +static struct adie_codec_hwsetting_entry iheadset_mic_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = iheadset_mic_tx_osr256_actions, + .action_sz = ARRAY_SIZE(iheadset_mic_tx_osr256_actions), + } +}; + +static struct adie_codec_dev_profile iheadset_mic_profile = { + .path_type = ADIE_CODEC_TX, + .settings = iheadset_mic_tx_settings, + .setting_sz = ARRAY_SIZE(iheadset_mic_tx_settings), +}; + +static struct snddev_icodec_data snddev_headset_mic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "headset_mono_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &iheadset_mic_profile, + .channel_mode = 1, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_headset_mic_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_headset_mic_data }, +}; + +static struct adie_codec_action_unit + ihs_stereo_speaker_stereo_rx_48KHz_osr256_actions[] = + SPEAKER_HPH_AB_CPL_PRI_STEREO_48000_OSR_256; + +static struct adie_codec_hwsetting_entry + ihs_stereo_speaker_stereo_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ihs_stereo_speaker_stereo_rx_48KHz_osr256_actions, + .action_sz = + ARRAY_SIZE(ihs_stereo_speaker_stereo_rx_48KHz_osr256_actions), + } +}; + +static struct adie_codec_dev_profile ihs_stereo_speaker_stereo_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ihs_stereo_speaker_stereo_rx_settings, + .setting_sz = ARRAY_SIZE(ihs_stereo_speaker_stereo_rx_settings), +}; + +static struct snddev_icodec_data snddev_ihs_stereo_speaker_stereo_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "headset_stereo_speaker_stereo_rx", + .copp_id = 0, + .profile = &ihs_stereo_speaker_stereo_rx_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, +}; + +static struct platform_device msm_ihs_stereo_speaker_stereo_rx_device = { + .name = "snddev_icodec", + .id = 22, + .dev = { .platform_data = &snddev_ihs_stereo_speaker_stereo_rx_data }, +}; + +/* define the value for BT_SCO */ + +static struct snddev_ecodec_data snddev_bt_sco_earpiece_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "bt_sco_rx", + .copp_id = PCM_RX, + .channel_mode = 1, +}; + +static struct snddev_ecodec_data snddev_bt_sco_mic_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "bt_sco_tx", + .copp_id = PCM_TX, + .channel_mode = 1, +}; + +struct platform_device msm_bt_sco_earpiece_device = { + .name = "msm_snddev_ecodec", + .dev = { .platform_data = &snddev_bt_sco_earpiece_data }, +}; + +struct platform_device msm_bt_sco_mic_device = { + .name = "msm_snddev_ecodec", + .dev = { .platform_data = &snddev_bt_sco_mic_data }, +}; + +static struct adie_codec_action_unit itty_mono_tx_actions[] = + TTY_HEADSET_MONO_TX_OSR_256; + +static struct adie_codec_hwsetting_entry itty_mono_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = itty_mono_tx_actions, + .action_sz = ARRAY_SIZE(itty_mono_tx_actions), + }, +}; + +static struct adie_codec_dev_profile itty_mono_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = itty_mono_tx_settings, + .setting_sz = ARRAY_SIZE(itty_mono_tx_settings), +}; + +static struct snddev_icodec_data snddev_itty_mono_tx_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE | SNDDEV_CAP_TTY), + .name = "tty_headset_mono_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &itty_mono_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, +}; + +static struct platform_device msm_itty_mono_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_itty_mono_tx_data }, +}; + +static struct adie_codec_action_unit itty_mono_rx_actions[] = + TTY_HEADSET_MONO_RX_8000_OSR_256; + +static struct adie_codec_hwsetting_entry itty_mono_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = itty_mono_rx_actions, + .action_sz = ARRAY_SIZE(itty_mono_rx_actions), + }, +}; + +static struct adie_codec_dev_profile itty_mono_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = itty_mono_rx_settings, + .setting_sz = ARRAY_SIZE(itty_mono_rx_settings), +}; + +static struct snddev_icodec_data snddev_itty_mono_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE | SNDDEV_CAP_TTY), + .name = "tty_headset_mono_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &itty_mono_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, +}; + +static struct platform_device msm_itty_mono_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_itty_mono_rx_data }, +}; + +static struct adie_codec_action_unit linein_pri_actions[] = + LINEIN_PRI_STEREO_OSR_256; + +static struct adie_codec_hwsetting_entry linein_pri_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = linein_pri_actions, + .action_sz = ARRAY_SIZE(linein_pri_actions), + }, +}; + +static struct adie_codec_dev_profile linein_pri_profile = { + .path_type = ADIE_CODEC_TX, + .settings = linein_pri_settings, + .setting_sz = ARRAY_SIZE(linein_pri_settings), +}; + +static struct snddev_icodec_data snddev_linein_pri_data = { + .capability = SNDDEV_CAP_TX, + .name = "linein_pri_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &linein_pri_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, +}; + +static struct platform_device msm_linein_pri_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_linein_pri_data }, +}; + +static struct adie_codec_action_unit auxpga_lp_lo_actions[] = + LB_AUXPGA_LO_STEREO; + +static struct adie_codec_hwsetting_entry auxpga_lp_lo_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = auxpga_lp_lo_actions, + .action_sz = ARRAY_SIZE(auxpga_lp_lo_actions), + }, +}; + +static struct adie_codec_dev_profile auxpga_lp_lo_profile = { + .path_type = ADIE_CODEC_LB, + .settings = auxpga_lp_lo_settings, + .setting_sz = ARRAY_SIZE(auxpga_lp_lo_settings), +}; + +static struct snddev_icodec_data snddev_auxpga_lp_lo_data = { + .capability = SNDDEV_CAP_LB, + .name = "speaker_stereo_lb", + .copp_id = PRIMARY_I2S_RX, + .profile = &auxpga_lp_lo_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_ANALOG, +}; + +static struct platform_device msm_auxpga_lp_lo_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_auxpga_lp_lo_data }, +}; + +static struct adie_codec_action_unit auxpga_lp_hs_actions[] = + LB_AUXPGA_HPH_AB_CPLS_STEREO; + +static struct adie_codec_hwsetting_entry auxpga_lp_hs_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = auxpga_lp_hs_actions, + .action_sz = ARRAY_SIZE(auxpga_lp_hs_actions), + }, +}; + +static struct adie_codec_dev_profile auxpga_lp_hs_profile = { + .path_type = ADIE_CODEC_LB, + .settings = auxpga_lp_hs_settings, + .setting_sz = ARRAY_SIZE(auxpga_lp_hs_settings), +}; + +static struct snddev_icodec_data snddev_auxpga_lp_hs_data = { + .capability = SNDDEV_CAP_LB, + .name = "hs_stereo_lb", + .copp_id = PRIMARY_I2S_RX, + .profile = &auxpga_lp_hs_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, + .dev_vol_type = SNDDEV_DEV_VOL_ANALOG, +}; + +static struct platform_device msm_auxpga_lp_hs_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &snddev_auxpga_lp_hs_data }, +}; + +#ifdef CONFIG_MSM8X60_FTM_AUDIO_DEVICES +static struct adie_codec_action_unit ftm_headset_mono_rx_actions[] = + HPH_PRI_AB_CPLS_MONO; + +static struct adie_codec_hwsetting_entry ftm_headset_mono_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_headset_mono_rx_actions, + .action_sz = ARRAY_SIZE(ftm_headset_mono_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_headset_mono_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_headset_mono_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_headset_mono_rx_settings), +}; + +static struct snddev_icodec_data ftm_headset_mono_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_headset_mono_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_headset_mono_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_headset_mono_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_headset_mono_rx_data}, +}; + +static struct adie_codec_action_unit ftm_headset_mono_diff_rx_actions[] = + HEADSET_MONO_DIFF_RX; + +static struct adie_codec_hwsetting_entry ftm_headset_mono_diff_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_headset_mono_diff_rx_actions, + .action_sz = ARRAY_SIZE(ftm_headset_mono_diff_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_headset_mono_diff_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_headset_mono_diff_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_headset_mono_diff_rx_settings), +}; + +static struct snddev_icodec_data ftm_headset_mono_diff_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_headset_mono_diff_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_headset_mono_diff_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_headset_mono_diff_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_headset_mono_diff_rx_data}, +}; + +static struct adie_codec_action_unit ftm_spkr_mono_rx_actions[] = + SPEAKER_PRI_STEREO_48000_OSR_256; + +static struct adie_codec_hwsetting_entry ftm_spkr_mono_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_spkr_mono_rx_actions, + .action_sz = ARRAY_SIZE(ftm_spkr_mono_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_spkr_mono_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_spkr_mono_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_spkr_mono_rx_settings), +}; + +static struct snddev_icodec_data ftm_spkr_mono_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_spkr_mono_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_spkr_mono_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_spkr_mono_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_spkr_mono_rx_data}, +}; + +static struct adie_codec_action_unit ftm_spkr_l_rx_actions[] = + FTM_SPKR_L_RX; + +static struct adie_codec_hwsetting_entry ftm_spkr_l_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_spkr_l_rx_actions, + .action_sz = ARRAY_SIZE(ftm_spkr_l_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_spkr_l_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_spkr_l_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_spkr_l_rx_settings), +}; + +static struct snddev_icodec_data ftm_spkr_l_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_spkr_l_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_spkr_l_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_spkr_l_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_spkr_l_rx_data}, +}; + +static struct adie_codec_action_unit ftm_spkr_r_rx_actions[] = + SPKR_R_RX; + +static struct adie_codec_hwsetting_entry ftm_spkr_r_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_spkr_r_rx_actions, + .action_sz = ARRAY_SIZE(ftm_spkr_r_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_spkr_r_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_spkr_r_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_spkr_r_rx_settings), +}; + +static struct snddev_icodec_data ftm_spkr_r_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_spkr_r_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_spkr_r_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_spkr_r_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_spkr_r_rx_data}, +}; + +static struct adie_codec_action_unit ftm_spkr_mono_diff_rx_actions[] = + SPKR_MONO_DIFF_RX; + +static struct adie_codec_hwsetting_entry ftm_spkr_mono_diff_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_spkr_mono_diff_rx_actions, + .action_sz = ARRAY_SIZE(ftm_spkr_mono_diff_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_spkr_mono_diff_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_spkr_mono_diff_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_spkr_mono_diff_rx_settings), +}; + +static struct snddev_icodec_data ftm_spkr_mono_diff_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_spkr_mono_diff_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_spkr_mono_diff_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_spkr_mono_diff_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_spkr_mono_diff_rx_data}, +}; + +static struct adie_codec_action_unit ftm_headset_mono_l_rx_actions[] = + HPH_PRI_AB_CPLS_MONO_LEFT; + +static struct adie_codec_hwsetting_entry ftm_headset_mono_l_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_headset_mono_l_rx_actions, + .action_sz = ARRAY_SIZE(ftm_headset_mono_l_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_headset_mono_l_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_headset_mono_l_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_headset_mono_l_rx_settings), +}; + +static struct snddev_icodec_data ftm_headset_mono_l_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_headset_mono_l_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_headset_mono_l_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_headset_mono_l_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_headset_mono_l_rx_data}, +}; + +static struct adie_codec_action_unit ftm_headset_mono_r_rx_actions[] = + HPH_PRI_AB_CPLS_MONO_RIGHT; + +static struct adie_codec_hwsetting_entry ftm_headset_mono_r_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_headset_mono_r_rx_actions, + .action_sz = ARRAY_SIZE(ftm_headset_mono_r_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_headset_mono_r_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_headset_mono_r_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_headset_mono_r_rx_settings), +}; + +static struct snddev_icodec_data ftm_headset_mono_r_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_headset_mono_r_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_headset_mono_r_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_headset_mono_r_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_headset_mono_r_rx_data}, +}; + +static struct adie_codec_action_unit ftm_linein_l_tx_actions[] = + LINEIN_MONO_L_TX; + +static struct adie_codec_hwsetting_entry ftm_linein_l_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_linein_l_tx_actions, + .action_sz = ARRAY_SIZE(ftm_linein_l_tx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_linein_l_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_linein_l_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_linein_l_tx_settings), +}; + +static struct snddev_icodec_data ftm_linein_l_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "ftm_linein_l_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_linein_l_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_linein_l_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_linein_l_tx_data }, +}; + +static struct adie_codec_action_unit ftm_linein_r_tx_actions[] = + LINEIN_MONO_R_TX; + +static struct adie_codec_hwsetting_entry ftm_linein_r_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_linein_r_tx_actions, + .action_sz = ARRAY_SIZE(ftm_linein_r_tx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_linein_r_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_linein_r_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_linein_r_tx_settings), +}; + +static struct snddev_icodec_data ftm_linein_r_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "ftm_linein_r_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_linein_r_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_linein_r_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_linein_r_tx_data }, +}; + +static struct adie_codec_action_unit ftm_aux_out_rx_actions[] = + AUX_OUT_RX; + +static struct adie_codec_hwsetting_entry ftm_aux_out_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_aux_out_rx_actions, + .action_sz = ARRAY_SIZE(ftm_aux_out_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_aux_out_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_aux_out_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_aux_out_rx_settings), +}; + +static struct snddev_icodec_data ftm_aux_out_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_aux_out_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_aux_out_rx_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_aux_out_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_aux_out_rx_data}, +}; + +static struct adie_codec_action_unit ftm_dmic1_left_tx_actions[] = + DMIC1_LEFT_TX; + +static struct adie_codec_hwsetting_entry ftm_dmic1_left_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_dmic1_left_tx_actions, + .action_sz = ARRAY_SIZE(ftm_dmic1_left_tx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_dmic1_left_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_dmic1_left_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_dmic1_left_tx_settings), +}; + +static struct snddev_icodec_data ftm_dmic1_left_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "ftm_dmic1_left_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_dmic1_left_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_dmic1_left_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_dmic1_left_tx_data}, +}; + +static struct adie_codec_action_unit ftm_dmic1_right_tx_actions[] = + DMIC1_RIGHT_TX; + +static struct adie_codec_hwsetting_entry ftm_dmic1_right_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_dmic1_right_tx_actions, + .action_sz = ARRAY_SIZE(ftm_dmic1_right_tx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_dmic1_right_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_dmic1_right_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_dmic1_right_tx_settings), +}; + +static struct snddev_icodec_data ftm_dmic1_right_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "ftm_dmic1_right_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_dmic1_right_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_dmic1_right_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_dmic1_right_tx_data}, +}; + +static struct adie_codec_action_unit ftm_dmic1_l_and_r_tx_actions[] = + DMIC1_LEFT_AND_RIGHT_TX; + +static struct adie_codec_hwsetting_entry ftm_dmic1_l_and_r_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_dmic1_l_and_r_tx_actions, + .action_sz = ARRAY_SIZE(ftm_dmic1_l_and_r_tx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_dmic1_l_and_r_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_dmic1_l_and_r_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_dmic1_l_and_r_tx_settings), +}; + +static struct snddev_icodec_data ftm_dmic1_l_and_r_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "ftm_dmic1_l_and_r_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_dmic1_l_and_r_tx_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_dmic1_l_and_r_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_dmic1_l_and_r_tx_data}, +}; + +static struct adie_codec_action_unit ftm_dmic2_left_tx_actions[] = + DMIC2_LEFT_TX; + +static struct adie_codec_hwsetting_entry ftm_dmic2_left_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_dmic2_left_tx_actions, + .action_sz = ARRAY_SIZE(ftm_dmic2_left_tx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_dmic2_left_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_dmic2_left_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_dmic2_left_tx_settings), +}; + +static struct snddev_icodec_data ftm_dmic2_left_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "ftm_dmic2_left_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_dmic2_left_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_dmic2_left_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_dmic2_left_tx_data }, +}; + +static struct adie_codec_action_unit ftm_dmic2_right_tx_actions[] = + DMIC2_RIGHT_TX; + +static struct adie_codec_hwsetting_entry ftm_dmic2_right_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_dmic2_right_tx_actions, + .action_sz = ARRAY_SIZE(ftm_dmic2_right_tx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_dmic2_right_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_dmic2_right_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_dmic2_right_tx_settings), +}; + +static struct snddev_icodec_data ftm_dmic2_right_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "ftm_dmic2_right_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_dmic2_right_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_dmic2_right_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_dmic2_right_tx_data }, +}; + +static struct adie_codec_action_unit ftm_dmic2_l_and_r_tx_actions[] = + DMIC2_LEFT_AND_RIGHT_TX; + +static struct adie_codec_hwsetting_entry ftm_dmic2_l_and_r_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_dmic2_l_and_r_tx_actions, + .action_sz = ARRAY_SIZE(ftm_dmic2_l_and_r_tx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_dmic2_l_and_r_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_dmic2_l_and_r_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_dmic2_l_and_r_tx_settings), +}; + +static struct snddev_icodec_data ftm_dmic2_l_and_r_tx_data = { + .capability = SNDDEV_CAP_TX, + .name = "ftm_dmic2_l_and_r_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_dmic2_l_and_r_tx_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_dmic_power, + .pamp_off = msm_snddev_disable_dmic_power, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_dmic2_l_and_r_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_dmic2_l_and_r_tx_data}, +}; + +static struct adie_codec_action_unit ftm_handset_mic1_aux_in_actions[] = + HANDSET_MIC1_AUX_IN; + +static struct adie_codec_hwsetting_entry ftm_handset_mic1_aux_in_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_handset_mic1_aux_in_actions, + .action_sz = ARRAY_SIZE(ftm_handset_mic1_aux_in_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_handset_mic1_aux_in_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_handset_mic1_aux_in_settings, + .setting_sz = ARRAY_SIZE(ftm_handset_mic1_aux_in_settings), +}; + +static struct snddev_icodec_data ftm_handset_mic1_aux_in_data = { + .capability = SNDDEV_CAP_TX, + .name = "ftm_handset_mic1_aux_in", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_handset_mic1_aux_in_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + /* Assumption is that inputs are not tied to analog mic, so + * no need to enable mic bias. + */ + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_handset_mic1_aux_in_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_handset_mic1_aux_in_data}, +}; + +static struct snddev_mi2s_data snddev_mi2s_sd0_rx_data = { + .capability = SNDDEV_CAP_RX , + .name = "mi2s_sd0_rx", + .copp_id = MI2S_RX, + .channel_mode = 2, /* stereo */ + .sd_lines = MI2S_SD0, /* sd0 */ + .sample_rate = 48000, +}; + +static struct platform_device ftm_mi2s_sd0_rx_device = { + .name = "snddev_mi2s", + .dev = { .platform_data = &snddev_mi2s_sd0_rx_data }, +}; + +static struct snddev_mi2s_data snddev_mi2s_sd1_rx_data = { + .capability = SNDDEV_CAP_RX , + .name = "mi2s_sd1_rx", + .copp_id = MI2S_RX, + .channel_mode = 2, /* stereo */ + .sd_lines = MI2S_SD1, /* sd1 */ + .sample_rate = 48000, +}; + +static struct platform_device ftm_mi2s_sd1_rx_device = { + .name = "snddev_mi2s", + .dev = { .platform_data = &snddev_mi2s_sd1_rx_data }, +}; + +static struct snddev_mi2s_data snddev_mi2s_sd2_rx_data = { + .capability = SNDDEV_CAP_RX , + .name = "mi2s_sd2_rx", + .copp_id = MI2S_RX, + .channel_mode = 2, /* stereo */ + .sd_lines = MI2S_SD2, /* sd2 */ + .sample_rate = 48000, +}; + +static struct platform_device ftm_mi2s_sd2_rx_device = { + .name = "snddev_mi2s", + .dev = { .platform_data = &snddev_mi2s_sd2_rx_data }, +}; + +/* earpiece */ +static struct adie_codec_action_unit ftm_handset_adie_lp_rx_actions[] = + EAR_PRI_MONO_LB; + +static struct adie_codec_hwsetting_entry ftm_handset_adie_lp_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_handset_adie_lp_rx_actions, + .action_sz = ARRAY_SIZE(ftm_handset_adie_lp_rx_actions), + } +}; + +static struct adie_codec_dev_profile ftm_handset_adie_lp_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_handset_adie_lp_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_handset_adie_lp_rx_settings), +}; + +static struct snddev_icodec_data ftm_handset_adie_lp_rx_data = { + .capability = (SNDDEV_CAP_RX | SNDDEV_CAP_VOICE), + .name = "ftm_handset_adie_lp_rx", + .copp_id = 0, + .profile = &ftm_handset_adie_lp_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_handset_adie_lp_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_handset_adie_lp_rx_data }, +}; + +static struct adie_codec_action_unit ftm_headset_l_adie_lp_rx_actions[] = + FTM_HPH_PRI_AB_CPLS_MONO_LB_LEFT; + +static struct adie_codec_hwsetting_entry ftm_headset_l_adie_lp_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_headset_l_adie_lp_rx_actions, + .action_sz = ARRAY_SIZE(ftm_headset_l_adie_lp_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_headset_l_adie_lp_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_headset_l_adie_lp_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_headset_l_adie_lp_rx_settings), +}; + +static struct snddev_icodec_data ftm_headset_l_adie_lp_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_headset_l_adie_lp_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_headset_l_adie_lp_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_headset_l_adie_lp_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_headset_l_adie_lp_rx_data }, +}; + +static struct adie_codec_action_unit ftm_headset_r_adie_lp_rx_actions[] = + FTM_HPH_PRI_AB_CPLS_MONO_LB_RIGHT; + +static struct adie_codec_hwsetting_entry ftm_headset_r_adie_lp_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_headset_r_adie_lp_rx_actions, + .action_sz = ARRAY_SIZE(ftm_headset_r_adie_lp_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_headset_r_adie_lp_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_headset_r_adie_lp_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_headset_r_adie_lp_rx_settings), +}; + +static struct snddev_icodec_data ftm_headset_r_adie_lp_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_headset_r_adie_lp_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_headset_r_adie_lp_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .voltage_on = msm_snddev_voltage_on, + .voltage_off = msm_snddev_voltage_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_headset_r_adie_lp_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_headset_r_adie_lp_rx_data }, +}; + +static struct adie_codec_action_unit ftm_spkr_l_rx_lp_actions[] = + FTM_SPKR_L_RX; + +static struct adie_codec_hwsetting_entry ftm_spkr_l_rx_lp_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_spkr_l_rx_lp_actions, + .action_sz = ARRAY_SIZE(ftm_spkr_l_rx_lp_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_spkr_l_rx_lp_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_spkr_l_rx_lp_settings, + .setting_sz = ARRAY_SIZE(ftm_spkr_l_rx_lp_settings), +}; + +static struct snddev_icodec_data ftm_spkr_l_rx_lp_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_spk_l_adie_lp_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_spkr_l_rx_lp_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_spk_l_adie_lp_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_spkr_l_rx_lp_data}, +}; + +static struct adie_codec_action_unit ftm_spkr_r_adie_lp_rx_actions[] = + FTM_SPKR_RX_LB; + +static struct adie_codec_hwsetting_entry ftm_spkr_r_adie_lp_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_spkr_r_adie_lp_rx_actions, + .action_sz = ARRAY_SIZE(ftm_spkr_r_adie_lp_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_spkr_r_adie_lp_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_spkr_r_adie_lp_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_spkr_r_adie_lp_rx_settings), +}; + +static struct snddev_icodec_data ftm_spkr_r_adie_lp_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_spk_r_adie_lp_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_spkr_r_adie_lp_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_spk_r_adie_lp_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_spkr_r_adie_lp_rx_data}, +}; + +static struct adie_codec_action_unit ftm_spkr_adie_lp_rx_actions[] = + FTM_SPKR_RX_LB; + +static struct adie_codec_hwsetting_entry ftm_spkr_adie_lp_rx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_spkr_adie_lp_rx_actions, + .action_sz = ARRAY_SIZE(ftm_spkr_adie_lp_rx_actions), + }, +}; + +static struct adie_codec_dev_profile ftm_spkr_adie_lp_rx_profile = { + .path_type = ADIE_CODEC_RX, + .settings = ftm_spkr_adie_lp_rx_settings, + .setting_sz = ARRAY_SIZE(ftm_spkr_adie_lp_rx_settings), +}; + +static struct snddev_icodec_data ftm_spkr_adie_lp_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "ftm_spk_adie_lp_rx", + .copp_id = PRIMARY_I2S_RX, + .profile = &ftm_spkr_adie_lp_rx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_poweramp_on, + .pamp_off = msm_snddev_poweramp_off, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_spk_adie_lp_rx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_spkr_adie_lp_rx_data}, +}; + +static struct adie_codec_action_unit ftm_handset_dual_tx_lp_actions[] = + FTM_AMIC_DUAL_HANDSET_TX_LB; + +static struct adie_codec_hwsetting_entry ftm_handset_dual_tx_lp_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_handset_dual_tx_lp_actions, + .action_sz = ARRAY_SIZE(ftm_handset_dual_tx_lp_actions), + } +}; + +static struct adie_codec_dev_profile ftm_handset_dual_tx_lp_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_handset_dual_tx_lp_settings, + .setting_sz = ARRAY_SIZE(ftm_handset_dual_tx_lp_settings), +}; + +static struct snddev_icodec_data ftm_handset_dual_tx_lp_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "handset_mic1_handset_mic2", + .copp_id = 1, + .profile = &ftm_handset_dual_tx_lp_profile, + .channel_mode = 2, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_amic_power, + .pamp_off = msm_snddev_disable_amic_power, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_handset_dual_tx_lp_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_handset_dual_tx_lp_data }, +}; + +static struct adie_codec_action_unit ftm_handset_mic_adie_lp_tx_actions[] = + FTM_HANDSET_LB_TX; + +static struct adie_codec_hwsetting_entry + ftm_handset_mic_adie_lp_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_handset_mic_adie_lp_tx_actions, + .action_sz = ARRAY_SIZE(ftm_handset_mic_adie_lp_tx_actions), + } +}; + +static struct adie_codec_dev_profile ftm_handset_mic_adie_lp_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_handset_mic_adie_lp_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_handset_mic_adie_lp_tx_settings), +}; + +static struct snddev_icodec_data ftm_handset_mic_adie_lp_tx_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "ftm_handset_mic_adie_lp_tx", + .copp_id = 1, + .profile = &ftm_handset_mic_adie_lp_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .pamp_on = msm_snddev_enable_amic_power, + .pamp_off = msm_snddev_disable_amic_power, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_handset_mic_adie_lp_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_handset_mic_adie_lp_tx_data }, +}; + +static struct adie_codec_action_unit ftm_headset_mic_adie_lp_tx_actions[] = + FTM_HEADSET_LB_TX; + +static struct adie_codec_hwsetting_entry + ftm_headset_mic_adie_lp_tx_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = ftm_headset_mic_adie_lp_tx_actions, + .action_sz = ARRAY_SIZE(ftm_headset_mic_adie_lp_tx_actions), + } +}; + +static struct adie_codec_dev_profile ftm_headset_mic_adie_lp_tx_profile = { + .path_type = ADIE_CODEC_TX, + .settings = ftm_headset_mic_adie_lp_tx_settings, + .setting_sz = ARRAY_SIZE(ftm_headset_mic_adie_lp_tx_settings), +}; + +static struct snddev_icodec_data ftm_headset_mic_adie_lp_tx_data = { + .capability = (SNDDEV_CAP_TX | SNDDEV_CAP_VOICE), + .name = "ftm_headset_mic_adie_lp_tx", + .copp_id = PRIMARY_I2S_TX, + .profile = &ftm_headset_mic_adie_lp_tx_profile, + .channel_mode = 1, + .default_sample_rate = 48000, + .dev_vol_type = SNDDEV_DEV_VOL_DIGITAL, +}; + +static struct platform_device ftm_headset_mic_adie_lp_tx_device = { + .name = "snddev_icodec", + .dev = { .platform_data = &ftm_headset_mic_adie_lp_tx_data }, +}; +#endif /* CONFIG_MSM8X60_FTM_AUDIO_DEVICES */ + +static struct snddev_virtual_data snddev_uplink_rx_data = { + .capability = SNDDEV_CAP_RX, + .name = "uplink_rx", + .copp_id = VOICE_PLAYBACK_TX, +}; + +static struct platform_device msm_uplink_rx_device = { + .name = "snddev_virtual", + .dev = { .platform_data = &snddev_uplink_rx_data }, +}; + +static struct snddev_hdmi_data snddev_hdmi_non_linear_pcm_rx_data = { + .capability = SNDDEV_CAP_RX , + .name = "hdmi_pass_through", + .default_sample_rate = 48000, + .on_apps = 1, +}; + +static struct platform_device msm_snddev_hdmi_non_linear_pcm_rx_device = { + .name = "snddev_hdmi", + .dev = { .platform_data = &snddev_hdmi_non_linear_pcm_rx_data }, +}; + + +#ifdef CONFIG_DEBUG_FS +static struct adie_codec_action_unit + ihs_stereo_rx_class_d_legacy_48KHz_osr256_actions[] = + HPH_PRI_D_LEG_STEREO; + +static struct adie_codec_hwsetting_entry + ihs_stereo_rx_class_d_legacy_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = + ihs_stereo_rx_class_d_legacy_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE + (ihs_stereo_rx_class_d_legacy_48KHz_osr256_actions), + } +}; + +static struct adie_codec_action_unit + ihs_stereo_rx_class_ab_legacy_48KHz_osr256_actions[] = + HPH_PRI_AB_LEG_STEREO; + +static struct adie_codec_hwsetting_entry + ihs_stereo_rx_class_ab_legacy_settings[] = { + { + .freq_plan = 48000, + .osr = 256, + .actions = + ihs_stereo_rx_class_ab_legacy_48KHz_osr256_actions, + .action_sz = ARRAY_SIZE + (ihs_stereo_rx_class_ab_legacy_48KHz_osr256_actions), + } +}; + +static void snddev_hsed_config_modify_setting(int type) +{ + struct platform_device *device; + struct snddev_icodec_data *icodec_data; + + device = &msm_headset_stereo_device; + icodec_data = (struct snddev_icodec_data *)device->dev.platform_data; + + if (icodec_data) { + if (type == 1) { + icodec_data->voltage_on = NULL; + icodec_data->voltage_off = NULL; + icodec_data->profile->settings = + ihs_stereo_rx_class_d_legacy_settings; + icodec_data->profile->setting_sz = + ARRAY_SIZE(ihs_stereo_rx_class_d_legacy_settings); + } else if (type == 2) { + icodec_data->voltage_on = NULL; + icodec_data->voltage_off = NULL; + icodec_data->profile->settings = + ihs_stereo_rx_class_ab_legacy_settings; + icodec_data->profile->setting_sz = + ARRAY_SIZE(ihs_stereo_rx_class_ab_legacy_settings); + } + } +} + +static void snddev_hsed_config_restore_setting(void) +{ + struct platform_device *device; + struct snddev_icodec_data *icodec_data; + + device = &msm_headset_stereo_device; + icodec_data = (struct snddev_icodec_data *)device->dev.platform_data; + + if (icodec_data) { + icodec_data->voltage_on = msm_snddev_voltage_on; + icodec_data->voltage_off = msm_snddev_voltage_off; + icodec_data->profile->settings = headset_ab_cpls_settings; + icodec_data->profile->setting_sz = + ARRAY_SIZE(headset_ab_cpls_settings); + } +} + +static ssize_t snddev_hsed_config_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *lb_str = filp->private_data; + char cmd; + + if (get_user(cmd, ubuf)) + return -EFAULT; + + if (!strcmp(lb_str, "msm_hsed_config")) { + switch (cmd) { + case '0': + snddev_hsed_config_restore_setting(); + break; + + case '1': + snddev_hsed_config_modify_setting(1); + break; + + case '2': + snddev_hsed_config_modify_setting(2); + break; + + default: + break; + } + } + return cnt; +} + +static int snddev_hsed_config_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations snddev_hsed_config_debug_fops = { + .open = snddev_hsed_config_debug_open, + .write = snddev_hsed_config_debug_write +}; +#endif + +static struct platform_device *snd_devices_ffa[] __initdata = { + &msm_iearpiece_ffa_device, + &msm_imic_ffa_device, + &msm_ispkr_stereo_device, + &msm_snddev_hdmi_stereo_rx_device, + &msm_headset_mic_device, + &msm_ispkr_mic_device, + &msm_bt_sco_earpiece_device, + &msm_bt_sco_mic_device, + &msm_headset_stereo_device, + &msm_itty_mono_tx_device, + &msm_itty_mono_rx_device, + &msm_mi2s_fm_tx_device, + &msm_mi2s_fm_rx_device, + &msm_hs_dual_mic_endfire_device, + &msm_spkr_dual_mic_endfire_device, + &msm_hs_dual_mic_broadside_device, + &msm_spkr_dual_mic_broadside_device, + &msm_ihs_stereo_speaker_stereo_rx_device, + &msm_anc_headset_device, + &msm_auxpga_lp_hs_device, + &msm_auxpga_lp_lo_device, + &msm_linein_pri_device, + &msm_icodec_gpio_device, + &msm_snddev_hdmi_non_linear_pcm_rx_device, +}; + +static struct platform_device *snd_devices_surf[] __initdata = { + &msm_iearpiece_device, + &msm_imic_device, + &msm_ispkr_stereo_device, + &msm_snddev_hdmi_stereo_rx_device, + &msm_headset_mic_device, + &msm_ispkr_mic_device, + &msm_bt_sco_earpiece_device, + &msm_bt_sco_mic_device, + &msm_headset_stereo_device, + &msm_itty_mono_tx_device, + &msm_itty_mono_rx_device, + &msm_mi2s_fm_tx_device, + &msm_mi2s_fm_rx_device, + &msm_ihs_stereo_speaker_stereo_rx_device, + &msm_auxpga_lp_hs_device, + &msm_auxpga_lp_lo_device, + &msm_linein_pri_device, + &msm_icodec_gpio_device, + &msm_snddev_hdmi_non_linear_pcm_rx_device, +}; + +static struct platform_device *snd_devices_fluid[] __initdata = { + &msm_iearpiece_device, + &msm_imic_device, + &msm_ispkr_stereo_device, + &msm_snddev_hdmi_stereo_rx_device, + &msm_headset_stereo_device, + &msm_headset_mic_device, + &msm_fluid_ispkr_mic_device, + &msm_bt_sco_earpiece_device, + &msm_bt_sco_mic_device, + &msm_mi2s_fm_tx_device, + &msm_mi2s_fm_rx_device, + &msm_anc_headset_device, + &msm_auxpga_lp_hs_device, + &msm_auxpga_lp_lo_device, + &msm_icodec_gpio_device, + &msm_snddev_hdmi_non_linear_pcm_rx_device, +}; + +static struct platform_device *snd_devices_common[] __initdata = { + &msm_aux_pcm_device, + &msm_cdcclk_ctl_device, + &msm_mi2s_device, + &msm_uplink_rx_device, +}; + +#ifdef CONFIG_MSM8X60_FTM_AUDIO_DEVICES +static struct platform_device *snd_devices_ftm[] __initdata = { + &ftm_headset_mono_rx_device, + &ftm_headset_mono_l_rx_device, + &ftm_headset_mono_r_rx_device, + &ftm_headset_mono_diff_rx_device, + &ftm_spkr_mono_rx_device, + &ftm_spkr_l_rx_device, + &ftm_spkr_r_rx_device, + &ftm_spkr_mono_diff_rx_device, + &ftm_linein_l_tx_device, + &ftm_linein_r_tx_device, + &ftm_aux_out_rx_device, + &ftm_dmic1_left_tx_device, + &ftm_dmic1_right_tx_device, + &ftm_dmic1_l_and_r_tx_device, + &ftm_dmic2_left_tx_device, + &ftm_dmic2_right_tx_device, + &ftm_dmic2_l_and_r_tx_device, + &ftm_handset_mic1_aux_in_device, + &ftm_mi2s_sd0_rx_device, + &ftm_mi2s_sd1_rx_device, + &ftm_mi2s_sd2_rx_device, + &ftm_handset_mic_adie_lp_tx_device, + &ftm_headset_mic_adie_lp_tx_device, + &ftm_handset_adie_lp_rx_device, + &ftm_headset_l_adie_lp_rx_device, + &ftm_headset_r_adie_lp_rx_device, + &ftm_spk_l_adie_lp_rx_device, + &ftm_spk_r_adie_lp_rx_device, + &ftm_spk_adie_lp_rx_device, + &ftm_handset_dual_tx_lp_device, +}; +#else +static struct platform_device *snd_devices_ftm[] __initdata = {}; +#endif + + +void __init msm_snddev_init(void) +{ + int i; + int dev_id; + + atomic_set(&pamp_ref_cnt, 0); + atomic_set(&preg_ref_cnt, 0); + + for (i = 0, dev_id = 0; i < ARRAY_SIZE(snd_devices_common); i++) + snd_devices_common[i]->id = dev_id++; + + platform_add_devices(snd_devices_common, + ARRAY_SIZE(snd_devices_common)); + + /* Auto detect device base on machine info */ + if (machine_is_msm8x60_surf() || machine_is_msm8x60_fusion()) { + for (i = 0; i < ARRAY_SIZE(snd_devices_surf); i++) + snd_devices_surf[i]->id = dev_id++; + + platform_add_devices(snd_devices_surf, + ARRAY_SIZE(snd_devices_surf)); + } else if (machine_is_msm8x60_ffa() || + machine_is_msm8x60_fusn_ffa()) { + for (i = 0; i < ARRAY_SIZE(snd_devices_ffa); i++) + snd_devices_ffa[i]->id = dev_id++; + + platform_add_devices(snd_devices_ffa, + ARRAY_SIZE(snd_devices_ffa)); + } else if (machine_is_msm8x60_fluid()) { + for (i = 0; i < ARRAY_SIZE(snd_devices_fluid); i++) + snd_devices_fluid[i]->id = dev_id++; + + platform_add_devices(snd_devices_fluid, + ARRAY_SIZE(snd_devices_fluid)); + } else if (machine_is_msm8x60_surf()) { + for (i = 0; i < ARRAY_SIZE(snd_devices_ftm); i++) + snd_devices_ftm[i]->id = dev_id++; + + platform_add_devices(snd_devices_ftm, + ARRAY_SIZE(snd_devices_ftm)); + } + +#ifdef CONFIG_DEBUG_FS + debugfs_hsed_config = debugfs_create_file("msm_hsed_config", + S_IFREG | S_IRUGO, NULL, + (void *) "msm_hsed_config", &snddev_hsed_config_debug_fops); +#endif +} diff --git a/arch/arm/mach-msm/qdsp6v2/dsp_debug.c b/arch/arm/mach-msm/qdsp6v2/dsp_debug.c new file mode 100644 index 00000000000..4382c2302d4 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/dsp_debug.c @@ -0,0 +1,224 @@ +/* arch/arm/mach-msm/qdsp6/dsp_dump.c + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../proc_comm.h" +#include +#include +#include + +static wait_queue_head_t dsp_wait; +static int dsp_has_crashed; +static int dsp_wait_count; + +static atomic_t dsp_crash_count = ATOMIC_INIT(0); +dsp_state_cb cb_ptr; + +void q6audio_dsp_not_responding(void) +{ + if (cb_ptr) + cb_ptr(DSP_STATE_CRASHED); + if (atomic_add_return(1, &dsp_crash_count) != 1) { + pr_err("q6audio_dsp_not_responding() \ + - parking additional crasher...\n"); + for (;;) + msleep(1000); + } + if (dsp_wait_count) { + dsp_has_crashed = 1; + wake_up(&dsp_wait); + + while (dsp_has_crashed != 2) + wait_event(dsp_wait, dsp_has_crashed == 2); + } else { + pr_err("q6audio_dsp_not_responding() - no waiter?\n"); + } + if (cb_ptr) + cb_ptr(DSP_STATE_CRASH_DUMP_DONE); + + BUG(); +} + +static int dsp_open(struct inode *inode, struct file *file) +{ + return 0; +} + +#define DSP_NMI_ADDR 0x28800010 + +static ssize_t dsp_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + char cmd[32]; + void __iomem *ptr; + unsigned int flags = MSM_SUBSYSTEM_MAP_KADDR | MSM_SUBSYSTEM_MAP_CACHED; + struct msm_mapped_buffer *mem_buffer; + + if (count >= sizeof(cmd)) + return -EINVAL; + if (copy_from_user(cmd, buf, count)) + return -EFAULT; + cmd[count] = 0; + + if ((count > 1) && (cmd[count-1] == '\n')) + cmd[count-1] = 0; + + if (!strcmp(cmd, "wait-for-crash")) { + while (!dsp_has_crashed) { + int res; + dsp_wait_count++; + res = wait_event_interruptible(dsp_wait, + dsp_has_crashed); + if (res < 0) { + dsp_wait_count--; + return res; + } + } + /* assert DSP NMI */ + mem_buffer = msm_subsystem_map_buffer(DSP_NMI_ADDR, 0x16, flags, + NULL, 0); + if (IS_ERR((void *)mem_buffer)) { + pr_err("%s:map_buffer failed, error = %ld\n", __func__, + PTR_ERR((void *)mem_buffer)); + return -ENOMEM; + } + ptr = mem_buffer->vaddr; + if (!ptr) { + pr_err("Unable to map DSP NMI\n"); + return -EFAULT; + } + writel(0x1, (void *)ptr); + if (msm_subsystem_unmap_buffer(mem_buffer) < 0) + pr_err("%s:unmap buffer failed\n", __func__); + } else if (!strcmp(cmd, "boom")) { + q6audio_dsp_not_responding(); + } else if (!strcmp(cmd, "continue-crash")) { + dsp_has_crashed = 2; + wake_up(&dsp_wait); + } else { + pr_err("[%s:%s] unknown dsp_debug command: %s\n", __MM_FILE__, + __func__, cmd); + } + + return count; +} + +#define DSP_RAM_BASE 0x46700000 +#define DSP_RAM_SIZE 0x2000000 + +static unsigned copy_ok_count; + +static ssize_t dsp_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + size_t actual = 0; + size_t mapsize = PAGE_SIZE; + unsigned addr; + void __iomem *ptr; + unsigned int flags = MSM_SUBSYSTEM_MAP_KADDR | MSM_SUBSYSTEM_MAP_CACHED; + struct msm_mapped_buffer *mem_buffer; + + if (*pos >= DSP_RAM_SIZE) + return 0; + + if (*pos & (PAGE_SIZE - 1)) + return -EINVAL; + + addr = (*pos + DSP_RAM_BASE); + + /* don't blow up if we're unaligned */ + if (addr & (PAGE_SIZE - 1)) + mapsize *= 2; + + while (count >= PAGE_SIZE) { + mem_buffer = msm_subsystem_map_buffer(addr, mapsize, flags, + NULL, 0); + if (IS_ERR((void *)mem_buffer)) { + pr_err("%s:map_buffer failed, error = %ld\n", + __func__, PTR_ERR((void *)mem_buffer)); + return -ENOMEM; + } + ptr = mem_buffer->vaddr; + if (!ptr) { + pr_err("[%s:%s] map error @ %x\n", __MM_FILE__, + __func__, addr); + return -EFAULT; + } + if (copy_to_user(buf, ptr, PAGE_SIZE)) { + if (msm_subsystem_unmap_buffer(mem_buffer) < 0) + pr_err("%s: unmap buffer failed\n", __func__); + pr_err("[%s:%s] copy error @ %p\n", __MM_FILE__, + __func__, buf); + return -EFAULT; + } + copy_ok_count += PAGE_SIZE; + if (msm_subsystem_unmap_buffer(mem_buffer) < 0) + pr_err("%s: unmap buffer failed\n", __func__); + addr += PAGE_SIZE; + buf += PAGE_SIZE; + actual += PAGE_SIZE; + count -= PAGE_SIZE; + } + + *pos += actual; + return actual; +} + +static int dsp_release(struct inode *inode, struct file *file) +{ + return 0; +} + +int dsp_debug_register(dsp_state_cb ptr) +{ + if (ptr == NULL) + return -EINVAL; + cb_ptr = ptr; + + return 0; +} + +static const struct file_operations dsp_fops = { + .owner = THIS_MODULE, + .open = dsp_open, + .read = dsp_read, + .write = dsp_write, + .release = dsp_release, +}; + +static struct miscdevice dsp_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "dsp_debug", + .fops = &dsp_fops, +}; + + +static int __init dsp_init(void) +{ + init_waitqueue_head(&dsp_wait); + return misc_register(&dsp_misc); +} + +device_initcall(dsp_init); diff --git a/arch/arm/mach-msm/qdsp6v2/evrc_in.c b/arch/arm/mach-msm/qdsp6v2/evrc_in.c new file mode 100644 index 00000000000..0f566204927 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/evrc_in.c @@ -0,0 +1,332 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audio_utils.h" + +/* Buffer with meta*/ +#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in)) + +/* Maximum 10 frames in buffer with meta */ +#define FRAME_SIZE (1 + ((23+sizeof(struct meta_out_dsp)) * 10)) + +void q6asm_evrc_in_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6audio_in * audio = (struct q6audio_in *)priv; + unsigned long flags; + + pr_debug("%s:session id %d: opcode - %d\n", __func__, + audio->ac->session, opcode); + + spin_lock_irqsave(&audio->dsp_lock, flags); + switch (opcode) { + case ASM_DATA_EVENT_READ_DONE: + audio_in_get_dsp_frames(audio, token, payload); + break; + case ASM_DATA_EVENT_WRITE_DONE: + atomic_inc(&audio->in_count); + wake_up(&audio->write_wait); + break; + case ASM_DATA_CMDRSP_EOS: + audio->eos_rsp = 1; + wake_up(&audio->read_wait); + break; + case ASM_STREAM_CMDRSP_GET_ENCDEC_PARAM: + break; + case ASM_STREAM_CMDRSP_GET_PP_PARAMS: + break; + case ASM_SESSION_EVENT_TX_OVERFLOW: + pr_err("%s:session id %d: ASM_SESSION_EVENT_TX_OVERFLOW\n", + __func__, audio->ac->session); + break; + default: + pr_err("%s:session id %d: Ignore opcode[0x%x]\n", __func__, + audio->ac->session, opcode); + break; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} + +/* ------------------- device --------------------- */ +static long evrc_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct q6audio_in *audio = file->private_data; + int rc = 0; + int cnt = 0; + + switch (cmd) { + case AUDIO_START: { + struct msm_audio_evrc_enc_config *enc_cfg; + enc_cfg = audio->enc_cfg; + pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__, + audio->ac->session, audio->buf_alloc); + if (audio->enabled == 1) { + pr_info("%s:AUDIO_START already over\n", __func__); + rc = 0; + break; + } + rc = audio_in_buf_alloc(audio); + if (rc < 0) { + pr_err("%s:session id %d: buffer allocation failed\n", + __func__, audio->ac->session); + break; + } + + /* rate_modulation_cmd set to zero + currently not configurable from user space */ + rc = q6asm_enc_cfg_blk_evrc(audio->ac, + audio->buf_cfg.frames_per_buf, + enc_cfg->min_bit_rate, + enc_cfg->max_bit_rate, 0); + + if (rc < 0) { + pr_err("%s:session id %d: cmd evrc media format block\ + failed\n", __func__, audio->ac->session); + break; + } + if (audio->feedback == NON_TUNNEL_MODE) { + rc = q6asm_media_format_block_pcm(audio->ac, + audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + + if (rc < 0) { + pr_err("%s:session id %d: media format block\ + failed\n", __func__, audio->ac->session); + break; + } + } + pr_debug("%s:session id %d: AUDIO_START enable[%d]\n", + __func__, audio->ac->session, audio->enabled); + rc = audio_in_enable(audio); + if (!rc) { + audio->enabled = 1; + } else { + audio->enabled = 0; + pr_err("%s:session id %d: Audio Start procedure failed\ + rc=%d\n", __func__, audio->ac->session, rc); + break; + } + while (cnt++ < audio->str_cfg.buffer_count) + q6asm_read(audio->ac); /* Push buffer to DSP */ + rc = 0; + pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n", + __func__, audio->ac->session, audio->enabled); + break; + } + case AUDIO_STOP: { + pr_debug("%s:session id %d: AUDIO_STOP\n", __func__, + audio->ac->session); + rc = audio_in_disable(audio); + if (rc < 0) { + pr_err("%s:session id %d: Audio Stop procedure failed\ + rc=%d\n", __func__, audio->ac->session, rc); + break; + } + break; + } + case AUDIO_GET_EVRC_ENC_CONFIG: { + if (copy_to_user((void *)arg, audio->enc_cfg, + sizeof(struct msm_audio_evrc_enc_config))) + rc = -EFAULT; + break; + } + case AUDIO_SET_EVRC_ENC_CONFIG: { + struct msm_audio_evrc_enc_config cfg; + struct msm_audio_evrc_enc_config *enc_cfg; + enc_cfg = audio->enc_cfg; + + if (copy_from_user(&cfg, (void *) arg, + sizeof(struct msm_audio_evrc_enc_config))) { + rc = -EFAULT; + break; + } + + if (cfg.min_bit_rate > 4 || + cfg.min_bit_rate < 1 || + (cfg.min_bit_rate == 2)) { + pr_err("%s:session id %d: invalid min bitrate\n", + __func__, audio->ac->session); + rc = -EINVAL; + break; + } + if (cfg.max_bit_rate > 4 || + cfg.max_bit_rate < 1 || + (cfg.max_bit_rate == 2)) { + pr_err("%s:session id %d: invalid max bitrate\n", + __func__, audio->ac->session); + rc = -EINVAL; + break; + } + enc_cfg->min_bit_rate = cfg.min_bit_rate; + enc_cfg->max_bit_rate = cfg.max_bit_rate; + pr_debug("%s:session id %d: min_bit_rate= 0x%x\ + max_bit_rate=0x%x\n", __func__, + audio->ac->session, enc_cfg->min_bit_rate, + enc_cfg->max_bit_rate); + break; + } + default: + rc = -EINVAL; + } + return rc; +} + +static int evrc_in_open(struct inode *inode, struct file *file) +{ + struct q6audio_in *audio = NULL; + struct msm_audio_evrc_enc_config *enc_cfg; + int rc = 0; + + audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL); + + if (audio == NULL) { + pr_err("%s:session id %d: Could not allocate memory for evrc\ + driver\n", __func__, audio->ac->session); + return -ENOMEM; + } + /* Allocate memory for encoder config param */ + audio->enc_cfg = kzalloc(sizeof(struct msm_audio_evrc_enc_config), + GFP_KERNEL); + if (audio->enc_cfg == NULL) { + pr_err("%s:session id %d: Could not allocate memory for aac\ + config param\n", __func__, audio->ac->session); + kfree(audio); + return -ENOMEM; + } + enc_cfg = audio->enc_cfg; + mutex_init(&audio->lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->write_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->write_wait); + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + audio->min_frame_size = 23; + audio->max_frames_per_buf = 10; + audio->pcm_cfg.buffer_size = PCM_BUF_SIZE; + audio->pcm_cfg.buffer_count = PCM_BUF_COUNT; + enc_cfg->min_bit_rate = 4; + enc_cfg->max_bit_rate = 4; + audio->pcm_cfg.channel_count = 1; + audio->pcm_cfg.sample_rate = 8000; + audio->buf_cfg.meta_info_enable = 0x01; + audio->buf_cfg.frames_per_buf = 0x01; + + audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_evrc_in_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("%s:session id %d: Could not allocate memory for audio\ + client\n", __func__, audio->ac->session); + kfree(audio->enc_cfg); + kfree(audio); + return -ENOMEM; + } + + /* open evrc encoder in T/NT mode */ + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->feedback = NON_TUNNEL_MODE; + rc = q6asm_open_read_write(audio->ac, FORMAT_EVRC, + FORMAT_LINEAR_PCM); + if (rc < 0) { + pr_err("%s:session id %d: NT mode Open failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + pr_info("%s:session id %d: NT mode encoder success\n", + __func__, audio->ac->session); + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->feedback = TUNNEL_MODE; + rc = q6asm_open_read(audio->ac, FORMAT_EVRC); + if (rc < 0) { + pr_err("%s:session id %d: T mode Open failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + /* register for tx overflow (valid for tunnel mode only) */ + rc = q6asm_reg_tx_overflow(audio->ac, 0x01); + if (rc < 0) { + pr_err("%s:session id %d: TX Overflow registration\ + failed rc=%d\n", __func__, + audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + pr_info("%s:session id %d: T mode encoder success\n", __func__, + audio->ac->session); + } else { + pr_err("%s:session id %d: Unexpected mode\n", __func__, + audio->ac->session); + rc = -EACCES; + goto fail; + } + + audio->opened = 1; + atomic_set(&audio->in_count, PCM_BUF_COUNT); + atomic_set(&audio->out_count, 0x00); + audio->enc_ioctl = evrc_in_ioctl; + file->private_data = audio; + + pr_info("%s:session id %d: success\n", __func__, audio->ac->session); + return 0; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio->enc_cfg); + kfree(audio); + return rc; +} + +static const struct file_operations audio_in_fops = { + .owner = THIS_MODULE, + .open = evrc_in_open, + .release = audio_in_release, + .read = audio_in_read, + .write = audio_in_write, + .unlocked_ioctl = audio_in_ioctl, +}; + +struct miscdevice audio_evrc_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_evrc_in", + .fops = &audio_in_fops, +}; + +static int __init evrc_in_init(void) +{ + return misc_register(&audio_evrc_in_misc); +} + +device_initcall(evrc_in_init); diff --git a/arch/arm/mach-msm/qdsp6v2/fm.c b/arch/arm/mach-msm/qdsp6v2/fm.c new file mode 100644 index 00000000000..9cf2723ccb7 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/fm.c @@ -0,0 +1,262 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * + * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5v2/audio_mp3.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SESSION_ID_FM (MAX_SESSIONS + 1) +#define FM_ENABLE 0x1 +#define FM_DISABLE 0x0 +#define FM_COPP 0x7 + +struct audio { + struct mutex lock; + + int opened; + int enabled; + int running; + + uint16_t fm_source; + uint16_t fm_src_copp_id; + uint16_t fm_dest; + uint16_t fm_dst_copp_id; + uint16_t dec_id; + uint32_t device_events; + uint16_t volume; +}; + + +static struct audio fm_audio; +static int fm_audio_enable(struct audio *audio) +{ + if (audio->enabled) + return 0; + + pr_info("%s: fm dest= %08x fm_source = %08x\n", __func__, + audio->fm_dst_copp_id, audio->fm_src_copp_id); + + /* do afe loopback here */ + + if (audio->fm_dest && audio->fm_source) { + if (afe_loopback(FM_ENABLE, audio->fm_dst_copp_id, + audio->fm_src_copp_id) < 0) { + pr_err("%s: afe_loopback failed\n", __func__); + } + + audio->running = 1; + } + + audio->enabled = 1; + return 0; +} + +static void fm_audio_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct audio *audio = (struct audio *) private_data; + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + pr_info("%s :AUDDEV_EVT_DEV_RDY\n", __func__); + if (evt_payload->routing_id == FM_COPP) { + audio->fm_source = 1; + audio->fm_src_copp_id = FM_COPP; + } else { + audio->fm_dest = 1; + audio->fm_dst_copp_id = evt_payload->routing_id; + } + + if (audio->enabled && + audio->fm_dest && + audio->fm_source) { + + afe_loopback_gain(audio->fm_src_copp_id, + audio->volume); + afe_loopback(FM_ENABLE, audio->fm_dst_copp_id, + audio->fm_src_copp_id); + audio->running = 1; + } + break; + case AUDDEV_EVT_DEV_RLS: + pr_info("%s: AUDDEV_EVT_DEV_RLS\n", __func__); + if (evt_payload->routing_id == audio->fm_src_copp_id) + audio->fm_source = 0; + else + audio->fm_dest = 0; + if (audio->running + && (!audio->fm_dest || !audio->fm_source)) { + afe_loopback(FM_DISABLE, audio->fm_dst_copp_id, + audio->fm_src_copp_id); + audio->running = 0; + } else { + pr_err("%s: device switch happened\n", __func__); + } + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + pr_debug("%s: AUDDEV_EVT_STREAM_VOL_CHG\n", __func__); + if (audio->fm_source) { + audio->volume = evt_payload->session_vol; + afe_loopback_gain(audio->fm_src_copp_id, + audio->volume); + } + break; + + default: + pr_err("%s: ERROR:wrong event %08x\n", __func__, evt_id); + break; + } +} + +static int fm_audio_disable(struct audio *audio) +{ + + /* break the AFE loopback here */ + afe_loopback(FM_DISABLE, audio->fm_dst_copp_id, audio->fm_src_copp_id); + return 0; +} + +static long fm_audio_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct audio *audio = file->private_data; + int rc = -EINVAL; + + + mutex_lock(&audio->lock); + switch (cmd) { + case AUDIO_START: + pr_info("%s: AUDIO_START\n", __func__); + rc = fm_audio_enable(audio); + break; + case AUDIO_STOP: + pr_info("%s: AUDIO_STOP\n", __func__); + rc = fm_audio_disable(audio); + audio->running = 0; + audio->enabled = 0; + break; + case AUDIO_GET_SESSION_ID: + if (copy_to_user((void *) arg, &audio->dec_id, + sizeof(unsigned short))) + rc = -EFAULT; + else + rc = 0; + break; + default: + rc = -EINVAL; + pr_err("%s: Un supported IOCTL\n", __func__); + } + mutex_unlock(&audio->lock); + return rc; +} + +static int fm_audio_release(struct inode *inode, struct file *file) +{ + struct audio *audio = file->private_data; + + pr_debug("audio instance 0x%08x freeing\n", (int)audio); + mutex_lock(&audio->lock); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id); + fm_audio_disable(audio); + audio->running = 0; + audio->enabled = 0; + audio->opened = 0; + mutex_unlock(&audio->lock); + return 0; +} + +static int fm_audio_open(struct inode *inode, struct file *file) +{ + struct audio *audio = &fm_audio; + int rc = 0; + + + if (audio->opened) + return -EPERM; + + /* Allocate the decoder */ + audio->dec_id = SESSION_ID_FM; + + audio->running = 0; + audio->fm_source = 0; + audio->fm_dest = 0; + + audio->device_events = AUDDEV_EVT_DEV_RDY + |AUDDEV_EVT_DEV_RLS| + AUDDEV_EVT_STREAM_VOL_CHG; + + rc = auddev_register_evt_listner(audio->device_events, + AUDDEV_CLNT_DEC, + audio->dec_id, + fm_audio_listner, + (void *)audio); + + if (rc) { + pr_err("%s: failed to register listnet\n", __func__); + goto event_err; + } + + audio->opened = 1; + file->private_data = audio; + +event_err: + return rc; +} + +static const struct file_operations audio_fm_fops = { + .owner = THIS_MODULE, + .open = fm_audio_open, + .release = fm_audio_release, + .unlocked_ioctl = fm_audio_ioctl, +}; + +struct miscdevice audio_fm_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_fm", + .fops = &audio_fm_fops, +}; + +static int __init fm_audio_init(void) +{ + struct audio *audio = &fm_audio; + + mutex_init(&audio->lock); + return misc_register(&audio_fm_misc); +} + +device_initcall(fm_audio_init); + +MODULE_DESCRIPTION("MSM FM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/lpa_if_hdmi.c b/arch/arm/mach-msm/qdsp6v2/lpa_if_hdmi.c new file mode 100644 index 00000000000..11fff288ecb --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/lpa_if_hdmi.c @@ -0,0 +1,381 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6core.h" + +#define NUM_FRA_IN_BLOCK 192 +#define NUM_SAMP_PER_CH_PER_AC3_FRAME 1536 +#define AC3_REP_PER 1536 /* num of 60958 Frames */ +#define FRAME_60958_SZ 8 /* bytes */ +#define PREABLE_61937_SZ_16_BIT 4 /* in 16 bit words */ + + +#define MAX_AC3_FRA_SZ_16_BIT 1920 /* in 16 bit words */ +#define DMA_PERIOD_SZ (AC3_REP_PER * FRAME_60958_SZ) +#define DMA_BUF_SZ (DMA_PERIOD_SZ * 2) +#define USER_BUF_SZ DMA_PERIOD_SZ +#define DMA_ALLOC_BUF_SZ (SZ_4K * 6) + +#define HDMI_AUDIO_FIFO_WATER_MARK 4 + +struct audio_buffer { + dma_addr_t phys; + void *data; + uint32_t size; + uint32_t used; /* 1 = CPU is waiting for DMA to consume this buf */ + uint32_t actual_size; /* actual number of bytes read by DMA */ +}; + +struct lpa_if { + struct mutex lock; + struct msm_audio_config cfg; + struct audio_buffer audio_buf[2]; + int cpu_buf; /* next buffer the CPU will touch */ + int dma_buf; /* next buffer the DMA will touch */ + u8 *buffer; + dma_addr_t buffer_phys; + u32 dma_ch; + wait_queue_head_t wait; + u32 config; +}; + +static struct lpa_if *lpa_if_ptr; + +static unsigned int dma_buf_index; + +static irqreturn_t lpa_if_irq(int intrsrc, void *data) +{ + struct lpa_if *lpa_if = data; + int dma_ch = 0; + unsigned int pending; + + if (lpa_if) + dma_ch = lpa_if->dma_ch; + else { + pr_err("invalid lpa_if\n"); + return IRQ_NONE; + } + + pending = (intrsrc + & (UNDER_CH(dma_ch) | PER_CH(dma_ch) | ERR_CH(dma_ch))); + + pr_debug("pending = 0x%08x\n", pending); + + if (pending & UNDER_CH(dma_ch)) + pr_err("under run\n"); + if (pending & ERR_CH(dma_ch)) + pr_err("DMA %x Master Error\n", dma_ch); + + if (pending & PER_CH(dma_ch)) { + + lpa_if->audio_buf[lpa_if->dma_buf].used = 0; + + pr_debug("dma_buf %d used %d\n", lpa_if->dma_buf, + lpa_if->audio_buf[lpa_if->dma_buf].used); + + lpa_if->dma_buf ^= 1; + + wake_up(&lpa_if->wait); + } + return IRQ_HANDLED; +} + + +int lpa_if_start(struct lpa_if *lpa_if) +{ + pr_debug("buf1 0x%x, buf2 0x%x dma_ch %d\n", + (unsigned int)lpa_if->audio_buf[0].data, + (unsigned int)lpa_if->audio_buf[1].data, lpa_if->dma_ch); + + dai_start_hdmi(lpa_if->dma_ch); + + mb(); + + hdmi_audio_enable(1, HDMI_AUDIO_FIFO_WATER_MARK); + mb(); + return 0; +} + +int lpa_if_config(struct lpa_if *lpa_if) +{ + struct dai_dma_params dma_params; + + dma_params.src_start = lpa_if->buffer_phys; + dma_params.buffer = lpa_if->buffer; + dma_params.buffer_size = DMA_BUF_SZ; + dma_params.period_size = DMA_PERIOD_SZ; + dma_params.channels = 2; + + lpa_if->dma_ch = 0; + + dai_set_params(lpa_if->dma_ch, &dma_params); + + register_dma_irq_handler(lpa_if->dma_ch, lpa_if_irq, (void *)lpa_if); + + mb(); + pr_debug("lpa_if 0x%08x buf_vir 0x%08x buf_phys 0x%08x " + "config %u\n", (u32)lpa_if, (u32) (lpa_if->buffer), + lpa_if->buffer_phys, lpa_if->config); + + pr_debug("user_buf_cnt %u user_buf_size %u\n", + lpa_if->cfg.buffer_count, lpa_if->cfg.buffer_size); + + lpa_if->config = 1; + + lpa_if_start(lpa_if); + + return 0; +} + + +static long lpa_if_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct lpa_if *lpa_if = file->private_data; + int rc = 0; + + pr_debug("cmd %u\n", cmd); + + mutex_lock(&lpa_if->lock); + + switch (cmd) { + case AUDIO_START: + pr_debug("AUDIO_START\n"); + + if (dma_buf_index == 2) { + if (!lpa_if->config) { + rc = lpa_if_config(lpa_if); + if (rc) + pr_err("lpa_if_config failed\n"); + } + } else { + pr_err("did not receved two buffer for " + "AUDIO_STAR\n"); + rc = -EPERM; + } + break; + + case AUDIO_STOP: + pr_debug("AUDIO_STOP\n"); + break; + + case AUDIO_FLUSH: + pr_debug("AUDIO_FLUSH\n"); + break; + + + case AUDIO_GET_CONFIG: + pr_debug("AUDIO_GET_CONFIG\n"); + if (copy_to_user((void *)arg, &lpa_if->cfg, + sizeof(struct msm_audio_config))) { + rc = -EFAULT; + } + break; + + default: + pr_err("UnKnown Ioctl\n"); + rc = -EINVAL; + } + + mutex_unlock(&lpa_if->lock); + + return rc; +} + + +static int lpa_if_open(struct inode *inode, struct file *file) +{ + struct lpa_if *lpa_if; + + pr_debug("\n"); + + file->private_data = lpa_if_ptr; + lpa_if = lpa_if_ptr; + + lpa_if->cfg.buffer_count = 2; + lpa_if->cfg.buffer_size = USER_BUF_SZ; + + lpa_if->audio_buf[0].phys = lpa_if->buffer_phys; + lpa_if->audio_buf[0].data = lpa_if->buffer; + lpa_if->audio_buf[0].size = DMA_PERIOD_SZ; + lpa_if->audio_buf[0].used = 0; + + lpa_if->audio_buf[1].phys = lpa_if->buffer_phys + DMA_PERIOD_SZ; + lpa_if->audio_buf[1].data = lpa_if->buffer + DMA_PERIOD_SZ; + lpa_if->audio_buf[1].size = DMA_PERIOD_SZ; + lpa_if->audio_buf[1].used = 0; + + dma_buf_index = 0; + + core_req_bus_bandwith(AUDIO_IF_BUS_ID, 100000, 0); + + return 0; +} + +static ssize_t lpa_if_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct lpa_if *lpa_if = file->private_data; + struct audio_buffer *ab; + const char __user *start = buf; + int xfer, rc; + + pr_debug("count %u cpu_buf %d dma_buf %d\n", + (unsigned int)count, lpa_if->cpu_buf, lpa_if->dma_buf); + + mutex_lock(&lpa_if->lock); + + if (dma_buf_index < 2) { + + ab = lpa_if->audio_buf + dma_buf_index; + + if (copy_from_user(ab->data, buf, count)) { + pr_err("copy from user failed\n"); + rc = 0; + goto end; + + } + pr_debug("prefill: count %u audio_buf[%u].size %u\n", + count, dma_buf_index, ab->size); + + ab->used = 1; + dma_buf_index++; + rc = count; + goto end; + } + + if (lpa_if->config != 1) { + pr_err("AUDIO_START did not happen\n"); + rc = 0; + goto end; + } + + while (count > 0) { + + ab = lpa_if->audio_buf + lpa_if->cpu_buf; + + rc = wait_event_timeout(lpa_if->wait, (ab->used == 0), 10 * HZ); + if (!rc) { + pr_err("wait_event_timeout failed\n"); + rc = buf - start; + goto end; + } + + xfer = count; + + if (xfer > USER_BUF_SZ) + xfer = USER_BUF_SZ; + + if (copy_from_user(ab->data, buf, xfer)) { + pr_err("copy from user failed\n"); + rc = buf - start; + goto end; + } + + mb(); + buf += xfer; + count -= xfer; + ab->used = 1; + + pr_debug("xfer %d, size %d, used %d cpu_buf %d\n", + xfer, ab->size, ab->used, lpa_if->cpu_buf); + + lpa_if->cpu_buf ^= 1; + } + rc = buf - start; +end: + mutex_unlock(&lpa_if->lock); + return rc; +} + +static int lpa_if_release(struct inode *inode, struct file *file) +{ + struct lpa_if *lpa_if = file->private_data; + hdmi_audio_enable(0, HDMI_AUDIO_FIFO_WATER_MARK); + + smp_mb(); + + if (lpa_if->config) { + unregister_dma_irq_handler(lpa_if->dma_ch); + dai_stop_hdmi(lpa_if->dma_ch); + lpa_if->config = 0; + } + return 0; +} + +static const struct file_operations lpa_if_fops = { + .owner = THIS_MODULE, + .open = lpa_if_open, + .write = lpa_if_write, + .release = lpa_if_release, + .unlocked_ioctl = lpa_if_ioctl, +}; + +struct miscdevice lpa_if_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_lpa_if_out", + .fops = &lpa_if_fops, +}; + +static int __init lpa_if_init(void) +{ + int rc; + + lpa_if_ptr = kzalloc(sizeof(struct lpa_if), GFP_KERNEL); + if (!lpa_if_ptr) { + pr_info("No mem for lpa-if\n"); + return -ENOMEM; + } + + mutex_init(&lpa_if_ptr->lock); + init_waitqueue_head(&lpa_if_ptr->wait); + + lpa_if_ptr->buffer = dma_alloc_coherent(NULL, DMA_ALLOC_BUF_SZ, + &(lpa_if_ptr->buffer_phys), GFP_KERNEL); + if (!lpa_if_ptr->buffer) { + pr_err("dma_alloc_coherent failed\n"); + kfree(lpa_if_ptr); + return -ENOMEM; + } + + pr_info("lpa_if_ptr 0x%08x buf_vir 0x%08x buf_phy 0x%08x " + " buf_zise %u\n", (u32)lpa_if_ptr, + (u32)(lpa_if_ptr->buffer), lpa_if_ptr->buffer_phys, + DMA_ALLOC_BUF_SZ); + + rc = misc_register(&lpa_if_misc); + if (rc < 0) { + pr_err("misc_register failed\n"); + + dma_free_coherent(NULL, DMA_ALLOC_BUF_SZ, lpa_if_ptr->buffer, + lpa_if_ptr->buffer_phys); + kfree(lpa_if_ptr); + } + return rc; +} + +device_initcall(lpa_if_init); diff --git a/arch/arm/mach-msm/qdsp6v2/msm_qdsp6_audio.h b/arch/arm/mach-msm/qdsp6v2/msm_qdsp6_audio.h new file mode 100644 index 00000000000..3890816c3d8 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/msm_qdsp6_audio.h @@ -0,0 +1,52 @@ +/* arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h + * + * Copyright (C) 2009 Google, Inc. + * Author: Brian Swetland + * + * 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_MSM_QDSP6_Q6AUDIO_ +#define _MACH_MSM_QDSP6_Q6AUDIO_ + +#define AUDIO_FLAG_READ 0 +#define AUDIO_FLAG_WRITE 1 +#define AUDIO_FLAG_INCALL_MIXED 2 + +struct audio_buffer { + dma_addr_t phys; + void *data; + uint32_t size; + uint32_t used; /* 1 = CPU is waiting for DSP to consume this buf */ + uint32_t actual_size; /* actual number of bytes read by DSP */ +}; + +struct audio_client { + struct audio_buffer buf[2]; + int cpu_buf; /* next buffer the CPU will touch */ + int dsp_buf; /* next buffer the DSP will touch */ + int running; + int session; + + int state; + + wait_queue_head_t wait; + wait_queue_head_t cmd_wait; + + struct dal_client *client; + + int cb_status; + uint32_t flags; + void *apr; + int ref_count; +}; + +#endif diff --git a/arch/arm/mach-msm/qdsp6v2/pcm_in.c b/arch/arm/mach-msm/qdsp6v2/pcm_in.c new file mode 100644 index 00000000000..be1927d1d20 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/pcm_in.c @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (C) 2009 HTC Corporation + * Copyright (c) 2010-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_BUF 4 +#define BUFSZ (480 * 8) +#define BUFFER_SIZE_MULTIPLE 4 +#define MIN_BUFFER_SIZE 160 + +#define VOC_REC_NONE 0xFF + +struct pcm { + struct mutex lock; + struct mutex read_lock; + wait_queue_head_t wait; + spinlock_t dsp_lock; + struct audio_client *ac; + uint32_t sample_rate; + uint32_t channel_count; + uint32_t buffer_size; + uint32_t buffer_count; + uint32_t rec_mode; + uint32_t in_frame_info[MAX_BUF][2]; + atomic_t in_count; + atomic_t in_enabled; + atomic_t in_opened; + atomic_t in_stopped; +}; + +static void pcm_in_get_dsp_buffers(struct pcm*, + uint32_t token, uint32_t *payload); + +void pcm_in_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct pcm *pcm = (struct pcm *) priv; + unsigned long flags; + + spin_lock_irqsave(&pcm->dsp_lock, flags); + switch (opcode) { + case ASM_DATA_EVENT_READ_DONE: + pcm_in_get_dsp_buffers(pcm, token, payload); + break; + default: + break; + } + spin_unlock_irqrestore(&pcm->dsp_lock, flags); +} + +static void pcm_in_get_dsp_buffers(struct pcm *pcm, + uint32_t token, uint32_t *payload) +{ + pcm->in_frame_info[token][0] = payload[7]; + pcm->in_frame_info[token][1] = payload[3]; + if (atomic_read(&pcm->in_count) <= pcm->buffer_count) + atomic_inc(&pcm->in_count); + wake_up(&pcm->wait); +} + +static int pcm_in_enable(struct pcm *pcm) +{ + if (atomic_read(&pcm->in_enabled)) + return 0; + return q6asm_run(pcm->ac, 0, 0, 0); +} + +static int pcm_in_disable(struct pcm *pcm) +{ + int rc = 0; + + if (atomic_read(&pcm->in_opened)) { + atomic_set(&pcm->in_enabled, 0); + atomic_set(&pcm->in_opened, 0); + rc = q6asm_cmd(pcm->ac, CMD_CLOSE); + + atomic_set(&pcm->in_stopped, 1); + memset(pcm->in_frame_info, 0, + sizeof(char) * pcm->buffer_count * 2); + wake_up(&pcm->wait); + } + return rc; +} + +static int config(struct pcm *pcm) +{ + int rc = 0; + + pr_debug("%s: pcm prefill, buffer_size = %d\n", __func__, + pcm->buffer_size); + rc = q6asm_audio_client_buf_alloc(OUT, pcm->ac, + pcm->buffer_size, pcm->buffer_count); + if (rc < 0) { + pr_err("Audio Start: Buffer Allocation failed \ + rc = %d\n", rc); + goto fail; + } + + rc = q6asm_enc_cfg_blk_pcm(pcm->ac, pcm->sample_rate, + pcm->channel_count); + if (rc < 0) { + pr_err("%s: cmd media format block failed", __func__); + goto fail; + } +fail: + return rc; +} + +static long pcm_in_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct pcm *pcm = file->private_data; + int rc = 0; + + mutex_lock(&pcm->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: + break; + case AUDIO_GET_STATS: { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + rc = -EFAULT; + break; + } + case AUDIO_START: { + int cnt = 0; + if (atomic_read(&pcm->in_enabled)) { + pr_info("%s:AUDIO_START already over\n", __func__); + rc = 0; + break; + } + rc = config(pcm); + if (rc) { + pr_err("%s: IN Configuration failed\n", __func__); + rc = -EFAULT; + break; + } + + rc = pcm_in_enable(pcm); + if (rc) { + pr_err("%s: In Enable failed\n", __func__); + rc = -EFAULT; + break; + } + + atomic_set(&pcm->in_enabled, 1); + + while (cnt++ < pcm->buffer_count) + q6asm_read(pcm->ac); + pr_info("%s: AUDIO_START session id[%d]\n", __func__, + pcm->ac->session); + + if (pcm->rec_mode != VOC_REC_NONE) + msm_enable_incall_recording(pcm->ac->session, + pcm->rec_mode, pcm->sample_rate, pcm->channel_count); + + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &pcm->ac->session, + sizeof(unsigned short))) + rc = -EFAULT; + break; + } + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + pr_debug("%s: SET_CONFIG: buffer_size:%d channel_count:%d" + "sample_rate:%d, buffer_count:%d\n", __func__, + config.buffer_size, config.channel_count, + config.sample_rate, config.buffer_count); + + if (!config.channel_count || config.channel_count > 2) { + rc = -EINVAL; + break; + } + + if (config.sample_rate < 8000 || config.sample_rate > 48000) { + rc = -EINVAL; + break; + } + + if ((config.buffer_size % (config.channel_count * + BUFFER_SIZE_MULTIPLE)) || + (config.buffer_size < MIN_BUFFER_SIZE)) { + pr_err("%s: Buffer Size should be multiple of " + "[4 * no. of channels] and greater than 160\n", + __func__); + rc = -EINVAL; + break; + } + + pcm->sample_rate = config.sample_rate; + pcm->channel_count = config.channel_count; + pcm->buffer_size = config.buffer_size; + pcm->buffer_count = config.buffer_count; + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + config.buffer_size = pcm->buffer_size; + config.buffer_count = pcm->buffer_count; + config.sample_rate = pcm->sample_rate; + config.channel_count = pcm->channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + break; + } + case AUDIO_ENABLE_AUDPRE: { + + uint16_t enable_mask; + + if (copy_from_user(&enable_mask, (void *) arg, + sizeof(enable_mask))) { + rc = -EFAULT; + break; + } + if (enable_mask & FLUENCE_ENABLE) + rc = auddev_cfg_tx_copp_topology(pcm->ac->session, + VPM_TX_DM_FLUENCE_COPP_TOPOLOGY); + else + rc = auddev_cfg_tx_copp_topology(pcm->ac->session, + DEFAULT_COPP_TOPOLOGY); + break; + } + + case AUDIO_SET_INCALL: { + if (copy_from_user(&pcm->rec_mode, + (void *) arg, + sizeof(pcm->rec_mode))) { + rc = -EFAULT; + pr_err("%s: Error copying in-call mode\n", __func__); + break; + } + + if (pcm->rec_mode != VOC_REC_UPLINK && + pcm->rec_mode != VOC_REC_DOWNLINK && + pcm->rec_mode != VOC_REC_BOTH) { + rc = -EINVAL; + pcm->rec_mode = VOC_REC_NONE; + + pr_err("%s: Invalid %d in-call rec_mode\n", + __func__, pcm->rec_mode); + break; + } + + pr_debug("%s: In-call rec_mode %d\n", __func__, pcm->rec_mode); + break; + } + + default: + rc = -EINVAL; + break; + } + mutex_unlock(&pcm->lock); + return rc; +} + +static int pcm_in_open(struct inode *inode, struct file *file) +{ + struct pcm *pcm; + int rc = 0; + + pcm = kzalloc(sizeof(struct pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + pcm->channel_count = 1; + pcm->sample_rate = 8000; + pcm->buffer_size = BUFSZ; + pcm->buffer_count = MAX_BUF; + + pcm->ac = q6asm_audio_client_alloc((app_cb)pcm_in_cb, (void *)pcm); + if (!pcm->ac) { + pr_err("%s: Could not allocate memory\n", __func__); + rc = -ENOMEM; + goto fail; + } + + mutex_init(&pcm->lock); + mutex_init(&pcm->read_lock); + spin_lock_init(&pcm->dsp_lock); + init_waitqueue_head(&pcm->wait); + + rc = q6asm_open_read(pcm->ac, FORMAT_LINEAR_PCM); + if (rc < 0) { + pr_err("%s: Cmd Open Failed\n", __func__); + goto fail; + } + + atomic_set(&pcm->in_stopped, 0); + atomic_set(&pcm->in_enabled, 0); + atomic_set(&pcm->in_count, 0); + atomic_set(&pcm->in_opened, 1); + + pcm->rec_mode = VOC_REC_NONE; + + file->private_data = pcm; + pr_info("%s: pcm in open session id[%d]\n", __func__, pcm->ac->session); + return 0; +fail: + if (pcm->ac) + q6asm_audio_client_free(pcm->ac); + kfree(pcm); + return rc; +} + +static ssize_t pcm_in_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct pcm *pcm = file->private_data; + const char __user *start = buf; + void *data; + uint32_t offset = 0; + uint32_t size = 0; + uint32_t idx; + int rc = 0; + int len = 0; + + if (!atomic_read(&pcm->in_enabled)) + return -EFAULT; + mutex_lock(&pcm->read_lock); + while (count > 0) { + rc = wait_event_timeout(pcm->wait, + (atomic_read(&pcm->in_count) || + atomic_read(&pcm->in_stopped)), 5 * HZ); + if (!rc) { + pr_err("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + + if (atomic_read(&pcm->in_stopped) && + !atomic_read(&pcm->in_count)) { + mutex_unlock(&pcm->read_lock); + return 0; + } + + data = q6asm_is_cpu_buf_avail(OUT, pcm->ac, &size, &idx); + if (count >= size) + len = size; + else { + len = count; + pr_err("%s: short read data[%p]bytesavail[%d]" + "bytesrequest[%d]" + "bytesrejected%d]\n",\ + __func__, data, size, + count, (size - count)); + } + if ((len) && data) { + offset = pcm->in_frame_info[idx][1]; + if (copy_to_user(buf, data+offset, len)) { + pr_err("%s copy_to_user failed len[%d]\n", + __func__, len); + rc = -EFAULT; + goto fail; + } + count -= len; + buf += len; + } + atomic_dec(&pcm->in_count); + memset(&pcm->in_frame_info[idx], 0, + sizeof(uint32_t) * 2); + + rc = q6asm_read(pcm->ac); + if (rc < 0) { + pr_err("%s q6asm_read fail\n", __func__); + goto fail; + } + rmb(); + break; + } + rc = buf-start; +fail: + mutex_unlock(&pcm->read_lock); + return rc; +} + +static int pcm_in_release(struct inode *inode, struct file *file) +{ + int rc = 0; + struct pcm *pcm = file->private_data; + + pr_info("[%s:%s] release session id[%d]\n", __MM_FILE__, + __func__, pcm->ac->session); + mutex_lock(&pcm->lock); + + if ((pcm->rec_mode != VOC_REC_NONE) && atomic_read(&pcm->in_enabled)) { + msm_disable_incall_recording(pcm->ac->session, pcm->rec_mode); + + pcm->rec_mode = VOC_REC_NONE; + } + + /* remove this session from topology list */ + auddev_cfg_tx_copp_topology(pcm->ac->session, + DEFAULT_COPP_TOPOLOGY); + mutex_unlock(&pcm->lock); + + rc = pcm_in_disable(pcm); + msm_clear_session_id(pcm->ac->session); + q6asm_audio_client_free(pcm->ac); + kfree(pcm); + return rc; +} + +static const struct file_operations pcm_in_fops = { + .owner = THIS_MODULE, + .open = pcm_in_open, + .read = pcm_in_read, + .release = pcm_in_release, + .unlocked_ioctl = pcm_in_ioctl, +}; + +struct miscdevice pcm_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_in", + .fops = &pcm_in_fops, +}; + +static int __init pcm_in_init(void) +{ + return misc_register(&pcm_in_misc); +} + +device_initcall(pcm_in_init); diff --git a/arch/arm/mach-msm/qdsp6v2/pcm_out.c b/arch/arm/mach-msm/qdsp6v2/pcm_out.c new file mode 100644 index 00000000000..b8521f6aec6 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/pcm_out.c @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2009 Google, Inc. + * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_BUF 2 +#define BUFSZ (4800) + +struct pcm { + struct mutex lock; + struct mutex write_lock; + spinlock_t dsp_lock; + wait_queue_head_t write_wait; + struct audio_client *ac; + uint32_t sample_rate; + uint32_t channel_count; + uint32_t buffer_size; + uint32_t buffer_count; + uint32_t rec_mode; + uint32_t stream_event; + uint32_t volume; + atomic_t out_count; + atomic_t out_enabled; + atomic_t out_opened; + atomic_t out_stopped; + atomic_t out_prefill; + struct wake_lock wakelock; + struct wake_lock idlelock; +}; + +void pcm_out_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct pcm *pcm = (struct pcm *) priv; + unsigned long flags; + + spin_lock_irqsave(&pcm->dsp_lock, flags); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: + atomic_inc(&pcm->out_count); + wake_up(&pcm->write_wait); + break; + default: + break; + } + spin_unlock_irqrestore(&pcm->dsp_lock, flags); +} + +static void audio_prevent_sleep(struct pcm *audio) +{ + pr_debug("%s:\n", __func__); + wake_lock(&audio->wakelock); + wake_lock(&audio->idlelock); +} + +static void audio_allow_sleep(struct pcm *audio) +{ + pr_debug("%s:\n", __func__); + wake_unlock(&audio->wakelock); + wake_unlock(&audio->idlelock); +} + +static int pcm_out_enable(struct pcm *pcm) +{ + if (atomic_read(&pcm->out_enabled)) + return 0; + return q6asm_run(pcm->ac, 0, 0, 0); +} + +static int pcm_out_disable(struct pcm *pcm) +{ + int rc = 0; + + if (atomic_read(&pcm->out_opened)) { + atomic_set(&pcm->out_enabled, 0); + atomic_set(&pcm->out_opened, 0); + rc = q6asm_cmd(pcm->ac, CMD_CLOSE); + + atomic_set(&pcm->out_stopped, 1); + wake_up(&pcm->write_wait); + } + return rc; +} + +static int config(struct pcm *pcm) +{ + int rc = 0; + if (!atomic_read(&pcm->out_prefill)) { + pr_debug("%s: pcm prefill\n", __func__); + rc = q6asm_audio_client_buf_alloc(IN, pcm->ac, + pcm->buffer_size, pcm->buffer_count); + if (rc < 0) { + pr_err("Audio Start: Buffer Allocation failed \ + rc = %d\n", rc); + goto fail; + } + + rc = q6asm_media_format_block_pcm(pcm->ac, pcm->sample_rate, + pcm->channel_count); + if (rc < 0) + pr_err("%s: CMD Format block failed\n", __func__); + + atomic_set(&pcm->out_prefill, 1); + atomic_set(&pcm->out_count, pcm->buffer_count); + } +fail: + return rc; +} + +static void pcm_event_listner(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct pcm *pcm = (struct pcm *) private_data; + int rc = 0; + + switch (evt_id) { + case AUDDEV_EVT_STREAM_VOL_CHG: + pcm->volume = evt_payload->session_vol; + pr_debug("%s: AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d, " + "enabled = %d\n", __func__, pcm->volume, + atomic_read(&pcm->out_enabled)); + if (atomic_read(&pcm->out_enabled)) { + if (pcm->ac) { + rc = q6asm_set_volume(pcm->ac, pcm->volume); + if (rc < 0) + pr_err("%s: Send Volume command" + "failed rc=%d\n", __func__, rc); + } + } + break; + default: + pr_err("%s:ERROR:wrong event\n", __func__); + break; + } +} + +static long pcm_out_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct pcm *pcm = file->private_data; + int rc = 0; + + if (cmd == AUDIO_GET_STATS) { + struct msm_audio_stats stats; + memset(&stats, 0, sizeof(stats)); + if (copy_to_user((void *) arg, &stats, sizeof(stats))) + return -EFAULT; + return 0; + } + + mutex_lock(&pcm->lock); + switch (cmd) { + case AUDIO_SET_VOLUME: { + int vol; + if (copy_from_user(&vol, (void *) arg, sizeof(vol))) { + rc = -EFAULT; + break; + } + break; + } + case AUDIO_START: { + pr_info("%s: AUDIO_START\n", __func__); + rc = config(pcm); + if (rc) { + pr_err("%s: Out Configuration failed\n", __func__); + rc = -EFAULT; + break; + } + + rc = pcm_out_enable(pcm); + if (rc) { + pr_err("Out enable failed\n"); + rc = -EFAULT; + break; + } + audio_prevent_sleep(pcm); + atomic_set(&pcm->out_enabled, 1); + + rc = q6asm_set_volume(pcm->ac, pcm->volume); + if (rc < 0) + pr_err("%s: Send Volume command failed rc=%d\n", + __func__, rc); + rc = q6asm_set_lrgain(pcm->ac, 0x2000, 0x2000); + if (rc < 0) + pr_err("%s: Send channel gain failed rc=%d\n", + __func__, rc); + /* disable mute by default */ + rc = q6asm_set_mute(pcm->ac, 0); + if (rc < 0) + pr_err("%s: Send mute command failed rc=%d\n", + __func__, rc); + break; + } + case AUDIO_GET_SESSION_ID: { + if (copy_to_user((void *) arg, &pcm->ac->session, + sizeof(unsigned short))) + rc = -EFAULT; + break; + } + case AUDIO_STOP: + break; + case AUDIO_FLUSH: + break; + case AUDIO_SET_CONFIG: { + struct msm_audio_config config; + pr_debug("%s: AUDIO_SET_CONFIG\n", __func__); + if (copy_from_user(&config, (void *) arg, sizeof(config))) { + rc = -EFAULT; + break; + } + if (config.channel_count < 1 || config.channel_count > 2) { + rc = -EINVAL; + break; + } + if (config.sample_rate < 8000 || config.sample_rate > 48000) { + rc = -EINVAL; + break; + } + if (config.buffer_size < 128) { + rc = -EINVAL; + break; + } + pcm->sample_rate = config.sample_rate; + pcm->channel_count = config.channel_count; + pcm->buffer_size = config.buffer_size; + pcm->buffer_count = config.buffer_count; + pr_debug("%s:buffer_size:%d buffer_count:%d sample_rate:%d \ + channel_count:%d\n", __func__, pcm->buffer_size, + pcm->buffer_count, pcm->sample_rate, + pcm->channel_count); + break; + } + case AUDIO_GET_CONFIG: { + struct msm_audio_config config; + pr_debug("%s: AUDIO_GET_CONFIG\n", __func__); + config.buffer_size = pcm->buffer_size; + config.buffer_count = pcm->buffer_count; + config.sample_rate = pcm->sample_rate; + config.channel_count = pcm->channel_count; + config.unused[0] = 0; + config.unused[1] = 0; + config.unused[2] = 0; + if (copy_to_user((void *) arg, &config, sizeof(config))) + rc = -EFAULT; + break; + } + case AUDIO_SET_EQ: { + struct msm_audio_eq_stream_config eq_config; + if (copy_from_user(&eq_config, (void *) arg, + sizeof(eq_config))) { + rc = -EFAULT; + break; + } + rc = q6asm_equalizer(pcm->ac, (void *) &eq_config); + if (rc < 0) + pr_err("%s: EQUALIZER FAILED\n", __func__); + break; + } + default: + rc = -EINVAL; + } + mutex_unlock(&pcm->lock); + return rc; +} + +static int pcm_out_open(struct inode *inode, struct file *file) +{ + struct pcm *pcm; + int rc = 0; + char name[24]; + + pr_info("[%s:%s] open\n", __MM_FILE__, __func__); + pcm = kzalloc(sizeof(struct pcm), GFP_KERNEL); + if (!pcm) { + pr_err("%s: Failed to allocated memory\n", __func__); + return -ENOMEM; + } + + pcm->channel_count = 2; + pcm->sample_rate = 44100; + pcm->buffer_size = BUFSZ; + pcm->buffer_count = MAX_BUF; + pcm->stream_event = AUDDEV_EVT_STREAM_VOL_CHG; + pcm->volume = 0x2000; + + pcm->ac = q6asm_audio_client_alloc((app_cb)pcm_out_cb, (void *)pcm); + if (!pcm->ac) { + pr_err("%s: Could not allocate memory\n", __func__); + rc = -ENOMEM; + goto fail; + } + + rc = q6asm_open_write(pcm->ac, FORMAT_LINEAR_PCM); + if (rc < 0) { + pr_err("%s: pcm out open failed for session %d\n", __func__, + pcm->ac->session); + rc = -EINVAL; + goto fail; + } + + mutex_init(&pcm->lock); + mutex_init(&pcm->write_lock); + init_waitqueue_head(&pcm->write_wait); + spin_lock_init(&pcm->dsp_lock); + atomic_set(&pcm->out_enabled, 0); + atomic_set(&pcm->out_stopped, 0); + atomic_set(&pcm->out_count, pcm->buffer_count); + atomic_set(&pcm->out_prefill, 0); + atomic_set(&pcm->out_opened, 1); + snprintf(name, sizeof name, "audio_pcm_%x", pcm->ac->session); + wake_lock_init(&pcm->wakelock, WAKE_LOCK_SUSPEND, name); + snprintf(name, sizeof name, "audio_pcm_idle_%x", pcm->ac->session); + wake_lock_init(&pcm->idlelock, WAKE_LOCK_IDLE, name); + + rc = auddev_register_evt_listner(pcm->stream_event, + AUDDEV_CLNT_DEC, + pcm->ac->session, + pcm_event_listner, + (void *)pcm); + if (rc < 0) { + pr_err("%s: failed to register listner\n", __func__); + goto fail; + } + + file->private_data = pcm; + pr_info("[%s:%s] open session id[%d]\n", __MM_FILE__, + __func__, pcm->ac->session); + return 0; +fail: + if (pcm->ac) + q6asm_audio_client_free(pcm->ac); + kfree(pcm); + return rc; +} + +static ssize_t pcm_out_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct pcm *pcm = file->private_data; + const char __user *start = buf; + int xfer; + char *bufptr; + uint32_t idx; + void *data; + int rc = 0; + uint32_t size; + + if (!pcm->ac) + return -ENODEV; + + if (!atomic_read(&pcm->out_enabled)) { + rc = config(pcm); + if (rc < 0) + return rc; + } + + mutex_lock(&pcm->write_lock); + while (count > 0) { + rc = wait_event_timeout(pcm->write_wait, + (atomic_read(&pcm->out_count) || + atomic_read(&pcm->out_stopped)), 5 * HZ); + if (!rc) { + pr_err("%s: wait_event_timeout failed for session %d\n", + __func__, pcm->ac->session); + goto fail; + } + + if (atomic_read(&pcm->out_stopped) && + !atomic_read(&pcm->out_count)) { + pr_info("%s: pcm stopped out_count 0\n", __func__); + mutex_unlock(&pcm->write_lock); + return 0; + } + + data = q6asm_is_cpu_buf_avail(IN, pcm->ac, &size, &idx); + bufptr = data; + if (bufptr) { + xfer = count; + if (xfer > BUFSZ) + xfer = BUFSZ; + + if (copy_from_user(bufptr, buf, xfer)) { + rc = -EFAULT; + goto fail; + } + buf += xfer; + count -= xfer; + rc = q6asm_write(pcm->ac, xfer, 0, 0, NO_TIMESTAMP); + wmb(); + if (rc < 0) { + rc = -EFAULT; + goto fail; + } + } + atomic_dec(&pcm->out_count); + } + + rc = buf - start; +fail: + mutex_unlock(&pcm->write_lock); + return rc; +} + +static int pcm_out_release(struct inode *inode, struct file *file) +{ + struct pcm *pcm = file->private_data; + + pr_info("[%s:%s] release session id[%d]\n", __MM_FILE__, + __func__, pcm->ac->session); + if (pcm->ac) + pcm_out_disable(pcm); + msm_clear_session_id(pcm->ac->session); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, pcm->ac->session); + q6asm_audio_client_free(pcm->ac); + audio_allow_sleep(pcm); + wake_lock_destroy(&pcm->wakelock); + wake_lock_destroy(&pcm->idlelock); + mutex_destroy(&pcm->lock); + mutex_destroy(&pcm->write_lock); + kfree(pcm); + return 0; +} + +static const struct file_operations pcm_out_fops = { + .owner = THIS_MODULE, + .open = pcm_out_open, + .write = pcm_out_write, + .release = pcm_out_release, + .unlocked_ioctl = pcm_out_ioctl, +}; + +struct miscdevice pcm_out_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_pcm_out", + .fops = &pcm_out_fops, +}; + +static int __init pcm_out_init(void) +{ + return misc_register(&pcm_out_misc); +} + +device_initcall(pcm_out_init); diff --git a/arch/arm/mach-msm/qdsp6v2/q6core.c b/arch/arm/mach-msm/qdsp6v2/q6core.c new file mode 100644 index 00000000000..edb1e7ddf82 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/q6core.c @@ -0,0 +1,409 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6core.h" + +#define TIMEOUT_MS 1000 + +static struct apr_svc *apr_handle_q; +static struct apr_svc *apr_handle_m; +static struct apr_svc *core_handle_q; + +static int32_t query_adsp_ver; +static wait_queue_head_t adsp_version_wait; +static uint32_t adsp_version; + +static wait_queue_head_t bus_bw_req_wait; +static u32 bus_bw_resp_received; + +static struct dentry *dentry; +static char l_buf[4096]; + +static int32_t aprv2_core_fn_q(struct apr_client_data *data, void *priv) +{ + struct adsp_get_version *payload; + uint32_t *payload1; + struct adsp_service_info *svc_info; + int i; + + pr_info("core msg: payload len = %u, apr resp opcode = 0x%X\n", + data->payload_size, data->opcode); + + switch (data->opcode) { + + case APR_BASIC_RSP_RESULT:{ + + if (data->payload_size == 0) { + pr_err("%s: APR_BASIC_RSP_RESULT No Payload ", + __func__); + return 0; + } + + payload1 = data->payload; + + switch (payload1[0]) { + + case ADSP_CMD_SET_POWER_COLLAPSE_STATE: + pr_info("Cmd = ADSP_CMD_SET_POWER_COLLAPSE_STATE" + " status[0x%x]\n", payload1[1]); + break; + case ADSP_CMD_REMOTE_BUS_BW_REQUEST: + pr_info("%s: cmd = ADSP_CMD_REMOTE_BUS_BW_REQUEST" + " status = 0x%x\n", __func__, payload1[1]); + + bus_bw_resp_received = 1; + wake_up(&bus_bw_req_wait); + break; + default: + pr_err("Invalid cmd rsp[0x%x][0x%x]\n", + payload1[0], payload1[1]); + break; + } + break; + } + case ADSP_GET_VERSION_RSP:{ + if (data->payload_size) { + payload = data->payload; + if (query_adsp_ver == 1) { + query_adsp_ver = 0; + adsp_version = payload->build_id; + wake_up(&adsp_version_wait); + } + svc_info = (struct adsp_service_info *) + ((char *)payload + sizeof(struct adsp_get_version)); + pr_info("----------------------------------------\n"); + pr_info("Build id = %x\n", payload->build_id); + pr_info("Number of services= %x\n", payload->svc_cnt); + pr_info("----------------------------------------\n"); + for (i = 0; i < payload->svc_cnt; i++) { + pr_info("svc-id[%d]\tver[%x.%x]\n", + svc_info[i].svc_id, + (svc_info[i].svc_ver & 0xFFFF0000) + >> 16, + (svc_info[i].svc_ver & 0xFFFF)); + } + pr_info("-----------------------------------------\n"); + } else + pr_info("zero payload for ADSP_GET_VERSION_RSP\n"); + break; + } + case RESET_EVENTS:{ + pr_debug("Reset event received in Core service"); + apr_reset(core_handle_q); + core_handle_q = NULL; + break; + } + + default: + pr_err("Message id from adsp core svc: %d\n", data->opcode); + break; + } + + return 0; +} + +static int32_t aprv2_debug_fn_q(struct apr_client_data *data, void *priv) +{ + pr_debug("Q6_Payload Length = %d\n", data->payload_size); + if (memcmp(data->payload, l_buf + 20, data->payload_size)) + pr_info("FAIL: %d\n", data->payload_size); + else + pr_info("SUCCESS: %d\n", data->payload_size); + return 0; +} + +static int32_t aprv2_debug_fn_m(struct apr_client_data *data, void *priv) +{ + pr_info("M_Payload Length = %d\n", data->payload_size); + return 0; +} + +static ssize_t apr_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + pr_debug("apr debugfs opened\n"); + return 0; +} + +void core_open(void) +{ + if (core_handle_q == NULL) { + core_handle_q = apr_register("ADSP", "CORE", + aprv2_core_fn_q, 0xFFFFFFFF, NULL); + } + pr_info("Open_q %p\n", core_handle_q); + if (core_handle_q == NULL) { + pr_err("%s: Unable to register CORE\n", __func__); + } +} + +int core_req_bus_bandwith(u16 bus_id, u32 ab_bps, u32 ib_bps) +{ + struct adsp_cmd_remote_bus_bw_request bus_bw_req; + int ret; + + pr_debug("%s: bus_id %u ab_bps %u ib_bps %u\n", + __func__, bus_id, ab_bps, ib_bps); + + core_open(); + if (core_handle_q == NULL) { + pr_info("%s: apr registration for CORE failed\n", __func__); + return -ENODEV; + } + + bus_bw_req.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + bus_bw_req.hdr.pkt_size = sizeof(struct adsp_cmd_remote_bus_bw_request); + + bus_bw_req.hdr.src_port = 0; + bus_bw_req.hdr.dest_port = 0; + bus_bw_req.hdr.token = 0; + bus_bw_req.hdr.opcode = ADSP_CMD_REMOTE_BUS_BW_REQUEST; + + bus_bw_req.bus_identifier = bus_id; + bus_bw_req.reserved = 0; + bus_bw_req.ab_bps = ab_bps; + bus_bw_req.ib_bps = ib_bps; + + bus_bw_resp_received = 0; + ret = apr_send_pkt(core_handle_q, (uint32_t *) &bus_bw_req); + if (ret < 0) { + pr_err("%s: CORE bus bw request failed\n", __func__); + goto fail_cmd; + } + + ret = wait_event_timeout(bus_bw_req_wait, (bus_bw_resp_received == 1), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -ETIME; + goto fail_cmd; + } + + return 0; + +fail_cmd: + return ret; +} + +uint32_t core_get_adsp_version(void) +{ + struct apr_hdr *hdr; + int32_t rc = 0, ret = 0; + core_open(); + if (core_handle_q) { + hdr = (struct apr_hdr *)l_buf; + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_EVENT, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + hdr->pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, 0); + hdr->src_port = 0; + hdr->dest_port = 0; + hdr->token = 0; + hdr->opcode = ADSP_GET_VERSION; + + apr_send_pkt(core_handle_q, (uint32_t *)l_buf); + query_adsp_ver = 1; + pr_info("Write_q\n"); + ret = wait_event_timeout(adsp_version_wait, + (query_adsp_ver == 0), + msecs_to_jiffies(TIMEOUT_MS)); + rc = adsp_version; + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + rc = -ENODEV; + } + } else + pr_info("apr registration failed\n"); + return rc; +} +EXPORT_SYMBOL(core_get_adsp_version); + +static ssize_t apr_debug_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int len; + static int t_len; + + if (count < 0) + return 0; + len = count > 63 ? 63 : count; + if (copy_from_user(l_buf + 20 , buf, len)) { + pr_info("Unable to copy data from user space\n"); + return -EFAULT; + } + l_buf[len + 20] = 0; + if (l_buf[len + 20 - 1] == '\n') { + l_buf[len + 20 - 1] = 0; + len--; + } + if (!strncmp(l_buf + 20, "open_q", 64)) { + apr_handle_q = apr_register("ADSP", "TEST", aprv2_debug_fn_q, + 0xFFFFFFFF, NULL); + pr_info("Open_q %p\n", apr_handle_q); + } else if (!strncmp(l_buf + 20, "open_m", 64)) { + apr_handle_m = apr_register("MODEM", "TEST", aprv2_debug_fn_m, + 0xFFFFFFFF, NULL); + pr_info("Open_m %p\n", apr_handle_m); + } else if (!strncmp(l_buf + 20, "write_q", 64)) { + struct apr_hdr *hdr; + + t_len++; + t_len = t_len % 450; + if (!t_len % 99) + msleep(2000); + hdr = (struct apr_hdr *)l_buf; + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_EVENT, + APR_HDR_LEN(20), APR_PKT_VER); + hdr->pkt_size = APR_PKT_SIZE(20, t_len); + hdr->src_port = 0; + hdr->dest_port = 0; + hdr->token = 0; + hdr->opcode = 0x12345678; + memset(l_buf + 20, 9, 4060); + + apr_send_pkt(apr_handle_q, (uint32_t *)l_buf); + pr_debug("Write_q\n"); + } else if (!strncmp(l_buf + 20, "write_m", 64)) { + struct apr_hdr *hdr; + + hdr = (struct apr_hdr *)l_buf; + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_EVENT, + APR_HDR_LEN(20), APR_PKT_VER); + hdr->pkt_size = APR_PKT_SIZE(20, 8); + hdr->src_port = 0; + hdr->dest_port = 0; + hdr->token = 0; + hdr->opcode = 0x12345678; + memset(l_buf + 30, 9, 4060); + + apr_send_pkt(apr_handle_m, (uint32_t *)l_buf); + pr_info("Write_m\n"); + } else if (!strncmp(l_buf + 20, "write_q4", 64)) { + struct apr_hdr *hdr; + + hdr = (struct apr_hdr *)l_buf; + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_EVENT, + APR_HDR_LEN(20), APR_PKT_VER); + hdr->pkt_size = APR_PKT_SIZE(20, 4076); + hdr->src_port = 0; + hdr->dest_port = 0; + hdr->token = 0; + hdr->opcode = 0x12345678; + memset(l_buf + 30, 9, 4060); + + apr_send_pkt(apr_handle_q, (uint32_t *)l_buf); + pr_info("Write_q\n"); + } else if (!strncmp(l_buf + 20, "write_m4", 64)) { + struct apr_hdr *hdr; + + hdr = (struct apr_hdr *)l_buf; + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_EVENT, + APR_HDR_LEN(20), APR_PKT_VER); + hdr->pkt_size = APR_PKT_SIZE(20, 4076); + hdr->src_port = 0; + hdr->dest_port = 0; + hdr->token = 0; + hdr->opcode = 0x12345678; + memset(l_buf + 30, 9, 4060); + + apr_send_pkt(apr_handle_m, (uint32_t *)l_buf); + pr_info("Write_m\n"); + } else if (!strncmp(l_buf + 20, "close", 64)) { + if (apr_handle_q) + apr_deregister(apr_handle_q); + } else if (!strncmp(l_buf + 20, "loaded", 64)) { + change_q6_state(APR_Q6_LOADED); + } else if (!strncmp(l_buf + 20, "boom", 64)) { + q6audio_dsp_not_responding(); + } else if (!strncmp(l_buf + 20, "dsp_ver", 64)) { + core_get_adsp_version(); + } else if (!strncmp(l_buf + 20, "en_pwr_col", 64)) { + struct adsp_power_collapse pc; + + core_open(); + if (core_handle_q) { + pc.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_EVENT, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + pc.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(uint32_t));; + pc.hdr.src_port = 0; + pc.hdr.dest_port = 0; + pc.hdr.token = 0; + pc.hdr.opcode = ADSP_CMD_SET_POWER_COLLAPSE_STATE; + pc.power_collapse = 0x00000000; + apr_send_pkt(core_handle_q, (uint32_t *)&pc); + pr_info("Write_q :enable power collapse\n"); + } + } else if (!strncmp(l_buf + 20, "dis_pwr_col", 64)) { + struct adsp_power_collapse pc; + + core_open(); + if (core_handle_q) { + pc.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_EVENT, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + pc.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(uint32_t)); + pc.hdr.src_port = 0; + pc.hdr.dest_port = 0; + pc.hdr.token = 0; + pc.hdr.opcode = ADSP_CMD_SET_POWER_COLLAPSE_STATE; + pc.power_collapse = 0x00000001; + apr_send_pkt(core_handle_q, (uint32_t *)&pc); + pr_info("Write_q:disable power collapse\n"); + } + } else + pr_info("Unknown Command\n"); + + return count; +} + +static const struct file_operations apr_debug_fops = { + .write = apr_debug_write, + .open = apr_debug_open, +}; + +static int __init core_init(void) +{ + init_waitqueue_head(&bus_bw_req_wait); + bus_bw_resp_received = 0; + + query_adsp_ver = 0; + init_waitqueue_head(&adsp_version_wait); + adsp_version = 0; + + core_handle_q = NULL; + +#ifdef CONFIG_DEBUG_FS + dentry = debugfs_create_file("apr", S_IFREG | S_IRUGO | S_IWUSR + | S_IWGRP, NULL, (void *) NULL, &apr_debug_fops); +#endif /* CONFIG_DEBUG_FS */ + + return 0; +} + +device_initcall(core_init); diff --git a/arch/arm/mach-msm/qdsp6v2/q6core.h b/arch/arm/mach-msm/qdsp6v2/q6core.h new file mode 100644 index 00000000000..cb25d6b82ec --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/q6core.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __Q6CORE_H__ +#define __Q6CORE_H__ +#include + + +#define ADSP_CMD_REMOTE_BUS_BW_REQUEST 0x0001115D +#define AUDIO_IF_BUS_ID 1 + +struct adsp_cmd_remote_bus_bw_request { + struct apr_hdr hdr; + u16 bus_identifier; + u16 reserved; + u32 ab_bps; + u32 ib_bps; +} __packed; + +#define ADSP_GET_VERSION 0x00011152 +#define ADSP_GET_VERSION_RSP 0x00011153 + +struct adsp_get_version { + uint32_t build_id; + uint32_t svc_cnt; +}; + +struct adsp_service_info { + uint32_t svc_id; + uint32_t svc_ver; +}; + +#define ADSP_CMD_SET_POWER_COLLAPSE_STATE 0x0001115C +struct adsp_power_collapse { + struct apr_hdr hdr; + uint32_t power_collapse; +}; + +int core_req_bus_bandwith(u16 bus_id, u32 ab_bps, u32 ib_bps); + +uint32_t core_get_adsp_version(void); + +#endif /* __Q6CORE_H__ */ diff --git a/arch/arm/mach-msm/qdsp6v2/q6voice.c b/arch/arm/mach-msm/qdsp6v2/q6voice.c new file mode 100644 index 00000000000..b5a152c2f2b --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/q6voice.c @@ -0,0 +1,2817 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "q6core.h" + + +#define TIMEOUT_MS 3000 +#define SNDDEV_CAP_TTY 0x20 + +#define CMD_STATUS_SUCCESS 0 +#define CMD_STATUS_FAIL 1 + +#define VOC_PATH_PASSIVE 0 +#define VOC_PATH_FULL 1 +#define ADSP_VERSION_CVD 0x60300000 + +#define BUFFER_PAYLOAD_SIZE 4000 + +#define VOC_REC_NONE 0xFF + +struct voice_data voice; + +static bool is_adsp_support_cvd(void) +{ + return (voice.adsp_version >= ADSP_VERSION_CVD); +} +static int voice_send_enable_vocproc_cmd(struct voice_data *v); +static int voice_send_netid_timing_cmd(struct voice_data *v); + +static void *voice_get_apr_mvm(struct voice_data *v) +{ + void *apr_mvm = NULL; + + if (v->voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + apr_mvm = v->apr_mvm; + else + apr_mvm = v->apr_q6_mvm; + + pr_debug("%s: apr_mvm 0x%x\n", __func__, (unsigned int)apr_mvm); + + return apr_mvm; +} + +static void voice_set_apr_mvm(struct voice_data *v, void *apr_mvm) +{ + pr_debug("%s: apr_mvm 0x%x\n", __func__, (unsigned int)apr_mvm); + + if (v->voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + v->apr_mvm = apr_mvm; + else + v->apr_q6_mvm = apr_mvm; +} + +static void *voice_get_apr_cvs(struct voice_data *v) +{ + void *apr_cvs = NULL; + + if (v->voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + apr_cvs = v->apr_cvs; + else + apr_cvs = v->apr_q6_cvs; + + pr_debug("%s: apr_cvs 0x%x\n", __func__, (unsigned int)apr_cvs); + + return apr_cvs; +} + +static void voice_set_apr_cvs(struct voice_data *v, void *apr_cvs) +{ + pr_debug("%s: apr_cvs 0x%x\n", __func__, (unsigned int)apr_cvs); + + if (v->voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + v->apr_cvs = apr_cvs; + else + v->apr_q6_cvs = apr_cvs; +#ifdef CONFIG_MSM8X60_RTAC + rtac_set_voice_handle(RTAC_CVS, apr_cvs); +#endif +} + +static void *voice_get_apr_cvp(struct voice_data *v) +{ + void *apr_cvp = NULL; + + if (v->voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + apr_cvp = v->apr_cvp; + else + apr_cvp = v->apr_q6_cvp; + + pr_debug("%s: apr_cvp 0x%x\n", __func__, (unsigned int)apr_cvp); + + return apr_cvp; +} + +static void voice_set_apr_cvp(struct voice_data *v, void *apr_cvp) +{ + pr_debug("%s: apr_cvp 0x%x\n", __func__, (unsigned int)apr_cvp); + + if (v->voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) + v->apr_cvp = apr_cvp; + else + v->apr_q6_cvp = apr_cvp; +#ifdef CONFIG_MSM8X60_RTAC + rtac_set_voice_handle(RTAC_CVP, apr_cvp); +#endif +} + +static u16 voice_get_mvm_handle(struct voice_data *v) +{ + u16 mvm_handle = 0; + + if (v->voc_path == VOC_PATH_PASSIVE) + mvm_handle = v->mvm_handle; + else + mvm_handle = v->mvm_q6_handle; + + pr_debug("%s: mvm_handle %d\n", __func__, mvm_handle); + + return mvm_handle; +} + +static void voice_set_mvm_handle(struct voice_data *v, u16 mvm_handle) +{ + pr_debug("%s: mvm_handle %d\n", __func__, mvm_handle); + + if (v->voc_path == VOC_PATH_PASSIVE) + v->mvm_handle = mvm_handle; + else + v->mvm_q6_handle = mvm_handle; +} + +static u16 voice_get_cvs_handle(struct voice_data *v) +{ + u16 cvs_handle = 0; + + if (v->voc_path == VOC_PATH_PASSIVE) + cvs_handle = v->cvs_handle; + else + cvs_handle = v->cvs_q6_handle; + + pr_debug("%s: cvs_handle %d\n", __func__, cvs_handle); + + return cvs_handle; +} + +static void voice_set_cvs_handle(struct voice_data *v, u16 cvs_handle) +{ + pr_debug("%s: cvs_handle %d\n", __func__, cvs_handle); + + if (v->voc_path == VOC_PATH_PASSIVE) + v->cvs_handle = cvs_handle; + else + v->cvs_q6_handle = cvs_handle; +} + +static u16 voice_get_cvp_handle(struct voice_data *v) +{ + u16 cvp_handle = 0; + + if (v->voc_path == VOC_PATH_PASSIVE) + cvp_handle = v->cvp_handle; + else + cvp_handle = v->cvp_q6_handle; + + pr_debug("%s: cvp_handle %d\n", __func__, cvp_handle); + + return cvp_handle; +} + +static void voice_set_cvp_handle(struct voice_data *v, u16 cvp_handle) +{ + pr_debug("%s: cvp_handle %d\n", __func__, cvp_handle); + + if (v->voc_path == VOC_PATH_PASSIVE) + v->cvp_handle = cvp_handle; + else + v->cvp_q6_handle = cvp_handle; +} + +static void voice_auddev_cb_function(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data); + +static int32_t modem_mvm_callback(struct apr_client_data *data, void *priv); +static int32_t modem_cvs_callback(struct apr_client_data *data, void *priv); +static int32_t modem_cvp_callback(struct apr_client_data *data, void *priv); + +static int voice_apr_register(struct voice_data *v) +{ + int rc = 0; + void *apr_mvm; + void *apr_cvs; + void *apr_cvp; + + if (v->adsp_version == 0) { + v->adsp_version = core_get_adsp_version(); + pr_info("adsp_ver fetched:%x\n", v->adsp_version); + } + apr_mvm = voice_get_apr_mvm(v); + apr_cvs = voice_get_apr_cvs(v); + apr_cvp = voice_get_apr_cvp(v); + + + pr_debug("into voice_apr_register_callback\n"); + /* register callback to APR */ + if (apr_mvm == NULL) { + pr_debug("start to register MVM callback\n"); + + if (v->voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) { + apr_mvm = apr_register("MODEM", "MVM", + modem_mvm_callback, 0xFFFFFFFF, + v); + } else { + apr_mvm = apr_register("ADSP", "MVM", + modem_mvm_callback, 0xFFFFFFFF, + v); + } + + if (apr_mvm == NULL) { + pr_err("Unable to register MVM %d\n", + is_adsp_support_cvd()); + rc = -ENODEV; + goto done; + } + + voice_set_apr_mvm(v, apr_mvm); + } + + if (apr_cvs == NULL) { + pr_debug("start to register CVS callback\n"); + + if (v->voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) { + apr_cvs = apr_register("MODEM", "CVS", + modem_cvs_callback, 0xFFFFFFFF, + v); + } else { + apr_cvs = apr_register("ADSP", "CVS", + modem_cvs_callback, 0xFFFFFFFF, + v); + } + + if (apr_cvs == NULL) { + pr_err("Unable to register CVS %d\n", + is_adsp_support_cvd()); + rc = -ENODEV; + goto err; + } + + voice_set_apr_cvs(v, apr_cvs); + } + + if (apr_cvp == NULL) { + pr_debug("start to register CVP callback\n"); + + if (v->voc_path == VOC_PATH_PASSIVE && + !(is_adsp_support_cvd())) { + apr_cvp = apr_register("MODEM", "CVP", + modem_cvp_callback, 0xFFFFFFFF, + v); + } else { + apr_cvp = apr_register("ADSP", "CVP", + modem_cvp_callback, 0xFFFFFFFF, + v); + } + + if (apr_cvp == NULL) { + pr_err("Unable to register CVP %d\n", + is_adsp_support_cvd()); + rc = -ENODEV; + goto err1; + } + + voice_set_apr_cvp(v, apr_cvp); + } + return 0; + +err1: + apr_deregister(apr_cvs); + apr_cvs = NULL; + voice_set_apr_cvs(v, apr_cvs); +err: + apr_deregister(apr_mvm); + apr_mvm = NULL; + voice_set_apr_mvm(v, apr_mvm); + +done: + return rc; +} + +static int voice_create_mvm_cvs_session(struct voice_data *v) +{ + int ret = 0; + struct mvm_create_ctl_session_cmd mvm_session_cmd; + struct cvs_create_passive_ctl_session_cmd cvs_session_cmd; + struct cvs_create_full_ctl_session_cmd cvs_full_ctl_cmd; + struct mvm_attach_stream_cmd attach_stream_cmd; + void *apr_mvm = voice_get_apr_mvm(v); + void *apr_cvs = voice_get_apr_cvs(v); + void *apr_cvp = voice_get_apr_cvp(v); + u16 mvm_handle = voice_get_mvm_handle(v); + u16 cvs_handle = voice_get_cvs_handle(v); + u16 cvp_handle = voice_get_cvp_handle(v); + + pr_info("%s:\n", __func__); + + /* start to ping if modem service is up */ + pr_debug("in voice_create_mvm_cvs_session, mvm_hdl=%d, cvs_hdl=%d\n", + mvm_handle, cvs_handle); + /* send cmd to create mvm session and wait for response */ + + if (!mvm_handle) { + if (v->voc_path == VOC_PATH_PASSIVE) { + mvm_session_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_session_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_session_cmd) - APR_HDR_SIZE); + pr_debug("Send mvm create session pkt size = %d\n", + mvm_session_cmd.hdr.pkt_size); + mvm_session_cmd.hdr.src_port = 0; + mvm_session_cmd.hdr.dest_port = 0; + mvm_session_cmd.hdr.token = 0; + pr_debug("%s: Creating MVM passive ctrl\n", __func__); + mvm_session_cmd.hdr.opcode = + VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION; + strncpy(mvm_session_cmd.mvm_session.name, + "default modem voice", SESSION_NAME_LEN); + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &mvm_session_cmd); + if (ret < 0) { + pr_err("Error sending MVM_CONTROL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } else { + mvm_session_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_session_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_session_cmd) - APR_HDR_SIZE); + pr_debug("Send mvm create session pkt size = %d\n", + mvm_session_cmd.hdr.pkt_size); + mvm_session_cmd.hdr.src_port = 0; + mvm_session_cmd.hdr.dest_port = 0; + mvm_session_cmd.hdr.token = 0; + pr_debug("%s: Creating MVM full ctrl\n", __func__); + mvm_session_cmd.hdr.opcode = + VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION; + strncpy(mvm_session_cmd.mvm_session.name, + "default voip", SESSION_NAME_LEN); + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &mvm_session_cmd); + if (ret < 0) { + pr_err("Error sending MVM_FULL_CTL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } + + /* Get the created MVM handle. */ + mvm_handle = voice_get_mvm_handle(v); + } + + /* send cmd to create cvs session */ + if (!cvs_handle) { + if (v->voc_path == VOC_PATH_PASSIVE) { + pr_info("%s:creating CVS passive session\n", __func__); + + cvs_session_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_session_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_session_cmd) - APR_HDR_SIZE); + pr_info("send stream create session pkt size = %d\n", + cvs_session_cmd.hdr.pkt_size); + cvs_session_cmd.hdr.src_port = 0; + cvs_session_cmd.hdr.dest_port = 0; + cvs_session_cmd.hdr.token = 0; + cvs_session_cmd.hdr.opcode = + VSS_ISTREAM_CMD_CREATE_PASSIVE_CONTROL_SESSION; + strncpy(cvs_session_cmd.cvs_session.name, + "default modem voice", SESSION_NAME_LEN); + + v->cvs_state = CMD_STATUS_FAIL; + + pr_info("%s: CVS create\n", __func__); + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_session_cmd); + if (ret < 0) { + pr_err("Fail in sending STREAM_CONTROL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* Get the created CVS handle. */ + cvs_handle = voice_get_cvs_handle(v); + } else { + pr_info("%s:creating CVS full session\n", __func__); + + cvs_full_ctl_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + + cvs_full_ctl_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_full_ctl_cmd) - APR_HDR_SIZE); + + cvs_full_ctl_cmd.hdr.src_port = 0; + cvs_full_ctl_cmd.hdr.dest_port = 0; + cvs_full_ctl_cmd.hdr.token = 0; + cvs_full_ctl_cmd.hdr.opcode = + VSS_ISTREAM_CMD_CREATE_FULL_CONTROL_SESSION; + cvs_full_ctl_cmd.cvs_session.direction = 2; + + cvs_full_ctl_cmd.cvs_session.enc_media_type = + v->mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.dec_media_type = + v->mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.network_id = + v->mvs_info.network_type; + strncpy(cvs_full_ctl_cmd.cvs_session.name, + "default voip", SESSION_NAME_LEN); + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, + (uint32_t *) &cvs_full_ctl_cmd); + + if (ret < 0) { + pr_err("%s: Err %d sending CREATE_FULL_CTRL\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* Get the created CVS handle. */ + cvs_handle = voice_get_cvs_handle(v); + + /* Attach MVM to CVS. */ + pr_info("%s: Attach MVM to stream\n", __func__); + + attach_stream_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + + attach_stream_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(attach_stream_cmd) - APR_HDR_SIZE); + attach_stream_cmd.hdr.src_port = 0; + attach_stream_cmd.hdr.dest_port = mvm_handle; + attach_stream_cmd.hdr.token = 0; + attach_stream_cmd.hdr.opcode = + VSS_IMVM_CMD_ATTACH_STREAM; + attach_stream_cmd.attach_stream.handle = cvs_handle; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &attach_stream_cmd); + if (ret < 0) { + pr_err("%s: Error %d sending ATTACH_STREAM\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } + } + + return 0; + +fail: + apr_deregister(apr_mvm); + apr_mvm = NULL; + voice_set_apr_mvm(v, apr_mvm); + + apr_deregister(apr_cvs); + apr_cvs = NULL; + voice_set_apr_cvs(v, apr_cvs); + + apr_deregister(apr_cvp); + apr_cvp = NULL; + voice_set_apr_cvp(v, apr_cvp); + + cvp_handle = 0; + voice_set_cvp_handle(v, cvp_handle); + + cvs_handle = 0; + voice_set_cvs_handle(v, cvs_handle); + + return -EINVAL; +} + +static int voice_destroy_mvm_cvs_session(struct voice_data *v) +{ + int ret = 0; + struct mvm_detach_stream_cmd detach_stream; + struct apr_hdr mvm_destroy; + struct apr_hdr cvs_destroy; + void *apr_mvm = voice_get_apr_mvm(v); + void *apr_cvs = voice_get_apr_cvs(v); + u16 mvm_handle = voice_get_mvm_handle(v); + u16 cvs_handle = voice_get_cvs_handle(v); + + /* MVM, CVS sessions are destroyed only for Full control sessions. */ + if (v->voc_path == VOC_PATH_FULL) { + pr_info("%s: MVM detach stream\n", __func__); + + /* Detach voice stream. */ + detach_stream.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + detach_stream.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(detach_stream) - APR_HDR_SIZE); + detach_stream.hdr.src_port = 0; + detach_stream.hdr.dest_port = mvm_handle; + detach_stream.hdr.token = 0; + detach_stream.hdr.opcode = VSS_IMVM_CMD_DETACH_STREAM; + detach_stream.detach_stream.handle = cvs_handle; + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &detach_stream); + if (ret < 0) { + pr_err("%s: Error %d sending DETACH_STREAM\n", + __func__, ret); + + goto fail; + } + + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait event timeout\n", __func__); + goto fail; + } + + /* Destroy CVS. */ + pr_info("%s: CVS destroy session\n", __func__); + + cvs_destroy.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_destroy.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_destroy) - APR_HDR_SIZE); + cvs_destroy.src_port = 0; + cvs_destroy.dest_port = cvs_handle; + cvs_destroy.token = 0; + cvs_destroy.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_destroy); + if (ret < 0) { + pr_err("%s: Error %d sending CVS DESTROY\n", + __func__, ret); + + goto fail; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait event timeout\n", __func__); + + goto fail; + } + cvs_handle = 0; + voice_set_cvs_handle(v, cvs_handle); + + /* Destroy MVM. */ + pr_info("%s: MVM destroy session\n", __func__); + + mvm_destroy.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_destroy.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_destroy) - APR_HDR_SIZE); + mvm_destroy.src_port = 0; + mvm_destroy.dest_port = mvm_handle; + mvm_destroy.token = 0; + mvm_destroy.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_destroy); + if (ret < 0) { + pr_err("%s: Error %d sending MVM DESTROY\n", + __func__, ret); + + goto fail; + } + + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait event timeout\n", __func__); + + goto fail; + } + mvm_handle = 0; + voice_set_mvm_handle(v, mvm_handle); + } + +fail: + return 0; +} + +static int voice_send_tty_mode_to_modem(struct voice_data *v) +{ + struct msm_snddev_info *dev_tx_info; + struct msm_snddev_info *dev_rx_info; + int tty_mode = 0; + int ret = 0; + struct mvm_set_tty_mode_cmd mvm_tty_mode_cmd; + void *apr_mvm = voice_get_apr_mvm(v); + u16 mvm_handle = voice_get_mvm_handle(v); + + dev_rx_info = audio_dev_ctrl_find_dev(v->dev_rx.dev_id); + if (IS_ERR(dev_rx_info)) { + pr_err("bad dev_id %d\n", v->dev_rx.dev_id); + goto done; + } + + dev_tx_info = audio_dev_ctrl_find_dev(v->dev_tx.dev_id); + if (IS_ERR(dev_tx_info)) { + pr_err("bad dev_id %d\n", v->dev_tx.dev_id); + goto done; + } + + if ((dev_rx_info->capability & SNDDEV_CAP_TTY) && + (dev_tx_info->capability & SNDDEV_CAP_TTY)) + tty_mode = 3; /* FULL */ + else if (!(dev_tx_info->capability & SNDDEV_CAP_TTY) && + (dev_rx_info->capability & SNDDEV_CAP_TTY)) + tty_mode = 2; /* VCO */ + else if ((dev_tx_info->capability & SNDDEV_CAP_TTY) && + !(dev_rx_info->capability & SNDDEV_CAP_TTY)) + tty_mode = 1; /* HCO */ + + if (tty_mode) { + /* send tty mode cmd to mvm */ + mvm_tty_mode_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_tty_mode_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_tty_mode_cmd) - APR_HDR_SIZE); + pr_debug("pkt size = %d\n", mvm_tty_mode_cmd.hdr.pkt_size); + mvm_tty_mode_cmd.hdr.src_port = 0; + mvm_tty_mode_cmd.hdr.dest_port = mvm_handle; + mvm_tty_mode_cmd.hdr.token = 0; + mvm_tty_mode_cmd.hdr.opcode = VSS_ISTREAM_CMD_SET_TTY_MODE; + mvm_tty_mode_cmd.tty_mode.mode = tty_mode; + pr_info("tty mode =%d\n", mvm_tty_mode_cmd.tty_mode.mode); + + v->mvm_state = CMD_STATUS_FAIL; + pr_info("%s: MVM set tty\n", __func__); + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_tty_mode_cmd); + if (ret < 0) { + pr_err("Fail: sending VSS_ISTREAM_CMD_SET_TTY_MODE\n"); + goto done; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto done; + } + } + return 0; +done: + return -EINVAL; +} + +static int voice_send_cvs_cal_to_modem(struct voice_data *v) +{ + struct apr_hdr cvs_cal_cmd_hdr; + uint32_t *cmd_buf; + struct acdb_cal_data cal_data; + struct acdb_cal_block *cal_blk; + int32_t cal_size_per_network; + uint32_t *cal_data_per_network; + int index = 0; + int ret = 0; + void *apr_cvs = voice_get_apr_cvs(v); + u16 cvs_handle = voice_get_cvs_handle(v); + + /* fill the header */ + cvs_cal_cmd_hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_cal_cmd_hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_cal_cmd_hdr) - APR_HDR_SIZE); + cvs_cal_cmd_hdr.src_port = 0; + cvs_cal_cmd_hdr.dest_port = cvs_handle; + cvs_cal_cmd_hdr.token = 0; + cvs_cal_cmd_hdr.opcode = + VSS_ISTREAM_CMD_CACHE_CALIBRATION_DATA; + + pr_debug("voice_send_cvs_cal_to_modem\n"); + /* get the cvs cal data */ + get_vocstrm_cal(&cal_data); + if (cal_data.num_cal_blocks == 0) { + pr_err("%s: No calibration data to send!\n", __func__); + goto done; + } + + /* send cvs cal to modem */ + cmd_buf = kzalloc((sizeof(struct apr_hdr) + BUFFER_PAYLOAD_SIZE), + GFP_KERNEL); + if (!cmd_buf) { + pr_err("No memory is allocated.\n"); + return -ENOMEM; + } + pr_debug("----- num_cal_blocks=%d\n", (s32)cal_data.num_cal_blocks); + cal_blk = cal_data.cal_blocks; + pr_debug("cal_blk =%x\n", (uint32_t)cal_data.cal_blocks); + + for (; index < cal_data.num_cal_blocks; index++) { + cal_size_per_network = cal_blk[index].cal_size; + pr_debug(" cal size =%d\n", cal_size_per_network); + if (cal_size_per_network >= BUFFER_PAYLOAD_SIZE) + pr_err("Cal size is too big\n"); + cal_data_per_network = (u32 *)cal_blk[index].cal_kvaddr; + pr_debug(" cal data=%x\n", (uint32_t)cal_data_per_network); + cvs_cal_cmd_hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + cal_size_per_network); + pr_debug("header size =%d, pkt_size =%d\n", + APR_HDR_SIZE, cvs_cal_cmd_hdr.pkt_size); + memcpy(cmd_buf, &cvs_cal_cmd_hdr, APR_HDR_SIZE); + memcpy(cmd_buf + (APR_HDR_SIZE / sizeof(uint32_t)), + cal_data_per_network, cal_size_per_network); + pr_debug("send cvs cal: index =%d\n", index); + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, cmd_buf); + if (ret < 0) { + pr_err("Fail: sending cvs cal, idx=%d\n", index); + continue; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + } + kfree(cmd_buf); +done: + return 0; +} + +static int voice_send_cvp_cal_to_modem(struct voice_data *v) +{ + struct apr_hdr cvp_cal_cmd_hdr; + uint32_t *cmd_buf; + struct acdb_cal_data cal_data; + struct acdb_cal_block *cal_blk; + int32_t cal_size_per_network; + uint32_t *cal_data_per_network; + int index = 0; + int ret = 0; + void *apr_cvp = voice_get_apr_cvp(v); + u16 cvp_handle = voice_get_cvp_handle(v); + + + /* fill the header */ + cvp_cal_cmd_hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_cal_cmd_hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_cal_cmd_hdr) - APR_HDR_SIZE); + cvp_cal_cmd_hdr.src_port = 0; + cvp_cal_cmd_hdr.dest_port = cvp_handle; + cvp_cal_cmd_hdr.token = 0; + cvp_cal_cmd_hdr.opcode = + VSS_IVOCPROC_CMD_CACHE_CALIBRATION_DATA; + + /* get cal data */ + get_vocproc_cal(&cal_data); + if (cal_data.num_cal_blocks == 0) { + pr_err("%s: No calibration data to send!\n", __func__); + goto done; + } + + /* send cal to modem */ + cmd_buf = kzalloc((sizeof(struct apr_hdr) + BUFFER_PAYLOAD_SIZE), + GFP_KERNEL); + if (!cmd_buf) { + pr_err("No memory is allocated.\n"); + return -ENOMEM; + } + pr_debug("----- num_cal_blocks=%d\n", (s32)cal_data.num_cal_blocks); + cal_blk = cal_data.cal_blocks; + pr_debug(" cal_blk =%x\n", (uint32_t)cal_data.cal_blocks); + + for (; index < cal_data.num_cal_blocks; index++) { + cal_size_per_network = cal_blk[index].cal_size; + if (cal_size_per_network >= BUFFER_PAYLOAD_SIZE) + pr_err("Cal size is too big\n"); + pr_debug(" cal size =%d\n", cal_size_per_network); + cal_data_per_network = (u32 *)cal_blk[index].cal_kvaddr; + pr_debug(" cal data=%x\n", (uint32_t)cal_data_per_network); + + cvp_cal_cmd_hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + cal_size_per_network); + memcpy(cmd_buf, &cvp_cal_cmd_hdr, APR_HDR_SIZE); + memcpy(cmd_buf + (APR_HDR_SIZE / sizeof(*cmd_buf)), + cal_data_per_network, cal_size_per_network); + pr_debug("Send cvp cal\n"); + v->cvp_state = CMD_STATUS_FAIL; + pr_info("%s: CVP calib\n", __func__); + ret = apr_send_pkt(apr_cvp, cmd_buf); + if (ret < 0) { + pr_err("Fail: sending cvp cal, idx=%d\n", index); + continue; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + } + kfree(cmd_buf); +done: + return 0; +} + +static int voice_send_cvp_vol_tbl_to_modem(struct voice_data *v) +{ + struct apr_hdr cvp_vol_cal_cmd_hdr; + uint32_t *cmd_buf; + struct acdb_cal_data cal_data; + struct acdb_cal_block *cal_blk; + int32_t cal_size_per_network; + uint32_t *cal_data_per_network; + int index = 0; + int ret = 0; + void *apr_cvp = voice_get_apr_cvp(v); + u16 cvp_handle = voice_get_cvp_handle(v); + + + /* fill the header */ + cvp_vol_cal_cmd_hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_vol_cal_cmd_hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_vol_cal_cmd_hdr) - APR_HDR_SIZE); + cvp_vol_cal_cmd_hdr.src_port = 0; + cvp_vol_cal_cmd_hdr.dest_port = cvp_handle; + cvp_vol_cal_cmd_hdr.token = 0; + cvp_vol_cal_cmd_hdr.opcode = + VSS_IVOCPROC_CMD_CACHE_VOLUME_CALIBRATION_TABLE; + + /* get cal data */ + get_vocvol_cal(&cal_data); + if (cal_data.num_cal_blocks == 0) { + pr_err("%s: No calibration data to send!\n", __func__); + goto done; + } + + /* send cal to modem */ + cmd_buf = kzalloc((sizeof(struct apr_hdr) + BUFFER_PAYLOAD_SIZE), + GFP_KERNEL); + if (!cmd_buf) { + pr_err("No memory is allocated.\n"); + return -ENOMEM; + } + pr_debug("----- num_cal_blocks=%d\n", (s32)cal_data.num_cal_blocks); + cal_blk = cal_data.cal_blocks; + pr_debug("Cal_blk =%x\n", (uint32_t)cal_data.cal_blocks); + + for (; index < cal_data.num_cal_blocks; index++) { + cal_size_per_network = cal_blk[index].cal_size; + cal_data_per_network = (u32 *)cal_blk[index].cal_kvaddr; + pr_debug("Cal size =%d, index=%d\n", cal_size_per_network, + index); + pr_debug("Cal data=%x\n", (uint32_t)cal_data_per_network); + cvp_vol_cal_cmd_hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + cal_size_per_network); + memcpy(cmd_buf, &cvp_vol_cal_cmd_hdr, APR_HDR_SIZE); + memcpy(cmd_buf + (APR_HDR_SIZE / sizeof(uint32_t)), + cal_data_per_network, cal_size_per_network); + pr_debug("Send vol table\n"); + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, cmd_buf); + if (ret < 0) { + pr_err("Fail: sending cvp vol cal, idx=%d\n", index); + continue; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + } + kfree(cmd_buf); +done: + return 0; +} + +static int voice_set_dtx(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs = voice_get_apr_cvs(v); + u16 cvs_handle = voice_get_cvs_handle(v); + + /* Set DTX */ + struct cvs_set_enc_dtx_mode_cmd cvs_set_dtx = { + .hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER), + .hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_dtx) - APR_HDR_SIZE), + .hdr.src_port = 0, + .hdr.dest_port = cvs_handle, + .hdr.token = 0, + .hdr.opcode = VSS_ISTREAM_CMD_SET_ENC_DTX_MODE, + .dtx_mode.enable = v->mvs_info.dtx_mode, + }; + + pr_debug("%s: Setting DTX %d\n", __func__, v->mvs_info.dtx_mode); + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_dtx); + if (ret < 0) { + pr_err("%s: Error %d sending SET_DTX\n", __func__, ret); + + goto done; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + ret = -EINVAL; + } + +done: + return ret; +} + +static int voice_config_cvs_vocoder(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs = voice_get_apr_cvs(v); + u16 cvs_handle = voice_get_cvs_handle(v); + + /* Set media type. */ + struct cvs_set_media_type_cmd cvs_set_media_cmd; + + pr_info("%s: Setting media type\n", __func__); + + cvs_set_media_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_media_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_media_cmd) - APR_HDR_SIZE); + cvs_set_media_cmd.hdr.src_port = 0; + cvs_set_media_cmd.hdr.dest_port = cvs_handle; + cvs_set_media_cmd.hdr.token = 0; + cvs_set_media_cmd.hdr.opcode = VSS_ISTREAM_CMD_SET_MEDIA_TYPE; + cvs_set_media_cmd.media_type.tx_media_id = v->mvs_info.media_type; + cvs_set_media_cmd.media_type.rx_media_id = v->mvs_info.media_type; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_media_cmd); + if (ret < 0) { + pr_err("%s: Error %d sending SET_MEDIA_TYPE\n", + __func__, ret); + + goto done; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + ret = -EINVAL; + goto done; + } + + /* Set encoder properties. */ + switch (v->mvs_info.media_type) { + case VSS_MEDIA_ID_EVRC_MODEM: { + struct cvs_set_cdma_enc_minmax_rate_cmd cvs_set_cdma_rate; + + pr_info("%s: Setting EVRC min-max rate\n", __func__); + + cvs_set_cdma_rate.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_cdma_rate.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_cdma_rate) - APR_HDR_SIZE); + cvs_set_cdma_rate.hdr.src_port = 0; + cvs_set_cdma_rate.hdr.dest_port = cvs_handle; + cvs_set_cdma_rate.hdr.token = 0; + cvs_set_cdma_rate.hdr.opcode = + VSS_ISTREAM_CMD_CDMA_SET_ENC_MINMAX_RATE; + cvs_set_cdma_rate.cdma_rate.min_rate = v->mvs_info.rate; + cvs_set_cdma_rate.cdma_rate.max_rate = v->mvs_info.rate; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_cdma_rate); + if (ret < 0) { + pr_err("%s: Error %d sending SET_EVRC_MINMAX_RATE\n", + __func__, ret); + + goto done; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + ret = -EINVAL; + goto done; + } + + break; + } + + case VSS_MEDIA_ID_AMR_NB_MODEM: { + struct cvs_set_amr_enc_rate_cmd cvs_set_amr_rate; + + pr_info("%s: Setting AMR rate\n", __func__); + + cvs_set_amr_rate.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_amr_rate.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_amr_rate) - APR_HDR_SIZE); + cvs_set_amr_rate.hdr.src_port = 0; + cvs_set_amr_rate.hdr.dest_port = cvs_handle; + cvs_set_amr_rate.hdr.token = 0; + cvs_set_amr_rate.hdr.opcode = + VSS_ISTREAM_CMD_VOC_AMR_SET_ENC_RATE; + cvs_set_amr_rate.amr_rate.mode = v->mvs_info.rate; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_amr_rate); + if (ret < 0) { + pr_err("%s: Error %d sending SET_AMR_RATE\n", + __func__, ret); + + goto done; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + ret = -EINVAL; + goto done; + } + + ret = voice_set_dtx(v); + + break; + } + + case VSS_MEDIA_ID_AMR_WB_MODEM: { + struct cvs_set_amrwb_enc_rate_cmd cvs_set_amrwb_rate; + + pr_info("%s: Setting AMR WB rate\n", __func__); + + cvs_set_amrwb_rate.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_amrwb_rate.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_amrwb_rate) - APR_HDR_SIZE); + cvs_set_amrwb_rate.hdr.src_port = 0; + cvs_set_amrwb_rate.hdr.dest_port = cvs_handle; + cvs_set_amrwb_rate.hdr.token = 0; + cvs_set_amrwb_rate.hdr.opcode = + VSS_ISTREAM_CMD_VOC_AMRWB_SET_ENC_RATE; + cvs_set_amrwb_rate.amrwb_rate.mode = v->mvs_info.rate; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_amrwb_rate); + if (ret < 0) { + pr_err("%s: Error %d sending SET_AMRWB_RATE\n", + __func__, ret); + + goto done; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + ret = -EINVAL; + goto done; + } + + ret = voice_set_dtx(v); + + break; + } + + case VSS_MEDIA_ID_G729: + case VSS_MEDIA_ID_G711_ALAW: + case VSS_MEDIA_ID_G711_MULAW: { + ret = voice_set_dtx(v); + + break; + } + + default: { + /* Do nothing. */ + } + } + +done: + return ret; +} + +static int voice_send_start_voice_cmd(struct voice_data *v) +{ + struct apr_hdr mvm_start_voice_cmd; + int ret = 0; + void *apr_mvm = voice_get_apr_mvm(v); + u16 mvm_handle = voice_get_mvm_handle(v); + + mvm_start_voice_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_start_voice_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_start_voice_cmd) - APR_HDR_SIZE); + pr_info("send mvm_start_voice_cmd pkt size = %d\n", + mvm_start_voice_cmd.pkt_size); + mvm_start_voice_cmd.src_port = 0; + mvm_start_voice_cmd.dest_port = mvm_handle; + mvm_start_voice_cmd.token = 0; + mvm_start_voice_cmd.opcode = VSS_IMVM_CMD_START_VOICE; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_start_voice_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_START_VOICE\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_disable_vocproc(struct voice_data *v) +{ + struct apr_hdr cvp_disable_cmd; + int ret = 0; + void *apr_cvp = voice_get_apr_cvp(v); + u16 cvp_handle = voice_get_cvp_handle(v); + + /* disable vocproc and wait for respose */ + cvp_disable_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_disable_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_disable_cmd) - APR_HDR_SIZE); + pr_debug("cvp_disable_cmd pkt size = %d, cvp_handle=%d\n", + cvp_disable_cmd.pkt_size, cvp_handle); + cvp_disable_cmd.src_port = 0; + cvp_disable_cmd.dest_port = cvp_handle; + cvp_disable_cmd.token = 0; + cvp_disable_cmd.opcode = VSS_IVOCPROC_CMD_DISABLE; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_disable_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IVOCPROC_CMD_DISABLE\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } +#ifdef CONFIG_MSM8X60_RTAC + rtac_remove_voice(v); +#endif + + return 0; +fail: + return -EINVAL; +} + +static int voice_set_device(struct voice_data *v) +{ + struct cvp_set_device_cmd cvp_setdev_cmd; + struct msm_snddev_info *dev_tx_info; + int ret = 0; + void *apr_cvp = voice_get_apr_cvp(v); + u16 cvp_handle = voice_get_cvp_handle(v); + + + /* set device and wait for response */ + cvp_setdev_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_setdev_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_setdev_cmd) - APR_HDR_SIZE); + pr_debug(" send create cvp setdev, pkt size = %d\n", + cvp_setdev_cmd.hdr.pkt_size); + cvp_setdev_cmd.hdr.src_port = 0; + cvp_setdev_cmd.hdr.dest_port = cvp_handle; + cvp_setdev_cmd.hdr.token = 0; + cvp_setdev_cmd.hdr.opcode = VSS_IVOCPROC_CMD_SET_DEVICE; + + dev_tx_info = audio_dev_ctrl_find_dev(v->dev_tx.dev_id); + if (IS_ERR(dev_tx_info)) { + pr_err("bad dev_id %d\n", v->dev_tx.dev_id); + goto fail; + } + + cvp_setdev_cmd.cvp_set_device.tx_topology_id = + get_voice_tx_topology(); + if (cvp_setdev_cmd.cvp_set_device.tx_topology_id == 0) { + if (dev_tx_info->channel_mode > 1) + cvp_setdev_cmd.cvp_set_device.tx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_TX_DM_FLUENCE; + else + cvp_setdev_cmd.cvp_set_device.tx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + } + + /* Use default topology if invalid value in ACDB */ + cvp_setdev_cmd.cvp_set_device.rx_topology_id = + get_voice_rx_topology(); + if (cvp_setdev_cmd.cvp_set_device.rx_topology_id == 0) + cvp_setdev_cmd.cvp_set_device.rx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + cvp_setdev_cmd.cvp_set_device.tx_port_id = v->dev_tx.dev_port_id; + cvp_setdev_cmd.cvp_set_device.rx_port_id = v->dev_rx.dev_port_id; + pr_info("topology=%d , tx_port_id=%d, rx_port_id=%d\n", + cvp_setdev_cmd.cvp_set_device.tx_topology_id, + cvp_setdev_cmd.cvp_set_device.tx_port_id, + cvp_setdev_cmd.cvp_set_device.rx_port_id); + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_setdev_cmd); + if (ret < 0) { + pr_err("Fail in sending VOCPROC_FULL_CONTROL_SESSION\n"); + goto fail; + } + pr_debug("wait for cvp create session event\n"); + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* send cvs cal */ + voice_send_cvs_cal_to_modem(v); + + /* send cvp cal */ + voice_send_cvp_cal_to_modem(v); + + /* send cvp vol table cal */ + voice_send_cvp_vol_tbl_to_modem(v); + + /* enable vocproc and wait for respose */ + voice_send_enable_vocproc_cmd(v); + + /* send tty mode if tty device is used */ + voice_send_tty_mode_to_modem(v); + + if (v->voc_path == VOC_PATH_FULL) + voice_send_netid_timing_cmd(v); + +#ifdef CONFIG_MSM8X60_RTAC + rtac_add_voice(v); +#endif + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_stop_voice_cmd(struct voice_data *v) +{ + struct apr_hdr mvm_stop_voice_cmd; + int ret = 0; + void *apr_mvm = voice_get_apr_mvm(v); + u16 mvm_handle = voice_get_mvm_handle(v); + + mvm_stop_voice_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_stop_voice_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_stop_voice_cmd) - APR_HDR_SIZE); + pr_info("send mvm_stop_voice_cmd pkt size = %d\n", + mvm_stop_voice_cmd.pkt_size); + mvm_stop_voice_cmd.src_port = 0; + mvm_stop_voice_cmd.dest_port = mvm_handle; + mvm_stop_voice_cmd.token = 0; + mvm_stop_voice_cmd.opcode = VSS_IMVM_CMD_STOP_VOICE; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_stop_voice_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_STOP_VOICE\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_setup_modem_voice(struct voice_data *v) +{ + struct cvp_create_full_ctl_session_cmd cvp_session_cmd; + int ret = 0; + struct msm_snddev_info *dev_tx_info; + void *apr_cvp = voice_get_apr_cvp(v); + + /* create cvp session and wait for response */ + cvp_session_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_session_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_session_cmd) - APR_HDR_SIZE); + pr_info(" send create cvp session, pkt size = %d\n", + cvp_session_cmd.hdr.pkt_size); + cvp_session_cmd.hdr.src_port = 0; + cvp_session_cmd.hdr.dest_port = 0; + cvp_session_cmd.hdr.token = 0; + cvp_session_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION; + + dev_tx_info = audio_dev_ctrl_find_dev(v->dev_tx.dev_id); + if (IS_ERR(dev_tx_info)) { + pr_err("bad dev_id %d\n", v->dev_tx.dev_id); + goto fail; + } + + /* Use default topology if invalid value in ACDB */ + cvp_session_cmd.cvp_session.tx_topology_id = + get_voice_tx_topology(); + if (cvp_session_cmd.cvp_session.tx_topology_id == 0) { + if (dev_tx_info->channel_mode > 1) + cvp_session_cmd.cvp_session.tx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_TX_DM_FLUENCE; + else + cvp_session_cmd.cvp_session.tx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + } + + cvp_session_cmd.cvp_session.rx_topology_id = + get_voice_rx_topology(); + if (cvp_session_cmd.cvp_session.rx_topology_id == 0) + cvp_session_cmd.cvp_session.rx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + + cvp_session_cmd.cvp_session.direction = 2; /*tx and rx*/ + cvp_session_cmd.cvp_session.network_id = VSS_NETWORK_ID_DEFAULT; + cvp_session_cmd.cvp_session.tx_port_id = v->dev_tx.dev_port_id; + cvp_session_cmd.cvp_session.rx_port_id = v->dev_rx.dev_port_id; + pr_info("topology=%d net_id=%d, dir=%d tx_port_id=%d, rx_port_id=%d\n", + cvp_session_cmd.cvp_session.tx_topology_id, + cvp_session_cmd.cvp_session.network_id, + cvp_session_cmd.cvp_session.direction, + cvp_session_cmd.cvp_session.tx_port_id, + cvp_session_cmd.cvp_session.rx_port_id); + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_session_cmd); + if (ret < 0) { + pr_err("Fail in sending VOCPROC_FULL_CONTROL_SESSION\n"); + goto fail; + } + pr_debug("wait for cvp create session event\n"); + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* send cvs cal */ + voice_send_cvs_cal_to_modem(v); + + /* send cvp cal */ + voice_send_cvp_cal_to_modem(v); + + /* send cvp vol table cal */ + voice_send_cvp_vol_tbl_to_modem(v); + + return 0; + +fail: + return -EINVAL; +} + +static int voice_send_enable_vocproc_cmd(struct voice_data *v) +{ + int ret = 0; + struct apr_hdr cvp_enable_cmd; + + u16 cvp_handle = voice_get_cvp_handle(v); + void *apr_cvp = voice_get_apr_cvp(v); + + /* enable vocproc and wait for respose */ + cvp_enable_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_enable_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_enable_cmd) - APR_HDR_SIZE); + pr_debug("cvp_enable_cmd pkt size = %d, cvp_handle=%d\n", + cvp_enable_cmd.pkt_size, cvp_handle); + cvp_enable_cmd.src_port = 0; + cvp_enable_cmd.dest_port = cvp_handle; + cvp_enable_cmd.token = 0; + cvp_enable_cmd.opcode = VSS_IVOCPROC_CMD_ENABLE; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_enable_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IVOCPROC_CMD_ENABLE\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_netid_timing_cmd(struct voice_data *v) +{ + int ret = 0; + void *apr_mvm = voice_get_apr_mvm(v); + struct mvm_set_network_cmd mvm_set_network; + struct mvm_set_voice_timing_cmd mvm_set_voice_timing; + u16 mvm_handle = voice_get_mvm_handle(v); + + ret = voice_config_cvs_vocoder(v); + if (ret < 0) { + pr_err("%s: Error %d configuring CVS voc", + __func__, ret); + goto fail; + } + /* Set network ID. */ + pr_debug("%s: Setting network ID\n", __func__); + + mvm_set_network.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_set_network.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_set_network) - APR_HDR_SIZE); + mvm_set_network.hdr.src_port = 0; + mvm_set_network.hdr.dest_port = mvm_handle; + mvm_set_network.hdr.token = 0; + mvm_set_network.hdr.opcode = VSS_ICOMMON_CMD_SET_NETWORK; + mvm_set_network.network.network_id = v->mvs_info.network_type; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_set_network); + if (ret < 0) { + pr_err("%s: Error %d sending SET_NETWORK\n", __func__, ret); + goto fail; + } + + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* Set voice timing. */ + pr_debug("%s: Setting voice timing\n", __func__); + + mvm_set_voice_timing.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_set_voice_timing.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_set_voice_timing) - APR_HDR_SIZE); + mvm_set_voice_timing.hdr.src_port = 0; + mvm_set_voice_timing.hdr.dest_port = mvm_handle; + mvm_set_voice_timing.hdr.token = 0; + mvm_set_voice_timing.hdr.opcode = + VSS_ICOMMON_CMD_SET_VOICE_TIMING; + mvm_set_voice_timing.timing.mode = 0; + mvm_set_voice_timing.timing.enc_offset = 8000; + mvm_set_voice_timing.timing.dec_req_offset = 3300; + mvm_set_voice_timing.timing.dec_offset = 8300; + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_set_voice_timing); + if (ret < 0) { + pr_err("%s: Error %d sending SET_TIMING\n", __func__, ret); + goto fail; + } + + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_attach_vocproc(struct voice_data *v) +{ + int ret = 0; + struct mvm_attach_vocproc_cmd mvm_a_vocproc_cmd; + void *apr_mvm = voice_get_apr_mvm(v); + u16 mvm_handle = voice_get_mvm_handle(v); + u16 cvp_handle = voice_get_cvp_handle(v); + + /* send enable vocproc */ + voice_send_enable_vocproc_cmd(v); + + /* attach vocproc and wait for response */ + mvm_a_vocproc_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_a_vocproc_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_a_vocproc_cmd) - APR_HDR_SIZE); + pr_info("send mvm_a_vocproc_cmd pkt size = %d\n", + mvm_a_vocproc_cmd.hdr.pkt_size); + mvm_a_vocproc_cmd.hdr.src_port = 0; + mvm_a_vocproc_cmd.hdr.dest_port = mvm_handle; + mvm_a_vocproc_cmd.hdr.token = 0; + mvm_a_vocproc_cmd.hdr.opcode = VSS_IMVM_CMD_ATTACH_VOCPROC; + mvm_a_vocproc_cmd.mvm_attach_cvp_handle.handle = cvp_handle; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_a_vocproc_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_ATTACH_VOCPROC\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* send tty mode if tty device is used */ + voice_send_tty_mode_to_modem(v); + + if (v->voc_path == VOC_PATH_FULL) + voice_send_netid_timing_cmd(v); + +#ifdef CONFIG_MSM8X60_RTAC + rtac_add_voice(v); +#endif + return 0; +fail: + return -EINVAL; +} + +static int voice_destroy_modem_voice(struct voice_data *v) +{ + struct mvm_detach_vocproc_cmd mvm_d_vocproc_cmd; + struct apr_hdr cvp_destroy_session_cmd; + int ret = 0; + void *apr_mvm = voice_get_apr_mvm(v); + void *apr_cvp = voice_get_apr_cvp(v); + u16 mvm_handle = voice_get_mvm_handle(v); + u16 cvp_handle = voice_get_cvp_handle(v); + + /* detach VOCPROC and wait for response from mvm */ + mvm_d_vocproc_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_d_vocproc_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_d_vocproc_cmd) - APR_HDR_SIZE); + pr_info("mvm_d_vocproc_cmd pkt size = %d\n", + mvm_d_vocproc_cmd.hdr.pkt_size); + mvm_d_vocproc_cmd.hdr.src_port = 0; + mvm_d_vocproc_cmd.hdr.dest_port = mvm_handle; + mvm_d_vocproc_cmd.hdr.token = 0; + mvm_d_vocproc_cmd.hdr.opcode = VSS_IMVM_CMD_DETACH_VOCPROC; + mvm_d_vocproc_cmd.mvm_detach_cvp_handle.handle = cvp_handle; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_d_vocproc_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_DETACH_VOCPROC\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* destrop cvp session */ + cvp_destroy_session_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_destroy_session_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_destroy_session_cmd) - APR_HDR_SIZE); + pr_info("cvp_destroy_session_cmd pkt size = %d\n", + cvp_destroy_session_cmd.pkt_size); + cvp_destroy_session_cmd.src_port = 0; + cvp_destroy_session_cmd.dest_port = cvp_handle; + cvp_destroy_session_cmd.token = 0; + cvp_destroy_session_cmd.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_destroy_session_cmd); + if (ret < 0) { + pr_err("Fail in sending APRV2_IBASIC_CMD_DESTROY_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + +#ifdef CONFIG_MSM8X60_RTAC + rtac_remove_voice(v); +#endif + cvp_handle = 0; + voice_set_cvp_handle(v, cvp_handle); + + return 0; + +fail: + return -EINVAL; +} + +static int voice_send_mute_cmd_to_modem(struct voice_data *v) +{ + struct cvs_set_mute_cmd cvs_mute_cmd; + int ret = 0; + void *apr_cvs = voice_get_apr_cvs(v); + u16 cvs_handle = voice_get_cvs_handle(v); + + /* send mute/unmute to cvs */ + cvs_mute_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_mute_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_mute_cmd) - APR_HDR_SIZE); + cvs_mute_cmd.hdr.src_port = 0; + cvs_mute_cmd.hdr.dest_port = cvs_handle; + cvs_mute_cmd.hdr.token = 0; + cvs_mute_cmd.hdr.opcode = VSS_ISTREAM_CMD_SET_MUTE; + cvs_mute_cmd.cvs_set_mute.direction = 0; /*tx*/ + cvs_mute_cmd.cvs_set_mute.mute_flag = v->dev_tx.mute; + + pr_info(" mute value =%d\n", cvs_mute_cmd.cvs_set_mute.mute_flag); + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_mute_cmd); + if (ret < 0) { + pr_err("Fail: send STREAM SET MUTE\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) + pr_err("%s: wait_event timeout\n", __func__); + +fail: + return 0; +} + +static int voice_send_vol_index_to_modem(struct voice_data *v) +{ + struct cvp_set_rx_volume_index_cmd cvp_vol_cmd; + int ret = 0; + void *apr_cvp = voice_get_apr_cvp(v); + u16 cvp_handle = voice_get_cvp_handle(v); + + /* send volume index to cvp */ + cvp_vol_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvp_vol_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_vol_cmd) - APR_HDR_SIZE); + cvp_vol_cmd.hdr.src_port = 0; + cvp_vol_cmd.hdr.dest_port = cvp_handle; + cvp_vol_cmd.hdr.token = 0; + cvp_vol_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_SET_RX_VOLUME_INDEX; + cvp_vol_cmd.cvp_set_vol_idx.vol_index = v->dev_rx.volume; + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_vol_cmd); + if (ret < 0) { + pr_err("Fail in sending RX VOL INDEX\n"); + return -EINVAL; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + return 0; +} + +static int voice_cvs_start_record(struct voice_data *v, uint32_t rec_mode) +{ + int ret = 0; + void *apr_cvs = voice_get_apr_cvs(v); + u16 cvs_handle = voice_get_cvs_handle(v); + struct cvs_start_record_cmd cvs_start_record; + + pr_debug("%s: Start record %d\n", __func__, rec_mode); + + cvs_start_record.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_start_record.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_start_record) - APR_HDR_SIZE); + cvs_start_record.hdr.src_port = 0; + cvs_start_record.hdr.dest_port = cvs_handle; + cvs_start_record.hdr.token = 0; + cvs_start_record.hdr.opcode = VSS_ISTREAM_CMD_START_RECORD; + + if (rec_mode == VOC_REC_UPLINK) { + cvs_start_record.rec_mode.rx_tap_point = VSS_TAP_POINT_NONE; + cvs_start_record.rec_mode.tx_tap_point = + VSS_TAP_POINT_STREAM_END; + } else if (rec_mode == VOC_REC_DOWNLINK) { + cvs_start_record.rec_mode.rx_tap_point = + VSS_TAP_POINT_STREAM_END; + cvs_start_record.rec_mode.tx_tap_point = VSS_TAP_POINT_NONE; + } else if (rec_mode == VOC_REC_BOTH) { + cvs_start_record.rec_mode.rx_tap_point = + VSS_TAP_POINT_STREAM_END; + cvs_start_record.rec_mode.tx_tap_point = + VSS_TAP_POINT_STREAM_END; + } else { + pr_err("%s: Invalid in-call rec_mode %d\n", __func__, rec_mode); + + ret = -EINVAL; + goto fail; + } + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_start_record); + if (ret < 0) { + pr_err("%s: Error %d sending START_RECORD\n", __func__, ret); + + goto fail; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + + return 0; + +fail: + return ret; +} + +static int voice_cvs_stop_record(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs = voice_get_apr_cvs(v); + u16 cvs_handle = voice_get_cvs_handle(v); + struct apr_hdr cvs_stop_record; + + pr_debug("%s: Stop record\n", __func__); + + cvs_stop_record.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_stop_record.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_stop_record) - APR_HDR_SIZE); + cvs_stop_record.src_port = 0; + cvs_stop_record.dest_port = cvs_handle; + cvs_stop_record.token = 0; + cvs_stop_record.opcode = VSS_ISTREAM_CMD_STOP_RECORD; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_stop_record); + if (ret < 0) { + pr_err("%s: Error %d sending STOP_RECORD\n", __func__, ret); + + goto fail; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + + return 0; + +fail: + return ret; +} + +int voice_start_record(uint32_t rec_mode, uint32_t set) +{ + int ret = 0; + u16 cvs_handle; + + pr_debug("%s: rec_mode %d, set %d\n", __func__, rec_mode, set); + + mutex_lock(&voice.lock); + + cvs_handle = voice_get_cvs_handle(&voice); + + if (cvs_handle != 0) { + if (set) + ret = voice_cvs_start_record(&voice, rec_mode); + else + ret = voice_cvs_stop_record(&voice); + } else { + /* Cache the value for later. */ + voice.rec_info.pending = set; + voice.rec_info.rec_mode = rec_mode; + } + + mutex_unlock(&voice.lock); + + return ret; +} + +static int voice_cvs_start_playback(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs = voice_get_apr_cvs(v); + u16 cvs_handle = voice_get_cvs_handle(v); + struct apr_hdr cvs_start_playback; + + pr_debug("%s: Start playback\n", __func__); + + cvs_start_playback.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_start_playback.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_start_playback) - APR_HDR_SIZE); + cvs_start_playback.src_port = 0; + cvs_start_playback.dest_port = cvs_handle; + cvs_start_playback.token = 0; + cvs_start_playback.opcode = VSS_ISTREAM_CMD_START_PLAYBACK; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_start_playback); + if (ret < 0) { + pr_err("%s: Error %d sending START_PLAYBACK\n", + __func__, ret); + + goto fail; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + + v->music_info.playing = 1; + + return 0; + +fail: + return ret; +} + +static int voice_cvs_stop_playback(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs = voice_get_apr_cvs(v); + u16 cvs_handle = voice_get_cvs_handle(v); + struct apr_hdr cvs_stop_playback; + + pr_debug("%s: Stop playback\n", __func__); + + if (v->music_info.playing) { + cvs_stop_playback.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cvs_stop_playback.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_stop_playback) - APR_HDR_SIZE); + cvs_stop_playback.src_port = 0; + cvs_stop_playback.dest_port = cvs_handle; + cvs_stop_playback.token = 0; + + cvs_stop_playback.opcode = VSS_ISTREAM_CMD_STOP_PLAYBACK; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_stop_playback); + if (ret < 0) { + pr_err("%s: Error %d sending STOP_PLAYBACK\n", + __func__, ret); + + goto fail; + } + + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + + v->music_info.playing = 0; + } else { + pr_err("%s: Stop playback already sent\n", __func__); + } + + return 0; + +fail: + return ret; +} + +int voice_start_playback(uint32_t set) +{ + int ret = 0; + u16 cvs_handle; + + pr_debug("%s: Start playback %d\n", __func__, set); + + mutex_lock(&voice.lock); + + cvs_handle = voice_get_cvs_handle(&voice); + + if (cvs_handle != 0) { + if (set) + ret = voice_cvs_start_playback(&voice); + else + ret = voice_cvs_stop_playback(&voice); + } else { + /* Cache the value for later. */ + pr_debug("%s: Caching ICP value", __func__); + + voice.music_info.pending = set; + } + + mutex_unlock(&voice.lock); + + return ret; +} + +static void voice_auddev_cb_function(u32 evt_id, + union auddev_evt_data *evt_payload, + void *private_data) +{ + struct voice_data *v = &voice; + struct sidetone_cal sidetone_cal_data; + int rc = 0; + pr_info("auddev_cb_function, evt_id=%d,\n", evt_id); + if ((evt_id != AUDDEV_EVT_START_VOICE) || + (evt_id != AUDDEV_EVT_END_VOICE)) { + if (evt_payload == NULL) { + pr_err(" evt_payload is NULL pointer\n"); + return; + } + } + + switch (evt_id) { + case AUDDEV_EVT_START_VOICE: + mutex_lock(&v->lock); + + if ((v->voc_state == VOC_INIT) || + (v->voc_state == VOC_RELEASE)) { + v->v_call_status = VOICE_CALL_START; + if ((v->dev_rx.enabled == VOICE_DEV_ENABLED) + && (v->dev_tx.enabled == VOICE_DEV_ENABLED)) { + rc = voice_apr_register(v); + if (rc < 0) { + pr_err("%s: voice apr registration" + "failed\n", __func__); + mutex_unlock(&v->lock); + return; + } + voice_create_mvm_cvs_session(v); + voice_setup_modem_voice(v); + voice_attach_vocproc(v); + voice_send_start_voice_cmd(v); + get_sidetone_cal(&sidetone_cal_data); + msm_snddev_enable_sidetone( + v->dev_rx.dev_id, + sidetone_cal_data.enable, + sidetone_cal_data.gain); + v->voc_state = VOC_RUN; + + /* Start in-call recording if command was + * pending. */ + if (v->rec_info.pending) { + voice_cvs_start_record(v, + v->rec_info.rec_mode); + + v->rec_info.pending = 0; + } + + /* Start in-call music delivery if command was + * pending. */ + if (v->music_info.pending) { + voice_cvs_start_playback(v); + + v->music_info.pending = 0; + } + } + } + + mutex_unlock(&v->lock); + break; + case AUDDEV_EVT_DEV_CHG_VOICE: + if (v->dev_rx.enabled == VOICE_DEV_ENABLED) + msm_snddev_enable_sidetone(v->dev_rx.dev_id, 0, 0); + v->dev_rx.enabled = VOICE_DEV_DISABLED; + v->dev_tx.enabled = VOICE_DEV_DISABLED; + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN) { + /* send cmd to modem to do voice device change */ + voice_disable_vocproc(v); + v->voc_state = VOC_CHANGE; + } + + mutex_unlock(&v->lock); + break; + case AUDDEV_EVT_DEV_RDY: + mutex_lock(&v->lock); + + if (v->voc_state == VOC_CHANGE) { + /* get port Ids */ + if (evt_payload->voc_devinfo.dev_type == DIR_RX) { + v->dev_rx.dev_port_id = + evt_payload->voc_devinfo.dev_port_id; + v->dev_rx.sample = + evt_payload->voc_devinfo.dev_sample; + v->dev_rx.dev_id = + evt_payload->voc_devinfo.dev_id; + v->dev_rx.enabled = VOICE_DEV_ENABLED; + } else { + v->dev_tx.dev_port_id = + evt_payload->voc_devinfo.dev_port_id; + v->dev_tx.sample = + evt_payload->voc_devinfo.dev_sample; + v->dev_tx.enabled = VOICE_DEV_ENABLED; + v->dev_tx.dev_id = + evt_payload->voc_devinfo.dev_id; + } + if ((v->dev_rx.enabled == VOICE_DEV_ENABLED) && + (v->dev_tx.enabled == VOICE_DEV_ENABLED)) { + voice_set_device(v); + get_sidetone_cal(&sidetone_cal_data); + msm_snddev_enable_sidetone( + v->dev_rx.dev_id, + sidetone_cal_data.enable, + sidetone_cal_data.gain); + v->voc_state = VOC_RUN; + } + } else if ((v->voc_state == VOC_INIT) || + (v->voc_state == VOC_RELEASE)) { + /* get AFE ports */ + if (evt_payload->voc_devinfo.dev_type == DIR_RX) { + /* get rx port id */ + v->dev_rx.dev_port_id = + evt_payload->voc_devinfo.dev_port_id; + v->dev_rx.sample = + evt_payload->voc_devinfo.dev_sample; + v->dev_rx.dev_id = + evt_payload->voc_devinfo.dev_id; + v->dev_rx.enabled = VOICE_DEV_ENABLED; + } else { + /* get tx port id */ + v->dev_tx.dev_port_id = + evt_payload->voc_devinfo.dev_port_id; + v->dev_tx.sample = + evt_payload->voc_devinfo.dev_sample; + v->dev_tx.dev_id = + evt_payload->voc_devinfo.dev_id; + v->dev_tx.enabled = VOICE_DEV_ENABLED; + } + if ((v->dev_rx.enabled == VOICE_DEV_ENABLED) && + (v->dev_tx.enabled == VOICE_DEV_ENABLED) && + (v->v_call_status == VOICE_CALL_START)) { + rc = voice_apr_register(v); + if (rc < 0) { + pr_err("%s: voice apr registration" + "failed\n", __func__); + mutex_unlock(&v->lock); + return; + } + voice_create_mvm_cvs_session(v); + voice_setup_modem_voice(v); + voice_attach_vocproc(v); + voice_send_start_voice_cmd(v); + get_sidetone_cal(&sidetone_cal_data); + msm_snddev_enable_sidetone( + v->dev_rx.dev_id, + sidetone_cal_data.enable, + sidetone_cal_data.gain); + v->voc_state = VOC_RUN; + + /* Start in-call recording if command was + * pending. */ + if (v->rec_info.pending) { + voice_cvs_start_record(v, + v->rec_info.rec_mode); + + v->rec_info.pending = 0; + } + + /* Start in-call music delivery if command was + * pending. */ + if (v->music_info.pending) { + voice_cvs_start_playback(v); + + v->music_info.pending = 0; + } + } + } + + mutex_unlock(&v->lock); + break; + case AUDDEV_EVT_DEVICE_VOL_MUTE_CHG: + /* cache the mute and volume index value */ + if (evt_payload->voc_devinfo.dev_type == DIR_TX) { + v->dev_tx.mute = + evt_payload->voc_vm_info.dev_vm_val.mute; + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN) + voice_send_mute_cmd_to_modem(v); + + mutex_unlock(&v->lock); + } else { + v->dev_rx.volume = evt_payload-> + voc_vm_info.dev_vm_val.vol; + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN) + voice_send_vol_index_to_modem(v); + + mutex_unlock(&v->lock); + } + break; + case AUDDEV_EVT_REL_PENDING: + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN) { + voice_disable_vocproc(v); + v->voc_state = VOC_CHANGE; + } + + mutex_unlock(&v->lock); + + if (evt_payload->voc_devinfo.dev_type == DIR_RX) + v->dev_rx.enabled = VOICE_DEV_DISABLED; + else + v->dev_tx.enabled = VOICE_DEV_DISABLED; + + break; + case AUDDEV_EVT_END_VOICE: + /* recover the tx mute and rx volume to the default values */ + v->dev_tx.mute = v->default_mute_val; + v->dev_rx.volume = v->default_vol_val; + if (v->dev_rx.enabled == VOICE_DEV_ENABLED) + msm_snddev_enable_sidetone(v->dev_rx.dev_id, 0, 0); + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN) { + /* call stop modem voice */ + voice_send_stop_voice_cmd(v); + voice_destroy_modem_voice(v); + voice_destroy_mvm_cvs_session(v); + v->voc_state = VOC_RELEASE; + } else if (v->voc_state == VOC_CHANGE) { + voice_send_stop_voice_cmd(v); + voice_destroy_mvm_cvs_session(v); + v->voc_state = VOC_RELEASE; + } + + mutex_unlock(&v->lock); + + v->v_call_status = VOICE_CALL_END; + + break; + default: + pr_err("UNKNOWN EVENT\n"); + } + return; +} +EXPORT_SYMBOL(voice_auddev_cb_function); + +int voice_set_voc_path_full(uint32_t set) +{ + int rc = 0; + + pr_info("%s: %d\n", __func__, set); + + mutex_lock(&voice.lock); + + if (voice.voc_state == VOC_INIT || voice.voc_state == VOC_RELEASE) { + if (set) + voice.voc_path = VOC_PATH_FULL; + else + voice.voc_path = VOC_PATH_PASSIVE; + } else { + pr_err("%s: Invalid voc path set to %d, in state %d\n", + __func__, set, voice.voc_state); + + rc = -EPERM; + } + + mutex_unlock(&voice.lock); + + return rc; +} +EXPORT_SYMBOL(voice_set_voc_path_full); + +void voice_register_mvs_cb(ul_cb_fn ul_cb, + dl_cb_fn dl_cb, + void *private_data) +{ + voice.mvs_info.ul_cb = ul_cb; + voice.mvs_info.dl_cb = dl_cb; + voice.mvs_info.private_data = private_data; +} + +void voice_config_vocoder(uint32_t media_type, + uint32_t rate, + uint32_t network_type, + uint32_t dtx_mode) +{ + voice.mvs_info.media_type = media_type; + voice.mvs_info.rate = rate; + voice.mvs_info.network_type = network_type; + voice.mvs_info.dtx_mode = dtx_mode; +} + +static int32_t modem_mvm_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr; + struct voice_data *v = priv; + + pr_debug("%s\n", __func__); + pr_debug("%s: Payload Length = %d, opcode=%x\n", __func__, + data->payload_size, data->opcode); + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s:Reset event received in Voice service\n", + __func__); + apr_reset(v->apr_mvm); + apr_reset(v->apr_q6_mvm); + v->apr_q6_mvm = NULL; + v->apr_mvm = NULL; + v->mvm_handle = 0; + v->mvm_q6_handle = 0; + return 0; + } + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (data->payload_size) { + ptr = data->payload; + + pr_info("%x %x\n", ptr[0], ptr[1]); + /* ping mvm service ACK */ + + if (ptr[0] == + VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION || + ptr[0] == + VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION) { + /* Passive session is used for voice call + * through modem. Full session is used for voice + * call through Q6. */ + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + if (!ptr[1]) { + pr_debug("%s: MVM handle is %d\n", + __func__, data->src_port); + + voice_set_mvm_handle(v, data->src_port); + } else + pr_info("got NACK for sending \ + MVM create session \n"); + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == VSS_IMVM_CMD_START_VOICE) { + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == VSS_IMVM_CMD_ATTACH_VOCPROC) { + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == VSS_IMVM_CMD_STOP_VOICE) { + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == VSS_IMVM_CMD_DETACH_VOCPROC) { + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == VSS_ISTREAM_CMD_SET_TTY_MODE) { + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == APRV2_IBASIC_CMD_DESTROY_SESSION) { + pr_debug("%s: DESTROY resp\n", __func__); + + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == VSS_IMVM_CMD_ATTACH_STREAM) { + pr_debug("%s: ATTACH_STREAM resp 0x%x\n", + __func__, ptr[1]); + + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == VSS_IMVM_CMD_DETACH_STREAM) { + pr_debug("%s: DETACH_STREAM resp 0x%x\n", + __func__, ptr[1]); + + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == VSS_ICOMMON_CMD_SET_NETWORK) { + pr_debug("%s: SET_NETWORK resp 0x%x\n", + __func__, ptr[1]); + + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else if (ptr[0] == VSS_ICOMMON_CMD_SET_VOICE_TIMING) { + pr_debug("%s: SET_VOICE_TIMING resp 0x%x\n", + __func__, ptr[1]); + + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + } else + pr_debug("%s: not match cmd = 0x%x\n", + __func__, ptr[0]); + } + } + + return 0; +} + +static int32_t modem_cvs_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr; + struct voice_data *v = priv; + + pr_debug("%s\n", __func__); + pr_debug("%s: Payload Length = %d, opcode=%x\n", __func__, + data->payload_size, data->opcode); + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s:Reset event received in Voice service\n", + __func__); + apr_reset(v->apr_cvs); + apr_reset(v->apr_q6_cvs); + v->apr_q6_cvs = NULL; + v->apr_cvs = NULL; + v->cvs_handle = 0; + v->cvs_q6_handle = 0; + return 0; + } + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (data->payload_size) { + ptr = data->payload; + + pr_info("%x %x\n", ptr[0], ptr[1]); + /*response from modem CVS */ + if (ptr[0] == + VSS_ISTREAM_CMD_CREATE_PASSIVE_CONTROL_SESSION || + ptr[0] == + VSS_ISTREAM_CMD_CREATE_FULL_CONTROL_SESSION) { + if (!ptr[1]) { + pr_debug("%s: CVS handle is %d\n", + __func__, data->src_port); + voice_set_cvs_handle(v, data->src_port); + } else + pr_info("got NACK for sending \ + CVS create session \n"); + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == + VSS_ISTREAM_CMD_CACHE_CALIBRATION_DATA) { + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == + VSS_ISTREAM_CMD_SET_MUTE) { + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == VSS_ISTREAM_CMD_SET_MEDIA_TYPE) { + pr_debug("%s: SET_MEDIA resp 0x%x\n", + __func__, ptr[1]); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == + VSS_ISTREAM_CMD_VOC_AMR_SET_ENC_RATE) { + pr_debug("%s: SET_AMR_RATE resp 0x%x\n", + __func__, ptr[1]); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == + VSS_ISTREAM_CMD_VOC_AMRWB_SET_ENC_RATE) { + pr_debug("%s: SET_AMR_WB_RATE resp 0x%x\n", + __func__, ptr[1]); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == VSS_ISTREAM_CMD_SET_ENC_DTX_MODE) { + pr_debug("%s: SET_DTX resp 0x%x\n", + __func__, ptr[1]); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == + VSS_ISTREAM_CMD_CDMA_SET_ENC_MINMAX_RATE) { + pr_debug("%s: SET_CDMA_RATE resp 0x%x\n", + __func__, ptr[1]); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == APRV2_IBASIC_CMD_DESTROY_SESSION) { + pr_debug("%s: DESTROY resp\n", __func__); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == VSS_ISTREAM_CMD_START_RECORD) { + pr_debug("%s: START_RECORD resp 0x%x\n", + __func__, ptr[1]); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == VSS_ISTREAM_CMD_STOP_RECORD) { + pr_debug("%s: STOP_RECORD resp 0x%x\n", + __func__, ptr[1]); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); +#ifdef CONFIG_MSM8X60_RTAC + } else if (ptr[0] == VOICE_CMD_SET_PARAM) { + rtac_make_voice_callback(RTAC_CVS, ptr, + data->payload_size); +#endif + } else if (ptr[0] == VSS_ISTREAM_CMD_START_PLAYBACK) { + pr_debug("%s: START_PLAYBACK resp 0x%x\n", + __func__, ptr[1]); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else if (ptr[0] == VSS_ISTREAM_CMD_STOP_PLAYBACK) { + pr_debug("%s: STOP_PLAYBACK resp 0x%x\n", + __func__, ptr[1]); + + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + } else + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + } + } else if (data->opcode == VSS_ISTREAM_EVT_SEND_ENC_BUFFER) { + uint32_t *voc_pkt = data->payload; + uint32_t pkt_len = data->payload_size; + + if (voc_pkt != NULL && v->mvs_info.ul_cb != NULL) { + pr_debug("%s: Media type is 0x%x\n", + __func__, voc_pkt[0]); + + /* Remove media ID from payload. */ + voc_pkt++; + pkt_len = pkt_len - 4; + + v->mvs_info.ul_cb((uint8_t *)voc_pkt, + pkt_len, + v->mvs_info.private_data); + } else { + pr_err("%s: voc_pkt is 0x%x ul_cb is 0x%x\n", + __func__, (unsigned int)voc_pkt, + (unsigned int) v->mvs_info.ul_cb); + } + } else if (data->opcode == VSS_ISTREAM_EVT_SEND_DEC_BUFFER) { + pr_debug("%s: Send dec buf resp\n", __func__); + } else if (data->opcode == VSS_ISTREAM_EVT_REQUEST_DEC_BUFFER) { + struct cvs_send_dec_buf_cmd send_dec_buf; + int ret = 0; + uint32_t pkt_len = 0; + + if (v->mvs_info.dl_cb != NULL) { + send_dec_buf.dec_buf.media_id = v->mvs_info.media_type; + + v->mvs_info.dl_cb( + (uint8_t *)&send_dec_buf.dec_buf.packet_data, + &pkt_len, + v->mvs_info.private_data); + + send_dec_buf.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + send_dec_buf.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(send_dec_buf.dec_buf.media_id) + pkt_len); + send_dec_buf.hdr.src_port = 0; + send_dec_buf.hdr.dest_port = voice_get_cvs_handle(v); + send_dec_buf.hdr.token = 0; + send_dec_buf.hdr.opcode = + VSS_ISTREAM_EVT_SEND_DEC_BUFFER; + + ret = apr_send_pkt(voice_get_apr_cvs(v), + (uint32_t *) &send_dec_buf); + if (ret < 0) { + pr_err("%s: Error %d sending DEC_BUF\n", + __func__, ret); + goto fail; + } + } else { + pr_err("%s: ul_cb is NULL\n", __func__); + } +#ifdef CONFIG_MSM8X60_RTAC + } else if (data->opcode == VOICE_EVT_GET_PARAM_ACK) { + rtac_make_voice_callback(RTAC_CVS, data->payload, + data->payload_size); +#endif + + } else { + pr_debug("%s: Unknown opcode 0x%x\n", __func__, data->opcode); + } + +fail: + return 0; +} + +static int32_t modem_cvp_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr; + struct voice_data *v = priv; + + pr_debug("%s\n", __func__); + pr_debug("%s: Payload Length = %d, opcode=%x\n", __func__, + data->payload_size, data->opcode); + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s:Reset event received in Voice service\n", + __func__); + apr_reset(v->apr_cvp); + apr_reset(v->apr_q6_cvp); + v->apr_q6_cvp = NULL; + v->apr_cvp = NULL; + v->cvp_handle = 0; + v->cvp_q6_handle = 0; + return 0; + } + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (data->payload_size) { + ptr = data->payload; + + pr_info("%x %x\n", ptr[0], ptr[1]); + /*response from modem CVP */ + if (ptr[0] == + VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION) { + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + if (!ptr[1]) { + voice_set_cvp_handle(v, data->src_port); + pr_debug("cvphdl=%d\n", data->src_port); + } else + pr_info("got NACK from CVP create \ + session response\n"); + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + } else if (ptr[0] == + VSS_IVOCPROC_CMD_CACHE_CALIBRATION_DATA) { + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + } else if (ptr[0] == VSS_IVOCPROC_CMD_SET_DEVICE) { + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + } else if (ptr[0] == + VSS_IVOCPROC_CMD_SET_RX_VOLUME_INDEX) { + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + } else if (ptr[0] == VSS_IVOCPROC_CMD_ENABLE) { + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + } else if (ptr[0] == VSS_IVOCPROC_CMD_DISABLE) { + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + } else if (ptr[0] == APRV2_IBASIC_CMD_DESTROY_SESSION) { + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + } else if (ptr[0] == + VSS_IVOCPROC_CMD_CACHE_VOLUME_CALIBRATION_TABLE + ) { + + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); +#ifdef CONFIG_MSM8X60_RTAC + } else if (ptr[0] == VOICE_CMD_SET_PARAM) { + rtac_make_voice_callback(RTAC_CVP, ptr, + data->payload_size); +#endif + } else + pr_debug("%s: not match cmd = 0x%x\n", + __func__, ptr[0]); + } +#ifdef CONFIG_MSM8X60_RTAC + } else if (data->opcode == VOICE_EVT_GET_PARAM_ACK) { + rtac_make_voice_callback(RTAC_CVP, data->payload, + data->payload_size); +#endif + } + return 0; +} + + +static int __init voice_init(void) +{ + int rc = 0; + struct voice_data *v = &voice; + + /* set default value */ + v->default_mute_val = 1; /* default is mute */ + v->default_vol_val = 0; + v->default_sample_val = 8000; + + /* initialize dev_rx and dev_tx */ + memset(&v->dev_tx, 0, sizeof(struct device_data)); + memset(&v->dev_rx, 0, sizeof(struct device_data)); + v->dev_rx.volume = v->default_vol_val; + v->dev_tx.mute = v->default_mute_val; + + v->voc_state = VOC_INIT; + v->voc_path = VOC_PATH_PASSIVE; + v->adsp_version = 0; + init_waitqueue_head(&v->mvm_wait); + init_waitqueue_head(&v->cvs_wait); + init_waitqueue_head(&v->cvp_wait); + + mutex_init(&v->lock); + + v->mvm_handle = 0; + v->cvs_handle = 0; + v->cvp_handle = 0; + + v->mvm_q6_handle = 0; + v->cvs_q6_handle = 0; + v->cvp_q6_handle = 0; + + v->apr_mvm = NULL; + v->apr_cvs = NULL; + v->apr_cvp = NULL; + + v->apr_q6_mvm = NULL; + v->apr_q6_cvs = NULL; + v->apr_q6_cvp = NULL; + + /* Initialize MVS info. */ + memset(&v->mvs_info, 0, sizeof(v->mvs_info)); + v->mvs_info.network_type = VSS_NETWORK_ID_DEFAULT; + + v->rec_info.pending = 0; + v->rec_info.rec_mode = VOC_REC_NONE; + + memset(&v->music_info, 0, sizeof(v->music_info)); + + v->device_events = AUDDEV_EVT_DEV_CHG_VOICE | + AUDDEV_EVT_DEV_RDY | + AUDDEV_EVT_REL_PENDING | + AUDDEV_EVT_START_VOICE | + AUDDEV_EVT_END_VOICE | + AUDDEV_EVT_DEVICE_VOL_MUTE_CHG | + AUDDEV_EVT_FREQ_CHG; + + pr_debug("to register call back\n"); + /* register callback to auddev */ + auddev_register_evt_listner(v->device_events, AUDDEV_CLNT_VOC, + 0, voice_auddev_cb_function, v); + + return rc; +} + +device_initcall(voice_init); diff --git a/arch/arm/mach-msm/qdsp6v2/qcelp_in.c b/arch/arm/mach-msm/qdsp6v2/qcelp_in.c new file mode 100644 index 00000000000..894f1ede843 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/qcelp_in.c @@ -0,0 +1,329 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "audio_utils.h" + +/* Buffer with meta*/ +#define PCM_BUF_SIZE (4096 + sizeof(struct meta_in)) + +/* Maximum 10 frames in buffer with meta */ +#define FRAME_SIZE (1 + ((35+sizeof(struct meta_out_dsp)) * 10)) + +void q6asm_qcelp_in_cb(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6audio_in * audio = (struct q6audio_in *)priv; + unsigned long flags; + + pr_debug("%s:session id %d: opcode - %d\n", __func__, + audio->ac->session, opcode); + + spin_lock_irqsave(&audio->dsp_lock, flags); + switch (opcode) { + case ASM_DATA_EVENT_READ_DONE: + audio_in_get_dsp_frames(audio, token, payload); + break; + case ASM_DATA_EVENT_WRITE_DONE: + atomic_inc(&audio->in_count); + wake_up(&audio->write_wait); + break; + case ASM_DATA_CMDRSP_EOS: + audio->eos_rsp = 1; + wake_up(&audio->read_wait); + break; + case ASM_STREAM_CMDRSP_GET_ENCDEC_PARAM: + break; + case ASM_STREAM_CMDRSP_GET_PP_PARAMS: + break; + case ASM_SESSION_EVENT_TX_OVERFLOW: + pr_err("%s:session id %d:ASM_SESSION_EVENT_TX_OVERFLOW\n", + __func__, audio->ac->session); + break; + default: + pr_err("%s:session id %d: Ignore opcode[0x%x]\n", __func__, + audio->ac->session, opcode); + break; + } + spin_unlock_irqrestore(&audio->dsp_lock, flags); +} +/* ------------------- device --------------------- */ +static long qcelp_in_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct q6audio_in *audio = file->private_data; + int rc = 0; + int cnt = 0; + + switch (cmd) { + case AUDIO_START: { + struct msm_audio_qcelp_enc_config *enc_cfg; + enc_cfg = audio->enc_cfg; + pr_debug("%s:session id %d: default buf alloc[%d]\n", __func__, + audio->ac->session, audio->buf_alloc); + if (audio->enabled == 1) { + pr_info("%s:AUDIO_START already over\n", __func__); + rc = 0; + break; + } + rc = audio_in_buf_alloc(audio); + if (rc < 0) { + pr_err("%s:session id %d: buffer allocation failed\n", + __func__, audio->ac->session); + break; + } + + /* reduced_rate_level, rate_modulation_cmd set to zero + currently not configurable from user space */ + rc = q6asm_enc_cfg_blk_qcelp(audio->ac, + audio->buf_cfg.frames_per_buf, + enc_cfg->min_bit_rate, + enc_cfg->max_bit_rate, 0, 0); + + if (rc < 0) { + pr_err("%s:session id %d: cmd qcelp media format block\ + failed\n", __func__, audio->ac->session); + break; + } + if (audio->feedback == NON_TUNNEL_MODE) { + rc = q6asm_media_format_block_pcm(audio->ac, + audio->pcm_cfg.sample_rate, + audio->pcm_cfg.channel_count); + + if (rc < 0) { + pr_err("%s:session id %d: media format block\ + failed\n", __func__, audio->ac->session); + break; + } + } + pr_debug("%s:session id %d: AUDIO_START enable[%d]\n", __func__, + audio->ac->session, audio->enabled); + rc = audio_in_enable(audio); + if (!rc) { + audio->enabled = 1; + } else { + audio->enabled = 0; + pr_err("%s:session id %d: Audio Start procedure failed\ + rc=%d\n", __func__, audio->ac->session, rc); + break; + } + while (cnt++ < audio->str_cfg.buffer_count) + q6asm_read(audio->ac); /* Push buffer to DSP */ + rc = 0; + pr_debug("%s:session id %d: AUDIO_START success enable[%d]\n", + __func__, audio->ac->session, audio->enabled); + break; + } + case AUDIO_STOP: { + pr_debug("%s:session id %d: AUDIO_STOP\n", __func__, + audio->ac->session); + rc = audio_in_disable(audio); + if (rc < 0) { + pr_err("%s:session id %d: Audio Stop procedure failed\ + rc=%d\n", __func__, audio->ac->session, + rc); + break; + } + break; + } + case AUDIO_GET_QCELP_ENC_CONFIG: { + if (copy_to_user((void *)arg, audio->enc_cfg, + sizeof(struct msm_audio_qcelp_enc_config))) + rc = -EFAULT; + break; + } + case AUDIO_SET_QCELP_ENC_CONFIG: { + struct msm_audio_qcelp_enc_config cfg; + struct msm_audio_qcelp_enc_config *enc_cfg; + enc_cfg = audio->enc_cfg; + if (copy_from_user(&cfg, (void *) arg, + sizeof(struct msm_audio_qcelp_enc_config))) { + rc = -EFAULT; + break; + } + + if (cfg.min_bit_rate > 4 || + cfg.min_bit_rate < 1) { + pr_err("%s:session id %d: invalid min bitrate\n", + __func__, audio->ac->session); + rc = -EINVAL; + break; + } + if (cfg.max_bit_rate > 4 || + cfg.max_bit_rate < 1) { + pr_err("%s:session id %d: invalid max bitrate\n", + __func__, audio->ac->session); + rc = -EINVAL; + break; + } + enc_cfg->min_bit_rate = cfg.min_bit_rate; + enc_cfg->max_bit_rate = cfg.max_bit_rate; + pr_debug("%s:session id %d: min_bit_rate= 0x%x\ + max_bit_rate=0x%x\n", __func__, + audio->ac->session, enc_cfg->min_bit_rate, + enc_cfg->max_bit_rate); + break; + } + default: + rc = -EINVAL; + } + return rc; +} + +static int qcelp_in_open(struct inode *inode, struct file *file) +{ + struct q6audio_in *audio = NULL; + struct msm_audio_qcelp_enc_config *enc_cfg; + int rc = 0; + + audio = kzalloc(sizeof(struct q6audio_in), GFP_KERNEL); + + if (audio == NULL) { + pr_err("%s:session id %d: Could not allocate memory for qcelp\ + driver\n", __func__, audio->ac->session); + return -ENOMEM; + } + /* Allocate memory for encoder config param */ + audio->enc_cfg = kzalloc(sizeof(struct msm_audio_qcelp_enc_config), + GFP_KERNEL); + if (audio->enc_cfg == NULL) { + pr_err("%s:session id %d: Could not allocate memory for aac\ + config param\n", __func__, audio->ac->session); + kfree(audio); + return -ENOMEM; + } + enc_cfg = audio->enc_cfg; + + mutex_init(&audio->lock); + mutex_init(&audio->read_lock); + mutex_init(&audio->write_lock); + spin_lock_init(&audio->dsp_lock); + init_waitqueue_head(&audio->read_wait); + init_waitqueue_head(&audio->write_wait); + + /* Settings will be re-config at AUDIO_SET_CONFIG, + * but at least we need to have initial config + */ + audio->str_cfg.buffer_size = FRAME_SIZE; + audio->str_cfg.buffer_count = FRAME_NUM; + audio->min_frame_size = 35; + audio->max_frames_per_buf = 10; + audio->pcm_cfg.buffer_size = PCM_BUF_SIZE; + audio->pcm_cfg.buffer_count = PCM_BUF_COUNT; + enc_cfg->min_bit_rate = 4; + enc_cfg->max_bit_rate = 4; + audio->pcm_cfg.channel_count = 1; + audio->pcm_cfg.sample_rate = 8000; + audio->buf_cfg.meta_info_enable = 0x01; + audio->buf_cfg.frames_per_buf = 0x01; + + audio->ac = q6asm_audio_client_alloc((app_cb)q6asm_qcelp_in_cb, + (void *)audio); + + if (!audio->ac) { + pr_err("%s:session id %d: Could not allocate memory for audio\ + client\n", __func__, audio->ac->session); + kfree(audio->enc_cfg); + kfree(audio); + return -ENOMEM; + } + + /* open qcelp encoder in T/NT mode */ + if ((file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->feedback = NON_TUNNEL_MODE; + rc = q6asm_open_read_write(audio->ac, FORMAT_V13K, + FORMAT_LINEAR_PCM); + if (rc < 0) { + pr_err("%s:session id %d: NT mode Open failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + pr_info("%s:session id %d: NT mode encoder success\n", __func__, + audio->ac->session); + } else if (!(file->f_mode & FMODE_WRITE) && + (file->f_mode & FMODE_READ)) { + audio->feedback = TUNNEL_MODE; + rc = q6asm_open_read(audio->ac, FORMAT_V13K); + if (rc < 0) { + pr_err("%s:session id %d: T mode Open failed rc=%d\n", + __func__, audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + /* register for tx overflow (valid for tunnel mode only) */ + rc = q6asm_reg_tx_overflow(audio->ac, 0x01); + if (rc < 0) { + pr_err("%s:session id %d: TX Overflow registration\ + failed rc=%d\n", __func__, audio->ac->session, rc); + rc = -ENODEV; + goto fail; + } + pr_info("%s:session id %d: T mode encoder success\n", __func__, + audio->ac->session); + } else { + pr_err("%s:session id %d: Unexpected mode\n", __func__, + audio->ac->session); + rc = -EACCES; + goto fail; + } + + audio->opened = 1; + atomic_set(&audio->in_count, PCM_BUF_COUNT); + atomic_set(&audio->out_count, 0x00); + audio->enc_ioctl = qcelp_in_ioctl; + file->private_data = audio; + + pr_info("%s:session id %d: success\n", __func__, audio->ac->session); + return 0; +fail: + q6asm_audio_client_free(audio->ac); + kfree(audio->enc_cfg); + kfree(audio); + return rc; +} + +static const struct file_operations audio_in_fops = { + .owner = THIS_MODULE, + .open = qcelp_in_open, + .release = audio_in_release, + .read = audio_in_read, + .write = audio_in_write, + .unlocked_ioctl = audio_in_ioctl, +}; + +struct miscdevice audio_qcelp_in_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_qcelp_in", + .fops = &audio_in_fops, +}; + +static int __init qcelp_in_init(void) +{ + return misc_register(&audio_qcelp_in_misc); +} + +device_initcall(qcelp_in_init); diff --git a/arch/arm/mach-msm/qdsp6v2/rtac.c b/arch/arm/mach-msm/qdsp6v2/rtac.c new file mode 100644 index 00000000000..34762246dbd --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/rtac.c @@ -0,0 +1,1032 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Max size of payload (buf size - apr header) */ +#define MAX_PAYLOAD_SIZE 4076 +#define RTAC_MAX_ACTIVE_DEVICES 4 +#define RTAC_MAX_ACTIVE_VOICE_COMBOS 2 +#define RTAC_MAX_ACTIVE_POPP 8 +#define RTAC_BUF_SIZE 4096 + +#define TIMEOUT_MS 1000 + +/* APR data */ +struct rtac_apr_data { + void *apr_handle; + atomic_t cmd_state; + wait_queue_head_t cmd_wait; +}; + +static struct rtac_apr_data rtac_adm_apr_data; +static struct rtac_apr_data rtac_asm_apr_data[SESSION_MAX+1]; +static struct rtac_apr_data rtac_voice_apr_data[RTAC_VOICE_MODES]; + + +/* Dev ctrl info */ +struct rtac_dev_ctrl_data { + uint32_t dev_id; + uint32_t afe_port; +}; + +struct rtac_dev_ctrl { + uint32_t num_of_dev; + struct rtac_dev_ctrl_data device[RTAC_MAX_ACTIVE_DEVICES]; +}; + +static struct rtac_dev_ctrl rtac_dev_ctl_data; + + +/* ADM info & APR */ +struct rtac_adm_data { + uint32_t afe_port; + uint32_t copp; + uint32_t num_of_popp; + uint32_t popp[RTAC_MAX_ACTIVE_POPP]; +}; + +struct rtac_adm { + uint32_t num_of_dev; + struct rtac_adm_data device[RTAC_MAX_ACTIVE_DEVICES]; +}; +static struct rtac_adm rtac_adm_data; +static u32 rtac_adm_payload_size; +static u32 rtac_adm_user_buf_size; +static u8 *rtac_adm_buffer; + + +/* ASM APR */ +static u32 rtac_asm_payload_size; +static u32 rtac_asm_user_buf_size; +static u8 *rtac_asm_buffer; + + +/* Voice info & APR */ +struct rtac_voice_data { + uint32_t tx_dev_id; + uint32_t rx_dev_id; + uint32_t tx_afe_port; + uint32_t rx_afe_port; + uint16_t cvs_handle; + uint16_t cvp_handle; +}; + +struct rtac_voice { + uint32_t num_of_voice_combos; + struct rtac_voice_data voice[RTAC_MAX_ACTIVE_VOICE_COMBOS]; +}; + +static struct rtac_voice rtac_voice_data; +static u32 rtac_voice_payload_size; +static u32 rtac_voice_user_buf_size; +static u8 *rtac_voice_buffer; + + + +struct mutex rtac_dev_ctrl_mutex; +struct mutex rtac_adm_mutex; +struct mutex rtac_adm_apr_mutex; +struct mutex rtac_asm_apr_mutex; +struct mutex rtac_voice_mutex; +struct mutex rtac_voice_apr_mutex; + +static int rtac_open(struct inode *inode, struct file *f) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static int rtac_release(struct inode *inode, struct file *f) +{ + pr_debug("%s\n", __func__); + return 0; +} + + +/* Dev ctrl info */ +void rtac_add_dev_ctrl_device(u32 dev_id, struct msm_snddev_info *dev_info) +{ + s32 i = 0; + pr_debug("%s\n", __func__); + + mutex_lock(&rtac_dev_ctrl_mutex); + if (rtac_dev_ctl_data.num_of_dev == RTAC_MAX_ACTIVE_DEVICES) { + pr_err("%s, Can't add anymore RTAC devices!\n", __func__); + goto done; + } + + /* Check if device already added */ + if (rtac_dev_ctl_data.num_of_dev != 0) { + for (; i < rtac_dev_ctl_data.num_of_dev; i++) { + if (rtac_dev_ctl_data.device[i].dev_id == dev_id) + goto done; + } + } + + /* Add device */ + rtac_dev_ctl_data.num_of_dev++; + rtac_dev_ctl_data.device[i].dev_id = dev_id; + rtac_dev_ctl_data.device[i].afe_port = dev_info->copp_id; +done: + mutex_unlock(&rtac_dev_ctrl_mutex); + return; +} + +void shift_dev_ctrl_devices(u32 dev_idx) +{ + for (; dev_idx < rtac_dev_ctl_data.num_of_dev - 1; dev_idx++) { + rtac_dev_ctl_data.device[dev_idx].dev_id = + rtac_dev_ctl_data.device[dev_idx + 1].dev_id; + rtac_dev_ctl_data.device[dev_idx].afe_port = + rtac_dev_ctl_data.device[dev_idx + 1].afe_port; + } +} + +void rtac_remove_dev_ctrl_device(u32 dev_id) +{ + s32 i; + pr_debug("%s\n", __func__); + + mutex_lock(&rtac_dev_ctrl_mutex); + if (rtac_dev_ctl_data.num_of_dev == 0) + goto done; + + /* look for device */ + for (i = 0; i < rtac_dev_ctl_data.num_of_dev; i++) { + if (rtac_dev_ctl_data.device[i].dev_id == dev_id) { + shift_dev_ctrl_devices(i); + rtac_dev_ctl_data.device[i].dev_id = 0; + rtac_dev_ctl_data.device[i].afe_port = 0; + rtac_dev_ctl_data.num_of_dev--; + break; + } + } +done: + mutex_unlock(&rtac_dev_ctrl_mutex); + return; +} + +void update_rtac(u32 evt_id, u32 dev_id, struct msm_snddev_info *dev_info) +{ + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + rtac_add_dev_ctrl_device(dev_id, dev_info); + break; + case AUDDEV_EVT_DEV_RLS: + rtac_remove_dev_ctrl_device(dev_id); + break; + default: + break; + } +} + + +/* ADM Info */ +void add_popp(u32 dev_idx, u32 port_id, u32 popp_id) +{ + u32 i = 0; + + for (; i < rtac_adm_data.device[dev_idx].num_of_popp; i++) + if (rtac_adm_data.device[dev_idx].popp[i] == popp_id) + goto done; + + + if (rtac_adm_data.device[dev_idx].num_of_popp == + RTAC_MAX_ACTIVE_POPP) { + pr_err("%s, Max POPP!\n", __func__); + goto done; + } + rtac_adm_data.device[dev_idx].popp[ + rtac_adm_data.device[dev_idx].num_of_popp++] = popp_id; +done: + return; +} + +void rtac_add_adm_device(u32 port_id, u32 popp_id) +{ + u32 i = 0; + pr_debug("%s\n", __func__); + + mutex_lock(&rtac_adm_mutex); + if (rtac_adm_data.num_of_dev == RTAC_MAX_ACTIVE_DEVICES) { + pr_err("%s, Can't add anymore RTAC devices!\n", __func__); + goto done; + } + + /* Check if device already added */ + if (rtac_adm_data.num_of_dev != 0) { + for (; i < rtac_adm_data.num_of_dev; i++) { + if (rtac_adm_data.device[i].afe_port == port_id) + add_popp(i, port_id, popp_id); + goto done; + } + } + + if (rtac_adm_data.device[i].num_of_popp == RTAC_MAX_ACTIVE_POPP) { + pr_err("%s, Max POPP!\n", __func__); + goto done; + } + + /* Add device */ + rtac_adm_data.num_of_dev++; + rtac_adm_data.device[i].afe_port = port_id; + rtac_adm_data.device[i].copp = adm_get_copp_id(port_id); + rtac_adm_data.device[i].popp[ + rtac_adm_data.device[i].num_of_popp++] = popp_id; +done: + mutex_unlock(&rtac_adm_mutex); + return; +} + +void shift_adm_devices(u32 dev_idx) +{ + for (; dev_idx < rtac_adm_data.num_of_dev - 1; dev_idx++) { + memcpy(&rtac_adm_data.device[dev_idx], + &rtac_adm_data.device[dev_idx + 1], + sizeof(rtac_adm_data.device[dev_idx])); + } +} + +void rtac_remove_adm_device(u32 port_id) +{ + s32 i; + pr_debug("%s\n", __func__); + + mutex_lock(&rtac_adm_mutex); + /* look for device */ + for (i = 0; i < rtac_adm_data.num_of_dev; i++) { + if (rtac_adm_data.device[i].afe_port == port_id) { + shift_adm_devices(i); + memset(&rtac_adm_data.device[i], 0, + sizeof(rtac_adm_data.device[i])); + rtac_adm_data.num_of_dev--; + break; + } + } + mutex_unlock(&rtac_adm_mutex); + return; +} + + +/* Voice Info */ +void set_rtac_voice_data(int idx, struct voice_data *v) +{ + rtac_voice_data.voice[idx].tx_dev_id = v->dev_tx.dev_id; + rtac_voice_data.voice[idx].rx_dev_id = v->dev_rx.dev_id; + rtac_voice_data.voice[idx].tx_afe_port = v->dev_tx.dev_port_id; + rtac_voice_data.voice[idx].rx_afe_port = v->dev_rx.dev_port_id; + rtac_voice_data.voice[idx].cvs_handle = v->cvs_handle; + rtac_voice_data.voice[idx].cvp_handle = v->cvp_handle; + +} + +void rtac_add_voice(struct voice_data *v) +{ + u32 i = 0; + pr_debug("%s\n", __func__); + mutex_lock(&rtac_voice_mutex); + + if (rtac_voice_data.num_of_voice_combos == + RTAC_MAX_ACTIVE_VOICE_COMBOS) { + pr_err("%s, Can't add anymore RTAC devices!\n", __func__); + goto done; + } + + /* Check if device already added */ + if (rtac_voice_data.num_of_voice_combos != 0) { + for (; i < rtac_voice_data.num_of_voice_combos; i++) { + if (rtac_voice_data.voice[i].cvp_handle == + v->cvp_handle) { + set_rtac_voice_data(i, v); + goto done; + } + } + } + + /* Add device */ + rtac_voice_data.num_of_voice_combos++; + set_rtac_voice_data(i, v); +done: + mutex_unlock(&rtac_voice_mutex); + return; +} + +void shift_voice_devices(u32 idx) +{ + for (; idx < rtac_voice_data.num_of_voice_combos - 1; idx++) { + memcpy(&rtac_voice_data.voice[idx], + &rtac_voice_data.voice[idx + 1], + sizeof(rtac_voice_data.voice[idx])); + } +} + +void rtac_remove_voice(struct voice_data *v) +{ + u32 i = 0; + pr_debug("%s\n", __func__); + + mutex_lock(&rtac_voice_mutex); + /* look for device */ + for (i = 0; i < rtac_voice_data.num_of_voice_combos; i++) { + if (rtac_voice_data.voice[i].cvp_handle == v->cvp_handle) { + shift_voice_devices(i); + rtac_voice_data.num_of_voice_combos--; + memset(&rtac_voice_data.voice[ + rtac_voice_data.num_of_voice_combos], 0, + sizeof(rtac_voice_data.voice + [rtac_voice_data.num_of_voice_combos])); + break; + } + } + mutex_unlock(&rtac_voice_mutex); + return; +} + + + +/* ADM APR */ +void rtac_set_adm_handle(void *handle) +{ + pr_debug("%s\n", __func__); + + mutex_lock(&rtac_adm_apr_mutex); + rtac_adm_apr_data.apr_handle = handle; + mutex_unlock(&rtac_adm_apr_mutex); +} + +bool rtac_make_adm_callback(uint32_t *payload, u32 payload_size) +{ + if (atomic_read(&rtac_adm_apr_data.cmd_state) != 1) + return false; + + pr_debug("%s\n", __func__); + /* Offset data for in-band payload */ + rtac_copy_adm_payload_to_user(payload, payload_size); + atomic_set(&rtac_adm_apr_data.cmd_state, 0); + wake_up(&rtac_adm_apr_data.cmd_wait); + return true; +} + +void rtac_copy_adm_payload_to_user(void *payload, u32 payload_size) +{ + pr_debug("%s\n", __func__); + rtac_adm_payload_size = payload_size; + + memcpy(rtac_adm_buffer, &payload_size, sizeof(u32)); + if (payload_size != 0) { + if (payload_size > rtac_adm_user_buf_size) { + pr_err("%s: Buffer set not big enough for " + "returned data, buf size = %d, " + "ret data = %d\n", __func__, + rtac_adm_user_buf_size, payload_size); + goto done; + } + memcpy(rtac_adm_buffer + sizeof(u32), payload, payload_size); + } +done: + return; +} + +u32 send_adm_apr(void *buf, u32 opcode) +{ + s32 result; + u32 count = 0; + u32 bytes_returned = 0; + u32 port_id = 0; + u32 copp_id; + u32 payload_size; + struct apr_hdr adm_params; + pr_debug("%s\n", __func__); + + if (copy_from_user(&count, (void *)buf, sizeof(count))) { + pr_err("%s: Copy to user failed! buf = 0x%x\n", + __func__, (unsigned int)buf); + result = -EFAULT; + goto done; + } + + if (count <= 0) { + pr_err("%s: Invalid buffer size = %d\n", __func__, count); + goto done; + } + + if (copy_from_user(&payload_size, buf + sizeof(u32), sizeof(u32))) { + pr_err("%s: Could not copy payload size from user buffer\n", + __func__); + goto done; + } + + + if ((payload_size < 0) || + (payload_size > MAX_PAYLOAD_SIZE)) { + + pr_err("%s: Invalid payload size = %d\n", + __func__, payload_size); + goto done; + } + + if (copy_from_user(&copp_id, buf + 2 * sizeof(u32), sizeof(u32))) { + pr_err("%s: Could not copy port id from user buffer\n", + __func__); + goto done; + } + + for (port_id = 0; port_id < AFE_MAX_PORTS; port_id++) { + if (adm_get_copp_id(port_id) == copp_id) + break; + } + if (port_id >= AFE_MAX_PORTS) { + pr_err("%s: Invalid Port ID = %d\n", __func__, port_id); + goto done; + } + + mutex_lock(&rtac_adm_apr_mutex); + if (rtac_adm_apr_data.apr_handle == NULL) { + pr_err("%s: APR not initialized\n", __func__); + goto err; + } + + /* Set globals for copy of returned payload */ + rtac_adm_user_buf_size = count; + /* Copy buffer to in-band payload */ + if (copy_from_user(rtac_adm_buffer + sizeof(adm_params), + buf + 3 * sizeof(u32), payload_size)) { + pr_err("%s: Could not copy payload from user buffer\n", + __func__); + goto err; + } + + /* Pack header */ + adm_params.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(20), APR_PKT_VER); + adm_params.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + payload_size); + adm_params.src_svc = APR_SVC_ADM; + adm_params.src_domain = APR_DOMAIN_APPS; + adm_params.src_port = port_id; + adm_params.dest_svc = APR_SVC_ADM; + adm_params.dest_domain = APR_DOMAIN_ADSP; + adm_params.dest_port = adm_get_copp_id(port_id); + adm_params.token = port_id; + adm_params.opcode = opcode; + + memcpy(rtac_adm_buffer, &adm_params, sizeof(adm_params)); + atomic_set(&rtac_adm_apr_data.cmd_state, 1); + + pr_debug("%s: Sending RTAC command size = %d\n", + __func__, adm_params.pkt_size); + + result = apr_send_pkt(rtac_adm_apr_data.apr_handle, + (uint32_t *)rtac_adm_buffer); + if (result < 0) { + pr_err("%s: Set params failed port = %d\n", + __func__, port_id); + goto err; + } + /* Wait for the callback */ + result = wait_event_timeout(rtac_adm_apr_data.cmd_wait, + (atomic_read(&rtac_adm_apr_data.cmd_state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + mutex_unlock(&rtac_adm_apr_mutex); + if (!result) { + pr_err("%s: Set params timed out port = %d\n", + __func__, port_id); + goto done; + } + + if (rtac_adm_payload_size != 0) { + if (copy_to_user(buf, rtac_adm_buffer, + rtac_adm_payload_size + sizeof(u32))) { + pr_err("%s: Could not copy buffer to user," + "size = %d\n", __func__, payload_size); + goto done; + } + } + + /* Return data written for SET & data read for GET */ + if (opcode == ADM_CMD_GET_PARAMS) + bytes_returned = rtac_adm_payload_size; + else + bytes_returned = payload_size; +done: + return bytes_returned; +err: + mutex_unlock(&rtac_adm_apr_mutex); + return bytes_returned; +} + + +/* ASM APR */ +void rtac_set_asm_handle(u32 session_id, void *handle) +{ + pr_debug("%s\n", __func__); + + mutex_lock(&rtac_asm_apr_mutex); + rtac_asm_apr_data[session_id].apr_handle = handle; + mutex_unlock(&rtac_asm_apr_mutex); +} + +bool rtac_make_asm_callback(u32 session_id, uint32_t *payload, + u32 payload_size) +{ + if (atomic_read(&rtac_asm_apr_data[session_id].cmd_state) != 1) + return false; + + pr_debug("%s\n", __func__); + /* Offset data for in-band payload */ + rtac_copy_asm_payload_to_user(payload, payload_size); + atomic_set(&rtac_asm_apr_data[session_id].cmd_state, 0); + wake_up(&rtac_asm_apr_data[session_id].cmd_wait); + return true; +} + +void rtac_copy_asm_payload_to_user(void *payload, u32 payload_size) +{ + pr_debug("%s\n", __func__); + rtac_asm_payload_size = payload_size; + + memcpy(rtac_asm_buffer, &payload_size, sizeof(u32)); + if (payload_size) { + if (payload_size > rtac_asm_user_buf_size) { + pr_err("%s: Buffer set not big enough for " + "returned data, buf size = %d, " + "ret data = %d\n", __func__, + rtac_asm_user_buf_size, payload_size); + goto done; + } + memcpy(rtac_asm_buffer + sizeof(u32), payload, payload_size); + } +done: + return; +} + +u32 send_rtac_asm_apr(void *buf, u32 opcode) +{ + s32 result; + u32 count = 0; + u32 bytes_returned = 0; + u32 session_id = 0; + u32 payload_size; + struct apr_hdr asm_params; + pr_debug("%s\n", __func__); + + if (copy_from_user(&count, (void *)buf, sizeof(count))) { + pr_err("%s: Copy to user failed! buf = 0x%x\n", + __func__, (unsigned int)buf); + result = -EFAULT; + goto done; + } + + if (count <= 0) { + pr_err("%s: Invalid buffer size = %d\n", __func__, count); + goto done; + } + + if (copy_from_user(&payload_size, buf + sizeof(u32), sizeof(u32))) { + pr_err("%s: Could not copy payload size from user buffer\n", + __func__); + goto done; + } + + if ((payload_size < 0) || + (payload_size > MAX_PAYLOAD_SIZE)) { + + pr_err("%s: Invalid payload size = %d\n", + __func__, payload_size); + goto done; + } + + if (copy_from_user(&session_id, buf + 2 * sizeof(u32), sizeof(u32))) { + pr_err("%s: Could not copy session id from user buffer\n", + __func__); + goto done; + } + if (session_id >= AFE_MAX_PORTS) { + pr_err("%s: Invalid Session = %d\n", __func__, session_id); + goto done; + } + + mutex_lock(&rtac_asm_apr_mutex); + if (rtac_asm_apr_data[session_id].apr_handle == NULL) { + pr_err("%s: APR not initialized\n", __func__); + goto err; + } + + /* Set globals for copy of returned payload */ + rtac_asm_user_buf_size = count; + + /* Copy buffer to in-band payload */ + if (copy_from_user(rtac_asm_buffer + sizeof(asm_params), + buf + 3 * sizeof(u32), payload_size)) { + pr_err("%s: Could not copy payload from user buffer\n", + __func__); + goto err; + } + + /* Pack header */ + asm_params.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(20), APR_PKT_VER); + asm_params.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + payload_size); + asm_params.src_svc = q6asm_get_apr_service_id(session_id); + asm_params.src_domain = APR_DOMAIN_APPS; + asm_params.src_port = (session_id << 8) | 0x0001; + asm_params.dest_svc = APR_SVC_ASM; + asm_params.dest_domain = APR_DOMAIN_ADSP; + asm_params.dest_port = (session_id << 8) | 0x0001; + asm_params.token = session_id; + asm_params.opcode = opcode; + + memcpy(rtac_asm_buffer, &asm_params, sizeof(asm_params)); + atomic_set(&rtac_asm_apr_data[session_id].cmd_state, 1); + + pr_debug("%s: Sending RTAC command size = %d, session_id=%d\n", + __func__, asm_params.pkt_size, session_id); + + result = apr_send_pkt(rtac_asm_apr_data[session_id].apr_handle, + (uint32_t *)rtac_asm_buffer); + if (result < 0) { + pr_err("%s: Set params failed session = %d\n", + __func__, session_id); + goto err; + } + + /* Wait for the callback */ + result = wait_event_timeout(rtac_asm_apr_data[session_id].cmd_wait, + (atomic_read(&rtac_asm_apr_data[session_id].cmd_state) == 0), + 5 * HZ); + mutex_unlock(&rtac_asm_apr_mutex); + if (!result) { + pr_err("%s: Set params timed out session = %d\n", + __func__, session_id); + goto done; + } + + if (rtac_asm_payload_size != 0) { + if (copy_to_user(buf, rtac_asm_buffer, + rtac_asm_payload_size + sizeof(u32))) { + pr_err("%s: Could not copy buffer to user," + "size = %d\n", __func__, payload_size); + goto done; + } + } + + /* Return data written for SET & data read for GET */ + if (opcode == ASM_STREAM_CMD_GET_PP_PARAMS) + bytes_returned = rtac_asm_payload_size; + else + bytes_returned = payload_size; +done: + return bytes_returned; +err: + mutex_unlock(&rtac_asm_apr_mutex); + return bytes_returned; +} + + +/* Voice APR */ +void rtac_set_voice_handle(u32 mode, void *handle) +{ + pr_debug("%s\n", __func__); + + mutex_lock(&rtac_voice_apr_mutex); + rtac_voice_apr_data[mode].apr_handle = handle; + mutex_unlock(&rtac_voice_apr_mutex); +} + +bool rtac_make_voice_callback(u32 mode, uint32_t *payload, u32 payload_size) +{ + if ((atomic_read(&rtac_voice_apr_data[mode].cmd_state) != 1) || + (mode < 0) || (mode >= RTAC_VOICE_MODES)) + return false; + + pr_debug("%s\n", __func__); + /* Offset data for in-band payload */ + rtac_copy_voice_payload_to_user(payload, payload_size); + atomic_set(&rtac_voice_apr_data[mode].cmd_state, 0); + wake_up(&rtac_voice_apr_data[mode].cmd_wait); + return true; +} + +void rtac_copy_voice_payload_to_user(void *payload, u32 payload_size) +{ + pr_debug("%s\n", __func__); + rtac_voice_payload_size = payload_size; + + memcpy(rtac_voice_buffer, &payload_size, sizeof(u32)); + if (payload_size) { + if (payload_size > rtac_voice_user_buf_size) { + pr_err("%s: Buffer set not big enough for " + "returned data, buf size = %d, " + "ret data = %d\n", __func__, + rtac_voice_user_buf_size, payload_size); + goto done; + } + memcpy(rtac_voice_buffer + sizeof(u32), payload, payload_size); + } +done: + return; +} + +u32 send_voice_apr(u32 mode, void *buf, u32 opcode) +{ + s32 result; + u32 count = 0; + u32 bytes_returned = 0; + u32 payload_size; + u16 dest_port; + struct apr_hdr voice_params; + pr_debug("%s\n", __func__); + + if (copy_from_user(&count, (void *)buf, sizeof(count))) { + pr_err("%s: Copy to user failed! buf = 0x%x\n", + __func__, (unsigned int)buf); + result = -EFAULT; + goto done; + } + + if (count <= 0) { + pr_err("%s: Invalid buffer size = %d\n", __func__, count); + goto done; + } + + if (copy_from_user(&payload_size, buf + sizeof(u32), sizeof(u32))) { + pr_err("%s: Could not copy payload size from user buffer\n", + __func__); + goto done; + } + + if ((payload_size < 0) || + (payload_size > MAX_PAYLOAD_SIZE)) { + + pr_err("%s: Invalid payload size = %d\n", + __func__, payload_size); + goto done; + } + + if (copy_from_user(&dest_port, buf + 2 * sizeof(u32), sizeof(u32))) { + pr_err("%s: Could not copy port id from user buffer\n", + __func__); + goto done; + } + + if ((mode != RTAC_CVP) && (mode != RTAC_CVS)) { + pr_err("%s: Invalid Mode for APR, mode = %d\n", + __func__, mode); + goto done; + } + + mutex_lock(&rtac_voice_apr_mutex); + if (rtac_voice_apr_data[mode].apr_handle == NULL) { + pr_err("%s: APR not initialized\n", __func__); + goto err; + } + + /* Set globals for copy of returned payload */ + rtac_voice_user_buf_size = count; + + /* Copy buffer to in-band payload */ + if (copy_from_user(rtac_voice_buffer + sizeof(voice_params), + buf + 3 * sizeof(u32), payload_size)) { + pr_err("%s: Could not copy payload from user buffer\n", + __func__); + goto err; + } + + /* Pack header */ + voice_params.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(20), APR_PKT_VER); + voice_params.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + payload_size); + voice_params.src_svc = 0; + voice_params.src_domain = APR_DOMAIN_APPS; + voice_params.src_port = 0; + voice_params.dest_svc = 0; + voice_params.dest_domain = APR_DOMAIN_MODEM; + voice_params.dest_port = dest_port; + voice_params.token = 0; + voice_params.opcode = opcode; + + memcpy(rtac_voice_buffer, &voice_params, sizeof(voice_params)); + atomic_set(&rtac_voice_apr_data[mode].cmd_state, 1); + + pr_debug("%s: Sending RTAC command size = %d, opcode = %x\n", + __func__, voice_params.pkt_size, opcode); + + result = apr_send_pkt(rtac_voice_apr_data[mode].apr_handle, + (uint32_t *)rtac_voice_buffer); + if (result < 0) { + pr_err("%s: apr_send_pkt failed opcode = %x\n", + __func__, opcode); + goto err; + } + /* Wait for the callback */ + result = wait_event_timeout(rtac_voice_apr_data[mode].cmd_wait, + (atomic_read(&rtac_voice_apr_data[mode].cmd_state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + mutex_unlock(&rtac_voice_apr_mutex); + if (!result) { + pr_err("%s: apr_send_pkt timed out opcode = %x\n", + __func__, opcode); + goto done; + } + + if (rtac_voice_payload_size != 0) { + if (copy_to_user(buf, rtac_voice_buffer, + rtac_voice_payload_size + sizeof(u32))) { + pr_err("%s: Could not copy buffer to user," + "size = %d\n", __func__, payload_size); + goto done; + } + } + + /* Return data written for SET & data read for GET */ + if (opcode == VOICE_CMD_GET_PARAM) + bytes_returned = rtac_voice_payload_size; + else + bytes_returned = payload_size; +done: + return bytes_returned; +err: + mutex_unlock(&rtac_voice_apr_mutex); + return bytes_returned; +} + + + +static int rtac_ioctl(struct inode *inode, struct file *f, + unsigned int cmd, unsigned long arg) +{ + s32 result = 0; + pr_debug("%s\n", __func__); + + if (arg == 0) { + pr_err("%s: No data sent to driver!\n", __func__); + result = -EFAULT; + goto done; + } + + switch (cmd) { + case AUDIO_GET_RTAC_DEV_CTRL_INFO: + if (copy_to_user((void *)arg, &rtac_dev_ctl_data, + sizeof(rtac_dev_ctl_data))) + pr_err("%s: Could not copy to userspace!\n", __func__); + else + result = sizeof(rtac_dev_ctl_data); + break; + case AUDIO_GET_RTAC_ADM_INFO: + if (copy_to_user((void *)arg, &rtac_adm_data, + sizeof(rtac_adm_data))) + pr_err("%s: Could not copy to userspace!\n", __func__); + else + result = sizeof(rtac_adm_data); + break; + case AUDIO_GET_RTAC_VOICE_INFO: + if (copy_to_user((void *)arg, &rtac_voice_data, + sizeof(rtac_voice_data))) + pr_err("%s: Could not copy to userspace!\n", __func__); + else + result = sizeof(rtac_voice_data); + break; + case AUDIO_GET_RTAC_ADM_CAL: + result = send_adm_apr((void *)arg, ADM_CMD_GET_PARAMS); + break; + case AUDIO_SET_RTAC_ADM_CAL: + result = send_adm_apr((void *)arg, ADM_CMD_SET_PARAMS); + break; + case AUDIO_GET_RTAC_ASM_CAL: + result = send_rtac_asm_apr((void *)arg, + ASM_STREAM_CMD_GET_PP_PARAMS); + break; + case AUDIO_SET_RTAC_ASM_CAL: + result = send_rtac_asm_apr((void *)arg, + ASM_STREAM_CMD_SET_PP_PARAMS); + break; + case AUDIO_GET_RTAC_CVS_CAL: + result = send_voice_apr(RTAC_CVS, (void *)arg, + VOICE_CMD_GET_PARAM); + break; + case AUDIO_SET_RTAC_CVS_CAL: + result = send_voice_apr(RTAC_CVS, (void *)arg, + VOICE_CMD_SET_PARAM); + break; + case AUDIO_GET_RTAC_CVP_CAL: + result = send_voice_apr(RTAC_CVP, (void *)arg, + VOICE_CMD_GET_PARAM); + break; + case AUDIO_SET_RTAC_CVP_CAL: + result = send_voice_apr(RTAC_CVP, (void *)arg, + VOICE_CMD_SET_PARAM); + break; + default: + pr_err("%s: Invalid IOCTL, command = %d!\n", + __func__, cmd); + } +done: + return result; +} + + +static const struct file_operations rtac_fops = { + .owner = THIS_MODULE, + .open = rtac_open, + .release = rtac_release, + .ioctl = rtac_ioctl, +}; + +struct miscdevice rtac_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "msm_rtac", + .fops = &rtac_fops, +}; + +static int __init rtac_init(void) +{ + int i = 0; + pr_debug("%s\n", __func__); + + /* Dev ctrl */ + memset(&rtac_dev_ctl_data, 0, sizeof(rtac_dev_ctl_data)); + mutex_init(&rtac_dev_ctrl_mutex); + + /* ADM */ + memset(&rtac_adm_data, 0, sizeof(rtac_adm_data)); + rtac_adm_apr_data.apr_handle = NULL; + atomic_set(&rtac_adm_apr_data.cmd_state, 0); + init_waitqueue_head(&rtac_adm_apr_data.cmd_wait); + mutex_init(&rtac_adm_mutex); + mutex_init(&rtac_adm_apr_mutex); + + rtac_adm_buffer = kmalloc(RTAC_BUF_SIZE, GFP_KERNEL); + if (rtac_adm_buffer == NULL) { + pr_err("%s: Could not allocate payload of size = %d\n", + __func__, RTAC_BUF_SIZE); + goto nomem; + } + + /* ASM */ + for (i = 0; i < SESSION_MAX+1; i++) { + rtac_asm_apr_data[i].apr_handle = NULL; + atomic_set(&rtac_asm_apr_data[i].cmd_state, 0); + init_waitqueue_head(&rtac_asm_apr_data[i].cmd_wait); + } + mutex_init(&rtac_asm_apr_mutex); + + rtac_asm_buffer = kmalloc(RTAC_BUF_SIZE, GFP_KERNEL); + if (rtac_asm_buffer == NULL) { + pr_err("%s: Could not allocate payload of size = %d\n", + __func__, RTAC_BUF_SIZE); + goto nomem; + } + + /* Voice */ + memset(&rtac_voice_data, 0, sizeof(rtac_voice_data)); + for (i = 0; i < RTAC_VOICE_MODES; i++) { + rtac_voice_apr_data[i].apr_handle = NULL; + atomic_set(&rtac_voice_apr_data[i].cmd_state, 0); + init_waitqueue_head(&rtac_voice_apr_data[i].cmd_wait); + } + mutex_init(&rtac_voice_mutex); + mutex_init(&rtac_voice_apr_mutex); + + rtac_voice_buffer = kmalloc(RTAC_BUF_SIZE, GFP_KERNEL); + if (rtac_voice_buffer == NULL) { + pr_err("%s: Could not allocate payload of size = %d\n", + __func__, RTAC_BUF_SIZE); + goto nomem; + } + + return misc_register(&rtac_misc); +nomem: + return -ENOMEM; +} + +module_init(rtac_init); + +MODULE_DESCRIPTION("MSM 8x60 Real-Time Audio Calibration driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_ecodec.c b/arch/arm/mach-msm/qdsp6v2/snddev_ecodec.c new file mode 100644 index 00000000000..eb394a30ab3 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_ecodec.c @@ -0,0 +1,382 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "snddev_ecodec.h" + +#define ECODEC_SAMPLE_RATE 8000 + +/* Context for each external codec device */ +struct snddev_ecodec_state { + struct snddev_ecodec_data *data; + u32 sample_rate; +}; + +/* Global state for the driver */ +struct snddev_ecodec_drv_state { + struct mutex dev_lock; + int ref_cnt; /* ensure one rx device at a time */ + struct clk *ecodec_clk; +}; + +static struct snddev_ecodec_drv_state snddev_ecodec_drv; + +struct aux_pcm_state { + unsigned int dout; + unsigned int din; + unsigned int syncout; + unsigned int clkin_a; +}; + +static struct aux_pcm_state the_aux_pcm_state; + +static int aux_pcm_gpios_request(void) +{ + int rc = 0; + + pr_debug("%s\n", __func__); + rc = gpio_request(the_aux_pcm_state.dout, "AUX PCM DOUT"); + if (rc < 0) { + pr_err("%s: GPIO request for AUX PCM DOUT failed\n", __func__); + return rc; + } + + rc = gpio_request(the_aux_pcm_state.din, "AUX PCM DIN"); + if (rc < 0) { + pr_err("%s: GPIO request for AUX PCM DIN failed\n", __func__); + gpio_free(the_aux_pcm_state.dout); + return rc; + } + + rc = gpio_request(the_aux_pcm_state.syncout, "AUX PCM SYNC OUT"); + if (rc < 0) { + pr_err("%s: GPIO request for AUX PCM SYNC OUT failed\n", + __func__); + gpio_free(the_aux_pcm_state.dout); + gpio_free(the_aux_pcm_state.din); + return rc; + } + + rc = gpio_request(the_aux_pcm_state.clkin_a, "AUX PCM CLKIN A"); + if (rc < 0) { + pr_err("%s: GPIO request for AUX PCM CLKIN A failed\n", + __func__); + gpio_free(the_aux_pcm_state.dout); + gpio_free(the_aux_pcm_state.din); + gpio_free(the_aux_pcm_state.syncout); + return rc; + } + + return rc; +} + +static void aux_pcm_gpios_free(void) +{ + pr_debug("%s\n", __func__); + gpio_free(the_aux_pcm_state.dout); + gpio_free(the_aux_pcm_state.din); + gpio_free(the_aux_pcm_state.syncout); + gpio_free(the_aux_pcm_state.clkin_a); +} + +static int get_aux_pcm_gpios(struct platform_device *pdev) +{ + int rc = 0; + struct resource *res; + + /* Claim all of the GPIOs. */ + res = platform_get_resource_byname(pdev, IORESOURCE_IO, "aux_pcm_dout"); + if (!res) { + pr_err("%s: failed to get gpio AUX PCM DOUT\n", __func__); + return -ENODEV; + } + + the_aux_pcm_state.dout = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, "aux_pcm_din"); + if (!res) { + pr_err("%s: failed to get gpio AUX PCM DIN\n", __func__); + return -ENODEV; + } + + the_aux_pcm_state.din = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "aux_pcm_syncout"); + if (!res) { + pr_err("%s: failed to get gpio AUX PCM SYNC OUT\n", __func__); + return -ENODEV; + } + + the_aux_pcm_state.syncout = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "aux_pcm_clkin_a"); + if (!res) { + pr_err("%s: failed to get gpio AUX PCM CLKIN A\n", __func__); + return -ENODEV; + } + + the_aux_pcm_state.clkin_a = res->start; + + return rc; +} + +static int aux_pcm_probe(struct platform_device *pdev) +{ + int rc = 0; + + rc = get_aux_pcm_gpios(pdev); + if (rc < 0) { + pr_err("%s: GPIO configuration failed\n", __func__); + return -ENODEV; + } + return rc; +} + +static struct platform_driver aux_pcm_driver = { + .probe = aux_pcm_probe, + .driver = { .name = "msm_aux_pcm"} +}; + +static int snddev_ecodec_open(struct msm_snddev_info *dev_info) +{ + int rc; + struct snddev_ecodec_drv_state *drv = &snddev_ecodec_drv; + union afe_port_config afe_config; + + pr_debug("%s\n", __func__); + + mutex_lock(&drv->dev_lock); + + if (dev_info->opened) { + pr_err("%s: ERROR: %s already opened\n", __func__, + dev_info->name); + mutex_unlock(&drv->dev_lock); + return -EBUSY; + } + + if (drv->ref_cnt != 0) { + pr_debug("%s: opened %s\n", __func__, dev_info->name); + drv->ref_cnt++; + mutex_unlock(&drv->dev_lock); + return 0; + } + + pr_info("%s: opening %s\n", __func__, dev_info->name); + + rc = aux_pcm_gpios_request(); + if (rc < 0) { + pr_err("%s: GPIO request failed\n", __func__); + return rc; + } + + clk_reset(drv->ecodec_clk, CLK_RESET_ASSERT); + + afe_config.pcm.mode = AFE_PCM_CFG_MODE_PCM; + afe_config.pcm.sync = AFE_PCM_CFG_SYNC_INT; + afe_config.pcm.frame = AFE_PCM_CFG_FRM_256BPF; + afe_config.pcm.quant = AFE_PCM_CFG_QUANT_LINEAR_NOPAD; + afe_config.pcm.slot = 0; + afe_config.pcm.data = AFE_PCM_CFG_CDATAOE_MASTER; + + rc = afe_open(PCM_RX, &afe_config, ECODEC_SAMPLE_RATE); + if (rc < 0) { + pr_err("%s: afe open failed for PCM_RX\n", __func__); + goto err_rx_afe; + } + + rc = afe_open(PCM_TX, &afe_config, ECODEC_SAMPLE_RATE); + if (rc < 0) { + pr_err("%s: afe open failed for PCM_TX\n", __func__); + goto err_tx_afe; + } + + rc = clk_set_rate(drv->ecodec_clk, 2048000); + if (rc < 0) { + pr_err("%s: clk_set_rate failed\n", __func__); + goto err_clk; + } + + clk_enable(drv->ecodec_clk); + + clk_reset(drv->ecodec_clk, CLK_RESET_DEASSERT); + + drv->ref_cnt++; + mutex_unlock(&drv->dev_lock); + + return 0; + +err_clk: + afe_close(PCM_TX); +err_tx_afe: + afe_close(PCM_RX); +err_rx_afe: + aux_pcm_gpios_free(); + mutex_unlock(&drv->dev_lock); + return -ENODEV; +} + +int snddev_ecodec_close(struct msm_snddev_info *dev_info) +{ + struct snddev_ecodec_drv_state *drv = &snddev_ecodec_drv; + + pr_debug("%s: closing %s\n", __func__, dev_info->name); + + mutex_lock(&drv->dev_lock); + + if (!dev_info->opened) { + pr_err("%s: ERROR: %s is not opened\n", __func__, + dev_info->name); + mutex_unlock(&drv->dev_lock); + return -EPERM; + } + + drv->ref_cnt--; + + if (drv->ref_cnt == 0) { + + pr_info("%s: closing all devices\n", __func__); + + clk_disable(drv->ecodec_clk); + aux_pcm_gpios_free(); + + afe_close(PCM_RX); + afe_close(PCM_TX); + } + + mutex_unlock(&drv->dev_lock); + + return 0; +} + +int snddev_ecodec_set_freq(struct msm_snddev_info *dev_info, u32 rate) +{ + int rc = 0; + + if (!dev_info) { + rc = -EINVAL; + goto error; + } + return ECODEC_SAMPLE_RATE; + +error: + return rc; +} + +static int snddev_ecodec_probe(struct platform_device *pdev) +{ + int rc = 0; + struct snddev_ecodec_data *pdata; + struct msm_snddev_info *dev_info; + struct snddev_ecodec_state *ecodec; + + if (!pdev || !pdev->dev.platform_data) { + printk(KERN_ALERT "Invalid caller\n"); + rc = -1; + goto error; + } + pdata = pdev->dev.platform_data; + + ecodec = kzalloc(sizeof(struct snddev_ecodec_state), GFP_KERNEL); + if (!ecodec) { + rc = -ENOMEM; + goto error; + } + + dev_info = kzalloc(sizeof(struct msm_snddev_info), GFP_KERNEL); + if (!dev_info) { + kfree(ecodec); + rc = -ENOMEM; + goto error; + } + + dev_info->name = pdata->name; + dev_info->copp_id = pdata->copp_id; + dev_info->private_data = (void *)ecodec; + dev_info->dev_ops.open = snddev_ecodec_open; + dev_info->dev_ops.close = snddev_ecodec_close; + dev_info->dev_ops.set_freq = snddev_ecodec_set_freq; + dev_info->dev_ops.enable_sidetone = NULL; + dev_info->capability = pdata->capability; + dev_info->opened = 0; + + msm_snddev_register(dev_info); + + ecodec->data = pdata; + ecodec->sample_rate = ECODEC_SAMPLE_RATE; /* Default to 8KHz */ +error: + return rc; +} + +struct platform_driver snddev_ecodec_driver = { + .probe = snddev_ecodec_probe, + .driver = {.name = "msm_snddev_ecodec"} +}; + +int __init snddev_ecodec_init(void) +{ + int rc = 0; + struct snddev_ecodec_drv_state *drv = &snddev_ecodec_drv; + + mutex_init(&drv->dev_lock); + drv->ref_cnt = 0; + + drv->ecodec_clk = clk_get(NULL, "pcm_clk"); + if (IS_ERR(drv->ecodec_clk)) { + pr_err("%s: could not get pcm_clk\n", __func__); + return PTR_ERR(drv->ecodec_clk); + } + + rc = platform_driver_register(&aux_pcm_driver); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: platform_driver_register for aux pcm failed\n", + __func__); + goto error_aux_pcm_platform_driver; + } + + rc = platform_driver_register(&snddev_ecodec_driver); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: platform_driver_register for ecodec failed\n", + __func__); + goto error_ecodec_platform_driver; + } + + return 0; + +error_ecodec_platform_driver: + platform_driver_unregister(&aux_pcm_driver); +error_aux_pcm_platform_driver: + clk_put(drv->ecodec_clk); + + pr_err("%s: encounter error\n", __func__); + return -ENODEV; +} + +device_initcall(snddev_ecodec_init); + +MODULE_DESCRIPTION("ECodec Sound Device driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_ecodec.h b/arch/arm/mach-msm/qdsp6v2/snddev_ecodec.h new file mode 100644 index 00000000000..b102de0ba5f --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_ecodec.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP6V2_SNDDEV_ECODEC_H +#define __MACH_QDSP6V2_SNDDEV_ECODEC_H +#include + +struct snddev_ecodec_data { + u32 capability; /* RX or TX */ + const char *name; + u32 copp_id; /* audpp routing */ + u8 channel_mode; + u32 conf_pcm_ctl_val; + u32 conf_aux_codec_intf; + u32 conf_data_format_padding_val; +}; +#endif diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_hdmi.c b/arch/arm/mach-msm/qdsp6v2/snddev_hdmi.c new file mode 100644 index 00000000000..4eeb654d4cb --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_hdmi.c @@ -0,0 +1,198 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "snddev_hdmi.h" + +static DEFINE_MUTEX(snddev_hdmi_lock); +static int snddev_hdmi_active; + +static int snddev_hdmi_open(struct msm_snddev_info *dev_info) +{ + int rc = 0; + union afe_port_config afe_config; + struct snddev_hdmi_data *snddev_hdmi_data; + + if (!dev_info) { + pr_err("msm_snddev_info is null\n"); + return -EINVAL; + } + + snddev_hdmi_data = dev_info->private_data; + + mutex_lock(&snddev_hdmi_lock); + + if (snddev_hdmi_active) { + pr_err("HDMI snddev already active\n"); + mutex_unlock(&snddev_hdmi_lock); + return -EBUSY; + } + + if (snddev_hdmi_data->on_apps) { + snddev_hdmi_active = 1; + pr_debug("%s open done\n", dev_info->name); + mutex_unlock(&snddev_hdmi_lock); + return 0; + } + + afe_config.hdmi.channel_mode = snddev_hdmi_data->channel_mode; + afe_config.hdmi.bitwidth = 16; + afe_config.hdmi.data_type = 0; + rc = afe_open(snddev_hdmi_data->copp_id, &afe_config, + dev_info->sample_rate); + + if (rc < 0) { + pr_err("afe_open failed\n"); + mutex_unlock(&snddev_hdmi_lock); + return -EINVAL; + } + snddev_hdmi_active = 1; + + pr_debug("%s open done\n", dev_info->name); + + mutex_unlock(&snddev_hdmi_lock); + + return 0; +} + +static int snddev_hdmi_close(struct msm_snddev_info *dev_info) +{ + + struct snddev_hdmi_data *snddev_hdmi_data; + + if (!dev_info) { + pr_err("msm_snddev_info is null\n"); + return -EINVAL; + } + + snddev_hdmi_data = dev_info->private_data; + + if (!dev_info->opened) { + pr_err("calling close device with out opening the" + " device\n"); + return -EPERM; + } + mutex_lock(&snddev_hdmi_lock); + + if (!snddev_hdmi_active) { + pr_err("HDMI snddev not active\n"); + mutex_unlock(&snddev_hdmi_lock); + return -EPERM; + } + snddev_hdmi_active = 0; + + if (snddev_hdmi_data->on_apps) { + pr_debug("%s open done\n", dev_info->name); + + mutex_unlock(&snddev_hdmi_lock); + return 0; + } + + + afe_close(HDMI_RX); + + pr_debug("%s closed\n", dev_info->name); + mutex_unlock(&snddev_hdmi_lock); + + return 0; +} + +static int snddev_hdmi_set_freq(struct msm_snddev_info *dev_info, u32 req_freq) +{ + if (req_freq != 48000) { + pr_debug("Unsupported Frequency:%d\n", req_freq); + return -EINVAL; + } + return 48000; +} + +static int snddev_hdmi_probe(struct platform_device *pdev) +{ + int rc = 0; + struct snddev_hdmi_data *pdata; + struct msm_snddev_info *dev_info; + + if (!pdev || !pdev->dev.platform_data) { + printk(KERN_ALERT "Invalid caller\n"); + return -ENODEV; + } + + pdata = pdev->dev.platform_data; + if (!(pdata->capability & SNDDEV_CAP_RX)) { + pr_err("invalid device data either RX or TX\n"); + return -ENODEV; + } + + dev_info = kzalloc(sizeof(struct msm_snddev_info), GFP_KERNEL); + if (!dev_info) { + pr_err("unable to allocate memeory for msm_snddev_info\n"); + return -ENOMEM; + } + + dev_info->name = pdata->name; + dev_info->copp_id = pdata->copp_id; + dev_info->acdb_id = pdata->acdb_id; + dev_info->private_data = (void *)pdata; + dev_info->dev_ops.open = snddev_hdmi_open; + dev_info->dev_ops.close = snddev_hdmi_close; + dev_info->dev_ops.set_freq = snddev_hdmi_set_freq; + dev_info->capability = pdata->capability; + dev_info->opened = 0; + msm_snddev_register(dev_info); + dev_info->sample_rate = pdata->default_sample_rate; + + pr_debug("probe done for %s\n", pdata->name); + return rc; +} + +static struct platform_driver snddev_hdmi_driver = { + .probe = snddev_hdmi_probe, + .driver = {.name = "snddev_hdmi"} +}; + +static int __init snddev_hdmi_init(void) +{ + s32 rc; + + rc = platform_driver_register(&snddev_hdmi_driver); + if (IS_ERR_VALUE(rc)) { + + pr_err("platform_driver_register failed.\n"); + goto error_platform_driver; + } + + pr_debug("snddev_hdmi_init : done\n"); + + return 0; + +error_platform_driver: + + pr_err("encounterd error\n"); + return -ENODEV; +} + +module_init(snddev_hdmi_init); + +MODULE_DESCRIPTION("HDMI Sound Device driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_hdmi.h b/arch/arm/mach-msm/qdsp6v2/snddev_hdmi.h new file mode 100644 index 00000000000..cc69033f08b --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_hdmi.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP6_V2_SNDDEV_HDMI_H +#define __MACH_QDSP6_V2_SNDDEV_HDMI_H + +struct snddev_hdmi_data { + u32 capability; /* RX or TX */ + const char *name; + u32 copp_id; /* audpp routing */ + u32 acdb_id; /* Audio Cal purpose */ + u8 channel_mode; + u32 default_sample_rate; + u32 on_apps; +}; +#endif diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_icodec.c b/arch/arm/mach-msm/qdsp6v2/snddev_icodec.c new file mode 100644 index 00000000000..0abc9ff1eac --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_icodec.c @@ -0,0 +1,1087 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "snddev_icodec.h" + +#define SNDDEV_ICODEC_PCM_SZ 32 /* 16 bit / sample stereo mode */ +#define SNDDEV_ICODEC_MUL_FACTOR 3 /* Multi by 8 Shift by 3 */ +#define SNDDEV_ICODEC_CLK_RATE(freq) \ + (((freq) * (SNDDEV_ICODEC_PCM_SZ)) << (SNDDEV_ICODEC_MUL_FACTOR)) +#define SNDDEV_LOW_POWER_MODE 0 +#define SNDDEV_HIGH_POWER_MODE 1 +/* Voltage required for S4 in microVolts, 2.2V or 2200000microvolts */ +#define SNDDEV_VREG_8058_S4_VOLTAGE (2200000) +/* Load Current required for S4 in microAmps, + 36mA - 56mA */ +#define SNDDEV_VREG_LOW_POWER_LOAD (36000) +#define SNDDEV_VREG_HIGH_POWER_LOAD (56000) + +int msm_codec_i2s_slave_mode; + +/* Context for each internal codec sound device */ +struct snddev_icodec_state { + struct snddev_icodec_data *data; + struct adie_codec_path *adie_path; + u32 sample_rate; + u32 enabled; +}; + +/* Global state for the driver */ +struct snddev_icodec_drv_state { + struct mutex rx_lock; + struct mutex lb_lock; + struct mutex tx_lock; + u32 rx_active; /* ensure one rx device at a time */ + u32 tx_active; /* ensure one tx device at a time */ + struct clk *rx_osrclk; + struct clk *rx_bitclk; + struct clk *tx_osrclk; + struct clk *tx_bitclk; + + struct wake_lock rx_idlelock; + struct wake_lock tx_idlelock; + + /* handle to pmic8058 regulator smps4 */ + struct regulator *snddev_vreg; +}; + +static struct snddev_icodec_drv_state snddev_icodec_drv; + +struct regulator *vreg_init(void) +{ + int rc; + struct regulator *vreg_ptr; + + vreg_ptr = regulator_get(NULL, "8058_s4"); + if (IS_ERR(vreg_ptr)) { + pr_err("%s: regulator_get 8058_s4 failed\n", __func__); + return NULL; + } + + rc = regulator_set_voltage(vreg_ptr, SNDDEV_VREG_8058_S4_VOLTAGE, + SNDDEV_VREG_8058_S4_VOLTAGE); + if (rc == 0) + return vreg_ptr; + else + return NULL; +} + +static void vreg_deinit(struct regulator *vreg) +{ + regulator_put(vreg); +} + +static void vreg_mode_vote(struct regulator *vreg, int enable, int mode) +{ + int rc; + if (enable) { + rc = regulator_enable(vreg); + if (rc != 0) + pr_err("%s:Enabling regulator failed\n", __func__); + else { + if (mode) + regulator_set_optimum_mode(vreg, + SNDDEV_VREG_HIGH_POWER_LOAD); + else + regulator_set_optimum_mode(vreg, + SNDDEV_VREG_LOW_POWER_LOAD); + } + } else { + rc = regulator_disable(vreg); + if (rc != 0) + pr_err("%s:Disabling regulator failed\n", __func__); + } +} + +struct msm_cdcclk_ctl_state { + unsigned int rx_mclk; + unsigned int rx_mclk_requested; + unsigned int tx_mclk; + unsigned int tx_mclk_requested; +}; + +static struct msm_cdcclk_ctl_state the_msm_cdcclk_ctl_state; + +static int msm_snddev_rx_mclk_request(void) +{ + int rc = 0; + + rc = gpio_request(the_msm_cdcclk_ctl_state.rx_mclk, + "MSM_SNDDEV_RX_MCLK"); + if (rc < 0) { + pr_err("%s: GPIO request for MSM SNDDEV RX failed\n", __func__); + return rc; + } + the_msm_cdcclk_ctl_state.rx_mclk_requested = 1; + return rc; +} +static int msm_snddev_tx_mclk_request(void) +{ + int rc = 0; + + rc = gpio_request(the_msm_cdcclk_ctl_state.tx_mclk, + "MSM_SNDDEV_TX_MCLK"); + if (rc < 0) { + pr_err("%s: GPIO request for MSM SNDDEV TX failed\n", __func__); + return rc; + } + the_msm_cdcclk_ctl_state.tx_mclk_requested = 1; + return rc; +} +static void msm_snddev_rx_mclk_free(void) +{ + if (the_msm_cdcclk_ctl_state.rx_mclk_requested) { + gpio_free(the_msm_cdcclk_ctl_state.rx_mclk); + the_msm_cdcclk_ctl_state.rx_mclk_requested = 0; + } +} +static void msm_snddev_tx_mclk_free(void) +{ + if (the_msm_cdcclk_ctl_state.tx_mclk_requested) { + gpio_free(the_msm_cdcclk_ctl_state.tx_mclk); + the_msm_cdcclk_ctl_state.tx_mclk_requested = 0; + } +} +static int get_msm_cdcclk_ctl_gpios(struct platform_device *pdev) +{ + int rc = 0; + struct resource *res; + + /* Claim all of the GPIOs. */ + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "msm_snddev_rx_mclk"); + if (!res) { + pr_err("%s: failed to get gpio MSM SNDDEV RX\n", __func__); + return -ENODEV; + } + the_msm_cdcclk_ctl_state.rx_mclk = res->start; + the_msm_cdcclk_ctl_state.rx_mclk_requested = 0; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "msm_snddev_tx_mclk"); + if (!res) { + pr_err("%s: failed to get gpio MSM SNDDEV TX\n", __func__); + return -ENODEV; + } + the_msm_cdcclk_ctl_state.tx_mclk = res->start; + the_msm_cdcclk_ctl_state.tx_mclk_requested = 0; + + return rc; +} +static int msm_cdcclk_ctl_probe(struct platform_device *pdev) +{ + int rc = 0; + + rc = get_msm_cdcclk_ctl_gpios(pdev); + if (rc < 0) { + pr_err("%s: GPIO configuration failed\n", __func__); + return -ENODEV; + } + return rc; +} +static struct platform_driver msm_cdcclk_ctl_driver = { + .probe = msm_cdcclk_ctl_probe, + .driver = { .name = "msm_cdcclk_ctl"} +}; + +static int snddev_icodec_open_lb(struct snddev_icodec_state *icodec) +{ + int trc; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + /* Voting for low power is ok here as all use cases are + * supported in low power mode. + */ + if (drv->snddev_vreg) + vreg_mode_vote(drv->snddev_vreg, 1, + SNDDEV_LOW_POWER_MODE); + + if (icodec->data->voltage_on) + icodec->data->voltage_on(); + + trc = adie_codec_open(icodec->data->profile, &icodec->adie_path); + if (IS_ERR_VALUE(trc)) + pr_err("%s: adie codec open failed\n", __func__); + else + adie_codec_setpath(icodec->adie_path, + icodec->sample_rate, 256); + + if (icodec->adie_path) + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_ANALOG_READY); + + if (icodec->data->pamp_on) + icodec->data->pamp_on(); + + icodec->enabled = 1; + + return 0; +} +static int initialize_msm_icodec_gpios(struct platform_device *pdev) +{ + int rc = 0; + struct resource *res; + int i = 0; + int *reg_defaults = pdev->dev.platform_data; + + while ((res = platform_get_resource(pdev, IORESOURCE_IO, i))) { + rc = gpio_request(res->start, res->name); + if (rc) { + pr_err("%s: icodec gpio %d request failed\n", __func__, + res->start); + goto err; + } else { + /* This platform data structure only works if all gpio + * resources are to be used only in output mode. + * If gpio resources are added which are to be used in + * input mode, then the platform data structure will + * have to be changed. + */ + + gpio_direction_output(res->start, reg_defaults[i]); + gpio_free(res->start); + } + i++; + } +err: + return rc; +} +static int msm_icodec_gpio_probe(struct platform_device *pdev) +{ + int rc = 0; + + rc = initialize_msm_icodec_gpios(pdev); + if (rc < 0) { + pr_err("%s: GPIO configuration failed\n", __func__); + return -ENODEV; + } + return rc; +} +static struct platform_driver msm_icodec_gpio_driver = { + .probe = msm_icodec_gpio_probe, + .driver = { .name = "msm_icodec_gpio"} +}; + +static int snddev_icodec_open_rx(struct snddev_icodec_state *icodec) +{ + int trc; + int afe_channel_mode; + union afe_port_config afe_config; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + wake_lock(&drv->rx_idlelock); + + if (drv->snddev_vreg) { + if (!strcmp(icodec->data->name, "headset_stereo_rx")) + vreg_mode_vote(drv->snddev_vreg, 1, + SNDDEV_LOW_POWER_MODE); + else + vreg_mode_vote(drv->snddev_vreg, 1, + SNDDEV_HIGH_POWER_MODE); + } + msm_snddev_rx_mclk_request(); + + drv->rx_osrclk = clk_get(0, "i2s_spkr_osr_clk"); + if (IS_ERR(drv->rx_osrclk)) + pr_err("%s master clock Error\n", __func__); + + trc = clk_set_rate(drv->rx_osrclk, + SNDDEV_ICODEC_CLK_RATE(icodec->sample_rate)); + if (IS_ERR_VALUE(trc)) { + pr_err("ERROR setting m clock1\n"); + goto error_invalid_freq; + } + + clk_enable(drv->rx_osrclk); + drv->rx_bitclk = clk_get(0, "i2s_spkr_bit_clk"); + if (IS_ERR(drv->rx_bitclk)) + pr_err("%s clock Error\n", __func__); + + /* Master clock = Sample Rate * OSR rate bit clock + * OSR Rate bit clock = bit/sample * channel master + * clock / bit clock = divider value = 8 + */ + if (msm_codec_i2s_slave_mode) { + pr_info("%s: configuring bit clock for slave mode\n", + __func__); + trc = clk_set_rate(drv->rx_bitclk, 0); + } else + trc = clk_set_rate(drv->rx_bitclk, 8); + + if (IS_ERR_VALUE(trc)) { + pr_err("ERROR setting m clock1\n"); + goto error_adie; + } + clk_enable(drv->rx_bitclk); + + if (icodec->data->voltage_on) + icodec->data->voltage_on(); + + /* Configure ADIE */ + trc = adie_codec_open(icodec->data->profile, &icodec->adie_path); + if (IS_ERR_VALUE(trc)) + pr_err("%s: adie codec open failed\n", __func__); + else + adie_codec_setpath(icodec->adie_path, + icodec->sample_rate, 256); + /* OSR default to 256, can be changed for power optimization + * If OSR is to be changed, need clock API for setting the divider + */ + + switch (icodec->data->channel_mode) { + case 2: + afe_channel_mode = MSM_AFE_STEREO; + break; + case 1: + default: + afe_channel_mode = MSM_AFE_MONO; + break; + } + afe_config.mi2s.channel = afe_channel_mode; + afe_config.mi2s.bitwidth = 16; + afe_config.mi2s.line = 1; + if (msm_codec_i2s_slave_mode) + afe_config.mi2s.ws = 0; + else + afe_config.mi2s.ws = 1; + + trc = afe_open(icodec->data->copp_id, &afe_config, icodec->sample_rate); + + /* Enable ADIE */ + if (icodec->adie_path) { + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_READY); + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_ANALOG_READY); + } + + if (msm_codec_i2s_slave_mode) + adie_codec_set_master_mode(icodec->adie_path, 1); + else + adie_codec_set_master_mode(icodec->adie_path, 0); + + /* Enable power amplifier */ + if (icodec->data->pamp_on) { + if (icodec->data->pamp_on()) { + pr_err("%s: Error turning on rx power\n", __func__); + goto error_pamp; + } + } + + icodec->enabled = 1; + + wake_unlock(&drv->rx_idlelock); + return 0; + +error_pamp: +error_adie: + clk_disable(drv->rx_osrclk); +error_invalid_freq: + + pr_err("%s: encounter error\n", __func__); + + wake_unlock(&drv->rx_idlelock); + return -ENODEV; +} + +static int snddev_icodec_open_tx(struct snddev_icodec_state *icodec) +{ + int trc; + int afe_channel_mode; + union afe_port_config afe_config; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv;; + + wake_lock(&drv->tx_idlelock); + + if (drv->snddev_vreg) + vreg_mode_vote(drv->snddev_vreg, 1, SNDDEV_HIGH_POWER_MODE); + + /* Reuse pamp_on for TX platform-specific setup */ + if (icodec->data->pamp_on) { + if (icodec->data->pamp_on()) { + pr_err("%s: Error turning on tx power\n", __func__); + goto error_pamp; + } + } + + msm_snddev_tx_mclk_request(); + + drv->tx_osrclk = clk_get(0, "i2s_mic_osr_clk"); + if (IS_ERR(drv->tx_osrclk)) + pr_err("%s master clock Error\n", __func__); + + trc = clk_set_rate(drv->tx_osrclk, + SNDDEV_ICODEC_CLK_RATE(icodec->sample_rate)); + if (IS_ERR_VALUE(trc)) { + pr_err("ERROR setting m clock1\n"); + goto error_invalid_freq; + } + + clk_enable(drv->tx_osrclk); + drv->tx_bitclk = clk_get(0, "i2s_mic_bit_clk"); + if (IS_ERR(drv->tx_bitclk)) + pr_err("%s clock Error\n", __func__); + + /* Master clock = Sample Rate * OSR rate bit clock + * OSR Rate bit clock = bit/sample * channel master + * clock / bit clock = divider value = 8 + */ + if (msm_codec_i2s_slave_mode) { + pr_info("%s: configuring bit clock for slave mode\n", + __func__); + trc = clk_set_rate(drv->tx_bitclk, 0); + } else + trc = clk_set_rate(drv->tx_bitclk, 8); + + clk_enable(drv->tx_bitclk); + + /* Enable ADIE */ + trc = adie_codec_open(icodec->data->profile, &icodec->adie_path); + if (IS_ERR_VALUE(trc)) + pr_err("%s: adie codec open failed\n", __func__); + else + adie_codec_setpath(icodec->adie_path, + icodec->sample_rate, 256); + + switch (icodec->data->channel_mode) { + case 2: + afe_channel_mode = MSM_AFE_STEREO; + break; + case 1: + default: + afe_channel_mode = MSM_AFE_MONO; + break; + } + afe_config.mi2s.channel = afe_channel_mode; + afe_config.mi2s.bitwidth = 16; + afe_config.mi2s.line = 1; + if (msm_codec_i2s_slave_mode) + afe_config.mi2s.ws = 0; + else + afe_config.mi2s.ws = 1; + + trc = afe_open(icodec->data->copp_id, &afe_config, icodec->sample_rate); + + if (icodec->adie_path) { + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_READY); + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_ANALOG_READY); + } + + if (msm_codec_i2s_slave_mode) + adie_codec_set_master_mode(icodec->adie_path, 1); + else + adie_codec_set_master_mode(icodec->adie_path, 0); + + icodec->enabled = 1; + + wake_unlock(&drv->tx_idlelock); + return 0; + +error_invalid_freq: + + if (icodec->data->pamp_off) + icodec->data->pamp_off(); + + pr_err("%s: encounter error\n", __func__); +error_pamp: + wake_unlock(&drv->tx_idlelock); + return -ENODEV; +} + +static int snddev_icodec_close_lb(struct snddev_icodec_state *icodec) +{ + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + /* Disable power amplifier */ + if (icodec->data->pamp_off) + icodec->data->pamp_off(); + + if (drv->snddev_vreg) + vreg_mode_vote(drv->snddev_vreg, 0, SNDDEV_LOW_POWER_MODE); + + if (icodec->adie_path) { + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(icodec->adie_path); + icodec->adie_path = NULL; + } + + if (icodec->data->voltage_off) + icodec->data->voltage_off(); + + return 0; +} + +static int snddev_icodec_close_rx(struct snddev_icodec_state *icodec) +{ + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + wake_lock(&drv->rx_idlelock); + + if (drv->snddev_vreg) + vreg_mode_vote(drv->snddev_vreg, 0, SNDDEV_HIGH_POWER_MODE); + + /* Disable power amplifier */ + if (icodec->data->pamp_off) + icodec->data->pamp_off(); + + /* Disable ADIE */ + if (icodec->adie_path) { + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(icodec->adie_path); + icodec->adie_path = NULL; + } + + afe_close(icodec->data->copp_id); + + if (icodec->data->voltage_off) + icodec->data->voltage_off(); + + clk_disable(drv->rx_bitclk); + clk_disable(drv->rx_osrclk); + + msm_snddev_rx_mclk_free(); + + icodec->enabled = 0; + + wake_unlock(&drv->rx_idlelock); + return 0; +} + +static int snddev_icodec_close_tx(struct snddev_icodec_state *icodec) +{ + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + wake_lock(&drv->tx_idlelock); + + if (drv->snddev_vreg) + vreg_mode_vote(drv->snddev_vreg, 0, SNDDEV_HIGH_POWER_MODE); + + /* Disable ADIE */ + if (icodec->adie_path) { + adie_codec_proceed_stage(icodec->adie_path, + ADIE_CODEC_DIGITAL_OFF); + adie_codec_close(icodec->adie_path); + icodec->adie_path = NULL; + } + + afe_close(icodec->data->copp_id); + + clk_disable(drv->tx_bitclk); + clk_disable(drv->tx_osrclk); + + msm_snddev_tx_mclk_free(); + + /* Reuse pamp_off for TX platform-specific setup */ + if (icodec->data->pamp_off) + icodec->data->pamp_off(); + + icodec->enabled = 0; + + wake_unlock(&drv->tx_idlelock); + return 0; +} + +static int snddev_icodec_set_device_volume_impl( + struct msm_snddev_info *dev_info, u32 volume) +{ + struct snddev_icodec_state *icodec; + + int rc = 0; + + icodec = dev_info->private_data; + + if (icodec->data->dev_vol_type & SNDDEV_DEV_VOL_DIGITAL) { + + rc = adie_codec_set_device_digital_volume(icodec->adie_path, + icodec->data->channel_mode, volume); + if (rc < 0) { + pr_err("%s: unable to set_device_digital_volume for" + "%s volume in percentage = %u\n", + __func__, dev_info->name, volume); + return rc; + } + + } else if (icodec->data->dev_vol_type & SNDDEV_DEV_VOL_ANALOG) { + rc = adie_codec_set_device_analog_volume(icodec->adie_path, + icodec->data->channel_mode, volume); + if (rc < 0) { + pr_err("%s: unable to set_device_analog_volume for" + "%s volume in percentage = %u\n", + __func__, dev_info->name, volume); + return rc; + } + } else { + pr_err("%s: Invalid device volume control\n", __func__); + return -EPERM; + } + return rc; +} + +static int snddev_icodec_open(struct msm_snddev_info *dev_info) +{ + int rc = 0; + struct snddev_icodec_state *icodec; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + if (!dev_info) { + rc = -EINVAL; + goto error; + } + + icodec = dev_info->private_data; + + if (icodec->data->capability & SNDDEV_CAP_RX) { + mutex_lock(&drv->rx_lock); + if (drv->rx_active) { + mutex_unlock(&drv->rx_lock); + rc = -EBUSY; + goto error; + } + rc = snddev_icodec_open_rx(icodec); + + if (!IS_ERR_VALUE(rc)) { + drv->rx_active = 1; + if ((icodec->data->dev_vol_type & ( + SNDDEV_DEV_VOL_DIGITAL | + SNDDEV_DEV_VOL_ANALOG))) + rc = snddev_icodec_set_device_volume_impl( + dev_info, dev_info->dev_volume); + } + mutex_unlock(&drv->rx_lock); + } else if (icodec->data->capability & SNDDEV_CAP_LB) { + mutex_lock(&drv->lb_lock); + rc = snddev_icodec_open_lb(icodec); + + if (!IS_ERR_VALUE(rc)) { + if ((icodec->data->dev_vol_type & ( + SNDDEV_DEV_VOL_DIGITAL | + SNDDEV_DEV_VOL_ANALOG))) + rc = snddev_icodec_set_device_volume_impl( + dev_info, dev_info->dev_volume); + } + + mutex_unlock(&drv->lb_lock); + } else { + mutex_lock(&drv->tx_lock); + if (drv->tx_active) { + mutex_unlock(&drv->tx_lock); + rc = -EBUSY; + goto error; + } + rc = snddev_icodec_open_tx(icodec); + + if (!IS_ERR_VALUE(rc)) { + drv->tx_active = 1; + if ((icodec->data->dev_vol_type & ( + SNDDEV_DEV_VOL_DIGITAL | + SNDDEV_DEV_VOL_ANALOG))) + rc = snddev_icodec_set_device_volume_impl( + dev_info, dev_info->dev_volume); + } + mutex_unlock(&drv->tx_lock); + } +error: + return rc; +} + +static int snddev_icodec_close(struct msm_snddev_info *dev_info) +{ + int rc = 0; + struct snddev_icodec_state *icodec; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + if (!dev_info) { + rc = -EINVAL; + goto error; + } + + icodec = dev_info->private_data; + + if (icodec->data->capability & SNDDEV_CAP_RX) { + mutex_lock(&drv->rx_lock); + if (!drv->rx_active) { + mutex_unlock(&drv->rx_lock); + rc = -EPERM; + goto error; + } + rc = snddev_icodec_close_rx(icodec); + if (!IS_ERR_VALUE(rc)) + drv->rx_active = 0; + mutex_unlock(&drv->rx_lock); + } else if (icodec->data->capability & SNDDEV_CAP_LB) { + mutex_lock(&drv->lb_lock); + rc = snddev_icodec_close_lb(icodec); + mutex_unlock(&drv->lb_lock); + } else { + mutex_lock(&drv->tx_lock); + if (!drv->tx_active) { + mutex_unlock(&drv->tx_lock); + rc = -EPERM; + goto error; + } + rc = snddev_icodec_close_tx(icodec); + if (!IS_ERR_VALUE(rc)) + drv->tx_active = 0; + mutex_unlock(&drv->tx_lock); + } + +error: + return rc; +} + +static int snddev_icodec_check_freq(u32 req_freq) +{ + int rc = -EINVAL; + + if ((req_freq != 0) && (req_freq >= 8000) && (req_freq <= 48000)) { + if ((req_freq == 8000) || (req_freq == 11025) || + (req_freq == 12000) || (req_freq == 16000) || + (req_freq == 22050) || (req_freq == 24000) || + (req_freq == 32000) || (req_freq == 44100) || + (req_freq == 48000)) { + rc = 0; + } else + pr_info("%s: Unsupported Frequency:%d\n", __func__, + req_freq); + } + return rc; +} + +static int snddev_icodec_set_freq(struct msm_snddev_info *dev_info, u32 rate) +{ + int rc; + struct snddev_icodec_state *icodec; + + if (!dev_info) { + rc = -EINVAL; + goto error; + } + + icodec = dev_info->private_data; + if (adie_codec_freq_supported(icodec->data->profile, rate) != 0) { + rc = -EINVAL; + goto error; + } else { + if (snddev_icodec_check_freq(rate) != 0) { + rc = -EINVAL; + goto error; + } else + icodec->sample_rate = rate; + } + + if (icodec->enabled) { + snddev_icodec_close(dev_info); + snddev_icodec_open(dev_info); + } + + return icodec->sample_rate; + +error: + return rc; +} + +static int snddev_icodec_enable_sidetone(struct msm_snddev_info *dev_info, + u32 enable, uint16_t gain) +{ + int rc = 0; + struct snddev_icodec_state *icodec; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + if (!dev_info) { + pr_err("invalid dev_info\n"); + rc = -EINVAL; + goto error; + } + + icodec = dev_info->private_data; + + if (icodec->data->capability & SNDDEV_CAP_RX) { + mutex_lock(&drv->rx_lock); + if (!drv->rx_active || !dev_info->opened) { + pr_err("dev not active\n"); + rc = -EPERM; + mutex_unlock(&drv->rx_lock); + goto error; + } + rc = afe_sidetone(PRIMARY_I2S_TX, PRIMARY_I2S_RX, enable, gain); + if (rc < 0) + pr_err("%s: AFE command sidetone failed\n", __func__); + mutex_unlock(&drv->rx_lock); + } else { + rc = -EINVAL; + pr_err("rx device only\n"); + } + +error: + return rc; + +} +static int snddev_icodec_enable_anc(struct msm_snddev_info *dev_info, + u32 enable) +{ + int rc = 0; + struct adie_codec_anc_data *reg_writes; + struct acdb_cal_block cal_block; + struct snddev_icodec_state *icodec; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + + pr_info("%s: enable=%d\n", __func__, enable); + + if (!dev_info) { + pr_err("invalid dev_info\n"); + rc = -EINVAL; + goto error; + } + icodec = dev_info->private_data; + + if ((icodec->data->capability & SNDDEV_CAP_RX) && + (icodec->data->capability & SNDDEV_CAP_ANC)) { + mutex_lock(&drv->rx_lock); + + if (!drv->rx_active || !dev_info->opened) { + pr_err("dev not active\n"); + rc = -EPERM; + mutex_unlock(&drv->rx_lock); + goto error; + } + if (enable) { + get_anc_cal(&cal_block); + reg_writes = (struct adie_codec_anc_data *) + cal_block.cal_kvaddr; + + if (reg_writes == NULL) { + pr_err("error, no calibration data\n"); + rc = -1; + mutex_unlock(&drv->rx_lock); + goto error; + } + + rc = adie_codec_enable_anc(icodec->adie_path, + 1, reg_writes); + } else { + rc = adie_codec_enable_anc(icodec->adie_path, + 0, NULL); + } + mutex_unlock(&drv->rx_lock); + } else { + rc = -EINVAL; + pr_err("rx and ANC device only\n"); + } + +error: + return rc; + +} + +int snddev_icodec_set_device_volume(struct msm_snddev_info *dev_info, + u32 volume) +{ + struct snddev_icodec_state *icodec; + struct mutex *lock; + struct snddev_icodec_drv_state *drv = &snddev_icodec_drv; + int rc = -EPERM; + + if (!dev_info) { + pr_info("%s : device not intilized.\n", __func__); + return -EINVAL; + } + + icodec = dev_info->private_data; + + if (!(icodec->data->dev_vol_type & (SNDDEV_DEV_VOL_DIGITAL + | SNDDEV_DEV_VOL_ANALOG))) { + + pr_info("%s : device %s does not support device volume " + "control.", __func__, dev_info->name); + return -EPERM; + } + dev_info->dev_volume = volume; + + if (icodec->data->capability & SNDDEV_CAP_RX) + lock = &drv->rx_lock; + else if (icodec->data->capability & SNDDEV_CAP_LB) + lock = &drv->lb_lock; + else + lock = &drv->tx_lock; + + mutex_lock(lock); + + rc = snddev_icodec_set_device_volume_impl(dev_info, + dev_info->dev_volume); + mutex_unlock(lock); + return rc; +} + +static int snddev_icodec_probe(struct platform_device *pdev) +{ + int rc = 0; + struct snddev_icodec_data *pdata; + struct msm_snddev_info *dev_info; + struct snddev_icodec_state *icodec; + + if (!pdev || !pdev->dev.platform_data) { + printk(KERN_ALERT "Invalid caller\n"); + rc = -1; + goto error; + } + pdata = pdev->dev.platform_data; + if ((pdata->capability & SNDDEV_CAP_RX) && + (pdata->capability & SNDDEV_CAP_TX)) { + pr_err("%s: invalid device data either RX or TX\n", __func__); + goto error; + } + icodec = kzalloc(sizeof(struct snddev_icodec_state), GFP_KERNEL); + if (!icodec) { + rc = -ENOMEM; + goto error; + } + dev_info = kmalloc(sizeof(struct msm_snddev_info), GFP_KERNEL); + if (!dev_info) { + kfree(icodec); + rc = -ENOMEM; + goto error; + } + + dev_info->name = pdata->name; + dev_info->copp_id = pdata->copp_id; + dev_info->private_data = (void *) icodec; + dev_info->dev_ops.open = snddev_icodec_open; + dev_info->dev_ops.close = snddev_icodec_close; + dev_info->dev_ops.set_freq = snddev_icodec_set_freq; + dev_info->dev_ops.set_device_volume = snddev_icodec_set_device_volume; + dev_info->capability = pdata->capability; + dev_info->opened = 0; + msm_snddev_register(dev_info); + icodec->data = pdata; + icodec->sample_rate = pdata->default_sample_rate; + dev_info->sample_rate = pdata->default_sample_rate; + dev_info->channel_mode = pdata->channel_mode; + if (pdata->capability & SNDDEV_CAP_RX) + dev_info->dev_ops.enable_sidetone = + snddev_icodec_enable_sidetone; + else + dev_info->dev_ops.enable_sidetone = NULL; + + if (pdata->capability & SNDDEV_CAP_ANC) { + dev_info->dev_ops.enable_anc = + snddev_icodec_enable_anc; + } else { + dev_info->dev_ops.enable_anc = NULL; + } +error: + return rc; +} + +static int snddev_icodec_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver snddev_icodec_driver = { + .probe = snddev_icodec_probe, + .remove = snddev_icodec_remove, + .driver = { .name = "snddev_icodec" } +}; + +module_param(msm_codec_i2s_slave_mode, bool, 0); +MODULE_PARM_DESC(msm_codec_i2s_slave_mode, "Set MSM to I2S slave clock mode"); + +static int __init snddev_icodec_init(void) +{ + s32 rc; + struct snddev_icodec_drv_state *icodec_drv = &snddev_icodec_drv; + + rc = platform_driver_register(&snddev_icodec_driver); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: platform_driver_register for snddev icodec failed\n", + __func__); + goto error_snddev_icodec_driver; + } + + rc = platform_driver_register(&msm_cdcclk_ctl_driver); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: platform_driver_register for msm snddev failed\n", + __func__); + goto error_msm_cdcclk_ctl_driver; + } + + rc = platform_driver_register(&msm_icodec_gpio_driver); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: platform_driver_register for msm snddev gpio failed\n", + __func__); + goto error_msm_icodec_gpio_driver; + } + + mutex_init(&icodec_drv->rx_lock); + mutex_init(&icodec_drv->lb_lock); + mutex_init(&icodec_drv->tx_lock); + icodec_drv->rx_active = 0; + icodec_drv->tx_active = 0; + icodec_drv->snddev_vreg = vreg_init(); + + wake_lock_init(&icodec_drv->tx_idlelock, WAKE_LOCK_IDLE, + "snddev_tx_idle"); + wake_lock_init(&icodec_drv->rx_idlelock, WAKE_LOCK_IDLE, + "snddev_rx_idle"); + return 0; +error_msm_icodec_gpio_driver: + platform_driver_unregister(&msm_cdcclk_ctl_driver); +error_msm_cdcclk_ctl_driver: + platform_driver_unregister(&snddev_icodec_driver); +error_snddev_icodec_driver: + return -ENODEV; +} + +static void __exit snddev_icodec_exit(void) +{ + struct snddev_icodec_drv_state *icodec_drv = &snddev_icodec_drv; + + platform_driver_unregister(&snddev_icodec_driver); + platform_driver_unregister(&msm_cdcclk_ctl_driver); + platform_driver_unregister(&msm_icodec_gpio_driver); + + clk_put(icodec_drv->rx_osrclk); + clk_put(icodec_drv->tx_osrclk); + if (icodec_drv->snddev_vreg) { + vreg_deinit(icodec_drv->snddev_vreg); + icodec_drv->snddev_vreg = NULL; + } + return; +} + +module_init(snddev_icodec_init); +module_exit(snddev_icodec_exit); + +MODULE_DESCRIPTION("ICodec Sound Device driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_icodec.h b/arch/arm/mach-msm/qdsp6v2/snddev_icodec.h new file mode 100644 index 00000000000..8d5613f1c16 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_icodec.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP6V2_SNDDEV_ICODEC_H +#define __MACH_QDSP6V2_SNDDEV_ICODEC_H +#include +#include +#include + +struct snddev_icodec_data { + u32 capability; /* RX or TX */ + const char *name; + u32 copp_id; /* audpp routing */ + /* Adie profile */ + struct adie_codec_dev_profile *profile; + /* Afe setting */ + u8 channel_mode; + u32 default_sample_rate; + int (*pamp_on) (void); + void (*pamp_off) (void); + int (*voltage_on) (void); + void (*voltage_off) (void); + u32 dev_vol_type; +}; + +#endif diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_mi2s.c b/arch/arm/mach-msm/qdsp6v2/snddev_mi2s.c new file mode 100644 index 00000000000..db27d9e9161 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_mi2s.c @@ -0,0 +1,462 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "snddev_mi2s.h" + +#define SNDDEV_MI2S_PCM_SZ 32 /* 16 bit / sample stereo mode */ +#define SNDDEV_MI2S_MUL_FACTOR 3 /* Multi by 8 Shift by 3 */ +#define SNDDEV_MI2S_CLK_RATE(freq) \ + (((freq) * (SNDDEV_MI2S_PCM_SZ)) << (SNDDEV_MI2S_MUL_FACTOR)) + + +/* Global state for the driver */ +struct snddev_mi2s_drv_state { + + struct clk *tx_osrclk; + struct clk *tx_bitclk; + int mi2s_ws; + int mi2s_mclk; + int mi2s_sclk; + int fm_mi2s_sd; +}; + +static struct snddev_mi2s_drv_state snddev_mi2s_drv; + +static struct msm_mi2s_gpio_data *mi2s_gpio; + +static int mi2s_gpios_request(void) +{ + int rc = 0; + + pr_debug("%s\n", __func__); + rc = gpio_request(snddev_mi2s_drv.mi2s_ws, "MI2S_WS"); + if (rc < 0) { + pr_err("%s: GPIO request for MI2S_WS failed\n", __func__); + return rc; + } + + rc = gpio_request(snddev_mi2s_drv.mi2s_sclk, "MI2S_SCLK"); + if (rc < 0) { + pr_err("%s: GPIO request for MI2S_SCLK failed\n", __func__); + gpio_free(snddev_mi2s_drv.mi2s_sclk); + return rc; + } + + rc = gpio_request(snddev_mi2s_drv.mi2s_mclk, "MI2S_MCLK"); + if (rc < 0) { + pr_err("%s: GPIO request for MI2S_MCLK failed\n", + __func__); + gpio_free(snddev_mi2s_drv.mi2s_ws); + gpio_free(snddev_mi2s_drv.mi2s_sclk); + return rc; + } + + rc = gpio_request(snddev_mi2s_drv.fm_mi2s_sd, "FM_MI2S_SD"); + if (rc < 0) { + pr_err("%s: GPIO request for FM_MI2S_SD failed\n", + __func__); + gpio_free(snddev_mi2s_drv.mi2s_ws); + gpio_free(snddev_mi2s_drv.mi2s_sclk); + gpio_free(snddev_mi2s_drv.mi2s_mclk); + return rc; + } + + return rc; +} + +static void mi2s_gpios_free(void) +{ + pr_debug("%s\n", __func__); + gpio_free(snddev_mi2s_drv.mi2s_ws); + gpio_free(snddev_mi2s_drv.mi2s_sclk); + gpio_free(snddev_mi2s_drv.mi2s_mclk); + gpio_free(snddev_mi2s_drv.fm_mi2s_sd); +} + +static int mi2s_get_gpios(struct platform_device *pdev) +{ + int rc = 0; + struct resource *res; + + /* Claim all of the GPIOs. */ + res = platform_get_resource_byname(pdev, IORESOURCE_IO, "mi2s_ws"); + if (!res) { + pr_err("%s: failed to get gpio MI2S_WS\n", __func__); + return -ENODEV; + } + + snddev_mi2s_drv.mi2s_ws = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, "mi2s_sclk"); + if (!res) { + pr_err("%s: failed to get gpio MI2S_SCLK\n", __func__); + return -ENODEV; + } + + snddev_mi2s_drv.mi2s_sclk = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "mi2s_mclk"); + if (!res) { + pr_err("%s: failed to get gpio MI2S_MCLK\n", __func__); + return -ENODEV; + } + + snddev_mi2s_drv.mi2s_mclk = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "fm_mi2s_sd"); + if (!res) { + pr_err("%s: failed to get gpio FM_MI2S_SD\n", __func__); + return -ENODEV; + } + + snddev_mi2s_drv.fm_mi2s_sd = res->start; + + return rc; +} + +static int mi2s_fm_probe(struct platform_device *pdev) +{ + int rc = 0; + + rc = mi2s_get_gpios(pdev); + if (rc < 0) { + pr_err("%s: GPIO configuration failed\n", __func__); + return rc; + } + + mi2s_gpio = (struct msm_mi2s_gpio_data *)(pdev->dev.platform_data); + return rc; +} + +static struct platform_driver mi2s_fm_driver = { + .probe = mi2s_fm_probe, + .driver = { .name = "msm_mi2s"} +}; + +static u8 num_of_bits_set(u8 sd_line_mask) +{ + u8 num_bits_set = 0; + + while (sd_line_mask) { + + if (sd_line_mask & 1) + num_bits_set++; + sd_line_mask = sd_line_mask >> 1; + } + return num_bits_set; +} + +static int snddev_mi2s_open(struct msm_snddev_info *dev_info) +{ + int rc = 0; + union afe_port_config afe_config; + u8 channels; + u8 num_of_sd_lines = 0; + struct snddev_mi2s_drv_state *drv = &snddev_mi2s_drv; + struct snddev_mi2s_data *snddev_mi2s_data = dev_info->private_data; + + if (!dev_info) { + pr_err("%s: msm_snddev_info is null\n", __func__); + return -EINVAL; + } + + /* set up osr clk */ + drv->tx_osrclk = clk_get(0, "mi2s_osr_clk"); + if (IS_ERR(drv->tx_osrclk)) + pr_err("%s master clock Error\n", __func__); + + rc = clk_set_rate(drv->tx_osrclk, + SNDDEV_MI2S_CLK_RATE(dev_info->sample_rate)); + if (IS_ERR_VALUE(rc)) { + pr_err("ERROR setting osr clock\n"); + return -ENODEV; + } + clk_enable(drv->tx_osrclk); + + /* set up bit clk */ + drv->tx_bitclk = clk_get(0, "mi2s_bit_clk"); + if (IS_ERR(drv->tx_bitclk)) + pr_err("%s clock Error\n", __func__); + + rc = clk_set_rate(drv->tx_bitclk, 8); + if (IS_ERR_VALUE(rc)) { + pr_err("ERROR setting bit clock\n"); + clk_disable(drv->tx_osrclk); + return -ENODEV; + } + clk_enable(drv->tx_bitclk); + + afe_config.mi2s.bitwidth = 16; + + if (snddev_mi2s_data->channel_mode == 1) + channels = AFE_MI2S_MONO; + else if (snddev_mi2s_data->channel_mode == 2) + channels = AFE_MI2S_STEREO; + else if (snddev_mi2s_data->channel_mode == 4) + channels = AFE_MI2S_4CHANNELS; + else if (snddev_mi2s_data->channel_mode == 6) + channels = AFE_MI2S_6CHANNELS; + else if (snddev_mi2s_data->channel_mode == 8) + channels = AFE_MI2S_8CHANNELS; + else { + pr_err("ERROR: Invalid MI2S channel mode\n"); + goto error_invalid_data; + } + + num_of_sd_lines = num_of_bits_set(snddev_mi2s_data->sd_lines); + + switch (num_of_sd_lines) { + case 1: + switch (snddev_mi2s_data->sd_lines) { + case MI2S_SD0: + afe_config.mi2s.line = AFE_I2S_SD0; + break; + case MI2S_SD1: + afe_config.mi2s.line = AFE_I2S_SD1; + break; + case MI2S_SD2: + afe_config.mi2s.line = AFE_I2S_SD2; + break; + case MI2S_SD3: + afe_config.mi2s.line = AFE_I2S_SD3; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + if (channels != AFE_MI2S_STEREO && + channels != AFE_MI2S_MONO) { + pr_err("%s: for one SD line, channel " + "must be 1 or 2\n", __func__); + goto error_invalid_data; + } + afe_config.mi2s.channel = channels; + break; + case 2: + switch (snddev_mi2s_data->sd_lines) { + case MI2S_SD0 | MI2S_SD1: + afe_config.mi2s.line = AFE_I2S_QUAD01; + break; + case MI2S_SD2 | MI2S_SD3: + afe_config.mi2s.line = AFE_I2S_QUAD23; + break; + default: + pr_err("%s: invalid SD line\n", + __func__); + goto error_invalid_data; + } + if (channels != AFE_MI2S_4CHANNELS) { + pr_err("%s: for two SD lines, channel " + "must be 1 and 2 or 3 and 4\n", __func__); + goto error_invalid_data; + } + break; + case 3: + switch (snddev_mi2s_data->sd_lines) { + case MI2S_SD0 | MI2S_SD1 | MI2S_SD2: + afe_config.mi2s.line = AFE_I2S_6CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + if (channels != AFE_MI2S_6CHANNELS) { + pr_err("%s: for three SD lines, lines " + "must be 1, 2, and 3\n", __func__); + goto error_invalid_data; + } + break; + case 4: + switch (snddev_mi2s_data->sd_lines) { + case MI2S_SD0 | MI2S_SD1 | MI2S_SD2 | MI2S_SD3: + afe_config.mi2s.line = AFE_I2S_8CHS; + break; + default: + pr_err("%s: invalid SD lines\n", + __func__); + goto error_invalid_data; + } + + if (channels != AFE_MI2S_8CHANNELS) { + pr_err("%s: for four SD lines, lines " + "must be 1, 2, 3, and 4\n", __func__); + goto error_invalid_data; + } + break; + default: + pr_err("%s: invalid SD lines\n", __func__); + goto error_invalid_data; + } + afe_config.mi2s.ws = 1; + rc = afe_open(snddev_mi2s_data->copp_id, &afe_config, + dev_info->sample_rate); + + if (rc < 0) { + pr_err("%s: afe_open failed\n", __func__); + goto error_invalid_data; + } + + /*enable fm gpio here*/ + rc = mi2s_gpios_request(); + if (rc < 0) { + pr_err("%s: GPIO request failed\n", __func__); + return rc; + } + + pr_info("%s: afe_open done\n", __func__); + + return rc; + +error_invalid_data: + + clk_disable(drv->tx_bitclk); + clk_disable(drv->tx_osrclk); + return -EINVAL; +} + +static int snddev_mi2s_close(struct msm_snddev_info *dev_info) +{ + + struct snddev_mi2s_drv_state *mi2s_drv = &snddev_mi2s_drv; + struct snddev_mi2s_data *snddev_mi2s_data = dev_info->private_data; + + if (!dev_info) { + pr_err("%s: msm_snddev_info is null\n", __func__); + return -EINVAL; + } + + if (!dev_info->opened) { + pr_err(" %s: calling close device with out opening the" + " device\n", __func__); + return -EIO; + } + afe_close(snddev_mi2s_data->copp_id); + clk_disable(mi2s_drv->tx_bitclk); + clk_disable(mi2s_drv->tx_osrclk); + + mi2s_gpios_free(); + + pr_info("%s:\n", __func__); + + return 0; +} + +static int snddev_mi2s_set_freq(struct msm_snddev_info *dev_info, u32 req_freq) +{ + if (req_freq != 48000) { + pr_info("%s: Unsupported Frequency:%d\n", __func__, req_freq); + return -EINVAL; + } + return 48000; +} + + +static int snddev_mi2s_probe(struct platform_device *pdev) +{ + int rc = 0; + struct snddev_mi2s_data *pdata; + struct msm_snddev_info *dev_info; + + if (!pdev || !pdev->dev.platform_data) { + printk(KERN_ALERT "Invalid caller\n"); + return -ENODEV; + } + + pdata = pdev->dev.platform_data; + + dev_info = kzalloc(sizeof(struct msm_snddev_info), GFP_KERNEL); + if (!dev_info) { + pr_err("%s: uneable to allocate memeory for msm_snddev_info\n", + __func__); + + return -ENOMEM; + } + + dev_info->name = pdata->name; + dev_info->copp_id = pdata->copp_id; + dev_info->dev_ops.open = snddev_mi2s_open; + dev_info->dev_ops.close = snddev_mi2s_close; + dev_info->private_data = (void *)pdata; + dev_info->dev_ops.set_freq = snddev_mi2s_set_freq; + dev_info->capability = pdata->capability; + dev_info->opened = 0; + dev_info->sample_rate = pdata->sample_rate; + msm_snddev_register(dev_info); + + return rc; +} + +static struct platform_driver snddev_mi2s_driver = { + .probe = snddev_mi2s_probe, + .driver = {.name = "snddev_mi2s"} +}; + +static int __init snddev_mi2s_init(void) +{ + s32 rc = 0; + + rc = platform_driver_register(&mi2s_fm_driver); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: platform_driver_register for mi2s_fm_driver failed\n", + __func__); + goto error_mi2s_fm_platform_driver; + } + + rc = platform_driver_register(&snddev_mi2s_driver); + if (IS_ERR_VALUE(rc)) { + + pr_err("%s: platform_driver_register failed\n", __func__); + goto error_platform_driver; + } + + return rc; + +error_platform_driver: + platform_driver_unregister(&mi2s_fm_driver); +error_mi2s_fm_platform_driver: + pr_err("%s: encounter error\n", __func__); + return -ENODEV; +} + +static void __exit snddev_mi2s_exit(void) +{ + struct snddev_mi2s_drv_state *mi2s_drv = &snddev_mi2s_drv; + + platform_driver_unregister(&snddev_mi2s_driver); + clk_put(mi2s_drv->tx_osrclk); + clk_put(mi2s_drv->tx_bitclk); + return; +} + + +module_init(snddev_mi2s_init); +module_exit(snddev_mi2s_exit); + +MODULE_DESCRIPTION("MI2S Sound Device driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_mi2s.h b/arch/arm/mach-msm/qdsp6v2/snddev_mi2s.h new file mode 100644 index 00000000000..d369c969ac1 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_mi2s.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP6_V2_SNDDEV_MI2S_H +#define __MACH_QDSP6_V2_SNDDEV_MI2S_H + +struct snddev_mi2s_data { + u32 capability; /* RX or TX */ + const char *name; + u32 copp_id; /* audpp routing */ + u16 channel_mode; + u16 sd_lines; + u32 sample_rate; +}; + +#define MI2S_SD0 (1 << 0) +#define MI2S_SD1 (1 << 1) +#define MI2S_SD2 (1 << 2) +#define MI2S_SD3 (1 << 3) + +#endif diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_virtual.c b/arch/arm/mach-msm/qdsp6v2/snddev_virtual.c new file mode 100644 index 00000000000..f48aa0ec713 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_virtual.c @@ -0,0 +1,172 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include "snddev_virtual.h" + +static DEFINE_MUTEX(snddev_virtual_lock); + +static int snddev_virtual_open(struct msm_snddev_info *dev_info) +{ + int rc = 0; + + pr_debug("%s\n", __func__); + + mutex_lock(&snddev_virtual_lock); + + if (!dev_info) { + pr_err("%s: NULL dev_info\n", __func__); + + rc = -EINVAL; + goto done; + } + + if (!dev_info->opened) { + rc = afe_start_pseudo_port(dev_info->copp_id); + } else { + pr_err("%s: Pseudo port 0x%x is already open\n", + __func__, dev_info->copp_id); + + rc = -EBUSY; + } + +done: + mutex_unlock(&snddev_virtual_lock); + + return rc; +} + +static int snddev_virtual_close(struct msm_snddev_info *dev_info) +{ + int rc = 0; + + pr_debug("%s\n", __func__); + + mutex_lock(&snddev_virtual_lock); + + if (!dev_info) { + pr_err("%s: NULL dev_info\n", __func__); + + rc = -EINVAL; + goto done; + } + + if (dev_info->opened) { + rc = afe_stop_pseudo_port(dev_info->copp_id); + } else { + pr_err("%s: Pseudo port 0x%x is not open\n", + __func__, dev_info->copp_id); + + rc = -EPERM; + } + +done: + mutex_unlock(&snddev_virtual_lock); + + return rc; +} + +static int snddev_virtual_set_freq(struct msm_snddev_info *dev_info, u32 rate) +{ + int rc = 0; + + if (!dev_info) + rc = -EINVAL; + + return rate; +} + +static int snddev_virtual_probe(struct platform_device *pdev) +{ + int rc = 0; + struct snddev_virtual_data *pdata; + struct msm_snddev_info *dev_info; + + pr_debug("%s\n", __func__); + + if (!pdev || !pdev->dev.platform_data) { + pr_err("%s: Invalid caller\n", __func__); + + rc = -EPERM; + goto done; + } + + pdata = pdev->dev.platform_data; + + dev_info = kmalloc(sizeof(struct msm_snddev_info), GFP_KERNEL); + if (!dev_info) { + pr_err("%s: Out of memory\n", __func__); + + rc = -ENOMEM; + goto done; + } + + dev_info->name = pdata->name; + dev_info->copp_id = pdata->copp_id; + dev_info->private_data = (void *) NULL; + dev_info->dev_ops.open = snddev_virtual_open; + dev_info->dev_ops.close = snddev_virtual_close; + dev_info->dev_ops.set_freq = snddev_virtual_set_freq; + dev_info->capability = pdata->capability; + dev_info->sample_rate = 48000; + dev_info->opened = 0; + dev_info->sessions = 0; + + msm_snddev_register(dev_info); + +done: + return rc; +} + +static int snddev_virtual_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver snddev_virtual_driver = { + .probe = snddev_virtual_probe, + .remove = snddev_virtual_remove, + .driver = { .name = "snddev_virtual" } +}; + +static int __init snddev_virtual_init(void) +{ + int rc = 0; + + pr_debug("%s\n", __func__); + + rc = platform_driver_register(&snddev_virtual_driver); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: Platform driver register failure\n", __func__); + + return -ENODEV; + } + + return 0; +} + +static void __exit snddev_virtual_exit(void) +{ + platform_driver_unregister(&snddev_virtual_driver); + + return; +} + +module_init(snddev_virtual_init); +module_exit(snddev_virtual_exit); + +MODULE_DESCRIPTION("Virtual Sound Device driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/snddev_virtual.h b/arch/arm/mach-msm/qdsp6v2/snddev_virtual.h new file mode 100644 index 00000000000..dec4d0739de --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/snddev_virtual.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __MACH_QDSP6V2_SNDDEV_VIRTUAL_H +#define __MACH_QDSP6V2_SNDDEV_VIRTUAL_H + +struct snddev_virtual_data { + u32 capability; /* RX or TX */ + const char *name; + u32 copp_id; /* Audpp routing */ +}; +#endif diff --git a/arch/arm/mach-msm/qdsp6v2/timpani_profile_8x60.h b/arch/arm/mach-msm/qdsp6v2/timpani_profile_8x60.h new file mode 100644 index 00000000000..71973910a53 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/timpani_profile_8x60.h @@ -0,0 +1,3100 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MACH_QDSP6V2_TIMPANI_PROFILE_H +#define __MACH_QDSP6V2_TIMPANI_PROFILE_H + +/* + * TX Device Profiles + */ + +/* Analog MIC */ +/* AMIC Primary mono */ +#define AMIC_PRI_MONO_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xD0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x00)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3A98}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + + +/* AMIC Secondary mono */ +#define AMIC_SEC_MONO_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAC, 0x09, 0x00)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3A98 },\ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAC, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* AMIC dual */ +#define AMIC_DUAL_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xB0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAC, 0x09, 0x00)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAC, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +/* + * Digital MIC + */ +/* DMIC1 Primary (DMIC 1 - TX1) */ +#define DMIC1_PRI_MONO_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0x1F, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0x3F, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0x3F, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x39, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* DMIC1 Secondary - (DMIC 2 - TX1) */ +#define DMIC1_SEC_MONO_OSR_64 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x12)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x0F, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* DMIC Dual Primary (DMIC 1/2 - TX1) */ +#define DMIC1_PRI_STEREO_OSR_256 \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0x1F, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0x3F, 0x19)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0x3F, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x39, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x0F, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)} } + +/* DMIC2 Dual Primary (DMIC 3/4 - TX2 - Left/Right) */ +#define DMIC2_SEC_DUAL_OSR_64 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA6, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA7, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x22)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x96, 0xFF, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0xF0, 0xE0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0xC0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA6, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA7, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HS_DMIC2_STEREO_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0x1F, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0x3F, 0x19)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0x3F, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x39, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* + * LINE IN + */ +#define LINEIN_PRI_MONO_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LINEIN_PRI_STEREO_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0xA2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LINEIN_SEC_MONO_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA6, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA7, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x2E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x96, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0xF0, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x18, 0xFF, 0xA2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0xC0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA6, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA7, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x18, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LINEIN_SEC_STEREO_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA6, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA7, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x2E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x96, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0xF0, 0xE0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x18, 0xFF, 0xA2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x19, 0xFF, 0xA2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0xC0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA6, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA7, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x18, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x19, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LINEIN_SEC_STEREO_OSR_64 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA6, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA7, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x22)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x96, 0xFF, 0x18)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0xF0, 0xE0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x18, 0xFF, 0xA2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x19, 0xFF, 0xA2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0xC0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA6, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA7, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0xC0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x18, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x19, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* + * AUX IN + */ +#define AUXIN_MONO_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xA1)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* Headset MIC */ +#define HEADSET_AMIC2_TX_MONO_PRI_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xC8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x00)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3A98 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* + * RX Device Profiles + */ + +/* RX EAR */ +#define EAR_PRI_MONO_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define EAR_SEC_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* ANC Headset: Speakers on Primary Rx, Noise Microphones on Secondary Tx */ + +#define ANC_HEADSET_CPLS_AMIC1_AUXL_RX1_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x95, 0xFF, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9B, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x18, 0xFF, 0xD0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x19, 0xFF, 0xC1)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xC0, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xD0, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x18, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x19, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x09, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0A, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* + * RX HPH PRIMARY + */ + +/* RX HPH CLASS AB CAPLESS */ + +#define HEADSET_AB_CPLS_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HPH_PRI_AB_CPLS_MONO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* based on HEADSET_AB_CPLS_48000_OSR_256, change 0x83 */ +/* change 0x31 */ +#define HPH_PRI_AB_CPLS_MONO_LEFT \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xC5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* based on HEADSET_AB_CPLS_48000_OSR_256 */ +/* add 0x8A to mute rx1 left 0x01 */ +/* change 0x31 */ +#define HPH_PRI_AB_CPLS_MONO_RIGHT \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0D)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x35)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define FTM_HPH_PRI_AB_CPLS_MONO_LB_LEFT \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xC5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x3C)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define FTM_HPH_PRI_AB_CPLS_MONO_LB_RIGHT \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0D)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x35)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* This is for differential signaling, which is a test mode. */ +#define HPH_PRI_AB_CPLS_DIFF \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x06)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HPH_PRI_AB_CPLS_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x55)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* RX HPH CLASS AB LEGACY */ + +#define HPH_PRI_AB_LEG_MONO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0xF9)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HP_PRI_AB_LEG_DIFF \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0xF9)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HPH_PRI_AB_LEG_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x09)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x186A0}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xF9)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x27)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* RX HPH CLASS D LEGACY */ + +#define HPH_PRI_D_LEG_DIFF \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0x90, 0x90)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0A, 0x0A)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HPH_PRI_D_LEG_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x21, 0xFF, 0x60)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x22, 0xFF, 0xE1)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x26, 0xFF, 0xD0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2D, 0xFF, 0x6F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2E, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3F, 0xFF, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x40, 0xFF, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x41, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x42, 0xFF, 0xBB)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x43, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x44, 0xF7, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x45, 0xFF, 0xFF)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x46, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x47, 0xFF, 0xF2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x48, 0xF7, 0x37)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x49, 0xFF, 0xFF)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4A, 0xFF, 0x77)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0x8C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x0A)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* + * RX HPH SECONDARY + */ + +/* RX HPH CLASS AB CAPLESS */ +#define HPH_SEC_AB_CPLS_MONO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x48, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x55)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HPH_SEC_AB_CPLS_DIFF \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x48, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x55)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HPH_SEC_AB_CPLS_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA5, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x55)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA5, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* RX HPH CLASS AB LEGACY */ +#define HPH_SEC_AB_LEG_MONO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x48, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0xF9)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HPH_SEC_AB_LEG_DIFF \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x48, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0xF9)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define HPH_SEC_AB_LEG_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA5, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0xF9)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA5, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* RX HPH CLASS D LEGACY */ + +#define HPH_SEC_D_LEG_DIFF \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0x50, 0x50)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0A, 0x0A)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000},\ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HPH_SEC_D_LEG_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA5, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0x50, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0A, 0x0A)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* RX LINE OUT PRIMARY */ +/* spkr phone mono rx */ +#define LINEOUT_PRI_MONO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x58, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LINEOUT_PRI_DIFF \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x06)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x24, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LINEOUT_PRI_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x58, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0c)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* RX LINE OUT SECONDARY */ +#define LINEOUT_SEC_MONO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x48, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x48, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x58, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LINEOUT_SEC_DIFF \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x48, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x48, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x58, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LINEOUT_SEC_STEREO \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA5, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x29, 0xFF, 0xC2)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x48, 0x48)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x58, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA5, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x48, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define SPEAKER_PRI_STEREO_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* RX AUX */ +#define AUXOUT_PRI_MONO_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x20, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x03)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 50000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x07)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x20, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define AUXOUT_SEC_MONO_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA1, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x98, 0xFF, 0x02)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x28, 0xFF, 0xCA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x40)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x03)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 50000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x07)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x30, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA4, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAA, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x40, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_AUXPGA_HPH_AB_CPLS_STEREO \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2F, 0xFF, 0x44)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x30, 0xFF, 0x92)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xAA)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x90, 0x90)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFD, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define LB_AUXPGA_LO_STEREO \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x2F, 0xFF, 0x44)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x30, 0xFF, 0x92)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xAA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x90, 0x90)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0xF0, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x90, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +/* + * LB Device Profiles + */ + +/* EAR */ +#define LB_EAR_PRI_MONO \ + {{ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x04, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x04, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* HPH CLASS AB CAPLESS */ +#define LB_HPH_AB_CPLS_PRI_MONO \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x55)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_HPH_AB_CPLS_PRI_DIFF \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x10, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x55)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define SPEAKER_HPH_AB_CPL_PRI_STEREO_48000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_HPH_AB_CPLS_PRI_STEREO \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x90, 0x90)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xAA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x55)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x90, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* HPH CLASS AB LEGACY */ +#define LB_HPH_AB_LEG_PRI_MONO \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xFC)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_PHP_AB_LEG_PRI_DIFF \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x10, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xFC)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x08, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_HPH_AB_LEG_PRI_STEREO \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x90, 0x90)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xAA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x59)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xFC)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x90, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* HPH CLASS D LEGACY */ +#define LB_HPH_D_LEG_PRI_DIFF \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x3A, 0x2A)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x3F, 0x2F)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_HPH_D_LEG_PRI_STEREO \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0x30, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xAA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0xA6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x3A, 0x3A)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 300000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x3F, 0x3F)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3E, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* LINE OUT */ +#define LB_LINEOUT_PRI_MONO \ + {{ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x58, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_LINEOUT_PRI_DIFF \ + {{ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x80, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x10, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x58, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x80, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x10, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define LB_LINEOUT_PRI_STEREO \ + {{ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x90, 0x90)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xAA)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x58, 0x58)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 100000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFF, 0xA4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x90, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* AUX OUT */ +#define LB_AUXOUT_PRI_MONO \ + {{ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0xE0, 0x80)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x38, 0xFF, 0xA0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x03)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 50000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x07)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x33, 0x30, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0xE0, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* TTY RX */ +#define TTY_HEADSET_MONO_RX_8000_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x06)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x45)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0xC5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x27, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x08)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* TTY TX */ +#define TTY_HEADSET_MONO_TX_OSR_256 \ + {{ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* FTM devices */ +/* from HPH_PRI_AB_CPLS_DIFF */ +#define HEADSET_MONO_DIFF_RX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x06)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x4C, 0xFF, 0x29)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x55)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xBB8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFC, 0xF5)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x04)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE2, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3C, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3D, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* based on SPEAKER_PRI_STEREO_48000_OSR_256 */ +/* change 0x8A */ +#define FTM_SPKR_L_RX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* based on SPEAKER_PRI_STEREO_48000_OSR_256 */ +/* change 0x8A */ +#define SPKR_R_RX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3B, 0x24, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* based on SPEAKER_PRI_STEREO_48000_OSR_256 */ +#define FTM_SPKR_RX_LB \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x06)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0x30, 0x30)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + + +#define SPKR_MONO_DIFF_RX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x85, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0x6F, 0x6C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xB7, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x08)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x48)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x1388}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0xF8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x10)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x1C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFE, 0x3C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE0, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xE1, 0xFC, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0xF8, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x3A, 0x24, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* from AMIC_PRI_MONO_OSR_256, change TxFE (reg 0x0D) */ +#define LINEIN_MONO_L_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xD4)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x00)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3A98}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* from AMIC_PRI_MONO_OSR_256, change TxFE (reg 0x0D) */ +#define LINEIN_MONO_R_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0xD6)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x00)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3A98}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* from AMIC_PRI_MONO_OSR_256 */ +#define AUX_IN_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xC1)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x00)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3A98}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* AUXOUT_PRI_MONO_8000_OSR_256 */ +#define AUX_OUT_RX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x20, 0x20)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x03)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 50000}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x07)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x32, 0x07, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x20, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* from DMIC1_PRI_MONO_OSR_256 */ +#define DMIC1_LEFT_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0x1F, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0x3F, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0x3F, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x39, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define DMIC1_RIGHT_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0x1F, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0x3F, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0x3F, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x39, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x0F, 0x06)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define DMIC1_LEFT_AND_RIGHT_TX DMIC1_PRI_STEREO_OSR_256 + +#define DMIC2_LEFT_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0x1F, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0x3F, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0x3F, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x39, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x0F, 0x0A)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define DMIC2_RIGHT_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0x1F, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0x3F, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0x3F, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x39, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define DMIC2_LEFT_AND_RIGHT_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0x1F, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0x3F, 0x21)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0x3F, 0x24)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x39, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA8, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x3F, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x92, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#define HANDSET_MIC1_AUX_IN \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xB0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0xA1)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* based on AMIC_PRI_MONO_OSR_256 */ +#define FTM_HANDSET_LB_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xD0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x00)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0x3A98}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8B, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8C, 0x07, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA0, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xAB, 0x09, 0x09)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* based on HEADSET_AMIC2_TX_MONO_PRI_OSR_256 */ +#define FTM_HEADSET_LB_TX \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xC8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8B, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8C, 0x07, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA0, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* based on EAR_PRI_MONO_8000_OSR_256 */ +#define EAR_PRI_MONO_LB \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x02, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0F)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x02, 0x02)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x97, 0xFF, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0x60, 0x60)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x24, 0xFF, 0x4C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x03, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x84, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x0E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x81, 0xFF, 0x3C)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0x0F, 0x03)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x31, 0x03, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x39, 0x01, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +/* based on AMIC_DUAL_OSR_256 */ +#define FTM_AMIC_DUAL_HANDSET_TX_LB \ + {{ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_FLASH_IMAGE}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x05)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x80, 0x05, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0x30)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0xAC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x82, 0xFF, 0x1E)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA3, 0x01, 0x01)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x93, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x94, 0xFF, 0x1B)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x99, 0x0F, 0x04)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x9F, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0xB0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0xA8)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0xBC)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x65)}, \ + {ADIE_CODEC_ACTION_DELAY_WAIT, 0xbb8 }, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x0C)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x86, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x87, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xC0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0xA0, 0x03, 0x03)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_ANALOG_READY}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x8A, 0xF0, 0xF0)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x83, 0x0C, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_ANALOG_OFF}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0D, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x0E, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x14, 0xFF, 0x64)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x11, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_ENTRY, ADIE_CODEC_PACK_ENTRY(0x12, 0xFF, 0x00)}, \ + {ADIE_CODEC_ACTION_STAGE_REACHED, ADIE_CODEC_DIGITAL_OFF} } + +#endif diff --git a/arch/arm/mach-msm/ramdump.c b/arch/arm/mach-msm/ramdump.c new file mode 100644 index 00000000000..962371bfa0b --- /dev/null +++ b/arch/arm/mach-msm/ramdump.c @@ -0,0 +1,262 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ramdump.h" + +#define RAMDUMP_WAIT_MSECS 120000 + +struct ramdump_device { + char name[256]; + + unsigned int data_ready; + unsigned int consumer_present; + int ramdump_status; + + struct completion ramdump_complete; + struct miscdevice device; + + wait_queue_head_t dump_wait_q; + int nsegments; + struct ramdump_segment *segments; +}; + +static int ramdump_open(struct inode *inode, struct file *filep) +{ + struct ramdump_device *rd_dev = container_of(filep->private_data, + struct ramdump_device, device); + rd_dev->consumer_present = 1; + rd_dev->ramdump_status = 0; + return 0; +} + +static int ramdump_release(struct inode *inode, struct file *filep) +{ + struct ramdump_device *rd_dev = container_of(filep->private_data, + struct ramdump_device, device); + rd_dev->consumer_present = 0; + rd_dev->data_ready = 0; + complete(&rd_dev->ramdump_complete); + return 0; +} + +static unsigned long offset_translate(loff_t user_offset, + struct ramdump_device *rd_dev, unsigned long *data_left) +{ + int i = 0; + + for (i = 0; i < rd_dev->nsegments; i++) + if (user_offset >= rd_dev->segments[i].size) + user_offset -= rd_dev->segments[i].size; + else + break; + + if (i == rd_dev->nsegments) { + pr_debug("Ramdump(%s): offset_translate returning zero\n", + rd_dev->name); + *data_left = 0; + return 0; + } + + *data_left = rd_dev->segments[i].size - user_offset; + + pr_debug("Ramdump(%s): Returning address: %llx, data_left = %ld\n", + rd_dev->name, rd_dev->segments[i].address + user_offset, + *data_left); + + return rd_dev->segments[i].address + user_offset; +} + +#define MAX_IOREMAP_SIZE SZ_1M + +static int ramdump_read(struct file *filep, char __user *buf, size_t count, + loff_t *pos) +{ + struct ramdump_device *rd_dev = container_of(filep->private_data, + struct ramdump_device, device); + void *device_mem = NULL; + unsigned long data_left = 0; + unsigned long addr = 0; + size_t copy_size = 0; + int ret = 0; + + if (rd_dev->data_ready == 0) { + pr_err("Ramdump(%s): Read when there's no dump available!", + rd_dev->name); + return -EPIPE; + } + + addr = offset_translate(*pos, rd_dev, &data_left); + + /* EOF check */ + if (data_left == 0) { + pr_debug("Ramdump(%s): Ramdump complete. %lld bytes read.", + rd_dev->name, *pos); + rd_dev->ramdump_status = 0; + ret = 0; + goto ramdump_done; + } + + copy_size = min(count, (size_t)MAX_IOREMAP_SIZE); + copy_size = min((unsigned long)copy_size, data_left); + device_mem = ioremap_nocache(addr, copy_size); + + if (device_mem == NULL) { + pr_err("Ramdump(%s): Unable to ioremap: addr %lx, size %x\n", + rd_dev->name, addr, copy_size); + rd_dev->ramdump_status = -1; + ret = -ENOMEM; + goto ramdump_done; + } + + if (copy_to_user(buf, device_mem, copy_size)) { + pr_err("Ramdump(%s): Couldn't copy all data to user.", + rd_dev->name); + iounmap(device_mem); + rd_dev->ramdump_status = -1; + ret = -EFAULT; + goto ramdump_done; + } + + iounmap(device_mem); + *pos += copy_size; + + pr_debug("Ramdump(%s): Read %d bytes from address %lx.", + rd_dev->name, copy_size, addr); + + return copy_size; + +ramdump_done: + rd_dev->data_ready = 0; + *pos = 0; + complete(&rd_dev->ramdump_complete); + return ret; +} + +static unsigned int ramdump_poll(struct file *filep, + struct poll_table_struct *wait) +{ + struct ramdump_device *rd_dev = container_of(filep->private_data, + struct ramdump_device, device); + unsigned int mask = 0; + + if (rd_dev->data_ready) + mask |= (POLLIN | POLLRDNORM); + + poll_wait(filep, &rd_dev->dump_wait_q, wait); + return mask; +} + +const struct file_operations ramdump_file_ops = { + .open = ramdump_open, + .release = ramdump_release, + .read = ramdump_read, + .poll = ramdump_poll +}; + +void *create_ramdump_device(const char *dev_name) +{ + int ret; + struct ramdump_device *rd_dev; + + if (!dev_name) { + pr_err("%s: Invalid device name.\n", __func__); + return NULL; + } + + rd_dev = kzalloc(sizeof(struct ramdump_device), GFP_KERNEL); + + if (!rd_dev) { + pr_err("%s: Couldn't alloc space for ramdump device!", + __func__); + return NULL; + } + + strncpy(rd_dev->name, "ramdump_", 256); + strncat(rd_dev->name, dev_name, 256); + + init_completion(&rd_dev->ramdump_complete); + + rd_dev->device.minor = MISC_DYNAMIC_MINOR; + rd_dev->device.name = rd_dev->name; + rd_dev->device.fops = &ramdump_file_ops; + + init_waitqueue_head(&rd_dev->dump_wait_q); + + ret = misc_register(&rd_dev->device); + + if (ret) { + pr_err("%s: misc_register failed for %s (%d)", __func__, + dev_name, ret); + kfree(rd_dev); + return NULL; + } + + return (void *)rd_dev; +} + +int do_ramdump(void *handle, struct ramdump_segment *segments, + int nsegments) +{ + int ret, i; + struct ramdump_device *rd_dev = (struct ramdump_device *)handle; + + if (!rd_dev->consumer_present) { + pr_err("Ramdump(%s): No consumers. Aborting..\n", rd_dev->name); + return -EPIPE; + } + + for (i = 0; i < nsegments; i++) + segments[i].size = PAGE_ALIGN(segments[i].size); + + rd_dev->segments = segments; + rd_dev->nsegments = nsegments; + + rd_dev->data_ready = 1; + rd_dev->ramdump_status = -1; + + INIT_COMPLETION(rd_dev->ramdump_complete); + + /* Tell userspace that the data is ready */ + wake_up(&rd_dev->dump_wait_q); + + /* Wait (with a timeout) to let the ramdump complete */ + ret = wait_for_completion_timeout(&rd_dev->ramdump_complete, + msecs_to_jiffies(RAMDUMP_WAIT_MSECS)); + + if (!ret) { + pr_err("Ramdump(%s): Timed out waiting for userspace.\n", + rd_dev->name); + ret = -EPIPE; + } else + ret = (rd_dev->ramdump_status == 0) ? 0 : -EPIPE; + + rd_dev->data_ready = 0; + return ret; +} diff --git a/arch/arm/mach-msm/ramdump.h b/arch/arm/mach-msm/ramdump.h new file mode 100644 index 00000000000..0b60a4464bb --- /dev/null +++ b/arch/arm/mach-msm/ramdump.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _RAMDUMP_HEADER +#define _RAMDUMP_HEADER + +struct ramdump_segment { + unsigned long address; + unsigned long size; +}; + +void *create_ramdump_device(const char *dev_name); +int do_ramdump(void *handle, struct ramdump_segment *segments, + int nsegments); + +#endif diff --git a/arch/arm/mach-msm/remote_spinlock.c b/arch/arm/mach-msm/remote_spinlock.c new file mode 100644 index 00000000000..2bb44753c1f --- /dev/null +++ b/arch/arm/mach-msm/remote_spinlock.c @@ -0,0 +1,128 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include "smd_private.h" +#include + +#define SMEM_SPINLOCK_COUNT 8 +#define SMEM_SPINLOCK_ARRAY_SIZE (SMEM_SPINLOCK_COUNT * sizeof(uint32_t)) + +static int remote_spinlock_smem_init(int id, _remote_spinlock_t *lock) +{ + _remote_spinlock_t spinlock_start; + + if (id >= SMEM_SPINLOCK_COUNT) + return -EINVAL; + + spinlock_start = smem_alloc(SMEM_SPINLOCK_ARRAY, + SMEM_SPINLOCK_ARRAY_SIZE); + if (spinlock_start == NULL) + return -ENXIO; + + *lock = spinlock_start + id; + + return 0; +} + +static int +remote_spinlock_dal_init(const char *chunk_name, _remote_spinlock_t *lock) +{ + void *dal_smem_start, *dal_smem_end; + uint32_t dal_smem_size; + struct dal_chunk_header *cur_header; + + if (!chunk_name) + return -EINVAL; + + dal_smem_start = smem_get_entry(SMEM_DAL_AREA, &dal_smem_size); + if (!dal_smem_start) + return -ENXIO; + + dal_smem_end = dal_smem_start + dal_smem_size; + + /* Find first chunk header */ + cur_header = (struct dal_chunk_header *) + (((uint32_t)dal_smem_start + (4095)) & ~4095); + *lock = NULL; + while (cur_header->size != 0 + && ((uint32_t)(cur_header + 1) < (uint32_t)dal_smem_end)) { + + /* Check if chunk name matches */ + if (!strncmp(cur_header->name, chunk_name, + DAL_CHUNK_NAME_LENGTH)) { + *lock = (_remote_spinlock_t)&cur_header->lock; + return 0; + } + cur_header = (void *)cur_header + cur_header->size; + } + + pr_err("%s: DAL remote lock \"%s\" not found.\n", __func__, + chunk_name); + return -EINVAL; +} + +int _remote_spin_lock_init(remote_spinlock_id_t id, _remote_spinlock_t *lock) +{ + BUG_ON(id == NULL); + + if (id[0] == 'D' && id[1] == ':') { + /* DAL chunk name starts after "D:" */ + return remote_spinlock_dal_init(&id[2], lock); + } else if (id[0] == 'S' && id[1] == ':') { + /* Single-digit SMEM lock ID follows "S:" */ + BUG_ON(id[3] != '\0'); + return remote_spinlock_smem_init((((uint8_t)id[2])-'0'), lock); + } else + return -EINVAL; +} + +int _remote_mutex_init(struct remote_mutex_id *id, _remote_mutex_t *lock) +{ + BUG_ON(id == NULL); + + lock->delay_us = id->delay_us; + return _remote_spin_lock_init(id->r_spinlock_id, &(lock->r_spinlock)); +} +EXPORT_SYMBOL(_remote_mutex_init); + +void _remote_mutex_lock(_remote_mutex_t *lock) +{ + while (!_remote_spin_trylock(&(lock->r_spinlock))) { + if (lock->delay_us >= 1000) + msleep(lock->delay_us/1000); + else + udelay(lock->delay_us); + } +} +EXPORT_SYMBOL(_remote_mutex_lock); + +void _remote_mutex_unlock(_remote_mutex_t *lock) +{ + _remote_spin_unlock(&(lock->r_spinlock)); +} +EXPORT_SYMBOL(_remote_mutex_unlock); + +int _remote_mutex_trylock(_remote_mutex_t *lock) +{ + return _remote_spin_trylock(&(lock->r_spinlock)); +} +EXPORT_SYMBOL(_remote_mutex_trylock); diff --git a/arch/arm/mach-msm/reset_modem.c b/arch/arm/mach-msm/reset_modem.c new file mode 100644 index 00000000000..9344af6a2ce --- /dev/null +++ b/arch/arm/mach-msm/reset_modem.c @@ -0,0 +1,183 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * MSM architecture driver to reset the modem + */ +#include +#include +#include +#include + +#include "smd_private.h" +#include "proc_comm.h" + +#define DEBUG +/* #undef DEBUG */ +#ifdef DEBUG +#define D(x...) printk(x) +#else +#define D(x...) do {} while (0) +#endif + +static ssize_t reset_modem_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + return 0; +} + +static ssize_t reset_modem_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + unsigned char cmd[64]; + int len; + int time; + int zero = 0; + int r; + + if (count < 1) + return 0; + + len = count > 63 ? 63 : count; + + if (copy_from_user(cmd, buf, len)) + return -EFAULT; + + cmd[len] = 0; + + /* lazy */ + if (cmd[len-1] == '\n') { + cmd[len-1] = 0; + len--; + } + + if (!strncmp(cmd, "wait", 4)) { + D(KERN_ERR "INFO:%s:%i:%s: " + "MODEM RESTART: WAIT\n", + __FILE__, + __LINE__, + __func__); + smsm_reset_modem(SMSM_MODEM_WAIT); + } else if (!strncmp(cmd, "continue", 8)) { + D(KERN_ERR "INFO:%s:%i:%s: " + "MODEM RESTART: CONTINUE\n", + __FILE__, + __LINE__, + __func__); + smsm_reset_modem_cont(); + } else if (!strncmp(cmd, "download", 8)) { + D(KERN_ERR "INFO:%s:%i:%s: " + "MODEM RESTART: DOWNLOAD\n", + __FILE__, + __LINE__, + __func__); + smsm_reset_modem(SMSM_SYSTEM_DOWNLOAD); + } else if (sscanf(cmd, "deferred reset %i", &time) == 1) { + D(KERN_ERR "INFO:%s:%i:%s: " + "MODEM RESTART: DEFERRED RESET %ims\n", + __FILE__, + __LINE__, + __func__, + time); + if (time == 0) { + r = 0; + msm_proc_comm_reset_modem_now(); + } else { + r = msm_proc_comm(PCOM_RESET_MODEM, &time, &zero); + } + if (r < 0) + return r; + } else if (!strncmp(cmd, "deferred reset", 14)) { + D(KERN_ERR "INFO:%s:%i:%s: " + "MODEM RESTART: DEFERRED RESET 0ms\n", + __FILE__, + __LINE__, + __func__); + r = 0; + msm_proc_comm_reset_modem_now(); + if (r < 0) + return r; + } else if (!strncmp(cmd, "reset chip now", 14)) { + uint param1 = 0x0; + uint param2 = 0x0; + + D(KERN_ERR "INFO:%s:%i:%s: " + "MODEM RESTART: CHIP RESET IMMEDIATE\n", + __FILE__, + __LINE__, + __func__); + + r = msm_proc_comm(PCOM_RESET_CHIP_IMM, ¶m1, ¶m2); + + if (r < 0) + return r; + } else if (!strncmp(cmd, "reset chip", 10)) { + + uint param1 = 0x0; + uint param2 = 0x0; + + D(KERN_ERR "INFO:%s:%i:%s: " + "MODEM RESTART: CHIP RESET \n", + __FILE__, + __LINE__, + __func__); + + r = msm_proc_comm(PCOM_RESET_CHIP, ¶m1, ¶m2); + + if (r < 0) + return r; + } else if (!strncmp(cmd, "reset", 5)) { + printk(KERN_ERR "INFO:%s:%i:%s: " + "MODEM RESTART: RESET\n", + __FILE__, + __LINE__, + __func__); + smsm_reset_modem(SMSM_RESET); + } else + return -EINVAL; + + return count; +} + +static int reset_modem_open(struct inode *ip, struct file *fp) +{ + return 0; +} + +static int reset_modem_release(struct inode *ip, struct file *fp) +{ + return 0; +} + +static const struct file_operations reset_modem_fops = { + .owner = THIS_MODULE, + .read = reset_modem_read, + .write = reset_modem_write, + .open = reset_modem_open, + .release = reset_modem_release, +}; + +static struct miscdevice reset_modem_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "reset_modem", + .fops = &reset_modem_fops, +}; + +static int __init reset_modem_init(void) +{ + return misc_register(&reset_modem_dev); +} + +module_init(reset_modem_init); + +MODULE_DESCRIPTION("Reset Modem"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/restart-fsm9xxx.c b/arch/arm/mach-msm/restart-fsm9xxx.c new file mode 100644 index 00000000000..f542368f48d --- /dev/null +++ b/arch/arm/mach-msm/restart-fsm9xxx.c @@ -0,0 +1,42 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FEMTO_GPIO_PS_HOLD 161 + +void arch_reset(char mode, const char *cmd) +{ + pr_notice("Going down for restart now\n"); + msleep(3000); + + /* Configure FEMTO_GPIO_PS_HOLD as a general purpose output */ + if (gpio_tlmm_config(GPIO_CFG(FEMTO_GPIO_PS_HOLD, 0, + GPIO_CFG_OUTPUT, GPIO_CFG_NO_PULL, + GPIO_CFG_2MA), GPIO_CFG_ENABLE)) + pr_err("%s: gpio_tlmm_config (gpio=%d) failed\n", + __func__, FEMTO_GPIO_PS_HOLD); + + /* Now set it low to power cycle the entire board */ + gpio_set_value(FEMTO_GPIO_PS_HOLD, 0); + + msleep(10000); + pr_err("Restarting has failed\n"); +} diff --git a/arch/arm/mach-msm/restart.c b/arch/arm/mach-msm/restart.c new file mode 100644 index 00000000000..1181c1e2c07 --- /dev/null +++ b/arch/arm/mach-msm/restart.c @@ -0,0 +1,196 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define TCSR_WDT_CFG 0x30 + +#define WDT0_RST (MSM_TMR0_BASE + 0x38) +#define WDT0_EN (MSM_TMR0_BASE + 0x40) +#define WDT0_BARK_TIME (MSM_TMR0_BASE + 0x4C) +#define WDT0_BITE_TIME (MSM_TMR0_BASE + 0x5C) + +#define PSHOLD_CTL_SU (MSM_TLMM_BASE + 0x820) + +#define RESTART_REASON_ADDR 0x65C +#define DLOAD_MODE_ADDR 0x0 + +static int restart_mode; +void *restart_reason; + +#ifdef CONFIG_MSM_DLOAD_MODE +static int in_panic; +static void *dload_mode_addr; + +/* Download mode master kill-switch */ +static int dload_set(const char *val, struct kernel_param *kp); +static int download_mode = 1; +module_param_call(download_mode, dload_set, param_get_int, + &download_mode, 0644); + +static int panic_prep_restart(struct notifier_block *this, + unsigned long event, void *ptr) +{ + in_panic = 1; + return NOTIFY_DONE; +} + +static struct notifier_block panic_blk = { + .notifier_call = panic_prep_restart, +}; + +static void set_dload_mode(int on) +{ + if (dload_mode_addr) { + __raw_writel(on ? 0xE47B337D : 0, dload_mode_addr); + __raw_writel(on ? 0xCE14091A : 0, + dload_mode_addr + sizeof(unsigned int)); + mb(); + } +} + +static int dload_set(const char *val, struct kernel_param *kp) +{ + int ret; + int old_val = download_mode; + + ret = param_set_int(val, kp); + + if (ret) + return ret; + + /* If download_mode is not zero or one, ignore. */ + if (download_mode >> 1) { + download_mode = old_val; + return -EINVAL; + } + + set_dload_mode(download_mode); + + return 0; +} +#else +#define set_dload_mode(x) do {} while (0) +#endif + +void msm_set_restart_mode(int mode) +{ + restart_mode = mode; +} +EXPORT_SYMBOL(msm_set_restart_mode); + +static void msm_power_off(void) +{ + printk(KERN_NOTICE "Powering off the SoC\n"); +#ifdef CONFIG_MSM_DLOAD_MODE + set_dload_mode(0); +#endif + if (cpu_is_msm8x60()) { + pm8058_reset_pwr_off(0); + pm8901_reset_pwr_off(0); + } + __raw_writel(0, PSHOLD_CTL_SU); + mdelay(10000); + printk(KERN_ERR "Powering off has failed\n"); + return; +} + +void arch_reset(char mode, const char *cmd) +{ + +#ifdef CONFIG_MSM_DLOAD_MODE + + /* This looks like a normal reboot at this point. */ + set_dload_mode(0); + + /* Write download mode flags if we're panic'ing */ + set_dload_mode(in_panic); + + /* Write download mode flags if restart_mode says so */ + if (restart_mode == RESTART_DLOAD) + set_dload_mode(1); + + /* Kill download mode if master-kill switch is set */ + if (!download_mode) + set_dload_mode(0); +#endif + + printk(KERN_NOTICE "Going down for restart now\n"); + + if (cpu_is_msm8x60()) + pm8058_reset_pwr_off(1); + + if (cmd != NULL) { + if (!strncmp(cmd, "bootloader", 10)) { + __raw_writel(0x77665500, restart_reason); + } else if (!strncmp(cmd, "recovery", 8)) { + __raw_writel(0x77665502, restart_reason); + } else if (!strncmp(cmd, "oem-", 4)) { + unsigned long code; + code = simple_strtoul(cmd + 4, NULL, 16) & 0xff; + __raw_writel(0x6f656d00 | code, restart_reason); + } else { + __raw_writel(0x77665501, restart_reason); + } + } + + __raw_writel(0, WDT0_EN); + if (!(machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa())) { + mb(); + __raw_writel(0, PSHOLD_CTL_SU); /* Actually reset the chip */ + mdelay(5000); + pr_notice("PS_HOLD didn't work, falling back to watchdog\n"); + } + + __raw_writel(1, WDT0_RST); + __raw_writel(5*0x31F3, WDT0_BARK_TIME); + __raw_writel(0x31F3, WDT0_BITE_TIME); + __raw_writel(3, WDT0_EN); + secure_writel(3, MSM_TCSR_BASE + TCSR_WDT_CFG); + + mdelay(10000); + printk(KERN_ERR "Restarting has failed\n"); +} + +static int __init msm_restart_init(void) +{ +#ifdef CONFIG_MSM_DLOAD_MODE + atomic_notifier_chain_register(&panic_notifier_list, &panic_blk); + dload_mode_addr = MSM_IMEM_BASE + DLOAD_MODE_ADDR; + + /* Reset detection is switched on below.*/ + set_dload_mode(1); +#endif + restart_reason = MSM_IMEM_BASE + RESTART_REASON_ADDR; + pm_power_off = msm_power_off; + + return 0; +} + +late_initcall(msm_restart_init); diff --git a/arch/arm/mach-msm/rfic-fsm9xxx.c b/arch/arm/mach-msm/rfic-fsm9xxx.c new file mode 100644 index 00000000000..68147c67bc8 --- /dev/null +++ b/arch/arm/mach-msm/rfic-fsm9xxx.c @@ -0,0 +1,400 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * FTR8700 RFIC + */ + +#define RFIC_FTR_DEVICE_NUM 2 +#define RFIC_GRFC_REG_NUM 6 + +#define ANY_BUS 0x0 +#define TX1_BUS 0x0 +#define TX2_BUS 0x1 +#define MISC_BUS 0x2 +#define RX_BUS 0x3 +#define BUS_BITS 0x3 + +/* + * Device private information per device node + */ + +static struct ftr_dev_node_info { + void *grfcCtrlAddr; + void *grfcMaskAddr; + unsigned int busSelect[4]; + struct i2c_adapter *ssbi_adap; + + /* lock */ + struct mutex lock; +} ftr_dev_info[RFIC_FTR_DEVICE_NUM]; + +/* + * Device private information per file + */ + +struct ftr_dev_file_info { + int ftrId; +}; + +/* + * File interface + */ + +static int ftr_find_id(int minor); + +static int ftr_open(struct inode *inode, struct file *file) +{ + struct ftr_dev_file_info *pdfi; + + /* private data allocation */ + pdfi = kmalloc(sizeof(*pdfi), GFP_KERNEL); + if (pdfi == NULL) + return -ENOMEM; + file->private_data = pdfi; + + /* FTR ID */ + pdfi->ftrId = ftr_find_id(MINOR(inode->i_rdev)); + + return 0; +} + +static int ftr_release(struct inode *inode, struct file *file) +{ + struct ftr_dev_file_info *pdfi; + + pdfi = (struct ftr_dev_file_info *) file->private_data; + + kfree(file->private_data); + file->private_data = NULL; + + return 0; +} + +static ssize_t ftr_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + return 0; +} + +static ssize_t ftr_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + return 0; +} + +static int ftr_ssbi_read( + struct ftr_dev_node_info *pdev, + unsigned int addr, + u8 *buf, + size_t len) +{ + int ret; + struct i2c_msg msg = { + .addr = addr, + .flags = I2C_M_RD, + .buf = buf, + .len = len, + }; + + ret = i2c_transfer(pdev->ssbi_adap, &msg, 1); + + return (ret == 1) ? 0 : ret; +} + +static int ftr_ssbi_write( + struct ftr_dev_node_info *pdev, + unsigned int addr, + u8 *buf, + size_t len) +{ + int ret; + struct i2c_msg msg = { + .addr = addr, + .flags = 0x0, + .buf = (u8 *) buf, + .len = len, + }; + + ret = i2c_transfer(pdev->ssbi_adap, &msg, 1); + + return (ret == 1) ? 0 : ret; +} + +static long ftr_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + unsigned int __user *argp = (unsigned int __user *) arg; + struct ftr_dev_file_info *pdfi = + (struct ftr_dev_file_info *) file->private_data; + struct ftr_dev_node_info *pdev; + + if (pdfi->ftrId < 0 || pdfi->ftrId >= RFIC_FTR_DEVICE_NUM) + return -EINVAL; + + pdev = ftr_dev_info + pdfi->ftrId; + + switch (cmd) { + case RFIC_IOCTL_IS_UMTS: + return __raw_readl(MSM_TCSR_BASE + 0x0008) & 0x01; + + case RFIC_IOCTL_READ_REGISTER: + { + int ret; + unsigned int rficAddr; + u8 value; + + if (get_user(rficAddr, argp)) + return -EFAULT; + + mutex_lock(&pdev->lock); + mb(); + /* Need to write twice due to bug in hardware */ + __raw_writel( + pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)], + pdev->grfcCtrlAddr); + __raw_writel( + pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)], + pdev->grfcCtrlAddr); + mb(); + ret = ftr_ssbi_read(pdev, RFIC_FTR_GET_ADDR(rficAddr), + &value, 1); + mutex_unlock(&pdev->lock); + + if (ret) + return ret; + + if (put_user(value, argp)) + return -EFAULT; + } + break; + + case RFIC_IOCTL_WRITE_REGISTER: + { + int ret; + struct rfic_write_register_param param; + unsigned int rficAddr; + u8 value; + + if (copy_from_user(¶m, argp, sizeof param)) + return -EFAULT; + rficAddr = param.rficAddr; + value = (u8) param.value; + + mutex_lock(&pdev->lock); + mb(); + /* Need to write twice due to bug in hardware */ + __raw_writel( + pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)], + pdev->grfcCtrlAddr); + __raw_writel( + pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)], + pdev->grfcCtrlAddr); + mb(); + ret = ftr_ssbi_write(pdev, RFIC_FTR_GET_ADDR(rficAddr), + &value, 1); + mutex_unlock(&pdev->lock); + + if (ret) + return ret; + } + break; + + case RFIC_IOCTL_WRITE_REGISTER_WITH_MASK: + { + int ret; + struct rfic_write_register_mask_param param; + unsigned int rficAddr; + u8 value; + + if (copy_from_user(¶m, argp, sizeof param)) + return -EFAULT; + rficAddr = param.rficAddr; + + mutex_lock(&pdev->lock); + mb(); + /* Need to write twice due to bug in hardware */ + __raw_writel( + pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)], + pdev->grfcCtrlAddr); + __raw_writel( + pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)], + pdev->grfcCtrlAddr); + mb(); + ret = ftr_ssbi_read(pdev, RFIC_FTR_GET_ADDR(rficAddr), + &value, 1); + value &= (u8) ~param.mask; + value |= (u8) (param.value & param.mask); + ret = ftr_ssbi_write(pdev, RFIC_FTR_GET_ADDR(rficAddr), + &value, 1); + mutex_unlock(&pdev->lock); + + if (ret) + return ret; + } + break; + + case RFIC_IOCTL_GET_GRFC: + { + struct rfic_grfc_param param; + + if (copy_from_user(¶m, argp, sizeof param)) + return -EFAULT; + + if (param.grfcId >= RFIC_GRFC_REG_NUM) + return -EINVAL; + + param.maskValue = __raw_readl( + MSM_GRFC_BASE + 0x18 + param.grfcId * 4); + param.ctrlValue = __raw_readl( + MSM_GRFC_BASE + 0x00 + param.grfcId * 4); + + if (copy_to_user(argp, ¶m, sizeof param)) + return -EFAULT; + } + break; + + case RFIC_IOCTL_SET_GRFC: + { + struct rfic_grfc_param param; + + if (copy_from_user(¶m, argp, sizeof param)) + return -EFAULT; + + if (param.grfcId >= RFIC_GRFC_REG_NUM) + return -EINVAL; + + __raw_writel(param.maskValue, + MSM_GRFC_BASE + 0x18 + param.grfcId * 4); + /* Need to write twice due to bug in hardware */ + __raw_writel(param.ctrlValue, + MSM_GRFC_BASE + 0x00 + param.grfcId * 4); + __raw_writel(param.ctrlValue, + MSM_GRFC_BASE + 0x00 + param.grfcId * 4); + mb(); + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +static const struct file_operations ftr_fops = { + .owner = THIS_MODULE, + .open = ftr_open, + .release = ftr_release, + .read = ftr_read, + .write = ftr_write, + .unlocked_ioctl = ftr_ioctl, +}; + +/* + * Driver initialization & cleanup + */ + +struct miscdevice ftr_misc_dev[RFIC_FTR_DEVICE_NUM] = { + { + .minor = MISC_DYNAMIC_MINOR, + .name = RFIC_FTR_DEVICE_NAME "0", + .fops = &ftr_fops, + }, + { + .minor = MISC_DYNAMIC_MINOR, + .name = RFIC_FTR_DEVICE_NAME "1", + .fops = &ftr_fops, + }, +}; + +int ftr_find_id(int minor) +{ + int i; + + for (i = 0; i < RFIC_FTR_DEVICE_NUM; ++i) + if (ftr_misc_dev[i].minor == minor) + break; + + return i; +} + +static int __init ftr_init(void) +{ + int i, ret; + struct ftr_dev_node_info *pdev; + + for (i = 0; i < RFIC_FTR_DEVICE_NUM; ++i) { + pdev = ftr_dev_info + i; + + if (i == 0) { + pdev->grfcCtrlAddr = MSM_GRFC_BASE + 0x00; + pdev->grfcMaskAddr = MSM_GRFC_BASE + 0x18; + __raw_writel(0x300000, pdev->grfcMaskAddr); + pdev->busSelect[TX1_BUS] = 0x000000; + pdev->busSelect[TX2_BUS] = 0x100000; + pdev->busSelect[MISC_BUS] = 0x200000; + pdev->busSelect[RX_BUS] = 0x300000; + pdev->ssbi_adap = i2c_get_adapter(1); + } else { + pdev->grfcCtrlAddr = MSM_GRFC_BASE + 0x04; + pdev->grfcMaskAddr = MSM_GRFC_BASE + 0x1c; + __raw_writel(0x480000, pdev->grfcMaskAddr); + pdev->busSelect[TX1_BUS] = 0x000000; + pdev->busSelect[TX2_BUS] = 0x400000; + pdev->busSelect[MISC_BUS] = 0x080000; + pdev->busSelect[RX_BUS] = 0x480000; + pdev->ssbi_adap = i2c_get_adapter(2); + } + + mutex_init(&pdev->lock); + ret = misc_register(ftr_misc_dev + i); + + if (ret < 0) { + while (--i >= 0) + misc_deregister(ftr_misc_dev + i); + return ret; + } + } + + return 0; +} + +static void __exit ftr_exit(void) +{ + int i; + + for (i = 0; i < RFIC_FTR_DEVICE_NUM; ++i) + misc_deregister(ftr_misc_dev + i); +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Rohit Vaswani "); +MODULE_DESCRIPTION("Qualcomm FSM RFIC driver"); +MODULE_VERSION("1.0"); + +module_init(ftr_init); +module_exit(ftr_exit); diff --git a/arch/arm/mach-msm/rmt_storage_client.c b/arch/arm/mach-msm/rmt_storage_client.c new file mode 100644 index 00000000000..b71c1c00b97 --- /dev/null +++ b/arch/arm/mach-msm/rmt_storage_client.c @@ -0,0 +1,1753 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_MSM_SDIO_SMEM +#include +#endif +#include "smd_private.h" + +enum { + RMT_STORAGE_EVNT_OPEN = 0, + RMT_STORAGE_EVNT_CLOSE, + RMT_STORAGE_EVNT_WRITE_BLOCK, + RMT_STORAGE_EVNT_GET_DEV_ERROR, + RMT_STORAGE_EVNT_WRITE_IOVEC, + RMT_STORAGE_EVNT_SEND_USER_DATA, + RMT_STORAGE_EVNT_READ_IOVEC, + RMT_STORAGE_EVNT_ALLOC_RMT_BUF, +} rmt_storage_event; + +struct shared_ramfs_entry { + uint32_t client_id; /* Client id to uniquely identify a client */ + uint32_t base_addr; /* Base address of shared RAMFS memory */ + uint32_t size; /* Size of the shared RAMFS memory */ + uint32_t client_sts; /* This will be initialized to 1 when + remote storage RPC client is ready + to process requests */ +}; +struct shared_ramfs_table { + uint32_t magic_id; /* Identify RAMFS details in SMEM */ + uint32_t version; /* Version of shared_ramfs_table */ + uint32_t entries; /* Total number of valid entries */ + /* List all entries */ + struct shared_ramfs_entry ramfs_entry[MAX_RAMFS_TBL_ENTRIES]; +}; + +struct rmt_storage_client_info { + unsigned long cids; + struct list_head shrd_mem_list; /* List of shared memory entries */ + int open_excl; + atomic_t total_events; + wait_queue_head_t event_q; + struct list_head event_list; + struct list_head client_list; /* List of remote storage clients */ + /* Lock to protect lists */ + spinlock_t lock; + /* Wakelock to be acquired when processing requests from modem */ + struct wake_lock wlock; + atomic_t wcount; + struct workqueue_struct *workq; +}; + +struct rmt_storage_kevent { + struct list_head list; + struct rmt_storage_event event; +}; + +/* Remote storage server on modem */ +struct rmt_storage_srv { + uint32_t prog; + int sync_token; + struct platform_driver plat_drv; + struct msm_rpc_client *rpc_client; + struct delayed_work restart_work; +}; + +/* Remote storage client on modem */ +struct rmt_storage_client { + uint32_t handle; + uint32_t sid; /* Storage ID */ + char path[MAX_PATH_NAME]; + struct rmt_storage_srv *srv; + struct list_head list; +}; + +struct rmt_shrd_mem { + struct list_head list; + struct rmt_shrd_mem_param param; + struct shared_ramfs_entry *smem_info; + struct rmt_storage_srv *srv; +}; + +static struct rmt_storage_srv *rmt_storage_get_srv(uint32_t prog); +static uint32_t rmt_storage_get_sid(const char *path); +#ifdef CONFIG_MSM_SDIO_SMEM +static void rmt_storage_sdio_smem_work(struct work_struct *work); +#endif + +static struct rmt_storage_client_info *rmc; + +#ifdef CONFIG_MSM_SDIO_SMEM +DECLARE_DELAYED_WORK(sdio_smem_work, rmt_storage_sdio_smem_work); +#endif + +#ifdef CONFIG_MSM_SDIO_SMEM +#define MDM_LOCAL_BUF_SZ 0xC0000 +static struct sdio_smem_client *sdio_smem; +#endif + +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS +struct rmt_storage_op_stats { + unsigned long count; + ktime_t start; + ktime_t min; + ktime_t max; + ktime_t total; +}; +struct rmt_storage_stats { + char path[MAX_PATH_NAME]; + struct rmt_storage_op_stats rd_stats; + struct rmt_storage_op_stats wr_stats; +}; +static struct rmt_storage_stats client_stats[MAX_NUM_CLIENTS]; +static struct dentry *stats_dentry; +#endif + +#define MSM_RMT_STORAGE_APIPROG 0x300000A7 +#define MDM_RMT_STORAGE_APIPROG 0x300100A7 + +#define RMT_STORAGE_OP_FINISH_PROC 2 +#define RMT_STORAGE_REGISTER_OPEN_PROC 3 +#define RMT_STORAGE_REGISTER_WRITE_IOVEC_PROC 4 +#define RMT_STORAGE_REGISTER_CB_PROC 5 +#define RMT_STORAGE_UN_REGISTER_CB_PROC 6 +#define RMT_STORAGE_FORCE_SYNC_PROC 7 +#define RMT_STORAGE_GET_SYNC_STATUS_PROC 8 +#define RMT_STORAGE_REGISTER_READ_IOVEC_PROC 9 +#define RMT_STORAGE_REGISTER_ALLOC_RMT_BUF_PROC 10 + +#define RMT_STORAGE_OPEN_CB_TYPE_PROC 1 +#define RMT_STORAGE_WRITE_IOVEC_CB_TYPE_PROC 2 +#define RMT_STORAGE_EVENT_CB_TYPE_PROC 3 +#define RMT_STORAGE_READ_IOVEC_CB_TYPE_PROC 4 +#define RMT_STORAGE_ALLOC_RMT_BUF_CB_TYPE_PROC 5 + +#define RAMFS_INFO_MAGICNUMBER 0x654D4D43 +#define RAMFS_INFO_VERSION 0x00000001 +#define RAMFS_DEFAULT 0xFFFFFFFF + +/* MSM EFS*/ +#define RAMFS_MODEMSTORAGE_ID 0x4D454653 +#define RAMFS_SHARED_EFS_RAM_BASE 0x46100000 +#define RAMFS_SHARED_EFS_RAM_SIZE (3 * 1024 * 1024) + +/* MDM EFS*/ +#define RAMFS_MDM_STORAGE_ID 0x4D4583A1 +/* SSD */ +#define RAMFS_SSD_STORAGE_ID 0x00535344 +#define RAMFS_SHARED_SSD_RAM_BASE 0x42E00000 +#define RAMFS_SHARED_SSD_RAM_SIZE 0x2000 + +static struct rmt_storage_client *rmt_storage_get_client(uint32_t handle) +{ + struct rmt_storage_client *rs_client; + list_for_each_entry(rs_client, &rmc->client_list, list) + if (rs_client->handle == handle) + return rs_client; + return NULL; +} + +static struct rmt_storage_client * +rmt_storage_get_client_by_path(const char *path) +{ + struct rmt_storage_client *rs_client; + list_for_each_entry(rs_client, &rmc->client_list, list) + if (!strncmp(path, rs_client->path, MAX_PATH_NAME)) + return rs_client; + return NULL; +} + +static struct rmt_shrd_mem_param *rmt_storage_get_shrd_mem(uint32_t sid) +{ + struct rmt_shrd_mem *shrd_mem; + struct rmt_shrd_mem_param *shrd_mem_param = NULL; + + spin_lock(&rmc->lock); + list_for_each_entry(shrd_mem, &rmc->shrd_mem_list, list) + if (shrd_mem->param.sid == sid) + shrd_mem_param = &shrd_mem->param; + spin_unlock(&rmc->lock); + + return shrd_mem_param; +} + +static int rmt_storage_add_shrd_mem(uint32_t sid, uint32_t start, + uint32_t size, void *base, + struct shared_ramfs_entry *smem_info, + struct rmt_storage_srv *srv) +{ + struct rmt_shrd_mem *shrd_mem; + + shrd_mem = kzalloc(sizeof(struct rmt_shrd_mem), GFP_KERNEL); + if (!shrd_mem) + return -ENOMEM; + shrd_mem->param.sid = sid; + shrd_mem->param.start = start; + shrd_mem->param.size = size; + shrd_mem->param.base = base; + shrd_mem->smem_info = smem_info; + shrd_mem->srv = srv; + + spin_lock(&rmc->lock); + list_add(&shrd_mem->list, &rmc->shrd_mem_list); + spin_unlock(&rmc->lock); + return 0; +} + +static struct msm_rpc_client *rmt_storage_get_rpc_client(uint32_t handle) +{ + struct rmt_storage_client *rs_client; + + rs_client = rmt_storage_get_client(handle); + if (!rs_client) + return NULL; + return rs_client->srv->rpc_client; +} + +static int rmt_storage_validate_iovec(uint32_t handle, + struct rmt_storage_iovec_desc *xfer) +{ + struct rmt_storage_client *rs_client; + struct rmt_shrd_mem_param *shrd_mem; + + rs_client = rmt_storage_get_client(handle); + if (!rs_client) + return -EINVAL; + shrd_mem = rmt_storage_get_shrd_mem(rs_client->sid); + if (!shrd_mem) + return -EINVAL; + + if ((xfer->data_phy_addr < shrd_mem->start) || + ((xfer->data_phy_addr + RAMFS_BLOCK_SIZE * xfer->num_sector) > + (shrd_mem->start + shrd_mem->size))) + return -EINVAL; + return 0; +} + +static int rmt_storage_send_sts_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct rmt_storage_send_sts *args = data; + + xdr_send_uint32(xdr, &args->handle); + xdr_send_uint32(xdr, &args->err_code); + xdr_send_uint32(xdr, &args->data); + return 0; +} + +static void put_event(struct rmt_storage_client_info *rmc, + struct rmt_storage_kevent *kevent) +{ + spin_lock(&rmc->lock); + list_add_tail(&kevent->list, &rmc->event_list); + spin_unlock(&rmc->lock); +} + +static struct rmt_storage_kevent *get_event(struct rmt_storage_client_info *rmc) +{ + struct rmt_storage_kevent *kevent = NULL; + + spin_lock(&rmc->lock); + if (!list_empty(&rmc->event_list)) { + kevent = list_first_entry(&rmc->event_list, + struct rmt_storage_kevent, list); + list_del(&kevent->list); + } + spin_unlock(&rmc->lock); + return kevent; +} + +static int rmt_storage_event_open_cb(struct rmt_storage_event *event_args, + struct msm_rpc_xdr *xdr) +{ + uint32_t cid, len, event_type; + char *path; + int ret; + struct rmt_storage_srv *srv; + struct rmt_storage_client *rs_client = NULL; +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS + struct rmt_storage_stats *stats; +#endif + + srv = rmt_storage_get_srv(event_args->usr_data); + if (!srv) + return -EINVAL; + + xdr_recv_uint32(xdr, &event_type); + if (event_type != RMT_STORAGE_EVNT_OPEN) + return -1; + + pr_info("%s: open callback received\n", __func__); + + ret = xdr_recv_bytes(xdr, (void **)&path, &len); + if (ret || !path) { + pr_err("%s: Invalid path\n", __func__); + if (!ret) + ret = -1; + goto free_rs_client; + } + + rs_client = rmt_storage_get_client_by_path(path); + if (rs_client) { + pr_debug("%s: Handle %d found for %s\n", + __func__, rs_client->handle, path); + event_args->id = RMT_STORAGE_NOOP; + cid = rs_client->handle; + goto end_open_cb; + } + + rs_client = kzalloc(sizeof(struct rmt_storage_client), GFP_KERNEL); + if (!rs_client) { + pr_err("%s: Error allocating rmt storage client\n", __func__); + ret = -ENOMEM; + goto free_path; + } + + memcpy(event_args->path, path, len); + rs_client->sid = rmt_storage_get_sid(event_args->path); + if (!rs_client->sid) { + pr_err("%s: No storage id found for %s\n", __func__, + event_args->path); + ret = -EINVAL; + goto free_path; + } + strncpy(rs_client->path, event_args->path, MAX_PATH_NAME); + + cid = find_first_zero_bit(&rmc->cids, sizeof(rmc->cids) * 8); + if (cid > MAX_NUM_CLIENTS) { + pr_err("%s: Max clients are reached\n", __func__); + cid = 0; + return cid; + } + __set_bit(cid, &rmc->cids); + pr_info("open partition %s handle=%d\n", event_args->path, cid); + +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS + stats = &client_stats[cid - 1]; + memcpy(stats->path, event_args->path, len); + memset(stats->rd_stats, 0, sizeof(struct rmt_storage_op_stats)); + memset(stats->wr_stats, 0, sizeof(struct rmt_storage_op_stats)); + stats->rd_stats.min.tv64 = KTIME_MAX; + stats->wr_stats.min.tv64 = KTIME_MAX; +#endif + event_args->id = RMT_STORAGE_OPEN; + event_args->sid = rs_client->sid; + event_args->handle = cid; + + rs_client->handle = event_args->handle; + rs_client->srv = srv; + INIT_LIST_HEAD(&rs_client->list); + spin_lock(&rmc->lock); + list_add_tail(&rs_client->list, &rmc->client_list); + spin_unlock(&rmc->lock); + +end_open_cb: + kfree(path); + return cid; + +free_path: + kfree(path); +free_rs_client: + kfree(rs_client); + return ret; +} + +struct rmt_storage_close_args { + uint32_t handle; +}; + +struct rmt_storage_rw_block_args { + uint32_t handle; + uint32_t data_phy_addr; + uint32_t sector_addr; + uint32_t num_sector; +}; + +struct rmt_storage_get_err_args { + uint32_t handle; +}; + +struct rmt_storage_user_data_args { + uint32_t handle; + uint32_t data; +}; + +struct rmt_storage_event_params { + uint32_t type; + union { + struct rmt_storage_close_args close; + struct rmt_storage_rw_block_args block; + struct rmt_storage_get_err_args get_err; + struct rmt_storage_user_data_args user_data; + } params; +}; + +static int rmt_storage_parse_params(struct msm_rpc_xdr *xdr, + struct rmt_storage_event_params *event) +{ + xdr_recv_uint32(xdr, &event->type); + + switch (event->type) { + case RMT_STORAGE_EVNT_CLOSE: { + struct rmt_storage_close_args *args; + args = &event->params.close; + + xdr_recv_uint32(xdr, &args->handle); + break; + } + + case RMT_STORAGE_EVNT_WRITE_BLOCK: { + struct rmt_storage_rw_block_args *args; + args = &event->params.block; + + xdr_recv_uint32(xdr, &args->handle); + xdr_recv_uint32(xdr, &args->data_phy_addr); + xdr_recv_uint32(xdr, &args->sector_addr); + xdr_recv_uint32(xdr, &args->num_sector); + break; + } + + case RMT_STORAGE_EVNT_GET_DEV_ERROR: { + struct rmt_storage_get_err_args *args; + args = &event->params.get_err; + + xdr_recv_uint32(xdr, &args->handle); + break; + } + + case RMT_STORAGE_EVNT_SEND_USER_DATA: { + struct rmt_storage_user_data_args *args; + args = &event->params.user_data; + + xdr_recv_uint32(xdr, &args->handle); + xdr_recv_uint32(xdr, &args->data); + break; + } + + default: + pr_err("%s: unknown event %d\n", __func__, event->type); + return -1; + } + return 0; +} + +static int rmt_storage_event_close_cb(struct rmt_storage_event *event_args, + struct msm_rpc_xdr *xdr) +{ + struct rmt_storage_event_params *event; + struct rmt_storage_close_args *close; + struct rmt_storage_client *rs_client; + uint32_t event_type; + int ret; + + xdr_recv_uint32(xdr, &event_type); + if (event_type != RMT_STORAGE_EVNT_CLOSE) + return -1; + + pr_debug("%s: close callback received\n", __func__); + ret = xdr_recv_pointer(xdr, (void **)&event, + sizeof(struct rmt_storage_event_params), + rmt_storage_parse_params); + + if (ret || !event) + return -1; + + close = &event->params.close; + event_args->handle = close->handle; + event_args->id = RMT_STORAGE_CLOSE; + __clear_bit(event_args->handle, &rmc->cids); + rs_client = rmt_storage_get_client(event_args->handle); + if (rs_client) { + list_del(&rs_client->list); + kfree(rs_client); + } + kfree(event); + return RMT_STORAGE_NO_ERROR; +} + +static int rmt_storage_event_write_block_cb( + struct rmt_storage_event *event_args, + struct msm_rpc_xdr *xdr) +{ + struct rmt_storage_event_params *event; + struct rmt_storage_rw_block_args *write_block; + struct rmt_storage_iovec_desc *xfer; + uint32_t event_type; + int ret; + + xdr_recv_uint32(xdr, &event_type); + if (event_type != RMT_STORAGE_EVNT_WRITE_BLOCK) + return -1; + + pr_debug("%s: write block callback received\n", __func__); + ret = xdr_recv_pointer(xdr, (void **)&event, + sizeof(struct rmt_storage_event_params), + rmt_storage_parse_params); + + if (ret || !event) + return -1; + + write_block = &event->params.block; + event_args->handle = write_block->handle; + xfer = &event_args->xfer_desc[0]; + xfer->sector_addr = write_block->sector_addr; + xfer->data_phy_addr = write_block->data_phy_addr; + xfer->num_sector = write_block->num_sector; + + ret = rmt_storage_validate_iovec(event_args->handle, xfer); + if (ret) + return -1; + event_args->xfer_cnt = 1; + event_args->id = RMT_STORAGE_WRITE; + + if (atomic_inc_return(&rmc->wcount) == 1) + wake_lock(&rmc->wlock); + + pr_debug("sec_addr = %u, data_addr = %x, num_sec = %d\n\n", + xfer->sector_addr, xfer->data_phy_addr, + xfer->num_sector); + + kfree(event); + return RMT_STORAGE_NO_ERROR; +} + +static int rmt_storage_event_get_err_cb(struct rmt_storage_event *event_args, + struct msm_rpc_xdr *xdr) +{ + struct rmt_storage_event_params *event; + struct rmt_storage_get_err_args *get_err; + uint32_t event_type; + int ret; + + xdr_recv_uint32(xdr, &event_type); + if (event_type != RMT_STORAGE_EVNT_GET_DEV_ERROR) + return -1; + + pr_debug("%s: get err callback received\n", __func__); + ret = xdr_recv_pointer(xdr, (void **)&event, + sizeof(struct rmt_storage_event_params), + rmt_storage_parse_params); + + if (ret || !event) + return -1; + + get_err = &event->params.get_err; + event_args->handle = get_err->handle; + kfree(event); + /* Not implemented */ + return -1; + +} + +static int rmt_storage_event_user_data_cb(struct rmt_storage_event *event_args, + struct msm_rpc_xdr *xdr) +{ + struct rmt_storage_event_params *event; + struct rmt_storage_user_data_args *user_data; + uint32_t event_type; + int ret; + + xdr_recv_uint32(xdr, &event_type); + if (event_type != RMT_STORAGE_EVNT_SEND_USER_DATA) + return -1; + + pr_info("%s: send user data callback received\n", __func__); + ret = xdr_recv_pointer(xdr, (void **)&event, + sizeof(struct rmt_storage_event_params), + rmt_storage_parse_params); + + if (ret || !event) + return -1; + + user_data = &event->params.user_data; + event_args->handle = user_data->handle; + event_args->usr_data = user_data->data; + event_args->id = RMT_STORAGE_SEND_USER_DATA; + + kfree(event); + return RMT_STORAGE_NO_ERROR; +} + +static int rmt_storage_event_write_iovec_cb( + struct rmt_storage_event *event_args, + struct msm_rpc_xdr *xdr) +{ + struct rmt_storage_iovec_desc *xfer; + uint32_t i, ent, event_type; +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS + struct rmt_storage_stats *stats; +#endif + + xdr_recv_uint32(xdr, &event_type); + if (event_type != RMT_STORAGE_EVNT_WRITE_IOVEC) + return -EINVAL; + + pr_info("%s: write iovec callback received\n", __func__); + xdr_recv_uint32(xdr, &event_args->handle); + xdr_recv_uint32(xdr, &ent); + pr_debug("handle = %d\n", event_args->handle); + +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS + stats = &client_stats[event_args->handle - 1]; + stats->wr_stats.start = ktime_get(); +#endif + for (i = 0; i < ent; i++) { + xfer = &event_args->xfer_desc[i]; + xdr_recv_uint32(xdr, &xfer->sector_addr); + xdr_recv_uint32(xdr, &xfer->data_phy_addr); + xdr_recv_uint32(xdr, &xfer->num_sector); + + if (rmt_storage_validate_iovec(event_args->handle, xfer)) + return -EINVAL; + + pr_debug("sec_addr = %u, data_addr = %x, num_sec = %d\n", + xfer->sector_addr, xfer->data_phy_addr, + xfer->num_sector); + } + xdr_recv_uint32(xdr, &event_args->xfer_cnt); + event_args->id = RMT_STORAGE_WRITE; + if (atomic_inc_return(&rmc->wcount) == 1) + wake_lock(&rmc->wlock); + + pr_debug("iovec transfer count = %d\n\n", event_args->xfer_cnt); + return RMT_STORAGE_NO_ERROR; +} + +static int rmt_storage_event_read_iovec_cb( + struct rmt_storage_event *event_args, + struct msm_rpc_xdr *xdr) +{ + struct rmt_storage_iovec_desc *xfer; + uint32_t i, ent, event_type; +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS + struct rmt_storage_stats *stats; +#endif + + xdr_recv_uint32(xdr, &event_type); + if (event_type != RMT_STORAGE_EVNT_READ_IOVEC) + return -EINVAL; + + pr_info("%s: read iovec callback received\n", __func__); + xdr_recv_uint32(xdr, &event_args->handle); + xdr_recv_uint32(xdr, &ent); + pr_debug("handle = %d\n", event_args->handle); + +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS + stats = &client_stats[event_args->handle - 1]; + stats->rd_stats.start = ktime_get(); +#endif + for (i = 0; i < ent; i++) { + xfer = &event_args->xfer_desc[i]; + xdr_recv_uint32(xdr, &xfer->sector_addr); + xdr_recv_uint32(xdr, &xfer->data_phy_addr); + xdr_recv_uint32(xdr, &xfer->num_sector); + + if (rmt_storage_validate_iovec(event_args->handle, xfer)) + return -EINVAL; + + pr_debug("sec_addr = %u, data_addr = %x, num_sec = %d\n", + xfer->sector_addr, xfer->data_phy_addr, + xfer->num_sector); + } + xdr_recv_uint32(xdr, &event_args->xfer_cnt); + event_args->id = RMT_STORAGE_READ; + if (atomic_inc_return(&rmc->wcount) == 1) + wake_lock(&rmc->wlock); + + pr_debug("iovec transfer count = %d\n\n", event_args->xfer_cnt); + return RMT_STORAGE_NO_ERROR; +} + +#ifdef CONFIG_MSM_SDIO_SMEM +static int sdio_smem_cb(int event) +{ + pr_debug("%s: Received event %d\n", __func__, event); + + switch (event) { + case SDIO_SMEM_EVENT_READ_DONE: + pr_debug("Read done\n"); + break; + case SDIO_SMEM_EVENT_READ_ERR: + pr_err("Read overflow\n"); + return -EIO; + default: + pr_err("Unhandled event\n"); + } + return 0; +} + +static int rmt_storage_sdio_smem_probe(struct platform_device *pdev) +{ + int ret = 0; + struct rmt_shrd_mem_param *shrd_mem; + + sdio_smem = container_of(pdev, struct sdio_smem_client, plat_dev); + + /* SDIO SMEM is supported only for MDM */ + shrd_mem = rmt_storage_get_shrd_mem(RAMFS_MDM_STORAGE_ID); + if (!shrd_mem) { + pr_err("%s: No shared mem entry for sid=0x%08x\n", + __func__, (uint32_t)RAMFS_MDM_STORAGE_ID); + return -ENOMEM; + } + sdio_smem->buf = __va(shrd_mem->start); + sdio_smem->size = shrd_mem->size; + sdio_smem->cb_func = sdio_smem_cb; + ret = sdio_smem_register_client(); + if (ret) + pr_info("%s: Error (%d) registering sdio_smem client\n", + __func__, ret); + return ret; +} + +static int rmt_storage_sdio_smem_remove(struct platform_device *pdev) +{ + sdio_smem_unregister_client(); + queue_delayed_work(rmc->workq, &sdio_smem_work, 0); + return 0; +} + +static int sdio_smem_drv_registered; +static struct platform_driver sdio_smem_drv = { + .probe = rmt_storage_sdio_smem_probe, + .remove = rmt_storage_sdio_smem_remove, + .driver = { + .name = "SDIO_SMEM_CLIENT", + .owner = THIS_MODULE, + }, +}; + +static void rmt_storage_sdio_smem_work(struct work_struct *work) +{ + platform_driver_unregister(&sdio_smem_drv); + sdio_smem_drv_registered = 0; +} +#endif + +static int rmt_storage_event_alloc_rmt_buf_cb( + struct rmt_storage_event *event_args, + struct msm_rpc_xdr *xdr) +{ + struct rmt_storage_client *rs_client; + struct rmt_shrd_mem_param *shrd_mem; + uint32_t event_type, handle, size; +#ifdef CONFIG_MSM_SDIO_SMEM + int ret; +#endif + xdr_recv_uint32(xdr, &event_type); + if (event_type != RMT_STORAGE_EVNT_ALLOC_RMT_BUF) + return -EINVAL; + + pr_info("%s: Alloc rmt buf callback received\n", __func__); + xdr_recv_uint32(xdr, &handle); + xdr_recv_uint32(xdr, &size); + + pr_debug("%s: handle=0x%x size=0x%x\n", __func__, handle, size); + + rs_client = rmt_storage_get_client(handle); + if (!rs_client) { + pr_err("%s: Unable to find client for handle=%d\n", + __func__, handle); + return -EINVAL; + } + + rs_client->sid = rmt_storage_get_sid(rs_client->path); + if (!rs_client->sid) { + pr_err("%s: No storage id found for %s\n", + __func__, rs_client->path); + return -EINVAL; + } + + shrd_mem = rmt_storage_get_shrd_mem(rs_client->sid); + if (!shrd_mem) { + pr_err("%s: No shared memory entry found\n", + __func__); + return -ENOMEM; + } + if (shrd_mem->size < size) { + pr_err("%s: Size mismatch for handle=%d\n", + __func__, rs_client->handle); + return -EINVAL; + } + pr_debug("%s: %d bytes at phys=0x%x for handle=%d found\n", + __func__, size, shrd_mem->start, rs_client->handle); + +#ifdef CONFIG_MSM_SDIO_SMEM + if (rs_client->srv->prog == MDM_RMT_STORAGE_APIPROG) { + if (!sdio_smem_drv_registered) { + ret = platform_driver_register(&sdio_smem_drv); + if (!ret) + sdio_smem_drv_registered = 1; + else + pr_err("%s: Cant register sdio smem client\n", + __func__); + } + } +#endif + event_args->id = RMT_STORAGE_NOOP; + return (int)shrd_mem->start; +} + +static int handle_rmt_storage_call(struct msm_rpc_client *client, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + int rc; + uint32_t result = RMT_STORAGE_NO_ERROR; + uint32_t rpc_status = RPC_ACCEPTSTAT_SUCCESS; + struct rmt_storage_event *event_args; + struct rmt_storage_kevent *kevent; + + kevent = kzalloc(sizeof(struct rmt_storage_kevent), GFP_KERNEL); + if (!kevent) { + rpc_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + goto out; + } + event_args = &kevent->event; + + switch (req->procedure) { + case RMT_STORAGE_OPEN_CB_TYPE_PROC: + /* client created in cb needs a ref. to its server */ + event_args->usr_data = client->prog; + /* fall through */ + + case RMT_STORAGE_WRITE_IOVEC_CB_TYPE_PROC: + /* fall through */ + + case RMT_STORAGE_READ_IOVEC_CB_TYPE_PROC: + /* fall through */ + + case RMT_STORAGE_ALLOC_RMT_BUF_CB_TYPE_PROC: + /* fall through */ + + case RMT_STORAGE_EVENT_CB_TYPE_PROC: { + uint32_t cb_id; + int (*cb_func)(struct rmt_storage_event *event_args, + struct msm_rpc_xdr *xdr); + + xdr_recv_uint32(xdr, &cb_id); + cb_func = msm_rpc_get_cb_func(client, cb_id); + + if (!cb_func) { + rpc_status = RPC_ACCEPTSTAT_GARBAGE_ARGS; + kfree(kevent); + goto out; + } + + rc = cb_func(event_args, xdr); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: Invalid parameters received\n", __func__); + if (req->procedure == RMT_STORAGE_OPEN_CB_TYPE_PROC) + result = 0; /* bad handle to signify err */ + else + result = RMT_STORAGE_ERROR_PARAM; + kfree(kevent); + goto out; + } + result = (uint32_t) rc; + break; + } + + default: + kfree(kevent); + pr_err("%s: unknown procedure %d\n", __func__, req->procedure); + rpc_status = RPC_ACCEPTSTAT_PROC_UNAVAIL; + goto out; + } + + if (kevent->event.id != RMT_STORAGE_NOOP) { + put_event(rmc, kevent); + atomic_inc(&rmc->total_events); + wake_up(&rmc->event_q); + } else + kfree(kevent); + +out: + pr_debug("%s: Sending result=0x%x\n", __func__, result); + xdr_start_accepted_reply(xdr, rpc_status); + xdr_send_uint32(xdr, &result); + rc = xdr_send_msg(xdr); + if (rc) + pr_err("%s: send accepted reply failed: %d\n", __func__, rc); + + return rc; +} + +static int rmt_storage_open(struct inode *ip, struct file *fp) +{ + int ret = 0; + + spin_lock(&rmc->lock); + if (!rmc->open_excl) + rmc->open_excl = 1; + else + ret = -EBUSY; + spin_unlock(&rmc->lock); + + return ret; +} + +static int rmt_storage_release(struct inode *ip, struct file *fp) +{ + spin_lock(&rmc->lock); + rmc->open_excl = 0; + spin_unlock(&rmc->lock); + + return 0; +} + +static long rmt_storage_ioctl(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct rmt_storage_kevent *kevent; + struct rmt_storage_send_sts status; + static struct msm_rpc_client *rpc_client; + struct rmt_shrd_mem_param usr_shrd_mem, *shrd_mem; + +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS + struct rmt_storage_stats *stats; + struct rmt_storage_op_stats *op_stats; + ktime_t curr_stat; +#endif + + switch (cmd) { + + case RMT_STORAGE_SHRD_MEM_PARAM: + pr_debug("%s: get shared memory parameters ioctl\n", __func__); + if (copy_from_user(&usr_shrd_mem, (void __user *)arg, + sizeof(struct rmt_shrd_mem_param))) { + pr_err("%s: copy from user failed\n\n", __func__); + ret = -EFAULT; + break; + } + + shrd_mem = rmt_storage_get_shrd_mem(usr_shrd_mem.sid); + if (!shrd_mem) { + pr_err("%s: invalid sid (0x%x)\n", __func__, + usr_shrd_mem.sid); + ret = -EFAULT; + break; + } + + if (copy_to_user((void __user *)arg, shrd_mem, + sizeof(struct rmt_shrd_mem_param))) { + pr_err("%s: copy to user failed\n\n", __func__); + ret = -EFAULT; + } + break; + + case RMT_STORAGE_WAIT_FOR_REQ: + pr_debug("%s: wait for request ioctl\n", __func__); + if (atomic_read(&rmc->total_events) == 0) { + ret = wait_event_interruptible(rmc->event_q, + atomic_read(&rmc->total_events) != 0); + } + if (ret < 0) + break; + atomic_dec(&rmc->total_events); + + kevent = get_event(rmc); + WARN_ON(kevent == NULL); + if (copy_to_user((void __user *)arg, &kevent->event, + sizeof(struct rmt_storage_event))) { + pr_err("%s: copy to user failed\n\n", __func__); + ret = -EFAULT; + } + kfree(kevent); + break; + + case RMT_STORAGE_SEND_STATUS: + pr_info("%s: send status ioctl\n", __func__); + if (copy_from_user(&status, (void __user *)arg, + sizeof(struct rmt_storage_send_sts))) { + pr_err("%s: copy from user failed\n\n", __func__); + ret = -EFAULT; + if (atomic_dec_return(&rmc->wcount) == 0) + wake_unlock(&rmc->wlock); + break; + } +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS + stats = &client_stats[status.handle - 1]; + if (status.xfer_dir == RMT_STORAGE_WRITE) + op_stats = &stats->wr_stats; + else + op_stats = &stats->rd_stats; + curr_stat = ktime_sub(ktime_get(), op_stats->start); + op_stats->total = ktime_add(op_stats->total, curr_stat); + op_stats->count++; + if (curr_stat.tv64 < stats->min.tv64) + op_stats->min = curr_stat; + if (curr_stat.tv64 > stats->max.tv64) + op_stats->max = curr_stat; +#endif + pr_debug("%s: \thandle=%d err_code=%d data=0x%x\n", __func__, + status.handle, status.err_code, status.data); + rpc_client = rmt_storage_get_rpc_client(status.handle); + if (rpc_client) + ret = msm_rpc_client_req2(rpc_client, + RMT_STORAGE_OP_FINISH_PROC, + rmt_storage_send_sts_arg, + &status, NULL, NULL, -1); + else + ret = -EINVAL; + if (ret < 0) + pr_err("%s: send status failed with ret val = %d\n", + __func__, ret); + if (atomic_dec_return(&rmc->wcount) == 0) + wake_unlock(&rmc->wlock); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +struct rmt_storage_sync_recv_arg { + int data; +}; + +static int rmt_storage_receive_sync_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct rmt_storage_sync_recv_arg *args = data; + struct rmt_storage_srv *srv; + + srv = rmt_storage_get_srv(client->prog); + if (!srv) + return -EINVAL; + xdr_recv_int32(xdr, &args->data); + srv->sync_token = args->data; + return 0; +} + +static int rmt_storage_force_sync(struct msm_rpc_client *client) +{ + struct rmt_storage_sync_recv_arg args; + int rc; + rc = msm_rpc_client_req2(client, + RMT_STORAGE_FORCE_SYNC_PROC, NULL, NULL, + rmt_storage_receive_sync_arg, &args, -1); + if (rc) { + pr_err("%s: force sync RPC req failed: %d\n", __func__, rc); + return rc; + } + return 0; +} + +struct rmt_storage_sync_sts_arg { + int token; +}; + +static int rmt_storage_send_sync_sts_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct rmt_storage_sync_sts_arg *req = data; + + xdr_send_int32(xdr, &req->token); + return 0; +} + +static int rmt_storage_receive_sync_sts_arg(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct rmt_storage_sync_recv_arg *args = data; + + xdr_recv_int32(xdr, &args->data); + return 0; +} + +static int rmt_storage_get_sync_status(struct msm_rpc_client *client) +{ + struct rmt_storage_sync_recv_arg recv_args; + struct rmt_storage_sync_sts_arg send_args; + struct rmt_storage_srv *srv; + int rc; + + srv = rmt_storage_get_srv(client->prog); + if (!srv) + return -EINVAL; + + if (srv->sync_token < 0) + return -EINVAL; + + send_args.token = srv->sync_token; + rc = msm_rpc_client_req2(client, + RMT_STORAGE_GET_SYNC_STATUS_PROC, + rmt_storage_send_sync_sts_arg, &send_args, + rmt_storage_receive_sync_sts_arg, &recv_args, -1); + if (rc) { + pr_err("%s: sync status RPC req failed: %d\n", __func__, rc); + return rc; + } + return recv_args.data; +} + +static int rmt_storage_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long vsize = vma->vm_end - vma->vm_start; + int ret = -EINVAL; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + ret = io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vsize, vma->vm_page_prot); + if (ret < 0) + pr_err("%s: failed with return val %d\n", __func__, ret); + return ret; +} + +struct rmt_storage_reg_cb_args { + uint32_t event; + uint32_t cb_id; +}; + +static int rmt_storage_arg_cb(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct rmt_storage_reg_cb_args *args = data; + + xdr_send_uint32(xdr, &args->event); + xdr_send_uint32(xdr, &args->cb_id); + return 0; +} + +static int rmt_storage_reg_cb(struct msm_rpc_client *client, + uint32_t proc, uint32_t event, void *callback) +{ + struct rmt_storage_reg_cb_args args; + int rc, cb_id; + int retries = 10; + + cb_id = msm_rpc_add_cb_func(client, callback); + if ((cb_id < 0) && (cb_id != MSM_RPC_CLIENT_NULL_CB_ID)) + return cb_id; + + args.event = event; + args.cb_id = cb_id; + + while (retries) { + rc = msm_rpc_client_req2(client, proc, rmt_storage_arg_cb, + &args, NULL, NULL, -1); + if (rc != -ETIMEDOUT) + break; + retries--; + udelay(1000); + } + if (rc) + pr_err("%s: Failed to register callback for event %d\n", + __func__, event); + return rc; +} + +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS +static int rmt_storage_stats_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t rmt_storage_stats_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + uint32_t tot_clients; + char buf[512]; + int max, j, i = 0; + struct rmt_storage_stats *stats; + + max = sizeof(buf) - 1; + tot_clients = find_first_zero_bit(&rmc->cids, sizeof(rmc->cids)) - 1; + + for (j = 0; j < tot_clients; j++) { + stats = &client_stats[j]; + i += scnprintf(buf + i, max - i, "stats for partition %s:\n", + stats->path); + i += scnprintf(buf + i, max - i, "Min read time: %lld us\n", + ktime_to_us(stats->rd_stats.min)); + i += scnprintf(buf + i, max - i, "Max read time: %lld us\n", + ktime_to_us(stats->rd_stats.max)); + i += scnprintf(buf + i, max - i, "Total read time: %lld us\n", + ktime_to_us(stats->rd_stats.total)); + i += scnprintf(buf + i, max - i, "Total read requests: %ld\n", + stats->rd_stats.count); + if (stats->count) + i += scnprintf(buf + i, max - i, + "Avg read time: %lld us\n", + div_s64(ktime_to_us(stats->total), + stats->rd_stats.count)); + + i += scnprintf(buf + i, max - i, "Min write time: %lld us\n", + ktime_to_us(stats->wr_stats.min)); + i += scnprintf(buf + i, max - i, "Max write time: %lld us\n", + ktime_to_us(stats->wr_stats.max)); + i += scnprintf(buf + i, max - i, "Total write time: %lld us\n", + ktime_to_us(stats->wr_stats.total)); + i += scnprintf(buf + i, max - i, "Total read requests: %ld\n", + stats->wr_stats.count); + if (stats->count) + i += scnprintf(buf + i, max - i, + "Avg write time: %lld us\n", + div_s64(ktime_to_us(stats->total), + stats->wr_stats.count)); + } + return simple_read_from_buffer(ubuf, count, ppos, buf, i); +} + +static const struct file_operations debug_ops = { + .owner = THIS_MODULE, + .open = rmt_storage_stats_open, + .read = rmt_storage_stats_read, +}; +#endif + +const struct file_operations rmt_storage_fops = { + .owner = THIS_MODULE, + .open = rmt_storage_open, + .unlocked_ioctl = rmt_storage_ioctl, + .mmap = rmt_storage_mmap, + .release = rmt_storage_release, +}; + +static struct miscdevice rmt_storage_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "rmt_storage", + .fops = &rmt_storage_fops, +}; + +static int rmt_storage_get_ramfs(struct rmt_storage_srv *srv) +{ + struct shared_ramfs_table *ramfs_table; + struct shared_ramfs_entry *ramfs_entry; + int index, ret; + + if (srv->prog != MSM_RMT_STORAGE_APIPROG) + return 0; + + ramfs_table = smem_alloc(SMEM_SEFS_INFO, + sizeof(struct shared_ramfs_table)); + + if (!ramfs_table) { + pr_err("%s: No RAMFS table in SMEM\n", __func__); + return -ENOENT; + } + + if ((ramfs_table->magic_id != (u32) RAMFS_INFO_MAGICNUMBER) || + (ramfs_table->version != (u32) RAMFS_INFO_VERSION)) { + pr_err("%s: Magic / Version mismatch:, " + "magic_id=%#x, format_version=%#x\n", __func__, + ramfs_table->magic_id, ramfs_table->version); + return -ENOENT; + } + + for (index = 0; index < ramfs_table->entries; index++) { + ramfs_entry = &ramfs_table->ramfs_entry[index]; + if (!ramfs_entry->client_id || + ramfs_entry->client_id == (u32) RAMFS_DEFAULT) + break; + + pr_info("%s: RAMFS entry: addr = 0x%08x, size = 0x%08x\n", + __func__, ramfs_entry->base_addr, ramfs_entry->size); + + ret = rmt_storage_add_shrd_mem(ramfs_entry->client_id, + ramfs_entry->base_addr, + ramfs_entry->size, + NULL, + ramfs_entry, + srv); + if (ret) { + pr_err("%s: Error (%d) adding shared mem\n", + __func__, ret); + return ret; + } + } + return 0; +} + +static ssize_t +set_force_sync(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev; + struct rpcsvr_platform_device *rpc_pdev; + struct rmt_storage_srv *srv; + int value, rc; + + pdev = container_of(dev, struct platform_device, dev); + rpc_pdev = container_of(pdev, struct rpcsvr_platform_device, base); + srv = rmt_storage_get_srv(rpc_pdev->prog); + if (!srv) { + pr_err("%s: Unable to find prog=0x%x\n", __func__, + rpc_pdev->prog); + return -EINVAL; + } + + sscanf(buf, "%d", &value); + if (!!value) { + rc = rmt_storage_force_sync(srv->rpc_client); + if (rc) + return rc; + } + return count; +} + +/* Returns -EINVAL for invalid sync token and an error value for any failure + * in RPC call. Upon success, it returns a sync status of 1 (sync done) + * or 0 (sync still pending). + */ +static ssize_t +show_sync_sts(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev; + struct rpcsvr_platform_device *rpc_pdev; + struct rmt_storage_srv *srv; + + pdev = container_of(dev, struct platform_device, dev); + rpc_pdev = container_of(pdev, struct rpcsvr_platform_device, base); + srv = rmt_storage_get_srv(rpc_pdev->prog); + if (!srv) { + pr_err("%s: Unable to find prog=0x%x\n", __func__, + rpc_pdev->prog); + return -EINVAL; + } + return snprintf(buf, PAGE_SIZE, "%d\n", + rmt_storage_get_sync_status(srv->rpc_client)); +} + +static int rmt_storage_init_ramfs(struct rmt_storage_srv *srv) +{ + struct shared_ramfs_table *ramfs_table; + + if (srv->prog != MSM_RMT_STORAGE_APIPROG) + return 0; + + ramfs_table = smem_alloc(SMEM_SEFS_INFO, + sizeof(struct shared_ramfs_table)); + + if (!ramfs_table) { + pr_err("%s: No RAMFS table in SMEM\n", __func__); + return -ENOENT; + } + + if (ramfs_table->magic_id == RAMFS_INFO_MAGICNUMBER) { + pr_debug("RAMFS table already filled... skipping %s", \ + __func__); + return 0; + } + + ramfs_table->ramfs_entry[0].client_id = RAMFS_MODEMSTORAGE_ID; + ramfs_table->ramfs_entry[0].base_addr = RAMFS_SHARED_EFS_RAM_BASE; + ramfs_table->ramfs_entry[0].size = RAMFS_SHARED_EFS_RAM_SIZE; + ramfs_table->ramfs_entry[0].client_sts = RAMFS_DEFAULT; + + ramfs_table->ramfs_entry[1].client_id = RAMFS_SSD_STORAGE_ID; + ramfs_table->ramfs_entry[1].base_addr = RAMFS_SHARED_SSD_RAM_BASE; + ramfs_table->ramfs_entry[1].size = RAMFS_SHARED_SSD_RAM_SIZE; + ramfs_table->ramfs_entry[1].client_sts = RAMFS_DEFAULT; + + ramfs_table->entries = 2; + ramfs_table->version = RAMFS_INFO_VERSION; + ramfs_table->magic_id = RAMFS_INFO_MAGICNUMBER; + + return 0; +} + +static void rmt_storage_set_client_status(struct rmt_storage_srv *srv, + int enable) +{ + struct rmt_shrd_mem *shrd_mem; + + spin_lock(&rmc->lock); + list_for_each_entry(shrd_mem, &rmc->shrd_mem_list, list) + if (shrd_mem->srv->prog == srv->prog) + if (shrd_mem->smem_info) + shrd_mem->smem_info->client_sts = !!enable; + spin_unlock(&rmc->lock); +} + +static DEVICE_ATTR(force_sync, S_IRUGO | S_IWUSR, NULL, set_force_sync); +static DEVICE_ATTR(sync_sts, S_IRUGO | S_IWUSR, show_sync_sts, NULL); +static struct attribute *dev_attrs[] = { + &dev_attr_force_sync.attr, + &dev_attr_sync_sts.attr, + NULL, +}; +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, +}; + +static void handle_restart_teardown(struct msm_rpc_client *client) +{ + struct rmt_storage_srv *srv; + + srv = rmt_storage_get_srv(client->prog); + if (!srv) + return; + pr_debug("%s: Modem restart for 0x%08x\n", __func__, srv->prog); + cancel_delayed_work_sync(&srv->restart_work); +} + +#define RESTART_WORK_DELAY_MS 1000 + +static void handle_restart_setup(struct msm_rpc_client *client) +{ + struct rmt_storage_srv *srv; + + srv = rmt_storage_get_srv(client->prog); + if (!srv) + return; + pr_debug("%s: Scheduling restart for 0x%08x\n", __func__, srv->prog); + queue_delayed_work(rmc->workq, &srv->restart_work, + msecs_to_jiffies(RESTART_WORK_DELAY_MS)); +} + +static int rmt_storage_reg_callbacks(struct msm_rpc_client *client) +{ + int ret; + + ret = rmt_storage_reg_cb(client, + RMT_STORAGE_REGISTER_OPEN_PROC, + RMT_STORAGE_EVNT_OPEN, + rmt_storage_event_open_cb); + if (ret) + return ret; + ret = rmt_storage_reg_cb(client, + RMT_STORAGE_REGISTER_CB_PROC, + RMT_STORAGE_EVNT_CLOSE, + rmt_storage_event_close_cb); + if (ret) + return ret; + ret = rmt_storage_reg_cb(client, + RMT_STORAGE_REGISTER_CB_PROC, + RMT_STORAGE_EVNT_WRITE_BLOCK, + rmt_storage_event_write_block_cb); + if (ret) + return ret; + ret = rmt_storage_reg_cb(client, + RMT_STORAGE_REGISTER_CB_PROC, + RMT_STORAGE_EVNT_GET_DEV_ERROR, + rmt_storage_event_get_err_cb); + if (ret) + return ret; + ret = rmt_storage_reg_cb(client, + RMT_STORAGE_REGISTER_WRITE_IOVEC_PROC, + RMT_STORAGE_EVNT_WRITE_IOVEC, + rmt_storage_event_write_iovec_cb); + if (ret) + return ret; + ret = rmt_storage_reg_cb(client, + RMT_STORAGE_REGISTER_READ_IOVEC_PROC, + RMT_STORAGE_EVNT_READ_IOVEC, + rmt_storage_event_read_iovec_cb); + if (ret) + return ret; + ret = rmt_storage_reg_cb(client, + RMT_STORAGE_REGISTER_CB_PROC, + RMT_STORAGE_EVNT_SEND_USER_DATA, + rmt_storage_event_user_data_cb); + if (ret) + return ret; + ret = rmt_storage_reg_cb(client, + RMT_STORAGE_REGISTER_ALLOC_RMT_BUF_PROC, + RMT_STORAGE_EVNT_ALLOC_RMT_BUF, + rmt_storage_event_alloc_rmt_buf_cb); + if (ret) + pr_info("%s: Unable (%d) registering aloc_rmt_buf\n", + __func__, ret); + + pr_debug("%s: Callbacks (re)registered for 0x%08x\n\n", __func__, + client->prog); + return 0; +} + +static void rmt_storage_restart_work(struct work_struct *work) +{ + struct rmt_storage_srv *srv; + int ret; + + srv = container_of((struct delayed_work *)work, + struct rmt_storage_srv, restart_work); + if (!rmt_storage_get_srv(srv->prog)) { + pr_err("%s: Invalid server\n", __func__); + return; + } + + ret = rmt_storage_reg_callbacks(srv->rpc_client); + if (!ret) + return; + + pr_err("%s: Error (%d) re-registering callbacks for0x%08x\n", + __func__, ret, srv->prog); + + if (!msm_rpc_client_in_reset(srv->rpc_client)) + queue_delayed_work(rmc->workq, &srv->restart_work, + msecs_to_jiffies(RESTART_WORK_DELAY_MS)); +} + +static int rmt_storage_probe(struct platform_device *pdev) +{ + struct rpcsvr_platform_device *dev; + struct rmt_storage_srv *srv; + int ret; + + dev = container_of(pdev, struct rpcsvr_platform_device, base); + srv = rmt_storage_get_srv(dev->prog); + if (!srv) { + pr_err("%s: Invalid prog = %#x\n", __func__, dev->prog); + return -ENXIO; + } + + rmt_storage_init_ramfs(srv); + rmt_storage_get_ramfs(srv); + + INIT_DELAYED_WORK(&srv->restart_work, rmt_storage_restart_work); + + /* Client Registration */ + srv->rpc_client = msm_rpc_register_client2("rmt_storage", + dev->prog, dev->vers, 1, + handle_rmt_storage_call); + if (IS_ERR(srv->rpc_client)) { + pr_err("%s: Unable to register client (prog %.8x vers %.8x)\n", + __func__, dev->prog, dev->vers); + ret = PTR_ERR(srv->rpc_client); + return ret; + } + + ret = msm_rpc_register_reset_callbacks(srv->rpc_client, + handle_restart_teardown, + handle_restart_setup); + if (ret) + goto unregister_client; + + pr_info("%s: Remote storage RPC client (0x%x)initialized\n", + __func__, dev->prog); + + /* register server callbacks */ + ret = rmt_storage_reg_callbacks(srv->rpc_client); + if (ret) + goto unregister_client; + + /* For targets that poll SMEM, set status to ready */ + rmt_storage_set_client_status(srv, 1); + + ret = sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp); + if (ret) + pr_err("%s: Failed to create sysfs node: %d\n", __func__, ret); + + return 0; + +unregister_client: + msm_rpc_unregister_client(srv->rpc_client); + return ret; +} + +static void rmt_storage_client_shutdown(struct platform_device *pdev) +{ + struct rpcsvr_platform_device *dev; + struct rmt_storage_srv *srv; + + dev = container_of(pdev, struct rpcsvr_platform_device, base); + srv = rmt_storage_get_srv(dev->prog); + rmt_storage_set_client_status(srv, 0); +} + +static void rmt_storage_destroy_rmc(void) +{ + wake_lock_destroy(&rmc->wlock); +} + +static void __init rmt_storage_init_client_info(void) +{ + /* Initialization */ + init_waitqueue_head(&rmc->event_q); + spin_lock_init(&rmc->lock); + atomic_set(&rmc->total_events, 0); + INIT_LIST_HEAD(&rmc->event_list); + INIT_LIST_HEAD(&rmc->client_list); + INIT_LIST_HEAD(&rmc->shrd_mem_list); + /* The client expects a non-zero return value for + * its open requests. Hence reserve 0 bit. */ + __set_bit(0, &rmc->cids); + atomic_set(&rmc->wcount, 0); + wake_lock_init(&rmc->wlock, WAKE_LOCK_SUSPEND, "rmt_storage"); +} + +static struct rmt_storage_srv msm_srv = { + .prog = MSM_RMT_STORAGE_APIPROG, + .plat_drv = { + .probe = rmt_storage_probe, + .shutdown = rmt_storage_client_shutdown, + .driver = { + .name = "rs300000a7", + .owner = THIS_MODULE, + }, + }, +}; + +static struct rmt_storage_srv mdm_srv = { + .prog = MDM_RMT_STORAGE_APIPROG, + .plat_drv = { + .probe = rmt_storage_probe, + .shutdown = rmt_storage_client_shutdown, + .driver = { + .name = "rs300100a7", + .owner = THIS_MODULE, + }, + }, +}; + +static struct rmt_storage_srv *rmt_storage_get_srv(uint32_t prog) +{ + if (prog == MSM_RMT_STORAGE_APIPROG) + return &msm_srv; + if (prog == MDM_RMT_STORAGE_APIPROG) + return &mdm_srv; + return NULL; +} + + +static uint32_t rmt_storage_get_sid(const char *path) +{ + if (!strncmp(path, "/boot/modem_fs1", MAX_PATH_NAME)) + return RAMFS_MODEMSTORAGE_ID; + if (!strncmp(path, "/boot/modem_fs2", MAX_PATH_NAME)) + return RAMFS_MODEMSTORAGE_ID; + if (!strncmp(path, "/boot/modem_fsg", MAX_PATH_NAME)) + return RAMFS_MODEMSTORAGE_ID; + if (!strncmp(path, "/q6_fs1_parti_id_0x59", MAX_PATH_NAME)) + return RAMFS_MDM_STORAGE_ID; + if (!strncmp(path, "/q6_fs2_parti_id_0x5A", MAX_PATH_NAME)) + return RAMFS_MDM_STORAGE_ID; + if (!strncmp(path, "/q6_fsg_parti_id_0x5B", MAX_PATH_NAME)) + return RAMFS_MDM_STORAGE_ID; + if (!strncmp(path, "ssd", MAX_PATH_NAME)) + return RAMFS_SSD_STORAGE_ID; + return 0; +} + +static int __init rmt_storage_init(void) +{ +#ifdef CONFIG_MSM_SDIO_SMEM + void *mdm_local_buf; +#endif + int ret = 0; + + rmc = kzalloc(sizeof(struct rmt_storage_client_info), GFP_KERNEL); + if (!rmc) { + pr_err("%s: Unable to allocate memory\n", __func__); + return -ENOMEM; + } + rmt_storage_init_client_info(); + + ret = platform_driver_register(&msm_srv.plat_drv); + if (ret) { + pr_err("%s: Unable to register MSM RPC driver\n", __func__); + goto rmc_free; + } + + ret = platform_driver_register(&mdm_srv.plat_drv); + if (ret) { + pr_err("%s: Unable to register MDM RPC driver\n", __func__); + goto unreg_msm_rpc; + } + + ret = misc_register(&rmt_storage_device); + if (ret) { + pr_err("%s: Unable to register misc device %d\n", __func__, + MISC_DYNAMIC_MINOR); + goto unreg_mdm_rpc; + } + +#ifdef CONFIG_MSM_SDIO_SMEM + mdm_local_buf = kzalloc(MDM_LOCAL_BUF_SZ, GFP_KERNEL); + if (!mdm_local_buf) { + pr_err("%s: Unable to allocate shadow mem\n", __func__); + ret = -ENOMEM; + goto unreg_misc; + } + + ret = rmt_storage_add_shrd_mem(RAMFS_MDM_STORAGE_ID, + __pa(mdm_local_buf), + MDM_LOCAL_BUF_SZ, + NULL, NULL, &mdm_srv); + if (ret) { + pr_err("%s: Unable to add shadow mem entry\n", __func__); + goto free_mdm_local_buf; + } + + pr_debug("%s: Shadow memory at %p (phys=%lx), %d bytes\n", __func__, + mdm_local_buf, __pa(mdm_local_buf), MDM_LOCAL_BUF_SZ); +#endif + + rmc->workq = create_singlethread_workqueue("rmt_storage"); + if (!rmc->workq) + return -ENOMEM; + +#ifdef CONFIG_MSM_RMT_STORAGE_CLIENT_STATS + stats_dentry = debugfs_create_file("rmt_storage_stats", 0444, 0, + NULL, &debug_ops); + if (!stats_dentry) + pr_err("%s: Failed to create stats debugfs file\n", __func__); +#endif + return 0; + +#ifdef CONFIG_MSM_SDIO_SMEM +free_mdm_local_buf: + kfree(mdm_local_buf); +unreg_misc: + misc_deregister(&rmt_storage_device); +#endif +unreg_mdm_rpc: + platform_driver_unregister(&mdm_srv.plat_drv); +unreg_msm_rpc: + platform_driver_unregister(&msm_srv.plat_drv); +rmc_free: + rmt_storage_destroy_rmc(); + kfree(rmc); + return ret; +} + +module_init(rmt_storage_init); +MODULE_DESCRIPTION("Remote Storage RPC Client"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/rpc_dog_keepalive.c b/arch/arm/mach-msm/rpc_dog_keepalive.c new file mode 100644 index 00000000000..609b1258bfb --- /dev/null +++ b/arch/arm/mach-msm/rpc_dog_keepalive.c @@ -0,0 +1,240 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * DOG KEEPALIVE RPC CLIENT MODULE + */ + +#include +#include +#include +#include +#include + +#define DOG_KEEPALIVE_PROG 0x300000A2 +#define DOG_KEEPALIVE_VERS 0x00010001 + +#define DOG_KEEPALIVE_REGISTER_PROC 2 +#define DOG_KEEPALIVE_UNREGISTER_PROC 3 + +#define DOG_KEEPALIVE_CB_PROC 1 + +static int dog_keepalive_debug; +module_param_named(debug, dog_keepalive_debug, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#if defined(DEBUG) +#define DBG(x...) do { \ + if (dog_keepalive_debug) \ + printk(KERN_INFO x); \ + } while (0) +#else +#define DBG(x...) do { } while (0) +#endif + +static struct msm_rpc_client *dog_keepalive_rpc_client; +static int32_t dog_clnt_id = -1; + +struct dog_keepalive_cb_arg { + uint32_t cb_id; +}; + +struct dog_keepalive_cb_ret { + uint32_t result; +}; + +static int dog_keepalive_cb(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr) +{ + int rc; + void *cb_func; + uint32_t accept_status; + struct dog_keepalive_cb_arg arg; + struct dog_keepalive_cb_ret ret; + + xdr_recv_uint32(xdr, &arg.cb_id); /* cb_id */ + + cb_func = msm_rpc_get_cb_func(client, arg.cb_id); + if (cb_func) { + rc = ((int (*) + (struct dog_keepalive_cb_arg *, + struct dog_keepalive_cb_ret *)) + cb_func)(&arg, &ret); + if (rc) + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + else + accept_status = RPC_ACCEPTSTAT_SUCCESS; + } else + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + + xdr_start_accepted_reply(xdr, accept_status); + + if (accept_status == RPC_ACCEPTSTAT_SUCCESS) + xdr_send_uint32(xdr, &ret.result); /* result */ + + rc = xdr_send_msg(xdr); + if (rc) + pr_err("%s: send accepted reply failed: %d\n", __func__, rc); + + return rc; +} + +static int dog_keepalive_cb_func(struct msm_rpc_client *client, + struct rpc_request_hdr *req, + struct msm_rpc_xdr *xdr) +{ + int rc = 0; + + switch (req->procedure) { + case DOG_KEEPALIVE_CB_PROC: + rc = dog_keepalive_cb(client, xdr); + break; + default: + pr_err("%s: procedure not supported %d\n", + __func__, req->procedure); + xdr_start_accepted_reply(xdr, RPC_ACCEPTSTAT_PROC_UNAVAIL); + rc = xdr_send_msg(xdr); + if (rc) + pr_err("%s: sending reply failed: %d\n", __func__, rc); + break; + } + return rc; +} + +struct dog_keepalive_register_arg { + int (*cb_func)( + struct dog_keepalive_cb_arg *arg, + struct dog_keepalive_cb_ret *ret); + uint32_t response_msec; + uint32_t clnt_id_valid; +}; + +struct dog_keepalive_register_ret { + uint32_t *clnt_id; + uint32_t result; +}; + +static int dog_keepalive_register_arg_func(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct dog_keepalive_register_arg *arg = data; + int cb_id; + + /* cb_func */ + cb_id = msm_rpc_add_cb_func(client, (void *)arg->cb_func); + if ((cb_id < 0) && (cb_id != MSM_RPC_CLIENT_NULL_CB_ID)) + return cb_id; + + xdr_send_uint32(xdr, &cb_id); + xdr_send_uint32(xdr, &arg->response_msec); /* response_msec */ + xdr_send_uint32(xdr, &arg->clnt_id_valid); /* clnt_id valid */ + return 0; +} + +static int dog_keepalive_register_ret_func(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data) +{ + struct dog_keepalive_register_ret *ret = data; + + /* clnt_id */ + xdr_recv_pointer(xdr, (void **)&(ret->clnt_id), sizeof(uint32_t), + xdr_recv_uint32); + + /* result */ + xdr_recv_uint32(xdr, &ret->result); + return 0; +} + +static int dog_keepalive_register_func(struct msm_rpc_client *client, + struct dog_keepalive_register_arg *arg, + struct dog_keepalive_register_ret *ret) +{ + return msm_rpc_client_req2(client, + DOG_KEEPALIVE_REGISTER_PROC, + dog_keepalive_register_arg_func, arg, + dog_keepalive_register_ret_func, ret, -1); +} + +static int dog_keepalive_cb_proc_func(struct dog_keepalive_cb_arg *arg, + struct dog_keepalive_cb_ret *ret) +{ + DBG("%s: received, client %d \n", __func__, dog_clnt_id); + ret->result = 1; + return 0; +} + +static void dog_keepalive_register(void) +{ + struct dog_keepalive_register_arg arg; + struct dog_keepalive_register_ret ret; + int rc; + + arg.cb_func = dog_keepalive_cb_proc_func; + arg.response_msec = 1000; + arg.clnt_id_valid = 1; + ret.clnt_id = NULL; + rc = dog_keepalive_register_func(dog_keepalive_rpc_client, + &arg, &ret); + if (rc) + pr_err("%s: register request failed\n", __func__); + else + dog_clnt_id = *ret.clnt_id; + + kfree(ret.clnt_id); + DBG("%s: register complete\n", __func__); +} + +/* Registration with the platform driver for notification on the availability + * of the DOG_KEEPALIVE remote server + */ +static int dog_keepalive_init_probe(struct platform_device *pdev) +{ + DBG("%s: probe called\n", __func__); + dog_keepalive_rpc_client = msm_rpc_register_client2( + "dog-keepalive", + DOG_KEEPALIVE_PROG, + DOG_KEEPALIVE_VERS, + 0, dog_keepalive_cb_func); + + if (IS_ERR(dog_keepalive_rpc_client)) { + pr_err("%s: RPC client creation failed\n", __func__); + return PTR_ERR(dog_keepalive_rpc_client); + } + + /* Send RPC call to register for callbacks */ + dog_keepalive_register(); + + return 0; +} + +static char dog_keepalive_driver_name[] = "rs00000000"; + +static struct platform_driver dog_keepalive_init_driver = { + .probe = dog_keepalive_init_probe, + .driver = { + .owner = THIS_MODULE, + }, +}; + +static int __init rpc_dog_keepalive_init(void) +{ + snprintf(dog_keepalive_driver_name, sizeof(dog_keepalive_driver_name), + "rs%08x", DOG_KEEPALIVE_PROG); + dog_keepalive_init_driver.driver.name = dog_keepalive_driver_name; + + return platform_driver_register(&dog_keepalive_init_driver); +} + +late_initcall(rpc_dog_keepalive_init); +MODULE_DESCRIPTION("DOG KEEPALIVE RPC CLIENT"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/rpc_fsusb.c b/arch/arm/mach-msm/rpc_fsusb.c new file mode 100644 index 00000000000..1fd33de0a0b --- /dev/null +++ b/arch/arm/mach-msm/rpc_fsusb.c @@ -0,0 +1,243 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include + +#define PM_APP_OTG_PROG 0x30000080 +#define PM_APP_OTG_VERS 0x00010001 + +#define PM_APP_OTG_INIT_PHY 17 +#define PM_APP_OTG_RESET_PHY 18 +#define PM_APP_OTG_SUSPEND_PHY 7 +#define PM_APP_OTG_RESUME_PHY 8 +#define PM_APP_OTG_DEV_DISCONNECTED 9 +#define PM_APP_OTG_SET_WAKEUP 10 +#define PM_APP_OTG_ACQUIRE_BUS 3 +#define PM_APP_OTG_RELINQUISH_BUS 4 + +#define PM_APP_OTG_INIT_DONE_CB_PROC 1 +#define PM_APP_OTG_HOST_INIT_CB_PROC 3 +#define PM_APP_OTG_REMOTE_DEV_LOST_CB_PROC 8 +#define PM_APP_OTG_REMOTE_DEV_RESUMED_CB_PROC 9 +#define PM_APP_OTG_ERROR_NOTIFY_CB_PROC 11 + +#define NUM_OF_CALLBACKS 11 +static struct msm_rpc_client *client; +static struct msm_otg_ops *host_ops; + +static int msm_fsusb_rpc_arg(struct msm_rpc_client *client, + void *buf, void *data) +{ + int i, size = 0; + uint32_t proc = *(uint32_t *)data; + + switch (proc) { + case PM_APP_OTG_INIT_PHY: { + for (i = 0; i < NUM_OF_CALLBACKS; i++) { + *((uint32_t *)buf) = cpu_to_be32(0x11111111); + size += sizeof(uint32_t); + buf += sizeof(uint32_t); + } + + /* sleep_assert callback fucntion will be registered locally*/ + *((uint32_t *)buf) = cpu_to_be32(0xffffffff); + size += sizeof(uint32_t); + break; + } + case PM_APP_OTG_SET_WAKEUP: { + *((uint32_t *)buf) = cpu_to_be32(1); + size += sizeof(uint32_t); + break; + } + case PM_APP_OTG_ACQUIRE_BUS: { + *((uint32_t *)buf) = cpu_to_be32(0xffffffff); + size += sizeof(uint32_t); + break; + } + default: + pr_info("%s: No arguments expected\n", __func__); + } + return size; +} + +int msm_fsusb_init_phy(void) +{ + uint32_t data = PM_APP_OTG_INIT_PHY; + + return msm_rpc_client_req(client, + PM_APP_OTG_INIT_PHY, + msm_fsusb_rpc_arg, &data, + NULL, NULL, -1); +} +EXPORT_SYMBOL(msm_fsusb_init_phy); + +int msm_fsusb_reset_phy(void) +{ + return msm_rpc_client_req(client, + PM_APP_OTG_RESET_PHY, + NULL, NULL, + NULL, NULL, -1); + +} +EXPORT_SYMBOL(msm_fsusb_reset_phy); + +int msm_fsusb_suspend_phy(void) +{ + return msm_rpc_client_req(client, + PM_APP_OTG_SUSPEND_PHY, + NULL, NULL, + NULL, NULL, -1); + +} +EXPORT_SYMBOL(msm_fsusb_suspend_phy); + +int msm_fsusb_resume_phy(void) +{ + return msm_rpc_client_req(client, + PM_APP_OTG_RESUME_PHY, + NULL, NULL, + NULL, NULL, -1); + +} +EXPORT_SYMBOL(msm_fsusb_resume_phy); + +int msm_fsusb_remote_dev_disconnected(void) +{ + return msm_rpc_client_req(client, + PM_APP_OTG_DEV_DISCONNECTED, + NULL, NULL, + NULL, NULL, -1); + +} +EXPORT_SYMBOL(msm_fsusb_remote_dev_disconnected); + +int msm_fsusb_set_remote_wakeup(void) +{ + uint32_t data = PM_APP_OTG_SET_WAKEUP; + + return msm_rpc_client_req(client, + PM_APP_OTG_SET_WAKEUP, + msm_fsusb_rpc_arg, &data, + NULL, NULL, -1); + +} +EXPORT_SYMBOL(msm_fsusb_set_remote_wakeup); + +static int msm_fsusb_acquire_bus(void) +{ + uint32_t data = PM_APP_OTG_ACQUIRE_BUS; + + return msm_rpc_client_req(client, + PM_APP_OTG_ACQUIRE_BUS, + msm_fsusb_rpc_arg, &data, + NULL, NULL, -1); + +} + +static int msm_fsusb_relinquish_bus(void) +{ + return msm_rpc_client_req(client, + PM_APP_OTG_RELINQUISH_BUS, + NULL, NULL, + NULL, NULL, -1); + +} + +static void msm_fsusb_request_session(void) +{ + int ret; + + ret = msm_fsusb_relinquish_bus(); + if (ret < 0) + pr_err("relinquish_bus rpc failed\n"); + ret = msm_fsusb_acquire_bus(); + if (ret < 0) + pr_err("acquire_bus rpc failed\n"); +} + +static int msm_fsusb_cb_func(struct msm_rpc_client *client, + void *buffer, int in_size) +{ + struct rpc_request_hdr *req; + int rc; + + req = buffer; + + msm_rpc_start_accepted_reply(client, be32_to_cpu(req->xid), + RPC_ACCEPTSTAT_SUCCESS); + rc = msm_rpc_send_accepted_reply(client, 0); + if (rc) { + pr_err("%s: sending reply failed: %d\n", __func__, rc); + return rc; + } + + switch (be32_to_cpu(req->procedure)) { + case PM_APP_OTG_INIT_DONE_CB_PROC: { + pr_debug("pm_app_otg_init_done callback received"); + msm_fsusb_request_session(); + break; + } + case PM_APP_OTG_HOST_INIT_CB_PROC: { + pr_debug("pm_app_otg_host_init_cb_proc callback received"); + host_ops->request(host_ops->handle, REQUEST_START); + break; + } + case PM_APP_OTG_REMOTE_DEV_LOST_CB_PROC: { + pr_debug("pm_app_otg_remote_dev_lost_cb_proc" + " callback received"); + msm_fsusb_acquire_bus(); + host_ops->request(host_ops->handle, REQUEST_STOP); + break; + } + case PM_APP_OTG_REMOTE_DEV_RESUMED_CB_PROC: { + pr_debug("pm_app_otg_remote_dev_resumed_cb_proc" + "callback received"); + host_ops->request(host_ops->handle, REQUEST_RESUME); + break; + } + case PM_APP_OTG_ERROR_NOTIFY_CB_PROC: { + pr_err("pm_app_otg_error_notify_cb_proc callback received"); + break; + } + default: + pr_err("%s: unknown callback(proc = %d) received\n", + __func__, req->procedure); + } + return 0; +} + +int msm_fsusb_rpc_init(struct msm_otg_ops *ops) +{ + host_ops = ops; + client = msm_rpc_register_client("fsusb", + PM_APP_OTG_PROG, + PM_APP_OTG_VERS, 1, + msm_fsusb_cb_func); + if (IS_ERR(client)) { + pr_err("%s: couldn't open rpc client\n", __func__); + return PTR_ERR(client); + } + + return 0; + +} +EXPORT_SYMBOL(msm_fsusb_rpc_init); + +void msm_fsusb_rpc_deinit(void) +{ + msm_rpc_unregister_client(client); +} +EXPORT_SYMBOL(msm_fsusb_rpc_deinit); diff --git a/arch/arm/mach-msm/rpc_hsusb.c b/arch/arm/mach-msm/rpc_hsusb.c new file mode 100644 index 00000000000..635ef312053 --- /dev/null +++ b/arch/arm/mach-msm/rpc_hsusb.c @@ -0,0 +1,659 @@ +/* linux/arch/arm/mach-msm/rpc_hsusb.c + * + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include + +static struct msm_rpc_endpoint *usb_ep; +static struct msm_rpc_endpoint *chg_ep; + +#define MSM_RPC_CHG_PROG 0x3000001a + +struct msm_chg_rpc_ids { + unsigned long vers_comp; + unsigned chg_usb_charger_connected_proc; + unsigned chg_usb_charger_disconnected_proc; + unsigned chg_usb_i_is_available_proc; + unsigned chg_usb_i_is_not_available_proc; +}; + +struct msm_hsusb_rpc_ids { + unsigned long prog; + unsigned long vers_comp; + unsigned long init_phy; + unsigned long vbus_pwr_up; + unsigned long vbus_pwr_down; + unsigned long update_product_id; + unsigned long update_serial_num; + unsigned long update_is_serial_num_null; + unsigned long reset_rework_installed; + unsigned long enable_pmic_ulpi_data0; + unsigned long disable_pmic_ulpi_data0; +}; + +static struct msm_hsusb_rpc_ids usb_rpc_ids; +static struct msm_chg_rpc_ids chg_rpc_ids; + +static int msm_hsusb_init_rpc_ids(unsigned long vers) +{ + if (vers == 0x00010001) { + usb_rpc_ids.prog = 0x30000064; + usb_rpc_ids.vers_comp = 0x00010001; + usb_rpc_ids.init_phy = 2; + usb_rpc_ids.vbus_pwr_up = 6; + usb_rpc_ids.vbus_pwr_down = 7; + usb_rpc_ids.update_product_id = 8; + usb_rpc_ids.update_serial_num = 9; + usb_rpc_ids.update_is_serial_num_null = 10; + usb_rpc_ids.reset_rework_installed = 17; + usb_rpc_ids.enable_pmic_ulpi_data0 = 18; + usb_rpc_ids.disable_pmic_ulpi_data0 = 19; + return 0; + } else if (vers == 0x00010002) { + usb_rpc_ids.prog = 0x30000064; + usb_rpc_ids.vers_comp = 0x00010002; + usb_rpc_ids.init_phy = 2; + usb_rpc_ids.vbus_pwr_up = 6; + usb_rpc_ids.vbus_pwr_down = 7; + usb_rpc_ids.update_product_id = 8; + usb_rpc_ids.update_serial_num = 9; + usb_rpc_ids.update_is_serial_num_null = 10; + usb_rpc_ids.reset_rework_installed = 17; + usb_rpc_ids.enable_pmic_ulpi_data0 = 18; + usb_rpc_ids.disable_pmic_ulpi_data0 = 19; + return 0; + } else { + pr_err("%s: no matches found for version\n", + __func__); + return -ENODATA; + } +} + +static int msm_chg_init_rpc(unsigned long vers) +{ + if (((vers & RPC_VERSION_MAJOR_MASK) == 0x00010000) || + ((vers & RPC_VERSION_MAJOR_MASK) == 0x00020000) || + ((vers & RPC_VERSION_MAJOR_MASK) == 0x00030000) || + ((vers & RPC_VERSION_MAJOR_MASK) == 0x00040000)) { + chg_ep = msm_rpc_connect_compatible(MSM_RPC_CHG_PROG, vers, + MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(chg_ep)) + return -ENODATA; + chg_rpc_ids.vers_comp = vers; + chg_rpc_ids.chg_usb_charger_connected_proc = 7; + chg_rpc_ids.chg_usb_charger_disconnected_proc = 8; + chg_rpc_ids.chg_usb_i_is_available_proc = 9; + chg_rpc_ids.chg_usb_i_is_not_available_proc = 10; + return 0; + } else + return -ENODATA; +} + +/* rpc connect for hsusb */ +int msm_hsusb_rpc_connect(void) +{ + + if (usb_ep && !IS_ERR(usb_ep)) { + pr_debug("%s: usb_ep already connected\n", __func__); + return 0; + } + + /* Initialize rpc ids */ + if (msm_hsusb_init_rpc_ids(0x00010001)) { + pr_err("%s: rpc ids initialization failed\n" + , __func__); + return -ENODATA; + } + + usb_ep = msm_rpc_connect_compatible(usb_rpc_ids.prog, + usb_rpc_ids.vers_comp, + MSM_RPC_UNINTERRUPTIBLE); + + if (IS_ERR(usb_ep)) { + pr_err("%s: connect compatible failed vers = %lx\n", + __func__, usb_rpc_ids.vers_comp); + + /* Initialize rpc ids */ + if (msm_hsusb_init_rpc_ids(0x00010002)) { + pr_err("%s: rpc ids initialization failed\n", + __func__); + return -ENODATA; + } + usb_ep = msm_rpc_connect_compatible(usb_rpc_ids.prog, + usb_rpc_ids.vers_comp, + MSM_RPC_UNINTERRUPTIBLE); + } + + if (IS_ERR(usb_ep)) { + pr_err("%s: connect compatible failed vers = %lx\n", + __func__, usb_rpc_ids.vers_comp); + return -EAGAIN; + } else + pr_debug("%s: rpc connect success vers = %lx\n", + __func__, usb_rpc_ids.vers_comp); + + return 0; +} +EXPORT_SYMBOL(msm_hsusb_rpc_connect); + +/* rpc connect for charging */ +int msm_chg_rpc_connect(void) +{ + uint32_t chg_vers; + + if (machine_is_msm7x27_surf() || machine_is_qsd8x50_surf()) + return -ENOTSUPP; + + if (chg_ep && !IS_ERR(chg_ep)) { + pr_debug("%s: chg_ep already connected\n", __func__); + return 0; + } + + chg_vers = 0x00040001; + if (!msm_chg_init_rpc(chg_vers)) + goto chg_found; + + chg_vers = 0x00030001; + if (!msm_chg_init_rpc(chg_vers)) + goto chg_found; + + chg_vers = 0x00020001; + if (!msm_chg_init_rpc(chg_vers)) + goto chg_found; + + chg_vers = 0x00010001; + if (!msm_chg_init_rpc(chg_vers)) + goto chg_found; + + pr_err("%s: connect compatible failed \n", + __func__); + return -EAGAIN; + +chg_found: + pr_debug("%s: connected to rpc vers = %x\n", + __func__, chg_vers); + return 0; +} +EXPORT_SYMBOL(msm_chg_rpc_connect); + +/* rpc call for phy_reset */ +int msm_hsusb_phy_reset(void) +{ + int rc = 0; + struct hsusb_phy_start_req { + struct rpc_request_hdr hdr; + } req; + + if (!usb_ep || IS_ERR(usb_ep)) { + pr_err("%s: phy_reset rpc failed before call," + "rc = %ld\n", __func__, PTR_ERR(usb_ep)); + return -EAGAIN; + } + + rc = msm_rpc_call(usb_ep, usb_rpc_ids.init_phy, + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + pr_err("%s: phy_reset rpc failed! rc = %d\n", + __func__, rc); + } else + pr_debug("msm_hsusb_phy_reset\n"); + + return rc; +} +EXPORT_SYMBOL(msm_hsusb_phy_reset); + +/* rpc call for vbus powerup */ +int msm_hsusb_vbus_powerup(void) +{ + int rc = 0; + struct hsusb_phy_start_req { + struct rpc_request_hdr hdr; + } req; + + if (!usb_ep || IS_ERR(usb_ep)) { + pr_err("%s: vbus_powerup rpc failed before call," + "rc = %ld\n", __func__, PTR_ERR(usb_ep)); + return -EAGAIN; + } + + rc = msm_rpc_call(usb_ep, usb_rpc_ids.vbus_pwr_up, + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + pr_err("%s: vbus_powerup failed! rc = %d\n", + __func__, rc); + } else + pr_debug("msm_hsusb_vbus_powerup\n"); + + return rc; +} +EXPORT_SYMBOL(msm_hsusb_vbus_powerup); + +/* rpc call for vbus shutdown */ +int msm_hsusb_vbus_shutdown(void) +{ + int rc = 0; + struct hsusb_phy_start_req { + struct rpc_request_hdr hdr; + } req; + + if (!usb_ep || IS_ERR(usb_ep)) { + pr_err("%s: vbus_shutdown rpc failed before call," + "rc = %ld\n", __func__, PTR_ERR(usb_ep)); + return -EAGAIN; + } + + rc = msm_rpc_call(usb_ep, usb_rpc_ids.vbus_pwr_down, + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + pr_err("%s: vbus_shutdown failed! rc = %d\n", + __func__, rc); + } else + pr_debug("msm_hsusb_vbus_shutdown\n"); + + return rc; +} +EXPORT_SYMBOL(msm_hsusb_vbus_shutdown); + +int msm_hsusb_send_productID(uint32_t product_id) +{ + int rc = 0; + struct hsusb_phy_start_req { + struct rpc_request_hdr hdr; + uint32_t product_id; + } req; + + if (!usb_ep || IS_ERR(usb_ep)) { + pr_err("%s: rpc connect failed: rc = %ld\n", + __func__, PTR_ERR(usb_ep)); + return -EAGAIN; + } + + req.product_id = cpu_to_be32(product_id); + rc = msm_rpc_call(usb_ep, usb_rpc_ids.update_product_id, + &req, sizeof(req), + 5 * HZ); + if (rc < 0) + pr_err("%s: rpc call failed! error: %d\n", + __func__, rc); + else + pr_debug("%s: rpc call success\n" , __func__); + + return rc; +} +EXPORT_SYMBOL(msm_hsusb_send_productID); + +int msm_hsusb_send_serial_number(const char *serial_number) +{ + int rc = 0, serial_len, rlen; + struct hsusb_send_sn_req { + struct rpc_request_hdr hdr; + uint32_t length; + char sn[0]; + } *req; + + if (!usb_ep || IS_ERR(usb_ep)) { + pr_err("%s: rpc connect failed: rc = %ld\n", + __func__, PTR_ERR(usb_ep)); + return -EAGAIN; + } + + /* + * USB driver passes null terminated string to us. Modem processor + * expects serial number to be 32 bit aligned. + */ + serial_len = strlen(serial_number)+1; + rlen = sizeof(struct rpc_request_hdr) + sizeof(uint32_t) + + ((serial_len + 3) & ~3); + + req = kmalloc(rlen, GFP_KERNEL); + if (!req) + return -ENOMEM; + + req->length = cpu_to_be32(serial_len); + strncpy(req->sn , serial_number, serial_len); + rc = msm_rpc_call(usb_ep, usb_rpc_ids.update_serial_num, + req, rlen, 5 * HZ); + if (rc < 0) + pr_err("%s: rpc call failed! error: %d\n", + __func__, rc); + else + pr_debug("%s: rpc call success\n", __func__); + + kfree(req); + return rc; +} +EXPORT_SYMBOL(msm_hsusb_send_serial_number); + +int msm_hsusb_is_serial_num_null(uint32_t val) +{ + int rc = 0; + struct hsusb_phy_start_req { + struct rpc_request_hdr hdr; + uint32_t value; + } req; + + if (!usb_ep || IS_ERR(usb_ep)) { + pr_err("%s: rpc connect failed: rc = %ld\n", + __func__, PTR_ERR(usb_ep)); + return -EAGAIN; + } + if (!usb_rpc_ids.update_is_serial_num_null) { + pr_err("%s: proc id not supported \n", __func__); + return -ENODATA; + } + + req.value = cpu_to_be32(val); + rc = msm_rpc_call(usb_ep, usb_rpc_ids.update_is_serial_num_null, + &req, sizeof(req), + 5 * HZ); + if (rc < 0) + pr_err("%s: rpc call failed! error: %d\n" , + __func__, rc); + else + pr_debug("%s: rpc call success\n", __func__); + + return rc; +} +EXPORT_SYMBOL(msm_hsusb_is_serial_num_null); + +int msm_chg_usb_charger_connected(uint32_t device) +{ + int rc = 0; + struct hsusb_start_req { + struct rpc_request_hdr hdr; + uint32_t otg_dev; + } req; + + if (!chg_ep || IS_ERR(chg_ep)) + return -EAGAIN; + req.otg_dev = cpu_to_be32(device); + rc = msm_rpc_call(chg_ep, chg_rpc_ids.chg_usb_charger_connected_proc, + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + pr_err("%s: charger_connected failed! rc = %d\n", + __func__, rc); + } else + pr_debug("msm_chg_usb_charger_connected\n"); + + return rc; +} +EXPORT_SYMBOL(msm_chg_usb_charger_connected); + +int msm_chg_usb_i_is_available(uint32_t sample) +{ + int rc = 0; + struct hsusb_start_req { + struct rpc_request_hdr hdr; + uint32_t i_ma; + } req; + + if (!chg_ep || IS_ERR(chg_ep)) + return -EAGAIN; + req.i_ma = cpu_to_be32(sample); + rc = msm_rpc_call(chg_ep, chg_rpc_ids.chg_usb_i_is_available_proc, + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + pr_err("%s: charger_i_available failed! rc = %d\n", + __func__, rc); + } else + pr_debug("msm_chg_usb_i_is_available(%u)\n", sample); + + return rc; +} +EXPORT_SYMBOL(msm_chg_usb_i_is_available); + +int msm_chg_usb_i_is_not_available(void) +{ + int rc = 0; + struct hsusb_start_req { + struct rpc_request_hdr hdr; + } req; + + if (!chg_ep || IS_ERR(chg_ep)) + return -EAGAIN; + rc = msm_rpc_call(chg_ep, chg_rpc_ids.chg_usb_i_is_not_available_proc, + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + pr_err("%s: charger_i_not_available failed! rc =" + "%d \n", __func__, rc); + } else + pr_debug("msm_chg_usb_i_is_not_available\n"); + + return rc; +} +EXPORT_SYMBOL(msm_chg_usb_i_is_not_available); + +int msm_chg_usb_charger_disconnected(void) +{ + int rc = 0; + struct hsusb_start_req { + struct rpc_request_hdr hdr; + } req; + + if (!chg_ep || IS_ERR(chg_ep)) + return -EAGAIN; + rc = msm_rpc_call(chg_ep, chg_rpc_ids.chg_usb_charger_disconnected_proc, + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + pr_err("%s: charger_disconnected failed! rc = %d\n", + __func__, rc); + } else + pr_debug("msm_chg_usb_charger_disconnected\n"); + + return rc; +} +EXPORT_SYMBOL(msm_chg_usb_charger_disconnected); + +/* rpc call to close connection */ +int msm_hsusb_rpc_close(void) +{ + int rc = 0; + + if (IS_ERR(usb_ep)) { + pr_err("%s: rpc_close failed before call, rc = %ld\n", + __func__, PTR_ERR(usb_ep)); + return -EAGAIN; + } + + rc = msm_rpc_close(usb_ep); + usb_ep = NULL; + + if (rc < 0) { + pr_err("%s: close rpc failed! rc = %d\n", + __func__, rc); + return -EAGAIN; + } else + pr_debug("rpc close success\n"); + + return rc; +} +EXPORT_SYMBOL(msm_hsusb_rpc_close); + +/* rpc call to close charging connection */ +int msm_chg_rpc_close(void) +{ + int rc = 0; + + if (IS_ERR(chg_ep)) { + pr_err("%s: rpc_close failed before call, rc = %ld\n", + __func__, PTR_ERR(chg_ep)); + return -EAGAIN; + } + + rc = msm_rpc_close(chg_ep); + chg_ep = NULL; + + if (rc < 0) { + pr_err("%s: close rpc failed! rc = %d\n", + __func__, rc); + return -EAGAIN; + } else + pr_debug("rpc close success\n"); + + return rc; +} +EXPORT_SYMBOL(msm_chg_rpc_close); + +int msm_hsusb_reset_rework_installed(void) +{ + int rc = 0; + struct hsusb_start_req { + struct rpc_request_hdr hdr; + } req; + struct hsusb_rpc_rep { + struct rpc_reply_hdr hdr; + uint32_t rework; + } rep; + + memset(&rep, 0, sizeof(rep)); + + if (!usb_ep || IS_ERR(usb_ep)) { + pr_err("%s: hsusb rpc connection not initialized, rc = %ld\n", + __func__, PTR_ERR(usb_ep)); + return -EAGAIN; + } + + rc = msm_rpc_call_reply(usb_ep, usb_rpc_ids.reset_rework_installed, + &req, sizeof(req), + &rep, sizeof(rep), 5 * HZ); + + if (rc < 0) { + pr_err("%s: rpc call failed! error: (%d)" + "proc id: (%lx)\n", + __func__, rc, + usb_rpc_ids.reset_rework_installed); + return rc; + } + + pr_info("%s: rework: (%d)\n", __func__, rep.rework); + return be32_to_cpu(rep.rework); +} +EXPORT_SYMBOL(msm_hsusb_reset_rework_installed); + +static int msm_hsusb_pmic_ulpidata0_config(int enable) +{ + int rc = 0; + struct hsusb_start_req { + struct rpc_request_hdr hdr; + } req; + + if (!usb_ep || IS_ERR(usb_ep)) { + pr_err("%s: hsusb rpc connection not initialized, rc = %ld\n", + __func__, PTR_ERR(usb_ep)); + return -EAGAIN; + } + + if (enable) + rc = msm_rpc_call(usb_ep, usb_rpc_ids.enable_pmic_ulpi_data0, + &req, sizeof(req), 5 * HZ); + else + rc = msm_rpc_call(usb_ep, usb_rpc_ids.disable_pmic_ulpi_data0, + &req, sizeof(req), 5 * HZ); + + if (rc < 0) + pr_err("%s: rpc call failed! error: %d\n", + __func__, rc); + return rc; +} + +int msm_hsusb_enable_pmic_ulpidata0(void) +{ + return msm_hsusb_pmic_ulpidata0_config(1); +} +EXPORT_SYMBOL(msm_hsusb_enable_pmic_ulpidata0); + +int msm_hsusb_disable_pmic_ulpidata0(void) +{ + return msm_hsusb_pmic_ulpidata0_config(0); +} +EXPORT_SYMBOL(msm_hsusb_disable_pmic_ulpidata0); + + +/* wrapper for sending pid and serial# info to bootloader */ +int usb_diag_update_pid_and_serial_num(uint32_t pid, const char *snum) +{ + int ret; + + ret = msm_hsusb_send_productID(pid); + if (ret) + return ret; + + if (!snum) { + ret = msm_hsusb_is_serial_num_null(1); + if (ret) + return ret; + } + + ret = msm_hsusb_is_serial_num_null(0); + if (ret) + return ret; + ret = msm_hsusb_send_serial_number(snum); + if (ret) + return ret; + + return 0; +} + + +#ifdef CONFIG_USB_GADGET_MSM_72K +/* charger api wrappers */ +int hsusb_chg_init(int connect) +{ + if (connect) + return msm_chg_rpc_connect(); + else + return msm_chg_rpc_close(); +} +EXPORT_SYMBOL(hsusb_chg_init); + +void hsusb_chg_vbus_draw(unsigned mA) +{ + msm_chg_usb_i_is_available(mA); +} +EXPORT_SYMBOL(hsusb_chg_vbus_draw); + +void hsusb_chg_connected(enum chg_type chgtype) +{ + char *chg_types[] = {"STD DOWNSTREAM PORT", + "CARKIT", + "DEDICATED CHARGER", + "INVALID"}; + + if (chgtype == USB_CHG_TYPE__INVALID) { + msm_chg_usb_i_is_not_available(); + msm_chg_usb_charger_disconnected(); + return; + } + + pr_info("\nCharger Type: %s\n", chg_types[chgtype]); + + msm_chg_usb_charger_connected(chgtype); +} +EXPORT_SYMBOL(hsusb_chg_connected); +#endif diff --git a/arch/arm/mach-msm/rpc_pmapp.c b/arch/arm/mach-msm/rpc_pmapp.c new file mode 100644 index 00000000000..3f80760baa5 --- /dev/null +++ b/arch/arm/mach-msm/rpc_pmapp.c @@ -0,0 +1,560 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define PMAPP_RPC_PROG 0x30000060 +#define PMAPP_RPC_VER_1_1 0x00010001 +#define PMAPP_RPC_VER_1_2 0x00010002 +#define PMAPP_RPC_VER_2_1 0x00020001 +#define PMAPP_RPC_VER_3_1 0x00030001 +#define PMAPP_RPC_VER_5_1 0x00050001 +#define PMAPP_RPC_VER_6_1 0x00060001 + +#define VBUS_SESS_VALID_CB_PROC 1 +#define PM_VOTE_USB_PWR_SEL_SWITCH_APP__HSUSB (1 << 2) +#define PM_USB_PWR_SEL_SWITCH_ID 0 + +#define PMAPP_RPC_TIMEOUT (5*HZ) + +#define PMAPP_DISPLAY_CLOCK_CONFIG_PROC 21 +#define PMAPP_VREG_LEVEL_VOTE_PROC 23 +#define PMAPP_SMPS_CLOCK_VOTE_PROC 26 +#define PMAPP_CLOCK_VOTE_PROC 27 +#define PMAPP_SMPS_MODE_VOTE_PROC 28 +#define PMAPP_VREG_PINCNTRL_VOTE_PROC 30 +#define PMAPP_DISP_BACKLIGHT_SET_PROC 31 +#define PMAPP_DISP_BACKLIGHT_INIT_PROC 32 + +/* Clock voter name max length */ +#define PMAPP_CLOCK_VOTER_ID_LEN 4 + +struct rpc_pmapp_ids { + unsigned long reg_for_vbus_valid; + unsigned long vote_for_vbus_valid_switch; +}; + +static struct rpc_pmapp_ids rpc_ids; +static struct msm_rpc_client *client; + +/* Add newer versions at the top of array */ +static const unsigned int rpc_vers[] = { + PMAPP_RPC_VER_6_1, + PMAPP_RPC_VER_5_1, + PMAPP_RPC_VER_3_1, + PMAPP_RPC_VER_2_1, +}; + +static void rpc_pmapp_init_rpc_ids(unsigned long vers) +{ + if (vers == PMAPP_RPC_VER_1_1) { + rpc_ids.reg_for_vbus_valid = 5; + rpc_ids.vote_for_vbus_valid_switch = 6; + } else if (vers == PMAPP_RPC_VER_1_2) { + rpc_ids.reg_for_vbus_valid = 16; + rpc_ids.vote_for_vbus_valid_switch = 17; + } else if (vers == PMAPP_RPC_VER_2_1) { + rpc_ids.reg_for_vbus_valid = 0; /* NA */ + rpc_ids.vote_for_vbus_valid_switch = 0; /* NA */ + } +} + +struct usb_pwr_sel_switch_args { + uint32_t cmd; + uint32_t switch_id; + uint32_t app_mask; +}; + +static int usb_pwr_sel_switch_arg_cb(struct msm_rpc_client *client, + void *buf, void *data) +{ + struct usb_pwr_sel_switch_args *args = buf; + + args->cmd = cpu_to_be32(*(uint32_t *)data); + args->switch_id = cpu_to_be32(PM_USB_PWR_SEL_SWITCH_ID); + args->app_mask = cpu_to_be32(PM_VOTE_USB_PWR_SEL_SWITCH_APP__HSUSB); + return sizeof(struct usb_pwr_sel_switch_args); +} + +static int msm_pm_app_vote_usb_pwr_sel_switch(uint32_t cmd) +{ + return msm_rpc_client_req(client, + rpc_ids.vote_for_vbus_valid_switch, + usb_pwr_sel_switch_arg_cb, + &cmd, NULL, NULL, -1); +} + +struct vbus_sess_valid_args { + uint32_t cb_id; +}; + +static int vbus_sess_valid_arg_cb(struct msm_rpc_client *client, + void *buf, void *data) +{ + struct vbus_sess_valid_args *args = buf; + + args->cb_id = cpu_to_be32(*(uint32_t *)data); + return sizeof(struct vbus_sess_valid_args); +} + + +int pmic_vote_3p3_pwr_sel_switch(int boost) +{ + int ret; + + ret = msm_pm_app_vote_usb_pwr_sel_switch(boost); + + return ret; +} +EXPORT_SYMBOL(pmic_vote_3p3_pwr_sel_switch); + +struct vbus_sn_notification_args { + uint32_t cb_id; + uint32_t vbus; /* vbus = 0 if VBUS is present */ +}; + +static int vbus_notification_cb(struct msm_rpc_client *client, + void *buffer, int in_size) +{ + struct vbus_sn_notification_args *args; + struct rpc_request_hdr *req = buffer; + int rc; + uint32_t accept_status; + void (*cb_func)(int); + uint32_t cb_id; + int vbus; + + args = (struct vbus_sn_notification_args *) (req + 1); + cb_id = be32_to_cpu(args->cb_id); + vbus = be32_to_cpu(args->vbus); + + cb_func = msm_rpc_get_cb_func(client, cb_id); + if (cb_func) { + cb_func(!vbus); + accept_status = RPC_ACCEPTSTAT_SUCCESS; + } else + accept_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + + msm_rpc_start_accepted_reply(client, be32_to_cpu(req->xid), + accept_status); + rc = msm_rpc_send_accepted_reply(client, 0); + if (rc) + pr_err("%s: send accepted reply failed: %d\n", __func__, rc); + + return rc; +} + +static int pm_app_usb_cb_func(struct msm_rpc_client *client, + void *buffer, int in_size) +{ + int rc; + struct rpc_request_hdr *req = buffer; + + switch (be32_to_cpu(req->procedure)) { + case VBUS_SESS_VALID_CB_PROC: + rc = vbus_notification_cb(client, buffer, in_size); + break; + default: + pr_err("%s: procedure not supported %d\n", __func__, + be32_to_cpu(req->procedure)); + msm_rpc_start_accepted_reply(client, be32_to_cpu(req->xid), + RPC_ACCEPTSTAT_PROC_UNAVAIL); + rc = msm_rpc_send_accepted_reply(client, 0); + if (rc) + pr_err("%s: sending reply failed: %d\n", __func__, rc); + break; + } + return rc; +} + +int msm_pm_app_rpc_init(void (*callback)(int online)) +{ + uint32_t cb_id, rc; + + if (!machine_is_qsd8x50_ffa() && !machine_is_msm7x27_ffa()) + return -ENOTSUPP; + + client = msm_rpc_register_client("pmapp_usb", + PMAPP_RPC_PROG, + PMAPP_RPC_VER_2_1, 1, + pm_app_usb_cb_func); + if (!IS_ERR(client)) { + rpc_pmapp_init_rpc_ids(PMAPP_RPC_VER_2_1); + goto done; + } + + client = msm_rpc_register_client("pmapp_usb", + PMAPP_RPC_PROG, + PMAPP_RPC_VER_1_2, 1, + pm_app_usb_cb_func); + if (!IS_ERR(client)) { + rpc_pmapp_init_rpc_ids(PMAPP_RPC_VER_1_2); + goto done; + } + + client = msm_rpc_register_client("pmapp_usb", + PMAPP_RPC_PROG, + PMAPP_RPC_VER_1_1, 1, + pm_app_usb_cb_func); + if (!IS_ERR(client)) + rpc_pmapp_init_rpc_ids(PMAPP_RPC_VER_1_1); + else + return PTR_ERR(client); + +done: + cb_id = msm_rpc_add_cb_func(client, (void *)callback); + /* In case of NULL callback funtion, cb_id would be -1 */ + if ((int) cb_id < -1) + return cb_id; + rc = msm_rpc_client_req(client, + rpc_ids.reg_for_vbus_valid, + vbus_sess_valid_arg_cb, + &cb_id, NULL, NULL, -1); + return rc; +} +EXPORT_SYMBOL(msm_pm_app_rpc_init); + +void msm_pm_app_rpc_deinit(void(*callback)(int online)) +{ + if (client) { + msm_rpc_remove_cb_func(client, (void *)callback); + msm_rpc_unregister_client(client); + } +} +EXPORT_SYMBOL(msm_pm_app_rpc_deinit); + +/* error bit flags defined by modem side */ +#define PM_ERR_FLAG__PAR1_OUT_OF_RANGE (0x0001) +#define PM_ERR_FLAG__PAR2_OUT_OF_RANGE (0x0002) +#define PM_ERR_FLAG__PAR3_OUT_OF_RANGE (0x0004) +#define PM_ERR_FLAG__PAR4_OUT_OF_RANGE (0x0008) +#define PM_ERR_FLAG__PAR5_OUT_OF_RANGE (0x0010) + +#define PM_ERR_FLAG__ALL_PARMS_OUT_OF_RANGE (0x001F) /* all 5 previous */ + +#define PM_ERR_FLAG__SBI_OPT_ERR (0x0080) +#define PM_ERR_FLAG__FEATURE_NOT_SUPPORTED (0x0100) + +#define PMAPP_BUFF_SIZE 256 + +struct pmapp_buf { + char *start; /* buffer start addr */ + char *end; /* buffer end addr */ + int size; /* buffer size */ + char *data; /* payload begin addr */ + int len; /* payload len */ +}; + +static DEFINE_MUTEX(pmapp_mtx); + +struct pmapp_ctrl { + int inited; + struct pmapp_buf tbuf; + struct pmapp_buf rbuf; + struct msm_rpc_endpoint *endpoint; +}; + +static struct pmapp_ctrl pmapp_ctrl = { + .inited = -1, +}; + + +static int pmapp_rpc_set_only(uint data0, uint data1, uint data2, + uint data3, int num, int proc); + +static int pmapp_buf_init(void) +{ + struct pmapp_ctrl *pm = &pmapp_ctrl; + + memset(&pmapp_ctrl, 0, sizeof(pmapp_ctrl)); + + pm->tbuf.start = kmalloc(PMAPP_BUFF_SIZE, GFP_KERNEL); + if (pm->tbuf.start == NULL) { + printk(KERN_ERR "%s:%u\n", __func__, __LINE__); + return -ENOMEM; + } + + pm->tbuf.data = pm->tbuf.start; + pm->tbuf.size = PMAPP_BUFF_SIZE; + pm->tbuf.end = pm->tbuf.start + PMAPP_BUFF_SIZE; + pm->tbuf.len = 0; + + pm->rbuf.start = kmalloc(PMAPP_BUFF_SIZE, GFP_KERNEL); + if (pm->rbuf.start == NULL) { + kfree(pm->tbuf.start); + printk(KERN_ERR "%s:%u\n", __func__, __LINE__); + return -ENOMEM; + } + pm->rbuf.data = pm->rbuf.start; + pm->rbuf.size = PMAPP_BUFF_SIZE; + pm->rbuf.end = pm->rbuf.start + PMAPP_BUFF_SIZE; + pm->rbuf.len = 0; + + pm->inited = 1; + + return 0; +} + +static inline void pmapp_buf_reserve(struct pmapp_buf *bp, int len) +{ + bp->data += len; +} + +static inline void pmapp_buf_reset(struct pmapp_buf *bp) +{ + bp->data = bp->start; + bp->len = 0; +} + +static int modem_to_linux_err(uint err) +{ + if (err == 0) + return 0; + + if (err & PM_ERR_FLAG__ALL_PARMS_OUT_OF_RANGE) + return -EINVAL; /* PM_ERR_FLAG__PAR[1..5]_OUT_OF_RANGE */ + + if (err & PM_ERR_FLAG__SBI_OPT_ERR) + return -EIO; + + if (err & PM_ERR_FLAG__FEATURE_NOT_SUPPORTED) + return -ENOSYS; + + return -EPERM; +} + +static int pmapp_put_tx_data(struct pmapp_buf *tp, uint datav) +{ + uint *lp; + + if ((tp->size - tp->len) < sizeof(datav)) { + printk(KERN_ERR "%s: OVERFLOW size=%d len=%d\n", + __func__, tp->size, tp->len); + return -1; + } + + lp = (uint *)tp->data; + *lp = cpu_to_be32(datav); + tp->data += sizeof(datav); + tp->len += sizeof(datav); + + return sizeof(datav); +} + +static int pmapp_pull_rx_data(struct pmapp_buf *rp, uint *datap) +{ + uint *lp; + + if (rp->len < sizeof(*datap)) { + printk(KERN_ERR "%s: UNDERRUN len=%d\n", __func__, rp->len); + return -1; + } + lp = (uint *)rp->data; + *datap = be32_to_cpu(*lp); + rp->data += sizeof(*datap); + rp->len -= sizeof(*datap); + + return sizeof(*datap); +} + + +static int pmapp_rpc_req_reply(struct pmapp_buf *tbuf, struct pmapp_buf *rbuf, + int proc) +{ + struct pmapp_ctrl *pm = &pmapp_ctrl; + int ans, len, i; + + + if ((pm->endpoint == NULL) || IS_ERR(pm->endpoint)) { + for (i = 0; i < ARRAY_SIZE(rpc_vers); i++) { + pm->endpoint = msm_rpc_connect_compatible( + PMAPP_RPC_PROG, rpc_vers[i], 0); + + if (IS_ERR(pm->endpoint)) { + ans = PTR_ERR(pm->endpoint); + printk(KERN_ERR "%s: init rpc failed! ans = %d" + " for 0x%x version, fallback\n", + __func__, ans, rpc_vers[i]); + } else { + printk(KERN_DEBUG "%s: successfully connected" + " to 0x%x rpc version\n", + __func__, rpc_vers[i]); + break; + } + } + } + + if (IS_ERR(pm->endpoint)) { + ans = PTR_ERR(pm->endpoint); + return ans; + } + + /* + * data is point to next available space at this moment, + * move it back to beginning of request header and increase + * the length + */ + tbuf->data = tbuf->start; + tbuf->len += sizeof(struct rpc_request_hdr); + + len = msm_rpc_call_reply(pm->endpoint, proc, + tbuf->data, tbuf->len, + rbuf->data, rbuf->size, + PMAPP_RPC_TIMEOUT); + + if (len <= 0) { + printk(KERN_ERR "%s: rpc failed! len = %d\n", __func__, len); + pm->endpoint = NULL; /* re-connect later ? */ + return len; + } + + rbuf->len = len; + /* strip off rpc_reply_hdr */ + rbuf->data += sizeof(struct rpc_reply_hdr); + rbuf->len -= sizeof(struct rpc_reply_hdr); + + return rbuf->len; +} + +static int pmapp_rpc_set_only(uint data0, uint data1, uint data2, uint data3, + int num, int proc) +{ + struct pmapp_ctrl *pm = &pmapp_ctrl; + struct pmapp_buf *tp; + struct pmapp_buf *rp; + int stat; + + + if (mutex_lock_interruptible(&pmapp_mtx)) + return -ERESTARTSYS; + + if (pm->inited <= 0) { + stat = pmapp_buf_init(); + if (stat < 0) { + mutex_unlock(&pmapp_mtx); + return stat; + } + } + + tp = &pm->tbuf; + rp = &pm->rbuf; + + pmapp_buf_reset(tp); + pmapp_buf_reserve(tp, sizeof(struct rpc_request_hdr)); + pmapp_buf_reset(rp); + + if (num > 0) + pmapp_put_tx_data(tp, data0); + + if (num > 1) + pmapp_put_tx_data(tp, data1); + + if (num > 2) + pmapp_put_tx_data(tp, data2); + + if (num > 3) + pmapp_put_tx_data(tp, data3); + + stat = pmapp_rpc_req_reply(tp, rp, proc); + if (stat < 0) { + mutex_unlock(&pmapp_mtx); + return stat; + } + + pmapp_pull_rx_data(rp, &stat); /* result from server */ + + mutex_unlock(&pmapp_mtx); + + return modem_to_linux_err(stat); +} + +int pmapp_display_clock_config(uint enable) +{ + return pmapp_rpc_set_only(enable, 0, 0, 0, 1, + PMAPP_DISPLAY_CLOCK_CONFIG_PROC); +} +EXPORT_SYMBOL(pmapp_display_clock_config); + +int pmapp_clock_vote(const char *voter_id, uint clock_id, uint vote) +{ + if (strlen(voter_id) != PMAPP_CLOCK_VOTER_ID_LEN) + return -EINVAL; + + return pmapp_rpc_set_only(*((uint *) voter_id), clock_id, vote, 0, 3, + PMAPP_CLOCK_VOTE_PROC); +} +EXPORT_SYMBOL(pmapp_clock_vote); + +int pmapp_smps_clock_vote(const char *voter_id, uint vreg_id, uint vote) +{ + if (strlen(voter_id) != PMAPP_CLOCK_VOTER_ID_LEN) + return -EINVAL; + + return pmapp_rpc_set_only(*((uint *) voter_id), vreg_id, vote, 0, 3, + PMAPP_SMPS_CLOCK_VOTE_PROC); +} +EXPORT_SYMBOL(pmapp_smps_clock_vote); + +int pmapp_vreg_level_vote(const char *voter_id, uint vreg_id, uint level) +{ + if (strlen(voter_id) != PMAPP_CLOCK_VOTER_ID_LEN) + return -EINVAL; + + return pmapp_rpc_set_only(*((uint *) voter_id), vreg_id, level, 0, 3, + PMAPP_VREG_LEVEL_VOTE_PROC); +} +EXPORT_SYMBOL(pmapp_vreg_level_vote); + +int pmapp_smps_mode_vote(const char *voter_id, uint vreg_id, uint mode) +{ + if (strlen(voter_id) != PMAPP_CLOCK_VOTER_ID_LEN) + return -EINVAL; + + return pmapp_rpc_set_only(*((uint *) voter_id), vreg_id, mode, 0, 3, + PMAPP_SMPS_MODE_VOTE_PROC); +} +EXPORT_SYMBOL(pmapp_smps_mode_vote); + +int pmapp_vreg_pincntrl_vote(const char *voter_id, uint vreg_id, + uint clock_id, uint vote) +{ + if (strlen(voter_id) != PMAPP_CLOCK_VOTER_ID_LEN) + return -EINVAL; + + return pmapp_rpc_set_only(*((uint *) voter_id), vreg_id, clock_id, + vote, 4, + PMAPP_VREG_PINCNTRL_VOTE_PROC); +} +EXPORT_SYMBOL(pmapp_vreg_pincntrl_vote); + +int pmapp_disp_backlight_set_brightness(int value) +{ + if (value < 0 || value > 100) + return -EINVAL; + + return pmapp_rpc_set_only(value, 0, 0, 0, 1, + PMAPP_DISP_BACKLIGHT_SET_PROC); +} +EXPORT_SYMBOL(pmapp_disp_backlight_set_brightness); + +void pmapp_disp_backlight_init(void) +{ + pmapp_rpc_set_only(0, 0, 0, 0, 0, PMAPP_DISP_BACKLIGHT_INIT_PROC); +} +EXPORT_SYMBOL(pmapp_disp_backlight_init); diff --git a/arch/arm/mach-msm/rpc_server_dog_keepalive.c b/arch/arm/mach-msm/rpc_server_dog_keepalive.c new file mode 100644 index 00000000000..5e0f46da379 --- /dev/null +++ b/arch/arm/mach-msm/rpc_server_dog_keepalive.c @@ -0,0 +1,77 @@ +/* arch/arm/mach-msm/rpc_server_dog_keepalive.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Author: Iliyan Malchev + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +/* dog_keepalive server definitions */ + +#define DOG_KEEPALIVE_PROG 0x30000015 +#if CONFIG_MSM_AMSS_VERSION==6210 +#define DOG_KEEPALIVE_VERS 0 +#define RPC_DOG_KEEPALIVE_BEACON 1 +#elif (CONFIG_MSM_AMSS_VERSION==6220) || (CONFIG_MSM_AMSS_VERSION==6225) +#define DOG_KEEPALIVE_VERS 0x731fa727 +#define RPC_DOG_KEEPALIVE_BEACON 2 +#else +#error "Unsupported AMSS version" +#endif +#define DOG_KEEPALIVE_VERS_COMP 0x00010001 +#define RPC_DOG_KEEPALIVE_NULL 0 + + +/* TODO: Remove server registration with _VERS when modem is upated with _COMP*/ + +static int handle_rpc_call(struct msm_rpc_server *server, + struct rpc_request_hdr *req, unsigned len) +{ + switch (req->procedure) { + case RPC_DOG_KEEPALIVE_NULL: + return 0; + case RPC_DOG_KEEPALIVE_BEACON: + return 0; + default: + return -ENODEV; + } +} + +static struct msm_rpc_server rpc_server[] = { + { + .prog = DOG_KEEPALIVE_PROG, + .vers = DOG_KEEPALIVE_VERS, + .rpc_call = handle_rpc_call, + }, + { + .prog = DOG_KEEPALIVE_PROG, + .vers = DOG_KEEPALIVE_VERS_COMP, + .rpc_call = handle_rpc_call, + }, +}; + +static int __init rpc_server_init(void) +{ + /* Dual server registration to support backwards compatibility vers */ + int ret; + ret = msm_rpc_create_server(&rpc_server[1]); + if (ret < 0) + return ret; + return msm_rpc_create_server(&rpc_server[0]); +} + + +module_init(rpc_server_init); diff --git a/arch/arm/mach-msm/rpc_server_handset.c b/arch/arm/mach-msm/rpc_server_handset.c new file mode 100644 index 00000000000..b011aeba6f8 --- /dev/null +++ b/arch/arm/mach-msm/rpc_server_handset.c @@ -0,0 +1,707 @@ +/* arch/arm/mach-msm/rpc_server_handset.c + * + * Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define DRIVER_NAME "msm-handset" + +#define HS_SERVER_PROG 0x30000062 +#define HS_SERVER_VERS 0x00010001 + +#define HS_RPC_PROG 0x30000091 + +#define HS_PROCESS_CMD_PROC 0x02 +#define HS_SUBSCRIBE_SRVC_PROC 0x03 +#define HS_REPORT_EVNT_PROC 0x05 +#define HS_EVENT_CB_PROC 1 +#define HS_EVENT_DATA_VER 1 + +#define RPC_KEYPAD_NULL_PROC 0 +#define RPC_KEYPAD_PASS_KEY_CODE_PROC 2 +#define RPC_KEYPAD_SET_PWR_KEY_STATE_PROC 3 + +#define HS_PWR_K 0x6F /* Power key */ +#define HS_END_K 0x51 /* End key or Power key */ +#define HS_STEREO_HEADSET_K 0x82 +#define HS_HEADSET_SWITCH_K 0x84 +#define HS_HEADSET_SWITCH_2_K 0xF0 +#define HS_HEADSET_SWITCH_3_K 0xF1 +#define HS_HEADSET_HEADPHONE_K 0xF6 +#define HS_HEADSET_MICROPHONE_K 0xF7 +#define HS_REL_K 0xFF /* key release */ + +#define SW_HEADPHONE_INSERT_W_MIC 1 /* HS with mic */ + +#define KEY(hs_key, input_key) ((hs_key << 24) | input_key) + +enum hs_event { + HS_EVNT_EXT_PWR = 0, /* External Power status */ + HS_EVNT_HSD, /* Headset Detection */ + HS_EVNT_HSTD, /* Headset Type Detection */ + HS_EVNT_HSSD, /* Headset Switch Detection */ + HS_EVNT_KPD, + HS_EVNT_FLIP, /* Flip / Clamshell status (open/close) */ + HS_EVNT_CHARGER, /* Battery is being charged or not */ + HS_EVNT_ENV, /* Events from runtime environment like DEM */ + HS_EVNT_REM, /* Events received from HS counterpart on a + remote processor*/ + HS_EVNT_DIAG, /* Diag Events */ + HS_EVNT_LAST, /* Should always be the last event type */ + HS_EVNT_MAX /* Force enum to be an 32-bit number */ +}; + +enum hs_src_state { + HS_SRC_STATE_UNKWN = 0, + HS_SRC_STATE_LO, + HS_SRC_STATE_HI, +}; + +struct hs_event_data { + uint32_t ver; /* Version number */ + enum hs_event event_type; /* Event Type */ + enum hs_event enum_disc; /* discriminator */ + uint32_t data_length; /* length of the next field */ + enum hs_src_state data; /* Pointer to data */ + uint32_t data_size; /* Elements to be processed in data */ +}; + +enum hs_return_value { + HS_EKPDLOCKED = -2, /* Operation failed because keypad is locked */ + HS_ENOTSUPPORTED = -1, /* Functionality not supported */ + HS_FALSE = 0, /* Inquired condition is not true */ + HS_FAILURE = 0, /* Requested operation was not successful */ + HS_TRUE = 1, /* Inquired condition is true */ + HS_SUCCESS = 1, /* Requested operation was successful */ + HS_MAX_RETURN = 0x7FFFFFFF/* Force enum to be a 32 bit number */ +}; + +struct hs_key_data { + uint32_t ver; /* Version number to track sturcture changes */ + uint32_t code; /* which key? */ + uint32_t parm; /* key status. Up/down or pressed/released */ +}; + +enum hs_subs_srvc { + HS_SUBS_SEND_CMD = 0, /* Subscribe to send commands to HS */ + HS_SUBS_RCV_EVNT, /* Subscribe to receive Events from HS */ + HS_SUBS_SRVC_MAX +}; + +enum hs_subs_req { + HS_SUBS_REGISTER, /* Subscribe */ + HS_SUBS_CANCEL, /* Unsubscribe */ + HS_SUB_STATUS_MAX +}; + +enum hs_event_class { + HS_EVNT_CLASS_ALL = 0, /* All HS events */ + HS_EVNT_CLASS_LAST, /* Should always be the last class type */ + HS_EVNT_CLASS_MAX +}; + +enum hs_cmd_class { + HS_CMD_CLASS_LCD = 0, /* Send LCD related commands */ + HS_CMD_CLASS_KPD, /* Send KPD related commands */ + HS_CMD_CLASS_LAST, /* Should always be the last class type */ + HS_CMD_CLASS_MAX +}; + +/* + * Receive events or send command + */ +union hs_subs_class { + enum hs_event_class evnt; + enum hs_cmd_class cmd; +}; + +struct hs_subs { + uint32_t ver; + enum hs_subs_srvc srvc; /* commands or events */ + enum hs_subs_req req; /* subscribe or unsubscribe */ + uint32_t host_os; + enum hs_subs_req disc; /* discriminator */ + union hs_subs_class id; +}; + +struct hs_event_cb_recv { + uint32_t cb_id; + uint32_t hs_key_data_ptr; + struct hs_key_data key; +}; +enum hs_ext_cmd_type { + HS_EXT_CMD_KPD_SEND_KEY = 0, /* Send Key */ + HS_EXT_CMD_KPD_BKLT_CTRL, /* Keypad backlight intensity */ + HS_EXT_CMD_LCD_BKLT_CTRL, /* LCD Backlight intensity */ + HS_EXT_CMD_DIAG_KEYMAP, /* Emulating a Diag key sequence */ + HS_EXT_CMD_DIAG_LOCK, /* Device Lock/Unlock */ + HS_EXT_CMD_GET_EVNT_STATUS, /* Get the status for one of the drivers */ + HS_EXT_CMD_KPD_GET_KEYS_STATUS,/* Get a list of keys status */ + HS_EXT_CMD_KPD_SET_PWR_KEY_RST_THOLD, /* PWR Key HW Reset duration */ + HS_EXT_CMD_KPD_SET_PWR_KEY_THOLD, /* Set pwr key threshold duration */ + HS_EXT_CMD_LAST, /* Should always be the last command type */ + HS_EXT_CMD_MAX = 0x7FFFFFFF /* Force enum to be an 32-bit number */ +}; + +struct hs_cmd_data_type { + uint32_t hs_cmd_data_type_ptr; /* hs_cmd_data_type ptr length */ + uint32_t ver; /* version */ + enum hs_ext_cmd_type id; /* command id */ + uint32_t handle; /* handle returned from subscribe proc */ + enum hs_ext_cmd_type disc_id1; /* discriminator id */ + uint32_t input_ptr; /* input ptr length */ + uint32_t input_val; /* command specific data */ + uint32_t input_len; /* length of command input */ + enum hs_ext_cmd_type disc_id2; /* discriminator id */ + uint32_t output_len; /* length of output data */ + uint32_t delayed; /* execution context for modem + true - caller context + false - hs task context*/ +}; + +static const uint32_t hs_key_map[] = { + KEY(HS_PWR_K, KEY_POWER), + KEY(HS_END_K, KEY_END), + KEY(HS_STEREO_HEADSET_K, SW_HEADPHONE_INSERT_W_MIC), + KEY(HS_HEADSET_HEADPHONE_K, SW_HEADPHONE_INSERT), + KEY(HS_HEADSET_MICROPHONE_K, SW_MICROPHONE_INSERT), + KEY(HS_HEADSET_SWITCH_K, KEY_MEDIA), + KEY(HS_HEADSET_SWITCH_2_K, KEY_VOLUMEUP), + KEY(HS_HEADSET_SWITCH_3_K, KEY_VOLUMEDOWN), + 0 +}; + +enum { + NO_DEVICE = 0, + MSM_HEADSET = 1, +}; +/* Add newer versions at the top of array */ +static const unsigned int rpc_vers[] = { + 0x00030001, + 0x00020001, + 0x00010001, +}; +/* hs subscription request parameters */ +struct hs_subs_rpc_req { + uint32_t hs_subs_ptr; + struct hs_subs hs_subs; + uint32_t hs_cb_id; + uint32_t hs_handle_ptr; + uint32_t hs_handle_data; +}; + +static struct hs_subs_rpc_req *hs_subs_req; + +struct msm_handset { + struct input_dev *ipdev; + struct switch_dev sdev; + struct msm_handset_platform_data *hs_pdata; + bool mic_on, hs_on; +}; + +static struct msm_rpc_client *rpc_client; +static struct msm_handset *hs; + +static int hs_find_key(uint32_t hscode) +{ + int i, key; + + key = KEY(hscode, 0); + + for (i = 0; hs_key_map[i] != 0; i++) { + if ((hs_key_map[i] & 0xff000000) == key) + return hs_key_map[i] & 0x00ffffff; + } + return -1; +} + +static void update_state(void) +{ + int state; + + if (hs->mic_on && hs->hs_on) + state = 1 << 0; + else if (hs->hs_on) + state = 1 << 1; + else if (hs->mic_on) + state = 1 << 2; + else + state = 0; + + switch_set_state(&hs->sdev, state); +} + +/* + * tuple format: (key_code, key_param) + * + * old-architecture: + * key-press = (key_code, 0) + * key-release = (0xff, key_code) + * + * new-architecutre: + * key-press = (key_code, 0) + * key-release = (key_code, 0xff) + */ +static void report_hs_key(uint32_t key_code, uint32_t key_parm) +{ + int key, temp_key_code; + + if (key_code == HS_REL_K) + key = hs_find_key(key_parm); + else + key = hs_find_key(key_code); + + temp_key_code = key_code; + + if (key_parm == HS_REL_K) + key_code = key_parm; + + switch (key) { + case KEY_POWER: + case KEY_END: + case KEY_MEDIA: + case KEY_VOLUMEUP: + case KEY_VOLUMEDOWN: + input_report_key(hs->ipdev, key, (key_code != HS_REL_K)); + break; + case SW_HEADPHONE_INSERT_W_MIC: + hs->mic_on = hs->hs_on = (key_code != HS_REL_K) ? 1 : 0; + input_report_switch(hs->ipdev, SW_HEADPHONE_INSERT, + hs->hs_on); + input_report_switch(hs->ipdev, SW_MICROPHONE_INSERT, + hs->mic_on); + update_state(); + break; + + case SW_HEADPHONE_INSERT: + hs->hs_on = (key_code != HS_REL_K) ? 1 : 0; + input_report_switch(hs->ipdev, key, hs->hs_on); + update_state(); + break; + case SW_MICROPHONE_INSERT: + hs->mic_on = (key_code != HS_REL_K) ? 1 : 0; + input_report_switch(hs->ipdev, key, hs->mic_on); + update_state(); + break; + case -1: + printk(KERN_ERR "%s: No mapping for remote handset event %d\n", + __func__, temp_key_code); + return; + } + input_sync(hs->ipdev); +} + +static int handle_hs_rpc_call(struct msm_rpc_server *server, + struct rpc_request_hdr *req, unsigned len) +{ + struct rpc_keypad_pass_key_code_args { + uint32_t key_code; + uint32_t key_parm; + }; + + switch (req->procedure) { + case RPC_KEYPAD_NULL_PROC: + return 0; + + case RPC_KEYPAD_PASS_KEY_CODE_PROC: { + struct rpc_keypad_pass_key_code_args *args; + + args = (struct rpc_keypad_pass_key_code_args *)(req + 1); + args->key_code = be32_to_cpu(args->key_code); + args->key_parm = be32_to_cpu(args->key_parm); + + report_hs_key(args->key_code, args->key_parm); + + return 0; + } + + case RPC_KEYPAD_SET_PWR_KEY_STATE_PROC: + /* This RPC function must be available for the ARM9 + * to function properly. This function is redundant + * when RPC_KEYPAD_PASS_KEY_CODE_PROC is handled. So + * input_report_key is not needed. + */ + return 0; + default: + return -ENODEV; + } +} + +static struct msm_rpc_server hs_rpc_server = { + .prog = HS_SERVER_PROG, + .vers = HS_SERVER_VERS, + .rpc_call = handle_hs_rpc_call, +}; + +static int process_subs_srvc_callback(struct hs_event_cb_recv *recv) +{ + if (!recv) + return -ENODATA; + + report_hs_key(be32_to_cpu(recv->key.code), be32_to_cpu(recv->key.parm)); + + return 0; +} + +static void process_hs_rpc_request(uint32_t proc, void *data) +{ + if (proc == HS_EVENT_CB_PROC) + process_subs_srvc_callback(data); + else + pr_err("%s: unknown rpc proc %d\n", __func__, proc); +} + +static int hs_rpc_report_event_arg(struct msm_rpc_client *client, + void *buffer, void *data) +{ + struct hs_event_rpc_req { + uint32_t hs_event_data_ptr; + struct hs_event_data data; + }; + + struct hs_event_rpc_req *req = buffer; + + req->hs_event_data_ptr = cpu_to_be32(0x1); + req->data.ver = cpu_to_be32(HS_EVENT_DATA_VER); + req->data.event_type = cpu_to_be32(HS_EVNT_HSD); + req->data.enum_disc = cpu_to_be32(HS_EVNT_HSD); + req->data.data_length = cpu_to_be32(0x1); + req->data.data = cpu_to_be32(*(enum hs_src_state *)data); + req->data.data_size = cpu_to_be32(sizeof(enum hs_src_state)); + + return sizeof(*req); +} + +static int hs_rpc_report_event_res(struct msm_rpc_client *client, + void *buffer, void *data) +{ + enum hs_return_value result; + + result = be32_to_cpu(*(enum hs_return_value *)buffer); + pr_debug("%s: request completed: 0x%x\n", __func__, result); + + if (result == HS_SUCCESS) + return 0; + + return 1; +} + +void report_headset_status(bool connected) +{ + int rc = -1; + enum hs_src_state status; + + if (connected == true) + status = HS_SRC_STATE_HI; + else + status = HS_SRC_STATE_LO; + + rc = msm_rpc_client_req(rpc_client, HS_REPORT_EVNT_PROC, + hs_rpc_report_event_arg, &status, + hs_rpc_report_event_res, NULL, -1); + + if (rc) + pr_err("%s: couldn't send rpc client request\n", __func__); +} +EXPORT_SYMBOL(report_headset_status); + +static int hs_rpc_pwr_cmd_arg(struct msm_rpc_client *client, + void *buffer, void *data) +{ + struct hs_cmd_data_type *hs_pwr_cmd = buffer; + + hs_pwr_cmd->hs_cmd_data_type_ptr = cpu_to_be32(0x01); + + hs_pwr_cmd->ver = cpu_to_be32(0x03); + hs_pwr_cmd->id = cpu_to_be32(HS_EXT_CMD_KPD_SET_PWR_KEY_THOLD); + hs_pwr_cmd->handle = cpu_to_be32(hs_subs_req->hs_handle_data); + hs_pwr_cmd->disc_id1 = cpu_to_be32(HS_EXT_CMD_KPD_SET_PWR_KEY_THOLD); + hs_pwr_cmd->input_ptr = cpu_to_be32(0x01); + hs_pwr_cmd->input_val = cpu_to_be32(hs->hs_pdata->pwr_key_delay_ms); + hs_pwr_cmd->input_len = cpu_to_be32(0x01); + hs_pwr_cmd->disc_id2 = cpu_to_be32(HS_EXT_CMD_KPD_SET_PWR_KEY_THOLD); + hs_pwr_cmd->output_len = cpu_to_be32(0x00); + hs_pwr_cmd->delayed = cpu_to_be32(0x00); + + return sizeof(*hs_pwr_cmd); +} + +static int hs_rpc_pwr_cmd_res(struct msm_rpc_client *client, + void *buffer, void *data) +{ + uint32_t result; + + result = be32_to_cpu(*((uint32_t *)buffer)); + pr_debug("%s: request completed: 0x%x\n", __func__, result); + + return 0; +} + +static int hs_rpc_register_subs_arg(struct msm_rpc_client *client, + void *buffer, void *data) +{ + hs_subs_req = buffer; + + hs_subs_req->hs_subs_ptr = cpu_to_be32(0x1); + hs_subs_req->hs_subs.ver = cpu_to_be32(0x1); + hs_subs_req->hs_subs.srvc = cpu_to_be32(HS_SUBS_RCV_EVNT); + hs_subs_req->hs_subs.req = cpu_to_be32(HS_SUBS_REGISTER); + hs_subs_req->hs_subs.host_os = cpu_to_be32(0x4); /* linux */ + hs_subs_req->hs_subs.disc = cpu_to_be32(HS_SUBS_RCV_EVNT); + hs_subs_req->hs_subs.id.evnt = cpu_to_be32(HS_EVNT_CLASS_ALL); + + hs_subs_req->hs_cb_id = cpu_to_be32(0x1); + + hs_subs_req->hs_handle_ptr = cpu_to_be32(0x1); + hs_subs_req->hs_handle_data = cpu_to_be32(0x0); + + return sizeof(*hs_subs_req); +} + +static int hs_rpc_register_subs_res(struct msm_rpc_client *client, + void *buffer, void *data) +{ + uint32_t result; + + result = be32_to_cpu(*((uint32_t *)buffer)); + pr_debug("%s: request completed: 0x%x\n", __func__, result); + + return 0; +} + +static int hs_cb_func(struct msm_rpc_client *client, void *buffer, int in_size) +{ + int rc = -1; + + struct rpc_request_hdr *hdr = buffer; + + hdr->type = be32_to_cpu(hdr->type); + hdr->xid = be32_to_cpu(hdr->xid); + hdr->rpc_vers = be32_to_cpu(hdr->rpc_vers); + hdr->prog = be32_to_cpu(hdr->prog); + hdr->vers = be32_to_cpu(hdr->vers); + hdr->procedure = be32_to_cpu(hdr->procedure); + + process_hs_rpc_request(hdr->procedure, + (void *) (hdr + 1)); + + msm_rpc_start_accepted_reply(client, hdr->xid, + RPC_ACCEPTSTAT_SUCCESS); + rc = msm_rpc_send_accepted_reply(client, 0); + if (rc) { + pr_err("%s: sending reply failed: %d\n", __func__, rc); + return rc; + } + + return 0; +} + +static int __init hs_rpc_cb_init(void) +{ + int rc = 0, i, num_vers; + + num_vers = ARRAY_SIZE(rpc_vers); + + for (i = 0; i < num_vers; i++) { + rpc_client = msm_rpc_register_client("hs", + HS_RPC_PROG, rpc_vers[i], 0, hs_cb_func); + + if (IS_ERR(rpc_client)) + pr_debug("%s: RPC Client version %d failed, fallback\n", + __func__, rpc_vers[i]); + else + break; + } + + if (IS_ERR(rpc_client)) { + pr_err("%s: Incompatible RPC version error %ld\n", + __func__, PTR_ERR(rpc_client)); + return PTR_ERR(rpc_client); + } + + rc = msm_rpc_client_req(rpc_client, HS_SUBSCRIBE_SRVC_PROC, + hs_rpc_register_subs_arg, NULL, + hs_rpc_register_subs_res, NULL, -1); + if (rc) { + pr_err("%s: RPC client request failed for subscribe services\n", + __func__); + goto err_client_req; + } + + rc = msm_rpc_client_req(rpc_client, HS_PROCESS_CMD_PROC, + hs_rpc_pwr_cmd_arg, NULL, + hs_rpc_pwr_cmd_res, NULL, -1); + if (rc) + pr_err("%s: RPC client request failed for pwr key" + " delay cmd, using normal mode\n", __func__); + return 0; +err_client_req: + msm_rpc_unregister_client(rpc_client); + return rc; +} + +static int __devinit hs_rpc_init(void) +{ + int rc; + + rc = hs_rpc_cb_init(); + if (rc) { + pr_err("%s: failed to initialize rpc client, try server...\n", + __func__); + + rc = msm_rpc_create_server(&hs_rpc_server); + if (rc) { + pr_err("%s: failed to create rpc server\n", __func__); + return rc; + } + } + + return rc; +} + +static void __devexit hs_rpc_deinit(void) +{ + if (rpc_client) + msm_rpc_unregister_client(rpc_client); +} + +static ssize_t msm_headset_print_name(struct switch_dev *sdev, char *buf) +{ + switch (switch_get_state(&hs->sdev)) { + case NO_DEVICE: + return sprintf(buf, "No Device\n"); + case MSM_HEADSET: + return sprintf(buf, "Headset\n"); + } + return -EINVAL; +} + +static int __devinit hs_probe(struct platform_device *pdev) +{ + int rc = 0; + struct input_dev *ipdev; + + hs = kzalloc(sizeof(struct msm_handset), GFP_KERNEL); + if (!hs) + return -ENOMEM; + + hs->sdev.name = "h2w"; + hs->sdev.print_name = msm_headset_print_name; + + rc = switch_dev_register(&hs->sdev); + if (rc) + goto err_switch_dev_register; + + ipdev = input_allocate_device(); + if (!ipdev) { + rc = -ENOMEM; + goto err_alloc_input_dev; + } + input_set_drvdata(ipdev, hs); + + hs->ipdev = ipdev; + + if (pdev->dev.platform_data) + hs->hs_pdata = pdev->dev.platform_data; + + if (hs->hs_pdata->hs_name) + ipdev->name = hs->hs_pdata->hs_name; + else + ipdev->name = DRIVER_NAME; + + ipdev->id.vendor = 0x0001; + ipdev->id.product = 1; + ipdev->id.version = 1; + + input_set_capability(ipdev, EV_KEY, KEY_MEDIA); + input_set_capability(ipdev, EV_KEY, KEY_VOLUMEUP); + input_set_capability(ipdev, EV_KEY, KEY_VOLUMEDOWN); + input_set_capability(ipdev, EV_SW, SW_HEADPHONE_INSERT); + input_set_capability(ipdev, EV_SW, SW_MICROPHONE_INSERT); + input_set_capability(ipdev, EV_KEY, KEY_POWER); + input_set_capability(ipdev, EV_KEY, KEY_END); + + rc = input_register_device(ipdev); + if (rc) { + dev_err(&ipdev->dev, + "hs_probe: input_register_device rc=%d\n", rc); + goto err_reg_input_dev; + } + + platform_set_drvdata(pdev, hs); + + rc = hs_rpc_init(); + if (rc) { + dev_err(&ipdev->dev, "rpc init failure\n"); + goto err_hs_rpc_init; + } + + return 0; + +err_hs_rpc_init: + input_unregister_device(ipdev); + ipdev = NULL; +err_reg_input_dev: + input_free_device(ipdev); +err_alloc_input_dev: + switch_dev_unregister(&hs->sdev); +err_switch_dev_register: + kfree(hs); + return rc; +} + +static int __devexit hs_remove(struct platform_device *pdev) +{ + struct msm_handset *hs = platform_get_drvdata(pdev); + + input_unregister_device(hs->ipdev); + switch_dev_unregister(&hs->sdev); + kfree(hs); + hs_rpc_deinit(); + return 0; +} + +static struct platform_driver hs_driver = { + .probe = hs_probe, + .remove = __devexit_p(hs_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init hs_init(void) +{ + return platform_driver_register(&hs_driver); +} +late_initcall(hs_init); + +static void __exit hs_exit(void) +{ + platform_driver_unregister(&hs_driver); +} +module_exit(hs_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:msm-handset"); diff --git a/arch/arm/mach-msm/rpc_server_time_remote.c b/arch/arm/mach-msm/rpc_server_time_remote.c new file mode 100644 index 00000000000..a7e68543dca --- /dev/null +++ b/arch/arm/mach-msm/rpc_server_time_remote.c @@ -0,0 +1,168 @@ +/* arch/arm/mach-msm/rpc_server_time_remote.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011 Code Aurora Forum. All rights reserved. + * Author: Iliyan Malchev + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include "rpc_server_time_remote.h" +#include +#include +#include + +/* time_remote_mtoa server definitions. */ + +#define TIME_REMOTE_MTOA_PROG 0x3000005d +#define TIME_REMOTE_MTOA_VERS_OLD 0 +#define TIME_REMOTE_MTOA_VERS 0x9202a8e4 +#define TIME_REMOTE_MTOA_VERS_COMP 0x00010002 +#define RPC_TIME_REMOTE_MTOA_NULL 0 +#define RPC_TIME_TOD_SET_APPS_BASES 2 +#define RPC_TIME_GET_APPS_USER_TIME 3 + +struct rpc_time_tod_set_apps_bases_args { + uint32_t tick; + uint64_t stamp; +}; + +static int read_rtc0_time(struct msm_rpc_server *server, + struct rpc_request_hdr *req, + unsigned len) +{ + int err; + unsigned long tm_sec; + uint32_t size = 0; + void *reply; + uint32_t output_valid; + uint32_t rpc_status = RPC_ACCEPTSTAT_SYSTEM_ERR; + struct rtc_time tm; + struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + + if (rtc == NULL) { + pr_err("%s: unable to open rtc device (%s)\n", + __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); + goto send_reply; + } + + err = rtc_read_time(rtc, &tm); + if (err) { + pr_err("%s: Error reading rtc device (%s) : %d\n", + __FILE__, CONFIG_RTC_HCTOSYS_DEVICE, err); + goto close_dev; + } + + err = rtc_valid_tm(&tm); + if (err) { + pr_err("%s: Invalid RTC time (%s)\n", + __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); + goto close_dev; + } + + rtc_tm_to_time(&tm, &tm_sec); + rpc_status = RPC_ACCEPTSTAT_SUCCESS; + +close_dev: + rtc_class_close(rtc); + +send_reply: + reply = msm_rpc_server_start_accepted_reply(server, req->xid, + rpc_status); + if (rpc_status == RPC_ACCEPTSTAT_SUCCESS) { + output_valid = *((uint32_t *)(req + 1)); + *(uint32_t *)reply = output_valid; + size = sizeof(uint32_t); + if (be32_to_cpu(output_valid)) { + reply += sizeof(uint32_t); + *(uint32_t *)reply = cpu_to_be32(tm_sec); + size += sizeof(uint32_t); + } + } + err = msm_rpc_server_send_accepted_reply(server, size); + if (err) + pr_err("%s: send accepted reply failed: %d\n", __func__, err); + + return 1; +} + +static int handle_rpc_call(struct msm_rpc_server *server, + struct rpc_request_hdr *req, unsigned len) +{ + struct timespec ts, tv; + + switch (req->procedure) { + case RPC_TIME_REMOTE_MTOA_NULL: + return 0; + + case RPC_TIME_TOD_SET_APPS_BASES: { + struct rpc_time_tod_set_apps_bases_args *args; + args = (struct rpc_time_tod_set_apps_bases_args *)(req + 1); + args->tick = be32_to_cpu(args->tick); + args->stamp = be64_to_cpu(args->stamp); + printk(KERN_INFO "RPC_TIME_TOD_SET_APPS_BASES:\n" + "\ttick = %d\n" + "\tstamp = %lld\n", + args->tick, args->stamp); + getnstimeofday(&ts); + msmrtc_updateatsuspend(&ts); + rtc_hctosys(); + getnstimeofday(&tv); + /* Update the alarm information with the new time info. */ + alarm_update_timedelta(ts, tv); + return 0; + } + + case RPC_TIME_GET_APPS_USER_TIME: + return read_rtc0_time(server, req, len); + + default: + return -ENODEV; + } +} + +static struct msm_rpc_server rpc_server[] = { + { + .prog = TIME_REMOTE_MTOA_PROG, + .vers = TIME_REMOTE_MTOA_VERS_OLD, + .rpc_call = handle_rpc_call, + }, + { + .prog = TIME_REMOTE_MTOA_PROG, + .vers = TIME_REMOTE_MTOA_VERS, + .rpc_call = handle_rpc_call, + }, + { + .prog = TIME_REMOTE_MTOA_PROG, + .vers = TIME_REMOTE_MTOA_VERS_COMP, + .rpc_call = handle_rpc_call, + }, +}; + +static int __init rpc_server_init(void) +{ + /* Dual server registration to support backwards compatibility vers */ + int ret; + ret = msm_rpc_create_server(&rpc_server[2]); + if (ret < 0) + return ret; + ret = msm_rpc_create_server(&rpc_server[1]); + if (ret < 0) + return ret; + return msm_rpc_create_server(&rpc_server[0]); +} + + +module_init(rpc_server_init); diff --git a/arch/arm/mach-msm/rpc_server_time_remote.h b/arch/arm/mach-msm/rpc_server_time_remote.h new file mode 100644 index 00000000000..056666f5001 --- /dev/null +++ b/arch/arm/mach-msm/rpc_server_time_remote.h @@ -0,0 +1,21 @@ +/* arch/arm/mach-msm/rpc_server_time_remote.h + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPC_SERVER_TIME_REMOTE_H +#define __ARCH_ARM_MACH_MSM_RPC_SERVER_TIME_REMOTE_H + +int rtc_hctosys(void); + +#endif diff --git a/arch/arm/mach-msm/rpcrouter_sdio_xprt.c b/arch/arm/mach-msm/rpcrouter_sdio_xprt.c new file mode 100644 index 00000000000..f3cff621057 --- /dev/null +++ b/arch/arm/mach-msm/rpcrouter_sdio_xprt.c @@ -0,0 +1,656 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * RPCROUTER SDIO XPRT module. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "smd_rpcrouter.h" + +enum { + MSM_SDIO_XPRT_DEBUG = 1U << 0, + MSM_SDIO_XPRT_INFO = 1U << 1, +}; + +static int msm_sdio_xprt_debug_mask; +module_param_named(debug_mask, msm_sdio_xprt_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#if defined(CONFIG_MSM_RPC_SDIO_DEBUG) +#define SDIO_XPRT_DBG(x...) do { \ + if (msm_sdio_xprt_debug_mask & MSM_SDIO_XPRT_DEBUG) \ + printk(KERN_DEBUG x); \ + } while (0) + +#define SDIO_XPRT_INFO(x...) do { \ + if (msm_sdio_xprt_debug_mask & MSM_SDIO_XPRT_INFO) \ + printk(KERN_INFO x); \ + } while (0) +#else +#define SDIO_XPRT_DBG(x...) do { } while (0) +#define SDIO_XPRT_INFO(x...) do { } while (0) +#endif + +#define MAX_SDIO_WRITE_RETRY 5 +#define SDIO_BUF_SIZE (RPCROUTER_MSGSIZE_MAX + sizeof(struct rr_header) - 8) +#define NUM_SDIO_BUFS 20 +#define MAX_TX_BUFS 10 +#define MAX_RX_BUFS 10 + +struct sdio_xprt { + struct sdio_channel *handle; + + struct list_head write_list; + spinlock_t write_list_lock; + + struct list_head read_list; + spinlock_t read_list_lock; + + struct list_head free_list; + spinlock_t free_list_lock; + + struct wake_lock read_wakelock; +}; + +struct rpcrouter_sdio_xprt { + struct rpcrouter_xprt xprt; + struct sdio_xprt *channel; +}; + +static struct rpcrouter_sdio_xprt sdio_remote_xprt; + +static void sdio_xprt_read_data(struct work_struct *work); +static DECLARE_DELAYED_WORK(work_read_data, sdio_xprt_read_data); +static struct workqueue_struct *sdio_xprt_read_workqueue; + +struct sdio_buf_struct { + struct list_head list; + uint32_t size; + uint32_t read_index; + uint32_t write_index; + unsigned char data[SDIO_BUF_SIZE]; +}; + +static void sdio_xprt_write_data(struct work_struct *work); +static DECLARE_WORK(work_write_data, sdio_xprt_write_data); +static wait_queue_head_t write_avail_wait_q; +static uint32_t num_free_bufs; +static uint32_t num_tx_bufs; +static uint32_t num_rx_bufs; + +static DEFINE_MUTEX(modem_reset_lock); +static uint32_t modem_reset; + +static void free_sdio_xprt(struct sdio_xprt *chnl) +{ + struct sdio_buf_struct *buf; + unsigned long flags; + + if (!chnl) { + printk(KERN_ERR "Invalid chnl to free\n"); + return; + } + + spin_lock_irqsave(&chnl->free_list_lock, flags); + while (!list_empty(&chnl->free_list)) { + buf = list_first_entry(&chnl->free_list, + struct sdio_buf_struct, list); + list_del(&buf->list); + kfree(buf); + } + num_free_bufs = 0; + spin_unlock_irqrestore(&chnl->free_list_lock, flags); + + spin_lock_irqsave(&chnl->write_list_lock, flags); + while (!list_empty(&chnl->write_list)) { + buf = list_first_entry(&chnl->write_list, + struct sdio_buf_struct, list); + list_del(&buf->list); + kfree(buf); + } + num_tx_bufs = 0; + spin_unlock_irqrestore(&chnl->write_list_lock, flags); + + spin_lock_irqsave(&chnl->read_list_lock, flags); + while (!list_empty(&chnl->read_list)) { + buf = list_first_entry(&chnl->read_list, + struct sdio_buf_struct, list); + list_del(&buf->list); + kfree(buf); + } + num_rx_bufs = 0; + spin_unlock_irqrestore(&chnl->read_list_lock, flags); + wake_unlock(&chnl->read_wakelock); +} + +static struct sdio_buf_struct *alloc_from_free_list(struct sdio_xprt *chnl) +{ + struct sdio_buf_struct *buf; + unsigned long flags; + + spin_lock_irqsave(&chnl->free_list_lock, flags); + if (list_empty(&chnl->free_list)) { + spin_unlock_irqrestore(&chnl->free_list_lock, flags); + SDIO_XPRT_DBG("%s: Free list empty\n", __func__); + return NULL; + } + buf = list_first_entry(&chnl->free_list, struct sdio_buf_struct, list); + list_del(&buf->list); + num_free_bufs--; + spin_unlock_irqrestore(&chnl->free_list_lock, flags); + + buf->size = 0; + buf->read_index = 0; + buf->write_index = 0; + + return buf; +} + +static void return_to_free_list(struct sdio_xprt *chnl, + struct sdio_buf_struct *buf) +{ + unsigned long flags; + + if (!chnl || !buf) { + pr_err("%s: Invalid chnl or buf\n", __func__); + return; + } + + buf->size = 0; + buf->read_index = 0; + buf->write_index = 0; + + spin_lock_irqsave(&chnl->free_list_lock, flags); + list_add_tail(&buf->list, &chnl->free_list); + num_free_bufs++; + spin_unlock_irqrestore(&chnl->free_list_lock, flags); + +} + +static int rpcrouter_sdio_remote_read_avail(void) +{ + int read_avail = 0; + unsigned long flags; + struct sdio_buf_struct *buf; + + spin_lock_irqsave(&sdio_remote_xprt.channel->read_list_lock, flags); + list_for_each_entry(buf, &sdio_remote_xprt.channel->read_list, list) { + read_avail += buf->size; + } + spin_unlock_irqrestore(&sdio_remote_xprt.channel->read_list_lock, + flags); + return read_avail; +} + +static int rpcrouter_sdio_remote_read(void *data, uint32_t len) +{ + struct sdio_buf_struct *buf; + unsigned char *buf_data; + unsigned long flags; + + SDIO_XPRT_DBG("sdio_xprt Called %s\n", __func__); + if (len < 0 || !data) + return -EINVAL; + else if (len == 0) + return 0; + + spin_lock_irqsave(&sdio_remote_xprt.channel->read_list_lock, flags); + if (list_empty(&sdio_remote_xprt.channel->read_list)) { + spin_unlock_irqrestore( + &sdio_remote_xprt.channel->read_list_lock, flags); + return -EINVAL; + } + + buf = list_first_entry(&sdio_remote_xprt.channel->read_list, + struct sdio_buf_struct, list); + if (buf->size < len) { + spin_unlock_irqrestore( + &sdio_remote_xprt.channel->read_list_lock, flags); + return -EINVAL; + } + + buf_data = buf->data + buf->read_index; + memcpy(data, buf_data, len); + buf->read_index += len; + buf->size -= len; + if (buf->size == 0) { + list_del(&buf->list); + num_rx_bufs--; + return_to_free_list(sdio_remote_xprt.channel, buf); + } + + if (list_empty(&sdio_remote_xprt.channel->read_list)) + wake_unlock(&sdio_remote_xprt.channel->read_wakelock); + spin_unlock_irqrestore(&sdio_remote_xprt.channel->read_list_lock, + flags); + return len; +} + +static int rpcrouter_sdio_remote_write_avail(void) +{ + uint32_t write_avail = 0; + unsigned long flags; + + SDIO_XPRT_DBG("sdio_xprt Called %s\n", __func__); + spin_lock_irqsave(&sdio_remote_xprt.channel->write_list_lock, flags); + write_avail = (MAX_TX_BUFS - num_tx_bufs) * SDIO_BUF_SIZE; + spin_unlock_irqrestore(&sdio_remote_xprt.channel->write_list_lock, + flags); + return write_avail; +} + +static int rpcrouter_sdio_remote_write(void *data, uint32_t len, + enum write_data_type type) +{ + unsigned long flags; + static struct sdio_buf_struct *buf; + unsigned char *buf_data; + + switch (type) { + case HEADER: + spin_lock_irqsave(&sdio_remote_xprt.channel->write_list_lock, + flags); + if (num_tx_bufs == MAX_TX_BUFS) { + spin_unlock_irqrestore( + &sdio_remote_xprt.channel->write_list_lock, + flags); + return -ENOMEM; + } + spin_unlock_irqrestore( + &sdio_remote_xprt.channel->write_list_lock, flags); + + SDIO_XPRT_DBG("sdio_xprt WRITE HEADER %s\n", __func__); + buf = alloc_from_free_list(sdio_remote_xprt.channel); + if (!buf) { + pr_err("%s: alloc_from_free_list failed\n", __func__); + return -ENOMEM; + } + buf_data = buf->data + buf->write_index; + memcpy(buf_data, data, len); + buf->write_index += len; + buf->size += len; + return len; + case PACKMARK: + SDIO_XPRT_DBG("sdio_xprt WRITE PACKMARK %s\n", __func__); + if (!buf) { + pr_err("%s: HEADER not written or alloc failed\n", + __func__); + return -ENOMEM; + } + buf_data = buf->data + buf->write_index; + memcpy(buf_data, data, len); + buf->write_index += len; + buf->size += len; + return len; + case PAYLOAD: + SDIO_XPRT_DBG("sdio_xprt WRITE PAYLOAD %s\n", __func__); + if (!buf) { + pr_err("%s: HEADER not written or alloc failed\n", + __func__); + return -ENOMEM; + } + buf_data = buf->data + buf->write_index; + memcpy(buf_data, data, len); + buf->write_index += len; + buf->size += len; + + SDIO_XPRT_DBG("sdio_xprt flush %d bytes\n", buf->size); + spin_lock_irqsave(&sdio_remote_xprt.channel->write_list_lock, + flags); + list_add_tail(&buf->list, + &sdio_remote_xprt.channel->write_list); + num_tx_bufs++; + spin_unlock_irqrestore( + &sdio_remote_xprt.channel->write_list_lock, flags); + queue_work(sdio_xprt_read_workqueue, &work_write_data); + buf = NULL; + return len; + default: + return -EINVAL; + } +} + +static void sdio_xprt_write_data(struct work_struct *work) +{ + int rc = 0, sdio_write_retry = 0; + unsigned long flags; + struct sdio_buf_struct *buf; + + mutex_lock(&modem_reset_lock); + if (modem_reset) { + mutex_unlock(&modem_reset_lock); + return; + } + + spin_lock_irqsave(&sdio_remote_xprt.channel->write_list_lock, flags); + while (!list_empty(&sdio_remote_xprt.channel->write_list)) { + buf = list_first_entry(&sdio_remote_xprt.channel->write_list, + struct sdio_buf_struct, list); + list_del(&buf->list); + spin_unlock_irqrestore( + &sdio_remote_xprt.channel->write_list_lock, flags); + mutex_unlock(&modem_reset_lock); + + wait_event(write_avail_wait_q, + (!(modem_reset) && (sdio_write_avail( + sdio_remote_xprt.channel->handle) >= + buf->size))); + + mutex_lock(&modem_reset_lock); + while (!(modem_reset) && + ((rc = sdio_write(sdio_remote_xprt.channel->handle, + buf->data, buf->size)) < 0) && + (sdio_write_retry++ < MAX_SDIO_WRITE_RETRY)) { + printk(KERN_ERR "sdio_write failed with RC %d\n", rc); + mutex_unlock(&modem_reset_lock); + msleep(250); + mutex_lock(&modem_reset_lock); + } + if (modem_reset) { + mutex_unlock(&modem_reset_lock); + kfree(buf); + return; + } else { + return_to_free_list(sdio_remote_xprt.channel, buf); + } + + if (!rc) + SDIO_XPRT_DBG("sdio_write %d bytes completed\n", + buf->size); + + spin_lock_irqsave(&sdio_remote_xprt.channel->write_list_lock, + flags); + num_tx_bufs--; + } + spin_unlock_irqrestore(&sdio_remote_xprt.channel->write_list_lock, + flags); + mutex_unlock(&modem_reset_lock); +} + +static int rpcrouter_sdio_remote_close(void) +{ + SDIO_XPRT_DBG("sdio_xprt Called %s\n", __func__); + flush_workqueue(sdio_xprt_read_workqueue); + sdio_close(sdio_remote_xprt.channel->handle); + free_sdio_xprt(sdio_remote_xprt.channel); + return 0; +} + +static void sdio_xprt_read_data(struct work_struct *work) +{ + int size = 0, read_avail; + unsigned long flags; + struct sdio_buf_struct *buf; + SDIO_XPRT_DBG("sdio_xprt Called %s\n", __func__); + + mutex_lock(&modem_reset_lock); + while (!(modem_reset) && + ((read_avail = + sdio_read_avail(sdio_remote_xprt.channel->handle)) > 0)) { + spin_lock_irqsave(&sdio_remote_xprt.channel->read_list_lock, + flags); + if (num_rx_bufs == MAX_RX_BUFS) { + spin_unlock_irqrestore( + &sdio_remote_xprt.channel->read_list_lock, + flags); + queue_delayed_work(sdio_xprt_read_workqueue, + &work_read_data, + msecs_to_jiffies(100)); + break; + } + spin_unlock_irqrestore( + &sdio_remote_xprt.channel->read_list_lock, flags); + + buf = alloc_from_free_list(sdio_remote_xprt.channel); + if (!buf) { + SDIO_XPRT_DBG("%s: Failed to alloc_from_free_list" + " Try again later\n", __func__); + queue_delayed_work(sdio_xprt_read_workqueue, + &work_read_data, + msecs_to_jiffies(100)); + break; + } + + size = sdio_read(sdio_remote_xprt.channel->handle, + buf->data, read_avail); + if (size < 0) { + printk(KERN_ERR "sdio_read failed," + " read %d bytes, expected %d\n", + size, read_avail); + return_to_free_list(sdio_remote_xprt.channel, buf); + queue_delayed_work(sdio_xprt_read_workqueue, + &work_read_data, + msecs_to_jiffies(100)); + break; + } + + if (size == 0) + size = read_avail; + + buf->size = size; + buf->write_index = size; + spin_lock_irqsave(&sdio_remote_xprt.channel->read_list_lock, + flags); + list_add_tail(&buf->list, + &sdio_remote_xprt.channel->read_list); + num_rx_bufs++; + spin_unlock_irqrestore( + &sdio_remote_xprt.channel->read_list_lock, flags); + wake_lock(&sdio_remote_xprt.channel->read_wakelock); + } + + if (!modem_reset && !list_empty(&sdio_remote_xprt.channel->read_list)) + msm_rpcrouter_xprt_notify(&sdio_remote_xprt.xprt, + RPCROUTER_XPRT_EVENT_DATA); + mutex_unlock(&modem_reset_lock); +} + +static void rpcrouter_sdio_remote_notify(void *_dev, unsigned event) +{ + if (event == SDIO_EVENT_DATA_READ_AVAIL) { + SDIO_XPRT_DBG("%s Received Notify" + "SDIO_EVENT_DATA_READ_AVAIL\n", __func__); + queue_delayed_work(sdio_xprt_read_workqueue, + &work_read_data, 0); + } + if (event == SDIO_EVENT_DATA_WRITE_AVAIL) { + SDIO_XPRT_DBG("%s Received Notify" + "SDIO_EVENT_DATA_WRITE_AVAIL\n", __func__); + wake_up(&write_avail_wait_q); + } +} + +static int allocate_sdio_xprt(struct sdio_xprt **sdio_xprt_chnl) +{ + struct sdio_buf_struct *buf; + struct sdio_xprt *chnl; + int i; + unsigned long flags; + int rc = -ENOMEM; + + if (!(*sdio_xprt_chnl)) { + chnl = kmalloc(sizeof(struct sdio_xprt), GFP_KERNEL); + if (!chnl) { + printk(KERN_ERR "sdio_xprt channel" + " allocation failed\n"); + return rc; + } + + spin_lock_init(&chnl->write_list_lock); + spin_lock_init(&chnl->read_list_lock); + spin_lock_init(&chnl->free_list_lock); + + INIT_LIST_HEAD(&chnl->write_list); + INIT_LIST_HEAD(&chnl->read_list); + INIT_LIST_HEAD(&chnl->free_list); + wake_lock_init(&chnl->read_wakelock, + WAKE_LOCK_SUSPEND, "rpc_sdio_xprt_read"); + } else { + chnl = *sdio_xprt_chnl; + } + + for (i = 0; i < NUM_SDIO_BUFS; i++) { + buf = kzalloc(sizeof(struct sdio_buf_struct), GFP_KERNEL); + if (!buf) { + printk(KERN_ERR "sdio_buf_struct alloc failed\n"); + goto alloc_failure; + } + spin_lock_irqsave(&chnl->free_list_lock, flags); + list_add_tail(&buf->list, &chnl->free_list); + spin_unlock_irqrestore(&chnl->free_list_lock, flags); + } + num_free_bufs = NUM_SDIO_BUFS; + + *sdio_xprt_chnl = chnl; + return 0; + +alloc_failure: + spin_lock_irqsave(&chnl->free_list_lock, flags); + while (!list_empty(&chnl->free_list)) { + buf = list_first_entry(&chnl->free_list, + struct sdio_buf_struct, + list); + list_del(&buf->list); + kfree(buf); + } + spin_unlock_irqrestore(&chnl->free_list_lock, flags); + wake_lock_destroy(&chnl->read_wakelock); + + kfree(chnl); + *sdio_xprt_chnl = NULL; + return rc; +} + +static int rpcrouter_sdio_remote_probe(struct platform_device *pdev) +{ + int rc; + + SDIO_XPRT_INFO("%s Called\n", __func__); + + mutex_lock(&modem_reset_lock); + if (!modem_reset) { + sdio_xprt_read_workqueue = + create_singlethread_workqueue("sdio_xprt"); + if (!sdio_xprt_read_workqueue) { + mutex_unlock(&modem_reset_lock); + return -ENOMEM; + } + + sdio_remote_xprt.xprt.name = "rpcrotuer_sdio_xprt"; + sdio_remote_xprt.xprt.read_avail = + rpcrouter_sdio_remote_read_avail; + sdio_remote_xprt.xprt.read = rpcrouter_sdio_remote_read; + sdio_remote_xprt.xprt.write_avail = + rpcrouter_sdio_remote_write_avail; + sdio_remote_xprt.xprt.write = rpcrouter_sdio_remote_write; + sdio_remote_xprt.xprt.close = rpcrouter_sdio_remote_close; + sdio_remote_xprt.xprt.priv = NULL; + + init_waitqueue_head(&write_avail_wait_q); + } + mutex_unlock(&modem_reset_lock); + + rc = allocate_sdio_xprt(&sdio_remote_xprt.channel); + if (rc) { + destroy_workqueue(sdio_xprt_read_workqueue); + return rc; + } + + /* Open up SDIO channel */ + rc = sdio_open("SDIO_RPC", &sdio_remote_xprt.channel->handle, NULL, + rpcrouter_sdio_remote_notify); + + if (rc < 0) { + free_sdio_xprt(sdio_remote_xprt.channel); + destroy_workqueue(sdio_xprt_read_workqueue); + return rc; + } + + mutex_lock(&modem_reset_lock); + modem_reset = 0; + mutex_unlock(&modem_reset_lock); + + msm_rpcrouter_xprt_notify(&sdio_remote_xprt.xprt, + RPCROUTER_XPRT_EVENT_OPEN); + + SDIO_XPRT_INFO("%s Completed\n", __func__); + + return 0; +} + +static int rpcrouter_sdio_remote_remove(struct platform_device *pdev) +{ + SDIO_XPRT_INFO("%s Called\n", __func__); + + mutex_lock(&modem_reset_lock); + modem_reset = 1; + wake_up(&write_avail_wait_q); + free_sdio_xprt(sdio_remote_xprt.channel); + mutex_unlock(&modem_reset_lock); + + msm_rpcrouter_xprt_notify(&sdio_remote_xprt.xprt, + RPCROUTER_XPRT_EVENT_CLOSE); + + SDIO_XPRT_INFO("%s Completed\n", __func__); + + return 0; +} + +/*Remove this platform driver after mainline of SDIO_AL update*/ +static struct platform_driver rpcrouter_sdio_remote_driver = { + .probe = rpcrouter_sdio_remote_probe, + .driver = { + .name = "SDIO_AL", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver rpcrouter_sdio_driver = { + .probe = rpcrouter_sdio_remote_probe, + .remove = rpcrouter_sdio_remote_remove, + .driver = { + .name = "SDIO_RPC", + .owner = THIS_MODULE, + }, +}; + +static int __init rpcrouter_sdio_init(void) +{ + int rc; + msm_sdio_xprt_debug_mask = 0x2; + rc = platform_driver_register(&rpcrouter_sdio_remote_driver); + if (rc < 0) + return rc; + return platform_driver_register(&rpcrouter_sdio_driver); +} + +module_init(rpcrouter_sdio_init); +MODULE_DESCRIPTION("RPC Router SDIO XPRT"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/rpcrouter_smd_xprt.c b/arch/arm/mach-msm/rpcrouter_smd_xprt.c new file mode 100644 index 00000000000..c7fba008a2c --- /dev/null +++ b/arch/arm/mach-msm/rpcrouter_smd_xprt.c @@ -0,0 +1,331 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * RPCROUTER SMD XPRT module. + */ + +#include +#include + +#include +#include "smd_rpcrouter.h" +#include "smd_private.h" + +struct rpcrouter_smd_xprt { + struct rpcrouter_xprt xprt; + + smd_channel_t *channel; +}; + +static struct rpcrouter_smd_xprt smd_remote_xprt; +#ifdef CONFIG_ARCH_FSM9XXX +static struct rpcrouter_smd_xprt smd_remote_qdsp_xprt; +#endif + +static int rpcrouter_smd_remote_read_avail(void) +{ + return smd_read_avail(smd_remote_xprt.channel); +} + +static int rpcrouter_smd_remote_read(void *data, uint32_t len) +{ + return smd_read(smd_remote_xprt.channel, data, len); +} + +static int rpcrouter_smd_remote_write_avail(void) +{ + return smd_write_avail(smd_remote_xprt.channel); +} + +static int rpcrouter_smd_remote_write(void *data, uint32_t len, uint32_t type) +{ + return smd_write(smd_remote_xprt.channel, data, len); +} + +static int rpcrouter_smd_remote_close(void) +{ + smsm_change_state(SMSM_APPS_STATE, SMSM_RPCINIT, 0); + return smd_close(smd_remote_xprt.channel); +} + +static void rpcrouter_smd_remote_notify(void *_dev, unsigned event) +{ + switch (event) { + case SMD_EVENT_DATA: + msm_rpcrouter_xprt_notify(&smd_remote_xprt.xprt, + RPCROUTER_XPRT_EVENT_DATA); + break; + + case SMD_EVENT_OPEN: + pr_info("%s: smd opened 0x%p\n", __func__, _dev); + + msm_rpcrouter_xprt_notify(&smd_remote_xprt.xprt, + RPCROUTER_XPRT_EVENT_OPEN); + break; + + case SMD_EVENT_CLOSE: + pr_info("%s: smd closed 0x%p\n", __func__, _dev); + + msm_rpcrouter_xprt_notify(&smd_remote_xprt.xprt, + RPCROUTER_XPRT_EVENT_CLOSE); + break; + } +} + +#ifdef CONFIG_ARCH_FSM9XXX +static int rpcrouter_smd_remote_qdsp_read_avail(void) +{ + return smd_read_avail(smd_remote_qdsp_xprt.channel); +} + +static int rpcrouter_smd_remote_qdsp_read(void *data, uint32_t len) +{ + return smd_read(smd_remote_qdsp_xprt.channel, data, len); +} + +static int rpcrouter_smd_remote_qdsp_write_avail(void) +{ + return smd_write_avail(smd_remote_qdsp_xprt.channel); +} + +static int rpcrouter_smd_remote_qdsp_write(void *data, + uint32_t len, uint32_t type) +{ + return smd_write(smd_remote_qdsp_xprt.channel, data, len); +} + +static int rpcrouter_smd_remote_qdsp_close(void) +{ + /* + * TBD: Implement when we have N way SMSM ported + * smsm_change_state(SMSM_APPS_STATE, SMSM_RPCINIT, 0); + */ + return smd_close(smd_remote_qdsp_xprt.channel); +} + +static void rpcrouter_smd_remote_qdsp_notify(void *_dev, unsigned event) +{ + switch (event) { + case SMD_EVENT_DATA: + msm_rpcrouter_xprt_notify(&smd_remote_qdsp_xprt.xprt, + RPCROUTER_XPRT_EVENT_DATA); + break; + + case SMD_EVENT_OPEN: + /* Print log info */ + pr_debug("%s: smd opened\n", __func__); + + msm_rpcrouter_xprt_notify(&smd_remote_qdsp_xprt.xprt, + RPCROUTER_XPRT_EVENT_OPEN); + break; + + case SMD_EVENT_CLOSE: + /* Print log info */ + pr_debug("%s: smd closed\n", __func__); + + msm_rpcrouter_xprt_notify(&smd_remote_qdsp_xprt.xprt, + RPCROUTER_XPRT_EVENT_CLOSE); + break; + } +} + +static int rpcrouter_smd_remote_qdsp_probe(struct platform_device *pdev) +{ + int rc; + + smd_remote_qdsp_xprt.xprt.name = "rpcrotuer_smd_qdsp_xprt"; + smd_remote_qdsp_xprt.xprt.read_avail = + rpcrouter_smd_remote_qdsp_read_avail; + smd_remote_qdsp_xprt.xprt.read = rpcrouter_smd_remote_qdsp_read; + smd_remote_qdsp_xprt.xprt.write_avail = + rpcrouter_smd_remote_qdsp_write_avail; + smd_remote_qdsp_xprt.xprt.write = rpcrouter_smd_remote_qdsp_write; + smd_remote_qdsp_xprt.xprt.close = rpcrouter_smd_remote_qdsp_close; + smd_remote_qdsp_xprt.xprt.priv = NULL; + + /* Open up SMD channel */ + rc = smd_named_open_on_edge("RPCCALL_QDSP", SMD_APPS_QDSP, + &smd_remote_qdsp_xprt.channel, NULL, + rpcrouter_smd_remote_qdsp_notify); + if (rc < 0) + return rc; + + smd_disable_read_intr(smd_remote_qdsp_xprt.channel); + + return 0; +} + +static struct platform_driver rpcrouter_smd_remote_qdsp_driver = { + .probe = rpcrouter_smd_remote_qdsp_probe, + .driver = { + .name = "RPCCALL_QDSP", + .owner = THIS_MODULE, + }, +}; + +static inline int register_smd_remote_qpsp_driver(void) +{ + return platform_driver_register(&rpcrouter_smd_remote_qdsp_driver); +} +#else /* CONFIG_ARCH_FSM9XXX */ +static inline int register_smd_remote_qpsp_driver(void) +{ + return 0; +} +#endif + +#if defined(CONFIG_MSM_RPC_LOOPBACK_XPRT) + +static struct rpcrouter_smd_xprt smd_loopback_xprt; + +static int rpcrouter_smd_loopback_read_avail(void) +{ + return smd_read_avail(smd_loopback_xprt.channel); +} + +static int rpcrouter_smd_loopback_read(void *data, uint32_t len) +{ + return smd_read(smd_loopback_xprt.channel, data, len); +} + +static int rpcrouter_smd_loopback_write_avail(void) +{ + return smd_write_avail(smd_loopback_xprt.channel); +} + +static int rpcrouter_smd_loopback_write(void *data, uint32_t len, uint32 type) +{ + return smd_write(smd_loopback_xprt.channel, data, len); +} + +static int rpcrouter_smd_loopback_close(void) +{ + return smd_close(smd_loopback_xprt.channel); +} + +static void rpcrouter_smd_loopback_notify(void *_dev, unsigned event) +{ + switch (event) { + case SMD_EVENT_DATA: + msm_rpcrouter_xprt_notify(&smd_loopback_xprt.xprt, + RPCROUTER_XPRT_EVENT_DATA); + break; + + case SMD_EVENT_OPEN: + pr_debug("%s: smd loopback opened 0x%p\n", __func__, _dev); + + msm_rpcrouter_xprt_notify(&smd_loopback_xprt.xprt, + RPCROUTER_XPRT_EVENT_OPEN); + break; + + case SMD_EVENT_CLOSE: + pr_debug("%s: smd loopback closed 0x%p\n", __func__, _dev); + + msm_rpcrouter_xprt_notify(&smd_loopback_xprt.xprt, + RPCROUTER_XPRT_EVENT_CLOSE); + break; + } +} + +static int rpcrouter_smd_loopback_probe(struct platform_device *pdev) +{ + int rc; + + smd_loopback_xprt.xprt.name = "rpcrouter_loopback_xprt"; + smd_loopback_xprt.xprt.read_avail = rpcrouter_smd_loopback_read_avail; + smd_loopback_xprt.xprt.read = rpcrouter_smd_loopback_read; + smd_loopback_xprt.xprt.write_avail = rpcrouter_smd_loopback_write_avail; + smd_loopback_xprt.xprt.write = rpcrouter_smd_loopback_write; + smd_loopback_xprt.xprt.close = rpcrouter_smd_loopback_close; + smd_loopback_xprt.xprt.priv = NULL; + + /* Open up SMD LOOPBACK channel */ + rc = smd_named_open_on_edge("local_loopback", SMD_LOOPBACK_TYPE, + &smd_loopback_xprt.channel, NULL, + rpcrouter_smd_loopback_notify); + if (rc < 0) + return rc; + + smd_disable_read_intr(smd_remote_xprt.channel); + return 0; +} + +static struct platform_driver rpcrouter_smd_loopback_driver = { + .probe = rpcrouter_smd_loopback_probe, + .driver = { + .name = "local_loopback", + .owner = THIS_MODULE, + }, +}; + +static inline int register_smd_loopback_driver(void) +{ + return platform_driver_register(&rpcrouter_smd_loopback_driver); +} +#else /* CONFIG_MSM_RPC_LOOPBACK_XPRT */ +static inline int register_smd_loopback_driver(void) +{ + return 0; +} +#endif + +static int rpcrouter_smd_remote_probe(struct platform_device *pdev) +{ + int rc; + + smd_remote_xprt.xprt.name = "rpcrotuer_smd_xprt"; + smd_remote_xprt.xprt.read_avail = rpcrouter_smd_remote_read_avail; + smd_remote_xprt.xprt.read = rpcrouter_smd_remote_read; + smd_remote_xprt.xprt.write_avail = rpcrouter_smd_remote_write_avail; + smd_remote_xprt.xprt.write = rpcrouter_smd_remote_write; + smd_remote_xprt.xprt.close = rpcrouter_smd_remote_close; + smd_remote_xprt.xprt.priv = NULL; + + /* Open up SMD channel */ + rc = smd_open("RPCCALL", &smd_remote_xprt.channel, NULL, + rpcrouter_smd_remote_notify); + if (rc < 0) + return rc; + + smd_disable_read_intr(smd_remote_xprt.channel); + + return 0; +} + +static struct platform_driver rpcrouter_smd_remote_driver = { + .probe = rpcrouter_smd_remote_probe, + .driver = { + .name = "RPCCALL", + .owner = THIS_MODULE, + }, +}; + +static int __init rpcrouter_smd_init(void) +{ + int rc; + + rc = register_smd_loopback_driver(); + if (rc < 0) + return rc; + + rc = register_smd_remote_qpsp_driver(); + if (rc < 0) + return rc; + + return platform_driver_register(&rpcrouter_smd_remote_driver); +} + +module_init(rpcrouter_smd_init); +MODULE_DESCRIPTION("RPC Router SMD XPRT"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/rpm-regulator-8960.c b/arch/arm/mach-msm/rpm-regulator-8960.c new file mode 100644 index 00000000000..619081522bf --- /dev/null +++ b/arch/arm/mach-msm/rpm-regulator-8960.c @@ -0,0 +1,1663 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rpm_resources.h" + +/* Debug Definitions */ + +enum { + MSM_RPM_VREG_DEBUG_REQUEST = BIT(0), + MSM_RPM_VREG_DEBUG_VOTE = BIT(1), + MSM_RPM_VREG_DEBUG_DUPLICATE = BIT(2), + MSM_RPM_VREG_DEBUG_IGNORE_VDD_MEM_DIG = BIT(3), +}; + +static int msm_rpm_vreg_debug_mask; +module_param_named( + debug_mask, msm_rpm_vreg_debug_mask, int, S_IRUSR | S_IWUSR +); + +#define REGULATOR_TYPE_LDO 0 +#define REGULATOR_TYPE_SMPS 1 +#define REGULATOR_TYPE_VS 2 +#define REGULATOR_TYPE_NCP 3 + +#define MICRO_TO_MILLI(uV) ((uV) / 1000) +#define MILLI_TO_MICRO(mV) ((mV) * 1000) + +#define SET_PART(_vreg, _part, _val) \ + _vreg->req[_vreg->part->_part.word].value \ + = (_vreg->req[_vreg->part->_part.word].value \ + & ~vreg->part->_part.mask) \ + | (((_val) << vreg->part->_part.shift) & vreg->part->_part.mask) + +#define GET_PART(_vreg, _part) \ + ((_vreg->req[_vreg->part->_part.word].value & vreg->part->_part.mask) \ + >> vreg->part->_part.shift) + +struct request_member { + int word; + unsigned int mask; + int shift; +}; + +struct rpm_vreg_parts { + struct request_member mV; /* voltage: used if voltage is in mV */ + struct request_member uV; /* voltage: used if voltage is in uV */ + struct request_member ip; /* peak current in mA */ + struct request_member pd; /* pull down enable */ + struct request_member ia; /* average current in mA */ + struct request_member fm; /* force mode */ + struct request_member pm; /* power mode */ + struct request_member pc; /* pin control */ + struct request_member pf; /* pin function */ + struct request_member enable_state; /* NCP and switch */ + struct request_member comp_mode; /* NCP */ + struct request_member freq; /* frequency: NCP and SMPS */ + struct request_member freq_clk_src; /* clock source: SMPS */ + struct request_member hpm; /* switch: control OCP ans SS */ + int request_len; +}; + +#define REQUEST_MEMBER(_word, _mask, _shift) \ + { \ + .word = _word, \ + .mask = _mask, \ + .shift = _shift, \ + } + +struct rpm_vreg_parts ldo_parts = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .fm = REQUEST_MEMBER(0, 0x0F000000, 24), + .pc = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .pf = REQUEST_MEMBER(1, 0x00003C00, 10), + .ia = REQUEST_MEMBER(1, 0x00FFC000, 14), +}; + +struct rpm_vreg_parts smps_parts = { + .request_len = 2, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .pd = REQUEST_MEMBER(0, 0x00800000, 23), + .fm = REQUEST_MEMBER(0, 0x0F000000, 24), + .pc = REQUEST_MEMBER(0, 0xF0000000, 28), + .ip = REQUEST_MEMBER(1, 0x000003FF, 0), + .pf = REQUEST_MEMBER(1, 0x00003C00, 10), + .ia = REQUEST_MEMBER(1, 0x00FFC000, 14), + .freq = REQUEST_MEMBER(1, 0x1F000000, 24), + .freq_clk_src = REQUEST_MEMBER(1, 0x60000000, 29), + .pm = REQUEST_MEMBER(1, 0x80000000, 31), +}; + +struct rpm_vreg_parts switch_parts = { + .request_len = 1, + .enable_state = REQUEST_MEMBER(0, 0x00000001, 0), + .pd = REQUEST_MEMBER(0, 0x00000002, 1), + .pc = REQUEST_MEMBER(0, 0x0000003C, 2), + .pf = REQUEST_MEMBER(0, 0x000003C0, 6), + .hpm = REQUEST_MEMBER(0, 0x00000C00, 10), +}; + +struct rpm_vreg_parts ncp_parts = { + .request_len = 1, + .uV = REQUEST_MEMBER(0, 0x007FFFFF, 0), + .enable_state = REQUEST_MEMBER(0, 0x00800000, 23), + .comp_mode = REQUEST_MEMBER(0, 0x01000000, 24), + .freq = REQUEST_MEMBER(0, 0x3E000000, 25), +}; + +struct vreg_range { + int min_uV; + int max_uV; + int step_uV; + unsigned n_voltages; +}; + +struct vreg_set_points { + struct vreg_range *range; + int count; + unsigned n_voltages; +}; + +#define VOLTAGE_RANGE(_min_uV, _max_uV, _step_uV) \ + { \ + .min_uV = _min_uV, \ + .max_uV = _max_uV, \ + .step_uV = _step_uV, \ + } + +static struct vreg_range pldo_ranges[] = { + VOLTAGE_RANGE( 750000, 1487500, 12500), + VOLTAGE_RANGE(1500000, 3075000, 25000), + VOLTAGE_RANGE(3100000, 4900000, 50000), +}; + +static struct vreg_range nldo_ranges[] = { + VOLTAGE_RANGE( 750000, 1537500, 12500), +}; + +static struct vreg_range nldo1200_ranges[] = { + VOLTAGE_RANGE( 375000, 743750, 6250), + VOLTAGE_RANGE( 750000, 1537500, 12500), +}; + +static struct vreg_range smps_ranges[] = { + VOLTAGE_RANGE( 375000, 737500, 12500), + VOLTAGE_RANGE( 750000, 1487500, 12500), + VOLTAGE_RANGE(1500000, 3075000, 25000), +}; + +static struct vreg_range ftsmps_ranges[] = { + VOLTAGE_RANGE( 350000, 650000, 50000), + VOLTAGE_RANGE( 700000, 1400000, 12500), + VOLTAGE_RANGE(1500000, 3300000, 50000), +}; + +static struct vreg_range ncp_ranges[] = { + VOLTAGE_RANGE(1500000, 3050000, 50000), +}; + +#define SET_POINTS(_ranges) \ +{ \ + .range = _ranges, \ + .count = ARRAY_SIZE(_ranges), \ +}; + +static struct vreg_set_points pldo_set_points = SET_POINTS(pldo_ranges); +static struct vreg_set_points nldo_set_points = SET_POINTS(nldo_ranges); +static struct vreg_set_points nldo1200_set_points = SET_POINTS(nldo1200_ranges); +static struct vreg_set_points smps_set_points = SET_POINTS(smps_ranges); +static struct vreg_set_points ftsmps_set_points = SET_POINTS(ftsmps_ranges); +static struct vreg_set_points ncp_set_points = SET_POINTS(ncp_ranges); + +/* + * This is used when voting for LPM or HPM by subtracting or adding to the + * hpm_min_load of a regulator. It has units of uA. + */ +#define LOAD_THRESHOLD_STEP 1000 + +/* This is the maximum uA load that can be passed to the RPM. */ +#define MAX_POSSIBLE_LOAD (MILLI_TO_MICRO(0xFFF)) + +struct vreg { + struct msm_rpm_iv_pair req[2]; + struct msm_rpm_iv_pair prev_active_req[2]; + struct msm_rpm_iv_pair prev_sleep_req[2]; + struct rpm_regulator_init_data pdata; + struct regulator_dev *rdev; + struct regulator_dev *rdev_pc; + const char *name; + struct vreg_set_points *set_points; + struct rpm_vreg_parts *part; + int type; + enum rpm_vreg_id id; + struct mutex pc_lock; + int save_uV; + int mode; + bool is_enabled; + bool is_enabled_pc; + const int hpm_min_load; + int active_min_uV_vote[RPM_VREG_VOTER_COUNT]; + int sleep_min_uV_vote[RPM_VREG_VOTER_COUNT]; + +}; + +#define LDO(_id, _ranges, _hpm_min_load) \ + [RPM_VREG_ID_PM8921_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8921_##_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_PM8921_##_id##_1, }, \ + }, \ + .hpm_min_load = RPM_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = REGULATOR_TYPE_LDO, \ + .set_points = &_ranges##_set_points, \ + .part = &ldo_parts, \ + .id = RPM_VREG_ID_PM8921_##_id, \ + } + +#define SMPS(_id, _ranges, _hpm_min_load) \ + [RPM_VREG_ID_PM8921_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8921_##_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_PM8921_##_id##_1, }, \ + }, \ + .hpm_min_load = RPM_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + .type = REGULATOR_TYPE_SMPS, \ + .set_points = &_ranges##_set_points, \ + .part = &smps_parts, \ + .id = RPM_VREG_ID_PM8921_##_id, \ + } + +#define LVS(_id) \ + [RPM_VREG_ID_PM8921_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_PM8921_##_id, }, \ + [1] = { .id = -1, }, \ + }, \ + .type = REGULATOR_TYPE_VS, \ + .part = &switch_parts, \ + .id = RPM_VREG_ID_PM8921_##_id, \ + } + +#define MVS(_vreg_id, _rpm_id) \ + [RPM_VREG_ID_PM8921_##_vreg_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_##_rpm_id, }, \ + [1] = { .id = -1, }, \ + }, \ + .type = REGULATOR_TYPE_VS, \ + .part = &switch_parts, \ + .id = RPM_VREG_ID_PM8921_##_vreg_id, \ + } + +#define NCP(_id) \ + [RPM_VREG_ID_PM8921_##_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_##_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_##_id##_1, }, \ + }, \ + .type = REGULATOR_TYPE_NCP, \ + .set_points = &ncp_set_points, \ + .part = &ncp_parts, \ + .id = RPM_VREG_ID_PM8921_##_id, \ + } + +static struct vreg vregs[] = { + LDO(L1, nldo, LDO_150), + LDO(L2, nldo, LDO_150), + LDO(L3 , pldo, LDO_150), + LDO(L4, pldo, LDO_50), + LDO(L5, pldo, LDO_300), + LDO(L6, pldo, LDO_600), + LDO(L7, pldo, LDO_150), + LDO(L8, pldo, LDO_300), + LDO(L9, pldo, LDO_300), + LDO(L10, pldo, LDO_600), + LDO(L11, pldo, LDO_150), + LDO(L12, nldo, LDO_150), + LDO(L14, pldo, LDO_50), + LDO(L15, pldo, LDO_150), + LDO(L16, pldo, LDO_300), + LDO(L17, pldo, LDO_150), + LDO(L18, nldo, LDO_150), + LDO(L21, pldo, LDO_150), + LDO(L22, pldo, LDO_150), + LDO(L23, pldo, LDO_150), + LDO(L24, nldo1200, LDO_1200), + LDO(L25, nldo1200, LDO_1200), + LDO(L26, nldo1200, LDO_1200), + LDO(L27, nldo1200, LDO_1200), + LDO(L28, nldo1200, LDO_1200), + LDO(L29, pldo, LDO_150), + + SMPS(S1, smps, SMPS_1500), + SMPS(S2, smps, SMPS_1500), + SMPS(S3, smps, SMPS_1500), + SMPS(S4, smps, SMPS_1500), + SMPS(S5, ftsmps, SMPS_2000), + SMPS(S6, ftsmps, SMPS_2000), + SMPS(S7, smps, SMPS_1500), + SMPS(S8, smps, SMPS_1500), + + LVS(LVS1), + LVS(LVS2), + LVS(LVS3), + LVS(LVS4), + LVS(LVS5), + LVS(LVS6), + LVS(LVS7), + MVS(USB_OTG, USB_OTG_SWITCH), + MVS(HDMI_MVS, HDMI_SWITCH), + + NCP(NCP), +}; + +#define vreg_err(vreg, fmt, ...) \ + pr_err("%s: " fmt, vreg->name, ##__VA_ARGS__) + +#define VREG_ID_IS_VDD_MEM_OR_DIG(id) \ + ((id == RPM_VREG_ID_PM8921_L24) || (id == RPM_VREG_ID_PM8921_S3)) + +const char *pin_func_label[] = { + [RPM_VREG_PIN_FN_DONT_CARE] = "don't care", + [RPM_VREG_PIN_FN_ENABLE] = "on/off", + [RPM_VREG_PIN_FN_MODE] = "HPM/LPM", + [RPM_VREG_PIN_FN_SLEEP_B] = "sleep_b", + [RPM_VREG_PIN_FN_NONE] = "none", +}; + +const char *force_mode_label[] = { + [RPM_VREG_FORCE_MODE_NONE] = "none", + [RPM_VREG_FORCE_MODE_LPM] = "LPM", + [RPM_VREG_FORCE_MODE_AUTO] = "auto", + [RPM_VREG_FORCE_MODE_HPM] = "HPM", + [RPM_VREG_FORCE_MODE_BYPASS] = "BYP", +}; + +const char *power_mode_label[] = { + [RPM_VREG_POWER_MODE_HYSTERETIC] = "HYS", + [RPM_VREG_POWER_MODE_PWM] = "PWM", +}; + +static void rpm_regulator_req(struct vreg *vreg, int set) +{ + int uV, ip, fm, pm, pc, pf, pd, ia, freq, clk, state, hpm, comp_mode; + const char *pf_label = "", *fm_label = "", *pc_total = ""; + const char *pc_en0 = "", *pc_en1 = "", *pc_en2 = "", *pc_en3 = ""; + const char *pm_label = ""; + + /* Suppress VDD_MEM and VDD_DIG printing. */ + if ((msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_IGNORE_VDD_MEM_DIG) + && VREG_ID_IS_VDD_MEM_OR_DIG(vreg->id)) + return; + + if (vreg->part->uV.mask) + uV = GET_PART(vreg, uV); + else + uV = MILLI_TO_MICRO(GET_PART(vreg, mV)); + + ip = GET_PART(vreg, ip); + fm = GET_PART(vreg, fm); + pm = GET_PART(vreg, pm); + pc = GET_PART(vreg, pc); + pf = GET_PART(vreg, pf); + pd = GET_PART(vreg, pd); + ia = GET_PART(vreg, ia); + freq = GET_PART(vreg, freq); + clk = GET_PART(vreg, freq_clk_src); + state = GET_PART(vreg, enable_state); + hpm = GET_PART(vreg, hpm); + comp_mode = GET_PART(vreg, comp_mode); + + if (pf >= 0 && pf < ARRAY_SIZE(pin_func_label)) + pf_label = pin_func_label[pf]; + + if (fm >= 0 && fm < ARRAY_SIZE(force_mode_label)) + fm_label = force_mode_label[fm]; + + if (pm >= 0 && pm < ARRAY_SIZE(power_mode_label)) + pm_label = power_mode_label[pm]; + + if (pc & RPM_VREG_PIN_CTRL_EN0) + pc_en0 = " D1"; + if (pc & RPM_VREG_PIN_CTRL_EN1) + pc_en1 = " A0"; + if (pc & RPM_VREG_PIN_CTRL_EN2) + pc_en2 = " A1"; + if (pc & RPM_VREG_PIN_CTRL_EN3) + pc_en3 = " A2"; + if (pc == RPM_VREG_PIN_CTRL_NONE) + pc_total = " none"; + + switch (vreg->type) { + case REGULATOR_TYPE_LDO: + pr_info("%s %-9s: s=%c, v=%7d uV, ip=%4d mA, fm=%s (%d), " + "pc=%s%s%s%s%s (%d), pf=%s (%d), pd=%s (%d), " + "ia=%4d mA; req[0]={%d, 0x%08X}, req[1]={%d, 0x%08X}\n", + (set == MSM_RPM_CTX_SET_0 ? "sending " : "buffered"), + vreg->name, + (set == MSM_RPM_CTX_SET_0 ? 'A' : 'S'), uV, ip, + fm_label, fm, pc_en0, pc_en1, pc_en2, pc_en3, pc_total, + pc, pf_label, pf, (pd == 1 ? "Y" : "N"), pd, ia, + vreg->req[0].id, vreg->req[0].value, + vreg->req[1].id, vreg->req[1].value); + break; + case REGULATOR_TYPE_SMPS: + pr_info("%s %-9s: s=%c, v=%7d uV, ip=%4d mA, fm=%s (%d), " + "pc=%s%s%s%s%s (%d), pf=%s (%d), pd=%s (%d), " + "ia=%4d mA, freq=%2d, pm=%s (%d), clk_src=%d; " + "req[0]={%d, 0x%08X}, req[1]={%d, 0x%08X}\n", + (set == MSM_RPM_CTX_SET_0 ? "sending " : "buffered"), + vreg->name, + (set == MSM_RPM_CTX_SET_0 ? 'A' : 'S'), uV, ip, + fm_label, fm, pc_en0, pc_en1, pc_en2, pc_en3, pc_total, + pc, pf_label, pf, (pd == 1 ? "Y" : "N"), pd, ia, freq, + pm_label, pm, clk, vreg->req[0].id, vreg->req[0].value, + vreg->req[1].id, vreg->req[1].value); + break; + case REGULATOR_TYPE_VS: + pr_info("%s %-9s: s=%c, state=%s (%d), pd=%s (%d), " + "pc =%s%s%s%s%s (%d), pf=%s (%d), hpm=%d; " + "req[0]={%d, 0x%08X}\n", + (set == MSM_RPM_CTX_SET_0 ? "sending " : "buffered"), + vreg->name, (set == MSM_RPM_CTX_SET_0 ? 'A' : 'S'), + (state == 1 ? "on" : "off"), state, + (pd == 1 ? "Y" : "N"), pd, pc_en0, pc_en1, pc_en2, + pc_en3, pc_total, pc, pf_label, pf, hpm, + vreg->req[0].id, vreg->req[0].value); + break; + case REGULATOR_TYPE_NCP: + pr_info("%s %-9s: s=%c, v=-%7d uV, state=%s (%d), freq=%2d, " + "comp=%d; req[0]={%d, 0x%08X}\n", + (set == MSM_RPM_CTX_SET_0 ? "sending " : "buffered"), + vreg->name, (set == MSM_RPM_CTX_SET_0 ? 'A' : 'S'), + uV, (state == 1 ? "on" : "off"), state, freq, comp_mode, + vreg->req[0].id, vreg->req[0].value); + break; + } +} + +static void rpm_regulator_vote(struct vreg *vreg, enum rpm_vreg_voter voter, + int set, int voter_uV, int aggregate_uV) +{ + /* Suppress VDD_MEM and VDD_DIG printing. */ + if ((msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_IGNORE_VDD_MEM_DIG) + && VREG_ID_IS_VDD_MEM_OR_DIG(vreg->id)) + return; + + pr_info("vote received %-9s: voter=%d, set=%c, v_voter=%7d uV, " + "v_aggregate=%7d uV\n", vreg->name, voter, + (set == 0 ? 'A' : 'S'), voter_uV, aggregate_uV); +} + +static void rpm_regulator_duplicate(struct vreg *vreg, int set, int cnt) +{ + /* Suppress VDD_MEM and VDD_DIG printing. */ + if ((msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_IGNORE_VDD_MEM_DIG) + && VREG_ID_IS_VDD_MEM_OR_DIG(vreg->id)) + return; + + if (cnt == 2) + pr_info("ignored request %-9s: set=%c; req[0]={%d, 0x%08X}, " + "req[1]={%d, 0x%08X}\n", vreg->name, + (set == 0 ? 'A' : 'S'), + vreg->req[0].id, vreg->req[0].value, + vreg->req[1].id, vreg->req[1].value); + else if (cnt == 1) + pr_info("ignored request %-9s: set=%c; req[0]={%d, 0x%08X}\n", + vreg->name, (set == 0 ? 'A' : 'S'), + vreg->req[0].id, vreg->req[0].value); +} + +/* Spin lock needed for sleep-selectable regulators. */ +static DEFINE_SPINLOCK(pm8921_noirq_lock); + +static int voltage_from_req(struct vreg *vreg) +{ + int uV = 0; + + if (vreg->part->uV.mask) + uV = GET_PART(vreg, uV); + else + uV = MILLI_TO_MICRO(GET_PART(vreg, mV)); + + return uV; +} + +static void voltage_to_req(int uV, struct vreg *vreg) +{ + if (vreg->part->uV.mask) + SET_PART(vreg, uV, uV); + else + SET_PART(vreg, mV, MICRO_TO_MILLI(uV)); +} + +static int vreg_send_request(struct vreg *vreg, enum rpm_vreg_voter voter, + int set, unsigned mask0, unsigned val0, + unsigned mask1, unsigned val1, unsigned cnt, + int update_voltage) +{ + struct msm_rpm_iv_pair *prev_req; + int rc = 0, max_uV_vote = 0; + unsigned prev0, prev1; + int *min_uV_vote; + int i; + + if (set == MSM_RPM_CTX_SET_0) { + min_uV_vote = vreg->active_min_uV_vote; + prev_req = vreg->prev_active_req; + } else { + min_uV_vote = vreg->sleep_min_uV_vote; + prev_req = vreg->prev_sleep_req; + } + + prev0 = vreg->req[0].value; + vreg->req[0].value &= ~mask0; + vreg->req[0].value |= val0 & mask0; + + prev1 = vreg->req[1].value; + vreg->req[1].value &= ~mask1; + vreg->req[1].value |= val1 & mask1; + + if (update_voltage) + min_uV_vote[voter] = voltage_from_req(vreg); + + /* Find the highest voltage voted for and use it. */ + for (i = 0; i < RPM_VREG_VOTER_COUNT; i++) + max_uV_vote = max(max_uV_vote, min_uV_vote[i]); + voltage_to_req(max_uV_vote, vreg); + + if (msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_VOTE) + rpm_regulator_vote(vreg, voter, set, min_uV_vote[voter], + max_uV_vote); + + /* Ignore duplicate requests */ + if (vreg->req[0].value != prev_req[0].value || + vreg->req[1].value != prev_req[1].value) { + rc = msm_rpmrs_set_noirq(set, vreg->req, cnt); + if (rc) { + vreg->req[0].value = prev0; + vreg->req[1].value = prev1; + + vreg_err(vreg, "msm_rpmrs_set_noirq failed - " + "set=%s, id=%d, rc=%d\n", + (set == MSM_RPM_CTX_SET_0 ? "active" : "sleep"), + vreg->req[0].id, rc); + } else { + /* Only save if nonzero and active set. */ + if (max_uV_vote && (set == MSM_RPM_CTX_SET_0)) + vreg->save_uV = max_uV_vote; + if (msm_rpm_vreg_debug_mask + & MSM_RPM_VREG_DEBUG_REQUEST) + rpm_regulator_req(vreg, set); + prev_req[0].value = vreg->req[0].value; + prev_req[1].value = vreg->req[1].value; + } + } else if (msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_DUPLICATE) { + rpm_regulator_duplicate(vreg, set, cnt); + } + + return rc; +} + +static int vreg_set_noirq(struct vreg *vreg, enum rpm_vreg_voter voter, + int sleep, unsigned mask0, unsigned val0, + unsigned mask1, unsigned val1, unsigned cnt, + int update_voltage) +{ + unsigned int s_mask[2] = {mask0, mask1}, s_val[2] = {val0, val1}; + unsigned long flags; + int rc; + + if (voter < 0 || voter >= RPM_VREG_VOTER_COUNT) + return -EINVAL; + + spin_lock_irqsave(&pm8921_noirq_lock, flags); + + /* + * Send sleep set request first so that subsequent set_mode, etc calls + * use the voltage from the active set. + */ + if (sleep) + rc = vreg_send_request(vreg, voter, MSM_RPM_CTX_SET_SLEEP, + mask0, val0, mask1, val1, cnt, update_voltage); + else { + /* + * Vote for 0 V in the sleep set when active set-only is + * specified. This ensures that a disable vote will be issued + * at some point for the sleep set of the regulator. + */ + if (vreg->part->uV.mask) { + s_val[vreg->part->uV.word] = 0 << vreg->part->uV.shift; + s_mask[vreg->part->uV.word] = vreg->part->uV.mask; + } else { + s_val[vreg->part->mV.word] = 0 << vreg->part->mV.shift; + s_mask[vreg->part->mV.word] = vreg->part->mV.mask; + } + + rc = vreg_send_request(vreg, voter, MSM_RPM_CTX_SET_SLEEP, + s_mask[0], s_val[0], s_mask[1], s_val[1], + cnt, update_voltage); + } + + rc = vreg_send_request(vreg, voter, MSM_RPM_CTX_SET_0, mask0, val0, + mask1, val1, cnt, update_voltage); + + spin_unlock_irqrestore(&pm8921_noirq_lock, flags); + + return rc; +} + +/** + * rpm_vreg_set_voltage - vote for a min_uV value of specified regualtor + * @vreg: ID for regulator + * @voter: ID for the voter + * @min_uV: minimum acceptable voltage (in uV) that is voted for + * @max_uV: maximum acceptable voltage (in uV) that is voted for + * @sleep_also: 0 for active set only, non-0 for active set and sleep set + * + * Returns 0 on success or errno. + * + * This function is used to vote for the voltage of a regulator without + * using the regulator framework. It is needed by consumers which hold spin + * locks or have interrupts disabled because the regulator framework can sleep. + * It is also needed by consumers which wish to only vote for active set + * regulator voltage. + * + * If sleep_also == 0, then a sleep-set value of 0V will be voted for. + * + * This function may only be called for regulators which have the sleep flag + * specified in their private data. + */ +int rpm_vreg_set_voltage(enum rpm_vreg_id vreg_id, enum rpm_vreg_voter voter, + int min_uV, int max_uV, int sleep_also) +{ + unsigned int mask[2] = {0}, val[2] = {0}; + struct vreg_range *range; + struct vreg *vreg; + int uV = min_uV; + int lim_min_uV, lim_max_uV, i, rc; + + /* + * TODO: make this function a no-op so that it can be called by + * consumers before RPM capabilities are present. (needed for + * acpuclock driver) + */ + return 0; + + if (vreg_id < 0 || vreg_id > RPM_VREG_ID_PM8921_MAX_REAL) { + pr_err("invalid regulator id=%d\n", vreg_id); + return -EINVAL; + } + vreg = &vregs[vreg_id]; + range = &vreg->set_points->range[0]; + + if (!vreg->pdata.sleep_selectable) { + vreg_err(vreg, "regulator is not marked sleep selectable\n"); + return -EINVAL; + } + + /* + * Check if request voltage is outside of allowed range. The regulator + * core has already checked that constraint range is inside of the + * physically allowed range. + */ + lim_min_uV = vreg->pdata.init_data.constraints.min_uV; + lim_max_uV = vreg->pdata.init_data.constraints.max_uV; + + if (uV < lim_min_uV && max_uV >= lim_min_uV) + uV = lim_min_uV; + + if (uV < lim_min_uV || uV > lim_max_uV) { + vreg_err(vreg, + "request v=[%d, %d] is outside allowed v=[%d, %d]\n", + min_uV, max_uV, lim_min_uV, lim_max_uV); + return -EINVAL; + } + + /* Find the range which uV is inside of. */ + for (i = vreg->set_points->count - 1; i > 0; i++) { + if (uV > vreg->set_points->range[i - 1].max_uV) { + range = &vreg->set_points->range[i]; + break; + } + } + + /* + * Force uV to be an allowed set point and apply a ceiling function + * to non-set point values. + */ + uV = (uV - range->min_uV + range->step_uV - 1) / range->step_uV; + uV = uV * range->step_uV + range->min_uV; + + if (vreg->part->uV.mask) { + val[vreg->part->uV.word] = uV << vreg->part->uV.shift; + mask[vreg->part->uV.word] = vreg->part->uV.mask; + } else { + val[vreg->part->mV.word] + = MICRO_TO_MILLI(uV) << vreg->part->mV.shift; + mask[vreg->part->mV.word] = vreg->part->mV.mask; + } + + rc = vreg_set_noirq(vreg, voter, sleep_also, mask[0], val[0], mask[1], + val[1], vreg->part->request_len, 1); + if (rc) + vreg_err(vreg, "vreg_set_noirq failed, rc=%d\n", rc); + + return rc; +} +EXPORT_SYMBOL_GPL(rpm_vreg_set_voltage); + +/** + * rpm_vreg_set_frequency - sets the frequency of a switching regulator + * @vreg: ID for regulator + * @freq: enum corresponding to desired frequency + * + * Returns 0 on success or errno. + */ +int rpm_vreg_set_frequency(enum rpm_vreg_id vreg_id, enum rpm_vreg_freq freq) +{ + unsigned int mask[2] = {0}, val[2] = {0}; + struct vreg *vreg; + int rc; + + /* + * TODO: make this function a no-op so that it can be called by + * consumers before RPM capabilities are present. (needed for + * acpuclock driver) + */ + return 0; + + if (vreg_id < 0 || vreg_id > RPM_VREG_ID_PM8921_MAX_REAL) { + pr_err("invalid regulator id=%d\n", vreg_id); + return -EINVAL; + } + vreg = &vregs[vreg_id]; + + if (freq < 0 || freq > RPM_VREG_FREQ_1p20) { + vreg_err(vreg, "invalid frequency=%d\n", freq); + return -EINVAL; + } + if (!vreg->pdata.sleep_selectable) { + vreg_err(vreg, "regulator is not marked sleep selectable\n"); + return -EINVAL; + } + if (!vreg->part->freq.mask) { + vreg_err(vreg, "frequency not supported\n"); + return -EINVAL; + } + + val[vreg->part->freq.word] = freq << vreg->part->freq.shift; + mask[vreg->part->freq.word] = vreg->part->freq.mask; + + rc = vreg_set_noirq(vreg, RPM_VREG_VOTER_REG_FRAMEWORK, 1, mask[0], + val[0], mask[1], val[1], vreg->part->request_len, 0); + if (rc) + vreg_err(vreg, "vreg_set failed, rc=%d\n", rc); + + return rc; +} +EXPORT_SYMBOL_GPL(rpm_vreg_set_frequency); + +static inline int vreg_hpm_min_uA(struct vreg *vreg) +{ + return vreg->hpm_min_load; +} + +static inline int vreg_lpm_max_uA(struct vreg *vreg) +{ + return vreg->hpm_min_load - LOAD_THRESHOLD_STEP; +} + +static inline unsigned saturate_peak_load(struct vreg *vreg, unsigned load_uA) +{ + unsigned load_max + = MILLI_TO_MICRO(vreg->part->ip.mask >> vreg->part->ip.shift); + + return (load_uA > load_max ? load_max : load_uA); +} + +static inline unsigned saturate_avg_load(struct vreg *vreg, unsigned load_uA) +{ + unsigned load_max + = MILLI_TO_MICRO(vreg->part->ia.mask >> vreg->part->ia.shift); + return (load_uA > load_max ? load_max : load_uA); +} + +/* Change vreg->req, but do not send it to the RPM. */ +static int vreg_store(struct vreg *vreg, unsigned mask0, unsigned val0, + unsigned mask1, unsigned val1) +{ + unsigned long flags = 0; + + if (vreg->pdata.sleep_selectable) + spin_lock_irqsave(&pm8921_noirq_lock, flags); + + vreg->req[0].value &= ~mask0; + vreg->req[0].value |= val0 & mask0; + + vreg->req[1].value &= ~mask1; + vreg->req[1].value |= val1 & mask1; + + if (vreg->pdata.sleep_selectable) + spin_unlock_irqrestore(&pm8921_noirq_lock, flags); + + return 0; +} + +static int vreg_set(struct vreg *vreg, unsigned mask0, unsigned val0, + unsigned mask1, unsigned val1, unsigned cnt) +{ + unsigned prev0 = 0, prev1 = 0; + int rc; + + /* + * Bypass the normal route for regulators that can be called to change + * just the active set values. + */ + if (vreg->pdata.sleep_selectable) + return vreg_set_noirq(vreg, RPM_VREG_VOTER_REG_FRAMEWORK, 1, + mask0, val0, mask1, val1, cnt, 1); + + prev0 = vreg->req[0].value; + vreg->req[0].value &= ~mask0; + vreg->req[0].value |= val0 & mask0; + + prev1 = vreg->req[1].value; + vreg->req[1].value &= ~mask1; + vreg->req[1].value |= val1 & mask1; + + /* Ignore duplicate requests */ + if (vreg->req[0].value == vreg->prev_active_req[0].value && + vreg->req[1].value == vreg->prev_active_req[1].value) { + if (msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_DUPLICATE) + rpm_regulator_duplicate(vreg, MSM_RPM_CTX_SET_0, cnt); + return 0; + } + + rc = msm_rpm_set(MSM_RPM_CTX_SET_0, vreg->req, cnt); + if (rc) { + vreg->req[0].value = prev0; + vreg->req[1].value = prev1; + + vreg_err(vreg, "msm_rpm_set failed, set=active, id=%d, rc=%d\n", + vreg->req[0].id, rc); + } else { + if (msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_REQUEST) + rpm_regulator_req(vreg, MSM_RPM_CTX_SET_0); + vreg->prev_active_req[0].value = vreg->req[0].value; + vreg->prev_active_req[1].value = vreg->req[1].value; + } + + return rc; +} + +static int vreg_is_enabled(struct regulator_dev *rdev) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + int enabled; + + mutex_lock(&vreg->pc_lock); + enabled = vreg->is_enabled; + mutex_unlock(&vreg->pc_lock); + + return enabled; +} + +static void set_enable(struct vreg *vreg, unsigned int *mask, unsigned int *val) +{ + switch (vreg->type) { + case REGULATOR_TYPE_LDO: + case REGULATOR_TYPE_SMPS: + /* Enable by setting a voltage. */ + if (vreg->part->uV.mask) { + val[vreg->part->uV.word] + |= vreg->save_uV << vreg->part->uV.shift; + mask[vreg->part->uV.word] |= vreg->part->uV.mask; + } else { + val[vreg->part->mV.word] + |= MICRO_TO_MILLI(vreg->save_uV) + << vreg->part->mV.shift; + mask[vreg->part->mV.word] |= vreg->part->mV.mask; + } + break; + case REGULATOR_TYPE_VS: + case REGULATOR_TYPE_NCP: + /* Enable by setting enable_state. */ + val[vreg->part->enable_state.word] + |= RPM_VREG_STATE_ON << vreg->part->enable_state.shift; + mask[vreg->part->enable_state.word] + |= vreg->part->enable_state.mask; + } +} + +static int vreg_enable(struct regulator_dev *rdev) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mask[2] = {0}, val[2] = {0}; + int rc = 0; + + set_enable(vreg, mask, val); + + mutex_lock(&vreg->pc_lock); + + rc = vreg_set(vreg, mask[0], val[0], mask[1], val[1], + vreg->part->request_len); + if (!rc) + vreg->is_enabled = true; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "vreg_set failed, rc=%d\n", rc); + + return rc; +} + +static void set_disable(struct vreg *vreg, unsigned int *mask, + unsigned int *val) +{ + switch (vreg->type) { + case REGULATOR_TYPE_LDO: + case REGULATOR_TYPE_SMPS: + /* Disable by setting a voltage of 0 uV. */ + if (vreg->part->uV.mask) { + val[vreg->part->uV.word] |= 0 << vreg->part->uV.shift; + mask[vreg->part->uV.word] |= vreg->part->uV.mask; + } else { + val[vreg->part->mV.word] |= 0 << vreg->part->mV.shift; + mask[vreg->part->mV.word] |= vreg->part->mV.mask; + } + break; + case REGULATOR_TYPE_VS: + case REGULATOR_TYPE_NCP: + /* Disable by setting enable_state. */ + val[vreg->part->enable_state.word] + |= RPM_VREG_STATE_OFF << vreg->part->enable_state.shift; + mask[vreg->part->enable_state.word] + |= vreg->part->enable_state.mask; + } +} + +static int vreg_disable(struct regulator_dev *rdev) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mask[2] = {0}, val[2] = {0}; + int rc = 0; + + set_disable(vreg, mask, val); + + mutex_lock(&vreg->pc_lock); + + /* Only disable if pin control is not in use. */ + if (!vreg->is_enabled_pc) + rc = vreg_set(vreg, mask[0], val[0], mask[1], val[1], + vreg->part->request_len); + + if (!rc) + vreg->is_enabled = false; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "vreg_set failed, rc=%d\n", rc); + + return rc; +} + +static int vreg_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, + unsigned *selector) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + struct vreg_range *range = &vreg->set_points->range[0]; + unsigned int mask[2] = {0}, val[2] = {0}; + int rc = 0, uV = min_uV; + int lim_min_uV, lim_max_uV, i; + + /* Check if request voltage is outside of physically settable range. */ + lim_min_uV = vreg->set_points->range[0].min_uV; + lim_max_uV = + vreg->set_points->range[vreg->set_points->count - 1].max_uV; + + if (uV < lim_min_uV && max_uV >= lim_min_uV) + uV = lim_min_uV; + + if (uV < lim_min_uV || uV > lim_max_uV) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, lim_min_uV, lim_max_uV); + return -EINVAL; + } + + /* Find the range which uV is inside of. */ + for (i = vreg->set_points->count - 1; i > 0; i++) { + if (uV > vreg->set_points->range[i - 1].max_uV) { + range = &vreg->set_points->range[i]; + break; + } + } + + /* + * Force uV to be an allowed set point and apply a ceiling function + * to non-set point values. + */ + uV = (uV - range->min_uV + range->step_uV - 1) / range->step_uV; + uV = uV * range->step_uV + range->min_uV; + + if (vreg->part->uV.mask) { + val[vreg->part->uV.word] = uV << vreg->part->uV.shift; + mask[vreg->part->uV.word] = vreg->part->uV.mask; + } else { + val[vreg->part->mV.word] + = MICRO_TO_MILLI(uV) << vreg->part->mV.shift; + mask[vreg->part->mV.word] = vreg->part->mV.mask; + } + + mutex_lock(&vreg->pc_lock); + + /* + * Only send a request for a new voltage if the regulator is currently + * enabled. This will ensure that LDO and SMPS regulators are not + * inadvertently turned on because voltage > 0 is equivalent to + * enabling. For NCP, this just removes unnecessary RPM requests. + */ + if (vreg->is_enabled) { + rc = vreg_set(vreg, mask[0], val[0], mask[1], val[1], + vreg->part->request_len); + if (rc) + vreg_err(vreg, "vreg_set failed, rc=%d\n", rc); + } else if (vreg->type == REGULATOR_TYPE_NCP) { + /* Regulator is disabled; store but don't send new request. */ + rc = vreg_store(vreg, mask[0], val[0], mask[1], val[1]); + } + + if (!rc && (!vreg->pdata.sleep_selectable || !vreg->is_enabled)) + vreg->save_uV = uV; + + mutex_unlock(&vreg->pc_lock); + + return rc; +} + +static int vreg_get_voltage(struct regulator_dev *rdev) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->save_uV; +} + +static int vreg_list_voltage(struct regulator_dev *rdev, unsigned selector) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + int uV = 0; + int i; + + if (!vreg->set_points) { + vreg_err(vreg, "no voltages available\n"); + return -EINVAL; + } + + if (selector >= vreg->set_points->n_voltages) + return 0; + + for (i = 0; i < vreg->set_points->count; i++) { + if (selector < vreg->set_points->range[i].n_voltages) { + uV = selector * vreg->set_points->range[i].step_uV + + vreg->set_points->range[i].min_uV; + break; + } else { + selector -= vreg->set_points->range[i].n_voltages; + } + } + + return uV; +} + +static int vreg_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mask[2] = {0}, val[2] = {0}; + int rc = 0; + int peak_uA; + + mutex_lock(&vreg->pc_lock); + + peak_uA = MILLI_TO_MICRO((vreg->req[vreg->part->ip.word].value + & vreg->part->ip.mask) >> vreg->part->ip.shift); + + switch (mode) { + case REGULATOR_MODE_NORMAL: + /* Make sure that request currents are in HPM range. */ + if (peak_uA < vreg_hpm_min_uA(vreg)) { + val[vreg->part->ip.word] + = MICRO_TO_MILLI(vreg_hpm_min_uA(vreg)) + << vreg->part->ip.shift; + mask[vreg->part->ip.word] = vreg->part->ip.mask; + } + break; + case REGULATOR_MODE_IDLE: + /* Make sure that request currents are in LPM range. */ + if (peak_uA > vreg_lpm_max_uA(vreg)) { + val[vreg->part->ip.word] + = MICRO_TO_MILLI(vreg_lpm_max_uA(vreg)) + << vreg->part->ip.shift; + mask[vreg->part->ip.word] = vreg->part->ip.mask; + } + break; + default: + vreg_err(vreg, "invalid mode: %u\n", mode); + mutex_unlock(&vreg->pc_lock); + return -EINVAL; + } + + if (vreg->is_enabled) { + rc = vreg_set(vreg, mask[0], val[0], mask[1], val[1], + vreg->part->request_len); + } else { + /* Regulator is disabled; store but don't send new request. */ + rc = vreg_store(vreg, mask[0], val[0], mask[1], val[1]); + } + + if (rc) + vreg_err(vreg, "vreg_set failed, rc=%d\n", rc); + else + vreg->mode = mode; + + mutex_unlock(&vreg->pc_lock); + + return rc; +} + +static unsigned int vreg_get_mode(struct regulator_dev *rdev) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->mode; +} + +static unsigned int vreg_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, int load_uA) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mode; + + load_uA += vreg->pdata.system_uA; + + mutex_lock(&vreg->pc_lock); + SET_PART(vreg, ip, MICRO_TO_MILLI(saturate_peak_load(vreg, load_uA))); + mutex_unlock(&vreg->pc_lock); + + if (load_uA >= vreg->hpm_min_load) + mode = REGULATOR_MODE_NORMAL; + else + mode = REGULATOR_MODE_IDLE; + + return mode; +} + +/* + * Returns the logical pin control enable state because the pin control options + * present in the hardware out of restart could be different from those desired + * by the consumer. + */ +static int vreg_pin_control_is_enabled(struct regulator_dev *rdev) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->is_enabled_pc; +} + +static int vreg_pin_control_enable(struct regulator_dev *rdev) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mask[2] = {0}, val[2] = {0}; + int rc; + + mutex_lock(&vreg->pc_lock); + + val[vreg->part->pc.word] + |= vreg->pdata.pin_ctrl << vreg->part->pc.shift; + mask[vreg->part->pc.word] |= vreg->part->pc.mask; + + val[vreg->part->pf.word] |= vreg->pdata.pin_fn << vreg->part->pf.shift; + mask[vreg->part->pf.word] |= vreg->part->pf.mask; + + if (!vreg->is_enabled) + set_enable(vreg, mask, val); + + rc = vreg_set(vreg, mask[0], val[0], mask[1], val[1], + vreg->part->request_len); + + if (!rc) + vreg->is_enabled_pc = true; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "vreg_set failed, rc=%d\n", rc); + + return rc; +} + +static int vreg_pin_control_disable(struct regulator_dev *rdev) +{ + struct vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mask[2] = {0}, val[2] = {0}; + enum rpm_vreg_pin_fn pin_fn; + int rc; + + mutex_lock(&vreg->pc_lock); + + val[vreg->part->pc.word] + |= RPM_VREG_PIN_CTRL_NONE << vreg->part->pc.shift; + mask[vreg->part->pc.word] |= vreg->part->pc.mask; + + pin_fn = RPM_VREG_PIN_FN_NONE; + if (vreg->pdata.pin_fn == RPM_VREG_PIN_FN_SLEEP_B) + pin_fn = RPM_VREG_PIN_FN_SLEEP_B; + val[vreg->part->pf.word] |= pin_fn << vreg->part->pf.shift; + mask[vreg->part->pf.word] |= vreg->part->pf.mask; + + if (!vreg->is_enabled) + set_disable(vreg, mask, val); + + rc = vreg_set(vreg, mask[0], val[0], mask[1], val[1], + vreg->part->request_len); + + if (!rc) + vreg->is_enabled_pc = false; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "vreg_set failed, rc=%d\n", rc); + + return rc; +} + +/* Real regulator operations. */ +static struct regulator_ops ldo_ops = { + .enable = vreg_enable, + .disable = vreg_disable, + .is_enabled = vreg_is_enabled, + .set_voltage = vreg_set_voltage, + .get_voltage = vreg_get_voltage, + .list_voltage = vreg_list_voltage, + .set_mode = vreg_set_mode, + .get_mode = vreg_get_mode, + .get_optimum_mode = vreg_get_optimum_mode, +}; + +static struct regulator_ops smps_ops = { + .enable = vreg_enable, + .disable = vreg_disable, + .is_enabled = vreg_is_enabled, + .set_voltage = vreg_set_voltage, + .get_voltage = vreg_get_voltage, + .list_voltage = vreg_list_voltage, + .set_mode = vreg_set_mode, + .get_mode = vreg_get_mode, + .get_optimum_mode = vreg_get_optimum_mode, +}; + +static struct regulator_ops switch_ops = { + .enable = vreg_enable, + .disable = vreg_disable, + .is_enabled = vreg_is_enabled, +}; + +static struct regulator_ops ncp_ops = { + .enable = vreg_enable, + .disable = vreg_disable, + .is_enabled = vreg_is_enabled, + .set_voltage = vreg_set_voltage, + .get_voltage = vreg_get_voltage, + .list_voltage = vreg_list_voltage, +}; + +/* Pin control regulator operations. */ +static struct regulator_ops pin_control_ops = { + .enable = vreg_pin_control_enable, + .disable = vreg_pin_control_disable, + .is_enabled = vreg_pin_control_is_enabled, +}; + +#define VREG_DESC(_id, _name, _ops) \ + [RPM_VREG_ID_PM8921_##_id] = { \ + .id = RPM_VREG_ID_PM8921_##_id, \ + .name = _name, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static struct regulator_desc vreg_description[] = { + VREG_DESC(L1, "8921_l1", &ldo_ops), + VREG_DESC(L2, "8921_l2", &ldo_ops), + VREG_DESC(L3, "8921_l3", &ldo_ops), + VREG_DESC(L4, "8921_l4", &ldo_ops), + VREG_DESC(L5, "8921_l5", &ldo_ops), + VREG_DESC(L6, "8921_l6", &ldo_ops), + VREG_DESC(L7, "8921_l7", &ldo_ops), + VREG_DESC(L8, "8921_l8", &ldo_ops), + VREG_DESC(L9, "8921_l9", &ldo_ops), + VREG_DESC(L10, "8921_l10", &ldo_ops), + VREG_DESC(L11, "8921_l11", &ldo_ops), + VREG_DESC(L12, "8921_l12", &ldo_ops), + VREG_DESC(L14, "8921_l14", &ldo_ops), + VREG_DESC(L15, "8921_l15", &ldo_ops), + VREG_DESC(L16, "8921_l16", &ldo_ops), + VREG_DESC(L17, "8921_l17", &ldo_ops), + VREG_DESC(L18, "8921_l18", &ldo_ops), + VREG_DESC(L21, "8921_l21", &ldo_ops), + VREG_DESC(L22, "8921_l22", &ldo_ops), + VREG_DESC(L23, "8921_l23", &ldo_ops), + VREG_DESC(L24, "8921_l24", &ldo_ops), + VREG_DESC(L25, "8921_l25", &ldo_ops), + VREG_DESC(L26, "8921_l26", &ldo_ops), + VREG_DESC(L27, "8921_l27", &ldo_ops), + VREG_DESC(L28, "8921_l28", &ldo_ops), + VREG_DESC(L29, "8921_l29", &ldo_ops), + + VREG_DESC(S1, "8921_s1", &smps_ops), + VREG_DESC(S2, "8921_s2", &smps_ops), + VREG_DESC(S3, "8921_s3", &smps_ops), + VREG_DESC(S4, "8921_s4", &smps_ops), + VREG_DESC(S5, "8921_s5", &smps_ops), + VREG_DESC(S6, "8921_s6", &smps_ops), + VREG_DESC(S7, "8921_s7", &smps_ops), + VREG_DESC(S8, "8921_s8", &smps_ops), + + VREG_DESC(LVS1, "8921_lvs1", &switch_ops), + VREG_DESC(LVS2, "8921_lvs2", &switch_ops), + VREG_DESC(LVS3, "8921_lvs3", &switch_ops), + VREG_DESC(LVS4, "8921_lvs4", &switch_ops), + VREG_DESC(LVS5, "8921_lvs5", &switch_ops), + VREG_DESC(LVS6, "8921_lvs6", &switch_ops), + VREG_DESC(LVS7, "8921_lvs7", &switch_ops), + + VREG_DESC(USB_OTG, "8921_usb_otg", &switch_ops), + VREG_DESC(HDMI_MVS, "8921_hdmi_mvs", &switch_ops), + VREG_DESC(NCP, "8921_ncp", &ncp_ops), + + VREG_DESC(L1_PC, "8921_l1_pc", &pin_control_ops), + VREG_DESC(L2_PC, "8921_l2_pc", &pin_control_ops), + VREG_DESC(L3_PC, "8921_l3_pc", &pin_control_ops), + VREG_DESC(L4_PC, "8921_l4_pc", &pin_control_ops), + VREG_DESC(L5_PC, "8921_l5_pc", &pin_control_ops), + VREG_DESC(L6_PC, "8921_l6_pc", &pin_control_ops), + VREG_DESC(L7_PC, "8921_l7_pc", &pin_control_ops), + VREG_DESC(L8_PC, "8921_l8_pc", &pin_control_ops), + VREG_DESC(L9_PC, "8921_l9_pc", &pin_control_ops), + VREG_DESC(L10_PC, "8921_l10_pc", &pin_control_ops), + VREG_DESC(L11_PC, "8921_l11_pc", &pin_control_ops), + VREG_DESC(L12_PC, "8921_l12_pc", &pin_control_ops), + VREG_DESC(L14_PC, "8921_l14_pc", &pin_control_ops), + VREG_DESC(L15_PC, "8921_l15_pc", &pin_control_ops), + VREG_DESC(L16_PC, "8921_l16_pc", &pin_control_ops), + VREG_DESC(L17_PC, "8921_l17_pc", &pin_control_ops), + VREG_DESC(L18_PC, "8921_l18_pc", &pin_control_ops), + VREG_DESC(L21_PC, "8921_l21_pc", &pin_control_ops), + VREG_DESC(L22_PC, "8921_l22_pc", &pin_control_ops), + VREG_DESC(L23_PC, "8921_l23_pc", &pin_control_ops), + VREG_DESC(L29_PC, "8921_l29_pc", &pin_control_ops), + + VREG_DESC(S1_PC, "8921_s1_pc", &pin_control_ops), + VREG_DESC(S2_PC, "8921_s2_pc", &pin_control_ops), + VREG_DESC(S3_PC, "8921_s3_pc", &pin_control_ops), + VREG_DESC(S4_PC, "8921_s4_pc", &pin_control_ops), + VREG_DESC(S7_PC, "8921_s7_pc", &pin_control_ops), + VREG_DESC(S8_PC, "8921_s8_pc", &pin_control_ops), + + VREG_DESC(LVS1_PC, "8921_lvs1_pc", &pin_control_ops), + VREG_DESC(LVS3_PC, "8921_lvs3_pc", &pin_control_ops), + VREG_DESC(LVS4_PC, "8921_lvs4_pc", &pin_control_ops), + VREG_DESC(LVS5_PC, "8921_lvs5_pc", &pin_control_ops), + VREG_DESC(LVS6_PC, "8921_lvs6_pc", &pin_control_ops), + VREG_DESC(LVS7_PC, "8921_lvs7_pc", &pin_control_ops), +}; + +static inline int is_real_regulator(int id) +{ + return (id >= 0) && (id <= RPM_VREG_ID_PM8921_MAX_REAL); +} + +static int pc_id_to_real_id(int id) +{ + int real_id; + + if (id >= RPM_VREG_ID_PM8921_L1_PC && id <= RPM_VREG_ID_PM8921_L23_PC) + real_id = id - RPM_VREG_ID_PM8921_L1_PC; + else if (id >= RPM_VREG_ID_PM8921_L29_PC + && id <= RPM_VREG_ID_PM8921_S4_PC) + real_id = id - RPM_VREG_ID_PM8921_L29_PC + + RPM_VREG_ID_PM8921_L29; + else if (id >= RPM_VREG_ID_PM8921_S7_PC + && id <= RPM_VREG_ID_PM8921_LVS1_PC) + real_id = id - RPM_VREG_ID_PM8921_S7_PC + RPM_VREG_ID_PM8921_S7; + else + real_id = id - RPM_VREG_ID_PM8921_LVS3_PC + + RPM_VREG_ID_PM8921_LVS3; + + return real_id; +} + +static int __devinit +rpm_vreg_init_regulator(const struct rpm_regulator_init_data *pdata, + struct device *dev) +{ + enum rpm_vreg_pin_fn pin_fn; + struct regulator_desc *rdesc; + struct regulator_dev *rdev; + struct vreg *vreg; + const char *reg_name = ""; + unsigned pin_ctrl; + int rc = 0, id = pdata->id; + + if (id < 0 || id > RPM_VREG_ID_PM8921_MAX) { + pr_err("invalid regulator id: %d\n", id); + return -ENODEV; + } + + rdesc = &vreg_description[pdata->id]; + if (!is_real_regulator(pdata->id)) + id = pc_id_to_real_id(pdata->id); + vreg = &vregs[id]; + reg_name = vreg_description[pdata->id].name; + if (!pdata) { + pr_err("%s: requires platform data\n", reg_name); + return -EINVAL; + } + if (vreg->set_points) + rdesc->n_voltages = vreg->set_points->n_voltages; + else + rdesc->n_voltages = 0; + + mutex_lock(&vreg->pc_lock); + + if (is_real_regulator(pdata->id)) { + /* Do not modify pin control and pin function values. */ + pin_ctrl = vreg->pdata.pin_ctrl; + pin_fn = vreg->pdata.pin_fn; + memcpy(&(vreg->pdata), pdata, + sizeof(struct rpm_regulator_init_data)); + vreg->pdata.pin_ctrl = pin_ctrl; + vreg->pdata.pin_fn = pin_fn; + vreg->name = reg_name; + + vreg->save_uV = vreg->pdata.default_uV; + if (vreg->pdata.peak_uA >= vreg->hpm_min_load) + vreg->mode = REGULATOR_MODE_NORMAL; + else + vreg->mode = REGULATOR_MODE_IDLE; + + /* Initialize the RPM request. */ + SET_PART(vreg, ip, + MICRO_TO_MILLI(saturate_peak_load(vreg, vreg->pdata.peak_uA))); + SET_PART(vreg, fm, vreg->pdata.force_mode); + SET_PART(vreg, pm, vreg->pdata.power_mode); + SET_PART(vreg, pd, vreg->pdata.pull_down_enable); + SET_PART(vreg, ia, + MICRO_TO_MILLI(saturate_avg_load(vreg, vreg->pdata.avg_uA))); + SET_PART(vreg, freq, vreg->pdata.freq); + SET_PART(vreg, freq_clk_src, 0); + SET_PART(vreg, comp_mode, 0); + SET_PART(vreg, hpm, 0); + if (!vreg->is_enabled_pc) { + SET_PART(vreg, pf, RPM_VREG_PIN_FN_NONE); + SET_PART(vreg, pc, RPM_VREG_PIN_CTRL_NONE); + } + } else { + /* Pin control regulator */ + if ((pdata->pin_ctrl & RPM_VREG_PIN_CTRL_ALL) + == RPM_VREG_PIN_CTRL_NONE + && pdata->pin_fn != RPM_VREG_PIN_FN_SLEEP_B) { + pr_err("%s: no pin control input specified\n", + reg_name); + mutex_unlock(&vreg->pc_lock); + return -EINVAL; + } + vreg->pdata.pin_ctrl = pdata->pin_ctrl; + vreg->pdata.pin_fn = pdata->pin_fn; + if (!vreg->name) + vreg->name = reg_name; + + /* Initialize the RPM request. */ + pin_fn = RPM_VREG_PIN_FN_NONE; + /* Allow pf=sleep_b to be specified by platform data. */ + if (vreg->pdata.pin_fn == RPM_VREG_PIN_FN_SLEEP_B) + pin_fn = RPM_VREG_PIN_FN_SLEEP_B; + SET_PART(vreg, pf, pin_fn); + SET_PART(vreg, pc, RPM_VREG_PIN_CTRL_NONE); + } + + mutex_unlock(&vreg->pc_lock); + + if (rc) + goto bail; + + rdev = regulator_register(rdesc, dev, &(pdata->init_data), vreg); + if (IS_ERR(rdev)) { + rc = PTR_ERR(rdev); + pr_err("regulator_register failed: %s, rc=%d\n", reg_name, rc); + return rc; + } else { + if (is_real_regulator(pdata->id)) + vreg->rdev = rdev; + else + vreg->rdev_pc = rdev; + } + +bail: + if (rc) + pr_err("error for %s, rc=%d\n", reg_name, rc); + + return rc; +} + +static int __devinit rpm_vreg_probe(struct platform_device *pdev) +{ + struct rpm_regulator_platform_data *platform_data; + int rc = 0; + int i; + + platform_data = pdev->dev.platform_data; + if (!platform_data) { + pr_err("rpm-regulator requires platform data\n"); + return -EINVAL; + } + + /* Initialize all of the regulators listed in the platform data. */ + for (i = 0; i < platform_data->num_regulators; i++) { + rc = rpm_vreg_init_regulator(&platform_data->init_data[i], + &pdev->dev); + if (rc) { + pr_err("rpm_vreg_init_regulator failed, rc=%d\n", rc); + goto remove_regulators; + } + } + + platform_set_drvdata(pdev, platform_data); + + return rc; + +remove_regulators: + /* Unregister all regulators added before the erroring one. */ + for (; i >= 0; i--) { + if (is_real_regulator(platform_data->init_data[i].id)) + regulator_unregister(vregs[i].rdev); + else + regulator_unregister( + vregs[pc_id_to_real_id(i)].rdev_pc); + } + + return rc; +} + +static int __devexit rpm_vreg_remove(struct platform_device *pdev) +{ + struct rpm_regulator_platform_data *platform_data; + int i, id; + + platform_data = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + if (platform_data) { + for (i = 0; i < platform_data->num_regulators; i++) { + id = platform_data->init_data[i].id; + if (is_real_regulator(id)) { + regulator_unregister(vregs[id].rdev); + vregs[id].rdev = NULL; + } else { + regulator_unregister( + vregs[pc_id_to_real_id(id)].rdev_pc); + vregs[id].rdev_pc = NULL; + } + } + } + + return 0; +} + +static struct platform_driver rpm_vreg_driver = { + .probe = rpm_vreg_probe, + .remove = __devexit_p(rpm_vreg_remove), + .driver = { + .name = RPM_REGULATOR_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init rpm_vreg_init(void) +{ + struct vreg_set_points *set_points[] = { + &pldo_set_points, + &nldo_set_points, + &nldo1200_set_points, + &smps_set_points, + &ftsmps_set_points, + &ncp_set_points, + }; + int i, j; + + /* Calculate the number of set points available for each regualtor. */ + for (i = 0; i < ARRAY_SIZE(set_points); i++) { + for (j = 0; j < set_points[i]->count; j++) { + set_points[i]->range[j].n_voltages + = (set_points[i]->range[j].max_uV + - set_points[i]->range[j].min_uV) + / set_points[i]->range[j].step_uV + 1; + set_points[i]->n_voltages + += set_points[i]->range[j].n_voltages; + } + } + + /* Initialize pin control mutexes */ + for (i = 0; i < ARRAY_SIZE(vregs); i++) + mutex_init(&vregs[i].pc_lock); + + return platform_driver_register(&rpm_vreg_driver); +} + +static void __exit rpm_vreg_exit(void) +{ + int i; + + platform_driver_unregister(&rpm_vreg_driver); + + for (i = 0; i < ARRAY_SIZE(vregs); i++) + mutex_destroy(&vregs[i].pc_lock); +} + +postcore_initcall(rpm_vreg_init); +module_exit(rpm_vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM8960 rpm regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" RPM_REGULATOR_DEV_NAME); diff --git a/arch/arm/mach-msm/rpm-regulator.c b/arch/arm/mach-msm/rpm-regulator.c new file mode 100644 index 00000000000..b9e577a9e70 --- /dev/null +++ b/arch/arm/mach-msm/rpm-regulator.c @@ -0,0 +1,1667 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rpm_resources.h" + +/* Debug Definitions */ + +enum { + MSM_RPM_VREG_DEBUG_REQUEST = BIT(0), + MSM_RPM_VREG_DEBUG_VOTE = BIT(1), + MSM_RPM_VREG_DEBUG_DUPLICATE = BIT(2), + MSM_RPM_VREG_DEBUG_IGNORE_8058_S0_S1 = BIT(3), +}; + +static int msm_rpm_vreg_debug_mask; +module_param_named( + debug_mask, msm_rpm_vreg_debug_mask, int, S_IRUSR | S_IWUSR +); + +#define MICRO_TO_MILLI(uV) ((uV) / 1000) +#define MILLI_TO_MICRO(mV) ((mV) * 1000) + +/* LDO register word 1 */ +#define LDO_VOLTAGE 0x00000FFF +#define LDO_VOLTAGE_SHIFT 0 +#define LDO_PEAK_CURRENT 0x00FFF000 +#define LDO_PEAK_CURRENT_SHIFT 12 +#define LDO_MODE 0x03000000 +#define LDO_MODE_SHIFT 24 +#define LDO_PIN_CTRL 0x3C000000 +#define LDO_PIN_CTRL_SHIFT 26 +#define LDO_PIN_FN 0xC0000000 +#define LDO_PIN_FN_SHIFT 30 + +/* LDO register word 2 */ +#define LDO_PULL_DOWN_ENABLE 0x00000001 +#define LDO_PULL_DOWN_ENABLE_SHIFT 0 +#define LDO_AVG_CURRENT 0x00001FFE +#define LDO_AVG_CURRENT_SHIFT 1 + +/* SMPS register word 1 */ +#define SMPS_VOLTAGE 0x00000FFF +#define SMPS_VOLTAGE_SHIFT 0 +#define SMPS_PEAK_CURRENT 0x00FFF000 +#define SMPS_PEAK_CURRENT_SHIFT 12 +#define SMPS_MODE 0x03000000 +#define SMPS_MODE_SHIFT 24 +#define SMPS_PIN_CTRL 0x3C000000 +#define SMPS_PIN_CTRL_SHIFT 26 +#define SMPS_PIN_FN 0xC0000000 +#define SMPS_PIN_FN_SHIFT 30 + +/* SMPS register word 2 */ +#define SMPS_PULL_DOWN_ENABLE 0x00000001 +#define SMPS_PULL_DOWN_ENABLE_SHIFT 0 +#define SMPS_AVG_CURRENT 0x00001FFE +#define SMPS_AVG_CURRENT_SHIFT 1 +#define SMPS_FREQ 0x001FE000 +#define SMPS_FREQ_SHIFT 13 +#define SMPS_CLK_SRC 0x00600000 +#define SMPS_CLK_SRC_SHIFT 21 + +/* SWITCH register word 1 */ +#define SWITCH_STATE 0x0001 +#define SWITCH_STATE_SHIFT 0 +#define SWITCH_PULL_DOWN_ENABLE 0x0002 +#define SWITCH_PULL_DOWN_ENABLE_SHIFT 1 +#define SWITCH_PIN_CTRL 0x003C +#define SWITCH_PIN_CTRL_SHIFT 2 +#define SWITCH_PIN_FN 0x00C0 +#define SWITCH_PIN_FN_SHIFT 6 + +/* NCP register word 1 */ +#define NCP_VOLTAGE 0x0FFF +#define NCP_VOLTAGE_SHIFT 0 +#define NCP_STATE 0x1000 +#define NCP_STATE_SHIFT 12 + +/* + * This is used when voting for LPM or HPM by subtracting or adding to the + * hpm_min_load of a regulator. It has units of uA. + */ +#define LOAD_THRESHOLD_STEP 1000 + +/* This is the maximum uA load that can be passed to the RPM. */ +#define MAX_POSSIBLE_LOAD (MILLI_TO_MICRO(0xFFF)) + +/* Voltage regulator types */ +#define IS_LDO(id) ((id >= RPM_VREG_ID_PM8058_L0 && \ + id <= RPM_VREG_ID_PM8058_L25) || \ + (id >= RPM_VREG_ID_PM8901_L0 && \ + id <= RPM_VREG_ID_PM8901_L6)) +#define IS_SMPS(id) ((id >= RPM_VREG_ID_PM8058_S0 && \ + id <= RPM_VREG_ID_PM8058_S4) || \ + (id >= RPM_VREG_ID_PM8901_S0 && \ + id <= RPM_VREG_ID_PM8901_S4)) +#define IS_SWITCH(id) ((id >= RPM_VREG_ID_PM8058_LVS0 && \ + id <= RPM_VREG_ID_PM8058_LVS1) || \ + (id >= RPM_VREG_ID_PM8901_LVS0 && \ + id <= RPM_VREG_ID_PM8901_LVS3) || \ + (id == RPM_VREG_ID_PM8901_MVS0)) +#define IS_NCP(id) (id == RPM_VREG_ID_PM8058_NCP) + +#define IS_8901_SMPS(id) ((id >= RPM_VREG_ID_PM8901_S0 && \ + id <= RPM_VREG_ID_PM8901_S4)) + +struct vreg { + struct msm_rpm_iv_pair req[2]; + struct msm_rpm_iv_pair prev_active_req[2]; + struct msm_rpm_iv_pair prev_sleep_req[2]; + struct rpm_vreg_pdata *pdata; + int save_uV; + const int hpm_min_load; + unsigned pc_vote; + unsigned optimum; + unsigned mode_initialized; + int active_min_mV_vote[RPM_VREG_VOTER_COUNT]; + int sleep_min_mV_vote[RPM_VREG_VOTER_COUNT]; + enum rpm_vreg_id id; +}; + +#define RPM_VREG_NCP_HPM_MIN_LOAD 0 + +#define VREG_2(_vreg_id, _rpm_id, _hpm_min_load) \ + [RPM_VREG_ID_##_vreg_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_##_rpm_id##_0, }, \ + [1] = { .id = MSM_RPM_ID_##_rpm_id##_1, }, \ + }, \ + .hpm_min_load = RPM_VREG_##_hpm_min_load, \ + } + +#define VREG_1(_vreg_id, _rpm_id) \ + [RPM_VREG_ID_##_vreg_id] = { \ + .req = { \ + [0] = { .id = MSM_RPM_ID_##_rpm_id, }, \ + [1] = { .id = -1, }, \ + }, \ + } + +static struct vreg vregs[RPM_VREG_ID_MAX] = { + VREG_2(PM8058_L0, LDO0, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L1, LDO1, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L2, LDO2, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L3, LDO3, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L4, LDO4, LDO_50_HPM_MIN_LOAD), + VREG_2(PM8058_L5, LDO5, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L6, LDO6, LDO_50_HPM_MIN_LOAD), + VREG_2(PM8058_L7, LDO7, LDO_50_HPM_MIN_LOAD), + VREG_2(PM8058_L8, LDO8, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L9, LDO9, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L10, LDO10, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L11, LDO11, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L12, LDO12, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L13, LDO13, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L14, LDO14, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L15, LDO15, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L16, LDO16, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L17, LDO17, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L18, LDO18, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L19, LDO19, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L20, LDO20, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L21, LDO21, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L22, LDO22, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L23, LDO23, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8058_L24, LDO24, LDO_150_HPM_MIN_LOAD), + VREG_2(PM8058_L25, LDO25, LDO_150_HPM_MIN_LOAD), + + VREG_2(PM8058_S0, SMPS0, SMPS_HPM_MIN_LOAD), + VREG_2(PM8058_S1, SMPS1, SMPS_HPM_MIN_LOAD), + VREG_2(PM8058_S2, SMPS2, SMPS_HPM_MIN_LOAD), + VREG_2(PM8058_S3, SMPS3, SMPS_HPM_MIN_LOAD), + VREG_2(PM8058_S4, SMPS4, SMPS_HPM_MIN_LOAD), + + VREG_1(PM8058_LVS0, LVS0), + VREG_1(PM8058_LVS1, LVS1), + + VREG_2(PM8058_NCP, NCP, NCP_HPM_MIN_LOAD), + + VREG_2(PM8901_L0, LDO0B, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8901_L1, LDO1B, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8901_L2, LDO2B, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8901_L3, LDO3B, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8901_L4, LDO4B, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8901_L5, LDO5B, LDO_300_HPM_MIN_LOAD), + VREG_2(PM8901_L6, LDO6B, LDO_300_HPM_MIN_LOAD), + + VREG_2(PM8901_S0, SMPS0B, FTSMPS_HPM_MIN_LOAD), + VREG_2(PM8901_S1, SMPS1B, FTSMPS_HPM_MIN_LOAD), + VREG_2(PM8901_S2, SMPS2B, FTSMPS_HPM_MIN_LOAD), + VREG_2(PM8901_S3, SMPS3B, FTSMPS_HPM_MIN_LOAD), + VREG_2(PM8901_S4, SMPS4B, FTSMPS_HPM_MIN_LOAD), + + VREG_1(PM8901_LVS0, LVS0B), + VREG_1(PM8901_LVS1, LVS1B), + VREG_1(PM8901_LVS2, LVS2B), + VREG_1(PM8901_LVS3, LVS3B), + + VREG_1(PM8901_MVS0, MVS), +}; + +static void print_rpm_request(struct vreg *vreg, int set); +static void print_rpm_vote(struct vreg *vreg, enum rpm_vreg_voter voter, + int set, int voter_mV, int aggregate_mV); +static void print_rpm_duplicate(struct vreg *vreg, int set, int cnt); + +static unsigned int smps_get_mode(struct regulator_dev *dev); +static unsigned int ldo_get_mode(struct regulator_dev *dev); +static unsigned int switch_get_mode(struct regulator_dev *dev); + +/* Spin lock needed for sleep-selectable regulators. */ +static DEFINE_SPINLOCK(pm8058_noirq_lock); + +static int voltage_from_req(struct vreg *vreg) +{ + int shift = 0; + uint32_t value = 0, mask = 0; + + value = vreg->req[0].value; + + if (IS_SMPS(vreg->id)) { + mask = SMPS_VOLTAGE; + shift = SMPS_VOLTAGE_SHIFT; + } else if (IS_LDO(vreg->id)) { + mask = LDO_VOLTAGE; + shift = LDO_VOLTAGE_SHIFT; + } else if (IS_NCP(vreg->id)) { + mask = NCP_VOLTAGE; + shift = NCP_VOLTAGE_SHIFT; + } + + return (value & mask) >> shift; +} + +static void voltage_to_req(int voltage, struct vreg *vreg) +{ + int shift = 0; + uint32_t *value = NULL, mask = 0; + + value = &(vreg->req[0].value); + + if (IS_SMPS(vreg->id)) { + mask = SMPS_VOLTAGE; + shift = SMPS_VOLTAGE_SHIFT; + } else if (IS_LDO(vreg->id)) { + mask = LDO_VOLTAGE; + shift = LDO_VOLTAGE_SHIFT; + } else if (IS_NCP(vreg->id)) { + mask = NCP_VOLTAGE; + shift = NCP_VOLTAGE_SHIFT; + } + + *value &= ~mask; + *value |= (voltage << shift) & mask; +} + +static int vreg_send_request(struct vreg *vreg, enum rpm_vreg_voter voter, + int set, unsigned mask0, unsigned val0, + unsigned mask1, unsigned val1, unsigned cnt, + int update_voltage) +{ + struct msm_rpm_iv_pair *prev_req; + int rc = 0, max_mV_vote = 0, i; + unsigned prev0, prev1; + int *min_mV_vote; + + if (set == MSM_RPM_CTX_SET_0) { + min_mV_vote = vreg->active_min_mV_vote; + prev_req = vreg->prev_active_req; + } else { + min_mV_vote = vreg->sleep_min_mV_vote; + prev_req = vreg->prev_sleep_req; + } + + prev0 = vreg->req[0].value; + vreg->req[0].value &= ~mask0; + vreg->req[0].value |= val0 & mask0; + + prev1 = vreg->req[1].value; + vreg->req[1].value &= ~mask1; + vreg->req[1].value |= val1 & mask1; + + if (update_voltage) + min_mV_vote[voter] = voltage_from_req(vreg); + + /* Find the highest voltage voted for and use it. */ + for (i = 0; i < RPM_VREG_VOTER_COUNT; i++) + max_mV_vote = max(max_mV_vote, min_mV_vote[i]); + voltage_to_req(max_mV_vote, vreg); + + if (msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_VOTE) + print_rpm_vote(vreg, voter, set, min_mV_vote[voter], + max_mV_vote); + + /* Ignore duplicate requests */ + if (vreg->req[0].value != prev_req[0].value || + vreg->req[1].value != prev_req[1].value) { + + rc = msm_rpmrs_set_noirq(set, vreg->req, cnt); + if (rc) { + vreg->req[0].value = prev0; + vreg->req[1].value = prev1; + + pr_err("%s: msm_rpmrs_set_noirq failed - " + "set=%s, id=%d, rc=%d\n", __func__, + (set == MSM_RPM_CTX_SET_0 ? "active" : "sleep"), + vreg->req[0].id, rc); + } else { + /* Only save if nonzero and active set. */ + if (max_mV_vote && (set == MSM_RPM_CTX_SET_0)) + vreg->save_uV = MILLI_TO_MICRO(max_mV_vote); + if (msm_rpm_vreg_debug_mask + & MSM_RPM_VREG_DEBUG_REQUEST) + print_rpm_request(vreg, set); + prev_req[0].value = vreg->req[0].value; + prev_req[1].value = vreg->req[1].value; + } + } else if (msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_DUPLICATE) { + print_rpm_duplicate(vreg, set, cnt); + } + + return rc; +} + +static int vreg_set_noirq(struct vreg *vreg, enum rpm_vreg_voter voter, + int sleep, unsigned mask0, unsigned val0, + unsigned mask1, unsigned val1, unsigned cnt, + int update_voltage) +{ + unsigned long flags; + int rc; + unsigned val0_sleep, mask0_sleep; + + if (voter < 0 || voter >= RPM_VREG_VOTER_COUNT) + return -EINVAL; + + spin_lock_irqsave(&pm8058_noirq_lock, flags); + + /* + * Send sleep set request first so that subsequent set_mode, etc calls + * use the voltage from the active set. + */ + if (sleep) + rc = vreg_send_request(vreg, voter, MSM_RPM_CTX_SET_SLEEP, + mask0, val0, mask1, val1, cnt, update_voltage); + else { + /* + * Vote for 0 V in the sleep set when active set-only is + * specified. This ensures that a disable vote will be issued + * at some point for the sleep set of the regulator. + */ + val0_sleep = val0; + mask0_sleep = mask0; + if (IS_SMPS(vreg->id)) { + val0_sleep &= ~SMPS_VOLTAGE; + mask0_sleep |= SMPS_VOLTAGE; + } else if (IS_LDO(vreg->id)) { + val0_sleep &= ~LDO_VOLTAGE; + mask0_sleep |= LDO_VOLTAGE; + } else if (IS_NCP(vreg->id)) { + val0_sleep &= ~NCP_VOLTAGE; + mask0_sleep |= NCP_VOLTAGE; + } + + rc = vreg_send_request(vreg, voter, MSM_RPM_CTX_SET_SLEEP, + mask0_sleep, val0_sleep, + mask1, val1, cnt, update_voltage); + } + + rc = vreg_send_request(vreg, voter, MSM_RPM_CTX_SET_0, mask0, val0, + mask1, val1, cnt, update_voltage); + + spin_unlock_irqrestore(&pm8058_noirq_lock, flags); + + return rc; +} + +/** + * rpm_vreg_set_voltage - vote for a min_uV value of specified regualtor + * @vreg: ID for regulator + * @voter: ID for the voter + * @min_uV: minimum acceptable voltage (in uV) that is voted for + * @max_uV: maximum acceptable voltage (in uV) that is voted for + * @sleep_also: 0 for active set only, non-0 for active set and sleep set + * + * Returns 0 on success or errno. + * + * This function is used to vote for the voltage of a regulator without + * using the regulator framework. It is needed by consumers which hold spin + * locks or have interrupts disabled because the regulator framework can sleep. + * It is also needed by consumers which wish to only vote for active set + * regulator voltage. + * + * If sleep_also == 0, then a sleep-set value of 0V will be voted for. + * + * This function may only be called for regulators which have the sleep flag + * specified in their private data. + */ +int rpm_vreg_set_voltage(enum rpm_vreg_id vreg_id, enum rpm_vreg_voter voter, + int min_uV, int max_uV, int sleep_also) +{ + int rc; + unsigned val0 = 0, val1 = 0, mask0 = 0, mask1 = 0, cnt = 2; + + if (vreg_id < 0 || vreg_id >= RPM_VREG_ID_MAX) + return -EINVAL; + + if (!vregs[vreg_id].pdata->sleep_selectable) + return -EINVAL; + + if (min_uV < vregs[vreg_id].pdata->init_data.constraints.min_uV || + min_uV > vregs[vreg_id].pdata->init_data.constraints.max_uV) + return -EINVAL; + + if (IS_SMPS(vreg_id)) { + mask0 = SMPS_VOLTAGE; + val0 = MICRO_TO_MILLI(min_uV) << SMPS_VOLTAGE_SHIFT; + } else if (IS_LDO(vreg_id)) { + mask0 = LDO_VOLTAGE; + val0 = MICRO_TO_MILLI(min_uV) << LDO_VOLTAGE_SHIFT; + } else if (IS_NCP(vreg_id)) { + mask0 = NCP_VOLTAGE; + val0 = MICRO_TO_MILLI(min_uV) << NCP_VOLTAGE_SHIFT; + cnt = 1; + } else { + cnt = 1; + } + + rc = vreg_set_noirq(&vregs[vreg_id], voter, sleep_also, mask0, val0, + mask1, val1, cnt, 1); + + return rc; +} +EXPORT_SYMBOL_GPL(rpm_vreg_set_voltage); + +/** + * rpm_vreg_set_frequency - sets the frequency of a switching regulator + * @vreg: ID for regulator + * @min_uV: minimum acceptable frequency of operation + * + * Returns 0 on success or errno. + */ +int rpm_vreg_set_frequency(enum rpm_vreg_id vreg_id, enum rpm_vreg_freq freq) +{ + unsigned val0 = 0, val1 = 0, mask0 = 0, mask1 = 0, cnt = 2; + int rc; + + if (vreg_id < 0 || vreg_id >= RPM_VREG_ID_MAX) { + pr_err("%s: invalid regulator id=%d\n", __func__, vreg_id); + return -EINVAL; + } + + if (freq < 0 || freq > RPM_VREG_FREQ_1p20) { + pr_err("%s: invalid frequency=%d\n", __func__, freq); + return -EINVAL; + } + + if (!IS_SMPS(vreg_id)) { + pr_err("%s: regulator id=%d does not support frequency\n", + __func__, vreg_id); + return -EINVAL; + } + + if (!vregs[vreg_id].pdata->sleep_selectable) { + pr_err("%s: regulator id=%d is not marked sleep selectable\n", + __func__, vreg_id); + return -EINVAL; + } + + mask1 = SMPS_FREQ; + val1 = freq << SMPS_FREQ_SHIFT; + + rc = vreg_set_noirq(&vregs[vreg_id], RPM_VREG_VOTER_REG_FRAMEWORK, + 1, mask0, val0, mask1, val1, cnt, 0); + + return rc; +} +EXPORT_SYMBOL_GPL(rpm_vreg_set_frequency); + +#define IS_PMIC_8901_V1(rev) ((rev) == PM_8901_REV_1p0 || \ + (rev) == PM_8901_REV_1p1) + +#define PMIC_8901_V1_SCALE(uV) ((((uV) - 62100) * 23) / 25) + +static inline int vreg_hpm_min_uA(struct vreg *vreg) +{ + return vreg->hpm_min_load; +} + +static inline int vreg_lpm_max_uA(struct vreg *vreg) +{ + return vreg->hpm_min_load - LOAD_THRESHOLD_STEP; +} + +static inline unsigned saturate_load(unsigned load_uA) +{ + return (load_uA > MAX_POSSIBLE_LOAD ? MAX_POSSIBLE_LOAD : load_uA); +} + +/* Change vreg->req, but do not send it to the RPM. */ +static int vreg_store(struct vreg *vreg, unsigned mask0, unsigned val0, + unsigned mask1, unsigned val1) +{ + unsigned long flags = 0; + + if (vreg->pdata->sleep_selectable) + spin_lock_irqsave(&pm8058_noirq_lock, flags); + + vreg->req[0].value &= ~mask0; + vreg->req[0].value |= val0 & mask0; + + vreg->req[1].value &= ~mask1; + vreg->req[1].value |= val1 & mask1; + + if (vreg->pdata->sleep_selectable) + spin_unlock_irqrestore(&pm8058_noirq_lock, flags); + + return 0; +} + +static int vreg_set(struct vreg *vreg, unsigned mask0, unsigned val0, + unsigned mask1, unsigned val1, unsigned cnt) +{ + unsigned prev0 = 0, prev1 = 0; + int rc; + + /* + * Bypass the normal route for regulators that can be called to change + * just the active set values. + */ + if (vreg->pdata->sleep_selectable) + return vreg_set_noirq(vreg, RPM_VREG_VOTER_REG_FRAMEWORK, 1, + mask0, val0, mask1, val1, cnt, 1); + + prev0 = vreg->req[0].value; + vreg->req[0].value &= ~mask0; + vreg->req[0].value |= val0 & mask0; + + prev1 = vreg->req[1].value; + vreg->req[1].value &= ~mask1; + vreg->req[1].value |= val1 & mask1; + + /* Ignore duplicate requests */ + if (vreg->req[0].value == vreg->prev_active_req[0].value && + vreg->req[1].value == vreg->prev_active_req[1].value) { + if (msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_DUPLICATE) + print_rpm_duplicate(vreg, MSM_RPM_CTX_SET_0, cnt); + return 0; + } + + rc = msm_rpm_set(MSM_RPM_CTX_SET_0, vreg->req, cnt); + if (rc) { + vreg->req[0].value = prev0; + vreg->req[1].value = prev1; + + pr_err("%s: msm_rpm_set fail id=%d, rc=%d\n", + __func__, vreg->req[0].id, rc); + } else { + if (msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_REQUEST) + print_rpm_request(vreg, MSM_RPM_CTX_SET_0); + vreg->prev_active_req[0].value = vreg->req[0].value; + vreg->prev_active_req[1].value = vreg->req[1].value; + } + + return rc; +} + +static int smps_is_enabled(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + return ((vreg->req[0].value & SMPS_VOLTAGE) >> SMPS_VOLTAGE_SHIFT) != 0; +} + +static int _smps_set_voltage(struct regulator_dev *dev, int min_uV) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + int scaled_min_uV = min_uV; + static int pmic8901_rev; + + /* Scale input request voltage down if using v1 PMIC 8901. */ + if (IS_8901_SMPS(vreg->id) && min_uV) { + if (pmic8901_rev <= 0) + pmic8901_rev = pm8901_rev(NULL); + + if (pmic8901_rev < 0) + pr_err("%s: setting %s to %d uV; PMIC 8901 revision " + "unavailable, no scaling can be performed.\n", + __func__, dev->desc->name, min_uV); + else if (IS_PMIC_8901_V1(pmic8901_rev)) + scaled_min_uV = PMIC_8901_V1_SCALE(min_uV); + } + + return vreg_set(vreg, SMPS_VOLTAGE, + MICRO_TO_MILLI(scaled_min_uV) << SMPS_VOLTAGE_SHIFT, + 0, 0, 2); +} + +static int smps_set_voltage(struct regulator_dev *dev, int min_uV, int max_uV, + unsigned *selector) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + int rc = 0; + + if (smps_is_enabled(dev)) + rc = _smps_set_voltage(dev, min_uV); + if (rc) + return rc; + + /* only save if nonzero (or not disabling) */ + if (min_uV && (!vreg->pdata->sleep_selectable || !smps_is_enabled(dev))) + vreg->save_uV = min_uV; + + return rc; +} + +static int smps_get_voltage(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + return vreg->save_uV; +} + +static int smps_enable(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + int rc = 0; + unsigned mask, val; + + /* enable by setting voltage */ + if (MICRO_TO_MILLI(vreg->save_uV) > 0) { + /* reenable pin control if it is in use */ + if (smps_get_mode(dev) == REGULATOR_MODE_IDLE) { + mask = SMPS_PIN_CTRL | SMPS_PIN_FN; + val = vreg->pdata->pin_ctrl << SMPS_PIN_CTRL_SHIFT + | vreg->pdata->pin_fn << SMPS_PIN_FN_SHIFT; + vreg_store(vreg, mask, val, 0, 0); + } + + rc = _smps_set_voltage(dev, vreg->save_uV); + } + return rc; +} + +static int smps_disable(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + unsigned mask, val; + + /* turn off pin control */ + mask = SMPS_PIN_CTRL | SMPS_PIN_FN; + val = RPM_VREG_PIN_CTRL_NONE << SMPS_PIN_CTRL_SHIFT + | RPM_VREG_PIN_FN_NONE << SMPS_PIN_FN_SHIFT; + vreg_store(vreg, mask, val, 0, 0); + + /* disable by setting voltage to zero */ + return _smps_set_voltage(dev, 0); +} + +/* + * Optimum mode programming: + * REGULATOR_MODE_FAST: Go to HPM (highest priority) + * REGULATOR_MODE_STANDBY: Go to pin ctrl mode if there are any pin ctrl + * votes, else go to LPM + * + * Pin ctrl mode voting via regulator set_mode: + * REGULATOR_MODE_IDLE: Go to pin ctrl mode if the optimum mode is LPM, else + * go to HPM + * REGULATOR_MODE_NORMAL: Go to LPM if it is the optimum mode, else go to HPM + * + * Pin ctrl mode takes priority on the RPM when force mode is not set; + * therefore, pin ctrl bits must be cleared if LPM or HPM is being voted for. + */ +static int smps_set_mode(struct regulator_dev *dev, unsigned int mode) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + unsigned optimum = vreg->optimum; + unsigned pc_vote = vreg->pc_vote; + unsigned mode_initialized = vreg->mode_initialized; + unsigned mask0 = 0, val0 = 0, mask1 = 0, val1 = 0; + int set_hpm = -1, set_pin_control = -1; + int peak_uA; + int rc = 0; + + peak_uA = MILLI_TO_MICRO((vreg->req[0].value & SMPS_PEAK_CURRENT) >> + SMPS_PEAK_CURRENT_SHIFT); + + switch (mode) { + case REGULATOR_MODE_FAST: + set_hpm = 1; + set_pin_control = 0; + optimum = REGULATOR_MODE_FAST; + mode_initialized = 1; + break; + + case REGULATOR_MODE_STANDBY: + set_hpm = 0; + if (pc_vote) + set_pin_control = 1; + else + set_pin_control = 0; + optimum = REGULATOR_MODE_STANDBY; + mode_initialized = 1; + break; + + case REGULATOR_MODE_IDLE: + if (pc_vote++) + goto done; /* already taken care of */ + + if (mode_initialized && optimum == REGULATOR_MODE_FAST) { + set_hpm = 1; + set_pin_control = 0; + } else { + set_pin_control = 1; + } + break; + + case REGULATOR_MODE_NORMAL: + if (pc_vote && --pc_vote) + goto done; /* already taken care of */ + + if (optimum == REGULATOR_MODE_STANDBY) + set_hpm = 0; + else + set_hpm = 1; + set_pin_control = 0; + break; + + default: + return -EINVAL; + } + + if (set_hpm == 1) { + /* Make sure that request currents are at HPM level. */ + if (peak_uA < vreg_hpm_min_uA(vreg)) { + mask0 = SMPS_PEAK_CURRENT; + mask1 = SMPS_AVG_CURRENT; + val0 = (MICRO_TO_MILLI(vreg_hpm_min_uA(vreg)) << + SMPS_PEAK_CURRENT_SHIFT) & SMPS_PEAK_CURRENT; + val1 = (MICRO_TO_MILLI(vreg_hpm_min_uA(vreg)) << + SMPS_AVG_CURRENT_SHIFT) & SMPS_AVG_CURRENT; + } + } else if (set_hpm == 0) { + /* Make sure that request currents are at LPM level. */ + if (peak_uA > vreg_lpm_max_uA(vreg)) { + mask0 = SMPS_PEAK_CURRENT; + mask1 = SMPS_AVG_CURRENT; + val0 = (MICRO_TO_MILLI(vreg_lpm_max_uA(vreg)) << + SMPS_PEAK_CURRENT_SHIFT) & SMPS_PEAK_CURRENT; + val1 = (MICRO_TO_MILLI(vreg_lpm_max_uA(vreg)) << + SMPS_AVG_CURRENT_SHIFT) & SMPS_AVG_CURRENT; + } + } + + if (set_pin_control == 1) { + /* Enable pin control and pin function. */ + mask0 |= SMPS_PIN_CTRL | SMPS_PIN_FN; + val0 |= vreg->pdata->pin_ctrl << SMPS_PIN_CTRL_SHIFT + | vreg->pdata->pin_fn << SMPS_PIN_FN_SHIFT; + } else if (set_pin_control == 0) { + /* Clear pin control and pin function*/ + mask0 |= SMPS_PIN_CTRL | SMPS_PIN_FN; + val0 |= RPM_VREG_PIN_CTRL_NONE << SMPS_PIN_CTRL_SHIFT + | RPM_VREG_PIN_FN_NONE << SMPS_PIN_FN_SHIFT; + } + + if (smps_is_enabled(dev)) { + rc = vreg_set(vreg, mask0, val0, mask1, val1, 2); + } else { + /* Regulator is disabled; store but don't send new request. */ + rc = vreg_store(vreg, mask0, val0, mask1, val1); + } + if (rc) + return rc; + +done: + vreg->mode_initialized = mode_initialized; + vreg->optimum = optimum; + vreg->pc_vote = pc_vote; + + return 0; +} + +static unsigned int smps_get_mode(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + + if ((vreg->optimum == REGULATOR_MODE_FAST) && vreg->mode_initialized) + return REGULATOR_MODE_FAST; + else if (vreg->pc_vote) + return REGULATOR_MODE_IDLE; + else if (vreg->optimum == REGULATOR_MODE_STANDBY) + return REGULATOR_MODE_STANDBY; + return REGULATOR_MODE_FAST; +} + +unsigned int smps_get_optimum_mode(struct regulator_dev *dev, int input_uV, + int output_uV, int load_uA) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + + if (MICRO_TO_MILLI(load_uA) > 0) { + vreg->req[0].value &= ~SMPS_PEAK_CURRENT; + vreg->req[0].value |= (MICRO_TO_MILLI(saturate_load(load_uA)) << + SMPS_PEAK_CURRENT_SHIFT) & SMPS_PEAK_CURRENT; + vreg->req[1].value &= ~SMPS_AVG_CURRENT; + vreg->req[1].value |= (MICRO_TO_MILLI(saturate_load(load_uA)) << + SMPS_AVG_CURRENT_SHIFT) & SMPS_AVG_CURRENT; + } else { + /* + * smps_get_optimum_mode is being called before consumers have + * specified their load currents via regulator_set_optimum_mode. + * Return whatever the existing mode is. + */ + return smps_get_mode(dev); + } + + if (load_uA >= vreg->hpm_min_load) + return REGULATOR_MODE_FAST; + return REGULATOR_MODE_STANDBY; +} + +static int ldo_is_enabled(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + return ((vreg->req[0].value & LDO_VOLTAGE) >> LDO_VOLTAGE_SHIFT) != 0; +} + +static int _ldo_set_voltage(struct regulator_dev *dev, int min_uV) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + + return vreg_set(vreg, LDO_VOLTAGE, + MICRO_TO_MILLI(min_uV) << LDO_VOLTAGE_SHIFT, + 0, 0, 2); +} + +static int ldo_set_voltage(struct regulator_dev *dev, int min_uV, int max_uV, + unsigned *selector) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + int rc = 0; + + if (ldo_is_enabled(dev)) + rc = _ldo_set_voltage(dev, min_uV); + if (rc) + return rc; + + /* only save if nonzero (or not disabling) */ + if (min_uV && (!vreg->pdata->sleep_selectable || !ldo_is_enabled(dev))) + vreg->save_uV = min_uV; + + return rc; +} + +static int ldo_get_voltage(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + return vreg->save_uV; +} + +static int ldo_enable(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + int rc = 0; + unsigned mask, val; + + /* enable by setting voltage */ + if (MICRO_TO_MILLI(vreg->save_uV) > 0) { + /* reenable pin control if it is in use */ + if (ldo_get_mode(dev) == REGULATOR_MODE_IDLE) { + mask = LDO_PIN_CTRL | LDO_PIN_FN; + val = vreg->pdata->pin_ctrl << LDO_PIN_CTRL_SHIFT + | vreg->pdata->pin_fn << LDO_PIN_FN_SHIFT; + vreg_store(vreg, mask, val, 0, 0); + } + + rc = _ldo_set_voltage(dev, vreg->save_uV); + } + return rc; +} + +static int ldo_disable(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + unsigned mask, val; + + /* turn off pin control */ + mask = LDO_PIN_CTRL | LDO_PIN_FN; + val = RPM_VREG_PIN_CTRL_NONE << LDO_PIN_CTRL_SHIFT + | RPM_VREG_PIN_FN_NONE << LDO_PIN_FN_SHIFT; + vreg_store(vreg, mask, val, 0, 0); + + /* disable by setting voltage to zero */ + return _ldo_set_voltage(dev, 0); +} + +/* + * Optimum mode programming: + * REGULATOR_MODE_FAST: Go to HPM (highest priority) + * REGULATOR_MODE_STANDBY: Go to pin ctrl mode if there are any pin ctrl + * votes, else go to LPM + * + * Pin ctrl mode voting via regulator set_mode: + * REGULATOR_MODE_IDLE: Go to pin ctrl mode if the optimum mode is LPM, else + * go to HPM + * REGULATOR_MODE_NORMAL: Go to LPM if it is the optimum mode, else go to HPM + * + * Pin ctrl mode takes priority on the RPM when force mode is not set; + * therefore, pin ctrl bits must be cleared if LPM or HPM is being voted for. + */ +static int ldo_set_mode(struct regulator_dev *dev, unsigned int mode) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + unsigned optimum = vreg->optimum; + unsigned pc_vote = vreg->pc_vote; + unsigned mode_initialized = vreg->mode_initialized; + unsigned mask0 = 0, val0 = 0, mask1 = 0, val1 = 0; + int set_hpm = -1, set_pin_control = -1; + int peak_uA; + int rc = 0; + + peak_uA = MILLI_TO_MICRO((vreg->req[0].value & LDO_PEAK_CURRENT) >> + LDO_PEAK_CURRENT_SHIFT); + + switch (mode) { + case REGULATOR_MODE_FAST: + set_hpm = 1; + set_pin_control = 0; + optimum = REGULATOR_MODE_FAST; + mode_initialized = 1; + break; + + case REGULATOR_MODE_STANDBY: + set_hpm = 0; + if (pc_vote) + set_pin_control = 1; + else + set_pin_control = 0; + optimum = REGULATOR_MODE_STANDBY; + mode_initialized = 1; + break; + + case REGULATOR_MODE_IDLE: + if (pc_vote++) + goto done; /* already taken care of */ + + if (mode_initialized && optimum == REGULATOR_MODE_FAST) { + set_hpm = 1; + set_pin_control = 0; + } else { + set_pin_control = 1; + } + break; + + case REGULATOR_MODE_NORMAL: + if (pc_vote && --pc_vote) + goto done; /* already taken care of */ + + if (optimum == REGULATOR_MODE_STANDBY) + set_hpm = 0; + else + set_hpm = 1; + set_pin_control = 0; + break; + + default: + return -EINVAL; + } + + if (set_hpm == 1) { + /* Make sure that request currents are at HPM level. */ + if (peak_uA < vreg_hpm_min_uA(vreg)) { + mask0 = LDO_PEAK_CURRENT; + mask1 = LDO_AVG_CURRENT; + val0 = (MICRO_TO_MILLI(vreg_hpm_min_uA(vreg)) << + LDO_PEAK_CURRENT_SHIFT) & LDO_PEAK_CURRENT; + val1 = (MICRO_TO_MILLI(vreg_hpm_min_uA(vreg)) << + LDO_AVG_CURRENT_SHIFT) & LDO_AVG_CURRENT; + } + } else if (set_hpm == 0) { + /* Make sure that request currents are at LPM level. */ + if (peak_uA > vreg_lpm_max_uA(vreg)) { + mask0 = LDO_PEAK_CURRENT; + mask1 = LDO_AVG_CURRENT; + val0 = (MICRO_TO_MILLI(vreg_lpm_max_uA(vreg)) << + LDO_PEAK_CURRENT_SHIFT) & LDO_PEAK_CURRENT; + val1 = (MICRO_TO_MILLI(vreg_lpm_max_uA(vreg)) << + LDO_AVG_CURRENT_SHIFT) & LDO_AVG_CURRENT; + } + } + + if (set_pin_control == 1) { + /* Enable pin control and pin function. */ + mask0 |= LDO_PIN_CTRL | LDO_PIN_FN; + val0 |= vreg->pdata->pin_ctrl << LDO_PIN_CTRL_SHIFT + | vreg->pdata->pin_fn << LDO_PIN_FN_SHIFT; + } else if (set_pin_control == 0) { + /* Clear pin control and pin function*/ + mask0 |= LDO_PIN_CTRL | LDO_PIN_FN; + val0 |= RPM_VREG_PIN_CTRL_NONE << LDO_PIN_CTRL_SHIFT + | RPM_VREG_PIN_FN_NONE << LDO_PIN_FN_SHIFT; + } + + if (ldo_is_enabled(dev)) { + rc = vreg_set(vreg, mask0, val0, mask1, val1, 2); + } else { + /* Regulator is disabled; store but don't send new request. */ + rc = vreg_store(vreg, mask0, val0, mask1, val1); + } + if (rc) + return rc; + +done: + vreg->mode_initialized = mode_initialized; + vreg->optimum = optimum; + vreg->pc_vote = pc_vote; + + return 0; +} + +static unsigned int ldo_get_mode(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + + if ((vreg->optimum == REGULATOR_MODE_FAST) && vreg->mode_initialized) + return REGULATOR_MODE_FAST; + else if (vreg->pc_vote) + return REGULATOR_MODE_IDLE; + else if (vreg->optimum == REGULATOR_MODE_STANDBY) + return REGULATOR_MODE_STANDBY; + return REGULATOR_MODE_FAST; +} + +unsigned int ldo_get_optimum_mode(struct regulator_dev *dev, int input_uV, + int output_uV, int load_uA) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + + if (MICRO_TO_MILLI(load_uA) > 0) { + vreg->req[0].value &= ~LDO_PEAK_CURRENT; + vreg->req[0].value |= (MICRO_TO_MILLI(saturate_load(load_uA)) << + LDO_PEAK_CURRENT_SHIFT) & LDO_PEAK_CURRENT; + vreg->req[1].value &= ~LDO_AVG_CURRENT; + vreg->req[1].value |= (MICRO_TO_MILLI(saturate_load(load_uA)) << + LDO_AVG_CURRENT_SHIFT) & LDO_AVG_CURRENT; + } else { + /* + * ldo_get_optimum_mode is being called before consumers have + * specified their load currents via regulator_set_optimum_mode. + * Return whatever the existing mode is. + */ + return ldo_get_mode(dev); + } + + if (load_uA >= vreg->hpm_min_load) + return REGULATOR_MODE_FAST; + return REGULATOR_MODE_STANDBY; +} + +static int switch_enable(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + unsigned mask = 0, val = 0; + + /* reenable pin control if it is in use */ + if (switch_get_mode(dev) == REGULATOR_MODE_IDLE) { + mask = SWITCH_PIN_CTRL | SWITCH_PIN_FN; + val = vreg->pdata->pin_ctrl << SWITCH_PIN_CTRL_SHIFT + | vreg->pdata->pin_fn << SWITCH_PIN_FN_SHIFT; + } + + return vreg_set(rdev_get_drvdata(dev), SWITCH_STATE | mask, + (RPM_VREG_STATE_ON << SWITCH_STATE_SHIFT) | val, 0, 0, 1); +} + +static int switch_disable(struct regulator_dev *dev) +{ + unsigned mask, val; + + /* turn off pin control */ + mask = SWITCH_PIN_CTRL | SWITCH_PIN_FN; + val = RPM_VREG_PIN_CTRL_NONE << SWITCH_PIN_CTRL_SHIFT + | RPM_VREG_PIN_FN_NONE << SWITCH_PIN_FN_SHIFT; + + return vreg_set(rdev_get_drvdata(dev), SWITCH_STATE | mask, + (RPM_VREG_STATE_OFF << SWITCH_STATE_SHIFT) | val, 0, 0, 1); +} + +static int switch_is_enabled(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + enum rpm_vreg_state state; + + state = (vreg->req[0].value & SWITCH_STATE) >> SWITCH_STATE_SHIFT; + + return state == RPM_VREG_STATE_ON; +} + +/* + * Pin ctrl mode voting via regulator set_mode: + * REGULATOR_MODE_IDLE: Go to pin ctrl mode if the optimum mode is LPM, else + * go to HPM + * REGULATOR_MODE_NORMAL: Go to LPM if it is the optimum mode, else go to HPM + */ +static int switch_set_mode(struct regulator_dev *dev, unsigned int mode) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + unsigned pc_vote = vreg->pc_vote; + unsigned mask, val; + int rc; + + switch (mode) { + case REGULATOR_MODE_IDLE: + if (pc_vote++) + goto done; /* already taken care of */ + + mask = SWITCH_PIN_CTRL | SWITCH_PIN_FN; + val = vreg->pdata->pin_ctrl << SWITCH_PIN_CTRL_SHIFT + | vreg->pdata->pin_fn << SWITCH_PIN_FN_SHIFT; + break; + + case REGULATOR_MODE_NORMAL: + if (--pc_vote) + goto done; /* already taken care of */ + + mask = SWITCH_PIN_CTRL | SWITCH_PIN_FN; + val = RPM_VREG_PIN_CTRL_NONE << SWITCH_PIN_CTRL_SHIFT + | RPM_VREG_PIN_FN_NONE << SWITCH_PIN_FN_SHIFT; + break; + + default: + return -EINVAL; + } + + if (switch_is_enabled(dev)) { + rc = vreg_set(vreg, mask, val, 0, 0, 2); + } else { + /* Regulator is disabled; store but don't send new request. */ + rc = vreg_store(vreg, mask, val, 0, 0); + } + if (rc) + return rc; + +done: + vreg->pc_vote = pc_vote; + return 0; +} + +static unsigned int switch_get_mode(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + + if (vreg->pc_vote) + return REGULATOR_MODE_IDLE; + return REGULATOR_MODE_NORMAL; +} + +static int ncp_enable(struct regulator_dev *dev) +{ + return vreg_set(rdev_get_drvdata(dev), NCP_STATE, + RPM_VREG_STATE_ON << NCP_STATE_SHIFT, 0, 0, 2); +} + +static int ncp_disable(struct regulator_dev *dev) +{ + return vreg_set(rdev_get_drvdata(dev), NCP_STATE, + RPM_VREG_STATE_OFF << NCP_STATE_SHIFT, 0, 0, 2); +} + +static int ncp_is_enabled(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + enum rpm_vreg_state state; + + state = (vreg->req[0].value & NCP_STATE) >> NCP_STATE_SHIFT; + + return state == RPM_VREG_STATE_ON; +} + +static int ncp_set_voltage(struct regulator_dev *dev, int min_uV, int max_uV, + unsigned *selector) +{ + return vreg_set(rdev_get_drvdata(dev), NCP_VOLTAGE, + MICRO_TO_MILLI(min_uV) << NCP_VOLTAGE_SHIFT, 0, 0, 2); +} + +static int ncp_get_voltage(struct regulator_dev *dev) +{ + struct vreg *vreg = rdev_get_drvdata(dev); + + return MILLI_TO_MICRO((vreg->req[0].value & NCP_VOLTAGE) >> + NCP_VOLTAGE_SHIFT); +} + +static struct regulator_ops ldo_ops = { + .enable = ldo_enable, + .disable = ldo_disable, + .is_enabled = ldo_is_enabled, + .set_voltage = ldo_set_voltage, + .get_voltage = ldo_get_voltage, + .set_mode = ldo_set_mode, + .get_optimum_mode = ldo_get_optimum_mode, + .get_mode = ldo_get_mode, +}; + +static struct regulator_ops smps_ops = { + .enable = smps_enable, + .disable = smps_disable, + .is_enabled = smps_is_enabled, + .set_voltage = smps_set_voltage, + .get_voltage = smps_get_voltage, + .set_mode = smps_set_mode, + .get_optimum_mode = smps_get_optimum_mode, + .get_mode = smps_get_mode, +}; + +static struct regulator_ops switch_ops = { + .enable = switch_enable, + .disable = switch_disable, + .is_enabled = switch_is_enabled, + .set_mode = switch_set_mode, + .get_mode = switch_get_mode, +}; + +static struct regulator_ops ncp_ops = { + .enable = ncp_enable, + .disable = ncp_disable, + .is_enabled = ncp_is_enabled, + .set_voltage = ncp_set_voltage, + .get_voltage = ncp_get_voltage, +}; + +#define DESC(_id, _name, _ops) \ + [_id] = { \ + .id = _id, \ + .name = _name, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static struct regulator_desc vreg_descrip[RPM_VREG_ID_MAX] = { + DESC(RPM_VREG_ID_PM8058_L0, "8058_l0", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L1, "8058_l1", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L2, "8058_l2", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L3, "8058_l3", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L4, "8058_l4", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L5, "8058_l5", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L6, "8058_l6", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L7, "8058_l7", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L8, "8058_l8", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L9, "8058_l9", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L10, "8058_l10", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L11, "8058_l11", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L12, "8058_l12", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L13, "8058_l13", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L14, "8058_l14", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L15, "8058_l15", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L16, "8058_l16", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L17, "8058_l17", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L18, "8058_l18", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L19, "8058_l19", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L20, "8058_l20", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L21, "8058_l21", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L22, "8058_l22", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L23, "8058_l23", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L24, "8058_l24", &ldo_ops), + DESC(RPM_VREG_ID_PM8058_L25, "8058_l25", &ldo_ops), + + DESC(RPM_VREG_ID_PM8058_S0, "8058_s0", &smps_ops), + DESC(RPM_VREG_ID_PM8058_S1, "8058_s1", &smps_ops), + DESC(RPM_VREG_ID_PM8058_S2, "8058_s2", &smps_ops), + DESC(RPM_VREG_ID_PM8058_S3, "8058_s3", &smps_ops), + DESC(RPM_VREG_ID_PM8058_S4, "8058_s4", &smps_ops), + + DESC(RPM_VREG_ID_PM8058_LVS0, "8058_lvs0", &switch_ops), + DESC(RPM_VREG_ID_PM8058_LVS1, "8058_lvs1", &switch_ops), + + DESC(RPM_VREG_ID_PM8058_NCP, "8058_ncp", &ncp_ops), + + DESC(RPM_VREG_ID_PM8901_L0, "8901_l0", &ldo_ops), + DESC(RPM_VREG_ID_PM8901_L1, "8901_l1", &ldo_ops), + DESC(RPM_VREG_ID_PM8901_L2, "8901_l2", &ldo_ops), + DESC(RPM_VREG_ID_PM8901_L3, "8901_l3", &ldo_ops), + DESC(RPM_VREG_ID_PM8901_L4, "8901_l4", &ldo_ops), + DESC(RPM_VREG_ID_PM8901_L5, "8901_l5", &ldo_ops), + DESC(RPM_VREG_ID_PM8901_L6, "8901_l6", &ldo_ops), + + DESC(RPM_VREG_ID_PM8901_S0, "8901_s0", &smps_ops), + DESC(RPM_VREG_ID_PM8901_S1, "8901_s1", &smps_ops), + DESC(RPM_VREG_ID_PM8901_S2, "8901_s2", &smps_ops), + DESC(RPM_VREG_ID_PM8901_S3, "8901_s3", &smps_ops), + DESC(RPM_VREG_ID_PM8901_S4, "8901_s4", &smps_ops), + + DESC(RPM_VREG_ID_PM8901_LVS0, "8901_lvs0", &switch_ops), + DESC(RPM_VREG_ID_PM8901_LVS1, "8901_lvs1", &switch_ops), + DESC(RPM_VREG_ID_PM8901_LVS2, "8901_lvs2", &switch_ops), + DESC(RPM_VREG_ID_PM8901_LVS3, "8901_lvs3", &switch_ops), + + DESC(RPM_VREG_ID_PM8901_MVS0, "8901_mvs0", &switch_ops), +}; + +static void ldo_init(struct vreg *vreg) +{ + enum rpm_vreg_pin_fn pf = RPM_VREG_PIN_FN_NONE; + + /* Allow pf=sleep_b to be specified by platform data. */ + if (vreg->pdata->pin_fn == RPM_VREG_PIN_FN_SLEEP_B) + pf = RPM_VREG_PIN_FN_SLEEP_B; + + vreg->req[0].value = + MICRO_TO_MILLI(saturate_load(vreg->pdata->peak_uA)) << + LDO_PEAK_CURRENT_SHIFT | + vreg->pdata->mode << LDO_MODE_SHIFT | pf << LDO_PIN_FN_SHIFT | + RPM_VREG_PIN_CTRL_NONE << LDO_PIN_CTRL_SHIFT; + + vreg->req[1].value = + vreg->pdata->pull_down_enable << LDO_PULL_DOWN_ENABLE_SHIFT | + MICRO_TO_MILLI(saturate_load(vreg->pdata->avg_uA)) << + LDO_AVG_CURRENT_SHIFT; +} + +static void smps_init(struct vreg *vreg) +{ + enum rpm_vreg_pin_fn pf = RPM_VREG_PIN_FN_NONE; + + /* Allow pf=sleep_b to be specified by platform data. */ + if (vreg->pdata->pin_fn == RPM_VREG_PIN_FN_SLEEP_B) + pf = RPM_VREG_PIN_FN_SLEEP_B; + + vreg->req[0].value = + MICRO_TO_MILLI(saturate_load(vreg->pdata->peak_uA)) << + SMPS_PEAK_CURRENT_SHIFT | + vreg->pdata->mode << SMPS_MODE_SHIFT | pf << SMPS_PIN_FN_SHIFT | + RPM_VREG_PIN_CTRL_NONE << SMPS_PIN_CTRL_SHIFT; + + + vreg->req[1].value = + vreg->pdata->pull_down_enable << SMPS_PULL_DOWN_ENABLE_SHIFT | + MICRO_TO_MILLI(saturate_load(vreg->pdata->avg_uA)) << + SMPS_AVG_CURRENT_SHIFT | + vreg->pdata->freq << SMPS_FREQ_SHIFT | + 0 << SMPS_CLK_SRC_SHIFT; +} + +static void ncp_init(struct vreg *vreg) +{ + vreg->req[0].value = vreg->pdata->state << NCP_STATE_SHIFT; +} + +static void switch_init(struct vreg *vreg) +{ + enum rpm_vreg_pin_fn pf = RPM_VREG_PIN_FN_NONE; + + /* Allow pf=sleep_b to be specified by platform data. */ + if (vreg->pdata->pin_fn == RPM_VREG_PIN_FN_SLEEP_B) + pf = RPM_VREG_PIN_FN_SLEEP_B; + + vreg->req[0].value = + vreg->pdata->state << SWITCH_STATE_SHIFT | + vreg->pdata->pull_down_enable << + SWITCH_PULL_DOWN_ENABLE_SHIFT | + pf << SWITCH_PIN_FN_SHIFT | + RPM_VREG_PIN_CTRL_NONE << SWITCH_PIN_CTRL_SHIFT; +} + +static int vreg_init(enum rpm_vreg_id id, struct vreg *vreg) +{ + vreg->save_uV = vreg->pdata->default_uV; + + if (vreg->pdata->peak_uA >= vreg->hpm_min_load) + vreg->optimum = REGULATOR_MODE_FAST; + else + vreg->optimum = REGULATOR_MODE_STANDBY; + + vreg->mode_initialized = 0; + + if (IS_LDO(id)) + ldo_init(vreg); + else if (IS_SMPS(id)) + smps_init(vreg); + else if (IS_NCP(id)) + ncp_init(vreg); + else if (IS_SWITCH(id)) + switch_init(vreg); + else + return -EINVAL; + + return 0; +} + +static int __devinit rpm_vreg_probe(struct platform_device *pdev) +{ + struct regulator_desc *rdesc; + struct regulator_dev *rdev; + struct vreg *vreg; + int rc; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id < 0 || pdev->id >= RPM_VREG_ID_MAX) + return -ENODEV; + + vreg = &vregs[pdev->id]; + vreg->pdata = pdev->dev.platform_data; + vreg->id = pdev->id; + rdesc = &vreg_descrip[pdev->id]; + + rc = vreg_init(pdev->id, vreg); + if (rc) { + pr_err("%s: vreg_init failed, rc=%d\n", __func__, rc); + return rc; + } + + /* Disallow idle and normal modes if pin control isn't set. */ + if ((vreg->pdata->pin_ctrl == RPM_VREG_PIN_CTRL_NONE) + && ((vreg->pdata->pin_fn == RPM_VREG_PIN_FN_ENABLE) + || (vreg->pdata->pin_fn == RPM_VREG_PIN_FN_MODE))) + vreg->pdata->init_data.constraints.valid_modes_mask + &= ~(REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE); + + rdev = regulator_register(rdesc, &pdev->dev, + &vreg->pdata->init_data, vreg); + if (IS_ERR(rdev)) { + rc = PTR_ERR(rdev); + pr_err("%s: id=%d, rc=%d\n", __func__, + pdev->id, rc); + return rc; + } + + platform_set_drvdata(pdev, rdev); + + return rc; +} + +static int __devexit rpm_vreg_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + regulator_unregister(rdev); + + return 0; +} + +static struct platform_driver rpm_vreg_driver = { + .probe = rpm_vreg_probe, + .remove = __devexit_p(rpm_vreg_remove), + .driver = { + .name = "rpm-regulator", + .owner = THIS_MODULE, + }, +}; + +static int __init rpm_vreg_init(void) +{ + return platform_driver_register(&rpm_vreg_driver); +} + +static void __exit rpm_vreg_exit(void) +{ + platform_driver_unregister(&rpm_vreg_driver); +} + +postcore_initcall(rpm_vreg_init); +module_exit(rpm_vreg_exit); + +#define VREG_ID_IS_8058_S0_OR_S1(id) \ + ((id == RPM_VREG_ID_PM8058_S0) || (id == RPM_VREG_ID_PM8058_S1)) + +static void print_rpm_request(struct vreg *vreg, int set) +{ + int v, ip, fm, pc, pf, pd, ia, freq, clk, state; + + /* Suppress 8058_s0 and 8058_s1 printing. */ + if ((msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_IGNORE_8058_S0_S1) + && VREG_ID_IS_8058_S0_OR_S1(vreg->id)) + return; + + if (IS_LDO(vreg->id)) { + v = (vreg->req[0].value & LDO_VOLTAGE) >> LDO_VOLTAGE_SHIFT; + ip = (vreg->req[0].value & LDO_PEAK_CURRENT) + >> LDO_PEAK_CURRENT_SHIFT; + fm = (vreg->req[0].value & LDO_MODE) >> LDO_MODE_SHIFT; + pc = (vreg->req[0].value & LDO_PIN_CTRL) >> LDO_PIN_CTRL_SHIFT; + pf = (vreg->req[0].value & LDO_PIN_FN) >> LDO_PIN_FN_SHIFT; + pd = (vreg->req[1].value & LDO_PULL_DOWN_ENABLE) + >> LDO_PULL_DOWN_ENABLE_SHIFT; + ia = (vreg->req[1].value & LDO_AVG_CURRENT) + >> LDO_AVG_CURRENT_SHIFT; + + pr_info("rpm-regulator: %s %-9s: s=%c, v=%4d mV, ip=%4d " + "mA, fm=%s (%d), pc=%s%s%s%s%s (%d), pf=%s (%d), pd=%s " + "(%d), ia=%4d mA; req[0]={%d, 0x%08X}, " + "req[1]={%d, 0x%08X}\n", + (set == MSM_RPM_CTX_SET_0 ? "sending " : "buffered"), + vreg_descrip[vreg->id].name, + (set == MSM_RPM_CTX_SET_0 ? 'A' : 'S'), v, ip, + (fm == RPM_VREG_MODE_NONE ? "none" : + (fm == RPM_VREG_MODE_LPM ? "LPM" : + (fm == RPM_VREG_MODE_HPM ? "HPM" : ""))), + fm, + (pc & RPM_VREG_PIN_CTRL_A0 ? " A0" : ""), + (pc & RPM_VREG_PIN_CTRL_A1 ? " A1" : ""), + (pc & RPM_VREG_PIN_CTRL_D0 ? " D0" : ""), + (pc & RPM_VREG_PIN_CTRL_D1 ? " D1" : ""), + (pc == RPM_VREG_PIN_CTRL_NONE ? " none" : ""), pc, + (pf == RPM_VREG_PIN_FN_NONE ? + "none" : + (pf == RPM_VREG_PIN_FN_ENABLE ? + "on/off" : + (pf == RPM_VREG_PIN_FN_MODE ? + "HPM/LPM" : + (pf == RPM_VREG_PIN_FN_SLEEP_B ? + "sleep_b" : "")))), + pf, (pd == 1 ? "Y" : "N"), pd, ia, + vreg->req[0].id, vreg->req[0].value, + vreg->req[1].id, vreg->req[1].value); + + } else if (IS_SMPS(vreg->id)) { + v = (vreg->req[0].value & SMPS_VOLTAGE) >> SMPS_VOLTAGE_SHIFT; + ip = (vreg->req[0].value & SMPS_PEAK_CURRENT) + >> SMPS_PEAK_CURRENT_SHIFT; + fm = (vreg->req[0].value & SMPS_MODE) >> SMPS_MODE_SHIFT; + pc = (vreg->req[0].value & SMPS_PIN_CTRL) + >> SMPS_PIN_CTRL_SHIFT; + pf = (vreg->req[0].value & SMPS_PIN_FN) >> SMPS_PIN_FN_SHIFT; + pd = (vreg->req[1].value & SMPS_PULL_DOWN_ENABLE) + >> SMPS_PULL_DOWN_ENABLE_SHIFT; + ia = (vreg->req[1].value & SMPS_AVG_CURRENT) + >> SMPS_AVG_CURRENT_SHIFT; + freq = (vreg->req[1].value & SMPS_FREQ) >> SMPS_FREQ_SHIFT; + clk = (vreg->req[1].value & SMPS_CLK_SRC) >> SMPS_CLK_SRC_SHIFT; + + pr_info("rpm-regulator: %s %-9s: s=%c, v=%4d mV, ip=%4d " + "mA, fm=%s (%d), pc=%s%s%s%s%s (%d), pf=%s (%d), pd=%s " + "(%d), ia=%4d mA, freq=%2d, clk=%d; " + "req[0]={%d, 0x%08X}, req[1]={%d, 0x%08X}\n", + (set == MSM_RPM_CTX_SET_0 ? "sending " : "buffered"), + vreg_descrip[vreg->id].name, + (set == MSM_RPM_CTX_SET_0 ? 'A' : 'S'), v, ip, + (fm == RPM_VREG_MODE_NONE ? "none" : + (fm == RPM_VREG_MODE_LPM ? "LPM" : + (fm == RPM_VREG_MODE_HPM ? "HPM" : ""))), + fm, + (pc & RPM_VREG_PIN_CTRL_A0 ? " A0" : ""), + (pc & RPM_VREG_PIN_CTRL_A1 ? " A1" : ""), + (pc & RPM_VREG_PIN_CTRL_D0 ? " D0" : ""), + (pc & RPM_VREG_PIN_CTRL_D1 ? " D1" : ""), + (pc == RPM_VREG_PIN_CTRL_NONE ? " none" : ""), pc, + (pf == RPM_VREG_PIN_FN_NONE ? + "none" : + (pf == RPM_VREG_PIN_FN_ENABLE ? + "on/off" : + (pf == RPM_VREG_PIN_FN_MODE ? + "HPM/LPM" : + (pf == RPM_VREG_PIN_FN_SLEEP_B ? + "sleep_b" : "")))), + pf, (pd == 1 ? "Y" : "N"), pd, ia, freq, clk, + vreg->req[0].id, vreg->req[0].value, + vreg->req[1].id, vreg->req[1].value); + + } else if (IS_SWITCH(vreg->id)) { + state = (vreg->req[0].value & SWITCH_STATE) + >> SWITCH_STATE_SHIFT; + pd = (vreg->req[0].value & SWITCH_PULL_DOWN_ENABLE) + >> SWITCH_PULL_DOWN_ENABLE_SHIFT; + pc = (vreg->req[0].value & SWITCH_PIN_CTRL) + >> SWITCH_PIN_CTRL_SHIFT; + pf = (vreg->req[0].value & SWITCH_PIN_FN) + >> SWITCH_PIN_FN_SHIFT; + + pr_info("rpm-regulator: %s %-9s: s=%c, state=%s (%d), " + "pd=%s (%d), pc =%s%s%s%s%s (%d), pf=%s (%d); " + "req[0]={%d, 0x%08X}\n", + (set == MSM_RPM_CTX_SET_0 ? "sending " : "buffered"), + vreg_descrip[vreg->id].name, + (set == MSM_RPM_CTX_SET_0 ? 'A' : 'S'), + (state == 1 ? "on" : "off"), state, + (pd == 1 ? "Y" : "N"), pd, + (pc & RPM_VREG_PIN_CTRL_A0 ? " A0" : ""), + (pc & RPM_VREG_PIN_CTRL_A1 ? " A1" : ""), + (pc & RPM_VREG_PIN_CTRL_D0 ? " D0" : ""), + (pc & RPM_VREG_PIN_CTRL_D1 ? " D1" : ""), + (pc == RPM_VREG_PIN_CTRL_NONE ? " none" : ""), pc, + (pf == RPM_VREG_PIN_FN_NONE ? + "none" : + (pf == RPM_VREG_PIN_FN_ENABLE ? + "on/off" : + (pf == RPM_VREG_PIN_FN_MODE ? + "HPM/LPM" : + (pf == RPM_VREG_PIN_FN_SLEEP_B ? + "sleep_b" : "")))), + pf, vreg->req[0].id, vreg->req[0].value); + + } else if (IS_NCP(vreg->id)) { + v = (vreg->req[0].value & NCP_VOLTAGE) >> NCP_VOLTAGE_SHIFT; + state = (vreg->req[0].value & NCP_STATE) >> NCP_STATE_SHIFT; + + pr_info("rpm-regulator: %s %-9s: s=%c, v=-%4d mV, " + "state=%s (%d); req[0]={%d, 0x%08X}\n", + (set == MSM_RPM_CTX_SET_0 ? "sending " : "buffered"), + vreg_descrip[vreg->id].name, + (set == MSM_RPM_CTX_SET_0 ? 'A' : 'S'), + v, (state == 1 ? "on" : "off"), state, + vreg->req[0].id, vreg->req[0].value); + } +} + +static void print_rpm_vote(struct vreg *vreg, enum rpm_vreg_voter voter, + int set, int voter_mV, int aggregate_mV) +{ + /* Suppress 8058_s0 and 8058_s1 printing. */ + if ((msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_IGNORE_8058_S0_S1) + && VREG_ID_IS_8058_S0_OR_S1(vreg->id)) + return; + + pr_info("rpm-regulator: vote received %-9s: voter=%d, set=%c, " + "v_voter=%4d mV, v_aggregate=%4d mV\n", + vreg_descrip[vreg->id].name, voter, (set == 0 ? 'A' : 'S'), + voter_mV, aggregate_mV); +} + +static void print_rpm_duplicate(struct vreg *vreg, int set, int cnt) +{ + /* Suppress 8058_s0 and 8058_s1 printing. */ + if ((msm_rpm_vreg_debug_mask & MSM_RPM_VREG_DEBUG_IGNORE_8058_S0_S1) + && VREG_ID_IS_8058_S0_OR_S1(vreg->id)) + return; + + if (cnt == 2) + pr_info("rpm-regulator: ignored duplicate request %-9s: set=%c;" + " req[0]={%d, 0x%08X}, req[1]={%d, 0x%08X}\n", + vreg_descrip[vreg->id].name, (set == 0 ? 'A' : 'S'), + vreg->req[0].id, vreg->req[0].value, + vreg->req[1].id, vreg->req[1].value); + else if (cnt == 1) + pr_info("rpm-regulator: ignored duplicate request %-9s: set=%c;" + " req[0]={%d, 0x%08X}\n", + vreg_descrip[vreg->id].name, (set == 0 ? 'A' : 'S'), + vreg->req[0].id, vreg->req[0].value); +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("rpm regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:rpm-regulator"); diff --git a/arch/arm/mach-msm/rpm.c b/arch/arm/mach-msm/rpm.c new file mode 100644 index 00000000000..a2b0ebc878a --- /dev/null +++ b/arch/arm/mach-msm/rpm.c @@ -0,0 +1,831 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/****************************************************************************** + * Data type and structure definitions + *****************************************************************************/ + +struct msm_rpm_request { + struct msm_rpm_iv_pair *req; + int count; + uint32_t *ctx_mask_ack; + uint32_t *sel_masks_ack; + struct completion *done; +}; + +struct msm_rpm_notif_config { + struct msm_rpm_iv_pair iv[MSM_RPM_SEL_MASK_SIZE * 2]; +}; + +#define configured_iv(notif_cfg) ((notif_cfg)->iv) +#define registered_iv(notif_cfg) ((notif_cfg)->iv + MSM_RPM_SEL_MASK_SIZE) + +static struct msm_rpm_platform_data *msm_rpm_platform; +static uint32_t msm_rpm_map[MSM_RPM_ID_LAST + 1]; + +static DEFINE_MUTEX(msm_rpm_mutex); +static DEFINE_SPINLOCK(msm_rpm_lock); +static DEFINE_SPINLOCK(msm_rpm_irq_lock); + +static struct msm_rpm_request *msm_rpm_request; +static struct msm_rpm_request msm_rpm_request_irq_mode; +static struct msm_rpm_request msm_rpm_request_poll_mode; + +static LIST_HEAD(msm_rpm_notifications); +static struct msm_rpm_notif_config msm_rpm_notif_cfgs[MSM_RPM_CTX_SET_COUNT]; +static bool msm_rpm_init_notif_done; + +/****************************************************************************** + * Internal functions + *****************************************************************************/ + +static inline uint32_t msm_rpm_read(unsigned int page, unsigned int reg) +{ + return __raw_readl(msm_rpm_platform->reg_base_addrs[page] + reg * 4); +} + +static inline void msm_rpm_write( + unsigned int page, unsigned int reg, uint32_t value) +{ + __raw_writel(value, msm_rpm_platform->reg_base_addrs[page] + reg * 4); +} + +static inline void msm_rpm_read_contiguous( + unsigned int page, unsigned int reg, uint32_t *values, int count) +{ + int i; + + for (i = 0; i < count; i++) + values[i] = msm_rpm_read(page, reg + i); +} + +static inline void msm_rpm_write_contiguous( + unsigned int page, unsigned int reg, uint32_t *values, int count) +{ + int i; + + for (i = 0; i < count; i++) + msm_rpm_write(page, reg + i, values[i]); +} + +static inline void msm_rpm_write_contiguous_zeros( + unsigned int page, unsigned int reg, int count) +{ + int i; + + for (i = 0; i < count; i++) + msm_rpm_write(page, reg + i, 0); +} + +static inline uint32_t msm_rpm_map_id_to_sel(uint32_t id) +{ + return (id > MSM_RPM_ID_LAST) ? MSM_RPM_SEL_LAST + 1 : msm_rpm_map[id]; +} + +/* + * Note: the function does not clear the masks before filling them. + * + * Return value: + * 0: success + * -EINVAL: invalid id in array + */ +static int msm_rpm_fill_sel_masks( + uint32_t *sel_masks, struct msm_rpm_iv_pair *req, int count) +{ + uint32_t sel; + int i; + + for (i = 0; i < count; i++) { + sel = msm_rpm_map_id_to_sel(req[i].id); + + if (sel > MSM_RPM_SEL_LAST) + return -EINVAL; + + sel_masks[msm_rpm_get_sel_mask_reg(sel)] |= + msm_rpm_get_sel_mask(sel); + } + + return 0; +} + +static inline void msm_rpm_send_req_interrupt(void) +{ + __raw_writel(msm_rpm_platform->msm_apps_ipc_rpm_val, + msm_rpm_platform->msm_apps_ipc_rpm_reg); +} + +/* + * Note: assumes caller has acquired . + * + * Return value: + * 0: request acknowledgement + * 1: notification + * 2: spurious interrupt + */ +static int msm_rpm_process_ack_interrupt(void) +{ + uint32_t ctx_mask_ack; + uint32_t sel_masks_ack[MSM_RPM_SEL_MASK_SIZE]; + + ctx_mask_ack = msm_rpm_read(MSM_RPM_PAGE_CTRL, MSM_RPM_CTRL_ACK_CTX_0); + msm_rpm_read_contiguous(MSM_RPM_PAGE_CTRL, + MSM_RPM_CTRL_ACK_SEL_0, sel_masks_ack, MSM_RPM_SEL_MASK_SIZE); + + if (ctx_mask_ack & msm_rpm_get_ctx_mask(MSM_RPM_CTX_NOTIFICATION)) { + struct msm_rpm_notification *n; + int i; + + list_for_each_entry(n, &msm_rpm_notifications, list) + for (i = 0; i < MSM_RPM_SEL_MASK_SIZE; i++) + if (sel_masks_ack[i] & n->sel_masks[i]) { + up(&n->sem); + break; + } + + msm_rpm_write_contiguous_zeros(MSM_RPM_PAGE_CTRL, + MSM_RPM_CTRL_ACK_SEL_0, MSM_RPM_SEL_MASK_SIZE); + msm_rpm_write(MSM_RPM_PAGE_CTRL, MSM_RPM_CTRL_ACK_CTX_0, 0); + /* Ensure the write is complete before return */ + mb(); + + return 1; + } + + if (msm_rpm_request) { + int i; + + *(msm_rpm_request->ctx_mask_ack) = ctx_mask_ack; + memcpy(msm_rpm_request->sel_masks_ack, sel_masks_ack, + sizeof(sel_masks_ack)); + + for (i = 0; i < msm_rpm_request->count; i++) + msm_rpm_request->req[i].value = + msm_rpm_read(MSM_RPM_PAGE_ACK, + msm_rpm_request->req[i].id); + + msm_rpm_write_contiguous_zeros(MSM_RPM_PAGE_CTRL, + MSM_RPM_CTRL_ACK_SEL_0, MSM_RPM_SEL_MASK_SIZE); + msm_rpm_write(MSM_RPM_PAGE_CTRL, MSM_RPM_CTRL_ACK_CTX_0, 0); + /* Ensure the write is complete before return */ + mb(); + + if (msm_rpm_request->done) + complete_all(msm_rpm_request->done); + + msm_rpm_request = NULL; + return 0; + } + + return 2; +} + +static irqreturn_t msm_rpm_ack_interrupt(int irq, void *dev_id) +{ + unsigned long flags; + int rc; + + if (dev_id != &msm_rpm_ack_interrupt) + return IRQ_NONE; + + spin_lock_irqsave(&msm_rpm_irq_lock, flags); + rc = msm_rpm_process_ack_interrupt(); + spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); + + return IRQ_HANDLED; +} + +/* + * Note: assumes caller has acquired . + */ +static void msm_rpm_busy_wait_for_request_completion( + bool allow_async_completion) +{ + int rc; + + do { + while (!gic_is_spi_pending(msm_rpm_platform->irq_ack) && + msm_rpm_request) { + if (allow_async_completion) + spin_unlock(&msm_rpm_irq_lock); + udelay(1); + if (allow_async_completion) + spin_lock(&msm_rpm_irq_lock); + } + + if (!msm_rpm_request) + break; + + rc = msm_rpm_process_ack_interrupt(); + gic_clear_spi_pending(msm_rpm_platform->irq_ack); + } while (rc); +} + +/* Upon return, the array will contain values from the ack page. + * + * Note: assumes caller has acquired . + * + * Return value: + * 0: success + * -ENOSPC: request rejected + */ +static int msm_rpm_set_exclusive(int ctx, + uint32_t *sel_masks, struct msm_rpm_iv_pair *req, int count) +{ + DECLARE_COMPLETION_ONSTACK(ack); + unsigned long flags; + uint32_t ctx_mask = msm_rpm_get_ctx_mask(ctx); + uint32_t ctx_mask_ack; + uint32_t sel_masks_ack[MSM_RPM_SEL_MASK_SIZE]; + int i; + + msm_rpm_request_irq_mode.req = req; + msm_rpm_request_irq_mode.count = count; + msm_rpm_request_irq_mode.ctx_mask_ack = &ctx_mask_ack; + msm_rpm_request_irq_mode.sel_masks_ack = sel_masks_ack; + msm_rpm_request_irq_mode.done = &ack; + + spin_lock_irqsave(&msm_rpm_lock, flags); + spin_lock(&msm_rpm_irq_lock); + + BUG_ON(msm_rpm_request); + msm_rpm_request = &msm_rpm_request_irq_mode; + + for (i = 0; i < count; i++) { + BUG_ON(req[i].id > MSM_RPM_ID_LAST); + msm_rpm_write(MSM_RPM_PAGE_REQ, req[i].id, req[i].value); + } + + msm_rpm_write_contiguous(MSM_RPM_PAGE_CTRL, + MSM_RPM_CTRL_REQ_SEL_0, sel_masks, MSM_RPM_SEL_MASK_SIZE); + msm_rpm_write(MSM_RPM_PAGE_CTRL, MSM_RPM_CTRL_REQ_CTX_0, ctx_mask); + + /* Ensure RPM data is written before sending the interrupt */ + mb(); + msm_rpm_send_req_interrupt(); + + spin_unlock(&msm_rpm_irq_lock); + spin_unlock_irqrestore(&msm_rpm_lock, flags); + + wait_for_completion(&ack); + + BUG_ON((ctx_mask_ack & ~(msm_rpm_get_ctx_mask(MSM_RPM_CTX_REJECTED))) + != ctx_mask); + BUG_ON(memcmp(sel_masks, sel_masks_ack, sizeof(sel_masks_ack))); + + return (ctx_mask_ack & msm_rpm_get_ctx_mask(MSM_RPM_CTX_REJECTED)) + ? -ENOSPC : 0; +} + +/* Upon return, the array will contain values from the ack page. + * + * Note: assumes caller has acquired . + * + * Return value: + * 0: success + * -ENOSPC: request rejected + */ +static int msm_rpm_set_exclusive_noirq(int ctx, + uint32_t *sel_masks, struct msm_rpm_iv_pair *req, int count) +{ + unsigned int irq = msm_rpm_platform->irq_ack; + unsigned long flags; + uint32_t ctx_mask = msm_rpm_get_ctx_mask(ctx); + uint32_t ctx_mask_ack; + uint32_t sel_masks_ack[MSM_RPM_SEL_MASK_SIZE]; + int i; + + msm_rpm_request_poll_mode.req = req; + msm_rpm_request_poll_mode.count = count; + msm_rpm_request_poll_mode.ctx_mask_ack = &ctx_mask_ack; + msm_rpm_request_poll_mode.sel_masks_ack = sel_masks_ack; + msm_rpm_request_poll_mode.done = NULL; + + spin_lock_irqsave(&msm_rpm_irq_lock, flags); + irq_get_chip(irq)->irq_mask(irq_get_irq_data(irq)); + + if (msm_rpm_request) { + msm_rpm_busy_wait_for_request_completion(true); + BUG_ON(msm_rpm_request); + } + + msm_rpm_request = &msm_rpm_request_poll_mode; + + for (i = 0; i < count; i++) { + BUG_ON(req[i].id > MSM_RPM_ID_LAST); + msm_rpm_write(MSM_RPM_PAGE_REQ, req[i].id, req[i].value); + } + + msm_rpm_write_contiguous(MSM_RPM_PAGE_CTRL, + MSM_RPM_CTRL_REQ_SEL_0, sel_masks, MSM_RPM_SEL_MASK_SIZE); + msm_rpm_write(MSM_RPM_PAGE_CTRL, MSM_RPM_CTRL_REQ_CTX_0, ctx_mask); + + /* Ensure RPM data is written before sending the interrupt */ + mb(); + msm_rpm_send_req_interrupt(); + + msm_rpm_busy_wait_for_request_completion(false); + BUG_ON(msm_rpm_request); + + irq_get_chip(irq)->irq_unmask(irq_get_irq_data(irq)); + spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); + + BUG_ON((ctx_mask_ack & ~(msm_rpm_get_ctx_mask(MSM_RPM_CTX_REJECTED))) + != ctx_mask); + BUG_ON(memcmp(sel_masks, sel_masks_ack, sizeof(sel_masks_ack))); + + return (ctx_mask_ack & msm_rpm_get_ctx_mask(MSM_RPM_CTX_REJECTED)) + ? -ENOSPC : 0; +} + +/* Upon return, the array will contain values from the ack page. + * + * Return value: + * 0: success + * -EINTR: interrupted + * -EINVAL: invalid or invalid id in array + * -ENOSPC: request rejected + */ +static int msm_rpm_set_common( + int ctx, struct msm_rpm_iv_pair *req, int count, bool noirq) +{ + uint32_t sel_masks[MSM_RPM_SEL_MASK_SIZE] = {}; + int rc; + + if (ctx >= MSM_RPM_CTX_SET_COUNT) { + rc = -EINVAL; + goto set_common_exit; + } + + rc = msm_rpm_fill_sel_masks(sel_masks, req, count); + if (rc) + goto set_common_exit; + + if (noirq) { + unsigned long flags; + + spin_lock_irqsave(&msm_rpm_lock, flags); + rc = msm_rpm_set_exclusive_noirq(ctx, sel_masks, req, count); + spin_unlock_irqrestore(&msm_rpm_lock, flags); + } else { + rc = mutex_lock_interruptible(&msm_rpm_mutex); + if (rc) + goto set_common_exit; + + rc = msm_rpm_set_exclusive(ctx, sel_masks, req, count); + mutex_unlock(&msm_rpm_mutex); + } + +set_common_exit: + return rc; +} + +/* + * Return value: + * 0: success + * -EINTR: interrupted + * -EINVAL: invalid or invalid id in array + */ +static int msm_rpm_clear_common( + int ctx, struct msm_rpm_iv_pair *req, int count, bool noirq) +{ + uint32_t sel_masks[MSM_RPM_SEL_MASK_SIZE] = {}; + struct msm_rpm_iv_pair r[MSM_RPM_SEL_MASK_SIZE]; + int rc; + int i; + + if (ctx >= MSM_RPM_CTX_SET_COUNT) { + rc = -EINVAL; + goto clear_common_exit; + } + + rc = msm_rpm_fill_sel_masks(sel_masks, req, count); + if (rc) + goto clear_common_exit; + + for (i = 0; i < ARRAY_SIZE(r); i++) { + r[i].id = MSM_RPM_ID_INVALIDATE_0 + i; + r[i].value = sel_masks[i]; + } + + memset(sel_masks, 0, sizeof(sel_masks)); + sel_masks[msm_rpm_get_sel_mask_reg(MSM_RPM_SEL_INVALIDATE)] |= + msm_rpm_get_sel_mask(MSM_RPM_SEL_INVALIDATE); + + if (noirq) { + unsigned long flags; + + spin_lock_irqsave(&msm_rpm_lock, flags); + rc = msm_rpm_set_exclusive_noirq(ctx, sel_masks, r, + ARRAY_SIZE(r)); + spin_unlock_irqrestore(&msm_rpm_lock, flags); + BUG_ON(rc); + } else { + rc = mutex_lock_interruptible(&msm_rpm_mutex); + if (rc) + goto clear_common_exit; + + rc = msm_rpm_set_exclusive(ctx, sel_masks, r, ARRAY_SIZE(r)); + mutex_unlock(&msm_rpm_mutex); + BUG_ON(rc); + } + +clear_common_exit: + return rc; +} + +/* + * Note: assumes caller has acquired . + */ +static void msm_rpm_update_notification(uint32_t ctx, + struct msm_rpm_notif_config *curr_cfg, + struct msm_rpm_notif_config *new_cfg) +{ + if (memcmp(curr_cfg, new_cfg, sizeof(*new_cfg))) { + uint32_t sel_masks[MSM_RPM_SEL_MASK_SIZE] = {}; + int rc; + + sel_masks[msm_rpm_get_sel_mask_reg(MSM_RPM_SEL_NOTIFICATION)] + |= msm_rpm_get_sel_mask(MSM_RPM_SEL_NOTIFICATION); + + rc = msm_rpm_set_exclusive(ctx, + sel_masks, new_cfg->iv, ARRAY_SIZE(new_cfg->iv)); + BUG_ON(rc); + + memcpy(curr_cfg, new_cfg, sizeof(*new_cfg)); + } +} + +/* + * Note: assumes caller has acquired . + */ +static void msm_rpm_initialize_notification(void) +{ + struct msm_rpm_notif_config cfg; + unsigned int ctx; + int i; + + for (ctx = MSM_RPM_CTX_SET_0; ctx <= MSM_RPM_CTX_SET_SLEEP; ctx++) { + cfg = msm_rpm_notif_cfgs[ctx]; + + for (i = 0; i < MSM_RPM_SEL_MASK_SIZE; i++) { + configured_iv(&cfg)[i].id = + MSM_RPM_ID_NOTIFICATION_CONFIGURED_0 + i; + configured_iv(&cfg)[i].value = ~0UL; + + registered_iv(&cfg)[i].id = + MSM_RPM_ID_NOTIFICATION_REGISTERED_0 + i; + registered_iv(&cfg)[i].value = 0; + } + + msm_rpm_update_notification(ctx, + &msm_rpm_notif_cfgs[ctx], &cfg); + } +} + +/****************************************************************************** + * Public functions + *****************************************************************************/ + +int msm_rpm_local_request_is_outstanding(void) +{ + unsigned long flags; + int outstanding = 0; + + if (!spin_trylock_irqsave(&msm_rpm_lock, flags)) + goto local_request_is_outstanding_exit; + + if (!spin_trylock(&msm_rpm_irq_lock)) + goto local_request_is_outstanding_unlock; + + outstanding = (msm_rpm_request != NULL); + spin_unlock(&msm_rpm_irq_lock); + +local_request_is_outstanding_unlock: + spin_unlock_irqrestore(&msm_rpm_lock, flags); + +local_request_is_outstanding_exit: + return outstanding; +} + +/* + * Read the specified status registers and return their values. + * + * status: array of id-value pairs. Each specifies a status register, + * i.e, one of MSM_RPM_STATUS_ID_xxxx. Upon return, each will + * contain the value of the status register. + * count: number of id-value pairs in the array + * + * Return value: + * 0: success + * -EBUSY: RPM is updating the status page; values across different registers + * may not be consistent + * -EINVAL: invalid id in array + */ +int msm_rpm_get_status(struct msm_rpm_iv_pair *status, int count) +{ + uint32_t seq_begin; + uint32_t seq_end; + int rc; + int i; + + seq_begin = msm_rpm_read(MSM_RPM_PAGE_STATUS, + MSM_RPM_STATUS_ID_SEQUENCE); + + for (i = 0; i < count; i++) { + if (status[i].id > MSM_RPM_STATUS_ID_LAST) { + rc = -EINVAL; + goto get_status_exit; + } + + status[i].value = msm_rpm_read(MSM_RPM_PAGE_STATUS, + status[i].id); + } + + seq_end = msm_rpm_read(MSM_RPM_PAGE_STATUS, + MSM_RPM_STATUS_ID_SEQUENCE); + + rc = (seq_begin != seq_end || (seq_begin & 0x01)) ? -EBUSY : 0; + +get_status_exit: + return rc; +} +EXPORT_SYMBOL(msm_rpm_get_status); + +/* + * Issue a resource request to RPM to set resource values. + * + * Note: the function may sleep and must be called in a task context. + * + * ctx: the request's context. + * There two contexts that a RPM driver client can use: + * MSM_RPM_CTX_SET_0 and MSM_RPM_CTX_SET_SLEEP. For resource values + * that are intended to take effect when the CPU is active, + * MSM_RPM_CTX_SET_0 should be used. For resource values that are + * intended to take effect when the CPU is not active, + * MSM_RPM_CTX_SET_SLEEP should be used. + * req: array of id-value pairs. Each specifies a RPM resource, + * i.e, one of MSM_RPM_ID_xxxx. Each specifies the requested + * resource value. + * count: number of id-value pairs in the array + * + * Return value: + * 0: success + * -EINTR: interrupted + * -EINVAL: invalid or invalid id in array + * -ENOSPC: request rejected + */ +int msm_rpm_set(int ctx, struct msm_rpm_iv_pair *req, int count) +{ + return msm_rpm_set_common(ctx, req, count, false); +} +EXPORT_SYMBOL(msm_rpm_set); + +/* + * Issue a resource request to RPM to set resource values. + * + * Note: the function is similar to msm_rpm_set() except that it must be + * called with interrupts masked. If possible, use msm_rpm_set() + * instead, to maximize CPU throughput. + */ +int msm_rpm_set_noirq(int ctx, struct msm_rpm_iv_pair *req, int count) +{ + WARN(!irqs_disabled(), "msm_rpm_set_noirq can only be called " + "safely when local irqs are disabled. Consider using " + "msm_rpm_set or msm_rpm_set_nosleep instead."); + return msm_rpm_set_common(ctx, req, count, true); +} +EXPORT_SYMBOL(msm_rpm_set_noirq); + +/* + * Issue a resource request to RPM to clear resource values. Once the + * values are cleared, the resources revert back to their default values + * for this RPM master. + * + * Note: the function may sleep and must be called in a task context. + * + * ctx: the request's context. + * req: array of id-value pairs. Each specifies a RPM resource, + * i.e, one of MSM_RPM_ID_xxxx. 's are ignored. + * count: number of id-value pairs in the array + * + * Return value: + * 0: success + * -EINTR: interrupted + * -EINVAL: invalid or invalid id in array + */ +int msm_rpm_clear(int ctx, struct msm_rpm_iv_pair *req, int count) +{ + return msm_rpm_clear_common(ctx, req, count, false); +} +EXPORT_SYMBOL(msm_rpm_clear); + +/* + * Issue a resource request to RPM to clear resource values. + * + * Note: the function is similar to msm_rpm_clear() except that it must be + * called with interrupts masked. If possible, use msm_rpm_clear() + * instead, to maximize CPU throughput. + */ +int msm_rpm_clear_noirq(int ctx, struct msm_rpm_iv_pair *req, int count) +{ + WARN(!irqs_disabled(), "msm_rpm_clear_noirq can only be called " + "safely when local irqs are disabled. Consider using " + "msm_rpm_clear or msm_rpm_clear_nosleep instead."); + return msm_rpm_clear_common(ctx, req, count, true); +} +EXPORT_SYMBOL(msm_rpm_clear_noirq); + +/* + * Register for RPM notification. When the specified resources + * change their status on RPM, RPM sends out notifications and the + * driver will "up" the semaphore in struct msm_rpm_notification. + * + * Note: the function may sleep and must be called in a task context. + * + * Memory for must not be freed until the notification is + * unregistered. Memory for can be freed after this + * function returns. + * + * n: the notifcation object. Caller should initialize only the + * semaphore field. When a notification arrives later, the + * semaphore will be "up"ed. + * req: array of id-value pairs. Each specifies a status register, + * i.e, one of MSM_RPM_STATUS_ID_xxxx. 's are ignored. + * count: number of id-value pairs in the array + * + * Return value: + * 0: success + * -EINTR: interrupted + * -EINVAL: invalid id in array + */ +int msm_rpm_register_notification(struct msm_rpm_notification *n, + struct msm_rpm_iv_pair *req, int count) +{ + unsigned long flags; + unsigned int ctx; + struct msm_rpm_notif_config cfg; + int rc; + int i; + + INIT_LIST_HEAD(&n->list); + rc = msm_rpm_fill_sel_masks(n->sel_masks, req, count); + if (rc) + goto register_notification_exit; + + rc = mutex_lock_interruptible(&msm_rpm_mutex); + if (rc) + goto register_notification_exit; + + if (!msm_rpm_init_notif_done) { + msm_rpm_initialize_notification(); + msm_rpm_init_notif_done = true; + } + + spin_lock_irqsave(&msm_rpm_irq_lock, flags); + list_add(&n->list, &msm_rpm_notifications); + spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); + + ctx = MSM_RPM_CTX_SET_0; + cfg = msm_rpm_notif_cfgs[ctx]; + + for (i = 0; i < MSM_RPM_SEL_MASK_SIZE; i++) + registered_iv(&cfg)[i].value |= n->sel_masks[i]; + + msm_rpm_update_notification(ctx, &msm_rpm_notif_cfgs[ctx], &cfg); + mutex_unlock(&msm_rpm_mutex); + +register_notification_exit: + return rc; +} +EXPORT_SYMBOL(msm_rpm_register_notification); + +/* + * Unregister a notification. + * + * Note: the function may sleep and must be called in a task context. + * + * n: the notifcation object that was registered previously. + * + * Return value: + * 0: success + * -EINTR: interrupted + */ +int msm_rpm_unregister_notification(struct msm_rpm_notification *n) +{ + unsigned long flags; + unsigned int ctx; + struct msm_rpm_notif_config cfg; + int rc; + int i; + + rc = mutex_lock_interruptible(&msm_rpm_mutex); + if (rc) + goto unregister_notification_exit; + + ctx = MSM_RPM_CTX_SET_0; + cfg = msm_rpm_notif_cfgs[ctx]; + + for (i = 0; i < MSM_RPM_SEL_MASK_SIZE; i++) + registered_iv(&cfg)[i].value = 0; + + spin_lock_irqsave(&msm_rpm_irq_lock, flags); + list_del(&n->list); + list_for_each_entry(n, &msm_rpm_notifications, list) + for (i = 0; i < MSM_RPM_SEL_MASK_SIZE; i++) + registered_iv(&cfg)[i].value |= n->sel_masks[i]; + spin_unlock_irqrestore(&msm_rpm_irq_lock, flags); + + msm_rpm_update_notification(ctx, &msm_rpm_notif_cfgs[ctx], &cfg); + mutex_unlock(&msm_rpm_mutex); + +unregister_notification_exit: + return rc; +} +EXPORT_SYMBOL(msm_rpm_unregister_notification); + +static void __init msm_rpm_populate_map(void) +{ + int i, k; + + for (i = 0; i < ARRAY_SIZE(msm_rpm_map); i++) + msm_rpm_map[i] = MSM_RPM_SEL_LAST + 1; + + for (i = 0; i < rpm_map_data_size; i++) { + struct msm_rpm_map_data *raw_data = &rpm_map_data[i]; + + for (k = 0; k < raw_data->count; k++) + msm_rpm_map[raw_data->id + k] = raw_data->sel; + } +} + +int __init msm_rpm_init(struct msm_rpm_platform_data *data) +{ + uint32_t major; + uint32_t minor; + uint32_t build; + unsigned int irq; + int rc; + + msm_rpm_platform = data; + + major = msm_rpm_read(MSM_RPM_PAGE_STATUS, + MSM_RPM_STATUS_ID_VERSION_MAJOR); + minor = msm_rpm_read(MSM_RPM_PAGE_STATUS, + MSM_RPM_STATUS_ID_VERSION_MINOR); + build = msm_rpm_read(MSM_RPM_PAGE_STATUS, + MSM_RPM_STATUS_ID_VERSION_BUILD); + pr_info("%s: RPM firmware %u.%u.%u\n", __func__, major, minor, build); + + msm_rpm_write(MSM_RPM_PAGE_CTRL, MSM_RPM_CTRL_VERSION_MAJOR, 2); + msm_rpm_write(MSM_RPM_PAGE_CTRL, MSM_RPM_CTRL_VERSION_MINOR, 0); + msm_rpm_write(MSM_RPM_PAGE_CTRL, MSM_RPM_CTRL_VERSION_BUILD, 0); + + irq = msm_rpm_platform->irq_ack; + + rc = request_irq(irq, msm_rpm_ack_interrupt, + IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND, + "rpm_drv", msm_rpm_ack_interrupt); + if (rc) { + pr_err("%s: failed to request irq %d: %d\n", + __func__, irq, rc); + return rc; + } + + rc = irq_set_irq_wake(irq, 1); + if (rc) { + pr_err("%s: failed to set wakeup irq %u: %d\n", + __func__, irq, rc); + return rc; + } + + msm_rpm_populate_map(); + return 0; +} diff --git a/arch/arm/mach-msm/rpm_log.c b/arch/arm/mach-msm/rpm_log.c new file mode 100644 index 00000000000..3ed55da2848 --- /dev/null +++ b/arch/arm/mach-msm/rpm_log.c @@ -0,0 +1,363 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "rpm_log.h" + +/* registers in MSM_RPM_LOG_PAGE_INDICES */ +enum { + MSM_RPM_LOG_TAIL, + MSM_RPM_LOG_HEAD +}; + +/* used to 4 byte align message lengths */ +#define PADDED_LENGTH(x) (0xFFFFFFFC & ((x) + 3)) + +/* calculates the character string length of a message of byte length x */ +#define PRINTED_LENGTH(x) ((x) * 6 + 3) + +/* number of ms to wait between checking for new messages in the RPM log */ +#define RECHECK_TIME (50) + +struct msm_rpm_log_buffer { + char *data; + u32 len; + u32 pos; + u32 max_len; + u32 read_idx; + struct msm_rpm_log_platform_data *pdata; +}; + +/****************************************************************************** + * Internal functions + *****************************************************************************/ + +static inline u32 +msm_rpm_log_read(const struct msm_rpm_log_platform_data *pdata, u32 page, + u32 reg) +{ + return readl_relaxed(pdata->reg_base + pdata->reg_offsets[page] + + reg * 4); +} + +/* + * msm_rpm_log_copy() - Copies messages from a volatile circular buffer in + * the RPM's shared memory into a private local buffer + * msg_buffer: pointer to local buffer (string) + * buf_len: length of local buffer in bytes + * read_start_idx: index into shared memory buffer + * + * Return value: number of bytes written to the local buffer + * + * Copies messages stored in a circular buffer in the RPM Message Memory into + * a specified local buffer. The RPM processor is unaware of these reading + * efforts, so care is taken to make sure that messages are valid both before + * and after reading. The RPM processor utilizes a ULog driver to write the + * log. The RPM processor maintains tail and head indices. These correspond + * to the next byte to write into, and the first valid byte, respectively. + * Both indices increase monotonically (except for rollover). + * + * Messages take the form of [(u32)length] [(char)data0,1,...] in which the + * length specifies the number of payload bytes. Messages must be 4 byte + * aligned, so padding is added at the end of a message as needed. + * + * Print format: + * - 0xXX, 0xXX, 0xXX + * - 0xXX + * etc... + */ +static u32 msm_rpm_log_copy(const struct msm_rpm_log_platform_data *pdata, + char *msg_buffer, u32 buf_len, u32 *read_idx) +{ + u32 head_idx, tail_idx; + u32 pos = 0; + u32 i = 0; + u32 msg_len; + u32 pos_start; + char temp[4]; + + tail_idx = msm_rpm_log_read(pdata, MSM_RPM_LOG_PAGE_INDICES, + MSM_RPM_LOG_TAIL); + head_idx = msm_rpm_log_read(pdata, MSM_RPM_LOG_PAGE_INDICES, + MSM_RPM_LOG_HEAD); + + /* loop while the remote buffer has valid messages left to read */ + while (tail_idx - head_idx > 0 && tail_idx - *read_idx > 0) { + head_idx = msm_rpm_log_read(pdata, MSM_RPM_LOG_PAGE_INDICES, + MSM_RPM_LOG_HEAD); + /* check if the message to be read is valid */ + if (tail_idx - *read_idx > tail_idx - head_idx) { + *read_idx = head_idx; + continue; + } + /* + * Ensure that the reported buffer size is within limits of + * known maximum size and that all indices are 4 byte aligned. + * These conditions are required to interact with a ULog buffer + * properly. + */ + if (tail_idx - head_idx > pdata->log_len || + !IS_ALIGNED((tail_idx | head_idx | *read_idx), 4)) + break; + + msg_len = msm_rpm_log_read(pdata, MSM_RPM_LOG_PAGE_BUFFER, + (*read_idx >> 2) & pdata->log_len_mask); + + /* handle messages that claim to be longer than the log */ + if (PADDED_LENGTH(msg_len) > tail_idx - *read_idx - 4) + msg_len = tail_idx - *read_idx - 4; + + /* check that the local buffer has enough space for this msg */ + if (pos + PRINTED_LENGTH(msg_len) > buf_len) + break; + + pos_start = pos; + pos += scnprintf(msg_buffer + pos, buf_len - pos, "- "); + + /* copy message payload to local buffer */ + for (i = 0; i < msg_len; i++) { + /* read from shared memory 4 bytes at a time */ + if (IS_ALIGNED(i, 4)) + *((u32 *)temp) = msm_rpm_log_read(pdata, + MSM_RPM_LOG_PAGE_BUFFER, + ((*read_idx + 4 + i) >> 2) & + pdata->log_len_mask); + + pos += scnprintf(msg_buffer + pos, buf_len - pos, + "0x%02X, ", temp[i & 0x03]); + } + + pos += scnprintf(msg_buffer + pos, buf_len - pos, "\n"); + + head_idx = msm_rpm_log_read(pdata, MSM_RPM_LOG_PAGE_INDICES, + MSM_RPM_LOG_HEAD); + + /* roll back if message that was read is not still valid */ + if (tail_idx - *read_idx > tail_idx - head_idx) + pos = pos_start; + + *read_idx += PADDED_LENGTH(msg_len) + 4; + } + + return pos; +} + + +/* + * msm_rpm_log_file_read() - Reads in log buffer messages then outputs them to a + * user buffer + * + * Return value: + * 0: success + * -ENOMEM: no memory available + * -EINVAL: user buffer null or requested bytes 0 + * -EFAULT: user buffer not writeable + * -EAGAIN: no bytes available at the moment + */ +static ssize_t msm_rpm_log_file_read(struct file *file, char __user *bufu, + size_t count, loff_t *ppos) +{ + u32 out_len, remaining; + struct msm_rpm_log_platform_data *pdata; + struct msm_rpm_log_buffer *buf; + + buf = file->private_data; + pdata = buf->pdata; + if (!pdata) + return -EINVAL; + if (!buf) + return -ENOMEM; + if (!buf->data) + return -ENOMEM; + if (!bufu || count < 0) + return -EINVAL; + if (!access_ok(VERIFY_WRITE, bufu, count)) + return -EFAULT; + + /* check for more messages if local buffer empty */ + if (buf->pos == buf->len) { + buf->pos = 0; + buf->len = msm_rpm_log_copy(pdata, buf->data, buf->max_len, + &(buf->read_idx)); + } + + if ((file->f_flags & O_NONBLOCK) && buf->len == 0) + return -EAGAIN; + + /* loop until new messages arrive */ + while (buf->len == 0) { + cond_resched(); + if (msleep_interruptible(RECHECK_TIME)) + break; + buf->len = msm_rpm_log_copy(pdata, buf->data, buf->max_len, + &(buf->read_idx)); + } + + out_len = ((buf->len - buf->pos) < count ? buf->len - buf->pos : count); + + remaining = __copy_to_user(bufu, &(buf->data[buf->pos]), out_len); + buf->pos += out_len - remaining; + + return out_len - remaining; +} + + +/* + * msm_rpm_log_file_open() - Allows a new reader to open the RPM log virtual + * file + * + * One local buffer is kmalloc'ed for each reader, so no resource sharing has + * to take place (besides the read only access to the RPM log buffer). + * + * Return value: + * 0: success + * -ENOMEM: no memory available + */ +static int msm_rpm_log_file_open(struct inode *inode, struct file *file) +{ + struct msm_rpm_log_buffer *buf; + struct msm_rpm_log_platform_data *pdata; + + pdata = inode->i_private; + if (!pdata) + return -EINVAL; + + file->private_data = + kmalloc(sizeof(struct msm_rpm_log_buffer), GFP_KERNEL); + if (!file->private_data) { + pr_err("%s: ERROR kmalloc failed to allocate %d bytes\n", + __func__, sizeof(struct msm_rpm_log_buffer)); + return -ENOMEM; + } + buf = file->private_data; + + buf->data = kmalloc(PRINTED_LENGTH(pdata->log_len), GFP_KERNEL); + if (!buf->data) { + kfree(file->private_data); + file->private_data = NULL; + pr_err("%s: ERROR kmalloc failed to allocate %d bytes\n", + __func__, PRINTED_LENGTH(pdata->log_len)); + return -ENOMEM; + } + + buf->pdata = pdata; + buf->len = 0; + buf->pos = 0; + buf->max_len = PRINTED_LENGTH(pdata->log_len); + buf->read_idx = msm_rpm_log_read(pdata, MSM_RPM_LOG_PAGE_INDICES, + MSM_RPM_LOG_HEAD); + return 0; +} + +static int msm_rpm_log_file_close(struct inode *inode, struct file *file) +{ + kfree(((struct msm_rpm_log_buffer *)file->private_data)->data); + kfree(file->private_data); + return 0; +} + + +static const struct file_operations msm_rpm_log_file_fops = { + .owner = THIS_MODULE, + .open = msm_rpm_log_file_open, + .read = msm_rpm_log_file_read, + .release = msm_rpm_log_file_close, +}; + +static int __devinit msm_rpm_log_probe(struct platform_device *pdev) +{ + struct dentry *dent; + struct msm_rpm_log_platform_data *pdata; + + pdata = pdev->dev.platform_data; + if (!pdata) + return -EINVAL; + + pdata->reg_base = ioremap(pdata->phys_addr_base, pdata->phys_size); + if (!pdata->reg_base) { + pr_err("%s: ERROR could not ioremap: start=%p, len=%u\n", + __func__, (void *) pdata->phys_addr_base, + pdata->phys_size); + return -EBUSY; + } + + dent = debugfs_create_file("rpm_log", S_IRUGO, NULL, + pdev->dev.platform_data, &msm_rpm_log_file_fops); + if (!dent) { + pr_err("%s: ERROR debugfs_create_file failed\n", __func__); + return -ENOMEM; + } + + platform_set_drvdata(pdev, dent); + + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit msm_rpm_log_remove(struct platform_device *pdev) +{ + struct dentry *dent; + struct msm_rpm_log_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + iounmap(pdata->reg_base); + + dent = platform_get_drvdata(pdev); + debugfs_remove(dent); + platform_set_drvdata(pdev, NULL); + + pr_notice("%s: OK\n", __func__); + return 0; +} + +static struct platform_driver msm_rpm_log_driver = { + .probe = msm_rpm_log_probe, + .remove = __devexit_p(msm_rpm_log_remove), + .driver = { + .name = "msm_rpm_log", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_rpm_log_init(void) +{ + return platform_driver_register(&msm_rpm_log_driver); +} + +static void __exit msm_rpm_log_exit(void) +{ + platform_driver_unregister(&msm_rpm_log_driver); +} + +module_init(msm_rpm_log_init); +module_exit(msm_rpm_log_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM RPM Log driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:msm_rpm_log"); diff --git a/arch/arm/mach-msm/rpm_log.h b/arch/arm/mach-msm/rpm_log.h new file mode 100644 index 00000000000..37349b60f0a --- /dev/null +++ b/arch/arm/mach-msm/rpm_log.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPM_LOG_H +#define __ARCH_ARM_MACH_MSM_RPM_LOG_H + +#include + +enum { + MSM_RPM_LOG_PAGE_INDICES, + MSM_RPM_LOG_PAGE_BUFFER, + MSM_RPM_LOG_PAGE_COUNT +}; + +struct msm_rpm_log_platform_data { + u32 reg_offsets[MSM_RPM_LOG_PAGE_COUNT]; + u32 log_len; + u32 log_len_mask; + phys_addr_t phys_addr_base; + u32 phys_size; + void __iomem *reg_base; +}; + +#endif /* __ARCH_ARM_MACH_MSM_RPM_LOG_H */ diff --git a/arch/arm/mach-msm/rpm_resources.c b/arch/arm/mach-msm/rpm_resources.c new file mode 100644 index 00000000000..4aae8a0966a --- /dev/null +++ b/arch/arm/mach-msm/rpm_resources.c @@ -0,0 +1,1014 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mpm.h" +#include "rpm_resources.h" +#include "spm.h" + +/****************************************************************************** + * Debug Definitions + *****************************************************************************/ + +enum { + MSM_RPMRS_DEBUG_OUTPUT = BIT(0), + MSM_RPMRS_DEBUG_BUFFER = BIT(1), +}; + +static int msm_rpmrs_debug_mask; +module_param_named( + debug_mask, msm_rpmrs_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); + +static struct msm_rpmrs_level *msm_rpmrs_levels; +static int msm_rpmrs_level_count; + +static bool msm_rpmrs_pxo_beyond_limits(struct msm_rpmrs_limits *limits); +static void msm_rpmrs_aggregate_pxo(struct msm_rpmrs_limits *limits); +static void msm_rpmrs_restore_pxo(void); +static bool msm_rpmrs_l2_cache_beyond_limits(struct msm_rpmrs_limits *limits); +static void msm_rpmrs_aggregate_l2_cache(struct msm_rpmrs_limits *limits); +static void msm_rpmrs_restore_l2_cache(void); +static bool msm_rpmrs_vdd_mem_beyond_limits(struct msm_rpmrs_limits *limits); +static void msm_rpmrs_aggregate_vdd_mem(struct msm_rpmrs_limits *limits); +static void msm_rpmrs_restore_vdd_mem(void); +static bool msm_rpmrs_vdd_dig_beyond_limits(struct msm_rpmrs_limits *limits); +static void msm_rpmrs_aggregate_vdd_dig(struct msm_rpmrs_limits *limits); +static void msm_rpmrs_restore_vdd_dig(void); +static void msm_rpmrs_aggregate_rpm_cpu(struct msm_rpmrs_limits *limits); +static void msm_rpmrs_restore_rpm_cpu(void); + +#ifdef CONFIG_MSM_L2_SPM +static void *msm_rpmrs_l2_counter_addr; +static int msm_rpmrs_l2_reset_count; +#define L2_PC_COUNTER_ADDR 0x660 +#endif + +#define MSM_RPMRS_MAX_RS_REGISTER_COUNT 2 + +struct msm_rpmrs_resource { + struct msm_rpm_iv_pair rs[MSM_RPMRS_MAX_RS_REGISTER_COUNT]; + uint32_t size; + char *name; + + uint32_t enable_low_power; + + bool (*beyond_limits)(struct msm_rpmrs_limits *limits); + void (*aggregate)(struct msm_rpmrs_limits *limits); + void (*restore)(void); +}; + +static struct msm_rpmrs_resource msm_rpmrs_pxo = { + .rs[0].id = MSM_RPMRS_ID_PXO_CLK, + .size = 1, + .name = "pxo", + .beyond_limits = msm_rpmrs_pxo_beyond_limits, + .aggregate = msm_rpmrs_aggregate_pxo, + .restore = msm_rpmrs_restore_pxo, +}; + +static struct msm_rpmrs_resource msm_rpmrs_l2_cache = { + .rs[0].id = MSM_RPMRS_ID_APPS_L2_CACHE_CTL, + .size = 1, + .name = "L2_cache", + .beyond_limits = msm_rpmrs_l2_cache_beyond_limits, + .aggregate = msm_rpmrs_aggregate_l2_cache, + .restore = msm_rpmrs_restore_l2_cache, +}; + +static struct msm_rpmrs_resource msm_rpmrs_vdd_mem = { + .rs[0].id = MSM_RPMRS_ID_VDD_MEM_0, + .rs[1].id = MSM_RPMRS_ID_VDD_MEM_1, + .size = 2, + .name = "vdd_mem", + .beyond_limits = msm_rpmrs_vdd_mem_beyond_limits, + .aggregate = msm_rpmrs_aggregate_vdd_mem, + .restore = msm_rpmrs_restore_vdd_mem, +}; + +static struct msm_rpmrs_resource msm_rpmrs_vdd_dig = { + .rs[0].id = MSM_RPMRS_ID_VDD_DIG_0, + .rs[1].id = MSM_RPMRS_ID_VDD_DIG_1, + .size = 2, + .name = "vdd_dig", + .beyond_limits = msm_rpmrs_vdd_dig_beyond_limits, + .aggregate = msm_rpmrs_aggregate_vdd_dig, + .restore = msm_rpmrs_restore_vdd_dig, +}; + +static struct msm_rpmrs_resource msm_rpmrs_rpm_cpu = { + .rs[0].id = MSM_RPMRS_ID_RPM_CTL, + .size = 1, + .name = "rpm_cpu", + .beyond_limits = NULL, + .aggregate = msm_rpmrs_aggregate_rpm_cpu, + .restore = msm_rpmrs_restore_rpm_cpu, +}; + +static struct msm_rpmrs_resource *msm_rpmrs_resources[] = { + &msm_rpmrs_pxo, + &msm_rpmrs_l2_cache, + &msm_rpmrs_vdd_mem, + &msm_rpmrs_vdd_dig, + &msm_rpmrs_rpm_cpu, +}; + +static uint32_t msm_rpmrs_buffer[MSM_RPM_ID_LAST + 1]; +static DECLARE_BITMAP(msm_rpmrs_buffered, MSM_RPM_ID_LAST + 1); +static DECLARE_BITMAP(msm_rpmrs_listed, MSM_RPM_ID_LAST + 1); +static DEFINE_SPINLOCK(msm_rpmrs_lock); + +#define MSM_RPMRS_VDD_MASK 0xfff +#define MSM_RPMRS_VDD(v) ((v) & (MSM_RPMRS_VDD_MASK)) + +/****************************************************************************** + * Attribute Definitions + *****************************************************************************/ + +struct msm_rpmrs_kboj_attribute { + struct msm_rpmrs_resource *rs; + struct kobj_attribute ka; +}; + +#define GET_RS_FROM_ATTR(attr) \ + (container_of(attr, struct msm_rpmrs_kboj_attribute, ka)->rs) + +struct msm_rpmrs_resource_sysfs { + struct attribute_group attr_group; + struct attribute *attrs[2]; + struct msm_rpmrs_kboj_attribute kas; +}; + +/****************************************************************************** + * Resource Specific Functions + *****************************************************************************/ + +static void msm_rpmrs_aggregate_sclk(uint32_t sclk_count) +{ + msm_rpmrs_buffer[MSM_RPM_ID_TRIGGER_TIMED_TO] = 0; + set_bit(MSM_RPM_ID_TRIGGER_TIMED_TO, msm_rpmrs_buffered); + msm_rpmrs_buffer[MSM_RPM_ID_TRIGGER_TIMED_SCLK_COUNT] = sclk_count; + set_bit(MSM_RPM_ID_TRIGGER_TIMED_SCLK_COUNT, msm_rpmrs_buffered); +} + +static void msm_rpmrs_restore_sclk(void) +{ + clear_bit(MSM_RPM_ID_TRIGGER_TIMED_SCLK_COUNT, msm_rpmrs_buffered); + msm_rpmrs_buffer[MSM_RPM_ID_TRIGGER_TIMED_SCLK_COUNT] = 0; + clear_bit(MSM_RPM_ID_TRIGGER_TIMED_TO, msm_rpmrs_buffered); + msm_rpmrs_buffer[MSM_RPM_ID_TRIGGER_TIMED_TO] = 0; +} + +static bool msm_rpmrs_pxo_beyond_limits(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_pxo; + uint32_t pxo; + + if (rs->enable_low_power && test_bit(rs->rs[0].id, msm_rpmrs_buffered)) + pxo = msm_rpmrs_buffer[rs->rs[0].id]; + else + pxo = MSM_RPMRS_PXO_ON; + + return pxo > limits->pxo; +} + +static void msm_rpmrs_aggregate_pxo(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_pxo; + uint32_t *buf = &msm_rpmrs_buffer[rs->rs[0].id]; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) { + rs->rs[0].value = *buf; + if (limits->pxo > *buf) + *buf = limits->pxo; + if (MSM_RPMRS_DEBUG_OUTPUT & msm_rpmrs_debug_mask) + pr_info("%s: %d (0x%x)\n", __func__, *buf, *buf); + } +} + +static void msm_rpmrs_restore_pxo(void) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_pxo; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) + msm_rpmrs_buffer[rs->rs[0].id] = rs->rs[0].value; +} + +static bool msm_rpmrs_l2_cache_beyond_limits(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_l2_cache; + uint32_t l2_cache; + + if (rs->enable_low_power && test_bit(rs->rs[0].id, msm_rpmrs_buffered)) + l2_cache = msm_rpmrs_buffer[rs->rs[0].id]; + else + l2_cache = MSM_RPMRS_L2_CACHE_ACTIVE; + + return l2_cache > limits->l2_cache; +} + +static void msm_rpmrs_aggregate_l2_cache(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_l2_cache; + uint32_t *buf = &msm_rpmrs_buffer[rs->rs[0].id]; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) { + rs->rs[0].value = *buf; + if (limits->l2_cache > *buf) + *buf = limits->l2_cache; + + if (MSM_RPMRS_DEBUG_OUTPUT & msm_rpmrs_debug_mask) + pr_info("%s: %d (0x%x)\n", __func__, *buf, *buf); + } +} + +#ifdef CONFIG_MSM_L2_SPM +static bool msm_spm_l2_cache_beyond_limits(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_l2_cache; + uint32_t l2_cache = rs->rs[0].value; + + if (!rs->enable_low_power) + l2_cache = MSM_RPMRS_L2_CACHE_ACTIVE; + + return l2_cache > limits->l2_cache; +} +#endif + +static void msm_rpmrs_restore_l2_cache(void) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_l2_cache; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) + msm_rpmrs_buffer[rs->rs[0].id] = rs->rs[0].value; +} + +static bool msm_rpmrs_vdd_mem_beyond_limits(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_vdd_mem; + uint32_t vdd_mem; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) { + uint32_t buffered_value = msm_rpmrs_buffer[rs->rs[0].id]; + + if (rs->enable_low_power == 0) + vdd_mem = MSM_RPMRS_VDD_MEM_ACTIVE; + else if (rs->enable_low_power == 1) + vdd_mem = MSM_RPMRS_VDD_MEM_RET_HIGH; + else + vdd_mem = MSM_RPMRS_VDD_MEM_RET_LOW; + + if (MSM_RPMRS_VDD(buffered_value) > MSM_RPMRS_VDD(vdd_mem)) + vdd_mem = buffered_value; + } else { + vdd_mem = MSM_RPMRS_VDD_MEM_ACTIVE; + } + + return MSM_RPMRS_VDD(vdd_mem) >= + MSM_RPMRS_VDD(limits->vdd_mem_upper_bound); +} + +static void msm_rpmrs_aggregate_vdd_mem(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_vdd_mem; + uint32_t *buf = &msm_rpmrs_buffer[rs->rs[0].id]; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) { + rs->rs[0].value = *buf; + if (MSM_RPMRS_VDD(limits->vdd_mem) > MSM_RPMRS_VDD(*buf)) { + *buf &= ~MSM_RPMRS_VDD_MASK; + *buf |= MSM_RPMRS_VDD(limits->vdd_mem); + } + + if (MSM_RPMRS_DEBUG_OUTPUT & msm_rpmrs_debug_mask) + pr_info("%s: vdd %d (0x%x)\n", __func__, + MSM_RPMRS_VDD(*buf), MSM_RPMRS_VDD(*buf)); + } +} + +static void msm_rpmrs_restore_vdd_mem(void) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_vdd_mem; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) + msm_rpmrs_buffer[rs->rs[0].id] = rs->rs[0].value; +} + +static bool msm_rpmrs_vdd_dig_beyond_limits(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_vdd_dig; + uint32_t vdd_dig; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) { + uint32_t buffered_value = msm_rpmrs_buffer[rs->rs[0].id]; + + if (rs->enable_low_power == 0) + vdd_dig = MSM_RPMRS_VDD_DIG_ACTIVE; + else if (rs->enable_low_power == 1) + vdd_dig = MSM_RPMRS_VDD_DIG_RET_HIGH; + else + vdd_dig = MSM_RPMRS_VDD_DIG_RET_LOW; + + if (MSM_RPMRS_VDD(buffered_value) > MSM_RPMRS_VDD(vdd_dig)) + vdd_dig = buffered_value; + } else { + vdd_dig = MSM_RPMRS_VDD_DIG_ACTIVE; + } + + return MSM_RPMRS_VDD(vdd_dig) >= + MSM_RPMRS_VDD(limits->vdd_dig_upper_bound); +} + +static void msm_rpmrs_aggregate_vdd_dig(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_vdd_dig; + uint32_t *buf = &msm_rpmrs_buffer[rs->rs[0].id]; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) { + rs->rs[0].value = *buf; + if (MSM_RPMRS_VDD(limits->vdd_dig) > MSM_RPMRS_VDD(*buf)) { + *buf &= ~MSM_RPMRS_VDD_MASK; + *buf |= MSM_RPMRS_VDD(limits->vdd_dig); + } + + + if (MSM_RPMRS_DEBUG_OUTPUT & msm_rpmrs_debug_mask) + pr_info("%s: vdd %d (0x%x)\n", __func__, + MSM_RPMRS_VDD(*buf), MSM_RPMRS_VDD(*buf)); + } +} + +static void msm_rpmrs_restore_vdd_dig(void) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_vdd_dig; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) + msm_rpmrs_buffer[rs->rs[0].id] = rs->rs[0].value; +} + +static void msm_rpmrs_aggregate_rpm_cpu(struct msm_rpmrs_limits *limits) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_rpm_cpu; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) { + rs->rs[0].value = msm_rpmrs_buffer[rs->rs[0].id]; + if (!msm_rpmrs_rpm_cpu.enable_low_power) + msm_rpmrs_buffer[rs->rs[0].id] = 1; + } +} + +static void msm_rpmrs_restore_rpm_cpu(void) +{ + struct msm_rpmrs_resource *rs = &msm_rpmrs_rpm_cpu; + + if (test_bit(rs->rs[0].id, msm_rpmrs_buffered)) + msm_rpmrs_buffer[rs->rs[0].id] = rs->rs[0].value; +} + +/****************************************************************************** + * Buffering Functions + *****************************************************************************/ + +static bool msm_rpmrs_irqs_detectable(struct msm_rpmrs_limits *limits, + bool irqs_detect, bool gpio_detect) +{ + + if (limits->vdd_dig <= MSM_RPMRS_VDD_DIG_RET_HIGH) + return irqs_detect; + + if (limits->pxo == MSM_RPMRS_PXO_OFF) + return gpio_detect; + + return true; +} + +static bool msm_rpmrs_use_mpm(struct msm_rpmrs_limits *limits) +{ + return (limits->pxo == MSM_RPMRS_PXO_OFF) || + (limits->vdd_dig <= MSM_RPMRS_VDD_DIG_RET_HIGH); +} + +static void msm_rpmrs_update_levels(void) +{ + int i, k; + + for (i = 0; i < msm_rpmrs_level_count; i++) { + struct msm_rpmrs_level *level = &msm_rpmrs_levels[i]; + + if (level->sleep_mode != MSM_PM_SLEEP_MODE_POWER_COLLAPSE) + continue; + + level->available = true; + + for (k = 0; k < ARRAY_SIZE(msm_rpmrs_resources); k++) { + struct msm_rpmrs_resource *rs = msm_rpmrs_resources[k]; + + if (rs->beyond_limits && + rs->beyond_limits(&level->rs_limits)) { + level->available = false; + break; + } + } + } +} + +/* + * Return value: + * 0: no entries in is on our resource list + * 1: one or more entries in is on our resource list + * -EINVAL: invalid id in array + */ +static int msm_rpmrs_buffer_request(struct msm_rpm_iv_pair *req, int count) +{ + bool listed; + int i; + + for (i = 0; i < count; i++) + if (req[i].id > MSM_RPM_ID_LAST) + return -EINVAL; + + for (i = 0, listed = false; i < count; i++) { + msm_rpmrs_buffer[req[i].id] = req[i].value; + set_bit(req[i].id, msm_rpmrs_buffered); + + if (MSM_RPMRS_DEBUG_BUFFER & msm_rpmrs_debug_mask) + pr_info("%s: reg %d: 0x%x\n", + __func__, req[i].id, req[i].value); + + if (listed) + continue; + + if (test_bit(req[i].id, msm_rpmrs_listed)) + listed = true; + } + + return listed ? 1 : 0; +} + +/* + * Return value: + * 0: no entries in is on our resource list + * 1: one or more entries in is on our resource list + * -EINVAL: invalid id in array + */ +static int msm_rpmrs_clear_buffer(struct msm_rpm_iv_pair *req, int count) +{ + bool listed; + int i; + + for (i = 0; i < count; i++) + if (req[i].id > MSM_RPM_ID_LAST) + return -EINVAL; + + for (i = 0, listed = false; i < count; i++) { + msm_rpmrs_buffer[req[i].id] = 0; + clear_bit(req[i].id, msm_rpmrs_buffered); + + if (MSM_RPMRS_DEBUG_BUFFER & msm_rpmrs_debug_mask) + pr_info("%s: reg %d\n", __func__, req[i].id); + + if (listed) + continue; + + if (test_bit(req[i].id, msm_rpmrs_listed)) + listed = true; + } + + return listed ? 1 : 0; +} + +#ifdef CONFIG_MSM_L2_SPM +static int msm_rpmrs_flush_L2(struct msm_rpmrs_limits *limits, int notify_rpm) +{ + int rc = 0; + int lpm; + + switch (limits->l2_cache) { + case MSM_RPMRS_L2_CACHE_HSFS_OPEN: + lpm = MSM_SPM_L2_MODE_POWER_COLLAPSE; + /* Increment the counter for TZ to init L2 on warmboot */ + /* Barrier in msm_spm_l2_set_low_power_mode */ + BUG_ON(!msm_rpmrs_l2_counter_addr); + writel_relaxed(++msm_rpmrs_l2_reset_count, + msm_rpmrs_l2_counter_addr); + break; + case MSM_RPMRS_L2_CACHE_GDHS: + lpm = MSM_SPM_L2_MODE_GDHS; + break; + case MSM_RPMRS_L2_CACHE_RETENTION: + lpm = MSM_SPM_L2_MODE_RETENTION; + break; + default: + case MSM_RPMRS_L2_CACHE_ACTIVE: + lpm = MSM_SPM_L2_MODE_DISABLED; + break; + } + + rc = msm_spm_l2_set_low_power_mode(lpm, notify_rpm); + if (MSM_RPMRS_DEBUG_BUFFER & msm_rpmrs_debug_mask) + pr_info("%s: Requesting low power mode %d returned %d\n", + __func__, lpm, rc); + + return rc; +} +#else +static int msm_rpmrs_flush_L2(struct msm_rpmrs_limits *limits, int notify_rpm) +{ + return 0; +} +#endif + +static int msm_rpmrs_flush_buffer( + uint32_t sclk_count, struct msm_rpmrs_limits *limits, int from_idle) +{ + struct msm_rpm_iv_pair *req; + int count; + int rc; + int i; + + msm_rpmrs_aggregate_sclk(sclk_count); + for (i = 0; i < ARRAY_SIZE(msm_rpmrs_resources); i++) { + if (msm_rpmrs_resources[i]->aggregate) + msm_rpmrs_resources[i]->aggregate(limits); + } + + count = bitmap_weight(msm_rpmrs_buffered, MSM_RPM_ID_LAST + 1); + + req = kmalloc(sizeof(*req) * count, GFP_ATOMIC); + if (!req) { + rc = -ENOMEM; + goto flush_buffer_restore; + } + + count = 0; + i = find_first_bit(msm_rpmrs_buffered, MSM_RPM_ID_LAST + 1); + + while (i < MSM_RPM_ID_LAST + 1) { + if (MSM_RPMRS_DEBUG_OUTPUT & msm_rpmrs_debug_mask) + pr_info("%s: reg %d: 0x%x\n", + __func__, i, msm_rpmrs_buffer[i]); + + req[count].id = i; + req[count].value = msm_rpmrs_buffer[i]; + count++; + + i = find_next_bit(msm_rpmrs_buffered, MSM_RPM_ID_LAST+1, i+1); + } + + rc = msm_rpm_set_noirq(MSM_RPM_CTX_SET_SLEEP, req, count); + kfree(req); + + if (rc) + goto flush_buffer_restore; + + bitmap_and(msm_rpmrs_buffered, + msm_rpmrs_buffered, msm_rpmrs_listed, MSM_RPM_ID_LAST + 1); + +flush_buffer_restore: + for (i = 0; i < ARRAY_SIZE(msm_rpmrs_resources); i++) { + if (msm_rpmrs_resources[i]->restore) + msm_rpmrs_resources[i]->restore(); + } + msm_rpmrs_restore_sclk(); + + if (rc) + pr_err("%s: failed: %d\n", __func__, rc); + return rc; +} + +static int msm_rpmrs_set_common( + int ctx, struct msm_rpm_iv_pair *req, int count, bool noirq) +{ + if (ctx == MSM_RPM_CTX_SET_SLEEP) { + unsigned long flags; + int rc; + + spin_lock_irqsave(&msm_rpmrs_lock, flags); + rc = msm_rpmrs_buffer_request(req, count); + if (rc > 0) { + msm_rpmrs_update_levels(); + rc = 0; + } + spin_unlock_irqrestore(&msm_rpmrs_lock, flags); + + return rc; + } + + if (noirq) + return msm_rpm_set_noirq(ctx, req, count); + else + return msm_rpm_set(ctx, req, count); +} + +static int msm_rpmrs_clear_common( + int ctx, struct msm_rpm_iv_pair *req, int count, bool noirq) +{ + if (ctx == MSM_RPM_CTX_SET_SLEEP) { + unsigned long flags; + int rc; + + spin_lock_irqsave(&msm_rpmrs_lock, flags); + rc = msm_rpmrs_clear_buffer(req, count); + if (rc > 0) { + msm_rpmrs_update_levels(); + rc = 0; + } + spin_unlock_irqrestore(&msm_rpmrs_lock, flags); + + if (rc < 0) + return rc; + } + + if (noirq) + return msm_rpm_clear_noirq(ctx, req, count); + else + return msm_rpm_clear(ctx, req, count); +} + +/****************************************************************************** + * Attribute Functions + *****************************************************************************/ + +static ssize_t msm_rpmrs_resource_attr_show( + struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct kernel_param kp; + unsigned long flags; + unsigned int temp; + int rc; + + spin_lock_irqsave(&msm_rpmrs_lock, flags); + temp = GET_RS_FROM_ATTR(attr)->enable_low_power; + spin_unlock_irqrestore(&msm_rpmrs_lock, flags); + + kp.arg = &temp; + rc = param_get_uint(buf, &kp); + + if (rc > 0) { + strcat(buf, "\n"); + rc++; + } + + return rc; +} + +static ssize_t msm_rpmrs_resource_attr_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + struct kernel_param kp; + unsigned long flags; + unsigned int temp; + int rc; + + kp.arg = &temp; + rc = param_set_uint(buf, &kp); + if (rc) + return rc; + + spin_lock_irqsave(&msm_rpmrs_lock, flags); + GET_RS_FROM_ATTR(attr)->enable_low_power = temp; + msm_rpmrs_update_levels(); + spin_unlock_irqrestore(&msm_rpmrs_lock, flags); + + return count; +} + +static int __init msm_rpmrs_resource_sysfs_add(void) +{ + struct kobject *module_kobj; + struct kobject *low_power_kboj; + struct msm_rpmrs_resource_sysfs *rs; + int i; + int rc; + + module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); + if (!module_kobj) { + pr_err("%s: cannot find kobject for module %s\n", + __func__, KBUILD_MODNAME); + rc = -ENOENT; + goto resource_sysfs_add_exit; + } + + low_power_kboj = kobject_create_and_add( + "enable_low_power", module_kobj); + if (!low_power_kboj) { + pr_err("%s: cannot create kobject\n", __func__); + rc = -ENOMEM; + goto resource_sysfs_add_exit; + } + + for (i = 0; i < ARRAY_SIZE(msm_rpmrs_resources); i++) { + rs = kzalloc(sizeof(*rs), GFP_KERNEL); + if (!rs) { + pr_err("%s: cannot allocate memory for attributes\n", + __func__); + rc = -ENOMEM; + goto resource_sysfs_add_exit; + } + + rs->kas.rs = msm_rpmrs_resources[i]; + rs->kas.ka.attr.name = msm_rpmrs_resources[i]->name; + rs->kas.ka.attr.mode = 0644; + rs->kas.ka.show = msm_rpmrs_resource_attr_show; + rs->kas.ka.store = msm_rpmrs_resource_attr_store; + + rs->attrs[0] = &rs->kas.ka.attr; + rs->attrs[1] = NULL; + rs->attr_group.attrs = rs->attrs; + + rc = sysfs_create_group(low_power_kboj, &rs->attr_group); + if (rc) { + pr_err("%s: cannot create kobject attribute group\n", + __func__); + goto resource_sysfs_add_exit; + } + } + + rc = 0; + +resource_sysfs_add_exit: + return rc; +} + +/****************************************************************************** + * Public Functions + *****************************************************************************/ + +int msm_rpmrs_set(int ctx, struct msm_rpm_iv_pair *req, int count) +{ + return msm_rpmrs_set_common(ctx, req, count, false); +} + +int msm_rpmrs_set_noirq(int ctx, struct msm_rpm_iv_pair *req, int count) +{ + WARN(!irqs_disabled(), "msm_rpmrs_set_noirq can only be called " + "safely when local irqs are disabled. Consider using " + "msm_rpmrs_set or msm_rpmrs_set_nosleep instead."); + return msm_rpmrs_set_common(ctx, req, count, true); +} + +int msm_rpmrs_clear(int ctx, struct msm_rpm_iv_pair *req, int count) +{ + return msm_rpmrs_clear_common(ctx, req, count, false); +} + +int msm_rpmrs_clear_noirq(int ctx, struct msm_rpm_iv_pair *req, int count) +{ + WARN(!irqs_disabled(), "msm_rpmrs_clear_noirq can only be called " + "safely when local irqs are disabled. Consider using " + "msm_rpmrs_clear or msm_rpmrs_clear_nosleep instead."); + return msm_rpmrs_clear_common(ctx, req, count, true); +} + +void msm_rpmrs_show_resources(void) +{ + struct msm_rpmrs_resource *rs; + unsigned long flags; + int i; + + spin_lock_irqsave(&msm_rpmrs_lock, flags); + for (i = 0; i < ARRAY_SIZE(msm_rpmrs_resources); i++) { + rs = msm_rpmrs_resources[i]; + if (rs->rs[0].id < MSM_RPM_ID_LAST + 1) + pr_info("%s: resource %s: buffered %d, value 0x%x\n", + __func__, rs->name, + test_bit(rs->rs[0].id, msm_rpmrs_buffered), + msm_rpmrs_buffer[rs->rs[0].id]); + else + pr_info("%s: resource %s: value %d\n", + __func__, rs->name, rs->rs[0].value); + } + spin_unlock_irqrestore(&msm_rpmrs_lock, flags); +} + +struct msm_rpmrs_limits *msm_rpmrs_lowest_limits( + bool from_idle, enum msm_pm_sleep_mode sleep_mode, uint32_t latency_us, + uint32_t sleep_us) +{ + unsigned int cpu = smp_processor_id(); + struct msm_rpmrs_level *best_level = NULL; + bool irqs_detectable = false; + bool gpio_detectable = false; + int i; + + if (sleep_mode == MSM_PM_SLEEP_MODE_POWER_COLLAPSE) { + irqs_detectable = msm_mpm_irqs_detectable(from_idle); + gpio_detectable = msm_mpm_gpio_irqs_detectable(from_idle); + } + + for (i = 0; i < msm_rpmrs_level_count; i++) { + struct msm_rpmrs_level *level = &msm_rpmrs_levels[i]; + uint32_t power; + + if (!level->available) + continue; + + if (sleep_mode != level->sleep_mode) + continue; + + if (latency_us < level->latency_us) + continue; + + if (!msm_rpmrs_irqs_detectable(&level->rs_limits, + irqs_detectable, gpio_detectable)) + continue; + + if (sleep_us <= 1) { + power = level->energy_overhead; + } else if (sleep_us <= level->time_overhead_us) { + power = level->energy_overhead / sleep_us; + } else if ((sleep_us >> 10) > level->time_overhead_us) { + power = level->steady_state_power; + } else { + power = (sleep_us - level->time_overhead_us); + power *= level->steady_state_power; + power /= sleep_us; + power += level->energy_overhead / sleep_us; + } + + if (!best_level || + best_level->rs_limits.power[cpu] >= power) { + level->rs_limits.latency_us[cpu] = level->latency_us; + level->rs_limits.power[cpu] = power; + best_level = level; + } + } + + return best_level ? &best_level->rs_limits : NULL; +} + +int msm_rpmrs_enter_sleep(uint32_t sclk_count, struct msm_rpmrs_limits *limits, + bool from_idle, bool notify_rpm) +{ + int rc = 0; + + rc = msm_rpmrs_flush_L2(limits, notify_rpm); + if (rc) + return rc; + + if (notify_rpm) { + rc = msm_rpmrs_flush_buffer(sclk_count, limits, from_idle); + if (rc) + return rc; + + if (msm_rpmrs_use_mpm(limits)) + msm_mpm_enter_sleep(from_idle); + } + + return rc; +} + +void msm_rpmrs_exit_sleep(struct msm_rpmrs_limits *limits, + bool from_idle, bool notify_rpm) +{ + + /* Disable L2 for now, we dont want L2 to do retention by default */ + msm_spm_l2_set_low_power_mode(MSM_SPM_MODE_DISABLED, notify_rpm); + + if (msm_rpmrs_use_mpm(limits)) + msm_mpm_exit_sleep(from_idle); +} + +#ifdef CONFIG_MSM_L2_SPM +static int rpmrs_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + switch (action) { + case CPU_ONLINE_FROZEN: + case CPU_ONLINE: + if (num_online_cpus() > 1) + msm_rpmrs_l2_cache.rs[0].value = + MSM_RPMRS_L2_CACHE_ACTIVE; + break; + case CPU_DEAD_FROZEN: + case CPU_DEAD: + if (num_online_cpus() == 1) + msm_rpmrs_l2_cache.rs[0].value = + MSM_RPMRS_L2_CACHE_HSFS_OPEN; + break; + } + + msm_rpmrs_update_levels(); + return NOTIFY_OK; +} + +static struct notifier_block __refdata rpmrs_cpu_notifier = { + .notifier_call = rpmrs_cpu_callback, +}; +#endif + +int __init msm_rpmrs_levels_init(struct msm_rpmrs_level *levels, int size) +{ + msm_rpmrs_levels = kzalloc(sizeof(struct msm_rpmrs_level) * size, + GFP_KERNEL); + if (!msm_rpmrs_levels) + return -ENOMEM; + msm_rpmrs_level_count = size; + memcpy(msm_rpmrs_levels, levels, size * sizeof(struct msm_rpmrs_level)); + + return 0; +} + +static int __init msm_rpmrs_init(void) +{ + struct msm_rpm_iv_pair req; + int rc; + + BUG_ON(!msm_rpmrs_levels); + + if (machine_is_msm8x60_surf() || machine_is_msm8x60_ffa() || + machine_is_msm8x60_fluid() || machine_is_msm8x60_fusion() || + machine_is_msm8x60_fusn_ffa()) { + + req.id = MSM_RPMRS_ID_APPS_L2_CACHE_CTL; + req.value = 1; + + rc = msm_rpm_set(MSM_RPM_CTX_SET_0, &req, 1); + if (rc) { + pr_err("%s: failed to request L2 cache: %d\n", + __func__, rc); + goto init_exit; + } + + req.id = MSM_RPMRS_ID_APPS_L2_CACHE_CTL; + req.value = 0; + + rc = msm_rpmrs_set(MSM_RPM_CTX_SET_SLEEP, &req, 1); + if (rc) { + pr_err("%s: failed to initialize L2 cache for sleep: " + "%d\n", __func__, rc); + goto init_exit; + } + } + + req.id = MSM_RPMRS_ID_RPM_CTL; + req.value = 0; + + rc = msm_rpmrs_set(MSM_RPM_CTX_SET_SLEEP, &req, 1); + if (rc) { + pr_err("%s: failed to initialize RPM CPU for sleep: %d\n", + __func__, rc); + goto init_exit; + } + + rc = msm_rpmrs_resource_sysfs_add(); + +init_exit: + return rc; +} +device_initcall(msm_rpmrs_init); + +static int __init msm_rpmrs_early_init(void) +{ + int i, k; + + /* Initialize listed bitmap for valid resource IDs */ + for (i = 0; i < ARRAY_SIZE(msm_rpmrs_resources); i++) { + for (k = 0; k < msm_rpmrs_resources[i]->size; k++) + set_bit(msm_rpmrs_resources[i]->rs[k].id, + msm_rpmrs_listed); + } + + return 0; +} +early_initcall(msm_rpmrs_early_init); + +#ifdef CONFIG_MSM_L2_SPM +static int __init msm_rpmrs_l2_counter_init(void) +{ + msm_rpmrs_l2_counter_addr = MSM_IMEM_BASE + L2_PC_COUNTER_ADDR; + writel_relaxed(msm_rpmrs_l2_reset_count, msm_rpmrs_l2_counter_addr); + mb(); + + msm_rpmrs_l2_cache.beyond_limits = msm_spm_l2_cache_beyond_limits; + msm_rpmrs_l2_cache.aggregate = NULL; + msm_rpmrs_l2_cache.restore = NULL; + + register_hotcpu_notifier(&rpmrs_cpu_notifier); + + return 0; +} +early_initcall(msm_rpmrs_l2_counter_init); +#endif diff --git a/arch/arm/mach-msm/rpm_resources.h b/arch/arm/mach-msm/rpm_resources.h new file mode 100644 index 00000000000..49e4a84831b --- /dev/null +++ b/arch/arm/mach-msm/rpm_resources.h @@ -0,0 +1,124 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPM_RESOURCES_H +#define __ARCH_ARM_MACH_MSM_RPM_RESOURCES_H + +#include +#include "pm.h" + + +struct msm_rpmrs_limits { + uint32_t pxo; + uint32_t l2_cache; + uint32_t vdd_mem_upper_bound; + uint32_t vdd_mem; + uint32_t vdd_dig_upper_bound; + uint32_t vdd_dig; + + uint32_t latency_us[NR_CPUS]; + uint32_t power[NR_CPUS]; +}; + +enum { + MSM_RPMRS_PXO_OFF = 0, + MSM_RPMRS_PXO_ON = 1, +}; + +enum { + MSM_RPMRS_L2_CACHE_HSFS_OPEN = 0, + MSM_RPMRS_L2_CACHE_GDHS = 1, + MSM_RPMRS_L2_CACHE_RETENTION = 2, + MSM_RPMRS_L2_CACHE_ACTIVE = 3, +}; + +enum { + MSM_RPMRS_VDD_MEM_RET_LOW = 500, + MSM_RPMRS_VDD_MEM_RET_HIGH = 750, + MSM_RPMRS_VDD_MEM_ACTIVE = 1000, + MSM_RPMRS_VDD_MEM_MAX = 1250, +}; + +enum { + MSM_RPMRS_VDD_DIG_RET_LOW = 500, + MSM_RPMRS_VDD_DIG_RET_HIGH = 750, + MSM_RPMRS_VDD_DIG_ACTIVE = 1000, + MSM_RPMRS_VDD_DIG_MAX = 1250, +}; + +#define MSM_RPMRS_LIMITS(_pxo, _l2, _vdd_upper_b, _vdd) { \ + MSM_RPMRS_PXO_##_pxo, \ + MSM_RPMRS_L2_CACHE_##_l2, \ + MSM_RPMRS_VDD_MEM_##_vdd_upper_b, \ + MSM_RPMRS_VDD_MEM_##_vdd, \ + MSM_RPMRS_VDD_DIG_##_vdd_upper_b, \ + MSM_RPMRS_VDD_DIG_##_vdd, \ + {0}, {0}, \ +} + +struct msm_rpmrs_level { + enum msm_pm_sleep_mode sleep_mode; + struct msm_rpmrs_limits rs_limits; + bool available; + uint32_t latency_us; + uint32_t steady_state_power; + uint32_t energy_overhead; + uint32_t time_overhead_us; +}; + +int msm_rpmrs_set(int ctx, struct msm_rpm_iv_pair *req, int count); +int msm_rpmrs_set_noirq(int ctx, struct msm_rpm_iv_pair *req, int count); + +static inline int msm_rpmrs_set_nosleep( + int ctx, struct msm_rpm_iv_pair *req, int count) +{ + unsigned long flags; + int rc; + + local_irq_save(flags); + rc = msm_rpmrs_set_noirq(ctx, req, count); + local_irq_restore(flags); + + return rc; +} + +int msm_rpmrs_clear(int ctx, struct msm_rpm_iv_pair *req, int count); +int msm_rpmrs_clear_noirq(int ctx, struct msm_rpm_iv_pair *req, int count); + +static inline int msm_rpmrs_clear_nosleep( + int ctx, struct msm_rpm_iv_pair *req, int count) +{ + unsigned long flags; + int rc; + + local_irq_save(flags); + rc = msm_rpmrs_clear_noirq(ctx, req, count); + local_irq_restore(flags); + + return rc; +} + +void msm_rpmrs_show_resources(void); + +struct msm_rpmrs_limits *msm_rpmrs_lowest_limits( + bool from_idle, enum msm_pm_sleep_mode sleep_mode, uint32_t latency_us, + uint32_t sleep_us); + +int msm_rpmrs_enter_sleep(uint32_t sclk_count, struct msm_rpmrs_limits *limits, + bool from_idle, bool notify_rpm); +void msm_rpmrs_exit_sleep(struct msm_rpmrs_limits *limits, + bool from_idle, bool notify_rpm); + +int msm_rpmrs_levels_init(struct msm_rpmrs_level *levels, int size); + +#endif /* __ARCH_ARM_MACH_MSM_RPM_RESOURCES_H */ diff --git a/arch/arm/mach-msm/rpm_stats.c b/arch/arm/mach-msm/rpm_stats.c new file mode 100644 index 00000000000..a831bd5a35f --- /dev/null +++ b/arch/arm/mach-msm/rpm_stats.c @@ -0,0 +1,247 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "rpm_stats.h" + +enum { + ID_COUNTER, + ID_ACCUM_TIME_SCLK, + ID_MAX, +}; + +static char *msm_rpmstats_id_labels[ID_MAX] = { + [ID_COUNTER] = "Count", + [ID_ACCUM_TIME_SCLK] = "Total time(uSec)", +}; + +#define SCLK_HZ 32768 +struct msm_rpmstats_record{ + char name[32]; + uint32_t id; + uint32_t val; +}; + +struct msm_rpmstats_private_data{ + void __iomem *reg_base; + u32 num_records; + u32 read_idx; + u32 len; + char buf[128]; + struct msm_rpmstats_platform_data *platform_data; +}; + +static inline unsigned long msm_rpmstats_read_register(void __iomem *regbase, + int index, int offset) +{ + return readl_relaxed(regbase + index * 12 + (offset + 1) * 4); +} +static void msm_rpmstats_strcpy(char *dest, char *src) +{ + union { + char ch[4]; + unsigned long word; + } string; + int index = 0; + + do { + int i; + string.word = readl_relaxed(src + 4 * index); + for (i = 0; i < 4; i++) { + *dest++ = string.ch[i]; + if (!string.ch[i]) + break; + } + index++; + } while (*(dest-1)); + +} +static int msm_rpmstats_copy_stats(struct msm_rpmstats_private_data *pdata) +{ + + struct msm_rpmstats_record record; + unsigned long ptr; + unsigned long offset; + char *str; + uint64_t usec; + + ptr = msm_rpmstats_read_register(pdata->reg_base, pdata->read_idx, 0); + offset = (ptr - (unsigned long)pdata->platform_data->phys_addr_base); + + if (offset > pdata->platform_data->phys_size) + str = (char *)ioremap(ptr, SZ_256); + else + str = (char *) pdata->reg_base + offset; + + msm_rpmstats_strcpy(record.name, str); + + if (offset > pdata->platform_data->phys_size) + iounmap(str); + + record.id = msm_rpmstats_read_register(pdata->reg_base, + pdata->read_idx, 1); + record.val = msm_rpmstats_read_register(pdata->reg_base, + pdata->read_idx, 2); + + if (record.id == ID_ACCUM_TIME_SCLK) { + usec = record.val * USEC_PER_SEC; + do_div(usec, SCLK_HZ); + } else + usec = (unsigned long)record.val; + + pdata->read_idx++; + + return snprintf(pdata->buf, sizeof(pdata->buf), + "RPM Mode:%s\n\t%s:%llu\n", + record.name, + msm_rpmstats_id_labels[record.id], + usec); +} + +static int msm_rpmstats_file_read(struct file *file, char __user *bufu, + size_t count, loff_t *ppos) +{ + struct msm_rpmstats_private_data *prvdata; + prvdata = file->private_data; + + if (!prvdata) + return -EINVAL; + + if (!bufu || count < 0) + return -EINVAL; + + if (!prvdata->num_records) + prvdata->num_records = readl_relaxed(prvdata->reg_base); + + if ((*ppos >= prvdata->len) + && (prvdata->read_idx < prvdata->num_records)) { + prvdata->len = msm_rpmstats_copy_stats(prvdata); + *ppos = 0; + } + + return simple_read_from_buffer(bufu, count, ppos, + prvdata->buf, prvdata->len); +} + +static int msm_rpmstats_file_open(struct inode *inode, struct file *file) +{ + struct msm_rpmstats_private_data *prvdata; + struct msm_rpmstats_platform_data *pdata; + + pdata = inode->i_private; + + file->private_data = + kmalloc(sizeof(struct msm_rpmstats_private_data), GFP_KERNEL); + + if (!file->private_data) + return -ENOMEM; + prvdata = file->private_data; + + prvdata->reg_base = ioremap(pdata->phys_addr_base, pdata->phys_size); + if (!prvdata->reg_base) { + kfree(file->private_data); + prvdata = NULL; + pr_err("%s: ERROR could not ioremap start=%p, len=%u\n", + __func__, (void *)pdata->phys_addr_base, + pdata->phys_size); + return -EBUSY; + } + + prvdata->read_idx = prvdata->num_records = prvdata->len = 0; + prvdata->platform_data = pdata; + return 0; +} + +static int msm_rpmstats_file_close(struct inode *inode, struct file *file) +{ + struct msm_rpmstats_private_data *private = file->private_data; + + if (private->reg_base) + iounmap(private->reg_base); + kfree(file->private_data); + + return 0; +} + +static const struct file_operations msm_rpmstats_fops = { + .owner = THIS_MODULE, + .open = msm_rpmstats_file_open, + .read = msm_rpmstats_file_read, + .release = msm_rpmstats_file_close, + .llseek = no_llseek, +}; + +static int __devinit msm_rpmstats_probe(struct platform_device *pdev) +{ + struct dentry *dent; + struct msm_rpmstats_platform_data *pdata; + + pdata = pdev->dev.platform_data; + if (!pdata) + return -EINVAL; + dent = debugfs_create_file("rpm_stats", S_IRUGO, NULL, + pdev->dev.platform_data, &msm_rpmstats_fops); + + if (!dent) { + pr_err("%s: ERROR debugfs_create_file failed\n", __func__); + return -ENOMEM; + } + platform_set_drvdata(pdev, dent); + return 0; +} + +static int __devexit msm_rpmstats_remove(struct platform_device *pdev) +{ + struct dentry *dent; + + dent = platform_get_drvdata(pdev); + debugfs_remove(dent); + platform_set_drvdata(pdev, NULL); + return 0; +} +static struct platform_driver msm_rpmstats_driver = { + .probe = msm_rpmstats_probe, + .remove = __devexit_p(msm_rpmstats_remove), + .driver = { + .name = "msm_rpm_stat", + .owner = THIS_MODULE, + }, +}; +static int __init msm_rpmstats_init(void) +{ + return platform_driver_register(&msm_rpmstats_driver); +} +static void __exit msm_rpmstats_exit(void) +{ + platform_driver_unregister(&msm_rpmstats_driver); +} +module_init(msm_rpmstats_init); +module_exit(msm_rpmstats_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM RPM Statistics driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:msm_stat_log"); diff --git a/arch/arm/mach-msm/rpm_stats.h b/arch/arm/mach-msm/rpm_stats.h new file mode 100644 index 00000000000..918d4fba7af --- /dev/null +++ b/arch/arm/mach-msm/rpm_stats.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ARCH_ARM_MACH_MSM_RPM_STATS_H +#define __ARCH_ARM_MACH_MSM_RPM_STATS_H + +#include + +struct msm_rpmstats_platform_data { + phys_addr_t phys_addr_base; + u32 phys_size; +}; +#endif diff --git a/arch/arm/mach-msm/saw-regulator.c b/arch/arm/mach-msm/saw-regulator.c new file mode 100644 index 00000000000..b11b1fa2cae --- /dev/null +++ b/arch/arm/mach-msm/saw-regulator.c @@ -0,0 +1,236 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spm.h" + +#define FTSMPS_VCTRL_BAND_MASK 0xC0 +#define FTSMPS_VCTRL_BAND_1 0x40 +#define FTSMPS_VCTRL_BAND_2 0x80 +#define FTSMPS_VCTRL_BAND_3 0xC0 +#define FTSMPS_VCTRL_VPROG_MASK 0x3F + +#define FTSMPS_BAND1_UV_MIN 350000 +#define FTSMPS_BAND1_UV_MAX 650000 +/* 3 LSB's of program voltage must be 0 in band 1. */ +/* Logical step size */ +#define FTSMPS_BAND1_UV_LOG_STEP 50000 +/* Physical step size */ +#define FTSMPS_BAND1_UV_PHYS_STEP 6250 + +#define FTSMPS_BAND2_UV_MIN 700000 +#define FTSMPS_BAND2_UV_MAX 1400000 +#define FTSMPS_BAND2_UV_STEP 12500 + +#define FTSMPS_BAND3_UV_MIN 1400000 +#define FTSMPS_BAND3_UV_SET_POINT_MIN 1500000 +#define FTSMPS_BAND3_UV_MAX 3300000 +#define FTSMPS_BAND3_UV_STEP 50000 + +struct saw_vreg { + struct regulator_desc desc; + struct regulator_dev *rdev; + char *name; + int uV; +}; + +/* Minimum core operating voltage */ +#define MIN_CORE_VOLTAGE 950000 + +/* Specifies the PMIC internal slew rate in uV/us. */ +#define REGULATOR_SLEW_RATE 1250 + +static int saw_get_voltage(struct regulator_dev *rdev) +{ + struct saw_vreg *vreg = rdev_get_drvdata(rdev); + + return vreg->uV; +} + +static int saw_set_voltage(struct regulator_dev *rdev, int min_uV, int max_uV, + unsigned *selector) +{ + struct saw_vreg *vreg = rdev_get_drvdata(rdev); + int uV = min_uV; + int rc; + u8 vprog, band; + + if (uV < FTSMPS_BAND1_UV_MIN && max_uV >= FTSMPS_BAND1_UV_MIN) + uV = FTSMPS_BAND1_UV_MIN; + + if (uV < FTSMPS_BAND1_UV_MIN || uV > FTSMPS_BAND3_UV_MAX) { + pr_err("%s: request v=[%d, %d] is outside possible " + "v=[%d, %d]\n", vreg->name, min_uV, max_uV, + FTSMPS_BAND1_UV_MIN, FTSMPS_BAND3_UV_MAX); + return -EINVAL; + } + + /* Round up for set points in the gaps between bands. */ + if (uV > FTSMPS_BAND1_UV_MAX && uV < FTSMPS_BAND2_UV_MIN) + uV = FTSMPS_BAND2_UV_MIN; + else if (uV > FTSMPS_BAND2_UV_MAX + && uV < FTSMPS_BAND3_UV_SET_POINT_MIN) + uV = FTSMPS_BAND3_UV_SET_POINT_MIN; + + if (uV > FTSMPS_BAND2_UV_MAX) { + vprog = (uV - FTSMPS_BAND3_UV_MIN + FTSMPS_BAND3_UV_STEP - 1) + / FTSMPS_BAND3_UV_STEP; + band = FTSMPS_VCTRL_BAND_3; + uV = FTSMPS_BAND3_UV_MIN + vprog * FTSMPS_BAND3_UV_STEP; + } else if (uV > FTSMPS_BAND1_UV_MAX) { + vprog = (uV - FTSMPS_BAND2_UV_MIN + FTSMPS_BAND2_UV_STEP - 1) + / FTSMPS_BAND2_UV_STEP; + band = FTSMPS_VCTRL_BAND_2; + uV = FTSMPS_BAND2_UV_MIN + vprog * FTSMPS_BAND2_UV_STEP; + } else { + vprog = (uV - FTSMPS_BAND1_UV_MIN + + FTSMPS_BAND1_UV_LOG_STEP - 1) + / FTSMPS_BAND1_UV_LOG_STEP; + uV = FTSMPS_BAND1_UV_MIN + vprog * FTSMPS_BAND1_UV_LOG_STEP; + vprog *= FTSMPS_BAND1_UV_LOG_STEP / FTSMPS_BAND1_UV_PHYS_STEP; + band = FTSMPS_VCTRL_BAND_1; + } + + if (uV > max_uV) { + pr_err("%s: request v=[%d, %d] cannot be met by any setpoint\n", + vreg->name, min_uV, max_uV); + return -EINVAL; + } + + rc = msm_spm_set_vdd(rdev_get_id(rdev), band | vprog); + if (!rc) { + if (uV > vreg->uV) { + /* Wait for voltage to stabalize. */ + udelay((uV - vreg->uV) / REGULATOR_SLEW_RATE); + } + vreg->uV = uV; + } else { + pr_err("%s: msm_spm_set_vdd failed %d\n", vreg->name, rc); + } + + return rc; +} + +static struct regulator_ops saw_ops = { + .get_voltage = saw_get_voltage, + .set_voltage = saw_set_voltage, +}; + +static int __devinit saw_probe(struct platform_device *pdev) +{ + struct regulator_init_data *init_data; + struct saw_vreg *vreg; + int rc = 0; + + if (!pdev->dev.platform_data) { + pr_err("platform data required.\n"); + return -EINVAL; + } + + init_data = pdev->dev.platform_data; + if (!init_data->constraints.name) { + pr_err("regulator name must be specified in constraints.\n"); + return -EINVAL; + } + + vreg = kzalloc(sizeof(struct saw_vreg), GFP_KERNEL); + if (!vreg) { + pr_err("kzalloc failed.\n"); + return -ENOMEM; + } + + vreg->name = kstrdup(init_data->constraints.name, GFP_KERNEL); + if (!vreg->name) { + pr_err("kzalloc failed.\n"); + rc = -ENOMEM; + goto free_vreg; + } + + vreg->desc.name = vreg->name; + vreg->desc.id = pdev->id; + vreg->desc.ops = &saw_ops; + vreg->desc.type = REGULATOR_VOLTAGE; + vreg->desc.owner = THIS_MODULE; + vreg->uV = MIN_CORE_VOLTAGE; + + vreg->rdev = regulator_register(&vreg->desc, &pdev->dev, init_data, + vreg); + if (IS_ERR(vreg->rdev)) { + rc = PTR_ERR(vreg->rdev); + pr_err("regulator_register failed, rc=%d.\n", rc); + goto free_name; + } + + platform_set_drvdata(pdev, vreg); + + pr_info("id=%d, name=%s\n", pdev->id, vreg->name); + + return rc; + +free_name: + kfree(vreg->name); +free_vreg: + kfree(vreg); + + return rc; +} + +static int __devexit saw_remove(struct platform_device *pdev) +{ + struct saw_vreg *vreg = platform_get_drvdata(pdev); + + regulator_unregister(vreg->rdev); + kfree(vreg->name); + kfree(vreg); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver saw_driver = { + .probe = saw_probe, + .remove = __devexit_p(saw_remove), + .driver = { + .name = "saw-regulator", + .owner = THIS_MODULE, + }, +}; + +static int __init saw_init(void) +{ + return platform_driver_register(&saw_driver); +} + +static void __exit saw_exit(void) +{ + platform_driver_unregister(&saw_driver); +} + +postcore_initcall(saw_init); +module_exit(saw_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SAW regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:saw-regulator"); diff --git a/arch/arm/mach-msm/scm-boot.c b/arch/arm/mach-msm/scm-boot.c index 45cee3e469a..e377633255d 100644 --- a/arch/arm/mach-msm/scm-boot.c +++ b/arch/arm/mach-msm/scm-boot.c @@ -8,27 +8,22 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include #include -#include "scm.h" +#include #include "scm-boot.h" /* * Set the cold/warm boot address for one of the CPU cores. */ -int scm_set_boot_addr(phys_addr_t addr, int flags) +int scm_set_boot_addr(void *addr, int flags) { struct { unsigned int flags; - phys_addr_t addr; + void *addr; } cmd; cmd.addr = addr; @@ -37,3 +32,4 @@ int scm_set_boot_addr(phys_addr_t addr, int flags) &cmd, sizeof(cmd), NULL, 0); } EXPORT_SYMBOL(scm_set_boot_addr); + diff --git a/arch/arm/mach-msm/scm-boot.h b/arch/arm/mach-msm/scm-boot.h index 7be32ff5d68..c2c473b40bc 100644 --- a/arch/arm/mach-msm/scm-boot.h +++ b/arch/arm/mach-msm/scm-boot.h @@ -17,6 +17,6 @@ #define SCM_FLAG_WARMBOOT_CPU1 0x2 #define SCM_FLAG_WARMBOOT_CPU0 0x4 -int scm_set_boot_addr(phys_addr_t addr, int flags); +int scm_set_boot_addr(void *addr, int flags); #endif diff --git a/arch/arm/mach-msm/scm-io.c b/arch/arm/mach-msm/scm-io.c new file mode 100644 index 00000000000..0fc068a6216 --- /dev/null +++ b/arch/arm/mach-msm/scm-io.c @@ -0,0 +1,86 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include +#include + +#define SCM_IO_READ ((((0x5 << 10) | 0x1) << 12) | (0x2 << 8) | 0x1) +#define SCM_IO_WRITE ((((0x5 << 10) | 0x2) << 12) | (0x2 << 8) | 0x2) + +#define BETWEEN(p, st, sz) ((p) >= (void __iomem *)(st) && \ + (p) < ((void __iomem *)(st) + (sz))) +#define XLATE(p, pst, vst) ((u32)((p) - (vst)) + (pst)) + +static u32 __secure_readl(u32 addr) +{ + u32 context_id; + register u32 r0 asm("r0") = SCM_IO_READ; + register u32 r1 asm("r1") = (u32)&context_id; + register u32 r2 asm("r2") = addr; + asm( + __asmeq("%0", "r0") + __asmeq("%1", "r0") + __asmeq("%2", "r1") + __asmeq("%3", "r2") + "smc #0 @ switch to secure world\n" + : "=r" (r0) + : "r" (r0), "r" (r1), "r" (r2) + ); + __iormb(); + return r0; +} + +u32 secure_readl(void __iomem *c) +{ + if (BETWEEN(c, MSM_MMSS_CLK_CTL_BASE, MSM_MMSS_CLK_CTL_SIZE)) + return __secure_readl(XLATE(c, MSM_MMSS_CLK_CTL_PHYS, + MSM_MMSS_CLK_CTL_BASE)); + else if (BETWEEN(c, MSM_TCSR_BASE, MSM_TCSR_SIZE)) + return __secure_readl(XLATE(c, MSM_TCSR_PHYS, MSM_TCSR_BASE)); + return readl(c); +} +EXPORT_SYMBOL(secure_readl); + +static void __secure_writel(u32 v, u32 addr) +{ + u32 context_id; + register u32 r0 asm("r0") = SCM_IO_WRITE; + register u32 r1 asm("r1") = (u32)&context_id; + register u32 r2 asm("r2") = addr; + register u32 r3 asm("r3") = v; + + __iowmb(); + asm( + __asmeq("%0", "r0") + __asmeq("%1", "r1") + __asmeq("%2", "r2") + __asmeq("%3", "r3") + "smc #0 @ switch to secure world\n" + : /* No return value */ + : "r" (r0), "r" (r1), "r" (r2), "r" (r3) + ); +} + +void secure_writel(u32 v, void __iomem *c) +{ + if (BETWEEN(c, MSM_MMSS_CLK_CTL_BASE, MSM_MMSS_CLK_CTL_SIZE)) + __secure_writel(v, XLATE(c, MSM_MMSS_CLK_CTL_PHYS, + MSM_MMSS_CLK_CTL_BASE)); + else if (BETWEEN(c, MSM_TCSR_BASE, MSM_TCSR_SIZE)) + __secure_writel(v, XLATE(c, MSM_TCSR_PHYS, MSM_TCSR_BASE)); + else + writel(v, c); +} +EXPORT_SYMBOL(secure_writel); diff --git a/arch/arm/mach-msm/scm.c b/arch/arm/mach-msm/scm.c index 232f97a0450..737ba76a152 100644 --- a/arch/arm/mach-msm/scm.c +++ b/arch/arm/mach-msm/scm.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include @@ -21,13 +16,11 @@ #include #include #include +#include #include -#include "scm.h" - -/* Cache line size for msm8x60 */ -#define CACHELINESIZE 32 +#include #define SCM_ENOMEM -5 #define SCM_EOPNOTSUPP -4 @@ -207,6 +200,21 @@ static int __scm_call(const struct scm_command *cmd) return ret; } +static u32 cacheline_size; + +static void scm_inv_range(unsigned long start, unsigned long end) +{ + start = round_down(start, cacheline_size); + end = round_up(end, cacheline_size); + while (start < end) { + asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start) + : "memory"); + start += cacheline_size; + } + dsb(); + isb(); +} + /** * scm_call() - Send an SCM command * @svc_id: service identifier @@ -224,6 +232,7 @@ int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, size_t cmd_len, int ret; struct scm_command *cmd; struct scm_response *rsp; + unsigned long start, end; cmd = alloc_scm_command(cmd_len, resp_len); if (!cmd) @@ -240,17 +249,15 @@ int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, size_t cmd_len, goto out; rsp = scm_command_to_response(cmd); + start = (unsigned long)rsp; + do { - u32 start = (u32)rsp; - u32 end = (u32)scm_get_response_buffer(rsp) + resp_len; - start &= ~(CACHELINESIZE - 1); - while (start < end) { - asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start) - : "memory"); - start += CACHELINESIZE; - } + scm_inv_range(start, start + sizeof(*rsp)); } while (!rsp->is_complete); + end = (unsigned long)scm_get_response_buffer(rsp) + resp_len; + scm_inv_range(start, end); + if (resp_buf) memcpy(resp_buf, scm_get_response_buffer(rsp), resp_len); out: @@ -291,3 +298,14 @@ u32 scm_get_version(void) return version; } EXPORT_SYMBOL(scm_get_version); + +static int scm_init(void) +{ + u32 ctr; + + asm volatile("mrc p15, 0, %0, c0, c0, 1" : "=r" (ctr)); + cacheline_size = 4 << ((ctr >> 16) & 0xf); + + return 0; +} +early_initcall(scm_init); diff --git a/arch/arm/mach-msm/sdio_al.c b/arch/arm/mach-msm/sdio_al.c new file mode 100644 index 00000000000..53b358b7dec --- /dev/null +++ b/arch/arm/mach-msm/sdio_al.c @@ -0,0 +1,3526 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * SDIO-Abstraction-Layer Module. + * + * To be used with Qualcomm's SDIO-Client connected to this host. + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../../../drivers/mmc/host/msm_sdcc.h" + +/** + * Func#0 has SDIO standard registers + * Func#1 is for Mailbox. + * Functions 2..7 are for channels. + * Currently only functions 2..5 are active due to SDIO-Client + * number of pipes. + * + */ +#define SDIO_AL_MAX_CHANNELS 6 + +/** Func 1..5 */ +#define SDIO_AL_MAX_FUNCS (SDIO_AL_MAX_CHANNELS+1) +#define SDIO_AL_WAKEUP_FUNC 6 + +/** Number of SDIO-Client pipes */ +#define SDIO_AL_MAX_PIPES 16 +#define SDIO_AL_ACTIVE_PIPES 8 + +/** CMD53/CMD54 Block size */ +#define SDIO_AL_BLOCK_SIZE 128 + +/** Func#1 hardware Mailbox base address */ +#define HW_MAILBOX_ADDR 0x1000 + +/** Func#1 peer sdioc software version. + * The header is duplicated also to the mailbox of the other + * functions. It can be used before other functions are enabled. */ +#define SDIOC_SW_HEADER_ADDR 0x0400 + +/** Func#2..7 software Mailbox base address at 16K */ +#define SDIOC_SW_MAILBOX_ADDR 0x4000 + +/** Some Mailbox registers address, written by host for + control */ +#define PIPES_THRESHOLD_ADDR 0x01000 + +#define PIPES_0_7_IRQ_MASK_ADDR 0x01048 + +#define PIPES_8_15_IRQ_MASK_ADDR 0x0104C + +#define FUNC_1_4_MASK_IRQ_ADDR 0x01040 +#define FUNC_5_7_MASK_IRQ_ADDR 0x01044 +#define FUNC_1_4_USER_IRQ_ADDR 0x01050 +#define FUNC_5_7_USER_IRQ_ADDR 0x01054 + +#define EOT_PIPES_ENABLE 0x00 + +/** Maximum read/write data available is SDIO-Client limitation */ +#define MAX_DATA_AVAILABLE (16*1024) +#define INVALID_DATA_AVAILABLE (0x8000) + +/** SDIO-Client HW threshold to generate interrupt to the + * SDIO-Host on write available bytes. + */ +#define DEFAULT_WRITE_THRESHOLD (1024) + +/** SDIO-Client HW threshold to generate interrupt to the + * SDIO-Host on read available bytes, for streaming (non + * packet) rx data. + */ +#define DEFAULT_READ_THRESHOLD (1024) + +/** SW threshold to trigger reading the mailbox. */ +#define DEFAULT_MIN_WRITE_THRESHOLD (1024) +#define DEFAULT_MIN_WRITE_THRESHOLD_STREAMING (1600) + +#define THRESHOLD_DISABLE_VAL (0xFFFFFFFF) + + +/** Mailbox polling time for packet channels */ +#define DEFAULT_POLL_DELAY_MSEC 10 +/** Mailbox polling time for streaming channels */ +#define DEFAULT_POLL_DELAY_NOPACKET_MSEC 30 + +/** The SDIO-Client prepares N buffers of size X per Tx pipe. + * Even when the transfer fills a partial buffer, + * that buffer becomes unusable for the next transfer. */ +#define DEFAULT_PEER_TX_BUF_SIZE (128) + +#define ROUND_UP(x, n) (((x + n - 1) / n) * n) + +/** Func#2..7 FIFOs are r/w via + sdio_readsb() & sdio_writesb(),when inc_addr=0 */ +#define PIPE_RX_FIFO_ADDR 0x00 +#define PIPE_TX_FIFO_ADDR 0x00 + +/** Inactivity time to go to sleep in mseconds */ +#define INACTIVITY_TIME_MSEC 30 +#define INITIAL_INACTIVITY_TIME_MSEC 5000 + +/** Context validity check */ +#define SDIO_AL_SIGNATURE 0xAABBCCDD + +/* Vendor Specific Command */ +#define SD_IO_RW_EXTENDED_QCOM 54 + +#define TIME_TO_WAIT_US 500 + +#define SDIO_TEST_POSTFIX "_TEST" + +#define DATA_DEBUG(x...) if (sdio_al->debug.debug_data_on) pr_info(x) +#define LPM_DEBUG(x...) if (sdio_al->debug.debug_lpm_on) pr_info(x) + + +/* The index of the SDIO card used for the sdio_al_dloader */ +#define SDIO_BOOTLOADER_CARD_INDEX 1 + + +/* SDIO card state machine */ +enum sdio_al_device_state { + CARD_INSERTED, + CARD_REMOVED, + MODEM_RESTART +}; + +struct sdio_al_debug { + + u8 debug_lpm_on; + u8 debug_data_on; + struct dentry *sdio_al_debug_root; + struct dentry *sdio_al_debug_lpm_on; + struct dentry *sdio_al_debug_data_on; + struct dentry *sdio_al_debug_info; +}; + +/* Polling time for the inactivity timer for devices that doesn't have + * a streaming channel + */ +#define SDIO_AL_POLL_TIME_NO_STREAMING 30 + +#define CHAN_TO_FUNC(x) ((x) + 2 - 1) + +/** + * Mailbox structure. + * The Mailbox is located on the SDIO-Client Function#1. + * The mailbox size is 128 bytes, which is one block. + * The mailbox allows the host ton: + * 1. Get the number of available bytes on the pipes. + * 2. Enable/Disable SDIO-Client interrupt, related to pipes. + * 3. Set the Threshold for generating interrupt. + * + */ +struct sdio_mailbox { + u32 pipe_bytes_threshold[SDIO_AL_MAX_PIPES]; /* Addr 0x1000 */ + + /* Mask USER interrupts generated towards host - Addr 0x1040 */ + u32 mask_irq_func_1:8; /* LSB */ + u32 mask_irq_func_2:8; + u32 mask_irq_func_3:8; + u32 mask_irq_func_4:8; + + u32 mask_irq_func_5:8; + u32 mask_irq_func_6:8; + u32 mask_irq_func_7:8; + u32 mask_mutex_irq:8; + + /* Mask PIPE interrupts generated towards host - Addr 0x1048 */ + u32 mask_eot_pipe_0_7:8; + u32 mask_thresh_above_limit_pipe_0_7:8; + u32 mask_overflow_pipe_0_7:8; + u32 mask_underflow_pipe_0_7:8; + + u32 mask_eot_pipe_8_15:8; + u32 mask_thresh_above_limit_pipe_8_15:8; + u32 mask_overflow_pipe_8_15:8; + u32 mask_underflow_pipe_8_15:8; + + /* Status of User interrupts generated towards host - Addr 0x1050 */ + u32 user_irq_func_1:8; + u32 user_irq_func_2:8; + u32 user_irq_func_3:8; + u32 user_irq_func_4:8; + + u32 user_irq_func_5:8; + u32 user_irq_func_6:8; + u32 user_irq_func_7:8; + u32 user_mutex_irq:8; + + /* Status of PIPE interrupts generated towards host */ + /* Note: All sources are cleared once they read. - Addr 0x1058 */ + u32 eot_pipe_0_7:8; + u32 thresh_above_limit_pipe_0_7:8; + u32 overflow_pipe_0_7:8; + u32 underflow_pipe_0_7:8; + + u32 eot_pipe_8_15:8; + u32 thresh_above_limit_pipe_8_15:8; + u32 overflow_pipe_8_15:8; + u32 underflow_pipe_8_15:8; + + u16 pipe_bytes_avail[SDIO_AL_MAX_PIPES]; +}; + +/** Track pending Rx Packet size */ +struct rx_packet_size { + u32 size; /* in bytes */ + struct list_head list; +}; + +#define PEER_SDIOC_SW_MAILBOX_SIGNATURE 0xFACECAFE +#define PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE 0x5D107E57 +#define PEER_SDIOC_SW_MAILBOX_BOOT_SIGNATURE 0xDEADBEEF + +/* Allow support in old sdio version */ +#define PEER_SDIOC_OLD_VERSION_MAJOR 0x0002 + +#define MAX_NUM_OF_SDIO_DEVICES 2 + +#define INVALID_SDIO_CHAN 0xFF + +/** + * Peer SDIO-Client software header. + */ +struct peer_sdioc_sw_header { + u32 signature; + u32 version; + u32 max_channels; + char channel_names[SDIO_AL_MAX_CHANNELS][PEER_CHANNEL_NAME_SIZE]; + u32 reserved[23]; +}; + +struct peer_sdioc_boot_sw_header { + u32 signature; + u32 version; + u32 boot_ch_num; + u32 reserved[29]; /* 32 - previous fields */ +}; + +/** + * Peer SDIO-Client software mailbox. + */ +struct peer_sdioc_sw_mailbox { + struct peer_sdioc_sw_header sw_header; + struct peer_sdioc_channel_config ch_config[SDIO_AL_MAX_CHANNELS]; +}; + +/** + * SDIO Abstraction Layer driver context. + * + * @pdata - + * @debug - + * @devices - an array of the the devices claimed by sdio_al + * @unittest_mode - a flag to indicate if sdio_al is in + * unittest mode + * @bootloader_dev - the device which is used for the + * bootloader + * @subsys_notif_handle - handle for modem restart + * notifications + * + */ +struct sdio_al { + struct sdio_al_platform_data *pdata; + struct sdio_al_debug debug; + struct sdio_al_device *devices[MAX_NUM_OF_SDIO_DEVICES]; + int unittest_mode; + struct sdio_al_device *bootloader_dev; + void *subsys_notif_handle; + int sdioc_major; +}; + +struct sdio_al_work { + struct work_struct work; + struct sdio_al_device *sdio_al_dev; +}; + + +/** + * SDIO Abstraction Layer device context. + * + * @card - card claimed. + * + * @mailbox - A shadow of the SDIO-Client mailbox. + * + * @channel - Channels context. + * + * @workqueue - workqueue to read the mailbox and handle + * pending requests. Reading the mailbox should not happen + * in interrupt context. + * + * @work - work to submit to workqueue. + * + * @is_ready - driver is ready. + * + * @ask_mbox - Flag to request reading the mailbox, + * for different reasons. + * + * @wake_lock - Lock when can't sleep. + * + * @lpm_chan - Channel to use for LPM (low power mode) + * communication. + * + * @is_ok_to_sleep - Mark if driver is OK with going to sleep + * (no pending transactions). + * + * @inactivity_time - time allowed to be in inactivity before + * going to sleep + * + * @timer - timer to use for polling the mailbox. + * + * @poll_delay_msec - timer delay for polling the mailbox. + * + * @is_err - error detected. + * + * @signature - Context Validity Check. + * + * @flashless_boot_on - flag to indicate if sdio_al is in + * flshless boot mode + * + */ +struct sdio_al_device { + struct mmc_card *card; + struct sdio_mailbox *mailbox; + struct sdio_channel channel[SDIO_AL_MAX_CHANNELS]; + + struct peer_sdioc_sw_header *sdioc_sw_header; + struct peer_sdioc_boot_sw_header *sdioc_boot_sw_header; + + struct workqueue_struct *workqueue; + struct sdio_al_work sdio_al_work; + struct sdio_al_work boot_work; + + int is_ready; + + wait_queue_head_t wait_mbox; + int ask_mbox; + int bootloader_done; + + struct wake_lock wake_lock; + int lpm_chan; + int is_ok_to_sleep; + unsigned long inactivity_time; + + struct timer_list timer; + u32 poll_delay_msec; + int is_timer_initialized; + + int is_err; + + u32 signature; + + unsigned int clock; + + unsigned int is_suspended; + + int flashless_boot_on; + + int state; + int (*lpm_callback)(void *, int); +}; + +/* + * On the kernel command line specify + * sdio_al.debug_lpm_on=1 to enable the LPM debug messages + * By default the LPM debug messages are turned off + */ +static int debug_lpm_on; +module_param(debug_lpm_on, int, 0); + +/* + * On the kernel command line specify + * sdio_al.debug_data_on=1 to enable the DATA debug messages + * By default the DATA debug messages are turned off + */ +static int debug_data_on; +module_param(debug_data_on, int, 0); + +/** The driver context */ +static struct sdio_al *sdio_al; + +/* Static functions declaration */ +static int enable_eot_interrupt(struct sdio_al_device *sdio_al_dev, + int pipe_index, int enable); +static int enable_threshold_interrupt(struct sdio_al_device *sdio_al_dev, + int pipe_index, int enable); +static void sdio_func_irq(struct sdio_func *func); +static void sdio_al_timer_handler(unsigned long data); +static int get_min_poll_time_msec(struct sdio_al_device *sdio_al_dev); +static u32 check_pending_rx_packet(struct sdio_channel *ch, u32 eot); +static u32 remove_handled_rx_packet(struct sdio_channel *ch); +static int set_pipe_threshold(struct sdio_al_device *sdio_al_dev, + int pipe_index, int threshold); +static int sdio_al_wake_up(struct sdio_al_device *sdio_al_dev, + u32 not_from_int); +static int sdio_al_client_setup(struct sdio_al_device *sdio_al_dev); +static int enable_mask_irq(struct sdio_al_device *sdio_al_dev, + int func_num, int enable, u8 bit_offset); +static int sdio_al_enable_func_retry(struct sdio_func *func, const char *name); +static void sdio_al_print_info(void); + +#define SDIO_AL_ERR(func) \ + do { \ + printk_once(KERN_ERR MODULE_NAME \ + ":In Error state, ignore %s\n", \ + func); \ + sdio_al_print_info(); \ + } while (0) + +#ifdef CONFIG_DEBUG_FS +static int debug_info_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t debug_info_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + sdio_al_print_info(); + return 1; +} + +const struct file_operations debug_info_ops = { + .open = debug_info_open, + .write = debug_info_write, +}; + +/* +* +* Trigger on/off for debug messages +* for trigger off the data messages debug level use: +* echo 0 > /sys/kernel/debugfs/sdio_al/debug_data_on +* for trigger on the data messages debug level use: +* echo 1 > /sys/kernel/debugfs/sdio_al/debug_data_on +* for trigger off the lpm messages debug level use: +* echo 0 > /sys/kernel/debugfs/sdio_al/debug_lpm_on +* for trigger on the lpm messages debug level use: +* echo 1 > /sys/kernel/debugfs/sdio_al/debug_lpm_on +*/ +static int sdio_al_debugfs_init(void) +{ + sdio_al->debug.sdio_al_debug_root = debugfs_create_dir("sdio_al", NULL); + if (!sdio_al->debug.sdio_al_debug_root) + return -ENOENT; + + sdio_al->debug.sdio_al_debug_lpm_on = debugfs_create_u8("debug_lpm_on", + S_IRUGO | S_IWUGO, + sdio_al->debug.sdio_al_debug_root, + &sdio_al->debug.debug_lpm_on); + + sdio_al->debug.sdio_al_debug_data_on = debugfs_create_u8( + "debug_data_on", + S_IRUGO | S_IWUGO, + sdio_al->debug.sdio_al_debug_root, + &sdio_al->debug.debug_data_on); + + sdio_al->debug.sdio_al_debug_info = debugfs_create_file( + "sdio_debug_info", + S_IRUGO | S_IWUGO, + sdio_al->debug.sdio_al_debug_root, + NULL, + &debug_info_ops); + + if ((!sdio_al->debug.sdio_al_debug_data_on) && + (!sdio_al->debug.sdio_al_debug_lpm_on) && + (!sdio_al->debug.sdio_al_debug_info) + ) { + debugfs_remove(sdio_al->debug.sdio_al_debug_root); + sdio_al->debug.sdio_al_debug_root = NULL; + return -ENOENT; + } + return 0; +} + +static void sdio_al_debugfs_cleanup(void) +{ + debugfs_remove(sdio_al->debug.sdio_al_debug_lpm_on); + debugfs_remove(sdio_al->debug.sdio_al_debug_data_on); + debugfs_remove(sdio_al->debug.sdio_al_debug_info); + debugfs_remove(sdio_al->debug.sdio_al_debug_root); +} +#endif + +static int sdio_al_verify_func1(struct sdio_al_device *sdio_al_dev, + char const *func) +{ + if (sdio_al_dev == NULL) { + pr_err(MODULE_NAME ": %s: NULL sdio_al_dev\n", func); + return -ENODEV; + } + if (!sdio_al_dev->card) { + pr_err(MODULE_NAME ": %s: NULL card\n", func); + return -ENODEV; + } + if (!sdio_al_dev->card->sdio_func[0]) { + pr_err(MODULE_NAME ": %s: NULL func1\n", func); + return -ENODEV; + } + return 0; +} + + +static int sdio_al_verify_dev(struct sdio_al_device *sdio_al_dev, + char const *func) +{ + int ret; + + ret = sdio_al_verify_func1(sdio_al_dev, func); + if (ret) + return ret; + + if ((sdio_al_dev->state == MODEM_RESTART) || + (sdio_al_dev->state == CARD_REMOVED)) { + pr_err(MODULE_NAME ": %s: device state %d\n", func, + sdio_al_dev->state); + return -ENODEV; + } + return 0; +} + +static void sdio_al_get_into_err_state(struct sdio_al_device *sdio_al_dev) +{ + if ((!sdio_al) || (!sdio_al_dev)) + return; + + sdio_al_dev->is_err = true; + sdio_al->debug.debug_data_on = 0; + sdio_al->debug.debug_lpm_on = 0; + sdio_al_print_info(); +} + +void sdio_al_register_lpm_cb(void *device_handle, + int(*lpm_callback)(void *, int)) +{ + struct sdio_al_device *sdio_al_dev = + (struct sdio_al_device *) device_handle; + + if (!sdio_al_dev) { + pr_err(MODULE_NAME ": %s - device_handle is NULL\n", + __func__); + return; + } + + if (lpm_callback) { + sdio_al_dev->lpm_callback = lpm_callback; + lpm_callback((void *)sdio_al_dev, + sdio_al_dev->is_ok_to_sleep); + } + LPM_DEBUG(MODULE_NAME ": %s - device %d registered for wakeup " + "callback\n", __func__, sdio_al_dev->card->host->index); +} + +void sdio_al_unregister_lpm_cb(void *device_handle) +{ + struct sdio_al_device *sdio_al_dev = + (struct sdio_al_device *) device_handle; + + if (!sdio_al_dev) { + pr_err(MODULE_NAME ": %s - device_handle is NULL\n", + __func__); + return; + } + + sdio_al_dev->lpm_callback = NULL; + LPM_DEBUG(MODULE_NAME ": %s - device %d unregister for wakeup " + "callback\n", __func__, sdio_al_dev->card->host->index); +} + +static void sdio_al_vote_for_sleep(struct sdio_al_device *sdio_al_dev, + int is_vote_for_sleep) +{ + pr_debug(MODULE_NAME ": %s()", __func__); + + if (is_vote_for_sleep) { + LPM_DEBUG(MODULE_NAME ": %s - sdio vote for Sleep", __func__); + wake_unlock(&sdio_al_dev->wake_lock); + } else { + LPM_DEBUG(MODULE_NAME ": %s - sdio vote against sleep", + __func__); + wake_lock(&sdio_al_dev->wake_lock); + } + + if (sdio_al_dev->lpm_callback != NULL) { + LPM_DEBUG(MODULE_NAME ": %s - is_vote_for_sleep=%d for " + "card#%d, calling callback...", + __func__, + is_vote_for_sleep, + sdio_al_dev->card->host->index); + sdio_al_dev->lpm_callback((void *)sdio_al_dev, + is_vote_for_sleep); + } +} + +/** + * Write SDIO-Client lpm information + * Should only be called with host claimed. + */ +static int write_lpm_info(struct sdio_al_device *sdio_al_dev) +{ + struct sdio_func *lpm_func = + sdio_al_dev->card->sdio_func[sdio_al_dev->lpm_chan+1]; + int offset = offsetof(struct peer_sdioc_sw_mailbox, ch_config)+ + sizeof(struct peer_sdioc_channel_config) * + sdio_al_dev->lpm_chan+ + offsetof(struct peer_sdioc_channel_config, is_host_ok_to_sleep); + int ret; + + if (sdio_al_dev->lpm_chan == INVALID_SDIO_CHAN) { + pr_err(MODULE_NAME ":Invalid lpm_chan for card %d\n", + sdio_al_dev->card->host->index); + return -EINVAL; + } + + pr_debug(MODULE_NAME ":write_lpm_info is_ok_to_sleep=%d, device %d\n", + sdio_al_dev->is_ok_to_sleep, + sdio_al_dev->card->host->index); + + ret = sdio_memcpy_toio(lpm_func, SDIOC_SW_MAILBOX_ADDR+offset, + &sdio_al_dev->is_ok_to_sleep, sizeof(u32)); + if (ret) { + pr_err(MODULE_NAME ":failed to write lpm info for card %d\n", + sdio_al_dev->card->host->index); + return ret; + } + + return 0; +} + +/* Set inactivity counter to intial value to allow clients come up */ +static inline void start_inactive_time(struct sdio_al_device *sdio_al_dev) +{ + sdio_al_dev->inactivity_time = jiffies + + msecs_to_jiffies(INITIAL_INACTIVITY_TIME_MSEC); +} + +static inline void restart_inactive_time(struct sdio_al_device *sdio_al_dev) +{ + sdio_al_dev->inactivity_time = jiffies + + msecs_to_jiffies(INACTIVITY_TIME_MSEC); +} + +static inline int is_inactive_time_expired(struct sdio_al_device *sdio_al_dev) +{ + return time_after(jiffies, sdio_al_dev->inactivity_time); +} + + +static int is_user_irq_enabled(struct sdio_al_device *sdio_al_dev, + int func_num) +{ + int ret = 0; + struct sdio_func *func1; + u32 user_irq = 0; + u32 addr = 0; + u32 offset = 0; + u32 masked_user_irq = 0; + + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return 0; + func1 = sdio_al_dev->card->sdio_func[0]; + + if (func_num < 4) { + addr = FUNC_1_4_USER_IRQ_ADDR; + offset = func_num * 8; + } else { + addr = FUNC_5_7_USER_IRQ_ADDR; + offset = (func_num - 4) * 8; + } + + user_irq = sdio_readl(func1, addr, &ret); + if (ret) { + pr_debug(MODULE_NAME ":read_user_irq fail\n"); + return 0; + } + + masked_user_irq = (user_irq >> offset) && 0xFF; + if (masked_user_irq == 0x1) { + pr_info(MODULE_NAME ":user_irq enabled\n"); + return 1; + } + + return 0; +} + +static void sdio_al_sleep(struct sdio_al_device *sdio_al_dev, + struct mmc_host *host) +{ + int i; + + /* Go to sleep */ + LPM_DEBUG(MODULE_NAME ":Inactivity timer expired." + " Going to sleep\n"); + /* Stop mailbox timer */ + sdio_al_dev->poll_delay_msec = 0; + del_timer_sync(&sdio_al_dev->timer); + /* Make sure we get interrupt for non-packet-mode right away */ + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + struct sdio_channel *ch = &sdio_al_dev->channel[i]; + if ((!ch->is_valid) || (!ch->is_open)) + continue; + if (ch->is_packet_mode == false) { + ch->read_threshold = 1; + set_pipe_threshold(sdio_al_dev, + ch->rx_pipe_index, + ch->read_threshold); + } + } + /* Mark HOST_OK_TOSLEEP */ + sdio_al_dev->is_ok_to_sleep = 1; + write_lpm_info(sdio_al_dev); + + /* Clock rate is required to enable the clock and set its rate. + * Hence, save the clock rate before disabling it */ + sdio_al_dev->clock = host->ios.clock; + /* Disable clocks here */ + host->ios.clock = 0; + msmsdcc_lpm_enable(host); + LPM_DEBUG(MODULE_NAME ":Finished sleep sequence for card %d. " + "Sleep now.\n", + sdio_al_dev->card->host->index); + /* Release wakelock */ + sdio_al_vote_for_sleep(sdio_al_dev, 1); +} + + +/** + * Read SDIO-Client Mailbox from Function#1.thresh_pipe + * + * The mailbox contain the bytes available per pipe, + * and the End-Of-Transfer indication per pipe (if available). + * + * WARNING: Each time the Mailbox is read from the client, the + * read_bytes_avail is incremented with another pending + * transfer. Therefore, a pending rx-packet should be added to a + * list before the next read of the mailbox. + * + * This function should run from a workqueue context since it + * notifies the clients. + * + * This function assumes that sdio_claim_host was called before + * calling it. + * + */ +static int read_mailbox(struct sdio_al_device *sdio_al_dev, int from_isr) +{ + int ret; + struct sdio_func *func1 = sdio_al_dev->card->sdio_func[0]; + struct sdio_mailbox *mailbox = sdio_al_dev->mailbox; + struct mmc_host *host = func1->card->host; + u32 new_write_avail = 0; + u32 old_write_avail = 0; + u32 any_read_avail = 0; + u32 any_write_pending = 0; + int i; + u32 rx_notify_bitmask = 0; + u32 tx_notify_bitmask = 0; + u32 eot_pipe = 0; + u32 thresh_pipe = 0; + u32 overflow_pipe = 0; + u32 underflow_pipe = 0; + u32 thresh_intr_mask = 0; + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + return 0; + } + + pr_debug(MODULE_NAME ":start %s from_isr = %d for card %d.\n" + , __func__, from_isr, sdio_al_dev->card->host->index); + + pr_debug(MODULE_NAME ":before sdio_memcpy_fromio.\n"); + ret = sdio_memcpy_fromio(func1, mailbox, + HW_MAILBOX_ADDR, sizeof(*mailbox)); + pr_debug(MODULE_NAME ":after sdio_memcpy_fromio.\n"); + + eot_pipe = (mailbox->eot_pipe_0_7) | + (mailbox->eot_pipe_8_15<<8); + thresh_pipe = (mailbox->thresh_above_limit_pipe_0_7) | + (mailbox->thresh_above_limit_pipe_8_15<<8); + + overflow_pipe = (mailbox->overflow_pipe_0_7) | + (mailbox->overflow_pipe_8_15<<8); + underflow_pipe = mailbox->underflow_pipe_0_7 | + (mailbox->underflow_pipe_8_15<<8); + thresh_intr_mask = + (mailbox->mask_thresh_above_limit_pipe_0_7) | + (mailbox->mask_thresh_above_limit_pipe_8_15<<8); + + if (ret) { + pr_err(MODULE_NAME ":Fail to read Mailbox for card %d," + " goto error state\n", + sdio_al_dev->card->host->index); + sdio_al_get_into_err_state(sdio_al_dev); + /* Stop the timer to stop reading the mailbox */ + sdio_al_dev->poll_delay_msec = 0; + goto exit_err; + } + + if (overflow_pipe || underflow_pipe) + pr_err(MODULE_NAME ":Mailbox ERROR " + "overflow=0x%x, underflow=0x%x\n", + overflow_pipe, underflow_pipe); + + /* In case of modem reset we would like to read the daya from the modem + to clear the interrupts but do not process it */ + if (sdio_al_dev->state != CARD_INSERTED) { + pr_err(MODULE_NAME ":sdio_al_device (card %d) is in invalid " + "state %d\n", + sdio_al_dev->card->host->index, + sdio_al_dev->state); + return -ENODEV; + } + + pr_debug(MODULE_NAME ":card %d: eot=0x%x, thresh=0x%x\n", + sdio_al_dev->card->host->index, + eot_pipe, thresh_pipe); + + /* Scan for Rx Packets available and update read available bytes */ + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + struct sdio_channel *ch = &sdio_al_dev->channel[i]; + u32 old_read_avail; + u32 read_avail; + u32 new_packet_size = 0; + + if ((!ch->is_valid) || (!ch->is_open)) + continue; + + old_read_avail = ch->read_avail; + read_avail = mailbox->pipe_bytes_avail[ch->rx_pipe_index]; + + if (read_avail > INVALID_DATA_AVAILABLE) { + pr_err(MODULE_NAME + ":Invalid read_avail 0x%x for pipe %d\n", + read_avail, ch->rx_pipe_index); + continue; + } + any_read_avail |= read_avail | old_read_avail; + ch->statistics.last_any_read_avail = any_read_avail; + ch->statistics.last_read_avail = read_avail; + ch->statistics.last_old_read_avail = old_read_avail; + + if (ch->is_packet_mode) + new_packet_size = check_pending_rx_packet(ch, eot_pipe); + else + ch->read_avail = read_avail; + + if ((ch->is_packet_mode) && (new_packet_size > 0)) { + rx_notify_bitmask |= (1<num); + ch->statistics.total_notifs++; + } + + if ((!ch->is_packet_mode) && (ch->read_avail > 0) && + (old_read_avail == 0)) { + rx_notify_bitmask |= (1<num); + ch->statistics.total_notifs++; + } + } + + /* Update Write available */ + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + struct sdio_channel *ch = &sdio_al_dev->channel[i]; + + if ((!ch->is_valid) || (!ch->is_open)) + continue; + + new_write_avail = mailbox->pipe_bytes_avail[ch->tx_pipe_index]; + + if (new_write_avail > INVALID_DATA_AVAILABLE) { + pr_err(MODULE_NAME + ":Invalid write_avail 0x%x for pipe %d\n", + new_write_avail, ch->tx_pipe_index); + continue; + } + + old_write_avail = ch->write_avail; + ch->write_avail = new_write_avail; + + if ((old_write_avail <= ch->min_write_avail) && + (new_write_avail >= ch->min_write_avail)) + tx_notify_bitmask |= (1<num); + + /* There is not enough write avail for this channel. + We need to keep reading mailbox to wait for the appropriate + write avail and cannot sleep. Ignore SMEM channel that has + only one direction. */ + if (strcmp(ch->name, "SDIO_SMEM")) + any_write_pending |= + (new_write_avail < ch->ch_config.max_tx_threshold); + } + + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + struct sdio_channel *ch = &sdio_al_dev->channel[i]; + + if ((!ch->is_valid) || (!ch->is_open) || (ch->notify == NULL)) + continue; + + if (rx_notify_bitmask & (1<num)) + ch->notify(ch->priv, + SDIO_EVENT_DATA_READ_AVAIL); + + if (tx_notify_bitmask & (1<num)) + ch->notify(ch->priv, + SDIO_EVENT_DATA_WRITE_AVAIL); + } + + + if ((rx_notify_bitmask == 0) && (tx_notify_bitmask == 0) && + !any_read_avail && !any_write_pending) { + DATA_DEBUG(MODULE_NAME ":Nothing to Notify for card %d\n", + sdio_al_dev->card->host->index); + if (is_inactive_time_expired(sdio_al_dev)) + sdio_al_sleep(sdio_al_dev, host); + } else { + DATA_DEBUG(MODULE_NAME ":Notify bitmask for card %d " + "rx=0x%x, tx=0x%x.\n", + sdio_al_dev->card->host->index, rx_notify_bitmask, + tx_notify_bitmask); + /* Restart inactivity timer if any activity on the channel */ + restart_inactive_time(sdio_al_dev); + } + + pr_debug(MODULE_NAME ":end %s.\n", __func__); + +exit_err: + return ret; +} + +/** + * Check pending rx packet when reading the mailbox. + */ +static u32 check_pending_rx_packet(struct sdio_channel *ch, u32 eot) +{ + u32 rx_pending; + u32 rx_avail; + u32 new_packet_size = 0; + struct sdio_al_device *sdio_al_dev = ch->sdio_al_dev; + + + if (sdio_al_dev == NULL) { + pr_err(MODULE_NAME ": NULL sdio_al_dev for channel %s\n", + ch->name); + return -EINVAL; + } + + mutex_lock(&ch->ch_lock); + + rx_pending = ch->rx_pending_bytes; + rx_avail = sdio_al_dev->mailbox->pipe_bytes_avail[ch->rx_pipe_index]; + + pr_debug(MODULE_NAME ":pipe %d of card %d rx_avail=0x%x, " + "rx_pending=0x%x\n", + ch->rx_pipe_index, sdio_al_dev->card->host->index, rx_avail, + rx_pending); + + + /* new packet detected */ + if (eot & (1<rx_pipe_index)) { + struct rx_packet_size *p = NULL; + new_packet_size = rx_avail - rx_pending; + + if ((rx_avail <= rx_pending)) { + pr_err(MODULE_NAME ":Invalid new packet size." + " rx_avail=%d.\n", rx_avail); + new_packet_size = 0; + goto exit_err; + } + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) + goto exit_err; + p->size = new_packet_size; + /* Add new packet as last */ + list_add_tail(&p->list, &ch->rx_size_list_head); + ch->rx_pending_bytes += new_packet_size; + + if (ch->read_avail == 0) + ch->read_avail = new_packet_size; + } + +exit_err: + mutex_unlock(&ch->ch_lock); + + return new_packet_size; +} + + + +/** + * Remove first pending packet from the list. + */ +static u32 remove_handled_rx_packet(struct sdio_channel *ch) +{ + struct rx_packet_size *p = NULL; + + mutex_lock(&ch->ch_lock); + + ch->rx_pending_bytes -= ch->read_avail; + + if (!list_empty(&ch->rx_size_list_head)) { + p = list_first_entry(&ch->rx_size_list_head, + struct rx_packet_size, list); + list_del(&p->list); + kfree(p); + } + + if (list_empty(&ch->rx_size_list_head)) { + ch->read_avail = 0; + } else { + p = list_first_entry(&ch->rx_size_list_head, + struct rx_packet_size, list); + ch->read_avail = p->size; + } + + mutex_unlock(&ch->ch_lock); + + return ch->read_avail; +} + + +/** + * Bootloader worker function. + * + * @note: clear the bootloader_done flag only after reading the + * mailbox, to ignore more requests while reading the mailbox. + */ +static void boot_worker(struct work_struct *work) +{ + int ret = 0; + int func_num = 0; + int i; + struct sdio_al_device *sdio_al_dev = NULL; + struct sdio_al_work *sdio_al_work = container_of(work, + struct sdio_al_work, + work); + + if (sdio_al_work == NULL) { + pr_err(MODULE_NAME ": %s: NULL sdio_al_work\n", __func__); + return; + } + + sdio_al_dev = sdio_al_work->sdio_al_dev; + if (sdio_al_dev == NULL) { + pr_err(MODULE_NAME ": %s: NULL sdio_al_dev\n", __func__); + return; + } + pr_info(MODULE_NAME ":Bootloader Worker Started, " + "wait for bootloader_done event..\n"); + wait_event(sdio_al_dev->wait_mbox, + sdio_al_dev->bootloader_done); + pr_info(MODULE_NAME ":Got bootloader_done event..\n"); + /* Do polling until MDM is up */ + for (i = 0; i < 5000; ++i) { + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return; + sdio_claim_host(sdio_al_dev->card->sdio_func[0]); + if (is_user_irq_enabled(sdio_al_dev, func_num)) { + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + sdio_al_dev->bootloader_done = 0; + ret = sdio_al_client_setup(sdio_al_dev); + if (ret) { + pr_err(MODULE_NAME ":" + "sdio_al_client_setup failed, " + "for card %d ret=%d\n", + sdio_al_dev->card->host->index, + ret); + sdio_al_get_into_err_state(sdio_al_dev); + } + goto done; + } + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + msleep(100); + } + pr_err(MODULE_NAME ":Timeout waiting for user_irq for card %d\n", + sdio_al_dev->card->host->index); + sdio_al_get_into_err_state(sdio_al_dev); + +done: + pr_debug(MODULE_NAME ":Boot Worker for card %d Exit!\n", + sdio_al_dev->card->host->index); +} + +/** + * Worker function. + * + * @note: clear the ask_mbox flag only after + * reading the mailbox, to ignore more requests while + * reading the mailbox. + */ +static void worker(struct work_struct *work) +{ + int ret = 0; + struct sdio_al_device *sdio_al_dev = NULL; + struct sdio_al_work *sdio_al_work = container_of(work, + struct sdio_al_work, + work); + if (sdio_al_work == NULL) { + pr_err(MODULE_NAME ": worker: NULL sdio_al_work\n"); + return; + } + + sdio_al_dev = sdio_al_work->sdio_al_dev; + if (sdio_al_dev == NULL) { + pr_err(MODULE_NAME ": worker: NULL sdio_al_dev\n"); + return; + } + pr_debug(MODULE_NAME ":Worker Started..\n"); + while ((sdio_al_dev->is_ready) && (ret == 0)) { + pr_debug(MODULE_NAME ":Wait for read mailbox request..\n"); + wait_event(sdio_al_dev->wait_mbox, sdio_al_dev->ask_mbox); + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + break; + if (!sdio_al_dev->is_ready) + break; + sdio_claim_host(sdio_al_dev->card->sdio_func[0]); + if (sdio_al_dev->is_ok_to_sleep) { + ret = sdio_al_wake_up(sdio_al_dev, 1); + if (ret) { + sdio_release_host( + sdio_al_dev->card->sdio_func[0]); + return; + } + } + ret = read_mailbox(sdio_al_dev, false); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + sdio_al_dev->ask_mbox = false; + } + pr_debug(MODULE_NAME ":Worker Exit!\n"); +} + +/** + * Write command using CMD54 rather than CMD53. + * Writing with CMD54 generate EOT interrupt at the + * SDIO-Client. + * Based on mmc_io_rw_extended() + */ +static int sdio_write_cmd54(struct mmc_card *card, unsigned fn, + unsigned addr, const u8 *buf, + unsigned blocks, unsigned blksz) +{ + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + struct scatterlist sg; + int incr_addr = 1; /* MUST */ + int write = 1; + + BUG_ON(!card); + BUG_ON(fn > 7); + BUG_ON(blocks == 1 && blksz > 512); + WARN_ON(blocks == 0); + WARN_ON(blksz == 0); + + write = true; + pr_debug(MODULE_NAME ":sdio_write_cmd54()" + "fn=%d,buf=0x%x,blocks=%d,blksz=%d\n", + fn, (u32) buf, blocks, blksz); + + memset(&mrq, 0, sizeof(struct mmc_request)); + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + + mrq.cmd = &cmd; + mrq.data = &data; + + cmd.opcode = SD_IO_RW_EXTENDED_QCOM; + + cmd.arg = write ? 0x80000000 : 0x00000000; + cmd.arg |= fn << 28; + cmd.arg |= incr_addr ? 0x04000000 : 0x00000000; + cmd.arg |= addr << 9; + if (blocks == 1 && blksz <= 512) + cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ + else + cmd.arg |= 0x08000000 | blocks; /* block mode */ + cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; + + data.blksz = blksz; + data.blocks = blocks; + data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + sg_init_one(&sg, buf, blksz * blocks); + + mmc_set_data_timeout(&data, card); + + mmc_wait_for_req(card->host, &mrq); + + if (cmd.error) + return cmd.error; + if (data.error) + return data.error; + + if (mmc_host_is_spi(card->host)) { + /* host driver already reported errors */ + } else { + if (cmd.resp[0] & R5_ERROR) { + pr_err(MODULE_NAME ":%s: R5_ERROR", __func__); + return -EIO; + } + if (cmd.resp[0] & R5_FUNCTION_NUMBER) { + pr_err(MODULE_NAME ":%s: R5_FUNCTION_NUMBER", __func__); + return -EINVAL; + } + if (cmd.resp[0] & R5_OUT_OF_RANGE) { + pr_err(MODULE_NAME ":%s: R5_OUT_OF_RANGE", __func__); + return -ERANGE; + } + } + + return 0; +} + + +/** + * Write data to channel. + * Handle different data size types. + * + */ +static int sdio_ch_write(struct sdio_channel *ch, const u8 *buf, u32 len) +{ + int ret = 0; + unsigned blksz = ch->func->cur_blksize; + int blocks = len / blksz; + int remain_bytes = len % blksz; + struct mmc_card *card = NULL; + u32 fn = ch->func->num; + + if (len == 0) { + pr_err(MODULE_NAME ":channel %s trying to write 0 bytes\n", + ch->name); + return -EINVAL; + } + + card = ch->func->card; + + if (remain_bytes) { + /* CMD53 */ + if (blocks) { + ret = sdio_memcpy_toio(ch->func, PIPE_TX_FIFO_ADDR, + (void *) buf, blocks*blksz); + if (ret != 0) { + pr_err(MODULE_NAME ":%s: sdio_memcpy_toio " + "failed for channel %s\n", + __func__, ch->name); + ch->sdio_al_dev->is_err = true; + return ret; + } + } + + buf += (blocks*blksz); + + ret = sdio_write_cmd54(card, fn, PIPE_TX_FIFO_ADDR, + buf, 1, remain_bytes); + } else { + ret = sdio_write_cmd54(card, fn, PIPE_TX_FIFO_ADDR, + buf, blocks, blksz); + } + + if (ret != 0) { + pr_err(MODULE_NAME ":%s: sdio_write_cmd54 " + "failed for channel %s\n", + __func__, ch->name); + ch->sdio_al_dev->is_err = true; + return ret; + } + + return ret; +} + +static int sdio_al_bootloader_completed(void) +{ + int i; + + pr_debug(MODULE_NAME ":sdio_al_bootloader_completed was called\n"); + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { + struct sdio_al_device *dev = NULL; + if (sdio_al->devices[i] == NULL) + continue; + dev = sdio_al->devices[i]; + dev->bootloader_done = 1; + wake_up(&dev->wait_mbox); + } + + return 0; +} + +static int sdio_al_wait_for_bootloader_comp(struct sdio_al_device *sdio_al_dev) +{ + int ret = 0; + + struct sdio_func *func1; + + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + func1 = sdio_al_dev->card->sdio_func[0]; + + sdio_claim_host(func1); + /* + * Enable function 0 interrupt mask to allow 9k to raise this interrupt + * in power-up. When sdio_downloader will notify its completion + * we will poll on this interrupt to wait for 9k power-up + */ + ret = enable_mask_irq(sdio_al_dev, 0, 1, 0); + if (ret) { + pr_err(MODULE_NAME ":Enable_mask_irq for card %d failed, " + "ret=%d\n", + sdio_al_dev->card->host->index, ret); + sdio_release_host(func1); + return ret; + } + + sdio_release_host(func1); + + /* + * Start bootloader worker that will wait for the bootloader + * completion + */ + sdio_al_dev->boot_work.sdio_al_dev = sdio_al_dev; + INIT_WORK(&sdio_al_dev->boot_work.work, boot_worker); + sdio_al_dev->bootloader_done = 0; + queue_work(sdio_al_dev->workqueue, &sdio_al_dev->boot_work.work); + + return 0; +} + +static int sdio_al_bootloader_setup(void) +{ + int ret = 0; + struct sdio_func *func1; + struct sdio_al_device *bootloader_dev = sdio_al->bootloader_dev; + + if (bootloader_dev == NULL) { + pr_err(MODULE_NAME ":No bootloader_dev\n"); + return -ENODEV; + } + + + if (bootloader_dev->flashless_boot_on) { + pr_info(MODULE_NAME ":Already in boot process.\n"); + return 0; + } + + func1 = bootloader_dev->card->sdio_func[0]; + if (!func1) { + pr_err(MODULE_NAME ": %s: NULL func1\n", __func__); + return -ENODEV; + } + + bootloader_dev->sdioc_boot_sw_header + = kzalloc(sizeof(*bootloader_dev->sdioc_boot_sw_header), + GFP_KERNEL); + if (bootloader_dev->sdioc_boot_sw_header == NULL) { + pr_err(MODULE_NAME ":fail to allocate sdioc boot sw header.\n"); + return -ENOMEM; + } + + sdio_claim_host(func1); + + ret = sdio_memcpy_fromio(func1, + bootloader_dev->sdioc_boot_sw_header, + SDIOC_SW_HEADER_ADDR, + sizeof(struct peer_sdioc_boot_sw_header)); + if (ret) { + pr_err(MODULE_NAME ":fail to read sdioc boot sw header.\n"); + sdio_release_host(func1); + goto exit_err; + } + + if (bootloader_dev->sdioc_boot_sw_header->signature != + (u32) PEER_SDIOC_SW_MAILBOX_BOOT_SIGNATURE) { + pr_err(MODULE_NAME ":invalid mailbox signature 0x%x.\n", + bootloader_dev->sdioc_boot_sw_header->signature); + sdio_release_host(func1); + ret = -EINVAL; + goto exit_err; + } + + /* Upper byte has to be equal - no backward compatibility for unequal */ + if ((bootloader_dev->sdioc_boot_sw_header->version >> 16) != + (sdio_al->pdata->peer_sdioc_boot_version_major)) { + pr_err(MODULE_NAME ": HOST(0x%x) and CLIENT(0x%x) SDIO_AL BOOT " + "VERSION don't match\n", + ((sdio_al->pdata->peer_sdioc_boot_version_major<<16)+ + sdio_al->pdata->peer_sdioc_boot_version_minor), + bootloader_dev->sdioc_boot_sw_header->version); + sdio_release_host(func1); + ret = -EIO; + goto exit_err; + } + + pr_info(MODULE_NAME ": SDIOC BOOT SW version 0x%x\n", + bootloader_dev->sdioc_boot_sw_header->version); + + bootloader_dev->flashless_boot_on = true; + + sdio_release_host(func1); + + ret = sdio_al_wait_for_bootloader_comp(bootloader_dev); + if (ret) { + pr_err(MODULE_NAME ":sdio_al_wait_for_bootloader_comp failed, " + "err=%d\n", ret); + goto exit_err; + } + + ret = sdio_downloader_setup(bootloader_dev->card, 1, + bootloader_dev->sdioc_boot_sw_header->boot_ch_num, + sdio_al_bootloader_completed); + + if (ret) { + pr_err(MODULE_NAME ":sdio_downloader_setup failed, err=%d\n", + ret); + goto exit_err; + } + + pr_info(MODULE_NAME ":In Flashless boot, waiting for its " + "completion\n"); + + +exit_err: + pr_info(MODULE_NAME ":free sdioc_boot_sw_header.\n"); + kfree(bootloader_dev->sdioc_boot_sw_header); + bootloader_dev->sdioc_boot_sw_header = NULL; + bootloader_dev = NULL; + + return ret; +} + + +/** + * Read SDIO-Client software header + * + */ +static int read_sdioc_software_header(struct sdio_al_device *sdio_al_dev, + struct peer_sdioc_sw_header *header) +{ + int ret; + int i; + int test_version = 0; + int sdioc_test_version = 0; + + pr_debug(MODULE_NAME ":reading sdioc sw header.\n"); + + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + ret = sdio_memcpy_fromio(sdio_al_dev->card->sdio_func[0], header, + SDIOC_SW_HEADER_ADDR, sizeof(*header)); + if (ret) { + pr_err(MODULE_NAME ":fail to read sdioc sw header.\n"); + goto exit_err; + } + + if (header->signature == (u32)PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE) { + pr_info(MODULE_NAME ":SDIOC SW unittest signature. 0x%x\n", + header->signature); + sdio_al->unittest_mode = true; + /* Verify test code compatibility with the modem */ + sdioc_test_version = (header->version & 0xFF00) >> 8; + test_version = sdio_al->pdata->peer_sdioc_version_minor >> 8; + if (test_version != sdioc_test_version) + pr_err(MODULE_NAME ":HOST(0x%x) and CLIENT(0x%x) " + "testing VERSION don't match, tests may fail\n", + test_version, + sdioc_test_version); + } + + if ((header->signature != (u32) PEER_SDIOC_SW_MAILBOX_SIGNATURE) && + (header->signature != (u32) PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE)) { + pr_err(MODULE_NAME ":SDIOC SW invalid signature. 0x%x\n", + header->signature); + goto exit_err; + } + /* Upper byte has to be equal - no backward compatibility for unequal */ + sdio_al->sdioc_major = header->version >> 16; + if (sdio_al->pdata->allow_sdioc_version_major_2) { + if ((sdio_al->sdioc_major != + sdio_al->pdata->peer_sdioc_version_major) && + (sdio_al->sdioc_major != PEER_SDIOC_OLD_VERSION_MAJOR)) { + pr_err(MODULE_NAME ": HOST(0x%x) and CLIENT(0x%x) " + "SDIO_AL VERSION don't match\n", + ((sdio_al->pdata->peer_sdioc_version_major<<16)+ + sdio_al->pdata->peer_sdioc_version_minor), + header->version); + goto exit_err; + } + } else { + if (sdio_al->sdioc_major != + sdio_al->pdata->peer_sdioc_version_major) { + pr_err(MODULE_NAME ": HOST(0x%x) and CLIENT(0x%x) " + "SDIO_AL VERSION don't match\n", + ((sdio_al->pdata->peer_sdioc_version_major<<16)+ + sdio_al->pdata->peer_sdioc_version_minor), + header->version); + goto exit_err; + } + } + + pr_info(MODULE_NAME ":SDIOC SW version 0x%x\n", header->version); + + sdio_al_dev->flashless_boot_on = false; + + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + struct sdio_channel *ch = &sdio_al_dev->channel[i]; + + /* Set default values */ + ch->read_threshold = DEFAULT_READ_THRESHOLD; + ch->write_threshold = DEFAULT_WRITE_THRESHOLD; + ch->min_write_avail = DEFAULT_MIN_WRITE_THRESHOLD; + ch->is_packet_mode = true; + ch->peer_tx_buf_size = DEFAULT_PEER_TX_BUF_SIZE; + ch->poll_delay_msec = 0; + + ch->num = i; + + memset(ch->name, 0, sizeof(ch->name)); + + if (header->channel_names[i][0]) { + memcpy(ch->name, SDIO_PREFIX, + strlen(SDIO_PREFIX)); + memcpy(ch->name + strlen(SDIO_PREFIX), + header->channel_names[i], + PEER_CHANNEL_NAME_SIZE); + + ch->is_valid = 1; + ch->sdio_al_dev = sdio_al_dev; + } + + pr_info(MODULE_NAME ":Channel=%s, is_valid=%d\n", ch->name, + ch->is_valid); + } + + return 0; + +exit_err: + sdio_al_get_into_err_state(sdio_al_dev); + memset(header, 0, sizeof(*header)); + + return -EIO; +} + +/** + * Read SDIO-Client channel configuration + * + */ +static int read_sdioc_channel_config(struct sdio_channel *ch) +{ + int ret; + struct peer_sdioc_sw_mailbox *sw_mailbox = NULL; + struct peer_sdioc_channel_config *ch_config = NULL; + struct sdio_al_device *sdio_al_dev = ch->sdio_al_dev; + + if (sdio_al_dev == NULL) { + pr_err(MODULE_NAME ": NULL sdio_al_dev for channel %s\n", + ch->name); + return -EINVAL; + } + + if (sdio_al_dev->sdioc_sw_header->version == 0) + return -1; + + pr_debug(MODULE_NAME ":reading sw mailbox %s channel.\n", ch->name); + + sw_mailbox = kzalloc(sizeof(*sw_mailbox), GFP_KERNEL); + if (sw_mailbox == NULL) + return -ENOMEM; + + ret = sdio_memcpy_fromio(ch->func, sw_mailbox, + SDIOC_SW_MAILBOX_ADDR, sizeof(*sw_mailbox)); + if (ret) { + pr_err(MODULE_NAME ":fail to read sw mailbox.\n"); + goto exit_err; + } + + ch_config = &sw_mailbox->ch_config[ch->num]; + memcpy(&ch->ch_config, ch_config, + sizeof(struct peer_sdioc_channel_config)); + + if (!ch_config->is_ready) { + pr_err(MODULE_NAME ":sw mailbox channel not ready.\n"); + goto exit_err; + } + + pr_info(MODULE_NAME ":ch_config %s max_rx_threshold=%d.\n", + ch->name, ch_config->max_rx_threshold); + pr_info(MODULE_NAME ":ch_config %s max_tx_threshold=%d.\n", + ch->name, ch_config->max_tx_threshold); + pr_info(MODULE_NAME ":ch_config %s tx_buf_size=%d.\n", + ch->name, ch_config->tx_buf_size); + + /* Aggregation up to 90% of the maximum size */ + ch->read_threshold = (ch_config->max_rx_threshold * 9) / 10; + /* Threshold on 50% of the maximum size , sdioc uses double-buffer */ + ch->write_threshold = (ch_config->max_tx_threshold * 5) / 10; + + ch->def_read_threshold = ch->read_threshold; + + ch->is_packet_mode = ch_config->is_packet_mode; + if (!ch->is_packet_mode) { + ch->poll_delay_msec = DEFAULT_POLL_DELAY_NOPACKET_MSEC; + ch->min_write_avail = DEFAULT_MIN_WRITE_THRESHOLD_STREAMING; + } + pr_info(MODULE_NAME ":ch %s is_packet_mode=%d.\n", + ch->name, ch->is_packet_mode); + + /* The max_packet_size is set by the modem in version 3 and on */ + if (sdio_al->sdioc_major > PEER_SDIOC_OLD_VERSION_MAJOR) + ch->min_write_avail = ch_config->max_packet_size; + + if (ch->min_write_avail > ch->write_threshold) + ch->min_write_avail = ch->write_threshold; + + pr_info(MODULE_NAME ":ch %s read_threshold=%d.\n", + ch->name, ch->read_threshold); + pr_info(MODULE_NAME ":ch %s write_threshold=%d.\n", + ch->name, ch->write_threshold); + pr_info(MODULE_NAME ":ch %s def_read_threshold=%d.\n", + ch->name, ch->def_read_threshold); + pr_info(MODULE_NAME ":ch %s min_write_avail=%d.\n", + ch->name, ch->min_write_avail); + + ch->peer_tx_buf_size = ch_config->tx_buf_size; + + kfree(sw_mailbox); + + return 0; + +exit_err: + pr_info(MODULE_NAME ":Reading SW Mailbox error.\n"); + kfree(sw_mailbox); + + return -1; +} + + +/** + * Enable/Disable EOT interrupt of a pipe. + * + */ +static int enable_eot_interrupt(struct sdio_al_device *sdio_al_dev, + int pipe_index, int enable) +{ + int ret = 0; + struct sdio_func *func1; + u32 mask; + u32 pipe_mask; + u32 addr; + + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + func1 = sdio_al_dev->card->sdio_func[0]; + + if (pipe_index < 8) { + addr = PIPES_0_7_IRQ_MASK_ADDR; + pipe_mask = (1<card->sdio_func[0]; + + if (func_num < 4) { + addr = FUNC_1_4_MASK_IRQ_ADDR; + offset = func_num * 8 + bit_offset; + } else { + addr = FUNC_5_7_MASK_IRQ_ADDR; + offset = (func_num - 4) * 8 + bit_offset; + } + + func_mask = 1<card->sdio_func[0]; + + if (pipe_index < 8) { + addr = PIPES_0_7_IRQ_MASK_ADDR; + pipe_mask = (1<card->sdio_func[0]; + + sdio_writel(func1, threshold, + PIPES_THRESHOLD_ADDR+pipe_index*4, &ret); + if (ret) + pr_err(MODULE_NAME ":set_pipe_threshold err=%d\n", -ret); + + return ret; +} + +/** + * Enable func w/ retries + * + */ +static int sdio_al_enable_func_retry(struct sdio_func *func, const char *name) +{ + int ret, i; + for (i = 0; i < 200; i++) { + ret = sdio_enable_func(func); + if (ret) { + pr_debug(MODULE_NAME ":retry enable %s func#%d " + "ret=%d\n", + name, func->num, ret); + msleep(10); + } else + break; + } + + return ret; +} + +/** + * Open Channel + * + * 1. Init Channel Context. + * 2. Init the Channel SDIO-Function. + * 3. Init the Channel Pipes on Mailbox. + */ +static int open_channel(struct sdio_channel *ch) +{ + int ret = 0; + struct sdio_al_device *sdio_al_dev = ch->sdio_al_dev; + + if (sdio_al_dev == NULL) { + pr_err(MODULE_NAME ": NULL sdio_al_dev for channel %s\n", + ch->name); + return -EINVAL; + } + + /* Init channel Context */ + /** Func#1 is reserved for mailbox */ + ch->func = sdio_al_dev->card->sdio_func[ch->num+1]; + ch->rx_pipe_index = ch->num*2; + ch->tx_pipe_index = ch->num*2+1; + ch->signature = SDIO_AL_SIGNATURE; + + ch->total_rx_bytes = 0; + ch->total_tx_bytes = 0; + + ch->write_avail = 0; + ch->read_avail = 0; + ch->rx_pending_bytes = 0; + + mutex_init(&ch->ch_lock); + + pr_debug(MODULE_NAME ":open_channel %s func#%d\n", + ch->name, ch->func->num); + + INIT_LIST_HEAD(&(ch->rx_size_list_head)); + + /* Init SDIO Function */ + ret = sdio_al_enable_func_retry(ch->func, ch->name); + if (ret) { + pr_err(MODULE_NAME ":sdio_enable_func() err=%d\n", -ret); + goto exit_err; + } + + /* Note: Patch Func CIS tuple issue */ + ret = sdio_set_block_size(ch->func, SDIO_AL_BLOCK_SIZE); + if (ret) { + pr_err(MODULE_NAME ":sdio_set_block_size()failed, err=%d\n", + -ret); + goto exit_err; + } + + ch->func->max_blksize = SDIO_AL_BLOCK_SIZE; + + sdio_set_drvdata(ch->func, ch); + + /* Get channel parameters from the peer SDIO-Client */ + read_sdioc_channel_config(ch); + + /* Set Pipes Threshold on Mailbox */ + ret = set_pipe_threshold(sdio_al_dev, + ch->rx_pipe_index, ch->read_threshold); + if (ret) + goto exit_err; + ret = set_pipe_threshold(sdio_al_dev, + ch->tx_pipe_index, ch->write_threshold); + if (ret) + goto exit_err; + + /* Set flag before interrupts are enabled to allow notify */ + ch->is_open = true; + + sdio_al_dev->poll_delay_msec = get_min_poll_time_msec(sdio_al_dev); + + /* lpm mechanism lives under the assumption there is always a timer */ + /* Check if need to start the timer */ + if ((sdio_al_dev->poll_delay_msec) && + (sdio_al_dev->is_timer_initialized == false)) { + + init_timer(&sdio_al_dev->timer); + sdio_al_dev->timer.data = (unsigned long) sdio_al_dev; + sdio_al_dev->timer.function = sdio_al_timer_handler; + sdio_al_dev->timer.expires = jiffies + + msecs_to_jiffies(sdio_al_dev->poll_delay_msec); + add_timer(&sdio_al_dev->timer); + sdio_al_dev->is_timer_initialized = true; + } + + /* Enable Pipes Interrupts */ + enable_eot_interrupt(sdio_al_dev, ch->rx_pipe_index, true); + enable_eot_interrupt(sdio_al_dev, ch->tx_pipe_index, true); + + enable_threshold_interrupt(sdio_al_dev, ch->rx_pipe_index, true); + enable_threshold_interrupt(sdio_al_dev, ch->tx_pipe_index, true); + +exit_err: + + return ret; +} + +/** + * Ask the worker to read the mailbox. + */ +static void ask_reading_mailbox(struct sdio_al_device *sdio_al_dev) +{ + if (!sdio_al_dev->ask_mbox) { + pr_debug(MODULE_NAME ":ask_reading_mailbox for card %d\n", + sdio_al_dev->card->host->index); + sdio_al_dev->ask_mbox = true; + wake_up(&sdio_al_dev->wait_mbox); + } +} + +/** + * Start the timer + */ +static void start_timer(struct sdio_al_device *sdio_al_dev) +{ + if ((sdio_al_dev->poll_delay_msec) && + (sdio_al_dev->state == CARD_INSERTED)) { + sdio_al_dev->timer.expires = jiffies + + msecs_to_jiffies(sdio_al_dev->poll_delay_msec); + add_timer(&sdio_al_dev->timer); + } +} + +/** + * Restart(postpone) the already working timer + */ +static void restart_timer(struct sdio_al_device *sdio_al_dev) +{ + if ((sdio_al_dev->poll_delay_msec) && + (sdio_al_dev->state == CARD_INSERTED)) { + ulong expires = jiffies + + msecs_to_jiffies(sdio_al_dev->poll_delay_msec); + mod_timer(&sdio_al_dev->timer, expires); + } +} + +/** + * Do the wakup sequence. + * This function should be called after claiming the host! + * The caller is responsible for releasing the host. + * + * Wake up sequence + * 1. Get lock + * 2. Enable wake up function if needed + * 3. Mark NOT OK to sleep and write it + * 4. Restore default thresholds + * 5. Start the mailbox and inactivity timer again + */ +static int sdio_al_wake_up(struct sdio_al_device *sdio_al_dev, + u32 not_from_int) +{ + int ret = 0, i; + struct sdio_func *wk_func = + sdio_al_dev->card->sdio_func[SDIO_AL_WAKEUP_FUNC-1]; + unsigned long time_to_wait; + struct mmc_host *host = wk_func->card->host; + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + return -ENODEV; + } + + /* Wake up sequence */ + sdio_al_vote_for_sleep(sdio_al_dev, 0); + if (not_from_int) { + LPM_DEBUG(MODULE_NAME ": Wake up card %d (not by interrupt)", + sdio_al_dev->card->host->index); + } else { + LPM_DEBUG(MODULE_NAME ": Wake up card %d by interrupt", + sdio_al_dev->card->host->index); + } + + if (!sdio_al_dev->is_ok_to_sleep) { + LPM_DEBUG(MODULE_NAME ":card %d already awake, " + "no need to wake up\n", + sdio_al_dev->card->host->index); + return 0; + } + + pr_debug(MODULE_NAME ":Turn clock on for card %d\n", + sdio_al_dev->card->host->index); + /* Enable the clock and set its rate */ + host->ios.clock = sdio_al_dev->clock; + msmsdcc_lpm_disable(host); + msmsdcc_set_pwrsave(sdio_al_dev->card->host, 0); + /* Poll the GPIO */ + time_to_wait = jiffies + msecs_to_jiffies(1000); + while (time_before(jiffies, time_to_wait)) { + if (sdio_al->pdata->get_mdm2ap_status()) + break; + udelay(TIME_TO_WAIT_US); + } + LPM_DEBUG(MODULE_NAME ":GPIO mdm2ap_status=%d\n", + sdio_al->pdata->get_mdm2ap_status()); + + /* Here get_mdm2ap_status() returning 0 is not an error condition */ + if (sdio_al->pdata->get_mdm2ap_status() == 0) + LPM_DEBUG(MODULE_NAME ": get_mdm2ap_status() is 0\n"); + + /* Enable Wake up Function */ + ret = sdio_al_enable_func_retry(wk_func, "wakeup func"); + if (ret) { + pr_err(MODULE_NAME ":sdio_enable_func() err=%d\n", + -ret); + goto error_exit; + } + /* Mark NOT OK_TOSLEEP */ + sdio_al_dev->is_ok_to_sleep = 0; + ret = write_lpm_info(sdio_al_dev); + if (ret) { + pr_err(MODULE_NAME ":write_lpm_info() failed, err=%d\n", + -ret); + sdio_al_dev->is_ok_to_sleep = 1; + sdio_disable_func(wk_func); + goto error_exit; + } + + /* Restore default thresh for non packet channels */ + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + struct sdio_channel *ch = &sdio_al_dev->channel[i]; + if ((!ch->is_valid) || (!ch->is_open)) + continue; + if (ch->is_packet_mode == false) { + ch->read_threshold = ch->def_read_threshold; + set_pipe_threshold(sdio_al_dev, ch->rx_pipe_index, + ch->read_threshold); + } + } + sdio_disable_func(wk_func); + + /* Start the timer again*/ + restart_inactive_time(sdio_al_dev); + sdio_al_dev->poll_delay_msec = get_min_poll_time_msec(sdio_al_dev); + start_timer(sdio_al_dev); + + LPM_DEBUG(MODULE_NAME "Finished Wake up sequence for card %d", + sdio_al_dev->card->host->index); + + msmsdcc_set_pwrsave(sdio_al_dev->card->host, 1); + pr_debug(MODULE_NAME ":Turn clock off\n"); + + return ret; +error_exit: + sdio_al_vote_for_sleep(sdio_al_dev, 1); + msmsdcc_set_pwrsave(sdio_al_dev->card->host, 1); + WARN_ON(ret); + sdio_al_get_into_err_state(sdio_al_dev); + return ret; +} + + +/** + * SDIO Function Interrupt handler. + * + * Interrupt shall be triggered by SDIO-Client when: + * 1. End-Of-Transfer (EOT) detected in packet mode. + * 2. Bytes-available reached the threshold. + * + * Reading the mailbox clears the EOT/Threshold interrupt + * source. + * The interrupt source should be cleared before this ISR + * returns. This ISR is called from IRQ Thread and not + * interrupt, so it may sleep. + * + */ +static void sdio_func_irq(struct sdio_func *func) +{ + struct sdio_al_device *sdio_al_dev = sdio_get_drvdata(func); + + pr_debug(MODULE_NAME ":start %s.\n", __func__); + + if (sdio_al_dev == NULL) { + pr_err(MODULE_NAME ": NULL sdio_al_dev for card %d\n", + func->card->host->index); + return; + } + + if (sdio_al_dev->is_ok_to_sleep) + sdio_al_wake_up(sdio_al_dev, 0); + else + restart_timer(sdio_al_dev); + + read_mailbox(sdio_al_dev, true); + + pr_debug(MODULE_NAME ":end %s.\n", __func__); +} + +/** + * Timer Expire Handler + * + */ +static void sdio_al_timer_handler(unsigned long data) +{ + struct sdio_al_device *sdio_al_dev = (struct sdio_al_device *)data; + if (sdio_al_dev == NULL) { + pr_err(MODULE_NAME ": NULL sdio_al_dev for data %lu\n", + data); + return; + } + if (sdio_al_dev->state != CARD_INSERTED) { + pr_err(MODULE_NAME ": sdio_al_dev is in invalid state %d\n", + sdio_al_dev->state); + return; + } + pr_debug(MODULE_NAME " Timer Expired\n"); + + ask_reading_mailbox(sdio_al_dev); + + restart_timer(sdio_al_dev); +} + +/** + * Driver Setup. + * + */ +static int sdio_al_setup(struct sdio_al_device *sdio_al_dev) +{ + int ret = 0; + struct mmc_card *card = sdio_al_dev->card; + struct sdio_func *func1 = NULL; + int i = 0; + int fn = 0; + + if (card == NULL) { + pr_err(MODULE_NAME ":sdio_al_setup: No Card detected\n"); + return -ENODEV; + } + + + pr_info(MODULE_NAME ":sdio_al_setup for card %d\n", + sdio_al_dev->card->host->index); + + func1 = card->sdio_func[0]; + + ret = sdio_al->pdata->config_mdm2ap_status(1); + if (ret) { + pr_err(MODULE_NAME "Could not request GPIO\n"); + return ret; + } + + INIT_WORK(&sdio_al_dev->sdio_al_work.work, worker); + /* disable all pipes interrupts before claim irq. + since all are enabled by default. */ + for (i = 0 ; i < SDIO_AL_MAX_PIPES; i++) { + enable_eot_interrupt(sdio_al_dev, i, false); + enable_threshold_interrupt(sdio_al_dev, i, false); + } + + /* Disable all SDIO Functions before claim irq. */ + for (fn = 1 ; fn <= card->sdio_funcs; fn++) + sdio_disable_func(card->sdio_func[fn-1]); + + sdio_set_drvdata(func1, sdio_al_dev); + pr_info(MODULE_NAME ":claim IRQ for card %d\n", + card->host->index); + + ret = sdio_claim_irq(func1, sdio_func_irq); + if (ret) { + pr_err(MODULE_NAME ":Fail to claim IRQ for card %d\n", + card->host->index); + goto exit_err; + } + + sdio_al_dev->is_ready = true; + + /* Start worker before interrupt might happen */ + queue_work(sdio_al_dev->workqueue, &sdio_al_dev->sdio_al_work.work); + + start_inactive_time(sdio_al_dev); + + pr_debug(MODULE_NAME ":Ready.\n"); + + return 0; + +exit_err: + sdio_release_host(func1); + pr_err(MODULE_NAME ":Setup Failure.\n"); + + return ret; +} + +/** + * Driver Tear-Down. + * + */ +static void sdio_al_tear_down(void) +{ + int i; + struct sdio_al_device *sdio_al_dev = NULL; + struct sdio_func *func1; + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { + if (sdio_al->devices[i] == NULL) + continue; + sdio_al_dev = sdio_al->devices[i]; + + if (sdio_al_dev->is_ready) { + sdio_al_dev->is_ready = false; /* Flag worker to exit */ + sdio_al_dev->ask_mbox = false; + ask_reading_mailbox(sdio_al_dev); /* Wakeup worker */ + /* allow gracefully exit of the worker thread */ + msleep(100); + + flush_workqueue(sdio_al_dev->workqueue); + destroy_workqueue(sdio_al_dev->workqueue); + + sdio_al_vote_for_sleep(sdio_al_dev, 1); + + if (sdio_al_verify_func1(sdio_al_dev, __func__)) { + pr_err(MODULE_NAME ": %s: Invalid func1", + __func__); + return; + } + func1 = sdio_al_dev->card->sdio_func[0]; + + sdio_claim_host(func1); + sdio_release_irq(func1); + sdio_disable_func(func1); + sdio_release_host(func1); + } + } + + sdio_al->pdata->config_mdm2ap_status(0); +} + +/** + * Find channel by name. + * + */ +static struct sdio_channel *find_channel_by_name(const char *name) +{ + struct sdio_channel *ch = NULL; + int i, j; + struct sdio_al_device *sdio_al_dev = NULL; + + for (j = 0; j < MAX_NUM_OF_SDIO_DEVICES; ++j) { + if (sdio_al->devices[j] == NULL) + continue; + sdio_al_dev = sdio_al->devices[j]; + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + if (!sdio_al_dev->channel[i].is_valid) + continue; + if (strcmp(sdio_al_dev->channel[i].name, name) == 0) { + ch = &sdio_al_dev->channel[i]; + break; + } + } + if (ch != NULL) + break; + } + + return ch; +} + +/** + * Find the minimal poll time. + * + */ +static int get_min_poll_time_msec(struct sdio_al_device *sdio_sl_dev) +{ + int i; + int poll_delay_msec = 0x0FFFFFFF; + + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) + if ((sdio_sl_dev->channel[i].is_valid) && + (sdio_sl_dev->channel[i].is_open) && + (sdio_sl_dev->channel[i].poll_delay_msec > 0) && + (sdio_sl_dev->channel[i].poll_delay_msec < poll_delay_msec)) + poll_delay_msec = + sdio_sl_dev->channel[i].poll_delay_msec; + + if (poll_delay_msec == 0x0FFFFFFF) + poll_delay_msec = SDIO_AL_POLL_TIME_NO_STREAMING; + + pr_debug(MODULE_NAME ":poll delay time is %d msec\n", poll_delay_msec); + + return poll_delay_msec; +} + +/** + * Open SDIO Channel. + * + * Enable the channel. + * Set the channel context. + * Trigger reading the mailbox to check available bytes. + * + */ +int sdio_open(const char *name, struct sdio_channel **ret_ch, void *priv, + void (*notify)(void *priv, unsigned ch_event)) +{ + int ret = 0; + struct sdio_channel *ch = NULL; + struct sdio_al_device *sdio_al_dev = NULL; + + *ret_ch = NULL; /* default */ + + ch = find_channel_by_name(name); + if (ch == NULL) { + pr_err(MODULE_NAME ":Can't find channel name %s\n", name); + return -EINVAL; + } + + sdio_al_dev = ch->sdio_al_dev; + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + sdio_claim_host(sdio_al_dev->card->sdio_func[0]); + + if (ch->is_open) { + pr_err(MODULE_NAME ":Channel already opened %s\n", name); + ret = -EPERM; + goto exit_err; + } + + if (sdio_al_dev->state != CARD_INSERTED) { + pr_err(MODULE_NAME ":%s: sdio_al_dev is in invalid state %d\n", + __func__, sdio_al_dev->state); + ret = -ENODEV; + goto exit_err; + } + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + ret = -ENODEV; + goto exit_err; + } + + ret = sdio_al_wake_up(sdio_al_dev, 1); + if (ret) + goto exit_err; + + ch->notify = notify; + ch->priv = priv; + + /* Note: Set caller returned context before interrupts are enabled */ + *ret_ch = ch; + + ret = open_channel(ch); + if (ret) { + pr_err(MODULE_NAME ":sdio_open %s err=%d\n", name, -ret); + goto exit_err; + } + + pr_info(MODULE_NAME ":sdio_open %s completed OK\n", name); + if (sdio_al_dev->lpm_chan == INVALID_SDIO_CHAN) { + if (sdio_al->sdioc_major == PEER_SDIOC_OLD_VERSION_MAJOR) { + if (!ch->is_packet_mode) { + pr_info(MODULE_NAME ":setting channel %s as " + "lpm_chan\n", name); + sdio_al_dev->lpm_chan = ch->num; + } + } else { + pr_info(MODULE_NAME ":setting channel %s as lpm_chan\n", + name); + sdio_al_dev->lpm_chan = ch->num; + } + } + +exit_err: + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return ret; +} +EXPORT_SYMBOL(sdio_open); + +/** + * Close SDIO Channel. + * + */ +int sdio_close(struct sdio_channel *ch) +{ + if (!ch) { + pr_err(MODULE_NAME ":%s: NULL channel\n", __func__); + return -ENODEV; + } + pr_debug(MODULE_NAME ":sdio_close is not supported\n"); + + return -EPERM; +} +EXPORT_SYMBOL(sdio_close); + +/** + * Get the number of available bytes to write. + * + */ +int sdio_write_avail(struct sdio_channel *ch) +{ + if (!ch) { + pr_err(MODULE_NAME ":%s: NULL channel\n", __func__); + return -ENODEV; + } + if (ch->signature != SDIO_AL_SIGNATURE) { + pr_err(MODULE_NAME ":%s: Invalid signature 0x%x\n", __func__, + ch->signature); + return -ENODEV; + } + + pr_debug(MODULE_NAME ":sdio_write_avail %s 0x%x\n", + ch->name, ch->write_avail); + + return ch->write_avail; +} +EXPORT_SYMBOL(sdio_write_avail); + +/** + * Get the number of available bytes to read. + * + */ +int sdio_read_avail(struct sdio_channel *ch) +{ + if (!ch) { + pr_err(MODULE_NAME ":%s: NULL channel\n", __func__); + return -ENODEV; + } + if (ch->signature != SDIO_AL_SIGNATURE) { + pr_err(MODULE_NAME ":%s: Invalid signature 0x%x\n", __func__, + ch->signature); + return -ENODEV; + } + + pr_debug(MODULE_NAME ":sdio_read_avail %s 0x%x\n", + ch->name, ch->read_avail); + + return ch->read_avail; + +} +EXPORT_SYMBOL(sdio_read_avail); + +/** + * Read from SDIO Channel. + * + * Reading from the pipe will trigger interrupt if there are + * other pending packets on the SDIO-Client. + * + */ +int sdio_read(struct sdio_channel *ch, void *data, int len) +{ + int ret = 0; + struct sdio_al_device *sdio_al_dev = NULL; + + if (!ch) { + pr_err(MODULE_NAME ":%s: NULL channel\n", __func__); + return -ENODEV; + } + if (!data) { + pr_err(MODULE_NAME ":%s: NULL data\n", __func__); + return -ENODEV; + } + if (len == 0) { + pr_err(MODULE_NAME ":channel %s trying to read 0 bytes\n", + ch->name); + return -EINVAL; + } + + if (ch->signature != SDIO_AL_SIGNATURE) { + pr_err(MODULE_NAME ":%s: Invalid signature 0x%x\n", __func__, + ch->signature); + return -ENODEV; + } + + sdio_al_dev = ch->sdio_al_dev; + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + sdio_claim_host(sdio_al_dev->card->sdio_func[0]); + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return -ENODEV; + } + + if (sdio_al_dev->state != CARD_INSERTED) { + pr_err(MODULE_NAME ":%s: sdio_al_dev is in invalid state %d\n", + __func__, sdio_al_dev->state); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return -ENODEV; + } + + /* lpm policy says we can't go to sleep when we have pending rx data, + so either we had rx interrupt and woken up, or we never went to + sleep */ + if (sdio_al_dev->is_ok_to_sleep) { + pr_err(MODULE_NAME ":%s: called when is_ok_to_sleep is set " + "for ch %s, len=%d, last_any_read_avail=%d," + "last_read_avail=%d, last_old_read_avail=%d", + __func__, ch->name, len, + ch->statistics.last_any_read_avail, + ch->statistics.last_read_avail, + ch->statistics.last_old_read_avail); + } + BUG_ON(sdio_al_dev->is_ok_to_sleep); + + if (!ch->is_open) { + pr_err(MODULE_NAME ":reading from closed channel %s\n", + ch->name); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return -EINVAL; + } + + DATA_DEBUG(MODULE_NAME ":start ch %s read %d avail %d.\n", + ch->name, len, ch->read_avail); + + restart_inactive_time(sdio_al_dev); + + if ((ch->is_packet_mode) && (len != ch->read_avail)) { + pr_err(MODULE_NAME ":sdio_read ch %s len != read_avail\n", + ch->name); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return -EINVAL; + } + + if (len > ch->read_avail) { + pr_err(MODULE_NAME ":ERR ch %s: reading more bytes (%d) than" + " the avail(%d).\n", + ch->name, len, ch->read_avail); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return -ENOMEM; + } + + ret = sdio_memcpy_fromio(ch->func, data, PIPE_RX_FIFO_ADDR, len); + + if (ret) { + pr_err(MODULE_NAME ":sdio_read err=%d, len=%d, read_avail=%d\n", + -ret, len, ch->read_avail); + sdio_al_dev->is_err = true; + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return ret; + } + + ch->statistics.total_read_times++; + + /* Remove handled packet from the list regardless if ret is ok */ + if (ch->is_packet_mode) + remove_handled_rx_packet(ch); + else + ch->read_avail -= len; + + ch->total_rx_bytes += len; + DATA_DEBUG(MODULE_NAME ":end ch %s read %d avail %d total %d.\n", + ch->name, len, ch->read_avail, ch->total_rx_bytes); + + if ((ch->read_avail == 0) && !(ch->is_packet_mode)) + ask_reading_mailbox(sdio_al_dev); + + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + + return ret; +} +EXPORT_SYMBOL(sdio_read); + +/** + * Write to SDIO Channel. + * + */ +int sdio_write(struct sdio_channel *ch, const void *data, int len) +{ + int ret = 0; + struct sdio_al_device *sdio_al_dev = NULL; + + if (!ch) { + pr_err(MODULE_NAME ":%s: NULL channel\n", __func__); + return -ENODEV; + } + if (!data) { + pr_err(MODULE_NAME ":%s: NULL data\n", __func__); + return -ENODEV; + } + if (len == 0) { + pr_err(MODULE_NAME ":channel %s trying to write 0 bytes\n", + ch->name); + return -EINVAL; + } + + if (ch->signature != SDIO_AL_SIGNATURE) { + pr_err(MODULE_NAME ":%s: Invalid signature 0x%x\n", __func__, + ch->signature); + return -ENODEV; + } + + sdio_al_dev = ch->sdio_al_dev; + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + sdio_claim_host(sdio_al_dev->card->sdio_func[0]); + + + if (sdio_al_dev->state != CARD_INSERTED) { + pr_err(MODULE_NAME ":%s: sdio_al_dev is in invalid state %d\n", + __func__, sdio_al_dev->state); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return -ENODEV; + } + WARN_ON(len > ch->write_avail); + + if (sdio_al_dev->is_err) { + SDIO_AL_ERR(__func__); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return -ENODEV; + } + + if (!ch->is_open) { + pr_err(MODULE_NAME ":writing to closed channel %s\n", + ch->name); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return -EINVAL; + } + + if (sdio_al_dev->is_ok_to_sleep) { + ret = sdio_al_wake_up(sdio_al_dev, 1); + if (ret) { + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return ret; + } + } else { + restart_inactive_time(sdio_al_dev); + } + + DATA_DEBUG(MODULE_NAME ":start ch %s write %d avail %d.\n", + ch->name, len, ch->write_avail); + + if (len > ch->write_avail) { + pr_err(MODULE_NAME ":ERR ch %s: write more bytes (%d) than " + " available %d.\n", + ch->name, len, ch->write_avail); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return -ENOMEM; + } + + ret = sdio_ch_write(ch, data, len); + if (ret) { + pr_err(MODULE_NAME ":sdio_write on channel %s err=%d\n", + ch->name, -ret); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return ret; + } + + ch->total_tx_bytes += len; + DATA_DEBUG(MODULE_NAME ":end ch %s write %d avail %d total %d.\n", + ch->name, len, ch->write_avail, ch->total_tx_bytes); + + /* Round up to whole buffer size */ + len = ROUND_UP(len, ch->peer_tx_buf_size); + /* Protect from wraparound */ + len = min(len, (int) ch->write_avail); + ch->write_avail -= len; + + if (ch->write_avail < ch->min_write_avail) + ask_reading_mailbox(sdio_al_dev); + + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + + return ret; +} +EXPORT_SYMBOL(sdio_write); + +static int __devinit msm_sdio_al_probe(struct platform_device *pdev) +{ + if (!sdio_al) { + pr_err(MODULE_NAME ": %s: NULL sdio_al\n", __func__); + return -ENODEV; + } + + sdio_al->pdata = pdev->dev.platform_data; + return 0; +} + +static int __devexit msm_sdio_al_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver msm_sdio_al_driver = { + .probe = msm_sdio_al_probe, + .remove = __exit_p(msm_sdio_al_remove), + .driver = { + .name = "msm_sdio_al", + }, +}; + +/** + * Initialize SDIO_AL channels. + * + */ +static int init_channels(struct sdio_al_device *sdio_al_dev) +{ + int ret = 0; + int i; + + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + + sdio_claim_host(sdio_al_dev->card->sdio_func[0]); + + ret = read_sdioc_software_header(sdio_al_dev, + sdio_al_dev->sdioc_sw_header); + if (ret) + goto exit; + + ret = sdio_al_setup(sdio_al_dev); + if (ret) + goto exit; + + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + int ch_name_size; + if (!sdio_al_dev->channel[i].is_valid) + continue; + if (sdio_al->unittest_mode) { + test_channel_init(sdio_al_dev->channel[i].name); + memset(sdio_al_dev->channel[i].ch_test_name, 0, + sizeof(sdio_al_dev->channel[i].ch_test_name)); + ch_name_size = strnlen(sdio_al_dev->channel[i].name, + CHANNEL_NAME_SIZE); + strncpy(sdio_al_dev->channel[i].ch_test_name, + sdio_al_dev->channel[i].name, + ch_name_size); + strncat(sdio_al_dev->channel[i].ch_test_name + + ch_name_size, + SDIO_TEST_POSTFIX, + SDIO_TEST_POSTFIX_SIZE); + pr_debug(MODULE_NAME ":pdev.name = %s\n", + sdio_al_dev->channel[i].ch_test_name); + sdio_al_dev->channel[i].pdev = platform_device_alloc( + sdio_al_dev->channel[i].ch_test_name, -1); + } else { + pr_debug(MODULE_NAME ":pdev.name = %s\n", + sdio_al_dev->channel[i].name); + sdio_al_dev->channel[i].pdev = platform_device_alloc( + sdio_al_dev->channel[i].name, -1); + } + if (!sdio_al_dev->channel[i].pdev) { + pr_err(MODULE_NAME ":NULL platform device for ch %s", + sdio_al_dev->channel[i].name); + sdio_al_dev->channel[i].is_valid = 0; + continue; + } + ret = platform_device_add(sdio_al_dev->channel[i].pdev); + if (ret) { + pr_err(MODULE_NAME ":platform_device_add failed, " + "ret=%d\n", ret); + sdio_al_dev->channel[i].is_valid = 0; + } + } + +exit: + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + return ret; +} + +/** + * Initialize SDIO_AL channels according to the client setup. + * This function also check if the client is in boot mode and + * flashless boot is required to be activated or the client is + * up and running. + * + */ +static int sdio_al_client_setup(struct sdio_al_device *sdio_al_dev) +{ + int ret = 0; + struct sdio_func *func1; + int signature = 0; + + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return -ENODEV; + func1 = sdio_al_dev->card->sdio_func[0]; + + sdio_claim_host(func1); + + /* Read the header signature to determine the status of the MDM + * SDIO Client + */ + signature = sdio_readl(func1, SDIOC_SW_HEADER_ADDR, &ret); + sdio_release_host(func1); + if (ret) { + pr_err(MODULE_NAME ":fail to read signature from sw header.\n"); + return ret; + } + + switch (signature) { + case PEER_SDIOC_SW_MAILBOX_BOOT_SIGNATURE: + if (sdio_al_dev == sdio_al->bootloader_dev) { + pr_info(MODULE_NAME ":setup bootloader on card %d\n", + sdio_al_dev->card->host->index); + return sdio_al_bootloader_setup(); + } else { + pr_info(MODULE_NAME ":wait for bootloader completion " + "on card %d\n", + sdio_al_dev->card->host->index); + return sdio_al_wait_for_bootloader_comp(sdio_al_dev); + } + case PEER_SDIOC_SW_MAILBOX_SIGNATURE: + case PEER_SDIOC_SW_MAILBOX_UT_SIGNATURE: + return init_channels(sdio_al_dev); + default: + pr_err(MODULE_NAME ":Invalid signature 0x%x\n", signature); + return -EINVAL; + } + + return 0; +} + +/* + * SDIO driver functions + */ +static int sdio_al_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *sdio_dev_id) +{ + int ret = 0; + struct sdio_al_device *sdio_al_dev = NULL; + int i; + struct mmc_card *card = NULL; + + if (!func) { + pr_err(MODULE_NAME ": %s: NULL func\n", __func__); + return -ENODEV; + } + card = func->card; + + if (!card) { + pr_err(MODULE_NAME ": %s: NULL card\n", __func__); + return -ENODEV; + } + + if (card->sdio_funcs < SDIO_AL_MAX_FUNCS) { + dev_info(&card->dev, + "SDIO-functions# %d less than expected.\n", + card->sdio_funcs); + return -ENODEV; + } + + /* Check if there is already a device for this card */ + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { + if (sdio_al->devices[i] == NULL) + continue; + if (sdio_al->devices[i]->card == card) + return 0; + } + + dev_info(&card->dev, "SDIO Card claimed.\n"); + + sdio_al_dev = kzalloc(sizeof(struct sdio_al_device), GFP_KERNEL); + if (sdio_al_dev == NULL) + return -ENOMEM; + + sdio_al_dev->state = CARD_INSERTED; + + if (card->host->index == SDIO_BOOTLOADER_CARD_INDEX) + sdio_al->bootloader_dev = sdio_al_dev; + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES ; ++i) + if (sdio_al->devices[i] == NULL) { + sdio_al->devices[i] = sdio_al_dev; + break; + } + if (i == MAX_NUM_OF_SDIO_DEVICES) { + pr_err(MODULE_NAME ":No space in devices array for the " + "device\n"); + return -ENOMEM; + } + + sdio_al_dev->is_ready = false; + + sdio_al_dev->signature = SDIO_AL_SIGNATURE; + + sdio_al_dev->is_suspended = 0; + sdio_al_dev->is_timer_initialized = false; + + sdio_al_dev->lpm_chan = INVALID_SDIO_CHAN; + + sdio_al_dev->card = card; + + sdio_al_dev->mailbox = kzalloc(sizeof(struct sdio_mailbox), GFP_KERNEL); + if (sdio_al_dev->mailbox == NULL) + return -ENOMEM; + + sdio_al_dev->sdioc_sw_header + = kzalloc(sizeof(*sdio_al_dev->sdioc_sw_header), GFP_KERNEL); + if (sdio_al_dev->sdioc_sw_header == NULL) + return -ENOMEM; + + sdio_al_dev->timer.data = (unsigned long)sdio_al_dev; + + wake_lock_init(&sdio_al_dev->wake_lock, WAKE_LOCK_SUSPEND, MODULE_NAME); + /* Don't allow sleep until all required clients register */ + sdio_al_vote_for_sleep(sdio_al_dev, 0); + + sdio_claim_host(card->sdio_func[0]); + + /* Init Func#1 */ + ret = sdio_enable_func(card->sdio_func[0]); + if (ret) { + pr_err(MODULE_NAME ":Fail to enable Func#%d\n", + card->sdio_func[0]->num); + goto exit; + } + + /* Patch Func CIS tuple issue */ + ret = sdio_set_block_size(card->sdio_func[0], SDIO_AL_BLOCK_SIZE); + if (ret) { + pr_err(MODULE_NAME ":Fail to set block size, Func#%d\n", + card->sdio_func[0]->num); + goto exit; + } + sdio_al_dev->card->sdio_func[0]->max_blksize = SDIO_AL_BLOCK_SIZE; + + sdio_al_dev->workqueue = create_singlethread_workqueue("sdio_al_wq"); + sdio_al_dev->sdio_al_work.sdio_al_dev = sdio_al_dev; + init_waitqueue_head(&sdio_al_dev->wait_mbox); + + ret = sdio_al_client_setup(sdio_al_dev); + +exit: + sdio_release_host(card->sdio_func[0]); + return ret; +} + +static void sdio_al_sdio_remove(struct sdio_func *func) +{ + struct sdio_al_device *sdio_al_dev = NULL; + int i; + int state; + struct mmc_card *card = NULL; + + if (!func) { + pr_err(MODULE_NAME ": %s: NULL func\n", __func__); + return; + } + card = func->card; + + if (!card) { + pr_err(MODULE_NAME ": %s: NULL card\n", __func__); + return; + } + + /* Find the sdio_al_device of this card */ + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; ++i) { + if (sdio_al->devices[i] == NULL) + continue; + if (sdio_al->devices[i]->card == card) { + sdio_al_dev = sdio_al->devices[i]; + sdio_al->devices[i] = NULL; + break; + } + } + if (sdio_al_dev == NULL) { + pr_debug(MODULE_NAME ":%s :NULL sdio_al_dev for card %d\n", + __func__, card->host->index); + return; + } + + pr_info(MODULE_NAME ":%s for card %d\n", + __func__, card->host->index); + + if (card->sdio_func[0]) + sdio_claim_host(card->sdio_func[0]); + else + pr_err(MODULE_NAME ":%s: NULL func1 for card %d\n", + __func__, card->host->index); + + if (sdio_al_dev->state == CARD_REMOVED) + return; + + state = sdio_al_dev->state; + sdio_al_dev->state = CARD_REMOVED; + + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) + sdio_al_dev->channel[i].signature = 0x0; + + pr_info(MODULE_NAME ":%s: ask_reading_mailbox for card %d\n", + __func__, card->host->index); + sdio_al_dev->is_ready = false; /* Flag worker to exit */ + sdio_al_dev->ask_mbox = false; + ask_reading_mailbox(sdio_al_dev); /* Wakeup worker */ + + if (state != MODEM_RESTART) { + if (sdio_al_dev->is_timer_initialized) { + pr_info(MODULE_NAME ": %s: Stop timer for card %d", + __func__, sdio_al_dev->card->host->index); + sdio_al_dev->poll_delay_msec = 0; + del_timer_sync(&sdio_al_dev->timer); + } + + if (!sdio_al->unittest_mode) { + pr_info(MODULE_NAME ":%s: notifying clients for " + "card %d\n", + __func__, card->host->index); + for (i = 0; i < SDIO_AL_MAX_CHANNELS; i++) { + if (!sdio_al_dev->channel[i].is_valid) + continue; + platform_device_unregister( + sdio_al_dev->channel[i].pdev); + sdio_al_dev->channel[i].signature = 0x0; + } + } + } + if (card->sdio_func[0]) + sdio_release_host(card->sdio_func[0]); + + pr_info(MODULE_NAME ":%s: vote for sleep for card %d\n", + __func__, card->host->index); + sdio_al_vote_for_sleep(sdio_al_dev, 1); + + pr_info(MODULE_NAME ":%s: flush_workqueue for card %d\n", + __func__, card->host->index); + flush_workqueue(sdio_al_dev->workqueue); + destroy_workqueue(sdio_al_dev->workqueue); + wake_lock_destroy(&sdio_al_dev->wake_lock); + + pr_info(MODULE_NAME ":%s: delete data structures for card %d\n", + __func__, card->host->index); + kfree(sdio_al_dev->sdioc_sw_header); + kfree(sdio_al_dev->mailbox); + kfree(sdio_al_dev); + + pr_info(MODULE_NAME ":%s: sdio card %d removed.\n", __func__, + card->host->index); +} + +static void sdio_print_mailbox(char *prefix_str, struct sdio_mailbox *mailbox) +{ + int k = 0; + char buf[256]; + char buf1[10]; + + if (!mailbox) { + pr_err(MODULE_NAME ": mailbox is NULL\n"); + return; + } + + pr_err(MODULE_NAME ": %s: pipes 0_7: eot=0x%x, " + "thresh=0x%x, overflow=0x%x, " + "underflow=0x%x, mask_thresh=0x%x\n", + prefix_str, mailbox->eot_pipe_0_7, + mailbox->thresh_above_limit_pipe_0_7, + mailbox->overflow_pipe_0_7, + mailbox->underflow_pipe_0_7, + mailbox->mask_thresh_above_limit_pipe_0_7); + + memset(buf, 0, sizeof(buf)); + strncat(buf, ": bytes_avail:", sizeof(buf)); + + for (k = 0 ; k < SDIO_AL_ACTIVE_PIPES ; ++k) { + snprintf(buf1, sizeof(buf1), "%d, ", + mailbox->pipe_bytes_avail[k]); + strncat(buf, buf1, sizeof(buf)); + } + + pr_err(MODULE_NAME "%s", buf); +} + +static void sdio_al_print_info(void) +{ + int i = 0; + int j = 0; + int ret = 0; + struct sdio_mailbox *mailbox = NULL; + struct sdio_mailbox *hw_mailbox = NULL; + struct peer_sdioc_channel_config *ch_config = NULL; + struct sdio_func *func1 = NULL; + struct sdio_func *lpm_func = NULL; + int offset = 0; + int is_ok_to_sleep = 0; + static atomic_t first_time; + char buf[50]; + + if (atomic_read(&first_time) == 1) + return; + + atomic_set(&first_time, 1); + + pr_err(MODULE_NAME ": %s - SDIO DEBUG INFO\n", __func__); + + if (!sdio_al) { + pr_err(MODULE_NAME ": %s - ERROR - sdio_al is NULL\n", + __func__); + return; + } + + pr_err(MODULE_NAME ": GPIO mdm2ap_status=%d\n", + sdio_al->pdata->get_mdm2ap_status()); + + for (j = 0 ; j < MAX_NUM_OF_SDIO_DEVICES ; ++j) { + struct sdio_al_device *sdio_al_dev = sdio_al->devices[j]; + + if (sdio_al_dev == NULL) { + continue; + } + + if (!sdio_al_dev->card && !sdio_al_dev->card->host) { + pr_err(MODULE_NAME ": Card or Host fields " + "are NULL\n);"); + continue; + } + + snprintf(buf, sizeof(buf), "Card#%d: Shadow HW MB", + sdio_al_dev->card->host->index); + + /* printing Shadowing HW Mailbox*/ + mailbox = sdio_al_dev->mailbox; + sdio_print_mailbox(buf, mailbox); + + pr_err(MODULE_NAME ": Card#%d: " + "is_ok_to_sleep=%d\n", + sdio_al_dev->card->host->index, + sdio_al_dev->is_ok_to_sleep); + + + pr_err(MODULE_NAME ": Card#%d: " + "Shadow channels SW MB:", + sdio_al_dev->card->host->index); + + /* printing Shadowing SW Mailbox per channel*/ + for (i = 0 ; i < SDIO_AL_MAX_CHANNELS ; ++i) { + struct sdio_channel *ch = &sdio_al_dev->channel[i]; + + if (ch == NULL) { + continue; + } + + if (!ch->is_valid) { + continue; + } + + ch_config = &sdio_al_dev->channel[i].ch_config; + + pr_err(MODULE_NAME ": Ch %s: max_rx_thres=0x%x, " + "max_tx_thres=0x%x, tx_buf=0x%x, " + "is_packet_mode=%d, " + "max_packet=0x%x, min_write=0x%x", + ch->name, ch_config->max_rx_threshold, + ch_config->max_tx_threshold, + ch_config->tx_buf_size, + ch_config->is_packet_mode, + ch_config->max_packet_size, + ch->min_write_avail); + + if (!ch->is_open) { + pr_err(MODULE_NAME + ": %s is VALID but NOT OPEN. " + "continuing...", ch->name); + continue; + } + + pr_err(MODULE_NAME ": total_rx=0x%x, " + "total_tx=0x%x, " + "read_avail=0x%x, " + "write_avail=0x%x, rx_pending=0x%x, " + "num_reads=0x%x, num_notifs=0x%x", + ch->total_rx_bytes, ch->total_tx_bytes, + ch->read_avail, ch->write_avail, + ch->rx_pending_bytes, + ch->statistics.total_read_times, + ch->statistics.total_notifs); + } /* end loop over all channels */ + + } /* end loop over all devices */ + + /* reading from client and printing is_host_ok_to_sleep per device */ + for (j = 0 ; j < MAX_NUM_OF_SDIO_DEVICES ; ++j) { + struct sdio_al_device *sdio_al_dev = sdio_al->devices[j]; + + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return; + + if (!sdio_al_dev->card->host) { + pr_err(MODULE_NAME ": Host is NULL"); + continue; + } + + if (sdio_al_dev->lpm_chan == INVALID_SDIO_CHAN) { + pr_err(MODULE_NAME ": %s - for " + "Card#%d, is lpm_chan==" + "INVALID_SDIO_CHAN. continuing...", + __func__, sdio_al_dev->card->host->index); + continue; + } + + offset = offsetof(struct peer_sdioc_sw_mailbox, ch_config)+ + sizeof(struct peer_sdioc_channel_config) * + sdio_al_dev->lpm_chan+ + offsetof(struct peer_sdioc_channel_config, is_host_ok_to_sleep); + + lpm_func = sdio_al_dev->card->sdio_func[sdio_al_dev-> + lpm_chan+1]; + if (!lpm_func) { + pr_err(MODULE_NAME ": %s - lpm_func is NULL for card#%d" + " continuing...\n", __func__, + sdio_al_dev->card->host->index); + continue; + } + + sdio_claim_host(sdio_al_dev->card->sdio_func[0]); + ret = sdio_memcpy_fromio(lpm_func, + &is_ok_to_sleep, + SDIOC_SW_MAILBOX_ADDR+offset, + sizeof(int)); + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + + if (ret) + pr_err(MODULE_NAME ": %s - fail to read " + "is_HOST_ok_to_sleep from mailbox for card %d", + __func__, sdio_al_dev->card->host->index); + else + pr_err(MODULE_NAME ": Card#%d: " + "is_HOST_ok_to_sleep=%d\n", + sdio_al_dev->card->host->index, + is_ok_to_sleep); + } + + for (j = 0 ; j < MAX_NUM_OF_SDIO_DEVICES ; ++j) { + struct sdio_al_device *sdio_al_dev = sdio_al->devices[j]; + + if (sdio_al_verify_dev(sdio_al_dev, __func__)) + return; + + if (!sdio_al_dev->card->host) { + pr_err(MODULE_NAME ": Host is NULL"); + continue; + } + + /* Reading HW Mailbox */ + hw_mailbox = sdio_al_dev->mailbox; + func1 = sdio_al_dev->card->sdio_func[0]; + + sdio_claim_host(func1); + ret = sdio_memcpy_fromio(func1, hw_mailbox, + HW_MAILBOX_ADDR, sizeof(*hw_mailbox)); + sdio_release_host(func1); + + if (ret) { + pr_err(MODULE_NAME ": fail to read " + "mailbox for card#%d. " + "continuing...\n", + sdio_al_dev->card->host->index); + continue; + } + + snprintf(buf, sizeof(buf), "Card#%d: Current HW MB", + sdio_al_dev->card->host->index); + + /* Printing HW Mailbox */ + sdio_print_mailbox(buf, hw_mailbox); + } +} + +static struct sdio_device_id sdio_al_sdioid[] = { + {.class = 0, .vendor = 0x70, .device = 0x2460}, + {.class = 0, .vendor = 0x70, .device = 0x0460}, + {.class = 0, .vendor = 0x70, .device = 0x23F1}, + {.class = 0, .vendor = 0x70, .device = 0x23F0}, + {} +}; + +static struct sdio_driver sdio_al_sdiofn_driver = { + .name = "sdio_al_sdiofn", + .id_table = sdio_al_sdioid, + .probe = sdio_al_sdio_probe, + .remove = sdio_al_sdio_remove, +}; + +#ifdef CONFIG_MSM_SUBSYSTEM_RESTART +/* + * Callback for notifications from restart mudule. + * This function handles only the BEFORE_RESTART notification. + * Stop all the activity on the card and notify our clients. + */ +static int sdio_al_subsys_notifier_cb(struct notifier_block *this, + unsigned long notif_type, + void *data) +{ + int i, j; + struct sdio_func *func1 = NULL; + int ret; + + if (notif_type != SUBSYS_BEFORE_SHUTDOWN) { + pr_info(MODULE_NAME ": %s: got notification %ld", + __func__, notif_type); + return NOTIFY_DONE; + } + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; i++) { + struct sdio_al_device *sdio_al_dev = NULL; + if (sdio_al->devices[i] == NULL) { + pr_debug(MODULE_NAME ": %s: NULL device in index %d", + __func__, i); + continue; + } + sdio_al_dev = sdio_al->devices[i]; + if (sdio_al_dev->state == CARD_REMOVED) { + pr_info(MODULE_NAME ": %s: card %d is already removed", + __func__, sdio_al_dev->card->host->index); + continue; + } + if (sdio_al_dev->state == MODEM_RESTART) { + pr_info(MODULE_NAME ": %s: card %d was already " + "notified for modem reset", + __func__, sdio_al_dev->card->host->index); + continue; + } + + pr_info(MODULE_NAME ": %s: Set the state to MODEM_RESTART" + " for card %d", + __func__, sdio_al_dev->card->host->index); + sdio_al_dev->state = MODEM_RESTART; + sdio_al_dev->is_ready = false; + + /* Stop mailbox timer */ + if (sdio_al_dev->is_timer_initialized) { + pr_debug(MODULE_NAME ": %s: Stop timer for card %d", + __func__, sdio_al_dev->card->host->index); + sdio_al_dev->poll_delay_msec = 0; + del_timer_sync(&sdio_al_dev->timer); + } + } + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES; i++) { + struct sdio_al_device *sdio_al_dev; + if (sdio_al->devices[i] == NULL) { + pr_debug(MODULE_NAME ": %s: NULL device in index %d", + __func__, i); + continue; + } + sdio_al_dev = sdio_al->devices[i]; + + if (!sdio_al_verify_func1(sdio_al_dev, __func__)) { + func1 = sdio_al_dev->card->sdio_func[0]; + sdio_claim_host(func1); + + if ((sdio_al_dev->is_ok_to_sleep) && + (!sdio_al_dev->is_err)) { + pr_debug(MODULE_NAME ": %s: wakeup modem for " + "card %d", __func__, + sdio_al_dev->card->host->index); + ret = sdio_al_wake_up(sdio_al_dev, 1); + if (ret == 0) { + pr_info(MODULE_NAME ": %s: " + "sdio_release_irq" + " for card %d", + __func__, + sdio_al_dev->card->host->index); + sdio_release_irq(func1); + } + } else { + pr_debug(MODULE_NAME ": %s: sdio_release_irq" + " for card %d", + __func__, + sdio_al_dev->card->host->index); + sdio_release_irq(func1); + } + } + + pr_debug(MODULE_NAME ": %s: Notifying SDIO clients for card %d", + __func__, sdio_al_dev->card->host->index); + if (!sdio_al->unittest_mode) + for (j = 0; j < SDIO_AL_MAX_CHANNELS; j++) { + if (!sdio_al_dev->channel[j].is_valid) + continue; + platform_device_unregister( + sdio_al_dev->channel[j].pdev); + sdio_al_dev->channel[i].signature = 0x0; + } + + if (!sdio_al_verify_func1(sdio_al_dev, __func__)) + sdio_release_host(sdio_al_dev->card->sdio_func[0]); + + pr_debug(MODULE_NAME ": %s: Allows sleep for card %d", __func__, + sdio_al_dev->card->host->index); + sdio_al_vote_for_sleep(sdio_al_dev, 1); + } + + return NOTIFY_OK; +} + +static struct notifier_block sdio_al_nb = { + .notifier_call = sdio_al_subsys_notifier_cb, +}; +#endif + +/** + * Module Init. + * + * @warn: allocate sdio_al context before registering driver. + * + */ +static int __init sdio_al_init(void) +{ + int ret = 0; + int i; + + pr_debug(MODULE_NAME ":sdio_al_init\n"); + + pr_info(MODULE_NAME ":SDIO-AL SW version %s\n", + DRV_VERSION); + + sdio_al = kzalloc(sizeof(struct sdio_al), GFP_KERNEL); + if (sdio_al == NULL) + return -ENOMEM; + + for (i = 0; i < MAX_NUM_OF_SDIO_DEVICES ; ++i) + sdio_al->devices[i] = NULL; + + sdio_al->unittest_mode = false; + + sdio_al->debug.debug_lpm_on = debug_lpm_on; + sdio_al->debug.debug_data_on = debug_data_on; + +#ifdef CONFIG_DEBUG_FS + sdio_al_debugfs_init(); +#endif + + +#ifdef CONFIG_MSM_SUBSYSTEM_RESTART + sdio_al->subsys_notif_handle = subsys_notif_register_notifier( + "external_modem", &sdio_al_nb); +#endif + + ret = platform_driver_register(&msm_sdio_al_driver); + if (ret) { + pr_err(MODULE_NAME ": platform_driver_register failed: %d\n", + ret); + goto exit; + } + + sdio_register_driver(&sdio_al_sdiofn_driver); +exit: + if (ret) + kfree(sdio_al); + return ret; +} + +/** + * Module Exit. + * + * Free allocated memory. + * Disable SDIO-Card. + * Unregister driver. + * + */ +static void __exit sdio_al_exit(void) +{ + if (sdio_al == NULL) + return; + + pr_debug(MODULE_NAME ":sdio_al_exit\n"); + +#ifdef CONFIG_MSM_SUBSYSTEM_RESTART + subsys_notif_unregister_notifier( + sdio_al->subsys_notif_handle, &sdio_al_nb); +#endif + + sdio_al_tear_down(); + + sdio_unregister_driver(&sdio_al_sdiofn_driver); + + kfree(sdio_al); + +#ifdef CONFIG_DEBUG_FS + sdio_al_debugfs_cleanup(); +#endif + + platform_driver_unregister(&msm_sdio_al_driver); + + pr_debug(MODULE_NAME ":sdio_al_exit complete\n"); +} + +module_init(sdio_al_init); +module_exit(sdio_al_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SDIO Abstraction Layer"); +MODULE_AUTHOR("Amir Samuelov "); +MODULE_VERSION(DRV_VERSION); + diff --git a/arch/arm/mach-msm/sdio_al_dloader.c b/arch/arm/mach-msm/sdio_al_dloader.c new file mode 100644 index 00000000000..a172d2bea50 --- /dev/null +++ b/arch/arm/mach-msm/sdio_al_dloader.c @@ -0,0 +1,2540 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * SDIO-Downloader + * + * To be used with Qualcomm's SDIO-Client connected to this host. + */ + +/* INCLUDES */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* DEFINES AND MACROS */ +#define MAX_NUM_DEVICES 1 +#define TTY_SDIO_DEV "tty_sdio_0" +#define TTY_SDIO_DEV_TEST "tty_sdio_test_0" +#define SDIOC_MAILBOX_ADDRESS 0 +#define SDIO_DL_BLOCK_SIZE 512 +#define SDIO_DL_MAIN_THREAD_NAME "sdio_tty_main_thread" +#define SDIOC_DL_BUFF_ADDRESS 0 +#define SDIOC_UP_BUFF_ADDRESS 0x4 +#define SDIOC_DL_BUFF_SIZE_OFFSET 0x8 +#define SDIOC_UP_BUFF_SIZE_OFFSET 0xC +#define SDIOC_DL_WR_PTR 0x10 +#define SDIOC_DL_RD_PTR 0x14 +#define SDIOC_UL_WR_PTR 0x18 +#define SDIOC_UL_RD_PTR 0x1C +#define SDIOC_EXIT_PTR 0x20 +#define SDIOC_OP_MODE_PTR 0x24 +#define SDIOC_PTRS_OFFSET 0x10 +#define SDIOC_PTR_REGS_SIZE 0x10 +#define SDIOC_CFG_REGS_SIZE 0x10 +#define WRITE_RETRIES 0xFFFFFFFF +#define INPUT_SPEED 4800 +#define OUTPUT_SPEED 4800 +#define SDIOC_EXIT_CODE 0xDEADDEAD +#define SLEEP_MS 10 +#define PRINTING_GAP 200 +#define TIMER_DURATION 10 +#define PUSH_TIMER_DURATION 5000 +#define MULTIPLE_RATIO 1 +#define MS_IN_SEC 1000 +#define BITS_IN_BYTE 8 +#define BYTES_IN_KB 1024 +#define WRITE_TILL_END_RETRIES 5 +#define SDIO_DLD_NORMAL_MODE_NAME "SDIO DLD NORMAL MODE" +#define SDIO_DLD_BOOT_TEST_MODE_NAME "SDIO DLD BOOT TEST MODE" +#define SDIO_DLD_AMSS_TEST_MODE_NAME "SDIO DLD AMSS TEST MODE" +#define TEST_NAME_MAX_SIZE 30 +#define PUSH_STRING +#define SDIO_DLD_OUTGOING_BUFFER_SIZE (48*1024*MULTIPLE_RATIO) + +/* FORWARD DECLARATIONS */ +static int sdio_dld_open(struct tty_struct *tty, struct file *file); +static void sdio_dld_close(struct tty_struct *tty, struct file *file); +static int sdio_dld_write_callback(struct tty_struct *tty, + const unsigned char *buf, int count); +static int sdio_dld_write_room(struct tty_struct *tty); +static int sdio_dld_main_task(void *card); +static void sdio_dld_print_info(void); +#ifdef CONFIG_DEBUG_FS +static int sdio_dld_debug_info_open(struct inode *inode, struct file *file); +static ssize_t sdio_dld_debug_info_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos); +#endif + + +/* STRUCTURES AND TYPES */ +enum sdio_dld_op_mode { + SDIO_DLD_NO_MODE = 0, + SDIO_DLD_NORMAL_MODE = 1, + SDIO_DLD_BOOT_TEST_MODE = 2, + SDIO_DLD_AMSS_TEST_MODE = 3, + SDIO_DLD_NUM_OF_MODES, +}; + +struct sdioc_reg_sequential_chunk_ptrs { + unsigned int dl_wr_ptr; + unsigned int dl_rd_ptr; + unsigned int up_wr_ptr; + unsigned int up_rd_ptr; +}; + +struct sdioc_reg_sequential_chunk_cfg { + unsigned int dl_buff_address; + unsigned int up_buff_address; + unsigned int dl_buff_size; + unsigned int ul_buff_size; +}; + +struct sdioc_reg { + unsigned int reg_val; + unsigned int reg_offset; +}; + +struct sdioc_reg_chunk { + struct sdioc_reg dl_buff_address; + struct sdioc_reg up_buff_address; + struct sdioc_reg dl_buff_size; + struct sdioc_reg ul_buff_size; + struct sdioc_reg dl_wr_ptr; + struct sdioc_reg dl_rd_ptr; + struct sdioc_reg up_wr_ptr; + struct sdioc_reg up_rd_ptr; + struct sdioc_reg good_to_exit_ptr; +}; + +struct sdio_data { + char *data; + int offset_read_p; + int offset_write_p; + int buffer_size; + int num_of_bytes_in_use; +}; + +struct sdio_dld_data { + struct sdioc_reg_chunk sdioc_reg; + struct sdio_data incoming_data; + struct sdio_data outgoing_data; +}; + +struct sdio_dld_wait_event { + wait_queue_head_t wait_event; + int wake_up_signal; +}; + +struct sdio_dld_task { + struct task_struct *dld_task; + const char *task_name; + struct sdio_dld_wait_event exit_wait; + atomic_t please_close; +}; + +#ifdef CONFIG_DEBUG_FS +struct sdio_dloader_debug { + struct dentry *sdio_dld_debug_root; + struct dentry *sdio_al_dloader; +}; + +const struct file_operations sdio_dld_debug_info_ops = { + .open = sdio_dld_debug_info_open, + .write = sdio_dld_debug_info_write, +}; +#endif + +struct sdio_downloader { + int sdioc_boot_func; + struct sdio_dld_wait_event write_callback_event; + struct sdio_dld_task dld_main_thread; + struct tty_driver *tty_drv; + struct tty_struct *tty_str; + struct sdio_dld_data sdio_dloader_data; + struct mmc_card *card; + int(*done_callback)(void); + struct sdio_dld_wait_event main_loop_event; + struct timer_list timer; + unsigned int poll_ms; + struct timer_list push_timer; + unsigned int push_timer_ms; + enum sdio_dld_op_mode op_mode; + char op_mode_name[TEST_NAME_MAX_SIZE]; +}; + +struct sdio_dld_global_info { + int global_bytes_write_toio; + int global_bytes_write_tty; + int global_bytes_read_fromio; + int global_bytes_push_tty; + u64 start_time; + u64 end_time; + u64 delta_jiffies; + unsigned int time_msec; + unsigned int throughput; + int cl_dl_wr_ptr; + int cl_dl_rd_ptr; + int cl_up_wr_ptr; + int cl_up_rd_ptr; + int host_read_ptr; + int host_write_ptr; + int cl_dl_buffer_size; + int cl_up_buffer_size; + int host_outgoing_buffer_size; + int cl_dl_buffer_address; + int cl_up_buffer_address; +}; + +static const struct tty_operations sdio_dloader_tty_ops = { + .open = sdio_dld_open, + .close = sdio_dld_close, + .write = sdio_dld_write_callback, + .write_room = sdio_dld_write_room, +}; + +/* GLOBAL VARIABLES */ +struct sdio_downloader *sdio_dld; +struct sdio_dld_global_info sdio_dld_info; +static char outgoing_data_buffer[SDIO_DLD_OUTGOING_BUFFER_SIZE]; + +static DEFINE_SPINLOCK(lock1); +static unsigned long lock_flags1; +static DEFINE_SPINLOCK(lock2); +static unsigned long lock_flags2; + +/* + * sdio_op_mode sets the operation mode of the sdio_dloader - + * it may be in NORMAL_MODE, BOOT_TEST_MODE or AMSS_TEST_MODE + */ +static int sdio_op_mode = (int)SDIO_DLD_NORMAL_MODE; +module_param(sdio_op_mode, int, 0); + +#ifdef CONFIG_DEBUG_FS + +struct sdio_dloader_debug sdio_dld_debug; + +#define ARR_SIZE 30000 +#define SDIO_DLD_DEBUGFS_INIT_VALUE 87654321 +#define SDIO_DLD_DEBUGFS_CASE_1_CODE 11111111 +#define SDIO_DLD_DEBUGFS_CASE_2_CODE 22222222 +#define SDIO_DLD_DEBUGFS_CASE_3_CODE 33333333 +#define SDIO_DLD_DEBUGFS_CASE_4_CODE 44444444 +#define SDIO_DLD_DEBUGFS_CASE_5_CODE 55555555 +#define SDIO_DLD_DEBUGFS_CASE_6_CODE 66666666 +#define SDIO_DLD_DEBUGFS_CASE_7_CODE 77777777 +#define SDIO_DLD_DEBUGFS_CASE_8_CODE 88888888 +#define SDIO_DLD_DEBUGFS_CASE_9_CODE 99999999 +#define SDIO_DLD_DEBUGFS_CASE_10_CODE 10101010 +#define SDIO_DLD_DEBUGFS_CASE_11_CODE 11001100 +#define SDIO_DLD_DEBUGFS_CASE_12_CODE 12001200 +#define SDIO_DLD_DEBUGFS_LOOP_WAIT 7 +#define SDIO_DLD_DEBUGFS_LOOP_WAKEUP 8 +#define SDIO_DLD_DEBUGFS_CB_WAIT 3 +#define SDIO_DLD_DEBUGFS_CB_WAKEUP 4 + +static int curr_index; +struct ptrs { + int h_w_ptr; + int h_r_ptr; + int c_u_w_ptr; + int c_u_r_ptr; + int code; + int h_has_to_send; + int c_has_to_receive; + int min_of; + int reserve2; + int tty_count; + int write_tty; + int write_toio; + int loop_wait_wake; + int cb_wait_wake; + int c_d_w_ptr; + int c_d_r_ptr; + int to_read; + int push_to_tty; + int global_tty_send; + int global_sdio_send; + int global_tty_received; + int global_sdio_received; + int reserve22; + int reserve23; + int reserve24; + int reserve25; + int reserve26; + int reserve27; + int reserve28; + int reserve29; + int reserve30; + int reserve31; +}; + +struct global_data { + int curr_i; + int duration_ms; + int global_bytes_sent; + int throughput_Mbs; + int host_outgoing_buffer_size_KB; + int client_up_buffer_size_KB; + int client_dl_buffer_size_KB; + int client_dl_buffer_address; + int client_up_buffer_address; + int global_bytes_received; + int global_bytes_pushed; + int reserve11; + int reserve12; + int reserve13; + int reserve14; + int reserve15; + int reserve16; + int reserve17; + int reserve18; + int reserve19; + int reserve20; + int reserve21; + int reserve22; + int reserve23; + int reserve24; + int reserve25; + int reserve26; + int reserve27; + int reserve28; + int reserve29; + int reserve30; + int reserve31; + struct ptrs ptr_array[ARR_SIZE]; +}; + +static struct global_data gd; +static struct debugfs_blob_wrapper blob; +static struct dentry *root; +static struct dentry *dld; + +struct debugfs_global { + int global_8k_has; + int global_9k_has; + int global_min; + int global_count; + int global_write_tty; + int global_write_toio; + int global_bytes_cb_tty; + int global_to_read; + int global_push_to_tty; + int global_tty_send; + int global_sdio_send; + int global_sdio_received; + int global_tty_push; +}; + +static struct debugfs_global debugfs_glob; + +static void update_standard_fields(int index) +{ + + gd.ptr_array[index].global_tty_send = + sdio_dld_info.global_bytes_write_tty; + gd.ptr_array[index].global_sdio_send = + sdio_dld_info.global_bytes_write_toio; + gd.ptr_array[index].global_tty_received = + sdio_dld_info.global_bytes_push_tty; + gd.ptr_array[index].global_sdio_received = + sdio_dld_info.global_bytes_read_fromio; +} + +static void update_gd(int code) +{ + struct sdioc_reg_chunk *reg_str = + &sdio_dld->sdio_dloader_data.sdioc_reg; + struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data; + int index = curr_index%ARR_SIZE; + + gd.curr_i = curr_index; + gd.duration_ms = 0; + gd.global_bytes_sent = 0; + gd.throughput_Mbs = 0; + gd.host_outgoing_buffer_size_KB = 0; + gd.client_up_buffer_size_KB = 0; + gd.client_dl_buffer_size_KB = 0; + gd.client_dl_buffer_address = 0; + gd.client_up_buffer_address = 0; + gd.global_bytes_received = 0; + gd.global_bytes_pushed = 0; + gd.reserve11 = 0; + gd.reserve12 = 0; + gd.reserve13 = 0; + gd.reserve14 = 0; + gd.reserve15 = 0; + gd.reserve16 = 0; + gd.reserve17 = 0; + gd.reserve18 = 0; + gd.reserve19 = 0; + gd.reserve20 = 0; + gd.reserve21 = 0; + gd.reserve22 = 0; + gd.reserve23 = 0; + gd.reserve24 = 0; + gd.reserve25 = 0; + gd.reserve26 = 0; + gd.reserve27 = 0; + gd.reserve28 = 0; + gd.reserve29 = 0; + gd.reserve30 = 0; + gd.reserve31 = 0; + + gd.ptr_array[index].h_w_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*0*/ + gd.ptr_array[index].h_r_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*1*/ + gd.ptr_array[index].c_u_w_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*2*/ + gd.ptr_array[index].c_u_r_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*3*/ + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_INIT_VALUE; /*4*/ + gd.ptr_array[index].h_has_to_send = SDIO_DLD_DEBUGFS_INIT_VALUE;/*5*/ + gd.ptr_array[index].c_has_to_receive = + SDIO_DLD_DEBUGFS_INIT_VALUE; /*6*/ + gd.ptr_array[index].min_of = SDIO_DLD_DEBUGFS_INIT_VALUE; /*7*/ + gd.ptr_array[index].reserve2 = SDIO_DLD_DEBUGFS_INIT_VALUE; /*8*/ + gd.ptr_array[index].tty_count = SDIO_DLD_DEBUGFS_INIT_VALUE; /*9*/ + gd.ptr_array[index].write_tty = SDIO_DLD_DEBUGFS_INIT_VALUE; /*A*/ + gd.ptr_array[index].write_toio = SDIO_DLD_DEBUGFS_INIT_VALUE; /*B*/ + gd.ptr_array[index].loop_wait_wake = + SDIO_DLD_DEBUGFS_INIT_VALUE; /*C*/ + gd.ptr_array[index].cb_wait_wake = SDIO_DLD_DEBUGFS_INIT_VALUE; /*D*/ + gd.ptr_array[index].c_d_w_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*E*/ + gd.ptr_array[index].c_d_r_ptr = SDIO_DLD_DEBUGFS_INIT_VALUE; /*F*/ + gd.ptr_array[index].to_read = + SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x10*/ + gd.ptr_array[index].push_to_tty = + SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x11*/ + gd.ptr_array[index].global_tty_send = + SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x12*/ + gd.ptr_array[index].global_sdio_send = + SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x13*/ + gd.ptr_array[index].global_tty_received = + SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x14*/ + gd.ptr_array[index].global_sdio_received = + SDIO_DLD_DEBUGFS_INIT_VALUE; /*0x15*/ + gd.ptr_array[index].reserve22 = SDIO_DLD_DEBUGFS_INIT_VALUE; + gd.ptr_array[index].reserve23 = SDIO_DLD_DEBUGFS_INIT_VALUE; + gd.ptr_array[index].reserve24 = SDIO_DLD_DEBUGFS_INIT_VALUE; + gd.ptr_array[index].reserve25 = SDIO_DLD_DEBUGFS_INIT_VALUE; + gd.ptr_array[index].reserve26 = SDIO_DLD_DEBUGFS_INIT_VALUE; + gd.ptr_array[index].reserve27 = SDIO_DLD_DEBUGFS_INIT_VALUE; + gd.ptr_array[index].reserve28 = SDIO_DLD_DEBUGFS_INIT_VALUE; + gd.ptr_array[index].reserve29 = SDIO_DLD_DEBUGFS_INIT_VALUE; + gd.ptr_array[index].reserve30 = SDIO_DLD_DEBUGFS_INIT_VALUE; + gd.ptr_array[index].reserve31 = SDIO_DLD_DEBUGFS_INIT_VALUE; + + switch (code) { + case SDIO_DLD_DEBUGFS_CASE_1_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_1_CODE; + gd.ptr_array[index].h_w_ptr = outgoing->offset_write_p; + gd.ptr_array[index].h_r_ptr = outgoing->offset_read_p; + gd.ptr_array[index].c_u_w_ptr = reg_str->up_wr_ptr.reg_val; + gd.ptr_array[index].c_u_r_ptr = reg_str->up_rd_ptr.reg_val; + gd.ptr_array[index].c_d_w_ptr = reg_str->dl_wr_ptr.reg_val; + gd.ptr_array[index].c_d_r_ptr = reg_str->dl_rd_ptr.reg_val; + break; + + case SDIO_DLD_DEBUGFS_CASE_2_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_2_CODE; + gd.ptr_array[index].c_u_r_ptr = reg_str->up_rd_ptr.reg_val; + gd.ptr_array[index].c_u_w_ptr = reg_str->up_wr_ptr.reg_val; + gd.ptr_array[index].h_has_to_send = debugfs_glob.global_8k_has; + gd.ptr_array[index].c_has_to_receive = + debugfs_glob.global_9k_has; + gd.ptr_array[index].min_of = debugfs_glob.global_min; + break; + + case SDIO_DLD_DEBUGFS_CASE_3_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_3_CODE; + gd.ptr_array[index].h_w_ptr = outgoing->offset_write_p; + gd.ptr_array[index].h_r_ptr = outgoing->offset_read_p; + gd.ptr_array[index].write_tty = debugfs_glob.global_write_tty; + break; + + case SDIO_DLD_DEBUGFS_CASE_4_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_4_CODE; + gd.ptr_array[index].h_w_ptr = outgoing->offset_write_p; + gd.ptr_array[index].h_r_ptr = outgoing->offset_read_p; + gd.ptr_array[index].c_u_r_ptr = reg_str->up_rd_ptr.reg_val; + gd.ptr_array[index].c_u_w_ptr = reg_str->up_wr_ptr.reg_val; + gd.ptr_array[index].write_toio = + debugfs_glob.global_write_toio; + break; + + case SDIO_DLD_DEBUGFS_CASE_5_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_5_CODE; + gd.ptr_array[index].tty_count = debugfs_glob.global_count; + break; + + case SDIO_DLD_DEBUGFS_CASE_6_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_6_CODE; + gd.ptr_array[index].loop_wait_wake = 7; + break; + + case SDIO_DLD_DEBUGFS_CASE_7_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_7_CODE; + gd.ptr_array[index].loop_wait_wake = 8; + break; + + case SDIO_DLD_DEBUGFS_CASE_8_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_8_CODE; + gd.ptr_array[index].cb_wait_wake = 3; + break; + + case SDIO_DLD_DEBUGFS_CASE_9_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_9_CODE; + gd.ptr_array[index].cb_wait_wake = 4; + break; + + case SDIO_DLD_DEBUGFS_CASE_10_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_10_CODE; + gd.ptr_array[index].cb_wait_wake = + debugfs_glob.global_bytes_cb_tty; + break; + + case SDIO_DLD_DEBUGFS_CASE_11_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_11_CODE; + gd.ptr_array[index].to_read = debugfs_glob.global_to_read; + break; + + case SDIO_DLD_DEBUGFS_CASE_12_CODE: + gd.ptr_array[index].code = SDIO_DLD_DEBUGFS_CASE_12_CODE; + gd.ptr_array[index].push_to_tty = + debugfs_glob.global_push_to_tty; + break; + + default: + break; + } + update_standard_fields(index); + curr_index++; +} + +static int __init bootloader_debugfs_init(void) +{ + /* /sys/kernel/debug/bootloader there will be dld_arr file */ + root = debugfs_create_dir("bootloader", NULL); + if (!root) { + pr_info(MODULE_NAME ": %s - creating root dir " + "failed\n", __func__); + return -ENODEV; + } + + blob.data = &gd; + blob.size = sizeof(struct global_data); + dld = debugfs_create_blob("dld_arr", S_IRUGO, root, &blob); + if (!dld) { + debugfs_remove_recursive(root); + pr_err(MODULE_NAME ": %s, failed to create debugfs entry\n", + __func__); + return -ENODEV; + } + + return 0; +} + +/* +* for triggering the sdio_dld info use: +* echo 1 > /sys/kernel/debug/sdio_al_dld/sdio_al_dloader_info +*/ +static int sdio_dld_debug_init(void) +{ + sdio_dld_debug.sdio_dld_debug_root = + debugfs_create_dir("sdio_al_dld", NULL); + if (!sdio_dld_debug.sdio_dld_debug_root) { + pr_err(MODULE_NAME ": %s - Failed to create folder. " + "sdio_dld_debug_root is NULL", + __func__); + return -ENOENT; + } + + sdio_dld_debug.sdio_al_dloader = debugfs_create_file( + "sdio_al_dloader_info", + S_IRUGO | S_IWUGO, + sdio_dld_debug.sdio_dld_debug_root, + NULL, + &sdio_dld_debug_info_ops); + + if (!sdio_dld_debug.sdio_al_dloader) { + pr_err(MODULE_NAME ": %s - Failed to create a file. " + "sdio_al_dloader is NULL", + __func__); + debugfs_remove(sdio_dld_debug.sdio_dld_debug_root); + sdio_dld_debug.sdio_dld_debug_root = NULL; + return -ENOENT; + } + + return 0; +} + +static int sdio_dld_debug_info_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t sdio_dld_debug_info_write(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + sdio_dld_print_info(); + return count; +} +#endif /* CONFIG_DEBUG_FS */ + +static void sdio_dld_print_info(void) +{ + + sdio_dld_info.end_time = get_jiffies_64(); /* read the current time */ + sdio_dld_info.delta_jiffies = + sdio_dld_info.end_time - sdio_dld_info.start_time; + sdio_dld_info.time_msec = jiffies_to_msecs(sdio_dld_info.delta_jiffies); + + sdio_dld_info.throughput = sdio_dld_info.global_bytes_write_toio * + BITS_IN_BYTE / sdio_dld_info.time_msec; + sdio_dld_info.throughput /= MS_IN_SEC; + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - DURATION IN MSEC = %d\n", + __func__, + sdio_dld_info.time_msec); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - BYTES WRITTEN ON SDIO BUS " + "= %d...BYTES SENT BY TTY = %d", + __func__, + sdio_dld_info.global_bytes_write_toio, + sdio_dld_info.global_bytes_write_tty); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - BYTES RECEIVED ON SDIO BUS " + "= %d...BYTES SENT TO TTY = %d", + __func__, + sdio_dld_info.global_bytes_read_fromio, + sdio_dld_info.global_bytes_push_tty); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - THROUGHPUT=%d Mbit/Sec", + __func__, sdio_dld_info.throughput); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT DL_BUFFER_SIZE=%d" + " KB..CLIENT UL_BUFFER=%d KB\n", + __func__, + sdio_dld_info.cl_dl_buffer_size/BYTES_IN_KB, + sdio_dld_info.cl_up_buffer_size/BYTES_IN_KB); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - HOST OUTGOING BUFFER_SIZE" + "=%d KB", + __func__, + sdio_dld_info.host_outgoing_buffer_size/BYTES_IN_KB); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT DL BUFFER " + "ADDRESS = 0x%x", __func__, + sdio_dld_info.cl_dl_buffer_address); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT UP BUFFER " + "ADDRESS = 0x%x", + __func__, + sdio_dld_info.cl_up_buffer_address); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT - UPLINK BUFFER - " + "READ POINTER = %d", __func__, + sdio_dld_info.cl_up_rd_ptr); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT - UPLINK BUFFER - " + "WRITE POINTER = %d", __func__, + sdio_dld_info.cl_up_wr_ptr); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT - DOWNLINK BUFFER - " + "READ POINTER = %d", __func__, + sdio_dld_info.cl_dl_rd_ptr); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - CLIENT - DOWNLINK BUFFER - " + "WRITE POINTER = %d", __func__, + sdio_dld_info.cl_dl_wr_ptr); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - HOST - OUTGOING BUFFER - " + "READ POINTER = %d", __func__, + sdio_dld_info.host_read_ptr); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - HOST - OUTGOING BUFFER - " + "WRITE POINTER = %d", __func__, + sdio_dld_info.host_write_ptr); + + pr_info(MODULE_NAME ": %s, FLASHLESS BOOT - END DEBUG INFO", __func__); +} + +/** + * sdio_dld_set_op_mode + * sets the op_mode and the name of the op_mode. Also, in case + * it's invalid mode sets op_mode to SDIO_DLD_NORMAL_MODE + * + * @op_mode: the operation mode to be set + * @return NONE + */ +static void sdio_dld_set_op_mode(enum sdio_dld_op_mode op_mode) +{ + sdio_dld->op_mode = op_mode; + + switch (op_mode) { + case SDIO_DLD_NORMAL_MODE: + memcpy(sdio_dld->op_mode_name, + SDIO_DLD_NORMAL_MODE_NAME, TEST_NAME_MAX_SIZE); + break; + case SDIO_DLD_BOOT_TEST_MODE: + memcpy(sdio_dld->op_mode_name, + SDIO_DLD_BOOT_TEST_MODE_NAME, TEST_NAME_MAX_SIZE); + break; + case SDIO_DLD_AMSS_TEST_MODE: + memcpy(sdio_dld->op_mode_name, + SDIO_DLD_AMSS_TEST_MODE_NAME, TEST_NAME_MAX_SIZE); + break; + default: + sdio_dld->op_mode = SDIO_DLD_NORMAL_MODE; + pr_err(MODULE_NAME ": %s - Invalid Op_Mode = %d. Settings " + "Op_Mode to default - NORMAL_MODE\n", + __func__, op_mode); + memcpy(sdio_dld->op_mode_name, + SDIO_DLD_NORMAL_MODE_NAME, TEST_NAME_MAX_SIZE); + break; + } + + if (sdio_dld->op_mode_name != NULL) { + pr_info(MODULE_NAME ": %s - FLASHLESS BOOT - Op_Mode is set to " + "%s\n", __func__, sdio_dld->op_mode_name); + } else { + pr_info(MODULE_NAME ": %s - FLASHLESS BOOT - op_mode_name is " + "NULL\n", __func__); + } +} + +/** + * sdio_dld_allocate_local_buffers + * allocates local outgoing and incoming buffers and also sets + * threshold for outgoing data. + * + * @return 0 on success or negative value on error. + */ +static int sdio_dld_allocate_local_buffers(void) +{ + struct sdioc_reg_chunk *reg_str = &sdio_dld->sdio_dloader_data. + sdioc_reg; + struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data; + struct sdio_data *incoming = &sdio_dld->sdio_dloader_data.incoming_data; + + incoming->data = + kzalloc(reg_str->dl_buff_size.reg_val, GFP_KERNEL); + + if (!incoming->data) { + pr_err(MODULE_NAME ": %s - param ""incoming->data"" is NULL. " + "Couldn't allocate incoming_data local buffer\n", + __func__); + return -ENOMEM; + } + + incoming->buffer_size = reg_str->dl_buff_size.reg_val; + + outgoing->data = outgoing_data_buffer; + + outgoing->buffer_size = SDIO_DLD_OUTGOING_BUFFER_SIZE; + + if (outgoing->buffer_size != + reg_str->ul_buff_size.reg_val*MULTIPLE_RATIO) { + pr_err(MODULE_NAME ": %s - HOST outgoing buffer size (%d bytes)" + "must be a multiple of ClIENT uplink buffer size (%d " + "bytes). HOST_SIZE == n*CLIENT_SIZE.(n=1,2,3...)\n", + __func__, + SDIO_DLD_OUTGOING_BUFFER_SIZE, + reg_str->ul_buff_size.reg_val); + kfree(incoming->data); + return -EINVAL; + } + + /* keep sdio_dld_info up to date */ + sdio_dld_info.host_outgoing_buffer_size = outgoing->buffer_size; + + return 0; +} + +/** + * sdio_dld_dealloc_local_buffers frees incoming and outgoing + * buffers. + * + * @return None. + */ +static void sdio_dld_dealloc_local_buffers(void) +{ + kfree((void *)sdio_dld->sdio_dloader_data.incoming_data.data); +} + +/** + * mailbox_to_seq_chunk_read_cfg + * reads 4 configuration registers of mailbox from str_func, as + * a sequentail chunk in memory, and updates global struct + * accordingly. + * + * @str_func: a pointer to func struct. + * @return 0 on success or negative value on error. + */ +static int mailbox_to_seq_chunk_read_cfg(struct sdio_func *str_func) +{ + struct sdioc_reg_sequential_chunk_cfg seq_chunk; + struct sdioc_reg_chunk *reg = &sdio_dld->sdio_dloader_data.sdioc_reg; + int status = 0; + + if (!str_func) { + pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n", + __func__); + return -EINVAL; + } + + sdio_claim_host(str_func); + + /* reading SDIOC_MAILBOX_SIZE bytes from SDIOC_MAILBOX_ADDRESS */ + status = sdio_memcpy_fromio(str_func, + (void *)&seq_chunk, + SDIOC_MAILBOX_ADDRESS, + SDIOC_CFG_REGS_SIZE); + if (status) { + pr_err(MODULE_NAME ": %s - sdio_memcpy_fromio()" + " READING CFG MAILBOX failed. status=%d.\n", + __func__, status); + } + + sdio_release_host(str_func); + + reg->dl_buff_address.reg_val = seq_chunk.dl_buff_address; + reg->up_buff_address.reg_val = seq_chunk.up_buff_address; + reg->dl_buff_size.reg_val = seq_chunk.dl_buff_size; + reg->ul_buff_size.reg_val = seq_chunk.ul_buff_size; + + /* keep sdio_dld_info up to date */ + sdio_dld_info.cl_dl_buffer_size = seq_chunk.dl_buff_size; + sdio_dld_info.cl_up_buffer_size = seq_chunk.ul_buff_size; + sdio_dld_info.cl_dl_buffer_address = seq_chunk.dl_buff_address; + sdio_dld_info.cl_up_buffer_address = seq_chunk.up_buff_address; + + return status; +} + +/** + * mailbox_to_seq_chunk_read_ptrs + * reads 4 pointers registers of mailbox from str_func, as a + * sequentail chunk in memory, and updates global struct + * accordingly. + * + * @str_func: a pointer to func struct. + * @return 0 on success or negative value on error. + */ +static int mailbox_to_seq_chunk_read_ptrs(struct sdio_func *str_func) +{ + struct sdioc_reg_sequential_chunk_ptrs seq_chunk; + struct sdioc_reg_chunk *reg = &sdio_dld->sdio_dloader_data.sdioc_reg; + int status = 0; + + struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data; + static int counter = 1; + static int offset_write_p; + static int offset_read_p; + static int up_wr_ptr; + static int up_rd_ptr; + static int dl_wr_ptr; + static int dl_rd_ptr; + + if (!str_func) { + pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n", + __func__); + return -EINVAL; + } + + sdio_claim_host(str_func); + + /* reading SDIOC_MAILBOX_SIZE bytes from SDIOC_MAILBOX_ADDRESS */ + status = sdio_memcpy_fromio(str_func, + (void *)&seq_chunk, + SDIOC_PTRS_OFFSET, + SDIOC_PTR_REGS_SIZE); + if (status) { + pr_err(MODULE_NAME ": %s - sdio_memcpy_fromio()" + " READING PTRS MAILBOX failed. status=%d.\n", + __func__, status); + } + + sdio_release_host(str_func); + + reg->dl_rd_ptr.reg_val = seq_chunk.dl_rd_ptr; + reg->dl_wr_ptr.reg_val = seq_chunk.dl_wr_ptr; + reg->up_rd_ptr.reg_val = seq_chunk.up_rd_ptr; + reg->up_wr_ptr.reg_val = seq_chunk.up_wr_ptr; + + /* keeping sdio_dld_info up to date */ + sdio_dld_info.cl_dl_rd_ptr = seq_chunk.dl_rd_ptr; + sdio_dld_info.cl_dl_wr_ptr = seq_chunk.dl_wr_ptr; + sdio_dld_info.cl_up_rd_ptr = seq_chunk.up_rd_ptr; + sdio_dld_info.cl_up_wr_ptr = seq_chunk.up_wr_ptr; + + + /* DEBUG - if there was a change in value */ + if ((offset_write_p != outgoing->offset_write_p) || + (offset_read_p != outgoing->offset_read_p) || + (up_wr_ptr != reg->up_wr_ptr.reg_val) || + (up_rd_ptr != reg->up_rd_ptr.reg_val) || + (dl_wr_ptr != reg->dl_wr_ptr.reg_val) || + (dl_rd_ptr != reg->dl_rd_ptr.reg_val) || + (counter % PRINTING_GAP == 0)) { + counter = 1; + pr_debug(MODULE_NAME ": %s MailBox pointers: BLOCK_SIZE=%d, " + "hw=%d, hr=%d, cuw=%d, cur=%d, cdw=%d, cdr=%d\n", + __func__, + SDIO_DL_BLOCK_SIZE, + outgoing->offset_write_p, + outgoing->offset_read_p, + reg->up_wr_ptr.reg_val, + reg->up_rd_ptr.reg_val, + reg->dl_wr_ptr.reg_val, + reg->dl_rd_ptr.reg_val); + +#ifdef CONFIG_DEBUG_FS + update_gd(SDIO_DLD_DEBUGFS_CASE_1_CODE); +#endif + /* update static variables */ + offset_write_p = outgoing->offset_write_p; + offset_read_p = outgoing->offset_read_p; + up_wr_ptr = reg->up_wr_ptr.reg_val; + up_rd_ptr = reg->up_rd_ptr.reg_val; + dl_wr_ptr = reg->dl_wr_ptr.reg_val; + dl_rd_ptr = reg->dl_rd_ptr.reg_val; + } else { + counter++; + } + return status; +} + +/** + * sdio_dld_init_func + * enables the sdio func, and sets the func block size. + * + * @str_func: a pointer to func struct. + * @return 0 on success or negative value on error. + */ +static int sdio_dld_init_func(struct sdio_func *str_func) +{ + int status1 = 0; + int status2 = 0; + + if (!str_func) { + pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n", + __func__); + return -EINVAL; + } + + sdio_claim_host(str_func); + + status1 = sdio_enable_func(str_func); + if (status1) { + sdio_release_host(str_func); + pr_err(MODULE_NAME ": %s - sdio_enable_func() failed. " + "status=%d\n", __func__, status1); + return status1; + } + + status2 = sdio_set_block_size(str_func, SDIO_DL_BLOCK_SIZE); + if (status2) { + pr_err(MODULE_NAME ": %s - sdio_set_block_size() failed. " + "status=%d\n", __func__, status2); + status1 = sdio_disable_func(str_func); + if (status1) { + pr_err(MODULE_NAME ": %s - sdio_disable_func() " + "failed. status=%d\n", __func__, status1); + } + sdio_release_host(str_func); + return status2; + } + + sdio_release_host(str_func); + str_func->max_blksize = SDIO_DL_BLOCK_SIZE; + return 0; +} + +/** + * sdio_dld_allocate_buffers + * initializes the sdio func, and then reads the mailbox, in + * order to allocate incoming and outgoing buffers according to + * the size that was read from the mailbox. + * + * @str_func: a pointer to func struct. + * @return 0 on success or negative value on error. + */ +static int sdio_dld_allocate_buffers(struct sdio_func *str_func) +{ + int status = 0; + + if (!str_func) { + pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n", + __func__); + return -EINVAL; + } + + status = mailbox_to_seq_chunk_read_cfg(str_func); + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "mailbox_to_seq_chunk_read_cfg(). status=%d\n", + __func__, status); + return status; + } + + status = sdio_dld_allocate_local_buffers(); + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdio_dld_allocate_local_buffers(). status=%d\n", + __func__, status); + return status; + } + return 0; +} + +/** + * sdio_dld_create_thread + * creates thread and wakes it up. + * + * @return 0 on success or negative value on error. + */ +static int sdio_dld_create_thread(void) +{ + sdio_dld->dld_main_thread.task_name = SDIO_DL_MAIN_THREAD_NAME; + + sdio_dld->dld_main_thread.dld_task = + kthread_create(sdio_dld_main_task, + (void *)(sdio_dld->card), + sdio_dld->dld_main_thread.task_name); + + if (IS_ERR(sdio_dld->dld_main_thread.dld_task)) { + pr_err(MODULE_NAME ": %s - kthread_create() failed\n", + __func__); + return -ENOMEM; + } + wake_up_process(sdio_dld->dld_main_thread.dld_task); + return 0; +} + +/** + * start_timer + * sets the timer and starts. + * + * @timer: the timer to configure and add + * @ms: the ms until it expires + * @return None. + */ +static void start_timer(struct timer_list *timer, unsigned int ms) +{ + if ((ms == 0) || (timer == NULL)) { + pr_err(MODULE_NAME ": %s - invalid parameter", __func__); + } else { + timer->expires = jiffies + + msecs_to_jiffies(ms); + add_timer(timer); + } +} + +/** + * sdio_dld_timer_handler + * this is the timer handler. whenever it is invoked, it wakes + * up the main loop task, and the write callback, and starts + * the timer again. + * + * @data: a pointer to the tty device driver structure. + * @return None. + */ + +static void sdio_dld_timer_handler(unsigned long data) +{ + pr_debug(MODULE_NAME " Timer Expired\n"); + spin_lock_irqsave(&lock2, lock_flags2); + if (sdio_dld->main_loop_event.wake_up_signal == 0) { + sdio_dld->main_loop_event.wake_up_signal = 1; + wake_up(&sdio_dld->main_loop_event.wait_event); + } + spin_unlock_irqrestore(&lock2, lock_flags2); + + sdio_dld->write_callback_event.wake_up_signal = 1; + wake_up(&sdio_dld->write_callback_event.wait_event); + + start_timer(&sdio_dld->timer, sdio_dld->poll_ms); +} + +/** + * sdio_dld_push_timer_handler + * this is a timer handler of the push_timer. + * + * @data: a pointer to the tty device driver structure. + * @return None. + */ +static void sdio_dld_push_timer_handler(unsigned long data) +{ + pr_err(MODULE_NAME " %s - Push Timer Expired... Trying to " + "push data to TTY Core for over then %d ms.\n", + __func__, sdio_dld->push_timer_ms); +} + +/** + * sdio_dld_open + * this is the open callback of the tty driver. + * it initializes the sdio func, allocates the buffers, and + * creates the main thread. + * + * @tty: a pointer to the tty struct. + * @file: file descriptor. + * @return 0 on success or negative value on error. + */ +static int sdio_dld_open(struct tty_struct *tty, struct file *file) +{ + int status = 0; + int func_in_array = + REAL_FUNC_TO_FUNC_IN_ARRAY(sdio_dld->sdioc_boot_func); + struct sdio_func *str_func = sdio_dld->card->sdio_func[func_in_array]; + + sdio_dld->tty_str = tty; + sdio_dld->tty_str->low_latency = 1; + sdio_dld->tty_str->icanon = 0; + set_bit(TTY_NO_WRITE_SPLIT, &sdio_dld->tty_str->flags); + + pr_info(MODULE_NAME ": %s, TTY DEVICE FOR FLASHLESS BOOT OPENED\n", + __func__); + sdio_dld_info.start_time = get_jiffies_64(); /* read the current time */ + + if (!tty) { + pr_err(MODULE_NAME ": %s - param ""tty"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!str_func) { + pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n", + __func__); + return -EINVAL; + } + + atomic_set(&sdio_dld->dld_main_thread.please_close, 0); + sdio_dld->dld_main_thread.exit_wait.wake_up_signal = 0; + + status = sdio_dld_allocate_buffers(str_func); + if (status) { + pr_err(MODULE_NAME ": %s, failed in " + "sdio_dld_allocate_buffers(). status=%d\n", + __func__, status); + return status; + } + + status = sdio_dld_create_thread(); + if (status) { + sdio_dld_dealloc_local_buffers(); + pr_err(MODULE_NAME ": %s, failed in sdio_dld_create_thread()." + "status=%d\n", __func__, status); + return status; + } + + /* init waiting event of the write callback */ + init_waitqueue_head(&sdio_dld->write_callback_event.wait_event); + + /* init waiting event of the main loop */ + init_waitqueue_head(&sdio_dld->main_loop_event.wait_event); + + /* configure and init the timer */ + sdio_dld->poll_ms = TIMER_DURATION; + init_timer(&sdio_dld->timer); + sdio_dld->timer.data = (unsigned long) sdio_dld; + sdio_dld->timer.function = sdio_dld_timer_handler; + sdio_dld->timer.expires = jiffies + + msecs_to_jiffies(sdio_dld->poll_ms); + add_timer(&sdio_dld->timer); + + sdio_dld->push_timer_ms = PUSH_TIMER_DURATION; + init_timer(&sdio_dld->push_timer); + sdio_dld->push_timer.data = (unsigned long) sdio_dld; + sdio_dld->push_timer.function = sdio_dld_push_timer_handler; + + return 0; +} + +/** + * sdio_dld_close + * this is the close callback of the tty driver. it requests + * the main thread to exit, and waits for notification of it. + * it also de-allocates the buffers, and unregisters the tty + * driver and device. + * + * @tty: a pointer to the tty struct. + * @file: file descriptor. + * @return None. + */ +static void sdio_dld_close(struct tty_struct *tty, struct file *file) +{ + int status = 0; + struct sdioc_reg_chunk *reg = &sdio_dld->sdio_dloader_data.sdioc_reg; + + /* informing the SDIOC that it can exit boot phase */ + sdio_dld->sdio_dloader_data.sdioc_reg.good_to_exit_ptr.reg_val = + SDIOC_EXIT_CODE; + + atomic_set(&sdio_dld->dld_main_thread.please_close, 1); + + pr_debug(MODULE_NAME ": %s - CLOSING - WAITING...", __func__); + + wait_event(sdio_dld->dld_main_thread.exit_wait.wait_event, + sdio_dld->dld_main_thread.exit_wait.wake_up_signal); + pr_debug(MODULE_NAME ": %s - CLOSING - WOKE UP...", __func__); + + del_timer_sync(&sdio_dld->timer); + del_timer_sync(&sdio_dld->push_timer); + + sdio_dld_dealloc_local_buffers(); + + tty_unregister_device(sdio_dld->tty_drv, 0); + + status = tty_unregister_driver(sdio_dld->tty_drv); + + if (status) { + pr_err(MODULE_NAME ": %s - tty_unregister_driver() failed\n", + __func__); + } + +#ifdef CONFIG_DEBUG_FS + gd.curr_i = curr_index; + gd.duration_ms = sdio_dld_info.time_msec; + gd.global_bytes_sent = sdio_dld_info.global_bytes_write_toio; + gd.global_bytes_received = 0; + gd.throughput_Mbs = sdio_dld_info.throughput; + gd.host_outgoing_buffer_size_KB = sdio_dld->sdio_dloader_data. + outgoing_data.buffer_size/BYTES_IN_KB; + gd.client_up_buffer_size_KB = reg->ul_buff_size.reg_val/BYTES_IN_KB; + gd.client_dl_buffer_size_KB = reg->dl_buff_size.reg_val/BYTES_IN_KB; + gd.client_dl_buffer_address = reg->dl_buff_address.reg_val; + gd.client_up_buffer_address = reg->up_buff_address.reg_val; + gd.global_bytes_received = sdio_dld_info.global_bytes_read_fromio; + gd.global_bytes_pushed = sdio_dld_info.global_bytes_push_tty; +#endif + + /* saving register values before deallocating sdio_dld + in order to use it in sdio_dld_print_info() through shell command */ + sdio_dld_info.cl_dl_rd_ptr = reg->dl_rd_ptr.reg_val; + sdio_dld_info.cl_dl_wr_ptr = reg->dl_wr_ptr.reg_val; + sdio_dld_info.cl_up_rd_ptr = reg->up_rd_ptr.reg_val; + sdio_dld_info.cl_up_wr_ptr = reg->up_wr_ptr.reg_val; + + sdio_dld_info.host_read_ptr = + sdio_dld->sdio_dloader_data.outgoing_data.offset_read_p; + + sdio_dld_info.host_write_ptr = + sdio_dld->sdio_dloader_data.outgoing_data.offset_write_p; + + sdio_dld_info.cl_dl_buffer_size = + sdio_dld->sdio_dloader_data.sdioc_reg.dl_buff_size.reg_val; + + sdio_dld_info.cl_up_buffer_size = + sdio_dld->sdio_dloader_data.sdioc_reg.ul_buff_size.reg_val; + + sdio_dld_info.host_outgoing_buffer_size = + sdio_dld->sdio_dloader_data.outgoing_data.buffer_size; + + sdio_dld_info.cl_dl_buffer_address = + sdio_dld->sdio_dloader_data.sdioc_reg.dl_buff_address.reg_val; + + sdio_dld_info.cl_up_buffer_address = + sdio_dld->sdio_dloader_data.sdioc_reg.up_buff_address.reg_val; + + sdio_dld_print_info(); + + if (sdio_dld->done_callback) + sdio_dld->done_callback(); + + pr_info(MODULE_NAME ": %s - Freeing sdio_dld data structure, and " + " returning...", __func__); + kfree(sdio_dld); +} + +/** + * writing_size_to_buf + * writes from src buffer into dest buffer. if dest buffer + * reaches its end, rollover happens. + * + * @dest: destination buffer. + * @src: source buffer. + * @dest_wr_ptr: writing pointer in destination buffer. + * @dest_size: destination buffer size. + * @dest_rd_ptr: reading pointer in destination buffer. + * @size_to_write: size of bytes to write. + * @return -how many bytes actually written to destination + * buffer. + * + * ONLY destination buffer is treated as cyclic buffer. + */ +static int writing_size_to_buf(char *dest, + const unsigned char *src, + int *dest_wr_ptr, + int dest_size, + int dest_rd_ptr, + int size_to_write) +{ + int actually_written = 0; + int size_to_add = *dest_wr_ptr; + + if (!dest) { + pr_err(MODULE_NAME ": %s - param ""dest"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!src) { + pr_err(MODULE_NAME ": %s - param ""src"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!dest_wr_ptr) { + pr_err(MODULE_NAME ": %s - param ""dest_wr_ptr"" is NULL.\n", + __func__); + return -EINVAL; + } + + for (actually_written = 0 ; + actually_written < size_to_write ; ++actually_written) { + /* checking if buffer is full */ + if (((size_to_add + 1) % dest_size) == dest_rd_ptr) { + *dest_wr_ptr = size_to_add; + return actually_written; + } + + dest[size_to_add] = src[actually_written]; + size_to_add = (size_to_add+1)%dest_size; + } + + *dest_wr_ptr = size_to_add; + + return actually_written; +} + +/** + * sdioc_bytes_till_end_of_buffer - this routine calculates how many bytes are + * empty/in use. if calculation requires rap around - it will ignore the rap + * around and will do the calculation untill the end of the buffer + * + * @write_ptr: writing pointer. + * @read_ptr: reading pointer. + * @total_size: buffer size. + * @free_bytes: return value-how many free bytes. + * @bytes_in_use: return value-how many bytes in use. + * @return 0 on success or negative value on error. + * + * buffer is treated as a cyclic buffer. + */ +static int sdioc_bytes_till_end_of_buffer(int write_ptr, + int read_ptr, + int total_size, + int *free_bytes, + int *bytes_in_use) +{ + if (!free_bytes) { + pr_err(MODULE_NAME ": %s - param ""free_bytes"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!bytes_in_use) { + pr_err(MODULE_NAME ": %s - param ""bytes_in_use"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (write_ptr >= read_ptr) { + if (read_ptr == 0) + *free_bytes = total_size - write_ptr - 1; + else + *free_bytes = total_size - write_ptr; + *bytes_in_use = write_ptr - read_ptr; + } else { + *bytes_in_use = total_size - read_ptr; + *free_bytes = read_ptr - write_ptr - 1; + } + + return 0; +} + +/** + * sdioc_bytes_free_in_buffer + * this routine calculates how many bytes are free in a buffer + * and how many are in use, according to its reading and + * writing pointer offsets. + * + * @write_ptr: writing pointer. + * @read_ptr: reading pointer. + * @total_size: buffer size. + * @free_bytes: return value-how many free bytes in buffer. + * @bytes_in_use: return value-how many bytes in use in buffer. + * @return 0 on success or negative value on error. + * + * buffer is treated as a cyclic buffer. + */ +static int sdioc_bytes_free_in_buffer(int write_ptr, + int read_ptr, + int total_size, + int *free_bytes, + int *bytes_in_use) +{ + if (!free_bytes) { + pr_err(MODULE_NAME ": %s - param ""free_bytes"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!bytes_in_use) { + pr_err(MODULE_NAME ": %s - param ""bytes_in_use"" is NULL.\n", + __func__); + return -EINVAL; + } + + /* if pointers equel - buffers are empty. nothing to read/write */ + + if (write_ptr >= read_ptr) + *bytes_in_use = write_ptr - read_ptr; + else + *bytes_in_use = total_size - (read_ptr - write_ptr); + + *free_bytes = total_size - *bytes_in_use - 1; + + return 0; +} + +/* +* sdio_dld_write_room +* +* This is the write_room function of the tty driver. +* +* @tty: pointer to tty struct. +* @return free bytes for write. +* +*/ +static int sdio_dld_write_room(struct tty_struct *tty) +{ + return sdio_dld->sdio_dloader_data.outgoing_data.buffer_size; +} + +/** + * sdio_dld_write_callback + * this is the write callback of the tty driver. + * + * @tty: pointer to tty struct. + * @buf: buffer to write from. + * @count: number of bytes to write. + * @return bytes written or negative value on error. + * + * if destination buffer has not enough room for the incoming + * data, returns an error. + */ +static int sdio_dld_write_callback(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data; + int dst_free_bytes = 0; + int dummy = 0; + int status = 0; + int bytes_written = 0; + int total_written = 0; + static int write_retry; + int pending_to_write = count; + +#ifdef CONFIG_DEBUG_FS + debugfs_glob.global_count = count; + update_gd(SDIO_DLD_DEBUGFS_CASE_5_CODE); +#endif + + pr_debug(MODULE_NAME ": %s - WRITING CALLBACK CALLED WITH %d bytes\n", + __func__, count); + + if (!outgoing->data) { + pr_err(MODULE_NAME ": %s - param ""outgoing->data"" is NULL.\n", + __func__); + return -EINVAL; + } + + pr_debug(MODULE_NAME ": %s - WRITE CALLBACK size to write to outgoing" + " buffer %d\n", __func__, count); + + /* as long as there is something to write to outgoing buffer */ + do { + int bytes_to_write = 0; + status = sdioc_bytes_free_in_buffer( + outgoing->offset_write_p, + outgoing->offset_read_p, + outgoing->buffer_size, + &dst_free_bytes, + &dummy); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdioc_bytes_free_in_buffer(). status=%d\n", + __func__, status); + return status; + } + + /* + * if there is free room in outgoing buffer + * lock mutex and request trigger notification from the main + * task. unlock mutex, and wait for sinal + */ + if (dst_free_bytes > 0) { + write_retry = 0; + /* + * if there is more data to write to outgoing buffer + * than it can receive, wait for signal from main task + */ + if (pending_to_write > dst_free_bytes) { + + /* sampling updated dst_free_bytes */ + status = sdioc_bytes_free_in_buffer( + outgoing->offset_write_p, + outgoing->offset_read_p, + outgoing->buffer_size, + &dst_free_bytes, + &dummy); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in " + "Function " + "sdioc_bytes_free_in_buffer(). " + "status=%d\n", __func__, status); + return status; + } + } + + bytes_to_write = min(pending_to_write, dst_free_bytes); + bytes_written = + writing_size_to_buf(outgoing->data, + buf+total_written, + &outgoing->offset_write_p, + outgoing->buffer_size, + outgoing->offset_read_p, + bytes_to_write); + + /* keeping sdio_dld_info up to date */ + sdio_dld_info.host_write_ptr = + sdio_dld->sdio_dloader_data. + outgoing_data.offset_write_p; + +#ifdef CONFIG_DEBUG_FS + debugfs_glob.global_write_tty = bytes_written; + update_gd(SDIO_DLD_DEBUGFS_CASE_3_CODE); +#endif + sdio_dld_info.global_bytes_write_tty += bytes_written; + + spin_lock_irqsave(&lock2, lock_flags2); + if (sdio_dld->main_loop_event.wake_up_signal == 0) { + sdio_dld->main_loop_event.wake_up_signal = 1; + wake_up(&sdio_dld->main_loop_event.wait_event); + } + spin_unlock_irqrestore(&lock2, lock_flags2); + + /* + * although outgoing buffer has enough room, writing + * failed + */ + if (bytes_written != bytes_to_write) { + pr_err(MODULE_NAME ": %s - couldn't write " + "%d bytes to " "outgoing buffer." + "bytes_written=%d\n", + __func__, bytes_to_write, + bytes_written); + return -EIO; + } + + total_written += bytes_written; + pending_to_write -= bytes_written; + outgoing->num_of_bytes_in_use += bytes_written; + + pr_debug(MODULE_NAME ": %s - WRITE CHUNK to outgoing " + "buffer. pending_to_write=%d, " + "outgoing_free_bytes=%d, " + "bytes_written=%d\n", + __func__, + pending_to_write, + dst_free_bytes, + bytes_written); + + } else { + write_retry++; + + pr_debug(MODULE_NAME ": %s - WRITE CALLBACK - NO ROOM." + " pending_to_write=%d, write_retry=%d\n", + __func__, + pending_to_write, + write_retry); + + spin_lock_irqsave(&lock1, lock_flags1); + sdio_dld->write_callback_event.wake_up_signal = 0; + spin_unlock_irqrestore(&lock1, lock_flags1); + + pr_debug(MODULE_NAME ": %s - WRITE CALLBACK - " + "WAITING...", __func__); +#ifdef CONFIG_DEBUG_FS + update_gd(SDIO_DLD_DEBUGFS_CASE_8_CODE); +#endif + wait_event(sdio_dld->write_callback_event.wait_event, + sdio_dld->write_callback_event. + wake_up_signal); +#ifdef CONFIG_DEBUG_FS + update_gd(SDIO_DLD_DEBUGFS_CASE_9_CODE); +#endif + pr_debug(MODULE_NAME ": %s - WRITE CALLBACK - " + "WOKE UP...", __func__); + } + } while (pending_to_write > 0 && write_retry < WRITE_RETRIES); + + if (pending_to_write > 0) { + + pr_err(MODULE_NAME ": %s - WRITE CALLBACK - pending data is " + "%d out of %d > 0. total written in this " + "callback = %d\n", + __func__, pending_to_write, count, total_written); + } + + if (write_retry == WRITE_RETRIES) { + pr_err(MODULE_NAME ": %s, write_retry=%d= max\n", + __func__, write_retry); + } + +#ifdef CONFIG_DEBUG_FS + debugfs_glob.global_bytes_cb_tty = total_written; + update_gd(SDIO_DLD_DEBUGFS_CASE_10_CODE); +#endif + + return total_written; +} + +/** + * sdio_memcpy_fromio_wrapper - + * reads from sdioc, and updats the sdioc registers according + * to how many bytes were actually read. + * + * @str_func: a pointer to func struct. + * @client_rd_ptr: sdioc value of downlink read ptr. + * @client_wr_ptr: sdioc value of downlink write ptr. + * @buffer_to_store: buffer to store incoming data. + * @address_to_read: address to start reading from in sdioc. + * @size_to_read: size of bytes to read. + * @client_buffer_size: sdioc downlink buffer size. + * @return 0 on success or negative value on error. + */ +static int sdio_memcpy_fromio_wrapper(struct sdio_func *str_func, + unsigned int client_rd_ptr, + unsigned int client_wr_ptr, + void *buffer_to_store, + unsigned int address_to_read_from, + int size_to_read, + int client_buffer_size) +{ + int status = 0; + struct sdioc_reg_chunk *reg_str = + &sdio_dld->sdio_dloader_data.sdioc_reg; + + if (!str_func) { + pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!buffer_to_store) { + pr_err(MODULE_NAME ": %s - param ""buffer_to_store"" is " + "NULL.\n", + __func__); + return -EINVAL; + } + + if (size_to_read < 0) { + pr_err(MODULE_NAME ": %s - invalid size to read=%d\n", + __func__, size_to_read); + return -EINVAL; + } + + sdio_claim_host(str_func); + + pr_debug(MODULE_NAME ": %s, READING DATA - from add %d, " + "size_to_read=%d\n", + __func__, address_to_read_from, size_to_read); + + status = sdio_memcpy_fromio(str_func, + (void *)buffer_to_store, + address_to_read_from, + size_to_read); + if (status) { + pr_err(MODULE_NAME ": %s - sdio_memcpy_fromio()" + " DATA failed. status=%d.\n", + __func__, status); + sdio_release_host(str_func); + return status; + } + + /* updating an offset according to cyclic buffer size */ + reg_str->dl_rd_ptr.reg_val = + (reg_str->dl_rd_ptr.reg_val + size_to_read) % + client_buffer_size; + /* keeping sdio_dld_info up to date */ + sdio_dld_info.cl_dl_rd_ptr = reg_str->dl_rd_ptr.reg_val; + + status = sdio_memcpy_toio(str_func, + reg_str->dl_rd_ptr.reg_offset, + (void *)®_str->dl_rd_ptr.reg_val, + sizeof(reg_str->dl_rd_ptr.reg_val)); + + if (status) { + pr_err(MODULE_NAME ": %s - sdio_memcpy_toio() " + "UPDATE PTR failed. status=%d.\n", + __func__, status); + } + + sdio_release_host(str_func); + return status; +} + +/** + * sdio_memcpy_toio_wrapper + * writes to sdioc, and updats the sdioc registers according + * to how many bytes were actually read. + * + * @str_func: a pointer to func struct. + * @client_wr_ptr: sdioc downlink write ptr. + * @h_read_ptr: host incoming read ptrs + * @buf_write_from: buffer to write from. + * @bytes_to_write: number of bytes to write. + * @return 0 on success or negative value on error. + */ +static int sdio_memcpy_toio_wrapper(struct sdio_func *str_func, + unsigned int client_wr_ptr, + unsigned int h_read_ptr, + void *buf_write_from, + int bytes_to_write) +{ + int status = 0; + struct sdioc_reg_chunk *reg_str = + &sdio_dld->sdio_dloader_data.sdioc_reg; + struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data; + + if (!str_func) { + pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!buf_write_from) { + pr_err(MODULE_NAME ": %s - param ""buf_write_from"" is NULL.\n", + __func__); + return -EINVAL; + } + + sdio_claim_host(str_func); + + pr_debug(MODULE_NAME ": %s, WRITING DATA TOIO to address 0x%x, " + "bytes_to_write=%d\n", + __func__, + reg_str->up_buff_address.reg_val + reg_str->up_wr_ptr.reg_val, + bytes_to_write); + + status = sdio_memcpy_toio(str_func, + reg_str->up_buff_address.reg_val + + reg_str->up_wr_ptr.reg_val, + (void *) (outgoing->data + h_read_ptr), + bytes_to_write); + + if (status) { + pr_err(MODULE_NAME ": %s - sdio_memcpy_toio() " + "DATA failed. status=%d.\n", __func__, status); + sdio_release_host(str_func); + return status; + } + + sdio_dld_info.global_bytes_write_toio += bytes_to_write; + outgoing->num_of_bytes_in_use -= bytes_to_write; + + /* + * if writing to client succeeded, then + * 1. update the client up_wr_ptr + * 2. update the host outgoing rd ptr + **/ + reg_str->up_wr_ptr.reg_val = + ((reg_str->up_wr_ptr.reg_val + bytes_to_write) % + reg_str->ul_buff_size.reg_val); + + /* keeping sdio_dld_info up to date */ + sdio_dld_info.cl_up_wr_ptr = reg_str->up_wr_ptr.reg_val; + + outgoing->offset_read_p = + ((outgoing->offset_read_p + bytes_to_write) % + outgoing->buffer_size); + + /* keeping sdio_dld_info up to date*/ + sdio_dld_info.host_read_ptr = outgoing->offset_read_p; + +#ifdef CONFIG_DEBUG_FS + debugfs_glob.global_write_toio = bytes_to_write; + update_gd(SDIO_DLD_DEBUGFS_CASE_4_CODE); +#endif + + /* updating uplink write pointer according to size that was written */ + status = sdio_memcpy_toio(str_func, + reg_str->up_wr_ptr.reg_offset, + (void *)(®_str->up_wr_ptr.reg_val), + sizeof(reg_str->up_wr_ptr.reg_val)); + if (status) { + pr_err(MODULE_NAME ": %s - sdio_memcpy_toio() " + "UPDATE PTR failed. status=%d.\n", + __func__, status); + } + + sdio_release_host(str_func); + return status; +} + +/** + * sdio_dld_read + * reads from sdioc + * + * @client_rd_ptr: sdioc downlink read ptr. + * @client_wr_ptr: sdioc downlink write ptr. + * @reg_str: sdioc register shadowing struct. + * @str_func: a pointer to func struct. + * @bytes_read:how many bytes read. + * @return 0 on success or negative value on error. + */ +static int sdio_dld_read(unsigned int client_rd_ptr, + unsigned int client_wr_ptr, + struct sdioc_reg_chunk *reg_str, + struct sdio_func *str_func, + int *bytes_read) +{ + int status = 0; + struct sdio_data *incoming = &sdio_dld->sdio_dloader_data.incoming_data; + + if (!reg_str) { + pr_err(MODULE_NAME ": %s - param ""reg_str"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!str_func) { + pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!bytes_read) { + pr_err(MODULE_NAME ": %s - param ""bytes_read"" is NULL.\n", + __func__); + return -EINVAL; + } + + /* there is data to read in ONE chunk */ + if (client_wr_ptr > client_rd_ptr) { + status = sdio_memcpy_fromio_wrapper( + str_func, + client_rd_ptr, + client_wr_ptr, + (void *)incoming->data, + reg_str->dl_buff_address.reg_val + client_rd_ptr, + client_wr_ptr - client_rd_ptr, + reg_str->dl_buff_size.reg_val); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdio_memcpy_fromio_wrapper(). " + "SINGLE CHUNK READ. status=%d\n", + __func__, status); + return status; + } + + incoming->num_of_bytes_in_use += client_wr_ptr - client_rd_ptr; + *bytes_read = client_wr_ptr - client_rd_ptr; + +#ifdef CONFIG_DEBUG_FS + debugfs_glob.global_to_read = + client_wr_ptr - client_rd_ptr; + update_gd(SDIO_DLD_DEBUGFS_CASE_11_CODE); +#endif + } + + /* there is data to read in TWO chunks */ + else { + int dl_buf_size = reg_str->dl_buff_size.reg_val; + int tail_size = dl_buf_size - client_rd_ptr; + + /* reading chunk#1: from rd_ptr to the end of the buffer */ + status = sdio_memcpy_fromio_wrapper( + str_func, + client_rd_ptr, + dl_buf_size, + (void *)incoming->data, + reg_str->dl_buff_address.reg_val + client_rd_ptr, + tail_size, + dl_buf_size); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdio_memcpy_fromio_wrapper(). " + "1 of 2 CHUNKS READ. status=%d\n", + __func__, status); + return status; + } + + incoming->num_of_bytes_in_use += tail_size; + *bytes_read = tail_size; + +#ifdef CONFIG_DEBUG_FS + debugfs_glob.global_to_read = tail_size; + update_gd(SDIO_DLD_DEBUGFS_CASE_11_CODE); +#endif + + /* reading chunk#2: reading from beginning buffer */ + status = sdio_memcpy_fromio_wrapper( + str_func, + client_rd_ptr, + client_wr_ptr, + (void *)(incoming->data + tail_size), + reg_str->dl_buff_address.reg_val, + client_wr_ptr, + reg_str->dl_buff_size.reg_val); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdio_memcpy_fromio_wrapper(). " + "2 of 2 CHUNKS READ. status=%d\n", + __func__, status); + return status; + } + + incoming->num_of_bytes_in_use += client_wr_ptr; + *bytes_read += client_wr_ptr; + +#ifdef CONFIG_DEBUG_FS + debugfs_glob.global_to_read = client_wr_ptr; + update_gd(SDIO_DLD_DEBUGFS_CASE_11_CODE); +#endif + } + return 0; +} + +/** + * sdio_dld_main_task + * sdio downloader main task. reads mailboxf checks if there is + * anything to read, checks if host has anything to + * write. + * + * @card: a pointer to mmc_card. + * @return 0 on success or negative value on error. + */ +static int sdio_dld_main_task(void *card) +{ + int status = 0; + struct tty_struct *tty = sdio_dld->tty_str; + struct sdioc_reg_chunk *reg_str = + &sdio_dld->sdio_dloader_data.sdioc_reg; + int func = sdio_dld->sdioc_boot_func; + struct sdio_func *str_func = NULL; + struct sdio_data *outgoing = &sdio_dld->sdio_dloader_data.outgoing_data; + struct sdio_data *incoming = &sdio_dld->sdio_dloader_data.incoming_data; + struct sdio_dld_task *task = &sdio_dld->dld_main_thread; + int retries = 0; +#ifdef PUSH_STRING + int bytes_pushed = 0; +#endif + + msleep(SLEEP_MS); + + if (!card) { + pr_err(MODULE_NAME ": %s - param ""card"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!tty) { + pr_err(MODULE_NAME ": %s - param ""tty"" is NULL.\n", + __func__); + return -EINVAL; + } + + str_func = ((struct mmc_card *)card)-> + sdio_func[REAL_FUNC_TO_FUNC_IN_ARRAY(func)]; + + if (!str_func) { + pr_err(MODULE_NAME ": %s - param ""str_func"" is NULL.\n", + __func__); + return -EINVAL; + } + + while (true) { + /* client pointers for both buffers */ + int client_ul_wr_ptr = 0; + int client_ul_rd_ptr = 0; + int client_dl_wr_ptr = 0; + int client_dl_rd_ptr = 0; + + /* host pointer for outgoing buffer */ + int h_out_wr_ptr = 0; + int h_out_rd_ptr = 0; + + int h_bytes_rdy_wr = 0; + int c_bytes_rdy_rcve = 0; + + int need_to_write = 0; + int need_to_read = 0; + + /* + * forever, checking for signal to die, then read MailBox. + * if nothing to read or nothing to write to client, sleep, + * and again read MailBox + */ + do { + int dummy = 0; + + /* checking if a signal to die was sent */ + if (atomic_read(&task->please_close) == 1) { + + pr_debug(MODULE_NAME ": %s - 0x%x was written " + "to 9K\n", __func__, SDIOC_EXIT_CODE); + + sdio_claim_host(str_func); + + /* returned value is not checked on purpose */ + sdio_memcpy_toio( + str_func, + reg_str->good_to_exit_ptr.reg_offset, + (void *)®_str->good_to_exit_ptr. + reg_val, + sizeof(reg_str->good_to_exit_ptr. + reg_val)); + + sdio_release_host(str_func); + + task->exit_wait.wake_up_signal = 1; + wake_up(&task->exit_wait.wait_event); + return 0; + } + + status = mailbox_to_seq_chunk_read_ptrs(str_func); + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "mailbox_to_seq_chunk_read_ptrs(). " + "status=%d\n", __func__, status); + return status; + } + + /* calculate how many bytes the host has send */ + h_out_wr_ptr = outgoing->offset_write_p; + h_out_rd_ptr = outgoing->offset_read_p; + + status = sdioc_bytes_till_end_of_buffer( + h_out_wr_ptr, + h_out_rd_ptr, + outgoing->buffer_size, + &dummy, + &h_bytes_rdy_wr); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdioc_bytes_till_end_of_buffer(). " + "status=%d\n", __func__, status); + return status; + } + + /* is there something to read from client */ + client_dl_wr_ptr = reg_str->dl_wr_ptr.reg_val; + client_dl_rd_ptr = reg_str->dl_rd_ptr.reg_val; + + if (client_dl_rd_ptr != client_dl_wr_ptr) + need_to_read = 1; + + /* + * calculate how many bytes the client can receive + * from host + */ + client_ul_wr_ptr = reg_str->up_wr_ptr.reg_val; + client_ul_rd_ptr = reg_str->up_rd_ptr.reg_val; + + status = sdioc_bytes_till_end_of_buffer( + client_ul_wr_ptr, + client_ul_rd_ptr, + reg_str->ul_buff_size.reg_val, + &c_bytes_rdy_rcve, + &dummy); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdioc_bytes_till_end_of_buffer(). " + "status=%d\n", __func__, status); + return status; + } + + /* if host has anything to write */ + if (h_bytes_rdy_wr > 0) + need_to_write = 1; + + if (need_to_write || need_to_read) + break; + + spin_lock_irqsave(&lock2, lock_flags2); + sdio_dld->main_loop_event.wake_up_signal = 0; + spin_unlock_irqrestore(&lock2, lock_flags2); + + pr_debug(MODULE_NAME ": %s - MAIN LOOP - WAITING...\n", + __func__); +#ifdef CONFIG_DEBUG_FS + update_gd(SDIO_DLD_DEBUGFS_CASE_6_CODE); +#endif + wait_event(sdio_dld->main_loop_event.wait_event, + sdio_dld->main_loop_event.wake_up_signal); +#ifdef CONFIG_DEBUG_FS + update_gd(SDIO_DLD_DEBUGFS_CASE_7_CODE); +#endif + + pr_debug(MODULE_NAME ": %s - MAIN LOOP - WOKE UP...\n", + __func__); + + } while (1); + + /* CHECK IF THERE IS ANYTHING TO READ IN CLIENT */ + if (need_to_read) { +#ifdef PUSH_STRING + int num_push = 0; + int left = 0; + int bytes_read; +#else + int i; +#endif + need_to_read = 0; + + status = sdio_dld_read(client_dl_rd_ptr, + client_dl_wr_ptr, + reg_str, + str_func, + &bytes_read); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdio_dld_read(). status=%d\n", + __func__, status); + return status; + } + + sdio_dld_info.global_bytes_read_fromio += + bytes_read; + + bytes_pushed = 0; +#ifdef PUSH_STRING + left = incoming->num_of_bytes_in_use; + start_timer(&sdio_dld->push_timer, + sdio_dld->push_timer_ms); + do { + num_push = tty_insert_flip_string( + tty, + incoming->data+bytes_pushed, + left); + + bytes_pushed += num_push; + left -= num_push; + tty_flip_buffer_push(tty); + } while (left != 0); + + del_timer(&sdio_dld->push_timer); + + if (bytes_pushed != incoming->num_of_bytes_in_use) { + pr_err(MODULE_NAME ": %s - failed\n", + __func__); + } +#else + pr_debug(MODULE_NAME ": %s - NEED TO READ %d\n", + __func__, incoming->num_of_bytes_in_use); + + for (i = 0 ; i < incoming->num_of_bytes_in_use ; ++i) { + int err = 0; + err = tty_insert_flip_char(tty, + incoming->data[i], + TTY_NORMAL); + tty_flip_buffer_push(tty); + } + + pr_debug(MODULE_NAME ": %s - JUST READ\n", __func__); +#endif /*PUSH_STRING*/ + sdio_dld_info.global_bytes_push_tty += + incoming->num_of_bytes_in_use; +#ifdef CONFIG_DEBUG_FS + debugfs_glob.global_push_to_tty = bytes_read; + update_gd(SDIO_DLD_DEBUGFS_CASE_12_CODE); +#endif + incoming->num_of_bytes_in_use = 0; + tty_flip_buffer_push(tty); + } + + /* CHECK IF THERE IS ANYTHING TO WRITE IN HOST AND HOW MUCH */ + if (need_to_write) { + int dummy = 0; + + do { + int bytes_to_write = min(c_bytes_rdy_rcve, + h_bytes_rdy_wr); + + /* + * in case nothing to send or no room to + * receive + */ + if (bytes_to_write == 0) + break; + + if (client_ul_rd_ptr == 0 && + (client_ul_rd_ptr != client_ul_wr_ptr)) + break; + + /* + * if client_rd_ptr points to start, but there + * is data to read wait until WRITE_TILL_END + * before writing a chunk of data, to avoid + * writing until (BUF_SIZE - 1), because it will + * yield an extra write of "1" bytes + */ + if (client_ul_rd_ptr == 0 && + (client_ul_rd_ptr != client_ul_wr_ptr) && + retries < WRITE_TILL_END_RETRIES) { + retries++; + break; + } + retries = 0; + +#ifdef CONFIG_DEBUG_FS + debugfs_glob.global_8k_has = h_bytes_rdy_wr; + debugfs_glob.global_9k_has = c_bytes_rdy_rcve; + debugfs_glob.global_min = bytes_to_write; + update_gd(SDIO_DLD_DEBUGFS_CASE_2_CODE); +#endif + need_to_write = 0; + + pr_debug(MODULE_NAME ": %s - NEED TO WRITE " + "TOIO %d\n", + __func__, bytes_to_write); + + status = sdio_memcpy_toio_wrapper( + str_func, + reg_str->up_wr_ptr.reg_val, + outgoing->offset_read_p, + (void *)((char *)outgoing->data + + outgoing->offset_read_p), + bytes_to_write); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in " + "Function " + "sdio_memcpy_toio_wrapper(). " + "SINGLE CHUNK WRITE. " + "status=%d\n", + __func__, status); + return status; + } + + sdio_claim_host(str_func); + + status = sdio_memcpy_fromio( + str_func, + (void *)®_str->up_rd_ptr.reg_val, + SDIOC_UL_RD_PTR, + sizeof(reg_str->up_rd_ptr.reg_val)); + + if (status) { + pr_err(MODULE_NAME ": %s - " + "sdio_memcpy_fromio() " + "failed. status=%d\n", + __func__, status); + sdio_release_host(str_func); + + return status; + } + + sdio_release_host(str_func); + + spin_lock_irqsave(&lock1, lock_flags1); + if (sdio_dld->write_callback_event. + wake_up_signal == 0) { + sdio_dld->write_callback_event. + wake_up_signal = 1; + wake_up(&sdio_dld-> + write_callback_event. + wait_event); + } + + spin_unlock_irqrestore(&lock1, lock_flags1); + client_ul_wr_ptr = reg_str->up_wr_ptr.reg_val; + client_ul_rd_ptr = reg_str->up_rd_ptr.reg_val; + + status = sdioc_bytes_till_end_of_buffer( + client_ul_wr_ptr, + client_ul_rd_ptr, + reg_str->ul_buff_size.reg_val, + &c_bytes_rdy_rcve, + &dummy); + + /* calculate how many bytes host has to send */ + h_out_wr_ptr = outgoing->offset_write_p; + h_out_rd_ptr = outgoing->offset_read_p; + + status = sdioc_bytes_till_end_of_buffer( + h_out_wr_ptr, + h_out_rd_ptr, + outgoing->buffer_size, + &dummy, + &h_bytes_rdy_wr); + + } while (h_out_wr_ptr != h_out_rd_ptr); + } + } + return 0; +} + +/** + * sdio_dld_init_global + * initialization of sdio_dld global struct + * + * @card: a pointer to mmc_card. + * @return 0 on success or negative value on error. + */ +static int sdio_dld_init_global(struct mmc_card *card, + int(*done)(void)) +{ + if (!card) { + pr_err(MODULE_NAME ": %s - param ""card"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!done) { + pr_err(MODULE_NAME ": %s - param ""done"" is NULL.\n", + __func__); + return -EINVAL; + } + + sdio_dld->done_callback = done; + sdio_dld->card = card; + init_waitqueue_head(&sdio_dld->dld_main_thread.exit_wait.wait_event); + sdio_dld->write_callback_event.wake_up_signal = 1; + sdio_dld->main_loop_event.wake_up_signal = 1; + + sdio_dld->sdio_dloader_data.sdioc_reg.dl_buff_size.reg_offset = + SDIOC_DL_BUFF_SIZE_OFFSET; + + sdio_dld->sdio_dloader_data.sdioc_reg.dl_rd_ptr.reg_offset = + SDIOC_DL_RD_PTR; + + sdio_dld->sdio_dloader_data.sdioc_reg.dl_wr_ptr.reg_offset = + SDIOC_DL_WR_PTR; + + sdio_dld->sdio_dloader_data.sdioc_reg.ul_buff_size.reg_offset = + SDIOC_UP_BUFF_SIZE_OFFSET; + + sdio_dld->sdio_dloader_data.sdioc_reg.up_rd_ptr.reg_offset = + SDIOC_UL_RD_PTR; + + sdio_dld->sdio_dloader_data.sdioc_reg.up_wr_ptr.reg_offset = + SDIOC_UL_WR_PTR; + + sdio_dld->sdio_dloader_data.sdioc_reg.good_to_exit_ptr.reg_offset = + SDIOC_EXIT_PTR; + + sdio_dld->sdio_dloader_data.sdioc_reg.dl_buff_address.reg_offset = + SDIOC_DL_BUFF_ADDRESS; + + sdio_dld->sdio_dloader_data.sdioc_reg.up_buff_address.reg_offset = + SDIOC_UP_BUFF_ADDRESS; + + sdio_dld_set_op_mode(SDIO_DLD_NORMAL_MODE); + + return 0; +} + +/** + * sdio_downloader_setup + * initializes the TTY driver + * + * @card: a pointer to mmc_card. + * @num_of_devices: number of devices. + * @channel_number: channel number. + * @return 0 on success or negative value on error. + * + * The TTY stack needs to know in advance how many devices it should + * plan to manage. Use this call to set up the ports that will + * be exported through SDIO. + */ +int sdio_downloader_setup(struct mmc_card *card, + unsigned int num_of_devices, + int channel_number, + int(*done)(void)) +{ + int status = 0; + int result = 0; + int func_in_array = 0; + struct sdio_func *str_func = NULL; + struct device *tty_dev; + + if (num_of_devices == 0 || num_of_devices > MAX_NUM_DEVICES) { + pr_err(MODULE_NAME ": %s - invalid number of devices\n", + __func__); + return -EINVAL; + } + + if (!card) { + pr_err(MODULE_NAME ": %s - param ""card"" is NULL.\n", + __func__); + return -EINVAL; + } + + if (!done) { + pr_err(MODULE_NAME ": %s - param ""done"" is NULL.\n", + __func__); + return -EINVAL; + } + + sdio_dld = kzalloc(sizeof(struct sdio_downloader), GFP_KERNEL); + if (!sdio_dld) { + pr_err(MODULE_NAME ": %s - couldn't allocate sdio_dld data " + "structure.", __func__); + return -ENOMEM; + } + +#ifdef CONFIG_DEBUG_FS + bootloader_debugfs_init(); +#endif /* CONFIG_DEBUG_FS */ + + status = sdio_dld_init_global(card, done); + + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdio_dld_init_global(). status=%d\n", + __func__, status); + kfree(sdio_dld); + return status; + } + + sdio_dld->tty_drv = alloc_tty_driver(num_of_devices); + + if (!sdio_dld->tty_drv) { + pr_err(MODULE_NAME ": %s - param ""sdio_dld->tty_drv"" is " + "NULL.\n", __func__); + kfree(sdio_dld); + return -EINVAL; + } + + sdio_dld_set_op_mode((enum sdio_dld_op_mode)sdio_op_mode); + + /* according to op_mode, a different tty device is created */ + if (sdio_dld->op_mode == SDIO_DLD_BOOT_TEST_MODE) + sdio_dld->tty_drv->name = TTY_SDIO_DEV_TEST; + else + sdio_dld->tty_drv->name = TTY_SDIO_DEV; + + sdio_dld->tty_drv->owner = THIS_MODULE; + sdio_dld->tty_drv->driver_name = "SDIO_Dloader"; + + /* uses dynamically assigned dev_t values */ + sdio_dld->tty_drv->type = TTY_DRIVER_TYPE_SERIAL; + sdio_dld->tty_drv->subtype = SERIAL_TYPE_NORMAL; + sdio_dld->tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV + | TTY_DRIVER_RESET_TERMIOS; + + /* initializing the tty driver */ + sdio_dld->tty_drv->init_termios = tty_std_termios; + sdio_dld->tty_drv->init_termios.c_cflag = + B4800 | CS8 | CREAD | HUPCL | CLOCAL; + sdio_dld->tty_drv->init_termios.c_ispeed = INPUT_SPEED; + sdio_dld->tty_drv->init_termios.c_ospeed = OUTPUT_SPEED; + + tty_set_operations(sdio_dld->tty_drv, &sdio_dloader_tty_ops); + + status = tty_register_driver(sdio_dld->tty_drv); + if (status) { + put_tty_driver(sdio_dld->tty_drv); + pr_err(MODULE_NAME ": %s - tty_register_driver() failed\n", + __func__); + + sdio_dld->tty_drv = NULL; + kfree(sdio_dld); + return status; + } + + tty_dev = tty_register_device(sdio_dld->tty_drv, 0, NULL); + if (IS_ERR(tty_dev)) { + pr_err(MODULE_NAME ": %s - tty_register_device() " + "failed\n", __func__); + tty_unregister_driver(sdio_dld->tty_drv); + kfree(sdio_dld); + return PTR_ERR(tty_dev); + } + + sdio_dld->sdioc_boot_func = SDIOC_CHAN_TO_FUNC_NUM(channel_number); + func_in_array = REAL_FUNC_TO_FUNC_IN_ARRAY(sdio_dld->sdioc_boot_func); + str_func = sdio_dld->card->sdio_func[func_in_array]; + status = sdio_dld_init_func(str_func); + if (status) { + pr_err(MODULE_NAME ": %s - Failure in Function " + "sdio_dld_init_func(). status=%d\n", + __func__, status); + goto exit_err; + } + +#ifdef CONFIG_DEBUG_FS + sdio_dld_debug_init(); +#endif + + sdio_claim_host(str_func); + + /* + * notifing the client by writing what mode we are by writing + * to a special register + */ + status = sdio_memcpy_toio(str_func, + SDIOC_OP_MODE_PTR, + (void *)&sdio_dld->op_mode, + sizeof(sdio_dld->op_mode)); + + sdio_release_host(str_func); + + if (status) { + pr_err(MODULE_NAME ": %s - sdio_memcpy_toio() " + "writing to OP_MODE_REGISTER failed. " + "status=%d.\n", + __func__, status); + goto exit_err; + } + + return 0; + +exit_err: + tty_unregister_device(sdio_dld->tty_drv, 0); + result = tty_unregister_driver(sdio_dld->tty_drv); + if (result) + pr_err(MODULE_NAME ": %s - tty_unregister_driver() " + "failed. result=%d\n", __func__, -result); + kfree(sdio_dld); + return status; +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SDIO Downloader"); +MODULE_AUTHOR("Yaniv Gardi "); +MODULE_VERSION(DRV_VERSION); + diff --git a/arch/arm/mach-msm/sdio_al_private.h b/arch/arm/mach-msm/sdio_al_private.h new file mode 100644 index 00000000000..20a12998a9c --- /dev/null +++ b/arch/arm/mach-msm/sdio_al_private.h @@ -0,0 +1,255 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * SDIO-Abstraction-Layer internal interface. + */ + +#ifndef __SDIO_AL_PRIVATE__ +#define __SDIO_AL_PRIVATE__ + +#include +#include +#include + +#define DRV_VERSION "1.30" +#define MODULE_NAME "sdio_al" +#define SDIOC_CHAN_TO_FUNC_NUM(x) ((x)+2) +#define REAL_FUNC_TO_FUNC_IN_ARRAY(x) ((x)-1) +#define SDIO_PREFIX "SDIO_" +#define PEER_CHANNEL_NAME_SIZE 4 +#define CHANNEL_NAME_SIZE (sizeof(SDIO_PREFIX) + PEER_CHANNEL_NAME_SIZE) +#define SDIO_TEST_POSTFIX_SIZE 5 + +struct sdio_al_device; /* Forward Declaration */ + +/** + * Peer SDIO-Client channel configuration. + * + * @is_ready - channel is ready and the data is valid. + * + * @max_rx_threshold - maximum rx threshold, according to the + * total buffers size on the peer pipe. + * @max_tx_threshold - maximum tx threshold, according to the + * total buffers size on the peer pipe. + * @tx_buf_size - size of a single buffer on the peer pipe; a + * transfer smaller than the buffer size still + * make the buffer unusable for the next transfer. + * @max_packet_size + * @is_host_ok_to_sleep - Host marks this bit when it's okay to + * sleep (no pending transactions) + */ +struct peer_sdioc_channel_config { + u32 is_ready; + u32 max_rx_threshold; /* Downlink */ + u32 max_tx_threshold; /* Uplink */ + u32 tx_buf_size; + u32 max_packet_size; + u32 is_host_ok_to_sleep; + u32 is_packet_mode; + u32 reserved[25]; +}; + + +/** + * Peer SDIO-Client channel statsitics. + * + * @last_any_read_avail - the last read avail in all the + * channels including this channel. + * @last_read_avail - the last read_avail that was read from HW + * mailbox. + * @last_old_read_avail - the last read_avail channel shadow. + * @total_notifs - the total number of read notifications sent + * to this channel client + * @total_read_times - the total number of successful sdio_read + * calls for this channel + */ +struct sdio_channel_statistics { + int last_any_read_avail; + int last_read_avail; + int last_old_read_avail; + int total_notifs; + int total_read_times; +}; + +/** + * SDIO Channel context. + * + * @name - channel name. Used by the caller to open the + * channel. + * + * @read_threshold - Threshold on SDIO-Client mailbox for Rx + * Data available bytes. When the limit exceed + * the SDIO-Client generates an interrupt to the + * host. + * + * @write_threshold - Threshold on SDIO-Client mailbox for Tx + * Data available bytes. When the limit exceed + * the SDIO-Client generates an interrupt to the + * host. + * + * @def_read_threshold - Default theshold on SDIO-Client for Rx + * + * @min_write_avail - Threshold of minimal available bytes + * to write. Below that threshold the host + * will initiate reading the mailbox. + * + * @poll_delay_msec - Delay between polling the mailbox. When + * the SDIO-Client doesn't generates EOT + * interrupt for Rx Available bytes, the host + * should poll the SDIO-Client mailbox. + * + * @is_packet_mode - The host get interrupt when a packet is + * available at the SDIO-client (pipe EOT + * indication). + * + * @num - channel number. + * + * @notify - Client's callback. Should not call sdio read/write. + * + * @priv - Client's private context, provided to callback. + * + * @is_valid - Channel is used (we have a list of + * SDIO_AL_MAX_CHANNELS and not all of them are in + * use). + * + * @is_open - Channel is open. + * + * @func - SDIO Function handle. + * + * @rx_pipe_index - SDIO-Client Pipe Index for Rx Data. + * + * @tx_pipe_index - SDIO-Client Pipe Index for Tx Data. + * + * @ch_lock - Channel lock to protect channel specific Data + * + * @rx_pending_bytes - Total number of Rx pending bytes, at Rx + * packet list. Maximum of 16KB-1 limited by + * SDIO-Client specification. + * + * @read_avail - Available bytes to read. + * + * @write_avail - Available bytes to write. + * + * @rx_size_list_head - The head of Rx Pending Packets List. + * + * @pdev - platform device - clients to probe for the sdio-al. + * + * @signature - Context Validity check. + * + * @sdio_al_dev - a pointer to the sdio_al_device instance of + * this channel + * + * @statistics - channel statistics + * + */ +struct sdio_channel { + /* Channel Configuration Parameters*/ + char name[CHANNEL_NAME_SIZE]; + char ch_test_name[CHANNEL_NAME_SIZE+SDIO_TEST_POSTFIX_SIZE]; + int read_threshold; + int write_threshold; + int def_read_threshold; + int min_write_avail; + int poll_delay_msec; + int is_packet_mode; + + struct peer_sdioc_channel_config ch_config; + + /* Channel Info */ + int num; + + void (*notify)(void *priv, unsigned channel_event); + void *priv; + + int is_valid; + int is_open; + + struct sdio_func *func; + + int rx_pipe_index; + int tx_pipe_index; + + struct mutex ch_lock; + + u32 read_avail; + u32 write_avail; + + u32 peer_tx_buf_size; + + u16 rx_pending_bytes; + + struct list_head rx_size_list_head; + + struct platform_device *pdev; + + u32 total_rx_bytes; + u32 total_tx_bytes; + + u32 signature; + + struct sdio_al_device *sdio_al_dev; + + struct sdio_channel_statistics statistics; +}; + +/** + * sdio_downloader_setup + * initializes the TTY driver + * + * @card: a pointer to mmc_card. + * @num_of_devices: number of devices. + * @channel_number: channel number. + * @return 0 on success or negative value on error. + * + * The TTY stack needs to know in advance how many devices it should + * plan to manage. Use this call to set up the ports that will + * be exported through SDIO. + */ +int sdio_downloader_setup(struct mmc_card *card, + unsigned int num_of_devices, + int func_number, + int(*func)(void)); + +/** + * test_channel_init + * initializes a test channel + * + * @name: the channel name. + * @return 0 on success or negative value on error. + * + */ +int test_channel_init(char *name); + +/** + * sdio_al_register_lpm_cb + * Allow the sdio_al test to register for lpm voting + * notifications + * + * @device_handle: the device handle. + * @wakeup_callback: callback function to be called when voting. + * + */ +void sdio_al_register_lpm_cb(void *device_handle, + int(*lpm_callback)(void *, int)); + +/** + * sdio_al_unregister_lpm_cb + * Allow the sdio_al test to unregister for lpm voting + * notifications + * + * @device_handle: the device handle. + * + */ +void sdio_al_unregister_lpm_cb(void *device_handle); + +#endif /* __SDIO_AL_PRIVATE__ */ diff --git a/arch/arm/mach-msm/sdio_al_test.c b/arch/arm/mach-msm/sdio_al_test.c new file mode 100644 index 00000000000..35ed55e2145 --- /dev/null +++ b/arch/arm/mach-msm/sdio_al_test.c @@ -0,0 +1,2199 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * SDIO-Abstraction-Layer Test Module. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +enum lpm_test_msg_type { + LPM_NO_MSG, + LPM_MSG_SEND, + LPM_MSG_REC, + LPM_SLEEP, + LPM_WAKEUP +}; + +#define LPM_NO_MSG_NAME "LPM No Event" +#define LPM_MSG_SEND_NAME "LPM Send Msg Event" +#define LPM_MSG_REC_NAME "LPM Receive Msg Event" +#define LPM_SLEEP_NAME "LPM Sleep Event" +#define LPM_WAKEUP_NAME "LPM Wakeup Event" + +/** Module name string */ +#define TEST_MODULE_NAME "sdio_al_test" + +#define TEST_SIGNATURE 0x12345678 +#define TEST_CONFIG_SIGNATURE 0xBEEFCAFE + +#define MAX_XFER_SIZE (16*1024) +#define SMEM_MAX_XFER_SIZE 0xBC000 +#define A2_MIN_PACKET_SIZE 5 + +#define TEST_DBG(x...) if (test_ctx->runtime_debug) pr_info(x) + +#define LPM_TEST_NUM_OF_PACKETS 100 +#define LPM_ARRAY_SIZE (7*LPM_TEST_NUM_OF_PACKETS) +#define SDIO_LPM_TEST "sdio_lpm_test_reading_task" +#define LPM_TEST_CONFIG_SIGNATURE 0xDEADBABE + +enum sdio_test_case_type { + SDIO_TEST_LOOPBACK_HOST, + SDIO_TEST_LOOPBACK_CLIENT, + SDIO_TEST_LPM_HOST_WAKER, + SDIO_TEST_LPM_CLIENT_WAKER, + SDIO_TEST_LPM_RANDOM, + SDIO_TEST_HOST_SENDER_NO_LP, + SDIO_TEST_PERF, /* must be last since is not part of the 9k tests */ +}; + +struct lpm_task { + struct task_struct *lpm_task; + const char *task_name; +}; + +struct lpm_entry_type { + enum lpm_test_msg_type msg_type; + u32 counter; + u32 current_ms; +}; + +static DEFINE_SPINLOCK(lpm_lock); +unsigned long flags; + +struct lpm_msg { + u32 signature; + u32 counter; + u32 reserve1; + u32 reserve2; +}; + +struct lpm_test_struct { + u64 start_jiffies; + u64 end_jiffies; + unsigned int total_ms; + u32 next_avail_entry_in_array; + u32 next_index_in_sent_msg; + u32 last_index_in_rec_msg; + struct lpm_task lpm_test_task; + struct test_channel *test_ch; + struct lpm_entry_type lpm_arr[LPM_ARRAY_SIZE]; +}; + +struct test_config_msg { + u32 signature; + u32 test_case; + u32 test_param; + u32 num_packets; + u32 num_iterations; +}; + +struct test_result_msg { + u32 signature; + u32 is_successful; +}; + +struct test_work { + struct work_struct work; + struct test_channel *test_ch; +}; + +enum sdio_channels_ids { + SDIO_RPC, + SDIO_QMI, + SDIO_RMNT, + SDIO_DIAG, + SDIO_DUN, + SDIO_SMEM, + SDIO_CIQ, + SDIO_MAX_CHANNELS +}; + +enum sdio_test_results { + TEST_NO_RESULT, + TEST_FAILED, + TEST_PASSED +}; + +enum sdio_lpm_vote_state { + SDIO_NO_VOTE, + SDIO_VOTE_FOR_SLEEP, + SDIO_VOTE_AGAINST_SLEEP +}; + +struct test_channel { + struct sdio_channel *ch; + + char name[CHANNEL_NAME_SIZE]; + int ch_id; + + u32 *buf; + u32 buf_size; + + struct workqueue_struct *workqueue; + struct test_work test_work; + + u32 rx_bytes; + u32 tx_bytes; + + wait_queue_head_t wait_q; + atomic_t rx_notify_count; + atomic_t tx_notify_count; + atomic_t any_notify_count; + atomic_t wakeup_client; + + int wait_counter; + + int is_used; + int test_type; + int ch_ready; + + struct test_config_msg config_msg; + + int test_completed; + int test_result; + struct timer_list timer; + int timer_interval_ms; + + struct timer_list timeout_timer; + int timeout_ms; + void *sdio_al_device; + int is_ok_to_sleep; + struct lpm_test_struct lpm_test_db; + unsigned int packet_length; + + int random_packet_size; +}; + +struct sdio_al_test_debug { + u32 dun_throughput; + u32 rmnt_throughput; + struct dentry *debug_root; + struct dentry *debug_test_result; + struct dentry *debug_dun_throughput; + struct dentry *debug_rmnt_throughput; +}; + +struct test_context { + dev_t dev_num; + struct device *dev; + struct cdev *cdev; + + struct test_channel *test_ch; + + struct test_channel *test_ch_arr[SDIO_MAX_CHANNELS]; + + long testcase; + + const char *name; + + int exit_flag; + + u32 signature; + + int runtime_debug; + + struct platform_device smem_pdev; + struct sdio_smem_client *sdio_smem; + int smem_was_init; + u8 *smem_buf; + + wait_queue_head_t wait_q; + int test_completed; + int test_result; + struct sdio_al_test_debug debug; + + struct wake_lock wake_lock; +}; + +/* + * Seed for pseudo random time sleeping in Random LPM test. + * If not set, current time in jiffies is used. + */ +static unsigned int seed; +module_param(seed, int, 0); + +static struct test_context *test_ctx; + +#ifdef CONFIG_DEBUG_FS +/* +* +* Trigger on/off for debug messages +* for trigger off the data messages debug level use: +* echo 0 > /sys/kernel/debugfs/sdio_al/debug_data_on +* for trigger on the data messages debug level use: +* echo 1 > /sys/kernel/debugfs/sdio_al/debug_data_on +* for trigger off the lpm messages debug level use: +* echo 0 > /sys/kernel/debugfs/sdio_al/debug_lpm_on +* for trigger on the lpm messages debug level use: +* echo 1 > /sys/kernel/debugfs/sdio_al/debug_lpm_on +*/ +static int sdio_al_test_debugfs_init(void) +{ + test_ctx->debug.debug_root = debugfs_create_dir("sdio_al_test", + NULL); + if (!test_ctx->debug.debug_root) + return -ENOENT; + + test_ctx->debug.debug_test_result = debugfs_create_u32( + "test_result", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + &test_ctx->test_result); + + test_ctx->debug.debug_dun_throughput = debugfs_create_u32( + "dun_throughput", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + &test_ctx->debug.dun_throughput); + + test_ctx->debug.debug_rmnt_throughput = debugfs_create_u32( + "rmnt_throughput", + S_IRUGO | S_IWUGO, + test_ctx->debug.debug_root, + &test_ctx->debug.rmnt_throughput); + + if ((!test_ctx->debug.debug_dun_throughput) && + (!test_ctx->debug.debug_rmnt_throughput)) { + debugfs_remove_recursive(test_ctx->debug.debug_root); + test_ctx->debug.debug_root = NULL; + return -ENOENT; + } + return 0; +} + +static void sdio_al_test_debugfs_cleanup(void) +{ + debugfs_remove(test_ctx->debug.debug_dun_throughput); + debugfs_remove(test_ctx->debug.debug_rmnt_throughput); + debugfs_remove(test_ctx->debug.debug_root); +} +#endif + +static int channel_name_to_id(char *name) +{ + pr_info(TEST_MODULE_NAME "%s: channel name %s\n", + __func__, name); + + if (!strncmp(name, "SDIO_RPC", strnlen("SDIO_RPC", CHANNEL_NAME_SIZE))) + return SDIO_RPC; + else if (!strncmp(name, "SDIO_QMI", + strnlen("SDIO_QMI", CHANNEL_NAME_SIZE))) + return SDIO_QMI; + else if (!strncmp(name, "SDIO_RMNT", + strnlen("SDIO_RMNT", CHANNEL_NAME_SIZE))) + return SDIO_RMNT; + else if (!strncmp(name, "SDIO_DIAG", + strnlen("SDIO_DIAG", CHANNEL_NAME_SIZE))) + return SDIO_DIAG; + else if (!strncmp(name, "SDIO_DUN", + strnlen("SDIO_DUN", CHANNEL_NAME_SIZE))) + return SDIO_DUN; + else if (!strncmp(name, "SDIO_SMEM", + strnlen("SDIO_SMEM", CHANNEL_NAME_SIZE))) + return SDIO_SMEM; + else if (!strncmp(name, "SDIO_CIQ", + strnlen("SDIO_CIQ", CHANNEL_NAME_SIZE))) + return SDIO_CIQ; + else + return SDIO_MAX_CHANNELS; + + return SDIO_MAX_CHANNELS; +} + +/** + * Config message + */ + +static void send_config_msg(struct test_channel *test_ch) +{ + int ret = 0 ; + u32 write_avail = 0; + int size = sizeof(test_ch->config_msg); + + pr_debug(TEST_MODULE_NAME "%s\n", __func__); + + memcpy(test_ch->buf, (void *)&test_ch->config_msg, size); + + if (test_ctx->exit_flag) { + pr_info(TEST_MODULE_NAME ":Exit Test.\n"); + return; + } + + pr_info(TEST_MODULE_NAME ":Sending the config message.\n"); + + /* wait for data ready event */ + write_avail = sdio_write_avail(test_ch->ch); + pr_debug(TEST_MODULE_NAME ":write_avail=%d\n", write_avail); + if (write_avail < size) { + wait_event(test_ch->wait_q, + atomic_read(&test_ch->tx_notify_count)); + atomic_dec(&test_ch->tx_notify_count); + } + + write_avail = sdio_write_avail(test_ch->ch); + pr_debug(TEST_MODULE_NAME ":write_avail=%d\n", write_avail); + if (write_avail < size) { + pr_info(TEST_MODULE_NAME ":not enough write avail.\n"); + return; + } + + ret = sdio_write(test_ch->ch, test_ch->buf, size); + if (ret) + pr_err(TEST_MODULE_NAME ":%s sdio_write err=%d.\n", + __func__, -ret); + else + pr_info(TEST_MODULE_NAME ":%s sent config_msg successfully.\n", + __func__); +} + +/** + * Loopback Test + */ +static void loopback_test(struct test_channel *test_ch) +{ + int ret = 0 ; + u32 read_avail = 0; + u32 write_avail = 0; + + while (1) { + + if (test_ctx->exit_flag) { + pr_info(TEST_MODULE_NAME ":Exit Test.\n"); + return; + } + + TEST_DBG(TEST_MODULE_NAME "--LOOPBACK WAIT FOR EVENT--.\n"); + /* wait for data ready event */ + wait_event(test_ch->wait_q, + atomic_read(&test_ch->rx_notify_count)); + atomic_dec(&test_ch->rx_notify_count); + + read_avail = sdio_read_avail(test_ch->ch); + if (read_avail == 0) + continue; + + + write_avail = sdio_write_avail(test_ch->ch); + if (write_avail < read_avail) { + pr_info(TEST_MODULE_NAME + ":not enough write avail.\n"); + continue; + } + + ret = sdio_read(test_ch->ch, test_ch->buf, read_avail); + if (ret) { + pr_info(TEST_MODULE_NAME + ":worker, sdio_read err=%d.\n", -ret); + continue; + } + test_ch->rx_bytes += read_avail; + + TEST_DBG(TEST_MODULE_NAME ":worker total rx bytes = 0x%x.\n", + test_ch->rx_bytes); + + + ret = sdio_write(test_ch->ch, + test_ch->buf, read_avail); + if (ret) { + pr_info(TEST_MODULE_NAME + ":loopback sdio_write err=%d.\n", + -ret); + continue; + } + test_ch->tx_bytes += read_avail; + + TEST_DBG(TEST_MODULE_NAME + ":loopback total tx bytes = 0x%x.\n", + test_ch->tx_bytes); + } /* end of while */ +} + +/** + * Check if all tests completed + */ +static void check_test_completion(void) +{ + int i; + + for (i = 0; i < SDIO_MAX_CHANNELS; i++) { + struct test_channel *tch = test_ctx->test_ch_arr[i]; + + if ((!tch) || (!tch->is_used) || (!tch->ch_ready)) + continue; + if (!tch->test_completed) { + pr_info(TEST_MODULE_NAME ":Channel %s test is not " + "completed", + tch->name); + return; + } + } + pr_info(TEST_MODULE_NAME ":Test is completed"); + test_ctx->test_completed = 1; + wake_up(&test_ctx->wait_q); +} + +static int pseudo_random_seed(unsigned int *seed_number) +{ + if (!seed_number) + return 0; + + *seed_number = (unsigned int)(((unsigned long)*seed_number * + (unsigned long)1103515367) + 35757); + return (int)(*seed_number / (64*1024) % 1000); +} + +static void lpm_test_update_entry(struct test_channel *tch, + enum lpm_test_msg_type msg_type, + int counter) +{ + u32 index = 0; + static int print_full = 1; + + if (!tch) { + pr_err(TEST_MODULE_NAME ": %s - NULL channel\n", __func__); + return; + } + + if (tch->lpm_test_db.next_avail_entry_in_array >= LPM_ARRAY_SIZE) { + pr_err(TEST_MODULE_NAME ": %s - lpm array is full", + __func__); + if (print_full) { + print_hex_dump(KERN_INFO, TEST_MODULE_NAME ": lpm_arr:", + 0, 32, 2, + (void *)tch->lpm_test_db.lpm_arr, + sizeof(tch->lpm_test_db.lpm_arr), false); + print_full = 0; + } + return; + } + + index = tch->lpm_test_db.next_avail_entry_in_array; + if ((msg_type == LPM_MSG_SEND) || (msg_type == LPM_MSG_REC)) + tch->lpm_test_db.lpm_arr[index].counter = counter; + else + tch->lpm_test_db.lpm_arr[index].counter = 0; + tch->lpm_test_db.lpm_arr[index].msg_type = msg_type; + tch->lpm_test_db.lpm_arr[index].current_ms = + jiffies_to_msecs(get_jiffies_64()); + + tch->lpm_test_db.next_avail_entry_in_array++; +} + +static int wait_for_result_msg(struct test_channel *test_ch) +{ + u32 read_avail = 0; + int ret = 0; + + pr_info(TEST_MODULE_NAME ": %s - START\n", __func__); + + while (1) { + read_avail = sdio_read_avail(test_ch->ch); + + if (read_avail == 0) { + pr_info(TEST_MODULE_NAME + ": read_avail is 0 for chan %s\n", + test_ch->name); + wait_event(test_ch->wait_q, + atomic_read(&test_ch->rx_notify_count)); + atomic_dec(&test_ch->rx_notify_count); + continue; + } + + memset(test_ch->buf, 0x00, test_ch->buf_size); + + ret = sdio_read(test_ch->ch, test_ch->buf, read_avail); + if (ret) { + pr_info(TEST_MODULE_NAME ": sdio_read for chan" + "%s failed, err=%d.\n", + test_ch->name, -ret); + goto exit_err; + } + + if (test_ch->buf[0] != TEST_CONFIG_SIGNATURE) { + pr_info(TEST_MODULE_NAME ": Not a test_result " + "signature. expected 0x%x. received 0x%x " + "for chan %s\n", + TEST_CONFIG_SIGNATURE, + test_ch->buf[0], + test_ch->name); + continue; + } else { + pr_info(TEST_MODULE_NAME ": Signature is " + "TEST_CONFIG_SIGNATURE as expected\n"); + break; + } + } + + return test_ch->buf[1]; + +exit_err: + return 0; +} + +static int check_random_lpm_test_array(struct test_channel *test_ch) +{ + int i = 0, j = 0; + struct lpm_test_struct *lpm_db = &test_ch->lpm_test_db; + unsigned int delta_ms = 0; + int arr_ind = 0; + + if (!test_ch) { + pr_err(TEST_MODULE_NAME ": %s - NULL channel\n", __func__); + return -ENODEV; + } + + print_hex_dump(KERN_INFO, TEST_MODULE_NAME ": lpm_arr:", 0, 32, 2, + (void *)test_ch->lpm_test_db.lpm_arr, + sizeof(test_ch->lpm_test_db.lpm_arr), false); + + for (i = 0; i < lpm_db->next_avail_entry_in_array; i++) { + if ((lpm_db->lpm_arr[i].msg_type == LPM_MSG_SEND) || + (lpm_db->lpm_arr[i].msg_type == LPM_MSG_REC)) { + /* find the next message in the array */ + arr_ind = lpm_db->next_avail_entry_in_array; + for (j = i+1; j < arr_ind; j++) { + if ((lpm_db->lpm_arr[j].msg_type == + LPM_MSG_SEND) || + (lpm_db->lpm_arr[j].msg_type == + LPM_MSG_REC)) + break; + } + if (j == arr_ind) { + if (lpm_db->lpm_arr[i].counter == + test_ch->config_msg.num_packets - 1) { + /* i is last msg in the array */ + return 1; + } else { + pr_err(TEST_MODULE_NAME "%s: invalid " + "last msg, i=%d, counter=%d", + __func__, i, + lpm_db->lpm_arr[i].counter); + return 0; + } + } + delta_ms = lpm_db->lpm_arr[j].current_ms - + lpm_db->lpm_arr[i].current_ms; + if (delta_ms < 30) { + if (j != i+1) { + pr_err(TEST_MODULE_NAME "%s: lpm " + "activity while delta is less " + "than 30, i=%d, j=%d", + __func__, i, j); + return 0; + } + } else { + if (delta_ms > 90) { + if (j != i+3) { + pr_err(TEST_MODULE_NAME "%s: " + "lpm activity while " + "delta is less than " + "30, i=%d, j=%d", + __func__, i, j); + return 0; + } + if (lpm_db->lpm_arr[i+1].msg_type != + LPM_SLEEP) { + pr_err(TEST_MODULE_NAME "%s: " + "no sleep when delta " + "is bigger than 90" + ", i=%d, j=%d", + __func__, i, j); + return 0; + } + if (lpm_db->lpm_arr[i+2].msg_type != + LPM_WAKEUP) { + pr_err(TEST_MODULE_NAME "%s: " + "no sleep when delta " + "is bigger than 90" + ", i=%d, j=%d", + __func__, i, j); + return 0; + } + } + } + } + + } + return 1; +} + +static int lpm_test_main_task(void *ptr) +{ + u32 read_avail = 0; + int last_msg_index = 0; + struct test_channel *test_ch = (struct test_channel *)ptr; + struct lpm_msg lpm_msg; + int ret = 0; + int modem_result = 0; + int host_result = 0; + + pr_info(TEST_MODULE_NAME ": %s - STARTED\n", __func__); + + if (!test_ch) { + pr_err(TEST_MODULE_NAME ": %s - NULL channel\n", __func__); + return -ENODEV; + } + + while (last_msg_index < test_ch->config_msg.num_packets - 1) { + TEST_DBG(TEST_MODULE_NAME ": %s - " + "IN LOOP last_msg_index=%d\n", + __func__, last_msg_index); + + read_avail = sdio_read_avail(test_ch->ch); + if (read_avail == 0) { + TEST_DBG(TEST_MODULE_NAME + ":read_avail 0 for chan %s, " + "wait for event\n", + test_ch->name); + wait_event(test_ch->wait_q, + atomic_read(&test_ch->rx_notify_count)); + atomic_dec(&test_ch->rx_notify_count); + + read_avail = sdio_read_avail(test_ch->ch); + if (read_avail == 0) { + pr_err(TEST_MODULE_NAME + ":read_avail size %d for chan %s not as" + " expected\n", + read_avail, test_ch->name); + continue; + } + } + + memset(test_ch->buf, 0x00, sizeof(test_ch->buf)); + + ret = sdio_read(test_ch->ch, test_ch->buf, read_avail); + if (ret) { + pr_info(TEST_MODULE_NAME ":sdio_read for chan %s" + " err=%d.\n", + test_ch->name, -ret); + goto exit_err; + } + + memcpy((void *)&lpm_msg, test_ch->buf, sizeof(lpm_msg)); + + if (lpm_msg.signature != LPM_TEST_CONFIG_SIGNATURE) { + pr_err(TEST_MODULE_NAME ": Not lpm test_result " + "signature. expected 0x%x. received 0x%x " + "for chan %s\n", + LPM_TEST_CONFIG_SIGNATURE, + lpm_msg.signature, + test_ch->name); + continue; + } else { + pr_debug(TEST_MODULE_NAME ": Signature is " + "LPM_TEST_CONFIG_SIGNATURE as expected\n"); + + spin_lock_irqsave(&lpm_lock, flags); + last_msg_index = lpm_msg.counter; + test_ch->lpm_test_db.last_index_in_rec_msg = + last_msg_index; + lpm_test_update_entry(test_ch, LPM_MSG_REC, + last_msg_index); + spin_unlock_irqrestore(&lpm_lock, flags); + continue; + } + } + + pr_info(TEST_MODULE_NAME ":%s: Finished to recieve all apckets from the" + " modem, waiting for result_msg", __func__); + + /* Wait for the resault message from the modem */ + modem_result = wait_for_result_msg(test_ch); + + pr_info(TEST_MODULE_NAME ": modem result was %d", modem_result); + + /* Wait for all the packets to be sent to the modem */ + while (1) { + spin_lock_irqsave(&lpm_lock, flags); + if (test_ch->lpm_test_db.next_index_in_sent_msg >= + test_ch->config_msg.num_packets - 1) { + spin_unlock_irqrestore(&lpm_lock, flags); + break; + } else { + pr_info(TEST_MODULE_NAME ":%s: Didn't finished to send " + "all apckets, next_index_in_sent_msg = %d ", + __func__, + test_ch->lpm_test_db.next_index_in_sent_msg); + } + spin_unlock_irqrestore(&lpm_lock, flags); + msleep(60); + } + + sdio_al_unregister_lpm_cb(test_ch->sdio_al_device); + + host_result = check_random_lpm_test_array(test_ch); + + test_ch->test_completed = 1; + if (modem_result && host_result) { + pr_info(TEST_MODULE_NAME ": Random LPM TEST_PASSED for ch %s", + test_ch->name); + test_ch->test_result = TEST_PASSED; + } else { + pr_info(TEST_MODULE_NAME ": Random LPM TEST_FAILED for ch %s", + test_ch->name); + test_ch->test_result = TEST_FAILED; + } + + check_test_completion(); + + return 0; + +exit_err: + pr_info(TEST_MODULE_NAME ": TEST FAIL for chan %s.\n", + test_ch->name); + test_ch->test_completed = 1; + test_ch->test_result = TEST_FAILED; + check_test_completion(); + return -ENODEV; +} + +static int lpm_test_create_read_thread(struct test_channel *test_ch) +{ + pr_info(TEST_MODULE_NAME ": %s - STARTED\n", __func__); + + if (!test_ch) { + pr_err(TEST_MODULE_NAME ": %s - NULL channel\n", __func__); + return -ENODEV; + } + + test_ch->lpm_test_db.lpm_test_task.task_name = SDIO_LPM_TEST; + + test_ch->lpm_test_db.lpm_test_task.lpm_task = + kthread_create(lpm_test_main_task, + (void *)(test_ch), + test_ch->lpm_test_db.lpm_test_task.task_name); + + if (IS_ERR(test_ch->lpm_test_db.lpm_test_task.lpm_task)) { + pr_err(TEST_MODULE_NAME ": %s - kthread_create() failed\n", + __func__); + return -ENOMEM; + } + + wake_up_process(test_ch->lpm_test_db.lpm_test_task.lpm_task); + + return 0; +} + +static void lpm_continuous_rand_test(struct test_channel *test_ch) +{ + unsigned int local_ms = 0; + int ret = 0; + unsigned int write_avail = 0; + + pr_info(MODULE_NAME ": %s - STARTED\n", __func__); + + if (!test_ch) { + pr_err(TEST_MODULE_NAME ": %s - NULL channel\n", __func__); + return; + } + + /* initialize lpm_test_db */ + test_ch->lpm_test_db.next_avail_entry_in_array = 0; + test_ch->lpm_test_db.next_index_in_sent_msg = 0; + /* read the current time */ + test_ch->lpm_test_db.start_jiffies = get_jiffies_64(); + + pr_err(TEST_MODULE_NAME ": %s - initializing the lpm_array", __func__); + + memset(test_ch->lpm_test_db.lpm_arr, 0, + sizeof(test_ch->lpm_test_db.lpm_arr)); + + ret = lpm_test_create_read_thread(test_ch); + if (ret != 0) { + pr_err(TEST_MODULE_NAME ": %s - failed to create lpm reading " + "thread", __func__); + } + + while (test_ch->lpm_test_db.next_index_in_sent_msg <= + test_ch->config_msg.num_packets - 1) { + struct lpm_msg msg; + u32 ret = 0; + + local_ms = pseudo_random_seed(&test_ch->config_msg.test_param); + TEST_DBG(TEST_MODULE_NAME ":%s: SLEEPING for %d ms", + __func__, local_ms); + msleep(local_ms); + + msg.counter = test_ch->lpm_test_db.next_index_in_sent_msg; + msg.signature = LPM_TEST_CONFIG_SIGNATURE; + msg.reserve1 = 0; + msg.reserve2 = 0; + + /* wait for data ready event */ + write_avail = sdio_write_avail(test_ch->ch); + pr_debug(TEST_MODULE_NAME ": %s: write_avail=%d\n", + __func__, write_avail); + if (write_avail < sizeof(msg)) { + wait_event(test_ch->wait_q, + atomic_read(&test_ch->tx_notify_count)); + atomic_dec(&test_ch->tx_notify_count); + } + + write_avail = sdio_write_avail(test_ch->ch); + if (write_avail < sizeof(msg)) { + pr_info(TEST_MODULE_NAME ": %s: not enough write " + "avail.\n", __func__); + break; + } + + ret = sdio_write(test_ch->ch, (u32 *)&msg, sizeof(msg)); + if (ret) + pr_err(TEST_MODULE_NAME ":%s: sdio_write err=%d.\n", + __func__, -ret); + + TEST_DBG(TEST_MODULE_NAME ": %s: write, index=%d\n", + __func__, test_ch->lpm_test_db.next_index_in_sent_msg); + + spin_lock_irqsave(&lpm_lock, flags); + lpm_test_update_entry(test_ch, LPM_MSG_SEND, + test_ch->lpm_test_db.next_index_in_sent_msg); + test_ch->lpm_test_db.next_index_in_sent_msg++; + spin_unlock_irqrestore(&lpm_lock, flags); + } + + pr_info(TEST_MODULE_NAME ": %s: Finished to send all packets to " + "the modem", __func__); + + return; +} + +static void lpm_test(struct test_channel *test_ch) +{ + int modem_result = 0; + + pr_info(TEST_MODULE_NAME ": %s - START\n", __func__); + + if (!test_ch) { + pr_err(TEST_MODULE_NAME ": %s - NULL channel\n", __func__); + return; + } + + modem_result = wait_for_result_msg(test_ch); + pr_debug(TEST_MODULE_NAME ": %s - delete the timeout timer\n", + __func__); + del_timer_sync(&test_ch->timeout_timer); + + if (modem_result == 0) { + pr_err(TEST_MODULE_NAME ": LPM TEST - Client didn't sleep. " + "Result Msg - is_successful=%d\n", test_ch->buf[1]); + goto exit_err; + } else { + pr_info(TEST_MODULE_NAME ": %s -" + "LPM 9K WAS SLEEPING - PASS\n", __func__); + if (test_ch->test_result == TEST_PASSED) { + pr_info(TEST_MODULE_NAME ": LPM TEST_PASSED\n"); + test_ch->test_completed = 1; + check_test_completion(); + } else { + pr_err(TEST_MODULE_NAME ": LPM TEST - Host didn't " + "sleep. Client slept\n"); + goto exit_err; + } + } + + return; + +exit_err: + pr_info(TEST_MODULE_NAME ": TEST FAIL for chan %s.\n", + test_ch->name); + test_ch->test_completed = 1; + test_ch->test_result = TEST_FAILED; + check_test_completion(); + return; +} + + +/** + * LPM Test while the host wakes up the modem + */ +static void lpm_test_host_waker(struct test_channel *test_ch) +{ + pr_info(TEST_MODULE_NAME ": %s - START\n", __func__); + wait_event(test_ch->wait_q, atomic_read(&test_ch->wakeup_client)); + atomic_set(&test_ch->wakeup_client, 0); + + pr_info(TEST_MODULE_NAME ": %s - Sending the config_msg to wakeup " + " the client\n", __func__); + send_config_msg(test_ch); + + lpm_test(test_ch); +} + +/** + * sender Test + */ +static void sender_test(struct test_channel *test_ch) +{ + int ret = 0 ; + u32 read_avail = 0; + u32 write_avail = 0; + int packet_count = 0; + int size = 512; + u16 *buf16 = (u16 *) test_ch->buf; + int i; + int max_packet_count = 10000; + int random_num = 0; + + max_packet_count = test_ch->config_msg.num_packets; + + for (i = 0 ; i < size / 2 ; i++) + buf16[i] = (u16) (i & 0xFFFF); + + + pr_info(TEST_MODULE_NAME + ":SENDER TEST START for chan %s\n", test_ch->name); + + while (packet_count < max_packet_count) { + + if (test_ctx->exit_flag) { + pr_info(TEST_MODULE_NAME ":Exit Test.\n"); + return; + } + + random_num = get_random_int(); + size = (random_num % test_ch->packet_length) + 1; + + TEST_DBG(TEST_MODULE_NAME "SENDER WAIT FOR EVENT for chan %s\n", + test_ch->name); + + /* wait for data ready event */ + write_avail = sdio_write_avail(test_ch->ch); + TEST_DBG(TEST_MODULE_NAME ":write_avail=%d\n", write_avail); + if (write_avail < size) { + wait_event(test_ch->wait_q, + atomic_read(&test_ch->tx_notify_count)); + atomic_dec(&test_ch->tx_notify_count); + } + + write_avail = sdio_write_avail(test_ch->ch); + TEST_DBG(TEST_MODULE_NAME ":write_avail=%d\n", write_avail); + if (write_avail < size) { + pr_info(TEST_MODULE_NAME ":not enough write avail.\n"); + continue; + } + + test_ch->buf[0] = packet_count; + + ret = sdio_write(test_ch->ch, test_ch->buf, size); + if (ret) { + pr_info(TEST_MODULE_NAME ":sender sdio_write err=%d.\n", + -ret); + goto exit_err; + } + + /* wait for read data ready event */ + TEST_DBG(TEST_MODULE_NAME ":sender wait for rx data for " + "chan %s\n", + test_ch->name); + read_avail = sdio_read_avail(test_ch->ch); + wait_event(test_ch->wait_q, + atomic_read(&test_ch->rx_notify_count)); + atomic_dec(&test_ch->rx_notify_count); + + read_avail = sdio_read_avail(test_ch->ch); + + if (read_avail != size) { + pr_info(TEST_MODULE_NAME + ":read_avail size %d for chan %s not as " + "expected size %d.\n", + read_avail, test_ch->name, size); + goto exit_err; + } + + memset(test_ch->buf, 0x00, size); + + ret = sdio_read(test_ch->ch, test_ch->buf, size); + if (ret) { + pr_info(TEST_MODULE_NAME ":sender sdio_read for chan %s" + " err=%d.\n", + test_ch->name, -ret); + goto exit_err; + } + + + if ((test_ch->buf[0] != packet_count) && (size != 1)) { + pr_info(TEST_MODULE_NAME ":sender sdio_read WRONG DATA" + " for chan %s, size=%d\n", + test_ch->name, size); + goto exit_err; + } + + test_ch->tx_bytes += size; + test_ch->rx_bytes += size; + packet_count++; + + TEST_DBG(TEST_MODULE_NAME + ":sender total rx bytes = 0x%x , packet#=%d, size=%d" + " for chan %s\n", + test_ch->rx_bytes, packet_count, size, test_ch->name); + TEST_DBG(TEST_MODULE_NAME + ":sender total tx bytes = 0x%x , packet#=%d, size=%d" + " for chan %s\n", + test_ch->tx_bytes, packet_count, size, test_ch->name); + + } /* end of while */ + + pr_info(TEST_MODULE_NAME + ":SENDER TEST END: total rx bytes = 0x%x, " + " total tx bytes = 0x%x for chan %s\n", + test_ch->rx_bytes, test_ch->tx_bytes, test_ch->name); + + pr_info(TEST_MODULE_NAME ": TEST PASS for chan %s.\n", + test_ch->name); + test_ch->test_completed = 1; + test_ch->test_result = TEST_PASSED; + check_test_completion(); + return; + +exit_err: + pr_info(TEST_MODULE_NAME ": TEST FAIL for chan %s.\n", + test_ch->name); + test_ch->test_completed = 1; + test_ch->test_result = TEST_FAILED; + check_test_completion(); + return; +} + +/** + * A2 Perf Test + */ +static void a2_performance_test(struct test_channel *test_ch) +{ + int ret = 0 ; + u32 read_avail = 0; + u32 write_avail = 0; + int tx_packet_count = 0; + int rx_packet_count = 0; + int size = 0; + u16 *buf16 = (u16 *) test_ch->buf; + int i; + int total_bytes = 0; + int max_packets = 10000; + u32 packet_size = test_ch->buf_size; + int rand_size = 0; + + u64 start_jiffy, end_jiffy, delta_jiffies; + unsigned int time_msec = 0; + u32 throughput = 0; + + max_packets = test_ch->config_msg.num_packets; + packet_size = test_ch->packet_length; + + for (i = 0; i < packet_size / 2; i++) + buf16[i] = (u16) (i & 0xFFFF); + + pr_info(TEST_MODULE_NAME ": A2 PERFORMANCE TEST START for chan %s\n", + test_ch->name); + + start_jiffy = get_jiffies_64(); /* read the current time */ + + while (tx_packet_count < max_packets) { + + if (test_ctx->exit_flag) { + pr_info(TEST_MODULE_NAME ":Exit Test.\n"); + return; + } + + if (test_ch->random_packet_size) { + rand_size = get_random_int(); + packet_size = (rand_size % test_ch->packet_length) + 1; + if (packet_size < A2_MIN_PACKET_SIZE) + packet_size = A2_MIN_PACKET_SIZE; + } + + /* wait for data ready event */ + /* use a func to avoid compiler optimizations */ + write_avail = sdio_write_avail(test_ch->ch); + read_avail = sdio_read_avail(test_ch->ch); + TEST_DBG(TEST_MODULE_NAME ":channel %s, write_avail=%d, " + "read_avail=%d for chan %s\n", + test_ch->name, write_avail, read_avail, + test_ch->name); + if ((write_avail == 0) && (read_avail == 0)) { + wait_event(test_ch->wait_q, + atomic_read(&test_ch->any_notify_count)); + atomic_set(&test_ch->any_notify_count, 0); + } + + write_avail = sdio_write_avail(test_ch->ch); + TEST_DBG(TEST_MODULE_NAME ":channel %s, write_avail=%d\n", + test_ch->name, write_avail); + if (write_avail > 0) { + size = min(packet_size, write_avail) ; + TEST_DBG(TEST_MODULE_NAME ":tx size = %d for chan %s\n", + size, test_ch->name); + test_ch->buf[0] = tx_packet_count; + test_ch->buf[(size/4)-1] = tx_packet_count; + + ret = sdio_write(test_ch->ch, test_ch->buf, size); + if (ret) { + pr_info(TEST_MODULE_NAME ":sdio_write err=%d" + " for chan %s\n", + -ret, test_ch->name); + goto exit_err; + } + tx_packet_count++; + test_ch->tx_bytes += size; + } + + read_avail = sdio_read_avail(test_ch->ch); + TEST_DBG(TEST_MODULE_NAME ":channel %s, read_avail=%d\n", + test_ch->name, read_avail); + if (read_avail > 0) { + size = min(packet_size, read_avail); + pr_debug(TEST_MODULE_NAME ":rx size = %d.\n", size); + ret = sdio_read(test_ch->ch, test_ch->buf, size); + if (ret) { + pr_info(TEST_MODULE_NAME ": sdio_read size %d " + " err=%d" + " for chan %s\n", + size, -ret, test_ch->name); + goto exit_err; + } + rx_packet_count++; + test_ch->rx_bytes += size; + } + + TEST_DBG(TEST_MODULE_NAME + ":total rx bytes = %d , rx_packet#=%d" + " for chan %s\n", + test_ch->rx_bytes, rx_packet_count, test_ch->name); + TEST_DBG(TEST_MODULE_NAME + ":total tx bytes = %d , tx_packet#=%d" + " for chan %s\n", + test_ch->tx_bytes, tx_packet_count, test_ch->name); + + } /* while (tx_packet_count < max_packets ) */ + + end_jiffy = get_jiffies_64(); /* read the current time */ + + delta_jiffies = end_jiffy - start_jiffy; + time_msec = jiffies_to_msecs(delta_jiffies); + + pr_info(TEST_MODULE_NAME ":total rx bytes = 0x%x , rx_packet#=%d for" + " chan %s.\n", + test_ch->rx_bytes, rx_packet_count, test_ch->name); + pr_info(TEST_MODULE_NAME ":total tx bytes = 0x%x , tx_packet#=%d" + " for chan %s.\n", + test_ch->tx_bytes, tx_packet_count, test_ch->name); + + total_bytes = (test_ch->tx_bytes + test_ch->rx_bytes); + pr_err(TEST_MODULE_NAME ":total bytes = %d, time msec = %d" + " for chan %s\n", + total_bytes , (int) time_msec, test_ch->name); + + if (!test_ch->random_packet_size) { + throughput = (total_bytes / time_msec) * 8 / 1000; + pr_err(TEST_MODULE_NAME ":Performance = %d Mbit/sec for " + "chan %s\n", + throughput, test_ch->name); + } + +#ifdef CONFIG_DEBUG_FS + switch (test_ch->ch_id) { + case SDIO_DUN: + test_ctx->debug.dun_throughput = throughput; + break; + case SDIO_RMNT: + test_ctx->debug.rmnt_throughput = throughput; + break; + default: + pr_err(TEST_MODULE_NAME "No debugfs for this channel " + "throughput"); + } +#endif + + pr_err(TEST_MODULE_NAME ": A2 PERFORMANCE TEST END for chan %s.\n", + test_ch->name); + + pr_err(TEST_MODULE_NAME ": TEST PASS for chan %s\n", test_ch->name); + test_ch->test_completed = 1; + test_ch->test_result = TEST_PASSED; + check_test_completion(); + return; + +exit_err: + pr_err(TEST_MODULE_NAME ": TEST FAIL for chan %s\n", test_ch->name); + test_ch->test_completed = 1; + test_ch->test_result = TEST_FAILED; + check_test_completion(); + return; +} + +/** + * sender No loopback Test + */ +static void sender_no_loopback_test(struct test_channel *test_ch) +{ + int ret = 0 ; + u32 write_avail = 0; + int packet_count = 0; + int size = 512; + u16 *buf16 = (u16 *) test_ch->buf; + int i; + int max_packet_count = 10000; + int random_num = 0; + int modem_result; + + max_packet_count = test_ch->config_msg.num_packets; + + for (i = 0 ; i < size / 2 ; i++) + buf16[i] = (u16) (i & 0xFFFF); + + pr_info(TEST_MODULE_NAME + ":SENDER NO LP TEST START for chan %s\n", test_ch->name); + + while (packet_count < max_packet_count) { + + if (test_ctx->exit_flag) { + pr_info(TEST_MODULE_NAME ":Exit Test.\n"); + return; + } + + random_num = get_random_int(); + size = (random_num % test_ch->packet_length) + 1; + + TEST_DBG(TEST_MODULE_NAME ":SENDER WAIT FOR EVENT " + "for chan %s\n", + test_ch->name); + + /* wait for data ready event */ + write_avail = sdio_write_avail(test_ch->ch); + TEST_DBG(TEST_MODULE_NAME ":write_avail=%d\n", write_avail); + if (write_avail < size) { + wait_event(test_ch->wait_q, + atomic_read(&test_ch->tx_notify_count)); + atomic_dec(&test_ch->tx_notify_count); + } + + write_avail = sdio_write_avail(test_ch->ch); + TEST_DBG(TEST_MODULE_NAME ":write_avail=%d\n", write_avail); + if (write_avail < size) { + pr_info(TEST_MODULE_NAME ":not enough write avail.\n"); + continue; + } + + test_ch->buf[0] = packet_count; + + ret = sdio_write(test_ch->ch, test_ch->buf, size); + if (ret) { + pr_info(TEST_MODULE_NAME ":sender sdio_write err=%d.\n", + -ret); + goto exit_err; + } + + test_ch->tx_bytes += size; + packet_count++; + + TEST_DBG(TEST_MODULE_NAME + ":sender total tx bytes = 0x%x , packet#=%d, size=%d" + " for chan %s\n", + test_ch->tx_bytes, packet_count, size, test_ch->name); + + } /* end of while */ + + pr_info(TEST_MODULE_NAME + ":SENDER TEST END: total tx bytes = 0x%x, " + " for chan %s\n", + test_ch->tx_bytes, test_ch->name); + + modem_result = wait_for_result_msg(test_ch); + + if (modem_result) { + pr_info(TEST_MODULE_NAME ": TEST PASS for chan %s.\n", + test_ch->name); + test_ch->test_result = TEST_PASSED; + } else { + pr_info(TEST_MODULE_NAME ": TEST FAILURE for chan %s.\n", + test_ch->name); + test_ch->test_result = TEST_FAILED; + } + test_ch->test_completed = 1; + check_test_completion(); + return; + +exit_err: + pr_info(TEST_MODULE_NAME ": TEST FAIL for chan %s.\n", + test_ch->name); + test_ch->test_completed = 1; + test_ch->test_result = TEST_FAILED; + check_test_completion(); + return; +} + +/** + * Worker thread to handle the tests types + */ +static void worker(struct work_struct *work) +{ + struct test_channel *test_ch = NULL; + struct test_work *test_work = container_of(work, + struct test_work, + work); + int test_type = 0; + + test_ch = test_work->test_ch; + + if (test_ch == NULL) { + pr_err(TEST_MODULE_NAME ":NULL test_ch\n"); + return; + } + + test_type = test_ch->test_type; + + switch (test_type) { + case SDIO_TEST_LOOPBACK_HOST: + loopback_test(test_ch); + break; + case SDIO_TEST_LOOPBACK_CLIENT: + sender_test(test_ch); + break; + case SDIO_TEST_PERF: + a2_performance_test(test_ch); + break; + case SDIO_TEST_LPM_CLIENT_WAKER: + lpm_test(test_ch); + break; + case SDIO_TEST_LPM_HOST_WAKER: + lpm_test_host_waker(test_ch); + break; + case SDIO_TEST_HOST_SENDER_NO_LP: + sender_no_loopback_test(test_ch); + break; + case SDIO_TEST_LPM_RANDOM: + lpm_continuous_rand_test(test_ch); + break; + default: + pr_err(TEST_MODULE_NAME ":Bad Test type = %d.\n", + (int) test_type); + } +} + + +/** + * Notification Callback + * + * Notify the worker + * + */ +static void notify(void *priv, unsigned channel_event) +{ + struct test_channel *test_ch = (struct test_channel *) priv; + + pr_debug(TEST_MODULE_NAME ":notify event=%d.\n", channel_event); + + if (test_ch->ch == NULL) { + pr_info(TEST_MODULE_NAME ":notify before ch ready.\n"); + return; + } + + switch (channel_event) { + case SDIO_EVENT_DATA_READ_AVAIL: + atomic_inc(&test_ch->rx_notify_count); + atomic_set(&test_ch->any_notify_count, 1); + TEST_DBG(TEST_MODULE_NAME ":SDIO_EVENT_DATA_READ_AVAIL, " + "any_notify_count=%d, " + "rx_notify_count=%d\n", + atomic_read(&test_ch->any_notify_count), + atomic_read(&test_ch->rx_notify_count)); + break; + + case SDIO_EVENT_DATA_WRITE_AVAIL: + atomic_inc(&test_ch->tx_notify_count); + atomic_set(&test_ch->any_notify_count, 1); + TEST_DBG(TEST_MODULE_NAME ":SDIO_EVENT_DATA_WRITE_AVAIL, " + "any_notify_count=%d, " + "tx_notify_count=%d\n", + atomic_read(&test_ch->any_notify_count), + atomic_read(&test_ch->tx_notify_count)); + break; + + default: + BUG(); + } + wake_up(&test_ch->wait_q); + +} + +#ifdef CONFIG_MSM_SDIO_SMEM +static int sdio_smem_test_cb(int event) +{ + struct test_channel *tch = test_ctx->test_ch_arr[SDIO_SMEM]; + pr_debug(TEST_MODULE_NAME ":%s: Received event %d\n", __func__, event); + + switch (event) { + case SDIO_SMEM_EVENT_READ_DONE: + tch->rx_bytes += SMEM_MAX_XFER_SIZE; + if (tch->rx_bytes >= 40000000) { + if (!tch->test_completed) { + pr_info(TEST_MODULE_NAME ":SMEM test PASSED\n"); + tch->test_completed = 1; + tch->test_result = TEST_PASSED; + check_test_completion(); + } + + } + break; + case SDIO_SMEM_EVENT_READ_ERR: + pr_err(TEST_MODULE_NAME ":Read overflow, SMEM test FAILED\n"); + tch->test_completed = 1; + tch->test_result = TEST_FAILED; + check_test_completion(); + return -EIO; + default: + pr_err(TEST_MODULE_NAME ":Unhandled event\n"); + return -EINVAL; + } + return 0; +} + +static int sdio_smem_test_probe(struct platform_device *pdev) +{ + int ret = 0; + + test_ctx->sdio_smem = container_of(pdev, struct sdio_smem_client, + plat_dev); + + test_ctx->sdio_smem->buf = test_ctx->smem_buf; + test_ctx->sdio_smem->size = SMEM_MAX_XFER_SIZE; + test_ctx->sdio_smem->cb_func = sdio_smem_test_cb; + ret = sdio_smem_register_client(); + if (ret) + pr_info(TEST_MODULE_NAME "%s: Error (%d) registering sdio_smem " + "test client\n", + __func__, ret); + return ret; +} + +static struct platform_driver sdio_smem_drv = { + .probe = sdio_smem_test_probe, + .driver = { + .name = "SDIO_SMEM_CLIENT", + .owner = THIS_MODULE, + }, +}; +#endif + + +static void default_sdio_al_test_release(struct device *dev) +{ + pr_info(MODULE_NAME ":platform device released.\n"); +} + +static void sdio_test_lpm_timeout_handler(unsigned long data) +{ + struct test_channel *tch = (struct test_channel *)data; + + pr_info(TEST_MODULE_NAME ": %s - LPM TEST TIMEOUT Expired after " + "%d ms\n", __func__, tch->timeout_ms); + tch->test_completed = 1; + pr_info(TEST_MODULE_NAME ": %s - tch->test_result = TEST_FAILED\n", + __func__); + tch->test_completed = 1; + tch->test_result = TEST_FAILED; + check_test_completion(); + return; +} + +static void sdio_test_lpm_timer_handler(unsigned long data) +{ + struct test_channel *tch = (struct test_channel *)data; + + pr_info(TEST_MODULE_NAME ": %s - LPM TEST Timer Expired after " + "%d ms\n", __func__, tch->timer_interval_ms); + + if (!tch) { + pr_err(TEST_MODULE_NAME ": %s - LPM TEST FAILED. " + "tch is NULL\n", __func__); + return; + } + + if (!tch->ch) { + pr_err(TEST_MODULE_NAME ": %s - LPM TEST FAILED. tch->ch " + "is NULL\n", __func__); + tch->test_result = TEST_FAILED; + return; + } + + /* Verfiy that we voted for sleep */ + if (tch->is_ok_to_sleep) { + tch->test_result = TEST_PASSED; + pr_info(TEST_MODULE_NAME ": %s - 8K voted for sleep\n", + __func__); + } else { + tch->test_result = TEST_FAILED; + pr_info(TEST_MODULE_NAME ": %s - 8K voted against sleep\n", + __func__); + + } + sdio_al_unregister_lpm_cb(tch->sdio_al_device); + + if (tch->test_type == SDIO_TEST_LPM_HOST_WAKER) { + atomic_set(&tch->wakeup_client, 1); + wake_up(&tch->wait_q); + } +} + +int sdio_test_wakeup_callback(void *device_handle, int is_vote_for_sleep) +{ + int i = 0; + + TEST_DBG(TEST_MODULE_NAME ": %s is_vote_for_sleep=%d!!!", + __func__, is_vote_for_sleep); + + for (i = 0; i < SDIO_MAX_CHANNELS; i++) { + struct test_channel *tch = test_ctx->test_ch_arr[i]; + + if ((!tch) || (!tch->is_used) || (!tch->ch_ready)) + continue; + if (tch->sdio_al_device == device_handle) { + tch->is_ok_to_sleep = is_vote_for_sleep; + + spin_lock_irqsave(&lpm_lock, flags); + if (is_vote_for_sleep == 1) + lpm_test_update_entry(tch, LPM_SLEEP, 0); + else + lpm_test_update_entry(tch, LPM_WAKEUP, 0); + spin_unlock_irqrestore(&lpm_lock, flags); + } + } + + + return 0; +} + +/** + * Test Main + */ +static int test_start(void) +{ + int ret = -ENOMEM; + int i; + + pr_debug(TEST_MODULE_NAME ":Starting Test ....\n"); + + test_ctx->test_completed = 0; + test_ctx->test_result = TEST_NO_RESULT; + test_ctx->debug.dun_throughput = 0; + test_ctx->debug.rmnt_throughput = 0; + + /* Open The Channels */ + for (i = 0; i < SDIO_MAX_CHANNELS; i++) { + struct test_channel *tch = test_ctx->test_ch_arr[i]; + + if ((!tch) || (!tch->is_used)) + continue; + + tch->rx_bytes = 0; + tch->tx_bytes = 0; + + atomic_set(&tch->tx_notify_count, 0); + atomic_set(&tch->rx_notify_count, 0); + atomic_set(&tch->any_notify_count, 0); + atomic_set(&tch->wakeup_client, 0); + + memset(tch->buf, 0x00, tch->buf_size); + tch->test_result = TEST_NO_RESULT; + + tch->test_completed = 0; + + memset(&tch->lpm_test_db, 0, sizeof(tch->lpm_test_db)); + + if (!tch->ch_ready) { + pr_info(TEST_MODULE_NAME ":openning channel %s\n", + tch->name); + tch->ch_ready = true; + if (tch->ch_id == SDIO_SMEM) { + test_ctx->smem_pdev.name = "SDIO_SMEM"; + test_ctx->smem_pdev.dev.release = + default_sdio_al_test_release; + platform_device_register(&test_ctx->smem_pdev); + } else { + ret = sdio_open(tch->name , &tch->ch, tch, + notify); + if (ret) { + pr_info(TEST_MODULE_NAME + ":openning channel %s failed\n", + tch->name); + tch->ch_ready = false; + } + + tch->sdio_al_device = tch->ch->sdio_al_dev; + } + } + + if ((tch->ch_ready) && (tch->ch_id != SDIO_SMEM)) + send_config_msg(tch); + + if ((tch->test_type == SDIO_TEST_LPM_HOST_WAKER) || + (tch->test_type == SDIO_TEST_LPM_CLIENT_WAKER) || + (tch->test_type == SDIO_TEST_LPM_RANDOM)) { + sdio_al_register_lpm_cb(tch->sdio_al_device, + sdio_test_wakeup_callback); + + if (tch->timer_interval_ms > 0) { + pr_info(TEST_MODULE_NAME ": %s - init timer, " + "ms=%d\n", + __func__, tch->timer_interval_ms); + init_timer(&tch->timer); + tch->timer.data = (unsigned long)tch; + tch->timer.function = + sdio_test_lpm_timer_handler; + tch->timer.expires = jiffies + + msecs_to_jiffies(tch->timer_interval_ms); + add_timer(&tch->timer); + } + } + } + + pr_debug(TEST_MODULE_NAME ":queue_work..\n"); + for (i = 0; i < SDIO_MAX_CHANNELS; i++) { + struct test_channel *tch = test_ctx->test_ch_arr[i]; + + if ((!tch) || (!tch->is_used) || (!tch->ch_ready) || + (tch->ch_id == SDIO_SMEM)) + continue; + queue_work(tch->workqueue, &tch->test_work.work); + } + + pr_info(TEST_MODULE_NAME ":Waiting for the test completion\n"); + + if (!test_ctx->test_completed) { + wait_event(test_ctx->wait_q, test_ctx->test_completed); + pr_info(TEST_MODULE_NAME ":Test Completed\n"); + for (i = 0; i < SDIO_MAX_CHANNELS; i++) { + struct test_channel *tch = test_ctx->test_ch_arr[i]; + + if ((!tch) || (!tch->is_used) || (!tch->ch_ready)) + continue; + if (tch->test_result == TEST_FAILED) { + pr_info(TEST_MODULE_NAME ":Test FAILED\n"); + test_ctx->test_result = TEST_FAILED; + return 0; + } + } + pr_info(TEST_MODULE_NAME ":Test PASSED\n"); + test_ctx->test_result = TEST_PASSED; + } + + + return 0; +} + +static int set_params_loopback_9k(struct test_channel *tch) +{ + if (!tch) { + pr_err(TEST_MODULE_NAME ":NULL channel\n"); + return -EINVAL; + } + tch->is_used = 1; + tch->test_type = SDIO_TEST_LOOPBACK_CLIENT; + tch->config_msg.signature = TEST_CONFIG_SIGNATURE; + tch->config_msg.test_case = SDIO_TEST_LOOPBACK_CLIENT; + tch->config_msg.num_packets = 10000; + tch->config_msg.num_iterations = 1; + + tch->packet_length = 512; + if (tch->ch_id == SDIO_RPC) + tch->packet_length = 128; + tch->timer_interval_ms = 0; + + return 0; +} + +static int set_params_a2_perf(struct test_channel *tch) +{ + if (!tch) { + pr_err(TEST_MODULE_NAME ":NULL channel\n"); + return -EINVAL; + } + tch->is_used = 1; + tch->test_type = SDIO_TEST_PERF; + tch->config_msg.signature = TEST_CONFIG_SIGNATURE; + tch->config_msg.test_case = SDIO_TEST_LOOPBACK_CLIENT; + if (tch->ch_id == SDIO_DIAG) + tch->packet_length = 512; + else + tch->packet_length = MAX_XFER_SIZE; + + tch->config_msg.num_packets = 10000; + tch->config_msg.num_iterations = 1; + tch->random_packet_size = 0; + + tch->timer_interval_ms = 0; + + return 0; +} + +static int set_params_a2_small_pkts(struct test_channel *tch) +{ + if (!tch) { + pr_err(TEST_MODULE_NAME ":NULL channel\n"); + return -EINVAL; + } + tch->is_used = 1; + tch->test_type = SDIO_TEST_PERF; + tch->config_msg.signature = TEST_CONFIG_SIGNATURE; + tch->config_msg.test_case = SDIO_TEST_LOOPBACK_CLIENT; + tch->packet_length = 128; + + tch->config_msg.num_packets = 1000000; + tch->config_msg.num_iterations = 1; + tch->random_packet_size = 1; + + tch->timer_interval_ms = 0; + + return 0; +} + +static int set_params_smem_test(struct test_channel *tch) +{ + if (!tch) { + pr_err(TEST_MODULE_NAME ":NULL channel\n"); + return -EINVAL; + } + tch->is_used = 1; + tch->timer_interval_ms = 0; + + return 0; +} + +static int set_params_lpm_test(struct test_channel *tch, + enum sdio_test_case_type test, + int timer_interval_ms) +{ + static int first_time = 1; + if (!tch) { + pr_err(TEST_MODULE_NAME ": %s - NULL channel\n", __func__); + return -EINVAL; + } + tch->is_used = 1; + tch->test_type = test; + tch->config_msg.signature = TEST_CONFIG_SIGNATURE; + tch->config_msg.test_case = test; + tch->config_msg.num_packets = 100; + tch->config_msg.num_iterations = 1; + if (seed != 0) + tch->config_msg.test_param = seed; + else + tch->config_msg.test_param = + (unsigned int)(get_jiffies_64() & 0xFFFF); + pr_info(TEST_MODULE_NAME ":%s: seed is %d", + __func__, tch->config_msg.test_param); + + tch->timer_interval_ms = timer_interval_ms; + tch->timeout_ms = 10000; + + tch->packet_length = 0; + if (test != SDIO_TEST_LPM_RANDOM) { + init_timer(&tch->timeout_timer); + tch->timeout_timer.data = (unsigned long)tch; + tch->timeout_timer.function = sdio_test_lpm_timeout_handler; + tch->timeout_timer.expires = jiffies + + msecs_to_jiffies(tch->timeout_ms); + add_timer(&tch->timeout_timer); + pr_info(TEST_MODULE_NAME ": %s - Initiated LPM TIMEOUT TIMER." + "set to %d ms\n", + __func__, tch->timeout_ms); + } + + if (first_time) { + pr_info(TEST_MODULE_NAME ": %s - wake_lock_init() called\n", + __func__); + wake_lock_init(&test_ctx->wake_lock, + WAKE_LOCK_SUSPEND, TEST_MODULE_NAME); + first_time = 0; + } + + pr_info(TEST_MODULE_NAME ": %s - wake_lock() for the TEST is " + "called. to prevent real sleeping\n", __func__); + wake_lock(&test_ctx->wake_lock); + + return 0; +} + +static int set_params_8k_sender_no_lp(struct test_channel *tch) +{ + if (!tch) { + pr_err(TEST_MODULE_NAME ":NULL channel\n"); + return -EINVAL; + } + tch->is_used = 1; + tch->test_type = SDIO_TEST_HOST_SENDER_NO_LP; + tch->config_msg.signature = TEST_CONFIG_SIGNATURE; + tch->config_msg.test_case = SDIO_TEST_HOST_SENDER_NO_LP; + tch->config_msg.num_packets = 1000; + tch->config_msg.num_iterations = 1; + + tch->packet_length = 512; + if (tch->ch_id == SDIO_RPC) + tch->packet_length = 128; + tch->timer_interval_ms = 0; + + return 0; +} + +/** + * Write File. + * + * @note Trigger the test from user space by: + * echo 1 > /dev/sdio_al_test + * + */ +ssize_t test_write(struct file *filp, const char __user *buf, size_t size, + loff_t *f_pos) +{ + int ret = 0; + int i; + + for (i = 0; i < SDIO_MAX_CHANNELS; i++) { + struct test_channel *tch = test_ctx->test_ch_arr[i]; + if (!tch) + continue; + tch->is_used = 0; + } + + ret = strict_strtol(buf, 10, &test_ctx->testcase); + + switch (test_ctx->testcase) { + case 1: + /* RPC */ + pr_debug(TEST_MODULE_NAME " --RPC sender--.\n"); + if (set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_RPC])) + return size; + break; + case 2: + /* RPC, QMI and DIAG */ + pr_debug(TEST_MODULE_NAME " --RPC, QMI and DIAG sender--.\n"); + if (set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_RPC]) || + set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_QMI]) || + set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_DIAG])) + return size; + break; + case 4: + pr_debug(TEST_MODULE_NAME " --SMEM--.\n"); + if (set_params_smem_test(test_ctx->test_ch_arr[SDIO_SMEM])) + return size; + break; + + case 5: + pr_debug(TEST_MODULE_NAME " --SMEM and RPC--.\n"); + if (set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_RPC]) || + set_params_smem_test(test_ctx->test_ch_arr[SDIO_SMEM])) + return size; + break; + case 6: + pr_debug(TEST_MODULE_NAME " --RmNet A2 Performance--.\n"); + if (set_params_a2_perf(test_ctx->test_ch_arr[SDIO_RMNT])) + return size; + break; + + case 7: + pr_debug(TEST_MODULE_NAME " --DUN A2 Performance--.\n"); + if (set_params_a2_perf(test_ctx->test_ch_arr[SDIO_DUN])) + return size; + break; + case 8: + pr_debug(TEST_MODULE_NAME " --RmNet and DUN A2 Performance--." + "\n"); + if (set_params_a2_perf(test_ctx->test_ch_arr[SDIO_RMNT]) || + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_DUN])) + return size; + break; + case 9: + pr_debug(TEST_MODULE_NAME " --RPC sender and RmNet A2 " + "Performance--.\n"); + if (set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_RPC]) || + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_RMNT])) + return size; + break; + case 10: + pr_debug(TEST_MODULE_NAME " --All the channels--.\n"); + if (set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_RPC]) || + set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_QMI]) || + set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_DIAG]) || + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_RMNT]) || + set_params_a2_perf(test_ctx->test_ch_arr[SDIO_DUN]) || + set_params_smem_test(test_ctx->test_ch_arr[SDIO_SMEM]) || + set_params_loopback_9k(test_ctx->test_ch_arr[SDIO_CIQ])) + return size; + break; + case 11: + pr_info(TEST_MODULE_NAME " --LPM Test For Device 0. Client " + "wakes the Host --.\n"); + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RMNT], + SDIO_TEST_LPM_CLIENT_WAKER, 90); + break; + case 12: + pr_info(TEST_MODULE_NAME " --LPM Test For Device 1. Client " + "wakes the Host --.\n"); + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RPC], + SDIO_TEST_LPM_CLIENT_WAKER, 90); + break; + case 13: + pr_info(TEST_MODULE_NAME " --LPM Test For Device 0. Host " + "wakes the Client --.\n"); + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RMNT], + SDIO_TEST_LPM_HOST_WAKER, 120); + break; + case 14: + pr_info(TEST_MODULE_NAME " --LPM Test For Device 1. Host " + "wakes the Client --.\n"); + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RPC], + SDIO_TEST_LPM_HOST_WAKER, 120); + break; + case 15: + pr_info(TEST_MODULE_NAME " --LPM Test RANDOM --.\n"); + set_params_lpm_test(test_ctx->test_ch_arr[SDIO_RPC], + SDIO_TEST_LPM_RANDOM, 0); + break; + case 16: + pr_info(TEST_MODULE_NAME " -- host sender no LP for Diag --"); + set_params_8k_sender_no_lp(test_ctx->test_ch_arr[SDIO_DIAG]); + break; + case 17: + pr_info(TEST_MODULE_NAME " -- host sender no LP for Diag, RPC, " + "CIQ --"); + set_params_8k_sender_no_lp(test_ctx->test_ch_arr[SDIO_DIAG]); + set_params_8k_sender_no_lp(test_ctx->test_ch_arr[SDIO_CIQ]); + set_params_8k_sender_no_lp(test_ctx->test_ch_arr[SDIO_RPC]); + break; + case 18: + pr_info(TEST_MODULE_NAME " -- rmnet small packets (5-128) --"); + if (set_params_a2_small_pkts(test_ctx->test_ch_arr[SDIO_RMNT])) + return size; + break; + case 98: + pr_info(TEST_MODULE_NAME " set runtime debug on"); + test_ctx->runtime_debug = 1; + return size; + case 99: + pr_info(TEST_MODULE_NAME " set runtime debug off"); + test_ctx->runtime_debug = 0; + return size; + default: + pr_info(TEST_MODULE_NAME ":Bad Test number = %d.\n", + (int)test_ctx->testcase); + return 0; + } + ret = test_start(); + if (ret) { + pr_err(TEST_MODULE_NAME ":test_start failed, ret = %d.\n", + ret); + + } + return size; +} + +/** + * Test Channel Init. + */ +int test_channel_init(char *name) +{ + struct test_channel *test_ch; + int ch_id = 0; +#ifdef CONFIG_MSM_SDIO_SMEM + int ret; +#endif + + pr_debug(TEST_MODULE_NAME ":%s.\n", __func__); + pr_info(TEST_MODULE_NAME ": init test cahnnel %s.\n", name); + + ch_id = channel_name_to_id(name); + pr_debug(TEST_MODULE_NAME ":id = %d.\n", ch_id); + if (test_ctx->test_ch_arr[ch_id] == NULL) { + test_ch = kzalloc(sizeof(*test_ch), GFP_KERNEL); + if (test_ch == NULL) { + pr_err(TEST_MODULE_NAME ":kzalloc err for allocating " + "test_ch %s.\n", + name); + return -ENOMEM; + } + test_ctx->test_ch_arr[ch_id] = test_ch; + + test_ch->ch_id = ch_id; + + memcpy(test_ch->name, name, CHANNEL_NAME_SIZE); + + test_ch->buf_size = MAX_XFER_SIZE; + + test_ch->buf = kzalloc(test_ch->buf_size, GFP_KERNEL); + if (test_ch->buf == NULL) { + kfree(test_ch); + test_ctx->test_ch = NULL; + return -ENOMEM; + } + + if (test_ch->ch_id == SDIO_SMEM) { + test_ctx->smem_buf = kzalloc(SMEM_MAX_XFER_SIZE, + GFP_KERNEL); + if (test_ctx->smem_buf == NULL) { + pr_err(TEST_MODULE_NAME ":%s: Unable to " + "allocate smem buf\n", + __func__); + kfree(test_ch); + test_ctx->test_ch = NULL; + return -ENOMEM; + } + +#ifdef CONFIG_MSM_SDIO_SMEM + ret = platform_driver_register(&sdio_smem_drv); + if (ret) { + pr_err(TEST_MODULE_NAME ":%s: Unable to " + "register sdio smem " + "test client\n", + __func__); + return ret; + } +#endif + } else { + test_ch->workqueue = + create_singlethread_workqueue(test_ch->name); + test_ch->test_work.test_ch = test_ch; + INIT_WORK(&test_ch->test_work.work, worker); + + init_waitqueue_head(&test_ch->wait_q); + } + } else { + pr_err(TEST_MODULE_NAME ":trying to call test_channel_init " + "twice for chan %d\n", + ch_id); + } + + return 0; +} + +static struct class *test_class; + +const struct file_operations test_fops = { + .owner = THIS_MODULE, + .write = test_write, +}; + +/** + * Module Init. + */ +static int __init test_init(void) +{ + int ret; + + pr_debug(TEST_MODULE_NAME ":test_init.\n"); + + test_ctx = kzalloc(sizeof(*test_ctx), GFP_KERNEL); + if (test_ctx == NULL) { + pr_err(TEST_MODULE_NAME ":kzalloc err.\n"); + return -ENOMEM; + } + test_ctx->test_ch = NULL; + test_ctx->signature = TEST_SIGNATURE; + + test_ctx->name = "UNKNOWN"; + + init_waitqueue_head(&test_ctx->wait_q); + +#ifdef CONFIG_DEBUG_FS + sdio_al_test_debugfs_init(); +#endif + + test_class = class_create(THIS_MODULE, TEST_MODULE_NAME); + + ret = alloc_chrdev_region(&test_ctx->dev_num, 0, 1, TEST_MODULE_NAME); + if (ret) { + pr_err(TEST_MODULE_NAME "alloc_chrdev_region err.\n"); + return -ENODEV; + } + + test_ctx->dev = device_create(test_class, NULL, test_ctx->dev_num, + test_ctx, TEST_MODULE_NAME); + if (IS_ERR(test_ctx->dev)) { + pr_err(TEST_MODULE_NAME ":device_create err.\n"); + return -ENODEV; + } + + test_ctx->cdev = cdev_alloc(); + if (test_ctx->cdev == NULL) { + pr_err(TEST_MODULE_NAME ":cdev_alloc err.\n"); + return -ENODEV; + } + cdev_init(test_ctx->cdev, &test_fops); + test_ctx->cdev->owner = THIS_MODULE; + + ret = cdev_add(test_ctx->cdev, test_ctx->dev_num, 1); + if (ret) + pr_err(TEST_MODULE_NAME ":cdev_add err=%d\n", -ret); + else + pr_debug(TEST_MODULE_NAME ":SDIO-AL-Test init OK..\n"); + + return ret; +} + +/** + * Module Exit. + */ +static void __exit test_exit(void) +{ + int i; + + pr_debug(TEST_MODULE_NAME ":test_exit.\n"); + + test_ctx->exit_flag = true; + + msleep(100); /* allow gracefully exit of the worker thread */ + + cdev_del(test_ctx->cdev); + device_destroy(test_class, test_ctx->dev_num); + unregister_chrdev_region(test_ctx->dev_num, 1); + + for (i = 0; i < SDIO_MAX_CHANNELS; i++) { + struct test_channel *tch = test_ctx->test_ch_arr[i]; + if (!tch) + continue; + kfree(tch->buf); + kfree(tch); + } + +#ifdef CONFIG_DEBUG_FS + sdio_al_test_debugfs_cleanup(); +#endif + + kfree(test_ctx); + + pr_debug(TEST_MODULE_NAME ":test_exit complete.\n"); +} + +module_init(test_init); +module_exit(test_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SDIO_AL Test"); +MODULE_AUTHOR("Amir Samuelov "); + + diff --git a/arch/arm/mach-msm/sdio_cmux.c b/arch/arm/mach-msm/sdio_cmux.c new file mode 100644 index 00000000000..f6e8f402e3a --- /dev/null +++ b/arch/arm/mach-msm/sdio_cmux.c @@ -0,0 +1,866 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "modem_notifier.h" + +#define MAX_WRITE_RETRY 5 +#define MAGIC_NO_V1 0x33FC + +static int msm_sdio_cmux_debug_mask; +module_param_named(debug_mask, msm_sdio_cmux_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +enum cmd_type { + DATA = 0, + OPEN, + CLOSE, + STATUS, + NUM_CMDS +}; + +#define DSR_POS 0x1 +#define CTS_POS 0x2 +#define RI_POS 0x4 +#define CD_POS 0x8 + +struct sdio_cmux_ch { + int lc_id; + + struct mutex lc_lock; + wait_queue_head_t open_wait_queue; + int is_remote_open; + int is_local_open; + int is_channel_reset; + + char local_status; + char remote_status; + + struct mutex tx_lock; + struct list_head tx_list; + + void *priv; + void (*receive_cb)(void *, int, void *); + void (*write_done)(void *, int, void *); + void (*status_callback)(int, void *); +} logical_ch[SDIO_CMUX_NUM_CHANNELS]; + +struct sdio_cmux_hdr { + uint16_t magic_no; + uint8_t status; /* This field is reserved for commands other + * than STATUS */ + uint8_t cmd; + uint8_t pad_bytes; + uint8_t lc_id; + uint16_t pkt_len; +}; + +struct sdio_cmux_pkt { + struct sdio_cmux_hdr *hdr; + void *data; +}; + +struct sdio_cmux_list_elem { + struct list_head list; + struct sdio_cmux_pkt cmux_pkt; +}; + +#define logical_ch_is_local_open(x) \ + (logical_ch[(x)].is_local_open) + +#define logical_ch_is_remote_open(x) \ + (logical_ch[(x)].is_remote_open) + +static void sdio_cdemux_fn(struct work_struct *work); +static DECLARE_WORK(sdio_cdemux_work, sdio_cdemux_fn); +static struct workqueue_struct *sdio_cdemux_wq; + +static DEFINE_MUTEX(write_lock); +static uint32_t bytes_to_write; +static DEFINE_MUTEX(temp_rx_lock); +static LIST_HEAD(temp_rx_list); + +static void sdio_cmux_fn(struct work_struct *work); +static DECLARE_WORK(sdio_cmux_work, sdio_cmux_fn); +static struct workqueue_struct *sdio_cmux_wq; + +static struct sdio_channel *sdio_qmi_chl; +static uint32_t sdio_cmux_inited; + +static uint32_t abort_tx; +static DEFINE_MUTEX(modem_reset_lock); + +enum { + MSM_SDIO_CMUX_DEBUG = 1U << 0, + MSM_SDIO_CMUX_DUMP_BUFFER = 1U << 1, +}; + +static struct platform_device sdio_ctl_dev = { + .name = "SDIO_CTL", + .id = -1, +}; + +#if defined(DEBUG) +#define D_DUMP_BUFFER(prestr, cnt, buf) \ +do { \ + if (msm_sdio_cmux_debug_mask & MSM_SDIO_CMUX_DUMP_BUFFER) { \ + int i; \ + pr_debug("%s", prestr); \ + for (i = 0; i < cnt; i++) \ + pr_info("%.2x", buf[i]); \ + pr_debug("\n"); \ + } \ +} while (0) + +#define D(x...) \ +do { \ + if (msm_sdio_cmux_debug_mask & MSM_SDIO_CMUX_DEBUG) \ + pr_debug(x); \ +} while (0) + +#else +#define D_DUMP_BUFFER(prestr, cnt, buf) do {} while (0) +#define D(x...) do {} while (0) +#endif + +static int sdio_cmux_ch_alloc(int id) +{ + if (id < 0 || id >= SDIO_CMUX_NUM_CHANNELS) { + pr_err("%s: Invalid lc_id - %d\n", __func__, id); + return -EINVAL; + } + + logical_ch[id].lc_id = id; + mutex_init(&logical_ch[id].lc_lock); + init_waitqueue_head(&logical_ch[id].open_wait_queue); + logical_ch[id].is_remote_open = 0; + logical_ch[id].is_local_open = 0; + logical_ch[id].is_channel_reset = 0; + + INIT_LIST_HEAD(&logical_ch[id].tx_list); + mutex_init(&logical_ch[id].tx_lock); + + logical_ch[id].priv = NULL; + logical_ch[id].receive_cb = NULL; + logical_ch[id].write_done = NULL; + return 0; +} + +static int sdio_cmux_ch_clear_and_signal(int id) +{ + struct sdio_cmux_list_elem *list_elem; + + if (id < 0 || id >= SDIO_CMUX_NUM_CHANNELS) { + pr_err("%s: Invalid lc_id - %d\n", __func__, id); + return -EINVAL; + } + + mutex_lock(&logical_ch[id].lc_lock); + logical_ch[id].is_remote_open = 0; + mutex_lock(&logical_ch[id].tx_lock); + while (!list_empty(&logical_ch[id].tx_list)) { + list_elem = list_first_entry(&logical_ch[id].tx_list, + struct sdio_cmux_list_elem, + list); + list_del(&list_elem->list); + kfree(list_elem->cmux_pkt.hdr); + kfree(list_elem); + } + mutex_unlock(&logical_ch[id].tx_lock); + if (logical_ch[id].receive_cb) + logical_ch[id].receive_cb(NULL, 0, logical_ch[id].priv); + if (logical_ch[id].write_done) + logical_ch[id].write_done(NULL, 0, logical_ch[id].priv); + mutex_unlock(&logical_ch[id].lc_lock); + wake_up(&logical_ch[id].open_wait_queue); + return 0; +} + +static int sdio_cmux_write_cmd(const int id, enum cmd_type type) +{ + int write_size = 0; + void *write_data = NULL; + struct sdio_cmux_list_elem *list_elem; + + if (id < 0 || id >= SDIO_CMUX_NUM_CHANNELS) { + pr_err("%s: Invalid lc_id - %d\n", __func__, id); + return -EINVAL; + } + + if (type < 0 || type > NUM_CMDS) { + pr_err("%s: Invalid cmd - %d\n", __func__, type); + return -EINVAL; + } + + write_size = sizeof(struct sdio_cmux_hdr); + list_elem = kmalloc(sizeof(struct sdio_cmux_list_elem), GFP_KERNEL); + if (!list_elem) { + pr_err("%s: list_elem alloc failed\n", __func__); + return -ENOMEM; + } + + write_data = kmalloc(write_size, GFP_KERNEL); + if (!write_data) { + pr_err("%s: write_data alloc failed\n", __func__); + kfree(list_elem); + return -ENOMEM; + } + + list_elem->cmux_pkt.hdr = (struct sdio_cmux_hdr *)write_data; + list_elem->cmux_pkt.data = NULL; + + list_elem->cmux_pkt.hdr->lc_id = (uint8_t)id; + list_elem->cmux_pkt.hdr->pkt_len = (uint16_t)0; + list_elem->cmux_pkt.hdr->cmd = (uint8_t)type; + list_elem->cmux_pkt.hdr->status = (uint8_t)0; + if (type == STATUS) + list_elem->cmux_pkt.hdr->status = logical_ch[id].local_status; + list_elem->cmux_pkt.hdr->pad_bytes = (uint8_t)0; + list_elem->cmux_pkt.hdr->magic_no = (uint16_t)MAGIC_NO_V1; + + mutex_lock(&logical_ch[id].tx_lock); + list_add_tail(&list_elem->list, &logical_ch[id].tx_list); + mutex_unlock(&logical_ch[id].tx_lock); + + mutex_lock(&write_lock); + bytes_to_write += write_size; + mutex_unlock(&write_lock); + queue_work(sdio_cmux_wq, &sdio_cmux_work); + + return 0; +} + +int sdio_cmux_open(const int id, + void (*receive_cb)(void *, int, void *), + void (*write_done)(void *, int, void *), + void (*status_callback)(int, void *), + void *priv) +{ + int r; + struct sdio_cmux_list_elem *list_elem, *list_elem_tmp; + + if (!sdio_cmux_inited) + return -ENODEV; + if (id < 0 || id >= SDIO_CMUX_NUM_CHANNELS) { + pr_err("%s: Invalid id - %d\n", __func__, id); + return -EINVAL; + } + + r = wait_event_timeout(logical_ch[id].open_wait_queue, + logical_ch[id].is_remote_open, (1 * HZ)); + if (r < 0) { + pr_err("ERROR %s: wait_event_timeout() failed for" + " ch%d with rc %d\n", __func__, id, r); + return r; + } + if (r == 0) { + pr_err("ERROR %s: Wait Timed Out for ch%d\n", __func__, id); + return -ETIMEDOUT; + } + + mutex_lock(&logical_ch[id].lc_lock); + if (!logical_ch[id].is_remote_open) { + pr_err("%s: Remote ch%d not opened\n", __func__, id); + mutex_unlock(&logical_ch[id].lc_lock); + return -EINVAL; + } + if (logical_ch[id].is_local_open) { + mutex_unlock(&logical_ch[id].lc_lock); + return 0; + } + logical_ch[id].is_local_open = 1; + logical_ch[id].priv = priv; + logical_ch[id].receive_cb = receive_cb; + logical_ch[id].write_done = write_done; + logical_ch[id].status_callback = status_callback; + if (logical_ch[id].receive_cb) { + mutex_lock(&temp_rx_lock); + list_for_each_entry_safe(list_elem, list_elem_tmp, + &temp_rx_list, list) { + if ((int)list_elem->cmux_pkt.hdr->lc_id == id) { + logical_ch[id].receive_cb( + list_elem->cmux_pkt.data, + (int)list_elem->cmux_pkt.hdr->pkt_len, + logical_ch[id].priv); + list_del(&list_elem->list); + kfree(list_elem->cmux_pkt.hdr); + kfree(list_elem); + } + } + mutex_unlock(&temp_rx_lock); + } + mutex_unlock(&logical_ch[id].lc_lock); + sdio_cmux_write_cmd(id, OPEN); + return 0; +} +EXPORT_SYMBOL(sdio_cmux_open); + +int sdio_cmux_close(int id) +{ + struct sdio_cmux_ch *ch; + + if (!sdio_cmux_inited) + return -ENODEV; + if (id < 0 || id >= SDIO_CMUX_NUM_CHANNELS) { + pr_err("%s: Invalid channel close\n", __func__); + return -EINVAL; + } + + ch = &logical_ch[id]; + mutex_lock(&ch->lc_lock); + ch->is_local_open = 0; + ch->priv = NULL; + ch->receive_cb = NULL; + ch->write_done = NULL; + mutex_unlock(&ch->lc_lock); + sdio_cmux_write_cmd(ch->lc_id, CLOSE); + return 0; +} +EXPORT_SYMBOL(sdio_cmux_close); + +int sdio_cmux_write_avail(int id) +{ + int write_avail; + + mutex_lock(&logical_ch[id].lc_lock); + if (logical_ch[id].is_channel_reset) { + mutex_unlock(&logical_ch[id].lc_lock); + return -ENETRESET; + } + mutex_unlock(&logical_ch[id].lc_lock); + write_avail = sdio_write_avail(sdio_qmi_chl); + return write_avail - bytes_to_write; +} +EXPORT_SYMBOL(sdio_cmux_write_avail); + +int sdio_cmux_write(int id, void *data, int len) +{ + struct sdio_cmux_list_elem *list_elem; + uint32_t write_size; + void *write_data = NULL; + struct sdio_cmux_ch *ch; + int ret; + + if (!sdio_cmux_inited) + return -ENODEV; + if (id < 0 || id >= SDIO_CMUX_NUM_CHANNELS) { + pr_err("%s: Invalid channel id %d\n", __func__, id); + return -ENODEV; + } + + ch = &logical_ch[id]; + if (len <= 0) { + pr_err("%s: Invalid len %d bytes to write\n", + __func__, len); + return -EINVAL; + } + + write_size = sizeof(struct sdio_cmux_hdr) + len; + list_elem = kmalloc(sizeof(struct sdio_cmux_list_elem), GFP_KERNEL); + if (!list_elem) { + pr_err("%s: list_elem alloc failed\n", __func__); + return -ENOMEM; + } + + write_data = kmalloc(write_size, GFP_KERNEL); + if (!write_data) { + pr_err("%s: write_data alloc failed\n", __func__); + kfree(list_elem); + return -ENOMEM; + } + + list_elem->cmux_pkt.hdr = (struct sdio_cmux_hdr *)write_data; + list_elem->cmux_pkt.data = (void *)((char *)write_data + + sizeof(struct sdio_cmux_hdr)); + memcpy(list_elem->cmux_pkt.data, data, len); + + list_elem->cmux_pkt.hdr->lc_id = (uint8_t)ch->lc_id; + list_elem->cmux_pkt.hdr->pkt_len = (uint16_t)len; + list_elem->cmux_pkt.hdr->cmd = (uint8_t)DATA; + list_elem->cmux_pkt.hdr->status = (uint8_t)0; + list_elem->cmux_pkt.hdr->pad_bytes = (uint8_t)0; + list_elem->cmux_pkt.hdr->magic_no = (uint16_t)MAGIC_NO_V1; + + mutex_lock(&ch->lc_lock); + if (!ch->is_remote_open || !ch->is_local_open) { + pr_err("%s: Local ch%d sending data before sending/receiving" + " OPEN command\n", __func__, ch->lc_id); + if (ch->is_channel_reset) + ret = -ENETRESET; + else + ret = -ENODEV; + mutex_unlock(&ch->lc_lock); + kfree(write_data); + kfree(list_elem); + return ret; + } + mutex_lock(&ch->tx_lock); + list_add_tail(&list_elem->list, &ch->tx_list); + mutex_unlock(&ch->tx_lock); + mutex_unlock(&ch->lc_lock); + + mutex_lock(&write_lock); + bytes_to_write += write_size; + mutex_unlock(&write_lock); + queue_work(sdio_cmux_wq, &sdio_cmux_work); + + return len; +} +EXPORT_SYMBOL(sdio_cmux_write); + +int is_remote_open(int id) +{ + if (id < 0 || id >= SDIO_CMUX_NUM_CHANNELS) + return -ENODEV; + + return logical_ch_is_remote_open(id); +} +EXPORT_SYMBOL(is_remote_open); + +int sdio_cmux_is_channel_reset(int id) +{ + int ret; + if (id < 0 || id >= SDIO_CMUX_NUM_CHANNELS) + return -ENODEV; + + mutex_lock(&logical_ch[id].lc_lock); + ret = logical_ch[id].is_channel_reset; + mutex_unlock(&logical_ch[id].lc_lock); + return ret; +} +EXPORT_SYMBOL(sdio_cmux_is_channel_reset); + +int sdio_cmux_tiocmget(int id) +{ + int ret = (logical_ch[id].remote_status & DSR_POS ? TIOCM_DSR : 0) | + (logical_ch[id].remote_status & CTS_POS ? TIOCM_CTS : 0) | + (logical_ch[id].remote_status & CD_POS ? TIOCM_CD : 0) | + (logical_ch[id].remote_status & RI_POS ? TIOCM_RI : 0) | + (logical_ch[id].local_status & CTS_POS ? TIOCM_RTS : 0) | + (logical_ch[id].local_status & DSR_POS ? TIOCM_DTR : 0); + return ret; +} +EXPORT_SYMBOL(sdio_cmux_tiocmget); + +int sdio_cmux_tiocmset(int id, unsigned int set, unsigned int clear) +{ + if (set & TIOCM_DTR) + logical_ch[id].local_status |= DSR_POS; + + if (set & TIOCM_RTS) + logical_ch[id].local_status |= CTS_POS; + + if (clear & TIOCM_DTR) + logical_ch[id].local_status &= ~DSR_POS; + + if (clear & TIOCM_RTS) + logical_ch[id].local_status &= ~CTS_POS; + + sdio_cmux_write_cmd(id, STATUS); + return 0; +} +EXPORT_SYMBOL(sdio_cmux_tiocmset); + +static int copy_packet(void *pkt, int size) +{ + struct sdio_cmux_list_elem *list_elem = NULL; + void *temp_pkt = NULL; + + list_elem = kmalloc(sizeof(struct sdio_cmux_list_elem), GFP_KERNEL); + if (!list_elem) { + pr_err("%s: list_elem alloc failed\n", __func__); + return -ENOMEM; + } + temp_pkt = kmalloc(size, GFP_KERNEL); + if (!temp_pkt) { + pr_err("%s: temp_pkt alloc failed\n", __func__); + kfree(list_elem); + return -ENOMEM; + } + + memcpy(temp_pkt, pkt, size); + list_elem->cmux_pkt.hdr = temp_pkt; + list_elem->cmux_pkt.data = (void *)((char *)temp_pkt + + sizeof(struct sdio_cmux_hdr)); + mutex_lock(&temp_rx_lock); + list_add_tail(&list_elem->list, &temp_rx_list); + mutex_unlock(&temp_rx_lock); + return 0; +} + +static int process_cmux_pkt(void *pkt, int size) +{ + struct sdio_cmux_hdr *mux_hdr; + uint32_t id, data_size; + void *data; + char *dump_buf = (char *)pkt; + + D_DUMP_BUFFER("process_cmux_pkt:", size, dump_buf); + mux_hdr = (struct sdio_cmux_hdr *)pkt; + switch (mux_hdr->cmd) { + case OPEN: + id = (uint32_t)(mux_hdr->lc_id); + D("%s: Received OPEN command for ch%d\n", __func__, id); + mutex_lock(&logical_ch[id].lc_lock); + logical_ch[id].is_remote_open = 1; + logical_ch[id].is_channel_reset = 0; + mutex_unlock(&logical_ch[id].lc_lock); + wake_up(&logical_ch[id].open_wait_queue); + break; + + case CLOSE: + id = (uint32_t)(mux_hdr->lc_id); + D("%s: Received CLOSE command for ch%d\n", __func__, id); + sdio_cmux_ch_clear_and_signal(id); + break; + + case DATA: + id = (uint32_t)(mux_hdr->lc_id); + D("%s: Received DATA for ch%d\n", __func__, id); + /*Channel is not locally open & if single packet received + then drop it*/ + mutex_lock(&logical_ch[id].lc_lock); + if (!logical_ch[id].is_remote_open) { + mutex_unlock(&logical_ch[id].lc_lock); + pr_err("%s: Remote Ch%d sent data before sending/" + "receiving OPEN command\n", __func__, id); + return -ENODEV; + } + + data = (void *)((char *)pkt + sizeof(struct sdio_cmux_hdr)); + data_size = (int)(((struct sdio_cmux_hdr *)pkt)->pkt_len); + if (logical_ch[id].receive_cb) + logical_ch[id].receive_cb(data, data_size, + logical_ch[id].priv); + else + copy_packet(pkt, size); + mutex_unlock(&logical_ch[id].lc_lock); + break; + + case STATUS: + id = (uint32_t)(mux_hdr->lc_id); + D("%s: Received STATUS command for ch%d\n", __func__, id); + if (logical_ch[id].remote_status != mux_hdr->status) { + mutex_lock(&logical_ch[id].lc_lock); + logical_ch[id].remote_status = mux_hdr->status; + mutex_unlock(&logical_ch[id].lc_lock); + if (logical_ch[id].status_callback) + logical_ch[id].status_callback( + sdio_cmux_tiocmget(id), + logical_ch[id].priv); + } + break; + } + return 0; +} + +static void parse_cmux_data(void *data, int size) +{ + int data_parsed = 0, pkt_size; + char *temp_ptr; + + D("Entered %s\n", __func__); + temp_ptr = (char *)data; + while (data_parsed < size) { + pkt_size = sizeof(struct sdio_cmux_hdr) + + (int)(((struct sdio_cmux_hdr *)temp_ptr)->pkt_len); + D("Parsed %d bytes, Current Pkt Size %d bytes," + " Total size %d bytes\n", data_parsed, pkt_size, size); + process_cmux_pkt((void *)temp_ptr, pkt_size); + data_parsed += pkt_size; + temp_ptr += pkt_size; + } + + kfree(data); +} + +static void sdio_cdemux_fn(struct work_struct *work) +{ + int r = 0, read_avail = 0; + void *cmux_data; + + while (1) { + read_avail = sdio_read_avail(sdio_qmi_chl); + if (read_avail < 0) { + pr_err("%s: sdio_read_avail failed with rc %d\n", + __func__, read_avail); + return; + } + + if (read_avail == 0) { + D("%s: Nothing to read\n", __func__); + return; + } + + D("%s: kmalloc %d bytes\n", __func__, read_avail); + cmux_data = kmalloc(read_avail, GFP_KERNEL); + if (!cmux_data) { + pr_err("%s: kmalloc Failed\n", __func__); + return; + } + + D("%s: sdio_read %d bytes\n", __func__, read_avail); + r = sdio_read(sdio_qmi_chl, cmux_data, read_avail); + if (r < 0) { + pr_err("%s: sdio_read failed with rc %d\n", + __func__, r); + kfree(cmux_data); + return; + } + + parse_cmux_data(cmux_data, read_avail); + } + return; +} + +static void sdio_cmux_fn(struct work_struct *work) +{ + int i, r = 0; + void *write_data; + uint32_t write_size, write_avail, write_retry = 0; + int bytes_written; + struct sdio_cmux_list_elem *list_elem = NULL; + struct sdio_cmux_ch *ch; + + for (i = 0; i < SDIO_CMUX_NUM_CHANNELS; ++i) { + ch = &logical_ch[i]; + bytes_written = 0; + mutex_lock(&ch->tx_lock); + while (!list_empty(&ch->tx_list)) { + list_elem = list_first_entry(&ch->tx_list, + struct sdio_cmux_list_elem, + list); + list_del(&list_elem->list); + mutex_unlock(&ch->tx_lock); + + write_data = (void *)list_elem->cmux_pkt.hdr; + write_size = sizeof(struct sdio_cmux_hdr) + + (uint32_t)list_elem->cmux_pkt.hdr->pkt_len; + + mutex_lock(&modem_reset_lock); + while (!(abort_tx) && + ((write_avail = sdio_write_avail(sdio_qmi_chl)) + < write_size)) { + mutex_unlock(&modem_reset_lock); + pr_err("%s: sdio_write_avail %d bytes, " + "write size %d bytes. Waiting...\n", + __func__, write_avail, write_size); + msleep(250); + mutex_lock(&modem_reset_lock); + } + while (!(abort_tx) && + ((r = sdio_write(sdio_qmi_chl, + write_data, write_size)) < 0) + && (write_retry++ < MAX_WRITE_RETRY)) { + mutex_unlock(&modem_reset_lock); + pr_err("%s: sdio_write failed with rc %d." + "Retrying...", __func__, r); + msleep(250); + mutex_lock(&modem_reset_lock); + } + if (!r && !abort_tx) { + D("%s: sdio_write_completed %dbytes\n", + __func__, write_size); + bytes_written += write_size; + } + mutex_unlock(&modem_reset_lock); + kfree(list_elem->cmux_pkt.hdr); + kfree(list_elem); + mutex_lock(&write_lock); + bytes_to_write -= write_size; + mutex_unlock(&write_lock); + mutex_lock(&ch->tx_lock); + } + if (ch->write_done) + ch->write_done(NULL, bytes_written, ch->priv); + mutex_unlock(&ch->tx_lock); + } + return; +} + +static void sdio_qmi_chl_notify(void *priv, unsigned event) +{ + if (event == SDIO_EVENT_DATA_READ_AVAIL) { + D("%s: Received SDIO_EVENT_DATA_READ_AVAIL\n", __func__); + queue_work(sdio_cdemux_wq, &sdio_cdemux_work); + } +} + +#ifdef CONFIG_DEBUG_FS + +static int debug_tbl(char *buf, int max) +{ + int i = 0; + int j; + + for (j = 0; j < SDIO_CMUX_NUM_CHANNELS; ++j) { + i += scnprintf(buf + i, max - i, + "ch%02d local open=%s remote open=%s\n", + j, logical_ch_is_local_open(j) ? "Y" : "N", + logical_ch_is_remote_open(j) ? "Y" : "N"); + } + + return i; +} + +#define DEBUG_BUFMAX 4096 +static char debug_buffer[DEBUG_BUFMAX]; + +static ssize_t debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int (*fill)(char *buf, int max) = file->private_data; + int bsize = fill(debug_buffer, DEBUG_BUFMAX); + return simple_read_from_buffer(buf, count, ppos, debug_buffer, bsize); +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + + +static const struct file_operations debug_ops = { + .read = debug_read, + .open = debug_open, +}; + +static void debug_create(const char *name, mode_t mode, + struct dentry *dent, + int (*fill)(char *buf, int max)) +{ + debugfs_create_file(name, mode, dent, fill, &debug_ops); +} + +#endif + +static int sdio_cmux_probe(struct platform_device *pdev) +{ + int i, r; + + D("%s Begins\n", __func__); + if (sdio_cmux_inited) { + mutex_lock(&modem_reset_lock); + r = sdio_open("SDIO_QMI", &sdio_qmi_chl, NULL, + sdio_qmi_chl_notify); + if (r < 0) { + mutex_unlock(&modem_reset_lock); + pr_err("%s: sdio_open() failed\n", __func__); + goto error2; + } + abort_tx = 0; + mutex_unlock(&modem_reset_lock); + return 0; + } + + for (i = 0; i < SDIO_CMUX_NUM_CHANNELS; ++i) + sdio_cmux_ch_alloc(i); + INIT_LIST_HEAD(&temp_rx_list); + + sdio_cmux_wq = create_singlethread_workqueue("sdio_cmux"); + if (IS_ERR(sdio_cmux_wq)) { + pr_err("%s: create_singlethread_workqueue() ENOMEM\n", + __func__); + r = -ENOMEM; + goto error0; + } + + sdio_cdemux_wq = create_singlethread_workqueue("sdio_cdemux"); + if (IS_ERR(sdio_cdemux_wq)) { + pr_err("%s: create_singlethread_workqueue() ENOMEM\n", + __func__); + r = -ENOMEM; + goto error1; + } + + r = sdio_open("SDIO_QMI", &sdio_qmi_chl, NULL, sdio_qmi_chl_notify); + if (r < 0) { + pr_err("%s: sdio_open() failed\n", __func__); + goto error2; + } + + platform_device_register(&sdio_ctl_dev); + sdio_cmux_inited = 1; + D("SDIO Control MUX Driver Initialized.\n"); + return 0; + +error2: + destroy_workqueue(sdio_cdemux_wq); +error1: + destroy_workqueue(sdio_cmux_wq); +error0: + return r; +} + +static int sdio_cmux_remove(struct platform_device *pdev) +{ + int i; + + mutex_lock(&modem_reset_lock); + abort_tx = 1; + + for (i = 0; i < SDIO_CMUX_NUM_CHANNELS; ++i) { + mutex_lock(&logical_ch[i].lc_lock); + logical_ch[i].is_channel_reset = 1; + mutex_unlock(&logical_ch[i].lc_lock); + sdio_cmux_ch_clear_and_signal(i); + } + sdio_qmi_chl = NULL; + mutex_unlock(&modem_reset_lock); + + return 0; +} + +static struct platform_driver sdio_cmux_driver = { + .probe = sdio_cmux_probe, + .remove = sdio_cmux_remove, + .driver = { + .name = "SDIO_QMI", + .owner = THIS_MODULE, + }, +}; + +static int __init sdio_cmux_init(void) +{ +#ifdef CONFIG_DEBUG_FS + struct dentry *dent; + + dent = debugfs_create_dir("sdio_cmux", 0); + if (!IS_ERR(dent)) + debug_create("tbl", 0444, dent, debug_tbl); +#endif + + msm_sdio_cmux_debug_mask = 0; + return platform_driver_register(&sdio_cmux_driver); +} + +module_init(sdio_cmux_init); +MODULE_DESCRIPTION("MSM SDIO Control MUX"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/sdio_ctl.c b/arch/arm/mach-msm/sdio_ctl.c new file mode 100644 index 00000000000..582fc69314a --- /dev/null +++ b/arch/arm/mach-msm/sdio_ctl.c @@ -0,0 +1,507 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * SDIO Control Driver -- Provides a binary SDIO muxed control port + * interface. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "modem_notifier.h" +#include + +#define MAX_WRITE_RETRY 5 +#define MAGIC_NO_V1 0x33FC +#define NUM_SDIO_CTL_PORTS 9 +#define DEVICE_NAME "sdioctl" +#define MAX_BUF_SIZE 2048 +#define DEBUG + +static int msm_sdio_ctl_debug_mask; +module_param_named(debug_mask, msm_sdio_ctl_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +struct sdio_ctl_dev { + int id; + char name[9]; + struct cdev cdev; + struct device *devicep; + struct mutex dev_lock; + int ref_count; + + struct mutex rx_lock; + uint32_t read_avail; + struct list_head rx_list; + + wait_queue_head_t read_wait_queue; + wait_queue_head_t write_wait_queue; +} *sdio_ctl_devp[NUM_SDIO_CTL_PORTS]; + +struct sdio_ctl_pkt { + int data_size; + void *data; +}; + +struct sdio_ctl_list_elem { + struct list_head list; + struct sdio_ctl_pkt ctl_pkt; +}; + +struct class *sdio_ctl_classp; +static dev_t sdio_ctl_number; +static uint32_t sdio_ctl_inited; + +enum { + MSM_SDIO_CTL_DEBUG = 1U << 0, + MSM_SDIO_CTL_DUMP_BUFFER = 1U << 1, +}; + +#if defined(DEBUG) +#define D_DUMP_BUFFER(prestr, cnt, buf) \ +do { \ + if (msm_sdio_ctl_debug_mask & MSM_SDIO_CTL_DUMP_BUFFER) { \ + int i; \ + pr_info("%s", prestr); \ + for (i = 0; i < cnt; i++) \ + pr_info("%.2x", buf[i]); \ + pr_info("\n"); \ + } \ +} while (0) + +#define D(x...) \ +do { \ + if (msm_sdio_ctl_debug_mask & MSM_SDIO_CTL_DEBUG) \ + pr_info(x); \ +} while (0) + +#else +#define D_DUMP_BUFFER(prestr, cnt, buf) do {} while (0) +#define D(x...) do {} while (0) +#endif + +static void sdio_ctl_receive_cb(void *data, int size, void *priv) +{ + struct sdio_ctl_list_elem *list_elem = NULL; + int id = ((struct sdio_ctl_dev *)(priv))->id; + + if (id < 0 || id >= NUM_SDIO_CTL_PORTS) + return; + if (!data || size <= 0) { + wake_up(&sdio_ctl_devp[id]->read_wait_queue); + return; + } + + list_elem = kmalloc(sizeof(struct sdio_ctl_list_elem), GFP_KERNEL); + if (!list_elem) { + pr_err("%s: list_elem alloc failed\n", __func__); + return; + } + + list_elem->ctl_pkt.data = kmalloc(size, GFP_KERNEL); + if (!list_elem->ctl_pkt.data) { + pr_err("%s: list_elem->data alloc failed\n", __func__); + kfree(list_elem); + return; + } + memcpy(list_elem->ctl_pkt.data, data, size); + list_elem->ctl_pkt.data_size = size; + mutex_lock(&sdio_ctl_devp[id]->rx_lock); + list_add_tail(&list_elem->list, &sdio_ctl_devp[id]->rx_list); + sdio_ctl_devp[id]->read_avail += size; + mutex_unlock(&sdio_ctl_devp[id]->rx_lock); + wake_up(&sdio_ctl_devp[id]->read_wait_queue); +} + +static void sdio_ctl_write_done(void *data, int size, void *priv) +{ + int id = ((struct sdio_ctl_dev *)(priv))->id; + if (id < 0 || id >= NUM_SDIO_CTL_PORTS) + return; + + wake_up(&sdio_ctl_devp[id]->write_wait_queue); +} + +static long sdio_ctl_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret; + struct sdio_ctl_dev *sdio_ctl_devp; + + sdio_ctl_devp = file->private_data; + if (!sdio_ctl_devp) + return -ENODEV; + + switch (cmd) { + case TIOCMGET: + ret = sdio_cmux_tiocmget(sdio_ctl_devp->id); + break; + case TIOCMSET: + ret = sdio_cmux_tiocmset(sdio_ctl_devp->id, arg, ~arg); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +ssize_t sdio_ctl_read(struct file *file, + char __user *buf, + size_t count, + loff_t *ppos) +{ + int r = 0, id, bytes_to_read; + struct sdio_ctl_dev *sdio_ctl_devp; + struct sdio_ctl_list_elem *list_elem = NULL; + + sdio_ctl_devp = file->private_data; + + if (!sdio_ctl_devp) + return -ENODEV; + + D("%s: read from ch%d\n", __func__, sdio_ctl_devp->id); + + id = sdio_ctl_devp->id; + mutex_lock(&sdio_ctl_devp->rx_lock); + while (sdio_ctl_devp->read_avail <= 0) { + mutex_unlock(&sdio_ctl_devp->rx_lock); + r = wait_event_interruptible(sdio_ctl_devp->read_wait_queue, + sdio_ctl_devp->read_avail > 0 || + !is_remote_open(id)); + if (sdio_cmux_is_channel_reset(id)) + return -ENETRESET; + + if (!is_remote_open(id)) + return -ENODEV; + + if (r < 0) { + /* qualify error message */ + /* we get this anytime a signal comes in */ + if (r != -ERESTARTSYS) + pr_err("ERROR:%s: wait_event_interruptible " + "ret %i\n", __func__, r); + return r; + } + mutex_lock(&sdio_ctl_devp->rx_lock); + } + + if (list_empty(&sdio_ctl_devp->rx_list)) { + mutex_unlock(&sdio_ctl_devp->rx_lock); + D("%s: Nothing in ch%d's rx_list\n", __func__, + sdio_ctl_devp->id); + return -EAGAIN; + } + + list_elem = list_first_entry(&sdio_ctl_devp->rx_list, + struct sdio_ctl_list_elem, list); + bytes_to_read = (uint32_t)(list_elem->ctl_pkt.data_size); + if (bytes_to_read > count) { + mutex_unlock(&sdio_ctl_devp->rx_lock); + pr_err("%s: Packet size %d > buf size %d\n", __func__, + bytes_to_read, count); + return -ENOMEM; + } + + if (copy_to_user(buf, list_elem->ctl_pkt.data, bytes_to_read)) { + mutex_unlock(&sdio_ctl_devp->rx_lock); + pr_err("%s: copy_to_user failed for ch%d\n", __func__, + sdio_ctl_devp->id); + return -EFAULT; + } + sdio_ctl_devp->read_avail -= bytes_to_read; + list_del(&list_elem->list); + kfree(list_elem->ctl_pkt.data); + kfree(list_elem); + mutex_unlock(&sdio_ctl_devp->rx_lock); + D("%s: Returning %d bytes to ch%d\n", __func__, + bytes_to_read, sdio_ctl_devp->id); + return bytes_to_read; +} + + +ssize_t sdio_ctl_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int r = 0, id; + char *temp_buf; + struct sdio_ctl_dev *sdio_ctl_devp; + + if (count <= 0) + return -EINVAL; + + sdio_ctl_devp = file->private_data; + if (!sdio_ctl_devp) + return -ENODEV; + + D("%s: writing %i bytes on ch%d\n", + __func__, count, sdio_ctl_devp->id); + id = sdio_ctl_devp->id; + mutex_lock(&sdio_ctl_devp->dev_lock); + while (sdio_cmux_write_avail(id) < count) { + mutex_unlock(&sdio_ctl_devp->dev_lock); + r = wait_event_interruptible(sdio_ctl_devp->write_wait_queue, + sdio_cmux_write_avail(id) >= count + || !is_remote_open(id)); + + if (sdio_cmux_is_channel_reset(id)) + return -ENETRESET; + + if (!is_remote_open(id)) + return -ENODEV; + + if (r < 0) { + /* qualify error message */ + /* we get this anytime a signal comes in */ + if (r != -ERESTARTSYS) + pr_err("ERROR:%s: wait_event_interruptible " + "ret %i\n", __func__, r); + return r; + } + mutex_lock(&sdio_ctl_devp->dev_lock); + } + + temp_buf = kmalloc(count, GFP_KERNEL); + if (!temp_buf) { + mutex_unlock(&sdio_ctl_devp->dev_lock); + pr_err("%s: temp_buf alloc failed\n", __func__); + return -ENOMEM; + } + + if (copy_from_user(temp_buf, buf, count)) { + mutex_unlock(&sdio_ctl_devp->dev_lock); + pr_err("%s: copy_from_user failed\n", __func__); + kfree(temp_buf); + return -EFAULT; + } + + r = sdio_cmux_write(id, (void *)temp_buf, count); + kfree(temp_buf); + mutex_unlock(&sdio_ctl_devp->dev_lock); + return r; +} + + +int sdio_ctl_open(struct inode *inode, struct file *file) +{ + int r = 0; + struct sdio_ctl_dev *sdio_ctl_devp; + + if (!sdio_ctl_inited) + return -EIO; + + sdio_ctl_devp = container_of(inode->i_cdev, struct sdio_ctl_dev, cdev); + + if (!sdio_ctl_devp) + return -ENODEV; + + D("%s called on sdioctl%d device\n", __func__, sdio_ctl_devp->id); + r = sdio_cmux_open(sdio_ctl_devp->id, sdio_ctl_receive_cb, + sdio_ctl_write_done, NULL, + sdio_ctl_devp); + if (r < 0) { + pr_err("ERROR %s: sdio_cmux_open failed with rc %d\n", + __func__, r); + return r; + } + + mutex_lock(&sdio_ctl_devp->dev_lock); + sdio_ctl_devp->ref_count++; + mutex_unlock(&sdio_ctl_devp->dev_lock); + + file->private_data = sdio_ctl_devp; + return 0; +} + +int sdio_ctl_release(struct inode *inode, struct file *file) +{ + struct sdio_ctl_dev *sdio_ctl_devp; + struct sdio_ctl_list_elem *list_elem = NULL; + + sdio_ctl_devp = file->private_data; + if (!sdio_ctl_devp) + return -EINVAL; + + D("%s called on sdioctl%d device\n", __func__, sdio_ctl_devp->id); + + mutex_lock(&sdio_ctl_devp->dev_lock); + if (sdio_ctl_devp->ref_count > 0) { + sdio_ctl_devp->ref_count--; + if (!sdio_ctl_devp->ref_count) { + mutex_lock(&sdio_ctl_devp->rx_lock); + while (!list_empty(&sdio_ctl_devp->rx_list)) { + list_elem = list_first_entry( + &sdio_ctl_devp->rx_list, + struct sdio_ctl_list_elem, + list); + list_del(&list_elem->list); + kfree(list_elem->ctl_pkt.data); + kfree(list_elem); + } + sdio_ctl_devp->read_avail = 0; + mutex_unlock(&sdio_ctl_devp->rx_lock); + sdio_cmux_close(sdio_ctl_devp->id); + } + } + mutex_unlock(&sdio_ctl_devp->dev_lock); + + file->private_data = NULL; + return 0; +} + +static const struct file_operations sdio_ctl_fops = { + .owner = THIS_MODULE, + .open = sdio_ctl_open, + .release = sdio_ctl_release, + .read = sdio_ctl_read, + .write = sdio_ctl_write, + .unlocked_ioctl = sdio_ctl_ioctl, +}; + +static int sdio_ctl_probe(struct platform_device *pdev) +{ + int i; + int r; + + pr_info("%s Begins\n", __func__); + for (i = 0; i < NUM_SDIO_CTL_PORTS; ++i) { + sdio_ctl_devp[i] = kzalloc(sizeof(struct sdio_ctl_dev), + GFP_KERNEL); + if (IS_ERR(sdio_ctl_devp[i])) { + pr_err("ERROR:%s kmalloc() ENOMEM\n", __func__); + r = -ENOMEM; + goto error0; + } + + sdio_ctl_devp[i]->id = i; + sdio_ctl_devp[i]->ref_count = 0; + + mutex_init(&sdio_ctl_devp[i]->dev_lock); + init_waitqueue_head(&sdio_ctl_devp[i]->read_wait_queue); + init_waitqueue_head(&sdio_ctl_devp[i]->write_wait_queue); + mutex_init(&sdio_ctl_devp[i]->rx_lock); + INIT_LIST_HEAD(&sdio_ctl_devp[i]->rx_list); + sdio_ctl_devp[i]->read_avail = 0; + } + + r = alloc_chrdev_region(&sdio_ctl_number, 0, NUM_SDIO_CTL_PORTS, + DEVICE_NAME); + if (IS_ERR_VALUE(r)) { + pr_err("ERROR:%s: alloc_chrdev_region() ret %i.\n", + __func__, r); + goto error0; + } + + sdio_ctl_classp = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(sdio_ctl_classp)) { + pr_err("ERROR:%s: class_create() ENOMEM\n", __func__); + r = -ENOMEM; + goto error1; + } + + for (i = 0; i < NUM_SDIO_CTL_PORTS; ++i) { + cdev_init(&sdio_ctl_devp[i]->cdev, &sdio_ctl_fops); + sdio_ctl_devp[i]->cdev.owner = THIS_MODULE; + + r = cdev_add(&sdio_ctl_devp[i]->cdev, (sdio_ctl_number + i), + 1); + + if (IS_ERR_VALUE(r)) { + pr_err("%s: cdev_add() ret %i\n", __func__, r); + kfree(sdio_ctl_devp[i]); + goto error2; + } + + sdio_ctl_devp[i]->devicep = + device_create(sdio_ctl_classp, NULL, + (sdio_ctl_number + i), NULL, + DEVICE_NAME "%d", i); + + if (IS_ERR(sdio_ctl_devp[i]->devicep)) { + pr_err("%s: device_create() ENOMEM\n", __func__); + r = -ENOMEM; + cdev_del(&sdio_ctl_devp[i]->cdev); + kfree(sdio_ctl_devp[i]); + goto error2; + } + } + + sdio_ctl_inited = 1; + D("SDIO Control Port Driver Initialized.\n"); + return 0; + +error2: + while (--i >= 0) { + cdev_del(&sdio_ctl_devp[i]->cdev); + device_destroy(sdio_ctl_classp, + MKDEV(MAJOR(sdio_ctl_number), i)); + } + + class_destroy(sdio_ctl_classp); + i = NUM_SDIO_CTL_PORTS; +error1: + unregister_chrdev_region(MAJOR(sdio_ctl_number), NUM_SDIO_CTL_PORTS); +error0: + while (--i >= 0) + kfree(sdio_ctl_devp[i]); + return r; +} + +static int sdio_ctl_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < NUM_SDIO_CTL_PORTS; ++i) { + cdev_del(&sdio_ctl_devp[i]->cdev); + kfree(sdio_ctl_devp[i]); + device_destroy(sdio_ctl_classp, + MKDEV(MAJOR(sdio_ctl_number), i)); + } + class_destroy(sdio_ctl_classp); + unregister_chrdev_region(MAJOR(sdio_ctl_number), NUM_SDIO_CTL_PORTS); + + return 0; +} + +static struct platform_driver sdio_ctl_driver = { + .probe = sdio_ctl_probe, + .remove = sdio_ctl_remove, + .driver = { + .name = "SDIO_CTL", + .owner = THIS_MODULE, + }, +}; + +static int __init sdio_ctl_init(void) +{ + msm_sdio_ctl_debug_mask = 0; + return platform_driver_register(&sdio_ctl_driver); +} + +module_init(sdio_ctl_init); +MODULE_DESCRIPTION("MSM SDIO Control Port"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/sdio_dmux.c b/arch/arm/mach-msm/sdio_dmux.c new file mode 100644 index 00000000000..46788f06e41 --- /dev/null +++ b/arch/arm/mach-msm/sdio_dmux.c @@ -0,0 +1,906 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * SDIO DMUX module. + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SDIO_CH_LOCAL_OPEN 0x1 +#define SDIO_CH_REMOTE_OPEN 0x2 +#define SDIO_CH_IN_RESET 0x4 + +#define SDIO_MUX_HDR_MAGIC_NO 0x33fc + +#define SDIO_MUX_HDR_CMD_DATA 0 +#define SDIO_MUX_HDR_CMD_OPEN 1 +#define SDIO_MUX_HDR_CMD_CLOSE 2 + +#define LOW_WATERMARK 2 +#define HIGH_WATERMARK 4 + +static int msm_sdio_dmux_debug_enable; +module_param_named(debug_enable, msm_sdio_dmux_debug_enable, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#if defined(DEBUG) +static uint32_t sdio_dmux_read_cnt; +static uint32_t sdio_dmux_write_cnt; +static uint32_t sdio_dmux_write_cpy_cnt; +static uint32_t sdio_dmux_write_cpy_bytes; + +#define DBG(x...) do { \ + if (msm_sdio_dmux_debug_enable) \ + pr_debug(x); \ + } while (0) + +#define DBG_INC_READ_CNT(x) do { \ + sdio_dmux_read_cnt += (x); \ + if (msm_sdio_dmux_debug_enable) \ + pr_debug("%s: total read bytes %u\n", \ + __func__, sdio_dmux_read_cnt); \ + } while (0) + +#define DBG_INC_WRITE_CNT(x) do { \ + sdio_dmux_write_cnt += (x); \ + if (msm_sdio_dmux_debug_enable) \ + pr_debug("%s: total written bytes %u\n", \ + __func__, sdio_dmux_write_cnt); \ + } while (0) + +#define DBG_INC_WRITE_CPY(x) do { \ + sdio_dmux_write_cpy_bytes += (x); \ + sdio_dmux_write_cpy_cnt++; \ + if (msm_sdio_dmux_debug_enable) \ + pr_debug("%s: total write copy cnt %u, bytes %u\n", \ + __func__, sdio_dmux_write_cpy_cnt, \ + sdio_dmux_write_cpy_bytes); \ + } while (0) +#else +#define DBG(x...) do { } while (0) +#define DBG_INC_READ_CNT(x...) do { } while (0) +#define DBG_INC_WRITE_CNT(x...) do { } while (0) +#define DBG_INC_WRITE_CPY(x...) do { } while (0) +#endif + +struct sdio_ch_info { + uint32_t status; + void (*receive_cb)(void *, struct sk_buff *); + void (*write_done)(void *, struct sk_buff *); + void *priv; + spinlock_t lock; + int num_tx_pkts; + int use_wm; +}; + +static struct sk_buff_head sdio_mux_write_pool; +static spinlock_t sdio_mux_write_lock; + +static struct sdio_channel *sdio_mux_ch; +static struct sdio_ch_info sdio_ch[SDIO_DMUX_NUM_CHANNELS]; +struct wake_lock sdio_mux_ch_wakelock; +static int sdio_mux_initialized; +static int fatal_error; + +struct sdio_mux_hdr { + uint16_t magic_num; + uint8_t reserved; + uint8_t cmd; + uint8_t pad_len; + uint8_t ch_id; + uint16_t pkt_len; +}; + +struct sdio_partial_pkt_info { + uint32_t valid; + struct sk_buff *skb; + struct sdio_mux_hdr *hdr; +}; + +static void sdio_mux_read_data(struct work_struct *work); +static void sdio_mux_write_data(struct work_struct *work); +static void sdio_mux_send_open_cmd(uint32_t id); + +static DEFINE_MUTEX(sdio_mux_lock); +static DECLARE_WORK(work_sdio_mux_read, sdio_mux_read_data); +static DECLARE_WORK(work_sdio_mux_write, sdio_mux_write_data); +static DECLARE_DELAYED_WORK(delayed_work_sdio_mux_write, sdio_mux_write_data); + +static struct workqueue_struct *sdio_mux_workqueue; +static struct sdio_partial_pkt_info sdio_partial_pkt; + +#define sdio_ch_is_open(x) \ + (sdio_ch[(x)].status == (SDIO_CH_LOCAL_OPEN | SDIO_CH_REMOTE_OPEN)) + +#define sdio_ch_is_local_open(x) \ + (sdio_ch[(x)].status & SDIO_CH_LOCAL_OPEN) + +#define sdio_ch_is_remote_open(x) \ + (sdio_ch[(x)].status & SDIO_CH_REMOTE_OPEN) + +#define sdio_ch_is_in_reset(x) \ + (sdio_ch[(x)].status & SDIO_CH_IN_RESET) + +static inline void skb_set_data(struct sk_buff *skb, + unsigned char *data, + unsigned int len) +{ + /* panic if tail > end */ + skb->data = data; + skb->tail = skb->data + len; + skb->len = len; + skb->truesize = len + sizeof(struct sk_buff); +} + +static void sdio_mux_save_partial_pkt(struct sdio_mux_hdr *hdr, + struct sk_buff *skb_mux) +{ + struct sk_buff *skb; + + /* i think we can avoid cloning here */ + skb = skb_clone(skb_mux, GFP_KERNEL); + if (!skb) { + pr_err("%s: cannot clone skb\n", __func__); + return; + } + + /* protect? */ + skb_set_data(skb, (unsigned char *)hdr, + skb->tail - (unsigned char *)hdr); + sdio_partial_pkt.skb = skb; + sdio_partial_pkt.valid = 1; + DBG("%s: head %p data %p tail %p end %p len %d\n", __func__, + skb->head, skb->data, skb->tail, skb->end, skb->len); + return; +} + +static void *handle_sdio_mux_data(struct sdio_mux_hdr *hdr, + struct sk_buff *skb_mux) +{ + struct sk_buff *skb; + void *rp = (void *)hdr; + unsigned long flags; + + /* protect? */ + rp += sizeof(*hdr); + if (rp < (void *)skb_mux->tail) + rp += (hdr->pkt_len + hdr->pad_len); + + if (rp > (void *)skb_mux->tail) { + /* partial packet */ + sdio_mux_save_partial_pkt(hdr, skb_mux); + goto packet_done; + } + + DBG("%s: hdr %p next %p tail %p pkt_size %d\n", + __func__, hdr, rp, skb_mux->tail, hdr->pkt_len + hdr->pad_len); + + skb = skb_clone(skb_mux, GFP_KERNEL); + if (!skb) { + pr_err("%s: cannot clone skb\n", __func__); + goto packet_done; + } + + skb_set_data(skb, (unsigned char *)(hdr + 1), hdr->pkt_len); + DBG("%s: head %p data %p tail %p end %p len %d\n", + __func__, skb->head, skb->data, skb->tail, skb->end, skb->len); + + /* probably we should check channel status */ + /* discard packet early if local side not open */ + spin_lock_irqsave(&sdio_ch[hdr->ch_id].lock, flags); + if (sdio_ch[hdr->ch_id].receive_cb) + sdio_ch[hdr->ch_id].receive_cb(sdio_ch[hdr->ch_id].priv, skb); + else + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&sdio_ch[hdr->ch_id].lock, flags); + +packet_done: + return rp; +} + +static void *handle_sdio_mux_command(struct sdio_mux_hdr *hdr, + struct sk_buff *skb_mux) +{ + void *rp; + unsigned long flags; + int send_open = 0; + + DBG("%s: cmd %d ch %d\n", __func__, hdr->cmd, hdr->ch_id); + switch (hdr->cmd) { + case SDIO_MUX_HDR_CMD_DATA: + rp = handle_sdio_mux_data(hdr, skb_mux); + break; + case SDIO_MUX_HDR_CMD_OPEN: + spin_lock_irqsave(&sdio_ch[hdr->ch_id].lock, flags); + sdio_ch[hdr->ch_id].status |= SDIO_CH_REMOTE_OPEN; + + if (sdio_ch_is_in_reset(hdr->ch_id)) { + DBG("%s: in reset - sending open cmd\n", __func__); + sdio_ch[hdr->ch_id].status &= ~SDIO_CH_IN_RESET; + send_open = 1; + } + + /* notify client so it can update its status */ + if (sdio_ch[hdr->ch_id].receive_cb) + sdio_ch[hdr->ch_id].receive_cb( + sdio_ch[hdr->ch_id].priv, NULL); + + if (sdio_ch[hdr->ch_id].write_done) + sdio_ch[hdr->ch_id].write_done( + sdio_ch[hdr->ch_id].priv, NULL); + spin_unlock_irqrestore(&sdio_ch[hdr->ch_id].lock, flags); + rp = hdr + 1; + if (send_open) + sdio_mux_send_open_cmd(hdr->ch_id); + + break; + case SDIO_MUX_HDR_CMD_CLOSE: + /* probably should drop pending write */ + spin_lock_irqsave(&sdio_ch[hdr->ch_id].lock, flags); + sdio_ch[hdr->ch_id].status &= ~SDIO_CH_REMOTE_OPEN; + spin_unlock_irqrestore(&sdio_ch[hdr->ch_id].lock, flags); + rp = hdr + 1; + break; + default: + rp = hdr + 1; + } + + return rp; +} + +static void *handle_sdio_partial_pkt(struct sk_buff *skb_mux) +{ + struct sk_buff *p_skb; + struct sdio_mux_hdr *p_hdr; + void *ptr, *rp = skb_mux->data; + + /* protoect? */ + if (sdio_partial_pkt.valid) { + p_skb = sdio_partial_pkt.skb; + + ptr = skb_push(skb_mux, p_skb->len); + memcpy(ptr, p_skb->data, p_skb->len); + sdio_partial_pkt.skb = NULL; + sdio_partial_pkt.valid = 0; + dev_kfree_skb_any(p_skb); + + DBG("%s: head %p data %p tail %p end %p len %d\n", __func__, + skb_mux->head, skb_mux->data, skb_mux->tail, + skb_mux->end, skb_mux->len); + + p_hdr = (struct sdio_mux_hdr *)skb_mux->data; + rp = handle_sdio_mux_command(p_hdr, skb_mux); + } + return rp; +} + +static void sdio_mux_read_data(struct work_struct *work) +{ + struct sk_buff *skb_mux; + void *ptr = 0; + int sz, rc, len = 0; + struct sdio_mux_hdr *hdr; + + DBG("%s: reading\n", __func__); + /* should probably have a separate read lock */ + mutex_lock(&sdio_mux_lock); + sz = sdio_read_avail(sdio_mux_ch); + DBG("%s: read avail %d\n", __func__, sz); + if (sz <= 0) { + if (sz) + pr_err("%s: read avail failed %d\n", __func__, sz); + mutex_unlock(&sdio_mux_lock); + return; + } + + /* net_ip_aling is probably not required */ + if (sdio_partial_pkt.valid) + len = sdio_partial_pkt.skb->len; + + /* If allocation fails attempt to get a smaller chunk of mem */ + do { + skb_mux = __dev_alloc_skb(sz + NET_IP_ALIGN + len, GFP_KERNEL); + if (skb_mux) + break; + + pr_err("%s: cannot allocate skb of size:%d + " + "%d (NET_SKB_PAD)\n", __func__, + sz + NET_IP_ALIGN + len, NET_SKB_PAD); + /* the skb structure adds NET_SKB_PAD bytes to the memory + * request, which may push the actual request above PAGE_SIZE + * in that case, we need to iterate one more time to make sure + * we get the memory request under PAGE_SIZE + */ + if (sz + NET_IP_ALIGN + len + NET_SKB_PAD <= PAGE_SIZE) { + pr_err("%s: allocation failed\n", __func__); + mutex_unlock(&sdio_mux_lock); + return; + } + sz /= 2; + } while (1); + + skb_reserve(skb_mux, NET_IP_ALIGN + len); + ptr = skb_put(skb_mux, sz); + + /* half second wakelock is fine? */ + wake_lock_timeout(&sdio_mux_ch_wakelock, HZ / 2); + rc = sdio_read(sdio_mux_ch, ptr, sz); + DBG("%s: read %d\n", __func__, rc); + if (rc) { + pr_err("%s: sdio read failed %d\n", __func__, rc); + dev_kfree_skb_any(skb_mux); + mutex_unlock(&sdio_mux_lock); + queue_work(sdio_mux_workqueue, &work_sdio_mux_read); + return; + } + mutex_unlock(&sdio_mux_lock); + + DBG_INC_READ_CNT(sz); + DBG("%s: head %p data %p tail %p end %p len %d\n", __func__, + skb_mux->head, skb_mux->data, skb_mux->tail, + skb_mux->end, skb_mux->len); + + /* move to a separate function */ + /* probably do skb_pull instead of pointer adjustment */ + hdr = handle_sdio_partial_pkt(skb_mux); + while ((void *)hdr < (void *)skb_mux->tail) { + + if (((void *)hdr + sizeof(*hdr)) > (void *)skb_mux->tail) { + /* handle partial header */ + sdio_mux_save_partial_pkt(hdr, skb_mux); + break; + } + + if (hdr->magic_num != SDIO_MUX_HDR_MAGIC_NO) { + pr_err("%s: packet error\n", __func__); + break; + } + + hdr = handle_sdio_mux_command(hdr, skb_mux); + } + dev_kfree_skb_any(skb_mux); + + DBG("%s: read done\n", __func__); + queue_work(sdio_mux_workqueue, &work_sdio_mux_read); +} + +static int sdio_mux_write(struct sk_buff *skb) +{ + int rc, sz; + + mutex_lock(&sdio_mux_lock); + sz = sdio_write_avail(sdio_mux_ch); + DBG("%s: avail %d len %d\n", __func__, sz, skb->len); + if (skb->len <= sz) { + rc = sdio_write(sdio_mux_ch, skb->data, skb->len); + DBG("%s: write returned %d\n", __func__, rc); + if (rc == 0) + DBG_INC_WRITE_CNT(skb->len); + } else + rc = -ENOMEM; + + mutex_unlock(&sdio_mux_lock); + return rc; +} + +static int sdio_mux_write_cmd(void *data, uint32_t len) +{ + int avail, rc; + for (;;) { + mutex_lock(&sdio_mux_lock); + avail = sdio_write_avail(sdio_mux_ch); + DBG("%s: avail %d len %d\n", __func__, avail, len); + if (avail >= len) { + rc = sdio_write(sdio_mux_ch, data, len); + DBG("%s: write returned %d\n", __func__, rc); + if (!rc) { + DBG_INC_WRITE_CNT(len); + break; + } + } + mutex_unlock(&sdio_mux_lock); + msleep(250); + } + mutex_unlock(&sdio_mux_lock); + return 0; +} + +static void sdio_mux_send_open_cmd(uint32_t id) +{ + struct sdio_mux_hdr hdr = { + .magic_num = SDIO_MUX_HDR_MAGIC_NO, + .cmd = SDIO_MUX_HDR_CMD_OPEN, + .reserved = 0, + .ch_id = id, + .pkt_len = 0, + .pad_len = 0 + }; + + sdio_mux_write_cmd((void *)&hdr, sizeof(hdr)); +} + +static void sdio_mux_write_data(struct work_struct *work) +{ + int rc, reschedule = 0; + int notify = 0; + struct sk_buff *skb; + unsigned long flags; + int avail; + int ch_id; + + spin_lock_irqsave(&sdio_mux_write_lock, flags); + while ((skb = __skb_dequeue(&sdio_mux_write_pool))) { + ch_id = ((struct sdio_mux_hdr *)skb->data)->ch_id; + + avail = sdio_write_avail(sdio_mux_ch); + if (avail < skb->len) { + /* we may have to wait for write avail + * notification from sdio al + */ + DBG("%s: sdio_write_avail(%d) < skb->len(%d)\n", + __func__, avail, skb->len); + + reschedule = 1; + break; + } + spin_unlock_irqrestore(&sdio_mux_write_lock, flags); + rc = sdio_mux_write(skb); + spin_lock_irqsave(&sdio_mux_write_lock, flags); + if (rc == 0) { + + spin_lock(&sdio_ch[ch_id].lock); + sdio_ch[ch_id].num_tx_pkts--; + spin_unlock(&sdio_ch[ch_id].lock); + + if (sdio_ch[ch_id].write_done) + sdio_ch[ch_id].write_done( + sdio_ch[ch_id].priv, skb); + else + dev_kfree_skb_any(skb); + } else if (rc == -EAGAIN || rc == -ENOMEM) { + /* recoverable error - retry again later */ + reschedule = 1; + break; + } else if (rc == -ENODEV) { + /* + * sdio_al suffered some kind of fatal error + * prevent future writes and clean up pending ones + */ + fatal_error = 1; + dev_kfree_skb_any(skb); + while ((skb = __skb_dequeue(&sdio_mux_write_pool))) + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&sdio_mux_write_lock, flags); + return; + } else { + /* unknown error condition - drop the + * skb and reschedule for the + * other skb's + */ + pr_err("%s: sdio_mux_write error %d" + " for ch %d, skb=%p\n", + __func__, rc, ch_id, skb); + notify = 1; + break; + } + } + + if (reschedule) { + if (sdio_ch_is_in_reset(ch_id)) { + notify = 1; + } else { + __skb_queue_head(&sdio_mux_write_pool, skb); + queue_delayed_work(sdio_mux_workqueue, + &delayed_work_sdio_mux_write, + msecs_to_jiffies(250) + ); + } + } + + if (notify) { + spin_lock(&sdio_ch[ch_id].lock); + sdio_ch[ch_id].num_tx_pkts--; + spin_unlock(&sdio_ch[ch_id].lock); + + if (sdio_ch[ch_id].write_done) + sdio_ch[ch_id].write_done( + sdio_ch[ch_id].priv, skb); + else + dev_kfree_skb_any(skb); + } + spin_unlock_irqrestore(&sdio_mux_write_lock, flags); +} + +int msm_sdio_is_channel_in_reset(uint32_t id) +{ + int rc = 0; + + if (id >= SDIO_DMUX_NUM_CHANNELS) + return -EINVAL; + + if (sdio_ch_is_in_reset(id)) + rc = 1; + + return rc; +} + +int msm_sdio_dmux_write(uint32_t id, struct sk_buff *skb) +{ + int rc = 0; + struct sdio_mux_hdr *hdr; + unsigned long flags; + struct sk_buff *new_skb; + + if (id >= SDIO_DMUX_NUM_CHANNELS) + return -EINVAL; + if (!skb) + return -EINVAL; + if (!sdio_mux_initialized) + return -ENODEV; + if (fatal_error) + return -ENODEV; + + DBG("%s: writing to ch %d len %d\n", __func__, id, skb->len); + spin_lock_irqsave(&sdio_ch[id].lock, flags); + if (sdio_ch_is_in_reset(id)) { + spin_unlock_irqrestore(&sdio_ch[id].lock, flags); + pr_err("%s: port is in reset: %d\n", __func__, + sdio_ch[id].status); + return -ENETRESET; + } + if (!sdio_ch_is_local_open(id)) { + spin_unlock_irqrestore(&sdio_ch[id].lock, flags); + pr_err("%s: port not open: %d\n", __func__, sdio_ch[id].status); + return -ENODEV; + } + if (sdio_ch[id].use_wm && + (sdio_ch[id].num_tx_pkts >= HIGH_WATERMARK)) { + spin_unlock_irqrestore(&sdio_ch[id].lock, flags); + pr_err("%s: watermark exceeded: %d\n", __func__, id); + return -EAGAIN; + } + spin_unlock_irqrestore(&sdio_ch[id].lock, flags); + + spin_lock_irqsave(&sdio_mux_write_lock, flags); + /* if skb do not have any tailroom for padding, + copy the skb into a new expanded skb */ + if ((skb->len & 0x3) && (skb_tailroom(skb) < (4 - (skb->len & 0x3)))) { + /* revisit, probably dev_alloc_skb and memcpy is effecient */ + new_skb = skb_copy_expand(skb, skb_headroom(skb), + 4 - (skb->len & 0x3), GFP_ATOMIC); + if (new_skb == NULL) { + pr_err("%s: cannot allocate skb\n", __func__); + rc = -ENOMEM; + goto write_done; + } + dev_kfree_skb_any(skb); + skb = new_skb; + DBG_INC_WRITE_CPY(skb->len); + } + + hdr = (struct sdio_mux_hdr *)skb_push(skb, sizeof(struct sdio_mux_hdr)); + + /* caller should allocate for hdr and padding + hdr is fine, padding is tricky */ + hdr->magic_num = SDIO_MUX_HDR_MAGIC_NO; + hdr->cmd = SDIO_MUX_HDR_CMD_DATA; + hdr->reserved = 0; + hdr->ch_id = id; + hdr->pkt_len = skb->len - sizeof(struct sdio_mux_hdr); + if (skb->len & 0x3) + skb_put(skb, 4 - (skb->len & 0x3)); + + hdr->pad_len = skb->len - (sizeof(struct sdio_mux_hdr) + hdr->pkt_len); + + DBG("%s: data %p, tail %p skb len %d pkt len %d pad len %d\n", + __func__, skb->data, skb->tail, skb->len, + hdr->pkt_len, hdr->pad_len); + __skb_queue_tail(&sdio_mux_write_pool, skb); + + spin_lock(&sdio_ch[id].lock); + sdio_ch[id].num_tx_pkts++; + spin_unlock(&sdio_ch[id].lock); + + queue_work(sdio_mux_workqueue, &work_sdio_mux_write); + +write_done: + spin_unlock_irqrestore(&sdio_mux_write_lock, flags); + return rc; +} + +int msm_sdio_dmux_open(uint32_t id, void *priv, + void (*receive_cb)(void *, struct sk_buff *), + void (*write_done)(void *, struct sk_buff *)) +{ + unsigned long flags; + + DBG("%s: opening ch %d\n", __func__, id); + if (!sdio_mux_initialized) + return -ENODEV; + if (id >= SDIO_DMUX_NUM_CHANNELS) + return -EINVAL; + + spin_lock_irqsave(&sdio_ch[id].lock, flags); + if (sdio_ch_is_local_open(id)) { + pr_info("%s: Already opened %d\n", __func__, id); + spin_unlock_irqrestore(&sdio_ch[id].lock, flags); + goto open_done; + } + + sdio_ch[id].receive_cb = receive_cb; + sdio_ch[id].write_done = write_done; + sdio_ch[id].priv = priv; + sdio_ch[id].status |= SDIO_CH_LOCAL_OPEN; + sdio_ch[id].num_tx_pkts = 0; + sdio_ch[id].use_wm = 0; + spin_unlock_irqrestore(&sdio_ch[id].lock, flags); + + sdio_mux_send_open_cmd(id); + +open_done: + pr_info("%s: opened ch %d\n", __func__, id); + return 0; +} + +int msm_sdio_dmux_close(uint32_t id) +{ + struct sdio_mux_hdr hdr; + unsigned long flags; + + if (id >= SDIO_DMUX_NUM_CHANNELS) + return -EINVAL; + DBG("%s: closing ch %d\n", __func__, id); + if (!sdio_mux_initialized) + return -ENODEV; + spin_lock_irqsave(&sdio_ch[id].lock, flags); + + sdio_ch[id].receive_cb = NULL; + sdio_ch[id].priv = NULL; + sdio_ch[id].status &= ~SDIO_CH_LOCAL_OPEN; + sdio_ch[id].status &= ~SDIO_CH_IN_RESET; + spin_unlock_irqrestore(&sdio_ch[id].lock, flags); + + hdr.magic_num = SDIO_MUX_HDR_MAGIC_NO; + hdr.cmd = SDIO_MUX_HDR_CMD_CLOSE; + hdr.reserved = 0; + hdr.ch_id = id; + hdr.pkt_len = 0; + hdr.pad_len = 0; + + sdio_mux_write_cmd((void *)&hdr, sizeof(hdr)); + + pr_info("%s: closed ch %d\n", __func__, id); + return 0; +} + +static void sdio_mux_notify(void *_dev, unsigned event) +{ + DBG("%s: event %d notified\n", __func__, event); + + /* write avail may not be enouogh for a packet, but should be fine */ + if ((event == SDIO_EVENT_DATA_WRITE_AVAIL) && + sdio_write_avail(sdio_mux_ch)) + queue_work(sdio_mux_workqueue, &work_sdio_mux_write); + + if ((event == SDIO_EVENT_DATA_READ_AVAIL) && + sdio_read_avail(sdio_mux_ch)) + queue_work(sdio_mux_workqueue, &work_sdio_mux_read); +} + +int msm_sdio_dmux_is_ch_full(uint32_t id) +{ + unsigned long flags; + int ret; + + if (id >= SDIO_DMUX_NUM_CHANNELS) + return -EINVAL; + + spin_lock_irqsave(&sdio_ch[id].lock, flags); + sdio_ch[id].use_wm = 1; + ret = sdio_ch[id].num_tx_pkts >= HIGH_WATERMARK; + DBG("%s: ch %d num tx pkts=%d, HWM=%d\n", __func__, + id, sdio_ch[id].num_tx_pkts, ret); + if (!sdio_ch_is_local_open(id)) { + ret = -ENODEV; + pr_err("%s: port not open: %d\n", __func__, sdio_ch[id].status); + } + spin_unlock_irqrestore(&sdio_ch[id].lock, flags); + + return ret; +} + +int msm_sdio_dmux_is_ch_low(uint32_t id) +{ + unsigned long flags; + int ret; + + if (id >= SDIO_DMUX_NUM_CHANNELS) + return -EINVAL; + + spin_lock_irqsave(&sdio_ch[id].lock, flags); + sdio_ch[id].use_wm = 1; + ret = sdio_ch[id].num_tx_pkts <= LOW_WATERMARK; + DBG("%s: ch %d num tx pkts=%d, LWM=%d\n", __func__, + id, sdio_ch[id].num_tx_pkts, ret); + if (!sdio_ch_is_local_open(id)) { + ret = -ENODEV; + pr_err("%s: port not open: %d\n", __func__, sdio_ch[id].status); + } + spin_unlock_irqrestore(&sdio_ch[id].lock, flags); + + return ret; +} + +#ifdef CONFIG_DEBUG_FS + +static int debug_tbl(char *buf, int max) +{ + int i = 0; + int j; + + for (j = 0; j < SDIO_DMUX_NUM_CHANNELS; ++j) { + i += scnprintf(buf + i, max - i, + "ch%02d local open=%s remote open=%s\n", + j, sdio_ch_is_local_open(j) ? "Y" : "N", + sdio_ch_is_remote_open(j) ? "Y" : "N"); + } + + return i; +} + +#define DEBUG_BUFMAX 4096 +static char debug_buffer[DEBUG_BUFMAX]; + +static ssize_t debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int (*fill)(char *buf, int max) = file->private_data; + int bsize = fill(debug_buffer, DEBUG_BUFMAX); + return simple_read_from_buffer(buf, count, ppos, debug_buffer, bsize); +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + + +static const struct file_operations debug_ops = { + .read = debug_read, + .open = debug_open, +}; + +static void debug_create(const char *name, mode_t mode, + struct dentry *dent, + int (*fill)(char *buf, int max)) +{ + debugfs_create_file(name, mode, dent, fill, &debug_ops); +} + +#endif + +static int sdio_dmux_probe(struct platform_device *pdev) +{ + int rc; + + DBG("%s probe called\n", __func__); + + if (!sdio_mux_initialized) { + sdio_mux_workqueue = create_singlethread_workqueue("sdio_dmux"); + if (!sdio_mux_workqueue) + return -ENOMEM; + + skb_queue_head_init(&sdio_mux_write_pool); + spin_lock_init(&sdio_mux_write_lock); + + for (rc = 0; rc < SDIO_DMUX_NUM_CHANNELS; ++rc) + spin_lock_init(&sdio_ch[rc].lock); + + + wake_lock_init(&sdio_mux_ch_wakelock, WAKE_LOCK_SUSPEND, + "sdio_dmux"); + } + + rc = sdio_open("SDIO_RMNT", &sdio_mux_ch, NULL, sdio_mux_notify); + if (rc < 0) { + pr_err("%s: sido open failed %d\n", __func__, rc); + wake_lock_destroy(&sdio_mux_ch_wakelock); + destroy_workqueue(sdio_mux_workqueue); + sdio_mux_initialized = 0; + return rc; + } + + sdio_mux_initialized = 1; + return 0; +} + +static int sdio_dmux_remove(struct platform_device *pdev) +{ + int i; + unsigned long ch_lock_flags; + unsigned long write_lock_flags; + struct sk_buff *skb; + + DBG("%s remove called\n", __func__); + if (!sdio_mux_initialized) + return 0; + + /* set reset state for any open channels */ + for (i = 0; i < SDIO_DMUX_NUM_CHANNELS; ++i) { + spin_lock_irqsave(&sdio_ch[i].lock, ch_lock_flags); + if (sdio_ch_is_open(i)) { + sdio_ch[i].status |= SDIO_CH_IN_RESET; + sdio_ch[i].status &= ~SDIO_CH_REMOTE_OPEN; + + /* cancel any pending writes */ + spin_lock_irqsave(&sdio_mux_write_lock, + write_lock_flags); + while ((skb = __skb_dequeue(&sdio_mux_write_pool))) { + if (sdio_ch[i].write_done) + sdio_ch[i].write_done( + sdio_ch[i].priv, skb); + else + dev_kfree_skb_any(skb); + } + spin_unlock_irqrestore(&sdio_mux_write_lock, + write_lock_flags); + + /* notify client so it can update its status */ + if (sdio_ch[i].receive_cb) + sdio_ch[i].receive_cb( + sdio_ch[i].priv, NULL); + } + spin_unlock_irqrestore(&sdio_ch[i].lock, ch_lock_flags); + } + + return 0; +} + +static struct platform_driver sdio_dmux_driver = { + .probe = sdio_dmux_probe, + .remove = sdio_dmux_remove, + .driver = { + .name = "SDIO_RMNT", + .owner = THIS_MODULE, + }, +}; + +static int __init sdio_dmux_init(void) +{ +#ifdef CONFIG_DEBUG_FS + struct dentry *dent; + + dent = debugfs_create_dir("sdio_dmux", 0); + if (!IS_ERR(dent)) + debug_create("tbl", 0444, dent, debug_tbl); +#endif + return platform_driver_register(&sdio_dmux_driver); +} + +module_init(sdio_dmux_init); +MODULE_DESCRIPTION("MSM SDIO DMUX"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/sdio_smem.c b/arch/arm/mach-msm/sdio_smem.c new file mode 100644 index 00000000000..4da81996c2f --- /dev/null +++ b/arch/arm/mach-msm/sdio_smem.c @@ -0,0 +1,149 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +static void sdio_smem_read(struct work_struct *work); + +static struct sdio_channel *channel; +static struct workqueue_struct *workq; +static DECLARE_WORK(work_read, sdio_smem_read); +static DECLARE_WAIT_QUEUE_HEAD(waitq); +static int bytes_avail; + +static void sdio_smem_release(struct device *dev) +{ + pr_debug("sdio smem released\n"); +} + +static struct sdio_smem_client client; + +static void sdio_smem_read(struct work_struct *work) +{ + int err; + int read_avail; + char *data = client.buf; + + read_avail = sdio_read_avail(channel); + if (read_avail > bytes_avail || + read_avail < 0) { + pr_err("Error: read_avail=%d bytes_avail=%d\n", + read_avail, bytes_avail); + client.cb_func(SDIO_SMEM_EVENT_READ_ERR); + return; + } + + err = sdio_read(channel, + &data[client.size - bytes_avail], + read_avail); + if (err < 0) { + pr_err("sdio_read error (%d)", err); + client.cb_func(SDIO_SMEM_EVENT_READ_ERR); + return; + } + + bytes_avail -= read_avail; + pr_debug("read %d bytes (bytes_avail = %d)\n", + read_avail, bytes_avail); + + if (!bytes_avail) { + bytes_avail = client.size; + err = client.cb_func(SDIO_SMEM_EVENT_READ_DONE); + } + if (err) + pr_err("error (%d) on callback\n", err); +} + +static void sdio_smem_notify(void *priv, unsigned event) +{ + pr_debug("%d event received\n", event); + + if (event == SDIO_EVENT_DATA_READ_AVAIL || + event == SDIO_EVENT_DATA_WRITE_AVAIL) + queue_work(workq, &work_read); +} + +int sdio_smem_register_client(void) +{ + int err = 0; + + if (!client.buf || !client.size || !client.cb_func) + return -EINVAL; + + pr_debug("buf = %p\n", client.buf); + pr_debug("size = 0x%x\n", client.size); + + bytes_avail = client.size; + workq = create_singlethread_workqueue("sdio_smem"); + if (!workq) + return -ENOMEM; + + err = sdio_open("SDIO_SMEM", &channel, NULL, sdio_smem_notify); + if (err < 0) { + pr_err("sdio_open error (%d)\n", err); + destroy_workqueue(workq); + return err; + } + pr_debug("SDIO SMEM channel opened\n"); + return err; +} + +int sdio_smem_unregister_client(void) +{ + destroy_workqueue(workq); + bytes_avail = 0; + client.buf = NULL; + client.cb_func = NULL; + client.size = 0; + pr_debug("SDIO SMEM channel closed\n"); + return 0; +} + +static int sdio_smem_probe(struct platform_device *pdev) +{ + client.plat_dev.name = "SDIO_SMEM_CLIENT"; + client.plat_dev.id = -1; + client.plat_dev.dev.release = sdio_smem_release; + + return platform_device_register(&client.plat_dev); +} + +static int sdio_smem_remove(struct platform_device *pdev) +{ + platform_device_unregister(&client.plat_dev); + memset(&client, 0, sizeof(client)); + return 0; +} +static struct platform_driver sdio_smem_drv = { + .probe = sdio_smem_probe, + .remove = sdio_smem_remove, + .driver = { + .name = "SDIO_SMEM", + .owner = THIS_MODULE, + }, +}; + +static int __init sdio_smem_init(void) +{ + return platform_driver_register(&sdio_smem_drv); +}; + +module_init(sdio_smem_init); + +MODULE_DESCRIPTION("SDIO SMEM"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/sdio_tty.c b/arch/arm/mach-msm/sdio_tty.c new file mode 100644 index 00000000000..d1254e647df --- /dev/null +++ b/arch/arm/mach-msm/sdio_tty.c @@ -0,0 +1,588 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define INPUT_SPEED 4800 +#define OUTPUT_SPEED 4800 +#define SDIO_TTY_MODULE_NAME "sdio_tty" +#define SDIO_TTY_MAX_PACKET_SIZE 4096 +#define MAX_SDIO_TTY_DRV 1 + +enum sdio_tty_state { + TTY_INITIAL = 0, + TTY_REGISTERED = 1, + TTY_OPENED = 2, + TTY_CLOSED = 3, +}; + +struct sdio_tty { + struct sdio_channel *ch; + char *sdio_ch_name; + struct workqueue_struct *workq; + struct work_struct work_read; + wait_queue_head_t waitq; + struct tty_driver *tty_drv; + struct tty_struct *tty_str; + int debug_msg_on; + char *read_buf; + enum sdio_tty_state sdio_tty_state; + int is_sdio_open; +}; + +static struct sdio_tty *sdio_tty; + +#define DEBUG_MSG(sdio_tty, x...) if (sdio_tty->debug_msg_on) pr_info(x) + +static void sdio_tty_read(struct work_struct *work) +{ + int ret = 0; + int read_avail = 0; + int left = 0; + int total_push = 0; + int num_push = 0; + struct sdio_tty *sdio_tty_drv = NULL; + + sdio_tty_drv = container_of(work, struct sdio_tty, work_read); + + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty", + __func__); + return ; + } + + if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_state = %d", + __func__, sdio_tty_drv->sdio_tty_state); + return; + } + + if (!sdio_tty_drv->read_buf) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL read_buf", __func__); + return; + } + + /* Read the data from teh SDIO channel as long as there is available + data */ + while (1) { + if (test_bit(TTY_THROTTLED, &sdio_tty_drv->tty_str->flags)) { + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: TTY_THROTTLED bit" + " is set, exit", + __func__); + return; + } + + total_push = 0; + read_avail = sdio_read_avail(sdio_tty_drv->ch); + + DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME + ": %s: read_avail is %d", __func__, + read_avail); + + if (read_avail == 0) { + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: read_avail is 0", + __func__); + return; + } + + if (read_avail > SDIO_TTY_MAX_PACKET_SIZE) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: read_avail(%d) is " + "bigger than SDIO_TTY_MAX_PACKET_SIZE(%d)", + __func__, read_avail, SDIO_TTY_MAX_PACKET_SIZE); + return; + } + + ret = sdio_read(sdio_tty_drv->ch, + sdio_tty_drv->read_buf, + read_avail); + if (ret < 0) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_read error(%d)", + __func__, ret); + return; + } + + left = read_avail; + do { + num_push = tty_insert_flip_string( + sdio_tty_drv->tty_str, + sdio_tty_drv->read_buf+total_push, + left); + total_push += num_push; + left -= num_push; + tty_flip_buffer_push(sdio_tty_drv->tty_str); + } while (left != 0); + + if (total_push != read_avail) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: failed, total_push" + "(%d) != read_avail(%d)\n", + __func__, total_push, read_avail); + } + + tty_flip_buffer_push(sdio_tty_drv->tty_str); + + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: End of read %d bytes", + __func__, read_avail); + } +} + +/** + * sdio_tty_write_room + * + * This is the write_room function of the tty driver. + * + * @tty: pointer to tty struct. + * @return free bytes for write. + * + */ +static int sdio_tty_write_room(struct tty_struct *tty) +{ + int write_avail = 0; + struct sdio_tty *sdio_tty_drv = NULL; + + if (!tty) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", + __func__); + return -ENODEV; + } + sdio_tty_drv = tty->driver_data; + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + return -ENODEV; + } + + write_avail = sdio_write_avail(sdio_tty_drv->ch); + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: write_avail=%d", + __func__, write_avail); + + return write_avail; +} + +/** + * sdio_tty_write_callback + * this is the write callback of the tty driver. + * + * @tty: pointer to tty struct. + * @buf: buffer to write from. + * @count: number of bytes to write. + * @return bytes written or negative value on error. + * + * if destination buffer has not enough room for the incoming + * data, writes the possible amount of bytes . + */ +static int sdio_tty_write_callback(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + int write_avail = 0; + int len = count; + int ret = 0; + struct sdio_tty *sdio_tty_drv = NULL; + + if (!tty) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", + __func__); + return -ENODEV; + } + sdio_tty_drv = tty->driver_data; + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + return -ENODEV; + } + + if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_state = %d", + __func__, sdio_tty_drv->sdio_tty_state); + return -EPERM; + } + + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: WRITING CALLBACK CALLED WITH " + "%d bytes\n", + __func__, count); + write_avail = sdio_write_avail(sdio_tty_drv->ch); + if (write_avail == 0) { + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: write_avail is 0\n", + __func__); + return 0; + } + if (write_avail > SDIO_TTY_MAX_PACKET_SIZE) { + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: write_avail(%d) is " + "bigger than max packet size,(%d), setting to " + "max_packet_size\n", + __func__, write_avail, SDIO_TTY_MAX_PACKET_SIZE); + write_avail = SDIO_TTY_MAX_PACKET_SIZE; + } + if (write_avail < count) { + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: write_avail(%d) is " + "smaller than " + "required(%d), writing only %d bytes\n", + __func__, write_avail, count, write_avail); + len = write_avail; + } + ret = sdio_write(sdio_tty_drv->ch, buf, len); + if (ret) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_write failed, ret=%d\n", + __func__, ret); + return 0; + } + + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: End of function, len=%d bytes\n", + __func__, len); + + return len; +} + +static void sdio_tty_notify(void *priv, unsigned event) +{ + struct sdio_tty *sdio_tty_drv = priv; + + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + } + + if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_state = %d", + __func__, sdio_tty_drv->sdio_tty_state); + return; + } + + DEBUG_MSG(sdio_tty_drv, + SDIO_TTY_MODULE_NAME ": %s: event %d received\n", __func__, + event); + + if (event == SDIO_EVENT_DATA_READ_AVAIL) + queue_work(sdio_tty_drv->workq, &sdio_tty_drv->work_read); +} + +/** + * sdio_tty_open + * This is the open callback of the tty driver. it opens + * the sdio channel, and creates the workqueue. + * + * @tty: a pointer to the tty struct. + * @file: file descriptor. + * @return 0 on success or negative value on error. + */ +static int sdio_tty_open(struct tty_struct *tty, struct file *file) +{ + int ret = 0; + struct sdio_tty *sdio_tty_drv = NULL; + + if (!tty) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", + __func__); + return -ENODEV; + } + sdio_tty_drv = sdio_tty; + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + return -ENODEV; + } + + tty->driver_data = sdio_tty_drv; + + sdio_tty_drv->tty_str = tty; + sdio_tty_drv->tty_str->low_latency = 1; + sdio_tty_drv->tty_str->icanon = 0; + set_bit(TTY_NO_WRITE_SPLIT, &sdio_tty_drv->tty_str->flags); + + sdio_tty_drv->read_buf = kzalloc(SDIO_TTY_MAX_PACKET_SIZE, GFP_KERNEL); + if (sdio_tty_drv->read_buf == NULL) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: failed to allocate read_buf", + __func__); + return -ENOMEM; + } + + sdio_tty_drv->workq = create_singlethread_workqueue("sdio_tty_read"); + if (!sdio_tty_drv->workq) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: failed to create workq", + __func__); + return -ENOMEM; + } + + if (sdio_tty_drv->sdio_tty_state == TTY_OPENED) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: tty is already open", + __func__); + return -EBUSY; + } + + if (!sdio_tty_drv->is_sdio_open) { + ret = sdio_open(sdio_tty_drv->sdio_ch_name, &sdio_tty_drv->ch, + sdio_tty_drv, sdio_tty_notify); + if (ret < 0) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_open err=%d\n", + __func__, ret); + destroy_workqueue(sdio_tty_drv->workq); + return ret; + } + + pr_info(SDIO_TTY_MODULE_NAME ": %s: SDIO_TTY channel opened\n", + __func__); + + sdio_tty_drv->is_sdio_open = 1; + } else { + /* If SDIO channel is already open try to read the data + * from the modem + */ + queue_work(sdio_tty_drv->workq, &sdio_tty_drv->work_read); + + } + + sdio_tty_drv->sdio_tty_state = TTY_OPENED; + + pr_info(SDIO_TTY_MODULE_NAME ": %s: TTY device opened\n", + __func__); + + return ret; +} + +/** + * sdio_tty_close + * This is the close callback of the tty driver. it requests + * the main thread to exit, and waits for notification of it. + * it also de-allocates the buffers, and unregisters the tty + * driver and device. + * + * @tty: a pointer to the tty struct. + * @file: file descriptor. + * @return None. + */ +static void sdio_tty_close(struct tty_struct *tty, struct file *file) +{ + struct sdio_tty *sdio_tty_drv = NULL; + + if (!tty) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", + __func__); + return; + } + sdio_tty_drv = tty->driver_data; + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + return; + } + if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: trying to close a " + "TTY device that was not opened\n", + __func__); + return; + } + + flush_workqueue(sdio_tty_drv->workq); + destroy_workqueue(sdio_tty_drv->workq); + + kfree(sdio_tty_drv->read_buf); + sdio_tty_drv->read_buf = NULL; + + sdio_tty_drv->sdio_tty_state = TTY_CLOSED; + + pr_info(SDIO_TTY_MODULE_NAME ": %s: SDIO_TTY channel closed\n", + __func__); +} + +static void sdio_tty_unthrottle(struct tty_struct *tty) +{ + struct sdio_tty *sdio_tty_drv = NULL; + + if (!tty) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", + __func__); + return; + } + sdio_tty_drv = tty->driver_data; + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + return; + } + + if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_state = %d", + __func__, sdio_tty_drv->sdio_tty_state); + return; + } + + queue_work(sdio_tty_drv->workq, &sdio_tty_drv->work_read); + return; +} + +static const struct tty_operations sdio_tty_ops = { + .open = sdio_tty_open, + .close = sdio_tty_close, + .write = sdio_tty_write_callback, + .write_room = sdio_tty_write_room, + .unthrottle = sdio_tty_unthrottle, +}; + +void *sdio_tty_init_tty(char *tty_name, char *sdio_ch_name) +{ + int ret = 0; + struct device *tty_dev; + struct sdio_tty *sdio_tty_drv; + + pr_info(SDIO_TTY_MODULE_NAME ": %s\n", __func__); + + sdio_tty_drv = kzalloc(sizeof(struct sdio_tty), GFP_KERNEL); + if (sdio_tty_drv == NULL) { + pr_err(SDIO_TTY_MODULE_NAME "%s: failed to allocate sdio_tty", + __func__); + return NULL; + } + + sdio_tty = sdio_tty_drv; + sdio_tty_drv->sdio_ch_name = sdio_ch_name; + + INIT_WORK(&sdio_tty_drv->work_read, sdio_tty_read); + + sdio_tty_drv->tty_drv = alloc_tty_driver(MAX_SDIO_TTY_DRV); + + if (!sdio_tty_drv->tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s - tty_drv is NULL", + __func__); + kfree(sdio_tty_drv); + return NULL; + } + + sdio_tty_drv->tty_drv->name = tty_name; + sdio_tty_drv->tty_drv->owner = THIS_MODULE; + sdio_tty_drv->tty_drv->driver_name = "SDIO_tty"; + /* uses dynamically assigned dev_t values */ + sdio_tty_drv->tty_drv->type = TTY_DRIVER_TYPE_SERIAL; + sdio_tty_drv->tty_drv->subtype = SERIAL_TYPE_NORMAL; + sdio_tty_drv->tty_drv->flags = TTY_DRIVER_REAL_RAW + | TTY_DRIVER_DYNAMIC_DEV + | TTY_DRIVER_RESET_TERMIOS; + + /* initializing the tty driver */ + sdio_tty_drv->tty_drv->init_termios = tty_std_termios; + sdio_tty_drv->tty_drv->init_termios.c_cflag = + B4800 | CS8 | CREAD | HUPCL | CLOCAL; + sdio_tty_drv->tty_drv->init_termios.c_ispeed = INPUT_SPEED; + sdio_tty_drv->tty_drv->init_termios.c_ospeed = OUTPUT_SPEED; + + tty_set_operations(sdio_tty_drv->tty_drv, &sdio_tty_ops); + + ret = tty_register_driver(sdio_tty_drv->tty_drv); + if (ret) { + put_tty_driver(sdio_tty_drv->tty_drv); + pr_err(SDIO_TTY_MODULE_NAME ": %s: tty_register_driver() " + "failed\n", __func__); + + sdio_tty_drv->tty_drv = NULL; + kfree(sdio_tty_drv); + return NULL; + } + + tty_dev = tty_register_device(sdio_tty_drv->tty_drv, 0, NULL); + if (IS_ERR(tty_dev)) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: tty_register_device() " + "failed\n", __func__); + tty_unregister_driver(sdio_tty_drv->tty_drv); + put_tty_driver(sdio_tty_drv->tty_drv); + kfree(sdio_tty_drv); + return NULL; + } + + sdio_tty_drv->sdio_tty_state = TTY_REGISTERED; + return sdio_tty_drv; +} +EXPORT_SYMBOL(sdio_tty_init_tty); + +int sdio_tty_uninit_tty(void *sdio_tty_handle) +{ + int ret = 0; + struct sdio_tty *sdio_tty_drv = sdio_tty_handle; + + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + return -ENODEV; + } + + if (sdio_tty_drv->sdio_tty_state != TTY_INITIAL) { + tty_unregister_device(sdio_tty_drv->tty_drv, 0); + + ret = tty_unregister_driver(sdio_tty_drv->tty_drv); + if (ret) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: " + "tty_unregister_driver() failed\n", __func__); + } + put_tty_driver(sdio_tty_drv->tty_drv); + sdio_tty_drv->sdio_tty_state = TTY_INITIAL; + sdio_tty_drv->tty_drv = NULL; + } + + pr_info(SDIO_TTY_MODULE_NAME ": %s: Freeing sdio_tty structure", + __func__); + kfree(sdio_tty_drv); + + return 0; +} +EXPORT_SYMBOL(sdio_tty_uninit_tty); + + +void sdio_tty_enable_debug_msg(void *sdio_tty_handle, int enable) +{ + struct sdio_tty *sdio_tty_drv = sdio_tty_handle; + + if (!sdio_tty_drv) { + pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", + __func__); + return; + } + pr_info(SDIO_TTY_MODULE_NAME ": %s: setting debug_msg_on to %d", + __func__, enable); + sdio_tty_drv->debug_msg_on = enable; +} +EXPORT_SYMBOL(sdio_tty_enable_debug_msg); + + +static int __init sdio_tty_init(void) +{ + return 0; +}; + +/* + * Module Exit. + * + * Unregister SDIO driver. + * + */ +static void __exit sdio_tty_exit(void) +{ +} + +module_init(sdio_tty_init); +module_exit(sdio_tty_exit); + +MODULE_DESCRIPTION("SDIO TTY"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Maya Erez "); diff --git a/arch/arm/mach-msm/sdio_tty_ciq.c b/arch/arm/mach-msm/sdio_tty_ciq.c new file mode 100644 index 00000000000..8f3d7762a8a --- /dev/null +++ b/arch/arm/mach-msm/sdio_tty_ciq.c @@ -0,0 +1,134 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#define SDIO_TTY_DEV "sdio_tty_ciq_0" +#define SDIO_CIQ "sdio_ciq" +#define SDIO_TTY_DEV_TEST "sdio_tty_ciq_test_0" +#define TTY_CIQ_MODULE_NAME "sdio_tty_ciq" + +struct sdio_tty_ciq { + void *sdio_tty_handle; +}; + +static struct sdio_tty_ciq sdio_tty_ciq; + +/* + * Enable sdio_tty debug messages + * By default the sdio_tty debug messages are turned off + */ +static int debug_msg_on; +module_param(debug_msg_on, int, 0); + +static int sdio_tty_probe(struct platform_device *pdev) +{ + sdio_tty_ciq.sdio_tty_handle = sdio_tty_init_tty(SDIO_TTY_DEV, + "SDIO_CIQ"); + if (!sdio_tty_ciq.sdio_tty_handle) { + pr_err(TTY_CIQ_MODULE_NAME ": %s: NULL sdio_tty_handle", + __func__); + return -ENODEV; + } + + if (debug_msg_on) + sdio_tty_enable_debug_msg(sdio_tty_ciq.sdio_tty_handle, + debug_msg_on); + + return 0; +} + +static int sdio_tty_test_probe(struct platform_device *pdev) +{ + sdio_tty_ciq.sdio_tty_handle = sdio_tty_init_tty(SDIO_TTY_DEV_TEST, + "SDIO_CIQ"); + if (!sdio_tty_ciq.sdio_tty_handle) { + pr_err(TTY_CIQ_MODULE_NAME ": %s: NULL sdio_tty_handle", + __func__); + return -ENODEV; + } + + if (debug_msg_on) + sdio_tty_enable_debug_msg(sdio_tty_ciq.sdio_tty_handle, + debug_msg_on); + + return 0; +} + +static int sdio_tty_remove(struct platform_device *pdev) +{ + int ret = 0; + + pr_info(TTY_CIQ_MODULE_NAME ": %s", __func__); + ret = sdio_tty_uninit_tty(sdio_tty_ciq.sdio_tty_handle); + if (ret) { + pr_err(TTY_CIQ_MODULE_NAME ": %s: sdio_tty_uninit_tty failed", + __func__); + return ret; + } + + return 0; +} + +static struct platform_driver sdio_tty_pdrv = { + .probe = sdio_tty_probe, + .remove = sdio_tty_remove, + .driver = { + .name = "SDIO_CIQ", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver sdio_tty_test_pdrv = { + .probe = sdio_tty_test_probe, + .remove = sdio_tty_remove, + .driver = { + .name = "SDIO_CIQ_TEST", + .owner = THIS_MODULE, + }, +}; + +static int __init sdio_tty_ciq_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&sdio_tty_pdrv); + if (ret) { + pr_err(TTY_CIQ_MODULE_NAME ": %s: platform_driver_register " + "failed", __func__); + return ret; + } + return platform_driver_register(&sdio_tty_test_pdrv); +}; + +/* + * Module Exit. + * + * Unregister SDIO driver. + * + */ +static void __exit sdio_tty_ciq_exit(void) +{ + platform_driver_unregister(&sdio_tty_pdrv); + platform_driver_unregister(&sdio_tty_test_pdrv); +} + +module_init(sdio_tty_ciq_init); +module_exit(sdio_tty_ciq_exit); + +MODULE_DESCRIPTION("SDIO TTY CIQ"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Maya Erez "); diff --git a/arch/arm/mach-msm/sirc-fsm9xxx.c b/arch/arm/mach-msm/sirc-fsm9xxx.c new file mode 100644 index 00000000000..71fa60ae22b --- /dev/null +++ b/arch/arm/mach-msm/sirc-fsm9xxx.c @@ -0,0 +1,166 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include "sirc.h" + +static unsigned int sirc_int_enable[2]; + +static struct sirc_regs_t sirc_regs = { + .int_enable = SPSS_SIRC_INT_ENABLE, + .int_type = SPSS_SIRC_INT_TYPE, + .int_polarity = SPSS_SIRC_INT_POLARITY, + .int_clear = SPSS_SIRC_INT_CLEAR, +}; + +static inline void sirc_get_group_offset_mask(unsigned int irq, + unsigned int *group, unsigned int *offset, unsigned int *mask) +{ + *group = 0; + *offset = irq - FIRST_SIRC_IRQ; + if (*offset >= NR_SIRC_IRQS_GROUPA) { + *group = 1; + *offset -= NR_SIRC_IRQS_GROUPA; + } + *mask = 1 << *offset; +} + +static void sirc_irq_mask(struct irq_data *d) +{ + void *reg_enable; + unsigned int group, offset, mask; + unsigned int val; + + sirc_get_group_offset_mask(d->irq, &group, &offset, &mask); + + reg_enable = sirc_regs.int_enable + group * 4; + val = __raw_readl(reg_enable); + __raw_writel(val & ~mask, reg_enable); + sirc_int_enable[group] &= ~mask; + mb(); +} + +static void sirc_irq_unmask(struct irq_data *d) +{ + void *reg_enable; + void *reg_clear; + unsigned int group, offset, mask; + unsigned int val; + + sirc_get_group_offset_mask(d->irq, &group, &offset, &mask); + + if (irq_desc[d->irq].handle_irq == handle_level_irq) { + reg_clear = sirc_regs.int_clear + group * 4; + __raw_writel(mask, reg_clear); + } + + reg_enable = sirc_regs.int_enable + group * 4; + val = __raw_readl(reg_enable); + __raw_writel(val | mask, reg_enable); + sirc_int_enable[group] |= mask; + mb(); +} + +static void sirc_irq_ack(struct irq_data *d) +{ + void *reg_clear; + unsigned int group, offset, mask; + + sirc_get_group_offset_mask(d->irq, &group, &offset, &mask); + + reg_clear = sirc_regs.int_clear + group * 4; + __raw_writel(mask, reg_clear); +} + +static int sirc_irq_set_wake(struct irq_data *d, unsigned int on) +{ + return 0; +} + +static int sirc_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + void *reg_polarity, *reg_type; + unsigned int group, offset, mask; + unsigned int val; + + sirc_get_group_offset_mask(d->irq, &group, &offset, &mask); + + reg_polarity = sirc_regs.int_polarity + group * 4; + val = __raw_readl(reg_polarity); + + if (flow_type & (IRQF_TRIGGER_LOW | IRQF_TRIGGER_FALLING)) + val &= ~mask; + else + val |= mask; + + __raw_writel(val, reg_polarity); + + reg_type = sirc_regs.int_type + group * 4; + val = __raw_readl(reg_type); + + if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { + val |= mask; + irq_desc[d->irq].handle_irq = handle_edge_irq; + } else { + val &= ~mask; + irq_desc[d->irq].handle_irq = handle_level_irq; + } + + __raw_writel(val, reg_type); + + return 0; +} + +/* Finds the pending interrupt on the passed cascade irq and redrives it */ +static void sirc_irq_handler(unsigned int irq, struct irq_desc *desc) +{ + unsigned int sirq; + + for (;;) { + sirq = __raw_readl(SPSS_SIRC_VEC_INDEX_RD); + if (sirq >= NR_SIRC_IRQS) + break; + + generic_handle_irq(sirq + FIRST_SIRC_IRQ); + } + + irq_desc_get_chip(desc)->irq_ack(irq_get_irq_data(irq)); +} + +static struct irq_chip sirc_irq_chip = { + .name = "sirc", + .irq_ack = sirc_irq_ack, + .irq_mask = sirc_irq_mask, + .irq_unmask = sirc_irq_unmask, + .irq_set_wake = sirc_irq_set_wake, + .irq_set_type = sirc_irq_set_type, +}; + +void __init msm_init_sirc(void) +{ + int i; + + sirc_int_enable[0] = 0; + sirc_int_enable[1] = 0; + + for (i = FIRST_SIRC_IRQ; i <= LAST_SIRC_IRQ; i++) { + irq_set_chip_and_handler(i, &sirc_irq_chip, handle_edge_irq); + set_irq_flags(i, IRQF_VALID); + } + + irq_set_chained_handler(INT_SIRC_0, sirc_irq_handler); + irq_set_irq_wake(INT_SIRC_0, 1); +} diff --git a/arch/arm/mach-msm/sirc.c b/arch/arm/mach-msm/sirc.c index 689e78c95f3..04124c5dcbf 100644 --- a/arch/arm/mach-msm/sirc.c +++ b/arch/arm/mach-msm/sirc.c @@ -1,25 +1,29 @@ -/* Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. +/* linux/arch/arm/mach-msm/irq.c * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. + * Copyright (c) 2009-2011 Code Aurora Forum. All rights reserved. + * Copyright (C) 2009 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * */ #include #include #include +#include #include +#include +#include +#include + +#include "sirc.h" static unsigned int int_enable; static unsigned int wake_enable; @@ -37,9 +41,13 @@ static struct sirc_cascade_regs sirc_reg_table[] = { { .int_status = SPSS_SIRC_IRQ_STATUS, .cascade_irq = INT_SIRC_0, + .cascade_fiq = INT_SIRC_1, } }; +static unsigned int save_type; +static unsigned int save_polarity; + /* Mask off the given interrupt. Keep the int_enable mask in sync with the enable reg, so it can be restored after power collapse. */ static void sirc_irq_mask(struct irq_data *d) @@ -49,6 +57,7 @@ static void sirc_irq_mask(struct irq_data *d) mask = 1 << (d->irq - FIRST_SIRC_IRQ); writel(mask, sirc_regs.int_enable_clear); int_enable &= ~mask; + mb(); return; } @@ -60,6 +69,7 @@ static void sirc_irq_unmask(struct irq_data *d) mask = 1 << (d->irq - FIRST_SIRC_IRQ); writel(mask, sirc_regs.int_enable_set); + mb(); int_enable |= mask; return; } @@ -70,6 +80,7 @@ static void sirc_irq_ack(struct irq_data *d) mask = 1 << (d->irq - FIRST_SIRC_IRQ); writel(mask, sirc_regs.int_clear); + mb(); return; } @@ -105,17 +116,35 @@ static int sirc_irq_set_type(struct irq_data *d, unsigned int flow_type) val = readl(sirc_regs.int_type); if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { val |= mask; - __irq_set_handler_locked(d->irq, handle_edge_irq); } else { val &= ~mask; - __irq_set_handler_locked(d->irq, handle_level_irq); } writel(val, sirc_regs.int_type); + mb(); return 0; } +#if defined(CONFIG_MSM_FIQ_SUPPORT) +void sirc_fiq_select(int irq, bool enable) +{ + uint32_t mask = 1 << (irq - FIRST_SIRC_IRQ); + uint32_t val; + unsigned long flags; + + local_irq_save(flags); + val = readl(SPSS_SIRC_INT_SELECT); + if (enable) + val |= mask; + else + val &= ~mask; + writel(val, SPSS_SIRC_INT_SELECT); + mb(); + local_irq_restore(flags); +} +#endif + /* Finds the pending interrupt on the passed cascade irq and redrives it */ static void sirc_irq_handler(unsigned int irq, struct irq_desc *desc) { @@ -127,6 +156,12 @@ static void sirc_irq_handler(unsigned int irq, struct irq_desc *desc) (sirc_reg_table[reg].cascade_irq != irq)) reg++; + if (reg == ARRAY_SIZE(sirc_reg_table)) { + printk(KERN_ERR "%s: incorrect irq %d called\n", + __func__, irq); + return; + } + status = readl(sirc_reg_table[reg].int_status); status &= SIRC_MASK; if (status == 0) @@ -138,16 +173,34 @@ static void sirc_irq_handler(unsigned int irq, struct irq_desc *desc) ; generic_handle_irq(sirq+FIRST_SIRC_IRQ); - desc->irq_data.chip->irq_ack(&desc->irq_data); + irq_desc_get_chip(desc)->irq_ack(irq_get_irq_data(irq)); +} + +void msm_sirc_enter_sleep(void) +{ + save_type = readl(sirc_regs.int_type); + save_polarity = readl(sirc_regs.int_polarity); + writel(wake_enable, sirc_regs.int_enable); + mb(); + return; +} + +void msm_sirc_exit_sleep(void) +{ + writel(save_type, sirc_regs.int_type); + writel(save_polarity, sirc_regs.int_polarity); + writel(int_enable, sirc_regs.int_enable); + mb(); + return; } static struct irq_chip sirc_irq_chip = { - .name = "sirc", - .irq_ack = sirc_irq_ack, - .irq_mask = sirc_irq_mask, - .irq_unmask = sirc_irq_unmask, - .irq_set_wake = sirc_irq_set_wake, - .irq_set_type = sirc_irq_set_type, + .name = "sirc", + .irq_ack = sirc_irq_ack, + .irq_mask = sirc_irq_mask, + .irq_unmask = sirc_irq_unmask, + .irq_set_wake = sirc_irq_set_wake, + .irq_set_type = sirc_irq_set_type, }; void __init msm_init_sirc(void) @@ -166,6 +219,10 @@ void __init msm_init_sirc(void) irq_set_chained_handler(sirc_reg_table[i].cascade_irq, sirc_irq_handler); irq_set_irq_wake(sirc_reg_table[i].cascade_irq, 1); +#if defined(CONFIG_MSM_FIQ_SUPPORT) + msm_fiq_select(sirc_reg_table[i].cascade_fiq); + msm_fiq_enable(sirc_reg_table[i].cascade_fiq); +#endif } return; } diff --git a/arch/arm/mach-msm/sirc.h b/arch/arm/mach-msm/sirc.h new file mode 100644 index 00000000000..f719969018d --- /dev/null +++ b/arch/arm/mach-msm/sirc.h @@ -0,0 +1,36 @@ +/* arch/arm/mach-msm/sirc.h + * + * Copyright (C) 2009 Google, Inc. + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_SIRC_H +#define _ARCH_ARM_MACH_MSM_SIRC_H + +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) +void sirc_fiq_select(int irq, bool enable); +#else +static inline void sirc_fiq_select(int irq, bool enable) {} +#endif + +#if defined(CONFIG_ARCH_QSD8X50) || defined(CONFIG_ARCH_FSM9XXX) +void __init msm_init_sirc(void); +void msm_sirc_enter_sleep(void); +void msm_sirc_exit_sleep(void); +#else +static inline void __init msm_init_sirc(void) {} +static inline void msm_sirc_enter_sleep(void) { } +static inline void msm_sirc_exit_sleep(void) { } +#endif + +#endif diff --git a/arch/arm/mach-msm/smd.c b/arch/arm/mach-msm/smd.c index 657be73297d..5d2e20c2b89 100644 --- a/arch/arm/mach-msm/smd.c +++ b/arch/arm/mach-msm/smd.c @@ -1,6 +1,7 @@ /* arch/arm/mach-msm/smd.c * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -14,8 +15,6 @@ * */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - #include #include #include @@ -26,109 +25,331 @@ #include #include #include -#include #include - +#include +#include +#include +#include +#include #include +#include #include +#include #include "smd_private.h" #include "proc_comm.h" +#include "modem_notifier.h" -#if defined(CONFIG_ARCH_QSD8X50) +#if defined(CONFIG_ARCH_QSD8X50) || defined(CONFIG_ARCH_MSM8X60) \ + || defined(CONFIG_ARCH_MSM8960) || defined(CONFIG_ARCH_FSM9XXX) #define CONFIG_QDSP6 1 #endif -void (*msm_hw_reset_hook)(void); +#if defined(CONFIG_ARCH_MSM8X60) || defined(CONFIG_ARCH_MSM8960) +#define CONFIG_DSPS 1 +#endif + +#if defined(CONFIG_ARCH_MSM8960) +#define CONFIG_WCNSS 1 +#endif #define MODULE_NAME "msm_smd" +#define SMEM_VERSION 0x000B +#define SMD_VERSION 0x00020000 + +uint32_t SMSM_NUM_ENTRIES = 8; +uint32_t SMSM_NUM_HOSTS = 3; enum { MSM_SMD_DEBUG = 1U << 0, - MSM_SMSM_DEBUG = 1U << 0, + MSM_SMSM_DEBUG = 1U << 1, + MSM_SMD_INFO = 1U << 2, + MSM_SMSM_INFO = 1U << 3, }; -static int msm_smd_debug_mask; +struct smsm_shared_info { + uint32_t *state; + uint32_t *intr_mask; + uint32_t *intr_mux; +}; -struct shared_info { - int ready; - unsigned state; +static struct smsm_shared_info smsm_info; + +struct smsm_size_info_type { + uint32_t num_hosts; + uint32_t num_entries; + uint32_t reserved0; + uint32_t reserved1; +}; + +struct smsm_state_cb_info { + struct list_head cb_list; + uint32_t mask; + void *data; + void (*notify)(void *data, uint32_t old_state, uint32_t new_state); +}; + +struct smsm_state_info { + struct list_head callbacks; + uint32_t last_value; }; -static unsigned dummy_state[SMSM_STATE_COUNT]; +#define SMSM_STATE_ADDR(entry) (smsm_info.state + entry) +#define SMSM_INTR_MASK_ADDR(entry, host) (smsm_info.intr_mask + \ + entry * SMSM_NUM_HOSTS + host) +#define SMSM_INTR_MUX_ADDR(entry) (smsm_info.intr_mux + entry) -static struct shared_info smd_info = { - .state = (unsigned) &dummy_state, +/* Internal definitions which are not exported in some targets */ +enum { + SMSM_APPS_DEM_I = 3, }; +static int msm_smd_debug_mask; module_param_named(debug_mask, msm_smd_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); +#if defined(CONFIG_MSM_SMD_DEBUG) +#define SMD_DBG(x...) do { \ + if (msm_smd_debug_mask & MSM_SMD_DEBUG) \ + printk(KERN_DEBUG x); \ + } while (0) + +#define SMSM_DBG(x...) do { \ + if (msm_smd_debug_mask & MSM_SMSM_DEBUG) \ + printk(KERN_DEBUG x); \ + } while (0) + +#define SMD_INFO(x...) do { \ + if (msm_smd_debug_mask & MSM_SMD_INFO) \ + printk(KERN_INFO x); \ + } while (0) + +#define SMSM_INFO(x...) do { \ + if (msm_smd_debug_mask & MSM_SMSM_INFO) \ + printk(KERN_INFO x); \ + } while (0) +#else +#define SMD_DBG(x...) do { } while (0) +#define SMSM_DBG(x...) do { } while (0) +#define SMD_INFO(x...) do { } while (0) +#define SMSM_INFO(x...) do { } while (0) +#endif + static unsigned last_heap_free = 0xffffffff; -static inline void notify_other_smsm(void) +static inline void smd_write_intr(unsigned int val, + const void __iomem *addr); + +#if defined(CONFIG_ARCH_MSM7X30) +#define MSM_TRIG_A2M_SMD_INT \ + (smd_write_intr(1 << 0, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2Q6_SMD_INT \ + (smd_write_intr(1 << 8, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2M_SMSM_INT \ + (smd_write_intr(1 << 5, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2Q6_SMSM_INT \ + (smd_write_intr(1 << 8, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2DSPS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#elif defined(CONFIG_ARCH_MSM8X60) +#define MSM_TRIG_A2M_SMD_INT \ + (smd_write_intr(1 << 3, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2Q6_SMD_INT \ + (smd_write_intr(1 << 15, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2M_SMSM_INT \ + (smd_write_intr(1 << 4, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2Q6_SMSM_INT \ + (smd_write_intr(1 << 14, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2DSPS_SMD_INT \ + (smd_write_intr(1, MSM_SIC_NON_SECURE_BASE + 0x4080)) +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#elif defined(CONFIG_ARCH_MSM8960) +#define MSM_TRIG_A2M_SMD_INT \ + (smd_write_intr(1 << 3, MSM_APCS_GCC_BASE + 0x8)) +#define MSM_TRIG_A2Q6_SMD_INT \ + (smd_write_intr(1 << 15, MSM_APCS_GCC_BASE + 0x8)) +#define MSM_TRIG_A2M_SMSM_INT \ + (smd_write_intr(1 << 4, MSM_APCS_GCC_BASE + 0x8)) +#define MSM_TRIG_A2Q6_SMSM_INT \ + (smd_write_intr(1 << 14, MSM_APCS_GCC_BASE + 0x8)) +#define MSM_TRIG_A2DSPS_SMD_INT \ + (smd_write_intr(1, MSM_SIC_NON_SECURE_BASE + 0x4080)) +#define MSM_TRIG_A2WCNSS_SMD_INT \ + (smd_write_intr(1 << 25, MSM_APCS_GCC_BASE + 0x8)) +#define MSM_TRIG_A2WCNSS_SMSM_INT \ + (smd_write_intr(1 << 23, MSM_APCS_GCC_BASE + 0x8)) +#elif defined(CONFIG_ARCH_FSM9XXX) +#define MSM_TRIG_A2Q6_SMD_INT \ + (smd_write_intr(1 << 10, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2Q6_SMSM_INT \ + (smd_write_intr(1 << 10, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2M_SMD_INT \ + (smd_write_intr(1 << 0, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2M_SMSM_INT \ + (smd_write_intr(1 << 5, MSM_GCC_BASE + 0x8)) +#define MSM_TRIG_A2DSPS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#else +#define MSM_TRIG_A2M_SMD_INT \ + (smd_write_intr(1, MSM_CSR_BASE + 0x400 + (0) * 4)) +#define MSM_TRIG_A2Q6_SMD_INT \ + (smd_write_intr(1, MSM_CSR_BASE + 0x400 + (8) * 4)) +#define MSM_TRIG_A2M_SMSM_INT \ + (smd_write_intr(1, MSM_CSR_BASE + 0x400 + (5) * 4)) +#define MSM_TRIG_A2Q6_SMSM_INT \ + (smd_write_intr(1, MSM_CSR_BASE + 0x400 + (8) * 4)) +#define MSM_TRIG_A2DSPS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMD_INT +#define MSM_TRIG_A2WCNSS_SMSM_INT +#endif + +#define SMD_LOOPBACK_CID 100 + +static LIST_HEAD(smd_ch_list_loopback); +static irqreturn_t smsm_irq_handler(int irq, void *data); +static void smd_fake_irq_handler(unsigned long arg); + +static void notify_smsm_cb_clients_worker(struct work_struct *work); +static DECLARE_WORK(smsm_cb_work, notify_smsm_cb_clients_worker); +static DEFINE_SPINLOCK(smsm_lock); +static struct smsm_state_info *smsm_states; + +static inline void smd_write_intr(unsigned int val, + const void __iomem *addr) +{ + wmb(); + __raw_writel(val, addr); +} + +#ifdef CONFIG_WCNSS +static inline void wakeup_v1_riva(void) +{ + /* + * workaround hack for RIVA v1 hardware bug + * trigger GPIO 40 to wake up RIVA from power collaspe + * not to be sent to customers + */ + __raw_writel(0x0, MSM_TLMM_BASE + 0x1284); + __raw_writel(0x2, MSM_TLMM_BASE + 0x1284); + /* end workaround */ +} +#else +static inline void wakeup_v1_riva(void) {} +#endif + +static void notify_other_smsm(uint32_t smsm_entry, uint32_t notify_mask) { - msm_a2m_int(5); -#ifdef CONFIG_QDSP6 - msm_a2m_int(8); + /* older protocol don't use smsm_intr_mask, + but still communicates with modem */ + if (!smsm_info.intr_mask || + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_MODEM)) + & notify_mask)) + MSM_TRIG_A2M_SMSM_INT; + + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_Q6)) + & notify_mask)) { +#if !defined(CONFIG_ARCH_MSM8X60) && !defined(MCONFIG_ARCH_MSM8960) + uint32_t mux_val; + + if (smsm_info.intr_mux) { + mux_val = __raw_readl( + SMSM_INTR_MUX_ADDR(SMEM_APPS_Q6_SMSM)); + mux_val++; + __raw_writel(mux_val, + SMSM_INTR_MUX_ADDR(SMEM_APPS_Q6_SMSM)); + } #endif + MSM_TRIG_A2Q6_SMSM_INT; + } + + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_WCNSS)) + & notify_mask)) { + wakeup_v1_riva(); + MSM_TRIG_A2WCNSS_SMSM_INT; + } + + schedule_work(&smsm_cb_work); } static inline void notify_modem_smd(void) { - msm_a2m_int(0); + MSM_TRIG_A2M_SMD_INT; } static inline void notify_dsp_smd(void) { - msm_a2m_int(8); + MSM_TRIG_A2Q6_SMD_INT; +} + +static inline void notify_dsps_smd(void) +{ + MSM_TRIG_A2DSPS_SMD_INT; +} + +static inline void notify_wcnss_smd(void) +{ + wakeup_v1_riva(); + MSM_TRIG_A2WCNSS_SMD_INT; } -static void smd_diag(void) +void smd_diag(void) { char *x; + int size; x = smem_find(ID_DIAG_ERR_MSG, SZ_DIAG_ERR_MSG); if (x != 0) { x[SZ_DIAG_ERR_MSG - 1] = 0; - pr_debug("DIAG '%s'\n", x); + SMD_INFO("smem: DIAG '%s'\n", x); + } + + x = smem_get_entry(SMEM_ERR_CRASH_LOG, &size); + if (x != 0) { + x[size - 1] = 0; + pr_err("smem: CRASH LOG\n'%s'\n", x); } } -/* call when SMSM_RESET flag is set in the A9's smsm_state */ + static void handle_modem_crash(void) { - pr_err("ARM9 has CRASHED\n"); + pr_err("MODEM/AMSS has CRASHED\n"); smd_diag(); - /* hard reboot if possible */ - if (msm_hw_reset_hook) - msm_hw_reset_hook(); + /* hard reboot if possible FIXME + if (msm_reset_hook) + msm_reset_hook(); + */ /* in this case the modem or watchdog should reboot us */ for (;;) ; } -uint32_t raw_smsm_get_state(enum smsm_state_item item) +int smsm_check_for_modem_crash(void) { - return readl(smd_info.state + item * 4); -} + /* if the modem's not ready yet, we have to hope for the best */ + if (!smsm_info.state) + return 0; -static int check_for_modem_crash(void) -{ - if (raw_smsm_get_state(SMSM_STATE_MODEM) & SMSM_RESET) { + if (__raw_readl(SMSM_STATE_ADDR(SMSM_MODEM_STATE)) & SMSM_RESET) { handle_modem_crash(); return -1; } return 0; } +EXPORT_SYMBOL(smsm_check_for_modem_crash); /* the spinlock is used to synchronize between the * irq handler and code that mutates the channel * list or fiddles with channel state */ -DEFINE_SPINLOCK(smd_lock); +static DEFINE_SPINLOCK(smd_lock); DEFINE_SPINLOCK(smem_lock); /* the mutex is used during open() and close() @@ -139,13 +360,291 @@ static DEFINE_MUTEX(smd_creation_mutex); static int smd_initialized; -LIST_HEAD(smd_ch_closed_list); -LIST_HEAD(smd_ch_list_modem); -LIST_HEAD(smd_ch_list_dsp); +struct smd_shared_v1 { + struct smd_half_channel ch0; + unsigned char data0[SMD_BUF_SIZE]; + struct smd_half_channel ch1; + unsigned char data1[SMD_BUF_SIZE]; +}; + +struct smd_shared_v2 { + struct smd_half_channel ch0; + struct smd_half_channel ch1; +}; + +struct smd_channel { + volatile struct smd_half_channel *send; + volatile struct smd_half_channel *recv; + unsigned char *send_data; + unsigned char *recv_data; + unsigned fifo_size; + unsigned fifo_mask; + struct list_head ch_list; + + unsigned current_packet; + unsigned n; + void *priv; + void (*notify)(void *priv, unsigned flags); + + int (*read)(smd_channel_t *ch, void *data, int len, int user_buf); + int (*write)(smd_channel_t *ch, const void *data, int len, + int user_buf); + int (*read_avail)(smd_channel_t *ch); + int (*write_avail)(smd_channel_t *ch); + int (*read_from_cb)(smd_channel_t *ch, void *data, int len, + int user_buf); + + void (*update_state)(smd_channel_t *ch); + unsigned last_state; + void (*notify_other_cpu)(void); + + char name[20]; + struct platform_device pdev; + unsigned type; + + int pending_pkt_sz; + + char is_pkt_ch; +}; + +struct edge_to_pid { + uint32_t local_pid; + uint32_t remote_pid; +}; + +/** + * Maps edge type to local and remote processor ID's. + */ +static struct edge_to_pid edge_to_pids[] = { + [SMD_APPS_MODEM] = {SMSM_APPS, SMSM_MODEM}, + [SMD_APPS_QDSP] = {SMSM_APPS, SMSM_Q6}, + [SMD_MODEM_QDSP] = {SMSM_MODEM, SMSM_Q6}, + [SMD_APPS_DSPS] = {SMSM_APPS, SMSM_DSPS}, + [SMD_MODEM_DSPS] = {SMSM_MODEM, SMSM_DSPS}, + [SMD_QDSP_DSPS] = {SMSM_Q6, SMSM_DSPS}, + [SMD_APPS_WCNSS] = {SMSM_APPS, SMSM_WCNSS}, + [SMD_MODEM_WCNSS] = {SMSM_MODEM, SMSM_WCNSS}, + [SMD_QDSP_WCNSS] = {SMSM_Q6, SMSM_WCNSS}, + [SMD_DSPS_WCNSS] = {SMSM_DSPS, SMSM_WCNSS}, +}; + +struct restart_notifier_block { + unsigned processor; + char *name; + struct notifier_block nb; +}; + +static struct platform_device loopback_tty_pdev = {.name = "LOOPBACK_TTY"}; + +static LIST_HEAD(smd_ch_closed_list); +static LIST_HEAD(smd_ch_closing_list); +static LIST_HEAD(smd_ch_to_close_list); +static LIST_HEAD(smd_ch_list_modem); +static LIST_HEAD(smd_ch_list_dsp); +static LIST_HEAD(smd_ch_list_dsps); +static LIST_HEAD(smd_ch_list_wcnss); static unsigned char smd_ch_allocated[64]; static struct work_struct probe_work; +static void finalize_channel_close_fn(struct work_struct *work); +static DECLARE_WORK(finalize_channel_close_work, finalize_channel_close_fn); +static struct workqueue_struct *channel_close_wq; + +static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm); + +/* on smp systems, the probe might get called from multiple cores, + hence use a lock */ +static DEFINE_MUTEX(smd_probe_lock); + +static void smd_channel_probe_worker(struct work_struct *work) +{ + struct smd_alloc_elm *shared; + unsigned n; + uint32_t type; + + shared = smem_find(ID_CH_ALLOC_TBL, sizeof(*shared) * 64); + + if (!shared) { + pr_err("%s: allocation table not initialized\n", __func__); + return; + } + + mutex_lock(&smd_probe_lock); + for (n = 0; n < 64; n++) { + if (smd_ch_allocated[n]) + continue; + + /* channel should be allocated only if APPS + processor is involved */ + type = SMD_CHANNEL_TYPE(shared[n].type); + if ((type != SMD_APPS_MODEM) && (type != SMD_APPS_QDSP) && + (type != SMD_APPS_DSPS) && (type != SMD_APPS_WCNSS)) + continue; + if (!shared[n].ref_count) + continue; + if (!shared[n].name[0]) + continue; + + if (!smd_alloc_channel(&shared[n])) + smd_ch_allocated[n] = 1; + else + SMD_INFO("Probe skipping ch %d, not allocated\n", n); + } + mutex_unlock(&smd_probe_lock); +} + +/** + * Lookup processor ID and determine if it belongs to the proved edge + * type. + * + * @shared2: Pointer to v2 shared channel structure + * @type: Edge type + * @pid: Processor ID of processor on edge + * @local_ch: Channel that belongs to processor @pid + * @remote_ch: Other side of edge contained @pid + * + * Returns 0 for not on edge, 1 for found on edge + */ +static int pid_is_on_edge(struct smd_shared_v2 *shared2, + uint32_t type, uint32_t pid, + struct smd_half_channel **local_ch, + struct smd_half_channel **remote_ch + ) +{ + int ret = 0; + struct edge_to_pid *edge; + + *local_ch = 0; + *remote_ch = 0; + + if (!shared2 || (type >= ARRAY_SIZE(edge_to_pids))) + return 0; + + edge = &edge_to_pids[type]; + if (edge->local_pid != edge->remote_pid) { + if (pid == edge->local_pid) { + *local_ch = &shared2->ch0; + *remote_ch = &shared2->ch1; + ret = 1; + } else if (pid == edge->remote_pid) { + *local_ch = &shared2->ch1; + *remote_ch = &shared2->ch0; + ret = 1; + } + } + + return ret; +} + + +static void smd_channel_reset_state(struct smd_alloc_elm *shared, + unsigned new_state, unsigned pid) +{ + unsigned n; + struct smd_shared_v2 *shared2; + uint32_t type; + struct smd_half_channel *local_ch; + struct smd_half_channel *remote_ch; + + for (n = 0; n < SMD_CHANNELS; n++) { + if (!shared[n].ref_count) + continue; + if (!shared[n].name[0]) + continue; + + type = SMD_CHANNEL_TYPE(shared[n].type); + shared2 = smem_alloc(SMEM_SMD_BASE_ID + n, sizeof(*shared2)); + if (!shared2) + continue; + + if (pid_is_on_edge(shared2, type, pid, &local_ch, &remote_ch)) { + + /* force remote state for processor being restarted */ + if (local_ch->state != SMD_SS_CLOSED) { + local_ch->state = new_state; + local_ch->fDSR = 0; + local_ch->fCTS = 0; + local_ch->fCD = 0; + local_ch->fSTATE = 1; + } + } + } +} + + +void smd_channel_reset(uint32_t restart_pid) +{ + struct smd_alloc_elm *shared; + unsigned n; + uint32_t *smem_lock; + unsigned long flags; + + SMD_DBG("%s: starting reset\n", __func__); + shared = smem_find(ID_CH_ALLOC_TBL, sizeof(*shared) * 64); + if (!shared) { + pr_err("%s: allocation table not initialized\n", __func__); + return; + } + + smem_lock = smem_alloc(SMEM_SPINLOCK_ARRAY, 8 * sizeof(uint32_t)); + if (smem_lock) { + SMD_DBG("%s: releasing locks\n", __func__); + for (n = 0; n < 8; n++) { + uint32_t pid = readl_relaxed(smem_lock); + if (pid == (restart_pid + 1)) + writel_relaxed(0, smem_lock); + smem_lock++; + } + } + + /* reset SMSM entry */ + if (smsm_info.state) { + writel_relaxed(0, SMSM_STATE_ADDR(restart_pid)); + + /* clear apps SMSM to restart SMSM init handshake */ + if (restart_pid == SMSM_MODEM) + writel_relaxed(0, SMSM_STATE_ADDR(SMSM_APPS)); + + /* notify SMSM processors */ + smsm_irq_handler(0, 0); + MSM_TRIG_A2M_SMSM_INT; + MSM_TRIG_A2Q6_SMSM_INT; + } + + /* change all remote states to CLOSING */ + mutex_lock(&smd_probe_lock); + spin_lock_irqsave(&smd_lock, flags); + smd_channel_reset_state(shared, SMD_SS_CLOSING, restart_pid); + spin_unlock_irqrestore(&smd_lock, flags); + mutex_unlock(&smd_probe_lock); + + /* notify SMD processors */ + mb(); + smd_fake_irq_handler(0); + notify_modem_smd(); + notify_dsp_smd(); + notify_dsps_smd(); + notify_wcnss_smd(); + + /* change all remote states to CLOSED */ + mutex_lock(&smd_probe_lock); + spin_lock_irqsave(&smd_lock, flags); + smd_channel_reset_state(shared, SMD_SS_CLOSED, restart_pid); + spin_unlock_irqrestore(&smd_lock, flags); + mutex_unlock(&smd_probe_lock); + + /* notify SMD processors */ + mb(); + smd_fake_irq_handler(0); + notify_modem_smd(); + notify_dsp_smd(); + notify_dsps_smd(); + notify_wcnss_smd(); + + SMD_DBG("%s: finished reset\n", __func__); +} + /* how many bytes are available for reading */ static int smd_stream_read_avail(struct smd_channel *ch) { @@ -179,8 +678,9 @@ static int smd_packet_write_avail(struct smd_channel *ch) static int ch_is_open(struct smd_channel *ch) { - return (ch->recv->state == SMD_SS_OPENED) && - (ch->send->state == SMD_SS_OPENED); + return (ch->recv->state == SMD_SS_OPENED || + ch->recv->state == SMD_SS_FLUSHING) + && (ch->send->state == SMD_SS_OPENED); } /* provide a pointer and length to readable data in the fifo */ @@ -196,11 +696,17 @@ static unsigned ch_read_buffer(struct smd_channel *ch, void **ptr) return ch->fifo_size - tail; } +static int read_intr_blocked(struct smd_channel *ch) +{ + return ch->recv->fBLOCKREADINTR; +} + /* advance the fifo read pointer after data from ch_read_buffer is consumed */ static void ch_read_done(struct smd_channel *ch, unsigned count) { BUG_ON(count > smd_stream_read_avail(ch)); ch->recv->tail = (ch->recv->tail + count) & ch->fifo_mask; + wmb(); ch->send->fTAIL = 1; } @@ -208,12 +714,13 @@ static void ch_read_done(struct smd_channel *ch, unsigned count) * by smd_*_read() and update_packet_state() * will read-and-discard if the _data pointer is null */ -static int ch_read(struct smd_channel *ch, void *_data, int len) +static int ch_read(struct smd_channel *ch, void *_data, int len, int user_buf) { void *ptr; unsigned n; unsigned char *data = _data; int orig_len = len; + int r = 0; while (len > 0) { n = ch_read_buffer(ch, &ptr); @@ -222,8 +729,19 @@ static int ch_read(struct smd_channel *ch, void *_data, int len) if (n > len) n = len; - if (_data) - memcpy(data, ptr, n); + if (_data) { + if (user_buf) { + r = copy_to_user(data, ptr, n); + if (r > 0) { + pr_err("%s: " + "copy_to_user could not copy " + "%i bytes.\n", + __func__, + r); + } + } else + memcpy(data, ptr, n); + } data += n; len -= n; @@ -244,17 +762,18 @@ static void update_packet_state(struct smd_channel *ch) int r; /* can't do anything if we're in the middle of a packet */ - if (ch->current_packet != 0) - return; + while (ch->current_packet == 0) { + /* discard 0 length packets if any */ - /* don't bother unless we can get the full header */ - if (smd_stream_read_avail(ch) < SMD_HEADER_SIZE) - return; + /* don't bother unless we can get the full header */ + if (smd_stream_read_avail(ch) < SMD_HEADER_SIZE) + return; - r = ch_read(ch, hdr, SMD_HEADER_SIZE); - BUG_ON(r != SMD_HEADER_SIZE); + r = ch_read(ch, hdr, SMD_HEADER_SIZE, 0); + BUG_ON(r != SMD_HEADER_SIZE); - ch->current_packet = hdr[0]; + ch->current_packet = hdr[0]; + } } /* provide a pointer and length to next free space in the fifo */ @@ -281,6 +800,7 @@ static void ch_write_done(struct smd_channel *ch, unsigned count) { BUG_ON(count > smd_stream_write_avail(ch)); ch->send->head = (ch->send->head + count) & ch->fifo_mask; + wmb(); ch->send->fHEAD = 1; } @@ -314,62 +834,102 @@ static void smd_state_change(struct smd_channel *ch, { ch->last_state = next; - pr_debug("ch %d %d -> %d\n", ch->n, last, next); + SMD_INFO("SMD: ch %d %d -> %d\n", ch->n, last, next); switch (next) { case SMD_SS_OPENING: - ch->recv->tail = 0; + if (ch->send->state == SMD_SS_CLOSING || + ch->send->state == SMD_SS_CLOSED) { + ch->recv->tail = 0; + ch->send->head = 0; + ch->send->fBLOCKREADINTR = 0; + ch_set_state(ch, SMD_SS_OPENING); + } + break; case SMD_SS_OPENED: - if (ch->send->state != SMD_SS_OPENED) + if (ch->send->state == SMD_SS_OPENING) { ch_set_state(ch, SMD_SS_OPENED); - ch->notify(ch->priv, SMD_EVENT_OPEN); + ch->notify(ch->priv, SMD_EVENT_OPEN); + } break; case SMD_SS_FLUSHING: case SMD_SS_RESET: /* we should force them to close? */ - default: - ch->notify(ch->priv, SMD_EVENT_CLOSE); + break; + case SMD_SS_CLOSED: + if (ch->send->state == SMD_SS_OPENED) { + ch_set_state(ch, SMD_SS_CLOSING); + ch->current_packet = 0; + ch->notify(ch->priv, SMD_EVENT_CLOSE); + } + break; + case SMD_SS_CLOSING: + if (ch->send->state == SMD_SS_CLOSED) { + list_move(&ch->ch_list, + &smd_ch_to_close_list); + queue_work(channel_close_wq, + &finalize_channel_close_work); + } + break; + } +} + +static void handle_smd_irq_closing_list(void) +{ + unsigned long flags; + struct smd_channel *ch; + struct smd_channel *index; + unsigned tmp; + + spin_lock_irqsave(&smd_lock, flags); + list_for_each_entry_safe(ch, index, &smd_ch_closing_list, ch_list) { + if (ch->recv->fSTATE) + ch->recv->fSTATE = 0; + tmp = ch->recv->state; + if (tmp != ch->last_state) + smd_state_change(ch, ch->last_state, tmp); } + spin_unlock_irqrestore(&smd_lock, flags); } static void handle_smd_irq(struct list_head *list, void (*notify)(void)) { unsigned long flags; struct smd_channel *ch; - int do_notify = 0; unsigned ch_flags; unsigned tmp; + unsigned char state_change; spin_lock_irqsave(&smd_lock, flags); list_for_each_entry(ch, list, ch_list) { + state_change = 0; ch_flags = 0; if (ch_is_open(ch)) { if (ch->recv->fHEAD) { ch->recv->fHEAD = 0; ch_flags |= 1; - do_notify |= 1; } if (ch->recv->fTAIL) { ch->recv->fTAIL = 0; ch_flags |= 2; - do_notify |= 1; } if (ch->recv->fSTATE) { ch->recv->fSTATE = 0; ch_flags |= 4; - do_notify |= 1; } } tmp = ch->recv->state; - if (tmp != ch->last_state) + if (tmp != ch->last_state) { smd_state_change(ch, ch->last_state, tmp); + state_change = 1; + } if (ch_flags) { ch->update_state(ch); ch->notify(ch->priv, SMD_EVENT_DATA); } + if (ch_flags & 0x4 && !state_change) + ch->notify(ch->priv, SMD_EVENT_STATUS); } - if (do_notify) - notify(); spin_unlock_irqrestore(&smd_lock, flags); do_smd_probe(); } @@ -377,6 +937,7 @@ static void handle_smd_irq(struct list_head *list, void (*notify)(void)) static irqreturn_t smd_modem_irq_handler(int irq, void *data) { handle_smd_irq(&smd_ch_list_modem, notify_modem_smd); + handle_smd_irq_closing_list(); return IRQ_HANDLED; } @@ -384,6 +945,25 @@ static irqreturn_t smd_modem_irq_handler(int irq, void *data) static irqreturn_t smd_dsp_irq_handler(int irq, void *data) { handle_smd_irq(&smd_ch_list_dsp, notify_dsp_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} +#endif + +#if defined(CONFIG_DSPS) +static irqreturn_t smd_dsps_irq_handler(int irq, void *data) +{ + handle_smd_irq(&smd_ch_list_dsps, notify_dsps_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} +#endif + +#if defined(CONFIG_WCNSS) +static irqreturn_t smd_wcnss_irq_handler(int irq, void *data) +{ + handle_smd_irq(&smd_ch_list_wcnss, notify_wcnss_smd); + handle_smd_irq_closing_list(); return IRQ_HANDLED; } #endif @@ -392,6 +972,9 @@ static void smd_fake_irq_handler(unsigned long arg) { handle_smd_irq(&smd_ch_list_modem, notify_modem_smd); handle_smd_irq(&smd_ch_list_dsp, notify_dsp_smd); + handle_smd_irq(&smd_ch_list_dsps, notify_dsps_smd); + handle_smd_irq(&smd_ch_list_wcnss, notify_wcnss_smd); + handle_smd_irq_closing_list(); } static DECLARE_TASKLET(smd_fake_irq_tasklet, smd_fake_irq_handler, 0); @@ -426,68 +1009,80 @@ void smd_sleep_exit(void) break; } } + list_for_each_entry(ch, &smd_ch_list_dsps, ch_list) { + if (smd_need_int(ch)) { + need_int = 1; + break; + } + } + list_for_each_entry(ch, &smd_ch_list_wcnss, ch_list) { + if (smd_need_int(ch)) { + need_int = 1; + break; + } + } spin_unlock_irqrestore(&smd_lock, flags); do_smd_probe(); if (need_int) { - if (msm_smd_debug_mask & MSM_SMD_DEBUG) - pr_info("smd_sleep_exit need interrupt\n"); + SMD_DBG("smd_sleep_exit need interrupt\n"); tasklet_schedule(&smd_fake_irq_tasklet); } } +EXPORT_SYMBOL(smd_sleep_exit); - -void smd_kick(smd_channel_t *ch) +static int smd_is_packet(struct smd_alloc_elm *alloc_elm) { - unsigned long flags; - unsigned tmp; + if (SMD_XFER_TYPE(alloc_elm->type) == 1) + return 0; + else if (SMD_XFER_TYPE(alloc_elm->type) == 2) + return 1; - spin_lock_irqsave(&smd_lock, flags); - ch->update_state(ch); - tmp = ch->recv->state; - if (tmp != ch->last_state) { - ch->last_state = tmp; - if (tmp == SMD_SS_OPENED) - ch->notify(ch->priv, SMD_EVENT_OPEN); - else - ch->notify(ch->priv, SMD_EVENT_CLOSE); - } - ch->notify(ch->priv, SMD_EVENT_DATA); - ch->notify_other_cpu(); - spin_unlock_irqrestore(&smd_lock, flags); -} + /* for cases where xfer type is 0 */ + if (!strncmp(alloc_elm->name, "DAL", 3)) + return 0; -static int smd_is_packet(int chn, unsigned type) -{ - type &= SMD_KIND_MASK; - if (type == SMD_KIND_PACKET) - return 1; - if (type == SMD_KIND_STREAM) + /* for cases where xfer type is 0 */ + if (!strncmp(alloc_elm->name, "RPCCALL_QDSP", 12)) return 0; - /* older AMSS reports SMD_KIND_UNKNOWN always */ - if ((chn > 4) || (chn == 1)) + if (alloc_elm->cid > 4 || alloc_elm->cid == 1) return 1; else return 0; } -static int smd_stream_write(smd_channel_t *ch, const void *_data, int len) +static int smd_stream_write(smd_channel_t *ch, const void *_data, int len, + int user_buf) { void *ptr; const unsigned char *buf = _data; unsigned xfer; int orig_len = len; + int r = 0; + SMD_DBG("smd_stream_write() %d -> ch%d\n", len, ch->n); if (len < 0) return -EINVAL; + else if (len == 0) + return 0; while ((xfer = ch_write_buffer(ch, &ptr)) != 0) { if (!ch_is_open(ch)) break; if (xfer > len) xfer = len; - memcpy(ptr, buf, xfer); + if (user_buf) { + r = copy_from_user(ptr, buf, xfer); + if (r > 0) { + pr_err("%s: " + "copy_from_user could not copy %i " + "bytes.\n", + __func__, + r); + } + } else + memcpy(ptr, buf, xfer); ch_write_done(ch, xfer); len -= xfer; buf += xfer; @@ -495,17 +1090,23 @@ static int smd_stream_write(smd_channel_t *ch, const void *_data, int len) break; } - ch->notify_other_cpu(); + if (orig_len - len) + ch->notify_other_cpu(); return orig_len - len; } -static int smd_packet_write(smd_channel_t *ch, const void *_data, int len) +static int smd_packet_write(smd_channel_t *ch, const void *_data, int len, + int user_buf) { + int ret; unsigned hdr[5]; + SMD_DBG("smd_packet_write() %d -> ch%d\n", len, ch->n); if (len < 0) return -EINVAL; + else if (len == 0) + return 0; if (smd_stream_write_avail(ch) < (len + SMD_HEADER_SIZE)) return -ENOMEM; @@ -513,27 +1114,41 @@ static int smd_packet_write(smd_channel_t *ch, const void *_data, int len) hdr[0] = len; hdr[1] = hdr[2] = hdr[3] = hdr[4] = 0; - smd_stream_write(ch, hdr, sizeof(hdr)); - smd_stream_write(ch, _data, len); + + ret = smd_stream_write(ch, hdr, sizeof(hdr), 0); + if (ret < 0 || ret != sizeof(hdr)) { + SMD_DBG("%s failed to write pkt header: " + "%d returned\n", __func__, ret); + return -1; + } + + + ret = smd_stream_write(ch, _data, len, user_buf); + if (ret < 0 || ret != len) { + SMD_DBG("%s failed to write pkt data: " + "%d returned\n", __func__, ret); + return ret; + } return len; } -static int smd_stream_read(smd_channel_t *ch, void *data, int len) +static int smd_stream_read(smd_channel_t *ch, void *data, int len, int user_buf) { int r; if (len < 0) return -EINVAL; - r = ch_read(ch, data, len); + r = ch_read(ch, data, len, user_buf); if (r > 0) - ch->notify_other_cpu(); + if (!read_intr_blocked(ch)) + ch->notify_other_cpu(); return r; } -static int smd_packet_read(smd_channel_t *ch, void *data, int len) +static int smd_packet_read(smd_channel_t *ch, void *data, int len, int user_buf) { unsigned long flags; int r; @@ -544,9 +1159,10 @@ static int smd_packet_read(smd_channel_t *ch, void *data, int len) if (len > ch->current_packet) len = ch->current_packet; - r = ch_read(ch, data, len); + r = ch_read(ch, data, len, user_buf); if (r > 0) - ch->notify_other_cpu(); + if (!read_intr_blocked(ch)) + ch->notify_other_cpu(); spin_lock_irqsave(&smd_lock, flags); ch->current_packet -= r; @@ -556,111 +1172,233 @@ static int smd_packet_read(smd_channel_t *ch, void *data, int len) return r; } -static int smd_alloc_channel(const char *name, uint32_t cid, uint32_t type) +static int smd_packet_read_from_cb(smd_channel_t *ch, void *data, int len, + int user_buf) { - struct smd_channel *ch; - - ch = kzalloc(sizeof(struct smd_channel), GFP_KERNEL); - if (ch == 0) { - pr_err("smd_alloc_channel() out of memory\n"); - return -1; - } - ch->n = cid; + int r; - if (_smd_alloc_channel(ch)) { - kfree(ch); - return -1; - } + if (len < 0) + return -EINVAL; - ch->fifo_mask = ch->fifo_size - 1; - ch->type = type; + if (len > ch->current_packet) + len = ch->current_packet; - if ((type & SMD_TYPE_MASK) == SMD_TYPE_APPS_MODEM) - ch->notify_other_cpu = notify_modem_smd; - else - ch->notify_other_cpu = notify_dsp_smd; + r = ch_read(ch, data, len, user_buf); + if (r > 0) + if (!read_intr_blocked(ch)) + ch->notify_other_cpu(); - if (smd_is_packet(cid, type)) { + ch->current_packet -= r; + update_packet_state(ch); + + return r; +} + +static int smd_alloc_v2(struct smd_channel *ch) +{ + struct smd_shared_v2 *shared2; + void *buffer; + unsigned buffer_sz; + + shared2 = smem_alloc(SMEM_SMD_BASE_ID + ch->n, sizeof(*shared2)); + if (!shared2) { + SMD_INFO("smem_alloc failed ch=%d\n", ch->n); + return -1; + } + buffer = smem_get_entry(SMEM_SMD_FIFO_BASE_ID + ch->n, &buffer_sz); + if (!buffer) { + SMD_INFO("smem_get_entry failed \n"); + return -1; + } + + /* buffer must be a power-of-two size */ + if (buffer_sz & (buffer_sz - 1)) + return -1; + + buffer_sz /= 2; + ch->send = &shared2->ch0; + ch->recv = &shared2->ch1; + ch->send_data = buffer; + ch->recv_data = buffer + buffer_sz; + ch->fifo_size = buffer_sz; + return 0; +} + +static int smd_alloc_v1(struct smd_channel *ch) +{ + struct smd_shared_v1 *shared1; + shared1 = smem_alloc(ID_SMD_CHANNELS + ch->n, sizeof(*shared1)); + if (!shared1) { + pr_err("smd_alloc_channel() cid %d does not exist\n", ch->n); + return -1; + } + ch->send = &shared1->ch0; + ch->recv = &shared1->ch1; + ch->send_data = shared1->data0; + ch->recv_data = shared1->data1; + ch->fifo_size = SMD_BUF_SIZE; + return 0; +} + +static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm) +{ + struct smd_channel *ch; + + ch = kzalloc(sizeof(struct smd_channel), GFP_KERNEL); + if (ch == 0) { + pr_err("smd_alloc_channel() out of memory\n"); + return -1; + } + ch->n = alloc_elm->cid; + + if (smd_alloc_v2(ch) && smd_alloc_v1(ch)) { + kfree(ch); + return -1; + } + + ch->fifo_mask = ch->fifo_size - 1; + ch->type = SMD_CHANNEL_TYPE(alloc_elm->type); + + if (ch->type == SMD_APPS_MODEM) + ch->notify_other_cpu = notify_modem_smd; + else if (ch->type == SMD_APPS_QDSP) + ch->notify_other_cpu = notify_dsp_smd; + else if (ch->type == SMD_APPS_DSPS) + ch->notify_other_cpu = notify_dsps_smd; + else + ch->notify_other_cpu = notify_wcnss_smd; + + if (smd_is_packet(alloc_elm)) { ch->read = smd_packet_read; ch->write = smd_packet_write; ch->read_avail = smd_packet_read_avail; ch->write_avail = smd_packet_write_avail; ch->update_state = update_packet_state; + ch->read_from_cb = smd_packet_read_from_cb; + ch->is_pkt_ch = 1; } else { ch->read = smd_stream_read; ch->write = smd_stream_write; ch->read_avail = smd_stream_read_avail; ch->write_avail = smd_stream_write_avail; ch->update_state = update_stream_state; + ch->read_from_cb = smd_stream_read; } - if ((type & 0xff) == 0) - memcpy(ch->name, "SMD_", 4); - else - memcpy(ch->name, "DSP_", 4); - memcpy(ch->name + 4, name, 20); - ch->name[23] = 0; + memcpy(ch->name, alloc_elm->name, SMD_MAX_CH_NAME_LEN); + ch->name[SMD_MAX_CH_NAME_LEN-1] = 0; + ch->pdev.name = ch->name; - ch->pdev.id = -1; + ch->pdev.id = ch->type; - pr_debug("smd_alloc_channel() cid=%02d size=%05d '%s'\n", - ch->n, ch->fifo_size, ch->name); + SMD_INFO("smd_alloc_channel() '%s' cid=%d\n", + ch->name, ch->n); mutex_lock(&smd_creation_mutex); list_add(&ch->ch_list, &smd_ch_closed_list); mutex_unlock(&smd_creation_mutex); platform_device_register(&ch->pdev); + if (!strncmp(ch->name, "LOOPBACK", 8) && ch->type == SMD_APPS_MODEM) { + /* create a platform driver to be used by smd_tty driver + * so that it can access the loopback port + */ + loopback_tty_pdev.id = ch->type; + platform_device_register(&loopback_tty_pdev); + } return 0; } -static void smd_channel_probe_worker(struct work_struct *work) +static inline void notify_loopback_smd(void) { - struct smd_alloc_elm *shared; - unsigned ctype; - unsigned type; - unsigned n; + unsigned long flags; + struct smd_channel *ch; - shared = smem_find(ID_CH_ALLOC_TBL, sizeof(*shared) * 64); - if (!shared) { - pr_err("cannot find allocation table\n"); - return; + spin_lock_irqsave(&smd_lock, flags); + list_for_each_entry(ch, &smd_ch_list_loopback, ch_list) { + ch->notify(ch->priv, SMD_EVENT_DATA); } - for (n = 0; n < 64; n++) { - if (smd_ch_allocated[n]) - continue; - if (!shared[n].ref_count) - continue; - if (!shared[n].name[0]) - continue; - ctype = shared[n].ctype; - type = ctype & SMD_TYPE_MASK; + spin_unlock_irqrestore(&smd_lock, flags); +} - /* DAL channels are stream but neither the modem, - * nor the DSP correctly indicate this. Fixup manually. - */ - if (!memcmp(shared[n].name, "DAL", 3)) - ctype = (ctype & (~SMD_KIND_MASK)) | SMD_KIND_STREAM; +static int smd_alloc_loopback_channel(void) +{ + static struct smd_half_channel smd_loopback_ctl; + static char smd_loopback_data[SMD_BUF_SIZE]; + struct smd_channel *ch; - type = shared[n].ctype & SMD_TYPE_MASK; - if ((type == SMD_TYPE_APPS_MODEM) || - (type == SMD_TYPE_APPS_DSP)) - if (!smd_alloc_channel(shared[n].name, shared[n].cid, ctype)) - smd_ch_allocated[n] = 1; + ch = kzalloc(sizeof(struct smd_channel), GFP_KERNEL); + if (ch == 0) { + pr_err("%s: out of memory\n", __func__); + return -1; } + ch->n = SMD_LOOPBACK_CID; + + ch->send = &smd_loopback_ctl; + ch->recv = &smd_loopback_ctl; + ch->send_data = smd_loopback_data; + ch->recv_data = smd_loopback_data; + ch->fifo_size = SMD_BUF_SIZE; + + ch->fifo_mask = ch->fifo_size - 1; + ch->type = SMD_LOOPBACK_TYPE; + ch->notify_other_cpu = notify_loopback_smd; + + ch->read = smd_stream_read; + ch->write = smd_stream_write; + ch->read_avail = smd_stream_read_avail; + ch->write_avail = smd_stream_write_avail; + ch->update_state = update_stream_state; + ch->read_from_cb = smd_stream_read; + + memset(ch->name, 0, 20); + memcpy(ch->name, "local_loopback", 14); + + ch->pdev.name = ch->name; + ch->pdev.id = ch->type; + + SMD_INFO("%s: '%s' cid=%d\n", __func__, ch->name, ch->n); + + mutex_lock(&smd_creation_mutex); + list_add(&ch->ch_list, &smd_ch_closed_list); + mutex_unlock(&smd_creation_mutex); + + platform_device_register(&ch->pdev); + return 0; } static void do_nothing_notify(void *priv, unsigned flags) { } -struct smd_channel *smd_get_channel(const char *name) +static void finalize_channel_close_fn(struct work_struct *work) +{ + unsigned long flags; + struct smd_channel *ch; + struct smd_channel *index; + + spin_lock_irqsave(&smd_lock, flags); + list_for_each_entry_safe(ch, index, &smd_ch_to_close_list, ch_list) { + list_del(&ch->ch_list); + spin_unlock_irqrestore(&smd_lock, flags); + mutex_lock(&smd_creation_mutex); + list_add(&ch->ch_list, &smd_ch_closed_list); + mutex_unlock(&smd_creation_mutex); + ch->notify(ch->priv, SMD_EVENT_REOPEN_READY); + ch->notify = do_nothing_notify; + spin_lock_irqsave(&smd_lock, flags); + } + spin_unlock_irqrestore(&smd_lock, flags); +} + +struct smd_channel *smd_get_channel(const char *name, uint32_t type) { struct smd_channel *ch; mutex_lock(&smd_creation_mutex); list_for_each_entry(ch, &smd_ch_closed_list, ch_list) { - if (!strcmp(name, ch->name)) { + if (!strcmp(name, ch->name) && + (type == ch->type)) { list_del(&ch->ch_list); mutex_unlock(&smd_creation_mutex); return ch; @@ -671,18 +1409,21 @@ struct smd_channel *smd_get_channel(const char *name) return NULL; } -int smd_open(const char *name, smd_channel_t **_ch, - void *priv, void (*notify)(void *, unsigned)) +int smd_named_open_on_edge(const char *name, uint32_t edge, + smd_channel_t **_ch, + void *priv, void (*notify)(void *, unsigned)) { struct smd_channel *ch; unsigned long flags; if (smd_initialized == 0) { - pr_info("smd_open() before smd_init()\n"); + SMD_INFO("smd_open() before smd_init()\n"); return -ENODEV; } - ch = smd_get_channel(name); + SMD_DBG("smd_open('%s', %p, %p)\n", name, priv, notify); + + ch = smd_get_channel(name, edge); if (!ch) return -ENODEV; @@ -694,34 +1435,49 @@ int smd_open(const char *name, smd_channel_t **_ch, ch->last_state = SMD_SS_CLOSED; ch->priv = priv; + if (edge == SMD_LOOPBACK_TYPE) { + ch->last_state = SMD_SS_OPENED; + ch->send->state = SMD_SS_OPENED; + ch->send->fDSR = 1; + ch->send->fCTS = 1; + ch->send->fCD = 1; + } + *_ch = ch; - spin_lock_irqsave(&smd_lock, flags); + SMD_DBG("smd_open: opening '%s'\n", ch->name); - if ((ch->type & SMD_TYPE_MASK) == SMD_TYPE_APPS_MODEM) + spin_lock_irqsave(&smd_lock, flags); + if (SMD_CHANNEL_TYPE(ch->type) == SMD_APPS_MODEM) list_add(&ch->ch_list, &smd_ch_list_modem); - else + else if (SMD_CHANNEL_TYPE(ch->type) == SMD_APPS_QDSP) list_add(&ch->ch_list, &smd_ch_list_dsp); + else if (SMD_CHANNEL_TYPE(ch->type) == SMD_APPS_DSPS) + list_add(&ch->ch_list, &smd_ch_list_dsps); + else if (SMD_CHANNEL_TYPE(ch->type) == SMD_APPS_WCNSS) + list_add(&ch->ch_list, &smd_ch_list_wcnss); + else + list_add(&ch->ch_list, &smd_ch_list_loopback); + + SMD_DBG("%s: opening ch %d\n", __func__, ch->n); + + if (edge != SMD_LOOPBACK_TYPE) + smd_state_change(ch, ch->last_state, SMD_SS_OPENING); - /* If the remote side is CLOSING, we need to get it to - * move to OPENING (which we'll do by moving from CLOSED to - * OPENING) and then get it to move from OPENING to - * OPENED (by doing the same state change ourselves). - * - * Otherwise, it should be OPENING and we can move directly - * to OPENED so that it will follow. - */ - if (ch->recv->state == SMD_SS_CLOSING) { - ch->send->head = 0; - ch_set_state(ch, SMD_SS_OPENING); - } else { - ch_set_state(ch, SMD_SS_OPENED); - } spin_unlock_irqrestore(&smd_lock, flags); - smd_kick(ch); return 0; } +EXPORT_SYMBOL(smd_named_open_on_edge); + + +int smd_open(const char *name, smd_channel_t **_ch, + void *priv, void (*notify)(void *, unsigned)) +{ + return smd_named_open_on_edge(name, SMD_APPS_MODEM, _ch, priv, + notify); +} +EXPORT_SYMBOL(smd_open); int smd_close(smd_channel_t *ch) { @@ -730,48 +1486,180 @@ int smd_close(smd_channel_t *ch) if (ch == 0) return -1; + SMD_INFO("smd_close(%s)\n", ch->name); + spin_lock_irqsave(&smd_lock, flags); - ch->notify = do_nothing_notify; list_del(&ch->ch_list); - ch_set_state(ch, SMD_SS_CLOSED); - spin_unlock_irqrestore(&smd_lock, flags); + if (ch->n == SMD_LOOPBACK_CID) { + ch->send->fDSR = 0; + ch->send->fCTS = 0; + ch->send->fCD = 0; + ch->send->state = SMD_SS_CLOSED; + } else + ch_set_state(ch, SMD_SS_CLOSED); - mutex_lock(&smd_creation_mutex); - list_add(&ch->ch_list, &smd_ch_closed_list); - mutex_unlock(&smd_creation_mutex); + if (ch->recv->state == SMD_SS_OPENED) { + list_add(&ch->ch_list, &smd_ch_closing_list); + spin_unlock_irqrestore(&smd_lock, flags); + } else { + spin_unlock_irqrestore(&smd_lock, flags); + ch->notify = do_nothing_notify; + mutex_lock(&smd_creation_mutex); + list_add(&ch->ch_list, &smd_ch_closed_list); + mutex_unlock(&smd_creation_mutex); + } + + return 0; +} +EXPORT_SYMBOL(smd_close); + +int smd_write_start(smd_channel_t *ch, int len) +{ + int ret; + unsigned hdr[5]; + + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + if (!ch->is_pkt_ch) { + pr_err("%s: non-packet channel specified\n", __func__); + return -EACCES; + } + if (len < 1) { + pr_err("%s: invalid length: %d\n", __func__, len); + return -EINVAL; + } + + if (ch->pending_pkt_sz) { + pr_err("%s: packet of size: %d in progress\n", __func__, + ch->pending_pkt_sz); + return -EBUSY; + } + ch->pending_pkt_sz = len; + + if (smd_stream_write_avail(ch) < (SMD_HEADER_SIZE)) { + ch->pending_pkt_sz = 0; + SMD_DBG("%s: no space to write packet header\n", __func__); + return -EAGAIN; + } + + hdr[0] = len; + hdr[1] = hdr[2] = hdr[3] = hdr[4] = 0; + + ret = smd_stream_write(ch, hdr, sizeof(hdr), 0); + if (ret < 0 || ret != sizeof(hdr)) { + ch->pending_pkt_sz = 0; + pr_err("%s: packet header failed to write\n", __func__); + return -EPERM; + } return 0; } +EXPORT_SYMBOL(smd_write_start); + +int smd_write_segment(smd_channel_t *ch, void *data, int len, int user_buf) +{ + int bytes_written; + + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + if (len < 1) { + pr_err("%s: invalid length: %d\n", __func__, len); + return -EINVAL; + } + + if (!ch->pending_pkt_sz) { + pr_err("%s: no transaction in progress\n", __func__); + return -ENOEXEC; + } + if (ch->pending_pkt_sz - len < 0) { + pr_err("%s: segment of size: %d will make packet go over " + "length\n", __func__, len); + return -EINVAL; + } + + bytes_written = smd_stream_write(ch, data, len, user_buf); + + ch->pending_pkt_sz -= bytes_written; + + return bytes_written; +} +EXPORT_SYMBOL(smd_write_segment); + +int smd_write_end(smd_channel_t *ch) +{ + + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + if (ch->pending_pkt_sz) { + pr_err("%s: current packet not completely written\n", __func__); + return -E2BIG; + } + + return 0; +} +EXPORT_SYMBOL(smd_write_end); int smd_read(smd_channel_t *ch, void *data, int len) { - return ch->read(ch, data, len); + return ch->read(ch, data, len, 0); } +EXPORT_SYMBOL(smd_read); + +int smd_read_user_buffer(smd_channel_t *ch, void *data, int len) +{ + return ch->read(ch, data, len, 1); +} +EXPORT_SYMBOL(smd_read_user_buffer); + +int smd_read_from_cb(smd_channel_t *ch, void *data, int len) +{ + return ch->read_from_cb(ch, data, len, 0); +} +EXPORT_SYMBOL(smd_read_from_cb); int smd_write(smd_channel_t *ch, const void *data, int len) { - return ch->write(ch, data, len); + return ch->pending_pkt_sz ? -EBUSY : ch->write(ch, data, len, 0); } +EXPORT_SYMBOL(smd_write); -int smd_write_atomic(smd_channel_t *ch, const void *data, int len) +int smd_write_user_buffer(smd_channel_t *ch, const void *data, int len) { - unsigned long flags; - int res; - spin_lock_irqsave(&smd_lock, flags); - res = ch->write(ch, data, len); - spin_unlock_irqrestore(&smd_lock, flags); - return res; + return ch->pending_pkt_sz ? -EBUSY : ch->write(ch, data, len, 1); } +EXPORT_SYMBOL(smd_write_user_buffer); int smd_read_avail(smd_channel_t *ch) { return ch->read_avail(ch); } +EXPORT_SYMBOL(smd_read_avail); int smd_write_avail(smd_channel_t *ch) { return ch->write_avail(ch); } +EXPORT_SYMBOL(smd_write_avail); + +void smd_enable_read_intr(smd_channel_t *ch) +{ + if (ch) + ch->send->fBLOCKREADINTR = 0; +} +EXPORT_SYMBOL(smd_enable_read_intr); + +void smd_disable_read_intr(smd_channel_t *ch) +{ + if (ch) + ch->send->fBLOCKREADINTR = 1; +} +EXPORT_SYMBOL(smd_disable_read_intr); int smd_wait_until_readable(smd_channel_t *ch, int bytes) { @@ -787,16 +1675,109 @@ int smd_cur_packet_size(smd_channel_t *ch) { return ch->current_packet; } +EXPORT_SYMBOL(smd_cur_packet_size); +int smd_tiocmget(smd_channel_t *ch) +{ + return (ch->recv->fDSR ? TIOCM_DSR : 0) | + (ch->recv->fCTS ? TIOCM_CTS : 0) | + (ch->recv->fCD ? TIOCM_CD : 0) | + (ch->recv->fRI ? TIOCM_RI : 0) | + (ch->send->fCTS ? TIOCM_RTS : 0) | + (ch->send->fDSR ? TIOCM_DTR : 0); +} +EXPORT_SYMBOL(smd_tiocmget); -/* ------------------------------------------------------------------------- */ +int smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear) +{ + unsigned long flags; + spin_lock_irqsave(&smd_lock, flags); + if (set & TIOCM_DTR) + ch->send->fDSR = 1; + + if (set & TIOCM_RTS) + ch->send->fCTS = 1; + + if (clear & TIOCM_DTR) + ch->send->fDSR = 0; + + if (clear & TIOCM_RTS) + ch->send->fCTS = 0; + + ch->send->fSTATE = 1; + barrier(); + ch->notify_other_cpu(); + spin_unlock_irqrestore(&smd_lock, flags); + + return 0; +} +EXPORT_SYMBOL(smd_tiocmset); + + +/* -------------------------------------------------------------------------- */ + +/* smem_alloc returns the pointer to smem item if it is already allocated. + * Otherwise, it returns NULL. + */ void *smem_alloc(unsigned id, unsigned size) { return smem_find(id, size); } +EXPORT_SYMBOL(smem_alloc); + +#define SMEM_SPINLOCK_SMEM_ALLOC "S:3" +static remote_spinlock_t remote_spinlock; -void *smem_item(unsigned id, unsigned *size) +/* smem_alloc2 returns the pointer to smem item. If it is not allocated, + * it allocates it and then returns the pointer to it. + */ +static void *smem_alloc2(unsigned id, unsigned size_in) +{ + struct smem_shared *shared = (void *) MSM_SHARED_RAM_BASE; + struct smem_heap_entry *toc = shared->heap_toc; + unsigned long flags; + void *ret = NULL; + + if (!shared->heap_info.initialized) { + pr_err("%s: smem heap info not initialized\n", __func__); + return NULL; + } + + if (id >= SMEM_NUM_ITEMS) + return NULL; + + size_in = ALIGN(size_in, 8); + remote_spin_lock_irqsave(&remote_spinlock, flags); + if (toc[id].allocated) { + SMD_DBG("%s: %u already allocated\n", __func__, id); + if (size_in != toc[id].size) + pr_err("%s: wrong size %u (expected %u)\n", + __func__, toc[id].size, size_in); + else + ret = (void *)(MSM_SHARED_RAM_BASE + toc[id].offset); + } else if (id > SMEM_FIXED_ITEM_LAST) { + SMD_DBG("%s: allocating %u\n", __func__, id); + if (shared->heap_info.heap_remaining >= size_in) { + toc[id].offset = shared->heap_info.free_offset; + toc[id].size = size_in; + wmb(); + toc[id].allocated = 1; + + shared->heap_info.free_offset += size_in; + shared->heap_info.heap_remaining -= size_in; + ret = (void *)(MSM_SHARED_RAM_BASE + toc[id].offset); + } else + pr_err("%s: not enough memory %u (required %u)\n", + __func__, shared->heap_info.heap_remaining, + size_in); + } + wmb(); + remote_spin_unlock_irqrestore(&remote_spinlock, flags); + return ret; +} + +void *smem_get_entry(unsigned id, unsigned *size) { struct smem_shared *shared = (void *) MSM_SHARED_RAM_BASE; struct smem_heap_entry *toc = shared->heap_toc; @@ -804,8 +1785,10 @@ void *smem_item(unsigned id, unsigned *size) if (id >= SMEM_NUM_ITEMS) return 0; + /* toc is in device memory and cannot be speculatively accessed */ if (toc[id].allocated) { *size = toc[id].size; + barrier(); return (void *) (MSM_SHARED_RAM_BASE + toc[id].offset); } else { *size = 0; @@ -813,13 +1796,14 @@ void *smem_item(unsigned id, unsigned *size) return 0; } +EXPORT_SYMBOL(smem_get_entry); void *smem_find(unsigned id, unsigned size_in) { unsigned size; void *ptr; - ptr = smem_item(id, &size); + ptr = smem_get_entry(id, &size); if (!ptr) return 0; @@ -832,194 +1816,662 @@ void *smem_find(unsigned id, unsigned size_in) return ptr; } +EXPORT_SYMBOL(smem_find); + +static int smsm_cb_init(void) +{ + unsigned long flags; + struct smsm_state_info *state_info; + int n; + int ret = 0; + + smsm_states = kmalloc(sizeof(struct smsm_state_info)*SMSM_NUM_ENTRIES, + GFP_KERNEL); + + if (!smsm_states) { + pr_err("%s: SMSM init failed\n", __func__); + return -ENOMEM; + } + + spin_lock_irqsave(&smsm_lock, flags); + for (n = 0; n < SMSM_NUM_ENTRIES; n++) { + state_info = &smsm_states[n]; + state_info->last_value = __raw_readl(SMSM_STATE_ADDR(n)); + INIT_LIST_HEAD(&state_info->callbacks); + } + spin_unlock_irqrestore(&smsm_lock, flags); + + return ret; +} + +static int smsm_init(void) +{ + struct smem_shared *shared = (void *) MSM_SHARED_RAM_BASE; + int i; + struct smsm_size_info_type *smsm_size_info; + + i = remote_spin_lock_init(&remote_spinlock, SMEM_SPINLOCK_SMEM_ALLOC); + if (i) { + pr_err("%s: remote spinlock init failed %d\n", __func__, i); + return i; + } + + smsm_size_info = smem_alloc(SMEM_SMSM_SIZE_INFO, + sizeof(struct smsm_size_info_type)); + if (smsm_size_info) { + SMSM_NUM_ENTRIES = smsm_size_info->num_entries; + SMSM_NUM_HOSTS = smsm_size_info->num_hosts; + } + + if (!smsm_info.state) { + smsm_info.state = smem_alloc2(ID_SHARED_STATE, + SMSM_NUM_ENTRIES * + sizeof(uint32_t)); + + if (smsm_info.state) { + __raw_writel(0, SMSM_STATE_ADDR(SMSM_APPS_STATE)); + if ((shared->version[VERSION_MODEM] >> 16) >= 0xB) + __raw_writel(0, \ + SMSM_STATE_ADDR(SMSM_APPS_DEM_I)); + } + } + + if (!smsm_info.intr_mask) { + smsm_info.intr_mask = smem_alloc2(SMEM_SMSM_CPU_INTR_MASK, + SMSM_NUM_ENTRIES * + SMSM_NUM_HOSTS * + sizeof(uint32_t)); + + if (smsm_info.intr_mask) + for (i = 0; i < SMSM_NUM_ENTRIES; i++) + __raw_writel(0xffffffff, + SMSM_INTR_MASK_ADDR(i, SMSM_APPS)); + } + + if (!smsm_info.intr_mux) + smsm_info.intr_mux = smem_alloc2(SMEM_SMD_SMSM_INTR_MUX, + SMSM_NUM_INTR_MUX * + sizeof(uint32_t)); + + i = smsm_cb_init(); + if (i) + return i; + + wmb(); + return 0; +} + +void smsm_reset_modem(unsigned mode) +{ + if (mode == SMSM_SYSTEM_DOWNLOAD) { + mode = SMSM_RESET | SMSM_SYSTEM_DOWNLOAD; + } else if (mode == SMSM_MODEM_WAIT) { + mode = SMSM_RESET | SMSM_MODEM_WAIT; + } else { /* reset_mode is SMSM_RESET or default */ + mode = SMSM_RESET; + } + + smsm_change_state(SMSM_APPS_STATE, mode, mode); +} +EXPORT_SYMBOL(smsm_reset_modem); + +void smsm_reset_modem_cont(void) +{ + unsigned long flags; + uint32_t state; + + if (!smsm_info.state) + return; + + spin_lock_irqsave(&smem_lock, flags); + state = __raw_readl(SMSM_STATE_ADDR(SMSM_APPS_STATE)) \ + & ~SMSM_MODEM_WAIT; + __raw_writel(state, SMSM_STATE_ADDR(SMSM_APPS_STATE)); + wmb(); + spin_unlock_irqrestore(&smem_lock, flags); +} +EXPORT_SYMBOL(smsm_reset_modem_cont); static irqreturn_t smsm_irq_handler(int irq, void *data) { unsigned long flags; - unsigned apps, modm; + +#if !defined(CONFIG_ARCH_MSM8X60) + uint32_t mux_val; + static uint32_t prev_smem_q6_apps_smsm; + + if (irq == INT_ADSP_A11_SMSM) { + if (!smsm_info.intr_mux) + return IRQ_HANDLED; + mux_val = __raw_readl(SMSM_INTR_MUX_ADDR(SMEM_Q6_APPS_SMSM)); + if (mux_val != prev_smem_q6_apps_smsm) + prev_smem_q6_apps_smsm = mux_val; + return IRQ_HANDLED; + } +#else + if (irq == INT_ADSP_A11_SMSM) + return IRQ_HANDLED; +#endif + spin_lock_irqsave(&smem_lock, flags); + if (!smsm_info.state) { + SMSM_INFO("\n"); + } else { + unsigned old_apps, apps; + unsigned modm = __raw_readl(SMSM_STATE_ADDR(SMSM_MODEM_STATE)); - apps = raw_smsm_get_state(SMSM_STATE_APPS); - modm = raw_smsm_get_state(SMSM_STATE_MODEM); + old_apps = apps = __raw_readl(SMSM_STATE_ADDR(SMSM_APPS_STATE)); - if (msm_smd_debug_mask & MSM_SMSM_DEBUG) - pr_info("\n", apps, modm); - if (modm & SMSM_RESET) - handle_modem_crash(); + SMSM_DBG("\n", apps, modm); + if (apps & SMSM_RESET) { + /* If we get an interrupt and the apps SMSM_RESET + bit is already set, the modem is acking the + app's reset ack. */ + apps &= ~SMSM_RESET; - do_smd_probe(); + /* Issue a fake irq to handle any + * smd state changes during reset + */ + smd_fake_irq_handler(0); + + /* queue modem restart notify chain */ + modem_queue_start_reset_notify(); + + } else if (modm & SMSM_RESET) { + apps |= SMSM_RESET; + + pr_err("\nSMSM: Modem SMSM state changed to SMSM_RESET."); + modem_queue_start_reset_notify(); + + } else if (modm & SMSM_INIT) { + if (!(apps & SMSM_INIT)) { + apps |= SMSM_INIT; + modem_queue_smsm_init_notify(); + } + + if (modm & SMSM_SMDINIT) + apps |= SMSM_SMDINIT; + if ((apps & (SMSM_INIT | SMSM_SMDINIT | SMSM_RPCINIT)) == + (SMSM_INIT | SMSM_SMDINIT | SMSM_RPCINIT)) + apps |= SMSM_RUN; + } else if (modm & SMSM_SYSTEM_DOWNLOAD) { + pr_err("\nSMSM: Modem SMSM state changed to SMSM_SYSTEM_DOWNLOAD."); + modem_queue_start_reset_notify(); + } + if (old_apps != apps) { + SMSM_DBG("\n", apps); + __raw_writel(apps, SMSM_STATE_ADDR(SMSM_APPS_STATE)); + do_smd_probe(); + notify_other_smsm(SMSM_APPS_STATE, (old_apps ^ apps)); + } + + schedule_work(&smsm_cb_work); + } spin_unlock_irqrestore(&smem_lock, flags); return IRQ_HANDLED; } -int smsm_change_state(enum smsm_state_item item, - uint32_t clear_mask, uint32_t set_mask) +int smsm_change_intr_mask(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask) { - unsigned long addr = smd_info.state + item * 4; + uint32_t old_mask, new_mask; unsigned long flags; - unsigned state; - if (!smd_info.ready) + if (smsm_entry >= SMSM_NUM_ENTRIES) { + pr_err("smsm_change_state: Invalid entry %d\n", + smsm_entry); + return -EINVAL; + } + + if (!smsm_info.intr_mask) { + pr_err("smsm_change_intr_mask \n"); return -EIO; + } spin_lock_irqsave(&smem_lock, flags); - if (raw_smsm_get_state(SMSM_STATE_MODEM) & SMSM_RESET) - handle_modem_crash(); + old_mask = __raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); + new_mask = (old_mask & ~clear_mask) | set_mask; + __raw_writel(new_mask, SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); - state = (readl(addr) & ~clear_mask) | set_mask; - writel(state, addr); + wmb(); + spin_unlock_irqrestore(&smem_lock, flags); - if (msm_smd_debug_mask & MSM_SMSM_DEBUG) - pr_info("smsm_change_state %d %x\n", item, state); - notify_other_smsm(); + return 0; +} +EXPORT_SYMBOL(smsm_change_intr_mask); - spin_unlock_irqrestore(&smem_lock, flags); +int smsm_get_intr_mask(uint32_t smsm_entry, uint32_t *intr_mask) +{ + if (smsm_entry >= SMSM_NUM_ENTRIES) { + pr_err("smsm_change_state: Invalid entry %d\n", + smsm_entry); + return -EINVAL; + } + if (!smsm_info.intr_mask) { + pr_err("smsm_change_intr_mask \n"); + return -EIO; + } + + *intr_mask = __raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); return 0; } +EXPORT_SYMBOL(smsm_get_intr_mask); -uint32_t smsm_get_state(enum smsm_state_item item) +int smsm_change_state(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask) { unsigned long flags; - uint32_t rv; + uint32_t old_state, new_state; - spin_lock_irqsave(&smem_lock, flags); + if (smsm_entry >= SMSM_NUM_ENTRIES) { + pr_err("smsm_change_state: Invalid entry %d", + smsm_entry); + return -EINVAL; + } - rv = readl(smd_info.state + item * 4); + if (!smsm_info.state) { + pr_err("smsm_change_state \n"); + return -EIO; + } + spin_lock_irqsave(&smem_lock, flags); - if (item == SMSM_STATE_MODEM && (rv & SMSM_RESET)) - handle_modem_crash(); + old_state = __raw_readl(SMSM_STATE_ADDR(smsm_entry)); + new_state = (old_state & ~clear_mask) | set_mask; + __raw_writel(new_state, SMSM_STATE_ADDR(smsm_entry)); + SMSM_DBG("smsm_change_state %x\n", new_state); + notify_other_smsm(SMSM_APPS_STATE, (old_state ^ new_state)); spin_unlock_irqrestore(&smem_lock, flags); - return rv; + return 0; } +EXPORT_SYMBOL(smsm_change_state); -#ifdef CONFIG_ARCH_MSM_SCORPION +uint32_t smsm_get_state(uint32_t smsm_entry) +{ + uint32_t rv = 0; -int smsm_set_sleep_duration(uint32_t delay) + /* needs interface change to return error code */ + if (smsm_entry >= SMSM_NUM_ENTRIES) { + pr_err("smsm_change_state: Invalid entry %d", + smsm_entry); + return 0; + } + + if (!smsm_info.state) { + pr_err("smsm_get_state \n"); + } else { + rv = __raw_readl(SMSM_STATE_ADDR(smsm_entry)); + } + + return rv; +} +EXPORT_SYMBOL(smsm_get_state); + +/** + * Performs SMSM callback client notifiction. + */ +void notify_smsm_cb_clients_worker(struct work_struct *work) { - struct msm_dem_slave_data *ptr; + unsigned long flags; + struct smsm_state_cb_info *cb_info; + struct smsm_state_info *state_info; + int n; + uint32_t new_state; + uint32_t state_changes; - ptr = smem_find(SMEM_APPS_DEM_SLAVE_DATA, sizeof(*ptr)); - if (ptr == NULL) { - pr_err("smsm_set_sleep_duration \n"); - return -EIO; + spin_lock_irqsave(&smsm_lock, flags); + + if (!smsm_states) { + /* smsm not yet initialized */ + spin_unlock_irqrestore(&smsm_lock, flags); + return; } - if (msm_smd_debug_mask & MSM_SMSM_DEBUG) - pr_info("smsm_set_sleep_duration %d -> %d\n", - ptr->sleep_time, delay); - ptr->sleep_time = delay; - return 0; + + for (n = 0; n < SMSM_NUM_ENTRIES; n++) { + state_info = &smsm_states[n]; + new_state = __raw_readl(SMSM_STATE_ADDR(n)); + + if (new_state != state_info->last_value) { + state_changes = state_info->last_value ^ new_state; + + list_for_each_entry(cb_info, + &state_info->callbacks, cb_list) { + + if (cb_info->mask & state_changes) + cb_info->notify(cb_info->data, + state_info->last_value, + new_state); + } + state_info->last_value = new_state; + } + } + + spin_unlock_irqrestore(&smsm_lock, flags); } -#else -int smsm_set_sleep_duration(uint32_t delay) +/** + * Registers callback for SMSM state notifications when the specified + * bits change. + * + * @smsm_entry Processor entry to deregister + * @mask Bits to deregister (if result is 0, callback is removed) + * @notify Notification function to deregister + * @data Opaque data passed in to callback + * + * @returns Status code + * <0 error code + * 0 inserted new entry + * 1 updated mask of existing entry + */ +int smsm_state_cb_register(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t, uint32_t), void *data) { - uint32_t *ptr; + unsigned long flags; + struct smsm_state_cb_info *cb_info; + struct smsm_state_cb_info *cb_found = 0; + int ret = 0; - ptr = smem_find(SMEM_SMSM_SLEEP_DELAY, sizeof(*ptr)); - if (ptr == NULL) { - pr_err("smsm_set_sleep_duration \n"); - return -EIO; + if (smsm_entry >= SMSM_NUM_ENTRIES) + return -EINVAL; + + spin_lock_irqsave(&smsm_lock, flags); + + if (!smsm_states) { + /* smsm not yet initialized */ + ret = -ENODEV; + goto cleanup; } - if (msm_smd_debug_mask & MSM_SMSM_DEBUG) - pr_info("smsm_set_sleep_duration %d -> %d\n", - *ptr, delay); - *ptr = delay; - return 0; + + list_for_each_entry(cb_info, + &smsm_states[smsm_entry].callbacks, cb_list) { + if ((cb_info->notify == notify) && + (cb_info->data == data)) { + cb_info->mask |= mask; + cb_found = cb_info; + ret = 1; + break; + } + } + + if (!cb_found) { + cb_info = kmalloc(sizeof(struct smsm_state_cb_info), + GFP_ATOMIC); + if (!cb_info) { + ret = -ENOMEM; + goto cleanup; + } + + cb_info->mask = mask; + cb_info->notify = notify; + cb_info->data = data; + INIT_LIST_HEAD(&cb_info->cb_list); + list_add_tail(&cb_info->cb_list, + &smsm_states[smsm_entry].callbacks); + } + +cleanup: + spin_unlock_irqrestore(&smsm_lock, flags); + return ret; } +EXPORT_SYMBOL(smsm_state_cb_register); -#endif -int smd_core_init(void) +/** + * Deregisters for SMSM state notifications for the specified bits. + * + * @smsm_entry Processor entry to deregister + * @mask Bits to deregister (if result is 0, callback is removed) + * @notify Notification function to deregister + * @data Opaque data passed in to callback + * + * @returns Status code + * <0 error code + * 0 not found + * 1 updated mask + * 2 removed callback + */ +int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t, uint32_t), void *data) { - int r; + unsigned long flags; + struct smsm_state_cb_info *cb_info; + int ret = 0; + + if (smsm_entry >= SMSM_NUM_ENTRIES) + return -EINVAL; + + spin_lock_irqsave(&smsm_lock, flags); - /* wait for essential items to be initialized */ - for (;;) { - unsigned size; - void *state; - state = smem_item(SMEM_SMSM_SHARED_STATE, &size); - if (size == SMSM_V1_SIZE || size == SMSM_V2_SIZE) { - smd_info.state = (unsigned)state; + if (!smsm_states) { + /* smsm not yet initialized */ + spin_unlock_irqrestore(&smsm_lock, flags); + return -ENODEV; + } + + list_for_each_entry(cb_info, + &smsm_states[smsm_entry].callbacks, cb_list) { + if ((cb_info->notify == notify) && + (cb_info->data == data)) { + cb_info->mask &= ~mask; + ret = 1; + if (!cb_info->mask) { + /* no mask bits set, remove callback */ + list_del(&cb_info->cb_list); + kfree(cb_info); + ret = 2; + } break; } } - smd_info.ready = 1; + spin_unlock_irqrestore(&smsm_lock, flags); + return ret; +} +EXPORT_SYMBOL(smsm_state_cb_deregister); + + +int smd_core_init(void) +{ + int r; + unsigned long flags = IRQF_TRIGGER_RISING; + SMD_INFO("smd_core_init()\n"); r = request_irq(INT_A9_M2A_0, smd_modem_irq_handler, - IRQF_TRIGGER_RISING, "smd_dev", 0); + flags, "smd_dev", 0); if (r < 0) return r; r = enable_irq_wake(INT_A9_M2A_0); if (r < 0) - pr_err("smd_core_init: enable_irq_wake failed for A9_M2A_0\n"); + pr_err("smd_core_init: " + "enable_irq_wake failed for INT_A9_M2A_0\n"); r = request_irq(INT_A9_M2A_5, smsm_irq_handler, - IRQF_TRIGGER_RISING, "smsm_dev", 0); + flags, "smsm_dev", 0); if (r < 0) { free_irq(INT_A9_M2A_0, 0); return r; } r = enable_irq_wake(INT_A9_M2A_5); if (r < 0) - pr_err("smd_core_init: enable_irq_wake failed for A9_M2A_5\n"); + pr_err("smd_core_init: " + "enable_irq_wake failed for INT_A9_M2A_5\n"); #if defined(CONFIG_QDSP6) +#if (INT_ADSP_A11 == INT_ADSP_A11_SMSM) + flags |= IRQF_SHARED; +#endif r = request_irq(INT_ADSP_A11, smd_dsp_irq_handler, - IRQF_TRIGGER_RISING, "smd_dsp", 0); + flags, "smd_dev", smd_dsp_irq_handler); + if (r < 0) { + free_irq(INT_A9_M2A_0, 0); + free_irq(INT_A9_M2A_5, 0); + return r; + } + + r = request_irq(INT_ADSP_A11_SMSM, smsm_irq_handler, + flags, "smsm_dev", smsm_irq_handler); + if (r < 0) { + free_irq(INT_A9_M2A_0, 0); + free_irq(INT_A9_M2A_5, 0); + free_irq(INT_ADSP_A11, smd_dsp_irq_handler); + return r; + } + + r = enable_irq_wake(INT_ADSP_A11); + if (r < 0) + pr_err("smd_core_init: " + "enable_irq_wake failed for INT_ADSP_A11\n"); + +#if (INT_ADSP_A11 != INT_ADSP_A11_SMSM) + r = enable_irq_wake(INT_ADSP_A11_SMSM); + if (r < 0) + pr_err("smd_core_init: enable_irq_wake " + "failed for INT_ADSP_A11_SMSM\n"); +#endif + flags &= ~IRQF_SHARED; +#endif + +#if defined(CONFIG_DSPS) + r = request_irq(INT_DSPS_A11, smd_dsps_irq_handler, + flags, "smd_dev", smd_dsps_irq_handler); if (r < 0) { free_irq(INT_A9_M2A_0, 0); free_irq(INT_A9_M2A_5, 0); + free_irq(INT_ADSP_A11, smd_dsp_irq_handler); + free_irq(INT_ADSP_A11_SMSM, smsm_irq_handler); return r; } + + r = enable_irq_wake(INT_DSPS_A11); + if (r < 0) + pr_err("smd_core_init: " + "enable_irq_wake failed for INT_ADSP_A11\n"); #endif - /* check for any SMD channels that may already exist */ - do_smd_probe(); +#if defined(CONFIG_WCNSS) + r = request_irq(INT_WCNSS_A11, smd_wcnss_irq_handler, + flags, "smd_dev", smd_wcnss_irq_handler); + if (r < 0) { + free_irq(INT_A9_M2A_0, 0); + free_irq(INT_A9_M2A_5, 0); + free_irq(INT_ADSP_A11, smd_dsp_irq_handler); + free_irq(INT_ADSP_A11_SMSM, smsm_irq_handler); + free_irq(INT_DSPS_A11, smd_dsps_irq_handler); + return r; + } + + r = enable_irq_wake(INT_WCNSS_A11); + if (r < 0) + pr_err("smd_core_init: " + "enable_irq_wake failed for INT_WCNSS_A11\n"); - /* indicate that we're up and running */ - smsm_change_state(SMSM_STATE_APPS, - ~0, SMSM_INIT | SMSM_SMDINIT | SMSM_RPCINIT | SMSM_RUN); -#ifdef CONFIG_ARCH_MSM_SCORPION - smsm_change_state(SMSM_STATE_APPS_DEM, ~0, 0); + r = request_irq(INT_WCNSS_A11_SMSM, smsm_irq_handler, + flags, "smsm_dev", smsm_irq_handler); + if (r < 0) { + free_irq(INT_A9_M2A_0, 0); + free_irq(INT_A9_M2A_5, 0); + free_irq(INT_ADSP_A11, smd_dsp_irq_handler); + free_irq(INT_ADSP_A11_SMSM, smsm_irq_handler); + free_irq(INT_DSPS_A11, smd_dsps_irq_handler); + free_irq(INT_WCNSS_A11, smd_wcnss_irq_handler); + return r; + } + + r = enable_irq_wake(INT_WCNSS_A11_SMSM); + if (r < 0) + pr_err("smd_core_init: " + "enable_irq_wake failed for INT_WCNSS_A11_SMSM\n"); #endif + /* we may have missed a signal while booting -- fake + * an interrupt to make sure we process any existing + * state + */ + smsm_irq_handler(0, 0); + + SMD_INFO("smd_core_init() done\n"); + return 0; } static int __devinit msm_smd_probe(struct platform_device *pdev) { - /* - * If we haven't waited for the ARM9 to boot up till now, - * then we need to wait here. Otherwise this should just - * return immediately. - */ - proc_comm_boot_wait(); + SMD_INFO("smd probe\n"); INIT_WORK(&probe_work, smd_channel_probe_worker); + channel_close_wq = create_singlethread_workqueue("smd_channel_close"); + if (IS_ERR(channel_close_wq)) { + pr_err("%s: create_singlethread_workqueue ENOMEM\n", __func__); + return -ENOMEM; + } + + if (smsm_init()) { + pr_err("smsm_init() failed\n"); + return -1; + } + if (smd_core_init()) { pr_err("smd_core_init() failed\n"); return -1; } - do_smd_probe(); + smd_initialized = 1; - msm_check_for_modem_crash = check_for_modem_crash; + smd_alloc_loopback_channel(); - msm_init_last_radio_log(THIS_MODULE); + return 0; +} - smd_initialized = 1; +static int restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data); + +static struct restart_notifier_block restart_notifiers[] = { + {SMSM_MODEM, "modem", .nb.notifier_call = restart_notifier_cb}, + {SMSM_Q6, "lpass", .nb.notifier_call = restart_notifier_cb}, +}; + +static int restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data) +{ + if (code == SUBSYS_AFTER_SHUTDOWN) { + struct restart_notifier_block *notifier; + + notifier = container_of(this, + struct restart_notifier_block, nb); + SMD_INFO("%s: ssrestart for processor %d ('%s')\n", + __func__, notifier->processor, + notifier->name); + smd_channel_reset(notifier->processor); + } + + return NOTIFY_DONE; +} + +static __init int modem_restart_late_init(void) +{ + int i; + void *handle; + struct restart_notifier_block *nb; + + for (i = 0; i < ARRAY_SIZE(restart_notifiers); i++) { + nb = &restart_notifiers[i]; + handle = subsys_notif_register_notifier(nb->name, &nb->nb); + SMD_DBG("%s: registering notif for '%s', handle=%p\n", + __func__, nb->name, handle); + } return 0; } +late_initcall(modem_restart_late_init); static struct platform_driver msm_smd_driver = { .probe = msm_smd_probe, diff --git a/arch/arm/mach-msm/smd_debug.c b/arch/arm/mach-msm/smd_debug.c index 8736afff82f..855482bf9a9 100644 --- a/arch/arm/mach-msm/smd_debug.c +++ b/arch/arm/mach-msm/smd_debug.c @@ -1,6 +1,7 @@ /* arch/arm/mach-msm/smd_debug.c * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -16,6 +17,7 @@ #include #include +#include #include @@ -45,17 +47,160 @@ static char *chstate(unsigned n) } } +static int debug_f3(char *buf, int max) +{ + char *x; + int size; + int i = 0, j = 0; + unsigned cols = 0; + char str[4*sizeof(unsigned)+1] = {0}; + + i += scnprintf(buf + i, max - i, + "Printing to log\n"); + + x = smem_get_entry(SMEM_ERR_F3_TRACE_LOG, &size); + if (x != 0) { + pr_info("smem: F3 TRACE LOG\n"); + while (size > 0) { + if (size >= sizeof(unsigned)) { + pr_info("%08x", *((unsigned *) x)); + for (j = 0; j < sizeof(unsigned); ++j) + if (isprint(*(x+j))) + str[cols*sizeof(unsigned) + j] + = *(x+j); + else + str[cols*sizeof(unsigned) + j] + = '-'; + x += sizeof(unsigned); + size -= sizeof(unsigned); + } else { + while (size-- > 0) + pr_info("%02x", (unsigned) *x++); + break; + } + if (cols == 3) { + cols = 0; + str[4*sizeof(unsigned)] = 0; + pr_info(" %s\n", str); + str[0] = 0; + } else { + cols++; + pr_info(" "); + } + } + pr_info("\n"); + } + + return max; +} + +static int debug_diag(char *buf, int max) +{ + int i = 0; + + i += scnprintf(buf + i, max - i, + "Printing to log\n"); + smd_diag(); + + return i; +} + +static int debug_modem_err_f3(char *buf, int max) +{ + char *x; + int size; + int i = 0, j = 0; + unsigned cols = 0; + char str[4*sizeof(unsigned)+1] = {0}; + + x = smem_get_entry(SMEM_ERR_F3_TRACE_LOG, &size); + if (x != 0) { + pr_info("smem: F3 TRACE LOG\n"); + while (size > 0 && max - i) { + if (size >= sizeof(unsigned)) { + i += scnprintf(buf + i, max - i, "%08x", + *((unsigned *) x)); + for (j = 0; j < sizeof(unsigned); ++j) + if (isprint(*(x+j))) + str[cols*sizeof(unsigned) + j] + = *(x+j); + else + str[cols*sizeof(unsigned) + j] + = '-'; + x += sizeof(unsigned); + size -= sizeof(unsigned); + } else { + while (size-- > 0 && max - i) + i += scnprintf(buf + i, max - i, + "%02x", + (unsigned) *x++); + break; + } + if (cols == 3) { + cols = 0; + str[4*sizeof(unsigned)] = 0; + i += scnprintf(buf + i, max - i, " %s\n", + str); + str[0] = 0; + } else { + cols++; + i += scnprintf(buf + i, max - i, " "); + } + } + i += scnprintf(buf + i, max - i, "\n"); + } + + return i; +} + +static int debug_modem_err(char *buf, int max) +{ + char *x; + int size; + int i = 0; + + x = smem_find(ID_DIAG_ERR_MSG, SZ_DIAG_ERR_MSG); + if (x != 0) { + x[SZ_DIAG_ERR_MSG - 1] = 0; + i += scnprintf(buf + i, max - i, + "smem: DIAG '%s'\n", x); + } + + x = smem_get_entry(SMEM_ERR_CRASH_LOG, &size); + if (x != 0) { + x[size - 1] = 0; + i += scnprintf(buf + i, max - i, + "smem: CRASH LOG\n'%s'\n", x); + } + i += scnprintf(buf + i, max - i, "\n"); + + return i; +} -static int dump_ch(char *buf, int max, struct smd_channel *ch) +static int debug_read_diag_msg(char *buf, int max) { - volatile struct smd_half_channel *s = ch->send; - volatile struct smd_half_channel *r = ch->recv; + char *msg; + int i = 0; + + msg = smem_find(ID_DIAG_ERR_MSG, SZ_DIAG_ERR_MSG); + if (msg) { + msg[SZ_DIAG_ERR_MSG - 1] = 0; + i += scnprintf(buf + i, max - i, "diag: '%s'\n", msg); + } + return i; +} + +static int dump_ch(char *buf, int max, int n, + struct smd_half_channel *s, + struct smd_half_channel *r, + unsigned size) +{ return scnprintf( buf, max, "ch%02d:" - " %8s(%05d/%05d) %c%c%c%c%c%c%c <->" - " %8s(%05d/%05d) %c%c%c%c%c%c%c '%s'\n", ch->n, + " %8s(%04d/%04d) %c%c%c%c%c%c%c%c <->" + " %8s(%04d/%04d) %c%c%c%c%c%c%c%c : %5x\n", n, chstate(s->state), s->tail, s->head, s->fDSR ? 'D' : 'd', s->fCTS ? 'C' : 'c', @@ -64,6 +209,7 @@ static int dump_ch(char *buf, int max, struct smd_channel *ch) s->fHEAD ? 'W' : 'w', s->fTAIL ? 'R' : 'r', s->fSTATE ? 'S' : 's', + s->fBLOCKREADINTR ? 'B' : 'b', chstate(r->state), r->tail, r->head, r->fDSR ? 'D' : 'd', r->fCTS ? 'R' : 'r', @@ -72,37 +218,193 @@ static int dump_ch(char *buf, int max, struct smd_channel *ch) r->fHEAD ? 'W' : 'w', r->fTAIL ? 'R' : 'r', r->fSTATE ? 'S' : 's', - ch->name + r->fBLOCKREADINTR ? 'B' : 'b', + size ); } -static int debug_read_stat(char *buf, int max) +static int debug_read_smsm_state(char *buf, int max) { - char *msg; - int i = 0; + uint32_t *smsm; + int n, i = 0; - msg = smem_find(ID_DIAG_ERR_MSG, SZ_DIAG_ERR_MSG); + smsm = smem_find(ID_SHARED_STATE, + SMSM_NUM_ENTRIES * sizeof(uint32_t)); + + if (smsm) + for (n = 0; n < SMSM_NUM_ENTRIES; n++) + i += scnprintf(buf + i, max - i, "entry %d: 0x%08x\n", + n, smsm[n]); + + return i; +} + +struct SMSM_CB_DATA { + int cb_count; + void *data; + uint32_t old_state; + uint32_t new_state; +}; +static struct SMSM_CB_DATA smsm_cb_data; + +static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state) +{ + smsm_cb_data.cb_count++; + smsm_cb_data.old_state = old_state; + smsm_cb_data.new_state = new_state; + smsm_cb_data.data = data; +} + +#define UT_EQ_INT(a, b) \ + if ((a) != (b)) { \ + i += scnprintf(buf + i, max - i, \ + "%s:%d " #a "(%d) != " #b "(%d)\n", \ + __func__, __LINE__, \ + a, b); \ + break; \ + } \ + do {} while (0) + +#define SMSM_CB_TEST_INIT() \ + do { \ + smsm_cb_data.cb_count = 0; \ + smsm_cb_data.old_state = 0; \ + smsm_cb_data.new_state = 0; \ + smsm_cb_data.data = 0; \ + } while (0) + + +static int debug_test_smsm(char *buf, int max) +{ + int i = 0; + int test_num = 0; + int ret; + + /* Test case 1 - Register new callback for notification */ + do { + test_num++; + SMSM_CB_TEST_INIT(); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 0); + + /* de-assert SMSM_SMD_INIT to trigger state update */ + UT_EQ_INT(smsm_cb_data.cb_count, 0); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + + UT_EQ_INT(smsm_cb_data.cb_count, 1); + UT_EQ_INT(smsm_cb_data.cb_count, 1); + UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, SMSM_SMDINIT); + UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, 0x0); + UT_EQ_INT((int)smsm_cb_data.data, 0x1234); + + /* re-assert SMSM_SMD_INIT to trigger state update */ + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_EQ_INT(smsm_cb_data.cb_count, 2); + UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, 0x0); + UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, SMSM_SMDINIT); + + /* deregister callback */ + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 2); + + /* make sure state change doesn't cause any more callbacks */ + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_EQ_INT(smsm_cb_data.cb_count, 2); + + i += scnprintf(buf + i, max - i, "Test %d - PASS\n", test_num); + } while (0); + + /* Test case 2 - Update already registered callback */ + do { + test_num++; + SMSM_CB_TEST_INIT(); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 0); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_INIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 1); + + /* verify both callback bits work */ + UT_EQ_INT(smsm_cb_data.cb_count, 0); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + UT_EQ_INT(smsm_cb_data.cb_count, 1); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_EQ_INT(smsm_cb_data.cb_count, 2); + + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + UT_EQ_INT(smsm_cb_data.cb_count, 3); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + UT_EQ_INT(smsm_cb_data.cb_count, 4); + + /* deregister 1st callback */ + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 1); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_EQ_INT(smsm_cb_data.cb_count, 4); + + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + UT_EQ_INT(smsm_cb_data.cb_count, 5); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + UT_EQ_INT(smsm_cb_data.cb_count, 6); + + /* deregister 2nd callback */ + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_INIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 2); + + /* make sure state change doesn't cause any more callbacks */ + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + UT_EQ_INT(smsm_cb_data.cb_count, 6); + + i += scnprintf(buf + i, max - i, "Test %d - PASS\n", test_num); + } while (0); + + /* Test case 3 - Two callback registrations with different data */ + do { + test_num++; + SMSM_CB_TEST_INIT(); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 0); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_INIT, + smsm_state_cb, (void *)0x3456); + UT_EQ_INT(ret, 0); + + /* verify both callbacks work */ + UT_EQ_INT(smsm_cb_data.cb_count, 0); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + UT_EQ_INT(smsm_cb_data.cb_count, 1); + UT_EQ_INT((int)smsm_cb_data.data, 0x1234); + + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + UT_EQ_INT(smsm_cb_data.cb_count, 2); + UT_EQ_INT((int)smsm_cb_data.data, 0x3456); + + /* cleanup and unregister + * degregister in reverse to verify data field is + * being used + */ + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, + SMSM_INIT, + smsm_state_cb, (void *)0x3456); + UT_EQ_INT(ret, 2); + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, + SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 2); + + i += scnprintf(buf + i, max - i, "Test %d - PASS\n", test_num); + } while (0); - if (raw_smsm_get_state(SMSM_STATE_MODEM) & SMSM_RESET) - i += scnprintf(buf + i, max - i, - "smsm: ARM9 HAS CRASHED\n"); - - i += scnprintf(buf + i, max - i, "smsm: a9: %08x a11: %08x\n", - raw_smsm_get_state(SMSM_STATE_MODEM), - raw_smsm_get_state(SMSM_STATE_APPS)); -#ifdef CONFIG_ARCH_MSM_SCORPION - i += scnprintf(buf + i, max - i, "smsm dem: apps: %08x modem: %08x " - "qdsp6: %08x power: %08x time: %08x\n", - raw_smsm_get_state(SMSM_STATE_APPS_DEM), - raw_smsm_get_state(SMSM_STATE_MODEM_DEM), - raw_smsm_get_state(SMSM_STATE_QDSP6_DEM), - raw_smsm_get_state(SMSM_STATE_POWER_MASTER_DEM), - raw_smsm_get_state(SMSM_STATE_TIME_MASTER_DEM)); -#endif - if (msg) { - msg[SZ_DIAG_ERR_MSG - 1] = 0; - i += scnprintf(buf + i, max - i, "diag: '%s'\n", msg); - } return i; } @@ -129,29 +431,100 @@ static int debug_read_mem(char *buf, int max) return i; } -static int debug_read_ch(char *buf, int max) +static int debug_read_ch_v1(char *buf, int max) { - struct smd_channel *ch; - unsigned long flags; - int i = 0; + void *shared; + int n, i = 0; - spin_lock_irqsave(&smd_lock, flags); - list_for_each_entry(ch, &smd_ch_list_dsp, ch_list) - i += dump_ch(buf + i, max - i, ch); - list_for_each_entry(ch, &smd_ch_list_modem, ch_list) - i += dump_ch(buf + i, max - i, ch); - list_for_each_entry(ch, &smd_ch_closed_list, ch_list) - i += dump_ch(buf + i, max - i, ch); - spin_unlock_irqrestore(&smd_lock, flags); + for (n = 0; n < SMD_CHANNELS; n++) { + shared = smem_find(ID_SMD_CHANNELS + n, + 2 * (sizeof(struct smd_half_channel) + + SMD_BUF_SIZE)); + + if (shared == 0) + continue; + i += dump_ch(buf + i, max - i, n, shared, + (shared + sizeof(struct smd_half_channel) + + SMD_BUF_SIZE), SMD_BUF_SIZE); + } + + return i; +} + +static int debug_read_ch_v2(char *buf, int max) +{ + void *shared, *buffer; + unsigned buffer_sz; + int n, i = 0; + + for (n = 0; n < SMD_CHANNELS; n++) { + shared = smem_find(ID_SMD_CHANNELS + n, + 2 * sizeof(struct smd_half_channel)); + + if (shared == 0) + continue; + + buffer = smem_get_entry(SMEM_SMD_FIFO_BASE_ID + n, &buffer_sz); + + if (buffer == 0) + continue; + + i += dump_ch(buf + i, max - i, n, shared, + (shared + sizeof(struct smd_half_channel)), + buffer_sz / 2); + } return i; } -static int debug_read_version(char *buf, int max) +static int debug_read_ch(char *buf, int max) +{ + uint32_t *smd_ver; + + smd_ver = smem_alloc(SMEM_VERSION_SMD, 32 * sizeof(uint32_t)); + + if (smd_ver && (((smd_ver[VERSION_MODEM] >> 16) >= 1) || + ((smd_ver[VERSION_QDSP6] >> 16) >= 1) || + ((smd_ver[VERSION_DSPS] >> 16) >= 1))) + return debug_read_ch_v2(buf, max); + else + return debug_read_ch_v1(buf, max); +} + +static int debug_read_smem_version(char *buf, int max) { struct smem_shared *shared = (void *) MSM_SHARED_RAM_BASE; - unsigned version = shared->version[VERSION_MODEM]; - return sprintf(buf, "%d.%d\n", version >> 16, version & 0xffff); + uint32_t n, version, i = 0; + + for (n = 0; n < 32; n++) { + version = shared->version[n]; + i += scnprintf(buf + i, max - i, + "entry %d: smem = %d proc_comm = %d\n", n, + version >> 16, + version & 0xffff); + } + + return i; +} + +/* NNV: revist, it may not be smd version */ +static int debug_read_smd_version(char *buf, int max) +{ + uint32_t *smd_ver; + uint32_t n, version, i = 0; + + smd_ver = smem_alloc(SMEM_VERSION_SMD, 32 * sizeof(uint32_t)); + + if (smd_ver) + for (n = 0; n < 32; n++) { + version = smd_ver[n]; + i += scnprintf(buf + i, max - i, + "entry %d: %d.%d\n", n, + version >> 16, + version & 0xffff); + } + + return i; } static int debug_read_build_id(char *buf, int max) @@ -159,7 +532,7 @@ static int debug_read_build_id(char *buf, int max) unsigned size; void *data; - data = smem_item(SMEM_HW_SW_BUILD_ID, &size); + data = smem_get_entry(SMEM_HW_SW_BUILD_ID, &size); if (!data) return 0; @@ -175,23 +548,62 @@ static int debug_read_alloc_tbl(char *buf, int max) struct smd_alloc_elm *shared; int n, i = 0; - shared = smem_find(ID_CH_ALLOC_TBL, sizeof(*shared) * 64); + shared = smem_find(ID_CH_ALLOC_TBL, sizeof(struct smd_alloc_elm[64])); + + if (!shared) + return 0; for (n = 0; n < 64; n++) { - if (shared[n].ref_count == 0) - continue; i += scnprintf(buf + i, max - i, - "%03d: %-20s cid=%02d type=%03d " - "kind=%02d ref_count=%d\n", - n, shared[n].name, shared[n].cid, - shared[n].ctype & 0xff, - (shared[n].ctype >> 8) & 0xf, - shared[n].ref_count); + "name=%s cid=%d ch type=%d " + "xfer type=%d ref_count=%d\n", + shared[n].name, + shared[n].cid, + SMD_CHANNEL_TYPE(shared[n].type), + SMD_XFER_TYPE(shared[n].type), + shared[n].ref_count); } return i; } +static int debug_read_intr_mask(char *buf, int max) +{ + uint32_t *smsm; + int m, n, i = 0; + + smsm = smem_alloc(SMEM_SMSM_CPU_INTR_MASK, + SMSM_NUM_ENTRIES * SMSM_NUM_HOSTS * sizeof(uint32_t)); + + if (smsm) + for (m = 0; m < SMSM_NUM_ENTRIES; m++) { + i += scnprintf(buf + i, max - i, "entry %d:", m); + for (n = 0; n < SMSM_NUM_HOSTS; n++) + i += scnprintf(buf + i, max - i, + " host %d: 0x%08x", + n, smsm[m * SMSM_NUM_HOSTS + n]); + i += scnprintf(buf + i, max - i, "\n"); + } + + return i; +} + +static int debug_read_intr_mux(char *buf, int max) +{ + uint32_t *smsm; + int n, i = 0; + + smsm = smem_alloc(SMEM_SMD_SMSM_INTR_MUX, + SMSM_NUM_INTR_MUX * sizeof(uint32_t)); + + if (smsm) + for (n = 0; n < SMSM_NUM_INTR_MUX; n++) + i += scnprintf(buf + i, max - i, "entry %d: %d\n", + n, smsm[n]); + + return i; +} + #define DEBUG_BUFMAX 4096 static char debug_buffer[DEBUG_BUFMAX]; @@ -212,7 +624,6 @@ static int debug_open(struct inode *inode, struct file *file) static const struct file_operations debug_ops = { .read = debug_read, .open = debug_open, - .llseek = default_llseek, }; static void debug_create(const char *name, mode_t mode, @@ -222,25 +633,49 @@ static void debug_create(const char *name, mode_t mode, debugfs_create_file(name, mode, dent, fill, &debug_ops); } -static int smd_debugfs_init(void) +static int __init smd_debugfs_init(void) { struct dentry *dent; dent = debugfs_create_dir("smd", 0); if (IS_ERR(dent)) - return 1; + return PTR_ERR(dent); debug_create("ch", 0444, dent, debug_read_ch); - debug_create("stat", 0444, dent, debug_read_stat); + debug_create("diag", 0444, dent, debug_read_diag_msg); debug_create("mem", 0444, dent, debug_read_mem); - debug_create("version", 0444, dent, debug_read_version); + debug_create("version", 0444, dent, debug_read_smd_version); debug_create("tbl", 0444, dent, debug_read_alloc_tbl); + debug_create("modem_err", 0444, dent, debug_modem_err); + debug_create("modem_err_f3", 0444, dent, debug_modem_err_f3); + debug_create("print_diag", 0444, dent, debug_diag); + debug_create("print_f3", 0444, dent, debug_f3); + + /* NNV: this is google only stuff */ debug_create("build", 0444, dent, debug_read_build_id); return 0; } +static int __init smsm_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("smsm", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debug_create("state", 0444, dent, debug_read_smsm_state); + debug_create("intr_mask", 0444, dent, debug_read_intr_mask); + debug_create("intr_mux", 0444, dent, debug_read_intr_mux); + debug_create("version", 0444, dent, debug_read_smem_version); + debug_create("smsm_test", 0444, dent, debug_test_smsm); + + return 0; +} + late_initcall(smd_debugfs_init); +late_initcall(smsm_debugfs_init); #endif @@ -265,38 +700,29 @@ struct tramp_gpio_smem { uint32_t polarity[NUM_GPIO_INT_REGISTERS]; }; - -void smsm_print_sleep_info(void) +/* + * Print debug information on shared memory sleep variables + */ +void smsm_print_sleep_info(uint32_t sleep_delay, uint32_t sleep_limit, + uint32_t irq_mask, uint32_t wakeup_reason, uint32_t pending_irqs) { unsigned long flags; uint32_t *ptr; -#ifndef CONFIG_ARCH_MSM_SCORPION struct tramp_gpio_smem *gpio; - struct smsm_interrupt_info *int_info; -#endif - spin_lock_irqsave(&smem_lock, flags); - ptr = smem_alloc(SMEM_SMSM_SLEEP_DELAY, sizeof(*ptr)); - if (ptr) - pr_info("SMEM_SMSM_SLEEP_DELAY: %x\n", *ptr); - - ptr = smem_alloc(SMEM_SMSM_LIMIT_SLEEP, sizeof(*ptr)); - if (ptr) - pr_info("SMEM_SMSM_LIMIT_SLEEP: %x\n", *ptr); + pr_info("SMEM_SMSM_SLEEP_DELAY: %x\n", sleep_delay); + pr_info("SMEM_SMSM_LIMIT_SLEEP: %x\n", sleep_limit); ptr = smem_alloc(SMEM_SLEEP_POWER_COLLAPSE_DISABLED, sizeof(*ptr)); if (ptr) pr_info("SMEM_SLEEP_POWER_COLLAPSE_DISABLED: %x\n", *ptr); + else + pr_info("SMEM_SLEEP_POWER_COLLAPSE_DISABLED: missing\n"); -#ifndef CONFIG_ARCH_MSM_SCORPION - int_info = smem_alloc(SMEM_SMSM_INT_INFO, sizeof(*int_info)); - if (int_info) - pr_info("SMEM_SMSM_INT_INFO %x %x %x\n", - int_info->interrupt_mask, - int_info->pending_interrupts, - int_info->wakeup_reason); + pr_info("SMEM_SMSM_INT_INFO %x %x %x\n", + irq_mask, pending_irqs, wakeup_reason); gpio = smem_alloc(SMEM_GPIO_INT, sizeof(*gpio)); if (gpio) { @@ -310,9 +736,8 @@ void smsm_print_sleep_info(void) pr_info("SMEM_GPIO_INT: %d: f %d: %d %d...\n", i, gpio->num_fired[i], gpio->fired[i][0], gpio->fired[i][1]); - } -#else -#endif + } else + pr_info("SMEM_GPIO_INT: missing\n"); + spin_unlock_irqrestore(&smem_lock, flags); } - diff --git a/arch/arm/mach-msm/smd_nmea.c b/arch/arm/mach-msm/smd_nmea.c new file mode 100644 index 00000000000..1aedbf5e1c1 --- /dev/null +++ b/arch/arm/mach-msm/smd_nmea.c @@ -0,0 +1,205 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * SMD NMEA Driver -- Provides GPS NMEA device to SMD port interface. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MAX_BUF_SIZE 200 + +static DEFINE_MUTEX(nmea_ch_lock); +static DEFINE_MUTEX(nmea_rx_buf_lock); + +static DECLARE_WAIT_QUEUE_HEAD(nmea_wait_queue); + +struct nmea_device_t { + struct miscdevice misc; + + struct smd_channel *ch; + + unsigned char rx_buf[MAX_BUF_SIZE]; + unsigned int bytes_read; +}; + +struct nmea_device_t *nmea_devp; + +static void nmea_work_func(struct work_struct *ws) +{ + int sz; + + for (;;) { + sz = smd_cur_packet_size(nmea_devp->ch); + if (sz == 0) + break; + if (sz > smd_read_avail(nmea_devp->ch)) + break; + if (sz > MAX_BUF_SIZE) { + smd_read(nmea_devp->ch, 0, sz); + continue; + } + + mutex_lock(&nmea_rx_buf_lock); + if (smd_read(nmea_devp->ch, nmea_devp->rx_buf, sz) != sz) { + mutex_unlock(&nmea_rx_buf_lock); + printk(KERN_ERR "nmea: not enough data?!\n"); + continue; + } + nmea_devp->bytes_read = sz; + mutex_unlock(&nmea_rx_buf_lock); + wake_up_interruptible(&nmea_wait_queue); + } +} + +struct workqueue_struct *nmea_wq; +static DECLARE_WORK(nmea_work, nmea_work_func); + +static void nmea_notify(void *priv, unsigned event) +{ + switch (event) { + case SMD_EVENT_DATA: { + int sz; + sz = smd_cur_packet_size(nmea_devp->ch); + if ((sz > 0) && (sz <= smd_read_avail(nmea_devp->ch))) + queue_work(nmea_wq, &nmea_work); + break; + } + case SMD_EVENT_OPEN: + printk(KERN_INFO "nmea: smd opened\n"); + break; + case SMD_EVENT_CLOSE: + printk(KERN_INFO "nmea: smd closed\n"); + break; + } +} + +static ssize_t nmea_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + int r; + int bytes_read; + + r = wait_event_interruptible(nmea_wait_queue, + nmea_devp->bytes_read); + if (r < 0) { + /* qualify error message */ + if (r != -ERESTARTSYS) { + /* we get this anytime a signal comes in */ + printk(KERN_ERR "ERROR:%s:%i:%s: " + "wait_event_interruptible ret %i\n", + __FILE__, + __LINE__, + __func__, + r + ); + } + return r; + } + + mutex_lock(&nmea_rx_buf_lock); + bytes_read = nmea_devp->bytes_read; + nmea_devp->bytes_read = 0; + r = copy_to_user(buf, nmea_devp->rx_buf, bytes_read); + mutex_unlock(&nmea_rx_buf_lock); + + if (r > 0) { + printk(KERN_ERR "ERROR:%s:%i:%s: " + "copy_to_user could not copy %i bytes.\n", + __FILE__, + __LINE__, + __func__, + r); + return r; + } + + return bytes_read; +} + +static int nmea_open(struct inode *ip, struct file *fp) +{ + int r = 0; + + mutex_lock(&nmea_ch_lock); + if (nmea_devp->ch == 0) + r = smd_open("GPSNMEA", &nmea_devp->ch, nmea_devp, nmea_notify); + mutex_unlock(&nmea_ch_lock); + + return r; +} + +static int nmea_release(struct inode *ip, struct file *fp) +{ + int r = 0; + + mutex_lock(&nmea_ch_lock); + if (nmea_devp->ch != 0) { + r = smd_close(nmea_devp->ch); + nmea_devp->ch = 0; + } + mutex_unlock(&nmea_ch_lock); + + return r; +} + +static const struct file_operations nmea_fops = { + .owner = THIS_MODULE, + .read = nmea_read, + .open = nmea_open, + .release = nmea_release, +}; + +static struct nmea_device_t nmea_device = { + .misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "nmea", + .fops = &nmea_fops, + } +}; + +static void __exit nmea_exit(void) +{ + destroy_workqueue(nmea_wq); + misc_deregister(&nmea_device.misc); +} + +static int __init nmea_init(void) +{ + int ret; + + nmea_device.bytes_read = 0; + nmea_devp = &nmea_device; + + nmea_wq = create_singlethread_workqueue("nmea"); + if (nmea_wq == 0) + return -ENOMEM; + + ret = misc_register(&nmea_device.misc); + return ret; +} + +module_init(nmea_init); +module_exit(nmea_exit); + +MODULE_DESCRIPTION("MSM Shared Memory NMEA Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/smd_pkt.c b/arch/arm/mach-msm/smd_pkt.c new file mode 100644 index 00000000000..781d4fcbdd8 --- /dev/null +++ b/arch/arm/mach-msm/smd_pkt.c @@ -0,0 +1,864 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * SMD Packet Driver -- Provides a binary SMD non-muxed packet port + * interface. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "smd_private.h" +#ifdef CONFIG_ARCH_FSM9XXX +#define NUM_SMD_PKT_PORTS 4 +#else +#define NUM_SMD_PKT_PORTS 12 +#endif + +#define LOOPBACK_INX (NUM_SMD_PKT_PORTS - 1) + +#define DEVICE_NAME "smdpkt" + +struct smd_pkt_dev { + struct cdev cdev; + struct device *devicep; + void *pil; + struct platform_driver driver; + + struct smd_channel *ch; + struct mutex ch_lock; + struct mutex rx_lock; + struct mutex tx_lock; + wait_queue_head_t ch_read_wait_queue; + wait_queue_head_t ch_write_wait_queue; + wait_queue_head_t ch_opened_wait_queue; + + int i; + + int blocking_write; + int needed_space; + int is_open; + unsigned ch_size; + uint open_modem_wait; + + int has_reset; + int do_reset_notification; + struct completion ch_allocated; + +} *smd_pkt_devp[NUM_SMD_PKT_PORTS]; + +struct class *smd_pkt_classp; +static dev_t smd_pkt_number; +static struct delayed_work loopback_work; +static void check_and_wakeup_reader(struct smd_pkt_dev *smd_pkt_devp); +static void check_and_wakeup_writer(struct smd_pkt_dev *smd_pkt_devp); +static uint32_t is_modem_smsm_inited(void); + +static int msm_smd_pkt_debug_mask; +module_param_named(debug_mask, msm_smd_pkt_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); +#define DEBUG + +#ifdef DEBUG +#define D_DUMP_BUFFER(prestr, cnt, buf) \ +do { \ + if (msm_smd_pkt_debug_mask) \ + print_hex_dump(KERN_DEBUG, prestr, \ + DUMP_PREFIX_NONE, 16, 1, \ + buf, cnt, 1); \ +} while (0) +#else +#define D_DUMP_BUFFER(prestr, cnt, buf) do {} while (0) +#endif + +#ifdef DEBUG +#define D(x...) if (msm_smd_pkt_debug_mask) printk(x) +#else +#define D(x...) do {} while (0) +#endif + +static ssize_t open_timeout_store(struct device *d, + struct device_attribute *attr, + const char *buf, + size_t n) +{ + int i; + unsigned long tmp; + for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) { + if (smd_pkt_devp[i]->devicep == d) + break; + } + if (!strict_strtoul(buf, 10, &tmp)) { + smd_pkt_devp[i]->open_modem_wait = tmp; + return n; + } else { + pr_err("%s: unable to convert: %s to an int\n", __func__, + buf); + return -EINVAL; + } +} + +static ssize_t open_timeout_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + int i; + for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) { + if (smd_pkt_devp[i]->devicep == d) + break; + } + return sprintf(buf, "%d\n", smd_pkt_devp[i]->open_modem_wait); +} + +static DEVICE_ATTR(open_timeout, 0664, open_timeout_show, open_timeout_store); + +static int notify_reset(struct smd_pkt_dev *smd_pkt_devp) +{ + smd_pkt_devp->do_reset_notification = 0; + + return -ENETRESET; +} + +static void clean_and_signal(struct smd_pkt_dev *smd_pkt_devp) +{ + smd_pkt_devp->do_reset_notification = 1; + smd_pkt_devp->has_reset = 1; + + smd_pkt_devp->is_open = 0; + + wake_up_interruptible(&smd_pkt_devp->ch_read_wait_queue); + wake_up_interruptible(&smd_pkt_devp->ch_write_wait_queue); + wake_up_interruptible(&smd_pkt_devp->ch_opened_wait_queue); +} + +static void loopback_probe_worker(struct work_struct *work) +{ + + /* Wait for the modem SMSM to be inited for the SMD + ** Loopback channel to be allocated at the modem. Since + ** the wait need to be done atmost once, using msleep + ** doesn't degrade the performance. */ + if (!is_modem_smsm_inited()) + schedule_delayed_work(&loopback_work, msecs_to_jiffies(1000)); + else + smsm_change_state(SMSM_APPS_STATE, + 0, SMSM_SMD_LOOPBACK); + +} + +static long smd_pkt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret; + struct smd_pkt_dev *smd_pkt_devp; + + smd_pkt_devp = file->private_data; + if (!smd_pkt_devp) + return -EINVAL; + + switch (cmd) { + case TIOCMGET: + ret = smd_tiocmget(smd_pkt_devp->ch); + break; + case TIOCMSET: + ret = smd_tiocmset(smd_pkt_devp->ch, arg, ~arg); + break; + case SMD_PKT_IOCTL_BLOCKING_WRITE: + ret = get_user(smd_pkt_devp->blocking_write, (int *)arg); + break; + default: + ret = -1; + } + + return ret; +} + +ssize_t smd_pkt_read(struct file *file, + char __user *buf, + size_t count, + loff_t *ppos) +{ + int r; + int bytes_read; + struct smd_pkt_dev *smd_pkt_devp; + struct smd_channel *chl; + + D(KERN_ERR "%s: read %i bytes\n", + __func__, count); + + smd_pkt_devp = file->private_data; + + if (!smd_pkt_devp || !smd_pkt_devp->ch) + return -EINVAL; + + if (smd_pkt_devp->do_reset_notification) { + /* notify client that a reset occurred */ + return notify_reset(smd_pkt_devp); + } + + chl = smd_pkt_devp->ch; +wait_for_packet: + r = wait_event_interruptible(smd_pkt_devp->ch_read_wait_queue, + (smd_cur_packet_size(chl) > 0 && + smd_read_avail(chl) >= + smd_cur_packet_size(chl)) || + smd_pkt_devp->has_reset); + + if (smd_pkt_devp->has_reset) + return notify_reset(smd_pkt_devp); + + if (r < 0) { + /* qualify error message */ + if (r != -ERESTARTSYS) { + /* we get this anytime a signal comes in */ + printk(KERN_ERR "ERROR:%s:%i:%s: " + "wait_event_interruptible ret %i\n", + __FILE__, + __LINE__, + __func__, + r + ); + } + return r; + } + + /* Here we have a whole packet waiting for us */ + + mutex_lock(&smd_pkt_devp->rx_lock); + bytes_read = smd_cur_packet_size(smd_pkt_devp->ch); + + D(KERN_ERR "%s: after wait_event_interruptible bytes_read = %i\n", + __func__, bytes_read); + + if (bytes_read == 0 || + bytes_read < smd_read_avail(smd_pkt_devp->ch)) { + D(KERN_ERR "%s: Nothing to read\n", __func__); + mutex_unlock(&smd_pkt_devp->rx_lock); + goto wait_for_packet; + } + + if (bytes_read > count) { + printk(KERN_ERR "packet size %i > buffer size %i, " + "dropping packet!", bytes_read, count); + smd_read(smd_pkt_devp->ch, 0, bytes_read); + mutex_unlock(&smd_pkt_devp->rx_lock); + return -EINVAL; + } + + if (smd_read_user_buffer(smd_pkt_devp->ch, buf, bytes_read) + != bytes_read) { + mutex_unlock(&smd_pkt_devp->rx_lock); + if (smd_pkt_devp->has_reset) + return notify_reset(smd_pkt_devp); + + printk(KERN_ERR "user read: not enough data?!\n"); + return -EINVAL; + } + D_DUMP_BUFFER("read: ", bytes_read, buf); + mutex_unlock(&smd_pkt_devp->rx_lock); + + D(KERN_ERR "%s: just read %i bytes\n", + __func__, bytes_read); + + /* check and wakeup read threads waiting on this device */ + check_and_wakeup_reader(smd_pkt_devp); + + return bytes_read; +} + +ssize_t smd_pkt_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int r = 0; + struct smd_pkt_dev *smd_pkt_devp; + DEFINE_WAIT(write_wait); + + D(KERN_ERR "%s: writting %i bytes\n", + __func__, count); + + smd_pkt_devp = file->private_data; + + if (!smd_pkt_devp || !smd_pkt_devp->ch) + return -EINVAL; + + if (count > smd_pkt_devp->ch_size) + return -EINVAL; + + if (smd_pkt_devp->do_reset_notification) { + /* notify client that a reset occurred */ + return notify_reset(smd_pkt_devp); + } + + if (smd_pkt_devp->blocking_write) { + for (;;) { + mutex_lock(&smd_pkt_devp->tx_lock); + if (smd_pkt_devp->has_reset) { + smd_disable_read_intr(smd_pkt_devp->ch); + mutex_unlock(&smd_pkt_devp->tx_lock); + return notify_reset(smd_pkt_devp); + } + if (signal_pending(current)) { + smd_disable_read_intr(smd_pkt_devp->ch); + mutex_unlock(&smd_pkt_devp->tx_lock); + return -ERESTARTSYS; + } + + prepare_to_wait(&smd_pkt_devp->ch_write_wait_queue, + &write_wait, TASK_INTERRUPTIBLE); + smd_enable_read_intr(smd_pkt_devp->ch); + if (smd_write_avail(smd_pkt_devp->ch) < count) { + if (!smd_pkt_devp->needed_space || + count < smd_pkt_devp->needed_space) + smd_pkt_devp->needed_space = count; + mutex_unlock(&smd_pkt_devp->tx_lock); + schedule(); + } else + break; + } + finish_wait(&smd_pkt_devp->ch_write_wait_queue, &write_wait); + smd_disable_read_intr(smd_pkt_devp->ch); + if (smd_pkt_devp->has_reset) { + mutex_unlock(&smd_pkt_devp->tx_lock); + return notify_reset(smd_pkt_devp); + } + if (signal_pending(current)) { + mutex_unlock(&smd_pkt_devp->tx_lock); + return -ERESTARTSYS; + } + } else { + if (smd_pkt_devp->has_reset) + return notify_reset(smd_pkt_devp); + if (signal_pending(current)) + return -ERESTARTSYS; + + mutex_lock(&smd_pkt_devp->tx_lock); + if (smd_write_avail(smd_pkt_devp->ch) < count) { + D(KERN_ERR "%s: Not enough space to write\n", + __func__); + mutex_unlock(&smd_pkt_devp->tx_lock); + return -ENOMEM; + } + } + + D_DUMP_BUFFER("write: ", count, buf); + + smd_pkt_devp->needed_space = 0; + + r = smd_write_user_buffer(smd_pkt_devp->ch, buf, count); + if (r != count) { + mutex_unlock(&smd_pkt_devp->tx_lock); + if (smd_pkt_devp->has_reset) + return notify_reset(smd_pkt_devp); + + printk(KERN_ERR "ERROR:%s:%i:%s: " + "smd_write(ch,buf,count = %i) ret %i.\n", + __FILE__, + __LINE__, + __func__, + count, + r); + return r; + } + mutex_unlock(&smd_pkt_devp->tx_lock); + + D(KERN_ERR "%s: just wrote %i bytes\n", + __func__, count); + + return count; +} + +static unsigned int smd_pkt_poll(struct file *file, poll_table *wait) +{ + struct smd_pkt_dev *smd_pkt_devp; + unsigned int mask = 0; + + smd_pkt_devp = file->private_data; + if (!smd_pkt_devp) + return POLLERR; + + poll_wait(file, &smd_pkt_devp->ch_read_wait_queue, wait); + if (smd_read_avail(smd_pkt_devp->ch)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static void check_and_wakeup_reader(struct smd_pkt_dev *smd_pkt_devp) +{ + int sz; + + if (!smd_pkt_devp || !smd_pkt_devp->ch) + return; + + sz = smd_cur_packet_size(smd_pkt_devp->ch); + if (sz == 0) { + D(KERN_ERR "%s: packet size is 0\n", __func__); + return; + } + if (sz > smd_read_avail(smd_pkt_devp->ch)) { + D(KERN_ERR "%s: packet size is %i - " + "the whole packet isn't here\n", + __func__, sz); + return; + } + + /* here we have a packet of size sz ready */ + wake_up_interruptible(&smd_pkt_devp->ch_read_wait_queue); + D(KERN_ERR "%s: after wake_up\n", __func__); +} + +static void check_and_wakeup_writer(struct smd_pkt_dev *smd_pkt_devp) +{ + int sz; + + if (!smd_pkt_devp || !smd_pkt_devp->ch) + return; + + sz = smd_write_avail(smd_pkt_devp->ch); + if (sz >= smd_pkt_devp->needed_space) { + D(KERN_ERR "%s: %d bytes Write Space available\n", + __func__, sz); + smd_disable_read_intr(smd_pkt_devp->ch); + wake_up_interruptible(&smd_pkt_devp->ch_write_wait_queue); + } +} + +static void ch_notify(void *priv, unsigned event) +{ + struct smd_pkt_dev *smd_pkt_devp = priv; + + if (smd_pkt_devp->ch == 0) + return; + + switch (event) { + case SMD_EVENT_DATA: { + D(KERN_ERR "%s: data\n", __func__); + check_and_wakeup_reader(smd_pkt_devp); + if (smd_pkt_devp->blocking_write) + check_and_wakeup_writer(smd_pkt_devp); + D(KERN_ERR "%s: data after check_and_wakeup\n", __func__); + break; + } + case SMD_EVENT_OPEN: + D(KERN_ERR "%s: smd opened\n", + __func__); + + smd_pkt_devp->has_reset = 0; + smd_pkt_devp->is_open = 1; + wake_up_interruptible(&smd_pkt_devp->ch_opened_wait_queue); + break; + case SMD_EVENT_CLOSE: + smd_pkt_devp->is_open = 0; + printk(KERN_ERR "%s: smd closed\n", + __func__); + + /* put port into reset state */ + clean_and_signal(smd_pkt_devp); + if (smd_pkt_devp->i == LOOPBACK_INX) + schedule_delayed_work(&loopback_work, + msecs_to_jiffies(1000)); + break; + } +} + +#ifdef CONFIG_ARCH_FSM9XXX +static char *smd_pkt_dev_name[] = { + "smdcntl1", + "smdcntl2", + "smd22", + "smd_pkt_loopback", +}; + +static char *smd_ch_name[] = { + "DATA6_CNTL", + "DATA7_CNTL", + "DATA22", + "LOOPBACK", +}; + +static uint32_t smd_ch_edge[] = { + SMD_APPS_QDSP, + SMD_APPS_QDSP, + SMD_APPS_QDSP, + SMD_APPS_QDSP +}; +#else +static char *smd_pkt_dev_name[] = { + "smdcntl0", + "smdcntl1", + "smdcntl2", + "smdcntl3", + "smdcntl4", + "smdcntl5", + "smdcntl6", + "smdcntl7", + "smd22", + "smd_sns_dsps", + "apr_apps_user", + "smd_pkt_loopback", +}; + +static char *smd_ch_name[] = { + "DATA5_CNTL", + "DATA6_CNTL", + "DATA7_CNTL", + "DATA8_CNTL", + "DATA9_CNTL", + "DATA12_CNTL", + "DATA13_CNTL", + "DATA14_CNTL", + "DATA22", + "SENSOR", + "apr_apps_user", + "LOOPBACK", +}; + +static uint32_t smd_ch_edge[] = { + SMD_APPS_MODEM, + SMD_APPS_MODEM, + SMD_APPS_MODEM, + SMD_APPS_MODEM, + SMD_APPS_MODEM, + SMD_APPS_MODEM, + SMD_APPS_MODEM, + SMD_APPS_MODEM, + SMD_APPS_MODEM, + SMD_APPS_DSPS, + SMD_APPS_QDSP, + SMD_APPS_MODEM, +}; +#endif + +static int smd_pkt_dummy_probe(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < NUM_SMD_PKT_PORTS; i++) { + if (!strcmp(pdev->name, smd_ch_name[i])) { + complete_all(&smd_pkt_devp[i]->ch_allocated); + break; + } + } + return 0; +} + +static uint32_t is_modem_smsm_inited(void) +{ + uint32_t modem_state; + uint32_t ready_state = (SMSM_INIT | SMSM_SMDINIT); + + modem_state = smsm_get_state(SMSM_MODEM_STATE); + return (modem_state & ready_state) == ready_state; +} + +int smd_pkt_open(struct inode *inode, struct file *file) +{ + int r = 0; + struct smd_pkt_dev *smd_pkt_devp; + char *peripheral = NULL; + + smd_pkt_devp = container_of(inode->i_cdev, struct smd_pkt_dev, cdev); + + if (!smd_pkt_devp) + return -EINVAL; + + file->private_data = smd_pkt_devp; + + mutex_lock(&smd_pkt_devp->ch_lock); + if (smd_pkt_devp->ch == 0) { + + if (smd_ch_edge[smd_pkt_devp->i] == SMD_APPS_MODEM) + peripheral = "modem"; + else if (smd_ch_edge[smd_pkt_devp->i] == SMD_APPS_QDSP) + peripheral = "q6"; + + if (peripheral) { + smd_pkt_devp->pil = pil_get(peripheral); + if (IS_ERR(smd_pkt_devp->pil)) { + r = PTR_ERR(smd_pkt_devp->pil); + goto out; + } + + /* Wait for the modem SMSM to be inited for the SMD + ** Loopback channel to be allocated at the modem. Since + ** the wait need to be done atmost once, using msleep + ** doesn't degrade the performance. */ + if (!strcmp(smd_ch_name[smd_pkt_devp->i], "LOOPBACK")) { + if (!is_modem_smsm_inited()) + msleep(5000); + smsm_change_state(SMSM_APPS_STATE, + 0, SMSM_SMD_LOOPBACK); + msleep(100); + } + + /* + * Wait for a packet channel to be allocated so we know + * the modem is ready enough. + */ + if (smd_pkt_devp->open_modem_wait) { + r = wait_for_completion_interruptible_timeout( + &smd_pkt_devp->ch_allocated, + msecs_to_jiffies( + smd_pkt_devp->open_modem_wait + * 1000)); + if (r == 0) + r = -ETIMEDOUT; + if (r < 0) { + pr_err("%s: wait failed for smd port:" + " %d\n", __func__, r); + goto release_pil; + } + } + } + + r = smd_named_open_on_edge(smd_ch_name[smd_pkt_devp->i], + smd_ch_edge[smd_pkt_devp->i], + &smd_pkt_devp->ch, + smd_pkt_devp, + ch_notify); + if (r < 0) { + pr_err("%s: %s open failed %d\n", __func__, + smd_ch_name[smd_pkt_devp->i], r); + goto release_pil; + } + + r = wait_event_interruptible_timeout( + smd_pkt_devp->ch_opened_wait_queue, + smd_pkt_devp->is_open, (2 * HZ)); + if (r == 0) + r = -ETIMEDOUT; + + if (r < 0) { + pr_err("%s: wait failed for smd open: %d\n", + __func__, r); + } else if (!smd_pkt_devp->is_open) { + pr_err("%s: Invalid open notification\n", __func__); + r = -ENODEV; + } else { + smd_disable_read_intr(smd_pkt_devp->ch); + smd_pkt_devp->ch_size = + smd_write_avail(smd_pkt_devp->ch); + r = 0; + } + } +release_pil: + if (peripheral && (r < 0)) + pil_put(smd_pkt_devp->pil); +out: + mutex_unlock(&smd_pkt_devp->ch_lock); + + return r; +} + +int smd_pkt_release(struct inode *inode, struct file *file) +{ + int r = 0; + struct smd_pkt_dev *smd_pkt_devp = file->private_data; + + if (!smd_pkt_devp) + return -EINVAL; + + clean_and_signal(smd_pkt_devp); + + mutex_lock(&smd_pkt_devp->ch_lock); + if (smd_pkt_devp->ch != 0) { + r = smd_close(smd_pkt_devp->ch); + smd_pkt_devp->ch = 0; + smd_pkt_devp->blocking_write = 0; + if (smd_pkt_devp->pil) + pil_put(smd_pkt_devp->pil); + } + mutex_unlock(&smd_pkt_devp->ch_lock); + + smd_pkt_devp->has_reset = 0; + smd_pkt_devp->do_reset_notification = 0; + + return r; +} + +static const struct file_operations smd_pkt_fops = { + .owner = THIS_MODULE, + .open = smd_pkt_open, + .release = smd_pkt_release, + .read = smd_pkt_read, + .write = smd_pkt_write, + .poll = smd_pkt_poll, + .unlocked_ioctl = smd_pkt_ioctl, +}; + +static int __init smd_pkt_init(void) +{ + int i; + int r; + + r = alloc_chrdev_region(&smd_pkt_number, + 0, + NUM_SMD_PKT_PORTS, + DEVICE_NAME); + if (IS_ERR_VALUE(r)) { + printk(KERN_ERR "ERROR:%s:%i:%s: " + "alloc_chrdev_region() ret %i.\n", + __FILE__, + __LINE__, + __func__, + r); + goto error0; + } + + smd_pkt_classp = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(smd_pkt_classp)) { + printk(KERN_ERR "ERROR:%s:%i:%s: " + "class_create() ENOMEM\n", + __FILE__, + __LINE__, + __func__); + r = -ENOMEM; + goto error1; + } + + for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) { + smd_pkt_devp[i] = kzalloc(sizeof(struct smd_pkt_dev), + GFP_KERNEL); + if (IS_ERR(smd_pkt_devp[i])) { + printk(KERN_ERR "ERROR:%s:%i:%s kmalloc() ENOMEM\n", + __FILE__, + __LINE__, + __func__); + r = -ENOMEM; + goto error2; + } + + smd_pkt_devp[i]->i = i; + + init_waitqueue_head(&smd_pkt_devp[i]->ch_read_wait_queue); + init_waitqueue_head(&smd_pkt_devp[i]->ch_write_wait_queue); + smd_pkt_devp[i]->is_open = 0; + init_waitqueue_head(&smd_pkt_devp[i]->ch_opened_wait_queue); + + mutex_init(&smd_pkt_devp[i]->ch_lock); + mutex_init(&smd_pkt_devp[i]->rx_lock); + mutex_init(&smd_pkt_devp[i]->tx_lock); + init_completion(&smd_pkt_devp[i]->ch_allocated); + + cdev_init(&smd_pkt_devp[i]->cdev, &smd_pkt_fops); + smd_pkt_devp[i]->cdev.owner = THIS_MODULE; + + r = cdev_add(&smd_pkt_devp[i]->cdev, + (smd_pkt_number + i), + 1); + + if (IS_ERR_VALUE(r)) { + printk(KERN_ERR "%s:%i:%s: cdev_add() ret %i\n", + __FILE__, + __LINE__, + __func__, + r); + kfree(smd_pkt_devp[i]); + goto error2; + } + + smd_pkt_devp[i]->devicep = + device_create(smd_pkt_classp, + NULL, + (smd_pkt_number + i), + NULL, + smd_pkt_dev_name[i]); + + if (IS_ERR(smd_pkt_devp[i]->devicep)) { + printk(KERN_ERR "%s:%i:%s: " + "device_create() ENOMEM\n", + __FILE__, + __LINE__, + __func__); + r = -ENOMEM; + cdev_del(&smd_pkt_devp[i]->cdev); + kfree(smd_pkt_devp[i]); + goto error2; + } + if (device_create_file(smd_pkt_devp[i]->devicep, + &dev_attr_open_timeout)) + pr_err("%s: unable to create device attr on #%d\n", + __func__, i); + + smd_pkt_devp[i]->driver.probe = smd_pkt_dummy_probe; + smd_pkt_devp[i]->driver.driver.name = smd_ch_name[i]; + smd_pkt_devp[i]->driver.driver.owner = THIS_MODULE; + r = platform_driver_register(&smd_pkt_devp[i]->driver); + if (r) + goto error2; + } + + INIT_DELAYED_WORK(&loopback_work, loopback_probe_worker); + + D(KERN_INFO "SMD Packet Port Driver Initialized.\n"); + return 0; + + error2: + if (i > 0) { + while (--i >= 0) { + platform_driver_unregister(&smd_pkt_devp[i]->driver); + cdev_del(&smd_pkt_devp[i]->cdev); + kfree(smd_pkt_devp[i]); + device_destroy(smd_pkt_classp, + MKDEV(MAJOR(smd_pkt_number), i)); + } + } + + class_destroy(smd_pkt_classp); + error1: + unregister_chrdev_region(MAJOR(smd_pkt_number), NUM_SMD_PKT_PORTS); + error0: + return r; +} + +static void __exit smd_pkt_cleanup(void) +{ + int i; + + for (i = 0; i < NUM_SMD_PKT_PORTS; ++i) { + platform_driver_unregister(&smd_pkt_devp[i]->driver); + cdev_del(&smd_pkt_devp[i]->cdev); + kfree(smd_pkt_devp[i]); + device_destroy(smd_pkt_classp, + MKDEV(MAJOR(smd_pkt_number), i)); + } + + class_destroy(smd_pkt_classp); + + unregister_chrdev_region(MAJOR(smd_pkt_number), NUM_SMD_PKT_PORTS); +} + +module_init(smd_pkt_init); +module_exit(smd_pkt_cleanup); + +MODULE_DESCRIPTION("MSM Shared Memory Packet Port"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/smd_private.h b/arch/arm/mach-msm/smd_private.h index 727bfe68aa9..6db86251a69 100644 --- a/arch/arm/mach-msm/smd_private.h +++ b/arch/arm/mach-msm/smd_private.h @@ -1,7 +1,7 @@ /* arch/arm/mach-msm/smd_private.h * * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2007 QUALCOMM Incorporated + * Copyright (c) 2007-2011, Code Aurora Forum. 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 @@ -16,12 +16,21 @@ #ifndef _ARCH_ARM_MACH_MSM_MSM_SMD_PRIVATE_H_ #define _ARCH_ARM_MACH_MSM_MSM_SMD_PRIVATE_H_ -#include +#include #include -#include -#include +#include -#include +#define PC_APPS 0 +#define PC_MODEM 1 + +#define VERSION_QDSP6 4 +#define VERSION_APPS_SBL 6 +#define VERSION_MODEM_SBL 7 +#define VERSION_APPS 8 +#define VERSION_MODEM 9 +#define VERSION_DSPS 10 + +#define SMD_HEAP_SIZE 512 struct smem_heap_info { unsigned initialized; @@ -44,47 +53,36 @@ struct smem_proc_comm { unsigned data2; }; -#define PC_APPS 0 -#define PC_MODEM 1 - -#define VERSION_SMD 0 -#define VERSION_QDSP6 4 -#define VERSION_APPS_SBL 6 -#define VERSION_MODEM_SBL 7 -#define VERSION_APPS 8 -#define VERSION_MODEM 9 - struct smem_shared { struct smem_proc_comm proc_comm[4]; unsigned version[32]; struct smem_heap_info heap_info; - struct smem_heap_entry heap_toc[512]; + struct smem_heap_entry heap_toc[SMD_HEAP_SIZE]; }; -#define SMSM_V1_SIZE (sizeof(unsigned) * 8) -#define SMSM_V2_SIZE (sizeof(unsigned) * 4) - -#ifdef CONFIG_MSM_SMD_PKG3 +#if defined(CONFIG_MSM_SMD_PKG4) struct smsm_interrupt_info { - uint32_t interrupt_mask; - uint32_t pending_interrupts; - uint32_t wakeup_reason; + uint32_t aArm_en_mask; + uint32_t aArm_interrupts_pending; + uint32_t aArm_wakeup_reason; + uint32_t aArm_rpc_prog; + uint32_t aArm_rpc_proc; + char aArm_smd_port_name[20]; + uint32_t aArm_gpio_info; }; -#else -#define DEM_MAX_PORT_NAME_LEN (20) -struct msm_dem_slave_data { - uint32_t sleep_time; - uint32_t interrupt_mask; - uint32_t resources_used; - uint32_t reserved1; - - uint32_t wakeup_reason; - uint32_t pending_interrupts; - uint32_t rpc_prog; - uint32_t rpc_proc; - char smd_port_name[DEM_MAX_PORT_NAME_LEN]; - uint32_t reserved2; +#elif defined(CONFIG_MSM_SMD_PKG3) +struct smsm_interrupt_info { + uint32_t aArm_en_mask; + uint32_t aArm_interrupts_pending; + uint32_t aArm_wakeup_reason; }; +#elif !defined(CONFIG_MSM_SMD) +void *smem_alloc(unsigned id, unsigned size) +{ + return NULL; +} +#else +#error No SMD Package Specified; aborting #endif #define SZ_DIAG_ERR_MSG 0xC8 @@ -93,167 +91,34 @@ struct msm_dem_slave_data { #define ID_SHARED_STATE SMEM_SMSM_SHARED_STATE #define ID_CH_ALLOC_TBL SMEM_CHANNEL_ALLOC_TBL -#define SMSM_INIT 0x00000001 -#define SMSM_SMDINIT 0x00000008 -#define SMSM_RPCINIT 0x00000020 -#define SMSM_RESET 0x00000040 -#define SMSM_RSA 0x00000080 -#define SMSM_RUN 0x00000100 -#define SMSM_PWRC 0x00000200 -#define SMSM_TIMEWAIT 0x00000400 -#define SMSM_TIMEINIT 0x00000800 -#define SMSM_PWRC_EARLY_EXIT 0x00001000 -#define SMSM_WFPI 0x00002000 -#define SMSM_SLEEP 0x00004000 -#define SMSM_SLEEPEXIT 0x00008000 -#define SMSM_APPS_REBOOT 0x00020000 -#define SMSM_SYSTEM_POWER_DOWN 0x00040000 -#define SMSM_SYSTEM_REBOOT 0x00080000 -#define SMSM_SYSTEM_DOWNLOAD 0x00100000 -#define SMSM_PWRC_SUSPEND 0x00200000 -#define SMSM_APPS_SHUTDOWN 0x00400000 -#define SMSM_SMD_LOOPBACK 0x00800000 -#define SMSM_RUN_QUIET 0x01000000 -#define SMSM_MODEM_WAIT 0x02000000 -#define SMSM_MODEM_BREAK 0x04000000 -#define SMSM_MODEM_CONTINUE 0x08000000 -#define SMSM_UNKNOWN 0x80000000 - -#define SMSM_WKUP_REASON_RPC 0x00000001 -#define SMSM_WKUP_REASON_INT 0x00000002 -#define SMSM_WKUP_REASON_GPIO 0x00000004 -#define SMSM_WKUP_REASON_TIMER 0x00000008 -#define SMSM_WKUP_REASON_ALARM 0x00000010 -#define SMSM_WKUP_REASON_RESET 0x00000020 - -#ifdef CONFIG_ARCH_MSM7X00A -enum smsm_state_item { - SMSM_STATE_APPS = 1, - SMSM_STATE_MODEM = 3, - SMSM_STATE_COUNT, -}; -#else -enum smsm_state_item { - SMSM_STATE_APPS, - SMSM_STATE_MODEM, - SMSM_STATE_HEXAGON, - SMSM_STATE_APPS_DEM, - SMSM_STATE_MODEM_DEM, - SMSM_STATE_QDSP6_DEM, - SMSM_STATE_POWER_MASTER_DEM, - SMSM_STATE_TIME_MASTER_DEM, - SMSM_STATE_COUNT, -}; -#endif - -void *smem_alloc(unsigned id, unsigned size); -int smsm_change_state(enum smsm_state_item item, uint32_t clear_mask, uint32_t set_mask); -uint32_t smsm_get_state(enum smsm_state_item item); -int smsm_set_sleep_duration(uint32_t delay); -void smsm_print_sleep_info(void); - -#define SMEM_NUM_SMD_CHANNELS 64 - -typedef enum { - /* fixed items */ - SMEM_PROC_COMM = 0, - SMEM_HEAP_INFO, - SMEM_ALLOCATION_TABLE, - SMEM_VERSION_INFO, - SMEM_HW_RESET_DETECT, - SMEM_AARM_WARM_BOOT, - SMEM_DIAG_ERR_MESSAGE, - SMEM_SPINLOCK_ARRAY, - SMEM_MEMORY_BARRIER_LOCATION, - - /* dynamic items */ - SMEM_AARM_PARTITION_TABLE, - SMEM_AARM_BAD_BLOCK_TABLE, - SMEM_RESERVE_BAD_BLOCKS, - SMEM_WM_UUID, - SMEM_CHANNEL_ALLOC_TBL, - SMEM_SMD_BASE_ID, - SMEM_SMEM_LOG_IDX = SMEM_SMD_BASE_ID + SMEM_NUM_SMD_CHANNELS, - SMEM_SMEM_LOG_EVENTS, - SMEM_SMEM_STATIC_LOG_IDX, - SMEM_SMEM_STATIC_LOG_EVENTS, - SMEM_SMEM_SLOW_CLOCK_SYNC, - SMEM_SMEM_SLOW_CLOCK_VALUE, - SMEM_BIO_LED_BUF, - SMEM_SMSM_SHARED_STATE, - SMEM_SMSM_INT_INFO, - SMEM_SMSM_SLEEP_DELAY, - SMEM_SMSM_LIMIT_SLEEP, - SMEM_SLEEP_POWER_COLLAPSE_DISABLED, - SMEM_KEYPAD_KEYS_PRESSED, - SMEM_KEYPAD_STATE_UPDATED, - SMEM_KEYPAD_STATE_IDX, - SMEM_GPIO_INT, - SMEM_MDDI_LCD_IDX, - SMEM_MDDI_HOST_DRIVER_STATE, - SMEM_MDDI_LCD_DISP_STATE, - SMEM_LCD_CUR_PANEL, - SMEM_MARM_BOOT_SEGMENT_INFO, - SMEM_AARM_BOOT_SEGMENT_INFO, - SMEM_SLEEP_STATIC, - SMEM_SCORPION_FREQUENCY, - SMEM_SMD_PROFILES, - SMEM_TSSC_BUSY, - SMEM_HS_SUSPEND_FILTER_INFO, - SMEM_BATT_INFO, - SMEM_APPS_BOOT_MODE, - SMEM_VERSION_FIRST, - SMEM_VERSION_LAST = SMEM_VERSION_FIRST + 24, - SMEM_OSS_RRCASN1_BUF1, - SMEM_OSS_RRCASN1_BUF2, - SMEM_ID_VENDOR0, - SMEM_ID_VENDOR1, - SMEM_ID_VENDOR2, - SMEM_HW_SW_BUILD_ID, - SMEM_SMD_BLOCK_PORT_BASE_ID, - SMEM_SMD_BLOCK_PORT_PROC0_HEAP = SMEM_SMD_BLOCK_PORT_BASE_ID + SMEM_NUM_SMD_CHANNELS, - SMEM_SMD_BLOCK_PORT_PROC1_HEAP = SMEM_SMD_BLOCK_PORT_PROC0_HEAP + SMEM_NUM_SMD_CHANNELS, - SMEM_I2C_MUTEX = SMEM_SMD_BLOCK_PORT_PROC1_HEAP + SMEM_NUM_SMD_CHANNELS, - SMEM_SCLK_CONVERSION, - SMEM_SMD_SMSM_INTR_MUX, - SMEM_SMSM_CPU_INTR_MASK, - SMEM_APPS_DEM_SLAVE_DATA, - SMEM_QDSP6_DEM_SLAVE_DATA, - SMEM_CLKREGIM_BSP, - SMEM_CLKREGIM_SOURCES, - SMEM_SMD_FIFO_BASE_ID, - SMEM_USABLE_RAM_PARTITION_TABLE = SMEM_SMD_FIFO_BASE_ID + SMEM_NUM_SMD_CHANNELS, - SMEM_POWER_ON_STATUS_INFO, - SMEM_DAL_AREA, - SMEM_SMEM_LOG_POWER_IDX, - SMEM_SMEM_LOG_POWER_WRAP, - SMEM_SMEM_LOG_POWER_EVENTS, - SMEM_ERR_CRASH_LOG, - SMEM_ERR_F3_TRACE_LOG, - SMEM_NUM_ITEMS, -} smem_mem_type; - - -#define SMD_SS_CLOSED 0x00000000 -#define SMD_SS_OPENING 0x00000001 -#define SMD_SS_OPENED 0x00000002 -#define SMD_SS_FLUSHING 0x00000003 -#define SMD_SS_CLOSING 0x00000004 -#define SMD_SS_RESET 0x00000005 -#define SMD_SS_RESET_OPENING 0x00000006 - -#define SMD_BUF_SIZE 8192 -#define SMD_CHANNELS 64 - -#define SMD_HEADER_SIZE 20 - +#define SMD_SS_CLOSED 0x00000000 +#define SMD_SS_OPENING 0x00000001 +#define SMD_SS_OPENED 0x00000002 +#define SMD_SS_FLUSHING 0x00000003 +#define SMD_SS_CLOSING 0x00000004 +#define SMD_SS_RESET 0x00000005 +#define SMD_SS_RESET_OPENING 0x00000006 + +#define SMD_BUF_SIZE 8192 +#define SMD_CHANNELS 64 +#define SMD_HEADER_SIZE 20 + +/* 'type' field of smd_alloc_elm structure + * has the following breakup + * bits 0-7 -> channel type + * bits 8-11 -> xfer type + * bits 12-31 -> reserved + */ struct smd_alloc_elm { char name[20]; uint32_t cid; - uint32_t ctype; + uint32_t type; uint32_t ref_count; }; +#define SMD_CHANNEL_TYPE(x) ((x) & 0x000000FF) +#define SMD_XFER_TYPE(x) (((x) & 0x00000F00) >> 8) + struct smd_half_channel { unsigned state; unsigned char fDSR; @@ -263,141 +128,14 @@ struct smd_half_channel { unsigned char fHEAD; unsigned char fTAIL; unsigned char fSTATE; - unsigned char fUNUSED; + unsigned char fBLOCKREADINTR; unsigned tail; unsigned head; -} __attribute__(( aligned(4), packed )); - -/* Only used on SMD package v3 on msm7201a */ -struct smd_shared_v1 { - struct smd_half_channel ch0; - unsigned char data0[SMD_BUF_SIZE]; - struct smd_half_channel ch1; - unsigned char data1[SMD_BUF_SIZE]; }; -/* Used on SMD package v4 */ -struct smd_shared_v2 { - struct smd_half_channel ch0; - struct smd_half_channel ch1; -}; - -struct smd_channel { - volatile struct smd_half_channel *send; - volatile struct smd_half_channel *recv; - unsigned char *send_data; - unsigned char *recv_data; - - unsigned fifo_mask; - unsigned fifo_size; - unsigned current_packet; - unsigned n; - - struct list_head ch_list; - - void *priv; - void (*notify)(void *priv, unsigned flags); - - int (*read)(struct smd_channel *ch, void *data, int len); - int (*write)(struct smd_channel *ch, const void *data, int len); - int (*read_avail)(struct smd_channel *ch); - int (*write_avail)(struct smd_channel *ch); - - void (*update_state)(struct smd_channel *ch); - unsigned last_state; - void (*notify_other_cpu)(void); - unsigned type; - - char name[32]; - struct platform_device pdev; -}; - -#define SMD_TYPE_MASK 0x0FF -#define SMD_TYPE_APPS_MODEM 0x000 -#define SMD_TYPE_APPS_DSP 0x001 -#define SMD_TYPE_MODEM_DSP 0x002 - -#define SMD_KIND_MASK 0xF00 -#define SMD_KIND_UNKNOWN 0x000 -#define SMD_KIND_STREAM 0x100 -#define SMD_KIND_PACKET 0x200 - -extern struct list_head smd_ch_closed_list; -extern struct list_head smd_ch_list_modem; -extern struct list_head smd_ch_list_dsp; - -extern spinlock_t smd_lock; extern spinlock_t smem_lock; -void *smem_find(unsigned id, unsigned size); -void *smem_item(unsigned id, unsigned *size); -uint32_t raw_smsm_get_state(enum smsm_state_item item); - -extern void msm_init_last_radio_log(struct module *); - -#ifdef CONFIG_MSM_SMD_PKG3 -/* - * This allocator assumes an SMD Package v3 which only exists on - * MSM7x00 SoC's. - */ -static inline int _smd_alloc_channel(struct smd_channel *ch) -{ - struct smd_shared_v1 *shared1; - - shared1 = smem_alloc(ID_SMD_CHANNELS + ch->n, sizeof(*shared1)); - if (!shared1) { - pr_err("smd_alloc_channel() cid %d does not exist\n", ch->n); - return -1; - } - ch->send = &shared1->ch0; - ch->recv = &shared1->ch1; - ch->send_data = shared1->data0; - ch->recv_data = shared1->data1; - ch->fifo_size = SMD_BUF_SIZE; - return 0; -} -#else -/* - * This allocator assumes an SMD Package v4, the most common - * and the default. - */ -static inline int _smd_alloc_channel(struct smd_channel *ch) -{ - struct smd_shared_v2 *shared2; - void *buffer; - unsigned buffer_sz; - - shared2 = smem_alloc(SMEM_SMD_BASE_ID + ch->n, sizeof(*shared2)); - buffer = smem_item(SMEM_SMD_FIFO_BASE_ID + ch->n, &buffer_sz); - - if (!buffer) - return -1; - - /* buffer must be a power-of-two size */ - if (buffer_sz & (buffer_sz - 1)) - return -1; - - buffer_sz /= 2; - ch->send = &shared2->ch0; - ch->recv = &shared2->ch1; - ch->send_data = buffer; - ch->recv_data = buffer + buffer_sz; - ch->fifo_size = buffer_sz; - return 0; -} -#endif /* CONFIG_MSM_SMD_PKG3 */ - -#if defined(CONFIG_ARCH_MSM7X30) -static inline void msm_a2m_int(uint32_t irq) -{ - writel(1 << irq, MSM_GCC_BASE + 0x8); -} -#else -static inline void msm_a2m_int(uint32_t irq) -{ - writel(1, MSM_CSR_BASE + 0x400 + (irq * 4)); -} -#endif /* CONFIG_ARCH_MSM7X30 */ +void smd_diag(void); #endif diff --git a/arch/arm/mach-msm/smd_qmi.c b/arch/arm/mach-msm/smd_qmi.c new file mode 100644 index 00000000000..ca10f00d0cf --- /dev/null +++ b/arch/arm/mach-msm/smd_qmi.c @@ -0,0 +1,848 @@ +/* arch/arm/mach-msm/smd_qmi.c + * + * QMI Control Driver -- Manages network data connections. + * + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define QMI_CTL 0x00 +#define QMI_WDS 0x01 +#define QMI_DMS 0x02 +#define QMI_NAS 0x03 + +#define QMI_RESULT_SUCCESS 0x0000 +#define QMI_RESULT_FAILURE 0x0001 + +struct qmi_msg { + unsigned char service; + unsigned char client_id; + unsigned short txn_id; + unsigned short type; + unsigned short size; + unsigned char *tlv; +}; + +#define qmi_ctl_client_id 0 + +#define STATE_OFFLINE 0 +#define STATE_QUERYING 1 +#define STATE_ONLINE 2 + +struct qmi_ctxt { + struct miscdevice misc; + + struct mutex lock; + + unsigned char ctl_txn_id; + unsigned char wds_client_id; + unsigned short wds_txn_id; + + unsigned wds_busy; + unsigned wds_handle; + unsigned state_dirty; + unsigned state; + + unsigned char addr[4]; + unsigned char mask[4]; + unsigned char gateway[4]; + unsigned char dns1[4]; + unsigned char dns2[4]; + + smd_channel_t *ch; + const char *ch_name; + struct wake_lock wake_lock; + + struct work_struct open_work; + struct work_struct read_work; +}; + +static struct qmi_ctxt *qmi_minor_to_ctxt(unsigned n); + +static void qmi_read_work(struct work_struct *ws); +static void qmi_open_work(struct work_struct *work); + +void qmi_ctxt_init(struct qmi_ctxt *ctxt, unsigned n) +{ + mutex_init(&ctxt->lock); + INIT_WORK(&ctxt->read_work, qmi_read_work); + INIT_WORK(&ctxt->open_work, qmi_open_work); + wake_lock_init(&ctxt->wake_lock, WAKE_LOCK_SUSPEND, ctxt->misc.name); + ctxt->ctl_txn_id = 1; + ctxt->wds_txn_id = 1; + ctxt->wds_busy = 1; + ctxt->state = STATE_OFFLINE; + +} + +static struct workqueue_struct *qmi_wq; + +static int verbose = 0; + +/* anyone waiting for a state change waits here */ +static DECLARE_WAIT_QUEUE_HEAD(qmi_wait_queue); + + +static void qmi_dump_msg(struct qmi_msg *msg, const char *prefix) +{ + unsigned sz, n; + unsigned char *x; + + if (!verbose) + return; + + printk(KERN_INFO + "qmi: %s: svc=%02x cid=%02x tid=%04x type=%04x size=%04x\n", + prefix, msg->service, msg->client_id, + msg->txn_id, msg->type, msg->size); + + x = msg->tlv; + sz = msg->size; + + while (sz >= 3) { + sz -= 3; + + n = x[1] | (x[2] << 8); + if (n > sz) + break; + + printk(KERN_INFO "qmi: %s: tlv: %02x %04x { ", + prefix, x[0], n); + x += 3; + sz -= n; + while (n-- > 0) + printk("%02x ", *x++); + printk("}\n"); + } +} + +int qmi_add_tlv(struct qmi_msg *msg, + unsigned type, unsigned size, const void *data) +{ + unsigned char *x = msg->tlv + msg->size; + + x[0] = type; + x[1] = size; + x[2] = size >> 8; + + memcpy(x + 3, data, size); + + msg->size += (size + 3); + + return 0; +} + +/* Extract a tagged item from a qmi message buffer, +** taking care not to overrun the buffer. +*/ +static int qmi_get_tlv(struct qmi_msg *msg, + unsigned type, unsigned size, void *data) +{ + unsigned char *x = msg->tlv; + unsigned len = msg->size; + unsigned n; + + while (len >= 3) { + len -= 3; + + /* size of this item */ + n = x[1] | (x[2] << 8); + if (n > len) + break; + + if (x[0] == type) { + if (n != size) + return -1; + memcpy(data, x + 3, size); + return 0; + } + + x += (n + 3); + len -= n; + } + + return -1; +} + +static unsigned qmi_get_status(struct qmi_msg *msg, unsigned *error) +{ + unsigned short status[2]; + if (qmi_get_tlv(msg, 0x02, sizeof(status), status)) { + *error = 0; + return QMI_RESULT_FAILURE; + } else { + *error = status[1]; + return status[0]; + } +} + +/* 0x01 */ +#define QMUX_HEADER 13 + +/* should be >= HEADER + FOOTER */ +#define QMUX_OVERHEAD 16 + +static int qmi_send(struct qmi_ctxt *ctxt, struct qmi_msg *msg) +{ + unsigned char *data; + unsigned hlen; + unsigned len; + int r; + + qmi_dump_msg(msg, "send"); + + if (msg->service == QMI_CTL) { + hlen = QMUX_HEADER - 1; + } else { + hlen = QMUX_HEADER; + } + + /* QMUX length is total header + total payload - IFC selector */ + len = hlen + msg->size - 1; + if (len > 0xffff) + return -1; + + data = msg->tlv - hlen; + + /* prepend encap and qmux header */ + *data++ = 0x01; /* ifc selector */ + + /* qmux header */ + *data++ = len; + *data++ = len >> 8; + *data++ = 0x00; /* flags: client */ + *data++ = msg->service; + *data++ = msg->client_id; + + /* qmi header */ + *data++ = 0x00; /* flags: send */ + *data++ = msg->txn_id; + if (msg->service != QMI_CTL) + *data++ = msg->txn_id >> 8; + + *data++ = msg->type; + *data++ = msg->type >> 8; + *data++ = msg->size; + *data++ = msg->size >> 8; + + /* len + 1 takes the interface selector into account */ + r = smd_write(ctxt->ch, msg->tlv - hlen, len + 1); + + if (r != len) { + return -1; + } else { + return 0; + } +} + +static void qmi_process_ctl_msg(struct qmi_ctxt *ctxt, struct qmi_msg *msg) +{ + unsigned err; + if (msg->type == 0x0022) { + unsigned char n[2]; + if (qmi_get_status(msg, &err)) + return; + if (qmi_get_tlv(msg, 0x01, sizeof(n), n)) + return; + if (n[0] == QMI_WDS) { + printk(KERN_INFO + "qmi: ctl: wds use client_id 0x%02x\n", n[1]); + ctxt->wds_client_id = n[1]; + ctxt->wds_busy = 0; + } + } +} + +static int qmi_network_get_profile(struct qmi_ctxt *ctxt); + +static void swapaddr(unsigned char *src, unsigned char *dst) +{ + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; +} + +static unsigned char zero[4]; +static void qmi_read_runtime_profile(struct qmi_ctxt *ctxt, struct qmi_msg *msg) +{ + unsigned char tmp[4]; + unsigned r; + + r = qmi_get_tlv(msg, 0x1e, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->addr); + r = qmi_get_tlv(msg, 0x21, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->mask); + r = qmi_get_tlv(msg, 0x20, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->gateway); + r = qmi_get_tlv(msg, 0x15, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->dns1); + r = qmi_get_tlv(msg, 0x16, 4, tmp); + swapaddr(r ? zero : tmp, ctxt->dns2); +} + +static void qmi_process_unicast_wds_msg(struct qmi_ctxt *ctxt, + struct qmi_msg *msg) +{ + unsigned err; + switch (msg->type) { + case 0x0021: + if (qmi_get_status(msg, &err)) { + printk(KERN_ERR + "qmi: wds: network stop failed (%04x)\n", err); + } else { + printk(KERN_INFO + "qmi: wds: network stopped\n"); + ctxt->state = STATE_OFFLINE; + ctxt->state_dirty = 1; + } + break; + case 0x0020: + if (qmi_get_status(msg, &err)) { + printk(KERN_ERR + "qmi: wds: network start failed (%04x)\n", err); + } else if (qmi_get_tlv(msg, 0x01, sizeof(ctxt->wds_handle), &ctxt->wds_handle)) { + printk(KERN_INFO + "qmi: wds no handle?\n"); + } else { + printk(KERN_INFO + "qmi: wds: got handle 0x%08x\n", + ctxt->wds_handle); + } + break; + case 0x002D: + printk("qmi: got network profile\n"); + if (ctxt->state == STATE_QUERYING) { + qmi_read_runtime_profile(ctxt, msg); + ctxt->state = STATE_ONLINE; + ctxt->state_dirty = 1; + } + break; + default: + printk(KERN_ERR "qmi: unknown msg type 0x%04x\n", msg->type); + } + ctxt->wds_busy = 0; +} + +static void qmi_process_broadcast_wds_msg(struct qmi_ctxt *ctxt, + struct qmi_msg *msg) +{ + if (msg->type == 0x0022) { + unsigned char n[2]; + if (qmi_get_tlv(msg, 0x01, sizeof(n), n)) + return; + switch (n[0]) { + case 1: + printk(KERN_INFO "qmi: wds: DISCONNECTED\n"); + ctxt->state = STATE_OFFLINE; + ctxt->state_dirty = 1; + break; + case 2: + printk(KERN_INFO "qmi: wds: CONNECTED\n"); + ctxt->state = STATE_QUERYING; + ctxt->state_dirty = 1; + qmi_network_get_profile(ctxt); + break; + case 3: + printk(KERN_INFO "qmi: wds: SUSPENDED\n"); + ctxt->state = STATE_OFFLINE; + ctxt->state_dirty = 1; + } + } else { + printk(KERN_ERR "qmi: unknown bcast msg type 0x%04x\n", msg->type); + } +} + +static void qmi_process_wds_msg(struct qmi_ctxt *ctxt, + struct qmi_msg *msg) +{ + printk("wds: %04x @ %02x\n", msg->type, msg->client_id); + if (msg->client_id == ctxt->wds_client_id) { + qmi_process_unicast_wds_msg(ctxt, msg); + } else if (msg->client_id == 0xff) { + qmi_process_broadcast_wds_msg(ctxt, msg); + } else { + printk(KERN_ERR + "qmi_process_wds_msg client id 0x%02x unknown\n", + msg->client_id); + } +} + +static void qmi_process_qmux(struct qmi_ctxt *ctxt, + unsigned char *buf, unsigned sz) +{ + struct qmi_msg msg; + + /* require a full header */ + if (sz < 5) + return; + + /* require a size that matches the buffer size */ + if (sz != (buf[0] | (buf[1] << 8))) + return; + + /* only messages from a service (bit7=1) are allowed */ + if (buf[2] != 0x80) + return; + + msg.service = buf[3]; + msg.client_id = buf[4]; + + /* annoyingly, CTL messages have a shorter TID */ + if (buf[3] == 0) { + if (sz < 7) + return; + msg.txn_id = buf[6]; + buf += 7; + sz -= 7; + } else { + if (sz < 8) + return; + msg.txn_id = buf[6] | (buf[7] << 8); + buf += 8; + sz -= 8; + } + + /* no type and size!? */ + if (sz < 4) + return; + sz -= 4; + + msg.type = buf[0] | (buf[1] << 8); + msg.size = buf[2] | (buf[3] << 8); + msg.tlv = buf + 4; + + if (sz != msg.size) + return; + + qmi_dump_msg(&msg, "recv"); + + mutex_lock(&ctxt->lock); + switch (msg.service) { + case QMI_CTL: + qmi_process_ctl_msg(ctxt, &msg); + break; + case QMI_WDS: + qmi_process_wds_msg(ctxt, &msg); + break; + default: + printk(KERN_ERR "qmi: msg from unknown svc 0x%02x\n", + msg.service); + break; + } + mutex_unlock(&ctxt->lock); + + wake_up(&qmi_wait_queue); +} + +#define QMI_MAX_PACKET (256 + QMUX_OVERHEAD) + +static void qmi_read_work(struct work_struct *ws) +{ + struct qmi_ctxt *ctxt = container_of(ws, struct qmi_ctxt, read_work); + struct smd_channel *ch = ctxt->ch; + unsigned char buf[QMI_MAX_PACKET]; + int sz; + + for (;;) { + sz = smd_cur_packet_size(ch); + if (sz == 0) + break; + if (sz < smd_read_avail(ch)) + break; + if (sz > QMI_MAX_PACKET) { + smd_read(ch, 0, sz); + continue; + } + if (smd_read(ch, buf, sz) != sz) { + printk(KERN_ERR "qmi: not enough data?!\n"); + continue; + } + + /* interface selector must be 1 */ + if (buf[0] != 0x01) + continue; + + qmi_process_qmux(ctxt, buf + 1, sz - 1); + } +} + +static int qmi_request_wds_cid(struct qmi_ctxt *ctxt); + +static void qmi_open_work(struct work_struct *ws) +{ + struct qmi_ctxt *ctxt = container_of(ws, struct qmi_ctxt, open_work); + mutex_lock(&ctxt->lock); + qmi_request_wds_cid(ctxt); + mutex_unlock(&ctxt->lock); +} + +static void qmi_notify(void *priv, unsigned event) +{ + struct qmi_ctxt *ctxt = priv; + + switch (event) { + case SMD_EVENT_DATA: { + int sz; + sz = smd_cur_packet_size(ctxt->ch); + if ((sz > 0) && (sz <= smd_read_avail(ctxt->ch))) { + wake_lock_timeout(&ctxt->wake_lock, HZ / 2); + queue_work(qmi_wq, &ctxt->read_work); + } + break; + } + case SMD_EVENT_OPEN: + printk(KERN_INFO "qmi: smd opened\n"); + queue_work(qmi_wq, &ctxt->open_work); + break; + case SMD_EVENT_CLOSE: + printk(KERN_INFO "qmi: smd closed\n"); + break; + } +} + +static int qmi_request_wds_cid(struct qmi_ctxt *ctxt) +{ + unsigned char data[64 + QMUX_OVERHEAD]; + struct qmi_msg msg; + unsigned char n; + + msg.service = QMI_CTL; + msg.client_id = qmi_ctl_client_id; + msg.txn_id = ctxt->ctl_txn_id; + msg.type = 0x0022; + msg.size = 0; + msg.tlv = data + QMUX_HEADER; + + ctxt->ctl_txn_id += 2; + + n = QMI_WDS; + qmi_add_tlv(&msg, 0x01, 0x01, &n); + + return qmi_send(ctxt, &msg); +} + +static int qmi_network_get_profile(struct qmi_ctxt *ctxt) +{ + unsigned char data[96 + QMUX_OVERHEAD]; + struct qmi_msg msg; + + msg.service = QMI_WDS; + msg.client_id = ctxt->wds_client_id; + msg.txn_id = ctxt->wds_txn_id; + msg.type = 0x002D; + msg.size = 0; + msg.tlv = data + QMUX_HEADER; + + ctxt->wds_txn_id += 2; + + return qmi_send(ctxt, &msg); +} + +static int qmi_network_up(struct qmi_ctxt *ctxt, char *apn) +{ + unsigned char data[96 + QMUX_OVERHEAD]; + struct qmi_msg msg; + char *user; + char *pass; + + for (user = apn; *user; user++) { + if (*user == ' ') { + *user++ = 0; + break; + } + } + for (pass = user; *pass; pass++) { + if (*pass == ' ') { + *pass++ = 0; + break; + } + } + + msg.service = QMI_WDS; + msg.client_id = ctxt->wds_client_id; + msg.txn_id = ctxt->wds_txn_id; + msg.type = 0x0020; + msg.size = 0; + msg.tlv = data + QMUX_HEADER; + + ctxt->wds_txn_id += 2; + + qmi_add_tlv(&msg, 0x14, strlen(apn), apn); + if (*user) { + unsigned char x; + x = 3; + qmi_add_tlv(&msg, 0x16, 1, &x); + qmi_add_tlv(&msg, 0x17, strlen(user), user); + if (*pass) + qmi_add_tlv(&msg, 0x18, strlen(pass), pass); + } + return qmi_send(ctxt, &msg); +} + +static int qmi_network_down(struct qmi_ctxt *ctxt) +{ + unsigned char data[16 + QMUX_OVERHEAD]; + struct qmi_msg msg; + + msg.service = QMI_WDS; + msg.client_id = ctxt->wds_client_id; + msg.txn_id = ctxt->wds_txn_id; + msg.type = 0x0021; + msg.size = 0; + msg.tlv = data + QMUX_HEADER; + + ctxt->wds_txn_id += 2; + + qmi_add_tlv(&msg, 0x01, sizeof(ctxt->wds_handle), &ctxt->wds_handle); + + return qmi_send(ctxt, &msg); +} + +static int qmi_print_state(struct qmi_ctxt *ctxt, char *buf, int max) +{ + int i; + char *statename; + + if (ctxt->state == STATE_ONLINE) { + statename = "up"; + } else if (ctxt->state == STATE_OFFLINE) { + statename = "down"; + } else { + statename = "busy"; + } + + i = scnprintf(buf, max, "STATE=%s\n", statename); + i += scnprintf(buf + i, max - i, "CID=%d\n",ctxt->wds_client_id); + + if (ctxt->state != STATE_ONLINE){ + return i; + } + + i += scnprintf(buf + i, max - i, "ADDR=%d.%d.%d.%d\n", + ctxt->addr[0], ctxt->addr[1], ctxt->addr[2], ctxt->addr[3]); + i += scnprintf(buf + i, max - i, "MASK=%d.%d.%d.%d\n", + ctxt->mask[0], ctxt->mask[1], ctxt->mask[2], ctxt->mask[3]); + i += scnprintf(buf + i, max - i, "GATEWAY=%d.%d.%d.%d\n", + ctxt->gateway[0], ctxt->gateway[1], ctxt->gateway[2], + ctxt->gateway[3]); + i += scnprintf(buf + i, max - i, "DNS1=%d.%d.%d.%d\n", + ctxt->dns1[0], ctxt->dns1[1], ctxt->dns1[2], ctxt->dns1[3]); + i += scnprintf(buf + i, max - i, "DNS2=%d.%d.%d.%d\n", + ctxt->dns2[0], ctxt->dns2[1], ctxt->dns2[2], ctxt->dns2[3]); + + return i; +} + +static ssize_t qmi_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct qmi_ctxt *ctxt = fp->private_data; + char msg[256]; + int len; + int r; + + mutex_lock(&ctxt->lock); + for (;;) { + if (ctxt->state_dirty) { + ctxt->state_dirty = 0; + len = qmi_print_state(ctxt, msg, 256); + break; + } + mutex_unlock(&ctxt->lock); + r = wait_event_interruptible(qmi_wait_queue, ctxt->state_dirty); + if (r < 0) + return r; + mutex_lock(&ctxt->lock); + } + mutex_unlock(&ctxt->lock); + + if (len > count) + len = count; + + if (copy_to_user(buf, msg, len)) + return -EFAULT; + + return len; +} + + +static ssize_t qmi_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct qmi_ctxt *ctxt = fp->private_data; + unsigned char cmd[64]; + int len; + int r; + + if (count < 1) + return 0; + + len = count > 63 ? 63 : count; + + if (copy_from_user(cmd, buf, len)) + return -EFAULT; + + cmd[len] = 0; + + /* lazy */ + if (cmd[len-1] == '\n') { + cmd[len-1] = 0; + len--; + } + + if (!strncmp(cmd, "verbose", 7)) { + verbose = 1; + } else if (!strncmp(cmd, "terse", 5)) { + verbose = 0; + } else if (!strncmp(cmd, "poll", 4)) { + ctxt->state_dirty = 1; + wake_up(&qmi_wait_queue); + } else if (!strncmp(cmd, "down", 4)) { +retry_down: + mutex_lock(&ctxt->lock); + if (ctxt->wds_busy) { + mutex_unlock(&ctxt->lock); + r = wait_event_interruptible(qmi_wait_queue, !ctxt->wds_busy); + if (r < 0) + return r; + goto retry_down; + } + ctxt->wds_busy = 1; + qmi_network_down(ctxt); + mutex_unlock(&ctxt->lock); + } else if (!strncmp(cmd, "up:", 3)) { +retry_up: + mutex_lock(&ctxt->lock); + if (ctxt->wds_busy) { + mutex_unlock(&ctxt->lock); + r = wait_event_interruptible(qmi_wait_queue, !ctxt->wds_busy); + if (r < 0) + return r; + goto retry_up; + } + ctxt->wds_busy = 1; + qmi_network_up(ctxt, cmd+3); + mutex_unlock(&ctxt->lock); + } else { + return -EINVAL; + } + + return count; +} + +static int qmi_open(struct inode *ip, struct file *fp) +{ + struct qmi_ctxt *ctxt = qmi_minor_to_ctxt(MINOR(ip->i_rdev)); + int r = 0; + + if (!ctxt) { + printk(KERN_ERR "unknown qmi misc %d\n", MINOR(ip->i_rdev)); + return -ENODEV; + } + + fp->private_data = ctxt; + + mutex_lock(&ctxt->lock); + if (ctxt->ch == 0) + r = smd_open(ctxt->ch_name, &ctxt->ch, ctxt, qmi_notify); + if (r == 0) + wake_up(&qmi_wait_queue); + mutex_unlock(&ctxt->lock); + + return r; +} + +static int qmi_release(struct inode *ip, struct file *fp) +{ + return 0; +} + +static struct file_operations qmi_fops = { + .owner = THIS_MODULE, + .read = qmi_read, + .write = qmi_write, + .open = qmi_open, + .release = qmi_release, +}; + +static struct qmi_ctxt qmi_device0 = { + .ch_name = "SMD_DATA5_CNTL", + .misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qmi0", + .fops = &qmi_fops, + } +}; +static struct qmi_ctxt qmi_device1 = { + .ch_name = "SMD_DATA6_CNTL", + .misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qmi1", + .fops = &qmi_fops, + } +}; +static struct qmi_ctxt qmi_device2 = { + .ch_name = "SMD_DATA7_CNTL", + .misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qmi2", + .fops = &qmi_fops, + } +}; + +static struct qmi_ctxt *qmi_minor_to_ctxt(unsigned n) +{ + if (n == qmi_device0.misc.minor) + return &qmi_device0; + if (n == qmi_device1.misc.minor) + return &qmi_device1; + if (n == qmi_device2.misc.minor) + return &qmi_device2; + return 0; +} + +static int __init qmi_init(void) +{ + int ret; + + qmi_wq = create_singlethread_workqueue("qmi"); + if (qmi_wq == 0) + return -ENOMEM; + + qmi_ctxt_init(&qmi_device0, 0); + qmi_ctxt_init(&qmi_device1, 1); + qmi_ctxt_init(&qmi_device2, 2); + + ret = misc_register(&qmi_device0.misc); + if (ret == 0) + ret = misc_register(&qmi_device1.misc); + if (ret == 0) + ret = misc_register(&qmi_device2.misc); + return ret; +} + +module_init(qmi_init); diff --git a/arch/arm/mach-msm/smd_rpc_sym b/arch/arm/mach-msm/smd_rpc_sym new file mode 100755 index 00000000000..6965cf6fec3 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpc_sym @@ -0,0 +1,156 @@ +0x30000000 cm +0x30000001 db +0x30000002 snd +0x30000003 wms +0x30000004 pdsm +0x30000005 misc_modem_apis +0x30000006 misc_apps_apis +0x30000007 joyst +0x3000000A adsprtosatom +0x3000000B adsprtosmtoa +0x3000000C i2c +0x3000000D time_remote +0x3000000E nv +0x3000000F clkrgm_sec +0x30000010 rdevmap +0x30000012 pbmlib +0x30000013 audmgr +0x30000014 mvs +0x30000015 dog_keepalive +0x30000016 gsdi_exp +0x30000017 auth +0x30000018 nvruimi +0x30000019 mmgsdilib +0x3000001A charger +0x3000001B uim +0x3000001D pdsm_atl +0x3000001E fs_xmount +0x3000001F secutil +0x30000020 mccmeid +0x30000021 pm_strobe_flash +0x30000023 smd_bridge +0x30000024 smd_port_mgr +0x30000025 bus_perf +0x30000026 bus_mon_remote +0x30000027 mc +0x30000028 mccap +0x30000029 mccdma +0x3000002A mccds +0x3000002B mccsch +0x3000002C mccsrid +0x3000002D snm +0x3000002E mccsyobj +0x30000031 dsrlp_apis +0x30000032 rlp_apis +0x30000033 ds_mp_shim_modem +0x30000035 dshdr_mdm_apis +0x30000036 ds_mp_shim_apps +0x30000037 hdrmc_apis +0x3000003A pmapp_otg +0x3000003B diag +0x3000003C gstk_exp +0x3000003D dsbc_mdm_apis +0x3000003E hdrmrlp_mdm_apis +0x30000040 hdrmc_mrlp_apis +0x30000041 pdcomm_app_api +0x30000042 dsat_apis +0x30000043 rfm +0x30000044 cmipapp +0x30000045 dsmp_umts_modem_apis +0x30000047 dsucsdmpshim +0x30000048 time_remote_atom +0x3000004A sd +0x3000004B mmoc +0x3000004D wlan_cp_cm +0x3000004E ftm_wlan +0x30000050 CprmInterface +0x30000051 data_on_modem_mtoa_apis +0x30000053 misc_modem_apis_nonwinmob +0x30000054 misc_apps_apis_nonwinmob +0x30000055 pmem_remote +0x30000056 tcxomgr +0x30000058 bt +0x30000059 pd_comms_api +0x3000005A pd_comms_client_api +0x3000005B pdapi +0x3000005D time_remote_mtoa +0x3000005E ftm_bt +0x3000005F dsucsdappif_apis +0x30000060 pmapp_gen +0x30000061 pm_lib +0x30000063 hsu_app_apis +0x30000064 hsu_mdm_apis +0x30000065 adie_adc_remote_atom +0x30000066 tlmm_remote_atom +0x30000067 ui_callctrl +0x30000068 uiutils +0x30000069 prl +0x3000006A hw +0x3000006B oem_rapi +0x3000006C wmspm +0x3000006D btpf +0x3000006F usb_apps_rpc +0x30000070 usb_modem_rpc +0x30000071 adc +0x30000072 cameraremoted +0x30000073 secapiremoted +0x30000074 dsatapi +0x30000075 clkctl_rpc +0x30000076 BrewAppCoord +0x30000078 wlan_trp_utils +0x30000079 gpio_rpc +0x3000007C l1_ds +0x3000007F oss_rrcasn_remote +0x30000080 pmapp_otg_remote +0x30000081 ping_mdm_rpc +0x30000087 ukcc_ipc_apis +0x30000089 vbatt_remote +0x3000008A mfpal_fps +0x3000008B dsumtspdpreg +0x3000008C loc_api +0x3000008E cmgan +0x3000008F isense +0x30000090 time_secure +0x30000091 hs_rem +0x30000092 acdb +0x30000093 net +0x30000094 led +0x30000095 dspae +0x30000096 mfkal +0x3000009B test_api +0x3000009C remotefs_srv_api +0x3000009D isi_transport +0x3000009E oem_ftm +0x3000009F touch_screen_adc +0x300000A0 smd_bridge_apps +0x300000A1 smd_bridge_modem +0x300000A2 dog_keepalive_modem +0x300000A3 voem_if +0x300000A4 npa_remote +0x300000A5 mmgsdisessionlib +0x300000A6 ifta_remote +0x300000A7 remote_storage +0x300000A8 mf_remote_file +0x300000A9 mfsc_chunked_transport +0x300000AA mfim3 +0x300000AB fm_wan_api +0x300000AC wlan_rapi +0x300000AD dsmgr_apis +0x300100AE cm_mm_fusion +0x30010081 ping_lte_rpc +0x30010061 pm_lib_fusion +0x3001000E nv_fusion +0x30010003 wms_fusion +0x3001001B uim_fusion +0x30010012 pbmlib_fusion +0x3001003C gstk_exp_fusion +0x30010000 cm_fusion +0x3001006B oem_rapi_fusion +0x3001000F clkrgm_sec_fusion +0x300100A0 smd_bridge_apps_fusion +0x300100A1 smd_bridge_modem_fusion +0x30010024 smd_port_mgr_fusion +0x30010019 mmgsdilib_fusion +0x300100A5 mmgsdisessionlib_fusion +0x3001009C remotefs_srv_api_fusion +0x30010016 gsdi_exp_fusion diff --git a/arch/arm/mach-msm/smd_rpc_sym.h b/arch/arm/mach-msm/smd_rpc_sym.h new file mode 100644 index 00000000000..e9a8b474737 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpc_sym.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Code Aurora Forum, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_SMD_RPC_SYM_H +#define _ARCH_ARM_MACH_MSM_SMD_RPC_SYM_H + +#if defined(CONFIG_DEBUG_FS) +const char *smd_rpc_get_sym(uint32_t val); +#endif + +#endif diff --git a/arch/arm/mach-msm/smd_rpcrouter.c b/arch/arm/mach-msm/smd_rpcrouter.c new file mode 100644 index 00000000000..b45b182d5e7 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter.c @@ -0,0 +1,2508 @@ +/* arch/arm/mach-msm/smd_rpcrouter.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2011, Code Aurora Forum. All rights reserved. + * Author: San Mehat + * + * 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. + * + */ + +/* TODO: handle cases where smd_write() will tempfail due to full fifo */ +/* TODO: thread priority? schedule a work to bump it? */ +/* TODO: maybe make server_list_lock a mutex */ +/* TODO: pool fragments to avoid kmalloc/kfree churn */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "smd_rpcrouter.h" +#include "modem_notifier.h" +#include "smd_rpc_sym.h" +#include "smd_private.h" + +enum { + SMEM_LOG = 1U << 0, + RTR_DBG = 1U << 1, + R2R_MSG = 1U << 2, + R2R_RAW = 1U << 3, + RPC_MSG = 1U << 4, + NTFY_MSG = 1U << 5, + RAW_PMR = 1U << 6, + RAW_PMW = 1U << 7, + R2R_RAW_HDR = 1U << 8, +}; +static int msm_rpc_connect_timeout_ms; +module_param_named(connect_timeout, msm_rpc_connect_timeout_ms, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +static int smd_rpcrouter_debug_mask; +module_param_named(debug_mask, smd_rpcrouter_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define DIAG(x...) printk(KERN_ERR "[RR] ERROR " x) + +#if defined(CONFIG_MSM_ONCRPCROUTER_DEBUG) +#define D(x...) do { \ +if (smd_rpcrouter_debug_mask & RTR_DBG) \ + printk(KERN_ERR x); \ +} while (0) + +#define RR(x...) do { \ +if (smd_rpcrouter_debug_mask & R2R_MSG) \ + printk(KERN_ERR "[RR] "x); \ +} while (0) + +#define RAW(x...) do { \ +if (smd_rpcrouter_debug_mask & R2R_RAW) \ + printk(KERN_ERR "[RAW] "x); \ +} while (0) + +#define RAW_HDR(x...) do { \ +if (smd_rpcrouter_debug_mask & R2R_RAW_HDR) \ + printk(KERN_ERR "[HDR] "x); \ +} while (0) + +#define RAW_PMR(x...) do { \ +if (smd_rpcrouter_debug_mask & RAW_PMR) \ + printk(KERN_ERR "[PMR] "x); \ +} while (0) + +#define RAW_PMR_NOMASK(x...) do { \ + printk(KERN_ERR "[PMR] "x); \ +} while (0) + +#define RAW_PMW(x...) do { \ +if (smd_rpcrouter_debug_mask & RAW_PMW) \ + printk(KERN_ERR "[PMW] "x); \ +} while (0) + +#define RAW_PMW_NOMASK(x...) do { \ + printk(KERN_ERR "[PMW] "x); \ +} while (0) + +#define IO(x...) do { \ +if (smd_rpcrouter_debug_mask & RPC_MSG) \ + printk(KERN_ERR "[RPC] "x); \ +} while (0) + +#define NTFY(x...) do { \ +if (smd_rpcrouter_debug_mask & NTFY_MSG) \ + printk(KERN_ERR "[NOTIFY] "x); \ +} while (0) +#else +#define D(x...) do { } while (0) +#define RR(x...) do { } while (0) +#define RAW(x...) do { } while (0) +#define RAW_HDR(x...) do { } while (0) +#define RAW_PMR(x...) do { } while (0) +#define RAW_PMR_NO_MASK(x...) do { } while (0) +#define RAW_PMW(x...) do { } while (0) +#define RAW_PMW_NO_MASK(x...) do { } while (0) +#define IO(x...) do { } while (0) +#define NTFY(x...) do { } while (0) +#endif + + +static LIST_HEAD(local_endpoints); +static LIST_HEAD(remote_endpoints); + +static LIST_HEAD(server_list); + +static wait_queue_head_t newserver_wait; +static wait_queue_head_t subsystem_restart_wait; + +static DEFINE_SPINLOCK(local_endpoints_lock); +static DEFINE_SPINLOCK(remote_endpoints_lock); +static DEFINE_SPINLOCK(server_list_lock); + +static LIST_HEAD(rpc_board_dev_list); +static DEFINE_SPINLOCK(rpc_board_dev_list_lock); + +static struct workqueue_struct *rpcrouter_workqueue; + +static atomic_t next_xid = ATOMIC_INIT(1); +static atomic_t pm_mid = ATOMIC_INIT(1); + +static void do_read_data(struct work_struct *work); +static void do_create_pdevs(struct work_struct *work); +static void do_create_rpcrouter_pdev(struct work_struct *work); + +static DECLARE_WORK(work_create_pdevs, do_create_pdevs); +static DECLARE_WORK(work_create_rpcrouter_pdev, do_create_rpcrouter_pdev); + +#define RR_STATE_IDLE 0 +#define RR_STATE_HEADER 1 +#define RR_STATE_BODY 2 +#define RR_STATE_ERROR 3 + +/* State for remote ep following restart */ +#define RESTART_QUOTA_ABORT 1 + +struct rr_context { + struct rr_packet *pkt; + uint8_t *ptr; + uint32_t state; /* current assembly state */ + uint32_t count; /* bytes needed in this state */ +}; + +struct rr_context the_rr_context; + +struct rpc_board_dev_info { + struct list_head list; + + struct rpc_board_dev *dev; +}; + +static struct platform_device rpcrouter_pdev = { + .name = "oncrpc_router", + .id = -1, +}; + +struct rpcrouter_xprt_info { + struct list_head list; + + struct rpcrouter_xprt *xprt; + + int remote_pid; + uint32_t initialized; + wait_queue_head_t read_wait; + struct wake_lock wakelock; + spinlock_t lock; + uint32_t need_len; + struct work_struct read_data; + struct workqueue_struct *workqueue; + int abort_data_read; + unsigned char r2r_buf[RPCROUTER_MSGSIZE_MAX]; +}; + +static LIST_HEAD(xprt_info_list); +static DEFINE_MUTEX(xprt_info_list_lock); + +DECLARE_COMPLETION(rpc_remote_router_up); +static atomic_t pending_close_count = ATOMIC_INIT(0); + +/* + * Search for transport (xprt) that matches the provided PID. + * + * Note: The calling function must ensure that the mutex + * xprt_info_list_lock is locked when this function + * is called. + * + * @remote_pid Remote PID for the transport + * + * @returns Pointer to transport or NULL if not found + */ +static struct rpcrouter_xprt_info *rpcrouter_get_xprt_info(uint32_t remote_pid) +{ + struct rpcrouter_xprt_info *xprt_info; + + list_for_each_entry(xprt_info, &xprt_info_list, list) { + if (xprt_info->remote_pid == remote_pid) + return xprt_info; + } + return NULL; +} + +static int rpcrouter_send_control_msg(struct rpcrouter_xprt_info *xprt_info, + union rr_control_msg *msg) +{ + struct rr_header hdr; + unsigned long flags = 0; + int need; + + if (xprt_info->remote_pid == RPCROUTER_PID_LOCAL) + return 0; + + if (!(msg->cmd == RPCROUTER_CTRL_CMD_HELLO) && + !xprt_info->initialized) { + printk(KERN_ERR "rpcrouter_send_control_msg(): Warning, " + "router not initialized\n"); + return -EINVAL; + } + + hdr.version = RPCROUTER_VERSION; + hdr.type = msg->cmd; + hdr.src_pid = RPCROUTER_PID_LOCAL; + hdr.src_cid = RPCROUTER_ROUTER_ADDRESS; + hdr.confirm_rx = 0; + hdr.size = sizeof(*msg); + hdr.dst_pid = xprt_info->remote_pid; + hdr.dst_cid = RPCROUTER_ROUTER_ADDRESS; + + /* TODO: what if channel is full? */ + + need = sizeof(hdr) + hdr.size; + spin_lock_irqsave(&xprt_info->lock, flags); + while (xprt_info->xprt->write_avail() < need) { + spin_unlock_irqrestore(&xprt_info->lock, flags); + msleep(250); + spin_lock_irqsave(&xprt_info->lock, flags); + } + xprt_info->xprt->write(&hdr, sizeof(hdr), HEADER); + xprt_info->xprt->write(msg, hdr.size, PAYLOAD); + spin_unlock_irqrestore(&xprt_info->lock, flags); + + return 0; +} + +static void modem_reset_cleanup(struct rpcrouter_xprt_info *xprt_info) +{ + struct msm_rpc_endpoint *ept; + struct rr_remote_endpoint *r_ept; + struct rr_packet *pkt, *tmp_pkt; + struct rr_fragment *frag, *next; + struct msm_rpc_reply *reply, *reply_tmp; + unsigned long flags; + + spin_lock_irqsave(&local_endpoints_lock, flags); + /* remove all partial packets received */ + list_for_each_entry(ept, &local_endpoints, list) { + RR("%s EPT DST PID %x, remote_pid:%d\n", __func__, + ept->dst_pid, xprt_info->remote_pid); + + if (xprt_info->remote_pid != ept->dst_pid) + continue; + + D("calling teardown cb %p\n", ept->cb_restart_teardown); + if (ept->cb_restart_teardown) + ept->cb_restart_teardown(ept->client_data); + ept->do_setup_notif = 1; + + /* remove replies */ + spin_lock(&ept->reply_q_lock); + list_for_each_entry_safe(reply, reply_tmp, + &ept->reply_pend_q, list) { + list_del(&reply->list); + kfree(reply); + } + list_for_each_entry_safe(reply, reply_tmp, + &ept->reply_avail_q, list) { + list_del(&reply->list); + kfree(reply); + } + spin_unlock(&ept->reply_q_lock); + + /* Set restart state for local ep */ + RR("EPT:0x%p, State %d RESTART_PEND_NTFY_SVR " + "PROG:0x%08x VERS:0x%08x\n", + ept, ept->restart_state, + be32_to_cpu(ept->dst_prog), + be32_to_cpu(ept->dst_vers)); + spin_lock(&ept->restart_lock); + ept->restart_state = RESTART_PEND_NTFY_SVR; + + /* remove incomplete packets */ + spin_lock(&ept->incomplete_lock); + list_for_each_entry_safe(pkt, tmp_pkt, + &ept->incomplete, list) { + list_del(&pkt->list); + frag = pkt->first; + while (frag != NULL) { + next = frag->next; + kfree(frag); + frag = next; + } + kfree(pkt); + } + spin_unlock(&ept->incomplete_lock); + + /* remove all completed packets waiting to be read */ + spin_lock(&ept->read_q_lock); + list_for_each_entry_safe(pkt, tmp_pkt, &ept->read_q, + list) { + list_del(&pkt->list); + frag = pkt->first; + while (frag != NULL) { + next = frag->next; + kfree(frag); + frag = next; + } + kfree(pkt); + } + spin_unlock(&ept->read_q_lock); + + spin_unlock(&ept->restart_lock); + wake_up(&ept->wait_q); + } + + spin_unlock_irqrestore(&local_endpoints_lock, flags); + + /* Unblock endpoints waiting for quota ack*/ + spin_lock_irqsave(&remote_endpoints_lock, flags); + list_for_each_entry(r_ept, &remote_endpoints, list) { + spin_lock(&r_ept->quota_lock); + r_ept->quota_restart_state = RESTART_QUOTA_ABORT; + RR("Set STATE_PENDING PID:0x%08x CID:0x%08x \n", r_ept->pid, + r_ept->cid); + spin_unlock(&r_ept->quota_lock); + wake_up(&r_ept->quota_wait); + } + spin_unlock_irqrestore(&remote_endpoints_lock, flags); +} + +static void modem_reset_startup(struct rpcrouter_xprt_info *xprt_info) +{ + struct msm_rpc_endpoint *ept; + unsigned long flags; + + spin_lock_irqsave(&local_endpoints_lock, flags); + + /* notify all endpoints that we are coming back up */ + list_for_each_entry(ept, &local_endpoints, list) { + RR("%s EPT DST PID %x, remote_pid:%d\n", __func__, + ept->dst_pid, xprt_info->remote_pid); + + if (xprt_info->remote_pid != ept->dst_pid) + continue; + + D("calling setup cb %d:%p\n", ept->do_setup_notif, + ept->cb_restart_setup); + if (ept->do_setup_notif && ept->cb_restart_setup) + ept->cb_restart_setup(ept->client_data); + ept->do_setup_notif = 0; + } + + spin_unlock_irqrestore(&local_endpoints_lock, flags); +} + +/* + * Blocks and waits for endpoint if a reset is in progress. + * + * @returns + * ENETRESET Reset is in progress and a notification needed + * ERESTARTSYS Signal occurred + * 0 Reset is not in progress + */ +static int wait_for_restart_and_notify(struct msm_rpc_endpoint *ept) +{ + unsigned long flags; + int ret = 0; + DEFINE_WAIT(__wait); + + for (;;) { + prepare_to_wait(&ept->restart_wait, &__wait, + TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&ept->restart_lock, flags); + if (ept->restart_state == RESTART_NORMAL) { + spin_unlock_irqrestore(&ept->restart_lock, flags); + break; + } else if (ept->restart_state & RESTART_PEND_NTFY) { + ept->restart_state &= ~RESTART_PEND_NTFY; + spin_unlock_irqrestore(&ept->restart_lock, flags); + ret = -ENETRESET; + break; + } + if (signal_pending(current) && + ((!(ept->flags & MSM_RPC_UNINTERRUPTIBLE)))) { + spin_unlock_irqrestore(&ept->restart_lock, flags); + ret = -ERESTARTSYS; + break; + } + spin_unlock_irqrestore(&ept->restart_lock, flags); + schedule(); + } + finish_wait(&ept->restart_wait, &__wait); + return ret; +} + +static struct rr_server *rpcrouter_create_server(uint32_t pid, + uint32_t cid, + uint32_t prog, + uint32_t ver) +{ + struct rr_server *server; + unsigned long flags; + int rc; + + server = kmalloc(sizeof(struct rr_server), GFP_KERNEL); + if (!server) + return ERR_PTR(-ENOMEM); + + memset(server, 0, sizeof(struct rr_server)); + server->pid = pid; + server->cid = cid; + server->prog = prog; + server->vers = ver; + + spin_lock_irqsave(&server_list_lock, flags); + list_add_tail(&server->list, &server_list); + spin_unlock_irqrestore(&server_list_lock, flags); + + rc = msm_rpcrouter_create_server_cdev(server); + if (rc < 0) + goto out_fail; + + return server; +out_fail: + spin_lock_irqsave(&server_list_lock, flags); + list_del(&server->list); + spin_unlock_irqrestore(&server_list_lock, flags); + kfree(server); + return ERR_PTR(rc); +} + +static void rpcrouter_destroy_server(struct rr_server *server) +{ + unsigned long flags; + + spin_lock_irqsave(&server_list_lock, flags); + list_del(&server->list); + spin_unlock_irqrestore(&server_list_lock, flags); + device_destroy(msm_rpcrouter_class, server->device_number); + kfree(server); +} + +int msm_rpc_add_board_dev(struct rpc_board_dev *devices, int num) +{ + unsigned long flags; + struct rpc_board_dev_info *board_info; + int i; + + for (i = 0; i < num; i++) { + board_info = kzalloc(sizeof(struct rpc_board_dev_info), + GFP_KERNEL); + if (!board_info) + return -ENOMEM; + + board_info->dev = &devices[i]; + D("%s: adding program %x\n", __func__, board_info->dev->prog); + spin_lock_irqsave(&rpc_board_dev_list_lock, flags); + list_add_tail(&board_info->list, &rpc_board_dev_list); + spin_unlock_irqrestore(&rpc_board_dev_list_lock, flags); + } + + return 0; +} +EXPORT_SYMBOL(msm_rpc_add_board_dev); + +static void rpcrouter_register_board_dev(struct rr_server *server) +{ + struct rpc_board_dev_info *board_info; + unsigned long flags; + int rc; + + spin_lock_irqsave(&rpc_board_dev_list_lock, flags); + list_for_each_entry(board_info, &rpc_board_dev_list, list) { + if (server->prog == board_info->dev->prog) { + D("%s: registering device %x\n", + __func__, board_info->dev->prog); + list_del(&board_info->list); + rc = platform_device_register(&board_info->dev->pdev); + if (rc) + pr_err("%s: board dev register failed %d\n", + __func__, rc); + kfree(board_info); + break; + } + } + spin_unlock_irqrestore(&rpc_board_dev_list_lock, flags); +} + +static struct rr_server *rpcrouter_lookup_server(uint32_t prog, uint32_t ver) +{ + struct rr_server *server; + unsigned long flags; + + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + if (server->prog == prog + && server->vers == ver) { + spin_unlock_irqrestore(&server_list_lock, flags); + return server; + } + } + spin_unlock_irqrestore(&server_list_lock, flags); + return NULL; +} + +static struct rr_server *rpcrouter_lookup_server_by_dev(dev_t dev) +{ + struct rr_server *server; + unsigned long flags; + + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + if (server->device_number == dev) { + spin_unlock_irqrestore(&server_list_lock, flags); + return server; + } + } + spin_unlock_irqrestore(&server_list_lock, flags); + return NULL; +} + +struct msm_rpc_endpoint *msm_rpcrouter_create_local_endpoint(dev_t dev) +{ + struct msm_rpc_endpoint *ept; + unsigned long flags; + + ept = kmalloc(sizeof(struct msm_rpc_endpoint), GFP_KERNEL); + if (!ept) + return NULL; + memset(ept, 0, sizeof(struct msm_rpc_endpoint)); + ept->cid = (uint32_t) ept; + ept->pid = RPCROUTER_PID_LOCAL; + ept->dev = dev; + + if ((dev != msm_rpcrouter_devno) && (dev != MKDEV(0, 0))) { + struct rr_server *srv; + /* + * This is a userspace client which opened + * a program/ver devicenode. Bind the client + * to that destination + */ + srv = rpcrouter_lookup_server_by_dev(dev); + /* TODO: bug? really? */ + BUG_ON(!srv); + + ept->dst_pid = srv->pid; + ept->dst_cid = srv->cid; + ept->dst_prog = cpu_to_be32(srv->prog); + ept->dst_vers = cpu_to_be32(srv->vers); + } else { + /* mark not connected */ + ept->dst_pid = 0xffffffff; + } + + init_waitqueue_head(&ept->wait_q); + INIT_LIST_HEAD(&ept->read_q); + spin_lock_init(&ept->read_q_lock); + INIT_LIST_HEAD(&ept->reply_avail_q); + INIT_LIST_HEAD(&ept->reply_pend_q); + spin_lock_init(&ept->reply_q_lock); + spin_lock_init(&ept->restart_lock); + init_waitqueue_head(&ept->restart_wait); + ept->restart_state = RESTART_NORMAL; + wake_lock_init(&ept->read_q_wake_lock, WAKE_LOCK_SUSPEND, "rpc_read"); + wake_lock_init(&ept->reply_q_wake_lock, WAKE_LOCK_SUSPEND, "rpc_reply"); + INIT_LIST_HEAD(&ept->incomplete); + spin_lock_init(&ept->incomplete_lock); + + spin_lock_irqsave(&local_endpoints_lock, flags); + list_add_tail(&ept->list, &local_endpoints); + spin_unlock_irqrestore(&local_endpoints_lock, flags); + return ept; +} + +int msm_rpcrouter_destroy_local_endpoint(struct msm_rpc_endpoint *ept) +{ + int rc; + union rr_control_msg msg; + struct msm_rpc_reply *reply, *reply_tmp; + unsigned long flags; + struct rpcrouter_xprt_info *xprt_info; + + /* Endpoint with dst_pid = 0xffffffff corresponds to that of + ** router port. So don't send a REMOVE CLIENT message while + ** destroying it.*/ + if (ept->dst_pid != 0xffffffff) { + msg.cmd = RPCROUTER_CTRL_CMD_REMOVE_CLIENT; + msg.cli.pid = ept->pid; + msg.cli.cid = ept->cid; + + RR("x REMOVE_CLIENT id=%d:%08x\n", ept->pid, ept->cid); + mutex_lock(&xprt_info_list_lock); + list_for_each_entry(xprt_info, &xprt_info_list, list) { + rc = rpcrouter_send_control_msg(xprt_info, &msg); + if (rc < 0) { + mutex_unlock(&xprt_info_list_lock); + return rc; + } + } + mutex_unlock(&xprt_info_list_lock); + } + + /* Free replies */ + spin_lock_irqsave(&ept->reply_q_lock, flags); + list_for_each_entry_safe(reply, reply_tmp, &ept->reply_pend_q, list) { + list_del(&reply->list); + kfree(reply); + } + list_for_each_entry_safe(reply, reply_tmp, &ept->reply_avail_q, list) { + list_del(&reply->list); + kfree(reply); + } + spin_unlock_irqrestore(&ept->reply_q_lock, flags); + + wake_lock_destroy(&ept->read_q_wake_lock); + wake_lock_destroy(&ept->reply_q_wake_lock); + spin_lock_irqsave(&local_endpoints_lock, flags); + list_del(&ept->list); + spin_unlock_irqrestore(&local_endpoints_lock, flags); + kfree(ept); + return 0; +} + +static int rpcrouter_create_remote_endpoint(uint32_t pid, uint32_t cid) +{ + struct rr_remote_endpoint *new_c; + unsigned long flags; + + new_c = kmalloc(sizeof(struct rr_remote_endpoint), GFP_KERNEL); + if (!new_c) + return -ENOMEM; + memset(new_c, 0, sizeof(struct rr_remote_endpoint)); + + new_c->cid = cid; + new_c->pid = pid; + init_waitqueue_head(&new_c->quota_wait); + spin_lock_init(&new_c->quota_lock); + + spin_lock_irqsave(&remote_endpoints_lock, flags); + list_add_tail(&new_c->list, &remote_endpoints); + new_c->quota_restart_state = RESTART_NORMAL; + spin_unlock_irqrestore(&remote_endpoints_lock, flags); + return 0; +} + +static struct msm_rpc_endpoint *rpcrouter_lookup_local_endpoint(uint32_t cid) +{ + struct msm_rpc_endpoint *ept; + unsigned long flags; + + spin_lock_irqsave(&local_endpoints_lock, flags); + list_for_each_entry(ept, &local_endpoints, list) { + if (ept->cid == cid) { + spin_unlock_irqrestore(&local_endpoints_lock, flags); + return ept; + } + } + spin_unlock_irqrestore(&local_endpoints_lock, flags); + return NULL; +} + +static struct rr_remote_endpoint *rpcrouter_lookup_remote_endpoint(uint32_t pid, + uint32_t cid) +{ + struct rr_remote_endpoint *ept; + unsigned long flags; + + spin_lock_irqsave(&remote_endpoints_lock, flags); + list_for_each_entry(ept, &remote_endpoints, list) { + if ((ept->pid == pid) && (ept->cid == cid)) { + spin_unlock_irqrestore(&remote_endpoints_lock, flags); + return ept; + } + } + spin_unlock_irqrestore(&remote_endpoints_lock, flags); + return NULL; +} + +static void handle_server_restart(struct rr_server *server, + uint32_t pid, uint32_t cid, + uint32_t prog, uint32_t vers) +{ + struct rr_remote_endpoint *r_ept; + struct msm_rpc_endpoint *ept; + unsigned long flags; + r_ept = rpcrouter_lookup_remote_endpoint(pid, cid); + if (r_ept && (r_ept->quota_restart_state != + RESTART_NORMAL)) { + spin_lock_irqsave(&r_ept->quota_lock, flags); + r_ept->tx_quota_cntr = 0; + r_ept->quota_restart_state = + RESTART_NORMAL; + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + D(KERN_INFO "rpcrouter: Remote EPT Reset %0x\n", + (unsigned int)r_ept); + wake_up(&r_ept->quota_wait); + } + spin_lock_irqsave(&local_endpoints_lock, flags); + list_for_each_entry(ept, &local_endpoints, list) { + if ((be32_to_cpu(ept->dst_prog) == prog) && + (be32_to_cpu(ept->dst_vers) == vers) && + (ept->restart_state & RESTART_PEND_SVR)) { + spin_lock(&ept->restart_lock); + ept->restart_state &= ~RESTART_PEND_SVR; + spin_unlock(&ept->restart_lock); + D("rpcrouter: Local EPT Reset %08x:%08x \n", + prog, vers); + wake_up(&ept->restart_wait); + wake_up(&ept->wait_q); + } + } + spin_unlock_irqrestore(&local_endpoints_lock, flags); +} + +static int process_control_msg(struct rpcrouter_xprt_info *xprt_info, + union rr_control_msg *msg, int len) +{ + union rr_control_msg ctl; + struct rr_server *server; + struct rr_remote_endpoint *r_ept; + int rc = 0; + unsigned long flags; + static int first = 1; + + if (len != sizeof(*msg)) { + RR(KERN_ERR "rpcrouter: r2r msg size %d != %d\n", + len, sizeof(*msg)); + return -EINVAL; + } + + switch (msg->cmd) { + case RPCROUTER_CTRL_CMD_HELLO: + RR("o HELLO PID %d\n", xprt_info->remote_pid); + memset(&ctl, 0, sizeof(ctl)); + ctl.cmd = RPCROUTER_CTRL_CMD_HELLO; + rpcrouter_send_control_msg(xprt_info, &ctl); + + xprt_info->initialized = 1; + + /* Send list of servers one at a time */ + ctl.cmd = RPCROUTER_CTRL_CMD_NEW_SERVER; + + /* TODO: long time to hold a spinlock... */ + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + if (server->pid != RPCROUTER_PID_LOCAL) + continue; + ctl.srv.pid = server->pid; + ctl.srv.cid = server->cid; + ctl.srv.prog = server->prog; + ctl.srv.vers = server->vers; + + RR("x NEW_SERVER id=%d:%08x prog=%08x:%08x\n", + server->pid, server->cid, + server->prog, server->vers); + + rpcrouter_send_control_msg(xprt_info, &ctl); + } + spin_unlock_irqrestore(&server_list_lock, flags); + + if (first) { + first = 0; + queue_work(rpcrouter_workqueue, + &work_create_rpcrouter_pdev); + } + break; + + case RPCROUTER_CTRL_CMD_RESUME_TX: + RR("o RESUME_TX id=%d:%08x\n", msg->cli.pid, msg->cli.cid); + + r_ept = rpcrouter_lookup_remote_endpoint(msg->cli.pid, + msg->cli.cid); + if (!r_ept) { + printk(KERN_ERR + "rpcrouter: Unable to resume client\n"); + break; + } + spin_lock_irqsave(&r_ept->quota_lock, flags); + r_ept->tx_quota_cntr = 0; + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + wake_up(&r_ept->quota_wait); + break; + + case RPCROUTER_CTRL_CMD_NEW_SERVER: + if (msg->srv.vers == 0) { + pr_err( + "rpcrouter: Server create rejected, version = 0, " + "program = %08x\n", msg->srv.prog); + break; + } + + RR("o NEW_SERVER id=%d:%08x prog=%08x:%08x\n", + msg->srv.pid, msg->srv.cid, msg->srv.prog, msg->srv.vers); + + server = rpcrouter_lookup_server(msg->srv.prog, msg->srv.vers); + + if (!server) { + server = rpcrouter_create_server( + msg->srv.pid, msg->srv.cid, + msg->srv.prog, msg->srv.vers); + if (!server) + return -ENOMEM; + /* + * XXX: Verify that its okay to add the + * client to our remote client list + * if we get a NEW_SERVER notification + */ + if (!rpcrouter_lookup_remote_endpoint(msg->srv.pid, + msg->srv.cid)) { + rc = rpcrouter_create_remote_endpoint( + msg->srv.pid, msg->srv.cid); + if (rc < 0) + printk(KERN_ERR + "rpcrouter:Client create" + "error (%d)\n", rc); + } + rpcrouter_register_board_dev(server); + schedule_work(&work_create_pdevs); + wake_up(&newserver_wait); + } else { + if ((server->pid == msg->srv.pid) && + (server->cid == msg->srv.cid)) { + handle_server_restart(server, + msg->srv.pid, + msg->srv.cid, + msg->srv.prog, + msg->srv.vers); + } else { + server->pid = msg->srv.pid; + server->cid = msg->srv.cid; + } + } + break; + + case RPCROUTER_CTRL_CMD_REMOVE_SERVER: + RR("o REMOVE_SERVER prog=%08x:%d\n", + msg->srv.prog, msg->srv.vers); + server = rpcrouter_lookup_server(msg->srv.prog, msg->srv.vers); + if (server) + rpcrouter_destroy_server(server); + break; + + case RPCROUTER_CTRL_CMD_REMOVE_CLIENT: + RR("o REMOVE_CLIENT id=%d:%08x\n", msg->cli.pid, msg->cli.cid); + if (msg->cli.pid == RPCROUTER_PID_LOCAL) { + printk(KERN_ERR + "rpcrouter: Denying remote removal of " + "local client\n"); + break; + } + r_ept = rpcrouter_lookup_remote_endpoint(msg->cli.pid, + msg->cli.cid); + if (r_ept) { + spin_lock_irqsave(&remote_endpoints_lock, flags); + list_del(&r_ept->list); + spin_unlock_irqrestore(&remote_endpoints_lock, flags); + kfree(r_ept); + } + + /* Notify local clients of this event */ + printk(KERN_ERR "rpcrouter: LOCAL NOTIFICATION NOT IMP\n"); + rc = -ENOSYS; + + break; + case RPCROUTER_CTRL_CMD_PING: + /* No action needed for ping messages received */ + RR("o PING\n"); + break; + default: + RR("o UNKNOWN(%08x)\n", msg->cmd); + rc = -ENOSYS; + } + + return rc; +} + +static void do_create_rpcrouter_pdev(struct work_struct *work) +{ + D("%s: modem rpc router up\n", __func__); + platform_device_register(&rpcrouter_pdev); + complete_all(&rpc_remote_router_up); +} + +static void do_create_pdevs(struct work_struct *work) +{ + unsigned long flags; + struct rr_server *server; + + /* TODO: race if destroyed while being registered */ + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + if (server->pid != RPCROUTER_PID_LOCAL) { + if (server->pdev_name[0] == 0) { + sprintf(server->pdev_name, "rs%.8x", + server->prog); + spin_unlock_irqrestore(&server_list_lock, + flags); + msm_rpcrouter_create_server_pdev(server); + schedule_work(&work_create_pdevs); + return; + } + } + } + spin_unlock_irqrestore(&server_list_lock, flags); +} + +static void *rr_malloc(unsigned sz) +{ + void *ptr = kmalloc(sz, GFP_KERNEL); + if (ptr) + return ptr; + + printk(KERN_ERR "rpcrouter: kmalloc of %d failed, retrying...\n", sz); + do { + ptr = kmalloc(sz, GFP_KERNEL); + } while (!ptr); + + return ptr; +} + +static int rr_read(struct rpcrouter_xprt_info *xprt_info, + void *data, uint32_t len) +{ + int rc; + unsigned long flags; + + while (!xprt_info->abort_data_read) { + spin_lock_irqsave(&xprt_info->lock, flags); + if (xprt_info->xprt->read_avail() >= len) { + rc = xprt_info->xprt->read(data, len); + spin_unlock_irqrestore(&xprt_info->lock, flags); + if (rc == len && !xprt_info->abort_data_read) + return 0; + else + return -EIO; + } + xprt_info->need_len = len; + wake_unlock(&xprt_info->wakelock); + spin_unlock_irqrestore(&xprt_info->lock, flags); + + wait_event(xprt_info->read_wait, + xprt_info->xprt->read_avail() >= len + || xprt_info->abort_data_read); + } + return -EIO; +} + +#if defined(CONFIG_MSM_ONCRPCROUTER_DEBUG) +static char *type_to_str(int i) +{ + switch (i) { + case RPCROUTER_CTRL_CMD_DATA: + return "data "; + case RPCROUTER_CTRL_CMD_HELLO: + return "hello "; + case RPCROUTER_CTRL_CMD_BYE: + return "bye "; + case RPCROUTER_CTRL_CMD_NEW_SERVER: + return "new_srvr"; + case RPCROUTER_CTRL_CMD_REMOVE_SERVER: + return "rmv_srvr"; + case RPCROUTER_CTRL_CMD_REMOVE_CLIENT: + return "rmv_clnt"; + case RPCROUTER_CTRL_CMD_RESUME_TX: + return "resum_tx"; + case RPCROUTER_CTRL_CMD_EXIT: + return "cmd_exit"; + default: + return "invalid"; + } +} +#endif + +static void do_read_data(struct work_struct *work) +{ + struct rr_header hdr; + struct rr_packet *pkt; + struct rr_fragment *frag; + struct msm_rpc_endpoint *ept; +#if defined(CONFIG_MSM_ONCRPCROUTER_DEBUG) + struct rpc_request_hdr *rq; +#endif + uint32_t pm, mid; + unsigned long flags; + + struct rpcrouter_xprt_info *xprt_info = + container_of(work, + struct rpcrouter_xprt_info, + read_data); + + if (rr_read(xprt_info, &hdr, sizeof(hdr))) + goto fail_io; + + RR("- ver=%d type=%d src=%d:%08x crx=%d siz=%d dst=%d:%08x\n", + hdr.version, hdr.type, hdr.src_pid, hdr.src_cid, + hdr.confirm_rx, hdr.size, hdr.dst_pid, hdr.dst_cid); + RAW_HDR("[r rr_h] " + "ver=%i,type=%s,src_pid=%08x,src_cid=%08x," + "confirm_rx=%i,size=%3i,dst_pid=%08x,dst_cid=%08x\n", + hdr.version, type_to_str(hdr.type), hdr.src_pid, hdr.src_cid, + hdr.confirm_rx, hdr.size, hdr.dst_pid, hdr.dst_cid); + + if (hdr.version != RPCROUTER_VERSION) { + DIAG("version %d != %d\n", hdr.version, RPCROUTER_VERSION); + goto fail_data; + } + if (hdr.size > RPCROUTER_MSGSIZE_MAX) { + DIAG("msg size %d > max %d\n", hdr.size, RPCROUTER_MSGSIZE_MAX); + goto fail_data; + } + + if (hdr.dst_cid == RPCROUTER_ROUTER_ADDRESS) { + if (xprt_info->remote_pid == -1) { + xprt_info->remote_pid = hdr.src_pid; + + /* do restart notification */ + modem_reset_startup(xprt_info); + } + + if (rr_read(xprt_info, xprt_info->r2r_buf, hdr.size)) + goto fail_io; + process_control_msg(xprt_info, + (void *) xprt_info->r2r_buf, hdr.size); + goto done; + } + + if (hdr.size < sizeof(pm)) { + DIAG("runt packet (no pacmark)\n"); + goto fail_data; + } + if (rr_read(xprt_info, &pm, sizeof(pm))) + goto fail_io; + + hdr.size -= sizeof(pm); + + frag = rr_malloc(sizeof(*frag)); + frag->next = NULL; + frag->length = hdr.size; + if (rr_read(xprt_info, frag->data, hdr.size)) { + kfree(frag); + goto fail_io; + } + +#if defined(CONFIG_MSM_ONCRPCROUTER_DEBUG) + if ((smd_rpcrouter_debug_mask & RAW_PMR) && + ((pm >> 30 & 0x1) || (pm >> 31 & 0x1))) { + uint32_t xid = 0; + if (pm >> 30 & 0x1) { + rq = (struct rpc_request_hdr *) frag->data; + xid = ntohl(rq->xid); + } + if ((pm >> 31 & 0x1) || (pm >> 30 & 0x1)) + RAW_PMR_NOMASK("xid:0x%03x first=%i,last=%i,mid=%3i," + "len=%3i,dst_cid=%08x\n", + xid, + pm >> 30 & 0x1, + pm >> 31 & 0x1, + pm >> 16 & 0xFF, + pm & 0xFFFF, hdr.dst_cid); + } + + if (smd_rpcrouter_debug_mask & SMEM_LOG) { + rq = (struct rpc_request_hdr *) frag->data; + if (rq->xid == 0) + smem_log_event(SMEM_LOG_PROC_ID_APPS | + RPC_ROUTER_LOG_EVENT_MID_READ, + PACMARK_MID(pm), + hdr.dst_cid, + hdr.src_cid); + else + smem_log_event(SMEM_LOG_PROC_ID_APPS | + RPC_ROUTER_LOG_EVENT_MSG_READ, + ntohl(rq->xid), + hdr.dst_cid, + hdr.src_cid); + } +#endif + + ept = rpcrouter_lookup_local_endpoint(hdr.dst_cid); + if (!ept) { + DIAG("no local ept for cid %08x\n", hdr.dst_cid); + kfree(frag); + goto done; + } + + /* See if there is already a partial packet that matches our mid + * and if so, append this fragment to that packet. + */ + mid = PACMARK_MID(pm); + spin_lock_irqsave(&ept->incomplete_lock, flags); + list_for_each_entry(pkt, &ept->incomplete, list) { + if (pkt->mid == mid) { + pkt->last->next = frag; + pkt->last = frag; + pkt->length += frag->length; + if (PACMARK_LAST(pm)) { + list_del(&pkt->list); + spin_unlock_irqrestore(&ept->incomplete_lock, + flags); + goto packet_complete; + } + spin_unlock_irqrestore(&ept->incomplete_lock, flags); + goto done; + } + } + spin_unlock_irqrestore(&ept->incomplete_lock, flags); + /* This mid is new -- create a packet for it, and put it on + * the incomplete list if this fragment is not a last fragment, + * otherwise put it on the read queue. + */ + pkt = rr_malloc(sizeof(struct rr_packet)); + pkt->first = frag; + pkt->last = frag; + memcpy(&pkt->hdr, &hdr, sizeof(hdr)); + pkt->mid = mid; + pkt->length = frag->length; + if (!PACMARK_LAST(pm)) { + list_add_tail(&pkt->list, &ept->incomplete); + goto done; + } + +packet_complete: + spin_lock_irqsave(&ept->read_q_lock, flags); + D("%s: take read lock on ept %p\n", __func__, ept); + wake_lock(&ept->read_q_wake_lock); + list_add_tail(&pkt->list, &ept->read_q); + wake_up(&ept->wait_q); + spin_unlock_irqrestore(&ept->read_q_lock, flags); +done: + + if (hdr.confirm_rx) { + union rr_control_msg msg; + + msg.cmd = RPCROUTER_CTRL_CMD_RESUME_TX; + msg.cli.pid = hdr.dst_pid; + msg.cli.cid = hdr.dst_cid; + + RR("x RESUME_TX id=%d:%08x\n", msg.cli.pid, msg.cli.cid); + rpcrouter_send_control_msg(xprt_info, &msg); + +#if defined(CONFIG_MSM_ONCRPCROUTER_DEBUG) + if (smd_rpcrouter_debug_mask & SMEM_LOG) + smem_log_event(SMEM_LOG_PROC_ID_APPS | + RPC_ROUTER_LOG_EVENT_MSG_CFM_SNT, + RPCROUTER_PID_LOCAL, + hdr.dst_cid, + hdr.src_cid); +#endif + + } + + /* don't requeue if we should be shutting down */ + if (!xprt_info->abort_data_read) { + queue_work(xprt_info->workqueue, &xprt_info->read_data); + return; + } + + D("rpc_router terminating for '%s'\n", + xprt_info->xprt->name); + +fail_io: +fail_data: + D(KERN_ERR "rpc_router has died for '%s'\n", + xprt_info->xprt->name); +} + +void msm_rpc_setup_req(struct rpc_request_hdr *hdr, uint32_t prog, + uint32_t vers, uint32_t proc) +{ + memset(hdr, 0, sizeof(struct rpc_request_hdr)); + hdr->xid = cpu_to_be32(atomic_add_return(1, &next_xid)); + hdr->rpc_vers = cpu_to_be32(2); + hdr->prog = cpu_to_be32(prog); + hdr->vers = cpu_to_be32(vers); + hdr->procedure = cpu_to_be32(proc); +} +EXPORT_SYMBOL(msm_rpc_setup_req); + +struct msm_rpc_endpoint *msm_rpc_open(void) +{ + struct msm_rpc_endpoint *ept; + + ept = msm_rpcrouter_create_local_endpoint(MKDEV(0, 0)); + if (ept == NULL) + return ERR_PTR(-ENOMEM); + + return ept; +} + +void msm_rpc_read_wakeup(struct msm_rpc_endpoint *ept) +{ + ept->forced_wakeup = 1; + wake_up(&ept->wait_q); +} + +int msm_rpc_close(struct msm_rpc_endpoint *ept) +{ + if (!ept) + return -EINVAL; + return msm_rpcrouter_destroy_local_endpoint(ept); +} +EXPORT_SYMBOL(msm_rpc_close); + +static int msm_rpc_write_pkt( + struct rr_header *hdr, + struct msm_rpc_endpoint *ept, + struct rr_remote_endpoint *r_ept, + void *buffer, + int count, + int first, + int last, + uint32_t mid + ) +{ +#if defined(CONFIG_MSM_ONCRPCROUTER_DEBUG) + struct rpc_request_hdr *rq = buffer; + uint32_t event_id; +#endif + uint32_t pacmark; + unsigned long flags = 0; + int rc; + struct rpcrouter_xprt_info *xprt_info; + int needed; + + DEFINE_WAIT(__wait); + + /* Create routing header */ + hdr->type = RPCROUTER_CTRL_CMD_DATA; + hdr->version = RPCROUTER_VERSION; + hdr->src_pid = ept->pid; + hdr->src_cid = ept->cid; + hdr->confirm_rx = 0; + hdr->size = count + sizeof(uint32_t); + + rc = wait_for_restart_and_notify(ept); + if (rc) + return rc; + + if (r_ept) { + for (;;) { + prepare_to_wait(&r_ept->quota_wait, &__wait, + TASK_INTERRUPTIBLE); + spin_lock_irqsave(&r_ept->quota_lock, flags); + if ((r_ept->tx_quota_cntr < + RPCROUTER_DEFAULT_RX_QUOTA) || + (r_ept->quota_restart_state != RESTART_NORMAL)) + break; + if (signal_pending(current) && + (!(ept->flags & MSM_RPC_UNINTERRUPTIBLE))) + break; + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + schedule(); + } + finish_wait(&r_ept->quota_wait, &__wait); + + if (r_ept->quota_restart_state != RESTART_NORMAL) { + spin_lock(&ept->restart_lock); + ept->restart_state &= ~RESTART_PEND_NTFY; + spin_unlock(&ept->restart_lock); + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + return -ENETRESET; + } + + if (signal_pending(current) && + (!(ept->flags & MSM_RPC_UNINTERRUPTIBLE))) { + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + return -ERESTARTSYS; + } + r_ept->tx_quota_cntr++; + if (r_ept->tx_quota_cntr == RPCROUTER_DEFAULT_RX_QUOTA) { + hdr->confirm_rx = 1; + +#if defined(CONFIG_MSM_ONCRPCROUTER_DEBUG) + if (smd_rpcrouter_debug_mask & SMEM_LOG) { + event_id = (rq->xid == 0) ? + RPC_ROUTER_LOG_EVENT_MID_CFM_REQ : + RPC_ROUTER_LOG_EVENT_MSG_CFM_REQ; + + smem_log_event(SMEM_LOG_PROC_ID_APPS | event_id, + hdr->dst_pid, + hdr->dst_cid, + hdr->src_cid); + } +#endif + + } + } + pacmark = PACMARK(count, mid, first, last); + + if (r_ept) + spin_unlock_irqrestore(&r_ept->quota_lock, flags); + + mutex_lock(&xprt_info_list_lock); + xprt_info = rpcrouter_get_xprt_info(hdr->dst_pid); + if (!xprt_info) { + mutex_unlock(&xprt_info_list_lock); + return -ENETRESET; + } + spin_lock_irqsave(&xprt_info->lock, flags); + mutex_unlock(&xprt_info_list_lock); + spin_lock(&ept->restart_lock); + if (ept->restart_state != RESTART_NORMAL) { + ept->restart_state &= ~RESTART_PEND_NTFY; + spin_unlock(&ept->restart_lock); + spin_unlock_irqrestore(&xprt_info->lock, flags); + return -ENETRESET; + } + + needed = sizeof(*hdr) + hdr->size; + while ((ept->restart_state == RESTART_NORMAL) && + (xprt_info->xprt->write_avail() < needed)) { + spin_unlock(&ept->restart_lock); + spin_unlock_irqrestore(&xprt_info->lock, flags); + msleep(250); + + /* refresh xprt pointer to ensure that it hasn't + * been deleted since our last retrieval */ + mutex_lock(&xprt_info_list_lock); + xprt_info = rpcrouter_get_xprt_info(hdr->dst_pid); + if (!xprt_info) { + mutex_unlock(&xprt_info_list_lock); + return -ENETRESET; + } + spin_lock_irqsave(&xprt_info->lock, flags); + mutex_unlock(&xprt_info_list_lock); + spin_lock(&ept->restart_lock); + } + if (ept->restart_state != RESTART_NORMAL) { + ept->restart_state &= ~RESTART_PEND_NTFY; + spin_unlock(&ept->restart_lock); + spin_unlock_irqrestore(&xprt_info->lock, flags); + return -ENETRESET; + } + + /* TODO: deal with full fifo */ + xprt_info->xprt->write(hdr, sizeof(*hdr), HEADER); + RAW_HDR("[w rr_h] " + "ver=%i,type=%s,src_pid=%08x,src_cid=%08x," + "confirm_rx=%i,size=%3i,dst_pid=%08x,dst_cid=%08x\n", + hdr->version, type_to_str(hdr->type), + hdr->src_pid, hdr->src_cid, + hdr->confirm_rx, hdr->size, hdr->dst_pid, hdr->dst_cid); + xprt_info->xprt->write(&pacmark, sizeof(pacmark), PACKMARK); + +#if defined(CONFIG_MSM_ONCRPCROUTER_DEBUG) + if ((smd_rpcrouter_debug_mask & RAW_PMW) && + ((pacmark >> 30 & 0x1) || (pacmark >> 31 & 0x1))) { + uint32_t xid = 0; + if (pacmark >> 30 & 0x1) + xid = ntohl(rq->xid); + if ((pacmark >> 31 & 0x1) || (pacmark >> 30 & 0x1)) + RAW_PMW_NOMASK("xid:0x%03x first=%i,last=%i,mid=%3i," + "len=%3i,src_cid=%x\n", + xid, + pacmark >> 30 & 0x1, + pacmark >> 31 & 0x1, + pacmark >> 16 & 0xFF, + pacmark & 0xFFFF, hdr->src_cid); + } +#endif + + xprt_info->xprt->write(buffer, count, PAYLOAD); + spin_unlock(&ept->restart_lock); + spin_unlock_irqrestore(&xprt_info->lock, flags); + +#if defined(CONFIG_MSM_ONCRPCROUTER_DEBUG) + if (smd_rpcrouter_debug_mask & SMEM_LOG) { + if (rq->xid == 0) + smem_log_event(SMEM_LOG_PROC_ID_APPS | + RPC_ROUTER_LOG_EVENT_MID_WRITTEN, + PACMARK_MID(pacmark), + hdr->dst_cid, + hdr->src_cid); + else + smem_log_event(SMEM_LOG_PROC_ID_APPS | + RPC_ROUTER_LOG_EVENT_MSG_WRITTEN, + ntohl(rq->xid), + hdr->dst_cid, + hdr->src_cid); + } +#endif + + return needed; +} + +static struct msm_rpc_reply *get_pend_reply(struct msm_rpc_endpoint *ept, + uint32_t xid) +{ + unsigned long flags; + struct msm_rpc_reply *reply; + spin_lock_irqsave(&ept->reply_q_lock, flags); + list_for_each_entry(reply, &ept->reply_pend_q, list) { + if (reply->xid == xid) { + list_del(&reply->list); + spin_unlock_irqrestore(&ept->reply_q_lock, flags); + return reply; + } + } + spin_unlock_irqrestore(&ept->reply_q_lock, flags); + return NULL; +} + +void get_requesting_client(struct msm_rpc_endpoint *ept, uint32_t xid, + struct msm_rpc_client_info *clnt_info) +{ + unsigned long flags; + struct msm_rpc_reply *reply; + + if (!clnt_info) + return; + + spin_lock_irqsave(&ept->reply_q_lock, flags); + list_for_each_entry(reply, &ept->reply_pend_q, list) { + if (reply->xid == xid) { + clnt_info->pid = reply->pid; + clnt_info->cid = reply->cid; + clnt_info->prog = reply->prog; + clnt_info->vers = reply->vers; + spin_unlock_irqrestore(&ept->reply_q_lock, flags); + return; + } + } + spin_unlock_irqrestore(&ept->reply_q_lock, flags); + return; +} + +static void set_avail_reply(struct msm_rpc_endpoint *ept, + struct msm_rpc_reply *reply) +{ + unsigned long flags; + spin_lock_irqsave(&ept->reply_q_lock, flags); + list_add_tail(&reply->list, &ept->reply_avail_q); + spin_unlock_irqrestore(&ept->reply_q_lock, flags); +} + +static struct msm_rpc_reply *get_avail_reply(struct msm_rpc_endpoint *ept) +{ + struct msm_rpc_reply *reply; + unsigned long flags; + if (list_empty(&ept->reply_avail_q)) { + if (ept->reply_cnt >= RPCROUTER_PEND_REPLIES_MAX) { + printk(KERN_ERR + "exceeding max replies of %d \n", + RPCROUTER_PEND_REPLIES_MAX); + return 0; + } + reply = kmalloc(sizeof(struct msm_rpc_reply), GFP_KERNEL); + if (!reply) + return 0; + D("Adding reply 0x%08x \n", (unsigned int)reply); + memset(reply, 0, sizeof(struct msm_rpc_reply)); + spin_lock_irqsave(&ept->reply_q_lock, flags); + ept->reply_cnt++; + spin_unlock_irqrestore(&ept->reply_q_lock, flags); + } else { + spin_lock_irqsave(&ept->reply_q_lock, flags); + reply = list_first_entry(&ept->reply_avail_q, + struct msm_rpc_reply, + list); + list_del(&reply->list); + spin_unlock_irqrestore(&ept->reply_q_lock, flags); + } + return reply; +} + +static void set_pend_reply(struct msm_rpc_endpoint *ept, + struct msm_rpc_reply *reply) +{ + unsigned long flags; + spin_lock_irqsave(&ept->reply_q_lock, flags); + D("%s: take reply lock on ept %p\n", __func__, ept); + wake_lock(&ept->reply_q_wake_lock); + list_add_tail(&reply->list, &ept->reply_pend_q); + spin_unlock_irqrestore(&ept->reply_q_lock, flags); +} + +int msm_rpc_write(struct msm_rpc_endpoint *ept, void *buffer, int count) +{ + struct rr_header hdr; + struct rpc_request_hdr *rq = buffer; + struct rr_remote_endpoint *r_ept; + struct msm_rpc_reply *reply = NULL; + int max_tx; + int tx_cnt; + char *tx_buf; + int rc; + int first_pkt = 1; + uint32_t mid; + unsigned long flags; + + /* snoop the RPC packet and enforce permissions */ + + /* has to have at least the xid and type fields */ + if (count < (sizeof(uint32_t) * 2)) { + printk(KERN_ERR "rr_write: rejecting runt packet\n"); + return -EINVAL; + } + + if (rq->type == 0) { + /* RPC CALL */ + if (count < (sizeof(uint32_t) * 6)) { + printk(KERN_ERR + "rr_write: rejecting runt call packet\n"); + return -EINVAL; + } + if (ept->dst_pid == 0xffffffff) { + printk(KERN_ERR "rr_write: not connected\n"); + return -ENOTCONN; + } + if ((ept->dst_prog != rq->prog) || + ((be32_to_cpu(ept->dst_vers) & 0x0fff0000) != + (be32_to_cpu(rq->vers) & 0x0fff0000))) { + printk(KERN_ERR + "rr_write: cannot write to %08x:%08x " + "(bound to %08x:%08x)\n", + be32_to_cpu(rq->prog), be32_to_cpu(rq->vers), + be32_to_cpu(ept->dst_prog), + be32_to_cpu(ept->dst_vers)); + return -EINVAL; + } + hdr.dst_pid = ept->dst_pid; + hdr.dst_cid = ept->dst_cid; + IO("CALL to %08x:%d @ %d:%08x (%d bytes)\n", + be32_to_cpu(rq->prog), be32_to_cpu(rq->vers), + ept->dst_pid, ept->dst_cid, count); + } else { + /* RPC REPLY */ + reply = get_pend_reply(ept, rq->xid); + if (!reply) { + printk(KERN_ERR + "rr_write: rejecting, reply not found \n"); + return -EINVAL; + } + hdr.dst_pid = reply->pid; + hdr.dst_cid = reply->cid; + IO("REPLY to xid=%d @ %d:%08x (%d bytes)\n", + be32_to_cpu(rq->xid), hdr.dst_pid, hdr.dst_cid, count); + } + + r_ept = rpcrouter_lookup_remote_endpoint(hdr.dst_pid, hdr.dst_cid); + + if ((!r_ept) && (hdr.dst_pid != RPCROUTER_PID_LOCAL)) { + printk(KERN_ERR + "msm_rpc_write(): No route to ept " + "[PID %x CID %x]\n", hdr.dst_pid, hdr.dst_cid); + count = -EHOSTUNREACH; + goto write_release_lock; + } + + tx_cnt = count; + tx_buf = buffer; + mid = atomic_add_return(1, &pm_mid) & 0xFF; + /* The modem's router can only take 500 bytes of data. The + first 8 bytes it uses on the modem side for addressing, + the next 4 bytes are for the pacmark header. */ + max_tx = RPCROUTER_MSGSIZE_MAX - 8 - sizeof(uint32_t); + IO("Writing %d bytes, max pkt size is %d\n", + tx_cnt, max_tx); + while (tx_cnt > 0) { + if (tx_cnt > max_tx) { + rc = msm_rpc_write_pkt(&hdr, ept, r_ept, + tx_buf, max_tx, + first_pkt, 0, mid); + if (rc < 0) { + count = rc; + goto write_release_lock; + } + IO("Wrote %d bytes First %d, Last 0 mid %d\n", + rc, first_pkt, mid); + tx_cnt -= max_tx; + tx_buf += max_tx; + } else { + rc = msm_rpc_write_pkt(&hdr, ept, r_ept, + tx_buf, tx_cnt, + first_pkt, 1, mid); + if (rc < 0) { + count = rc; + goto write_release_lock; + } + IO("Wrote %d bytes First %d Last 1 mid %d\n", + rc, first_pkt, mid); + break; + } + first_pkt = 0; + } + + write_release_lock: + /* if reply, release wakelock after writing to the transport */ + if (rq->type != 0) { + /* Upon failure, add reply tag to the pending list. + ** Else add reply tag to the avail/free list. */ + if (count < 0) + set_pend_reply(ept, reply); + else + set_avail_reply(ept, reply); + + spin_lock_irqsave(&ept->reply_q_lock, flags); + if (list_empty(&ept->reply_pend_q)) { + D("%s: release reply lock on ept %p\n", __func__, ept); + wake_unlock(&ept->reply_q_wake_lock); + } + spin_unlock_irqrestore(&ept->reply_q_lock, flags); + } + + return count; +} +EXPORT_SYMBOL(msm_rpc_write); + +/* + * NOTE: It is the responsibility of the caller to kfree buffer + */ +int msm_rpc_read(struct msm_rpc_endpoint *ept, void **buffer, + unsigned user_len, long timeout) +{ + struct rr_fragment *frag, *next; + char *buf; + int rc; + + rc = __msm_rpc_read(ept, &frag, user_len, timeout); + if (rc <= 0) + return rc; + + /* single-fragment messages conveniently can be + * returned as-is (the buffer is at the front) + */ + if (frag->next == 0) { + *buffer = (void*) frag; + return rc; + } + + /* multi-fragment messages, we have to do it the + * hard way, which is rather disgusting right now + */ + buf = rr_malloc(rc); + *buffer = buf; + + while (frag != NULL) { + memcpy(buf, frag->data, frag->length); + next = frag->next; + buf += frag->length; + kfree(frag); + frag = next; + } + + return rc; +} +EXPORT_SYMBOL(msm_rpc_read); + +int msm_rpc_call(struct msm_rpc_endpoint *ept, uint32_t proc, + void *_request, int request_size, + long timeout) +{ + return msm_rpc_call_reply(ept, proc, + _request, request_size, + NULL, 0, timeout); +} +EXPORT_SYMBOL(msm_rpc_call); + +int msm_rpc_call_reply(struct msm_rpc_endpoint *ept, uint32_t proc, + void *_request, int request_size, + void *_reply, int reply_size, + long timeout) +{ + struct rpc_request_hdr *req = _request; + struct rpc_reply_hdr *reply; + int rc; + + if (request_size < sizeof(*req)) + return -ETOOSMALL; + + if (ept->dst_pid == 0xffffffff) + return -ENOTCONN; + + memset(req, 0, sizeof(*req)); + req->xid = cpu_to_be32(atomic_add_return(1, &next_xid)); + req->rpc_vers = cpu_to_be32(2); + req->prog = ept->dst_prog; + req->vers = ept->dst_vers; + req->procedure = cpu_to_be32(proc); + + rc = msm_rpc_write(ept, req, request_size); + if (rc < 0) + return rc; + + for (;;) { + rc = msm_rpc_read(ept, (void*) &reply, -1, timeout); + if (rc < 0) + return rc; + if (rc < (3 * sizeof(uint32_t))) { + rc = -EIO; + break; + } + /* we should not get CALL packets -- ignore them */ + if (reply->type == 0) { + kfree(reply); + continue; + } + /* If an earlier call timed out, we could get the (no + * longer wanted) reply for it. Ignore replies that + * we don't expect + */ + if (reply->xid != req->xid) { + kfree(reply); + continue; + } + if (reply->reply_stat != 0) { + rc = -EPERM; + break; + } + if (reply->data.acc_hdr.accept_stat != 0) { + rc = -EINVAL; + break; + } + if (_reply == NULL) { + rc = 0; + break; + } + if (rc > reply_size) { + rc = -ENOMEM; + } else { + memcpy(_reply, reply, rc); + } + break; + } + kfree(reply); + return rc; +} +EXPORT_SYMBOL(msm_rpc_call_reply); + + +static inline int ept_packet_available(struct msm_rpc_endpoint *ept) +{ + unsigned long flags; + int ret; + spin_lock_irqsave(&ept->read_q_lock, flags); + ret = !list_empty(&ept->read_q); + spin_unlock_irqrestore(&ept->read_q_lock, flags); + return ret; +} + +int __msm_rpc_read(struct msm_rpc_endpoint *ept, + struct rr_fragment **frag_ret, + unsigned len, long timeout) +{ + struct rr_packet *pkt; + struct rpc_request_hdr *rq; + struct msm_rpc_reply *reply; + unsigned long flags; + int rc; + + rc = wait_for_restart_and_notify(ept); + if (rc) + return rc; + + IO("READ on ept %p\n", ept); + if (ept->flags & MSM_RPC_UNINTERRUPTIBLE) { + if (timeout < 0) { + wait_event(ept->wait_q, (ept_packet_available(ept) || + ept->forced_wakeup || + ept->restart_state)); + if (!msm_rpc_clear_netreset(ept)) + return -ENETRESET; + } else { + rc = wait_event_timeout( + ept->wait_q, + (ept_packet_available(ept) || + ept->forced_wakeup || + ept->restart_state), + timeout); + if (!msm_rpc_clear_netreset(ept)) + return -ENETRESET; + if (rc == 0) + return -ETIMEDOUT; + } + } else { + if (timeout < 0) { + rc = wait_event_interruptible( + ept->wait_q, (ept_packet_available(ept) || + ept->forced_wakeup || + ept->restart_state)); + if (!msm_rpc_clear_netreset(ept)) + return -ENETRESET; + if (rc < 0) + return rc; + } else { + rc = wait_event_interruptible_timeout( + ept->wait_q, + (ept_packet_available(ept) || + ept->forced_wakeup || + ept->restart_state), + timeout); + if (!msm_rpc_clear_netreset(ept)) + return -ENETRESET; + if (rc == 0) + return -ETIMEDOUT; + } + } + + if (ept->forced_wakeup) { + ept->forced_wakeup = 0; + return 0; + } + + spin_lock_irqsave(&ept->read_q_lock, flags); + if (list_empty(&ept->read_q)) { + spin_unlock_irqrestore(&ept->read_q_lock, flags); + return -EAGAIN; + } + pkt = list_first_entry(&ept->read_q, struct rr_packet, list); + if (pkt->length > len) { + spin_unlock_irqrestore(&ept->read_q_lock, flags); + return -ETOOSMALL; + } + list_del(&pkt->list); + spin_unlock_irqrestore(&ept->read_q_lock, flags); + + rc = pkt->length; + + *frag_ret = pkt->first; + rq = (void*) pkt->first->data; + if ((rc >= (sizeof(uint32_t) * 3)) && (rq->type == 0)) { + /* RPC CALL */ + reply = get_avail_reply(ept); + if (!reply) { + rc = -ENOMEM; + goto read_release_lock; + } + reply->cid = pkt->hdr.src_cid; + reply->pid = pkt->hdr.src_pid; + reply->xid = rq->xid; + reply->prog = rq->prog; + reply->vers = rq->vers; + set_pend_reply(ept, reply); + } + + kfree(pkt); + + IO("READ on ept %p (%d bytes)\n", ept, rc); + + read_release_lock: + + /* release read wakelock after taking reply wakelock */ + spin_lock_irqsave(&ept->read_q_lock, flags); + if (list_empty(&ept->read_q)) { + D("%s: release read lock on ept %p\n", __func__, ept); + wake_unlock(&ept->read_q_wake_lock); + } + spin_unlock_irqrestore(&ept->read_q_lock, flags); + + return rc; +} + +int msm_rpc_is_compatible_version(uint32_t server_version, + uint32_t client_version) +{ + + if ((server_version & RPC_VERSION_MODE_MASK) != + (client_version & RPC_VERSION_MODE_MASK)) + return 0; + + if (server_version & RPC_VERSION_MODE_MASK) + return server_version == client_version; + + return ((server_version & RPC_VERSION_MAJOR_MASK) == + (client_version & RPC_VERSION_MAJOR_MASK)) && + ((server_version & RPC_VERSION_MINOR_MASK) >= + (client_version & RPC_VERSION_MINOR_MASK)); +} +EXPORT_SYMBOL(msm_rpc_is_compatible_version); + +static struct rr_server *msm_rpc_get_server(uint32_t prog, uint32_t vers, + uint32_t accept_compatible, + uint32_t *found_prog) +{ + struct rr_server *server; + unsigned long flags; + + if (found_prog == NULL) + return NULL; + + *found_prog = 0; + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(server, &server_list, list) { + if (server->prog == prog) { + *found_prog = 1; + spin_unlock_irqrestore(&server_list_lock, flags); + if (accept_compatible) { + if (msm_rpc_is_compatible_version(server->vers, + vers)) { + return server; + } else { + return NULL; + } + } else if (server->vers == vers) { + return server; + } else + return NULL; + } + } + spin_unlock_irqrestore(&server_list_lock, flags); + return NULL; +} + +static struct msm_rpc_endpoint *__msm_rpc_connect(uint32_t prog, uint32_t vers, + uint32_t accept_compatible, + unsigned flags) +{ + struct msm_rpc_endpoint *ept; + struct rr_server *server; + uint32_t found_prog; + int rc = 0; + + DEFINE_WAIT(__wait); + + for (;;) { + prepare_to_wait(&newserver_wait, &__wait, + TASK_INTERRUPTIBLE); + + server = msm_rpc_get_server(prog, vers, accept_compatible, + &found_prog); + if (server) + break; + + if (found_prog) { + pr_info("%s: server not found %x:%x\n", + __func__, prog, vers); + rc = -EHOSTUNREACH; + break; + } + + if (msm_rpc_connect_timeout_ms == 0) { + rc = -EHOSTUNREACH; + break; + } + + if (signal_pending(current)) { + rc = -ERESTARTSYS; + break; + } + + rc = schedule_timeout( + msecs_to_jiffies(msm_rpc_connect_timeout_ms)); + if (!rc) { + rc = -ETIMEDOUT; + break; + } + } + finish_wait(&newserver_wait, &__wait); + + if (!server) + return ERR_PTR(rc); + + if (accept_compatible && (server->vers != vers)) { + D("RPC Using new version 0x%08x(0x%08x) prog 0x%08x", + vers, server->vers, prog); + D(" ... Continuing\n"); + } + + ept = msm_rpc_open(); + if (IS_ERR(ept)) + return ept; + + ept->flags = flags; + ept->dst_pid = server->pid; + ept->dst_cid = server->cid; + ept->dst_prog = cpu_to_be32(prog); + ept->dst_vers = cpu_to_be32(server->vers); + + return ept; +} + +struct msm_rpc_endpoint *msm_rpc_connect_compatible(uint32_t prog, + uint32_t vers, unsigned flags) +{ + return __msm_rpc_connect(prog, vers, 1, flags); +} +EXPORT_SYMBOL(msm_rpc_connect_compatible); + +struct msm_rpc_endpoint *msm_rpc_connect(uint32_t prog, + uint32_t vers, unsigned flags) +{ + return __msm_rpc_connect(prog, vers, 0, flags); +} +EXPORT_SYMBOL(msm_rpc_connect); + +/* TODO: permission check? */ +int msm_rpc_register_server(struct msm_rpc_endpoint *ept, + uint32_t prog, uint32_t vers) +{ + int rc; + union rr_control_msg msg; + struct rr_server *server; + struct rpcrouter_xprt_info *xprt_info; + + server = rpcrouter_create_server(ept->pid, ept->cid, + prog, vers); + if (!server) + return -ENODEV; + + msg.srv.cmd = RPCROUTER_CTRL_CMD_NEW_SERVER; + msg.srv.pid = ept->pid; + msg.srv.cid = ept->cid; + msg.srv.prog = prog; + msg.srv.vers = vers; + + RR("x NEW_SERVER id=%d:%08x prog=%08x:%08x\n", + ept->pid, ept->cid, prog, vers); + + mutex_lock(&xprt_info_list_lock); + list_for_each_entry(xprt_info, &xprt_info_list, list) { + rc = rpcrouter_send_control_msg(xprt_info, &msg); + if (rc < 0) { + mutex_unlock(&xprt_info_list_lock); + return rc; + } + } + mutex_unlock(&xprt_info_list_lock); + return 0; +} + +int msm_rpc_clear_netreset(struct msm_rpc_endpoint *ept) +{ + unsigned long flags; + int rc = 1; + spin_lock_irqsave(&ept->restart_lock, flags); + if (ept->restart_state != RESTART_NORMAL) { + ept->restart_state &= ~RESTART_PEND_NTFY; + rc = 0; + } + spin_unlock_irqrestore(&ept->restart_lock, flags); + return rc; +} + +/* TODO: permission check -- disallow unreg of somebody else's server */ +int msm_rpc_unregister_server(struct msm_rpc_endpoint *ept, + uint32_t prog, uint32_t vers) +{ + struct rr_server *server; + server = rpcrouter_lookup_server(prog, vers); + + if (!server) + return -ENOENT; + rpcrouter_destroy_server(server); + return 0; +} + +int msm_rpc_get_curr_pkt_size(struct msm_rpc_endpoint *ept) +{ + unsigned long flags; + struct rr_packet *pkt; + int rc = 0; + + if (!ept) + return -EINVAL; + + if (!msm_rpc_clear_netreset(ept)) + return -ENETRESET; + + spin_lock_irqsave(&ept->read_q_lock, flags); + if (!list_empty(&ept->read_q)) { + pkt = list_first_entry(&ept->read_q, struct rr_packet, list); + rc = pkt->length; + } + spin_unlock_irqrestore(&ept->read_q_lock, flags); + + return rc; +} + +int msm_rpcrouter_close(void) +{ + struct rpcrouter_xprt_info *xprt_info, *tmp_xprt_info; + union rr_control_msg ctl; + + ctl.cmd = RPCROUTER_CTRL_CMD_BYE; + mutex_lock(&xprt_info_list_lock); + list_for_each_entry_safe(xprt_info, tmp_xprt_info, + &xprt_info_list, list) { + rpcrouter_send_control_msg(xprt_info, &ctl); + xprt_info->xprt->close(); + list_del(&xprt_info->list); + kfree(xprt_info); + } + mutex_unlock(&xprt_info_list_lock); + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +static int dump_servers(char *buf, int max) +{ + int i = 0; + unsigned long flags; + struct rr_server *svr; + const char *sym; + + spin_lock_irqsave(&server_list_lock, flags); + list_for_each_entry(svr, &server_list, list) { + i += scnprintf(buf + i, max - i, "pdev_name: %s\n", + svr->pdev_name); + i += scnprintf(buf + i, max - i, "pid: 0x%08x\n", svr->pid); + i += scnprintf(buf + i, max - i, "cid: 0x%08x\n", svr->cid); + i += scnprintf(buf + i, max - i, "prog: 0x%08x", svr->prog); + sym = smd_rpc_get_sym(svr->prog); + if (sym) + i += scnprintf(buf + i, max - i, " (%s)\n", sym); + else + i += scnprintf(buf + i, max - i, "\n"); + i += scnprintf(buf + i, max - i, "vers: 0x%08x\n", svr->vers); + i += scnprintf(buf + i, max - i, "\n"); + } + spin_unlock_irqrestore(&server_list_lock, flags); + + return i; +} + +static int dump_remote_endpoints(char *buf, int max) +{ + int i = 0; + unsigned long flags; + struct rr_remote_endpoint *ept; + + spin_lock_irqsave(&remote_endpoints_lock, flags); + list_for_each_entry(ept, &remote_endpoints, list) { + i += scnprintf(buf + i, max - i, "pid: 0x%08x\n", ept->pid); + i += scnprintf(buf + i, max - i, "cid: 0x%08x\n", ept->cid); + i += scnprintf(buf + i, max - i, "tx_quota_cntr: %i\n", + ept->tx_quota_cntr); + i += scnprintf(buf + i, max - i, "quota_restart_state: %i\n", + ept->quota_restart_state); + i += scnprintf(buf + i, max - i, "\n"); + } + spin_unlock_irqrestore(&remote_endpoints_lock, flags); + + return i; +} + +static int dump_msm_rpc_endpoint(char *buf, int max) +{ + int i = 0; + unsigned long flags; + struct msm_rpc_reply *reply; + struct msm_rpc_endpoint *ept; + struct rr_packet *pkt; + const char *sym; + + spin_lock_irqsave(&local_endpoints_lock, flags); + list_for_each_entry(ept, &local_endpoints, list) { + i += scnprintf(buf + i, max - i, "pid: 0x%08x\n", ept->pid); + i += scnprintf(buf + i, max - i, "cid: 0x%08x\n", ept->cid); + i += scnprintf(buf + i, max - i, "dst_pid: 0x%08x\n", + ept->dst_pid); + i += scnprintf(buf + i, max - i, "dst_cid: 0x%08x\n", + ept->dst_cid); + i += scnprintf(buf + i, max - i, "dst_prog: 0x%08x", + be32_to_cpu(ept->dst_prog)); + sym = smd_rpc_get_sym(be32_to_cpu(ept->dst_prog)); + if (sym) + i += scnprintf(buf + i, max - i, " (%s)\n", sym); + else + i += scnprintf(buf + i, max - i, "\n"); + i += scnprintf(buf + i, max - i, "dst_vers: 0x%08x\n", + be32_to_cpu(ept->dst_vers)); + i += scnprintf(buf + i, max - i, "reply_cnt: %i\n", + ept->reply_cnt); + i += scnprintf(buf + i, max - i, "restart_state: %i\n", + ept->restart_state); + + i += scnprintf(buf + i, max - i, "outstanding xids:\n"); + spin_lock(&ept->reply_q_lock); + list_for_each_entry(reply, &ept->reply_pend_q, list) + i += scnprintf(buf + i, max - i, " xid = %u\n", + ntohl(reply->xid)); + spin_unlock(&ept->reply_q_lock); + + i += scnprintf(buf + i, max - i, "complete unread packets:\n"); + spin_lock(&ept->read_q_lock); + list_for_each_entry(pkt, &ept->read_q, list) { + i += scnprintf(buf + i, max - i, " mid = %i\n", + pkt->mid); + i += scnprintf(buf + i, max - i, " length = %i\n", + pkt->length); + } + spin_unlock(&ept->read_q_lock); + i += scnprintf(buf + i, max - i, "\n"); + } + spin_unlock_irqrestore(&local_endpoints_lock, flags); + + return i; +} + +#define DEBUG_BUFMAX 4096 +static char debug_buffer[DEBUG_BUFMAX]; + +static ssize_t debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int (*fill)(char *buf, int max) = file->private_data; + int bsize = fill(debug_buffer, DEBUG_BUFMAX); + return simple_read_from_buffer(buf, count, ppos, debug_buffer, bsize); +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations debug_ops = { + .read = debug_read, + .open = debug_open, +}; + +static void debug_create(const char *name, mode_t mode, + struct dentry *dent, + int (*fill)(char *buf, int max)) +{ + debugfs_create_file(name, mode, dent, fill, &debug_ops); +} + +static void debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("smd_rpcrouter", 0); + if (IS_ERR(dent)) + return; + + debug_create("dump_msm_rpc_endpoints", 0444, dent, + dump_msm_rpc_endpoint); + debug_create("dump_remote_endpoints", 0444, dent, + dump_remote_endpoints); + debug_create("dump_servers", 0444, dent, + dump_servers); + +} + +#else +static void debugfs_init(void) {} +#endif + +static int msm_rpcrouter_add_xprt(struct rpcrouter_xprt *xprt) +{ + struct rpcrouter_xprt_info *xprt_info; + + D("Registering xprt %s to RPC Router\n", xprt->name); + + xprt_info = kmalloc(sizeof(struct rpcrouter_xprt_info), GFP_KERNEL); + if (!xprt_info) + return -ENOMEM; + + xprt_info->xprt = xprt; + xprt_info->initialized = 0; + xprt_info->remote_pid = -1; + init_waitqueue_head(&xprt_info->read_wait); + spin_lock_init(&xprt_info->lock); + wake_lock_init(&xprt_info->wakelock, + WAKE_LOCK_SUSPEND, xprt->name); + xprt_info->need_len = 0; + xprt_info->abort_data_read = 0; + INIT_WORK(&xprt_info->read_data, do_read_data); + INIT_LIST_HEAD(&xprt_info->list); + + xprt_info->workqueue = create_singlethread_workqueue(xprt->name); + if (!xprt_info->workqueue) { + kfree(xprt_info); + return -ENOMEM; + } + + if (!strcmp(xprt->name, "rpcrouter_loopback_xprt")) { + xprt_info->remote_pid = RPCROUTER_PID_LOCAL; + xprt_info->initialized = 1; + } else { + smsm_change_state(SMSM_APPS_STATE, 0, SMSM_RPCINIT); + } + + mutex_lock(&xprt_info_list_lock); + list_add_tail(&xprt_info->list, &xprt_info_list); + mutex_unlock(&xprt_info_list_lock); + + queue_work(xprt_info->workqueue, &xprt_info->read_data); + + xprt->priv = xprt_info; + + return 0; +} + +static void msm_rpcrouter_remove_xprt(struct rpcrouter_xprt *xprt) +{ + struct rpcrouter_xprt_info *xprt_info; + unsigned long flags; + + if (xprt && xprt->priv) { + xprt_info = xprt->priv; + + /* abort rr_read thread */ + xprt_info->abort_data_read = 1; + wake_up(&xprt_info->read_wait); + + /* remove xprt from available xprts */ + mutex_lock(&xprt_info_list_lock); + spin_lock_irqsave(&xprt_info->lock, flags); + list_del(&xprt_info->list); + + /* unlock the spinlock last to avoid a race + * condition with rpcrouter_get_xprt_info + * in msm_rpc_write_pkt in which the + * xprt is returned from rpcrouter_get_xprt_info + * and then deleted here. */ + mutex_unlock(&xprt_info_list_lock); + spin_unlock_irqrestore(&xprt_info->lock, flags); + + /* cleanup workqueues and wakelocks */ + flush_workqueue(xprt_info->workqueue); + destroy_workqueue(xprt_info->workqueue); + wake_lock_destroy(&xprt_info->wakelock); + + + /* free memory */ + xprt->priv = 0; + kfree(xprt_info); + } +} + +struct rpcrouter_xprt_work { + struct rpcrouter_xprt *xprt; + struct work_struct work; +}; + +static void xprt_open_worker(struct work_struct *work) +{ + struct rpcrouter_xprt_work *xprt_work = + container_of(work, struct rpcrouter_xprt_work, work); + + msm_rpcrouter_add_xprt(xprt_work->xprt); + + kfree(xprt_work); +} + +static void xprt_close_worker(struct work_struct *work) +{ + struct rpcrouter_xprt_work *xprt_work = + container_of(work, struct rpcrouter_xprt_work, work); + + modem_reset_cleanup(xprt_work->xprt->priv); + msm_rpcrouter_remove_xprt(xprt_work->xprt); + + if (atomic_dec_return(&pending_close_count) == 0) + wake_up(&subsystem_restart_wait); + + kfree(xprt_work); +} + +void msm_rpcrouter_xprt_notify(struct rpcrouter_xprt *xprt, unsigned event) +{ + struct rpcrouter_xprt_info *xprt_info; + struct rpcrouter_xprt_work *xprt_work; + + /* Workqueue is created in init function which works for all existing + * clients. If this fails in the future, then it will need to be + * created earlier. */ + BUG_ON(!rpcrouter_workqueue); + + switch (event) { + case RPCROUTER_XPRT_EVENT_OPEN: + D("open event for '%s'\n", xprt->name); + xprt_work = kmalloc(sizeof(struct rpcrouter_xprt_work), + GFP_ATOMIC); + xprt_work->xprt = xprt; + INIT_WORK(&xprt_work->work, xprt_open_worker); + queue_work(rpcrouter_workqueue, &xprt_work->work); + break; + + case RPCROUTER_XPRT_EVENT_CLOSE: + D("close event for '%s'\n", xprt->name); + + atomic_inc(&pending_close_count); + + xprt_work = kmalloc(sizeof(struct rpcrouter_xprt_work), + GFP_ATOMIC); + xprt_work->xprt = xprt; + INIT_WORK(&xprt_work->work, xprt_close_worker); + queue_work(rpcrouter_workqueue, &xprt_work->work); + break; + } + + xprt_info = xprt->priv; + if (xprt_info) { + /* Check read_avail even for OPEN event to handle missed + DATA events while processing the OPEN event*/ + if (xprt->read_avail() >= xprt_info->need_len) + wake_lock(&xprt_info->wakelock); + wake_up(&xprt_info->read_wait); + } +} + +static int modem_restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data); +static struct notifier_block nb = { + .notifier_call = modem_restart_notifier_cb, +}; + +static int modem_restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data) +{ + switch (code) { + case SUBSYS_BEFORE_SHUTDOWN: + D("%s: SUBSYS_BEFORE_SHUTDOWN\n", __func__); + break; + + case SUBSYS_BEFORE_POWERUP: + D("%s: waiting for RPC restart to complete\n", __func__); + wait_event(subsystem_restart_wait, + atomic_read(&pending_close_count) == 0); + D("%s: finished restart wait\n", __func__); + break; + + default: + break; + } + + return NOTIFY_DONE; +} + +static void *restart_notifier_handle; +static __init int modem_restart_late_init(void) +{ + restart_notifier_handle = subsys_notif_register_notifier("modem", &nb); + return 0; +} +late_initcall(modem_restart_late_init); + +static int __init rpcrouter_init(void) +{ + int ret; + + msm_rpc_connect_timeout_ms = 0; + smd_rpcrouter_debug_mask |= SMEM_LOG; + debugfs_init(); + + + /* Initialize what we need to start processing */ + rpcrouter_workqueue = + create_singlethread_workqueue("rpcrouter"); + if (!rpcrouter_workqueue) { + msm_rpcrouter_exit_devices(); + return -ENOMEM; + } + + init_waitqueue_head(&newserver_wait); + init_waitqueue_head(&subsystem_restart_wait); + + ret = msm_rpcrouter_init_devices(); + if (ret < 0) + return ret; + + return ret; +} + +module_init(rpcrouter_init); +MODULE_DESCRIPTION("MSM RPC Router"); +MODULE_AUTHOR("San Mehat "); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/smd_rpcrouter.h b/arch/arm/mach-msm/smd_rpcrouter.h new file mode 100644 index 00000000000..1da7579aef8 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter.h @@ -0,0 +1,266 @@ +/** arch/arm/mach-msm/smd_rpcrouter.h + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2011, Code Aurora Forum. All rights reserved. + * Author: San Mehat + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_SMD_RPCROUTER_H +#define _ARCH_ARM_MACH_MSM_SMD_RPCROUTER_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* definitions for the R2R wire protcol */ + +#define RPCROUTER_VERSION 1 +#define RPCROUTER_PROCESSORS_MAX 4 +#define RPCROUTER_MSGSIZE_MAX 512 +#define RPCROUTER_PEND_REPLIES_MAX 32 + +#define RPCROUTER_CLIENT_BCAST_ID 0xffffffff +#define RPCROUTER_ROUTER_ADDRESS 0xfffffffe + +#define RPCROUTER_PID_LOCAL 1 + +#define RPCROUTER_CTRL_CMD_DATA 1 +#define RPCROUTER_CTRL_CMD_HELLO 2 +#define RPCROUTER_CTRL_CMD_BYE 3 +#define RPCROUTER_CTRL_CMD_NEW_SERVER 4 +#define RPCROUTER_CTRL_CMD_REMOVE_SERVER 5 +#define RPCROUTER_CTRL_CMD_REMOVE_CLIENT 6 +#define RPCROUTER_CTRL_CMD_RESUME_TX 7 +#define RPCROUTER_CTRL_CMD_EXIT 8 +#define RPCROUTER_CTRL_CMD_PING 9 + +#define RPCROUTER_DEFAULT_RX_QUOTA 5 + +#define RPCROUTER_XPRT_EVENT_DATA 1 +#define RPCROUTER_XPRT_EVENT_OPEN 2 +#define RPCROUTER_XPRT_EVENT_CLOSE 3 + +/* Restart states for endpoint. + * + * Two different bits are specified here, one for + * the remote server notification (RESTART_PEND_SVR) + * and one for client notification (RESTART_PEND_NTFY). + * The client notification is used to ensure that + * the client gets notified by an ENETRESET return + * code at least once, even if they miss the actual + * reset event. The server notification is used to + * properly handle the reset state of the endpoint. + */ +#define RESTART_NORMAL 0x0 +#define RESTART_PEND_SVR 0x1 +#define RESTART_PEND_NTFY 0x2 +#define RESTART_PEND_NTFY_SVR (RESTART_PEND_SVR | RESTART_PEND_NTFY) + +union rr_control_msg { + uint32_t cmd; + struct { + uint32_t cmd; + uint32_t prog; + uint32_t vers; + uint32_t pid; + uint32_t cid; + } srv; + struct { + uint32_t cmd; + uint32_t pid; + uint32_t cid; + } cli; +}; + +struct rr_header { + uint32_t version; + uint32_t type; + uint32_t src_pid; + uint32_t src_cid; + uint32_t confirm_rx; + uint32_t size; + uint32_t dst_pid; + uint32_t dst_cid; +}; + +/* internals */ + +#define RPCROUTER_MAX_REMOTE_SERVERS 100 + +struct rr_fragment { + unsigned char data[RPCROUTER_MSGSIZE_MAX]; + uint32_t length; + struct rr_fragment *next; +}; + +struct rr_packet { + struct list_head list; + struct rr_fragment *first; + struct rr_fragment *last; + struct rr_header hdr; + uint32_t mid; + uint32_t length; +}; + +#define PACMARK_LAST(n) ((n) & 0x80000000) +#define PACMARK_MID(n) (((n) >> 16) & 0xFF) +#define PACMARK_LEN(n) ((n) & 0xFFFF) + +static inline uint32_t PACMARK(uint32_t len, uint32_t mid, uint32_t first, + uint32_t last) +{ + return (len & 0xFFFF) | + ((mid & 0xFF) << 16) | + ((!!first) << 30) | + ((!!last) << 31); +} + +struct rr_server { + struct list_head list; + + uint32_t pid; + uint32_t cid; + uint32_t prog; + uint32_t vers; + + dev_t device_number; + struct cdev cdev; + struct device *device; + struct rpcsvr_platform_device p_device; + char pdev_name[32]; +}; + +struct rr_remote_endpoint { + uint32_t pid; + uint32_t cid; + + int tx_quota_cntr; + int quota_restart_state; + spinlock_t quota_lock; + wait_queue_head_t quota_wait; + + struct list_head list; +}; + +struct msm_rpc_reply { + struct list_head list; + uint32_t pid; + uint32_t cid; + uint32_t prog; /* be32 */ + uint32_t vers; /* be32 */ + uint32_t xid; /* be32 */ +}; + +struct msm_rpc_endpoint { + struct list_head list; + + /* incomplete packets waiting for assembly */ + struct list_head incomplete; + spinlock_t incomplete_lock; + + /* complete packets waiting to be read */ + struct list_head read_q; + spinlock_t read_q_lock; + struct wake_lock read_q_wake_lock; + wait_queue_head_t wait_q; + unsigned flags; + uint32_t forced_wakeup; + + /* restart handling */ + int restart_state; + spinlock_t restart_lock; + wait_queue_head_t restart_wait; + + /* modem restart notifications */ + int do_setup_notif; + void *client_data; + void (*cb_restart_teardown)(void *client_data); + void (*cb_restart_setup)(void *client_data); + + /* endpoint address */ + uint32_t pid; + uint32_t cid; + + /* bound remote address + * if not connected (dst_pid == 0xffffffff) RPC_CALL writes fail + * RPC_CALLs must be to the prog/vers below or they will fail + */ + uint32_t dst_pid; + uint32_t dst_cid; + uint32_t dst_prog; /* be32 */ + uint32_t dst_vers; /* be32 */ + + /* reply queue for inbound messages */ + struct list_head reply_pend_q; + struct list_head reply_avail_q; + spinlock_t reply_q_lock; + uint32_t reply_cnt; + struct wake_lock reply_q_wake_lock; + + /* device node if this endpoint is accessed via userspace */ + dev_t dev; +}; + +enum write_data_type { + HEADER = 1, + PACKMARK, + PAYLOAD, +}; + +struct rpcrouter_xprt { + char *name; + void *priv; + + int (*read_avail)(void); + int (*read)(void *data, uint32_t len); + int (*write_avail)(void); + int (*write)(void *data, uint32_t len, enum write_data_type type); + int (*close)(void); +}; + +/* shared between smd_rpcrouter*.c */ +void msm_rpcrouter_xprt_notify(struct rpcrouter_xprt *xprt, unsigned event); +int __msm_rpc_read(struct msm_rpc_endpoint *ept, + struct rr_fragment **frag, + unsigned len, long timeout); + +int msm_rpcrouter_close(void); +struct msm_rpc_endpoint *msm_rpcrouter_create_local_endpoint(dev_t dev); +int msm_rpcrouter_destroy_local_endpoint(struct msm_rpc_endpoint *ept); + +int msm_rpcrouter_create_server_cdev(struct rr_server *server); +int msm_rpcrouter_create_server_pdev(struct rr_server *server); + +int msm_rpcrouter_init_devices(void); +void msm_rpcrouter_exit_devices(void); + +void get_requesting_client(struct msm_rpc_endpoint *ept, uint32_t xid, + struct msm_rpc_client_info *clnt_info); + +extern dev_t msm_rpcrouter_devno; +extern struct completion rpc_remote_router_up; +extern struct class *msm_rpcrouter_class; + +void xdr_init(struct msm_rpc_xdr *xdr); +void xdr_init_input(struct msm_rpc_xdr *xdr, void *buf, uint32_t size); +void xdr_init_output(struct msm_rpc_xdr *xdr, void *buf, uint32_t size); +void xdr_clean_input(struct msm_rpc_xdr *xdr); +void xdr_clean_output(struct msm_rpc_xdr *xdr); +uint32_t xdr_read_avail(struct msm_rpc_xdr *xdr); +#endif diff --git a/arch/arm/mach-msm/smd_rpcrouter_clients.c b/arch/arm/mach-msm/smd_rpcrouter_clients.c new file mode 100644 index 00000000000..8190321c8c2 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter_clients.c @@ -0,0 +1,916 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * SMD RPCROUTER CLIENTS module. + */ + +#include +#include +#include +#include + +#include +#include "smd_rpcrouter.h" + +struct msm_rpc_client_cb_item { + struct list_head list; + + void *buf; + int size; +}; + +struct msm_rpc_cb_table_item { + struct list_head list; + + uint32_t cb_id; + void *cb_func; +}; + +static int rpc_clients_cb_thread(void *data) +{ + struct msm_rpc_client_cb_item *cb_item; + struct msm_rpc_client *client; + struct rpc_request_hdr req; + int ret; + + client = data; + for (;;) { + wait_event(client->cb_wait, client->cb_avail); + if (client->exit_flag) + break; + + client->cb_avail = 0; + mutex_lock(&client->cb_item_list_lock); + while (!list_empty(&client->cb_item_list)) { + cb_item = list_first_entry( + &client->cb_item_list, + struct msm_rpc_client_cb_item, + list); + list_del(&cb_item->list); + mutex_unlock(&client->cb_item_list_lock); + xdr_init_input(&client->cb_xdr, cb_item->buf, + cb_item->size); + ret = xdr_recv_req(&client->cb_xdr, &req); + if (ret) + goto bad_rpc; + + if (req.type != 0) + goto bad_rpc; + if (req.rpc_vers != 2) + goto bad_rpc; + if (req.prog != + (client->prog | 0x01000000)) + goto bad_rpc; + + if (client->version == 2) + client->cb_func2(client, &req, &client->cb_xdr); + else + client->cb_func(client, client->cb_xdr.in_buf, + client->cb_xdr.in_size); + bad_rpc: + xdr_clean_input(&client->cb_xdr); + kfree(cb_item); + mutex_lock(&client->cb_item_list_lock); + } + mutex_unlock(&client->cb_item_list_lock); + } + complete_and_exit(&client->cb_complete, 0); +} + +static int rpc_clients_thread(void *data) +{ + void *buffer; + uint32_t type; + struct msm_rpc_client *client; + int rc = 0; + struct msm_rpc_client_cb_item *cb_item; + struct rpc_request_hdr req; + + client = data; + for (;;) { + buffer = NULL; + rc = msm_rpc_read(client->ept, &buffer, -1, -1); + + if (client->exit_flag) { + kfree(buffer); + break; + } + + if (rc < 0) { + /* wakeup any pending requests */ + wake_up(&client->reply_wait); + kfree(buffer); + continue; + } + + if (rc < ((int)(sizeof(uint32_t) * 2))) { + kfree(buffer); + continue; + } + + type = be32_to_cpu(*((uint32_t *)buffer + 1)); + if (type == 1) { + xdr_init_input(&client->xdr, buffer, rc); + wake_up(&client->reply_wait); + } else if (type == 0) { + if (client->cb_thread == NULL) { + xdr_init_input(&client->cb_xdr, buffer, rc); + xdr_recv_req(&client->cb_xdr, &req); + + if ((req.rpc_vers == 2) && + (req.prog == (client->prog | 0x01000000))) { + if (client->version == 2) + client->cb_func2(client, &req, + &client->cb_xdr); + else + client->cb_func(client, + client->cb_xdr.in_buf, rc); + } + xdr_clean_input(&client->cb_xdr); + } else { + cb_item = kmalloc(sizeof(*cb_item), GFP_KERNEL); + if (!cb_item) { + pr_err("%s: no memory for cb item\n", + __func__); + continue; + } + + INIT_LIST_HEAD(&cb_item->list); + cb_item->buf = buffer; + cb_item->size = rc; + mutex_lock(&client->cb_item_list_lock); + list_add_tail(&cb_item->list, + &client->cb_item_list); + mutex_unlock(&client->cb_item_list_lock); + client->cb_avail = 1; + wake_up(&client->cb_wait); + } + } + } + complete_and_exit(&client->complete, 0); +} + +static struct msm_rpc_client *msm_rpc_create_client(void) +{ + struct msm_rpc_client *client; + void *buf; + + client = kmalloc(sizeof(struct msm_rpc_client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + xdr_init(&client->xdr); + xdr_init(&client->cb_xdr); + + buf = kmalloc(MSM_RPC_MSGSIZE_MAX, GFP_KERNEL); + if (!buf) { + kfree(client); + return ERR_PTR(-ENOMEM); + } + xdr_init_output(&client->xdr, buf, MSM_RPC_MSGSIZE_MAX); + + buf = kmalloc(MSM_RPC_MSGSIZE_MAX, GFP_KERNEL); + if (!buf) { + xdr_clean_output(&client->xdr); + kfree(client); + return ERR_PTR(-ENOMEM); + } + xdr_init_output(&client->cb_xdr, buf, MSM_RPC_MSGSIZE_MAX); + + init_waitqueue_head(&client->reply_wait); + mutex_init(&client->req_lock); + client->buf = NULL; + client->cb_buf = NULL; + client->cb_size = 0; + client->exit_flag = 0; + client->cb_restart_teardown = NULL; + client->cb_restart_setup = NULL; + client->in_reset = 0; + + init_completion(&client->complete); + init_completion(&client->cb_complete); + INIT_LIST_HEAD(&client->cb_item_list); + mutex_init(&client->cb_item_list_lock); + client->cb_avail = 0; + init_waitqueue_head(&client->cb_wait); + INIT_LIST_HEAD(&client->cb_list); + mutex_init(&client->cb_list_lock); + atomic_set(&client->next_cb_id, 1); + + return client; +} + +static void msm_rpc_destroy_client(struct msm_rpc_client *client) +{ + xdr_clean_output(&client->xdr); + xdr_clean_output(&client->cb_xdr); + + kfree(client); +} + +void msm_rpc_remove_all_cb_func(struct msm_rpc_client *client) +{ + struct msm_rpc_cb_table_item *cb_item, *tmp_cb_item; + + mutex_lock(&client->cb_list_lock); + list_for_each_entry_safe(cb_item, tmp_cb_item, + &client->cb_list, list) { + list_del(&cb_item->list); + kfree(cb_item); + } + mutex_unlock(&client->cb_list_lock); +} + +static void cb_restart_teardown(void *client_data) +{ + struct msm_rpc_client *client; + + client = (struct msm_rpc_client *)client_data; + if (client) { + client->in_reset = 1; + msm_rpc_remove_all_cb_func(client); + client->xdr.out_index = 0; + + if (client->cb_restart_teardown) + client->cb_restart_teardown(client); + } +} + +static void cb_restart_setup(void *client_data) +{ + struct msm_rpc_client *client; + + client = (struct msm_rpc_client *)client_data; + + if (client) { + client->in_reset = 0; + if (client->cb_restart_setup) + client->cb_restart_setup(client); + } +} + +/* Returns the reset state of the client. + * + * Return Value: + * 0 if client isn't in reset, >0 otherwise. + */ +int msm_rpc_client_in_reset(struct msm_rpc_client *client) +{ + int ret = 1; + + if (client) + ret = client->in_reset; + + return ret; +} +EXPORT_SYMBOL(msm_rpc_client_in_reset); + +/* + * Interface to be used to register the client. + * + * name: string representing the client + * + * prog: program number of the client + * + * ver: version number of the client + * + * create_cb_thread: if set calls the callback function from a seprate thread + * which helps the client requests to be processed without + * getting loaded by callback handling. + * + * cb_func: function to be called if callback request is received. + * unmarshaling should be handled by the user in callback function + * + * Return Value: + * Pointer to initialized client data sturcture + * Or, the error code if registration fails. + * + */ +struct msm_rpc_client *msm_rpc_register_client( + const char *name, + uint32_t prog, uint32_t ver, + uint32_t create_cb_thread, + int (*cb_func)(struct msm_rpc_client *, void *, int)) +{ + struct msm_rpc_client *client; + struct msm_rpc_endpoint *ept; + int rc; + + client = msm_rpc_create_client(); + if (IS_ERR(client)) + return client; + + ept = msm_rpc_connect_compatible(prog, ver, MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(ept)) { + msm_rpc_destroy_client(client); + return (struct msm_rpc_client *)ept; + } + + ept->client_data = client; + ept->cb_restart_teardown = cb_restart_teardown; + ept->cb_restart_setup = cb_restart_setup; + + client->prog = prog; + client->ver = ver; + client->ept = client->xdr.ept = client->cb_xdr.ept = ept; + client->cb_func = cb_func; + client->version = 1; + + /* start the read thread */ + client->read_thread = kthread_run(rpc_clients_thread, client, + "k%sclntd", name); + if (IS_ERR(client->read_thread)) { + rc = PTR_ERR(client->read_thread); + msm_rpc_close(client->ept); + msm_rpc_destroy_client(client); + return ERR_PTR(rc); + } + + if (!create_cb_thread || (cb_func == NULL)) { + client->cb_thread = NULL; + return client; + } + + /* start the callback thread */ + client->cb_thread = kthread_run(rpc_clients_cb_thread, client, + "k%sclntcbd", name); + if (IS_ERR(client->cb_thread)) { + rc = PTR_ERR(client->cb_thread); + client->exit_flag = 1; + msm_rpc_read_wakeup(client->ept); + wait_for_completion(&client->complete); + msm_rpc_close(client->ept); + msm_rpc_destroy_client(client); + return ERR_PTR(rc); + } + + return client; +} +EXPORT_SYMBOL(msm_rpc_register_client); + +/* + * Interface to be used to register the client. + * + * name: string representing the client + * + * prog: program number of the client + * + * ver: version number of the client + * + * create_cb_thread: if set calls the callback function from a seprate thread + * which helps the client requests to be processed without + * getting loaded by callback handling. + * + * cb_func: function to be called if callback request is received. + * unmarshaling should be handled by the user in callback function + * + * Return Value: + * Pointer to initialized client data sturcture + * Or, the error code if registration fails. + * + */ +struct msm_rpc_client *msm_rpc_register_client2( + const char *name, + uint32_t prog, uint32_t ver, + uint32_t create_cb_thread, + int (*cb_func)(struct msm_rpc_client *, + struct rpc_request_hdr *req, struct msm_rpc_xdr *)) +{ + struct msm_rpc_client *client; + struct msm_rpc_endpoint *ept; + int rc; + + client = msm_rpc_create_client(); + if (IS_ERR(client)) + return client; + + ept = msm_rpc_connect_compatible(prog, ver, MSM_RPC_UNINTERRUPTIBLE); + if (IS_ERR(ept)) { + msm_rpc_destroy_client(client); + return (struct msm_rpc_client *)ept; + } + + client->prog = prog; + client->ver = ver; + client->ept = client->xdr.ept = client->cb_xdr.ept = ept; + client->cb_func2 = cb_func; + client->version = 2; + + ept->client_data = client; + ept->cb_restart_teardown = cb_restart_teardown; + ept->cb_restart_setup = cb_restart_setup; + + /* start the read thread */ + client->read_thread = kthread_run(rpc_clients_thread, client, + "k%sclntd", name); + if (IS_ERR(client->read_thread)) { + rc = PTR_ERR(client->read_thread); + msm_rpc_close(client->ept); + msm_rpc_destroy_client(client); + return ERR_PTR(rc); + } + + if (!create_cb_thread || (cb_func == NULL)) { + client->cb_thread = NULL; + return client; + } + + /* start the callback thread */ + client->cb_thread = kthread_run(rpc_clients_cb_thread, client, + "k%sclntcbd", name); + if (IS_ERR(client->cb_thread)) { + rc = PTR_ERR(client->cb_thread); + client->exit_flag = 1; + msm_rpc_read_wakeup(client->ept); + wait_for_completion(&client->complete); + msm_rpc_close(client->ept); + msm_rpc_destroy_client(client); + return ERR_PTR(rc); + } + + return client; +} +EXPORT_SYMBOL(msm_rpc_register_client2); + +/* + * Register callbacks for modem state changes. + * + * Teardown is called when the modem is going into reset. + * Setup is called after the modem has come out of reset (but may not + * be available, yet). + * + * client: pointer to client data structure. + * + * Return Value: + * 0 (success) + * 1 (client pointer invalid) + */ +int msm_rpc_register_reset_callbacks( + struct msm_rpc_client *client, + void (*teardown)(struct msm_rpc_client *client), + void (*setup)(struct msm_rpc_client *client) + ) +{ + int rc = 1; + + if (client) { + client->cb_restart_teardown = teardown; + client->cb_restart_setup = setup; + rc = 0; + } + + return rc; +} +EXPORT_SYMBOL(msm_rpc_register_reset_callbacks); + +/* + * Interface to be used to unregister the client + * No client operations should be done once the unregister function + * is called. + * + * client: pointer to client data structure. + * + * Return Value: + * Always returns 0 (success). + */ +int msm_rpc_unregister_client(struct msm_rpc_client *client) +{ + pr_info("%s: stopping client...\n", __func__); + client->exit_flag = 1; + if (client->cb_thread) { + client->cb_avail = 1; + wake_up(&client->cb_wait); + wait_for_completion(&client->cb_complete); + } + + msm_rpc_read_wakeup(client->ept); + wait_for_completion(&client->complete); + + msm_rpc_close(client->ept); + msm_rpc_remove_all_cb_func(client); + xdr_clean_output(&client->xdr); + xdr_clean_output(&client->cb_xdr); + kfree(client); + return 0; +} +EXPORT_SYMBOL(msm_rpc_unregister_client); + +/* + * Interface to be used to send a client request. + * If the request takes any arguments or expects any return, the user + * should handle it in 'arg_func' and 'ret_func' respectively. + * Marshaling and Unmarshaling should be handled by the user in argument + * and return functions. + * + * client: pointer to client data sturcture + * + * proc: procedure being requested + * + * arg_func: argument function pointer. 'buf' is where arguments needs to + * be filled. 'data' is arg_data. + * + * ret_func: return function pointer. 'buf' is where returned data should + * be read from. 'data' is ret_data. + * + * arg_data: passed as an input parameter to argument function. + * + * ret_data: passed as an input parameter to return function. + * + * timeout: timeout for reply wait in jiffies. If negative timeout is + * specified a default timeout of 10s is used. + * + * Return Value: + * 0 on success, otherwise an error code is returned. + */ +int msm_rpc_client_req(struct msm_rpc_client *client, uint32_t proc, + int (*arg_func)(struct msm_rpc_client *client, + void *buf, void *data), + void *arg_data, + int (*ret_func)(struct msm_rpc_client *client, + void *buf, void *data), + void *ret_data, long timeout) +{ + struct rpc_reply_hdr *rpc_rsp; + int rc = 0; + uint32_t req_xid; + + mutex_lock(&client->req_lock); + + msm_rpc_setup_req((struct rpc_request_hdr *)client->xdr.out_buf, + client->prog, client->ver, proc); + client->xdr.out_index = sizeof(struct rpc_request_hdr); + req_xid = *(uint32_t *)client->xdr.out_buf; + if (arg_func) { + rc = arg_func(client, + (void *)((struct rpc_request_hdr *) + client->xdr.out_buf + 1), + arg_data); + if (rc < 0) + goto release_locks; + else + client->xdr.out_index += rc; + } + + rc = msm_rpc_write(client->ept, client->xdr.out_buf, + client->xdr.out_index); + if (rc < 0) { + pr_err("%s: couldn't send RPC request:%d\n", __func__, rc); + goto release_locks; + } else + rc = 0; + + if (timeout < 0) + timeout = msecs_to_jiffies(10000); + + do { + rc = wait_event_timeout(client->reply_wait, + xdr_read_avail(&client->xdr) || client->in_reset, + timeout); + + if (client->in_reset) { + rc = -ENETRESET; + goto release_locks; + } + + if (rc == 0) { + pr_err("%s: request timeout\n", __func__); + rc = -ETIMEDOUT; + goto release_locks; + } + + rpc_rsp = (struct rpc_reply_hdr *)client->xdr.in_buf; + if (req_xid != rpc_rsp->xid) { + pr_info("%s: xid mismatch, req %d reply %d\n", + __func__, be32_to_cpu(req_xid), + be32_to_cpu(rpc_rsp->xid)); + timeout = rc; + xdr_clean_input(&client->xdr); + } else + rc = 0; + } while (rc); + + if (be32_to_cpu(rpc_rsp->reply_stat) != RPCMSG_REPLYSTAT_ACCEPTED) { + pr_err("%s: RPC call was denied! %d\n", __func__, + be32_to_cpu(rpc_rsp->reply_stat)); + rc = -EPERM; + goto free_and_release; + } + + if (be32_to_cpu(rpc_rsp->data.acc_hdr.accept_stat) != + RPC_ACCEPTSTAT_SUCCESS) { + pr_err("%s: RPC call was not successful (%d)\n", __func__, + be32_to_cpu(rpc_rsp->data.acc_hdr.accept_stat)); + rc = -EINVAL; + goto free_and_release; + } + + if (ret_func) + rc = ret_func(client, (void *)(rpc_rsp + 1), ret_data); + + free_and_release: + xdr_clean_input(&client->xdr); + client->xdr.out_index = 0; + release_locks: + mutex_unlock(&client->req_lock); + return rc; +} +EXPORT_SYMBOL(msm_rpc_client_req); + +/* + * Interface to be used to send a client request. + * If the request takes any arguments or expects any return, the user + * should handle it in 'arg_func' and 'ret_func' respectively. + * Marshaling and Unmarshaling should be handled by the user in argument + * and return functions. + * + * client: pointer to client data sturcture + * + * proc: procedure being requested + * + * arg_func: argument function pointer. 'xdr' is the xdr being used. + * 'data' is arg_data. + * + * ret_func: return function pointer. 'xdr' is the xdr being used. + * 'data' is ret_data. + * + * arg_data: passed as an input parameter to argument function. + * + * ret_data: passed as an input parameter to return function. + * + * timeout: timeout for reply wait in jiffies. If negative timeout is + * specified a default timeout of 10s is used. + * + * Return Value: + * 0 on success, otherwise an error code is returned. + */ +int msm_rpc_client_req2(struct msm_rpc_client *client, uint32_t proc, + int (*arg_func)(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data), + void *arg_data, + int (*ret_func)(struct msm_rpc_client *client, + struct msm_rpc_xdr *xdr, void *data), + void *ret_data, long timeout) +{ + struct rpc_reply_hdr rpc_rsp; + int rc = 0; + uint32_t req_xid; + + mutex_lock(&client->req_lock); + + if (client->in_reset) { + rc = -ENETRESET; + goto release_locks; + } + + xdr_start_request(&client->xdr, client->prog, client->ver, proc); + req_xid = be32_to_cpu(*(uint32_t *)client->xdr.out_buf); + if (arg_func) { + rc = arg_func(client, &client->xdr, arg_data); + if (rc < 0) { + mutex_unlock(&client->xdr.out_lock); + goto release_locks; + } + } + + rc = xdr_send_msg(&client->xdr); + if (rc < 0) { + pr_err("%s: couldn't send RPC request:%d\n", __func__, rc); + goto release_locks; + } else + rc = 0; + + if (timeout < 0) + timeout = msecs_to_jiffies(10000); + + do { + rc = wait_event_timeout(client->reply_wait, + xdr_read_avail(&client->xdr) || client->in_reset, + timeout); + + if (client->in_reset) { + rc = -ENETRESET; + goto release_locks; + } + + if (rc == 0) { + pr_err("%s: request timeout\n", __func__); + rc = -ETIMEDOUT; + goto release_locks; + } + + xdr_recv_reply(&client->xdr, &rpc_rsp); + /* TODO: may be this check should be a xdr function */ + if (req_xid != rpc_rsp.xid) { + pr_info("%s: xid mismatch, req %d reply %d\n", + __func__, req_xid, rpc_rsp.xid); + timeout = rc; + xdr_clean_input(&client->xdr); + } else + rc = 0; + } while (rc); + + if (rpc_rsp.reply_stat != RPCMSG_REPLYSTAT_ACCEPTED) { + pr_err("%s: RPC call was denied! %d\n", + __func__, rpc_rsp.reply_stat); + rc = -EPERM; + goto free_and_release; + } + + if (rpc_rsp.data.acc_hdr.accept_stat != RPC_ACCEPTSTAT_SUCCESS) { + pr_err("%s: RPC call was not successful (%d)\n", __func__, + rpc_rsp.data.acc_hdr.accept_stat); + rc = -EINVAL; + goto free_and_release; + } + + if (ret_func) + rc = ret_func(client, &client->xdr, ret_data); + + free_and_release: + xdr_clean_input(&client->xdr); + /* TODO: put it in xdr_reset_output */ + client->xdr.out_index = 0; + release_locks: + mutex_unlock(&client->req_lock); + return rc; +} +EXPORT_SYMBOL(msm_rpc_client_req2); + +/* + * Interface to be used to start accepted reply message required in + * callback handling. Returns the buffer pointer to attach any + * payload. Should call msm_rpc_send_accepted_reply to complete + * sending reply. Marshaling should be handled by user for the payload. + * + * client: pointer to client data structure + * + * xid: transaction id. Has to be same as the one in callback request. + * + * accept_status: acceptance status + * + * Return Value: + * pointer to buffer to attach the payload. + */ +void *msm_rpc_start_accepted_reply(struct msm_rpc_client *client, + uint32_t xid, uint32_t accept_status) +{ + struct rpc_reply_hdr *reply; + + mutex_lock(&client->cb_xdr.out_lock); + + reply = (struct rpc_reply_hdr *)client->cb_xdr.out_buf; + + reply->xid = cpu_to_be32(xid); + reply->type = cpu_to_be32(1); /* reply */ + reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + + reply->data.acc_hdr.accept_stat = cpu_to_be32(accept_status); + reply->data.acc_hdr.verf_flavor = 0; + reply->data.acc_hdr.verf_length = 0; + + client->cb_xdr.out_index = sizeof(*reply); + return reply + 1; +} +EXPORT_SYMBOL(msm_rpc_start_accepted_reply); + +/* + * Interface to be used to send accepted reply required in callback handling. + * msm_rpc_start_accepted_reply should have been called before. + * Marshaling should be handled by user for the payload. + * + * client: pointer to client data structure + * + * size: additional payload size + * + * Return Value: + * 0 on success, otherwise returns an error code. + */ +int msm_rpc_send_accepted_reply(struct msm_rpc_client *client, uint32_t size) +{ + int rc = 0; + + client->cb_xdr.out_index += size; + rc = msm_rpc_write(client->ept, client->cb_xdr.out_buf, + client->cb_xdr.out_index); + if (rc > 0) + rc = 0; + + mutex_unlock(&client->cb_xdr.out_lock); + return rc; +} +EXPORT_SYMBOL(msm_rpc_send_accepted_reply); + +/* + * Interface to be used to add a callback function. + * If the call back function is already in client's 'cb_id - cb_func' + * table, then that cb_id is returned. otherwise, new entry + * is added to the above table and corresponding cb_id is returned. + * + * client: pointer to client data structure + * + * cb_func: callback function + * + * Return Value: + * callback ID on success, otherwise returns an error code. + * If cb_func is NULL, the callback Id returned is 0xffffffff. + * This tells the other processor that no callback is reqested. + */ +int msm_rpc_add_cb_func(struct msm_rpc_client *client, void *cb_func) +{ + struct msm_rpc_cb_table_item *cb_item; + + if (cb_func == NULL) + return MSM_RPC_CLIENT_NULL_CB_ID; + + mutex_lock(&client->cb_list_lock); + list_for_each_entry(cb_item, &client->cb_list, list) { + if (cb_item->cb_func == cb_func) { + mutex_unlock(&client->cb_list_lock); + return cb_item->cb_id; + } + } + mutex_unlock(&client->cb_list_lock); + + cb_item = kmalloc(sizeof(struct msm_rpc_cb_table_item), GFP_KERNEL); + if (!cb_item) + return -ENOMEM; + + INIT_LIST_HEAD(&cb_item->list); + cb_item->cb_id = atomic_add_return(1, &client->next_cb_id); + cb_item->cb_func = cb_func; + + mutex_lock(&client->cb_list_lock); + list_add_tail(&cb_item->list, &client->cb_list); + mutex_unlock(&client->cb_list_lock); + + return cb_item->cb_id; +} +EXPORT_SYMBOL(msm_rpc_add_cb_func); + +/* + * Interface to be used to get a callback function from a callback ID. + * If no entry is found, NULL is returned. + * + * client: pointer to client data structure + * + * cb_id: callback ID + * + * Return Value: + * callback function pointer if entry with given cb_id is found, + * otherwise returns NULL. + */ +void *msm_rpc_get_cb_func(struct msm_rpc_client *client, uint32_t cb_id) +{ + struct msm_rpc_cb_table_item *cb_item; + + mutex_lock(&client->cb_list_lock); + list_for_each_entry(cb_item, &client->cb_list, list) { + if (cb_item->cb_id == cb_id) { + mutex_unlock(&client->cb_list_lock); + return cb_item->cb_func; + } + } + mutex_unlock(&client->cb_list_lock); + return NULL; +} +EXPORT_SYMBOL(msm_rpc_get_cb_func); + +/* + * Interface to be used to remove a callback function. + * + * client: pointer to client data structure + * + * cb_func: callback function + * + */ +void msm_rpc_remove_cb_func(struct msm_rpc_client *client, void *cb_func) +{ + struct msm_rpc_cb_table_item *cb_item, *tmp_cb_item; + + if (cb_func == NULL) + return; + + mutex_lock(&client->cb_list_lock); + list_for_each_entry_safe(cb_item, tmp_cb_item, + &client->cb_list, list) { + if (cb_item->cb_func == cb_func) { + list_del(&cb_item->list); + kfree(cb_item); + mutex_unlock(&client->cb_list_lock); + return; + } + } + mutex_unlock(&client->cb_list_lock); +} +EXPORT_SYMBOL(msm_rpc_remove_cb_func); diff --git a/arch/arm/mach-msm/smd_rpcrouter_device.c b/arch/arm/mach-msm/smd_rpcrouter_device.c new file mode 100644 index 00000000000..e84d2130b86 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter_device.c @@ -0,0 +1,476 @@ +/* arch/arm/mach-msm/smd_rpcrouter_device.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2011, Code Aurora Forum. All rights reserved. + * Author: San Mehat + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "smd_rpcrouter.h" + +/* Support 64KB of data plus some space for headers */ +#define SAFETY_MEM_SIZE (65536 + sizeof(struct rpc_request_hdr)) + +/* modem load timeout */ +#define MODEM_LOAD_TIMEOUT (10 * HZ) + +/* Next minor # available for a remote server */ +static int next_minor = 1; +static DEFINE_SPINLOCK(server_cdev_lock); + +struct class *msm_rpcrouter_class; +dev_t msm_rpcrouter_devno; + +static struct cdev rpcrouter_cdev; +static struct device *rpcrouter_device; + +struct rpcrouter_file_info { + struct msm_rpc_endpoint *ept; + void *modem_pil; +}; + +static void msm_rpcrouter_unload_modem(void *pil) +{ + if (pil) + pil_put(pil); +} + +static void *msm_rpcrouter_load_modem(void) +{ + void *pil; + int rc; + + pil = pil_get("modem"); + if (IS_ERR(pil)) + pr_err("%s: modem load failed\n", __func__); + else { + rc = wait_for_completion_interruptible_timeout( + &rpc_remote_router_up, + MODEM_LOAD_TIMEOUT); + if (!rc) + rc = -ETIMEDOUT; + if (rc < 0) { + pr_err("%s: wait for remote router failed %d\n", + __func__, rc); + msm_rpcrouter_unload_modem(pil); + pil = ERR_PTR(rc); + } + } + + return pil; +} + +static int rpcrouter_open(struct inode *inode, struct file *filp) +{ + int rc; + void *pil; + struct msm_rpc_endpoint *ept; + struct rpcrouter_file_info *file_info; + + rc = nonseekable_open(inode, filp); + if (rc < 0) + return rc; + + file_info = kzalloc(sizeof(*file_info), GFP_KERNEL); + if (!file_info) + return -ENOMEM; + + ept = msm_rpcrouter_create_local_endpoint(inode->i_rdev); + if (!ept) { + kfree(file_info); + return -ENOMEM; + } + file_info->ept = ept; + + /* if router device, load the modem */ + if (inode->i_rdev == msm_rpcrouter_devno) { + pil = msm_rpcrouter_load_modem(); + if (IS_ERR(pil)) { + kfree(file_info); + msm_rpcrouter_destroy_local_endpoint(ept); + return PTR_ERR(pil); + } + file_info->modem_pil = pil; + } + + filp->private_data = file_info; + return 0; +} + +static int rpcrouter_release(struct inode *inode, struct file *filp) +{ + struct rpcrouter_file_info *file_info = filp->private_data; + struct msm_rpc_endpoint *ept; + static unsigned int rpcrouter_release_cnt; + + ept = (struct msm_rpc_endpoint *) file_info->ept; + + /* A user program with many files open when ends abruptly, + * will cause a flood of REMOVE_CLIENT messages to the + * remote processor. This will cause remote processors + * internal queue to overflow. Inserting a sleep here + * regularly is the effecient option. + */ + if (rpcrouter_release_cnt++ % 2) + msleep(1); + + /* if router device, unload the modem */ + if (inode->i_rdev == msm_rpcrouter_devno) + msm_rpcrouter_unload_modem(file_info->modem_pil); + + kfree(file_info); + return msm_rpcrouter_destroy_local_endpoint(ept); +} + +static ssize_t rpcrouter_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct rpcrouter_file_info *file_info = filp->private_data; + struct msm_rpc_endpoint *ept; + struct rr_fragment *frag, *next; + int rc; + + ept = (struct msm_rpc_endpoint *) file_info->ept; + + rc = __msm_rpc_read(ept, &frag, count, -1); + if (rc < 0) + return rc; + + count = rc; + + while (frag != NULL) { + if (copy_to_user(buf, frag->data, frag->length)) { + printk(KERN_ERR + "rpcrouter: could not copy all read data to user!\n"); + rc = -EFAULT; + } + buf += frag->length; + next = frag->next; + kfree(frag); + frag = next; + } + + return rc; +} + +static ssize_t rpcrouter_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct rpcrouter_file_info *file_info = filp->private_data; + struct msm_rpc_endpoint *ept; + int rc = 0; + void *k_buffer; + + ept = (struct msm_rpc_endpoint *) file_info->ept; + + /* A check for safety, this seems non-standard */ + if (count > SAFETY_MEM_SIZE) + return -EINVAL; + + k_buffer = kmalloc(count, GFP_KERNEL); + if (!k_buffer) + return -ENOMEM; + + if (copy_from_user(k_buffer, buf, count)) { + rc = -EFAULT; + goto write_out_free; + } + + rc = msm_rpc_write(ept, k_buffer, count); + if (rc < 0) + goto write_out_free; + + rc = count; +write_out_free: + kfree(k_buffer); + return rc; +} + +/* RPC VFS Poll Implementation + * + * POLLRDHUP - restart in progress + * POLLOUT - writes accepted (without blocking) + * POLLIN - data ready to read + * + * The restart state consists of several different phases including a client + * notification and a server restart. If the server has been restarted, then + * reads and writes can be performed and the POLLOUT bit will be set. If a + * restart is in progress, but the server hasn't been restarted, then only the + * POLLRDHUP is active and reads and writes will block. See the table + * below for a summary. POLLRDHUP is cleared once a call to msm_rpc_write_pkt + * or msm_rpc_read_pkt returns ENETRESET. + * + * POLLOUT POLLRDHUP + * 1 0 Normal operation + * 0 1 Restart in progress and server hasn't restarted yet + * 1 1 Server has been restarted, but client has + * not been notified of a restart by a return code + * of ENETRESET from msm_rpc_write_pkt or + * msm_rpc_read_pkt. + */ +static unsigned int rpcrouter_poll(struct file *filp, + struct poll_table_struct *wait) +{ + struct rpcrouter_file_info *file_info = filp->private_data; + struct msm_rpc_endpoint *ept; + unsigned mask = 0; + + ept = (struct msm_rpc_endpoint *) file_info->ept; + + poll_wait(filp, &ept->wait_q, wait); + poll_wait(filp, &ept->restart_wait, wait); + + if (!list_empty(&ept->read_q)) + mask |= POLLIN; + if (!(ept->restart_state & RESTART_PEND_SVR)) + mask |= POLLOUT; + if (ept->restart_state != 0) + mask |= POLLRDHUP; + + return mask; +} + +static long rpcrouter_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct rpcrouter_file_info *file_info = filp->private_data; + struct msm_rpc_endpoint *ept; + struct rpcrouter_ioctl_server_args server_args; + int rc = 0; + uint32_t n; + + ept = (struct msm_rpc_endpoint *) file_info->ept; + switch (cmd) { + + case RPC_ROUTER_IOCTL_GET_VERSION: + n = RPC_ROUTER_VERSION_V1; + rc = put_user(n, (unsigned int *) arg); + break; + + case RPC_ROUTER_IOCTL_GET_MTU: + /* the pacmark word reduces the actual payload + * possible per message + */ + n = RPCROUTER_MSGSIZE_MAX - sizeof(uint32_t); + rc = put_user(n, (unsigned int *) arg); + break; + + case RPC_ROUTER_IOCTL_REGISTER_SERVER: + rc = copy_from_user(&server_args, (void *) arg, + sizeof(server_args)); + if (rc < 0) + break; + msm_rpc_register_server(ept, + server_args.prog, + server_args.vers); + break; + + case RPC_ROUTER_IOCTL_UNREGISTER_SERVER: + rc = copy_from_user(&server_args, (void *) arg, + sizeof(server_args)); + if (rc < 0) + break; + + msm_rpc_unregister_server(ept, + server_args.prog, + server_args.vers); + break; + + case RPC_ROUTER_IOCTL_CLEAR_NETRESET: + msm_rpc_clear_netreset(ept); + break; + + case RPC_ROUTER_IOCTL_GET_CURR_PKT_SIZE: + rc = msm_rpc_get_curr_pkt_size(ept); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static struct file_operations rpcrouter_server_fops = { + .owner = THIS_MODULE, + .open = rpcrouter_open, + .release = rpcrouter_release, + .read = rpcrouter_read, + .write = rpcrouter_write, + .poll = rpcrouter_poll, + .unlocked_ioctl = rpcrouter_ioctl, +}; + +static struct file_operations rpcrouter_router_fops = { + .owner = THIS_MODULE, + .open = rpcrouter_open, + .release = rpcrouter_release, + .read = rpcrouter_read, + .write = rpcrouter_write, + .poll = rpcrouter_poll, + .unlocked_ioctl = rpcrouter_ioctl, +}; + +int msm_rpcrouter_create_server_cdev(struct rr_server *server) +{ + int rc; + uint32_t dev_vers; + unsigned long flags; + + spin_lock_irqsave(&server_cdev_lock, flags); + if (next_minor == RPCROUTER_MAX_REMOTE_SERVERS) { + spin_unlock_irqrestore(&server_cdev_lock, flags); + printk(KERN_ERR + "rpcrouter: Minor numbers exhausted - Increase " + "RPCROUTER_MAX_REMOTE_SERVERS\n"); + return -ENOBUFS; + } + + /* Servers with bit 31 set are remote msm servers with hashkey version. + * Servers with bit 31 not set are remote msm servers with + * backwards compatible version type in which case the minor number + * (lower 16 bits) is set to zero. + * + */ + if ((server->vers & 0x80000000)) + dev_vers = server->vers; + else + dev_vers = server->vers & 0xffff0000; + + server->device_number = + MKDEV(MAJOR(msm_rpcrouter_devno), next_minor++); + spin_unlock_irqrestore(&server_cdev_lock, flags); + + server->device = + device_create(msm_rpcrouter_class, rpcrouter_device, + server->device_number, NULL, "%.8x:%.8x", + server->prog, dev_vers); + if (IS_ERR(server->device)) { + printk(KERN_ERR + "rpcrouter: Unable to create device (%ld)\n", + PTR_ERR(server->device)); + return PTR_ERR(server->device);; + } + + cdev_init(&server->cdev, &rpcrouter_server_fops); + server->cdev.owner = THIS_MODULE; + + rc = cdev_add(&server->cdev, server->device_number, 1); + if (rc < 0) { + printk(KERN_ERR + "rpcrouter: Unable to add chrdev (%d)\n", rc); + device_destroy(msm_rpcrouter_class, server->device_number); + return rc; + } + return 0; +} + +/* for backward compatible version type (31st bit cleared) + * clearing minor number (lower 16 bits) in device name + * is neccessary for driver binding + */ +int msm_rpcrouter_create_server_pdev(struct rr_server *server) +{ + server->p_device.base.id = (server->vers & RPC_VERSION_MODE_MASK) ? + server->vers : + (server->vers & RPC_VERSION_MAJOR_MASK); + server->p_device.base.name = server->pdev_name; + + server->p_device.prog = server->prog; + server->p_device.vers = server->vers; + + platform_device_register(&server->p_device.base); + return 0; +} + +int msm_rpcrouter_init_devices(void) +{ + int rc; + int major; + + /* Create the device nodes */ + msm_rpcrouter_class = class_create(THIS_MODULE, "oncrpc"); + if (IS_ERR(msm_rpcrouter_class)) { + rc = -ENOMEM; + printk(KERN_ERR + "rpcrouter: failed to create oncrpc class\n"); + goto fail; + } + + rc = alloc_chrdev_region(&msm_rpcrouter_devno, 0, + RPCROUTER_MAX_REMOTE_SERVERS + 1, + "oncrpc"); + if (rc < 0) { + printk(KERN_ERR + "rpcrouter: Failed to alloc chardev region (%d)\n", rc); + goto fail_destroy_class; + } + + major = MAJOR(msm_rpcrouter_devno); + rpcrouter_device = device_create(msm_rpcrouter_class, NULL, + msm_rpcrouter_devno, NULL, "%.8x:%d", + 0, 0); + if (IS_ERR(rpcrouter_device)) { + rc = -ENOMEM; + goto fail_unregister_cdev_region; + } + + cdev_init(&rpcrouter_cdev, &rpcrouter_router_fops); + rpcrouter_cdev.owner = THIS_MODULE; + + rc = cdev_add(&rpcrouter_cdev, msm_rpcrouter_devno, 1); + if (rc < 0) + goto fail_destroy_device; + + return 0; + +fail_destroy_device: + device_destroy(msm_rpcrouter_class, msm_rpcrouter_devno); +fail_unregister_cdev_region: + unregister_chrdev_region(msm_rpcrouter_devno, + RPCROUTER_MAX_REMOTE_SERVERS + 1); +fail_destroy_class: + class_destroy(msm_rpcrouter_class); +fail: + return rc; +} + +void msm_rpcrouter_exit_devices(void) +{ + cdev_del(&rpcrouter_cdev); + device_destroy(msm_rpcrouter_class, msm_rpcrouter_devno); + unregister_chrdev_region(msm_rpcrouter_devno, + RPCROUTER_MAX_REMOTE_SERVERS + 1); + class_destroy(msm_rpcrouter_class); +} + diff --git a/arch/arm/mach-msm/smd_rpcrouter_servers.c b/arch/arm/mach-msm/smd_rpcrouter_servers.c new file mode 100644 index 00000000000..3561c2198e2 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter_servers.c @@ -0,0 +1,618 @@ +/* arch/arm/mach-msm/rpc_servers.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. + * Author: Iliyan Malchev + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include "smd_rpcrouter.h" + +static struct msm_rpc_endpoint *endpoint; + +#define FLAG_REGISTERED 0x0001 + +static LIST_HEAD(rpc_server_list); +static DEFINE_MUTEX(rpc_server_list_lock); +static int rpc_servers_active; +static struct wake_lock rpc_servers_wake_lock; +static struct msm_rpc_xdr server_xdr; +static uint32_t current_xid; + +static void rpc_server_register(struct msm_rpc_server *server) +{ + int rc; + rc = msm_rpc_register_server(endpoint, server->prog, server->vers); + if (rc < 0) + printk(KERN_ERR "[rpcserver] error registering %p @ %08x:%d\n", + server, server->prog, server->vers); +} + +static struct msm_rpc_server *rpc_server_find(uint32_t prog, uint32_t vers) +{ + struct msm_rpc_server *server; + + mutex_lock(&rpc_server_list_lock); + list_for_each_entry(server, &rpc_server_list, list) { + if ((server->prog == prog) && + msm_rpc_is_compatible_version(server->vers, vers)) { + mutex_unlock(&rpc_server_list_lock); + return server; + } + } + mutex_unlock(&rpc_server_list_lock); + return NULL; +} + +static void rpc_server_register_all(void) +{ + struct msm_rpc_server *server; + + mutex_lock(&rpc_server_list_lock); + list_for_each_entry(server, &rpc_server_list, list) { + if (!(server->flags & FLAG_REGISTERED)) { + rpc_server_register(server); + server->flags |= FLAG_REGISTERED; + } + } + mutex_unlock(&rpc_server_list_lock); +} + +int msm_rpc_create_server(struct msm_rpc_server *server) +{ + void *buf; + + /* make sure we're in a sane state first */ + server->flags = 0; + INIT_LIST_HEAD(&server->list); + mutex_init(&server->cb_req_lock); + + server->version = 1; + + xdr_init(&server->cb_xdr); + buf = kmalloc(MSM_RPC_MSGSIZE_MAX, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + xdr_init_output(&server->cb_xdr, buf, MSM_RPC_MSGSIZE_MAX); + + server->cb_ept = server->cb_xdr.ept = msm_rpc_open(); + if (IS_ERR(server->cb_ept)) { + xdr_clean_output(&server->cb_xdr); + return PTR_ERR(server->cb_ept); + } + + server->cb_ept->flags = MSM_RPC_UNINTERRUPTIBLE; + server->cb_ept->dst_prog = cpu_to_be32(server->prog | 0x01000000); + server->cb_ept->dst_vers = cpu_to_be32(server->vers); + + mutex_lock(&rpc_server_list_lock); + list_add(&server->list, &rpc_server_list); + if (rpc_servers_active) { + rpc_server_register(server); + server->flags |= FLAG_REGISTERED; + } + mutex_unlock(&rpc_server_list_lock); + + return 0; +} +EXPORT_SYMBOL(msm_rpc_create_server); + +int msm_rpc_create_server2(struct msm_rpc_server *server) +{ + int rc; + + rc = msm_rpc_create_server(server); + server->version = 2; + + return rc; +} +EXPORT_SYMBOL(msm_rpc_create_server2); + +static int rpc_send_accepted_void_reply(struct msm_rpc_endpoint *client, + uint32_t xid, uint32_t accept_status) +{ + int rc = 0; + uint8_t reply_buf[sizeof(struct rpc_reply_hdr)]; + struct rpc_reply_hdr *reply = (struct rpc_reply_hdr *)reply_buf; + + reply->xid = cpu_to_be32(xid); + reply->type = cpu_to_be32(1); /* reply */ + reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + + reply->data.acc_hdr.accept_stat = cpu_to_be32(accept_status); + reply->data.acc_hdr.verf_flavor = 0; + reply->data.acc_hdr.verf_length = 0; + + rc = msm_rpc_write(client, reply_buf, sizeof(reply_buf)); + if (rc == -ENETRESET) { + /* Modem restarted, drop reply, clear state */ + msm_rpc_clear_netreset(client); + } + if (rc < 0) + printk(KERN_ERR + "%s: could not write response: %d\n", + __FUNCTION__, rc); + + return rc; +} + +/* + * Interface to be used to start accepted reply message for a + * request. Returns the buffer pointer to attach any payload. + * Should call msm_rpc_server_send_accepted_reply to complete sending + * reply. Marshaling should be handled by user for the payload. + * + * server: pointer to server data structure + * + * xid: transaction id. Has to be same as the one in request. + * + * accept_status: acceptance status + * + * Return Value: + * pointer to buffer to attach the payload. + */ +void *msm_rpc_server_start_accepted_reply(struct msm_rpc_server *server, + uint32_t xid, uint32_t accept_status) +{ + struct rpc_reply_hdr *reply; + + mutex_lock(&server_xdr.out_lock); + + reply = (struct rpc_reply_hdr *)server_xdr.out_buf; + + reply->xid = cpu_to_be32(xid); + reply->type = cpu_to_be32(1); /* reply */ + reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + + reply->data.acc_hdr.accept_stat = cpu_to_be32(accept_status); + reply->data.acc_hdr.verf_flavor = 0; + reply->data.acc_hdr.verf_length = 0; + + server_xdr.out_index += sizeof(*reply); + + return reply + 1; +} +EXPORT_SYMBOL(msm_rpc_server_start_accepted_reply); + +/* + * Interface to be used to send accepted reply for a request. + * msm_rpc_server_start_accepted_reply should have been called before. + * Marshaling should be handled by user for the payload. + * + * server: pointer to server data structure + * + * size: additional payload size + * + * Return Value: + * 0 on success, otherwise returns an error code. + */ +int msm_rpc_server_send_accepted_reply(struct msm_rpc_server *server, + uint32_t size) +{ + int rc = 0; + + server_xdr.out_index += size; + rc = msm_rpc_write(endpoint, server_xdr.out_buf, + server_xdr.out_index); + if (rc > 0) + rc = 0; + + mutex_unlock(&server_xdr.out_lock); + return rc; +} +EXPORT_SYMBOL(msm_rpc_server_send_accepted_reply); + +/* + * Interface to be used to send a server callback request. + * If the request takes any arguments or expects any return, the user + * should handle it in 'arg_func' and 'ret_func' respectively. + * Marshaling and Unmarshaling should be handled by the user in argument + * and return functions. + * + * server: pointer to server data sturcture + * + * clnt_info: pointer to client information data structure. + * callback will be sent to this client. + * + * cb_proc: callback procedure being requested + * + * arg_func: argument function pointer. 'buf' is where arguments needs to + * be filled. 'data' is arg_data. + * + * ret_func: return function pointer. 'buf' is where returned data should + * be read from. 'data' is ret_data. + * + * arg_data: passed as an input parameter to argument function. + * + * ret_data: passed as an input parameter to return function. + * + * timeout: timeout for reply wait in jiffies. If negative timeout is + * specified a default timeout of 10s is used. + * + * Return Value: + * 0 on success, otherwise an error code is returned. + */ +int msm_rpc_server_cb_req(struct msm_rpc_server *server, + struct msm_rpc_client_info *clnt_info, + uint32_t cb_proc, + int (*arg_func)(struct msm_rpc_server *server, + void *buf, void *data), + void *arg_data, + int (*ret_func)(struct msm_rpc_server *server, + void *buf, void *data), + void *ret_data, long timeout) +{ + struct rpc_reply_hdr *rpc_rsp; + void *buffer; + int rc = 0; + uint32_t req_xid; + + if (!clnt_info) + return -EINVAL; + + mutex_lock(&server->cb_req_lock); + + msm_rpc_setup_req((struct rpc_request_hdr *)server->cb_xdr.out_buf, + (server->prog | 0x01000000), + be32_to_cpu(clnt_info->vers), cb_proc); + server->cb_xdr.out_index = sizeof(struct rpc_request_hdr); + req_xid = *(uint32_t *)server->cb_xdr.out_buf; + + if (arg_func) { + rc = arg_func(server, (void *)((struct rpc_request_hdr *) + server->cb_xdr.out_buf + 1), + arg_data); + if (rc < 0) + goto release_locks; + else + server->cb_xdr.out_index += rc; + } + + server->cb_ept->dst_pid = clnt_info->pid; + server->cb_ept->dst_cid = clnt_info->cid; + rc = msm_rpc_write(server->cb_ept, server->cb_xdr.out_buf, + server->cb_xdr.out_index); + if (rc < 0) { + pr_err("%s: couldn't send RPC CB request:%d\n", __func__, rc); + goto release_locks; + } else + rc = 0; + + if (timeout < 0) + timeout = msecs_to_jiffies(10000); + + do { + buffer = NULL; + rc = msm_rpc_read(server->cb_ept, &buffer, -1, timeout); + xdr_init_input(&server->cb_xdr, buffer, rc); + if ((rc < ((int)(sizeof(uint32_t) * 2))) || + (be32_to_cpu(*((uint32_t *)buffer + 1)) != 1)) { + printk(KERN_ERR "%s: Invalid reply: %d\n", + __func__, rc); + goto free_and_release; + } + + rpc_rsp = (struct rpc_reply_hdr *)server->cb_xdr.in_buf; + if (req_xid != rpc_rsp->xid) { + pr_info("%s: xid mismatch, req %d reply %d\n", + __func__, be32_to_cpu(req_xid), + be32_to_cpu(rpc_rsp->xid)); + xdr_clean_input(&server->cb_xdr); + rc = timeout; + /* timeout is not adjusted, but it is not critical */ + } else + rc = 0; + } while (rc); + + if (be32_to_cpu(rpc_rsp->reply_stat) != RPCMSG_REPLYSTAT_ACCEPTED) { + pr_err("%s: RPC cb req was denied! %d\n", __func__, + be32_to_cpu(rpc_rsp->reply_stat)); + rc = -EPERM; + goto free_and_release; + } + + if (be32_to_cpu(rpc_rsp->data.acc_hdr.accept_stat) != + RPC_ACCEPTSTAT_SUCCESS) { + pr_err("%s: RPC cb req was not successful (%d)\n", __func__, + be32_to_cpu(rpc_rsp->data.acc_hdr.accept_stat)); + rc = -EINVAL; + goto free_and_release; + } + + if (ret_func) + rc = ret_func(server, (void *)(rpc_rsp + 1), ret_data); + +free_and_release: + xdr_clean_input(&server->cb_xdr); + server->cb_xdr.out_index = 0; +release_locks: + mutex_unlock(&server->cb_req_lock); + return rc; +} +EXPORT_SYMBOL(msm_rpc_server_cb_req); + +/* + * Interface to be used to send a server callback request. + * If the request takes any arguments or expects any return, the user + * should handle it in 'arg_func' and 'ret_func' respectively. + * Marshaling and Unmarshaling should be handled by the user in argument + * and return functions. + * + * server: pointer to server data sturcture + * + * clnt_info: pointer to client information data structure. + * callback will be sent to this client. + * + * cb_proc: callback procedure being requested + * + * arg_func: argument function pointer. 'xdr' is the xdr being used. + * 'data' is arg_data. + * + * ret_func: return function pointer. 'xdr' is the xdr being used. + * 'data' is ret_data. + * + * arg_data: passed as an input parameter to argument function. + * + * ret_data: passed as an input parameter to return function. + * + * timeout: timeout for reply wait in jiffies. If negative timeout is + * specified a default timeout of 10s is used. + * + * Return Value: + * 0 on success, otherwise an error code is returned. + */ +int msm_rpc_server_cb_req2(struct msm_rpc_server *server, + struct msm_rpc_client_info *clnt_info, + uint32_t cb_proc, + int (*arg_func)(struct msm_rpc_server *server, + struct msm_rpc_xdr *xdr, void *data), + void *arg_data, + int (*ret_func)(struct msm_rpc_server *server, + struct msm_rpc_xdr *xdr, void *data), + void *ret_data, long timeout) +{ + int size = 0; + struct rpc_reply_hdr rpc_rsp; + void *buffer; + int rc = 0; + uint32_t req_xid; + + if (!clnt_info) + return -EINVAL; + + mutex_lock(&server->cb_req_lock); + + xdr_start_request(&server->cb_xdr, (server->prog | 0x01000000), + be32_to_cpu(clnt_info->vers), cb_proc); + req_xid = be32_to_cpu(*(uint32_t *)server->cb_xdr.out_buf); + if (arg_func) { + rc = arg_func(server, &server->cb_xdr, arg_data); + if (rc < 0) + goto release_locks; + else + size += rc; + } + + server->cb_ept->dst_pid = clnt_info->pid; + server->cb_ept->dst_cid = clnt_info->cid; + rc = xdr_send_msg(&server->cb_xdr); + if (rc < 0) { + pr_err("%s: couldn't send RPC CB request:%d\n", __func__, rc); + goto release_locks; + } else + rc = 0; + + if (timeout < 0) + timeout = msecs_to_jiffies(10000); + + do { + buffer = NULL; + rc = msm_rpc_read(server->cb_ept, &buffer, -1, timeout); + if (rc < 0) { + server->cb_xdr.out_index = 0; + goto release_locks; + } + + xdr_init_input(&server->cb_xdr, buffer, rc); + rc = xdr_recv_reply(&server->cb_xdr, &rpc_rsp); + if (rc || (rpc_rsp.type != 1)) { + printk(KERN_ERR "%s: Invalid reply :%d\n", + __func__, rc); + rc = -EINVAL; + goto free_and_release; + } + + if (req_xid != rpc_rsp.xid) { + pr_info("%s: xid mismatch, req %d reply %d\n", + __func__, req_xid, rpc_rsp.xid); + xdr_clean_input(&server->cb_xdr); + rc = timeout; + /* timeout is not adjusted, but it is not critical */ + } else + rc = 0; + + } while (rc); + + if (rpc_rsp.reply_stat != RPCMSG_REPLYSTAT_ACCEPTED) { + pr_err("%s: RPC cb req was denied! %d\n", __func__, + rpc_rsp.reply_stat); + rc = -EPERM; + goto free_and_release; + } + + if (rpc_rsp.data.acc_hdr.accept_stat != RPC_ACCEPTSTAT_SUCCESS) { + pr_err("%s: RPC cb req was not successful (%d)\n", __func__, + rpc_rsp.data.acc_hdr.accept_stat); + rc = -EINVAL; + goto free_and_release; + } + + if (ret_func) + rc = ret_func(server, &server->cb_xdr, ret_data); + +free_and_release: + xdr_clean_input(&server->cb_xdr); + server->cb_xdr.out_index = 0; +release_locks: + mutex_unlock(&server->cb_req_lock); + return rc; +} +EXPORT_SYMBOL(msm_rpc_server_cb_req2); + +void msm_rpc_server_get_requesting_client(struct msm_rpc_client_info *clnt_info) +{ + if (!clnt_info) + return; + + get_requesting_client(endpoint, current_xid, clnt_info); +} + +static int rpc_servers_thread(void *data) +{ + void *buffer, *buf; + struct rpc_request_hdr req; + struct rpc_request_hdr *req1; + struct msm_rpc_server *server; + int rc; + + xdr_init(&server_xdr); + server_xdr.ept = endpoint; + + buf = kmalloc(MSM_RPC_MSGSIZE_MAX, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + xdr_init_output(&server_xdr, buf, MSM_RPC_MSGSIZE_MAX); + + for (;;) { + wake_unlock(&rpc_servers_wake_lock); + rc = wait_event_interruptible(endpoint->wait_q, + !list_empty(&endpoint->read_q)); + wake_lock(&rpc_servers_wake_lock); + + rc = msm_rpc_read(endpoint, &buffer, -1, -1); + if (rc < 0) { + printk(KERN_ERR "%s: could not read: %d\n", + __FUNCTION__, rc); + break; + } + + req1 = (struct rpc_request_hdr *)buffer; + current_xid = req1->xid; + + xdr_init_input(&server_xdr, buffer, rc); + xdr_recv_req(&server_xdr, &req); + + server = rpc_server_find(req.prog, req.vers); + + if (req.rpc_vers != 2) + goto free_buffer; + if (req.type != 0) + goto free_buffer; + if (!server) { + rpc_send_accepted_void_reply( + endpoint, req.xid, + RPC_ACCEPTSTAT_PROG_UNAVAIL); + goto free_buffer; + } + + if (server->version == 2) + rc = server->rpc_call2(server, &req, &server_xdr); + else { + req1->type = be32_to_cpu(req1->type); + req1->xid = be32_to_cpu(req1->xid); + req1->rpc_vers = be32_to_cpu(req1->rpc_vers); + req1->prog = be32_to_cpu(req1->prog); + req1->vers = be32_to_cpu(req1->vers); + req1->procedure = be32_to_cpu(req1->procedure); + + rc = server->rpc_call(server, req1, rc); + } + + if (rc == 0) { + msm_rpc_server_start_accepted_reply( + server, req.xid, + RPC_ACCEPTSTAT_SUCCESS); + msm_rpc_server_send_accepted_reply(server, 0); + } else if (rc < 0) { + msm_rpc_server_start_accepted_reply( + server, req.xid, + RPC_ACCEPTSTAT_PROC_UNAVAIL); + msm_rpc_server_send_accepted_reply(server, 0); + } + free_buffer: + xdr_clean_input(&server_xdr); + server_xdr.out_index = 0; + } + do_exit(0); +} + +static int rpcservers_probe(struct platform_device *pdev) +{ + struct task_struct *server_thread; + + endpoint = msm_rpc_open(); + if (IS_ERR(endpoint)) + return PTR_ERR(endpoint); + + /* we're online -- register any servers installed beforehand */ + rpc_servers_active = 1; + current_xid = 0; + rpc_server_register_all(); + + /* start the kernel thread */ + server_thread = kthread_run(rpc_servers_thread, NULL, "krpcserversd"); + if (IS_ERR(server_thread)) + return PTR_ERR(server_thread); + + return 0; +} + +static struct platform_driver rpcservers_driver = { + .probe = rpcservers_probe, + .driver = { + .name = "oncrpc_router", + .owner = THIS_MODULE, + }, +}; + +static int __init rpc_servers_init(void) +{ + wake_lock_init(&rpc_servers_wake_lock, WAKE_LOCK_SUSPEND, "rpc_server"); + return platform_driver_register(&rpcservers_driver); +} + +module_init(rpc_servers_init); + +MODULE_DESCRIPTION("MSM RPC Servers"); +MODULE_AUTHOR("Iliyan Malchev "); +MODULE_LICENSE("GPL"); diff --git a/arch/arm/mach-msm/smd_rpcrouter_xdr.c b/arch/arm/mach-msm/smd_rpcrouter_xdr.c new file mode 100644 index 00000000000..17935161754 --- /dev/null +++ b/arch/arm/mach-msm/smd_rpcrouter_xdr.c @@ -0,0 +1,415 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * SMD RPCROUTER XDR module. + */ + +#include +#include +#include +#include +#include +#include + +#include + +int xdr_send_uint32(struct msm_rpc_xdr *xdr, const uint32_t *value) +{ + if ((xdr->out_index + sizeof(uint32_t)) > xdr->out_size) { + pr_err("%s: xdr out buffer full\n", __func__); + return -1; + } + + *(uint32_t *)(xdr->out_buf + xdr->out_index) = cpu_to_be32(*value); + xdr->out_index += sizeof(uint32_t); + return 0; +} + +int xdr_send_int8(struct msm_rpc_xdr *xdr, const int8_t *value) +{ + return xdr_send_uint32(xdr, (uint32_t *)value); +} + +int xdr_send_uint8(struct msm_rpc_xdr *xdr, const uint8_t *value) +{ + return xdr_send_uint32(xdr, (uint32_t *)value); +} + +int xdr_send_int16(struct msm_rpc_xdr *xdr, const int16_t *value) +{ + return xdr_send_uint32(xdr, (uint32_t *)value); +} + +int xdr_send_uint16(struct msm_rpc_xdr *xdr, const uint16_t *value) +{ + return xdr_send_uint32(xdr, (uint32_t *)value); +} + +int xdr_send_int32(struct msm_rpc_xdr *xdr, const int32_t *value) +{ + return xdr_send_uint32(xdr, (uint32_t *)value); +} + +int xdr_send_bytes(struct msm_rpc_xdr *xdr, const void **data, + uint32_t *size) +{ + void *buf = xdr->out_buf + xdr->out_index; + uint32_t temp; + + if (!size || !data || !*data) + return -1; + + temp = *size; + if (temp & 0x3) + temp += 4 - (temp & 0x3); + + temp += sizeof(uint32_t); + if ((xdr->out_index + temp) > xdr->out_size) { + pr_err("%s: xdr out buffer full\n", __func__); + return -1; + } + + *((uint32_t *)buf) = cpu_to_be32(*size); + buf += sizeof(uint32_t); + memcpy(buf, *data, *size); + buf += *size; + if (*size & 0x3) { + memset(buf, 0, 4 - (*size & 0x3)); + buf += 4 - (*size & 0x3); + } + + xdr->out_index = buf - xdr->out_buf; + return 0; +} + +int xdr_recv_uint32(struct msm_rpc_xdr *xdr, uint32_t *value) +{ + if ((xdr->in_index + sizeof(uint32_t)) > xdr->in_size) { + pr_err("%s: xdr in buffer full\n", __func__); + return -1; + } + + *value = be32_to_cpu(*(uint32_t *)(xdr->in_buf + xdr->in_index)); + xdr->in_index += sizeof(uint32_t); + return 0; +} + +int xdr_recv_int8(struct msm_rpc_xdr *xdr, int8_t *value) +{ + return xdr_recv_uint32(xdr, (uint32_t *)value); +} + +int xdr_recv_uint8(struct msm_rpc_xdr *xdr, uint8_t *value) +{ + return xdr_recv_uint32(xdr, (uint32_t *)value); +} + +int xdr_recv_int16(struct msm_rpc_xdr *xdr, int16_t *value) +{ + return xdr_recv_uint32(xdr, (uint32_t *)value); +} + +int xdr_recv_uint16(struct msm_rpc_xdr *xdr, uint16_t *value) +{ + return xdr_recv_uint32(xdr, (uint32_t *)value); +} + +int xdr_recv_int32(struct msm_rpc_xdr *xdr, int32_t *value) +{ + return xdr_recv_uint32(xdr, (uint32_t *)value); +} + +int xdr_recv_bytes(struct msm_rpc_xdr *xdr, void **data, + uint32_t *size) +{ + void *buf = xdr->in_buf + xdr->in_index; + uint32_t temp; + + if (!size || !data) + return -1; + + *size = be32_to_cpu(*(uint32_t *)buf); + buf += sizeof(uint32_t); + + temp = *size; + if (temp & 0x3) + temp += 4 - (temp & 0x3); + + temp += sizeof(uint32_t); + if ((xdr->in_index + temp) > xdr->in_size) { + pr_err("%s: xdr in buffer full\n", __func__); + return -1; + } + + if (*size) { + *data = kmalloc(*size, GFP_KERNEL); + if (!*data) + return -1; + + memcpy(*data, buf, *size); + + buf += *size; + if (*size & 0x3) + buf += 4 - (*size & 0x3); + } else + *data = NULL; + + xdr->in_index = buf - xdr->in_buf; + return 0; +} + +int xdr_send_pointer(struct msm_rpc_xdr *xdr, void **obj, + uint32_t obj_size, void *xdr_op) +{ + uint32_t ptr_valid, rc; + + ptr_valid = (*obj != NULL); + + rc = xdr_send_uint32(xdr, &ptr_valid); + if (rc) + return rc; + + if (!ptr_valid) + return 0; + + return ((int (*) (struct msm_rpc_xdr *, void *))xdr_op)(xdr, *obj); +} + +int xdr_recv_pointer(struct msm_rpc_xdr *xdr, void **obj, + uint32_t obj_size, void *xdr_op) +{ + uint32_t rc, ptr_valid = 0; + + rc = xdr_recv_uint32(xdr, &ptr_valid); + if (rc) + return rc; + + if (!ptr_valid) { + *obj = NULL; + return 0; + } + + *obj = kmalloc(obj_size, GFP_KERNEL); + if (!*obj) + return -1; + + rc = ((int (*) (struct msm_rpc_xdr *, void *))xdr_op)(xdr, *obj); + if (rc) + kfree(*obj); + + return rc; +} + +int xdr_send_array(struct msm_rpc_xdr *xdr, void **addr, uint32_t *size, + uint32_t maxsize, uint32_t elm_size, void *xdr_op) +{ + int i, rc; + void *tmp_addr = *addr; + + if (!size || !tmp_addr || (*size > maxsize) || !xdr_op) + return -1; + + rc = xdr_send_uint32(xdr, size); + if (rc) + return rc; + + for (i = 0; i < *size; i++) { + rc = ((int (*) (struct msm_rpc_xdr *, void *))xdr_op) + (xdr, tmp_addr); + if (rc) + return rc; + + tmp_addr += elm_size; + } + + return 0; +} + +int xdr_recv_array(struct msm_rpc_xdr *xdr, void **addr, uint32_t *size, + uint32_t maxsize, uint32_t elm_size, void *xdr_op) +{ + int i, rc; + void *tmp_addr; + + if (!size || !xdr_op) + return -1; + + rc = xdr_recv_uint32(xdr, size); + if (rc) + return rc; + + if (*size > maxsize) + return -1; + + tmp_addr = kmalloc((*size * elm_size), GFP_KERNEL); + if (!tmp_addr) + return -1; + + *addr = tmp_addr; + for (i = 0; i < *size; i++) { + rc = ((int (*) (struct msm_rpc_xdr *, void *))xdr_op) + (xdr, tmp_addr); + if (rc) { + kfree(*addr); + *addr = NULL; + return rc; + } + + tmp_addr += elm_size; + } + + return 0; +} + +int xdr_recv_req(struct msm_rpc_xdr *xdr, struct rpc_request_hdr *req) +{ + int rc = 0; + if (!req) + return -1; + + rc |= xdr_recv_uint32(xdr, &req->xid); /* xid */ + rc |= xdr_recv_uint32(xdr, &req->type); /* type */ + rc |= xdr_recv_uint32(xdr, &req->rpc_vers); /* rpc_vers */ + rc |= xdr_recv_uint32(xdr, &req->prog); /* prog */ + rc |= xdr_recv_uint32(xdr, &req->vers); /* vers */ + rc |= xdr_recv_uint32(xdr, &req->procedure); /* procedure */ + rc |= xdr_recv_uint32(xdr, &req->cred_flavor); /* cred_flavor */ + rc |= xdr_recv_uint32(xdr, &req->cred_length); /* cred_length */ + rc |= xdr_recv_uint32(xdr, &req->verf_flavor); /* verf_flavor */ + rc |= xdr_recv_uint32(xdr, &req->verf_length); /* verf_length */ + + return rc; +} + +int xdr_recv_reply(struct msm_rpc_xdr *xdr, struct rpc_reply_hdr *reply) +{ + int rc = 0; + + if (!reply) + return -1; + + rc |= xdr_recv_uint32(xdr, &reply->xid); /* xid */ + rc |= xdr_recv_uint32(xdr, &reply->type); /* type */ + rc |= xdr_recv_uint32(xdr, &reply->reply_stat); /* reply_stat */ + + /* acc_hdr */ + if (reply->reply_stat == RPCMSG_REPLYSTAT_ACCEPTED) { + rc |= xdr_recv_uint32(xdr, &reply->data.acc_hdr.verf_flavor); + rc |= xdr_recv_uint32(xdr, &reply->data.acc_hdr.verf_length); + rc |= xdr_recv_uint32(xdr, &reply->data.acc_hdr.accept_stat); + } + + return rc; +} + +int xdr_start_request(struct msm_rpc_xdr *xdr, uint32_t prog, + uint32_t ver, uint32_t proc) +{ + mutex_lock(&xdr->out_lock); + + /* TODO: replace below function with its implementation */ + msm_rpc_setup_req((struct rpc_request_hdr *)xdr->out_buf, + prog, ver, proc); + + xdr->out_index = sizeof(struct rpc_request_hdr); + return 0; +} + +int xdr_start_accepted_reply(struct msm_rpc_xdr *xdr, uint32_t accept_status) +{ + struct rpc_reply_hdr *reply; + + mutex_lock(&xdr->out_lock); + + /* TODO: err if xdr is not cb xdr */ + reply = (struct rpc_reply_hdr *)xdr->out_buf; + + /* TODO: use xdr functions instead */ + reply->xid = ((struct rpc_request_hdr *)(xdr->in_buf))->xid; + reply->type = cpu_to_be32(1); /* reply */ + reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + + reply->data.acc_hdr.accept_stat = cpu_to_be32(accept_status); + reply->data.acc_hdr.verf_flavor = 0; + reply->data.acc_hdr.verf_length = 0; + + xdr->out_index = sizeof(*reply); + return 0; +} + +int xdr_send_msg(struct msm_rpc_xdr *xdr) +{ + int rc = 0; + + rc = msm_rpc_write(xdr->ept, xdr->out_buf, + xdr->out_index); + if (rc > 0) + rc = 0; + + mutex_unlock(&xdr->out_lock); + return rc; +} + +void xdr_init(struct msm_rpc_xdr *xdr) +{ + mutex_init(&xdr->out_lock); + init_waitqueue_head(&xdr->in_buf_wait_q); + + xdr->in_buf = NULL; + xdr->in_size = 0; + xdr->in_index = 0; + + xdr->out_buf = NULL; + xdr->out_size = 0; + xdr->out_index = 0; +} + +void xdr_init_input(struct msm_rpc_xdr *xdr, void *buf, uint32_t size) +{ + wait_event(xdr->in_buf_wait_q, !(xdr->in_buf)); + + xdr->in_buf = buf; + xdr->in_size = size; + xdr->in_index = 0; +} + +void xdr_init_output(struct msm_rpc_xdr *xdr, void *buf, uint32_t size) +{ + xdr->out_buf = buf; + xdr->out_size = size; + xdr->out_index = 0; +} + +void xdr_clean_input(struct msm_rpc_xdr *xdr) +{ + kfree(xdr->in_buf); + xdr->in_size = 0; + xdr->in_index = 0; + xdr->in_buf = NULL; + + wake_up(&xdr->in_buf_wait_q); +} + +void xdr_clean_output(struct msm_rpc_xdr *xdr) +{ + kfree(xdr->out_buf); + xdr->out_buf = NULL; + xdr->out_size = 0; + xdr->out_index = 0; +} + +uint32_t xdr_read_avail(struct msm_rpc_xdr *xdr) +{ + return xdr->in_size; +} diff --git a/arch/arm/mach-msm/smd_tty.c b/arch/arm/mach-msm/smd_tty.c new file mode 100644 index 00000000000..a8a504f62cb --- /dev/null +++ b/arch/arm/mach-msm/smd_tty.c @@ -0,0 +1,590 @@ +/* arch/arm/mach-msm/smd_tty.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "smd_private.h" + +#define MAX_SMD_TTYS 37 +#define MAX_TTY_BUF_SIZE 2048 + +static DEFINE_MUTEX(smd_tty_lock); + +static uint smd_tty_modem_wait; +module_param_named(modem_wait, smd_tty_modem_wait, + uint, S_IRUGO | S_IWUSR | S_IWGRP); + +struct smd_tty_info { + smd_channel_t *ch; + struct tty_struct *tty; + struct wake_lock wake_lock; + int open_count; + struct tasklet_struct tty_tsklt; + struct timer_list buf_req_timer; + struct completion ch_allocated; + struct platform_driver driver; + void *pil; + int in_reset; + int in_reset_updated; + spinlock_t reset_lock; +}; + +#define LOOPBACK_IDX 36 +static char *smd_ch_name[] = { + [0] = "DS", + [1] = "APPS_FM", + [2] = "APPS_RIVA_BT_ACL", + [3] = "APPS_RIVA_BT_CMD", + [7] = "DATA1", + [21] = "DATA21", + [27] = "GPSNMEA", + [36] = "LOOPBACK", +}; + +static uint32_t smd_ch_edge[] = { + [0] = SMD_APPS_MODEM, + [1] = SMD_APPS_WCNSS, + [2] = SMD_APPS_WCNSS, + [3] = SMD_APPS_WCNSS, + [7] = SMD_APPS_MODEM, + [21] = SMD_APPS_MODEM, + [27] = SMD_APPS_MODEM, + [36] = SMD_APPS_MODEM, +}; + + +static struct delayed_work loopback_work; +static struct smd_tty_info smd_tty[MAX_SMD_TTYS]; + +static int is_in_reset(struct smd_tty_info *info) +{ + return info->in_reset; +} + +static void buf_req_retry(unsigned long param) +{ + struct smd_tty_info *info = (struct smd_tty_info *)param; + tasklet_hi_schedule(&info->tty_tsklt); +} + +static void smd_tty_read(unsigned long param) +{ + unsigned char *ptr; + int avail; + struct smd_tty_info *info = (struct smd_tty_info *)param; + struct tty_struct *tty = info->tty; + + if (!tty) + return; + + for (;;) { + if (is_in_reset(info)) { + /* signal TTY clients using TTY_BREAK */ + tty_insert_flip_char(tty, 0x00, TTY_BREAK); + tty_flip_buffer_push(tty); + break; + } + + if (test_bit(TTY_THROTTLED, &tty->flags)) break; + avail = smd_read_avail(info->ch); + if (avail == 0) + break; + + if (avail > MAX_TTY_BUF_SIZE) + avail = MAX_TTY_BUF_SIZE; + + avail = tty_prepare_flip_string(tty, &ptr, avail); + if (avail <= 0) { + if (!timer_pending(&info->buf_req_timer)) { + init_timer(&info->buf_req_timer); + info->buf_req_timer.expires = jiffies + + ((30 * HZ)/1000); + info->buf_req_timer.function = buf_req_retry; + info->buf_req_timer.data = param; + add_timer(&info->buf_req_timer); + } + return; + } + + if (smd_read(info->ch, ptr, avail) != avail) { + /* shouldn't be possible since we're in interrupt + ** context here and nobody else could 'steal' our + ** characters. + */ + printk(KERN_ERR "OOPS - smd_tty_buffer mismatch?!"); + } + + wake_lock_timeout(&info->wake_lock, HZ / 2); + tty_flip_buffer_push(tty); + } + + /* XXX only when writable and necessary */ + tty_wakeup(tty); +} + +static void smd_tty_notify(void *priv, unsigned event) +{ + struct smd_tty_info *info = priv; + unsigned long flags; + + switch (event) { + case SMD_EVENT_DATA: + /* There may be clients (tty framework) that are blocked + * waiting for space to write data, so if a possible read + * interrupt came in wake anyone waiting and disable the + * interrupts + */ + if (smd_write_avail(info->ch)) { + smd_disable_read_intr(info->ch); + if (info->tty) + wake_up_interruptible(&info->tty->write_wait); + } + tasklet_hi_schedule(&info->tty_tsklt); + break; + + case SMD_EVENT_OPEN: + spin_lock_irqsave(&info->reset_lock, flags); + info->in_reset = 0; + info->in_reset_updated = 1; + spin_unlock_irqrestore(&info->reset_lock, flags); + break; + + case SMD_EVENT_CLOSE: + spin_lock_irqsave(&info->reset_lock, flags); + info->in_reset = 1; + info->in_reset_updated = 1; + spin_unlock_irqrestore(&info->reset_lock, flags); + /* schedule task to send TTY_BREAK */ + tasklet_hi_schedule(&info->tty_tsklt); + + if (info->tty->index == LOOPBACK_IDX) + schedule_delayed_work(&loopback_work, + msecs_to_jiffies(1000)); + break; + } +} + +static uint32_t is_modem_smsm_inited(void) +{ + uint32_t modem_state; + uint32_t ready_state = (SMSM_INIT | SMSM_SMDINIT); + + modem_state = smsm_get_state(SMSM_MODEM_STATE); + return (modem_state & ready_state) == ready_state; +} + +static int smd_tty_open(struct tty_struct *tty, struct file *f) +{ + int res = 0; + int n = tty->index; + struct smd_tty_info *info; + char *peripheral = NULL; + + + if (!smd_ch_name[n]) + return -ENODEV; + + info = smd_tty + n; + + mutex_lock(&smd_tty_lock); + tty->driver_data = info; + + if (info->open_count++ == 0) { + if (smd_ch_edge[n] == SMD_APPS_MODEM) + peripheral = "modem"; + + if (peripheral) { + info->pil = pil_get("modem"); + if (IS_ERR(info->pil)) { + res = PTR_ERR(info->pil); + goto out; + } + + /* Wait for the modem SMSM to be inited for the SMD + * Loopback channel to be allocated at the modem. Since + * the wait need to be done atmost once, using msleep + * doesn't degrade the performance. + */ + if (n == 36) { + if (!is_modem_smsm_inited()) + msleep(5000); + smsm_change_state(SMSM_APPS_STATE, + 0, SMSM_SMD_LOOPBACK); + msleep(100); + } + + + /* + * Wait for a channel to be allocated so we know + * the modem is ready enough. + */ + if (smd_tty_modem_wait) { + res = wait_for_completion_interruptible_timeout( + &info->ch_allocated, + msecs_to_jiffies(smd_tty_modem_wait * + 1000)); + + if (res == 0) { + pr_err("Timed out waiting for SMD" + " channel\n"); + res = -ETIMEDOUT; + goto release_pil; + } else if (res < 0) { + pr_err("Error waiting for SMD channel:" + " %d\n", + res); + goto release_pil; + } + + res = 0; + } + } + + + info->tty = tty; + tasklet_init(&info->tty_tsklt, smd_tty_read, + (unsigned long)info); + wake_lock_init(&info->wake_lock, WAKE_LOCK_SUSPEND, + smd_ch_name[n]); + if (!info->ch) { + res = smd_named_open_on_edge(smd_ch_name[n], + smd_ch_edge[n], + &info->ch, info, + smd_tty_notify); + } + } + +release_pil: + if (res < 0) + pil_put(info->pil); + else + smd_disable_read_intr(info->ch); +out: + mutex_unlock(&smd_tty_lock); + + return res; +} + +static void smd_tty_close(struct tty_struct *tty, struct file *f) +{ + struct smd_tty_info *info = tty->driver_data; + + if (info == 0) + return; + + mutex_lock(&smd_tty_lock); + if (--info->open_count == 0) { + if (info->tty) { + tasklet_kill(&info->tty_tsklt); + wake_lock_destroy(&info->wake_lock); + info->tty = 0; + } + tty->driver_data = 0; + del_timer(&info->buf_req_timer); + if (info->ch) { + smd_close(info->ch); + info->ch = 0; + pil_put(info->pil); + } + } + mutex_unlock(&smd_tty_lock); +} + +static int smd_tty_write(struct tty_struct *tty, const unsigned char *buf, int len) +{ + struct smd_tty_info *info = tty->driver_data; + int avail; + + /* if we're writing to a packet channel we will + ** never be able to write more data than there + ** is currently space for + */ + if (is_in_reset(info)) + return -ENETRESET; + + avail = smd_write_avail(info->ch); + /* if no space, we'll have to setup a notification later to wake up the + * tty framework when space becomes avaliable + */ + if (!avail) { + smd_enable_read_intr(info->ch); + return 0; + } + if (len > avail) + len = avail; + + return smd_write(info->ch, buf, len); +} + +static int smd_tty_write_room(struct tty_struct *tty) +{ + struct smd_tty_info *info = tty->driver_data; + return smd_write_avail(info->ch); +} + +static int smd_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct smd_tty_info *info = tty->driver_data; + return smd_read_avail(info->ch); +} + +static void smd_tty_unthrottle(struct tty_struct *tty) +{ + struct smd_tty_info *info = tty->driver_data; + tasklet_hi_schedule(&info->tty_tsklt); + return; +} + +/* + * Returns the current TIOCM status bits including: + * SMD Signals (DTR/DSR, CTS/RTS, CD, RI) + * TIOCM_OUT1 - reset state (1=in reset) + * TIOCM_OUT2 - reset state updated (1=updated) + */ +static int smd_tty_tiocmget(struct tty_struct *tty) +{ + struct smd_tty_info *info = tty->driver_data; + unsigned long flags; + int tiocm; + + tiocm = smd_tiocmget(info->ch); + + spin_lock_irqsave(&info->reset_lock, flags); + tiocm |= (info->in_reset ? TIOCM_OUT1 : 0); + if (info->in_reset_updated) { + tiocm |= TIOCM_OUT2; + info->in_reset_updated = 0; + } + spin_unlock_irqrestore(&info->reset_lock, flags); + + return tiocm; +} + +static int smd_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct smd_tty_info *info = tty->driver_data; + + if (info->in_reset) + return -ENETRESET; + + return smd_tiocmset(info->ch, set, clear); +} + +static void loopback_probe_worker(struct work_struct *work) +{ + /* wait for modem to restart before requesting loopback server */ + if (!is_modem_smsm_inited()) + schedule_delayed_work(&loopback_work, msecs_to_jiffies(1000)); + else + smsm_change_state(SMSM_APPS_STATE, + 0, SMSM_SMD_LOOPBACK); +} + +static struct tty_operations smd_tty_ops = { + .open = smd_tty_open, + .close = smd_tty_close, + .write = smd_tty_write, + .write_room = smd_tty_write_room, + .chars_in_buffer = smd_tty_chars_in_buffer, + .unthrottle = smd_tty_unthrottle, + .tiocmget = smd_tty_tiocmget, + .tiocmset = smd_tty_tiocmset, +}; + +static int smd_tty_dummy_probe(struct platform_device *pdev) +{ + if (!strncmp(pdev->name, smd_ch_name[0], + strnlen(smd_ch_name[0], SMD_MAX_CH_NAME_LEN))) + complete_all(&smd_tty[0].ch_allocated); + else if (!strncmp(pdev->name, smd_ch_name[1], + strnlen(smd_ch_name[1], SMD_MAX_CH_NAME_LEN))) + complete_all(&smd_tty[1].ch_allocated); + else if (!strncmp(pdev->name, smd_ch_name[2], + strnlen(smd_ch_name[2], SMD_MAX_CH_NAME_LEN))) + complete_all(&smd_tty[2].ch_allocated); + else if (!strncmp(pdev->name, smd_ch_name[3], + strnlen(smd_ch_name[3], SMD_MAX_CH_NAME_LEN))) + complete_all(&smd_tty[3].ch_allocated); + else if (!strncmp(pdev->name, smd_ch_name[7], + strnlen(smd_ch_name[7], SMD_MAX_CH_NAME_LEN))) + complete_all(&smd_tty[7].ch_allocated); + else if (!strncmp(pdev->name, smd_ch_name[21], + strnlen(smd_ch_name[21], SMD_MAX_CH_NAME_LEN))) + complete_all(&smd_tty[21].ch_allocated); + else if (!strncmp(pdev->name, smd_ch_name[27], + strnlen(smd_ch_name[27], SMD_MAX_CH_NAME_LEN))) + complete_all(&smd_tty[27].ch_allocated); + else if (!strncmp(pdev->name, "LOOPBACK_TTY", + strnlen("LOOPBACK_TTY", SMD_MAX_CH_NAME_LEN))) + complete_all(&smd_tty[36].ch_allocated); + + return 0; +} + +static struct tty_driver *smd_tty_driver; + +static int __init smd_tty_init(void) +{ + int ret; + + smd_tty_driver = alloc_tty_driver(MAX_SMD_TTYS); + if (smd_tty_driver == 0) + return -ENOMEM; + + smd_tty_driver->owner = THIS_MODULE; + smd_tty_driver->driver_name = "smd_tty_driver"; + smd_tty_driver->name = "smd"; + smd_tty_driver->major = 0; + smd_tty_driver->minor_start = 0; + smd_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + smd_tty_driver->subtype = SERIAL_TYPE_NORMAL; + smd_tty_driver->init_termios = tty_std_termios; + smd_tty_driver->init_termios.c_iflag = 0; + smd_tty_driver->init_termios.c_oflag = 0; + smd_tty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD; + smd_tty_driver->init_termios.c_lflag = 0; + smd_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS | + TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + tty_set_operations(smd_tty_driver, &smd_tty_ops); + + ret = tty_register_driver(smd_tty_driver); + if (ret) return ret; + + /* this should be dynamic */ + tty_register_device(smd_tty_driver, 0, 0); + tty_register_device(smd_tty_driver, 1, 0); + tty_register_device(smd_tty_driver, 2, 0); + tty_register_device(smd_tty_driver, 3, 0); + tty_register_device(smd_tty_driver, 7, 0); + tty_register_device(smd_tty_driver, 21, 0); + tty_register_device(smd_tty_driver, 27, 0); + tty_register_device(smd_tty_driver, 36, 0); + + init_completion(&smd_tty[0].ch_allocated); + init_completion(&smd_tty[1].ch_allocated); + init_completion(&smd_tty[2].ch_allocated); + init_completion(&smd_tty[3].ch_allocated); + init_completion(&smd_tty[7].ch_allocated); + init_completion(&smd_tty[21].ch_allocated); + init_completion(&smd_tty[27].ch_allocated); + init_completion(&smd_tty[36].ch_allocated); + + smd_tty[0].driver.probe = smd_tty_dummy_probe; + smd_tty[0].driver.driver.name = smd_ch_name[0]; + smd_tty[0].driver.driver.owner = THIS_MODULE; + spin_lock_init(&smd_tty[0].reset_lock); + ret = platform_driver_register(&smd_tty[0].driver); + if (ret) + goto out; + smd_tty[1].driver.probe = smd_tty_dummy_probe; + smd_tty[1].driver.driver.name = smd_ch_name[1]; + smd_tty[1].driver.driver.owner = THIS_MODULE; + spin_lock_init(&smd_tty[1].reset_lock); + ret = platform_driver_register(&smd_tty[1].driver); + if (ret) + goto unreg0; + smd_tty[2].driver.probe = smd_tty_dummy_probe; + smd_tty[2].driver.driver.name = smd_ch_name[2]; + smd_tty[2].driver.driver.owner = THIS_MODULE; + spin_lock_init(&smd_tty[2].reset_lock); + ret = platform_driver_register(&smd_tty[2].driver); + if (ret) + goto unreg1; + smd_tty[3].driver.probe = smd_tty_dummy_probe; + smd_tty[3].driver.driver.name = smd_ch_name[3]; + smd_tty[3].driver.driver.owner = THIS_MODULE; + spin_lock_init(&smd_tty[3].reset_lock); + ret = platform_driver_register(&smd_tty[3].driver); + if (ret) + goto unreg2; + smd_tty[7].driver.probe = smd_tty_dummy_probe; + smd_tty[7].driver.driver.name = smd_ch_name[7]; + smd_tty[7].driver.driver.owner = THIS_MODULE; + spin_lock_init(&smd_tty[7].reset_lock); + ret = platform_driver_register(&smd_tty[7].driver); + if (ret) + goto unreg3; + smd_tty[21].driver.probe = smd_tty_dummy_probe; + smd_tty[21].driver.driver.name = smd_ch_name[21]; + smd_tty[21].driver.driver.owner = THIS_MODULE; + spin_lock_init(&smd_tty[21].reset_lock); + ret = platform_driver_register(&smd_tty[21].driver); + if (ret) + goto unreg7; + smd_tty[27].driver.probe = smd_tty_dummy_probe; + smd_tty[27].driver.driver.name = smd_ch_name[27]; + smd_tty[27].driver.driver.owner = THIS_MODULE; + spin_lock_init(&smd_tty[27].reset_lock); + ret = platform_driver_register(&smd_tty[27].driver); + if (ret) + goto unreg21; + smd_tty[36].driver.probe = smd_tty_dummy_probe; + smd_tty[36].driver.driver.name = "LOOPBACK_TTY"; + smd_tty[36].driver.driver.owner = THIS_MODULE; + spin_lock_init(&smd_tty[36].reset_lock); + INIT_DELAYED_WORK(&loopback_work, loopback_probe_worker); + ret = platform_driver_register(&smd_tty[36].driver); + if (ret) + goto unreg27; + + return 0; + +unreg27: + platform_driver_unregister(&smd_tty[27].driver); +unreg21: + platform_driver_unregister(&smd_tty[21].driver); +unreg7: + platform_driver_unregister(&smd_tty[7].driver); +unreg3: + platform_driver_unregister(&smd_tty[3].driver); +unreg2: + platform_driver_unregister(&smd_tty[2].driver); +unreg1: + platform_driver_unregister(&smd_tty[1].driver); +unreg0: + platform_driver_unregister(&smd_tty[0].driver); +out: + tty_unregister_device(smd_tty_driver, 0); + tty_unregister_device(smd_tty_driver, 1); + tty_unregister_device(smd_tty_driver, 2); + tty_unregister_device(smd_tty_driver, 3); + tty_unregister_device(smd_tty_driver, 7); + tty_unregister_device(smd_tty_driver, 21); + tty_unregister_device(smd_tty_driver, 27); + tty_unregister_device(smd_tty_driver, 36); + tty_unregister_driver(smd_tty_driver); + put_tty_driver(smd_tty_driver); + return ret; +} + +module_init(smd_tty_init); diff --git a/arch/arm/mach-msm/smem_log.c b/arch/arm/mach-msm/smem_log.c new file mode 100644 index 00000000000..f0e81c5db2e --- /dev/null +++ b/arch/arm/mach-msm/smem_log.c @@ -0,0 +1,1961 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Shared memory logging implementation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "smd_private.h" +#include "smd_rpc_sym.h" +#include "modem_notifier.h" + +#define DEBUG +#undef DEBUG + +#ifdef DEBUG +#define D_DUMP_BUFFER(prestr, cnt, buf) \ +do { \ + int i; \ + printk(KERN_ERR "%s", prestr); \ + for (i = 0; i < cnt; i++) \ + printk(KERN_ERR "%.2x", buf[i]); \ + printk(KERN_ERR "\n"); \ +} while (0) +#else +#define D_DUMP_BUFFER(prestr, cnt, buf) +#endif + +#ifdef DEBUG +#define D(x...) printk(x) +#else +#define D(x...) do {} while (0) +#endif + +#if defined(CONFIG_ARCH_MSM7X30) || defined(CONFIG_ARCH_MSM8X60) \ + || defined(CONFIG_ARCH_FSM9XXX) +#define TIMESTAMP_ADDR (MSM_TMR_BASE + 0x08) +#else +#define TIMESTAMP_ADDR (MSM_TMR_BASE + 0x04) +#endif + +struct smem_log_item { + uint32_t identifier; + uint32_t timetick; + uint32_t data1; + uint32_t data2; + uint32_t data3; +}; + +#define SMEM_LOG_NUM_ENTRIES 2000 +#define SMEM_LOG_EVENTS_SIZE (sizeof(struct smem_log_item) * \ + SMEM_LOG_NUM_ENTRIES) + +#define SMEM_LOG_NUM_STATIC_ENTRIES 150 +#define SMEM_STATIC_LOG_EVENTS_SIZE (sizeof(struct smem_log_item) * \ + SMEM_LOG_NUM_STATIC_ENTRIES) + +#define SMEM_LOG_NUM_POWER_ENTRIES 2000 +#define SMEM_POWER_LOG_EVENTS_SIZE (sizeof(struct smem_log_item) * \ + SMEM_LOG_NUM_POWER_ENTRIES) + +#define SMEM_SPINLOCK_SMEM_LOG "S:2" +#define SMEM_SPINLOCK_STATIC_LOG "S:5" +/* POWER shares with SMEM_SPINLOCK_SMEM_LOG */ + +static remote_spinlock_t remote_spinlock; +static remote_spinlock_t remote_spinlock_static; +static uint32_t smem_log_enable; +static int smem_log_initialized; + +module_param_named(log_enable, smem_log_enable, int, + S_IRUGO | S_IWUSR | S_IWGRP); + + +struct smem_log_inst { + int which_log; + struct smem_log_item __iomem *events; + uint32_t __iomem *idx; + uint32_t num; + uint32_t read_idx; + uint32_t last_read_avail; + wait_queue_head_t read_wait; + remote_spinlock_t *remote_spinlock; +}; + +enum smem_logs { + GEN = 0, + STA, + POW, + NUM +}; + +static struct smem_log_inst inst[NUM]; + +#if defined(CONFIG_DEBUG_FS) + +#define HSIZE 13 + +struct sym { + uint32_t val; + char *str; + struct hlist_node node; +}; + +struct sym id_syms[] = { + { SMEM_LOG_PROC_ID_MODEM, "MODM" }, + { SMEM_LOG_PROC_ID_Q6, "QDSP" }, + { SMEM_LOG_PROC_ID_APPS, "APPS" }, +}; + +struct sym base_syms[] = { + { SMEM_LOG_ONCRPC_EVENT_BASE, "ONCRPC" }, + { SMEM_LOG_SMEM_EVENT_BASE, "SMEM" }, + { SMEM_LOG_TMC_EVENT_BASE, "TMC" }, + { SMEM_LOG_TIMETICK_EVENT_BASE, "TIMETICK" }, + { SMEM_LOG_DEM_EVENT_BASE, "DEM" }, + { SMEM_LOG_ERROR_EVENT_BASE, "ERROR" }, + { SMEM_LOG_DCVS_EVENT_BASE, "DCVS" }, + { SMEM_LOG_SLEEP_EVENT_BASE, "SLEEP" }, + { SMEM_LOG_RPC_ROUTER_EVENT_BASE, "ROUTER" }, +}; + +struct sym event_syms[] = { +#if defined(CONFIG_MSM_N_WAY_SMSM) + { DEM_SMSM_ISR, "SMSM_ISR" }, + { DEM_STATE_CHANGE, "STATE_CHANGE" }, + { DEM_STATE_MACHINE_ENTER, "STATE_MACHINE_ENTER" }, + { DEM_ENTER_SLEEP, "ENTER_SLEEP" }, + { DEM_END_SLEEP, "END_SLEEP" }, + { DEM_SETUP_SLEEP, "SETUP_SLEEP" }, + { DEM_SETUP_POWER_COLLAPSE, "SETUP_POWER_COLLAPSE" }, + { DEM_SETUP_SUSPEND, "SETUP_SUSPEND" }, + { DEM_EARLY_EXIT, "EARLY_EXIT" }, + { DEM_WAKEUP_REASON, "WAKEUP_REASON" }, + { DEM_DETECT_WAKEUP, "DETECT_WAKEUP" }, + { DEM_DETECT_RESET, "DETECT_RESET" }, + { DEM_DETECT_SLEEPEXIT, "DETECT_SLEEPEXIT" }, + { DEM_DETECT_RUN, "DETECT_RUN" }, + { DEM_APPS_SWFI, "APPS_SWFI" }, + { DEM_SEND_WAKEUP, "SEND_WAKEUP" }, + { DEM_ASSERT_OKTS, "ASSERT_OKTS" }, + { DEM_NEGATE_OKTS, "NEGATE_OKTS" }, + { DEM_PROC_COMM_CMD, "PROC_COMM_CMD" }, + { DEM_REMOVE_PROC_PWR, "REMOVE_PROC_PWR" }, + { DEM_RESTORE_PROC_PWR, "RESTORE_PROC_PWR" }, + { DEM_SMI_CLK_DISABLED, "SMI_CLK_DISABLED" }, + { DEM_SMI_CLK_ENABLED, "SMI_CLK_ENABLED" }, + { DEM_MAO_INTS, "MAO_INTS" }, + { DEM_APPS_WAKEUP_INT, "APPS_WAKEUP_INT" }, + { DEM_PROC_WAKEUP, "PROC_WAKEUP" }, + { DEM_PROC_POWERUP, "PROC_POWERUP" }, + { DEM_TIMER_EXPIRED, "TIMER_EXPIRED" }, + { DEM_SEND_BATTERY_INFO, "SEND_BATTERY_INFO" }, + { DEM_REMOTE_PWR_CB, "REMOTE_PWR_CB" }, + { DEM_TIME_SYNC_START, "TIME_SYNC_START" }, + { DEM_TIME_SYNC_SEND_VALUE, "TIME_SYNC_SEND_VALUE" }, + { DEM_TIME_SYNC_DONE, "TIME_SYNC_DONE" }, + { DEM_TIME_SYNC_REQUEST, "TIME_SYNC_REQUEST" }, + { DEM_TIME_SYNC_POLL, "TIME_SYNC_POLL" }, + { DEM_TIME_SYNC_INIT, "TIME_SYNC_INIT" }, + { DEM_INIT, "INIT" }, +#else + + { DEM_NO_SLEEP, "NO_SLEEP" }, + { DEM_INSUF_TIME, "INSUF_TIME" }, + { DEMAPPS_ENTER_SLEEP, "APPS_ENTER_SLEEP" }, + { DEMAPPS_DETECT_WAKEUP, "APPS_DETECT_WAKEUP" }, + { DEMAPPS_END_APPS_TCXO, "APPS_END_APPS_TCXO" }, + { DEMAPPS_ENTER_SLEEPEXIT, "APPS_ENTER_SLEEPEXIT" }, + { DEMAPPS_END_APPS_SLEEP, "APPS_END_APPS_SLEEP" }, + { DEMAPPS_SETUP_APPS_PWRCLPS, "APPS_SETUP_APPS_PWRCLPS" }, + { DEMAPPS_PWRCLPS_EARLY_EXIT, "APPS_PWRCLPS_EARLY_EXIT" }, + { DEMMOD_SEND_WAKEUP, "MOD_SEND_WAKEUP" }, + { DEMMOD_NO_APPS_VOTE, "MOD_NO_APPS_VOTE" }, + { DEMMOD_NO_TCXO_SLEEP, "MOD_NO_TCXO_SLEEP" }, + { DEMMOD_BT_CLOCK, "MOD_BT_CLOCK" }, + { DEMMOD_UART_CLOCK, "MOD_UART_CLOCK" }, + { DEMMOD_OKTS, "MOD_OKTS" }, + { DEM_SLEEP_INFO, "SLEEP_INFO" }, + { DEMMOD_TCXO_END, "MOD_TCXO_END" }, + { DEMMOD_END_SLEEP_SIG, "MOD_END_SLEEP_SIG" }, + { DEMMOD_SETUP_APPSSLEEP, "MOD_SETUP_APPSSLEEP" }, + { DEMMOD_ENTER_TCXO, "MOD_ENTER_TCXO" }, + { DEMMOD_WAKE_APPS, "MOD_WAKE_APPS" }, + { DEMMOD_POWER_COLLAPSE_APPS, "MOD_POWER_COLLAPSE_APPS" }, + { DEMMOD_RESTORE_APPS_PWR, "MOD_RESTORE_APPS_PWR" }, + { DEMAPPS_ASSERT_OKTS, "APPS_ASSERT_OKTS" }, + { DEMAPPS_RESTART_START_TIMER, "APPS_RESTART_START_TIMER" }, + { DEMAPPS_ENTER_RUN, "APPS_ENTER_RUN" }, + { DEMMOD_MAO_INTS, "MOD_MAO_INTS" }, + { DEMMOD_POWERUP_APPS_CALLED, "MOD_POWERUP_APPS_CALLED" }, + { DEMMOD_PC_TIMER_EXPIRED, "MOD_PC_TIMER_EXPIRED" }, + { DEM_DETECT_SLEEPEXIT, "_DETECT_SLEEPEXIT" }, + { DEM_DETECT_RUN, "DETECT_RUN" }, + { DEM_SET_APPS_TIMER, "SET_APPS_TIMER" }, + { DEM_NEGATE_OKTS, "NEGATE_OKTS" }, + { DEMMOD_APPS_WAKEUP_INT, "MOD_APPS_WAKEUP_INT" }, + { DEMMOD_APPS_SWFI, "MOD_APPS_SWFI" }, + { DEM_SEND_BATTERY_INFO, "SEND_BATTERY_INFO" }, + { DEM_SMI_CLK_DISABLED, "SMI_CLK_DISABLED" }, + { DEM_SMI_CLK_ENABLED, "SMI_CLK_ENABLED" }, + { DEMAPPS_SETUP_APPS_SUSPEND, "APPS_SETUP_APPS_SUSPEND" }, + { DEM_RPC_EARLY_EXIT, "RPC_EARLY_EXIT" }, + { DEMAPPS_WAKEUP_REASON, "APPS_WAKEUP_REASON" }, + { DEM_INIT, "INIT" }, +#endif + { DEMMOD_UMTS_BASE, "MOD_UMTS_BASE" }, + { DEMMOD_GL1_GO_TO_SLEEP, "GL1_GO_TO_SLEEP" }, + { DEMMOD_GL1_SLEEP_START, "GL1_SLEEP_START" }, + { DEMMOD_GL1_AFTER_GSM_CLK_ON, "GL1_AFTER_GSM_CLK_ON" }, + { DEMMOD_GL1_BEFORE_RF_ON, "GL1_BEFORE_RF_ON" }, + { DEMMOD_GL1_AFTER_RF_ON, "GL1_AFTER_RF_ON" }, + { DEMMOD_GL1_FRAME_TICK, "GL1_FRAME_TICK" }, + { DEMMOD_GL1_WCDMA_START, "GL1_WCDMA_START" }, + { DEMMOD_GL1_WCDMA_ENDING, "GL1_WCDMA_ENDING" }, + { DEMMOD_UMTS_NOT_OKTS, "UMTS_NOT_OKTS" }, + { DEMMOD_UMTS_START_TCXO_SHUTDOWN, "UMTS_START_TCXO_SHUTDOWN" }, + { DEMMOD_UMTS_END_TCXO_SHUTDOWN, "UMTS_END_TCXO_SHUTDOWN" }, + { DEMMOD_UMTS_START_ARM_HALT, "UMTS_START_ARM_HALT" }, + { DEMMOD_UMTS_END_ARM_HALT, "UMTS_END_ARM_HALT" }, + { DEMMOD_UMTS_NEXT_WAKEUP_SCLK, "UMTS_NEXT_WAKEUP_SCLK" }, + { TIME_REMOTE_LOG_EVENT_START, "START" }, + { TIME_REMOTE_LOG_EVENT_GOTO_WAIT, + "GOTO_WAIT" }, + { TIME_REMOTE_LOG_EVENT_GOTO_INIT, + "GOTO_INIT" }, + { ERR_ERROR_FATAL, "ERR_ERROR_FATAL" }, + { ERR_ERROR_FATAL_TASK, "ERR_ERROR_FATAL_TASK" }, + { DCVSAPPS_LOG_IDLE, "DCVSAPPS_LOG_IDLE" }, + { DCVSAPPS_LOG_ERR, "DCVSAPPS_LOG_ERR" }, + { DCVSAPPS_LOG_CHG, "DCVSAPPS_LOG_CHG" }, + { DCVSAPPS_LOG_REG, "DCVSAPPS_LOG_REG" }, + { DCVSAPPS_LOG_DEREG, "DCVSAPPS_LOG_DEREG" }, + { SMEM_LOG_EVENT_CB, "CB" }, + { SMEM_LOG_EVENT_START, "START" }, + { SMEM_LOG_EVENT_INIT, "INIT" }, + { SMEM_LOG_EVENT_RUNNING, "RUNNING" }, + { SMEM_LOG_EVENT_STOP, "STOP" }, + { SMEM_LOG_EVENT_RESTART, "RESTART" }, + { SMEM_LOG_EVENT_SS, "SS" }, + { SMEM_LOG_EVENT_READ, "READ" }, + { SMEM_LOG_EVENT_WRITE, "WRITE" }, + { SMEM_LOG_EVENT_SIGS1, "SIGS1" }, + { SMEM_LOG_EVENT_SIGS2, "SIGS2" }, + { SMEM_LOG_EVENT_WRITE_DM, "WRITE_DM" }, + { SMEM_LOG_EVENT_READ_DM, "READ_DM" }, + { SMEM_LOG_EVENT_SKIP_DM, "SKIP_DM" }, + { SMEM_LOG_EVENT_STOP_DM, "STOP_DM" }, + { SMEM_LOG_EVENT_ISR, "ISR" }, + { SMEM_LOG_EVENT_TASK, "TASK" }, + { SMEM_LOG_EVENT_RS, "RS" }, + { ONCRPC_LOG_EVENT_SMD_WAIT, "SMD_WAIT" }, + { ONCRPC_LOG_EVENT_RPC_WAIT, "RPC_WAIT" }, + { ONCRPC_LOG_EVENT_RPC_BOTH_WAIT, "RPC_BOTH_WAIT" }, + { ONCRPC_LOG_EVENT_RPC_INIT, "RPC_INIT" }, + { ONCRPC_LOG_EVENT_RUNNING, "RUNNING" }, + { ONCRPC_LOG_EVENT_APIS_INITED, "APIS_INITED" }, + { ONCRPC_LOG_EVENT_AMSS_RESET, "AMSS_RESET" }, + { ONCRPC_LOG_EVENT_SMD_RESET, "SMD_RESET" }, + { ONCRPC_LOG_EVENT_ONCRPC_RESET, "ONCRPC_RESET" }, + { ONCRPC_LOG_EVENT_CB, "CB" }, + { ONCRPC_LOG_EVENT_STD_CALL, "STD_CALL" }, + { ONCRPC_LOG_EVENT_STD_REPLY, "STD_REPLY" }, + { ONCRPC_LOG_EVENT_STD_CALL_ASYNC, "STD_CALL_ASYNC" }, + { NO_SLEEP_OLD, "NO_SLEEP_OLD" }, + { INSUF_TIME, "INSUF_TIME" }, + { MOD_UART_CLOCK, "MOD_UART_CLOCK" }, + { SLEEP_INFO, "SLEEP_INFO" }, + { MOD_TCXO_END, "MOD_TCXO_END" }, + { MOD_ENTER_TCXO, "MOD_ENTER_TCXO" }, + { NO_SLEEP_NEW, "NO_SLEEP_NEW" }, + { RPC_ROUTER_LOG_EVENT_UNKNOWN, "UNKNOWN" }, + { RPC_ROUTER_LOG_EVENT_MSG_READ, "MSG_READ" }, + { RPC_ROUTER_LOG_EVENT_MSG_WRITTEN, "MSG_WRITTEN" }, + { RPC_ROUTER_LOG_EVENT_MSG_CFM_REQ, "MSG_CFM_REQ" }, + { RPC_ROUTER_LOG_EVENT_MSG_CFM_SNT, "MSG_CFM_SNT" }, + { RPC_ROUTER_LOG_EVENT_MID_READ, "MID_READ" }, + { RPC_ROUTER_LOG_EVENT_MID_WRITTEN, "MID_WRITTEN" }, + { RPC_ROUTER_LOG_EVENT_MID_CFM_REQ, "MID_CFM_REQ" }, +}; + +struct sym wakeup_syms[] = { + { 0x00000040, "OTHER" }, + { 0x00000020, "RESET" }, + { 0x00000010, "ALARM" }, + { 0x00000008, "TIMER" }, + { 0x00000004, "GPIO" }, + { 0x00000002, "INT" }, + { 0x00000001, "RPC" }, + { 0x00000000, "NONE" }, +}; + +struct sym wakeup_int_syms[] = { + { 0, "MDDI_EXT" }, + { 1, "MDDI_PRI" }, + { 2, "MDDI_CLIENT"}, + { 3, "USB_OTG" }, + { 4, "I2CC" }, + { 5, "SDC1_0" }, + { 6, "SDC1_1" }, + { 7, "SDC2_0" }, + { 8, "SDC2_1" }, + { 9, "ADSP_A9A11" }, + { 10, "UART1" }, + { 11, "UART2" }, + { 12, "UART3" }, + { 13, "DP_RX_DATA" }, + { 14, "DP_RX_DATA2" }, + { 15, "DP_RX_DATA3" }, + { 16, "DM_UART" }, + { 17, "DM_DP_RX_DATA" }, + { 18, "KEYSENSE" }, + { 19, "HSSD" }, + { 20, "NAND_WR_ER_DONE" }, + { 21, "NAND_OP_DONE" }, + { 22, "TCHSCRN1" }, + { 23, "TCHSCRN2" }, + { 24, "TCHSCRN_SSBI" }, + { 25, "USB_HS" }, + { 26, "UART2_DM_RX" }, + { 27, "UART2_DM" }, + { 28, "SDC4_1" }, + { 29, "SDC4_0" }, + { 30, "SDC3_1" }, + { 31, "SDC3_0" }, +}; + +struct sym smsm_syms[] = { + { 0x80000000, "UN" }, + { 0x7F000000, "ERR" }, + { 0x00800000, "SMLP" }, + { 0x00400000, "ADWN" }, + { 0x00200000, "PWRS" }, + { 0x00100000, "DWLD" }, + { 0x00080000, "SRBT" }, + { 0x00040000, "SDWN" }, + { 0x00020000, "ARBT" }, + { 0x00010000, "REL" }, + { 0x00008000, "SLE" }, + { 0x00004000, "SLP" }, + { 0x00002000, "WFPI" }, + { 0x00001000, "EEX" }, + { 0x00000800, "TIN" }, + { 0x00000400, "TWT" }, + { 0x00000200, "PWRC" }, + { 0x00000100, "RUN" }, + { 0x00000080, "SA" }, + { 0x00000040, "RES" }, + { 0x00000020, "RIN" }, + { 0x00000010, "RWT" }, + { 0x00000008, "SIN" }, + { 0x00000004, "SWT" }, + { 0x00000002, "OE" }, + { 0x00000001, "I" }, +}; + +/* never reorder */ +struct sym voter_d2_syms[] = { + { 0x00000001, NULL }, + { 0x00000002, NULL }, + { 0x00000004, NULL }, + { 0x00000008, NULL }, + { 0x00000010, NULL }, + { 0x00000020, NULL }, + { 0x00000040, NULL }, + { 0x00000080, NULL }, + { 0x00000100, NULL }, + { 0x00000200, NULL }, + { 0x00000400, NULL }, + { 0x00000800, NULL }, + { 0x00001000, NULL }, + { 0x00002000, NULL }, + { 0x00004000, NULL }, + { 0x00008000, NULL }, + { 0x00010000, NULL }, + { 0x00020000, NULL }, + { 0x00040000, NULL }, + { 0x00080000, NULL }, + { 0x00100000, NULL }, + { 0x00200000, NULL }, + { 0x00400000, NULL }, + { 0x00800000, NULL }, + { 0x01000000, NULL }, + { 0x02000000, NULL }, + { 0x04000000, NULL }, + { 0x08000000, NULL }, + { 0x10000000, NULL }, + { 0x20000000, NULL }, + { 0x40000000, NULL }, + { 0x80000000, NULL }, +}; + +/* never reorder */ +struct sym voter_d3_syms[] = { + { 0x00000001, NULL }, + { 0x00000002, NULL }, + { 0x00000004, NULL }, + { 0x00000008, NULL }, + { 0x00000010, NULL }, + { 0x00000020, NULL }, + { 0x00000040, NULL }, + { 0x00000080, NULL }, + { 0x00000100, NULL }, + { 0x00000200, NULL }, + { 0x00000400, NULL }, + { 0x00000800, NULL }, + { 0x00001000, NULL }, + { 0x00002000, NULL }, + { 0x00004000, NULL }, + { 0x00008000, NULL }, + { 0x00010000, NULL }, + { 0x00020000, NULL }, + { 0x00040000, NULL }, + { 0x00080000, NULL }, + { 0x00100000, NULL }, + { 0x00200000, NULL }, + { 0x00400000, NULL }, + { 0x00800000, NULL }, + { 0x01000000, NULL }, + { 0x02000000, NULL }, + { 0x04000000, NULL }, + { 0x08000000, NULL }, + { 0x10000000, NULL }, + { 0x20000000, NULL }, + { 0x40000000, NULL }, + { 0x80000000, NULL }, +}; + +struct sym dem_state_master_syms[] = { + { 0, "INIT" }, + { 1, "RUN" }, + { 2, "SLEEP_WAIT" }, + { 3, "SLEEP_CONFIRMED" }, + { 4, "SLEEP_EXIT" }, + { 5, "RSA" }, + { 6, "EARLY_EXIT" }, + { 7, "RSA_DELAYED" }, + { 8, "RSA_CHECK_INTS" }, + { 9, "RSA_CONFIRMED" }, + { 10, "RSA_WAKING" }, + { 11, "RSA_RESTORE" }, + { 12, "RESET" }, +}; + +struct sym dem_state_slave_syms[] = { + { 0, "INIT" }, + { 1, "RUN" }, + { 2, "SLEEP_WAIT" }, + { 3, "SLEEP_EXIT" }, + { 4, "SLEEP_RUN_PENDING" }, + { 5, "POWER_COLLAPSE" }, + { 6, "CHECK_INTERRUPTS" }, + { 7, "SWFI" }, + { 8, "WFPI" }, + { 9, "EARLY_EXIT" }, + { 10, "RESET_RECOVER" }, + { 11, "RESET_ACKNOWLEDGE" }, + { 12, "ERROR" }, +}; + +struct sym smsm_entry_type_syms[] = { + { 0, "SMSM_APPS_STATE" }, + { 1, "SMSM_MODEM_STATE" }, + { 2, "SMSM_Q6_STATE" }, + { 3, "SMSM_APPS_DEM" }, + { 4, "SMSM_MODEM_DEM" }, + { 5, "SMSM_Q6_DEM" }, + { 6, "SMSM_POWER_MASTER_DEM" }, + { 7, "SMSM_TIME_MASTER_DEM" }, +}; + +struct sym smsm_state_syms[] = { + { 0x00000001, "INIT" }, + { 0x00000002, "OSENTERED" }, + { 0x00000004, "SMDWAIT" }, + { 0x00000008, "SMDINIT" }, + { 0x00000010, "RPCWAIT" }, + { 0x00000020, "RPCINIT" }, + { 0x00000040, "RESET" }, + { 0x00000080, "RSA" }, + { 0x00000100, "RUN" }, + { 0x00000200, "PWRC" }, + { 0x00000400, "TIMEWAIT" }, + { 0x00000800, "TIMEINIT" }, + { 0x00001000, "PWRC_EARLY_EXIT" }, + { 0x00002000, "WFPI" }, + { 0x00004000, "SLEEP" }, + { 0x00008000, "SLEEPEXIT" }, + { 0x00010000, "OEMSBL_RELEASE" }, + { 0x00020000, "APPS_REBOOT" }, + { 0x00040000, "SYSTEM_POWER_DOWN" }, + { 0x00080000, "SYSTEM_REBOOT" }, + { 0x00100000, "SYSTEM_DOWNLOAD" }, + { 0x00200000, "PWRC_SUSPEND" }, + { 0x00400000, "APPS_SHUTDOWN" }, + { 0x00800000, "SMD_LOOPBACK" }, + { 0x01000000, "RUN_QUIET" }, + { 0x02000000, "MODEM_WAIT" }, + { 0x04000000, "MODEM_BREAK" }, + { 0x08000000, "MODEM_CONTINUE" }, + { 0x80000000, "UNKNOWN" }, +}; + +#define ID_SYM 0 +#define BASE_SYM 1 +#define EVENT_SYM 2 +#define WAKEUP_SYM 3 +#define WAKEUP_INT_SYM 4 +#define SMSM_SYM 5 +#define VOTER_D2_SYM 6 +#define VOTER_D3_SYM 7 +#define DEM_STATE_MASTER_SYM 8 +#define DEM_STATE_SLAVE_SYM 9 +#define SMSM_ENTRY_TYPE_SYM 10 +#define SMSM_STATE_SYM 11 + +static struct sym_tbl { + struct sym *data; + int size; + struct hlist_head hlist[HSIZE]; +} tbl[] = { + { id_syms, ARRAY_SIZE(id_syms) }, + { base_syms, ARRAY_SIZE(base_syms) }, + { event_syms, ARRAY_SIZE(event_syms) }, + { wakeup_syms, ARRAY_SIZE(wakeup_syms) }, + { wakeup_int_syms, ARRAY_SIZE(wakeup_int_syms) }, + { smsm_syms, ARRAY_SIZE(smsm_syms) }, + { voter_d2_syms, ARRAY_SIZE(voter_d2_syms) }, + { voter_d3_syms, ARRAY_SIZE(voter_d3_syms) }, + { dem_state_master_syms, ARRAY_SIZE(dem_state_master_syms) }, + { dem_state_slave_syms, ARRAY_SIZE(dem_state_slave_syms) }, + { smsm_entry_type_syms, ARRAY_SIZE(smsm_entry_type_syms) }, + { smsm_state_syms, ARRAY_SIZE(smsm_state_syms) }, +}; + +static void find_voters(void) +{ + void *x, *next; + unsigned size; + int i = 0, j = 0; + + x = smem_get_entry(SMEM_SLEEP_STATIC, &size); + next = x; + while (next && (next < (x + size)) && + ((i + j) < (ARRAY_SIZE(voter_d3_syms) + + ARRAY_SIZE(voter_d2_syms)))) { + + if (i < ARRAY_SIZE(voter_d3_syms)) { + voter_d3_syms[i].str = (char *) next; + i++; + } else if (i >= ARRAY_SIZE(voter_d3_syms) && + j < ARRAY_SIZE(voter_d2_syms)) { + voter_d2_syms[j].str = (char *) next; + j++; + } + + next += 9; + } +} + +#define hash(val) (val % HSIZE) + +static void init_syms(void) +{ + int i; + int j; + + for (i = 0; i < ARRAY_SIZE(tbl); ++i) + for (j = 0; j < HSIZE; ++j) + INIT_HLIST_HEAD(&tbl[i].hlist[j]); + + for (i = 0; i < ARRAY_SIZE(tbl); ++i) + for (j = 0; j < tbl[i].size; ++j) { + INIT_HLIST_NODE(&tbl[i].data[j].node); + hlist_add_head(&tbl[i].data[j].node, + &tbl[i].hlist[hash(tbl[i].data[j].val)]); + } +} + +static char *find_sym(uint32_t id, uint32_t val) +{ + struct hlist_node *n; + struct sym *s; + + hlist_for_each(n, &tbl[id].hlist[hash(val)]) { + s = hlist_entry(n, struct sym, node); + if (s->val == val) + return s->str; + } + + return 0; +} + +#else +static void init_syms(void) {} +#endif + +static inline unsigned int read_timestamp(void) +{ + unsigned int tick = 0; + + /* no barriers necessary as the read value is a dependency for the + * comparison operation so the processor shouldn't be able to + * reorder things + */ + do { + tick = __raw_readl(TIMESTAMP_ADDR); + } while (tick != __raw_readl(TIMESTAMP_ADDR)); + + return tick; +} + +static void smem_log_event_from_user(struct smem_log_inst *inst, + const char __user *buf, int size, int num) +{ + uint32_t idx; + uint32_t next_idx; + unsigned long flags; + uint32_t identifier = 0; + uint32_t timetick = 0; + int first = 1; + int ret; + + remote_spin_lock_irqsave(inst->remote_spinlock, flags); + + while (num--) { + idx = *inst->idx; + + if (idx < inst->num) { + ret = copy_from_user(&inst->events[idx], + buf, size); + if (ret) { + printk("ERROR %s:%i tried to write " + "%i got ret %i", + __func__, __LINE__, + size, size - ret); + goto out; + } + + if (first) { + identifier = + inst->events[idx]. + identifier; + timetick = read_timestamp(); + first = 0; + } else { + identifier |= SMEM_LOG_CONT; + } + inst->events[idx].identifier = + identifier; + inst->events[idx].timetick = + timetick; + } + + next_idx = idx + 1; + if (next_idx >= inst->num) + next_idx = 0; + *inst->idx = next_idx; + buf += sizeof(struct smem_log_item); + } + + out: + wmb(); + remote_spin_unlock_irqrestore(inst->remote_spinlock, flags); +} + +static void _smem_log_event( + struct smem_log_item __iomem *events, + uint32_t __iomem *_idx, + remote_spinlock_t *lock, + int num, + uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3) +{ + struct smem_log_item item; + uint32_t idx; + uint32_t next_idx; + unsigned long flags; + + item.timetick = read_timestamp(); + item.identifier = id; + item.data1 = data1; + item.data2 = data2; + item.data3 = data3; + + remote_spin_lock_irqsave(lock, flags); + + idx = *_idx; + + if (idx < num) { + memcpy(&events[idx], + &item, sizeof(item)); + } + + next_idx = idx + 1; + if (next_idx >= num) + next_idx = 0; + *_idx = next_idx; + wmb(); + + remote_spin_unlock_irqrestore(lock, flags); +} + +static void _smem_log_event6( + struct smem_log_item __iomem *events, + uint32_t __iomem *_idx, + remote_spinlock_t *lock, + int num, + uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3, uint32_t data4, uint32_t data5, + uint32_t data6) +{ + struct smem_log_item item[2]; + uint32_t idx; + uint32_t next_idx; + unsigned long flags; + + item[0].timetick = read_timestamp(); + item[0].identifier = id; + item[0].data1 = data1; + item[0].data2 = data2; + item[0].data3 = data3; + item[1].identifier = item[0].identifier; + item[1].timetick = item[0].timetick; + item[1].data1 = data4; + item[1].data2 = data5; + item[1].data3 = data6; + + remote_spin_lock_irqsave(lock, flags); + + idx = *_idx; + + /* FIXME: Wrap around */ + if (idx < (num-1)) { + memcpy(&events[idx], + &item, sizeof(item)); + } + + next_idx = idx + 2; + if (next_idx >= num) + next_idx = 0; + *_idx = next_idx; + + wmb(); + remote_spin_unlock_irqrestore(lock, flags); +} + +void smem_log_event(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3) +{ + if (smem_log_enable) + _smem_log_event(inst[GEN].events, inst[GEN].idx, + inst[GEN].remote_spinlock, + SMEM_LOG_NUM_ENTRIES, id, + data1, data2, data3); +} + +void smem_log_event6(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3, uint32_t data4, uint32_t data5, + uint32_t data6) +{ + if (smem_log_enable) + _smem_log_event6(inst[GEN].events, inst[GEN].idx, + inst[GEN].remote_spinlock, + SMEM_LOG_NUM_ENTRIES, id, + data1, data2, data3, data4, data5, data6); +} + +void smem_log_event_to_static(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3) +{ + if (smem_log_enable) + _smem_log_event(inst[STA].events, inst[STA].idx, + inst[STA].remote_spinlock, + SMEM_LOG_NUM_STATIC_ENTRIES, id, + data1, data2, data3); +} + +void smem_log_event6_to_static(uint32_t id, uint32_t data1, uint32_t data2, + uint32_t data3, uint32_t data4, uint32_t data5, + uint32_t data6) +{ + if (smem_log_enable) + _smem_log_event6(inst[STA].events, inst[STA].idx, + inst[STA].remote_spinlock, + SMEM_LOG_NUM_STATIC_ENTRIES, id, + data1, data2, data3, data4, data5, data6); +} + +static int _smem_log_init(void) +{ + int ret; + + inst[GEN].which_log = GEN; + inst[GEN].events = + (struct smem_log_item *)smem_alloc(SMEM_SMEM_LOG_EVENTS, + SMEM_LOG_EVENTS_SIZE); + inst[GEN].idx = (uint32_t *)smem_alloc(SMEM_SMEM_LOG_IDX, + sizeof(uint32_t)); + if (!inst[GEN].events || !inst[GEN].idx) + pr_info("%s: no log or log_idx allocated\n", __func__); + + inst[GEN].num = SMEM_LOG_NUM_ENTRIES; + inst[GEN].read_idx = 0; + inst[GEN].last_read_avail = SMEM_LOG_NUM_ENTRIES; + init_waitqueue_head(&inst[GEN].read_wait); + inst[GEN].remote_spinlock = &remote_spinlock; + + inst[STA].which_log = STA; + inst[STA].events = + (struct smem_log_item *) + smem_alloc(SMEM_SMEM_STATIC_LOG_EVENTS, + SMEM_STATIC_LOG_EVENTS_SIZE); + inst[STA].idx = (uint32_t *)smem_alloc(SMEM_SMEM_STATIC_LOG_IDX, + sizeof(uint32_t)); + if (!inst[STA].events || !inst[STA].idx) + pr_info("%s: no static log or log_idx allocated\n", __func__); + + inst[STA].num = SMEM_LOG_NUM_STATIC_ENTRIES; + inst[STA].read_idx = 0; + inst[STA].last_read_avail = SMEM_LOG_NUM_ENTRIES; + init_waitqueue_head(&inst[STA].read_wait); + inst[STA].remote_spinlock = &remote_spinlock_static; + + inst[POW].which_log = POW; + inst[POW].events = + (struct smem_log_item *) + smem_alloc(SMEM_SMEM_LOG_POWER_EVENTS, + SMEM_POWER_LOG_EVENTS_SIZE); + inst[POW].idx = (uint32_t *)smem_alloc(SMEM_SMEM_LOG_POWER_IDX, + sizeof(uint32_t)); + if (!inst[POW].events || !inst[POW].idx) + pr_info("%s: no power log or log_idx allocated\n", __func__); + + inst[POW].num = SMEM_LOG_NUM_POWER_ENTRIES; + inst[POW].read_idx = 0; + inst[POW].last_read_avail = SMEM_LOG_NUM_ENTRIES; + init_waitqueue_head(&inst[POW].read_wait); + inst[POW].remote_spinlock = &remote_spinlock; + + ret = remote_spin_lock_init(&remote_spinlock, + SMEM_SPINLOCK_SMEM_LOG); + if (ret) { + mb(); + return ret; + } + + ret = remote_spin_lock_init(&remote_spinlock_static, + SMEM_SPINLOCK_STATIC_LOG); + if (ret) { + mb(); + return ret; + } + + init_syms(); + mb(); + + return 0; +} + +static ssize_t smem_log_read_bin(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + int idx; + int orig_idx; + unsigned long flags; + int ret; + int tot_bytes = 0; + struct smem_log_inst *inst; + + inst = fp->private_data; + + remote_spin_lock_irqsave(inst->remote_spinlock, flags); + + orig_idx = *inst->idx; + idx = orig_idx; + + while (1) { + idx--; + if (idx < 0) + idx = inst->num - 1; + if (idx == orig_idx) { + ret = tot_bytes; + break; + } + + if ((tot_bytes + sizeof(struct smem_log_item)) > count) { + ret = tot_bytes; + break; + } + + ret = copy_to_user(buf, &inst[GEN].events[idx], + sizeof(struct smem_log_item)); + if (ret) { + ret = -EIO; + break; + } + + tot_bytes += sizeof(struct smem_log_item); + + buf += sizeof(struct smem_log_item); + } + + remote_spin_unlock_irqrestore(inst->remote_spinlock, flags); + + return ret; +} + +static ssize_t smem_log_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + char loc_buf[128]; + int i; + int idx; + int orig_idx; + unsigned long flags; + int ret; + int tot_bytes = 0; + struct smem_log_inst *inst; + + inst = fp->private_data; + + remote_spin_lock_irqsave(inst->remote_spinlock, flags); + + orig_idx = *inst->idx; + idx = orig_idx; + + while (1) { + idx--; + if (idx < 0) + idx = inst->num - 1; + if (idx == orig_idx) { + ret = tot_bytes; + break; + } + + i = scnprintf(loc_buf, 128, + "0x%x 0x%x 0x%x 0x%x 0x%x\n", + inst->events[idx].identifier, + inst->events[idx].timetick, + inst->events[idx].data1, + inst->events[idx].data2, + inst->events[idx].data3); + if (i == 0) { + ret = -EIO; + break; + } + + if ((tot_bytes + i) > count) { + ret = tot_bytes; + break; + } + + tot_bytes += i; + + ret = copy_to_user(buf, loc_buf, i); + if (ret) { + ret = -EIO; + break; + } + + buf += i; + } + + remote_spin_unlock_irqrestore(inst->remote_spinlock, flags); + + return ret; +} + +static ssize_t smem_log_write_bin(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + if (count < sizeof(struct smem_log_item)) + return -EINVAL; + + if (smem_log_enable) + smem_log_event_from_user(fp->private_data, buf, + sizeof(struct smem_log_item), + count / sizeof(struct smem_log_item)); + return count; +} + +static ssize_t smem_log_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + int ret; + const char delimiters[] = " ,;"; + char locbuf[256] = {0}; + uint32_t val[10] = {0}; + int vals = 0; + char *token; + char *running; + struct smem_log_inst *inst; + unsigned long res; + + inst = fp->private_data; + + count = count > 255 ? 255 : count; + + if (!smem_log_enable) + return count; + + locbuf[count] = '\0'; + + ret = copy_from_user(locbuf, buf, count); + if (ret != 0) { + printk(KERN_ERR "ERROR: %s could not copy %i bytes\n", + __func__, ret); + return -EINVAL; + } + + D(KERN_ERR "%s: ", __func__); + D_DUMP_BUFFER("We got", len, locbuf); + + running = locbuf; + + token = strsep(&running, delimiters); + while (token && vals < ARRAY_SIZE(val)) { + if (*token != '\0') { + D(KERN_ERR "%s: ", __func__); + D_DUMP_BUFFER("", strlen(token), token); + ret = strict_strtoul(token, 0, &res); + if (ret) { + printk(KERN_ERR "ERROR: %s:%i got bad char " + "at strict_strtoul\n", + __func__, __LINE__-4); + return -EINVAL; + } + val[vals++] = res; + } + token = strsep(&running, delimiters); + } + + if (vals > 5) { + if (inst->which_log == GEN) + smem_log_event6(val[0], val[2], val[3], val[4], + val[7], val[8], val[9]); + else if (inst->which_log == STA) + smem_log_event6_to_static(val[0], + val[2], val[3], val[4], + val[7], val[8], val[9]); + else + return -1; + } else { + if (inst->which_log == GEN) + smem_log_event(val[0], val[2], val[3], val[4]); + else if (inst->which_log == STA) + smem_log_event_to_static(val[0], + val[2], val[3], val[4]); + else + return -1; + } + + return count; +} + +static int smem_log_open(struct inode *ip, struct file *fp) +{ + fp->private_data = &inst[GEN]; + + return 0; +} + + +static int smem_log_release(struct inode *ip, struct file *fp) +{ + return 0; +} + +static long smem_log_ioctl(struct file *fp, unsigned int cmd, + unsigned long arg); + +static const struct file_operations smem_log_fops = { + .owner = THIS_MODULE, + .read = smem_log_read, + .write = smem_log_write, + .open = smem_log_open, + .release = smem_log_release, + .unlocked_ioctl = smem_log_ioctl, +}; + +static const struct file_operations smem_log_bin_fops = { + .owner = THIS_MODULE, + .read = smem_log_read_bin, + .write = smem_log_write_bin, + .open = smem_log_open, + .release = smem_log_release, + .unlocked_ioctl = smem_log_ioctl, +}; + +static long smem_log_ioctl(struct file *fp, + unsigned int cmd, unsigned long arg) +{ + struct smem_log_inst *inst; + + inst = fp->private_data; + + switch (cmd) { + default: + return -ENOTTY; + + case SMIOC_SETMODE: + if (arg == SMIOC_TEXT) { + D("%s set text mode\n", __func__); + fp->f_op = &smem_log_fops; + } else if (arg == SMIOC_BINARY) { + D("%s set bin mode\n", __func__); + fp->f_op = &smem_log_bin_fops; + } else { + return -EINVAL; + } + break; + case SMIOC_SETLOG: + if (arg == SMIOC_LOG) + fp->private_data = &inst[GEN]; + else if (arg == SMIOC_STATIC_LOG) + fp->private_data = &inst[STA]; + else + return -EINVAL; + break; + } + + return 0; +} + +static struct miscdevice smem_log_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "smem_log", + .fops = &smem_log_fops, +}; + +#if defined(CONFIG_DEBUG_FS) + +#define SMEM_LOG_ITEM_PRINT_SIZE 160 + +#define EVENTS_PRINT_SIZE \ +(SMEM_LOG_ITEM_PRINT_SIZE * SMEM_LOG_NUM_ENTRIES) + +static uint32_t smem_log_timeout_ms; +module_param_named(timeout_ms, smem_log_timeout_ms, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +static int smem_log_debug_mask; +module_param_named(debug_mask, smem_log_debug_mask, int, + S_IRUGO | S_IWUSR | S_IWGRP); + +#define DBG(x...) do {\ + if (smem_log_debug_mask) \ + printk(KERN_DEBUG x);\ + } while (0) + +static int update_read_avail(struct smem_log_inst *inst) +{ + int curr_read_avail; + unsigned long flags = 0; + + remote_spin_lock_irqsave(inst->remote_spinlock, flags); + + curr_read_avail = (*inst->idx - inst->read_idx); + if (curr_read_avail < 0) + curr_read_avail = inst->num - inst->read_idx + *inst->idx; + + DBG("%s: read = %d write = %d curr = %d last = %d\n", __func__, + inst->read_idx, *inst->idx, curr_read_avail, inst->last_read_avail); + + if (curr_read_avail < inst->last_read_avail) { + if (inst->last_read_avail != inst->num) + pr_info("smem_log: skipping %d log entries\n", + inst->last_read_avail); + inst->read_idx = *inst->idx + 1; + inst->last_read_avail = inst->num - 1; + } else + inst->last_read_avail = curr_read_avail; + + remote_spin_unlock_irqrestore(inst->remote_spinlock, flags); + + DBG("%s: read = %d write = %d curr = %d last = %d\n", __func__, + inst->read_idx, *inst->idx, curr_read_avail, inst->last_read_avail); + + return inst->last_read_avail; +} + +static int _debug_dump(int log, char *buf, int max, uint32_t cont) +{ + unsigned int idx; + int write_idx, read_avail = 0; + unsigned long flags; + int i = 0; + + if (!inst[log].events) + return 0; + + if (cont && update_read_avail(&inst[log]) == 0) + return 0; + + remote_spin_lock_irqsave(inst[log].remote_spinlock, flags); + + if (cont) { + idx = inst[log].read_idx; + write_idx = (inst[log].read_idx + inst[log].last_read_avail); + if (write_idx >= inst[log].num) + write_idx -= inst[log].num; + } else { + write_idx = *inst[log].idx; + idx = (write_idx + 1); + } + + DBG("%s: read %d write %d idx %d num %d\n", __func__, + inst[log].read_idx, write_idx, idx, inst[log].num - 1); + + while ((max - i) > 50) { + if ((inst[log].num - 1) < idx) + idx = 0; + + if (idx == write_idx) + break; + + if (inst[log].events[idx].identifier) { + + i += scnprintf(buf + i, max - i, + "%08x %08x %08x %08x %08x\n", + inst[log].events[idx].identifier, + inst[log].events[idx].timetick, + inst[log].events[idx].data1, + inst[log].events[idx].data2, + inst[log].events[idx].data3); + } + idx++; + } + if (cont) { + inst[log].read_idx = idx; + read_avail = (write_idx - inst[log].read_idx); + if (read_avail < 0) + read_avail = inst->num - inst->read_idx + write_idx; + inst[log].last_read_avail = read_avail; + } + + remote_spin_unlock_irqrestore(inst[log].remote_spinlock, flags); + + DBG("%s: read %d write %d idx %d num %d\n", __func__, + inst[log].read_idx, write_idx, idx, inst[log].num); + + return i; +} + +static int _debug_dump_voters(char *buf, int max) +{ + int k, i = 0; + + find_voters(); + + i += scnprintf(buf + i, max - i, "Voters:\n"); + for (k = 0; k < ARRAY_SIZE(voter_d3_syms); ++k) + if (voter_d3_syms[k].str) + i += scnprintf(buf + i, max - i, "%s ", + voter_d3_syms[k].str); + for (k = 0; k < ARRAY_SIZE(voter_d2_syms); ++k) + if (voter_d2_syms[k].str) + i += scnprintf(buf + i, max - i, "%s ", + voter_d2_syms[k].str); + i += scnprintf(buf + i, max - i, "\n"); + + return i; +} + +static int _debug_dump_sym(int log, char *buf, int max, uint32_t cont) +{ + unsigned int idx; + int write_idx, read_avail = 0; + unsigned long flags; + int i = 0; + + char *proc; + char *sub; + char *id; + const char *sym = NULL; + + uint32_t data[3]; + + uint32_t proc_val = 0; + uint32_t sub_val = 0; + uint32_t id_val = 0; + uint32_t id_only_val = 0; + uint32_t data1 = 0; + uint32_t data2 = 0; + uint32_t data3 = 0; + + if (!inst[log].events) + return 0; + + find_voters(); + + if (cont && update_read_avail(&inst[log]) == 0) + return 0; + + remote_spin_lock_irqsave(inst[log].remote_spinlock, flags); + + if (cont) { + idx = inst[log].read_idx; + write_idx = (inst[log].read_idx + inst[log].last_read_avail); + if (write_idx >= inst[log].num) + write_idx -= inst[log].num; + } else { + write_idx = *inst[log].idx; + idx = (write_idx + 1); + } + + DBG("%s: read %d write %d idx %d num %d\n", __func__, + inst[log].read_idx, write_idx, idx, inst[log].num - 1); + + for (; (max - i) > SMEM_LOG_ITEM_PRINT_SIZE; idx++) { + if (idx > (inst[log].num - 1)) + idx = 0; + + if (idx == write_idx) + break; + + if (idx < inst[log].num) { + if (!inst[log].events[idx].identifier) + continue; + + proc_val = PROC & inst[log].events[idx].identifier; + sub_val = SUB & inst[log].events[idx].identifier; + id_val = (SUB | ID) & inst[log].events[idx].identifier; + id_only_val = ID & inst[log].events[idx].identifier; + data1 = inst[log].events[idx].data1; + data2 = inst[log].events[idx].data2; + data3 = inst[log].events[idx].data3; + + if (!(proc_val & SMEM_LOG_CONT)) { + i += scnprintf(buf + i, max - i, "\n"); + + proc = find_sym(ID_SYM, proc_val); + + if (proc) + i += scnprintf(buf + i, max - i, + "%4s: ", proc); + else + i += scnprintf(buf + i, max - i, + "%04x: ", + PROC & + inst[log].events[idx]. + identifier); + + i += scnprintf(buf + i, max - i, "%10u ", + inst[log].events[idx].timetick); + + sub = find_sym(BASE_SYM, sub_val); + + if (sub) + i += scnprintf(buf + i, max - i, + "%9s: ", sub); + else + i += scnprintf(buf + i, max - i, + "%08x: ", sub_val); + + id = find_sym(EVENT_SYM, id_val); + + if (id) + i += scnprintf(buf + i, max - i, + "%11s: ", id); + else + i += scnprintf(buf + i, max - i, + "%08x: ", id_only_val); + } + + if ((proc_val & SMEM_LOG_CONT) && + (id_val == ONCRPC_LOG_EVENT_STD_CALL || + id_val == ONCRPC_LOG_EVENT_STD_REPLY)) { + data[0] = data1; + data[1] = data2; + data[2] = data3; + i += scnprintf(buf + i, max - i, + " %.16s", (char *) data); + } else if (proc_val & SMEM_LOG_CONT) { + i += scnprintf(buf + i, max - i, + " %08x %08x %08x", + data1, data2, data3); + } else if (id_val == ONCRPC_LOG_EVENT_STD_CALL) { + sym = smd_rpc_get_sym(data2); + + if (sym) + i += scnprintf(buf + i, max - i, + "xid:%4i %8s proc:%3i", + data1, sym, data3); + else + i += scnprintf(buf + i, max - i, + "xid:%4i %08x proc:%3i", + data1, data2, data3); +#if defined(CONFIG_MSM_N_WAY_SMSM) + } else if (id_val == DEM_STATE_CHANGE) { + if (data1 == 1) { + i += scnprintf(buf + i, max - i, + "MASTER: "); + sym = find_sym(DEM_STATE_MASTER_SYM, + data2); + } else if (data1 == 0) { + i += scnprintf(buf + i, max - i, + " SLAVE: "); + sym = find_sym(DEM_STATE_SLAVE_SYM, + data2); + } else { + i += scnprintf(buf + i, max - i, + "%x: ", data1); + sym = NULL; + } + if (sym) + i += scnprintf(buf + i, max - i, + "from:%s ", sym); + else + i += scnprintf(buf + i, max - i, + "from:0x%x ", data2); + + if (data1 == 1) + sym = find_sym(DEM_STATE_MASTER_SYM, + data3); + else if (data1 == 0) + sym = find_sym(DEM_STATE_SLAVE_SYM, + data3); + else + sym = NULL; + if (sym) + i += scnprintf(buf + i, max - i, + "to:%s ", sym); + else + i += scnprintf(buf + i, max - i, + "to:0x%x ", data3); + + } else if (id_val == DEM_STATE_MACHINE_ENTER) { + i += scnprintf(buf + i, max - i, + "swfi:%i timer:%i manexit:%i", + data1, data2, data3); + + } else if (id_val == DEM_TIME_SYNC_REQUEST || + id_val == DEM_TIME_SYNC_POLL || + id_val == DEM_TIME_SYNC_INIT) { + sym = find_sym(SMSM_ENTRY_TYPE_SYM, + data1); + if (sym) + i += scnprintf(buf + i, max - i, + "hostid:%s", sym); + else + i += scnprintf(buf + i, max - i, + "hostid:%x", data1); + + } else if (id_val == DEM_TIME_SYNC_START || + id_val == DEM_TIME_SYNC_SEND_VALUE) { + unsigned mask = 0x1; + unsigned tmp = 0; + if (id_val == DEM_TIME_SYNC_START) + i += scnprintf(buf + i, max - i, + "req:"); + else + i += scnprintf(buf + i, max - i, + "pol:"); + while (mask) { + if (mask & data1) { + sym = find_sym( + SMSM_ENTRY_TYPE_SYM, + tmp); + if (sym) + i += scnprintf(buf + i, + max - i, + "%s ", + sym); + else + i += scnprintf(buf + i, + max - i, + "%i ", + tmp); + } + mask <<= 1; + tmp++; + } + if (id_val == DEM_TIME_SYNC_SEND_VALUE) + i += scnprintf(buf + i, max - i, + "tick:%x", data2); + } else if (id_val == DEM_SMSM_ISR) { + unsigned vals[] = {data2, data3}; + unsigned j; + unsigned mask; + unsigned tmp; + unsigned once; + sym = find_sym(SMSM_ENTRY_TYPE_SYM, + data1); + if (sym) + i += scnprintf(buf + i, max - i, + "%s ", sym); + else + i += scnprintf(buf + i, max - i, + "%x ", data1); + + for (j = 0; j < ARRAY_SIZE(vals); ++j) { + i += scnprintf(buf + i, max - i, "["); + mask = 0x80000000; + once = 0; + while (mask) { + tmp = vals[j] & mask; + mask >>= 1; + if (!tmp) + continue; + sym = find_sym(SMSM_STATE_SYM, + tmp); + + if (once) + i += scnprintf(buf + i, + max - i, + " "); + if (sym) + i += scnprintf(buf + i, + max - i, + "%s", + sym); + else + i += scnprintf(buf + i, + max - i, + "0x%x", + tmp); + once = 1; + } + i += scnprintf(buf + i, max - i, "] "); + } +#else + } else if (id_val == DEMAPPS_WAKEUP_REASON) { + unsigned mask = 0x80000000; + unsigned tmp = 0; + while (mask) { + tmp = data1 & mask; + mask >>= 1; + if (!tmp) + continue; + sym = find_sym(WAKEUP_SYM, tmp); + if (sym) + i += scnprintf(buf + i, + max - i, + "%s ", + sym); + else + i += scnprintf(buf + i, + max - i, + "%08x ", + tmp); + } + i += scnprintf(buf + i, max - i, + "%08x %08x", data2, data3); + } else if (id_val == DEMMOD_APPS_WAKEUP_INT) { + sym = find_sym(WAKEUP_INT_SYM, data1); + + if (sym) + i += scnprintf(buf + i, max - i, + "%s %08x %08x", + sym, data2, data3); + else + i += scnprintf(buf + i, max - i, + "%08x %08x %08x", + data1, data2, data3); + } else if (id_val == DEM_NO_SLEEP || + id_val == NO_SLEEP_NEW) { + unsigned vals[] = {data3, data2}; + unsigned j; + unsigned mask; + unsigned tmp; + unsigned once; + i += scnprintf(buf + i, max - i, "%08x ", + data1); + i += scnprintf(buf + i, max - i, "["); + once = 0; + for (j = 0; j < ARRAY_SIZE(vals); ++j) { + mask = 0x00000001; + while (mask) { + tmp = vals[j] & mask; + mask <<= 1; + if (!tmp) + continue; + if (j == 0) + sym = find_sym( + VOTER_D3_SYM, + tmp); + else + sym = find_sym( + VOTER_D2_SYM, + tmp); + + if (once) + i += scnprintf(buf + i, + max - i, + " "); + if (sym) + i += scnprintf(buf + i, + max - i, + "%s", + sym); + else + i += scnprintf(buf + i, + max - i, + "%08x", + tmp); + once = 1; + } + } + i += scnprintf(buf + i, max - i, "] "); +#endif + } else if (id_val == SMEM_LOG_EVENT_CB) { + unsigned vals[] = {data2, data3}; + unsigned j; + unsigned mask; + unsigned tmp; + unsigned once; + i += scnprintf(buf + i, max - i, "%08x ", + data1); + for (j = 0; j < ARRAY_SIZE(vals); ++j) { + i += scnprintf(buf + i, max - i, "["); + mask = 0x80000000; + once = 0; + while (mask) { + tmp = vals[j] & mask; + mask >>= 1; + if (!tmp) + continue; + sym = find_sym(SMSM_SYM, tmp); + + if (once) + i += scnprintf(buf + i, + max - i, + " "); + if (sym) + i += scnprintf(buf + i, + max - i, + "%s", + sym); + else + i += scnprintf(buf + i, + max - i, + "%08x", + tmp); + once = 1; + } + i += scnprintf(buf + i, max - i, "] "); + } + } else { + i += scnprintf(buf + i, max - i, + "%08x %08x %08x", + data1, data2, data3); + } + } + } + if (cont) { + inst[log].read_idx = idx; + read_avail = (write_idx - inst[log].read_idx); + if (read_avail < 0) + read_avail = inst->num - inst->read_idx + write_idx; + inst[log].last_read_avail = read_avail; + } + + remote_spin_unlock_irqrestore(inst[log].remote_spinlock, flags); + + DBG("%s: read %d write %d idx %d num %d\n", __func__, + inst[log].read_idx, write_idx, idx, inst[log].num); + + return i; +} + +static int debug_dump(char *buf, int max, uint32_t cont) +{ + int r; + while (cont) { + update_read_avail(&inst[GEN]); + r = wait_event_interruptible_timeout(inst[GEN].read_wait, + inst[GEN].last_read_avail, + smem_log_timeout_ms * + HZ / 1000); + DBG("%s: read available %d\n", __func__, + inst[GEN].last_read_avail); + if (r < 0) + return 0; + else if (inst[GEN].last_read_avail) + break; + } + + return _debug_dump(GEN, buf, max, cont); +} + +static int debug_dump_sym(char *buf, int max, uint32_t cont) +{ + int r; + while (cont) { + update_read_avail(&inst[GEN]); + r = wait_event_interruptible_timeout(inst[GEN].read_wait, + inst[GEN].last_read_avail, + smem_log_timeout_ms * + HZ / 1000); + DBG("%s: readavailable %d\n", __func__, + inst[GEN].last_read_avail); + if (r < 0) + return 0; + else if (inst[GEN].last_read_avail) + break; + } + + return _debug_dump_sym(GEN, buf, max, cont); +} + +static int debug_dump_static(char *buf, int max, uint32_t cont) +{ + int r; + while (cont) { + update_read_avail(&inst[STA]); + r = wait_event_interruptible_timeout(inst[STA].read_wait, + inst[STA].last_read_avail, + smem_log_timeout_ms * + HZ / 1000); + DBG("%s: readavailable %d\n", __func__, + inst[STA].last_read_avail); + if (r < 0) + return 0; + else if (inst[STA].last_read_avail) + break; + } + + return _debug_dump(STA, buf, max, cont); +} + +static int debug_dump_static_sym(char *buf, int max, uint32_t cont) +{ + int r; + while (cont) { + update_read_avail(&inst[STA]); + r = wait_event_interruptible_timeout(inst[STA].read_wait, + inst[STA].last_read_avail, + smem_log_timeout_ms * + HZ / 1000); + DBG("%s: readavailable %d\n", __func__, + inst[STA].last_read_avail); + if (r < 0) + return 0; + else if (inst[STA].last_read_avail) + break; + } + + return _debug_dump_sym(STA, buf, max, cont); +} + +static int debug_dump_power(char *buf, int max, uint32_t cont) +{ + int r; + while (cont) { + update_read_avail(&inst[POW]); + r = wait_event_interruptible_timeout(inst[POW].read_wait, + inst[POW].last_read_avail, + smem_log_timeout_ms * + HZ / 1000); + DBG("%s: readavailable %d\n", __func__, + inst[POW].last_read_avail); + if (r < 0) + return 0; + else if (inst[POW].last_read_avail) + break; + } + + return _debug_dump(POW, buf, max, cont); +} + +static int debug_dump_power_sym(char *buf, int max, uint32_t cont) +{ + int r; + while (cont) { + update_read_avail(&inst[POW]); + r = wait_event_interruptible_timeout(inst[POW].read_wait, + inst[POW].last_read_avail, + smem_log_timeout_ms * + HZ / 1000); + DBG("%s: readavailable %d\n", __func__, + inst[POW].last_read_avail); + if (r < 0) + return 0; + else if (inst[POW].last_read_avail) + break; + } + + return _debug_dump_sym(POW, buf, max, cont); +} + +static int debug_dump_voters(char *buf, int max, uint32_t cont) +{ + return _debug_dump_voters(buf, max); +} + +static char debug_buffer[EVENTS_PRINT_SIZE]; + +static ssize_t debug_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int r; + static int bsize; + int (*fill)(char *, int, uint32_t) = file->private_data; + if (!(*ppos)) + bsize = fill(debug_buffer, EVENTS_PRINT_SIZE, 0); + DBG("%s: count %d ppos %d\n", __func__, count, (unsigned int)*ppos); + r = simple_read_from_buffer(buf, count, ppos, debug_buffer, + bsize); + return r; +} + +static ssize_t debug_read_cont(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int (*fill)(char *, int, uint32_t) = file->private_data; + char *buffer = kmalloc(count, GFP_KERNEL); + int bsize; + if (!buffer) + return -ENOMEM; + bsize = fill(buffer, count, 1); + DBG("%s: count %d bsize %d\n", __func__, count, bsize); + if (copy_to_user(buf, buffer, bsize)) { + kfree(buffer); + return -EFAULT; + } + kfree(buffer); + return bsize; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations debug_ops = { + .read = debug_read, + .open = debug_open, +}; + +static const struct file_operations debug_ops_cont = { + .read = debug_read_cont, + .open = debug_open, +}; + +static void debug_create(const char *name, mode_t mode, + struct dentry *dent, + int (*fill)(char *buf, int max, uint32_t cont), + const struct file_operations *fops) +{ + debugfs_create_file(name, mode, dent, fill, fops); +} + +static void smem_log_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("smem_log", 0); + if (IS_ERR(dent)) + return; + + debug_create("dump", 0444, dent, debug_dump, &debug_ops); + debug_create("dump_sym", 0444, dent, debug_dump_sym, &debug_ops); + debug_create("dump_static", 0444, dent, debug_dump_static, &debug_ops); + debug_create("dump_static_sym", 0444, dent, + debug_dump_static_sym, &debug_ops); + debug_create("dump_power", 0444, dent, debug_dump_power, &debug_ops); + debug_create("dump_power_sym", 0444, dent, + debug_dump_power_sym, &debug_ops); + debug_create("dump_voters", 0444, dent, + debug_dump_voters, &debug_ops); + + debug_create("dump_cont", 0444, dent, debug_dump, &debug_ops_cont); + debug_create("dump_sym_cont", 0444, dent, + debug_dump_sym, &debug_ops_cont); + debug_create("dump_static_cont", 0444, dent, + debug_dump_static, &debug_ops_cont); + debug_create("dump_static_sym_cont", 0444, dent, + debug_dump_static_sym, &debug_ops_cont); + debug_create("dump_power_cont", 0444, dent, + debug_dump_power, &debug_ops_cont); + debug_create("dump_power_sym_cont", 0444, dent, + debug_dump_power_sym, &debug_ops_cont); + + smem_log_timeout_ms = 500; + smem_log_debug_mask = 0; +} +#else +static void smem_log_debugfs_init(void) {} +#endif + +static int smem_log_initialize(void) +{ + int ret; + + ret = _smem_log_init(); + if (ret < 0) { + pr_err("%s: init failed %d\n", __func__, ret); + return ret; + } + + ret = misc_register(&smem_log_dev); + if (ret < 0) { + pr_err("%s: device register failed %d\n", __func__, ret); + return ret; + } + + smem_log_enable = 1; + smem_log_initialized = 1; + smem_log_debugfs_init(); + return ret; +} + +static int modem_notifier(struct notifier_block *this, + unsigned long code, + void *_cmd) +{ + switch (code) { + case MODEM_NOTIFIER_SMSM_INIT: + if (!smem_log_initialized) + smem_log_initialize(); + break; + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block nb = { + .notifier_call = modem_notifier, +}; + +static int __init smem_log_init(void) +{ + return modem_register_notifier(&nb); +} + + +module_init(smem_log_init); + +MODULE_DESCRIPTION("smem log"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/socinfo.c b/arch/arm/mach-msm/socinfo.c new file mode 100644 index 00000000000..e3411a0b96c --- /dev/null +++ b/arch/arm/mach-msm/socinfo.c @@ -0,0 +1,666 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * SOC Info Routines + * + */ + +#include +#include +#include +#include + +#include "smd_private.h" + +#define BUILD_ID_LENGTH 32 + +enum { + HW_PLATFORM_UNKNOWN = 0, + HW_PLATFORM_SURF = 1, + HW_PLATFORM_FFA = 2, + HW_PLATFORM_FLUID = 3, + HW_PLATFORM_SVLTE_FFA = 4, + HW_PLATFORM_SVLTE_SURF = 5, + HW_PLATFORM_INVALID +}; + +const char *hw_platform[] = { + [HW_PLATFORM_UNKNOWN] = "Unknown", + [HW_PLATFORM_SURF] = "Surf", + [HW_PLATFORM_FFA] = "FFA", + [HW_PLATFORM_FLUID] = "Fluid", + [HW_PLATFORM_SVLTE_FFA] = "SVLTE_FFA", + [HW_PLATFORM_SVLTE_SURF] = "SLVTE_SURF" +}; + +enum { + ACCESSORY_CHIP_UNKNOWN = 0, + ACCESSORY_CHIP_CHARM = 58, +}; + +enum { + PLATFORM_SUBTYPE_UNKNOWN = 0x0, + PLATFORM_SUBTYPE_CHARM = 0x1, + PLATFORM_SUBTYPE_STRANGE = 0x2, + PLATFORM_SUBTYPE_STRANGE_2A = 0x3, + PLATFORM_SUBTYPE_INVALID, +}; + +const char *hw_platform_subtype[] = { + [PLATFORM_SUBTYPE_UNKNOWN] = "Unknown", + [PLATFORM_SUBTYPE_CHARM] = "charm", + [PLATFORM_SUBTYPE_STRANGE] = "strange", + [PLATFORM_SUBTYPE_STRANGE_2A] = "strange_2a," +}; + +/* Used to parse shared memory. Must match the modem. */ +struct socinfo_v1 { + uint32_t format; + uint32_t id; + uint32_t version; + char build_id[BUILD_ID_LENGTH]; +}; + +struct socinfo_v2 { + struct socinfo_v1 v1; + + /* only valid when format==2 */ + uint32_t raw_id; + uint32_t raw_version; +}; + +struct socinfo_v3 { + struct socinfo_v2 v2; + + /* only valid when format==3 */ + uint32_t hw_platform; +}; + +struct socinfo_v4 { + struct socinfo_v3 v3; + + /* only valid when format==4 */ + uint32_t platform_version; +}; + +struct socinfo_v5 { + struct socinfo_v4 v4; + + /* only valid when format==5 */ + uint32_t accessory_chip; +}; + +struct socinfo_v6 { + struct socinfo_v5 v5; + + /* only valid when format==6 */ + uint32_t hw_platform_subtype; +}; + +static union { + struct socinfo_v1 v1; + struct socinfo_v2 v2; + struct socinfo_v3 v3; + struct socinfo_v4 v4; + struct socinfo_v5 v5; + struct socinfo_v6 v6; +} *socinfo; + +static enum msm_cpu cpu_of_id[] = { + + /* 7x01 IDs */ + [1] = MSM_CPU_7X01, + [16] = MSM_CPU_7X01, + [17] = MSM_CPU_7X01, + [18] = MSM_CPU_7X01, + [19] = MSM_CPU_7X01, + [23] = MSM_CPU_7X01, + [25] = MSM_CPU_7X01, + [26] = MSM_CPU_7X01, + [32] = MSM_CPU_7X01, + [33] = MSM_CPU_7X01, + [34] = MSM_CPU_7X01, + [35] = MSM_CPU_7X01, + + /* 7x25 IDs */ + [20] = MSM_CPU_7X25, + [21] = MSM_CPU_7X25, /* 7225 */ + [24] = MSM_CPU_7X25, /* 7525 */ + [27] = MSM_CPU_7X25, /* 7625 */ + [39] = MSM_CPU_7X25, + [40] = MSM_CPU_7X25, + [41] = MSM_CPU_7X25, + [42] = MSM_CPU_7X25, + [62] = MSM_CPU_7X25, /* 7625-1 */ + [63] = MSM_CPU_7X25, /* 7225-1 */ + [66] = MSM_CPU_7X25, /* 7225-2 */ + + + /* 7x27 IDs */ + [43] = MSM_CPU_7X27, + [44] = MSM_CPU_7X27, + [61] = MSM_CPU_7X27, + [67] = MSM_CPU_7X27, /* 7227-1 */ + [68] = MSM_CPU_7X27, /* 7627-1 */ + [69] = MSM_CPU_7X27, /* 7627-2 */ + + + /* 8x50 IDs */ + [30] = MSM_CPU_8X50, + [36] = MSM_CPU_8X50, + [37] = MSM_CPU_8X50, + [38] = MSM_CPU_8X50, + + /* 7x30 IDs */ + [59] = MSM_CPU_7X30, + [60] = MSM_CPU_7X30, + + /* 8x55 IDs */ + [74] = MSM_CPU_8X55, + [75] = MSM_CPU_8X55, + [85] = MSM_CPU_8X55, + + /* 8x60 IDs */ + [70] = MSM_CPU_8X60, + [71] = MSM_CPU_8X60, + [86] = MSM_CPU_8X60, + + /* 8960 IDs */ + [87] = MSM_CPU_8960, + + /* 7x25A IDs */ + [88] = MSM_CPU_7X25A, + [89] = MSM_CPU_7X25A, + [96] = MSM_CPU_7X25A, + + /* 7x27A IDs */ + [90] = MSM_CPU_7X27A, + [91] = MSM_CPU_7X27A, + [92] = MSM_CPU_7X27A, + [97] = MSM_CPU_7X27A, + + /* FSM9xxx ID */ + [94] = FSM_CPU_9XXX, + [95] = FSM_CPU_9XXX, + + /* 7x25AA ID */ + [98] = MSM_CPU_7X25AA, + [99] = MSM_CPU_7X25AA, + [100] = MSM_CPU_7X25AA, + + /* Uninitialized IDs are not known to run Linux. + MSM_CPU_UNKNOWN is set to 0 to ensure these IDs are + considered as unknown CPU. */ +}; + +static enum msm_cpu cur_cpu; + +static struct socinfo_v1 dummy_socinfo = { + .format = 1, + .version = 1, + .build_id = "Dummy socinfo placeholder" +}; + +uint32_t socinfo_get_id(void) +{ + return (socinfo) ? socinfo->v1.id : 0; +} +EXPORT_SYMBOL_GPL(socinfo_get_id); + +uint32_t socinfo_get_version(void) +{ + return (socinfo) ? socinfo->v1.version : 0; +} + +char *socinfo_get_build_id(void) +{ + return (socinfo) ? socinfo->v1.build_id : NULL; +} + +uint32_t socinfo_get_raw_id(void) +{ + return socinfo ? + (socinfo->v1.format >= 2 ? socinfo->v2.raw_id : 0) + : 0; +} + +uint32_t socinfo_get_raw_version(void) +{ + return socinfo ? + (socinfo->v1.format >= 2 ? socinfo->v2.raw_version : 0) + : 0; +} + +uint32_t socinfo_get_platform_type(void) +{ + return socinfo ? + (socinfo->v1.format >= 3 ? socinfo->v3.hw_platform : 0) + : 0; +} + + +uint32_t socinfo_get_platform_version(void) +{ + return socinfo ? + (socinfo->v1.format >= 4 ? socinfo->v4.platform_version : 0) + : 0; +} + +/* This information is directly encoded by the machine id */ +/* Thus no external callers rely on this information at the moment */ +static uint32_t socinfo_get_accessory_chip(void) +{ + return socinfo ? + (socinfo->v1.format >= 5 ? socinfo->v5.accessory_chip : 0) + : 0; +} + +uint32_t socinfo_get_platform_subtype(void) +{ + return socinfo ? + (socinfo->v1.format >= 6 ? socinfo->v6.hw_platform_subtype : 0) + : 0; +} + +enum msm_cpu socinfo_get_msm_cpu(void) +{ + return cur_cpu; +} +EXPORT_SYMBOL_GPL(socinfo_get_msm_cpu); + +static ssize_t +socinfo_show_id(struct sys_device *dev, + struct sysdev_attribute *attr, + char *buf) +{ + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return 0; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", socinfo_get_id()); +} + +static ssize_t +socinfo_show_version(struct sys_device *dev, + struct sysdev_attribute *attr, + char *buf) +{ + uint32_t version; + + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return 0; + } + + version = socinfo_get_version(); + return snprintf(buf, PAGE_SIZE, "%u.%u\n", + SOCINFO_VERSION_MAJOR(version), + SOCINFO_VERSION_MINOR(version)); +} + +static ssize_t +socinfo_show_build_id(struct sys_device *dev, + struct sysdev_attribute *attr, + char *buf) +{ + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return 0; + } + + return snprintf(buf, PAGE_SIZE, "%-.32s\n", socinfo_get_build_id()); +} + +static ssize_t +socinfo_show_raw_id(struct sys_device *dev, + struct sysdev_attribute *attr, + char *buf) +{ + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return 0; + } + if (socinfo->v1.format < 2) { + pr_err("%s: Raw ID not available!\n", __func__); + return 0; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", socinfo_get_raw_id()); +} + +static ssize_t +socinfo_show_raw_version(struct sys_device *dev, + struct sysdev_attribute *attr, + char *buf) +{ + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return 0; + } + if (socinfo->v1.format < 2) { + pr_err("%s: Raw version not available!\n", __func__); + return 0; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", socinfo_get_raw_version()); +} + +static ssize_t +socinfo_show_platform_type(struct sys_device *dev, + struct sysdev_attribute *attr, + char *buf) +{ + uint32_t hw_type; + + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return 0; + } + if (socinfo->v1.format < 3) { + pr_err("%s: platform type not available!\n", __func__); + return 0; + } + + hw_type = socinfo_get_platform_type(); + if (hw_type >= HW_PLATFORM_INVALID) { + pr_err("%s: Invalid hardware platform type found\n", + __func__); + hw_type = HW_PLATFORM_UNKNOWN; + } + + return snprintf(buf, PAGE_SIZE, "%-.32s\n", hw_platform[hw_type]); +} + +static ssize_t +socinfo_show_platform_version(struct sys_device *dev, + struct sysdev_attribute *attr, + char *buf) +{ + + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return 0; + } + if (socinfo->v1.format < 4) { + pr_err("%s: platform version not available!\n", __func__); + return 0; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", + socinfo_get_platform_version()); +} + +static ssize_t +socinfo_show_accessory_chip(struct sys_device *dev, + struct sysdev_attribute *attr, + char *buf) +{ + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return 0; + } + if (socinfo->v1.format < 5) { + pr_err("%s: accessory chip not available!\n", __func__); + return 0; + } + + return snprintf(buf, PAGE_SIZE, "%u\n", + socinfo_get_accessory_chip()); +} + +static ssize_t +socinfo_show_platform_subtype(struct sys_device *dev, + struct sysdev_attribute *attr, + char *buf) +{ + uint32_t hw_subtype; + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return 0; + } + if (socinfo->v1.format < 6) { + pr_err("%s: platform subtype not available!\n", __func__); + return 0; + } + + hw_subtype = socinfo_get_platform_subtype(); + if (hw_subtype >= PLATFORM_SUBTYPE_INVALID) { + pr_err("%s: Invalid hardware platform sub type found\n", + __func__); + hw_subtype = PLATFORM_SUBTYPE_UNKNOWN; + } + return snprintf(buf, PAGE_SIZE, "%-.32s\n", + hw_platform_subtype[hw_subtype]); +} + +static struct sysdev_attribute socinfo_v1_files[] = { + _SYSDEV_ATTR(id, 0444, socinfo_show_id, NULL), + _SYSDEV_ATTR(version, 0444, socinfo_show_version, NULL), + _SYSDEV_ATTR(build_id, 0444, socinfo_show_build_id, NULL), +}; + +static struct sysdev_attribute socinfo_v2_files[] = { + _SYSDEV_ATTR(raw_id, 0444, socinfo_show_raw_id, NULL), + _SYSDEV_ATTR(raw_version, 0444, socinfo_show_raw_version, NULL), +}; + +static struct sysdev_attribute socinfo_v3_files[] = { + _SYSDEV_ATTR(hw_platform, 0444, socinfo_show_platform_type, NULL), +}; + +static struct sysdev_attribute socinfo_v4_files[] = { + _SYSDEV_ATTR(platform_version, 0444, + socinfo_show_platform_version, NULL), +}; + +static struct sysdev_attribute socinfo_v5_files[] = { + _SYSDEV_ATTR(accessory_chip, 0444, + socinfo_show_accessory_chip, NULL), +}; + +static struct sysdev_attribute socinfo_v6_files[] = { + _SYSDEV_ATTR(platform_subtype, 0444, + socinfo_show_platform_subtype, NULL), +}; + +static struct sysdev_class soc_sysdev_class = { + .name = "soc", +}; + +static struct sys_device soc_sys_device = { + .id = 0, + .cls = &soc_sysdev_class, +}; + +static int __init socinfo_create_files(struct sys_device *dev, + struct sysdev_attribute files[], + int size) +{ + int i; + for (i = 0; i < size; i++) { + int err = sysdev_create_file(dev, &files[i]); + if (err) { + pr_err("%s: sysdev_create_file(%s)=%d\n", + __func__, files[i].attr.name, err); + return err; + } + } + return 0; +} + +static int __init socinfo_init_sysdev(void) +{ + int err; + + if (!socinfo) { + pr_err("%s: No socinfo found!\n", __func__); + return -ENODEV; + } + + err = sysdev_class_register(&soc_sysdev_class); + if (err) { + pr_err("%s: sysdev_class_register fail (%d)\n", + __func__, err); + return err; + } + err = sysdev_register(&soc_sys_device); + if (err) { + pr_err("%s: sysdev_register fail (%d)\n", + __func__, err); + return err; + } + socinfo_create_files(&soc_sys_device, socinfo_v1_files, + ARRAY_SIZE(socinfo_v1_files)); + if (socinfo->v1.format < 2) + return err; + socinfo_create_files(&soc_sys_device, socinfo_v2_files, + ARRAY_SIZE(socinfo_v2_files)); + + if (socinfo->v1.format < 3) + return err; + + socinfo_create_files(&soc_sys_device, socinfo_v3_files, + ARRAY_SIZE(socinfo_v3_files)); + + if (socinfo->v1.format < 4) + return err; + + socinfo_create_files(&soc_sys_device, socinfo_v4_files, + ARRAY_SIZE(socinfo_v4_files)); + + if (socinfo->v1.format < 5) + return err; + + socinfo_create_files(&soc_sys_device, socinfo_v5_files, + ARRAY_SIZE(socinfo_v5_files)); + + if (socinfo->v1.format < 6) + return err; + + return socinfo_create_files(&soc_sys_device, socinfo_v6_files, + ARRAY_SIZE(socinfo_v6_files)); + +} + +arch_initcall(socinfo_init_sysdev); + +void *setup_dummy_socinfo(void) +{ + if (machine_is_msm8960_rumi3() || machine_is_msm8960_sim() || + machine_is_msm8960_cdp()) + dummy_socinfo.id = 87; + return (void *) &dummy_socinfo; +} + +int __init socinfo_init(void) +{ + socinfo = smem_alloc(SMEM_HW_SW_BUILD_ID, sizeof(struct socinfo_v6)); + + if (!socinfo) + socinfo = smem_alloc(SMEM_HW_SW_BUILD_ID, + sizeof(struct socinfo_v5)); + + if (!socinfo) + socinfo = smem_alloc(SMEM_HW_SW_BUILD_ID, + sizeof(struct socinfo_v4)); + + if (!socinfo) + socinfo = smem_alloc(SMEM_HW_SW_BUILD_ID, + sizeof(struct socinfo_v3)); + + if (!socinfo) + socinfo = smem_alloc(SMEM_HW_SW_BUILD_ID, + sizeof(struct socinfo_v2)); + + if (!socinfo) + socinfo = smem_alloc(SMEM_HW_SW_BUILD_ID, + sizeof(struct socinfo_v1)); + + if (!socinfo) { + pr_warn("%s: Can't find SMEM_HW_SW_BUILD_ID; falling back on " + "dummy values.\n", __func__); + socinfo = setup_dummy_socinfo(); + } + + WARN(!socinfo_get_id(), "Unknown SOC ID!\n"); + WARN(socinfo_get_id() >= ARRAY_SIZE(cpu_of_id), + "New IDs added! ID => CPU mapping might need an update.\n"); + + if (socinfo->v1.id < ARRAY_SIZE(cpu_of_id)) + cur_cpu = cpu_of_id[socinfo->v1.id]; + + switch (socinfo->v1.format) { + case 1: + pr_info("%s: v%u, id=%u, ver=%u.%u\n", + __func__, socinfo->v1.format, socinfo->v1.id, + SOCINFO_VERSION_MAJOR(socinfo->v1.version), + SOCINFO_VERSION_MINOR(socinfo->v1.version)); + break; + case 2: + pr_info("%s: v%u, id=%u, ver=%u.%u, " + "raw_id=%u, raw_ver=%u\n", + __func__, socinfo->v1.format, socinfo->v1.id, + SOCINFO_VERSION_MAJOR(socinfo->v1.version), + SOCINFO_VERSION_MINOR(socinfo->v1.version), + socinfo->v2.raw_id, socinfo->v2.raw_version); + break; + case 3: + pr_info("%s: v%u, id=%u, ver=%u.%u, " + "raw_id=%u, raw_ver=%u, hw_plat=%u\n", + __func__, socinfo->v1.format, socinfo->v1.id, + SOCINFO_VERSION_MAJOR(socinfo->v1.version), + SOCINFO_VERSION_MINOR(socinfo->v1.version), + socinfo->v2.raw_id, socinfo->v2.raw_version, + socinfo->v3.hw_platform); + break; + case 4: + pr_info("%s: v%u, id=%u, ver=%u.%u, " + "raw_id=%u, raw_ver=%u, hw_plat=%u, hw_plat_ver=%u\n", + __func__, socinfo->v1.format, socinfo->v1.id, + SOCINFO_VERSION_MAJOR(socinfo->v1.version), + SOCINFO_VERSION_MINOR(socinfo->v1.version), + socinfo->v2.raw_id, socinfo->v2.raw_version, + socinfo->v3.hw_platform, socinfo->v4.platform_version); + break; + case 5: + pr_info("%s: v%u, id=%u, ver=%u.%u, " + "raw_id=%u, raw_ver=%u, hw_plat=%u, hw_plat_ver=%u\n" + " accessory_chip=%u\n", __func__, socinfo->v1.format, + socinfo->v1.id, + SOCINFO_VERSION_MAJOR(socinfo->v1.version), + SOCINFO_VERSION_MINOR(socinfo->v1.version), + socinfo->v2.raw_id, socinfo->v2.raw_version, + socinfo->v3.hw_platform, socinfo->v4.platform_version, + socinfo->v5.accessory_chip); + break; + case 6: + pr_info("%s: v%u, id=%u, ver=%u.%u, " + "raw_id=%u, raw_ver=%u, hw_plat=%u, hw_plat_ver=%u\n" + " accessory_chip=%u hw_plat_subtype=%u\n", __func__, + socinfo->v1.format, + socinfo->v1.id, + SOCINFO_VERSION_MAJOR(socinfo->v1.version), + SOCINFO_VERSION_MINOR(socinfo->v1.version), + socinfo->v2.raw_id, socinfo->v2.raw_version, + socinfo->v3.hw_platform, socinfo->v4.platform_version, + socinfo->v5.accessory_chip, + socinfo->v6.hw_platform_subtype); + break; + default: + pr_err("%s: Unknown format found\n", __func__); + break; + } + + return 0; +} diff --git a/arch/arm/mach-msm/spm-v2.c b/arch/arm/mach-msm/spm-v2.c new file mode 100644 index 00000000000..46d6eb314e1 --- /dev/null +++ b/arch/arm/mach-msm/spm-v2.c @@ -0,0 +1,318 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "spm_driver.h" + +enum { + MSM_SPM_DEBUG_SHADOW = 1U << 0, + MSM_SPM_DEBUG_VCTL = 1U << 1, +}; + +static int msm_spm_debug_mask; +module_param_named( + debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); + +#define MSM_SPM_PMIC_STATE_IDLE 0 + + +static uint32_t msm_spm_reg_offsets[MSM_SPM_REG_NR] = { + [MSM_SPM_REG_SAW2_SECURE] = 0x00, + + [MSM_SPM_REG_SAW2_ID] = 0x04, + [MSM_SPM_REG_SAW2_CFG] = 0x08, + [MSM_SPM_REG_SAW2_STS0] = 0x0C, + [MSM_SPM_REG_SAW2_STS1] = 0x10, + + [MSM_SPM_REG_SAW2_VCTL] = 0x14, + + [MSM_SPM_REG_SAW2_AVS_CTL] = 0x18, + [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x1C, + + [MSM_SPM_REG_SAW2_SPM_CTL] = 0x20, + [MSM_SPM_REG_SAW2_PMIC_DLY] = 0x24, + [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x28, + [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x2C, + [MSM_SPM_REG_SAW2_RST] = 0x30, + + [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80, +}; + +/****************************************************************************** + * Internal helper functions + *****************************************************************************/ + +static inline void msm_spm_drv_set_vctl(struct msm_spm_driver_data *dev, + uint32_t vlevel) +{ + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] &= ~0xFF; + dev->reg_shadow[MSM_SPM_REG_SAW2_VCTL] |= vlevel; + + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_0] &= ~0xFF; + dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_0] |= vlevel; +} + +static void msm_spm_drv_flush_shadow(struct msm_spm_driver_data *dev, + unsigned int reg_index) +{ + __raw_writel(dev->reg_shadow[reg_index], + dev->reg_base_addr + msm_spm_reg_offsets[reg_index]); +} + +static void msm_spm_drv_load_shadow(struct msm_spm_driver_data *dev, + unsigned int reg_index) +{ + dev->reg_shadow[reg_index] = + __raw_readl(dev->reg_base_addr + + msm_spm_reg_offsets[reg_index]); +} + +static inline uint32_t msm_spm_drv_get_awake_vlevel( + struct msm_spm_driver_data *dev) +{ + return dev->reg_shadow[MSM_SPM_REG_SAW2_PMIC_DATA_0] & 0xFF; +} + +static inline uint32_t msm_spm_drv_get_sts_pmic_state( + struct msm_spm_driver_data *dev) +{ + return (dev->reg_shadow[MSM_SPM_REG_SAW2_STS0] >> 10) & 0x03; +} + +static inline uint32_t msm_spm_drv_get_sts_curr_pmic_data( + struct msm_spm_driver_data *dev) +{ + return dev->reg_shadow[MSM_SPM_REG_SAW2_STS1] & 0xFF; +} + +static inline uint32_t msm_spm_drv_get_num_spm_entry( + struct msm_spm_driver_data *dev) +{ + return 32; +} + +static inline void msm_spm_drv_set_start_addr( + struct msm_spm_driver_data *dev, uint32_t addr) +{ + addr &= 0x7F; + addr <<= 4; + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F; + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr; +} + + +/****************************************************************************** + * Public functions + *****************************************************************************/ +inline int msm_spm_drv_set_spm_enable( + struct msm_spm_driver_data *dev, bool enable) +{ + uint32_t value = enable ? 0x01 : 0x00; + + if (!dev) + return -EINVAL; + + if ((dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & 0x01) ^ value) { + + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1; + dev->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value; + + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); + wmb(); + } + return 0; +} +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev) +{ + int i; + int num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); + + if (!dev) { + __WARN(); + return; + } + + for (i = 0; i < num_spm_entry; i++) { + __raw_writel(dev->reg_seq_entry_shadow[i], + dev->reg_base_addr + + msm_spm_reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY] + + 4 * i); + } + mb(); +} + +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, + uint8_t *cmd, uint32_t offset) +{ + uint32_t offset_w = offset / 4; + int ret = 0; + + if (!cmd || !dev) { + __WARN(); + goto failed_write_seq_data; + }; + + while (1) { + int i; + uint32_t cmd_w = 0; + uint8_t last_cmd = 0; + + for (i = 0; i < 4; i++) { + last_cmd = (last_cmd == 0x0f) ? 0x0f : *(cmd + i); + cmd_w |= last_cmd << (i * 8); + ret++; + } + + if (offset_w >= msm_spm_drv_get_num_spm_entry(dev)) { + __WARN(); + goto failed_write_seq_data; + } + + cmd += i; + dev->reg_seq_entry_shadow[offset_w++] = cmd_w; + if (last_cmd == 0x0f) + break; + } + return ret; + +failed_write_seq_data: + return -EINVAL; +} + +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, + uint32_t addr) +{ + + /* SPM is configured to reset start address to zero after end of Program + */ + if (!dev) + return -EINVAL; + + msm_spm_drv_set_start_addr(dev, addr); + + if (addr) { + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_SPM_CTL); + wmb(); + } + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) { + int i; + for (i = 0; i < MSM_SPM_REG_NR; i++) + pr_info("%s: reg %02x = 0x%08x\n", __func__, + msm_spm_reg_offsets[i], dev->reg_shadow[i]); + } + + return 0; +} + +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, unsigned int vlevel) +{ + uint32_t timeout_us; + + if (!dev) + return -EINVAL; + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: requesting vlevel 0x%x\n", + __func__, vlevel); + + msm_spm_drv_set_vctl(dev, vlevel); + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_VCTL); + msm_spm_drv_flush_shadow(dev, MSM_SPM_REG_SAW2_PMIC_DATA_0); + mb(); + + /* Wait for PMIC state to return to idle or until timeout */ + timeout_us = dev->vctl_timeout_us; + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_STS0); + while (msm_spm_drv_get_sts_pmic_state(dev) != MSM_SPM_PMIC_STATE_IDLE) { + if (!timeout_us) + goto set_vdd_bail; + + if (timeout_us > 10) { + udelay(10); + timeout_us -= 10; + } else { + udelay(timeout_us); + timeout_us = 0; + } + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_STS0); + } + + msm_spm_drv_load_shadow(dev, MSM_SPM_REG_SAW2_STS1); + + if (msm_spm_drv_get_sts_curr_pmic_data(dev) != vlevel) + goto set_vdd_bail; + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: done, remaining timeout %uus\n", + __func__, timeout_us); + + return 0; + +set_vdd_bail: + pr_err("%s: failed, remaining timeout %uus, vlevel 0x%x\n", + __func__, timeout_us, msm_spm_drv_get_sts_curr_pmic_data(dev)); + return -EIO; +} + +int __init msm_spm_drv_init(struct msm_spm_driver_data *dev, + struct msm_spm_platform_data *data) +{ + + int i; + int num_spm_entry; + + BUG_ON(!dev || !data); + + dev->reg_base_addr = data->reg_base_addr; + memcpy(dev->reg_shadow, data->reg_init_values, + sizeof(data->reg_init_values)); + + dev->vctl_timeout_us = data->vctl_timeout_us; + + for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++) + msm_spm_drv_flush_shadow(dev, i); + /* barrier to ensure write completes before we update shadow + * registers + */ + mb(); + + for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++) + msm_spm_drv_load_shadow(dev, i); + + /* barrier to ensure read completes before we proceed further*/ + mb(); + + num_spm_entry = msm_spm_drv_get_num_spm_entry(dev); + + dev->reg_seq_entry_shadow = + kmalloc(sizeof(*dev->reg_seq_entry_shadow) * num_spm_entry, + GFP_KERNEL); + + if (!dev->reg_seq_entry_shadow) + return -ENOMEM; + + + memset(dev->reg_seq_entry_shadow, 0x0f, + num_spm_entry * sizeof(*dev->reg_seq_entry_shadow)); + + return 0; +} diff --git a/arch/arm/mach-msm/spm.c b/arch/arm/mach-msm/spm.c new file mode 100644 index 00000000000..4654fba0b6f --- /dev/null +++ b/arch/arm/mach-msm/spm.c @@ -0,0 +1,303 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "spm.h" + + +enum { + MSM_SPM_DEBUG_SHADOW = 1U << 0, + MSM_SPM_DEBUG_VCTL = 1U << 1, +}; + +static int msm_spm_debug_mask; +module_param_named( + debug_mask, msm_spm_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP +); + +#define MSM_SPM_PMIC_STATE_IDLE 0 + +static uint32_t msm_spm_reg_offsets[MSM_SPM_REG_NR] = { + [MSM_SPM_REG_SAW_AVS_CTL] = 0x04, + + [MSM_SPM_REG_SAW_VCTL] = 0x08, + [MSM_SPM_REG_SAW_STS] = 0x0C, + [MSM_SPM_REG_SAW_CFG] = 0x10, + + [MSM_SPM_REG_SAW_SPM_CTL] = 0x14, + [MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY] = 0x18, + [MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY] = 0x1C, + + [MSM_SPM_REG_SAW_SPM_PMIC_CTL] = 0x20, + [MSM_SPM_REG_SAW_SLP_CLK_EN] = 0x24, + [MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN] = 0x28, + [MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN] = 0x2C, + + [MSM_SPM_REG_SAW_SLP_CLMP_EN] = 0x30, + [MSM_SPM_REG_SAW_SLP_RST_EN] = 0x34, + [MSM_SPM_REG_SAW_SPM_MPM_CFG] = 0x38, +}; + +struct msm_spm_device { + void __iomem *reg_base_addr; + uint32_t reg_shadow[MSM_SPM_REG_NR]; + + uint8_t awake_vlevel; + uint8_t retention_vlevel; + uint8_t collapse_vlevel; + uint8_t retention_mid_vlevel; + uint8_t collapse_mid_vlevel; + + uint32_t vctl_timeout_us; + + unsigned int low_power_mode; + bool notify_rpm; + bool dirty; +}; + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_spm_devices); +static atomic_t msm_spm_set_vdd_x_cpu_allowed = ATOMIC_INIT(1); + +/****************************************************************************** + * Internal helper functions + *****************************************************************************/ + +static inline void msm_spm_set_vctl( + struct msm_spm_device *dev, uint32_t vlevel) +{ + dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] &= ~0xFF; + dev->reg_shadow[MSM_SPM_REG_SAW_VCTL] |= vlevel; +} + +static inline void msm_spm_set_spm_ctl(struct msm_spm_device *dev, + uint32_t rpm_bypass, uint32_t mode_encoding) +{ + dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] &= ~0x0F; + dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] |= rpm_bypass << 3; + dev->reg_shadow[MSM_SPM_REG_SAW_SPM_CTL] |= mode_encoding; +} + +static inline void msm_spm_set_pmic_ctl(struct msm_spm_device *dev, + uint32_t awake_vlevel, uint32_t mid_vlevel, uint32_t sleep_vlevel) +{ + dev->reg_shadow[MSM_SPM_REG_SAW_SPM_PMIC_CTL] = + (mid_vlevel << 16) | (awake_vlevel << 8) | (sleep_vlevel); +} + +static inline void msm_spm_set_slp_rst_en( + struct msm_spm_device *dev, uint32_t slp_rst_en) +{ + dev->reg_shadow[MSM_SPM_REG_SAW_SLP_RST_EN] = slp_rst_en; +} + +static inline void msm_spm_flush_shadow( + struct msm_spm_device *dev, unsigned int reg_index) +{ + __raw_writel(dev->reg_shadow[reg_index], + dev->reg_base_addr + msm_spm_reg_offsets[reg_index]); +} + +static inline void msm_spm_load_shadow( + struct msm_spm_device *dev, unsigned int reg_index) +{ + dev->reg_shadow[reg_index] = __raw_readl(dev->reg_base_addr + + msm_spm_reg_offsets[reg_index]); +} + +static inline uint32_t msm_spm_get_sts_pmic_state(struct msm_spm_device *dev) +{ + return (dev->reg_shadow[MSM_SPM_REG_SAW_STS] >> 20) & 0x03; +} + +static inline uint32_t msm_spm_get_sts_curr_pmic_data( + struct msm_spm_device *dev) +{ + return (dev->reg_shadow[MSM_SPM_REG_SAW_STS] >> 10) & 0xFF; +} + +/****************************************************************************** + * Public functions + *****************************************************************************/ +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) +{ + struct msm_spm_device *dev = &__get_cpu_var(msm_spm_devices); + uint32_t rpm_bypass = notify_rpm ? 0x00 : 0x01; + + if (mode == dev->low_power_mode && notify_rpm == dev->notify_rpm + && !dev->dirty) + return 0; + + switch (mode) { + case MSM_SPM_MODE_CLOCK_GATING: + msm_spm_set_spm_ctl(dev, rpm_bypass, 0x00); + msm_spm_set_slp_rst_en(dev, 0x00); + break; + + case MSM_SPM_MODE_POWER_RETENTION: + msm_spm_set_spm_ctl(dev, rpm_bypass, 0x02); + msm_spm_set_pmic_ctl(dev, dev->awake_vlevel, + dev->retention_mid_vlevel, dev->retention_vlevel); + msm_spm_set_slp_rst_en(dev, 0x00); + break; + + case MSM_SPM_MODE_POWER_COLLAPSE: + msm_spm_set_spm_ctl(dev, rpm_bypass, 0x02); + msm_spm_set_pmic_ctl(dev, dev->awake_vlevel, + dev->collapse_mid_vlevel, dev->collapse_vlevel); + msm_spm_set_slp_rst_en(dev, 0x01); + break; + + default: + BUG(); + } + + msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_CTL); + msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SPM_PMIC_CTL); + msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_SLP_RST_EN); + /* Ensure that the registers are written before returning */ + mb(); + + dev->low_power_mode = mode; + dev->notify_rpm = notify_rpm; + dev->dirty = false; + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_SHADOW) { + int i; + for (i = 0; i < MSM_SPM_REG_NR; i++) + pr_info("%s: reg %02x = 0x%08x\n", __func__, + msm_spm_reg_offsets[i], dev->reg_shadow[i]); + } + + return 0; +} + +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) +{ + unsigned long flags; + struct msm_spm_device *dev; + uint32_t timeout_us; + + local_irq_save(flags); + + if (!atomic_read(&msm_spm_set_vdd_x_cpu_allowed) && + unlikely(smp_processor_id() != cpu)) { + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: attempting to set vdd of cpu %u from " + "cpu %u\n", __func__, cpu, smp_processor_id()); + goto set_vdd_x_cpu_bail; + } + + dev = &per_cpu(msm_spm_devices, cpu); + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: requesting cpu %u vlevel 0x%x\n", + __func__, cpu, vlevel); + + msm_spm_set_vctl(dev, vlevel); + msm_spm_flush_shadow(dev, MSM_SPM_REG_SAW_VCTL); + + /* Wait for PMIC state to return to idle or until timeout */ + timeout_us = dev->vctl_timeout_us; + msm_spm_load_shadow(dev, MSM_SPM_REG_SAW_STS); + while (msm_spm_get_sts_pmic_state(dev) != MSM_SPM_PMIC_STATE_IDLE) { + if (!timeout_us) + goto set_vdd_bail; + + if (timeout_us > 10) { + udelay(10); + timeout_us -= 10; + } else { + udelay(timeout_us); + timeout_us = 0; + } + msm_spm_load_shadow(dev, MSM_SPM_REG_SAW_STS); + } + + if (msm_spm_get_sts_curr_pmic_data(dev) != vlevel) + goto set_vdd_bail; + + dev->awake_vlevel = vlevel; + dev->dirty = true; + + if (msm_spm_debug_mask & MSM_SPM_DEBUG_VCTL) + pr_info("%s: cpu %u done, remaining timeout %uus\n", + __func__, cpu, timeout_us); + + local_irq_restore(flags); + return 0; + +set_vdd_bail: + pr_err("%s: cpu %u failed, remaining timeout %uus, vlevel 0x%x\n", + __func__, cpu, timeout_us, msm_spm_get_sts_curr_pmic_data(dev)); + +set_vdd_x_cpu_bail: + local_irq_restore(flags); + return -EIO; +} + +void msm_spm_reinit(void) +{ + struct msm_spm_device *dev = &__get_cpu_var(msm_spm_devices); + int i; + + for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++) + msm_spm_flush_shadow(dev, i); + + /* Ensure that the registers are written before returning */ + mb(); +} + +void msm_spm_allow_x_cpu_set_vdd(bool allowed) +{ + atomic_set(&msm_spm_set_vdd_x_cpu_allowed, allowed ? 1 : 0); +} + +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs) +{ + unsigned int cpu; + + BUG_ON(nr_devs < num_possible_cpus()); + for_each_possible_cpu(cpu) { + struct msm_spm_device *dev = &per_cpu(msm_spm_devices, cpu); + int i; + + dev->reg_base_addr = data[cpu].reg_base_addr; + memcpy(dev->reg_shadow, data[cpu].reg_init_values, + sizeof(data[cpu].reg_init_values)); + + dev->awake_vlevel = data[cpu].awake_vlevel; + dev->retention_vlevel = data[cpu].retention_vlevel; + dev->collapse_vlevel = data[cpu].collapse_vlevel; + dev->retention_mid_vlevel = data[cpu].retention_mid_vlevel; + dev->collapse_mid_vlevel = data[cpu].collapse_mid_vlevel; + dev->vctl_timeout_us = data[cpu].vctl_timeout_us; + + for (i = 0; i < MSM_SPM_REG_NR_INITIALIZE; i++) + msm_spm_flush_shadow(dev, i); + + /* Ensure that the registers are written before returning */ + mb(); + + dev->low_power_mode = MSM_SPM_MODE_CLOCK_GATING; + dev->notify_rpm = false; + dev->dirty = true; + } + + return 0; +} diff --git a/arch/arm/mach-msm/spm.h b/arch/arm/mach-msm/spm.h new file mode 100644 index 00000000000..5825bcf373d --- /dev/null +++ b/arch/arm/mach-msm/spm.h @@ -0,0 +1,153 @@ +/* Copyright (c) 2010-2011 Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ARCH_ARM_MACH_MSM_SPM_H +#define __ARCH_ARM_MACH_MSM_SPM_H +enum { + MSM_SPM_MODE_DISABLED, + MSM_SPM_MODE_CLOCK_GATING, + MSM_SPM_MODE_POWER_RETENTION, + MSM_SPM_MODE_POWER_COLLAPSE, + MSM_SPM_MODE_NR +}; + +enum { + MSM_SPM_L2_MODE_DISABLED = MSM_SPM_MODE_DISABLED, + MSM_SPM_L2_MODE_RETENTION, + MSM_SPM_L2_MODE_GDHS, + MSM_SPM_L2_MODE_POWER_COLLAPSE, +}; + +#if defined(CONFIG_MSM_SPM_V1) + +enum { + MSM_SPM_REG_SAW_AVS_CTL, + MSM_SPM_REG_SAW_CFG, + MSM_SPM_REG_SAW_SPM_CTL, + MSM_SPM_REG_SAW_SPM_SLP_TMR_DLY, + MSM_SPM_REG_SAW_SPM_WAKE_TMR_DLY, + MSM_SPM_REG_SAW_SLP_CLK_EN, + MSM_SPM_REG_SAW_SLP_HSFS_PRECLMP_EN, + MSM_SPM_REG_SAW_SLP_HSFS_POSTCLMP_EN, + MSM_SPM_REG_SAW_SLP_CLMP_EN, + MSM_SPM_REG_SAW_SLP_RST_EN, + MSM_SPM_REG_SAW_SPM_MPM_CFG, + MSM_SPM_REG_NR_INITIALIZE, + + MSM_SPM_REG_SAW_VCTL = MSM_SPM_REG_NR_INITIALIZE, + MSM_SPM_REG_SAW_STS, + MSM_SPM_REG_SAW_SPM_PMIC_CTL, + MSM_SPM_REG_NR +}; + +struct msm_spm_platform_data { + void __iomem *reg_base_addr; + uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE]; + + uint8_t awake_vlevel; + uint8_t retention_vlevel; + uint8_t collapse_vlevel; + uint8_t retention_mid_vlevel; + uint8_t collapse_mid_vlevel; + + uint32_t vctl_timeout_us; +}; + +#elif defined(CONFIG_MSM_SPM_V2) + +enum { + MSM_SPM_REG_SAW2_SECURE, + MSM_SPM_REG_SAW2_ID, + MSM_SPM_REG_SAW2_CFG, + MSM_SPM_REG_SAW2_STS0, + MSM_SPM_REG_SAW2_STS1, + MSM_SPM_REG_SAW2_VCTL, + MSM_SPM_REG_SAW2_AVS_CTL, + MSM_SPM_REG_SAW2_AVS_HYSTERESIS, + MSM_SPM_REG_SAW2_SPM_CTL, + MSM_SPM_REG_SAW2_PMIC_DLY, + MSM_SPM_REG_SAW2_PMIC_DATA_0, + MSM_SPM_REG_SAW2_PMIC_DATA_1, + MSM_SPM_REG_SAW2_RST, + + MSM_SPM_REG_NR_INITIALIZE, + MSM_SPM_REG_SAW2_SEQ_ENTRY = MSM_SPM_REG_NR_INITIALIZE, + + MSM_SPM_REG_NR +}; + +struct msm_spm_seq_entry { + uint32_t mode; + uint8_t *cmd; + bool notify_rpm; +}; + +struct msm_spm_platform_data { + void __iomem *reg_base_addr; + uint32_t reg_init_values[MSM_SPM_REG_NR_INITIALIZE]; + + uint8_t awake_vlevel; + uint32_t vctl_timeout_us; + + uint32_t num_modes; + struct msm_spm_seq_entry *modes; +}; +#endif + +#if defined(CONFIG_MSM_SPM_V1) || defined(CONFIG_MSM_SPM_V2) + +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm); +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel); +void msm_spm_reinit(void); +void msm_spm_allow_x_cpu_set_vdd(bool allowed); +int msm_spm_init(struct msm_spm_platform_data *data, int nr_devs); + +#if defined(CONFIG_MSM_L2_SPM) +int msm_spm_l2_set_low_power_mode(unsigned int mode, bool notify_rpm); +int msm_spm_l2_init(struct msm_spm_platform_data *data); +#else +static inline int msm_spm_l2_set_low_power_mode(unsigned int mode, + bool notify_rpm) +{ + return -ENOSYS; +} +static inline int msm_spm_l2_init(struct msm_spm_platform_data *data) +{ + return -ENOSYS; +} +#endif /* defined(CONFIG_MSM_L2_SPM) */ + +#else /* defined(CONFIG_MSM_SPM_V1) || defined(CONFIG_MSM_SPM_V2) */ + +static inline int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) +{ + return -ENOSYS; +} + +static inline int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) +{ + return -ENOSYS; +} + +static inline void msm_spm_reinit(void) +{ + /* empty */ +} + +static inline void msm_spm_allow_x_cpu_set_vdd(bool allowed) +{ + /* empty */ +} + +#endif /*defined(CONFIG_MSM_SPM_V1) || defined (CONFIG_MSM_SPM_V2) */ + +#endif /* __ARCH_ARM_MACH_MSM_SPM_H */ diff --git a/arch/arm/mach-msm/spm_devices.c b/arch/arm/mach-msm/spm_devices.c new file mode 100644 index 00000000000..01bf9ae9d67 --- /dev/null +++ b/arch/arm/mach-msm/spm_devices.c @@ -0,0 +1,168 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "spm.h" +#include "spm_driver.h" + +struct msm_spm_power_modes { + uint32_t mode; + bool notify_rpm; + uint32_t start_addr; + +}; + +struct msm_spm_device { + struct msm_spm_driver_data reg_data; + struct msm_spm_power_modes *modes; + uint32_t num_modes; +}; + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device); +static atomic_t msm_spm_set_vdd_x_cpu_allowed = ATOMIC_INIT(1); + +void msm_spm_allow_x_cpu_set_vdd(bool allowed) +{ + atomic_set(&msm_spm_set_vdd_x_cpu_allowed, allowed ? 1 : 0); +} + +int msm_spm_set_vdd(unsigned int cpu, unsigned int vlevel) +{ + unsigned long flags; + struct msm_spm_device *dev; + int ret = -EIO; + + local_irq_save(flags); + if (!atomic_read(&msm_spm_set_vdd_x_cpu_allowed) && + unlikely(smp_processor_id() != cpu)) { + goto set_vdd_x_cpu_bail; + } + + dev = &per_cpu(msm_cpu_spm_device, cpu); + ret = msm_spm_drv_set_vdd(&dev->reg_data, vlevel); + +set_vdd_x_cpu_bail: + local_irq_restore(flags); + return ret; +} + +static int msm_spm_dev_set_low_power_mode(struct msm_spm_device *dev, + unsigned int mode, bool notify_rpm) +{ + uint32_t i; + uint32_t start_addr = 0; + int ret = -EINVAL; + + if (mode == MSM_SPM_MODE_DISABLED) { + ret = msm_spm_drv_set_spm_enable(&dev->reg_data, false); + } else if (!msm_spm_drv_set_spm_enable(&dev->reg_data, true)) { + for (i = 0; i < dev->num_modes; i++) { + if ((dev->modes[i].mode == mode) && + (dev->modes[i].notify_rpm == notify_rpm)) { + start_addr = dev->modes[i].start_addr; + break; + } + } + ret = msm_spm_drv_set_low_power_mode(&dev->reg_data, + start_addr); + } + return ret; +} + +static int __init msm_spm_dev_init(struct msm_spm_device *dev, + struct msm_spm_platform_data *data) +{ + int i, ret = -ENOMEM; + uint32_t offset = 0; + + dev->num_modes = data->num_modes; + dev->modes = kmalloc( + sizeof(struct msm_spm_power_modes) * dev->num_modes, + GFP_KERNEL); + + if (!dev->modes) + goto spm_failed_malloc; + + ret = msm_spm_drv_init(&dev->reg_data, data); + + if (ret) + goto spm_failed_init; + + for (i = 0; i < dev->num_modes; i++) { + + ret = msm_spm_drv_write_seq_data(&dev->reg_data, + data->modes[i].cmd, offset); + if (ret < 0) + goto spm_failed_init; + + dev->modes[i].mode = data->modes[i].mode; + dev->modes[i].notify_rpm = data->modes[i].notify_rpm; + dev->modes[i].start_addr = offset; + offset += ret; + } + msm_spm_drv_flush_seq_entry(&dev->reg_data); + return 0; + +spm_failed_init: + kfree(dev->modes); +spm_failed_malloc: + return ret; +} + +int msm_spm_set_low_power_mode(unsigned int mode, bool notify_rpm) +{ + struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device); + return msm_spm_dev_set_low_power_mode(dev, mode, notify_rpm); +} + +int __init msm_spm_init(struct msm_spm_platform_data *data, int nr_devs) +{ + unsigned int cpu; + int ret = 0; + + BUG_ON((nr_devs < num_possible_cpus()) || !data); + + for_each_possible_cpu(cpu) { + struct msm_spm_device *dev = &per_cpu(msm_cpu_spm_device, cpu); + ret = msm_spm_dev_init(dev, &data[cpu]); + if (ret < 0) { + pr_warn("%s():failed CPU:%u ret:%d\n", __func__, + cpu, ret); + break; + } + } + + return ret; +} + +#if defined(CONFIG_MSM_L2_SPM) +static struct msm_spm_device msm_spm_l2_device; + +int msm_spm_l2_set_low_power_mode(unsigned int mode, bool notify_rpm) +{ + return msm_spm_dev_set_low_power_mode( + &msm_spm_l2_device, mode, notify_rpm); +} + +int __init msm_spm_l2_init(struct msm_spm_platform_data *data) +{ + return msm_spm_dev_init(&msm_spm_l2_device, data); +} +#endif diff --git a/arch/arm/mach-msm/spm_driver.h b/arch/arm/mach-msm/spm_driver.h new file mode 100644 index 00000000000..3719847cd75 --- /dev/null +++ b/arch/arm/mach-msm/spm_driver.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __ARCH_ARM_MACH_MSM_SPM_DEVICES_H +#define __ARCH_ARM_MACH_MSM_SPM_DEVICES_H + +#include "spm.h" + +struct msm_spm_driver_data { + void __iomem *reg_base_addr; + uint32_t vctl_timeout_us; + uint32_t reg_shadow[MSM_SPM_REG_NR_INITIALIZE]; + uint32_t *reg_seq_entry_shadow; +}; + +int msm_spm_drv_init(struct msm_spm_driver_data *dev, + struct msm_spm_platform_data *data); +int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, + uint32_t addr); +int msm_spm_drv_set_vdd(struct msm_spm_driver_data *dev, + unsigned int vlevel); +int msm_spm_drv_write_seq_data(struct msm_spm_driver_data *dev, + uint8_t *cmd, uint32_t offset); +void msm_spm_drv_flush_seq_entry(struct msm_spm_driver_data *dev); +int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev, + bool enable); + +#endif diff --git a/arch/arm/mach-msm/subsystem-fatal-8x60.c b/arch/arm/mach-msm/subsystem-fatal-8x60.c new file mode 100644 index 00000000000..6bb6a6d3f41 --- /dev/null +++ b/arch/arm/mach-msm/subsystem-fatal-8x60.c @@ -0,0 +1,416 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "smd_private.h" +#include "modem_notifier.h" +#include "ramdump.h" + +#define MODEM_HWIO_MSS_RESET_ADDR 0x00902C48 +#define SCM_Q6_NMI_CMD 0x1 +#define MODULE_NAME "subsystem_fatal_8x60" +#define Q6SS_SOFT_INTR_WAKEUP 0x288A001C +#define MODEM_WDOG_ENABLE 0x10020008 +#define Q6SS_WDOG_ENABLE 0x28882024 + +#define SUBSYS_FATAL_DEBUG + +#if defined(SUBSYS_FATAL_DEBUG) +static void debug_crash_modem_fn(struct work_struct *); +static int reset_modem; + +static DECLARE_DELAYED_WORK(debug_crash_modem_work, + debug_crash_modem_fn); + +module_param(reset_modem, int, 0644); +#endif + +static void do_soc_restart(void); + +/* Subsystem restart: QDSP6 data, functions */ +static void q6_fatal_fn(struct work_struct *); +static DECLARE_WORK(q6_fatal_work, q6_fatal_fn); +static void *q6_ramdump_dev, *modem_ramdump_dev; + +static void q6_fatal_fn(struct work_struct *work) +{ + pr_err("%s: Watchdog bite received from Q6!\n", MODULE_NAME); + subsystem_restart("lpass"); +} + +static void send_q6_nmi(void) +{ + /* Send NMI to QDSP6 via an SCM call. */ + uint32_t cmd = 0x1; + void __iomem *q6_wakeup_intr; + + scm_call(SCM_SVC_UTIL, SCM_Q6_NMI_CMD, + &cmd, sizeof(cmd), NULL, 0); + + /* Wakeup the Q6 */ + q6_wakeup_intr = ioremap_nocache(Q6SS_SOFT_INTR_WAKEUP, 8); + writel_relaxed(0x2000, q6_wakeup_intr); + iounmap(q6_wakeup_intr); + mb(); + + /* Q6 requires atleast 5ms to dump caches etc.*/ + usleep(5000); + + pr_info("subsystem-fatal-8x60: Q6 NMI was sent.\n"); +} + +int subsys_q6_shutdown(const struct subsys_data *crashed_subsys) +{ + void __iomem *q6_wdog_addr = + ioremap_nocache(Q6SS_WDOG_ENABLE, 8); + + send_q6_nmi(); + writel_relaxed(0x0, q6_wdog_addr); + /* The write needs to go through before the q6 is shutdown. */ + mb(); + iounmap(q6_wdog_addr); + + pil_force_shutdown("q6"); + disable_irq_nosync(LPASS_Q6SS_WDOG_EXPIRED); + + if (get_restart_level() == RESET_SUBSYS_MIXED) + smsm_reset_modem(SMSM_RESET); + + return 0; +} + +int subsys_q6_powerup(const struct subsys_data *crashed_subsys) +{ + int ret = pil_force_boot("q6"); + enable_irq(LPASS_Q6SS_WDOG_EXPIRED); + return ret; +} + +/* FIXME: Get address, size from PIL */ +static struct ramdump_segment q6_segments[] = { {0x46700000, 0x47F00000 - + 0x46700000}, {0x28400000, 0x12800} }; +static int subsys_q6_ramdump(int enable, + const struct subsys_data *crashed_subsys) +{ + if (enable) + return do_ramdump(q6_ramdump_dev, q6_segments, + ARRAY_SIZE(q6_segments)); + else + return 0; +} + +void subsys_q6_crash_shutdown(const struct subsys_data *crashed_subsys) +{ + send_q6_nmi(); +} + +/* Subsystem restart: Modem data, functions */ +static void modem_fatal_fn(struct work_struct *); +static void modem_unlock_timeout(struct work_struct *work); +static int modem_notif_handler(struct notifier_block *this, + unsigned long code, + void *_cmd); +static DECLARE_WORK(modem_fatal_work, modem_fatal_fn); +static DECLARE_DELAYED_WORK(modem_unlock_timeout_work, + modem_unlock_timeout); + +static struct notifier_block modem_notif_nb = { + .notifier_call = modem_notif_handler, +}; + +static void modem_unlock_timeout(struct work_struct *work) +{ + void __iomem *hwio_modem_reset_addr = + ioremap_nocache(MODEM_HWIO_MSS_RESET_ADDR, 8); + pr_crit("%s: Timeout waiting for modem to unlock.\n", MODULE_NAME); + + /* Set MSS_MODEM_RESET to 0x0 since the unlock didn't work */ + writel_relaxed(0x0, hwio_modem_reset_addr); + /* Write needs to go through before the modem is restarted. */ + mb(); + iounmap(hwio_modem_reset_addr); + + subsystem_restart("modem"); +} + +static void modem_fatal_fn(struct work_struct *work) +{ + uint32_t modem_state; + uint32_t panic_smsm_states = SMSM_RESET | SMSM_SYSTEM_DOWNLOAD; + uint32_t reset_smsm_states = SMSM_SYSTEM_REBOOT_USR | + SMSM_SYSTEM_PWRDWN_USR; + + pr_err("%s: Watchdog bite received from modem!\n", MODULE_NAME); + + modem_state = smsm_get_state(SMSM_MODEM_STATE); + pr_err("%s: Modem SMSM state = 0x%x!", MODULE_NAME, modem_state); + + if (modem_state == 0 || modem_state & panic_smsm_states) { + + subsystem_restart("modem"); + + } else if (modem_state & reset_smsm_states) { + + pr_err("%s: User-invoked system reset/powerdown.", + MODULE_NAME); + do_soc_restart(); + + } else { + + int ret; + void *hwio_modem_reset_addr = + ioremap_nocache(MODEM_HWIO_MSS_RESET_ADDR, 8); + + pr_err("%s: Modem AHB locked up.\n", MODULE_NAME); + pr_err("%s: Trying to free up modem!\n", MODULE_NAME); + + writel(0x3, hwio_modem_reset_addr); + + /* If we are still alive after 6 seconds (allowing for + * the 5-second-delayed-panic-reboot), modem is either + * still wedged or SMSM didn't come through. Force panic + * in that case. + */ + ret = schedule_delayed_work(&modem_unlock_timeout_work, + msecs_to_jiffies(6000)); + + iounmap(hwio_modem_reset_addr); + } +} + +static int modem_notif_handler(struct notifier_block *this, + unsigned long code, + void *_cmd) +{ + if (code == MODEM_NOTIFIER_START_RESET) { + + pr_err("%s: Modem error fatal'ed.", MODULE_NAME); + subsystem_restart("modem"); + } + return NOTIFY_DONE; +} + +static int subsys_modem_shutdown(const struct subsys_data *crashed_subsys) +{ + void __iomem *modem_wdog_addr; + int smsm_notif_unregistered = 0; + + /* If the modem didn't already crash, setting SMSM_RESET + * here will help flush caches etc. Unregister for SMSM + * notifications to prevent unnecessary secondary calls to + * subsystem_restart. + */ + if (!(smsm_get_state(SMSM_MODEM_STATE) & SMSM_RESET)) { + modem_unregister_notifier(&modem_notif_nb); + smsm_notif_unregistered = 1; + smsm_reset_modem(SMSM_RESET); + } + + /* Disable the modem watchdog to allow clean modem bootup */ + modem_wdog_addr = ioremap_nocache(MODEM_WDOG_ENABLE, 8); + writel_relaxed(0x0, modem_wdog_addr); + + /* + * The write above needs to go through before the modem is + * powered up again (subsystem restart). + */ + mb(); + iounmap(modem_wdog_addr); + + /* Wait for 5ms to allow the modem to clean up caches etc. */ + usleep(5000); + pil_force_shutdown("modem"); + disable_irq_nosync(MARM_WDOG_EXPIRED); + + /* Re-register for SMSM notifications if necessary */ + if (smsm_notif_unregistered) + modem_register_notifier(&modem_notif_nb); + + + return 0; +} + +static int subsys_modem_powerup(const struct subsys_data *crashed_subsys) +{ + int ret; + + ret = pil_force_boot("modem"); + enable_irq(MARM_WDOG_EXPIRED); + + return ret; +} + +/* FIXME: Get address, size from PIL */ +static struct ramdump_segment modem_segments[] = { + {0x42F00000, 0x46000000 - 0x42F00000} }; + +static int subsys_modem_ramdump(int enable, + const struct subsys_data *crashed_subsys) +{ + if (enable) + return do_ramdump(modem_ramdump_dev, modem_segments, + ARRAY_SIZE(modem_segments)); + else + return 0; +} + +static void subsys_modem_crash_shutdown( + const struct subsys_data *crashed_subsys) +{ + /* If modem hasn't already crashed, send SMSM_RESET. */ + if (!(smsm_get_state(SMSM_MODEM_STATE) & SMSM_RESET)) { + modem_unregister_notifier(&modem_notif_nb); + smsm_reset_modem(SMSM_RESET); + } + + /* Wait for 5ms to allow the modem to clean up caches etc. */ + usleep(5000); +} + +/* Non-subsystem-specific functions */ +static void do_soc_restart(void) +{ + pr_err("%s: Rebooting SoC..\n", MODULE_NAME); + kernel_restart(NULL); +} + +static irqreturn_t subsys_wdog_bite_irq(int irq, void *dev_id) +{ + int ret; + + switch (irq) { + + case MARM_WDOG_EXPIRED: + ret = schedule_work(&modem_fatal_work); + disable_irq_nosync(MARM_WDOG_EXPIRED); + break; + + case LPASS_Q6SS_WDOG_EXPIRED: + ret = schedule_work(&q6_fatal_work); + disable_irq_nosync(LPASS_Q6SS_WDOG_EXPIRED); + break; + + default: + pr_err("%s: %s: Unknown IRQ!\n", MODULE_NAME, __func__); + } + + return IRQ_HANDLED; +} + +static struct subsys_data subsys_8x60_q6 = { + .name = "lpass", + .shutdown = subsys_q6_shutdown, + .powerup = subsys_q6_powerup, + .ramdump = subsys_q6_ramdump, + .crash_shutdown = subsys_q6_crash_shutdown +}; + +static struct subsys_data subsys_8x60_modem = { + .name = "modem", + .shutdown = subsys_modem_shutdown, + .powerup = subsys_modem_powerup, + .ramdump = subsys_modem_ramdump, + .crash_shutdown = subsys_modem_crash_shutdown +}; + +static int __init subsystem_restart_8x60_init(void) +{ + ssr_register_subsystem(&subsys_8x60_modem); + ssr_register_subsystem(&subsys_8x60_q6); + + return 0; +} + +static int __init subsystem_fatal_init(void) +{ + int ret; + + /* Need to listen for SMSM_RESET always */ + modem_register_notifier(&modem_notif_nb); + +#if defined(SUBSYS_FATAL_DEBUG) + schedule_delayed_work(&debug_crash_modem_work, msecs_to_jiffies(5000)); +#endif + + ret = request_irq(MARM_WDOG_EXPIRED, subsys_wdog_bite_irq, + IRQF_TRIGGER_RISING, "modem_wdog", NULL); + + if (ret < 0) { + pr_err("%s: Unable to request MARM_WDOG_EXPIRED irq.", + __func__); + goto out; + } + + ret = request_irq(LPASS_Q6SS_WDOG_EXPIRED, subsys_wdog_bite_irq, + IRQF_TRIGGER_RISING, "q6_wdog", NULL); + + if (ret < 0) { + pr_err("%s: Unable to request LPASS_Q6SS_WDOG_EXPIRED irq.", + __func__); + goto out; + } + + q6_ramdump_dev = create_ramdump_device("lpass"); + + if (!q6_ramdump_dev) { + ret = -ENOMEM; + goto out; + } + + modem_ramdump_dev = create_ramdump_device("modem"); + + if (!modem_ramdump_dev) { + ret = -ENOMEM; + goto out; + } + + ret = subsystem_restart_8x60_init(); +out: + return ret; +} + +static void __exit subsystem_fatal_exit(void) +{ + free_irq(MARM_WDOG_EXPIRED, NULL); + free_irq(LPASS_Q6SS_WDOG_EXPIRED, NULL); +} + +#ifdef SUBSYS_FATAL_DEBUG +static void debug_crash_modem_fn(struct work_struct *work) +{ + if (reset_modem == 1) + smsm_reset_modem(SMSM_RESET); + else if (reset_modem == 2) + subsystem_restart("lpass"); + + reset_modem = 0; + schedule_delayed_work(&debug_crash_modem_work, msecs_to_jiffies(1000)); +} +#endif + +module_init(subsystem_fatal_init); +module_exit(subsystem_fatal_exit); diff --git a/arch/arm/mach-msm/subsystem_map.c b/arch/arm/mach-msm/subsystem_map.c new file mode 100644 index 00000000000..32c03e1ea39 --- /dev/null +++ b/arch/arm/mach-msm/subsystem_map.c @@ -0,0 +1,521 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * TODO Have this passed in from the board file or convert + * to whatever API upstream comes up with + * + * Listed in decending order as large page sizes should be tried before + * smaller sizes + */ +static unsigned int iommu_page_sizes[4] = {SZ_16M, SZ_1M, SZ_64K, SZ_4K}; + +struct msm_buffer_node { + struct rb_node rb_node_all_buffer; + struct rb_node rb_node_paddr; + struct msm_mapped_buffer *buf; + unsigned long length; + unsigned int *subsystems; + unsigned int nsubsys; + unsigned int pg_size; + unsigned int phys; +}; + +static struct rb_root buffer_root; +static struct rb_root phys_root; +DEFINE_MUTEX(msm_buffer_mutex); + +static struct msm_buffer_node *find_buffer(void *key) +{ + struct rb_root *root = &buffer_root; + struct rb_node *p = root->rb_node; + + mutex_lock(&msm_buffer_mutex); + + while (p) { + struct msm_buffer_node *node; + + node = rb_entry(p, struct msm_buffer_node, rb_node_all_buffer); + if (node->buf->vaddr) { + if (key < node->buf->vaddr) + p = p->rb_left; + else if (key > node->buf->vaddr) + p = p->rb_right; + else { + mutex_unlock(&msm_buffer_mutex); + return node; + } + } else { + if (key < (void *)node->buf) + p = p->rb_left; + else if (key > (void *)node->buf) + p = p->rb_right; + else { + mutex_unlock(&msm_buffer_mutex); + return node; + } + } + } + mutex_unlock(&msm_buffer_mutex); + return NULL; +} + +static struct msm_buffer_node *find_buffer_phys(unsigned int phys) +{ + struct rb_root *root = &phys_root; + struct rb_node *p = root->rb_node; + + mutex_lock(&msm_buffer_mutex); + + while (p) { + struct msm_buffer_node *node; + + node = rb_entry(p, struct msm_buffer_node, rb_node_paddr); + if (phys < node->phys) + p = p->rb_left; + else if (phys > node->phys) + p = p->rb_right; + else { + mutex_unlock(&msm_buffer_mutex); + return node; + } + } + mutex_unlock(&msm_buffer_mutex); + return NULL; + +} + +static int add_buffer(struct msm_buffer_node *node) +{ + struct rb_root *root = &buffer_root; + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + void *key; + + if (node->buf->vaddr) + key = node->buf->vaddr; + else + key = node->buf; + + mutex_lock(&msm_buffer_mutex); + while (*p) { + struct msm_buffer_node *tmp; + parent = *p; + + tmp = rb_entry(parent, struct msm_buffer_node, + rb_node_all_buffer); + + if (tmp->buf->vaddr) { + if (key < tmp->buf->vaddr) + p = &(*p)->rb_left; + else if (key > tmp->buf->vaddr) + p = &(*p)->rb_right; + else { + WARN(1, "tried to add buffer twice! buf = %p" + " vaddr = %p iova = %p", tmp->buf, + tmp->buf->vaddr, + tmp->buf->iova); + mutex_unlock(&msm_buffer_mutex); + return -EINVAL; + + } + } else { + if (key < (void *)tmp->buf) + p = &(*p)->rb_left; + else if (key > (void *)tmp->buf) + p = &(*p)->rb_right; + else { + WARN(1, "tried to add buffer twice! buf = %p" + " vaddr = %p iova = %p", tmp->buf, + tmp->buf->vaddr, + tmp->buf->iova); + mutex_unlock(&msm_buffer_mutex); + return -EINVAL; + } + } + } + rb_link_node(&node->rb_node_all_buffer, parent, p); + rb_insert_color(&node->rb_node_all_buffer, root); + mutex_unlock(&msm_buffer_mutex); + return 0; +} + +static int add_buffer_phys(struct msm_buffer_node *node) +{ + struct rb_root *root = &phys_root; + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + + mutex_lock(&msm_buffer_mutex); + while (*p) { + struct msm_buffer_node *tmp; + parent = *p; + + tmp = rb_entry(parent, struct msm_buffer_node, rb_node_paddr); + + if (node->phys < tmp->phys) + p = &(*p)->rb_left; + else if (node->phys > tmp->phys) + p = &(*p)->rb_right; + else { + WARN(1, "tried to add buffer twice! buf = %p" + " vaddr = %p iova = %p", tmp->buf, + tmp->buf->vaddr, + tmp->buf->iova); + mutex_unlock(&msm_buffer_mutex); + return -EINVAL; + + } + } + rb_link_node(&node->rb_node_paddr, parent, p); + rb_insert_color(&node->rb_node_paddr, root); + mutex_unlock(&msm_buffer_mutex); + return 0; +} + +static int remove_buffer(struct msm_buffer_node *victim_node) +{ + struct rb_root *root = &buffer_root; + + if (!victim_node) + return -EINVAL; + + mutex_lock(&msm_buffer_mutex); + rb_erase(&victim_node->rb_node_all_buffer, root); + mutex_unlock(&msm_buffer_mutex); + return 0; +} + +static int remove_buffer_phys(struct msm_buffer_node *victim_node) +{ + struct rb_root *root = &phys_root; + + if (!victim_node) + return -EINVAL; + + mutex_lock(&msm_buffer_mutex); + rb_erase(&victim_node->rb_node_paddr, root); + mutex_unlock(&msm_buffer_mutex); + return 0; +} + +static unsigned long allocate_iova_address(unsigned long size, + int subsys_id, + unsigned long align) +{ + struct mem_pool *pool = msm_subsystem_get_pool(subsys_id); + unsigned long iova; + + iova = gen_pool_alloc_aligned(pool->gpool, size, ilog2(align)); + if (iova) + pool->free -= size; + + return iova; +} + +static void free_iova_address(unsigned long iova, + unsigned long size, + int subsys_id) +{ + struct mem_pool *pool = msm_subsystem_get_pool(subsys_id); + + pool->free += size; + gen_pool_free(pool->gpool, iova, size); +} + +static int subsys_validate(int subsys_id) +{ + struct mem_pool *pool; + struct iommu_domain *subsys_domain; + + if (!msm_subsystem_check_id(subsys_id)) { + WARN(1, "subsystem id is not valid. Caller should check this."); + return 0; + } + + pool = msm_subsystem_get_pool(subsys_id); + subsys_domain = msm_subsystem_get_domain(subsys_id); + + return subsys_domain && pool && pool->gpool; +} + +phys_addr_t msm_subsystem_check_iova_mapping(int subsys_id, unsigned long iova) +{ + struct iommu_domain *subsys_domain; + + if (!subsys_validate(subsys_id)) + /* + * If the subsystem is not valid, assume a phys = iova + * mapping. Just return the iova in this case. + */ + return iova; + + subsys_domain = msm_subsystem_get_domain(subsys_id); + + return iommu_iova_to_phys(subsys_domain, iova); +} +EXPORT_SYMBOL(msm_subsystem_check_iova_mapping); + +struct msm_mapped_buffer *msm_subsystem_map_buffer(unsigned long phys, + unsigned int length, + unsigned int flags, + int *subsys_ids, + unsigned int nsubsys) +{ + struct msm_mapped_buffer *buf, *err; + struct msm_buffer_node *node; + int i = 0, j = 0, ret; + unsigned long iova_start = 0, temp_phys, temp_va = 0; + unsigned int order = 0, pg_size = 0; + struct iommu_domain *d = NULL; + + if (!((flags & MSM_SUBSYSTEM_MAP_KADDR) || + (flags & MSM_SUBSYSTEM_MAP_IOVA))) { + pr_warn("%s: no mapping flag was specified. The caller" + " should explicitly specify what to map in the" + " flags.\n", __func__); + err = ERR_PTR(-EINVAL); + goto outret; + } + + buf = kzalloc(sizeof(*buf), GFP_ATOMIC); + if (!buf) { + err = ERR_PTR(-ENOMEM); + goto outret; + } + + node = kzalloc(sizeof(*node), GFP_ATOMIC); + if (!node) { + err = ERR_PTR(-ENOMEM); + goto outkfreebuf; + } + + node->phys = phys; + + if (flags & MSM_SUBSYSTEM_MAP_KADDR) { + struct msm_buffer_node *old_buffer; + + old_buffer = find_buffer_phys(phys); + + if (old_buffer) { + WARN(1, "%s: Attempting to map %lx twice in the kernel" + " virtual space. Don't do that!\n", __func__, + phys); + err = ERR_PTR(-EINVAL); + goto outkfreenode; + } + + if (flags & MSM_SUBSYSTEM_MAP_CACHED) + buf->vaddr = ioremap(phys, length); + else if (flags & MSM_SUBSYSTEM_MAP_KADDR) + buf->vaddr = ioremap_nocache(phys, length); + else { + pr_warn("%s: no cachability flag was indicated. Caller" + " must specify a cachability flag.\n", + __func__); + err = ERR_PTR(-EINVAL); + goto outkfreenode; + } + + if (!buf->vaddr) { + pr_err("%s: could not ioremap\n", __func__); + err = ERR_PTR(-EINVAL); + goto outkfreenode; + } + + if (add_buffer_phys(node)) { + err = ERR_PTR(-EINVAL); + goto outiounmap; + } + } + + if ((flags & MSM_SUBSYSTEM_MAP_IOVA) && subsys_ids) { + + pg_size = SZ_4K; + + for (i = 0; i < ARRAY_SIZE(iommu_page_sizes); i++) { + if ((length > iommu_page_sizes[i]) && + IS_ALIGNED(phys, iommu_page_sizes[i])) { + pg_size = iommu_page_sizes[i]; + break; + } + } + + length = round_up(length, pg_size); + + buf->iova = kzalloc(sizeof(unsigned long)*nsubsys, GFP_ATOMIC); + if (!buf->iova) { + err = ERR_PTR(-ENOMEM); + goto outremovephys; + } + + order = get_order(pg_size); + for (i = 0; i < nsubsys; i++) { + if (!subsys_validate(subsys_ids[i])) { + buf->iova[i] = phys; + continue; + } + + d = msm_subsystem_get_domain(subsys_ids[i]); + + iova_start = allocate_iova_address(length, + subsys_ids[i], pg_size); + + if (!iova_start) { + pr_err("%s: could not allocate iova address\n", + __func__); + continue; + } + + temp_phys = phys; + temp_va = iova_start; + for (j = length; j > 0; j -= pg_size, + temp_phys += pg_size, + temp_va += pg_size) { + ret = iommu_map(d, temp_va, temp_phys, + order, 0); + if (ret) { + pr_err("%s: could not map iommu for" + " domain %p, iova %lx," + " phys %lx\n", __func__, d, + temp_va, temp_phys); + err = ERR_PTR(-EINVAL); + goto outdomain; + } + } + buf->iova[i] = iova_start; + } + + } + + node->buf = buf; + node->subsystems = subsys_ids; + node->length = length; + node->pg_size = pg_size; + node->nsubsys = nsubsys; + + if (add_buffer(node)) { + err = ERR_PTR(-EINVAL); + goto outiova; + } + + return buf; + +outiova: + if (flags & MSM_SUBSYSTEM_MAP_IOVA) + iommu_unmap(d, temp_va, order); +outdomain: + if (flags & MSM_SUBSYSTEM_MAP_IOVA) { + for (j -= pg_size, temp_va -= pg_size; + j > 0; temp_va -= pg_size, j -= pg_size) + iommu_unmap(d, temp_va, order); + + for (i--; i >= 0; i--) { + if (!subsys_validate(subsys_ids[i])) + continue; + + temp_va = buf->iova[i]; + for (j = length; j > 0; j -= pg_size, + temp_va += pg_size) + iommu_unmap(d, temp_va, order); + free_iova_address(buf->iova[i], length, subsys_ids[i]); + } + + kfree(buf->iova); + } + +outremovephys: + if (flags & MSM_SUBSYSTEM_MAP_KADDR) + remove_buffer_phys(node); +outiounmap: + if (flags & MSM_SUBSYSTEM_MAP_KADDR) + iounmap(buf->vaddr); +outkfreenode: + kfree(node); +outkfreebuf: + kfree(buf); +outret: + return err; +} +EXPORT_SYMBOL(msm_subsystem_map_buffer); + +int msm_subsystem_unmap_buffer(struct msm_mapped_buffer *buf) +{ + unsigned int order; + struct msm_buffer_node *node; + int i, j, ret; + unsigned long temp_va; + + if (buf->vaddr) + node = find_buffer(buf->vaddr); + else + node = find_buffer(buf); + + if (!node) + goto out; + + if (node->buf != buf) { + pr_err("%s: caller must pass in the same buffer structure" + " returned from map_buffer when freeding\n", __func__); + goto out; + } + + order = get_order(node->pg_size); + + if (buf->iova) { + for (i = 0; i < node->nsubsys; i++) { + struct iommu_domain *subsys_domain; + + if (!subsys_validate(node->subsystems[i])) + continue; + + subsys_domain = msm_subsystem_get_domain( + node->subsystems[i]); + temp_va = buf->iova[i]; + for (j = node->length; j > 0; j -= node->pg_size, + temp_va += node->pg_size) { + ret = iommu_unmap(subsys_domain, temp_va, + order); + WARN(ret, "iommu_unmap returned a non-zero" + " value.\n"); + } + free_iova_address(buf->iova[i], node->length, + node->subsystems[i]); + } + kfree(buf->iova); + + } + + if (buf->vaddr) { + remove_buffer_phys(node); + iounmap(buf->vaddr); + } + + remove_buffer(node); + kfree(node); + kfree(buf); + + return 0; +out: + return -EINVAL; +} +EXPORT_SYMBOL(msm_subsystem_unmap_buffer); diff --git a/arch/arm/mach-msm/subsystem_notif.c b/arch/arm/mach-msm/subsystem_notif.c new file mode 100644 index 00000000000..f7db54c1b39 --- /dev/null +++ b/arch/arm/mach-msm/subsystem_notif.c @@ -0,0 +1,222 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * Subsystem Notifier -- Provides notifications + * of subsys events. + * + * Use subsys_notif_register_notifier to register for notifications + * and subsys_notif_queue_notification to send notifications. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct subsys_notif_info { + char name[50]; + struct srcu_notifier_head subsys_notif_rcvr_list; + struct list_head list; +}; + +static LIST_HEAD(subsystem_list); +static DEFINE_MUTEX(notif_lock); +static DEFINE_MUTEX(notif_add_lock); + +#if defined(SUBSYS_RESTART_DEBUG) +static void subsys_notif_reg_test_notifier(const char *); +#endif + +static struct subsys_notif_info *_notif_find_subsys(const char *subsys_name) +{ + struct subsys_notif_info *subsys; + + mutex_lock(¬if_lock); + list_for_each_entry(subsys, &subsystem_list, list) + if (!strncmp(subsys->name, subsys_name, + ARRAY_SIZE(subsys->name))) { + mutex_unlock(¬if_lock); + return subsys; + } + mutex_unlock(¬if_lock); + + return NULL; +} + +void *subsys_notif_register_notifier( + const char *subsys_name, struct notifier_block *nb) +{ + int ret; + struct subsys_notif_info *subsys = _notif_find_subsys(subsys_name); + + if (!subsys) { + + /* Possible first time reference to this subsystem. Add it. */ + subsys = (struct subsys_notif_info *) + subsys_notif_add_subsys(subsys_name); + + if (!subsys) + return ERR_PTR(-EINVAL); + } + + ret = srcu_notifier_chain_register( + &subsys->subsys_notif_rcvr_list, nb); + + if (ret < 0) + return ERR_PTR(ret); + + return subsys; +} +EXPORT_SYMBOL(subsys_notif_register_notifier); + +int subsys_notif_unregister_notifier(void *subsys_handle, + struct notifier_block *nb) +{ + int ret; + struct subsys_notif_info *subsys = + (struct subsys_notif_info *)subsys_handle; + + if (!subsys) + return -EINVAL; + + ret = srcu_notifier_chain_unregister( + &subsys->subsys_notif_rcvr_list, nb); + + return ret; +} +EXPORT_SYMBOL(subsys_notif_unregister_notifier); + +void *subsys_notif_add_subsys(const char *subsys_name) +{ + struct subsys_notif_info *subsys = NULL; + + if (!subsys_name) + goto done; + + mutex_lock(¬if_add_lock); + + subsys = _notif_find_subsys(subsys_name); + + if (subsys) { + mutex_unlock(¬if_add_lock); + goto done; + } + + subsys = kmalloc(sizeof(struct subsys_notif_info), GFP_KERNEL); + + if (!subsys) { + mutex_unlock(¬if_add_lock); + return ERR_PTR(-EINVAL); + } + + strlcpy(subsys->name, subsys_name, ARRAY_SIZE(subsys->name)); + + srcu_init_notifier_head(&subsys->subsys_notif_rcvr_list); + + INIT_LIST_HEAD(&subsys->list); + + mutex_lock(¬if_lock); + list_add_tail(&subsys->list, &subsystem_list); + mutex_unlock(¬if_lock); + + #if defined(SUBSYS_RESTART_DEBUG) + subsys_notif_reg_test_notifier(subsys->name); + #endif + + mutex_unlock(¬if_add_lock); + +done: + return subsys; +} +EXPORT_SYMBOL(subsys_notif_add_subsys); + +int subsys_notif_queue_notification(void *subsys_handle, + enum subsys_notif_type notif_type) +{ + int ret = 0; + struct subsys_notif_info *subsys = + (struct subsys_notif_info *) subsys_handle; + + if (!subsys) + return -EINVAL; + + if (notif_type < 0 || notif_type >= SUBSYS_NOTIF_TYPE_COUNT) + return -EINVAL; + + ret = srcu_notifier_call_chain( + &subsys->subsys_notif_rcvr_list, notif_type, + (void *)subsys); + + return ret; +} +EXPORT_SYMBOL(subsys_notif_queue_notification); + +#if defined(SUBSYS_RESTART_DEBUG) +static const char *notif_to_string(enum subsys_notif_type notif_type) +{ + switch (notif_type) { + + case SUBSYS_BEFORE_SHUTDOWN: + return __stringify(SUBSYS_BEFORE_SHUTDOWN); + + case SUBSYS_AFTER_SHUTDOWN: + return __stringify(SUBSYS_AFTER_SHUTDOWN); + + case SUBSYS_BEFORE_POWERUP: + return __stringify(SUBSYS_BEFORE_POWERUP); + + case SUBSYS_AFTER_POWERUP: + return __stringify(SUBSYS_AFTER_POWERUP); + + default: + return "unknown"; + } +} + +static int subsys_notifier_test_call(struct notifier_block *this, + unsigned long code, + void *data) +{ + switch (code) { + + default: + printk(KERN_WARNING "%s: Notification %s from subsystem %p\n", + __func__, notif_to_string(code), data); + break; + + } + + return NOTIFY_DONE; +} + +static struct notifier_block nb = { + .notifier_call = subsys_notifier_test_call, +}; + +static void subsys_notif_reg_test_notifier(const char *subsys_name) +{ + void *handle = subsys_notif_register_notifier(subsys_name, &nb); + printk(KERN_WARNING "%s: Registered test notifier, handle=%p", + __func__, handle); +} +#endif + +MODULE_DESCRIPTION("Subsystem Restart Notifier"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/subsystem_restart.c b/arch/arm/mach-msm/subsystem_restart.c new file mode 100644 index 00000000000..002c9f8ba80 --- /dev/null +++ b/arch/arm/mach-msm/subsystem_restart.c @@ -0,0 +1,523 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "smd_private.h" + +#if defined(SUBSYS_RESTART_DEBUG) +#define dprintk(msg...) printk(msg) +#else +#define dprintk(msg...) +#endif + +struct subsys_soc_restart_order { + const char * const *subsystem_list; + int count; + + struct mutex shutdown_lock; + struct mutex powerup_lock; + struct subsys_data *subsys_ptrs[]; +}; + +struct restart_thread_data { + struct subsys_data *subsys; + int coupled; +}; + +static int restart_level; +static int enable_ramdumps; + +static LIST_HEAD(subsystem_list); +static DEFINE_MUTEX(subsystem_list_lock); +static DEFINE_MUTEX(soc_order_reg_lock); + +/* SOC specific restart orders go here */ + +#define DEFINE_SINGLE_RESTART_ORDER(name, order) \ + static struct subsys_soc_restart_order __##name = { \ + .subsystem_list = order, \ + .count = ARRAY_SIZE(order), \ + .subsys_ptrs = {[ARRAY_SIZE(order)] = NULL} \ + }; \ + static struct subsys_soc_restart_order *name[] = { \ + &__##name, \ + } + +/* MSM 8x60 restart ordering info */ +static const char * const _order_8x60_all[] = { + "external_modem", "modem", "lpass" +}; +DEFINE_SINGLE_RESTART_ORDER(orders_8x60_all, _order_8x60_all); + +static const char * const _order_8x60_modems[] = {"external_modem", "modem"}; +DEFINE_SINGLE_RESTART_ORDER(orders_8x60_modems, _order_8x60_modems); + +/* MSM 8960 restart ordering info */ +static const char * const order_8960[] = {"modem", "lpass"}; + +static struct subsys_soc_restart_order restart_orders_8960_one = { + .subsystem_list = order_8960, + .count = ARRAY_SIZE(order_8960), + .subsys_ptrs = {[ARRAY_SIZE(order_8960)] = NULL} + }; + +static struct subsys_soc_restart_order *restart_orders_8960[] = { + &restart_orders_8960_one, +}; + +/* These will be assigned to one of the sets above after + * runtime SoC identification. + */ +static struct subsys_soc_restart_order **restart_orders; +static int n_restart_orders; + +module_param(enable_ramdumps, int, S_IRUGO | S_IWUSR); + +static struct subsys_soc_restart_order *_update_restart_order( + struct subsys_data *subsys); + +int get_restart_level() +{ + return restart_level; +} +EXPORT_SYMBOL(get_restart_level); + +static void restart_level_changed(void) +{ + struct subsys_data *subsys; + + if (cpu_is_msm8x60() && restart_level == RESET_SUBSYS_COUPLED) { + restart_orders = orders_8x60_all; + n_restart_orders = ARRAY_SIZE(orders_8x60_all); + } + + if (cpu_is_msm8x60() && restart_level == RESET_SUBSYS_MIXED) { + restart_orders = orders_8x60_modems; + n_restart_orders = ARRAY_SIZE(orders_8x60_modems); + } + + mutex_lock(&subsystem_list_lock); + list_for_each_entry(subsys, &subsystem_list, list) + subsys->restart_order = _update_restart_order(subsys); + mutex_unlock(&subsystem_list_lock); +} + +static int restart_level_set(const char *val, struct kernel_param *kp) +{ + int ret; + int old_val = restart_level; + + ret = param_set_int(val, kp); + if (ret) + return ret; + + switch (restart_level) { + + case RESET_SOC: + case RESET_SUBSYS_COUPLED: + case RESET_SUBSYS_INDEPENDENT: + pr_info("Subsystem Restart: Phase %d behavior activated.\n", + restart_level); + break; + + case RESET_SUBSYS_MIXED: + pr_info("Subsystem Restart: Phase 2+ behavior activated.\n"); + break; + + default: + restart_level = old_val; + return -EINVAL; + break; + + } + + if (restart_level != old_val) + restart_level_changed(); + + return 0; +} + +module_param_call(restart_level, restart_level_set, param_get_int, + &restart_level, 0644); + +static struct subsys_data *_find_subsystem(const char *subsys_name) +{ + struct subsys_data *subsys; + + mutex_lock(&subsystem_list_lock); + list_for_each_entry(subsys, &subsystem_list, list) + if (!strncmp(subsys->name, subsys_name, + SUBSYS_NAME_MAX_LENGTH)) { + mutex_unlock(&subsystem_list_lock); + return subsys; + } + mutex_unlock(&subsystem_list_lock); + + return NULL; +} + +static struct subsys_soc_restart_order *_update_restart_order( + struct subsys_data *subsys) +{ + int i, j; + + if (!subsys) + return NULL; + + if (!subsys->name) + return NULL; + + mutex_lock(&soc_order_reg_lock); + for (j = 0; j < n_restart_orders; j++) { + for (i = 0; i < restart_orders[j]->count; i++) + if (!strncmp(restart_orders[j]->subsystem_list[i], + subsys->name, SUBSYS_NAME_MAX_LENGTH)) { + + restart_orders[j]->subsys_ptrs[i] = + subsys; + mutex_unlock(&soc_order_reg_lock); + return restart_orders[j]; + } + } + + mutex_unlock(&soc_order_reg_lock); + + return NULL; +} + +static void _send_notification_to_order(struct subsys_data + **restart_list, int count, + enum subsys_notif_type notif_type) +{ + int i; + + for (i = 0; i < count; i++) + if (restart_list[i]) + subsys_notif_queue_notification( + restart_list[i]->notif_handle, notif_type); +} + +static int subsystem_restart_thread(void *data) +{ + struct restart_thread_data *r_work = data; + struct subsys_data **restart_list; + struct subsys_data *subsys = r_work->subsys; + struct subsys_soc_restart_order *soc_restart_order = NULL; + + struct mutex *powerup_lock; + struct mutex *shutdown_lock; + + int i; + int restart_list_count = 0; + + if (r_work->coupled) + soc_restart_order = subsys->restart_order; + + /* It's OK to not take the registration lock at this point. + * This is because the subsystem list inside the relevant + * restart order is not being traversed. + */ + if (!soc_restart_order) { + restart_list = subsys->single_restart_list; + restart_list_count = 1; + powerup_lock = &subsys->powerup_lock; + shutdown_lock = &subsys->shutdown_lock; + } else { + restart_list = soc_restart_order->subsys_ptrs; + restart_list_count = soc_restart_order->count; + powerup_lock = &soc_restart_order->powerup_lock; + shutdown_lock = &soc_restart_order->shutdown_lock; + } + + dprintk("%s[%p]: Attempting to get shutdown lock!\n", __func__, + current); + + /* Try to acquire shutdown_lock. If this fails, these subsystems are + * already being restarted - return. + */ + if (!mutex_trylock(shutdown_lock)) { + kfree(data); + do_exit(0); + } + + dprintk("%s[%p]: Attempting to get powerup lock!\n", __func__, + current); + + /* Now that we've acquired the shutdown lock, either we're the first to + * restart these subsystems or some other thread is doing the powerup + * sequence for these subsystems. In the latter case, panic and bail + * out, since a subsystem died in its powerup sequence. + */ + if (!mutex_trylock(powerup_lock)) + panic("%s: Subsystem died during powerup!", __func__); + + /* Now it is necessary to take the registration lock. This is because + * the subsystem list in the SoC restart order will be traversed + * and it shouldn't be changed until _this_ restart sequence completes. + */ + mutex_lock(&soc_order_reg_lock); + + dprintk("%s: Starting restart sequence for %s\n", __func__, + r_work->subsys->name); + + _send_notification_to_order(restart_list, + restart_list_count, + SUBSYS_BEFORE_SHUTDOWN); + + for (i = 0; i < restart_list_count; i++) { + + if (!restart_list[i]) + continue; + + pr_info("subsys-restart: Shutting down %s\n", + restart_list[i]->name); + + if (restart_list[i]->shutdown(subsys) < 0) + panic("%s: Failed to shutdown %s!\n", __func__, + restart_list[i]->name); + } + + _send_notification_to_order(restart_list, restart_list_count, + SUBSYS_AFTER_SHUTDOWN); + + /* Now that we've finished shutting down these subsystems, release the + * shutdown lock. If a subsystem restart request comes in for a + * subsystem in _this_ restart order after the unlock below, and + * before the powerup lock is released, panic and bail out. + */ + mutex_unlock(shutdown_lock); + + /* Collect ram dumps for all subsystems in order here */ + for (i = 0; i < restart_list_count; i++) { + if (!restart_list[i]) + continue; + + if (restart_list[i]->ramdump) + if (restart_list[i]->ramdump(enable_ramdumps, + subsys) < 0) + pr_warn("%s(%s): Ramdump failed.", __func__, + restart_list[i]->name); + } + + _send_notification_to_order(restart_list, + restart_list_count, + SUBSYS_BEFORE_POWERUP); + + for (i = restart_list_count - 1; i >= 0; i--) { + + if (!restart_list[i]) + continue; + + pr_info("subsys-restart: Powering up %s\n", + restart_list[i]->name); + + if (restart_list[i]->powerup(subsys) < 0) + panic("%s: Failed to powerup %s!", __func__, + restart_list[i]->name); + } + + _send_notification_to_order(restart_list, + restart_list_count, + SUBSYS_AFTER_POWERUP); + + pr_info("%s: Restart sequence for %s completed.", __func__, + r_work->subsys->name); + + mutex_unlock(powerup_lock); + + mutex_unlock(&soc_order_reg_lock); + + dprintk("%s: Released powerup lock!\n", __func__); + + kfree(data); + do_exit(0); +} + +int subsystem_restart(const char *subsys_name) +{ + struct subsys_data *subsys; + struct task_struct *tsk; + struct restart_thread_data *data = NULL; + + if (!subsys_name) { + pr_err("%s: Invalid subsystem name.", __func__); + return -EINVAL; + } + + pr_info("Subsystem Restart: Restart sequence requested for %s\n", + subsys_name); + + /* List of subsystems is protected by a lock. New subsystems can + * still come in. + */ + subsys = _find_subsystem(subsys_name); + + if (!subsys) { + pr_warn("%s: Unregistered subsystem %s!", __func__, + subsys_name); + return -EINVAL; + } + + if (restart_level != RESET_SOC) { + data = kzalloc(sizeof(struct restart_thread_data), GFP_KERNEL); + if (!data) { + restart_level = RESET_SOC; + pr_warn("%s: Failed to alloc restart data. Resetting.", + __func__); + } else { + if (restart_level == RESET_SUBSYS_COUPLED || + restart_level == RESET_SUBSYS_MIXED) + data->coupled = 1; + else + data->coupled = 0; + + data->subsys = subsys; + } + } + + switch (restart_level) { + + case RESET_SUBSYS_COUPLED: + case RESET_SUBSYS_MIXED: + case RESET_SUBSYS_INDEPENDENT: + dprintk("%s: Restarting %s [level=%d]!\n", __func__, + subsys_name, restart_level); + + /* Let the kthread handle the actual restarting. Using a + * workqueue will not work since all restart requests are + * serialized and it prevents the short circuiting of + * restart requests for subsystems already in a restart + * sequence. + */ + tsk = kthread_run(subsystem_restart_thread, data, + "subsystem_subsystem_restart_thread"); + if (IS_ERR(tsk)) + panic("%s: Unable to create thread to restart %s", + __func__, subsys->name); + + break; + + case RESET_SOC: + + mutex_lock(&subsystem_list_lock); + list_for_each_entry(subsys, &subsystem_list, list) + if (subsys->crash_shutdown) + subsys->crash_shutdown(subsys); + mutex_unlock(&subsystem_list_lock); + + panic("Resetting the SOC"); + break; + + default: + panic("subsys-restart: Unknown restart level!\n"); + break; + + } + + return 0; +} +EXPORT_SYMBOL(subsystem_restart); + +int ssr_register_subsystem(struct subsys_data *subsys) +{ + if (!subsys) + goto err; + + if (!subsys->name) + goto err; + + if (!subsys->powerup || !subsys->shutdown) + goto err; + + subsys->notif_handle = subsys_notif_add_subsys(subsys->name); + subsys->restart_order = _update_restart_order(subsys); + subsys->single_restart_list[0] = subsys; + + mutex_init(&subsys->shutdown_lock); + mutex_init(&subsys->powerup_lock); + + mutex_lock(&subsystem_list_lock); + list_add(&subsys->list, &subsystem_list); + mutex_unlock(&subsystem_list_lock); + + return 0; + +err: + return -EINVAL; +} +EXPORT_SYMBOL(ssr_register_subsystem); + +static int __init ssr_init_soc_restart_orders(void) +{ + int i; + + if (cpu_is_msm8x60()) { + for (i = 0; i < ARRAY_SIZE(orders_8x60_all); i++) { + mutex_init(&orders_8x60_all[i]->powerup_lock); + mutex_init(&orders_8x60_all[i]->shutdown_lock); + } + + for (i = 0; i < ARRAY_SIZE(orders_8x60_modems); i++) { + mutex_init(&orders_8x60_modems[i]->powerup_lock); + mutex_init(&orders_8x60_modems[i]->shutdown_lock); + } + + restart_orders = orders_8x60_all; + n_restart_orders = ARRAY_SIZE(orders_8x60_all); + } + + if (cpu_is_msm8960()) { + restart_orders = restart_orders_8960; + n_restart_orders = ARRAY_SIZE(restart_orders_8960); + } + + if (restart_orders == NULL || n_restart_orders < 1) { + WARN_ON(1); + return -EINVAL; + } + + return 0; +} + +static int __init subsys_restart_init(void) +{ + int ret = 0; + + restart_level = RESET_SOC; + + ret = ssr_init_soc_restart_orders(); + + return ret; +} + +arch_initcall(subsys_restart_init); + +MODULE_DESCRIPTION("Subsystem Restart Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/timer.c b/arch/arm/mach-msm/timer.c index 63621f152c9..b30f5260de6 100644 --- a/arch/arm/mach-msm/timer.c +++ b/arch/arm/mach-msm/timer.c @@ -1,6 +1,7 @@ /* linux/arch/arm/mach-msm/timer.c * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 @@ -21,18 +22,52 @@ #include #include #include +#include #include #include - #include -#include +#include +#include + +#if defined(CONFIG_MSM_SMD) +#include "smd_private.h" +#endif +#include "timer.h" + +enum { + MSM_TIMER_DEBUG_SYNC = 1U << 0, +}; +static int msm_timer_debug_mask; +module_param_named(debug_mask, msm_timer_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); + +#if defined(CONFIG_ARCH_MSM7X30) || defined(CONFIG_ARCH_MSM8X60) || \ + defined(CONFIG_ARCH_MSM8960) || defined(CONFIG_ARCH_FSM9XXX) || \ + defined(CONFIG_ARCH_APQ8064) +#define MSM_GPT_BASE (MSM_TMR_BASE + 0x4) +#define MSM_DGT_BASE (MSM_TMR_BASE + 0x24) +#else +#define MSM_GPT_BASE MSM_TMR_BASE +#define MSM_DGT_BASE (MSM_TMR_BASE + 0x10) +#endif + +#ifdef CONFIG_MSM7X00A_USE_GP_TIMER + #define DG_TIMER_RATING 100 + #define MSM_GLOBAL_TIMER MSM_CLOCK_GPT +#else + #define DG_TIMER_RATING 300 + #define MSM_GLOBAL_TIMER MSM_CLOCK_DGT +#endif + +#if defined(CONFIG_ARCH_MSM_ARM11) || defined(CONFIG_ARCH_MSM_CORTEX_A5) +#define MSM_DGT_SHIFT (5) +#else +#define MSM_DGT_SHIFT (0) +#endif #define TIMER_MATCH_VAL 0x0000 #define TIMER_COUNT_VAL 0x0004 #define TIMER_ENABLE 0x0008 -#define TIMER_ENABLE_CLR_ON_MATCH_EN 2 -#define TIMER_ENABLE_EN 1 #define TIMER_CLEAR 0x000C #define DGT_CLK_CTL 0x0034 enum { @@ -41,33 +76,72 @@ enum { DGT_CLK_CTL_DIV_3 = 2, DGT_CLK_CTL_DIV_4 = 3, }; -#define CSR_PROTECTION 0x0020 -#define CSR_PROTECTION_EN 1 +#define TIMER_ENABLE_EN 1 +#define TIMER_ENABLE_CLR_ON_MATCH_EN 2 -#define GPT_HZ 32768 +#define LOCAL_TIMER 0 +#define GLOBAL_TIMER 1 -enum timer_location { - LOCAL_TIMER = 0, - GLOBAL_TIMER = 1, -}; +/* + * MSM_TMR_GLOBAL is added to the regbase of a timer to force the memory access + * to come from the CPU0 region. + */ +#ifdef MSM_TMR0_BASE +#define MSM_TMR_GLOBAL (MSM_TMR0_BASE - MSM_TMR_BASE) +#else +#define MSM_TMR_GLOBAL 0 +#endif -#define MSM_GLOBAL_TIMER MSM_CLOCK_DGT +#if defined(CONFIG_MSM_DIRECT_SCLK_ACCESS) +#define MPM_SCLK_COUNT_VAL 0x0024 +#endif + +#define NR_TIMERS ARRAY_SIZE(msm_clocks) -/* TODO: Remove these ifdefs */ #if defined(CONFIG_ARCH_QSD8X50) -#define DGT_HZ (19200000 / 4) /* 19.2 MHz / 4 by default */ -#define MSM_DGT_SHIFT (0) +#define DGT_HZ 4800000 /* Uses TCXO/4 (19.2 MHz / 4) */ #elif defined(CONFIG_ARCH_MSM7X30) -#define DGT_HZ (24576000 / 4) /* 24.576 MHz (LPXO) / 4 by default */ -#define MSM_DGT_SHIFT (0) -#elif defined(CONFIG_ARCH_MSM8X60) || defined(CONFIG_ARCH_MSM8960) -#define DGT_HZ (27000000 / 4) /* 27 MHz (PXO) / 4 by default */ -#define MSM_DGT_SHIFT (0) +#define DGT_HZ 6144000 /* Uses LPXO/4 (24.576 MHz / 4) */ +#elif defined(CONFIG_ARCH_MSM8X60) || defined(CONFIG_ARCH_MSM8960) || \ + defined(CONFIG_ARCH_APQ8064) +/* Uses PXO/4 (24.576 MHz / 4) on V1, (27 MHz / 4) on V2 */ +#define DGT_HZ 6750000 #else -#define DGT_HZ 19200000 /* 19.2 MHz or 600 KHz after shift */ -#define MSM_DGT_SHIFT (5) +#define DGT_HZ 19200000 /* Uses TCXO (19.2 MHz) */ +#endif + +#define GPT_HZ 32768 +#define SCLK_HZ 32768 + +#if defined(CONFIG_MSM_N_WAY_SMSM) +/* Time Master State Bits */ +#define MASTER_BITS_PER_CPU 1 +#define MASTER_TIME_PENDING \ + (0x01UL << (MASTER_BITS_PER_CPU * SMSM_APPS_STATE)) + +/* Time Slave State Bits */ +#define SLAVE_TIME_REQUEST 0x0400 +#define SLAVE_TIME_POLL 0x0800 +#define SLAVE_TIME_INIT 0x1000 #endif +#ifdef CONFIG_SMP +static struct msm_clock *clockevent_to_clock(struct clock_event_device *evt); +#endif +static irqreturn_t msm_timer_interrupt(int irq, void *dev_id); +static cycle_t msm_gpt_read(struct clocksource *cs); +static cycle_t msm_dgt_read(struct clocksource *cs); +static void msm_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt); +static int msm_timer_set_next_event(unsigned long cycles, + struct clock_event_device *evt); + +enum { + MSM_CLOCK_FLAGS_UNSTABLE_COUNT = 1U << 0, + MSM_CLOCK_FLAGS_ODD_MATCH_WRITE = 1U << 1, + MSM_CLOCK_FLAGS_DELAYED_WRITE_POST = 1U << 2, +}; + struct msm_clock { struct clock_event_device clockevent; struct clocksource clocksource; @@ -75,84 +149,36 @@ struct msm_clock { void __iomem *regbase; uint32_t freq; uint32_t shift; - void __iomem *global_counter; - void __iomem *local_counter; + uint32_t flags; + uint32_t write_delay; + uint32_t rollover_offset; + uint32_t index; }; enum { MSM_CLOCK_GPT, MSM_CLOCK_DGT, - NR_TIMERS, }; -static struct msm_clock msm_clocks[]; -static struct clock_event_device *local_clock_event; - -static irqreturn_t msm_timer_interrupt(int irq, void *dev_id) -{ - struct clock_event_device *evt = dev_id; - if (smp_processor_id() != 0) - evt = local_clock_event; - if (evt->event_handler == NULL) - return IRQ_HANDLED; - evt->event_handler(evt); - return IRQ_HANDLED; -} - -static cycle_t msm_read_timer_count(struct clocksource *cs) -{ - struct msm_clock *clk = container_of(cs, struct msm_clock, clocksource); - - /* - * Shift timer count down by a constant due to unreliable lower bits - * on some targets. - */ - return readl(clk->global_counter) >> clk->shift; -} - -static struct msm_clock *clockevent_to_clock(struct clock_event_device *evt) -{ -#ifdef CONFIG_SMP - int i; - for (i = 0; i < NR_TIMERS; i++) - if (evt == &(msm_clocks[i].clockevent)) - return &msm_clocks[i]; - return &msm_clocks[MSM_GLOBAL_TIMER]; -#else - return container_of(evt, struct msm_clock, clockevent); -#endif -} - -static int msm_timer_set_next_event(unsigned long cycles, - struct clock_event_device *evt) -{ - struct msm_clock *clock = clockevent_to_clock(evt); - uint32_t now = readl(clock->local_counter); - uint32_t alarm = now + (cycles << clock->shift); - - writel(alarm, clock->regbase + TIMER_MATCH_VAL); - return 0; -} - -static void msm_timer_set_mode(enum clock_event_mode mode, - struct clock_event_device *evt) -{ - struct msm_clock *clock = clockevent_to_clock(evt); +struct msm_clock_percpu_data { + uint32_t last_set; + uint32_t sleep_offset; + uint32_t alarm_vtime; + uint32_t alarm; + uint32_t non_sleep_offset; + uint32_t in_sync; + cycle_t stopped_tick; + int stopped; + uint32_t last_sync_gpt; + u64 last_sync_jiffies; +}; - switch (mode) { - case CLOCK_EVT_MODE_RESUME: - case CLOCK_EVT_MODE_PERIODIC: - break; - case CLOCK_EVT_MODE_ONESHOT: - writel(TIMER_ENABLE_EN, clock->regbase + TIMER_ENABLE); - break; - case CLOCK_EVT_MODE_UNUSED: - case CLOCK_EVT_MODE_SHUTDOWN: - writel(0, clock->regbase + TIMER_ENABLE); - break; - } -} +struct msm_timer_sync_data_t { + struct msm_clock *clock; + uint32_t timeout; + int exit_sleep; +}; static struct msm_clock msm_clocks[] = { [MSM_CLOCK_GPT] = { @@ -167,96 +193,860 @@ static struct msm_clock msm_clocks[] = { .clocksource = { .name = "gp_timer", .rating = 200, - .read = msm_read_timer_count, + .read = msm_gpt_read, .mask = CLOCKSOURCE_MASK(32), + .shift = 17, .flags = CLOCK_SOURCE_IS_CONTINUOUS, }, .irq = { .name = "gp_timer", - .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_TRIGGER_RISING, + .flags = IRQF_DISABLED | IRQF_TIMER | + IRQF_TRIGGER_RISING, .handler = msm_timer_interrupt, .dev_id = &msm_clocks[0].clockevent, .irq = INT_GP_TIMER_EXP }, + .regbase = MSM_GPT_BASE, .freq = GPT_HZ, + .index = MSM_CLOCK_GPT, + .flags = +#if defined(CONFIG_ARCH_MSM_ARM11) || defined(CONFIG_ARCH_MSM_CORTEX_A5) + MSM_CLOCK_FLAGS_UNSTABLE_COUNT | + MSM_CLOCK_FLAGS_ODD_MATCH_WRITE | + MSM_CLOCK_FLAGS_DELAYED_WRITE_POST | +#endif + 0, + .write_delay = 9, }, [MSM_CLOCK_DGT] = { .clockevent = { .name = "dg_timer", .features = CLOCK_EVT_FEAT_ONESHOT, .shift = 32 + MSM_DGT_SHIFT, - .rating = 300, + .rating = DG_TIMER_RATING, .set_next_event = msm_timer_set_next_event, .set_mode = msm_timer_set_mode, }, .clocksource = { .name = "dg_timer", - .rating = 300, - .read = msm_read_timer_count, - .mask = CLOCKSOURCE_MASK((32 - MSM_DGT_SHIFT)), + .rating = DG_TIMER_RATING, + .read = msm_dgt_read, + .mask = CLOCKSOURCE_MASK((32-MSM_DGT_SHIFT)), + .shift = 24 - MSM_DGT_SHIFT, .flags = CLOCK_SOURCE_IS_CONTINUOUS, }, .irq = { .name = "dg_timer", - .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_TRIGGER_RISING, + .flags = IRQF_DISABLED | IRQF_TIMER | + IRQF_TRIGGER_RISING, .handler = msm_timer_interrupt, .dev_id = &msm_clocks[1].clockevent, .irq = INT_DEBUG_TIMER_EXP }, + .regbase = MSM_DGT_BASE, .freq = DGT_HZ >> MSM_DGT_SHIFT, + .index = MSM_CLOCK_DGT, .shift = MSM_DGT_SHIFT, + .write_delay = 9, } }; +static DEFINE_PER_CPU(struct clock_event_device*, local_clock_event); + +static DEFINE_PER_CPU(struct msm_clock_percpu_data[NR_TIMERS], + msm_clocks_percpu); + +static DEFINE_PER_CPU(struct msm_clock *, msm_active_clock); + +static irqreturn_t msm_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + if (smp_processor_id() != 0) + evt = __get_cpu_var(local_clock_event); + if (evt->event_handler == NULL) + return IRQ_HANDLED; + evt->event_handler(evt); + return IRQ_HANDLED; +} + +static uint32_t msm_read_timer_count(struct msm_clock *clock, int global) +{ + uint32_t t1, t2; + int loop_count = 0; + + if (global) + t1 = __raw_readl(clock->regbase + TIMER_COUNT_VAL + + MSM_TMR_GLOBAL); + else + t1 = __raw_readl(clock->regbase + TIMER_COUNT_VAL); + + if (!(clock->flags & MSM_CLOCK_FLAGS_UNSTABLE_COUNT)) + return t1; + while (1) { + if (global) + t2 = __raw_readl(clock->regbase + TIMER_COUNT_VAL + + MSM_TMR_GLOBAL); + else + t2 = __raw_readl(clock->regbase + TIMER_COUNT_VAL); + if (t1 == t2) + return t1; + if (loop_count++ > 10) { + printk(KERN_ERR "msm_read_timer_count timer %s did not" + "stabilize %u != %u\n", clock->clockevent.name, + t2, t1); + return t2; + } + t1 = t2; + } +} + +static cycle_t msm_gpt_read(struct clocksource *cs) +{ + struct msm_clock *clock = &msm_clocks[MSM_CLOCK_GPT]; + struct msm_clock_percpu_data *clock_state = + &per_cpu(msm_clocks_percpu, 0)[MSM_CLOCK_GPT]; + + if (clock_state->stopped) + return clock_state->stopped_tick; + + return msm_read_timer_count(clock, GLOBAL_TIMER) + + clock_state->sleep_offset; +} + +static cycle_t msm_dgt_read(struct clocksource *cs) +{ + struct msm_clock *clock = &msm_clocks[MSM_CLOCK_DGT]; + struct msm_clock_percpu_data *clock_state = + &per_cpu(msm_clocks_percpu, 0)[MSM_CLOCK_DGT]; + + if (clock_state->stopped) + return clock_state->stopped_tick >> MSM_DGT_SHIFT; + + return (msm_read_timer_count(clock, GLOBAL_TIMER) + + clock_state->sleep_offset) >> MSM_DGT_SHIFT; +} + +#ifdef CONFIG_SMP +static struct msm_clock *clockevent_to_clock(struct clock_event_device *evt) +{ + int i; + for (i = 0; i < NR_TIMERS; i++) + if (evt == &(msm_clocks[i].clockevent)) + return &msm_clocks[i]; + return &msm_clocks[MSM_GLOBAL_TIMER]; +} +#endif + +static int msm_timer_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + int i; + struct msm_clock *clock; + struct msm_clock_percpu_data *clock_state; + uint32_t now; + uint32_t alarm; + int late; + +#ifdef CONFIG_SMP + clock = clockevent_to_clock(evt); +#else + clock = container_of(evt, struct msm_clock, clockevent); +#endif + clock_state = &__get_cpu_var(msm_clocks_percpu)[clock->index]; + if (clock_state->stopped) + return 0; + now = msm_read_timer_count(clock, LOCAL_TIMER); + alarm = now + (cycles << clock->shift); + if (clock->flags & MSM_CLOCK_FLAGS_ODD_MATCH_WRITE) + while (now == clock_state->last_set) + now = msm_read_timer_count(clock, LOCAL_TIMER); + + clock_state->alarm = alarm; + __raw_writel(alarm, clock->regbase + TIMER_MATCH_VAL); + + if (clock->flags & MSM_CLOCK_FLAGS_DELAYED_WRITE_POST) { + /* read the counter four extra times to make sure write posts + before reading the time */ + for (i = 0; i < 4; i++) + __raw_readl(clock->regbase + TIMER_COUNT_VAL); + } + now = msm_read_timer_count(clock, LOCAL_TIMER); + clock_state->last_set = now; + clock_state->alarm_vtime = alarm + clock_state->sleep_offset; + late = now - alarm; + if (late >= (int)(-clock->write_delay << clock->shift) && + late < clock->freq*5) + return -ETIME; + + return 0; +} + +static void msm_timer_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + struct msm_clock *clock; + struct msm_clock_percpu_data *clock_state, *gpt_state; + unsigned long irq_flags; + +#ifdef CONFIG_SMP + clock = clockevent_to_clock(evt); +#else + clock = container_of(evt, struct msm_clock, clockevent); +#endif + clock_state = &__get_cpu_var(msm_clocks_percpu)[clock->index]; + gpt_state = &__get_cpu_var(msm_clocks_percpu)[MSM_CLOCK_GPT]; + + local_irq_save(irq_flags); + + switch (mode) { + case CLOCK_EVT_MODE_RESUME: + case CLOCK_EVT_MODE_PERIODIC: + break; + case CLOCK_EVT_MODE_ONESHOT: + clock_state->stopped = 0; + clock_state->sleep_offset = + -msm_read_timer_count(clock, LOCAL_TIMER) + + clock_state->stopped_tick; + get_cpu_var(msm_active_clock) = clock; + put_cpu_var(msm_active_clock); + __raw_writel(TIMER_ENABLE_EN, clock->regbase + TIMER_ENABLE); + if (irq_get_chip(clock->irq.irq) && + irq_get_chip(clock->irq.irq)->irq_unmask) { + irq_get_chip(clock->irq.irq)->irq_unmask( + irq_get_irq_data(clock->irq.irq)); + } + if (clock != &msm_clocks[MSM_CLOCK_GPT]) + __raw_writel(TIMER_ENABLE_EN, + msm_clocks[MSM_CLOCK_GPT].regbase + + TIMER_ENABLE); + break; + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + get_cpu_var(msm_active_clock) = NULL; + put_cpu_var(msm_active_clock); + clock_state->in_sync = 0; + clock_state->stopped = 1; + clock_state->stopped_tick = + msm_read_timer_count(clock, LOCAL_TIMER) + + clock_state->sleep_offset; + __raw_writel(0, clock->regbase + TIMER_MATCH_VAL); + if (irq_get_chip(clock->irq.irq) && + irq_get_chip(clock->irq.irq)->irq_mask) { + irq_get_chip(clock->irq.irq)->irq_mask( + irq_get_irq_data(clock->irq.irq)); + } +#ifdef CONFIG_MSM_SMP + if (clock != &msm_clocks[MSM_CLOCK_DGT] || smp_processor_id()) +#endif + __raw_writel(0, clock->regbase + TIMER_ENABLE); + if (clock != &msm_clocks[MSM_CLOCK_GPT]) { + gpt_state->in_sync = 0; + __raw_writel(0, msm_clocks[MSM_CLOCK_GPT].regbase + + TIMER_ENABLE); + } + break; + } + wmb(); + local_irq_restore(irq_flags); +} + +#ifdef CONFIG_PM +/* + * Retrieve the cycle count from sclk and optionally synchronize local clock + * with the sclk value. + * + * time_start and time_expired are callbacks that must be specified. The + * protocol uses them to detect timeout. The update callback is optional. + * If not NULL, update will be called so that it can update local clock. + * + * The function does not use the argument data directly; it passes data to + * the callbacks. + * + * Return value: + * 0: the operation failed + * >0: the slow clock value after time-sync + */ +static void (*msm_timer_sync_timeout)(void); +#if defined(CONFIG_MSM_DIRECT_SCLK_ACCESS) +static uint32_t msm_timer_do_sync_to_sclk( + void (*time_start)(struct msm_timer_sync_data_t *data), + bool (*time_expired)(struct msm_timer_sync_data_t *data), + void (*update)(struct msm_timer_sync_data_t *, uint32_t, uint32_t), + struct msm_timer_sync_data_t *data) +{ + uint32_t t1, t2; + int loop_count = 10; + int loop_zero_count = 3; + int tmp = USEC_PER_SEC/SCLK_HZ/(loop_zero_count-1); + + while (loop_zero_count--) { + t1 = __raw_readl(MSM_RPM_MPM_BASE + MPM_SCLK_COUNT_VAL); + do { + udelay(1); + t2 = t1; + t1 = __raw_readl(MSM_RPM_MPM_BASE + MPM_SCLK_COUNT_VAL); + } while ((t2 != t1) && --loop_count); + + if (!loop_count) { + printk(KERN_EMERG "SCLK did not stabilize\n"); + return 0; + } + + if (t1) + break; + + udelay(tmp); + } + + if (!loop_zero_count) { + printk(KERN_EMERG "SCLK reads zero\n"); + return 0; + } + + if (update != NULL) + update(data, t1, SCLK_HZ); + return t1; +} +#elif defined(CONFIG_MSM_N_WAY_SMSM) +static uint32_t msm_timer_do_sync_to_sclk( + void (*time_start)(struct msm_timer_sync_data_t *data), + bool (*time_expired)(struct msm_timer_sync_data_t *data), + void (*update)(struct msm_timer_sync_data_t *, uint32_t, uint32_t), + struct msm_timer_sync_data_t *data) +{ + uint32_t *smem_clock; + uint32_t smem_clock_val; + uint32_t state; + + smem_clock = smem_alloc(SMEM_SMEM_SLOW_CLOCK_VALUE, sizeof(uint32_t)); + if (smem_clock == NULL) { + printk(KERN_ERR "no smem clock\n"); + return 0; + } + + state = smsm_get_state(SMSM_MODEM_STATE); + if ((state & SMSM_INIT) == 0) { + printk(KERN_ERR "smsm not initialized\n"); + return 0; + } + + time_start(data); + while ((state = smsm_get_state(SMSM_TIME_MASTER_DEM)) & + MASTER_TIME_PENDING) { + if (time_expired(data)) { + printk(KERN_EMERG "get_smem_clock: timeout 1 still " + "invalid state %x\n", state); + msm_timer_sync_timeout(); + } + } + + smsm_change_state(SMSM_APPS_DEM, SLAVE_TIME_POLL | SLAVE_TIME_INIT, + SLAVE_TIME_REQUEST); + + time_start(data); + while (!((state = smsm_get_state(SMSM_TIME_MASTER_DEM)) & + MASTER_TIME_PENDING)) { + if (time_expired(data)) { + printk(KERN_EMERG "get_smem_clock: timeout 2 still " + "invalid state %x\n", state); + msm_timer_sync_timeout(); + } + } + + smsm_change_state(SMSM_APPS_DEM, SLAVE_TIME_REQUEST, SLAVE_TIME_POLL); + + time_start(data); + do { + smem_clock_val = *smem_clock; + } while (smem_clock_val == 0 && !time_expired(data)); + + state = smsm_get_state(SMSM_TIME_MASTER_DEM); + + if (smem_clock_val) { + if (update != NULL) + update(data, smem_clock_val, SCLK_HZ); + + if (msm_timer_debug_mask & MSM_TIMER_DEBUG_SYNC) + printk(KERN_INFO + "get_smem_clock: state %x clock %u\n", + state, smem_clock_val); + } else { + printk(KERN_EMERG + "get_smem_clock: timeout state %x clock %u\n", + state, smem_clock_val); + msm_timer_sync_timeout(); + } + + smsm_change_state(SMSM_APPS_DEM, SLAVE_TIME_REQUEST | SLAVE_TIME_POLL, + SLAVE_TIME_INIT); + return smem_clock_val; +} +#else /* CONFIG_MSM_N_WAY_SMSM */ +static uint32_t msm_timer_do_sync_to_sclk( + void (*time_start)(struct msm_timer_sync_data_t *data), + bool (*time_expired)(struct msm_timer_sync_data_t *data), + void (*update)(struct msm_timer_sync_data_t *, uint32_t, uint32_t), + struct msm_timer_sync_data_t *data) +{ + uint32_t *smem_clock; + uint32_t smem_clock_val; + uint32_t last_state; + uint32_t state; + + smem_clock = smem_alloc(SMEM_SMEM_SLOW_CLOCK_VALUE, + sizeof(uint32_t)); + + if (smem_clock == NULL) { + printk(KERN_ERR "no smem clock\n"); + return 0; + } + + last_state = state = smsm_get_state(SMSM_MODEM_STATE); + smem_clock_val = *smem_clock; + if (smem_clock_val) { + printk(KERN_INFO "get_smem_clock: invalid start state %x " + "clock %u\n", state, smem_clock_val); + smsm_change_state(SMSM_APPS_STATE, + SMSM_TIMEWAIT, SMSM_TIMEINIT); + + time_start(data); + while (*smem_clock != 0 && !time_expired(data)) + ; + + smem_clock_val = *smem_clock; + if (smem_clock_val) { + printk(KERN_EMERG "get_smem_clock: timeout still " + "invalid state %x clock %u\n", + state, smem_clock_val); + msm_timer_sync_timeout(); + } + } + + time_start(data); + smsm_change_state(SMSM_APPS_STATE, SMSM_TIMEINIT, SMSM_TIMEWAIT); + do { + smem_clock_val = *smem_clock; + state = smsm_get_state(SMSM_MODEM_STATE); + if (state != last_state) { + last_state = state; + if (msm_timer_debug_mask & MSM_TIMER_DEBUG_SYNC) + printk(KERN_INFO + "get_smem_clock: state %x clock %u\n", + state, smem_clock_val); + } + } while (smem_clock_val == 0 && !time_expired(data)); + + if (smem_clock_val) { + if (update != NULL) + update(data, smem_clock_val, SCLK_HZ); + } else { + printk(KERN_EMERG + "get_smem_clock: timeout state %x clock %u\n", + state, smem_clock_val); + msm_timer_sync_timeout(); + } + + smsm_change_state(SMSM_APPS_STATE, SMSM_TIMEWAIT, SMSM_TIMEINIT); + return smem_clock_val; +} +#endif /* CONFIG_MSM_N_WAY_SMSM */ + +/* + * Callback function that initializes the timeout value. + */ +static void msm_timer_sync_to_sclk_time_start( + struct msm_timer_sync_data_t *data) +{ + /* approx 2 seconds */ + uint32_t delta = data->clock->freq << data->clock->shift << 1; + data->timeout = msm_read_timer_count(data->clock, LOCAL_TIMER) + delta; +} + +/* + * Callback function that checks the timeout. + */ +static bool msm_timer_sync_to_sclk_time_expired( + struct msm_timer_sync_data_t *data) +{ + uint32_t delta = msm_read_timer_count(data->clock, LOCAL_TIMER) - + data->timeout; + return ((int32_t) delta) > 0; +} + +/* + * Callback function that updates local clock from the specified source clock + * value and frequency. + */ +static void msm_timer_sync_update(struct msm_timer_sync_data_t *data, + uint32_t src_clk_val, uint32_t src_clk_freq) +{ + struct msm_clock *dst_clk = data->clock; + struct msm_clock_percpu_data *dst_clk_state = + &__get_cpu_var(msm_clocks_percpu)[dst_clk->index]; + uint32_t dst_clk_val = msm_read_timer_count(dst_clk, LOCAL_TIMER); + uint32_t new_offset; + + if ((dst_clk->freq << dst_clk->shift) == src_clk_freq) { + new_offset = src_clk_val - dst_clk_val; + } else { + uint64_t temp; + + /* separate multiplication and division steps to reduce + rounding error */ + temp = src_clk_val; + temp *= dst_clk->freq << dst_clk->shift; + do_div(temp, src_clk_freq); + + new_offset = (uint32_t)(temp) - dst_clk_val; + } + + if (dst_clk_state->sleep_offset + dst_clk_state->non_sleep_offset != + new_offset) { + if (data->exit_sleep) + dst_clk_state->sleep_offset = + new_offset - dst_clk_state->non_sleep_offset; + else + dst_clk_state->non_sleep_offset = + new_offset - dst_clk_state->sleep_offset; + + if (msm_timer_debug_mask & MSM_TIMER_DEBUG_SYNC) + printk(KERN_INFO "sync clock %s: " + "src %u, new offset %u + %u\n", + dst_clk->clocksource.name, src_clk_val, + dst_clk_state->sleep_offset, + dst_clk_state->non_sleep_offset); + } +} + +/* + * Synchronize GPT clock with sclk. + */ +static void msm_timer_sync_gpt_to_sclk(int exit_sleep) +{ + struct msm_clock *gpt_clk = &msm_clocks[MSM_CLOCK_GPT]; + struct msm_clock_percpu_data *gpt_clk_state = + &__get_cpu_var(msm_clocks_percpu)[MSM_CLOCK_GPT]; + struct msm_timer_sync_data_t data; + uint32_t ret; + + if (gpt_clk_state->in_sync) + return; + + data.clock = gpt_clk; + data.timeout = 0; + data.exit_sleep = exit_sleep; + + ret = msm_timer_do_sync_to_sclk( + msm_timer_sync_to_sclk_time_start, + msm_timer_sync_to_sclk_time_expired, + msm_timer_sync_update, + &data); + + if (ret) + gpt_clk_state->in_sync = 1; +} + +/* + * Synchronize clock with GPT clock. + */ +static void msm_timer_sync_to_gpt(struct msm_clock *clock, int exit_sleep) +{ + struct msm_clock *gpt_clk = &msm_clocks[MSM_CLOCK_GPT]; + struct msm_clock_percpu_data *gpt_clk_state = + &__get_cpu_var(msm_clocks_percpu)[MSM_CLOCK_GPT]; + struct msm_clock_percpu_data *clock_state = + &__get_cpu_var(msm_clocks_percpu)[clock->index]; + struct msm_timer_sync_data_t data; + uint32_t gpt_clk_val; + u64 gpt_period = (1ULL << 32) * HZ / GPT_HZ; + u64 now = get_jiffies_64(); + + BUG_ON(clock == gpt_clk); + + if (clock_state->in_sync && + (now - clock_state->last_sync_jiffies < (gpt_period >> 1))) + return; + + gpt_clk_val = msm_read_timer_count(gpt_clk, LOCAL_TIMER) + + gpt_clk_state->sleep_offset + gpt_clk_state->non_sleep_offset; + + if (exit_sleep && gpt_clk_val < clock_state->last_sync_gpt) + clock_state->non_sleep_offset -= clock->rollover_offset; + + data.clock = clock; + data.timeout = 0; + data.exit_sleep = exit_sleep; + + msm_timer_sync_update(&data, gpt_clk_val, GPT_HZ); + + clock_state->in_sync = 1; + clock_state->last_sync_gpt = gpt_clk_val; + clock_state->last_sync_jiffies = now; +} + +static void msm_timer_reactivate_alarm(struct msm_clock *clock) +{ + struct msm_clock_percpu_data *clock_state = + &__get_cpu_var(msm_clocks_percpu)[clock->index]; + long alarm_delta = clock_state->alarm_vtime - + clock_state->sleep_offset - + msm_read_timer_count(clock, LOCAL_TIMER); + alarm_delta >>= clock->shift; + if (alarm_delta < (long)clock->write_delay + 4) + alarm_delta = clock->write_delay + 4; + while (msm_timer_set_next_event(alarm_delta, &clock->clockevent)) + ; +} + +int64_t msm_timer_enter_idle(void) +{ + struct msm_clock *gpt_clk = &msm_clocks[MSM_CLOCK_GPT]; + struct msm_clock *clock = __get_cpu_var(msm_active_clock); + struct msm_clock_percpu_data *clock_state = + &__get_cpu_var(msm_clocks_percpu)[clock->index]; + uint32_t alarm; + uint32_t count; + int32_t delta; + + BUG_ON(clock != &msm_clocks[MSM_CLOCK_GPT] && + clock != &msm_clocks[MSM_CLOCK_DGT]); + + msm_timer_sync_gpt_to_sclk(0); + if (clock != gpt_clk) + msm_timer_sync_to_gpt(clock, 0); + + count = msm_read_timer_count(clock, LOCAL_TIMER); + if (clock_state->stopped++ == 0) + clock_state->stopped_tick = count + clock_state->sleep_offset; + alarm = clock_state->alarm; + delta = alarm - count; + if (delta <= -(int32_t)((clock->freq << clock->shift) >> 10)) { + /* timer should have triggered 1ms ago */ + printk(KERN_ERR "msm_timer_enter_idle: timer late %d, " + "reprogram it\n", delta); + msm_timer_reactivate_alarm(clock); + } + if (delta <= 0) + return 0; + return clocksource_cyc2ns((alarm - count) >> clock->shift, + clock->clocksource.mult, + clock->clocksource.shift); +} + +void msm_timer_exit_idle(int low_power) +{ + struct msm_clock *gpt_clk = &msm_clocks[MSM_CLOCK_GPT]; + struct msm_clock *clock = __get_cpu_var(msm_active_clock); + struct msm_clock_percpu_data *gpt_clk_state = + &__get_cpu_var(msm_clocks_percpu)[MSM_CLOCK_GPT]; + struct msm_clock_percpu_data *clock_state = + &__get_cpu_var(msm_clocks_percpu)[clock->index]; + uint32_t enabled; + + BUG_ON(clock != &msm_clocks[MSM_CLOCK_GPT] && + clock != &msm_clocks[MSM_CLOCK_DGT]); + + if (!low_power) + goto exit_idle_exit; + + enabled = __raw_readl(gpt_clk->regbase + TIMER_ENABLE) & + TIMER_ENABLE_EN; + if (!enabled) + __raw_writel(TIMER_ENABLE_EN, gpt_clk->regbase + TIMER_ENABLE); + +#if defined(CONFIG_ARCH_MSM_SCORPION) || defined(CONFIG_ARCH_MSM_KRAIT) + gpt_clk_state->in_sync = 0; +#else + gpt_clk_state->in_sync = gpt_clk_state->in_sync && enabled; +#endif + /* Make sure timer is actually enabled before we sync it */ + wmb(); + msm_timer_sync_gpt_to_sclk(1); + + if (clock == gpt_clk) + goto exit_idle_alarm; + + enabled = __raw_readl(clock->regbase + TIMER_ENABLE) & TIMER_ENABLE_EN; + if (!enabled) + __raw_writel(TIMER_ENABLE_EN, clock->regbase + TIMER_ENABLE); + +#if defined(CONFIG_ARCH_MSM_SCORPION) || defined(CONFIG_ARCH_MSM_KRAIT) + clock_state->in_sync = 0; +#else + clock_state->in_sync = clock_state->in_sync && enabled; +#endif + /* Make sure timer is actually enabled before we sync it */ + wmb(); + msm_timer_sync_to_gpt(clock, 1); + +exit_idle_alarm: + msm_timer_reactivate_alarm(clock); + +exit_idle_exit: + clock_state->stopped--; +} + +/* + * Callback function that initializes the timeout value. + */ +static void msm_timer_get_sclk_time_start( + struct msm_timer_sync_data_t *data) +{ + data->timeout = 200000; +} + +/* + * Callback function that checks the timeout. + */ +static bool msm_timer_get_sclk_time_expired( + struct msm_timer_sync_data_t *data) +{ + udelay(10); + return --data->timeout <= 0; +} + +/* + * Retrieve the cycle count from the sclk and convert it into + * nanoseconds. + * + * On exit, if period is not NULL, it contains the period of the + * sclk in nanoseconds, i.e. how long the cycle count wraps around. + * + * Return value: + * 0: the operation failed; period is not set either + * >0: time in nanoseconds + */ +int64_t msm_timer_get_sclk_time(int64_t *period) +{ + struct msm_timer_sync_data_t data; + uint32_t clock_value; + int64_t tmp; + + memset(&data, 0, sizeof(data)); + clock_value = msm_timer_do_sync_to_sclk( + msm_timer_get_sclk_time_start, + msm_timer_get_sclk_time_expired, + NULL, + &data); + + if (!clock_value) + return 0; + + if (period) { + tmp = 1LL << 32; + tmp = tmp * NSEC_PER_SEC / SCLK_HZ; + *period = tmp; + } + + tmp = (int64_t)clock_value; + tmp = tmp * NSEC_PER_SEC / SCLK_HZ; + return tmp; +} + +int __init msm_timer_init_time_sync(void (*timeout)(void)) +{ +#if defined(CONFIG_MSM_N_WAY_SMSM) && !defined(CONFIG_MSM_DIRECT_SCLK_ACCESS) + int ret = smsm_change_intr_mask(SMSM_TIME_MASTER_DEM, 0xFFFFFFFF, 0); + + if (ret) { + printk(KERN_ERR "%s: failed to clear interrupt mask, %d\n", + __func__, ret); + return ret; + } + + smsm_change_state(SMSM_APPS_DEM, + SLAVE_TIME_REQUEST | SLAVE_TIME_POLL, SLAVE_TIME_INIT); +#endif + + BUG_ON(timeout == NULL); + msm_timer_sync_timeout = timeout; + + return 0; +} + +#endif + +unsigned long long sched_clock(void) +{ + static cycle_t last_ticks; + static unsigned long long last_ns; + static DEFINE_SPINLOCK(msm_timer_sched_clock_lock); + + struct msm_clock *clock; + struct clocksource *cs; + cycle_t ticks, delta; + unsigned long irq_flags; + + clock = &msm_clocks[MSM_GLOBAL_TIMER]; + cs = &clock->clocksource; + + ticks = cs->read(cs); + + spin_lock_irqsave(&msm_timer_sched_clock_lock, irq_flags); + delta = (ticks - last_ticks) & cs->mask; + + if (delta < cs->mask/2) { + last_ticks += delta; + last_ns += clocksource_cyc2ns(delta, cs->mult, cs->shift); + } + + ticks = last_ticks; + spin_unlock_irqrestore(&msm_timer_sched_clock_lock, irq_flags); + + return last_ns; +} + +#ifdef CONFIG_MSM_SMP +int read_current_timer(unsigned long *timer_val) +{ + struct msm_clock *dgt = &msm_clocks[MSM_CLOCK_DGT]; + *timer_val = msm_read_timer_count(dgt, GLOBAL_TIMER); + return 0; +} +#endif + static void __init msm_timer_init(void) { int i; int res; - int global_offset = 0; - - if (cpu_is_msm7x01()) { - msm_clocks[MSM_CLOCK_GPT].regbase = MSM_CSR_BASE; - msm_clocks[MSM_CLOCK_DGT].regbase = MSM_CSR_BASE + 0x10; - } else if (cpu_is_msm7x30()) { - msm_clocks[MSM_CLOCK_GPT].regbase = MSM_CSR_BASE + 0x04; - msm_clocks[MSM_CLOCK_DGT].regbase = MSM_CSR_BASE + 0x24; - } else if (cpu_is_qsd8x50()) { - msm_clocks[MSM_CLOCK_GPT].regbase = MSM_CSR_BASE; - msm_clocks[MSM_CLOCK_DGT].regbase = MSM_CSR_BASE + 0x10; - } else if (cpu_is_msm8x60() || cpu_is_msm8960()) { - msm_clocks[MSM_CLOCK_GPT].regbase = MSM_TMR_BASE + 0x04; - msm_clocks[MSM_CLOCK_DGT].regbase = MSM_TMR_BASE + 0x24; - - /* Use CPU0's timer as the global timer. */ - global_offset = MSM_TMR0_BASE - MSM_TMR_BASE; - } else - BUG(); - -#ifdef CONFIG_ARCH_MSM_SCORPIONMP - writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); + +#if defined(CONFIG_ARCH_MSM8X60) || defined(CONFIG_ARCH_MSM8960) || \ + defined(CONFIG_ARCH_APQ8064) + __raw_writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); #endif for (i = 0; i < ARRAY_SIZE(msm_clocks); i++) { struct msm_clock *clock = &msm_clocks[i]; struct clock_event_device *ce = &clock->clockevent; struct clocksource *cs = &clock->clocksource; + __raw_writel(0, clock->regbase + TIMER_ENABLE); + __raw_writel(1, clock->regbase + TIMER_CLEAR); + __raw_writel(0, clock->regbase + TIMER_COUNT_VAL); + __raw_writel(~0, clock->regbase + TIMER_MATCH_VAL); + + if ((clock->freq << clock->shift) == GPT_HZ) { + clock->rollover_offset = 0; + } else { + uint64_t temp; - clock->local_counter = clock->regbase + TIMER_COUNT_VAL; - clock->global_counter = clock->local_counter + global_offset; + temp = clock->freq << clock->shift; + temp <<= 32; + temp /= GPT_HZ; - writel(0, clock->regbase + TIMER_ENABLE); - writel(0, clock->regbase + TIMER_CLEAR); - writel(~0, clock->regbase + TIMER_MATCH_VAL); + clock->rollover_offset = (uint32_t) temp; + } ce->mult = div_sc(clock->freq, NSEC_PER_SEC, ce->shift); /* allow at least 10 seconds to notice that the timer wrapped */ ce->max_delta_ns = clockevent_delta2ns(0xf0000000 >> clock->shift, ce); - /* 4 gets rounded down to 3 */ - ce->min_delta_ns = clockevent_delta2ns(4, ce); + /* ticks gets rounded down by one */ + ce->min_delta_ns = + clockevent_delta2ns(clock->write_delay + 4, ce); ce->cpumask = cpumask_of(0); - res = clocksource_register_hz(cs, clock->freq); + cs->mult = clocksource_hz2mult(clock->freq, cs->shift); + res = clocksource_register(cs); if (res) printk(KERN_ERR "msm_timer_init: clocksource_register " "failed for %s\n", cs->name); @@ -266,25 +1056,36 @@ static void __init msm_timer_init(void) printk(KERN_ERR "msm_timer_init: setup_irq " "failed for %s\n", cs->name); + irq_get_chip(clock->irq.irq)->irq_mask(irq_get_irq_data( + clock->irq.irq)); + clockevents_register_device(ce); } +#ifdef CONFIG_MSM_SMP + __raw_writel(1, msm_clocks[MSM_CLOCK_DGT].regbase + TIMER_ENABLE); + set_delay_fn(read_current_timer_delay_loop); +#endif } #ifdef CONFIG_SMP + int __cpuinit local_timer_setup(struct clock_event_device *evt) { + unsigned long flags; + static bool first_boot = true; struct msm_clock *clock = &msm_clocks[MSM_GLOBAL_TIMER]; /* Use existing clock_event for cpu 0 */ if (!smp_processor_id()) return 0; - writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); + __raw_writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); - if (!local_clock_event) { - writel(0, clock->regbase + TIMER_ENABLE); - writel(0, clock->regbase + TIMER_CLEAR); - writel(~0, clock->regbase + TIMER_MATCH_VAL); + if (first_boot) { + __raw_writel(0, clock->regbase + TIMER_ENABLE); + __raw_writel(0, clock->regbase + TIMER_CLEAR); + __raw_writel(~0, clock->regbase + TIMER_MATCH_VAL); + first_boot = false; } evt->irq = clock->irq.irq; evt->name = "local_timer"; @@ -298,19 +1099,22 @@ int __cpuinit local_timer_setup(struct clock_event_device *evt) clockevent_delta2ns(0xf0000000 >> clock->shift, evt); evt->min_delta_ns = clockevent_delta2ns(4, evt); - local_clock_event = evt; + __get_cpu_var(local_clock_event) = evt; + local_irq_save(flags); + gic_clear_spi_pending(clock->irq.irq); + local_irq_restore(flags); gic_enable_ppi(clock->irq.irq); clockevents_register_device(evt); + return 0; } -inline int local_timer_ack(void) +int local_timer_ack(void) { return 1; } - #endif struct sys_timer msm_timer = { diff --git a/arch/arm/mach-msm/timer.h b/arch/arm/mach-msm/timer.h new file mode 100644 index 00000000000..5388f68958f --- /dev/null +++ b/arch/arm/mach-msm/timer.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCH_ARM_MACH_MSM_TIMER_H_ +#define _ARCH_ARM_MACH_MSM_TIMER_H_ + +extern struct sys_timer msm_timer; + +int64_t msm_timer_enter_idle(void); +void msm_timer_exit_idle(int low_power); +int64_t msm_timer_get_sclk_time(int64_t *period); +int msm_timer_init_time_sync(void (*timeout)(void)); +#endif diff --git a/arch/arm/mach-msm/vreg.c b/arch/arm/mach-msm/vreg.c index a9103bc6615..8f782a99e4a 100644 --- a/arch/arm/mach-msm/vreg.c +++ b/arch/arm/mach-msm/vreg.c @@ -1,7 +1,7 @@ /* arch/arm/mach-msm/vreg.c * * Copyright (C) 2008 Google, Inc. - * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009-2011 Code Aurora Forum. All rights reserved. * Author: Brian Swetland * * This software is licensed under the terms of the GNU General Public @@ -24,6 +24,14 @@ #include "proc_comm.h" +#if defined(CONFIG_MSM_VREG_SWITCH_INVERTED) +#define VREG_SWITCH_ENABLE 0 +#define VREG_SWITCH_DISABLE 1 +#else +#define VREG_SWITCH_ENABLE 1 +#define VREG_SWITCH_DISABLE 0 +#endif + struct vreg { const char *name; unsigned id; @@ -83,6 +91,15 @@ static struct vreg vregs[] = { VREG("xo_out", 46, 0, 0), VREG("lvsw0", 47, 0, 0), VREG("lvsw1", 48, 0, 0), + VREG("mddi", 49, 0, 0), + VREG("pllx", 50, 0, 0), + VREG("wlan3", 51, 0, 0), + VREG("emmc", 52, 0, 0), + VREG("wlan_tcx0", 53, 0, 0), + VREG("usim2", 54, 0, 0), + VREG("usim", 55, 0, 0), + VREG("bt", 56, 0, 0), + VREG("wlan4", 57, 0, 0), }; struct vreg *vreg_get(struct device *dev, const char *id) @@ -94,6 +111,7 @@ struct vreg *vreg_get(struct device *dev, const char *id) } return ERR_PTR(-ENOENT); } +EXPORT_SYMBOL(vreg_get); void vreg_put(struct vreg *vreg) { @@ -102,7 +120,7 @@ void vreg_put(struct vreg *vreg) int vreg_enable(struct vreg *vreg) { unsigned id = vreg->id; - unsigned enable = 1; + int enable = VREG_SWITCH_ENABLE; if (vreg->refcnt == 0) vreg->status = msm_proc_comm(PCOM_VREG_SWITCH, &id, &enable); @@ -112,23 +130,25 @@ int vreg_enable(struct vreg *vreg) return vreg->status; } +EXPORT_SYMBOL(vreg_enable); int vreg_disable(struct vreg *vreg) { unsigned id = vreg->id; - unsigned enable = 0; + int disable = VREG_SWITCH_DISABLE; if (!vreg->refcnt) return 0; if (vreg->refcnt == 1) - vreg->status = msm_proc_comm(PCOM_VREG_SWITCH, &id, &enable); + vreg->status = msm_proc_comm(PCOM_VREG_SWITCH, &id, &disable); if (!vreg->status) vreg->refcnt--; return vreg->status; } +EXPORT_SYMBOL(vreg_disable); int vreg_set_level(struct vreg *vreg, unsigned mv) { @@ -137,6 +157,7 @@ int vreg_set_level(struct vreg *vreg, unsigned mv) vreg->status = msm_proc_comm(PCOM_VREG_SET_LEVEL, &id, &mv); return vreg->status; } +EXPORT_SYMBOL(vreg_set_level); #if defined(CONFIG_DEBUG_FS) diff --git a/arch/arm/mm/Kconfig b/arch/arm/mm/Kconfig index 0074b8dba79..e862f63aea6 100644 --- a/arch/arm/mm/Kconfig +++ b/arch/arm/mm/Kconfig @@ -588,6 +588,9 @@ config CPU_TLB_V6 config CPU_TLB_V7 bool +config EMULATE_DOMAIN_MANAGER_V7 + bool + config VERIFY_PERMISSION_FAULT bool endif @@ -738,6 +741,19 @@ config CPU_DCACHE_SIZE If your SoC is configured to have a different size, define the value here with proper conditions. +config CPU_CACHE_ERR_REPORT + bool "Report errors in the L1 and L2 caches" + depends on ARCH_MSM_SCORPION + default n + help + The Scorpion processor supports reporting L2 errors, L1 icache parity + errors, and L1 dcache parity errors as imprecise external aborts. If + this option is not enabled these errors will go unreported and data + corruption will occur. + + Say Y here to have errors in the L1 and L2 caches reported as + imprecise data aborts. + config CPU_DCACHE_WRITETHROUGH bool "Force write through D-cache" depends on (CPU_ARM740T || CPU_ARM920T || CPU_ARM922T || CPU_ARM925T || CPU_ARM926T || CPU_ARM940T || CPU_ARM946E || CPU_ARM1020 || CPU_FA526) && !CPU_DCACHE_DISABLE @@ -821,7 +837,7 @@ config CACHE_L2X0 depends on REALVIEW_EB_ARM11MP || MACH_REALVIEW_PB11MP || MACH_REALVIEW_PB1176 || \ REALVIEW_EB_A9MP || SOC_IMX35 || SOC_IMX31 || MACH_REALVIEW_PBX || \ ARCH_NOMADIK || ARCH_OMAP4 || ARCH_EXYNOS4 || ARCH_TEGRA || \ - ARCH_U8500 || ARCH_VEXPRESS_CA9X4 || ARCH_SHMOBILE + ARCH_U8500 || ARCH_VEXPRESS_CA9X4 || ARCH_SHMOBILE || ARCH_MSM7X27 default y select OUTER_CACHE select OUTER_CACHE_SYNC @@ -889,3 +905,30 @@ config ARCH_HAS_BARRIERS help This option allows the use of custom mandatory barriers included via the mach/barriers.h file. + +config VCM_MM + bool + +config VCM + bool "Virtual Contiguous Memory (VCM) Layer" + depends on MMU + select GENERIC_ALLOCATOR + select VCM_MM + default n + help + Virtual Contiguous Memory layer. This is the layer that is intended to + replace PMEM. + + If you don't know what this is, say N here. + +config STRICT_MEMORY_RWX + bool "restrict kernel memory permissions as much as possible" + default n + help + If this is set, kernel text will be made RX, kernel data and stack + RW, rodata R (otherwise all of the kernel 1-to-1 mapping is + made RWX). + The tradeoff is that several sections are padded to + 1M boundaries (because their permissions are different and + splitting the 1M pages into 4K ones causes TLB performance + problems), wasting memory. diff --git a/arch/arm/mm/Makefile b/arch/arm/mm/Makefile index bca7e61928c..80460267cbd 100644 --- a/arch/arm/mm/Makefile +++ b/arch/arm/mm/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_CPU_FEROCEON) += proc-feroceon.o obj-$(CONFIG_CPU_V6) += proc-v6.o obj-$(CONFIG_CPU_V6K) += proc-v6.o obj-$(CONFIG_CPU_V7) += proc-v7.o +obj-$(CONFIG_EMULATE_DOMAIN_MANAGER_V7) += emulate_domain_manager-v7.o AFLAGS_proc-v6.o :=-Wa,-march=armv6 AFLAGS_proc-v7.o :=-Wa,-march=armv7-a @@ -100,3 +101,5 @@ obj-$(CONFIG_CACHE_FEROCEON_L2) += cache-feroceon-l2.o obj-$(CONFIG_CACHE_L2X0) += cache-l2x0.o obj-$(CONFIG_CACHE_XSC3L2) += cache-xsc3l2.o obj-$(CONFIG_CACHE_TAUROS2) += cache-tauros2.o +obj-$(CONFIG_VCM) += vcm.o vcm_alloc.o +obj-$(CONFIG_VCM_MM) += vcm_mm.o diff --git a/arch/arm/mm/cache-fa.S b/arch/arm/mm/cache-fa.S index 1fa6f71470d..ad953fe4ef5 100644 --- a/arch/arm/mm/cache-fa.S +++ b/arch/arm/mm/cache-fa.S @@ -168,7 +168,7 @@ ENTRY(fa_flush_kern_dcache_area) * - start - virtual start address * - end - virtual end address */ -fa_dma_inv_range: +ENTRY(fa_dma_inv_range) tst r0, #CACHE_DLINESIZE - 1 bic r0, r0, #CACHE_DLINESIZE - 1 mcrne p15, 0, r0, c7, c14, 1 @ clean & invalidate D entry @@ -191,7 +191,7 @@ fa_dma_inv_range: * - start - virtual start address * - end - virtual end address */ -fa_dma_clean_range: +ENTRY(fa_dma_clean_range) bic r0, r0, #CACHE_DLINESIZE - 1 1: mcr p15, 0, r0, c7, c10, 1 @ clean D entry add r0, r0, #CACHE_DLINESIZE @@ -253,5 +253,7 @@ ENTRY(fa_cache_fns) .long fa_flush_kern_dcache_area .long fa_dma_map_area .long fa_dma_unmap_area + .long fa_dma_inv_range + .long fa_dma_clean_range .long fa_dma_flush_range .size fa_cache_fns, . - fa_cache_fns diff --git a/arch/arm/mm/cache-l2x0.c b/arch/arm/mm/cache-l2x0.c index 44c086710d2..25a42602806 100644 --- a/arch/arm/mm/cache-l2x0.c +++ b/arch/arm/mm/cache-l2x0.c @@ -2,6 +2,7 @@ * arch/arm/mm/cache-l2x0.c - L210/L220 cache controller support * * Copyright (C) 2007 ARM Limited + * Copyright (c) 2009, 2011, Code Aurora Forum. 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 @@ -26,6 +27,7 @@ #define CACHE_LINE_SIZE 32 static void __iomem *l2x0_base; +static uint32_t aux_ctrl_save; static DEFINE_SPINLOCK(l2x0_lock); static uint32_t l2x0_way_mask; /* Bitmask of active ways */ static uint32_t l2x0_size; @@ -111,13 +113,9 @@ static inline void l2x0_flush_line(unsigned long addr) } #endif -static void l2x0_cache_sync(void) +void l2x0_cache_sync(void) { - unsigned long flags; - - spin_lock_irqsave(&l2x0_lock, flags); cache_sync(); - spin_unlock_irqrestore(&l2x0_lock, flags); } static void __l2x0_flush_all(void) @@ -204,6 +202,27 @@ static void l2x0_inv_range(unsigned long start, unsigned long end) spin_unlock_irqrestore(&l2x0_lock, flags); } +static void l2x0_inv_range_atomic(unsigned long start, unsigned long end) +{ + unsigned long addr; + + if (start & (CACHE_LINE_SIZE - 1)) { + start &= ~(CACHE_LINE_SIZE - 1); + writel_relaxed(start, l2x0_base + L2X0_CLEAN_INV_LINE_PA); + start += CACHE_LINE_SIZE; + } + + if (end & (CACHE_LINE_SIZE - 1)) { + end &= ~(CACHE_LINE_SIZE - 1); + writel_relaxed(end, l2x0_base + L2X0_CLEAN_INV_LINE_PA); + } + + for (addr = start; addr < end; addr += CACHE_LINE_SIZE) + writel_relaxed(addr, l2x0_base + L2X0_INV_LINE_PA); + + mb(); +} + static void l2x0_clean_range(unsigned long start, unsigned long end) { void __iomem *base = l2x0_base; @@ -234,6 +253,17 @@ static void l2x0_clean_range(unsigned long start, unsigned long end) spin_unlock_irqrestore(&l2x0_lock, flags); } +static void l2x0_clean_range_atomic(unsigned long start, unsigned long end) +{ + unsigned long addr; + + start &= ~(CACHE_LINE_SIZE - 1); + for (addr = start; addr < end; addr += CACHE_LINE_SIZE) + writel_relaxed(addr, l2x0_base + L2X0_CLEAN_LINE_PA); + + mb(); +} + static void l2x0_flush_range(unsigned long start, unsigned long end) { void __iomem *base = l2x0_base; @@ -266,6 +296,17 @@ static void l2x0_flush_range(unsigned long start, unsigned long end) spin_unlock_irqrestore(&l2x0_lock, flags); } +void l2x0_flush_range_atomic(unsigned long start, unsigned long end) +{ + unsigned long addr; + + start &= ~(CACHE_LINE_SIZE - 1); + for (addr = start; addr < end; addr += CACHE_LINE_SIZE) + writel_relaxed(addr, l2x0_base + L2X0_CLEAN_INV_LINE_PA); + + mb(); +} + static void l2x0_disable(void) { unsigned long flags; @@ -279,15 +320,19 @@ static void l2x0_disable(void) void __init l2x0_init(void __iomem *base, __u32 aux_val, __u32 aux_mask) { - __u32 aux; + __u32 aux, bits; __u32 cache_id; __u32 way_size = 0; int ways; const char *type; l2x0_base = base; - cache_id = readl_relaxed(l2x0_base + L2X0_CACHE_ID); + + bits = readl_relaxed(l2x0_base + L2X0_CTRL); + bits &= ~0x01; /* clear bit 0 */ + writel_relaxed(bits, l2x0_base + L2X0_CTRL); + aux = readl_relaxed(l2x0_base + L2X0_AUX_CTRL); aux &= aux_mask; @@ -312,7 +357,7 @@ void __init l2x0_init(void __iomem *base, __u32 aux_val, __u32 aux_mask) type = "L2x0 series"; break; } - + writel_relaxed(aux, l2x0_base + L2X0_AUX_CTRL); l2x0_way_mask = (1 << ways) - 1; /* @@ -322,32 +367,76 @@ void __init l2x0_init(void __iomem *base, __u32 aux_val, __u32 aux_mask) way_size = 1 << (way_size + 3); l2x0_size = ways * way_size * SZ_1K; - /* - * Check if l2x0 controller is already enabled. - * If you are booting from non-secure mode - * accessing the below registers will fault. - */ - if (!(readl_relaxed(l2x0_base + L2X0_CTRL) & 1)) { + l2x0_inv_all(); - /* l2x0 controller is disabled */ - writel_relaxed(aux, l2x0_base + L2X0_AUX_CTRL); - - l2x0_inv_all(); + /* enable L2X0 */ + bits = readl_relaxed(l2x0_base + L2X0_CTRL); + bits |= 0x01; /* set bit 0 */ + writel_relaxed(bits, l2x0_base + L2X0_CTRL); - /* enable L2X0 */ - writel_relaxed(1, l2x0_base + L2X0_CTRL); + switch (cache_id & L2X0_CACHE_ID_PART_MASK) { + case L2X0_CACHE_ID_PART_L220: + outer_cache.inv_range = l2x0_inv_range; + outer_cache.clean_range = l2x0_clean_range; + outer_cache.flush_range = l2x0_flush_range; + printk(KERN_INFO "L220 cache controller enabled\n"); + break; + case L2X0_CACHE_ID_PART_L310: + outer_cache.inv_range = l2x0_inv_range; + outer_cache.clean_range = l2x0_clean_range; + outer_cache.flush_range = l2x0_flush_range; + printk(KERN_INFO "L310 cache controller enabled\n"); + break; + case L2X0_CACHE_ID_PART_L210: + default: + outer_cache.inv_range = l2x0_inv_range_atomic; + outer_cache.clean_range = l2x0_clean_range_atomic; + outer_cache.flush_range = l2x0_flush_range_atomic; + printk(KERN_INFO "L210 cache controller enabled\n"); + break; } - outer_cache.inv_range = l2x0_inv_range; - outer_cache.clean_range = l2x0_clean_range; - outer_cache.flush_range = l2x0_flush_range; outer_cache.sync = l2x0_cache_sync; + outer_cache.flush_all = l2x0_flush_all; outer_cache.inv_all = l2x0_inv_all; outer_cache.disable = l2x0_disable; outer_cache.set_debug = l2x0_set_debug; + mb(); printk(KERN_INFO "%s cache controller enabled\n", type); printk(KERN_INFO "l2x0: %d ways, CACHE_ID 0x%08x, AUX_CTRL 0x%08x, Cache size: %d B\n", ways, cache_id, aux, l2x0_size); } + +void l2x0_suspend(void) +{ + /* Save aux control register value */ + aux_ctrl_save = readl_relaxed(l2x0_base + L2X0_AUX_CTRL); + /* Flush all cache */ + l2x0_flush_all(); + /* Disable the cache */ + writel_relaxed(0, l2x0_base + L2X0_CTRL); + + /* Memory barrier */ + dmb(); +} + +void l2x0_resume(int collapsed) +{ + if (collapsed) { + /* Disable the cache */ + writel_relaxed(0, l2x0_base + L2X0_CTRL); + + /* Restore aux control register value */ + writel_relaxed(aux_ctrl_save, l2x0_base + L2X0_AUX_CTRL); + + /* Invalidate the cache */ + l2x0_inv_all(); + } + + /* Enable the cache */ + writel_relaxed(1, l2x0_base + L2X0_CTRL); + + mb(); +} diff --git a/arch/arm/mm/cache-v3.S b/arch/arm/mm/cache-v3.S index 2e2bc406a18..64f739eaa4c 100644 --- a/arch/arm/mm/cache-v3.S +++ b/arch/arm/mm/cache-v3.S @@ -92,6 +92,20 @@ ENTRY(v3_coherent_user_range) ENTRY(v3_flush_kern_dcache_area) /* FALLTHROUGH */ +/* + * dma_inv_range(start, end) + * + * Invalidate (discard) the specified virtual address range. + * May not write back any entries. If 'start' or 'end' + * are not cache line aligned, those lines must be written + * back. + * + * - start - virtual start address + * - end - virtual end address + */ +ENTRY(v3_dma_inv_range) + /* FALLTHROUGH */ + /* * dma_flush_range(start, end) * @@ -103,6 +117,17 @@ ENTRY(v3_flush_kern_dcache_area) ENTRY(v3_dma_flush_range) mov r0, #0 mcr p15, 0, r0, c7, c0, 0 @ flush ID cache + /* FALLTHROUGH */ + +/* + * dma_clean_range(start, end) + * + * Clean (write back) the specified virtual address range. + * + * - start - virtual start address + * - end - virtual end address + */ +ENTRY(v3_dma_clean_range) mov pc, lr /* @@ -113,7 +138,7 @@ ENTRY(v3_dma_flush_range) */ ENTRY(v3_dma_unmap_area) teq r2, #DMA_TO_DEVICE - bne v3_dma_flush_range + bne v3_dma_inv_range /* FALLTHROUGH */ /* @@ -140,5 +165,7 @@ ENTRY(v3_cache_fns) .long v3_flush_kern_dcache_area .long v3_dma_map_area .long v3_dma_unmap_area + .long v3_dma_inv_range + .long v3_dma_clean_range .long v3_dma_flush_range .size v3_cache_fns, . - v3_cache_fns diff --git a/arch/arm/mm/cache-v4.S b/arch/arm/mm/cache-v4.S index a8fefb523f1..7824cf6e14a 100644 --- a/arch/arm/mm/cache-v4.S +++ b/arch/arm/mm/cache-v4.S @@ -102,6 +102,20 @@ ENTRY(v4_coherent_user_range) ENTRY(v4_flush_kern_dcache_area) /* FALLTHROUGH */ +/* + * dma_inv_range(start, end) + * + * Invalidate (discard) the specified virtual address range. + * May not write back any entries. If 'start' or 'end' + * are not cache line aligned, those lines must be written + * back. + * + * - start - virtual start address + * - end - virtual end address + */ +ENTRY(v4_dma_inv_range) + /* FALLTHROUGH */ + /* * dma_flush_range(start, end) * @@ -115,6 +129,17 @@ ENTRY(v4_dma_flush_range) mov r0, #0 mcr p15, 0, r0, c7, c7, 0 @ flush ID cache #endif + /* FALLTHROUGH */ + +/* + * dma_clean_range(start, end) + * + * Clean (write back) the specified virtual address range. + * + * - start - virtual start address + * - end - virtual end address + */ +ENTRY(v4_dma_clean_range) mov pc, lr /* @@ -125,7 +150,7 @@ ENTRY(v4_dma_flush_range) */ ENTRY(v4_dma_unmap_area) teq r2, #DMA_TO_DEVICE - bne v4_dma_flush_range + bne v4_dma_inv_range /* FALLTHROUGH */ /* @@ -152,5 +177,7 @@ ENTRY(v4_cache_fns) .long v4_flush_kern_dcache_area .long v4_dma_map_area .long v4_dma_unmap_area + .long v4_dma_inv_range + .long v4_dma_clean_range .long v4_dma_flush_range .size v4_cache_fns, . - v4_cache_fns diff --git a/arch/arm/mm/cache-v4wb.S b/arch/arm/mm/cache-v4wb.S index f40c69656d8..acbdaeb04e2 100644 --- a/arch/arm/mm/cache-v4wb.S +++ b/arch/arm/mm/cache-v4wb.S @@ -184,7 +184,7 @@ ENTRY(v4wb_coherent_user_range) * - start - virtual start address * - end - virtual end address */ -v4wb_dma_inv_range: +ENTRY(v4wb_dma_inv_range) tst r0, #CACHE_DLINESIZE - 1 bic r0, r0, #CACHE_DLINESIZE - 1 mcrne p15, 0, r0, c7, c10, 1 @ clean D entry @@ -205,7 +205,7 @@ v4wb_dma_inv_range: * - start - virtual start address * - end - virtual end address */ -v4wb_dma_clean_range: +ENTRY(v4wb_dma_clean_range) bic r0, r0, #CACHE_DLINESIZE - 1 1: mcr p15, 0, r0, c7, c10, 1 @ clean D entry add r0, r0, #CACHE_DLINESIZE @@ -264,5 +264,7 @@ ENTRY(v4wb_cache_fns) .long v4wb_flush_kern_dcache_area .long v4wb_dma_map_area .long v4wb_dma_unmap_area + .long v4wb_dma_inv_range + .long v4wb_dma_clean_range .long v4wb_dma_flush_range .size v4wb_cache_fns, . - v4wb_cache_fns diff --git a/arch/arm/mm/cache-v4wt.S b/arch/arm/mm/cache-v4wt.S index a7b276dbda1..5f60392c875 100644 --- a/arch/arm/mm/cache-v4wt.S +++ b/arch/arm/mm/cache-v4wt.S @@ -153,12 +153,23 @@ ENTRY(v4wt_flush_kern_dcache_area) * - start - virtual start address * - end - virtual end address */ -v4wt_dma_inv_range: +ENTRY(v4wt_dma_inv_range) bic r0, r0, #CACHE_DLINESIZE - 1 1: mcr p15, 0, r0, c7, c6, 1 @ invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 blo 1b + /* FALLTHROUGH */ + +/* + * dma_clean_range(start, end) + * + * Clean the specified virtual address range. + * + * - start - virtual start address + * - end - virtual end address + */ +ENTRY(v4wt_dma_clean_range) mov pc, lr /* @@ -208,5 +219,7 @@ ENTRY(v4wt_cache_fns) .long v4wt_flush_kern_dcache_area .long v4wt_dma_map_area .long v4wt_dma_unmap_area + .long v4wt_dma_inv_range + .long v4wt_dma_clean_range .long v4wt_dma_flush_range .size v4wt_cache_fns, . - v4wt_cache_fns diff --git a/arch/arm/mm/cache-v6.S b/arch/arm/mm/cache-v6.S index 6b5441d737b..0a2528d6805 100644 --- a/arch/arm/mm/cache-v6.S +++ b/arch/arm/mm/cache-v6.S @@ -203,7 +203,7 @@ ENTRY(v6_flush_kern_dcache_area) * - start - virtual start address of region * - end - virtual end address of region */ -v6_dma_inv_range: +ENTRY(v6_dma_inv_range) #ifdef CONFIG_DMA_CACHE_RWFO ldrb r2, [r0] @ read for ownership strb r2, [r0] @ write for ownership @@ -248,7 +248,7 @@ v6_dma_inv_range: * - start - virtual start address of region * - end - virtual end address of region */ -v6_dma_clean_range: +ENTRY(v6_dma_clean_range) bic r0, r0, #D_CACHE_LINE_SIZE - 1 1: #ifdef CONFIG_DMA_CACHE_RWFO @@ -358,5 +358,7 @@ ENTRY(v6_cache_fns) .long v6_flush_kern_dcache_area .long v6_dma_map_area .long v6_dma_unmap_area + .long v6_dma_inv_range + .long v6_dma_clean_range .long v6_dma_flush_range .size v6_cache_fns, . - v6_cache_fns diff --git a/arch/arm/mm/cache-v7.S b/arch/arm/mm/cache-v7.S index d32f02b6186..0db20922459 100644 --- a/arch/arm/mm/cache-v7.S +++ b/arch/arm/mm/cache-v7.S @@ -242,7 +242,7 @@ ENDPROC(v7_flush_kern_dcache_area) * - start - virtual start address of region * - end - virtual end address of region */ -v7_dma_inv_range: +ENTRY(v7_dma_inv_range) dcache_line_size r2, r3 sub r3, r2, #1 tst r0, r3 @@ -266,7 +266,7 @@ ENDPROC(v7_dma_inv_range) * - start - virtual start address of region * - end - virtual end address of region */ -v7_dma_clean_range: +ENTRY(v7_dma_clean_range) dcache_line_size r2, r3 sub r3, r2, #1 bic r0, r0, r3 @@ -336,5 +336,7 @@ ENTRY(v7_cache_fns) .long v7_flush_kern_dcache_area .long v7_dma_map_area .long v7_dma_unmap_area + .long v7_dma_inv_range + .long v7_dma_clean_range .long v7_dma_flush_range .size v7_cache_fns, . - v7_cache_fns diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c index 82a093cee09..4fc0e3f4f19 100644 --- a/arch/arm/mm/dma-mapping.c +++ b/arch/arm/mm/dma-mapping.c @@ -429,18 +429,22 @@ EXPORT_SYMBOL(dma_free_coherent); void ___dma_single_cpu_to_dev(const void *kaddr, size_t size, enum dma_data_direction dir) { +#ifdef CONFIG_OUTER_CACHE unsigned long paddr; BUG_ON(!virt_addr_valid(kaddr) || !virt_addr_valid(kaddr + size - 1)); +#endif dmac_map_area(kaddr, size, dir); +#ifdef CONFIG_OUTER_CACHE paddr = __pa(kaddr); if (dir == DMA_FROM_DEVICE) { outer_inv_range(paddr, paddr + size); } else { outer_clean_range(paddr, paddr + size); } +#endif /* FIXME: non-speculating: flush on bidirectional mappings? */ } EXPORT_SYMBOL(___dma_single_cpu_to_dev); @@ -448,6 +452,7 @@ EXPORT_SYMBOL(___dma_single_cpu_to_dev); void ___dma_single_dev_to_cpu(const void *kaddr, size_t size, enum dma_data_direction dir) { +#ifdef CONFIG_OUTER_CACHE BUG_ON(!virt_addr_valid(kaddr) || !virt_addr_valid(kaddr + size - 1)); /* FIXME: non-speculating: not required */ @@ -456,7 +461,7 @@ void ___dma_single_dev_to_cpu(const void *kaddr, size_t size, unsigned long paddr = __pa(kaddr); outer_inv_range(paddr, paddr + size); } - +#endif dmac_unmap_area(kaddr, size, dir); } EXPORT_SYMBOL(___dma_single_dev_to_cpu); diff --git a/arch/arm/mm/emulate_domain_manager-v7.c b/arch/arm/mm/emulate_domain_manager-v7.c new file mode 100644 index 00000000000..3797e211a4d --- /dev/null +++ b/arch/arm/mm/emulate_domain_manager-v7.c @@ -0,0 +1,345 @@ +/* + * Basic implementation of a SW emulation of the domain manager feature in + * ARM architecture. Assumes single processor ARMv7 chipset. + * + * Requires hooks to be alerted to any runtime changes of dacr or MMU context. + * + * Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#define DOMAIN_MANAGER_BITS (0xAAAAAAAA) + +#define DFSR_DOMAIN(dfsr) ((dfsr >> 4) & (16-1)) + +#define FSR_PERMISSION_FAULT(fsr) ((fsr & 0x40D) == 0x00D) +#define FSR_PERMISSION_SECT(fsr) ((fsr & 0x40F) == 0x00D) + +/* ARMv7 MMU HW Macros. Not conveniently defined elsewhere */ +#define MMU_TTB_ADDRESS(x) ((u32 *)(((u32)(x)) & ~((1 << 14) - 1))) +#define MMU_PMD_INDEX(addr) (((u32)addr) >> SECTION_SHIFT) +#define MMU_TABLE_ADDRESS(x) ((u32 *)((x) & ~((1 << 10) - 1))) +#define MMU_TABLE_INDEX(x) ((((u32)x) >> 12) & (256 - 1)) + +/* Convenience Macros */ +#define PMD_IS_VALID(x) (PMD_IS_TABLE(x) || PMD_IS_SECTION(x)) +#define PMD_IS_TABLE(x) ((x & PMD_TYPE_MASK) == PMD_TYPE_TABLE) +#define PMD_IS_SECTION(x) ((x & PMD_TYPE_MASK) == PMD_TYPE_SECT) +#define PMD_IS_SUPERSECTION(x) \ + (PMD_IS_SECTION(x) && ((x & PMD_SECT_SUPER) == PMD_SECT_SUPER)) + +#define PMD_GET_DOMAIN(x) \ + (PMD_IS_TABLE(x) || \ + (PMD_IS_SECTION(x) && !PMD_IS_SUPERSECTION(x)) ? \ + 0 : (x >> 5) & (16-1)) + +#define PTE_IS_LARGE(x) ((x & PTE_TYPE_MASK) == PTE_TYPE_LARGE) + + +/* Only DOMAIN_MMU_ENTRIES will be granted access simultaneously */ +#define DOMAIN_MMU_ENTRIES (8) + +#define LRU_INC(lru) ((lru + 1) >= DOMAIN_MMU_ENTRIES ? 0 : lru + 1) + + +static DEFINE_SPINLOCK(edm_lock); + +static u32 edm_manager_bits; + +struct domain_entry_save { + u32 *mmu_entry; + u32 *addr; + u32 value; + u16 sect; + u16 size; +}; + +static struct domain_entry_save edm_save[DOMAIN_MMU_ENTRIES]; + +static u32 edm_lru; + + +/* + * Return virtual address of pmd (level 1) entry for addr + * + * This routine walks the ARMv7 page tables in HW. + */ +static inline u32 *__get_pmd_v7(u32 *addr) +{ + u32 *ttb; + + __asm__ __volatile__( + "mrc p15, 0, %0, c2, c0, 0 @ ttbr0\n\t" + : "=r" (ttb) + : + ); + + return __va(MMU_TTB_ADDRESS(ttb) + MMU_PMD_INDEX(addr)); +} + +/* + * Return virtual address of pte (level 2) entry for addr + * + * This routine walks the ARMv7 page tables in HW. + */ +static inline u32 *__get_pte_v7(u32 *addr) +{ + u32 *pmd = __get_pmd_v7(addr); + u32 *table_pa = pmd && PMD_IS_TABLE(*pmd) ? + MMU_TABLE_ADDRESS(*pmd) : 0; + u32 *entry = table_pa ? __va(table_pa[MMU_TABLE_INDEX(addr)]) : 0; + + return entry; +} + +/* + * Invalidate the TLB for a given address for the current context + * + * After manipulating access permissions, TLB invalidation changes are + * observed + */ +static inline void __tlb_invalidate(u32 *addr) +{ + __asm__ __volatile__( + "mrc p15, 0, %%r2, c13, c0, 1 @ contextidr\n\t" + "and %%r2, %%r2, #0xff @ asid\n\t" + "mov %%r3, %0, lsr #12 @ mva[31:12]\n\t" + "orr %%r2, %%r2, %%r3, lsl #12 @ tlb mva and asid\n\t" + "mcr p15, 0, %%r2, c8, c7, 1 @ utlbimva\n\t" + "isb" + : + : "r" (addr) + : "r2", "r3" + ); +} + +/* + * Set HW MMU entry and do required synchronization operations. + */ +static inline void __set_entry(u32 *entry, u32 *addr, u32 value, int size) +{ + int i; + + if (!entry) + return; + + entry = (u32 *)((u32) entry & ~(size * sizeof(u32) - 1)); + + for (i = 0; i < size; i++) + entry[i] = value; + + __asm__ __volatile__( + "mcr p15, 0, %0, c7, c10, 1 @ flush entry\n\t" + "dsb\n\t" + "isb\n\t" + : + : "r" (entry) + ); + __tlb_invalidate(addr); +} + +/* + * Return the number of duplicate entries associated with entry value. + * Supersections and Large page table entries are replicated 16x. + */ +static inline int __entry_size(int sect, int value) +{ + u32 size; + + if (sect) + size = PMD_IS_SUPERSECTION(value) ? 16 : 1; + else + size = PTE_IS_LARGE(value) ? 16 : 1; + + return size; +} + +/* + * Change entry permissions to emulate domain manager access + */ +static inline int __manager_perm(int sect, int value) +{ + u32 edm_value; + + if (sect) { + edm_value = (value & ~(PMD_SECT_APX | PMD_SECT_XN)) | + (PMD_SECT_AP_READ | PMD_SECT_AP_WRITE); + } else { + edm_value = (value & ~(PTE_EXT_APX | PTE_EXT_XN)) | + (PTE_EXT_AP1 | PTE_EXT_AP0); + } + return edm_value; +} + +/* + * Restore original HW MMU entry. Cancels domain manager access + */ +static inline void __restore_entry(int index) +{ + struct domain_entry_save *entry = &edm_save[index]; + u32 edm_value; + + if (!entry->mmu_entry) + return; + + edm_value = __manager_perm(entry->sect, entry->value); + + if (*entry->mmu_entry == edm_value) + __set_entry(entry->mmu_entry, entry->addr, + entry->value, entry->size); + + entry->mmu_entry = 0; +} + +/* + * Modify HW MMU entry to grant domain manager access for a given MMU entry. + * This adds full read, write, and exec access permissions. + */ +static inline void __set_manager(int sect, u32 *addr) +{ + u32 *entry = sect ? __get_pmd_v7(addr) : __get_pte_v7(addr); + u32 value; + u32 edm_value; + u16 size; + + if (!entry) + return; + + value = *entry; + + size = __entry_size(sect, value); + edm_value = __manager_perm(sect, value); + + __set_entry(entry, addr, edm_value, size); + + __restore_entry(edm_lru); + + edm_save[edm_lru].mmu_entry = entry; + edm_save[edm_lru].addr = addr; + edm_save[edm_lru].value = value; + edm_save[edm_lru].sect = sect; + edm_save[edm_lru].size = size; + + edm_lru = LRU_INC(edm_lru); +} + +/* + * Restore original HW MMU entries. + * + * entry - MVA for HW MMU entry + */ +static inline void __restore(void) +{ + if (unlikely(edm_manager_bits)) { + u32 i; + + for (i = 0; i < DOMAIN_MMU_ENTRIES; i++) + __restore_entry(i); + } +} + +/* + * Common abort handler code + * + * If domain manager was actually set, permission fault would not happen. + * Open access permissions to emulate. Save original settings to restore + * later. Return 1 to pretend fault did not happen. + */ +static int __emulate_domain_manager_abort(u32 fsr, u32 far, int dabort) +{ + if (unlikely(FSR_PERMISSION_FAULT(fsr) && edm_manager_bits)) { + int domain = dabort ? DFSR_DOMAIN(fsr) : PMD_GET_DOMAIN(far); + if (edm_manager_bits & domain_val(domain, DOMAIN_MANAGER)) { + unsigned long flags; + + spin_lock_irqsave(&edm_lock, flags); + + __set_manager(FSR_PERMISSION_SECT(fsr), (u32 *) far); + + spin_unlock_irqrestore(&edm_lock, flags); + return 1; + } + } + return 0; +} + +/* + * Change domain setting. + * + * Lock and restore original contents. Extract and save manager bits. Set + * DACR, excluding manager bits. + */ +void emulate_domain_manager_set(u32 domain) +{ + unsigned long flags; + + spin_lock_irqsave(&edm_lock, flags); + + if (edm_manager_bits != (domain & DOMAIN_MANAGER_BITS)) { + __restore(); + edm_manager_bits = domain & DOMAIN_MANAGER_BITS; + } + + __asm__ __volatile__( + "mcr p15, 0, %0, c3, c0, 0 @ set domain\n\t" + "isb" + : + : "r" (domain & ~DOMAIN_MANAGER_BITS) + ); + + spin_unlock_irqrestore(&edm_lock, flags); +} +EXPORT_SYMBOL_GPL(emulate_domain_manager_set); + +/* + * Switch thread context. Restore original contents. + */ +void emulate_domain_manager_switch_mm(unsigned long pgd_phys, + struct mm_struct *mm, + void (*switch_mm)(unsigned long pgd_phys, struct mm_struct *)) +{ + unsigned long flags; + + spin_lock_irqsave(&edm_lock, flags); + + __restore(); + + /* Call underlying kernel handler */ + switch_mm(pgd_phys, mm); + + spin_unlock_irqrestore(&edm_lock, flags); +} +EXPORT_SYMBOL_GPL(emulate_domain_manager_switch_mm); + +/* + * Kernel data_abort hook + */ +int emulate_domain_manager_data_abort(u32 dfsr, u32 dfar) +{ + return __emulate_domain_manager_abort(dfsr, dfar, 1); +} +EXPORT_SYMBOL_GPL(emulate_domain_manager_data_abort); + +/* + * Kernel prefetch_abort hook + */ +int emulate_domain_manager_prefetch_abort(u32 ifsr, u32 ifar) +{ + return __emulate_domain_manager_abort(ifsr, ifar, 0); +} +EXPORT_SYMBOL_GPL(emulate_domain_manager_prefetch_abort); + diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index bc0e1d88fd3..b6850fedf62 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c @@ -23,6 +23,14 @@ #include #include #include +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) +#include +#include +#endif + +#ifdef CONFIG_EMULATE_DOMAIN_MANAGER_V7 +#include +#endif /* CONFIG_EMULATE_DOMAIN_MANAGER_V7 */ #include "fault.h" @@ -484,6 +492,49 @@ do_bad(unsigned long addr, unsigned int fsr, struct pt_regs *regs) return 1; } +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) +#define __str(x) #x +#define MRC(x, v1, v2, v4, v5, v6) do { \ + unsigned int __##x; \ + asm("mrc " __str(v1) ", " __str(v2) ", %0, " __str(v4) ", " \ + __str(v5) ", " __str(v6) "\n" \ + : "=r" (__##x)); \ + pr_info("%s: %s = 0x%.8x\n", __func__, #x, __##x); \ +} while(0) + +#define MSM_TCSR_SPARE2 (MSM_TCSR_BASE + 0x60) + +#endif + +static int +do_imprecise_ext(unsigned long addr, unsigned int fsr, struct pt_regs *regs) +{ +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) + MRC(ADFSR, p15, 0, c5, c1, 0); + MRC(DFSR, p15, 0, c5, c0, 0); + MRC(ACTLR, p15, 0, c1, c0, 1); + MRC(EFSR, p15, 7, c15, c0, 1); + MRC(L2SR, p15, 3, c15, c1, 0); + MRC(L2CR0, p15, 3, c15, c0, 1); + MRC(L2CPUESR, p15, 3, c15, c1, 1); + MRC(L2CPUCR, p15, 3, c15, c0, 2); + MRC(SPESR, p15, 1, c9, c7, 0); + MRC(SPCR, p15, 0, c9, c7, 0); + MRC(DMACHSR, p15, 1, c11, c0, 0); + MRC(DMACHESR, p15, 1, c11, c0, 1); + MRC(DMACHCR, p15, 0, c11, c0, 2); + + /* clear out EFSR and ADFSR after fault */ + asm volatile ("mcr p15, 7, %0, c15, c0, 1\n\t" + "mcr p15, 0, %0, c5, c1, 0" + : : "r" (0)); +#endif +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) + pr_info("%s: TCSR_SPARE2 = 0x%.8x\n", __func__, readl(MSM_TCSR_SPARE2)); +#endif + return 1; +} + static struct fsr_info { int (*fn)(unsigned long addr, unsigned int fsr, struct pt_regs *regs); int sig; @@ -521,7 +572,7 @@ static struct fsr_info { { do_bad, SIGBUS, 0, "unknown 19" }, { do_bad, SIGBUS, 0, "lock abort" }, /* xscale */ { do_bad, SIGBUS, 0, "unknown 21" }, - { do_bad, SIGBUS, BUS_OBJERR, "imprecise external abort" }, /* xscale */ + { do_imprecise_ext, SIGBUS, BUS_OBJERR, "imprecise external abort" }, /* xscale */ { do_bad, SIGBUS, 0, "unknown 23" }, { do_bad, SIGBUS, 0, "dcache parity error" }, /* xscale */ { do_bad, SIGBUS, 0, "unknown 25" }, @@ -555,6 +606,11 @@ do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) const struct fsr_info *inf = fsr_info + fsr_fs(fsr); struct siginfo info; +#ifdef CONFIG_EMULATE_DOMAIN_MANAGER_V7 + if (emulate_domain_manager_data_abort(fsr, addr)) + return; +#endif + if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs)) return; @@ -623,6 +679,11 @@ do_PrefetchAbort(unsigned long addr, unsigned int ifsr, struct pt_regs *regs) const struct fsr_info *inf = ifsr_info + fsr_fs(ifsr); struct siginfo info; +#ifdef CONFIG_EMULATE_DOMAIN_MANAGER_V7 + if (emulate_domain_manager_prefetch_abort(ifsr, addr)) + return; +#endif + if (!inf->fn(addr, ifsr | FSR_LNX_PF, regs)) return; diff --git a/arch/arm/mm/init.c b/arch/arm/mm/init.c index c19571c40a2..9afb93a0e53 100644 --- a/arch/arm/mm/init.c +++ b/arch/arm/mm/init.c @@ -20,6 +20,9 @@ #include #include #include +#ifdef CONFIG_MEMORY_HOTPLUG +#include +#endif #include #include @@ -362,6 +365,46 @@ void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc) memblock_dump_all(); } +#ifdef CONFIG_MEMORY_HOTPLUG_SPARSE +int _early_pfn_valid(unsigned long pfn) +{ + struct meminfo *mi = &meminfo; + unsigned int left = 0, right = mi->nr_banks; + + do { + unsigned int mid = (right + left) / 2; + struct membank *bank = &mi->bank[mid]; + + if (pfn < bank_pfn_start(bank)) + right = mid; + else if (pfn >= bank_pfn_end(bank)) + left = mid + 1; + else + return 1; + } while (left < right); + return 0; +} +EXPORT_SYMBOL(_early_pfn_valid); +#endif + +#ifdef CONFIG_MEMORY_HOTPLUG +static void map_reserved_memory(void) +{ + struct map_desc map; + + map.pfn = (movable_reserved_start >> PAGE_SHIFT); + map.virtual = __phys_to_virt(movable_reserved_start); + map.length = movable_reserved_size; +#ifdef CONFIG_STRICT_MEMORY_RWX + map.type = MT_MEMORY_RW; +#else + map.type = MT_MEMORY; +#endif + + create_mapping(&map); +} +#endif + void __init bootmem_init(void) { unsigned long min, max_low, max_high; @@ -390,6 +433,13 @@ void __init bootmem_init(void) */ arm_bootmem_free(min, max_low, max_high); +#ifdef CONFIG_MEMORY_HOTPLUG + if (movable_reserved_size) { + max_low = (movable_reserved_start + movable_reserved_size) + >> PAGE_SHIFT; + map_reserved_memory(); + } +#endif high_memory = __va(((phys_addr_t)max_low << PAGE_SHIFT) - 1) + 1; /* @@ -715,6 +765,46 @@ void free_initmem(void) "init"); } +#ifdef CONFIG_MEMORY_HOTPLUG +int arch_add_memory(int nid, u64 start, u64 size) +{ + struct pglist_data *pgdata = NODE_DATA(nid); + struct zone *zone = pgdata->node_zones + ZONE_MOVABLE; + unsigned long start_pfn = start >> PAGE_SHIFT; + unsigned long nr_pages = size >> PAGE_SHIFT; + int ret; + + ret = __add_pages(nid, zone, start_pfn, nr_pages); + if (ret) + return ret; + return platform_physical_active_pages(start_pfn, nr_pages); +} + +int arch_physical_active_memory(u64 start, u64 size) +{ + unsigned long start_pfn = start >> PAGE_SHIFT; + unsigned long nr_pages = size >> PAGE_SHIFT; + + return platform_physical_active_pages(start_pfn, nr_pages); +} + +int arch_physical_remove_memory(u64 start, u64 size) +{ + unsigned long start_pfn = start >> PAGE_SHIFT; + unsigned long nr_pages = size >> PAGE_SHIFT; + + return platform_physical_remove_pages(start_pfn, nr_pages); +} + +int arch_physical_low_power_memory(u64 start, u64 size) +{ + unsigned long start_pfn = start >> PAGE_SHIFT; + unsigned long nr_pages = size >> PAGE_SHIFT; + + return platform_physical_low_power_pages(start_pfn, nr_pages); +} +#endif + #ifdef CONFIG_BLK_DEV_INITRD static int keep_initrd; diff --git a/arch/arm/mm/ioremap.c b/arch/arm/mm/ioremap.c index ab506272b2d..17e7b0b57e4 100644 --- a/arch/arm/mm/ioremap.c +++ b/arch/arm/mm/ioremap.c @@ -204,8 +204,12 @@ void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn, /* * Don't allow RAM to be mapped - this causes problems with ARMv6+ */ - if (WARN_ON(pfn_valid(pfn))) - return NULL; + if (pfn_valid(pfn)) { + printk(KERN_WARNING "BUG: Your driver calls ioremap() on system memory. This leads\n" + KERN_WARNING "to architecturally unpredictable behaviour on ARMv6+, and ioremap()\n" + KERN_WARNING "will fail in the next kernel release. Please fix your driver.\n"); + WARN_ON(1); + } type = get_mem_type(mtype); if (!type) diff --git a/arch/arm/mm/mm.h b/arch/arm/mm/mm.h index 5b3d7d54365..cd9eb2c5d48 100644 --- a/arch/arm/mm/mm.h +++ b/arch/arm/mm/mm.h @@ -23,5 +23,8 @@ extern void __flush_dcache_page(struct address_space *mapping, struct page *page #endif +struct map_desc; + void __init bootmem_init(void); void arm_mm_memblock_reserve(void); +void __init create_mapping(struct map_desc *md); diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c index 594d677b92c..fb48d3061b2 100644 --- a/arch/arm/mm/mmu.c +++ b/arch/arm/mm/mmu.c @@ -212,6 +212,12 @@ static struct mem_type mem_types[] = { .prot_sect = PROT_SECT_DEVICE | PMD_SECT_WB, .domain = DOMAIN_IO, }, + [MT_DEVICE_STRONGLY_ORDERED] = { /* Guaranteed strongly ordered */ + .prot_pte = PROT_PTE_DEVICE, + .prot_l1 = PMD_TYPE_TABLE, + .prot_sect = PROT_SECT_DEVICE | PMD_SECT_UNCACHED, + .domain = DOMAIN_IO, + }, [MT_DEVICE_WC] = { /* ioremap_wc */ .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_WC, .prot_l1 = PMD_TYPE_TABLE, @@ -250,6 +256,18 @@ static struct mem_type mem_types[] = { .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE, .domain = DOMAIN_KERNEL, }, + [MT_MEMORY_R] = { + .prot_sect = PMD_TYPE_SECT | PMD_SECT_XN, + .domain = DOMAIN_KERNEL, + }, + [MT_MEMORY_RW] = { + .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_XN, + .domain = DOMAIN_KERNEL, + }, + [MT_MEMORY_RX] = { + .prot_sect = PMD_TYPE_SECT, + .domain = DOMAIN_KERNEL, + }, [MT_ROM] = { .prot_sect = PMD_TYPE_SECT, .domain = DOMAIN_KERNEL, @@ -353,6 +371,8 @@ static void __init build_mem_type_table(void) mem_types[MT_DEVICE_NONSHARED].prot_sect |= PMD_SECT_XN; mem_types[MT_DEVICE_CACHED].prot_sect |= PMD_SECT_XN; mem_types[MT_DEVICE_WC].prot_sect |= PMD_SECT_XN; + mem_types[MT_DEVICE_STRONGLY_ORDERED].prot_sect |= + PMD_SECT_XN; } if (cpu_arch >= CPU_ARCH_ARMv7 && (cr & CR_TRE)) { /* @@ -426,6 +446,8 @@ static void __init build_mem_type_table(void) * from SVC mode and no access from userspace. */ mem_types[MT_ROM].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE; + mem_types[MT_MEMORY_RX].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE; + mem_types[MT_MEMORY_R].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE; mem_types[MT_MINICLEAN].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE; mem_types[MT_CACHECLEAN].prot_sect |= PMD_SECT_APX|PMD_SECT_AP_WRITE; @@ -444,6 +466,9 @@ static void __init build_mem_type_table(void) mem_types[MT_MEMORY].prot_sect |= PMD_SECT_S; mem_types[MT_MEMORY].prot_pte |= L_PTE_SHARED; mem_types[MT_MEMORY_NONCACHED].prot_sect |= PMD_SECT_S; + mem_types[MT_MEMORY_R].prot_sect |= PMD_SECT_S; + mem_types[MT_MEMORY_RW].prot_sect |= PMD_SECT_S; + mem_types[MT_MEMORY_RX].prot_sect |= PMD_SECT_S; mem_types[MT_MEMORY_NONCACHED].prot_pte |= L_PTE_SHARED; } } @@ -483,6 +508,9 @@ static void __init build_mem_type_table(void) mem_types[MT_MEMORY].prot_sect |= ecc_mask | cp->pmd; mem_types[MT_MEMORY].prot_pte |= kern_pgprot; mem_types[MT_MEMORY_NONCACHED].prot_sect |= ecc_mask; + mem_types[MT_MEMORY_R].prot_sect |= ecc_mask | cp->pmd; + mem_types[MT_MEMORY_RW].prot_sect |= ecc_mask | cp->pmd; + mem_types[MT_MEMORY_RX].prot_sect |= ecc_mask | cp->pmd; mem_types[MT_ROM].prot_sect |= cp->pmd; switch (cp->pmd) { @@ -662,7 +690,7 @@ static void __init create_36bit_mapping(struct map_desc *md, * offsets, and we take full advantage of sections and * supersections. */ -static void __init create_mapping(struct map_desc *md) +void __init create_mapping(struct map_desc *md) { unsigned long addr, length, end; phys_addr_t phys; @@ -727,7 +755,7 @@ void __init iotable_init(struct map_desc *io_desc, int nr) create_mapping(io_desc + i); } -static void * __initdata vmalloc_min = (void *)(VMALLOC_END - SZ_128M); +static void * __initdata vmalloc_min = (void *)(VMALLOC_END - CONFIG_VMALLOC_RESERVE); /* * vmalloc=size forces the vmalloc area to be exactly 'size' @@ -1049,4 +1077,16 @@ void __init paging_init(struct machine_desc *mdesc) empty_zero_page = virt_to_page(zero_page); __flush_dcache_page(NULL, empty_zero_page); + +#if defined(CONFIG_ARCH_MSM7X27) + /* + * ensure that the strongly ordered page is mapped before the + * first call to write_to_strongly_ordered_memory. This page + * is necessary for the msm 7x27 due to hardware quirks. The + * map call is made here to ensure the bootmem call is made + * in the right window (after initialization, before full + * allocators are initialized) + */ + map_page_strongly_ordered(); +#endif } diff --git a/arch/arm/mm/proc-arm1026.S b/arch/arm/mm/proc-arm1026.S index 413684660aa..dfeb57f33af 100644 --- a/arch/arm/mm/proc-arm1026.S +++ b/arch/arm/mm/proc-arm1026.S @@ -251,7 +251,7 @@ ENTRY(arm1026_flush_kern_dcache_area) * * (same as v4wb) */ -arm1026_dma_inv_range: +ENTRY(arm1026_dma_inv_range) mov ip, #0 #ifndef CONFIG_CPU_DCACHE_DISABLE tst r0, #CACHE_DLINESIZE - 1 @@ -277,7 +277,7 @@ arm1026_dma_inv_range: * * (same as v4wb) */ -arm1026_dma_clean_range: +ENTRY(arm1026_dma_clean_range) mov ip, #0 #ifndef CONFIG_CPU_DCACHE_DISABLE bic r0, r0, #CACHE_DLINESIZE - 1 @@ -343,6 +343,8 @@ ENTRY(arm1026_cache_fns) .long arm1026_flush_kern_dcache_area .long arm1026_dma_map_area .long arm1026_dma_unmap_area + .long arm1026_dma_inv_range + .long arm1026_dma_clean_range .long arm1026_dma_flush_range .align 5 diff --git a/arch/arm/mm/proc-v7.S b/arch/arm/mm/proc-v7.S index 089c0b5e454..449d46338ce 100644 --- a/arch/arm/mm/proc-v7.S +++ b/arch/arm/mm/proc-v7.S @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -61,7 +62,14 @@ ENDPROC(cpu_v7_proc_fin) */ .align 5 ENTRY(cpu_v7_reset) - mov pc, r0 + mrc p15, 0, r1, c1, c0, 0 @ ctrl register + bic r1, r1, #0x0001 @ ...............m + mcr p15, 0, r1, c1, c0, 0 @ Turn off MMU + mcr p15, 0, ip, c8, c7, 0 @ invalidate I & D,flush TLB + mcr p15, 0, ip, c7, c5, 6 @ flush BTC + dsb + isb + mov pc,r0 ENDPROC(cpu_v7_reset) /* @@ -101,6 +109,11 @@ ENDPROC(cpu_v7_dcache_clean_area) */ ENTRY(cpu_v7_switch_mm) #ifdef CONFIG_MMU +#ifdef CONFIG_EMULATE_DOMAIN_MANAGER_V7 + ldr r2, =cpu_v7_switch_mm_private + b emulate_domain_manager_switch_mm +cpu_v7_switch_mm_private: +#endif mov r2, #0 ldr r1, [r1, #MM_CONTEXT_ID] @ get mm->context.id ALT_SMP(orr r0, r0, #TTB_FLAGS_SMP) @@ -280,9 +293,8 @@ cpu_resume_l1_flags: * - cache type register is implemented */ __v7_ca9mp_setup: -#ifdef CONFIG_SMP - ALT_SMP(mrc p15, 0, r0, c1, c0, 1) - ALT_UP(mov r0, #(1 << 6)) @ fake it for UP +#if defined(CONFIG_SMP) + mrc p15, 0, r0, c1, c0, 1 tst r0, #(1 << 6) @ SMP/nAMP mode enabled? orreq r0, r0, #(1 << 6) | (1 << 0) @ Enable SMP/nAMP mode and mcreq p15, 0, r0, c1, c0, 1 @ TLB ops broadcasting @@ -375,6 +387,35 @@ __v7_setup: ALT_SMP(orr r8, r8, #TTB_FLAGS_SMP) ALT_UP(orr r8, r8, #TTB_FLAGS_UP) mcr p15, 0, r8, c2, c0, 1 @ load TTB1 +#ifndef CONFIG_EMULATE_DOMAIN_MANAGER_V7 + mov r10, #0x1f @ domains 0, 1 = manager + mcr p15, 0, r10, c3, c0, 0 @ load domain access register +#endif +#if defined(CONFIG_ARCH_MSM_SCORPION) && !defined(CONFIG_MSM_SMP) + mov r0, #0x33 + mcr p15, 3, r0, c15, c0, 3 @ set L2CR1 +#endif +#if defined (CONFIG_ARCH_MSM_SCORPION) + mrc p15, 0, r0, c1, c0, 1 @ read ACTLR +#ifdef CONFIG_CPU_CACHE_ERR_REPORT + orr r0, r0, #0x37 @ turn on L1/L2 error reporting +#else + bic r0, r0, #0x37 +#endif +#if defined (CONFIG_ARCH_MSM_SCORPIONMP) + orr r0, r0, #0x1 << 24 @ optimal setting for Scorpion MP +#endif +#ifndef CONFIG_ARCH_MSM_KRAIT + mcr p15, 0, r0, c1, c0, 1 @ write ACTLR +#endif +#endif + +#if defined (CONFIG_ARCH_MSM_SCORPIONMP) + mrc p15, 3, r0, c15, c0, 2 @ optimal setting for Scorpion MP + orr r0, r0, #0x1 << 21 + mcr p15, 3, r0, c15, c0, 2 +#endif + ldr r5, =PRRR @ PRRR ldr r6, =NMRR @ NMRR mcr p15, 0, r5, c10, c2, 0 @ write PRRR diff --git a/arch/arm/mm/tlb-v7.S b/arch/arm/mm/tlb-v7.S index 53cd5b45467..8338c6e37d6 100644 --- a/arch/arm/mm/tlb-v7.S +++ b/arch/arm/mm/tlb-v7.S @@ -38,11 +38,19 @@ ENTRY(v7wbi_flush_user_tlb_range) dsb mov r0, r0, lsr #PAGE_SHIFT @ align address mov r1, r1, lsr #PAGE_SHIFT +#ifdef CONFIG_ARCH_MSM8X60 + mov r0, r0, lsl #PAGE_SHIFT +#else asid r3, r3 @ mask ASID orr r0, r3, r0, lsl #PAGE_SHIFT @ Create initial MVA +#endif mov r1, r1, lsl #PAGE_SHIFT 1: +#ifdef CONFIG_ARCH_MSM8X60 + ALT_SMP(mcr p15, 0, r0, c8, c3, 3) @ TLB invalidate U MVA (shareable) +#else ALT_SMP(mcr p15, 0, r0, c8, c3, 1) @ TLB invalidate U MVA (shareable) +#endif ALT_UP(mcr p15, 0, r0, c8, c7, 1) @ TLB invalidate U MVA add r0, r0, #PAGE_SZ @@ -70,7 +78,11 @@ ENTRY(v7wbi_flush_kern_tlb_range) mov r0, r0, lsl #PAGE_SHIFT mov r1, r1, lsl #PAGE_SHIFT 1: +#ifdef CONFIG_ARCH_MSM8X60 + ALT_SMP(mcr p15, 0, r0, c8, c3, 3) @ TLB invalidate U MVA (shareable) +#else ALT_SMP(mcr p15, 0, r0, c8, c3, 1) @ TLB invalidate U MVA (shareable) +#endif ALT_UP(mcr p15, 0, r0, c8, c7, 1) @ TLB invalidate U MVA add r0, r0, #PAGE_SZ cmp r0, r1 diff --git a/arch/arm/mm/vcm.c b/arch/arm/mm/vcm.c new file mode 100644 index 00000000000..5c52a9cd00e --- /dev/null +++ b/arch/arm/mm/vcm.c @@ -0,0 +1,1830 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +/* alloc_vm_area */ +#include +#include +#include + +#include +#include + +#define ONE_TO_ONE_CHK 1 + +#define vcm_err(a, ...) \ + pr_err("ERROR %s %i " a, __func__, __LINE__, ##__VA_ARGS__) + +static unsigned int smmu_map_sizes[4] = {SZ_16M, SZ_1M, SZ_64K, SZ_4K}; + +static phys_addr_t *bootmem_cont; +static int cont_sz; +static struct vcm *cont_vcm_id; +static struct phys_chunk *cont_phys_chunk; + +DEFINE_SPINLOCK(vcmlock); + +/* Leaving this in for now to keep compatibility of the API. */ +/* This will disappear. */ +phys_addr_t vcm_get_dev_addr(struct res *res) +{ + if (!res) { + vcm_err("NULL RES"); + return -EINVAL; + } + return res->dev_addr; +} + +static int vcm_no_res(struct vcm *vcm) +{ + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + return list_empty(&vcm->res_head); +fail: + return -EINVAL; +} + +static int vcm_no_assoc(struct vcm *vcm) +{ + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + return list_empty(&vcm->assoc_head); +fail: + return -EINVAL; +} + +static int vcm_all_activated(struct vcm *vcm) +{ + struct avcm *avcm; + + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + list_for_each_entry(avcm, &vcm->assoc_head, assoc_elm) + if (!avcm->is_active) + return 0; + + return 1; +fail: + return -EINVAL; +} + +static void vcm_destroy_common(struct vcm *vcm) +{ + if (!vcm) { + vcm_err("NULL vcm\n"); + return; + } + + memset(vcm, 0, sizeof(*vcm)); + kfree(vcm); +} + +static struct vcm *vcm_create_common(void) +{ + struct vcm *vcm = 0; + + vcm = kzalloc(sizeof(*vcm), GFP_KERNEL); + if (!vcm) { + vcm_err("kzalloc(%i, GFP_KERNEL) ret 0\n", + sizeof(*vcm)); + goto fail; + } + + INIT_LIST_HEAD(&vcm->res_head); + INIT_LIST_HEAD(&vcm->assoc_head); + + return vcm; + +fail: + return NULL; +} + + +static int vcm_create_pool(struct vcm *vcm, unsigned long start_addr, + size_t len) +{ + int ret = 0; + + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + vcm->start_addr = start_addr; + vcm->len = len; + + vcm->pool = gen_pool_create(PAGE_SHIFT, -1); + if (!vcm->pool) { + vcm_err("gen_pool_create(%x, -1) ret 0\n", PAGE_SHIFT); + ret = -EINVAL; + goto fail; + } + + ret = gen_pool_add(vcm->pool, start_addr, len, -1); + if (ret) { + vcm_err("gen_pool_add(%p, %p, %i, -1) ret %i\n", vcm->pool, + (void *) start_addr, len, ret); + goto fail; + } + + vcm->domain = iommu_domain_alloc(); + if (!vcm->domain) { + vcm_err("Could not allocate domain\n"); + ret = -ENOMEM; + goto fail; + } + +fail: + if (ret && vcm->pool) + gen_pool_destroy(vcm->pool); + + return ret; +} + + +static struct vcm *vcm_create_flagged(int flag, unsigned long start_addr, + size_t len) +{ + int ret = 0; + struct vcm *vcm = 0; + + vcm = vcm_create_common(); + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + /* special one-to-one mapping case */ + if ((flag & ONE_TO_ONE_CHK) && + bootmem_cont && + start_addr == (size_t) bootmem_cont && + len == cont_sz) { + vcm->type = VCM_ONE_TO_ONE; + } else { + ret = vcm_create_pool(vcm, start_addr, len); + vcm->type = VCM_DEVICE; + } + + if (ret) { + vcm_err("vcm_create_pool(%p, %p, %i) ret %i\n", vcm, + (void *) start_addr, len, ret); + goto fail2; + } + + return vcm; + +fail2: + vcm_destroy_common(vcm); +fail: + return NULL; +} + +struct vcm *vcm_create(unsigned long start_addr, size_t len) +{ + unsigned long flags; + struct vcm *vcm; + + spin_lock_irqsave(&vcmlock, flags); + vcm = vcm_create_flagged(ONE_TO_ONE_CHK, start_addr, len); + spin_unlock_irqrestore(&vcmlock, flags); + return vcm; +} + + +static int ext_vcm_id_valid(size_t ext_vcm_id) +{ + return ((ext_vcm_id == VCM_PREBUILT_KERNEL) || + (ext_vcm_id == VCM_PREBUILT_USER)); +} + + +struct vcm *vcm_create_from_prebuilt(size_t ext_vcm_id) +{ + unsigned long flags; + struct vcm *vcm = 0; + + spin_lock_irqsave(&vcmlock, flags); + + if (!ext_vcm_id_valid(ext_vcm_id)) { + vcm_err("ext_vcm_id_valid(%i) ret 0\n", ext_vcm_id); + goto fail; + } + + vcm = vcm_create_common(); + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + if (ext_vcm_id == VCM_PREBUILT_KERNEL) + vcm->type = VCM_EXT_KERNEL; + else if (ext_vcm_id == VCM_PREBUILT_USER) + vcm->type = VCM_EXT_USER; + else { + vcm_err("UNREACHABLE ext_vcm_id is illegal\n"); + goto fail_free; + } + + /* TODO: set kernel and userspace start_addr and len, if this + * makes sense */ + + spin_unlock_irqrestore(&vcmlock, flags); + return vcm; + +fail_free: + vcm_destroy_common(vcm); +fail: + spin_unlock_irqrestore(&vcmlock, flags); + return NULL; +} + + +struct vcm *vcm_clone(struct vcm *vcm) +{ + return 0; +} + + +/* No lock needed, vcm->start_addr is never updated after creation */ +size_t vcm_get_start_addr(struct vcm *vcm) +{ + if (!vcm) { + vcm_err("NULL vcm\n"); + return 1; + } + + return vcm->start_addr; +} + + +/* No lock needed, vcm->len is never updated after creation */ +size_t vcm_get_len(struct vcm *vcm) +{ + if (!vcm) { + vcm_err("NULL vcm\n"); + return 0; + } + + return vcm->len; +} + + +static int vcm_free_common_rule(struct vcm *vcm) +{ + int ret; + + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + ret = vcm_no_res(vcm); + if (!ret) { + vcm_err("vcm_no_res(%p) ret 0\n", vcm); + goto fail_busy; + } + + if (ret == -EINVAL) { + vcm_err("vcm_no_res(%p) ret -EINVAL\n", vcm); + goto fail; + } + + ret = vcm_no_assoc(vcm); + if (!ret) { + vcm_err("vcm_no_assoc(%p) ret 0\n", vcm); + goto fail_busy; + } + + if (ret == -EINVAL) { + vcm_err("vcm_no_assoc(%p) ret -EINVAL\n", vcm); + goto fail; + } + + return 0; + +fail_busy: + return -EBUSY; +fail: + return -EINVAL; +} + + +static int vcm_free_pool_rule(struct vcm *vcm) +{ + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + /* A vcm always has a valid pool, don't free the vcm because + what we got is probably invalid. + */ + if (!vcm->pool) { + vcm_err("NULL vcm->pool\n"); + goto fail; + } + + return 0; + +fail: + return -EINVAL; +} + + +static void vcm_free_common(struct vcm *vcm) +{ + memset(vcm, 0, sizeof(*vcm)); + + kfree(vcm); +} + + +static int vcm_free_pool(struct vcm *vcm) +{ + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + gen_pool_destroy(vcm->pool); + + return 0; + +fail: + return -EINVAL; +} + + +static int __vcm_free(struct vcm *vcm) +{ + int ret; + + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + ret = vcm_free_common_rule(vcm); + if (ret != 0) { + vcm_err("vcm_free_common_rule(%p) ret %i\n", vcm, ret); + goto fail; + } + + if (vcm->type == VCM_DEVICE) { + ret = vcm_free_pool_rule(vcm); + if (ret != 0) { + vcm_err("vcm_free_pool_rule(%p) ret %i\n", + (void *) vcm, ret); + goto fail; + } + if (vcm->domain) + iommu_domain_free(vcm->domain); + + vcm->domain = NULL; + ret = vcm_free_pool(vcm); + if (ret != 0) { + vcm_err("vcm_free_pool(%p) ret %i", (void *) vcm, ret); + goto fail; + } + } + + vcm_free_common(vcm); + + return 0; + +fail: + return -EINVAL; +} + +int vcm_free(struct vcm *vcm) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&vcmlock, flags); + ret = __vcm_free(vcm); + spin_unlock_irqrestore(&vcmlock, flags); + + return ret; +} + + +static struct res *__vcm_reserve(struct vcm *vcm, size_t len, u32 attr) +{ + struct res *res = NULL; + int align_attr = 0, i = 0; + + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + if (len == 0) { + vcm_err("len is 0\n"); + goto fail; + } + + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (!res) { + vcm_err("kzalloc(%i, GFP_KERNEL) ret 0", sizeof(*res)); + goto fail; + } + + align_attr = (attr >> VCM_ALIGN_SHIFT) & VCM_ALIGN_MASK; + + if (align_attr >= 32) { + vcm_err("Invalid alignment attribute: %d\n", align_attr); + goto fail2; + } + + INIT_LIST_HEAD(&res->res_elm); + res->vcm = vcm; + res->len = len; + res->attr = attr; + res->alignment_req = smmu_map_sizes[ARRAY_SIZE(smmu_map_sizes) - 1]; + + if (align_attr == 0) { + for (i = 0; i < ARRAY_SIZE(smmu_map_sizes); i++) + if (len / smmu_map_sizes[i]) { + res->alignment_req = smmu_map_sizes[i]; + break; + } + } else + res->alignment_req = 1 << align_attr; + + res->aligned_len = res->alignment_req + len; + + switch (vcm->type) { + case VCM_DEVICE: + /* should always be not zero */ + if (!vcm->pool) { + vcm_err("NULL vcm->pool\n"); + goto fail2; + } + + res->ptr = gen_pool_alloc(vcm->pool, res->aligned_len); + if (!res->ptr) { + vcm_err("gen_pool_alloc(%p, %i) ret 0\n", + vcm->pool, res->aligned_len); + goto fail2; + } + + /* Calculate alignment... this will all change anyway */ + res->dev_addr = res->ptr + + (res->alignment_req - + (res->ptr & (res->alignment_req - 1))); + + break; + case VCM_EXT_KERNEL: + res->vm_area = alloc_vm_area(res->aligned_len); + res->mapped = 0; /* be explicit */ + if (!res->vm_area) { + vcm_err("NULL res->vm_area\n"); + goto fail2; + } + + res->dev_addr = (size_t) res->vm_area->addr + + (res->alignment_req - + ((size_t) res->vm_area->addr & + (res->alignment_req - 1))); + + break; + case VCM_ONE_TO_ONE: + break; + default: + vcm_err("%i is an invalid vcm->type\n", vcm->type); + goto fail2; + } + + list_add_tail(&res->res_elm, &vcm->res_head); + + return res; + +fail2: + kfree(res); +fail: + return 0; +} + + +struct res *vcm_reserve(struct vcm *vcm, size_t len, u32 attr) +{ + unsigned long flags; + struct res *res; + + spin_lock_irqsave(&vcmlock, flags); + res = __vcm_reserve(vcm, len, attr); + spin_unlock_irqrestore(&vcmlock, flags); + + return res; +} + + +struct res *vcm_reserve_at(enum memtarget_t memtarget, struct vcm *vcm, + size_t len, u32 attr) +{ + return 0; +} + + +static int __vcm_unreserve(struct res *res) +{ + struct vcm *vcm; + + if (!res) { + vcm_err("NULL res\n"); + goto fail; + } + + if (!res->vcm) { + vcm_err("NULL res->vcm\n"); + goto fail; + } + + vcm = res->vcm; + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + switch (vcm->type) { + case VCM_DEVICE: + if (!res->vcm->pool) { + vcm_err("NULL (res->vcm))->pool\n"); + goto fail; + } + + /* res->ptr could be zero, this isn't an error */ + gen_pool_free(res->vcm->pool, res->ptr, + res->aligned_len); + break; + case VCM_EXT_KERNEL: + if (res->mapped) { + vcm_err("res->mapped is true\n"); + goto fail; + } + + /* This may take a little explaining. + * In the kernel vunmap will free res->vm_area + * so if we've called it then we shouldn't call + * free_vm_area(). If we've called it we set + * res->vm_area to 0. + */ + if (res->vm_area) { + free_vm_area(res->vm_area); + res->vm_area = 0; + } + + break; + case VCM_ONE_TO_ONE: + break; + default: + vcm_err("%i is an invalid vcm->type\n", vcm->type); + goto fail; + } + + list_del(&res->res_elm); + + /* be extra careful by clearing the memory before freeing it */ + memset(res, 0, sizeof(*res)); + + kfree(res); + + return 0; + +fail: + return -EINVAL; +} + + +int vcm_unreserve(struct res *res) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&vcmlock, flags); + ret = __vcm_unreserve(res); + spin_unlock_irqrestore(&vcmlock, flags); + + return ret; +} + + +/* No lock needed, res->len is never updated after creation */ +size_t vcm_get_res_len(struct res *res) +{ + if (!res) { + vcm_err("res is 0\n"); + return 0; + } + + return res->len; +} + + +int vcm_set_res_attr(struct res *res, u32 attr) +{ + return 0; +} + + +u32 vcm_get_res_attr(struct res *res) +{ + return 0; +} + + +size_t vcm_get_num_res(struct vcm *vcm) +{ + return 0; +} + + +struct res *vcm_get_next_res(struct vcm *vcm, struct res *res) +{ + return 0; +} + + +size_t vcm_res_copy(struct res *to, size_t to_off, struct res *from, size_t + from_off, size_t len) +{ + return 0; +} + + +size_t vcm_get_min_page_size(void) +{ + return PAGE_SIZE; +} + + +static int vcm_to_smmu_attr(u32 attr) +{ + int smmu_attr = 0; + + switch (attr & VCM_CACHE_POLICY) { + case VCM_NOTCACHED: + smmu_attr = VCM_DEV_ATTR_NONCACHED; + break; + case VCM_WB_WA: + smmu_attr = VCM_DEV_ATTR_CACHED_WB_WA; + smmu_attr |= VCM_DEV_ATTR_SH; + break; + case VCM_WB_NWA: + smmu_attr = VCM_DEV_ATTR_CACHED_WB_NWA; + smmu_attr |= VCM_DEV_ATTR_SH; + break; + case VCM_WT: + smmu_attr = VCM_DEV_ATTR_CACHED_WT; + smmu_attr |= VCM_DEV_ATTR_SH; + break; + default: + return -EINVAL; + } + + return smmu_attr; +} + + +static int vcm_process_chunk(struct iommu_domain *domain, phys_addr_t pa, + unsigned long va, size_t len, u32 attr, int map) +{ + int ret, i, map_order; + unsigned long map_len = smmu_map_sizes[ARRAY_SIZE(smmu_map_sizes) - 1]; + + for (i = 0; i < ARRAY_SIZE(smmu_map_sizes); i++) { + if (IS_ALIGNED(va, smmu_map_sizes[i]) && len >= + smmu_map_sizes[i]) { + map_len = smmu_map_sizes[i]; + break; + } + } + +#ifdef VCM_PERF_DEBUG + if (va & (len - 1)) + pr_warning("Warning! Suboptimal VCM mapping alignment " + "va = %p, len = %p. Expect TLB performance " + "degradation.\n", (void *) va, (void *) len); +#endif + + map_order = get_order(map_len); + + while (len) { + if (va & (SZ_4K - 1)) { + vcm_err("Tried to map w/ align < 4k! va = %08lx\n", va); + goto fail; + } + + if (map_len > len) { + vcm_err("map_len = %lu, len = %d, trying to overmap\n", + map_len, len); + goto fail; + } + + if (map) + ret = iommu_map(domain, va, pa, map_order, attr); + else + ret = iommu_unmap(domain, va, map_order); + + if (ret) { + vcm_err("iommu_map/unmap(%p, %p, %p, 0x%x, 0x%x) ret %i" + "map = %d", (void *) domain, (void *) pa, + (void *) va, (int) map_len, attr, ret, map); + goto fail; + } + + va += map_len; + pa += map_len; + len -= map_len; + } + + return 0; +fail: + return -EINVAL; +} + +/* TBD if you vcm_back again what happens? */ +int vcm_back(struct res *res, struct physmem *physmem) +{ + unsigned long flags; + struct vcm *vcm; + struct phys_chunk *chunk; + size_t va = 0; + int ret; + int attr; + + spin_lock_irqsave(&vcmlock, flags); + + if (!res) { + vcm_err("NULL res\n"); + goto fail; + } + + vcm = res->vcm; + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + switch (vcm->type) { + case VCM_DEVICE: + case VCM_EXT_KERNEL: /* hack part 1 */ + attr = vcm_to_smmu_attr(res->attr); + if (attr == -1) { + vcm_err("Bad SMMU attr\n"); + goto fail; + } + break; + default: + attr = 0; + break; + } + + if (!physmem) { + vcm_err("NULL physmem\n"); + goto fail; + } + + if (res->len == 0) { + vcm_err("res->len is 0\n"); + goto fail; + } + + if (physmem->len == 0) { + vcm_err("physmem->len is 0\n"); + goto fail; + } + + if (res->len != physmem->len) { + vcm_err("res->len (%i) != physmem->len (%i)\n", + res->len, physmem->len); + goto fail; + } + + if (physmem->is_cont) { + if (physmem->res == 0) { + vcm_err("cont physmem->res is 0"); + goto fail; + } + } else { + /* fail if no physmem */ + if (list_empty(&physmem->alloc_head.allocated)) { + vcm_err("no allocated phys memory"); + goto fail; + } + } + + ret = vcm_no_assoc(res->vcm); + if (ret == 1) { + vcm_err("can't back un associated VCM\n"); + goto fail; + } + + if (ret == -1) { + vcm_err("vcm_no_assoc() ret -1\n"); + goto fail; + } + + ret = vcm_all_activated(res->vcm); + if (ret == 0) { + vcm_err("can't back, not all associations are activated\n"); + goto fail_eagain; + } + + if (ret == -1) { + vcm_err("vcm_all_activated() ret -1\n"); + goto fail; + } + + va = res->dev_addr; + + list_for_each_entry(chunk, &physmem->alloc_head.allocated, + allocated) { + struct vcm *vcm = res->vcm; + size_t chunk_size = chunk->size; + + if (chunk_size <= 0) { + vcm_err("Bad chunk size: %d\n", chunk_size); + goto fail; + } + + switch (vcm->type) { + case VCM_DEVICE: + { + /* map all */ + ret = vcm_process_chunk(vcm->domain, chunk->pa, + va, chunk_size, attr, 1); + if (ret != 0) { + vcm_err("vcm_process_chunk(%p, %p, %p," + " 0x%x, 0x%x)" + " ret %i", + vcm->domain, + (void *) chunk->pa, + (void *) va, + (int) chunk_size, attr, ret); + goto fail; + } + break; + } + + case VCM_EXT_KERNEL: + { + unsigned int pages_in_chunk = chunk_size / PAGE_SIZE; + unsigned long loc_va = va; + unsigned long loc_pa = chunk->pa; + + const struct mem_type *mtype; + + /* TODO: get this based on MEMTYPE */ + mtype = get_mem_type(MT_DEVICE); + if (!mtype) { + vcm_err("mtype is 0\n"); + goto fail; + } + + /* TODO: Map with the same chunk size */ + while (pages_in_chunk--) { + ret = ioremap_page(loc_va, + loc_pa, + mtype); + if (ret != 0) { + vcm_err("ioremap_page(%p, %p, %p) ret" + " %i", (void *) loc_va, + (void *) loc_pa, + (void *) mtype, ret); + goto fail; + /* TODO handle weird + inter-map case */ + } + + /* hack part 2 */ + /* we're changing the PT entry behind + * linux's back + */ + ret = cpu_set_attr(loc_va, PAGE_SIZE, attr); + if (ret != 0) { + vcm_err("cpu_set_attr(%p, %lu, %x)" + "ret %i\n", + (void *) loc_va, PAGE_SIZE, + attr, ret); + goto fail; + /* TODO handle weird + inter-map case */ + } + + res->mapped = 1; + + loc_va += PAGE_SIZE; + loc_pa += PAGE_SIZE; + } + + flush_cache_vmap(va, loc_va); + break; + } + case VCM_ONE_TO_ONE: + va = chunk->pa; + break; + default: + /* this should never happen */ + goto fail; + } + + va += chunk_size; + /* also add res to the allocated chunk list of refs */ + } + + /* note the reservation */ + res->physmem = physmem; + + spin_unlock_irqrestore(&vcmlock, flags); + return 0; +fail_eagain: + spin_unlock_irqrestore(&vcmlock, flags); + return -EAGAIN; +fail: + spin_unlock_irqrestore(&vcmlock, flags); + return -EINVAL; +} + + +int vcm_unback(struct res *res) +{ + unsigned long flags; + struct vcm *vcm; + struct physmem *physmem; + int ret; + + spin_lock_irqsave(&vcmlock, flags); + + if (!res) + goto fail; + + vcm = res->vcm; + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + if (!res->physmem) { + vcm_err("can't unback a non-backed reservation\n"); + goto fail; + } + + physmem = res->physmem; + if (!physmem) { + vcm_err("physmem is NULL\n"); + goto fail; + } + + if (list_empty(&physmem->alloc_head.allocated)) { + vcm_err("physmem allocation is empty\n"); + goto fail; + } + + ret = vcm_no_assoc(res->vcm); + if (ret == 1) { + vcm_err("can't unback a unassociated reservation\n"); + goto fail; + } + + if (ret == -1) { + vcm_err("vcm_no_assoc(%p) ret -1\n", (void *) res->vcm); + goto fail; + } + + ret = vcm_all_activated(res->vcm); + if (ret == 0) { + vcm_err("can't unback, not all associations are active\n"); + goto fail_eagain; + } + + if (ret == -1) { + vcm_err("vcm_all_activated(%p) ret -1\n", (void *) res->vcm); + goto fail; + } + + + switch (vcm->type) { + case VCM_EXT_KERNEL: + if (!res->mapped) { + vcm_err("can't unback an unmapped VCM_EXT_KERNEL" + " VCM\n"); + goto fail; + } + + /* vunmap free's vm_area */ + vunmap(res->vm_area->addr); + res->vm_area = 0; + + res->mapped = 0; + break; + + case VCM_DEVICE: + { + struct phys_chunk *chunk; + size_t va = res->dev_addr; + + list_for_each_entry(chunk, &physmem->alloc_head.allocated, + allocated) { + struct vcm *vcm = res->vcm; + size_t chunk_size = chunk->size; + + ret = vcm_process_chunk(vcm->domain, 0, va, + chunk_size, 0, 0); + if (ret != 0) { + vcm_err("vcm_unback_chunk(%p, %p, 0x%x)" + " ret %i", + (void *) vcm->domain, + (void *) va, + (int) chunk_size, ret); + goto fail; + /* TODO handle weird inter-unmap state*/ + } + + va += chunk_size; + /* may to a light unback, depending on the requested + * functionality + */ + } + break; + } + + case VCM_ONE_TO_ONE: + break; + default: + /* this should never happen */ + goto fail; + } + + /* clear the reservation */ + res->physmem = 0; + + spin_unlock_irqrestore(&vcmlock, flags); + return 0; +fail_eagain: + spin_unlock_irqrestore(&vcmlock, flags); + return -EAGAIN; +fail: + spin_unlock_irqrestore(&vcmlock, flags); + return -EINVAL; +} + + +enum memtarget_t vcm_get_memtype_of_res(struct res *res) +{ + return VCM_INVALID; +} + +static int vcm_free_max_munch_cont(struct phys_chunk *head) +{ + struct phys_chunk *chunk, *tmp; + + if (!head) + return -EINVAL; + + list_for_each_entry_safe(chunk, tmp, &head->allocated, + allocated) { + list_del_init(&chunk->allocated); + } + + return 0; +} + +static int vcm_alloc_max_munch_cont(size_t start_addr, size_t len, + struct phys_chunk *head) +{ + /* this function should always succeed, since it + parallels a VCM */ + + int i, j; + + if (!head) { + vcm_err("head is NULL in continuous map.\n"); + goto fail; + } + + if (start_addr < (int) bootmem_cont) { + vcm_err("phys start addr (%p) < base (%p)\n", + (void *) start_addr, (void *) bootmem_cont); + goto fail; + } + + if ((start_addr + len) >= ((size_t) bootmem_cont + cont_sz)) { + vcm_err("requested region (%p + %i) > " + " available region (%p + %i)", + (void *) start_addr, (int) len, + (void *) bootmem_cont, cont_sz); + goto fail; + } + + i = (start_addr - (size_t) bootmem_cont)/SZ_4K; + + for (j = 0; j < ARRAY_SIZE(smmu_map_sizes); ++j) { + while (len/smmu_map_sizes[j]) { + if (!list_empty(&cont_phys_chunk[i].allocated)) { + vcm_err("chunk %i ( addr %p) already mapped\n", + i, (void *) (start_addr + + (i*smmu_map_sizes[j]))); + goto fail_free; + } + list_add_tail(&cont_phys_chunk[i].allocated, + &head->allocated); + cont_phys_chunk[i].size = smmu_map_sizes[j]; + + len -= smmu_map_sizes[j]; + i += smmu_map_sizes[j]/SZ_4K; + } + } + + if (len % SZ_4K) { + if (!list_empty(&cont_phys_chunk[i].allocated)) { + vcm_err("chunk %i (addr %p) already mapped\n", + i, (void *) (start_addr + (i*SZ_4K))); + goto fail_free; + } + len -= SZ_4K; + list_add_tail(&cont_phys_chunk[i].allocated, + &head->allocated); + + i++; + } + + return i; + +fail_free: + { + struct phys_chunk *chunk, *tmp; + /* just remove from list, if we're double alloc'ing + we don't want to stamp on the other guy */ + list_for_each_entry_safe(chunk, tmp, &head->allocated, + allocated) { + list_del(&chunk->allocated); + } + } +fail: + return 0; +} + +struct physmem *vcm_phys_alloc(enum memtype_t memtype, size_t len, u32 attr) +{ + unsigned long flags; + int ret; + struct physmem *physmem = NULL; + int blocks_allocated; + + spin_lock_irqsave(&vcmlock, flags); + + physmem = kzalloc(sizeof(*physmem), GFP_KERNEL); + if (!physmem) { + vcm_err("physmem is NULL\n"); + goto fail; + } + + physmem->memtype = memtype; + physmem->len = len; + physmem->attr = attr; + + INIT_LIST_HEAD(&physmem->alloc_head.allocated); + + if (attr & VCM_PHYS_CONT) { + if (!cont_vcm_id) { + vcm_err("cont_vcm_id is NULL\n"); + goto fail2; + } + + physmem->is_cont = 1; + + /* TODO: get attributes */ + physmem->res = __vcm_reserve(cont_vcm_id, len, 0); + if (physmem->res == 0) { + vcm_err("contiguous space allocation failed\n"); + goto fail2; + } + + /* if we're here we know we have memory, create + the shadow physmem links*/ + blocks_allocated = + vcm_alloc_max_munch_cont( + physmem->res->dev_addr, + len, + &physmem->alloc_head); + + if (blocks_allocated == 0) { + vcm_err("shadow physmem allocation failed\n"); + goto fail3; + } + } else { + blocks_allocated = vcm_alloc_max_munch(len, memtype, + &physmem->alloc_head); + if (blocks_allocated == 0) { + vcm_err("physical allocation failed:" + " vcm_alloc_max_munch(%i, %p) ret 0\n", + len, &physmem->alloc_head); + goto fail2; + } + } + + spin_unlock_irqrestore(&vcmlock, flags); + return physmem; + +fail3: + ret = __vcm_unreserve(physmem->res); + if (ret != 0) { + vcm_err("vcm_unreserve(%p) ret %i during cleanup", + (void *) physmem->res, ret); + spin_unlock_irqrestore(&vcmlock, flags); + return 0; + } +fail2: + kfree(physmem); +fail: + spin_unlock_irqrestore(&vcmlock, flags); + return 0; +} + + +int vcm_phys_free(struct physmem *physmem) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&vcmlock, flags); + + if (!physmem) { + vcm_err("physmem is NULL\n"); + goto fail; + } + + if (physmem->is_cont) { + if (physmem->res == 0) { + vcm_err("contiguous reservation is NULL\n"); + goto fail; + } + + ret = vcm_free_max_munch_cont(&physmem->alloc_head); + if (ret != 0) { + vcm_err("failed to free physical blocks:" + " vcm_free_max_munch_cont(%p) ret %i\n", + (void *) &physmem->alloc_head, ret); + goto fail; + } + + ret = __vcm_unreserve(physmem->res); + if (ret != 0) { + vcm_err("failed to free virtual blocks:" + " vcm_unreserve(%p) ret %i\n", + (void *) physmem->res, ret); + goto fail; + } + + } else { + + ret = vcm_alloc_free_blocks(physmem->memtype, + &physmem->alloc_head); + if (ret != 0) { + vcm_err("failed to free physical blocks:" + " vcm_alloc_free_blocks(%p) ret %i\n", + (void *) &physmem->alloc_head, ret); + goto fail; + } + } + + memset(physmem, 0, sizeof(*physmem)); + + kfree(physmem); + + spin_unlock_irqrestore(&vcmlock, flags); + return 0; + +fail: + spin_unlock_irqrestore(&vcmlock, flags); + return -EINVAL; +} + + +struct avcm *vcm_assoc(struct vcm *vcm, struct device *dev, u32 attr) +{ + unsigned long flags; + struct avcm *avcm = NULL; + + spin_lock_irqsave(&vcmlock, flags); + + if (!vcm) { + vcm_err("vcm is NULL\n"); + goto fail; + } + + if (!dev) { + vcm_err("dev_id is NULL\n"); + goto fail; + } + + if (vcm->type == VCM_EXT_KERNEL && !list_empty(&vcm->assoc_head)) { + vcm_err("only one device may be assocoated with a" + " VCM_EXT_KERNEL\n"); + goto fail; + } + + avcm = kzalloc(sizeof(*avcm), GFP_KERNEL); + if (!avcm) { + vcm_err("kzalloc(%i, GFP_KERNEL) ret NULL\n", sizeof(*avcm)); + goto fail; + } + + avcm->dev = dev; + + avcm->vcm = vcm; + avcm->attr = attr; + avcm->is_active = 0; + + INIT_LIST_HEAD(&avcm->assoc_elm); + list_add(&avcm->assoc_elm, &vcm->assoc_head); + + spin_unlock_irqrestore(&vcmlock, flags); + return avcm; + +fail: + spin_unlock_irqrestore(&vcmlock, flags); + return 0; +} + + +int vcm_deassoc(struct avcm *avcm) +{ + unsigned long flags; + + spin_lock_irqsave(&vcmlock, flags); + + if (!avcm) { + vcm_err("avcm is NULL\n"); + goto fail; + } + + if (list_empty(&avcm->assoc_elm)) { + vcm_err("nothing to deassociate\n"); + goto fail; + } + + if (avcm->is_active) { + vcm_err("association still activated\n"); + goto fail_busy; + } + + list_del(&avcm->assoc_elm); + + memset(avcm, 0, sizeof(*avcm)); + + kfree(avcm); + spin_unlock_irqrestore(&vcmlock, flags); + return 0; +fail_busy: + spin_unlock_irqrestore(&vcmlock, flags); + return -EBUSY; +fail: + spin_unlock_irqrestore(&vcmlock, flags); + return -EINVAL; +} + + +int vcm_set_assoc_attr(struct avcm *avcm, u32 attr) +{ + return 0; +} + + +u32 vcm_get_assoc_attr(struct avcm *avcm) +{ + return 0; +} + + +int vcm_activate(struct avcm *avcm) +{ + unsigned long flags; + struct vcm *vcm; + + spin_lock_irqsave(&vcmlock, flags); + + if (!avcm) { + vcm_err("avcm is NULL\n"); + goto fail; + } + + vcm = avcm->vcm; + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + if (!avcm->dev) { + vcm_err("cannot activate without a device\n"); + goto fail_nodev; + } + + if (avcm->is_active) { + vcm_err("double activate\n"); + goto fail_busy; + } + + if (vcm->type == VCM_DEVICE) { +#ifdef CONFIG_SMMU + int ret; + ret = iommu_attach_device(vcm->domain, avcm->dev); + if (ret != 0) { + dev_err(avcm->dev, "failed to attach to domain\n"); + goto fail_dev; + } +#else + vcm_err("No SMMU support - cannot activate/deactivate\n"); + goto fail_nodev; +#endif + } + + avcm->is_active = 1; + spin_unlock_irqrestore(&vcmlock, flags); + return 0; + +#ifdef CONFIG_SMMU +fail_dev: + spin_unlock_irqrestore(&vcmlock, flags); + return -ENODEV; +#endif +fail_busy: + spin_unlock_irqrestore(&vcmlock, flags); + return -EBUSY; +fail_nodev: + spin_unlock_irqrestore(&vcmlock, flags); + return -ENODEV; +fail: + spin_unlock_irqrestore(&vcmlock, flags); + return -EINVAL; +} + + +int vcm_deactivate(struct avcm *avcm) +{ + unsigned long flags; + struct vcm *vcm; + + spin_lock_irqsave(&vcmlock, flags); + + if (!avcm) + goto fail; + + vcm = avcm->vcm; + if (!vcm) { + vcm_err("NULL vcm\n"); + goto fail; + } + + if (!avcm->dev) { + vcm_err("cannot deactivate without a device\n"); + goto fail; + } + + if (!avcm->is_active) { + vcm_err("double deactivate\n"); + goto fail_nobusy; + } + + if (vcm->type == VCM_DEVICE) { +#ifdef CONFIG_SMMU + /* TODO, pmem check */ + iommu_detach_device(vcm->domain, avcm->dev); +#else + vcm_err("No SMMU support - cannot activate/deactivate\n"); + goto fail; +#endif + } + + avcm->is_active = 0; + spin_unlock_irqrestore(&vcmlock, flags); + return 0; +fail_nobusy: + spin_unlock_irqrestore(&vcmlock, flags); + return -ENOENT; +fail: + spin_unlock_irqrestore(&vcmlock, flags); + return -EINVAL; +} + +struct bound *vcm_create_bound(struct vcm *vcm, size_t len) +{ + return 0; +} + + +int vcm_free_bound(struct bound *bound) +{ + return -EINVAL; +} + + +struct res *vcm_reserve_from_bound(struct bound *bound, size_t len, + u32 attr) +{ + return 0; +} + + +size_t vcm_get_bound_start_addr(struct bound *bound) +{ + return 0; +} + + +size_t vcm_get_bound_len(struct bound *bound) +{ + return 0; +} + + +struct physmem *vcm_map_phys_addr(phys_addr_t phys, size_t len) +{ + return 0; +} + + +size_t vcm_get_next_phys_addr(struct physmem *physmem, phys_addr_t phys, + size_t *len) +{ + return 0; +} + + +struct res *vcm_get_res(unsigned long dev_addr, struct vcm *vcm) +{ + return 0; +} + + +size_t vcm_translate(struct device *src_dev, struct vcm *src_vcm, + struct vcm *dst_vcm) +{ + return 0; +} + + +size_t vcm_get_phys_num_res(phys_addr_t phys) +{ + return 0; +} + + +struct res *vcm_get_next_phys_res(phys_addr_t phys, struct res *res, + size_t *len) +{ + return 0; +} + + +phys_addr_t vcm_get_pgtbl_pa(struct vcm *vcm) +{ + return 0; +} + + +/* No lock needed, smmu_translate has its own lock */ +phys_addr_t vcm_dev_addr_to_phys_addr(struct vcm *vcm, unsigned long dev_addr) +{ + if (!vcm) + return -EINVAL; +#ifdef CONFIG_SMMU + return iommu_iova_to_phys(vcm->domain, dev_addr); +#else + vcm_err("No support for SMMU - manual translation not supported\n"); + return -ENODEV; +#endif +} + + +/* No lock needed, bootmem_cont never changes after */ +phys_addr_t vcm_get_cont_memtype_pa(enum memtype_t memtype) +{ + if (memtype != VCM_MEMTYPE_0) { + vcm_err("memtype != VCM_MEMTYPE_0\n"); + goto fail; + } + + if (!bootmem_cont) { + vcm_err("bootmem_cont 0\n"); + goto fail; + } + + return (size_t) bootmem_cont; +fail: + return 0; +} + + +/* No lock needed, constant */ +size_t vcm_get_cont_memtype_len(enum memtype_t memtype) +{ + if (memtype != VCM_MEMTYPE_0) { + vcm_err("memtype != VCM_MEMTYPE_0\n"); + return 0; + } + + return cont_sz; +} + +int vcm_hook(struct device *dev, vcm_handler handler, void *data) +{ +#ifdef CONFIG_SMMU + vcm_err("No interrupts in IOMMU API\n"); + return -ENODEV; +#else + vcm_err("No support for SMMU - interrupts not supported\n"); + return -ENODEV; +#endif +} + + +size_t vcm_hw_ver(size_t dev) +{ + return 0; +} + + +static int vcm_cont_phys_chunk_init(void) +{ + int i; + int cont_pa; + + if (!cont_phys_chunk) { + vcm_err("cont_phys_chunk 0\n"); + goto fail; + } + + if (!bootmem_cont) { + vcm_err("bootmem_cont 0\n"); + goto fail; + } + + cont_pa = (size_t) bootmem_cont; + + for (i = 0; i < cont_sz/PAGE_SIZE; ++i) { + cont_phys_chunk[i].pa = cont_pa; cont_pa += PAGE_SIZE; + cont_phys_chunk[i].size = SZ_4K; + /* Not part of an allocator-managed pool */ + cont_phys_chunk[i].pool_idx = -1; + INIT_LIST_HEAD(&cont_phys_chunk[i].allocated); + } + + return 0; + +fail: + return -EINVAL; +} + +int vcm_sys_init(struct physmem_region *mem, int n_regions, + struct vcm_memtype_map *mt_map, int n_mt, + void *cont_pa, unsigned int cont_len) +{ + int ret; + printk(KERN_INFO "VCM Initialization\n"); + bootmem_cont = cont_pa; + cont_sz = cont_len; + + if (!bootmem_cont) { + vcm_err("bootmem_cont is 0\n"); + ret = -1; + goto fail; + } + + ret = vcm_setup_tex_classes(); + if (ret != 0) { + printk(KERN_INFO "Could not determine TEX attribute mapping\n"); + ret = -1; + goto fail; + } + + + ret = vcm_alloc_init(mem, n_regions, mt_map, n_mt); + + if (ret != 0) { + vcm_err("vcm_alloc_init() ret %i\n", ret); + ret = -1; + goto fail; + } + + cont_phys_chunk = kzalloc(sizeof(*cont_phys_chunk)*(cont_sz/PAGE_SIZE), + GFP_KERNEL); + if (!cont_phys_chunk) { + vcm_err("kzalloc(%lu, GFP_KERNEL) ret 0", + sizeof(*cont_phys_chunk)*(cont_sz/PAGE_SIZE)); + goto fail_free; + } + + /* the address and size will hit our special case unless we + pass an override */ + cont_vcm_id = vcm_create_flagged(0, (size_t)bootmem_cont, cont_sz); + if (cont_vcm_id == 0) { + vcm_err("vcm_create_flagged(0, %p, %i) ret 0\n", + bootmem_cont, cont_sz); + ret = -1; + goto fail_free2; + } + + ret = vcm_cont_phys_chunk_init(); + if (ret != 0) { + vcm_err("vcm_cont_phys_chunk_init() ret %i\n", ret); + goto fail_free3; + } + + printk(KERN_INFO "VCM Initialization OK\n"); + return 0; + +fail_free3: + ret = __vcm_free(cont_vcm_id); + if (ret != 0) { + vcm_err("vcm_free(%p) ret %i during failure path\n", + (void *) cont_vcm_id, ret); + return ret; + } + +fail_free2: + kfree(cont_phys_chunk); + cont_phys_chunk = 0; + +fail_free: + ret = vcm_alloc_destroy(); + if (ret != 0) + vcm_err("vcm_alloc_destroy() ret %i during failure path\n", + ret); + + ret = -EINVAL; +fail: + return ret; +} + + +int vcm_sys_destroy(void) +{ + int ret = 0; + + if (!cont_phys_chunk) { + vcm_err("cont_phys_chunk is 0\n"); + return -ENODEV; + } + + if (!cont_vcm_id) { + vcm_err("cont_vcm_id is 0\n"); + return -ENODEV; + } + + ret = __vcm_free(cont_vcm_id); + if (ret != 0) { + vcm_err("vcm_free(%p) ret %i\n", (void *) cont_vcm_id, ret); + return -ENODEV; + } + + cont_vcm_id = 0; + + kfree(cont_phys_chunk); + cont_phys_chunk = 0; + + ret = vcm_alloc_destroy(); + if (ret != 0) { + vcm_err("vcm_alloc_destroy() ret %i\n", ret); + return ret; + } + + return ret; +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Zach Pfeffer "); diff --git a/arch/arm/mm/vcm_alloc.c b/arch/arm/mm/vcm_alloc.c new file mode 100644 index 00000000000..5f3c024757d --- /dev/null +++ b/arch/arm/mm/vcm_alloc.c @@ -0,0 +1,557 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +int basicalloc_init; + +#define vcm_alloc_err(a, ...) \ + pr_err("ERROR %s %i " a, __func__, __LINE__, ##__VA_ARGS__) + +struct phys_chunk_head { + struct list_head head; + int num; +}; + +struct phys_pool { + int size; + int chunk_size; + struct phys_chunk_head head; +}; + +static int vcm_num_phys_pools; +static int vcm_num_memtypes; +static struct phys_pool *vcm_phys_pool; +static struct vcm_memtype_map *memtype_map; + +static int num_pools(enum memtype_t memtype) +{ + if (memtype >= vcm_num_memtypes) { + vcm_alloc_err("Bad memtype: %d\n", memtype); + return -EINVAL; + } + return memtype_map[memtype].num_pools; +} + +static int pool_chunk_size(enum memtype_t memtype, int prio_idx) +{ + int pool_idx; + if (memtype >= vcm_num_memtypes) { + vcm_alloc_err("Bad memtype: %d\n", memtype); + return -EINVAL; + } + + if (prio_idx >= num_pools(memtype)) { + vcm_alloc_err("Bad prio index: %d, max=%d, mt=%d\n", prio_idx, + num_pools(memtype), memtype); + return -EINVAL; + } + + pool_idx = memtype_map[memtype].pool_id[prio_idx]; + return vcm_phys_pool[pool_idx].chunk_size; +} + +int vcm_alloc_pool_idx_to_size(int pool_idx) +{ + if (pool_idx >= vcm_num_phys_pools) { + vcm_alloc_err("Bad pool index: %d\n, max=%d\n", pool_idx, + vcm_num_phys_pools); + return -EINVAL; + } + return vcm_phys_pool[pool_idx].chunk_size; +} + +static struct phys_chunk_head *get_chunk_list(enum memtype_t memtype, + int prio_idx) +{ + unsigned int pool_idx; + + if (memtype >= vcm_num_memtypes) { + vcm_alloc_err("Bad memtype: %d\n", memtype); + return NULL; + } + + if (prio_idx >= num_pools(memtype)) { + vcm_alloc_err("bad chunk size: mt=%d, prioidx=%d, np=%d\n", + memtype, prio_idx, num_pools(memtype)); + BUG(); + return NULL; + } + + if (!vcm_phys_pool) { + vcm_alloc_err("phys_pool is null\n"); + return NULL; + } + + /* We don't have a "pool count" anywhere but this is coming + * strictly from data in a board file + */ + pool_idx = memtype_map[memtype].pool_id[prio_idx]; + + return &vcm_phys_pool[pool_idx].head; +} + +static int is_allocated(struct list_head *allocated) +{ + /* This should not happen under normal conditions */ + if (!allocated) { + vcm_alloc_err("no allocated\n"); + return 0; + } + + if (!basicalloc_init) { + vcm_alloc_err("no basicalloc_init\n"); + return 0; + } + return !list_empty(allocated); +} + +static int count_allocated_size(enum memtype_t memtype, int idx) +{ + int cnt = 0; + struct phys_chunk *chunk, *tmp; + struct phys_chunk_head *pch; + + if (!basicalloc_init) { + vcm_alloc_err("no basicalloc_init\n"); + return 0; + } + + pch = get_chunk_list(memtype, idx); + if (!pch) { + vcm_alloc_err("null pch\n"); + return -EINVAL; + } + + list_for_each_entry_safe(chunk, tmp, &pch->head, list) { + if (is_allocated(&chunk->allocated)) + cnt++; + } + + return cnt; +} + + +int vcm_alloc_get_mem_size(void) +{ + if (!vcm_phys_pool) { + vcm_alloc_err("No physical pool set up!\n"); + return -ENODEV; + } + return vcm_phys_pool[0].size; +} +EXPORT_SYMBOL(vcm_alloc_get_mem_size); + +void vcm_alloc_print_list(enum memtype_t memtype, int just_allocated) +{ + int i; + struct phys_chunk *chunk, *tmp; + struct phys_chunk_head *pch; + + if (!basicalloc_init) { + vcm_alloc_err("no basicalloc_init\n"); + return; + } + + for (i = 0; i < num_pools(memtype); ++i) { + pch = get_chunk_list(memtype, i); + + if (!pch) { + vcm_alloc_err("pch is null\n"); + return; + } + + if (list_empty(&pch->head)) + continue; + + list_for_each_entry_safe(chunk, tmp, &pch->head, list) { + if (just_allocated && !is_allocated(&chunk->allocated)) + continue; + + printk(KERN_INFO "pa = %#x, size = %#x\n", + chunk->pa, vcm_phys_pool[chunk->pool_idx].chunk_size); + } + } +} +EXPORT_SYMBOL(vcm_alloc_print_list); + +int vcm_alloc_blocks_avail(enum memtype_t memtype, int idx) +{ + struct phys_chunk_head *pch; + if (!basicalloc_init) { + vcm_alloc_err("no basicalloc_init\n"); + return 0; + } + pch = get_chunk_list(memtype, idx); + + if (!pch) { + vcm_alloc_err("pch is null\n"); + return 0; + } + return pch->num; +} +EXPORT_SYMBOL(vcm_alloc_blocks_avail); + + +int vcm_alloc_get_num_chunks(enum memtype_t memtype) +{ + return num_pools(memtype); +} +EXPORT_SYMBOL(vcm_alloc_get_num_chunks); + + +int vcm_alloc_all_blocks_avail(enum memtarget_t memtype) +{ + int i; + int cnt = 0; + + if (!basicalloc_init) { + vcm_alloc_err("no basicalloc_init\n"); + return 0; + } + + for (i = 0; i < num_pools(memtype); ++i) + cnt += vcm_alloc_blocks_avail(memtype, i); + return cnt; +} +EXPORT_SYMBOL(vcm_alloc_all_blocks_avail); + + +int vcm_alloc_count_allocated(enum memtype_t memtype) +{ + int i; + int cnt = 0; + + if (!basicalloc_init) { + vcm_alloc_err("no basicalloc_init\n"); + return 0; + } + + for (i = 0; i < num_pools(memtype); ++i) + cnt += count_allocated_size(memtype, i); + return cnt; +} +EXPORT_SYMBOL(vcm_alloc_count_allocated); + +int vcm_alloc_destroy(void) +{ + int i, mt; + struct phys_chunk *chunk, *tmp; + + if (!basicalloc_init) { + vcm_alloc_err("no basicalloc_init\n"); + return -ENODEV; + } + + /* can't destroy a space that has allocations */ + for (mt = 0; mt < vcm_num_memtypes; mt++) + if (vcm_alloc_count_allocated(mt)) { + vcm_alloc_err("allocations still present\n"); + return -EBUSY; + } + + for (i = 0; i < vcm_num_phys_pools; i++) { + struct phys_chunk_head *pch = &vcm_phys_pool[i].head; + + if (list_empty(&pch->head)) + continue; + list_for_each_entry_safe(chunk, tmp, &pch->head, list) { + list_del(&chunk->list); + memset(chunk, 0, sizeof(*chunk)); + kfree(chunk); + } + vcm_phys_pool[i].head.num = 0; + } + + kfree(vcm_phys_pool); + kfree(memtype_map); + + vcm_phys_pool = NULL; + memtype_map = NULL; + basicalloc_init = 0; + vcm_num_phys_pools = 0; + return 0; +} +EXPORT_SYMBOL(vcm_alloc_destroy); + + +int vcm_alloc_init(struct physmem_region *mem, int n_regions, + struct vcm_memtype_map *mt_map, int n_mt) +{ + int i = 0, j = 0, r = 0, num_chunks; + struct phys_chunk *chunk; + struct phys_chunk_head *pch = NULL; + unsigned long pa; + + /* no double inits */ + if (basicalloc_init) { + vcm_alloc_err("double basicalloc_init\n"); + BUG(); + goto fail; + } + memtype_map = kzalloc(sizeof(*mt_map) * n_mt, GFP_KERNEL); + if (!memtype_map) { + vcm_alloc_err("Could not copy memtype map\n"); + goto fail; + } + memcpy(memtype_map, mt_map, sizeof(*mt_map) * n_mt); + + vcm_phys_pool = kzalloc(sizeof(*vcm_phys_pool) * n_regions, GFP_KERNEL); + vcm_num_phys_pools = n_regions; + vcm_num_memtypes = n_mt; + + if (!vcm_phys_pool) { + vcm_alloc_err("Could not allocate physical pool structure\n"); + goto fail; + } + + /* separate out to ensure good cleanup */ + for (i = 0; i < n_regions; i++) { + pch = &vcm_phys_pool[i].head; + INIT_LIST_HEAD(&pch->head); + pch->num = 0; + } + + for (r = 0; r < n_regions; r++) { + pa = mem[r].addr; + vcm_phys_pool[r].size = mem[r].size; + vcm_phys_pool[r].chunk_size = mem[r].chunk_size; + pch = &vcm_phys_pool[r].head; + + num_chunks = mem[r].size / mem[r].chunk_size; + + printk(KERN_INFO "VCM Init: region %d, chunk size=%d, " + "num=%d, pa=%p\n", r, mem[r].chunk_size, num_chunks, + (void *)pa); + + for (j = 0; j < num_chunks; ++j) { + chunk = kzalloc(sizeof(*chunk), GFP_KERNEL); + if (!chunk) { + vcm_alloc_err("null chunk\n"); + goto fail; + } + chunk->pa = pa; + chunk->size = mem[r].chunk_size; + pa += mem[r].chunk_size; + chunk->pool_idx = r; + INIT_LIST_HEAD(&chunk->allocated); + list_add_tail(&chunk->list, &pch->head); + pch->num++; + } + } + + basicalloc_init = 1; + return 0; +fail: + vcm_alloc_destroy(); + return -EINVAL; +} +EXPORT_SYMBOL(vcm_alloc_init); + + +int vcm_alloc_free_blocks(enum memtype_t memtype, struct phys_chunk *alloc_head) +{ + struct phys_chunk *chunk, *tmp; + struct phys_chunk_head *pch = NULL; + + if (!basicalloc_init) { + vcm_alloc_err("no basicalloc_init\n"); + goto fail; + } + + if (!alloc_head) { + vcm_alloc_err("no alloc_head\n"); + goto fail; + } + + list_for_each_entry_safe(chunk, tmp, &alloc_head->allocated, + allocated) { + list_del_init(&chunk->allocated); + pch = &vcm_phys_pool[chunk->pool_idx].head; + + if (!pch) { + vcm_alloc_err("null pch\n"); + goto fail; + } + pch->num++; + } + + return 0; +fail: + return -ENODEV; +} +EXPORT_SYMBOL(vcm_alloc_free_blocks); + + +int vcm_alloc_num_blocks(int num, enum memtype_t memtype, int idx, + struct phys_chunk *alloc_head) +{ + struct phys_chunk *chunk; + struct phys_chunk_head *pch = NULL; + int num_allocated = 0; + + if (!basicalloc_init) { + vcm_alloc_err("no basicalloc_init\n"); + goto fail; + } + + if (!alloc_head) { + vcm_alloc_err("no alloc_head\n"); + goto fail; + } + + pch = get_chunk_list(memtype, idx); + + if (!pch) { + vcm_alloc_err("null pch\n"); + goto fail; + } + if (list_empty(&pch->head)) { + vcm_alloc_err("list is empty\n"); + goto fail; + } + + if (vcm_alloc_blocks_avail(memtype, idx) < num) { + vcm_alloc_err("not enough blocks? num=%d\n", num); + goto fail; + } + + list_for_each_entry(chunk, &pch->head, list) { + if (num_allocated == num) + break; + if (is_allocated(&chunk->allocated)) + continue; + + list_add_tail(&chunk->allocated, &alloc_head->allocated); + pch->num--; + num_allocated++; + } + return num_allocated; +fail: + return 0; +} +EXPORT_SYMBOL(vcm_alloc_num_blocks); + + +int vcm_alloc_max_munch(int len, enum memtype_t memtype, + struct phys_chunk *alloc_head) +{ + int i; + + int blocks_req = 0; + int block_residual = 0; + int blocks_allocated = 0; + int cur_chunk_size = 0; + int ba = 0; + + if (!basicalloc_init) { + vcm_alloc_err("basicalloc_init is 0\n"); + goto fail; + } + + if (!alloc_head) { + vcm_alloc_err("alloc_head is NULL\n"); + goto fail; + } + + if (num_pools(memtype) <= 0) { + vcm_alloc_err("Memtype %d has improper mempool configuration\n", + memtype); + goto fail; + } + + for (i = 0; i < num_pools(memtype); ++i) { + cur_chunk_size = pool_chunk_size(memtype, i); + if (cur_chunk_size <= 0) { + vcm_alloc_err("Bad chunk size: %d\n", cur_chunk_size); + goto fail; + } + + blocks_req = len / cur_chunk_size; + block_residual = len % cur_chunk_size; + + len = block_residual; /* len left */ + if (blocks_req) { + int blocks_available = 0; + int blocks_diff = 0; + int bytes_diff = 0; + + blocks_available = vcm_alloc_blocks_avail(memtype, i); + if (blocks_available < blocks_req) { + blocks_diff = + (blocks_req - blocks_available); + bytes_diff = + blocks_diff * cur_chunk_size; + + /* add back in the rest */ + len += bytes_diff; + } else { + /* got all the blocks I need */ + blocks_available = + (blocks_available > blocks_req) + ? blocks_req : blocks_available; + } + + ba = vcm_alloc_num_blocks(blocks_available, memtype, i, + alloc_head); + + if (ba != blocks_available) { + vcm_alloc_err("blocks allocated (%i) !=" + " blocks_available (%i):" + " chunk size = %#x," + " alloc_head = %p\n", + ba, blocks_available, + i, (void *) alloc_head); + goto fail; + } + blocks_allocated += blocks_available; + } + } + + if (len) { + int blocks_available = 0; + int last_sz = num_pools(memtype) - 1; + blocks_available = vcm_alloc_blocks_avail(memtype, last_sz); + + if (blocks_available > 0) { + ba = vcm_alloc_num_blocks(1, memtype, last_sz, + alloc_head); + if (ba != 1) { + vcm_alloc_err("blocks allocated (%i) !=" + " blocks_available (%i):" + " chunk size = %#x," + " alloc_head = %p\n", + ba, 1, + last_sz, + (void *) alloc_head); + goto fail; + } + blocks_allocated += 1; + } else { + vcm_alloc_err("blocks_available (%#x) <= 1\n", + blocks_available); + goto fail; + } + } + + return blocks_allocated; +fail: + vcm_alloc_free_blocks(memtype, alloc_head); + return 0; +} +EXPORT_SYMBOL(vcm_alloc_max_munch); diff --git a/arch/arm/mm/vcm_mm.c b/arch/arm/mm/vcm_mm.c new file mode 100644 index 00000000000..dee51fab8a4 --- /dev/null +++ b/arch/arm/mm/vcm_mm.c @@ -0,0 +1,253 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* Architecture-specific VCM functions */ + +#include +#include + +#include +#include + +#define MRC(reg, processor, op1, crn, crm, op2) \ +__asm__ __volatile__ ( \ +" mrc " #processor "," #op1 ", %0," #crn "," #crm "," #op2 " \n" \ +: "=r" (reg)) + +#define RCP15_PRRR(reg) MRC(reg, p15, 0, c10, c2, 0) +#define RCP15_NMRR(reg) MRC(reg, p15, 0, c10, c2, 1) + + +/* Local type attributes (not the same as VCM) */ +#define ARM_MT_NORMAL 2 +#define ARM_MT_STRONGLYORDERED 0 +#define ARM_MT_DEVICE 1 + +#define ARM_CP_NONCACHED 0 +#define ARM_CP_WB_WA 1 +#define ARM_CP_WB_NWA 3 +#define ARM_CP_WT_NWA 2 + +#define smmu_err(a, ...) \ + pr_err("ERROR %s %i " a, __func__, __LINE__, ##__VA_ARGS__) + +#define FL_OFFSET(va) (((va) & 0xFFF00000) >> 20) +#define SL_OFFSET(va) (((va) & 0xFF000) >> 12) + +int vcm_driver_tex_class[4]; + +static int find_tex_class(int icp, int ocp, int mt, int nos) +{ + int i = 0; + unsigned int prrr = 0; + unsigned int nmrr = 0; + int c_icp, c_ocp, c_mt, c_nos; + + RCP15_PRRR(prrr); + RCP15_NMRR(nmrr); + + /* There are only 8 classes on this architecture */ + /* If they add more classes, registers will VASTLY change */ + for (i = 0; i < 8; i++) { + c_nos = prrr & (1 << (i + 24)) ? 1 : 0; + c_mt = (prrr & (3 << (i * 2))) >> (i * 2); + c_icp = (nmrr & (3 << (i * 2))) >> (i * 2); + c_ocp = (nmrr & (3 << (i * 2 + 16))) >> (i * 2 + 16); + + if (icp == c_icp && ocp == c_ocp && c_mt == mt && c_nos == nos) + return i; + } + smmu_err("Could not find TEX class for ICP=%d, OCP=%d, MT=%d, NOS=%d\n", + icp, ocp, mt, nos); + + /* In reality, we may want to remove this panic. Some classes just */ + /* will not be available, and will fail in smmu_set_attr */ + panic("SMMU: Could not determine TEX attribute mapping.\n"); + return -1; +} + + +int vcm_setup_tex_classes(void) +{ + unsigned int cpu_prrr; + unsigned int cpu_nmrr; + + if (!(get_cr() & CR_TRE)) /* No TRE? */ + panic("TEX remap not enabled, but the SMMU driver needs it!\n"); + + RCP15_PRRR(cpu_prrr); + RCP15_NMRR(cpu_nmrr); + + vcm_driver_tex_class[VCM_DEV_ATTR_NONCACHED] = + find_tex_class(ARM_CP_NONCACHED, ARM_CP_NONCACHED, + ARM_MT_NORMAL, 1); + + vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_WA] = + find_tex_class(ARM_CP_WB_WA, ARM_CP_WB_WA, + ARM_MT_NORMAL, 1); + + vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_NWA] = + find_tex_class(ARM_CP_WB_NWA, ARM_CP_WB_NWA, + ARM_MT_NORMAL, 1); + + vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WT] = + find_tex_class(ARM_CP_WT_NWA, ARM_CP_WT_NWA, + ARM_MT_NORMAL, 1); +#ifdef DEBUG_TEX + printk(KERN_INFO "VCM driver debug: Using TEX classes: %d %d %d %d\n", + vcm_driver_tex_class[VCM_DEV_ATTR_NONCACHED], + vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_WA], + vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WB_NWA], + vcm_driver_tex_class[VCM_DEV_ATTR_CACHED_WT]); +#endif + return 0; +} + + +int set_arm7_pte_attr(unsigned long pt_base, unsigned long va, + unsigned long len, unsigned int attr) +{ + unsigned long *fl_table = NULL; + unsigned long *fl_pte = NULL; + unsigned long fl_offset = 0; + unsigned long *sl_table = NULL; + unsigned long *sl_pte = NULL; + unsigned long sl_offset = 0; + int i; + int sh = 0; + int class = 0; + + /* Alignment */ + if (va & (len-1)) { + smmu_err("misaligned va: %p\n", (void *) va); + goto fail; + } + if (attr > 7) { + smmu_err("bad attribute: %d\n", attr); + goto fail; + } + + sh = (attr & VCM_DEV_ATTR_SH) ? 1 : 0; + class = vcm_driver_tex_class[attr & 0x03]; + + if (class > 7 || class < 0) { /* Bad class */ + smmu_err("bad tex class: %d\n", class); + goto fail; + } + + if (len != SZ_16M && len != SZ_1M && + len != SZ_64K && len != SZ_4K) { + smmu_err("bad size: %lu\n", len); + goto fail; + } + + fl_table = (unsigned long *) pt_base; + + if (!fl_table) { + smmu_err("null page table\n"); + goto fail; + } + + fl_offset = FL_OFFSET(va); /* Upper 12 bits */ + fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */ + + if (*fl_pte == 0) { /* Nothing there! */ + smmu_err("first level pte is 0\n"); + goto fail; + } + + /* Supersection attributes */ + if (len == SZ_16M) { + for (i = 0; i < 16; i++) { + /* Clear the old bits */ + *(fl_pte+i) &= ~(PMD_SECT_S | PMD_SECT_CACHEABLE | + PMD_SECT_BUFFERABLE | PMD_SECT_TEX(1)); + + /* Assign new class and S bit */ + *(fl_pte+i) |= sh ? PMD_SECT_S : 0; + *(fl_pte+i) |= class & 0x01 ? PMD_SECT_BUFFERABLE : 0; + *(fl_pte+i) |= class & 0x02 ? PMD_SECT_CACHEABLE : 0; + *(fl_pte+i) |= class & 0x04 ? PMD_SECT_TEX(1) : 0; + } + } else if (len == SZ_1M) { + + /* Clear the old bits */ + *(fl_pte) &= ~(PMD_SECT_S | PMD_SECT_CACHEABLE | + PMD_SECT_BUFFERABLE | PMD_SECT_TEX(1)); + + /* Assign new class and S bit */ + *(fl_pte) |= sh ? PMD_SECT_S : 0; + *(fl_pte) |= class & 0x01 ? PMD_SECT_BUFFERABLE : 0; + *(fl_pte) |= class & 0x02 ? PMD_SECT_CACHEABLE : 0; + *(fl_pte) |= class & 0x04 ? PMD_SECT_TEX(1) : 0; + } + + sl_table = (unsigned long *) __va(((*fl_pte) & 0xFFFFFC00)); + sl_offset = SL_OFFSET(va); + sl_pte = sl_table + sl_offset; + + if (len == SZ_64K) { + for (i = 0; i < 16; i++) { + /* Clear the old bits */ + *(sl_pte+i) &= ~(PTE_EXT_SHARED | PTE_CACHEABLE | + PTE_BUFFERABLE | PTE_EXT_TEX(1)); + + /* Assign new class and S bit */ + *(sl_pte+i) |= sh ? PTE_EXT_SHARED : 0; + *(sl_pte+i) |= class & 0x01 ? PTE_BUFFERABLE : 0; + *(sl_pte+i) |= class & 0x02 ? PTE_CACHEABLE : 0; + *(sl_pte+i) |= class & 0x04 ? PTE_EXT_TEX(1) : 0; + } + } else if (len == SZ_4K) { + /* Clear the old bits */ + *(sl_pte) &= ~(PTE_EXT_SHARED | PTE_CACHEABLE | + PTE_BUFFERABLE | PTE_EXT_TEX(1)); + + /* Assign new class and S bit */ + *(sl_pte) |= sh ? PTE_EXT_SHARED : 0; + *(sl_pte) |= class & 0x01 ? PTE_BUFFERABLE : 0; + *(sl_pte) |= class & 0x02 ? PTE_CACHEABLE : 0; + *(sl_pte) |= class & 0x04 ? PTE_EXT_TEX(1) : 0; + } + + + mb(); + return 0; +fail: + return 1; +} + + +int cpu_set_attr(unsigned long va, unsigned long len, unsigned int attr) +{ + int ret; + pgd_t *pgd = init_mm.pgd; + + if (!pgd) { + smmu_err("null pgd\n"); + goto fail; + } + + ret = set_arm7_pte_attr((unsigned long)pgd, va, len, attr); + + if (ret != 0) { + smmu_err("could not set attribute: \ + pgd=%p, va=%p, len=%lu, attr=%d\n", + (void *) pgd, (void *) va, len, attr); + goto fail; + } + dmb(); + flush_tlb_all(); + return 0; +fail: + return -1; +} diff --git a/arch/arm/oprofile/common.c b/arch/arm/oprofile/common.c index c074e66ad22..afd7afba96e 100644 --- a/arch/arm/oprofile/common.c +++ b/arch/arm/oprofile/common.c @@ -40,6 +40,12 @@ char *op_name_from_perf_id(void) return "arm/armv7"; case ARM_PERF_PMU_ID_CA9: return "arm/armv7-ca9"; + case ARM_PERF_PMU_ID_SCORPION: + return "arm/armv7-scorpion"; + case ARM_PERF_PMU_ID_SCORPIONMP: + return "arm/armv7-scorpionmp"; + case ARM_PERF_PMU_ID_KRAIT: + return "arm/armv7-krait"; default: return NULL; } diff --git a/arch/arm/perfmon/Makefile b/arch/arm/perfmon/Makefile new file mode 100644 index 00000000000..716e0873d39 --- /dev/null +++ b/arch/arm/perfmon/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_KSAPI) += ksapi.o + +# Object file lists. +obj-y += perf-function-hooks.o +ksapi-y += perf-v7.o per.o per-process-perf.o per-axi.o +ksapi-$(CONFIG_ARCH_MSM8X60) += perf-smp.o diff --git a/arch/arm/perfmon/cp15_registers.h b/arch/arm/perfmon/cp15_registers.h new file mode 100644 index 00000000000..3de4d8bea7d --- /dev/null +++ b/arch/arm/perfmon/cp15_registers.h @@ -0,0 +1,94 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* +cp15_registers.h + +DESCRIPTION: define macros for reading and writing to the cp registers +for the ARMv7 + +REV/DATE: Fri Mar 18 15:54:32 EST 2005 +*/ + +#ifndef __cp15_registers__ +#define __cp15_registers__ + +#include "mcrmrc.h" + +#define WCP15_SDER(reg) MCR15(reg, 0, c1, c1, 1) +/* +* Performance Monitor Registers +*/ +#define WCP15_PMACTLR(reg) MCR15(reg, 0, c9, c15, 5) +#define WCP15_PMCCNTCR(reg) MCR15(reg, 0, c9, c15, 2) +#define WCP15_PMCCNTR(reg) MCR15(reg, 0, c9, c13, 0) +#define WCP15_PMCCNTSR(reg) MCR15(reg, 0, c9, c13, 3) +#define WCP15_PMCNTENCLR(reg) MCR15(reg, 0, c9, c12, 2) +#define WCP15_PMCNTENSET(reg) MCR15(reg, 0, c9, c12, 1) +#define WCP15_PMCR(reg) MCR15(reg, 0, c9, c12, 0) +#define WCP15_PMINTENCLR(reg) MCR15(reg, 0, c9, c14, 2) +#define WCP15_PMINTENSET(reg) MCR15(reg, 0, c9, c14, 1) +#define WCP15_PMOVSR(reg) MCR15(reg, 0, c9, c12, 3) +#define WCP15_PMRLDR(reg) MCR15(reg, 0, c9, c15, 4) +#define WCP15_PMSELR(reg) MCR15(reg, 0, c9, c12, 5) +#define WCP15_PMSWINC(reg) MCR15(reg, 0, c9, c12, 4) +#define WCP15_PMUSERENR(reg) MCR15(reg, 0, c9, c14, 0) +#define WCP15_PMXEVCNTCR(reg) MCR15(reg, 0, c9, c15, 0) +#define WCP15_PMXEVCNTR(reg) MCR15(reg, 0, c9, c13, 2) +#define WCP15_PMXEVCNTSR(reg) MCR15(reg, 0, c9, c15, 1) +#define WCP15_PMXEVTYPER(reg) MCR15(reg, 0, c9, c13, 1) +#define WCP15_LPM0EVTYPER(reg) MCR15(reg, 0, c15, c0, 0) +#define WCP15_LPM1EVTYPER(reg) MCR15(reg, 1, c15, c0, 0) +#define WCP15_LPM2EVTYPER(reg) MCR15(reg, 2, c15, c0, 0) +#define WCP15_LPM3EVTYPER(reg) MCR15(reg, 3, c15, c0, 0) +#define WCP15_L2LPMEVTYPER(reg) MCR15(reg, 3, c15, c2, 0) +#define WCP15_VLPMEVTYPER(reg) MCR15(reg, 7, c11, c0, 0) +#define WCP15_L2VR3F1(reg) MCR15(reg, 3, c15, c15, 1) + +/* +* READ the registers +*/ +#define RCP15_SDER(reg) MRC15(reg, 0, c1, c1, 1) +/* +* Performance Monitor Registers +*/ +#define RCP15_PMACTLR(reg) MRC15(reg, 0, c9, c15, 5) +#define RCP15_PMCCNTCR(reg) MRC15(reg, 0, c9, c15, 2) +#define RCP15_PMCCNTR(reg) MRC15(reg, 0, c9, c13, 0) +#define RCP15_PMCCNTSR(reg) MRC15(reg, 0, c9, c13, 3) +#define RCP15_PMCNTENCLR(reg) MRC15(reg, 0, c9, c12, 2) +#define RCP15_PMCNTENSET(reg) MRC15(reg, 0, c9, c12, 1) +#define RCP15_PMCR(reg) MRC15(reg, 0, c9, c12, 0) +#define RCP15_PMINTENCLR(reg) MRC15(reg, 0, c9, c14, 2) +#define RCP15_PMINTENSET(reg) MRC15(reg, 0, c9, c14, 1) +#define RCP15_PMOVSR(reg) MRC15(reg, 0, c9, c12, 3) +#define RCP15_PMRLDR(reg) MRC15(reg, 0, c9, c15, 4) +#define RCP15_PMSELR(reg) MRC15(reg, 0, c9, c12, 5) +#define RCP15_PMSWINC(reg) MRC15(reg, 0, c9, c12, 4) +#define RCP15_PMUSERENR(reg) MRC15(reg, 0, c9, c14, 0) +#define RCP15_PMXEVCNTCR(reg) MRC15(reg, 0, c9, c15, 0) +#define RCP15_PMXEVCNTR(reg) MRC15(reg, 0, c9, c13, 2) +#define RCP15_PMXEVCNTSR(reg) MRC15(reg, 0, c9, c15, 1) +#define RCP15_PMXEVTYPER(reg) MRC15(reg, 0, c9, c13, 1) +#define RCP15_LPM0EVTYPER(reg) MRC15(reg, 0, c15, c0, 0) +#define RCP15_LPM1EVTYPER(reg) MRC15(reg, 1, c15, c0, 0) +#define RCP15_LPM2EVTYPER(reg) MRC15(reg, 2, c15, c0, 0) +#define RCP15_LPM3EVTYPER(reg) MRC15(reg, 3, c15, c0, 0) +#define RCP15_L2LPMEVTYPER(reg) MRC15(reg, 3, c15, c2, 0) +#define RCP15_VLPMEVTYPER(reg) MRC15(reg, 7, c11, c0, 0) +#define RCP15_CONTEXTIDR(reg) MRC15(reg, 0, c13, c0, 1) +#define RCP15_L2CR0(reg) MRC15(reg, 3, c15, c0, 1) +#define RCP15_L2VR3F1(reg) MRC15(reg, 3, c15, c15, 1) + +#endif + diff --git a/arch/arm/perfmon/l2_cp15_registers.h b/arch/arm/perfmon/l2_cp15_registers.h new file mode 100644 index 00000000000..796dc8b4353 --- /dev/null +++ b/arch/arm/perfmon/l2_cp15_registers.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* +cp15_registers.h + +DESCRIPTION: define macros for reading and writing to the cp registers +for the ARMv7 + +REV/DATE: Fri Mar 18 15:54:32 EST 2005 +*/ + +#ifndef __l2_cp15_registers__ +#define __l2_cp15_registers__ + +#include "mcrmrc.h" + +#define WCP15_SDER(reg) MCR15(reg, 0, c1, c1, 1) +/* +* Performance Monitor Registers +*/ +#define WCP15_L2MPCR(reg) MCR15(reg, 3, c15, c0, 4) +#define WCP15_L2PMCCNTCR(reg) MCR15(reg, 3, c15, c4, 4) +#define WCP15_L2PMCCNTR(reg) MCR15(reg, 3, c15, c4, 5) +#define WCP15_L2PMCCNTSR(reg) MCR15(reg, 3, c15, c4, 6) +#define WCP15_L2PMCNTENCLR(reg) MCR15(reg, 3, c15, c4, 2) +#define WCP15_L2PMCNTENSET(reg) MCR15(reg, 3, c15, c4, 3) +#define WCP15_L2PMCR(reg) MCR15(reg, 3, c15, c4, 0) +#define WCP15_L2PMINTENCLR(reg) MCR15(reg, 3, c15, c5, 0) +#define WCP15_L2PMINTENSET(reg) MCR15(reg, 3, c15, c5, 1) +#define WCP15_L2PMOVSR(reg) MCR15(reg, 3, c15, c4, 1) +#define WCP15_L2PMRLDR(reg) MCR15(reg, 3, c15, c4, 7) +#define WCP15_L2PMSELR(reg) MCR15(reg, 3, c15, c6, 0) +#define WCP15_L2PMXEVCNTCR(reg) MCR15(reg, 3, c15, c6, 4) +#define WCP15_L2PMXEVCNTR(reg) MCR15(reg, 3, c15, c6, 5) +#define WCP15_L2PMXEVCNTSR(reg) MCR15(reg, 3, c15, c6, 6) +#define WCP15_L2PMXEVTYPER(reg) MCR15(reg, 3, c15, c6, 7) +#define WCP15_L2PMXEVFILTER(reg) MCR15(reg, 3, c15, c6, 3) +#define WCP15_L2PMEVTYPER0(reg) MCR15(reg, 3, c15, c7, 0) +#define WCP15_L2PMEVTYPER1(reg) MCR15(reg, 3, c15, c7, 1) +#define WCP15_L2PMEVTYPER2(reg) MCR15(reg, 3, c15, c7, 2) +#define WCP15_L2PMEVTYPER3(reg) MCR15(reg, 3, c15, c7, 3) +#define WCP15_L2PMEVTYPER4(reg) MCR15(reg, 3, c15, c7, 4) +#define WCP15_L2VR3F1(reg) MCR15(reg, 3, c15, c15, 1) + +/* +* READ the registers +*/ +#define RCP15_SDER(reg) MRC15(reg, 0, c1, c1, 1) +/* +* Performance Monitor Registers +*/ +#define RCP15_L2MPCR(reg) MRC15(reg, 3, c15, c0, 4) +#define RCP15_L2PMCCNTCR(reg) MRC15(reg, 3, c15, c4, 4) +#define RCP15_L2PMCCNTR(reg) MRC15(reg, 3, c15, c4, 5) +#define RCP15_L2PMCCNTSR(reg) MRC15(reg, 3, c15, c4, 6) +#define RCP15_L2PMCNTENCLR(reg) MRC15(reg, 3, c15, c4, 2) +#define RCP15_L2PMCNTENSET(reg) MRC15(reg, 3, c15, c4, 3) +#define RCP15_L2PMCR(reg) MRC15(reg, 3, c15, c4, 0) +#define RCP15_L2PMINTENCLR(reg) MRC15(reg, 3, c15, c5, 0) +#define RCP15_L2PMINTENSET(reg) MRC15(reg, 3, c15, c5, 1) +#define RCP15_L2PMOVSR(reg) MRC15(reg, 3, c15, c4, 1) +#define RCP15_L2PMRLDR(reg) MRC15(reg, 3, c15, c4, 7) +#define RCP15_L2PMSELR(reg) MRC15(reg, 3, c15, c6, 0) +#define RCP15_L2PMXEVCNTCR(reg) MRC15(reg, 3, c15, c6, 4) +#define RCP15_L2PMXEVCNTR(reg) MRC15(reg, 3, c15, c6, 5) +#define RCP15_L2PMXEVCNTSR(reg) MRC15(reg, 3, c15, c6, 6) +#define RCP15_L2PMXEVTYPER(reg) MRC15(reg, 3, c15, c6, 7) +#define RCP15_L2PMXEVFILTER(reg) MRC15(reg, 3, c15, c6, 3) +#define RCP15_L2PMEVTYPER0(reg) MRC15(reg, 3, c15, c7, 0) +#define RCP15_L2PMEVTYPER1(reg) MRC15(reg, 3, c15, c7, 1) +#define RCP15_L2PMEVTYPER2(reg) MRC15(reg, 3, c15, c7, 2) +#define RCP15_L2PMEVTYPER3(reg) MRC15(reg, 3, c15, c7, 3) +#define RCP15_L2PMEVTYPER4(reg) MRC15(reg, 3, c15, c7, 4) +#define RCP15_L2VR3F1(reg) MRC15(reg, 3, c15, c15, 1) + +#endif + diff --git a/arch/arm/perfmon/mcrmrc.h b/arch/arm/perfmon/mcrmrc.h new file mode 100644 index 00000000000..29f9ac0f6dd --- /dev/null +++ b/arch/arm/perfmon/mcrmrc.h @@ -0,0 +1,86 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* +mrcmcr.h + +DESCRIPTION: Convenience macros for access the cp registers in the arm. + +REV/DATE: Fri Mar 18 16:34:44 EST 2005 +*/ + +#ifndef __mrcmcr__h_ +#define __mrcmcr__h_ + +/* +* Define some convenience macros to acccess the cp registers from c code +* Lots of macro trickery here. +* +* Takes the same format as the asm instructions and unfortunatly you cannot +* use variables to select the crn, crn or op fields... +* +* For those unfamiliar with the # and string stuff. +* # creates a string from the value and any two strings that are beside +* are concatenated...thus these create one big asm string for the +* inline asm code. +* +* When compiled these compile to single asm instructions (fast) but +* without all the hassel of __asm__ __volatile__ (...) =r +* +* Format is: +* +* unsigned long reg; // destination variable +* MRC(reg, p15, 0, c1, c0, 0 ); +* +* MRC read control register +* MCR control register write +*/ + +/* +* Some assembly macros so we can use the same macros as in the C version. +* Turns the ASM code a little C-ish but keeps the code consistent and in +* one location... +*/ +#ifdef __ASSEMBLY__ + + +#define MRC(reg, processor, op1, crn, crm, op2) \ +(mrc processor , op1 , reg, crn , crm , op2) + +#define MCR(reg, processor, op1, crn, crm, op2) \ +(mcr processor , op1 , reg, crn , crm , op2) + +/* +* C version of the macros. +*/ +#else + +#define MRC(reg, processor, op1, crn, crm, op2) \ +__asm__ __volatile__ ( \ +" mrc " #processor "," #op1 ", %0," #crn "," #crm "," #op2 "\n" \ +: "=r" (reg)) + +#define MCR(reg, processor, op1, crn, crm, op2) \ +__asm__ __volatile__ ( \ +" mcr " #processor "," #op1 ", %0," #crn "," #crm "," #op2 "\n" \ +: : "r" (reg)) +#endif + + +/* +* Easy access convenience function to read CP15 registers from c code +*/ +#define MRC15(reg, op1, crn, crm, op2) MRC(reg, p15, op1, crn, crm, op2) +#define MCR15(reg, op1, crn, crm, op2) MCR(reg, p15, op1, crn, crm, op2) + +#endif diff --git a/arch/arm/perfmon/per-axi.c b/arch/arm/perfmon/per-axi.c new file mode 100644 index 00000000000..48309bed82e --- /dev/null +++ b/arch/arm/perfmon/per-axi.c @@ -0,0 +1,759 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* +per-axi +DESCRIPTION +Functions related to AXI bus performance counter manipulations. +*/ + +#include +#include +#include +#include +#include +#include "asm/uaccess.h" +#include "per-axi.h" +#include "perf.h" + +/* +Definitions for AXI register addresses, macros to set and get register values +*/ +#define AXI_BASE_SIZE 0x00004000 +#define AXI_REG_BASE (AXI_BASE + 0x00000000) +#define AXI_REG_BASE_PHYS 0xa8200000 + +#define __inpdw(port) ioread32(port) +#define in_dword_masked(addr, mask) (__inpdw(addr) & (mask)) +#define __outpdw(port, val) (iowrite32((uint32_t) (val), port)) +#define out_dword(addr, val) __outpdw(addr, val) + +#define HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_ADDR \ + (AXI_REG_BASE + 0x00003434) +#define HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_RMSK 0xffff +#define HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_IN \ + in_dword_masked(HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_ADDR, \ + HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_RMSK) + +#define HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_ADDR (AXI_REG_BASE + 0x00003438) +#define HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_RMSK 0xffff +#define HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_IN \ + in_dword_masked(HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_ADDR, \ + HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_RMSK) + +#define HWIO_AXI_MONITOR_SELECTION_REG0_ADDR (AXI_REG_BASE + 0x00003428) +#define HWIO_AXI_MONITOR_SELECTION_REG1_ADDR (AXI_REG_BASE + 0x0000342c) +#define HWIO_AXI_MONITOR_TENURE_SELECTION_REG_ADDR (AXI_REG_BASE + 0x00003430) +#define HWIO_AXI_MONITOR_SELECTION_REG0_ETC_BMSK 0x4000 +#define HWIO_AXI_MONITOR_SELECTION_REG0_ECC_BMSK 0x2000 +#define HWIO_AXI_MONITOR_SELECTION_REG0_EEC1_BMSK 0x800 +#define HWIO_AXI_MONITOR_SELECTION_REG0_EEC0_BMSK 0x200 +#define HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_OUT(v) \ + out_dword(HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_ADDR, v) +#define HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_OUT(v) \ + out_dword(HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_ADDR, v) +#define HWIO_AXI_MONITOR_SELECTION_REG0_OUT(v) \ + out_dword(HWIO_AXI_MONITOR_SELECTION_REG0_ADDR, v) +#define HWIO_AXI_MONITOR_SELECTION_REG1_OUT(v) \ + out_dword(HWIO_AXI_MONITOR_SELECTION_REG1_ADDR, v) +#define HWIO_AXI_MONITOR_TENURE_SELECTION_REG_OUT(v) \ + out_dword(HWIO_AXI_MONITOR_TENURE_SELECTION_REG_ADDR, v) +#define HWIO_AXI_MONITOR_SELECTION_REG0_RMSK 0xffff +#define HWIO_AXI_MONITOR_SELECTION_REG0_IN \ + in_dword_masked(HWIO_AXI_MONITOR_SELECTION_REG0_ADDR, \ + HWIO_AXI_MONITOR_SELECTION_REG0_RMSK) + +#define HWIO_AXI_CONFIGURATION_REG_ADDR (AXI_REG_BASE + 0x00000008) +#define HWIO_AXI_CONFIGURATION_REG_OUT(v) \ + out_dword(HWIO_AXI_CONFIGURATION_REG_ADDR, v) +#define HWIO_AXI_CONFIGURATION_REG_PPDM_BMSK 0x0 +#define HWIO_AXI_CONFIGURATION_REG_DISABLE 0x2 +#define AXI_EVTSEL_ENABLE_MASK 0x6a00 +#define AXI_EVTSEL_DISABLE_MASK 0x95ff +#define AXI_EVTSEL_RESET_MASK 0xfe40 + +#define HWIO_AXI_MONITOR_EVENT_LOWER_REG0_ADDR (AXI_REG_BASE + 0x00003450) +#define HWIO_AXI_MONITOR_EVENT_LOWER_REG0_RMSK 0xffff +#define HWIO_AXI_MONITOR_EVENT_LOWER_REG0_SHFT 0 +#define HWIO_AXI_MONITOR_EVENT_LOWER_REG0_IN \ + in_dword_masked(HWIO_AXI_MONITOR_EVENT_LOWER_REG0_ADDR, \ + HWIO_AXI_MONITOR_EVENT_LOWER_REG0_RMSK) +#define HWIO_AXI_MONITOR_EVENT_UPPER_REG0_ADDR (AXI_REG_BASE + 0x00003454) +#define HWIO_AXI_MONITOR_EVENT_UPPER_REG0_RMSK 0xffff +#define HWIO_AXI_MONITOR_EVENT_UPPER_REG0_SHFT 0 +#define HWIO_AXI_MONITOR_EVENT_UPPER_REG0_IN \ + in_dword_masked(HWIO_AXI_MONITOR_EVENT_UPPER_REG0_ADDR, \ + HWIO_AXI_MONITOR_EVENT_UPPER_REG0_RMSK) + +#define HWIO_AXI_MONITOR_EVENT_LOWER_REG1_ADDR (AXI_REG_BASE + 0x00003458) +#define HWIO_AXI_MONITOR_EVENT_LOWER_REG1_RMSK 0xffff +#define HWIO_AXI_MONITOR_EVENT_LOWER_REG1_SHFT 0 +#define HWIO_AXI_MONITOR_EVENT_LOWER_REG1_IN \ + in_dword_masked(HWIO_AXI_MONITOR_EVENT_LOWER_REG1_ADDR, \ + HWIO_AXI_MONITOR_EVENT_LOWER_REG1_RMSK) +#define HWIO_AXI_MONITOR_EVENT_UPPER_REG1_ADDR (AXI_REG_BASE + 0x0000345c) +#define HWIO_AXI_MONITOR_EVENT_UPPER_REG1_RMSK 0xffff +#define HWIO_AXI_MONITOR_EVENT_UPPER_REG1_SHFT 0 +#define HWIO_AXI_MONITOR_EVENT_UPPER_REG1_IN \ + in_dword_masked(HWIO_AXI_MONITOR_EVENT_UPPER_REG1_ADDR, \ + HWIO_AXI_MONITOR_EVENT_UPPER_REG1_RMSK) + +#define HWIO_AXI_MONITOR_TENURE_LOWER_REG_ADDR (AXI_REG_BASE + 0x00003448) +#define HWIO_AXI_MONITOR_TENURE_LOWER_REG_RMSK 0xffff +#define HWIO_AXI_MONITOR_TENURE_LOWER_REG_SHFT 0 +#define HWIO_AXI_MONITOR_TENURE_LOWER_REG_IN \ + in_dword_masked(HWIO_AXI_MONITOR_TENURE_LOWER_REG_ADDR, \ + HWIO_AXI_MONITOR_TENURE_LOWER_REG_RMSK) +#define HWIO_AXI_MONITOR_TENURE_UPPER_REG_ADDR (AXI_REG_BASE + 0x00003444) +#define HWIO_AXI_MONITOR_TENURE_UPPER_REG_RMSK 0xffff +#define HWIO_AXI_MONITOR_TENURE_UPPER_REG_SHFT 0 +#define HWIO_AXI_MONITOR_TENURE_UPPER_REG_IN \ + in_dword_masked(HWIO_AXI_MONITOR_TENURE_UPPER_REG_ADDR, \ + HWIO_AXI_MONITOR_TENURE_UPPER_REG_RMSK) + +#define HWIO_AXI_MONITOR_MIN_REG_ADDR (AXI_REG_BASE + 0x0000343c) +#define HWIO_AXI_MONITOR_MIN_REG_RMSK 0xffff +#define HWIO_AXI_MONITOR_MIN_REG_SHFT 0 +#define HWIO_AXI_MONITOR_MIN_REG_IN \ + in_dword_masked(HWIO_AXI_MONITOR_MIN_REG_ADDR, \ + HWIO_AXI_MONITOR_MIN_REG_RMSK) +#define HWIO_AXI_MONITOR_MAX_REG_ADDR (AXI_REG_BASE + 0x00003440) +#define HWIO_AXI_MONITOR_MAX_REG_RMSK 0xffff +#define HWIO_AXI_MONITOR_MAX_REG_SHFT 0 +#define HWIO_AXI_MONITOR_MAX_REG_IN \ + in_dword_masked(HWIO_AXI_MONITOR_MAX_REG_ADDR, \ + HWIO_AXI_MONITOR_MAX_REG_RMSK) +#define HWIO_AXI_MONITOR_LAST_TENURE_REG_ADDR (AXI_REG_BASE + 0x0000344c) +#define HWIO_AXI_MONITOR_LAST_TENURE_REG_RMSK 0xffff +#define HWIO_AXI_MONITOR_LAST_TENURE_REG_SHFT 0 +#define HWIO_AXI_MONITOR_LAST_TENURE_REG_IN \ + in_dword_masked(HWIO_AXI_MONITOR_LAST_TENURE_REG_ADDR, \ + HWIO_AXI_MONITOR_LAST_TENURE_REG_RMSK) +#define HWIO_AXI_MONITOR_TENURE_UPPER_REG_OUT(v) \ + out_dword(HWIO_AXI_MONITOR_TENURE_UPPER_REG_ADDR, v) +#define HWIO_AXI_MONITOR_TENURE_LOWER_REG_OUT(v) \ + out_dword(HWIO_AXI_MONITOR_TENURE_LOWER_REG_ADDR, v) + +#define HWIO_AXI_RESET_ALL 0x9400 +#define HWIO_AXI_ENABLE_ALL_NOCYCLES 0x4a00 +#define HWIO_AXI_DISABLE_ALL 0xb500 +uint32_t AXI_BASE; + +unsigned int is_first = 1; +struct perf_mon_axi_data pm_axi_info; +struct perf_mon_axi_cnts axi_cnts; + +/* +FUNCTION get_axi_sel_reg0 + +DESCRIPTION + Retrieve the value of AXI_SEL_REG0 + +DEPENDENCIES + +RETURN VALUE + AXI_SEL_REG0 +SIDE EFFECTS +*/ +unsigned long get_axi_sel_reg0(void) +{ + return pm_axi_info.sel_reg0; +} + +/* +FUNCTION get_axi_sel_reg1 + +DESCRIPTION + Retrieve the value of AXI_SEL_REG1 + +DEPENDENCIES + +RETURN VALUE + AXI_SEL_REG1 +SIDE EFFECTS +*/ +unsigned long get_axi_sel_reg1(void) +{ + return pm_axi_info.sel_reg1; +} + +/* +FUNCTION get_axi_ten_sel_reg + +DESCRIPTION + Retrieve the value of AXI_TEN_REG + +DEPENDENCIES + +RETURN VALUE + AXI_TEN_REG +SIDE EFFECTS +*/ +unsigned long get_axi_ten_sel_reg(void) +{ + return pm_axi_info.ten_sel_reg; +} + +/* +FUNCTION get_axi_valid + +DESCRIPTION + Retrieve the value of AXI valid bit + +DEPENDENCIES + +RETURN VALUE + AXI Valid bit +SIDE EFFECTS +*/ +unsigned long get_axi_valid(void) +{ + return pm_axi_info.valid; +} + +/* +FUNCTION get_axi_enable + +DESCRIPTION + Retrieve the value of AXI enable bit + +DEPENDENCIES + +RETURN VALUE + AXI enable bit +SIDE EFFECTS +*/ +unsigned long get_axi_enable(void) +{ + return pm_axi_info.enable; +} + +/* +FUNCTION get_axi_clear + +DESCRIPTION + Retrieve the value of AXI clear bit + +DEPENDENCIES + +RETURN VALUE + AXI clear bit +SIDE EFFECTS +*/ +unsigned long get_axi_clear(void) +{ + return pm_axi_info.clear; +} + +/* +FUNCTION pm_axi_cnts_write + +DESCRIPTION + Write handler for the /proc axi results directory. + +DEPENDENCIES + +RETURN VALUE + Number of characters to output. + +SIDE EFFECTS +*/ +int pm_axi_cnts_write(struct file *file, const char *buff, + unsigned long cnt, void *data) +{ + char *newbuf; + struct PerfMonAxiCnts *p = + (struct PerfMonAxiCnts *)data; + + if (p == 0) + return cnt; + /* + * Alloc the user data in kernel space. and then copy user to kernel + */ + newbuf = kmalloc(cnt + 1, GFP_KERNEL); + if (0 == newbuf) + return cnt; + if (copy_from_user(newbuf, buff, cnt) != 0) { + printk(KERN_INFO "%s copy_from_user failed\n", __func__); + return cnt; + } + return cnt; +} + +/* +FUNCTION pm_axi_update_cnts + +DESCRIPTION + Read the current AXI counter values. Check for overflows and + adjust the values stored accordingly. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void pm_axi_update_cnts(void) +{ + if (is_first) { + pm_axi_start(); + } else { + if (pm_axi_info.valid == 1) { + pm_axi_info.valid = 0; + pm_axi_update(); + } else { + pm_axi_enable(); + } + } + is_first = 0; + axi_cnts.cycles += pm_get_axi_cycle_count(); + axi_cnts.cnt0 += pm_get_axi_evt0_count(); + axi_cnts.cnt1 += pm_get_axi_evt1_count(); + axi_cnts.tenure_total += pm_get_axi_ten_total_count(); + + axi_cnts.tenure_min = pm_get_axi_ten_min_count(); + axi_cnts.tenure_max = pm_get_axi_ten_max_count(); + axi_cnts.tenure_last = pm_get_axi_ten_last_count(); + + pm_axi_start(); +} + +/* +FUNCTION pm_axi_clear_cnts + +DESCRIPTION + Clear the locally stored AXI counter values. + Also clear the AXI counter registers. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void pm_axi_clear_cnts(void) +{ + axi_cnts.cycles = 0; + axi_cnts.cnt0 = 0; + axi_cnts.cnt1 = 0; + axi_cnts.tenure_total = 0; + axi_cnts.tenure_min = 0; + axi_cnts.tenure_max = 0; + axi_cnts.tenure_last = 0; + pm_axi_start(); +} + +/* +FUNCTION pm_axi_read_decimal + +DESCRIPTION + Read handler for the /proc axi results directory in decimal format. + +DEPENDENCIES + +RETURN VALUE + Number of characters to output. + +SIDE EFFECTS +*/ +int pm_axi_read_decimal(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + struct perf_mon_axi_cnts *p = (struct perf_mon_axi_cnts *)data; + + return sprintf(page, "cnt0:%llu cnt1:%llu tenure:%llu ten_max:%llu \ + ten_min:%llu ten_last:%llu cycles:%llu\n", + p->cnt0, + p->cnt1, + p->tenure_total, + p->tenure_max, + p->tenure_min, + p->tenure_last, + p->cycles); +} + +/* +FUNCTION pm_axi_read_hex + +DESCRIPTION + Read handler for the /proc axi results directory in hex format. + +DEPENDENCIES + +RETURN VALUE + Number of characters to output. + +SIDE EFFECTS +*/ +int pm_axi_read_hex(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + struct perf_mon_axi_cnts *p = (struct perf_mon_axi_cnts *)data; + + return sprintf(page, "cnt0:%llx cnt1:%llx tenure:%llx ten_max:%llx \ + ten_min:%llx ten_last:%llx cycles:%llx\n", + p->cnt0, + p->cnt1, + p->tenure_total, + p->tenure_max, + p->tenure_min, + p->tenure_last, + p->cycles); + +} + +/* +FUNCTION pm_axi_set_proc_entry + +DESCRIPTION + Create a generic entry for the /proc axi settings directory. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void pm_axi_set_proc_entry(char *name, unsigned long *var, + struct proc_dir_entry *d, int hex) +{ + struct proc_dir_entry *pe; + pe = create_proc_entry(name, 0777, d); + if (0 == pe) + return; + if (hex) { + pe->read_proc = per_process_read; + pe->write_proc = per_process_write_hex; + } else { + pe->read_proc = per_process_read_decimal; + pe->write_proc = per_process_write_dec; + } + pe->data = (void *)var; +} + +/* +FUNCTION pm_axi_get_cnt_proc_entry + +DESCRIPTION + Create a generic entry for the /proc axi results directory. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void pm_axi_get_cnt_proc_entry(char *name, struct perf_mon_axi_cnts *var, + struct proc_dir_entry *d, int hex) +{ + struct proc_dir_entry *pe; + pe = create_proc_entry(name, 0777, d); + if (0 == pe) + return; + if (hex) { + pe->read_proc = pm_axi_read_hex; + pe->write_proc = pm_axi_cnts_write; + } else { + pe->read_proc = pm_axi_read_decimal; + pe->write_proc = pm_axi_cnts_write; + } + pe->data = (void *)var; +} + +/* +FUNCTION pm_axi_clear_tenure + +DESCRIPTION + Clear AXI tenure cntr manually. Temporary solution till hardware bug + is fixed + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void pm_axi_clear_tenure(void) +{ + HWIO_AXI_MONITOR_TENURE_UPPER_REG_OUT(0x0); + HWIO_AXI_MONITOR_TENURE_LOWER_REG_OUT(0x0); +} + +/* +FUNCTION pm_axi_init + +DESCRIPTION + Map AXI region to virtual memory. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void pm_axi_init() +{ + /*Map the AXI regs*/ + #ifdef CONFIG_ARCH_QSD8X50 + { + /*Map the AXI regs*/ + AXI_BASE = (uint32_t)ioremap(AXI_REG_BASE_PHYS, AXI_BASE_SIZE); + if (!AXI_BASE) + printk(KERN_ERR "Mem map failed\n"); + } + #else + { + AXI_BASE = (uint32_t)kmalloc(AXI_BASE_SIZE, GFP_KERNEL); + } + #endif + +} + +/* +FUNCTION pm_axi_start + +DESCRIPTION + Set event0, event1 and tenure registers based on the /proc entries. + Set cycle cntr to fffffffe to start counters. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void +pm_axi_start() +{ + unsigned long sel_reg0, sel_reg1, ten_sel_reg; + sel_reg0 = get_axi_sel_reg0(); + sel_reg1 = get_axi_sel_reg1(); + ten_sel_reg = get_axi_ten_sel_reg(); + HWIO_AXI_CONFIGURATION_REG_OUT(HWIO_AXI_CONFIGURATION_REG_PPDM_BMSK); + /*Set AXI Cycle Counter to enable AXI Monitors*/ + HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_OUT(0xffff); + HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_OUT(0xfffe); + /*Set master/slave*/ + HWIO_AXI_MONITOR_SELECTION_REG1_OUT(sel_reg1); + HWIO_AXI_MONITOR_SELECTION_REG0_OUT(HWIO_AXI_RESET_ALL); + HWIO_AXI_MONITOR_SELECTION_REG0_OUT(HWIO_AXI_ENABLE_ALL_NOCYCLES); + HWIO_AXI_MONITOR_SELECTION_REG0_OUT(HWIO_AXI_MONITOR_SELECTION_REG0_IN + | sel_reg0); + HWIO_AXI_MONITOR_SELECTION_REG0_OUT(HWIO_AXI_MONITOR_SELECTION_REG0_IN + | HWIO_AXI_MONITOR_SELECTION_REG0_ECC_BMSK); + HWIO_AXI_CONFIGURATION_REG_OUT(HWIO_AXI_CONFIGURATION_REG_PPDM_BMSK); +} + +/* +FUNCTION pm_axi_update + +DESCRIPTION + Set event0, event1 and tenure registers based on the /proc entries. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void +pm_axi_update() +{ + HWIO_AXI_CONFIGURATION_REG_OUT(HWIO_AXI_CONFIGURATION_REG_PPDM_BMSK); + HWIO_AXI_MONITOR_SELECTION_REG0_OUT(HWIO_AXI_MONITOR_SELECTION_REG0_IN + | HWIO_AXI_RESET_ALL); + HWIO_AXI_MONITOR_SELECTION_REG0_OUT(HWIO_AXI_MONITOR_SELECTION_REG0_IN + & HWIO_AXI_DISABLE_ALL); + pm_axi_start(); +} + +/* +FUNCTION pm_axi_disable + +DESCRIPTION + Disable all cntrs. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void +pm_axi_disable(void) +{ + unsigned long sel_reg0; + /*Disable cntrs*/ + sel_reg0 = get_axi_sel_reg0(); + HWIO_AXI_MONITOR_SELECTION_REG0_OUT(sel_reg0 & AXI_EVTSEL_DISABLE_MASK); + /*Disable clk*/ + HWIO_AXI_CONFIGURATION_REG_OUT(HWIO_AXI_CONFIGURATION_REG_DISABLE); +} + +/* +FUNCTION pm_axi_enable + +DESCRIPTION + Enable all cntrs. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void +pm_axi_enable(void) +{ + unsigned long sel_reg0; + /*Enable cntrs*/ + sel_reg0 = get_axi_sel_reg0(); + HWIO_AXI_MONITOR_SELECTION_REG0_OUT(sel_reg0 | 0x6a00); + /*Enable clk*/ + HWIO_AXI_CONFIGURATION_REG_OUT(HWIO_AXI_CONFIGURATION_REG_PPDM_BMSK); +} + +/* +FUNCTION pm_axi_disable_cnts + +DESCRIPTION + Read cycle cntr value + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +unsigned long +pm_get_axi_cycle_count(void) +{ + if (HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_IN == 0x0 && + HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_IN == 0x0) { + /*Set AXI Cycle Counter to enable AXI Monitors*/ + HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_OUT(0xffff); + HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_OUT(0xfffe); + } + return 0xfffffffe - ((HWIO_AXI_MONITOR_CYCLE_COUNT_UPPER_REG_IN << 16) + + HWIO_AXI_MONITOR_CYCLE_COUNT_LOWER_REG_IN); +} + +/* +FUNCTION pm_get_axi_evt0_count + +DESCRIPTION + Read Event0 cntr value + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +unsigned long +pm_get_axi_evt0_count(void) +{ + return (HWIO_AXI_MONITOR_EVENT_UPPER_REG0_IN << 16) + + HWIO_AXI_MONITOR_EVENT_LOWER_REG0_IN; +} + +/* +FUNCTION pm_get_axi_evt1_count + +DESCRIPTION + Read Event1 cntr value + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +unsigned long +pm_get_axi_evt1_count(void) +{ + return (HWIO_AXI_MONITOR_EVENT_UPPER_REG1_IN << 16) + + HWIO_AXI_MONITOR_EVENT_LOWER_REG1_IN; +} + +/* +FUNCTION pm_get_axi_ten_min_count + +DESCRIPTION + Read min tenure cntr value + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +unsigned long +pm_get_axi_ten_min_count(void) +{ + return HWIO_AXI_MONITOR_MIN_REG_IN; +} + +/* +FUNCTION pm_get_axi_ten_max_count + +DESCRIPTION + Read max tenure cntr value + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +unsigned long +pm_get_axi_ten_max_count(void) +{ + return HWIO_AXI_MONITOR_MAX_REG_IN; +} + +/* +FUNCTION pm_get_axi_ten_total_count + +DESCRIPTION + Read total tenure cntr value + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +unsigned long +pm_get_axi_ten_total_count(void) +{ + return (HWIO_AXI_MONITOR_TENURE_UPPER_REG_IN << 16) + + HWIO_AXI_MONITOR_TENURE_LOWER_REG_IN; +} + +/* +FUNCTION pm_get_axi_ten_last_count + +DESCRIPTION + Read last tenure cntr value + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +unsigned long +pm_get_axi_ten_last_count(void) +{ + return HWIO_AXI_MONITOR_LAST_TENURE_REG_IN; +} diff --git a/arch/arm/perfmon/per-axi.h b/arch/arm/perfmon/per-axi.h new file mode 100644 index 00000000000..89f67fc5060 --- /dev/null +++ b/arch/arm/perfmon/per-axi.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* +*per-axi +*DESCRIPTION +*Header File for Functions related to AXI bus performance counter manipulations. +*/ + +#ifndef __PER_AXI_H__ +#define __PER_AXI_H__ +unsigned long pm_get_axi_cycle_count(void); +unsigned long pm_get_axi_evt0_count(void); +unsigned long pm_get_axi_evt1_count(void); +unsigned long pm_get_axi_evt2_count(void); +unsigned long pm_get_axi_ten_min_count(void); +unsigned long pm_get_axi_ten_max_count(void); +unsigned long pm_get_axi_ten_total_count(void); +unsigned long pm_get_axi_ten_last_count(void); + +unsigned long get_axi_sel_reg0(void); +unsigned long get_axi_sel_seg1(void); +unsigned long get_axi_ten_sel_reg(void); +unsigned long get_axi_valid(void); +unsigned long get_axi_enable(void); +unsigned long get_axi_clear(void); + +void pm_axi_clear_cnts(void); +void pm_axi_update_cnts(void); + +void pm_axi_init(void); +void pm_axi_start(void); +void pm_axi_update(void); +void pm_axi_disable(void); +void pm_axi_enable(void); + +struct perf_mon_axi_cnts{ + unsigned long long cycles; + unsigned long long cnt0; + unsigned long long cnt1; + unsigned long long tenure_total; + unsigned long long tenure_min; + unsigned long long tenure_max; + unsigned long long tenure_last; +}; + +struct perf_mon_axi_data{ + struct proc_dir_entry *proc; + unsigned long enable; + unsigned long clear; + unsigned long valid; + unsigned long sel_reg0; + unsigned long sel_reg1; + unsigned long ten_sel_reg; + unsigned long refresh; +}; + +extern struct perf_mon_axi_data pm_axi_info; +extern struct perf_mon_axi_cnts axi_cnts; + +void pm_axi_set_proc_entry(char *name, unsigned long *var, + struct proc_dir_entry *d, int hex); +void pm_axi_get_cnt_proc_entry(char *name, struct perf_mon_axi_cnts *var, + struct proc_dir_entry *d, int hex); + +#endif diff --git a/arch/arm/perfmon/per-process-perf.c b/arch/arm/perfmon/per-process-perf.c new file mode 100644 index 00000000000..c8bebd842d1 --- /dev/null +++ b/arch/arm/perfmon/per-process-perf.c @@ -0,0 +1,1251 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +*/ + +/* +per-process_perf +DESCRIPTION +Capture the processor performances registers when the process context +switches. The /proc file system is used to control and access the results +of the performance counters. + +Each time a process is context switched, the performance counters for +the Snoop Control Unit and the standard ARM counters are set according +to the values stored for that process. + +The events to capture per process are set in the /proc/ppPerf/settings +directory. + +EXTERNALIZED FUNCTIONS + +INITIALIZATION AND SEQUENCING REQUIREMENTS +Detail how to initialize and use this service. The sequencing aspect +is only needed if the order of operations is important. +*/ + +/* +INCLUDE FILES FOR MODULE +*/ +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" +#include "linux/kernel_stat.h" +#include +#include "asm/uaccess.h" +#include "cp15_registers.h" +#include "l2_cp15_registers.h" +#include +#include "per-axi.h" +#include "perf.h" + +#define DEBUG_SWAPIO +#ifdef DEBUG_SWAPIO +#define MR_SIZE 1024 +#define PM_PP_ERR -1 +struct mark_data_s { + long c; + long cpu; + unsigned long pid_old; + unsigned long pid_new; +}; + +struct mark_data_s markRay[MR_SIZE] __attribute__((aligned(16))); +int mrcnt; + +DEFINE_SPINLOCK(_mark_lock); + +static inline void MARKPIDS(char a, int opid, int npid) +{ + int cpu = smp_processor_id(); + + if (opid == 0) + return; + spin_lock(&_mark_lock); + if (++mrcnt >= MR_SIZE) + mrcnt = 0; + spin_unlock(&_mark_lock); + + markRay[mrcnt].pid_old = opid; + markRay[mrcnt].pid_new = npid; + markRay[mrcnt].cpu = cpu; + markRay[mrcnt].c = a; +} +static inline void MARK(char a) { MARKPIDS(a, 0xFFFF, 0xFFFF); } +static inline void MARKPID(char a, int pid) { MARKPIDS(a, pid, 0xFFFF); } + +#else +#define MARK(a) +#define MARKPID(a, b) +#define MARKPIDS(a, b, c) + +#endif /* DEBUG_SWAPIO */ + +/* +DEFINITIONS AND DECLARATIONS FOR MODULE + +This section contains definitions for constants, macros, types, variables +and other items needed by this module. +*/ + +/* +Constant / Define Declarations +*/ + +#define PERF_MON_PROCESS_NUM 0x400 +#define PERF_MON_PROCESS_MASK (PERF_MON_PROCESS_NUM-1) +#define PP_MAX_PROC_ENTRIES 32 + +/* + * The entry is locked and is not to be replaced. + */ +#define PERF_ENTRY_LOCKED (1<<0) +#define PERF_NOT_FIRST_TIME (1<<1) +#define PERF_EXITED (1<<2) +#define PERF_AUTOLOCK (1<<3) + +#define IS_LOCKED(p) (p->flags & PERF_ENTRY_LOCKED) + +#define PERF_NUM_MONITORS 4 + +#define L1_EVENTS_0 0 +#define L1_EVENTS_1 1 +#define L2_EVENTS_0 2 +#define L2_EVENTS_1 3 + +#define PM_CYCLE_OVERFLOW_MASK 0x80000000 +#define L2_PM_CYCLE_OVERFLOW_MASK 0x80000000 + +#define PM_START_ALL() do {\ + if (pm_global) \ + pmStartAll();\ + } while (0); +#define PM_STOP_ALL() do {\ + if (pm_global)\ + pmStopAll();\ + } while (0); +#define PM_RESET_ALL() do {\ + if (pm_global)\ + pmResetAll();\ + } while (0); + +/* + * Accessors for SMP based variables. + */ +#define _SWAPS(p) ((p)->cnts[smp_processor_id()].swaps) +#define _CYCLES(p) ((p)->cnts[smp_processor_id()].cycles) +#define _COUNTS(p, i) ((p)->cnts[smp_processor_id()].counts[i]) +#define _L2COUNTS(p, i) ((p)->cnts[smp_processor_id()].l2_counts[i]) +#define _L2CYCLES(p) ((p)->cnts[smp_processor_id()].l2_cycles) + +/* + Type Declarations +*/ + +/* + * Counts are on a per core basis. + */ +struct pm_counters_s { + unsigned long long cycles; + unsigned long long l2_cycles; + unsigned long long counts[PERF_NUM_MONITORS]; + unsigned long long l2_counts[PERF_NUM_MONITORS]; + unsigned long swaps; +}; + +struct per_process_perf_mon_type{ + struct pm_counters_s cnts[NR_CPUS]; + unsigned long control; + unsigned long index[PERF_NUM_MONITORS]; + unsigned long l2_index[PERF_NUM_MONITORS]; + unsigned long pid; + struct proc_dir_entry *proc; + struct proc_dir_entry *l2_proc; + unsigned short flags; + unsigned short running_cpu; + char *pidName; + unsigned long lpm0evtyper; + unsigned long lpm1evtyper; + unsigned long lpm2evtyper; + unsigned long l2lpmevtyper; + unsigned long vlpmevtyper; + unsigned long l2pmevtyper0; + unsigned long l2pmevtyper1; + unsigned long l2pmevtyper2; + unsigned long l2pmevtyper3; + unsigned long l2pmevtyper4; +}; + +unsigned long last_in_pid[NR_CPUS]; +unsigned long fake_swap_out[NR_CPUS] = {0}; + +/* + Local Object Definitions +*/ +struct per_process_perf_mon_type perf_mons[PERF_MON_PROCESS_NUM]; +struct proc_dir_entry *proc_dir; +struct proc_dir_entry *settings_dir; +struct proc_dir_entry *values_dir; +struct proc_dir_entry *axi_dir; +struct proc_dir_entry *l2_dir; +struct proc_dir_entry *axi_settings_dir; +struct proc_dir_entry *axi_results_dir; +struct proc_dir_entry *l2_results_dir; + +unsigned long pp_enabled; +unsigned long pp_settings_valid = -1; +unsigned long pp_auto_lock; +unsigned long pp_set_pid; +signed long pp_clear_pid = -1; +unsigned long per_proc_event[PERF_NUM_MONITORS]; +unsigned long l2_per_proc_event[PERF_NUM_MONITORS]; +unsigned long dbg_flags; +unsigned long pp_lpm0evtyper; +unsigned long pp_lpm1evtyper; +unsigned long pp_lpm2evtyper; +unsigned long pp_l2lpmevtyper; +unsigned long pp_vlpmevtyper; +unsigned long pm_stop_for_interrupts; +unsigned long pm_global; /* track all, not process based */ +unsigned long pm_global_enable; +unsigned long pm_remove_pid; + +unsigned long pp_l2pmevtyper0; +unsigned long pp_l2pmevtyper1; +unsigned long pp_l2pmevtyper2; +unsigned long pp_l2pmevtyper3; +unsigned long pp_l2pmevtyper4; + +unsigned long pp_proc_entry_index; +char *per_process_proc_names[PP_MAX_PROC_ENTRIES]; + +unsigned int axi_swaps; +#define MAX_AXI_SWAPS 10 +int first_switch = 1; +/* + Forward Declarations +*/ + +/* +Function Definitions +*/ + +/* +FUNCTION per_process_find + +DESCRIPTION + Find the per process information based on the process id (pid) passed. + This is a simple mask based on the number of entries stored in the + static array + +DEPENDENCIES + +RETURN VALUE + Pointer to the per process data +SIDE EFFECTS + +*/ +struct per_process_perf_mon_type *per_process_find(unsigned long pid) +{ + return &perf_mons[pid & PERF_MON_PROCESS_MASK]; +} + +/* +FUNCTION per_process_get_name + +DESCRIPTION + Retreive the name of the performance counter based on the table and + index passed. We have two different sets of performance counters so + different table need to be used. + +DEPENDENCIES + +RETURN VALUE + Pointer to char string with the name of the event or "BAD" + Never returns NULL or a bad pointer. + +SIDE EFFECTS +*/ +char *per_process_get_name(unsigned long index) +{ + return pm_find_event_name(index); +} + +/* +FUNCTION per_process_results_read + +DESCRIPTION + Print out the formatted results from the process id read. Event names + and counts are printed. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +int per_process_results_read(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + struct per_process_perf_mon_type *p = + (struct per_process_perf_mon_type *)data; + struct pm_counters_s cnts; + int i, j; + + /* + * Total across all CPUS + */ + memset(&cnts, 0, sizeof(cnts)); + for (i = 0; i < num_possible_cpus(); i++) { + cnts.swaps += p->cnts[i].swaps; + cnts.cycles += p->cnts[i].cycles; + for (j = 0; j < PERF_NUM_MONITORS; j++) + cnts.counts[j] += p->cnts[i].counts[j]; + } + + /* + * Display as single results of the totals calculated above. + * Do we want to display or have option to display individula cores? + */ + return sprintf(page, "pid:%lu one:%s:%llu two:%s:%llu three:%s:%llu \ + four:%s:%llu cycles:%llu swaps:%lu\n", + p->pid, + per_process_get_name(p->index[0]), cnts.counts[0], + per_process_get_name(p->index[1]), cnts.counts[1], + per_process_get_name(p->index[2]), cnts.counts[2], + per_process_get_name(p->index[3]), cnts.counts[3], + cnts.cycles, cnts.swaps); +} + +int per_process_l2_results_read(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + struct per_process_perf_mon_type *p = + (struct per_process_perf_mon_type *)data; + struct pm_counters_s cnts; + int i, j; + + /* + * Total across all CPUS + */ + memset(&cnts, 0, sizeof(cnts)); + for (i = 0; i < num_possible_cpus(); i++) { + cnts.l2_cycles += p->cnts[i].l2_cycles; + for (j = 0; j < PERF_NUM_MONITORS; j++) + cnts.l2_counts[j] += p->cnts[i].l2_counts[j]; + } + + /* + * Display as single results of the totals calculated above. + * Do we want to display or have option to display individula cores? + */ + return sprintf(page, "pid:%lu l2_one:%s:%llu l2_two:%s:%llu \ + l2_three:%s:%llu \ + l2_four:%s:%llu l2_cycles:%llu\n", + p->pid, + per_process_get_name(p->l2_index[0]), cnts.l2_counts[0], + per_process_get_name(p->l2_index[1]), cnts.l2_counts[1], + per_process_get_name(p->l2_index[2]), cnts.l2_counts[2], + per_process_get_name(p->l2_index[3]), cnts.l2_counts[3], + cnts.l2_cycles); +} + +/* +FUNCTION per_process_results_write + +DESCRIPTION + Allow some control over the results. If the user forgets to autolock or + wants to unlock the results so they will be deleted, then this is + where it is processed. + + For example, to unlock process 23 + echo "unlock" > 23 + +DEPENDENCIES + +RETURN VALUE + Number of characters used (all of them!) + +SIDE EFFECTS +*/ +int per_process_results_write(struct file *file, const char *buff, + unsigned long cnt, void *data) +{ + char *newbuf; + struct per_process_perf_mon_type *p = + (struct per_process_perf_mon_type *)data; + + if (p == 0) + return cnt; + /* + * Alloc the user data in kernel space. and then copy user to kernel + */ + newbuf = kmalloc(cnt + 1, GFP_KERNEL); + if (0 == newbuf) + return cnt; + if (copy_from_user(newbuf, buff, cnt) != 0) { + printk(KERN_INFO "%s copy_from_user failed\n", __func__); + return cnt; + } + + if (0 == strcmp("lock", newbuf)) + p->flags |= PERF_ENTRY_LOCKED; + else if (0 == strcmp("unlock", newbuf)) + p->flags &= ~PERF_ENTRY_LOCKED; + else if (0 == strcmp("auto", newbuf)) + p->flags |= PERF_AUTOLOCK; + else if (0 == strcmp("autoun", newbuf)) + p->flags &= ~PERF_AUTOLOCK; + + return cnt; +} + +/* +FUNCTION perProcessCreateResults + +DESCRIPTION + Create the results /proc file if the system parameters allow it... +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void per_process_create_results_proc(struct per_process_perf_mon_type *p) +{ + + if (0 == p->pidName) + p->pidName = kmalloc(12, GFP_KERNEL); + if (0 == p->pidName) + return; + sprintf(p->pidName, "%ld", p->pid); + + if (0 == p->proc) { + p->proc = create_proc_entry(p->pidName, 0777, values_dir); + if (0 == p->proc) + return; + } else { + p->proc->name = p->pidName; + } + + p->proc->read_proc = per_process_results_read; + p->proc->write_proc = per_process_results_write; + p->proc->data = (void *)p; +} + +void per_process_create_l2_results_proc(struct per_process_perf_mon_type *p) +{ + + if (0 == p->pidName) + p->pidName = kmalloc(12, GFP_KERNEL); + if (0 == p->pidName) + return; + sprintf(p->pidName, "%ld", p->pid); + + if (0 == p->l2_proc) { + p->l2_proc = create_proc_entry(p->pidName, 0777, + l2_results_dir); + if (0 == p->l2_proc) + return; + } else { + p->l2_proc->name = p->pidName; + } + + p->l2_proc->read_proc = per_process_l2_results_read; + p->l2_proc->write_proc = per_process_results_write; + p->l2_proc->data = (void *)p; +} +/* +FUNCTION per_process_swap_out + +DESCRIPTION + Store the counters from the process that is about to swap out. We take + the old counts and add them to the current counts in the perf registers. + Before the new process is swapped in, the counters are reset. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +typedef void (*vfun)(void *); +void per_process_swap_out(struct per_process_perf_mon_type *data) +{ + int i; + unsigned long overflow; +#ifdef CONFIG_ARCH_MSM8X60 + unsigned long l2_overflow; +#endif + struct per_process_perf_mon_type *p = data; + + MARKPIDS('O', p->pid, 0); + RCP15_PMOVSR(overflow); +#ifdef CONFIG_ARCH_MSM8X60 + RCP15_L2PMOVSR(l2_overflow); +#endif + + if (!pp_enabled) + return; + + /* + * The kernel for some reason (2.6.32.9) starts a process context on + * one core and ends on another. So the swap in and swap out can be + * on different cores. If this happens, we need to stop the + * counters and collect the data on the core that started the counters + * ....otherwise we receive invalid data. So we mark the the core with + * the process as deferred. The next time a process is swapped on + * the core that the process was running on, the counters will be + * updated. + */ + if ((smp_processor_id() != p->running_cpu) && (p->pid != 0)) { + fake_swap_out[p->running_cpu] = 1; + return; + } + + _SWAPS(p)++; + _CYCLES(p) += pm_get_cycle_count(); + + if (overflow & PM_CYCLE_OVERFLOW_MASK) + _CYCLES(p) += 0xFFFFFFFF; + + for (i = 0; i < PERF_NUM_MONITORS; i++) { + _COUNTS(p, i) += pm_get_count(i); + if (overflow & (1 << i)) + _COUNTS(p, i) += 0xFFFFFFFF; + } + +#ifdef CONFIG_ARCH_MSM8X60 + _L2CYCLES(p) += l2_pm_get_cycle_count(); + if (l2_overflow & L2_PM_CYCLE_OVERFLOW_MASK) + _L2CYCLES(p) += 0xFFFFFFFF; + for (i = 0; i < PERF_NUM_MONITORS; i++) { + _L2COUNTS(p, i) += l2_pm_get_count(i); + if (l2_overflow & (1 << i)) + _L2COUNTS(p, i) += 0xFFFFFFFF; + } +#endif +} + +/* +FUNCTION per_process_remove_manual + +DESCRIPTION + Remove an entry from the results directory if the flags allow this. + When not enbled or the entry is locked, the values/results will + not be removed. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void per_process_remove_manual(unsigned long pid) +{ + struct per_process_perf_mon_type *p = per_process_find(pid); + + /* + * Check all of the flags to see if we can remove this one + * Then mark as not used + */ + if (0 == p) + return; + p->pid = (0xFFFFFFFF); + + /* + * Remove the proc entry. + */ + if (p->proc) + remove_proc_entry(p->pidName, values_dir); + if (p->l2_proc) + remove_proc_entry(p->pidName, l2_results_dir); + kfree(p->pidName); + + /* + * Clear them out...and ensure the pid is invalid + */ + memset(p, 0, sizeof *p); + p->pid = 0xFFFFFFFF; + pm_remove_pid = -1; +} + +/* +* Remove called when a process exits... +*/ +void _per_process_remove(unsigned long pid) {} + +/* +FUNCTION per_process_initialize + +DESCRIPTION +Initialize performance collection information for a new process. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +May create a new proc entry +*/ +void per_process_initialize(struct per_process_perf_mon_type *p, + unsigned long pid) +{ + int i; + + /* + * See if this is the pid we are interested in... + */ + if (pp_settings_valid == -1) + return; + if ((pp_set_pid != pid) && (pp_set_pid != 0)) + return; + + /* + * Clear out the statistics table then insert this pid + * We want to keep the proc entry and the name + */ + p->pid = pid; + + /* + * Create a proc entry for this pid, then get the current event types and + * store in data struct so when the process is switched in we can track + * it. + */ + if (p->proc == 0) { + per_process_create_results_proc(p); +#ifdef CONFIG_ARCH_MSM8X60 + per_process_create_l2_results_proc(p); +#endif + } + _CYCLES(p) = 0; + _L2CYCLES(p) = 0; + _SWAPS(p) = 0; + /* + * Set the per process data struct, but not the monitors until later... + * Init only happens with the user sets the SetPID variable to this pid + * so we can load new values. + */ + for (i = 0; i < PERF_NUM_MONITORS; i++) { + p->index[i] = per_proc_event[i]; +#ifdef CONFIG_ARCH_MSM8X60 + p->l2_index[i] = l2_per_proc_event[i]; +#endif + _COUNTS(p, i) = 0; + _L2COUNTS(p, i) = 0; + } + p->lpm0evtyper = pp_lpm0evtyper; + p->lpm1evtyper = pp_lpm1evtyper; + p->lpm2evtyper = pp_lpm2evtyper; + p->l2lpmevtyper = pp_l2lpmevtyper; + p->vlpmevtyper = pp_vlpmevtyper; + +#ifdef CONFIG_ARCH_MSM8X60 + p->l2pmevtyper0 = pp_l2pmevtyper0; + p->l2pmevtyper1 = pp_l2pmevtyper1; + p->l2pmevtyper2 = pp_l2pmevtyper2; + p->l2pmevtyper3 = pp_l2pmevtyper3; + p->l2pmevtyper4 = pp_l2pmevtyper4; +#endif + + /* + * Reset pid and settings value + */ + pp_set_pid = -1; + pp_settings_valid = -1; +} + +/* +FUNCTION per_process_swap_in + +DESCRIPTION + Called when a context switch is about to start this PID. + We check to see if this process has an entry or not and create one + if not locked... + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void per_process_swap_in(struct per_process_perf_mon_type *p_new, + unsigned long pid) +{ + int i; + + MARKPIDS('I', p_new->pid, 0); + /* + * If the set proc variable == the current pid then init a new + * entry... + */ + if (pp_set_pid == pid) + per_process_initialize(p_new, pid); + + p_new->running_cpu = smp_processor_id(); + last_in_pid[smp_processor_id()] = pid; + + /* + * setup the monitors for this process. + */ + for (i = 0; i < PERF_NUM_MONITORS; i++) { + pm_set_event(i, p_new->index[i]); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_set_event(i, p_new->l2_index[i]); +#endif + } + pm_set_local_iu(p_new->lpm0evtyper); + pm_set_local_xu(p_new->lpm1evtyper); + pm_set_local_su(p_new->lpm2evtyper); + pm_set_local_l2(p_new->l2lpmevtyper); + +#ifdef CONFIG_ARCH_MSM8X60 + pm_set_local_bu(p_new->l2pmevtyper0); + pm_set_local_cb(p_new->l2pmevtyper1); + pm_set_local_mp(p_new->l2pmevtyper2); + pm_set_local_sp(p_new->l2pmevtyper3); + pm_set_local_scu(p_new->l2pmevtyper4); +#endif +} + +/* +FUNCTION perProcessSwitch + +DESCRIPTION + Called during context switch. Updates the counts on the process about to + be swapped out and brings in the counters for the process about to be + swapped in. + + All is dependant on the enabled and lock flags. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ + +DEFINE_SPINLOCK(pm_lock); +void _per_process_switch(unsigned long old_pid, unsigned long new_pid) +{ + struct per_process_perf_mon_type *p_old, *p_new; + + if (pm_global_enable == 0) + return; + + spin_lock(&pm_lock); + + pm_stop_all(); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_stop_all(); +#endif + + /* + * We detected that the process was swapped in on one core and out on + * a different core. This does not allow us to stop and stop counters + * properly so we need to defer processing. This checks to see if there + * is any defered processing necessary. And does it... */ + if (fake_swap_out[smp_processor_id()] != 0) { + fake_swap_out[smp_processor_id()] = 0; + p_old = per_process_find(last_in_pid[smp_processor_id()]); + last_in_pid[smp_processor_id()] = 0; + if (p_old != 0) + per_process_swap_out(p_old); + } + + /* + * Clear the data collected so far for this process? + */ + if (pp_clear_pid != -1) { + struct per_process_perf_mon_type *p_clear = + per_process_find(pp_clear_pid); + if (p_clear) { + memset(p_clear->cnts, 0, + sizeof(struct pm_counters_s)*num_possible_cpus()); + printk(KERN_INFO "Clear Per Processor Stats for \ + PID:%ld\n", pp_clear_pid); + pp_clear_pid = -1; + } + } + /* + * Always collect for 0, it collects for all. + */ + if (pp_enabled) { + if (first_switch == 1) { + per_process_initialize(&perf_mons[0], 0); + first_switch = 0; + } + if (pm_global) { + per_process_swap_out(&perf_mons[0]); + per_process_swap_in(&perf_mons[0], 0); + } else { + p_old = per_process_find(old_pid); + p_new = per_process_find(new_pid); + + + /* + * save the old counts to the old data struct, if the + * returned ptr is NULL or the process id passed is not + * the same as the process id in the data struct then + * don't update the data. + */ + if ((p_old) && (p_old->pid == old_pid) && + (p_old->pid != 0)) { + per_process_swap_out(p_old); + } + + /* + * Setup the counters for the new process + */ + if (pp_set_pid == new_pid) + per_process_initialize(p_new, new_pid); + if ((p_new->pid == new_pid) && (new_pid != 0)) + per_process_swap_in(p_new, new_pid); + } + pm_reset_all(); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_reset_all(); +#endif +#ifdef CONFIG_ARCH_QSD8X50 + axi_swaps++; + if (axi_swaps%pm_axi_info.refresh == 0) { + if (pm_axi_info.clear == 1) { + pm_axi_clear_cnts(); + pm_axi_info.clear = 0; + } + if (pm_axi_info.enable == 0) + pm_axi_disable(); + else + pm_axi_update_cnts(); + axi_swaps = 0; + } +#endif + } + pm_start_all(); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_start_all(); +#endif + + spin_unlock(&pm_lock); +} + +/* +FUNCTION pmInterruptIn + +DESCRIPTION + Called when an interrupt is being processed. If the pmStopForInterrutps + flag is non zero then we disable the counting of performance monitors. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +static int pm_interrupt_nesting_count; +static unsigned long pm_cycle_in, pm_cycle_out; +void _perf_mon_interrupt_in(void) +{ + if (pm_global_enable == 0) + return; + if (pm_stop_for_interrupts == 0) + return; + pm_interrupt_nesting_count++; /* Atomic */ + pm_stop_all(); + pm_cycle_in = pm_get_cycle_count(); +} + +/* +FUNCTION perfMonInterruptOut + +DESCRIPTION + Reenable performance monitor counting whn the nest count goes to zero + provided the counting has been stoped + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void _perf_mon_interrupt_out(void) +{ + if (pm_global_enable == 0) + return; + if (pm_stop_for_interrupts == 0) + return; + --pm_interrupt_nesting_count; /* Atomic?? */ + + if (pm_interrupt_nesting_count <= 0) { + pm_cycle_out = pm_get_cycle_count(); + if (pm_cycle_in != pm_cycle_out) + printk(KERN_INFO "pmIn!=pmOut in:%lx out:%lx\n", + pm_cycle_in, pm_cycle_out); + if (pp_enabled) { + pm_start_all(); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_start_all(); +#endif + } + pm_interrupt_nesting_count = 0; + } +} + +void per_process_do_global(unsigned long g) +{ + pm_global = g; + + if (pm_global == 1) { + pm_stop_all(); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_stop_all(); +#endif + pm_reset_all(); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_reset_all(); +#endif + pp_set_pid = 0; + per_process_swap_in(&perf_mons[0], 0); + pm_start_all(); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_start_all(); +#endif + } else { + pm_stop_all(); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_stop_all(); +#endif + } +} + + +/* +FUNCTION per_process_write + +DESCRIPTION + Generic routine to handle any of the settings /proc directory writes. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +int per_process_write(struct file *file, const char *buff, + unsigned long cnt, void *data, const char *fmt) +{ + char *newbuf; + unsigned long *d = (unsigned long *)data; + + /* + * Alloc the user data in kernel space. and then copy user to kernel + */ + newbuf = kmalloc(cnt + 1, GFP_KERNEL); + if (0 == newbuf) + return PM_PP_ERR; + if (copy_from_user(newbuf, buff, cnt) != 0) { + printk(KERN_INFO "%s copy_from_user failed\n", __func__); + return cnt; + } + sscanf(newbuf, fmt, d); + kfree(newbuf); + + /* + * If this is a remove command then do it now... + */ + if (d == &pm_remove_pid) + per_process_remove_manual(*d); + if (d == &pm_global) + per_process_do_global(*d); + return cnt; +} + +int per_process_write_dec(struct file *file, const char *buff, + unsigned long cnt, void *data) +{ + return per_process_write(file, buff, cnt, data, "%ld"); +} + +int per_process_write_hex(struct file *file, const char *buff, + unsigned long cnt, void *data) +{ + return per_process_write(file, buff, cnt, data, "%lx"); +} + +/* +FUNCTION per_process_read + +DESCRIPTION + Generic read handler for the /proc settings directory. + +DEPENDENCIES + +RETURN VALUE + Number of characters to output. + +SIDE EFFECTS +*/ +int per_process_read(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + unsigned long *d = (unsigned long *)data; + return sprintf(page, "%lx", *d); +} + +int per_process_read_decimal(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + unsigned long *d = (unsigned long *)data; + return sprintf(page, "%ld", *d); +} + +/* +FUNCTION per_process_proc_entry + +DESCRIPTION + Create a generic entry for the /proc settings directory. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +void per_process_proc_entry(char *name, unsigned long *var, + struct proc_dir_entry *d, int hex) +{ + struct proc_dir_entry *pe; + + pe = create_proc_entry(name, 0777, d); + if (0 == pe) + return; + if (hex) { + pe->read_proc = per_process_read; + pe->write_proc = per_process_write_hex; + } else { + pe->read_proc = per_process_read_decimal; + pe->write_proc = per_process_write_dec; + } + pe->data = (void *)var; + + if (pp_proc_entry_index >= PP_MAX_PROC_ENTRIES) { + printk(KERN_INFO "PERF: proc entry overflow,\ + memleak on module unload occured"); + return; + } + per_process_proc_names[pp_proc_entry_index++] = name; +} + +static int perfmon_notifier(struct notifier_block *self, unsigned long cmd, + void *v) +{ + static int old_pid = -1; + struct thread_info *thread = v; + int current_pid; + + if (cmd != THREAD_NOTIFY_SWITCH) + return old_pid; + + current_pid = thread->task->pid; + if (old_pid != -1) + _per_process_switch(old_pid, current_pid); + old_pid = current_pid; + return old_pid; +} + +static struct notifier_block perfmon_notifier_block = { + .notifier_call = perfmon_notifier, +}; + +/* +FUNCTION per_process_perf_init + +DESCRIPTION + Initialze the per process performance monitor variables and /proc space. + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +int per_process_perf_init(void) +{ +#ifdef CONFIG_ARCH_MSM8X60 + smp_call_function_single(0, (void *)pm_initialize, (void *)NULL, 1); + smp_call_function_single(1, (void *)pm_initialize, (void *)NULL, 1); + l2_pm_initialize(); +#else + pm_initialize(); +#endif + pm_axi_init(); + pm_axi_clear_cnts(); + proc_dir = proc_mkdir("ppPerf", NULL); + values_dir = proc_mkdir("results", proc_dir); + settings_dir = proc_mkdir("settings", proc_dir); + per_process_proc_entry("enable", &pp_enabled, settings_dir, 1); + per_process_proc_entry("valid", &pp_settings_valid, settings_dir, 1); + per_process_proc_entry("setPID", &pp_set_pid, settings_dir, 0); + per_process_proc_entry("clearPID", &pp_clear_pid, settings_dir, 0); + per_process_proc_entry("event0", &per_proc_event[0], settings_dir, 1); + per_process_proc_entry("event1", &per_proc_event[1], settings_dir, 1); + per_process_proc_entry("event2", &per_proc_event[2], settings_dir, 1); + per_process_proc_entry("event3", &per_proc_event[3], settings_dir, 1); + per_process_proc_entry("l2_event0", &l2_per_proc_event[0], settings_dir, + 1); + per_process_proc_entry("l2_event1", &l2_per_proc_event[1], settings_dir, + 1); + per_process_proc_entry("l2_event2", &l2_per_proc_event[2], settings_dir, + 1); + per_process_proc_entry("l2_event3", &l2_per_proc_event[3], settings_dir, + 1); + per_process_proc_entry("debug", &dbg_flags, settings_dir, 1); + per_process_proc_entry("autolock", &pp_auto_lock, settings_dir, 1); + per_process_proc_entry("lpm0evtyper", &pp_lpm0evtyper, settings_dir, 1); + per_process_proc_entry("lpm1evtyper", &pp_lpm1evtyper, settings_dir, 1); + per_process_proc_entry("lpm2evtyper", &pp_lpm2evtyper, settings_dir, 1); + per_process_proc_entry("l2lpmevtyper", &pp_l2lpmevtyper, settings_dir, + 1); + per_process_proc_entry("vlpmevtyper", &pp_vlpmevtyper, settings_dir, 1); + per_process_proc_entry("l2pmevtyper0", &pp_l2pmevtyper0, settings_dir, + 1); + per_process_proc_entry("l2pmevtyper1", &pp_l2pmevtyper1, settings_dir, + 1); + per_process_proc_entry("l2pmevtyper2", &pp_l2pmevtyper2, settings_dir, + 1); + per_process_proc_entry("l2pmevtyper3", &pp_l2pmevtyper3, settings_dir, + 1); + per_process_proc_entry("l2pmevtyper4", &pp_l2pmevtyper4, settings_dir, + 1); + per_process_proc_entry("stopForInterrupts", &pm_stop_for_interrupts, + settings_dir, 1); + per_process_proc_entry("global", &pm_global, settings_dir, 1); + per_process_proc_entry("globalEnable", &pm_global_enable, settings_dir, + 1); + per_process_proc_entry("removePID", &pm_remove_pid, settings_dir, 0); + + axi_dir = proc_mkdir("axi", proc_dir); + axi_settings_dir = proc_mkdir("settings", axi_dir); + axi_results_dir = proc_mkdir("results", axi_dir); + pm_axi_set_proc_entry("axi_enable", &pm_axi_info.enable, + axi_settings_dir, 1); + pm_axi_set_proc_entry("axi_clear", &pm_axi_info.clear, axi_settings_dir, + 0); + pm_axi_set_proc_entry("axi_valid", &pm_axi_info.valid, axi_settings_dir, + 1); + pm_axi_set_proc_entry("axi_sel_reg0", &pm_axi_info.sel_reg0, + axi_settings_dir, 1); + pm_axi_set_proc_entry("axi_sel_reg1", &pm_axi_info.sel_reg1, + axi_settings_dir, 1); + pm_axi_set_proc_entry("axi_ten_sel", &pm_axi_info.ten_sel_reg, + axi_settings_dir, 1); + pm_axi_set_proc_entry("axi_refresh", &pm_axi_info.refresh, + axi_settings_dir, 1); + pm_axi_get_cnt_proc_entry("axi_cnts", &axi_cnts, axi_results_dir, 0); + l2_dir = proc_mkdir("l2", proc_dir); + l2_results_dir = proc_mkdir("results", l2_dir); + + memset(perf_mons, 0, sizeof(perf_mons)); + per_process_create_results_proc(&perf_mons[0]); + per_process_create_l2_results_proc(&perf_mons[0]); + thread_register_notifier(&perfmon_notifier_block); + /* + * Set the function pointers so the module can be activated. + */ + pp_interrupt_out_ptr = _perf_mon_interrupt_out; + pp_interrupt_in_ptr = _perf_mon_interrupt_in; + pp_process_remove_ptr = _per_process_remove; + pp_loaded = 1; + pm_axi_info.refresh = 1; + +#ifdef CONFIG_ARCH_MSM8X60 + smp_call_function_single(0, (void *)pm_reset_all, (void *)NULL, 1); + smp_call_function_single(1, (void *)pm_reset_all, (void *)NULL, 1); + smp_call_function_single(0, (void *)l2_pm_reset_all, (void *)NULL, 1); + smp_call_function_single(1, (void *)l2_pm_reset_all, (void *)NULL, 1); +#else + pm_reset_all(); +#endif + + return 0; +} + +/* +FUNCTION per_process_perf_exit + +DESCRIPTION + Module exit functionm, clean up, renmove proc entries + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS + No more per process +*/ +void per_process_perf_exit(void) +{ + unsigned long i; + /* + * Sert the function pointers to 0 so the functions will no longer + * be invoked + */ + pp_loaded = 0; + pp_interrupt_out_ptr = 0; + pp_interrupt_in_ptr = 0; + pp_process_remove_ptr = 0; + /* + * Remove the results + */ + for (i = 0; i < PERF_MON_PROCESS_NUM; i++) + per_process_remove_manual(perf_mons[i].pid); + /* + * Remove the proc entries in the settings dir + */ + i = 0; + for (i = 0; i < pp_proc_entry_index; i++) + remove_proc_entry(per_process_proc_names[i], settings_dir); + + /*remove proc axi files*/ + remove_proc_entry("axi_enable", axi_settings_dir); + remove_proc_entry("axi_valid", axi_settings_dir); + remove_proc_entry("axi_refresh", axi_settings_dir); + remove_proc_entry("axi_clear", axi_settings_dir); + remove_proc_entry("axi_sel_reg0", axi_settings_dir); + remove_proc_entry("axi_sel_reg1", axi_settings_dir); + remove_proc_entry("axi_ten_sel", axi_settings_dir); + remove_proc_entry("axi_cnts", axi_results_dir); + /* + * Remove the directories + */ + remove_proc_entry("results", l2_dir); + remove_proc_entry("l2", proc_dir); + remove_proc_entry("results", proc_dir); + remove_proc_entry("settings", proc_dir); + remove_proc_entry("results", axi_dir); + remove_proc_entry("settings", axi_dir); + remove_proc_entry("axi", proc_dir); + remove_proc_entry("ppPerf", NULL); + pm_free_irq(); +#ifdef CONFIG_ARCH_MSM8X60 + l2_pm_free_irq(); +#endif + thread_unregister_notifier(&perfmon_notifier_block); +#ifdef CONFIG_ARCH_MSM8X60 + smp_call_function_single(0, (void *)pm_deinitialize, (void *)NULL, 1); + smp_call_function_single(1, (void *)pm_deinitialize, (void *)NULL, 1); + l2_pm_deinitialize(); +#else + pm_deinitialize(); +#endif +} diff --git a/arch/arm/perfmon/per.c b/arch/arm/perfmon/per.c new file mode 100644 index 00000000000..4222844f121 --- /dev/null +++ b/arch/arm/perfmon/per.c @@ -0,0 +1,59 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* +per.c + +DESCRIPTION: Performance count interface for linux via proc in the T32 +command file style +*/ + +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" +#include "linux/kernel_stat.h" +#include "asm/uaccess.h" +#include "cp15_registers.h" +#include "perf.h" + +#define PM_PER_ERR -1 +/* +FUNCTION perf_if_proc_init + +DESCRIPTION Initialize the proc interface for thje performance data. +*/ +static __init int per_init(void) +{ + + if (atomic_read(&pm_op_lock) == 1) { + printk(KERN_INFO "Can not load KSAPI, monitors are in use\n"); + return PM_PER_ERR; + } + atomic_set(&pm_op_lock, 1); + per_process_perf_init(); + printk(KERN_INFO "ksapi init\n"); + return 0; +} + +static void __exit per_exit(void) +{ + per_process_perf_exit(); + printk(KERN_INFO "ksapi exit\n"); + atomic_set(&pm_op_lock, 0); +} + +MODULE_LICENSE("GPL v2"); +module_init(per_init); +module_exit(per_exit); diff --git a/arch/arm/perfmon/perf-function-hooks.c b/arch/arm/perfmon/perf-function-hooks.c new file mode 100644 index 00000000000..aacc353741c --- /dev/null +++ b/arch/arm/perfmon/perf-function-hooks.c @@ -0,0 +1,81 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* +* perf-function-hooks.c +* DESCRIPTION +* Hooks for ksapi.ko +*/ + +#include +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" +#include "linux/kernel_stat.h" +#include "asm/uaccess.h" +#include +#include "cp15_registers.h" +#include +#include "perf.h" + +/* +* Function Pointers for when the module is installed... +* Should we use a single "ready" variable for the testing +* in the functions below, will be safer when module is removed +* testing for a locked variable... +*/ +VPVF pp_interrupt_out_ptr; +VPVF pp_interrupt_in_ptr; +VPULF pp_process_remove_ptr; +unsigned int pp_loaded; +EXPORT_SYMBOL(pp_loaded); +atomic_t pm_op_lock; +EXPORT_SYMBOL(pm_op_lock); + +/* +FUNCTION VARIOUS + +DESCRIPTION +Hooks to callinto the module functions after they are loaded. The +above pointers will be set and then these functions are ready to be +called. + +DEPENDENCIES +THe per preocess performance monitor needs to be loaded ... + +RETURN VALUE + +SIDE EFFECTS +*/ +void perf_mon_interrupt_out(void) +{ + if (pp_loaded) + (*pp_interrupt_out_ptr)(); +} +EXPORT_SYMBOL(pp_interrupt_out_ptr); + +void perf_mon_interrupt_in(void) +{ + if (pp_loaded) + (*pp_interrupt_in_ptr)(); +} +EXPORT_SYMBOL(pp_interrupt_in_ptr); + +void per_process_remove(unsigned long pid) +{ + if (pp_loaded) + (*pp_process_remove_ptr)(pid); +} +EXPORT_SYMBOL(pp_process_remove_ptr); diff --git a/arch/arm/perfmon/perf-smp.c b/arch/arm/perfmon/perf-smp.c new file mode 100644 index 00000000000..5417fc7f6a5 --- /dev/null +++ b/arch/arm/perfmon/perf-smp.c @@ -0,0 +1,751 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* +perf-smp.c +DESCRIPTION +Manipulation, initialization of the ARMV7 Performance counter register. + + +EXTERNALIZED FUNCTIONS + +INITIALIZATION AND SEQUENCING REQUIREMENTS +*/ + +/* +INCLUDE FILES FOR MODULE +*/ +#include +#include +#include +#include +#include + +#include +#include +#include "l2_cp15_registers.h" + +/* +DEFINITIONS AND DECLARATIONS FOR MODULE + +This section contains definitions for constants, macros, types, variables +and other items needed by this module. +*/ + +/* + Constant / Define Declarations +*/ + +#define PM_NUM_COUNTERS 4 +#define L2_PM_ERR -1 + +/*------------------------------------------------------------------------ + * Global control bits +------------------------------------------------------------------------*/ +#define PM_L2_GLOBAL_ENABLE (1<<0) +#define PM_L2_EVENT_RESET (1<<1) +#define PM_L2_CYCLE_RESET (1<<2) +#define PM_L2_CLKDIV (1<<3) +#define PM_L2_GLOBAL_TRACE (1<<4) +#define PM_L2_DISABLE_PROHIBIT (1<<5) + +/*--------------------------------------------------------------------------- + * Enable and clear bits for each event/trigger +----------------------------------------------------------------------------*/ +#define PM_L2EV0_ENABLE (1<<0) +#define PM_L2EV1_ENABLE (1<<1) +#define PM_L2EV2_ENABLE (1<<2) +#define PM_L2EV3_ENABLE (1<<3) +#define PM_L2_COUNT_ENABLE (1<<31) +#define PM_L2_ALL_ENABLE (0x8000000F) + + +/*----------------------------------------------------------------------------- + * Overflow actions +------------------------------------------------------------------------------*/ +#define PM_L2_OVERFLOW_NOACTION (0) +#define PM_L2_OVERFLOW_HALT (1) +#define PM_L2_OVERFLOW_STOP (2) +#define PM_L2_OVERFLOW_SKIP (3) + +/* + * Shifts for each trigger type + */ +#define PM_STOP_SHIFT 24 +#define PM_RELOAD_SHIFT 22 +#define PM_RESUME_SHIFT 20 +#define PM_SUSPEND_SHIFT 18 +#define PM_START_SHIFT 16 +#define PM_STOPALL_SHIFT 15 +#define PM_STOPCOND_SHIFT 12 +#define PM_RELOADCOND_SHIFT 9 +#define PM_RESUMECOND_SHIFT 6 +#define PM_SUSPENDCOND_SHIFT 3 +#define PM_STARTCOND_SHIFT 0 + + +/*--------------------------------------------------------------------------- +External control register. What todo when various events happen. +Triggering events, etc. +----------------------------------------------------------------------------*/ +#define PM_EXTTR0 0 +#define PM_EXTTR1 1 +#define PM_EXTTR2 2 +#define PM_EXTTR3 3 + +#define PM_COND_NO_STOP 0 +#define PM_COND_STOP_CNTOVRFLW 1 +#define PM_COND_STOP_EXTERNAL 4 +#define PM_COND_STOP_TRACE 5 +#define PM_COND_STOP_EVOVRFLW 6 +#define PM_COND_STOP_EVTYPER 7 + +/*-------------------------------------------------------------------------- +Protect against concurrent access. There is an index register that is +used to select the appropriate bank of registers. If multiple processes +are writting this at different times we could have a mess... +---------------------------------------------------------------------------*/ +#define PM_LOCK() +#define PM_UNLOCK() +#define PRINT printk + +/*-------------------------------------------------------------------------- +The Event definitions +--------------------------------------------------------------------------*/ +#define L2PM_EVT_PM0_EVT0 0x00 +#define L2PM_EVT_PM0_EVT1 0x01 +#define L2PM_EVT_PM0_EVT2 0x02 +#define L2PM_EVT_PM0_EVT3 0x03 +#define L2PM_EVT_PM1_EVT0 0x04 +#define L2PM_EVT_PM1_EVT1 0x05 +#define L2PM_EVT_PM1_EVT2 0x06 +#define L2PM_EVT_PM1_EVT3 0x07 +#define L2PM_EVT_PM2_EVT0 0x08 +#define L2PM_EVT_PM2_EVT1 0x09 +#define L2PM_EVT_PM2_EVT2 0x0a +#define L2PM_EVT_PM2_EVT3 0x0b +#define L2PM_EVT_PM3_EVT0 0x0c +#define L2PM_EVT_PM3_EVT1 0x0d +#define L2PM_EVT_PM3_EVT2 0x0e +#define L2PM_EVT_PM3_EVT3 0x0f +#define L2PM_EVT_PM4_EVT0 0x10 +#define L2PM_EVT_PM4_EVT1 0x11 +#define L2PM_EVT_PM4_EVT2 0x12 +#define L2PM_EVT_PM4_EVT3 0x13 + +/* +Type Declarations +*/ + +/* +Local Object Definitions +*/ + +unsigned long l2_pm_cycle_overflow_count; +unsigned long l2_pm_overflow_count[PM_NUM_COUNTERS]; + +/*--------------------------------------------------------------------------- +Max number of events read from the config registers +---------------------------------------------------------------------------*/ +static int pm_l2_max_events; + +static int irqid; + +/* +Function Definitions +*/ + +/* +FUNCTION l2_pm_group_stop + +DESCRIPTION Stop a group of the performance monitors. Event monitor 0 is bit +0, event monitor 1 bit 1, etc. The cycle count can also be disable with +bit 31. Macros are provided for all of the indexes including an ALL. + +DEPENDENCIES + +RETURN VALUE +None + +SIDE EFFECTS +Stops the performance monitoring for the index passed. +*/ +void pm_l2_group_stop(unsigned long mask) +{ + WCP15_L2PMCNTENCLR(mask); +} + +/* +FUNCTION l2_pm_group_start + +DESCRIPTION Start a group of the performance monitors. Event monitor 0 is bit +0, event monitor 1 bit 1, etc. The cycle count can also be enabled with +bit 31. Macros are provided for all of the indexes including an ALL. + +DEPENDENCIES + +RETURN VALUE +None + +SIDE EFFECTS +Starts the performance monitoring for the index passed. +*/ +void pm_l2_group_start(unsigned long mask) +{ + WCP15_L2PMCNTENSET(mask); +} + +/* +FUNCTION l2_pm_get_overflow + +DESCRIPTION Return the overflow condition for the index passed. + +DEPENDENCIES + +RETURN VALUE +0 no overflow +!0 (anything else) overflow; + +SIDE EFFECTS +*/ +unsigned long l2_pm_get_overflow(int index) +{ + unsigned long overflow = 0; + +/* +* Range check +*/ + if (index > pm_l2_max_events) + return L2_PM_ERR; + RCP15_L2PMOVSR(overflow); + + return overflow & (1< pm_l2_max_events) + return; + WCP15_L2PMCNTENCLR(1< pm_l2_max_events) + return; + WCP15_L2PMCNTENSET(1< pm_l2_max_events) + return L2_PM_ERR; + +/* +* Lock, select the index and read the count...unlock +*/ + PM_LOCK(); + WCP15_L2PMSELR(index); + WCP15_L2PMXEVCNTR(new_value); + PM_UNLOCK(); + return reg; +} + +int l2_pm_reset_count(int index) +{ + return l2_pm_set_count(index, 0); +} + +/* +FUNCTION l2_pm_get_count + +DESCRIPTION Return the number of events that have happened for the index +passed. + +DEPENDENCIES + +RETURN VALUE +-1 if the index is out of range +The number of events if inrange + +SIDE EFFECTS +*/ +unsigned long l2_pm_get_count(int index) +{ + unsigned long reg = 0; + +/* +* Range check +*/ + if (index > pm_l2_max_events) + return L2_PM_ERR; + +/* +* Lock, select the index and read the count...unlock +*/ + PM_LOCK(); + WCP15_L2PMSELR(index); + RCP15_L2PMXEVCNTR(reg); + PM_UNLOCK(); + return reg; +} + +unsigned long get_filter_code(unsigned long event) +{ + if (event == 0x0 || event == 0x4 || event == 0x08 + || event == 0x0c || event == 0x10) + return 0x0001003f; + else if (event == 0x1 || event == 0x5 || event == 0x09 + || event == 0x0d || event == 0x11) + return 0x0002003f; + else if (event == 0x2 || event == 0x6 || event == 0x0a + || event == 0x0e || event == 0x12) + return 0x0004003f; + else if (event == 0x3 || event == 0x7 || event == 0x0b + || event == 0x0f || event == 0x13) + return 0x0008003f; + else + return 0; +} + +int l2_pm_set_event(int index, unsigned long event) +{ + unsigned long reg = 0; + + /* + * Range check + */ + if (index > pm_l2_max_events) + return L2_PM_ERR; + + /* + * Lock, select the index and read the count...unlock + */ + PM_LOCK(); + WCP15_L2PMSELR(index); + WCP15_L2PMXEVTYPER(event); + /* WCP15_L2PMXEVFILTER(get_filter_code(event)); */ + WCP15_L2PMXEVFILTER(0x000f003f); + PM_UNLOCK(); + return reg; +} + +/* +FUNCTION pm_set_local_bu + +DESCRIPTION Set the local BU triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_bu(unsigned long value) +{ + WCP15_L2PMEVTYPER0(value); +} + +/* +FUNCTION pm_set_local_cb + +DESCRIPTION Set the local CB triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_cb(unsigned long value) +{ + WCP15_L2PMEVTYPER1(value); +} + +/* +FUNCTION pm_set_local_mp + +DESCRIPTION Set the local MP triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_mp(unsigned long value) +{ + WCP15_L2PMEVTYPER2(value); +} + +/* +FUNCTION pm_set_local_sp + +DESCRIPTION Set the local SP triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_sp(unsigned long value) +{ + WCP15_L2PMEVTYPER3(value); +} + +/* +FUNCTION pm_set_local_scu + +DESCRIPTION Set the local SCU triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_scu(unsigned long value) +{ + WCP15_L2PMEVTYPER4(value); +} + +/* +FUNCTION l2_pm_isr + +DESCRIPTION: + Performance Monitor interrupt service routine to capture overflows + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +static irqreturn_t l2_pm_isr(int irq, void *d) +{ + int i; + + for (i = 0; i < PM_NUM_COUNTERS; i++) { + if (l2_pm_get_overflow(i)) { + l2_pm_overflow_count[i]++; + l2_pm_reset_overflow(i); + } + } + + if (l2_pm_get_cycle_overflow()) { + l2_pm_cycle_overflow_count++; + l2_pm_reset_cycle_overflow(); + } + + return IRQ_HANDLED; +} + + +void l2_pm_stop_all(void) +{ + WCP15_L2PMCNTENCLR(0xFFFFFFFF); +} + +void l2_pm_reset_all(void) +{ + WCP15_L2PMCR(0xF); + WCP15_L2PMOVSR(PM_L2_ALL_ENABLE); /* overflow clear */ +} + +void l2_pm_start_all(void) +{ + WCP15_L2PMCNTENSET(PM_L2_ALL_ENABLE); +} + +/* +FUNCTION l2_pm_initialize + +DESCRIPTION Initialize the performanca monitoring for the v7 processor. + Ensures the cycle count is running and the event counters are enabled. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void l2_pm_initialize(void) +{ + unsigned long reg = 0; + unsigned char imp; + unsigned char id; + unsigned char num; + unsigned long enables = 0; + static int initialized; + + if (initialized) + return; + initialized = 1; + + irqid = SC_SICL2PERFMONIRPTREQ; + RCP15_L2PMCR(reg); + imp = (reg>>24) & 0xFF; + id = (reg>>16) & 0xFF; + pm_l2_max_events = num = (reg>>11) & 0xFF; + PRINT("V7 MP L2SCU Performance Monitor Capabilities\n"); + PRINT(" Implementor %c(%d)\n", imp, imp); + PRINT(" Id %d %x\n", id, id); + PRINT(" Num Events %d %x\n", num, num); + PRINT("\nCycle counter enabled by default...\n"); + + /* + * Global enable, ensure the global enable is set so all + * subsequent actions take effect. Also resets the counts + */ + RCP15_L2PMCR(enables); + WCP15_L2PMCR(enables | PM_L2_GLOBAL_ENABLE | PM_L2_EVENT_RESET | + PM_L2_CYCLE_RESET | PM_L2_CLKDIV); + + /* + * Enable access from user space + */ + + /* + * Install interrupt handler and the enable the interrupts + */ + l2_pm_reset_cycle_overflow(); + l2_pm_reset_overflow(0); + l2_pm_reset_overflow(1); + l2_pm_reset_overflow(2); + l2_pm_reset_overflow(3); + l2_pm_reset_overflow(4); + + if (0 != request_irq(irqid, l2_pm_isr, 0, "l2perfmon", 0)) + printk(KERN_ERR "%s:%d request_irq returned error\n", + __FILE__, __LINE__); + WCP15_L2PMINTENSET(PM_L2_ALL_ENABLE); + /* + * Enable the cycle counter. Default, count 1:1 no divisor. + */ + l2_pm_enable_cycle_counter(); + +} + +void l2_pm_free_irq(void) +{ + free_irq(irqid, 0); +} + +void l2_pm_deinitialize(void) +{ + unsigned long enables = 0; + RCP15_L2PMCR(enables); + WCP15_L2PMCR(enables & ~PM_L2_GLOBAL_ENABLE); +} + diff --git a/arch/arm/perfmon/perf-v7.c b/arch/arm/perfmon/perf-v7.c new file mode 100644 index 00000000000..614eedc9218 --- /dev/null +++ b/arch/arm/perfmon/perf-v7.c @@ -0,0 +1,1009 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* +perf-v7.c +DESCRIPTION +Manipulation, initialization of the ARMV7 Performance counter register. + + +EXTERNALIZED FUNCTIONS + +INITIALIZATION AND SEQUENCING REQUIREMENTS +*/ + +/* +INCLUDE FILES FOR MODULE +*/ +#include +#include +#include +#include +#include + +#include +#include +#include "cp15_registers.h" + +/* +DEFINITIONS AND DECLARATIONS FOR MODULE + +This section contains definitions for constants, macros, types, variables +and other items needed by this module. +*/ + +/* + Constant / Define Declarations +*/ + +#define PM_NUM_COUNTERS 4 +#define PM_V7_ERR -1 + +/*------------------------------------------------------------------------ + * Global control bits +------------------------------------------------------------------------*/ +#define PM_GLOBAL_ENABLE (1<<0) +#define PM_EVENT_RESET (1<<1) +#define PM_CYCLE_RESET (1<<2) +#define PM_CLKDIV (1<<3) +#define PM_GLOBAL_TRACE (1<<4) +#define PM_DISABLE_PROHIBIT (1<<5) + +/*--------------------------------------------------------------------------- + * Enable and clear bits for each event/trigger +----------------------------------------------------------------------------*/ +#define PM_EV0_ENABLE (1<<0) +#define PM_EV1_ENABLE (1<<1) +#define PM_EV2_ENABLE (1<<2) +#define PM_EV3_ENABLE (1<<3) +#define PM_COUNT_ENABLE (1<<31) +#define PM_ALL_ENABLE (0x8000000F) + + +/*----------------------------------------------------------------------------- + * Overflow actions +------------------------------------------------------------------------------*/ +#define PM_OVERFLOW_NOACTION (0) +#define PM_OVERFLOW_HALT (1) +#define PM_OVERFLOW_STOP (2) +#define PM_OVERFLOW_SKIP (3) + +/* + * Shifts for each trigger type + */ +#define PM_STOP_SHIFT 24 +#define PM_RELOAD_SHIFT 22 +#define PM_RESUME_SHIFT 20 +#define PM_SUSPEND_SHIFT 18 +#define PM_START_SHIFT 16 +#define PM_STOPALL_SHIFT 15 +#define PM_STOPCOND_SHIFT 12 +#define PM_RELOADCOND_SHIFT 9 +#define PM_RESUMECOND_SHIFT 6 +#define PM_SUSPENDCOND_SHIFT 3 +#define PM_STARTCOND_SHIFT 0 + + +/*--------------------------------------------------------------------------- +External control register. What todo when various events happen. +Triggering events, etc. +----------------------------------------------------------------------------*/ +#define PM_EXTTR0 0 +#define PM_EXTTR1 1 +#define PM_EXTTR2 2 +#define PM_EXTTR3 3 + +#define PM_COND_NO_STOP 0 +#define PM_COND_STOP_CNTOVRFLW 1 +#define PM_COND_STOP_EXTERNAL 4 +#define PM_COND_STOP_TRACE 5 +#define PM_COND_STOP_EVOVRFLW 6 +#define PM_COND_STOP_EVTYPER 7 + +/*-------------------------------------------------------------------------- +Protect against concurrent access. There is an index register that is +used to select the appropriate bank of registers. If multiple processes +are writting this at different times we could have a mess... +---------------------------------------------------------------------------*/ +#define PM_LOCK() +#define PM_UNLOCK() +#define PRINT printk + +/*-------------------------------------------------------------------------- +The Event definitions +--------------------------------------------------------------------------*/ +#define PM_EVT_SW_INCREMENT 0 +#define PM_EVT_L1_I_MISS 1 +#define PM_EVT_ITLB_MISS 2 +#define PM_EVT_L1_D_MISS 3 +#define PM_EVT_L1_D_ACCESS 4 +#define PM_EVT_DTLB_MISS 5 +#define PM_EVT_DATA_READ 6 +#define PM_EVT_DATA_WRITE 7 +#define PM_EVT_INSTRUCTION 8 +#define PM_EVT_EXCEPTIONS 9 +#define PM_EVT_EXCEPTION_RET 10 +#define PM_EVT_CTX_CHANGE 11 +#define PM_EVT_PC_CHANGE 12 +#define PM_EVT_BRANCH 13 +#define PM_EVT_RETURN 14 +#define PM_EVT_UNALIGNED 15 +#define PM_EVT_BRANCH_MISS 16 +#define PM_EVT_EXTERNAL0 0x40 +#define PM_EVT_EXTERNAL1 0x41 +#define PM_EVT_EXTERNAL2 0x42 +#define PM_EVT_EXTERNAL3 0x43 +#define PM_EVT_TRACE0 0x44 +#define PM_EVT_TRACE1 0x45 +#define PM_EVT_TRACE2 0x46 +#define PM_EVT_TRACE3 0x47 +#define PM_EVT_PM0 0x48 +#define PM_EVT_PM1 0x49 +#define PM_EVT_PM2 0x4a +#define PM_EVT_PM3 0x4b +#define PM_EVT_LPM0_EVT0 0x4c +#define PM_EVT_LPM0_EVT1 0x4d +#define PM_EVT_LPM0_EVT2 0x4e +#define PM_EVT_LPM0_EVT3 0x4f +#define PM_EVT_LPM1_EVT0 0x50 +#define PM_EVT_LPM1_EVT1 0x51 +#define PM_EVT_LPM1_EVT2 0x52 +#define PM_EVT_LPM1_EVT3 0x53 +#define PM_EVT_LPM2_EVT0 0x54 +#define PM_EVT_LPM2_EVT1 0x55 +#define PM_EVT_LPM2_EVT2 0x56 +#define PM_EVT_LPM2_EVT3 0x57 +#define PM_EVT_L2_EVT0 0x58 +#define PM_EVT_L2_EVT1 0x59 +#define PM_EVT_L2_EVT2 0x5a +#define PM_EVT_L2_EVT3 0x5b +#define PM_EVT_VLP_EVT0 0x5c +#define PM_EVT_VLP_EVT1 0x5d +#define PM_EVT_VLP_EVT2 0x5e +#define PM_EVT_VLP_EVT3 0x5f + +/* +Type Declarations +*/ + +/*-------------------------------------------------------------------------- +A performance monitor trigger setup/initialization structure. Contains +all of the fields necessary to setup a complex trigger with the internal +performance monitor. +---------------------------------------------------------------------------*/ +struct pm_trigger_s { + int index; + int event_type; + bool interrupt; + bool overflow_enable; + bool event_export; + unsigned char overflow_action; + unsigned char stop_index; + unsigned char reload_index; + unsigned char resume_index; + unsigned char suspend_index; + unsigned char start_index; + bool overflow_stop; + unsigned char stop_condition; + unsigned char reload_condition; + unsigned char resume_condition; + unsigned char suspend_condition; + unsigned char start_condition; +}; + +/* +* Name and index place holder so we can display the event +*/ +struct pm_name_s { + unsigned long index; + char *name; +}; + +/* +Local Object Definitions +*/ + +unsigned long pm_cycle_overflow_count; +unsigned long pm_overflow_count[PM_NUM_COUNTERS]; + +/*--------------------------------------------------------------------------- +Max number of events read from the config registers +---------------------------------------------------------------------------*/ +static int pm_max_events; + +/*-------------------------------------------------------------------------- +Storage area for each of the triggers +*---------------------------------------------------------------------------*/ +static struct pm_trigger_s pm_triggers[4]; + +/*-------------------------------------------------------------------------- +Names and indexes of the events +--------------------------------------------------------------------------*/ +static struct pm_name_s pm_names[] = { + { PM_EVT_SW_INCREMENT, "SW Increment"}, + { PM_EVT_L1_I_MISS, "L1 I MISS"}, + { PM_EVT_ITLB_MISS, "L1 ITLB MISS"}, + { PM_EVT_L1_D_MISS, "L1 D MISS"}, + { PM_EVT_L1_D_ACCESS, "L1 D ACCESS"}, + { PM_EVT_DTLB_MISS, "DTLB MISS"}, + { PM_EVT_DATA_READ, "DATA READ"}, + { PM_EVT_DATA_WRITE, "DATA WRITE"}, + { PM_EVT_INSTRUCTION, "INSTRUCTIONS"}, + { PM_EVT_EXCEPTIONS, "EXCEPTIONS"}, + { PM_EVT_EXCEPTION_RET, "EXCEPTION RETURN"}, + { PM_EVT_CTX_CHANGE, "CTX CHANGE"}, + { PM_EVT_PC_CHANGE, "PC CHANGE"}, + { PM_EVT_BRANCH, "BRANCH"}, + { PM_EVT_RETURN, "RETURN"}, + { PM_EVT_UNALIGNED, "UNALIGNED"}, + { PM_EVT_BRANCH_MISS, "BRANCH MISS"}, + { PM_EVT_EXTERNAL0, "EXTERNAL 0"}, + { PM_EVT_EXTERNAL1, "EXTERNAL 1"}, + { PM_EVT_EXTERNAL2, "EXTERNAL 2"}, + { PM_EVT_EXTERNAL3, "EXTERNAL 3"}, + { PM_EVT_TRACE0, "TRACE 0"}, + { PM_EVT_TRACE1, "TRACE 1"}, + { PM_EVT_TRACE2, "TRACE 2"}, + { PM_EVT_TRACE3, "TRACE 3"}, + { PM_EVT_PM0, "PM0"}, + { PM_EVT_PM1, "PM1"}, + { PM_EVT_PM2, "PM2"}, + { PM_EVT_PM3, "PM3"}, + { PM_EVT_LPM0_EVT0, "LPM0 E0"}, + { PM_EVT_LPM0_EVT1, "LPM0 E1"}, + { PM_EVT_LPM0_EVT2 , "LPM0 E2"}, + { PM_EVT_LPM0_EVT3, "LPM0 E3"}, + { PM_EVT_LPM1_EVT0, "LPM1 E0"}, + { PM_EVT_LPM1_EVT1, "LPM1 E1"}, + { PM_EVT_LPM1_EVT2, "LPM1 E2"}, + { PM_EVT_LPM1_EVT3, "LPM1 E3"}, + { PM_EVT_LPM2_EVT0, "LPM2 E0"}, + { PM_EVT_LPM2_EVT1 , "LPM2 E1"}, + { PM_EVT_LPM2_EVT2, "LPM2 E2"}, + { PM_EVT_LPM2_EVT3, "LPM2 E3"}, + { PM_EVT_L2_EVT0 , "L2 E0"}, + { PM_EVT_L2_EVT1, "L2 E1"}, + { PM_EVT_L2_EVT2, "L2 E2"}, + { PM_EVT_L2_EVT3 , "L2 E3"}, + { PM_EVT_VLP_EVT0 , "VLP E0"}, + { PM_EVT_VLP_EVT1, "VLP E1"}, + { PM_EVT_VLP_EVT2, "VLP E2"}, + { PM_EVT_VLP_EVT3, "VLP E3"}, +}; + +static int irqid; + +/* +Function Definitions +*/ + +/* +FUNCTION pm_find_event_name + +DESCRIPTION Find the name associated with the event index passed and return +the pointer. + +DEPENDENCIES + +RETURN VALUE +Pointer to text string containing the name of the event or pointer to +an error string. Either way access to the returned string will not +cause an access error. + +SIDE EFFECTS +*/ +char *pm_find_event_name(unsigned long index) +{ + unsigned long i = 0; + + while (pm_names[i].index != -1) { + if (pm_names[i].index == index) + return pm_names[i].name; + i++; + } + return "BAD INDEX"; +} + +/* +FUNCTION pm_group_stop + +DESCRIPTION Stop a group of the performance monitors. Event monitor 0 is bit +0, event monitor 1 bit 1, etc. The cycle count can also be disabled with +bit 31. Macros are provided for all of the indexes including an ALL. + +DEPENDENCIES + +RETURN VALUE +None + +SIDE EFFECTS +Stops the performance monitoring for the index passed. +*/ +void pm_group_stop(unsigned long mask) +{ + WCP15_PMCNTENCLR(mask); +} + +/* +FUNCTION pm_group_start + +DESCRIPTION Start a group of the performance monitors. Event monitor 0 is bit +0, event monitor 1 bit 1, etc. The cycle count can also be enabled with +bit 31. Macros are provided for all of the indexes including an ALL. + +DEPENDENCIES + +RETURN VALUE +None + +SIDE EFFECTS +Starts the performance monitoring for the index passed. +*/ +void pm_group_start(unsigned long mask) +{ + WCP15_PMCNTENSET(mask); +} + +/* +FUNCTION pm_cycle_overflow_action + +DESCRIPTION Action to take for an overflow of the cycle counter. + +DEPENDENCIES + +RETURN VALUE +None + +SIDE EFFECTS +Modify the state actions for overflow +*/ +void pm_cycle_overflow_action(int action) +{ + unsigned long reg = 0; + + if ((action > PM_OVERFLOW_SKIP) || (action < 0)) + return; + + RCP15_PMACTLR(reg); + reg &= ~(1<<30); /*clear it*/ + WCP15_PMACTLR(reg | (action<<30)); +} + +/* +FUNCTION pm_get_overflow + +DESCRIPTION Return the overflow condition for the index passed. + +DEPENDENCIES + +RETURN VALUE +0 no overflow +!0 (anything else) overflow; + +SIDE EFFECTS +*/ +unsigned long pm_get_overflow(int index) +{ + unsigned long overflow = 0; + +/* +* Range check +*/ + if (index > pm_max_events) + return PM_V7_ERR; + RCP15_PMOVSR(overflow); + + return overflow & (1< pm_max_events) + return; + WCP15_PMCNTENCLR(1< pm_max_events) + return; + WCP15_PMCNTENSET(1< pm_max_events) + return PM_V7_ERR; + +/* +* Lock, select the index and read the count...unlock +*/ + PM_LOCK(); + WCP15_PMSELR(index); + WCP15_PMXEVCNTR(new_value); + PM_UNLOCK(); + return reg; +} + +int pm_reset_count(int index) +{ + return pm_set_count(index, 0); +} + +/* +FUNCTION pm_get_count + +DESCRIPTION Return the number of events that have happened for the index +passed. + +DEPENDENCIES + +RETURN VALUE +-1 if the index is out of range +The number of events if inrange + +SIDE EFFECTS +*/ +unsigned long pm_get_count(int index) +{ + unsigned long reg = 0; + +/* +* Range check +*/ + if (index > pm_max_events) + return PM_V7_ERR; + +/* +* Lock, select the index and read the count...unlock +*/ + PM_LOCK(); + WCP15_PMSELR(index); + RCP15_PMXEVCNTR(reg); + PM_UNLOCK(); + return reg; +} + +/* +FUNCTION pm_show_event_info + +DESCRIPTION Display (print) the information about the event at the index +passed. Shows the index, name and count if a valid index is passed. If +the index is not valid, then nothing is displayed. + +DEPENDENCIES + +RETURN VALUE +None + +SIDE EFFECTS +*/ +void pm_show_event_info(unsigned long index) +{ + unsigned long count; + unsigned long event_type; + + if (index > pm_max_events) + return; + if (pm_triggers[index].index > pm_max_events) + return; + + count = pm_get_count(index); + event_type = pm_triggers[index].event_type; + + PRINT("Event %ld Trigger %s(%ld) count:%ld\n", index, + pm_find_event_name(event_type), event_type, count); +} + +/* +FUNCTION pm_event_init + +DESCRIPTION Given the struct pm_trigger_s info passed, configure the event. +This can be a complex trigger or a simple trigger. Any old values in the +event are lost. + +DEPENDENCIES + +RETURN VALUE +status + +SIDE EFFECTS +stops and clears the event at the index passed. +*/ +int pm_event_init(struct pm_trigger_s *data) +{ + unsigned long trigger; + unsigned long actlr = 0; + + if (0 == data) + return PM_V7_ERR; + if (data->index > pm_max_events) + return PM_V7_ERR; + + /* + * Setup the trigger based ont he passed values + */ + trigger = ((data->overflow_enable&1)<<31) | + ((data->event_export&1)<<30) | + ((data->stop_index&3)<reload_index&3)<resume_index&3)<suspend_index&3)<start_index&3)<overflow_stop&1)<stop_condition&7)<reload_condition&7)<resume_condition&7)<suspend_condition&7)<start_condition&7)<index); + + /* + * Lock, select the bank, set the trigger event and the event type + * then unlock. + */ + PM_LOCK(); + RCP15_PMACTLR(actlr); + actlr &= ~(3<<(data->index<<1)); + WCP15_PMACTLR(actlr | ((data->overflow_action&3) << (data->index<<1))); + WCP15_PMSELR(data->index); + WCP15_PMXEVTYPER(data->event_type); + WCP15_PMXEVCNTCR(trigger); + PM_UNLOCK(); + + /* + * Make a copy of the trigger so we know what it is when/if it triggers. + */ + memcpy(&pm_triggers[data->index], data, sizeof(*data)); + + /* + * We do not re-enable this here so events can be started together with + * pm_group_start() that way an accurate measure can be taken... + */ + + return 0; +} + +int pm_set_event(int index, unsigned long event) +{ + unsigned long reg = 0; + + /* + * Range check + */ + if (index > pm_max_events) + return PM_V7_ERR; + + /* + * Lock, select the index and read the count...unlock + */ + PM_LOCK(); + WCP15_PMSELR(index); + WCP15_PMXEVTYPER(event); + PM_UNLOCK(); + return reg; +} + +/* +FUNCTION pm_set_local_iu + +DESCRIPTION Set the local IU triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_iu(unsigned long value) +{ + WCP15_LPM0EVTYPER(value); +} + +/* +FUNCTION pm_set_local_iu + +DESCRIPTION Set the local IU triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_xu(unsigned long value) +{ + WCP15_LPM1EVTYPER(value); +} + +/* +FUNCTION pm_set_local_su + +DESCRIPTION Set the local SU triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_su(unsigned long value) +{ + WCP15_LPM2EVTYPER(value); +} + +/* +FUNCTION pm_set_local_l2 + +DESCRIPTION Set the local L2 triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_l2(unsigned long value) +{ + WCP15_L2LPMEVTYPER(value); +} + +/* +FUNCTION pm_set_local_vu + +DESCRIPTION Set the local VU triggers. Note that the MSB determines if + these are enabled or not. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_set_local_vu(unsigned long value) +{ + WCP15_VLPMEVTYPER(value); +} + +/* +FUNCTION pm_isr + +DESCRIPTION: + Performance Monitor interrupt service routine to capture overflows + +DEPENDENCIES + +RETURN VALUE + +SIDE EFFECTS +*/ +static irqreturn_t pm_isr(int irq, void *d) +{ + int i; + + for (i = 0; i < PM_NUM_COUNTERS; i++) { + if (pm_get_overflow(i)) { + pm_overflow_count[i]++; + pm_reset_overflow(i); + } + } + + if (pm_get_cycle_overflow()) { + pm_cycle_overflow_count++; + pm_reset_cycle_overflow(); + } + + return IRQ_HANDLED; +} + + +void pm_stop_all(void) +{ + WCP15_PMCNTENCLR(0xFFFFFFFF); +} + +void pm_reset_all(void) +{ + WCP15_PMCR(0xF); + WCP15_PMOVSR(PM_ALL_ENABLE); /* overflow clear */ +} + +void pm_start_all(void) +{ + WCP15_PMCNTENSET(PM_ALL_ENABLE); +} + +/* +FUNCTION pm_initialize + +DESCRIPTION Initialize the performanca monitoring for the v7 processor. + Ensures the cycle count is running and the event counters are enabled. + +DEPENDENCIES + +RETURN VALUE + NONE + +SIDE EFFECTS +*/ +void pm_initialize(void) +{ + unsigned long reg = 0; + unsigned char imp; + unsigned char id; + unsigned char num; + unsigned long enables = 0; + static int initialized; + + if (initialized) + return; + initialized = 1; + + irqid = INT_ARMQC_PERFMON; + RCP15_PMCR(reg); + imp = (reg>>24) & 0xFF; + id = (reg>>16) & 0xFF; + pm_max_events = num = (reg>>11) & 0xFF; + PRINT("V7Performance Monitor Capabilities\n"); + PRINT(" Implementor %c(%d)\n", imp, imp); + PRINT(" Id %d %x\n", id, id); + PRINT(" Num Events %d %x\n", num, num); + PRINT("\nCycle counter enabled by default...\n"); + + /* + * Global enable, ensure the global enable is set so all + * subsequent actions take effect. Also resets the counts + */ + RCP15_PMCR(enables); + WCP15_PMCR(enables | PM_GLOBAL_ENABLE | PM_EVENT_RESET | + PM_CYCLE_RESET | PM_CLKDIV); + + /* + * Enable access from user space + */ + WCP15_PMUSERENR(1); + WCP15_PMACTLR(1); + + /* + * Install interrupt handler and the enable the interrupts + */ + pm_reset_cycle_overflow(); + pm_reset_overflow(0); + pm_reset_overflow(1); + pm_reset_overflow(2); + pm_reset_overflow(3); + + if (0 != request_irq(irqid, pm_isr, 0, "perfmon", 0)) + printk(KERN_ERR "%s:%d request_irq returned error\n", + __FILE__, __LINE__); + WCP15_PMINTENSET(PM_ALL_ENABLE); + /* + * Enable the cycle counter. Default, count 1:1 no divisor. + */ + pm_enable_cycle_counter(); + +} + +void pm_free_irq(void) +{ + free_irq(irqid, 0); +} + +void pm_deinitialize(void) +{ + unsigned long enables = 0; + RCP15_PMCR(enables); + WCP15_PMCR(enables & ~PM_GLOBAL_ENABLE); +} diff --git a/arch/arm/perfmon/perf.h b/arch/arm/perfmon/perf.h new file mode 100644 index 00000000000..1a9bb8ba330 --- /dev/null +++ b/arch/arm/perfmon/perf.h @@ -0,0 +1,86 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* +perf.h + +DESCRIPTION: Reads and writes the performance monitoring registers in the ARM +by using the MRC and MCR instructions. +*/ +#ifndef PERF_H +#define PERF_H +extern unsigned long perf_get_cycles(void); +extern void perf_set_count1(unsigned long val); +extern void perf_set_count0(unsigned long val); +extern unsigned long perf_get_count1(void); +extern unsigned long perf_get_count0(void); +extern unsigned long perf_get_ctrl(void); +extern void perf_set_ctrl(void); +extern void perf_set_ctrl_with(unsigned long v); +extern void perf_enable_counting(void); +extern void perf_disable_counting(void); +extern void perf_set_divider(int d); +extern unsigned long perf_get_overflow(void); +extern void perf_clear_overflow(unsigned long bit); +extern void perf_export_event(unsigned long bit); +extern void perf_reset_counts(void); +extern int perf_set_event(unsigned long index, unsigned long val); +extern unsigned long perf_get_count(unsigned long index); +extern void perf_set_cycles(unsigned long c); + +extern void pm_stop_all(void); +extern void l2_pm_stop_all(void); +extern void pm_start_all(void); +extern void l2_pm_start_all(void); +extern void pm_reset_all(void); +extern void l2_pm_reset_all(void); +extern void pm_set_event(unsigned long monitorIndex, unsigned long eventIndex); +extern void l2_pm_set_event(unsigned long monitorIndex, + unsigned long eventIndex); +extern unsigned long pm_get_count(unsigned long monitorIndex); +extern unsigned long l2_pm_get_count(unsigned long monitorIndex); +extern unsigned long pm_get_cycle_count(void); +extern unsigned long l2_pm_get_cycle_count(void); +extern char *pm_find_event_name(unsigned long index); +extern void pm_set_local_iu(unsigned long events); +extern void pm_set_local_xu(unsigned long events); +extern void pm_set_local_su(unsigned long events); +extern void pm_set_local_l2(unsigned long events); +extern void pm_set_local_vu(unsigned long events); +extern void pm_set_local_bu(unsigned long events); +extern void pm_set_local_cb(unsigned long events); +extern void pm_set_local_mp(unsigned long events); +extern void pm_set_local_sp(unsigned long events); +extern void pm_set_local_scu(unsigned long events); +extern void pm_initialize(void); +extern void pm_deinitialize(void); +extern void l2_pm_initialize(void); +extern void l2_pm_deinitialize(void); +extern void pm_free_irq(void); +extern void l2_pm_free_irq(void); + +extern int per_process_perf_init(void); +extern void per_process_perf_exit(void); +int per_process_read(char *page, char **start, off_t off, int count, + int *eof, void *data); +int per_process_write_hex(struct file *file, const char *buff, + unsigned long cnt, void *data); +int per_process_read_decimal(char *page, char **start, off_t off, int count, + int *eof, void *data); +int per_process_write_dec(struct file *file, const char *buff, + unsigned long cnt, void *data); +void perfmon_register_callback(void); +void _per_process_switch(unsigned long oldPid, unsigned long newPid); +extern unsigned int pp_loaded; +extern atomic_t pm_op_lock; +#endif /*PERF_H*/ diff --git a/arch/arm/tools/mach-types b/arch/arm/tools/mach-types index 3b3776d0a1a..92ad04083bd 100644 --- a/arch/arm/tools/mach-types +++ b/arch/arm/tools/mach-types @@ -441,7 +441,6 @@ riot_bei2 MACH_RIOT_BEI2 RIOT_BEI2 2576 riot_x37 MACH_RIOT_X37 RIOT_X37 2578 capc7117 MACH_CAPC7117 CAPC7117 2612 icontrol MACH_ICONTROL ICONTROL 2624 -qsd8x50a_st1_5 MACH_QSD8X50A_ST1_5 QSD8X50A_ST1_5 2627 mx23evk MACH_MX23EVK MX23EVK 2629 ap4evb MACH_AP4EVB AP4EVB 2630 mityomapl138 MACH_MITYOMAPL138 MITYOMAPL138 2650 @@ -459,6 +458,7 @@ msm7x27_surf MACH_MSM7X27_SURF MSM7X27_SURF 2705 msm7x27_ffa MACH_MSM7X27_FFA MSM7X27_FFA 2706 msm7x30_ffa MACH_MSM7X30_FFA MSM7X30_FFA 2707 qsd8x50_surf MACH_QSD8X50_SURF QSD8X50_SURF 2708 +qsd8x50_ffa MACH_QSD8X50_FFA QSD8X50_FFA 2710 mx53_evk MACH_MX53_EVK MX53_EVK 2716 igep0030 MACH_IGEP0030 IGEP0030 2717 sbc3530 MACH_SBC3530 SBC3530 2722 @@ -472,6 +472,8 @@ msm8x60_sim MACH_MSM8X60_SIM MSM8X60_SIM 2756 tcc8000_sdk MACH_TCC8000_SDK TCC8000_SDK 2758 nanos MACH_NANOS NANOS 2759 stamp9g45 MACH_STAMP9G45 STAMP9G45 2761 +msm8x55_surf MACH_MSM8X55_SURF MSM8X55_SURF 2768 +msm8x55_ffa MACH_MSM8X55_FFA MSM8X55_FFA 2769 cns3420vb MACH_CNS3420VB CNS3420VB 2776 omap4_panda MACH_OMAP4_PANDA OMAP4_PANDA 2791 ti8168evm MACH_TI8168EVM TI8168EVM 2800 @@ -814,6 +816,7 @@ b5500 MACH_B5500 B5500 3177 s5500 MACH_S5500 S5500 3178 icon MACH_ICON ICON 3179 elephant MACH_ELEPHANT ELEPHANT 3180 +msm8x60_fusion MACH_MSM8X60_FUSION MSM8X60_FUSION 3181 shooter MACH_SHOOTER SHOOTER 3182 spade_lte MACH_SPADE_LTE SPADE_LTE 3183 philhwani MACH_PHILHWANI PHILHWANI 3184 @@ -830,6 +833,7 @@ top9000_su MACH_TOP9000_SU TOP9000_SU 3195 utm300 MACH_UTM300 UTM300 3196 tsunagi MACH_TSUNAGI TSUNAGI 3197 ts75xx MACH_TS75XX TS75XX 3198 +msm8x60_fusn_ffa MACH_MSM8X60_FUSN_FFA MSM8X60_FUSN_FFA 3199 ts47xx MACH_TS47XX TS47XX 3200 da850_k5 MACH_DA850_K5 DA850_K5 3201 ax502 MACH_AX502 AX502 3202 @@ -1017,7 +1021,7 @@ geneva_b5 MACH_GENEVA_B5 GENEVA_B5 3393 spear1340 MACH_SPEAR1340 SPEAR1340 3394 rexmas MACH_REXMAS REXMAS 3395 msm8960_cdp MACH_MSM8960_CDP MSM8960_CDP 3396 -msm8960_mdp MACH_MSM8960_MDP MSM8960_MDP 3397 +msm8960_mtp MACH_MSM8960_MTP MSM8960_MTP 3397 msm8960_fluid MACH_MSM8960_FLUID MSM8960_FLUID 3398 msm8960_apq MACH_MSM8960_APQ MSM8960_APQ 3399 helios_v2 MACH_HELIOS_V2 HELIOS_V2 3400 @@ -1113,3 +1117,6 @@ blissc MACH_BLISSC BLISSC 3491 thales_adc MACH_THALES_ADC THALES_ADC 3492 ubisys_p9d_evp MACH_UBISYS_P9D_EVP UBISYS_P9D_EVP 3493 atdgp318 MACH_ATDGP318 ATDGP318 3494 +msm8960_liquid MACH_MSM8960_LIQUID MSM8960_LIQUID 3535 +msm8x60_dragon MACH_MSM8X60_DRAGON MSM8X60_DRAGON 3586 +apq8064_sim MACH_APQ8064_SIM APQ8064_SIM 3572 diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c index f25e7ec8941..3554500657f 100644 --- a/arch/arm/vfp/vfpmodule.c +++ b/arch/arm/vfp/vfpmodule.c @@ -397,30 +397,45 @@ static void vfp_enable(void *unused) set_copro_access(access | CPACC_FULL(10) | CPACC_FULL(11)); } -#ifdef CONFIG_PM -#include - -static int vfp_pm_suspend(void) +int vfp_flush_context(void) { - struct thread_info *ti = current_thread_info(); - u32 fpexc = fmrx(FPEXC); + unsigned long flags; + struct thread_info *ti; + u32 fpexc; + u32 cpu; + int saved = 0; + + local_irq_save(flags); - /* if vfp is on, then save state for resumption */ - if (fpexc & FPEXC_EN) { - printk(KERN_DEBUG "%s: saving vfp state\n", __func__); - vfp_save_state(&ti->vfpstate, fpexc); + ti = current_thread_info(); + fpexc = fmrx(FPEXC); + cpu = ti->cpu; + +#ifdef CONFIG_SMP + /* On SMP, if VFP is enabled, save the old state */ + if ((fpexc & FPEXC_EN) && last_VFP_context[cpu]) { + last_VFP_context[cpu]->hard.cpu = cpu; +#else + /* If there is a VFP context we must save it. */ + if (last_VFP_context[cpu]) { + /* Enable VFP so we can save the old state. */ + fmxr(FPEXC, fpexc | FPEXC_EN); + isb(); +#endif + vfp_save_state(last_VFP_context[cpu], fpexc); /* disable, just in case */ fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN); + saved = 1; } + last_VFP_context[cpu] = NULL; - /* clear any information we had about last context state */ - memset(last_VFP_context, 0, sizeof(last_VFP_context)); + local_irq_restore(flags); - return 0; + return saved; } -static void vfp_pm_resume(void) +void vfp_reinit(void) { /* ensure we have access to the vfp */ vfp_enable(NULL); @@ -429,6 +444,21 @@ static void vfp_pm_resume(void) fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN); } +#ifdef CONFIG_PM +#include + +static int vfp_pm_suspend(void) +{ + vfp_flush_context(); + + return 0; +} + +static void vfp_pm_resume(void) +{ + vfp_reinit(); +} + static struct syscore_ops vfp_pm_syscore_ops = { .suspend = vfp_pm_suspend, .resume = vfp_pm_resume, diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c index b13ed393dfc..ea00b48274e 100644 --- a/arch/x86/kernel/cpu/amd.c +++ b/arch/x86/kernel/cpu/amd.c @@ -704,7 +704,7 @@ cpu_dev_register(amd_cpu_dev); */ const int amd_erratum_400[] = - AMD_OSVW_ERRATUM(1, AMD_MODEL_RANGE(0xf, 0x41, 0x2, 0xff, 0xf), + AMD_OSVW_ERRATUM(1, AMD_MODEL_RANGE(0x0f, 0x4, 0x2, 0xff, 0xf), AMD_MODEL_RANGE(0x10, 0x2, 0x1, 0xff, 0xf)); EXPORT_SYMBOL_GPL(amd_erratum_400); diff --git a/arch/x86/xen/mmu.c b/arch/x86/xen/mmu.c index 0ccccb67a99..0343708fb97 100644 --- a/arch/x86/xen/mmu.c +++ b/arch/x86/xen/mmu.c @@ -1776,9 +1776,7 @@ pgd_t * __init xen_setup_kernel_pagetable(pgd_t *pgd, initial_kernel_pmd = extend_brk(sizeof(pmd_t) * PTRS_PER_PMD, PAGE_SIZE); - max_pfn_mapped = PFN_DOWN(__pa(xen_start_info->pt_base) + - xen_start_info->nr_pt_frames * PAGE_SIZE + - 512*1024); + max_pfn_mapped = PFN_DOWN(__pa(xen_start_info->mfn_list)); kernel_pmd = m2v(pgd[KERNEL_PGD_BOUNDARY].pgd); memcpy(initial_kernel_pmd, kernel_pmd, sizeof(pmd_t) * PTRS_PER_PMD); diff --git a/chromeos/changelog b/chromeos/changelog new file mode 100644 index 00000000000..eb9b39d9ddc --- /dev/null +++ b/chromeos/changelog @@ -0,0 +1,13 @@ +linux (2.6.32-0.1) UNRELEASED; urgency=low + + CHANGELOG: Do not edit directly. Autogenerated at release. + CHANGELOG: Use the printchanges target to see the curent changes. + CHANGELOG: Use the insertchanges target to create the final log. + + -- Mandeep Singh Baines Fri Mar 12 11:16:34 2010 -0800 + +linux (2.6.31-0.0) karmic-proposed; urgency=low + + * Initial chrome based on Ubuntu-2.6.31-17.54 + + -- Tim Gardner Thu, 12 Mar 2009 19:16:07 -0600 diff --git a/chromeos/config/armel/config.common.armel b/chromeos/config/armel/config.common.armel new file mode 100644 index 00000000000..beefc662f98 --- /dev/null +++ b/chromeos/config/armel/config.common.armel @@ -0,0 +1,209 @@ +# +# Config options generated by splitconfig +# +CONFIG_AEABI=y +# CONFIG_AFS_FS is not set +CONFIG_ALIGNMENT_TRAP=y +# CONFIG_APM_EMULATION is not set +# CONFIG_ARCH_AAEC2000 is not set +# CONFIG_ARCH_AT91 is not set +# CONFIG_ARCH_BCMRING is not set +# CONFIG_ARCH_CLPS711X is not set +# CONFIG_ARCH_CNS3XXX is not set +# CONFIG_ARCH_DAVINCI is not set +# CONFIG_ARCH_DOVE is not set +# CONFIG_ARCH_EBSA110 is not set +# CONFIG_ARCH_EP93XX is not set +# CONFIG_ARCH_FOOTBRIDGE is not set +# CONFIG_ARCH_GEMINI is not set +# CONFIG_ARCH_H720X is not set +# CONFIG_ARCH_INTEGRATOR is not set +# CONFIG_ARCH_IOP13XX is not set +# CONFIG_ARCH_IOP32X is not set +# CONFIG_ARCH_IOP33X is not set +# CONFIG_ARCH_IXP2000 is not set +# CONFIG_ARCH_IXP23XX is not set +# CONFIG_ARCH_IXP4XX is not set +# CONFIG_ARCH_KIRKWOOD is not set +# CONFIG_ARCH_KS8695 is not set +# CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_LH7A40X is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MMP is not set +# CONFIG_ARCH_MV78XX0 is not set +# CONFIG_ARCH_MXC is not set +# CONFIG_ARCH_NETX is not set +# CONFIG_ARCH_NOMADIK is not set +# CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_NUC93X is not set +# CONFIG_ARCH_OMAP is not set +# CONFIG_ARCH_ORION5X is not set +# CONFIG_ARCH_PNX4008 is not set +# CONFIG_ARCH_PXA is not set +# CONFIG_ARCH_REALVIEW is not set +# CONFIG_ARCH_RPC is not set +# CONFIG_ARCH_S3C2410 is not set +# CONFIG_ARCH_S3C64XX is not set +# CONFIG_ARCH_S5P6440 is not set +# CONFIG_ARCH_S5P6442 is not set +# CONFIG_ARCH_S5PC100 is not set +# CONFIG_ARCH_S5PV210 is not set +# CONFIG_ARCH_SA1100 is not set +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +# CONFIG_ARCH_SHARK is not set +# CONFIG_ARCH_SHMOBILE is not set +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +# CONFIG_ARCH_STMP3XXX is not set +# CONFIG_ARCH_SUPPORTS_MSI is not set +# CONFIG_ARCH_U300 is not set +# CONFIG_ARCH_U8500 is not set +# CONFIG_ARCH_USES_GETTIMEOFFSET is not set +# CONFIG_ARCH_VEXPRESS is not set +# CONFIG_ARCH_W90X900 is not set +CONFIG_ARM=y +CONFIG_ARM_L1_CACHE_SHIFT=5 +CONFIG_ARM_THUMB=y +CONFIG_ARM_UNWIND=y +# CONFIG_ATA is not set +# CONFIG_AUDIT is not set +# CONFIG_AX88796 is not set +# CONFIG_BINFMT_AOUT is not set +# CONFIG_BINFMT_MISC is not set +# CONFIG_BSD_PROCESS_ACCT is not set +# CONFIG_CEPH_FS is not set +# CONFIG_CFG80211_DEBUGFS is not set +# CONFIG_CGROUPS is not set +# CONFIG_CODA_FS is not set +CONFIG_COMPAT_BRK=y +# CONFIG_CONNECTOR is not set +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y +# CONFIG_CPU_DCACHE_DISABLE is not set +# CONFIG_CPU_ICACHE_DISABLE is not set +CONFIG_CP_ACCESS=m +# CONFIG_CRC7 is not set +# CONFIG_CRC_ITU_T is not set +# CONFIG_CRC_T10DIF is not set +CONFIG_DEBUG_BUGVERBOSE=y +# CONFIG_DEBUG_DEVRES is not set +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_MEMORY_INIT=y +# CONFIG_DEBUG_STACK_USAGE is not set +CONFIG_DECOMPRESS_BZIP2=y +CONFIG_DECOMPRESS_LZMA=y +CONFIG_DEFAULT_SECURITY="" +CONFIG_DEFAULT_SECURITY_DAC=y +# CONFIG_DEFAULT_SECURITY_TOMOYO is not set +# CONFIG_DM9000 is not set +# CONFIG_DNET is not set +CONFIG_DNOTIFY=y +# CONFIG_DONT_RESERVE_FROM_MOVABLE_ZONE is not set +# CONFIG_DS1682 is not set +# CONFIG_EEPROM_AT24 is not set +# CONFIG_EEPROM_LEGACY is not set +CONFIG_ENABLE_WARN_DEPRECATED=y +# CONFIG_ETHOC is not set +# CONFIG_EXT4_FS is not set +# CONFIG_FIQ_DEBUGGER is not set +CONFIG_FLATMEM=y +CONFIG_FLATMEM_MANUAL=y +CONFIG_FLAT_NODE_MEM_MAP=y +CONFIG_FRAME_WARN=1024 +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_HAVE_AOUT=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_HAVE_PROC_CPU=y +# CONFIG_HIDRAW is not set +# CONFIG_HID_PID is not set +# CONFIG_HIGHMEM is not set +# CONFIG_HUGETLB_PAGE is not set +CONFIG_HZ=100 +# CONFIG_I2C_OCORES is not set +# CONFIG_I2C_PCA_PLATFORM is not set +# CONFIG_I2C_SIMTEC is not set +# CONFIG_INLINE_READ_UNLOCK is not set +# CONFIG_INLINE_READ_UNLOCK_IRQ is not set +# CONFIG_INLINE_SPIN_UNLOCK is not set +# CONFIG_INLINE_SPIN_UNLOCK_IRQ is not set +# CONFIG_INLINE_WRITE_UNLOCK is not set +# CONFIG_INLINE_WRITE_UNLOCK_IRQ is not set +# CONFIG_INPUT_ATI_REMOTE is not set +# CONFIG_INPUT_ATI_REMOTE2 is not set +# CONFIG_INPUT_CM109 is not set +# CONFIG_INPUT_FF_MEMLESS is not set +# CONFIG_INPUT_KEYSPAN_REMOTE is not set +# CONFIG_INPUT_POLLDEV is not set +# CONFIG_INPUT_POWERMATE is not set +# CONFIG_INPUT_SPARSEKMAP is not set +# CONFIG_INPUT_TABLET is not set +# CONFIG_INPUT_UINPUT is not set +# CONFIG_INPUT_YEALINK is not set +# CONFIG_IP_ADVANCED_ROUTER is not set +# CONFIG_IP_MULTICAST is not set +# CONFIG_ISO9660_FS is not set +# CONFIG_KALLSYMS_EXTRA_PASS is not set +# CONFIG_KEYBOARD_GPIO is not set +# CONFIG_KEYBOARD_LKKBD is not set +# CONFIG_KEYBOARD_NEWTON is not set +# CONFIG_KEYBOARD_STOWAWAY is not set +# CONFIG_KEYBOARD_SUNKBD is not set +# CONFIG_KEYBOARD_XTKBD is not set +CONFIG_LBDAF=y +# CONFIG_LIBCRC32C is not set +CONFIG_LOG_BUF_SHIFT=17 +# CONFIG_MAC80211_DEBUGFS is not set +# CONFIG_MD is not set +# CONFIG_NCP_FS is not set +CONFIG_NEED_DMA_MAP_STATE=y +# CONFIG_NETCONSOLE is not set +# CONFIG_NETFILTER is not set +# CONFIG_NETPOLL is not set +CONFIG_NETWORK_FILESYSTEMS=y +# CONFIG_NETWORK_SECMARK is not set +# CONFIG_NET_NS is not set +# CONFIG_NET_POLL_CONTROLLER is not set +# CONFIG_NET_SCHED is not set +# CONFIG_NFSD is not set +# CONFIG_OC_ETM is not set +CONFIG_PAGEFLAGS_EXTENDED=y +# CONFIG_PAGE_POISONING is not set +# CONFIG_PCI_SYSCALL is not set +CONFIG_PERF_USE_VMALLOC=y +# CONFIG_PHYS_ADDR_T_64BIT is not set +# CONFIG_PID_NS is not set +# CONFIG_PLAT_SPEAR is not set +# CONFIG_PM_DEBUG is not set +# CONFIG_POSIX_MQUEUE is not set +CONFIG_PREEMPT=y +# CONFIG_PREEMPT_TRACER is not set +# CONFIG_PREEMPT_VOLUNTARY is not set +# CONFIG_RAMZSWAP is not set +CONFIG_RD_BZIP2=y +CONFIG_RD_LZMA=y +# CONFIG_RELAY is not set +# CONFIG_RESERVE_FIRST_PAGE is not set +# CONFIG_RFKILL is not set +# CONFIG_SECURITY is not set +# CONFIG_SERIAL_NONSTANDARD is not set +# CONFIG_SMB_FS is not set +# CONFIG_SPARSEMEM_MANUAL is not set +# CONFIG_SPI is not set +# CONFIG_SQUASHFS is not set +CONFIG_SWAP=y +# CONFIG_SYN_COOKIES is not set +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +# CONFIG_TASKSTATS is not set +# CONFIG_TCP_CONG_ADVANCED is not set +# CONFIG_TCP_MD5SIG is not set +# CONFIG_THERMAL is not set +# CONFIG_UACCESS_WITH_MEMCPY is not set +# CONFIG_USB_ARCH_HAS_OHCI is not set +# CONFIG_USER_NS is not set +# CONFIG_UTS_NS is not set +# CONFIG_VCM is not set +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_VMALLOC_RESERVE=0x08000000 +# CONFIG_VMSPLIT_1G is not set +# CONFIG_XIP_KERNEL is not set +CONFIG_ZONE_DMA_FLAG=0 diff --git a/chromeos/config/armel/config.flavour.chromeos-arm b/chromeos/config/armel/config.flavour.chromeos-arm new file mode 100644 index 00000000000..ff782cf4a41 --- /dev/null +++ b/chromeos/config/armel/config.flavour.chromeos-arm @@ -0,0 +1,482 @@ +# +# Config options generated by splitconfig +# +# CONFIG_AMSS_7X25_VERSION_2008 is not set +CONFIG_AMSS_7X25_VERSION_2009=y +# CONFIG_ANDROID is not set +CONFIG_ANDROID_PARANOID_NETWORK=y +CONFIG_ARCH_HAS_CPUFREQ=y +CONFIG_ARCH_MSM=y +CONFIG_ARCH_MSM7X01A=y +# CONFIG_ARCH_MSM7X25 is not set +# CONFIG_ARCH_MSM7X27 is not set +# CONFIG_ARCH_MSM7X30 is not set +# CONFIG_ARCH_MSM8X60 is not set +CONFIG_ARCH_MSM_ARM11=y +# CONFIG_ARCH_QSD8X50 is not set +# CONFIG_ARCH_VERSATILE is not set +CONFIG_ARM_DMA_MEM_BUFFERABLE=y +# CONFIG_ARM_ERRATA_411920 is not set +# CONFIG_ASHMEM is not set +# CONFIG_ATH6K_LEGACY is not set +CONFIG_ATH_COMMON=m +# CONFIG_ATH_DEBUG is not set +# CONFIG_BACKLIGHT_LCD_SUPPORT is not set +CONFIG_BINARY_PRINTF=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_LOOP is not set +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set +CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0 +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +CONFIG_BROKEN_ON_SMP=y +CONFIG_BT=m +CONFIG_BT_BNEP=m +# CONFIG_BT_BNEP_MC_FILTER is not set +# CONFIG_BT_BNEP_PROTO_FILTER is not set +# CONFIG_BT_HCIBTSDIO is not set +# CONFIG_BT_HCIUART is not set +CONFIG_BT_HCIVHCI=m +CONFIG_BT_HIDP=m +CONFIG_BT_L2CAP=m +# CONFIG_BT_L2CAP_EXT_FEATURES is not set +# CONFIG_BT_MRVL is not set +CONFIG_BT_RFCOMM=m +# CONFIG_BT_RFCOMM_TTY is not set +CONFIG_BT_SCO=m +CONFIG_CFG80211=m +# CONFIG_CIFS is not set +CONFIG_CLOCK_BASED_SLEEP_LIMIT=y +CONFIG_CMDLINE="mem=64M console=ttyMSM,115200n8" +# CONFIG_CMDLINE_FORCE is not set +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_CPU_32v6=y +CONFIG_CPU_32v6K=y +CONFIG_CPU_ABRT_EV6=y +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_CPU_CACHE_V6=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +# CONFIG_CPU_FREQ is not set +CONFIG_CPU_HAS_ASID=y +CONFIG_CPU_HAS_PMU=y +# CONFIG_CPU_IDLE is not set +CONFIG_CPU_PABRT_V6=y +CONFIG_CPU_TLB_V6=y +CONFIG_CPU_V6=y +# CONFIG_CRAMFS is not set +CONFIG_CRC16=y +CONFIG_CRYPTO=m +CONFIG_CRYPTO_AEAD=m +CONFIG_CRYPTO_AEAD2=m +CONFIG_CRYPTO_AES=m +CONFIG_CRYPTO_ALGAPI=m +CONFIG_CRYPTO_ALGAPI2=m +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_ARC4=m +CONFIG_CRYPTO_AUTHENC=m +CONFIG_CRYPTO_BLKCIPHER=m +CONFIG_CRYPTO_BLKCIPHER2=m +CONFIG_CRYPTO_CBC=m +CONFIG_CRYPTO_CRC32C=m +CONFIG_CRYPTO_DES=m +CONFIG_CRYPTO_ECB=m +CONFIG_CRYPTO_HASH=m +CONFIG_CRYPTO_HASH2=m +CONFIG_CRYPTO_HMAC=m +CONFIG_CRYPTO_MANAGER=m +CONFIG_CRYPTO_MANAGER2=m +CONFIG_CRYPTO_MD5=m +CONFIG_CRYPTO_MICHAEL_MIC=m +CONFIG_CRYPTO_PCOMP=m +CONFIG_CRYPTO_RNG2=m +CONFIG_CRYPTO_SHA1=m +CONFIG_CRYPTO_SHA256=m +CONFIG_CRYPTO_WORKQUEUE=m +# CONFIG_DCC_TTY is not set +# CONFIG_DEBUG_ERRORS is not set +# CONFIG_DEBUG_GPIO is not set +# CONFIG_DEBUG_ICEDCC is not set +CONFIG_DEBUG_LL=y +CONFIG_DEBUG_MUTEXES=y +CONFIG_DEBUG_PREEMPT=y +# CONFIG_DEBUG_SLAB is not set +CONFIG_DEBUG_SPINLOCK_SLEEP=y +# CONFIG_DEBUG_USER is not set +CONFIG_DECOMPRESS_LZO=y +# CONFIG_DEFAULT_CFQ is not set +CONFIG_DEFAULT_IOSCHED="noop" +CONFIG_DEFAULT_NOOP=y +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DETECT_SOFTLOCKUP=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_DIAG_OVER_USB is not set +# CONFIG_DRM is not set +CONFIG_DUMMY=y +# CONFIG_EARLY_PRINTK is not set +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_EMBEDDED is not set +CONFIG_EVENT_TRACING=y +# CONFIG_EXT2_FS is not set +# CONFIG_EXT3_FS is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_CFB_COPYAREA is not set +# CONFIG_FB_CFB_FILLRECT is not set +# CONFIG_FB_CFB_IMAGEBLIT is not set +CONFIG_FB_MODE_HELPERS=y +# CONFIG_FB_MSM is not set +CONFIG_FB_TILEBLITTING=y +CONFIG_FRAME_POINTER=y +# CONFIG_FS_POSIX_ACL is not set +# CONFIG_FTL is not set +# CONFIG_FTRACE_STARTUP_TEST is not set +# CONFIG_FUNCTION_PROFILER is not set +CONFIG_FUNCTION_TRACER=y +# CONFIG_FUSE_FS is not set +CONFIG_GENERIC_TRACER=y +# CONFIG_GOBI_USBNET is not set +CONFIG_GPIOLIB=y +# CONFIG_GPIO_ADP5588 is not set +# CONFIG_GPIO_IT8761E is not set +# CONFIG_GPIO_MAX7300 is not set +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_GPIO_PCF857X is not set +# CONFIG_GPIO_SX150X is not set +# CONFIG_GPIO_SYSFS is not set +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_HID_APPLE=m +# CONFIG_HID_MAGICMOUSE is not set +CONFIG_HID_WACOM=m +# CONFIG_HID_WACOM_POWER_SUPPLY is not set +CONFIG_HIGH_RES_TIMERS=y +CONFIG_HOSTAP=m +CONFIG_HOSTAP_FIRMWARE=y +CONFIG_HOSTAP_FIRMWARE_NVRAM=y +# CONFIG_HTC_EGPIO is not set +# CONFIG_HTC_HEADSET is not set +# CONFIG_HTC_PWRSINK is not set +# CONFIG_HWMON is not set +# CONFIG_HW_RANDOM is not set +# CONFIG_I2C_CHARDEV is not set +CONFIG_I2C_MSM=y +CONFIG_I2C_STUB=m +CONFIG_IIO=m +# CONFIG_IIO_RING_BUFFER is not set +# CONFIG_IIO_TRIGGER is not set +CONFIG_IKCONFIG=m +CONFIG_INET_LRO=y +# CONFIG_INET_TUNNEL is not set +# CONFIG_INFTL is not set +# CONFIG_INPUT_EVBUG is not set +# CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_TOUCHSCREEN=y +# CONFIG_IOSCHED_CFQ is not set +# CONFIG_IOSCHED_DEADLINE is not set +# CONFIG_IPV6 is not set +# CONFIG_IP_PNP is not set +# CONFIG_JFFS2_FS is not set +# CONFIG_KALLSYMS_ALL is not set +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_QCIKBD is not set +# CONFIG_KEYS is not set +CONFIG_KPROBES=y +# CONFIG_KPROBES_SANITY_TEST is not set +CONFIG_KRETPROBES=y +# CONFIG_LATENCYTOP is not set +# CONFIG_LEDS_GPIO is not set +CONFIG_LEDS_MSM_PMIC=y +# CONFIG_LEDS_REGULATOR is not set +# CONFIG_LEDS_TRIGGER_GPIO is not set +CONFIG_LIB80211=m +CONFIG_LIB80211_CRYPT_CCMP=m +CONFIG_LIB80211_CRYPT_TKIP=m +CONFIG_LIB80211_CRYPT_WEP=m +# CONFIG_LIB80211_DEBUG is not set +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_DEBUG=y +# CONFIG_LIBERTAS_MESH is not set +# CONFIG_LIBERTAS_SDIO is not set +CONFIG_LIBERTAS_THINFIRM=m +# CONFIG_LIBERTAS_THINFIRM_DEBUG is not set +CONFIG_LOCALVERSION_AUTO=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_MAC80211=m +CONFIG_MAC80211_HWSIM=m +# CONFIG_MACH_HALIBUT is not set +# CONFIG_MACH_MSM7201A_FFA is not set +# CONFIG_MACH_MSM7201A_SURF is not set +# CONFIG_MACH_SAPPHIRE is not set +# CONFIG_MACH_TROUT is not set +# CONFIG_MAX1363 is not set +# CONFIG_MEDIA_SUPPORT is not set +# CONFIG_MFD_ASIC3 is not set +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_TC6393XB is not set +# CONFIG_MG_DISK is not set +CONFIG_MMC=m +CONFIG_MMC_BLOCK=m +# CONFIG_MMC_MSM is not set +# CONFIG_MMC_MSM7X00A is not set +# CONFIG_MMC_PARANOID_SD_INIT is not set +CONFIG_MMC_SDHCI=m +# CONFIG_MMC_SDHCI_PLTFM is not set +# CONFIG_MMC_UNSAFE_RESUME is not set +# CONFIG_MSDOS_FS is not set +CONFIG_MSM7X00A_IDLE_SLEEP_MIN_TIME=20000000 +CONFIG_MSM7X00A_IDLE_SLEEP_MODE=1 +# CONFIG_MSM7X00A_IDLE_SLEEP_MODE_APPS_SLEEP is not set +CONFIG_MSM7X00A_IDLE_SLEEP_MODE_POWER_COLLAPSE=y +# CONFIG_MSM7X00A_IDLE_SLEEP_MODE_POWER_COLLAPSE_SUSPEND is not set +# CONFIG_MSM7X00A_IDLE_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT is not set +# CONFIG_MSM7X00A_IDLE_SLEEP_WAIT_FOR_INTERRUPT is not set +CONFIG_MSM7X00A_IDLE_SPIN_TIME=80000 +CONFIG_MSM7X00A_SLEEP_MODE=0 +# CONFIG_MSM7X00A_SLEEP_MODE_APPS_SLEEP is not set +# CONFIG_MSM7X00A_SLEEP_MODE_POWER_COLLAPSE is not set +CONFIG_MSM7X00A_SLEEP_MODE_POWER_COLLAPSE_SUSPEND=y +# CONFIG_MSM7X00A_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT is not set +# CONFIG_MSM7X00A_SLEEP_WAIT_FOR_INTERRUPT is not set +# CONFIG_MSM7X00A_USE_DG_TIMER is not set +CONFIG_MSM7X00A_USE_GP_TIMER=y +# CONFIG_MSM_ADSP is not set +CONFIG_MSM_AMSS_VERSION=6225 +# CONFIG_MSM_AMSS_VERSION_6210 is not set +# CONFIG_MSM_AMSS_VERSION_6220 is not set +CONFIG_MSM_AMSS_VERSION_6225=y +# CONFIG_MSM_AVS_HW is not set +CONFIG_MSM_BUS_SCALING=y +CONFIG_MSM_DEBUG_UART=2 +# CONFIG_MSM_DEBUG_UART1 is not set +CONFIG_MSM_DEBUG_UART2=y +# CONFIG_MSM_DEBUG_UART3 is not set +# CONFIG_MSM_DEBUG_UART_NONE is not set +CONFIG_MSM_DIRECT_SCLK_ACCESS=y +CONFIG_MSM_DMA_TEST=m +CONFIG_MSM_FIQ_SUPPORT=y +CONFIG_MSM_HW3D=y +CONFIG_MSM_IDLE_STATS=y +CONFIG_MSM_IDLE_STATS_BUCKET_COUNT=10 +CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT=2 +CONFIG_MSM_IDLE_STATS_FIRST_BUCKET=62500 +CONFIG_MSM_IDLE_WAIT_ON_MODEM=0 +# CONFIG_MSM_MEMORY_LOW_POWER_MODE is not set +CONFIG_MSM_ONCRPCROUTER=y +CONFIG_MSM_ONCRPCROUTER_DEBUG=y +CONFIG_MSM_PM_TIMEOUT_HALT=y +# CONFIG_MSM_PM_TIMEOUT_RESET_CHIP is not set +# CONFIG_MSM_PM_TIMEOUT_RESET_MODEM is not set +CONFIG_MSM_PROC_COMM=y +CONFIG_MSM_REMOTE_SPINLOCK_SWP=y +CONFIG_MSM_RESET_MODEM=m +CONFIG_MSM_RMNET=y +# CONFIG_MSM_RMNET_DEBUG is not set +# CONFIG_MSM_RMT_STORAGE_CLIENT is not set +CONFIG_MSM_RPCSERVER_HANDSET=y +CONFIG_MSM_RPCSERVER_WATCHDOG=y +# CONFIG_MSM_RPC_LOOPBACK_XPRT is not set +CONFIG_MSM_RPC_OEM_RAPI=y +CONFIG_MSM_RPC_PING=y +CONFIG_MSM_RPC_PROC_COMM_TEST=y +# CONFIG_MSM_RPC_WATCHDOG is not set +CONFIG_MSM_RPM=y +# CONFIG_MSM_SCM is not set +# CONFIG_MSM_SERIAL_DEBUGGER is not set +CONFIG_MSM_SLEEP_TIME_OVERRIDE=y +CONFIG_MSM_SMD=y +CONFIG_MSM_SMD_DEBUG=y +CONFIG_MSM_SMD_LOGGING=y +CONFIG_MSM_SMD_NMEA=y +CONFIG_MSM_SMD_PKG3=y +# CONFIG_MSM_SMD_PKG4 is not set +CONFIG_MSM_SMD_PKT=y +CONFIG_MSM_SMD_QMI=y +CONFIG_MSM_SMD_TTY=y +# CONFIG_MSM_SOC_REV_A is not set +CONFIG_MSM_SOC_REV_NONE=y +CONFIG_MSM_STACKED_MEMORY=y +CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET=1000000000 +CONFIG_MSM_VIC=y +# CONFIG_MSM_VREG_SWITCH_INVERTED is not set +CONFIG_MTD=y +# CONFIG_MTD_ABSENT is not set +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_MTD_BLOCK2MTD is not set +# CONFIG_MTD_CFI is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +CONFIG_MTD_CHAR=y +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_CONCAT is not set +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +# CONFIG_MTD_JEDECPROBE is not set +# CONFIG_MTD_LPDDR is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +CONFIG_MTD_MAP_BANK_WIDTH_2=y +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +CONFIG_MTD_MSM_NAND=y +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_NAND is not set +CONFIG_MTD_NAND_IDS=y +# CONFIG_MTD_ONENAND is not set +# CONFIG_MTD_OOPS is not set +CONFIG_MTD_PARTITIONS=y +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_PLATRAM is not set +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_REDBOOT_PARTS is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_TESTS is not set +# CONFIG_MTD_UBI is not set +# CONFIG_MUTEX_SPIN_ON_OWNER is not set +CONFIG_NETDEV_1000=y +CONFIG_NETDEV_10000=y +# CONFIG_NET_DROP_MONITOR is not set +# CONFIG_NET_TCPPROBE is not set +# CONFIG_NFS_FS is not set +# CONFIG_NFTL is not set +# CONFIG_NLS is not set +CONFIG_NOP_TRACER=y +CONFIG_NO_HZ=y +# CONFIG_OABI_COMPAT is not set +CONFIG_P54_COMMON=m +CONFIG_P54_LEDS=y +# CONFIG_PACKET is not set +CONFIG_PAGE_OFFSET=0xC0000000 +# CONFIG_PARTITION_ADVANCED is not set +# CONFIG_PERF_EVENTS is not set +# CONFIG_PHYLIB is not set +CONFIG_PHYS_OFFSET=0x10000000 +# CONFIG_POWER_SUPPLY is not set +CONFIG_PPP=y +# CONFIG_PPPOE is not set +# CONFIG_PPPOLAC is not set +# CONFIG_PPPOPNS is not set +CONFIG_PPP_ASYNC=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +# CONFIG_PPP_FILTER is not set +# CONFIG_PPP_MPPE is not set +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_SYNC_TTY is not set +# CONFIG_PRINTK_TIME is not set +# CONFIG_PROFILING is not set +CONFIG_RD_LZO=y +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_BQ24022 is not set +# CONFIG_REGULATOR_DEBUG is not set +# CONFIG_REGULATOR_DUMMY is not set +# CONFIG_REGULATOR_FIXED_VOLTAGE is not set +# CONFIG_REGULATOR_LP3971 is not set +# CONFIG_REGULATOR_MAX1586 is not set +# CONFIG_REGULATOR_MAX8649 is not set +# CONFIG_REGULATOR_MAX8660 is not set +# CONFIG_REGULATOR_TPS65023 is not set +# CONFIG_REGULATOR_TPS6507X is not set +# CONFIG_REGULATOR_USERSPACE_CONSUMER is not set +# CONFIG_REGULATOR_VIRTUAL_CONSUMER is not set +# CONFIG_RFD_FTL is not set +# CONFIG_ROMFS_FS is not set +CONFIG_RT2X00=m +# CONFIG_RTC_CLASS is not set +CONFIG_SCHEDSTATS=y +CONFIG_SCHED_DEBUG=y +# CONFIG_SCSI is not set +# CONFIG_SCSI_DMA is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SENSORS_TSL2563 is not set +# CONFIG_SERIAL_8250 is not set +# CONFIG_SERIAL_BCM_BT_LPM is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_CLOCK_CONTROL=y +CONFIG_SERIAL_MSM_CONSOLE=y +# CONFIG_SERIAL_MSM_HS is not set +# CONFIG_SERIAL_MSM_HSL is not set +# CONFIG_SERIAL_MSM_RX_WAKEUP is not set +# CONFIG_SERIO is not set +CONFIG_SLAB=y +# CONFIG_SLIP is not set +# CONFIG_SLOW_WORK is not set +# CONFIG_SLUB is not set +# CONFIG_SMC911X is not set +CONFIG_SMC91X=y +# CONFIG_SMSC911X is not set +# CONFIG_SM_FTL is not set +# CONFIG_SOUND is not set +CONFIG_SPLIT_PTLOCK_CPUS=4 +# CONFIG_SSFDC is not set +CONFIG_STACKTRACE=y +# CONFIG_ST_BT is not set +# CONFIG_SWITCH is not set +# CONFIG_SYSCTL_SYSCALL_CHECK is not set +# CONFIG_SYSVIPC is not set +# CONFIG_TCG_TPM is not set +CONFIG_TICK_ONESHOT=y +# CONFIG_TIMER_STATS is not set +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_TOUCHSCREEN_AD7879 is not set +# CONFIG_TOUCHSCREEN_AD7879_I2C is not set +# CONFIG_TOUCHSCREEN_CY8C_TS is not set +# CONFIG_TOUCHSCREEN_CYTTSP_I2C is not set +# CONFIG_TOUCHSCREEN_DYNAPRO is not set +# CONFIG_TOUCHSCREEN_EETI is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_HAMPSHIRE is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MCS5000 is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +# CONFIG_TOUCHSCREEN_MSM_LEGACY is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_QT602240 is not set +# CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_TPS6507X is not set +# CONFIG_TOUCHSCREEN_TSC2007 is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_WACOM_W8001 is not set +# CONFIG_TPS65010 is not set +CONFIG_TRACEPOINTS=y +CONFIG_TRACING=y +# CONFIG_TSIF is not set +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_USB is not set +CONFIG_USB_ARCH_HAS_EHCI=y +# CONFIG_VFAT_FS is not set +# CONFIG_VFP is not set +# CONFIG_VGA_CONSOLE is not set +CONFIG_VIDEO_OUTPUT_CONTROL=y +# CONFIG_VMSPLIT_2G is not set +CONFIG_VMSPLIT_3G=y +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_WEXT_PRIV=y +CONFIG_WEXT_SPY=y +# CONFIG_WIFI_CONTROL_FUNC is not set +CONFIG_WIRELESS_EXT=y +# CONFIG_YAFFS_FS is not set +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZLIB_DEFLATE=y diff --git a/chromeos/config/armel/config.flavour.chromeos-tegra2 b/chromeos/config/armel/config.flavour.chromeos-tegra2 new file mode 100644 index 00000000000..3d9d39722bf --- /dev/null +++ b/chromeos/config/armel/config.flavour.chromeos-tegra2 @@ -0,0 +1,372 @@ +# +# Config options generated by splitconfig +# +# CONFIG_ANDROID is not set +CONFIG_ANDROID_PARANOID_NETWORK=y +# CONFIG_ARCH_MSM is not set +CONFIG_ARCH_VERSATILE=y +CONFIG_ARCH_VERSATILE_PB=y +CONFIG_ARM_AMBA=y +CONFIG_ARM_TIMER_SP804=y +CONFIG_ARM_VIC=y +CONFIG_ARM_VIC_NR=2 +# CONFIG_ASHMEM is not set +# CONFIG_ATH6K_LEGACY is not set +CONFIG_ATH_COMMON=m +# CONFIG_ATH_DEBUG is not set +# CONFIG_BACKLIGHT_LCD_SUPPORT is not set +CONFIG_BINARY_PRINTF=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_LOOP is not set +# CONFIG_BLK_DEV_RAM is not set +# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set +CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0 +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +CONFIG_BROKEN_ON_SMP=y +CONFIG_BT=m +CONFIG_BT_BNEP=m +# CONFIG_BT_BNEP_MC_FILTER is not set +# CONFIG_BT_BNEP_PROTO_FILTER is not set +# CONFIG_BT_HCIBTSDIO is not set +# CONFIG_BT_HCIUART is not set +CONFIG_BT_HCIVHCI=m +CONFIG_BT_HIDP=m +CONFIG_BT_L2CAP=m +# CONFIG_BT_L2CAP_EXT_FEATURES is not set +# CONFIG_BT_MRVL is not set +CONFIG_BT_RFCOMM=m +# CONFIG_BT_RFCOMM_TTY is not set +CONFIG_BT_SCO=m +CONFIG_CFG80211=m +# CONFIG_CIFS is not set +CONFIG_CMDLINE="mem=64M console=ttyMSM,115200n8" +# CONFIG_CMDLINE_FORCE is not set +CONFIG_COMMON_CLKDEV=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_CPU_32v5=y +CONFIG_CPU_ABRT_EV5TJ=y +CONFIG_CPU_ARM926T=y +# CONFIG_CPU_CACHE_ROUND_ROBIN is not set +CONFIG_CPU_CACHE_VIVT=y +CONFIG_CPU_COPY_V4WB=y +# CONFIG_CPU_DCACHE_WRITETHROUGH is not set +# CONFIG_CPU_IDLE is not set +CONFIG_CPU_PABRT_LEGACY=y +CONFIG_CPU_TLB_V4WBI=y +# CONFIG_CRAMFS is not set +CONFIG_CRC16=m +CONFIG_CRYPTO=m +CONFIG_CRYPTO_AEAD=m +CONFIG_CRYPTO_AEAD2=m +CONFIG_CRYPTO_AES=m +CONFIG_CRYPTO_ALGAPI=m +CONFIG_CRYPTO_ALGAPI2=m +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_ARC4=m +CONFIG_CRYPTO_AUTHENC=m +CONFIG_CRYPTO_BLKCIPHER=m +CONFIG_CRYPTO_BLKCIPHER2=m +CONFIG_CRYPTO_CBC=m +CONFIG_CRYPTO_CRC32C=m +CONFIG_CRYPTO_DES=m +CONFIG_CRYPTO_ECB=m +CONFIG_CRYPTO_HASH=m +CONFIG_CRYPTO_HASH2=m +CONFIG_CRYPTO_HMAC=m +CONFIG_CRYPTO_MANAGER=m +CONFIG_CRYPTO_MANAGER2=m +CONFIG_CRYPTO_MD5=m +CONFIG_CRYPTO_MICHAEL_MIC=m +CONFIG_CRYPTO_PCOMP=m +CONFIG_CRYPTO_RNG2=m +CONFIG_CRYPTO_SHA1=m +CONFIG_CRYPTO_SHA256=m +CONFIG_CRYPTO_WORKQUEUE=m +# CONFIG_DCC_TTY is not set +# CONFIG_DEBUG_ERRORS is not set +# CONFIG_DEBUG_GPIO is not set +# CONFIG_DEBUG_ICEDCC is not set +CONFIG_DEBUG_LL=y +CONFIG_DEBUG_MUTEXES=y +CONFIG_DEBUG_PREEMPT=y +# CONFIG_DEBUG_SLAB is not set +CONFIG_DEBUG_SPINLOCK_SLEEP=y +# CONFIG_DEBUG_USER is not set +CONFIG_DECOMPRESS_LZO=y +CONFIG_DEFAULT_CFQ=y +CONFIG_DEFAULT_IOSCHED="cfq" +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DETECT_SOFTLOCKUP=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_DRM is not set +CONFIG_DUMMY=y +# CONFIG_EARLY_PRINTK is not set +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_EMBEDDED is not set +CONFIG_EVENT_TRACING=y +# CONFIG_EXT2_FS is not set +# CONFIG_EXT3_FS is not set +# CONFIG_FB_ARMCLCD is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_CFB_COPYAREA is not set +# CONFIG_FB_CFB_FILLRECT is not set +# CONFIG_FB_CFB_IMAGEBLIT is not set +CONFIG_FB_MODE_HELPERS=y +CONFIG_FB_TILEBLITTING=y +CONFIG_FRAME_POINTER=y +# CONFIG_FS_POSIX_ACL is not set +# CONFIG_FTL is not set +# CONFIG_FTRACE_STARTUP_TEST is not set +# CONFIG_FUNCTION_PROFILER is not set +CONFIG_FUNCTION_TRACER=y +# CONFIG_FUSE_FS is not set +CONFIG_GENERIC_ATOMIC64=y +CONFIG_GENERIC_TRACER=y +# CONFIG_GOBI_USBNET is not set +CONFIG_GPIOLIB=y +# CONFIG_GPIO_ADP5588 is not set +# CONFIG_GPIO_IT8761E is not set +# CONFIG_GPIO_MAX7300 is not set +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_GPIO_PCF857X is not set +# CONFIG_GPIO_PL061 is not set +# CONFIG_GPIO_SX150X is not set +# CONFIG_GPIO_SYSFS is not set +CONFIG_HAVE_CLK=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_HID_APPLE=m +# CONFIG_HID_MAGICMOUSE is not set +CONFIG_HID_WACOM=m +# CONFIG_HID_WACOM_POWER_SUPPLY is not set +CONFIG_HIGH_RES_TIMERS=y +CONFIG_HOSTAP=m +CONFIG_HOSTAP_FIRMWARE=y +CONFIG_HOSTAP_FIRMWARE_NVRAM=y +# CONFIG_HTC_EGPIO is not set +# CONFIG_HWMON is not set +# CONFIG_HW_RANDOM is not set +# CONFIG_I2C_CHARDEV is not set +# CONFIG_I2C_DESIGNWARE is not set +CONFIG_I2C_STUB=m +# CONFIG_I2C_VERSATILE is not set +CONFIG_ICST=y +CONFIG_IIO=m +# CONFIG_IIO_RING_BUFFER is not set +# CONFIG_IIO_TRIGGER is not set +CONFIG_IKCONFIG=m +CONFIG_INET_LRO=y +# CONFIG_INET_TUNNEL is not set +# CONFIG_INFTL is not set +# CONFIG_INPUT_EVBUG is not set +# CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_IOSCHED_CFQ=y +# CONFIG_IOSCHED_DEADLINE is not set +# CONFIG_IPV6 is not set +# CONFIG_IP_PNP is not set +# CONFIG_JFFS2_FS is not set +# CONFIG_KALLSYMS_ALL is not set +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_QCIKBD is not set +# CONFIG_KEYS is not set +CONFIG_KPROBES=y +# CONFIG_KPROBES_SANITY_TEST is not set +CONFIG_KRETPROBES=y +# CONFIG_LATENCYTOP is not set +# CONFIG_LEDS is not set +# CONFIG_LEDS_GPIO is not set +# CONFIG_LEDS_TRIGGER_GPIO is not set +CONFIG_LIB80211=m +CONFIG_LIB80211_CRYPT_CCMP=m +CONFIG_LIB80211_CRYPT_TKIP=m +CONFIG_LIB80211_CRYPT_WEP=m +# CONFIG_LIB80211_DEBUG is not set +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_DEBUG=y +# CONFIG_LIBERTAS_MESH is not set +# CONFIG_LIBERTAS_SDIO is not set +CONFIG_LIBERTAS_THINFIRM=m +# CONFIG_LIBERTAS_THINFIRM_DEBUG is not set +CONFIG_LOCALVERSION_AUTO=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_MAC80211=m +CONFIG_MAC80211_HWSIM=m +# CONFIG_MACH_VERSATILE_AB is not set +# CONFIG_MAX1363 is not set +# CONFIG_MEDIA_SUPPORT is not set +# CONFIG_MFD_ASIC3 is not set +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_T7L66XB is not set +# CONFIG_MFD_TC6387XB is not set +# CONFIG_MFD_TC6393XB is not set +# CONFIG_MG_DISK is not set +CONFIG_MMC=m +# CONFIG_MMC_ARMMMCI is not set +CONFIG_MMC_BLOCK=m +# CONFIG_MMC_PARANOID_SD_INIT is not set +CONFIG_MMC_SDHCI=m +# CONFIG_MMC_SDHCI_PLTFM is not set +# CONFIG_MMC_UNSAFE_RESUME is not set +# CONFIG_MSDOS_FS is not set +CONFIG_MTD=y +# CONFIG_MTD_ABSENT is not set +# CONFIG_MTD_AFS_PARTS is not set +# CONFIG_MTD_AR7_PARTS is not set +CONFIG_MTD_BLKDEVS=y +CONFIG_MTD_BLOCK=y +# CONFIG_MTD_BLOCK2MTD is not set +# CONFIG_MTD_CFI is not set +CONFIG_MTD_CFI_I1=y +CONFIG_MTD_CFI_I2=y +# CONFIG_MTD_CFI_I4 is not set +# CONFIG_MTD_CFI_I8 is not set +CONFIG_MTD_CHAR=y +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_COMPLEX_MAPPINGS is not set +# CONFIG_MTD_CONCAT is not set +# CONFIG_MTD_DEBUG is not set +# CONFIG_MTD_DOC2000 is not set +# CONFIG_MTD_DOC2001 is not set +# CONFIG_MTD_DOC2001PLUS is not set +# CONFIG_MTD_JEDECPROBE is not set +# CONFIG_MTD_LPDDR is not set +CONFIG_MTD_MAP_BANK_WIDTH_1=y +# CONFIG_MTD_MAP_BANK_WIDTH_16 is not set +CONFIG_MTD_MAP_BANK_WIDTH_2=y +# CONFIG_MTD_MAP_BANK_WIDTH_32 is not set +CONFIG_MTD_MAP_BANK_WIDTH_4=y +# CONFIG_MTD_MAP_BANK_WIDTH_8 is not set +# CONFIG_MTD_MTDRAM is not set +# CONFIG_MTD_NAND is not set +# CONFIG_MTD_NAND_IDS is not set +# CONFIG_MTD_ONENAND is not set +# CONFIG_MTD_OOPS is not set +CONFIG_MTD_PARTITIONS=y +# CONFIG_MTD_PHRAM is not set +# CONFIG_MTD_PISMO is not set +# CONFIG_MTD_PLATRAM is not set +# CONFIG_MTD_RAM is not set +# CONFIG_MTD_REDBOOT_PARTS is not set +# CONFIG_MTD_ROM is not set +# CONFIG_MTD_SLRAM is not set +# CONFIG_MTD_TESTS is not set +# CONFIG_MTD_UBI is not set +# CONFIG_MUTEX_SPIN_ON_OWNER is not set +CONFIG_NETDEV_1000=y +CONFIG_NETDEV_10000=y +# CONFIG_NET_DROP_MONITOR is not set +# CONFIG_NET_TCPPROBE is not set +# CONFIG_NFS_FS is not set +# CONFIG_NFTL is not set +# CONFIG_NLS is not set +CONFIG_NOP_TRACER=y +CONFIG_NO_HZ=y +# CONFIG_OABI_COMPAT is not set +CONFIG_P54_COMMON=m +CONFIG_P54_LEDS=y +# CONFIG_PACKET is not set +CONFIG_PAGE_OFFSET=0xC0000000 +# CONFIG_PARTITION_ADVANCED is not set +# CONFIG_PCI is not set +# CONFIG_PERF_EVENTS is not set +# CONFIG_PHYLIB is not set +CONFIG_PLAT_VERSATILE=y +# CONFIG_POWER_SUPPLY is not set +CONFIG_PPP=y +# CONFIG_PPPOE is not set +# CONFIG_PPPOLAC is not set +# CONFIG_PPPOPNS is not set +CONFIG_PPP_ASYNC=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +# CONFIG_PPP_FILTER is not set +# CONFIG_PPP_MPPE is not set +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_SYNC_TTY is not set +# CONFIG_PRINTK_TIME is not set +# CONFIG_PROFILING is not set +CONFIG_RD_LZO=y +# CONFIG_REGULATOR is not set +# CONFIG_RFD_FTL is not set +# CONFIG_ROMFS_FS is not set +CONFIG_RT2X00=m +# CONFIG_RTC_CLASS is not set +CONFIG_SCHEDSTATS=y +CONFIG_SCHED_DEBUG=y +# CONFIG_SCSI is not set +# CONFIG_SCSI_DMA is not set +# CONFIG_SECURITYFS is not set +# CONFIG_SENSORS_TSL2563 is not set +# CONFIG_SERIAL_8250 is not set +# CONFIG_SERIAL_AMBA_PL011 is not set +# CONFIG_SERIO is not set +CONFIG_SLAB=y +# CONFIG_SLIP is not set +# CONFIG_SLOW_WORK is not set +# CONFIG_SLUB is not set +# CONFIG_SMC911X is not set +CONFIG_SMC91X=y +# CONFIG_SMSC911X is not set +# CONFIG_SM_FTL is not set +# CONFIG_SOUND is not set +CONFIG_SPLIT_PTLOCK_CPUS=999999 +# CONFIG_SSFDC is not set +CONFIG_STACKTRACE=y +# CONFIG_ST_BT is not set +# CONFIG_SWITCH is not set +# CONFIG_SYSCTL_SYSCALL_CHECK is not set +# CONFIG_SYSVIPC is not set +# CONFIG_TCG_TPM is not set +CONFIG_TICK_ONESHOT=y +# CONFIG_TIMER_STATS is not set +# CONFIG_TMPFS_POSIX_ACL is not set +# CONFIG_TOUCHSCREEN_AD7879 is not set +# CONFIG_TOUCHSCREEN_AD7879_I2C is not set +# CONFIG_TOUCHSCREEN_CY8C_TS is not set +# CONFIG_TOUCHSCREEN_CYTTSP_I2C is not set +# CONFIG_TOUCHSCREEN_DYNAPRO is not set +# CONFIG_TOUCHSCREEN_EETI is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_HAMPSHIRE is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MCS5000 is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_QT602240 is not set +# CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_TPS6507X is not set +# CONFIG_TOUCHSCREEN_TSC2007 is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_W90X900 is not set +# CONFIG_TOUCHSCREEN_WACOM_W8001 is not set +# CONFIG_TPS65010 is not set +CONFIG_TRACEPOINTS=y +CONFIG_TRACING=y +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_USB is not set +# CONFIG_USB_ARCH_HAS_EHCI is not set +# CONFIG_VFAT_FS is not set +# CONFIG_VFP is not set +CONFIG_VIDEO_OUTPUT_CONTROL=y +# CONFIG_VMSPLIT_2G is not set +CONFIG_VMSPLIT_3G=y +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_WEXT_PRIV=y +CONFIG_WEXT_SPY=y +CONFIG_WIRELESS_EXT=y +# CONFIG_YAFFS_FS is not set +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZLIB_DEFLATE=y diff --git a/chromeos/config/armel/config.flavour.chromeos-voguev210 b/chromeos/config/armel/config.flavour.chromeos-voguev210 new file mode 100644 index 00000000000..04e87973334 --- /dev/null +++ b/chromeos/config/armel/config.flavour.chromeos-voguev210 @@ -0,0 +1,450 @@ +# +# Config options generated by splitconfig +# +# CONFIG_ACORN_PARTITION is not set +# CONFIG_AMIGA_PARTITION is not set +# CONFIG_ANDROID is not set +CONFIG_ANDROID_PARANOID_NETWORK=y +# CONFIG_ARCH_MSM is not set +CONFIG_ARCH_VERSATILE=y +CONFIG_ARCH_VERSATILE_PB=y +CONFIG_ARM_AMBA=y +CONFIG_ARM_TIMER_SP804=y +CONFIG_ARM_VIC=y +CONFIG_ARM_VIC_NR=2 +# CONFIG_ASHMEM is not set +# CONFIG_ATARI_PARTITION is not set +# CONFIG_ATH6K_LEGACY is not set +CONFIG_ATH_COMMON=m +# CONFIG_ATH_DEBUG is not set +# CONFIG_BACKLIGHT_LCD_SUPPORT is not set +CONFIG_BINARY_PRINTF=y +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_BLK_DEV_SD=y +# CONFIG_BLK_DEV_SR is not set +# CONFIG_BLK_DEV_XIP is not set +# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set +CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0 +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +CONFIG_BROKEN_ON_SMP=y +CONFIG_BSD_DISKLABEL=y +CONFIG_BT=m +CONFIG_BT_BNEP=m +# CONFIG_BT_BNEP_MC_FILTER is not set +# CONFIG_BT_BNEP_PROTO_FILTER is not set +# CONFIG_BT_HCIBTSDIO is not set +# CONFIG_BT_HCIUART is not set +CONFIG_BT_HCIVHCI=m +CONFIG_BT_HIDP=m +CONFIG_BT_L2CAP=m +# CONFIG_BT_L2CAP_EXT_FEATURES is not set +# CONFIG_BT_MRVL is not set +CONFIG_BT_RFCOMM=m +# CONFIG_BT_RFCOMM_TTY is not set +CONFIG_BT_SCO=m +CONFIG_CFG80211=m +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_CHR_DEV_SCH is not set +CONFIG_CHR_DEV_SG=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CIFS is not set +CONFIG_CMDLINE="root=/dev/ram0 rw ramdisk=8192 initrd=0x20800000,8M console=ttySAC1,115200 init=/linuxrc" +# CONFIG_CMDLINE_FORCE is not set +CONFIG_COMMON_CLKDEV=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_CPU_32v5=y +CONFIG_CPU_ABRT_EV5TJ=y +CONFIG_CPU_ARM926T=y +# CONFIG_CPU_CACHE_ROUND_ROBIN is not set +CONFIG_CPU_CACHE_VIVT=y +CONFIG_CPU_COPY_V4WB=y +# CONFIG_CPU_DCACHE_WRITETHROUGH is not set +# CONFIG_CPU_IDLE is not set +CONFIG_CPU_PABRT_LEGACY=y +CONFIG_CPU_TLB_V4WBI=y +CONFIG_CRAMFS=y +CONFIG_CRC16=m +CONFIG_CRYPTO=y +CONFIG_CRYPTO_AEAD2=m +CONFIG_CRYPTO_AES=m +CONFIG_CRYPTO_ALGAPI=m +CONFIG_CRYPTO_ALGAPI2=m +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_ARC4=m +# CONFIG_CRYPTO_AUTHENC is not set +CONFIG_CRYPTO_BLKCIPHER=m +CONFIG_CRYPTO_BLKCIPHER2=m +# CONFIG_CRYPTO_CBC is not set +CONFIG_CRYPTO_CRC32C=m +# CONFIG_CRYPTO_DES is not set +CONFIG_CRYPTO_ECB=m +CONFIG_CRYPTO_HASH=m +CONFIG_CRYPTO_HASH2=m +# CONFIG_CRYPTO_HMAC is not set +CONFIG_CRYPTO_MANAGER=m +CONFIG_CRYPTO_MANAGER2=m +# CONFIG_CRYPTO_MD5 is not set +CONFIG_CRYPTO_MICHAEL_MIC=m +CONFIG_CRYPTO_PCOMP=m +CONFIG_CRYPTO_RNG2=m +# CONFIG_CRYPTO_SHA1 is not set +CONFIG_CRYPTO_SHA256=m +CONFIG_CRYPTO_WORKQUEUE=m +# CONFIG_DCC_TTY is not set +CONFIG_DEBUG_ERRORS=y +# CONFIG_DEBUG_GPIO is not set +# CONFIG_DEBUG_ICEDCC is not set +CONFIG_DEBUG_LL=y +CONFIG_DEBUG_MUTEXES=y +# CONFIG_DEBUG_PREEMPT is not set +# CONFIG_DEBUG_SLAB is not set +CONFIG_DEBUG_SPINLOCK_SLEEP=y +CONFIG_DEBUG_USER=y +CONFIG_DECOMPRESS_LZO=y +CONFIG_DEFAULT_CFQ=y +CONFIG_DEFAULT_IOSCHED="cfq" +# CONFIG_DEFAULT_NOOP is not set +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DETECT_SOFTLOCKUP=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_DRM is not set +# CONFIG_DUMMY is not set +# CONFIG_EARLY_PRINTK is not set +# CONFIG_EEPROM_93CX6 is not set +# CONFIG_EFI_PARTITION is not set +# CONFIG_EMBEDDED is not set +CONFIG_EVENT_TRACING=y +CONFIG_EXT2_FS=y +# CONFIG_EXT2_FS_XATTR is not set +# CONFIG_EXT2_FS_XIP is not set +# CONFIG_EXT3_FS is not set +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +CONFIG_FAT_FS=y +# CONFIG_FB_ARMCLCD is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_CFB_COPYAREA is not set +# CONFIG_FB_CFB_FILLRECT is not set +# CONFIG_FB_CFB_IMAGEBLIT is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_TILEBLITTING is not set +# CONFIG_FPE_FASTFPE is not set +# CONFIG_FPE_NWFPE is not set +CONFIG_FRAME_POINTER=y +CONFIG_FS_POSIX_ACL=y +# CONFIG_FTRACE_STARTUP_TEST is not set +# CONFIG_FUNCTION_PROFILER is not set +CONFIG_FUNCTION_TRACER=y +# CONFIG_FUSE_FS is not set +CONFIG_GENERIC_ACL=y +CONFIG_GENERIC_ATOMIC64=y +CONFIG_GENERIC_TRACER=y +# CONFIG_GOBI_USBNET is not set +CONFIG_GPIOLIB=y +# CONFIG_GPIO_ADP5588 is not set +# CONFIG_GPIO_IT8761E is not set +# CONFIG_GPIO_MAX7300 is not set +# CONFIG_GPIO_MAX732X is not set +# CONFIG_GPIO_PCA953X is not set +# CONFIG_GPIO_PCF857X is not set +# CONFIG_GPIO_PL061 is not set +# CONFIG_GPIO_SX150X is not set +# CONFIG_GPIO_SYSFS is not set +CONFIG_HAVE_CLK=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_HID_APPLE=m +# CONFIG_HID_MAGICMOUSE is not set +CONFIG_HID_WACOM=m +# CONFIG_HID_WACOM_POWER_SUPPLY is not set +# CONFIG_HIGH_RES_TIMERS is not set +CONFIG_HOSTAP=m +CONFIG_HOSTAP_FIRMWARE=y +CONFIG_HOSTAP_FIRMWARE_NVRAM=y +# CONFIG_HTC_EGPIO is not set +# CONFIG_HWMON is not set +CONFIG_HW_RANDOM=y +# CONFIG_HW_RANDOM_TIMERIOMEM is not set +# CONFIG_I2C_CHARDEV is not set +# CONFIG_I2C_DESIGNWARE is not set +CONFIG_I2C_STUB=m +# CONFIG_I2C_VERSATILE is not set +CONFIG_ICST=y +CONFIG_IIO=m +# CONFIG_IIO_RING_BUFFER is not set +# CONFIG_IIO_TRIGGER is not set +CONFIG_IKCONFIG=m +# CONFIG_INET6_AH is not set +# CONFIG_INET6_ESP is not set +# CONFIG_INET6_IPCOMP is not set +# CONFIG_INET6_TUNNEL is not set +CONFIG_INET6_XFRM_MODE_BEET=m +# CONFIG_INET6_XFRM_MODE_ROUTEOPTIMIZATION is not set +CONFIG_INET6_XFRM_MODE_TRANSPORT=m +CONFIG_INET6_XFRM_MODE_TUNNEL=m +# CONFIG_INET6_XFRM_TUNNEL is not set +CONFIG_INET_LRO=y +CONFIG_INET_TUNNEL=m +# CONFIG_INPUT_EVBUG is not set +# CONFIG_INPUT_GPIO_ROTARY_ENCODER is not set +# CONFIG_INPUT_MOUSE is not set +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_IOSCHED_CFQ=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IPV6=m +# CONFIG_IPV6_MIP6 is not set +# CONFIG_IPV6_MROUTE is not set +# CONFIG_IPV6_MULTIPLE_TABLES is not set +CONFIG_IPV6_NDISC_NODETYPE=y +# CONFIG_IPV6_OPTIMISTIC_DAD is not set +# CONFIG_IPV6_PRIVACY is not set +# CONFIG_IPV6_ROUTER_PREF is not set +CONFIG_IPV6_SIT=m +# CONFIG_IPV6_SIT_6RD is not set +# CONFIG_IPV6_TUNNEL is not set +# CONFIG_IP_PNP is not set +# CONFIG_ISCSI_TCP is not set +CONFIG_KALLSYMS_ALL=y +# CONFIG_KARMA_PARTITION is not set +# CONFIG_KEYBOARD_ATKBD is not set +# CONFIG_KEYBOARD_QCIKBD is not set +# CONFIG_KEYS is not set +CONFIG_KPROBES=y +# CONFIG_KPROBES_SANITY_TEST is not set +CONFIG_KRETPROBES=y +# CONFIG_LATENCYTOP is not set +# CONFIG_LDM_PARTITION is not set +# CONFIG_LEDS is not set +# CONFIG_LEDS_GPIO is not set +# CONFIG_LEDS_TRIGGER_GPIO is not set +CONFIG_LIB80211=m +CONFIG_LIB80211_CRYPT_CCMP=m +CONFIG_LIB80211_CRYPT_TKIP=m +CONFIG_LIB80211_CRYPT_WEP=m +# CONFIG_LIB80211_DEBUG is not set +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_DEBUG=y +# CONFIG_LIBERTAS_MESH is not set +# CONFIG_LIBERTAS_SDIO is not set +CONFIG_LIBERTAS_THINFIRM=m +# CONFIG_LIBERTAS_THINFIRM_DEBUG is not set +# CONFIG_LIBFC is not set +# CONFIG_LIBFCOE is not set +CONFIG_LOCALVERSION_AUTO=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_MAC80211=m +CONFIG_MAC80211_HWSIM=m +# CONFIG_MACH_VERSATILE_AB is not set +# CONFIG_MAC_PARTITION is not set +# CONFIG_MAX1363 is not set +# CONFIG_MEDIA_SUPPORT is not set +# CONFIG_MFD_ASIC3 is not set +# CONFIG_MFD_CORE is not set +# CONFIG_MFD_T7L66XB is not set +# CONFIG_MFD_TC6387XB is not set +# CONFIG_MFD_TC6393XB is not set +# CONFIG_MG_DISK is not set +# CONFIG_MINIX_SUBPARTITION is not set +CONFIG_MMC=m +# CONFIG_MMC_ARMMMCI is not set +CONFIG_MMC_BLOCK=m +# CONFIG_MMC_PARANOID_SD_INIT is not set +CONFIG_MMC_SDHCI=m +# CONFIG_MMC_SDHCI_PLTFM is not set +# CONFIG_MMC_UNSAFE_RESUME is not set +CONFIG_MSDOS_FS=y +# CONFIG_MTD is not set +# CONFIG_MUTEX_SPIN_ON_OWNER is not set +CONFIG_NETDEV_1000=y +CONFIG_NETDEV_10000=y +# CONFIG_NET_DROP_MONITOR is not set +# CONFIG_NET_TCPPROBE is not set +# CONFIG_NFS_FS is not set +CONFIG_NLS=y +CONFIG_NLS_ASCII=y +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +CONFIG_NLS_DEFAULT="iso8859-1" +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +# CONFIG_NLS_UTF8 is not set +CONFIG_NOP_TRACER=y +# CONFIG_NO_HZ is not set +CONFIG_OABI_COMPAT=y +# CONFIG_OSF_PARTITION is not set +CONFIG_P54_COMMON=m +CONFIG_P54_LEDS=y +# CONFIG_PACKET is not set +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PARTITION_ADVANCED=y +# CONFIG_PCI is not set +# CONFIG_PERF_EVENTS is not set +# CONFIG_PHYLIB is not set +CONFIG_PLAT_VERSATILE=y +# CONFIG_POWER_SUPPLY is not set +CONFIG_PPP=y +# CONFIG_PPPOE is not set +# CONFIG_PPPOLAC is not set +# CONFIG_PPPOPNS is not set +CONFIG_PPP_ASYNC=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +# CONFIG_PPP_FILTER is not set +# CONFIG_PPP_MPPE is not set +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_SYNC_TTY is not set +# CONFIG_PRINTK_TIME is not set +# CONFIG_PROFILING is not set +CONFIG_RD_LZO=y +# CONFIG_REGULATOR is not set +CONFIG_ROMFS_BACKED_BY_BLOCK=y +# CONFIG_ROMFS_BACKED_BY_BOTH is not set +# CONFIG_ROMFS_BACKED_BY_MTD is not set +CONFIG_ROMFS_FS=y +CONFIG_ROMFS_ON_BLOCK=y +CONFIG_RT2X00=m +# CONFIG_RTC_CLASS is not set +CONFIG_SCHEDSTATS=y +CONFIG_SCHED_DEBUG=y +CONFIG_SCSI=y +# CONFIG_SCSI_CONSTANTS is not set +# CONFIG_SCSI_DEBUG is not set +# CONFIG_SCSI_DH is not set +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_LOGGING is not set +CONFIG_SCSI_LOWLEVEL=y +# CONFIG_SCSI_MULTI_LUN is not set +# CONFIG_SCSI_OSD_INITIATOR is not set +CONFIG_SCSI_PROC_FS=y +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +# CONFIG_SCSI_SPI_ATTRS is not set +# CONFIG_SCSI_SRP_ATTRS is not set +# CONFIG_SCSI_TGT is not set +CONFIG_SCSI_WAIT_SCAN=m +# CONFIG_SECURITYFS is not set +# CONFIG_SENSORS_TSL2563 is not set +CONFIG_SERIAL_8250=y +# CONFIG_SERIAL_8250_CONSOLE is not set +# CONFIG_SERIAL_8250_EXTENDED is not set +CONFIG_SERIAL_8250_NR_UARTS=4 +CONFIG_SERIAL_8250_RUNTIME_UARTS=4 +# CONFIG_SERIAL_AMBA_PL011 is not set +CONFIG_SERIAL_CORE=y +CONFIG_SERIO=y +# CONFIG_SERIO_ALTERA_PS2 is not set +# CONFIG_SERIO_AMBAKMI is not set +# CONFIG_SERIO_RAW is not set +CONFIG_SERIO_SERPORT=y +# CONFIG_SGI_PARTITION is not set +CONFIG_SLAB=y +# CONFIG_SLIP is not set +# CONFIG_SLOW_WORK is not set +# CONFIG_SLUB is not set +# CONFIG_SMC911X is not set +# CONFIG_SMC91X is not set +# CONFIG_SMSC911X is not set +CONFIG_SOLARIS_X86_PARTITION=y +# CONFIG_SOUND is not set +CONFIG_SPLIT_PTLOCK_CPUS=999999 +CONFIG_STACKTRACE=y +# CONFIG_ST_BT is not set +# CONFIG_SUN_PARTITION is not set +# CONFIG_SWITCH is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +# CONFIG_SYSV68_PARTITION is not set +# CONFIG_SYSVIPC is not set +# CONFIG_TCG_TPM is not set +# CONFIG_TIMER_STATS is not set +CONFIG_TMPFS_POSIX_ACL=y +# CONFIG_TOUCHSCREEN_AD7879 is not set +# CONFIG_TOUCHSCREEN_AD7879_I2C is not set +# CONFIG_TOUCHSCREEN_CY8C_TS is not set +# CONFIG_TOUCHSCREEN_CYTTSP_I2C is not set +# CONFIG_TOUCHSCREEN_DYNAPRO is not set +# CONFIG_TOUCHSCREEN_EETI is not set +# CONFIG_TOUCHSCREEN_ELO is not set +# CONFIG_TOUCHSCREEN_FUJITSU is not set +# CONFIG_TOUCHSCREEN_GUNZE is not set +# CONFIG_TOUCHSCREEN_HAMPSHIRE is not set +# CONFIG_TOUCHSCREEN_INEXIO is not set +# CONFIG_TOUCHSCREEN_MCS5000 is not set +# CONFIG_TOUCHSCREEN_MK712 is not set +# CONFIG_TOUCHSCREEN_MTOUCH is not set +# CONFIG_TOUCHSCREEN_PENMOUNT is not set +# CONFIG_TOUCHSCREEN_QT602240 is not set +# CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI is not set +# CONFIG_TOUCHSCREEN_TOUCHIT213 is not set +# CONFIG_TOUCHSCREEN_TOUCHRIGHT is not set +# CONFIG_TOUCHSCREEN_TOUCHWIN is not set +# CONFIG_TOUCHSCREEN_TPS6507X is not set +# CONFIG_TOUCHSCREEN_TSC2007 is not set +# CONFIG_TOUCHSCREEN_USB_COMPOSITE is not set +# CONFIG_TOUCHSCREEN_W90X900 is not set +# CONFIG_TOUCHSCREEN_WACOM_W8001 is not set +# CONFIG_TPS65010 is not set +CONFIG_TRACEPOINTS=y +CONFIG_TRACING=y +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_ULTRIX_PARTITION is not set +# CONFIG_UNIXWARE_DISKLABEL is not set +# CONFIG_USB is not set +# CONFIG_USB_ARCH_HAS_EHCI is not set +CONFIG_VFAT_FS=y +CONFIG_VFP=y +CONFIG_VIDEO_OUTPUT_CONTROL=y +# CONFIG_VMSPLIT_2G is not set +CONFIG_VMSPLIT_3G=y +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_WEXT_PRIV=y +CONFIG_WEXT_SPY=y +CONFIG_WIRELESS_EXT=y +CONFIG_XFRM=y +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_XFRM_SUB_POLICY is not set +# CONFIG_XFRM_USER is not set +CONFIG_ZBOOT_ROM_BSS=0 +CONFIG_ZBOOT_ROM_TEXT=0 +CONFIG_ZLIB_DEFLATE=y diff --git a/chromeos/config/config.common.chromeos b/chromeos/config/config.common.chromeos new file mode 100644 index 00000000000..cb6ac334548 --- /dev/null +++ b/chromeos/config/config.common.chromeos @@ -0,0 +1,532 @@ +# +# Config options generated by splitconfig +# +# CONFIG_AB3100_CORE is not set +# CONFIG_ACCESSIBILITY is not set +# CONFIG_AD525X_DPOT is not set +# CONFIG_ADFS_FS is not set +# CONFIG_AFFS_FS is not set +# CONFIG_AF_RXRPC is not set +CONFIG_AIO=y +CONFIG_ANDROID_PMEM=y +CONFIG_ANON_INODES=y +# CONFIG_APANIC is not set +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y +# CONFIG_ARPD is not set +# CONFIG_ATALK is not set +# CONFIG_ATA_OVER_ETH is not set +# CONFIG_ATM is not set +# CONFIG_ATOMIC64_SELFTEST is not set +# CONFIG_AUTOFS4_FS is not set +# CONFIG_AUTOFS_FS is not set +# CONFIG_AUXDISPLAY is not set +# CONFIG_B43 is not set +# CONFIG_B43LEGACY is not set +# CONFIG_B44 is not set +# CONFIG_BACKTRACE_SELF_TEST is not set +CONFIG_BASE_FULL=y +CONFIG_BASE_SMALL=0 +# CONFIG_BATMAN_ADV is not set +# CONFIG_BCM4329 is not set +# CONFIG_BEFS_FS is not set +# CONFIG_BFS_FS is not set +CONFIG_BINFMT_ELF=y +CONFIG_BITREVERSE=y +CONFIG_BLK_DEV=y +# CONFIG_BLK_DEV_COW_COMMON is not set +CONFIG_BLK_DEV_INITRD=y +# CONFIG_BLK_DEV_INTEGRITY is not set +# CONFIG_BLK_DEV_IO_TRACE is not set +# CONFIG_BLK_DEV_NBD is not set +CONFIG_BLOCK=y +# CONFIG_BONDING is not set +# CONFIG_BOOT_PRINTK_DELAY is not set +# CONFIG_BOOT_TRACER is not set +CONFIG_BRANCH_PROFILE_NONE=y +# CONFIG_BRIDGE is not set +# CONFIG_BTRFS_FS is not set +CONFIG_BUG=y +# CONFIG_C2PORT is not set +# CONFIG_CAIF is not set +# CONFIG_CAN is not set +CONFIG_CC_OPTIMIZE_FOR_SIZE=y +# CONFIG_CDROM_PKTCDVD is not set +CONFIG_CFG80211_DEFAULT_PS=y +# CONFIG_CFG80211_DEVELOPER_WARNINGS is not set +# CONFIG_CFG80211_INTERNAL_REGDB is not set +# CONFIG_CFG80211_REG_DEBUG is not set +CONFIG_CFG80211_WEXT=y +# CONFIG_COMEDI is not set +# CONFIG_COMPACTION is not set +# CONFIG_CONFIGFS_FS is not set +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_CONSTRUCTORS=y +CONFIG_CRC32=y +CONFIG_CRC_CCITT=y +CONFIG_CROSS_COMPILE="" +# CONFIG_CRYPTO_ANUBIS is not set +# CONFIG_CRYPTO_BLOWFISH is not set +# CONFIG_CRYPTO_CAMELLIA is not set +# CONFIG_CRYPTO_CAST5 is not set +# CONFIG_CRYPTO_CAST6 is not set +# CONFIG_CRYPTO_CCM is not set +# CONFIG_CRYPTO_CRYPTD is not set +# CONFIG_CRYPTO_CTR is not set +# CONFIG_CRYPTO_CTS is not set +# CONFIG_CRYPTO_DEFLATE is not set +# CONFIG_CRYPTO_FCRYPT is not set +# CONFIG_CRYPTO_GCM is not set +# CONFIG_CRYPTO_GF128MUL is not set +# CONFIG_CRYPTO_GHASH is not set +CONFIG_CRYPTO_HW=y +# CONFIG_CRYPTO_KHAZAD is not set +# CONFIG_CRYPTO_LRW is not set +# CONFIG_CRYPTO_LZO is not set +# CONFIG_CRYPTO_MANAGER_TESTS is not set +# CONFIG_CRYPTO_MD4 is not set +# CONFIG_CRYPTO_NULL is not set +# CONFIG_CRYPTO_PCBC is not set +# CONFIG_CRYPTO_RMD128 is not set +# CONFIG_CRYPTO_RMD160 is not set +# CONFIG_CRYPTO_RMD256 is not set +# CONFIG_CRYPTO_RMD320 is not set +# CONFIG_CRYPTO_SALSA20 is not set +# CONFIG_CRYPTO_SEED is not set +# CONFIG_CRYPTO_SEQIV is not set +# CONFIG_CRYPTO_SERPENT is not set +# CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_TEA is not set +# CONFIG_CRYPTO_TEST is not set +# CONFIG_CRYPTO_TGR192 is not set +# CONFIG_CRYPTO_TWOFISH is not set +# CONFIG_CRYPTO_VMAC is not set +# CONFIG_CRYPTO_WP512 is not set +# CONFIG_CRYPTO_XCBC is not set +# CONFIG_CRYPTO_XTS is not set +# CONFIG_CRYPTO_ZLIB is not set +# CONFIG_DCB is not set +# CONFIG_DEBUG_BLOCK_EXT_DEVT is not set +# CONFIG_DEBUG_CREDENTIALS is not set +# CONFIG_DEBUG_DRIVER is not set +# CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set +CONFIG_DEBUG_FS=y +CONFIG_DEBUG_KERNEL=y +# CONFIG_DEBUG_KMEMLEAK is not set +# CONFIG_DEBUG_KOBJECT is not set +# CONFIG_DEBUG_LIST is not set +# CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set +# CONFIG_DEBUG_LOCK_ALLOC is not set +# CONFIG_DEBUG_NOTIFIERS is not set +# CONFIG_DEBUG_OBJECTS is not set +# CONFIG_DEBUG_RT_MUTEXES is not set +# CONFIG_DEBUG_SG is not set +# CONFIG_DEBUG_SHIRQ is not set +# CONFIG_DEBUG_SPINLOCK is not set +# CONFIG_DEBUG_VM is not set +# CONFIG_DEBUG_WRITECOUNT is not set +# CONFIG_DECNET is not set +CONFIG_DECOMPRESS_GZIP=y +# CONFIG_DEFAULT_DEADLINE is not set +CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 +# CONFIG_DEFAULT_SECURITY_SELINUX is not set +# CONFIG_DEFAULT_SECURITY_SMACK is not set +CONFIG_DEFAULT_TCP_CONG="cubic" +CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" +CONFIG_DEVKMEM=y +CONFIG_DEVMEM=y +# CONFIG_DEVPTS_MULTIPLE_INSTANCES is not set +# CONFIG_DISCONTIGMEM_MANUAL is not set +# CONFIG_DISPLAY_SUPPORT is not set +# CONFIG_DLM is not set +# CONFIG_DMADEVICES is not set +CONFIG_DUMMY_CONSOLE=y +# CONFIG_DYNAMIC_DEBUG is not set +# CONFIG_ECHO is not set +# CONFIG_ECONET is not set +# CONFIG_EEPROM_MAX6875 is not set +# CONFIG_EFS_FS is not set +CONFIG_ELF_CORE=y +CONFIG_ENABLE_MUST_CHECK=y +# CONFIG_ENCLOSURE_SERVICES is not set +CONFIG_EPOLL=y +# CONFIG_EQUALIZER is not set +CONFIG_EVENTFD=y +CONFIG_EXPERIMENTAL=y +CONFIG_EXTRA_FIRMWARE="" +# CONFIG_FAULT_INJECTION is not set +CONFIG_FB=y +# CONFIG_FB_BOOT_VESA_SUPPORT is not set +# CONFIG_FB_BROADSHEET is not set +# CONFIG_FB_CFB_REV_PIXELS_IN_BYTE is not set +# CONFIG_FB_DDC is not set +# CONFIG_FB_FOREIGN_ENDIAN is not set +# CONFIG_FB_MACMODES is not set +# CONFIG_FB_MB862XX is not set +# CONFIG_FB_METRONOME is not set +# CONFIG_FB_S1D13XXX is not set +# CONFIG_FB_SM7XX is not set +# CONFIG_FB_SVGALIB is not set +# CONFIG_FB_SYS_COPYAREA is not set +# CONFIG_FB_SYS_FILLRECT is not set +# CONFIG_FB_SYS_FOPS is not set +# CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_VIRTUAL is not set +CONFIG_FILE_LOCKING=y +# CONFIG_FIRMWARE_EDID is not set +CONFIG_FIRMWARE_IN_KERNEL=y +# CONFIG_FONTS is not set +CONFIG_FONT_8x16=y +CONFIG_FONT_8x8=y +CONFIG_FRAMEBUFFER_CONSOLE=y +# CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY is not set +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +CONFIG_FREEZER=y +# CONFIG_FSCACHE is not set +CONFIG_FSNOTIFY=y +CONFIG_FTRACE=y +CONFIG_FUTEX=y +CONFIG_FW_LOADER=y +# CONFIG_GAMEPORT is not set +# CONFIG_GCOV_KERNEL is not set +CONFIG_GENERIC_CALIBRATE_DELAY=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_GENERIC_FIND_LAST_BIT=y +CONFIG_GENERIC_GPIO=y +CONFIG_GENERIC_HARDIRQS=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y +CONFIG_GENERIC_HWEIGHT=y +CONFIG_GENERIC_IRQ_PROBE=y +CONFIG_GENERIC_TIME=y +# CONFIG_GFS2_FS is not set +# CONFIG_HAMRADIO is not set +# CONFIG_HAPTIC_ISA1200 is not set +CONFIG_HAS_DMA=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT=y +CONFIG_HAVE_ARCH_KGDB=y +CONFIG_HAVE_FUNCTION_TRACER=y +CONFIG_HAVE_IDE=y +CONFIG_HAVE_KERNEL_GZIP=y +CONFIG_HAVE_KERNEL_LZMA=y +CONFIG_HAVE_KERNEL_LZO=y +CONFIG_HAVE_KPROBES=y +CONFIG_HAVE_KRETPROBES=y +CONFIG_HAVE_OPROFILE=y +CONFIG_HAVE_PERF_EVENTS=y +# CONFIG_HEADERS_CHECK is not set +# CONFIG_HFSPLUS_FS is not set +# CONFIG_HFS_FS is not set +CONFIG_HID=y +CONFIG_HID_SUPPORT=y +CONFIG_HOTPLUG=y +# CONFIG_HPFS_FS is not set +# CONFIG_HTC_PASIC3 is not set +CONFIG_HW_CONSOLE=y +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_COMPAT=y +# CONFIG_I2C_DEBUG_ALGO is not set +# CONFIG_I2C_DEBUG_BUS is not set +# CONFIG_I2C_DEBUG_CORE is not set +# CONFIG_I2C_GPIO is not set +CONFIG_I2C_HELPER_AUTO=y +# CONFIG_I2C_PARPORT_LIGHT is not set +# CONFIG_I2C_TAOS_EVM is not set +# CONFIG_I2C_XILINX is not set +# CONFIG_IBM_NEW_EMAC_EMAC4 is not set +# CONFIG_IBM_NEW_EMAC_MAL_CLR_ICINTSTAT is not set +# CONFIG_IBM_NEW_EMAC_MAL_COMMON_ERR is not set +# CONFIG_IBM_NEW_EMAC_NO_FLOW_CTRL is not set +# CONFIG_IBM_NEW_EMAC_RGMII is not set +# CONFIG_IBM_NEW_EMAC_TAH is not set +# CONFIG_IBM_NEW_EMAC_ZMII is not set +# CONFIG_ICS932S401 is not set +# CONFIG_IDE is not set +# CONFIG_IEEE802154 is not set +CONFIG_IKCONFIG_PROC=y +CONFIG_INET=y +# CONFIG_INET_AH is not set +# CONFIG_INET_DIAG is not set +# CONFIG_INET_ESP is not set +# CONFIG_INET_IPCOMP is not set +# CONFIG_INET_XFRM_MODE_BEET is not set +# CONFIG_INET_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET_XFRM_MODE_TUNNEL is not set +# CONFIG_INET_XFRM_TUNNEL is not set +CONFIG_INITRAMFS_SOURCE="" +CONFIG_INIT_ENV_ARG_LIMIT=32 +# CONFIG_INLINE_READ_LOCK is not set +# CONFIG_INLINE_READ_LOCK_BH is not set +# CONFIG_INLINE_READ_LOCK_IRQ is not set +# CONFIG_INLINE_READ_LOCK_IRQSAVE is not set +# CONFIG_INLINE_READ_TRYLOCK is not set +# CONFIG_INLINE_READ_UNLOCK_BH is not set +# CONFIG_INLINE_READ_UNLOCK_IRQRESTORE is not set +# CONFIG_INLINE_SPIN_LOCK is not set +# CONFIG_INLINE_SPIN_LOCK_BH is not set +# CONFIG_INLINE_SPIN_LOCK_IRQ is not set +# CONFIG_INLINE_SPIN_LOCK_IRQSAVE is not set +# CONFIG_INLINE_SPIN_TRYLOCK is not set +# CONFIG_INLINE_SPIN_TRYLOCK_BH is not set +# CONFIG_INLINE_SPIN_UNLOCK_BH is not set +# CONFIG_INLINE_SPIN_UNLOCK_IRQRESTORE is not set +# CONFIG_INLINE_WRITE_LOCK is not set +# CONFIG_INLINE_WRITE_LOCK_BH is not set +# CONFIG_INLINE_WRITE_LOCK_IRQ is not set +# CONFIG_INLINE_WRITE_LOCK_IRQSAVE is not set +# CONFIG_INLINE_WRITE_TRYLOCK is not set +# CONFIG_INLINE_WRITE_UNLOCK_BH is not set +# CONFIG_INLINE_WRITE_UNLOCK_IRQRESTORE is not set +CONFIG_INOTIFY=y +CONFIG_INOTIFY_USER=y +CONFIG_INPUT=y +# CONFIG_INPUT_AD714X is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_INPUT_GPIO is not set +# CONFIG_INPUT_JOYDEV is not set +# CONFIG_INPUT_JOYSTICK is not set +CONFIG_INPUT_KEYBOARD=y +# CONFIG_INPUT_KEYCHORD is not set +# CONFIG_INPUT_KEYRESET is not set +CONFIG_INPUT_MISC=y +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024 +CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768 +# CONFIG_INPUT_PCF8574 is not set +CONFIG_IOSCHED_NOOP=y +# CONFIG_IPMI_HANDLER is not set +# CONFIG_IPX is not set +# CONFIG_IP_DCCP is not set +CONFIG_IP_FIB_HASH=y +# CONFIG_IP_SCTP is not set +# CONFIG_IRDA is not set +# CONFIG_IRQSOFF_TRACER is not set +# CONFIG_ISDN is not set +# CONFIG_ISL29003 is not set +# CONFIG_IWM is not set +# CONFIG_IWMC3200TOP is not set +# CONFIG_JFS_FS is not set +CONFIG_KALLSYMS=y +# CONFIG_KERNEL_BZIP2 is not set +# CONFIG_KERNEL_DEBUGGER_CORE is not set +CONFIG_KERNEL_GZIP=y +# CONFIG_KERNEL_LZMA is not set +# CONFIG_KERNEL_LZO is not set +# CONFIG_KEXEC is not set +# CONFIG_KEYBOARD_ADP5588 is not set +# CONFIG_KEYBOARD_LM8323 is not set +# CONFIG_KEYBOARD_MATRIX is not set +# CONFIG_KEYBOARD_MAX7359 is not set +# CONFIG_KEYBOARD_OPENCORES is not set +# CONFIG_KEYBOARD_QT2160 is not set +# CONFIG_KEYBOARD_TCA6416 is not set +# CONFIG_KGDB is not set +# CONFIG_KMEMTRACE is not set +# CONFIG_KS8842 is not set +# CONFIG_KS8851_MLL is not set +# CONFIG_KSM is not set +# CONFIG_L2TP is not set +# CONFIG_LAPB is not set +# CONFIG_LEDS_BD2802 is not set +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_CPLD=y +# CONFIG_LEDS_LP3944 is not set +# CONFIG_LEDS_LT3593 is not set +# CONFIG_LEDS_PCA9532 is not set +# CONFIG_LEDS_PCA955X is not set +CONFIG_LEDS_TRIGGERS=y +# CONFIG_LEDS_TRIGGER_BACKLIGHT is not set +# CONFIG_LEDS_TRIGGER_DEFAULT_ON is not set +# CONFIG_LEDS_TRIGGER_HEARTBEAT is not set +# CONFIG_LEDS_TRIGGER_TIMER is not set +# CONFIG_LEGACY_PTYS is not set +# CONFIG_LKDTM is not set +# CONFIG_LLC2 is not set +CONFIG_LOCALVERSION="" +CONFIG_LOCKDEP_SUPPORT=y +CONFIG_LOCK_KERNEL=y +# CONFIG_LOCK_STAT is not set +# CONFIG_LOGFS is not set +# CONFIG_LOGO is not set +# CONFIG_MAC80211_DEBUG_MENU is not set +CONFIG_MAC80211_HAS_RC=y +CONFIG_MAC80211_LEDS=y +# CONFIG_MAC80211_MESH is not set +CONFIG_MAC80211_RC_DEFAULT="minstrel" +CONFIG_MAC80211_RC_DEFAULT_MINSTREL=y +# CONFIG_MAC80211_RC_DEFAULT_PID is not set +CONFIG_MAC80211_RC_MINSTREL=y +# CONFIG_MACVLAN is not set +CONFIG_MAGIC_SYSRQ=y +# CONFIG_MEMSTICK is not set +# CONFIG_MFD_PCF50633 is not set +# CONFIG_MFD_SM501 is not set +# CONFIG_MFD_TMIO is not set +# CONFIG_MFD_WM831X is not set +# CONFIG_MFD_WM8350_I2C is not set +# CONFIG_MFD_WM8400 is not set +CONFIG_MII=y +# CONFIG_MINIX_FS is not set +CONFIG_MISC_DEVICES=y +CONFIG_MISC_FILESYSTEMS=y +CONFIG_MMC_BLOCK_BOUNCE=y +# CONFIG_MMC_BLOCK_DEFERRED_RESUME is not set +# CONFIG_MMC_DEBUG is not set +# CONFIG_MMC_EMBEDDED_SDIO is not set +# CONFIG_MMC_GENERIC_CSDIO is not set +# CONFIG_MMC_PERF_PROFILING is not set +# CONFIG_MMC_TEST is not set +CONFIG_MMU=y +CONFIG_MODULES=y +# CONFIG_MODULE_FORCE_LOAD is not set +CONFIG_MODULE_FORCE_UNLOAD=y +# CONFIG_MODULE_SRCVERSION_ALL is not set +CONFIG_MODULE_UNLOAD=y +# CONFIG_MODVERSIONS is not set +CONFIG_MSDOS_PARTITION=y +CONFIG_NAMESPACES=y +CONFIG_NET=y +CONFIG_NETDEVICES=y +# CONFIG_NET_9P is not set +CONFIG_NET_ACTIVITY_STATS=y +# CONFIG_NET_DSA is not set +CONFIG_NET_ETHERNET=y +# CONFIG_NET_IPGRE is not set +# CONFIG_NET_IPIP is not set +# CONFIG_NET_KEY is not set +# CONFIG_NET_PKTGEN is not set +CONFIG_NEW_LEDS=y +# CONFIG_NILFS2_FS is not set +# CONFIG_NL80211_TESTMODE is not set +CONFIG_NLATTR=y +# CONFIG_NTFS_FS is not set +# CONFIG_N_GSM is not set +# CONFIG_OCFS2_FS is not set +# CONFIG_OMFS_FS is not set +CONFIG_PANIC_TIMEOUT=0 +# CONFIG_PARPORT is not set +# CONFIG_PCCARD is not set +# CONFIG_PERF_COUNTERS is not set +# CONFIG_PHONE is not set +# CONFIG_PHONET is not set +CONFIG_PM=y +# CONFIG_PMIC_DA903X is not set +CONFIG_PM_OPS=y +# CONFIG_PM_RUNTIME is not set +CONFIG_PM_SLEEP=y +# CONFIG_POHMELFS is not set +# CONFIG_PPS is not set +# CONFIG_PREEMPT_NONE is not set +CONFIG_PREVENT_FIRMWARE_BUILD=y +CONFIG_PRINTK=y +CONFIG_PROC_FS=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_PROC_SYSCTL=y +# CONFIG_PROFILE_ALL_BRANCHES is not set +# CONFIG_PROFILE_ANNOTATED_BRANCHES is not set +# CONFIG_PROVE_LOCKING is not set +# CONFIG_QNX4FS_FS is not set +# CONFIG_QUOTA is not set +# CONFIG_R3964 is not set +# CONFIG_RAID_ATTRS is not set +# CONFIG_RAMOOPS is not set +# CONFIG_RAW_DRIVER is not set +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +CONFIG_RCU_FANOUT=32 +# CONFIG_RCU_FANOUT_EXACT is not set +# CONFIG_RCU_TORTURE_TEST is not set +# CONFIG_RCU_TRACE is not set +# CONFIG_RDS is not set +CONFIG_RD_GZIP=y +# CONFIG_REISERFS_FS is not set +CONFIG_RING_BUFFER=y +# CONFIG_RING_BUFFER_BENCHMARK is not set +CONFIG_RTC_LIB=y +CONFIG_RT_MUTEXES=y +# CONFIG_RT_MUTEX_TESTER is not set +CONFIG_RWSEM_XCHGADD_ALGORITHM=y +# CONFIG_SAMPLES is not set +# CONFIG_SCHED_TRACER is not set +CONFIG_SCSI_MOD=y +# CONFIG_SCSI_NETLINK is not set +# CONFIG_SDIO_UART is not set +CONFIG_SELECT_MEMORY_MODEL=y +# CONFIG_SENSORS_AK8975 is not set +# CONFIG_SENSORS_AKM8973 is not set +# CONFIG_SENSORS_AKM8976 is not set +# CONFIG_SENSORS_TSL2550 is not set +# CONFIG_SERIAL_ALTERA_JTAGUART is not set +# CONFIG_SERIAL_ALTERA_UART is not set +# CONFIG_SERIAL_TIMBERDALE is not set +CONFIG_SHMEM=y +CONFIG_SIGNALFD=y +CONFIG_SLABINFO=y +CONFIG_SLHC=y +# CONFIG_SLOB is not set +# CONFIG_SSB is not set +CONFIG_SSB_POSSIBLE=y +CONFIG_STACKTRACE_SUPPORT=y +# CONFIG_STACK_TRACER is not set +CONFIG_STAGING=y +# CONFIG_STAGING_EXCLUDE_BUILD is not set +CONFIG_STANDALONE=y +# CONFIG_STRIP_ASM_SYMS is not set +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +CONFIG_SUSPEND_NVS=y +CONFIG_SYSCTL=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_SYSFS=y +# CONFIG_SYSFS_DEPRECATED_V2 is not set +# CONFIG_SYSV_FS is not set +# CONFIG_SYS_HYPERVISOR is not set +CONFIG_TCP_CONG_CUBIC=y +CONFIG_TIMERFD=y +# CONFIG_TINY_RCU is not set +# CONFIG_TIPC is not set +CONFIG_TMPFS=y +CONFIG_TRACE_IRQFLAGS_SUPPORT=y +CONFIG_TRACING_SUPPORT=y +# CONFIG_TREE_PREEMPT_RCU is not set +CONFIG_TREE_RCU=y +# CONFIG_TREE_RCU_TRACE is not set +# CONFIG_TUN is not set +# CONFIG_TWL4030_CORE is not set +# CONFIG_UDF_FS is not set +# CONFIG_UFS_FS is not set +CONFIG_UID16=y +# CONFIG_UID_STAT is not set +# CONFIG_UIO is not set +CONFIG_UNIX=y +CONFIG_UNIX98_PTYS=y +# CONFIG_UNUSED_SYMBOLS is not set +CONFIG_USB_ARCH_HAS_HCD=y +# CONFIG_USB_FUNCTION is not set +# CONFIG_USB_GADGET is not set +CONFIG_USB_SUPPORT=y +# CONFIG_VETH is not set +# CONFIG_VGASTATE is not set +CONFIG_VIRT_TO_BUS=y +# CONFIG_VLAN_8021Q is not set +CONFIG_VM_EVENT_COUNTERS=y +# CONFIG_VP_A1026 is not set +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +# CONFIG_VXFS_FS is not set +# CONFIG_W1 is not set +# CONFIG_WAN is not set +# CONFIG_WAN_ROUTER is not set +# CONFIG_WATCHDOG is not set +CONFIG_WEXT_CORE=y +CONFIG_WEXT_PROC=y +# CONFIG_WIMAX is not set +CONFIG_WIRELESS=y +CONFIG_WIRELESS_EXT_SYSFS=y +# CONFIG_WL12XX is not set +CONFIG_WLAN=y +# CONFIG_WORKQUEUE_TRACER is not set +# CONFIG_X25 is not set +# CONFIG_XFS_FS is not set +CONFIG_ZLIB_INFLATE=y diff --git a/chromeos/config/i386/config.common.i386 b/chromeos/config/i386/config.common.i386 new file mode 100644 index 00000000000..f5d04131cb6 --- /dev/null +++ b/chromeos/config/i386/config.common.i386 @@ -0,0 +1,2195 @@ +# +# Config options generated by splitconfig +# +# CONFIG_4KSTACKS is not set +# CONFIG_64BIT is not set +CONFIG_8139CP=m +CONFIG_8139TOO=m +CONFIG_8139TOO_8129=y +CONFIG_8139TOO_PIO=y +CONFIG_8139TOO_TUNE_TWISTER=y +CONFIG_8139_OLD_RX_RESET=y +CONFIG_ACENIC=m +# CONFIG_ACENIC_OMIT_TIGON_I is not set +CONFIG_ACERHDF=y +CONFIG_ACER_WMI=m +# CONFIG_ACORN_PARTITION is not set +CONFIG_ACPI=y +CONFIG_ACPI_AC=y +# CONFIG_ACPI_APEI is not set +# CONFIG_ACPI_ASUS is not set +CONFIG_ACPI_BATTERY=y +CONFIG_ACPI_BLACKLIST_YEAR=0 +CONFIG_ACPI_BUTTON=y +# CONFIG_ACPI_CMPC is not set +CONFIG_ACPI_CONTAINER=y +# CONFIG_ACPI_CUSTOM_DSDT is not set +# CONFIG_ACPI_DEBUG is not set +CONFIG_ACPI_DOCK=y +CONFIG_ACPI_FAN=y +# CONFIG_ACPI_HED is not set +CONFIG_ACPI_HOTPLUG_CPU=y +# CONFIG_ACPI_PCI_SLOT is not set +# CONFIG_ACPI_POWER_METER is not set +CONFIG_ACPI_PROCESSOR=y +# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set +CONFIG_ACPI_PROCFS=y +CONFIG_ACPI_PROCFS_POWER=y +CONFIG_ACPI_PROC_EVENT=y +# CONFIG_ACPI_SBS is not set +CONFIG_ACPI_SLEEP=y +CONFIG_ACPI_SYSFS_POWER=y +CONFIG_ACPI_THERMAL=y +CONFIG_ACPI_TOSHIBA=m +CONFIG_ACPI_VIDEO=y +CONFIG_ACPI_WMI=m +CONFIG_ADAPTEC_STARFIRE=m +# CONFIG_ADIS16209 is not set +# CONFIG_ADIS16220 is not set +# CONFIG_ADIS16240 is not set +# CONFIG_ADIS16255 is not set +# CONFIG_ADIS16260 is not set +# CONFIG_ADIS16300 is not set +# CONFIG_ADIS16350 is not set +# CONFIG_ADIS16400 is not set +CONFIG_ADM8211=m +CONFIG_AGP=y +# CONFIG_AGP_ALI is not set +# CONFIG_AGP_AMD is not set +# CONFIG_AGP_AMD64 is not set +# CONFIG_AGP_ATI is not set +# CONFIG_AGP_EFFICEON is not set +CONFIG_AGP_INTEL=y +# CONFIG_AGP_NVIDIA is not set +# CONFIG_AGP_SIS is not set +# CONFIG_AGP_SWORKS is not set +# CONFIG_AGP_VIA is not set +CONFIG_AIRO=m +CONFIG_AMD8111_ETH=m +CONFIG_AMIGA_PARTITION=y +# CONFIG_ANDROID is not set +CONFIG_ANDROID_PARANOID_NETWORK=y +# CONFIG_APM is not set +# CONFIG_APPLICOM is not set +CONFIG_AR9170_LEDS=y +CONFIG_AR9170_USB=m +CONFIG_ARCH_CPU_PROBE_RELEASE=y +CONFIG_ARCH_DEFCONFIG="arch/x86/configs/i386_defconfig" +CONFIG_ARCH_FLATMEM_ENABLE=y +CONFIG_ARCH_HAS_CACHE_LINE_SIZE=y +CONFIG_ARCH_HAS_CPU_IDLE_WAIT=y +CONFIG_ARCH_HAS_CPU_RELAX=y +CONFIG_ARCH_HAS_DEFAULT_IDLE=y +CONFIG_ARCH_HIBERNATION_POSSIBLE=y +CONFIG_ARCH_HWEIGHT_CFLAGS="-fcall-saved-ecx -fcall-saved-edx" +CONFIG_ARCH_MAY_HAVE_PC_FDC=y +# CONFIG_ARCH_PHYS_ADDR_T_64BIT is not set +CONFIG_ARCH_POPULATES_NODE_MAP=y +CONFIG_ARCH_SELECT_MEMORY_MODEL=y +CONFIG_ARCH_SPARSEMEM_ENABLE=y +CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y +CONFIG_ARCH_SUPPORTS_MSI=y +CONFIG_ARCH_SUPPORTS_OPTIMIZED_INLINING=y +CONFIG_ARCH_USES_PG_UNCACHED=y +CONFIG_ARCH_WANT_FRAME_POINTERS=y +# CONFIG_ARCNET is not set +# CONFIG_ASHMEM is not set +CONFIG_ASK_IP_FIB_HASH=y +CONFIG_ASUS_LAPTOP=m +# CONFIG_ASUS_OLED is not set +CONFIG_AT76C50X_USB=m +CONFIG_ATA=y +# CONFIG_ATARI_PARTITION is not set +CONFIG_ATA_ACPI=y +CONFIG_ATA_BMDMA=y +CONFIG_ATA_GENERIC=y +# CONFIG_ATA_NONSTANDARD is not set +CONFIG_ATA_PIIX=y +CONFIG_ATA_SFF=y +CONFIG_ATA_VERBOSE_ERROR=y +CONFIG_ATH5K=m +# CONFIG_ATH5K_DEBUG is not set +# CONFIG_ATH6K_LEGACY is not set +CONFIG_ATH9K=m +CONFIG_ATH9K_COMMON=m +# CONFIG_ATH9K_DEBUGFS is not set +# CONFIG_ATH9K_HTC is not set +CONFIG_ATH9K_HW=m +CONFIG_ATH_COMMON=m +# CONFIG_ATH_DEBUG is not set +CONFIG_ATL1=m +CONFIG_ATL1C=m +CONFIG_ATL1E=m +CONFIG_ATL2=m +CONFIG_ATMEL=m +CONFIG_AUDIT=y +CONFIG_AUDITSYSCALL=y +# CONFIG_AUDIT_ARCH is not set +CONFIG_AUDIT_GENERIC=y +CONFIG_AUDIT_TREE=y +# CONFIG_BACKLIGHT_ADP8860 is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_BACKLIGHT_GENERIC=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_BACKLIGHT_MBP_NVIDIA is not set +# CONFIG_BACKLIGHT_PROGEAR is not set +# CONFIG_BACKLIGHT_SAHARA is not set +# CONFIG_BATTERY_BQ27x00 is not set +# CONFIG_BATTERY_DS2760 is not set +# CONFIG_BATTERY_DS2782 is not set +# CONFIG_BATTERY_MAX17040 is not set +CONFIG_BINARY_PRINTF=y +# CONFIG_BINFMT_AOUT is not set +CONFIG_BINFMT_MISC=y +# CONFIG_BLK_CGROUP is not set +# CONFIG_BLK_CPQ_CISS_DA is not set +# CONFIG_BLK_CPQ_DA is not set +CONFIG_BLK_DEV_BSG=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_DAC960 is not set +CONFIG_BLK_DEV_DM=y +# CONFIG_BLK_DEV_DRBD is not set +# CONFIG_BLK_DEV_FD is not set +# CONFIG_BLK_DEV_HD is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_MD is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_BLK_DEV_SD=y +# CONFIG_BLK_DEV_SR is not set +# CONFIG_BLK_DEV_SX8 is not set +# CONFIG_BLK_DEV_UB is not set +# CONFIG_BLK_DEV_UMEM is not set +CONFIG_BLK_DEV_XIP=y +CONFIG_BNX2=m +# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set +CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0 +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +CONFIG_BOUNCE=y +# CONFIG_BROADCOM_PHY is not set +CONFIG_BSD_DISKLABEL=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_BT=m +# CONFIG_BT_ATH3K is not set +CONFIG_BT_BNEP=m +# CONFIG_BT_BNEP_MC_FILTER is not set +# CONFIG_BT_BNEP_PROTO_FILTER is not set +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBTSDIO=m +CONFIG_BT_HCIBTUSB=m +# CONFIG_BT_HCIUART is not set +CONFIG_BT_HCIVHCI=m +CONFIG_BT_HIDP=m +CONFIG_BT_L2CAP=m +# CONFIG_BT_L2CAP_EXT_FEATURES is not set +# CONFIG_BT_MRVL is not set +CONFIG_BT_RFCOMM=m +# CONFIG_BT_RFCOMM_TTY is not set +CONFIG_BT_SCO=m +CONFIG_CAN_PM_TRACE=y +CONFIG_CASSINI=m +CONFIG_CB710_CORE=m +CONFIG_CB710_DEBUG=y +CONFIG_CB710_DEBUG_ASSUMPTIONS=y +# CONFIG_CC_STACKPROTECTOR is not set +CONFIG_CFG80211=m +CONFIG_CFG80211_DEBUGFS=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_CPUACCT=y +# CONFIG_CGROUP_DEBUG is not set +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_MEM_RES_CTLR=y +CONFIG_CGROUP_NS=y +CONFIG_CGROUP_SCHED=y +CONFIG_CHECK_SIGNATURE=y +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_CHR_DEV_SCH is not set +CONFIG_CHR_DEV_SG=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CICADA_PHY is not set +CONFIG_CLOCKSOURCE_WATCHDOG=y +# CONFIG_CMDLINE_BOOL is not set +# CONFIG_CNIC is not set +CONFIG_COMPAL_LAPTOP=m +# CONFIG_COMPAT_BRK is not set +# CONFIG_COMPAT_VDSO is not set +# CONFIG_COMPUTONE is not set +CONFIG_CONNECTOR=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y +# CONFIG_CPA_DEBUG is not set +CONFIG_CPUSETS=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_DEBUG=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +CONFIG_CPU_FREQ_TABLE=y +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_CPU_IDLE_GOV_MENU=y +# CONFIG_CPU_NOTIFIER_ERROR_INJECT is not set +CONFIG_CPU_SUP_AMD=y +CONFIG_CPU_SUP_CENTAUR=y +CONFIG_CPU_SUP_CYRIX_32=y +CONFIG_CPU_SUP_INTEL=y +CONFIG_CPU_SUP_TRANSMETA_32=y +CONFIG_CPU_SUP_UMC_32=y +CONFIG_CRAMFS=y +CONFIG_CRC16=y +CONFIG_CRC7=m +CONFIG_CRC_ITU_T=m +CONFIG_CRC_T10DIF=m +CONFIG_CRYPTO=y +CONFIG_CRYPTO_AEAD=y +CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_AES=y +# CONFIG_CRYPTO_AES_586 is not set +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_ARC4=y +CONFIG_CRYPTO_AUTHENC=y +CONFIG_CRYPTO_BLKCIPHER=y +CONFIG_CRYPTO_BLKCIPHER2=y +CONFIG_CRYPTO_CBC=y +CONFIG_CRYPTO_CRC32C=m +# CONFIG_CRYPTO_CRC32C_INTEL is not set +CONFIG_CRYPTO_DES=y +# CONFIG_CRYPTO_DEV_GEODE is not set +# CONFIG_CRYPTO_DEV_HIFN_795X is not set +CONFIG_CRYPTO_DEV_PADLOCK=y +CONFIG_CRYPTO_DEV_PADLOCK_AES=m +CONFIG_CRYPTO_DEV_PADLOCK_SHA=m +CONFIG_CRYPTO_ECB=y +CONFIG_CRYPTO_HASH=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_MANAGER=y +CONFIG_CRYPTO_MANAGER2=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_MICHAEL_MIC=m +CONFIG_CRYPTO_PCOMP=y +# CONFIG_CRYPTO_PCRYPT is not set +CONFIG_CRYPTO_RNG2=y +# CONFIG_CRYPTO_SALSA20_586 is not set +CONFIG_CRYPTO_SHA1=y +CONFIG_CRYPTO_SHA256=m +# CONFIG_CRYPTO_TWOFISH_586 is not set +CONFIG_CRYPTO_WORKQUEUE=y +# CONFIG_CRYSTALHD is not set +# CONFIG_CS5535_GPIO is not set +# CONFIG_CS5535_MFGPT is not set +# CONFIG_CUSE is not set +# CONFIG_CYCLADES is not set +# CONFIG_DAB is not set +# CONFIG_DAVICOM_PHY is not set +# CONFIG_DCDBAS is not set +# CONFIG_DE2104X is not set +# CONFIG_DE4X5 is not set +CONFIG_DEBUG_BOOT_PARAMS=y +# CONFIG_DEBUG_BUGVERBOSE is not set +CONFIG_DEBUG_DEVRES=y +CONFIG_DEBUG_GPIO=y +# CONFIG_DEBUG_INFO is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +# CONFIG_DEBUG_MUTEXES is not set +CONFIG_DEBUG_NX_TEST=m +# CONFIG_DEBUG_PAGEALLOC is not set +# CONFIG_DEBUG_PERF_USE_VMALLOC is not set +# CONFIG_DEBUG_PER_CPU_MAPS is not set +CONFIG_DEBUG_RODATA=y +# CONFIG_DEBUG_RODATA_TEST is not set +# CONFIG_DEBUG_SPINLOCK_SLEEP is not set +CONFIG_DEBUG_STACKOVERFLOW=y +CONFIG_DEBUG_STACK_USAGE=y +# CONFIG_DEBUG_STRICT_USER_COPY_CHECKS is not set +# CONFIG_DEBUG_VIRTUAL is not set +# CONFIG_DEFAULT_BIC is not set +CONFIG_DEFAULT_CFQ=y +CONFIG_DEFAULT_CUBIC=y +# CONFIG_DEFAULT_HTCP is not set +# CONFIG_DEFAULT_HYBLA is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_DEFAULT_IO_DELAY_TYPE=0 +# CONFIG_DEFAULT_NOOP is not set +# CONFIG_DEFAULT_RENO is not set +CONFIG_DEFAULT_SECURITY="tomoyo" +# CONFIG_DEFAULT_SECURITY_DAC is not set +CONFIG_DEFAULT_SECURITY_TOMOYO=y +# CONFIG_DEFAULT_VEGAS is not set +# CONFIG_DEFAULT_VENO is not set +# CONFIG_DEFAULT_WESTWOOD is not set +# CONFIG_DEFXX is not set +# CONFIG_DELL_RBU is not set +CONFIG_DELL_WMI=m +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DETECT_SOFTLOCKUP=y +CONFIG_DEVPORT=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_DIGIEPCA is not set +CONFIG_DL2K=m +# CONFIG_DM9102 is not set +# CONFIG_DMAR is not set +# CONFIG_DMA_API_DEBUG is not set +CONFIG_DMI=y +CONFIG_DMIID=y +CONFIG_DM_CRYPT=y +# CONFIG_DM_DEBUG is not set +# CONFIG_DM_DELAY is not set +# CONFIG_DM_MIRROR is not set +# CONFIG_DM_MULTIPATH is not set +# CONFIG_DM_SNAPSHOT is not set +# CONFIG_DM_UEVENT is not set +# CONFIG_DM_ZERO is not set +CONFIG_DNET=m +# CONFIG_DNOTIFY is not set +CONFIG_DOUBLEFAULT=y +CONFIG_DRM=y +# CONFIG_DRM_I2C_CH7006 is not set +# CONFIG_DRM_I810 is not set +# CONFIG_DRM_I830 is not set +CONFIG_DRM_I915=y +CONFIG_DRM_I915_KMS=y +CONFIG_DRM_KMS_HELPER=y +# CONFIG_DRM_MGA is not set +# CONFIG_DRM_NOUVEAU is not set +# CONFIG_DRM_R128 is not set +# CONFIG_DRM_RADEON is not set +# CONFIG_DRM_SAVAGE is not set +# CONFIG_DRM_SIS is not set +# CONFIG_DRM_TDFX is not set +# CONFIG_DRM_VIA is not set +# CONFIG_DRM_VMWGFX is not set +CONFIG_DS1682=m +# CONFIG_DT3155 is not set +# CONFIG_DUMMY is not set +# CONFIG_DVB_CORE is not set +CONFIG_DYNAMIC_FTRACE=y +CONFIG_E100=m +CONFIG_E1000=m +CONFIG_E1000E=m +CONFIG_EARLY_PRINTK=y +CONFIG_EARLY_PRINTK_DBGP=y +CONFIG_ECRYPT_FS=y +# CONFIG_EDAC is not set +# CONFIG_EDD is not set +CONFIG_EEEPC_LAPTOP=m +# CONFIG_EEEPC_WMI is not set +CONFIG_EEPROM_93CX6=m +CONFIG_EEPROM_AT24=m +CONFIG_EEPROM_AT25=m +CONFIG_EEPROM_LEGACY=m +CONFIG_EFI=y +CONFIG_EFI_PARTITION=y +# CONFIG_EFI_VARS is not set +CONFIG_EMBEDDED=y +# CONFIG_ENABLE_WARN_DEPRECATED is not set +# CONFIG_ENC28J60 is not set +CONFIG_EPIC100=m +# CONFIG_ET131X is not set +CONFIG_ETHOC=m +CONFIG_EVENT_TRACING=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_POSIX_ACL=y +CONFIG_EXT2_FS_SECURITY=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT2_FS_XIP=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT3_FS=y +CONFIG_EXT3_FS_POSIX_ACL=y +CONFIG_EXT3_FS_SECURITY=y +CONFIG_EXT3_FS_XATTR=y +# CONFIG_EXT4_DEBUG is not set +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_EXT4_FS_XATTR=y +# CONFIG_EZX_PCAP is not set +CONFIG_FAIR_GROUP_SCHED=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +CONFIG_FAT_FS=m +# CONFIG_FB_3DFX is not set +# CONFIG_FB_ARC is not set +# CONFIG_FB_ARK is not set +# CONFIG_FB_ASILIANT is not set +# CONFIG_FB_ATY is not set +# CONFIG_FB_ATY128 is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_CARMINE is not set +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CIRRUS is not set +# CONFIG_FB_CYBER2000 is not set +# CONFIG_FB_EFI is not set +# CONFIG_FB_GEODE is not set +# CONFIG_FB_HGA is not set +# CONFIG_FB_I810 is not set +# CONFIG_FB_IMSTT is not set +# CONFIG_FB_KYRO is not set +# CONFIG_FB_LE80578 is not set +# CONFIG_FB_MATROX is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_N411 is not set +# CONFIG_FB_NEOMAGIC is not set +# CONFIG_FB_NVIDIA is not set +# CONFIG_FB_PM2 is not set +# CONFIG_FB_PM3 is not set +# CONFIG_FB_RADEON is not set +# CONFIG_FB_RIVA is not set +# CONFIG_FB_S3 is not set +# CONFIG_FB_SAVAGE is not set +# CONFIG_FB_SIS is not set +# CONFIG_FB_TILEBLITTING is not set +# CONFIG_FB_TMIO is not set +# CONFIG_FB_TRIDENT is not set +# CONFIG_FB_UDL is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_VESA is not set +# CONFIG_FB_VGA16 is not set +# CONFIG_FB_VIA is not set +# CONFIG_FB_VOODOO1 is not set +# CONFIG_FB_VT8623 is not set +# CONFIG_FB_XGI is not set +CONFIG_FDDI=y +CONFIG_FEALNX=m +CONFIG_FIB_RULES=y +# CONFIG_FIREWIRE is not set +CONFIG_FIRMWARE_MEMMAP=y +# CONFIG_FIXED_PHY is not set +CONFIG_FIX_EARLYCON_MEM=y +# CONFIG_FLATMEM_MANUAL is not set +CONFIG_FORCEDETH=m +CONFIG_FRAME_POINTER=y +CONFIG_FRAME_WARN=2048 +CONFIG_FS_MBCACHE=y +CONFIG_FS_POSIX_ACL=y +CONFIG_FS_XIP=y +CONFIG_FTRACE_MCOUNT_RECORD=y +CONFIG_FTRACE_NMI_ENTER=y +# CONFIG_FTRACE_STARTUP_TEST is not set +# CONFIG_FTRACE_SYSCALLS is not set +CONFIG_FUJITSU_LAPTOP=m +# CONFIG_FUJITSU_LAPTOP_DEBUG is not set +# CONFIG_FUNCTION_PROFILER is not set +CONFIG_FUNCTION_TRACER=y +CONFIG_FUSE_FS=m +# CONFIG_FUSION is not set +CONFIG_GENERIC_ACL=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y +CONFIG_GENERIC_CMOS_UPDATE=y +# CONFIG_GENERIC_CPU is not set +CONFIG_GENERIC_FIND_FIRST_BIT=y +CONFIG_GENERIC_FIND_NEXT_BIT=y +CONFIG_GENERIC_IOMAP=y +CONFIG_GENERIC_ISA_DMA=y +CONFIG_GENERIC_PENDING_IRQ=y +# CONFIG_GENERIC_TIME_VSYSCALL is not set +CONFIG_GENERIC_TRACER=y +# CONFIG_GOBI_USBNET is not set +CONFIG_GPIOLIB=y +# CONFIG_GPIO_ADP5588 is not set +CONFIG_GPIO_BT8XX=m +# CONFIG_GPIO_CS5535 is not set +# CONFIG_GPIO_IT8761E is not set +# CONFIG_GPIO_LANGWELL is not set +# CONFIG_GPIO_MAX7300 is not set +CONFIG_GPIO_MAX7301=m +CONFIG_GPIO_MAX730X=m +CONFIG_GPIO_MAX732X=m +# CONFIG_GPIO_MC33880 is not set +CONFIG_GPIO_MCP23S08=m +CONFIG_GPIO_PCA953X=m +CONFIG_GPIO_PCF857X=m +# CONFIG_GPIO_RDC321X is not set +# CONFIG_GPIO_SCH is not set +# CONFIG_GPIO_SX150X is not set +CONFIG_GPIO_SYSFS=y +CONFIG_HAMACHI=m +# CONFIG_HANGCHECK_TIMER is not set +CONFIG_HAPPYMEAL=m +CONFIG_HAVE_AOUT=y +CONFIG_HAVE_ARCH_KMEMCHECK=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_ATOMIC_IOMAP=y +# CONFIG_HAVE_CPUMASK_OF_CPU_MAP is not set +CONFIG_HAVE_DMA_API_DEBUG=y +CONFIG_HAVE_DMA_ATTRS=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_EARLY_RES=y +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y +CONFIG_HAVE_FTRACE_NMI_ENTER=y +CONFIG_HAVE_FUNCTION_GRAPH_FP_TEST=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_HAVE_HW_BREAKPOINT=y +CONFIG_HAVE_IOREMAP_PROT=y +CONFIG_HAVE_KERNEL_BZIP2=y +CONFIG_HAVE_KVM=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_HAVE_MEMORY_PRESENT=y +CONFIG_HAVE_MIXED_BREAKPOINTS_REGS=y +CONFIG_HAVE_MMIOTRACE_SUPPORT=y +CONFIG_HAVE_OPTPROBES=y +CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y +CONFIG_HAVE_SETUP_PER_CPU_AREA=y +CONFIG_HAVE_SYSCALL_TRACEPOINTS=y +CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y +CONFIG_HAVE_USER_RETURN_NOTIFIER=y +CONFIG_HERMES=m +CONFIG_HERMES_CACHE_FW_ON_INIT=y +# CONFIG_HERMES_PRISM is not set +CONFIG_HIDRAW=y +# CONFIG_HID_3M_PCT is not set +# CONFIG_HID_A4TECH is not set +CONFIG_HID_APPLE=m +# CONFIG_HID_BELKIN is not set +# CONFIG_HID_CANDO is not set +# CONFIG_HID_CHERRY is not set +# CONFIG_HID_CHICONY is not set +# CONFIG_HID_CYPRESS is not set +# CONFIG_HID_DRAGONRISE is not set +# CONFIG_HID_EGALAX is not set +# CONFIG_HID_EZKEY is not set +# CONFIG_HID_GREENASIA is not set +# CONFIG_HID_GYRATION is not set +# CONFIG_HID_KENSINGTON is not set +# CONFIG_HID_KYE is not set +CONFIG_HID_LOGITECH=m +# CONFIG_HID_MAGICMOUSE is not set +CONFIG_HID_MICROSOFT=m +# CONFIG_HID_MONTEREY is not set +# CONFIG_HID_MOSART is not set +# CONFIG_HID_NTRIG is not set +# CONFIG_HID_ORTEK is not set +# CONFIG_HID_PANTHERLORD is not set +# CONFIG_HID_PETALYNX is not set +# CONFIG_HID_PICOLCD is not set +CONFIG_HID_PID=y +# CONFIG_HID_PRODIKEYS is not set +# CONFIG_HID_QUANTA is not set +# CONFIG_HID_ROCCAT is not set +# CONFIG_HID_ROCCAT_KONE is not set +# CONFIG_HID_SAMSUNG is not set +# CONFIG_HID_SMARTJOYPLUS is not set +# CONFIG_HID_SONY is not set +# CONFIG_HID_STANTUM is not set +# CONFIG_HID_SUNPLUS is not set +# CONFIG_HID_THRUSTMASTER is not set +# CONFIG_HID_TOPSEED is not set +# CONFIG_HID_TWINHAN is not set +# CONFIG_HID_WACOM is not set +# CONFIG_HID_ZEROPLUS is not set +# CONFIG_HID_ZYDACRON is not set +# CONFIG_HIGHMEM4G is not set +# CONFIG_HIGHMEM64G is not set +CONFIG_HIGH_RES_TIMERS=y +# CONFIG_HIPPI is not set +CONFIG_HOSTAP=m +CONFIG_HOSTAP_FIRMWARE=y +CONFIG_HOSTAP_FIRMWARE_NVRAM=y +CONFIG_HOSTAP_PCI=m +CONFIG_HOSTAP_PLX=m +CONFIG_HOTPLUG_CPU=y +CONFIG_HOTPLUG_PCI=y +# CONFIG_HOTPLUG_PCI_ACPI is not set +# CONFIG_HOTPLUG_PCI_COMPAQ is not set +# CONFIG_HOTPLUG_PCI_CPCI is not set +# CONFIG_HOTPLUG_PCI_FAKE is not set +# CONFIG_HOTPLUG_PCI_IBM is not set +CONFIG_HOTPLUG_PCI_PCIE=y +# CONFIG_HOTPLUG_PCI_SHPC is not set +CONFIG_HP100=m +CONFIG_HPET=y +CONFIG_HPET_EMULATE_RTC=y +# CONFIG_HPET_MMAP is not set +CONFIG_HPET_TIMER=y +# CONFIG_HP_ILO is not set +CONFIG_HP_WMI=m +CONFIG_HT_IRQ=y +CONFIG_HUGETLBFS=y +CONFIG_HUGETLB_PAGE=y +CONFIG_HWMON=y +# CONFIG_HWMON_DEBUG_CHIP is not set +CONFIG_HWMON_VID=m +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_AMD=y +CONFIG_HW_RANDOM_GEODE=y +CONFIG_HW_RANDOM_INTEL=y +# CONFIG_HW_RANDOM_TIMERIOMEM is not set +CONFIG_HW_RANDOM_VIA=y +# CONFIG_HYPERV is not set +CONFIG_HZ=1000 +# CONFIG_HZ_100 is not set +CONFIG_HZ_1000=y +# CONFIG_HZ_250 is not set +# CONFIG_HZ_300 is not set +CONFIG_I2C_ALGOBIT=y +CONFIG_I2C_ALGOPCA=m +CONFIG_I2C_ALI1535=m +CONFIG_I2C_ALI1563=m +CONFIG_I2C_ALI15X3=m +CONFIG_I2C_AMD756=m +CONFIG_I2C_AMD756_S4882=m +CONFIG_I2C_AMD8111=m +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_I801=m +CONFIG_I2C_ISCH=m +CONFIG_I2C_NFORCE2=m +CONFIG_I2C_NFORCE2_S4985=m +CONFIG_I2C_OCORES=m +CONFIG_I2C_PCA_PLATFORM=m +CONFIG_I2C_PIIX4=m +# CONFIG_I2C_SCMI is not set +CONFIG_I2C_SIMTEC=m +CONFIG_I2C_SIS5595=m +CONFIG_I2C_SIS630=m +CONFIG_I2C_SIS96X=m +CONFIG_I2C_STUB=m +# CONFIG_I2C_TINY_USB is not set +CONFIG_I2C_VIA=m +CONFIG_I2C_VIAPRO=m +# CONFIG_I2O is not set +# CONFIG_I8K is not set +# CONFIG_IBM_ASM is not set +# CONFIG_ICPLUS_PHY is not set +# CONFIG_IDE_PHISON is not set +# CONFIG_IEEE1394 is not set +# CONFIG_IFB is not set +CONFIG_IGB=m +CONFIG_IGBVF=m +CONFIG_IIO=m +# CONFIG_IIO_RING_BUFFER is not set +# CONFIG_IIO_TRIGGER is not set +CONFIG_IKCONFIG=m +CONFIG_ILLEGAL_POINTER_VALUE=0 +# CONFIG_IMA is not set +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +# CONFIG_INET6_IPCOMP is not set +# CONFIG_INET6_TUNNEL is not set +CONFIG_INET6_XFRM_MODE_BEET=y +# CONFIG_INET6_XFRM_MODE_ROUTEOPTIMIZATION is not set +CONFIG_INET6_XFRM_MODE_TRANSPORT=y +CONFIG_INET6_XFRM_MODE_TUNNEL=y +# CONFIG_INET6_XFRM_TUNNEL is not set +CONFIG_INET_LRO=y +CONFIG_INET_TUNNEL=y +# CONFIG_INFINIBAND is not set +CONFIG_INLINE_READ_UNLOCK=y +CONFIG_INLINE_READ_UNLOCK_IRQ=y +CONFIG_INLINE_SPIN_UNLOCK=y +CONFIG_INLINE_SPIN_UNLOCK_IRQ=y +CONFIG_INLINE_WRITE_UNLOCK=y +CONFIG_INLINE_WRITE_UNLOCK_IRQ=y +CONFIG_INPUT_APANEL=m +CONFIG_INPUT_ATI_REMOTE=m +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_ATLAS_BTNS=m +CONFIG_INPUT_CM109=m +# CONFIG_INPUT_EVBUG is not set +CONFIG_INPUT_FF_MEMLESS=y +CONFIG_INPUT_GPIO_ROTARY_ENCODER=m +CONFIG_INPUT_KEYSPAN_REMOTE=m +CONFIG_INPUT_MOUSE=y +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_PCSPKR=m +CONFIG_INPUT_POLLDEV=y +CONFIG_INPUT_POWERMATE=m +CONFIG_INPUT_SPARSEKMAP=m +CONFIG_INPUT_TABLET=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_INPUT_UINPUT=m +# CONFIG_INPUT_WINBOND_CIR is not set +CONFIG_INPUT_WISTRON_BTNS=m +CONFIG_INPUT_YEALINK=m +CONFIG_INSTRUCTION_DECODER=y +# CONFIG_INTEL_IDLE is not set +CONFIG_INTEL_MENLOW=m +# CONFIG_IOMMU_API is not set +# CONFIG_IOMMU_HELPER is not set +# CONFIG_IOMMU_STRESS is not set +CONFIG_IOSCHED_CFQ=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IO_DELAY_0X80=y +# CONFIG_IO_DELAY_0XED is not set +# CONFIG_IO_DELAY_NONE is not set +CONFIG_IO_DELAY_TYPE_0X80=0 +CONFIG_IO_DELAY_TYPE_0XED=1 +CONFIG_IO_DELAY_TYPE_NONE=3 +CONFIG_IO_DELAY_TYPE_UDELAY=2 +# CONFIG_IO_DELAY_UDELAY is not set +CONFIG_IP1000=m +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_TARGET_LOG=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IPC_NS=y +CONFIG_IPV6=y +# CONFIG_IPV6_MIP6 is not set +# CONFIG_IPV6_MROUTE is not set +# CONFIG_IPV6_MULTIPLE_TABLES is not set +CONFIG_IPV6_NDISC_NODETYPE=y +# CONFIG_IPV6_OPTIMISTIC_DAD is not set +# CONFIG_IPV6_PRIVACY is not set +# CONFIG_IPV6_ROUTER_PREF is not set +CONFIG_IPV6_SIT=y +# CONFIG_IPV6_SIT_6RD is not set +# CONFIG_IPV6_TUNNEL is not set +CONFIG_IPW2100=m +# CONFIG_IPW2100_DEBUG is not set +CONFIG_IPW2100_MONITOR=y +CONFIG_IPW2200=m +# CONFIG_IPW2200_DEBUG is not set +CONFIG_IPW2200_MONITOR=y +CONFIG_IPW2200_PROMISCUOUS=y +CONFIG_IPW2200_QOS=y +CONFIG_IPW2200_RADIOTAP=y +CONFIG_IP_ADVANCED_ROUTER=y +# CONFIG_IP_FIB_TRIE is not set +CONFIG_IP_MROUTE=y +# CONFIG_IP_MROUTE_MULTIPLE_TABLES is not set +CONFIG_IP_MULTICAST=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MANGLE=y +CONFIG_IP_NF_TARGET_LOG=y +CONFIG_IP_NF_TARGET_MASQUERADE=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_IP_NF_TARGET_ULOG=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_RARP=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +# CONFIG_IP_VS is not set +CONFIG_IR_CORE=m +# CONFIG_IR_IMON is not set +CONFIG_IR_JVC_DECODER=m +CONFIG_IR_NEC_DECODER=m +CONFIG_IR_RC5_DECODER=m +CONFIG_IR_RC6_DECODER=m +CONFIG_IR_SONY_DECODER=m +# CONFIG_ISA is not set +CONFIG_ISA_DMA_API=y +# CONFIG_ISCSI_IBFT_FIND is not set +# CONFIG_ISI is not set +CONFIG_ISO9660_FS=y +CONFIG_IWL3945=m +CONFIG_IWL4965=y +CONFIG_IWL5000=y +CONFIG_IWLAGN=m +CONFIG_IWLWIFI=m +# CONFIG_IWLWIFI_DEBUG is not set +# CONFIG_IWLWIFI_DEVICE_TRACING is not set +CONFIG_JBD=y +CONFIG_JBD2=y +# CONFIG_JBD2_DEBUG is not set +# CONFIG_JBD_DEBUG is not set +CONFIG_JME=m +CONFIG_JOLIET=y +CONFIG_K8_NB=y +CONFIG_KALLSYMS_ALL=y +CONFIG_KALLSYMS_EXTRA_PASS=y +CONFIG_KARMA_PARTITION=y +CONFIG_KEYBOARD_ATKBD=y +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_LKKBD=m +CONFIG_KEYBOARD_NEWTON=m +# CONFIG_KEYBOARD_QCIKBD is not set +CONFIG_KEYBOARD_STOWAWAY=m +CONFIG_KEYBOARD_SUNKBD=m +CONFIG_KEYBOARD_XTKBD=m +CONFIG_KEYS=y +CONFIG_KEYS_DEBUG_PROC_KEYS=y +CONFIG_KPROBES=y +# CONFIG_KPROBES_SANITY_TEST is not set +CONFIG_KPROBE_EVENT=y +CONFIG_KRETPROBES=y +# CONFIG_KS8851 is not set +# CONFIG_KSYM_TRACER is not set +# CONFIG_KSZ884X_PCI is not set +CONFIG_KTIME_SCALAR=y +# CONFIG_KXSD9 is not set +# CONFIG_LATENCYTOP is not set +CONFIG_LBDAF=y +CONFIG_LCD_CLASS_DEVICE=m +# CONFIG_LCD_L4F00242T03 is not set +# CONFIG_LCD_LMS283GF05 is not set +# CONFIG_LCD_LTV350QV is not set +# CONFIG_LCD_PLATFORM is not set +# CONFIG_LCD_S6E63M0 is not set +# CONFIG_LCD_TDO24M is not set +# CONFIG_LCD_VGG2432A4 is not set +# CONFIG_LDM_PARTITION is not set +# CONFIG_LEDS_ALIX2 is not set +# CONFIG_LEDS_CLEVO_MAIL is not set +# CONFIG_LEDS_DAC124S085 is not set +# CONFIG_LEDS_DELL_NETBOOKS is not set +# CONFIG_LEDS_GPIO is not set +# CONFIG_LEDS_INTEL_SS4200 is not set +# CONFIG_LEDS_TRIGGER_GPIO is not set +CONFIG_LIB80211=m +CONFIG_LIB80211_CRYPT_CCMP=m +CONFIG_LIB80211_CRYPT_TKIP=m +CONFIG_LIB80211_CRYPT_WEP=m +# CONFIG_LIB80211_DEBUG is not set +CONFIG_LIBCRC32C=m +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_DEBUG=y +# CONFIG_LIBERTAS_MESH is not set +# CONFIG_LIBERTAS_SDIO is not set +# CONFIG_LIBERTAS_SPI is not set +CONFIG_LIBERTAS_THINFIRM=m +# CONFIG_LIBERTAS_THINFIRM_DEBUG is not set +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_LIBERTAS_USB=m +CONFIG_LIBIPW=m +# CONFIG_LIBIPW_DEBUG is not set +# CONFIG_LINE6_USB is not set +# CONFIG_LIS3L02DQ is not set +# CONFIG_LOCALVERSION_AUTO is not set +# CONFIG_LOGIG940_FF is not set +# CONFIG_LOGIRUMBLEPAD2_FF is not set +# CONFIG_LOGITECH_FF is not set +CONFIG_LOG_BUF_SHIFT=18 +# CONFIG_LSI_ET1011C_PHY is not set +# CONFIG_LXT_PHY is not set +# CONFIG_M386 is not set +# CONFIG_M486 is not set +# CONFIG_M586 is not set +# CONFIG_M586MMX is not set +# CONFIG_M586TSC is not set +CONFIG_M686=y +CONFIG_MAC80211=m +CONFIG_MAC80211_DEBUGFS=y +CONFIG_MAC80211_HWSIM=m +# CONFIG_MAC80211_RC_PID is not set +# CONFIG_MACINTOSH_DRIVERS is not set +CONFIG_MAC_PARTITION=y +# CONFIG_MARVELL_PHY is not set +# CONFIG_MATH_EMULATION is not set +# CONFIG_MATOM is not set +# CONFIG_MAX1363 is not set +# CONFIG_MCA is not set +# CONFIG_MCORE2 is not set +# CONFIG_MCRUSOE is not set +# CONFIG_MCYRIXIII is not set +CONFIG_MD=y +# CONFIG_MDIO_BITBANG is not set +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_TUNER=m +CONFIG_MEDIA_TUNER_CUSTOMISE=y +CONFIG_MEDIA_TUNER_MAX2165=m +# CONFIG_MEDIA_TUNER_MC44S803 is not set +# CONFIG_MEDIA_TUNER_MT2060 is not set +# CONFIG_MEDIA_TUNER_MT20XX is not set +# CONFIG_MEDIA_TUNER_MT2131 is not set +# CONFIG_MEDIA_TUNER_MT2266 is not set +# CONFIG_MEDIA_TUNER_MXL5005S is not set +# CONFIG_MEDIA_TUNER_MXL5007T is not set +# CONFIG_MEDIA_TUNER_QT1010 is not set +# CONFIG_MEDIA_TUNER_SIMPLE is not set +# CONFIG_MEDIA_TUNER_TDA18271 is not set +# CONFIG_MEDIA_TUNER_TDA827X is not set +# CONFIG_MEDIA_TUNER_TDA8290 is not set +# CONFIG_MEDIA_TUNER_TDA9887 is not set +# CONFIG_MEDIA_TUNER_TEA5761 is not set +# CONFIG_MEDIA_TUNER_TEA5767 is not set +# CONFIG_MEDIA_TUNER_XC2028 is not set +# CONFIG_MEDIA_TUNER_XC5000 is not set +# CONFIG_MEFFICEON is not set +# CONFIG_MEMTEST is not set +CONFIG_MFD_CORE=m +# CONFIG_MFD_MC13783 is not set +# CONFIG_MGEODEGX1 is not set +# CONFIG_MGEODE_LX is not set +# CONFIG_MICREL_PHY is not set +CONFIG_MICROCODE=y +CONFIG_MICROCODE_AMD=y +CONFIG_MICROCODE_INTEL=y +CONFIG_MICROCODE_OLD_INTERFACE=y +CONFIG_MINIX_SUBPARTITION=y +# CONFIG_MK6 is not set +# CONFIG_MK7 is not set +# CONFIG_MK8 is not set +CONFIG_MMC=m +CONFIG_MMC_BLOCK=m +CONFIG_MMC_CB710=m +# CONFIG_MMC_PARANOID_SD_INIT is not set +# CONFIG_MMC_RICOH_MMC is not set +CONFIG_MMC_SDHCI=m +CONFIG_MMC_SDHCI_PCI=m +# CONFIG_MMC_SDHCI_PLTFM is not set +# CONFIG_MMC_SPI is not set +# CONFIG_MMC_TIFM_SD is not set +# CONFIG_MMC_UNSAFE_RESUME is not set +# CONFIG_MMC_VIA_SDMMC is not set +# CONFIG_MMC_WBSD is not set +# CONFIG_MMIOTRACE is not set +CONFIG_MM_OWNER=y +CONFIG_MOUSE_APPLETOUCH=m +CONFIG_MOUSE_BCM5974=m +CONFIG_MOUSE_GPIO=m +CONFIG_MOUSE_PS2=y +CONFIG_MOUSE_PS2_ALPS=y +# CONFIG_MOUSE_PS2_ELANTECH is not set +CONFIG_MOUSE_PS2_LIFEBOOK=y +CONFIG_MOUSE_PS2_LOGIPS2PP=y +# CONFIG_MOUSE_PS2_SENTELIC is not set +CONFIG_MOUSE_PS2_SYNAPTICS=y +# CONFIG_MOUSE_PS2_TOUCHKIT is not set +CONFIG_MOUSE_PS2_TRACKPOINT=y +# CONFIG_MOUSE_QCITP is not set +CONFIG_MOUSE_SERIAL=m +# CONFIG_MOUSE_SYNAPTICS_I2C is not set +CONFIG_MOUSE_VSXXXAA=m +# CONFIG_MOXA_INTELLIO is not set +# CONFIG_MOXA_SMARTIO is not set +# CONFIG_MPENTIUM4 is not set +# CONFIG_MPENTIUMII is not set +# CONFIG_MPENTIUMIII is not set +# CONFIG_MPENTIUMM is not set +# CONFIG_MPSC is not set +CONFIG_MSDOS_FS=m +CONFIG_MSI_LAPTOP=m +# CONFIG_MSI_WMI is not set +# CONFIG_MTD is not set +CONFIG_MTRR=y +# CONFIG_MTRR_SANITIZER is not set +CONFIG_MUTEX_SPIN_ON_OWNER=y +# CONFIG_MVIAC3_2 is not set +# CONFIG_MVIAC7 is not set +# CONFIG_MWAVE is not set +# CONFIG_MWINCHIP3D is not set +# CONFIG_MWINCHIPC6 is not set +CONFIG_MWL8K=m +# CONFIG_NATIONAL_PHY is not set +CONFIG_NATSEMI=m +CONFIG_NE2K_PCI=m +# CONFIG_NEED_DMA_MAP_STATE is not set +CONFIG_NEED_NODE_MEMMAP_SIZE=y +CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y +CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y +CONFIG_NEED_SG_DMA_LENGTH=y +CONFIG_NETCONSOLE=y +# CONFIG_NETCONSOLE_DYNAMIC is not set +CONFIG_NETDEV_1000=y +# CONFIG_NETDEV_10000 is not set +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_ADVANCED is not set +# CONFIG_NETFILTER_DEBUG is not set +CONFIG_NETFILTER_NETLINK=y +CONFIG_NETFILTER_NETLINK_LOG=y +CONFIG_NETFILTER_XTABLES=y +CONFIG_NETFILTER_XT_MARK=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_SECMARK=y +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +CONFIG_NETLABEL=y +CONFIG_NETPOLL=y +# CONFIG_NETPOLL_TRAP is not set +# CONFIG_NETWORK_FILESYSTEMS is not set +CONFIG_NETWORK_SECMARK=y +# CONFIG_NET_ACT_GACT is not set +# CONFIG_NET_ACT_IPT is not set +# CONFIG_NET_ACT_MIRRED is not set +# CONFIG_NET_ACT_NAT is not set +# CONFIG_NET_ACT_PEDIT is not set +# CONFIG_NET_ACT_POLICE is not set +# CONFIG_NET_ACT_SIMP is not set +# CONFIG_NET_ACT_SKBEDIT is not set +CONFIG_NET_CLS=y +CONFIG_NET_CLS_ACT=y +# CONFIG_NET_CLS_BASIC is not set +# CONFIG_NET_CLS_CGROUP is not set +# CONFIG_NET_CLS_FLOW is not set +# CONFIG_NET_CLS_FW is not set +# CONFIG_NET_CLS_ROUTE4 is not set +# CONFIG_NET_CLS_RSVP is not set +# CONFIG_NET_CLS_RSVP6 is not set +# CONFIG_NET_CLS_TCINDEX is not set +# CONFIG_NET_CLS_U32 is not set +# CONFIG_NET_DROP_MONITOR is not set +CONFIG_NET_EMATCH=y +# CONFIG_NET_EMATCH_CMP is not set +# CONFIG_NET_EMATCH_META is not set +# CONFIG_NET_EMATCH_NBYTE is not set +CONFIG_NET_EMATCH_STACK=32 +# CONFIG_NET_EMATCH_TEXT is not set +# CONFIG_NET_EMATCH_U32 is not set +# CONFIG_NET_FC is not set +CONFIG_NET_NS=y +CONFIG_NET_PCI=y +CONFIG_NET_POLL_CONTROLLER=y +# CONFIG_NET_SB1000 is not set +CONFIG_NET_SCHED=y +# CONFIG_NET_SCH_CBQ is not set +# CONFIG_NET_SCH_DRR is not set +# CONFIG_NET_SCH_DSMARK is not set +CONFIG_NET_SCH_FIFO=y +# CONFIG_NET_SCH_GRED is not set +# CONFIG_NET_SCH_HFSC is not set +# CONFIG_NET_SCH_HTB is not set +# CONFIG_NET_SCH_INGRESS is not set +# CONFIG_NET_SCH_MULTIQ is not set +# CONFIG_NET_SCH_NETEM is not set +# CONFIG_NET_SCH_PRIO is not set +# CONFIG_NET_SCH_RED is not set +# CONFIG_NET_SCH_SFQ is not set +# CONFIG_NET_SCH_TBF is not set +# CONFIG_NET_SCH_TEQL is not set +# CONFIG_NET_TCPPROBE is not set +CONFIG_NET_TULIP=y +CONFIG_NET_VENDOR_3COM=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_NF_CONNTRACK_IPV6=y +CONFIG_NF_CONNTRACK_IRC=y +CONFIG_NF_CONNTRACK_PROC_COMPAT=y +CONFIG_NF_CONNTRACK_SECMARK=y +CONFIG_NF_CONNTRACK_SIP=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NF_DEFRAG_IPV4=y +CONFIG_NF_NAT=y +# CONFIG_NF_NAT_AMANDA is not set +CONFIG_NF_NAT_FTP=y +# CONFIG_NF_NAT_H323 is not set +CONFIG_NF_NAT_IRC=y +CONFIG_NF_NAT_NEEDED=y +# CONFIG_NF_NAT_PPTP is not set +CONFIG_NF_NAT_SIP=y +# CONFIG_NF_NAT_TFTP is not set +CONFIG_NLS=y +CONFIG_NLS_ASCII=y +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=y +CONFIG_NOHIGHMEM=y +CONFIG_NOP_TRACER=y +# CONFIG_NOP_USB_XCEIV is not set +CONFIG_NORTEL_HERMES=m +# CONFIG_NOZOMI is not set +CONFIG_NO_BOOTMEM=y +CONFIG_NO_HZ=y +CONFIG_NR_CPUS=4 +CONFIG_NS83820=m +# CONFIG_NSC_GPIO is not set +CONFIG_NVRAM=y +# CONFIG_N_HDLC is not set +# CONFIG_OLPC is not set +# CONFIG_OPROFILE is not set +CONFIG_OPTIMIZE_INLINING=y +CONFIG_OPTPROBES=y +# CONFIG_ORINOCO_USB is not set +CONFIG_OSF_PARTITION=y +# CONFIG_OTUS is not set +CONFIG_OUTPUT_FORMAT="elf32-i386" +CONFIG_P54_COMMON=m +CONFIG_P54_LEDS=y +CONFIG_P54_PCI=m +# CONFIG_P54_SPI is not set +CONFIG_P54_USB=m +CONFIG_PACKET=y +CONFIG_PAGE_OFFSET=0x78000000 +CONFIG_PANASONIC_LAPTOP=m +# CONFIG_PARAVIRT_GUEST is not set +CONFIG_PARTITION_ADVANCED=y +# CONFIG_PATA_ACPI is not set +# CONFIG_PATA_ALI is not set +# CONFIG_PATA_AMD is not set +# CONFIG_PATA_ARTOP is not set +# CONFIG_PATA_ATIIXP is not set +# CONFIG_PATA_ATP867X is not set +# CONFIG_PATA_CMD640_PCI is not set +# CONFIG_PATA_CMD64X is not set +# CONFIG_PATA_CS5520 is not set +# CONFIG_PATA_CS5530 is not set +# CONFIG_PATA_CS5535 is not set +# CONFIG_PATA_CS5536 is not set +# CONFIG_PATA_CYPRESS is not set +# CONFIG_PATA_EFAR is not set +# CONFIG_PATA_HPT366 is not set +# CONFIG_PATA_HPT37X is not set +# CONFIG_PATA_HPT3X2N is not set +# CONFIG_PATA_HPT3X3 is not set +# CONFIG_PATA_IT8213 is not set +# CONFIG_PATA_IT821X is not set +# CONFIG_PATA_JMICRON is not set +# CONFIG_PATA_LEGACY is not set +# CONFIG_PATA_MARVELL is not set +CONFIG_PATA_MPIIX=y +# CONFIG_PATA_NETCELL is not set +# CONFIG_PATA_NINJA32 is not set +# CONFIG_PATA_NS87410 is not set +# CONFIG_PATA_NS87415 is not set +# CONFIG_PATA_OLDPIIX is not set +# CONFIG_PATA_OPTI is not set +# CONFIG_PATA_OPTIDMA is not set +# CONFIG_PATA_PDC2027X is not set +# CONFIG_PATA_PDC_OLD is not set +# CONFIG_PATA_PLATFORM is not set +# CONFIG_PATA_RADISYS is not set +# CONFIG_PATA_RDC is not set +# CONFIG_PATA_RZ1000 is not set +# CONFIG_PATA_SC1200 is not set +CONFIG_PATA_SCH=y +# CONFIG_PATA_SERVERWORKS is not set +# CONFIG_PATA_SIL680 is not set +# CONFIG_PATA_SIS is not set +# CONFIG_PATA_TOSHIBA is not set +# CONFIG_PATA_TRIFLEX is not set +# CONFIG_PATA_VIA is not set +# CONFIG_PATA_WINBOND is not set +# CONFIG_PC8736x_GPIO is not set +CONFIG_PCI=y +CONFIG_PCIEAER=y +# CONFIG_PCIEAER_INJECT is not set +CONFIG_PCIEASPM=y +# CONFIG_PCIEASPM_DEBUG is not set +CONFIG_PCIEPORTBUS=y +# CONFIG_PCIE_ECRC is not set +CONFIG_PCI_ATMEL=m +CONFIG_PCI_BIOS=y +# CONFIG_PCI_CNB20LE_QUIRK is not set +# CONFIG_PCI_DEBUG is not set +CONFIG_PCI_DIRECT=y +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_GOANY=y +# CONFIG_PCI_GOBIOS is not set +# CONFIG_PCI_GODIRECT is not set +# CONFIG_PCI_GOMMCONFIG is not set +# CONFIG_PCI_GOOLPC is not set +CONFIG_PCI_IOAPIC=y +# CONFIG_PCI_IOV is not set +CONFIG_PCI_MMCONFIG=y +CONFIG_PCI_MSI=y +CONFIG_PCI_QUIRKS=y +# CONFIG_PCI_STUB is not set +CONFIG_PCNET32=m +CONFIG_PCSPKR_PLATFORM=y +# CONFIG_PDA_POWER is not set +# CONFIG_PDC_ADMA is not set +CONFIG_PERF_EVENTS=y +# CONFIG_PHANTOM is not set +CONFIG_PHYLIB=y +CONFIG_PHYSICAL_ALIGN=0x200000 +CONFIG_PHYSICAL_START=0x1000000 +# CONFIG_PHYS_ADDR_T_64BIT is not set +CONFIG_PID_NS=y +CONFIG_PLX_HERMES=m +# CONFIG_PM_ADVANCED_DEBUG is not set +CONFIG_PM_DEBUG=y +CONFIG_PM_SLEEP_SMP=y +# CONFIG_PM_TEST_SUSPEND is not set +CONFIG_PM_TRACE=y +CONFIG_PM_TRACE_RTC=y +# CONFIG_PM_VERBOSE is not set +CONFIG_PNP=y +CONFIG_PNPACPI=y +CONFIG_PNP_DEBUG_MESSAGES=y +CONFIG_POSIX_MQUEUE=y +CONFIG_POSIX_MQUEUE_SYSCTL=y +CONFIG_POWER_SUPPLY=y +# CONFIG_POWER_SUPPLY_DEBUG is not set +CONFIG_PPP=y +# CONFIG_PPPOE is not set +# CONFIG_PPPOLAC is not set +# CONFIG_PPPOPNS is not set +CONFIG_PPP_ASYNC=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_FILTER=y +# CONFIG_PPP_MPPE is not set +# CONFIG_PPP_MULTILINK is not set +CONFIG_PPP_SYNC_TTY=y +# CONFIG_PREEMPT is not set +CONFIG_PREEMPT_VOLUNTARY=y +CONFIG_PRINTK_TIME=y +# CONFIG_PRISM2_USB is not set +CONFIG_PRISM54=m +# CONFIG_PROCESSOR_SELECT is not set +CONFIG_PROC_EVENTS=y +CONFIG_PROC_KCORE=y +CONFIG_PROC_PID_CPUSET=y +CONFIG_PROFILING=y +CONFIG_PROVIDE_OHCI1394_DMA_INIT=y +CONFIG_QLA3XXX=m +# CONFIG_QSEMI_PHY is not set +CONFIG_R6040=m +CONFIG_R8169=m +# CONFIG_R8187SE is not set +# CONFIG_RADIO_ADAPTERS is not set +# CONFIG_RAR_REGISTER is not set +# CONFIG_RCU_FAST_NO_HZ is not set +CONFIG_RC_MAP=m +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_LZO is not set +# CONFIG_REALTEK_PHY is not set +# CONFIG_REGULATOR is not set +CONFIG_RELAY=y +# CONFIG_RELOCATABLE is not set +CONFIG_RESOURCE_COUNTERS=y +CONFIG_RFKILL=y +# CONFIG_RFKILL_INPUT is not set +CONFIG_RFKILL_LEDS=y +CONFIG_RFKILL_PM=y +# CONFIG_RISCOM8 is not set +# CONFIG_ROCKETPORT is not set +# CONFIG_ROMFS_FS is not set +CONFIG_RPS=y +# CONFIG_RT2400PCI is not set +# CONFIG_RT2500PCI is not set +# CONFIG_RT2500USB is not set +# CONFIG_RT2800PCI is not set +CONFIG_RT2800PCI_PCI=y +# CONFIG_RT2800USB is not set +# CONFIG_RT2860 is not set +# CONFIG_RT2870 is not set +CONFIG_RT2X00=m +# CONFIG_RT61PCI is not set +# CONFIG_RT73USB is not set +CONFIG_RTC_CLASS=y +# CONFIG_RTC_DEBUG is not set +# CONFIG_RTC_DRV_BQ32K is not set +CONFIG_RTC_DRV_BQ4802=m +CONFIG_RTC_DRV_CMOS=m +CONFIG_RTC_DRV_DS1286=m +CONFIG_RTC_DRV_DS1305=m +CONFIG_RTC_DRV_DS1307=m +CONFIG_RTC_DRV_DS1374=m +CONFIG_RTC_DRV_DS1390=m +CONFIG_RTC_DRV_DS1511=m +CONFIG_RTC_DRV_DS1553=m +CONFIG_RTC_DRV_DS1672=m +CONFIG_RTC_DRV_DS1742=m +CONFIG_RTC_DRV_DS3234=m +CONFIG_RTC_DRV_FM3130=m +CONFIG_RTC_DRV_ISL1208=m +CONFIG_RTC_DRV_M41T80=m +CONFIG_RTC_DRV_M41T80_WDT=y +CONFIG_RTC_DRV_M41T94=m +CONFIG_RTC_DRV_M48T35=m +CONFIG_RTC_DRV_M48T59=m +CONFIG_RTC_DRV_M48T86=m +CONFIG_RTC_DRV_MAX6900=m +CONFIG_RTC_DRV_MAX6902=m +# CONFIG_RTC_DRV_MSM6242 is not set +# CONFIG_RTC_DRV_PCF2123 is not set +CONFIG_RTC_DRV_PCF8563=m +CONFIG_RTC_DRV_PCF8583=m +CONFIG_RTC_DRV_R9701=m +# CONFIG_RTC_DRV_RP5C01 is not set +CONFIG_RTC_DRV_RS5C348=m +CONFIG_RTC_DRV_RS5C372=m +# CONFIG_RTC_DRV_RX8025 is not set +CONFIG_RTC_DRV_RX8581=m +CONFIG_RTC_DRV_S35390A=m +CONFIG_RTC_DRV_STK17TA8=m +# CONFIG_RTC_DRV_TEST is not set +CONFIG_RTC_DRV_V3020=m +CONFIG_RTC_DRV_X1205=m +# CONFIG_RTC_HCTOSYS is not set +CONFIG_RTC_INTF_ALARM=y +CONFIG_RTC_INTF_ALARM_DEV=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTL8180=m +CONFIG_RTL8187=m +CONFIG_RTL8187_LEDS=y +# CONFIG_RTL8192E is not set +# CONFIG_RTL8192SU is not set +# CONFIG_RTL8192U is not set +# CONFIG_RT_GROUP_SCHED is not set +# CONFIG_RWSEM_GENERIC_SPINLOCK is not set +# CONFIG_SAMSUNG_LAPTOP is not set +CONFIG_SATA_AHCI=y +# CONFIG_SATA_AHCI_PLATFORM is not set +# CONFIG_SATA_INIC162X is not set +# CONFIG_SATA_MV is not set +# CONFIG_SATA_NV is not set +# CONFIG_SATA_PMP is not set +# CONFIG_SATA_PROMISE is not set +# CONFIG_SATA_QSTOR is not set +# CONFIG_SATA_SIL is not set +# CONFIG_SATA_SIL24 is not set +# CONFIG_SATA_SIS is not set +# CONFIG_SATA_SVW is not set +# CONFIG_SATA_SX4 is not set +# CONFIG_SATA_ULI is not set +# CONFIG_SATA_VIA is not set +# CONFIG_SATA_VITESSE is not set +CONFIG_SC92031=m +CONFIG_SCHEDSTATS=y +# CONFIG_SCHED_DEBUG is not set +CONFIG_SCHED_HRTICK=y +CONFIG_SCHED_MC=y +CONFIG_SCHED_OMIT_FRAME_POINTER=y +CONFIG_SCHED_SMT=y +CONFIG_SCSI=y +CONFIG_SCSI_CONSTANTS=y +# CONFIG_SCSI_DH is not set +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_LOWLEVEL is not set +# CONFIG_SCSI_MULTI_LUN is not set +# CONFIG_SCSI_OSD_INITIATOR is not set +CONFIG_SCSI_PROC_FS=y +# CONFIG_SCSI_SAS_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_SPI_ATTRS=y +# CONFIG_SCSI_SRP_ATTRS is not set +# CONFIG_SCSI_TGT is not set +CONFIG_SCSI_WAIT_SCAN=m +# CONFIG_SCx200 is not set +# CONFIG_SCx200_ACB is not set +CONFIG_SECCOMP=y +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +CONFIG_SECURITY_NETWORK=y +# CONFIG_SECURITY_NETWORK_XFRM is not set +CONFIG_SECURITY_PATH=y +# CONFIG_SECURITY_SELINUX is not set +# CONFIG_SECURITY_SMACK is not set +CONFIG_SECURITY_TOMOYO=y +CONFIG_SENSORS_ABITUGURU=m +CONFIG_SENSORS_ABITUGURU3=m +CONFIG_SENSORS_AD7414=m +CONFIG_SENSORS_AD7418=m +CONFIG_SENSORS_ADCXX=m +CONFIG_SENSORS_ADM1021=m +CONFIG_SENSORS_ADM1025=m +CONFIG_SENSORS_ADM1026=m +CONFIG_SENSORS_ADM1029=m +CONFIG_SENSORS_ADM1031=m +CONFIG_SENSORS_ADM9240=m +CONFIG_SENSORS_ADS7828=m +# CONFIG_SENSORS_ADS7871 is not set +# CONFIG_SENSORS_ADT7411 is not set +CONFIG_SENSORS_ADT7462=m +CONFIG_SENSORS_ADT7470=m +CONFIG_SENSORS_ADT7475=m +# CONFIG_SENSORS_AMC6821 is not set +CONFIG_SENSORS_APPLESMC=m +CONFIG_SENSORS_ASB100=m +# CONFIG_SENSORS_ASC7621 is not set +CONFIG_SENSORS_ATK0110=m +CONFIG_SENSORS_ATXP1=m +CONFIG_SENSORS_CORETEMP=m +CONFIG_SENSORS_DME1737=m +CONFIG_SENSORS_DS1621=m +# CONFIG_SENSORS_EMC1403 is not set +CONFIG_SENSORS_F71805F=m +CONFIG_SENSORS_F71882FG=m +CONFIG_SENSORS_F75375S=m +CONFIG_SENSORS_FSCHMD=m +CONFIG_SENSORS_G760A=m +CONFIG_SENSORS_GL518SM=m +CONFIG_SENSORS_GL520SM=m +CONFIG_SENSORS_HDAPS=m +CONFIG_SENSORS_I5K_AMB=m +CONFIG_SENSORS_IT87=m +# CONFIG_SENSORS_K10TEMP is not set +CONFIG_SENSORS_K8TEMP=m +CONFIG_SENSORS_LIS3LV02D=m +# CONFIG_SENSORS_LIS3_I2C is not set +CONFIG_SENSORS_LM63=m +CONFIG_SENSORS_LM70=m +# CONFIG_SENSORS_LM73 is not set +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_LM77=m +CONFIG_SENSORS_LM78=m +CONFIG_SENSORS_LM80=m +CONFIG_SENSORS_LM83=m +CONFIG_SENSORS_LM85=m +CONFIG_SENSORS_LM87=m +CONFIG_SENSORS_LM90=m +CONFIG_SENSORS_LM92=m +CONFIG_SENSORS_LM93=m +CONFIG_SENSORS_LM95241=m +CONFIG_SENSORS_LTC4215=m +CONFIG_SENSORS_LTC4245=m +CONFIG_SENSORS_MAX1111=m +CONFIG_SENSORS_MAX1619=m +CONFIG_SENSORS_MAX6650=m +CONFIG_SENSORS_PC87360=m +CONFIG_SENSORS_PC87427=m +CONFIG_SENSORS_PCF8591=m +CONFIG_SENSORS_SHT15=m +CONFIG_SENSORS_SIS5595=m +CONFIG_SENSORS_SMSC47B397=m +CONFIG_SENSORS_SMSC47M1=m +CONFIG_SENSORS_SMSC47M192=m +CONFIG_SENSORS_THMC50=m +# CONFIG_SENSORS_TMP102 is not set +# CONFIG_SENSORS_TMP401 is not set +# CONFIG_SENSORS_TMP421 is not set +# CONFIG_SENSORS_TSL2563 is not set +CONFIG_SENSORS_VIA686A=m +# CONFIG_SENSORS_VIA_CPUTEMP is not set +CONFIG_SENSORS_VT1211=m +CONFIG_SENSORS_VT8231=m +CONFIG_SENSORS_W83627EHF=m +CONFIG_SENSORS_W83627HF=m +CONFIG_SENSORS_W83781D=m +CONFIG_SENSORS_W83791D=m +CONFIG_SENSORS_W83792D=m +CONFIG_SENSORS_W83793=m +CONFIG_SENSORS_W83L785TS=m +CONFIG_SENSORS_W83L786NG=m +# CONFIG_SENSORS_WPCE775X is not set +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_DETECT_IRQ=y +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_MANY_PORTS=y +CONFIG_SERIAL_8250_NR_UARTS=32 +CONFIG_SERIAL_8250_PCI=y +CONFIG_SERIAL_8250_PNP=y +CONFIG_SERIAL_8250_RSA=y +CONFIG_SERIAL_8250_RUNTIME_UARTS=4 +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +# CONFIG_SERIAL_JSM is not set +# CONFIG_SERIAL_MAX3100 is not set +CONFIG_SERIAL_NONSTANDARD=y +CONFIG_SERIO=y +# CONFIG_SERIO_ALTERA_PS2 is not set +CONFIG_SERIO_CT82C710=m +CONFIG_SERIO_I8042=y +CONFIG_SERIO_LIBPS2=y +CONFIG_SERIO_PCIPS2=m +CONFIG_SERIO_RAW=m +CONFIG_SERIO_SERPORT=m +# CONFIG_SFI is not set +# CONFIG_SGI_IOC4 is not set +CONFIG_SGI_PARTITION=y +CONFIG_SIS190=m +CONFIG_SIS900=m +# CONFIG_SKFP is not set +CONFIG_SKGE=m +# CONFIG_SKGE_DEBUG is not set +CONFIG_SKY2=m +# CONFIG_SKY2_DEBUG is not set +# CONFIG_SLAB is not set +# CONFIG_SLICOSS is not set +# CONFIG_SLIP is not set +CONFIG_SLOW_WORK=y +# CONFIG_SLOW_WORK_DEBUG is not set +CONFIG_SLUB=y +CONFIG_SLUB_DEBUG=y +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +CONFIG_SMP=y +CONFIG_SMSC9420=m +CONFIG_SMSC_PHY=m +CONFIG_SND=m +# CONFIG_SND_AD1889 is not set +# CONFIG_SND_ALI5451 is not set +# CONFIG_SND_ALS300 is not set +# CONFIG_SND_ALS4000 is not set +# CONFIG_SND_ASIHPI is not set +# CONFIG_SND_ATIIXP is not set +# CONFIG_SND_ATIIXP_MODEM is not set +# CONFIG_SND_AU8810 is not set +# CONFIG_SND_AU8820 is not set +# CONFIG_SND_AU8830 is not set +# CONFIG_SND_AW2 is not set +# CONFIG_SND_AZT3328 is not set +# CONFIG_SND_BT87X is not set +# CONFIG_SND_CA0106 is not set +# CONFIG_SND_CMIPCI is not set +# CONFIG_SND_CS4281 is not set +# CONFIG_SND_CS46XX is not set +# CONFIG_SND_CS5530 is not set +# CONFIG_SND_CS5535AUDIO is not set +# CONFIG_SND_CTXFI is not set +# CONFIG_SND_DARLA20 is not set +# CONFIG_SND_DARLA24 is not set +# CONFIG_SND_DEBUG is not set +CONFIG_SND_DMA_SGBUF=y +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +CONFIG_SND_DYNAMIC_MINORS=y +# CONFIG_SND_ECHO3G is not set +# CONFIG_SND_EMU10K1 is not set +# CONFIG_SND_EMU10K1X is not set +# CONFIG_SND_EMU10K1_SEQ is not set +# CONFIG_SND_ENS1370 is not set +# CONFIG_SND_ENS1371 is not set +# CONFIG_SND_ES1938 is not set +# CONFIG_SND_ES1968 is not set +# CONFIG_SND_FM801 is not set +# CONFIG_SND_GINA20 is not set +# CONFIG_SND_GINA24 is not set +CONFIG_SND_HDA_CODEC_ANALOG=y +CONFIG_SND_HDA_CODEC_ATIHDMI=y +CONFIG_SND_HDA_CODEC_CA0110=y +CONFIG_SND_HDA_CODEC_CIRRUS=y +CONFIG_SND_HDA_CODEC_CMEDIA=y +CONFIG_SND_HDA_CODEC_CONEXANT=y +CONFIG_SND_HDA_CODEC_INTELHDMI=y +CONFIG_SND_HDA_CODEC_NVHDMI=y +CONFIG_SND_HDA_CODEC_REALTEK=y +CONFIG_SND_HDA_CODEC_SI3054=y +CONFIG_SND_HDA_CODEC_SIGMATEL=y +CONFIG_SND_HDA_CODEC_VIA=y +CONFIG_SND_HDA_ELD=y +CONFIG_SND_HDA_GENERIC=y +CONFIG_SND_HDA_HWDEP=y +# CONFIG_SND_HDA_INPUT_BEEP is not set +# CONFIG_SND_HDA_INPUT_JACK is not set +CONFIG_SND_HDA_INTEL=m +# CONFIG_SND_HDA_PATCH_LOADER is not set +CONFIG_SND_HDA_POWER_SAVE=y +CONFIG_SND_HDA_POWER_SAVE_DEFAULT=5 +# CONFIG_SND_HDA_RECONFIG is not set +# CONFIG_SND_HDSP is not set +# CONFIG_SND_HDSPM is not set +# CONFIG_SND_HIFIER is not set +CONFIG_SND_HRTIMER=m +CONFIG_SND_HWDEP=m +# CONFIG_SND_ICE1712 is not set +# CONFIG_SND_ICE1724 is not set +# CONFIG_SND_INDIGO is not set +# CONFIG_SND_INDIGODJ is not set +# CONFIG_SND_INDIGODJX is not set +# CONFIG_SND_INDIGOIO is not set +# CONFIG_SND_INDIGOIOX is not set +# CONFIG_SND_INTEL8X0 is not set +# CONFIG_SND_INTEL8X0M is not set +# CONFIG_SND_KORG1212 is not set +# CONFIG_SND_LAYLA20 is not set +# CONFIG_SND_LAYLA24 is not set +# CONFIG_SND_LX6464ES is not set +# CONFIG_SND_MAESTRO3 is not set +# CONFIG_SND_MIA is not set +# CONFIG_SND_MIXART is not set +CONFIG_SND_MIXER_OSS=m +# CONFIG_SND_MONA is not set +# CONFIG_SND_MPU401 is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_NM256 is not set +# CONFIG_SND_OPL3_LIB_SEQ is not set +# CONFIG_SND_OPL4_LIB_SEQ is not set +CONFIG_SND_OSSEMUL=y +# CONFIG_SND_OXYGEN is not set +CONFIG_SND_PCI=y +CONFIG_SND_PCM=m +CONFIG_SND_PCM_OSS=m +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_PCSP is not set +# CONFIG_SND_PCXHR is not set +CONFIG_SND_RAWMIDI=m +CONFIG_SND_RAWMIDI_SEQ=m +# CONFIG_SND_RIPTIDE is not set +# CONFIG_SND_RME32 is not set +# CONFIG_SND_RME96 is not set +# CONFIG_SND_RME9652 is not set +# CONFIG_SND_SBAWE_SEQ is not set +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQUENCER_OSS=y +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_SEQ_HRTIMER_DEFAULT=y +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_SIS7019 is not set +# CONFIG_SND_SOC is not set +# CONFIG_SND_SONICVIBES is not set +CONFIG_SND_SPI=y +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_TIMER=m +# CONFIG_SND_TRIDENT is not set +CONFIG_SND_USB=y +CONFIG_SND_USB_AUDIO=m +# CONFIG_SND_USB_CAIAQ is not set +# CONFIG_SND_USB_UA101 is not set +# CONFIG_SND_USB_US122L is not set +# CONFIG_SND_USB_USX2Y is not set +# CONFIG_SND_VERBOSE_PRINTK is not set +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VIA82XX is not set +# CONFIG_SND_VIA82XX_MODEM is not set +# CONFIG_SND_VIRMIDI is not set +# CONFIG_SND_VIRTUOSO is not set +CONFIG_SND_VMASTER=y +# CONFIG_SND_VX222 is not set +# CONFIG_SND_YMFPCI is not set +# CONFIG_SOC_CAMERA is not set +CONFIG_SOLARIS_X86_PARTITION=y +# CONFIG_SONYPI is not set +CONFIG_SONYPI_COMPAT=y +CONFIG_SONY_LAPTOP=m +CONFIG_SOUND=m +CONFIG_SOUND_OSS_CORE=y +CONFIG_SOUND_OSS_CORE_PRECLAIM=y +# CONFIG_SOUND_PRIME is not set +CONFIG_SPARSEMEM=y +CONFIG_SPARSEMEM_MANUAL=y +CONFIG_SPARSEMEM_STATIC=y +CONFIG_SPARSE_IRQ=y +# CONFIG_SPECIALIX is not set +CONFIG_SPI=y +CONFIG_SPI_BITBANG=m +# CONFIG_SPI_DEBUG is not set +# CONFIG_SPI_DESIGNWARE is not set +# CONFIG_SPI_GPIO is not set +CONFIG_SPI_MASTER=y +CONFIG_SPI_SPIDEV=m +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_SPI_XILINX is not set +CONFIG_SPLIT_PTLOCK_CPUS=4 +CONFIG_SQUASHFS=y +# CONFIG_SQUASHFS_EMBEDDED is not set +CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3 +# CONFIG_SQUASHFS_XATTRS is not set +CONFIG_STACKTRACE=y +# CONFIG_STALDRV is not set +# CONFIG_STE10XP is not set +CONFIG_STOP_MACHINE=y +# CONFIG_STRICT_DEVMEM is not set +# CONFIG_ST_BT is not set +CONFIG_SUNDANCE=m +CONFIG_SUNDANCE_MMIO=y +CONFIG_SUNGEM=m +CONFIG_SUN_PARTITION=y +# CONFIG_SWAP is not set +# CONFIG_SWITCH is not set +# CONFIG_SYNCLINK is not set +# CONFIG_SYNCLINKMP is not set +# CONFIG_SYNCLINK_GT is not set +CONFIG_SYN_COOKIES=y +CONFIG_SYSCTL_SYSCALL_CHECK=y +# CONFIG_SYSPROF_TRACER is not set +# CONFIG_SYSV68_PARTITION is not set +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_SYSCTL=y +CONFIG_TABLET_USB_ACECAD=m +CONFIG_TABLET_USB_AIPTEK=m +CONFIG_TABLET_USB_GTCO=m +CONFIG_TABLET_USB_KBTAB=m +CONFIG_TABLET_USB_WACOM=m +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_TASK_XACCT=y +CONFIG_TC1100_WMI=m +CONFIG_TCP_CONG_ADVANCED=y +# CONFIG_TCP_CONG_BIC is not set +# CONFIG_TCP_CONG_HSTCP is not set +# CONFIG_TCP_CONG_HTCP is not set +# CONFIG_TCP_CONG_HYBLA is not set +# CONFIG_TCP_CONG_ILLINOIS is not set +# CONFIG_TCP_CONG_LP is not set +# CONFIG_TCP_CONG_SCALABLE is not set +# CONFIG_TCP_CONG_VEGAS is not set +# CONFIG_TCP_CONG_VENO is not set +# CONFIG_TCP_CONG_WESTWOOD is not set +# CONFIG_TCP_CONG_YEAH is not set +CONFIG_TCP_MD5SIG=y +# CONFIG_TELCLOCK is not set +# CONFIG_TEST_POWER is not set +CONFIG_THERMAL=y +CONFIG_THERMAL_HWMON=y +# CONFIG_THERMAL_MSM_POPMEM is not set +CONFIG_THINKPAD_ACPI=m +CONFIG_THINKPAD_ACPI_ALSA_SUPPORT=y +# CONFIG_THINKPAD_ACPI_DEBUG is not set +# CONFIG_THINKPAD_ACPI_DEBUGFACILITIES is not set +CONFIG_THINKPAD_ACPI_HOTKEY_POLL=y +CONFIG_THINKPAD_ACPI_UNSAFE_LEDS=y +CONFIG_THINKPAD_ACPI_VIDEO=y +CONFIG_TICK_ONESHOT=y +# CONFIG_TIFM_CORE is not set +CONFIG_TIGON3=m +CONFIG_TIMER_STATS=y +# CONFIG_TI_DAC7512 is not set +# CONFIG_TI_ST is not set +CONFIG_TLAN=m +CONFIG_TMD_HERMES=m +CONFIG_TMPFS_POSIX_ACL=y +# CONFIG_TOPSTAR_LAPTOP is not set +# CONFIG_TOSHIBA is not set +# CONFIG_TOSHIBA_BT_RFKILL is not set +CONFIG_TOUCHSCREEN_AD7877=m +CONFIG_TOUCHSCREEN_AD7879=m +CONFIG_TOUCHSCREEN_AD7879_I2C=m +CONFIG_TOUCHSCREEN_ADS7846=m +# CONFIG_TOUCHSCREEN_CY8C_TS is not set +# CONFIG_TOUCHSCREEN_CYTTSP_I2C is not set +# CONFIG_TOUCHSCREEN_DYNAPRO is not set +# CONFIG_TOUCHSCREEN_EETI is not set +CONFIG_TOUCHSCREEN_ELO=m +CONFIG_TOUCHSCREEN_FUJITSU=m +CONFIG_TOUCHSCREEN_GUNZE=m +# CONFIG_TOUCHSCREEN_HAMPSHIRE is not set +CONFIG_TOUCHSCREEN_INEXIO=m +# CONFIG_TOUCHSCREEN_MCS5000 is not set +CONFIG_TOUCHSCREEN_MK712=m +CONFIG_TOUCHSCREEN_MTOUCH=m +CONFIG_TOUCHSCREEN_PENMOUNT=m +# CONFIG_TOUCHSCREEN_QT602240 is not set +# CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI is not set +CONFIG_TOUCHSCREEN_TOUCHIT213=m +CONFIG_TOUCHSCREEN_TOUCHRIGHT=m +CONFIG_TOUCHSCREEN_TOUCHWIN=m +# CONFIG_TOUCHSCREEN_TPS6507X is not set +CONFIG_TOUCHSCREEN_TSC2007=m +CONFIG_TOUCHSCREEN_USB_3M=y +CONFIG_TOUCHSCREEN_USB_COMPOSITE=m +CONFIG_TOUCHSCREEN_USB_DMC_TSC10=y +CONFIG_TOUCHSCREEN_USB_E2I=y +CONFIG_TOUCHSCREEN_USB_EGALAX=y +CONFIG_TOUCHSCREEN_USB_ETT_TC5UH=y +CONFIG_TOUCHSCREEN_USB_ETURBO=y +CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH=y +CONFIG_TOUCHSCREEN_USB_GOTOP=y +CONFIG_TOUCHSCREEN_USB_GUNZE=y +CONFIG_TOUCHSCREEN_USB_IDEALTEK=y +CONFIG_TOUCHSCREEN_USB_IRTOUCH=y +CONFIG_TOUCHSCREEN_USB_ITM=y +CONFIG_TOUCHSCREEN_USB_JASTEC=y +CONFIG_TOUCHSCREEN_USB_NEXIO=y +CONFIG_TOUCHSCREEN_USB_PANJIT=y +CONFIG_TOUCHSCREEN_USB_ZYTRONIC=y +CONFIG_TOUCHSCREEN_WACOM_W8001=m +# CONFIG_TPS65010 is not set +# CONFIG_TR is not set +CONFIG_TRACEPOINTS=y +CONFIG_TRACING=y +# CONFIG_TRANZPORT is not set +# CONFIG_TULIP is not set +CONFIG_TYPHOON=m +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_ULI526X is not set +# CONFIG_ULTRIX_PARTITION is not set +CONFIG_UNIXWARE_DISKLABEL=y +CONFIG_USB=y +CONFIG_USB_ACM=y +# CONFIG_USB_ADUTUX is not set +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +# CONFIG_USB_APPLEDISPLAY is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB_ARCH_HAS_OHCI=y +# CONFIG_USB_C67X00_HCD is not set +# CONFIG_USB_CATC is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_DEBUG is not set +CONFIG_USB_DEVICEFS=y +# CONFIG_USB_DEVICE_CLASS is not set +# CONFIG_USB_DYNAMIC_MINORS is not set +# CONFIG_USB_EHCI_EHSET is not set +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_ET61X251 is not set +# CONFIG_USB_EZUSB is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_GPIO_VBUS is not set +# CONFIG_USB_GSPCA is not set +CONFIG_USB_HID=y +CONFIG_USB_HIDDEV=y +# CONFIG_USB_HSO is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_IPHETH is not set +# CONFIG_USB_IP_COMMON is not set +# CONFIG_USB_ISIGHTFW is not set +CONFIG_USB_ISP116X_HCD=m +# CONFIG_USB_ISP1362_HCD is not set +CONFIG_USB_ISP1760_HCD=m +CONFIG_USB_KAWETH=m +# CONFIG_USB_LCD is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_LEGOTOWER is not set +CONFIG_USB_LIBUSUAL=y +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set +# CONFIG_USB_MON is not set +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_CDCETHER=m +# CONFIG_USB_NET_CDC_EEM is not set +# CONFIG_USB_NET_CDC_SUBSET is not set +CONFIG_USB_NET_DM9601=m +# CONFIG_USB_NET_GL620A is not set +# CONFIG_USB_NET_INT51X1 is not set +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_NET1080=m +# CONFIG_USB_NET_PLUSB is not set +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_RNDIS_WLAN=m +# CONFIG_USB_NET_SMSC75XX is not set +CONFIG_USB_NET_SMSC95XX=m +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_USB_OHCI_BIG_ENDIAN_DESC is not set +# CONFIG_USB_OHCI_BIG_ENDIAN_MMIO is not set +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_OHCI_LITTLE_ENDIAN=y +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +# CONFIG_USB_OTG_WHITELIST is not set +CONFIG_USB_OXU210HP_HCD=m +CONFIG_USB_PEGASUS=m +# CONFIG_USB_PRINTER is not set +CONFIG_USB_R8A66597_HCD=m +# CONFIG_USB_RIO500 is not set +CONFIG_USB_RTL8150=m +# CONFIG_USB_S2255 is not set +CONFIG_USB_SERIAL=y +# CONFIG_USB_SERIAL_AIRCABLE is not set +# CONFIG_USB_SERIAL_ARK3116 is not set +# CONFIG_USB_SERIAL_BELKIN is not set +# CONFIG_USB_SERIAL_CH341 is not set +# CONFIG_USB_SERIAL_CONSOLE is not set +# CONFIG_USB_SERIAL_CP210X is not set +# CONFIG_USB_SERIAL_CYBERJACK is not set +CONFIG_USB_SERIAL_CYPRESS_M8=m +# CONFIG_USB_SERIAL_DEBUG is not set +# CONFIG_USB_SERIAL_DIGI_ACCELEPORT is not set +# CONFIG_USB_SERIAL_EDGEPORT is not set +# CONFIG_USB_SERIAL_EDGEPORT_TI is not set +# CONFIG_USB_SERIAL_EMPEG is not set +# CONFIG_USB_SERIAL_FTDI_SIO is not set +# CONFIG_USB_SERIAL_FUNSOFT is not set +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_GENERIC=y +# CONFIG_USB_SERIAL_HP4X is not set +# CONFIG_USB_SERIAL_IPAQ is not set +# CONFIG_USB_SERIAL_IPW is not set +# CONFIG_USB_SERIAL_IR is not set +# CONFIG_USB_SERIAL_IUU is not set +# CONFIG_USB_SERIAL_KEYSPAN is not set +# CONFIG_USB_SERIAL_KEYSPAN_PDA is not set +# CONFIG_USB_SERIAL_KLSI is not set +# CONFIG_USB_SERIAL_KOBIL_SCT is not set +# CONFIG_USB_SERIAL_MCT_U232 is not set +# CONFIG_USB_SERIAL_MOS7720 is not set +# CONFIG_USB_SERIAL_MOS7840 is not set +# CONFIG_USB_SERIAL_MOTOROLA is not set +CONFIG_USB_SERIAL_NAVMAN=m +# CONFIG_USB_SERIAL_OMNINET is not set +# CONFIG_USB_SERIAL_OPTICON is not set +CONFIG_USB_SERIAL_OPTION=y +# CONFIG_USB_SERIAL_OTI6858 is not set +# CONFIG_USB_SERIAL_PL2303 is not set +# CONFIG_USB_SERIAL_QCAUX is not set +# CONFIG_USB_SERIAL_QUALCOMM is not set +# CONFIG_USB_SERIAL_QUATECH2 is not set +# CONFIG_USB_SERIAL_QUATECH_USB2 is not set +# CONFIG_USB_SERIAL_SAFE is not set +# CONFIG_USB_SERIAL_SIEMENS_MPI is not set +CONFIG_USB_SERIAL_SIERRAWIRELESS=y +# CONFIG_USB_SERIAL_SPCP8X5 is not set +# CONFIG_USB_SERIAL_SYMBOL is not set +# CONFIG_USB_SERIAL_TI is not set +# CONFIG_USB_SERIAL_VISOR is not set +# CONFIG_USB_SERIAL_VIVOPAY_SERIAL is not set +# CONFIG_USB_SERIAL_WHITEHEAT is not set +CONFIG_USB_SERIAL_WWAN=y +# CONFIG_USB_SERIAL_XIRCOM is not set +# CONFIG_USB_SERIAL_ZIO is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_SIERRA_NET is not set +# CONFIG_USB_SISUSBVGA is not set +CONFIG_USB_SL811_HCD=m +# CONFIG_USB_SN9C102 is not set +# CONFIG_USB_STKWEBCAM is not set +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_TMC is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +CONFIG_USB_UHCI_HCD=y +CONFIG_USB_USBNET=m +CONFIG_USB_VIDEO_CLASS=m +# CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_WHCI_HCD is not set +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set +# CONFIG_USB_XHCI_HCD is not set +# CONFIG_USB_ZC0301 is not set +CONFIG_USB_ZD1201=m +# CONFIG_USB_ZR364XX is not set +CONFIG_USER_NS=y +CONFIG_USER_STACKTRACE_SUPPORT=y +CONFIG_USE_GENERIC_SMP_HELPERS=y +CONFIG_UTS_NS=y +# CONFIG_UWB is not set +# CONFIG_V4L_MEM2MEM_DRIVERS is not set +CONFIG_V4L_USB_DRIVERS=y +CONFIG_VFAT_FS=m +# CONFIG_VGACON_SOFT_SCROLLBACK is not set +CONFIG_VGA_ARB=y +CONFIG_VGA_ARB_MAX_GPUS=16 +CONFIG_VGA_CONSOLE=y +# CONFIG_VGA_SWITCHEROO is not set +CONFIG_VIA_RHINE=m +CONFIG_VIA_RHINE_MMIO=y +CONFIG_VIA_VELOCITY=m +# CONFIG_VIDEO_ADV7170 is not set +# CONFIG_VIDEO_ADV7175 is not set +# CONFIG_VIDEO_ADV7180 is not set +# CONFIG_VIDEO_ADV7343 is not set +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_AK881X is not set +# CONFIG_VIDEO_ALLOW_V4L1 is not set +# CONFIG_VIDEO_BT819 is not set +# CONFIG_VIDEO_BT848 is not set +# CONFIG_VIDEO_BT856 is not set +# CONFIG_VIDEO_BT866 is not set +# CONFIG_VIDEO_CAFE_CCIC is not set +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_CS5345 is not set +# CONFIG_VIDEO_CS53L32A is not set +# CONFIG_VIDEO_CX231XX is not set +# CONFIG_VIDEO_CX2341X is not set +# CONFIG_VIDEO_CX25840 is not set +# CONFIG_VIDEO_CX88 is not set +CONFIG_VIDEO_DEV=m +# CONFIG_VIDEO_DT3155 is not set +# CONFIG_VIDEO_EM28XX is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +# CONFIG_VIDEO_GO7007 is not set +# CONFIG_VIDEO_HDPVR is not set +# CONFIG_VIDEO_HELPER_CHIPS_AUTO is not set +# CONFIG_VIDEO_HEXIUM_GEMINI is not set +# CONFIG_VIDEO_HEXIUM_ORION is not set +CONFIG_VIDEO_IR=m +CONFIG_VIDEO_IR_I2C=m +# CONFIG_VIDEO_IVTV is not set +# CONFIG_VIDEO_KS0127 is not set +# CONFIG_VIDEO_M52790 is not set +CONFIG_VIDEO_MEDIA=m +# CONFIG_VIDEO_MEYE is not set +# CONFIG_VIDEO_MSP3400 is not set +# CONFIG_VIDEO_MT9V011 is not set +# CONFIG_VIDEO_MXB is not set +CONFIG_VIDEO_OUTPUT_CONTROL=y +# CONFIG_VIDEO_OV7670 is not set +# CONFIG_VIDEO_PVRUSB2 is not set +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_VIDEO_SAA6588 is not set +# CONFIG_VIDEO_SAA7110 is not set +# CONFIG_VIDEO_SAA711X is not set +# CONFIG_VIDEO_SAA7127 is not set +# CONFIG_VIDEO_SAA7134 is not set +# CONFIG_VIDEO_SAA717X is not set +# CONFIG_VIDEO_SAA7185 is not set +# CONFIG_VIDEO_SAA7191 is not set +# CONFIG_VIDEO_TCM825X is not set +# CONFIG_VIDEO_TDA7432 is not set +# CONFIG_VIDEO_TDA9840 is not set +# CONFIG_VIDEO_TDA9875 is not set +# CONFIG_VIDEO_TEA6415C is not set +# CONFIG_VIDEO_TEA6420 is not set +# CONFIG_VIDEO_THS7303 is not set +# CONFIG_VIDEO_TLV320AIC23B is not set +# CONFIG_VIDEO_TM6000 is not set +# CONFIG_VIDEO_TVAUDIO is not set +# CONFIG_VIDEO_TVP514X is not set +# CONFIG_VIDEO_TVP5150 is not set +# CONFIG_VIDEO_TVP7002 is not set +# CONFIG_VIDEO_UPD64031A is not set +# CONFIG_VIDEO_UPD64083 is not set +# CONFIG_VIDEO_USBVISION is not set +# CONFIG_VIDEO_V4L1_COMPAT is not set +CONFIG_VIDEO_V4L2=m +CONFIG_VIDEO_V4L2_COMMON=m +# CONFIG_VIDEO_VP27SMPX is not set +# CONFIG_VIDEO_VPX3220 is not set +# CONFIG_VIDEO_WM8739 is not set +# CONFIG_VIDEO_WM8775 is not set +# CONFIG_VIDEO_ZORAN is not set +# CONFIG_VIRTUALIZATION is not set +# CONFIG_VITESSE_PHY is not set +CONFIG_VM86=y +# CONFIG_VME_BUS is not set +# CONFIG_VMSPLIT_1G is not set +# CONFIG_VMSPLIT_2G is not set +CONFIG_VMSPLIT_2G_OPT=y +# CONFIG_VMSPLIT_3G is not set +# CONFIG_VMSPLIT_3G_OPT is not set +# CONFIG_VMWARE_BALLOON is not set +# CONFIG_VMXNET3 is not set +CONFIG_VORTEX=m +# CONFIG_VT6655 is not set +# CONFIG_VT6656 is not set +CONFIG_VT_HW_CONSOLE_BINDING=y +# CONFIG_W35UND is not set +# CONFIG_WAKELOCK is not set +CONFIG_WEXT_PRIV=y +CONFIG_WEXT_SPY=y +# CONFIG_WINBOND_840 is not set +CONFIG_WIRELESS_EXT=y +# CONFIG_WL127X_RFKILL is not set +CONFIG_X86=y +CONFIG_X86_32=y +CONFIG_X86_32_LAZY_GS=y +# CONFIG_X86_32_NON_STANDARD is not set +CONFIG_X86_32_SMP=y +# CONFIG_X86_64 is not set +CONFIG_X86_ACPI_CPUFREQ=y +# CONFIG_X86_ANCIENT_MCE is not set +# CONFIG_X86_BIGSMP is not set +CONFIG_X86_BOOTPARAM_MEMORY_CORRUPTION_CHECK=y +CONFIG_X86_BSWAP=y +CONFIG_X86_CHECK_BIOS_CORRUPTION=y +CONFIG_X86_CMOV=y +CONFIG_X86_CMPXCHG=y +CONFIG_X86_CMPXCHG64=y +CONFIG_X86_CPU=y +# CONFIG_X86_CPUFREQ_NFORCE2 is not set +CONFIG_X86_CPUID=y +CONFIG_X86_DEBUGCTLMSR=y +# CONFIG_X86_DECODER_SELFTEST is not set +# CONFIG_X86_ELAN is not set +CONFIG_X86_EXTENDED_PLATFORM=y +# CONFIG_X86_E_POWERSAVER is not set +# CONFIG_X86_GENERIC is not set +# CONFIG_X86_GX_SUSPMOD is not set +CONFIG_X86_HT=y +CONFIG_X86_INTERNODE_CACHE_SHIFT=5 +CONFIG_X86_INVLPG=y +CONFIG_X86_IO_APIC=y +CONFIG_X86_L1_CACHE_SHIFT=5 +CONFIG_X86_LOCAL_APIC=y +# CONFIG_X86_LONGHAUL is not set +# CONFIG_X86_LONGRUN is not set +CONFIG_X86_MCE=y +# CONFIG_X86_MCE_AMD is not set +# CONFIG_X86_MCE_INJECT is not set +# CONFIG_X86_MCE_INTEL is not set +CONFIG_X86_MINIMUM_CPU_FAMILY=5 +CONFIG_X86_MPPARSE=y +# CONFIG_X86_MRST is not set +CONFIG_X86_MSR=y +# CONFIG_X86_P4_CLOCKMOD is not set +# CONFIG_X86_PAE is not set +CONFIG_X86_PAT=y +# CONFIG_X86_PCC_CPUFREQ is not set +CONFIG_X86_PLATFORM_DEVICES=y +CONFIG_X86_PM_TIMER=y +CONFIG_X86_POPAD_OK=y +# CONFIG_X86_POWERNOW_K6 is not set +# CONFIG_X86_POWERNOW_K7 is not set +# CONFIG_X86_POWERNOW_K8 is not set +# CONFIG_X86_PPRO_FENCE is not set +# CONFIG_X86_PTDUMP is not set +# CONFIG_X86_RDC321X is not set +# CONFIG_X86_REBOOTFIXUPS is not set +CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS=y +CONFIG_X86_RESERVE_LOW_64K=y +# CONFIG_X86_SPEEDSTEP_CENTRINO is not set +# CONFIG_X86_SPEEDSTEP_ICH is not set +# CONFIG_X86_SPEEDSTEP_LIB is not set +# CONFIG_X86_SPEEDSTEP_SMI is not set +CONFIG_X86_TRAMPOLINE=y +CONFIG_X86_TSC=y +CONFIG_X86_USE_PPRO_CHECKSUM=y +CONFIG_X86_VERBOSE_BOOTUP=y +CONFIG_X86_WP_WORKS_OK=y +CONFIG_X86_XADD=y +CONFIG_XFRM=y +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_XFRM_SUB_POLICY is not set +CONFIG_XFRM_USER=y +CONFIG_YELLOWFIN=m +CONFIG_ZD1211RW=m +# CONFIG_ZD1211RW_DEBUG is not set +CONFIG_ZISOFS=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_ZONE_DMA=y +# CONFIG_ZONE_DMA32 is not set +CONFIG_ZONE_DMA_FLAG=1 diff --git a/chromeos/config/i386/config.flavour.chromeos-intel-menlow b/chromeos/config/i386/config.flavour.chromeos-intel-menlow new file mode 100644 index 00000000000..8d56ac51cff --- /dev/null +++ b/chromeos/config/i386/config.flavour.chromeos-intel-menlow @@ -0,0 +1,9 @@ +# +# Config options generated by splitconfig +# +# CONFIG_TCG_ATMEL is not set +# CONFIG_TCG_INFINEON is not set +# CONFIG_TCG_NSC is not set +# CONFIG_TCG_ST_I2C is not set +CONFIG_TCG_TIS=m +CONFIG_TCG_TPM=m diff --git a/chromeos/config/i386/config.flavour.chromium-i386 b/chromeos/config/i386/config.flavour.chromium-i386 new file mode 100644 index 00000000000..8a7ff62836b --- /dev/null +++ b/chromeos/config/i386/config.flavour.chromium-i386 @@ -0,0 +1,4 @@ +# +# Config options generated by splitconfig +# +# CONFIG_TCG_TPM is not set diff --git a/chromeos/config/x86_64/config.common.x86_64 b/chromeos/config/x86_64/config.common.x86_64 new file mode 100644 index 00000000000..019f21ecf25 --- /dev/null +++ b/chromeos/config/x86_64/config.common.x86_64 @@ -0,0 +1,2160 @@ +# +# Config options generated by splitconfig +# +CONFIG_64BIT=y +CONFIG_8139CP=m +CONFIG_8139TOO=m +CONFIG_8139TOO_8129=y +CONFIG_8139TOO_PIO=y +CONFIG_8139TOO_TUNE_TWISTER=y +CONFIG_8139_OLD_RX_RESET=y +CONFIG_ACENIC=m +# CONFIG_ACENIC_OMIT_TIGON_I is not set +CONFIG_ACERHDF=y +CONFIG_ACER_WMI=m +# CONFIG_ACORN_PARTITION is not set +CONFIG_ACPI=y +CONFIG_ACPI_AC=y +# CONFIG_ACPI_APEI is not set +# CONFIG_ACPI_ASUS is not set +CONFIG_ACPI_BATTERY=y +CONFIG_ACPI_BLACKLIST_YEAR=0 +CONFIG_ACPI_BUTTON=y +# CONFIG_ACPI_CMPC is not set +CONFIG_ACPI_CONTAINER=y +# CONFIG_ACPI_CUSTOM_DSDT is not set +# CONFIG_ACPI_DEBUG is not set +CONFIG_ACPI_DOCK=y +CONFIG_ACPI_FAN=y +# CONFIG_ACPI_HED is not set +CONFIG_ACPI_HOTPLUG_CPU=y +# CONFIG_ACPI_PCI_SLOT is not set +# CONFIG_ACPI_POWER_METER is not set +CONFIG_ACPI_PROCESSOR=y +# CONFIG_ACPI_PROCESSOR_AGGREGATOR is not set +CONFIG_ACPI_PROCFS=y +CONFIG_ACPI_PROCFS_POWER=y +CONFIG_ACPI_PROC_EVENT=y +# CONFIG_ACPI_SBS is not set +CONFIG_ACPI_SLEEP=y +CONFIG_ACPI_SYSFS_POWER=y +CONFIG_ACPI_THERMAL=y +CONFIG_ACPI_TOSHIBA=m +CONFIG_ACPI_VIDEO=y +CONFIG_ACPI_WMI=m +CONFIG_ADAPTEC_STARFIRE=m +# CONFIG_ADIS16209 is not set +# CONFIG_ADIS16220 is not set +# CONFIG_ADIS16240 is not set +# CONFIG_ADIS16255 is not set +# CONFIG_ADIS16260 is not set +# CONFIG_ADIS16300 is not set +# CONFIG_ADIS16350 is not set +# CONFIG_ADIS16400 is not set +CONFIG_ADM8211=m +CONFIG_AGP=y +CONFIG_AGP_AMD64=y +CONFIG_AGP_INTEL=y +# CONFIG_AGP_SIS is not set +# CONFIG_AGP_VIA is not set +CONFIG_AIRO=m +CONFIG_AMD8111_ETH=m +# CONFIG_AMD_IOMMU is not set +CONFIG_AMIGA_PARTITION=y +# CONFIG_ANDROID is not set +CONFIG_ANDROID_PARANOID_NETWORK=y +# CONFIG_APPLICOM is not set +CONFIG_AR9170_LEDS=y +CONFIG_AR9170_USB=m +CONFIG_ARCH_CPU_PROBE_RELEASE=y +CONFIG_ARCH_DEFCONFIG="arch/x86/configs/x86_64_defconfig" +CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y +CONFIG_ARCH_HAS_CACHE_LINE_SIZE=y +CONFIG_ARCH_HAS_CPU_IDLE_WAIT=y +CONFIG_ARCH_HAS_CPU_RELAX=y +CONFIG_ARCH_HAS_DEFAULT_IDLE=y +CONFIG_ARCH_HIBERNATION_POSSIBLE=y +CONFIG_ARCH_HWEIGHT_CFLAGS="-fcall-saved-rdi -fcall-saved-rsi -fcall-saved-rdx -fcall-saved-rcx -fcall-saved-r8 -fcall-saved-r9 -fcall-saved-r10 -fcall-saved-r11" +CONFIG_ARCH_MAY_HAVE_PC_FDC=y +CONFIG_ARCH_PHYS_ADDR_T_64BIT=y +CONFIG_ARCH_POPULATES_NODE_MAP=y +CONFIG_ARCH_PROC_KCORE_TEXT=y +CONFIG_ARCH_SELECT_MEMORY_MODEL=y +CONFIG_ARCH_SPARSEMEM_DEFAULT=y +CONFIG_ARCH_SPARSEMEM_ENABLE=y +CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y +CONFIG_ARCH_SUPPORTS_MEMORY_FAILURE=y +CONFIG_ARCH_SUPPORTS_MSI=y +CONFIG_ARCH_SUPPORTS_OPTIMIZED_INLINING=y +CONFIG_ARCH_USES_PG_UNCACHED=y +CONFIG_ARCH_WANT_FRAME_POINTERS=y +# CONFIG_ARCNET is not set +# CONFIG_ASHMEM is not set +CONFIG_ASK_IP_FIB_HASH=y +CONFIG_ASUS_LAPTOP=m +# CONFIG_ASUS_OLED is not set +CONFIG_AT76C50X_USB=m +CONFIG_ATA=y +# CONFIG_ATARI_PARTITION is not set +CONFIG_ATA_ACPI=y +CONFIG_ATA_BMDMA=y +CONFIG_ATA_GENERIC=y +# CONFIG_ATA_NONSTANDARD is not set +CONFIG_ATA_PIIX=y +CONFIG_ATA_SFF=y +CONFIG_ATA_VERBOSE_ERROR=y +CONFIG_ATH5K=m +# CONFIG_ATH5K_DEBUG is not set +# CONFIG_ATH6K_LEGACY is not set +CONFIG_ATH9K=m +CONFIG_ATH9K_COMMON=m +# CONFIG_ATH9K_DEBUGFS is not set +# CONFIG_ATH9K_HTC is not set +CONFIG_ATH9K_HW=m +CONFIG_ATH_COMMON=m +# CONFIG_ATH_DEBUG is not set +CONFIG_ATL1=m +CONFIG_ATL1C=m +CONFIG_ATL1E=m +CONFIG_ATL2=m +CONFIG_ATMEL=m +CONFIG_AUDIT=y +CONFIG_AUDITSYSCALL=y +CONFIG_AUDIT_ARCH=y +CONFIG_AUDIT_TREE=y +# CONFIG_BACKLIGHT_ADP8860 is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_BACKLIGHT_GENERIC=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +# CONFIG_BACKLIGHT_MBP_NVIDIA is not set +# CONFIG_BACKLIGHT_PROGEAR is not set +# CONFIG_BACKLIGHT_SAHARA is not set +# CONFIG_BATTERY_BQ27x00 is not set +# CONFIG_BATTERY_DS2760 is not set +# CONFIG_BATTERY_DS2782 is not set +# CONFIG_BATTERY_MAX17040 is not set +CONFIG_BINARY_PRINTF=y +CONFIG_BINFMT_MISC=y +# CONFIG_BLK_CGROUP is not set +# CONFIG_BLK_CPQ_CISS_DA is not set +# CONFIG_BLK_CPQ_DA is not set +CONFIG_BLK_DEV_BSG=y +# CONFIG_BLK_DEV_CRYPTOLOOP is not set +# CONFIG_BLK_DEV_DAC960 is not set +CONFIG_BLK_DEV_DM=y +# CONFIG_BLK_DEV_DRBD is not set +# CONFIG_BLK_DEV_FD is not set +# CONFIG_BLK_DEV_HD is not set +CONFIG_BLK_DEV_LOOP=y +# CONFIG_BLK_DEV_MD is not set +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_COUNT=16 +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_BLK_DEV_SD=y +# CONFIG_BLK_DEV_SR is not set +# CONFIG_BLK_DEV_SX8 is not set +# CONFIG_BLK_DEV_UB is not set +# CONFIG_BLK_DEV_UMEM is not set +CONFIG_BLK_DEV_XIP=y +CONFIG_BLOCK_COMPAT=y +CONFIG_BNX2=m +# CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set +CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0 +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +CONFIG_BOUNCE=y +# CONFIG_BROADCOM_PHY is not set +CONFIG_BSD_DISKLABEL=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_BT=m +# CONFIG_BT_ATH3K is not set +CONFIG_BT_BNEP=m +# CONFIG_BT_BNEP_MC_FILTER is not set +# CONFIG_BT_BNEP_PROTO_FILTER is not set +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBTSDIO=m +CONFIG_BT_HCIBTUSB=m +# CONFIG_BT_HCIUART is not set +CONFIG_BT_HCIVHCI=m +CONFIG_BT_HIDP=m +CONFIG_BT_L2CAP=m +# CONFIG_BT_L2CAP_EXT_FEATURES is not set +# CONFIG_BT_MRVL is not set +CONFIG_BT_RFCOMM=m +# CONFIG_BT_RFCOMM_TTY is not set +CONFIG_BT_SCO=m +# CONFIG_CALGARY_IOMMU is not set +CONFIG_CAN_PM_TRACE=y +CONFIG_CASSINI=m +CONFIG_CB710_CORE=m +CONFIG_CB710_DEBUG=y +CONFIG_CB710_DEBUG_ASSUMPTIONS=y +# CONFIG_CC_STACKPROTECTOR is not set +CONFIG_CFG80211=m +CONFIG_CFG80211_DEBUGFS=y +CONFIG_CGROUPS=y +CONFIG_CGROUP_CPUACCT=y +# CONFIG_CGROUP_DEBUG is not set +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_MEM_RES_CTLR=y +CONFIG_CGROUP_NS=y +CONFIG_CGROUP_SCHED=y +CONFIG_CHECK_SIGNATURE=y +# CONFIG_CHR_DEV_OSST is not set +# CONFIG_CHR_DEV_SCH is not set +CONFIG_CHR_DEV_SG=y +# CONFIG_CHR_DEV_ST is not set +# CONFIG_CICADA_PHY is not set +CONFIG_CLOCKSOURCE_WATCHDOG=y +# CONFIG_CMDLINE_BOOL is not set +# CONFIG_CNIC is not set +CONFIG_COMPAL_LAPTOP=m +CONFIG_COMPAT=y +CONFIG_COMPAT_BINFMT_ELF=y +# CONFIG_COMPAT_BRK is not set +CONFIG_COMPAT_FOR_U64_ALIGNMENT=y +CONFIG_COMPAT_NETLINK_MESSAGES=y +# CONFIG_COMPAT_VDSO is not set +# CONFIG_COMPUTONE is not set +CONFIG_CONNECTOR=y +CONFIG_CONTEXT_SWITCH_TRACER=y +CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y +# CONFIG_CPA_DEBUG is not set +CONFIG_CPUSETS=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_DEBUG=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +CONFIG_CPU_FREQ_TABLE=y +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_CPU_IDLE_GOV_MENU=y +# CONFIG_CPU_NOTIFIER_ERROR_INJECT is not set +CONFIG_CPU_SUP_AMD=y +CONFIG_CPU_SUP_CENTAUR=y +CONFIG_CPU_SUP_INTEL=y +CONFIG_CRAMFS=y +# CONFIG_CRASH_DUMP is not set +CONFIG_CRC16=y +CONFIG_CRC7=m +CONFIG_CRC_ITU_T=m +CONFIG_CRC_T10DIF=m +CONFIG_CRYPTO=y +CONFIG_CRYPTO_AEAD=y +CONFIG_CRYPTO_AEAD2=y +CONFIG_CRYPTO_AES=y +# CONFIG_CRYPTO_AES_NI_INTEL is not set +# CONFIG_CRYPTO_AES_X86_64 is not set +CONFIG_CRYPTO_ALGAPI=y +CONFIG_CRYPTO_ALGAPI2=y +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_ARC4=y +CONFIG_CRYPTO_AUTHENC=y +CONFIG_CRYPTO_BLKCIPHER=y +CONFIG_CRYPTO_BLKCIPHER2=y +CONFIG_CRYPTO_CBC=y +CONFIG_CRYPTO_CRC32C=m +# CONFIG_CRYPTO_CRC32C_INTEL is not set +CONFIG_CRYPTO_DES=y +# CONFIG_CRYPTO_DEV_HIFN_795X is not set +CONFIG_CRYPTO_DEV_PADLOCK=y +CONFIG_CRYPTO_DEV_PADLOCK_AES=m +CONFIG_CRYPTO_DEV_PADLOCK_SHA=m +CONFIG_CRYPTO_ECB=y +# CONFIG_CRYPTO_GHASH_CLMUL_NI_INTEL is not set +CONFIG_CRYPTO_HASH=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_MANAGER=y +CONFIG_CRYPTO_MANAGER2=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_MICHAEL_MIC=m +CONFIG_CRYPTO_PCOMP=y +# CONFIG_CRYPTO_PCRYPT is not set +CONFIG_CRYPTO_RNG2=y +# CONFIG_CRYPTO_SALSA20_X86_64 is not set +CONFIG_CRYPTO_SHA1=y +CONFIG_CRYPTO_SHA256=m +# CONFIG_CRYPTO_TWOFISH_X86_64 is not set +CONFIG_CRYPTO_WORKQUEUE=y +# CONFIG_CRYSTALHD is not set +# CONFIG_CS5535_MFGPT is not set +# CONFIG_CUSE is not set +# CONFIG_CYCLADES is not set +# CONFIG_DAB is not set +# CONFIG_DAVICOM_PHY is not set +# CONFIG_DCDBAS is not set +# CONFIG_DE2104X is not set +# CONFIG_DE4X5 is not set +CONFIG_DEBUG_BOOT_PARAMS=y +# CONFIG_DEBUG_BUGVERBOSE is not set +CONFIG_DEBUG_DEVRES=y +CONFIG_DEBUG_GPIO=y +# CONFIG_DEBUG_INFO is not set +# CONFIG_DEBUG_MEMORY_INIT is not set +# CONFIG_DEBUG_MUTEXES is not set +CONFIG_DEBUG_NX_TEST=m +# CONFIG_DEBUG_PAGEALLOC is not set +# CONFIG_DEBUG_PERF_USE_VMALLOC is not set +# CONFIG_DEBUG_PER_CPU_MAPS is not set +CONFIG_DEBUG_RODATA=y +# CONFIG_DEBUG_RODATA_TEST is not set +# CONFIG_DEBUG_SPINLOCK_SLEEP is not set +CONFIG_DEBUG_STACKOVERFLOW=y +CONFIG_DEBUG_STACK_USAGE=y +# CONFIG_DEBUG_STRICT_USER_COPY_CHECKS is not set +# CONFIG_DEBUG_VIRTUAL is not set +# CONFIG_DEFAULT_BIC is not set +CONFIG_DEFAULT_CFQ=y +CONFIG_DEFAULT_CUBIC=y +# CONFIG_DEFAULT_HTCP is not set +# CONFIG_DEFAULT_HYBLA is not set +CONFIG_DEFAULT_IOSCHED="cfq" +CONFIG_DEFAULT_IO_DELAY_TYPE=0 +# CONFIG_DEFAULT_NOOP is not set +# CONFIG_DEFAULT_RENO is not set +CONFIG_DEFAULT_SECURITY="tomoyo" +# CONFIG_DEFAULT_SECURITY_DAC is not set +CONFIG_DEFAULT_SECURITY_TOMOYO=y +# CONFIG_DEFAULT_VEGAS is not set +# CONFIG_DEFAULT_VENO is not set +# CONFIG_DEFAULT_WESTWOOD is not set +# CONFIG_DEFXX is not set +# CONFIG_DELL_RBU is not set +CONFIG_DELL_WMI=m +CONFIG_DETECT_HUNG_TASK=y +CONFIG_DETECT_SOFTLOCKUP=y +CONFIG_DEVPORT=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +# CONFIG_DIGIEPCA is not set +CONFIG_DIRECT_GBPAGES=y +CONFIG_DL2K=m +# CONFIG_DM9102 is not set +# CONFIG_DMAR is not set +# CONFIG_DMA_API_DEBUG is not set +CONFIG_DMI=y +CONFIG_DMIID=y +CONFIG_DM_CRYPT=y +# CONFIG_DM_DEBUG is not set +# CONFIG_DM_DELAY is not set +# CONFIG_DM_MIRROR is not set +# CONFIG_DM_MULTIPATH is not set +# CONFIG_DM_SNAPSHOT is not set +# CONFIG_DM_UEVENT is not set +# CONFIG_DM_ZERO is not set +CONFIG_DNET=m +# CONFIG_DNOTIFY is not set +CONFIG_DRM=y +# CONFIG_DRM_I2C_CH7006 is not set +# CONFIG_DRM_I810 is not set +# CONFIG_DRM_I830 is not set +CONFIG_DRM_I915=y +CONFIG_DRM_I915_KMS=y +CONFIG_DRM_KMS_HELPER=y +# CONFIG_DRM_MGA is not set +# CONFIG_DRM_NOUVEAU is not set +# CONFIG_DRM_R128 is not set +# CONFIG_DRM_RADEON is not set +# CONFIG_DRM_SAVAGE is not set +# CONFIG_DRM_SIS is not set +# CONFIG_DRM_TDFX is not set +# CONFIG_DRM_VIA is not set +# CONFIG_DRM_VMWGFX is not set +CONFIG_DS1682=m +# CONFIG_DT3155 is not set +# CONFIG_DUMMY is not set +# CONFIG_DVB_CORE is not set +CONFIG_DYNAMIC_FTRACE=y +CONFIG_E100=m +CONFIG_E1000=m +CONFIG_E1000E=m +CONFIG_EARLY_PRINTK=y +CONFIG_EARLY_PRINTK_DBGP=y +CONFIG_ECRYPT_FS=y +# CONFIG_EDAC is not set +# CONFIG_EDD is not set +CONFIG_EEEPC_LAPTOP=m +# CONFIG_EEEPC_WMI is not set +CONFIG_EEPROM_93CX6=m +CONFIG_EEPROM_AT24=m +CONFIG_EEPROM_AT25=m +CONFIG_EEPROM_LEGACY=m +CONFIG_EFI=y +CONFIG_EFI_PARTITION=y +CONFIG_EFI_VARS=y +CONFIG_EMBEDDED=y +# CONFIG_ENABLE_WARN_DEPRECATED is not set +# CONFIG_ENC28J60 is not set +CONFIG_EPIC100=m +# CONFIG_ET131X is not set +CONFIG_ETHOC=m +CONFIG_EVENT_TRACING=y +CONFIG_EXT2_FS=y +CONFIG_EXT2_FS_POSIX_ACL=y +CONFIG_EXT2_FS_SECURITY=y +CONFIG_EXT2_FS_XATTR=y +CONFIG_EXT2_FS_XIP=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT3_FS=y +CONFIG_EXT3_FS_POSIX_ACL=y +CONFIG_EXT3_FS_SECURITY=y +CONFIG_EXT3_FS_XATTR=y +# CONFIG_EXT4_DEBUG is not set +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_EXT4_FS_XATTR=y +# CONFIG_EZX_PCAP is not set +CONFIG_FAIR_GROUP_SCHED=y +CONFIG_FAT_DEFAULT_CODEPAGE=437 +CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +CONFIG_FAT_FS=m +# CONFIG_FB_3DFX is not set +# CONFIG_FB_ARC is not set +# CONFIG_FB_ARK is not set +# CONFIG_FB_ASILIANT is not set +# CONFIG_FB_ATY is not set +# CONFIG_FB_ATY128 is not set +# CONFIG_FB_BACKLIGHT is not set +# CONFIG_FB_CARMINE is not set +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_IMAGEBLIT=y +# CONFIG_FB_CIRRUS is not set +# CONFIG_FB_CYBER2000 is not set +# CONFIG_FB_EFI is not set +# CONFIG_FB_GEODE is not set +# CONFIG_FB_HGA is not set +# CONFIG_FB_IMSTT is not set +# CONFIG_FB_KYRO is not set +# CONFIG_FB_LE80578 is not set +# CONFIG_FB_MATROX is not set +# CONFIG_FB_MODE_HELPERS is not set +# CONFIG_FB_N411 is not set +# CONFIG_FB_NEOMAGIC is not set +# CONFIG_FB_NVIDIA is not set +# CONFIG_FB_PM2 is not set +# CONFIG_FB_PM3 is not set +# CONFIG_FB_RADEON is not set +# CONFIG_FB_RIVA is not set +# CONFIG_FB_S3 is not set +# CONFIG_FB_SAVAGE is not set +# CONFIG_FB_SIS is not set +# CONFIG_FB_TILEBLITTING is not set +# CONFIG_FB_TMIO is not set +# CONFIG_FB_TRIDENT is not set +# CONFIG_FB_UDL is not set +# CONFIG_FB_UVESA is not set +# CONFIG_FB_VESA is not set +# CONFIG_FB_VGA16 is not set +# CONFIG_FB_VIA is not set +# CONFIG_FB_VOODOO1 is not set +# CONFIG_FB_VT8623 is not set +# CONFIG_FB_XGI is not set +CONFIG_FDDI=y +CONFIG_FEALNX=m +CONFIG_FIB_RULES=y +# CONFIG_FIREWIRE is not set +CONFIG_FIRMWARE_MEMMAP=y +# CONFIG_FIXED_PHY is not set +CONFIG_FIX_EARLYCON_MEM=y +# CONFIG_FLATMEM_MANUAL is not set +CONFIG_FORCEDETH=m +CONFIG_FRAME_POINTER=y +CONFIG_FRAME_WARN=2048 +CONFIG_FS_MBCACHE=y +CONFIG_FS_POSIX_ACL=y +CONFIG_FS_XIP=y +CONFIG_FTRACE_MCOUNT_RECORD=y +CONFIG_FTRACE_NMI_ENTER=y +# CONFIG_FTRACE_STARTUP_TEST is not set +# CONFIG_FTRACE_SYSCALLS is not set +CONFIG_FUJITSU_LAPTOP=m +# CONFIG_FUJITSU_LAPTOP_DEBUG is not set +CONFIG_FUNCTION_GRAPH_TRACER=y +# CONFIG_FUNCTION_PROFILER is not set +CONFIG_FUNCTION_TRACER=y +CONFIG_FUSE_FS=m +# CONFIG_FUSION is not set +CONFIG_GART_IOMMU=y +CONFIG_GENERIC_ACL=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y +CONFIG_GENERIC_CMOS_UPDATE=y +CONFIG_GENERIC_CPU=y +CONFIG_GENERIC_FIND_FIRST_BIT=y +CONFIG_GENERIC_FIND_NEXT_BIT=y +CONFIG_GENERIC_IOMAP=y +CONFIG_GENERIC_ISA_DMA=y +CONFIG_GENERIC_PENDING_IRQ=y +CONFIG_GENERIC_TIME_VSYSCALL=y +CONFIG_GENERIC_TRACER=y +# CONFIG_GOBI_USBNET is not set +CONFIG_GPIOLIB=y +# CONFIG_GPIO_ADP5588 is not set +CONFIG_GPIO_BT8XX=m +# CONFIG_GPIO_CS5535 is not set +# CONFIG_GPIO_IT8761E is not set +# CONFIG_GPIO_LANGWELL is not set +# CONFIG_GPIO_MAX7300 is not set +CONFIG_GPIO_MAX7301=m +CONFIG_GPIO_MAX730X=m +CONFIG_GPIO_MAX732X=m +# CONFIG_GPIO_MC33880 is not set +CONFIG_GPIO_MCP23S08=m +CONFIG_GPIO_PCA953X=m +CONFIG_GPIO_PCF857X=m +# CONFIG_GPIO_RDC321X is not set +# CONFIG_GPIO_SCH is not set +# CONFIG_GPIO_SX150X is not set +CONFIG_GPIO_SYSFS=y +CONFIG_HAMACHI=m +# CONFIG_HANGCHECK_TIMER is not set +CONFIG_HAPPYMEAL=m +# CONFIG_HAVE_AOUT is not set +CONFIG_HAVE_ARCH_KMEMCHECK=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_CPUMASK_OF_CPU_MAP=y +CONFIG_HAVE_DMA_API_DEBUG=y +CONFIG_HAVE_DMA_ATTRS=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_EARLY_RES=y +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y +CONFIG_HAVE_FTRACE_NMI_ENTER=y +CONFIG_HAVE_FUNCTION_GRAPH_FP_TEST=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST=y +# CONFIG_HAVE_GENERIC_DMA_COHERENT is not set +CONFIG_HAVE_HW_BREAKPOINT=y +CONFIG_HAVE_IOREMAP_PROT=y +CONFIG_HAVE_KERNEL_BZIP2=y +CONFIG_HAVE_KVM=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y +CONFIG_HAVE_MEMORY_PRESENT=y +CONFIG_HAVE_MIXED_BREAKPOINTS_REGS=y +CONFIG_HAVE_MMIOTRACE_SUPPORT=y +CONFIG_HAVE_OPTPROBES=y +CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y +CONFIG_HAVE_SETUP_PER_CPU_AREA=y +CONFIG_HAVE_SYSCALL_TRACEPOINTS=y +CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y +CONFIG_HAVE_USER_RETURN_NOTIFIER=y +CONFIG_HERMES=m +CONFIG_HERMES_CACHE_FW_ON_INIT=y +# CONFIG_HERMES_PRISM is not set +CONFIG_HIDRAW=y +# CONFIG_HID_3M_PCT is not set +# CONFIG_HID_A4TECH is not set +CONFIG_HID_APPLE=m +# CONFIG_HID_BELKIN is not set +# CONFIG_HID_CANDO is not set +# CONFIG_HID_CHERRY is not set +# CONFIG_HID_CHICONY is not set +# CONFIG_HID_CYPRESS is not set +# CONFIG_HID_DRAGONRISE is not set +# CONFIG_HID_EGALAX is not set +# CONFIG_HID_EZKEY is not set +# CONFIG_HID_GREENASIA is not set +# CONFIG_HID_GYRATION is not set +# CONFIG_HID_KENSINGTON is not set +# CONFIG_HID_KYE is not set +CONFIG_HID_LOGITECH=m +# CONFIG_HID_MAGICMOUSE is not set +CONFIG_HID_MICROSOFT=m +# CONFIG_HID_MONTEREY is not set +# CONFIG_HID_MOSART is not set +# CONFIG_HID_NTRIG is not set +# CONFIG_HID_ORTEK is not set +# CONFIG_HID_PANTHERLORD is not set +# CONFIG_HID_PETALYNX is not set +# CONFIG_HID_PICOLCD is not set +CONFIG_HID_PID=y +# CONFIG_HID_PRODIKEYS is not set +# CONFIG_HID_QUANTA is not set +# CONFIG_HID_ROCCAT is not set +# CONFIG_HID_ROCCAT_KONE is not set +# CONFIG_HID_SAMSUNG is not set +# CONFIG_HID_SMARTJOYPLUS is not set +# CONFIG_HID_SONY is not set +# CONFIG_HID_STANTUM is not set +# CONFIG_HID_SUNPLUS is not set +# CONFIG_HID_THRUSTMASTER is not set +# CONFIG_HID_TOPSEED is not set +# CONFIG_HID_TWINHAN is not set +# CONFIG_HID_WACOM is not set +# CONFIG_HID_ZEROPLUS is not set +# CONFIG_HID_ZYDACRON is not set +CONFIG_HIGH_RES_TIMERS=y +# CONFIG_HIPPI is not set +CONFIG_HOSTAP=m +CONFIG_HOSTAP_FIRMWARE=y +CONFIG_HOSTAP_FIRMWARE_NVRAM=y +CONFIG_HOSTAP_PCI=m +CONFIG_HOSTAP_PLX=m +CONFIG_HOTPLUG_CPU=y +CONFIG_HOTPLUG_PCI=y +# CONFIG_HOTPLUG_PCI_ACPI is not set +# CONFIG_HOTPLUG_PCI_CPCI is not set +# CONFIG_HOTPLUG_PCI_FAKE is not set +CONFIG_HOTPLUG_PCI_PCIE=y +# CONFIG_HOTPLUG_PCI_SHPC is not set +CONFIG_HP100=m +CONFIG_HPET=y +CONFIG_HPET_EMULATE_RTC=y +# CONFIG_HPET_MMAP is not set +CONFIG_HPET_TIMER=y +# CONFIG_HP_ILO is not set +CONFIG_HP_WMI=m +CONFIG_HT_IRQ=y +CONFIG_HUGETLBFS=y +CONFIG_HUGETLB_PAGE=y +CONFIG_HWMON=y +# CONFIG_HWMON_DEBUG_CHIP is not set +CONFIG_HWMON_VID=m +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_AMD=y +CONFIG_HW_RANDOM_INTEL=y +# CONFIG_HW_RANDOM_TIMERIOMEM is not set +CONFIG_HW_RANDOM_VIA=y +# CONFIG_HYPERV is not set +CONFIG_HZ=1000 +# CONFIG_HZ_100 is not set +CONFIG_HZ_1000=y +# CONFIG_HZ_250 is not set +# CONFIG_HZ_300 is not set +CONFIG_I2C_ALGOBIT=y +CONFIG_I2C_ALGOPCA=m +CONFIG_I2C_ALI1535=m +CONFIG_I2C_ALI1563=m +CONFIG_I2C_ALI15X3=m +CONFIG_I2C_AMD756=m +CONFIG_I2C_AMD756_S4882=m +CONFIG_I2C_AMD8111=m +CONFIG_I2C_CHARDEV=m +CONFIG_I2C_I801=m +CONFIG_I2C_ISCH=m +CONFIG_I2C_NFORCE2=m +CONFIG_I2C_NFORCE2_S4985=m +CONFIG_I2C_OCORES=m +CONFIG_I2C_PCA_PLATFORM=m +CONFIG_I2C_PIIX4=m +# CONFIG_I2C_SCMI is not set +CONFIG_I2C_SIMTEC=m +CONFIG_I2C_SIS5595=m +CONFIG_I2C_SIS630=m +CONFIG_I2C_SIS96X=m +CONFIG_I2C_STUB=m +# CONFIG_I2C_TINY_USB is not set +CONFIG_I2C_VIA=m +CONFIG_I2C_VIAPRO=m +# CONFIG_I2O is not set +# CONFIG_I7300_IDLE is not set +# CONFIG_I8K is not set +# CONFIG_IA32_AOUT is not set +CONFIG_IA32_EMULATION=y +# CONFIG_IBM_ASM is not set +# CONFIG_ICPLUS_PHY is not set +# CONFIG_IDE_PHISON is not set +# CONFIG_IEEE1394 is not set +# CONFIG_IFB is not set +CONFIG_IGB=m +CONFIG_IGBVF=m +CONFIG_IIO=m +# CONFIG_IIO_RING_BUFFER is not set +# CONFIG_IIO_TRIGGER is not set +CONFIG_IKCONFIG=m +CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 +# CONFIG_IMA is not set +CONFIG_INET6_AH=y +CONFIG_INET6_ESP=y +# CONFIG_INET6_IPCOMP is not set +# CONFIG_INET6_TUNNEL is not set +CONFIG_INET6_XFRM_MODE_BEET=y +# CONFIG_INET6_XFRM_MODE_ROUTEOPTIMIZATION is not set +CONFIG_INET6_XFRM_MODE_TRANSPORT=y +CONFIG_INET6_XFRM_MODE_TUNNEL=y +# CONFIG_INET6_XFRM_TUNNEL is not set +CONFIG_INET_LRO=y +CONFIG_INET_TUNNEL=y +# CONFIG_INFINIBAND is not set +CONFIG_INLINE_READ_UNLOCK=y +CONFIG_INLINE_READ_UNLOCK_IRQ=y +CONFIG_INLINE_SPIN_UNLOCK=y +CONFIG_INLINE_SPIN_UNLOCK_IRQ=y +CONFIG_INLINE_WRITE_UNLOCK=y +CONFIG_INLINE_WRITE_UNLOCK_IRQ=y +CONFIG_INPUT_APANEL=m +CONFIG_INPUT_ATI_REMOTE=m +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_ATLAS_BTNS=m +CONFIG_INPUT_CM109=m +# CONFIG_INPUT_EVBUG is not set +CONFIG_INPUT_FF_MEMLESS=y +CONFIG_INPUT_GPIO_ROTARY_ENCODER=m +CONFIG_INPUT_KEYSPAN_REMOTE=m +CONFIG_INPUT_MOUSE=y +# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_INPUT_PCSPKR=m +CONFIG_INPUT_POLLDEV=y +CONFIG_INPUT_POWERMATE=m +CONFIG_INPUT_SPARSEKMAP=m +CONFIG_INPUT_TABLET=y +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_INPUT_UINPUT=m +# CONFIG_INPUT_WINBOND_CIR is not set +CONFIG_INPUT_YEALINK=m +CONFIG_INSTRUCTION_DECODER=y +# CONFIG_INTEL_IDLE is not set +CONFIG_INTEL_MENLOW=m +# CONFIG_INTR_REMAP is not set +# CONFIG_IOMMU_API is not set +# CONFIG_IOMMU_DEBUG is not set +CONFIG_IOMMU_HELPER=y +# CONFIG_IOMMU_STRESS is not set +CONFIG_IOSCHED_CFQ=y +CONFIG_IOSCHED_DEADLINE=y +CONFIG_IO_DELAY_0X80=y +# CONFIG_IO_DELAY_0XED is not set +# CONFIG_IO_DELAY_NONE is not set +CONFIG_IO_DELAY_TYPE_0X80=0 +CONFIG_IO_DELAY_TYPE_0XED=1 +CONFIG_IO_DELAY_TYPE_NONE=3 +CONFIG_IO_DELAY_TYPE_UDELAY=2 +# CONFIG_IO_DELAY_UDELAY is not set +CONFIG_IP1000=m +CONFIG_IP6_NF_FILTER=y +CONFIG_IP6_NF_IPTABLES=y +CONFIG_IP6_NF_MANGLE=y +CONFIG_IP6_NF_MATCH_IPV6HEADER=y +CONFIG_IP6_NF_TARGET_LOG=y +CONFIG_IP6_NF_TARGET_REJECT=y +CONFIG_IPC_NS=y +CONFIG_IPV6=y +# CONFIG_IPV6_MIP6 is not set +# CONFIG_IPV6_MROUTE is not set +# CONFIG_IPV6_MULTIPLE_TABLES is not set +CONFIG_IPV6_NDISC_NODETYPE=y +# CONFIG_IPV6_OPTIMISTIC_DAD is not set +# CONFIG_IPV6_PRIVACY is not set +# CONFIG_IPV6_ROUTER_PREF is not set +CONFIG_IPV6_SIT=y +# CONFIG_IPV6_SIT_6RD is not set +# CONFIG_IPV6_TUNNEL is not set +CONFIG_IPW2100=m +# CONFIG_IPW2100_DEBUG is not set +CONFIG_IPW2100_MONITOR=y +CONFIG_IPW2200=m +# CONFIG_IPW2200_DEBUG is not set +CONFIG_IPW2200_MONITOR=y +CONFIG_IPW2200_PROMISCUOUS=y +CONFIG_IPW2200_QOS=y +CONFIG_IPW2200_RADIOTAP=y +CONFIG_IP_ADVANCED_ROUTER=y +# CONFIG_IP_FIB_TRIE is not set +CONFIG_IP_MROUTE=y +# CONFIG_IP_MROUTE_MULTIPLE_TABLES is not set +CONFIG_IP_MULTICAST=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_MANGLE=y +CONFIG_IP_NF_TARGET_LOG=y +CONFIG_IP_NF_TARGET_MASQUERADE=y +CONFIG_IP_NF_TARGET_REJECT=y +CONFIG_IP_NF_TARGET_ULOG=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_RARP=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +# CONFIG_IP_VS is not set +CONFIG_IR_CORE=m +# CONFIG_IR_IMON is not set +CONFIG_IR_JVC_DECODER=m +CONFIG_IR_NEC_DECODER=m +CONFIG_IR_RC5_DECODER=m +CONFIG_IR_RC6_DECODER=m +CONFIG_IR_SONY_DECODER=m +CONFIG_ISA_DMA_API=y +# CONFIG_ISCSI_IBFT_FIND is not set +# CONFIG_ISI is not set +CONFIG_ISO9660_FS=y +CONFIG_IWL3945=m +CONFIG_IWL4965=y +CONFIG_IWL5000=y +CONFIG_IWLAGN=m +CONFIG_IWLWIFI=m +# CONFIG_IWLWIFI_DEBUG is not set +# CONFIG_IWLWIFI_DEVICE_TRACING is not set +CONFIG_JBD=y +CONFIG_JBD2=y +# CONFIG_JBD2_DEBUG is not set +# CONFIG_JBD_DEBUG is not set +CONFIG_JME=m +CONFIG_JOLIET=y +CONFIG_K8_NB=y +CONFIG_KALLSYMS_ALL=y +CONFIG_KALLSYMS_EXTRA_PASS=y +CONFIG_KARMA_PARTITION=y +CONFIG_KEYBOARD_ATKBD=y +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_LKKBD=m +CONFIG_KEYBOARD_NEWTON=m +# CONFIG_KEYBOARD_QCIKBD is not set +CONFIG_KEYBOARD_STOWAWAY=m +CONFIG_KEYBOARD_SUNKBD=m +CONFIG_KEYBOARD_XTKBD=m +CONFIG_KEYS=y +CONFIG_KEYS_DEBUG_PROC_KEYS=y +CONFIG_KPROBES=y +# CONFIG_KPROBES_SANITY_TEST is not set +CONFIG_KPROBE_EVENT=y +CONFIG_KRETPROBES=y +# CONFIG_KS8851 is not set +# CONFIG_KSYM_TRACER is not set +# CONFIG_KSZ884X_PCI is not set +# CONFIG_KTIME_SCALAR is not set +# CONFIG_KXSD9 is not set +# CONFIG_LATENCYTOP is not set +CONFIG_LCD_CLASS_DEVICE=m +# CONFIG_LCD_L4F00242T03 is not set +# CONFIG_LCD_LMS283GF05 is not set +# CONFIG_LCD_LTV350QV is not set +# CONFIG_LCD_PLATFORM is not set +# CONFIG_LCD_S6E63M0 is not set +# CONFIG_LCD_TDO24M is not set +# CONFIG_LCD_VGG2432A4 is not set +# CONFIG_LDM_PARTITION is not set +# CONFIG_LEDS_ALIX2 is not set +# CONFIG_LEDS_CLEVO_MAIL is not set +# CONFIG_LEDS_DAC124S085 is not set +# CONFIG_LEDS_DELL_NETBOOKS is not set +# CONFIG_LEDS_GPIO is not set +# CONFIG_LEDS_INTEL_SS4200 is not set +# CONFIG_LEDS_TRIGGER_GPIO is not set +CONFIG_LIB80211=m +CONFIG_LIB80211_CRYPT_CCMP=m +CONFIG_LIB80211_CRYPT_TKIP=m +CONFIG_LIB80211_CRYPT_WEP=m +# CONFIG_LIB80211_DEBUG is not set +CONFIG_LIBCRC32C=m +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_DEBUG=y +# CONFIG_LIBERTAS_MESH is not set +# CONFIG_LIBERTAS_SDIO is not set +# CONFIG_LIBERTAS_SPI is not set +CONFIG_LIBERTAS_THINFIRM=m +# CONFIG_LIBERTAS_THINFIRM_DEBUG is not set +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_LIBERTAS_USB=m +CONFIG_LIBIPW=m +# CONFIG_LIBIPW_DEBUG is not set +# CONFIG_LINE6_USB is not set +# CONFIG_LIS3L02DQ is not set +# CONFIG_LOCALVERSION_AUTO is not set +# CONFIG_LOGIG940_FF is not set +# CONFIG_LOGIRUMBLEPAD2_FF is not set +# CONFIG_LOGITECH_FF is not set +CONFIG_LOG_BUF_SHIFT=18 +# CONFIG_LSI_ET1011C_PHY is not set +# CONFIG_LXT_PHY is not set +# CONFIG_M386 is not set +# CONFIG_M486 is not set +# CONFIG_M586 is not set +# CONFIG_M586MMX is not set +# CONFIG_M586TSC is not set +# CONFIG_M686 is not set +CONFIG_MAC80211=m +CONFIG_MAC80211_DEBUGFS=y +CONFIG_MAC80211_HWSIM=m +# CONFIG_MAC80211_RC_PID is not set +# CONFIG_MACINTOSH_DRIVERS is not set +CONFIG_MAC_PARTITION=y +# CONFIG_MARVELL_PHY is not set +# CONFIG_MATOM is not set +# CONFIG_MAX1363 is not set +# CONFIG_MAXSMP is not set +# CONFIG_MCORE2 is not set +# CONFIG_MCRUSOE is not set +# CONFIG_MCYRIXIII is not set +CONFIG_MD=y +# CONFIG_MDIO_BITBANG is not set +# CONFIG_MEDIA_ATTACH is not set +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_TUNER=m +CONFIG_MEDIA_TUNER_CUSTOMISE=y +CONFIG_MEDIA_TUNER_MAX2165=m +# CONFIG_MEDIA_TUNER_MC44S803 is not set +# CONFIG_MEDIA_TUNER_MT2060 is not set +# CONFIG_MEDIA_TUNER_MT20XX is not set +# CONFIG_MEDIA_TUNER_MT2131 is not set +# CONFIG_MEDIA_TUNER_MT2266 is not set +# CONFIG_MEDIA_TUNER_MXL5005S is not set +# CONFIG_MEDIA_TUNER_MXL5007T is not set +# CONFIG_MEDIA_TUNER_QT1010 is not set +# CONFIG_MEDIA_TUNER_SIMPLE is not set +# CONFIG_MEDIA_TUNER_TDA18271 is not set +# CONFIG_MEDIA_TUNER_TDA827X is not set +# CONFIG_MEDIA_TUNER_TDA8290 is not set +# CONFIG_MEDIA_TUNER_TDA9887 is not set +# CONFIG_MEDIA_TUNER_TEA5761 is not set +# CONFIG_MEDIA_TUNER_TEA5767 is not set +# CONFIG_MEDIA_TUNER_XC2028 is not set +# CONFIG_MEDIA_TUNER_XC5000 is not set +# CONFIG_MEFFICEON is not set +# CONFIG_MEMORY_FAILURE is not set +# CONFIG_MEMORY_HOTPLUG is not set +# CONFIG_MEMTEST is not set +CONFIG_MFD_CORE=m +# CONFIG_MFD_MC13783 is not set +# CONFIG_MGEODEGX1 is not set +# CONFIG_MGEODE_LX is not set +# CONFIG_MICREL_PHY is not set +CONFIG_MICROCODE=y +CONFIG_MICROCODE_AMD=y +CONFIG_MICROCODE_INTEL=y +CONFIG_MICROCODE_OLD_INTERFACE=y +CONFIG_MINIX_SUBPARTITION=y +# CONFIG_MK6 is not set +# CONFIG_MK7 is not set +# CONFIG_MK8 is not set +CONFIG_MMC=m +CONFIG_MMC_BLOCK=m +CONFIG_MMC_CB710=m +# CONFIG_MMC_PARANOID_SD_INIT is not set +# CONFIG_MMC_RICOH_MMC is not set +CONFIG_MMC_SDHCI=m +CONFIG_MMC_SDHCI_PCI=m +# CONFIG_MMC_SDHCI_PLTFM is not set +# CONFIG_MMC_SPI is not set +# CONFIG_MMC_TIFM_SD is not set +# CONFIG_MMC_UNSAFE_RESUME is not set +# CONFIG_MMC_VIA_SDMMC is not set +# CONFIG_MMC_WBSD is not set +# CONFIG_MMIOTRACE is not set +CONFIG_MM_OWNER=y +CONFIG_MOUSE_APPLETOUCH=m +CONFIG_MOUSE_BCM5974=m +CONFIG_MOUSE_GPIO=m +CONFIG_MOUSE_PS2=y +CONFIG_MOUSE_PS2_ALPS=y +# CONFIG_MOUSE_PS2_ELANTECH is not set +CONFIG_MOUSE_PS2_LIFEBOOK=y +CONFIG_MOUSE_PS2_LOGIPS2PP=y +# CONFIG_MOUSE_PS2_SENTELIC is not set +CONFIG_MOUSE_PS2_SYNAPTICS=y +# CONFIG_MOUSE_PS2_TOUCHKIT is not set +CONFIG_MOUSE_PS2_TRACKPOINT=y +# CONFIG_MOUSE_QCITP is not set +CONFIG_MOUSE_SERIAL=m +# CONFIG_MOUSE_SYNAPTICS_I2C is not set +CONFIG_MOUSE_VSXXXAA=m +# CONFIG_MOXA_INTELLIO is not set +# CONFIG_MOXA_SMARTIO is not set +# CONFIG_MPENTIUM4 is not set +# CONFIG_MPENTIUMII is not set +# CONFIG_MPENTIUMIII is not set +# CONFIG_MPENTIUMM is not set +# CONFIG_MPSC is not set +CONFIG_MSDOS_FS=m +CONFIG_MSI_LAPTOP=m +# CONFIG_MSI_WMI is not set +# CONFIG_MTD is not set +CONFIG_MTRR=y +# CONFIG_MTRR_SANITIZER is not set +CONFIG_MUTEX_SPIN_ON_OWNER=y +# CONFIG_MVIAC3_2 is not set +# CONFIG_MVIAC7 is not set +# CONFIG_MWAVE is not set +# CONFIG_MWINCHIP3D is not set +# CONFIG_MWINCHIPC6 is not set +CONFIG_MWL8K=m +# CONFIG_NATIONAL_PHY is not set +CONFIG_NATSEMI=m +CONFIG_NE2K_PCI=m +CONFIG_NEED_DMA_MAP_STATE=y +CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y +CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y +CONFIG_NEED_SG_DMA_LENGTH=y +CONFIG_NETCONSOLE=y +# CONFIG_NETCONSOLE_DYNAMIC is not set +CONFIG_NETDEV_1000=y +# CONFIG_NETDEV_10000 is not set +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_ADVANCED is not set +# CONFIG_NETFILTER_DEBUG is not set +CONFIG_NETFILTER_NETLINK=y +CONFIG_NETFILTER_NETLINK_LOG=y +CONFIG_NETFILTER_XTABLES=y +CONFIG_NETFILTER_XT_MARK=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_POLICY=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=y +CONFIG_NETFILTER_XT_TARGET_NFLOG=y +CONFIG_NETFILTER_XT_TARGET_SECMARK=y +CONFIG_NETFILTER_XT_TARGET_TCPMSS=y +CONFIG_NETLABEL=y +CONFIG_NETPOLL=y +# CONFIG_NETPOLL_TRAP is not set +# CONFIG_NETWORK_FILESYSTEMS is not set +CONFIG_NETWORK_SECMARK=y +# CONFIG_NET_ACT_GACT is not set +# CONFIG_NET_ACT_IPT is not set +# CONFIG_NET_ACT_MIRRED is not set +# CONFIG_NET_ACT_NAT is not set +# CONFIG_NET_ACT_PEDIT is not set +# CONFIG_NET_ACT_POLICE is not set +# CONFIG_NET_ACT_SIMP is not set +# CONFIG_NET_ACT_SKBEDIT is not set +CONFIG_NET_CLS=y +CONFIG_NET_CLS_ACT=y +# CONFIG_NET_CLS_BASIC is not set +# CONFIG_NET_CLS_CGROUP is not set +# CONFIG_NET_CLS_FLOW is not set +# CONFIG_NET_CLS_FW is not set +# CONFIG_NET_CLS_ROUTE4 is not set +# CONFIG_NET_CLS_RSVP is not set +# CONFIG_NET_CLS_RSVP6 is not set +# CONFIG_NET_CLS_TCINDEX is not set +# CONFIG_NET_CLS_U32 is not set +# CONFIG_NET_DROP_MONITOR is not set +CONFIG_NET_EMATCH=y +# CONFIG_NET_EMATCH_CMP is not set +# CONFIG_NET_EMATCH_META is not set +# CONFIG_NET_EMATCH_NBYTE is not set +CONFIG_NET_EMATCH_STACK=32 +# CONFIG_NET_EMATCH_TEXT is not set +# CONFIG_NET_EMATCH_U32 is not set +# CONFIG_NET_FC is not set +CONFIG_NET_NS=y +CONFIG_NET_PCI=y +CONFIG_NET_POLL_CONTROLLER=y +# CONFIG_NET_SB1000 is not set +CONFIG_NET_SCHED=y +# CONFIG_NET_SCH_CBQ is not set +# CONFIG_NET_SCH_DRR is not set +# CONFIG_NET_SCH_DSMARK is not set +CONFIG_NET_SCH_FIFO=y +# CONFIG_NET_SCH_GRED is not set +# CONFIG_NET_SCH_HFSC is not set +# CONFIG_NET_SCH_HTB is not set +# CONFIG_NET_SCH_INGRESS is not set +# CONFIG_NET_SCH_MULTIQ is not set +# CONFIG_NET_SCH_NETEM is not set +# CONFIG_NET_SCH_PRIO is not set +# CONFIG_NET_SCH_RED is not set +# CONFIG_NET_SCH_SFQ is not set +# CONFIG_NET_SCH_TBF is not set +# CONFIG_NET_SCH_TEQL is not set +# CONFIG_NET_TCPPROBE is not set +CONFIG_NET_TULIP=y +CONFIG_NET_VENDOR_3COM=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CONNTRACK_FTP=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_NF_CONNTRACK_IPV6=y +CONFIG_NF_CONNTRACK_IRC=y +CONFIG_NF_CONNTRACK_PROC_COMPAT=y +CONFIG_NF_CONNTRACK_SECMARK=y +CONFIG_NF_CONNTRACK_SIP=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NF_DEFRAG_IPV4=y +CONFIG_NF_NAT=y +# CONFIG_NF_NAT_AMANDA is not set +CONFIG_NF_NAT_FTP=y +# CONFIG_NF_NAT_H323 is not set +CONFIG_NF_NAT_IRC=y +CONFIG_NF_NAT_NEEDED=y +# CONFIG_NF_NAT_PPTP is not set +CONFIG_NF_NAT_SIP=y +# CONFIG_NF_NAT_TFTP is not set +CONFIG_NLS=y +CONFIG_NLS_ASCII=y +# CONFIG_NLS_CODEPAGE_1250 is not set +# CONFIG_NLS_CODEPAGE_1251 is not set +CONFIG_NLS_CODEPAGE_437=y +# CONFIG_NLS_CODEPAGE_737 is not set +# CONFIG_NLS_CODEPAGE_775 is not set +# CONFIG_NLS_CODEPAGE_850 is not set +# CONFIG_NLS_CODEPAGE_852 is not set +# CONFIG_NLS_CODEPAGE_855 is not set +# CONFIG_NLS_CODEPAGE_857 is not set +# CONFIG_NLS_CODEPAGE_860 is not set +# CONFIG_NLS_CODEPAGE_861 is not set +# CONFIG_NLS_CODEPAGE_862 is not set +# CONFIG_NLS_CODEPAGE_863 is not set +# CONFIG_NLS_CODEPAGE_864 is not set +# CONFIG_NLS_CODEPAGE_865 is not set +# CONFIG_NLS_CODEPAGE_866 is not set +# CONFIG_NLS_CODEPAGE_869 is not set +# CONFIG_NLS_CODEPAGE_874 is not set +# CONFIG_NLS_CODEPAGE_932 is not set +# CONFIG_NLS_CODEPAGE_936 is not set +# CONFIG_NLS_CODEPAGE_949 is not set +# CONFIG_NLS_CODEPAGE_950 is not set +CONFIG_NLS_DEFAULT="utf8" +CONFIG_NLS_ISO8859_1=y +# CONFIG_NLS_ISO8859_13 is not set +# CONFIG_NLS_ISO8859_14 is not set +# CONFIG_NLS_ISO8859_15 is not set +# CONFIG_NLS_ISO8859_2 is not set +# CONFIG_NLS_ISO8859_3 is not set +# CONFIG_NLS_ISO8859_4 is not set +# CONFIG_NLS_ISO8859_5 is not set +# CONFIG_NLS_ISO8859_6 is not set +# CONFIG_NLS_ISO8859_7 is not set +# CONFIG_NLS_ISO8859_8 is not set +# CONFIG_NLS_ISO8859_9 is not set +# CONFIG_NLS_KOI8_R is not set +# CONFIG_NLS_KOI8_U is not set +CONFIG_NLS_UTF8=y +CONFIG_NOP_TRACER=y +# CONFIG_NOP_USB_XCEIV is not set +CONFIG_NORTEL_HERMES=m +# CONFIG_NOZOMI is not set +CONFIG_NO_BOOTMEM=y +CONFIG_NO_HZ=y +CONFIG_NR_CPUS=4 +CONFIG_NS83820=m +# CONFIG_NUMA is not set +CONFIG_NVRAM=y +# CONFIG_N_HDLC is not set +# CONFIG_OPROFILE is not set +CONFIG_OPTIMIZE_INLINING=y +CONFIG_OPTPROBES=y +# CONFIG_ORINOCO_USB is not set +CONFIG_OSF_PARTITION=y +# CONFIG_OTUS is not set +CONFIG_OUTPUT_FORMAT="elf64-x86-64" +CONFIG_P54_COMMON=m +CONFIG_P54_LEDS=y +CONFIG_P54_PCI=m +# CONFIG_P54_SPI is not set +CONFIG_P54_USB=m +CONFIG_PACKET=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_PANASONIC_LAPTOP=m +# CONFIG_PARAVIRT_GUEST is not set +CONFIG_PARTITION_ADVANCED=y +# CONFIG_PATA_ACPI is not set +# CONFIG_PATA_ALI is not set +# CONFIG_PATA_AMD is not set +# CONFIG_PATA_ARTOP is not set +# CONFIG_PATA_ATIIXP is not set +# CONFIG_PATA_ATP867X is not set +# CONFIG_PATA_CMD640_PCI is not set +# CONFIG_PATA_CMD64X is not set +# CONFIG_PATA_CS5520 is not set +# CONFIG_PATA_CS5530 is not set +# CONFIG_PATA_CYPRESS is not set +# CONFIG_PATA_EFAR is not set +# CONFIG_PATA_HPT366 is not set +# CONFIG_PATA_HPT37X is not set +# CONFIG_PATA_HPT3X2N is not set +# CONFIG_PATA_HPT3X3 is not set +# CONFIG_PATA_IT8213 is not set +# CONFIG_PATA_IT821X is not set +# CONFIG_PATA_JMICRON is not set +# CONFIG_PATA_LEGACY is not set +# CONFIG_PATA_MARVELL is not set +CONFIG_PATA_MPIIX=y +# CONFIG_PATA_NETCELL is not set +# CONFIG_PATA_NINJA32 is not set +# CONFIG_PATA_NS87410 is not set +# CONFIG_PATA_NS87415 is not set +# CONFIG_PATA_OLDPIIX is not set +# CONFIG_PATA_OPTI is not set +# CONFIG_PATA_OPTIDMA is not set +# CONFIG_PATA_PDC2027X is not set +# CONFIG_PATA_PDC_OLD is not set +# CONFIG_PATA_PLATFORM is not set +# CONFIG_PATA_RADISYS is not set +# CONFIG_PATA_RDC is not set +# CONFIG_PATA_RZ1000 is not set +# CONFIG_PATA_SC1200 is not set +CONFIG_PATA_SCH=y +# CONFIG_PATA_SERVERWORKS is not set +# CONFIG_PATA_SIL680 is not set +# CONFIG_PATA_SIS is not set +# CONFIG_PATA_TOSHIBA is not set +# CONFIG_PATA_TRIFLEX is not set +# CONFIG_PATA_VIA is not set +# CONFIG_PATA_WINBOND is not set +# CONFIG_PC8736x_GPIO is not set +CONFIG_PCI=y +CONFIG_PCIEAER=y +# CONFIG_PCIEAER_INJECT is not set +CONFIG_PCIEASPM=y +# CONFIG_PCIEASPM_DEBUG is not set +CONFIG_PCIEPORTBUS=y +# CONFIG_PCIE_ECRC is not set +CONFIG_PCI_ATMEL=m +# CONFIG_PCI_CNB20LE_QUIRK is not set +# CONFIG_PCI_DEBUG is not set +CONFIG_PCI_DIRECT=y +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_IOAPIC=y +# CONFIG_PCI_IOV is not set +CONFIG_PCI_MMCONFIG=y +CONFIG_PCI_MSI=y +CONFIG_PCI_QUIRKS=y +# CONFIG_PCI_STUB is not set +CONFIG_PCNET32=m +CONFIG_PCSPKR_PLATFORM=y +# CONFIG_PDA_POWER is not set +# CONFIG_PDC_ADMA is not set +CONFIG_PERF_EVENTS=y +# CONFIG_PHANTOM is not set +CONFIG_PHYLIB=y +CONFIG_PHYSICAL_ALIGN=0x1000000 +CONFIG_PHYSICAL_START=0x1000000 +CONFIG_PHYS_ADDR_T_64BIT=y +CONFIG_PID_NS=y +CONFIG_PLX_HERMES=m +# CONFIG_PM_ADVANCED_DEBUG is not set +CONFIG_PM_DEBUG=y +CONFIG_PM_SLEEP_SMP=y +# CONFIG_PM_TEST_SUSPEND is not set +CONFIG_PM_TRACE=y +CONFIG_PM_TRACE_RTC=y +# CONFIG_PM_VERBOSE is not set +CONFIG_PNP=y +CONFIG_PNPACPI=y +CONFIG_PNP_DEBUG_MESSAGES=y +CONFIG_POSIX_MQUEUE=y +CONFIG_POSIX_MQUEUE_SYSCTL=y +CONFIG_POWER_SUPPLY=y +# CONFIG_POWER_SUPPLY_DEBUG is not set +CONFIG_PPP=y +# CONFIG_PPPOE is not set +# CONFIG_PPPOLAC is not set +# CONFIG_PPPOPNS is not set +CONFIG_PPP_ASYNC=y +CONFIG_PPP_BSDCOMP=y +CONFIG_PPP_DEFLATE=y +CONFIG_PPP_FILTER=y +# CONFIG_PPP_MPPE is not set +# CONFIG_PPP_MULTILINK is not set +CONFIG_PPP_SYNC_TTY=y +# CONFIG_PREEMPT is not set +CONFIG_PREEMPT_VOLUNTARY=y +CONFIG_PRINTK_TIME=y +# CONFIG_PRISM2_USB is not set +CONFIG_PRISM54=m +# CONFIG_PROCESSOR_SELECT is not set +CONFIG_PROC_EVENTS=y +CONFIG_PROC_KCORE=y +CONFIG_PROC_PID_CPUSET=y +CONFIG_PROFILING=y +CONFIG_PROVIDE_OHCI1394_DMA_INIT=y +CONFIG_QLA3XXX=m +# CONFIG_QSEMI_PHY is not set +CONFIG_R6040=m +CONFIG_R8169=m +# CONFIG_R8187SE is not set +# CONFIG_RADIO_ADAPTERS is not set +# CONFIG_RAR_REGISTER is not set +# CONFIG_RCU_FAST_NO_HZ is not set +CONFIG_RC_MAP=m +# CONFIG_RD_BZIP2 is not set +# CONFIG_RD_LZMA is not set +# CONFIG_RD_LZO is not set +# CONFIG_REALTEK_PHY is not set +# CONFIG_REGULATOR is not set +CONFIG_RELAY=y +# CONFIG_RELOCATABLE is not set +CONFIG_RESOURCE_COUNTERS=y +CONFIG_RFKILL=y +# CONFIG_RFKILL_INPUT is not set +CONFIG_RFKILL_LEDS=y +CONFIG_RFKILL_PM=y +# CONFIG_RISCOM8 is not set +# CONFIG_ROCKETPORT is not set +# CONFIG_ROMFS_FS is not set +CONFIG_RPS=y +# CONFIG_RT2400PCI is not set +# CONFIG_RT2500PCI is not set +# CONFIG_RT2500USB is not set +# CONFIG_RT2800PCI is not set +CONFIG_RT2800PCI_PCI=y +# CONFIG_RT2800USB is not set +# CONFIG_RT2860 is not set +# CONFIG_RT2870 is not set +CONFIG_RT2X00=m +# CONFIG_RT61PCI is not set +# CONFIG_RT73USB is not set +CONFIG_RTC_CLASS=y +# CONFIG_RTC_DEBUG is not set +# CONFIG_RTC_DRV_BQ32K is not set +CONFIG_RTC_DRV_BQ4802=m +CONFIG_RTC_DRV_CMOS=m +CONFIG_RTC_DRV_DS1286=m +CONFIG_RTC_DRV_DS1305=m +CONFIG_RTC_DRV_DS1307=m +CONFIG_RTC_DRV_DS1374=m +CONFIG_RTC_DRV_DS1390=m +CONFIG_RTC_DRV_DS1511=m +CONFIG_RTC_DRV_DS1553=m +CONFIG_RTC_DRV_DS1672=m +CONFIG_RTC_DRV_DS1742=m +CONFIG_RTC_DRV_DS3234=m +CONFIG_RTC_DRV_FM3130=m +CONFIG_RTC_DRV_ISL1208=m +CONFIG_RTC_DRV_M41T80=m +CONFIG_RTC_DRV_M41T80_WDT=y +CONFIG_RTC_DRV_M41T94=m +CONFIG_RTC_DRV_M48T35=m +CONFIG_RTC_DRV_M48T59=m +CONFIG_RTC_DRV_M48T86=m +CONFIG_RTC_DRV_MAX6900=m +CONFIG_RTC_DRV_MAX6902=m +# CONFIG_RTC_DRV_MSM6242 is not set +# CONFIG_RTC_DRV_PCF2123 is not set +CONFIG_RTC_DRV_PCF8563=m +CONFIG_RTC_DRV_PCF8583=m +CONFIG_RTC_DRV_R9701=m +# CONFIG_RTC_DRV_RP5C01 is not set +CONFIG_RTC_DRV_RS5C348=m +CONFIG_RTC_DRV_RS5C372=m +# CONFIG_RTC_DRV_RX8025 is not set +CONFIG_RTC_DRV_RX8581=m +CONFIG_RTC_DRV_S35390A=m +CONFIG_RTC_DRV_STK17TA8=m +# CONFIG_RTC_DRV_TEST is not set +CONFIG_RTC_DRV_V3020=m +CONFIG_RTC_DRV_X1205=m +# CONFIG_RTC_HCTOSYS is not set +CONFIG_RTC_INTF_ALARM=y +CONFIG_RTC_INTF_ALARM_DEV=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTL8180=m +CONFIG_RTL8187=m +CONFIG_RTL8187_LEDS=y +# CONFIG_RTL8192E is not set +# CONFIG_RTL8192SU is not set +# CONFIG_RTL8192U is not set +# CONFIG_RT_GROUP_SCHED is not set +# CONFIG_RWSEM_GENERIC_SPINLOCK is not set +# CONFIG_SAMSUNG_LAPTOP is not set +CONFIG_SATA_AHCI=y +# CONFIG_SATA_AHCI_PLATFORM is not set +# CONFIG_SATA_INIC162X is not set +# CONFIG_SATA_MV is not set +# CONFIG_SATA_NV is not set +# CONFIG_SATA_PMP is not set +# CONFIG_SATA_PROMISE is not set +# CONFIG_SATA_QSTOR is not set +# CONFIG_SATA_SIL is not set +# CONFIG_SATA_SIL24 is not set +# CONFIG_SATA_SIS is not set +# CONFIG_SATA_SVW is not set +# CONFIG_SATA_SX4 is not set +# CONFIG_SATA_ULI is not set +# CONFIG_SATA_VIA is not set +# CONFIG_SATA_VITESSE is not set +CONFIG_SC92031=m +CONFIG_SCHEDSTATS=y +# CONFIG_SCHED_DEBUG is not set +CONFIG_SCHED_HRTICK=y +CONFIG_SCHED_MC=y +CONFIG_SCHED_OMIT_FRAME_POINTER=y +CONFIG_SCHED_SMT=y +CONFIG_SCSI=y +CONFIG_SCSI_CONSTANTS=y +# CONFIG_SCSI_DH is not set +CONFIG_SCSI_DMA=y +# CONFIG_SCSI_FC_ATTRS is not set +# CONFIG_SCSI_ISCSI_ATTRS is not set +# CONFIG_SCSI_LOGGING is not set +# CONFIG_SCSI_LOWLEVEL is not set +# CONFIG_SCSI_MULTI_LUN is not set +# CONFIG_SCSI_OSD_INITIATOR is not set +CONFIG_SCSI_PROC_FS=y +# CONFIG_SCSI_SAS_ATTRS is not set +# CONFIG_SCSI_SAS_LIBSAS is not set +# CONFIG_SCSI_SCAN_ASYNC is not set +CONFIG_SCSI_SPI_ATTRS=y +# CONFIG_SCSI_SRP_ATTRS is not set +# CONFIG_SCSI_TGT is not set +CONFIG_SCSI_WAIT_SCAN=m +CONFIG_SECCOMP=y +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +CONFIG_SECURITY_NETWORK=y +# CONFIG_SECURITY_NETWORK_XFRM is not set +CONFIG_SECURITY_PATH=y +# CONFIG_SECURITY_SELINUX is not set +# CONFIG_SECURITY_SMACK is not set +CONFIG_SECURITY_TOMOYO=y +CONFIG_SENSORS_ABITUGURU=m +CONFIG_SENSORS_ABITUGURU3=m +CONFIG_SENSORS_AD7414=m +CONFIG_SENSORS_AD7418=m +CONFIG_SENSORS_ADCXX=m +CONFIG_SENSORS_ADM1021=m +CONFIG_SENSORS_ADM1025=m +CONFIG_SENSORS_ADM1026=m +CONFIG_SENSORS_ADM1029=m +CONFIG_SENSORS_ADM1031=m +CONFIG_SENSORS_ADM9240=m +CONFIG_SENSORS_ADS7828=m +# CONFIG_SENSORS_ADS7871 is not set +# CONFIG_SENSORS_ADT7411 is not set +CONFIG_SENSORS_ADT7462=m +CONFIG_SENSORS_ADT7470=m +CONFIG_SENSORS_ADT7475=m +# CONFIG_SENSORS_AMC6821 is not set +CONFIG_SENSORS_APPLESMC=m +CONFIG_SENSORS_ASB100=m +# CONFIG_SENSORS_ASC7621 is not set +CONFIG_SENSORS_ATK0110=m +CONFIG_SENSORS_ATXP1=m +CONFIG_SENSORS_CORETEMP=m +CONFIG_SENSORS_DME1737=m +CONFIG_SENSORS_DS1621=m +# CONFIG_SENSORS_EMC1403 is not set +CONFIG_SENSORS_F71805F=m +CONFIG_SENSORS_F71882FG=m +CONFIG_SENSORS_F75375S=m +CONFIG_SENSORS_FSCHMD=m +CONFIG_SENSORS_G760A=m +CONFIG_SENSORS_GL518SM=m +CONFIG_SENSORS_GL520SM=m +CONFIG_SENSORS_HDAPS=m +CONFIG_SENSORS_I5K_AMB=m +CONFIG_SENSORS_IT87=m +# CONFIG_SENSORS_K10TEMP is not set +CONFIG_SENSORS_K8TEMP=m +CONFIG_SENSORS_LIS3LV02D=m +# CONFIG_SENSORS_LIS3_I2C is not set +CONFIG_SENSORS_LM63=m +CONFIG_SENSORS_LM70=m +# CONFIG_SENSORS_LM73 is not set +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_LM77=m +CONFIG_SENSORS_LM78=m +CONFIG_SENSORS_LM80=m +CONFIG_SENSORS_LM83=m +CONFIG_SENSORS_LM85=m +CONFIG_SENSORS_LM87=m +CONFIG_SENSORS_LM90=m +CONFIG_SENSORS_LM92=m +CONFIG_SENSORS_LM93=m +CONFIG_SENSORS_LM95241=m +CONFIG_SENSORS_LTC4215=m +CONFIG_SENSORS_LTC4245=m +CONFIG_SENSORS_MAX1111=m +CONFIG_SENSORS_MAX1619=m +CONFIG_SENSORS_MAX6650=m +CONFIG_SENSORS_PC87360=m +CONFIG_SENSORS_PC87427=m +CONFIG_SENSORS_PCF8591=m +CONFIG_SENSORS_SHT15=m +CONFIG_SENSORS_SIS5595=m +CONFIG_SENSORS_SMSC47B397=m +CONFIG_SENSORS_SMSC47M1=m +CONFIG_SENSORS_SMSC47M192=m +CONFIG_SENSORS_THMC50=m +# CONFIG_SENSORS_TMP102 is not set +# CONFIG_SENSORS_TMP401 is not set +# CONFIG_SENSORS_TMP421 is not set +# CONFIG_SENSORS_TSL2563 is not set +CONFIG_SENSORS_VIA686A=m +# CONFIG_SENSORS_VIA_CPUTEMP is not set +CONFIG_SENSORS_VT1211=m +CONFIG_SENSORS_VT8231=m +CONFIG_SENSORS_W83627EHF=m +CONFIG_SENSORS_W83627HF=m +CONFIG_SENSORS_W83781D=m +CONFIG_SENSORS_W83791D=m +CONFIG_SENSORS_W83792D=m +CONFIG_SENSORS_W83793=m +CONFIG_SENSORS_W83L785TS=m +CONFIG_SENSORS_W83L786NG=m +# CONFIG_SENSORS_WPCE775X is not set +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_DETECT_IRQ=y +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_MANY_PORTS=y +CONFIG_SERIAL_8250_NR_UARTS=32 +CONFIG_SERIAL_8250_PCI=y +CONFIG_SERIAL_8250_PNP=y +CONFIG_SERIAL_8250_RSA=y +CONFIG_SERIAL_8250_RUNTIME_UARTS=4 +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_CORE=y +CONFIG_SERIAL_CORE_CONSOLE=y +# CONFIG_SERIAL_JSM is not set +# CONFIG_SERIAL_MAX3100 is not set +CONFIG_SERIAL_NONSTANDARD=y +CONFIG_SERIO=y +# CONFIG_SERIO_ALTERA_PS2 is not set +CONFIG_SERIO_CT82C710=m +CONFIG_SERIO_I8042=y +CONFIG_SERIO_LIBPS2=y +CONFIG_SERIO_PCIPS2=m +CONFIG_SERIO_RAW=m +CONFIG_SERIO_SERPORT=m +# CONFIG_SFI is not set +# CONFIG_SGI_IOC4 is not set +CONFIG_SGI_PARTITION=y +CONFIG_SIS190=m +CONFIG_SIS900=m +# CONFIG_SKFP is not set +CONFIG_SKGE=m +# CONFIG_SKGE_DEBUG is not set +CONFIG_SKY2=m +# CONFIG_SKY2_DEBUG is not set +# CONFIG_SLAB is not set +# CONFIG_SLICOSS is not set +# CONFIG_SLIP is not set +CONFIG_SLOW_WORK=y +# CONFIG_SLOW_WORK_DEBUG is not set +CONFIG_SLUB=y +CONFIG_SLUB_DEBUG=y +# CONFIG_SLUB_DEBUG_ON is not set +# CONFIG_SLUB_STATS is not set +CONFIG_SMP=y +CONFIG_SMSC9420=m +CONFIG_SMSC_PHY=m +CONFIG_SND=m +# CONFIG_SND_AD1889 is not set +# CONFIG_SND_ALI5451 is not set +# CONFIG_SND_ALS300 is not set +# CONFIG_SND_ALS4000 is not set +# CONFIG_SND_ASIHPI is not set +# CONFIG_SND_ATIIXP is not set +# CONFIG_SND_ATIIXP_MODEM is not set +# CONFIG_SND_AU8810 is not set +# CONFIG_SND_AU8820 is not set +# CONFIG_SND_AU8830 is not set +# CONFIG_SND_AW2 is not set +# CONFIG_SND_AZT3328 is not set +# CONFIG_SND_BT87X is not set +# CONFIG_SND_CA0106 is not set +# CONFIG_SND_CMIPCI is not set +# CONFIG_SND_CS4281 is not set +# CONFIG_SND_CS46XX is not set +# CONFIG_SND_CS5530 is not set +# CONFIG_SND_CS5535AUDIO is not set +# CONFIG_SND_CTXFI is not set +# CONFIG_SND_DARLA20 is not set +# CONFIG_SND_DARLA24 is not set +# CONFIG_SND_DEBUG is not set +CONFIG_SND_DMA_SGBUF=y +CONFIG_SND_DRIVERS=y +# CONFIG_SND_DUMMY is not set +CONFIG_SND_DYNAMIC_MINORS=y +# CONFIG_SND_ECHO3G is not set +# CONFIG_SND_EMU10K1 is not set +# CONFIG_SND_EMU10K1X is not set +# CONFIG_SND_EMU10K1_SEQ is not set +# CONFIG_SND_ENS1370 is not set +# CONFIG_SND_ENS1371 is not set +# CONFIG_SND_ES1938 is not set +# CONFIG_SND_ES1968 is not set +# CONFIG_SND_FM801 is not set +# CONFIG_SND_GINA20 is not set +# CONFIG_SND_GINA24 is not set +CONFIG_SND_HDA_CODEC_ANALOG=y +CONFIG_SND_HDA_CODEC_ATIHDMI=y +CONFIG_SND_HDA_CODEC_CA0110=y +CONFIG_SND_HDA_CODEC_CIRRUS=y +CONFIG_SND_HDA_CODEC_CMEDIA=y +CONFIG_SND_HDA_CODEC_CONEXANT=y +CONFIG_SND_HDA_CODEC_INTELHDMI=y +CONFIG_SND_HDA_CODEC_NVHDMI=y +CONFIG_SND_HDA_CODEC_REALTEK=y +CONFIG_SND_HDA_CODEC_SI3054=y +CONFIG_SND_HDA_CODEC_SIGMATEL=y +CONFIG_SND_HDA_CODEC_VIA=y +CONFIG_SND_HDA_ELD=y +CONFIG_SND_HDA_GENERIC=y +CONFIG_SND_HDA_HWDEP=y +# CONFIG_SND_HDA_INPUT_BEEP is not set +# CONFIG_SND_HDA_INPUT_JACK is not set +CONFIG_SND_HDA_INTEL=m +# CONFIG_SND_HDA_PATCH_LOADER is not set +CONFIG_SND_HDA_POWER_SAVE=y +CONFIG_SND_HDA_POWER_SAVE_DEFAULT=5 +# CONFIG_SND_HDA_RECONFIG is not set +# CONFIG_SND_HDSP is not set +# CONFIG_SND_HDSPM is not set +# CONFIG_SND_HIFIER is not set +CONFIG_SND_HRTIMER=m +CONFIG_SND_HWDEP=m +# CONFIG_SND_ICE1712 is not set +# CONFIG_SND_ICE1724 is not set +# CONFIG_SND_INDIGO is not set +# CONFIG_SND_INDIGODJ is not set +# CONFIG_SND_INDIGODJX is not set +# CONFIG_SND_INDIGOIO is not set +# CONFIG_SND_INDIGOIOX is not set +# CONFIG_SND_INTEL8X0 is not set +# CONFIG_SND_INTEL8X0M is not set +# CONFIG_SND_KORG1212 is not set +# CONFIG_SND_LAYLA20 is not set +# CONFIG_SND_LAYLA24 is not set +# CONFIG_SND_LX6464ES is not set +# CONFIG_SND_MAESTRO3 is not set +# CONFIG_SND_MIA is not set +# CONFIG_SND_MIXART is not set +CONFIG_SND_MIXER_OSS=m +# CONFIG_SND_MONA is not set +# CONFIG_SND_MPU401 is not set +# CONFIG_SND_MTPAV is not set +# CONFIG_SND_NM256 is not set +# CONFIG_SND_OPL3_LIB_SEQ is not set +# CONFIG_SND_OPL4_LIB_SEQ is not set +CONFIG_SND_OSSEMUL=y +# CONFIG_SND_OXYGEN is not set +CONFIG_SND_PCI=y +CONFIG_SND_PCM=m +CONFIG_SND_PCM_OSS=m +CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_PCSP is not set +# CONFIG_SND_PCXHR is not set +CONFIG_SND_RAWMIDI=m +CONFIG_SND_RAWMIDI_SEQ=m +# CONFIG_SND_RIPTIDE is not set +# CONFIG_SND_RME32 is not set +# CONFIG_SND_RME96 is not set +# CONFIG_SND_RME9652 is not set +# CONFIG_SND_SBAWE_SEQ is not set +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQUENCER_OSS=y +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_SEQ_HRTIMER_DEFAULT=y +# CONFIG_SND_SERIAL_U16550 is not set +# CONFIG_SND_SOC is not set +# CONFIG_SND_SONICVIBES is not set +CONFIG_SND_SPI=y +CONFIG_SND_SUPPORT_OLD_API=y +CONFIG_SND_TIMER=m +# CONFIG_SND_TRIDENT is not set +CONFIG_SND_USB=y +CONFIG_SND_USB_AUDIO=m +# CONFIG_SND_USB_CAIAQ is not set +# CONFIG_SND_USB_UA101 is not set +# CONFIG_SND_USB_US122L is not set +# CONFIG_SND_USB_USX2Y is not set +# CONFIG_SND_VERBOSE_PRINTK is not set +CONFIG_SND_VERBOSE_PROCFS=y +# CONFIG_SND_VIA82XX is not set +# CONFIG_SND_VIA82XX_MODEM is not set +# CONFIG_SND_VIRMIDI is not set +# CONFIG_SND_VIRTUOSO is not set +CONFIG_SND_VMASTER=y +# CONFIG_SND_VX222 is not set +# CONFIG_SND_YMFPCI is not set +# CONFIG_SOC_CAMERA is not set +CONFIG_SOLARIS_X86_PARTITION=y +CONFIG_SONYPI_COMPAT=y +CONFIG_SONY_LAPTOP=m +CONFIG_SOUND=m +CONFIG_SOUND_OSS_CORE=y +CONFIG_SOUND_OSS_CORE_PRECLAIM=y +# CONFIG_SOUND_PRIME is not set +CONFIG_SPARSEMEM=y +CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER=y +CONFIG_SPARSEMEM_EXTREME=y +CONFIG_SPARSEMEM_MANUAL=y +CONFIG_SPARSEMEM_VMEMMAP=y +CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y +CONFIG_SPARSE_IRQ=y +# CONFIG_SPECIALIX is not set +CONFIG_SPI=y +CONFIG_SPI_BITBANG=m +# CONFIG_SPI_DEBUG is not set +# CONFIG_SPI_DESIGNWARE is not set +# CONFIG_SPI_GPIO is not set +CONFIG_SPI_MASTER=y +CONFIG_SPI_SPIDEV=m +# CONFIG_SPI_TLE62X0 is not set +# CONFIG_SPI_XILINX is not set +CONFIG_SPLIT_PTLOCK_CPUS=4 +CONFIG_SQUASHFS=y +# CONFIG_SQUASHFS_EMBEDDED is not set +CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3 +# CONFIG_SQUASHFS_XATTRS is not set +CONFIG_STACKTRACE=y +# CONFIG_STALDRV is not set +# CONFIG_STE10XP is not set +CONFIG_STOP_MACHINE=y +# CONFIG_STRICT_DEVMEM is not set +# CONFIG_ST_BT is not set +CONFIG_SUNDANCE=m +CONFIG_SUNDANCE_MMIO=y +CONFIG_SUNGEM=m +CONFIG_SUN_PARTITION=y +# CONFIG_SWAP is not set +CONFIG_SWIOTLB=y +# CONFIG_SWITCH is not set +# CONFIG_SYNCLINK is not set +# CONFIG_SYNCLINKMP is not set +# CONFIG_SYNCLINK_GT is not set +CONFIG_SYN_COOKIES=y +CONFIG_SYSCTL_SYSCALL_CHECK=y +# CONFIG_SYSPROF_TRACER is not set +# CONFIG_SYSV68_PARTITION is not set +CONFIG_SYSVIPC=y +CONFIG_SYSVIPC_COMPAT=y +CONFIG_SYSVIPC_SYSCTL=y +CONFIG_TABLET_USB_ACECAD=m +CONFIG_TABLET_USB_AIPTEK=m +CONFIG_TABLET_USB_GTCO=m +CONFIG_TABLET_USB_KBTAB=m +CONFIG_TABLET_USB_WACOM=m +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_TASK_XACCT=y +# CONFIG_TCG_ATMEL is not set +# CONFIG_TCG_INFINEON is not set +# CONFIG_TCG_NSC is not set +# CONFIG_TCG_ST_I2C is not set +CONFIG_TCG_TIS=m +CONFIG_TCG_TPM=m +CONFIG_TCP_CONG_ADVANCED=y +# CONFIG_TCP_CONG_BIC is not set +# CONFIG_TCP_CONG_HSTCP is not set +# CONFIG_TCP_CONG_HTCP is not set +# CONFIG_TCP_CONG_HYBLA is not set +# CONFIG_TCP_CONG_ILLINOIS is not set +# CONFIG_TCP_CONG_LP is not set +# CONFIG_TCP_CONG_SCALABLE is not set +# CONFIG_TCP_CONG_VEGAS is not set +# CONFIG_TCP_CONG_VENO is not set +# CONFIG_TCP_CONG_WESTWOOD is not set +# CONFIG_TCP_CONG_YEAH is not set +CONFIG_TCP_MD5SIG=y +# CONFIG_TELCLOCK is not set +# CONFIG_TEST_POWER is not set +CONFIG_THERMAL=y +CONFIG_THERMAL_HWMON=y +# CONFIG_THERMAL_MSM_POPMEM is not set +CONFIG_THINKPAD_ACPI=m +CONFIG_THINKPAD_ACPI_ALSA_SUPPORT=y +# CONFIG_THINKPAD_ACPI_DEBUG is not set +# CONFIG_THINKPAD_ACPI_DEBUGFACILITIES is not set +CONFIG_THINKPAD_ACPI_HOTKEY_POLL=y +CONFIG_THINKPAD_ACPI_UNSAFE_LEDS=y +CONFIG_THINKPAD_ACPI_VIDEO=y +CONFIG_TICK_ONESHOT=y +# CONFIG_TIFM_CORE is not set +CONFIG_TIGON3=m +CONFIG_TIMER_STATS=y +# CONFIG_TI_DAC7512 is not set +# CONFIG_TI_ST is not set +CONFIG_TLAN=m +CONFIG_TMD_HERMES=m +CONFIG_TMPFS_POSIX_ACL=y +# CONFIG_TOPSTAR_LAPTOP is not set +# CONFIG_TOSHIBA_BT_RFKILL is not set +CONFIG_TOUCHSCREEN_AD7877=m +CONFIG_TOUCHSCREEN_AD7879=m +CONFIG_TOUCHSCREEN_AD7879_I2C=m +CONFIG_TOUCHSCREEN_ADS7846=m +# CONFIG_TOUCHSCREEN_CY8C_TS is not set +# CONFIG_TOUCHSCREEN_CYTTSP_I2C is not set +# CONFIG_TOUCHSCREEN_DYNAPRO is not set +# CONFIG_TOUCHSCREEN_EETI is not set +CONFIG_TOUCHSCREEN_ELO=m +CONFIG_TOUCHSCREEN_FUJITSU=m +CONFIG_TOUCHSCREEN_GUNZE=m +# CONFIG_TOUCHSCREEN_HAMPSHIRE is not set +CONFIG_TOUCHSCREEN_INEXIO=m +# CONFIG_TOUCHSCREEN_MCS5000 is not set +CONFIG_TOUCHSCREEN_MK712=m +CONFIG_TOUCHSCREEN_MTOUCH=m +CONFIG_TOUCHSCREEN_PENMOUNT=m +# CONFIG_TOUCHSCREEN_QT602240 is not set +# CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI is not set +CONFIG_TOUCHSCREEN_TOUCHIT213=m +CONFIG_TOUCHSCREEN_TOUCHRIGHT=m +CONFIG_TOUCHSCREEN_TOUCHWIN=m +# CONFIG_TOUCHSCREEN_TPS6507X is not set +CONFIG_TOUCHSCREEN_TSC2007=m +CONFIG_TOUCHSCREEN_USB_3M=y +CONFIG_TOUCHSCREEN_USB_COMPOSITE=m +CONFIG_TOUCHSCREEN_USB_DMC_TSC10=y +CONFIG_TOUCHSCREEN_USB_E2I=y +CONFIG_TOUCHSCREEN_USB_EGALAX=y +CONFIG_TOUCHSCREEN_USB_ETT_TC5UH=y +CONFIG_TOUCHSCREEN_USB_ETURBO=y +CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH=y +CONFIG_TOUCHSCREEN_USB_GOTOP=y +CONFIG_TOUCHSCREEN_USB_GUNZE=y +CONFIG_TOUCHSCREEN_USB_IDEALTEK=y +CONFIG_TOUCHSCREEN_USB_IRTOUCH=y +CONFIG_TOUCHSCREEN_USB_ITM=y +CONFIG_TOUCHSCREEN_USB_JASTEC=y +CONFIG_TOUCHSCREEN_USB_NEXIO=y +CONFIG_TOUCHSCREEN_USB_PANJIT=y +CONFIG_TOUCHSCREEN_USB_ZYTRONIC=y +CONFIG_TOUCHSCREEN_WACOM_W8001=m +# CONFIG_TPS65010 is not set +# CONFIG_TR is not set +CONFIG_TRACEPOINTS=y +CONFIG_TRACING=y +# CONFIG_TRANZPORT is not set +# CONFIG_TULIP is not set +CONFIG_TYPHOON=m +CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_ULI526X is not set +# CONFIG_ULTRIX_PARTITION is not set +CONFIG_UNIXWARE_DISKLABEL=y +CONFIG_USB=y +CONFIG_USB_ACM=y +# CONFIG_USB_ADUTUX is not set +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +# CONFIG_USB_APPLEDISPLAY is not set +CONFIG_USB_ARCH_HAS_EHCI=y +CONFIG_USB_ARCH_HAS_OHCI=y +# CONFIG_USB_C67X00_HCD is not set +# CONFIG_USB_CATC is not set +# CONFIG_USB_CYPRESS_CY7C63 is not set +# CONFIG_USB_CYTHERM is not set +# CONFIG_USB_DEBUG is not set +CONFIG_USB_DEVICEFS=y +# CONFIG_USB_DEVICE_CLASS is not set +# CONFIG_USB_DYNAMIC_MINORS is not set +# CONFIG_USB_EHCI_EHSET is not set +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +# CONFIG_USB_EHCI_TT_NEWSCHED is not set +# CONFIG_USB_EMI26 is not set +# CONFIG_USB_EMI62 is not set +# CONFIG_USB_ET61X251 is not set +# CONFIG_USB_EZUSB is not set +# CONFIG_USB_FTDI_ELAN is not set +# CONFIG_USB_GPIO_VBUS is not set +# CONFIG_USB_GSPCA is not set +CONFIG_USB_HID=y +CONFIG_USB_HIDDEV=y +# CONFIG_USB_HSO is not set +# CONFIG_USB_HWA_HCD is not set +# CONFIG_USB_IDMOUSE is not set +# CONFIG_USB_IOWARRIOR is not set +# CONFIG_USB_IPHETH is not set +# CONFIG_USB_IP_COMMON is not set +# CONFIG_USB_ISIGHTFW is not set +CONFIG_USB_ISP116X_HCD=m +# CONFIG_USB_ISP1362_HCD is not set +CONFIG_USB_ISP1760_HCD=m +CONFIG_USB_KAWETH=m +# CONFIG_USB_LCD is not set +# CONFIG_USB_LD is not set +# CONFIG_USB_LED is not set +# CONFIG_USB_LEGOTOWER is not set +CONFIG_USB_LIBUSUAL=y +# CONFIG_USB_MDC800 is not set +# CONFIG_USB_MICROTEK is not set +# CONFIG_USB_MON is not set +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_CDCETHER=m +# CONFIG_USB_NET_CDC_EEM is not set +# CONFIG_USB_NET_CDC_SUBSET is not set +CONFIG_USB_NET_DM9601=m +# CONFIG_USB_NET_GL620A is not set +# CONFIG_USB_NET_INT51X1 is not set +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_NET1080=m +# CONFIG_USB_NET_PLUSB is not set +CONFIG_USB_NET_RNDIS_HOST=m +CONFIG_USB_NET_RNDIS_WLAN=m +# CONFIG_USB_NET_SMSC75XX is not set +CONFIG_USB_NET_SMSC95XX=m +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_USB_OHCI_BIG_ENDIAN_DESC is not set +# CONFIG_USB_OHCI_BIG_ENDIAN_MMIO is not set +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_OHCI_LITTLE_ENDIAN=y +# CONFIG_USB_OTG_BLACKLIST_HUB is not set +# CONFIG_USB_OTG_WHITELIST is not set +CONFIG_USB_OXU210HP_HCD=m +CONFIG_USB_PEGASUS=m +# CONFIG_USB_PRINTER is not set +CONFIG_USB_R8A66597_HCD=m +# CONFIG_USB_RIO500 is not set +CONFIG_USB_RTL8150=m +# CONFIG_USB_S2255 is not set +CONFIG_USB_SERIAL=y +# CONFIG_USB_SERIAL_AIRCABLE is not set +# CONFIG_USB_SERIAL_ARK3116 is not set +# CONFIG_USB_SERIAL_BELKIN is not set +# CONFIG_USB_SERIAL_CH341 is not set +# CONFIG_USB_SERIAL_CONSOLE is not set +# CONFIG_USB_SERIAL_CP210X is not set +# CONFIG_USB_SERIAL_CYBERJACK is not set +CONFIG_USB_SERIAL_CYPRESS_M8=m +# CONFIG_USB_SERIAL_DEBUG is not set +# CONFIG_USB_SERIAL_DIGI_ACCELEPORT is not set +# CONFIG_USB_SERIAL_EDGEPORT is not set +# CONFIG_USB_SERIAL_EDGEPORT_TI is not set +# CONFIG_USB_SERIAL_EMPEG is not set +# CONFIG_USB_SERIAL_FTDI_SIO is not set +# CONFIG_USB_SERIAL_FUNSOFT is not set +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_GENERIC=y +# CONFIG_USB_SERIAL_HP4X is not set +# CONFIG_USB_SERIAL_IPAQ is not set +# CONFIG_USB_SERIAL_IPW is not set +# CONFIG_USB_SERIAL_IR is not set +# CONFIG_USB_SERIAL_IUU is not set +# CONFIG_USB_SERIAL_KEYSPAN is not set +# CONFIG_USB_SERIAL_KEYSPAN_PDA is not set +# CONFIG_USB_SERIAL_KLSI is not set +# CONFIG_USB_SERIAL_KOBIL_SCT is not set +# CONFIG_USB_SERIAL_MCT_U232 is not set +# CONFIG_USB_SERIAL_MOS7720 is not set +# CONFIG_USB_SERIAL_MOS7840 is not set +# CONFIG_USB_SERIAL_MOTOROLA is not set +CONFIG_USB_SERIAL_NAVMAN=m +# CONFIG_USB_SERIAL_OMNINET is not set +# CONFIG_USB_SERIAL_OPTICON is not set +CONFIG_USB_SERIAL_OPTION=y +# CONFIG_USB_SERIAL_OTI6858 is not set +# CONFIG_USB_SERIAL_PL2303 is not set +# CONFIG_USB_SERIAL_QCAUX is not set +# CONFIG_USB_SERIAL_QUALCOMM is not set +# CONFIG_USB_SERIAL_QUATECH2 is not set +# CONFIG_USB_SERIAL_QUATECH_USB2 is not set +# CONFIG_USB_SERIAL_SAFE is not set +# CONFIG_USB_SERIAL_SIEMENS_MPI is not set +CONFIG_USB_SERIAL_SIERRAWIRELESS=y +# CONFIG_USB_SERIAL_SPCP8X5 is not set +# CONFIG_USB_SERIAL_SYMBOL is not set +# CONFIG_USB_SERIAL_TI is not set +# CONFIG_USB_SERIAL_VISOR is not set +# CONFIG_USB_SERIAL_VIVOPAY_SERIAL is not set +# CONFIG_USB_SERIAL_WHITEHEAT is not set +CONFIG_USB_SERIAL_WWAN=y +# CONFIG_USB_SERIAL_XIRCOM is not set +# CONFIG_USB_SERIAL_ZIO is not set +# CONFIG_USB_SEVSEG is not set +# CONFIG_USB_SIERRA_NET is not set +# CONFIG_USB_SISUSBVGA is not set +CONFIG_USB_SL811_HCD=m +# CONFIG_USB_SN9C102 is not set +# CONFIG_USB_STKWEBCAM is not set +CONFIG_USB_STORAGE=y +# CONFIG_USB_STORAGE_ALAUDA is not set +# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set +# CONFIG_USB_STORAGE_DATAFAB is not set +# CONFIG_USB_STORAGE_DEBUG is not set +# CONFIG_USB_STORAGE_FREECOM is not set +# CONFIG_USB_STORAGE_ISD200 is not set +# CONFIG_USB_STORAGE_JUMPSHOT is not set +# CONFIG_USB_STORAGE_KARMA is not set +# CONFIG_USB_STORAGE_ONETOUCH is not set +# CONFIG_USB_STORAGE_SDDR09 is not set +# CONFIG_USB_STORAGE_SDDR55 is not set +# CONFIG_USB_STORAGE_USBAT is not set +# CONFIG_USB_TEST is not set +# CONFIG_USB_TMC is not set +# CONFIG_USB_TRANCEVIBRATOR is not set +CONFIG_USB_UHCI_HCD=y +CONFIG_USB_USBNET=m +CONFIG_USB_VIDEO_CLASS=m +# CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV is not set +# CONFIG_USB_WDM is not set +# CONFIG_USB_WHCI_HCD is not set +# CONFIG_USB_WUSB is not set +# CONFIG_USB_WUSB_CBAF is not set +# CONFIG_USB_XHCI_HCD is not set +# CONFIG_USB_ZC0301 is not set +CONFIG_USB_ZD1201=m +# CONFIG_USB_ZR364XX is not set +CONFIG_USER_NS=y +CONFIG_USER_STACKTRACE_SUPPORT=y +CONFIG_USE_GENERIC_SMP_HELPERS=y +CONFIG_UTS_NS=y +# CONFIG_UWB is not set +# CONFIG_V4L_MEM2MEM_DRIVERS is not set +CONFIG_V4L_USB_DRIVERS=y +CONFIG_VFAT_FS=m +# CONFIG_VGACON_SOFT_SCROLLBACK is not set +CONFIG_VGA_ARB=y +CONFIG_VGA_ARB_MAX_GPUS=16 +CONFIG_VGA_CONSOLE=y +# CONFIG_VGA_SWITCHEROO is not set +CONFIG_VIA_RHINE=m +CONFIG_VIA_RHINE_MMIO=y +CONFIG_VIA_VELOCITY=m +# CONFIG_VIDEO_ADV7170 is not set +# CONFIG_VIDEO_ADV7175 is not set +# CONFIG_VIDEO_ADV7180 is not set +# CONFIG_VIDEO_ADV7343 is not set +# CONFIG_VIDEO_ADV_DEBUG is not set +# CONFIG_VIDEO_AK881X is not set +# CONFIG_VIDEO_ALLOW_V4L1 is not set +# CONFIG_VIDEO_BT819 is not set +# CONFIG_VIDEO_BT848 is not set +# CONFIG_VIDEO_BT856 is not set +# CONFIG_VIDEO_BT866 is not set +# CONFIG_VIDEO_CAFE_CCIC is not set +CONFIG_VIDEO_CAPTURE_DRIVERS=y +# CONFIG_VIDEO_CS5345 is not set +# CONFIG_VIDEO_CS53L32A is not set +# CONFIG_VIDEO_CX231XX is not set +# CONFIG_VIDEO_CX2341X is not set +# CONFIG_VIDEO_CX25840 is not set +# CONFIG_VIDEO_CX88 is not set +CONFIG_VIDEO_DEV=m +# CONFIG_VIDEO_DT3155 is not set +# CONFIG_VIDEO_EM28XX is not set +# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set +# CONFIG_VIDEO_GO7007 is not set +# CONFIG_VIDEO_HDPVR is not set +# CONFIG_VIDEO_HELPER_CHIPS_AUTO is not set +# CONFIG_VIDEO_HEXIUM_GEMINI is not set +# CONFIG_VIDEO_HEXIUM_ORION is not set +CONFIG_VIDEO_IR=m +CONFIG_VIDEO_IR_I2C=m +# CONFIG_VIDEO_IVTV is not set +# CONFIG_VIDEO_KS0127 is not set +# CONFIG_VIDEO_M52790 is not set +CONFIG_VIDEO_MEDIA=m +# CONFIG_VIDEO_MEYE is not set +# CONFIG_VIDEO_MSP3400 is not set +# CONFIG_VIDEO_MT9V011 is not set +# CONFIG_VIDEO_MXB is not set +CONFIG_VIDEO_OUTPUT_CONTROL=y +# CONFIG_VIDEO_OV7670 is not set +# CONFIG_VIDEO_PVRUSB2 is not set +# CONFIG_VIDEO_SAA5246A is not set +# CONFIG_VIDEO_SAA5249 is not set +# CONFIG_VIDEO_SAA6588 is not set +# CONFIG_VIDEO_SAA7110 is not set +# CONFIG_VIDEO_SAA711X is not set +# CONFIG_VIDEO_SAA7127 is not set +# CONFIG_VIDEO_SAA7134 is not set +# CONFIG_VIDEO_SAA717X is not set +# CONFIG_VIDEO_SAA7185 is not set +# CONFIG_VIDEO_SAA7191 is not set +# CONFIG_VIDEO_TCM825X is not set +# CONFIG_VIDEO_TDA7432 is not set +# CONFIG_VIDEO_TDA9840 is not set +# CONFIG_VIDEO_TDA9875 is not set +# CONFIG_VIDEO_TEA6415C is not set +# CONFIG_VIDEO_TEA6420 is not set +# CONFIG_VIDEO_THS7303 is not set +# CONFIG_VIDEO_TLV320AIC23B is not set +# CONFIG_VIDEO_TM6000 is not set +# CONFIG_VIDEO_TVAUDIO is not set +# CONFIG_VIDEO_TVP514X is not set +# CONFIG_VIDEO_TVP5150 is not set +# CONFIG_VIDEO_TVP7002 is not set +# CONFIG_VIDEO_UPD64031A is not set +# CONFIG_VIDEO_UPD64083 is not set +# CONFIG_VIDEO_USBVISION is not set +# CONFIG_VIDEO_V4L1_COMPAT is not set +CONFIG_VIDEO_V4L2=m +CONFIG_VIDEO_V4L2_COMMON=m +# CONFIG_VIDEO_VP27SMPX is not set +# CONFIG_VIDEO_VPX3220 is not set +# CONFIG_VIDEO_WM8739 is not set +# CONFIG_VIDEO_WM8775 is not set +# CONFIG_VIDEO_ZORAN is not set +# CONFIG_VIRTUALIZATION is not set +# CONFIG_VITESSE_PHY is not set +# CONFIG_VME_BUS is not set +# CONFIG_VMWARE_BALLOON is not set +# CONFIG_VMXNET3 is not set +CONFIG_VORTEX=m +# CONFIG_VT6655 is not set +# CONFIG_VT6656 is not set +CONFIG_VT_HW_CONSOLE_BINDING=y +# CONFIG_W35UND is not set +# CONFIG_WAKELOCK is not set +CONFIG_WEXT_PRIV=y +CONFIG_WEXT_SPY=y +# CONFIG_WINBOND_840 is not set +CONFIG_WIRELESS_EXT=y +# CONFIG_WL127X_RFKILL is not set +CONFIG_X86=y +# CONFIG_X86_32 is not set +CONFIG_X86_64=y +CONFIG_X86_64_SMP=y +CONFIG_X86_ACPI_CPUFREQ=y +CONFIG_X86_BOOTPARAM_MEMORY_CORRUPTION_CHECK=y +CONFIG_X86_CHECK_BIOS_CORRUPTION=y +CONFIG_X86_CMOV=y +CONFIG_X86_CMPXCHG=y +CONFIG_X86_CMPXCHG64=y +CONFIG_X86_CPU=y +CONFIG_X86_CPUID=y +CONFIG_X86_DEBUGCTLMSR=y +# CONFIG_X86_DECODER_SELFTEST is not set +CONFIG_X86_EXTENDED_PLATFORM=y +CONFIG_X86_HT=y +CONFIG_X86_INTERNODE_CACHE_SHIFT=6 +CONFIG_X86_IO_APIC=y +CONFIG_X86_L1_CACHE_SHIFT=6 +CONFIG_X86_LOCAL_APIC=y +CONFIG_X86_MCE=y +# CONFIG_X86_MCE_AMD is not set +# CONFIG_X86_MCE_INJECT is not set +CONFIG_X86_MCE_INTEL=y +CONFIG_X86_MCE_THRESHOLD=y +CONFIG_X86_MINIMUM_CPU_FAMILY=64 +CONFIG_X86_MPPARSE=y +CONFIG_X86_MSR=y +# CONFIG_X86_P4_CLOCKMOD is not set +CONFIG_X86_PAT=y +# CONFIG_X86_PCC_CPUFREQ is not set +CONFIG_X86_PLATFORM_DEVICES=y +CONFIG_X86_PM_TIMER=y +# CONFIG_X86_POWERNOW_K8 is not set +# CONFIG_X86_PTDUMP is not set +CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS=y +CONFIG_X86_RESERVE_LOW_64K=y +# CONFIG_X86_SPEEDSTEP_CENTRINO is not set +# CONFIG_X86_SPEEDSTEP_LIB is not set +CONFIG_X86_SUPPORTS_MEMORY_FAILURE=y +CONFIG_X86_THERMAL_VECTOR=y +CONFIG_X86_TRAMPOLINE=y +CONFIG_X86_TSC=y +CONFIG_X86_VERBOSE_BOOTUP=y +# CONFIG_X86_VSMP is not set +CONFIG_X86_WP_WORKS_OK=y +CONFIG_X86_XADD=y +CONFIG_XFRM=y +# CONFIG_XFRM_MIGRATE is not set +# CONFIG_XFRM_STATISTICS is not set +# CONFIG_XFRM_SUB_POLICY is not set +CONFIG_XFRM_USER=y +CONFIG_YELLOWFIN=m +CONFIG_ZD1211RW=m +# CONFIG_ZD1211RW_DEBUG is not set +CONFIG_ZISOFS=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_ZONE_DMA=y +CONFIG_ZONE_DMA32=y +CONFIG_ZONE_DMA_FLAG=1 diff --git a/chromeos/config/x86_64/config.flavour.chromeos-intel-pineview b/chromeos/config/x86_64/config.flavour.chromeos-intel-pineview new file mode 100644 index 00000000000..fad4bbee31e --- /dev/null +++ b/chromeos/config/x86_64/config.flavour.chromeos-intel-pineview @@ -0,0 +1,3 @@ +# +# Config options generated by splitconfig +# diff --git a/chromeos/scripts/allconfigs b/chromeos/scripts/allconfigs new file mode 100755 index 00000000000..16431991d4d --- /dev/null +++ b/chromeos/scripts/allconfigs @@ -0,0 +1,28 @@ +#!/bin/bash + +bindir="`pwd`/chromeos/scripts" +confdir="`pwd`/chromeos/config" + +get_flavourconfigs() { + for file in `find $confdir`; do + if echo $file | egrep -q "config\.flavour\..*[^~]$"; then + basename $file | awk -F . '{ print $3 }' + fi + done +} + +get_arch() { + if find . -name config.flavour.$1 | grep -q i386; then + echo i386 + else + echo arm + fi +} + +for flavour in $(get_flavourconfigs); do + echo $flavour $(get_arch $flavour) + $bindir/prepareconfig $flavour + yes "" | make ARCH=$(get_arch $flavour) oldconfig + cp .config .config-$flavour +done + diff --git a/chromeos/scripts/compat_wireless_config b/chromeos/scripts/compat_wireless_config new file mode 100755 index 00000000000..22cbd9c8575 --- /dev/null +++ b/chromeos/scripts/compat_wireless_config @@ -0,0 +1,120 @@ +# /bin/sh + +# Copyright (c) 2009 The Chromium OS Authors. All rights reserved. +# Distributed under the terms of the GNU General Public License v2 + +# +# Helper script to setup compat-wireless configuration. +# + +S=${1:?No kernel source directory specified} + +COMPAT_WIRELESS="${S}"/chromeos/compat-wireless +COMPAT_VERSION=`cat ${COMPAT_WIRELESS}/compat_version` +COMPAT_RELEASE=`cat ${COMPAT_WIRELESS}/compat_version` +COMPAT_BASE_TREE=`cat ${COMPAT_WIRELESS}/compat_base_tree` +COMPAT_BASE_TREE_VERSION=`cat ${COMPAT_WIRELESS}/compat_base_tree_version` +# TODO(sleffler) calculate CONFIG_COMPAT_KERNEL version + +COMPAT_CONFIG=' + CONFIG_COMPAT_KERNEL_33=y + CONFIG_COMPAT_FIRMWARE_CLASS=m + + CONFIG_COMPAT_RFKILL=y + CONFIG_RFKILL_BACKPORT=y + CONFIG_RFKILL_BACKPORT_INPUT=y + + CONFIG_COMPAT_WIRELESS=m + CONFIG_COMPAT_WIRELESS_MODULES=m + + CONFIG_MAC80211=m + CONFIG_MAC80211_LEDS=y + CONFIG_MAC80211_DEBUGFS=y + CONFIG_MAC80211_RC_MINSTREL=y + CONFIG_MAC80211_RC_DEFAULT="minstrel" + CONFIG_COMPAT_MAC80211_RC_DEFAULT="minstrel" + + CONFIG_CFG80211=m + CONFIG_CFG80211_DEBUGFS=y + CONFIG_CFG80211_DEFAULT_PS=y + CONFIG_CFG80211_WEXT=y + + CONFIG_ATH=y + CONFIG_ATH_COMMON=m + CONFIG_ATH_DEBUG=y + CONFIG_ATH5K=m + CONFIG_ATH9K=m + CONFIG_ATH9K_HW=m + CONFIG_ATH9K_COMMON=m + CONFIG_ATH9K_DEBUGFS=y + + CONFIG_IWLWIFI=m + CONFIG_IWLWIFI_SPECTRUM_MANAGEMENT=y + CONFIG_IWLAGN=m + CONFIG_COMPAT_IWL4965=y + CONFIG_IWL5000=y + CONFIG_IWL3945=m + CONFIG_IWL3945_SPECTRUM_MANAGEMENT=y + CONFIG_IWLWIFI_DEBUG=y + CONFIG_IWLWIFI_DEBUGFS=y + + CONFIG_USB_NET_COMPAT_CDCETHER=m + CONFIG_USB_NET_COMPAT_RNDIS_HOST=m + CONFIG_USB_COMPAT_USBNET=m +' +echo "Configure compat-wireless: ${COMPAT_CONFIG}" + +# +# Edit Makefiles to reflect configuration knobs +# +SED_ARGS= +for c in $COMPAT_CONFIG; do + a=`echo $c | sed s/=.*//` + b=`echo $c | sed s/.*=//` + SED_ARGS="$SED_ARGS -e s/[\$]("$a")/"$b"/" +done + +Makefiles=`find -L "${COMPAT_WIRELESS}" -name Makefile` +for m in $Makefiles; do + sed $SED_ARGS -i $m +done + +# Construct include/linux/compat_autoconf.h +(cat<"${COMPAT_WIRELESS}"/include/linux/compat_autoconf.h diff --git a/chromeos/scripts/kernelconfig b/chromeos/scripts/kernelconfig new file mode 100755 index 00000000000..c056c8c6086 --- /dev/null +++ b/chromeos/scripts/kernelconfig @@ -0,0 +1,123 @@ +#!/bin/bash + +# Script to merge all configs and run 'make silentoldconfig' on it to wade out bad juju. +# Then split the configs into distro-commmon and flavour-specific parts + +# We have to be in the top level kernel source directory +if [ ! -f MAINTAINERS ] || [ ! -f Makefile ]; then + echo "This does not appear to be the kernel source directory." 1>&2 + exit 1 +fi + +mode=${1:?"Usage: $0 [oldconfig|editconfig]"} +case "$mode" in + oldconfig) ;; # All is good + editconfig) ;; # All is good + genconfig) ;; # All is good + *) echo "$0 called with invalid mode" 1>&2 + exit 1 ;; +esac +kerneldir="`pwd`" +confdir="$kerneldir/chromeos/config" +archs="x86_64 i386 armel" +family='chromeos' +bindir="`pwd`/chromeos/scripts" +common_conf="$confdir/config.common.$family" +tmpdir=`mktemp -d` + +if [ "$mode" = "genconfig" ]; then + keep=1 + mode="oldconfig" + test -d CONFIGS || mkdir CONFIGS +fi + +test -d build || mkdir build + +for arch in $archs; do + # Map debian archs to kernel archs + case "$arch" in + amd64) kernarch="x86_64" ;; + lpia) kernarch="x86" ;; + sparc) kernarch="sparc64" ;; + armel) kernarch="arm" ;; + *) kernarch="$arch" ;; + esac + + echo "" + echo "***************************************" + echo "* Processing $arch ($kernarch) ... " + archconfdir=$confdir/$arch + flavourconfigs=$(cd $archconfdir && ls config.flavour.*[^~]) + + # Merge configs + # We merge config.common.ubuntu + config.common. + + # config.flavour. + + for config in $flavourconfigs; do + fullconf="$tmpdir/$arch-$config-full" + case $config in + *) + : >"$fullconf" + if [ -f $common_conf ]; then + cat $common_conf >> "$fullconf" + fi + if [ -f $archconfdir/config.common.$arch ]; then + cat $archconfdir/config.common.$arch >> "$fullconf" + fi + cat "$archconfdir/$config" >>"$fullconf" + ;; + esac + done + + for config in $flavourconfigs; do + if [ -f $archconfdir/$config ]; then + fullconf="$tmpdir/$arch-$config-full" + cat "$fullconf" > build/.config + # Call oldconfig or menuconfig + case "$mode" in + oldconfig) + # Weed out incorrect config parameters + echo "* Run silentoldconfig on $arch/$config ..." + make O=`pwd`/build ARCH=$kernarch silentoldconfig ;; + editconfig) + # Interactively edit config parameters + echo " * Run menuconfig on $arch/$config... Press a key." + read + make O=`pwd`/build ARCH=$kernarch menuconfig ;; + *) # Bad! + exit 1 ;; + esac + cat build/.config > $archconfdir/$config + if [ "$keep" = "1" ]; then + cat build/.config > CONFIGS/$arch-$config + fi + else + echo "!! Config not found $archconfdir/$config..." + fi + done + + echo "Running splitconfig for $arch" + echo + + # Can we make this more robust by avoiding $tmpdir completely? + # This approach was used for now because I didn't want to change + # splitconfig + (cd $archconfdir; rm config.common.$arch; $bindir/splitconfig; \ + mv config.common config.common.$arch; \ + cp config.common.$arch $tmpdir) +done + +rm -f $common_conf + +# Now run splitconfig on all the config.common. copied to +# $tmpdir +(cd $tmpdir; $bindir/splitconfig) +( + cd $confdir; + rm -f *-full + grep -v 'is UNMERGABLE' <$tmpdir/config.common >$common_conf + for arch in $archs; do + grep -v 'is UNMERGABLE' <$tmpdir/config.common.$arch \ + >$arch/config.common.$arch + done +) diff --git a/chromeos/scripts/prepareconfig b/chromeos/scripts/prepareconfig new file mode 100755 index 00000000000..b8413669ea0 --- /dev/null +++ b/chromeos/scripts/prepareconfig @@ -0,0 +1,21 @@ +#!/bin/bash + +family=chromeos + +# The source package name will be the first token in the changelog +changelog="${family}/changelog" +package=$(sed -n '1s/\([^ ]*\).*/\1/p' ${changelog}) + +# Get some version info +release=$(sed -n "1s/^${package} (\([^-]*\)-.*/\1/p" ${changelog}) +revision=$(sed -n "1s/^${package} (${release}-\([^)]*\).*/\1/p" ${changelog}) + +flavourconf=$(find ${family} -name config.flavour.$1) +archconfdir=$(dirname ${flavourconf}) +version="Ubuntu ${release}-${revision}-$1" +re="s/.*\(CONFIG_VERSION_SIGNATURE\).*/\1=""\"${version}\"""/" + +# Generarte .config +cat ${family}/config/config.common.${family} \ + ${archconfdir}/config.common.* \ + ${flavourconf} | sed -e "${re}" > .config diff --git a/chromeos/scripts/splitconfig b/chromeos/scripts/splitconfig new file mode 100755 index 00000000000..262fa2015c1 --- /dev/null +++ b/chromeos/scripts/splitconfig @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +import os +import re +import sys + +allconfigs = {} + +# Parse config files +for config in os.listdir("."): + # Only config.* + if not config.startswith("config."): + continue + # Ignore emacs backups + if config.endswith("~"): + continue + # Nothing that is disabled, or remnant + if re.search("\.(default|disabled|stub)$", config): + continue + + allconfigs[config] = set() + + for line in open(config): + m = re.match("#*\s*CONFIG_(\w+)[\s=](.*)$", line) + if not m: + continue + option, value = m.groups() + allconfigs[config].add((option, value)) + +# Split out common config options +common = allconfigs.values()[0].copy() +for config in allconfigs.keys(): + common &= allconfigs[config] +for config in allconfigs.keys(): + allconfigs[config] -= common +allconfigs["config.common"] = common + +# Generate new splitconfigs +for config in allconfigs.keys(): + f = open(config, "w") + command = os.path.basename(sys.argv[0]) + print >>f, "#\n# Config options generated by %s\n#" % command + for option, value in sorted(list(allconfigs[config])): + if value == "is not set": + print >>f, "# CONFIG_%s %s" % (option, value) + else: + print >>f, "CONFIG_%s=%s" % (option, value) + + f.close() diff --git a/drivers/Kconfig b/drivers/Kconfig index d0258eb26d8..851189dc42c 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -52,6 +52,8 @@ source "drivers/i2c/Kconfig" source "drivers/spi/Kconfig" +source "drivers/slimbus/Kconfig" + source "drivers/pps/Kconfig" source "drivers/ptp/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 4ea4ac9e57a..e18822cd228 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_ATA) += ata/ obj-$(CONFIG_TARGET_CORE) += target/ obj-$(CONFIG_MTD) += mtd/ obj-$(CONFIG_SPI) += spi/ +obj-$(CONFIG_SLIMBUS) += slimbus/ obj-y += net/ obj-$(CONFIG_ATM) += atm/ obj-$(CONFIG_FUSION) += message/ @@ -71,6 +72,7 @@ obj-$(CONFIG_USB_OTG_UTILS) += usb/ obj-$(CONFIG_USB) += usb/ obj-$(CONFIG_PCI) += usb/ obj-$(CONFIG_USB_GADGET) += usb/ +obj-$(CONFIG_DIAG_CHAR) += char/diag/ obj-$(CONFIG_SERIO) += input/serio/ obj-$(CONFIG_GAMEPORT) += input/gameport/ obj-$(CONFIG_INPUT) += input/ diff --git a/drivers/base/memory.c b/drivers/base/memory.c index 45d7c8fc73b..42befd11225 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -62,6 +62,9 @@ static const struct kset_uevent_ops memory_uevent_ops = { static BLOCKING_NOTIFIER_HEAD(memory_chain); +unsigned long movable_reserved_start, movable_reserved_size; +unsigned long low_power_memory_start, low_power_memory_size; + int register_memory_notifier(struct notifier_block *nb) { return blocking_notifier_chain_register(&memory_chain, nb); @@ -366,6 +369,64 @@ static int block_size_init(void) &attr_block_size_bytes.attr); } +static ssize_t +print_movable_size(struct class *class, struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%lx\n", movable_reserved_size); +} + +static CLASS_ATTR(movable_size_bytes, 0444, print_movable_size, NULL); + +static int movable_size_init(void) +{ + return sysfs_create_file(&memory_sysdev_class.kset.kobj, + &class_attr_movable_size_bytes.attr); +} + +static ssize_t +print_movable_start(struct class *class, struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%lx\n", movable_reserved_start); +} + +static CLASS_ATTR(movable_start_bytes, 0444, print_movable_start, NULL); + +static int movable_start_init(void) +{ + return sysfs_create_file(&memory_sysdev_class.kset.kobj, + &class_attr_movable_start_bytes.attr); +} + +static ssize_t +print_low_power_memory_size(struct class *class, struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%lx\n", low_power_memory_size); +} + +static CLASS_ATTR(low_power_memory_size_bytes, 0444, + print_low_power_memory_size, NULL); + +static int low_power_memory_size_init(void) +{ + return sysfs_create_file(&memory_sysdev_class.kset.kobj, + &class_attr_low_power_memory_size_bytes.attr); +} + +static ssize_t +print_low_power_memory_start(struct class *class, struct class_attribute *attr, char *buf) +{ + return sprintf(buf, "%lx\n", low_power_memory_start); +} + +static CLASS_ATTR(low_power_memory_start_bytes, 0444, + print_low_power_memory_start, NULL); + +static int low_power_memory_start_init(void) +{ + return sysfs_create_file(&memory_sysdev_class.kset.kobj, + &class_attr_low_power_memory_start_bytes.attr); +} + /* * Some architectures will have custom drivers to do this, and * will not need to do it from userspace. The fake hot-add code @@ -473,6 +534,96 @@ static inline int memory_fail_init(void) } #endif +#ifdef CONFIG_ARCH_MEMORY_REMOVE +static ssize_t +memory_remove_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t count) +{ + u64 phys_addr; + int ret; + + phys_addr = simple_strtoull(buf, NULL, 0); + + ret = physical_remove_memory(phys_addr, + PAGES_PER_SECTION << PAGE_SHIFT); + + if (ret) + count = ret; + + return count; +} +static CLASS_ATTR(remove, S_IWUSR, NULL, memory_remove_store); + +static int memory_remove_init(void) +{ + return sysfs_create_file(&memory_sysdev_class.kset.kobj, + &class_attr_remove.attr); +} + +static ssize_t +memory_active_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t count) +{ + u64 phys_addr; + int ret; + + phys_addr = simple_strtoull(buf, NULL, 0); + + ret = physical_active_memory(phys_addr, + PAGES_PER_SECTION << PAGE_SHIFT); + + if (ret) + count = ret; + + return count; +} +static CLASS_ATTR(active, S_IWUSR, NULL, memory_active_store); + +static int memory_active_init(void) +{ + return sysfs_create_file(&memory_sysdev_class.kset.kobj, + &class_attr_active.attr); +} + +static ssize_t +memory_low_power_store(struct class *class, struct class_attribute *attr, + const char *buf, size_t count) +{ + u64 phys_addr; + int ret; + + phys_addr = simple_strtoull(buf, NULL, 0); + + ret = physical_low_power_memory(phys_addr, + PAGES_PER_SECTION << PAGE_SHIFT); + + if (ret) + count = ret; + + return count; +} +static CLASS_ATTR(low_power, S_IWUSR, NULL, memory_low_power_store); + +static int memory_low_power_init(void) +{ + return sysfs_create_file(&memory_sysdev_class.kset.kobj, + &class_attr_low_power.attr); +} +#else +static inline int memory_remove_init(void) +{ + return 0; +} +static inline int memory_active_init(void) +{ + return 0; +} +static inline int memory_low_power_init(void) +{ + return 0; +} +#endif + /* * Note that phys_device is optional. It is here to allow for * differentiation between which *physical* devices each @@ -663,11 +814,32 @@ int __init memory_dev_init(void) if (!ret) ret = err; err = memory_fail_init(); + if (!ret) + ret = err; + err = memory_remove_init(); + if (!ret) + ret = err; + err = memory_active_init(); + if (!ret) + ret = err; + err = memory_low_power_init(); if (!ret) ret = err; err = block_size_init(); if (!ret) ret = err; + err = movable_size_init(); + if (!ret) + ret = err; + err = movable_start_init(); + if (!ret) + ret = err; + err = low_power_memory_size_init(); + if (!ret) + ret = err; + err = low_power_memory_start_init(); + if (!ret) + ret = err; out: if (ret) printk(KERN_ERR "%s() failed: %d\n", __func__, ret); diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index 11b41fd40c2..ea1c27a36ca 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -2,6 +2,16 @@ menu "Bluetooth device drivers" depends on BT +config BT_HCISMD + tristate "HCI SMD driver" + help + Bluetooth HCI SMD driver. + This driver is required if you want to use Bluetoth device with + SMD interface. + + Say Y here to compile support for Bluetooth USB devices into the + kernel or say M to compile is as a module (hci_smd). + config BT_HCIBTUSB tristate "HCI USB driver" depends on USB @@ -81,6 +91,17 @@ config BT_HCIUART_LL Say Y here to compile support for HCILL protocol. +config BT_HCIUART_IBS + bool "HCI_IBS protocol support" + depends on BT_HCIUART + default n + help + HCI_IBS (HCI In-Band Sleep) is a serial protocol for communication + between Bluetooth device and host. This protocol is required for + UART clock control for some Qualcomm Bluetooth devices. + + Say Y here to compile support for HCI_IBS protocol. + config BT_HCIBCM203X tristate "HCI BCM203x USB driver" depends on USB @@ -104,6 +125,14 @@ config BT_HCIBPA10X Say Y here to compile support for HCI BPA10x devices into the kernel or say M to compile it as module (bpa10x). +config BT_MSM_SLEEP + tristate "MSM Bluesleep driver" + depends on BT && SERIAL_MSM_HS + default n + help + Bluetooth MSM bluesleep driver. + This driver provides support for BTS sleep. + config BT_HCIBFUSB tristate "HCI BlueFRITZ! USB driver" depends on USB @@ -207,6 +236,14 @@ config BT_MRVL_SDIO Say Y here to compile support for Marvell BT-over-SDIO driver into the kernel or say M to compile it as module. +config MSM_BT_POWER + tristate "MSM Bluetooth Power Control" + depends on ARCH_MSM && RFKILL + default m + help + Provides a parameter to switch on/off power from PMIC + to Bluetooth device. + config BT_ATH3K tristate "Atheros firmware download driver" depends on BT_HCIBTUSB diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index f4460f4f4b7..a20a056b77b 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -2,6 +2,7 @@ # Makefile for the Linux Bluetooth HCI device drivers. # +obj-$(CONFIG_BT_HCISMD) += hci_smd.o obj-$(CONFIG_BT_HCIVHCI) += hci_vhci.o obj-$(CONFIG_BT_HCIUART) += hci_uart.o obj-$(CONFIG_BT_HCIBCM203X) += bcm203x.o @@ -28,4 +29,8 @@ hci_uart-$(CONFIG_BT_HCIUART_H4) += hci_h4.o hci_uart-$(CONFIG_BT_HCIUART_BCSP) += hci_bcsp.o hci_uart-$(CONFIG_BT_HCIUART_LL) += hci_ll.o hci_uart-$(CONFIG_BT_HCIUART_ATH3K) += hci_ath.o +hci_uart-$(CONFIG_BT_HCIUART_IBS) += hci_ibs.o hci_uart-objs := $(hci_uart-y) +obj-$(CONFIG_BT_MSM_SLEEP) += msm_bt_sleep.o +msm_bt_sleep-objs := bluesleep.o +obj-$(CONFIG_MSM_BT_POWER) += bluetooth-power.o diff --git a/drivers/bluetooth/ath3k.c b/drivers/bluetooth/ath3k.c index a5854735bb2..695d4414bd4 100644 --- a/drivers/bluetooth/ath3k.c +++ b/drivers/bluetooth/ath3k.c @@ -62,7 +62,6 @@ static struct usb_device_id ath3k_table[] = { /* Atheros AR3011 with sflash firmware*/ { USB_DEVICE(0x0CF3, 0x3002) }, - { USB_DEVICE(0x13d3, 0x3304) }, /* Atheros AR9285 Malbec with sflash firmware */ { USB_DEVICE(0x03F0, 0x311D) }, @@ -375,11 +374,6 @@ static int ath3k_probe(struct usb_interface *intf, /* load patch and sysconfig files for AR3012 */ if (id->driver_info & BTUSB_ATH3012) { - - /* New firmware with patch and sysconfig files already loaded */ - if (le16_to_cpu(udev->descriptor.bcdDevice) > 0x0001) - return -ENODEV; - ret = ath3k_load_patch(udev); if (ret < 0) { BT_ERR("Loading patch file failed"); diff --git a/drivers/bluetooth/bluecard_cs.c b/drivers/bluetooth/bluecard_cs.c index aed1904ea67..4104b7feae6 100644 --- a/drivers/bluetooth/bluecard_cs.c +++ b/drivers/bluetooth/bluecard_cs.c @@ -930,7 +930,7 @@ static void bluecard_release(struct pcmcia_device *link) pcmcia_disable_device(link); } -static const struct pcmcia_device_id bluecard_ids[] = { +static struct pcmcia_device_id bluecard_ids[] = { PCMCIA_DEVICE_PROD_ID12("BlueCard", "LSE041", 0xbaf16fbf, 0x657cc15e), PCMCIA_DEVICE_PROD_ID12("BTCFCARD", "LSE139", 0xe3987764, 0x2524b59c), PCMCIA_DEVICE_PROD_ID12("WSS", "LSE039", 0x0a0736ec, 0x24e6dfab), diff --git a/drivers/bluetooth/bluesleep.c b/drivers/bluetooth/bluesleep.c new file mode 100644 index 00000000000..0d111411256 --- /dev/null +++ b/drivers/bluetooth/bluesleep.c @@ -0,0 +1,757 @@ +/* + + 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + + Copyright (C) 2006-2007 - Motorola + Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + + Date Author Comment + ----------- -------------- -------------------------------- + 2006-Apr-28 Motorola The kernel module for running the Bluetooth(R) + Sleep-Mode Protocol from the Host side + 2006-Sep-08 Motorola Added workqueue for handling sleep work. + 2007-Jan-24 Motorola Added mbm_handle_ioi() call to ISR. + +*/ + +#include /* kernel module definitions */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include /* event notifications */ +#include "hci_uart.h" + +#define BT_SLEEP_DBG +#ifndef BT_SLEEP_DBG +#define BT_DBG(fmt, arg...) +#endif +/* + * Defines + */ + +#define VERSION "1.1" +#define PROC_DIR "bluetooth/sleep" + +struct bluesleep_info { + unsigned host_wake; + unsigned ext_wake; + unsigned host_wake_irq; + struct uart_port *uport; +}; + +/* work function */ +static void bluesleep_sleep_work(struct work_struct *work); + +/* work queue */ +DECLARE_DELAYED_WORK(sleep_workqueue, bluesleep_sleep_work); + +/* Macros for handling sleep work */ +#define bluesleep_rx_busy() schedule_delayed_work(&sleep_workqueue, 0) +#define bluesleep_tx_busy() schedule_delayed_work(&sleep_workqueue, 0) +#define bluesleep_rx_idle() schedule_delayed_work(&sleep_workqueue, 0) +#define bluesleep_tx_idle() schedule_delayed_work(&sleep_workqueue, 0) + +/* 1 second timeout */ +#define TX_TIMER_INTERVAL 1 + +/* state variable names and bit positions */ +#define BT_PROTO 0x01 +#define BT_TXDATA 0x02 +#define BT_ASLEEP 0x04 + +/* global pointer to a single hci device. */ +static struct hci_dev *bluesleep_hdev; + +static struct bluesleep_info *bsi; + +/* module usage */ +static atomic_t open_count = ATOMIC_INIT(1); + +/* + * Local function prototypes + */ + +static int bluesleep_hci_event(struct notifier_block *this, + unsigned long event, void *data); + +/* + * Global variables + */ + +/** Global state flags */ +static unsigned long flags; + +/** Tasklet to respond to change in hostwake line */ +static struct tasklet_struct hostwake_task; + +/** Transmission timer */ +static struct timer_list tx_timer; + +/** Lock for state transitions */ +static spinlock_t rw_lock; + +/** Notifier block for HCI events */ +struct notifier_block hci_event_nblock = { + .notifier_call = bluesleep_hci_event, +}; + +struct proc_dir_entry *bluetooth_dir, *sleep_dir; + +/* + * Local functions + */ + +static void hsuart_power(int on) +{ + if (on) { + msm_hs_request_clock_on(bsi->uport); + msm_hs_set_mctrl(bsi->uport, TIOCM_RTS); + } else { + msm_hs_set_mctrl(bsi->uport, 0); + msm_hs_request_clock_off(bsi->uport); + } +} + + +/** + * @return 1 if the Host can go to sleep, 0 otherwise. + */ +static inline int bluesleep_can_sleep(void) +{ + /* check if MSM_WAKE_BT_GPIO and BT_WAKE_MSM_GPIO are both deasserted */ + return gpio_get_value(bsi->ext_wake) && + gpio_get_value(bsi->host_wake) && + (bsi->uport != NULL); +} + +void bluesleep_sleep_wakeup(void) +{ + if (test_bit(BT_ASLEEP, &flags)) { + BT_DBG("waking up..."); + /* Start the timer */ + mod_timer(&tx_timer, jiffies + (TX_TIMER_INTERVAL * HZ)); + gpio_set_value(bsi->ext_wake, 0); + clear_bit(BT_ASLEEP, &flags); + /*Activating UART */ + hsuart_power(1); + } +} + +/** + * @brief@ main sleep work handling function which update the flags + * and activate and deactivate UART ,check FIFO. + */ +static void bluesleep_sleep_work(struct work_struct *work) +{ + if (bluesleep_can_sleep()) { + /* already asleep, this is an error case */ + if (test_bit(BT_ASLEEP, &flags)) { + BT_DBG("already asleep"); + return; + } + + if (msm_hs_tx_empty(bsi->uport)) { + BT_DBG("going to sleep..."); + set_bit(BT_ASLEEP, &flags); + /*Deactivating UART */ + hsuart_power(0); + } else { + + mod_timer(&tx_timer, jiffies + (TX_TIMER_INTERVAL * HZ)); + return; + } + } else { + bluesleep_sleep_wakeup(); + } +} + +/** + * A tasklet function that runs in tasklet context and reads the value + * of the HOST_WAKE GPIO pin and further defer the work. + * @param data Not used. + */ +static void bluesleep_hostwake_task(unsigned long data) +{ + BT_DBG("hostwake line change"); + + spin_lock(&rw_lock); + + if (gpio_get_value(bsi->host_wake)) + bluesleep_rx_busy(); + else + bluesleep_rx_idle(); + + spin_unlock(&rw_lock); +} + +/** + * Handles proper timer action when outgoing data is delivered to the + * HCI line discipline. Sets BT_TXDATA. + */ +static void bluesleep_outgoing_data(void) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&rw_lock, irq_flags); + + /* log data passing by */ + set_bit(BT_TXDATA, &flags); + + /* if the tx side is sleeping... */ + if (gpio_get_value(bsi->ext_wake)) { + + BT_DBG("tx was sleeping"); + bluesleep_sleep_wakeup(); + } + + spin_unlock_irqrestore(&rw_lock, irq_flags); +} + +/** + * Handles HCI device events. + * @param this Not used. + * @param event The event that occurred. + * @param data The HCI device associated with the event. + * @return NOTIFY_DONE. + */ +static int bluesleep_hci_event(struct notifier_block *this, + unsigned long event, void *data) +{ + struct hci_dev *hdev = (struct hci_dev *) data; + struct hci_uart *hu; + struct uart_state *state; + + if (!hdev) + return NOTIFY_DONE; + + switch (event) { + case HCI_DEV_REG: + if (!bluesleep_hdev) { + bluesleep_hdev = hdev; + hu = (struct hci_uart *) hdev->driver_data; + state = (struct uart_state *) hu->tty->driver_data; + bsi->uport = state->uart_port; + } + break; + case HCI_DEV_UNREG: + bluesleep_hdev = NULL; + bsi->uport = NULL; + break; + case HCI_DEV_WRITE: + bluesleep_outgoing_data(); + break; + } + + return NOTIFY_DONE; +} + +/** + * Handles transmission timer expiration. + * @param data Not used. + */ +static void bluesleep_tx_timer_expire(unsigned long data) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&rw_lock, irq_flags); + + BT_DBG("Tx timer expired"); + + /* were we silent during the last timeout? */ + if (!test_bit(BT_TXDATA, &flags)) { + BT_DBG("Tx has been idle"); + gpio_set_value(bsi->ext_wake, 1); + bluesleep_tx_idle(); + } else { + BT_DBG("Tx data during last period"); + mod_timer(&tx_timer, jiffies + (TX_TIMER_INTERVAL*HZ)); + } + + /* clear the incoming data flag */ + clear_bit(BT_TXDATA, &flags); + + spin_unlock_irqrestore(&rw_lock, irq_flags); +} + +/** + * Schedules a tasklet to run when receiving an interrupt on the + * HOST_WAKE GPIO pin. + * @param irq Not used. + * @param dev_id Not used. + */ +static irqreturn_t bluesleep_hostwake_isr(int irq, void *dev_id) +{ + /* schedule a tasklet to handle the change in the host wake line */ + tasklet_schedule(&hostwake_task); + return IRQ_HANDLED; +} + +/** + * Starts the Sleep-Mode Protocol on the Host. + * @return On success, 0. On error, -1, and errno is set + * appropriately. + */ +static int bluesleep_start(void) +{ + int retval; + unsigned long irq_flags; + + spin_lock_irqsave(&rw_lock, irq_flags); + + if (test_bit(BT_PROTO, &flags)) { + spin_unlock_irqrestore(&rw_lock, irq_flags); + return 0; + } + + spin_unlock_irqrestore(&rw_lock, irq_flags); + + if (!atomic_dec_and_test(&open_count)) { + atomic_inc(&open_count); + return -EBUSY; + } + + /* start the timer */ + + mod_timer(&tx_timer, jiffies + (TX_TIMER_INTERVAL*HZ)); + + /* assert BT_WAKE */ + gpio_set_value(bsi->ext_wake, 0); + retval = request_irq(bsi->host_wake_irq, bluesleep_hostwake_isr, + IRQF_DISABLED | IRQF_TRIGGER_FALLING, + "bluetooth hostwake", NULL); + if (retval < 0) { + BT_ERR("Couldn't acquire BT_HOST_WAKE IRQ"); + goto fail; + } + + retval = enable_irq_wake(bsi->host_wake_irq); + if (retval < 0) { + BT_ERR("Couldn't enable BT_HOST_WAKE as wakeup interrupt"); + free_irq(bsi->host_wake_irq, NULL); + goto fail; + } + + set_bit(BT_PROTO, &flags); + return 0; +fail: + del_timer(&tx_timer); + atomic_inc(&open_count); + + return retval; +} + +/** + * Stops the Sleep-Mode Protocol on the Host. + */ +static void bluesleep_stop(void) +{ + unsigned long irq_flags; + + spin_lock_irqsave(&rw_lock, irq_flags); + + if (!test_bit(BT_PROTO, &flags)) { + spin_unlock_irqrestore(&rw_lock, irq_flags); + return; + } + + /* assert BT_WAKE */ + gpio_set_value(bsi->ext_wake, 0); + del_timer(&tx_timer); + clear_bit(BT_PROTO, &flags); + + if (test_bit(BT_ASLEEP, &flags)) { + clear_bit(BT_ASLEEP, &flags); + hsuart_power(1); + } + + atomic_inc(&open_count); + + spin_unlock_irqrestore(&rw_lock, irq_flags); + if (disable_irq_wake(bsi->host_wake_irq)) + BT_ERR("Couldn't disable hostwake IRQ wakeup mode\n"); + free_irq(bsi->host_wake_irq, NULL); +} +/** + * Read the BT_WAKE GPIO pin value via the proc interface. + * When this function returns, page will contain a 1 if the + * pin is high, 0 otherwise. + * @param page Buffer for writing data. + * @param start Not used. + * @param offset Not used. + * @param count Not used. + * @param eof Whether or not there is more data to be read. + * @param data Not used. + * @return The number of bytes written. + */ +static int bluepower_read_proc_btwake(char *page, char **start, off_t offset, + int count, int *eof, void *data) +{ + *eof = 1; + return sprintf(page, "btwake:%u\n", gpio_get_value(bsi->ext_wake)); +} + +/** + * Write the BT_WAKE GPIO pin value via the proc interface. + * @param file Not used. + * @param buffer The buffer to read from. + * @param count The number of bytes to be written. + * @param data Not used. + * @return On success, the number of bytes written. On error, -1, and + * errno is set appropriately. + */ +static int bluepower_write_proc_btwake(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + char *buf; + + if (count < 1) + return -EINVAL; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, buffer, count)) { + kfree(buf); + return -EFAULT; + } + + if (buf[0] == '0') { + gpio_set_value(bsi->ext_wake, 0); + } else if (buf[0] == '1') { + gpio_set_value(bsi->ext_wake, 1); + } else { + kfree(buf); + return -EINVAL; + } + + kfree(buf); + return count; +} + +/** + * Read the BT_HOST_WAKE GPIO pin value via the proc interface. + * When this function returns, page will contain a 1 if the pin + * is high, 0 otherwise. + * @param page Buffer for writing data. + * @param start Not used. + * @param offset Not used. + * @param count Not used. + * @param eof Whether or not there is more data to be read. + * @param data Not used. + * @return The number of bytes written. + */ +static int bluepower_read_proc_hostwake(char *page, char **start, off_t offset, + int count, int *eof, void *data) +{ + *eof = 1; + return sprintf(page, "hostwake: %u \n", gpio_get_value(bsi->host_wake)); +} + + +/** + * Read the low-power status of the Host via the proc interface. + * When this function returns, page contains a 1 if the Host + * is asleep, 0 otherwise. + * @param page Buffer for writing data. + * @param start Not used. + * @param offset Not used. + * @param count Not used. + * @param eof Whether or not there is more data to be read. + * @param data Not used. + * @return The number of bytes written. + */ +static int bluesleep_read_proc_asleep(char *page, char **start, off_t offset, + int count, int *eof, void *data) +{ + unsigned int asleep; + + asleep = test_bit(BT_ASLEEP, &flags) ? 1 : 0; + *eof = 1; + return sprintf(page, "asleep: %u\n", asleep); +} + +/** + * Read the low-power protocol being used by the Host via the proc interface. + * When this function returns, page will contain a 1 if the Host + * is using the Sleep Mode Protocol, 0 otherwise. + * @param page Buffer for writing data. + * @param start Not used. + * @param offset Not used. + * @param count Not used. + * @param eof Whether or not there is more data to be read. + * @param data Not used. + * @return The number of bytes written. + */ +static int bluesleep_read_proc_proto(char *page, char **start, off_t offset, + int count, int *eof, void *data) +{ + unsigned int proto; + + proto = test_bit(BT_PROTO, &flags) ? 1 : 0; + *eof = 1; + return sprintf(page, "proto: %u\n", proto); +} + +/** + * Modify the low-power protocol used by the Host via the proc interface. + * @param file Not used. + * @param buffer The buffer to read from. + * @param count The number of bytes to be written. + * @param data Not used. + * @return On success, the number of bytes written. On error, -1, and + * errno is set appropriately. + */ +static int bluesleep_write_proc_proto(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + char proto; + + if (count < 1) + return -EINVAL; + + if (copy_from_user(&proto, buffer, 1)) + return -EFAULT; + + if (proto == '0') + bluesleep_stop(); + else + bluesleep_start(); + + /* claim that we wrote everything */ + return count; +} + +static int __init bluesleep_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + + bsi = kzalloc(sizeof(struct bluesleep_info), GFP_KERNEL); + if (!bsi) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "gpio_host_wake"); + if (!res) { + BT_ERR("couldn't find host_wake gpio\n"); + ret = -ENODEV; + goto free_bsi; + } + bsi->host_wake = res->start; + + ret = gpio_request(bsi->host_wake, "bt_host_wake"); + if (ret) + goto free_bsi; + ret = gpio_direction_input(bsi->host_wake); + if (ret) + goto free_bt_host_wake; + + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + "gpio_ext_wake"); + if (!res) { + BT_ERR("couldn't find ext_wake gpio\n"); + ret = -ENODEV; + goto free_bt_host_wake; + } + bsi->ext_wake = res->start; + + ret = gpio_request(bsi->ext_wake, "bt_ext_wake"); + if (ret) + goto free_bt_host_wake; + /* assert bt wake */ + ret = gpio_direction_output(bsi->ext_wake, 0); + if (ret) + goto free_bt_ext_wake; + + bsi->host_wake_irq = platform_get_irq_byname(pdev, "host_wake"); + if (bsi->host_wake_irq < 0) { + BT_ERR("couldn't find host_wake irq\n"); + ret = -ENODEV; + goto free_bt_ext_wake; + } + + + return 0; + +free_bt_ext_wake: + gpio_free(bsi->ext_wake); +free_bt_host_wake: + gpio_free(bsi->host_wake); +free_bsi: + kfree(bsi); + return ret; +} + +static int bluesleep_remove(struct platform_device *pdev) +{ + /* assert bt wake */ + gpio_set_value(bsi->ext_wake, 0); + if (test_bit(BT_PROTO, &flags)) { + if (disable_irq_wake(bsi->host_wake_irq)) + BT_ERR("Couldn't disable hostwake IRQ wakeup mode \n"); + free_irq(bsi->host_wake_irq, NULL); + del_timer(&tx_timer); + if (test_bit(BT_ASLEEP, &flags)) + hsuart_power(1); + } + + gpio_free(bsi->host_wake); + gpio_free(bsi->ext_wake); + kfree(bsi); + return 0; +} + +static struct platform_driver bluesleep_driver = { + .remove = bluesleep_remove, + .driver = { + .name = "bluesleep", + .owner = THIS_MODULE, + }, +}; +/** + * Initializes the module. + * @return On success, 0. On error, -1, and errno is set + * appropriately. + */ +static int __init bluesleep_init(void) +{ + int retval; + struct proc_dir_entry *ent; + + BT_INFO("MSM Sleep Mode Driver Ver %s", VERSION); + + retval = platform_driver_probe(&bluesleep_driver, bluesleep_probe); + if (retval) + return retval; + + bluesleep_hdev = NULL; + + bluetooth_dir = proc_mkdir("bluetooth", NULL); + if (bluetooth_dir == NULL) { + BT_ERR("Unable to create /proc/bluetooth directory"); + return -ENOMEM; + } + + sleep_dir = proc_mkdir("sleep", bluetooth_dir); + if (sleep_dir == NULL) { + BT_ERR("Unable to create /proc/%s directory", PROC_DIR); + return -ENOMEM; + } + + /* Creating read/write "btwake" entry */ + ent = create_proc_entry("btwake", 0, sleep_dir); + if (ent == NULL) { + BT_ERR("Unable to create /proc/%s/btwake entry", PROC_DIR); + retval = -ENOMEM; + goto fail; + } + ent->read_proc = bluepower_read_proc_btwake; + ent->write_proc = bluepower_write_proc_btwake; + + /* read only proc entries */ + if (create_proc_read_entry("hostwake", 0, sleep_dir, + bluepower_read_proc_hostwake, NULL) == NULL) { + BT_ERR("Unable to create /proc/%s/hostwake entry", PROC_DIR); + retval = -ENOMEM; + goto fail; + } + + /* read/write proc entries */ + ent = create_proc_entry("proto", 0, sleep_dir); + if (ent == NULL) { + BT_ERR("Unable to create /proc/%s/proto entry", PROC_DIR); + retval = -ENOMEM; + goto fail; + } + ent->read_proc = bluesleep_read_proc_proto; + ent->write_proc = bluesleep_write_proc_proto; + + /* read only proc entries */ + if (create_proc_read_entry("asleep", 0, + sleep_dir, bluesleep_read_proc_asleep, NULL) == NULL) { + BT_ERR("Unable to create /proc/%s/asleep entry", PROC_DIR); + retval = -ENOMEM; + goto fail; + } + + flags = 0; /* clear all status bits */ + + /* Initialize spinlock. */ + spin_lock_init(&rw_lock); + + /* Initialize timer */ + init_timer(&tx_timer); + tx_timer.function = bluesleep_tx_timer_expire; + tx_timer.data = 0; + + /* initialize host wake tasklet */ + tasklet_init(&hostwake_task, bluesleep_hostwake_task, 0); + + hci_register_notifier(&hci_event_nblock); + + return 0; + +fail: + remove_proc_entry("asleep", sleep_dir); + remove_proc_entry("proto", sleep_dir); + remove_proc_entry("hostwake", sleep_dir); + remove_proc_entry("btwake", sleep_dir); + remove_proc_entry("sleep", bluetooth_dir); + remove_proc_entry("bluetooth", 0); + return retval; +} + +/** + * Cleans up the module. + */ +static void __exit bluesleep_exit(void) +{ + hci_unregister_notifier(&hci_event_nblock); + platform_driver_unregister(&bluesleep_driver); + + remove_proc_entry("asleep", sleep_dir); + remove_proc_entry("proto", sleep_dir); + remove_proc_entry("hostwake", sleep_dir); + remove_proc_entry("btwake", sleep_dir); + remove_proc_entry("sleep", bluetooth_dir); + remove_proc_entry("bluetooth", 0); +} + +module_init(bluesleep_init); +module_exit(bluesleep_exit); + +MODULE_DESCRIPTION("Bluetooth Sleep Mode Driver ver %s " VERSION); +#ifdef MODULE_LICENSE +MODULE_LICENSE("GPL"); +#endif diff --git a/drivers/bluetooth/bluetooth-power.c b/drivers/bluetooth/bluetooth-power.c new file mode 100644 index 00000000000..3bf49d1b451 --- /dev/null +++ b/drivers/bluetooth/bluetooth-power.c @@ -0,0 +1,138 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * Bluetooth Power Switch Module + * controls power to external Bluetooth device + * with interface to power management device + */ + +#include +#include +#include +#include +#include + +static bool previous; + +static int bluetooth_toggle_radio(void *data, bool blocked) +{ + int ret = 0; + int (*power_control)(int enable); + + power_control = data; + if (previous != blocked) + ret = (*power_control)(!blocked); + if (!ret) + previous = blocked; + return ret; +} + +static const struct rfkill_ops bluetooth_power_rfkill_ops = { + .set_block = bluetooth_toggle_radio, +}; + +static int bluetooth_power_rfkill_probe(struct platform_device *pdev) +{ + struct rfkill *rfkill; + int ret; + + rfkill = rfkill_alloc("bt_power", &pdev->dev, RFKILL_TYPE_BLUETOOTH, + &bluetooth_power_rfkill_ops, + pdev->dev.platform_data); + + if (!rfkill) { + dev_err(&pdev->dev, "rfkill allocate failed\n"); + return -ENOMEM; + } + + /* force Bluetooth off during init to allow for user control */ + rfkill_init_sw_state(rfkill, 1); + previous = 1; + + ret = rfkill_register(rfkill); + if (ret) { + dev_err(&pdev->dev, "rfkill register failed=%d\n", ret); + rfkill_destroy(rfkill); + return ret; + } + + platform_set_drvdata(pdev, rfkill); + + return 0; +} + +static void bluetooth_power_rfkill_remove(struct platform_device *pdev) +{ + struct rfkill *rfkill; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + rfkill = platform_get_drvdata(pdev); + if (rfkill) + rfkill_unregister(rfkill); + rfkill_destroy(rfkill); + platform_set_drvdata(pdev, NULL); +} + +static int __devinit bt_power_probe(struct platform_device *pdev) +{ + int ret = 0; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "platform data not initialized\n"); + return -ENOSYS; + } + + ret = bluetooth_power_rfkill_probe(pdev); + + return ret; +} + +static int __devexit bt_power_remove(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "%s\n", __func__); + + bluetooth_power_rfkill_remove(pdev); + + return 0; +} + +static struct platform_driver bt_power_driver = { + .probe = bt_power_probe, + .remove = __devexit_p(bt_power_remove), + .driver = { + .name = "bt_power", + .owner = THIS_MODULE, + }, +}; + +static int __init bluetooth_power_init(void) +{ + int ret; + + ret = platform_driver_register(&bt_power_driver); + return ret; +} + +static void __exit bluetooth_power_exit(void) +{ + platform_driver_unregister(&bt_power_driver); +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM Bluetooth power control driver"); +MODULE_VERSION("1.40"); + +module_init(bluetooth_power_init); +module_exit(bluetooth_power_exit); diff --git a/drivers/bluetooth/bt3c_cs.c b/drivers/bluetooth/bt3c_cs.c index 4fc01949d39..0c8a6558749 100644 --- a/drivers/bluetooth/bt3c_cs.c +++ b/drivers/bluetooth/bt3c_cs.c @@ -761,7 +761,7 @@ static void bt3c_release(struct pcmcia_device *link) } -static const struct pcmcia_device_id bt3c_ids[] = { +static struct pcmcia_device_id bt3c_ids[] = { PCMCIA_DEVICE_PROD_ID13("3COM", "Bluetooth PC Card", 0xefce0a31, 0xd4ce9b02), PCMCIA_DEVICE_NULL }; diff --git a/drivers/bluetooth/btmrvl_debugfs.c b/drivers/bluetooth/btmrvl_debugfs.c index 8ecf4c6c287..fd6305bf953 100644 --- a/drivers/bluetooth/btmrvl_debugfs.c +++ b/drivers/bluetooth/btmrvl_debugfs.c @@ -64,8 +64,6 @@ static ssize_t btmrvl_hscfgcmd_write(struct file *file, return -EFAULT; ret = strict_strtol(buf, 10, &result); - if (ret) - return ret; priv->btmrvl_dev.hscfgcmd = result; @@ -110,8 +108,6 @@ static ssize_t btmrvl_psmode_write(struct file *file, const char __user *ubuf, return -EFAULT; ret = strict_strtol(buf, 10, &result); - if (ret) - return ret; priv->btmrvl_dev.psmode = result; @@ -151,8 +147,6 @@ static ssize_t btmrvl_pscmd_write(struct file *file, const char __user *ubuf, return -EFAULT; ret = strict_strtol(buf, 10, &result); - if (ret) - return ret; priv->btmrvl_dev.pscmd = result; @@ -197,8 +191,6 @@ static ssize_t btmrvl_gpiogap_write(struct file *file, const char __user *ubuf, return -EFAULT; ret = strict_strtol(buf, 16, &result); - if (ret) - return ret; priv->btmrvl_dev.gpio_gap = result; @@ -238,8 +230,6 @@ static ssize_t btmrvl_hscmd_write(struct file *file, const char __user *ubuf, return -EFAULT; ret = strict_strtol(buf, 10, &result); - if (ret) - return ret; priv->btmrvl_dev.hscmd = result; if (priv->btmrvl_dev.hscmd) { @@ -282,8 +272,6 @@ static ssize_t btmrvl_hsmode_write(struct file *file, const char __user *ubuf, return -EFAULT; ret = strict_strtol(buf, 10, &result); - if (ret) - return ret; priv->btmrvl_dev.hsmode = result; diff --git a/drivers/bluetooth/btuart_cs.c b/drivers/bluetooth/btuart_cs.c index 526b61807d9..f8a0708e231 100644 --- a/drivers/bluetooth/btuart_cs.c +++ b/drivers/bluetooth/btuart_cs.c @@ -689,7 +689,7 @@ static void btuart_release(struct pcmcia_device *link) pcmcia_disable_device(link); } -static const struct pcmcia_device_id btuart_ids[] = { +static struct pcmcia_device_id btuart_ids[] = { /* don't use this driver. Use serial_cs + hci_uart instead */ PCMCIA_DEVICE_NULL }; diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 91d13a9e8c6..14776e5049c 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -54,7 +54,6 @@ static struct usb_driver btusb_driver; #define BTUSB_BCM92035 0x10 #define BTUSB_BROKEN_ISOC 0x20 #define BTUSB_WRONG_SCO_MTU 0x40 -#define BTUSB_ATH3012 0x80 static struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ @@ -105,13 +104,12 @@ static struct usb_device_id blacklist_table[] = { /* Atheros 3011 with sflash firmware */ { USB_DEVICE(0x0cf3, 0x3002), .driver_info = BTUSB_IGNORE }, - { USB_DEVICE(0x13d3, 0x3304), .driver_info = BTUSB_IGNORE }, /* Atheros AR9285 Malbec with sflash firmware */ { USB_DEVICE(0x03f0, 0x311d), .driver_info = BTUSB_IGNORE }, /* Atheros 3012 with sflash firmware */ - { USB_DEVICE(0x0cf3, 0x3004), .driver_info = BTUSB_ATH3012 }, + { USB_DEVICE(0x0cf3, 0x3004), .driver_info = BTUSB_IGNORE }, /* Atheros AR5BBU12 with sflash firmware */ { USB_DEVICE(0x0489, 0xe02c), .driver_info = BTUSB_IGNORE }, @@ -438,7 +436,7 @@ static void btusb_isoc_complete(struct urb *urb) } } -static inline void __fill_isoc_descriptor(struct urb *urb, int len, int mtu) +static void inline __fill_isoc_descriptor(struct urb *urb, int len, int mtu) { int i, offset = 0; @@ -786,7 +784,7 @@ static void btusb_notify(struct hci_dev *hdev, unsigned int evt) } } -static inline int __set_isoc_interface(struct hci_dev *hdev, int altsetting) +static int inline __set_isoc_interface(struct hci_dev *hdev, int altsetting) { struct btusb_data *data = hdev->driver_data; struct usb_interface *intf = data->isoc; @@ -915,15 +913,6 @@ static int btusb_probe(struct usb_interface *intf, if (ignore_sniffer && id->driver_info & BTUSB_SNIFFER) return -ENODEV; - if (id->driver_info & BTUSB_ATH3012) { - struct usb_device *udev = interface_to_usbdev(intf); - - /* Old firmware would otherwise let ath3k driver load - * patch and sysconfig files */ - if (le16_to_cpu(udev->descriptor.bcdDevice) <= 0x0001) - return -ENODEV; - } - data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; diff --git a/drivers/bluetooth/dtl1_cs.c b/drivers/bluetooth/dtl1_cs.c index 5e4c2de9fc3..26ee0cf88d2 100644 --- a/drivers/bluetooth/dtl1_cs.c +++ b/drivers/bluetooth/dtl1_cs.c @@ -636,7 +636,7 @@ static void dtl1_release(struct pcmcia_device *link) } -static const struct pcmcia_device_id dtl1_ids[] = { +static struct pcmcia_device_id dtl1_ids[] = { PCMCIA_DEVICE_PROD_ID12("Nokia Mobile Phones", "DTL-1", 0xe1bfdd64, 0xe168480d), PCMCIA_DEVICE_PROD_ID12("Nokia Mobile Phones", "DTL-4", 0xe1bfdd64, 0x9102bc82), PCMCIA_DEVICE_PROD_ID12("Socket", "CF", 0xb38bcc2e, 0x44ebf863), diff --git a/drivers/bluetooth/hci_ibs.c b/drivers/bluetooth/hci_ibs.c new file mode 100644 index 00000000000..2a6f3f84b93 --- /dev/null +++ b/drivers/bluetooth/hci_ibs.c @@ -0,0 +1,820 @@ +/* + * Qualcomm's Bluetooth Software In-Band Sleep UART protocol + * + * HCI_IBS (HCI In-Band Sleep) is Qualcomm's power management + * protocol extension to H4. + * + * Copyright (C) 2007 Texas Instruments, Inc. + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * Acknowledgements: + * This file is based on hci_ll.c, which was... + * Written by Ohad Ben-Cohen + * which was in turn based on hci_h4.c, which was written + * by Maxim Krasnyansky and Marcel Holtmann. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SERIAL_MSM_HS +#include +#endif + +#include +#include + +#include "hci_uart.h" + +/* HCI_IBS protocol messages */ +#define HCI_IBS_SLEEP_IND 0xFE +#define HCI_IBS_WAKE_IND 0xFD +#define HCI_IBS_WAKE_ACK 0xFC + +/* HCI_IBS receiver States */ +#define HCI_IBS_W4_PACKET_TYPE 0 +#define HCI_IBS_W4_EVENT_HDR 1 +#define HCI_IBS_W4_ACL_HDR 2 +#define HCI_IBS_W4_SCO_HDR 3 +#define HCI_IBS_W4_DATA 4 + +/* HCI_IBS transmit side sleep protocol states */ +enum tx_ibs_states_e { + HCI_IBS_TX_ASLEEP, + HCI_IBS_TX_WAKING, + HCI_IBS_TX_AWAKE, +}; + +/* HCI_IBS receive side sleep protocol states */ +enum rx_states_e { + HCI_IBS_RX_ASLEEP, + HCI_IBS_RX_AWAKE, +}; + +/* HCI_IBS transmit and receive side clock state vote */ +enum hci_ibs_clock_state_vote_e { + HCI_IBS_VOTE_STATS_UPDATE, + HCI_IBS_TX_VOTE_CLOCK_ON, + HCI_IBS_TX_VOTE_CLOCK_OFF, + HCI_IBS_RX_VOTE_CLOCK_ON, + HCI_IBS_RX_VOTE_CLOCK_OFF, +}; + +static unsigned long wake_retrans = 1; +static unsigned long tx_idle_delay = (HZ * 2); + +struct hci_ibs_cmd { + u8 cmd; +} __attribute__((packed)); + +struct ibs_struct { + unsigned long rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; + struct sk_buff_head txq; + struct sk_buff_head tx_wait_q; /* HCI_IBS wait queue */ + spinlock_t hci_ibs_lock; /* HCI_IBS state lock */ + unsigned long tx_ibs_state; /* HCI_IBS transmit side power state */ + unsigned long rx_ibs_state; /* HCI_IBS receive side power state */ + unsigned long tx_vote; /* clock must be on for TX */ + unsigned long rx_vote; /* clock must be on for RX */ + struct timer_list tx_idle_timer; + struct timer_list wake_retrans_timer; + /* debug */ + unsigned long ibs_sent_wacks; + unsigned long ibs_sent_slps; + unsigned long ibs_sent_wakes; + unsigned long ibs_recv_wacks; + unsigned long ibs_recv_slps; + unsigned long ibs_recv_wakes; + unsigned long vote_last_jif; + unsigned long vote_on_ticks; + unsigned long vote_off_ticks; + unsigned long tx_votes_on; + unsigned long rx_votes_on; + unsigned long tx_votes_off; + unsigned long rx_votes_off; + unsigned long votes_on; + unsigned long votes_off; +}; + +#ifdef CONFIG_SERIAL_MSM_HS +static void __ibs_msm_serial_clock_on(struct tty_struct *tty) +{ + struct uart_state *state = tty->driver_data; + struct uart_port *port = state->uart_port; + + msm_hs_request_clock_on(port); +} + +static void __ibs_msm_serial_clock_request_off(struct tty_struct *tty) +{ + struct uart_state *state = tty->driver_data; + struct uart_port *port = state->uart_port; + + msm_hs_request_clock_off(port); +} +#else +static inline void __ibs_msm_serial_clock_on(struct tty_struct *tty) {} +static inline void __ibs_msm_serial_clock_request_off(struct tty_struct *tty) {} +#endif + +/* clock_vote needs to be called with the ibs lock held */ +static void ibs_msm_serial_clock_vote(unsigned long vote, struct hci_uart *hu) +{ + struct ibs_struct *ibs = hu->priv; + + unsigned long old_vote = (ibs->tx_vote | ibs->rx_vote); + unsigned long new_vote; + + switch (vote) { + default: /* error */ + BT_ERR("voting irregularity"); + return; + case HCI_IBS_VOTE_STATS_UPDATE: + if (old_vote) + ibs->vote_off_ticks += (jiffies - ibs->vote_last_jif); + else + ibs->vote_on_ticks += (jiffies - ibs->vote_last_jif); + return; + case HCI_IBS_TX_VOTE_CLOCK_ON: + ibs->tx_vote = 1; + ibs->tx_votes_on++; + new_vote = 1; + break; + case HCI_IBS_RX_VOTE_CLOCK_ON: + ibs->rx_vote = 1; + ibs->rx_votes_on++; + new_vote = 1; + break; + case HCI_IBS_TX_VOTE_CLOCK_OFF: + ibs->tx_vote = 0; + ibs->tx_votes_off++; + new_vote = ibs->rx_vote | ibs->tx_vote; + break; + case HCI_IBS_RX_VOTE_CLOCK_OFF: + ibs->rx_vote = 0; + ibs->rx_votes_off++; + new_vote = ibs->rx_vote | ibs->tx_vote; + break; + } + if (new_vote != old_vote) { + if (new_vote) + __ibs_msm_serial_clock_on(hu->tty); + else + __ibs_msm_serial_clock_request_off(hu->tty); + + BT_DBG("HCIUART_IBS: vote msm_serial_hs clock %lu(%lu)", + new_vote, vote); + /* debug */ + if (new_vote) { + ibs->votes_on++; + ibs->vote_off_ticks += (jiffies - ibs->vote_last_jif); + } else { + ibs->votes_off++; + ibs->vote_on_ticks += (jiffies - ibs->vote_last_jif); + } + ibs->vote_last_jif = jiffies; + } +} + +/* + * Builds and sends an HCI_IBS command packet. + * These are very simple packets with only 1 cmd byte + */ +static int send_hci_ibs_cmd(u8 cmd, struct hci_uart *hu) +{ + int err = 0; + struct sk_buff *skb = NULL; + struct ibs_struct *ibs = hu->priv; + struct hci_ibs_cmd *hci_ibs_packet; + + BT_DBG("hu %p cmd 0x%x", hu, cmd); + + /* allocate packet */ + skb = bt_skb_alloc(1, GFP_ATOMIC); + if (!skb) { + BT_ERR("cannot allocate memory for HCI_IBS packet"); + err = -ENOMEM; + goto out; + } + + /* prepare packet */ + hci_ibs_packet = (struct hci_ibs_cmd *) skb_put(skb, 1); + hci_ibs_packet->cmd = cmd; + skb->dev = (void *) hu->hdev; + + /* send packet */ + skb_queue_tail(&ibs->txq, skb); +out: + return err; +} + +static void hci_ibs_tx_idle_timeout(unsigned long arg) +{ + struct hci_uart *hu = (struct hci_uart *) arg; + struct ibs_struct *ibs = hu->priv; + unsigned long flags; + unsigned long vote_tx_sleep = 0; + + BT_DBG("hu %p idle timeout in %lu state", hu, ibs->tx_ibs_state); + + spin_lock_irqsave_nested(&ibs->hci_ibs_lock, + flags, SINGLE_DEPTH_NESTING); + + switch (ibs->tx_ibs_state) { + default: + case HCI_IBS_TX_ASLEEP: + case HCI_IBS_TX_WAKING: + BT_ERR("spurrious timeout in tx state %ld", ibs->tx_ibs_state); + goto out; + case HCI_IBS_TX_AWAKE: /* TX_IDLE, go to SLEEP */ + if (send_hci_ibs_cmd(HCI_IBS_SLEEP_IND, hu) < 0) { + BT_ERR("cannot send SLEEP to device"); + goto out; + } + ibs->tx_ibs_state = HCI_IBS_TX_ASLEEP; + ibs->ibs_sent_slps++; /* debug */ + vote_tx_sleep = 1; + break; + } + + spin_unlock_irqrestore(&ibs->hci_ibs_lock, flags); + + hci_uart_tx_wakeup(hu); /* run HCI tx handling unlocked */ + + if (!vote_tx_sleep) + return; + /* now that message queued to tty driver, vote for tty clocks off */ + /* It is up to the tty driver to pend the clocks off until tx done. */ + + spin_lock_irqsave_nested(&ibs->hci_ibs_lock, + flags, SINGLE_DEPTH_NESTING); + ibs_msm_serial_clock_vote(HCI_IBS_TX_VOTE_CLOCK_OFF, hu); +out: + spin_unlock_irqrestore(&ibs->hci_ibs_lock, flags); +} + +static void hci_ibs_wake_retrans_timeout(unsigned long arg) +{ + struct hci_uart *hu = (struct hci_uart *) arg; + struct ibs_struct *ibs = hu->priv; + unsigned long flags; + unsigned long retransmit = 0; + + BT_DBG("hu %p wake retransmit timeout in %lu state", + hu, ibs->tx_ibs_state); + + spin_lock_irqsave_nested(&ibs->hci_ibs_lock, + flags, SINGLE_DEPTH_NESTING); + + switch (ibs->tx_ibs_state) { + default: + case HCI_IBS_TX_ASLEEP: + case HCI_IBS_TX_AWAKE: + BT_ERR("spurrious timeout tx state %ld", ibs->tx_ibs_state); + goto out; + case HCI_IBS_TX_WAKING: /* No WAKE_ACK, retransmit WAKE */ + retransmit = 1; + if (send_hci_ibs_cmd(HCI_IBS_WAKE_IND, hu) < 0) { + BT_ERR("cannot acknowledge device wake up"); + goto out; + } + ibs->ibs_sent_wakes++; /* debug */ + mod_timer(&ibs->wake_retrans_timer, jiffies + wake_retrans); + break; + } +out: + spin_unlock_irqrestore(&ibs->hci_ibs_lock, flags); + if (retransmit) + hci_uart_tx_wakeup(hu); +} + +/* Initialize protocol */ +static int ibs_open(struct hci_uart *hu) +{ + struct ibs_struct *ibs; + + BT_DBG("hu %p", hu); + + ibs = kzalloc(sizeof(*ibs), GFP_ATOMIC); + if (!ibs) + return -ENOMEM; + + skb_queue_head_init(&ibs->txq); + skb_queue_head_init(&ibs->tx_wait_q); + spin_lock_init(&ibs->hci_ibs_lock); + + /* Assume we start with both sides asleep -- extra wakes OK */ + ibs->tx_ibs_state = HCI_IBS_TX_ASLEEP; + ibs->rx_ibs_state = HCI_IBS_RX_ASLEEP; + /* clocks actually on, but we start votes off */ + ibs->tx_vote = 0; + ibs->rx_vote = 0; + + /* debug */ + ibs->ibs_sent_wacks = 0; + ibs->ibs_sent_slps = 0; + ibs->ibs_sent_wakes = 0; + ibs->ibs_recv_wacks = 0; + ibs->ibs_recv_slps = 0; + ibs->ibs_recv_wakes = 0; + ibs->vote_last_jif = jiffies; + ibs->vote_on_ticks = 0; + ibs->vote_off_ticks = 0; + ibs->votes_on = 0; + ibs->votes_off = 0; + ibs->tx_votes_on = 0; + ibs->tx_votes_off = 0; + ibs->rx_votes_on = 0; + ibs->rx_votes_off = 0; + + hu->priv = ibs; + + init_timer(&ibs->wake_retrans_timer); + ibs->wake_retrans_timer.function = hci_ibs_wake_retrans_timeout; + ibs->wake_retrans_timer.data = (u_long) hu; + + init_timer(&ibs->tx_idle_timer); + ibs->tx_idle_timer.function = hci_ibs_tx_idle_timeout; + ibs->tx_idle_timer.data = (u_long) hu; + + BT_INFO("HCI_IBS open, tx_idle_delay=%lu, wake_retrans=%lu", + tx_idle_delay, wake_retrans); + + return 0; +} + +void ibs_log_local_stats(struct ibs_struct *ibs) +{ + BT_INFO("HCI_IBS stats: tx_idle_delay=%lu, wake_retrans=%lu", + tx_idle_delay, wake_retrans); + + BT_INFO("HCI_IBS stats: tx_ibs_state=%lu, rx_ibs_state=%lu", + ibs->tx_ibs_state, ibs->rx_ibs_state); + BT_INFO("HCI_IBS stats: sent: sleep=%lu, wake=%lu, wake_ack=%lu", + ibs->ibs_sent_slps, ibs->ibs_sent_wakes, ibs->ibs_sent_wacks); + BT_INFO("HCI_IBS stats: recv: sleep=%lu, wake=%lu, wake_ack=%lu", + ibs->ibs_recv_slps, ibs->ibs_recv_wakes, ibs->ibs_recv_wacks); + + BT_INFO("HCI_IBS stats: queues: txq=%s, txwaitq=%s", + skb_queue_empty(&(ibs->txq)) ? "empty" : "full", + skb_queue_empty(&(ibs->tx_wait_q)) ? "empty" : "full"); + + BT_INFO("HCI_IBS stats: vote state: tx=%lu, rx=%lu", + ibs->tx_vote, ibs->rx_vote); + BT_INFO("HCI_IBS stats: tx votes cast: on=%lu, off=%lu", + ibs->tx_votes_on, ibs->tx_votes_off); + BT_INFO("HCI_IBS stats: rx votes cast: on=%lu, off=%lu", + ibs->rx_votes_on, ibs->rx_votes_off); + BT_INFO("HCI_IBS stats: msm_clock votes cast: on=%lu, off=%lu", + ibs->votes_on, ibs->votes_off); + BT_INFO("HCI_IBS stats: vote ticks: on=%lu, off=%lu", + ibs->vote_on_ticks, ibs->vote_off_ticks); +} + +/* Flush protocol data */ +static int ibs_flush(struct hci_uart *hu) +{ + struct ibs_struct *ibs = hu->priv; + + BT_DBG("hu %p", hu); + + skb_queue_purge(&ibs->tx_wait_q); + skb_queue_purge(&ibs->txq); + + return 0; +} + +/* Close protocol */ +static int ibs_close(struct hci_uart *hu) +{ + struct ibs_struct *ibs = hu->priv; + + BT_DBG("hu %p", hu); + + ibs_msm_serial_clock_vote(HCI_IBS_VOTE_STATS_UPDATE, hu); + ibs_log_local_stats(ibs); + + skb_queue_purge(&ibs->tx_wait_q); + skb_queue_purge(&ibs->txq); + del_timer(&ibs->tx_idle_timer); + del_timer(&ibs->wake_retrans_timer); + + kfree_skb(ibs->rx_skb); + + hu->priv = NULL; + + kfree(ibs); + + return 0; +} + +/* + * Called upon a wake-up-indication from the device + */ +static void ibs_device_want_to_wakeup(struct hci_uart *hu) +{ + unsigned long flags; + struct ibs_struct *ibs = hu->priv; + + BT_DBG("hu %p", hu); + + /* lock hci_ibs state */ + spin_lock_irqsave(&ibs->hci_ibs_lock, flags); + + /* debug */ + ibs->ibs_recv_wakes++; + + switch (ibs->rx_ibs_state) { + case HCI_IBS_RX_ASLEEP: + /* Make sure clock is on - we may have turned clock off since + * receiving the wake up indicator + */ + ibs_msm_serial_clock_vote(HCI_IBS_RX_VOTE_CLOCK_ON, hu); + ibs->rx_ibs_state = HCI_IBS_RX_AWAKE; + /* deliberate fall-through */ + case HCI_IBS_RX_AWAKE: + /* Always acknowledge device wake up, + * sending IBS message doesn't count as TX ON. + */ + if (send_hci_ibs_cmd(HCI_IBS_WAKE_ACK, hu) < 0) { + BT_ERR("cannot acknowledge device wake up"); + goto out; + } + ibs->ibs_sent_wacks++; /* debug */ + break; + default: + /* any other state is illegal */ + BT_ERR("received HCI_IBS_WAKE_IND in rx state %ld", + ibs->rx_ibs_state); + break; + } + +out: + spin_unlock_irqrestore(&ibs->hci_ibs_lock, flags); + + /* actually send the packets */ + hci_uart_tx_wakeup(hu); +} + +/* + * Called upon a sleep-indication from the device + */ +static void ibs_device_want_to_sleep(struct hci_uart *hu) +{ + unsigned long flags; + struct ibs_struct *ibs = hu->priv; + + BT_DBG("hu %p", hu); + + /* lock hci_ibs state */ + spin_lock_irqsave(&ibs->hci_ibs_lock, flags); + + /* debug */ + ibs->ibs_recv_slps++; + + switch (ibs->rx_ibs_state) { + case HCI_IBS_RX_AWAKE: + /* update state */ + ibs->rx_ibs_state = HCI_IBS_RX_ASLEEP; + ibs_msm_serial_clock_vote(HCI_IBS_RX_VOTE_CLOCK_OFF, hu); + break; + case HCI_IBS_RX_ASLEEP: + /* deliberate fall-through */ + default: + /* any other state is illegal */ + BT_ERR("received HCI_IBS_SLEEP_IND in rx state %ld", + ibs->rx_ibs_state); + break; + } + + spin_unlock_irqrestore(&ibs->hci_ibs_lock, flags); +} + +/* + * Called upon wake-up-acknowledgement from the device + */ +static void ibs_device_woke_up(struct hci_uart *hu) +{ + unsigned long flags; + struct ibs_struct *ibs = hu->priv; + struct sk_buff *skb = NULL; + + BT_DBG("hu %p", hu); + + /* lock hci_ibs state */ + spin_lock_irqsave(&ibs->hci_ibs_lock, flags); + + /* debug */ + ibs->ibs_recv_wacks++; + + switch (ibs->tx_ibs_state) { + case HCI_IBS_TX_ASLEEP: + /* This could be spurrious rx wake on the BT chip. + * Send it another SLEEP othwise it will stay awake. */ + default: + BT_ERR("received HCI_IBS_WAKE_ACK in tx state %ld", + ibs->tx_ibs_state); + break; + case HCI_IBS_TX_AWAKE: + /* expect one if we send 2 WAKEs */ + BT_DBG("received HCI_IBS_WAKE_ACK in tx state %ld", + ibs->tx_ibs_state); + break; + case HCI_IBS_TX_WAKING: + /* send pending packets */ + while ((skb = skb_dequeue(&ibs->tx_wait_q))) + skb_queue_tail(&ibs->txq, skb); + /* switch timers and change state to HCI_IBS_TX_AWAKE */ + del_timer(&ibs->wake_retrans_timer); + mod_timer(&ibs->tx_idle_timer, jiffies + tx_idle_delay); + ibs->tx_ibs_state = HCI_IBS_TX_AWAKE; + } + + spin_unlock_irqrestore(&ibs->hci_ibs_lock, flags); + + /* actually send the packets */ + hci_uart_tx_wakeup(hu); +} + +/* Enqueue frame for transmittion (padding, crc, etc) */ +/* may be called from two simultaneous tasklets */ +static int ibs_enqueue(struct hci_uart *hu, struct sk_buff *skb) +{ + unsigned long flags = 0; + struct ibs_struct *ibs = hu->priv; + + BT_DBG("hu %p skb %p", hu, skb); + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + + /* lock hci_ibs state */ + spin_lock_irqsave(&ibs->hci_ibs_lock, flags); + + /* act according to current state */ + switch (ibs->tx_ibs_state) { + case HCI_IBS_TX_AWAKE: + BT_DBG("device awake, sending normally"); + skb_queue_tail(&ibs->txq, skb); + mod_timer(&ibs->tx_idle_timer, jiffies + tx_idle_delay); + break; + + case HCI_IBS_TX_ASLEEP: + BT_DBG("device asleep, waking up and queueing packet"); + ibs_msm_serial_clock_vote(HCI_IBS_TX_VOTE_CLOCK_ON, hu); + /* save packet for later */ + skb_queue_tail(&ibs->tx_wait_q, skb); + /* awake device */ + if (send_hci_ibs_cmd(HCI_IBS_WAKE_IND, hu) < 0) { + BT_ERR("cannot send WAKE to device"); + break; + } + ibs->ibs_sent_wakes++; /* debug */ + + /* start retransmit timer */ + mod_timer(&ibs->wake_retrans_timer, jiffies + wake_retrans); + + ibs->tx_ibs_state = HCI_IBS_TX_WAKING; + break; + + case HCI_IBS_TX_WAKING: + BT_DBG("device waking up, queueing packet"); + /* transient state; just keep packet for later */ + skb_queue_tail(&ibs->tx_wait_q, skb); + break; + + default: + BT_ERR("illegal tx state: %ld (losing packet)", + ibs->tx_ibs_state); + kfree_skb(skb); + break; + } + + spin_unlock_irqrestore(&ibs->hci_ibs_lock, flags); + + return 0; +} + +static inline int ibs_check_data_len(struct ibs_struct *ibs, int len) +{ + register int room = skb_tailroom(ibs->rx_skb); + + BT_DBG("len %d room %d", len, room); + + if (!len) { + hci_recv_frame(ibs->rx_skb); + } else if (len > room) { + BT_ERR("Data length is too large"); + kfree_skb(ibs->rx_skb); + } else { + ibs->rx_state = HCI_IBS_W4_DATA; + ibs->rx_count = len; + return len; + } + + ibs->rx_state = HCI_IBS_W4_PACKET_TYPE; + ibs->rx_skb = NULL; + ibs->rx_count = 0; + + return 0; +} + +/* Recv data */ +static int ibs_recv(struct hci_uart *hu, void *data, int count) +{ + struct ibs_struct *ibs = hu->priv; + register char *ptr; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + register int len, type, dlen; + + BT_DBG("hu %p count %d rx_state %ld rx_count %ld", + hu, count, ibs->rx_state, ibs->rx_count); + + ptr = data; + while (count) { + if (ibs->rx_count) { + len = min_t(unsigned int, ibs->rx_count, count); + memcpy(skb_put(ibs->rx_skb, len), ptr, len); + ibs->rx_count -= len; count -= len; ptr += len; + + if (ibs->rx_count) + continue; + + switch (ibs->rx_state) { + case HCI_IBS_W4_DATA: + BT_DBG("Complete data"); + hci_recv_frame(ibs->rx_skb); + + ibs->rx_state = HCI_IBS_W4_PACKET_TYPE; + ibs->rx_skb = NULL; + continue; + + case HCI_IBS_W4_EVENT_HDR: + eh = (struct hci_event_hdr *) ibs->rx_skb->data; + + BT_DBG("Event header: evt 0x%2.2x plen %d", + eh->evt, eh->plen); + + ibs_check_data_len(ibs, eh->plen); + continue; + + case HCI_IBS_W4_ACL_HDR: + ah = (struct hci_acl_hdr *) ibs->rx_skb->data; + dlen = __le16_to_cpu(ah->dlen); + + BT_DBG("ACL header: dlen %d", dlen); + + ibs_check_data_len(ibs, dlen); + continue; + + case HCI_IBS_W4_SCO_HDR: + sh = (struct hci_sco_hdr *) ibs->rx_skb->data; + + BT_DBG("SCO header: dlen %d", sh->dlen); + + ibs_check_data_len(ibs, sh->dlen); + continue; + } + } + + /* HCI_IBS_W4_PACKET_TYPE */ + switch (*ptr) { + case HCI_EVENT_PKT: + BT_DBG("Event packet"); + ibs->rx_state = HCI_IBS_W4_EVENT_HDR; + ibs->rx_count = HCI_EVENT_HDR_SIZE; + type = HCI_EVENT_PKT; + break; + + case HCI_ACLDATA_PKT: + BT_DBG("ACL packet"); + ibs->rx_state = HCI_IBS_W4_ACL_HDR; + ibs->rx_count = HCI_ACL_HDR_SIZE; + type = HCI_ACLDATA_PKT; + break; + + case HCI_SCODATA_PKT: + BT_DBG("SCO packet"); + ibs->rx_state = HCI_IBS_W4_SCO_HDR; + ibs->rx_count = HCI_SCO_HDR_SIZE; + type = HCI_SCODATA_PKT; + break; + + /* HCI_IBS signals */ + case HCI_IBS_SLEEP_IND: + BT_DBG("HCI_IBS_SLEEP_IND packet"); + ibs_device_want_to_sleep(hu); + ptr++; count--; + continue; + + case HCI_IBS_WAKE_IND: + BT_DBG("HCI_IBS_WAKE_IND packet"); + ibs_device_want_to_wakeup(hu); + ptr++; count--; + continue; + + case HCI_IBS_WAKE_ACK: + BT_DBG("HCI_IBS_WAKE_ACK packet"); + ibs_device_woke_up(hu); + ptr++; count--; + continue; + + default: + BT_ERR("Unknown HCI packet type %2.2x", (__u8)*ptr); + hu->hdev->stat.err_rx++; + ptr++; count--; + continue; + }; + + ptr++; count--; + + /* Allocate packet */ + ibs->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!ibs->rx_skb) { + BT_ERR("Can't allocate mem for new packet"); + ibs->rx_state = HCI_IBS_W4_PACKET_TYPE; + ibs->rx_count = 0; + return 0; + } + + ibs->rx_skb->dev = (void *) hu->hdev; + bt_cb(ibs->rx_skb)->pkt_type = type; + } + + return count; +} + +static struct sk_buff *ibs_dequeue(struct hci_uart *hu) +{ + struct ibs_struct *ibs = hu->priv; + return skb_dequeue(&ibs->txq); +} + +static struct hci_uart_proto ibs_p = { + .id = HCI_UART_IBS, + .open = ibs_open, + .close = ibs_close, + .recv = ibs_recv, + .enqueue = ibs_enqueue, + .dequeue = ibs_dequeue, + .flush = ibs_flush, +}; + +int ibs_init(void) +{ + int err = hci_uart_register_proto(&ibs_p); + + if (!err) + BT_INFO("HCI_IBS protocol initialized"); + else + BT_ERR("HCI_IBS protocol registration failed"); + + return err; +} + +int ibs_deinit(void) +{ + return hci_uart_unregister_proto(&ibs_p); +} + +module_param(wake_retrans, ulong, 0644); +MODULE_PARM_DESC(wake_retrans, "Delay (1/HZ) to retransmit WAKE_IND"); + +module_param(tx_idle_delay, ulong, 0644); +MODULE_PARM_DESC(tx_idle_delay, "Delay (1/HZ) since last tx for SLEEP_IND"); diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c index 48ad2a7ab08..b07af022ca9 100644 --- a/drivers/bluetooth/hci_ldisc.c +++ b/drivers/bluetooth/hci_ldisc.c @@ -2,9 +2,9 @@ * * Bluetooth HCI UART driver * - * Copyright (C) 2000-2001 Qualcomm Incorporated * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2004-2005 Marcel Holtmann + * Copyright (c) 2000-2001, 2010-2011, Code Aurora Forum. All rights reserved. * * * This program is free software; you can redistribute it and/or modify @@ -359,6 +359,7 @@ static void hci_uart_tty_wakeup(struct tty_struct *tty) */ static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, char *flags, int count) { + int ret; struct hci_uart *hu = (void *)tty->disc_data; if (!hu || tty != hu->tty) @@ -368,8 +369,9 @@ static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data, char *f return; spin_lock(&hu->rx_lock); - hu->proto->recv(hu, (void *) data, count); - hu->hdev->stat.byte_rx += count; + ret = hu->proto->recv(hu, (void *) data, count); + if (ret > 0) + hu->hdev->stat.byte_rx += count; spin_unlock(&hu->rx_lock); tty_unthrottle(tty); @@ -468,11 +470,18 @@ static int hci_uart_tty_ioctl(struct tty_struct *tty, struct file * file, switch (cmd) { case HCIUARTSETPROTO: - if (!test_and_set_bit(HCI_UART_PROTO_SET, &hu->flags)) { + if (!test_and_set_bit(HCI_UART_PROTO_SET_IN_PROGRESS, + &hu->flags) && !test_bit(HCI_UART_PROTO_SET, + &hu->flags)) { err = hci_uart_set_proto(hu, arg); if (err) { - clear_bit(HCI_UART_PROTO_SET, &hu->flags); + clear_bit(HCI_UART_PROTO_SET_IN_PROGRESS, + &hu->flags); return err; + } else { + set_bit(HCI_UART_PROTO_SET, &hu->flags); + clear_bit(HCI_UART_PROTO_SET_IN_PROGRESS, + &hu->flags); } } else return -EBUSY; @@ -565,6 +574,9 @@ static int __init hci_uart_init(void) #ifdef CONFIG_BT_HCIUART_ATH3K ath_init(); #endif +#ifdef CONFIG_BT_HCIUART_IBS + ibs_init(); +#endif return 0; } @@ -585,6 +597,9 @@ static void __exit hci_uart_exit(void) #ifdef CONFIG_BT_HCIUART_ATH3K ath_deinit(); #endif +#ifdef CONFIG_BT_HCIUART_IBS + ibs_deinit(); +#endif /* Release tty registration of line discipline */ if ((err = tty_unregister_ldisc(N_HCI))) diff --git a/drivers/bluetooth/hci_ll.c b/drivers/bluetooth/hci_ll.c index 7e4b435f79f..38595e782d0 100644 --- a/drivers/bluetooth/hci_ll.c +++ b/drivers/bluetooth/hci_ll.c @@ -207,7 +207,7 @@ static void ll_device_want_to_wakeup(struct hci_uart *hu) /* * This state means that both the host and the BRF chip * have simultaneously sent a wake-up-indication packet. - * Traditionally, in this case, receiving a wake-up-indication + * Traditionaly, in this case, receiving a wake-up-indication * was enough and an additional wake-up-ack wasn't needed. * This has changed with the BRF6350, which does require an * explicit wake-up-ack. Other BRF versions, which do not diff --git a/drivers/bluetooth/hci_smd.c b/drivers/bluetooth/hci_smd.c new file mode 100644 index 00000000000..7132c7c3056 --- /dev/null +++ b/drivers/bluetooth/hci_smd.c @@ -0,0 +1,305 @@ +/* + * HCI_SMD (HCI Shared Memory Driver) is Qualcomm's Shared memory driver + * for the BT HCI protocol. + * + * Copyright (c) 2000-2001, 2011 Code Aurora Forum. All rights reserved. + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2006 Marcel Holtmann + * + * This file is based on drivers/bluetooth/hci_vhci.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EVENT_CHANNEL "APPS_RIVA_BT_CMD" +#define DATA_CHANNEL "APPS_RIVA_BT_ACL" + +struct hci_smd_data { + struct hci_dev *hdev; + + struct smd_channel *event_channel; + struct smd_channel *data_channel; +}; +struct hci_smd_data hs; + +static int hci_smd_open(struct hci_dev *hdev) +{ + set_bit(HCI_RUNNING, &hdev->flags); + return 0; +} + + +static int hci_smd_close(struct hci_dev *hdev) +{ + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + else + return -EPERM; +} + + +static void hci_smd_destruct(struct hci_dev *hdev) +{ + kfree(hdev->driver_data); +} + +static void hci_smd_recv_data(unsigned long arg) +{ + int len; + int rc; + struct sk_buff *skb; + unsigned char *buf; + struct hci_smd_data *hsmd = &hs; + + len = smd_read_avail(hsmd->data_channel); + + while (len > 0) { + skb = bt_skb_alloc(len, GFP_KERNEL); + if (!skb) { + BT_ERR("Error in allocating socket buffer\n"); + return; + } + + buf = kmalloc(len, GFP_KERNEL); + if (!buf) { + BT_ERR("Error in allocating buffer\n"); + kfree_skb(skb); + return; + } + + rc = smd_read_from_cb(hsmd->data_channel, (void *)buf, len); + if (rc < len) { + BT_ERR("Error in reading from the channel"); + return; + } + + memcpy(skb_put(skb, len), buf, len); + skb->dev = (void *)hsmd->hdev; + bt_cb(skb)->pkt_type = HCI_ACLDATA_PKT; + + skb_orphan(skb); + + rc = hci_recv_frame(skb); + if (rc < 0) { + BT_ERR("Error in passing the packet to HCI Layer"); + return; + } + + kfree(buf); + len = smd_read_avail(hsmd->data_channel); + } +} + +static void hci_smd_recv_event(unsigned long arg) +{ + int len; + int rc; + struct sk_buff *skb; + unsigned char *buf; + struct hci_smd_data *hsmd = &hs; + + len = smd_read_avail(hsmd->event_channel); + if (len > HCI_MAX_FRAME_SIZE) { + BT_ERR("Frame larger than the allowed size"); + return; + } + + while (len > 0) { + skb = bt_skb_alloc(len, GFP_KERNEL); + if (!skb) + return; + + buf = kmalloc(len, GFP_KERNEL); + if (!buf) { + kfree_skb(skb); + return; + } + + rc = smd_read_from_cb(hsmd->event_channel, (void *)buf, len); + + memcpy(skb_put(skb, len), buf, len); + skb->dev = (void *)hsmd->hdev; + bt_cb(skb)->pkt_type = HCI_EVENT_PKT; + + skb_orphan(skb); + + rc = hci_recv_frame(skb); + if (rc < 0) { + BT_ERR("Error in passing the packet to HCI Layer"); + return; + } + + kfree(buf); + len = smd_read_avail(hsmd->event_channel); + } +} + +static int hci_smd_send_frame(struct sk_buff *skb) +{ + int len; + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + len = smd_write(hs.event_channel, skb->data, skb->len); + if (len < skb->len) { + BT_ERR("Failed to write Command %d", len); + return -ENODEV; + } + break; + case HCI_ACLDATA_PKT: + case HCI_SCODATA_PKT: + len = smd_write(hs.data_channel, skb->data, skb->len); + if (len < skb->len) { + BT_ERR("Failed to write Data %d", len); + return -ENODEV; + } + break; + default: + BT_ERR("Uknown packet type\n"); + return -ENODEV; + break; + } + return 0; +} + + +static void hci_smd_notify_event(void *data, unsigned int event) +{ + struct hci_dev *hdev = hs.hdev; + + if (!hdev) { + BT_ERR("Frame for unknown HCI device (hdev=NULL)"); + return; + } + + switch (event) { + case SMD_EVENT_DATA: + hci_smd_recv_event(event); + break; + case SMD_EVENT_OPEN: + hci_smd_open(hdev); + break; + case SMD_EVENT_CLOSE: + hci_smd_close(hdev); + break; + default: + break; + } +} + +static void hci_smd_notify_data(void *data, unsigned int event) +{ + struct hci_dev *hdev = hs.hdev; + if (!hdev) { + BT_ERR("HCI device (hdev=NULL)"); + return; + } + + switch (event) { + case SMD_EVENT_DATA: + hci_smd_recv_data(event); + break; + case SMD_EVENT_OPEN: + hci_smd_open(hdev); + break; + case SMD_EVENT_CLOSE: + hci_smd_close(hdev); + break; + default: + break; + } + +} + +static int hci_smd_register_dev(struct hci_smd_data *hsmd) +{ + struct hci_dev *hdev; + int rc; + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + return -ENOMEM; + } + + hsmd->hdev = hdev; + hdev->bus = HCI_SMD; + hdev->driver_data = hsmd; + hdev->open = hci_smd_open; + hdev->close = hci_smd_close; + hdev->send = hci_smd_send_frame; + hdev->destruct = hci_smd_destruct; + hdev->owner = THIS_MODULE; + + /* Open the SMD Channel and device and register the callback function */ + rc = smd_named_open_on_edge(EVENT_CHANNEL, SMD_APPS_WCNSS, + &hsmd->event_channel, hdev, hci_smd_notify_event); + if (rc < 0) { + BT_ERR("Cannot open the command channel"); + hci_free_dev(hdev); + return -ENODEV; + } + + rc = smd_named_open_on_edge(DATA_CHANNEL, SMD_APPS_WCNSS, + &hsmd->data_channel, hdev, hci_smd_notify_data); + if (rc < 0) { + BT_ERR("Failed to open the Data channel\n"); + hci_free_dev(hdev); + return -ENODEV; + } + + /* Disable the read interrupts on the channel */ + smd_disable_read_intr(hsmd->event_channel); + smd_disable_read_intr(hsmd->data_channel); + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + +static void hci_smd_deregister(void) +{ + smd_close(hs.event_channel); + hs.event_channel = 0; + smd_close(hs.data_channel); + hs.data_channel = 0; +} + +static int hci_smd_init(void) +{ + return hci_smd_register_dev(&hs); +} +module_init(hci_smd_init); + +static void __exit hci_smd_exit(void) +{ + hci_smd_deregister(); +} +module_exit(hci_smd_exit); + +MODULE_AUTHOR("Ankur Nandwani "); +MODULE_DESCRIPTION("Bluetooth SMD driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h index 99fb35239d1..dc482397e47 100644 --- a/drivers/bluetooth/hci_uart.h +++ b/drivers/bluetooth/hci_uart.h @@ -2,9 +2,9 @@ * * Bluetooth HCI UART driver * - * Copyright (C) 2000-2001 Qualcomm Incorporated * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2004-2005 Marcel Holtmann + * Copyright (c) 2000-2001, 2010, Code Aurora Forum. All rights reserved. * * * This program is free software; you can redistribute it and/or modify @@ -35,14 +35,15 @@ #define HCIUARTGETFLAGS _IOR('U', 204, int) /* UART protocols */ -#define HCI_UART_MAX_PROTO 6 +#define HCI_UART_MAX_PROTO 7 #define HCI_UART_H4 0 #define HCI_UART_BCSP 1 #define HCI_UART_3WIRE 2 #define HCI_UART_H4DS 3 #define HCI_UART_LL 4 -#define HCI_UART_ATH3K 5 +#define HCI_UART_IBS 5 +#define HCI_UART_ATH3K 6 #define HCI_UART_RAW_DEVICE 0 @@ -73,7 +74,8 @@ struct hci_uart { }; /* HCI_UART proto flag bits */ -#define HCI_UART_PROTO_SET 0 +#define HCI_UART_PROTO_SET 0 +#define HCI_UART_PROTO_SET_IN_PROGRESS 1 /* TX states */ #define HCI_UART_SENDING 1 @@ -102,3 +104,8 @@ int ll_deinit(void); int ath_init(void); int ath_deinit(void); #endif + +#ifdef CONFIG_BT_HCIUART_IBS +int ibs_init(void); +int ibs_deinit(void); +#endif diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 7d10ae3de25..b55746cfccb 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -64,6 +64,8 @@ config SGI_MBCS source "drivers/tty/serial/Kconfig" +source "drivers/char/diag/Kconfig" + config TTY_PRINTK bool "TTY driver to output user messages via printk" depends on EXPERT @@ -633,5 +635,46 @@ config MSM_SMD_PKT Enables userspace clients to read and write to some packet SMD ports via device interface for MSM chipset. +config MSM_ROTATOR + tristate "MSM Offline Image Rotator Driver" + depends on (ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_MSM8960) && ANDROID_PMEM + default y + help + This driver provides support for the image rotator HW block in the + MSM 7x30 SoC. + +config MSM_ROTATOR_USE_IMEM + bool "Enable rotator driver to use iMem" + depends on ARCH_MSM7X30 && MSM_ROTATOR + default y + help + This option enables the msm_rotator driver to use the move efficient + iMem. Some MSM platforms may not have iMem available for the rotator + block. Or some systems may want the iMem to be dedicated to a + different function. + +config MMC_GENERIC_CSDIO + tristate "Generic sdio driver" + default n + help + SDIO function driver that extends SDIO card as character device + in user space. + +config CSDIO_VENDOR_ID + hex "Card VendorId" + depends on MMC_GENERIC_CSDIO + default "0" + help + Enter vendor id for targeted sdio device, this may be overwritten by + module parameters. + +config CSDIO_DEVICE_ID + hex "CardDeviceId" + depends on MMC_GENERIC_CSDIO + default "0" + help + Enter device id for targeted sdio device, this may be overwritten by + module parameters. +. endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 3f63254bbbd..1a295b80968 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -9,7 +9,6 @@ obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o obj-$(CONFIG_VIRTIO_CONSOLE) += virtio_console.o obj-$(CONFIG_RAW_DRIVER) += raw.o obj-$(CONFIG_SGI_SNSC) += snsc.o snsc_event.o -obj-$(CONFIG_MSM_SMD_PKT) += msm_smd_pkt.o obj-$(CONFIG_MSPEC) += mspec.o obj-$(CONFIG_MMTIMER) += mmtimer.o obj-$(CONFIG_UV_MMTIMER) += uv_mmtimer.o @@ -64,3 +63,6 @@ obj-$(CONFIG_RAMOOPS) += ramoops.o obj-$(CONFIG_JS_RTC) += js-rtc.o js-rtc-y = rtc.o + +obj-$(CONFIG_MSM_ROTATOR) += msm_rotator.o +obj-$(CONFIG_MMC_GENERIC_CSDIO) += csdio.o diff --git a/drivers/char/csdio.c b/drivers/char/csdio.c new file mode 100644 index 00000000000..ca7e98675de --- /dev/null +++ b/drivers/char/csdio.c @@ -0,0 +1,1074 @@ +/* + * Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Char device */ +#include +#include + +/* Sdio device */ +#include +#include +#include +#include +#include +#include + +#include + +#define FALSE 0 +#define TRUE 1 + +#define VERSION "0.5" +#define CSDIO_NUM_OF_SDIO_FUNCTIONS 7 +#define CSDIO_DEV_NAME "csdio" +#define TP_DEV_NAME CSDIO_DEV_NAME"f" +#define CSDIO_DEV_PERMISSIONS 0666 + +#define CSDIO_SDIO_BUFFER_SIZE (64*512) + +int csdio_major; +int csdio_minor; +int csdio_transport_nr_devs = CSDIO_NUM_OF_SDIO_FUNCTIONS; +static uint csdio_vendor_id; +static uint csdio_device_id; +static char *host_name; + +static struct csdio_func_t { + struct sdio_func *m_func; + int m_enabled; + struct cdev m_cdev; /* char device structure */ + struct device *m_device; + u32 m_block_size; +} *g_csdio_func_table[CSDIO_NUM_OF_SDIO_FUNCTIONS] = {0}; + +struct csdio_t { + struct cdev m_cdev; + struct device *m_device; + struct class *m_driver_class; + struct fasync_struct *m_async_queue; + unsigned char m_current_irq_mask; /* currently enabled irqs */ + struct mmc_host *m_host; + unsigned int m_num_of_func; +} g_csdio; + +struct csdio_file_descriptor { + struct csdio_func_t *m_port; + u32 m_block_mode;/* data tran. byte(0)/block(1) */ + u32 m_op_code; /* address auto increment flag */ + u32 m_address; +}; + +static void *g_sdio_buffer; + +/* + * Open and release + */ +static int csdio_transport_open(struct inode *inode, struct file *filp) +{ + int ret = 0; + struct csdio_func_t *port = NULL; /* device information */ + struct sdio_func *func = NULL; + struct csdio_file_descriptor *descriptor = NULL; + + port = container_of(inode->i_cdev, struct csdio_func_t, m_cdev); + func = port->m_func; + descriptor = kzalloc(sizeof(struct csdio_file_descriptor), GFP_KERNEL); + if (!descriptor) { + ret = -ENOMEM; + goto exit; + } + + pr_info(TP_DEV_NAME"%d: open: func=%p, port=%p\n", + func->num, func, port); + sdio_claim_host(func); + ret = sdio_enable_func(func); + if (ret) { + pr_err(TP_DEV_NAME"%d:Enable func failed (%d)\n", + func->num, ret); + ret = -EIO; + goto free_descriptor; + } + descriptor->m_port = port; + filp->private_data = descriptor; + goto release_host; + +free_descriptor: + kfree(descriptor); +release_host: + sdio_release_host(func); +exit: + return ret; +} + +static int csdio_transport_release(struct inode *inode, struct file *filp) +{ + int ret = 0; + struct csdio_file_descriptor *descriptor = filp->private_data; + struct csdio_func_t *port = descriptor->m_port; + struct sdio_func *func = port->m_func; + + pr_info(TP_DEV_NAME"%d: release\n", func->num); + sdio_claim_host(func); + ret = sdio_disable_func(func); + if (ret) { + pr_err(TP_DEV_NAME"%d:Disable func failed(%d)\n", + func->num, ret); + ret = -EIO; + } + sdio_release_host(func); + kfree(descriptor); + return ret; +} + +/* + * Data management: read and write + */ +static ssize_t csdio_transport_read(struct file *filp, + char __user *buf, + size_t count, + loff_t *f_pos) +{ + ssize_t ret = 0; + struct csdio_file_descriptor *descriptor = filp->private_data; + struct csdio_func_t *port = descriptor->m_port; + struct sdio_func *func = port->m_func; + size_t t_count = count; + + if (descriptor->m_block_mode) { + pr_info(TP_DEV_NAME "%d: CMD53 read, Md:%d, Addr:0x%04X," + " Un:%d (Bl:%d, BlSz:%d)\n", func->num, + descriptor->m_block_mode, + descriptor->m_address, + count*port->m_block_size, + count, port->m_block_size); + /* recalculate size */ + count *= port->m_block_size; + } + sdio_claim_host(func); + if (descriptor->m_op_code) { + /* auto increment */ + ret = sdio_memcpy_fromio(func, g_sdio_buffer, + descriptor->m_address, count); + } else { /* FIFO */ + ret = sdio_readsb(func, g_sdio_buffer, + descriptor->m_address, count); + } + sdio_release_host(func); + if (!ret) { + if (copy_to_user(buf, g_sdio_buffer, count)) + ret = -EFAULT; + else + ret = t_count; + } + if (ret < 0) { + pr_err(TP_DEV_NAME "%d: CMD53 read failed (%d)" + "(Md:%d, Addr:0x%04X, Sz:%d)\n", + func->num, ret, + descriptor->m_block_mode, + descriptor->m_address, count); + } + return ret; +} + +static ssize_t csdio_transport_write(struct file *filp, + const char __user *buf, + size_t count, + loff_t *f_pos) +{ + ssize_t ret = 0; + struct csdio_file_descriptor *descriptor = filp->private_data; + struct csdio_func_t *port = descriptor->m_port; + struct sdio_func *func = port->m_func; + size_t t_count = count; + + if (descriptor->m_block_mode) + count *= port->m_block_size; + + if (copy_from_user(g_sdio_buffer, buf, count)) { + pr_err(TP_DEV_NAME"%d:copy_from_user failed\n", func->num); + ret = -EFAULT; + } else { + sdio_claim_host(func); + if (descriptor->m_op_code) { + /* auto increment */ + ret = sdio_memcpy_toio(func, descriptor->m_address, + g_sdio_buffer, count); + } else { + /* FIFO */ + ret = sdio_writesb(func, descriptor->m_address, + g_sdio_buffer, count); + } + sdio_release_host(func); + if (!ret) { + ret = t_count; + } else { + pr_err(TP_DEV_NAME "%d: CMD53 write failed (%d)" + "(Md:%d, Addr:0x%04X, Sz:%d)\n", + func->num, ret, descriptor->m_block_mode, + descriptor->m_address, count); + } + } + return ret; +} + +/* disable interrupt for sdio client */ +static int disable_sdio_client_isr(struct sdio_func *func) +{ + int ret; + + /* disable for all functions, to restore interrupts + * use g_csdio.m_current_irq_mask */ + sdio_f0_writeb(func, 0, SDIO_CCCR_IENx, &ret); + if (ret) + pr_err(CSDIO_DEV_NAME" Can't sdio_f0_writeb (%d)\n", ret); + + return ret; +} + +/* + * This handles the interrupt from SDIO. + */ +static void csdio_sdio_irq(struct sdio_func *func) +{ + int ret; + + pr_info(CSDIO_DEV_NAME" csdio_sdio_irq: func=%d\n", func->num); + ret = disable_sdio_client_isr(func); + if (ret) { + pr_err(CSDIO_DEV_NAME" Can't disable client isr(%d)\n", ret); + return; + } + /* signal asynchronous readers */ + if (g_csdio.m_async_queue) + kill_fasync(&g_csdio.m_async_queue, SIGIO, POLL_IN); +} + +/* + * The ioctl() implementation + */ +static int csdio_transport_ioctl(struct inode *inode, + struct file *filp, + unsigned int cmd, + unsigned long arg) +{ + int err = 0; + int ret = 0; + struct csdio_file_descriptor *descriptor = filp->private_data; + struct csdio_func_t *port = descriptor->m_port; + struct sdio_func *func = port->m_func; + + /* extract the type and number bitfields + sanity check: return ENOTTY (inappropriate ioctl) before + access_ok() + */ + if ((_IOC_TYPE(cmd) != CSDIO_IOC_MAGIC) || + (_IOC_NR(cmd) > CSDIO_IOC_MAXNR)) { + pr_err(TP_DEV_NAME "Wrong ioctl command parameters\n"); + ret = -ENOTTY; + goto exit; + } + + /* the direction is a bitmask, and VERIFY_WRITE catches R/W + * transfers. `Type' is user-oriented, while access_ok is + kernel-oriented, so the concept of "read" and "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) { + err = !access_ok(VERIFY_WRITE, (void __user *)arg, + _IOC_SIZE(cmd)); + } else { + if (_IOC_DIR(cmd) & _IOC_WRITE) { + err = !access_ok(VERIFY_READ, (void __user *)arg, + _IOC_SIZE(cmd)); + } + } + if (err) { + pr_err(TP_DEV_NAME "Wrong ioctl access direction\n"); + ret = -EFAULT; + goto exit; + } + + switch (cmd) { + case CSDIO_IOC_SET_OP_CODE: + { + pr_info(TP_DEV_NAME"%d:SET_OP_CODE=%d\n", + func->num, descriptor->m_op_code); + ret = get_user(descriptor->m_op_code, + (unsigned char __user *)arg); + if (ret) { + pr_err(TP_DEV_NAME"%d:SET_OP_CODE get data" + " from user space failed(%d)\n", + func->num, ret); + ret = -ENOTTY; + break; + } + } + break; + case CSDIO_IOC_FUNCTION_SET_BLOCK_SIZE: + { + unsigned block_size; + + ret = get_user(block_size, (unsigned __user *)arg); + if (ret) { + pr_err(TP_DEV_NAME"%d:SET_BLOCK_SIZE get data" + " from user space failed(%d)\n", + func->num, ret); + ret = -ENOTTY; + break; + } + pr_info(TP_DEV_NAME"%d:SET_BLOCK_SIZE=%d\n", + func->num, block_size); + sdio_claim_host(func); + ret = sdio_set_block_size(func, block_size); + if (!ret) { + port->m_block_size = block_size; + } else { + pr_err(TP_DEV_NAME"%d:SET_BLOCK_SIZE set block" + " size to %d failed (%d)\n", + func->num, block_size, ret); + ret = -ENOTTY; + break; + } + sdio_release_host(func); + } + break; + case CSDIO_IOC_SET_BLOCK_MODE: + { + pr_info(TP_DEV_NAME"%d:SET_BLOCK_MODE=%d\n", + func->num, descriptor->m_block_mode); + ret = get_user(descriptor->m_block_mode, + (unsigned char __user *)arg); + if (ret) { + pr_err(TP_DEV_NAME"%d:SET_BLOCK_MODE get data" + " from user space failed\n", + func->num); + ret = -ENOTTY; + break; + } + } + break; + case CSDIO_IOC_CMD52: + { + struct csdio_cmd52_ctrl_t cmd52ctrl; + int cmd52ret; + + if (copy_from_user(&cmd52ctrl, + (const unsigned char __user *)arg, + sizeof(cmd52ctrl))) { + pr_err(TP_DEV_NAME"%d:IOC_CMD52 get data" + " from user space failed\n", + func->num); + ret = -ENOTTY; + break; + } + sdio_claim_host(func); + if (cmd52ctrl.m_write) + sdio_writeb(func, cmd52ctrl.m_data, + cmd52ctrl.m_address, &cmd52ret); + else + cmd52ctrl.m_data = sdio_readb(func, + cmd52ctrl.m_address, &cmd52ret); + + cmd52ctrl.m_ret = cmd52ret; + sdio_release_host(func); + if (cmd52ctrl.m_ret) + pr_err(TP_DEV_NAME"%d:IOC_CMD52 failed (%d)\n", + func->num, cmd52ctrl.m_ret); + + if (copy_to_user((unsigned char __user *)arg, + &cmd52ctrl, + sizeof(cmd52ctrl))) { + pr_err(TP_DEV_NAME"%d:IOC_CMD52 put data" + " to user space failed\n", + func->num); + ret = -ENOTTY; + break; + } + } + break; + case CSDIO_IOC_CMD53: + { + struct csdio_cmd53_ctrl_t csdio_cmd53_ctrl; + + if (copy_from_user(&csdio_cmd53_ctrl, + (const char __user *)arg, + sizeof(csdio_cmd53_ctrl))) { + ret = -EPERM; + pr_err(TP_DEV_NAME"%d:" + "Get data from user space failed\n", + func->num); + break; + } + descriptor->m_block_mode = + csdio_cmd53_ctrl.m_block_mode; + descriptor->m_op_code = csdio_cmd53_ctrl.m_op_code; + descriptor->m_address = csdio_cmd53_ctrl.m_address; + } + break; + case CSDIO_IOC_CONNECT_ISR: + { + pr_info(CSDIO_DEV_NAME" SDIO_CONNECT_ISR" + " func=%d, csdio_sdio_irq=%x\n", + func->num, (unsigned int)csdio_sdio_irq); + sdio_claim_host(func); + ret = sdio_claim_irq(func, csdio_sdio_irq); + sdio_release_host(func); + if (ret) { + pr_err(CSDIO_DEV_NAME" SDIO_CONNECT_ISR" + " claim irq failed(%d)\n", ret); + } else { + /* update current irq mask for disable/enable */ + g_csdio.m_current_irq_mask |= (1 << func->num); + } + } + break; + case CSDIO_IOC_DISCONNECT_ISR: + { + pr_info(CSDIO_DEV_NAME " SDIO_DISCONNECT_ISR func=%d\n", + func->num); + sdio_claim_host(func); + sdio_release_irq(func); + sdio_release_host(func); + /* update current irq mask for disable/enable */ + g_csdio.m_current_irq_mask &= ~(1 << func->num); + } + break; + default: /* redundant, as cmd was checked against MAXNR */ + pr_warning(TP_DEV_NAME"%d: Redundant IOCTL\n", + func->num); + ret = -ENOTTY; + } +exit: + return ret; +} + +static const struct file_operations csdio_transport_fops = { + .owner = THIS_MODULE, + .read = csdio_transport_read, + .write = csdio_transport_write, + .ioctl = csdio_transport_ioctl, + .open = csdio_transport_open, + .release = csdio_transport_release, +}; + +static void csdio_transport_cleanup(struct csdio_func_t *port) +{ + int devno = MKDEV(csdio_major, csdio_minor + port->m_func->num); + device_destroy(g_csdio.m_driver_class, devno); + port->m_device = NULL; + cdev_del(&port->m_cdev); +} + +#if defined(CONFIG_DEVTMPFS) +static inline int csdio_cdev_update_permissions( + const char *devname, int dev_minor) +{ + return 0; +} +#else +static int csdio_cdev_update_permissions( + const char *devname, int dev_minor) +{ + int ret = 0; + mm_segment_t fs; + struct file *file; + struct inode *inode; + struct iattr newattrs; + int mode = CSDIO_DEV_PERMISSIONS; + char dev_file[64]; + + fs = get_fs(); + set_fs(get_ds()); + + snprintf(dev_file, sizeof(dev_file), "/dev/%s%d", + devname, dev_minor); + file = filp_open(dev_file, O_RDWR, 0); + if (IS_ERR(file)) { + ret = -EFAULT; + goto exit; + } + + inode = file->f_path.dentry->d_inode; + + mutex_lock(&inode->i_mutex); + newattrs.ia_mode = + (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO); + newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; + ret = notify_change(file->f_path.dentry, &newattrs); + mutex_unlock(&inode->i_mutex); + + filp_close(file, NULL); + +exit: + set_fs(fs); + return ret; +} +#endif + +static struct device *csdio_cdev_init(struct cdev *char_dev, + const struct file_operations *file_op, int dev_minor, + const char *devname, struct device *parent) +{ + int ret = 0; + struct device *new_device = NULL; + dev_t devno = MKDEV(csdio_major, dev_minor); + + /* Initialize transport device */ + cdev_init(char_dev, file_op); + char_dev->owner = THIS_MODULE; + char_dev->ops = file_op; + ret = cdev_add(char_dev, devno, 1); + + /* Fail gracefully if need be */ + if (ret) { + pr_warning("Error %d adding CSDIO char device '%s%d'", + ret, devname, dev_minor); + goto exit; + } + pr_info("'%s%d' char driver registered\n", devname, dev_minor); + + /* create a /dev entry for transport drivers */ + new_device = device_create(g_csdio.m_driver_class, parent, devno, NULL, + "%s%d", devname, dev_minor); + if (!new_device) { + pr_err("Can't create device node '/dev/%s%d'\n", + devname, dev_minor); + goto cleanup; + } + /* no irq attached */ + g_csdio.m_current_irq_mask = 0; + + if (csdio_cdev_update_permissions(devname, dev_minor)) { + pr_warning("%s%d: Unable to update access permissions of the" + " '/dev/%s%d'\n", + devname, dev_minor, devname, dev_minor); + } + + pr_info("%s%d: Device node '/dev/%s%d' created successfully\n", + devname, dev_minor, devname, dev_minor); + goto exit; +cleanup: + cdev_del(char_dev); +exit: + return new_device; +} + +/* Looks for first non empty function, returns NULL otherwise */ +static struct sdio_func *get_active_func(void) +{ + int i; + + for (i = 0; i < CSDIO_NUM_OF_SDIO_FUNCTIONS; i++) { + if (g_csdio_func_table[i]) + return g_csdio_func_table[i]->m_func; + } + return NULL; +} + +static ssize_t +show_vdd(struct device *dev, struct device_attribute *attr, char *buf) +{ + if (NULL == g_csdio.m_host) + return snprintf(buf, PAGE_SIZE, "N/A\n"); + return snprintf(buf, PAGE_SIZE, "%d\n", + g_csdio.m_host->ios.vdd); +} + +static int +set_vdd_helper(int value) +{ + struct mmc_ios *ios = NULL; + + if (NULL == g_csdio.m_host) { + pr_err("%s0: Set VDD, no MMC host assigned\n", CSDIO_DEV_NAME); + return -ENXIO; + } + + mmc_claim_host(g_csdio.m_host); + ios = &g_csdio.m_host->ios; + ios->vdd = value; + g_csdio.m_host->ops->set_ios(g_csdio.m_host, ios); + mmc_release_host(g_csdio.m_host); + return 0; +} + +static ssize_t +set_vdd(struct device *dev, struct device_attribute *att, + const char *buf, size_t count) +{ + int value = 0; + + sscanf(buf, "%d", &value); + if (set_vdd_helper(value)) + return -ENXIO; + return count; +} + +static DEVICE_ATTR(vdd, S_IRUGO | S_IWUSR, + show_vdd, set_vdd); + +static struct attribute *dev_attrs[] = { + &dev_attr_vdd.attr, + NULL, +}; + +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, +}; + +/* + * The ioctl() implementation for control device + */ +static int csdio_ctrl_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int err = 0; + int ret = 0; + + pr_info("CSDIO ctrl ioctl.\n"); + + /* extract the type and number bitfields + sanity check: return ENOTTY (inappropriate ioctl) before + access_ok() + */ + if ((_IOC_TYPE(cmd) != CSDIO_IOC_MAGIC) || + (_IOC_NR(cmd) > CSDIO_IOC_MAXNR)) { + pr_err(CSDIO_DEV_NAME "Wrong ioctl command parameters\n"); + ret = -ENOTTY; + goto exit; + } + + /* the direction is a bitmask, and VERIFY_WRITE catches R/W + transfers. `Type' is user-oriented, while access_ok is + kernel-oriented, so the concept of "read" and "write" is reversed + */ + if (_IOC_DIR(cmd) & _IOC_READ) { + err = !access_ok(VERIFY_WRITE, (void __user *)arg, + _IOC_SIZE(cmd)); + } else { + if (_IOC_DIR(cmd) & _IOC_WRITE) + err = !access_ok(VERIFY_READ, (void __user *)arg, + _IOC_SIZE(cmd)); + } + if (err) { + pr_err(CSDIO_DEV_NAME "Wrong ioctl access direction\n"); + ret = -EFAULT; + goto exit; + } + + switch (cmd) { + case CSDIO_IOC_ENABLE_HIGHSPEED_MODE: + pr_info(CSDIO_DEV_NAME" ENABLE_HIGHSPEED_MODE\n"); + break; + case CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS: + { + struct mmc_host *host = g_csdio.m_host; + struct mmc_ios *ios = NULL; + + if (NULL == host) { + pr_err("%s0: " + "CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS," + " no MMC host assigned\n", + CSDIO_DEV_NAME); + ret = -EFAULT; + goto exit; + } + ios = &host->ios; + + mmc_claim_host(host); + ret = get_user(host->ios.clock, + (unsigned int __user *)arg); + if (ret) { + pr_err(CSDIO_DEV_NAME + " get data from user space failed\n"); + } else { + pr_err(CSDIO_DEV_NAME + "SET_DATA_TRANSFER_CLOCKS(%d-%d)(%d)\n", + host->f_min, host->f_max, + host->ios.clock); + host->ops->set_ios(host, ios); + } + mmc_release_host(host); + } + break; + case CSDIO_IOC_ENABLE_ISR: + { + int ret; + unsigned char reg; + struct sdio_func *func = get_active_func(); + + if (!func) { + pr_err(CSDIO_DEV_NAME " CSDIO_IOC_ENABLE_ISR" + " no active sdio function\n"); + ret = -EFAULT; + goto exit; + } + pr_info(CSDIO_DEV_NAME + " CSDIO_IOC_ENABLE_ISR func=%d\n", + func->num); + reg = g_csdio.m_current_irq_mask | 1; + + sdio_claim_host(func); + sdio_f0_writeb(func, reg, SDIO_CCCR_IENx, &ret); + sdio_release_host(func); + if (ret) { + pr_err(CSDIO_DEV_NAME + " Can't sdio_f0_writeb (%d)\n", + ret); + goto exit; + } + } + break; + case CSDIO_IOC_DISABLE_ISR: + { + int ret; + struct sdio_func *func = get_active_func(); + if (!func) { + pr_err(CSDIO_DEV_NAME " CSDIO_IOC_ENABLE_ISR" + " no active sdio function\n"); + ret = -EFAULT; + goto exit; + } + pr_info(CSDIO_DEV_NAME + " CSDIO_IOC_DISABLE_ISR func=%p\n", + func); + + sdio_claim_host(func); + ret = disable_sdio_client_isr(func); + sdio_release_host(func); + if (ret) { + pr_err("%s0: Can't disable client isr (%d)\n", + CSDIO_DEV_NAME, ret); + goto exit; + } + } + break; + case CSDIO_IOC_SET_VDD: + { + unsigned int vdd = 0; + + ret = get_user(vdd, (unsigned int __user *)arg); + if (ret) { + pr_err("%s0: CSDIO_IOC_SET_VDD," + " get data from user space failed\n", + CSDIO_DEV_NAME); + goto exit; + } + pr_info(CSDIO_DEV_NAME" CSDIO_IOC_SET_VDD - %d\n", vdd); + + ret = set_vdd_helper(vdd); + if (ret) + goto exit; + } + break; + case CSDIO_IOC_GET_VDD: + { + if (NULL == g_csdio.m_host) { + pr_err("%s0: CSDIO_IOC_GET_VDD," + " no MMC host assigned\n", + CSDIO_DEV_NAME); + ret = -EFAULT; + goto exit; + } + ret = put_user(g_csdio.m_host->ios.vdd, + (unsigned short __user *)arg); + if (ret) { + pr_err("%s0: CSDIO_IOC_GET_VDD, put data" + " to user space failed\n", + CSDIO_DEV_NAME); + goto exit; + } + } + break; + default: /* redundant, as cmd was checked against MAXNR */ + pr_warning(CSDIO_DEV_NAME" Redundant IOCTL\n"); + ret = -ENOTTY; + } +exit: + return ret; +} + +static int csdio_ctrl_fasync(int fd, struct file *filp, int mode) +{ + pr_info(CSDIO_DEV_NAME + " csdio_ctrl_fasync: fd=%d, filp=%p, mode=%d\n", + fd, filp, mode); + return fasync_helper(fd, filp, mode, &g_csdio.m_async_queue); +} + +/* + * Open and close + */ +static int csdio_ctrl_open(struct inode *inode, struct file *filp) +{ + int ret = 0; + struct csdio_t *csdio_ctrl_drv = NULL; /* device information */ + + pr_info("CSDIO ctrl open.\n"); + csdio_ctrl_drv = container_of(inode->i_cdev, struct csdio_t, m_cdev); + filp->private_data = csdio_ctrl_drv; /* for other methods */ + return ret; +} + +static int csdio_ctrl_release(struct inode *inode, struct file *filp) +{ + pr_info("CSDIO ctrl release.\n"); + /* remove this filp from the asynchronously notified filp's */ + csdio_ctrl_fasync(-1, filp, 0); + return 0; +} + +static const struct file_operations csdio_ctrl_fops = { + .owner = THIS_MODULE, + .ioctl = csdio_ctrl_ioctl, + .open = csdio_ctrl_open, + .release = csdio_ctrl_release, + .fasync = csdio_ctrl_fasync, +}; + +static int csdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct csdio_func_t *port; + int ret = 0; + struct mmc_host *host = func->card->host; + + if (NULL != g_csdio.m_host && g_csdio.m_host != host) { + pr_info("%s: Device is on unexpected host\n", + CSDIO_DEV_NAME); + ret = -ENODEV; + goto exit; + } + + /* enforce single instance policy */ + if (g_csdio_func_table[func->num-1]) { + pr_err("%s - only single SDIO device supported", + sdio_func_id(func)); + ret = -EEXIST; + goto exit; + } + + port = kzalloc(sizeof(struct csdio_func_t), GFP_KERNEL); + if (!port) { + pr_err("Can't allocate memory\n"); + ret = -ENOMEM; + goto exit; + } + + /* initialize SDIO side */ + port->m_func = func; + sdio_set_drvdata(func, port); + + pr_info("%s - SDIO device found. Function %d\n", + sdio_func_id(func), func->num); + + port->m_device = csdio_cdev_init(&port->m_cdev, &csdio_transport_fops, + csdio_minor + port->m_func->num, + TP_DEV_NAME, &port->m_func->dev); + + /* create appropriate char device */ + if (!port->m_device) + goto free; + + if (0 == g_csdio.m_num_of_func && NULL == host_name) + g_csdio.m_host = host; + g_csdio.m_num_of_func++; + g_csdio_func_table[func->num-1] = port; + port->m_enabled = TRUE; + goto exit; +free: + kfree(port); +exit: + return ret; +} + +static void csdio_remove(struct sdio_func *func) +{ + struct csdio_func_t *port = sdio_get_drvdata(func); + + csdio_transport_cleanup(port); + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + kfree(port); + g_csdio_func_table[func->num-1] = NULL; + g_csdio.m_num_of_func--; + if (0 == g_csdio.m_num_of_func && NULL == host_name) + g_csdio.m_host = NULL; + pr_info("%s%d: Device removed (%s). Function %d\n", + CSDIO_DEV_NAME, func->num, sdio_func_id(func), func->num); +} + +/* CONFIG_CSDIO_VENDOR_ID and CONFIG_CSDIO_DEVICE_ID are defined in Kconfig. + * Use kernel configuration to change the values or overwrite them through + * module parameters */ +static struct sdio_device_id csdio_ids[] = { + { SDIO_DEVICE(CONFIG_CSDIO_VENDOR_ID, CONFIG_CSDIO_DEVICE_ID) }, + { /* end: all zeroes */}, +}; + +MODULE_DEVICE_TABLE(sdio, csdio_ids); + +static struct sdio_driver csdio_driver = { + .probe = csdio_probe, + .remove = csdio_remove, + .name = "csdio", + .id_table = csdio_ids, +}; + +static void __exit csdio_exit(void) +{ + dev_t devno = MKDEV(csdio_major, csdio_minor); + + sdio_unregister_driver(&csdio_driver); + sysfs_remove_group(&g_csdio.m_device->kobj, &dev_attr_grp); + kfree(g_sdio_buffer); + device_destroy(g_csdio.m_driver_class, devno); + cdev_del(&g_csdio.m_cdev); + class_destroy(g_csdio.m_driver_class); + unregister_chrdev_region(devno, csdio_transport_nr_devs); + pr_info("%s: Exit driver module\n", CSDIO_DEV_NAME); +} + +static char *csdio_devnode(struct device *dev, mode_t *mode) +{ + *mode = CSDIO_DEV_PERMISSIONS; + return NULL; +} + +static int __init csdio_init(void) +{ + int ret = 0; + dev_t devno = 0; + + pr_info("Init CSDIO driver module.\n"); + + /* Get a range of minor numbers to work with, asking for a dynamic */ + /* major unless directed otherwise at load time. */ + if (csdio_major) { + devno = MKDEV(csdio_major, csdio_minor); + ret = register_chrdev_region(devno, csdio_transport_nr_devs, + CSDIO_DEV_NAME); + } else { + ret = alloc_chrdev_region(&devno, csdio_minor, + csdio_transport_nr_devs, CSDIO_DEV_NAME); + csdio_major = MAJOR(devno); + } + if (ret < 0) { + pr_err("CSDIO: can't get major %d\n", csdio_major); + goto exit; + } + pr_info("CSDIO char driver major number is %d\n", csdio_major); + + /* kernel module got parameters: overwrite vendor and device id's */ + if ((csdio_vendor_id != 0) && (csdio_device_id != 0)) { + csdio_ids[0].vendor = (u16)csdio_vendor_id; + csdio_ids[0].device = (u16)csdio_device_id; + } + + /* prepare create /dev/... instance */ + g_csdio.m_driver_class = class_create(THIS_MODULE, CSDIO_DEV_NAME); + if (IS_ERR(g_csdio.m_driver_class)) { + ret = -ENOMEM; + pr_err(CSDIO_DEV_NAME " class_create failed\n"); + goto unregister_region; + } + g_csdio.m_driver_class->devnode = csdio_devnode; + + /* create CSDIO ctrl driver */ + g_csdio.m_device = csdio_cdev_init(&g_csdio.m_cdev, + &csdio_ctrl_fops, csdio_minor, CSDIO_DEV_NAME, NULL); + if (!g_csdio.m_device) { + pr_err("%s: Unable to create ctrl driver\n", + CSDIO_DEV_NAME); + goto destroy_class; + } + + g_sdio_buffer = kmalloc(CSDIO_SDIO_BUFFER_SIZE, GFP_KERNEL); + if (!g_sdio_buffer) { + pr_err("Unable to allocate %d bytes\n", CSDIO_SDIO_BUFFER_SIZE); + ret = -ENOMEM; + goto destroy_cdev; + } + + ret = sysfs_create_group(&g_csdio.m_device->kobj, &dev_attr_grp); + if (ret) { + pr_err("%s: Unable to create device attribute\n", + CSDIO_DEV_NAME); + goto free_sdio_buff; + } + + g_csdio.m_num_of_func = 0; + g_csdio.m_host = NULL; + + if (NULL != host_name) { + struct device *dev = bus_find_device_by_name(&platform_bus_type, + NULL, host_name); + if (NULL != dev) { + g_csdio.m_host = dev_get_drvdata(dev); + } else { + pr_err("%s: Host '%s' doesn't exist!\n", CSDIO_DEV_NAME, + host_name); + } + } + + pr_info("%s: Match with VendorId=0x%X, DeviceId=0x%X, Host = %s\n", + CSDIO_DEV_NAME, csdio_device_id, csdio_vendor_id, + (NULL == host_name) ? "Any" : host_name); + + /* register sdio driver */ + ret = sdio_register_driver(&csdio_driver); + if (ret) { + pr_err("%s: Unable to register as SDIO driver\n", + CSDIO_DEV_NAME); + goto remove_group; + } + + goto exit; + +remove_group: + sysfs_remove_group(&g_csdio.m_device->kobj, &dev_attr_grp); +free_sdio_buff: + kfree(g_sdio_buffer); +destroy_cdev: + cdev_del(&g_csdio.m_cdev); +destroy_class: + class_destroy(g_csdio.m_driver_class); +unregister_region: + unregister_chrdev_region(devno, csdio_transport_nr_devs); +exit: + return ret; +} +module_param(csdio_vendor_id, uint, S_IRUGO); +module_param(csdio_device_id, uint, S_IRUGO); +module_param(host_name, charp, S_IRUGO); + +module_init(csdio_init); +module_exit(csdio_exit); + +MODULE_AUTHOR("Code Aurora Forum"); +MODULE_DESCRIPTION("CSDIO device driver version " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/char/dcc_tty.c b/drivers/char/dcc_tty.c index a787accdcb1..7643f50d527 100644 --- a/drivers/char/dcc_tty.c +++ b/drivers/char/dcc_tty.c @@ -21,12 +21,13 @@ #include #include #include +#include MODULE_DESCRIPTION("DCC TTY Driver"); MODULE_LICENSE("GPL"); MODULE_VERSION("1.0"); -static spinlock_t g_dcc_tty_lock = SPIN_LOCK_UNLOCKED; +static spinlock_t g_dcc_tty_lock = __SPIN_LOCK_UNLOCKED(g_dcc_tty_lock); static struct hrtimer g_dcc_timer; static char g_dcc_buffer[16]; static int g_dcc_buffer_head; diff --git a/drivers/char/diag/Kconfig b/drivers/char/diag/Kconfig new file mode 100644 index 00000000000..eb0b21ee11e --- /dev/null +++ b/drivers/char/diag/Kconfig @@ -0,0 +1,31 @@ +menu "Diag Support" + +config DIAG_CHAR + tristate "char driver interface and diag forwarding to/from modem" + default m + depends on USB_G_ANDROID || USB_FUNCTION_DIAG || USB_QCOM_MAEMO + depends on ARCH_MSM + help + Char driver interface for diag user space and diag-forwarding to modem ARM and back. + This enables diagchar for maemo usb gadget or android usb gadget based on config selected. +endmenu + +menu "DIAG traffic over USB" + +config DIAG_OVER_USB + bool "Enable DIAG traffic to go over USB" + depends on ARCH_MSM + default y + help + This feature helps segregate code required for DIAG traffic to go over USB. +endmenu + +menu "SDIO support for DIAG" + +config DIAG_SDIO_PIPE + depends on MSM_SDIO_AL + default y + bool "Enable 9K DIAG traffic over SDIO" + help + SDIO Transport Layer for DIAG Router +endmenu diff --git a/drivers/char/diag/Makefile b/drivers/char/diag/Makefile new file mode 100644 index 00000000000..52ab2b94e1d --- /dev/null +++ b/drivers/char/diag/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_DIAG_CHAR) := diagchar.o +obj-$(CONFIG_DIAG_SDIO_PIPE) += diagfwd_sdio.o +diagchar-objs := diagchar_core.o diagchar_hdlc.o diagfwd.o diagmem.o diagfwd_cntl.o diff --git a/drivers/char/diag/diagchar.h b/drivers/char/diag/diagchar.h new file mode 100644 index 00000000000..6041954e14a --- /dev/null +++ b/drivers/char/diag/diagchar.h @@ -0,0 +1,222 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DIAGCHAR_H +#define DIAGCHAR_H + +#include +#include +#include +#include +#include +#include +#include +#include +/* Size of the USB buffers used for read and write*/ +#define USB_MAX_OUT_BUF 4096 +#define IN_BUF_SIZE 16384 +#define MAX_IN_BUF_SIZE 32768 +#define MAX_SYNC_OBJ_NAME_SIZE 32 +/* Size of the buffer used for deframing a packet + reveived from the PC tool*/ +#define HDLC_MAX 4096 +#define HDLC_OUT_BUF_SIZE 8192 +#define POOL_TYPE_COPY 1 +#define POOL_TYPE_HDLC 2 +#define POOL_TYPE_WRITE_STRUCT 4 +#define POOL_TYPE_ALL 7 +#define MODEM_DATA 1 +#define QDSP_DATA 2 +#define APPS_DATA 3 +#define SDIO_DATA 4 +#define WCNSS_DATA 5 +#define MODEM_PROC 0 +#define APPS_PROC 1 +#define QDSP_PROC 2 +#define WCNSS_PROC 3 +#define MSG_MASK_SIZE 8000 +#define LOG_MASK_SIZE 8000 +#define EVENT_MASK_SIZE 1000 +#define PKT_SIZE 4096 +#define MAX_EQUIP_ID 12 + +/* Maximum number of pkt reg supported at initialization*/ +extern unsigned int diag_max_registration; +extern unsigned int diag_threshold_registration; + +#define APPEND_DEBUG(ch) \ +do { \ + diag_debug_buf[diag_debug_buf_idx] = ch; \ + (diag_debug_buf_idx < 1023) ? \ + (diag_debug_buf_idx++) : (diag_debug_buf_idx = 0); \ +} while (0) + +struct diag_master_table { + uint16_t cmd_code; + uint16_t subsys_id; + uint32_t client_id; + uint16_t cmd_code_lo; + uint16_t cmd_code_hi; + int process_id; +}; + +struct bindpkt_params_per_process { + /* Name of the synchronization object associated with this proc */ + char sync_obj_name[MAX_SYNC_OBJ_NAME_SIZE]; + uint32_t count; /* Number of entries in this bind */ + struct bindpkt_params *params; /* first bind params */ +}; + +struct bindpkt_params { + uint16_t cmd_code; + uint16_t subsys_id; + uint16_t cmd_code_lo; + uint16_t cmd_code_hi; + /* For Central Routing, used to store Processor number */ + uint16_t proc_id; + uint32_t event_id; + uint32_t log_code; + /* For Central Routing, used to store SMD channel pointer */ + uint32_t client_id; +}; + +struct diag_write_device { + void *buf; + int length; +}; + +struct diag_client_map { + char name[20]; + int pid; +}; + +/* This structure is defined in USB header file */ +#ifndef CONFIG_DIAG_OVER_USB +struct diag_request { + char *buf; + int length; + int actual; + int status; + void *context; +}; +#endif + +struct diagchar_dev { + + /* State for the char driver */ + unsigned int major; + unsigned int minor_start; + int num; + struct cdev *cdev; + char *name; + int dropped_count; + struct class *diagchar_class; + int ref_count; + struct mutex diagchar_mutex; + wait_queue_head_t wait_q; + struct diag_client_map *client_map; + int *data_ready; + int num_clients; + struct diag_write_device *buf_tbl; + + /* Memory pool parameters */ + unsigned int itemsize; + unsigned int poolsize; + unsigned int itemsize_hdlc; + unsigned int poolsize_hdlc; + unsigned int itemsize_write_struct; + unsigned int poolsize_write_struct; + unsigned int debug_flag; + /* State for the mempool for the char driver */ + mempool_t *diagpool; + mempool_t *diag_hdlc_pool; + mempool_t *diag_write_struct_pool; + struct mutex diagmem_mutex; + int count; + int count_hdlc_pool; + int count_write_struct_pool; + int used; + + /* State for diag forwarding */ + unsigned char *buf_in_1; + unsigned char *buf_in_2; + unsigned char *buf_in_cntl; + unsigned char *buf_in_qdsp_1; + unsigned char *buf_in_qdsp_2; + unsigned char *buf_in_qdsp_cntl; + unsigned char *buf_in_wcnss; + unsigned char *buf_in_wcnss_cntl; + unsigned char *usb_buf_out; + unsigned char *apps_rsp_buf; + smd_channel_t *ch; + smd_channel_t *ch_cntl; + smd_channel_t *chqdsp; + smd_channel_t *chqdsp_cntl; + smd_channel_t *ch_wcnss; + smd_channel_t *ch_wcnss_cntl; + int in_busy_1; + int in_busy_2; + int in_busy_qdsp_1; + int in_busy_qdsp_2; + int in_busy_wcnss; + int read_len_legacy; + unsigned char *hdlc_buf; + unsigned hdlc_count; + unsigned hdlc_escape; +#ifdef CONFIG_DIAG_OVER_USB + int usb_connected; + struct usb_diag_ch *legacy_ch; + struct work_struct diag_proc_hdlc_work; + struct work_struct diag_read_work; +#endif + struct workqueue_struct *diag_wq; + struct work_struct diag_drain_work; + struct work_struct diag_read_smd_work; + struct work_struct diag_read_smd_cntl_work; + struct work_struct diag_read_smd_qdsp_work; + struct work_struct diag_read_smd_qdsp_cntl_work; + struct work_struct diag_read_smd_wcnss_work; + struct work_struct diag_read_smd_wcnss_cntl_work; + uint8_t *msg_masks; + uint8_t *log_masks; + int log_masks_length; + uint8_t *event_masks; + struct diag_master_table *table; + uint8_t *pkt_buf; + int pkt_length; + struct diag_request *write_ptr_1; + struct diag_request *write_ptr_2; + struct diag_request *usb_read_ptr; + struct diag_request *write_ptr_svc; + struct diag_request *write_ptr_qdsp_1; + struct diag_request *write_ptr_qdsp_2; + struct diag_request *write_ptr_wcnss; + int logging_mode; + int logging_process_id; +#ifdef CONFIG_DIAG_SDIO_PIPE + unsigned char *buf_in_sdio; + unsigned char *usb_buf_mdm_out; + struct sdio_channel *sdio_ch; + int read_len_mdm; + int in_busy_sdio; + struct usb_diag_ch *mdm_ch; + struct work_struct diag_read_mdm_work; + struct workqueue_struct *diag_sdio_wq; + struct work_struct diag_read_sdio_work; + struct work_struct diag_remove_sdio_work; + struct diag_request *usb_read_mdm_ptr; + struct diag_request *write_ptr_mdm; +#endif +}; + +extern struct diagchar_dev *driver; +#endif diff --git a/drivers/char/diag/diagchar_core.c b/drivers/char/diag/diagchar_core.c new file mode 100644 index 00000000000..c9a9d573f1f --- /dev/null +++ b/drivers/char/diag/diagchar_core.c @@ -0,0 +1,982 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_DIAG_OVER_USB +#include +#endif +#include +#include "diagchar_hdlc.h" +#include "diagmem.h" +#include "diagchar.h" +#include "diagfwd.h" +#include "diagfwd_cntl.h" +#ifdef CONFIG_DIAG_SDIO_PIPE +#include "diagfwd_sdio.h" +#endif +#include + +MODULE_DESCRIPTION("Diag Char Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); + +#define INIT 1 +#define EXIT -1 +struct diagchar_dev *driver; +struct diagchar_priv { + int pid; +}; +/* The following variables can be specified by module options */ + /* for copy buffer */ +static unsigned int itemsize = 2048; /*Size of item in the mempool */ +static unsigned int poolsize = 10; /*Number of items in the mempool */ +/* for hdlc buffer */ +static unsigned int itemsize_hdlc = 8192; /*Size of item in the mempool */ +static unsigned int poolsize_hdlc = 8; /*Number of items in the mempool */ +/* for write structure buffer */ +static unsigned int itemsize_write_struct = 20; /*Size of item in the mempool */ +static unsigned int poolsize_write_struct = 8; /* Num of items in the mempool */ +/* This is the max number of user-space clients supported at initialization*/ +static unsigned int max_clients = 15; +static unsigned int threshold_client_limit = 30; +/* This is the maximum number of pkt registrations supported at initialization*/ +unsigned int diag_max_registration = 500; +unsigned int diag_threshold_registration = 650; + +/* Timer variables */ +static struct timer_list drain_timer; +static int timer_in_progress; +void *buf_hdlc; +module_param(itemsize, uint, 0); +module_param(poolsize, uint, 0); +module_param(max_clients, uint, 0); + +/* delayed_rsp_id 0 represents no delay in the response. Any other number + means that the diag packet has a delayed response. */ +static uint16_t delayed_rsp_id = 1; +#define DIAGPKT_MAX_DELAYED_RSP 0xFFFF +/* This macro gets the next delayed respose id. Once it reaches + DIAGPKT_MAX_DELAYED_RSP, it stays at DIAGPKT_MAX_DELAYED_RSP */ + +#define DIAGPKT_NEXT_DELAYED_RSP_ID(x) \ +((x < DIAGPKT_MAX_DELAYED_RSP) ? x++ : DIAGPKT_MAX_DELAYED_RSP) + +#define COPY_USER_SPACE_OR_EXIT(buf, data, length) \ +do { \ + if ((count < ret+length) || (copy_to_user(buf, \ + (void *)&data, length))) { \ + ret = -EFAULT; \ + goto exit; \ + } \ + ret += length; \ +} while (0) + +static void drain_timer_func(unsigned long data) +{ + queue_work(driver->diag_wq , &(driver->diag_drain_work)); +} + +void diag_drain_work_fn(struct work_struct *work) +{ + int err = 0; + timer_in_progress = 0; + + mutex_lock(&driver->diagchar_mutex); + if (buf_hdlc) { + err = diag_device_write(buf_hdlc, APPS_DATA, NULL); + if (err) { + /*Free the buffer right away if write failed */ + diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); + diagmem_free(driver, (unsigned char *)driver-> + write_ptr_svc, POOL_TYPE_WRITE_STRUCT); + } + buf_hdlc = NULL; +#ifdef DIAG_DEBUG + pr_debug("diag: Number of bytes written " + "from timer is %d ", driver->used); +#endif + driver->used = 0; + } + mutex_unlock(&driver->diagchar_mutex); +} + +void diag_read_smd_work_fn(struct work_struct *work) +{ + __diag_smd_send_req(); +} + +void diag_read_smd_qdsp_work_fn(struct work_struct *work) +{ + __diag_smd_qdsp_send_req(); +} + +void diag_read_smd_wcnss_work_fn(struct work_struct *work) +{ + __diag_smd_wcnss_send_req(); +} + +void diag_add_client(int i, struct file *file) +{ + struct diagchar_priv *diagpriv_data; + + driver->client_map[i].pid = current->tgid; + diagpriv_data = kmalloc(sizeof(struct diagchar_priv), + GFP_KERNEL); + if (diagpriv_data) + diagpriv_data->pid = current->tgid; + file->private_data = diagpriv_data; + strncpy(driver->client_map[i].name, current->comm, 20); + driver->client_map[i].name[19] = '\0'; +} + +static int diagchar_open(struct inode *inode, struct file *file) +{ + int i = 0; + void *temp; + + if (driver) { + mutex_lock(&driver->diagchar_mutex); + + for (i = 0; i < driver->num_clients; i++) + if (driver->client_map[i].pid == 0) + break; + + if (i < driver->num_clients) { + diag_add_client(i, file); + } else { + if (i < threshold_client_limit) { + driver->num_clients++; + temp = krealloc(driver->client_map + , (driver->num_clients) * sizeof(struct + diag_client_map), GFP_KERNEL); + if (!temp) + goto fail; + else + driver->client_map = temp; + temp = krealloc(driver->data_ready + , (driver->num_clients) * sizeof(int), + GFP_KERNEL); + if (!temp) + goto fail; + else + driver->data_ready = temp; + diag_add_client(i, file); + } else { + mutex_unlock(&driver->diagchar_mutex); + pr_alert("Max client limit for DIAG reached\n"); + pr_info("Cannot open handle %s" + " %d", current->comm, current->tgid); + for (i = 0; i < driver->num_clients; i++) + pr_debug("%d) %s PID=%d", i, driver-> + client_map[i].name, + driver->client_map[i].pid); + return -ENOMEM; + } + } + driver->data_ready[i] |= MSG_MASKS_TYPE; + driver->data_ready[i] |= EVENT_MASKS_TYPE; + driver->data_ready[i] |= LOG_MASKS_TYPE; + + if (driver->ref_count == 0) + diagmem_init(driver); + driver->ref_count++; + mutex_unlock(&driver->diagchar_mutex); + return 0; + } + return -ENOMEM; + +fail: + mutex_unlock(&driver->diagchar_mutex); + driver->num_clients--; + pr_alert("diag: Insufficient memory for new client"); + return -ENOMEM; +} + +static int diagchar_close(struct inode *inode, struct file *file) +{ + int i = 0; + struct diagchar_priv *diagpriv_data = file->private_data; + + if (!(file->private_data)) { + pr_alert("diag: Invalid file pointer"); + return -ENOMEM; + } + +#ifdef CONFIG_DIAG_OVER_USB + /* If the SD logging process exits, change logging to USB mode */ + if (driver->logging_process_id == current->tgid) { + driver->logging_mode = USB_MODE; + diagfwd_connect(); + } +#endif /* DIAG over USB */ + /* Delete the pkt response table entry for the exiting process */ + for (i = 0; i < diag_max_registration; i++) + if (driver->table[i].process_id == current->tgid) + driver->table[i].process_id = 0; + + if (driver) { + mutex_lock(&driver->diagchar_mutex); + driver->ref_count--; + /* On Client exit, try to destroy all 3 pools */ + diagmem_exit(driver, POOL_TYPE_COPY); + diagmem_exit(driver, POOL_TYPE_HDLC); + diagmem_exit(driver, POOL_TYPE_WRITE_STRUCT); + for (i = 0; i < driver->num_clients; i++) { + if (NULL != diagpriv_data && diagpriv_data->pid == + driver->client_map[i].pid) { + driver->client_map[i].pid = 0; + kfree(diagpriv_data); + diagpriv_data = NULL; + break; + } + } + mutex_unlock(&driver->diagchar_mutex); + return 0; + } + return -ENOMEM; +} + +void diag_fill_reg_table(int j, struct bindpkt_params *params, + int *success, int *count_entries) +{ + *success = 1; + driver->table[j].cmd_code = params->cmd_code; + driver->table[j].subsys_id = params->subsys_id; + driver->table[j].cmd_code_lo = params->cmd_code_lo; + driver->table[j].cmd_code_hi = params->cmd_code_hi; + if (params->proc_id == APPS_PROC) { + driver->table[j].process_id = current->tgid; + driver->table[j].client_id = APPS_PROC; + } else { + driver->table[j].process_id = NON_APPS_PROC; + driver->table[j].client_id = params->client_id; + } + (*count_entries)++; +} + +long diagchar_ioctl(struct file *filp, + unsigned int iocmd, unsigned long ioarg) +{ + int i, j, count_entries = 0, temp; + int success = -1; + void *temp_buf; + + if (iocmd == DIAG_IOCTL_COMMAND_REG) { + struct bindpkt_params_per_process *pkt_params = + (struct bindpkt_params_per_process *) ioarg; + mutex_lock(&driver->diagchar_mutex); + for (i = 0; i < diag_max_registration; i++) { + if (driver->table[i].process_id == 0) { + diag_fill_reg_table(i, pkt_params->params, + &success, &count_entries); + if (pkt_params->count > count_entries) { + pkt_params->params++; + } else { + mutex_unlock(&driver->diagchar_mutex); + return success; + } + } + } + if (i < diag_threshold_registration) { + /* Increase table size by amount required */ + diag_max_registration += pkt_params->count - + count_entries; + /* Make sure size doesnt go beyond threshold */ + if (diag_max_registration > diag_threshold_registration) + diag_max_registration = + diag_threshold_registration; + temp_buf = krealloc(driver->table, + diag_max_registration*sizeof(struct + diag_master_table), GFP_KERNEL); + if (!temp_buf) { + diag_max_registration -= pkt_params->count - + count_entries; + pr_alert("diag: Insufficient memory for reg."); + mutex_unlock(&driver->diagchar_mutex); + return 0; + } else { + driver->table = temp_buf; + } + for (j = i; j < diag_max_registration; j++) { + diag_fill_reg_table(j, pkt_params->params, + &success, &count_entries); + if (pkt_params->count > count_entries) { + pkt_params->params++; + } else { + mutex_unlock(&driver->diagchar_mutex); + return success; + } + } + } else { + mutex_unlock(&driver->diagchar_mutex); + pr_err("Max size reached, Pkt Registration failed for" + " Process %d", current->tgid); + } + success = 0; + } else if (iocmd == DIAG_IOCTL_GET_DELAYED_RSP_ID) { + struct diagpkt_delay_params *delay_params = + (struct diagpkt_delay_params *) ioarg; + + if ((delay_params->rsp_ptr) && + (delay_params->size == sizeof(delayed_rsp_id)) && + (delay_params->num_bytes_ptr)) { + *((uint16_t *)delay_params->rsp_ptr) = + DIAGPKT_NEXT_DELAYED_RSP_ID(delayed_rsp_id); + *(delay_params->num_bytes_ptr) = sizeof(delayed_rsp_id); + success = 0; + } + } else if (iocmd == DIAG_IOCTL_LSM_DEINIT) { + for (i = 0; i < driver->num_clients; i++) + if (driver->client_map[i].pid == current->tgid) + break; + if (i == -1) + return -EINVAL; + driver->data_ready[i] |= DEINIT_TYPE; + wake_up_interruptible(&driver->wait_q); + success = 1; + } else if (iocmd == DIAG_IOCTL_SWITCH_LOGGING) { + mutex_lock(&driver->diagchar_mutex); + temp = driver->logging_mode; + driver->logging_mode = (int)ioarg; + driver->logging_process_id = current->tgid; + mutex_unlock(&driver->diagchar_mutex); + if (temp == MEMORY_DEVICE_MODE && driver->logging_mode + == NO_LOGGING_MODE) { + driver->in_busy_1 = 1; + driver->in_busy_2 = 1; + driver->in_busy_qdsp_1 = 1; + driver->in_busy_qdsp_2 = 1; + } else if (temp == NO_LOGGING_MODE && driver->logging_mode + == MEMORY_DEVICE_MODE) { + driver->in_busy_1 = 0; + driver->in_busy_2 = 0; + driver->in_busy_qdsp_1 = 0; + driver->in_busy_qdsp_2 = 0; + /* Poll SMD channels to check for data*/ + if (driver->ch) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_work)); + if (driver->chqdsp) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_qdsp_work)); + } +#ifdef CONFIG_DIAG_OVER_USB + else if (temp == USB_MODE && driver->logging_mode + == NO_LOGGING_MODE) + diagfwd_disconnect(); + else if (temp == NO_LOGGING_MODE && driver->logging_mode + == USB_MODE) + diagfwd_connect(); + else if (temp == USB_MODE && driver->logging_mode + == MEMORY_DEVICE_MODE) { + diagfwd_disconnect(); + driver->in_busy_1 = 0; + driver->in_busy_2 = 0; + driver->in_busy_qdsp_2 = 0; + driver->in_busy_qdsp_2 = 0; + /* Poll SMD channels to check for data*/ + if (driver->ch) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_work)); + if (driver->chqdsp) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_qdsp_work)); + } else if (temp == MEMORY_DEVICE_MODE && driver->logging_mode + == USB_MODE) + diagfwd_connect(); +#endif /* DIAG over USB */ + success = 1; + } + + return success; +} + +static int diagchar_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int index = -1, i = 0, ret = 0; + int num_data = 0, data_type; + for (i = 0; i < driver->num_clients; i++) + if (driver->client_map[i].pid == current->tgid) + index = i; + + if (index == -1) { + pr_err("diag: Client PID not found in table"); + return -EINVAL; + } + + wait_event_interruptible(driver->wait_q, + driver->data_ready[index]); + mutex_lock(&driver->diagchar_mutex); + + if ((driver->data_ready[index] & MEMORY_DEVICE_LOG_TYPE) && (driver-> + logging_mode == MEMORY_DEVICE_MODE)) { + /*Copy the type of data being passed*/ + data_type = driver->data_ready[index] & MEMORY_DEVICE_LOG_TYPE; + COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); + /* place holder for number of data field */ + ret += 4; + + for (i = 0; i < driver->poolsize_write_struct; i++) { + if (driver->buf_tbl[i].length > 0) { +#ifdef DIAG_DEBUG + pr_debug("diag: WRITING the buf address " + "and length is %x , %d\n", (unsigned int) + (driver->buf_tbl[i].buf), + driver->buf_tbl[i].length); +#endif + num_data++; + /* Copy the length of data being passed */ + if (copy_to_user(buf+ret, (void *)&(driver-> + buf_tbl[i].length), 4)) { + num_data--; + goto drop; + } + ret += 4; + + /* Copy the actual data being passed */ + if (copy_to_user(buf+ret, (void *)driver-> + buf_tbl[i].buf, driver->buf_tbl[i].length)) { + ret -= 4; + num_data--; + goto drop; + } + ret += driver->buf_tbl[i].length; +drop: +#ifdef DIAG_DEBUG + pr_debug("diag: DEQUEUE buf address and" + " length is %x,%d\n", (unsigned int) + (driver->buf_tbl[i].buf), driver-> + buf_tbl[i].length); +#endif + diagmem_free(driver, (unsigned char *) + (driver->buf_tbl[i].buf), POOL_TYPE_HDLC); + driver->buf_tbl[i].length = 0; + driver->buf_tbl[i].buf = 0; + } + } + + /* copy modem data */ + if (driver->in_busy_1 == 1) { + num_data++; + /*Copy the length of data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + (driver->write_ptr_1->length), 4); + /*Copy the actual data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + *(driver->buf_in_1), + driver->write_ptr_1->length); + driver->in_busy_1 = 0; + } + if (driver->in_busy_2 == 1) { + num_data++; + /*Copy the length of data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + (driver->write_ptr_2->length), 4); + /*Copy the actual data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + *(driver->buf_in_2), + driver->write_ptr_2->length); + driver->in_busy_2 = 0; + } + + /* copy q6 data */ + if (driver->in_busy_qdsp_1 == 1) { + num_data++; + /*Copy the length of data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + (driver->write_ptr_qdsp_1->length), 4); + /*Copy the actual data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver-> + buf_in_qdsp_1), + driver->write_ptr_qdsp_1->length); + driver->in_busy_qdsp_1 = 0; + } + if (driver->in_busy_qdsp_2 == 1) { + num_data++; + /*Copy the length of data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, + (driver->write_ptr_qdsp_2->length), 4); + /*Copy the actual data being passed*/ + COPY_USER_SPACE_OR_EXIT(buf+ret, *(driver-> + buf_in_qdsp_2), driver-> + write_ptr_qdsp_2->length); + driver->in_busy_qdsp_2 = 0; + } + + /* copy number of data fields */ + COPY_USER_SPACE_OR_EXIT(buf+4, num_data, 4); + ret -= 4; + driver->data_ready[index] ^= MEMORY_DEVICE_LOG_TYPE; + if (driver->ch) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_work)); + if (driver->chqdsp) + queue_work(driver->diag_wq, + &(driver->diag_read_smd_qdsp_work)); + APPEND_DEBUG('n'); + goto exit; + } else if (driver->data_ready[index] & MEMORY_DEVICE_LOG_TYPE) { + /* In case, the thread wakes up and the logging mode is + not memory device any more, the condition needs to be cleared */ + driver->data_ready[index] ^= MEMORY_DEVICE_LOG_TYPE; + } + + if (driver->data_ready[index] & DEINIT_TYPE) { + /*Copy the type of data being passed*/ + data_type = driver->data_ready[index] & DEINIT_TYPE; + COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); + driver->data_ready[index] ^= DEINIT_TYPE; + goto exit; + } + + if (driver->data_ready[index] & MSG_MASKS_TYPE) { + /*Copy the type of data being passed*/ + data_type = driver->data_ready[index] & MSG_MASKS_TYPE; + COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); + COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->msg_masks), + MSG_MASK_SIZE); + driver->data_ready[index] ^= MSG_MASKS_TYPE; + goto exit; + } + + if (driver->data_ready[index] & EVENT_MASKS_TYPE) { + /*Copy the type of data being passed*/ + data_type = driver->data_ready[index] & EVENT_MASKS_TYPE; + COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); + COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->event_masks), + EVENT_MASK_SIZE); + driver->data_ready[index] ^= EVENT_MASKS_TYPE; + goto exit; + } + + if (driver->data_ready[index] & LOG_MASKS_TYPE) { + /*Copy the type of data being passed*/ + data_type = driver->data_ready[index] & LOG_MASKS_TYPE; + COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); + COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->log_masks), + LOG_MASK_SIZE); + driver->data_ready[index] ^= LOG_MASKS_TYPE; + goto exit; + } + + if (driver->data_ready[index] & PKT_TYPE) { + /*Copy the type of data being passed*/ + data_type = driver->data_ready[index] & PKT_TYPE; + COPY_USER_SPACE_OR_EXIT(buf, data_type, 4); + COPY_USER_SPACE_OR_EXIT(buf+4, *(driver->pkt_buf), + driver->pkt_length); + driver->data_ready[index] ^= PKT_TYPE; + goto exit; + } + +exit: + mutex_unlock(&driver->diagchar_mutex); + return ret; +} + +static int diagchar_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int err, ret = 0, pkt_type; +#ifdef DIAG_DEBUG + int length = 0, i; +#endif + struct diag_send_desc_type send = { NULL, NULL, DIAG_STATE_START, 0 }; + struct diag_hdlc_dest_type enc = { NULL, NULL, 0 }; + void *buf_copy = NULL; + int payload_size; +#ifdef CONFIG_DIAG_OVER_USB + if (((driver->logging_mode == USB_MODE) && (!driver->usb_connected)) || + (driver->logging_mode == NO_LOGGING_MODE)) { + /*Drop the diag payload */ + return -EIO; + } +#endif /* DIAG over USB */ + /* Get the packet type F3/log/event/Pkt response */ + err = copy_from_user((&pkt_type), buf, 4); + /*First 4 bytes indicate the type of payload - ignore these */ + payload_size = count - 4; + + if (pkt_type == MEMORY_DEVICE_LOG_TYPE) { + if (!mask_request_validate((unsigned char *)buf)) { + printk(KERN_ALERT "mask request Invalid ..cannot send to modem \n"); + return -EFAULT; + } + buf = buf + 4; +#ifdef DIAG_DEBUG + pr_debug("diag: masks: %d\n", payload_size); + for (i = 0; i < payload_size; i++) + printk(KERN_DEBUG "\t %x", *(((unsigned char *)buf)+i)); +#endif + diag_process_hdlc((void *)buf, payload_size); + return 0; + } + + buf_copy = diagmem_alloc(driver, payload_size, POOL_TYPE_COPY); + if (!buf_copy) { + driver->dropped_count++; + return -ENOMEM; + } + + err = copy_from_user(buf_copy, buf + 4, payload_size); + if (err) { + printk(KERN_INFO "diagchar : copy_from_user failed\n"); + ret = -EFAULT; + goto fail_free_copy; + } +#ifdef DIAG_DEBUG + printk(KERN_DEBUG "data is -->\n"); + for (i = 0; i < payload_size; i++) + printk(KERN_DEBUG "\t %x \t", *(((unsigned char *)buf_copy)+i)); +#endif + send.state = DIAG_STATE_START; + send.pkt = buf_copy; + send.last = (void *)(buf_copy + payload_size - 1); + send.terminate = 1; +#ifdef DIAG_DEBUG + pr_debug("diag: Already used bytes in buffer %d, and" + " incoming payload size is %d\n", driver->used, payload_size); + printk(KERN_DEBUG "hdlc encoded data is -->\n"); + for (i = 0; i < payload_size + 8; i++) { + printk(KERN_DEBUG "\t %x \t", *(((unsigned char *)buf_hdlc)+i)); + if (*(((unsigned char *)buf_hdlc)+i) != 0x7e) + length++; + } +#endif + mutex_lock(&driver->diagchar_mutex); + if (!buf_hdlc) + buf_hdlc = diagmem_alloc(driver, HDLC_OUT_BUF_SIZE, + POOL_TYPE_HDLC); + if (!buf_hdlc) { + ret = -ENOMEM; + goto fail_free_hdlc; + } + if (HDLC_OUT_BUF_SIZE - driver->used <= (2*payload_size) + 3) { + err = diag_device_write(buf_hdlc, APPS_DATA, NULL); + if (err) { + /*Free the buffer right away if write failed */ + diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); + diagmem_free(driver, (unsigned char *)driver-> + write_ptr_svc, POOL_TYPE_WRITE_STRUCT); + ret = -EIO; + goto fail_free_hdlc; + } + buf_hdlc = NULL; + driver->used = 0; + buf_hdlc = diagmem_alloc(driver, HDLC_OUT_BUF_SIZE, + POOL_TYPE_HDLC); + if (!buf_hdlc) { + ret = -ENOMEM; + goto fail_free_hdlc; + } + } + + enc.dest = buf_hdlc + driver->used; + enc.dest_last = (void *)(buf_hdlc + driver->used + 2*payload_size + 3); + diag_hdlc_encode(&send, &enc); + + /* This is to check if after HDLC encoding, we are still within the + limits of aggregation buffer. If not, we write out the current buffer + and start aggregation in a newly allocated buffer */ + if ((unsigned int) enc.dest >= + (unsigned int)(buf_hdlc + HDLC_OUT_BUF_SIZE)) { + err = diag_device_write(buf_hdlc, APPS_DATA, NULL); + if (err) { + /*Free the buffer right away if write failed */ + diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); + diagmem_free(driver, (unsigned char *)driver-> + write_ptr_svc, POOL_TYPE_WRITE_STRUCT); + ret = -EIO; + goto fail_free_hdlc; + } + buf_hdlc = NULL; + driver->used = 0; + buf_hdlc = diagmem_alloc(driver, HDLC_OUT_BUF_SIZE, + POOL_TYPE_HDLC); + if (!buf_hdlc) { + ret = -ENOMEM; + goto fail_free_hdlc; + } + enc.dest = buf_hdlc + driver->used; + enc.dest_last = (void *)(buf_hdlc + driver->used + + (2*payload_size) + 3); + diag_hdlc_encode(&send, &enc); + } + + driver->used = (uint32_t) enc.dest - (uint32_t) buf_hdlc; + if (pkt_type == DATA_TYPE_RESPONSE) { + err = diag_device_write(buf_hdlc, APPS_DATA, NULL); + if (err) { + /*Free the buffer right away if write failed */ + diagmem_free(driver, buf_hdlc, POOL_TYPE_HDLC); + diagmem_free(driver, (unsigned char *)driver-> + write_ptr_svc, POOL_TYPE_WRITE_STRUCT); + ret = -EIO; + goto fail_free_hdlc; + } + buf_hdlc = NULL; + driver->used = 0; + } + + mutex_unlock(&driver->diagchar_mutex); + diagmem_free(driver, buf_copy, POOL_TYPE_COPY); + if (!timer_in_progress) { + timer_in_progress = 1; + ret = mod_timer(&drain_timer, jiffies + msecs_to_jiffies(500)); + } + return 0; + +fail_free_hdlc: + buf_hdlc = NULL; + driver->used = 0; + diagmem_free(driver, buf_copy, POOL_TYPE_COPY); + mutex_unlock(&driver->diagchar_mutex); + return ret; + +fail_free_copy: + diagmem_free(driver, buf_copy, POOL_TYPE_COPY); + return ret; +} + +int mask_request_validate(unsigned char mask_buf[]) +{ + uint8_t packet_id; + uint8_t subsys_id; + uint16_t ss_cmd; + + packet_id = mask_buf[4]; + + if (packet_id == 0x4B) { + subsys_id = mask_buf[5]; + ss_cmd = *(uint16_t *)(mask_buf + 6); + /* Packets with SSID which are allowed */ + switch (subsys_id) { + case 0x04: /* DIAG_SUBSYS_WCDMA */ + if ((ss_cmd == 0) || (ss_cmd == 0xF)) + return 1; + break; + case 0x08: /* DIAG_SUBSYS_GSM */ + if ((ss_cmd == 0) || (ss_cmd == 0x1)) + return 1; + break; + case 0x09: /* DIAG_SUBSYS_UMTS */ + case 0x0F: /* DIAG_SUBSYS_CM */ + if (ss_cmd == 0) + return 1; + break; + case 0x0C: /* DIAG_SUBSYS_OS */ + if ((ss_cmd == 2) || (ss_cmd == 0x100)) + return 1; /* MPU and APU */ + break; + case 0x12: /* DIAG_SUBSYS_DIAG_SERV */ + if ((ss_cmd == 0) || (ss_cmd == 0x6) || (ss_cmd == 0x7)) + return 1; + break; + case 0x13: /* DIAG_SUBSYS_FS */ + if ((ss_cmd == 0) || (ss_cmd == 0x1)) + return 1; + break; + default: + return 0; + break; + } + } else { + switch (packet_id) { + case 0x00: /* Version Number */ + case 0x0C: /* CDMA status packet */ + case 0x1C: /* Diag Version */ + case 0x1D: /* Time Stamp */ + case 0x60: /* Event Report Control */ + case 0x63: /* Status snapshot */ + case 0x73: /* Logging Configuration */ + case 0x7C: /* Extended build ID */ + case 0x7D: /* Extended Message configuration */ + case 0x81: /* Event get mask */ + case 0x82: /* Set the event mask */ + return 1; + break; + default: + return 0; + break; + } + } + return 0; +} + +static const struct file_operations diagcharfops = { + .owner = THIS_MODULE, + .read = diagchar_read, + .write = diagchar_write, + .unlocked_ioctl = diagchar_ioctl, + .open = diagchar_open, + .release = diagchar_close +}; + +static int diagchar_setup_cdev(dev_t devno) +{ + + int err; + + cdev_init(driver->cdev, &diagcharfops); + + driver->cdev->owner = THIS_MODULE; + driver->cdev->ops = &diagcharfops; + + err = cdev_add(driver->cdev, devno, 1); + + if (err) { + printk(KERN_INFO "diagchar cdev registration failed !\n\n"); + return -1; + } + + driver->diagchar_class = class_create(THIS_MODULE, "diag"); + + if (IS_ERR(driver->diagchar_class)) { + printk(KERN_ERR "Error creating diagchar class.\n"); + return -1; + } + + device_create(driver->diagchar_class, NULL, devno, + (void *)driver, "diag"); + + return 0; + +} + +static int diagchar_cleanup(void) +{ + if (driver) { + if (driver->cdev) { + /* TODO - Check if device exists before deleting */ + device_destroy(driver->diagchar_class, + MKDEV(driver->major, + driver->minor_start)); + cdev_del(driver->cdev); + } + if (!IS_ERR(driver->diagchar_class)) + class_destroy(driver->diagchar_class); + kfree(driver); + } + return 0; +} + +#ifdef CONFIG_DIAG_SDIO_PIPE +void diag_sdio_fn(int type) +{ + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) { + if (type == INIT) + diagfwd_sdio_init(); + else if (type == EXIT) + diagfwd_sdio_exit(); + } +} +#else +inline void diag_sdio_fn(int type) {} +#endif + +static int __init diagchar_init(void) +{ + dev_t dev; + int error; + + pr_debug("diagfwd initializing ..\n"); + driver = kzalloc(sizeof(struct diagchar_dev) + 5, GFP_KERNEL); + + if (driver) { + driver->used = 0; + timer_in_progress = 0; + driver->debug_flag = 1; + setup_timer(&drain_timer, drain_timer_func, 1234); + driver->itemsize = itemsize; + driver->poolsize = poolsize; + driver->itemsize_hdlc = itemsize_hdlc; + driver->poolsize_hdlc = poolsize_hdlc; + driver->itemsize_write_struct = itemsize_write_struct; + driver->poolsize_write_struct = poolsize_write_struct; + driver->num_clients = max_clients; + driver->logging_mode = USB_MODE; + mutex_init(&driver->diagchar_mutex); + init_waitqueue_head(&driver->wait_q); + INIT_WORK(&(driver->diag_drain_work), diag_drain_work_fn); + INIT_WORK(&(driver->diag_read_smd_work), diag_read_smd_work_fn); + INIT_WORK(&(driver->diag_read_smd_cntl_work), + diag_read_smd_cntl_work_fn); + INIT_WORK(&(driver->diag_read_smd_qdsp_work), + diag_read_smd_qdsp_work_fn); + INIT_WORK(&(driver->diag_read_smd_qdsp_cntl_work), + diag_read_smd_qdsp_cntl_work_fn); + INIT_WORK(&(driver->diag_read_smd_wcnss_work), + diag_read_smd_wcnss_work_fn); + INIT_WORK(&(driver->diag_read_smd_wcnss_cntl_work), + diag_read_smd_wcnss_cntl_work_fn); + diagfwd_init(); + diagfwd_cntl_init(); + diag_sdio_fn(INIT); + pr_debug("diagchar initializing ..\n"); + driver->num = 1; + driver->name = ((void *)driver) + sizeof(struct diagchar_dev); + strlcpy(driver->name, "diag", 4); + + /* Get major number from kernel and initialize */ + error = alloc_chrdev_region(&dev, driver->minor_start, + driver->num, driver->name); + if (!error) { + driver->major = MAJOR(dev); + driver->minor_start = MINOR(dev); + } else { + printk(KERN_INFO "Major number not allocated\n"); + goto fail; + } + driver->cdev = cdev_alloc(); + error = diagchar_setup_cdev(dev); + if (error) + goto fail; + } else { + printk(KERN_INFO "kzalloc failed\n"); + goto fail; + } + + pr_info("diagchar initialized now"); + return 0; + +fail: + diagchar_cleanup(); + diagfwd_exit(); + diagfwd_cntl_exit(); + diag_sdio_fn(EXIT); + return -1; +} + +static void __exit diagchar_exit(void) +{ + printk(KERN_INFO "diagchar exiting ..\n"); + /* On Driver exit, send special pool type to + ensure no memory leaks */ + diagmem_exit(driver, POOL_TYPE_ALL); + diagfwd_exit(); + diagfwd_cntl_exit(); + diag_sdio_fn(EXIT); + diagchar_cleanup(); + printk(KERN_INFO "done diagchar exit\n"); +} + +module_init(diagchar_init); +module_exit(diagchar_exit); diff --git a/drivers/char/diag/diagchar_hdlc.c b/drivers/char/diag/diagchar_hdlc.c new file mode 100644 index 00000000000..ef57d525962 --- /dev/null +++ b/drivers/char/diag/diagchar_hdlc.c @@ -0,0 +1,223 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "diagchar_hdlc.h" + + +MODULE_LICENSE("GPL v2"); + +#define CRC_16_L_SEED 0xFFFF + +#define CRC_16_L_STEP(xx_crc, xx_c) \ + crc_ccitt_byte(xx_crc, xx_c) + +void diag_hdlc_encode(struct diag_send_desc_type *src_desc, + struct diag_hdlc_dest_type *enc) +{ + uint8_t *dest; + uint8_t *dest_last; + const uint8_t *src; + const uint8_t *src_last; + uint16_t crc; + unsigned char src_byte = 0; + enum diag_send_state_enum_type state; + unsigned int used = 0; + + if (src_desc && enc) { + + /* Copy parts to local variables. */ + src = src_desc->pkt; + src_last = src_desc->last; + state = src_desc->state; + dest = enc->dest; + dest_last = enc->dest_last; + + if (state == DIAG_STATE_START) { + crc = CRC_16_L_SEED; + state++; + } else { + /* Get a local copy of the CRC */ + crc = enc->crc; + } + + /* dest or dest_last may be NULL to trigger a + state transition only */ + if (dest && dest_last) { + /* This condition needs to include the possibility + of 2 dest bytes for an escaped byte */ + while (src <= src_last && dest <= dest_last) { + + src_byte = *src++; + + if ((src_byte == CONTROL_CHAR) || + (src_byte == ESC_CHAR)) { + + /* If the escape character is not the + last byte */ + if (dest != dest_last) { + crc = CRC_16_L_STEP(crc, + src_byte); + + *dest++ = ESC_CHAR; + used++; + + *dest++ = src_byte + ^ ESC_MASK; + used++; + } else { + + src--; + break; + } + + } else { + crc = CRC_16_L_STEP(crc, src_byte); + *dest++ = src_byte; + used++; + } + } + + if (src > src_last) { + + if (state == DIAG_STATE_BUSY) { + if (src_desc->terminate) { + crc = ~crc; + state++; + } else { + /* Done with fragment */ + state = DIAG_STATE_COMPLETE; + } + } + + while (dest <= dest_last && + state >= DIAG_STATE_CRC1 && + state < DIAG_STATE_TERM) { + /* Encode a byte of the CRC next */ + src_byte = crc & 0xFF; + + if ((src_byte == CONTROL_CHAR) + || (src_byte == ESC_CHAR)) { + + if (dest != dest_last) { + + *dest++ = ESC_CHAR; + used++; + *dest++ = src_byte ^ + ESC_MASK; + used++; + + crc >>= 8; + } else { + + break; + } + } else { + + crc >>= 8; + *dest++ = src_byte; + used++; + } + + state++; + } + + if (state == DIAG_STATE_TERM) { + if (dest_last >= dest) { + *dest++ = CONTROL_CHAR; + used++; + state++; /* Complete */ + } + } + } + } + /* Copy local variables back into the encode structure. */ + + enc->dest = dest; + enc->dest_last = dest_last; + enc->crc = crc; + src_desc->pkt = src; + src_desc->last = src_last; + src_desc->state = state; + } + + return; +} + + +int diag_hdlc_decode(struct diag_hdlc_decode_type *hdlc) +{ + uint8_t *src_ptr = NULL, *dest_ptr = NULL; + unsigned int src_length = 0, dest_length = 0; + + unsigned int len = 0; + unsigned int i; + uint8_t src_byte; + + int pkt_bnd = 0; + + if (hdlc && hdlc->src_ptr && hdlc->dest_ptr && + (hdlc->src_size - hdlc->src_idx > 0) && + (hdlc->dest_size - hdlc->dest_idx > 0)) { + + src_ptr = hdlc->src_ptr; + src_ptr = &src_ptr[hdlc->src_idx]; + src_length = hdlc->src_size - hdlc->src_idx; + + dest_ptr = hdlc->dest_ptr; + dest_ptr = &dest_ptr[hdlc->dest_idx]; + dest_length = hdlc->dest_size - hdlc->dest_idx; + + for (i = 0; i < src_length; i++) { + + src_byte = src_ptr[i]; + + if (hdlc->escaping) { + dest_ptr[len++] = src_byte ^ ESC_MASK; + hdlc->escaping = 0; + } else if (src_byte == ESC_CHAR) { + if (i == (src_length - 1)) { + hdlc->escaping = 1; + i++; + break; + } else { + dest_ptr[len++] = src_ptr[++i] + ^ ESC_MASK; + } + } else if (src_byte == CONTROL_CHAR) { + dest_ptr[len++] = src_byte; + pkt_bnd = 1; + i++; + break; + } else { + dest_ptr[len++] = src_byte; + } + + if (len >= dest_length) { + i++; + break; + } + } + + hdlc->src_idx += i; + hdlc->dest_idx += len; + } + + return pkt_bnd; +} diff --git a/drivers/char/diag/diagchar_hdlc.h b/drivers/char/diag/diagchar_hdlc.h new file mode 100644 index 00000000000..2df81deb086 --- /dev/null +++ b/drivers/char/diag/diagchar_hdlc.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DIAGCHAR_HDLC +#define DIAGCHAR_HDLC + +enum diag_send_state_enum_type { + DIAG_STATE_START, + DIAG_STATE_BUSY, + DIAG_STATE_CRC1, + DIAG_STATE_CRC2, + DIAG_STATE_TERM, + DIAG_STATE_COMPLETE +}; + +struct diag_send_desc_type { + const void *pkt; + const void *last; /* Address of last byte to send. */ + enum diag_send_state_enum_type state; + unsigned char terminate; /* True if this fragment + terminates the packet */ +}; + +struct diag_hdlc_dest_type { + void *dest; + void *dest_last; + /* Below: internal use only */ + uint16_t crc; +}; + +struct diag_hdlc_decode_type { + uint8_t *src_ptr; + unsigned int src_idx; + unsigned int src_size; + uint8_t *dest_ptr; + unsigned int dest_idx; + unsigned int dest_size; + int escaping; + +}; + +void diag_hdlc_encode(struct diag_send_desc_type *src_desc, + struct diag_hdlc_dest_type *enc); + +int diag_hdlc_decode(struct diag_hdlc_decode_type *hdlc); + +#define ESC_CHAR 0x7D +#define CONTROL_CHAR 0x7E +#define ESC_MASK 0x20 + +#endif diff --git a/drivers/char/diag/diagfwd.c b/drivers/char/diag/diagfwd.c new file mode 100644 index 00000000000..433f09a7dbc --- /dev/null +++ b/drivers/char/diag/diagfwd.c @@ -0,0 +1,1384 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_DIAG_OVER_USB +#include +#endif +#include +#include +#include +#include "diagmem.h" +#include "diagchar.h" +#include "diagfwd.h" +#include "diagfwd_cntl.h" +#include "diagchar_hdlc.h" +#ifdef CONFIG_DIAG_SDIO_PIPE +#include "diagfwd_sdio.h" +#endif +#define MODE_CMD 41 +#define RESET_ID 2 + +int diag_debug_buf_idx; +unsigned char diag_debug_buf[1024]; +static unsigned int buf_tbl_size = 8; /*Number of entries in table of buffers */ +struct diag_master_table entry; + +struct diag_send_desc_type send = { NULL, NULL, DIAG_STATE_START, 0 }; +struct diag_hdlc_dest_type enc = { NULL, NULL, 0 }; + +#define ENCODE_RSP_AND_SEND(buf_length) \ +do { \ + send.state = DIAG_STATE_START; \ + send.pkt = driver->apps_rsp_buf; \ + send.last = (void *)(driver->apps_rsp_buf + buf_length); \ + send.terminate = 1; \ + if (!driver->in_busy_1) { \ + enc.dest = driver->buf_in_1; \ + enc.dest_last = (void *)(driver->buf_in_1 + 499); \ + diag_hdlc_encode(&send, &enc); \ + driver->write_ptr_1->buf = driver->buf_in_1; \ + driver->write_ptr_1->length = (int)(enc.dest - \ + (void *)(driver->buf_in_1)); \ + usb_diag_write(driver->legacy_ch, driver->write_ptr_1); \ + memset(driver->apps_rsp_buf, '\0', 500); \ + } \ +} while (0) + +#define CHK_OVERFLOW(bufStart, start, end, length) \ +((bufStart <= start) && (end - start >= length)) ? 1 : 0 + +int chk_config_get_id() +{ + switch (socinfo_get_id()) { + case APQ8060_MACHINE_ID: + case MSM8660_MACHINE_ID: + return APQ8060_TOOLS_ID; + case AO8960_MACHINE_ID: + return AO8960_TOOLS_ID; + default: + return 0; + } +} + +void __diag_smd_send_req(void) +{ + void *buf = NULL; + int *in_busy_ptr = NULL; + struct diag_request *write_ptr_modem = NULL; + + if (!driver->in_busy_1) { + buf = driver->buf_in_1; + write_ptr_modem = driver->write_ptr_1; + in_busy_ptr = &(driver->in_busy_1); + } else if (!driver->in_busy_2) { + buf = driver->buf_in_2; + write_ptr_modem = driver->write_ptr_2; + in_busy_ptr = &(driver->in_busy_2); + } + + if (driver->ch && buf) { + int r = smd_read_avail(driver->ch); + + if (r > IN_BUF_SIZE) { + if (r < MAX_IN_BUF_SIZE) { + pr_err("diag: SMD sending in " + "packets upto %d bytes", r); + buf = krealloc(buf, r, GFP_KERNEL); + } else { + pr_err("diag: SMD sending in " + "packets more than %d bytes", MAX_IN_BUF_SIZE); + return; + } + } + if (r > 0) { + if (!buf) + pr_info("Out of diagmem for Modem\n"); + else { + APPEND_DEBUG('i'); + smd_read(driver->ch, buf, r); + APPEND_DEBUG('j'); + write_ptr_modem->length = r; + *in_busy_ptr = 1; + diag_device_write(buf, MODEM_DATA, + write_ptr_modem); + } + } + } +} + +int diag_device_write(void *buf, int proc_num, struct diag_request *write_ptr) +{ + int i, err = 0; + + if (driver->logging_mode == MEMORY_DEVICE_MODE) { + if (proc_num == APPS_DATA) { + for (i = 0; i < driver->poolsize_write_struct; i++) + if (driver->buf_tbl[i].length == 0) { + driver->buf_tbl[i].buf = buf; + driver->buf_tbl[i].length = + driver->used; +#ifdef DIAG_DEBUG + pr_debug("diag: ENQUEUE buf ptr" + " and length is %x , %d\n", + (unsigned int)(driver->buf_ + tbl[i].buf), driver->buf_tbl[i].length); +#endif + break; + } + } + for (i = 0; i < driver->num_clients; i++) + if (driver->client_map[i].pid == + driver->logging_process_id) + break; + if (i < driver->num_clients) { + driver->data_ready[i] |= MEMORY_DEVICE_LOG_TYPE; + wake_up_interruptible(&driver->wait_q); + } else + return -EINVAL; + } else if (driver->logging_mode == NO_LOGGING_MODE) { + if (proc_num == MODEM_DATA) { + driver->in_busy_1 = 0; + driver->in_busy_2 = 0; + queue_work(driver->diag_wq, &(driver-> + diag_read_smd_work)); + } else if (proc_num == QDSP_DATA) { + driver->in_busy_qdsp_1 = 0; + driver->in_busy_qdsp_2 = 0; + queue_work(driver->diag_wq, &(driver-> + diag_read_smd_qdsp_work)); + } else if (proc_num == WCNSS_DATA) { + driver->in_busy_wcnss = 0; + queue_work(driver->diag_wq, &(driver-> + diag_read_smd_wcnss_work)); + } + err = -1; + } +#ifdef CONFIG_DIAG_OVER_USB + else if (driver->logging_mode == USB_MODE) { + if (proc_num == APPS_DATA) { + driver->write_ptr_svc = (struct diag_request *) + (diagmem_alloc(driver, sizeof(struct diag_request), + POOL_TYPE_WRITE_STRUCT)); + if (driver->write_ptr_svc) { + driver->write_ptr_svc->length = driver->used; + driver->write_ptr_svc->buf = buf; + err = usb_diag_write(driver->legacy_ch, + driver->write_ptr_svc); + } else + err = -1; + } else if (proc_num == MODEM_DATA) { + write_ptr->buf = buf; +#ifdef DIAG_DEBUG + printk(KERN_INFO "writing data to USB," + "pkt length %d\n", write_ptr->length); + print_hex_dump(KERN_DEBUG, "Written Packet Data to" + " USB: ", 16, 1, DUMP_PREFIX_ADDRESS, + buf, write_ptr->length, 1); +#endif /* DIAG DEBUG */ + err = usb_diag_write(driver->legacy_ch, write_ptr); + } else if (proc_num == QDSP_DATA) { + write_ptr->buf = buf; + err = usb_diag_write(driver->legacy_ch, write_ptr); + } else if (proc_num == WCNSS_DATA) { + write_ptr->buf = buf; + err = usb_diag_write(driver->legacy_ch, write_ptr); + } +#ifdef CONFIG_DIAG_SDIO_PIPE + else if (proc_num == SDIO_DATA) { + if (machine_is_msm8x60_fusion() || + machine_is_msm8x60_fusn_ffa()) { + write_ptr->buf = buf; + err = usb_diag_write(driver->mdm_ch, write_ptr); + } else + pr_err("diag: Incorrect data while USB write"); + } +#endif + APPEND_DEBUG('d'); + } +#endif /* DIAG OVER USB */ + return err; +} + +void __diag_smd_wcnss_send_req(void) +{ + void *buf = driver->buf_in_wcnss; + int *in_busy_wcnss_ptr = &(driver->in_busy_wcnss); + struct diag_request *write_ptr_wcnss = driver->write_ptr_wcnss; + + if (driver->ch_wcnss && buf) { + int r = smd_read_avail(driver->ch_wcnss); + if (r > IN_BUF_SIZE) { + if (r < MAX_IN_BUF_SIZE) { + pr_err("diag: wcnss packets > %d bytes", r); + buf = krealloc(buf, r, GFP_KERNEL); + } else { + pr_err("diag: wcnss pkt > %d", MAX_IN_BUF_SIZE); + return; + } + } + if (r > 0) { + if (!buf) { + pr_err("Out of diagmem for wcnss\n"); + } else { + APPEND_DEBUG('i'); + smd_read(driver->ch_wcnss, buf, r); + APPEND_DEBUG('j'); + write_ptr_wcnss->length = r; + *in_busy_wcnss_ptr = 1; + diag_device_write(buf, WCNSS_DATA, + write_ptr_wcnss); + } + } + } +} + +void __diag_smd_qdsp_send_req(void) +{ + void *buf = NULL; + int *in_busy_qdsp_ptr = NULL; + struct diag_request *write_ptr_qdsp = NULL; + + if (!driver->in_busy_qdsp_1) { + buf = driver->buf_in_qdsp_1; + write_ptr_qdsp = driver->write_ptr_qdsp_1; + in_busy_qdsp_ptr = &(driver->in_busy_qdsp_1); + } else if (!driver->in_busy_qdsp_2) { + buf = driver->buf_in_qdsp_2; + write_ptr_qdsp = driver->write_ptr_qdsp_2; + in_busy_qdsp_ptr = &(driver->in_busy_qdsp_2); + } + + if (driver->chqdsp && buf) { + int r = smd_read_avail(driver->chqdsp); + + if (r > IN_BUF_SIZE) { + if (r < MAX_IN_BUF_SIZE) { + pr_err("diag: SMD sending in " + "packets upto %d bytes", r); + buf = krealloc(buf, r, GFP_KERNEL); + } else { + pr_err("diag: SMD sending in " + "packets more than %d bytes", MAX_IN_BUF_SIZE); + return; + } + } + if (r > 0) { + if (!buf) + printk(KERN_INFO "Out of diagmem for QDSP\n"); + else { + APPEND_DEBUG('i'); + smd_read(driver->chqdsp, buf, r); + APPEND_DEBUG('j'); + write_ptr_qdsp->length = r; + *in_busy_qdsp_ptr = 1; + diag_device_write(buf, QDSP_DATA, + write_ptr_qdsp); + } + } + } +} + +static void diag_print_mask_table(void) +{ +/* Enable this to print mask table when updated */ +#ifdef MASK_DEBUG + int first; + int last; + uint8_t *ptr = driver->msg_masks; + int i = 0; + + while (*(uint32_t *)(ptr + 4)) { + first = *(uint32_t *)ptr; + ptr += 4; + last = *(uint32_t *)ptr; + ptr += 4; + printk(KERN_INFO "SSID %d - %d\n", first, last); + for (i = 0 ; i <= last - first ; i++) + printk(KERN_INFO "MASK:%x\n", *((uint32_t *)ptr + i)); + ptr += ((last - first) + 1)*4; + + } +#endif +} + +static void diag_update_msg_mask(int start, int end , uint8_t *buf) +{ + int found = 0; + int first; + int last; + uint8_t *ptr = driver->msg_masks; + uint8_t *ptr_buffer_start = &(*(driver->msg_masks)); + uint8_t *ptr_buffer_end = &(*(driver->msg_masks)) + MSG_MASK_SIZE; + + mutex_lock(&driver->diagchar_mutex); + /* First SSID can be zero : So check that last is non-zero */ + + while (*(uint32_t *)(ptr + 4)) { + first = *(uint32_t *)ptr; + ptr += 4; + last = *(uint32_t *)ptr; + ptr += 4; + if (start >= first && start <= last) { + ptr += (start - first)*4; + if (end <= last) + if (CHK_OVERFLOW(ptr_buffer_start, ptr, + ptr_buffer_end, + (((end - start)+1)*4))) + memcpy(ptr, buf , ((end - start)+1)*4); + else + printk(KERN_CRIT "Not enough" + " buffer space for" + " MSG_MASK\n"); + else + printk(KERN_INFO "Unable to copy" + " mask change\n"); + + found = 1; + break; + } else { + ptr += ((last - first) + 1)*4; + } + } + /* Entry was not found - add new table */ + if (!found) { + if (CHK_OVERFLOW(ptr_buffer_start, ptr, ptr_buffer_end, + 8 + ((end - start) + 1)*4)) { + memcpy(ptr, &(start) , 4); + ptr += 4; + memcpy(ptr, &(end), 4); + ptr += 4; + memcpy(ptr, buf , ((end - start) + 1)*4); + } else + printk(KERN_CRIT " Not enough buffer" + " space for MSG_MASK\n"); + } + mutex_unlock(&driver->diagchar_mutex); + diag_print_mask_table(); + +} + +static void diag_update_event_mask(uint8_t *buf, int toggle, int num_bits) +{ + uint8_t *ptr = driver->event_masks; + uint8_t *temp = buf + 2; + + mutex_lock(&driver->diagchar_mutex); + if (!toggle) + memset(ptr, 0 , EVENT_MASK_SIZE); + else + if (CHK_OVERFLOW(ptr, ptr, + ptr+EVENT_MASK_SIZE, + num_bits/8 + 1)) + memcpy(ptr, temp , num_bits/8 + 1); + else + printk(KERN_CRIT "Not enough buffer space " + "for EVENT_MASK\n"); + mutex_unlock(&driver->diagchar_mutex); +} + +static void diag_update_log_mask(int equip_id, uint8_t *buf, int num_items) +{ + uint8_t *temp = buf; + struct mask_info { + int equip_id; + int index; + }; + int i = 0; + unsigned char *ptr_data; + int offset = 8*MAX_EQUIP_ID; + struct mask_info *ptr = (struct mask_info *)driver->log_masks; + + mutex_lock(&driver->diagchar_mutex); + /* Check if we already know index of this equipment ID */ + for (i = 0; i < MAX_EQUIP_ID; i++) { + if ((ptr->equip_id == equip_id) && (ptr->index != 0)) { + offset = ptr->index; + break; + } + if ((ptr->equip_id == 0) && (ptr->index == 0)) { + /*Reached a null entry */ + ptr->equip_id = equip_id; + ptr->index = driver->log_masks_length; + offset = driver->log_masks_length; + driver->log_masks_length += ((num_items+7)/8); + break; + } + ptr++; + } + ptr_data = driver->log_masks + offset; + if (CHK_OVERFLOW(driver->log_masks, ptr_data, driver->log_masks + + LOG_MASK_SIZE, (num_items+7)/8)) + memcpy(ptr_data, temp , (num_items+7)/8); + else + printk(KERN_CRIT " Not enough buffer space for LOG_MASK\n"); + mutex_unlock(&driver->diagchar_mutex); +} + +static void diag_update_pkt_buffer(unsigned char *buf) +{ + unsigned char *ptr = driver->pkt_buf; + unsigned char *temp = buf; + + mutex_lock(&driver->diagchar_mutex); + if (CHK_OVERFLOW(ptr, ptr, ptr + PKT_SIZE, driver->pkt_length)) + memcpy(ptr, temp , driver->pkt_length); + else + printk(KERN_CRIT " Not enough buffer space for PKT_RESP\n"); + mutex_unlock(&driver->diagchar_mutex); +} + +void diag_update_userspace_clients(unsigned int type) +{ + int i; + + mutex_lock(&driver->diagchar_mutex); + for (i = 0; i < driver->num_clients; i++) + if (driver->client_map[i].pid != 0) + driver->data_ready[i] |= type; + wake_up_interruptible(&driver->wait_q); + mutex_unlock(&driver->diagchar_mutex); +} + +void diag_update_sleeping_process(int process_id) +{ + int i; + + mutex_lock(&driver->diagchar_mutex); + for (i = 0; i < driver->num_clients; i++) + if (driver->client_map[i].pid == process_id) { + driver->data_ready[i] |= PKT_TYPE; + break; + } + wake_up_interruptible(&driver->wait_q); + mutex_unlock(&driver->diagchar_mutex); +} + +void diag_send_data(struct diag_master_table entry, unsigned char *buf, + int len, int type) +{ + driver->pkt_length = len; + if (entry.process_id != NON_APPS_PROC && type != MODEM_DATA) { + diag_update_pkt_buffer(buf); + diag_update_sleeping_process(entry.process_id); + } else { + if (len > 0) { + if (entry.client_id == MODEM_PROC && driver->ch) + smd_write(driver->ch, buf, len); + else if (entry.client_id == QDSP_PROC && + driver->chqdsp) + smd_write(driver->chqdsp, buf, len); + else if (entry.client_id == WCNSS_PROC && + driver->ch_wcnss) + smd_write(driver->ch_wcnss, buf, len); + else + pr_alert("diag: incorrect channel"); + } + } +} + +static int diag_process_apps_pkt(unsigned char *buf, int len) +{ + uint16_t subsys_cmd_code; + int subsys_id, ssid_first, ssid_last, ssid_range; + int packet_type = 1, i, cmd_code; + unsigned char *temp = buf; + int data_type; +#if defined(CONFIG_DIAG_OVER_USB) + int payload_length; + unsigned char *ptr; +#endif + + /* Check for registered clients and forward packet to apropriate proc */ + cmd_code = (int)(*(char *)buf); + temp++; + subsys_id = (int)(*(char *)temp); + temp++; + subsys_cmd_code = *(uint16_t *)temp; + temp += 2; + data_type = APPS_DATA; + /* Dont send any command other than mode reset */ + if (cpu_is_msm8960() && cmd_code == MODE_CMD) { + if (subsys_id != RESET_ID) + data_type = MODEM_DATA; + } + + pr_debug("diag: %d %d %d", cmd_code, subsys_id, subsys_cmd_code); + for (i = 0; i < diag_max_registration; i++) { + entry = driver->table[i]; + if (entry.process_id != NO_PROCESS) { + if (entry.cmd_code == cmd_code && entry.subsys_id == + subsys_id && entry.cmd_code_lo <= + subsys_cmd_code && + entry.cmd_code_hi >= subsys_cmd_code) { + diag_send_data(entry, buf, len, data_type); + packet_type = 0; + } else if (entry.cmd_code == 255 + && cmd_code == 75) { + if (entry.subsys_id == + subsys_id && + entry.cmd_code_lo <= + subsys_cmd_code && + entry.cmd_code_hi >= + subsys_cmd_code) { + diag_send_data(entry, buf, len, + data_type); + packet_type = 0; + } + } else if (entry.cmd_code == 255 && + entry.subsys_id == 255) { + if (entry.cmd_code_lo <= + cmd_code && + entry. + cmd_code_hi >= cmd_code) { + diag_send_data(entry, buf, len, + data_type); + packet_type = 0; + } + } + } + } + /* set event mask */ + if (*buf == 0x82) { + buf += 4; + diag_update_event_mask(buf, 1, *(uint16_t *)buf); + diag_update_userspace_clients(EVENT_MASKS_TYPE); + } + /* event mask change */ + else if ((*buf == 0x60) && (*(buf+1) == 0x0)) { + diag_update_event_mask(buf+1, 0, 0); + diag_update_userspace_clients(EVENT_MASKS_TYPE); +#if defined(CONFIG_DIAG_OVER_USB) + /* Check for Apps Only 8960 */ + if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)) { + /* echo response back for apps only DIAG */ + driver->apps_rsp_buf[0] = 0x60; + driver->apps_rsp_buf[1] = 0x0; + driver->apps_rsp_buf[2] = 0x0; + ENCODE_RSP_AND_SEND(2); + return 0; + } +#endif + } + /* Set log masks */ + else if (*buf == 0x73 && *(int *)(buf+4) == 3) { + buf += 8; + /* Read Equip ID and pass as first param below*/ + diag_update_log_mask(*(int *)buf, buf+8, *(int *)(buf+4)); + diag_update_userspace_clients(LOG_MASKS_TYPE); +#if defined(CONFIG_DIAG_OVER_USB) + /* Check for Apps Only 8960 */ + if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)) { + /* echo response back for Apps only DIAG */ + driver->apps_rsp_buf[0] = 0x73; + *(int *)(driver->apps_rsp_buf + 4) = 0x3; /* op. ID */ + *(int *)(driver->apps_rsp_buf + 8) = 0x0; /* success */ + payload_length = 8 + ((*(int *)(buf + 4)) + 7)/8; + for (i = 0; i < payload_length; i++) + *(int *)(driver->apps_rsp_buf+12+i) = + *(buf+8+i); + ENCODE_RSP_AND_SEND(12 + payload_length - 1); + return 0; + } +#endif + } + /* Check for set message mask */ + else if ((*buf == 0x7d) && (*(buf+1) == 0x4)) { + ssid_first = *(uint16_t *)(buf + 2); + ssid_last = *(uint16_t *)(buf + 4); + ssid_range = 4 * (ssid_last - ssid_first + 1); + diag_update_msg_mask(ssid_first, ssid_last , buf + 8); + diag_update_userspace_clients(MSG_MASKS_TYPE); +#if defined(CONFIG_DIAG_OVER_USB) + if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID)) { + /* echo response back for apps only DIAG */ + for (i = 0; i < 8 + ssid_range; i++) + *(driver->apps_rsp_buf + i) = *(buf+i); + ENCODE_RSP_AND_SEND(8 + ssid_range - 1); + return 0; + } +#endif + } +#if defined(CONFIG_DIAG_OVER_USB) + /* Check for Apps Only 8960 & get event mask request */ + else if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID) + && *buf == 0x81) { + driver->apps_rsp_buf[0] = 0x81; + driver->apps_rsp_buf[1] = 0x0; + *(uint16_t *)(driver->apps_rsp_buf + 2) = 0x0; + *(uint16_t *)(driver->apps_rsp_buf + 4) = EVENT_LAST_ID + 1; + for (i = 0; i < EVENT_LAST_ID/8 + 1; i++) + *(unsigned char *)(driver->apps_rsp_buf + 6 + i) = 0x0; + ENCODE_RSP_AND_SEND(6 + EVENT_LAST_ID/8); + return 0; + } + /* Get log ID range & Check for Apps Only 8960 */ + else if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID) + && (*buf == 0x73) && *(int *)(buf+4) == 1) { + driver->apps_rsp_buf[0] = 0x73; + *(int *)(driver->apps_rsp_buf + 4) = 0x1; /* operation ID */ + *(int *)(driver->apps_rsp_buf + 8) = 0x0; /* success code */ + *(int *)(driver->apps_rsp_buf + 12) = LOG_GET_ITEM_NUM(LOG_0); + *(int *)(driver->apps_rsp_buf + 16) = LOG_GET_ITEM_NUM(LOG_1); + *(int *)(driver->apps_rsp_buf + 20) = LOG_GET_ITEM_NUM(LOG_2); + *(int *)(driver->apps_rsp_buf + 24) = LOG_GET_ITEM_NUM(LOG_3); + *(int *)(driver->apps_rsp_buf + 28) = LOG_GET_ITEM_NUM(LOG_4); + *(int *)(driver->apps_rsp_buf + 32) = LOG_GET_ITEM_NUM(LOG_5); + *(int *)(driver->apps_rsp_buf + 36) = LOG_GET_ITEM_NUM(LOG_6); + *(int *)(driver->apps_rsp_buf + 40) = LOG_GET_ITEM_NUM(LOG_7); + *(int *)(driver->apps_rsp_buf + 44) = LOG_GET_ITEM_NUM(LOG_8); + *(int *)(driver->apps_rsp_buf + 48) = LOG_GET_ITEM_NUM(LOG_9); + *(int *)(driver->apps_rsp_buf + 52) = LOG_GET_ITEM_NUM(LOG_10); + *(int *)(driver->apps_rsp_buf + 56) = LOG_GET_ITEM_NUM(LOG_11); + *(int *)(driver->apps_rsp_buf + 60) = LOG_GET_ITEM_NUM(LOG_12); + *(int *)(driver->apps_rsp_buf + 64) = LOG_GET_ITEM_NUM(LOG_13); + *(int *)(driver->apps_rsp_buf + 68) = LOG_GET_ITEM_NUM(LOG_14); + *(int *)(driver->apps_rsp_buf + 72) = LOG_GET_ITEM_NUM(LOG_15); + ENCODE_RSP_AND_SEND(75); + return 0; + } + /* Respond to Get SSID Range request message */ + else if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID) + && (*buf == 0x7d) && (*(buf+1) == 0x1)) { + driver->apps_rsp_buf[0] = 0x7d; + driver->apps_rsp_buf[1] = 0x1; + driver->apps_rsp_buf[2] = 0x1; + driver->apps_rsp_buf[3] = 0x0; + *(int *)(driver->apps_rsp_buf + 4) = MSG_MASK_TBL_CNT; + *(uint16_t *)(driver->apps_rsp_buf + 8) = MSG_SSID_0; + *(uint16_t *)(driver->apps_rsp_buf + 10) = MSG_SSID_0_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 12) = MSG_SSID_1; + *(uint16_t *)(driver->apps_rsp_buf + 14) = MSG_SSID_1_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 16) = MSG_SSID_2; + *(uint16_t *)(driver->apps_rsp_buf + 18) = MSG_SSID_2_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 20) = MSG_SSID_3; + *(uint16_t *)(driver->apps_rsp_buf + 22) = MSG_SSID_3_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 24) = MSG_SSID_4; + *(uint16_t *)(driver->apps_rsp_buf + 26) = MSG_SSID_4_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 28) = MSG_SSID_5; + *(uint16_t *)(driver->apps_rsp_buf + 30) = MSG_SSID_5_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 32) = MSG_SSID_6; + *(uint16_t *)(driver->apps_rsp_buf + 34) = MSG_SSID_6_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 36) = MSG_SSID_7; + *(uint16_t *)(driver->apps_rsp_buf + 38) = MSG_SSID_7_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 40) = MSG_SSID_8; + *(uint16_t *)(driver->apps_rsp_buf + 42) = MSG_SSID_8_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 44) = MSG_SSID_9; + *(uint16_t *)(driver->apps_rsp_buf + 46) = MSG_SSID_9_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 48) = MSG_SSID_10; + *(uint16_t *)(driver->apps_rsp_buf + 50) = MSG_SSID_10_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 52) = MSG_SSID_11; + *(uint16_t *)(driver->apps_rsp_buf + 54) = MSG_SSID_11_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 56) = MSG_SSID_12; + *(uint16_t *)(driver->apps_rsp_buf + 58) = MSG_SSID_12_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 60) = MSG_SSID_13; + *(uint16_t *)(driver->apps_rsp_buf + 62) = MSG_SSID_13_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 64) = MSG_SSID_14; + *(uint16_t *)(driver->apps_rsp_buf + 66) = MSG_SSID_14_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 68) = MSG_SSID_15; + *(uint16_t *)(driver->apps_rsp_buf + 70) = MSG_SSID_15_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 72) = MSG_SSID_16; + *(uint16_t *)(driver->apps_rsp_buf + 74) = MSG_SSID_16_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 76) = MSG_SSID_17; + *(uint16_t *)(driver->apps_rsp_buf + 78) = MSG_SSID_17_LAST; + *(uint16_t *)(driver->apps_rsp_buf + 80) = MSG_SSID_18; + *(uint16_t *)(driver->apps_rsp_buf + 82) = MSG_SSID_18_LAST; + ENCODE_RSP_AND_SEND(83); + return 0; + } + /* Check for AO8960 Respond to Get Subsys Build mask */ + else if (!(driver->ch) && (chk_config_get_id() == AO8960_TOOLS_ID) + && (*buf == 0x7d) && (*(buf+1) == 0x2)) { + ssid_first = *(uint16_t *)(buf + 2); + ssid_last = *(uint16_t *)(buf + 4); + ssid_range = 4 * (ssid_last - ssid_first + 1); + /* frame response */ + driver->apps_rsp_buf[0] = 0x7d; + driver->apps_rsp_buf[1] = 0x2; + *(uint16_t *)(driver->apps_rsp_buf + 2) = ssid_first; + *(uint16_t *)(driver->apps_rsp_buf + 4) = ssid_last; + driver->apps_rsp_buf[6] = 0x1; + driver->apps_rsp_buf[7] = 0x0; + ptr = driver->apps_rsp_buf + 8; + /* bld time masks */ + switch (ssid_first) { + case MSG_SSID_0: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_0[i/4]; + break; + case MSG_SSID_1: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_1[i/4]; + break; + case MSG_SSID_2: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_2[i/4]; + break; + case MSG_SSID_3: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_3[i/4]; + break; + case MSG_SSID_4: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_4[i/4]; + break; + case MSG_SSID_5: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_5[i/4]; + break; + case MSG_SSID_6: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_6[i/4]; + break; + case MSG_SSID_7: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_7[i/4]; + break; + case MSG_SSID_8: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_8[i/4]; + break; + case MSG_SSID_9: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_9[i/4]; + break; + case MSG_SSID_10: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_10[i/4]; + break; + case MSG_SSID_11: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_11[i/4]; + break; + case MSG_SSID_12: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_12[i/4]; + break; + case MSG_SSID_13: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_13[i/4]; + break; + case MSG_SSID_14: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_14[i/4]; + break; + case MSG_SSID_15: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_15[i/4]; + break; + case MSG_SSID_16: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_16[i/4]; + break; + case MSG_SSID_17: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_17[i/4]; + break; + case MSG_SSID_18: + for (i = 0; i < ssid_range; i += 4) + *(int *)(ptr + i) = msg_bld_masks_18[i/4]; + break; + } + ENCODE_RSP_AND_SEND(8 + ssid_range - 1); + return 0; + } + /* Check for download command */ + else if ((cpu_is_msm8x60() || cpu_is_msm8960()) && (*buf == 0x3A)) { + /* send response back */ + driver->apps_rsp_buf[0] = *buf; + ENCODE_RSP_AND_SEND(0); + msleep(5000); + /* call download API */ + msm_set_restart_mode(RESTART_DLOAD); + printk(KERN_CRIT "diag: download mode set, Rebooting SoC..\n"); + kernel_restart(NULL); + /* Not required, represents that command isnt sent to modem */ + return 0; + } + /* Check for ID for NO MODEM present */ + else if (!(driver->ch)) { + /* Respond to polling for Apps only DIAG */ + if ((*buf == 0x4b) && (*(buf+1) == 0x32) && + (*(buf+2) == 0x03)) { + for (i = 0; i < 3; i++) + driver->apps_rsp_buf[i] = *(buf+i); + for (i = 0; i < 13; i++) + driver->apps_rsp_buf[i+3] = 0; + + ENCODE_RSP_AND_SEND(15); + return 0; + } + /* respond to 0x0 command */ + else if (*buf == 0x00) { + for (i = 0; i < 55; i++) + driver->apps_rsp_buf[i] = 0; + + ENCODE_RSP_AND_SEND(54); + return 0; + } + /* respond to 0x7c command */ + else if (*buf == 0x7c) { + driver->apps_rsp_buf[0] = 0x7c; + for (i = 1; i < 8; i++) + driver->apps_rsp_buf[i] = 0; + /* Tools ID for APQ 8060 */ + *(int *)(driver->apps_rsp_buf + 8) = + chk_config_get_id(); + *(unsigned char *)(driver->apps_rsp_buf + 12) = '\0'; + *(unsigned char *)(driver->apps_rsp_buf + 13) = '\0'; + ENCODE_RSP_AND_SEND(13); + return 0; + } + } +#endif + return packet_type; +} + +#ifdef CONFIG_DIAG_OVER_USB +void diag_send_error_rsp(int index) +{ + int i; + driver->apps_rsp_buf[0] = 0x13; /* error code 13 */ + for (i = 0; i < index; i++) + driver->apps_rsp_buf[i+1] = *(driver->hdlc_buf+i); + ENCODE_RSP_AND_SEND(index - 3); +} +#else +static inline void diag_send_error_rsp(int index) {} +#endif + +void diag_process_hdlc(void *data, unsigned len) +{ + struct diag_hdlc_decode_type hdlc; + int ret, type = 0; + pr_debug("diag: HDLC decode fn, len of data %d\n", len); + hdlc.dest_ptr = driver->hdlc_buf; + hdlc.dest_size = USB_MAX_OUT_BUF; + hdlc.src_ptr = data; + hdlc.src_size = len; + hdlc.src_idx = 0; + hdlc.dest_idx = 0; + hdlc.escaping = 0; + + ret = diag_hdlc_decode(&hdlc); + + if (ret) + type = diag_process_apps_pkt(driver->hdlc_buf, + hdlc.dest_idx - 3); + else if (driver->debug_flag) { + printk(KERN_ERR "Packet dropped due to bad HDLC coding/CRC" + " errors or partial packet received, packet" + " length = %d\n", len); + print_hex_dump(KERN_DEBUG, "Dropped Packet Data: ", 16, 1, + DUMP_PREFIX_ADDRESS, data, len, 1); + driver->debug_flag = 0; + } + /* send error responses from APPS for Central Routing */ + if (type == 1 && chk_config_get_id() == AO8960_TOOLS_ID) { + diag_send_error_rsp(hdlc.dest_idx); + type = 0; + } + /* implies this packet is NOT meant for apps */ + if (!(driver->ch) && type == 1) { + if (chk_config_get_id() == AO8960_TOOLS_ID) { + diag_send_error_rsp(hdlc.dest_idx); + } else { /* APQ 8060, Let Q6 respond */ + if (driver->chqdsp) + smd_write(driver->chqdsp, driver->hdlc_buf, + hdlc.dest_idx - 3); + } + type = 0; + } + +#ifdef DIAG_DEBUG + pr_debug("diag: hdlc.dest_idx = %d", hdlc.dest_idx); + for (i = 0; i < hdlc.dest_idx; i++) + printk(KERN_DEBUG "\t%x", *(((unsigned char *) + driver->hdlc_buf)+i)); +#endif /* DIAG DEBUG */ + /* ignore 2 bytes for CRC, one for 7E and send */ + if ((driver->ch) && (ret) && (type) && (hdlc.dest_idx > 3)) { + APPEND_DEBUG('g'); + smd_write(driver->ch, driver->hdlc_buf, hdlc.dest_idx - 3); + APPEND_DEBUG('h'); +#ifdef DIAG_DEBUG + printk(KERN_INFO "writing data to SMD, pkt length %d\n", len); + print_hex_dump(KERN_DEBUG, "Written Packet Data to SMD: ", 16, + 1, DUMP_PREFIX_ADDRESS, data, len, 1); +#endif /* DIAG DEBUG */ + } +} + +#ifdef CONFIG_DIAG_OVER_USB +#define N_LEGACY_WRITE (driver->poolsize + 5) /* 2+1 for modem ; 2 for q6 */ +#define N_LEGACY_READ 1 + +int diagfwd_connect(void) +{ + int err; + + printk(KERN_DEBUG "diag: USB connected\n"); + err = usb_diag_alloc_req(driver->legacy_ch, N_LEGACY_WRITE, + N_LEGACY_READ); + if (err) + printk(KERN_ERR "diag: unable to alloc USB req on legacy ch"); + + driver->usb_connected = 1; + driver->in_busy_1 = 0; + driver->in_busy_2 = 0; + driver->in_busy_qdsp_1 = 0; + driver->in_busy_qdsp_2 = 0; + driver->in_busy_wcnss = 0; + + /* Poll SMD channels to check for data*/ + queue_work(driver->diag_wq, &(driver->diag_read_smd_work)); + queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_work)); + queue_work(driver->diag_wq, &(driver->diag_read_smd_wcnss_work)); + /* Poll USB channel to check for data*/ + queue_work(driver->diag_wq, &(driver->diag_read_work)); +#ifdef CONFIG_DIAG_SDIO_PIPE + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) { + if (driver->mdm_ch && !IS_ERR(driver->mdm_ch)) + diagfwd_connect_sdio(); + else + printk(KERN_INFO "diag: No USB MDM ch"); + } +#endif + return 0; +} + +int diagfwd_disconnect(void) +{ + printk(KERN_DEBUG "diag: USB disconnected\n"); + driver->usb_connected = 0; + driver->in_busy_1 = 1; + driver->in_busy_2 = 1; + driver->in_busy_qdsp_1 = 1; + driver->in_busy_qdsp_2 = 1; + driver->in_busy_wcnss = 1; + driver->debug_flag = 1; + usb_diag_free_req(driver->legacy_ch); +#ifdef CONFIG_DIAG_SDIO_PIPE + if (machine_is_msm8x60_fusion() || machine_is_msm8x60_fusn_ffa()) + if (driver->mdm_ch && !IS_ERR(driver->mdm_ch)) + diagfwd_disconnect_sdio(); +#endif + /* TBD - notify and flow control SMD */ + return 0; +} + +int diagfwd_write_complete(struct diag_request *diag_write_ptr) +{ + unsigned char *buf = diag_write_ptr->buf; + /*Determine if the write complete is for data from modem/apps/q6 */ + /* Need a context variable here instead */ + if (buf == (void *)driver->buf_in_1) { + driver->in_busy_1 = 0; + APPEND_DEBUG('o'); + queue_work(driver->diag_wq, &(driver->diag_read_smd_work)); + } else if (buf == (void *)driver->buf_in_2) { + driver->in_busy_2 = 0; + APPEND_DEBUG('O'); + queue_work(driver->diag_wq, &(driver->diag_read_smd_work)); + } else if (buf == (void *)driver->buf_in_qdsp_1) { + driver->in_busy_qdsp_1 = 0; + APPEND_DEBUG('p'); + queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_work)); + } else if (buf == (void *)driver->buf_in_qdsp_2) { + driver->in_busy_qdsp_2 = 0; + APPEND_DEBUG('P'); + queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_work)); + } else if (buf == (void *)driver->buf_in_wcnss) { + driver->in_busy_wcnss = 0; + APPEND_DEBUG('R'); + queue_work(driver->diag_wq, + &(driver->diag_read_smd_wcnss_work)); + } +#ifdef CONFIG_DIAG_SDIO_PIPE + else if (buf == (void *)driver->buf_in_sdio) + if (machine_is_msm8x60_fusion() || + machine_is_msm8x60_fusn_ffa()) + diagfwd_write_complete_sdio(); + else + pr_err("diag: Incorrect buffer pointer while WRITE"); +#endif + else { + diagmem_free(driver, (unsigned char *)buf, POOL_TYPE_HDLC); + diagmem_free(driver, (unsigned char *)diag_write_ptr, + POOL_TYPE_WRITE_STRUCT); + APPEND_DEBUG('q'); + } + return 0; +} + +int diagfwd_read_complete(struct diag_request *diag_read_ptr) +{ + int status = diag_read_ptr->status; + unsigned char *buf = diag_read_ptr->buf; + + /* Determine if the read complete is for data on legacy/mdm ch */ + if (buf == (void *)driver->usb_buf_out) { + driver->read_len_legacy = diag_read_ptr->actual; + APPEND_DEBUG('s'); +#ifdef DIAG_DEBUG + printk(KERN_INFO "read data from USB, pkt length %d", + diag_read_ptr->actual); + print_hex_dump(KERN_DEBUG, "Read Packet Data from USB: ", 16, 1, + DUMP_PREFIX_ADDRESS, diag_read_ptr->buf, + diag_read_ptr->actual, 1); +#endif /* DIAG DEBUG */ + if (driver->logging_mode == USB_MODE) { + if (status != -ECONNRESET && status != -ESHUTDOWN) + queue_work(driver->diag_wq, + &(driver->diag_proc_hdlc_work)); + else + queue_work(driver->diag_wq, + &(driver->diag_read_work)); + } + } +#ifdef CONFIG_DIAG_SDIO_PIPE + else if (buf == (void *)driver->usb_buf_mdm_out) { + if (machine_is_msm8x60_fusion() || + machine_is_msm8x60_fusn_ffa()) { + driver->read_len_mdm = diag_read_ptr->actual; + diagfwd_read_complete_sdio(); + } else + pr_err("diag: Incorrect buffer pointer while READ"); + } +#endif + else + printk(KERN_ERR "diag: Unknown buffer ptr from USB"); + + return 0; +} + +void diag_read_work_fn(struct work_struct *work) +{ + APPEND_DEBUG('d'); + driver->usb_read_ptr->buf = driver->usb_buf_out; + driver->usb_read_ptr->length = USB_MAX_OUT_BUF; + usb_diag_read(driver->legacy_ch, driver->usb_read_ptr); + APPEND_DEBUG('e'); +} + +void diag_process_hdlc_fn(struct work_struct *work) +{ + APPEND_DEBUG('D'); + diag_process_hdlc(driver->usb_buf_out, driver->read_len_legacy); + diag_read_work_fn(work); + APPEND_DEBUG('E'); +} + +void diag_usb_legacy_notifier(void *priv, unsigned event, + struct diag_request *d_req) +{ + switch (event) { + case USB_DIAG_CONNECT: + diagfwd_connect(); + break; + case USB_DIAG_DISCONNECT: + diagfwd_disconnect(); + break; + case USB_DIAG_READ_DONE: + diagfwd_read_complete(d_req); + break; + case USB_DIAG_WRITE_DONE: + diagfwd_write_complete(d_req); + break; + default: + printk(KERN_ERR "Unknown event from USB diag\n"); + break; + } +} + +#endif /* DIAG OVER USB */ + +static void diag_smd_notify(void *ctxt, unsigned event) +{ + queue_work(driver->diag_wq, &(driver->diag_read_smd_work)); +} + +#if defined(CONFIG_MSM_N_WAY_SMD) +static void diag_smd_qdsp_notify(void *ctxt, unsigned event) +{ + queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_work)); +} +#endif + +static void diag_smd_wcnss_notify(void *ctxt, unsigned event) +{ + queue_work(driver->diag_wq, &(driver->diag_read_smd_wcnss_work)); +} + +static int diag_smd_probe(struct platform_device *pdev) +{ + int r = 0; + + if (pdev->id == SMD_APPS_MODEM) + r = smd_open("DIAG", &driver->ch, driver, diag_smd_notify); +#if defined(CONFIG_MSM_N_WAY_SMD) + if (pdev->id == SMD_APPS_QDSP) + r = smd_named_open_on_edge("DIAG", SMD_APPS_QDSP + , &driver->chqdsp, driver, diag_smd_qdsp_notify); +#endif + if (pdev->id == SMD_APPS_WCNSS) + r = smd_named_open_on_edge("APPS_RIVA_DATA", SMD_APPS_WCNSS + , &driver->ch_wcnss, driver, diag_smd_wcnss_notify); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pr_debug("diag: open SMD port, Id = %d, r = %d\n", pdev->id, r); + + return 0; +} + +static int diagfwd_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int diagfwd_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops diagfwd_dev_pm_ops = { + .runtime_suspend = diagfwd_runtime_suspend, + .runtime_resume = diagfwd_runtime_resume, +}; + +static struct platform_driver msm_smd_ch1_driver = { + + .probe = diag_smd_probe, + .driver = { + .name = "DIAG", + .owner = THIS_MODULE, + .pm = &diagfwd_dev_pm_ops, + }, +}; + +static struct platform_driver diag_smd_lite_driver = { + + .probe = diag_smd_probe, + .driver = { + .name = "APPS_RIVA_DATA", + .owner = THIS_MODULE, + .pm = &diagfwd_dev_pm_ops, + }, +}; + +void diagfwd_init(void) +{ + diag_debug_buf_idx = 0; + driver->read_len_legacy = 0; + if (driver->buf_in_1 == NULL) { + driver->buf_in_1 = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_1 == NULL) + goto err; + } + if (driver->buf_in_2 == NULL) { + driver->buf_in_2 = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_2 == NULL) + goto err; + } + if (driver->buf_in_qdsp_1 == NULL) { + driver->buf_in_qdsp_1 = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_qdsp_1 == NULL) + goto err; + } + if (driver->buf_in_qdsp_2 == NULL) { + driver->buf_in_qdsp_2 = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_qdsp_2 == NULL) + goto err; + } + if (driver->buf_in_wcnss == NULL) { + driver->buf_in_wcnss = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_wcnss == NULL) + goto err; + } + if (driver->usb_buf_out == NULL && + (driver->usb_buf_out = kzalloc(USB_MAX_OUT_BUF, + GFP_KERNEL)) == NULL) + goto err; + if (driver->hdlc_buf == NULL + && (driver->hdlc_buf = kzalloc(HDLC_MAX, GFP_KERNEL)) == NULL) + goto err; + if (driver->msg_masks == NULL + && (driver->msg_masks = kzalloc(MSG_MASK_SIZE, + GFP_KERNEL)) == NULL) + goto err; + if (driver->log_masks == NULL && + (driver->log_masks = kzalloc(LOG_MASK_SIZE, GFP_KERNEL)) == NULL) + goto err; + driver->log_masks_length = 8*MAX_EQUIP_ID; + if (driver->event_masks == NULL && + (driver->event_masks = kzalloc(EVENT_MASK_SIZE, + GFP_KERNEL)) == NULL) + goto err; + if (driver->client_map == NULL && + (driver->client_map = kzalloc + ((driver->num_clients) * sizeof(struct diag_client_map), + GFP_KERNEL)) == NULL) + goto err; + if (driver->buf_tbl == NULL) + driver->buf_tbl = kzalloc(buf_tbl_size * + sizeof(struct diag_write_device), GFP_KERNEL); + if (driver->buf_tbl == NULL) + goto err; + if (driver->data_ready == NULL && + (driver->data_ready = kzalloc(driver->num_clients * sizeof(int) + , GFP_KERNEL)) == NULL) + goto err; + if (driver->table == NULL && + (driver->table = kzalloc(diag_max_registration* + sizeof(struct diag_master_table), + GFP_KERNEL)) == NULL) + goto err; + if (driver->write_ptr_1 == NULL) { + driver->write_ptr_1 = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->write_ptr_1 == NULL) + goto err; + } + if (driver->write_ptr_2 == NULL) { + driver->write_ptr_2 = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->write_ptr_2 == NULL) + goto err; + } + if (driver->write_ptr_qdsp_1 == NULL) { + driver->write_ptr_qdsp_1 = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->write_ptr_qdsp_1 == NULL) + goto err; + } + if (driver->write_ptr_qdsp_2 == NULL) { + driver->write_ptr_qdsp_2 = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->write_ptr_qdsp_2 == NULL) + goto err; + } + if (driver->write_ptr_wcnss == NULL) { + driver->write_ptr_wcnss = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->write_ptr_wcnss == NULL) + goto err; + } + if (driver->usb_read_ptr == NULL) { + driver->usb_read_ptr = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->usb_read_ptr == NULL) + goto err; + } + if (driver->pkt_buf == NULL && + (driver->pkt_buf = kzalloc(PKT_SIZE, + GFP_KERNEL)) == NULL) + goto err; + if (driver->apps_rsp_buf == NULL) { + driver->apps_rsp_buf = kzalloc(500, GFP_KERNEL); + if (driver->apps_rsp_buf == NULL) + goto err; + } + driver->diag_wq = create_singlethread_workqueue("diag_wq"); +#ifdef CONFIG_DIAG_OVER_USB + INIT_WORK(&(driver->diag_proc_hdlc_work), diag_process_hdlc_fn); + INIT_WORK(&(driver->diag_read_work), diag_read_work_fn); + driver->legacy_ch = usb_diag_open(DIAG_LEGACY, driver, + diag_usb_legacy_notifier); + if (IS_ERR(driver->legacy_ch)) { + printk(KERN_ERR "Unable to open USB diag legacy channel\n"); + goto err; + } +#endif + platform_driver_register(&msm_smd_ch1_driver); + platform_driver_register(&diag_smd_lite_driver); + + return; +err: + pr_err("diag: Could not initialize diag buffers"); + kfree(driver->buf_in_1); + kfree(driver->buf_in_2); + kfree(driver->buf_in_qdsp_1); + kfree(driver->buf_in_qdsp_2); + kfree(driver->buf_in_wcnss); + kfree(driver->usb_buf_out); + kfree(driver->hdlc_buf); + kfree(driver->msg_masks); + kfree(driver->log_masks); + kfree(driver->event_masks); + kfree(driver->client_map); + kfree(driver->buf_tbl); + kfree(driver->data_ready); + kfree(driver->table); + kfree(driver->pkt_buf); + kfree(driver->write_ptr_1); + kfree(driver->write_ptr_2); + kfree(driver->write_ptr_qdsp_1); + kfree(driver->write_ptr_qdsp_2); + kfree(driver->write_ptr_wcnss); + kfree(driver->usb_read_ptr); + kfree(driver->apps_rsp_buf); + if (driver->diag_wq) + destroy_workqueue(driver->diag_wq); +} + +void diagfwd_exit(void) +{ + smd_close(driver->ch); + smd_close(driver->chqdsp); + smd_close(driver->ch_wcnss); + driver->ch = 0; /* SMD can make this NULL */ + driver->chqdsp = 0; + driver->ch_wcnss = 0; +#ifdef CONFIG_DIAG_OVER_USB + if (driver->usb_connected) + usb_diag_free_req(driver->legacy_ch); + usb_diag_close(driver->legacy_ch); +#endif + platform_driver_unregister(&msm_smd_ch1_driver); + platform_driver_unregister(&diag_smd_lite_driver); + kfree(driver->buf_in_1); + kfree(driver->buf_in_2); + kfree(driver->buf_in_qdsp_1); + kfree(driver->buf_in_qdsp_2); + kfree(driver->buf_in_wcnss); + kfree(driver->usb_buf_out); + kfree(driver->hdlc_buf); + kfree(driver->msg_masks); + kfree(driver->log_masks); + kfree(driver->event_masks); + kfree(driver->client_map); + kfree(driver->buf_tbl); + kfree(driver->data_ready); + kfree(driver->table); + kfree(driver->pkt_buf); + kfree(driver->write_ptr_1); + kfree(driver->write_ptr_2); + kfree(driver->write_ptr_qdsp_1); + kfree(driver->write_ptr_qdsp_2); + kfree(driver->write_ptr_wcnss); + kfree(driver->usb_read_ptr); + kfree(driver->apps_rsp_buf); + destroy_workqueue(driver->diag_wq); +} diff --git a/drivers/char/diag/diagfwd.h b/drivers/char/diag/diagfwd.h new file mode 100644 index 00000000000..cc24cbc42eb --- /dev/null +++ b/drivers/char/diag/diagfwd.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DIAGFWD_H +#define DIAGFWD_H + +#define NO_PROCESS 0 +#define NON_APPS_PROC -1 + +void diagfwd_init(void); +void diagfwd_exit(void); +void diag_process_hdlc(void *data, unsigned len); +void __diag_smd_send_req(void); +void __diag_smd_qdsp_send_req(void); +void __diag_smd_wcnss_send_req(void); +void diag_usb_legacy_notifier(void *, unsigned, struct diag_request *); +long diagchar_ioctl(struct file *, unsigned int, unsigned long); +int diag_device_write(void *, int, struct diag_request *); +int mask_request_validate(unsigned char mask_buf[]); +int chk_config_get_id(void); +/* State for diag forwarding */ +#ifdef CONFIG_DIAG_OVER_USB +int diagfwd_connect(void); +int diagfwd_disconnect(void); +#endif +extern int diag_debug_buf_idx; +extern unsigned char diag_debug_buf[1024]; + +#endif diff --git a/drivers/char/diag/diagfwd_cntl.c b/drivers/char/diag/diagfwd_cntl.c new file mode 100644 index 00000000000..45226ba1cf1 --- /dev/null +++ b/drivers/char/diag/diagfwd_cntl.c @@ -0,0 +1,226 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include "diagchar.h" +#include "diagfwd.h" +#include "diagfwd_cntl.h" + +#define HDR_SIZ 8 + +static void diag_smd_cntl_send_req(int proc_num) +{ + int data_len = 0, type = -1, count_bytes = 0, j, r; + struct bindpkt_params_per_process *pkt_params = + kzalloc(sizeof(struct bindpkt_params_per_process), GFP_KERNEL); + struct diag_ctrl_msg *msg; + struct cmd_code_range *range; + struct bindpkt_params *temp; + void *buf = NULL; + smd_channel_t *smd_ch = NULL; + + if (proc_num == MODEM_PROC) { + buf = driver->buf_in_cntl; + smd_ch = driver->ch_cntl; + } else if (proc_num == QDSP_PROC) { + buf = driver->buf_in_qdsp_cntl; + smd_ch = driver->chqdsp_cntl; + } else if (proc_num == WCNSS_PROC) { + buf = driver->buf_in_wcnss_cntl; + smd_ch = driver->ch_wcnss_cntl; + } + + if (!smd_ch || !buf) + return; + + r = smd_read_avail(smd_ch); + if (r > IN_BUF_SIZE) { + if (r < MAX_IN_BUF_SIZE) { + pr_err("diag: SMD CNTL sending pkt upto %d bytes", r); + buf = krealloc(buf, r, GFP_KERNEL); + } else { + pr_err("diag: CNTL pkt > %d bytes", MAX_IN_BUF_SIZE); + kfree(pkt_params); + return; + } + } + if (buf && r > 0) { + smd_read(smd_ch, buf, r); + while (count_bytes + HDR_SIZ <= r) { + type = *(uint32_t *)(buf); + data_len = *(uint32_t *)(buf + 4); + count_bytes = count_bytes+HDR_SIZ+data_len; + if (type == DIAG_CTRL_MSG_REG && r >= count_bytes) { + msg = buf+HDR_SIZ; + range = buf+HDR_SIZ+ + sizeof(struct diag_ctrl_msg); + pkt_params->count = msg->count_entries; + temp = kzalloc(pkt_params->count * sizeof(struct + bindpkt_params), GFP_KERNEL); + for (j = 0; j < pkt_params->count; j++) { + temp->cmd_code = msg->cmd_code; + temp->subsys_id = msg->subsysid; + temp->client_id = proc_num; + temp->proc_id = proc_num; + temp->cmd_code_lo = range->cmd_code_lo; + temp->cmd_code_hi = range->cmd_code_hi; + range++; + temp++; + } + temp -= pkt_params->count; + pkt_params->params = temp; + diagchar_ioctl(NULL, DIAG_IOCTL_COMMAND_REG, + (unsigned long)pkt_params); + kfree(temp); + buf = buf + HDR_SIZ + data_len; + } + } + } + kfree(pkt_params); +} + +void diag_read_smd_cntl_work_fn(struct work_struct *work) +{ + diag_smd_cntl_send_req(MODEM_PROC); +} + +void diag_read_smd_qdsp_cntl_work_fn(struct work_struct *work) +{ + diag_smd_cntl_send_req(QDSP_PROC); +} + +void diag_read_smd_wcnss_cntl_work_fn(struct work_struct *work) +{ + diag_smd_cntl_send_req(WCNSS_PROC); +} + +static void diag_smd_cntl_notify(void *ctxt, unsigned event) +{ + queue_work(driver->diag_wq, &(driver->diag_read_smd_cntl_work)); +} + +#if defined(CONFIG_MSM_N_WAY_SMD) +static void diag_smd_qdsp_cntl_notify(void *ctxt, unsigned event) +{ + queue_work(driver->diag_wq, &(driver->diag_read_smd_qdsp_cntl_work)); +} +#endif + +static void diag_smd_wcnss_cntl_notify(void *ctxt, unsigned event) +{ + queue_work(driver->diag_wq, &(driver->diag_read_smd_wcnss_cntl_work)); +} + +static int diag_smd_cntl_probe(struct platform_device *pdev) +{ + int r = 0; + + /* open control ports only on 8960 */ + if (chk_config_get_id() == AO8960_TOOLS_ID) { + if (pdev->id == SMD_APPS_MODEM) + r = smd_open("DIAG_CNTL", &driver->ch_cntl, driver, + diag_smd_cntl_notify); + if (pdev->id == SMD_APPS_QDSP) + r = smd_named_open_on_edge("DIAG_CNTL", SMD_APPS_QDSP + , &driver->chqdsp_cntl, driver, + diag_smd_qdsp_cntl_notify); + if (pdev->id == SMD_APPS_WCNSS) + r = smd_named_open_on_edge("APPS_RIVA_CTRL", + SMD_APPS_WCNSS, &driver->ch_wcnss_cntl, + driver, diag_smd_wcnss_cntl_notify); + pr_debug("diag: open CNTL port, ID = %d,r = %d\n", pdev->id, r); + } + return 0; +} + +static int diagfwd_cntl_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int diagfwd_cntl_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops diagfwd_cntl_dev_pm_ops = { + .runtime_suspend = diagfwd_cntl_runtime_suspend, + .runtime_resume = diagfwd_cntl_runtime_resume, +}; + +static struct platform_driver msm_smd_ch1_cntl_driver = { + + .probe = diag_smd_cntl_probe, + .driver = { + .name = "DIAG_CNTL", + .owner = THIS_MODULE, + .pm = &diagfwd_cntl_dev_pm_ops, + }, +}; + +static struct platform_driver diag_smd_lite_cntl_driver = { + + .probe = diag_smd_cntl_probe, + .driver = { + .name = "APPS_RIVA_CTRL", + .owner = THIS_MODULE, + .pm = &diagfwd_cntl_dev_pm_ops, + }, +}; + +void diagfwd_cntl_init(void) +{ + if (driver->buf_in_cntl == NULL) { + driver->buf_in_cntl = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_cntl == NULL) + goto err; + } + if (driver->buf_in_qdsp_cntl == NULL) { + driver->buf_in_qdsp_cntl = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_qdsp_cntl == NULL) + goto err; + } + if (driver->buf_in_wcnss_cntl == NULL) { + driver->buf_in_wcnss_cntl = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_wcnss_cntl == NULL) + goto err; + } + platform_driver_register(&msm_smd_ch1_cntl_driver); + platform_driver_register(&diag_smd_lite_cntl_driver); + + return; +err: + pr_err("diag: Could not initialize diag buffers"); + kfree(driver->buf_in_cntl); + kfree(driver->buf_in_qdsp_cntl); + kfree(driver->buf_in_wcnss_cntl); +} + +void diagfwd_cntl_exit(void) +{ + smd_close(driver->ch_cntl); + smd_close(driver->chqdsp_cntl); + smd_close(driver->ch_wcnss_cntl); + driver->ch_cntl = 0; + driver->chqdsp_cntl = 0; + driver->ch_wcnss_cntl = 0; + platform_driver_unregister(&msm_smd_ch1_cntl_driver); + platform_driver_unregister(&diag_smd_lite_cntl_driver); + + kfree(driver->buf_in_cntl); + kfree(driver->buf_in_qdsp_cntl); + kfree(driver->buf_in_wcnss_cntl); +} diff --git a/drivers/char/diag/diagfwd_cntl.h b/drivers/char/diag/diagfwd_cntl.h new file mode 100644 index 00000000000..542138df1f6 --- /dev/null +++ b/drivers/char/diag/diagfwd_cntl.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DIAGFWD_CNTL_H +#define DIAGFWD_CNTL_H + +#define DIAG_CTRL_MSG_REG 1 /* Message registration commands */ + +struct cmd_code_range { + uint16_t cmd_code_lo; + uint16_t cmd_code_hi; + uint32_t data; +}; + +struct diag_ctrl_msg { + uint32_t version; + uint16_t cmd_code; + uint16_t subsysid; + uint16_t count_entries; + uint16_t port; +}; + +void diagfwd_cntl_init(void); +void diagfwd_cntl_exit(void); +void diag_read_smd_cntl_work_fn(struct work_struct *); +void diag_read_smd_qdsp_cntl_work_fn(struct work_struct *); +void diag_read_smd_wcnss_cntl_work_fn(struct work_struct *); + +#endif diff --git a/drivers/char/diag/diagfwd_sdio.c b/drivers/char/diag/diagfwd_sdio.c new file mode 100644 index 00000000000..8d4328623e6 --- /dev/null +++ b/drivers/char/diag/diagfwd_sdio.c @@ -0,0 +1,261 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_DIAG_OVER_USB +#include +#endif +#include "diagchar_hdlc.h" +#include "diagmem.h" +#include "diagchar.h" +#include "diagfwd.h" +#include "diagfwd_sdio.h" + +void __diag_sdio_send_req(void) +{ + int r = 0; + void *buf = driver->buf_in_sdio; + + if (driver->sdio_ch && (!driver->in_busy_sdio)) { + r = sdio_read_avail(driver->sdio_ch); + + if (r > IN_BUF_SIZE) { + if (r < MAX_IN_BUF_SIZE) { + pr_err("diag: SDIO sending" + " in packets more than %d bytes", r); + buf = krealloc(buf, r, GFP_KERNEL); + } else { + pr_err("diag: SDIO sending" + " in packets more than %d bytes", MAX_IN_BUF_SIZE); + return; + } + } + if (r > 0) { + if (!buf) + printk(KERN_INFO "Out of diagmem for SDIO\n"); + else { + APPEND_DEBUG('i'); + sdio_read(driver->sdio_ch, buf, r); + APPEND_DEBUG('j'); + driver->write_ptr_mdm->length = r; + driver->in_busy_sdio = 1; + diag_device_write(buf, SDIO_DATA, + driver->write_ptr_mdm); + } + } + } +} + +static void diag_read_sdio_work_fn(struct work_struct *work) +{ + __diag_sdio_send_req(); +} + +int diagfwd_connect_sdio(void) +{ + int err; + + err = usb_diag_alloc_req(driver->mdm_ch, N_MDM_WRITE, + N_MDM_READ); + if (err) + printk(KERN_ERR "diag: unable to alloc USB req on mdm ch"); + + driver->in_busy_sdio = 0; + + /* Poll USB channel to check for data*/ + queue_work(driver->diag_sdio_wq, &(driver->diag_read_mdm_work)); + /* Poll SDIO channel to check for data*/ + queue_work(driver->diag_sdio_wq, &(driver->diag_read_sdio_work)); + return 0; +} + +int diagfwd_disconnect_sdio(void) +{ + driver->in_busy_sdio = 1; + usb_diag_free_req(driver->mdm_ch); + return 0; +} + +int diagfwd_write_complete_sdio(void) +{ + driver->in_busy_sdio = 0; + APPEND_DEBUG('q'); + queue_work(driver->diag_sdio_wq, &(driver->diag_read_sdio_work)); + return 0; +} + +int diagfwd_read_complete_sdio(void) +{ + queue_work(driver->diag_sdio_wq, &(driver->diag_read_mdm_work)); + return 0; +} + +void diag_read_mdm_work_fn(struct work_struct *work) +{ + if (driver->sdio_ch) { + wait_event_interruptible(driver->wait_q, (sdio_write_avail + (driver->sdio_ch) >= driver->read_len_mdm)); + if (driver->sdio_ch && driver->usb_buf_mdm_out && + (driver->read_len_mdm > 0)) + sdio_write(driver->sdio_ch, driver->usb_buf_mdm_out, + driver->read_len_mdm); + APPEND_DEBUG('x'); + driver->usb_read_mdm_ptr->buf = driver->usb_buf_mdm_out; + driver->usb_read_mdm_ptr->length = USB_MAX_OUT_BUF; + usb_diag_read(driver->mdm_ch, driver->usb_read_mdm_ptr); + APPEND_DEBUG('y'); + } +} + +static void diag_sdio_notify(void *ctxt, unsigned event) +{ + if (event == SDIO_EVENT_DATA_READ_AVAIL) + queue_work(driver->diag_sdio_wq, + &(driver->diag_read_sdio_work)); + + if (event == SDIO_EVENT_DATA_WRITE_AVAIL) + wake_up_interruptible(&driver->wait_q); +} + +static int diag_sdio_probe(struct platform_device *pdev) +{ + int err; + + err = sdio_open("SDIO_DIAG", &driver->sdio_ch, driver, + diag_sdio_notify); + if (err) + printk(KERN_INFO "DIAG could not open SDIO channel"); + else { + printk(KERN_INFO "DIAG opened SDIO channel"); + queue_work(driver->diag_sdio_wq, &(driver->diag_read_mdm_work)); + } + + return err; +} + +static int diag_sdio_remove(struct platform_device *pdev) +{ + queue_work(driver->diag_sdio_wq, &(driver->diag_remove_sdio_work)); + return 0; +} + +static void diag_remove_sdio_work_fn(struct work_struct *work) +{ + pr_debug("\n diag: sdio remove called"); + /*Disable SDIO channel to prevent further read/write */ + driver->sdio_ch = NULL; +} + +static int diagfwd_sdio_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int diagfwd_sdio_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops diagfwd_sdio_dev_pm_ops = { + .runtime_suspend = diagfwd_sdio_runtime_suspend, + .runtime_resume = diagfwd_sdio_runtime_resume, +}; + +static struct platform_driver msm_sdio_ch_driver = { + .probe = diag_sdio_probe, + .remove = diag_sdio_remove, + .driver = { + .name = "SDIO_DIAG", + .owner = THIS_MODULE, + .pm = &diagfwd_sdio_dev_pm_ops, + }, +}; + +void diagfwd_sdio_init(void) +{ + int ret; + + driver->read_len_mdm = 0; + if (driver->buf_in_sdio == NULL) + driver->buf_in_sdio = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (driver->buf_in_sdio == NULL) + goto err; + if (driver->usb_buf_mdm_out == NULL) + driver->usb_buf_mdm_out = kzalloc(USB_MAX_OUT_BUF, GFP_KERNEL); + if (driver->usb_buf_mdm_out == NULL) + goto err; + if (driver->write_ptr_mdm == NULL) + driver->write_ptr_mdm = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->write_ptr_mdm == NULL) + goto err; + if (driver->usb_read_mdm_ptr == NULL) + driver->usb_read_mdm_ptr = kzalloc( + sizeof(struct diag_request), GFP_KERNEL); + if (driver->usb_read_mdm_ptr == NULL) + goto err; + driver->diag_sdio_wq = create_singlethread_workqueue("diag_sdio_wq"); +#ifdef CONFIG_DIAG_OVER_USB + driver->mdm_ch = usb_diag_open(DIAG_MDM, driver, + diag_usb_legacy_notifier); + if (IS_ERR(driver->mdm_ch)) { + printk(KERN_ERR "Unable to open USB diag MDM channel\n"); + goto err; + } + INIT_WORK(&(driver->diag_read_mdm_work), diag_read_mdm_work_fn); +#endif + INIT_WORK(&(driver->diag_read_sdio_work), diag_read_sdio_work_fn); + INIT_WORK(&(driver->diag_remove_sdio_work), diag_remove_sdio_work_fn); + ret = platform_driver_register(&msm_sdio_ch_driver); + if (ret) + printk(KERN_INFO "DIAG could not register SDIO device"); + else + printk(KERN_INFO "DIAG registered SDIO device"); + + return; +err: + printk(KERN_INFO "\n Could not initialize diag buf for SDIO"); + kfree(driver->buf_in_sdio); + kfree(driver->usb_buf_mdm_out); + kfree(driver->write_ptr_mdm); + kfree(driver->usb_read_mdm_ptr); + if (driver->diag_sdio_wq) + destroy_workqueue(driver->diag_sdio_wq); +} + +void diagfwd_sdio_exit(void) +{ +#ifdef CONFIG_DIAG_OVER_USB + if (driver->usb_connected) + usb_diag_free_req(driver->mdm_ch); +#endif + platform_driver_unregister(&msm_sdio_ch_driver); +#ifdef CONFIG_DIAG_OVER_USB + usb_diag_close(driver->mdm_ch); +#endif + kfree(driver->buf_in_sdio); + kfree(driver->usb_buf_mdm_out); + kfree(driver->write_ptr_mdm); + kfree(driver->usb_read_mdm_ptr); + destroy_workqueue(driver->diag_sdio_wq); +} diff --git a/drivers/char/diag/diagfwd_sdio.h b/drivers/char/diag/diagfwd_sdio.h new file mode 100644 index 00000000000..40982c33783 --- /dev/null +++ b/drivers/char/diag/diagfwd_sdio.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DIAGFWD_SDIO_H +#define DIAGFWD_SDIO_H + +#include +#define N_MDM_WRITE 1 /* Upgrade to 2 with ping pong buffer */ +#define N_MDM_READ 1 + +void diagfwd_sdio_init(void); +void diagfwd_sdio_exit(void); +int diagfwd_connect_sdio(void); +int diagfwd_disconnect_sdio(void); +int diagfwd_read_complete_sdio(void); +int diagfwd_write_complete_sdio(void); + +#endif diff --git a/drivers/char/diag/diagmem.c b/drivers/char/diag/diagmem.c new file mode 100644 index 00000000000..0b5c27a5c12 --- /dev/null +++ b/drivers/char/diag/diagmem.c @@ -0,0 +1,145 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include "diagchar.h" + +void *diagmem_alloc(struct diagchar_dev *driver, int size, int pool_type) +{ + void *buf = NULL; + + if (pool_type == POOL_TYPE_COPY) { + if (driver->diagpool) { + mutex_lock(&driver->diagmem_mutex); + if (driver->count < driver->poolsize) { + atomic_add(1, (atomic_t *)&driver->count); + buf = mempool_alloc(driver->diagpool, + GFP_ATOMIC); + } + mutex_unlock(&driver->diagmem_mutex); + } + } else if (pool_type == POOL_TYPE_HDLC) { + if (driver->diag_hdlc_pool) { + if (driver->count_hdlc_pool < driver->poolsize_hdlc) { + atomic_add(1, + (atomic_t *)&driver->count_hdlc_pool); + buf = mempool_alloc(driver->diag_hdlc_pool, + GFP_ATOMIC); + } + } + } else if (pool_type == POOL_TYPE_WRITE_STRUCT) { + if (driver->diag_write_struct_pool) { + if (driver->count_write_struct_pool < + driver->poolsize_write_struct) { + atomic_add(1, + (atomic_t *)&driver->count_write_struct_pool); + buf = mempool_alloc( + driver->diag_write_struct_pool, GFP_ATOMIC); + } + } + } + return buf; +} + +void diagmem_exit(struct diagchar_dev *driver, int pool_type) +{ + if (driver->diagpool) { + if (driver->count == 0 && driver->ref_count == 0) { + mempool_destroy(driver->diagpool); + driver->diagpool = NULL; + } else if (driver->ref_count == 0 && pool_type == POOL_TYPE_ALL) + printk(KERN_ALERT "Unable to destroy COPY mempool"); + } + + if (driver->diag_hdlc_pool) { + if (driver->count_hdlc_pool == 0 && driver->ref_count == 0) { + mempool_destroy(driver->diag_hdlc_pool); + driver->diag_hdlc_pool = NULL; + } else if (driver->ref_count == 0 && pool_type == POOL_TYPE_ALL) + printk(KERN_ALERT "Unable to destroy HDLC mempool"); + } + + if (driver->diag_write_struct_pool) { + /* Free up struct pool ONLY if there are no outstanding + transactions(aggregation buffer) with USB */ + if (driver->count_write_struct_pool == 0 && + driver->count_hdlc_pool == 0 && driver->ref_count == 0) { + mempool_destroy(driver->diag_write_struct_pool); + driver->diag_write_struct_pool = NULL; + } else if (driver->ref_count == 0 && pool_type == POOL_TYPE_ALL) + printk(KERN_ALERT "Unable to destroy STRUCT mempool"); + } +} + +void diagmem_free(struct diagchar_dev *driver, void *buf, int pool_type) +{ + if (pool_type == POOL_TYPE_COPY) { + if (driver->diagpool != NULL && driver->count > 0) { + mempool_free(buf, driver->diagpool); + atomic_add(-1, (atomic_t *)&driver->count); + } else + pr_err("diag: Attempt to free up DIAG driver " + "mempool memory which is already free %d", driver->count); + } else if (pool_type == POOL_TYPE_HDLC) { + if (driver->diag_hdlc_pool != NULL && + driver->count_hdlc_pool > 0) { + mempool_free(buf, driver->diag_hdlc_pool); + atomic_add(-1, (atomic_t *)&driver->count_hdlc_pool); + } else + pr_err("diag: Attempt to free up DIAG driver " + "HDLC mempool which is already free %d ", driver->count_hdlc_pool); + } else if (pool_type == POOL_TYPE_WRITE_STRUCT) { + if (driver->diag_write_struct_pool != NULL && + driver->count_write_struct_pool > 0) { + mempool_free(buf, driver->diag_write_struct_pool); + atomic_add(-1, + (atomic_t *)&driver->count_write_struct_pool); + } else + pr_err("diag: Attempt to free up DIAG driver " + "USB structure mempool which is already free %d ", + driver->count_write_struct_pool); + } + + diagmem_exit(driver, pool_type); +} + +void diagmem_init(struct diagchar_dev *driver) +{ + mutex_init(&driver->diagmem_mutex); + + if (driver->count == 0) + driver->diagpool = mempool_create_kmalloc_pool( + driver->poolsize, driver->itemsize); + + if (driver->count_hdlc_pool == 0) + driver->diag_hdlc_pool = mempool_create_kmalloc_pool( + driver->poolsize_hdlc, driver->itemsize_hdlc); + + if (driver->count_write_struct_pool == 0) + driver->diag_write_struct_pool = mempool_create_kmalloc_pool( + driver->poolsize_write_struct, driver->itemsize_write_struct); + + if (!driver->diagpool) + printk(KERN_INFO "Cannot allocate diag mempool\n"); + + if (!driver->diag_hdlc_pool) + printk(KERN_INFO "Cannot allocate diag HDLC mempool\n"); + + if (!driver->diag_write_struct_pool) + printk(KERN_INFO "Cannot allocate diag USB struct mempool\n"); +} + diff --git a/drivers/char/diag/diagmem.h b/drivers/char/diag/diagmem.h new file mode 100644 index 00000000000..43829ae16e8 --- /dev/null +++ b/drivers/char/diag/diagmem.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DIAGMEM_H +#define DIAGMEM_H +#include "diagchar.h" + +void *diagmem_alloc(struct diagchar_dev *driver, int size, int pool_type); +void diagmem_free(struct diagchar_dev *driver, void *buf, int pool_type); +void diagmem_init(struct diagchar_dev *driver); +void diagmem_exit(struct diagchar_dev *driver, int pool_type); + +#endif diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index a60043b3e40..127bdc61065 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -210,3 +210,16 @@ config HW_RANDOM_PICOXCELL module will be called picoxcell-rng. If unsure, say Y. + +config HW_RANDOM_MSM + tristate "Qualcomm MSM Random Number Generator support" + depends on HW_RANDOM && ARCH_MSM + default n + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on Qualcomm MSM SoCs. + + To compile this driver as a module, choose M here: the + module will be called msm_rng. + + If unsure, say Y. diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index 3db4eb8b19c..d0c065dfb0d 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_HW_RANDOM_MXC_RNGA) += mxc-rnga.o obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o obj-$(CONFIG_HW_RANDOM_PICOXCELL) += picoxcell-rng.o +obj-$(CONFIG_HW_RANDOM_MSM) += msm_rng.o diff --git a/drivers/char/hw_random/msm_rng.c b/drivers/char/hw_random/msm_rng.c new file mode 100644 index 00000000000..7051bf96147 --- /dev/null +++ b/drivers/char/hw_random/msm_rng.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "msm_rng" + +/* Device specific register offsets */ +#define PRNG_DATA_OUT_OFFSET 0x0000 +#define PRNG_STATUS_OFFSET 0x0004 +#define PRNG_LFSR_CFG_OFFSET 0x0100 +#define PRNG_CONFIG_OFFSET 0x0104 + +/* Device specific register masks and config values */ +#define PRNG_LFSR_CFG_MASK 0xFFFF0000 +#define PRNG_LFSR_CFG_CLOCKS 0x0000DDDD +#define PRNG_CONFIG_MASK 0xFFFFFFFD +#define PRNG_CONFIG_ENABLE 0x00000002 + +#define MAX_HW_FIFO_DEPTH 16 /* FIFO is 16 words deep */ +#define MAX_HW_FIFO_SIZE (MAX_HW_FIFO_DEPTH * 4) /* FIFO is 32 bits wide */ + +struct msm_rng_device { + struct platform_device *pdev; + void __iomem *base; + struct clk *prng_clk; +}; + +static int msm_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + struct msm_rng_device *msm_rng_dev; + struct platform_device *pdev; + void __iomem *base; + size_t maxsize; + size_t currsize = 0; + unsigned long val; + unsigned long *retdata = data; + int ret; + + msm_rng_dev = (struct msm_rng_device *)rng->priv; + pdev = msm_rng_dev->pdev; + base = msm_rng_dev->base; + + /* calculate max size bytes to transfer back to caller */ + maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, max); + + /* no room for word data */ + if (maxsize < 4) + return 0; + + /* enable PRNG clock */ + ret = clk_enable(msm_rng_dev->prng_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock in callback\n"); + return 0; + } + + /* read random data from h/w */ + do { + /* check status bit if data is available */ + if (!(readl(base + PRNG_STATUS_OFFSET) & 0x00000001)) + break; /* no data to read so just bail */ + + /* read FIFO */ + val = readl(base + PRNG_DATA_OUT_OFFSET); + if (!val) + break; /* no data to read so just bail */ + + /* write data back to callers pointer */ + *(retdata++) = val; + currsize += 4; + + /* make sure we stay on 32bit boundary */ + if ((maxsize - currsize) < 4) + break; + } while (currsize < maxsize); + + /* vote to turn off clock */ + clk_disable(msm_rng_dev->prng_clk); + + return currsize; +} + +static struct hwrng msm_rng = { + .name = DRIVER_NAME, + .read = msm_rng_read, +}; + +static int __devinit msm_rng_probe(struct platform_device *pdev) +{ + struct resource *res; + struct msm_rng_device *msm_rng_dev = NULL; + void __iomem *base = NULL; + int error = 0; + unsigned long val; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "invalid address\n"); + error = -EFAULT; + goto err_exit; + } + + msm_rng_dev = kzalloc(sizeof(msm_rng_dev), GFP_KERNEL); + if (!msm_rng_dev) { + dev_err(&pdev->dev, "cannot allocate memory\n"); + error = -ENOMEM; + goto err_exit; + } + + base = ioremap(res->start, resource_size(res)); + if (!base) { + dev_err(&pdev->dev, "ioremap failed\n"); + error = -ENOMEM; + goto err_iomap; + } + msm_rng_dev->base = base; + + /* create a handle for clock control */ + msm_rng_dev->prng_clk = clk_get(NULL, "prng_clk"); + if (IS_ERR(msm_rng_dev->prng_clk)) { + dev_err(&pdev->dev, "failed to register clock source\n"); + error = -EPERM; + goto err_clk_get; + } + + /* save away pdev and register driver data */ + msm_rng_dev->pdev = pdev; + platform_set_drvdata(pdev, msm_rng_dev); + + ret = clk_enable(msm_rng_dev->prng_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock in probe\n"); + error = -EPERM; + goto err_clk_enable; + } + + /* enable PRNG h/w (this may not work since XPU protect may be enabled + * elsewhere which case then the hardware should have already been set + * up) + */ + val = readl(base + PRNG_LFSR_CFG_OFFSET) & PRNG_LFSR_CFG_MASK; + val |= PRNG_LFSR_CFG_CLOCKS; + writel(val, base + PRNG_LFSR_CFG_OFFSET); + + val = readl(base + PRNG_CONFIG_OFFSET) & PRNG_CONFIG_MASK; + val |= PRNG_CONFIG_ENABLE; + writel(val, base + PRNG_CONFIG_OFFSET); + + clk_disable(msm_rng_dev->prng_clk); + + /* register with hwrng framework */ + msm_rng.priv = (unsigned long) msm_rng_dev; + error = hwrng_register(&msm_rng); + if (error) { + dev_err(&pdev->dev, "failed to register hwrng\n"); + error = -EPERM; + goto err_hw_register; + } + + return 0; + +err_hw_register: +err_clk_enable: + clk_put(msm_rng_dev->prng_clk); +err_clk_get: + iounmap(msm_rng_dev->base); +err_iomap: + kfree(msm_rng_dev); +err_exit: + return error; +} + +static int __devexit msm_rng_remove(struct platform_device *pdev) +{ + struct msm_rng_device *msm_rng_dev = platform_get_drvdata(pdev); + + hwrng_unregister(&msm_rng); + clk_put(msm_rng_dev->prng_clk); + iounmap(msm_rng_dev->base); + platform_set_drvdata(pdev, NULL); + kfree(msm_rng_dev); + return 0; +} + +static struct platform_driver rng_driver = { + .probe = msm_rng_probe, + .remove = __devexit_p(msm_rng_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + } +}; + +static int __init msm_rng_init(void) +{ + return platform_driver_register(&rng_driver); +} + +module_init(msm_rng_init); + +static void __exit msm_rng_exit(void) +{ + platform_driver_unregister(&rng_driver); +} + +module_exit(msm_rng_exit); + +MODULE_AUTHOR("Code Aurora Forum"); +MODULE_DESCRIPTION("Qualcomm MSM Random Number Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/char/msm_rotator.c b/drivers/char/msm_rotator.c new file mode 100644 index 00000000000..e7c790df785 --- /dev/null +++ b/drivers/char/msm_rotator.c @@ -0,0 +1,1523 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "msm_rotator" + +#define MSM_ROTATOR_BASE (msm_rotator_dev->io_base) +#define MSM_ROTATOR_INTR_ENABLE (MSM_ROTATOR_BASE+0x0020) +#define MSM_ROTATOR_INTR_STATUS (MSM_ROTATOR_BASE+0x0024) +#define MSM_ROTATOR_INTR_CLEAR (MSM_ROTATOR_BASE+0x0028) +#define MSM_ROTATOR_START (MSM_ROTATOR_BASE+0x0030) +#define MSM_ROTATOR_MAX_BURST_SIZE (MSM_ROTATOR_BASE+0x0050) +#define MSM_ROTATOR_HW_VERSION (MSM_ROTATOR_BASE+0x0070) +#define MSM_ROTATOR_SRC_SIZE (MSM_ROTATOR_BASE+0x1108) +#define MSM_ROTATOR_SRCP0_ADDR (MSM_ROTATOR_BASE+0x110c) +#define MSM_ROTATOR_SRCP1_ADDR (MSM_ROTATOR_BASE+0x1110) +#define MSM_ROTATOR_SRC_YSTRIDE1 (MSM_ROTATOR_BASE+0x111c) +#define MSM_ROTATOR_SRC_YSTRIDE2 (MSM_ROTATOR_BASE+0x1120) +#define MSM_ROTATOR_SRC_FORMAT (MSM_ROTATOR_BASE+0x1124) +#define MSM_ROTATOR_SRC_UNPACK_PATTERN1 (MSM_ROTATOR_BASE+0x1128) +#define MSM_ROTATOR_SUB_BLOCK_CFG (MSM_ROTATOR_BASE+0x1138) +#define MSM_ROTATOR_OUT_PACK_PATTERN1 (MSM_ROTATOR_BASE+0x1154) +#define MSM_ROTATOR_OUTP0_ADDR (MSM_ROTATOR_BASE+0x1168) +#define MSM_ROTATOR_OUTP1_ADDR (MSM_ROTATOR_BASE+0x116c) +#define MSM_ROTATOR_OUT_YSTRIDE1 (MSM_ROTATOR_BASE+0x1178) +#define MSM_ROTATOR_OUT_YSTRIDE2 (MSM_ROTATOR_BASE+0x117c) +#define MSM_ROTATOR_SRC_XY (MSM_ROTATOR_BASE+0x1200) +#define MSM_ROTATOR_SRC_IMAGE_SIZE (MSM_ROTATOR_BASE+0x1208) + +#define MSM_ROTATOR_MAX_ROT 0x07 +#define MSM_ROTATOR_MAX_H 0x1fff +#define MSM_ROTATOR_MAX_W 0x1fff + +/* from lsb to msb */ +#define GET_PACK_PATTERN(a, x, y, z, bit) \ + (((a)<<((bit)*3))|((x)<<((bit)*2))|((y)<<(bit))|(z)) +#define CLR_G 0x0 +#define CLR_B 0x1 +#define CLR_R 0x2 +#define CLR_ALPHA 0x3 + +#define CLR_Y CLR_G +#define CLR_CB CLR_B +#define CLR_CR CLR_R + +#define ROTATIONS_TO_BITMASK(r) ((((r) & MDP_ROT_90) ? 1 : 0) | \ + (((r) & MDP_FLIP_LR) ? 2 : 0) | \ + (((r) & MDP_FLIP_UD) ? 4 : 0)) + +#define IMEM_NO_OWNER -1; + +#define MAX_SESSIONS 16 +#define INVALID_SESSION -1 +#define VERSION_KEY_MASK 0xFFFFFF00 + +struct tile_parm { + unsigned int width; /* tile's width */ + unsigned int height; /* tile's height */ + unsigned int row_tile_w; /* tiles per row's width */ + unsigned int row_tile_h; /* tiles per row's height */ +}; + +struct msm_rotator_dev { + void __iomem *io_base; + int irq; + struct msm_rotator_img_info *img_info[MAX_SESSIONS]; + struct clk *core_clk; + int pid_list[MAX_SESSIONS]; + struct clk *pclk; + struct clk *axi_clk; + int rot_clk_state; + struct regulator *regulator; + struct delayed_work rot_clk_work; + struct clk *imem_clk; + int imem_clk_state; + struct delayed_work imem_clk_work; + struct platform_device *pdev; + struct cdev cdev; + struct device *device; + struct class *class; + dev_t dev_num; + int processing; + int last_session_idx; + struct mutex rotator_lock; + struct mutex imem_lock; + int imem_owner; + wait_queue_head_t wq; +}; + +#define chroma_addr(start, w, h, bpp) ((start) + ((h) * (w) * (bpp))) + +#define COMPONENT_5BITS 1 +#define COMPONENT_6BITS 2 +#define COMPONENT_8BITS 3 + +static struct msm_rotator_dev *msm_rotator_dev; + +enum { + CLK_EN, + CLK_DIS, + CLK_SUSPEND, +}; + +int msm_rotator_imem_allocate(int requestor) +{ + int rc = 0; + +#ifdef CONFIG_MSM_ROTATOR_USE_IMEM + switch (requestor) { + case ROTATOR_REQUEST: + if (mutex_trylock(&msm_rotator_dev->imem_lock)) { + msm_rotator_dev->imem_owner = ROTATOR_REQUEST; + rc = 1; + } else + rc = 0; + break; + case JPEG_REQUEST: + mutex_lock(&msm_rotator_dev->imem_lock); + msm_rotator_dev->imem_owner = JPEG_REQUEST; + rc = 1; + break; + default: + rc = 0; + } +#else + if (requestor == JPEG_REQUEST) + rc = 1; +#endif + if (rc == 1) { + cancel_delayed_work(&msm_rotator_dev->imem_clk_work); + if (msm_rotator_dev->imem_clk_state != CLK_EN + && msm_rotator_dev->imem_clk) { + clk_enable(msm_rotator_dev->imem_clk); + msm_rotator_dev->imem_clk_state = CLK_EN; + } + } + + return rc; +} +EXPORT_SYMBOL(msm_rotator_imem_allocate); + +void msm_rotator_imem_free(int requestor) +{ +#ifdef CONFIG_MSM_ROTATOR_USE_IMEM + if (msm_rotator_dev->imem_owner == requestor) { + schedule_delayed_work(&msm_rotator_dev->imem_clk_work, HZ); + mutex_unlock(&msm_rotator_dev->imem_lock); + } +#else + if (requestor == JPEG_REQUEST) + schedule_delayed_work(&msm_rotator_dev->imem_clk_work, HZ); +#endif +} +EXPORT_SYMBOL(msm_rotator_imem_free); + +static void msm_rotator_imem_clk_work_f(struct work_struct *work) +{ +#ifdef CONFIG_MSM_ROTATOR_USE_IMEM + if (mutex_trylock(&msm_rotator_dev->imem_lock)) { + if (msm_rotator_dev->imem_clk_state == CLK_EN + && msm_rotator_dev->imem_clk) { + clk_disable(msm_rotator_dev->imem_clk); + msm_rotator_dev->imem_clk_state = CLK_DIS; + } else if (msm_rotator_dev->imem_clk_state == CLK_SUSPEND) + msm_rotator_dev->imem_clk_state = CLK_DIS; + mutex_unlock(&msm_rotator_dev->imem_lock); + } +#endif +} + +/* enable clocks needed by rotator block */ +static void enable_rot_clks(void) +{ + if (msm_rotator_dev->regulator) + regulator_enable(msm_rotator_dev->regulator); + if (msm_rotator_dev->core_clk != NULL) + clk_enable(msm_rotator_dev->core_clk); + if (msm_rotator_dev->pclk != NULL) + clk_enable(msm_rotator_dev->pclk); + if (msm_rotator_dev->axi_clk != NULL) + clk_enable(msm_rotator_dev->axi_clk); +} + +/* disable clocks needed by rotator block */ +static void disable_rot_clks(void) +{ + if (msm_rotator_dev->core_clk != NULL) + clk_disable(msm_rotator_dev->core_clk); + if (msm_rotator_dev->pclk != NULL) + clk_disable(msm_rotator_dev->pclk); + if (msm_rotator_dev->axi_clk != NULL) + clk_disable(msm_rotator_dev->axi_clk); + if (msm_rotator_dev->regulator) + regulator_disable(msm_rotator_dev->regulator); +} + +static void msm_rotator_rot_clk_work_f(struct work_struct *work) +{ + if (mutex_trylock(&msm_rotator_dev->rotator_lock)) { + if (msm_rotator_dev->rot_clk_state == CLK_EN) { + disable_rot_clks(); + msm_rotator_dev->rot_clk_state = CLK_DIS; + } else if (msm_rotator_dev->rot_clk_state == CLK_SUSPEND) + msm_rotator_dev->rot_clk_state = CLK_DIS; + mutex_unlock(&msm_rotator_dev->rotator_lock); + } +} + +static irqreturn_t msm_rotator_isr(int irq, void *dev_id) +{ + if (msm_rotator_dev->processing) { + msm_rotator_dev->processing = 0; + wake_up(&msm_rotator_dev->wq); + } else + printk(KERN_WARNING "%s: unexpected interrupt\n", DRIVER_NAME); + + return IRQ_HANDLED; +} + +static int get_bpp(int format) +{ + switch (format) { + case MDP_RGB_565: + case MDP_BGR_565: + return 2; + + case MDP_XRGB_8888: + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_BGRA_8888: + case MDP_RGBX_8888: + return 4; + + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CRCB_H2V2_TILE: + case MDP_Y_CBCR_H2V2_TILE: + return 1; + + case MDP_RGB_888: + return 3; + + case MDP_YCRYCB_H2V1: + return 2;/* YCrYCb interleave */ + + case MDP_Y_CRCB_H2V1: + case MDP_Y_CBCR_H2V1: + return 1; + + default: + return -1; + } + +} + +static int msm_rotator_ycxcx_h2v1(struct msm_rotator_img_info *info, + unsigned int in_paddr, + unsigned int out_paddr, + unsigned int use_imem, + int new_session, + unsigned int in_chroma_paddr, + unsigned int out_chroma_paddr) +{ + int bpp; + unsigned int in_chr_addr, out_chr_addr; + + if (info->src.format != info->dst.format) + return -EINVAL; + + bpp = get_bpp(info->src.format); + if (bpp < 0) + return -ENOTTY; + + if (!in_chroma_paddr) { + in_chr_addr = chroma_addr(in_paddr, info->src.width, + info->src.height, + bpp); + } else + in_chr_addr = in_chroma_paddr; + + if (!out_chroma_paddr) { + out_chr_addr = chroma_addr(out_paddr, info->dst.width, + info->dst.height, + bpp); + } else + out_chr_addr = out_chroma_paddr; + + iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); + + iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); + iowrite32(in_chr_addr, MSM_ROTATOR_SRCP1_ADDR); + iowrite32(out_paddr + + ((info->dst_y * info->dst.width) + info->dst_x), + MSM_ROTATOR_OUTP0_ADDR); + iowrite32(out_chr_addr + + ((info->dst_y * info->dst.width) + info->dst_x), + MSM_ROTATOR_OUTP1_ADDR); + + if (new_session) { + iowrite32(info->src.width | + info->src.width << 16, + MSM_ROTATOR_SRC_YSTRIDE1); + if (info->rotations & MDP_ROT_90) + iowrite32(info->dst.width | + info->dst.width*2 << 16, + MSM_ROTATOR_OUT_YSTRIDE1); + else + iowrite32(info->dst.width | + info->dst.width << 16, + MSM_ROTATOR_OUT_YSTRIDE1); + if (info->src.format == MDP_Y_CBCR_H2V1) { + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + } else { + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + } + iowrite32((1 << 18) | /* chroma sampling 1=H2V1 */ + (ROTATIONS_TO_BITMASK(info->rotations) << 9) | + 1 << 8, /* ROT_EN */ + MSM_ROTATOR_SUB_BLOCK_CFG); + iowrite32(0 << 29 | /* frame format 0 = linear */ + (use_imem ? 0 : 1) << 22 | /* tile size */ + 2 << 19 | /* fetch planes 2 = pseudo */ + 0 << 18 | /* unpack align */ + 1 << 17 | /* unpack tight */ + 1 << 13 | /* unpack count 0=1 component */ + (bpp-1) << 9 | /* src Bpp 0=1 byte ... */ + 0 << 8 | /* has alpha */ + 0 << 6 | /* alpha bits 3=8bits */ + 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */ + 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */ + 3 << 0, /* G/Y bits 1=5 2=6 3=8 */ + MSM_ROTATOR_SRC_FORMAT); + } + + return 0; +} + +static int msm_rotator_ycxcx_h2v2(struct msm_rotator_img_info *info, + unsigned int in_paddr, + unsigned int out_paddr, + unsigned int use_imem, + int new_session, + unsigned int in_chroma_paddr, + unsigned int out_chroma_paddr) +{ + int bpp; + unsigned int in_chr_addr, out_chr_addr; + + if (info->src.format != info->dst.format) + return -EINVAL; + + bpp = get_bpp(info->src.format); + if (bpp < 0) + return -ENOTTY; + + if (!in_chroma_paddr) { + in_chr_addr = chroma_addr(in_paddr, info->src.width, + info->src.height, + bpp); + } else + in_chr_addr = in_chroma_paddr; + + if (!out_chroma_paddr) { + out_chr_addr = chroma_addr(out_paddr, info->dst.width, + info->dst.height, + bpp); + } else + out_chr_addr = out_chroma_paddr; + + iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); + iowrite32(in_chr_addr, + MSM_ROTATOR_SRCP1_ADDR); + iowrite32(out_paddr + + ((info->dst_y * info->dst.width) + info->dst_x), + MSM_ROTATOR_OUTP0_ADDR); + iowrite32(out_chr_addr + + ((info->dst_y * info->dst.width)/2 + info->dst_x), + MSM_ROTATOR_OUTP1_ADDR); + + if (new_session) { + iowrite32(info->src.width | + info->src.width << 16, + MSM_ROTATOR_SRC_YSTRIDE1); + iowrite32(info->dst.width | + info->dst.width << 16, + MSM_ROTATOR_OUT_YSTRIDE1); + if (info->src.format == MDP_Y_CBCR_H2V2) { + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + } else { + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + } + iowrite32((3 << 18) | /* chroma sampling 3=4:2:0 */ + (ROTATIONS_TO_BITMASK(info->rotations) << 9) | + 1 << 8, /* ROT_EN */ + MSM_ROTATOR_SUB_BLOCK_CFG); + iowrite32(0 << 29 | /* frame format 0 = linear */ + (use_imem ? 0 : 1) << 22 | /* tile size */ + 2 << 19 | /* fetch planes 2 = pseudo */ + 0 << 18 | /* unpack align */ + 1 << 17 | /* unpack tight */ + 1 << 13 | /* unpack count 0=1 component */ + (bpp-1) << 9 | /* src Bpp 0=1 byte ... */ + 0 << 8 | /* has alpha */ + 0 << 6 | /* alpha bits 3=8bits */ + 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */ + 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */ + 3 << 0, /* G/Y bits 1=5 2=6 3=8 */ + MSM_ROTATOR_SRC_FORMAT); + } + return 0; +} + +static unsigned int tile_size(unsigned int src_width, + unsigned int src_height, + const struct tile_parm *tp) +{ + unsigned int tile_w, tile_h; + unsigned int row_num_w, row_num_h; + tile_w = tp->width * tp->row_tile_w; + tile_h = tp->height * tp->row_tile_h; + row_num_w = (src_width + tile_w - 1) / tile_w; + row_num_h = (src_height + tile_h - 1) / tile_h; + return ((row_num_w * row_num_h * tile_w * tile_h) + 8191) & ~8191; +} + +static int msm_rotator_ycxcx_h2v2_tile(struct msm_rotator_img_info *info, + unsigned int in_paddr, + unsigned int out_paddr, + unsigned int use_imem, + int new_session, + unsigned in_chroma_paddr, + unsigned out_chroma_paddr) +{ + int bpp; + unsigned int offset = 0; + unsigned int in_chr_addr, out_chr_addr; + /* + * each row of samsung tile consists of two tiles in height + * and two tiles in width which means width should align to + * 64 x 2 bytes and height should align to 32 x 2 bytes. + * video decoder generate two tiles in width and one tile + * in height which ends up height align to 32 X 1 bytes. + */ + const struct tile_parm tile = {64, 32, 2, 1}; + if ((info->src.format == MDP_Y_CRCB_H2V2_TILE && + info->dst.format != MDP_Y_CRCB_H2V2) || + (info->src.format == MDP_Y_CBCR_H2V2_TILE && + info->dst.format != MDP_Y_CBCR_H2V2)) + return -EINVAL; + + bpp = get_bpp(info->src.format); + if (bpp < 0) + return -ENOTTY; + + offset = tile_size(info->src.width, info->src.height, &tile); + if (!in_chroma_paddr) + in_chr_addr = in_paddr + offset; + else + in_chr_addr = in_chroma_paddr; + + if (!out_chroma_paddr) { + out_chr_addr = chroma_addr(out_paddr, info->dst.width, + info->dst.height, + bpp); + } else + out_chr_addr = out_chroma_paddr; + + iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); + iowrite32(in_paddr + offset, MSM_ROTATOR_SRCP1_ADDR); + iowrite32(out_paddr + + ((info->dst_y * info->dst.width) + info->dst_x), + MSM_ROTATOR_OUTP0_ADDR); + iowrite32(out_chr_addr + + ((info->dst_y * info->dst.width)/2 + info->dst_x), + MSM_ROTATOR_OUTP1_ADDR); + + if (new_session) { + iowrite32(info->src.width | + info->src.width << 16, + MSM_ROTATOR_SRC_YSTRIDE1); + + iowrite32(info->dst.width | + info->dst.width << 16, + MSM_ROTATOR_OUT_YSTRIDE1); + if (info->src.format == MDP_Y_CBCR_H2V2_TILE) { + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + } else { + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + } + iowrite32((3 << 18) | /* chroma sampling 3=4:2:0 */ + (ROTATIONS_TO_BITMASK(info->rotations) << 9) | + 1 << 8, /* ROT_EN */ + MSM_ROTATOR_SUB_BLOCK_CFG); + iowrite32(2 << 29 | /* frame format 2 = supertile */ + (use_imem ? 0 : 1) << 22 | /* tile size */ + 2 << 19 | /* fetch planes 2 = pseudo */ + 0 << 18 | /* unpack align */ + 1 << 17 | /* unpack tight */ + 1 << 13 | /* unpack count 0=1 component */ + (bpp-1) << 9 | /* src Bpp 0=1 byte ... */ + 0 << 8 | /* has alpha */ + 0 << 6 | /* alpha bits 3=8bits */ + 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */ + 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */ + 3 << 0, /* G/Y bits 1=5 2=6 3=8 */ + MSM_ROTATOR_SRC_FORMAT); + } + return 0; +} + +static int msm_rotator_ycrycb(struct msm_rotator_img_info *info, + unsigned int in_paddr, + unsigned int out_paddr, + unsigned int use_imem, + int new_session) +{ + int bpp; + + if (info->src.format != info->dst.format) + return -EINVAL; + + bpp = get_bpp(info->src.format); + if (bpp < 0) + return -ENOTTY; + + iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); + iowrite32(out_paddr + + ((info->dst_y * info->dst.width) + info->dst_x), + MSM_ROTATOR_OUTP0_ADDR); + + if (new_session) { + iowrite32(info->src.width, + MSM_ROTATOR_SRC_YSTRIDE1); + iowrite32(info->dst.width, + MSM_ROTATOR_OUT_YSTRIDE1); + iowrite32(GET_PACK_PATTERN(CLR_Y, CLR_CR, CLR_Y, CLR_CB, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(CLR_Y, CLR_CR, CLR_Y, CLR_CB, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + iowrite32((1 << 18) | /* chroma sampling 1=H2V1 */ + (ROTATIONS_TO_BITMASK(info->rotations) << 9) | + 1 << 8, /* ROT_EN */ + MSM_ROTATOR_SUB_BLOCK_CFG); + iowrite32(0 << 29 | /* frame format 0 = linear */ + (use_imem ? 0 : 1) << 22 | /* tile size */ + 0 << 19 | /* fetch planes 0=interleaved */ + 0 << 18 | /* unpack align */ + 1 << 17 | /* unpack tight */ + 3 << 13 | /* unpack count 0=1 component */ + (bpp-1) << 9 | /* src Bpp 0=1 byte ... */ + 0 << 8 | /* has alpha */ + 0 << 6 | /* alpha bits 3=8bits */ + 3 << 4 | /* R/Cr bits 1=5 2=6 3=8 */ + 3 << 2 | /* B/Cb bits 1=5 2=6 3=8 */ + 3 << 0, /* G/Y bits 1=5 2=6 3=8 */ + MSM_ROTATOR_SRC_FORMAT); + } + + return 0; +} + +static int msm_rotator_rgb_types(struct msm_rotator_img_info *info, + unsigned int in_paddr, + unsigned int out_paddr, + unsigned int use_imem, + int new_session) +{ + int bpp, abits, rbits, gbits, bbits; + + if (info->src.format != info->dst.format) + return -EINVAL; + + bpp = get_bpp(info->src.format); + if (bpp < 0) + return -ENOTTY; + + iowrite32(in_paddr, MSM_ROTATOR_SRCP0_ADDR); + iowrite32(out_paddr + + ((info->dst_y * info->dst.width) + info->dst_x) * bpp, + MSM_ROTATOR_OUTP0_ADDR); + + if (new_session) { + iowrite32(info->src.width * bpp, MSM_ROTATOR_SRC_YSTRIDE1); + iowrite32(info->dst.width * bpp, MSM_ROTATOR_OUT_YSTRIDE1); + iowrite32((0 << 18) | /* chroma sampling 0=rgb */ + (ROTATIONS_TO_BITMASK(info->rotations) << 9) | + 1 << 8, /* ROT_EN */ + MSM_ROTATOR_SUB_BLOCK_CFG); + switch (info->src.format) { + case MDP_RGB_565: + iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + abits = 0; + rbits = COMPONENT_5BITS; + gbits = COMPONENT_6BITS; + bbits = COMPONENT_5BITS; + break; + + case MDP_BGR_565: + iowrite32(GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + abits = 0; + rbits = COMPONENT_5BITS; + gbits = COMPONENT_6BITS; + bbits = COMPONENT_5BITS; + break; + + case MDP_RGB_888: + iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + abits = 0; + rbits = COMPONENT_8BITS; + gbits = COMPONENT_8BITS; + bbits = COMPONENT_8BITS; + break; + + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_XRGB_8888: + case MDP_RGBX_8888: + iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, + CLR_B, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, + CLR_B, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + abits = COMPONENT_8BITS; + rbits = COMPONENT_8BITS; + gbits = COMPONENT_8BITS; + bbits = COMPONENT_8BITS; + break; + + case MDP_BGRA_8888: + iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G, + CLR_R, 8), + MSM_ROTATOR_SRC_UNPACK_PATTERN1); + iowrite32(GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G, + CLR_R, 8), + MSM_ROTATOR_OUT_PACK_PATTERN1); + abits = COMPONENT_8BITS; + rbits = COMPONENT_8BITS; + gbits = COMPONENT_8BITS; + bbits = COMPONENT_8BITS; + break; + + default: + return -EINVAL; + } + iowrite32(0 << 29 | /* frame format 0 = linear */ + (use_imem ? 0 : 1) << 22 | /* tile size */ + 0 << 19 | /* fetch planes 0=interleaved */ + 0 << 18 | /* unpack align */ + 1 << 17 | /* unpack tight */ + (abits ? 3 : 2) << 13 | /* unpack count 0=1 comp */ + (bpp-1) << 9 | /* src Bpp 0=1 byte ... */ + (abits ? 1 : 0) << 8 | /* has alpha */ + abits << 6 | /* alpha bits 3=8bits */ + rbits << 4 | /* R/Cr bits 1=5 2=6 3=8 */ + bbits << 2 | /* B/Cb bits 1=5 2=6 3=8 */ + gbits << 0, /* G/Y bits 1=5 2=6 3=8 */ + MSM_ROTATOR_SRC_FORMAT); + } + + return 0; +} + +static int get_img(int memory_id, unsigned long *start, unsigned long *len, + struct file **pp_file) +{ + int ret = 0; +#ifdef CONFIG_FB + struct file *file; + int put_needed, fb_num; +#endif +#ifdef CONFIG_ANDROID_PMEM + unsigned long vstart; +#endif + +#ifdef CONFIG_ANDROID_PMEM + if (!get_pmem_file(memory_id, start, &vstart, len, pp_file)) + return 0; +#endif +#ifdef CONFIG_FB + file = fget_light(memory_id, &put_needed); + if (file == NULL) + return -1; + + if (MAJOR(file->f_dentry->d_inode->i_rdev) == FB_MAJOR) { + fb_num = MINOR(file->f_dentry->d_inode->i_rdev); + if (get_fb_phys_info(start, len, fb_num)) + ret = -1; + else + *pp_file = file; + } else + ret = -1; + if (ret) + fput_light(file, put_needed); +#endif + return ret; +} + +static int msm_rotator_do_rotate(unsigned long arg) +{ + int rc = 0; + unsigned int status; + struct msm_rotator_data_info info; + unsigned int in_paddr, out_paddr; + unsigned long len; + struct file *src_file = 0; + struct file *dst_file = 0; + int use_imem = 0; + int s; + struct file *src_chroma_file = 0; + struct file *dst_chroma_file = 0; + unsigned int in_chroma_paddr = 0, out_chroma_paddr = 0; + uint32_t format; + + if (copy_from_user(&info, (void __user *)arg, sizeof(info))) + return -EFAULT; + + rc = get_img(info.src.memory_id, (unsigned long *)&in_paddr, + (unsigned long *)&len, &src_file); + if (rc) { + printk(KERN_ERR "%s: in get_img() failed id=0x%08x\n", + DRIVER_NAME, info.src.memory_id); + return rc; + } + in_paddr += info.src.offset; + + rc = get_img(info.dst.memory_id, (unsigned long *)&out_paddr, + (unsigned long *)&len, &dst_file); + if (rc) { + printk(KERN_ERR "%s: out get_img() failed id=0x%08x\n", + DRIVER_NAME, info.dst.memory_id); + goto do_rotate_fail_dst_img; + } + out_paddr += info.dst.offset; + + mutex_lock(&msm_rotator_dev->rotator_lock); + for (s = 0; s < MAX_SESSIONS; s++) + if ((msm_rotator_dev->img_info[s] != NULL) && + (info.session_id == + (unsigned int)msm_rotator_dev->img_info[s] + )) + break; + + if (s == MAX_SESSIONS) { + dev_dbg(msm_rotator_dev->device, + "%s() : Attempt to use invalid session_id %d\n", + __func__, s); + rc = -EINVAL; + goto do_rotate_unlock_mutex; + } + + if (msm_rotator_dev->img_info[s]->enable == 0) { + dev_dbg(msm_rotator_dev->device, + "%s() : Session_id %d not enabled \n", + __func__, s); + rc = -EINVAL; + goto do_rotate_unlock_mutex; + } + + format = msm_rotator_dev->img_info[s]->src.format; + if (((info.version_key & VERSION_KEY_MASK) == 0xA5B4C300) && + ((info.version_key & ~VERSION_KEY_MASK) > 0) && + (format == MDP_Y_CBCR_H2V2 || + format == MDP_Y_CRCB_H2V2 || + format == MDP_Y_CRCB_H2V2_TILE || + format == MDP_Y_CBCR_H2V2_TILE || + format == MDP_Y_CBCR_H2V1 || + format == MDP_Y_CRCB_H2V1)) { + rc = get_img(info.src_chroma.memory_id, + (unsigned long *)&in_chroma_paddr, + (unsigned long *)&len, &src_chroma_file); + if (rc) { + printk(KERN_ERR "%s: in chroma get_img() failed id=0x%08x\n", + DRIVER_NAME, info.src_chroma.memory_id); + goto do_rotate_unlock_mutex; + } + in_chroma_paddr += info.src_chroma.offset; + + rc = get_img(info.dst_chroma.memory_id, + (unsigned long *)&out_chroma_paddr, + (unsigned long *)&len, &dst_chroma_file); + if (rc) { + printk(KERN_ERR "%s: out chroma get_img() failed id=0x%08x\n", + DRIVER_NAME, info.dst_chroma.memory_id); + goto do_rotate_fail_dst_chr_img; + } + out_chroma_paddr += info.dst_chroma.offset; + } + + cancel_delayed_work(&msm_rotator_dev->rot_clk_work); + if (msm_rotator_dev->rot_clk_state != CLK_EN) { + enable_rot_clks(); + msm_rotator_dev->rot_clk_state = CLK_EN; + } + enable_irq(msm_rotator_dev->irq); + +#ifdef CONFIG_MSM_ROTATOR_USE_IMEM + use_imem = msm_rotator_imem_allocate(ROTATOR_REQUEST); +#else + use_imem = 0; +#endif + /* + * workaround for a hardware bug. rotator hardware hangs when we + * use write burst beat size 16 on 128X128 tile fetch mode. As a + * temporary fix use 0x42 for BURST_SIZE when imem used. + */ + if (use_imem) + iowrite32(0x42, MSM_ROTATOR_MAX_BURST_SIZE); + + iowrite32(((msm_rotator_dev->img_info[s]->src_rect.h & 0x1fff) + << 16) | + (msm_rotator_dev->img_info[s]->src_rect.w & 0x1fff), + MSM_ROTATOR_SRC_SIZE); + iowrite32(((msm_rotator_dev->img_info[s]->src_rect.y & 0x1fff) + << 16) | + (msm_rotator_dev->img_info[s]->src_rect.x & 0x1fff), + MSM_ROTATOR_SRC_XY); + iowrite32(((msm_rotator_dev->img_info[s]->src.height & 0x1fff) + << 16) | + (msm_rotator_dev->img_info[s]->src.width & 0x1fff), + MSM_ROTATOR_SRC_IMAGE_SIZE); + + switch (format) { + case MDP_RGB_565: + case MDP_BGR_565: + case MDP_RGB_888: + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_XRGB_8888: + case MDP_BGRA_8888: + case MDP_RGBX_8888: + rc = msm_rotator_rgb_types(msm_rotator_dev->img_info[s], + in_paddr, out_paddr, + use_imem, + msm_rotator_dev->last_session_idx + != s); + break; + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + rc = msm_rotator_ycxcx_h2v2(msm_rotator_dev->img_info[s], + in_paddr, out_paddr, use_imem, + msm_rotator_dev->last_session_idx + != s, + in_chroma_paddr, + out_chroma_paddr); + break; + case MDP_Y_CRCB_H2V2_TILE: + case MDP_Y_CBCR_H2V2_TILE: + rc = msm_rotator_ycxcx_h2v2_tile(msm_rotator_dev->img_info[s], + in_paddr, out_paddr, use_imem, + msm_rotator_dev->last_session_idx + != s, + in_chroma_paddr, + out_chroma_paddr); + break; + + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + rc = msm_rotator_ycxcx_h2v1(msm_rotator_dev->img_info[s], + in_paddr, out_paddr, use_imem, + msm_rotator_dev->last_session_idx + != s, + in_chroma_paddr, + out_chroma_paddr); + break; + case MDP_YCRYCB_H2V1: + rc = msm_rotator_ycrycb(msm_rotator_dev->img_info[s], + in_paddr, out_paddr, use_imem, + msm_rotator_dev->last_session_idx != s); + break; + default: + rc = -EINVAL; + goto do_rotate_exit; + } + + if (rc != 0) { + msm_rotator_dev->last_session_idx = INVALID_SESSION; + goto do_rotate_exit; + } + + iowrite32(3, MSM_ROTATOR_INTR_ENABLE); + + msm_rotator_dev->processing = 1; + iowrite32(0x1, MSM_ROTATOR_START); + + wait_event(msm_rotator_dev->wq, + (msm_rotator_dev->processing == 0)); + status = (unsigned char)ioread32(MSM_ROTATOR_INTR_STATUS); + if ((status & 0x03) != 0x01) + rc = -EFAULT; + iowrite32(0, MSM_ROTATOR_INTR_ENABLE); + iowrite32(3, MSM_ROTATOR_INTR_CLEAR); + +do_rotate_exit: + disable_irq(msm_rotator_dev->irq); +#ifdef CONFIG_MSM_ROTATOR_USE_IMEM + msm_rotator_imem_free(ROTATOR_REQUEST); +#endif + schedule_delayed_work(&msm_rotator_dev->rot_clk_work, HZ); + if (dst_chroma_file) + put_pmem_file(dst_chroma_file); +do_rotate_fail_dst_chr_img: + if (src_chroma_file) + put_pmem_file(src_chroma_file); +do_rotate_unlock_mutex: + mutex_unlock(&msm_rotator_dev->rotator_lock); + if (dst_file) + put_pmem_file(dst_file); +do_rotate_fail_dst_img: + if (src_file) + put_pmem_file(src_file); + dev_dbg(msm_rotator_dev->device, "%s() returning rc = %d\n", + __func__, rc); + return rc; +} + +static int msm_rotator_start(unsigned long arg, int pid) +{ + struct msm_rotator_img_info info; + int rc = 0; + int s; + int first_free_index = INVALID_SESSION; + + if (copy_from_user(&info, (void __user *)arg, sizeof(info))) + return -EFAULT; + + if ((info.rotations > MSM_ROTATOR_MAX_ROT) || + (info.src.height > MSM_ROTATOR_MAX_H) || + (info.src.width > MSM_ROTATOR_MAX_W) || + (info.dst.height > MSM_ROTATOR_MAX_H) || + (info.dst.width > MSM_ROTATOR_MAX_W) || + ((info.src_rect.x + info.src_rect.w) > info.src.width) || + ((info.src_rect.y + info.src_rect.h) > info.src.height) || + ((info.rotations & MDP_ROT_90) && + ((info.dst_x + info.src_rect.h) > info.dst.width)) || + ((info.rotations & MDP_ROT_90) && + ((info.dst_y + info.src_rect.w) > info.dst.height)) || + (!(info.rotations & MDP_ROT_90) && + ((info.dst_x + info.src_rect.w) > info.dst.width)) || + (!(info.rotations & MDP_ROT_90) && + ((info.dst_y + info.src_rect.h) > info.dst.height))) + return -EINVAL; + + switch (info.src.format) { + case MDP_RGB_565: + case MDP_BGR_565: + case MDP_RGB_888: + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_XRGB_8888: + case MDP_RGBX_8888: + case MDP_BGRA_8888: + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + case MDP_YCRYCB_H2V1: + case MDP_Y_CRCB_H2V2_TILE: + case MDP_Y_CBCR_H2V2_TILE: + break; + default: + return -EINVAL; + } + + switch (info.dst.format) { + case MDP_RGB_565: + case MDP_BGR_565: + case MDP_RGB_888: + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_XRGB_8888: + case MDP_RGBX_8888: + case MDP_BGRA_8888: + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + case MDP_YCRYCB_H2V1: + break; + default: + return -EINVAL; + } + + mutex_lock(&msm_rotator_dev->rotator_lock); + for (s = 0; s < MAX_SESSIONS; s++) { + if ((msm_rotator_dev->img_info[s] != NULL) && + (info.session_id == + (unsigned int)msm_rotator_dev->img_info[s] + )) { + *(msm_rotator_dev->img_info[s]) = info; + msm_rotator_dev->pid_list[s] = pid; + + if (msm_rotator_dev->last_session_idx == s) + msm_rotator_dev->last_session_idx = + INVALID_SESSION; + break; + } + + if ((msm_rotator_dev->img_info[s] == NULL) && + (first_free_index == + INVALID_SESSION)) + first_free_index = s; + } + + if ((s == MAX_SESSIONS) && (first_free_index != INVALID_SESSION)) { + /* allocate a session id */ + msm_rotator_dev->img_info[first_free_index] = + kzalloc(sizeof(struct msm_rotator_img_info), + GFP_KERNEL); + if (!msm_rotator_dev->img_info[first_free_index]) { + printk(KERN_ERR "%s : unable to alloc mem\n", + __func__); + rc = -ENOMEM; + goto rotator_start_exit; + } + info.session_id = (unsigned int) + msm_rotator_dev->img_info[first_free_index]; + *(msm_rotator_dev->img_info[first_free_index]) = info; + msm_rotator_dev->pid_list[first_free_index] = pid; + + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + rc = -EFAULT; + } else if (s == MAX_SESSIONS) { + dev_dbg(msm_rotator_dev->device, "%s: all sessions in use\n", + __func__); + rc = -EBUSY; + } + +rotator_start_exit: + mutex_unlock(&msm_rotator_dev->rotator_lock); + + return rc; +} + +static int msm_rotator_finish(unsigned long arg) +{ + int rc = 0; + int s; + unsigned int session_id; + + if (copy_from_user(&session_id, (void __user *)arg, sizeof(s))) + return -EFAULT; + + mutex_lock(&msm_rotator_dev->rotator_lock); + for (s = 0; s < MAX_SESSIONS; s++) { + if ((msm_rotator_dev->img_info[s] != NULL) && + (session_id == + (unsigned int)msm_rotator_dev->img_info[s])) { + if (msm_rotator_dev->last_session_idx == s) + msm_rotator_dev->last_session_idx = + INVALID_SESSION; + kfree(msm_rotator_dev->img_info[s]); + msm_rotator_dev->img_info[s] = NULL; + msm_rotator_dev->pid_list[s] = 0; + break; + } + } + + if (s == MAX_SESSIONS) + rc = -EINVAL; + mutex_unlock(&msm_rotator_dev->rotator_lock); + return rc; +} + +static int +msm_rotator_open(struct inode *inode, struct file *filp) +{ + int *id; + int i; + + if (filp->private_data) + return -EBUSY; + + mutex_lock(&msm_rotator_dev->rotator_lock); + id = &msm_rotator_dev->pid_list[0]; + for (i = 0; i < MAX_SESSIONS; i++, id++) { + if (*id == 0) + break; + } + mutex_unlock(&msm_rotator_dev->rotator_lock); + + if (i == MAX_SESSIONS) + return -EBUSY; + + filp->private_data = (void *)task_tgid_nr(current); + + return 0; +} + +static int +msm_rotator_close(struct inode *inode, struct file *filp) +{ + int s; + int pid; + + pid = (int)filp->private_data; + mutex_lock(&msm_rotator_dev->rotator_lock); + for (s = 0; s < MAX_SESSIONS; s++) { + if (msm_rotator_dev->img_info[s] != NULL && + msm_rotator_dev->pid_list[s] == pid) { + kfree(msm_rotator_dev->img_info[s]); + msm_rotator_dev->img_info[s] = NULL; + if (msm_rotator_dev->last_session_idx == s) + msm_rotator_dev->last_session_idx = + INVALID_SESSION; + } + } + mutex_unlock(&msm_rotator_dev->rotator_lock); + + return 0; +} + +static long msm_rotator_ioctl(struct file *file, unsigned cmd, + unsigned long arg) +{ + int pid; + + if (_IOC_TYPE(cmd) != MSM_ROTATOR_IOCTL_MAGIC) + return -ENOTTY; + + pid = (int)file->private_data; + + switch (cmd) { + case MSM_ROTATOR_IOCTL_START: + return msm_rotator_start(arg, pid); + case MSM_ROTATOR_IOCTL_ROTATE: + return msm_rotator_do_rotate(arg); + case MSM_ROTATOR_IOCTL_FINISH: + return msm_rotator_finish(arg); + + default: + dev_dbg(msm_rotator_dev->device, + "unexpected IOCTL %d\n", cmd); + return -ENOTTY; + } +} + +static const struct file_operations msm_rotator_fops = { + .owner = THIS_MODULE, + .open = msm_rotator_open, + .release = msm_rotator_close, + .unlocked_ioctl = msm_rotator_ioctl, +}; + +static int __devinit msm_rotator_probe(struct platform_device *pdev) +{ + int rc = 0; + struct resource *res; + struct msm_rotator_platform_data *pdata = NULL; + int i, number_of_clks; + uint32_t ver; + + msm_rotator_dev = kzalloc(sizeof(struct msm_rotator_dev), GFP_KERNEL); + if (!msm_rotator_dev) { + printk(KERN_ERR "%s Unable to allocate memory for struct\n", + __func__); + return -ENOMEM; + } + for (i = 0; i < MAX_SESSIONS; i++) + msm_rotator_dev->img_info[i] = NULL; + msm_rotator_dev->last_session_idx = INVALID_SESSION; + + pdata = pdev->dev.platform_data; + number_of_clks = pdata->number_of_clocks; + + msm_rotator_dev->imem_owner = IMEM_NO_OWNER; + mutex_init(&msm_rotator_dev->imem_lock); + msm_rotator_dev->imem_clk_state = CLK_DIS; + INIT_DELAYED_WORK(&msm_rotator_dev->imem_clk_work, + msm_rotator_imem_clk_work_f); + msm_rotator_dev->imem_clk = NULL; + msm_rotator_dev->pdev = pdev; + + msm_rotator_dev->core_clk = NULL; + msm_rotator_dev->pclk = NULL; + msm_rotator_dev->axi_clk = NULL; + + for (i = 0; i < number_of_clks; i++) { + if (pdata->rotator_clks[i].clk_type == ROTATOR_IMEM_CLK) { + msm_rotator_dev->imem_clk = + clk_get(&msm_rotator_dev->pdev->dev, + pdata->rotator_clks[i].clk_name); + if (IS_ERR(msm_rotator_dev->imem_clk)) { + rc = PTR_ERR(msm_rotator_dev->imem_clk); + msm_rotator_dev->imem_clk = NULL; + printk(KERN_ERR "%s: cannot get imem_clk " + "rc=%d\n", DRIVER_NAME, rc); + goto error_imem_clk; + } + if (pdata->rotator_clks[i].clk_rate) + clk_set_min_rate(msm_rotator_dev->imem_clk, + pdata->rotator_clks[i].clk_rate); + } + if (pdata->rotator_clks[i].clk_type == ROTATOR_PCLK) { + msm_rotator_dev->pclk = + clk_get(&msm_rotator_dev->pdev->dev, + pdata->rotator_clks[i].clk_name); + if (IS_ERR(msm_rotator_dev->pclk)) { + rc = PTR_ERR(msm_rotator_dev->pclk); + msm_rotator_dev->pclk = NULL; + printk(KERN_ERR "%s: cannot get pclk rc=%d\n", + DRIVER_NAME, rc); + goto error_pclk; + } + + if (pdata->rotator_clks[i].clk_rate) + clk_set_min_rate(msm_rotator_dev->pclk, + pdata->rotator_clks[i].clk_rate); + } + + if (pdata->rotator_clks[i].clk_type == ROTATOR_CORE_CLK) { + msm_rotator_dev->core_clk = + clk_get(&msm_rotator_dev->pdev->dev, + pdata->rotator_clks[i].clk_name); + if (IS_ERR(msm_rotator_dev->core_clk)) { + rc = PTR_ERR(msm_rotator_dev->core_clk); + msm_rotator_dev->core_clk = NULL; + printk(KERN_ERR "%s: cannot get core clk " + "rc=%d\n", DRIVER_NAME, rc); + goto error_core_clk; + } + + if (pdata->rotator_clks[i].clk_rate) + clk_set_min_rate(msm_rotator_dev->core_clk, + pdata->rotator_clks[i].clk_rate); + } + + if (pdata->rotator_clks[i].clk_type == ROTATOR_AXI_CLK) { + msm_rotator_dev->axi_clk = + clk_get(&msm_rotator_dev->pdev->dev, + pdata->rotator_clks[i].clk_name); + if (IS_ERR(msm_rotator_dev->axi_clk)) { + rc = PTR_ERR(msm_rotator_dev->axi_clk); + msm_rotator_dev->axi_clk = NULL; + printk(KERN_ERR "%s: cannot get axi clk " + "rc=%d\n", DRIVER_NAME, rc); + goto error_axi_clk; + } + + if (pdata->rotator_clks[i].clk_rate) + clk_set_min_rate(msm_rotator_dev->axi_clk, + pdata->rotator_clks[i].clk_rate); + } + } + + msm_rotator_dev->regulator = regulator_get(NULL, pdata->regulator_name); + if (IS_ERR(msm_rotator_dev->regulator)) + msm_rotator_dev->regulator = NULL; + + msm_rotator_dev->rot_clk_state = CLK_DIS; + INIT_DELAYED_WORK(&msm_rotator_dev->rot_clk_work, + msm_rotator_rot_clk_work_f); + + mutex_init(&msm_rotator_dev->rotator_lock); + + platform_set_drvdata(pdev, msm_rotator_dev); + + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_ALERT + "%s: could not get IORESOURCE_MEM\n", DRIVER_NAME); + rc = -ENODEV; + goto error_get_resource; + } + msm_rotator_dev->io_base = ioremap(res->start, + resource_size(res)); + +#ifdef CONFIG_MSM_ROTATOR_USE_IMEM + if (msm_rotator_dev->imem_clk) + clk_enable(msm_rotator_dev->imem_clk); +#endif + enable_rot_clks(); + ver = ioread32(MSM_ROTATOR_HW_VERSION); + disable_rot_clks(); + +#ifdef CONFIG_MSM_ROTATOR_USE_IMEM + if (msm_rotator_dev->imem_clk) + clk_disable(msm_rotator_dev->imem_clk); +#endif + if (ver != pdata->hardware_version_number) { + printk(KERN_ALERT "%s: invalid HW version\n", DRIVER_NAME); + rc = -ENODEV; + goto error_get_resource; + } + msm_rotator_dev->irq = platform_get_irq(pdev, 0); + if (msm_rotator_dev->irq < 0) { + printk(KERN_ALERT "%s: could not get IORESOURCE_IRQ\n", + DRIVER_NAME); + rc = -ENODEV; + goto error_get_irq; + } + rc = request_irq(msm_rotator_dev->irq, msm_rotator_isr, + IRQF_TRIGGER_RISING, DRIVER_NAME, NULL); + if (rc) { + printk(KERN_ERR "%s: request_irq() failed\n", DRIVER_NAME); + goto error_get_irq; + } + /* we enable the IRQ when we need it in the ioctl */ + disable_irq(msm_rotator_dev->irq); + + rc = alloc_chrdev_region(&msm_rotator_dev->dev_num, 0, 1, DRIVER_NAME); + if (rc < 0) { + printk(KERN_ERR "%s: alloc_chrdev_region Failed rc = %d\n", + __func__, rc); + goto error_get_irq; + } + + msm_rotator_dev->class = class_create(THIS_MODULE, DRIVER_NAME); + if (IS_ERR(msm_rotator_dev->class)) { + rc = PTR_ERR(msm_rotator_dev->class); + printk(KERN_ERR "%s: couldn't create class rc = %d\n", + DRIVER_NAME, rc); + goto error_class_create; + } + + msm_rotator_dev->device = device_create(msm_rotator_dev->class, NULL, + msm_rotator_dev->dev_num, NULL, + DRIVER_NAME); + if (IS_ERR(msm_rotator_dev->device)) { + rc = PTR_ERR(msm_rotator_dev->device); + printk(KERN_ERR "%s: device_create failed %d\n", + DRIVER_NAME, rc); + goto error_class_device_create; + } + + cdev_init(&msm_rotator_dev->cdev, &msm_rotator_fops); + rc = cdev_add(&msm_rotator_dev->cdev, + MKDEV(MAJOR(msm_rotator_dev->dev_num), 0), + 1); + if (rc < 0) { + printk(KERN_ERR "%s: cdev_add failed %d\n", __func__, rc); + goto error_cdev_add; + } + + init_waitqueue_head(&msm_rotator_dev->wq); + + dev_dbg(msm_rotator_dev->device, "probe successful\n"); + return rc; + +error_cdev_add: + device_destroy(msm_rotator_dev->class, msm_rotator_dev->dev_num); +error_class_device_create: + class_destroy(msm_rotator_dev->class); +error_class_create: + unregister_chrdev_region(msm_rotator_dev->dev_num, 1); +error_get_irq: + iounmap(msm_rotator_dev->io_base); +error_get_resource: + mutex_destroy(&msm_rotator_dev->rotator_lock); + if (msm_rotator_dev->regulator) + regulator_put(msm_rotator_dev->regulator); + clk_put(msm_rotator_dev->axi_clk); +error_axi_clk: + clk_put(msm_rotator_dev->core_clk); +error_core_clk: + clk_put(msm_rotator_dev->pclk); +error_pclk: + if (msm_rotator_dev->imem_clk) + clk_put(msm_rotator_dev->imem_clk); +error_imem_clk: + mutex_destroy(&msm_rotator_dev->imem_lock); + kfree(msm_rotator_dev); + return rc; +} + +static int __devexit msm_rotator_remove(struct platform_device *plat_dev) +{ + int i; + + free_irq(msm_rotator_dev->irq, NULL); + mutex_destroy(&msm_rotator_dev->rotator_lock); + cdev_del(&msm_rotator_dev->cdev); + device_destroy(msm_rotator_dev->class, msm_rotator_dev->dev_num); + class_destroy(msm_rotator_dev->class); + unregister_chrdev_region(msm_rotator_dev->dev_num, 1); + iounmap(msm_rotator_dev->io_base); + if (msm_rotator_dev->imem_clk) { + if (msm_rotator_dev->imem_clk_state == CLK_EN) + clk_disable(msm_rotator_dev->imem_clk); + clk_put(msm_rotator_dev->imem_clk); + msm_rotator_dev->imem_clk = NULL; + } + if (msm_rotator_dev->rot_clk_state == CLK_EN) + disable_rot_clks(); + clk_put(msm_rotator_dev->core_clk); + clk_put(msm_rotator_dev->pclk); + clk_put(msm_rotator_dev->axi_clk); + if (msm_rotator_dev->regulator) + regulator_put(msm_rotator_dev->regulator); + msm_rotator_dev->core_clk = NULL; + msm_rotator_dev->pclk = NULL; + msm_rotator_dev->axi_clk = NULL; + mutex_destroy(&msm_rotator_dev->imem_lock); + for (i = 0; i < MAX_SESSIONS; i++) + if (msm_rotator_dev->img_info[i] != NULL) + kfree(msm_rotator_dev->img_info[i]); + kfree(msm_rotator_dev); + return 0; +} + +#ifdef CONFIG_PM +static int msm_rotator_suspend(struct platform_device *dev, pm_message_t state) +{ + mutex_lock(&msm_rotator_dev->imem_lock); + if (msm_rotator_dev->imem_clk_state == CLK_EN + && msm_rotator_dev->imem_clk) { + clk_disable(msm_rotator_dev->imem_clk); + msm_rotator_dev->imem_clk_state = CLK_SUSPEND; + } + mutex_unlock(&msm_rotator_dev->imem_lock); + mutex_lock(&msm_rotator_dev->rotator_lock); + if (msm_rotator_dev->rot_clk_state == CLK_EN) { + disable_rot_clks(); + msm_rotator_dev->rot_clk_state = CLK_SUSPEND; + } + mutex_unlock(&msm_rotator_dev->rotator_lock); + return 0; +} + +static int msm_rotator_resume(struct platform_device *dev) +{ + mutex_lock(&msm_rotator_dev->imem_lock); + if (msm_rotator_dev->imem_clk_state == CLK_SUSPEND + && msm_rotator_dev->imem_clk) { + clk_enable(msm_rotator_dev->imem_clk); + msm_rotator_dev->imem_clk_state = CLK_EN; + } + mutex_unlock(&msm_rotator_dev->imem_lock); + mutex_lock(&msm_rotator_dev->rotator_lock); + if (msm_rotator_dev->rot_clk_state == CLK_SUSPEND) { + enable_rot_clks(); + msm_rotator_dev->rot_clk_state = CLK_EN; + } + mutex_unlock(&msm_rotator_dev->rotator_lock); + return 0; +} +#endif + +static struct platform_driver msm_rotator_platform_driver = { + .probe = msm_rotator_probe, + .remove = __devexit_p(msm_rotator_remove), +#ifdef CONFIG_PM + .suspend = msm_rotator_suspend, + .resume = msm_rotator_resume, +#endif + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME + } +}; + +static int __init msm_rotator_init(void) +{ + return platform_driver_register(&msm_rotator_platform_driver); +} + +static void __exit msm_rotator_exit(void) +{ + return platform_driver_unregister(&msm_rotator_platform_driver); +} + +module_init(msm_rotator_init); +module_exit(msm_rotator_exit); + +MODULE_DESCRIPTION("MSM Offline Image Rotator driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/char/msm_smd_pkt.c b/drivers/char/msm_smd_pkt.c index b6f8a65c996..a8e28d39ca9 100644 --- a/drivers/char/msm_smd_pkt.c +++ b/drivers/char/msm_smd_pkt.c @@ -9,11 +9,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * */ /* * SMD Packet Driver -- Provides userspace interface to SMD packet ports. diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig index f6595aba4f0..c720e9a0234 100644 --- a/drivers/char/tpm/Kconfig +++ b/drivers/char/tpm/Kconfig @@ -60,4 +60,18 @@ config TCG_INFINEON Further information on this driver and the supported hardware can be found at http://www.trust.rub.de/projects/linux-device-driver-infineon-tpm/ +config TCG_ST_I2C + tristate "ST Micro ST19NP18-TPM-I2C TPM interface" + depends on I2C + default n + ---help--- + If you have a ST19NP18-TPM-I2C TPM security chip from ST Micro + say Yes and it will be accessible from Linux. + +config TCG_TPMD_DEV + tristate "tpmd_dev TPM Emulator driver" + default n + ---help--- + Enables the TPM emulator driver + endif # TCG_TPM diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile index ea3a1e02a82..c113cf1a603 100644 --- a/drivers/char/tpm/Makefile +++ b/drivers/char/tpm/Makefile @@ -6,6 +6,8 @@ ifdef CONFIG_ACPI obj-$(CONFIG_TCG_TPM) += tpm_bios.o endif obj-$(CONFIG_TCG_TIS) += tpm_tis.o +obj-$(CONFIG_TCG_ST_I2C) += tpm_st_i2c.o obj-$(CONFIG_TCG_NSC) += tpm_nsc.o obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o +obj-$(CONFIG_TCG_TPMD_DEV) += tpmd_dev/ diff --git a/drivers/char/tpm/tpm_st_i2c.c b/drivers/char/tpm/tpm_st_i2c.c new file mode 100644 index 00000000000..3a6e8c4f8b3 --- /dev/null +++ b/drivers/char/tpm/tpm_st_i2c.c @@ -0,0 +1,361 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include "tpm.h" + +#define DEVICE_NAME "tpm_st_i2c" + +#define TPM_HEADER_LEN sizeof(struct tpm_input_header) +#define TPM_ST_I2C_BLOCK_MAX 40 + +struct tpm_st_i2c_dev { + struct i2c_client *client; + struct tpm_st_i2c_platform_data *pd; + struct completion com[2]; +}; + +/* for completion array */ +#define ACCEPT_CMD_INDEX 0 +#define DATA_AVAIL_INDEX 1 + +static struct tpm_st_i2c_dev *tpm_st_i2c_dev; + +#define TPM_ST_I2C_REQ_COMPLETE_MASK 1 + +static u8 tpm_st_i2c_status(struct tpm_chip *chip) +{ + int gpio = tpm_st_i2c_dev->pd->data_avail_gpio; + return gpio_get_value(gpio); +} + +static void tpm_st_i2c_cancel(struct tpm_chip *chip) +{ + /* not supported */ + return; +} + +static int tpm_st_i2c_transfer_buf(struct tpm_chip *chip, u8 *buf, size_t count, + int recv) +{ + struct i2c_msg msg = { + .addr = tpm_st_i2c_dev->client->addr, + .flags = 0, + .buf = buf, + .len = TPM_HEADER_LEN, /* must read/write header first */ + }; + int gpio; + int irq; + struct completion *com; + __be32 *native_size; + int read_header = 0; + int rc = 0; + int len = count; + uint32_t size = count; + int tmp; + + if (recv) { + msg.flags |= I2C_M_RD; + read_header = 1; + gpio = tpm_st_i2c_dev->pd->data_avail_gpio; + irq = tpm_st_i2c_dev->pd->data_avail_irq; + com = &tpm_st_i2c_dev->com[DATA_AVAIL_INDEX]; + } else { + gpio = tpm_st_i2c_dev->pd->accept_cmd_gpio; + irq = tpm_st_i2c_dev->pd->accept_cmd_irq; + com = &tpm_st_i2c_dev->com[ACCEPT_CMD_INDEX]; + } + + if (len < TPM_HEADER_LEN) { + dev_dbg(chip->dev, "%s: invalid len\n", __func__); + return -EINVAL; + } + + do { + if (!gpio_get_value(gpio)) { + /* reset the completion in case the irq fired + * during the probe + */ + init_completion(com); + enable_irq(irq); + tmp = wait_for_completion_interruptible_timeout( + com, HZ/2); + if (!tmp) { + dev_dbg(chip->dev, "%s timeout\n", + __func__); + return -EBUSY; + } + } + rc = i2c_transfer(tpm_st_i2c_dev->client->adapter, + &msg, 1); + if (rc < 0) { + dev_dbg(chip->dev, "Error in I2C transfer\n"); + return rc; + } + if (read_header) { + read_header = 0; + native_size = (__force __be32 *) (buf + 2); + size = be32_to_cpu(*native_size); + if (count < size) { + dev_dbg(chip->dev, + "%s: invalid count\n", + __func__); + rc = -EIO; + } + len = size; + } + len -= msg.len; + if (len) { + buf += msg.len; + msg.buf = buf; + if (len > TPM_ST_I2C_BLOCK_MAX) + msg.len = TPM_ST_I2C_BLOCK_MAX; + else + msg.len = len; + } + } while (len > 0); + + if (rc >= 0) + return size; + else + return rc; +} + +static int tpm_st_i2c_recv(struct tpm_chip *chip, u8 *buf, size_t count) +{ + return tpm_st_i2c_transfer_buf(chip, buf, count, 1); +} + +static int tpm_st_i2c_send(struct tpm_chip *chip, u8 *buf, size_t len) +{ + return tpm_st_i2c_transfer_buf(chip, buf, len, 0); +} + +#ifdef CONFIG_PM +static int tpm_st_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return tpm_pm_suspend(&client->dev, msg); +} + +static int tpm_st_i2c_resume(struct i2c_client *client) +{ + return tpm_pm_resume(&client->dev); +} +#endif + +static const struct file_operations tpm_st_i2c_fs_ops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = tpm_open, + .read = tpm_read, + .write = tpm_write, + .release = tpm_release, +}; + +static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL); +static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL); +static DEVICE_ATTR(enabled, S_IRUGO, tpm_show_enabled, NULL); +static DEVICE_ATTR(active, S_IRUGO, tpm_show_active, NULL); +static DEVICE_ATTR(owned, S_IRUGO, tpm_show_owned, NULL); +static DEVICE_ATTR(temp_deactivated, S_IRUGO, tpm_show_temp_deactivated, + NULL); +static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps_1_2, NULL); + +static struct attribute *tpm_st_i2c_attrs[] = { + &dev_attr_pubek.attr, + &dev_attr_pcrs.attr, + &dev_attr_enabled.attr, + &dev_attr_active.attr, + &dev_attr_owned.attr, + &dev_attr_temp_deactivated.attr, + &dev_attr_caps.attr, + NULL, +}; + +static struct attribute_group tpm_st_i2c_attr_grp = { + .attrs = tpm_st_i2c_attrs +}; + +static struct tpm_vendor_specific tpm_st_i2c_vendor = { + .status = tpm_st_i2c_status, + .recv = tpm_st_i2c_recv, + .send = tpm_st_i2c_send, + .cancel = tpm_st_i2c_cancel, + .req_complete_mask = TPM_ST_I2C_REQ_COMPLETE_MASK, + .req_complete_val = TPM_ST_I2C_REQ_COMPLETE_MASK, + .req_canceled = 0xff, /* not supported */ + .attr_group = &tpm_st_i2c_attr_grp, + .miscdev = { + .fops = &tpm_st_i2c_fs_ops,}, +}; + +static irqreturn_t tpm_st_i2c_isr(int irq, void *dev_id) +{ + disable_irq_nosync(irq); + if (irq == tpm_st_i2c_dev->pd->accept_cmd_irq) + complete(&tpm_st_i2c_dev->com[ACCEPT_CMD_INDEX]); + else + complete(&tpm_st_i2c_dev->com[DATA_AVAIL_INDEX]); + return IRQ_HANDLED; +} + +static int tpm_st_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + struct tpm_st_i2c_platform_data *pd; + struct tpm_chip *chip; + int high; + + dev_dbg(&client->dev, "%s()\n", __func__); + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_I2C_BLOCK | + I2C_FUNC_I2C)) { + dev_err(&client->dev, "incompatible adapter\n"); + return -ENODEV; + } + + pd = client->dev.platform_data; + if (!pd || !pd->gpio_setup || !pd->gpio_release) { + dev_err(&client->dev, "platform data not setup\n"); + rc = -EFAULT; + goto no_platform_data; + } + rc = pd->gpio_setup(); + if (rc) { + dev_err(&client->dev, "gpio_setup failed\n"); + goto gpio_setup_fail; + } + + gpio_direction_input(pd->accept_cmd_gpio); + gpio_direction_input(pd->data_avail_gpio); + + tpm_st_i2c_dev = kzalloc(sizeof(struct tpm_st_i2c_dev), GFP_KERNEL); + if (!tpm_st_i2c_dev) { + printk(KERN_ERR "%s Unable to allocate memory for struct\n", + __func__); + rc = -ENOMEM; + goto kzalloc_fail; + } + + tpm_st_i2c_dev->client = client; + tpm_st_i2c_dev->pd = pd; + + init_completion(&tpm_st_i2c_dev->com[ACCEPT_CMD_INDEX]); + init_completion(&tpm_st_i2c_dev->com[DATA_AVAIL_INDEX]); + /* This logic allows us to setup irq but not have it enabled, in + * case the lines are already active + */ + high = gpio_get_value(pd->data_avail_gpio); + rc = request_irq(pd->data_avail_irq, tpm_st_i2c_isr, IRQF_TRIGGER_HIGH, + DEVICE_NAME "-data", NULL); + if (rc) { + dev_err(&client->dev, "request for data irq failed\n"); + goto data_irq_fail; + } + if (!high) + disable_irq(pd->data_avail_irq); + high = gpio_get_value(pd->accept_cmd_gpio); + rc = request_irq(pd->accept_cmd_irq, tpm_st_i2c_isr, IRQF_TRIGGER_HIGH, + DEVICE_NAME "-cmd", NULL); + if (rc) { + dev_err(&client->dev, "request for cmd irq failed\n"); + goto cmd_irq_fail; + } + if (!high) + disable_irq(pd->accept_cmd_irq); + + tpm_st_i2c_vendor.irq = pd->data_avail_irq; + + chip = tpm_register_hardware(&client->dev, &tpm_st_i2c_vendor); + if (!chip) { + dev_err(&client->dev, "Could not register tpm hardware\n"); + rc = -ENODEV; + goto tpm_reg_fail; + } + + dev_info(&client->dev, "added\n"); + + return 0; + +tpm_reg_fail: + free_irq(pd->accept_cmd_irq, NULL); +cmd_irq_fail: + free_irq(pd->data_avail_irq, NULL); +data_irq_fail: +kzalloc_fail: + pd->gpio_release(); +gpio_setup_fail: +no_platform_data: + + return rc; +} + +static int __exit tpm_st_i2c_remove(struct i2c_client *client) +{ + free_irq(tpm_st_i2c_dev->pd->accept_cmd_irq, NULL); + free_irq(tpm_st_i2c_dev->pd->data_avail_irq, NULL); + tpm_remove_hardware(&client->dev); + tpm_st_i2c_dev->pd->gpio_release(); + kfree(tpm_st_i2c_dev); + + return 0; +} + +static const struct i2c_device_id tpm_st_i2c_id[] = { + { DEVICE_NAME, 0 }, + { } +}; + +static struct i2c_driver tpm_st_i2c_driver = { + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + }, + .probe = tpm_st_i2c_probe, + .remove = __exit_p(tpm_st_i2c_remove), +#ifdef CONFIG_PM + .suspend = tpm_st_i2c_suspend, + .resume = tpm_st_i2c_resume, +#endif + .id_table = tpm_st_i2c_id, +}; + +static int __init tpm_st_i2c_init(void) +{ + int ret; + + ret = i2c_add_driver(&tpm_st_i2c_driver); + if (ret) + printk(KERN_ERR "%s: failed to add i2c driver\n", __func__); + + return ret; +} + +static void __exit tpm_st_i2c_exit(void) +{ + i2c_del_driver(&tpm_st_i2c_driver); +} + +module_init(tpm_st_i2c_init); +module_exit(tpm_st_i2c_exit); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); +MODULE_DESCRIPTION("ST19NP18-TPM-I2C driver"); diff --git a/drivers/char/tpm/tpmd_dev/Makefile b/drivers/char/tpm/tpmd_dev/Makefile new file mode 100644 index 00000000000..7d62de463de --- /dev/null +++ b/drivers/char/tpm/tpmd_dev/Makefile @@ -0,0 +1,4 @@ +# +# Makefile for the kernel tpm emulator device driver. +# +obj-$(CONFIG_TCG_TPM) += tpmd_dev.o diff --git a/drivers/char/tpm/tpmd_dev/config.h b/drivers/char/tpm/tpmd_dev/config.h new file mode 100644 index 00000000000..ec8d93e535d --- /dev/null +++ b/drivers/char/tpm/tpmd_dev/config.h @@ -0,0 +1,32 @@ +/* Software-based Trusted Platform Module (TPM) Emulator + * Copyright (C) 2004-2010 Mario Strasser + * + * This module 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 module is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * $Id: config.h.in 426 2010-02-22 17:11:58Z mast $ + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* project and build version */ +#define VERSION_MAJOR 0 +#define VERSION_MINOR 7 +#define VERSION_BUILD 424 + +/* TDDL and LKM configuration */ +#define TPM_SOCKET_NAME "/var/run/tpm/tpmd_socket:0" +#define TPM_STORAGE_NAME "/var/lib/tpm/tpm_emulator-1_2_0_7" +#define TPM_DEVICE_NAME "/dev/tpm" +#define TPM_LOG_FILE "" +#define TPM_CMD_BUF_SIZE 4096 + +#endif /* _CONFIG_H_ */ diff --git a/drivers/char/tpm/tpmd_dev/tpmd_dev.c b/drivers/char/tpm/tpmd_dev/tpmd_dev.c new file mode 100644 index 00000000000..cbfcbd869d9 --- /dev/null +++ b/drivers/char/tpm/tpmd_dev/tpmd_dev.c @@ -0,0 +1,272 @@ +/* Software-based Trusted Platform Module (TPM) Emulator + * Copyright (C) 2004-2010 Mario Strasser + * + * This module 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 module is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * $Id: tpmd_dev.c 426 2010-02-22 17:11:58Z mast $ + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "config.h" + +#define TPM_DEVICE_MINOR 224 +#define TPM_DEVICE_ID "tpm" +#define TPM_MODULE_NAME "tpmd_dev" + +#define TPM_STATE_IS_OPEN 0 + +#ifdef DEBUG +#define debug(fmt, ...) printk(KERN_DEBUG "%s %s:%d: Debug: " fmt "\n", \ + TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__) +#else +#define debug(fmt, ...) +#endif +#define info(fmt, ...) printk(KERN_INFO "%s %s:%d: Info: " fmt "\n", \ + TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__) +#define error(fmt, ...) printk(KERN_ERR "%s %s:%d: Error: " fmt "\n", \ + TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__) +#define alert(fmt, ...) printk(KERN_ALERT "%s %s:%d: Alert: " fmt "\n", \ + TPM_MODULE_NAME, __FILE__, __LINE__, ## __VA_ARGS__) + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mario Strasser "); +MODULE_DESCRIPTION("Trusted Platform Module (TPM) Emulator"); +MODULE_SUPPORTED_DEVICE(TPM_DEVICE_ID); + +/* module parameters */ +char *tpmd_socket_name = TPM_SOCKET_NAME; +module_param(tpmd_socket_name, charp, 0444); +MODULE_PARM_DESC(tpmd_socket_name, " Sets the name of the TPM daemon socket."); + +/* TPM lock */ +static struct semaphore tpm_mutex; + +/* TPM command response */ +static struct { + uint8_t *data; + uint32_t size; +} tpm_response; + +/* module state */ +static uint32_t module_state; +static struct socket *tpmd_sock; +static struct sockaddr_un addr; + +static int tpmd_connect(char *socket_name) +{ + int res; + res = sock_create(PF_UNIX, SOCK_STREAM, 0, &tpmd_sock); + if (res != 0) { + error("sock_create() failed: %d\n", res); + tpmd_sock = NULL; + return res; + } + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path)); + res = tpmd_sock->ops->connect(tpmd_sock, + (struct sockaddr*)&addr, sizeof(struct sockaddr_un), 0); + if (res != 0) { + error("sock_connect() failed: %d\n", res); + tpmd_sock->ops->release(tpmd_sock); + tpmd_sock = NULL; + return res; + } + return 0; +} + +static void tpmd_disconnect(void) +{ + if (tpmd_sock != NULL) tpmd_sock->ops->release(tpmd_sock); + tpmd_sock = NULL; +} + +static int tpmd_handle_command(const uint8_t *in, uint32_t in_size) +{ + int res; + mm_segment_t oldmm; + struct msghdr msg; + struct iovec iov; + /* send command to tpmd */ + memset(&msg, 0, sizeof(msg)); + iov.iov_base = (void*)in; + iov.iov_len = in_size; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + res = sock_sendmsg(tpmd_sock, &msg, in_size); + if (res < 0) { + error("sock_sendmsg() failed: %d\n", res); + return res; + } + /* receive response from tpmd */ + tpm_response.size = TPM_CMD_BUF_SIZE; + tpm_response.data = kmalloc(tpm_response.size, GFP_KERNEL); + if (tpm_response.data == NULL) return -1; + memset(&msg, 0, sizeof(msg)); + iov.iov_base = (void*)tpm_response.data; + iov.iov_len = tpm_response.size; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + oldmm = get_fs(); + set_fs(KERNEL_DS); + res = sock_recvmsg(tpmd_sock, &msg, tpm_response.size, 0); + set_fs(oldmm); + if (res < 0) { + error("sock_recvmsg() failed: %d\n", res); + tpm_response.data = NULL; + return res; + } + tpm_response.size = res; + return 0; +} + +static int tpm_open(struct inode *inode, struct file *file) +{ + int res; + debug("%s()", __FUNCTION__); + if (test_and_set_bit(TPM_STATE_IS_OPEN, (void*)&module_state)) return -EBUSY; + down(&tpm_mutex); + res = tpmd_connect(tpmd_socket_name); + up(&tpm_mutex); + if (res != 0) { + clear_bit(TPM_STATE_IS_OPEN, (void*)&module_state); + return -EIO; + } + return 0; +} + +static int tpm_release(struct inode *inode, struct file *file) +{ + debug("%s()", __FUNCTION__); + down(&tpm_mutex); + if (tpm_response.data != NULL) { + kfree(tpm_response.data); + tpm_response.data = NULL; + } + tpmd_disconnect(); + up(&tpm_mutex); + clear_bit(TPM_STATE_IS_OPEN, (void*)&module_state); + return 0; +} + +static ssize_t tpm_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + debug("%s(%zd)", __FUNCTION__, count); + down(&tpm_mutex); + if (tpm_response.data != NULL) { + count = min(count, (size_t)tpm_response.size - (size_t)*ppos); + count -= copy_to_user(buf, &tpm_response.data[*ppos], count); + *ppos += count; + if ((size_t)tpm_response.size == (size_t)*ppos) { + kfree(tpm_response.data); + tpm_response.data = NULL; + } + } else { + count = 0; + } + up(&tpm_mutex); + return count; +} + +static ssize_t tpm_write(struct file *file, const char *buf, size_t count, loff_t *ppos) +{ + debug("%s(%zd)", __FUNCTION__, count); + down(&tpm_mutex); + *ppos = 0; + if (tpm_response.data != NULL) { + kfree(tpm_response.data); + tpm_response.data = NULL; + } + if (tpmd_handle_command(buf, count) != 0) { + count = -EILSEQ; + tpm_response.data = NULL; + } + up(&tpm_mutex); + return count; +} + +#define TPMIOC_CANCEL _IO('T', 0x00) +#define TPMIOC_TRANSMIT _IO('T', 0x01) + +static int tpm_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + debug("%s(%d, %p)", __FUNCTION__, cmd, (char*)arg); + if (cmd == TPMIOC_TRANSMIT) { + uint32_t count = ntohl(*(uint32_t*)(arg + 2)); + down(&tpm_mutex); + if (tpm_response.data != NULL) { + kfree(tpm_response.data); + tpm_response.data = NULL; + } + if (tpmd_handle_command((char*)arg, count) == 0) { + tpm_response.size -= copy_to_user((char*)arg, tpm_response.data, tpm_response.size); + kfree(tpm_response.data); + tpm_response.data = NULL; + } else { + tpm_response.size = 0; + tpm_response.data = NULL; + } + up(&tpm_mutex); + return tpm_response.size; + } + return -1; +} + +struct file_operations fops = { + .owner = THIS_MODULE, + .open = tpm_open, + .release = tpm_release, + .read = tpm_read, + .write = tpm_write, + .ioctl = tpm_ioctl, +}; + +static struct miscdevice tpm_dev = { + .minor = TPM_DEVICE_MINOR, + .name = TPM_DEVICE_ID, + .fops = &fops, +}; + +int __init init_tpm_module(void) +{ + int res = misc_register(&tpm_dev); + if (res != 0) { + error("misc_register() failed for minor %d\n", TPM_DEVICE_MINOR); + return res; + } + /* initialize variables */ + sema_init(&tpm_mutex, 1); + module_state = 0; + tpm_response.data = NULL; + tpm_response.size = 0; + tpmd_sock = NULL; + return 0; +} + +void __exit cleanup_tpm_module(void) +{ + misc_deregister(&tpm_dev); + tpmd_disconnect(); + if (tpm_response.data != NULL) kfree(tpm_response.data); +} + +module_init(init_tpm_module); +module_exit(cleanup_tpm_module); + diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c new file mode 100644 index 00000000000..94bb4403ef9 --- /dev/null +++ b/drivers/char/tty_io.c @@ -0,0 +1,3154 @@ +/* + * linux/drivers/char/tty_io.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +/* + * 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles + * or rs-channels. It also implements echoing, cooked mode etc. + * + * Kill-line thanks to John T Kohl, who also corrected VMIN = VTIME = 0. + * + * Modified by Theodore Ts'o, 9/14/92, to dynamically allocate the + * tty_struct and tty_queue structures. Previously there was an array + * of 256 tty_struct's which was statically allocated, and the + * tty_queue structures were allocated at boot time. Both are now + * dynamically allocated only when the tty is open. + * + * Also restructured routines so that there is more of a separation + * between the high-level tty routines (tty_io.c and tty_ioctl.c) and + * the low-level tty routines (serial.c, pty.c, console.c). This + * makes for cleaner and more compact code. -TYT, 9/17/92 + * + * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines + * which can be dynamically activated and de-activated by the line + * discipline handling modules (like SLIP). + * + * NOTE: pay no attention to the line discipline code (yet); its + * interface is still subject to change in this version... + * -- TYT, 1/31/92 + * + * Added functionality to the OPOST tty handling. No delays, but all + * other bits should be there. + * -- Nick Holloway , 27th May 1993. + * + * Rewrote canonical mode and added more termios flags. + * -- julian@uhunix.uhcc.hawaii.edu (J. Cowley), 13Jan94 + * + * Reorganized FASYNC support so mouse code can share it. + * -- ctm@ardi.com, 9Sep95 + * + * New TIOCLINUX variants added. + * -- mj@k332.feld.cvut.cz, 19-Nov-95 + * + * Restrict vt switching via ioctl() + * -- grif@cs.ucr.edu, 5-Dec-95 + * + * Move console and virtual terminal code to more appropriate files, + * implement CONFIG_VT and generalize console device interface. + * -- Marko Kohtala , March 97 + * + * Rewrote tty_init_dev and tty_release_dev to eliminate races. + * -- Bill Hawes , June 97 + * + * Added devfs support. + * -- C. Scott Ananian , 13-Jan-1998 + * + * Added support for a Unix98-style ptmx device. + * -- C. Scott Ananian , 14-Jan-1998 + * + * Reduced memory usage for older ARM systems + * -- Russell King + * + * Move do_SAK() into process context. Less stack use in devfs functions. + * alloc_tty_struct() always uses kmalloc() + * -- Andrew Morton 17Mar01 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#undef TTY_DEBUG_HANGUP + +#define TTY_PARANOIA_CHECK 1 +#define CHECK_TTY_COUNT 1 + +struct ktermios tty_std_termios = { /* for the benefit of tty drivers */ + .c_iflag = ICRNL | IXON, + .c_oflag = OPOST | ONLCR, + .c_cflag = B38400 | CS8 | CREAD | HUPCL, + .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | + ECHOCTL | ECHOKE | IEXTEN, + .c_cc = INIT_C_CC, + .c_ispeed = 38400, + .c_ospeed = 38400 +}; + +EXPORT_SYMBOL(tty_std_termios); + +/* This list gets poked at by procfs and various bits of boot up code. This + could do with some rationalisation such as pulling the tty proc function + into this file */ + +LIST_HEAD(tty_drivers); /* linked list of tty drivers */ + +/* Mutex to protect creating and releasing a tty. This is shared with + vt.c for deeply disgusting hack reasons */ +DEFINE_MUTEX(tty_mutex); +EXPORT_SYMBOL(tty_mutex); + +static ssize_t tty_read(struct file *, char __user *, size_t, loff_t *); +static ssize_t tty_write(struct file *, const char __user *, size_t, loff_t *); +ssize_t redirected_tty_write(struct file *, const char __user *, + size_t, loff_t *); +static unsigned int tty_poll(struct file *, poll_table *); +static int tty_open(struct inode *, struct file *); +long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +#ifdef CONFIG_COMPAT +static long tty_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); +#else +#define tty_compat_ioctl NULL +#endif +static int tty_fasync(int fd, struct file *filp, int on); +static void release_tty(struct tty_struct *tty, int idx); +static void __proc_set_tty(struct task_struct *tsk, struct tty_struct *tty); +static void proc_set_tty(struct task_struct *tsk, struct tty_struct *tty); + +/** + * alloc_tty_struct - allocate a tty object + * + * Return a new empty tty structure. The data fields have not + * been initialized in any way but has been zeroed + * + * Locking: none + */ + +struct tty_struct *alloc_tty_struct(void) +{ + return kzalloc(sizeof(struct tty_struct), GFP_KERNEL); +} + +/** + * free_tty_struct - free a disused tty + * @tty: tty struct to free + * + * Free the write buffers, tty queue and tty memory itself. + * + * Locking: none. Must be called after tty is definitely unused + */ + +void free_tty_struct(struct tty_struct *tty) +{ + kfree(tty->write_buf); + tty_buffer_free_all(tty); + kfree(tty); +} + +#define TTY_NUMBER(tty) ((tty)->index + (tty)->driver->name_base) + +/** + * tty_name - return tty naming + * @tty: tty structure + * @buf: buffer for output + * + * Convert a tty structure into a name. The name reflects the kernel + * naming policy and if udev is in use may not reflect user space + * + * Locking: none + */ + +char *tty_name(struct tty_struct *tty, char *buf) +{ + if (!tty) /* Hmm. NULL pointer. That's fun. */ + strcpy(buf, "NULL tty"); + else + strcpy(buf, tty->name); + return buf; +} + +EXPORT_SYMBOL(tty_name); + +int tty_paranoia_check(struct tty_struct *tty, struct inode *inode, + const char *routine) +{ +#ifdef TTY_PARANOIA_CHECK + if (!tty) { + printk(KERN_WARNING + "null TTY for (%d:%d) in %s\n", + imajor(inode), iminor(inode), routine); + return 1; + } + if (tty->magic != TTY_MAGIC) { + printk(KERN_WARNING + "bad magic number for tty struct (%d:%d) in %s\n", + imajor(inode), iminor(inode), routine); + return 1; + } +#endif + return 0; +} + +static int check_tty_count(struct tty_struct *tty, const char *routine) +{ +#ifdef CHECK_TTY_COUNT + struct list_head *p; + int count = 0; + + file_list_lock(); + list_for_each(p, &tty->tty_files) { + count++; + } + file_list_unlock(); + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_SLAVE && + tty->link && tty->link->count) + count++; + if (tty->count != count) { + printk(KERN_WARNING "Warning: dev (%s) tty->count(%d) " + "!= #fd's(%d) in %s\n", + tty->name, tty->count, count, routine); + return count; + } +#endif + return 0; +} + +/** + * get_tty_driver - find device of a tty + * @dev_t: device identifier + * @index: returns the index of the tty + * + * This routine returns a tty driver structure, given a device number + * and also passes back the index number. + * + * Locking: caller must hold tty_mutex + */ + +static struct tty_driver *get_tty_driver(dev_t device, int *index) +{ + struct tty_driver *p; + + list_for_each_entry(p, &tty_drivers, tty_drivers) { + dev_t base = MKDEV(p->major, p->minor_start); + if (device < base || device >= base + p->num) + continue; + *index = device - base; + return tty_driver_kref_get(p); + } + return NULL; +} + +#ifdef CONFIG_CONSOLE_POLL + +/** + * tty_find_polling_driver - find device of a polled tty + * @name: name string to match + * @line: pointer to resulting tty line nr + * + * This routine returns a tty driver structure, given a name + * and the condition that the tty driver is capable of polled + * operation. + */ +struct tty_driver *tty_find_polling_driver(char *name, int *line) +{ + struct tty_driver *p, *res = NULL; + int tty_line = 0; + int len; + char *str, *stp; + + for (str = name; *str; str++) + if ((*str >= '0' && *str <= '9') || *str == ',') + break; + if (!*str) + return NULL; + + len = str - name; + tty_line = simple_strtoul(str, &str, 10); + + mutex_lock(&tty_mutex); + /* Search through the tty devices to look for a match */ + list_for_each_entry(p, &tty_drivers, tty_drivers) { + if (strncmp(name, p->name, len) != 0) + continue; + stp = str; + if (*stp == ',') + stp++; + if (*stp == '\0') + stp = NULL; + + if (tty_line >= 0 && tty_line <= p->num && p->ops && + p->ops->poll_init && !p->ops->poll_init(p, tty_line, stp)) { + res = tty_driver_kref_get(p); + *line = tty_line; + break; + } + } + mutex_unlock(&tty_mutex); + + return res; +} +EXPORT_SYMBOL_GPL(tty_find_polling_driver); +#endif + +/** + * tty_check_change - check for POSIX terminal changes + * @tty: tty to check + * + * If we try to write to, or set the state of, a terminal and we're + * not in the foreground, send a SIGTTOU. If the signal is blocked or + * ignored, go ahead and perform the operation. (POSIX 7.2) + * + * Locking: ctrl_lock + */ + +int tty_check_change(struct tty_struct *tty) +{ + unsigned long flags; + int ret = 0; + + if (current->signal->tty != tty) + return 0; + + spin_lock_irqsave(&tty->ctrl_lock, flags); + + if (!tty->pgrp) { + printk(KERN_WARNING "tty_check_change: tty->pgrp == NULL!\n"); + goto out_unlock; + } + if (task_pgrp(current) == tty->pgrp) + goto out_unlock; + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + if (is_ignored(SIGTTOU)) + goto out; + if (is_current_pgrp_orphaned()) { + ret = -EIO; + goto out; + } + kill_pgrp(task_pgrp(current), SIGTTOU, 1); + set_thread_flag(TIF_SIGPENDING); + ret = -ERESTARTSYS; +out: + return ret; +out_unlock: + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + return ret; +} + +EXPORT_SYMBOL(tty_check_change); + +static ssize_t hung_up_tty_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + return 0; +} + +static ssize_t hung_up_tty_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + return -EIO; +} + +/* No kernel lock held - none needed ;) */ +static unsigned int hung_up_tty_poll(struct file *filp, poll_table *wait) +{ + return POLLIN | POLLOUT | POLLERR | POLLHUP | POLLRDNORM | POLLWRNORM; +} + +static long hung_up_tty_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return cmd == TIOCSPGRP ? -ENOTTY : -EIO; +} + +static long hung_up_tty_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return cmd == TIOCSPGRP ? -ENOTTY : -EIO; +} + +static const struct file_operations tty_fops = { + .llseek = no_llseek, + .read = tty_read, + .write = tty_write, + .poll = tty_poll, + .unlocked_ioctl = tty_ioctl, + .compat_ioctl = tty_compat_ioctl, + .open = tty_open, + .release = tty_release, + .fasync = tty_fasync, +}; + +static const struct file_operations console_fops = { + .llseek = no_llseek, + .read = tty_read, + .write = redirected_tty_write, + .poll = tty_poll, + .unlocked_ioctl = tty_ioctl, + .compat_ioctl = tty_compat_ioctl, + .open = tty_open, + .release = tty_release, + .fasync = tty_fasync, +}; + +static const struct file_operations hung_up_tty_fops = { + .llseek = no_llseek, + .read = hung_up_tty_read, + .write = hung_up_tty_write, + .poll = hung_up_tty_poll, + .unlocked_ioctl = hung_up_tty_ioctl, + .compat_ioctl = hung_up_tty_compat_ioctl, + .release = tty_release, +}; + +static DEFINE_SPINLOCK(redirect_lock); +static struct file *redirect; + +/** + * tty_wakeup - request more data + * @tty: terminal + * + * Internal and external helper for wakeups of tty. This function + * informs the line discipline if present that the driver is ready + * to receive more output data. + */ + +void tty_wakeup(struct tty_struct *tty) +{ + struct tty_ldisc *ld; + + if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) { + ld = tty_ldisc_ref(tty); + if (ld) { + if (ld->ops->write_wakeup) + ld->ops->write_wakeup(tty); + tty_ldisc_deref(ld); + } + } + wake_up_interruptible_poll(&tty->write_wait, POLLOUT); +} + +EXPORT_SYMBOL_GPL(tty_wakeup); + +/** + * do_tty_hangup - actual handler for hangup events + * @work: tty device + * + * This can be called by the "eventd" kernel thread. That is process + * synchronous but doesn't hold any locks, so we need to make sure we + * have the appropriate locks for what we're doing. + * + * The hangup event clears any pending redirections onto the hung up + * device. It ensures future writes will error and it does the needed + * line discipline hangup and signal delivery. The tty object itself + * remains intact. + * + * Locking: + * BKL + * redirect lock for undoing redirection + * file list lock for manipulating list of ttys + * tty_ldisc_lock from called functions + * termios_mutex resetting termios data + * tasklist_lock to walk task list for hangup event + * ->siglock to protect ->signal/->sighand + */ +static void do_tty_hangup(struct work_struct *work) +{ + struct tty_struct *tty = + container_of(work, struct tty_struct, hangup_work); + struct file *cons_filp = NULL; + struct file *filp, *f = NULL; + struct task_struct *p; + int closecount = 0, n; + unsigned long flags; + int refs = 0; + + if (!tty) + return; + + + spin_lock(&redirect_lock); + if (redirect && redirect->private_data == tty) { + f = redirect; + redirect = NULL; + } + spin_unlock(&redirect_lock); + + /* inuse_filps is protected by the single kernel lock */ + lock_kernel(); + check_tty_count(tty, "do_tty_hangup"); + + file_list_lock(); + /* This breaks for file handles being sent over AF_UNIX sockets ? */ + list_for_each_entry(filp, &tty->tty_files, f_u.fu_list) { + if (filp->f_op->write == redirected_tty_write) + cons_filp = filp; + if (filp->f_op->write != tty_write) + continue; + closecount++; + tty_fasync(-1, filp, 0); /* can't block */ + filp->f_op = &hung_up_tty_fops; + } + file_list_unlock(); + + tty_ldisc_hangup(tty); + + read_lock(&tasklist_lock); + if (tty->session) { + do_each_pid_task(tty->session, PIDTYPE_SID, p) { + spin_lock_irq(&p->sighand->siglock); + if (p->signal->tty == tty) { + p->signal->tty = NULL; + /* We defer the dereferences outside fo + the tasklist lock */ + refs++; + } + if (!p->signal->leader) { + spin_unlock_irq(&p->sighand->siglock); + continue; + } + __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p); + __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p); + put_pid(p->signal->tty_old_pgrp); /* A noop */ + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (tty->pgrp) + p->signal->tty_old_pgrp = get_pid(tty->pgrp); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + spin_unlock_irq(&p->sighand->siglock); + } while_each_pid_task(tty->session, PIDTYPE_SID, p); + } + read_unlock(&tasklist_lock); + + spin_lock_irqsave(&tty->ctrl_lock, flags); + clear_bit(TTY_THROTTLED, &tty->flags); + clear_bit(TTY_PUSH, &tty->flags); + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + put_pid(tty->session); + put_pid(tty->pgrp); + tty->session = NULL; + tty->pgrp = NULL; + tty->ctrl_status = 0; + set_bit(TTY_HUPPED, &tty->flags); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + + /* Account for the p->signal references we killed */ + while (refs--) + tty_kref_put(tty); + + /* + * If one of the devices matches a console pointer, we + * cannot just call hangup() because that will cause + * tty->count and state->count to go out of sync. + * So we just call close() the right number of times. + */ + if (cons_filp) { + if (tty->ops->close) + for (n = 0; n < closecount; n++) + tty->ops->close(tty, cons_filp); + } else if (tty->ops->hangup) + (tty->ops->hangup)(tty); + /* + * We don't want to have driver/ldisc interactions beyond + * the ones we did here. The driver layer expects no + * calls after ->hangup() from the ldisc side. However we + * can't yet guarantee all that. + */ + set_bit(TTY_HUPPED, &tty->flags); + tty_ldisc_enable(tty); + unlock_kernel(); + if (f) + fput(f); +} + +/** + * tty_hangup - trigger a hangup event + * @tty: tty to hangup + * + * A carrier loss (virtual or otherwise) has occurred on this like + * schedule a hangup sequence to run after this event. + */ + +void tty_hangup(struct tty_struct *tty) +{ +#ifdef TTY_DEBUG_HANGUP + char buf[64]; + printk(KERN_DEBUG "%s hangup...\n", tty_name(tty, buf)); +#endif + schedule_work(&tty->hangup_work); +} + +EXPORT_SYMBOL(tty_hangup); + +/** + * tty_vhangup - process vhangup + * @tty: tty to hangup + * + * The user has asked via system call for the terminal to be hung up. + * We do this synchronously so that when the syscall returns the process + * is complete. That guarantee is necessary for security reasons. + */ + +void tty_vhangup(struct tty_struct *tty) +{ +#ifdef TTY_DEBUG_HANGUP + char buf[64]; + + printk(KERN_DEBUG "%s vhangup...\n", tty_name(tty, buf)); +#endif + do_tty_hangup(&tty->hangup_work); +} + +EXPORT_SYMBOL(tty_vhangup); + +/** + * tty_vhangup_self - process vhangup for own ctty + * + * Perform a vhangup on the current controlling tty + */ + +void tty_vhangup_self(void) +{ + struct tty_struct *tty; + + tty = get_current_tty(); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } +} + +/** + * tty_hung_up_p - was tty hung up + * @filp: file pointer of tty + * + * Return true if the tty has been subject to a vhangup or a carrier + * loss + */ + +int tty_hung_up_p(struct file *filp) +{ + return (filp->f_op == &hung_up_tty_fops); +} + +EXPORT_SYMBOL(tty_hung_up_p); + +static void session_clear_tty(struct pid *session) +{ + struct task_struct *p; + do_each_pid_task(session, PIDTYPE_SID, p) { + proc_clear_tty(p); + } while_each_pid_task(session, PIDTYPE_SID, p); +} + +/** + * disassociate_ctty - disconnect controlling tty + * @on_exit: true if exiting so need to "hang up" the session + * + * This function is typically called only by the session leader, when + * it wants to disassociate itself from its controlling tty. + * + * It performs the following functions: + * (1) Sends a SIGHUP and SIGCONT to the foreground process group + * (2) Clears the tty from being controlling the session + * (3) Clears the controlling tty for all processes in the + * session group. + * + * The argument on_exit is set to 1 if called when a process is + * exiting; it is 0 if called by the ioctl TIOCNOTTY. + * + * Locking: + * BKL is taken for hysterical raisins + * tty_mutex is taken to protect tty + * ->siglock is taken to protect ->signal/->sighand + * tasklist_lock is taken to walk process list for sessions + * ->siglock is taken to protect ->signal/->sighand + */ + +void disassociate_ctty(int on_exit) +{ + struct tty_struct *tty; + struct pid *tty_pgrp = NULL; + + if (!current->signal->leader) + return; + + tty = get_current_tty(); + if (tty) { + tty_pgrp = get_pid(tty->pgrp); + lock_kernel(); + if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY) + tty_vhangup(tty); + unlock_kernel(); + tty_kref_put(tty); + } else if (on_exit) { + struct pid *old_pgrp; + spin_lock_irq(¤t->sighand->siglock); + old_pgrp = current->signal->tty_old_pgrp; + current->signal->tty_old_pgrp = NULL; + spin_unlock_irq(¤t->sighand->siglock); + if (old_pgrp) { + kill_pgrp(old_pgrp, SIGHUP, on_exit); + kill_pgrp(old_pgrp, SIGCONT, on_exit); + put_pid(old_pgrp); + } + return; + } + if (tty_pgrp) { + kill_pgrp(tty_pgrp, SIGHUP, on_exit); + if (!on_exit) + kill_pgrp(tty_pgrp, SIGCONT, on_exit); + put_pid(tty_pgrp); + } + + spin_lock_irq(¤t->sighand->siglock); + put_pid(current->signal->tty_old_pgrp); + current->signal->tty_old_pgrp = NULL; + spin_unlock_irq(¤t->sighand->siglock); + + tty = get_current_tty(); + if (tty) { + unsigned long flags; + spin_lock_irqsave(&tty->ctrl_lock, flags); + put_pid(tty->session); + put_pid(tty->pgrp); + tty->session = NULL; + tty->pgrp = NULL; + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + tty_kref_put(tty); + } else { +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "error attempted to write to tty [0x%p]" + " = NULL", tty); +#endif + } + + /* Now clear signal->tty under the lock */ + read_lock(&tasklist_lock); + session_clear_tty(task_session(current)); + read_unlock(&tasklist_lock); +} + +/** + * + * no_tty - Ensure the current process does not have a controlling tty + */ +void no_tty(void) +{ + struct task_struct *tsk = current; + lock_kernel(); + disassociate_ctty(0); + unlock_kernel(); + proc_clear_tty(tsk); +} + + +/** + * stop_tty - propagate flow control + * @tty: tty to stop + * + * Perform flow control to the driver. For PTY/TTY pairs we + * must also propagate the TIOCKPKT status. May be called + * on an already stopped device and will not re-call the driver + * method. + * + * This functionality is used by both the line disciplines for + * halting incoming flow and by the driver. It may therefore be + * called from any context, may be under the tty atomic_write_lock + * but not always. + * + * Locking: + * Uses the tty control lock internally + */ + +void stop_tty(struct tty_struct *tty) +{ + unsigned long flags; + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (tty->stopped) { + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + return; + } + tty->stopped = 1; + if (tty->link && tty->link->packet) { + tty->ctrl_status &= ~TIOCPKT_START; + tty->ctrl_status |= TIOCPKT_STOP; + wake_up_interruptible_poll(&tty->link->read_wait, POLLIN); + } + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + if (tty->ops->stop) + (tty->ops->stop)(tty); +} + +EXPORT_SYMBOL(stop_tty); + +/** + * start_tty - propagate flow control + * @tty: tty to start + * + * Start a tty that has been stopped if at all possible. Perform + * any necessary wakeups and propagate the TIOCPKT status. If this + * is the tty was previous stopped and is being started then the + * driver start method is invoked and the line discipline woken. + * + * Locking: + * ctrl_lock + */ + +void start_tty(struct tty_struct *tty) +{ + unsigned long flags; + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (!tty->stopped || tty->flow_stopped) { + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + return; + } + tty->stopped = 0; + if (tty->link && tty->link->packet) { + tty->ctrl_status &= ~TIOCPKT_STOP; + tty->ctrl_status |= TIOCPKT_START; + wake_up_interruptible_poll(&tty->link->read_wait, POLLIN); + } + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + if (tty->ops->start) + (tty->ops->start)(tty); + /* If we have a running line discipline it may need kicking */ + tty_wakeup(tty); +} + +EXPORT_SYMBOL(start_tty); + +/** + * tty_read - read method for tty device files + * @file: pointer to tty file + * @buf: user buffer + * @count: size of user buffer + * @ppos: unused + * + * Perform the read system call function on this terminal device. Checks + * for hung up devices before calling the line discipline method. + * + * Locking: + * Locks the line discipline internally while needed. Multiple + * read calls may be outstanding in parallel. + */ + +static ssize_t tty_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int i; + struct tty_struct *tty; + struct inode *inode; + struct tty_ldisc *ld; + + tty = (struct tty_struct *)file->private_data; + inode = file->f_path.dentry->d_inode; + if (tty_paranoia_check(tty, inode, "tty_read")) + return -EIO; + if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags))) + return -EIO; + + /* We want to wait for the line discipline to sort out in this + situation */ + ld = tty_ldisc_ref_wait(tty); + if (ld->ops->read) + i = (ld->ops->read)(tty, file, buf, count); + else + i = -EIO; + tty_ldisc_deref(ld); + if (i > 0) + inode->i_atime = current_fs_time(inode->i_sb); + return i; +} + +void tty_write_unlock(struct tty_struct *tty) +{ + mutex_unlock(&tty->atomic_write_lock); + wake_up_interruptible_poll(&tty->write_wait, POLLOUT); +} + +int tty_write_lock(struct tty_struct *tty, int ndelay) +{ + if (!mutex_trylock(&tty->atomic_write_lock)) { + if (ndelay) + return -EAGAIN; + if (mutex_lock_interruptible(&tty->atomic_write_lock)) + return -ERESTARTSYS; + } + return 0; +} + +/* + * Split writes up in sane blocksizes to avoid + * denial-of-service type attacks + */ +static inline ssize_t do_tty_write( + ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t), + struct tty_struct *tty, + struct file *file, + const char __user *buf, + size_t count) +{ + ssize_t ret, written = 0; + unsigned int chunk; + + ret = tty_write_lock(tty, file->f_flags & O_NDELAY); + if (ret < 0) + return ret; + + /* + * We chunk up writes into a temporary buffer. This + * simplifies low-level drivers immensely, since they + * don't have locking issues and user mode accesses. + * + * But if TTY_NO_WRITE_SPLIT is set, we should use a + * big chunk-size.. + * + * The default chunk-size is 2kB, because the NTTY + * layer has problems with bigger chunks. It will + * claim to be able to handle more characters than + * it actually does. + * + * FIXME: This can probably go away now except that 64K chunks + * are too likely to fail unless switched to vmalloc... + */ + chunk = 2048; + if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags)) + chunk = 65536; + if (count < chunk) + chunk = count; + + /* write_buf/write_cnt is protected by the atomic_write_lock mutex */ + if (tty->write_cnt < chunk) { + unsigned char *buf_chunk; + + if (chunk < 1024) + chunk = 1024; + + buf_chunk = kmalloc(chunk, GFP_KERNEL); + if (!buf_chunk) { + ret = -ENOMEM; + goto out; + } + kfree(tty->write_buf); + tty->write_cnt = chunk; + tty->write_buf = buf_chunk; + } + + /* Do the write .. */ + for (;;) { + size_t size = count; + if (size > chunk) + size = chunk; + ret = -EFAULT; + if (copy_from_user(tty->write_buf, buf, size)) + break; + ret = write(tty, file, tty->write_buf, size); + if (ret <= 0) + break; + written += ret; + buf += ret; + count -= ret; + if (!count) + break; + ret = -ERESTARTSYS; + if (signal_pending(current)) + break; + cond_resched(); + } + if (written) { + struct inode *inode = file->f_path.dentry->d_inode; + inode->i_mtime = current_fs_time(inode->i_sb); + ret = written; + } +out: + tty_write_unlock(tty); + return ret; +} + +/** + * tty_write_message - write a message to a certain tty, not just the console. + * @tty: the destination tty_struct + * @msg: the message to write + * + * This is used for messages that need to be redirected to a specific tty. + * We don't put it into the syslog queue right now maybe in the future if + * really needed. + * + * We must still hold the BKL and test the CLOSING flag for the moment. + */ + +void tty_write_message(struct tty_struct *tty, char *msg) +{ + if (tty) { + mutex_lock(&tty->atomic_write_lock); + lock_kernel(); + if (tty->ops->write && !test_bit(TTY_CLOSING, &tty->flags)) { + unlock_kernel(); + tty->ops->write(tty, msg, strlen(msg)); + } else + unlock_kernel(); + tty_write_unlock(tty); + } + return; +} + + +/** + * tty_write - write method for tty device file + * @file: tty file pointer + * @buf: user data to write + * @count: bytes to write + * @ppos: unused + * + * Write data to a tty device via the line discipline. + * + * Locking: + * Locks the line discipline as required + * Writes to the tty driver are serialized by the atomic_write_lock + * and are then processed in chunks to the device. The line discipline + * write method will not be invoked in parallel for each device. + */ + +static ssize_t tty_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct tty_struct *tty; + struct inode *inode = file->f_path.dentry->d_inode; + ssize_t ret; + struct tty_ldisc *ld; + + tty = (struct tty_struct *)file->private_data; + if (tty_paranoia_check(tty, inode, "tty_write")) + return -EIO; + if (!tty || !tty->ops->write || + (test_bit(TTY_IO_ERROR, &tty->flags))) + return -EIO; + /* Short term debug to catch buggy drivers */ + if (tty->ops->write_room == NULL) + printk(KERN_ERR "tty driver %s lacks a write_room method.\n", + tty->driver->name); + ld = tty_ldisc_ref_wait(tty); + if (!ld->ops->write) + ret = -EIO; + else + ret = do_tty_write(ld->ops->write, tty, file, buf, count); + tty_ldisc_deref(ld); + return ret; +} + +ssize_t redirected_tty_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct file *p = NULL; + + spin_lock(&redirect_lock); + if (redirect) { + get_file(redirect); + p = redirect; + } + spin_unlock(&redirect_lock); + + if (p) { + ssize_t res; + res = vfs_write(p, buf, count, &p->f_pos); + fput(p); + return res; + } + return tty_write(file, buf, count, ppos); +} + +static char ptychar[] = "pqrstuvwxyzabcde"; + +/** + * pty_line_name - generate name for a pty + * @driver: the tty driver in use + * @index: the minor number + * @p: output buffer of at least 6 bytes + * + * Generate a name from a driver reference and write it to the output + * buffer. + * + * Locking: None + */ +static void pty_line_name(struct tty_driver *driver, int index, char *p) +{ + int i = index + driver->name_base; + /* ->name is initialized to "ttyp", but "tty" is expected */ + sprintf(p, "%s%c%x", + driver->subtype == PTY_TYPE_SLAVE ? "tty" : driver->name, + ptychar[i >> 4 & 0xf], i & 0xf); +} + +/** + * tty_line_name - generate name for a tty + * @driver: the tty driver in use + * @index: the minor number + * @p: output buffer of at least 7 bytes + * + * Generate a name from a driver reference and write it to the output + * buffer. + * + * Locking: None + */ +static void tty_line_name(struct tty_driver *driver, int index, char *p) +{ + sprintf(p, "%s%d", driver->name, index + driver->name_base); +} + +/** + * tty_driver_lookup_tty() - find an existing tty, if any + * @driver: the driver for the tty + * @idx: the minor number + * + * Return the tty, if found or ERR_PTR() otherwise. + * + * Locking: tty_mutex must be held. If tty is found, the mutex must + * be held until the 'fast-open' is also done. Will change once we + * have refcounting in the driver and per driver locking + */ +static struct tty_struct *tty_driver_lookup_tty(struct tty_driver *driver, + struct inode *inode, int idx) +{ + struct tty_struct *tty; + + if (driver->ops->lookup) + return driver->ops->lookup(driver, inode, idx); + + tty = driver->ttys[idx]; + return tty; +} + +/** + * tty_init_termios - helper for termios setup + * @tty: the tty to set up + * + * Initialise the termios structures for this tty. Thus runs under + * the tty_mutex currently so we can be relaxed about ordering. + */ + +int tty_init_termios(struct tty_struct *tty) +{ + struct ktermios *tp; + int idx = tty->index; + + tp = tty->driver->termios[idx]; + if (tp == NULL) { + tp = kzalloc(sizeof(struct ktermios[2]), GFP_KERNEL); + if (tp == NULL) + return -ENOMEM; + memcpy(tp, &tty->driver->init_termios, + sizeof(struct ktermios)); + tty->driver->termios[idx] = tp; + } + tty->termios = tp; + tty->termios_locked = tp + 1; + + /* Compatibility until drivers always set this */ + tty->termios->c_ispeed = tty_termios_input_baud_rate(tty->termios); + tty->termios->c_ospeed = tty_termios_baud_rate(tty->termios); + return 0; +} +EXPORT_SYMBOL_GPL(tty_init_termios); + +/** + * tty_driver_install_tty() - install a tty entry in the driver + * @driver: the driver for the tty + * @tty: the tty + * + * Install a tty object into the driver tables. The tty->index field + * will be set by the time this is called. This method is responsible + * for ensuring any need additional structures are allocated and + * configured. + * + * Locking: tty_mutex for now + */ +static int tty_driver_install_tty(struct tty_driver *driver, + struct tty_struct *tty) +{ + int idx = tty->index; + int ret; + + if (driver->ops->install) { + lock_kernel(); + ret = driver->ops->install(driver, tty); + unlock_kernel(); + return ret; + } + + if (tty_init_termios(tty) == 0) { + lock_kernel(); + tty_driver_kref_get(driver); + tty->count++; + driver->ttys[idx] = tty; + unlock_kernel(); + return 0; + } + return -ENOMEM; +} + +/** + * tty_driver_remove_tty() - remove a tty from the driver tables + * @driver: the driver for the tty + * @idx: the minor number + * + * Remvoe a tty object from the driver tables. The tty->index field + * will be set by the time this is called. + * + * Locking: tty_mutex for now + */ +static void tty_driver_remove_tty(struct tty_driver *driver, + struct tty_struct *tty) +{ + if (driver->ops->remove) + driver->ops->remove(driver, tty); + else + driver->ttys[tty->index] = NULL; +} + +/* + * tty_reopen() - fast re-open of an open tty + * @tty - the tty to open + * + * Return 0 on success, -errno on error. + * + * Locking: tty_mutex must be held from the time the tty was found + * till this open completes. + */ +static int tty_reopen(struct tty_struct *tty) +{ + struct tty_driver *driver = tty->driver; + + if (test_bit(TTY_CLOSING, &tty->flags)) + return -EIO; + + if (driver->type == TTY_DRIVER_TYPE_PTY && + driver->subtype == PTY_TYPE_MASTER) { + /* + * special case for PTY masters: only one open permitted, + * and the slave side open count is incremented as well. + */ + if (tty->count) + return -EIO; + + tty->link->count++; + } + tty->count++; + tty->driver = driver; /* N.B. why do this every time?? */ + + mutex_lock(&tty->ldisc_mutex); + WARN_ON(!test_bit(TTY_LDISC, &tty->flags)); + mutex_unlock(&tty->ldisc_mutex); + + return 0; +} + +/** + * tty_init_dev - initialise a tty device + * @driver: tty driver we are opening a device on + * @idx: device index + * @ret_tty: returned tty structure + * @first_ok: ok to open a new device (used by ptmx) + * + * Prepare a tty device. This may not be a "new" clean device but + * could also be an active device. The pty drivers require special + * handling because of this. + * + * Locking: + * The function is called under the tty_mutex, which + * protects us from the tty struct or driver itself going away. + * + * On exit the tty device has the line discipline attached and + * a reference count of 1. If a pair was created for pty/tty use + * and the other was a pty master then it too has a reference count of 1. + * + * WSH 06/09/97: Rewritten to remove races and properly clean up after a + * failed open. The new code protects the open with a mutex, so it's + * really quite straightforward. The mutex locking can probably be + * relaxed for the (most common) case of reopening a tty. + */ + +struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx, + int first_ok) +{ + struct tty_struct *tty; + int retval; + + lock_kernel(); + /* Check if pty master is being opened multiple times */ + if (driver->subtype == PTY_TYPE_MASTER && + (driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok) { + unlock_kernel(); + return ERR_PTR(-EIO); + } + unlock_kernel(); + + /* + * First time open is complex, especially for PTY devices. + * This code guarantees that either everything succeeds and the + * TTY is ready for operation, or else the table slots are vacated + * and the allocated memory released. (Except that the termios + * and locked termios may be retained.) + */ + + if (!try_module_get(driver->owner)) + return ERR_PTR(-ENODEV); + + tty = alloc_tty_struct(); + if (!tty) + goto fail_no_mem; + initialize_tty_struct(tty, driver, idx); + + retval = tty_driver_install_tty(driver, tty); + if (retval < 0) { + free_tty_struct(tty); + module_put(driver->owner); + return ERR_PTR(retval); + } + + /* + * Structures all installed ... call the ldisc open routines. + * If we fail here just call release_tty to clean up. No need + * to decrement the use counts, as release_tty doesn't care. + */ + retval = tty_ldisc_setup(tty, tty->link); + if (retval) + goto release_mem_out; + return tty; + +fail_no_mem: + module_put(driver->owner); + return ERR_PTR(-ENOMEM); + + /* call the tty release_tty routine to clean out this slot */ +release_mem_out: + if (printk_ratelimit()) + printk(KERN_INFO "tty_init_dev: ldisc open failed, " + "clearing slot %d\n", idx); + lock_kernel(); + release_tty(tty, idx); + unlock_kernel(); + return ERR_PTR(retval); +} + +void tty_free_termios(struct tty_struct *tty) +{ + struct ktermios *tp; + int idx = tty->index; + /* Kill this flag and push into drivers for locking etc */ + if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) { + /* FIXME: Locking on ->termios array */ + tp = tty->termios; + tty->driver->termios[idx] = NULL; + kfree(tp); + } +} +EXPORT_SYMBOL(tty_free_termios); + +void tty_shutdown(struct tty_struct *tty) +{ + tty_driver_remove_tty(tty->driver, tty); + tty_free_termios(tty); +} +EXPORT_SYMBOL(tty_shutdown); + +/** + * release_one_tty - release tty structure memory + * @kref: kref of tty we are obliterating + * + * Releases memory associated with a tty structure, and clears out the + * driver table slots. This function is called when a device is no longer + * in use. It also gets called when setup of a device fails. + * + * Locking: + * tty_mutex - sometimes only + * takes the file list lock internally when working on the list + * of ttys that the driver keeps. + * + * This method gets called from a work queue so that the driver private + * cleanup ops can sleep (needed for USB at least) + */ +static void release_one_tty(struct work_struct *work) +{ + struct tty_struct *tty = + container_of(work, struct tty_struct, hangup_work); + struct tty_driver *driver = tty->driver; + + if (tty->ops->cleanup) + tty->ops->cleanup(tty); + + tty->magic = 0; + tty_driver_kref_put(driver); + module_put(driver->owner); + + file_list_lock(); + list_del_init(&tty->tty_files); + file_list_unlock(); + + put_pid(tty->pgrp); + put_pid(tty->session); + free_tty_struct(tty); +} + +static void queue_release_one_tty(struct kref *kref) +{ + struct tty_struct *tty = container_of(kref, struct tty_struct, kref); + + if (tty->ops->shutdown) + tty->ops->shutdown(tty); + else + tty_shutdown(tty); + + /* The hangup queue is now free so we can reuse it rather than + waste a chunk of memory for each port */ + INIT_WORK(&tty->hangup_work, release_one_tty); + schedule_work(&tty->hangup_work); +} + +/** + * tty_kref_put - release a tty kref + * @tty: tty device + * + * Release a reference to a tty device and if need be let the kref + * layer destruct the object for us + */ + +void tty_kref_put(struct tty_struct *tty) +{ + if (tty) + kref_put(&tty->kref, queue_release_one_tty); +} +EXPORT_SYMBOL(tty_kref_put); + +/** + * release_tty - release tty structure memory + * + * Release both @tty and a possible linked partner (think pty pair), + * and decrement the refcount of the backing module. + * + * Locking: + * tty_mutex - sometimes only + * takes the file list lock internally when working on the list + * of ttys that the driver keeps. + * FIXME: should we require tty_mutex is held here ?? + * + */ +static void release_tty(struct tty_struct *tty, int idx) +{ + /* This should always be true but check for the moment */ + WARN_ON(tty->index != idx); + + if (tty->link) + tty_kref_put(tty->link); + tty_kref_put(tty); +} + +/** + * tty_release - vfs callback for close + * @inode: inode of tty + * @filp: file pointer for handle to tty + * + * Called the last time each file handle is closed that references + * this tty. There may however be several such references. + * + * Locking: + * Takes bkl. See tty_release_dev + * + * Even releasing the tty structures is a tricky business.. We have + * to be very careful that the structures are all released at the + * same time, as interrupts might otherwise get the wrong pointers. + * + * WSH 09/09/97: rewritten to avoid some nasty race conditions that could + * lead to double frees or releasing memory still in use. + */ + +int tty_release(struct inode *inode, struct file *filp) +{ + struct tty_struct *tty, *o_tty; + int pty_master, tty_closing, o_tty_closing, do_sleep; + int devpts; + int idx; + char buf[64]; + + tty = (struct tty_struct *)filp->private_data; + if (tty_paranoia_check(tty, inode, "tty_release_dev")) + return 0; + + lock_kernel(); + check_tty_count(tty, "tty_release_dev"); + + tty_fasync(-1, filp, 0); + + idx = tty->index; + pty_master = (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER); + devpts = (tty->driver->flags & TTY_DRIVER_DEVPTS_MEM) != 0; + o_tty = tty->link; + +#ifdef TTY_PARANOIA_CHECK + if (idx < 0 || idx >= tty->driver->num) { + printk(KERN_DEBUG "tty_release_dev: bad idx when trying to " + "free (%s)\n", tty->name); + unlock_kernel(); + return 0; + } + if (!devpts) { + if (tty != tty->driver->ttys[idx]) { + unlock_kernel(); + printk(KERN_DEBUG "tty_release_dev: driver.table[%d] not tty " + "for (%s)\n", idx, tty->name); + return 0; + } + if (tty->termios != tty->driver->termios[idx]) { + unlock_kernel(); + printk(KERN_DEBUG "tty_release_dev: driver.termios[%d] not termios " + "for (%s)\n", + idx, tty->name); + return 0; + } + } +#endif + +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "tty_release_dev of %s (tty count=%d)...", + tty_name(tty, buf), tty->count); +#endif + +#ifdef TTY_PARANOIA_CHECK + if (tty->driver->other && + !(tty->driver->flags & TTY_DRIVER_DEVPTS_MEM)) { + if (o_tty != tty->driver->other->ttys[idx]) { + unlock_kernel(); + printk(KERN_DEBUG "tty_release_dev: other->table[%d] " + "not o_tty for (%s)\n", + idx, tty->name); + return 0 ; + } + if (o_tty->termios != tty->driver->other->termios[idx]) { + unlock_kernel(); + printk(KERN_DEBUG "tty_release_dev: other->termios[%d] " + "not o_termios for (%s)\n", + idx, tty->name); + return 0; + } + if (o_tty->link != tty) { + unlock_kernel(); + printk(KERN_DEBUG "tty_release_dev: bad pty pointers\n"); + return 0; + } + } +#endif + if (tty->ops->close) + tty->ops->close(tty, filp); + + unlock_kernel(); + /* + * Sanity check: if tty->count is going to zero, there shouldn't be + * any waiters on tty->read_wait or tty->write_wait. We test the + * wait queues and kick everyone out _before_ actually starting to + * close. This ensures that we won't block while releasing the tty + * structure. + * + * The test for the o_tty closing is necessary, since the master and + * slave sides may close in any order. If the slave side closes out + * first, its count will be one, since the master side holds an open. + * Thus this test wouldn't be triggered at the time the slave closes, + * so we do it now. + * + * Note that it's possible for the tty to be opened again while we're + * flushing out waiters. By recalculating the closing flags before + * each iteration we avoid any problems. + */ + while (1) { + /* Guard against races with tty->count changes elsewhere and + opens on /dev/tty */ + + mutex_lock(&tty_mutex); + lock_kernel(); + tty_closing = tty->count <= 1; + o_tty_closing = o_tty && + (o_tty->count <= (pty_master ? 1 : 0)); + do_sleep = 0; + + if (tty_closing) { + if (waitqueue_active(&tty->read_wait)) { + wake_up_poll(&tty->read_wait, POLLIN); + do_sleep++; + } + if (waitqueue_active(&tty->write_wait)) { + wake_up_poll(&tty->write_wait, POLLOUT); + do_sleep++; + } + } + if (o_tty_closing) { + if (waitqueue_active(&o_tty->read_wait)) { + wake_up_poll(&o_tty->read_wait, POLLIN); + do_sleep++; + } + if (waitqueue_active(&o_tty->write_wait)) { + wake_up_poll(&o_tty->write_wait, POLLOUT); + do_sleep++; + } + } + if (!do_sleep) + break; + + printk(KERN_WARNING "tty_release_dev: %s: read/write wait queue " + "active!\n", tty_name(tty, buf)); + unlock_kernel(); + mutex_unlock(&tty_mutex); + schedule(); + } + + /* + * The closing flags are now consistent with the open counts on + * both sides, and we've completed the last operation that could + * block, so it's safe to proceed with closing. + */ + if (pty_master) { + if (--o_tty->count < 0) { + printk(KERN_WARNING "tty_release_dev: bad pty slave count " + "(%d) for %s\n", + o_tty->count, tty_name(o_tty, buf)); + o_tty->count = 0; + } + } + if (--tty->count < 0) { + printk(KERN_WARNING "tty_release_dev: bad tty->count (%d) for %s\n", + tty->count, tty_name(tty, buf)); + tty->count = 0; + } + + /* + * We've decremented tty->count, so we need to remove this file + * descriptor off the tty->tty_files list; this serves two + * purposes: + * - check_tty_count sees the correct number of file descriptors + * associated with this tty. + * - do_tty_hangup no longer sees this file descriptor as + * something that needs to be handled for hangups. + */ + file_kill(filp); + filp->private_data = NULL; + + /* + * Perform some housekeeping before deciding whether to return. + * + * Set the TTY_CLOSING flag if this was the last open. In the + * case of a pty we may have to wait around for the other side + * to close, and TTY_CLOSING makes sure we can't be reopened. + */ + if (tty_closing) + set_bit(TTY_CLOSING, &tty->flags); + if (o_tty_closing) + set_bit(TTY_CLOSING, &o_tty->flags); + + /* + * If _either_ side is closing, make sure there aren't any + * processes that still think tty or o_tty is their controlling + * tty. + */ + if (tty_closing || o_tty_closing) { + read_lock(&tasklist_lock); + session_clear_tty(tty->session); + if (o_tty) + session_clear_tty(o_tty->session); + read_unlock(&tasklist_lock); + } + + mutex_unlock(&tty_mutex); + + /* check whether both sides are closing ... */ + if (!tty_closing || (o_tty && !o_tty_closing)) { + unlock_kernel(); + return 0; + } + +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "freeing tty structure..."); +#endif + /* + * Ask the line discipline code to release its structures + */ + tty_ldisc_release(tty, o_tty); + /* + * The release_tty function takes care of the details of clearing + * the slots and preserving the termios structure. + */ + release_tty(tty, idx); + + /* Make this pty number available for reallocation */ + if (devpts) + devpts_kill_index(inode, idx); + unlock_kernel(); + return 0; +} + +/** + * tty_open - open a tty device + * @inode: inode of device file + * @filp: file pointer to tty + * + * tty_open and tty_release keep up the tty count that contains the + * number of opens done on a tty. We cannot use the inode-count, as + * different inodes might point to the same tty. + * + * Open-counting is needed for pty masters, as well as for keeping + * track of serial lines: DTR is dropped when the last close happens. + * (This is not done solely through tty->count, now. - Ted 1/27/92) + * + * The termios state of a pty is reset on first open so that + * settings don't persist across reuse. + * + * Locking: tty_mutex protects tty, get_tty_driver and tty_init_dev work. + * tty->count should protect the rest. + * ->siglock protects ->signal/->sighand + */ + +static int tty_open(struct inode *inode, struct file *filp) +{ + struct tty_struct *tty = NULL; + int noctty, retval; + struct tty_driver *driver; + int index; + dev_t device = inode->i_rdev; + unsigned saved_flags = filp->f_flags; + + nonseekable_open(inode, filp); + +retry_open: + noctty = filp->f_flags & O_NOCTTY; + index = -1; + retval = 0; + + mutex_lock(&tty_mutex); + lock_kernel(); + + if (device == MKDEV(TTYAUX_MAJOR, 0)) { + tty = get_current_tty(); + if (!tty) { + unlock_kernel(); + mutex_unlock(&tty_mutex); + return -ENXIO; + } + driver = tty_driver_kref_get(tty->driver); + index = tty->index; + filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */ + /* noctty = 1; */ + /* FIXME: Should we take a driver reference ? */ + tty_kref_put(tty); + goto got_driver; + } +#ifdef CONFIG_VT + if (device == MKDEV(TTY_MAJOR, 0)) { + extern struct tty_driver *console_driver; + driver = tty_driver_kref_get(console_driver); + index = fg_console; + noctty = 1; + goto got_driver; + } +#endif + if (device == MKDEV(TTYAUX_MAJOR, 1)) { + struct tty_driver *console_driver = console_device(&index); + if (console_driver) { + driver = tty_driver_kref_get(console_driver); + if (driver) { + /* Don't let /dev/console block */ + filp->f_flags |= O_NONBLOCK; + noctty = 1; + goto got_driver; + } + } + unlock_kernel(); + mutex_unlock(&tty_mutex); + return -ENODEV; + } + + driver = get_tty_driver(device, &index); + if (!driver) { + unlock_kernel(); + mutex_unlock(&tty_mutex); + return -ENODEV; + } +got_driver: + if (!tty) { + /* check whether we're reopening an existing tty */ + tty = tty_driver_lookup_tty(driver, inode, index); + + if (IS_ERR(tty)) { + unlock_kernel(); + mutex_unlock(&tty_mutex); + return PTR_ERR(tty); + } + } + + if (tty) { + retval = tty_reopen(tty); + if (retval) + tty = ERR_PTR(retval); + } else + tty = tty_init_dev(driver, index, 0); + + mutex_unlock(&tty_mutex); + tty_driver_kref_put(driver); + if (IS_ERR(tty)) { + unlock_kernel(); + return PTR_ERR(tty); + } + + filp->private_data = tty; + file_move(filp, &tty->tty_files); + check_tty_count(tty, "tty_open"); + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + noctty = 1; +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "opening %s...", tty->name); +#endif + if (!retval) { + if (tty->ops->open) + retval = tty->ops->open(tty, filp); + else + retval = -ENODEV; + } + filp->f_flags = saved_flags; + + if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && + !capable(CAP_SYS_ADMIN)) + retval = -EBUSY; + + if (retval) { +#ifdef TTY_DEBUG_HANGUP + printk(KERN_DEBUG "error %d in opening %s...", retval, + tty->name); +#endif + tty_release(inode, filp); + if (retval != -ERESTARTSYS) { + unlock_kernel(); + return retval; + } + if (signal_pending(current)) { + unlock_kernel(); + return retval; + } + schedule(); + /* + * Need to reset f_op in case a hangup happened. + */ + if (filp->f_op == &hung_up_tty_fops) + filp->f_op = &tty_fops; + unlock_kernel(); + goto retry_open; + } + unlock_kernel(); + + + mutex_lock(&tty_mutex); + lock_kernel(); + spin_lock_irq(¤t->sighand->siglock); + if (!noctty && + current->signal->leader && + !current->signal->tty && + tty->session == NULL) + __proc_set_tty(current, tty); + spin_unlock_irq(¤t->sighand->siglock); + unlock_kernel(); + mutex_unlock(&tty_mutex); + return 0; +} + + + +/** + * tty_poll - check tty status + * @filp: file being polled + * @wait: poll wait structures to update + * + * Call the line discipline polling method to obtain the poll + * status of the device. + * + * Locking: locks called line discipline but ldisc poll method + * may be re-entered freely by other callers. + */ + +static unsigned int tty_poll(struct file *filp, poll_table *wait) +{ + struct tty_struct *tty; + struct tty_ldisc *ld; + int ret = 0; + + tty = (struct tty_struct *)filp->private_data; + if (tty_paranoia_check(tty, filp->f_path.dentry->d_inode, "tty_poll")) + return 0; + + ld = tty_ldisc_ref_wait(tty); + if (ld->ops->poll) + ret = (ld->ops->poll)(tty, filp, wait); + tty_ldisc_deref(ld); + return ret; +} + +static int tty_fasync(int fd, struct file *filp, int on) +{ + struct tty_struct *tty; + unsigned long flags; + int retval = 0; + + lock_kernel(); + tty = (struct tty_struct *)filp->private_data; + if (tty_paranoia_check(tty, filp->f_path.dentry->d_inode, "tty_fasync")) + goto out; + + retval = fasync_helper(fd, filp, on, &tty->fasync); + if (retval <= 0) + goto out; + + if (on) { + enum pid_type type; + struct pid *pid; + if (!waitqueue_active(&tty->read_wait)) + tty->minimum_to_wake = 1; + spin_lock_irqsave(&tty->ctrl_lock, flags); + if (tty->pgrp) { + pid = tty->pgrp; + type = PIDTYPE_PGID; + } else { + pid = task_pid(current); + type = PIDTYPE_PID; + } + get_pid(pid); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + retval = __f_setown(filp, pid, type, 0); + put_pid(pid); + if (retval) + goto out; + } else { + if (!tty->fasync && !waitqueue_active(&tty->read_wait)) + tty->minimum_to_wake = N_TTY_BUF_SIZE; + } + retval = 0; +out: + unlock_kernel(); + return retval; +} + +/** + * tiocsti - fake input character + * @tty: tty to fake input into + * @p: pointer to character + * + * Fake input to a tty device. Does the necessary locking and + * input management. + * + * FIXME: does not honour flow control ?? + * + * Locking: + * Called functions take tty_ldisc_lock + * current->signal->tty check is safe without locks + * + * FIXME: may race normal receive processing + */ + +static int tiocsti(struct tty_struct *tty, char __user *p) +{ + char ch, mbz = 0; + struct tty_ldisc *ld; + + if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + if (get_user(ch, p)) + return -EFAULT; + tty_audit_tiocsti(tty, ch); + ld = tty_ldisc_ref_wait(tty); + ld->ops->receive_buf(tty, &ch, &mbz, 1); + tty_ldisc_deref(ld); + return 0; +} + +/** + * tiocgwinsz - implement window query ioctl + * @tty; tty + * @arg: user buffer for result + * + * Copies the kernel idea of the window size into the user buffer. + * + * Locking: tty->termios_mutex is taken to ensure the winsize data + * is consistent. + */ + +static int tiocgwinsz(struct tty_struct *tty, struct winsize __user *arg) +{ + int err; + + mutex_lock(&tty->termios_mutex); + err = copy_to_user(arg, &tty->winsize, sizeof(*arg)); + mutex_unlock(&tty->termios_mutex); + + return err ? -EFAULT: 0; +} + +/** + * tty_do_resize - resize event + * @tty: tty being resized + * @rows: rows (character) + * @cols: cols (character) + * + * Update the termios variables and send the necessary signals to + * peform a terminal resize correctly + */ + +int tty_do_resize(struct tty_struct *tty, struct winsize *ws) +{ + struct pid *pgrp; + unsigned long flags; + + /* Lock the tty */ + mutex_lock(&tty->termios_mutex); + if (!memcmp(ws, &tty->winsize, sizeof(*ws))) + goto done; + /* Get the PID values and reference them so we can + avoid holding the tty ctrl lock while sending signals */ + spin_lock_irqsave(&tty->ctrl_lock, flags); + pgrp = get_pid(tty->pgrp); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + + if (pgrp) + kill_pgrp(pgrp, SIGWINCH, 1); + put_pid(pgrp); + + tty->winsize = *ws; +done: + mutex_unlock(&tty->termios_mutex); + return 0; +} + +/** + * tiocswinsz - implement window size set ioctl + * @tty; tty side of tty + * @arg: user buffer for result + * + * Copies the user idea of the window size to the kernel. Traditionally + * this is just advisory information but for the Linux console it + * actually has driver level meaning and triggers a VC resize. + * + * Locking: + * Driver dependant. The default do_resize method takes the + * tty termios mutex and ctrl_lock. The console takes its own lock + * then calls into the default method. + */ + +static int tiocswinsz(struct tty_struct *tty, struct winsize __user *arg) +{ + struct winsize tmp_ws; + if (copy_from_user(&tmp_ws, arg, sizeof(*arg))) + return -EFAULT; + + if (tty->ops->resize) + return tty->ops->resize(tty, &tmp_ws); + else + return tty_do_resize(tty, &tmp_ws); +} + +/** + * tioccons - allow admin to move logical console + * @file: the file to become console + * + * Allow the adminstrator to move the redirected console device + * + * Locking: uses redirect_lock to guard the redirect information + */ + +static int tioccons(struct file *file) +{ + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (file->f_op->write == redirected_tty_write) { + struct file *f; + spin_lock(&redirect_lock); + f = redirect; + redirect = NULL; + spin_unlock(&redirect_lock); + if (f) + fput(f); + return 0; + } + spin_lock(&redirect_lock); + if (redirect) { + spin_unlock(&redirect_lock); + return -EBUSY; + } + get_file(file); + redirect = file; + spin_unlock(&redirect_lock); + return 0; +} + +/** + * fionbio - non blocking ioctl + * @file: file to set blocking value + * @p: user parameter + * + * Historical tty interfaces had a blocking control ioctl before + * the generic functionality existed. This piece of history is preserved + * in the expected tty API of posix OS's. + * + * Locking: none, the open file handle ensures it won't go away. + */ + +static int fionbio(struct file *file, int __user *p) +{ + int nonblock; + + if (get_user(nonblock, p)) + return -EFAULT; + + spin_lock(&file->f_lock); + if (nonblock) + file->f_flags |= O_NONBLOCK; + else + file->f_flags &= ~O_NONBLOCK; + spin_unlock(&file->f_lock); + return 0; +} + +/** + * tiocsctty - set controlling tty + * @tty: tty structure + * @arg: user argument + * + * This ioctl is used to manage job control. It permits a session + * leader to set this tty as the controlling tty for the session. + * + * Locking: + * Takes tty_mutex() to protect tty instance + * Takes tasklist_lock internally to walk sessions + * Takes ->siglock() when updating signal->tty + */ + +static int tiocsctty(struct tty_struct *tty, int arg) +{ + int ret = 0; + if (current->signal->leader && (task_session(current) == tty->session)) + return ret; + + mutex_lock(&tty_mutex); + /* + * The process must be a session leader and + * not have a controlling tty already. + */ + if (!current->signal->leader || current->signal->tty) { + ret = -EPERM; + goto unlock; + } + + if (tty->session) { + /* + * This tty is already the controlling + * tty for another session group! + */ + if (arg == 1 && capable(CAP_SYS_ADMIN)) { + /* + * Steal it away + */ + read_lock(&tasklist_lock); + session_clear_tty(tty->session); + read_unlock(&tasklist_lock); + } else { + ret = -EPERM; + goto unlock; + } + } + proc_set_tty(current, tty); +unlock: + mutex_unlock(&tty_mutex); + return ret; +} + +/** + * tty_get_pgrp - return a ref counted pgrp pid + * @tty: tty to read + * + * Returns a refcounted instance of the pid struct for the process + * group controlling the tty. + */ + +struct pid *tty_get_pgrp(struct tty_struct *tty) +{ + unsigned long flags; + struct pid *pgrp; + + spin_lock_irqsave(&tty->ctrl_lock, flags); + pgrp = get_pid(tty->pgrp); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + + return pgrp; +} +EXPORT_SYMBOL_GPL(tty_get_pgrp); + +/** + * tiocgpgrp - get process group + * @tty: tty passed by user + * @real_tty: tty side of the tty pased by the user if a pty else the tty + * @p: returned pid + * + * Obtain the process group of the tty. If there is no process group + * return an error. + * + * Locking: none. Reference to current->signal->tty is safe. + */ + +static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) +{ + struct pid *pid; + int ret; + /* + * (tty == real_tty) is a cheap way of + * testing if the tty is NOT a master pty. + */ + if (tty == real_tty && current->signal->tty != real_tty) + return -ENOTTY; + pid = tty_get_pgrp(real_tty); + ret = put_user(pid_vnr(pid), p); + put_pid(pid); + return ret; +} + +/** + * tiocspgrp - attempt to set process group + * @tty: tty passed by user + * @real_tty: tty side device matching tty passed by user + * @p: pid pointer + * + * Set the process group of the tty to the session passed. Only + * permitted where the tty session is our session. + * + * Locking: RCU, ctrl lock + */ + +static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) +{ + struct pid *pgrp; + pid_t pgrp_nr; + int retval = tty_check_change(real_tty); + unsigned long flags; + + if (retval == -EIO) + return -ENOTTY; + if (retval) + return retval; + if (!current->signal->tty || + (current->signal->tty != real_tty) || + (real_tty->session != task_session(current))) + return -ENOTTY; + if (get_user(pgrp_nr, p)) + return -EFAULT; + if (pgrp_nr < 0) + return -EINVAL; + rcu_read_lock(); + pgrp = find_vpid(pgrp_nr); + retval = -ESRCH; + if (!pgrp) + goto out_unlock; + retval = -EPERM; + if (session_of_pgrp(pgrp) != task_session(current)) + goto out_unlock; + retval = 0; + spin_lock_irqsave(&tty->ctrl_lock, flags); + put_pid(real_tty->pgrp); + real_tty->pgrp = get_pid(pgrp); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); +out_unlock: + rcu_read_unlock(); + return retval; +} + +/** + * tiocgsid - get session id + * @tty: tty passed by user + * @real_tty: tty side of the tty pased by the user if a pty else the tty + * @p: pointer to returned session id + * + * Obtain the session id of the tty. If there is no session + * return an error. + * + * Locking: none. Reference to current->signal->tty is safe. + */ + +static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) +{ + /* + * (tty == real_tty) is a cheap way of + * testing if the tty is NOT a master pty. + */ + if (tty == real_tty && current->signal->tty != real_tty) + return -ENOTTY; + if (!real_tty->session) + return -ENOTTY; + return put_user(pid_vnr(real_tty->session), p); +} + +/** + * tiocsetd - set line discipline + * @tty: tty device + * @p: pointer to user data + * + * Set the line discipline according to user request. + * + * Locking: see tty_set_ldisc, this function is just a helper + */ + +static int tiocsetd(struct tty_struct *tty, int __user *p) +{ + int ldisc; + int ret; + + if (get_user(ldisc, p)) + return -EFAULT; + + ret = tty_set_ldisc(tty, ldisc); + + return ret; +} + +/** + * send_break - performed time break + * @tty: device to break on + * @duration: timeout in mS + * + * Perform a timed break on hardware that lacks its own driver level + * timed break functionality. + * + * Locking: + * atomic_write_lock serializes + * + */ + +static int send_break(struct tty_struct *tty, unsigned int duration) +{ + int retval; + + if (tty->ops->break_ctl == NULL) + return 0; + + if (tty->driver->flags & TTY_DRIVER_HARDWARE_BREAK) + retval = tty->ops->break_ctl(tty, duration); + else { + /* Do the work ourselves */ + if (tty_write_lock(tty, 0) < 0) + return -EINTR; + retval = tty->ops->break_ctl(tty, -1); + if (retval) + goto out; + if (!signal_pending(current)) + msleep_interruptible(duration); + retval = tty->ops->break_ctl(tty, 0); +out: + tty_write_unlock(tty); + if (signal_pending(current)) + retval = -EINTR; + } + return retval; +} + +/** + * tty_tiocmget - get modem status + * @tty: tty device + * @file: user file pointer + * @p: pointer to result + * + * Obtain the modem status bits from the tty driver if the feature + * is supported. Return -EINVAL if it is not available. + * + * Locking: none (up to the driver) + */ + +static int tty_tiocmget(struct tty_struct *tty, struct file *file, int __user *p) +{ + int retval = -EINVAL; + + if (tty->ops->tiocmget) { + retval = tty->ops->tiocmget(tty, file); + + if (retval >= 0) + retval = put_user(retval, p); + } + return retval; +} + +/** + * tty_tiocmset - set modem status + * @tty: tty device + * @file: user file pointer + * @cmd: command - clear bits, set bits or set all + * @p: pointer to desired bits + * + * Set the modem status bits from the tty driver if the feature + * is supported. Return -EINVAL if it is not available. + * + * Locking: none (up to the driver) + */ + +static int tty_tiocmset(struct tty_struct *tty, struct file *file, unsigned int cmd, + unsigned __user *p) +{ + int retval; + unsigned int set, clear, val; + + if (tty->ops->tiocmset == NULL) + return -EINVAL; + + retval = get_user(val, p); + if (retval) + return retval; + set = clear = 0; + switch (cmd) { + case TIOCMBIS: + set = val; + break; + case TIOCMBIC: + clear = val; + break; + case TIOCMSET: + set = val; + clear = ~val; + break; + } + set &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP|TIOCM_CD| + TIOCM_RI|TIOCM_DSR|TIOCM_CTS; + clear &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP|TIOCM_CD| + TIOCM_RI|TIOCM_DSR|TIOCM_CTS; + return tty->ops->tiocmset(tty, file, set, clear); +} + +struct tty_struct *tty_pair_get_tty(struct tty_struct *tty) +{ + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + tty = tty->link; + return tty; +} +EXPORT_SYMBOL(tty_pair_get_tty); + +struct tty_struct *tty_pair_get_pty(struct tty_struct *tty) +{ + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + return tty; + return tty->link; +} +EXPORT_SYMBOL(tty_pair_get_pty); + +/* + * Split this up, as gcc can choke on it otherwise.. + */ +long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct tty_struct *tty, *real_tty; + void __user *p = (void __user *)arg; + int retval; + struct tty_ldisc *ld; + struct inode *inode = file->f_dentry->d_inode; + + tty = (struct tty_struct *)file->private_data; + if (tty_paranoia_check(tty, inode, "tty_ioctl")) + return -EINVAL; + + real_tty = tty_pair_get_tty(tty); + + /* + * Factor out some common prep work + */ + switch (cmd) { + case TIOCSETD: + case TIOCSBRK: + case TIOCCBRK: + case TCSBRK: + case TCSBRKP: + retval = tty_check_change(tty); + if (retval) + return retval; + if (cmd != TIOCCBRK) { + tty_wait_until_sent(tty, 0); + if (signal_pending(current)) + return -EINTR; + } + break; + } + + /* + * Now do the stuff. + */ + switch (cmd) { + case TIOCSTI: + return tiocsti(tty, p); + case TIOCGWINSZ: + return tiocgwinsz(real_tty, p); + case TIOCSWINSZ: + return tiocswinsz(real_tty, p); + case TIOCCONS: + return real_tty != tty ? -EINVAL : tioccons(file); + case FIONBIO: + return fionbio(file, p); + case TIOCEXCL: + set_bit(TTY_EXCLUSIVE, &tty->flags); + return 0; + case TIOCNXCL: + clear_bit(TTY_EXCLUSIVE, &tty->flags); + return 0; + case TIOCNOTTY: + if (current->signal->tty != tty) + return -ENOTTY; + no_tty(); + return 0; + case TIOCSCTTY: + return tiocsctty(tty, arg); + case TIOCGPGRP: + return tiocgpgrp(tty, real_tty, p); + case TIOCSPGRP: + return tiocspgrp(tty, real_tty, p); + case TIOCGSID: + return tiocgsid(tty, real_tty, p); + case TIOCGETD: + return put_user(tty->ldisc->ops->num, (int __user *)p); + case TIOCSETD: + return tiocsetd(tty, p); + /* + * Break handling + */ + case TIOCSBRK: /* Turn break on, unconditionally */ + if (tty->ops->break_ctl) + return tty->ops->break_ctl(tty, -1); + return 0; + case TIOCCBRK: /* Turn break off, unconditionally */ + if (tty->ops->break_ctl) + return tty->ops->break_ctl(tty, 0); + return 0; + case TCSBRK: /* SVID version: non-zero arg --> no break */ + /* non-zero arg means wait for all output data + * to be sent (performed above) but don't send break. + * This is used by the tcdrain() termios function. + */ + if (!arg) + return send_break(tty, 250); + return 0; + case TCSBRKP: /* support for POSIX tcsendbreak() */ + return send_break(tty, arg ? arg*100 : 250); + + case TIOCMGET: + return tty_tiocmget(tty, file, p); + case TIOCMSET: + case TIOCMBIC: + case TIOCMBIS: + return tty_tiocmset(tty, file, cmd, p); + case TCFLSH: + switch (arg) { + case TCIFLUSH: + case TCIOFLUSH: + /* flush tty buffer and allow ldisc to process ioctl */ + tty_buffer_flush(tty); + break; + } + break; + } + if (tty->ops->ioctl) { + retval = (tty->ops->ioctl)(tty, file, cmd, arg); + if (retval != -ENOIOCTLCMD) + return retval; + } + ld = tty_ldisc_ref_wait(tty); + retval = -EINVAL; + if (ld->ops->ioctl) { + retval = ld->ops->ioctl(tty, file, cmd, arg); + if (retval == -ENOIOCTLCMD) + retval = -EINVAL; + } + tty_ldisc_deref(ld); + return retval; +} + +#ifdef CONFIG_COMPAT +static long tty_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct inode *inode = file->f_dentry->d_inode; + struct tty_struct *tty = file->private_data; + struct tty_ldisc *ld; + int retval = -ENOIOCTLCMD; + + if (tty_paranoia_check(tty, inode, "tty_ioctl")) + return -EINVAL; + + if (tty->ops->compat_ioctl) { + retval = (tty->ops->compat_ioctl)(tty, file, cmd, arg); + if (retval != -ENOIOCTLCMD) + return retval; + } + + ld = tty_ldisc_ref_wait(tty); + if (ld->ops->compat_ioctl) + retval = ld->ops->compat_ioctl(tty, file, cmd, arg); + tty_ldisc_deref(ld); + + return retval; +} +#endif + +/* + * This implements the "Secure Attention Key" --- the idea is to + * prevent trojan horses by killing all processes associated with this + * tty when the user hits the "Secure Attention Key". Required for + * super-paranoid applications --- see the Orange Book for more details. + * + * This code could be nicer; ideally it should send a HUP, wait a few + * seconds, then send a INT, and then a KILL signal. But you then + * have to coordinate with the init process, since all processes associated + * with the current tty must be dead before the new getty is allowed + * to spawn. + * + * Now, if it would be correct ;-/ The current code has a nasty hole - + * it doesn't catch files in flight. We may send the descriptor to ourselves + * via AF_UNIX socket, close it and later fetch from socket. FIXME. + * + * Nasty bug: do_SAK is being called in interrupt context. This can + * deadlock. We punt it up to process context. AKPM - 16Mar2001 + */ +void __do_SAK(struct tty_struct *tty) +{ +#ifdef TTY_SOFT_SAK + tty_hangup(tty); +#else + struct task_struct *g, *p; + struct pid *session; + int i; + struct file *filp; + struct fdtable *fdt; + + if (!tty) + return; + session = tty->session; + + tty_ldisc_flush(tty); + + tty_driver_flush_buffer(tty); + + read_lock(&tasklist_lock); + /* Kill the entire session */ + do_each_pid_task(session, PIDTYPE_SID, p) { + printk(KERN_NOTICE "SAK: killed process %d" + " (%s): task_session(p)==tty->session\n", + task_pid_nr(p), p->comm); + send_sig(SIGKILL, p, 1); + } while_each_pid_task(session, PIDTYPE_SID, p); + /* Now kill any processes that happen to have the + * tty open. + */ + do_each_thread(g, p) { + if (p->signal->tty == tty) { + printk(KERN_NOTICE "SAK: killed process %d" + " (%s): task_session(p)==tty->session\n", + task_pid_nr(p), p->comm); + send_sig(SIGKILL, p, 1); + continue; + } + task_lock(p); + if (p->files) { + /* + * We don't take a ref to the file, so we must + * hold ->file_lock instead. + */ + spin_lock(&p->files->file_lock); + fdt = files_fdtable(p->files); + for (i = 0; i < fdt->max_fds; i++) { + filp = fcheck_files(p->files, i); + if (!filp) + continue; + if (filp->f_op->read == tty_read && + filp->private_data == tty) { + printk(KERN_NOTICE "SAK: killed process %d" + " (%s): fd#%d opened to the tty\n", + task_pid_nr(p), p->comm, i); + force_sig(SIGKILL, p); + break; + } + } + spin_unlock(&p->files->file_lock); + } + task_unlock(p); + } while_each_thread(g, p); + read_unlock(&tasklist_lock); +#endif +} + +static void do_SAK_work(struct work_struct *work) +{ + struct tty_struct *tty = + container_of(work, struct tty_struct, SAK_work); + __do_SAK(tty); +} + +/* + * The tq handling here is a little racy - tty->SAK_work may already be queued. + * Fortunately we don't need to worry, because if ->SAK_work is already queued, + * the values which we write to it will be identical to the values which it + * already has. --akpm + */ +void do_SAK(struct tty_struct *tty) +{ + if (!tty) + return; + schedule_work(&tty->SAK_work); +} + +EXPORT_SYMBOL(do_SAK); + +/** + * initialize_tty_struct + * @tty: tty to initialize + * + * This subroutine initializes a tty structure that has been newly + * allocated. + * + * Locking: none - tty in question must not be exposed at this point + */ + +void initialize_tty_struct(struct tty_struct *tty, + struct tty_driver *driver, int idx) +{ + memset(tty, 0, sizeof(struct tty_struct)); + kref_init(&tty->kref); + tty->magic = TTY_MAGIC; + tty_ldisc_init(tty); + tty->session = NULL; + tty->pgrp = NULL; + tty->overrun_time = jiffies; + tty->buf.head = tty->buf.tail = NULL; + tty_buffer_init(tty); + mutex_init(&tty->termios_mutex); + mutex_init(&tty->ldisc_mutex); + init_waitqueue_head(&tty->write_wait); + init_waitqueue_head(&tty->read_wait); + INIT_WORK(&tty->hangup_work, do_tty_hangup); + mutex_init(&tty->atomic_read_lock); + mutex_init(&tty->atomic_write_lock); + mutex_init(&tty->output_lock); + mutex_init(&tty->echo_lock); + spin_lock_init(&tty->read_lock); + spin_lock_init(&tty->ctrl_lock); + INIT_LIST_HEAD(&tty->tty_files); + INIT_WORK(&tty->SAK_work, do_SAK_work); + + tty->driver = driver; + tty->ops = driver->ops; + tty->index = idx; + tty_line_name(driver, idx, tty->name); +} + +/** + * tty_put_char - write one character to a tty + * @tty: tty + * @ch: character + * + * Write one byte to the tty using the provided put_char method + * if present. Returns the number of characters successfully output. + * + * Note: the specific put_char operation in the driver layer may go + * away soon. Don't call it directly, use this method + */ + +int tty_put_char(struct tty_struct *tty, unsigned char ch) +{ + if (tty->ops->put_char) + return tty->ops->put_char(tty, ch); + return tty->ops->write(tty, &ch, 1); +} +EXPORT_SYMBOL_GPL(tty_put_char); + +struct class *tty_class; + +/** + * tty_register_device - register a tty device + * @driver: the tty driver that describes the tty device + * @index: the index in the tty driver for this tty device + * @device: a struct device that is associated with this tty device. + * This field is optional, if there is no known struct device + * for this tty device it can be set to NULL safely. + * + * Returns a pointer to the struct device for this tty device + * (or ERR_PTR(-EFOO) on error). + * + * This call is required to be made to register an individual tty device + * if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set. If + * that bit is not set, this function should not be called by a tty + * driver. + * + * Locking: ?? + */ + +struct device *tty_register_device(struct tty_driver *driver, unsigned index, + struct device *device) +{ + char name[64]; + dev_t dev = MKDEV(driver->major, driver->minor_start) + index; + + if (index >= driver->num) { + printk(KERN_ERR "Attempt to register invalid tty line number " + " (%d).\n", index); + return ERR_PTR(-EINVAL); + } + + if (driver->type == TTY_DRIVER_TYPE_PTY) + pty_line_name(driver, index, name); + else + tty_line_name(driver, index, name); + + return device_create(tty_class, device, dev, NULL, name); +} +EXPORT_SYMBOL(tty_register_device); + +/** + * tty_unregister_device - unregister a tty device + * @driver: the tty driver that describes the tty device + * @index: the index in the tty driver for this tty device + * + * If a tty device is registered with a call to tty_register_device() then + * this function must be called when the tty device is gone. + * + * Locking: ?? + */ + +void tty_unregister_device(struct tty_driver *driver, unsigned index) +{ + device_destroy(tty_class, + MKDEV(driver->major, driver->minor_start) + index); +} +EXPORT_SYMBOL(tty_unregister_device); + +struct tty_driver *alloc_tty_driver(int lines) +{ + struct tty_driver *driver; + + driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL); + if (driver) { + kref_init(&driver->kref); + driver->magic = TTY_DRIVER_MAGIC; + driver->num = lines; + /* later we'll move allocation of tables here */ + } + return driver; +} +EXPORT_SYMBOL(alloc_tty_driver); + +static void destruct_tty_driver(struct kref *kref) +{ + struct tty_driver *driver = container_of(kref, struct tty_driver, kref); + int i; + struct ktermios *tp; + void *p; + + if (driver->flags & TTY_DRIVER_INSTALLED) { + /* + * Free the termios and termios_locked structures because + * we don't want to get memory leaks when modular tty + * drivers are removed from the kernel. + */ + for (i = 0; i < driver->num; i++) { + tp = driver->termios[i]; + if (tp) { + driver->termios[i] = NULL; + kfree(tp); + } + if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) + tty_unregister_device(driver, i); + } + p = driver->ttys; + proc_tty_unregister_driver(driver); + driver->ttys = NULL; + driver->termios = NULL; + kfree(p); + cdev_del(&driver->cdev); + } + kfree(driver); +} + +void tty_driver_kref_put(struct tty_driver *driver) +{ + kref_put(&driver->kref, destruct_tty_driver); +} +EXPORT_SYMBOL(tty_driver_kref_put); + +void tty_set_operations(struct tty_driver *driver, + const struct tty_operations *op) +{ + driver->ops = op; +}; +EXPORT_SYMBOL(tty_set_operations); + +void put_tty_driver(struct tty_driver *d) +{ + tty_driver_kref_put(d); +} +EXPORT_SYMBOL(put_tty_driver); + +/* + * Called by a tty driver to register itself. + */ +int tty_register_driver(struct tty_driver *driver) +{ + int error; + int i; + dev_t dev; + void **p = NULL; + + if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) { + p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL); + if (!p) + return -ENOMEM; + } + + if (!driver->major) { + error = alloc_chrdev_region(&dev, driver->minor_start, + driver->num, driver->name); + if (!error) { + driver->major = MAJOR(dev); + driver->minor_start = MINOR(dev); + } + } else { + dev = MKDEV(driver->major, driver->minor_start); + error = register_chrdev_region(dev, driver->num, driver->name); + } + if (error < 0) { + kfree(p); + return error; + } + + if (p) { + driver->ttys = (struct tty_struct **)p; + driver->termios = (struct ktermios **)(p + driver->num); + } else { + driver->ttys = NULL; + driver->termios = NULL; + } + + cdev_init(&driver->cdev, &tty_fops); + driver->cdev.owner = driver->owner; + error = cdev_add(&driver->cdev, dev, driver->num); + if (error) { + unregister_chrdev_region(dev, driver->num); + driver->ttys = NULL; + driver->termios = NULL; + kfree(p); + return error; + } + + mutex_lock(&tty_mutex); + list_add(&driver->tty_drivers, &tty_drivers); + mutex_unlock(&tty_mutex); + + if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { + for (i = 0; i < driver->num; i++) + tty_register_device(driver, i, NULL); + } + proc_tty_register_driver(driver); + driver->flags |= TTY_DRIVER_INSTALLED; + return 0; +} + +EXPORT_SYMBOL(tty_register_driver); + +/* + * Called by a tty driver to unregister itself. + */ +int tty_unregister_driver(struct tty_driver *driver) +{ +#if 0 + /* FIXME */ + if (driver->refcount) + return -EBUSY; +#endif + unregister_chrdev_region(MKDEV(driver->major, driver->minor_start), + driver->num); + mutex_lock(&tty_mutex); + list_del(&driver->tty_drivers); + mutex_unlock(&tty_mutex); + return 0; +} + +EXPORT_SYMBOL(tty_unregister_driver); + +dev_t tty_devnum(struct tty_struct *tty) +{ + return MKDEV(tty->driver->major, tty->driver->minor_start) + tty->index; +} +EXPORT_SYMBOL(tty_devnum); + +void proc_clear_tty(struct task_struct *p) +{ + unsigned long flags; + struct tty_struct *tty; + spin_lock_irqsave(&p->sighand->siglock, flags); + tty = p->signal->tty; + p->signal->tty = NULL; + spin_unlock_irqrestore(&p->sighand->siglock, flags); + tty_kref_put(tty); +} + +/* Called under the sighand lock */ + +static void __proc_set_tty(struct task_struct *tsk, struct tty_struct *tty) +{ + if (tty) { + unsigned long flags; + /* We should not have a session or pgrp to put here but.... */ + spin_lock_irqsave(&tty->ctrl_lock, flags); + put_pid(tty->session); + put_pid(tty->pgrp); + tty->pgrp = get_pid(task_pgrp(tsk)); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); + tty->session = get_pid(task_session(tsk)); + if (tsk->signal->tty) { + printk(KERN_DEBUG "tty not NULL!!\n"); + tty_kref_put(tsk->signal->tty); + } + } + put_pid(tsk->signal->tty_old_pgrp); + tsk->signal->tty = tty_kref_get(tty); + tsk->signal->tty_old_pgrp = NULL; +} + +static void proc_set_tty(struct task_struct *tsk, struct tty_struct *tty) +{ + spin_lock_irq(&tsk->sighand->siglock); + __proc_set_tty(tsk, tty); + spin_unlock_irq(&tsk->sighand->siglock); +} + +struct tty_struct *get_current_tty(void) +{ + struct tty_struct *tty; + unsigned long flags; + + spin_lock_irqsave(¤t->sighand->siglock, flags); + tty = tty_kref_get(current->signal->tty); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + return tty; +} +EXPORT_SYMBOL_GPL(get_current_tty); + +void tty_default_fops(struct file_operations *fops) +{ + *fops = tty_fops; +} + +/* + * Initialize the console device. This is called *early*, so + * we can't necessarily depend on lots of kernel help here. + * Just do some early initializations, and do the complex setup + * later. + */ +void __init console_init(void) +{ + initcall_t *call; + + /* Setup the default TTY line discipline. */ + tty_ldisc_begin(); + + /* + * set up the console device so that later boot sequences can + * inform about problems etc.. + */ + call = __con_initcall_start; + while (call < __con_initcall_end) { + (*call)(); + call++; + } +} + +static char *tty_devnode(struct device *dev, mode_t *mode) +{ + if (!mode) + return NULL; + if (dev->devt == MKDEV(TTYAUX_MAJOR, 0) || + dev->devt == MKDEV(TTYAUX_MAJOR, 2)) + *mode = 0666; + return NULL; +} + +static int __init tty_class_init(void) +{ + tty_class = class_create(THIS_MODULE, "tty"); + if (IS_ERR(tty_class)) + return PTR_ERR(tty_class); + tty_class->devnode = tty_devnode; + return 0; +} + +postcore_initcall(tty_class_init); + +/* 3/2004 jmc: why do these devices exist? */ + +static struct cdev tty_cdev, console_cdev; + +/* + * Ok, now we can initialize the rest of the tty devices and can count + * on memory allocations, interrupts etc.. + */ +int __init tty_init(void) +{ + cdev_init(&tty_cdev, &tty_fops); + if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) || + register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0) + panic("Couldn't register /dev/tty driver\n"); + device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, + "tty"); + + cdev_init(&console_cdev, &console_fops); + if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) || + register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0) + panic("Couldn't register /dev/console driver\n"); + device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL, + "console"); + +#ifdef CONFIG_VT + vty_init(&console_fops); +#endif + return 0; +} + diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 0a5bea9e358..ff15497e919 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -41,7 +41,11 @@ static struct cpufreq_driver *cpufreq_driver; static DEFINE_PER_CPU(struct cpufreq_policy *, cpufreq_cpu_data); #ifdef CONFIG_HOTPLUG_CPU /* This one keeps track of the previously set governor of a removed CPU */ -static DEFINE_PER_CPU(char[CPUFREQ_NAME_LEN], cpufreq_cpu_governor); +struct cpufreq_cpu_save_data { + char gov[CPUFREQ_NAME_LEN]; + unsigned int max, min; +}; +static DEFINE_PER_CPU(struct cpufreq_cpu_save_data, cpufreq_policy_save); #endif static DEFINE_SPINLOCK(cpufreq_driver_lock); @@ -68,7 +72,7 @@ static DEFINE_PER_CPU(int, cpufreq_policy_cpu); static DEFINE_PER_CPU(struct rw_semaphore, cpu_policy_rwsem); #define lock_policy_rwsem(mode, cpu) \ -static int lock_policy_rwsem_##mode \ +int lock_policy_rwsem_##mode \ (int cpu) \ { \ int policy_cpu = per_cpu(cpufreq_policy_cpu, cpu); \ @@ -93,7 +97,7 @@ static void unlock_policy_rwsem_read(int cpu) up_read(&per_cpu(cpu_policy_rwsem, policy_cpu)); } -static void unlock_policy_rwsem_write(int cpu) +void unlock_policy_rwsem_write(int cpu) { int policy_cpu = per_cpu(cpufreq_policy_cpu, cpu); BUG_ON(policy_cpu == -1); @@ -688,12 +692,22 @@ static int cpufreq_add_dev_policy(unsigned int cpu, #ifdef CONFIG_HOTPLUG_CPU struct cpufreq_governor *gov; - gov = __find_governor(per_cpu(cpufreq_cpu_governor, cpu)); + gov = __find_governor(per_cpu(cpufreq_policy_save, cpu).gov); if (gov) { policy->governor = gov; pr_debug("Restoring governor %s for cpu %d\n", policy->governor->name, cpu); } + if (per_cpu(cpufreq_policy_save, cpu).min) { + policy->min = per_cpu(cpufreq_policy_save, cpu).min; + policy->user_policy.min = policy->min; + } + if (per_cpu(cpufreq_policy_save, cpu).max) { + policy->max = per_cpu(cpufreq_policy_save, cpu).max; + policy->user_policy.max = policy->max; + } + pr_debug("Restoring CPU%d min %d and max %d\n", + cpu, policy->min, policy->max); #endif for_each_cpu(j, policy->cpus) { @@ -1043,8 +1057,12 @@ static int __cpufreq_remove_dev(struct sys_device *sys_dev) #ifdef CONFIG_SMP #ifdef CONFIG_HOTPLUG_CPU - strncpy(per_cpu(cpufreq_cpu_governor, cpu), data->governor->name, + strncpy(per_cpu(cpufreq_policy_save, cpu).gov, data->governor->name, CPUFREQ_NAME_LEN); + per_cpu(cpufreq_policy_save, cpu).min = data->min; + per_cpu(cpufreq_policy_save, cpu).max = data->max; + pr_debug("Saving CPU%d policy min %d and max %d\n", + cpu, data->min, data->max); #endif /* if we have other CPUs still registered, we need to unlink them, @@ -1068,8 +1086,12 @@ static int __cpufreq_remove_dev(struct sys_device *sys_dev) continue; pr_debug("removing link for cpu %u\n", j); #ifdef CONFIG_HOTPLUG_CPU - strncpy(per_cpu(cpufreq_cpu_governor, j), + strncpy(per_cpu(cpufreq_policy_save, j).gov, data->governor->name, CPUFREQ_NAME_LEN); + per_cpu(cpufreq_policy_save, j).min = data->min; + per_cpu(cpufreq_policy_save, j).max = data->max; + pr_debug("Saving CPU%d policy min %d and max %d\n", + j, data->min, data->max); #endif cpu_sys_dev = get_cpu_sysdev(j); kobj = &cpu_sys_dev->kobj; @@ -1555,8 +1577,11 @@ void cpufreq_unregister_governor(struct cpufreq_governor *governor) for_each_present_cpu(cpu) { if (cpu_online(cpu)) continue; - if (!strcmp(per_cpu(cpufreq_cpu_governor, cpu), governor->name)) - strcpy(per_cpu(cpufreq_cpu_governor, cpu), "\0"); + if (!strcmp(per_cpu(cpufreq_policy_save, cpu).gov, + governor->name)) + strcpy(per_cpu(cpufreq_policy_save, cpu).gov, "\0"); + per_cpu(cpufreq_policy_save, cpu).min = 0; + per_cpu(cpufreq_policy_save, cpu).max = 0; } #endif diff --git a/drivers/cpufreq/cpufreq_ondemand.c b/drivers/cpufreq/cpufreq_ondemand.c index 891360edecd..a175ae7ae37 100644 --- a/drivers/cpufreq/cpufreq_ondemand.c +++ b/drivers/cpufreq/cpufreq_ondemand.c @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include /* * dbs is used in this file as a shortform for demandbased switching @@ -37,6 +40,7 @@ #define MICRO_FREQUENCY_MIN_SAMPLE_RATE (10000) #define MIN_FREQUENCY_UP_THRESHOLD (11) #define MAX_FREQUENCY_UP_THRESHOLD (100) +#define MIN_FREQUENCY_DOWN_DIFFERENTIAL (1) /* * The polling frequency of this governor depends on the capability of @@ -103,6 +107,10 @@ static unsigned int dbs_enable; /* number of CPUs using this policy */ */ static DEFINE_MUTEX(dbs_mutex); +static struct workqueue_struct *input_wq; + +static DEFINE_PER_CPU(struct work_struct, dbs_refresh_work); + static struct dbs_tuners { unsigned int sampling_rate; unsigned int up_threshold; @@ -252,6 +260,7 @@ static ssize_t show_##file_name \ show_one(sampling_rate, sampling_rate); show_one(io_is_busy, io_is_busy); show_one(up_threshold, up_threshold); +show_one(down_differential, down_differential); show_one(sampling_down_factor, sampling_down_factor); show_one(ignore_nice_load, ignore_nice); show_one(powersave_bias, powersave_bias); @@ -296,6 +305,23 @@ static ssize_t store_up_threshold(struct kobject *a, struct attribute *b, return count; } +static ssize_t store_down_differential(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + + if (ret != 1 || input >= dbs_tuners_ins.up_threshold || + input < MIN_FREQUENCY_DOWN_DIFFERENTIAL) { + return -EINVAL; + } + + dbs_tuners_ins.down_differential = input; + + return count; +} + static ssize_t store_sampling_down_factor(struct kobject *a, struct attribute *b, const char *buf, size_t count) { @@ -370,6 +396,7 @@ static ssize_t store_powersave_bias(struct kobject *a, struct attribute *b, define_one_global_rw(sampling_rate); define_one_global_rw(io_is_busy); define_one_global_rw(up_threshold); +define_one_global_rw(down_differential); define_one_global_rw(sampling_down_factor); define_one_global_rw(ignore_nice_load); define_one_global_rw(powersave_bias); @@ -378,6 +405,7 @@ static struct attribute *dbs_attributes[] = { &sampling_rate_min.attr, &sampling_rate.attr, &up_threshold.attr, + &down_differential.attr, &sampling_down_factor.attr, &ignore_nice_load.attr, &powersave_bias.attr, @@ -619,6 +647,89 @@ static int should_io_be_busy(void) return 0; } +static void dbs_refresh_callback(struct work_struct *unused) +{ + struct cpufreq_policy *policy; + struct cpu_dbs_info_s *this_dbs_info; + unsigned int cpu = smp_processor_id(); + + if (lock_policy_rwsem_write(cpu) < 0) + return; + + this_dbs_info = &per_cpu(od_cpu_dbs_info, cpu); + policy = this_dbs_info->cur_policy; + + if (policy->cur < policy->max) { + policy->cur = policy->max; + + __cpufreq_driver_target(policy, policy->max, + CPUFREQ_RELATION_L); + this_dbs_info->prev_cpu_idle = get_cpu_idle_time(cpu, + &this_dbs_info->prev_cpu_wall); + } + unlock_policy_rwsem_write(cpu); +} + +static void dbs_input_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + int i; + + for_each_online_cpu(i) { + queue_work_on(i, input_wq, &per_cpu(dbs_refresh_work, i)); + } +} + +static int dbs_input_connect(struct input_handler *handler, + struct input_dev *dev, const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "cpufreq"; + + error = input_register_handle(handle); + if (error) + goto err2; + + error = input_open_device(handle); + if (error) + goto err1; + + return 0; +err1: + input_unregister_handle(handle); +err2: + kfree(handle); + return error; +} + +static void dbs_input_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id dbs_ids[] = { + { .driver_info = 1 }, + { }, +}; + +static struct input_handler dbs_input_handler = { + .event = dbs_input_event, + .connect = dbs_input_connect, + .disconnect = dbs_input_disconnect, + .name = "cpufreq_ond", + .id_table = dbs_ids, +}; + static int cpufreq_governor_dbs(struct cpufreq_policy *policy, unsigned int event) { @@ -678,6 +789,8 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy, latency * LATENCY_MULTIPLIER); dbs_tuners_ins.io_is_busy = should_io_be_busy(); } + if (!cpu) + rc = input_register_handler(&dbs_input_handler); mutex_unlock(&dbs_mutex); mutex_init(&this_dbs_info->timer_mutex); @@ -690,6 +803,8 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy, mutex_lock(&dbs_mutex); mutex_destroy(&this_dbs_info->timer_mutex); dbs_enable--; + if (!cpu) + input_unregister_handler(&dbs_input_handler); mutex_unlock(&dbs_mutex); if (!dbs_enable) sysfs_remove_group(cpufreq_global_kobject, @@ -715,6 +830,7 @@ static int __init cpufreq_gov_dbs_init(void) { cputime64_t wall; u64 idle_time; + unsigned int i; int cpu = get_cpu(); idle_time = get_cpu_idle_time_us(cpu, &wall); @@ -736,12 +852,22 @@ static int __init cpufreq_gov_dbs_init(void) MIN_SAMPLING_RATE_RATIO * jiffies_to_usecs(10); } + input_wq = create_workqueue("iewq"); + if (!input_wq) { + printk(KERN_ERR "Failed to create iewq workqueue\n"); + return -EFAULT; + } + for_each_possible_cpu(i) { + INIT_WORK(&per_cpu(dbs_refresh_work, i), dbs_refresh_callback); + } + return cpufreq_register_governor(&cpufreq_gov_ondemand); } static void __exit cpufreq_gov_dbs_exit(void) { cpufreq_unregister_governor(&cpufreq_gov_ondemand); + destroy_workqueue(input_wq); } diff --git a/drivers/cpufreq/cpufreq_stats.c b/drivers/cpufreq/cpufreq_stats.c index c315ec9d568..f6fba49450e 100644 --- a/drivers/cpufreq/cpufreq_stats.c +++ b/drivers/cpufreq/cpufreq_stats.c @@ -350,6 +350,7 @@ static int __cpuinit cpufreq_stat_cpu_callback(struct notifier_block *nfb, cpufreq_update_policy(cpu); break; case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: cpufreq_stats_free_sysfs(cpu); break; case CPU_DEAD: diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig index e0b25de1e33..8850516ec21 100644 --- a/drivers/crypto/Kconfig +++ b/drivers/crypto/Kconfig @@ -292,4 +292,46 @@ config CRYPTO_DEV_S5P Select this to offload Samsung S5PV210 or S5PC110 from AES algorithms execution. +config CRYPTO_DEV_QCE40 + bool + +config CRYPTO_DEV_QCRYPTO + tristate "Qualcomm Crypto accelerator" + select CRYPTO_DES + select CRYPTO_ALGAPI + select CRYPTO_AUTHENC + select CRYPTO_BLKCIPHER + default n + help + This driver supports Qualcomm crypto acceleration. + To compile this driver as a module, choose M here: the + module will be called qcrypto. + +config CRYPTO_DEV_QCE + tristate "Qualcomm Crypto Engine (QCE) module" + select CRYPTO_DEV_QCE40 if ARCH_MSM8960 + default n + help + This driver supports Qualcomm Crypto Engine in MSM7x30 MSM8660 + MSM8x55 and MSM8960 + To compile this driver as a module, choose M here: the + For MSM7x30 MSM8660 and MSM8x55 the module is called qce + For MSM8960 the module is called qce40 + +config CRYPTO_DEV_QCEDEV + tristate "QCEDEV Interface to CE module" + default n + help + This driver supports Qualcomm QCEDEV Crypto in MSM7x30 MSM8660, MSM8960. + This exposes the interface to the QCE hardware accelerator via IOCTLs + To compile this driver as a module, choose M here: the + module will be called qcedev. + +config CRYPTO_DEV_OTA_CRYPTO + tristate "OTA Crypto module" + help + This driver supports Qualcomm OTA Crypto in the FSM9xxx. + To compile this driver as a module, choose M here: the + module will be called ota_crypto. + endif # CRYPTO_HW diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile index 53ea5015531..549a7b2947f 100644 --- a/drivers/crypto/Makefile +++ b/drivers/crypto/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_CRYPTO_DEV_TALITOS) += talitos.o obj-$(CONFIG_CRYPTO_DEV_FSL_CAAM) += caam/ obj-$(CONFIG_CRYPTO_DEV_IXP4XX) += ixp4xx_crypto.o obj-$(CONFIG_CRYPTO_DEV_PPC4XX) += amcc/ +obj-$(CONFIG_CRYPTO_DEV_QCE) += msm/ obj-$(CONFIG_CRYPTO_DEV_OMAP_SHAM) += omap-sham.o obj-$(CONFIG_CRYPTO_DEV_OMAP_AES) += omap-aes.o obj-$(CONFIG_CRYPTO_DEV_PICOXCELL) += picoxcell_crypto.o diff --git a/drivers/crypto/msm/Makefile b/drivers/crypto/msm/Makefile new file mode 100644 index 00000000000..61406b9531c --- /dev/null +++ b/drivers/crypto/msm/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_CRYPTO_DEV_QCEDEV) += qcedev.o +ifeq ($(CONFIG_CRYPTO_DEV_QCE40), y) + obj-$(CONFIG_CRYPTO_DEV_QCE) += qce40.o +else + obj-$(CONFIG_CRYPTO_DEV_QCE) += qce.o +endif +obj-$(CONFIG_CRYPTO_DEV_QCRYPTO) += qcrypto.o +obj-$(CONFIG_CRYPTO_DEV_OTA_CRYPTO) += ota_crypto.o diff --git a/drivers/crypto/msm/inc/qce.h b/drivers/crypto/msm/inc/qce.h new file mode 100644 index 00000000000..7230036d04b --- /dev/null +++ b/drivers/crypto/msm/inc/qce.h @@ -0,0 +1,160 @@ +/* Qualcomm Crypto Engine driver API + * + * Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#ifndef __CRYPTO_MSM_QCE_H +#define __CRYPTO_MSM_QCE_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* SHA digest size in bytes */ +#define SHA256_DIGESTSIZE 32 +#define SHA1_DIGESTSIZE 20 + +/* key size in bytes */ +#define HMAC_KEY_SIZE (SHA1_DIGESTSIZE) /* hmac-sha1 */ +#define SHA_HMAC_KEY_SIZE 64 +#define DES_KEY_SIZE 8 +#define TRIPLE_DES_KEY_SIZE 24 +#define AES128_KEY_SIZE 16 +#define AES192_KEY_SIZE 24 +#define AES256_KEY_SIZE 32 +#define MAX_CIPHER_KEY_SIZE AES256_KEY_SIZE + +/* iv length in bytes */ +#define AES_IV_LENGTH 16 +#define DES_IV_LENGTH 8 +#define MAX_IV_LENGTH AES_IV_LENGTH + +/* Maximum number of bytes per transfer */ +#define QCE_MAX_OPER_DATA 0x8000 + +/* Maximum Nonce bytes */ +#define MAX_NONCE 16 + +typedef void (*qce_comp_func_ptr_t)(void *areq, + unsigned char *icv, unsigned char *iv, int ret); + +/* Cipher algorithms supported */ +enum qce_cipher_alg_enum { + CIPHER_ALG_DES = 0, + CIPHER_ALG_3DES = 1, + CIPHER_ALG_AES = 2, + CIPHER_ALG_LAST +}; + +/* Hash and hmac algorithms supported */ +enum qce_hash_alg_enum { + QCE_HASH_SHA1 = 0, + QCE_HASH_SHA256 = 1, + QCE_HASH_SHA1_HMAC = 2, + QCE_HASH_SHA256_HMAC = 3, + QCE_HASH_AES_CMAC = 4, + QCE_HASH_LAST +}; + +/* Cipher encryption/decryption operations */ +enum qce_cipher_dir_enum { + QCE_ENCRYPT = 0, + QCE_DECRYPT = 1, + QCE_CIPHER_DIR_LAST +}; + +/* Cipher algorithms modes */ +enum qce_cipher_mode_enum { + QCE_MODE_CBC = 0, + QCE_MODE_ECB = 1, + QCE_MODE_CTR = 2, + QCE_MODE_XTS = 3, + QCE_MODE_CCM = 4, + QCE_CIPHER_MODE_LAST +}; + +/* Cipher operation type */ +enum qce_req_op_enum { + QCE_REQ_ABLK_CIPHER = 0, + QCE_REQ_ABLK_CIPHER_NO_KEY = 1, + QCE_REQ_AEAD = 2, + QCE_REQ_LAST +}; + +/* Algorithms/features supported in CE HW engine */ +struct ce_hw_support { + bool sha1_hmac_20; /* Supports 20 bytes of HMAC key*/ + bool sha1_hmac; /* supports max HMAC key of 64 bytes*/ + bool sha256_hmac; /* supports max HMAC key of 64 bytes*/ + bool sha_hmac; /* supports SHA1 and SHA256 MAX HMAC key of 64 bytes*/ + bool cmac; + bool aes_key_192; + bool aes_xts; + bool aes_ccm; + bool ota; +}; + +/* Sha operation parameters */ +struct qce_sha_req { + qce_comp_func_ptr_t qce_cb; /* call back */ + enum qce_hash_alg_enum alg; /* sha algorithm */ + unsigned char *digest; /* sha digest */ + struct scatterlist *src; /* pointer to scatter list entry */ + uint32_t auth_data[4]; /* byte count */ + unsigned char *authkey; /* auth key */ + unsigned int authklen; /* auth key length */ + bool first_blk; /* first block indicator */ + bool last_blk; /* last block indicator */ + unsigned int size; /* data length in bytes */ + void *areq; +}; + +struct qce_req { + enum qce_req_op_enum op; /* operation type */ + qce_comp_func_ptr_t qce_cb; /* call back */ + void *areq; + enum qce_cipher_alg_enum alg; /* cipher algorithms*/ + enum qce_cipher_dir_enum dir; /* encryption? decryption? */ + enum qce_cipher_mode_enum mode; /* algorithm mode */ + unsigned char *authkey; /* authentication key */ + unsigned int authklen; /* authentication key kength */ + unsigned int authsize; /* authentication key kength */ + unsigned char nonce[MAX_NONCE];/* nonce for ccm mode */ + unsigned char *assoc; /* Ptr to formatted associated data */ + unsigned int assoclen; /* Formatted associated data length */ + struct scatterlist *asg; /* Formatted associated data sg */ + unsigned char *enckey; /* cipher key */ + unsigned int encklen; /* cipher key length */ + unsigned char *iv; /* initialization vector */ + unsigned int ivsize; /* initialization vector size*/ + unsigned int cryptlen; /* data length */ + unsigned int use_pmem; /* is source of data PMEM allocated? */ + struct qcedev_pmem_info *pmem; /* pointer to pmem_info structure*/ +}; + +void *qce_open(struct platform_device *pdev, int *rc); +int qce_close(void *handle); +int qce_aead_req(void *handle, struct qce_req *req); +int qce_ablk_cipher_req(void *handle, struct qce_req *req); +int qce_hw_support(void *handle, struct ce_hw_support *support); +int qce_process_sha_req(void *handle, struct qce_sha_req *s_req); + +#endif /* __CRYPTO_MSM_QCE_H */ diff --git a/drivers/crypto/msm/inc/qce_ota.h b/drivers/crypto/msm/inc/qce_ota.h new file mode 100644 index 00000000000..f21bd0bfef7 --- /dev/null +++ b/drivers/crypto/msm/inc/qce_ota.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* Qualcomm Crypto Engine driver OTA APIi */ + +#ifndef __CRYPTO_MSM_QCE_OTA_H +#define __CRYPTO_MSM_QCE_OTA_H + +#include +#include +#include + + +int qce_f8_req(void *handle, struct qce_f8_req *req, + void *cookie, qce_comp_func_ptr_t qce_cb); +int qce_f8_multi_pkt_req(void *handle, struct qce_f8_multi_pkt_req *req, + void *cookie, qce_comp_func_ptr_t qce_cb); +int qce_f9_req(void *handle, struct qce_f9_req *req, + void *cookie, qce_comp_func_ptr_t qce_cb); + +#endif /* __CRYPTO_MSM_QCE_OTA_H */ diff --git a/drivers/crypto/msm/inc/qcedev.h b/drivers/crypto/msm/inc/qcedev.h new file mode 100644 index 00000000000..893251f5310 --- /dev/null +++ b/drivers/crypto/msm/inc/qcedev.h @@ -0,0 +1,267 @@ +/* Qualcomm Crypto Engine driver QCEDEV API + * + * Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __QCEDEV__H +#define __QCEDEV__H + +#include +#include + +#define QCEDEV_MAX_SHA_BLOCK_SIZE 64 +#define QCEDEV_MAX_BEARER 31 +#define QCEDEV_MAX_KEY_SIZE 64 +#define QCEDEV_MAX_IV_SIZE 32 + +#define QCEDEV_MAX_BUFFERS 16 +#define QCEDEV_MAX_SHA_DIGEST 32 + +#define QCEDEV_USE_PMEM 1 +#define QCEDEV_NO_PMEM 0 + +#define QCEDEV_AES_KEY_128 16 +#define QCEDEV_AES_KEY_192 24 +#define QCEDEV_AES_KEY_256 32 +/** +*qcedev_oper_enum: Operation types +* @QCEDEV_OPER_ENC: Encrypt +* @QCEDEV_OPER_DEC: Decrypt +* @QCEDEV_OPER_ENC_NO_KEY: Encrypt. Do not need key to be specified by +* user. Key already set by an external processor. +* @QCEDEV_OPER_DEC_NO_KEY: Decrypt. Do not need the key to be specified by +* user. Key already set by an external processor. +*/ +enum qcedev_oper_enum { + QCEDEV_OPER_DEC = 0, + QCEDEV_OPER_ENC = 1, + QCEDEV_OPER_DEC_NO_KEY = 2, + QCEDEV_OPER_ENC_NO_KEY = 3, + QCEDEV_OPER_LAST +}; + +/** +*qcedev_oper_enum: Cipher algorithm types +* @QCEDEV_ALG_DES: DES +* @QCEDEV_ALG_3DES: 3DES +* @QCEDEV_ALG_AES: AES +*/ +enum qcedev_cipher_alg_enum { + QCEDEV_ALG_DES = 0, + QCEDEV_ALG_3DES = 1, + QCEDEV_ALG_AES = 2, + QCEDEV_ALG_LAST +}; + +/** +*qcedev_cipher_mode_enum : AES mode +* @QCEDEV_AES_MODE_CBC: CBC +* @QCEDEV_AES_MODE_ECB: ECB +* @QCEDEV_AES_MODE_CTR: CTR +* @QCEDEV_AES_MODE_XTS: XTS +* @QCEDEV_AES_MODE_CCM: CCM +* @QCEDEV_DES_MODE_CBC: CBC +* @QCEDEV_DES_MODE_ECB: ECB +*/ +enum qcedev_cipher_mode_enum { + QCEDEV_AES_MODE_CBC = 0, + QCEDEV_AES_MODE_ECB = 1, + QCEDEV_AES_MODE_CTR = 2, + QCEDEV_AES_MODE_XTS = 3, + QCEDEV_AES_MODE_CCM = 4, + QCEDEV_DES_MODE_CBC = 5, + QCEDEV_DES_MODE_ECB = 6, + QCEDEV_AES_DES_MODE_LAST +}; + +/** +*enum qcedev_sha_alg_enum : Secure Hashing Algorithm +* @QCEDEV_ALG_SHA1: Digest returned: 20 bytes (160 bits) +* @QCEDEV_ALG_SHA256: Digest returned: 32 bytes (256 bit) +* @QCEDEV_ALG_SHA1_HMAC: HMAC returned 20 bytes (160 bits) +* @QCEDEV_ALG_SHA256_HMAC: HMAC returned 32 bytes (256 bit) +* @QCEDEV_ALG_AES_CMAC: Configurable MAC size +*/ +enum qcedev_sha_alg_enum { + QCEDEV_ALG_SHA1 = 0, + QCEDEV_ALG_SHA256 = 1, + QCEDEV_ALG_SHA1_HMAC = 2, + QCEDEV_ALG_SHA256_HMAC = 3, + QCEDEV_ALG_AES_CMAC = 4, + QCEDEV_ALG_SHA_ALG_LAST +}; + +/** +* struct buf_info - Buffer information +* @offset: Offset from the base address of the buffer +* (Used when buffer is allocated using PMEM) +* @vaddr: Virtual buffer address pointer +* @len: Size of the buffer +*/ +struct buf_info { + union{ + uint32_t offset; + uint8_t *vaddr; + }; + uint32_t len; +}; + +/** +* struct qcedev_vbuf_info - Source and destination Buffer information +* @src: Array of buf_info for input/source +* @dst: Array of buf_info for output/destination +*/ +struct qcedev_vbuf_info { + struct buf_info src[QCEDEV_MAX_BUFFERS]; + struct buf_info dst[QCEDEV_MAX_BUFFERS]; +}; + +struct qcedev_sha_ctxt{ + uint32_t auth_data[4]; + uint8_t digest[QCEDEV_MAX_SHA_DIGEST]; + uint32_t diglen; + uint8_t trailing_buf[64]; + uint32_t trailing_buf_len; + uint8_t first_blk; + uint8_t last_blk; + uint8_t authkey[QCEDEV_MAX_SHA_BLOCK_SIZE]; +}; + +/** +* struct qcedev_pmem_info - Stores PMEM buffer information +* @fd_src: Handle to /dev/adsp_pmem used to allocate +* memory for input/src buffer +* @src: Array of buf_info for input/source +* @fd_dst: Handle to /dev/adsp_pmem used to allocate +* memory for output/dst buffer +* @dst: Array of buf_info for output/destination +* @pmem_src_offset: The offset from input/src buffer +* (allocated by PMEM) +*/ +struct qcedev_pmem_info{ + int fd_src; + struct buf_info src[QCEDEV_MAX_BUFFERS]; + int fd_dst; + struct buf_info dst[QCEDEV_MAX_BUFFERS]; +}; + +/** +* struct qcedev_cipher_op_req - Holds the ciphering request information +* @use_pmem (IN): Flag to indicate if buffer source is PMEM +* QCEDEV_USE_PMEM/QCEDEV_NO_PMEM +* @pmem (IN): Stores PMEM buffer information. +* Refer struct qcedev_pmem_info +* @vbuf (IN/OUT): Stores Source and destination Buffer information +* Refer to struct qcedev_vbuf_info +* @data_len (IN): Total Length of input/src and output/dst in bytes +* @in_place_op (IN): Indicates whether the operation is inplace where +* source == destination +* When using PMEM allocated memory, must set this to 1 +* @enckey (IN): 128 bits of confidentiality key +* enckey[0] bit 127-120, enckey[1] bit 119-112,.. +* enckey[15] bit 7-0 +* @encklen (IN): Length of the encryption key(set to 128 bits/16 +* bytes in the driver) +* @iv (IN/OUT): Initialisation vector data +* This is updated by the driver, incremented by +* number of blocks encrypted/decrypted. +* @ivlen (IN): Length of the IV +* @byteoffset (IN): Offset in the Cipher BLOCK (applicable and to be set +* for AES-128 CTR mode only) +* @alg (IN): Type of ciphering algorithm: AES/DES/3DES +* @mode (IN): Mode use when using AES algorithm: ECB/CBC/CTR +* Apllicabel when using AES algorithm only +* @op (IN): Type of operation: QCEDEV_OPER_DEC/QCEDEV_OPER_ENC or +* QCEDEV_OPER_ENC_NO_KEY/QCEDEV_OPER_DEC_NO_KEY +* +*If use_pmem is set to 0, the driver assumes that memory was not allocated +* via PMEM, and kernel will need to allocate memory and copy data from user +* space buffer (data_src/dta_dst) and process accordingly and copy data back +* to the user space buffer +* +* If use_pmem is set to 1, the driver assumes that memory was allocated via +* PMEM. +* The kernel driver will use the fd_src to determine the kernel virtual address +* base that maps to the user space virtual address base for the buffer +* allocated in user space. +* The final input/src and output/dst buffer pointer will be determined +* by adding the offsets to the kernel virtual addr. +* +* If use of hardware key is supported in the target, user can configure the +* key paramters (encklen, enckey) to use the hardware key. +* In order to use the hardware key, set encklen to 0 and set the enckey +* data array to 0. +*/ +struct qcedev_cipher_op_req { + uint8_t use_pmem; + union{ + struct qcedev_pmem_info pmem; + struct qcedev_vbuf_info vbuf; + }; + uint32_t entries; + uint32_t data_len; + uint8_t in_place_op; + uint8_t enckey[QCEDEV_MAX_KEY_SIZE]; + uint32_t encklen; + uint8_t iv[QCEDEV_MAX_IV_SIZE]; + uint32_t ivlen; + uint32_t byteoffset; + enum qcedev_cipher_alg_enum alg; + enum qcedev_cipher_mode_enum mode; + enum qcedev_oper_enum op; +}; + +/** +* struct qcedev_sha_op_req - Holds the hashing request information +* @data (IN): Array of pointers to the data to be hashed +* @entries (IN): Number of buf_info entries in the data array +* @data_len (IN): Length of data to be hashed +* @digest (IN/OUT): Returns the hashed data information +* @diglen (OUT): Size of the hashed/digest data +* @authkey (IN): Pointer to authentication key for HMAC +* @authklen (IN): Size of the authentication key +* @alg (IN): Secure Hash algorithm +* @ctxt (Reserved): RESERVED: User should not modify this data. +*/ +struct qcedev_sha_op_req { + struct buf_info data[QCEDEV_MAX_BUFFERS]; + uint32_t entries; + uint32_t data_len; + uint8_t digest[QCEDEV_MAX_SHA_DIGEST]; + uint32_t diglen; + uint8_t *authkey; + uint32_t authklen; + enum qcedev_sha_alg_enum alg; + struct qcedev_sha_ctxt ctxt; +}; + + +#define QCEDEV_IOC_MAGIC 0x87 + +#define QCEDEV_IOCTL_ENC_REQ \ + _IOWR(QCEDEV_IOC_MAGIC, 1, struct qcedev_cipher_op_req) +#define QCEDEV_IOCTL_DEC_REQ \ + _IOWR(QCEDEV_IOC_MAGIC, 2, struct qcedev_cipher_op_req) +#define QCEDEV_IOCTL_SHA_INIT_REQ \ + _IOWR(QCEDEV_IOC_MAGIC, 3, struct qcedev_sha_op_req) +#define QCEDEV_IOCTL_SHA_UPDATE_REQ \ + _IOWR(QCEDEV_IOC_MAGIC, 4, struct qcedev_sha_op_req) +#define QCEDEV_IOCTL_SHA_FINAL_REQ \ + _IOWR(QCEDEV_IOC_MAGIC, 5, struct qcedev_sha_op_req) +#define QCEDEV_IOCTL_GET_SHA_REQ \ + _IOWR(QCEDEV_IOC_MAGIC, 6, struct qcedev_sha_op_req) +#define QCEDEV_IOCTL_LOCK_CE \ + _IO(QCEDEV_IOC_MAGIC, 7) +#define QCEDEV_IOCTL_UNLOCK_CE \ + _IO(QCEDEV_IOC_MAGIC, 8) +#define QCEDEV_IOCTL_GET_CMAC_REQ \ + _IOWR(QCEDEV_IOC_MAGIC, 9, struct qcedev_cipher_op_req) +#endif /* _QCEDEV__H */ diff --git a/drivers/crypto/msm/inc/qcryptohw_30.h b/drivers/crypto/msm/inc/qcryptohw_30.h new file mode 100644 index 00000000000..edbee714215 --- /dev/null +++ b/drivers/crypto/msm/inc/qcryptohw_30.h @@ -0,0 +1,308 @@ +/* Copyright (c)2009- 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _DRIVERS_CRYPTO_MSM_QCRYPTOHW_30_H_ +#define _DRIVERS_CRYPTO_MSM_QCRYPTOHW_30_H_ + +#define QCE_AUTH_REG_BYTE_COUNT 2 +#define CRYPTO_DATA_IN_REG 0x0 +#define CRYPTO_DATA_OUT_REG 0x10 +#define CRYPTO_STATUS_REG 0x20 +#define CRYPTO_CONFIG_REG 0x24 +#define CRYPTO_DEBUG_REG 0x28 +#define CRYPTO_REGISTER_LOCK_REG 0x2C +#define CRYPTO_SEG_CFG_REG 0x30 +#define CRYPTO_ENCR_SEG_CFG_REG 0x34 +#define CRYPTO_AUTH_SEG_CFG_REG 0x38 +#define CRYPTO_SEG_SIZE_REG 0x3C +#define CRYPTO_GOPROC_REG 0x40 +#define CRYPTO_ENGINES_AVAIL 0x44 + +#define CRYPTO_DES_KEY0_REG 0x50 +#define CRYPTO_DES_KEY1_REG 0x54 +#define CRYPTO_DES_KEY2_REG 0x58 +#define CRYPTO_DES_KEY3_REG 0x5C +#define CRYPTO_DES_KEY4_REG 0x60 +#define CRYPTO_DES_KEY5_REG 0x64 + +#define CRYPTO_CNTR0_IV0_REG 0x70 +#define CRYPTO_CNTR1_IV1_REG 0x74 +#define CRYPTO_CNTR2_IV2_REG 0x78 +#define CRYPTO_CNTR3_IV3_REG 0x7C +#define CRYPTO_CNTR_MASK_REG 0x80 + +#define CRYPTO_AUTH_BYTECNT0_REG 0x90 +#define CRYPTO_AUTH_BYTECNT1_REG 0x94 +#define CRYPTO_AUTH_BYTECNT2_REG 0x98 +#define CRYPTO_AUTH_BYTECNT3_REG 0x9C + +#define CRYPTO_AUTH_IV0_REG 0x100 +#define CRYPTO_AUTH_IV1_REG 0x104 +#define CRYPTO_AUTH_IV2_REG 0x108 +#define CRYPTO_AUTH_IV3_REG 0x10C +#define CRYPTO_AUTH_IV4_REG 0x110 +#define CRYPTO_AUTH_IV5_REG 0x114 +#define CRYPTO_AUTH_IV6_REG 0x118 +#define CRYPTO_AUTH_IV7_REG 0x11C +#define CRYPTO_AUTH_IV8_REG 0x120 +#define CRYPTO_AUTH_IV9_REG 0x124 +#define CRYPTO_AUTH_IV10_REG 0x128 +#define CRYPTO_AUTH_IV11_REG 0x12C +#define CRYPTO_AUTH_IV12_REG 0x130 +#define CRYPTO_AUTH_IV13_REG 0x134 +#define CRYPTO_AUTH_IV14_REG 0x138 +#define CRYPTO_AUTH_IV15_REG 0x13C + +#define CRYPTO_AES_RNDKEY0 0x200 +#define CRYPTO_AES_RNDKEY1 0x204 +#define CRYPTO_AES_RNDKEY2 0x208 +#define CRYPTO_AES_RNDKEY3 0x20C +#define CRYPTO_AES_RNDKEY4 0x210 +#define CRYPTO_AES_RNDKEY5 0x214 +#define CRYPTO_AES_RNDKEY6 0x218 +#define CRYPTO_AES_RNDKEY7 0x21C +#define CRYPTO_AES_RNDKEY8 0x220 +#define CRYPTO_AES_RNDKEY9 0x224 +#define CRYPTO_AES_RNDKEY10 0x228 +#define CRYPTO_AES_RNDKEY11 0x22c +#define CRYPTO_AES_RNDKEY12 0x230 +#define CRYPTO_AES_RNDKEY13 0x234 +#define CRYPTO_AES_RNDKEY14 0x238 +#define CRYPTO_AES_RNDKEY15 0x23C +#define CRYPTO_AES_RNDKEY16 0x240 +#define CRYPTO_AES_RNDKEY17 0x244 +#define CRYPTO_AES_RNDKEY18 0x248 +#define CRYPTO_AES_RNDKEY19 0x24C +#define CRYPTO_AES_RNDKEY20 0x250 +#define CRYPTO_AES_RNDKEY21 0x254 +#define CRYPTO_AES_RNDKEY22 0x258 +#define CRYPTO_AES_RNDKEY23 0x25C +#define CRYPTO_AES_RNDKEY24 0x260 +#define CRYPTO_AES_RNDKEY25 0x264 +#define CRYPTO_AES_RNDKEY26 0x268 +#define CRYPTO_AES_RNDKEY27 0x26C +#define CRYPTO_AES_RNDKEY28 0x270 +#define CRYPTO_AES_RNDKEY29 0x274 +#define CRYPTO_AES_RNDKEY30 0x278 +#define CRYPTO_AES_RNDKEY31 0x27C +#define CRYPTO_AES_RNDKEY32 0x280 +#define CRYPTO_AES_RNDKEY33 0x284 +#define CRYPTO_AES_RNDKEY34 0x288 +#define CRYPTO_AES_RNDKEY35 0x28c +#define CRYPTO_AES_RNDKEY36 0x290 +#define CRYPTO_AES_RNDKEY37 0x294 +#define CRYPTO_AES_RNDKEY38 0x298 +#define CRYPTO_AES_RNDKEY39 0x29C +#define CRYPTO_AES_RNDKEY40 0x2A0 +#define CRYPTO_AES_RNDKEY41 0x2A4 +#define CRYPTO_AES_RNDKEY42 0x2A8 +#define CRYPTO_AES_RNDKEY43 0x2AC +#define CRYPTO_AES_RNDKEY44 0x2B0 +#define CRYPTO_AES_RNDKEY45 0x2B4 +#define CRYPTO_AES_RNDKEY46 0x2B8 +#define CRYPTO_AES_RNDKEY47 0x2BC +#define CRYPTO_AES_RNDKEY48 0x2C0 +#define CRYPTO_AES_RNDKEY49 0x2C4 +#define CRYPTO_AES_RNDKEY50 0x2C8 +#define CRYPTO_AES_RNDKEY51 0x2CC +#define CRYPTO_AES_RNDKEY52 0x2D0 +#define CRYPTO_AES_RNDKEY53 0x2D4 +#define CRYPTO_AES_RNDKEY54 0x2D8 +#define CRYPTO_AES_RNDKEY55 0x2DC +#define CRYPTO_AES_RNDKEY56 0x2E0 +#define CRYPTO_AES_RNDKEY57 0x2E4 +#define CRYPTO_AES_RNDKEY58 0x2E8 +#define CRYPTO_AES_RNDKEY59 0x2EC + +#define CRYPTO_DATA_SHADOW0 0x8000 +#define CRYPTO_DATA_SHADOW8191 0x8FFC + +/* status reg */ +#define CRYPTO_CORE_REV 28 /* bit 31-28 */ +#define CRYPTO_CORE_REV_MASK (0xf << CRYPTO_CORE_REV) +#define CRYPTO_DOUT_SIZE_AVAIL 22 /* bit 24-22 */ +#define CRYPTO_DOUT_SIZE_AVAIL_MASK (0x7 << CRYPTO_DOUT_SIZE_AVAIL) +#define CRYPTO_DIN_SIZE_AVAIL 19 /* bit 21-19 */ +#define CRYPTO_DIN_SIZE_AVAIL_MASK (0x7 << CRYPTO_DIN_SIZE_AVAIL) +#define CRYPTO_ACCESS_VIOL 18 +#define CRYPTO_SEG_CHNG_ERR 17 +#define CRYPTO_CFH_CHNG_ERR 16 +#define CRYPTO_DOUT_ERR 15 +#define CRYPTO_DIN_ERR 14 +#define CRYPTO_LOCKED 13 +#define CRYPTO_CRYPTO_STATE 10 /* bit 12-10 */ +#define CRYPTO_CRYPTO_STATE_MASK (0x7 << CRYPTO_CRYPTO_STATE) +#define CRYPTO_ENCR_BUSY 9 +#define CRYPTO_AUTH_BUSY 8 +#define CRYPTO_DOUT_INTR 7 +#define CRYPTO_DIN_INTR 6 +#define CRYPTO_AUTH_DONE_INTR 5 +#define CRYPTO_ERR_INTR 4 +#define CRYPTO_DOUT_RDY 3 +#define CRYPTO_DIN_RDY 2 +#define CRYPTO_AUTH_DONE 1 +#define CRYPTO_SW_ERR 0 + +#define CRYPTO_CRYPTO_STATE_IDLE 0 +#define CRYPTO_CRYPTO_STATE_LOCKED 1 +#define CRYPTO_CRYPTO_STATE_GO 3 +#define CRYPTO_CRYPTO_STATE_PROCESSING 4 +#define CRYPTO_CRYPTO_STATE_FINAL_READ 5 +#define CRYPTO_CRYPTO_STATE_CTXT_CLEARING 6 +#define CRYPTO_CRYPTO_STATE_UNLOCKING 7 + +/* config reg */ +#define CRYPTO_HIGH_SPD_HASH_EN_N 15 +#define CRYPTO_HIGH_SPD_OUT_EN_N 14 +#define CRYPTO_HIGH_SPD_IN_EN_N 13 +#define CRYPTO_DBG_EN 12 +#define CRYPTO_DBG_SEL 7 /* bit 11:7 */ +#define CRYPTO_DBG_SEL_MASK (0x1F << CRYPTO_DBG_SEL) +#define CRYPTO_MASK_DOUT_INTR 6 +#define CRYPTO_MASK_DIN_INTR 5 +#define CRYPTO_MASK_AUTH_DONE_INTR 4 +#define CRYPTO_MASK_ERR_INTR 3 +#define CRYPTO_AUTO_SHUTDOWN_EN 2 +#define CRYPTO_CLK_EN_N 1 +#define CRYPTO_SW_RST 0 + +/* seg_cfg reg */ +#define CRYPTO_F8_KEYSTREAM_ENABLE 25 +#define CRYPTO_F9_DIRECTION 24 +#define CRYPTO_F8_DIRECTION 23 +#define CRYPTO_USE_HW_KEY 22 + +#define CRYPTO_CNTR_ALG 20 /* bit 21-20 */ +#define CRYPTO_CNTR_ALG_MASK (3 << efine CRYPTO_CNTR_ALG) + +#define CRYPTO_CLR_CNTXT 19 +#define CRYPTO_LAST 18 +#define CRYPTO_FIRST 17 +#define CRYPTO_ENCODE 16 + +#define CRYPTO_AUTH_POS 14 /* bit 15-14 */ +#define CRYPTO_AUTH_POS_MASK (3 << CRYPTO_AUTH_POS) + +#define CRYPTO_AUTH_SIZE 11 /* bit 13-11 */ +#define CRYPTO_AUTH_SIZE_MASK (7 << CRYPTO_AUTH_SIZE) + +#define CRYPTO_AUTH_ALG 9 /* bit 10-9 */ +#define CRYPTO_AUTH_ALG_MASK (3 << CRYPTO_AUTH_ALG) + +#define CRYPTO_ENCR_MODE 6 /* bit 8-6 */ +#define CRYPTO_ENCR_MODE_MASK (7 << CRYPTO_ENCR_MODE) + +#define CRYPTO_ENCR_KEY_SZ 3 /* bit 5-3 */ +#define CRYPTO_ENCR_KEY_SZ_MASK (7 << CRYPTO_ENCR_KEY_SZ) + +#define CRYPTO_ENCR_ALG 0 /* bit 2-0 */ +#define CRYPTO_ENCR_ALG_MASK (7 << CRYPTO_ENCR_ALG) + +#define CRYPTO_CNTR_ALG_NIST 0 +#define CRYPTO_CNTR_ALG_UMB 1 +#define CRYPTO_CNTR_ALG_VAR2 2 + +#define CRYPTO_AUTH_POS_BEFORE 0 +#define CRYPTO_AUTH_POS_AFTER 1 + +#define CRYPTO_AUTH_SIZE_SHA1 0 +#define CRYPTO_AUTH_SIZE_SHA256 1 +#define CRYPTO_AUTH_SIZE_SHA384 2 +#define CRYPTO_AUTH_SIZE_SHA512 3 +#define CRYPTO_AUTH_SIZE_HMAC_SHA1 4 + +#define CRYPTO_AUTH_SIZE_UIA1 0 +#define CRYPTO_AUTH_SIZE_UIA2 1 + +#define CRYPTO_AUTH_ALG_NONE 0 +#define CRYPTO_AUTH_ALG_SHA 1 +#define CRYPTO_AUTH_ALG_F9 2 +#define CRYPTO_AUTH_ALG_RESERVED1 3 + +#define CRYPTO_ENCR_MODE_ECB 0 +#define CRYPTO_ENCR_MODE_CBC 1 +/* only valid when AES */ +#define CRYPTO_ENCR_MODE_CTR 2 + + +#define CRYPTO_ENCR_KEY_SZ_DES 0 +#define CRYPTO_ENCR_KEY_SZ_3DES 1 + +#define CRYPTO_ENCR_KEY_SZ_AES128 0 +#define CRYPTO_ENCR_KEY_SZ_AES192 1 +#define CRYPTO_ENCR_KEY_SZ_AES256 2 + +#define CRYPTO_ENCR_KEY_SZ_UEA1 0 +#define CRYPTO_ENCR_KEY_SZ_UEA2 1 + +#define CRYPTO_ENCR_ALG_NONE 0 +#define CRYPTO_ENCR_ALG_DES 1 +#define CRYPTO_ENCR_ALG_AES 2 +#define CRYPTO_ENCR_ALG_C2 3 +#define CRYPTO_ENCR_ALG_F8 4 + +/* encr_seg_cfg reg */ +#define CRYPTO_ENCR_SEG_SIZE 16 /* bit 31-16 */ +#define CRYPTO_ENCR_SEG_SIZE_MASK (0xffff << CRYPTO_ENCR_SEG_SIZE) + +#define CRYPTO_ENCR_START 0 +#define CRYPTO_ENCR_START_MASK (0xffff << CRYPTO_ENCR_START) + +/* auth_seg_cfg reg */ +#define CRYPTO_AUTH_SEG_SIZE 16 /* bit 31-16 */ +#define CRYPTO_AUTH_SEG_SIZE_MASK (0xffff << CRYPTO_AUTH_SEG_SIZE) + +#define CRYPTO_AUTH_START 0 +#define CRYPTO_AUTH_START_MASK (0xffff << CRYPTO_AUTH_START) + + +/* seg_size reg */ +#define CRYPTO_SEG_SIZE 0 +#define CRYPTO_SEG_SIZE_MASK (0xffff << CRYPTO_SEG_SIZE) + +/* goproc reg */ +#define CRYPTO_GO 0 + +/* engines_avail */ +#define CRYPTO_F9_SEL 8 +#define CRYPTO_F8_SEL 7 +#define CRYPTO_HMAC_SEL 6 +#define CRYPTO_SHA512_SEL 5 +#define CRYPTO_SHA_SEL 4 +#define CRYPTO_DES_SEL 3 +#define CRYPTO_C2_SEL 2 + +#define CRYPTO_AES_SEL 0 /* bit 1-0 */ +#define CRYPTO_AES_SEL_MASK (3 << CRYPTO_AES_SEL) +#define CRYPTO_AES_SEL_NO 0 +#define CRYPTO_AES_SEL_SLOW 1 +#define CRYPTO_AES_SEL_FAST 2 +#define CRYPTO_AES_SEL_RESERVED 3 + +/* F8 definition of CRYPTO_CNTR1_IV1_REG */ +#define CRYPTO_CNTR1_IV1_REG_F8_PKT_CNT 16 /* bit 31 - 16 */ +#define CRYPTO_CNTR1_IV1_REG_F8_PKT_CNT_MASK \ + (0xffff << CRYPTO_CNTR1_IV1_REG_F8_PKT_CNT) + +#define CRYPTO_CNTR1_IV1_REG_F8_BEARER 0 /* bit 4 - 0 */ +#define CRYPTO_CNTR1_IV1_REG_F8_BEARER_MASK \ + (0x1f << CRYPTO_CNTR1_IV1_REG_F8_BEARER) + +/* F9 definition of CRYPTO_AUTH_IV4_REG */ +#define CRYPTO_AUTH_IV4_REG_F9_VALID_BIS 0 /* bit 2 - 0 */ +#define CRYPTO_AUTH_IV4_REG_F9_VALID_BIS_MASK \ + (0x7 << CRYPTO_AUTH_IV4_REG_F9_VALID_BIS) + +/* misc */ +#define CRYPTO_AES_RNDKEYS 60 + +#endif /* _DRIVERS_CRYPTO_MSM_QCRYPTOHW_30_H_ */ diff --git a/drivers/crypto/msm/inc/qcryptohw_40.h b/drivers/crypto/msm/inc/qcryptohw_40.h new file mode 100644 index 00000000000..367bdaaf7ae --- /dev/null +++ b/drivers/crypto/msm/inc/qcryptohw_40.h @@ -0,0 +1,316 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _DRIVERS_CRYPTO_MSM_QCRYPTOHW_40_H_ +#define _DRIVERS_CRYPTO_MSM_QCRYPTOHW_40_H_ + + +#define QCE_AUTH_REG_BYTE_COUNT 4 +#define CRYPTO_VERSION_REG 0x0 +#define CRYPTO_DATA_IN_REG 0x008 +#define CRYPTO_DATA_OUT_REG 0x010 +#define CRYPTO_STATUS_REG 0x100 +#define CRYPTO_ENGINES_AVAIL 0x104 +#define CRYPTO3_VERSION_REG 0x108 +#define CRYPTO_SEG_SIZE_REG 0x200 +#define CRYPTO_GOPROC_REG 0x204 +#define CRYPTO_ENCR_SEG_CFG_REG 0x300 + +#define CRYPTO_ENCR_SEG_SIZE_REG 0x304 +#define CRYPTO_ENCR_SEG_START_REG 0x308 + +#define CRYPTO_ENCR_KEY0_REG 0x310 +#define CRYPTO_ENCR_KEY1_REG 0x314 +#define CRYPTO_ENCR_KEY2_REG 0x318 +#define CRYPTO_ENCR_KEY3_REG 0x31C +#define CRYPTO_ENCR_KEY4_REG 0x320 +#define CRYPTO_ENCR_KEY5_REG 0x324 +#define CRYPTO_ENCR_KEY6_REG 0x328 +#define CRYPTO_ENCR_KEY7_REG 0x32C + +#define CRYPTO_ENCR_XTS_KEY0_REG 0x330 +#define CRYPTO_ENCR_XTS_KEY1_REG 0x334 +#define CRYPTO_ENCR_XTS_KEY2_REG 0x338 +#define CRYPTO_ENCR_XTS_KEY3_REG 0x33C +#define CRYPTO_ENCR_XTS_KEY4_REG 0x340 +#define CRYPTO_ENCR_XTS_KEY5_REG 0x344 +#define CRYPTO_ENCR_XTS_KEY6_REG 0x348 +#define CRYPTO_ENCR_XTS_KEY7_REG 0x34C + +#define CRYPTO_CNTR0_IV0_REG 0x350 +#define CRYPTO_CNTR1_IV1_REG 0x354 +#define CRYPTO_CNTR2_IV2_REG 0x358 +#define CRYPTO_CNTR3_IV3_REG 0x35C + +#define CRYPTO_CNTR_MASK_REG 0x360 + +#define CRYPTO_ENCR_XTS_DU_SIZE_REG 0x364 + +#define CRYPTO_AUTH_SEG_CFG_REG 0x400 +#define CRYPTO_AUTH_SEG_SIZE_REG 0x404 +#define CRYPTO_AUTH_SEG_START_REG 0x408 + +#define CRYPTO_AUTH_KEY0_REG 0x410 +#define CRYPTO_AUTH_KEY1_REG 0x414 +#define CRYPTO_AUTH_KEY2_REG 0x418 +#define CRYPTO_AUTH_KEY3_REG 0x41C +#define CRYPTO_AUTH_KEY4_REG 0x420 +#define CRYPTO_AUTH_KEY5_REG 0x424 +#define CRYPTO_AUTH_KEY6_REG 0x428 +#define CRYPTO_AUTH_KEY7_REG 0x42C +#define CRYPTO_AUTH_KEY8_REG 0x430 +#define CRYPTO_AUTH_KEY9_REG 0x434 +#define CRYPTO_AUTH_KEY10_REG 0x438 +#define CRYPTO_AUTH_KEY11_REG 0x43C +#define CRYPTO_AUTH_KEY12_REG 0x440 +#define CRYPTO_AUTH_KEY13_REG 0x444 +#define CRYPTO_AUTH_KEY14_REG 0x448 +#define CRYPTO_AUTH_KEY15_REG 0x44C + +#define CRYPTO_AUTH_IV0_REG 0x450 +#define CRYPTO_AUTH_IV1_REG 0x454 +#define CRYPTO_AUTH_IV2_REG 0x458 +#define CRYPTO_AUTH_IV3_REG 0x45C +#define CRYPTO_AUTH_IV4_REG 0x460 +#define CRYPTO_AUTH_IV5_REG 0x464 +#define CRYPTO_AUTH_IV6_REG 0x468 +#define CRYPTO_AUTH_IV7_REG 0x46C +#define CRYPTO_AUTH_IV8_REG 0x470 +#define CRYPTO_AUTH_IV9_REG 0x474 +#define CRYPTO_AUTH_IV10_REG 0x478 +#define CRYPTO_AUTH_IV11_REG 0x47C +#define CRYPTO_AUTH_IV12_REG 0x480 +#define CRYPTO_AUTH_IV13_REG 0x484 +#define CRYPTO_AUTH_IV14_REG 0x488 +#define CRYPTO_AUTH_IV15_REG 0x48C + +#define CRYPTO_AUTH_INFO_NONCE0_REG 0x490 +#define CRYPTO_AUTH_INFO_NONCE1_REG 0x494 +#define CRYPTO_AUTH_INFO_NONCE2_REG 0x498 +#define CRYPTO_AUTH_INFO_NONCE3_REG 0x49C + +#define CRYPTO_AUTH_BYTECNT0_REG 0x4A0 +#define CRYPTO_AUTH_BYTECNT1_REG 0x4A4 +#define CRYPTO_AUTH_BYTECNT2_REG 0x4A8 +#define CRYPTO_AUTH_BYTECNT3_REG 0x4AC + +#define CRYPTO_AUTH_EXP_MAC0_REG 0x4B0 +#define CRYPTO_AUTH_EXP_MAC1_REG 0x4B4 +#define CRYPTO_AUTH_EXP_MAC2_REG 0x4B8 +#define CRYPTO_AUTH_EXP_MAC3_REG 0x4BC +#define CRYPTO_AUTH_EXP_MAC4_REG 0x4C0 +#define CRYPTO_AUTH_EXP_MAC5_REG 0x4C4 +#define CRYPTO_AUTH_EXP_MAC6_REG 0x4C8 +#define CRYPTO_AUTH_EXP_MAC7_REG 0x4CC + +#define CRYPTO_CONFIG_REG 0x500 +#define CRYPTO_SACR_REG 0x504 +#define CRYPTO_DEBUG_REG 0x508 + +#define CRYPTO_DATA_SHADOW0 0x8000 +#define CRYPTO_DATA_SHADOW8191 0x8FFC + + +/* Register bits */ + +#define CRYPTO_CORE_MAJOR_REV 4 /* bit 7-4 */ +#define CRYPTO_CORE_MAJOR_REV_MASK (0xF << CRYPTO_CORE_MAJOR_REV) +#define CRYPTO_CORE_MINOR_REV 0 /* bit 3-0 */ +#define CRYPTO_CORE_MINOR_REV_MASK (0xF << CRYPTO_CORE_MINOR_REV) +#define CRYPTO_CORE_REV_MASK 0xFF + +/* status reg */ +#define CRYPTO_MAC_FAILED 25 +#define CRYPTO_DOUT_SIZE_AVAIL 22 /* bit 24-22 */ +#define CRYPTO_DOUT_SIZE_AVAIL_MASK (0x7 << CRYPTO_DOUT_SIZE_AVAIL) +#define CRYPTO_DIN_SIZE_AVAIL 19 /* bit 21-19 */ +#define CRYPTO_DIN_SIZE_AVAIL_MASK (0x7 << CRYPTO_DIN_SIZE_AVAIL) +#define CRYPTO_ACCESS_VIOL 18 +#define CRYPTO_SEG_CHNG_ERR 17 +#define CRYPTO_CFH_CHNG_ERR 16 +#define CRYPTO_DOUT_ERR 15 +#define CRYPTO_DIN_ERR 14 +#define CRYPTO_LOCKED 13 +#define CRYPTO_CRYPTO_STATE 10 /* bit 12-10 */ +#define CRYPTO_CRYPTO_STATE_MASK (0x7 << CRYPTO_CRYPTO_STATE) +#define CRYPTO_ENCR_BUSY 9 +#define CRYPTO_AUTH_BUSY 8 +#define CRYPTO_DOUT_INTR 7 +#define CRYPTO_DIN_INTR 6 +#define CRYPTO_OP_DONE_INTR 5 +#define CRYPTO_ERR_INTR 4 +#define CRYPTO_DOUT_RDY 3 +#define CRYPTO_DIN_RDY 2 +#define CRYPTO_OPERATION_DONE 1 +#define CRYPTO_SW_ERR 0 + +/* config reg */ +#define CRYPTO_REQ_SIZE 30 /* bit 31-30 */ +#define CRYPTO_REQ_SIZE_MASK (0x3 << CRYPTO_REQ_SIZE) +#define CRYPTO_REQ_SIZE_ENUM_16_BYTES 0 +#define CRYPTO_REQ_SIZE_ENUM_32_BYTES 1 +#define CRYPTO_REQ_SIZE_ENUM_64_BYTES 2 + +#define CRYPTO_MAX_QUEUED_REQ 27 /* bit 29-27 */ +#define CRYPTO_MAX_QUEUED_REQ_MASK (0x7 << CRYPTO_MAX_QUEUED_REQ) +#define CRYPTO_ENUM1_QUEUED_REQS 0 +#define CRYPTO_ENUM2_QUEUED_REQS 1 +#define CRYPTO_ENUM3_QUEUED_REQS 2 +#define CRYPTO_ENUM4_QUEUED_REQS 3 + +#define CRYPTO_FIFO_THRESHOLD 24 /* bit 26-24 */ +#define CRYPTO_FIFO_THRESHOLD_MASK (0x7 << CRYPTO_FIFO_THRESHOLD) +#define CRYPTO_FIFO_ENUM_16_BYTES 0 +#define CRYPTO_FIFO_ENUM_32_BYTES 1 +#define CRYPTO_FIFO_ENUM_48_BYTES 2 +#define CRYPTO_FIFO_ENUM_64_BYTES 3 + +#define CRYPTO_IRQ_ENABLES 20 /* bit 23-20 */ +#define CRYPTO_IRQ_ENABLES_MASK (0xF << CRYPTO_IRQ_ENABLES) + +#define CRYPTO_ACR_EN 18 +#define CRYPTO_BAM_MODE 17 +#define CRYPTO_LITTLE_ENDIAN_MODE 16 +#define CRYPTO_HIGH_SPD_OUT_EN_N 14 +#define CRYPTO_HIGH_SPD_IN_EN_N 13 +#define CRYPTO_DBG_EN 12 + +#define CRYPTO_DBG_SEL 7 /* bit 11:7 */ +#define CRYPTO_DBG_SEL_MASK (0x1F << CRYPTO_DBG_SEL) + +#define CRYPTO_MASK_DOUT_INTR 6 +#define CRYPTO_MASK_DIN_INTR 5 +#define CRYPTO_MASK_OP_DONE_INTR 4 +#define CRYPTO_MASK_ERR_INTR 3 +#define CRYPTO_AUTO_SHUTDOWN_EN 2 +#define CRYPTO_CLK_EN_N 1 + +/* auth_seg_cfg reg */ +#define CRYPTO_COMP_EXP_MAC 20 +#define CRYPTO_COMP_EXP_MAC_DISABLED 0 +#define CRYPTO_COMP_EXP_MAC_ENABLED 1 + +#define CRYPTO_F9_DIRECTION 19 +#define CRYPTO_F9_DIRECTION_UPLINK 0 +#define CRYPTO_F9_DIRECTION_DOWNLINK 1 + +#define CRYPTO_AUTH_NONCE_NUM_WORDS 16 +#define CRYPTO_AUTH_NONCE_NUM_WORDS_MASK \ + (0x7 << CRYPTO_AUTH_NONCE_NUM_WORDS) + +#define CRYPTO_USE_HW_KEY_AUTH 15 + +#define CRYPTO_LAST 14 + +#define CRYPTO_AUTH_POS 12 /* bit 13 .. 12*/ +#define CRYPTO_AUTH_POS_MASK (0x3 << CRYPTO_AUTH_POS) +#define CRYPTO_AUTH_POS_BEFORE 0 +#define CRYPTO_AUTH_POS_AFTER 1 + +#define CRYPTO_AUTH_SIZE 9 /* bits 11 .. 9*/ +#define CRYPTO_AUTH_SIZE_MASK (0x7 << CRYPTO_AUTH_SIZE) +#define CRYPTO_AUTH_SIZE_SHA1 0 +#define CRYPTO_AUTH_SIZE_SHA256 1 +#define CRYPTO_AUTH_SIZE_ENUM_4_BYTES 0 +#define CRYPTO_AUTH_SIZE_ENUM_6_BYTES 1 +#define CRYPTO_AUTH_SIZE_ENUM_8_BYTES 2 +#define CRYPTO_AUTH_SIZE_ENUM_10_BYTES 3 +#define CRYPTO_AUTH_SIZE_ENUM_12_BYTES 4 +#define CRYPTO_AUTH_SIZE_ENUM_14_BYTES 5 +#define CRYPTO_AUTH_SIZE_ENUM_16_BYTES 6 + +#define CRYPTO_AUTH_MODE 6 /* bit 8 .. 6*/ +#define CRYPTO_AUTH_MODE_MASK (0x7 << CRYPTO_AUTH_MODE) +#define CRYPTO_AUTH_MODE_HASH 0 +#define CRYPTO_AUTH_MODE_HMAC 1 +#define CRYPTO_AUTH_MODE_CCM 0 +#define CRYPTO_AUTH_MODE_CMAC 1 + +#define CRYPTO_AUTH_KEY_SIZE 3 +#define CRYPTO_AUTH_KEY_SIZE_MASK (0x7 << CRYPTO_AUTH_KEY_SIZE) +#define CRYPTO_AUTH_KEY_SZ_AES128 0 +#define CRYPTO_AUTH_KEY_SZ_AES256 2 + +#define CRYPTO_AUTH_ALG 0 /* bit 2 .. 0*/ +#define CRYPTO_AUTH_ALG_MASK 7 +#define CRYPTO_AUTH_ALG_NONE 0 +#define CRYPTO_AUTH_ALG_SHA 1 +#define CRYPTO_AUTH_ALG_AES 2 +#define CRYPTO_AUTH_ALG_KASUMI 3 +#define CRYPTO_AUTH_ALG_SNOW3G 4 + +/* encr_xts_du_size reg */ +#define CRYPTO_ENCR_XTS_DU_SIZE 0 /* bit 19-0 */ +#define CRYPTO_ENCR_XTS_DU_SIZE_MASK 0xfffff + +/* encr_seg_cfg reg */ +#define CRYPTO_F8_KEYSTREAM_ENABLE 15 +#define CRYPTO_F8_KEYSTREAM_DISABLED 0 +#define CRYPTO_F8_KEYSTREAM_ENABLED 1 + +#define CRYPTO_F8_DIRECTION 14 +#define CRYPTO_F8_DIRECTION_UPLINK 0 +#define CRYPTO_F8_DIRECTION_DOWNLINK 1 + +#define CRYPTO_USE_HW_KEY_ENCR 13 +#define CRYPTO_USE_HW_KEY_REG 0 +#define CRYPTO_USE_HW_KEY 1 + +#define CRYPTO_CNTR_ALG 11 /* bit 12-11 */ +#define CRYPTO_CNTR_ALG_MASK (3 << CRYPTO_CNTR_ALG) +#define CRYPTO_CNTR_ALG_NIST 0 + +#define CRYPTO_ENCODE 10 + +#define CRYPTO_ENCR_MODE 6 /* bit 9-6 */ +#define CRYPTO_ENCR_MODE_MASK (0xF << CRYPTO_ENCR_MODE) +/* only valid when AES */ +#define CRYPTO_ENCR_MODE_ECB 0 +#define CRYPTO_ENCR_MODE_CBC 1 +#define CRYPTO_ENCR_MODE_CTR 2 +#define CRYPTO_ENCR_MODE_XTS 3 +#define CRYPTO_ENCR_MODE_CCM 4 + +#define CRYPTO_ENCR_KEY_SZ 3 /* bit 5-3 */ +#define CRYPTO_ENCR_KEY_SZ_MASK (7 << CRYPTO_ENCR_KEY_SZ) +#define CRYPTO_ENCR_KEY_SZ_DES 0 +#define CRYPTO_ENCR_KEY_SZ_3DES 1 +#define CRYPTO_ENCR_KEY_SZ_AES128 0 +#define CRYPTO_ENCR_KEY_SZ_AES256 2 +#define CRYPTO_ENCR_KEY_SZ_UEA1 0 +#define CRYPTO_ENCR_KEY_SZ_UEA2 1 + +#define CRYPTO_ENCR_ALG 0 /* bit 2-0 */ +#define CRYPTO_ENCR_ALG_MASK (7 << CRYPTO_ENCR_ALG) +#define CRYPTO_ENCR_ALG_NONE 0 +#define CRYPTO_ENCR_ALG_DES 1 +#define CRYPTO_ENCR_ALG_AES 2 +#define CRYPTO_ENCR_ALG_KASUMI 3 +#define CRYPTO_ENCR_ALG_SNOW_3G 5 + +/* goproc reg */ +#define CRYPTO_GO 0 +#define CRYPTO_CLR_CNTXT 1 + +/* engines_avail */ +#define CRYPTO_ENCR_AES_SEL 0 +#define CRYPTO_DES_SEL 3 +#define CRYPTO_ENCR_SNOW3G_SEL 4 +#define CRYPTO_ENCR_KASUMI_SEL 5 +#define CRYPTO_SHA_SEL 6 +#define CRYPTO_SHA512_SEL 7 +#define CRYPTO_AUTH_AES_SEL 8 +#define CRYPTO_AUTH_SNOW3G_SEL 9 +#define CRYPTO_AUTH_KASUMI_SEL 10 +#define CRYPTO_BAM_SEL 11 + +#endif /* _DRIVERS_CRYPTO_MSM_QCRYPTOHW_40_H_ */ diff --git a/drivers/crypto/msm/ota_crypto.c b/drivers/crypto/msm/ota_crypto.c new file mode 100644 index 00000000000..516253a9bf5 --- /dev/null +++ b/drivers/crypto/msm/ota_crypto.c @@ -0,0 +1,731 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* Qualcomm Over the Air (OTA) Crypto driver */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include "inc/qce_ota.h" +#include "inc/qce.h" + +enum qce_ota_oper_enum { + QCE_OTA_F8_OPER = 0, + QCE_OTA_MPKT_F8_OPER = 1, + QCE_OTA_F9_OPER = 2, + QCE_OTA_OPER_LAST +}; + +struct ota_dev_control; + +struct ota_async_req { + struct list_head list; + struct completion complete; + int err; + enum qce_ota_oper_enum op; + union { + struct qce_f9_req f9_req; + struct qce_f8_req f8_req; + struct qce_f8_multi_pkt_req f8_mp_req; + } req; + + struct ota_dev_control *podev; +}; + +/* + * Register ourselves as a misc device to be able to access the ota + * from userspace. + */ + + +#define QCOTA_DEV "qcota" + + +struct ota_dev_control { + + /* misc device */ + struct miscdevice miscdevice; + + /* qce handle */ + void *qce; + + /* platform device */ + struct platform_device *pdev; + + unsigned magic; + + struct list_head ready_commands; + struct ota_async_req *active_command; + spinlock_t lock; + struct tasklet_struct done_tasklet; +}; + +#define OTA_MAGIC 0x4f544143 + +static long qcota_ioctl(struct file *file, + unsigned cmd, unsigned long arg); +static int qcota_open(struct inode *inode, struct file *file); +static int qcota_release(struct inode *inode, struct file *file); +static int start_req(struct ota_dev_control *podev); + +static const struct file_operations qcota_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = qcota_ioctl, + .open = qcota_open, + .release = qcota_release, +}; + +static struct ota_dev_control qcota_dev[] = { + { + .miscdevice = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qcota0", + .fops = &qcota_fops, + }, + .magic = OTA_MAGIC, + }, + { + .miscdevice = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qcota1", + .fops = &qcota_fops, + }, + .magic = OTA_MAGIC, + }, + { + .miscdevice = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qcota2", + .fops = &qcota_fops, + }, + .magic = OTA_MAGIC, + } +}; + +#define MAX_OTA_DEVICE ARRAY_SIZE(qcota_dev) + +#define DEBUG_MAX_FNAME 16 +#define DEBUG_MAX_RW_BUF 1024 + +struct qcota_stat { + u32 f8_req; + u32 f8_mp_req; + u32 f9_req; + u32 f8_op_success; + u32 f8_op_fail; + u32 f8_mp_op_success; + u32 f8_mp_op_fail; + u32 f9_op_success; + u32 f9_op_fail; +}; +static struct qcota_stat _qcota_stat[MAX_OTA_DEVICE]; +static struct dentry *_debug_dent; +static char _debug_read_buf[DEBUG_MAX_RW_BUF]; +static int _debug_qcota[MAX_OTA_DEVICE]; + +static struct ota_dev_control *qcota_minor_to_control(unsigned n) +{ + int i; + + for (i = 0; i < MAX_OTA_DEVICE; i++) { + if (qcota_dev[i].miscdevice.minor == n) + return &qcota_dev[i]; + } + return NULL; +} + +static int qcota_open(struct inode *inode, struct file *file) +{ + struct ota_dev_control *podev; + + podev = qcota_minor_to_control(MINOR(inode->i_rdev)); + if (podev == NULL) { + pr_err("%s: no such device %d\n", __func__, + MINOR(inode->i_rdev)); + return -ENOENT; + } + + file->private_data = podev; + + return 0; +} + +static int qcota_release(struct inode *inode, struct file *file) +{ + struct ota_dev_control *podev; + + podev = file->private_data; + + if (podev != NULL && podev->magic != OTA_MAGIC) { + pr_err("%s: invalid handle %p\n", + __func__, podev); + } + + file->private_data = NULL; + + return 0; +} + +static void req_done(unsigned long data) +{ + struct ota_dev_control *podev = (struct ota_dev_control *)data; + struct ota_async_req *areq; + unsigned long flags; + struct ota_async_req *new_req = NULL; + int ret = 0; + + spin_lock_irqsave(&podev->lock, flags); + areq = podev->active_command; + podev->active_command = NULL; + +again: + if (!list_empty(&podev->ready_commands)) { + new_req = container_of(podev->ready_commands.next, + struct ota_async_req, list); + list_del(&new_req->list); + podev->active_command = new_req; + new_req->err = 0; + ret = start_req(podev); + } + + spin_unlock_irqrestore(&podev->lock, flags); + + if (areq) + complete(&areq->complete); + + if (new_req && ret) { + complete(&new_req->complete); + spin_lock_irqsave(&podev->lock, flags); + podev->active_command = NULL; + areq = NULL; + ret = 0; + new_req = NULL; + goto again; + } + + return; +} + +static void f9_cb(void *cookie, unsigned char *icv, unsigned char *iv, + int ret) +{ + struct ota_async_req *areq = (struct ota_async_req *) cookie; + struct ota_dev_control *podev; + struct qcota_stat *pstat; + + podev = areq->podev; + pstat = &_qcota_stat[podev->pdev->id]; + areq->req.f9_req.mac_i = (uint32_t) icv; + + if (ret) + areq->err = -ENXIO; + else + areq->err = 0; + + tasklet_schedule(&podev->done_tasklet); +}; + +static void f8_cb(void *cookie, unsigned char *icv, unsigned char *iv, + int ret) +{ + struct ota_async_req *areq = (struct ota_async_req *) cookie; + struct ota_dev_control *podev; + struct qcota_stat *pstat; + + podev = areq->podev; + pstat = &_qcota_stat[podev->pdev->id]; + + if (ret) + areq->err = -ENXIO; + else + areq->err = 0; + + tasklet_schedule(&podev->done_tasklet); +}; + +static int start_req(struct ota_dev_control *podev) +{ + struct ota_async_req *areq; + struct qce_f9_req *pf9; + struct qce_f8_multi_pkt_req *p_mp_f8; + struct qce_f8_req *pf8; + int ret = 0; + + /* start the command on the podev->active_command */ + areq = podev->active_command; + areq->podev = podev; + + switch (areq->op) { + case QCE_OTA_F8_OPER: + pf8 = &areq->req.f8_req; + ret = qce_f8_req(podev->qce, pf8, areq, f8_cb); + break; + case QCE_OTA_MPKT_F8_OPER: + p_mp_f8 = &areq->req.f8_mp_req; + ret = qce_f8_multi_pkt_req(podev->qce, p_mp_f8, areq, f8_cb); + break; + + case QCE_OTA_F9_OPER: + pf9 = &areq->req.f9_req; + ret = qce_f9_req(podev->qce, pf9, areq, f9_cb); + break; + + default: + ret = -ENOTSUPP; + break; + }; + areq->err = ret; + return ret; +}; + +static int submit_req(struct ota_async_req *areq, struct ota_dev_control *podev) +{ + unsigned long flags; + int ret = 0; + struct qcota_stat *pstat; + + areq->err = 0; + spin_lock_irqsave(&podev->lock, flags); + if (podev->active_command == NULL) { + podev->active_command = areq; + ret = start_req(podev); + } else { + list_add_tail(&areq->list, &podev->ready_commands); + } + + if (ret != 0) + podev->active_command = NULL; + spin_unlock_irqrestore(&podev->lock, flags); + + if (ret == 0) + wait_for_completion(&areq->complete); + + pstat = &_qcota_stat[podev->pdev->id]; + switch (areq->op) { + case QCE_OTA_F8_OPER: + if (areq->err) + pstat->f8_op_fail++; + else + pstat->f8_op_success++; + break; + + case QCE_OTA_MPKT_F8_OPER: + + if (areq->err) + pstat->f8_mp_op_fail++; + else + pstat->f8_mp_op_success++; + break; + + case QCE_OTA_F9_OPER: + default: + if (areq->err) + pstat->f9_op_fail++; + else + pstat->f9_op_success++; + break; + }; + + return areq->err; +}; + +static long qcota_ioctl(struct file *file, + unsigned cmd, unsigned long arg) +{ + int err = 0; + struct ota_dev_control *podev; + uint8_t *user_src; + uint8_t *user_dst; + uint8_t *k_buf = NULL; + struct ota_async_req areq; + uint32_t total; + struct qcota_stat *pstat; + + podev = file->private_data; + if (podev == NULL || podev->magic != OTA_MAGIC) { + pr_err("%s: invalid handle %p\n", + __func__, podev); + return -ENOENT; + } + + /* Verify user arguments. */ + if (_IOC_TYPE(cmd) != QCOTA_IOC_MAGIC) + return -ENOTTY; + + init_completion(&areq.complete); + + pstat = &_qcota_stat[podev->pdev->id]; + + switch (cmd) { + case QCOTA_F9_REQ: + if (!access_ok(VERIFY_WRITE, (void __user *)arg, + sizeof(struct qce_f9_req))) + return -EFAULT; + if (__copy_from_user(&areq.req.f9_req, (void __user *)arg, + sizeof(struct qce_f9_req))) + return -EFAULT; + + user_src = areq.req.f9_req.message; + if (!access_ok(VERIFY_READ, (void __user *)user_src, + areq.req.f9_req.msize)) + return -EFAULT; + + k_buf = kmalloc(areq.req.f9_req.msize, GFP_KERNEL); + if (k_buf == NULL) + return -ENOMEM; + + if (__copy_from_user(k_buf, (void __user *)user_src, + areq.req.f9_req.msize)) { + kfree(k_buf); + return -EFAULT; + } + + areq.req.f9_req.message = k_buf; + areq.op = QCE_OTA_F9_OPER; + + pstat->f9_req++; + err = submit_req(&areq, podev); + + areq.req.f9_req.message = user_src; + if (err == 0 && __copy_to_user((void __user *)arg, + &areq.req.f9_req, sizeof(struct qce_f9_req))) { + err = -EFAULT; + } + kfree(k_buf); + break; + + case QCOTA_F8_REQ: + if (!access_ok(VERIFY_WRITE, (void __user *)arg, + sizeof(struct qce_f8_req))) + return -EFAULT; + if (__copy_from_user(&areq.req.f8_req, (void __user *)arg, + sizeof(struct qce_f8_req))) + return -EFAULT; + total = areq.req.f8_req.data_len; + user_src = areq.req.f8_req.data_in; + if (user_src != NULL) { + if (!access_ok(VERIFY_READ, (void __user *) + user_src, total)) + return -EFAULT; + + }; + + user_dst = areq.req.f8_req.data_out; + if (!access_ok(VERIFY_WRITE, (void __user *) + user_dst, total)) + return -EFAULT; + + k_buf = kmalloc(total, GFP_KERNEL); + if (k_buf == NULL) + return -ENOMEM; + + /* k_buf returned from kmalloc should be cache line aligned */ + if (user_src && __copy_from_user(k_buf, + (void __user *)user_src, total)) { + kfree(k_buf); + return -EFAULT; + } + + if (user_src) + areq.req.f8_req.data_in = k_buf; + else + areq.req.f8_req.data_in = NULL; + areq.req.f8_req.data_out = k_buf; + + areq.op = QCE_OTA_F8_OPER; + + pstat->f8_req++; + err = submit_req(&areq, podev); + + if (err == 0 && __copy_to_user(user_dst, k_buf, total)) + err = -EFAULT; + kfree(k_buf); + + break; + + case QCOTA_F8_MPKT_REQ: + if (!access_ok(VERIFY_WRITE, (void __user *)arg, + sizeof(struct qce_f8_multi_pkt_req))) + return -EFAULT; + if (__copy_from_user(&areq.req.f8_mp_req, (void __user *)arg, + sizeof(struct qce_f8_multi_pkt_req))) + return -EFAULT; + + total = areq.req.f8_mp_req.num_pkt * + areq.req.f8_mp_req.qce_f8_req.data_len; + + user_src = areq.req.f8_mp_req.qce_f8_req.data_in; + if (!access_ok(VERIFY_READ, (void __user *) + user_src, total)) + return -EFAULT; + + user_dst = areq.req.f8_mp_req.qce_f8_req.data_out; + if (!access_ok(VERIFY_WRITE, (void __user *) + user_dst, total)) + return -EFAULT; + + k_buf = kmalloc(total, GFP_KERNEL); + if (k_buf == NULL) + return -ENOMEM; + /* k_buf returned from kmalloc should be cache line aligned */ + if (__copy_from_user(k_buf, (void __user *)user_src, total)) { + kfree(k_buf); + + return -EFAULT; + } + + areq.req.f8_mp_req.qce_f8_req.data_out = k_buf; + areq.req.f8_mp_req.qce_f8_req.data_in = k_buf; + + areq.op = QCE_OTA_MPKT_F8_OPER; + + pstat->f8_mp_req++; + err = submit_req(&areq, podev); + + if (err == 0 && __copy_to_user(user_dst, k_buf, total)) + err = -EFAULT; + kfree(k_buf); + break; + + default: + return -ENOTTY; + } + + return err; +} + +static int qcota_probe(struct platform_device *pdev) +{ + void *handle = NULL; + int rc = 0; + struct ota_dev_control *podev; + struct ce_hw_support ce_support; + + if (pdev->id >= MAX_OTA_DEVICE) { + pr_err("%s: device id %d exceeds allowed %d\n", + __func__, pdev->id, MAX_OTA_DEVICE); + return -ENOENT; + } + + podev = &qcota_dev[pdev->id]; + + INIT_LIST_HEAD(&podev->ready_commands); + podev->active_command = NULL; + spin_lock_init(&podev->lock); + tasklet_init(&podev->done_tasklet, req_done, (unsigned long)podev); + + /* open qce */ + handle = qce_open(pdev, &rc); + if (handle == NULL) { + pr_err("%s: device id %d, can not open qce\n", + __func__, pdev->id); + platform_set_drvdata(pdev, NULL); + return rc; + } + if (qce_hw_support(handle, &ce_support) < 0 || + ce_support.ota == false) { + pr_err("%s: device id %d, qce does not support ota capability\n", + __func__, pdev->id); + rc = -ENODEV; + goto err; + } + podev->qce = handle; + podev->pdev = pdev; + platform_set_drvdata(pdev, podev); + + rc = misc_register(&podev->miscdevice); + if (rc < 0) + goto err; + + return 0; +err: + if (handle) + qce_close(handle); + platform_set_drvdata(pdev, NULL); + podev->qce = NULL; + podev->pdev = NULL; + return rc; +}; + +static int qcota_remove(struct platform_device *pdev) +{ + struct ota_dev_control *podev; + + podev = platform_get_drvdata(pdev); + if (!podev) + return 0; + if (podev->qce) + qce_close(podev->qce); + + if (podev->miscdevice.minor != MISC_DYNAMIC_MINOR) + misc_deregister(&podev->miscdevice); + tasklet_kill(&podev->done_tasklet); + return 0; +}; + +static struct platform_driver qcota_plat_driver = { + .probe = qcota_probe, + .remove = qcota_remove, + .driver = { + .name = "qcota", + .owner = THIS_MODULE, + }, +}; + +static int _disp_stats(int id) +{ + struct qcota_stat *pstat; + int len = 0; + + pstat = &_qcota_stat[id]; + len = snprintf(_debug_read_buf, DEBUG_MAX_RW_BUF - 1, + "\nQualcomm OTA crypto accelerator %d Statistics:\n", + id + 1); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " F8 request : %d\n", + pstat->f8_req); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " F8 operation success : %d\n", + pstat->f8_op_success); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " F8 operation fail : %d\n", + pstat->f8_op_fail); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " F8 MP request : %d\n", + pstat->f8_mp_req); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " F8 MP operation success: %d\n", + pstat->f8_mp_op_success); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " F8 MP operation fail : %d\n", + pstat->f8_mp_op_fail); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " F9 request : %d\n", + pstat->f9_req); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " F9 operation success : %d\n", + pstat->f9_op_success); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " F9 operation fail : %d\n", + pstat->f9_op_fail); + + return len; +} + +static int _debug_stats_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t _debug_stats_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int rc = -EINVAL; + int qcota = *((int *) file->private_data); + int len; + + len = _disp_stats(qcota); + + rc = simple_read_from_buffer((void __user *) buf, len, + ppos, (void *) _debug_read_buf, len); + + return rc; +} + +static ssize_t _debug_stats_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + + int qcota = *((int *) file->private_data); + + memset((char *)&_qcota_stat[qcota], 0, sizeof(struct qcota_stat)); + return count; +}; + +static const struct file_operations _debug_stats_ops = { + .open = _debug_stats_open, + .read = _debug_stats_read, + .write = _debug_stats_write, +}; + +static int _qcota_debug_init(void) +{ + int rc; + char name[DEBUG_MAX_FNAME]; + int i; + struct dentry *dent; + + _debug_dent = debugfs_create_dir("qcota", NULL); + if (IS_ERR(_debug_dent)) { + pr_err("qcota debugfs_create_dir fail, error %ld\n", + PTR_ERR(_debug_dent)); + return PTR_ERR(_debug_dent); + } + + for (i = 0; i < MAX_OTA_DEVICE; i++) { + snprintf(name, DEBUG_MAX_FNAME-1, "stats-%d", i+1); + _debug_qcota[i] = i; + dent = debugfs_create_file(name, 0644, _debug_dent, + &_debug_qcota[i], &_debug_stats_ops); + if (dent == NULL) { + pr_err("qcota debugfs_create_file fail, error %ld\n", + PTR_ERR(dent)); + rc = PTR_ERR(dent); + goto err; + } + } + return 0; +err: + debugfs_remove_recursive(_debug_dent); + return rc; +} + +static int __init qcota_init(void) +{ + int rc; + + rc = _qcota_debug_init(); + if (rc) + return rc; + return platform_driver_register(&qcota_plat_driver); +} +static void __exit qcota_exit(void) +{ + debugfs_remove_recursive(_debug_dent); + platform_driver_unregister(&qcota_plat_driver); +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Rohit Vaswani "); +MODULE_DESCRIPTION("Qualcomm Ota Crypto driver"); +MODULE_VERSION("1.01"); + +module_init(qcota_init); +module_exit(qcota_exit); diff --git a/drivers/crypto/msm/qce.c b/drivers/crypto/msm/qce.c new file mode 100644 index 00000000000..b945d248ba2 --- /dev/null +++ b/drivers/crypto/msm/qce.c @@ -0,0 +1,2607 @@ +/* Qualcomm Crypto Engine driver. + * + * Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "inc/qce.h" +#include "inc/qcedev.h" +#include "inc/qcryptohw_30.h" +#include "inc/qce_ota.h" + +/* ADM definitions */ +#define LI_SG_CMD (1 << 31) /* last index in the scatter gather cmd */ +#define SRC_INDEX_SG_CMD(index) ((index & 0x3fff) << 16) +#define DST_INDEX_SG_CMD(index) (index & 0x3fff) +#define ADM_DESC_LAST (1 << 31) + +/* Data xfer between DM and CE in blocks of 16 bytes */ +#define ADM_CE_BLOCK_SIZE 16 + +/* Data xfer between DM and CE in blocks of 64 bytes */ +#define ADM_SHA_BLOCK_SIZE 64 + +#define ADM_DESC_LENGTH_MASK 0xffff +#define ADM_DESC_LENGTH(x) (x & ADM_DESC_LENGTH_MASK) + +struct dmov_desc { + uint32_t addr; + uint32_t len; +}; + +#define ADM_STATUS_OK 0x80000002 + +/* Misc definitions */ + +/* QCE max number of descriptor in a descriptor list */ +#define QCE_MAX_NUM_DESC 128 + +/* State of DM channel */ +enum qce_chan_st_enum { + QCE_CHAN_STATE_IDLE = 0, + QCE_CHAN_STATE_IN_PROG = 1, + QCE_CHAN_STATE_COMP = 2, + QCE_CHAN_STATE_LAST +}; + +/* + * CE HW device structure. + * Each engine has an instance of the structure. + * Each engine can only handle one crypto operation at one time. It is up to + * the sw above to ensure single threading of operation on an engine. + */ +struct qce_device { + struct device *pdev; /* Handle to platform_device structure */ + unsigned char *coh_vmem; /* Allocated coherent virtual memory */ + dma_addr_t coh_pmem; /* Allocated coherent physical memory */ + void __iomem *iobase; /* Virtual io base of CE HW */ + unsigned int phy_iobase; /* Physical io base of CE HW */ + struct clk *ce_clk; /* Handle to CE clk */ + unsigned int crci_in; /* CRCI for CE DM IN Channel */ + unsigned int crci_out; /* CRCI for CE DM OUT Channel */ + unsigned int crci_hash; /* CRCI for CE HASH */ + unsigned int chan_ce_in; /* ADM channel used for CE input + * and auth result if authentication + * only operation. */ + unsigned int chan_ce_out; /* ADM channel used for CE output, + and icv for esp */ + + + unsigned int *cmd_pointer_list_ce_in; + dma_addr_t phy_cmd_pointer_list_ce_in; + + unsigned int *cmd_pointer_list_ce_out; + dma_addr_t phy_cmd_pointer_list_ce_out; + + unsigned char *cmd_list_ce_in; + dma_addr_t phy_cmd_list_ce_in; + + unsigned char *cmd_list_ce_out; + dma_addr_t phy_cmd_list_ce_out; + + struct dmov_desc *ce_out_src_desc; + dma_addr_t phy_ce_out_src_desc; + + struct dmov_desc *ce_out_dst_desc; + dma_addr_t phy_ce_out_dst_desc; + + struct dmov_desc *ce_in_src_desc; + dma_addr_t phy_ce_in_src_desc; + + struct dmov_desc *ce_in_dst_desc; + dma_addr_t phy_ce_in_dst_desc; + + unsigned char *ce_out_ignore; + dma_addr_t phy_ce_out_ignore; + + unsigned char *ce_pad; + dma_addr_t phy_ce_pad; + + struct msm_dmov_cmd *chan_ce_in_cmd; + struct msm_dmov_cmd *chan_ce_out_cmd; + + uint32_t ce_out_ignore_size; + + int ce_out_dst_desc_index; + int ce_in_src_desc_index; + + enum qce_chan_st_enum chan_ce_in_state; /* chan ce_in state */ + enum qce_chan_st_enum chan_ce_out_state; /* chan ce_out state */ + + int chan_ce_in_status; /* chan ce_in status */ + int chan_ce_out_status; /* chan ce_out status */ + + + unsigned char *dig_result; + dma_addr_t phy_dig_result; + + /* cached aes key */ + uint32_t aeskey[AES256_KEY_SIZE/sizeof(uint32_t)]; + + uint32_t aes_key_size; /* cached aes key size in bytes */ + int fastaes; /* ce supports fast aes */ + int hmac; /* ce support hmac-sha1 */ + bool ota; /* ce support ota */ + + qce_comp_func_ptr_t qce_cb; /* qce callback function pointer */ + + int assoc_nents; + int src_nents; + int dst_nents; + + void *areq; + enum qce_cipher_mode_enum mode; + + dma_addr_t phy_iv_in; + dma_addr_t phy_ota_src; + dma_addr_t phy_ota_dst; + unsigned int ota_size; + int err; +}; + +/* Standard initialization vector for SHA-1, source: FIPS 180-2 */ +static uint32_t _std_init_vector_sha1[] = { + 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 +}; +/* Standard initialization vector for SHA-256, source: FIPS 180-2 */ +static uint32_t _std_init_vector_sha256[] = { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 +}; + +/* Source: FIPS 197, Figure 7. S-box: substitution values for the byte xy */ +static const uint32_t _s_box[256] = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, + 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, + 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, + 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, + 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, + 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, + 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, + 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, + 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, + 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, + 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, + 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + + +/* + * Source: FIPS 197, Sec 5.2 Key Expansion, Figure 11. Pseudo Code for Key + * Expansion. + */ +static void _aes_expand_key_schedule(uint32_t keysize, uint32_t *AES_KEY, + uint32_t *AES_RND_KEY) +{ + uint32_t i; + uint32_t Nk; + uint32_t Nr, rot_data; + uint32_t Rcon = 0x01000000; + uint32_t temp; + uint32_t data_in; + uint32_t MSB_store; + uint32_t byte_for_sub; + uint32_t word_sub[4]; + + switch (keysize) { + case 192: + Nk = 6; + Nr = 12; + break; + + case 256: + Nk = 8; + Nr = 14; + break; + + case 128: + default: /* default to AES128 */ + Nk = 4; + Nr = 10; + break; + } + + /* key expansion */ + i = 0; + while (i < Nk) { + AES_RND_KEY[i] = AES_KEY[i]; + i = i + 1; + } + + i = Nk; + while (i < (4 * (Nr + 1))) { + temp = AES_RND_KEY[i-1]; + if (Nr == 14) { + switch (i) { + case 8: + Rcon = 0x01000000; + break; + + case 16: + Rcon = 0x02000000; + break; + + case 24: + Rcon = 0x04000000; + break; + + case 32: + Rcon = 0x08000000; + break; + + case 40: + Rcon = 0x10000000; + break; + + case 48: + Rcon = 0x20000000; + break; + + case 56: + Rcon = 0x40000000; + break; + } + } else if (Nr == 12) { + switch (i) { + case 6: + Rcon = 0x01000000; + break; + + case 12: + Rcon = 0x02000000; + break; + + case 18: + Rcon = 0x04000000; + break; + + case 24: + Rcon = 0x08000000; + break; + + case 30: + Rcon = 0x10000000; + break; + + case 36: + Rcon = 0x20000000; + break; + + case 42: + Rcon = 0x40000000; + break; + + case 48: + Rcon = 0x80000000; + break; + } + } else if (Nr == 10) { + switch (i) { + case 4: + Rcon = 0x01000000; + break; + + case 8: + Rcon = 0x02000000; + break; + + case 12: + Rcon = 0x04000000; + break; + + case 16: + Rcon = 0x08000000; + break; + + case 20: + Rcon = 0x10000000; + break; + + case 24: + Rcon = 0x20000000; + break; + + case 28: + Rcon = 0x40000000; + break; + + case 32: + Rcon = 0x80000000; + break; + + case 36: + Rcon = 0x1b000000; + break; + + case 40: + Rcon = 0x36000000; + break; + } + } + + if ((i % Nk) == 0) { + data_in = temp; + MSB_store = (data_in >> 24 & 0xff); + rot_data = (data_in << 8) | MSB_store; + byte_for_sub = rot_data; + word_sub[0] = _s_box[(byte_for_sub & 0xff)]; + word_sub[1] = (_s_box[((byte_for_sub & 0xff00) >> 8)] + << 8); + word_sub[2] = (_s_box[((byte_for_sub & 0xff0000) >> 16)] + << 16); + word_sub[3] = (_s_box[((byte_for_sub & 0xff000000) + >> 24)] << 24); + word_sub[0] = word_sub[0] | word_sub[1] | word_sub[2] | + word_sub[3]; + temp = word_sub[0] ^ Rcon; + } else if ((Nk > 6) && ((i % Nk) == 4)) { + byte_for_sub = temp; + word_sub[0] = _s_box[(byte_for_sub & 0xff)]; + word_sub[1] = (_s_box[((byte_for_sub & 0xff00) >> 8)] + << 8); + word_sub[2] = (_s_box[((byte_for_sub & 0xff0000) >> 16)] + << 16); + word_sub[3] = (_s_box[((byte_for_sub & 0xff000000) >> + 24)] << 24); + word_sub[0] = word_sub[0] | word_sub[1] | word_sub[2] | + word_sub[3]; + temp = word_sub[0]; + } + + AES_RND_KEY[i] = AES_RND_KEY[i-Nk]^temp; + i = i+1; + } +} + +static void _byte_stream_to_net_words(uint32_t *iv, unsigned char *b, + unsigned int len) +{ + unsigned n; + + n = len / sizeof(uint32_t) ; + for (; n > 0; n--) { + *iv = ((*b << 24) & 0xff000000) | + (((*(b+1)) << 16) & 0xff0000) | + (((*(b+2)) << 8) & 0xff00) | + (*(b+3) & 0xff); + b += sizeof(uint32_t); + iv++; + } + + n = len % sizeof(uint32_t); + if (n == 3) { + *iv = ((*b << 24) & 0xff000000) | + (((*(b+1)) << 16) & 0xff0000) | + (((*(b+2)) << 8) & 0xff00) ; + } else if (n == 2) { + *iv = ((*b << 24) & 0xff000000) | + (((*(b+1)) << 16) & 0xff0000) ; + } else if (n == 1) { + *iv = ((*b << 24) & 0xff000000) ; + } +} + +static void _net_words_to_byte_stream(uint32_t *iv, unsigned char *b, + unsigned int len) +{ + unsigned n = len / sizeof(uint32_t); + + for (; n > 0; n--) { + *b++ = (unsigned char) ((*iv >> 24) & 0xff); + *b++ = (unsigned char) ((*iv >> 16) & 0xff); + *b++ = (unsigned char) ((*iv >> 8) & 0xff); + *b++ = (unsigned char) (*iv & 0xff); + iv++; + } + n = len % sizeof(uint32_t); + if (n == 3) { + *b++ = (unsigned char) ((*iv >> 24) & 0xff); + *b++ = (unsigned char) ((*iv >> 16) & 0xff); + *b = (unsigned char) ((*iv >> 8) & 0xff); + } else if (n == 2) { + *b++ = (unsigned char) ((*iv >> 24) & 0xff); + *b = (unsigned char) ((*iv >> 16) & 0xff); + } else if (n == 1) { + *b = (unsigned char) ((*iv >> 24) & 0xff); + } +} + +static int count_sg(struct scatterlist *sg, int nbytes) +{ + int i; + + for (i = 0; nbytes > 0; i++, sg = sg_next(sg)) + nbytes -= sg->length; + return i; +} + +static int dma_map_pmem_sg(struct buf_info *pmem, unsigned entries, + struct scatterlist *sg) +{ + int i = 0; + for (i = 0; i < entries; i++) { + + sg->dma_address = (dma_addr_t)pmem->offset; + sg++; + pmem++; + } + return 0; +} + +static int _probe_ce_engine(struct qce_device *pce_dev) +{ + unsigned int val; + unsigned int rev; + unsigned int eng_availability; /* engine available functions */ + + val = readl_relaxed(pce_dev->iobase + CRYPTO_STATUS_REG); + if ((val & 0xfffffff) != 0x0200004) { + dev_err(pce_dev->pdev, + "unknown Qualcomm crypto device at 0x%x 0x%x\n", + pce_dev->phy_iobase, val); + return -EIO; + }; + rev = (val & CRYPTO_CORE_REV_MASK) >> CRYPTO_CORE_REV; + if (rev == 0x2) { + dev_info(pce_dev->pdev, + "Qualcomm Crypto 3e device found at 0x%x\n", + pce_dev->phy_iobase); + } else if (rev == 0x1) { + dev_info(pce_dev->pdev, + "Qualcomm Crypto 3 device found at 0x%x\n", + pce_dev->phy_iobase); + } else if (rev == 0x0) { + dev_info(pce_dev->pdev, + "Qualcomm Crypto 2 device found at 0x%x\n", + pce_dev->phy_iobase); + } else { + dev_err(pce_dev->pdev, + "unknown Qualcomm crypto device at 0x%x\n", + pce_dev->phy_iobase); + return -EIO; + } + + eng_availability = readl_relaxed(pce_dev->iobase + + CRYPTO_ENGINES_AVAIL); + + if (((eng_availability & CRYPTO_AES_SEL_MASK) >> CRYPTO_AES_SEL) + == CRYPTO_AES_SEL_FAST) + pce_dev->fastaes = 1; + else + pce_dev->fastaes = 0; + + if (eng_availability & (1 << CRYPTO_HMAC_SEL)) + pce_dev->hmac = 1; + else + pce_dev->hmac = 0; + + if ((eng_availability & (1 << CRYPTO_F9_SEL)) && + (eng_availability & (1 << CRYPTO_F8_SEL))) + pce_dev->ota = true; + else + pce_dev->ota = false; + + pce_dev->aes_key_size = 0; + + return 0; +}; + +static int _init_ce_engine(struct qce_device *pce_dev) +{ + unsigned int val; + + /* reset qce */ + writel_relaxed(1 << CRYPTO_SW_RST, pce_dev->iobase + CRYPTO_CONFIG_REG); + + /* Ensure previous instruction (write to reset bit) + * was completed. + */ + mb(); + /* configure ce */ + val = (1 << CRYPTO_MASK_DOUT_INTR) | (1 << CRYPTO_MASK_DIN_INTR) | + (1 << CRYPTO_MASK_AUTH_DONE_INTR) | + (1 << CRYPTO_MASK_ERR_INTR); + writel_relaxed(val, pce_dev->iobase + CRYPTO_CONFIG_REG); + + if (_probe_ce_engine(pce_dev) < 0) + return -EIO; + if (readl_relaxed(pce_dev->iobase + CRYPTO_CONFIG_REG) != val) { + dev_err(pce_dev->pdev, + "unknown Qualcomm crypto device at 0x%x\n", + pce_dev->phy_iobase); + return -EIO; + }; + return 0; +}; + +static int _sha_ce_setup(struct qce_device *pce_dev, struct qce_sha_req *sreq) +{ + uint32_t auth32[SHA256_DIGEST_SIZE / sizeof(uint32_t)]; + uint32_t diglen; + int rc; + int i; + uint32_t cfg = 0; + + /* if not the last, the size has to be on the block boundary */ + if (sreq->last_blk == 0 && (sreq->size % SHA256_BLOCK_SIZE)) + return -EIO; + + switch (sreq->alg) { + case QCE_HASH_SHA1: + diglen = SHA1_DIGEST_SIZE; + break; + case QCE_HASH_SHA256: + diglen = SHA256_DIGEST_SIZE; + break; + default: + return -EINVAL; + } + /* + * write 20/32 bytes, 5/8 words into auth_iv + * for SHA1/SHA256 + */ + + if (sreq->first_blk) { + if (sreq->alg == QCE_HASH_SHA1) { + for (i = 0; i < 5; i++) + auth32[i] = _std_init_vector_sha1[i]; + } else { + for (i = 0; i < 8; i++) + auth32[i] = _std_init_vector_sha256[i]; + } + } else + _byte_stream_to_net_words(auth32, sreq->digest, diglen); + + rc = clk_enable(pce_dev->ce_clk); + if (rc) + return rc; + + writel_relaxed(auth32[0], pce_dev->iobase + CRYPTO_AUTH_IV0_REG); + writel_relaxed(auth32[1], pce_dev->iobase + CRYPTO_AUTH_IV1_REG); + writel_relaxed(auth32[2], pce_dev->iobase + CRYPTO_AUTH_IV2_REG); + writel_relaxed(auth32[3], pce_dev->iobase + CRYPTO_AUTH_IV3_REG); + writel_relaxed(auth32[4], pce_dev->iobase + CRYPTO_AUTH_IV4_REG); + + if (sreq->alg == QCE_HASH_SHA256) { + writel_relaxed(auth32[5], pce_dev->iobase + + CRYPTO_AUTH_IV5_REG); + writel_relaxed(auth32[6], pce_dev->iobase + + CRYPTO_AUTH_IV6_REG); + writel_relaxed(auth32[7], pce_dev->iobase + + CRYPTO_AUTH_IV7_REG); + } + /* write auth_bytecnt 0/1, start with 0 */ + writel_relaxed(sreq->auth_data[0], pce_dev->iobase + + CRYPTO_AUTH_BYTECNT0_REG); + writel_relaxed(sreq->auth_data[1], pce_dev->iobase + + CRYPTO_AUTH_BYTECNT1_REG); + + /* write auth_seg_cfg */ + writel_relaxed(sreq->size << CRYPTO_AUTH_SEG_SIZE, + pce_dev->iobase + CRYPTO_AUTH_SEG_CFG_REG); + + /* + * write seg_cfg + */ + + if (sreq->alg == QCE_HASH_SHA1) + cfg |= (CRYPTO_AUTH_SIZE_SHA1 << CRYPTO_AUTH_SIZE); + else + cfg = (CRYPTO_AUTH_SIZE_SHA256 << CRYPTO_AUTH_SIZE); + + if (sreq->first_blk) + cfg |= 1 << CRYPTO_FIRST; + if (sreq->last_blk) + cfg |= 1 << CRYPTO_LAST; + cfg |= CRYPTO_AUTH_ALG_SHA << CRYPTO_AUTH_ALG; + writel_relaxed(cfg, pce_dev->iobase + CRYPTO_SEG_CFG_REG); + + /* write seg_size */ + writel_relaxed(sreq->size, pce_dev->iobase + CRYPTO_SEG_SIZE_REG); + + /* issue go to crypto */ + writel_relaxed(1 << CRYPTO_GO, pce_dev->iobase + CRYPTO_GOPROC_REG); + /* Ensure previous instructions (setting the GO register) + * was completed before issuing a DMA transfer request + */ + mb(); + + return 0; +} + +static int _ce_setup(struct qce_device *pce_dev, struct qce_req *q_req, + uint32_t totallen, uint32_t coffset) +{ + uint32_t hmackey[HMAC_KEY_SIZE/sizeof(uint32_t)] = { + 0, 0, 0, 0, 0}; + uint32_t enckey32[MAX_CIPHER_KEY_SIZE/sizeof(uint32_t)] = { + 0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t enciv32[MAX_IV_LENGTH / sizeof(uint32_t)] = { + 0, 0, 0, 0}; + uint32_t enck_size_in_word = q_req->encklen / sizeof(uint32_t); + int aes_key_chg; + int i, rc; + uint32_t aes_round_key[CRYPTO_AES_RNDKEYS]; + uint32_t cfg; + uint32_t ivsize = q_req->ivsize; + + rc = clk_enable(pce_dev->ce_clk); + if (rc) + return rc; + + cfg = (1 << CRYPTO_FIRST) | (1 << CRYPTO_LAST); + if (q_req->op == QCE_REQ_AEAD) { + + /* do authentication setup */ + + cfg |= (CRYPTO_AUTH_SIZE_HMAC_SHA1 << CRYPTO_AUTH_SIZE)| + (CRYPTO_AUTH_ALG_SHA << CRYPTO_AUTH_ALG); + + /* write sha1 init vector */ + writel_relaxed(_std_init_vector_sha1[0], + pce_dev->iobase + CRYPTO_AUTH_IV0_REG); + writel_relaxed(_std_init_vector_sha1[1], + pce_dev->iobase + CRYPTO_AUTH_IV1_REG); + writel_relaxed(_std_init_vector_sha1[2], + pce_dev->iobase + CRYPTO_AUTH_IV2_REG); + writel_relaxed(_std_init_vector_sha1[3], + pce_dev->iobase + CRYPTO_AUTH_IV3_REG); + writel_relaxed(_std_init_vector_sha1[4], + pce_dev->iobase + CRYPTO_AUTH_IV4_REG); + /* write hmac key */ + _byte_stream_to_net_words(hmackey, q_req->authkey, + q_req->authklen); + writel_relaxed(hmackey[0], pce_dev->iobase + + CRYPTO_AUTH_IV5_REG); + writel_relaxed(hmackey[1], pce_dev->iobase + + CRYPTO_AUTH_IV6_REG); + writel_relaxed(hmackey[2], pce_dev->iobase + + CRYPTO_AUTH_IV7_REG); + writel_relaxed(hmackey[3], pce_dev->iobase + + CRYPTO_AUTH_IV8_REG); + writel_relaxed(hmackey[4], pce_dev->iobase + + CRYPTO_AUTH_IV9_REG); + writel_relaxed(0, pce_dev->iobase + CRYPTO_AUTH_BYTECNT0_REG); + writel_relaxed(0, pce_dev->iobase + CRYPTO_AUTH_BYTECNT1_REG); + + /* write auth_seg_cfg */ + writel_relaxed((totallen << CRYPTO_AUTH_SEG_SIZE) & 0xffff0000, + pce_dev->iobase + CRYPTO_AUTH_SEG_CFG_REG); + + } + + _byte_stream_to_net_words(enckey32, q_req->enckey, q_req->encklen); + + switch (q_req->mode) { + case QCE_MODE_ECB: + cfg |= (CRYPTO_ENCR_MODE_ECB << CRYPTO_ENCR_MODE); + break; + + case QCE_MODE_CBC: + cfg |= (CRYPTO_ENCR_MODE_CBC << CRYPTO_ENCR_MODE); + break; + + case QCE_MODE_CTR: + default: + cfg |= (CRYPTO_ENCR_MODE_CTR << CRYPTO_ENCR_MODE); + break; + } + pce_dev->mode = q_req->mode; + + switch (q_req->alg) { + case CIPHER_ALG_DES: + if (q_req->mode != QCE_MODE_ECB) { + _byte_stream_to_net_words(enciv32, q_req->iv, ivsize); + writel_relaxed(enciv32[0], pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG); + writel_relaxed(enciv32[1], pce_dev->iobase + + CRYPTO_CNTR1_IV1_REG); + } + writel_relaxed(enckey32[0], pce_dev->iobase + + CRYPTO_DES_KEY0_REG); + writel_relaxed(enckey32[1], pce_dev->iobase + + CRYPTO_DES_KEY1_REG); + cfg |= ((CRYPTO_ENCR_KEY_SZ_DES << CRYPTO_ENCR_KEY_SZ) | + (CRYPTO_ENCR_ALG_DES << CRYPTO_ENCR_ALG)); + break; + + case CIPHER_ALG_3DES: + if (q_req->mode != QCE_MODE_ECB) { + _byte_stream_to_net_words(enciv32, q_req->iv, ivsize); + writel_relaxed(enciv32[0], pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG); + writel_relaxed(enciv32[1], pce_dev->iobase + + CRYPTO_CNTR1_IV1_REG); + } + writel_relaxed(enckey32[0], pce_dev->iobase + + CRYPTO_DES_KEY0_REG); + writel_relaxed(enckey32[1], pce_dev->iobase + + CRYPTO_DES_KEY1_REG); + writel_relaxed(enckey32[2], pce_dev->iobase + + CRYPTO_DES_KEY2_REG); + writel_relaxed(enckey32[3], pce_dev->iobase + + CRYPTO_DES_KEY3_REG); + writel_relaxed(enckey32[4], pce_dev->iobase + + CRYPTO_DES_KEY4_REG); + writel_relaxed(enckey32[5], pce_dev->iobase + + CRYPTO_DES_KEY5_REG); + cfg |= ((CRYPTO_ENCR_KEY_SZ_3DES << CRYPTO_ENCR_KEY_SZ) | + (CRYPTO_ENCR_ALG_DES << CRYPTO_ENCR_ALG)); + break; + + case CIPHER_ALG_AES: + default: + if (q_req->mode != QCE_MODE_ECB) { + _byte_stream_to_net_words(enciv32, q_req->iv, ivsize); + writel_relaxed(enciv32[0], pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG); + writel_relaxed(enciv32[1], pce_dev->iobase + + CRYPTO_CNTR1_IV1_REG); + writel_relaxed(enciv32[2], pce_dev->iobase + + CRYPTO_CNTR2_IV2_REG); + writel_relaxed(enciv32[3], pce_dev->iobase + + CRYPTO_CNTR3_IV3_REG); + } + /* set number of counter bits */ + writel_relaxed(0xffff, pce_dev->iobase + CRYPTO_CNTR_MASK_REG); + + if (q_req->op == QCE_REQ_ABLK_CIPHER_NO_KEY) { + cfg |= (CRYPTO_ENCR_KEY_SZ_AES128 << + CRYPTO_ENCR_KEY_SZ); + cfg |= CRYPTO_ENCR_ALG_AES << CRYPTO_ENCR_ALG; + } else { + switch (q_req->encklen) { + case AES128_KEY_SIZE: + cfg |= (CRYPTO_ENCR_KEY_SZ_AES128 << + CRYPTO_ENCR_KEY_SZ); + break; + case AES192_KEY_SIZE: + cfg |= (CRYPTO_ENCR_KEY_SZ_AES192 << + CRYPTO_ENCR_KEY_SZ); + break; + case AES256_KEY_SIZE: + default: + cfg |= (CRYPTO_ENCR_KEY_SZ_AES256 << + CRYPTO_ENCR_KEY_SZ); + + /* check for null key. If null, use hw key*/ + for (i = 0; i < enck_size_in_word; i++) { + if (enckey32[i] != 0) + break; + } + if (i == enck_size_in_word) + cfg |= 1 << CRYPTO_USE_HW_KEY; + break; + } /* end of switch (q_req->encklen) */ + + cfg |= CRYPTO_ENCR_ALG_AES << CRYPTO_ENCR_ALG; + if (pce_dev->aes_key_size != q_req->encklen) + aes_key_chg = 1; + else { + for (i = 0; i < enck_size_in_word; i++) { + if (enckey32[i] != pce_dev->aeskey[i]) + break; + } + aes_key_chg = (i == enck_size_in_word) ? 0 : 1; + } + + if (aes_key_chg) { + if (pce_dev->fastaes) { + for (i = 0; i < enck_size_in_word; + i++) { + writel_relaxed(enckey32[i], + pce_dev->iobase + + CRYPTO_AES_RNDKEY0 + + (i * sizeof(uint32_t))); + } + } else { + /* size in bit */ + _aes_expand_key_schedule( + q_req->encklen * 8, + enckey32, aes_round_key); + + for (i = 0; i < CRYPTO_AES_RNDKEYS; + i++) { + writel_relaxed(aes_round_key[i], + pce_dev->iobase + + CRYPTO_AES_RNDKEY0 + + (i * sizeof(uint32_t))); + } + } + + pce_dev->aes_key_size = q_req->encklen; + for (i = 0; i < enck_size_in_word; i++) + pce_dev->aeskey[i] = enckey32[i]; + } /*if (aes_key_chg) { */ + } /* else of if (q_req->op == QCE_REQ_ABLK_CIPHER_NO_KEY) */ + break; + } /* end of switch (q_req->mode) */ + + if (q_req->dir == QCE_ENCRYPT) + cfg |= (1 << CRYPTO_AUTH_POS); + cfg |= ((q_req->dir == QCE_ENCRYPT) ? 1 : 0) << CRYPTO_ENCODE; + + /* write encr seg cfg */ + writel_relaxed((q_req->cryptlen << CRYPTO_ENCR_SEG_SIZE) | + (coffset & 0xffff), /* cipher offset */ + pce_dev->iobase + CRYPTO_ENCR_SEG_CFG_REG); + + /* write seg cfg and size */ + writel_relaxed(cfg, pce_dev->iobase + CRYPTO_SEG_CFG_REG); + writel_relaxed(totallen, pce_dev->iobase + CRYPTO_SEG_SIZE_REG); + + /* issue go to crypto */ + writel_relaxed(1 << CRYPTO_GO, pce_dev->iobase + CRYPTO_GOPROC_REG); + /* Ensure previous instructions (setting the GO register) + * was completed before issuing a DMA transfer request + */ + mb(); + return 0; +}; + +static int _aead_complete(struct qce_device *pce_dev) +{ + struct aead_request *areq; + struct crypto_aead *aead; + uint32_t ivsize; + uint32_t iv_out[4]; + unsigned char iv[4 * sizeof(uint32_t)]; + uint32_t status; + + areq = (struct aead_request *) pce_dev->areq; + aead = crypto_aead_reqtfm(areq); + ivsize = crypto_aead_ivsize(aead); + + if (areq->src != areq->dst) { + dma_unmap_sg(pce_dev->pdev, areq->dst, pce_dev->dst_nents, + DMA_FROM_DEVICE); + } + dma_unmap_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + dma_unmap_single(pce_dev->pdev, pce_dev->phy_iv_in, + ivsize, DMA_TO_DEVICE); + dma_unmap_sg(pce_dev->pdev, areq->assoc, pce_dev->assoc_nents, + DMA_TO_DEVICE); + + /* check ce error status */ + status = readl_relaxed(pce_dev->iobase + CRYPTO_STATUS_REG); + if (status & (1 << CRYPTO_SW_ERR)) { + pce_dev->err++; + dev_err(pce_dev->pdev, + "Qualcomm Crypto Error at 0x%x, status%x\n", + pce_dev->phy_iobase, status); + _init_ce_engine(pce_dev); + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, pce_dev->dig_result, NULL, -ENXIO); + return 0; + }; + + /* get iv out */ + if (pce_dev->mode == QCE_MODE_ECB) { + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, pce_dev->dig_result, NULL, + pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } else { + + iv_out[0] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG); + iv_out[1] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR1_IV1_REG); + iv_out[2] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR2_IV2_REG); + iv_out[3] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR3_IV3_REG); + + _net_words_to_byte_stream(iv_out, iv, sizeof(iv)); + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, pce_dev->dig_result, iv, + pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + }; + return 0; +}; + +static void _sha_complete(struct qce_device *pce_dev) +{ + + struct ahash_request *areq; + uint32_t auth_data[2]; + uint32_t status; + + areq = (struct ahash_request *) pce_dev->areq; + dma_unmap_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + DMA_TO_DEVICE); + + /* check ce error status */ + status = readl_relaxed(pce_dev->iobase + CRYPTO_STATUS_REG); + if (status & (1 << CRYPTO_SW_ERR)) { + pce_dev->err++; + dev_err(pce_dev->pdev, + "Qualcomm Crypto Error at 0x%x, status%x\n", + pce_dev->phy_iobase, status); + _init_ce_engine(pce_dev); + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, pce_dev->dig_result, NULL, -ENXIO); + return; + }; + + auth_data[0] = readl_relaxed(pce_dev->iobase + + CRYPTO_AUTH_BYTECNT0_REG); + auth_data[1] = readl_relaxed(pce_dev->iobase + + CRYPTO_AUTH_BYTECNT1_REG); + /* Ensure previous instruction (retriving byte count information) + * was completed before disabling the clk. + */ + mb(); + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, pce_dev->dig_result, (unsigned char *)auth_data, + pce_dev->chan_ce_in_status); +}; + +static int _ablk_cipher_complete(struct qce_device *pce_dev) +{ + struct ablkcipher_request *areq; + uint32_t iv_out[4]; + unsigned char iv[4 * sizeof(uint32_t)]; + uint32_t status; + + areq = (struct ablkcipher_request *) pce_dev->areq; + + if (areq->src != areq->dst) { + dma_unmap_sg(pce_dev->pdev, areq->dst, + pce_dev->dst_nents, DMA_FROM_DEVICE); + } + dma_unmap_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + + /* check ce error status */ + status = readl_relaxed(pce_dev->iobase + CRYPTO_STATUS_REG); + if (status & (1 << CRYPTO_SW_ERR)) { + pce_dev->err++; + dev_err(pce_dev->pdev, + "Qualcomm Crypto Error at 0x%x, status%x\n", + pce_dev->phy_iobase, status); + _init_ce_engine(pce_dev); + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, NULL, NULL, -ENXIO); + return 0; + }; + + /* get iv out */ + if (pce_dev->mode == QCE_MODE_ECB) { + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, NULL, NULL, pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } else { + iv_out[0] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG); + iv_out[1] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR1_IV1_REG); + iv_out[2] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR2_IV2_REG); + iv_out[3] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR3_IV3_REG); + + _net_words_to_byte_stream(iv_out, iv, sizeof(iv)); + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, NULL, iv, pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } + + return 0; +}; + +static int _ablk_cipher_use_pmem_complete(struct qce_device *pce_dev) +{ + struct ablkcipher_request *areq; + uint32_t iv_out[4]; + unsigned char iv[4 * sizeof(uint32_t)]; + uint32_t status; + + areq = (struct ablkcipher_request *) pce_dev->areq; + + /* check ce error status */ + status = readl_relaxed(pce_dev->iobase + CRYPTO_STATUS_REG); + if (status & (1 << CRYPTO_SW_ERR)) { + pce_dev->err++; + dev_err(pce_dev->pdev, + "Qualcomm Crypto Error at 0x%x, status%x\n", + pce_dev->phy_iobase, status); + _init_ce_engine(pce_dev); + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, NULL, NULL, -ENXIO); + return 0; + }; + + /* get iv out */ + if (pce_dev->mode == QCE_MODE_ECB) { + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, NULL, NULL, pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } else { + iv_out[0] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG); + iv_out[1] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR1_IV1_REG); + iv_out[2] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR2_IV2_REG); + iv_out[3] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR3_IV3_REG); + + _net_words_to_byte_stream(iv_out, iv, sizeof(iv)); + clk_disable(pce_dev->ce_clk); + pce_dev->qce_cb(areq, NULL, iv, pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } + + return 0; +}; + + + +static int _chain_sg_buffer_in(struct qce_device *pce_dev, + struct scatterlist *sg, unsigned int nbytes) +{ + unsigned int len; + unsigned int dlen; + struct dmov_desc *pdesc; + + pdesc = pce_dev->ce_in_src_desc + pce_dev->ce_in_src_desc_index; + /* + * Two consective chunks may be handled by the old + * buffer descriptor. + */ + while (nbytes > 0) { + len = min(nbytes, sg_dma_len(sg)); + dlen = pdesc->len & ADM_DESC_LENGTH_MASK; + nbytes -= len; + if (dlen == 0) { + pdesc->addr = sg_dma_address(sg); + pdesc->len = len; + } else if (sg_dma_address(sg) == (pdesc->addr + dlen)) + pdesc->len = dlen + len; + else { + pce_dev->ce_in_src_desc_index++; + if (pce_dev->ce_in_src_desc_index >= QCE_MAX_NUM_DESC) + return -ENOMEM; + pdesc++; + pdesc->len = len; + pdesc->addr = sg_dma_address(sg); + } + if (nbytes > 0) + sg = sg_next(sg); + } + return 0; +} + +static int _chain_pm_buffer_in(struct qce_device *pce_dev, + unsigned int pmem, unsigned int nbytes) +{ + unsigned int dlen; + struct dmov_desc *pdesc; + + pdesc = pce_dev->ce_in_src_desc + pce_dev->ce_in_src_desc_index; + dlen = pdesc->len & ADM_DESC_LENGTH_MASK; + if (dlen == 0) { + pdesc->addr = pmem; + pdesc->len = nbytes; + } else if (pmem == (pdesc->addr + dlen)) { + pdesc->len = dlen + nbytes; + } else { + pce_dev->ce_in_src_desc_index++; + if (pce_dev->ce_in_src_desc_index >= QCE_MAX_NUM_DESC) + return -ENOMEM; + pdesc++; + pdesc->len = nbytes; + pdesc->addr = pmem; + } + return 0; +} + +static void _chain_buffer_in_init(struct qce_device *pce_dev) +{ + struct dmov_desc *pdesc; + + pce_dev->ce_in_src_desc_index = 0; + pdesc = pce_dev->ce_in_src_desc; + pdesc->len = 0; +} + +static void _ce_in_final(struct qce_device *pce_dev, int ncmd, unsigned total) +{ + struct dmov_desc *pdesc; + dmov_sg *pcmd; + + pdesc = pce_dev->ce_in_src_desc + pce_dev->ce_in_src_desc_index; + pdesc->len |= ADM_DESC_LAST; + pdesc = pce_dev->ce_in_dst_desc; + pdesc->len = ADM_DESC_LAST | total; + + pcmd = (dmov_sg *) pce_dev->cmd_list_ce_in; + if (ncmd == 1) + pcmd->cmd |= CMD_LC; + else { + dmov_s *pscmd; + + pcmd->cmd &= ~CMD_LC; + pcmd++; + pscmd = (dmov_s *)pcmd; + pscmd->cmd |= CMD_LC; + } + +#ifdef QCE_DEBUG + dev_info(pce_dev->pdev, "_ce_in_final %d\n", + pce_dev->ce_in_src_desc_index); +#endif +} + +#ifdef QCE_DEBUG +static void _ce_in_dump(struct qce_device *pce_dev) +{ + int i; + struct dmov_desc *pdesc; + + dev_info(pce_dev->pdev, "_ce_in_dump\n"); + for (i = 0; i <= pce_dev->ce_in_src_desc_index; i++) { + pdesc = pce_dev->ce_in_src_desc + i; + dev_info(pce_dev->pdev, "%x , %x\n", pdesc->addr, + pdesc->len); + } + pdesc = pce_dev->ce_in_dst_desc; + dev_info(pce_dev->pdev, "dst - %x , %x\n", pdesc->addr, + pdesc->len); +}; + +static void _ce_out_dump(struct qce_device *pce_dev) +{ + int i; + struct dmov_desc *pdesc; + + dev_info(pce_dev->pdev, "_ce_out_dump\n"); + for (i = 0; i <= pce_dev->ce_out_dst_desc_index; i++) { + pdesc = pce_dev->ce_out_dst_desc + i; + dev_info(pce_dev->pdev, "%x , %x\n", pdesc->addr, + pdesc->len); + } + pdesc = pce_dev->ce_out_src_desc; + dev_info(pce_dev->pdev, "src - %x , %x\n", pdesc->addr, + pdesc->len); +}; +#endif + +static int _chain_sg_buffer_out(struct qce_device *pce_dev, + struct scatterlist *sg, unsigned int nbytes) +{ + unsigned int len; + unsigned int dlen; + struct dmov_desc *pdesc; + + pdesc = pce_dev->ce_out_dst_desc + pce_dev->ce_out_dst_desc_index; + /* + * Two consective chunks may be handled by the old + * buffer descriptor. + */ + while (nbytes > 0) { + len = min(nbytes, sg_dma_len(sg)); + dlen = pdesc->len & ADM_DESC_LENGTH_MASK; + nbytes -= len; + if (dlen == 0) { + pdesc->addr = sg_dma_address(sg); + pdesc->len = len; + } else if (sg_dma_address(sg) == (pdesc->addr + dlen)) { + pdesc->len = dlen + len; + } else { + pce_dev->ce_out_dst_desc_index++; + if (pce_dev->ce_out_dst_desc_index >= QCE_MAX_NUM_DESC) + return -EIO; + pdesc++; + pdesc->len = len; + pdesc->addr = sg_dma_address(sg); + } + if (nbytes > 0) + sg = sg_next(sg); + } + return 0; +} + +static int _chain_pm_buffer_out(struct qce_device *pce_dev, + unsigned int pmem, unsigned int nbytes) +{ + unsigned int dlen; + struct dmov_desc *pdesc; + + pdesc = pce_dev->ce_out_dst_desc + pce_dev->ce_out_dst_desc_index; + dlen = pdesc->len & ADM_DESC_LENGTH_MASK; + + if (dlen == 0) { + pdesc->addr = pmem; + pdesc->len = nbytes; + } else if (pmem == (pdesc->addr + dlen)) { + pdesc->len = dlen + nbytes; + } else { + pce_dev->ce_out_dst_desc_index++; + if (pce_dev->ce_out_dst_desc_index >= QCE_MAX_NUM_DESC) + return -EIO; + pdesc++; + pdesc->len = nbytes; + pdesc->addr = pmem; + } + return 0; +}; + +static void _chain_buffer_out_init(struct qce_device *pce_dev) +{ + struct dmov_desc *pdesc; + + pce_dev->ce_out_dst_desc_index = 0; + pdesc = pce_dev->ce_out_dst_desc; + pdesc->len = 0; +}; + +static void _ce_out_final(struct qce_device *pce_dev, int ncmd, unsigned total) +{ + struct dmov_desc *pdesc; + dmov_sg *pcmd; + + pdesc = pce_dev->ce_out_dst_desc + pce_dev->ce_out_dst_desc_index; + pdesc->len |= ADM_DESC_LAST; + pdesc = pce_dev->ce_out_src_desc; + pdesc->len = ADM_DESC_LAST | total; + pcmd = (dmov_sg *) pce_dev->cmd_list_ce_out; + if (ncmd == 1) + pcmd->cmd |= CMD_LC; + else { + dmov_s *pscmd; + + pcmd->cmd &= ~CMD_LC; + pcmd++; + pscmd = (dmov_s *)pcmd; + pscmd->cmd |= CMD_LC; + } +#ifdef QCE_DEBUG + dev_info(pce_dev->pdev, "_ce_out_final %d\n", + pce_dev->ce_out_dst_desc_index); +#endif + +}; + +static void _aead_ce_in_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else + pce_dev->chan_ce_in_status = 0; + + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _aead_complete(pce_dev); + } +}; + +static void _aead_ce_out_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_out_status = -1; + } else { + pce_dev->chan_ce_out_status = 0; + }; + + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _aead_complete(pce_dev); + } + +}; + +static void _sha_ce_in_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else + pce_dev->chan_ce_in_status = 0; + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + _sha_complete(pce_dev); +}; + +static void _ablk_cipher_ce_in_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else + pce_dev->chan_ce_in_status = 0; + + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _ablk_cipher_complete(pce_dev); + } +}; + +static void _ablk_cipher_ce_out_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_out_status = -1; + } else { + pce_dev->chan_ce_out_status = 0; + }; + + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _ablk_cipher_complete(pce_dev); + } +}; + + +static void _ablk_cipher_ce_in_call_back_pmem(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else + pce_dev->chan_ce_in_status = 0; + + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _ablk_cipher_use_pmem_complete(pce_dev); + } +}; + +static void _ablk_cipher_ce_out_call_back_pmem(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_out_status = -1; + } else { + pce_dev->chan_ce_out_status = 0; + }; + + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _ablk_cipher_use_pmem_complete(pce_dev); + } +}; + +static int _setup_cmd_template(struct qce_device *pce_dev) +{ + dmov_sg *pcmd; + dmov_s *pscmd; + struct dmov_desc *pdesc; + unsigned char *vaddr; + + /* Divide up the 4K coherent memory */ + /* 1. ce_in channel 1st command src descriptors, 128 entries */ + vaddr = pce_dev->coh_vmem; + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + pce_dev->ce_in_src_desc = (struct dmov_desc *) vaddr; + pce_dev->phy_ce_in_src_desc = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + /* 2. ce_in channel 1st command dst descriptor, 1 entry */ + pce_dev->ce_in_dst_desc = (struct dmov_desc *) vaddr; + pce_dev->phy_ce_in_dst_desc = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(struct dmov_desc) ; + + /* + * 3. ce_in channel command list of one scatter gather command + * and one simple command. + */ + pce_dev->cmd_list_ce_in = vaddr; + pce_dev->phy_cmd_list_ce_in = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(dmov_s) + sizeof(dmov_sg); + + /* 4. authentication result. */ + pce_dev->dig_result = vaddr; + pce_dev->phy_dig_result = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + SHA256_DIGESTSIZE; + + /* + * 5. ce_out channel command list of one scatter gather command + * and one simple command. + */ + pce_dev->cmd_list_ce_out = vaddr; + pce_dev->phy_cmd_list_ce_out = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(dmov_s) + sizeof(dmov_sg); + + /* 6. ce_out channel command src descriptors, 1 entry */ + pce_dev->ce_out_src_desc = (struct dmov_desc *) vaddr; + pce_dev->phy_ce_out_src_desc = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(struct dmov_desc) ; + + /* 7. ce_out channel command dst descriptors, 128 entries. */ + pce_dev->ce_out_dst_desc = (struct dmov_desc *) vaddr; + pce_dev->phy_ce_out_dst_desc = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + /* 8. pad area. */ + pce_dev->ce_pad = vaddr; + pce_dev->phy_ce_pad = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + ADM_CE_BLOCK_SIZE; + + /* 9. ce_in channel command pointer list. */ + pce_dev->cmd_pointer_list_ce_in = (unsigned int *) vaddr; + pce_dev->phy_cmd_pointer_list_ce_in = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(unsigned char *); + vaddr = (unsigned char *) ALIGN(((unsigned int) vaddr), 8); + + /* 10. ce_ou channel command pointer list. */ + pce_dev->cmd_pointer_list_ce_out = (unsigned int *) vaddr; + pce_dev->phy_cmd_pointer_list_ce_out = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(unsigned char *); + + /* 11. throw away area to store by-pass data from ce_out. */ + pce_dev->ce_out_ignore = (unsigned char *) vaddr; + pce_dev->phy_ce_out_ignore = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + pce_dev->ce_out_ignore_size = PAGE_SIZE - (vaddr - + pce_dev->coh_vmem); /* at least 1.5 K of space */ + /* + * The first command of command list ce_in is for the input of + * concurrent operation of encrypt/decrypt or for the input + * of authentication. + */ + pcmd = (dmov_sg *) pce_dev->cmd_list_ce_in; + /* swap byte and half word , dst crci , scatter gather */ + pcmd->cmd = CMD_DST_SWAP_BYTES | CMD_DST_SWAP_SHORTS | + CMD_DST_CRCI(pce_dev->crci_in) | CMD_MODE_SG; + pdesc = pce_dev->ce_in_src_desc; + pdesc->addr = 0; /* to be filled in each operation */ + pdesc->len = 0; /* to be filled in each operation */ + pcmd->src_dscr = (unsigned) pce_dev->phy_ce_in_src_desc; + pdesc = pce_dev->ce_in_dst_desc; + pdesc->addr = (CRYPTO_DATA_SHADOW0 + pce_dev->phy_iobase); + pdesc->len = 0 | ADM_DESC_LAST; /* to be filled in each operation */ + pcmd->dst_dscr = (unsigned) pce_dev->phy_ce_in_dst_desc; + pcmd->_reserved = LI_SG_CMD | SRC_INDEX_SG_CMD(0) | + DST_INDEX_SG_CMD(0); + pcmd++; + /* + * The second command is for the digested data of + * hashing operation only. For others, this command is not used. + */ + pscmd = (dmov_s *) pcmd; + /* last command, swap byte, half word, src crci, single */ + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | CMD_SRC_SWAP_SHORTS | + CMD_SRC_CRCI(pce_dev->crci_hash) | CMD_MODE_SINGLE; + pscmd->src = (unsigned) (CRYPTO_AUTH_IV0_REG + pce_dev->phy_iobase); + pscmd->len = SHA256_DIGESTSIZE; /* to be filled. */ + pscmd->dst = (unsigned) pce_dev->phy_dig_result; + /* setup command pointer list */ + *(pce_dev->cmd_pointer_list_ce_in) = (CMD_PTR_LP | DMOV_CMD_LIST | + DMOV_CMD_ADDR((unsigned int) + pce_dev->phy_cmd_list_ce_in)); + pce_dev->chan_ce_in_cmd->user = (void *) pce_dev; + pce_dev->chan_ce_in_cmd->exec_func = NULL; + pce_dev->chan_ce_in_cmd->cmdptr = DMOV_CMD_ADDR( + (unsigned int) pce_dev->phy_cmd_pointer_list_ce_in); + pce_dev->chan_ce_in_cmd->crci_mask = msm_dmov_build_crci_mask(2, + pce_dev->crci_in, pce_dev->crci_hash); + /* + * The first command in the command list ce_out. + * It is for encry/decryp output. + * If hashing only, ce_out is not used. + */ + pcmd = (dmov_sg *) pce_dev->cmd_list_ce_out; + /* swap byte, half word, source crci, scatter gather */ + pcmd->cmd = CMD_SRC_SWAP_BYTES | CMD_SRC_SWAP_SHORTS | + CMD_SRC_CRCI(pce_dev->crci_out) | CMD_MODE_SG; + pdesc = pce_dev->ce_out_src_desc; + pdesc->addr = (CRYPTO_DATA_SHADOW0 + pce_dev->phy_iobase); + pdesc->len = 0; /* to be filled in each opeation */ + pcmd->src_dscr = (unsigned) pce_dev->phy_ce_out_src_desc; + pdesc = pce_dev->ce_out_dst_desc; + pdesc->addr = 0; /* to be filled in each opeation */ + pdesc->len = 0; /* to be filled in each opeation */ + pcmd->dst_dscr = (unsigned) pce_dev->phy_ce_out_dst_desc; + pcmd->_reserved = LI_SG_CMD | SRC_INDEX_SG_CMD(0) | + DST_INDEX_SG_CMD(0); + pcmd++; + /* + * The second command is for digested data of esp operation. + * For ciphering, this command is not used. + */ + pscmd = (dmov_s *) pcmd; + /* last command, swap byte, half word, src crci, single */ + pscmd->cmd = CMD_LC | CMD_SRC_SWAP_BYTES | CMD_SRC_SWAP_SHORTS | + CMD_SRC_CRCI(pce_dev->crci_hash) | CMD_MODE_SINGLE; + pscmd->src = (CRYPTO_AUTH_IV0_REG + pce_dev->phy_iobase); + pscmd->len = SHA1_DIGESTSIZE; /* we only support hmac(sha1) */ + pscmd->dst = (unsigned) pce_dev->phy_dig_result; + /* setup command pointer list */ + *(pce_dev->cmd_pointer_list_ce_out) = (CMD_PTR_LP | DMOV_CMD_LIST | + DMOV_CMD_ADDR((unsigned int)pce_dev-> + phy_cmd_list_ce_out)); + + pce_dev->chan_ce_out_cmd->user = pce_dev; + pce_dev->chan_ce_out_cmd->exec_func = NULL; + pce_dev->chan_ce_out_cmd->cmdptr = DMOV_CMD_ADDR( + (unsigned int) pce_dev->phy_cmd_pointer_list_ce_out); + pce_dev->chan_ce_out_cmd->crci_mask = msm_dmov_build_crci_mask(2, + pce_dev->crci_out, pce_dev->crci_hash); + + + return 0; +}; + +static int _qce_start_dma(struct qce_device *pce_dev, bool ce_in, bool ce_out) +{ + + if (ce_in) + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IN_PROG; + else + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_COMP; + + if (ce_out) + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IN_PROG; + else + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_COMP; + + if (ce_in) + msm_dmov_enqueue_cmd(pce_dev->chan_ce_in, + pce_dev->chan_ce_in_cmd); + if (ce_out) + msm_dmov_enqueue_cmd(pce_dev->chan_ce_out, + pce_dev->chan_ce_out_cmd); + + return 0; +}; + +static void _f9_complete(struct qce_device *pce_dev) +{ + uint32_t mac_i; + uint32_t status; + + dma_unmap_single(pce_dev->pdev, pce_dev->phy_ota_src, + pce_dev->ota_size, DMA_TO_DEVICE); + + /* check ce error status */ + status = readl_relaxed(pce_dev->iobase + CRYPTO_STATUS_REG); + if (status & (1 << CRYPTO_SW_ERR)) { + pce_dev->err++; + dev_err(pce_dev->pdev, + "Qualcomm Crypto Error at 0x%x, status%x\n", + pce_dev->phy_iobase, status); + _init_ce_engine(pce_dev); + pce_dev->qce_cb(pce_dev->areq, NULL, NULL, -ENXIO); + return; + }; + + mac_i = readl_relaxed(pce_dev->iobase + CRYPTO_AUTH_IV0_REG); + pce_dev->qce_cb(pce_dev->areq, (void *) mac_i, NULL, + pce_dev->chan_ce_in_status); +}; + +static void _f8_complete(struct qce_device *pce_dev) +{ + uint32_t status; + + if (pce_dev->phy_ota_dst != 0) + dma_unmap_single(pce_dev->pdev, pce_dev->phy_ota_dst, + pce_dev->ota_size, DMA_FROM_DEVICE); + if (pce_dev->phy_ota_src != 0) + dma_unmap_single(pce_dev->pdev, pce_dev->phy_ota_src, + pce_dev->ota_size, (pce_dev->phy_ota_dst) ? + DMA_TO_DEVICE : DMA_BIDIRECTIONAL); + + /* check ce error status */ + status = readl_relaxed(pce_dev->iobase + CRYPTO_STATUS_REG); + if (status & (1 << CRYPTO_SW_ERR)) { + pce_dev->err++; + dev_err(pce_dev->pdev, + "Qualcomm Crypto Error at 0x%x, status%x\n", + pce_dev->phy_iobase, status); + _init_ce_engine(pce_dev); + pce_dev->qce_cb(pce_dev->areq, NULL, NULL, -ENXIO); + return; + }; + + pce_dev->qce_cb(pce_dev->areq, NULL, NULL, + pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); +}; + + +static void _f9_ce_in_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else + pce_dev->chan_ce_in_status = 0; + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + _f9_complete(pce_dev); +}; + +static void _f8_ce_in_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else + pce_dev->chan_ce_in_status = 0; + + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _f8_complete(pce_dev); + } +}; + +static void _f8_ce_out_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_out_status = -1; + } else { + pce_dev->chan_ce_out_status = 0; + }; + + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _f8_complete(pce_dev); + } +}; + +static int _ce_f9_setup(struct qce_device *pce_dev, struct qce_f9_req * req) +{ + uint32_t cfg; + uint32_t ikey[OTA_KEY_SIZE/sizeof(uint32_t)]; + + _byte_stream_to_net_words(ikey, &req->ikey[0], OTA_KEY_SIZE); + writel_relaxed(ikey[0], pce_dev->iobase + CRYPTO_AUTH_IV0_REG); + writel_relaxed(ikey[1], pce_dev->iobase + CRYPTO_AUTH_IV1_REG); + writel_relaxed(ikey[2], pce_dev->iobase + CRYPTO_AUTH_IV2_REG); + writel_relaxed(ikey[3], pce_dev->iobase + CRYPTO_AUTH_IV3_REG); + writel_relaxed(req->last_bits, pce_dev->iobase + CRYPTO_AUTH_IV4_REG); + + writel_relaxed(req->fresh, pce_dev->iobase + CRYPTO_AUTH_BYTECNT0_REG); + writel_relaxed(req->count_i, pce_dev->iobase + + CRYPTO_AUTH_BYTECNT1_REG); + + /* write auth_seg_cfg */ + writel_relaxed((uint32_t)req->msize << CRYPTO_AUTH_SEG_SIZE, + pce_dev->iobase + CRYPTO_AUTH_SEG_CFG_REG); + + /* write seg_cfg */ + cfg = (CRYPTO_AUTH_ALG_F9 << CRYPTO_AUTH_ALG) | (1 << CRYPTO_FIRST) | + (1 << CRYPTO_LAST); + + if (req->algorithm == QCE_OTA_ALGO_KASUMI) + cfg |= (CRYPTO_AUTH_SIZE_UIA1 << CRYPTO_AUTH_SIZE); + else + cfg |= (CRYPTO_AUTH_SIZE_UIA2 << CRYPTO_AUTH_SIZE) ; + + if (req->direction == QCE_OTA_DIR_DOWNLINK) + cfg |= 1 << CRYPTO_F9_DIRECTION; + + writel_relaxed(cfg, pce_dev->iobase + CRYPTO_SEG_CFG_REG); + + /* write seg_size */ + writel_relaxed(req->msize, pce_dev->iobase + CRYPTO_SEG_SIZE_REG); + + /* issue go to crypto */ + writel_relaxed(1 << CRYPTO_GO, pce_dev->iobase + CRYPTO_GOPROC_REG); + + /* + * barrier to ensure previous instructions + * (including GO) to CE finish before issue DMA transfer + * request. + */ + mb(); + return 0; +}; + +static int _ce_f8_setup(struct qce_device *pce_dev, struct qce_f8_req *req, + bool key_stream_mode, uint16_t npkts, uint16_t cipher_offset, + uint16_t cipher_size) +{ + uint32_t cfg; + uint32_t ckey[OTA_KEY_SIZE/sizeof(uint32_t)]; + + if ((key_stream_mode && (req->data_len & 0xf || npkts > 1)) || + (req->bearer >= QCE_OTA_MAX_BEARER)) + return -EINVAL; + + /* write seg_cfg */ + cfg = (CRYPTO_ENCR_ALG_F8 << CRYPTO_ENCR_ALG) | (1 << CRYPTO_FIRST) | + (1 << CRYPTO_LAST); + if (req->algorithm == QCE_OTA_ALGO_KASUMI) + cfg |= (CRYPTO_ENCR_KEY_SZ_UEA1 << CRYPTO_ENCR_KEY_SZ); + else + cfg |= (CRYPTO_ENCR_KEY_SZ_UEA2 << CRYPTO_ENCR_KEY_SZ) ; + if (key_stream_mode) + cfg |= 1 << CRYPTO_F8_KEYSTREAM_ENABLE; + if (req->direction == QCE_OTA_DIR_DOWNLINK) + cfg |= 1 << CRYPTO_F8_DIRECTION; + writel_relaxed(cfg, pce_dev->iobase + CRYPTO_SEG_CFG_REG); + + /* write seg_size */ + writel_relaxed(req->data_len, pce_dev->iobase + CRYPTO_SEG_SIZE_REG); + + /* write 0 to auth_size, auth_offset */ + writel_relaxed(0, pce_dev->iobase + CRYPTO_AUTH_SEG_CFG_REG); + + /* write encr_seg_cfg seg_size, seg_offset */ + writel_relaxed((((uint32_t) cipher_size) << CRYPTO_ENCR_SEG_SIZE) | + (cipher_offset & 0xffff), + pce_dev->iobase + CRYPTO_ENCR_SEG_CFG_REG); + + /* write keys */ + _byte_stream_to_net_words(ckey, &req->ckey[0], OTA_KEY_SIZE); + writel_relaxed(ckey[0], pce_dev->iobase + CRYPTO_DES_KEY0_REG); + writel_relaxed(ckey[1], pce_dev->iobase + CRYPTO_DES_KEY1_REG); + writel_relaxed(ckey[2], pce_dev->iobase + CRYPTO_DES_KEY2_REG); + writel_relaxed(ckey[3], pce_dev->iobase + CRYPTO_DES_KEY3_REG); + + /* write cntr0_iv0 for countC */ + writel_relaxed(req->count_c, pce_dev->iobase + CRYPTO_CNTR0_IV0_REG); + + /* write cntr1_iv1 for nPkts, and bearer */ + if (npkts == 1) + npkts = 0; + writel_relaxed(req->bearer << CRYPTO_CNTR1_IV1_REG_F8_BEARER | + npkts << CRYPTO_CNTR1_IV1_REG_F8_PKT_CNT, + pce_dev->iobase + CRYPTO_CNTR1_IV1_REG); + + /* issue go to crypto */ + writel_relaxed(1 << CRYPTO_GO, pce_dev->iobase + CRYPTO_GOPROC_REG); + + /* + * barrier to ensure previous instructions + * (including GO) to CE finish before issue DMA transfer + * request. + */ + mb(); + return 0; +}; + +int qce_aead_req(void *handle, struct qce_req *q_req) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + struct aead_request *areq = (struct aead_request *) q_req->areq; + struct crypto_aead *aead = crypto_aead_reqtfm(areq); + uint32_t ivsize = crypto_aead_ivsize(aead); + uint32_t totallen; + uint32_t pad_len; + uint32_t authsize = crypto_aead_authsize(aead); + int rc = 0; + + q_req->ivsize = ivsize; + if (q_req->dir == QCE_ENCRYPT) + q_req->cryptlen = areq->cryptlen; + else + q_req->cryptlen = areq->cryptlen - authsize; + + totallen = q_req->cryptlen + ivsize + areq->assoclen; + pad_len = ALIGN(totallen, ADM_CE_BLOCK_SIZE) - totallen; + + _chain_buffer_in_init(pce_dev); + _chain_buffer_out_init(pce_dev); + + pce_dev->assoc_nents = 0; + pce_dev->phy_iv_in = 0; + pce_dev->src_nents = 0; + pce_dev->dst_nents = 0; + + pce_dev->assoc_nents = count_sg(areq->assoc, areq->assoclen); + dma_map_sg(pce_dev->pdev, areq->assoc, pce_dev->assoc_nents, + DMA_TO_DEVICE); + if (_chain_sg_buffer_in(pce_dev, areq->assoc, areq->assoclen) < 0) { + rc = -ENOMEM; + goto bad; + } + + /* cipher iv for input */ + pce_dev->phy_iv_in = dma_map_single(pce_dev->pdev, q_req->iv, + ivsize, DMA_TO_DEVICE); + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_iv_in, ivsize) < 0) { + rc = -ENOMEM; + goto bad; + } + + /* for output, ignore associated data and cipher iv */ + if (_chain_pm_buffer_out(pce_dev, pce_dev->phy_ce_out_ignore, + ivsize + areq->assoclen) < 0) { + rc = -ENOMEM; + goto bad; + } + + /* cipher input */ + pce_dev->src_nents = count_sg(areq->src, q_req->cryptlen); + dma_map_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + if (_chain_sg_buffer_in(pce_dev, areq->src, q_req->cryptlen) < 0) { + rc = -ENOMEM; + goto bad; + } + + /* cipher output */ + if (areq->src != areq->dst) { + pce_dev->dst_nents = count_sg(areq->dst, q_req->cryptlen); + dma_map_sg(pce_dev->pdev, areq->dst, pce_dev->dst_nents, + DMA_FROM_DEVICE); + }; + if (_chain_sg_buffer_out(pce_dev, areq->dst, q_req->cryptlen) < 0) { + rc = -ENOMEM; + goto bad; + } + + /* pad data */ + if (pad_len) { + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + if (_chain_pm_buffer_out(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + } + + /* finalize the ce_in and ce_out channels command lists */ + _ce_in_final(pce_dev, 1, ALIGN(totallen, ADM_CE_BLOCK_SIZE)); + _ce_out_final(pce_dev, 2, ALIGN(totallen, ADM_CE_BLOCK_SIZE)); + + /* set up crypto device */ + rc = _ce_setup(pce_dev, q_req, totallen, ivsize + areq->assoclen); + if (rc < 0) + goto bad; + + /* setup for callback, and issue command to adm */ + pce_dev->areq = q_req->areq; + pce_dev->qce_cb = q_req->qce_cb; + + pce_dev->chan_ce_in_cmd->complete_func = _aead_ce_in_call_back; + pce_dev->chan_ce_out_cmd->complete_func = _aead_ce_out_call_back; + + rc = _qce_start_dma(pce_dev, true, true); + if (rc == 0) + return 0; +bad: + if (pce_dev->assoc_nents) { + dma_unmap_sg(pce_dev->pdev, areq->assoc, pce_dev->assoc_nents, + DMA_TO_DEVICE); + } + if (pce_dev->phy_iv_in) { + dma_unmap_single(pce_dev->pdev, pce_dev->phy_iv_in, + ivsize, DMA_TO_DEVICE); + } + if (pce_dev->src_nents) { + dma_unmap_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + } + if (pce_dev->dst_nents) { + dma_unmap_sg(pce_dev->pdev, areq->dst, pce_dev->dst_nents, + DMA_FROM_DEVICE); + } + return rc; +} +EXPORT_SYMBOL(qce_aead_req); + +int qce_ablk_cipher_req(void *handle, struct qce_req *c_req) +{ + int rc = 0; + struct qce_device *pce_dev = (struct qce_device *) handle; + struct ablkcipher_request *areq = (struct ablkcipher_request *) + c_req->areq; + + uint32_t pad_len = ALIGN(areq->nbytes, ADM_CE_BLOCK_SIZE) + - areq->nbytes; + + _chain_buffer_in_init(pce_dev); + _chain_buffer_out_init(pce_dev); + + pce_dev->src_nents = 0; + pce_dev->dst_nents = 0; + /* cipher input */ + pce_dev->src_nents = count_sg(areq->src, areq->nbytes); + + if (c_req->use_pmem != 1) + dma_map_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + else + dma_map_pmem_sg(&c_req->pmem->src[0], pce_dev->src_nents, + areq->src); + + if (_chain_sg_buffer_in(pce_dev, areq->src, areq->nbytes) < 0) { + rc = -ENOMEM; + goto bad; + } + + /* cipher output */ + if (areq->src != areq->dst) { + pce_dev->dst_nents = count_sg(areq->dst, areq->nbytes); + if (c_req->use_pmem != 1) + dma_map_sg(pce_dev->pdev, areq->dst, pce_dev->dst_nents, + DMA_FROM_DEVICE); + else + dma_map_pmem_sg(&c_req->pmem->dst[0], + pce_dev->dst_nents, areq->dst); + }; + if (_chain_sg_buffer_out(pce_dev, areq->dst, areq->nbytes) < 0) { + rc = -ENOMEM; + goto bad; + } + + /* pad data */ + if (pad_len) { + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + if (_chain_pm_buffer_out(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + } + + /* finalize the ce_in and ce_out channels command lists */ + _ce_in_final(pce_dev, 1, areq->nbytes + pad_len); + _ce_out_final(pce_dev, 1, areq->nbytes + pad_len); + +#ifdef QCE_DEBUG + _ce_in_dump(pce_dev); + _ce_out_dump(pce_dev); +#endif + /* set up crypto device */ + rc = _ce_setup(pce_dev, c_req, areq->nbytes, 0); + if (rc < 0) + goto bad; + + /* setup for callback, and issue command to adm */ + pce_dev->areq = areq; + pce_dev->qce_cb = c_req->qce_cb; + if (c_req->use_pmem == 1) { + pce_dev->chan_ce_in_cmd->complete_func = + _ablk_cipher_ce_in_call_back_pmem; + pce_dev->chan_ce_out_cmd->complete_func = + _ablk_cipher_ce_out_call_back_pmem; + } else { + pce_dev->chan_ce_in_cmd->complete_func = + _ablk_cipher_ce_in_call_back; + pce_dev->chan_ce_out_cmd->complete_func = + _ablk_cipher_ce_out_call_back; + } + rc = _qce_start_dma(pce_dev, true, true); + + if (rc == 0) + return 0; +bad: + if (c_req->use_pmem != 1) { + if (pce_dev->dst_nents) { + dma_unmap_sg(pce_dev->pdev, areq->dst, + pce_dev->dst_nents, DMA_FROM_DEVICE); + } + if (pce_dev->src_nents) { + dma_unmap_sg(pce_dev->pdev, areq->src, + pce_dev->src_nents, + (areq->src == areq->dst) ? + DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + } + } + return rc; +} +EXPORT_SYMBOL(qce_ablk_cipher_req); + +int qce_process_sha_req(void *handle, struct qce_sha_req *sreq) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + int rc; + uint32_t pad_len = ALIGN(sreq->size, ADM_CE_BLOCK_SIZE) - sreq->size; + struct ahash_request *areq = (struct ahash_request *)sreq->areq; + + _chain_buffer_in_init(pce_dev); + pce_dev->src_nents = count_sg(sreq->src, sreq->size); + dma_map_sg(pce_dev->pdev, sreq->src, pce_dev->src_nents, + DMA_TO_DEVICE); + + if (_chain_sg_buffer_in(pce_dev, sreq->src, sreq->size) < 0) { + rc = -ENOMEM; + goto bad; + } + + if (pad_len) { + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + } + _ce_in_final(pce_dev, 2, sreq->size + pad_len); + +#ifdef QCE_DEBUG + _ce_in_dump(pce_dev); +#endif + + rc = _sha_ce_setup(pce_dev, sreq); + + if (rc < 0) + goto bad; + + pce_dev->areq = areq; + pce_dev->qce_cb = sreq->qce_cb; + pce_dev->chan_ce_in_cmd->complete_func = _sha_ce_in_call_back; + + rc = _qce_start_dma(pce_dev, true, false); + + if (rc == 0) + return 0; +bad: + if (pce_dev->src_nents) { + dma_unmap_sg(pce_dev->pdev, sreq->src, + pce_dev->src_nents, DMA_TO_DEVICE); + } + + return rc; +} +EXPORT_SYMBOL(qce_process_sha_req); + +/* + * crypto engine open function. + */ +void *qce_open(struct platform_device *pdev, int *rc) +{ + struct qce_device *pce_dev; + struct resource *resource; + struct clk *ce_clk; + + pce_dev = kzalloc(sizeof(struct qce_device), GFP_KERNEL); + if (!pce_dev) { + *rc = -ENOMEM; + dev_err(&pdev->dev, "Can not allocate memory\n"); + return NULL; + } + pce_dev->pdev = &pdev->dev; + ce_clk = clk_get(pce_dev->pdev, "ce_clk"); + if (IS_ERR(ce_clk)) { + *rc = PTR_ERR(ce_clk); + return NULL; + } + pce_dev->ce_clk = ce_clk; + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + *rc = -ENXIO; + dev_err(pce_dev->pdev, "Missing MEM resource\n"); + goto err; + }; + pce_dev->phy_iobase = resource->start; + pce_dev->iobase = ioremap_nocache(resource->start, + resource->end - resource->start + 1); + if (!pce_dev->iobase) { + *rc = -ENOMEM; + dev_err(pce_dev->pdev, "Can not map io memory\n"); + goto err; + } + + pce_dev->chan_ce_in_cmd = kzalloc(sizeof(struct msm_dmov_cmd), + GFP_KERNEL); + pce_dev->chan_ce_out_cmd = kzalloc(sizeof(struct msm_dmov_cmd), + GFP_KERNEL); + if (pce_dev->chan_ce_in_cmd == NULL || + pce_dev->chan_ce_out_cmd == NULL) { + dev_err(pce_dev->pdev, "Can not allocate memory\n"); + *rc = -ENOMEM; + goto err; + } + + resource = platform_get_resource_byname(pdev, IORESOURCE_DMA, + "crypto_channels"); + if (!resource) { + *rc = -ENXIO; + dev_err(pce_dev->pdev, "Missing DMA channel resource\n"); + goto err; + }; + pce_dev->chan_ce_in = resource->start; + pce_dev->chan_ce_out = resource->end; + resource = platform_get_resource_byname(pdev, IORESOURCE_DMA, + "crypto_crci_in"); + if (!resource) { + *rc = -ENXIO; + dev_err(pce_dev->pdev, "Missing DMA crci in resource\n"); + goto err; + }; + pce_dev->crci_in = resource->start; + resource = platform_get_resource_byname(pdev, IORESOURCE_DMA, + "crypto_crci_out"); + if (!resource) { + *rc = -ENXIO; + dev_err(pce_dev->pdev, "Missing DMA crci out resource\n"); + goto err; + }; + pce_dev->crci_out = resource->start; + resource = platform_get_resource_byname(pdev, IORESOURCE_DMA, + "crypto_crci_hash"); + if (!resource) { + *rc = -ENXIO; + dev_err(pce_dev->pdev, "Missing DMA crci hash resource\n"); + goto err; + }; + pce_dev->crci_hash = resource->start; + pce_dev->coh_vmem = dma_alloc_coherent(pce_dev->pdev, + PAGE_SIZE, &pce_dev->coh_pmem, GFP_KERNEL); + + if (pce_dev->coh_vmem == NULL) { + *rc = -ENOMEM; + dev_err(pce_dev->pdev, "Can not allocate coherent memory.\n"); + goto err; + } + _setup_cmd_template(pce_dev); + + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + *rc = clk_enable(pce_dev->ce_clk); + if (*rc) + return NULL; + + if (_init_ce_engine(pce_dev)) { + *rc = -ENXIO; + clk_disable(pce_dev->ce_clk); + goto err; + } + *rc = 0; + clk_disable(pce_dev->ce_clk); + + pce_dev->err = 0; + + return pce_dev; +err: + if (pce_dev) + qce_close(pce_dev); + return NULL; +} +EXPORT_SYMBOL(qce_open); + +/* + * crypto engine close function. + */ +int qce_close(void *handle) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + + if (handle == NULL) + return -ENODEV; + if (pce_dev->iobase) + iounmap(pce_dev->iobase); + + if (pce_dev->coh_vmem) + dma_free_coherent(pce_dev->pdev, PAGE_SIZE, pce_dev->coh_vmem, + pce_dev->coh_pmem); + kfree(pce_dev->chan_ce_in_cmd); + kfree(pce_dev->chan_ce_out_cmd); + + kfree(handle); + clk_put(pce_dev->ce_clk); + return 0; +} +EXPORT_SYMBOL(qce_close); + +int qce_hw_support(void *handle, struct ce_hw_support *ce_support) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + + if (ce_support == NULL) + return -EINVAL; + + if (pce_dev->hmac == 1) + ce_support->sha1_hmac_20 = true; + else + ce_support->sha1_hmac_20 = false; + ce_support->sha1_hmac = false; + ce_support->sha256_hmac = false; + ce_support->sha_hmac = false; + ce_support->cmac = false; + ce_support->aes_key_192 = true; + ce_support->aes_xts = false; + ce_support->aes_ccm = false; + ce_support->ota = pce_dev->ota; + return 0; +} +EXPORT_SYMBOL(qce_hw_support); + +int qce_f8_req(void *handle, struct qce_f8_req *req, + void *cookie, qce_comp_func_ptr_t qce_cb) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + bool key_stream_mode; + dma_addr_t dst; + int rc; + uint32_t pad_len = ALIGN(req->data_len, ADM_CE_BLOCK_SIZE) - + req->data_len; + + _chain_buffer_in_init(pce_dev); + _chain_buffer_out_init(pce_dev); + + key_stream_mode = (req->data_in == NULL); + + /* F8 cipher input */ + if (key_stream_mode) + pce_dev->phy_ota_src = 0; + else { + pce_dev->phy_ota_src = dma_map_single(pce_dev->pdev, + req->data_in, req->data_len, + (req->data_in == req->data_out) ? + DMA_BIDIRECTIONAL : DMA_TO_DEVICE); + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_ota_src, + req->data_len) < 0) { + pce_dev->phy_ota_dst = 0; + rc = -ENOMEM; + goto bad; + } + } + + /* F8 cipher output */ + if (req->data_in != req->data_out) { + dst = dma_map_single(pce_dev->pdev, req->data_out, + req->data_len, DMA_FROM_DEVICE); + pce_dev->phy_ota_dst = dst; + } else { + dst = pce_dev->phy_ota_src; + pce_dev->phy_ota_dst = 0; + } + if (_chain_pm_buffer_out(pce_dev, dst, req->data_len) < 0) { + rc = -ENOMEM; + goto bad; + } + + pce_dev->ota_size = req->data_len; + + /* pad data */ + if (pad_len) { + if (!key_stream_mode && _chain_pm_buffer_in(pce_dev, + pce_dev->phy_ce_pad, pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + if (_chain_pm_buffer_out(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + } + + /* finalize the ce_in and ce_out channels command lists */ + if (!key_stream_mode) + _ce_in_final(pce_dev, 1, req->data_len + pad_len); + _ce_out_final(pce_dev, 1, req->data_len + pad_len); + + /* set up crypto device */ + rc = _ce_f8_setup(pce_dev, req, key_stream_mode, 1, 0, req->data_len); + if (rc < 0) + goto bad; + + /* setup for callback, and issue command to adm */ + pce_dev->areq = cookie; + pce_dev->qce_cb = qce_cb; + + if (!key_stream_mode) + pce_dev->chan_ce_in_cmd->complete_func = _f8_ce_in_call_back; + + pce_dev->chan_ce_out_cmd->complete_func = _f8_ce_out_call_back; + + rc = _qce_start_dma(pce_dev, !(key_stream_mode), true); + if (rc == 0) + return 0; +bad: + if (pce_dev->phy_ota_dst != 0) + dma_unmap_single(pce_dev->pdev, pce_dev->phy_ota_dst, + req->data_len, DMA_FROM_DEVICE); + if (pce_dev->phy_ota_src != 0) + dma_unmap_single(pce_dev->pdev, pce_dev->phy_ota_src, + req->data_len, + (req->data_in == req->data_out) ? + DMA_BIDIRECTIONAL : DMA_TO_DEVICE); + return rc; +} +EXPORT_SYMBOL(qce_f8_req); + +int qce_f8_multi_pkt_req(void *handle, struct qce_f8_multi_pkt_req *mreq, + void *cookie, qce_comp_func_ptr_t qce_cb) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + uint16_t num_pkt = mreq->num_pkt; + uint16_t cipher_start = mreq->cipher_start; + uint16_t cipher_size = mreq->cipher_size; + struct qce_f8_req *req = &mreq->qce_f8_req; + uint32_t total; + uint32_t pad_len; + dma_addr_t dst = 0; + int rc = 0; + + total = num_pkt * req->data_len; + pad_len = ALIGN(total, ADM_CE_BLOCK_SIZE) - total; + + _chain_buffer_in_init(pce_dev); + _chain_buffer_out_init(pce_dev); + + /* F8 cipher input */ + pce_dev->phy_ota_src = dma_map_single(pce_dev->pdev, + req->data_in, total, + (req->data_in == req->data_out) ? + DMA_BIDIRECTIONAL : DMA_TO_DEVICE); + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_ota_src, + total) < 0) { + pce_dev->phy_ota_dst = 0; + rc = -ENOMEM; + goto bad; + } + /* F8 cipher output */ + if (req->data_in != req->data_out) { + dst = dma_map_single(pce_dev->pdev, req->data_out, total, + DMA_FROM_DEVICE); + pce_dev->phy_ota_dst = dst; + } else { + dst = pce_dev->phy_ota_src; + pce_dev->phy_ota_dst = 0; + } + if (_chain_pm_buffer_out(pce_dev, dst, total) < 0) { + rc = -ENOMEM; + goto bad; + } + + pce_dev->ota_size = total; + + /* pad data */ + if (pad_len) { + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + if (_chain_pm_buffer_out(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + } + + /* finalize the ce_in and ce_out channels command lists */ + _ce_in_final(pce_dev, 1, total + pad_len); + _ce_out_final(pce_dev, 1, total + pad_len); + + + /* set up crypto device */ + rc = _ce_f8_setup(pce_dev, req, false, num_pkt, cipher_start, + cipher_size); + if (rc) + goto bad ; + + /* setup for callback, and issue command to adm */ + pce_dev->areq = cookie; + pce_dev->qce_cb = qce_cb; + + pce_dev->chan_ce_in_cmd->complete_func = _f8_ce_in_call_back; + pce_dev->chan_ce_out_cmd->complete_func = _f8_ce_out_call_back; + + rc = _qce_start_dma(pce_dev, true, true); + if (rc == 0) + return 0; +bad: + if (pce_dev->phy_ota_dst) + dma_unmap_single(pce_dev->pdev, pce_dev->phy_ota_dst, total, + DMA_FROM_DEVICE); + dma_unmap_single(pce_dev->pdev, pce_dev->phy_ota_src, total, + (req->data_in == req->data_out) ? + DMA_BIDIRECTIONAL : DMA_TO_DEVICE); + return rc; +} +EXPORT_SYMBOL(qce_f8_multi_pkt_req); + +int qce_f9_req(void *handle, struct qce_f9_req *req, void *cookie, + qce_comp_func_ptr_t qce_cb) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + int rc; + uint32_t pad_len = ALIGN(req->msize, ADM_CE_BLOCK_SIZE) - req->msize; + + pce_dev->phy_ota_src = dma_map_single(pce_dev->pdev, req->message, + req->msize, DMA_TO_DEVICE); + + _chain_buffer_in_init(pce_dev); + rc = _chain_pm_buffer_in(pce_dev, pce_dev->phy_ota_src, req->msize); + if (rc < 0) { + rc = -ENOMEM; + goto bad; + } + + pce_dev->ota_size = req->msize; + if (pad_len) { + rc = _chain_pm_buffer_in(pce_dev, pce_dev->phy_ce_pad, + pad_len); + if (rc < 0) { + rc = -ENOMEM; + goto bad; + } + } + _ce_in_final(pce_dev, 2, req->msize + pad_len); + rc = _ce_f9_setup(pce_dev, req); + if (rc < 0) + goto bad; + + /* setup for callback, and issue command to adm */ + pce_dev->areq = cookie; + pce_dev->qce_cb = qce_cb; + + pce_dev->chan_ce_in_cmd->complete_func = _f9_ce_in_call_back; + + rc = _qce_start_dma(pce_dev, true, false); + if (rc == 0) + return 0; +bad: + dma_unmap_single(pce_dev->pdev, pce_dev->phy_ota_src, + req->msize, DMA_TO_DEVICE); + return rc; +} +EXPORT_SYMBOL(qce_f9_req); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Mona Hossain "); +MODULE_DESCRIPTION("Crypto Engine driver"); +MODULE_VERSION("1.11"); + diff --git a/drivers/crypto/msm/qce40.c b/drivers/crypto/msm/qce40.c new file mode 100644 index 00000000000..7724d67cd68 --- /dev/null +++ b/drivers/crypto/msm/qce40.c @@ -0,0 +1,2038 @@ +/* Qualcomm Crypto Engine driver. + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "inc/qce.h" +#include "inc/qcedev.h" +#include "inc/qcryptohw_40.h" + +/* ADM definitions */ +#define LI_SG_CMD (1 << 31) /* last index in the scatter gather cmd */ +#define SRC_INDEX_SG_CMD(index) ((index & 0x3fff) << 16) +#define DST_INDEX_SG_CMD(index) (index & 0x3fff) +#define ADM_DESC_LAST (1 << 31) + +/* Data xfer between DM and CE in blocks of 16 bytes */ +#define ADM_CE_BLOCK_SIZE 16 + +#define ADM_DESC_LENGTH_MASK 0xffff +#define ADM_DESC_LENGTH(x) (x & ADM_DESC_LENGTH_MASK) + +struct dmov_desc { + uint32_t addr; + uint32_t len; +}; + +#define ADM_STATUS_OK 0x80000002 + +/* Misc definitions */ + +/* QCE max number of descriptor in a descriptor list */ +#define QCE_MAX_NUM_DESC 128 + +/* State of DM channel */ +enum qce_chan_st_enum { + QCE_CHAN_STATE_IDLE = 0, + QCE_CHAN_STATE_IN_PROG = 1, + QCE_CHAN_STATE_COMP = 2, + QCE_CHAN_STATE_LAST +}; + +/* + * CE HW device structure. + * Each engine has an instance of the structure. + * Each engine can only handle one crypto operation at one time. It is up to + * the sw above to ensure single threading of operation on an engine. + */ +struct qce_device { + struct device *pdev; /* Handle to platform_device structure */ + unsigned char *coh_vmem; /* Allocated coherent virtual memory */ + dma_addr_t coh_pmem; /* Allocated coherent physical memory */ + void __iomem *iobase; /* Virtual io base of CE HW */ + unsigned int phy_iobase; /* Physical io base of CE HW */ + struct clk *ce_core_clk; /* Handle to CE clk */ + struct clk *ce_clk; /* Handle to CE clk */ + unsigned int crci_in; /* CRCI for CE DM IN Channel */ + unsigned int crci_out; /* CRCI for CE DM OUT Channel */ + unsigned int chan_ce_in; /* ADM channel used for CE input + * and auth result if authentication + * only operation. */ + unsigned int chan_ce_out; /* ADM channel used for CE output, + and icv for esp */ + unsigned int *cmd_pointer_list_ce_in; + dma_addr_t phy_cmd_pointer_list_ce_in; + + unsigned int *cmd_pointer_list_ce_out; + dma_addr_t phy_cmd_pointer_list_ce_out; + + unsigned char *cmd_list_ce_in; + dma_addr_t phy_cmd_list_ce_in; + + unsigned char *cmd_list_ce_out; + dma_addr_t phy_cmd_list_ce_out; + + struct dmov_desc *ce_out_src_desc; + dma_addr_t phy_ce_out_src_desc; + + struct dmov_desc *ce_out_dst_desc; + dma_addr_t phy_ce_out_dst_desc; + + struct dmov_desc *ce_in_src_desc; + dma_addr_t phy_ce_in_src_desc; + + struct dmov_desc *ce_in_dst_desc; + dma_addr_t phy_ce_in_dst_desc; + + unsigned char *ce_out_ignore; + dma_addr_t phy_ce_out_ignore; + + unsigned char *ce_pad; + dma_addr_t phy_ce_pad; + + struct msm_dmov_cmd *chan_ce_in_cmd; + struct msm_dmov_cmd *chan_ce_out_cmd; + + uint32_t ce_out_ignore_size; + + int ce_out_dst_desc_index; + int ce_in_dst_desc_index; + + int ce_out_src_desc_index; + int ce_in_src_desc_index; + + enum qce_chan_st_enum chan_ce_in_state; /* chan ce_in state */ + enum qce_chan_st_enum chan_ce_out_state; /* chan ce_out state */ + + int chan_ce_in_status; /* chan ce_in status */ + int chan_ce_out_status; /* chan ce_out status */ + + unsigned char *dig_result; + dma_addr_t phy_dig_result; + + /* cached aes key */ + uint32_t cipher_key[MAX_CIPHER_KEY_SIZE/sizeof(uint32_t)]; + + uint32_t cipher_key_size; /* cached aes key size in bytes */ + qce_comp_func_ptr_t qce_cb; /* qce callback function pointer */ + + int assoc_nents; + int ivsize; + int authsize; + int src_nents; + int dst_nents; + + void *areq; + enum qce_cipher_mode_enum mode; + + dma_addr_t phy_iv_in; +}; + +/* Standard initialization vector for SHA-1, source: FIPS 180-2 */ +static uint32_t _std_init_vector_sha1[] = { + 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 +}; +/* Standard initialization vector for SHA-256, source: FIPS 180-2 */ +static uint32_t _std_init_vector_sha256[] = { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 +}; + +static void _byte_stream_to_net_words(uint32_t *iv, unsigned char *b, + unsigned int len) +{ + unsigned n; + + n = len / sizeof(uint32_t) ; + for (; n > 0; n--) { + *iv = ((*b << 24) & 0xff000000) | + (((*(b+1)) << 16) & 0xff0000) | + (((*(b+2)) << 8) & 0xff00) | + (*(b+3) & 0xff); + b += sizeof(uint32_t); + iv++; + } + + n = len % sizeof(uint32_t); + if (n == 3) { + *iv = ((*b << 24) & 0xff000000) | + (((*(b+1)) << 16) & 0xff0000) | + (((*(b+2)) << 8) & 0xff00) ; + } else if (n == 2) { + *iv = ((*b << 24) & 0xff000000) | + (((*(b+1)) << 16) & 0xff0000) ; + } else if (n == 1) { + *iv = ((*b << 24) & 0xff000000) ; + } +} + +static void _byte_stream_swap_to_net_words(uint32_t *iv, unsigned char *b, + unsigned int len) +{ + unsigned i, j; + unsigned char swap_iv[AES_IV_LENGTH]; + + memset(swap_iv, 0, AES_IV_LENGTH); + for (i = (AES_IV_LENGTH-len), j = len-1; i < AES_IV_LENGTH; i++, j--) + swap_iv[i] = b[j]; + _byte_stream_to_net_words(iv, swap_iv, AES_IV_LENGTH); +} + +static void _net_words_to_byte_stream(uint32_t *iv, unsigned char *b, + unsigned int len) +{ + unsigned n = len / sizeof(uint32_t); + + for (; n > 0; n--) { + *b++ = (unsigned char) ((*iv >> 24) & 0xff); + *b++ = (unsigned char) ((*iv >> 16) & 0xff); + *b++ = (unsigned char) ((*iv >> 8) & 0xff); + *b++ = (unsigned char) (*iv & 0xff); + iv++; + } + n = len % sizeof(uint32_t); + if (n == 3) { + *b++ = (unsigned char) ((*iv >> 24) & 0xff); + *b++ = (unsigned char) ((*iv >> 16) & 0xff); + *b = (unsigned char) ((*iv >> 8) & 0xff); + } else if (n == 2) { + *b++ = (unsigned char) ((*iv >> 24) & 0xff); + *b = (unsigned char) ((*iv >> 16) & 0xff); + } else if (n == 1) { + *b = (unsigned char) ((*iv >> 24) & 0xff); + } +} + +static int count_sg(struct scatterlist *sg, int nbytes) +{ + int i; + + for (i = 0; nbytes > 0; i++, sg = sg_next(sg)) + nbytes -= sg->length; + return i; +} + +static int dma_map_pmem_sg(struct buf_info *pmem, unsigned entries, + struct scatterlist *sg) +{ + int i; + for (i = 0; i < entries; i++) { + + sg->dma_address = (dma_addr_t)pmem->offset; + sg++; + pmem++; + } + return 0; +} + +static int _probe_ce_engine(struct qce_device *pce_dev) +{ + unsigned int val; + unsigned int rev; + + val = readl_relaxed(pce_dev->iobase + CRYPTO_VERSION_REG); + if (((val & 0xfffffff) != 0x0000042) && + ((val & 0xfffffff) != 0x0000040)) { + dev_err(pce_dev->pdev, + "Unknown Qualcomm crypto device at 0x%x 0x%x\n", + pce_dev->phy_iobase, val); + return -EIO; + }; + rev = (val & CRYPTO_CORE_REV_MASK); + if (rev == 0x42) { + dev_info(pce_dev->pdev, + "Qualcomm Crypto 4.2 device found at 0x%x\n", + pce_dev->phy_iobase); + } else { + if (rev == 0x40) { + dev_info(pce_dev->pdev, + "Qualcomm Crypto 4.0 device found at 0x%x\n", + pce_dev->phy_iobase); + } + } + + dev_info(pce_dev->pdev, + "IO base 0x%x, ce_in channel %d, " + "ce_out channel %d, " + "crci_in %d, crci_out %d\n", + (unsigned int) pce_dev->iobase, + pce_dev->chan_ce_in, pce_dev->chan_ce_out, + pce_dev->crci_in, pce_dev->crci_out); + + pce_dev->cipher_key_size = 0; + + return 0; +}; + +static int _init_ce_engine(struct qce_device *pce_dev) +{ + unsigned int val; + + /* Reset ce */ + clk_reset(pce_dev->ce_core_clk, CLK_RESET_ASSERT); + clk_reset(pce_dev->ce_core_clk, CLK_RESET_DEASSERT); + /* + * Ensure previous instruction (any writes to CLK registers) + * to toggle the CLK reset lines was completed. + */ + dsb(); + /* configure ce */ + val = (1 << CRYPTO_MASK_DOUT_INTR) | (1 << CRYPTO_MASK_DIN_INTR) | + (1 << CRYPTO_MASK_OP_DONE_INTR) | + (1 << CRYPTO_MASK_ERR_INTR); + writel_relaxed(val, pce_dev->iobase + CRYPTO_CONFIG_REG); + /* + * Ensure previous instruction (writel_relaxed to config register bit) + * was completed. + */ + dsb(); + val = readl_relaxed(pce_dev->iobase + CRYPTO_CONFIG_REG); + if (!val) { + dev_err(pce_dev->pdev, + "unknown Qualcomm crypto device at 0x%x\n", + pce_dev->phy_iobase); + return -EIO; + }; + if (_probe_ce_engine(pce_dev) < 0) + return -EIO; + return 0; +}; + +static int _ce_setup_hash(struct qce_device *pce_dev, struct qce_sha_req *sreq) +{ + uint32_t auth32[SHA256_DIGEST_SIZE / sizeof(uint32_t)]; + uint32_t diglen; + int i; + uint32_t auth_cfg = 0; + bool sha1 = false; + + if (sreq->alg == QCE_HASH_AES_CMAC) { + uint32_t authkey32[SHA_HMAC_KEY_SIZE/sizeof(uint32_t)] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t authklen32 = sreq->authklen/(sizeof(uint32_t)); + /* Clear auth_ivn, auth_keyn registers */ + for (i = 0; i < 16; i++) { + writel_relaxed(0, (pce_dev->iobase + + (CRYPTO_AUTH_IV0_REG + i * sizeof(uint32_t)))); + writel_relaxed(0, (pce_dev->iobase + + (CRYPTO_AUTH_KEY0_REG + i * sizeof(uint32_t)))); + } + /* write auth_bytecnt 0/1/2/3, start with 0 */ + for (i = 0; i < 4; i++) + writel_relaxed(0, pce_dev->iobase + + CRYPTO_AUTH_BYTECNT0_REG + + i * sizeof(uint32_t)); + + _byte_stream_to_net_words(authkey32, sreq->authkey, + sreq->authklen); + for (i = 0; i < authklen32; i++) + writel_relaxed(authkey32[i], pce_dev->iobase + + CRYPTO_AUTH_KEY0_REG + (i * sizeof(uint32_t))); + /* + * write seg_cfg + */ + auth_cfg |= (1 << CRYPTO_LAST); + auth_cfg |= (CRYPTO_AUTH_MODE_CMAC << CRYPTO_AUTH_MODE); + auth_cfg |= (CRYPTO_AUTH_SIZE_ENUM_16_BYTES << + CRYPTO_AUTH_SIZE); + auth_cfg |= CRYPTO_AUTH_ALG_AES << CRYPTO_AUTH_ALG; + + switch (sreq->authklen) { + case AES128_KEY_SIZE: + auth_cfg |= (CRYPTO_AUTH_KEY_SZ_AES128 << + CRYPTO_AUTH_KEY_SIZE); + break; + case AES256_KEY_SIZE: + auth_cfg |= (CRYPTO_AUTH_KEY_SZ_AES256 << + CRYPTO_AUTH_KEY_SIZE); + break; + default: + break; + } + + goto go_proc; + } + + /* if not the last, the size has to be on the block boundary */ + if (sreq->last_blk == 0 && (sreq->size % SHA256_BLOCK_SIZE)) + return -EIO; + + switch (sreq->alg) { + case QCE_HASH_SHA1: + case QCE_HASH_SHA1_HMAC: + diglen = SHA1_DIGEST_SIZE; + sha1 = true; + break; + case QCE_HASH_SHA256: + case QCE_HASH_SHA256_HMAC: + diglen = SHA256_DIGEST_SIZE; + break; + default: + return -EINVAL; + } + + if ((sreq->alg == QCE_HASH_SHA1_HMAC) || + (sreq->alg == QCE_HASH_SHA256_HMAC)) { + uint32_t hmackey[SHA_HMAC_KEY_SIZE/sizeof(uint32_t)] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; + uint32_t hmacklen = sreq->authklen/(sizeof(uint32_t)); + + _byte_stream_to_net_words(hmackey, sreq->authkey, + sreq->authklen); + /* write hmac key */ + for (i = 0; i < hmacklen; i++) + writel_relaxed(hmackey[i], pce_dev->iobase + + CRYPTO_AUTH_KEY0_REG + (i * sizeof(uint32_t))); + + auth_cfg |= (CRYPTO_AUTH_MODE_HMAC << CRYPTO_AUTH_MODE); + } else { + auth_cfg |= (CRYPTO_AUTH_MODE_HASH << CRYPTO_AUTH_MODE); + } + + /* write 20/32 bytes, 5/8 words into auth_iv for SHA1/SHA256 */ + + if (sreq->first_blk) { + if (sha1) { + for (i = 0; i < 5; i++) + auth32[i] = _std_init_vector_sha1[i]; + } else { + for (i = 0; i < 8; i++) + auth32[i] = _std_init_vector_sha256[i]; + } + } else { + _byte_stream_to_net_words(auth32, sreq->digest, diglen); + } + + for (i = 0; i < 5; i++) + writel_relaxed(auth32[i], (pce_dev->iobase + + (CRYPTO_AUTH_IV0_REG + i * sizeof(uint32_t)))); + + if ((sreq->alg == QCE_HASH_SHA256) || + (sreq->alg == QCE_HASH_SHA256_HMAC)) { + writel_relaxed(auth32[5], pce_dev->iobase + + CRYPTO_AUTH_IV5_REG); + writel_relaxed(auth32[6], pce_dev->iobase + + CRYPTO_AUTH_IV6_REG); + writel_relaxed(auth32[7], pce_dev->iobase + + CRYPTO_AUTH_IV7_REG); + } + + /* write auth_bytecnt 0/1, start with 0 */ + for (i = 0; i < 4; i++) + writel_relaxed(sreq->auth_data[i], (pce_dev->iobase + + (CRYPTO_AUTH_BYTECNT0_REG + i * sizeof(uint32_t)))); + + /* write seg_cfg */ + if (sha1) + auth_cfg |= (CRYPTO_AUTH_SIZE_SHA1 << CRYPTO_AUTH_SIZE); + else + auth_cfg |= (CRYPTO_AUTH_SIZE_SHA256 << CRYPTO_AUTH_SIZE); + + if (sreq->last_blk) + auth_cfg |= 1 << CRYPTO_LAST; + + auth_cfg |= CRYPTO_AUTH_ALG_SHA << CRYPTO_AUTH_ALG; + +go_proc: + auth_cfg |= (CRYPTO_AUTH_POS_BEFORE << CRYPTO_AUTH_POS); + + /* write seg_cfg */ + writel_relaxed(auth_cfg, pce_dev->iobase + CRYPTO_AUTH_SEG_CFG_REG); + + /* write seg_size */ + writel_relaxed(sreq->size, pce_dev->iobase + CRYPTO_SEG_SIZE_REG); + + /* write auth_seg_size */ + writel_relaxed(sreq->size, pce_dev->iobase + CRYPTO_AUTH_SEG_SIZE_REG); + + /* write auth_seg_start */ + writel_relaxed(0, pce_dev->iobase + CRYPTO_AUTH_SEG_START_REG); + /* + * Ensure previous instructions (write to all AUTH registers) + * was completed before accessing a register that is not in + * in the same 1K range. + */ + dsb(); + + writel_relaxed(0, pce_dev->iobase + CRYPTO_ENCR_SEG_CFG_REG); + /* + * Ensure previous instructions (setting all the CE registers) + * was completed before writing to GO register + */ + dsb(); + /* issue go to crypto */ + writel_relaxed(1 << CRYPTO_GO, pce_dev->iobase + CRYPTO_GOPROC_REG); + /* + * Ensure previous instructions (setting the GO register) + * was completed before issuing a DMA transfer request + */ + dsb(); + + return 0; +} + +static int _ce_setup_cipher(struct qce_device *pce_dev, struct qce_req *creq, + uint32_t totallen_in, uint32_t coffset) +{ + uint32_t enckey32[(MAX_CIPHER_KEY_SIZE * 2)/sizeof(uint32_t)] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t enciv32[MAX_IV_LENGTH / sizeof(uint32_t)] = { + 0, 0, 0, 0}; + uint32_t enck_size_in_word = creq->encklen / sizeof(uint32_t); + int aes_key_chg; + int i; + uint32_t encr_cfg = 0; + uint32_t ivsize = creq->ivsize; + + if (creq->mode == QCE_MODE_XTS) + _byte_stream_to_net_words(enckey32, creq->enckey, + creq->encklen/2); + else + _byte_stream_to_net_words(enckey32, creq->enckey, + creq->encklen); + + if ((creq->op == QCE_REQ_AEAD) && (creq->mode == QCE_MODE_CCM)) { + uint32_t authklen32 = creq->encklen/sizeof(uint32_t); + uint32_t noncelen32 = MAX_NONCE/sizeof(uint32_t); + uint32_t nonce32[MAX_NONCE/sizeof(uint32_t)] = {0, 0, 0, 0}; + uint32_t auth_cfg = 0; + + /* Clear auth_ivn, auth_keyn registers */ + for (i = 0; i < 16; i++) { + writel_relaxed(0, (pce_dev->iobase + + (CRYPTO_AUTH_IV0_REG + i*sizeof(uint32_t)))); + writel_relaxed(0, (pce_dev->iobase + + (CRYPTO_AUTH_KEY0_REG + i*sizeof(uint32_t)))); + } + /* write auth_bytecnt 0/1/2/3, start with 0 */ + for (i = 0; i < 4; i++) + writel_relaxed(0, pce_dev->iobase + + CRYPTO_AUTH_BYTECNT0_REG + + i * sizeof(uint32_t)); + /* write auth key */ + for (i = 0; i < authklen32; i++) + writel_relaxed(enckey32[i], pce_dev->iobase + + CRYPTO_AUTH_KEY0_REG + (i*sizeof(uint32_t))); + + /* write nonce */ + _byte_stream_to_net_words(nonce32, creq->nonce, MAX_NONCE); + for (i = 0; i < noncelen32; i++) + writel_relaxed(nonce32[i], pce_dev->iobase + + CRYPTO_AUTH_INFO_NONCE0_REG + + (i*sizeof(uint32_t))); + + auth_cfg |= (noncelen32 << CRYPTO_AUTH_NONCE_NUM_WORDS); + auth_cfg &= ~(1 << CRYPTO_USE_HW_KEY_AUTH); + auth_cfg |= (1 << CRYPTO_LAST); + if (creq->dir == QCE_ENCRYPT) + auth_cfg |= (CRYPTO_AUTH_POS_BEFORE << CRYPTO_AUTH_POS); + else + auth_cfg |= (CRYPTO_AUTH_POS_AFTER << CRYPTO_AUTH_POS); + auth_cfg |= (((creq->authsize >> 1) - 2) << CRYPTO_AUTH_SIZE); + auth_cfg |= (CRYPTO_AUTH_MODE_CCM << CRYPTO_AUTH_MODE); + if (creq->authklen == AES128_KEY_SIZE) + auth_cfg |= (CRYPTO_AUTH_KEY_SZ_AES128 << + CRYPTO_AUTH_KEY_SIZE); + else { + if (creq->authklen == AES256_KEY_SIZE) + auth_cfg |= (CRYPTO_AUTH_KEY_SZ_AES256 << + CRYPTO_AUTH_KEY_SIZE); + } + auth_cfg |= (CRYPTO_AUTH_ALG_AES << CRYPTO_AUTH_ALG); + writel_relaxed(auth_cfg, pce_dev->iobase + + CRYPTO_AUTH_SEG_CFG_REG); + if (creq->dir == QCE_ENCRYPT) + writel_relaxed(totallen_in, pce_dev->iobase + + CRYPTO_AUTH_SEG_SIZE_REG); + else + writel_relaxed((totallen_in - creq->authsize), + pce_dev->iobase + CRYPTO_AUTH_SEG_SIZE_REG); + writel_relaxed(0, pce_dev->iobase + CRYPTO_AUTH_SEG_START_REG); + } else { + writel_relaxed(0, pce_dev->iobase + CRYPTO_AUTH_SEG_CFG_REG); + } + /* + * Ensure previous instructions (write to all AUTH registers) + * was completed before accessing a register that is not in + * in the same 1K range. + */ + dsb(); + + switch (creq->mode) { + case QCE_MODE_ECB: + encr_cfg |= (CRYPTO_ENCR_MODE_ECB << CRYPTO_ENCR_MODE); + break; + + case QCE_MODE_CBC: + encr_cfg |= (CRYPTO_ENCR_MODE_CBC << CRYPTO_ENCR_MODE); + break; + + case QCE_MODE_XTS: + encr_cfg |= (CRYPTO_ENCR_MODE_XTS << CRYPTO_ENCR_MODE); + break; + + case QCE_MODE_CCM: + encr_cfg |= (CRYPTO_ENCR_MODE_CCM << CRYPTO_ENCR_MODE); + break; + + case QCE_MODE_CTR: + default: + encr_cfg |= (CRYPTO_ENCR_MODE_CTR << CRYPTO_ENCR_MODE); + break; + } + pce_dev->mode = creq->mode; + + switch (creq->alg) { + case CIPHER_ALG_DES: + if (creq->mode != QCE_MODE_ECB) { + _byte_stream_to_net_words(enciv32, creq->iv, ivsize); + writel_relaxed(enciv32[0], pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG); + writel_relaxed(enciv32[1], pce_dev->iobase + + CRYPTO_CNTR1_IV1_REG); + } + writel_relaxed(enckey32[0], pce_dev->iobase + + CRYPTO_ENCR_KEY0_REG); + writel_relaxed(enckey32[1], pce_dev->iobase + + CRYPTO_ENCR_KEY1_REG); + encr_cfg |= ((CRYPTO_ENCR_KEY_SZ_DES << CRYPTO_ENCR_KEY_SZ) | + (CRYPTO_ENCR_ALG_DES << CRYPTO_ENCR_ALG)); + break; + + case CIPHER_ALG_3DES: + if (creq->mode != QCE_MODE_ECB) { + _byte_stream_to_net_words(enciv32, creq->iv, ivsize); + writel_relaxed(enciv32[0], pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG); + writel_relaxed(enciv32[1], pce_dev->iobase + + CRYPTO_CNTR1_IV1_REG); + } + for (i = 0; i < 6; i++) + writel_relaxed(enckey32[0], (pce_dev->iobase + + (CRYPTO_ENCR_KEY0_REG + i * sizeof(uint32_t)))); + + encr_cfg |= ((CRYPTO_ENCR_KEY_SZ_3DES << CRYPTO_ENCR_KEY_SZ) | + (CRYPTO_ENCR_ALG_DES << CRYPTO_ENCR_ALG)); + break; + + case CIPHER_ALG_AES: + default: + if (creq->mode == QCE_MODE_XTS) { + uint32_t xtskey32[MAX_CIPHER_KEY_SIZE/sizeof(uint32_t)] + = {0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t xtsklen = + creq->encklen/(2 * sizeof(uint32_t)); + + _byte_stream_to_net_words(xtskey32, (creq->enckey + + creq->encklen/2), creq->encklen/2); + for (i = 0; i < xtsklen; i++) + writel_relaxed(xtskey32[i], pce_dev->iobase + + CRYPTO_ENCR_XTS_KEY0_REG + + (i * sizeof(uint32_t))); + + writel_relaxed(creq->cryptlen , + pce_dev->iobase + + CRYPTO_ENCR_XTS_DU_SIZE_REG); + } + if (creq->mode != QCE_MODE_ECB) { + if (creq->mode == QCE_MODE_XTS) + _byte_stream_swap_to_net_words(enciv32, + creq->iv, ivsize); + else + _byte_stream_to_net_words(enciv32, creq->iv, + ivsize); + for (i = 0; i <= 3; i++) + writel_relaxed(enciv32[i], pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG + + (i * sizeof(uint32_t))); + } + /* set number of counter bits */ + writel_relaxed(0xffffffff, pce_dev->iobase + + CRYPTO_CNTR_MASK_REG); + + if (creq->op == QCE_REQ_ABLK_CIPHER_NO_KEY) { + encr_cfg |= (CRYPTO_ENCR_KEY_SZ_AES128 << + CRYPTO_ENCR_KEY_SZ); + encr_cfg |= CRYPTO_ENCR_ALG_AES << CRYPTO_ENCR_ALG; + } else { + uint32_t key_size; + + if (creq->mode == QCE_MODE_XTS) { + key_size = creq->encklen/2; + enck_size_in_word = key_size/sizeof(uint32_t); + } else { + key_size = creq->encklen; + } + + switch (key_size) { + case AES128_KEY_SIZE: + encr_cfg |= (CRYPTO_ENCR_KEY_SZ_AES128 << + CRYPTO_ENCR_KEY_SZ); + break; + case AES256_KEY_SIZE: + default: + encr_cfg |= (CRYPTO_ENCR_KEY_SZ_AES256 << + CRYPTO_ENCR_KEY_SZ); + + /* check for null key. If null, use hw key*/ + for (i = 0; i < enck_size_in_word; i++) { + if (enckey32[i] != 0) + break; + } + if (i == enck_size_in_word) + encr_cfg |= 1 << CRYPTO_USE_HW_KEY; + break; + } /* end of switch (creq->encklen) */ + + encr_cfg |= CRYPTO_ENCR_ALG_AES << CRYPTO_ENCR_ALG; + if (pce_dev->cipher_key_size != creq->encklen) + aes_key_chg = 1; + else { + for (i = 0; i < enck_size_in_word; i++) { + if (enckey32[i] + != pce_dev->cipher_key[i]) + break; + } + aes_key_chg = (i == enck_size_in_word) ? 0 : 1; + } + + if (aes_key_chg) { + for (i = 0; i < enck_size_in_word; i++) + writel_relaxed(enckey32[i], + pce_dev->iobase + + CRYPTO_ENCR_KEY0_REG + + (i * sizeof(uint32_t))); + pce_dev->cipher_key_size = creq->encklen; + for (i = 0; i < enck_size_in_word; i++) + pce_dev->cipher_key[i] = enckey32[i]; + } /*if (aes_key_chg) { */ + } /* else of if (creq->op == QCE_REQ_ABLK_CIPHER_NO_KEY) */ + break; + } /* end of switch (creq->mode) */ + + /* write encr seg cfg */ + encr_cfg |= ((creq->dir == QCE_ENCRYPT) ? 1 : 0) << CRYPTO_ENCODE; + + /* write encr seg cfg */ + writel_relaxed(encr_cfg, pce_dev->iobase + CRYPTO_ENCR_SEG_CFG_REG); + + /* write encr seg size */ + if ((creq->mode == QCE_MODE_CCM) && (creq->dir == QCE_DECRYPT)) + writel_relaxed((creq->cryptlen + creq->authsize), + pce_dev->iobase + CRYPTO_ENCR_SEG_SIZE_REG); + else + writel_relaxed(creq->cryptlen, + pce_dev->iobase + CRYPTO_ENCR_SEG_SIZE_REG); + /* write encr seg start */ + writel_relaxed((coffset & 0xffff), + pce_dev->iobase + CRYPTO_ENCR_SEG_START_REG); + /* write seg size */ + writel_relaxed(totallen_in, pce_dev->iobase + CRYPTO_SEG_SIZE_REG); + /* + * Ensure previous instructions (setting all the CE registers) + * was completed before writing to GO register + */ + dsb(); + /* issue go to crypto */ + writel_relaxed(1 << CRYPTO_GO, pce_dev->iobase + CRYPTO_GOPROC_REG); + /* + * Ensure previous instructions (setting the GO register) + * was completed before issuing a DMA transfer request + */ + dsb(); + return 0; +}; + +static int _aead_complete(struct qce_device *pce_dev) +{ + struct aead_request *areq; + int i; + uint32_t ivsize; + uint32_t iv_out[4]; + unsigned char iv[4 * sizeof(uint32_t)]; + + areq = (struct aead_request *) pce_dev->areq; + ivsize = pce_dev->ivsize; + + if (areq->src != areq->dst) { + dma_unmap_sg(pce_dev->pdev, areq->dst, pce_dev->dst_nents, + DMA_FROM_DEVICE); + } + dma_unmap_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + + if (pce_dev->mode != QCE_MODE_CCM) + dma_unmap_single(pce_dev->pdev, pce_dev->phy_iv_in, + ivsize, DMA_TO_DEVICE); + dma_unmap_sg(pce_dev->pdev, areq->assoc, pce_dev->assoc_nents, + DMA_TO_DEVICE); + + /* get iv out */ + if ((pce_dev->mode == QCE_MODE_ECB) || + (pce_dev->mode == QCE_MODE_CCM)) { + if (pce_dev->mode == QCE_MODE_CCM) { + int result; + result = readl_relaxed(pce_dev->iobase + + CRYPTO_STATUS_REG); + result &= (1 << CRYPTO_MAC_FAILED); + result |= (pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + dsb(); + pce_dev->qce_cb(areq, pce_dev->dig_result, NULL, + result); + } else { + pce_dev->qce_cb(areq, pce_dev->dig_result, NULL, + pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } + } else { + for (i = 0; i < 4; i++) + iv_out[i] = readl_relaxed(pce_dev->iobase + + (CRYPTO_CNTR0_IV0_REG + i * sizeof(uint32_t))); + + _net_words_to_byte_stream(iv_out, iv, sizeof(iv)); + pce_dev->qce_cb(areq, pce_dev->dig_result, iv, + pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + }; + return 0; +}; + +static void _sha_complete(struct qce_device *pce_dev) +{ + + struct ahash_request *areq; + uint32_t auth_data[4]; + uint32_t digest[8]; + int i; + + areq = (struct ahash_request *) pce_dev->areq; + dma_unmap_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + DMA_TO_DEVICE); + + for (i = 0; i < 4; i++) + auth_data[i] = readl_relaxed(pce_dev->iobase + + (CRYPTO_AUTH_BYTECNT0_REG + + i * sizeof(uint32_t))); + + for (i = 0; i < 8; i++) + digest[i] = readl_relaxed(pce_dev->iobase + + CRYPTO_AUTH_IV0_REG + (i * sizeof(uint32_t))); + + _net_words_to_byte_stream(digest, pce_dev->dig_result, + SHA256_DIGEST_SIZE); + + pce_dev->qce_cb(areq, pce_dev->dig_result, (unsigned char *)auth_data, + pce_dev->chan_ce_in_status); +}; + +static int _ablk_cipher_complete(struct qce_device *pce_dev) +{ + struct ablkcipher_request *areq; + uint32_t iv_out[4]; + unsigned char iv[4 * sizeof(uint32_t)]; + + areq = (struct ablkcipher_request *) pce_dev->areq; + + if (areq->src != areq->dst) { + dma_unmap_sg(pce_dev->pdev, areq->dst, + pce_dev->dst_nents, DMA_FROM_DEVICE); + } + dma_unmap_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + /* get iv out */ + if (pce_dev->mode == QCE_MODE_ECB) { + pce_dev->qce_cb(areq, NULL, NULL, pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } else { + int i; + + for (i = 0; i < 4; i++) + iv_out[i] = readl_relaxed(pce_dev->iobase + + (CRYPTO_CNTR0_IV0_REG + i * sizeof(uint32_t))); + + _net_words_to_byte_stream(iv_out, iv, sizeof(iv)); + pce_dev->qce_cb(areq, NULL, iv, pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } + + return 0; +}; + +static int _ablk_cipher_use_pmem_complete(struct qce_device *pce_dev) +{ + struct ablkcipher_request *areq; + uint32_t iv_out[4]; + unsigned char iv[4 * sizeof(uint32_t)]; + + areq = (struct ablkcipher_request *) pce_dev->areq; + + /* get iv out */ + if (pce_dev->mode == QCE_MODE_ECB) { + pce_dev->qce_cb(areq, NULL, NULL, pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } else { + int i; + + for (i = 0; i < 4; i++) + iv_out[i] = readl_relaxed(pce_dev->iobase + + CRYPTO_CNTR0_IV0_REG + (i * sizeof(uint32_t))); + + _net_words_to_byte_stream(iv_out, iv, sizeof(iv)); + pce_dev->qce_cb(areq, NULL, iv, pce_dev->chan_ce_in_status | + pce_dev->chan_ce_out_status); + } + + return 0; +}; + +static int qce_split_and_insert_dm_desc(struct dmov_desc *pdesc, + unsigned int plen, unsigned int paddr, int *index) +{ + while (plen > 0x8000) { + pdesc->len = 0x8000; + if (paddr > 0) { + pdesc->addr = paddr; + paddr += 0x8000; + } + plen -= pdesc->len; + if (plen > 0) { + *index = (*index) + 1; + if ((*index) >= QCE_MAX_NUM_DESC) + return -ENOMEM; + pdesc++; + } + } + if ((plen > 0) && (plen <= 0x8000)) { + pdesc->len = plen; + if (paddr > 0) + pdesc->addr = paddr; + } + + return 0; +} + +static int _chain_sg_buffer_in(struct qce_device *pce_dev, + struct scatterlist *sg, unsigned int nbytes) +{ + unsigned int len; + unsigned int dlen; + struct dmov_desc *pdesc; + + pdesc = pce_dev->ce_in_dst_desc + pce_dev->ce_in_dst_desc_index; + if (nbytes > 0x8000) + qce_split_and_insert_dm_desc(pdesc, nbytes, 0, + &pce_dev->ce_in_dst_desc_index); + else + pdesc->len = nbytes; + + pdesc = pce_dev->ce_in_src_desc + pce_dev->ce_in_src_desc_index; + /* + * Two consective chunks may be handled by the old + * buffer descriptor. + */ + while (nbytes > 0) { + len = min(nbytes, sg_dma_len(sg)); + dlen = pdesc->len & ADM_DESC_LENGTH_MASK; + nbytes -= len; + if (dlen == 0) { + pdesc->addr = sg_dma_address(sg); + pdesc->len = len; + if (pdesc->len > 0x8000) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + sg_dma_address(sg), + &pce_dev->ce_in_src_desc_index); + } else if (sg_dma_address(sg) == (pdesc->addr + dlen)) { + pdesc->len = dlen + len; + if (pdesc->len > 0x8000) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + pdesc->addr, + &pce_dev->ce_in_src_desc_index); + } else { + pce_dev->ce_in_src_desc_index++; + if (pce_dev->ce_in_src_desc_index >= QCE_MAX_NUM_DESC) + return -ENOMEM; + pdesc++; + pdesc->len = len; + pdesc->addr = sg_dma_address(sg); + if (pdesc->len > 0x8000) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + sg_dma_address(sg), + &pce_dev->ce_in_src_desc_index); + } + if (nbytes > 0) + sg = sg_next(sg); + } + return 0; +} + +static int _chain_pm_buffer_in(struct qce_device *pce_dev, + unsigned int pmem, unsigned int nbytes) +{ + unsigned int dlen; + struct dmov_desc *pdesc; + + pdesc = pce_dev->ce_in_src_desc + pce_dev->ce_in_src_desc_index; + dlen = pdesc->len & ADM_DESC_LENGTH_MASK; + if (dlen == 0) { + pdesc->addr = pmem; + pdesc->len = nbytes; + } else if (pmem == (pdesc->addr + dlen)) { + pdesc->len = dlen + nbytes; + } else { + pce_dev->ce_in_src_desc_index++; + if (pce_dev->ce_in_src_desc_index >= QCE_MAX_NUM_DESC) + return -ENOMEM; + pdesc++; + pdesc->len = nbytes; + pdesc->addr = pmem; + } + pdesc = pce_dev->ce_in_dst_desc + pce_dev->ce_in_dst_desc_index; + pdesc->len += nbytes; + + return 0; +} + +static void _chain_buffer_in_init(struct qce_device *pce_dev) +{ + struct dmov_desc *pdesc; + + pce_dev->ce_in_src_desc_index = 0; + pce_dev->ce_in_dst_desc_index = 0; + pdesc = pce_dev->ce_in_src_desc; + pdesc->len = 0; +} + +static void _ce_in_final(struct qce_device *pce_dev, unsigned total) +{ + struct dmov_desc *pdesc; + dmov_sg *pcmd; + + pdesc = pce_dev->ce_in_src_desc + pce_dev->ce_in_src_desc_index; + pdesc->len |= ADM_DESC_LAST; + pdesc = pce_dev->ce_in_dst_desc + pce_dev->ce_in_dst_desc_index; + pdesc->len |= ADM_DESC_LAST; + + pcmd = (dmov_sg *) pce_dev->cmd_list_ce_in; + pcmd->cmd |= CMD_LC; +} + +#ifdef QCE_DEBUG +static void _ce_in_dump(struct qce_device *pce_dev) +{ + int i; + struct dmov_desc *pdesc; + + dev_info(pce_dev->pdev, "_ce_in_dump: src\n"); + for (i = 0; i <= pce_dev->ce_in_src_desc_index; i++) { + pdesc = pce_dev->ce_in_src_desc + i; + dev_info(pce_dev->pdev, "%x , %x\n", pdesc->addr, + pdesc->len); + } + dev_info(pce_dev->pdev, "_ce_in_dump: dst\n"); + for (i = 0; i <= pce_dev->ce_in_dst_desc_index; i++) { + pdesc = pce_dev->ce_in_dst_desc + i; + dev_info(pce_dev->pdev, "%x , %x\n", pdesc->addr, + pdesc->len); + } +}; + +static void _ce_out_dump(struct qce_device *pce_dev) +{ + int i; + struct dmov_desc *pdesc; + + dev_info(pce_dev->pdev, "_ce_out_dump: src\n"); + for (i = 0; i <= pce_dev->ce_out_src_desc_index; i++) { + pdesc = pce_dev->ce_out_src_desc + i; + dev_info(pce_dev->pdev, "%x , %x\n", pdesc->addr, + pdesc->len); + } + + dev_info(pce_dev->pdev, "_ce_out_dump: dst\n"); + for (i = 0; i <= pce_dev->ce_out_dst_desc_index; i++) { + pdesc = pce_dev->ce_out_dst_desc + i; + dev_info(pce_dev->pdev, "%x , %x\n", pdesc->addr, + pdesc->len); + } +}; + +#else + +static void _ce_in_dump(struct qce_device *pce_dev) +{ +}; + +static void _ce_out_dump(struct qce_device *pce_dev) +{ +}; + +#endif + +static int _chain_sg_buffer_out(struct qce_device *pce_dev, + struct scatterlist *sg, unsigned int nbytes) +{ + unsigned int len; + unsigned int dlen; + struct dmov_desc *pdesc; + + pdesc = pce_dev->ce_out_src_desc + pce_dev->ce_out_src_desc_index; + if (nbytes > 0x8000) + qce_split_and_insert_dm_desc(pdesc, nbytes, 0, + &pce_dev->ce_out_src_desc_index); + else + pdesc->len = nbytes; + + pdesc = pce_dev->ce_out_dst_desc + pce_dev->ce_out_dst_desc_index; + /* + * Two consective chunks may be handled by the old + * buffer descriptor. + */ + while (nbytes > 0) { + len = min(nbytes, sg_dma_len(sg)); + dlen = pdesc->len & ADM_DESC_LENGTH_MASK; + nbytes -= len; + if (dlen == 0) { + pdesc->addr = sg_dma_address(sg); + pdesc->len = len; + if (pdesc->len > 0x8000) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + sg_dma_address(sg), + &pce_dev->ce_out_dst_desc_index); + } else if (sg_dma_address(sg) == (pdesc->addr + dlen)) { + pdesc->len = dlen + len; + if (pdesc->len > 0x8000) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + pdesc->addr, + &pce_dev->ce_out_dst_desc_index); + + } else { + pce_dev->ce_out_dst_desc_index++; + if (pce_dev->ce_out_dst_desc_index >= QCE_MAX_NUM_DESC) + return -EIO; + pdesc++; + pdesc->len = len; + pdesc->addr = sg_dma_address(sg); + if (pdesc->len > 0x8000) + qce_split_and_insert_dm_desc(pdesc, pdesc->len, + sg_dma_address(sg), + &pce_dev->ce_out_dst_desc_index); + + } + if (nbytes > 0) + sg = sg_next(sg); + } + return 0; +} + +static int _chain_pm_buffer_out(struct qce_device *pce_dev, + unsigned int pmem, unsigned int nbytes) +{ + unsigned int dlen; + struct dmov_desc *pdesc; + + pdesc = pce_dev->ce_out_dst_desc + pce_dev->ce_out_dst_desc_index; + dlen = pdesc->len & ADM_DESC_LENGTH_MASK; + + if (dlen == 0) { + pdesc->addr = pmem; + pdesc->len = nbytes; + } else if (pmem == (pdesc->addr + dlen)) { + pdesc->len = dlen + nbytes; + } else { + pce_dev->ce_out_dst_desc_index++; + if (pce_dev->ce_out_dst_desc_index >= QCE_MAX_NUM_DESC) + return -EIO; + pdesc++; + pdesc->len = nbytes; + pdesc->addr = pmem; + } + pdesc = pce_dev->ce_out_src_desc + pce_dev->ce_out_src_desc_index; + pdesc->len += nbytes; + + return 0; +}; + +static void _chain_buffer_out_init(struct qce_device *pce_dev) +{ + struct dmov_desc *pdesc; + + pce_dev->ce_out_dst_desc_index = 0; + pce_dev->ce_out_src_desc_index = 0; + pdesc = pce_dev->ce_out_dst_desc; + pdesc->len = 0; +}; + +static void _ce_out_final(struct qce_device *pce_dev, unsigned total) +{ + struct dmov_desc *pdesc; + dmov_sg *pcmd; + + pdesc = pce_dev->ce_out_dst_desc + pce_dev->ce_out_dst_desc_index; + pdesc->len |= ADM_DESC_LAST; + pdesc = pce_dev->ce_out_src_desc + pce_dev->ce_out_src_desc_index; + pdesc->len |= ADM_DESC_LAST; + pcmd = (dmov_sg *) pce_dev->cmd_list_ce_out; + pcmd->cmd |= CMD_LC; +}; + +static void _aead_ce_in_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else { + pce_dev->chan_ce_in_status = 0; + } + + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _aead_complete(pce_dev); + } +}; + +static void _aead_ce_out_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_out_status = -1; + } else { + pce_dev->chan_ce_out_status = 0; + }; + + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _aead_complete(pce_dev); + } + +}; + +static void _sha_ce_in_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else { + pce_dev->chan_ce_in_status = 0; + } + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + _sha_complete(pce_dev); +}; + +static void _ablk_cipher_ce_in_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else { + pce_dev->chan_ce_in_status = 0; + } + + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _ablk_cipher_complete(pce_dev); + } +}; + +static void _ablk_cipher_ce_out_call_back(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_out_status = -1; + } else { + pce_dev->chan_ce_out_status = 0; + }; + + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _ablk_cipher_complete(pce_dev); + } +}; + + +static void _ablk_cipher_ce_in_call_back_pmem(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_in_status = -1; + } else { + pce_dev->chan_ce_in_status = 0; + } + + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_out_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _ablk_cipher_use_pmem_complete(pce_dev); + } +}; + +static void _ablk_cipher_ce_out_call_back_pmem(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, struct msm_dmov_errdata *err) +{ + struct qce_device *pce_dev; + + pce_dev = (struct qce_device *) cmd_ptr->user; + if (result != ADM_STATUS_OK) { + dev_err(pce_dev->pdev, "Qualcomm ADM status error %x\n", + result); + pce_dev->chan_ce_out_status = -1; + } else { + pce_dev->chan_ce_out_status = 0; + }; + + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_COMP; + if (pce_dev->chan_ce_in_state == QCE_CHAN_STATE_COMP) { + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + /* done */ + _ablk_cipher_use_pmem_complete(pce_dev); + } +}; + +static int _setup_cmd_template(struct qce_device *pce_dev) +{ + dmov_sg *pcmd; + struct dmov_desc *pdesc; + unsigned char *vaddr; + int i = 0; + + /* Divide up the 4K coherent memory */ + + /* 1. ce_in channel 1st command src descriptors, 128 entries */ + vaddr = pce_dev->coh_vmem; + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + pce_dev->ce_in_src_desc = (struct dmov_desc *) vaddr; + pce_dev->phy_ce_in_src_desc = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + /* 2. ce_in channel 1st command dst descriptor, 1 entry */ + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + pce_dev->ce_in_dst_desc = (struct dmov_desc *) vaddr; + pce_dev->phy_ce_in_dst_desc = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + /* 3. ce_in channel command list of one scatter gather command */ + pce_dev->cmd_list_ce_in = vaddr; + pce_dev->phy_cmd_list_ce_in = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(dmov_sg); + + /* 4. authentication result. */ + pce_dev->dig_result = vaddr; + pce_dev->phy_dig_result = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + SHA256_DIGESTSIZE; + + /* 5. ce_out channel command list of one scatter gather command */ + pce_dev->cmd_list_ce_out = vaddr; + pce_dev->phy_cmd_list_ce_out = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(dmov_sg); + + /* 6. ce_out channel command src descriptors, 1 entry */ + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + pce_dev->ce_out_src_desc = (struct dmov_desc *) vaddr; + pce_dev->phy_ce_out_src_desc = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + /* 7. ce_out channel command dst descriptors, 128 entries. */ + vaddr = (unsigned char *) ALIGN(((unsigned int)vaddr), 16); + pce_dev->ce_out_dst_desc = (struct dmov_desc *) vaddr; + pce_dev->phy_ce_out_dst_desc = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + (sizeof(struct dmov_desc) * QCE_MAX_NUM_DESC); + + /* 8. pad area. */ + pce_dev->ce_pad = vaddr; + pce_dev->phy_ce_pad = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + + /* Padding length is set to twice for worst case scenario in AES-CCM */ + vaddr = vaddr + 2 * ADM_CE_BLOCK_SIZE; + + /* 9. ce_in channel command pointer list. */ + vaddr = (unsigned char *) ALIGN(((unsigned int) vaddr), 8); + pce_dev->cmd_pointer_list_ce_in = (unsigned int *) vaddr; + pce_dev->phy_cmd_pointer_list_ce_in = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(unsigned char *); + + /* 10. ce_ou channel command pointer list. */ + vaddr = (unsigned char *) ALIGN(((unsigned int) vaddr), 8); + pce_dev->cmd_pointer_list_ce_out = (unsigned int *) vaddr; + pce_dev->phy_cmd_pointer_list_ce_out = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + vaddr = vaddr + sizeof(unsigned char *); + + /* 11. throw away area to store by-pass data from ce_out. */ + pce_dev->ce_out_ignore = (unsigned char *) vaddr; + pce_dev->phy_ce_out_ignore = pce_dev->coh_pmem + + (vaddr - pce_dev->coh_vmem); + pce_dev->ce_out_ignore_size = PAGE_SIZE - (vaddr - + pce_dev->coh_vmem); /* at least 1.5 K of space */ + /* + * The first command of command list ce_in is for the input of + * concurrent operation of encrypt/decrypt or for the input + * of authentication. + */ + pcmd = (dmov_sg *) pce_dev->cmd_list_ce_in; + /* swap byte and half word , dst crci , scatter gather */ + pcmd->cmd = CMD_DST_SWAP_BYTES | CMD_DST_SWAP_SHORTS | + CMD_DST_CRCI(pce_dev->crci_in) | CMD_MODE_SG; + pdesc = pce_dev->ce_in_src_desc; + pdesc->addr = 0; /* to be filled in each operation */ + pdesc->len = 0; /* to be filled in each operation */ + pcmd->src_dscr = (unsigned) pce_dev->phy_ce_in_src_desc; + + pdesc = pce_dev->ce_in_dst_desc; + for (i = 0; i < QCE_MAX_NUM_DESC; i++) { + pdesc->addr = (CRYPTO_DATA_SHADOW0 + pce_dev->phy_iobase); + pdesc->len = 0; /* to be filled in each operation */ + pdesc++; + } + pcmd->dst_dscr = (unsigned) pce_dev->phy_ce_in_dst_desc; + pcmd->_reserved = LI_SG_CMD | SRC_INDEX_SG_CMD(0) | + DST_INDEX_SG_CMD(0); + pcmd++; + + /* setup command pointer list */ + *(pce_dev->cmd_pointer_list_ce_in) = (CMD_PTR_LP | DMOV_CMD_LIST | + DMOV_CMD_ADDR((unsigned int) + pce_dev->phy_cmd_list_ce_in)); + pce_dev->chan_ce_in_cmd->user = (void *) pce_dev; + pce_dev->chan_ce_in_cmd->exec_func = NULL; + pce_dev->chan_ce_in_cmd->cmdptr = DMOV_CMD_ADDR( + (unsigned int) pce_dev->phy_cmd_pointer_list_ce_in); + pce_dev->chan_ce_in_cmd->crci_mask = msm_dmov_build_crci_mask(1, + pce_dev->crci_in); + + + /* + * The first command in the command list ce_out. + * It is for encry/decryp output. + * If hashing only, ce_out is not used. + */ + pcmd = (dmov_sg *) pce_dev->cmd_list_ce_out; + /* swap byte, half word, source crci, scatter gather */ + pcmd->cmd = CMD_SRC_SWAP_BYTES | CMD_SRC_SWAP_SHORTS | + CMD_SRC_CRCI(pce_dev->crci_out) | CMD_MODE_SG; + + pdesc = pce_dev->ce_out_src_desc; + for (i = 0; i < QCE_MAX_NUM_DESC; i++) { + pdesc->addr = (CRYPTO_DATA_SHADOW0 + pce_dev->phy_iobase); + pdesc->len = 0; /* to be filled in each operation */ + pdesc++; + } + pcmd->src_dscr = (unsigned) pce_dev->phy_ce_out_src_desc; + + pdesc = pce_dev->ce_out_dst_desc; + pdesc->addr = 0; /* to be filled in each operation */ + pdesc->len = 0; /* to be filled in each operation */ + pcmd->dst_dscr = (unsigned) pce_dev->phy_ce_out_dst_desc; + pcmd->_reserved = LI_SG_CMD | SRC_INDEX_SG_CMD(0) | + DST_INDEX_SG_CMD(0); + + pcmd++; + + /* setup command pointer list */ + *(pce_dev->cmd_pointer_list_ce_out) = (CMD_PTR_LP | DMOV_CMD_LIST | + DMOV_CMD_ADDR((unsigned int)pce_dev-> + phy_cmd_list_ce_out)); + + pce_dev->chan_ce_out_cmd->user = pce_dev; + pce_dev->chan_ce_out_cmd->exec_func = NULL; + pce_dev->chan_ce_out_cmd->cmdptr = DMOV_CMD_ADDR( + (unsigned int) pce_dev->phy_cmd_pointer_list_ce_out); + pce_dev->chan_ce_out_cmd->crci_mask = msm_dmov_build_crci_mask(1, + pce_dev->crci_out); + + return 0; +}; + +static int _qce_start_dma(struct qce_device *pce_dev, bool ce_in, bool ce_out) +{ + + if (ce_in) + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IN_PROG; + else + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_COMP; + + if (ce_out) + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IN_PROG; + else + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_COMP; + + if (ce_in) + msm_dmov_enqueue_cmd(pce_dev->chan_ce_in, + pce_dev->chan_ce_in_cmd); + if (ce_out) + msm_dmov_enqueue_cmd(pce_dev->chan_ce_out, + pce_dev->chan_ce_out_cmd); + + return 0; +}; + +int qce_aead_req(void *handle, struct qce_req *q_req) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + struct aead_request *areq = (struct aead_request *) q_req->areq; + uint32_t authsize = q_req->authsize; + uint32_t totallen_in, totallen_out, out_len; + uint32_t pad_len_in, pad_len_out; + uint32_t pad_mac_len_out, pad_ptx_len_out; + int rc = 0; + + if (q_req->dir == QCE_ENCRYPT) { + q_req->cryptlen = areq->cryptlen; + totallen_in = q_req->cryptlen + areq->assoclen; + totallen_out = q_req->cryptlen + authsize + areq->assoclen; + out_len = areq->cryptlen + authsize; + pad_len_in = ALIGN(totallen_in, ADM_CE_BLOCK_SIZE) - + totallen_in; + pad_mac_len_out = ALIGN(authsize, ADM_CE_BLOCK_SIZE) - + authsize; + pad_ptx_len_out = ALIGN(q_req->cryptlen, ADM_CE_BLOCK_SIZE) - + q_req->cryptlen; + pad_len_out = pad_ptx_len_out + pad_mac_len_out; + totallen_out += pad_len_out; + } else { + q_req->cryptlen = areq->cryptlen - authsize; + totallen_in = areq->cryptlen + areq->assoclen; + totallen_out = q_req->cryptlen + areq->assoclen; + out_len = areq->cryptlen - authsize; + pad_len_in = ALIGN(areq->cryptlen, ADM_CE_BLOCK_SIZE) - + areq->cryptlen; + pad_len_out = pad_len_in + authsize; + totallen_out += pad_len_out; + } + + _chain_buffer_in_init(pce_dev); + _chain_buffer_out_init(pce_dev); + + pce_dev->assoc_nents = 0; + pce_dev->src_nents = 0; + pce_dev->dst_nents = 0; + pce_dev->ivsize = q_req->ivsize; + pce_dev->authsize = q_req->authsize; + + /* associated data input */ + pce_dev->assoc_nents = count_sg(areq->assoc, areq->assoclen); + dma_map_sg(pce_dev->pdev, areq->assoc, pce_dev->assoc_nents, + DMA_TO_DEVICE); + if (_chain_sg_buffer_in(pce_dev, areq->assoc, areq->assoclen) < 0) { + rc = -ENOMEM; + goto bad; + } + /* cipher input */ + pce_dev->src_nents = count_sg(areq->src, areq->cryptlen); + dma_map_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + if (_chain_sg_buffer_in(pce_dev, areq->src, areq->cryptlen) < 0) { + rc = -ENOMEM; + goto bad; + } + /* pad data in */ + if (pad_len_in) { + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_ce_pad, + pad_len_in) < 0) { + rc = -ENOMEM; + goto bad; + } + } + + /* ignore associated data */ + if (_chain_pm_buffer_out(pce_dev, pce_dev->phy_ce_out_ignore, + areq->assoclen) < 0) { + rc = -ENOMEM; + goto bad; + } + /* cipher + mac output for encryption */ + if (areq->src != areq->dst) { + pce_dev->dst_nents = count_sg(areq->dst, out_len); + dma_map_sg(pce_dev->pdev, areq->dst, pce_dev->dst_nents, + DMA_FROM_DEVICE); + }; + if (_chain_sg_buffer_out(pce_dev, areq->dst, out_len) < 0) { + rc = -ENOMEM; + goto bad; + } + /* pad data out */ + if (pad_len_out) { + if (_chain_pm_buffer_out(pce_dev, pce_dev->phy_ce_pad, + pad_len_out) < 0) { + rc = -ENOMEM; + goto bad; + } + } + + /* finalize the ce_in and ce_out channels command lists */ + _ce_in_final(pce_dev, ALIGN(totallen_in, ADM_CE_BLOCK_SIZE)); + _ce_out_final(pce_dev, ALIGN(totallen_out, ADM_CE_BLOCK_SIZE)); + + /* set up crypto device */ + rc = _ce_setup_cipher(pce_dev, q_req, totallen_in, areq->assoclen); + if (rc < 0) + goto bad; + + /* setup for callback, and issue command to adm */ + pce_dev->areq = q_req->areq; + pce_dev->qce_cb = q_req->qce_cb; + + pce_dev->chan_ce_in_cmd->complete_func = _aead_ce_in_call_back; + pce_dev->chan_ce_out_cmd->complete_func = _aead_ce_out_call_back; + + _ce_in_dump(pce_dev); + _ce_out_dump(pce_dev); + + rc = _qce_start_dma(pce_dev, true, true); + if (rc == 0) + return 0; +bad: + if (pce_dev->assoc_nents) { + dma_unmap_sg(pce_dev->pdev, areq->assoc, pce_dev->assoc_nents, + DMA_TO_DEVICE); + } + + if (pce_dev->src_nents) { + dma_unmap_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + } + if (pce_dev->dst_nents) { + dma_unmap_sg(pce_dev->pdev, areq->dst, pce_dev->dst_nents, + DMA_FROM_DEVICE); + } + return rc; +} +EXPORT_SYMBOL(qce_aead_req); + +int qce_ablk_cipher_req(void *handle, struct qce_req *c_req) +{ + int rc = 0; + struct qce_device *pce_dev = (struct qce_device *) handle; + struct ablkcipher_request *areq = (struct ablkcipher_request *) + c_req->areq; + + uint32_t pad_len = ALIGN(areq->nbytes, ADM_CE_BLOCK_SIZE) + - areq->nbytes; + + _chain_buffer_in_init(pce_dev); + _chain_buffer_out_init(pce_dev); + + pce_dev->src_nents = 0; + pce_dev->dst_nents = 0; + + /* cipher input */ + pce_dev->src_nents = count_sg(areq->src, areq->nbytes); + + if (c_req->use_pmem != 1) + dma_map_sg(pce_dev->pdev, areq->src, pce_dev->src_nents, + (areq->src == areq->dst) ? DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + else + dma_map_pmem_sg(&c_req->pmem->src[0], pce_dev->src_nents, + areq->src); + + if (_chain_sg_buffer_in(pce_dev, areq->src, areq->nbytes) < 0) { + rc = -ENOMEM; + goto bad; + } + + /* cipher output */ + if (areq->src != areq->dst) { + pce_dev->dst_nents = count_sg(areq->dst, areq->nbytes); + if (c_req->use_pmem != 1) + dma_map_sg(pce_dev->pdev, areq->dst, pce_dev->dst_nents, + DMA_FROM_DEVICE); + else + dma_map_pmem_sg(&c_req->pmem->dst[0], + pce_dev->dst_nents, areq->dst); + }; + if (_chain_sg_buffer_out(pce_dev, areq->dst, areq->nbytes) < 0) { + rc = -ENOMEM; + goto bad; + } + + /* pad data */ + if (pad_len) { + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + if (_chain_pm_buffer_out(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + } + + /* finalize the ce_in and ce_out channels command lists */ + _ce_in_final(pce_dev, areq->nbytes + pad_len); + _ce_out_final(pce_dev, areq->nbytes + pad_len); + + _ce_in_dump(pce_dev); + _ce_out_dump(pce_dev); + + /* set up crypto device */ + rc = _ce_setup_cipher(pce_dev, c_req, areq->nbytes, 0); + if (rc < 0) + goto bad; + + /* setup for callback, and issue command to adm */ + pce_dev->areq = areq; + pce_dev->qce_cb = c_req->qce_cb; + if (c_req->use_pmem == 1) { + pce_dev->chan_ce_in_cmd->complete_func = + _ablk_cipher_ce_in_call_back_pmem; + pce_dev->chan_ce_out_cmd->complete_func = + _ablk_cipher_ce_out_call_back_pmem; + } else { + pce_dev->chan_ce_in_cmd->complete_func = + _ablk_cipher_ce_in_call_back; + pce_dev->chan_ce_out_cmd->complete_func = + _ablk_cipher_ce_out_call_back; + } + rc = _qce_start_dma(pce_dev, true, true); + + if (rc == 0) + return 0; +bad: + if (c_req->use_pmem != 1) { + if (pce_dev->dst_nents) { + dma_unmap_sg(pce_dev->pdev, areq->dst, + pce_dev->dst_nents, DMA_FROM_DEVICE); + } + if (pce_dev->src_nents) { + dma_unmap_sg(pce_dev->pdev, areq->src, + pce_dev->src_nents, + (areq->src == areq->dst) ? + DMA_BIDIRECTIONAL : + DMA_TO_DEVICE); + } + } + return rc; +} +EXPORT_SYMBOL(qce_ablk_cipher_req); + +int qce_process_sha_req(void *handle, struct qce_sha_req *sreq) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + int rc; + uint32_t pad_len = ALIGN(sreq->size, ADM_CE_BLOCK_SIZE) - sreq->size; + struct ahash_request *areq = (struct ahash_request *)sreq->areq; + + _chain_buffer_in_init(pce_dev); + pce_dev->src_nents = count_sg(sreq->src, sreq->size); + dma_map_sg(pce_dev->pdev, sreq->src, pce_dev->src_nents, + DMA_TO_DEVICE); + + if (_chain_sg_buffer_in(pce_dev, sreq->src, sreq->size) < 0) { + rc = -ENOMEM; + goto bad; + } + + if (pad_len) { + if (_chain_pm_buffer_in(pce_dev, pce_dev->phy_ce_pad, + pad_len) < 0) { + rc = -ENOMEM; + goto bad; + } + } + _ce_in_final(pce_dev, sreq->size + pad_len); + + _ce_in_dump(pce_dev); + + rc = _ce_setup_hash(pce_dev, sreq); + + if (rc < 0) + goto bad; + + pce_dev->areq = areq; + pce_dev->qce_cb = sreq->qce_cb; + pce_dev->chan_ce_in_cmd->complete_func = _sha_ce_in_call_back; + + rc = _qce_start_dma(pce_dev, true, false); + + if (rc == 0) + return 0; +bad: + if (pce_dev->src_nents) { + dma_unmap_sg(pce_dev->pdev, sreq->src, + pce_dev->src_nents, DMA_TO_DEVICE); + } + + return rc; +} +EXPORT_SYMBOL(qce_process_sha_req); + +/* crypto engine open function. */ +void *qce_open(struct platform_device *pdev, int *rc) +{ + struct qce_device *pce_dev; + struct resource *resource; + struct clk *ce_core_clk; + struct clk *ce_clk; + + pce_dev = kzalloc(sizeof(struct qce_device), GFP_KERNEL); + if (!pce_dev) { + *rc = -ENOMEM; + dev_err(&pdev->dev, "Can not allocate memory\n"); + return NULL; + } + pce_dev->pdev = &pdev->dev; + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + *rc = -ENXIO; + dev_err(pce_dev->pdev, "Missing MEM resource\n"); + goto err_pce_dev; + }; + pce_dev->phy_iobase = resource->start; + pce_dev->iobase = ioremap_nocache(resource->start, + resource->end - resource->start + 1); + if (!pce_dev->iobase) { + *rc = -ENOMEM; + dev_err(pce_dev->pdev, "Can not map io memory\n"); + goto err_pce_dev; + } + + pce_dev->chan_ce_in_cmd = kzalloc(sizeof(struct msm_dmov_cmd), + GFP_KERNEL); + pce_dev->chan_ce_out_cmd = kzalloc(sizeof(struct msm_dmov_cmd), + GFP_KERNEL); + if (pce_dev->chan_ce_in_cmd == NULL || + pce_dev->chan_ce_out_cmd == NULL) { + dev_err(pce_dev->pdev, "Can not allocate memory\n"); + *rc = -ENOMEM; + goto err_dm_chan_cmd; + } + + resource = platform_get_resource_byname(pdev, IORESOURCE_DMA, + "crypto_channels"); + if (!resource) { + *rc = -ENXIO; + dev_err(pce_dev->pdev, "Missing DMA channel resource\n"); + goto err_dm_chan_cmd; + }; + pce_dev->chan_ce_in = resource->start; + pce_dev->chan_ce_out = resource->end; + resource = platform_get_resource_byname(pdev, IORESOURCE_DMA, + "crypto_crci_in"); + if (!resource) { + *rc = -ENXIO; + dev_err(pce_dev->pdev, "Missing DMA crci in resource\n"); + goto err_dm_chan_cmd; + }; + pce_dev->crci_in = resource->start; + resource = platform_get_resource_byname(pdev, IORESOURCE_DMA, + "crypto_crci_out"); + if (!resource) { + *rc = -ENXIO; + dev_err(pce_dev->pdev, "Missing DMA crci out resource\n"); + goto err_dm_chan_cmd; + }; + pce_dev->crci_out = resource->start; + + pce_dev->coh_vmem = dma_alloc_coherent(pce_dev->pdev, + 2*PAGE_SIZE, &pce_dev->coh_pmem, GFP_KERNEL); + + if (pce_dev->coh_vmem == NULL) { + *rc = -ENOMEM; + dev_err(pce_dev->pdev, "Can not allocate coherent memory.\n"); + goto err; + } + + /* Get CE core clk */ + ce_core_clk = clk_get(pce_dev->pdev, "ce_clk"); + if (IS_ERR(ce_core_clk)) { + *rc = PTR_ERR(ce_core_clk); + goto err; + } + pce_dev->ce_core_clk = ce_core_clk; + /* Get CE clk */ + ce_clk = clk_get(pce_dev->pdev, "ce_pclk"); + if (IS_ERR(ce_clk)) { + *rc = PTR_ERR(ce_clk); + clk_put(pce_dev->ce_core_clk); + goto err; + } + pce_dev->ce_clk = ce_clk; + + /* Enable CE core clk */ + *rc = clk_enable(pce_dev->ce_core_clk); + if (*rc) { + clk_put(pce_dev->ce_core_clk); + clk_put(pce_dev->ce_clk); + goto err; + } else { + /* Enable CE clk */ + *rc = clk_enable(pce_dev->ce_clk); + if (*rc) { + clk_disable(pce_dev->ce_core_clk); + clk_put(pce_dev->ce_core_clk); + clk_put(pce_dev->ce_clk); + goto err; + + } + } + _setup_cmd_template(pce_dev); + + pce_dev->chan_ce_in_state = QCE_CHAN_STATE_IDLE; + pce_dev->chan_ce_out_state = QCE_CHAN_STATE_IDLE; + + if (_init_ce_engine(pce_dev)) { + *rc = -ENXIO; + goto err; + } + *rc = 0; + return pce_dev; + +err: + if (pce_dev->coh_vmem) + dma_free_coherent(pce_dev->pdev, PAGE_SIZE, pce_dev->coh_vmem, + pce_dev->coh_pmem); +err_dm_chan_cmd: + kfree(pce_dev->chan_ce_in_cmd); + kfree(pce_dev->chan_ce_out_cmd); + if (pce_dev->iobase) + iounmap(pce_dev->iobase); + +err_pce_dev: + + kfree(pce_dev); + + return NULL; +} +EXPORT_SYMBOL(qce_open); + +/* crypto engine close function. */ +int qce_close(void *handle) +{ + struct qce_device *pce_dev = (struct qce_device *) handle; + + if (handle == NULL) + return -ENODEV; + if (pce_dev->iobase) + iounmap(pce_dev->iobase); + + if (pce_dev->coh_vmem) + dma_free_coherent(pce_dev->pdev, 2*PAGE_SIZE, pce_dev->coh_vmem, + pce_dev->coh_pmem); + clk_disable(pce_dev->ce_clk); + clk_disable(pce_dev->ce_core_clk); + + clk_put(pce_dev->ce_clk); + clk_put(pce_dev->ce_core_clk); + + kfree(pce_dev->chan_ce_in_cmd); + kfree(pce_dev->chan_ce_out_cmd); + kfree(handle); + + return 0; +} +EXPORT_SYMBOL(qce_close); + +int qce_hw_support(void *handle, struct ce_hw_support *ce_support) +{ + if (ce_support == NULL) + return -EINVAL; + + ce_support->sha1_hmac_20 = false; + ce_support->sha1_hmac = false; + ce_support->sha256_hmac = false; + ce_support->sha_hmac = false; + ce_support->cmac = true; + ce_support->aes_key_192 = false; + ce_support->aes_xts = true; + ce_support->aes_ccm = true; + ce_support->ota = false; + return 0; +} +EXPORT_SYMBOL(qce_hw_support); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Mona Hossain "); +MODULE_DESCRIPTION("Crypto Engine driver"); +MODULE_VERSION("2.04"); diff --git a/drivers/crypto/msm/qcedev.c b/drivers/crypto/msm/qcedev.c new file mode 100644 index 00000000000..18eff2299d2 --- /dev/null +++ b/drivers/crypto/msm/qcedev.c @@ -0,0 +1,2095 @@ +/* Qualcomm CE device driver. + * + * Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "inc/qcedev.h" +#include "inc/qce.h" + + +#define CACHE_LINE_SIZE 32 +#define CE_SHA_BLOCK_SIZE SHA256_BLOCK_SIZE + +static uint8_t _std_init_vector_sha1_uint8[] = { + 0x67, 0x45, 0x23, 0x01, 0xEF, 0xCD, 0xAB, 0x89, + 0x98, 0xBA, 0xDC, 0xFE, 0x10, 0x32, 0x54, 0x76, + 0xC3, 0xD2, 0xE1, 0xF0 +}; +/* standard initialization vector for SHA-256, source: FIPS 180-2 */ +static uint8_t _std_init_vector_sha256_uint8[] = { + 0x6A, 0x09, 0xE6, 0x67, 0xBB, 0x67, 0xAE, 0x85, + 0x3C, 0x6E, 0xF3, 0x72, 0xA5, 0x4F, 0xF5, 0x3A, + 0x51, 0x0E, 0x52, 0x7F, 0x9B, 0x05, 0x68, 0x8C, + 0x1F, 0x83, 0xD9, 0xAB, 0x5B, 0xE0, 0xCD, 0x19 +}; + +enum qcedev_crypto_oper_type { + QCEDEV_CRYPTO_OPER_CIPHER = 0, + QCEDEV_CRYPTO_OPER_SHA = 1, + QCEDEV_CRYPTO_OPER_LAST +}; + +struct qcedev_control; + +struct qcedev_cipher_req { + struct ablkcipher_request creq; + void *cookie; +}; + +struct qcedev_sha_req { + struct ahash_request sreq; + struct qcedev_sha_ctxt *sha_ctxt; + void *cookie; +}; + +struct qcedev_async_req { + struct list_head list; + struct completion complete; + enum qcedev_crypto_oper_type op_type; + union { + struct qcedev_cipher_op_req cipher_op_req; + struct qcedev_sha_op_req sha_op_req; + }; + union{ + struct qcedev_cipher_req cipher_req; + struct qcedev_sha_req sha_req; + }; + struct qcedev_control *podev; + int err; +}; + +/********************************************************************** + * Register ourselves as a misc device to be able to access the dev driver + * from userspace. */ + + +#define QCEDEV_DEV "qcedev" + +struct qcedev_control{ + + /* CE features supported by platform */ + struct msm_ce_hw_support platform_support; + + bool ce_locked; + + /* CE features/algorithms supported by HW engine*/ + struct ce_hw_support ce_support; + + /* misc device */ + struct miscdevice miscdevice; + + /* qce handle */ + void *qce; + + /* platform device */ + struct platform_device *pdev; + + unsigned magic; + + struct list_head ready_commands; + struct qcedev_async_req *active_command; + spinlock_t lock; + struct tasklet_struct done_tasklet; +}; + +/*------------------------------------------------------------------------- +* Resource Locking Service +* ------------------------------------------------------------------------*/ +#define QCEDEV_CMD_ID 1 +#define QCEDEV_CE_LOCK_CMD 1 +#define QCEDEV_CE_UNLOCK_CMD 0 +#define NUM_RETRY 1000 +#define CE_BUSY 55 + +static int qcedev_scm_cmd(int resource, int cmd, int *response) +{ +#ifdef CONFIG_MSM_SCM + + struct { + int resource; + int cmd; + } cmd_buf; + + cmd_buf.resource = resource; + cmd_buf.cmd = cmd; + + return scm_call(SCM_SVC_TZ, QCEDEV_CMD_ID, &cmd_buf, + sizeof(cmd_buf), response, sizeof(*response)); + +#else + return 0; +#endif +} + +static int qcedev_unlock_ce(struct qcedev_control *podev) +{ + if ((podev->platform_support.ce_shared) && (podev->ce_locked == true)) { + int response = 0; + + if (qcedev_scm_cmd(podev->platform_support.shared_ce_resource, + QCEDEV_CE_UNLOCK_CMD, &response)) { + printk(KERN_ERR "%s Failed to release CE lock\n", + __func__); + return -EUSERS; + } + podev->ce_locked = false; + } + return 0; +} + +static int qcedev_lock_ce(struct qcedev_control *podev) +{ + if ((podev->platform_support.ce_shared) && + (podev->ce_locked == false)) { + int response = -CE_BUSY; + int i = 0; + + do { + if (qcedev_scm_cmd( + podev->platform_support.shared_ce_resource, + QCEDEV_CE_LOCK_CMD, &response)) { + response = -EINVAL; + break; + } + } while ((response == -CE_BUSY) && (i++ < NUM_RETRY)); + + if ((response == -CE_BUSY) && (i >= NUM_RETRY)) + return -EUSERS; + if (response < 0) + return -EINVAL; + + podev->ce_locked = true; + } + + return 0; +} + +#define QCEDEV_MAGIC 0x56434544 /* "qced" */ + +static long qcedev_ioctl(struct file *file, unsigned cmd, unsigned long arg); +static int qcedev_open(struct inode *inode, struct file *file); +static int qcedev_release(struct inode *inode, struct file *file); +static int start_cipher_req(struct qcedev_control *podev); +static int start_sha_req(struct qcedev_control *podev, + struct qcedev_sha_op_req *sha_op_req); + +static const struct file_operations qcedev_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = qcedev_ioctl, + .open = qcedev_open, + .release = qcedev_release, +}; + +static struct qcedev_control qce_dev[] = { + { + .miscdevice = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qce", + .fops = &qcedev_fops, + }, + .magic = QCEDEV_MAGIC, + }, +}; + + +#define MAX_QCE_DEVICE ARRAY_SIZE(qce_dev) +#define DEBUG_MAX_FNAME 16 +#define DEBUG_MAX_RW_BUF 1024 + +struct qcedev_stat { + u32 qcedev_dec_success; + u32 qcedev_dec_fail; + u32 qcedev_enc_success; + u32 qcedev_enc_fail; + u32 qcedev_sha_success; + u32 qcedev_sha_fail; +}; + +static struct qcedev_stat _qcedev_stat[MAX_QCE_DEVICE]; +static struct dentry *_debug_dent; +static char _debug_read_buf[DEBUG_MAX_RW_BUF]; +static int _debug_qcedev[MAX_QCE_DEVICE]; + +static struct qcedev_control *qcedev_minor_to_control(unsigned n) +{ + int i; + + for (i = 0; i < MAX_QCE_DEVICE; i++) { + if (qce_dev[i].miscdevice.minor == n) + return &qce_dev[i]; + } + return NULL; +} + +static int qcedev_open(struct inode *inode, struct file *file) +{ + struct qcedev_control *podev; + + podev = qcedev_minor_to_control(MINOR(inode->i_rdev)); + if (podev == NULL) { + printk(KERN_ERR "%s: no such device %d\n", __func__, + MINOR(inode->i_rdev)); + return -ENOENT; + } + + file->private_data = podev; + + return 0; +} + +static int qcedev_release(struct inode *inode, struct file *file) +{ + struct qcedev_control *podev; + + podev = file->private_data; + + if (podev != NULL && podev->magic != QCEDEV_MAGIC) { + printk(KERN_ERR "%s: invalid handle %p\n", + __func__, podev); + } + + file->private_data = NULL; + + return 0; +} + +static void req_done(unsigned long data) +{ + struct qcedev_control *podev = (struct qcedev_control *)data; + struct qcedev_async_req *areq; + unsigned long flags = 0; + struct qcedev_async_req *new_req = NULL; + int ret = 0; + + spin_lock_irqsave(&podev->lock, flags); + areq = podev->active_command; + podev->active_command = NULL; + +again: + if (!list_empty(&podev->ready_commands)) { + new_req = container_of(podev->ready_commands.next, + struct qcedev_async_req, list); + list_del(&new_req->list); + podev->active_command = new_req; + new_req->err = 0; + if (new_req->op_type == QCEDEV_CRYPTO_OPER_CIPHER) + ret = start_cipher_req(podev); + else + ret = start_sha_req(podev, &areq->sha_op_req); + } + + spin_unlock_irqrestore(&podev->lock, flags); + + if (areq) + complete(&areq->complete); + + if (new_req && ret) { + complete(&new_req->complete); + spin_lock_irqsave(&podev->lock, flags); + podev->active_command = NULL; + areq = NULL; + ret = 0; + new_req = NULL; + goto again; + } + + return; +} + +static void qcedev_sha_req_cb(void *cookie, unsigned char *digest, + unsigned char *authdata, int ret) +{ + struct qcedev_sha_req *areq; + struct qcedev_control *pdev; + uint32_t *auth32 = (uint32_t *)authdata; + + areq = (struct qcedev_sha_req *) cookie; + pdev = (struct qcedev_control *) areq->cookie; + + if (digest) + memcpy(&areq->sha_ctxt->digest[0], digest, 32); + + if (authdata) { + areq->sha_ctxt->auth_data[0] = auth32[0]; + areq->sha_ctxt->auth_data[1] = auth32[1]; + areq->sha_ctxt->auth_data[2] = auth32[2]; + areq->sha_ctxt->auth_data[3] = auth32[3]; + } + + tasklet_schedule(&pdev->done_tasklet); +}; + + +static void qcedev_cipher_req_cb(void *cookie, unsigned char *icv, + unsigned char *iv, int ret) +{ + struct qcedev_cipher_req *areq; + struct qcedev_control *pdev; + struct qcedev_async_req *qcedev_areq; + + areq = (struct qcedev_cipher_req *) cookie; + pdev = (struct qcedev_control *) areq->cookie; + qcedev_areq = pdev->active_command; + + if (iv) + memcpy(&qcedev_areq->cipher_op_req.iv[0], iv, + qcedev_areq->cipher_op_req.ivlen); + tasklet_schedule(&pdev->done_tasklet); +}; + +static int start_cipher_req(struct qcedev_control *podev) +{ + struct qcedev_async_req *qcedev_areq; + struct qce_req creq; + int ret = 0; + + /* start the command on the podev->active_command */ + qcedev_areq = podev->active_command; + qcedev_areq->podev = podev; + + qcedev_areq->cipher_req.cookie = qcedev_areq->podev; + creq.use_pmem = qcedev_areq->cipher_op_req.use_pmem; + if (qcedev_areq->cipher_op_req.use_pmem == QCEDEV_USE_PMEM) + creq.pmem = &qcedev_areq->cipher_op_req.pmem; + else + creq.pmem = NULL; + + switch (qcedev_areq->cipher_op_req.alg) { + case QCEDEV_ALG_DES: + creq.alg = CIPHER_ALG_DES; + break; + case QCEDEV_ALG_3DES: + creq.alg = CIPHER_ALG_3DES; + break; + case QCEDEV_ALG_AES: + creq.alg = CIPHER_ALG_AES; + break; + default: + break; + }; + + switch (qcedev_areq->cipher_op_req.mode) { + case QCEDEV_AES_MODE_CBC: + case QCEDEV_DES_MODE_CBC: + creq.mode = QCE_MODE_CBC; + break; + case QCEDEV_AES_MODE_ECB: + case QCEDEV_DES_MODE_ECB: + creq.mode = QCE_MODE_ECB; + break; + case QCEDEV_AES_MODE_CTR: + creq.mode = QCE_MODE_CTR; + break; + case QCEDEV_AES_MODE_XTS: + creq.mode = QCE_MODE_XTS; + break; + default: + break; + }; + + if ((creq.alg == CIPHER_ALG_AES) && + (creq.mode == QCE_MODE_CTR)) { + creq.dir = QCE_ENCRYPT; + } else { + if (QCEDEV_OPER_ENC == qcedev_areq->cipher_op_req.op) + creq.dir = QCE_ENCRYPT; + else + creq.dir = QCE_DECRYPT; + } + + creq.iv = &qcedev_areq->cipher_op_req.iv[0]; + creq.ivsize = qcedev_areq->cipher_op_req.ivlen; + + creq.enckey = &qcedev_areq->cipher_op_req.enckey[0]; + creq.encklen = qcedev_areq->cipher_op_req.encklen; + + creq.cryptlen = qcedev_areq->cipher_op_req.data_len; + + if (qcedev_areq->cipher_op_req.encklen == 0) { + if ((qcedev_areq->cipher_op_req.op == QCEDEV_OPER_ENC_NO_KEY) + || (qcedev_areq->cipher_op_req.op == + QCEDEV_OPER_DEC_NO_KEY)) + creq.op = QCE_REQ_ABLK_CIPHER_NO_KEY; + else { + int i; + + for (i = 0; i < QCEDEV_MAX_KEY_SIZE; i++) { + if (qcedev_areq->cipher_op_req.enckey[i] != 0) + break; + } + + if ((podev->platform_support.hw_key_support == 1) && + (i == QCEDEV_MAX_KEY_SIZE)) + creq.op = QCE_REQ_ABLK_CIPHER; + else { + ret = -EINVAL; + goto unsupported; + } + } + } else { + creq.op = QCE_REQ_ABLK_CIPHER; + } + + creq.qce_cb = qcedev_cipher_req_cb; + creq.areq = (void *)&qcedev_areq->cipher_req; + + ret = qce_ablk_cipher_req(podev->qce, &creq); +unsupported: + if (ret) + qcedev_areq->err = -ENXIO; + else + qcedev_areq->err = 0; + return ret; +}; + +static int start_sha_req(struct qcedev_control *podev, + struct qcedev_sha_op_req *sha_op_req) +{ + struct qcedev_async_req *qcedev_areq; + struct qce_sha_req sreq; + int ret = 0; + + /* start the command on the podev->active_command */ + qcedev_areq = podev->active_command; + qcedev_areq->podev = podev; + + switch (qcedev_areq->sha_op_req.alg) { + case QCEDEV_ALG_SHA1: + sreq.alg = QCE_HASH_SHA1; + break; + case QCEDEV_ALG_SHA256: + sreq.alg = QCE_HASH_SHA256; + break; + case QCEDEV_ALG_SHA1_HMAC: + if (podev->ce_support.sha_hmac) { + sreq.alg = QCE_HASH_SHA1_HMAC; + sreq.authkey = + &qcedev_areq->sha_op_req.ctxt.authkey[0]; + + } else { + sreq.alg = QCE_HASH_SHA1; + sreq.authkey = NULL; + } + break; + case QCEDEV_ALG_SHA256_HMAC: + if (podev->ce_support.sha_hmac) { + sreq.alg = QCE_HASH_SHA256_HMAC; + sreq.authkey = + &qcedev_areq->sha_op_req.ctxt.authkey[0]; + + } else { + sreq.alg = QCE_HASH_SHA256; + sreq.authkey = NULL; + } + break; + case QCEDEV_ALG_AES_CMAC: + sreq.alg = QCE_HASH_AES_CMAC; + sreq.authkey = &qcedev_areq->sha_op_req.ctxt.authkey[0]; + sreq.authklen = qcedev_areq->sha_op_req.authklen; + break; + default: + break; + }; + + qcedev_areq->sha_req.cookie = podev; + + sreq.qce_cb = qcedev_sha_req_cb; + if (qcedev_areq->sha_op_req.alg != QCEDEV_ALG_AES_CMAC) { + sreq.auth_data[0] = sha_op_req->ctxt.auth_data[0]; + sreq.auth_data[1] = sha_op_req->ctxt.auth_data[1]; + sreq.auth_data[2] = sha_op_req->ctxt.auth_data[2]; + sreq.auth_data[3] = sha_op_req->ctxt.auth_data[3]; + sreq.digest = &sha_op_req->ctxt.digest[0]; + sreq.first_blk = sha_op_req->ctxt.first_blk; + sreq.last_blk = sha_op_req->ctxt.last_blk; + } + sreq.size = qcedev_areq->sha_req.sreq.nbytes; + sreq.src = qcedev_areq->sha_req.sreq.src; + sreq.areq = (void *)&qcedev_areq->sha_req; + qcedev_areq->sha_req.sha_ctxt = + (struct qcedev_sha_ctxt *)(&sha_op_req->ctxt); + + ret = qce_process_sha_req(podev->qce, &sreq); + + if (ret) + qcedev_areq->err = -ENXIO; + else + qcedev_areq->err = 0; + return ret; +}; + +static int submit_req(struct qcedev_async_req *qcedev_areq, + struct qcedev_control *podev) +{ + unsigned long flags = 0; + int ret = 0; + struct qcedev_stat *pstat; + + qcedev_areq->err = 0; + + ret = qcedev_lock_ce(podev); + if (ret) + return ret; + + spin_lock_irqsave(&podev->lock, flags); + + if (podev->active_command == NULL) { + podev->active_command = qcedev_areq; + if (qcedev_areq->op_type == QCEDEV_CRYPTO_OPER_CIPHER) + ret = start_cipher_req(podev); + else + ret = start_sha_req(podev, &qcedev_areq->sha_op_req); + } else { + list_add_tail(&qcedev_areq->list, &podev->ready_commands); + } + + if (ret != 0) + podev->active_command = NULL; + + spin_unlock_irqrestore(&podev->lock, flags); + + if (ret == 0) + wait_for_completion(&qcedev_areq->complete); + + ret = qcedev_unlock_ce(podev); + if (ret) + qcedev_areq->err = -EIO; + + pstat = &_qcedev_stat[podev->pdev->id]; + if (qcedev_areq->op_type == QCEDEV_CRYPTO_OPER_CIPHER) { + switch (qcedev_areq->cipher_op_req.op) { + case QCEDEV_OPER_DEC: + if (qcedev_areq->err) + pstat->qcedev_dec_fail++; + else + pstat->qcedev_dec_success++; + break; + case QCEDEV_OPER_ENC: + if (qcedev_areq->err) + pstat->qcedev_enc_fail++; + else + pstat->qcedev_enc_success++; + break; + default: + break; + }; + } else { + if (qcedev_areq->err) + pstat->qcedev_sha_fail++; + else + pstat->qcedev_sha_success++; + } + + return qcedev_areq->err; +} + +static int qcedev_sha_init(struct qcedev_async_req *areq, + struct qcedev_control *podev) +{ + struct qcedev_sha_ctxt *sha_ctxt = &areq->sha_op_req.ctxt; + + memset(sha_ctxt, 0, sizeof(struct qcedev_sha_ctxt)); + sha_ctxt->first_blk = 1; + + if ((areq->sha_op_req.alg == QCEDEV_ALG_SHA1) || + (areq->sha_op_req.alg == QCEDEV_ALG_SHA1_HMAC)) { + memcpy(&sha_ctxt->digest[0], + &_std_init_vector_sha1_uint8[0], SHA1_DIGEST_SIZE); + sha_ctxt->diglen = SHA1_DIGEST_SIZE; + } else { + if ((areq->sha_op_req.alg == QCEDEV_ALG_SHA256) || + (areq->sha_op_req.alg == QCEDEV_ALG_SHA256_HMAC)) { + memcpy(&sha_ctxt->digest[0], + &_std_init_vector_sha256_uint8[0], + SHA256_DIGEST_SIZE); + sha_ctxt->diglen = SHA256_DIGEST_SIZE; + } + } + return 0; +} + + +static int qcedev_sha_update_max_xfer(struct qcedev_async_req *qcedev_areq, + struct qcedev_control *podev) +{ + int err = 0; + int i = 0; + struct scatterlist sg_src[2]; + uint32_t total; + + uint8_t *user_src = NULL; + uint8_t *k_src = NULL; + uint8_t *k_buf_src = NULL; + uint8_t *k_align_src = NULL; + + uint32_t sha_pad_len = 0; + uint32_t trailing_buf_len = 0; + uint32_t t_buf = qcedev_areq->sha_op_req.ctxt.trailing_buf_len; + uint32_t sha_block_size; + + total = qcedev_areq->sha_op_req.data_len + t_buf; + + if (qcedev_areq->sha_op_req.alg == QCEDEV_ALG_SHA1) + sha_block_size = SHA1_BLOCK_SIZE; + else + sha_block_size = SHA256_BLOCK_SIZE; + + if (total <= sha_block_size) { + uint32_t len = qcedev_areq->sha_op_req.data_len; + + i = 0; + + k_src = &qcedev_areq->sha_op_req.ctxt.trailing_buf[t_buf]; + + /* Copy data from user src(s) */ + while (len > 0) { + user_src = + (void __user *)qcedev_areq->sha_op_req.data[i].vaddr; + if (user_src && __copy_from_user(k_src, + (void __user *)user_src, + qcedev_areq->sha_op_req.data[i].len)) + return -EFAULT; + + len -= qcedev_areq->sha_op_req.data[i].len; + k_src += qcedev_areq->sha_op_req.data[i].len; + i++; + } + qcedev_areq->sha_op_req.ctxt.trailing_buf_len = total; + + return 0; + } + + + k_buf_src = kmalloc(total + CACHE_LINE_SIZE * 2, + GFP_KERNEL); + if (k_buf_src == NULL) + return -ENOMEM; + + k_align_src = (uint8_t *) ALIGN(((unsigned int)k_buf_src), + CACHE_LINE_SIZE); + k_src = k_align_src; + + /* check for trailing buffer from previous updates and append it */ + if (t_buf > 0) { + memcpy(k_src, &qcedev_areq->sha_op_req.ctxt.trailing_buf[0], + t_buf); + k_src += t_buf; + } + + /* Copy data from user src(s) */ + user_src = (void __user *)qcedev_areq->sha_op_req.data[0].vaddr; + if (user_src && __copy_from_user(k_src, + (void __user *)user_src, + qcedev_areq->sha_op_req.data[0].len)) { + kfree(k_buf_src); + return -EFAULT; + } + k_src += qcedev_areq->sha_op_req.data[0].len; + for (i = 1; i < qcedev_areq->sha_op_req.entries; i++) { + user_src = (void __user *)qcedev_areq->sha_op_req.data[i].vaddr; + if (user_src && __copy_from_user(k_src, + (void __user *)user_src, + qcedev_areq->sha_op_req.data[i].len)) { + kfree(k_buf_src); + return -EFAULT; + } + k_src += qcedev_areq->sha_op_req.data[i].len; + } + + /* get new trailing buffer */ + sha_pad_len = ALIGN(total, CE_SHA_BLOCK_SIZE) - total; + trailing_buf_len = CE_SHA_BLOCK_SIZE - sha_pad_len; + + qcedev_areq->sha_req.sreq.src = (struct scatterlist *) &sg_src[0]; + sg_set_buf(qcedev_areq->sha_req.sreq.src, k_align_src, + total-trailing_buf_len); + sg_mark_end(qcedev_areq->sha_req.sreq.src); + + qcedev_areq->sha_req.sreq.nbytes = total - trailing_buf_len; + + /* update sha_ctxt trailing buf content to new trailing buf */ + if (trailing_buf_len > 0) { + memset(&qcedev_areq->sha_op_req.ctxt.trailing_buf[0], 0, 64); + memcpy(&qcedev_areq->sha_op_req.ctxt.trailing_buf[0], + (k_src - trailing_buf_len), + trailing_buf_len); + } + qcedev_areq->sha_op_req.ctxt.trailing_buf_len = trailing_buf_len; + + err = submit_req(qcedev_areq, podev); + + qcedev_areq->sha_op_req.ctxt.last_blk = 0; + qcedev_areq->sha_op_req.ctxt.first_blk = 0; + + kfree(k_buf_src); + return err; +} + +static int qcedev_sha_update(struct qcedev_async_req *qcedev_areq, + struct qcedev_control *podev) +{ + int err = 0; + int i = 0; + int j = 0; + int k = 0; + int num_entries = 0; + uint32_t total = 0; + + /* verify address src(s) */ + for (i = 0; i < qcedev_areq->sha_op_req.entries; i++) + if (!access_ok(VERIFY_READ, + (void __user *)qcedev_areq->sha_op_req.data[i].vaddr, + qcedev_areq->sha_op_req.data[i].len)) + return -EFAULT; + + if (qcedev_areq->sha_op_req.data_len > QCE_MAX_OPER_DATA) { + + struct qcedev_sha_op_req *saved_req; + struct qcedev_sha_op_req req; + struct qcedev_sha_op_req *sreq = &qcedev_areq->sha_op_req; + + /* save the original req structure */ + saved_req = + kmalloc(sizeof(struct qcedev_sha_op_req), GFP_KERNEL); + if (saved_req == NULL) { + printk(KERN_ERR "%s:Can't Allocate mem:saved_req %x\n", + __func__, (uint32_t)saved_req); + return -ENOMEM; + } + memcpy(&req, sreq, sizeof(struct qcedev_sha_op_req)); + memcpy(saved_req, sreq, sizeof(struct qcedev_sha_op_req)); + + i = 0; + /* Address 32 KB at a time */ + while ((i < req.entries) && (err == 0)) { + if (sreq->data[i].len > QCE_MAX_OPER_DATA) { + sreq->data[0].len = QCE_MAX_OPER_DATA; + if (i > 0) { + sreq->data[0].vaddr = + sreq->data[i].vaddr; + } + + sreq->data_len = QCE_MAX_OPER_DATA; + sreq->entries = 1; + + err = qcedev_sha_update_max_xfer(qcedev_areq, + podev); + + sreq->data[i].len = req.data[i].len - + QCE_MAX_OPER_DATA; + sreq->data[i].vaddr = req.data[i].vaddr + + QCE_MAX_OPER_DATA; + req.data[i].vaddr = sreq->data[i].vaddr; + req.data[i].len = sreq->data[i].len; + } else { + total = 0; + for (j = i; j < req.entries; j++) { + num_entries++; + if ((total + sreq->data[j].len) >= + QCE_MAX_OPER_DATA) { + sreq->data[j].len = + (QCE_MAX_OPER_DATA - total); + total = QCE_MAX_OPER_DATA; + break; + } + total += sreq->data[j].len; + } + + sreq->data_len = total; + if (i > 0) + for (k = 0; k < num_entries; k++) { + sreq->data[k].len = + sreq->data[i+k].len; + sreq->data[k].vaddr = + sreq->data[i+k].vaddr; + } + sreq->entries = num_entries; + + i = j; + err = qcedev_sha_update_max_xfer(qcedev_areq, + podev); + num_entries = 0; + + sreq->data[i].vaddr = req.data[i].vaddr + + sreq->data[i].len; + sreq->data[i].len = req.data[i].len - + sreq->data[i].len; + req.data[i].vaddr = sreq->data[i].vaddr; + req.data[i].len = sreq->data[i].len; + + if (sreq->data[i].len == 0) + i++; + } + } /* end of while ((i < req.entries) && (err == 0)) */ + + /* Restore the original req structure */ + for (i = 0; i < saved_req->entries; i++) { + sreq->data[i].len = saved_req->data[i].len; + sreq->data[i].vaddr = saved_req->data[i].vaddr; + } + sreq->entries = saved_req->entries; + sreq->data_len = saved_req->data_len; + kfree(saved_req); + } else + err = qcedev_sha_update_max_xfer(qcedev_areq, podev); + + return err; +} + +static int qcedev_sha_final(struct qcedev_async_req *qcedev_areq, + struct qcedev_control *podev) +{ + int err = 0; + struct scatterlist sg_src; + uint32_t total; + + uint8_t *k_buf_src = NULL; + uint8_t *k_align_src = NULL; + + qcedev_areq->sha_op_req.ctxt.first_blk = 0; + qcedev_areq->sha_op_req.ctxt.last_blk = 1; + + total = qcedev_areq->sha_op_req.ctxt.trailing_buf_len; + + if (total) { + k_buf_src = kmalloc(total + CACHE_LINE_SIZE * 2, + GFP_KERNEL); + if (k_buf_src == NULL) + return -ENOMEM; + + k_align_src = (uint8_t *) ALIGN(((unsigned int)k_buf_src), + CACHE_LINE_SIZE); + memcpy(k_align_src, + &qcedev_areq->sha_op_req.ctxt.trailing_buf[0], + total); + } + qcedev_areq->sha_op_req.ctxt.last_blk = 1; + qcedev_areq->sha_op_req.ctxt.first_blk = 0; + + qcedev_areq->sha_req.sreq.src = (struct scatterlist *) &sg_src; + sg_set_buf(qcedev_areq->sha_req.sreq.src, k_align_src, total); + sg_mark_end(qcedev_areq->sha_req.sreq.src); + + qcedev_areq->sha_req.sreq.nbytes = total; + + err = submit_req(qcedev_areq, podev); + + qcedev_areq->sha_op_req.ctxt.first_blk = 0; + qcedev_areq->sha_op_req.ctxt.last_blk = 0; + qcedev_areq->sha_op_req.ctxt.auth_data[0] = 0; + qcedev_areq->sha_op_req.ctxt.auth_data[1] = 0; + qcedev_areq->sha_op_req.ctxt.trailing_buf_len = 0; + memset(&qcedev_areq->sha_op_req.ctxt.trailing_buf[0], 0, 64); + + kfree(k_buf_src); + return err; +} + +static int qcedev_hash_cmac(struct qcedev_async_req *qcedev_areq, + struct qcedev_control *podev) +{ + int err = 0; + int i = 0; + struct scatterlist sg_src[2]; + uint32_t total; + + uint8_t *user_src = NULL; + uint8_t *k_src = NULL; + uint8_t *k_buf_src = NULL; + + total = qcedev_areq->sha_op_req.data_len; + + /* verify address src(s) */ + for (i = 0; i < qcedev_areq->sha_op_req.entries; i++) + if (!access_ok(VERIFY_READ, + (void __user *)qcedev_areq->sha_op_req.data[i].vaddr, + qcedev_areq->sha_op_req.data[i].len)) + return -EFAULT; + + /* Verify Source Address */ + if (!access_ok(VERIFY_READ, + (void __user *)qcedev_areq->sha_op_req.authkey, + qcedev_areq->sha_op_req.authklen)) + return -EFAULT; + if (__copy_from_user(&qcedev_areq->sha_op_req.ctxt.authkey[0], + (void __user *)qcedev_areq->sha_op_req.authkey, + qcedev_areq->sha_op_req.authklen)) + return -EFAULT; + + + k_buf_src = kmalloc(total, GFP_KERNEL); + if (k_buf_src == NULL) + return -ENOMEM; + + k_src = k_buf_src; + + /* Copy data from user src(s) */ + user_src = (void __user *)qcedev_areq->sha_op_req.data[0].vaddr; + for (i = 0; i < qcedev_areq->sha_op_req.entries; i++) { + user_src = + (void __user *)qcedev_areq->sha_op_req.data[i].vaddr; + if (user_src && __copy_from_user(k_src, (void __user *)user_src, + qcedev_areq->sha_op_req.data[i].len)) { + kfree(k_buf_src); + return -EFAULT; + } + k_src += qcedev_areq->sha_op_req.data[i].len; + } + + qcedev_areq->sha_req.sreq.src = (struct scatterlist *) &sg_src[0]; + sg_set_buf(qcedev_areq->sha_req.sreq.src, k_buf_src, total); + sg_mark_end(qcedev_areq->sha_req.sreq.src); + + qcedev_areq->sha_req.sreq.nbytes = total; + qcedev_areq->sha_op_req.ctxt.diglen = qcedev_areq->sha_op_req.diglen; + err = submit_req(qcedev_areq, podev); + + kfree(k_buf_src); + return err; +} + +static int qcedev_set_hmac_auth_key(struct qcedev_async_req *areq, + struct qcedev_control *podev) +{ + int err = 0; + + if (areq->sha_op_req.authklen <= QCEDEV_MAX_KEY_SIZE) { + /* Verify Source Address */ + if (!access_ok(VERIFY_READ, + (void __user *)areq->sha_op_req.authkey, + areq->sha_op_req.authklen)) + return -EFAULT; + if (__copy_from_user(&areq->sha_op_req.ctxt.authkey[0], + (void __user *)areq->sha_op_req.authkey, + areq->sha_op_req.authklen)) + return -EFAULT; + } else { + struct qcedev_async_req authkey_areq; + + init_completion(&authkey_areq.complete); + + authkey_areq.sha_op_req.entries = 1; + authkey_areq.sha_op_req.data[0].vaddr = + areq->sha_op_req.authkey; + authkey_areq.sha_op_req.data[0].len = areq->sha_op_req.authklen; + authkey_areq.sha_op_req.data_len = areq->sha_op_req.authklen; + authkey_areq.sha_op_req.diglen = 0; + memset(&authkey_areq.sha_op_req.digest[0], 0, + QCEDEV_MAX_SHA_DIGEST); + if (areq->sha_op_req.alg == QCEDEV_ALG_SHA1_HMAC) + authkey_areq.sha_op_req.alg = QCEDEV_ALG_SHA1; + if (areq->sha_op_req.alg == QCEDEV_ALG_SHA256_HMAC) + authkey_areq.sha_op_req.alg = QCEDEV_ALG_SHA256; + + authkey_areq.op_type = QCEDEV_CRYPTO_OPER_SHA; + + qcedev_sha_init(&authkey_areq, podev); + err = qcedev_sha_update(&authkey_areq, podev); + if (!err) + err = qcedev_sha_final(&authkey_areq, podev); + else + return err; + memcpy(&areq->sha_op_req.ctxt.authkey[0], + &authkey_areq.sha_op_req.ctxt.digest[0], + authkey_areq.sha_op_req.ctxt.diglen); + } + return err; +} + +static int qcedev_hmac_get_ohash(struct qcedev_async_req *qcedev_areq, + struct qcedev_control *podev) +{ + int err = 0; + struct scatterlist sg_src; + uint8_t *k_src = NULL; + uint32_t sha_block_size = 0; + uint32_t sha_digest_size = 0; + + if (qcedev_areq->sha_op_req.alg == QCEDEV_ALG_SHA1_HMAC) { + sha_digest_size = SHA1_DIGEST_SIZE; + sha_block_size = SHA1_BLOCK_SIZE; + } else { + if (qcedev_areq->sha_op_req.alg == QCEDEV_ALG_SHA256_HMAC) { + sha_digest_size = SHA256_DIGEST_SIZE; + sha_block_size = SHA256_BLOCK_SIZE; + } + } + k_src = kmalloc(sha_block_size, GFP_KERNEL); + if (k_src == NULL) + return -ENOMEM; + + /* check for trailing buffer from previous updates and append it */ + memcpy(k_src, &qcedev_areq->sha_op_req.ctxt.trailing_buf[0], + qcedev_areq->sha_op_req.ctxt.trailing_buf_len); + + qcedev_areq->sha_req.sreq.src = (struct scatterlist *) &sg_src; + sg_set_buf(qcedev_areq->sha_req.sreq.src, k_src, sha_block_size); + sg_mark_end(qcedev_areq->sha_req.sreq.src); + + qcedev_areq->sha_req.sreq.nbytes = sha_block_size; + memset(&qcedev_areq->sha_op_req.ctxt.trailing_buf[0], 0, + sha_block_size); + memcpy(&qcedev_areq->sha_op_req.ctxt.trailing_buf[0], + &qcedev_areq->sha_op_req.ctxt.digest[0], + sha_digest_size); + qcedev_areq->sha_op_req.ctxt.trailing_buf_len = sha_digest_size; + + qcedev_areq->sha_op_req.ctxt.first_blk = 1; + qcedev_areq->sha_op_req.ctxt.last_blk = 0; + qcedev_areq->sha_op_req.ctxt.auth_data[0] = 0; + qcedev_areq->sha_op_req.ctxt.auth_data[1] = 0; + + if (qcedev_areq->sha_op_req.alg == QCEDEV_ALG_SHA1_HMAC) { + memcpy(&qcedev_areq->sha_op_req.ctxt.digest[0], + &_std_init_vector_sha1_uint8[0], SHA1_DIGEST_SIZE); + qcedev_areq->sha_op_req.ctxt.diglen = SHA1_DIGEST_SIZE; + } + + if (qcedev_areq->sha_op_req.alg == QCEDEV_ALG_SHA256_HMAC) { + memcpy(&qcedev_areq->sha_op_req.ctxt.digest[0], + &_std_init_vector_sha256_uint8[0], SHA256_DIGEST_SIZE); + qcedev_areq->sha_op_req.ctxt.diglen = SHA256_DIGEST_SIZE; + } + err = submit_req(qcedev_areq, podev); + + qcedev_areq->sha_op_req.ctxt.last_blk = 0; + qcedev_areq->sha_op_req.ctxt.first_blk = 0; + + kfree(k_src); + return err; +} + +static int qcedev_hmac_update_iokey(struct qcedev_async_req *areq, + struct qcedev_control *podev, bool ikey) +{ + int i; + uint32_t constant; + uint32_t sha_block_size; + + if (ikey) + constant = 0x36; + else + constant = 0x5c; + + if (areq->sha_op_req.alg == QCEDEV_ALG_SHA1_HMAC) + sha_block_size = SHA1_BLOCK_SIZE; + else + sha_block_size = SHA256_BLOCK_SIZE; + + memset(&areq->sha_op_req.ctxt.trailing_buf[0], 0, sha_block_size); + for (i = 0; i < sha_block_size; i++) + areq->sha_op_req.ctxt.trailing_buf[i] = + (areq->sha_op_req.ctxt.authkey[i] ^ constant); + + areq->sha_op_req.ctxt.trailing_buf_len = sha_block_size; + return 0; +} + +static int qcedev_hmac_init(struct qcedev_async_req *areq, + struct qcedev_control *podev) +{ + int err; + + qcedev_sha_init(areq, podev); + err = qcedev_set_hmac_auth_key(areq, podev); + if (err) + return err; + if (!podev->ce_support.sha_hmac) + qcedev_hmac_update_iokey(areq, podev, true); + return 0; +} + +static int qcedev_hmac_final(struct qcedev_async_req *areq, + struct qcedev_control *podev) +{ + int err; + + err = qcedev_sha_final(areq, podev); + if (podev->ce_support.sha_hmac) + return err; + + qcedev_hmac_update_iokey(areq, podev, false); + err = qcedev_hmac_get_ohash(areq, podev); + if (err) + return err; + err = qcedev_sha_final(areq, podev); + + return err; +} + +static int qcedev_hash_init(struct qcedev_async_req *areq, + struct qcedev_control *podev) +{ + if ((areq->sha_op_req.alg == QCEDEV_ALG_SHA1) || + (areq->sha_op_req.alg == QCEDEV_ALG_SHA256)) + return qcedev_sha_init(areq, podev); + else + return qcedev_hmac_init(areq, podev); +} + +static int qcedev_hash_update(struct qcedev_async_req *qcedev_areq, + struct qcedev_control *podev) +{ + return qcedev_sha_update(qcedev_areq, podev); +} + +static int qcedev_hash_final(struct qcedev_async_req *areq, + struct qcedev_control *podev) +{ + if ((areq->sha_op_req.alg == QCEDEV_ALG_SHA1) || + (areq->sha_op_req.alg == QCEDEV_ALG_SHA256)) + return qcedev_sha_final(areq, podev); + else + return qcedev_hmac_final(areq, podev); +} + +static int qcedev_pmem_ablk_cipher_max_xfer(struct qcedev_async_req *areq, + struct qcedev_control *podev) +{ + int i = 0; + int err = 0; + struct scatterlist *sg_src = NULL; + struct scatterlist *sg_dst = NULL; + struct scatterlist *sg_ndex = NULL; + struct file *file_src = NULL; + struct file *file_dst = NULL; + unsigned long paddr; + unsigned long kvaddr; + unsigned long len; + + sg_src = kmalloc((sizeof(struct scatterlist) * + areq->cipher_op_req.entries), GFP_KERNEL); + if (sg_src == NULL) { + printk(KERN_ERR "%s: Can't Allocate memory:s g_src 0x%x\n", + __func__, (uint32_t)sg_src); + return -ENOMEM; + + } + memset(sg_src, 0, (sizeof(struct scatterlist) * + areq->cipher_op_req.entries)); + sg_ndex = sg_src; + areq->cipher_req.creq.src = sg_src; + + /* address src */ + get_pmem_file(areq->cipher_op_req.pmem.fd_src, &paddr, + &kvaddr, &len, &file_src); + + for (i = 0; i < areq->cipher_op_req.entries; i++) { + sg_set_buf(sg_ndex, + ((uint8_t *)(areq->cipher_op_req.pmem.src[i].offset) + kvaddr), + areq->cipher_op_req.pmem.src[i].len); + sg_ndex++; + } + sg_mark_end(--sg_ndex); + + for (i = 0; i < areq->cipher_op_req.entries; i++) + areq->cipher_op_req.pmem.src[i].offset += (uint32_t)paddr; + + /* address dst */ + /* If not place encryption/decryption */ + if (areq->cipher_op_req.in_place_op != 1) { + sg_dst = kmalloc((sizeof(struct scatterlist) * + areq->cipher_op_req.entries), GFP_KERNEL); + if (sg_dst == NULL) + return -ENOMEM; + memset(sg_dst, 0, (sizeof(struct scatterlist) * + areq->cipher_op_req.entries)); + areq->cipher_req.creq.dst = sg_dst; + sg_ndex = sg_dst; + + get_pmem_file(areq->cipher_op_req.pmem.fd_dst, &paddr, + &kvaddr, &len, &file_dst); + for (i = 0; i < areq->cipher_op_req.entries; i++) + sg_set_buf(sg_ndex++, + ((uint8_t *)(areq->cipher_op_req.pmem.dst[i].offset) + + kvaddr), areq->cipher_op_req.pmem.dst[i].len); + sg_mark_end(--sg_ndex); + + for (i = 0; i < areq->cipher_op_req.entries; i++) + areq->cipher_op_req.pmem.dst[i].offset += + (uint32_t)paddr; + } else { + areq->cipher_req.creq.dst = sg_src; + for (i = 0; i < areq->cipher_op_req.entries; i++) { + areq->cipher_op_req.pmem.dst[i].offset = + areq->cipher_op_req.pmem.src[i].offset; + areq->cipher_op_req.pmem.dst[i].len = + areq->cipher_op_req.pmem.src[i].len; + } + } + + areq->cipher_req.creq.nbytes = areq->cipher_op_req.data_len; + areq->cipher_req.creq.info = areq->cipher_op_req.iv; + + err = submit_req(areq, podev); + + kfree(sg_src); + kfree(sg_dst); + + if (file_dst) + put_pmem_file(file_dst); + if (file_src) + put_pmem_file(file_src); + + return err; +}; + + +static int qcedev_pmem_ablk_cipher(struct qcedev_async_req *qcedev_areq, + struct qcedev_control *podev) +{ + int err = 0; + int i = 0; + int j = 0; + int k = 0; + int num_entries = 0; + uint32_t total = 0; + struct qcedev_cipher_op_req *saved_req; + struct qcedev_cipher_op_req *creq = &qcedev_areq->cipher_op_req; + + saved_req = kmalloc(sizeof(struct qcedev_cipher_op_req), GFP_KERNEL); + if (saved_req == NULL) { + printk(KERN_ERR "%s:Can't Allocate mem:saved_req %x\n", + __func__, (uint32_t)saved_req); + return -ENOMEM; + } + memcpy(saved_req, creq, sizeof(struct qcedev_cipher_op_req)); + + if (qcedev_areq->cipher_op_req.data_len > QCE_MAX_OPER_DATA) { + + struct qcedev_cipher_op_req req; + + /* save the original req structure */ + memcpy(&req, creq, sizeof(struct qcedev_cipher_op_req)); + + i = 0; + /* Address 32 KB at a time */ + while ((i < req.entries) && (err == 0)) { + if (creq->pmem.src[i].len > QCE_MAX_OPER_DATA) { + creq->pmem.src[0].len = QCE_MAX_OPER_DATA; + if (i > 0) { + creq->pmem.src[0].offset = + creq->pmem.src[i].offset; + } + + creq->data_len = QCE_MAX_OPER_DATA; + creq->entries = 1; + + err = + qcedev_pmem_ablk_cipher_max_xfer(qcedev_areq, + podev); + + creq->pmem.src[i].len = req.pmem.src[i].len - + QCE_MAX_OPER_DATA; + creq->pmem.src[i].offset = + req.pmem.src[i].offset + + QCE_MAX_OPER_DATA; + req.pmem.src[i].offset = + creq->pmem.src[i].offset; + req.pmem.src[i].len = creq->pmem.src[i].len; + } else { + total = 0; + for (j = i; j < req.entries; j++) { + num_entries++; + if ((total + creq->pmem.src[j].len) + >= QCE_MAX_OPER_DATA) { + creq->pmem.src[j].len = + QCE_MAX_OPER_DATA - total; + total = QCE_MAX_OPER_DATA; + break; + } + total += creq->pmem.src[j].len; + } + + creq->data_len = total; + if (i > 0) + for (k = 0; k < num_entries; k++) { + creq->pmem.src[k].len = + creq->pmem.src[i+k].len; + creq->pmem.src[k].offset = + creq->pmem.src[i+k].offset; + } + creq->entries = num_entries; + + i = j; + err = + qcedev_pmem_ablk_cipher_max_xfer(qcedev_areq, + podev); + num_entries = 0; + + creq->pmem.src[i].offset = + req.pmem.src[i].offset + + creq->pmem.src[i].len; + creq->pmem.src[i].len = + req.pmem.src[i].len - + creq->pmem.src[i].len; + req.pmem.src[i].offset = + creq->pmem.src[i].offset; + req.pmem.src[i].len = + creq->pmem.src[i].len; + + if (creq->pmem.src[i].len == 0) + i++; + } + + } /* end of while ((i < req.entries) && (err == 0)) */ + + } else + err = qcedev_pmem_ablk_cipher_max_xfer(qcedev_areq, podev); + + /* Restore the original req structure */ + for (i = 0; i < saved_req->entries; i++) { + creq->pmem.src[i].len = saved_req->pmem.src[i].len; + creq->pmem.src[i].offset = saved_req->pmem.src[i].offset; + } + creq->entries = saved_req->entries; + creq->data_len = saved_req->data_len; + kfree(saved_req); + + return err; + +} + +static int qcedev_vbuf_ablk_cipher_max_xfer(struct qcedev_async_req *areq, + int *di, struct qcedev_control *podev, + uint8_t *k_align_src) +{ + int err = 0; + int i = 0; + int dst_i = *di; + struct scatterlist sg_src; + uint32_t byteoffset = 0; + uint8_t *user_src = NULL; + uint8_t *k_align_dst = k_align_src; + struct qcedev_cipher_op_req *creq = &areq->cipher_op_req; + + + if (areq->cipher_op_req.mode == QCEDEV_AES_MODE_CTR) + byteoffset = areq->cipher_op_req.byteoffset; + + user_src = (void __user *)areq->cipher_op_req.vbuf.src[0].vaddr; + if (user_src && __copy_from_user((k_align_src + byteoffset), + (void __user *)user_src, + areq->cipher_op_req.vbuf.src[0].len)) + return -EFAULT; + + k_align_src += areq->cipher_op_req.vbuf.src[0].len; + + for (i = 1; i < areq->cipher_op_req.entries; i++) { + user_src = + (void __user *)areq->cipher_op_req.vbuf.src[i].vaddr; + if (user_src && __copy_from_user(k_align_src, + (void __user *)user_src, + areq->cipher_op_req.vbuf.src[i].len)) { + return -EFAULT; + } + k_align_src += areq->cipher_op_req.vbuf.src[i].len; + } + + /* restore src beginning */ + k_align_src = k_align_dst; + areq->cipher_op_req.data_len += byteoffset; + + areq->cipher_req.creq.src = (struct scatterlist *) &sg_src; + areq->cipher_req.creq.dst = (struct scatterlist *) &sg_src; + + /* In place encryption/decryption */ + sg_set_buf(areq->cipher_req.creq.src, + k_align_dst, + areq->cipher_op_req.data_len); + sg_mark_end(areq->cipher_req.creq.src); + + areq->cipher_req.creq.nbytes = areq->cipher_op_req.data_len; + areq->cipher_req.creq.info = areq->cipher_op_req.iv; + areq->cipher_op_req.entries = 1; + + err = submit_req(areq, podev); + + /* copy data to destination buffer*/ + creq->data_len -= byteoffset; + + while (creq->data_len > 0) { + if (creq->vbuf.dst[dst_i].len <= creq->data_len) { + if (err == 0 && __copy_to_user( + (void __user *)creq->vbuf.dst[dst_i].vaddr, + (k_align_dst + byteoffset), + creq->vbuf.dst[dst_i].len)) + return -EFAULT; + + k_align_dst += creq->vbuf.dst[dst_i].len + + byteoffset; + creq->data_len -= creq->vbuf.dst[dst_i].len; + dst_i++; + } else { + if (err == 0 && __copy_to_user( + (void __user *)creq->vbuf.dst[dst_i].vaddr, + (k_align_dst + byteoffset), + creq->data_len)) + return -EFAULT; + + k_align_dst += creq->data_len; + creq->vbuf.dst[dst_i].len -= creq->data_len; + creq->vbuf.dst[dst_i].vaddr += creq->data_len; + creq->data_len = 0; + } + } + *di = dst_i; + + return err; +}; + +static int qcedev_vbuf_ablk_cipher(struct qcedev_async_req *areq, + struct qcedev_control *podev) +{ + int err = 0; + int di = 0; + int i = 0; + int j = 0; + int k = 0; + uint32_t byteoffset = 0; + int num_entries = 0; + uint32_t total = 0; + uint32_t len; + uint8_t *k_buf_src = NULL; + uint8_t *k_align_src = NULL; + uint32_t max_data_xfer; + struct qcedev_cipher_op_req *saved_req; + struct qcedev_cipher_op_req *creq = &areq->cipher_op_req; + + /* Verify Source Address's */ + for (i = 0; i < areq->cipher_op_req.entries; i++) + if (!access_ok(VERIFY_READ, + (void __user *)areq->cipher_op_req.vbuf.src[i].vaddr, + areq->cipher_op_req.vbuf.src[i].len)) + return -EFAULT; + + /* Verify Destination Address's */ + if (areq->cipher_op_req.in_place_op != 1) + for (i = 0; i < areq->cipher_op_req.entries; i++) + if (!access_ok(VERIFY_READ, + (void __user *)areq->cipher_op_req.vbuf.dst[i].vaddr, + areq->cipher_op_req.vbuf.dst[i].len)) + return -EFAULT; + + if (areq->cipher_op_req.mode == QCEDEV_AES_MODE_CTR) + byteoffset = areq->cipher_op_req.byteoffset; + k_buf_src = kmalloc(QCE_MAX_OPER_DATA + CACHE_LINE_SIZE * 2, + GFP_KERNEL); + if (k_buf_src == NULL) { + printk(KERN_ERR "%s: Can't Allocate memory: k_buf_src 0x%x\n", + __func__, (uint32_t)k_buf_src); + return -ENOMEM; + } + k_align_src = (uint8_t *) ALIGN(((unsigned int)k_buf_src), + CACHE_LINE_SIZE); + max_data_xfer = QCE_MAX_OPER_DATA - byteoffset; + + saved_req = kmalloc(sizeof(struct qcedev_cipher_op_req), GFP_KERNEL); + if (saved_req == NULL) { + printk(KERN_ERR "%s: Can't Allocate memory:saved_req 0x%x\n", + __func__, (uint32_t)saved_req); + kfree(k_buf_src); + return -ENOMEM; + + } + memcpy(saved_req, creq, sizeof(struct qcedev_cipher_op_req)); + + if (areq->cipher_op_req.data_len > max_data_xfer) { + struct qcedev_cipher_op_req req; + + /* save the original req structure */ + memcpy(&req, creq, sizeof(struct qcedev_cipher_op_req)); + + i = 0; + /* Address 32 KB at a time */ + while ((i < req.entries) && (err == 0)) { + if (creq->vbuf.src[i].len > max_data_xfer) { + creq->vbuf.src[0].len = max_data_xfer; + if (i > 0) { + creq->vbuf.src[0].vaddr = + creq->vbuf.src[i].vaddr; + } + + creq->data_len = max_data_xfer; + creq->entries = 1; + + err = qcedev_vbuf_ablk_cipher_max_xfer(areq, + &di, podev, k_align_src); + if (err < 0) { + kfree(k_buf_src); + kfree(saved_req); + return err; + } + + creq->vbuf.src[i].len = req.vbuf.src[i].len - + max_data_xfer; + creq->vbuf.src[i].vaddr = + req.vbuf.src[i].vaddr + + max_data_xfer; + req.vbuf.src[i].vaddr = + creq->vbuf.src[i].vaddr; + req.vbuf.src[i].len = creq->vbuf.src[i].len; + + } else { + total = areq->cipher_op_req.byteoffset; + for (j = i; j < req.entries; j++) { + num_entries++; + if ((total + creq->vbuf.src[j].len) + >= max_data_xfer) { + creq->vbuf.src[j].len = + max_data_xfer - total; + total = max_data_xfer; + break; + } + total += creq->vbuf.src[j].len; + } + + creq->data_len = total; + if (i > 0) + for (k = 0; k < num_entries; k++) { + creq->vbuf.src[k].len = + creq->vbuf.src[i+k].len; + creq->vbuf.src[k].vaddr = + creq->vbuf.src[i+k].vaddr; + } + creq->entries = num_entries; + + i = j; + err = qcedev_vbuf_ablk_cipher_max_xfer(areq, + &di, podev, k_align_src); + if (err < 0) { + kfree(k_buf_src); + kfree(saved_req); + return err; + } + + num_entries = 0; + areq->cipher_op_req.byteoffset = 0; + + creq->vbuf.src[i].vaddr = req.vbuf.src[i].vaddr + + creq->vbuf.src[i].len; + creq->vbuf.src[i].len = req.vbuf.src[i].len - + creq->vbuf.src[i].len; + + req.vbuf.src[i].vaddr = + creq->vbuf.src[i].vaddr; + req.vbuf.src[i].len = creq->vbuf.src[i].len; + + if (creq->vbuf.src[i].len == 0) + i++; + } + + areq->cipher_op_req.byteoffset = 0; + max_data_xfer = QCE_MAX_OPER_DATA; + byteoffset = 0; + + } /* end of while ((i < req.entries) && (err == 0)) */ + } else + err = qcedev_vbuf_ablk_cipher_max_xfer(areq, &di, podev, + k_align_src); + + /* Restore the original req structure */ + for (i = 0; i < saved_req->entries; i++) { + creq->vbuf.src[i].len = saved_req->vbuf.src[i].len; + creq->vbuf.src[i].vaddr = saved_req->vbuf.src[i].vaddr; + } + for (len = 0, i = 0; len < saved_req->data_len; i++) { + creq->vbuf.dst[i].len = saved_req->vbuf.dst[i].len; + creq->vbuf.dst[i].vaddr = saved_req->vbuf.dst[i].vaddr; + len += saved_req->vbuf.dst[i].len; + } + creq->entries = saved_req->entries; + creq->data_len = saved_req->data_len; + creq->byteoffset = saved_req->byteoffset; + + kfree(saved_req); + kfree(k_buf_src); + return err; + +} + +static int qcedev_check_cipher_params(struct qcedev_cipher_op_req *req, + struct qcedev_control *podev) +{ + if ((req->entries == 0) || (req->data_len == 0)) + goto error; + if ((req->alg >= QCEDEV_ALG_LAST) || + (req->mode >= QCEDEV_AES_DES_MODE_LAST)) + goto error; + if (req->alg == QCEDEV_ALG_AES) { + if ((req->mode == QCEDEV_AES_MODE_XTS) && + (!podev->ce_support.aes_xts)) + goto error; + /* if intending to use HW key make sure key fields are set + * correctly and HW key is indeed supported in target + */ + if (req->encklen == 0) { + int i; + for (i = 0; i < QCEDEV_MAX_KEY_SIZE; i++) + if (req->enckey[i]) + goto error; + if ((req->op != QCEDEV_OPER_ENC_NO_KEY) && + (req->op != QCEDEV_OPER_DEC_NO_KEY)) + if (!podev->platform_support.hw_key_support) + goto error; + } else { + if (req->encklen == QCEDEV_AES_KEY_192) { + if (!podev->ce_support.aes_key_192) + goto error; + } else { + /* if not using HW key make sure key + * length is valid + */ + if (!((req->encklen == QCEDEV_AES_KEY_128) || + (req->encklen == QCEDEV_AES_KEY_256))) + goto error; + } + } + } + /* if using a byteoffset, make sure it is CTR mode using vbuf */ + if (req->byteoffset) { + if (req->mode != QCEDEV_AES_MODE_CTR) + goto error; + else { /* if using CTR mode make sure not using Pmem */ + if (req->use_pmem) + goto error; + } + } + /* if using PMEM with non-zero byteoffset, ensure it is in_place_op */ + if (req->use_pmem) { + if (!req->in_place_op) + goto error; + } + /* Ensure zer ivlen for ECB mode */ + if (req->ivlen != 0) { + if ((req->mode == QCEDEV_AES_MODE_ECB) || + (req->mode == QCEDEV_DES_MODE_ECB)) + goto error; + } else { + if ((req->mode != QCEDEV_AES_MODE_ECB) && + (req->mode != QCEDEV_DES_MODE_ECB)) + goto error; + } + + return 0; +error: + return -EINVAL; + +} + +static int qcedev_check_sha_params(struct qcedev_sha_op_req *req, + struct qcedev_control *podev) +{ + if ((req->alg == QCEDEV_ALG_AES_CMAC) && + (!podev->ce_support.cmac)) + goto sha_error; + + if ((req->entries == 0) || (req->data_len == 0)) + goto sha_error; + + if (req->alg >= QCEDEV_ALG_SHA_ALG_LAST) + goto sha_error; + + return 0; +sha_error: + return -EINVAL; +} + +static long qcedev_ioctl(struct file *file, unsigned cmd, unsigned long arg) +{ + int err = 0; + struct qcedev_control *podev; + struct qcedev_async_req qcedev_areq; + struct qcedev_stat *pstat; + + podev = file->private_data; + if (podev == NULL || podev->magic != QCEDEV_MAGIC) { + printk(KERN_ERR "%s: invalid handle %p\n", + __func__, podev); + return -ENOENT; + } + + /* Verify user arguments. */ + if (_IOC_TYPE(cmd) != QCEDEV_IOC_MAGIC) + return -ENOTTY; + + init_completion(&qcedev_areq.complete); + pstat = &_qcedev_stat[podev->pdev->id]; + + switch (cmd) { + case QCEDEV_IOCTL_LOCK_CE: + err = qcedev_lock_ce(podev); + break; + case QCEDEV_IOCTL_UNLOCK_CE: + err = qcedev_unlock_ce(podev); + break; + case QCEDEV_IOCTL_ENC_REQ: + case QCEDEV_IOCTL_DEC_REQ: + if (!access_ok(VERIFY_WRITE, (void __user *)arg, + sizeof(struct qcedev_cipher_op_req))) + return -EFAULT; + + if (__copy_from_user(&qcedev_areq.cipher_op_req, + (void __user *)arg, + sizeof(struct qcedev_cipher_op_req))) + return -EFAULT; + qcedev_areq.op_type = QCEDEV_CRYPTO_OPER_CIPHER; + + if (qcedev_check_cipher_params(&qcedev_areq.cipher_op_req, + podev)) + return -EINVAL; + + if (qcedev_areq.cipher_op_req.use_pmem == QCEDEV_USE_PMEM) + err = qcedev_pmem_ablk_cipher(&qcedev_areq, podev); + else + err = qcedev_vbuf_ablk_cipher(&qcedev_areq, podev); + if (err) + return err; + if (__copy_to_user((void __user *)arg, + &qcedev_areq.cipher_op_req, + sizeof(struct qcedev_cipher_op_req))) + return -EFAULT; + break; + + case QCEDEV_IOCTL_SHA_INIT_REQ: + + if (!access_ok(VERIFY_WRITE, (void __user *)arg, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + + if (__copy_from_user(&qcedev_areq.sha_op_req, + (void __user *)arg, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + if (qcedev_check_sha_params(&qcedev_areq.sha_op_req, podev)) + return -EINVAL; + qcedev_areq.op_type = QCEDEV_CRYPTO_OPER_SHA; + err = qcedev_hash_init(&qcedev_areq, podev); + if (err) + return err; + if (__copy_to_user((void __user *)arg, &qcedev_areq.sha_op_req, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + break; + case QCEDEV_IOCTL_GET_CMAC_REQ: + if (!podev->ce_support.cmac) + return -ENOTTY; + case QCEDEV_IOCTL_SHA_UPDATE_REQ: + if (!access_ok(VERIFY_WRITE, (void __user *)arg, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + + if (__copy_from_user(&qcedev_areq.sha_op_req, + (void __user *)arg, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + if (qcedev_check_sha_params(&qcedev_areq.sha_op_req, podev)) + return -EINVAL; + qcedev_areq.op_type = QCEDEV_CRYPTO_OPER_SHA; + + if (qcedev_areq.sha_op_req.alg == QCEDEV_ALG_AES_CMAC) { + err = qcedev_hash_cmac(&qcedev_areq, podev); + if (err) + return err; + } else { + err = qcedev_hash_update(&qcedev_areq, podev); + if (err) + return err; + } + + memcpy(&qcedev_areq.sha_op_req.digest[0], + &qcedev_areq.sha_op_req.ctxt.digest[0], + qcedev_areq.sha_op_req.ctxt.diglen); + if (__copy_to_user((void __user *)arg, &qcedev_areq.sha_op_req, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + break; + + case QCEDEV_IOCTL_SHA_FINAL_REQ: + + if (!access_ok(VERIFY_WRITE, (void __user *)arg, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + + if (__copy_from_user(&qcedev_areq.sha_op_req, + (void __user *)arg, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + if (qcedev_check_sha_params(&qcedev_areq.sha_op_req, podev)) + return -EINVAL; + qcedev_areq.op_type = QCEDEV_CRYPTO_OPER_SHA; + err = qcedev_hash_final(&qcedev_areq, podev); + if (err) + return err; + qcedev_areq.sha_op_req.diglen = + qcedev_areq.sha_op_req.ctxt.diglen; + memcpy(&qcedev_areq.sha_op_req.digest[0], + &qcedev_areq.sha_op_req.ctxt.digest[0], + qcedev_areq.sha_op_req.ctxt.diglen); + if (__copy_to_user((void __user *)arg, &qcedev_areq.sha_op_req, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + break; + + case QCEDEV_IOCTL_GET_SHA_REQ: + + if (!access_ok(VERIFY_WRITE, (void __user *)arg, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + + if (__copy_from_user(&qcedev_areq.sha_op_req, + (void __user *)arg, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + if (qcedev_check_sha_params(&qcedev_areq.sha_op_req, podev)) + return -EINVAL; + qcedev_areq.op_type = QCEDEV_CRYPTO_OPER_SHA; + qcedev_hash_init(&qcedev_areq, podev); + err = qcedev_hash_update(&qcedev_areq, podev); + if (err) + return err; + err = qcedev_hash_final(&qcedev_areq, podev); + if (err) + return err; + qcedev_areq.sha_op_req.diglen = + qcedev_areq.sha_op_req.ctxt.diglen; + memcpy(&qcedev_areq.sha_op_req.digest[0], + &qcedev_areq.sha_op_req.ctxt.digest[0], + qcedev_areq.sha_op_req.ctxt.diglen); + if (__copy_to_user((void __user *)arg, &qcedev_areq.sha_op_req, + sizeof(struct qcedev_sha_op_req))) + return -EFAULT; + break; + + default: + return -ENOTTY; + } + + return err; +} + +static int qcedev_probe(struct platform_device *pdev) +{ + void *handle = NULL; + int rc = 0; + struct qcedev_control *podev; + struct msm_ce_hw_support *platform_support; + + if (pdev->id >= MAX_QCE_DEVICE) { + printk(KERN_ERR "%s: device id %d exceeds allowed %d\n", + __func__, pdev->id, MAX_QCE_DEVICE); + return -ENOENT; + } + podev = &qce_dev[pdev->id]; + + platform_support = (struct msm_ce_hw_support *)pdev->dev.platform_data; + podev->platform_support.ce_shared = platform_support->ce_shared; + podev->platform_support.shared_ce_resource = + platform_support->shared_ce_resource; + podev->platform_support.hw_key_support = + platform_support->hw_key_support; + podev->ce_locked = false; + + INIT_LIST_HEAD(&podev->ready_commands); + podev->active_command = NULL; + + spin_lock_init(&podev->lock); + + tasklet_init(&podev->done_tasklet, req_done, (unsigned long)podev); + + /* open qce */ + handle = qce_open(pdev, &rc); + if (handle == NULL) { + platform_set_drvdata(pdev, NULL); + return rc; + } + + podev->qce = handle; + podev->pdev = pdev; + platform_set_drvdata(pdev, podev); + qce_hw_support(podev->qce, &podev->ce_support); + rc = misc_register(&podev->miscdevice); + + if (rc >= 0) + return 0; + + if (handle) + qce_close(handle); + platform_set_drvdata(pdev, NULL); + podev->qce = NULL; + podev->pdev = NULL; + return rc; +}; + +static int qcedev_remove(struct platform_device *pdev) +{ + struct qcedev_control *podev; + + podev = platform_get_drvdata(pdev); + if (!podev) + return 0; + if (podev->qce) + qce_close(podev->qce); + + if (podev->miscdevice.minor != MISC_DYNAMIC_MINOR) + misc_deregister(&podev->miscdevice); + tasklet_kill(&podev->done_tasklet); + return 0; +}; + +static struct platform_driver qcedev_plat_driver = { + .probe = qcedev_probe, + .remove = qcedev_remove, + .driver = { + .name = "qce", + .owner = THIS_MODULE, + }, +}; + +static int _disp_stats(int id) +{ + struct qcedev_stat *pstat; + int len = 0; + + pstat = &_qcedev_stat[id]; + len = snprintf(_debug_read_buf, DEBUG_MAX_RW_BUF - 1, + "\nQualcomm QCE dev driver %d Statistics:\n", + id + 1); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " Encryption operation success : %d\n", + pstat->qcedev_enc_success); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " Encryption operation fail : %d\n", + pstat->qcedev_enc_fail); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " Decryption operation success : %d\n", + pstat->qcedev_dec_success); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " Encryption operation fail : %d\n", + pstat->qcedev_dec_fail); + + return len; +} + +static int _debug_stats_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t _debug_stats_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int rc = -EINVAL; + int qcedev = *((int *) file->private_data); + int len; + + len = _disp_stats(qcedev); + + rc = simple_read_from_buffer((void __user *) buf, len, + ppos, (void *) _debug_read_buf, len); + + return rc; +} + +static ssize_t _debug_stats_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + + int qcedev = *((int *) file->private_data); + + memset((char *)&_qcedev_stat[qcedev], 0, sizeof(struct qcedev_stat)); + return count; +}; + +static const struct file_operations _debug_stats_ops = { + .open = _debug_stats_open, + .read = _debug_stats_read, + .write = _debug_stats_write, +}; + +static int _qcedev_debug_init(void) +{ + int rc; + char name[DEBUG_MAX_FNAME]; + int i; + struct dentry *dent; + + _debug_dent = debugfs_create_dir("qcedev", NULL); + if (IS_ERR(_debug_dent)) { + pr_err("qcedev debugfs_create_dir fail, error %ld\n", + PTR_ERR(_debug_dent)); + return PTR_ERR(_debug_dent); + } + + for (i = 0; i < MAX_QCE_DEVICE; i++) { + snprintf(name, DEBUG_MAX_FNAME-1, "stats-%d", i+1); + _debug_qcedev[i] = i; + dent = debugfs_create_file(name, 0644, _debug_dent, + &_debug_qcedev[i], &_debug_stats_ops); + if (dent == NULL) { + pr_err("qcedev debugfs_create_file fail, error %ld\n", + PTR_ERR(dent)); + rc = PTR_ERR(dent); + goto err; + } + } + return 0; +err: + debugfs_remove_recursive(_debug_dent); + return rc; +} + +static int qcedev_init(void) +{ + int rc; + + rc = _qcedev_debug_init(); + if (rc) + return rc; + return platform_driver_register(&qcedev_plat_driver); +} + +static void qcedev_exit(void) +{ + debugfs_remove_recursive(_debug_dent); + platform_driver_unregister(&qcedev_plat_driver); +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Mona Hossain "); +MODULE_DESCRIPTION("Qualcomm DEV Crypto driver"); +MODULE_VERSION("1.20"); + +module_init(qcedev_init); +module_exit(qcedev_exit); diff --git a/drivers/crypto/msm/qcrypto.c b/drivers/crypto/msm/qcrypto.c new file mode 100644 index 00000000000..c4fd64ba91b --- /dev/null +++ b/drivers/crypto/msm/qcrypto.c @@ -0,0 +1,3274 @@ +/* Qualcomm Crypto driver + * + * Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "inc/qce.h" + + +#define MAX_CRYPTO_DEVICE 3 +#define DEBUG_MAX_FNAME 16 +#define DEBUG_MAX_RW_BUF 1024 + +struct crypto_stat { + u32 aead_sha1_aes_enc; + u32 aead_sha1_aes_dec; + u32 aead_sha1_des_enc; + u32 aead_sha1_des_dec; + u32 aead_sha1_3des_enc; + u32 aead_sha1_3des_dec; + u32 aead_op_success; + u32 aead_op_fail; + u32 ablk_cipher_aes_enc; + u32 ablk_cipher_aes_dec; + u32 ablk_cipher_des_enc; + u32 ablk_cipher_des_dec; + u32 ablk_cipher_3des_enc; + u32 ablk_cipher_3des_dec; + u32 ablk_cipher_op_success; + u32 ablk_cipher_op_fail; + u32 sha1_digest; + u32 sha256_digest; + u32 sha_op_success; + u32 sha_op_fail; + u32 sha1_hmac_digest; + u32 sha256_hmac_digest; + u32 sha_hmac_op_success; + u32 sha_hmac_op_fail; +}; +static struct crypto_stat _qcrypto_stat[MAX_CRYPTO_DEVICE]; +static struct dentry *_debug_dent; +static char _debug_read_buf[DEBUG_MAX_RW_BUF]; + +struct crypto_priv { + /* CE features supported by target device*/ + struct msm_ce_hw_support platform_support; + + /* CE features/algorithms supported by HW engine*/ + struct ce_hw_support ce_support; + /* the lock protects queue and req*/ + spinlock_t lock; + + /* qce handle */ + void *qce; + + /* list of registered algorithms */ + struct list_head alg_list; + + /* platform device */ + struct platform_device *pdev; + + /* current active request */ + struct crypto_async_request *req; + int res; + + /* request queue */ + struct crypto_queue queue; + + uint32_t ce_lock_count; + + struct work_struct unlock_ce_ws; + + struct tasklet_struct done_tasklet; +}; + + +/*------------------------------------------------------------------------- +* Resource Locking Service +* ------------------------------------------------------------------------*/ +#define QCRYPTO_CMD_ID 1 +#define QCRYPTO_CE_LOCK_CMD 1 +#define QCRYPTO_CE_UNLOCK_CMD 0 +#define NUM_RETRY 1000 +#define CE_BUSY 55 + +static int qcrypto_scm_cmd(int resource, int cmd, int *response) +{ +#ifdef CONFIG_MSM_SCM + + struct { + int resource; + int cmd; + } cmd_buf; + + cmd_buf.resource = resource; + cmd_buf.cmd = cmd; + + return scm_call(SCM_SVC_TZ, QCRYPTO_CMD_ID, &cmd_buf, + sizeof(cmd_buf), response, sizeof(*response)); + +#else + return 0; +#endif +} + +static void qcrypto_unlock_ce(struct work_struct *work) +{ + int response = 0; + unsigned long flags; + struct crypto_priv *cp = container_of(work, struct crypto_priv, + unlock_ce_ws); + if (cp->ce_lock_count == 1) + BUG_ON(qcrypto_scm_cmd(cp->platform_support.shared_ce_resource, + QCRYPTO_CE_UNLOCK_CMD, &response) != 0); + spin_lock_irqsave(&cp->lock, flags); + cp->ce_lock_count--; + spin_unlock_irqrestore(&cp->lock, flags); +} + +static int qcrypto_lock_ce(struct crypto_priv *cp) +{ + unsigned long flags; + int response = -CE_BUSY; + int i = 0; + + if (cp->ce_lock_count == 0) { + do { + if (qcrypto_scm_cmd( + cp->platform_support.shared_ce_resource, + QCRYPTO_CE_LOCK_CMD, &response)) { + response = -EINVAL; + break; + } + } while ((response == -CE_BUSY) && (i++ < NUM_RETRY)); + + if ((response == -CE_BUSY) && (i >= NUM_RETRY)) + return -EUSERS; + if (response < 0) + return -EINVAL; + } + spin_lock_irqsave(&cp->lock, flags); + cp->ce_lock_count++; + spin_unlock_irqrestore(&cp->lock, flags); + + + return 0; +} + +enum qcrypto_alg_type { + QCRYPTO_ALG_CIPHER = 0, + QCRYPTO_ALG_SHA = 1, + QCRYPTO_ALG_LAST +}; + +struct qcrypto_alg { + struct list_head entry; + struct crypto_alg cipher_alg; + struct ahash_alg sha_alg; + enum qcrypto_alg_type alg_type; + struct crypto_priv *cp; +}; + +#define QCRYPTO_MAX_KEY_SIZE 64 +/* max of AES_BLOCK_SIZE, DES3_EDE_BLOCK_SIZE */ +#define QCRYPTO_MAX_IV_LENGTH 16 + +struct qcrypto_cipher_ctx { + u8 auth_key[QCRYPTO_MAX_KEY_SIZE]; + u8 iv[QCRYPTO_MAX_IV_LENGTH]; + + u8 enc_key[QCRYPTO_MAX_KEY_SIZE]; + unsigned int enc_key_len; + + unsigned int authsize; + unsigned int auth_key_len; + + struct crypto_priv *cp; +}; + +struct qcrypto_cipher_req_ctx { + u8 *iv; + unsigned int ivsize; + int aead; + struct scatterlist asg; /* Formatted associated data sg */ + unsigned char *assoc; /* Pointer to formatted assoc data */ + unsigned int assoclen; /* Save Unformatted assoc data length */ + struct scatterlist *assoc_sg; /* Save Unformatted assoc data sg */ + enum qce_cipher_alg_enum alg; + enum qce_cipher_dir_enum dir; + enum qce_cipher_mode_enum mode; +}; + +#define SHA_MAX_BLOCK_SIZE SHA256_BLOCK_SIZE +#define SHA_MAX_STATE_SIZE (SHA256_DIGEST_SIZE / sizeof(u32)) +#define SHA_MAX_DIGEST_SIZE SHA256_DIGEST_SIZE + +static uint8_t _std_init_vector_sha1_uint8[] = { + 0x67, 0x45, 0x23, 0x01, 0xEF, 0xCD, 0xAB, 0x89, + 0x98, 0xBA, 0xDC, 0xFE, 0x10, 0x32, 0x54, 0x76, + 0xC3, 0xD2, 0xE1, 0xF0 +}; + +/* standard initialization vector for SHA-256, source: FIPS 180-2 */ +static uint8_t _std_init_vector_sha256_uint8[] = { + 0x6A, 0x09, 0xE6, 0x67, 0xBB, 0x67, 0xAE, 0x85, + 0x3C, 0x6E, 0xF3, 0x72, 0xA5, 0x4F, 0xF5, 0x3A, + 0x51, 0x0E, 0x52, 0x7F, 0x9B, 0x05, 0x68, 0x8C, + 0x1F, 0x83, 0xD9, 0xAB, 0x5B, 0xE0, 0xCD, 0x19 +}; + +struct qcrypto_sha_ctx { + enum qce_hash_alg_enum alg; + uint32_t byte_count[4]; + uint8_t digest[SHA_MAX_DIGEST_SIZE]; + uint32_t diglen; + uint8_t *tmp_tbuf; + uint8_t *trailing_buf; + uint8_t *in_buf; + uint32_t authkey_in_len; + uint32_t trailing_buf_len; + uint8_t first_blk; + uint8_t last_blk; + uint8_t authkey[SHA_MAX_BLOCK_SIZE]; + struct ahash_request *ahash_req; + struct completion ahash_req_complete; + struct scatterlist *sg; + struct scatterlist tmp_sg; + struct crypto_priv *cp; +}; + +struct qcrypto_sha_req_ctx { + union { + struct sha1_state sha1_state_ctx; + struct sha256_state sha256_state_ctx; + }; + struct scatterlist *src; + uint32_t nbytes; +}; + +static void _byte_stream_to_words(uint32_t *iv, unsigned char *b, + unsigned int len) +{ + unsigned n; + + n = len / sizeof(uint32_t) ; + for (; n > 0; n--) { + *iv = ((*b << 24) & 0xff000000) | + (((*(b+1)) << 16) & 0xff0000) | + (((*(b+2)) << 8) & 0xff00) | + (*(b+3) & 0xff); + b += sizeof(uint32_t); + iv++; + } + + n = len % sizeof(uint32_t); + if (n == 3) { + *iv = ((*b << 24) & 0xff000000) | + (((*(b+1)) << 16) & 0xff0000) | + (((*(b+2)) << 8) & 0xff00) ; + } else if (n == 2) { + *iv = ((*b << 24) & 0xff000000) | + (((*(b+1)) << 16) & 0xff0000) ; + } else if (n == 1) { + *iv = ((*b << 24) & 0xff000000) ; + } +} + +static void _words_to_byte_stream(uint32_t *iv, unsigned char *b, + unsigned int len) +{ + unsigned n = len / sizeof(uint32_t); + + for (; n > 0; n--) { + *b++ = (unsigned char) ((*iv >> 24) & 0xff); + *b++ = (unsigned char) ((*iv >> 16) & 0xff); + *b++ = (unsigned char) ((*iv >> 8) & 0xff); + *b++ = (unsigned char) (*iv & 0xff); + iv++; + } + n = len % sizeof(uint32_t); + if (n == 3) { + *b++ = (unsigned char) ((*iv >> 24) & 0xff); + *b++ = (unsigned char) ((*iv >> 16) & 0xff); + *b = (unsigned char) ((*iv >> 8) & 0xff); + } else if (n == 2) { + *b++ = (unsigned char) ((*iv >> 24) & 0xff); + *b = (unsigned char) ((*iv >> 16) & 0xff); + } else if (n == 1) { + *b = (unsigned char) ((*iv >> 24) & 0xff); + } +} + +static void _start_qcrypto_process(struct crypto_priv *cp); + +static struct qcrypto_alg *_qcrypto_sha_alg_alloc(struct crypto_priv *cp, + struct ahash_alg *template) +{ + struct qcrypto_alg *q_alg; + q_alg = kzalloc(sizeof(struct qcrypto_alg), GFP_KERNEL); + if (!q_alg) { + pr_err("qcrypto Memory allocation of q_alg FAIL, error %ld\n", + PTR_ERR(q_alg)); + return ERR_PTR(-ENOMEM); + } + + q_alg->alg_type = QCRYPTO_ALG_SHA; + q_alg->sha_alg = *template; + q_alg->cp = cp; + + return q_alg; +}; + +static struct qcrypto_alg *_qcrypto_cipher_alg_alloc(struct crypto_priv *cp, + struct crypto_alg *template) +{ + struct qcrypto_alg *q_alg; + + q_alg = kzalloc(sizeof(struct qcrypto_alg), GFP_KERNEL); + if (!q_alg) { + pr_err("qcrypto Memory allocation of q_alg FAIL, error %ld\n", + PTR_ERR(q_alg)); + return ERR_PTR(-ENOMEM); + } + + q_alg->alg_type = QCRYPTO_ALG_CIPHER; + q_alg->cipher_alg = *template; + q_alg->cp = cp; + + return q_alg; +}; + +static int _qcrypto_cipher_cra_init(struct crypto_tfm *tfm) +{ + struct crypto_alg *alg = tfm->__crt_alg; + struct qcrypto_alg *q_alg; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(tfm); + + q_alg = container_of(alg, struct qcrypto_alg, cipher_alg); + + /* update context with ptr to cp */ + ctx->cp = q_alg->cp; + + /* random first IV */ + get_random_bytes(ctx->iv, QCRYPTO_MAX_IV_LENGTH); + + return 0; +}; + +static int _qcrypto_ahash_cra_init(struct crypto_tfm *tfm) +{ + struct crypto_ahash *ahash = __crypto_ahash_cast(tfm); + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(tfm); + struct ahash_alg *alg = container_of(crypto_hash_alg_common(ahash), + struct ahash_alg, halg); + struct qcrypto_alg *q_alg = container_of(alg, struct qcrypto_alg, + sha_alg); + + crypto_ahash_set_reqsize(ahash, sizeof(struct qcrypto_sha_req_ctx)); + /* update context with ptr to cp */ + sha_ctx->cp = q_alg->cp; + sha_ctx->sg = NULL; + sha_ctx->tmp_tbuf = kzalloc(SHA_MAX_BLOCK_SIZE + + SHA_MAX_DIGEST_SIZE, GFP_KERNEL); + if (sha_ctx->tmp_tbuf == NULL) { + pr_err("qcrypto Can't Allocate mem: sha_ctx->tmp_tbuf, error %ld\n", + PTR_ERR(sha_ctx->tmp_tbuf)); + return -ENOMEM; + } + + sha_ctx->trailing_buf = kzalloc(SHA_MAX_BLOCK_SIZE, GFP_KERNEL); + if (sha_ctx->trailing_buf == NULL) { + kfree(sha_ctx->tmp_tbuf); + sha_ctx->tmp_tbuf = NULL; + pr_err("qcrypto Can't Allocate mem: sha_ctx->trailing_buf, error %ld\n", + PTR_ERR(sha_ctx->trailing_buf)); + return -ENOMEM; + } + + sha_ctx->ahash_req = NULL; + return 0; +}; + +static void _qcrypto_ahash_cra_exit(struct crypto_tfm *tfm) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(tfm); + + kfree(sha_ctx->tmp_tbuf); + sha_ctx->tmp_tbuf = NULL; + kfree(sha_ctx->trailing_buf); + sha_ctx->trailing_buf = NULL; + if (sha_ctx->sg != NULL) { + kfree(sha_ctx->sg); + sha_ctx->sg = NULL; + } + if (sha_ctx->ahash_req != NULL) { + ahash_request_free(sha_ctx->ahash_req); + sha_ctx->ahash_req = NULL; + } +}; + + +static void _crypto_sha_hmac_ahash_req_complete( + struct crypto_async_request *req, int err); + +static int _qcrypto_ahash_hmac_cra_init(struct crypto_tfm *tfm) +{ + struct crypto_ahash *ahash = __crypto_ahash_cast(tfm); + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(tfm); + int ret = 0; + + ret = _qcrypto_ahash_cra_init(tfm); + if (ret) + return ret; + sha_ctx->ahash_req = ahash_request_alloc(ahash, GFP_KERNEL); + + if (sha_ctx->ahash_req == NULL) { + _qcrypto_ahash_cra_exit(tfm); + return -ENOMEM; + } + + init_completion(&sha_ctx->ahash_req_complete); + ahash_request_set_callback(sha_ctx->ahash_req, + CRYPTO_TFM_REQ_MAY_BACKLOG, + _crypto_sha_hmac_ahash_req_complete, + &sha_ctx->ahash_req_complete); + crypto_ahash_clear_flags(ahash, ~0); + + return 0; +}; + +static int _qcrypto_cra_ablkcipher_init(struct crypto_tfm *tfm) +{ + tfm->crt_ablkcipher.reqsize = sizeof(struct qcrypto_cipher_req_ctx); + return _qcrypto_cipher_cra_init(tfm); +}; + +static int _qcrypto_cra_aead_init(struct crypto_tfm *tfm) +{ + tfm->crt_aead.reqsize = sizeof(struct qcrypto_cipher_req_ctx); + return _qcrypto_cipher_cra_init(tfm); +}; + +static int _disp_stats(int id) +{ + struct crypto_stat *pstat; + int len = 0; + + pstat = &_qcrypto_stat[id]; + len = snprintf(_debug_read_buf, DEBUG_MAX_RW_BUF - 1, + "\nQualcomm crypto accelerator %d Statistics:\n", + id + 1); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " ABLK AES CIPHER encryption : %d\n", + pstat->ablk_cipher_aes_enc); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " ABLK AES CIPHER decryption : %d\n", + pstat->ablk_cipher_aes_dec); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " ABLK DES CIPHER encryption : %d\n", + pstat->ablk_cipher_des_enc); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " ABLK DES CIPHER decryption : %d\n", + pstat->ablk_cipher_des_dec); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " ABLK 3DES CIPHER encryption : %d\n", + pstat->ablk_cipher_3des_enc); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " ABLK 3DES CIPHER decryption : %d\n", + pstat->ablk_cipher_3des_dec); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " ABLK CIPHER operation success: %d\n", + pstat->ablk_cipher_op_success); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " ABLK CIPHER operation fail : %d\n", + pstat->ablk_cipher_op_fail); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " AEAD SHA1-AES encryption : %d\n", + pstat->aead_sha1_aes_enc); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " AEAD SHA1-AES decryption : %d\n", + pstat->aead_sha1_aes_dec); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " AEAD SHA1-DES encryption : %d\n", + pstat->aead_sha1_des_enc); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " AEAD SHA1-DES decryption : %d\n", + pstat->aead_sha1_des_dec); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " AEAD SHA1-3DES encryption : %d\n", + pstat->aead_sha1_3des_enc); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " AEAD SHA1-3DES decryption : %d\n", + pstat->aead_sha1_3des_dec); + + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " AEAD operation success : %d\n", + pstat->aead_op_success); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " AEAD operation fail : %d\n", + pstat->aead_op_fail); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " SHA1 digest : %d\n", + pstat->sha1_digest); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " SHA256 digest : %d\n", + pstat->sha256_digest); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " SHA operation fail : %d\n", + pstat->sha_op_fail); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " SHA operation success : %d\n", + pstat->sha_op_success); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " SHA1 HMAC digest : %d\n", + pstat->sha1_hmac_digest); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " SHA256 HMAC digest : %d\n", + pstat->sha256_hmac_digest); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " SHA HMAC operation fail : %d\n", + pstat->sha_hmac_op_fail); + len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, + " SHA HMAC operation success : %d\n", + pstat->sha_hmac_op_success); + return len; +} + +static int _qcrypto_remove(struct platform_device *pdev) +{ + struct crypto_priv *cp; + struct qcrypto_alg *q_alg; + struct qcrypto_alg *n; + + cp = platform_get_drvdata(pdev); + + if (!cp) + return 0; + + list_for_each_entry_safe(q_alg, n, &cp->alg_list, entry) { + if (q_alg->alg_type == QCRYPTO_ALG_CIPHER) + crypto_unregister_alg(&q_alg->cipher_alg); + if (q_alg->alg_type == QCRYPTO_ALG_SHA) + crypto_unregister_ahash(&q_alg->sha_alg); + list_del(&q_alg->entry); + kfree(q_alg); + } + + if (cp->qce) + qce_close(cp->qce); + tasklet_kill(&cp->done_tasklet); + kfree(cp); + return 0; +}; + +static int _qcrypto_setkey_aes(struct crypto_ablkcipher *cipher, const u8 *key, + unsigned int len) +{ + struct crypto_tfm *tfm = crypto_ablkcipher_tfm(cipher); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(tfm); + struct crypto_priv *cp = ctx->cp; + + switch (len) { + case AES_KEYSIZE_128: + case AES_KEYSIZE_256: + break; + case AES_KEYSIZE_192: + if (cp->ce_support.aes_key_192) + break; + default: + crypto_ablkcipher_set_flags(cipher, CRYPTO_TFM_RES_BAD_KEY_LEN); + return -EINVAL; + }; + ctx->enc_key_len = len; + memcpy(ctx->enc_key, key, len); + return 0; +}; + +static int _qcrypto_setkey_des(struct crypto_ablkcipher *cipher, const u8 *key, + unsigned int len) +{ + struct crypto_tfm *tfm = crypto_ablkcipher_tfm(cipher); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(tfm); + u32 tmp[DES_EXPKEY_WORDS]; + int ret = des_ekey(tmp, key); + + if (len != DES_KEY_SIZE) { + crypto_ablkcipher_set_flags(cipher, CRYPTO_TFM_RES_BAD_KEY_LEN); + return -EINVAL; + }; + + if (unlikely(ret == 0) && (tfm->crt_flags & CRYPTO_TFM_REQ_WEAK_KEY)) { + tfm->crt_flags |= CRYPTO_TFM_RES_WEAK_KEY; + return -EINVAL; + } + + ctx->enc_key_len = len; + memcpy(ctx->enc_key, key, len); + return 0; +}; + +static int _qcrypto_setkey_3des(struct crypto_ablkcipher *cipher, const u8 *key, + unsigned int len) +{ + struct crypto_tfm *tfm = crypto_ablkcipher_tfm(cipher); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(tfm); + + if (len != DES3_EDE_KEY_SIZE) { + crypto_ablkcipher_set_flags(cipher, CRYPTO_TFM_RES_BAD_KEY_LEN); + return -EINVAL; + }; + ctx->enc_key_len = len; + memcpy(ctx->enc_key, key, len); + return 0; +}; + +static void req_done(unsigned long data) +{ + struct crypto_async_request *areq; + struct crypto_priv *cp = (struct crypto_priv *)data; + unsigned long flags; + + spin_lock_irqsave(&cp->lock, flags); + areq = cp->req; + cp->req = NULL; + spin_unlock_irqrestore(&cp->lock, flags); + + if (areq) + areq->complete(areq, cp->res); + _start_qcrypto_process(cp); +}; + +static void _update_sha1_ctx(struct ahash_request *req) +{ + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct sha1_state *sha_state_ctx = &rctx->sha1_state_ctx; + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + + if (sha_ctx->last_blk == 1) + memset(sha_state_ctx, 0x00, sizeof(struct sha1_state)); + else { + memset(sha_state_ctx->buffer, 0x00, SHA1_BLOCK_SIZE); + memcpy(sha_state_ctx->buffer, sha_ctx->trailing_buf, + sha_ctx->trailing_buf_len); + _byte_stream_to_words(sha_state_ctx->state , sha_ctx->digest, + SHA1_DIGEST_SIZE); + } + return; +} + +static void _update_sha256_ctx(struct ahash_request *req) +{ + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct sha256_state *sha_state_ctx = &rctx->sha256_state_ctx; + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + + if (sha_ctx->last_blk == 1) + memset(sha_state_ctx, 0x00, sizeof(struct sha256_state)); + else { + memset(sha_state_ctx->buf, 0x00, SHA256_BLOCK_SIZE); + memcpy(sha_state_ctx->buf, sha_ctx->trailing_buf, + sha_ctx->trailing_buf_len); + _byte_stream_to_words(sha_state_ctx->state, sha_ctx->digest, + SHA256_DIGEST_SIZE); + } + return; +} + +static void _qce_ahash_complete(void *cookie, unsigned char *digest, + unsigned char *authdata, int ret) +{ + struct ahash_request *areq = (struct ahash_request *) cookie; + struct crypto_ahash *ahash = crypto_ahash_reqtfm(areq); + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(areq->base.tfm); + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(areq); + struct crypto_priv *cp = sha_ctx->cp; + struct crypto_stat *pstat; + uint32_t diglen = crypto_ahash_digestsize(ahash); + uint32_t *auth32 = (uint32_t *)authdata; + + pstat = &_qcrypto_stat[cp->pdev->id]; + +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qce_ahash_complete: %p ret %d\n", + areq, ret); +#endif + + if (digest) { + memcpy(sha_ctx->digest, digest, diglen); + memcpy(areq->result, digest, diglen); + } + if (authdata) { + sha_ctx->byte_count[0] = auth32[0]; + sha_ctx->byte_count[1] = auth32[1]; + sha_ctx->byte_count[2] = auth32[2]; + sha_ctx->byte_count[3] = auth32[3]; + } + areq->src = rctx->src; + areq->nbytes = rctx->nbytes; + + if (sha_ctx->sg != NULL) { + kfree(sha_ctx->sg); + sha_ctx->sg = NULL; + } + + if (sha_ctx->alg == QCE_HASH_SHA1) + _update_sha1_ctx(areq); + if (sha_ctx->alg == QCE_HASH_SHA256) + _update_sha256_ctx(areq); + + sha_ctx->last_blk = 0; + sha_ctx->first_blk = 0; + + if (ret) { + cp->res = -ENXIO; + pstat->sha_op_fail++; + } else { + cp->res = 0; + pstat->sha_op_success++; + } + + if (cp->platform_support.ce_shared) + schedule_work(&cp->unlock_ce_ws); + tasklet_schedule(&cp->done_tasklet); +}; + +static void _qce_ablk_cipher_complete(void *cookie, unsigned char *icb, + unsigned char *iv, int ret) +{ + struct ablkcipher_request *areq = (struct ablkcipher_request *) cookie; + struct crypto_ablkcipher *ablk = crypto_ablkcipher_reqtfm(areq); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(areq->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qce_ablk_cipher_complete: %p ret %d\n", + areq, ret); +#endif + if (iv) + memcpy(ctx->iv, iv, crypto_ablkcipher_ivsize(ablk)); + + if (ret) { + cp->res = -ENXIO; + pstat->ablk_cipher_op_fail++; + } else { + cp->res = 0; + pstat->ablk_cipher_op_success++; + } + if (cp->platform_support.ce_shared) + schedule_work(&cp->unlock_ce_ws); + tasklet_schedule(&cp->done_tasklet); +}; + + +static void _qce_aead_complete(void *cookie, unsigned char *icv, + unsigned char *iv, int ret) +{ + struct aead_request *areq = (struct aead_request *) cookie; + struct crypto_aead *aead = crypto_aead_reqtfm(areq); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(areq->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct qcrypto_cipher_req_ctx *rctx; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(areq); + + if (rctx->mode == QCE_MODE_CCM) { + kzfree(rctx->assoc); + areq->assoc = rctx->assoc_sg; + areq->assoclen = rctx->assoclen; + if (ret) { + if (ret == 0x2000000) + ret = -EBADMSG; + else + ret = -ENXIO; + } + } else { + if (ret == 0) { + if (rctx->dir == QCE_ENCRYPT) { + /* copy the icv to dst */ + scatterwalk_map_and_copy(icv, areq->dst, + areq->cryptlen, + ctx->authsize, 1); + + } else { + unsigned char tmp[SHA256_DIGESTSIZE]; + + /* compare icv from src */ + scatterwalk_map_and_copy(tmp, + areq->src, areq->cryptlen - + ctx->authsize, ctx->authsize, 0); + ret = memcmp(icv, tmp, ctx->authsize); + if (ret != 0) + ret = -EBADMSG; + + } + } else { + ret = -ENXIO; + } + + if (iv) + memcpy(ctx->iv, iv, crypto_aead_ivsize(aead)); + } + + if (ret) + pstat->aead_op_fail++; + else + pstat->aead_op_success++; + + if (cp->platform_support.ce_shared) + schedule_work(&cp->unlock_ce_ws); + tasklet_schedule(&cp->done_tasklet); +} + +static int aead_ccm_set_msg_len(u8 *block, unsigned int msglen, int csize) +{ + __be32 data; + + memset(block, 0, csize); + block += csize; + + if (csize >= 4) + csize = 4; + else if (msglen > (1 << (8 * csize))) + return -EOVERFLOW; + + data = cpu_to_be32(msglen); + memcpy(block - csize, (u8 *)&data + 4 - csize, csize); + + return 0; +} + +static int qccrypto_set_aead_ccm_nonce(struct qce_req *qreq) +{ + struct aead_request *areq = (struct aead_request *) qreq->areq; + unsigned int i = ((unsigned int)qreq->iv[0]) + 1; + + memcpy(&qreq->nonce[0] , qreq->iv, qreq->ivsize); + /* + * Format control info per RFC 3610 and + * NIST Special Publication 800-38C + */ + qreq->nonce[0] |= (8 * ((qreq->authsize - 2) / 2)); + if (areq->assoclen) + qreq->nonce[0] |= 64; + + return aead_ccm_set_msg_len(qreq->nonce + 16 - i, qreq->cryptlen, i); +} + +static int qcrypto_aead_ccm_format_adata(struct qce_req *qreq, uint32_t alen, + struct scatterlist *sg) +{ + unsigned char *adata; + uint32_t len, l; + + qreq->assoc = kzalloc((alen + 0x64), (GFP_KERNEL | __GFP_DMA)); + if (!qreq->assoc) { + pr_err("qcrypto Memory allocation of adata FAIL, error %ld\n", + PTR_ERR(qreq->assoc)); + return -ENOMEM; + } + adata = qreq->assoc; + /* + * Add control info for associated data + * RFC 3610 and NIST Special Publication 800-38C + */ + if (alen < 65280) { + *(__be16 *)adata = cpu_to_be16(alen); + len = 2; + } else { + if ((alen >= 65280) && (alen <= 0xffffffff)) { + *(__be16 *)adata = cpu_to_be16(0xfffe); + *(__be32 *)&adata[2] = cpu_to_be32(alen); + len = 6; + } else { + *(__be16 *)adata = cpu_to_be16(0xffff); + *(__be32 *)&adata[6] = cpu_to_be32(alen); + len = 10; + } + } + adata += len; + qreq->assoclen = ALIGN((alen + len), 16); + for (l = alen; l > 0; sg = sg_next(sg)) { + memcpy(adata, sg_virt(sg), sg->length); + l -= sg->length; + adata += sg->length; + } + return 0; +} + +static void _start_qcrypto_process(struct crypto_priv *cp) +{ + struct crypto_async_request *async_req = NULL; + struct crypto_async_request *backlog = NULL; + unsigned long flags; + u32 type; + struct qce_req qreq; + int ret; + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *cipher_ctx; + struct qcrypto_sha_ctx *sha_ctx; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + +again: + spin_lock_irqsave(&cp->lock, flags); + if (cp->req == NULL) { + backlog = crypto_get_backlog(&cp->queue); + async_req = crypto_dequeue_request(&cp->queue); + cp->req = async_req; + } + spin_unlock_irqrestore(&cp->lock, flags); + if (!async_req) + return; + if (backlog) + backlog->complete(backlog, -EINPROGRESS); + type = crypto_tfm_alg_type(async_req->tfm); + + if (type == CRYPTO_ALG_TYPE_ABLKCIPHER) { + struct ablkcipher_request *req; + struct crypto_ablkcipher *tfm; + + req = container_of(async_req, struct ablkcipher_request, base); + cipher_ctx = crypto_tfm_ctx(async_req->tfm); + rctx = ablkcipher_request_ctx(req); + tfm = crypto_ablkcipher_reqtfm(req); + + qreq.op = QCE_REQ_ABLK_CIPHER; + qreq.qce_cb = _qce_ablk_cipher_complete; + qreq.areq = req; + qreq.alg = rctx->alg; + qreq.dir = rctx->dir; + qreq.mode = rctx->mode; + qreq.enckey = cipher_ctx->enc_key; + qreq.encklen = cipher_ctx->enc_key_len; + qreq.iv = req->info; + qreq.ivsize = crypto_ablkcipher_ivsize(tfm); + qreq.cryptlen = req->nbytes; + qreq.use_pmem = 0; + + if ((cipher_ctx->enc_key_len == 0) && + (cp->platform_support.hw_key_support == 0)) + ret = -EINVAL; + else + ret = qce_ablk_cipher_req(cp->qce, &qreq); + } else { + if (type == CRYPTO_ALG_TYPE_AHASH) { + + struct ahash_request *req; + struct qce_sha_req sreq; + + req = container_of(async_req, + struct ahash_request, base); + sha_ctx = crypto_tfm_ctx(async_req->tfm); + + sreq.qce_cb = _qce_ahash_complete; + sreq.digest = &sha_ctx->digest[0]; + sreq.src = req->src; + sreq.auth_data[0] = sha_ctx->byte_count[0]; + sreq.auth_data[1] = sha_ctx->byte_count[1]; + sreq.auth_data[2] = sha_ctx->byte_count[2]; + sreq.auth_data[3] = sha_ctx->byte_count[3]; + sreq.first_blk = sha_ctx->first_blk; + sreq.last_blk = sha_ctx->last_blk; + sreq.size = req->nbytes; + sreq.areq = req; + + switch (sha_ctx->alg) { + case QCE_HASH_SHA1: + sreq.alg = QCE_HASH_SHA1; + sreq.authkey = NULL; + break; + case QCE_HASH_SHA256: + sreq.alg = QCE_HASH_SHA256; + sreq.authkey = NULL; + break; + case QCE_HASH_SHA1_HMAC: + sreq.alg = QCE_HASH_SHA1_HMAC; + sreq.authkey = &sha_ctx->authkey[0]; + break; + case QCE_HASH_SHA256_HMAC: + sreq.alg = QCE_HASH_SHA256_HMAC; + sreq.authkey = &sha_ctx->authkey[0]; + break; + default: + break; + }; + ret = qce_process_sha_req(cp->qce, &sreq); + + } else { + struct aead_request *req = container_of(async_req, + struct aead_request, base); + struct crypto_aead *aead = crypto_aead_reqtfm(req); + + rctx = aead_request_ctx(req); + cipher_ctx = crypto_tfm_ctx(async_req->tfm); + + qreq.op = QCE_REQ_AEAD; + qreq.qce_cb = _qce_aead_complete; + + qreq.areq = req; + qreq.alg = rctx->alg; + qreq.dir = rctx->dir; + qreq.mode = rctx->mode; + qreq.iv = rctx->iv; + + qreq.enckey = cipher_ctx->enc_key; + qreq.encklen = cipher_ctx->enc_key_len; + qreq.authkey = cipher_ctx->auth_key; + qreq.authklen = cipher_ctx->auth_key_len; + qreq.authsize = crypto_aead_authsize(aead); + qreq.ivsize = crypto_aead_ivsize(aead); + if (qreq.mode == QCE_MODE_CCM) { + if (qreq.dir == QCE_ENCRYPT) + qreq.cryptlen = req->cryptlen; + else + qreq.cryptlen = req->cryptlen - + qreq.authsize; + /* Get NONCE */ + ret = qccrypto_set_aead_ccm_nonce(&qreq); + if (ret) + goto done; + /* Format Associated data */ + ret = qcrypto_aead_ccm_format_adata(&qreq, + req->assoclen, + req->assoc); + if (ret) + goto done; + /* + * Save the original associated data + * length and sg + */ + rctx->assoc_sg = req->assoc; + rctx->assoclen = req->assoclen; + rctx->assoc = qreq.assoc; + /* + * update req with new formatted associated + * data info + */ + req->assoc = &rctx->asg; + req->assoclen = qreq.assoclen; + sg_set_buf(req->assoc, qreq.assoc, + req->assoclen); + sg_mark_end(req->assoc); + } + ret = qce_aead_req(cp->qce, &qreq); + } + }; +done: + if (ret) { + + spin_lock_irqsave(&cp->lock, flags); + cp->req = NULL; + spin_unlock_irqrestore(&cp->lock, flags); + + if (type == CRYPTO_ALG_TYPE_ABLKCIPHER) + pstat->ablk_cipher_op_fail++; + else + if (type == CRYPTO_ALG_TYPE_AHASH) + pstat->sha_op_fail++; + else + pstat->aead_op_fail++; + + async_req->complete(async_req, ret); + goto again; + }; +}; + +static int _qcrypto_queue_req(struct crypto_priv *cp, + struct crypto_async_request *req) +{ + int ret; + unsigned long flags; + + if (cp->platform_support.ce_shared) { + ret = qcrypto_lock_ce(cp); + if (ret) + return ret; + } + + spin_lock_irqsave(&cp->lock, flags); + ret = crypto_enqueue_request(&cp->queue, req); + spin_unlock_irqrestore(&cp->lock, flags); + _start_qcrypto_process(cp); + + return ret; +} + +static int _qcrypto_enc_aes_ecb(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qcrypto_enc_aes_ecb: %p\n", req); +#endif + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_ECB; + + pstat->ablk_cipher_aes_enc++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_enc_aes_cbc(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qcrypto_enc_aes_cbc: %p\n", req); +#endif + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CBC; + + pstat->ablk_cipher_aes_enc++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_enc_aes_ctr(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qcrypto_enc_aes_ctr: %p\n", req); +#endif + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CTR; + + pstat->ablk_cipher_aes_enc++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_enc_aes_xts(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_XTS; + + pstat->ablk_cipher_aes_enc++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_aead_encrypt_aes_ccm(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + if ((ctx->authsize > 16) || (ctx->authsize < 4) || (ctx->authsize & 1)) + return -EINVAL; + if ((ctx->auth_key_len != AES_KEYSIZE_128) && + (ctx->auth_key_len != AES_KEYSIZE_256)) + return -EINVAL; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CCM; + rctx->iv = req->iv; + + pstat->aead_sha1_aes_enc++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_enc_des_ecb(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_DES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_ECB; + + pstat->ablk_cipher_des_enc++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_enc_des_cbc(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_DES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CBC; + + pstat->ablk_cipher_des_enc++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_enc_3des_ecb(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_3DES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_ECB; + + pstat->ablk_cipher_3des_enc++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_enc_3des_cbc(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_3DES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CBC; + + pstat->ablk_cipher_3des_enc++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_dec_aes_ecb(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qcrypto_dec_aes_ecb: %p\n", req); +#endif + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_ECB; + + pstat->ablk_cipher_aes_dec++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_dec_aes_cbc(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qcrypto_dec_aes_cbc: %p\n", req); +#endif + + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_CBC; + + pstat->ablk_cipher_aes_dec++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_dec_aes_ctr(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qcrypto_dec_aes_ctr: %p\n", req); +#endif + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_AES; + rctx->mode = QCE_MODE_CTR; + + /* Note. There is no such thing as aes/counter mode, decrypt */ + rctx->dir = QCE_ENCRYPT; + + pstat->ablk_cipher_aes_dec++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_dec_des_ecb(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_DES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_ECB; + + pstat->ablk_cipher_des_dec++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_dec_des_cbc(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_DES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_CBC; + + pstat->ablk_cipher_des_dec++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_dec_3des_ecb(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_3DES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_ECB; + + pstat->ablk_cipher_3des_dec++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_dec_3des_cbc(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_3DES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_CBC; + + pstat->ablk_cipher_3des_dec++; + return _qcrypto_queue_req(cp, &req->base); +}; + +static int _qcrypto_dec_aes_xts(struct ablkcipher_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + BUG_ON(crypto_tfm_alg_type(req->base.tfm) != + CRYPTO_ALG_TYPE_ABLKCIPHER); + rctx = ablkcipher_request_ctx(req); + rctx->aead = 0; + rctx->alg = CIPHER_ALG_AES; + rctx->mode = QCE_MODE_XTS; + rctx->dir = QCE_DECRYPT; + + pstat->ablk_cipher_aes_dec++; + return _qcrypto_queue_req(cp, &req->base); +}; + + +static int _qcrypto_aead_decrypt_aes_ccm(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + if ((ctx->authsize > 16) || (ctx->authsize < 4) || (ctx->authsize & 1)) + return -EINVAL; + if ((ctx->auth_key_len != AES_KEYSIZE_128) && + (ctx->auth_key_len != AES_KEYSIZE_256)) + return -EINVAL; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_CCM; + rctx->iv = req->iv; + + pstat->aead_sha1_aes_dec++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_aead_setauthsize(struct crypto_aead *authenc, + unsigned int authsize) +{ + struct qcrypto_cipher_ctx *ctx = crypto_aead_ctx(authenc); + + ctx->authsize = authsize; + return 0; +} + +static int _qcrypto_aead_ccm_setauthsize(struct crypto_aead *authenc, + unsigned int authsize) +{ + struct qcrypto_cipher_ctx *ctx = crypto_aead_ctx(authenc); + + switch (authsize) { + case 4: + case 6: + case 8: + case 10: + case 12: + case 14: + case 16: + break; + default: + return -EINVAL; + } + ctx->authsize = authsize; + return 0; +} + +static int _qcrypto_aead_setkey(struct crypto_aead *tfm, const u8 *key, + unsigned int keylen) +{ + struct qcrypto_cipher_ctx *ctx = crypto_aead_ctx(tfm); + struct rtattr *rta = (struct rtattr *)key; + struct crypto_authenc_key_param *param; + + if (!RTA_OK(rta, keylen)) + goto badkey; + if (rta->rta_type != CRYPTO_AUTHENC_KEYA_PARAM) + goto badkey; + if (RTA_PAYLOAD(rta) < sizeof(*param)) + goto badkey; + + param = RTA_DATA(rta); + ctx->enc_key_len = be32_to_cpu(param->enckeylen); + + key += RTA_ALIGN(rta->rta_len); + keylen -= RTA_ALIGN(rta->rta_len); + + if (keylen < ctx->enc_key_len) + goto badkey; + + ctx->auth_key_len = keylen - ctx->enc_key_len; + if (ctx->enc_key_len >= QCRYPTO_MAX_KEY_SIZE || + ctx->auth_key_len >= QCRYPTO_MAX_KEY_SIZE) + goto badkey; + memset(ctx->auth_key, 0, QCRYPTO_MAX_KEY_SIZE); + memcpy(ctx->enc_key, key + ctx->auth_key_len, ctx->enc_key_len); + memcpy(ctx->auth_key, key, ctx->auth_key_len); + + return 0; +badkey: + ctx->enc_key_len = 0; + crypto_aead_set_flags(tfm, CRYPTO_TFM_RES_BAD_KEY_LEN); + return -EINVAL; +} + +static int _qcrypto_aead_ccm_setkey(struct crypto_aead *aead, const u8 *key, + unsigned int keylen) +{ + struct crypto_tfm *tfm = crypto_aead_tfm(aead); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(tfm); + struct crypto_priv *cp = ctx->cp; + + switch (keylen) { + case AES_KEYSIZE_128: + case AES_KEYSIZE_256: + break; + case AES_KEYSIZE_192: + if (cp->ce_support.aes_key_192) + break; + default: + ctx->enc_key_len = 0; + crypto_aead_set_flags(aead, CRYPTO_TFM_RES_BAD_KEY_LEN); + return -EINVAL; + }; + ctx->enc_key_len = keylen; + memcpy(ctx->enc_key, key, keylen); + ctx->auth_key_len = keylen; + memcpy(ctx->auth_key, key, keylen); + + return 0; +} + +static int _qcrypto_aead_encrypt_aes_cbc(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qcrypto_aead_encrypt_aes_cbc: %p\n", req); +#endif + + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CBC; + rctx->iv = req->iv; + + pstat->aead_sha1_aes_enc++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_aead_decrypt_aes_cbc(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + +#ifdef QCRYPTO_DEBUG + dev_info(&cp->pdev->dev, "_qcrypto_aead_decrypt_aes_cbc: %p\n", req); +#endif + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_CBC; + rctx->iv = req->iv; + + pstat->aead_sha1_aes_dec++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_aead_givencrypt_aes_cbc(struct aead_givcrypt_request *req) +{ + struct aead_request *areq = &req->areq; + struct crypto_aead *authenc = crypto_aead_reqtfm(areq); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(areq->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct qcrypto_cipher_req_ctx *rctx; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(areq); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CBC; + rctx->iv = req->giv; /* generated iv */ + + memcpy(req->giv, ctx->iv, crypto_aead_ivsize(authenc)); + /* avoid consecutive packets going out with same IV */ + *(__be64 *)req->giv ^= cpu_to_be64(req->seq); + pstat->aead_sha1_aes_enc++; + return _qcrypto_queue_req(cp, &areq->base); +} + +#ifdef QCRYPTO_AEAD_AES_CTR +static int _qcrypto_aead_encrypt_aes_ctr(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CTR; + rctx->iv = req->iv; + + pstat->aead_sha1_aes_enc++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_aead_decrypt_aes_ctr(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_AES; + + /* Note. There is no such thing as aes/counter mode, decrypt */ + rctx->dir = QCE_ENCRYPT; + + rctx->mode = QCE_MODE_CTR; + rctx->iv = req->iv; + + pstat->aead_sha1_aes_dec++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_aead_givencrypt_aes_ctr(struct aead_givcrypt_request *req) +{ + struct aead_request *areq = &req->areq; + struct crypto_aead *authenc = crypto_aead_reqtfm(areq); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(areq->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct qcrypto_cipher_req_ctx *rctx; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(areq); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_AES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CTR; + rctx->iv = req->giv; /* generated iv */ + + memcpy(req->giv, ctx->iv, crypto_aead_ivsize(authenc)); + /* avoid consecutive packets going out with same IV */ + *(__be64 *)req->giv ^= cpu_to_be64(req->seq); + pstat->aead_sha1_aes_enc++; + return _qcrypto_queue_req(cp, &areq->base); +}; +#endif /* QCRYPTO_AEAD_AES_CTR */ + +static int _qcrypto_aead_encrypt_des_cbc(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_DES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CBC; + rctx->iv = req->iv; + + pstat->aead_sha1_des_enc++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_aead_decrypt_des_cbc(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_DES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_CBC; + rctx->iv = req->iv; + + pstat->aead_sha1_des_dec++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_aead_givencrypt_des_cbc(struct aead_givcrypt_request *req) +{ + struct aead_request *areq = &req->areq; + struct crypto_aead *authenc = crypto_aead_reqtfm(areq); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(areq->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct qcrypto_cipher_req_ctx *rctx; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(areq); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_DES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CBC; + rctx->iv = req->giv; /* generated iv */ + + memcpy(req->giv, ctx->iv, crypto_aead_ivsize(authenc)); + /* avoid consecutive packets going out with same IV */ + *(__be64 *)req->giv ^= cpu_to_be64(req->seq); + pstat->aead_sha1_des_enc++; + return _qcrypto_queue_req(cp, &areq->base); +} + +static int _qcrypto_aead_encrypt_3des_cbc(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_3DES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CBC; + rctx->iv = req->iv; + + pstat->aead_sha1_3des_enc++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_aead_decrypt_3des_cbc(struct aead_request *req) +{ + struct qcrypto_cipher_req_ctx *rctx; + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(req); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_3DES; + rctx->dir = QCE_DECRYPT; + rctx->mode = QCE_MODE_CBC; + rctx->iv = req->iv; + + pstat->aead_sha1_3des_dec++; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _qcrypto_aead_givencrypt_3des_cbc(struct aead_givcrypt_request *req) +{ + struct aead_request *areq = &req->areq; + struct crypto_aead *authenc = crypto_aead_reqtfm(areq); + struct qcrypto_cipher_ctx *ctx = crypto_tfm_ctx(areq->base.tfm); + struct crypto_priv *cp = ctx->cp; + struct qcrypto_cipher_req_ctx *rctx; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + rctx = aead_request_ctx(areq); + rctx->aead = 1; + rctx->alg = CIPHER_ALG_3DES; + rctx->dir = QCE_ENCRYPT; + rctx->mode = QCE_MODE_CBC; + rctx->iv = req->giv; /* generated iv */ + + memcpy(req->giv, ctx->iv, crypto_aead_ivsize(authenc)); + /* avoid consecutive packets going out with same IV */ + *(__be64 *)req->giv ^= cpu_to_be64(req->seq); + pstat->aead_sha1_3des_enc++; + return _qcrypto_queue_req(cp, &areq->base); +} + +static int qcrypto_count_sg(struct scatterlist *sg, int nbytes) +{ + int i; + + for (i = 0; nbytes > 0; i++, sg = sg_next(sg)) + nbytes -= sg->length; + + return i; +} + +static int _sha_init(struct qcrypto_sha_ctx *ctx) +{ + ctx->first_blk = 1; + ctx->last_blk = 0; + ctx->byte_count[0] = 0; + ctx->byte_count[1] = 0; + ctx->byte_count[2] = 0; + ctx->byte_count[3] = 0; + ctx->trailing_buf_len = 0; + + return 0; +}; + +static int _sha1_init(struct ahash_request *req) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + _sha_init(sha_ctx); + sha_ctx->alg = QCE_HASH_SHA1; + + memset(&sha_ctx->trailing_buf[0], 0x00, SHA1_BLOCK_SIZE); + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha1_uint8[0], + SHA1_DIGEST_SIZE); + sha_ctx->diglen = SHA1_DIGEST_SIZE; + _update_sha1_ctx(req); + + pstat->sha1_digest++; + return 0; +}; + +static int _sha256_init(struct ahash_request *req) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + + _sha_init(sha_ctx); + sha_ctx->alg = QCE_HASH_SHA256; + + memset(&sha_ctx->trailing_buf[0], 0x00, SHA256_BLOCK_SIZE); + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha256_uint8[0], + SHA256_DIGEST_SIZE); + sha_ctx->diglen = SHA256_DIGEST_SIZE; + _update_sha256_ctx(req); + + pstat->sha256_digest++; + return 0; +}; + + +static int _sha1_export(struct ahash_request *req, void *out) +{ + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct sha1_state *sha_state_ctx = &rctx->sha1_state_ctx; + struct sha1_state *out_ctx = (struct sha1_state *)out; + + out_ctx->count = sha_state_ctx->count; + memcpy(out_ctx->state, sha_state_ctx->state, sizeof(out_ctx->state)); + memcpy(out_ctx->buffer, sha_state_ctx->buffer, SHA1_BLOCK_SIZE); + + return 0; +}; + +static int _sha1_import(struct ahash_request *req, const void *in) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct sha1_state *sha_state_ctx = &rctx->sha1_state_ctx; + struct sha1_state *in_ctx = (struct sha1_state *)in; + + sha_state_ctx->count = in_ctx->count; + memcpy(sha_state_ctx->state, in_ctx->state, sizeof(in_ctx->state)); + memcpy(sha_state_ctx->buffer, in_ctx->buffer, SHA1_BLOCK_SIZE); + memcpy(sha_ctx->trailing_buf, in_ctx->buffer, SHA1_BLOCK_SIZE); + + sha_ctx->byte_count[0] = (uint32_t)(in_ctx->count & 0xFFFFFFC0); + sha_ctx->byte_count[1] = (uint32_t)(in_ctx->count >> 32); + _words_to_byte_stream(in_ctx->state, sha_ctx->digest, sha_ctx->diglen); + + sha_ctx->trailing_buf_len = (uint32_t)(in_ctx->count & + (SHA1_BLOCK_SIZE-1)); + + if (!(in_ctx->count)) + sha_ctx->first_blk = 1; + else + sha_ctx->first_blk = 0; + + return 0; +} +static int _sha256_export(struct ahash_request *req, void *out) +{ + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct sha256_state *sha_state_ctx = &rctx->sha256_state_ctx; + struct sha256_state *out_ctx = (struct sha256_state *)out; + + out_ctx->count = sha_state_ctx->count; + memcpy(out_ctx->state, sha_state_ctx->state, sizeof(out_ctx->state)); + memcpy(out_ctx->buf, sha_state_ctx->buf, SHA256_BLOCK_SIZE); + + return 0; +}; + +static int _sha256_import(struct ahash_request *req, const void *in) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct sha256_state *sha_state_ctx = &rctx->sha256_state_ctx; + struct sha256_state *in_ctx = (struct sha256_state *)in; + + sha_state_ctx->count = in_ctx->count; + memcpy(sha_state_ctx->state, in_ctx->state, sizeof(in_ctx->state)); + memcpy(sha_state_ctx->buf, in_ctx->buf, SHA256_BLOCK_SIZE); + memcpy(sha_ctx->trailing_buf, in_ctx->buf, SHA256_BLOCK_SIZE); + + sha_ctx->byte_count[0] = (uint32_t)(in_ctx->count & 0xFFFFFFC0); + sha_ctx->byte_count[1] = (uint32_t)(in_ctx->count >> 32); + _words_to_byte_stream(in_ctx->state, sha_ctx->digest, sha_ctx->diglen); + + sha_ctx->trailing_buf_len = (uint32_t)(in_ctx->count & + (SHA256_BLOCK_SIZE-1)); + + if (!(in_ctx->count)) + sha_ctx->first_blk = 1; + else + sha_ctx->first_blk = 0; + + return 0; +} + + +static int _sha_update(struct ahash_request *req, uint32_t sha_block_size) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + uint32_t total, len, i, num_sg; + uint8_t *k_src = NULL; + uint32_t sha_pad_len = 0; + uint32_t end_src = 0; + uint32_t trailing_buf_len = 0; + uint32_t nbytes, index = 0; + uint32_t saved_length = 0; + int ret = 0; + + /* check for trailing buffer from previous updates and append it */ + total = req->nbytes + sha_ctx->trailing_buf_len; + len = req->nbytes; + + if (total <= sha_block_size) { + i = 0; + + k_src = &sha_ctx->trailing_buf[sha_ctx->trailing_buf_len]; + while (len > 0) { + memcpy(k_src, sg_virt(&req->src[i]), + req->src[i].length); + len -= req->src[i].length; + k_src += req->src[i].length; + i++; + } + sha_ctx->trailing_buf_len = total; + if (sha_ctx->alg == QCE_HASH_SHA1) + _update_sha1_ctx(req); + if (sha_ctx->alg == QCE_HASH_SHA256) + _update_sha256_ctx(req); + return 0; + } + + /* save the original req structure fields*/ + rctx->src = req->src; + rctx->nbytes = req->nbytes; + + memcpy(sha_ctx->tmp_tbuf, sha_ctx->trailing_buf, + sha_ctx->trailing_buf_len); + k_src = &sha_ctx->trailing_buf[0]; + /* get new trailing buffer */ + sha_pad_len = ALIGN(total, sha_block_size) - total; + trailing_buf_len = sha_block_size - sha_pad_len; + nbytes = total - trailing_buf_len; + num_sg = qcrypto_count_sg(req->src, req->nbytes); + + len = sha_ctx->trailing_buf_len; + i = 0; + + while (len < nbytes) { + if ((len + req->src[i].length) > nbytes) + break; + len += req->src[i].length; + i++; + } + + end_src = i; + if (len < nbytes) { + uint32_t remnant = (nbytes - len); + memcpy(k_src, (sg_virt(&req->src[i]) + remnant), + (req->src[i].length - remnant)); + k_src += (req->src[i].length - remnant); + saved_length = req->src[i].length; + index = i; + req->src[i].length = remnant; + i++; + } + + while (i < num_sg) { + memcpy(k_src, sg_virt(&req->src[i]), req->src[i].length); + k_src += req->src[i].length; + i++; + } + + if (sha_ctx->trailing_buf_len) { + num_sg = end_src + 2; + sha_ctx->sg = kzalloc(num_sg * (sizeof(struct scatterlist)), + GFP_KERNEL); + if (sha_ctx->sg == NULL) { + pr_err("qcrypto Can't Allocate mem: sha_ctx->sg, error %ld\n", + PTR_ERR(sha_ctx->sg)); + return -ENOMEM; + } + + sg_set_buf(&sha_ctx->sg[0], sha_ctx->tmp_tbuf, + sha_ctx->trailing_buf_len); + for (i = 1; i < num_sg; i++) + sg_set_buf(&sha_ctx->sg[i], sg_virt(&req->src[i-1]), + req->src[i-1].length); + + req->src = sha_ctx->sg; + sg_mark_end(&sha_ctx->sg[num_sg - 1]); + } else + sg_mark_end(&req->src[end_src]); + + req->nbytes = nbytes; + if (saved_length > 0) + rctx->src[index].length = saved_length; + sha_ctx->trailing_buf_len = trailing_buf_len; + + ret = _qcrypto_queue_req(cp, &req->base); + + return ret; +}; + +static int _sha1_update(struct ahash_request *req) +{ + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct sha1_state *sha_state_ctx = &rctx->sha1_state_ctx; + + sha_state_ctx->count += req->nbytes; + return _sha_update(req, SHA1_BLOCK_SIZE); +} + +static int _sha256_update(struct ahash_request *req) +{ + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct sha256_state *sha_state_ctx = &rctx->sha256_state_ctx; + + sha_state_ctx->count += req->nbytes; + return _sha_update(req, SHA256_BLOCK_SIZE); +} + +static int _sha_final(struct ahash_request *req, uint32_t sha_block_size) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + int ret = 0; + + sha_ctx->last_blk = 1; + + /* save the original req structure fields*/ + rctx->src = req->src; + rctx->nbytes = req->nbytes; + + sg_set_buf(&sha_ctx->tmp_sg, sha_ctx->trailing_buf, + sha_ctx->trailing_buf_len); + sg_mark_end(&sha_ctx->tmp_sg); + + req->src = &sha_ctx->tmp_sg; + req->nbytes = sha_ctx->trailing_buf_len; + + ret = _qcrypto_queue_req(cp, &req->base); + + return ret; +}; + +static int _sha1_final(struct ahash_request *req) +{ + return _sha_final(req, SHA1_BLOCK_SIZE); +} + +static int _sha256_final(struct ahash_request *req) +{ + return _sha_final(req, SHA256_BLOCK_SIZE); +} + +static int _sha_digest(struct ahash_request *req) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct crypto_priv *cp = sha_ctx->cp; + int ret = 0; + + /* save the original req structure fields*/ + rctx->src = req->src; + rctx->nbytes = req->nbytes; + + sha_ctx->last_blk = 1; + ret = _qcrypto_queue_req(cp, &req->base); + + return ret; +} + +static int _sha1_digest(struct ahash_request *req) +{ + _sha1_init(req); + return _sha_digest(req); +} + +static int _sha256_digest(struct ahash_request *req) +{ + _sha256_init(req); + return _sha_digest(req); +} + +static void _crypto_sha_hmac_ahash_req_complete( + struct crypto_async_request *req, int err) +{ + struct completion *ahash_req_complete = req->data; + + if (err == -EINPROGRESS) + return; + complete(ahash_req_complete); +} + +static int _sha_hmac_setkey(struct crypto_ahash *tfm, const u8 *key, + unsigned int len) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(&tfm->base); + int ret = 0; + + sha_ctx->in_buf = kzalloc(len, GFP_KERNEL); + if (sha_ctx->in_buf == NULL) { + pr_err("qcrypto Can't Allocate mem: sha_ctx->in_buf, error %ld\n", + PTR_ERR(sha_ctx->in_buf)); + return -ENOMEM; + } + memcpy(sha_ctx->in_buf, key, len); + sg_set_buf(&sha_ctx->tmp_sg, sha_ctx->in_buf, len); + sg_mark_end(&sha_ctx->tmp_sg); + + ahash_request_set_crypt(sha_ctx->ahash_req, &sha_ctx->tmp_sg, + &sha_ctx->authkey[0], len); + + ret = _sha_digest(sha_ctx->ahash_req); + if (ret == -EINPROGRESS || ret == -EBUSY) { + ret = + wait_for_completion_interruptible( + &sha_ctx->ahash_req_complete); + INIT_COMPLETION(sha_ctx->ahash_req_complete); + } + + sha_ctx->authkey_in_len = len; + kfree(sha_ctx->in_buf); + sha_ctx->in_buf = NULL; + + return ret; +} + +static int _sha1_hmac_setkey(struct crypto_ahash *tfm, const u8 *key, + unsigned int len) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(&tfm->base); + + if (len <= SHA1_BLOCK_SIZE) + memcpy(&sha_ctx->authkey[0], key, len); + else { + _sha_init(sha_ctx); + sha_ctx->alg = QCE_HASH_SHA1; + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha1_uint8[0], + SHA1_DIGEST_SIZE); + sha_ctx->diglen = SHA1_DIGEST_SIZE; + _sha_hmac_setkey(tfm, key, len); + } + return 0; +} + +static int _sha256_hmac_setkey(struct crypto_ahash *tfm, const u8 *key, + unsigned int len) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(&tfm->base); + + if (len <= SHA256_BLOCK_SIZE) + memcpy(&sha_ctx->authkey[0], key, len); + else { + _sha_init(sha_ctx); + sha_ctx->alg = QCE_HASH_SHA256; + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha256_uint8[0], + SHA256_DIGEST_SIZE); + sha_ctx->diglen = SHA256_DIGEST_SIZE; + _sha_hmac_setkey(tfm, key, len); + } + + return 0; +} + +static int _sha_hmac_init_ihash(struct ahash_request *req, + uint32_t sha_block_size) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + int i; + + for (i = 0; i < sha_block_size; i++) + sha_ctx->trailing_buf[i] = sha_ctx->authkey[i] ^ 0x36; + sha_ctx->trailing_buf_len = sha_block_size; + + return 0; +} + +static int _sha1_hmac_init(struct ahash_request *req) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + struct crypto_stat *pstat; + int ret = 0; + + pstat = &_qcrypto_stat[cp->pdev->id]; + pstat->sha1_hmac_digest++; + + _sha_init(sha_ctx); + memset(&sha_ctx->trailing_buf[0], 0x00, SHA1_BLOCK_SIZE); + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha1_uint8[0], + SHA1_DIGEST_SIZE); + sha_ctx->diglen = SHA1_DIGEST_SIZE; + _update_sha1_ctx(req); + + if (cp->ce_support.sha_hmac) + sha_ctx->alg = QCE_HASH_SHA1_HMAC; + else { + sha_ctx->alg = QCE_HASH_SHA1; + ret = _sha_hmac_init_ihash(req, SHA1_BLOCK_SIZE); + } + + return ret; +} + +static int _sha256_hmac_init(struct ahash_request *req) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + struct crypto_stat *pstat; + int ret = 0; + + pstat = &_qcrypto_stat[cp->pdev->id]; + pstat->sha256_hmac_digest++; + + _sha_init(sha_ctx); + memset(&sha_ctx->trailing_buf[0], 0x00, SHA256_BLOCK_SIZE); + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha256_uint8[0], + SHA256_DIGEST_SIZE); + sha_ctx->diglen = SHA256_DIGEST_SIZE; + _update_sha256_ctx(req); + + if (cp->ce_support.sha_hmac) + sha_ctx->alg = QCE_HASH_SHA256_HMAC; + else { + sha_ctx->alg = QCE_HASH_SHA256; + ret = _sha_hmac_init_ihash(req, SHA256_BLOCK_SIZE); + } + + return ret; +} + +static int _sha1_hmac_update(struct ahash_request *req) +{ + return _sha1_update(req); +} + +static int _sha256_hmac_update(struct ahash_request *req) +{ + return _sha256_update(req); +} + +static int _sha_hmac_outer_hash(struct ahash_request *req, + uint32_t sha_digest_size, uint32_t sha_block_size) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct qcrypto_sha_req_ctx *rctx = ahash_request_ctx(req); + struct crypto_priv *cp = sha_ctx->cp; + int i; + + for (i = 0; i < sha_block_size; i++) + sha_ctx->tmp_tbuf[i] = sha_ctx->authkey[i] ^ 0x5c; + + /* save the original req structure fields*/ + rctx->src = req->src; + rctx->nbytes = req->nbytes; + + memcpy(&sha_ctx->tmp_tbuf[sha_block_size], &sha_ctx->digest[0], + sha_digest_size); + + sg_set_buf(&sha_ctx->tmp_sg, sha_ctx->tmp_tbuf, sha_block_size + + sha_digest_size); + sg_mark_end(&sha_ctx->tmp_sg); + req->src = &sha_ctx->tmp_sg; + req->nbytes = sha_block_size + sha_digest_size; + + _sha_init(sha_ctx); + if (sha_ctx->alg == QCE_HASH_SHA1) { + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha1_uint8[0], + SHA1_DIGEST_SIZE); + sha_ctx->diglen = SHA1_DIGEST_SIZE; + } else { + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha256_uint8[0], + SHA256_DIGEST_SIZE); + sha_ctx->diglen = SHA256_DIGEST_SIZE; + } + + sha_ctx->last_blk = 1; + return _qcrypto_queue_req(cp, &req->base); +} + +static int _sha_hmac_inner_hash(struct ahash_request *req, + uint32_t sha_digest_size, uint32_t sha_block_size) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct ahash_request *areq = sha_ctx->ahash_req; + struct crypto_priv *cp = sha_ctx->cp; + int ret = 0; + + sha_ctx->last_blk = 1; + + sg_set_buf(&sha_ctx->tmp_sg, sha_ctx->trailing_buf, + sha_ctx->trailing_buf_len); + sg_mark_end(&sha_ctx->tmp_sg); + + ahash_request_set_crypt(areq, &sha_ctx->tmp_sg, &sha_ctx->digest[0], + sha_ctx->trailing_buf_len); + sha_ctx->last_blk = 1; + ret = _qcrypto_queue_req(cp, &areq->base); + + if (ret == -EINPROGRESS || ret == -EBUSY) { + ret = + wait_for_completion_interruptible(&sha_ctx->ahash_req_complete); + INIT_COMPLETION(sha_ctx->ahash_req_complete); + } + + return ret; +} + +static int _sha1_hmac_final(struct ahash_request *req) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + int ret = 0; + + if (cp->ce_support.sha_hmac) + return _sha_final(req, SHA1_BLOCK_SIZE); + else { + ret = _sha_hmac_inner_hash(req, SHA1_DIGEST_SIZE, + SHA1_BLOCK_SIZE); + if (ret) + return ret; + return _sha_hmac_outer_hash(req, SHA1_DIGEST_SIZE, + SHA1_BLOCK_SIZE); + } +} + +static int _sha256_hmac_final(struct ahash_request *req) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + int ret = 0; + + if (cp->ce_support.sha_hmac) + return _sha_final(req, SHA256_BLOCK_SIZE); + else { + ret = _sha_hmac_inner_hash(req, SHA256_DIGEST_SIZE, + SHA256_BLOCK_SIZE); + if (ret) + return ret; + return _sha_hmac_outer_hash(req, SHA256_DIGEST_SIZE, + SHA256_BLOCK_SIZE); + } + return 0; +} + + +static int _sha1_hmac_digest(struct ahash_request *req) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + pstat->sha1_hmac_digest++; + + _sha_init(sha_ctx); + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha1_uint8[0], + SHA1_DIGEST_SIZE); + sha_ctx->diglen = SHA1_DIGEST_SIZE; + sha_ctx->alg = QCE_HASH_SHA1_HMAC; + + return _sha_digest(req); +} + +static int _sha256_hmac_digest(struct ahash_request *req) +{ + struct qcrypto_sha_ctx *sha_ctx = crypto_tfm_ctx(req->base.tfm); + struct crypto_priv *cp = sha_ctx->cp; + struct crypto_stat *pstat; + + pstat = &_qcrypto_stat[cp->pdev->id]; + pstat->sha256_hmac_digest++; + + _sha_init(sha_ctx); + memcpy(&sha_ctx->digest[0], &_std_init_vector_sha256_uint8[0], + SHA256_DIGEST_SIZE); + sha_ctx->diglen = SHA256_DIGEST_SIZE; + sha_ctx->alg = QCE_HASH_SHA256_HMAC; + + return _sha_digest(req); +} + +static struct ahash_alg _qcrypto_ahash_algos[] = { + { + .init = _sha1_init, + .update = _sha1_update, + .final = _sha1_final, + .export = _sha1_export, + .import = _sha1_import, + .digest = _sha1_digest, + .halg = { + .digestsize = SHA1_DIGEST_SIZE, + .statesize = sizeof(struct sha1_state), + .base = { + .cra_name = "sha1", + .cra_driver_name = "qcrypto-sha1", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AHASH | + CRYPTO_ALG_ASYNC, + .cra_blocksize = SHA1_BLOCK_SIZE, + .cra_ctxsize = + sizeof(struct qcrypto_sha_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ahash_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_ahash_cra_init, + .cra_exit = _qcrypto_ahash_cra_exit, + }, + }, + }, + { + .init = _sha256_init, + .update = _sha256_update, + .final = _sha256_final, + .export = _sha256_export, + .import = _sha256_import, + .digest = _sha256_digest, + .halg = { + .digestsize = SHA256_DIGEST_SIZE, + .statesize = sizeof(struct sha256_state), + .base = { + .cra_name = "sha256", + .cra_driver_name = "qcrypto-sha256", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AHASH | + CRYPTO_ALG_ASYNC, + .cra_blocksize = SHA256_BLOCK_SIZE, + .cra_ctxsize = + sizeof(struct qcrypto_sha_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ahash_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_ahash_cra_init, + .cra_exit = _qcrypto_ahash_cra_exit, + }, + }, + }, +}; + +static struct ahash_alg _qcrypto_sha_hmac_algos[] = { + { + .init = _sha1_hmac_init, + .update = _sha1_hmac_update, + .final = _sha1_hmac_final, + .export = _sha1_export, + .import = _sha1_import, + .digest = _sha1_hmac_digest, + .setkey = _sha1_hmac_setkey, + .halg = { + .digestsize = SHA1_DIGEST_SIZE, + .statesize = sizeof(struct sha1_state), + .base = { + .cra_name = "hmac(sha1)", + .cra_driver_name = "qcrypto-hmac-sha1", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AHASH | + CRYPTO_ALG_ASYNC, + .cra_blocksize = SHA1_BLOCK_SIZE, + .cra_ctxsize = + sizeof(struct qcrypto_sha_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ahash_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_ahash_hmac_cra_init, + .cra_exit = _qcrypto_ahash_cra_exit, + }, + }, + }, + { + .init = _sha256_hmac_init, + .update = _sha256_hmac_update, + .final = _sha256_hmac_final, + .export = _sha256_export, + .import = _sha256_import, + .digest = _sha256_hmac_digest, + .setkey = _sha256_hmac_setkey, + .halg = { + .digestsize = SHA256_DIGEST_SIZE, + .statesize = sizeof(struct sha256_state), + .base = { + .cra_name = "hmac(sha256)", + .cra_driver_name = "qcrypto-hmac-sha256", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AHASH | + CRYPTO_ALG_ASYNC, + .cra_blocksize = SHA256_BLOCK_SIZE, + .cra_ctxsize = + sizeof(struct qcrypto_sha_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ahash_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_ahash_hmac_cra_init, + .cra_exit = _qcrypto_ahash_cra_exit, + }, + }, + }, +}; + +static struct crypto_alg _qcrypto_ablk_cipher_algos[] = { + { + .cra_name = "ecb(aes)", + .cra_driver_name = "qcrypto-ecb-aes", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ablkcipher_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_ablkcipher_init, + .cra_u = { + .ablkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = _qcrypto_setkey_aes, + .encrypt = _qcrypto_enc_aes_ecb, + .decrypt = _qcrypto_dec_aes_ecb, + }, + }, + }, + { + .cra_name = "cbc(aes)", + .cra_driver_name = "qcrypto-cbc-aes", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ablkcipher_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_ablkcipher_init, + .cra_u = { + .ablkcipher = { + .ivsize = AES_BLOCK_SIZE, + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = _qcrypto_setkey_aes, + .encrypt = _qcrypto_enc_aes_cbc, + .decrypt = _qcrypto_dec_aes_cbc, + }, + }, + }, + { + .cra_name = "ctr(aes)", + .cra_driver_name = "qcrypto-ctr-aes", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ablkcipher_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_ablkcipher_init, + .cra_u = { + .ablkcipher = { + .ivsize = AES_BLOCK_SIZE, + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = _qcrypto_setkey_aes, + .encrypt = _qcrypto_enc_aes_ctr, + .decrypt = _qcrypto_dec_aes_ctr, + }, + }, + }, + { + .cra_name = "ecb(des)", + .cra_driver_name = "qcrypto-ecb-des", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, + .cra_blocksize = DES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ablkcipher_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_ablkcipher_init, + .cra_u = { + .ablkcipher = { + .min_keysize = DES_KEY_SIZE, + .max_keysize = DES_KEY_SIZE, + .setkey = _qcrypto_setkey_des, + .encrypt = _qcrypto_enc_des_ecb, + .decrypt = _qcrypto_dec_des_ecb, + }, + }, + }, + { + .cra_name = "cbc(des)", + .cra_driver_name = "qcrypto-cbc-des", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, + .cra_blocksize = DES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ablkcipher_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_ablkcipher_init, + .cra_u = { + .ablkcipher = { + .ivsize = DES_BLOCK_SIZE, + .min_keysize = DES_KEY_SIZE, + .max_keysize = DES_KEY_SIZE, + .setkey = _qcrypto_setkey_des, + .encrypt = _qcrypto_enc_des_cbc, + .decrypt = _qcrypto_dec_des_cbc, + }, + }, + }, + { + .cra_name = "ecb(des3_ede)", + .cra_driver_name = "qcrypto-ecb-3des", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, + .cra_blocksize = DES3_EDE_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ablkcipher_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_ablkcipher_init, + .cra_u = { + .ablkcipher = { + .min_keysize = DES3_EDE_KEY_SIZE, + .max_keysize = DES3_EDE_KEY_SIZE, + .setkey = _qcrypto_setkey_3des, + .encrypt = _qcrypto_enc_3des_ecb, + .decrypt = _qcrypto_dec_3des_ecb, + }, + }, + }, + { + .cra_name = "cbc(des3_ede)", + .cra_driver_name = "qcrypto-cbc-3des", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, + .cra_blocksize = DES3_EDE_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ablkcipher_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_ablkcipher_init, + .cra_u = { + .ablkcipher = { + .ivsize = DES3_EDE_BLOCK_SIZE, + .min_keysize = DES3_EDE_KEY_SIZE, + .max_keysize = DES3_EDE_KEY_SIZE, + .setkey = _qcrypto_setkey_3des, + .encrypt = _qcrypto_enc_3des_cbc, + .decrypt = _qcrypto_dec_3des_cbc, + }, + }, + }, +}; + +static struct crypto_alg _qcrypto_ablk_cipher_xts_algo = { + .cra_name = "xts(aes)", + .cra_driver_name = "qcrypto-xts-aes", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_ablkcipher_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_ablkcipher_init, + .cra_u = { + .ablkcipher = { + .ivsize = AES_BLOCK_SIZE, + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = _qcrypto_setkey_aes, + .encrypt = _qcrypto_enc_aes_xts, + .decrypt = _qcrypto_dec_aes_xts, + }, + }, +}; + +static struct crypto_alg _qcrypto_aead_sha1_hmac_algos[] = { + { + .cra_name = "authenc(hmac(sha1),cbc(aes))", + .cra_driver_name = "qcrypto-aead-hmac-sha1-cbc-aes", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AEAD | CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_aead_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_aead_init, + .cra_u = { + .aead = { + .ivsize = AES_BLOCK_SIZE, + .maxauthsize = SHA1_DIGEST_SIZE, + .setkey = _qcrypto_aead_setkey, + .setauthsize = _qcrypto_aead_setauthsize, + .encrypt = _qcrypto_aead_encrypt_aes_cbc, + .decrypt = _qcrypto_aead_decrypt_aes_cbc, + .givencrypt = _qcrypto_aead_givencrypt_aes_cbc, + .geniv = "", + } + } + }, + +#ifdef QCRYPTO_AEAD_AES_CTR + { + .cra_name = "authenc(hmac(sha1),ctr(aes))", + .cra_driver_name = "qcrypto-aead-hmac-sha1-ctr-aes", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AEAD | CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_aead_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_aead_init, + .cra_u = { + .aead = { + .ivsize = AES_BLOCK_SIZE, + .maxauthsize = SHA1_DIGEST_SIZE, + .setkey = _qcrypto_aead_setkey, + .setauthsize = _qcrypto_aead_setauthsize, + .encrypt = _qcrypto_aead_encrypt_aes_ctr, + .decrypt = _qcrypto_aead_decrypt_aes_ctr, + .givencrypt = _qcrypto_aead_givencrypt_aes_ctr, + .geniv = "", + } + } + }, +#endif /* QCRYPTO_AEAD_AES_CTR */ + { + .cra_name = "authenc(hmac(sha1),cbc(des))", + .cra_driver_name = "qcrypto-aead-hmac-sha1-cbc-des", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AEAD | CRYPTO_ALG_ASYNC, + .cra_blocksize = DES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_aead_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_aead_init, + .cra_u = { + .aead = { + .ivsize = DES_BLOCK_SIZE, + .maxauthsize = SHA1_DIGEST_SIZE, + .setkey = _qcrypto_aead_setkey, + .setauthsize = _qcrypto_aead_setauthsize, + .encrypt = _qcrypto_aead_encrypt_des_cbc, + .decrypt = _qcrypto_aead_decrypt_des_cbc, + .givencrypt = _qcrypto_aead_givencrypt_des_cbc, + .geniv = "", + } + } + }, + { + .cra_name = "authenc(hmac(sha1),cbc(des3_ede))", + .cra_driver_name = "qcrypto-aead-hmac-sha1-cbc-3des", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AEAD | CRYPTO_ALG_ASYNC, + .cra_blocksize = DES3_EDE_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_aead_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_aead_init, + .cra_u = { + .aead = { + .ivsize = DES3_EDE_BLOCK_SIZE, + .maxauthsize = SHA1_DIGEST_SIZE, + .setkey = _qcrypto_aead_setkey, + .setauthsize = _qcrypto_aead_setauthsize, + .encrypt = _qcrypto_aead_encrypt_3des_cbc, + .decrypt = _qcrypto_aead_decrypt_3des_cbc, + .givencrypt = _qcrypto_aead_givencrypt_3des_cbc, + .geniv = "", + } + } + }, +}; + +static struct crypto_alg _qcrypto_aead_ccm_algo = { + .cra_name = "ccm(aes)", + .cra_driver_name = "qcrypto-aes-ccm", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AEAD | CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct qcrypto_cipher_ctx), + .cra_alignmask = 0, + .cra_type = &crypto_aead_type, + .cra_module = THIS_MODULE, + .cra_init = _qcrypto_cra_aead_init, + .cra_u = { + .aead = { + .ivsize = AES_BLOCK_SIZE, + .maxauthsize = SHA1_DIGEST_SIZE, + .setkey = _qcrypto_aead_ccm_setkey, + .setauthsize = _qcrypto_aead_ccm_setauthsize, + .encrypt = _qcrypto_aead_encrypt_aes_ccm, + .decrypt = _qcrypto_aead_decrypt_aes_ccm, + .geniv = "", + } + } +}; + + +static int _qcrypto_probe(struct platform_device *pdev) +{ + int rc = 0; + void *handle; + struct crypto_priv *cp; + int i; + struct msm_ce_hw_support *platform_support; + + if (pdev->id >= MAX_CRYPTO_DEVICE) { + printk(KERN_ERR "%s: device id %d exceeds allowed %d\n", + __func__, pdev->id, MAX_CRYPTO_DEVICE); + return -ENOENT; + } + + cp = kzalloc(sizeof(*cp), GFP_KERNEL); + if (!cp) { + pr_err("qcrypto Memory allocation of q_alg FAIL, error %ld\n", + PTR_ERR(cp)); + return -ENOMEM; + } + + /* open qce */ + handle = qce_open(pdev, &rc); + if (handle == NULL) { + kfree(cp); + platform_set_drvdata(pdev, NULL); + return rc; + } + + INIT_LIST_HEAD(&cp->alg_list); + platform_set_drvdata(pdev, cp); + spin_lock_init(&cp->lock); + tasklet_init(&cp->done_tasklet, req_done, (unsigned long)cp); + crypto_init_queue(&cp->queue, 50); + cp->qce = handle; + cp->pdev = pdev; + qce_hw_support(cp->qce, &cp->ce_support); + platform_support = (struct msm_ce_hw_support *)pdev->dev.platform_data; + cp->platform_support.ce_shared = platform_support->ce_shared; + cp->platform_support.shared_ce_resource = + platform_support->shared_ce_resource; + cp->platform_support.hw_key_support = + platform_support->hw_key_support; + cp->ce_lock_count = 0; + cp->platform_support.sha_hmac = platform_support->sha_hmac; + + if (cp->platform_support.ce_shared) + INIT_WORK(&cp->unlock_ce_ws, qcrypto_unlock_ce); + + /* register crypto cipher algorithms the device supports */ + for (i = 0; i < ARRAY_SIZE(_qcrypto_ablk_cipher_algos); i++) { + struct qcrypto_alg *q_alg; + + q_alg = _qcrypto_cipher_alg_alloc(cp, + &_qcrypto_ablk_cipher_algos[i]); + if (IS_ERR(q_alg)) { + rc = PTR_ERR(q_alg); + goto err; + } + rc = crypto_register_alg(&q_alg->cipher_alg); + if (rc) { + dev_err(&pdev->dev, "%s alg registration failed\n", + q_alg->cipher_alg.cra_driver_name); + kfree(q_alg); + } else { + list_add_tail(&q_alg->entry, &cp->alg_list); + dev_info(&pdev->dev, "%s\n", + q_alg->cipher_alg.cra_driver_name); + } + } + + /* register crypto cipher algorithms the device supports */ + if (cp->ce_support.aes_xts) { + struct qcrypto_alg *q_alg; + + q_alg = _qcrypto_cipher_alg_alloc(cp, + &_qcrypto_ablk_cipher_xts_algo); + if (IS_ERR(q_alg)) { + rc = PTR_ERR(q_alg); + goto err; + } + rc = crypto_register_alg(&q_alg->cipher_alg); + if (rc) { + dev_err(&pdev->dev, "%s alg registration failed\n", + q_alg->cipher_alg.cra_driver_name); + kfree(q_alg); + } else { + list_add_tail(&q_alg->entry, &cp->alg_list); + dev_info(&pdev->dev, "%s\n", + q_alg->cipher_alg.cra_driver_name); + } + } + + /* + * Register crypto hash (sha1 and sha256) algorithms the + * device supports + */ + for (i = 0; i < ARRAY_SIZE(_qcrypto_ahash_algos); i++) { + struct qcrypto_alg *q_alg = NULL; + + q_alg = _qcrypto_sha_alg_alloc(cp, &_qcrypto_ahash_algos[i]); + + if (IS_ERR(q_alg)) { + rc = PTR_ERR(q_alg); + goto err; + } + + rc = crypto_register_ahash(&q_alg->sha_alg); + if (rc) { + dev_err(&pdev->dev, "%s alg registration failed\n", + q_alg->sha_alg.halg.base.cra_driver_name); + kfree(q_alg); + } else { + list_add_tail(&q_alg->entry, &cp->alg_list); + dev_info(&pdev->dev, "%s\n", + q_alg->sha_alg.halg.base.cra_driver_name); + } + } + + /* register crypto aead (hmac-sha1) algorithms the device supports */ + if (cp->ce_support.sha1_hmac_20 || cp->ce_support.sha1_hmac) { + for (i = 0; i < ARRAY_SIZE(_qcrypto_aead_sha1_hmac_algos); + i++) { + struct qcrypto_alg *q_alg; + + q_alg = _qcrypto_cipher_alg_alloc(cp, + &_qcrypto_aead_sha1_hmac_algos[i]); + if (IS_ERR(q_alg)) { + rc = PTR_ERR(q_alg); + goto err; + } + + rc = crypto_register_alg(&q_alg->cipher_alg); + if (rc) { + dev_err(&pdev->dev, + "%s alg registration failed\n", + q_alg->cipher_alg.cra_driver_name); + kfree(q_alg); + } else { + list_add_tail(&q_alg->entry, &cp->alg_list); + dev_info(&pdev->dev, "%s\n", + q_alg->cipher_alg.cra_driver_name); + } + } + } + + if ((cp->ce_support.sha_hmac) || (cp->platform_support.sha_hmac)) { + /* register crypto hmac algorithms the device supports */ + for (i = 0; i < ARRAY_SIZE(_qcrypto_sha_hmac_algos); i++) { + struct qcrypto_alg *q_alg = NULL; + + q_alg = _qcrypto_sha_alg_alloc(cp, + &_qcrypto_sha_hmac_algos[i]); + + if (IS_ERR(q_alg)) { + rc = PTR_ERR(q_alg); + goto err; + } + + rc = crypto_register_ahash(&q_alg->sha_alg); + if (rc) { + dev_err(&pdev->dev, + "%s alg registration failed\n", + q_alg->sha_alg.halg.base.cra_driver_name); + kfree(q_alg); + } else { + list_add_tail(&q_alg->entry, &cp->alg_list); + dev_info(&pdev->dev, "%s\n", + q_alg->sha_alg.halg.base.cra_driver_name); + } + } + } + /* + * Register crypto cipher (aes-ccm) algorithms the + * device supports + */ + if (cp->ce_support.aes_ccm) { + struct qcrypto_alg *q_alg; + + q_alg = _qcrypto_cipher_alg_alloc(cp, &_qcrypto_aead_ccm_algo); + if (IS_ERR(q_alg)) { + rc = PTR_ERR(q_alg); + goto err; + } + rc = crypto_register_alg(&q_alg->cipher_alg); + if (rc) { + dev_err(&pdev->dev, "%s alg registration failed\n", + q_alg->cipher_alg.cra_driver_name); + kfree(q_alg); + } else { + list_add_tail(&q_alg->entry, &cp->alg_list); + dev_info(&pdev->dev, "%s\n", + q_alg->cipher_alg.cra_driver_name); + } + } + + return 0; +err: + _qcrypto_remove(pdev); + return rc; +}; + +static struct platform_driver _qualcomm_crypto = { + .probe = _qcrypto_probe, + .remove = _qcrypto_remove, + .driver = { + .owner = THIS_MODULE, + .name = "qcrypto", + }, +}; + +static int _debug_qcrypto[MAX_CRYPTO_DEVICE]; + +static int _debug_stats_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t _debug_stats_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int rc = -EINVAL; + int qcrypto = *((int *) file->private_data); + int len; + + len = _disp_stats(qcrypto); + + rc = simple_read_from_buffer((void __user *) buf, len, + ppos, (void *) _debug_read_buf, len); + + return rc; +} + +static ssize_t _debug_stats_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + + int qcrypto = *((int *) file->private_data); + + memset((char *)&_qcrypto_stat[qcrypto], 0, sizeof(struct crypto_stat)); + return count; +}; + +static const struct file_operations _debug_stats_ops = { + .open = _debug_stats_open, + .read = _debug_stats_read, + .write = _debug_stats_write, +}; + +static int _qcrypto_debug_init(void) +{ + int rc; + char name[DEBUG_MAX_FNAME]; + int i; + struct dentry *dent; + + _debug_dent = debugfs_create_dir("qcrypto", NULL); + if (IS_ERR(_debug_dent)) { + pr_err("qcrypto debugfs_create_dir fail, error %ld\n", + PTR_ERR(_debug_dent)); + return PTR_ERR(_debug_dent); + } + + for (i = 0; i < MAX_CRYPTO_DEVICE; i++) { + snprintf(name, DEBUG_MAX_FNAME-1, "stats-%d", i+1); + _debug_qcrypto[i] = i; + dent = debugfs_create_file(name, 0644, _debug_dent, + &_debug_qcrypto[i], &_debug_stats_ops); + if (dent == NULL) { + pr_err("qcrypto debugfs_create_file fail, error %ld\n", + PTR_ERR(dent)); + rc = PTR_ERR(dent); + goto err; + } + } + return 0; +err: + debugfs_remove_recursive(_debug_dent); + return rc; +} + +static int __init _qcrypto_init(void) +{ + int rc; + + rc = _qcrypto_debug_init(); + if (rc) + return rc; + + return platform_driver_register(&_qualcomm_crypto); +} + +static void __exit _qcrypto_exit(void) +{ + printk(KERN_ALERT "%s Unregister QCRYPTO\n", __func__); + debugfs_remove_recursive(_debug_dent); + platform_driver_unregister(&_qualcomm_crypto); +} + +module_init(_qcrypto_init); +module_exit(_qcrypto_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Mona Hossain "); +MODULE_DESCRIPTION("Qualcomm Crypto driver"); +MODULE_VERSION("1.18"); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 2967002a9f8..385e9c70588 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -453,4 +453,38 @@ config GPIO_TPS65910 help Select this option to enable GPIO driver for the TPS65910 chip family. + +config MPP_PMIC8901 + tristate "Qualcomm PMIC8901 MPP" + depends on GPIOLIB && PMIC8901 + default y + help + Say yes here to support GPIO functionality on Qualcomm's + PM8901 chip for MPP(Multi-Purpose Pin) pins. These pins + work like GPIO pins when configured as digital input and/or + output signals. + +config GPIO_PM8XXX + tristate "Qualcomm PM8xxx GPIO support" + depends on MFD_PM8XXX + default y if MFD_PM8XXX + help + This option enables support for on-chip GPIO found on Qualcomm PM8xxx + PMICs. + +config GPIO_PM8XXX_MPP + tristate "Support for Qualcomm PM8xxx MPP features" + depends on MFD_PM8XXX + default y if MFD_PM8XXX + help + This is the multi-purpose pin (MPP) driver for Qualcomm PM 8xxx PMIC + chips. + +config GPIO_PM8XXX_RPC + tristate "Qualcomm PM8xxx RPC based GPIO support" + depends on MSM_SMD + help + This option enables support for on-chip GPIO found on Qualcomm PM8xxx + PMICs through RPC. + endif diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index b605f8ec6fb..1b2c4b194d5 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -48,3 +48,7 @@ obj-$(CONFIG_GPIO_VX855) += vx855_gpio.o obj-$(CONFIG_GPIO_ML_IOH) += ml_ioh_gpio.o obj-$(CONFIG_AB8500_GPIO) += ab8500-gpio.o obj-$(CONFIG_GPIO_TPS65910) += tps65910-gpio.o +obj-$(CONFIG_MPP_PMIC8901) += pmic8901-mpp.o +obj-$(CONFIG_GPIO_PM8XXX) += pm8xxx-gpio.o +obj-$(CONFIG_GPIO_PM8XXX_MPP) += pm8xxx-mpp.o +obj-$(CONFIG_GPIO_PM8XXX_RPC) += gpio-pm8xxx-rpc.o diff --git a/drivers/gpio/gpio-pm8xxx-rpc.c b/drivers/gpio/gpio-pm8xxx-rpc.c new file mode 100644 index 00000000000..1acc7411b29 --- /dev/null +++ b/drivers/gpio/gpio-pm8xxx-rpc.c @@ -0,0 +1,241 @@ +/* + * Qualcomm PMIC8XXX GPIO driver based on RPC + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct pm8xxx_gpio_rpc_chip { + struct list_head link; + struct gpio_chip gpio_chip; +}; + +static LIST_HEAD(pm8xxx_gpio_rpc_chips); +static DEFINE_MUTEX(pm8xxx_gpio_chips_lock); + +static int pm8xxx_gpio_rpc_get(struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip, + unsigned gpio) +{ + int rc; + + if (gpio >= pm8xxx_gpio_chip->gpio_chip.ngpio + || pm8xxx_gpio_chip == NULL) + return -EINVAL; + + rc = pmic_gpio_get_value(gpio); + + return rc; +} + +static int pm8xxx_gpio_rpc_set(struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip, + unsigned gpio, int value) +{ + int rc; + + if (gpio >= pm8xxx_gpio_chip->gpio_chip.ngpio || + pm8xxx_gpio_chip == NULL) + return -EINVAL; + + rc = pmic_gpio_set_value(gpio, value); + + return rc; +} + +static int pm8xxx_gpio_rpc_set_direction(struct pm8xxx_gpio_rpc_chip + *pm8xxx_gpio_chip, unsigned gpio, int direction) +{ + int rc = 0; + + if (!direction || pm8xxx_gpio_chip == NULL) + return -EINVAL; + + if (direction == PM_GPIO_DIR_IN) + rc = pmic_gpio_direction_input(gpio); + else if (direction == PM_GPIO_DIR_OUT) + rc = pmic_gpio_direction_output(gpio); + + return rc; +} + +static int pm8xxx_gpio_rpc_read(struct gpio_chip *gpio_chip, unsigned offset) +{ + struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = + dev_get_drvdata(gpio_chip->dev); + + return pm8xxx_gpio_rpc_get(pm8xxx_gpio_chip, offset); +} + +static void pm8xxx_gpio_rpc_write(struct gpio_chip *gpio_chip, + unsigned offset, int val) +{ + struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = + dev_get_drvdata(gpio_chip->dev); + + pm8xxx_gpio_rpc_set(pm8xxx_gpio_chip, offset, !!val); +} + +static int pm8xxx_gpio_rpc_direction_input(struct gpio_chip *gpio_chip, + unsigned offset) +{ + struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = + dev_get_drvdata(gpio_chip->dev); + + return pm8xxx_gpio_rpc_set_direction(pm8xxx_gpio_chip, offset, + PM_GPIO_DIR_IN); +} + +static int pm8xxx_gpio_rpc_direction_output(struct gpio_chip *gpio_chip, + unsigned offset, int val) +{ + int ret = 0; + + struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = + dev_get_drvdata(gpio_chip->dev); + + ret = pm8xxx_gpio_rpc_set_direction(pm8xxx_gpio_chip, offset, + PM_GPIO_DIR_OUT); + if (!ret) + ret = pm8xxx_gpio_rpc_set(pm8xxx_gpio_chip, offset, !!val); + + return ret; +} + +static void pm8xxx_gpio_rpc_dbg_show(struct seq_file *s, struct gpio_chip + *gpio_chip) +{ + struct pm8xxx_gpio_rpc_chip *pmxx_gpio_chip = + dev_get_drvdata(gpio_chip->dev); + u8 state, mode; + const char *label; + int i; + + for (i = 0; i < gpio_chip->ngpio; i++) { + label = gpiochip_is_requested(gpio_chip, i); + state = pm8xxx_gpio_rpc_get(pmxx_gpio_chip, i); + mode = pmic_gpio_get_direction(i); + seq_printf(s, "gpio-%-3d (%-12.12s) %s %s", + gpio_chip->base + i, + label ? label : " ", mode ? "out" : "in", + state ? "hi" : "lo"); + seq_printf(s, "\n"); + } +} + +static int __devinit pm8xxx_gpio_rpc_probe(struct platform_device *pdev) +{ + int ret; + struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip; + const struct pm8xxx_gpio_rpc_platform_data *pdata = + pdev->dev.platform_data; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + pm8xxx_gpio_chip = kzalloc(sizeof(struct pm8xxx_gpio_rpc_chip), + GFP_KERNEL); + if (!pm8xxx_gpio_chip) { + pr_err("Cannot allocate pm8xxx_gpio_chip\n"); + return -ENOMEM; + } + + pm8xxx_gpio_chip->gpio_chip.label = "pm8xxx-gpio-rpc"; + pm8xxx_gpio_chip->gpio_chip.direction_input = + pm8xxx_gpio_rpc_direction_input; + pm8xxx_gpio_chip->gpio_chip.direction_output = + pm8xxx_gpio_rpc_direction_output; + pm8xxx_gpio_chip->gpio_chip.get = pm8xxx_gpio_rpc_read; + pm8xxx_gpio_chip->gpio_chip.set = pm8xxx_gpio_rpc_write; + pm8xxx_gpio_chip->gpio_chip.dbg_show = pm8xxx_gpio_rpc_dbg_show; + pm8xxx_gpio_chip->gpio_chip.ngpio = pdata->ngpios; + pm8xxx_gpio_chip->gpio_chip.can_sleep = 1; + pm8xxx_gpio_chip->gpio_chip.dev = &pdev->dev; + pm8xxx_gpio_chip->gpio_chip.base = pdata->gpio_base; + + mutex_lock(&pm8xxx_gpio_chips_lock); + list_add(&pm8xxx_gpio_chip->link, &pm8xxx_gpio_rpc_chips); + mutex_unlock(&pm8xxx_gpio_chips_lock); + platform_set_drvdata(pdev, pm8xxx_gpio_chip); + + ret = gpiochip_add(&pm8xxx_gpio_chip->gpio_chip); + if (ret) { + pr_err("gpiochip_add failed ret = %d\n", ret); + goto reset_drvdata; + } + + pr_info("OK: base=%d, ngpio=%d\n", pm8xxx_gpio_chip->gpio_chip.base, + pm8xxx_gpio_chip->gpio_chip.ngpio); + + return 0; + +reset_drvdata: + mutex_lock(&pm8xxx_gpio_chips_lock); + list_del(&pm8xxx_gpio_chip->link); + mutex_unlock(&pm8xxx_gpio_chips_lock); + platform_set_drvdata(pdev, NULL); + kfree(pm8xxx_gpio_chip); + mutex_destroy(&pm8xxx_gpio_chips_lock); + return ret; +} + +static int __devexit pm8xxx_gpio_rpc_remove(struct platform_device *pdev) +{ + struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = + platform_get_drvdata(pdev); + + mutex_lock(&pm8xxx_gpio_chips_lock); + list_del(&pm8xxx_gpio_chip->link); + mutex_unlock(&pm8xxx_gpio_chips_lock); + platform_set_drvdata(pdev, NULL); + if (gpiochip_remove(&pm8xxx_gpio_chip->gpio_chip)) + pr_err("failed to remove gpio chip\n"); + kfree(pm8xxx_gpio_chip); + mutex_destroy(&pm8xxx_gpio_chips_lock); + return 0; +} + +static struct platform_driver pm8xxx_gpio_rpc_driver = { + .probe = pm8xxx_gpio_rpc_probe, + .remove = __devexit_p(pm8xxx_gpio_rpc_remove), + .driver = { + .name = PM8XXX_GPIO_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_gpio_rpc_init(void) +{ + return platform_driver_register(&pm8xxx_gpio_rpc_driver); +} +postcore_initcall(pm8xxx_gpio_rpc_init); + +static void __exit pm8xxx_gpio_rpc_exit(void) +{ + platform_driver_unregister(&pm8xxx_gpio_rpc_driver); +} +module_exit(pm8xxx_gpio_rpc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC GPIO driver based on RPC"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_GPIO_DEV_NAME); diff --git a/drivers/gpio/pm8xxx-gpio.c b/drivers/gpio/pm8xxx-gpio.c new file mode 100644 index 00000000000..026fd056863 --- /dev/null +++ b/drivers/gpio/pm8xxx-gpio.c @@ -0,0 +1,458 @@ +/* + * Qualcomm PMIC8XXX GPIO driver + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* GPIO registers */ +#define SSBI_REG_ADDR_GPIO_BASE 0x150 +#define SSBI_REG_ADDR_GPIO(n) (SSBI_REG_ADDR_GPIO_BASE + n) + +/* GPIO */ +#define PM_GPIO_BANK_MASK 0x70 +#define PM_GPIO_BANK_SHIFT 4 +#define PM_GPIO_WRITE 0x80 + +/* Bank 0 */ +#define PM_GPIO_VIN_MASK 0x0E +#define PM_GPIO_VIN_SHIFT 1 +#define PM_GPIO_MODE_ENABLE 0x01 + +/* Bank 1 */ +#define PM_GPIO_MODE_MASK 0x0C +#define PM_GPIO_MODE_SHIFT 2 +#define PM_GPIO_OUT_BUFFER 0x02 +#define PM_GPIO_OUT_INVERT 0x01 + +#define PM_GPIO_MODE_OFF 3 +#define PM_GPIO_MODE_OUTPUT 2 +#define PM_GPIO_MODE_INPUT 0 +#define PM_GPIO_MODE_BOTH 1 + +/* Bank 2 */ +#define PM_GPIO_PULL_MASK 0x0E +#define PM_GPIO_PULL_SHIFT 1 + +/* Bank 3 */ +#define PM_GPIO_OUT_STRENGTH_MASK 0x0C +#define PM_GPIO_OUT_STRENGTH_SHIFT 2 +#define PM_GPIO_PIN_ENABLE 0x00 +#define PM_GPIO_PIN_DISABLE 0x01 + +/* Bank 4 */ +#define PM_GPIO_FUNC_MASK 0x0E +#define PM_GPIO_FUNC_SHIFT 1 + +/* Bank 5 */ +#define PM_GPIO_NON_INT_POL_INV 0x08 +#define PM_GPIO_BANKS 6 + +struct pm_gpio_chip { + struct list_head link; + struct gpio_chip gpio_chip; + spinlock_t pm_lock; + u8 *bank1; + int irq_base; +}; + +static LIST_HEAD(pm_gpio_chips); +static DEFINE_MUTEX(pm_gpio_chips_lock); + +static int pm_gpio_get(struct pm_gpio_chip *pm_gpio_chip, unsigned gpio) +{ + int mode; + + if (gpio >= pm_gpio_chip->gpio_chip.ngpio || pm_gpio_chip == NULL) + return -EINVAL; + + /* Get gpio value from config bank 1 if output gpio. + Get gpio value from IRQ RT status register for all other gpio modes. + */ + mode = (pm_gpio_chip->bank1[gpio] & PM_GPIO_MODE_MASK) >> + PM_GPIO_MODE_SHIFT; + if (mode == PM_GPIO_MODE_OUTPUT) + return pm_gpio_chip->bank1[gpio] & PM_GPIO_OUT_INVERT; + else + return pm8xxx_read_irq_stat(pm_gpio_chip->gpio_chip.dev->parent, + pm_gpio_chip->irq_base + gpio); +} + +static int pm_gpio_set(struct pm_gpio_chip *pm_gpio_chip, + unsigned gpio, int value) +{ + int rc; + u8 bank1; + unsigned long flags; + + if (gpio >= pm_gpio_chip->gpio_chip.ngpio || pm_gpio_chip == NULL) + return -EINVAL; + + spin_lock_irqsave(&pm_gpio_chip->pm_lock, flags); + bank1 = PM_GPIO_WRITE + | (pm_gpio_chip->bank1[gpio] & ~PM_GPIO_OUT_INVERT); + + if (value) + bank1 |= PM_GPIO_OUT_INVERT; + + pm_gpio_chip->bank1[gpio] = bank1; + rc = pm8xxx_writeb(pm_gpio_chip->gpio_chip.dev->parent, + SSBI_REG_ADDR_GPIO(gpio), bank1); + spin_unlock_irqrestore(&pm_gpio_chip->pm_lock, flags); + + if (rc) + pr_err("FAIL pm8xxx_writeb(): rc=%d. " + "(gpio=%d, value=%d)\n", + rc, gpio, value); + + return rc; +} + +static int dir_map[] = { + PM_GPIO_MODE_OFF, + PM_GPIO_MODE_OUTPUT, + PM_GPIO_MODE_INPUT, + PM_GPIO_MODE_BOTH, +}; + +static int pm_gpio_set_direction(struct pm_gpio_chip *pm_gpio_chip, + unsigned gpio, int direction) +{ + int rc; + u8 bank1; + unsigned long flags; + + if (!direction || pm_gpio_chip == NULL) + return -EINVAL; + + spin_lock_irqsave(&pm_gpio_chip->pm_lock, flags); + bank1 = PM_GPIO_WRITE + | (pm_gpio_chip->bank1[gpio] & ~PM_GPIO_MODE_MASK); + + bank1 |= ((dir_map[direction] << PM_GPIO_MODE_SHIFT) + & PM_GPIO_MODE_MASK); + + pm_gpio_chip->bank1[gpio] = bank1; + rc = pm8xxx_writeb(pm_gpio_chip->gpio_chip.dev->parent, + SSBI_REG_ADDR_GPIO(gpio), bank1); + spin_unlock_irqrestore(&pm_gpio_chip->pm_lock, flags); + + if (rc) + pr_err("Failed on pm8xxx_writeb(): rc=%d (GPIO config)\n", + rc); + + return rc; +} + +static int pm_gpio_init_bank1(struct pm_gpio_chip *pm_gpio_chip) +{ + int i, rc; + u8 bank; + + for (i = 0; i < pm_gpio_chip->gpio_chip.ngpio; i++) { + bank = 1 << PM_GPIO_BANK_SHIFT; + rc = pm8xxx_writeb(pm_gpio_chip->gpio_chip.dev->parent, + SSBI_REG_ADDR_GPIO(i), + bank); + if (rc) { + pr_err("error setting bank rc=%d\n", rc); + return rc; + } + + rc = pm8xxx_readb(pm_gpio_chip->gpio_chip.dev->parent, + SSBI_REG_ADDR_GPIO(i), + &pm_gpio_chip->bank1[i]); + if (rc) { + pr_err("error reading bank 1 rc=%d\n", rc); + return rc; + } + } + return 0; +} + +static int pm_gpio_to_irq(struct gpio_chip *gpio_chip, unsigned offset) +{ + struct pm_gpio_chip *pm_gpio_chip = dev_get_drvdata(gpio_chip->dev); + + return pm_gpio_chip->irq_base + offset; +} + +static int pm_gpio_read(struct gpio_chip *gpio_chip, unsigned offset) +{ + struct pm_gpio_chip *pm_gpio_chip = dev_get_drvdata(gpio_chip->dev); + + return pm_gpio_get(pm_gpio_chip, offset); +} + +static void pm_gpio_write(struct gpio_chip *gpio_chip, + unsigned offset, int val) +{ + struct pm_gpio_chip *pm_gpio_chip = dev_get_drvdata(gpio_chip->dev); + + pm_gpio_set(pm_gpio_chip, offset, val); +} + +static int pm_gpio_direction_input(struct gpio_chip *gpio_chip, + unsigned offset) +{ + struct pm_gpio_chip *pm_gpio_chip = dev_get_drvdata(gpio_chip->dev); + + return pm_gpio_set_direction(pm_gpio_chip, offset, PM_GPIO_DIR_IN); +} + +static int pm_gpio_direction_output(struct gpio_chip *gpio_chip, + unsigned offset, + int val) +{ + int ret; + struct pm_gpio_chip *pm_gpio_chip = dev_get_drvdata(gpio_chip->dev); + + ret = pm_gpio_set_direction(pm_gpio_chip, offset, PM_GPIO_DIR_OUT); + if (!ret) + ret = pm_gpio_set(pm_gpio_chip, offset, val); + + return ret; +} + +static void pm_gpio_dbg_show(struct seq_file *s, struct gpio_chip *gpio_chip) +{ + static const char * const cmode[] = { "in", "in/out", "out", "off" }; + struct pm_gpio_chip *pm_gpio_chip = dev_get_drvdata(gpio_chip->dev); + u8 mode, state, bank; + const char *label; + int i, j; + + for (i = 0; i < gpio_chip->ngpio; i++) { + label = gpiochip_is_requested(gpio_chip, i); + mode = (pm_gpio_chip->bank1[i] & PM_GPIO_MODE_MASK) >> + PM_GPIO_MODE_SHIFT; + state = pm_gpio_get(pm_gpio_chip, i); + seq_printf(s, "gpio-%-3d (%-12.12s) %-10.10s" + " %s", + gpio_chip->base + i, + label ? label : "--", + cmode[mode], + state ? "hi" : "lo"); + for (j = 0; j < PM_GPIO_BANKS; j++) { + bank = j << PM_GPIO_BANK_SHIFT; + pm8xxx_writeb(gpio_chip->dev->parent, + SSBI_REG_ADDR_GPIO(i), + bank); + pm8xxx_readb(gpio_chip->dev->parent, + SSBI_REG_ADDR_GPIO(i), + &bank); + seq_printf(s, " 0x%02x", bank); + } + seq_printf(s, "\n"); + } +} + +static int __devinit pm_gpio_probe(struct platform_device *pdev) +{ + int ret; + const struct pm8xxx_gpio_platform_data *pdata = pdev->dev.platform_data; + struct pm_gpio_chip *pm_gpio_chip; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + pm_gpio_chip = kzalloc(sizeof(struct pm_gpio_chip), GFP_KERNEL); + if (!pm_gpio_chip) { + pr_err("Cannot allocate pm_gpio_chip\n"); + return -ENOMEM; + } + + pm_gpio_chip->bank1 = kzalloc(sizeof(u8) * pdata->gpio_cdata.ngpios, + GFP_KERNEL); + if (!pm_gpio_chip->bank1) { + pr_err("Cannot allocate pm_gpio_chip->bank1\n"); + return -ENOMEM; + } + + spin_lock_init(&pm_gpio_chip->pm_lock); + pm_gpio_chip->gpio_chip.label = "pm-gpio"; + pm_gpio_chip->gpio_chip.direction_input = pm_gpio_direction_input; + pm_gpio_chip->gpio_chip.direction_output = pm_gpio_direction_output; + pm_gpio_chip->gpio_chip.to_irq = pm_gpio_to_irq; + pm_gpio_chip->gpio_chip.get = pm_gpio_read; + pm_gpio_chip->gpio_chip.set = pm_gpio_write; + pm_gpio_chip->gpio_chip.dbg_show = pm_gpio_dbg_show; + pm_gpio_chip->gpio_chip.ngpio = pdata->gpio_cdata.ngpios; + pm_gpio_chip->gpio_chip.can_sleep = 1; + pm_gpio_chip->gpio_chip.dev = &pdev->dev; + pm_gpio_chip->gpio_chip.base = pdata->gpio_base; + pm_gpio_chip->irq_base = platform_get_irq(pdev, 0); + mutex_lock(&pm_gpio_chips_lock); + list_add(&pm_gpio_chip->link, &pm_gpio_chips); + mutex_unlock(&pm_gpio_chips_lock); + platform_set_drvdata(pdev, pm_gpio_chip); + + ret = gpiochip_add(&pm_gpio_chip->gpio_chip); + if (ret) { + pr_err("gpiochip_add failed ret = %d\n", ret); + goto reset_drvdata; + } + + ret = pm_gpio_init_bank1(pm_gpio_chip); + if (ret) { + pr_err("gpio init bank failed ret = %d\n", ret); + goto remove_chip; + } + + pr_info("OK: base=%d, ngpio=%d\n", pm_gpio_chip->gpio_chip.base, + pm_gpio_chip->gpio_chip.ngpio); + + return 0; + +remove_chip: + if (gpiochip_remove(&pm_gpio_chip->gpio_chip)) + pr_err("failed to remove gpio chip\n"); +reset_drvdata: + platform_set_drvdata(pdev, NULL); + kfree(pm_gpio_chip); + return ret; +} + +static int __devexit pm_gpio_remove(struct platform_device *pdev) +{ + struct pm_gpio_chip *pm_gpio_chip + = platform_get_drvdata(pdev); + + mutex_lock(&pm_gpio_chips_lock); + list_del(&pm_gpio_chip->link); + mutex_unlock(&pm_gpio_chips_lock); + platform_set_drvdata(pdev, NULL); + if (gpiochip_remove(&pm_gpio_chip->gpio_chip)) + pr_err("failed to remove gpio chip\n"); + kfree(pm_gpio_chip->bank1); + kfree(pm_gpio_chip); + return 0; +} + +int pm8xxx_gpio_config(int gpio, struct pm_gpio *param) +{ + int rc, pm_gpio = -EINVAL; + u8 bank[8]; + unsigned long flags; + struct pm_gpio_chip *pm_gpio_chip; + struct gpio_chip *gpio_chip; + + if (param == NULL) + return -EINVAL; + + mutex_lock(&pm_gpio_chips_lock); + list_for_each_entry(pm_gpio_chip, &pm_gpio_chips, link) { + gpio_chip = &pm_gpio_chip->gpio_chip; + if (gpio >= gpio_chip->base + && gpio < gpio_chip->base + gpio_chip->ngpio) { + pm_gpio = gpio - gpio_chip->base; + break; + } + } + mutex_unlock(&pm_gpio_chips_lock); + if (pm_gpio < 0) { + pr_err("called on gpio %d not handled by any pmic\n", gpio); + return -EINVAL; + } + + /* Select banks and configure the gpio */ + bank[0] = PM_GPIO_WRITE | + ((param->vin_sel << PM_GPIO_VIN_SHIFT) & + PM_GPIO_VIN_MASK) | + PM_GPIO_MODE_ENABLE; + bank[1] = PM_GPIO_WRITE | + ((1 << PM_GPIO_BANK_SHIFT) & + PM_GPIO_BANK_MASK) | + ((dir_map[param->direction] << + PM_GPIO_MODE_SHIFT) & + PM_GPIO_MODE_MASK) | + ((param->direction & PM_GPIO_DIR_OUT) ? + ((param->output_buffer & 1) ? + PM_GPIO_OUT_BUFFER : 0) : 0) | + ((param->direction & PM_GPIO_DIR_OUT) ? + param->output_value & 0x01 : 0); + bank[2] = PM_GPIO_WRITE | + ((2 << PM_GPIO_BANK_SHIFT) & + PM_GPIO_BANK_MASK) | + ((param->pull << PM_GPIO_PULL_SHIFT) & + PM_GPIO_PULL_MASK); + bank[3] = PM_GPIO_WRITE | + ((3 << PM_GPIO_BANK_SHIFT) & + PM_GPIO_BANK_MASK) | + ((param->out_strength << + PM_GPIO_OUT_STRENGTH_SHIFT) & + PM_GPIO_OUT_STRENGTH_MASK) | + (param->disable_pin ? + PM_GPIO_PIN_DISABLE : PM_GPIO_PIN_ENABLE); + bank[4] = PM_GPIO_WRITE | + ((4 << PM_GPIO_BANK_SHIFT) & + PM_GPIO_BANK_MASK) | + ((param->function << PM_GPIO_FUNC_SHIFT) & + PM_GPIO_FUNC_MASK); + bank[5] = PM_GPIO_WRITE | + ((5 << PM_GPIO_BANK_SHIFT) & PM_GPIO_BANK_MASK) | + (param->inv_int_pol ? 0 : PM_GPIO_NON_INT_POL_INV); + + spin_lock_irqsave(&pm_gpio_chip->pm_lock, flags); + /* Remember bank1 for later use */ + pm_gpio_chip->bank1[pm_gpio] = bank[1]; + rc = pm8xxx_write_buf(pm_gpio_chip->gpio_chip.dev->parent, + SSBI_REG_ADDR_GPIO(pm_gpio), bank, 6); + spin_unlock_irqrestore(&pm_gpio_chip->pm_lock, flags); + + if (rc) + pr_err("Failed on pm8xxx_write_buf() rc=%d (GPIO config)\n", + rc); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_gpio_config); + +static struct platform_driver pm_gpio_driver = { + .probe = pm_gpio_probe, + .remove = __devexit_p(pm_gpio_remove), + .driver = { + .name = PM8XXX_GPIO_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm_gpio_init(void) +{ + return platform_driver_register(&pm_gpio_driver); +} +postcore_initcall(pm_gpio_init); + +static void __exit pm_gpio_exit(void) +{ + platform_driver_unregister(&pm_gpio_driver); +} +module_exit(pm_gpio_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC GPIO driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_GPIO_DEV_NAME); diff --git a/drivers/gpio/pm8xxx-mpp.c b/drivers/gpio/pm8xxx-mpp.c new file mode 100644 index 00000000000..82a11a22c41 --- /dev/null +++ b/drivers/gpio/pm8xxx-mpp.c @@ -0,0 +1,334 @@ +/* + * Qualcomm PM8XXX Multi-Purpose Pin (MPP) driver + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +/* MPP Type */ +#define PM8XXX_MPP_TYPE_MASK 0xE0 +#define PM8XXX_MPP_TYPE_SHIFT 5 + +/* MPP Config Level */ +#define PM8XXX_MPP_CONFIG_LVL_MASK 0x1C +#define PM8XXX_MPP_CONFIG_LVL_SHIFT 2 + +/* MPP Config Control */ +#define PM8XXX_MPP_CONFIG_CTRL_MASK 0x03 +#define PM8XXX_MPP_CONFIG_CTRL_SHIFT 0 + +struct pm8xxx_mpp_chip { + struct list_head link; + struct gpio_chip gpio_chip; + spinlock_t pm_lock; + u8 *ctrl_reg; + int mpp_base; + int irq_base; + int nmpps; + u16 base_addr; +}; + +static LIST_HEAD(pm8xxx_mpp_chips); +static DEFINE_MUTEX(pm8xxx_mpp_chips_lock); + +static int pm8xxx_mpp_write(struct pm8xxx_mpp_chip *mpp_chip, u16 offset, + u8 val, u8 mask) +{ + u8 reg; + int rc; + unsigned long flags; + + spin_lock_irqsave(&mpp_chip->pm_lock, flags); + + reg = (mpp_chip->ctrl_reg[offset] & ~mask) | (val & mask); + rc = pm8xxx_writeb(mpp_chip->gpio_chip.dev->parent, + mpp_chip->base_addr + offset, reg); + if (!rc) + mpp_chip->ctrl_reg[offset] = reg; + + spin_unlock_irqrestore(&mpp_chip->pm_lock, flags); + + return rc; +} + +static int pm8xxx_mpp_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pm8xxx_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + + return mpp_chip->irq_base + offset; +} + +static int pm8xxx_mpp_get(struct gpio_chip *chip, unsigned offset) +{ + struct pm8xxx_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + int rc; + + if ((mpp_chip->ctrl_reg[offset] & PM8XXX_MPP_TYPE_MASK) >> + PM8XXX_MPP_TYPE_SHIFT == PM8XXX_MPP_TYPE_D_OUTPUT) + rc = mpp_chip->ctrl_reg[offset] & PM8XXX_MPP_CONFIG_CTRL_MASK; + else + rc = pm8xxx_read_irq_stat(mpp_chip->gpio_chip.dev->parent, + mpp_chip->irq_base + offset); + + return rc; +} + +static void pm8xxx_mpp_set(struct gpio_chip *chip, unsigned offset, int val) +{ + struct pm8xxx_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + u8 reg = val ? PM8XXX_MPP_DOUT_CTRL_HIGH : PM8XXX_MPP_DOUT_CTRL_LOW; + int rc; + + rc = pm8xxx_mpp_write(mpp_chip, offset, reg, + PM8XXX_MPP_CONFIG_CTRL_MASK); + if (rc) + pr_err("pm8xxx_mpp_write(): rc=%d\n", rc); +} + +static int pm8xxx_mpp_dir_input(struct gpio_chip *chip, unsigned offset) +{ + struct pm8xxx_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + int rc = pm8xxx_mpp_write(mpp_chip, offset, + PM8XXX_MPP_TYPE_D_INPUT << PM8XXX_MPP_TYPE_SHIFT, + PM8XXX_MPP_TYPE_MASK); + + if (rc) + pr_err("pm8xxx_mpp_write(): rc=%d\n", rc); + return rc; +} + +static int pm8xxx_mpp_dir_output(struct gpio_chip *chip, + unsigned offset, int val) +{ + struct pm8xxx_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + u8 reg = (PM8XXX_MPP_TYPE_D_OUTPUT << PM8XXX_MPP_TYPE_SHIFT) | + (val & PM8XXX_MPP_CONFIG_CTRL_MASK); + u8 mask = PM8XXX_MPP_TYPE_MASK | PM8XXX_MPP_CONFIG_CTRL_MASK; + int rc = pm8xxx_mpp_write(mpp_chip, offset, reg, mask); + + if (rc) + pr_err("pm8xxx_mpp_write(): rc=%d\n", rc); + return rc; +} + +static void pm8xxx_mpp_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + static const char * const ctype[] = { "d_in", "d_out", "bi_dir", + "a_in", "a_out", "sink", + "dtest_sink", "dtest_out" + }; + struct pm8xxx_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + u8 type, state; + const char *label; + int i; + + for (i = 0; i < mpp_chip->nmpps; i++) { + label = gpiochip_is_requested(chip, i); + type = (mpp_chip->ctrl_reg[i] & PM8XXX_MPP_TYPE_MASK) >> + PM8XXX_MPP_TYPE_SHIFT; + state = pm8xxx_mpp_get(chip, i); + seq_printf(s, "gpio-%-3d (%-12.12s) %-10.10s" + " %s 0x%02x\n", + chip->base + i, + label ? label : "--", + ctype[type], + state ? "hi" : "lo", + mpp_chip->ctrl_reg[i]); + } +} + +int pm8xxx_mpp_config(unsigned mpp, struct pm8xxx_mpp_config_data *config) +{ + struct pm8xxx_mpp_chip *mpp_chip; + int rc, found = 0; + u8 config_reg, mask; + + if (!config) { + pr_err("config not specified for MPP %d\n", mpp); + return -EINVAL; + } + + mutex_lock(&pm8xxx_mpp_chips_lock); + list_for_each_entry(mpp_chip, &pm8xxx_mpp_chips, link) { + if (mpp >= mpp_chip->mpp_base + && mpp < mpp_chip->mpp_base + mpp_chip->nmpps) { + found = 1; + break; + } + } + mutex_unlock(&pm8xxx_mpp_chips_lock); + if (!found) { + pr_err("called on mpp %d not handled by any pmic\n", mpp); + return -EINVAL; + } + + mask = PM8XXX_MPP_TYPE_MASK | PM8XXX_MPP_CONFIG_LVL_MASK | + PM8XXX_MPP_CONFIG_CTRL_MASK; + config_reg = (config->type << PM8XXX_MPP_TYPE_SHIFT) + & PM8XXX_MPP_TYPE_MASK; + config_reg |= (config->level << PM8XXX_MPP_CONFIG_LVL_SHIFT) + & PM8XXX_MPP_CONFIG_LVL_MASK; + config_reg |= config->control & PM8XXX_MPP_CONFIG_CTRL_MASK; + + rc = pm8xxx_mpp_write(mpp_chip, mpp - mpp_chip->mpp_base, config_reg, + mask); + + if (rc) + pr_err("pm8xxx_mpp_write(): rc=%d\n", rc); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_mpp_config); + +static int __devinit pm8xxx_mpp_reg_init(struct pm8xxx_mpp_chip *mpp_chip) +{ + int rc, i; + + for (i = 0; i < mpp_chip->nmpps; i++) { + rc = pm8xxx_readb(mpp_chip->gpio_chip.dev->parent, + mpp_chip->base_addr + i, + &mpp_chip->ctrl_reg[i]); + if (rc) { + pr_err("failed to read register 0x%x rc=%d\n", + mpp_chip->base_addr + i, rc); + return rc; + } + } + return 0; +} + +static int __devinit pm8xxx_mpp_probe(struct platform_device *pdev) +{ + int rc; + const struct pm8xxx_mpp_platform_data *pdata = pdev->dev.platform_data; + struct pm8xxx_mpp_chip *mpp_chip; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + mpp_chip = kzalloc(sizeof(struct pm8xxx_mpp_chip), GFP_KERNEL); + if (!mpp_chip) { + pr_err("Cannot allocate %d bytes\n", + sizeof(struct pm8xxx_mpp_chip)); + return -ENOMEM; + } + + mpp_chip->ctrl_reg = kzalloc(pdata->core_data.nmpps, GFP_KERNEL); + if (!mpp_chip->ctrl_reg) { + pr_err("Cannot allocate %d bytes\n", pdata->core_data.nmpps); + rc = -ENOMEM; + goto free_mpp_chip; + } + + spin_lock_init(&mpp_chip->pm_lock); + + mpp_chip->gpio_chip.label = PM8XXX_MPP_DEV_NAME; + mpp_chip->gpio_chip.direction_input = pm8xxx_mpp_dir_input; + mpp_chip->gpio_chip.direction_output = pm8xxx_mpp_dir_output; + mpp_chip->gpio_chip.to_irq = pm8xxx_mpp_to_irq; + mpp_chip->gpio_chip.get = pm8xxx_mpp_get; + mpp_chip->gpio_chip.set = pm8xxx_mpp_set; + mpp_chip->gpio_chip.dbg_show = pm8xxx_mpp_dbg_show; + mpp_chip->gpio_chip.ngpio = pdata->core_data.nmpps; + mpp_chip->gpio_chip.can_sleep = 1; + mpp_chip->gpio_chip.dev = &pdev->dev; + mpp_chip->gpio_chip.base = pdata->mpp_base; + mpp_chip->irq_base = platform_get_irq(pdev, 0); + mpp_chip->mpp_base = pdata->mpp_base; + mpp_chip->base_addr = pdata->core_data.base_addr; + mpp_chip->nmpps = pdata->core_data.nmpps; + + mutex_lock(&pm8xxx_mpp_chips_lock); + list_add(&mpp_chip->link, &pm8xxx_mpp_chips); + mutex_unlock(&pm8xxx_mpp_chips_lock); + + platform_set_drvdata(pdev, mpp_chip); + + rc = gpiochip_add(&mpp_chip->gpio_chip); + if (rc) { + pr_err("gpiochip_add failed, rc=%d\n", rc); + goto reset_drvdata; + } + + rc = pm8xxx_mpp_reg_init(mpp_chip); + if (rc) { + pr_err("failed to read MPP ctrl registers, rc=%d\n", rc); + goto remove_chip; + } + + pr_info("OK: base=%d, ngpio=%d\n", mpp_chip->gpio_chip.base, + mpp_chip->gpio_chip.ngpio); + + return 0; + +remove_chip: + if (gpiochip_remove(&mpp_chip->gpio_chip)) + pr_err("failed to remove gpio chip\n"); +reset_drvdata: + platform_set_drvdata(pdev, NULL); +free_mpp_chip: + kfree(mpp_chip); + return rc; +} + +static int __devexit pm8xxx_mpp_remove(struct platform_device *pdev) +{ + struct pm8xxx_mpp_chip *mpp_chip = platform_get_drvdata(pdev); + + mutex_lock(&pm8xxx_mpp_chips_lock); + list_del(&mpp_chip->link); + mutex_unlock(&pm8xxx_mpp_chips_lock); + platform_set_drvdata(pdev, NULL); + if (gpiochip_remove(&mpp_chip->gpio_chip)) + pr_err("failed to remove gpio chip\n"); + kfree(mpp_chip->ctrl_reg); + kfree(mpp_chip); + + return 0; +} + +static struct platform_driver pm8xxx_mpp_driver = { + .probe = pm8xxx_mpp_probe, + .remove = __devexit_p(pm8xxx_mpp_remove), + .driver = { + .name = PM8XXX_MPP_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_mpp_init(void) +{ + return platform_driver_register(&pm8xxx_mpp_driver); +} +postcore_initcall(pm8xxx_mpp_init); + +static void __exit pm8xxx_mpp_exit(void) +{ + platform_driver_unregister(&pm8xxx_mpp_driver); +} +module_exit(pm8xxx_mpp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PM8XXX MPP driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_MPP_DEV_NAME); diff --git a/drivers/gpio/pmic8901-mpp.c b/drivers/gpio/pmic8901-mpp.c new file mode 100644 index 00000000000..85e65393f99 --- /dev/null +++ b/drivers/gpio/pmic8901-mpp.c @@ -0,0 +1,231 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8901 MPP driver + * + */ + +#include +#include +#include +#include +#include + +/* MPP Control Registers */ +#define SSBI_MPP_CNTRL_BASE 0x27 +#define SSBI_MPP_CNTRL(n) (SSBI_MPP_CNTRL_BASE + (n)) + +/* MPP Type */ +#define PM8901_MPP_TYPE_MASK 0xE0 +#define PM8901_MPP_TYPE_SHIFT 5 + +/* MPP Config Level */ +#define PM8901_MPP_CONFIG_LVL_MASK 0x1C +#define PM8901_MPP_CONFIG_LVL_SHIFT 2 + +/* MPP Config Control */ +#define PM8901_MPP_CONFIG_CTL_MASK 0x03 + +struct pm8901_mpp_chip { + struct gpio_chip chip; + struct pm8901_chip *pm_chip; + u8 ctrl[PM8901_MPPS]; +}; + +static int pm8901_mpp_write(struct pm8901_chip *chip, u16 addr, u8 val, + u8 mask, u8 *bak) +{ + u8 reg = (*bak & ~mask) | (val & mask); + int rc = pm8901_write(chip, addr, ®, 1); + if (!rc) + *bak = reg; + return rc; +} + +static int pm8901_mpp_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pm8901_gpio_platform_data *pdata; + pdata = chip->dev->platform_data; + return pdata->irq_base + offset; +} + +static int pm8901_mpp_get(struct gpio_chip *chip, unsigned offset) +{ + struct pm8901_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + int ret; + + if ((mpp_chip->ctrl[offset] & PM8901_MPP_TYPE_MASK) >> + PM8901_MPP_TYPE_SHIFT == PM_MPP_TYPE_D_OUTPUT) + ret = mpp_chip->ctrl[offset] & PM8901_MPP_CONFIG_CTL_MASK; + else + ret = pm8901_irq_get_rt_status(mpp_chip->pm_chip, + pm8901_mpp_to_irq(chip, offset)); + return ret; +} + +static void pm8901_mpp_set(struct gpio_chip *chip, unsigned offset, int val) +{ + struct pm8901_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + u8 reg = val ? PM_MPP_DOUT_CTL_HIGH : PM_MPP_DOUT_CTL_LOW; + int rc; + + rc = pm8901_mpp_write(mpp_chip->pm_chip, SSBI_MPP_CNTRL(offset), + reg, PM8901_MPP_CONFIG_CTL_MASK, + &mpp_chip->ctrl[offset]); + if (rc) + pr_err("%s: pm8901_mpp_write(): rc=%d\n", __func__, rc); +} + +static int pm8901_mpp_dir_input(struct gpio_chip *chip, unsigned offset) +{ + struct pm8901_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + int rc = pm8901_mpp_write(mpp_chip->pm_chip, + SSBI_MPP_CNTRL(offset), + PM_MPP_TYPE_D_INPUT << PM8901_MPP_TYPE_SHIFT, + PM8901_MPP_TYPE_MASK, &mpp_chip->ctrl[offset]); + if (rc) + pr_err("%s: pm8901_mpp_write(): rc=%d\n", __func__, rc); + return rc; +} + +static int pm8901_mpp_dir_output(struct gpio_chip *chip, + unsigned offset, int val) +{ + struct pm8901_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + u8 reg = (PM_MPP_TYPE_D_OUTPUT << PM8901_MPP_TYPE_SHIFT) | + (val & PM8901_MPP_CONFIG_CTL_MASK); + u8 mask = PM8901_MPP_TYPE_MASK | PM8901_MPP_CONFIG_CTL_MASK; + int rc = pm8901_mpp_write(mpp_chip->pm_chip, + SSBI_MPP_CNTRL(offset), reg, mask, + &mpp_chip->ctrl[offset]); + if (rc) + pr_err("%s: pm8901_mpp_write(): rc=%d\n", __func__, rc); + return rc; +} + +static void pm8901_mpp_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + static const char *ctype[] = { "d_in", "d_out", "bi_dir", "a_in", + "a_out", "sink", "dtest_sink", "dtest_out" }; + struct pm8901_mpp_chip *mpp_chip = dev_get_drvdata(chip->dev); + u8 type, state; + const char *label; + int i; + + for (i = 0; i < PM8901_MPPS; i++) { + label = gpiochip_is_requested(chip, i); + type = (mpp_chip->ctrl[i] & PM8901_MPP_TYPE_MASK) >> + PM8901_MPP_TYPE_SHIFT; + state = pm8901_mpp_get(chip, i); + seq_printf(s, "gpio-%-3d (%-12.12s) %-10.10s" + " %s 0x%02x\n", + chip->base + i, + label ? label : "--", + ctype[type], + state ? "hi" : "lo", + mpp_chip->ctrl[i]); + } +} + +static struct pm8901_mpp_chip pm8901_mpp_chip = { + .chip = { + .label = "pm8901-mpp", + .to_irq = pm8901_mpp_to_irq, + .get = pm8901_mpp_get, + .set = pm8901_mpp_set, + .direction_input = pm8901_mpp_dir_input, + .direction_output = pm8901_mpp_dir_output, + .dbg_show = pm8901_mpp_dbg_show, + .ngpio = PM8901_MPPS, + }, +}; + +int pm8901_mpp_config(unsigned mpp, unsigned type, unsigned level, + unsigned control) +{ + u8 config, mask; + int rc; + + if (mpp >= PM8901_MPPS) + return -EINVAL; + + mask = PM8901_MPP_TYPE_MASK | PM8901_MPP_CONFIG_LVL_MASK | + PM8901_MPP_CONFIG_CTL_MASK; + config = (type << PM8901_MPP_TYPE_SHIFT) & PM8901_MPP_TYPE_MASK; + config |= (level << PM8901_MPP_CONFIG_LVL_SHIFT) & + PM8901_MPP_CONFIG_LVL_MASK; + config |= control & PM8901_MPP_CONFIG_CTL_MASK; + + rc = pm8901_mpp_write(pm8901_mpp_chip.pm_chip, SSBI_MPP_CNTRL(mpp), + config, mask, &pm8901_mpp_chip.ctrl[mpp]); + if (rc) + pr_err("%s: pm8901_mpp_write(): rc=%d\n", __func__, rc); + + return rc; +} +EXPORT_SYMBOL(pm8901_mpp_config); + +static int __devinit pm8901_mpp_probe(struct platform_device *pdev) +{ + int ret, i; + struct pm8901_gpio_platform_data *pdata = pdev->dev.platform_data; + + pm8901_mpp_chip.pm_chip = dev_get_drvdata(pdev->dev.parent); + for (i = 0; i < PM8901_MPPS; i++) { + ret = pm8901_read(pm8901_mpp_chip.pm_chip, + SSBI_MPP_CNTRL(i), &pm8901_mpp_chip.ctrl[i], 1); + if (ret) + goto bail; + + } + platform_set_drvdata(pdev, &pm8901_mpp_chip); + pm8901_mpp_chip.chip.dev = &pdev->dev; + pm8901_mpp_chip.chip.base = pdata->gpio_base; + ret = gpiochip_add(&pm8901_mpp_chip.chip); + +bail: + pr_info("%s: gpiochip_add(): rc=%d\n", __func__, ret); + return ret; +} + +static int __devexit pm8901_mpp_remove(struct platform_device *pdev) +{ + return gpiochip_remove(&pm8901_mpp_chip.chip); +} + +static struct platform_driver pm8901_mpp_driver = { + .probe = pm8901_mpp_probe, + .remove = __devexit_p(pm8901_mpp_remove), + .driver = { + .name = "pm8901-mpp", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8901_mpp_init(void) +{ + return platform_driver_register(&pm8901_mpp_driver); +} + +static void __exit pm8901_mpp_exit(void) +{ + platform_driver_unregister(&pm8901_mpp_driver); +} + +subsys_initcall(pm8901_mpp_init); +module_exit(pm8901_mpp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8901 MPP driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8901-mpp"); diff --git a/drivers/gpio/sx150x.c b/drivers/gpio/sx150x.c index a4f73534394..93b94bd379c 100644 --- a/drivers/gpio/sx150x.c +++ b/drivers/gpio/sx150x.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include #include @@ -189,9 +184,9 @@ static int sx150x_get_io(struct sx150x_chip *chip, unsigned offset) return err; } -static void sx150x_set_oscio(struct sx150x_chip *chip, int val) +static s32 sx150x_set_oscio(struct sx150x_chip *chip, int val) { - sx150x_i2c_write(chip->client, + return sx150x_i2c_write(chip->client, chip->dev_cfg->reg_clock, (val ? 0x1f : 0x10)); } @@ -286,11 +281,13 @@ static int sx150x_gpio_direction_output(struct gpio_chip *gc, chip = container_of(gc, struct sx150x_chip, gpio_chip); - if (!offset_is_oscio(chip, offset)) { - mutex_lock(&chip->lock); + mutex_lock(&chip->lock); + if (offset_is_oscio(chip, offset)) + status = sx150x_set_oscio(chip, val); + else status = sx150x_io_output(chip, offset, val); - mutex_unlock(&chip->lock); - } + mutex_unlock(&chip->lock); + return status; } diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile index ca2d3b34dbf..01cef64bf4d 100644 --- a/drivers/gpu/Makefile +++ b/drivers/gpu/Makefile @@ -1 +1,2 @@ obj-y += drm/ vga/ stub/ ion/ +obj-$(CONFIG_MSM_KGSL) += msm/ diff --git a/drivers/gpu/msm/Kconfig b/drivers/gpu/msm/Kconfig new file mode 100644 index 00000000000..64cbc304c09 --- /dev/null +++ b/drivers/gpu/msm/Kconfig @@ -0,0 +1,105 @@ +config MSM_KGSL + tristate "MSM 3D Graphics driver" + default n + depends on ARCH_MSM && !ARCH_MSM7X00A && !ARCH_MSM7X25 + select GENERIC_ALLOCATOR + select FW_LOADER + ---help--- + 3D graphics driver. Required to use hardware accelerated + OpenGL ES 2.0 and 1.1. + +config MSM_KGSL_CFF_DUMP + bool "Enable KGSL Common File Format (CFF) Dump Feature [Use with caution]" + default n + depends on MSM_KGSL + select RELAY + ---help--- + This is an analysis and diagnostic feature only, and should only be + turned on during KGSL GPU diagnostics and will slow down the KGSL + performance sigificantly, hence *do not use in production builds*. + When enabled, CFF Dump is on at boot. It can be turned off at runtime + via 'echo 0 > /d/kgsl/cff_dump'. The log can be captured via + /d/kgsl-cff/cpu[0|1]. + +config MSM_KGSL_CFF_DUMP_NO_CONTEXT_MEM_DUMP + bool "When selected will disable KGSL CFF Dump for context switches" + default n + depends on MSM_KGSL_CFF_DUMP + ---help--- + Dumping all the memory for every context switch can produce quite + huge log files, to reduce this, turn this feature on. + +config MSM_KGSL_PSTMRTMDMP_CP_STAT_NO_DETAIL + bool "Disable human readable CP_STAT fields in post-mortem dump" + default n + depends on MSM_KGSL + ---help--- + For a more compact kernel log the human readable output of + CP_STAT can be turned off with this option. + +config MSM_KGSL_PSTMRTMDMP_NO_IB_DUMP + bool "Disable dumping current IB1 and IB2 in post-mortem dump" + default n + depends on MSM_KGSL + ---help--- + For a more compact kernel log the IB1 and IB2 embedded dump + can be turned off with this option. Some IB dumps take up + so much space that vital other information gets cut from the + post-mortem dump. + +config MSM_KGSL_PSTMRTMDMP_RB_HEX + bool "Use hex version for ring-buffer in post-mortem dump" + default n + depends on MSM_KGSL + ---help--- + Use hex version for the ring-buffer in the post-mortem dump, instead + of the human readable version. + +config MSM_KGSL_2D + tristate "MSM 2D graphics driver. Required for OpenVG" + default y + depends on MSM_KGSL && !ARCH_MSM7X27 && !ARCH_MSM7X27A && !(ARCH_QSD8X50 && !MSM_SOC_REV_A) + +config MSM_KGSL_DRM + bool "Build a DRM interface for the MSM_KGSL driver" + depends on MSM_KGSL && DRM + +config MSM_KGSL_MMU + bool "Enable the GPU MMU in the MSM_KGSL driver" + depends on MSM_KGSL && MMU && !MSM_KGSL_CFF_DUMP + default y + +config KGSL_PER_PROCESS_PAGE_TABLE + bool "Enable Per Process page tables for the KGSL driver" + default n + depends on MSM_KGSL_MMU && !MSM_KGSL_DRM + ---help--- + The MMU will use per process pagetables when enabled. + +config MSM_KGSL_PAGE_TABLE_SIZE + hex "Size of pagetables" + default 0xFFF0000 + depends on MSM_KGSL_MMU + ---help--- + Sets the pagetable size used by the MMU. The max value + is 0xFFF0000 or (256M - 64K). + +config MSM_KGSL_PAGE_TABLE_COUNT + int "Minimum of concurrent pagetables to support" + default 8 + depends on KGSL_PER_PROCESS_PAGE_TABLE + ---help--- + Specify the number of pagetables to allocate at init time + This is the number of concurrent processes that are guaranteed to + to run at any time. Additional processes can be created dynamically + assuming there is enough contiguous memory to allocate the pagetable. + +config MSM_KGSL_MMU_PAGE_FAULT + bool "Force the GPU MMU to page fault for unmapped regions" + default y + depends on MSM_KGSL_MMU + +config MSM_KGSL_DISABLE_SHADOW_WRITES + bool "Disable register shadow writes for context switches" + default n + depends on MSM_KGSL diff --git a/drivers/gpu/msm/Makefile b/drivers/gpu/msm/Makefile new file mode 100644 index 00000000000..e31a3dd3dc0 --- /dev/null +++ b/drivers/gpu/msm/Makefile @@ -0,0 +1,31 @@ +ccflags-y := -Iinclude/drm + +msm_kgsl_core-y = \ + kgsl.o \ + kgsl_sharedmem.o \ + kgsl_pwrctrl.o \ + kgsl_pwrscale.o + +msm_kgsl_core-$(CONFIG_DEBUG_FS) += kgsl_debugfs.o +msm_kgsl_core-$(CONFIG_MSM_KGSL_MMU) += kgsl_mmu.o +msm_kgsl_core-$(CONFIG_MSM_KGSL_CFF_DUMP) += kgsl_cffdump.o +msm_kgsl_core-$(CONFIG_MSM_KGSL_DRM) += kgsl_drm.o +msm_kgsl_core-$(CONFIG_MSM_SCM) += kgsl_pwrscale_trustzone.o + +msm_adreno-y += \ + adreno_ringbuffer.o \ + adreno_drawctxt.o \ + adreno_postmortem.o \ + adreno.o + +msm_adreno-$(CONFIG_DEBUG_FS) += adreno_debugfs.o + +msm_z180-y += z180.o + +msm_kgsl_core-objs = $(msm_kgsl_core-y) +msm_adreno-objs = $(msm_adreno-y) +msm_z180-objs = $(msm_z180-y) + +obj-$(CONFIG_MSM_KGSL) += msm_kgsl_core.o +obj-$(CONFIG_MSM_KGSL) += msm_adreno.o +obj-$(CONFIG_MSM_KGSL_2D) += msm_z180.o diff --git a/drivers/gpu/msm/a200_reg.h b/drivers/gpu/msm/a200_reg.h new file mode 100644 index 00000000000..e1681f9160c --- /dev/null +++ b/drivers/gpu/msm/a200_reg.h @@ -0,0 +1,408 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __A200_REG_H +#define __A200_REG_H + +enum VGT_EVENT_TYPE { + VS_DEALLOC = 0, + PS_DEALLOC = 1, + VS_DONE_TS = 2, + PS_DONE_TS = 3, + CACHE_FLUSH_TS = 4, + CONTEXT_DONE = 5, + CACHE_FLUSH = 6, + VIZQUERY_START = 7, + VIZQUERY_END = 8, + SC_WAIT_WC = 9, + RST_PIX_CNT = 13, + RST_VTX_CNT = 14, + TILE_FLUSH = 15, + CACHE_FLUSH_AND_INV_TS_EVENT = 20, + ZPASS_DONE = 21, + CACHE_FLUSH_AND_INV_EVENT = 22, + PERFCOUNTER_START = 23, + PERFCOUNTER_STOP = 24, + VS_FETCH_DONE = 27, + FACENESS_FLUSH = 28, +}; + +enum COLORFORMATX { + COLORX_4_4_4_4 = 0, + COLORX_1_5_5_5 = 1, + COLORX_5_6_5 = 2, + COLORX_8 = 3, + COLORX_8_8 = 4, + COLORX_8_8_8_8 = 5, + COLORX_S8_8_8_8 = 6, + COLORX_16_FLOAT = 7, + COLORX_16_16_FLOAT = 8, + COLORX_16_16_16_16_FLOAT = 9, + COLORX_32_FLOAT = 10, + COLORX_32_32_FLOAT = 11, + COLORX_32_32_32_32_FLOAT = 12, + COLORX_2_3_3 = 13, + COLORX_8_8_8 = 14, +}; + +enum SURFACEFORMAT { + FMT_1_REVERSE = 0, + FMT_1 = 1, + FMT_8 = 2, + FMT_1_5_5_5 = 3, + FMT_5_6_5 = 4, + FMT_6_5_5 = 5, + FMT_8_8_8_8 = 6, + FMT_2_10_10_10 = 7, + FMT_8_A = 8, + FMT_8_B = 9, + FMT_8_8 = 10, + FMT_Cr_Y1_Cb_Y0 = 11, + FMT_Y1_Cr_Y0_Cb = 12, + FMT_5_5_5_1 = 13, + FMT_8_8_8_8_A = 14, + FMT_4_4_4_4 = 15, + FMT_10_11_11 = 16, + FMT_11_11_10 = 17, + FMT_DXT1 = 18, + FMT_DXT2_3 = 19, + FMT_DXT4_5 = 20, + FMT_24_8 = 22, + FMT_24_8_FLOAT = 23, + FMT_16 = 24, + FMT_16_16 = 25, + FMT_16_16_16_16 = 26, + FMT_16_EXPAND = 27, + FMT_16_16_EXPAND = 28, + FMT_16_16_16_16_EXPAND = 29, + FMT_16_FLOAT = 30, + FMT_16_16_FLOAT = 31, + FMT_16_16_16_16_FLOAT = 32, + FMT_32 = 33, + FMT_32_32 = 34, + FMT_32_32_32_32 = 35, + FMT_32_FLOAT = 36, + FMT_32_32_FLOAT = 37, + FMT_32_32_32_32_FLOAT = 38, + FMT_32_AS_8 = 39, + FMT_32_AS_8_8 = 40, + FMT_16_MPEG = 41, + FMT_16_16_MPEG = 42, + FMT_8_INTERLACED = 43, + FMT_32_AS_8_INTERLACED = 44, + FMT_32_AS_8_8_INTERLACED = 45, + FMT_16_INTERLACED = 46, + FMT_16_MPEG_INTERLACED = 47, + FMT_16_16_MPEG_INTERLACED = 48, + FMT_DXN = 49, + FMT_8_8_8_8_AS_16_16_16_16 = 50, + FMT_DXT1_AS_16_16_16_16 = 51, + FMT_DXT2_3_AS_16_16_16_16 = 52, + FMT_DXT4_5_AS_16_16_16_16 = 53, + FMT_2_10_10_10_AS_16_16_16_16 = 54, + FMT_10_11_11_AS_16_16_16_16 = 55, + FMT_11_11_10_AS_16_16_16_16 = 56, + FMT_32_32_32_FLOAT = 57, + FMT_DXT3A = 58, + FMT_DXT5A = 59, + FMT_CTX1 = 60, + FMT_DXT3A_AS_1_1_1_1 = 61 +}; + +#define REG_PERF_MODE_CNT 0x0 +#define REG_PERF_STATE_RESET 0x0 +#define REG_PERF_STATE_ENABLE 0x1 +#define REG_PERF_STATE_FREEZE 0x2 + +#define RB_EDRAM_INFO_EDRAM_SIZE_SIZE 4 +#define RB_EDRAM_INFO_EDRAM_MAPPING_MODE_SIZE 2 +#define RB_EDRAM_INFO_UNUSED0_SIZE 8 +#define RB_EDRAM_INFO_EDRAM_RANGE_SIZE 18 + +struct rb_edram_info_t { + unsigned int edram_size:RB_EDRAM_INFO_EDRAM_SIZE_SIZE; + unsigned int edram_mapping_mode:RB_EDRAM_INFO_EDRAM_MAPPING_MODE_SIZE; + unsigned int unused0:RB_EDRAM_INFO_UNUSED0_SIZE; + unsigned int edram_range:RB_EDRAM_INFO_EDRAM_RANGE_SIZE; +}; + +union reg_rb_edram_info { + unsigned int val; + struct rb_edram_info_t f; +}; + +#define RBBM_READ_ERROR_UNUSED0_SIZE 2 +#define RBBM_READ_ERROR_READ_ADDRESS_SIZE 15 +#define RBBM_READ_ERROR_UNUSED1_SIZE 13 +#define RBBM_READ_ERROR_READ_REQUESTER_SIZE 1 +#define RBBM_READ_ERROR_READ_ERROR_SIZE 1 + +struct rbbm_read_error_t { + unsigned int unused0:RBBM_READ_ERROR_UNUSED0_SIZE; + unsigned int read_address:RBBM_READ_ERROR_READ_ADDRESS_SIZE; + unsigned int unused1:RBBM_READ_ERROR_UNUSED1_SIZE; + unsigned int read_requester:RBBM_READ_ERROR_READ_REQUESTER_SIZE; + unsigned int read_error:RBBM_READ_ERROR_READ_ERROR_SIZE; +}; + +union rbbm_read_error_u { + unsigned int val:32; + struct rbbm_read_error_t f; +}; + +#define CP_RB_CNTL_RB_BUFSZ_SIZE 6 +#define CP_RB_CNTL_UNUSED0_SIZE 2 +#define CP_RB_CNTL_RB_BLKSZ_SIZE 6 +#define CP_RB_CNTL_UNUSED1_SIZE 2 +#define CP_RB_CNTL_BUF_SWAP_SIZE 2 +#define CP_RB_CNTL_UNUSED2_SIZE 2 +#define CP_RB_CNTL_RB_POLL_EN_SIZE 1 +#define CP_RB_CNTL_UNUSED3_SIZE 6 +#define CP_RB_CNTL_RB_NO_UPDATE_SIZE 1 +#define CP_RB_CNTL_UNUSED4_SIZE 3 +#define CP_RB_CNTL_RB_RPTR_WR_ENA_SIZE 1 + +struct cp_rb_cntl_t { + unsigned int rb_bufsz:CP_RB_CNTL_RB_BUFSZ_SIZE; + unsigned int unused0:CP_RB_CNTL_UNUSED0_SIZE; + unsigned int rb_blksz:CP_RB_CNTL_RB_BLKSZ_SIZE; + unsigned int unused1:CP_RB_CNTL_UNUSED1_SIZE; + unsigned int buf_swap:CP_RB_CNTL_BUF_SWAP_SIZE; + unsigned int unused2:CP_RB_CNTL_UNUSED2_SIZE; + unsigned int rb_poll_en:CP_RB_CNTL_RB_POLL_EN_SIZE; + unsigned int unused3:CP_RB_CNTL_UNUSED3_SIZE; + unsigned int rb_no_update:CP_RB_CNTL_RB_NO_UPDATE_SIZE; + unsigned int unused4:CP_RB_CNTL_UNUSED4_SIZE; + unsigned int rb_rptr_wr_ena:CP_RB_CNTL_RB_RPTR_WR_ENA_SIZE; +}; + +union reg_cp_rb_cntl { + unsigned int val:32; + struct cp_rb_cntl_t f; +}; + +#define RB_COLOR_INFO__COLOR_FORMAT_MASK 0x0000000fL +#define RB_COPY_DEST_INFO__COPY_DEST_FORMAT__SHIFT 0x00000004 + + +#define SQ_INT_CNTL__PS_WATCHDOG_MASK 0x00000001L +#define SQ_INT_CNTL__VS_WATCHDOG_MASK 0x00000002L + +#define RBBM_INT_CNTL__RDERR_INT_MASK 0x00000001L +#define RBBM_INT_CNTL__DISPLAY_UPDATE_INT_MASK 0x00000002L +#define RBBM_INT_CNTL__GUI_IDLE_INT_MASK 0x00080000L + +#define RBBM_STATUS__CMDFIFO_AVAIL_MASK 0x0000001fL +#define RBBM_STATUS__TC_BUSY_MASK 0x00000020L +#define RBBM_STATUS__HIRQ_PENDING_MASK 0x00000100L +#define RBBM_STATUS__CPRQ_PENDING_MASK 0x00000200L +#define RBBM_STATUS__CFRQ_PENDING_MASK 0x00000400L +#define RBBM_STATUS__PFRQ_PENDING_MASK 0x00000800L +#define RBBM_STATUS__VGT_BUSY_NO_DMA_MASK 0x00001000L +#define RBBM_STATUS__RBBM_WU_BUSY_MASK 0x00004000L +#define RBBM_STATUS__CP_NRT_BUSY_MASK 0x00010000L +#define RBBM_STATUS__MH_BUSY_MASK 0x00040000L +#define RBBM_STATUS__MH_COHERENCY_BUSY_MASK 0x00080000L +#define RBBM_STATUS__SX_BUSY_MASK 0x00200000L +#define RBBM_STATUS__TPC_BUSY_MASK 0x00400000L +#define RBBM_STATUS__SC_CNTX_BUSY_MASK 0x01000000L +#define RBBM_STATUS__PA_BUSY_MASK 0x02000000L +#define RBBM_STATUS__VGT_BUSY_MASK 0x04000000L +#define RBBM_STATUS__SQ_CNTX17_BUSY_MASK 0x08000000L +#define RBBM_STATUS__SQ_CNTX0_BUSY_MASK 0x10000000L +#define RBBM_STATUS__RB_CNTX_BUSY_MASK 0x40000000L +#define RBBM_STATUS__GUI_ACTIVE_MASK 0x80000000L + +#define CP_INT_CNTL__SW_INT_MASK 0x00080000L +#define CP_INT_CNTL__T0_PACKET_IN_IB_MASK 0x00800000L +#define CP_INT_CNTL__OPCODE_ERROR_MASK 0x01000000L +#define CP_INT_CNTL__PROTECTED_MODE_ERROR_MASK 0x02000000L +#define CP_INT_CNTL__RESERVED_BIT_ERROR_MASK 0x04000000L +#define CP_INT_CNTL__IB_ERROR_MASK 0x08000000L +#define CP_INT_CNTL__IB2_INT_MASK 0x20000000L +#define CP_INT_CNTL__IB1_INT_MASK 0x40000000L +#define CP_INT_CNTL__RB_INT_MASK 0x80000000L + +#define MASTER_INT_SIGNAL__MH_INT_STAT 0x00000020L +#define MASTER_INT_SIGNAL__SQ_INT_STAT 0x04000000L +#define MASTER_INT_SIGNAL__CP_INT_STAT 0x40000000L +#define MASTER_INT_SIGNAL__RBBM_INT_STAT 0x80000000L + +#define RB_EDRAM_INFO__EDRAM_SIZE_MASK 0x0000000fL +#define RB_EDRAM_INFO__EDRAM_RANGE_MASK 0xffffc000L + +#define MH_ARBITER_CONFIG__SAME_PAGE_GRANULARITY__SHIFT 0x00000006 +#define MH_ARBITER_CONFIG__L1_ARB_ENABLE__SHIFT 0x00000007 +#define MH_ARBITER_CONFIG__L1_ARB_HOLD_ENABLE__SHIFT 0x00000008 +#define MH_ARBITER_CONFIG__L2_ARB_CONTROL__SHIFT 0x00000009 +#define MH_ARBITER_CONFIG__PAGE_SIZE__SHIFT 0x0000000a +#define MH_ARBITER_CONFIG__TC_REORDER_ENABLE__SHIFT 0x0000000d +#define MH_ARBITER_CONFIG__TC_ARB_HOLD_ENABLE__SHIFT 0x0000000e +#define MH_ARBITER_CONFIG__IN_FLIGHT_LIMIT_ENABLE__SHIFT 0x0000000f +#define MH_ARBITER_CONFIG__IN_FLIGHT_LIMIT__SHIFT 0x00000010 +#define MH_ARBITER_CONFIG__CP_CLNT_ENABLE__SHIFT 0x00000016 +#define MH_ARBITER_CONFIG__VGT_CLNT_ENABLE__SHIFT 0x00000017 +#define MH_ARBITER_CONFIG__TC_CLNT_ENABLE__SHIFT 0x00000018 +#define MH_ARBITER_CONFIG__RB_CLNT_ENABLE__SHIFT 0x00000019 +#define MH_ARBITER_CONFIG__PA_CLNT_ENABLE__SHIFT 0x0000001a + +#define CP_RB_CNTL__RB_BUFSZ__SHIFT 0x00000000 +#define CP_RB_CNTL__RB_BLKSZ__SHIFT 0x00000008 +#define CP_RB_CNTL__RB_POLL_EN__SHIFT 0x00000014 +#define CP_RB_CNTL__RB_NO_UPDATE__SHIFT 0x0000001b + +#define RB_COLOR_INFO__COLOR_FORMAT__SHIFT 0x00000000 +#define RB_EDRAM_INFO__EDRAM_MAPPING_MODE__SHIFT 0x00000004 +#define RB_EDRAM_INFO__EDRAM_RANGE__SHIFT 0x0000000e + +#define REG_CP_CSQ_IB1_STAT 0x01FE +#define REG_CP_CSQ_IB2_STAT 0x01FF +#define REG_CP_CSQ_RB_STAT 0x01FD +#define REG_CP_DEBUG 0x01FC +#define REG_CP_IB1_BASE 0x0458 +#define REG_CP_IB1_BUFSZ 0x0459 +#define REG_CP_IB2_BASE 0x045A +#define REG_CP_IB2_BUFSZ 0x045B +#define REG_CP_INT_ACK 0x01F4 +#define REG_CP_INT_CNTL 0x01F2 +#define REG_CP_INT_STATUS 0x01F3 +#define REG_CP_ME_CNTL 0x01F6 +#define REG_CP_ME_RAM_DATA 0x01FA +#define REG_CP_ME_RAM_WADDR 0x01F8 +#define REG_CP_ME_STATUS 0x01F7 +#define REG_CP_PFP_UCODE_ADDR 0x00C0 +#define REG_CP_PFP_UCODE_DATA 0x00C1 +#define REG_CP_QUEUE_THRESHOLDS 0x01D5 +#define REG_CP_RB_BASE 0x01C0 +#define REG_CP_RB_CNTL 0x01C1 +#define REG_CP_RB_RPTR 0x01C4 +#define REG_CP_RB_RPTR_ADDR 0x01C3 +#define REG_CP_RB_RPTR_WR 0x01C7 +#define REG_CP_RB_WPTR 0x01C5 +#define REG_CP_RB_WPTR_BASE 0x01C8 +#define REG_CP_RB_WPTR_DELAY 0x01C6 +#define REG_CP_STAT 0x047F +#define REG_CP_STATE_DEBUG_DATA 0x01ED +#define REG_CP_STATE_DEBUG_INDEX 0x01EC +#define REG_CP_ST_BASE 0x044D +#define REG_CP_ST_BUFSZ 0x044E + +#define REG_CP_PERFMON_CNTL 0x0444 +#define REG_CP_PERFCOUNTER_SELECT 0x0445 +#define REG_CP_PERFCOUNTER_LO 0x0446 +#define REG_CP_PERFCOUNTER_HI 0x0447 + +#define REG_RBBM_PERFCOUNTER1_SELECT 0x0395 +#define REG_RBBM_PERFCOUNTER1_HI 0x0398 +#define REG_RBBM_PERFCOUNTER1_LO 0x0397 + +#define REG_MASTER_INT_SIGNAL 0x03B7 + +#define REG_MH_ARBITER_CONFIG 0x0A40 +#define REG_MH_CLNT_INTF_CTRL_CONFIG1 0x0A54 +#define REG_MH_CLNT_INTF_CTRL_CONFIG2 0x0A55 + +#define REG_PA_CL_VPORT_XSCALE 0x210F +#define REG_PA_CL_VPORT_ZOFFSET 0x2114 +#define REG_PA_CL_VPORT_ZSCALE 0x2113 +#define REG_PA_CL_CLIP_CNTL 0x2204 +#define REG_PA_CL_VTE_CNTL 0x2206 +#define REG_PA_SC_AA_MASK 0x2312 +#define REG_PA_SC_LINE_CNTL 0x2300 +#define REG_PA_SC_SCREEN_SCISSOR_BR 0x200F +#define REG_PA_SC_SCREEN_SCISSOR_TL 0x200E +#define REG_PA_SC_VIZ_QUERY 0x2293 +#define REG_PA_SC_VIZ_QUERY_STATUS 0x0C44 +#define REG_PA_SC_WINDOW_OFFSET 0x2080 +#define REG_PA_SC_WINDOW_SCISSOR_BR 0x2082 +#define REG_PA_SC_WINDOW_SCISSOR_TL 0x2081 +#define REG_PA_SU_FACE_DATA 0x0C86 +#define REG_PA_SU_POINT_SIZE 0x2280 +#define REG_PA_SU_LINE_CNTL 0x2282 +#define REG_PA_SU_POLY_OFFSET_BACK_OFFSET 0x2383 +#define REG_PA_SU_POLY_OFFSET_FRONT_SCALE 0x2380 +#define REG_PA_SU_SC_MODE_CNTL 0x2205 + +#define REG_PC_INDEX_OFFSET 0x2102 + +#define REG_RBBM_CNTL 0x003B +#define REG_RBBM_INT_ACK 0x03B6 +#define REG_RBBM_INT_CNTL 0x03B4 +#define REG_RBBM_INT_STATUS 0x03B5 +#define REG_RBBM_PATCH_RELEASE 0x0001 +#define REG_RBBM_PERIPHID1 0x03F9 +#define REG_RBBM_PERIPHID2 0x03FA +#define REG_RBBM_DEBUG 0x039B +#define REG_RBBM_DEBUG_OUT 0x03A0 +#define REG_RBBM_DEBUG_CNTL 0x03A1 +#define REG_RBBM_PM_OVERRIDE1 0x039C +#define REG_RBBM_PM_OVERRIDE2 0x039D +#define REG_RBBM_READ_ERROR 0x03B3 +#define REG_RBBM_SOFT_RESET 0x003C +#define REG_RBBM_STATUS 0x05D0 + +#define REG_RB_COLORCONTROL 0x2202 +#define REG_RB_COLOR_DEST_MASK 0x2326 +#define REG_RB_COLOR_MASK 0x2104 +#define REG_RB_COPY_CONTROL 0x2318 +#define REG_RB_DEPTHCONTROL 0x2200 +#define REG_RB_EDRAM_INFO 0x0F02 +#define REG_RB_MODECONTROL 0x2208 +#define REG_RB_SURFACE_INFO 0x2000 +#define REG_RB_SAMPLE_POS 0x220a + +#define REG_SCRATCH_ADDR 0x01DD +#define REG_SCRATCH_REG0 0x0578 +#define REG_SCRATCH_REG2 0x057A +#define REG_SCRATCH_UMSK 0x01DC + +#define REG_SQ_CF_BOOLEANS 0x4900 +#define REG_SQ_CF_LOOP 0x4908 +#define REG_SQ_GPR_MANAGEMENT 0x0D00 +#define REG_SQ_INST_STORE_MANAGMENT 0x0D02 +#define REG_SQ_INT_ACK 0x0D36 +#define REG_SQ_INT_CNTL 0x0D34 +#define REG_SQ_INT_STATUS 0x0D35 +#define REG_SQ_PROGRAM_CNTL 0x2180 +#define REG_SQ_PS_PROGRAM 0x21F6 +#define REG_SQ_VS_PROGRAM 0x21F7 +#define REG_SQ_WRAPPING_0 0x2183 +#define REG_SQ_WRAPPING_1 0x2184 + +#define REG_VGT_ENHANCE 0x2294 +#define REG_VGT_INDX_OFFSET 0x2102 +#define REG_VGT_MAX_VTX_INDX 0x2100 +#define REG_VGT_MIN_VTX_INDX 0x2101 + +#define REG_TP0_CHICKEN 0x0E1E +#define REG_TC_CNTL_STATUS 0x0E00 +#define REG_PA_SC_AA_CONFIG 0x2301 +#define REG_VGT_VERTEX_REUSE_BLOCK_CNTL 0x2316 +#define REG_SQ_INTERPOLATOR_CNTL 0x2182 +#define REG_RB_DEPTH_INFO 0x2002 +#define REG_COHER_DEST_BASE_0 0x2006 +#define REG_RB_FOG_COLOR 0x2109 +#define REG_RB_STENCILREFMASK_BF 0x210C +#define REG_PA_SC_LINE_STIPPLE 0x2283 +#define REG_SQ_PS_CONST 0x2308 +#define REG_RB_DEPTH_CLEAR 0x231D +#define REG_RB_SAMPLE_COUNT_CTL 0x2324 +#define REG_SQ_CONSTANT_0 0x4000 +#define REG_SQ_FETCH_0 0x4800 + +#define REG_MH_DEBUG_CTRL 0xA4E +#define REG_MH_DEBUG_DATA 0xA4F +#define REG_COHER_BASE_PM4 0xA2A +#define REG_COHER_STATUS_PM4 0xA2B +#define REG_COHER_SIZE_PM4 0xA29 + +#endif /* __A200_REG_H */ diff --git a/drivers/gpu/msm/a220_reg.h b/drivers/gpu/msm/a220_reg.h new file mode 100644 index 00000000000..7cfe7056c7c --- /dev/null +++ b/drivers/gpu/msm/a220_reg.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __A205_REG_H +#define __A205_REG_H + +#define REG_LEIA_PC_INDX_OFFSET REG_VGT_INDX_OFFSET +#define REG_LEIA_PC_VERTEX_REUSE_BLOCK_CNTL REG_VGT_VERTEX_REUSE_BLOCK_CNTL +#define REG_LEIA_PC_MAX_VTX_INDX REG_VGT_MAX_VTX_INDX +#define REG_LEIA_RB_LRZ_VSC_CONTROL 0x2209 +#define REG_LEIA_GRAS_CONTROL 0x2210 +#define REG_LEIA_VSC_BIN_SIZE 0x0C01 +#define REG_LEIA_VSC_PIPE_DATA_LENGTH_7 0x0C1D + +#endif /*__A205_REG_H */ diff --git a/drivers/gpu/msm/adreno.c b/drivers/gpu/msm/adreno.c new file mode 100644 index 00000000000..6b9adf8d4b6 --- /dev/null +++ b/drivers/gpu/msm/adreno.c @@ -0,0 +1,1329 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include + +#include + +#include "kgsl.h" +#include "kgsl_pwrscale.h" +#include "kgsl_cffdump.h" +#include "kgsl_sharedmem.h" + +#include "adreno.h" +#include "adreno_pm4types.h" +#include "adreno_debugfs.h" +#include "adreno_postmortem.h" + +#include "a200_reg.h" + +#define DRIVER_VERSION_MAJOR 3 +#define DRIVER_VERSION_MINOR 1 + +#define KGSL_RBBM_INT_MASK \ + (RBBM_INT_CNTL__RDERR_INT_MASK | \ + RBBM_INT_CNTL__DISPLAY_UPDATE_INT_MASK) + +/* Adreno MH arbiter config*/ +#define ADRENO_CFG_MHARB \ + (0x10 \ + | (0 << MH_ARBITER_CONFIG__SAME_PAGE_GRANULARITY__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__L1_ARB_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__L1_ARB_HOLD_ENABLE__SHIFT) \ + | (0 << MH_ARBITER_CONFIG__L2_ARB_CONTROL__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__PAGE_SIZE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__TC_REORDER_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__TC_ARB_HOLD_ENABLE__SHIFT) \ + | (0 << MH_ARBITER_CONFIG__IN_FLIGHT_LIMIT_ENABLE__SHIFT) \ + | (0x8 << MH_ARBITER_CONFIG__IN_FLIGHT_LIMIT__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__CP_CLNT_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__VGT_CLNT_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__TC_CLNT_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__RB_CLNT_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__PA_CLNT_ENABLE__SHIFT)) + +#define ADRENO_MMU_CONFIG \ + (0x01 \ + | (MMU_CONFIG << MH_MMU_CONFIG__RB_W_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_W_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R0_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R1_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R2_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R3_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R4_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__VGT_R0_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__VGT_R1_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__TC_R_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__PA_W_CLNT_BEHAVIOR__SHIFT)) + +/* max msecs to wait for gpu to finish its operation(s) */ +#define MAX_WAITGPU_SECS (HZ + HZ/2) + +static const struct kgsl_functable adreno_functable; + +static struct adreno_device device_3d0 = { + .dev = { + .name = DEVICE_3D0_NAME, + .id = KGSL_DEVICE_3D0, + .ver_major = DRIVER_VERSION_MAJOR, + .ver_minor = DRIVER_VERSION_MINOR, + .mmu = { + .config = ADRENO_MMU_CONFIG, + /* turn off memory protection unit by setting + acceptable physical address range to include + all pages. */ + .mpu_base = 0x00000000, + .mpu_range = 0xFFFFF000, + }, + .pwrctrl = { + .regulator_name = "fs_gfx3d", + .irq_name = KGSL_3D0_IRQ, + .src_clk_name = "grp_src_clk", + }, + .mutex = __MUTEX_INITIALIZER(device_3d0.dev.mutex), + .state = KGSL_STATE_INIT, + .active_cnt = 0, + .iomemname = KGSL_3D0_REG_MEMORY, + .ftbl = &adreno_functable, + .display_off = { +#ifdef CONFIG_HAS_EARLYSUSPEND + .level = EARLY_SUSPEND_LEVEL_STOP_DRAWING, + .suspend = kgsl_early_suspend_driver, + .resume = kgsl_late_resume_driver, +#endif + }, + }, + .gmemspace = { + .gpu_base = 0, + .sizebytes = SZ_256K, + }, + .pfp_fw = NULL, + .pm4_fw = NULL, + .mharb = ADRENO_CFG_MHARB, +}; + +static int adreno_gmeminit(struct adreno_device *adreno_dev) +{ + struct kgsl_device *device = &adreno_dev->dev; + union reg_rb_edram_info rb_edram_info; + unsigned int gmem_size; + unsigned int edram_value = 0; + + /* make sure edram range is aligned to size */ + BUG_ON(adreno_dev->gmemspace.gpu_base & + (adreno_dev->gmemspace.sizebytes - 1)); + + /* get edram_size value equivalent */ + gmem_size = (adreno_dev->gmemspace.sizebytes >> 14); + while (gmem_size >>= 1) + edram_value++; + + rb_edram_info.val = 0; + + rb_edram_info.f.edram_size = edram_value; + if (!adreno_is_a220(adreno_dev)) + rb_edram_info.f.edram_mapping_mode = 0; /* EDRAM_MAP_UPPER */ + + /* must be aligned to size */ + rb_edram_info.f.edram_range = (adreno_dev->gmemspace.gpu_base >> 14); + + adreno_regwrite(device, REG_RB_EDRAM_INFO, rb_edram_info.val); + + return 0; +} + +static int adreno_gmemclose(struct kgsl_device *device) +{ + adreno_regwrite(device, REG_RB_EDRAM_INFO, 0x00000000); + + return 0; +} + +static void adreno_rbbm_intrcallback(struct kgsl_device *device) +{ + unsigned int status = 0; + unsigned int rderr = 0; + + adreno_regread(device, REG_RBBM_INT_STATUS, &status); + + if (status & RBBM_INT_CNTL__RDERR_INT_MASK) { + union rbbm_read_error_u rerr; + adreno_regread(device, REG_RBBM_READ_ERROR, &rderr); + rerr.val = rderr; + if (rerr.f.read_address == REG_CP_INT_STATUS && + rerr.f.read_error && + rerr.f.read_requester) + KGSL_DRV_WARN(device, + "rbbm read error interrupt: %08x\n", rderr); + else + KGSL_DRV_CRIT(device, + "rbbm read error interrupt: %08x\n", rderr); + } else if (status & RBBM_INT_CNTL__DISPLAY_UPDATE_INT_MASK) { + KGSL_DRV_INFO(device, "rbbm display update interrupt\n"); + } else if (status & RBBM_INT_CNTL__GUI_IDLE_INT_MASK) { + KGSL_DRV_INFO(device, "rbbm gui idle interrupt\n"); + } else { + KGSL_CMD_WARN(device, + "bad bits in REG_CP_INT_STATUS %08x\n", status); + } + + status &= KGSL_RBBM_INT_MASK; + adreno_regwrite(device, REG_RBBM_INT_ACK, status); +} + +irqreturn_t adreno_isr(int irq, void *data) +{ + irqreturn_t result = IRQ_NONE; + struct kgsl_device *device; + unsigned int status; + + device = (struct kgsl_device *) data; + + BUG_ON(device == NULL); + BUG_ON(device->regspace.sizebytes == 0); + BUG_ON(device->regspace.mmio_virt_base == 0); + + adreno_regread(device, REG_MASTER_INT_SIGNAL, &status); + + if (status & MASTER_INT_SIGNAL__MH_INT_STAT) { + kgsl_mh_intrcallback(device); + result = IRQ_HANDLED; + } + + if (status & MASTER_INT_SIGNAL__CP_INT_STAT) { + kgsl_cp_intrcallback(device); + result = IRQ_HANDLED; + } + + if (status & MASTER_INT_SIGNAL__RBBM_INT_STAT) { + adreno_rbbm_intrcallback(device); + result = IRQ_HANDLED; + } + + if (device->requested_state == KGSL_STATE_NONE) { + if (device->pwrctrl.nap_allowed == true) { + device->requested_state = KGSL_STATE_NAP; + queue_work(device->work_queue, &device->idle_check_ws); + } else if (device->pwrscale.policy != NULL) { + queue_work(device->work_queue, &device->idle_check_ws); + } + } + + /* Reset the time-out in our idle timer */ + mod_timer(&device->idle_timer, + jiffies + device->pwrctrl.interval_timeout); + return result; +} + +static int adreno_cleanup_pt(struct kgsl_device *device, + struct kgsl_pagetable *pagetable) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + + kgsl_mmu_unmap(pagetable, &rb->buffer_desc); + + kgsl_mmu_unmap(pagetable, &rb->memptrs_desc); + + kgsl_mmu_unmap(pagetable, &device->memstore); + + kgsl_mmu_unmap(pagetable, &device->mmu.dummyspace); + + return 0; +} + +static int adreno_setup_pt(struct kgsl_device *device, + struct kgsl_pagetable *pagetable) +{ + int result = 0; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + + BUG_ON(rb->buffer_desc.physaddr == 0); + BUG_ON(rb->memptrs_desc.physaddr == 0); + BUG_ON(device->memstore.physaddr == 0); +#ifdef CONFIG_MSM_KGSL_MMU + BUG_ON(device->mmu.dummyspace.physaddr == 0); +#endif + result = kgsl_mmu_map_global(pagetable, &rb->buffer_desc, + GSL_PT_PAGE_RV); + if (result) + goto error; + + result = kgsl_mmu_map_global(pagetable, &rb->memptrs_desc, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + if (result) + goto unmap_buffer_desc; + + result = kgsl_mmu_map_global(pagetable, &device->memstore, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + if (result) + goto unmap_memptrs_desc; + + result = kgsl_mmu_map_global(pagetable, &device->mmu.dummyspace, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + if (result) + goto unmap_memstore_desc; + + return result; + +unmap_memstore_desc: + kgsl_mmu_unmap(pagetable, &device->memstore); + +unmap_memptrs_desc: + kgsl_mmu_unmap(pagetable, &rb->memptrs_desc); + +unmap_buffer_desc: + kgsl_mmu_unmap(pagetable, &rb->buffer_desc); + +error: + return result; +} + +static void adreno_setstate(struct kgsl_device *device, uint32_t flags) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + unsigned int link[32]; + unsigned int *cmds = &link[0]; + int sizedwords = 0; + unsigned int mh_mmu_invalidate = 0x00000003; /*invalidate all and tc */ + + if (!kgsl_mmu_enabled() || !flags) + return; + + /* If possible, then set the state via the command stream to avoid + a CPU idle. Otherwise, use the default setstate which uses register + writes */ + + if (adreno_dev->drawctxt_active) { + if (flags & KGSL_MMUFLAGS_PTUPDATE) { + /* wait for graphics pipe to be idle */ + *cmds++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + + /* set page table base */ + *cmds++ = pm4_type0_packet(MH_MMU_PT_BASE, 1); + *cmds++ = device->mmu.hwpagetable->base.gpuaddr; + sizedwords += 4; + } + + if (flags & KGSL_MMUFLAGS_TLBFLUSH) { + if (!(flags & KGSL_MMUFLAGS_PTUPDATE)) { + *cmds++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, + 1); + *cmds++ = 0x00000000; + sizedwords += 2; + } + *cmds++ = pm4_type0_packet(MH_MMU_INVALIDATE, 1); + *cmds++ = mh_mmu_invalidate; + sizedwords += 2; + } + + if (flags & KGSL_MMUFLAGS_PTUPDATE && + !adreno_is_a220(adreno_dev)) { + /* HW workaround: to resolve MMU page fault interrupts + * caused by the VGT.It prevents the CP PFP from filling + * the VGT DMA request fifo too early,thereby ensuring + * that the VGT will not fetch vertex/bin data until + * after the page table base register has been updated. + * + * Two null DRAW_INDX_BIN packets are inserted right + * after the page table base update, followed by a + * wait for idle. The null packets will fill up the + * VGT DMA request fifo and prevent any further + * vertex/bin updates from occurring until the wait + * has finished. */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = (0x4 << 16) | + (REG_PA_SU_SC_MODE_CNTL - 0x2000); + *cmds++ = 0; /* disable faceness generation */ + *cmds++ = pm4_type3_packet(PM4_SET_BIN_BASE_OFFSET, 1); + *cmds++ = device->mmu.dummyspace.gpuaddr; + *cmds++ = pm4_type3_packet(PM4_DRAW_INDX_BIN, 6); + *cmds++ = 0; /* viz query info */ + *cmds++ = 0x0003C004; /* draw indicator */ + *cmds++ = 0; /* bin base */ + *cmds++ = 3; /* bin size */ + *cmds++ = device->mmu.dummyspace.gpuaddr; /* dma base */ + *cmds++ = 6; /* dma size */ + *cmds++ = pm4_type3_packet(PM4_DRAW_INDX_BIN, 6); + *cmds++ = 0; /* viz query info */ + *cmds++ = 0x0003C004; /* draw indicator */ + *cmds++ = 0; /* bin base */ + *cmds++ = 3; /* bin size */ + /* dma base */ + *cmds++ = device->mmu.dummyspace.gpuaddr; + *cmds++ = 6; /* dma size */ + *cmds++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmds++ = 0x00000000; + sizedwords += 21; + } + + if (flags & (KGSL_MMUFLAGS_PTUPDATE | KGSL_MMUFLAGS_TLBFLUSH)) { + *cmds++ = pm4_type3_packet(PM4_INVALIDATE_STATE, 1); + *cmds++ = 0x7fff; /* invalidate all base pointers */ + sizedwords += 2; + } + + adreno_ringbuffer_issuecmds(device, KGSL_CMD_FLAGS_PMODE, + &link[0], sizedwords); + } else + kgsl_default_setstate(device, flags); +} + +static unsigned int +adreno_getchipid(struct kgsl_device *device) +{ + unsigned int chipid = 0; + unsigned int coreid, majorid, minorid, patchid, revid; + + adreno_regread(device, REG_RBBM_PERIPHID1, &coreid); + adreno_regread(device, REG_RBBM_PERIPHID2, &majorid); + adreno_regread(device, REG_RBBM_PATCH_RELEASE, &revid); + + /* + * adreno 22x gpus are indicated by coreid 2, + * but REG_RBBM_PERIPHID1 always contains 0 for this field + */ + if (cpu_is_msm8960() || cpu_is_msm8x60()) + chipid = 2 << 24; + else + chipid = (coreid & 0xF) << 24; + + chipid |= ((majorid >> 4) & 0xF) << 16; + + minorid = ((revid >> 0) & 0xFF); + + patchid = ((revid >> 16) & 0xFF); + + /* 8x50 returns 0 for patch release, but it should be 1 */ + if (cpu_is_qsd8x50()) + patchid = 1; + /* userspace isn't prepared to deal with patch id for these chips yet */ + else if (cpu_is_msm8960() || cpu_is_msm8x60()) + patchid = 0; + + chipid |= (minorid << 8) | patchid; + + return chipid; +} + +/* all chipid fields are 8 bits wide so 256 won't occur in a real chipid */ +#define DONT_CARE 256 +static const struct { + unsigned int core; + unsigned int major; + unsigned int minor; + enum adreno_gpurev gpurev; +} gpurev_table[] = { + /* major and minor may be DONT_CARE, but core must not be */ + {0, 2, DONT_CARE, ADRENO_REV_A200}, + {0, 1, 0, ADRENO_REV_A205}, + {2, 1, DONT_CARE, ADRENO_REV_A220}, + {2, 2, DONT_CARE, ADRENO_REV_A225}, +}; + +static inline bool _rev_match(unsigned int id, unsigned int entry) +{ + return (entry == DONT_CARE || entry == id); +} +#undef DONT_CARE + +static void +adreno_identify_gpu(struct adreno_device *adreno_dev) +{ + enum adreno_gpurev gpurev = ADRENO_REV_UNKNOWN; + unsigned int i, core, major, minor; + + adreno_dev->chip_id = adreno_getchipid(&adreno_dev->dev); + + core = (adreno_dev->chip_id >> 24) & 0xff; + major = (adreno_dev->chip_id >> 16) & 0xff; + minor = (adreno_dev->chip_id >> 8) & 0xff; + + for (i = 0; i < ARRAY_SIZE(gpurev_table); i++) { + if (core == gpurev_table[i].core && + _rev_match(major, gpurev_table[i].major) && + _rev_match(minor, gpurev_table[i].minor)) { + gpurev = gpurev_table[i].gpurev; + break; + } + } + + adreno_dev->gpurev = gpurev; +} + +static int __devinit +adreno_probe(struct platform_device *pdev) +{ + struct kgsl_device *device; + struct adreno_device *adreno_dev; + int status = -EINVAL; + + device = (struct kgsl_device *)pdev->id_entry->driver_data; + adreno_dev = ADRENO_DEVICE(device); + device->parentdev = &pdev->dev; + + init_completion(&device->recovery_gate); + + status = adreno_ringbuffer_init(device); + if (status != 0) + goto error; + + status = kgsl_device_platform_probe(device, adreno_isr); + if (status) + goto error_close_rb; + + adreno_debugfs_init(device); + + kgsl_pwrscale_init(device); + kgsl_pwrscale_attach_policy(device, ADRENO_DEFAULT_PWRSCALE_POLICY); + + device->flags &= ~KGSL_FLAGS_SOFT_RESET; + return 0; + +error_close_rb: + adreno_ringbuffer_close(&adreno_dev->ringbuffer); +error: + device->parentdev = NULL; + return status; +} + +static int __devexit adreno_remove(struct platform_device *pdev) +{ + struct kgsl_device *device; + struct adreno_device *adreno_dev; + + device = (struct kgsl_device *)pdev->id_entry->driver_data; + adreno_dev = ADRENO_DEVICE(device); + + kgsl_pwrscale_detach_policy(device); + kgsl_pwrscale_close(device); + + adreno_ringbuffer_close(&adreno_dev->ringbuffer); + kgsl_device_platform_remove(device); + + return 0; +} + +static int adreno_start(struct kgsl_device *device, unsigned int init_ram) +{ + int status = -EINVAL; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + int init_reftimestamp = 0x7fffffff; + + device->state = KGSL_STATE_INIT; + device->requested_state = KGSL_STATE_NONE; + + /* Power up the device */ + kgsl_pwrctrl_enable(device); + + /* Identify the specific GPU */ + adreno_identify_gpu(adreno_dev); + + if (kgsl_mmu_start(device)) + goto error_clk_off; + + /*We need to make sure all blocks are powered up and clocked before + *issuing a soft reset. The overrides will then be turned off (set to 0) + */ + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE1, 0xfffffffe); + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE2, 0xffffffff); + + /* Only reset CP block if all blocks have previously been reset */ + if (!(device->flags & KGSL_FLAGS_SOFT_RESET) || + !adreno_is_a220(adreno_dev)) { + adreno_regwrite(device, REG_RBBM_SOFT_RESET, 0xFFFFFFFF); + device->flags |= KGSL_FLAGS_SOFT_RESET; + } else + adreno_regwrite(device, REG_RBBM_SOFT_RESET, 0x00000001); + + /* The core is in an indeterminate state until the reset completes + * after 30ms. + */ + msleep(30); + + adreno_regwrite(device, REG_RBBM_SOFT_RESET, 0x00000000); + + adreno_regwrite(device, REG_RBBM_CNTL, 0x00004442); + + adreno_regwrite(device, REG_MH_ARBITER_CONFIG, + adreno_dev->mharb); + + /* Remove 1k boundary check in z470 to avoid GPU hang. + Notice that, this solution won't work if both EBI and SMI are used */ + if (adreno_is_a220(adreno_dev)) { + adreno_regwrite(device, REG_MH_CLNT_INTF_CTRL_CONFIG1, + 0x00032f07); + } + + adreno_regwrite(device, REG_SQ_VS_PROGRAM, 0x00000000); + adreno_regwrite(device, REG_SQ_PS_PROGRAM, 0x00000000); + + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE1, 0); + if (!adreno_is_a220(adreno_dev)) + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE2, 0); + else + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE2, 0x80); + + kgsl_sharedmem_writel(&device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(ref_wait_ts), + init_reftimestamp); + + adreno_regwrite(device, REG_RBBM_DEBUG, 0x00080000); + + /* Make sure interrupts are disabled */ + + adreno_regwrite(device, REG_RBBM_INT_CNTL, 0); + adreno_regwrite(device, REG_CP_INT_CNTL, 0); + adreno_regwrite(device, REG_SQ_INT_CNTL, 0); + + if (adreno_is_a220(adreno_dev)) + adreno_dev->gmemspace.sizebytes = SZ_512K; + else + adreno_dev->gmemspace.sizebytes = SZ_256K; + adreno_gmeminit(adreno_dev); + + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_ON); + + status = adreno_ringbuffer_start(&adreno_dev->ringbuffer, init_ram); + if (status != 0) + goto error_irq_off; + + mod_timer(&device->idle_timer, jiffies + FIRST_TIMEOUT); + return status; + +error_irq_off: + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); +error_clk_off: + kgsl_pwrctrl_disable(device); + kgsl_mmu_stop(device); + + return status; +} + +static int adreno_stop(struct kgsl_device *device) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + del_timer(&device->idle_timer); + + adreno_dev->drawctxt_active = NULL; + + adreno_ringbuffer_stop(&adreno_dev->ringbuffer); + + adreno_gmemclose(device); + + kgsl_mmu_stop(device); + + /* Power down the device */ + kgsl_pwrctrl_disable(device); + + return 0; +} + +static int +adreno_recover_hang(struct kgsl_device *device) +{ + int ret; + unsigned int *rb_buffer; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + unsigned int timestamp; + unsigned int num_rb_contents; + unsigned int bad_context; + unsigned int reftimestamp; + unsigned int enable_ts; + unsigned int soptimestamp; + unsigned int eoptimestamp; + struct adreno_context *drawctxt; + + KGSL_DRV_ERR(device, "Starting recovery from 3D GPU hang....\n"); + rb_buffer = vmalloc(rb->buffer_desc.size); + if (!rb_buffer) { + KGSL_MEM_ERR(device, + "Failed to allocate memory for recovery: %x\n", + rb->buffer_desc.size); + return -ENOMEM; + } + /* Extract valid contents from rb which can stil be executed after + * hang */ + ret = adreno_ringbuffer_extract(rb, rb_buffer, &num_rb_contents); + if (ret) + goto done; + timestamp = rb->timestamp; + KGSL_DRV_ERR(device, "Last issued timestamp: %x\n", timestamp); + kgsl_sharedmem_readl(&device->memstore, &bad_context, + KGSL_DEVICE_MEMSTORE_OFFSET(current_context)); + kgsl_sharedmem_readl(&device->memstore, &reftimestamp, + KGSL_DEVICE_MEMSTORE_OFFSET(ref_wait_ts)); + kgsl_sharedmem_readl(&device->memstore, &enable_ts, + KGSL_DEVICE_MEMSTORE_OFFSET(ts_cmp_enable)); + kgsl_sharedmem_readl(&device->memstore, &soptimestamp, + KGSL_DEVICE_MEMSTORE_OFFSET(soptimestamp)); + kgsl_sharedmem_readl(&device->memstore, &eoptimestamp, + KGSL_DEVICE_MEMSTORE_OFFSET(eoptimestamp)); + /* Make sure memory is synchronized before restarting the GPU */ + mb(); + KGSL_CTXT_ERR(device, + "Context that caused a GPU hang: %x\n", bad_context); + /* restart device */ + ret = adreno_stop(device); + if (ret) + goto done; + ret = adreno_start(device, true); + if (ret) + goto done; + KGSL_DRV_ERR(device, "Device has been restarted after hang\n"); + /* Restore timestamp states */ + kgsl_sharedmem_writel(&device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(soptimestamp), + soptimestamp); + kgsl_sharedmem_writel(&device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(eoptimestamp), + eoptimestamp); + kgsl_sharedmem_writel(&device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(soptimestamp), + soptimestamp); + if (num_rb_contents) { + kgsl_sharedmem_writel(&device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(ref_wait_ts), + reftimestamp); + kgsl_sharedmem_writel(&device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(ts_cmp_enable), + enable_ts); + } + /* Make sure all writes are posted before the GPU reads them */ + wmb(); + /* Mark the invalid context so no more commands are accepted from + * that context */ + + drawctxt = (struct adreno_context *) bad_context; + + KGSL_CTXT_ERR(device, + "Context that caused a GPU hang: %x\n", bad_context); + + drawctxt->flags |= CTXT_FLAGS_GPU_HANG; + + /* Restore valid commands in ringbuffer */ + adreno_ringbuffer_restore(rb, rb_buffer, num_rb_contents); + rb->timestamp = timestamp; +done: + vfree(rb_buffer); + return ret; +} + +static int +adreno_dump_and_recover(struct kgsl_device *device) +{ + static int recovery; + int result = -ETIMEDOUT; + + if (device->state == KGSL_STATE_HUNG) + goto done; + if (device->state == KGSL_STATE_DUMP_AND_RECOVER && !recovery) { + mutex_unlock(&device->mutex); + wait_for_completion(&device->recovery_gate); + mutex_lock(&device->mutex); + if (!(device->state & KGSL_STATE_HUNG)) + /* recovery success */ + result = 0; + } else { + INIT_COMPLETION(device->recovery_gate); + /* Detected a hang - trigger an automatic dump */ + adreno_postmortem_dump(device, 0); + if (!recovery) { + recovery = 1; + result = adreno_recover_hang(device); + if (result) + device->state = KGSL_STATE_HUNG; + recovery = 0; + complete_all(&device->recovery_gate); + } else + KGSL_DRV_ERR(device, + "Cannot recover from another hang while " + "recovering from a hang\n"); + } +done: + return result; +} + +static int adreno_getproperty(struct kgsl_device *device, + enum kgsl_property_type type, + void *value, + unsigned int sizebytes) +{ + int status = -EINVAL; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + switch (type) { + case KGSL_PROP_DEVICE_INFO: + { + struct kgsl_devinfo devinfo; + + if (sizebytes != sizeof(devinfo)) { + status = -EINVAL; + break; + } + + memset(&devinfo, 0, sizeof(devinfo)); + devinfo.device_id = device->id+1; + devinfo.chip_id = adreno_dev->chip_id; + devinfo.mmu_enabled = kgsl_mmu_enabled(); + devinfo.gpu_id = adreno_dev->gpurev; + devinfo.gmem_gpubaseaddr = adreno_dev->gmemspace. + gpu_base; + devinfo.gmem_sizebytes = adreno_dev->gmemspace. + sizebytes; + + if (copy_to_user(value, &devinfo, sizeof(devinfo)) != + 0) { + status = -EFAULT; + break; + } + status = 0; + } + break; + case KGSL_PROP_DEVICE_SHADOW: + { + struct kgsl_shadowprop shadowprop; + + if (sizebytes != sizeof(shadowprop)) { + status = -EINVAL; + break; + } + memset(&shadowprop, 0, sizeof(shadowprop)); + if (device->memstore.hostptr) { + /*NOTE: with mmu enabled, gpuaddr doesn't mean + * anything to mmap(). + */ + shadowprop.gpuaddr = device->memstore.physaddr; + shadowprop.size = device->memstore.size; + /* GSL needs this to be set, even if it + appears to be meaningless */ + shadowprop.flags = KGSL_FLAGS_INITIALIZED; + } + if (copy_to_user(value, &shadowprop, + sizeof(shadowprop))) { + status = -EFAULT; + break; + } + status = 0; + } + break; + case KGSL_PROP_MMU_ENABLE: + { +#ifdef CONFIG_MSM_KGSL_MMU + int mmuProp = 1; +#else + int mmuProp = 0; +#endif + if (sizebytes != sizeof(int)) { + status = -EINVAL; + break; + } + if (copy_to_user(value, &mmuProp, sizeof(mmuProp))) { + status = -EFAULT; + break; + } + status = 0; + } + break; + case KGSL_PROP_INTERRUPT_WAITS: + { + int int_waits = 1; + if (sizebytes != sizeof(int)) { + status = -EINVAL; + break; + } + if (copy_to_user(value, &int_waits, sizeof(int))) { + status = -EFAULT; + break; + } + status = 0; + } + break; + default: + status = -EINVAL; + } + + return status; +} + +/* Caller must hold the device mutex. */ +int adreno_idle(struct kgsl_device *device, unsigned int timeout) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + unsigned int rbbm_status; + unsigned long wait_time = jiffies + MAX_WAITGPU_SECS; + + kgsl_cffdump_regpoll(device->id, REG_RBBM_STATUS << 2, + 0x00000000, 0x80000000); + /* first, wait until the CP has consumed all the commands in + * the ring buffer + */ +retry: + if (rb->flags & KGSL_FLAGS_STARTED) { + do { + GSL_RB_GET_READPTR(rb, &rb->rptr); + if (time_after(jiffies, wait_time)) { + KGSL_DRV_ERR(device, "rptr: %x, wptr: %x\n", + rb->rptr, rb->wptr); + goto err; + } + } while (rb->rptr != rb->wptr); + } + + /* now, wait for the GPU to finish its operations */ + wait_time = jiffies + MAX_WAITGPU_SECS; + while (time_before(jiffies, wait_time)) { + adreno_regread(device, REG_RBBM_STATUS, &rbbm_status); + if (rbbm_status == 0x110) + return 0; + } + +err: + KGSL_DRV_ERR(device, "spun too long waiting for RB to idle\n"); + if (!adreno_dump_and_recover(device)) { + wait_time = jiffies + MAX_WAITGPU_SECS; + goto retry; + } + return -ETIMEDOUT; +} + +static unsigned int adreno_isidle(struct kgsl_device *device) +{ + int status = false; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + unsigned int rbbm_status; + + if (rb->flags & KGSL_FLAGS_STARTED) { + /* Is the ring buffer is empty? */ + GSL_RB_GET_READPTR(rb, &rb->rptr); + if (!device->active_cnt && (rb->rptr == rb->wptr)) { + /* Is the core idle? */ + adreno_regread(device, REG_RBBM_STATUS, + &rbbm_status); + if (rbbm_status == 0x110) + status = true; + } + } else { + KGSL_DRV_ERR(device, "ringbuffer not started\n"); + BUG(); + } + return status; +} + +/* Caller must hold the device mutex. */ +static int adreno_suspend_context(struct kgsl_device *device) +{ + int status = 0; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + /* switch to NULL ctxt */ + if (adreno_dev->drawctxt_active != NULL) { + adreno_drawctxt_switch(adreno_dev, NULL, 0); + status = adreno_idle(device, KGSL_TIMEOUT_DEFAULT); + } + + return status; +} + +uint8_t *kgsl_sharedmem_convertaddr(struct kgsl_device *device, + unsigned int pt_base, unsigned int gpuaddr, unsigned int *size) +{ + uint8_t *result = NULL; + struct kgsl_mem_entry *entry; + struct kgsl_process_private *priv; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *ringbuffer = &adreno_dev->ringbuffer; + + if (kgsl_gpuaddr_in_memdesc(&ringbuffer->buffer_desc, gpuaddr)) { + return kgsl_gpuaddr_to_vaddr(&ringbuffer->buffer_desc, + gpuaddr, size); + } + + if (kgsl_gpuaddr_in_memdesc(&ringbuffer->memptrs_desc, gpuaddr)) { + return kgsl_gpuaddr_to_vaddr(&ringbuffer->memptrs_desc, + gpuaddr, size); + } + + if (kgsl_gpuaddr_in_memdesc(&device->memstore, gpuaddr)) { + return kgsl_gpuaddr_to_vaddr(&device->memstore, + gpuaddr, size); + } + + mutex_lock(&kgsl_driver.process_mutex); + list_for_each_entry(priv, &kgsl_driver.process_list, list) { + if (pt_base != 0 + && priv->pagetable + && priv->pagetable->base.gpuaddr != pt_base) { + continue; + } + + spin_lock(&priv->mem_lock); + entry = kgsl_sharedmem_find_region(priv, gpuaddr, + sizeof(unsigned int)); + if (entry) { + result = kgsl_gpuaddr_to_vaddr(&entry->memdesc, + gpuaddr, size); + spin_unlock(&priv->mem_lock); + mutex_unlock(&kgsl_driver.process_mutex); + return result; + } + spin_unlock(&priv->mem_lock); + } + mutex_unlock(&kgsl_driver.process_mutex); + + BUG_ON(!mutex_is_locked(&device->mutex)); + list_for_each_entry(entry, &device->memqueue, list) { + if (kgsl_gpuaddr_in_memdesc(&entry->memdesc, gpuaddr)) { + result = kgsl_gpuaddr_to_vaddr(&entry->memdesc, + gpuaddr, size); + break; + } + + } + return result; +} + +void adreno_regread(struct kgsl_device *device, unsigned int offsetwords, + unsigned int *value) +{ + unsigned int *reg; + BUG_ON(offsetwords*sizeof(uint32_t) >= device->regspace.sizebytes); + reg = (unsigned int *)(device->regspace.mmio_virt_base + + (offsetwords << 2)); + + if (!in_interrupt()) + kgsl_pre_hwaccess(device); + + /*ensure this read finishes before the next one. + * i.e. act like normal readl() */ + *value = __raw_readl(reg); + rmb(); +} + +void adreno_regwrite(struct kgsl_device *device, unsigned int offsetwords, + unsigned int value) +{ + unsigned int *reg; + + BUG_ON(offsetwords*sizeof(uint32_t) >= device->regspace.sizebytes); + + if (!in_interrupt()) + kgsl_pre_hwaccess(device); + + kgsl_cffdump_regwrite(device->id, offsetwords << 2, value); + reg = (unsigned int *)(device->regspace.mmio_virt_base + + (offsetwords << 2)); + + /*ensure previous writes post before this one, + * i.e. act like normal writel() */ + wmb(); + __raw_writel(value, reg); +} + +static int kgsl_check_interrupt_timestamp(struct kgsl_device *device, + unsigned int timestamp) +{ + int status; + unsigned int ref_ts, enableflag; + + status = kgsl_check_timestamp(device, timestamp); + if (!status) { + mutex_lock(&device->mutex); + kgsl_sharedmem_readl(&device->memstore, &enableflag, + KGSL_DEVICE_MEMSTORE_OFFSET(ts_cmp_enable)); + mb(); + + if (enableflag) { + kgsl_sharedmem_readl(&device->memstore, &ref_ts, + KGSL_DEVICE_MEMSTORE_OFFSET(ref_wait_ts)); + mb(); + if (timestamp_cmp(ref_ts, timestamp)) { + kgsl_sharedmem_writel(&device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(ref_wait_ts), + timestamp); + wmb(); + } + } else { + unsigned int cmds[2]; + kgsl_sharedmem_writel(&device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(ref_wait_ts), + timestamp); + enableflag = 1; + kgsl_sharedmem_writel(&device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(ts_cmp_enable), + enableflag); + wmb(); + /* submit a dummy packet so that even if all + * commands upto timestamp get executed we will still + * get an interrupt */ + cmds[0] = pm4_type3_packet(PM4_NOP, 1); + cmds[1] = 0; + adreno_ringbuffer_issuecmds(device, 0, &cmds[0], 2); + } + mutex_unlock(&device->mutex); + } + + return status; +} + +/* + wait_io_event_interruptible_timeout checks for the exit condition before + placing a process in wait q. For conditional interrupts we expect the + process to already be in its wait q when its exit condition checking + function is called. +*/ +#define kgsl_wait_io_event_interruptible_timeout(wq, condition, timeout)\ +({ \ + long __ret = timeout; \ + __wait_io_event_interruptible_timeout(wq, condition, __ret); \ + __ret; \ +}) + +/* MUST be called with the device mutex held */ +static int adreno_waittimestamp(struct kgsl_device *device, + unsigned int timestamp, + unsigned int msecs) +{ + long status = 0; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + if (timestamp != adreno_dev->ringbuffer.timestamp && + timestamp_cmp(timestamp, + adreno_dev->ringbuffer.timestamp)) { + KGSL_DRV_ERR(device, "Cannot wait for invalid ts: %x, " + "rb->timestamp: %x\n", + timestamp, adreno_dev->ringbuffer.timestamp); + status = -EINVAL; + goto done; + } + if (!kgsl_check_timestamp(device, timestamp)) { + mutex_unlock(&device->mutex); + /* We need to make sure that the process is placed in wait-q + * before its condition is called */ + status = kgsl_wait_io_event_interruptible_timeout( + device->wait_queue, + kgsl_check_interrupt_timestamp(device, + timestamp), msecs_to_jiffies(msecs)); + mutex_lock(&device->mutex); + + if (status > 0) + status = 0; + else if (status == 0) { + if (!kgsl_check_timestamp(device, timestamp)) { + status = -ETIMEDOUT; + KGSL_DRV_ERR(device, + "Device hang detected while waiting " + "for timestamp: %x, last " + "submitted(rb->timestamp): %x, wptr: " + "%x\n", timestamp, + adreno_dev->ringbuffer.timestamp, + adreno_dev->ringbuffer.wptr); + if (!adreno_dump_and_recover(device)) { + /* wait for idle after recovery as the + * timestamp that this process wanted + * to wait on may be invalid */ + if (!adreno_idle(device, + KGSL_TIMEOUT_DEFAULT)) + status = 0; + } + } + } + } + +done: + return (int)status; +} + +static unsigned int adreno_readtimestamp(struct kgsl_device *device, + enum kgsl_timestamp_type type) +{ + unsigned int timestamp = 0; + + if (type == KGSL_TIMESTAMP_CONSUMED) + adreno_regread(device, REG_CP_TIMESTAMP, ×tamp); + else if (type == KGSL_TIMESTAMP_RETIRED) + kgsl_sharedmem_readl(&device->memstore, ×tamp, + KGSL_DEVICE_MEMSTORE_OFFSET(eoptimestamp)); + rmb(); + + return timestamp; +} + +static long adreno_ioctl(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + struct kgsl_drawctxt_set_bin_base_offset *binbase; + struct kgsl_context *context; + + switch (cmd) { + case IOCTL_KGSL_DRAWCTXT_SET_BIN_BASE_OFFSET: + binbase = data; + + context = kgsl_find_context(dev_priv, binbase->drawctxt_id); + if (context) { + adreno_drawctxt_set_bin_base_offset( + dev_priv->device, context, binbase->offset); + } else { + result = -EINVAL; + KGSL_DRV_ERR(dev_priv->device, + "invalid drawctxt drawctxt_id %d " + "device_id=%d\n", + binbase->drawctxt_id, dev_priv->device->id); + } + break; + + default: + KGSL_DRV_INFO(dev_priv->device, + "invalid ioctl code %08x\n", cmd); + result = -EINVAL; + break; + } + return result; + +} + +static inline s64 adreno_ticks_to_us(u32 ticks, u32 gpu_freq) +{ + gpu_freq /= 1000000; + return ticks / gpu_freq; +} + +static void adreno_power_stats(struct kgsl_device *device, + struct kgsl_power_stats *stats) +{ + unsigned int reg; + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + + /* In order to calculate idle you have to have run the algorithm * + * at least once to get a start time. */ + if (pwr->time != 0) { + s64 tmp; + /* Stop the performance moniter and read the current * + * busy cycles. */ + adreno_regwrite(device, + REG_CP_PERFMON_CNTL, + REG_PERF_MODE_CNT | + REG_PERF_STATE_FREEZE); + adreno_regread(device, REG_RBBM_PERFCOUNTER1_LO, ®); + tmp = ktime_to_us(ktime_get()); + stats->total_time = tmp - pwr->time; + pwr->time = tmp; + stats->busy_time = adreno_ticks_to_us(reg, device->pwrctrl. + pwrlevels[device->pwrctrl.active_pwrlevel]. + gpu_freq); + + adreno_regwrite(device, + REG_CP_PERFMON_CNTL, + REG_PERF_MODE_CNT | + REG_PERF_STATE_RESET); + } else { + stats->total_time = 0; + stats->busy_time = 0; + pwr->time = ktime_to_us(ktime_get()); + } + + /* re-enable the performance moniters */ + adreno_regread(device, REG_RBBM_PM_OVERRIDE2, ®); + adreno_regwrite(device, REG_RBBM_PM_OVERRIDE2, (reg | 0x40)); + adreno_regwrite(device, REG_RBBM_PERFCOUNTER1_SELECT, 0x1); + adreno_regwrite(device, + REG_CP_PERFMON_CNTL, + REG_PERF_MODE_CNT | REG_PERF_STATE_ENABLE); +} + +void adreno_irqctrl(struct kgsl_device *device, int state) +{ + /* Enable GPU and GPUMMU interrupts */ + + if (state) { + adreno_regwrite(device, REG_RBBM_INT_CNTL, KGSL_RBBM_INT_MASK); + adreno_regwrite(device, REG_CP_INT_CNTL, KGSL_CP_INT_MASK); + adreno_regwrite(device, MH_INTERRUPT_MASK, KGSL_MMU_INT_MASK); + } else { + adreno_regwrite(device, REG_RBBM_INT_CNTL, 0); + adreno_regwrite(device, REG_CP_INT_CNTL, 0); + adreno_regwrite(device, MH_INTERRUPT_MASK, 0); + } +} + +static const struct kgsl_functable adreno_functable = { + /* Mandatory functions */ + .regread = adreno_regread, + .regwrite = adreno_regwrite, + .idle = adreno_idle, + .isidle = adreno_isidle, + .suspend_context = adreno_suspend_context, + .start = adreno_start, + .stop = adreno_stop, + .getproperty = adreno_getproperty, + .waittimestamp = adreno_waittimestamp, + .readtimestamp = adreno_readtimestamp, + .issueibcmds = adreno_ringbuffer_issueibcmds, + .ioctl = adreno_ioctl, + .setup_pt = adreno_setup_pt, + .cleanup_pt = adreno_cleanup_pt, + .power_stats = adreno_power_stats, + .irqctrl = adreno_irqctrl, + /* Optional functions */ + .setstate = adreno_setstate, + .drawctxt_create = adreno_drawctxt_create, + .drawctxt_destroy = adreno_drawctxt_destroy, +}; + +static struct platform_device_id adreno_id_table[] = { + { DEVICE_3D0_NAME, (kernel_ulong_t)&device_3d0.dev, }, + { }, +}; +MODULE_DEVICE_TABLE(platform, adreno_id_table); + +static struct platform_driver adreno_platform_driver = { + .probe = adreno_probe, + .remove = __devexit_p(adreno_remove), + .suspend = kgsl_suspend_driver, + .resume = kgsl_resume_driver, + .id_table = adreno_id_table, + .driver = { + .owner = THIS_MODULE, + .name = DEVICE_3D_NAME, + .pm = &kgsl_pm_ops, + } +}; + +static int __init kgsl_3d_init(void) +{ + return platform_driver_register(&adreno_platform_driver); +} + +static void __exit kgsl_3d_exit(void) +{ + platform_driver_unregister(&adreno_platform_driver); +} + +module_init(kgsl_3d_init); +module_exit(kgsl_3d_exit); + +MODULE_DESCRIPTION("3D Graphics driver"); +MODULE_VERSION("1.2"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:kgsl_3d"); diff --git a/drivers/gpu/msm/adreno.h b/drivers/gpu/msm/adreno.h new file mode 100644 index 00000000000..597c6b82d76 --- /dev/null +++ b/drivers/gpu/msm/adreno.h @@ -0,0 +1,116 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ADRENO_H +#define __ADRENO_H + +#include "kgsl_device.h" +#include "adreno_drawctxt.h" +#include "adreno_ringbuffer.h" + +#define DEVICE_3D_NAME "kgsl-3d" +#define DEVICE_3D0_NAME "kgsl-3d0" + +#define ADRENO_DEVICE(device) \ + KGSL_CONTAINER_OF(device, struct adreno_device, dev) + +/* Flags to control command packet settings */ +#define KGSL_CMD_FLAGS_PMODE 0x00000001 +#define KGSL_CMD_FLAGS_NO_TS_CMP 0x00000002 +#define KGSL_CMD_FLAGS_NOT_KERNEL_CMD 0x00000004 + +/* Command identifiers */ +#define KGSL_CONTEXT_TO_MEM_IDENTIFIER 0xDEADBEEF +#define KGSL_CMD_IDENTIFIER 0xFEEDFACE + +#ifdef CONFIG_MSM_SCM +#define ADRENO_DEFAULT_PWRSCALE_POLICY (&kgsl_pwrscale_policy_tz) +#else +#define ADRENO_DEFAULT_PWRSCALE_POLICY NULL +#endif + +#define KGSL_CP_INT_MASK \ + (CP_INT_CNTL__SW_INT_MASK | \ + CP_INT_CNTL__T0_PACKET_IN_IB_MASK | \ + CP_INT_CNTL__OPCODE_ERROR_MASK | \ + CP_INT_CNTL__PROTECTED_MODE_ERROR_MASK | \ + CP_INT_CNTL__RESERVED_BIT_ERROR_MASK | \ + CP_INT_CNTL__IB_ERROR_MASK | \ + CP_INT_CNTL__IB2_INT_MASK | \ + CP_INT_CNTL__IB1_INT_MASK | \ + CP_INT_CNTL__RB_INT_MASK) + +enum adreno_gpurev { + ADRENO_REV_UNKNOWN = 0, + ADRENO_REV_A200 = 200, + ADRENO_REV_A205 = 205, + ADRENO_REV_A220 = 220, + ADRENO_REV_A225 = 225, +}; + +struct adreno_device { + struct kgsl_device dev; /* Must be first field in this struct */ + unsigned int chip_id; + enum adreno_gpurev gpurev; + struct kgsl_memregion gmemspace; + struct adreno_context *drawctxt_active; + wait_queue_head_t ib1_wq; + unsigned int *pfp_fw; + size_t pfp_fw_size; + unsigned int *pm4_fw; + size_t pm4_fw_size; + struct adreno_ringbuffer ringbuffer; + unsigned int mharb; +}; + +int adreno_idle(struct kgsl_device *device, unsigned int timeout); +void adreno_regread(struct kgsl_device *device, unsigned int offsetwords, + unsigned int *value); +void adreno_regwrite(struct kgsl_device *device, unsigned int offsetwords, + unsigned int value); + +uint8_t *kgsl_sharedmem_convertaddr(struct kgsl_device *device, + unsigned int pt_base, unsigned int gpuaddr, unsigned int *size); + +static inline int adreno_is_a200(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A200); +} + +static inline int adreno_is_a205(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A200); +} + +static inline int adreno_is_a20x(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A200 || + adreno_dev->gpurev == ADRENO_REV_A205); +} + +static inline int adreno_is_a220(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A220); +} + +static inline int adreno_is_a225(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A225); +} + +static inline int adreno_is_a22x(struct adreno_device *adreno_dev) +{ + return (adreno_dev->gpurev == ADRENO_REV_A220 || + adreno_dev->gpurev == ADRENO_REV_A225); +} + +#endif /*__ADRENO_H */ diff --git a/drivers/gpu/msm/adreno_debugfs.c b/drivers/gpu/msm/adreno_debugfs.c new file mode 100644 index 00000000000..b897e109101 --- /dev/null +++ b/drivers/gpu/msm/adreno_debugfs.c @@ -0,0 +1,451 @@ +/* Copyright (c) 2002,2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include + +#include "kgsl.h" +#include "adreno_postmortem.h" +#include "adreno.h" + +#include "a200_reg.h" + +unsigned int kgsl_cff_dump_enable; +int kgsl_pm_regs_enabled; + +static uint32_t kgsl_ib_base; +static uint32_t kgsl_ib_size; + +static struct dentry *pm_d_debugfs; + +static int pm_dump_set(void *data, u64 val) +{ + struct kgsl_device *device = data; + + if (val) { + mutex_lock(&device->mutex); + adreno_postmortem_dump(device, 1); + mutex_unlock(&device->mutex); + } + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(pm_dump_fops, + NULL, + pm_dump_set, "%llu\n"); + +static int pm_regs_enabled_set(void *data, u64 val) +{ + kgsl_pm_regs_enabled = val ? 1 : 0; + return 0; +} + +static int pm_regs_enabled_get(void *data, u64 *val) +{ + *val = kgsl_pm_regs_enabled; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(pm_regs_enabled_fops, + pm_regs_enabled_get, + pm_regs_enabled_set, "%llu\n"); + + +static int kgsl_cff_dump_enable_set(void *data, u64 val) +{ +#ifdef CONFIG_MSM_KGSL_CFF_DUMP + kgsl_cff_dump_enable = (val != 0); + return 0; +#else + return -EINVAL; +#endif +} + +static int kgsl_cff_dump_enable_get(void *data, u64 *val) +{ + *val = kgsl_cff_dump_enable; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(kgsl_cff_dump_enable_fops, kgsl_cff_dump_enable_get, + kgsl_cff_dump_enable_set, "%llu\n"); + +static int kgsl_dbgfs_open(struct inode *inode, struct file *file) +{ + file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE); + file->private_data = inode->i_private; + return 0; +} + +static int kgsl_dbgfs_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static int kgsl_hex_dump(const char *prefix, int c, uint8_t *data, + int rowc, int linec, char __user *buff) +{ + int ss; + /* Prefix of 20 chars max, 32 bytes per row, in groups of four - that's + * 8 groups at 8 chars per group plus a space, plus new-line, plus + * ending character */ + char linebuf[20 + 64 + 1 + 1]; + + ss = snprintf(linebuf, sizeof(linebuf), prefix, c); + hex_dump_to_buffer(data, linec, rowc, 4, linebuf+ss, + sizeof(linebuf)-ss, 0); + strncat(linebuf, "\n", sizeof(linebuf)); + linebuf[sizeof(linebuf)-1] = 0; + ss = strlen(linebuf); + if (copy_to_user(buff, linebuf, ss+1)) + return -EFAULT; + return ss; +} + +static ssize_t kgsl_ib_dump_read( + struct file *file, + char __user *buff, + size_t buff_count, + loff_t *ppos) +{ + int i, count = kgsl_ib_size, remaining, pos = 0, tot = 0, ss; + struct kgsl_device *device = file->private_data; + const int rowc = 32; + unsigned int pt_base, ib_memsize; + uint8_t *base_addr; + char linebuf[80]; + + if (!ppos || !device || !kgsl_ib_base) + return 0; + + kgsl_regread(device, MH_MMU_PT_BASE, &pt_base); + base_addr = kgsl_sharedmem_convertaddr(device, pt_base, kgsl_ib_base, + &ib_memsize); + + if (!base_addr) + return 0; + + pr_info("%s ppos=%ld, buff_count=%d, count=%d\n", __func__, (long)*ppos, + buff_count, count); + ss = snprintf(linebuf, sizeof(linebuf), "IB: base=%08x(%08x" + "), size=%d, memsize=%d\n", kgsl_ib_base, + (uint32_t)base_addr, kgsl_ib_size, ib_memsize); + if (*ppos == 0) { + if (copy_to_user(buff, linebuf, ss+1)) + return -EFAULT; + tot += ss; + buff += ss; + *ppos += ss; + } + pos += ss; + remaining = count; + for (i = 0; i < count; i += rowc) { + int linec = min(remaining, rowc); + + remaining -= rowc; + ss = kgsl_hex_dump("IB: %05x: ", i, base_addr, rowc, linec, + buff); + if (ss < 0) + return ss; + + if (pos >= *ppos) { + if (tot+ss >= buff_count) { + ss = copy_to_user(buff, "", 1); + return tot; + } + tot += ss; + buff += ss; + *ppos += ss; + } + pos += ss; + base_addr += linec; + } + + return tot; +} + +static ssize_t kgsl_ib_dump_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + char local_buff[64]; + + if (count >= sizeof(local_buff)) + return -EFAULT; + + if (copy_from_user(local_buff, buff, count)) + return -EFAULT; + + local_buff[count] = 0; /* end of string */ + sscanf(local_buff, "%x %d", &kgsl_ib_base, &kgsl_ib_size); + + pr_info("%s: base=%08X size=%d\n", __func__, kgsl_ib_base, + kgsl_ib_size); + + return count; +} + +static const struct file_operations kgsl_ib_dump_fops = { + .open = kgsl_dbgfs_open, + .release = kgsl_dbgfs_release, + .read = kgsl_ib_dump_read, + .write = kgsl_ib_dump_write, +}; + +static int kgsl_regread_nolock(struct kgsl_device *device, + unsigned int offsetwords, unsigned int *value) +{ + unsigned int *reg; + + if (offsetwords*sizeof(uint32_t) >= device->regspace.sizebytes) { + KGSL_DRV_ERR(device, "invalid offset %d\n", offsetwords); + return -ERANGE; + } + + reg = (unsigned int *)(device->regspace.mmio_virt_base + + (offsetwords << 2)); + *value = __raw_readl(reg); + return 0; +} + +#define KGSL_ISTORE_START 0x5000 +#define KGSL_ISTORE_LENGTH 0x600 +static ssize_t kgsl_istore_read( + struct file *file, + char __user *buff, + size_t buff_count, + loff_t *ppos) +{ + int i, count = KGSL_ISTORE_LENGTH, remaining, pos = 0, tot = 0; + struct kgsl_device *device = file->private_data; + const int rowc = 8; + + if (!ppos || !device) + return 0; + + remaining = count; + for (i = 0; i < count; i += rowc) { + unsigned int vals[rowc]; + int j, ss; + int linec = min(remaining, rowc); + remaining -= rowc; + + if (pos >= *ppos) { + for (j = 0; j < linec; ++j) + kgsl_regread_nolock(device, + KGSL_ISTORE_START+i+j, vals+j); + } else + memset(vals, 0, sizeof(vals)); + + ss = kgsl_hex_dump("IS: %04x: ", i, (uint8_t *)vals, rowc*4, + linec*4, buff); + if (ss < 0) + return ss; + + if (pos >= *ppos) { + if (tot+ss >= buff_count) + return tot; + tot += ss; + buff += ss; + *ppos += ss; + } + pos += ss; + } + + return tot; +} + +static const struct file_operations kgsl_istore_fops = { + .open = kgsl_dbgfs_open, + .release = kgsl_dbgfs_release, + .read = kgsl_istore_read, + .llseek = default_llseek, +}; + +typedef void (*reg_read_init_t)(struct kgsl_device *device); +typedef void (*reg_read_fill_t)(struct kgsl_device *device, int i, + unsigned int *vals, int linec); +static ssize_t kgsl_reg_read(struct kgsl_device *device, int count, + reg_read_init_t reg_read_init, + reg_read_fill_t reg_read_fill, const char *prefix, char __user *buff, + loff_t *ppos) +{ + int i, remaining; + const int rowc = 8; + + if (!ppos || *ppos || !device) + return 0; + + mutex_lock(&device->mutex); + reg_read_init(device); + remaining = count; + for (i = 0; i < count; i += rowc) { + unsigned int vals[rowc]; + int ss; + int linec = min(remaining, rowc); + remaining -= rowc; + + reg_read_fill(device, i, vals, linec); + ss = kgsl_hex_dump(prefix, i, (uint8_t *)vals, rowc*4, linec*4, + buff); + if (ss < 0) { + mutex_unlock(&device->mutex); + return ss; + } + buff += ss; + *ppos += ss; + } + mutex_unlock(&device->mutex); + + return *ppos; +} + + +static void kgsl_sx_reg_read_init(struct kgsl_device *device) +{ + kgsl_regwrite(device, REG_RBBM_PM_OVERRIDE2, 0xFF); + kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0); +} + +static void kgsl_sx_reg_read_fill(struct kgsl_device *device, int i, + unsigned int *vals, int linec) +{ + int j; + + for (j = 0; j < linec; ++j) { + kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0x1B00 | i); + kgsl_regread(device, REG_RBBM_DEBUG_OUT, vals+j); + } +} + +static ssize_t kgsl_sx_debug_read( + struct file *file, + char __user *buff, + size_t buff_count, + loff_t *ppos) +{ + struct kgsl_device *device = file->private_data; + return kgsl_reg_read(device, 0x1B, kgsl_sx_reg_read_init, + kgsl_sx_reg_read_fill, "SX: %02x: ", buff, ppos); +} + +static const struct file_operations kgsl_sx_debug_fops = { + .open = kgsl_dbgfs_open, + .release = kgsl_dbgfs_release, + .read = kgsl_sx_debug_read, +}; + +static void kgsl_cp_reg_read_init(struct kgsl_device *device) +{ + kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0); +} + +static void kgsl_cp_reg_read_fill(struct kgsl_device *device, int i, + unsigned int *vals, int linec) +{ + int j; + + for (j = 0; j < linec; ++j) { + kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0x1628); + kgsl_regread(device, REG_RBBM_DEBUG_OUT, vals+j); + msleep(100); + } +} + +static ssize_t kgsl_cp_debug_read( + struct file *file, + char __user *buff, + size_t buff_count, + loff_t *ppos) +{ + struct kgsl_device *device = file->private_data; + return kgsl_reg_read(device, 20, kgsl_cp_reg_read_init, + kgsl_cp_reg_read_fill, + "CP: %02x: ", buff, ppos); +} + +static const struct file_operations kgsl_cp_debug_fops = { + .open = kgsl_dbgfs_open, + .release = kgsl_dbgfs_release, + .read = kgsl_cp_debug_read, +}; + +static void kgsl_mh_reg_read_init(struct kgsl_device *device) +{ + kgsl_regwrite(device, REG_RBBM_DEBUG_CNTL, 0); +} + +static void kgsl_mh_reg_read_fill(struct kgsl_device *device, int i, + unsigned int *vals, int linec) +{ + int j; + + for (j = 0; j < linec; ++j) { + kgsl_regwrite(device, REG_MH_DEBUG_CTRL, i+j); + kgsl_regread(device, REG_MH_DEBUG_DATA, vals+j); + } +} + +static ssize_t kgsl_mh_debug_read( + struct file *file, + char __user *buff, + size_t buff_count, + loff_t *ppos) +{ + struct kgsl_device *device = file->private_data; + return kgsl_reg_read(device, 0x40, kgsl_mh_reg_read_init, + kgsl_mh_reg_read_fill, + "MH: %02x: ", buff, ppos); +} + +static const struct file_operations kgsl_mh_debug_fops = { + .open = kgsl_dbgfs_open, + .release = kgsl_dbgfs_release, + .read = kgsl_mh_debug_read, +}; + +void adreno_debugfs_init(struct kgsl_device *device) +{ + if (!device->d_debugfs || IS_ERR(device->d_debugfs)) + return; + + debugfs_create_file("ib_dump", 0600, device->d_debugfs, device, + &kgsl_ib_dump_fops); + debugfs_create_file("istore", 0400, device->d_debugfs, device, + &kgsl_istore_fops); + debugfs_create_file("sx_debug", 0400, device->d_debugfs, device, + &kgsl_sx_debug_fops); + debugfs_create_file("cp_debug", 0400, device->d_debugfs, device, + &kgsl_cp_debug_fops); + debugfs_create_file("mh_debug", 0400, device->d_debugfs, device, + &kgsl_mh_debug_fops); + debugfs_create_file("cff_dump", 0644, device->d_debugfs, device, + &kgsl_cff_dump_enable_fops); + + /* Create post mortem control files */ + + pm_d_debugfs = debugfs_create_dir("postmortem", device->d_debugfs); + + if (IS_ERR(pm_d_debugfs)) + return; + + debugfs_create_file("dump", 0600, pm_d_debugfs, device, + &pm_dump_fops); + debugfs_create_file("regs_enabled", 0644, pm_d_debugfs, device, + &pm_regs_enabled_fops); +} diff --git a/drivers/gpu/msm/adreno_debugfs.h b/drivers/gpu/msm/adreno_debugfs.h new file mode 100644 index 00000000000..0356ac6e119 --- /dev/null +++ b/drivers/gpu/msm/adreno_debugfs.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2002,2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ADRENO_DEBUGFS_H +#define __ADRENO_DEBUGFS_H + +#ifdef CONFIG_DEBUG_FS + +int adreno_debugfs_init(struct kgsl_device *device); + +extern int kgsl_pm_regs_enabled; + +static inline int kgsl_pmregs_enabled(void) +{ + return kgsl_pm_regs_enabled; +} + +#else +static inline int adreno_debugfs_init(struct kgsl_device *device) +{ + return 0; +} + +static inline int kgsl_pmregs_enabled(void) +{ + /* If debugfs is turned off, then always print registers */ + return 1; +} +#endif + +#endif /* __ADRENO_DEBUGFS_H */ diff --git a/drivers/gpu/msm/adreno_drawctxt.c b/drivers/gpu/msm/adreno_drawctxt.c new file mode 100644 index 00000000000..4db3966edbe --- /dev/null +++ b/drivers/gpu/msm/adreno_drawctxt.c @@ -0,0 +1,1645 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include + +#include "kgsl.h" +#include "kgsl_sharedmem.h" +#include "kgsl_cffdump.h" + +#include "adreno.h" +#include "adreno_pm4types.h" +#include "adreno_drawctxt.h" + +/* + * + * Memory Map for Register, Constant & Instruction Shadow, and Command Buffers + * (34.5KB) + * + * +---------------------+------------+-------------+---+---------------------+ + * | ALU Constant Shadow | Reg Shadow | C&V Buffers |Tex| Shader Instr Shadow | + * +---------------------+------------+-------------+---+---------------------+ + * ________________________________/ \____________________ + * / | + * +--------------+-----------+------+-----------+------------------------+ + * | Restore Regs | Save Regs | Quad | Gmem Save | Gmem Restore | unused | + * +--------------+-----------+------+-----------+------------------------+ + * + * 8K - ALU Constant Shadow (8K aligned) + * 4K - H/W Register Shadow (8K aligned) + * 4K - Command and Vertex Buffers + * - Indirect command buffer : Const/Reg restore + * - includes Loop & Bool const shadows + * - Indirect command buffer : Const/Reg save + * - Quad vertices & texture coordinates + * - Indirect command buffer : Gmem save + * - Indirect command buffer : Gmem restore + * - Unused (padding to 8KB boundary) + * <1K - Texture Constant Shadow (768 bytes) (8K aligned) + * 18K - Shader Instruction Shadow + * - 6K vertex (32 byte aligned) + * - 6K pixel (32 byte aligned) + * - 6K shared (32 byte aligned) + * + * Note: Reading constants into a shadow, one at a time using REG_TO_MEM, takes + * 3 DWORDS per DWORD transfered, plus 1 DWORD for the shadow, for a total of + * 16 bytes per constant. If the texture constants were transfered this way, + * the Command & Vertex Buffers section would extend past the 16K boundary. + * By moving the texture constant shadow area to start at 16KB boundary, we + * only require approximately 40 bytes more memory, but are able to use the + * LOAD_CONSTANT_CONTEXT shadowing feature for the textures, speeding up + * context switching. + * + * [Using LOAD_CONSTANT_CONTEXT shadowing feature for the Loop and/or Bool + * constants would require an additional 8KB each, for alignment.] + * + */ + +/* Constants */ + +#define ALU_CONSTANTS 2048 /* DWORDS */ +#define NUM_REGISTERS 1024 /* DWORDS */ +#ifdef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES +#define CMD_BUFFER_LEN 9216 /* DWORDS */ +#else +#define CMD_BUFFER_LEN 3072 /* DWORDS */ +#endif +#define TEX_CONSTANTS (32*6) /* DWORDS */ +#define BOOL_CONSTANTS 8 /* DWORDS */ +#define LOOP_CONSTANTS 56 /* DWORDS */ +#define SHADER_INSTRUCT_LOG2 9U /* 2^n == SHADER_INSTRUCTIONS */ + +#if defined(PM4_IM_STORE) +/* 96-bit instructions */ +#define SHADER_INSTRUCT (1< exp) + frac = (uintval & (~(1 << exp))) << (23 - exp); + + /* Exp is biased by 127 and shifted 23 bits */ + exp = (exp + 127) << 23; + + return exp | frac; +} + +/* context save (gmem -> sys) */ + +/* pre-compiled vertex shader program +* +* attribute vec4 P; +* void main(void) +* { +* gl_Position = P; +* } +*/ +#define GMEM2SYS_VTX_PGM_LEN 0x12 + +static unsigned int gmem2sys_vtx_pgm[GMEM2SYS_VTX_PGM_LEN] = { + 0x00011003, 0x00001000, 0xc2000000, + 0x00001004, 0x00001000, 0xc4000000, + 0x00001005, 0x00002000, 0x00000000, + 0x1cb81000, 0x00398a88, 0x00000003, + 0x140f803e, 0x00000000, 0xe2010100, + 0x14000000, 0x00000000, 0xe2000000 +}; + +/* pre-compiled fragment shader program +* +* precision highp float; +* uniform vec4 clear_color; +* void main(void) +* { +* gl_FragColor = clear_color; +* } +*/ + +#define GMEM2SYS_FRAG_PGM_LEN 0x0c + +static unsigned int gmem2sys_frag_pgm[GMEM2SYS_FRAG_PGM_LEN] = { + 0x00000000, 0x1002c400, 0x10000000, + 0x00001003, 0x00002000, 0x00000000, + 0x140f8000, 0x00000000, 0x22000000, + 0x14000000, 0x00000000, 0xe2000000 +}; + +/* context restore (sys -> gmem) */ +/* pre-compiled vertex shader program +* +* attribute vec4 position; +* attribute vec4 texcoord; +* varying vec4 texcoord0; +* void main() +* { +* gl_Position = position; +* texcoord0 = texcoord; +* } +*/ + +#define SYS2GMEM_VTX_PGM_LEN 0x18 + +static unsigned int sys2gmem_vtx_pgm[SYS2GMEM_VTX_PGM_LEN] = { + 0x00052003, 0x00001000, 0xc2000000, 0x00001005, + 0x00001000, 0xc4000000, 0x00001006, 0x10071000, + 0x20000000, 0x18981000, 0x0039ba88, 0x00000003, + 0x12982000, 0x40257b08, 0x00000002, 0x140f803e, + 0x00000000, 0xe2010100, 0x140f8000, 0x00000000, + 0xe2020200, 0x14000000, 0x00000000, 0xe2000000 +}; + +/* pre-compiled fragment shader program +* +* precision mediump float; +* uniform sampler2D tex0; +* varying vec4 texcoord0; +* void main() +* { +* gl_FragColor = texture2D(tex0, texcoord0.xy); +* } +*/ + +#define SYS2GMEM_FRAG_PGM_LEN 0x0f + +static unsigned int sys2gmem_frag_pgm[SYS2GMEM_FRAG_PGM_LEN] = { + 0x00011002, 0x00001000, 0xc4000000, 0x00001003, + 0x10041000, 0x20000000, 0x10000001, 0x1ffff688, + 0x00000002, 0x140f8000, 0x00000000, 0xe2000000, + 0x14000000, 0x00000000, 0xe2000000 +}; + +/* shader texture constants (sysmem -> gmem) */ +#define SYS2GMEM_TEX_CONST_LEN 6 + +static unsigned int sys2gmem_tex_const[SYS2GMEM_TEX_CONST_LEN] = { + /* Texture, FormatXYZW=Unsigned, ClampXYZ=Wrap/Repeat, + * RFMode=ZeroClamp-1, Dim=1:2d + */ + 0x00000002, /* Pitch = TBD */ + + /* Format=6:8888_WZYX, EndianSwap=0:None, ReqSize=0:256bit, DimHi=0, + * NearestClamp=1:OGL Mode + */ + 0x00000800, /* Address[31:12] = TBD */ + + /* Width, Height, EndianSwap=0:None */ + 0, /* Width & Height = TBD */ + + /* NumFormat=0:RF, DstSelXYZW=XYZW, ExpAdj=0, MagFilt=MinFilt=0:Point, + * Mip=2:BaseMap + */ + 0 << 1 | 1 << 4 | 2 << 7 | 3 << 10 | 2 << 23, + + /* VolMag=VolMin=0:Point, MinMipLvl=0, MaxMipLvl=1, LodBiasH=V=0, + * Dim3d=0 + */ + 0, + + /* BorderColor=0:ABGRBlack, ForceBC=0:diable, TriJuice=0, Aniso=0, + * Dim=1:2d, MipPacking=0 + */ + 1 << 9 /* Mip Address[31:12] = TBD */ +}; + +/* quad for copying GMEM to context shadow */ +#define QUAD_LEN 12 + +static unsigned int gmem_copy_quad[QUAD_LEN] = { + 0x00000000, 0x00000000, 0x3f800000, + 0x00000000, 0x00000000, 0x3f800000, + 0x00000000, 0x00000000, 0x3f800000, + 0x00000000, 0x00000000, 0x3f800000 +}; + +#define TEXCOORD_LEN 8 + +static unsigned int gmem_copy_texcoord[TEXCOORD_LEN] = { + 0x00000000, 0x3f800000, + 0x3f800000, 0x3f800000, + 0x00000000, 0x00000000, + 0x3f800000, 0x00000000 +}; + +#define NUM_COLOR_FORMATS 13 + +static enum SURFACEFORMAT surface_format_table[NUM_COLOR_FORMATS] = { + FMT_4_4_4_4, /* COLORX_4_4_4_4 */ + FMT_1_5_5_5, /* COLORX_1_5_5_5 */ + FMT_5_6_5, /* COLORX_5_6_5 */ + FMT_8, /* COLORX_8 */ + FMT_8_8, /* COLORX_8_8 */ + FMT_8_8_8_8, /* COLORX_8_8_8_8 */ + FMT_8_8_8_8, /* COLORX_S8_8_8_8 */ + FMT_16_FLOAT, /* COLORX_16_FLOAT */ + FMT_16_16_FLOAT, /* COLORX_16_16_FLOAT */ + FMT_16_16_16_16_FLOAT, /* COLORX_16_16_16_16_FLOAT */ + FMT_32_FLOAT, /* COLORX_32_FLOAT */ + FMT_32_32_FLOAT, /* COLORX_32_32_FLOAT */ + FMT_32_32_32_32_FLOAT, /* COLORX_32_32_32_32_FLOAT */ +}; + +static unsigned int format2bytesperpixel[NUM_COLOR_FORMATS] = { + 2, /* COLORX_4_4_4_4 */ + 2, /* COLORX_1_5_5_5 */ + 2, /* COLORX_5_6_5 */ + 1, /* COLORX_8 */ + 2, /* COLORX_8_8 8*/ + 4, /* COLORX_8_8_8_8 */ + 4, /* COLORX_S8_8_8_8 */ + 2, /* COLORX_16_FLOAT */ + 4, /* COLORX_16_16_FLOAT */ + 8, /* COLORX_16_16_16_16_FLOAT */ + 4, /* COLORX_32_FLOAT */ + 8, /* COLORX_32_32_FLOAT */ + 16, /* COLORX_32_32_32_32_FLOAT */ +}; + +/* shader linkage info */ +#define SHADER_CONST_ADDR (11 * 6 + 3) + +/* gmem command buffer length */ +#define PM4_REG(reg) ((0x4 << 16) | (GSL_HAL_SUBBLOCK_OFFSET(reg))) + +/* functions */ +static void config_gmemsize(struct gmem_shadow_t *shadow, int gmem_size) +{ + int w = 64, h = 64; /* 16KB surface, minimum */ + + shadow->format = COLORX_8_8_8_8; + /* convert from bytes to 32-bit words */ + gmem_size = (gmem_size + 3) / 4; + + /* find the right surface size, close to a square. */ + while (w * h < gmem_size) + if (w < h) + w *= 2; + else + h *= 2; + + shadow->width = w; + shadow->pitch = w; + shadow->height = h; + shadow->gmem_pitch = shadow->pitch; + + shadow->size = shadow->pitch * shadow->height * 4; +} + +static unsigned int gpuaddr(unsigned int *cmd, struct kgsl_memdesc *memdesc) +{ + return memdesc->gpuaddr + ((char *)cmd - (char *)memdesc->hostptr); +} + +static void +create_ib1(struct adreno_context *drawctxt, unsigned int *cmd, + unsigned int *start, unsigned int *end) +{ + cmd[0] = PM4_HDR_INDIRECT_BUFFER_PFD; + cmd[1] = gpuaddr(start, &drawctxt->gpustate); + cmd[2] = end - start; +} + +static unsigned int *program_shader(unsigned int *cmds, int vtxfrag, + unsigned int *shader_pgm, int dwords) +{ + /* load the patched vertex shader stream */ + *cmds++ = pm4_type3_packet(PM4_IM_LOAD_IMMEDIATE, 2 + dwords); + /* 0=vertex shader, 1=fragment shader */ + *cmds++ = vtxfrag; + /* instruction start & size (in 32-bit words) */ + *cmds++ = ((0 << 16) | dwords); + + memcpy(cmds, shader_pgm, dwords << 2); + cmds += dwords; + + return cmds; +} + +static unsigned int *reg_to_mem(unsigned int *cmds, uint32_t dst, + uint32_t src, int dwords) +{ + while (dwords-- > 0) { + *cmds++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *cmds++ = src++; + *cmds++ = dst; + dst += 4; + } + + return cmds; +} + +#ifdef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + +static void build_reg_to_mem_range(unsigned int start, unsigned int end, + unsigned int **cmd, + struct adreno_context *drawctxt) +{ + unsigned int i = start; + + for (i = start; i <= end; i++) { + *(*cmd)++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *(*cmd)++ = i; + *(*cmd)++ = + ((drawctxt->gpustate.gpuaddr + REG_OFFSET) & 0xFFFFE000) + + (i - 0x2000) * 4; + } +} + +#endif + +/* chicken restore */ +static unsigned int *build_chicken_restore_cmds( + struct adreno_context *drawctxt, + struct tmp_ctx *ctx) +{ + unsigned int *start = ctx->cmd; + unsigned int *cmds = start; + + *cmds++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmds++ = 0; + + *cmds++ = pm4_type0_packet(REG_TP0_CHICKEN, 1); + ctx->chicken_restore = gpuaddr(cmds, &drawctxt->gpustate); + *cmds++ = 0x00000000; + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->chicken_restore, start, cmds); + + return cmds; +} + +/****************************************************************************/ +/* context save */ +/****************************************************************************/ + +static const unsigned int register_ranges_a20x[] = { + REG_RB_SURFACE_INFO, REG_RB_DEPTH_INFO, + REG_COHER_DEST_BASE_0, REG_PA_SC_SCREEN_SCISSOR_BR, + REG_PA_SC_WINDOW_OFFSET, REG_PA_SC_WINDOW_SCISSOR_BR, + REG_RB_STENCILREFMASK_BF, REG_PA_CL_VPORT_ZOFFSET, + REG_SQ_PROGRAM_CNTL, REG_SQ_WRAPPING_1, + REG_PA_SC_LINE_CNTL, REG_SQ_PS_CONST, + REG_PA_SC_AA_MASK, REG_PA_SC_AA_MASK, + REG_RB_SAMPLE_COUNT_CTL, REG_RB_COLOR_DEST_MASK, + REG_PA_SU_POLY_OFFSET_FRONT_SCALE, REG_PA_SU_POLY_OFFSET_BACK_OFFSET, + REG_VGT_MAX_VTX_INDX, REG_RB_FOG_COLOR, + REG_RB_DEPTHCONTROL, REG_RB_MODECONTROL, + REG_PA_SU_POINT_SIZE, REG_PA_SC_LINE_STIPPLE, + REG_PA_SC_VIZ_QUERY, REG_PA_SC_VIZ_QUERY, + REG_VGT_VERTEX_REUSE_BLOCK_CNTL, REG_RB_DEPTH_CLEAR +}; + +static const unsigned int register_ranges_a22x[] = { + REG_RB_SURFACE_INFO, REG_RB_DEPTH_INFO, + REG_COHER_DEST_BASE_0, REG_PA_SC_SCREEN_SCISSOR_BR, + REG_PA_SC_WINDOW_OFFSET, REG_PA_SC_WINDOW_SCISSOR_BR, + REG_RB_STENCILREFMASK_BF, REG_PA_CL_VPORT_ZOFFSET, + REG_SQ_PROGRAM_CNTL, REG_SQ_WRAPPING_1, + REG_PA_SC_LINE_CNTL, REG_SQ_PS_CONST, + REG_PA_SC_AA_MASK, REG_PA_SC_AA_MASK, + REG_RB_SAMPLE_COUNT_CTL, REG_RB_COLOR_DEST_MASK, + REG_PA_SU_POLY_OFFSET_FRONT_SCALE, REG_PA_SU_POLY_OFFSET_BACK_OFFSET, + /* all the below registers are specific to Leia */ + REG_LEIA_PC_MAX_VTX_INDX, REG_LEIA_PC_INDX_OFFSET, + REG_RB_COLOR_MASK, REG_RB_FOG_COLOR, + REG_RB_DEPTHCONTROL, REG_RB_COLORCONTROL, + REG_PA_CL_CLIP_CNTL, REG_PA_CL_VTE_CNTL, + REG_RB_MODECONTROL, REG_RB_SAMPLE_POS, + REG_PA_SU_POINT_SIZE, REG_PA_SU_LINE_CNTL, + REG_LEIA_PC_VERTEX_REUSE_BLOCK_CNTL, + REG_LEIA_PC_VERTEX_REUSE_BLOCK_CNTL, + REG_RB_COPY_CONTROL, REG_RB_DEPTH_CLEAR +}; + + +/* save h/w regs, alu constants, texture contants, etc. ... +* requires: bool_shadow_gpuaddr, loop_shadow_gpuaddr +*/ +static void build_regsave_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + struct tmp_ctx *ctx) +{ + unsigned int *start = ctx->cmd; + unsigned int *cmd = start; + + *cmd++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + +#ifdef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + /* Make sure the HW context has the correct register values + * before reading them. */ + *cmd++ = pm4_type3_packet(PM4_CONTEXT_UPDATE, 1); + *cmd++ = 0; + + { + unsigned int i = 0; + unsigned int reg_array_size = 0; + const unsigned int *ptr_register_ranges; + + /* Based on chip id choose the register ranges */ + if (adreno_is_a220(adreno_dev)) { + ptr_register_ranges = register_ranges_a22x; + reg_array_size = ARRAY_SIZE(register_ranges_a22x); + } else { + ptr_register_ranges = register_ranges_a20x; + reg_array_size = ARRAY_SIZE(register_ranges_a20x); + } + + + /* Write HW registers into shadow */ + for (i = 0; i < (reg_array_size/2) ; i++) { + build_reg_to_mem_range(ptr_register_ranges[i*2], + ptr_register_ranges[i*2+1], + &cmd, drawctxt); + } + } + + /* Copy ALU constants */ + cmd = + reg_to_mem(cmd, (drawctxt->gpustate.gpuaddr) & 0xFFFFE000, + REG_SQ_CONSTANT_0, ALU_CONSTANTS); + + /* Copy Tex constants */ + cmd = + reg_to_mem(cmd, + (drawctxt->gpustate.gpuaddr + TEX_OFFSET) & 0xFFFFE000, + REG_SQ_FETCH_0, TEX_CONSTANTS); +#else + + /* Insert a wait for idle packet before reading the registers. + * This is to fix a hang/reset seen during stress testing. In this + * hang, CP encountered a timeout reading SQ's boolean constant + * register. There is logic in the HW that blocks reading of this + * register when the SQ block is not idle, which we believe is + * contributing to the hang.*/ + *cmd++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + + /* H/w registers are already shadowed; just need to disable shadowing + * to prevent corruption. + */ + *cmd++ = pm4_type3_packet(PM4_LOAD_CONSTANT_CONTEXT, 3); + *cmd++ = (drawctxt->gpustate.gpuaddr + REG_OFFSET) & 0xFFFFE000; + *cmd++ = 4 << 16; /* regs, start=0 */ + *cmd++ = 0x0; /* count = 0 */ + + /* ALU constants are already shadowed; just need to disable shadowing + * to prevent corruption. + */ + *cmd++ = pm4_type3_packet(PM4_LOAD_CONSTANT_CONTEXT, 3); + *cmd++ = drawctxt->gpustate.gpuaddr & 0xFFFFE000; + *cmd++ = 0 << 16; /* ALU, start=0 */ + *cmd++ = 0x0; /* count = 0 */ + + /* Tex constants are already shadowed; just need to disable shadowing + * to prevent corruption. + */ + *cmd++ = pm4_type3_packet(PM4_LOAD_CONSTANT_CONTEXT, 3); + *cmd++ = (drawctxt->gpustate.gpuaddr + TEX_OFFSET) & 0xFFFFE000; + *cmd++ = 1 << 16; /* Tex, start=0 */ + *cmd++ = 0x0; /* count = 0 */ +#endif + + /* Need to handle some of the registers separately */ + *cmd++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *cmd++ = REG_SQ_GPR_MANAGEMENT; + *cmd++ = ctx->reg_values[0]; + + *cmd++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *cmd++ = REG_TP0_CHICKEN; + *cmd++ = ctx->reg_values[1]; + + if (adreno_is_a220(adreno_dev)) { + unsigned int i; + unsigned int j = 2; + for (i = REG_LEIA_VSC_BIN_SIZE; i <= + REG_LEIA_VSC_PIPE_DATA_LENGTH_7; i++) { + *cmd++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *cmd++ = i; + *cmd++ = ctx->reg_values[j]; + j++; + } + } + + /* Copy Boolean constants */ + cmd = reg_to_mem(cmd, ctx->bool_shadow, REG_SQ_CF_BOOLEANS, + BOOL_CONSTANTS); + + /* Copy Loop constants */ + cmd = reg_to_mem(cmd, ctx->loop_shadow, REG_SQ_CF_LOOP, LOOP_CONSTANTS); + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->reg_save, start, cmd); + + ctx->cmd = cmd; +} + +/*copy colour, depth, & stencil buffers from graphics memory to system memory*/ +static unsigned int *build_gmem2sys_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + struct tmp_ctx *ctx, + struct gmem_shadow_t *shadow) +{ + unsigned int *cmds = shadow->gmem_save_commands; + unsigned int *start = cmds; + /* Calculate the new offset based on the adjusted base */ + unsigned int bytesperpixel = format2bytesperpixel[shadow->format]; + unsigned int addr = shadow->gmemshadow.gpuaddr; + unsigned int offset = (addr - (addr & 0xfffff000)) / bytesperpixel; + + /* Store TP0_CHICKEN register */ + *cmds++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *cmds++ = REG_TP0_CHICKEN; + if (ctx) + *cmds++ = ctx->chicken_restore; + else + cmds++; + + *cmds++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmds++ = 0; + + /* Set TP0_CHICKEN to zero */ + *cmds++ = pm4_type0_packet(REG_TP0_CHICKEN, 1); + *cmds++ = 0x00000000; + + /* Set PA_SC_AA_CONFIG to 0 */ + *cmds++ = pm4_type0_packet(REG_PA_SC_AA_CONFIG, 1); + *cmds++ = 0x00000000; + + /* program shader */ + + /* load shader vtx constants ... 5 dwords */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 4); + *cmds++ = (0x1 << 16) | SHADER_CONST_ADDR; + *cmds++ = 0; + /* valid(?) vtx constant flag & addr */ + *cmds++ = shadow->quad_vertices.gpuaddr | 0x3; + /* limit = 12 dwords */ + *cmds++ = 0x00000030; + + /* Invalidate L2 cache to make sure vertices are updated */ + *cmds++ = pm4_type0_packet(REG_TC_CNTL_STATUS, 1); + *cmds++ = 0x1; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 4); + *cmds++ = PM4_REG(REG_VGT_MAX_VTX_INDX); + *cmds++ = 0x00ffffff; /* REG_VGT_MAX_VTX_INDX */ + *cmds++ = 0x0; /* REG_VGT_MIN_VTX_INDX */ + *cmds++ = 0x00000000; /* REG_VGT_INDX_OFFSET */ + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_SC_AA_MASK); + *cmds++ = 0x0000ffff; /* REG_PA_SC_AA_MASK */ + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_COLORCONTROL); + *cmds++ = 0x00000c20; + + /* load the patched vertex shader stream */ + cmds = program_shader(cmds, 0, gmem2sys_vtx_pgm, GMEM2SYS_VTX_PGM_LEN); + + /* Load the patched fragment shader stream */ + cmds = + program_shader(cmds, 1, gmem2sys_frag_pgm, GMEM2SYS_FRAG_PGM_LEN); + + /* SQ_PROGRAM_CNTL / SQ_CONTEXT_MISC */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_SQ_PROGRAM_CNTL); + if (adreno_is_a220(adreno_dev)) + *cmds++ = 0x10018001; + else + *cmds++ = 0x10010001; + *cmds++ = 0x00000008; + + /* resolve */ + + /* PA_CL_VTE_CNTL */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_CL_VTE_CNTL); + /* disable X/Y/Z transforms, X/Y/Z are premultiplied by W */ + *cmds++ = 0x00000b00; + + /* program surface info */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_RB_SURFACE_INFO); + *cmds++ = shadow->gmem_pitch; /* pitch, MSAA = 1 */ + + /* RB_COLOR_INFO Endian=none, Linear, Format=RGBA8888, Swap=0, + * Base=gmem_base + */ + /* gmem base assumed 4K aligned. */ + if (ctx) { + BUG_ON(ctx->gmem_base & 0xFFF); + *cmds++ = + (shadow-> + format << RB_COLOR_INFO__COLOR_FORMAT__SHIFT) | ctx-> + gmem_base; + } else { + unsigned int temp = *cmds; + *cmds++ = (temp & ~RB_COLOR_INFO__COLOR_FORMAT_MASK) | + (shadow->format << RB_COLOR_INFO__COLOR_FORMAT__SHIFT); + } + + /* disable Z */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_DEPTHCONTROL); + if (adreno_is_a220(adreno_dev)) + *cmds++ = 0x08; + else + *cmds++ = 0; + + /* set REG_PA_SU_SC_MODE_CNTL + * Front_ptype = draw triangles + * Back_ptype = draw triangles + * Provoking vertex = last + */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_SU_SC_MODE_CNTL); + *cmds++ = 0x00080240; + + /* Use maximum scissor values -- quad vertices already have the + * correct bounds */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_PA_SC_SCREEN_SCISSOR_TL); + *cmds++ = (0 << 16) | 0; + *cmds++ = (0x1fff << 16) | (0x1fff); + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_PA_SC_WINDOW_SCISSOR_TL); + *cmds++ = (unsigned int)((1U << 31) | (0 << 16) | 0); + *cmds++ = (0x1fff << 16) | (0x1fff); + + /* load the viewport so that z scale = clear depth and + * z offset = 0.0f + */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_PA_CL_VPORT_ZSCALE); + *cmds++ = 0xbf800000; /* -1.0f */ + *cmds++ = 0x0; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_COLOR_MASK); + *cmds++ = 0x0000000f; /* R = G = B = 1:enabled */ + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_COLOR_DEST_MASK); + *cmds++ = 0xffffffff; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_SQ_WRAPPING_0); + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + + /* load the stencil ref value + * $AAM - do this later + */ + + /* load the COPY state */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 6); + *cmds++ = PM4_REG(REG_RB_COPY_CONTROL); + *cmds++ = 0; /* RB_COPY_CONTROL */ + *cmds++ = addr & 0xfffff000; /* RB_COPY_DEST_BASE */ + *cmds++ = shadow->pitch >> 5; /* RB_COPY_DEST_PITCH */ + + /* Endian=none, Linear, Format=RGBA8888,Swap=0,!Dither, + * MaskWrite:R=G=B=A=1 + */ + *cmds++ = 0x0003c008 | + (shadow->format << RB_COPY_DEST_INFO__COPY_DEST_FORMAT__SHIFT); + /* Make sure we stay in offsetx field. */ + BUG_ON(offset & 0xfffff000); + *cmds++ = offset; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_MODECONTROL); + *cmds++ = 0x6; /* EDRAM copy */ + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_CL_CLIP_CNTL); + *cmds++ = 0x00010000; + + if (adreno_is_a220(adreno_dev)) { + *cmds++ = pm4_type3_packet(PM4_SET_DRAW_INIT_FLAGS, 1); + *cmds++ = 0; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_LEIA_RB_LRZ_VSC_CONTROL); + *cmds++ = 0x0000000; + + *cmds++ = pm4_type3_packet(PM4_DRAW_INDX, 3); + *cmds++ = 0; /* viz query info. */ + /* PrimType=RectList, SrcSel=AutoIndex, VisCullMode=Ignore */ + *cmds++ = 0x00004088; + *cmds++ = 3; /* NumIndices=3 */ + } else { + /* queue the draw packet */ + *cmds++ = pm4_type3_packet(PM4_DRAW_INDX, 2); + *cmds++ = 0; /* viz query info. */ + /* PrimType=RectList, NumIndices=3, SrcSel=AutoIndex */ + *cmds++ = 0x00030088; + } + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, shadow->gmem_save, start, cmds); + + return cmds; +} + +/* context restore */ + +/*copy colour, depth, & stencil buffers from system memory to graphics memory*/ +static unsigned int *build_sys2gmem_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + struct tmp_ctx *ctx, + struct gmem_shadow_t *shadow) +{ + unsigned int *cmds = shadow->gmem_restore_commands; + unsigned int *start = cmds; + + /* Store TP0_CHICKEN register */ + *cmds++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *cmds++ = REG_TP0_CHICKEN; + if (ctx) + *cmds++ = ctx->chicken_restore; + else + cmds++; + + *cmds++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmds++ = 0; + + /* Set TP0_CHICKEN to zero */ + *cmds++ = pm4_type0_packet(REG_TP0_CHICKEN, 1); + *cmds++ = 0x00000000; + + /* Set PA_SC_AA_CONFIG to 0 */ + *cmds++ = pm4_type0_packet(REG_PA_SC_AA_CONFIG, 1); + *cmds++ = 0x00000000; + /* shader constants */ + + /* vertex buffer constants */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 7); + + *cmds++ = (0x1 << 16) | (9 * 6); + /* valid(?) vtx constant flag & addr */ + *cmds++ = shadow->quad_vertices.gpuaddr | 0x3; + /* limit = 12 dwords */ + *cmds++ = 0x00000030; + /* valid(?) vtx constant flag & addr */ + *cmds++ = shadow->quad_texcoords.gpuaddr | 0x3; + /* limit = 8 dwords */ + *cmds++ = 0x00000020; + *cmds++ = 0; + *cmds++ = 0; + + /* Invalidate L2 cache to make sure vertices are updated */ + *cmds++ = pm4_type0_packet(REG_TC_CNTL_STATUS, 1); + *cmds++ = 0x1; + + cmds = program_shader(cmds, 0, sys2gmem_vtx_pgm, SYS2GMEM_VTX_PGM_LEN); + + /* Load the patched fragment shader stream */ + cmds = + program_shader(cmds, 1, sys2gmem_frag_pgm, SYS2GMEM_FRAG_PGM_LEN); + + /* SQ_PROGRAM_CNTL / SQ_CONTEXT_MISC */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_SQ_PROGRAM_CNTL); + *cmds++ = 0x10030002; + *cmds++ = 0x00000008; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_SC_AA_MASK); + *cmds++ = 0x0000ffff; /* REG_PA_SC_AA_MASK */ + + if (!adreno_is_a220(adreno_dev)) { + /* PA_SC_VIZ_QUERY */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_SC_VIZ_QUERY); + *cmds++ = 0x0; /*REG_PA_SC_VIZ_QUERY */ + } + + /* RB_COLORCONTROL */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_COLORCONTROL); + *cmds++ = 0x00000c20; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 4); + *cmds++ = PM4_REG(REG_VGT_MAX_VTX_INDX); + *cmds++ = 0x00ffffff; /* mmVGT_MAX_VTX_INDX */ + *cmds++ = 0x0; /* mmVGT_MIN_VTX_INDX */ + *cmds++ = 0x00000000; /* mmVGT_INDX_OFFSET */ + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_VGT_VERTEX_REUSE_BLOCK_CNTL); + *cmds++ = 0x00000002; /* mmVGT_VERTEX_REUSE_BLOCK_CNTL */ + *cmds++ = 0x00000002; /* mmVGT_OUT_DEALLOC_CNTL */ + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_SQ_INTERPOLATOR_CNTL); + *cmds++ = 0xffffffff; /* mmSQ_INTERPOLATOR_CNTL */ + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_SC_AA_CONFIG); + *cmds++ = 0x00000000; /* REG_PA_SC_AA_CONFIG */ + + /* set REG_PA_SU_SC_MODE_CNTL + * Front_ptype = draw triangles + * Back_ptype = draw triangles + * Provoking vertex = last + */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_SU_SC_MODE_CNTL); + *cmds++ = 0x00080240; + + /* texture constants */ + *cmds++ = + pm4_type3_packet(PM4_SET_CONSTANT, (SYS2GMEM_TEX_CONST_LEN + 1)); + *cmds++ = (0x1 << 16) | (0 * 6); + memcpy(cmds, sys2gmem_tex_const, SYS2GMEM_TEX_CONST_LEN << 2); + cmds[0] |= (shadow->pitch >> 5) << 22; + cmds[1] |= + shadow->gmemshadow.gpuaddr | surface_format_table[shadow->format]; + cmds[2] |= (shadow->width - 1) | (shadow->height - 1) << 13; + cmds += SYS2GMEM_TEX_CONST_LEN; + + /* program surface info */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_RB_SURFACE_INFO); + *cmds++ = shadow->gmem_pitch; /* pitch, MSAA = 1 */ + + /* RB_COLOR_INFO Endian=none, Linear, Format=RGBA8888, Swap=0, + * Base=gmem_base + */ + if (ctx) + *cmds++ = + (shadow-> + format << RB_COLOR_INFO__COLOR_FORMAT__SHIFT) | ctx-> + gmem_base; + else { + unsigned int temp = *cmds; + *cmds++ = (temp & ~RB_COLOR_INFO__COLOR_FORMAT_MASK) | + (shadow->format << RB_COLOR_INFO__COLOR_FORMAT__SHIFT); + } + + /* RB_DEPTHCONTROL */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_DEPTHCONTROL); + + if (adreno_is_a220(adreno_dev)) + *cmds++ = 8; /* disable Z */ + else + *cmds++ = 0; /* disable Z */ + + /* Use maximum scissor values -- quad vertices already + * have the correct bounds */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_PA_SC_SCREEN_SCISSOR_TL); + *cmds++ = (0 << 16) | 0; + *cmds++ = ((0x1fff) << 16) | 0x1fff; + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_PA_SC_WINDOW_SCISSOR_TL); + *cmds++ = (unsigned int)((1U << 31) | (0 << 16) | 0); + *cmds++ = ((0x1fff) << 16) | 0x1fff; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_CL_VTE_CNTL); + /* disable X/Y/Z transforms, X/Y/Z are premultiplied by W */ + *cmds++ = 0x00000b00; + + /*load the viewport so that z scale = clear depth and z offset = 0.0f */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_PA_CL_VPORT_ZSCALE); + *cmds++ = 0xbf800000; + *cmds++ = 0x0; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_COLOR_MASK); + *cmds++ = 0x0000000f; /* R = G = B = 1:enabled */ + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_COLOR_DEST_MASK); + *cmds++ = 0xffffffff; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 3); + *cmds++ = PM4_REG(REG_SQ_WRAPPING_0); + *cmds++ = 0x00000000; + *cmds++ = 0x00000000; + + /* load the stencil ref value + * $AAM - do this later + */ + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_RB_MODECONTROL); + /* draw pixels with color and depth/stencil component */ + *cmds++ = 0x4; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_PA_CL_CLIP_CNTL); + *cmds++ = 0x00010000; + + if (adreno_is_a220(adreno_dev)) { + *cmds++ = pm4_type3_packet(PM4_SET_DRAW_INIT_FLAGS, 1); + *cmds++ = 0; + + *cmds++ = pm4_type3_packet(PM4_SET_CONSTANT, 2); + *cmds++ = PM4_REG(REG_LEIA_RB_LRZ_VSC_CONTROL); + *cmds++ = 0x0000000; + + *cmds++ = pm4_type3_packet(PM4_DRAW_INDX, 3); + *cmds++ = 0; /* viz query info. */ + /* PrimType=RectList, SrcSel=AutoIndex, VisCullMode=Ignore */ + *cmds++ = 0x00004088; + *cmds++ = 3; /* NumIndices=3 */ + } else { + /* queue the draw packet */ + *cmds++ = pm4_type3_packet(PM4_DRAW_INDX, 2); + *cmds++ = 0; /* viz query info. */ + /* PrimType=RectList, NumIndices=3, SrcSel=AutoIndex */ + *cmds++ = 0x00030088; + } + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, shadow->gmem_restore, start, cmds); + + return cmds; +} + +/* restore h/w regs, alu constants, texture constants, etc. ... */ +static unsigned *reg_range(unsigned int *cmd, unsigned int start, + unsigned int end) +{ + *cmd++ = PM4_REG(start); /* h/w regs, start addr */ + *cmd++ = end - start + 1; /* count */ + return cmd; +} + +static void build_regrestore_cmds(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + struct tmp_ctx *ctx) +{ + unsigned int *start = ctx->cmd; + unsigned int *cmd = start; + + unsigned int i = 0; + unsigned int reg_array_size = 0; + const unsigned int *ptr_register_ranges; + + *cmd++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + + /* H/W Registers */ + /* deferred pm4_type3_packet(PM4_LOAD_CONSTANT_CONTEXT, ???); */ + cmd++; +#ifdef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + /* Force mismatch */ + *cmd++ = ((drawctxt->gpustate.gpuaddr + REG_OFFSET) & 0xFFFFE000) | 1; +#else + *cmd++ = (drawctxt->gpustate.gpuaddr + REG_OFFSET) & 0xFFFFE000; +#endif + + /* Based on chip id choose the registers ranges*/ + if (adreno_is_a220(adreno_dev)) { + ptr_register_ranges = register_ranges_a22x; + reg_array_size = ARRAY_SIZE(register_ranges_a22x); + } else { + ptr_register_ranges = register_ranges_a20x; + reg_array_size = ARRAY_SIZE(register_ranges_a20x); + } + + + for (i = 0; i < (reg_array_size/2); i++) { + cmd = reg_range(cmd, ptr_register_ranges[i*2], + ptr_register_ranges[i*2+1]); + } + + /* Now we know how many register blocks we have, we can compute command + * length + */ + start[2] = + pm4_type3_packet(PM4_LOAD_CONSTANT_CONTEXT, (cmd - start) - 3); + /* Enable shadowing for the entire register block. */ +#ifdef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + start[4] |= (0 << 24) | (4 << 16); /* Disable shadowing. */ +#else + start[4] |= (1 << 24) | (4 << 16); +#endif + + /* Need to handle some of the registers separately */ + *cmd++ = pm4_type0_packet(REG_SQ_GPR_MANAGEMENT, 1); + ctx->reg_values[0] = gpuaddr(cmd, &drawctxt->gpustate); + *cmd++ = 0x00040400; + + *cmd++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + *cmd++ = pm4_type0_packet(REG_TP0_CHICKEN, 1); + ctx->reg_values[1] = gpuaddr(cmd, &drawctxt->gpustate); + *cmd++ = 0x00000000; + + if (adreno_is_a220(adreno_dev)) { + unsigned int i; + unsigned int j = 2; + for (i = REG_LEIA_VSC_BIN_SIZE; i <= + REG_LEIA_VSC_PIPE_DATA_LENGTH_7; i++) { + *cmd++ = pm4_type0_packet(i, 1); + ctx->reg_values[j] = gpuaddr(cmd, &drawctxt->gpustate); + *cmd++ = 0x00000000; + j++; + } + } + + /* ALU Constants */ + *cmd++ = pm4_type3_packet(PM4_LOAD_CONSTANT_CONTEXT, 3); + *cmd++ = drawctxt->gpustate.gpuaddr & 0xFFFFE000; +#ifdef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + *cmd++ = (0 << 24) | (0 << 16) | 0; /* Disable shadowing */ +#else + *cmd++ = (1 << 24) | (0 << 16) | 0; +#endif + *cmd++ = ALU_CONSTANTS; + + /* Texture Constants */ + *cmd++ = pm4_type3_packet(PM4_LOAD_CONSTANT_CONTEXT, 3); + *cmd++ = (drawctxt->gpustate.gpuaddr + TEX_OFFSET) & 0xFFFFE000; +#ifdef CONFIG_MSM_KGSL_DISABLE_SHADOW_WRITES + /* Disable shadowing */ + *cmd++ = (0 << 24) | (1 << 16) | 0; +#else + *cmd++ = (1 << 24) | (1 << 16) | 0; +#endif + *cmd++ = TEX_CONSTANTS; + + /* Boolean Constants */ + *cmd++ = pm4_type3_packet(PM4_SET_CONSTANT, 1 + BOOL_CONSTANTS); + *cmd++ = (2 << 16) | 0; + + /* the next BOOL_CONSTANT dwords is the shadow area for + * boolean constants. + */ + ctx->bool_shadow = gpuaddr(cmd, &drawctxt->gpustate); + cmd += BOOL_CONSTANTS; + + /* Loop Constants */ + *cmd++ = pm4_type3_packet(PM4_SET_CONSTANT, 1 + LOOP_CONSTANTS); + *cmd++ = (3 << 16) | 0; + + /* the next LOOP_CONSTANTS dwords is the shadow area for + * loop constants. + */ + ctx->loop_shadow = gpuaddr(cmd, &drawctxt->gpustate); + cmd += LOOP_CONSTANTS; + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->reg_restore, start, cmd); + + ctx->cmd = cmd; +} + +/* quad for saving/restoring gmem */ +static void set_gmem_copy_quad(struct gmem_shadow_t *shadow) +{ + /* set vertex buffer values */ + gmem_copy_quad[1] = uint2float(shadow->height); + gmem_copy_quad[3] = uint2float(shadow->width); + gmem_copy_quad[4] = uint2float(shadow->height); + gmem_copy_quad[9] = uint2float(shadow->width); + + gmem_copy_quad[0] = uint2float(0); + gmem_copy_quad[6] = uint2float(0); + gmem_copy_quad[7] = uint2float(0); + gmem_copy_quad[10] = uint2float(0); + + memcpy(shadow->quad_vertices.hostptr, gmem_copy_quad, QUAD_LEN << 2); + + memcpy(shadow->quad_texcoords.hostptr, gmem_copy_texcoord, + TEXCOORD_LEN << 2); +} + +/* quad for saving/restoring gmem */ +static void build_quad_vtxbuff(struct adreno_context *drawctxt, + struct tmp_ctx *ctx, struct gmem_shadow_t *shadow) +{ + unsigned int *cmd = ctx->cmd; + + /* quad vertex buffer location (in GPU space) */ + shadow->quad_vertices.hostptr = cmd; + shadow->quad_vertices.gpuaddr = gpuaddr(cmd, &drawctxt->gpustate); + + cmd += QUAD_LEN; + + /* tex coord buffer location (in GPU space) */ + shadow->quad_texcoords.hostptr = cmd; + shadow->quad_texcoords.gpuaddr = gpuaddr(cmd, &drawctxt->gpustate); + + cmd += TEXCOORD_LEN; + + set_gmem_copy_quad(shadow); + + ctx->cmd = cmd; +} + +static void +build_shader_save_restore_cmds(struct adreno_context *drawctxt, + struct tmp_ctx *ctx) +{ + unsigned int *cmd = ctx->cmd; + unsigned int *save, *restore, *fixup; +#if defined(PM4_IM_STORE) + unsigned int *startSizeVtx, *startSizePix, *startSizeShared; +#endif + unsigned int *partition1; + unsigned int *shaderBases, *partition2; + +#if defined(PM4_IM_STORE) + /* compute vertex, pixel and shared instruction shadow GPU addresses */ + ctx->shader_vertex = drawctxt->gpustate.gpuaddr + SHADER_OFFSET; + ctx->shader_pixel = ctx->shader_vertex + SHADER_SHADOW_SIZE; + ctx->shader_shared = ctx->shader_pixel + SHADER_SHADOW_SIZE; +#endif + + /* restore shader partitioning and instructions */ + + restore = cmd; /* start address */ + + /* Invalidate Vertex & Pixel instruction code address and sizes */ + *cmd++ = pm4_type3_packet(PM4_INVALIDATE_STATE, 1); + *cmd++ = 0x00000300; /* 0x100 = Vertex, 0x200 = Pixel */ + + /* Restore previous shader vertex & pixel instruction bases. */ + *cmd++ = pm4_type3_packet(PM4_SET_SHADER_BASES, 1); + shaderBases = cmd++; /* TBD #5: shader bases (from fixup) */ + + /* write the shader partition information to a scratch register */ + *cmd++ = pm4_type0_packet(REG_SQ_INST_STORE_MANAGMENT, 1); + partition1 = cmd++; /* TBD #4a: partition info (from save) */ + +#if defined(PM4_IM_STORE) + /* load vertex shader instructions from the shadow. */ + *cmd++ = pm4_type3_packet(PM4_IM_LOAD, 2); + *cmd++ = ctx->shader_vertex + 0x0; /* 0x0 = Vertex */ + startSizeVtx = cmd++; /* TBD #1: start/size (from save) */ + + /* load pixel shader instructions from the shadow. */ + *cmd++ = pm4_type3_packet(PM4_IM_LOAD, 2); + *cmd++ = ctx->shader_pixel + 0x1; /* 0x1 = Pixel */ + startSizePix = cmd++; /* TBD #2: start/size (from save) */ + + /* load shared shader instructions from the shadow. */ + *cmd++ = pm4_type3_packet(PM4_IM_LOAD, 2); + *cmd++ = ctx->shader_shared + 0x2; /* 0x2 = Shared */ + startSizeShared = cmd++; /* TBD #3: start/size (from save) */ +#endif + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->shader_restore, restore, cmd); + + /* + * fixup SET_SHADER_BASES data + * + * since self-modifying PM4 code is being used here, a seperate + * command buffer is used for this fixup operation, to ensure the + * commands are not read by the PM4 engine before the data fields + * have been written. + */ + + fixup = cmd; /* start address */ + + /* write the shader partition information to a scratch register */ + *cmd++ = pm4_type0_packet(REG_SCRATCH_REG2, 1); + partition2 = cmd++; /* TBD #4b: partition info (from save) */ + + /* mask off unused bits, then OR with shader instruction memory size */ + *cmd++ = pm4_type3_packet(PM4_REG_RMW, 3); + *cmd++ = REG_SCRATCH_REG2; + /* AND off invalid bits. */ + *cmd++ = 0x0FFF0FFF; + /* OR in instruction memory size */ + *cmd++ = (unsigned int)((SHADER_INSTRUCT_LOG2 - 5U) << 29); + + /* write the computed value to the SET_SHADER_BASES data field */ + *cmd++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *cmd++ = REG_SCRATCH_REG2; + /* TBD #5: shader bases (to restore) */ + *cmd++ = gpuaddr(shaderBases, &drawctxt->gpustate); + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->shader_fixup, fixup, cmd); + + /* save shader partitioning and instructions */ + + save = cmd; /* start address */ + + *cmd++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + + /* fetch the SQ_INST_STORE_MANAGMENT register value, + * store the value in the data fields of the SET_CONSTANT commands + * above. + */ + *cmd++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *cmd++ = REG_SQ_INST_STORE_MANAGMENT; + /* TBD #4a: partition info (to restore) */ + *cmd++ = gpuaddr(partition1, &drawctxt->gpustate); + *cmd++ = pm4_type3_packet(PM4_REG_TO_MEM, 2); + *cmd++ = REG_SQ_INST_STORE_MANAGMENT; + /* TBD #4b: partition info (to fixup) */ + *cmd++ = gpuaddr(partition2, &drawctxt->gpustate); + +#if defined(PM4_IM_STORE) + + /* store the vertex shader instructions */ + *cmd++ = pm4_type3_packet(PM4_IM_STORE, 2); + *cmd++ = ctx->shader_vertex + 0x0; /* 0x0 = Vertex */ + /* TBD #1: start/size (to restore) */ + *cmd++ = gpuaddr(startSizeVtx, &drawctxt->gpustate); + + /* store the pixel shader instructions */ + *cmd++ = pm4_type3_packet(PM4_IM_STORE, 2); + *cmd++ = ctx->shader_pixel + 0x1; /* 0x1 = Pixel */ + /* TBD #2: start/size (to restore) */ + *cmd++ = gpuaddr(startSizePix, &drawctxt->gpustate); + + /* store the shared shader instructions if vertex base is nonzero */ + + *cmd++ = pm4_type3_packet(PM4_IM_STORE, 2); + *cmd++ = ctx->shader_shared + 0x2; /* 0x2 = Shared */ + /* TBD #3: start/size (to restore) */ + *cmd++ = gpuaddr(startSizeShared, &drawctxt->gpustate); + +#endif + + *cmd++ = pm4_type3_packet(PM4_WAIT_FOR_IDLE, 1); + *cmd++ = 0; + + /* create indirect buffer command for above command sequence */ + create_ib1(drawctxt, drawctxt->shader_save, save, cmd); + + ctx->cmd = cmd; +} + +/* create buffers for saving/restoring registers, constants, & GMEM */ +static int +create_gpustate_shadow(struct kgsl_device *device, + struct adreno_context *drawctxt, + struct tmp_ctx *ctx) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + int result; + + /* Allocate vmalloc memory to store the gpustate */ + result = kgsl_allocate(&drawctxt->gpustate, + drawctxt->pagetable, CONTEXT_SIZE); + + if (result) + return result; + + drawctxt->flags |= CTXT_FLAGS_STATE_SHADOW; + + /* Blank out h/w register, constant, and command buffer shadows. */ + kgsl_sharedmem_set(&drawctxt->gpustate, 0, 0, CONTEXT_SIZE); + + /* set-up command and vertex buffer pointers */ + ctx->cmd = ctx->start + = (unsigned int *)((char *)drawctxt->gpustate.hostptr + CMD_OFFSET); + + /* build indirect command buffers to save & restore regs/constants */ + adreno_idle(device, KGSL_TIMEOUT_DEFAULT); + build_regrestore_cmds(adreno_dev, drawctxt, ctx); + build_regsave_cmds(adreno_dev, drawctxt, ctx); + + build_shader_save_restore_cmds(drawctxt, ctx); + + kgsl_cache_range_op(&drawctxt->gpustate, + KGSL_CACHE_OP_FLUSH); + + return 0; +} + +/* create buffers for saving/restoring registers, constants, & GMEM */ +static int +create_gmem_shadow(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + struct tmp_ctx *ctx) +{ + struct kgsl_device *device = &adreno_dev->dev; + int result; + + config_gmemsize(&drawctxt->context_gmem_shadow, + adreno_dev->gmemspace.sizebytes); + ctx->gmem_base = adreno_dev->gmemspace.gpu_base; + + result = kgsl_allocate(&drawctxt->context_gmem_shadow.gmemshadow, + drawctxt->pagetable, drawctxt->context_gmem_shadow.size); + + if (result) + return result; + + /* we've allocated the shadow, when swapped out, GMEM must be saved. */ + drawctxt->flags |= CTXT_FLAGS_GMEM_SHADOW | CTXT_FLAGS_GMEM_SAVE; + + /* blank out gmem shadow. */ + kgsl_sharedmem_set(&drawctxt->context_gmem_shadow.gmemshadow, 0, 0, + drawctxt->context_gmem_shadow.size); + + /* build quad vertex buffer */ + build_quad_vtxbuff(drawctxt, ctx, &drawctxt->context_gmem_shadow); + + /* build TP0_CHICKEN register restore command buffer */ + ctx->cmd = build_chicken_restore_cmds(drawctxt, ctx); + + /* build indirect command buffers to save & restore gmem */ + /* Idle because we are reading PM override registers */ + adreno_idle(device, KGSL_TIMEOUT_DEFAULT); + drawctxt->context_gmem_shadow.gmem_save_commands = ctx->cmd; + ctx->cmd = + build_gmem2sys_cmds(adreno_dev, drawctxt, ctx, + &drawctxt->context_gmem_shadow); + drawctxt->context_gmem_shadow.gmem_restore_commands = ctx->cmd; + ctx->cmd = + build_sys2gmem_cmds(adreno_dev, drawctxt, ctx, + &drawctxt->context_gmem_shadow); + + kgsl_cache_range_op(&drawctxt->context_gmem_shadow.gmemshadow, + KGSL_CACHE_OP_FLUSH); + + return 0; +} + +/* create a new drawing context */ + +int adreno_drawctxt_create(struct kgsl_device *device, + struct kgsl_pagetable *pagetable, + struct kgsl_context *context, uint32_t flags) +{ + struct adreno_context *drawctxt; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct tmp_ctx ctx; + int ret; + + drawctxt = kzalloc(sizeof(struct adreno_context), GFP_KERNEL); + + if (drawctxt == NULL) + return -ENOMEM; + + drawctxt->pagetable = pagetable; + drawctxt->bin_base_offset = 0; + + ret = create_gpustate_shadow(device, drawctxt, &ctx); + if (ret) + goto err; + + /* Save the shader instruction memory on context switching */ + drawctxt->flags |= CTXT_FLAGS_SHADER_SAVE; + + if (!(flags & KGSL_CONTEXT_NO_GMEM_ALLOC)) { + /* create gmem shadow */ + ret = create_gmem_shadow(adreno_dev, drawctxt, &ctx); + if (ret != 0) + goto err; + } + + BUG_ON(ctx.cmd - ctx.start > CMD_BUFFER_LEN); + + context->devctxt = drawctxt; + return 0; +err: + kgsl_sharedmem_free(&drawctxt->gpustate); + kfree(drawctxt); + return ret; +} + +/* destroy a drawing context */ + +void adreno_drawctxt_destroy(struct kgsl_device *device, + struct kgsl_context *context) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_context *drawctxt = context->devctxt; + + if (drawctxt == NULL) + return; + + /* deactivate context */ + if (adreno_dev->drawctxt_active == drawctxt) { + /* no need to save GMEM or shader, the context is + * being destroyed. + */ + drawctxt->flags &= ~(CTXT_FLAGS_GMEM_SAVE | + CTXT_FLAGS_SHADER_SAVE | + CTXT_FLAGS_GMEM_SHADOW | + CTXT_FLAGS_STATE_SHADOW); + + adreno_drawctxt_switch(adreno_dev, NULL, 0); + } + + adreno_idle(device, KGSL_TIMEOUT_DEFAULT); + + kgsl_sharedmem_free(&drawctxt->gpustate); + kgsl_sharedmem_free(&drawctxt->context_gmem_shadow.gmemshadow); + + kfree(drawctxt); + context->devctxt = NULL; +} + +/* set bin base offset */ +void adreno_drawctxt_set_bin_base_offset(struct kgsl_device *device, + struct kgsl_context *context, + unsigned int offset) +{ + struct adreno_context *drawctxt = context->devctxt; + + if (drawctxt) + drawctxt->bin_base_offset = offset; +} + +/* switch drawing contexts */ +void +adreno_drawctxt_switch(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + unsigned int flags) +{ + struct adreno_context *active_ctxt = + adreno_dev->drawctxt_active; + struct kgsl_device *device = &adreno_dev->dev; + unsigned int cmds[5]; + + if (drawctxt) { + if (flags & KGSL_CONTEXT_SAVE_GMEM) + /* Set the flag in context so that the save is done + * when this context is switched out. */ + drawctxt->flags |= CTXT_FLAGS_GMEM_SAVE; + else + /* Remove GMEM saving flag from the context */ + drawctxt->flags &= ~CTXT_FLAGS_GMEM_SAVE; + } + /* already current? */ + if (active_ctxt == drawctxt) + return; + + KGSL_CTXT_INFO(device, "from %p to %p flags %d\n", + adreno_dev->drawctxt_active, drawctxt, flags); + /* save old context*/ + if (active_ctxt && active_ctxt->flags & CTXT_FLAGS_GPU_HANG) + KGSL_CTXT_WARN(device, + "Current active context has caused gpu hang\n"); + + if (active_ctxt != NULL) { + KGSL_CTXT_INFO(device, + "active_ctxt flags %08x\n", active_ctxt->flags); + /* save registers and constants. */ + adreno_ringbuffer_issuecmds(device, 0, + active_ctxt->reg_save, 3); + + if (active_ctxt->flags & CTXT_FLAGS_SHADER_SAVE) { + /* save shader partitioning and instructions. */ + adreno_ringbuffer_issuecmds(device, + KGSL_CMD_FLAGS_PMODE, + active_ctxt->shader_save, 3); + + /* fixup shader partitioning parameter for + * SET_SHADER_BASES. + */ + adreno_ringbuffer_issuecmds(device, 0, + active_ctxt->shader_fixup, 3); + + active_ctxt->flags |= CTXT_FLAGS_SHADER_RESTORE; + } + + if (active_ctxt->flags & CTXT_FLAGS_GMEM_SAVE + && active_ctxt->flags & CTXT_FLAGS_GMEM_SHADOW) { + /* save gmem. + * (note: changes shader. shader must already be saved.) + */ + adreno_ringbuffer_issuecmds(device, + KGSL_CMD_FLAGS_PMODE, + active_ctxt->context_gmem_shadow.gmem_save, 3); + + /* Restore TP0_CHICKEN */ + adreno_ringbuffer_issuecmds(device, 0, + active_ctxt->chicken_restore, 3); + + active_ctxt->flags |= CTXT_FLAGS_GMEM_RESTORE; + } + } + + adreno_dev->drawctxt_active = drawctxt; + + /* restore new context */ + if (drawctxt != NULL) { + + KGSL_CTXT_INFO(device, + "drawctxt flags %08x\n", drawctxt->flags); + cmds[0] = pm4_nop_packet(1); + cmds[1] = KGSL_CONTEXT_TO_MEM_IDENTIFIER; + cmds[2] = pm4_type3_packet(PM4_MEM_WRITE, 2); + cmds[3] = device->memstore.gpuaddr + + KGSL_DEVICE_MEMSTORE_OFFSET(current_context); + cmds[4] = (unsigned int)adreno_dev->drawctxt_active; + adreno_ringbuffer_issuecmds(device, 0, cmds, 5); + kgsl_mmu_setstate(device, drawctxt->pagetable); + +#ifndef CONFIG_MSM_KGSL_CFF_DUMP_NO_CONTEXT_MEM_DUMP + kgsl_cffdump_syncmem(NULL, &drawctxt->gpustate, + drawctxt->gpustate.gpuaddr, LCC_SHADOW_SIZE + + REG_SHADOW_SIZE + CMD_BUFFER_SIZE + TEX_SHADOW_SIZE, + false); +#endif + + /* restore gmem. + * (note: changes shader. shader must not already be restored.) + */ + if (drawctxt->flags & CTXT_FLAGS_GMEM_RESTORE) { + adreno_ringbuffer_issuecmds(device, + KGSL_CMD_FLAGS_PMODE, + drawctxt->context_gmem_shadow.gmem_restore, 3); + + /* Restore TP0_CHICKEN */ + adreno_ringbuffer_issuecmds(device, 0, + drawctxt->chicken_restore, 3); + + drawctxt->flags &= ~CTXT_FLAGS_GMEM_RESTORE; + } + + /* restore registers and constants. */ + adreno_ringbuffer_issuecmds(device, 0, + drawctxt->reg_restore, 3); + + /* restore shader instructions & partitioning. */ + if (drawctxt->flags & CTXT_FLAGS_SHADER_RESTORE) { + adreno_ringbuffer_issuecmds(device, 0, + drawctxt->shader_restore, 3); + } + + cmds[0] = pm4_type3_packet(PM4_SET_BIN_BASE_OFFSET, 1); + cmds[1] = drawctxt->bin_base_offset; + if (!adreno_is_a220(adreno_dev)) + adreno_ringbuffer_issuecmds(device, 0, cmds, 2); + + } else + kgsl_mmu_setstate(device, device->mmu.defaultpagetable); +} diff --git a/drivers/gpu/msm/adreno_drawctxt.h b/drivers/gpu/msm/adreno_drawctxt.h new file mode 100644 index 00000000000..049adf7d4d5 --- /dev/null +++ b/drivers/gpu/msm/adreno_drawctxt.h @@ -0,0 +1,97 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ADRENO_DRAWCTXT_H +#define __ADRENO_DRAWCTXT_H + +#include "a200_reg.h" +#include "a220_reg.h" + +/* Flags */ + +#define CTXT_FLAGS_NOT_IN_USE 0x00000000 +#define CTXT_FLAGS_IN_USE 0x00000001 + +/* state shadow memory allocated */ +#define CTXT_FLAGS_STATE_SHADOW 0x00000010 + +/* gmem shadow memory allocated */ +#define CTXT_FLAGS_GMEM_SHADOW 0x00000100 +/* gmem must be copied to shadow */ +#define CTXT_FLAGS_GMEM_SAVE 0x00000200 +/* gmem can be restored from shadow */ +#define CTXT_FLAGS_GMEM_RESTORE 0x00000400 +/* shader must be copied to shadow */ +#define CTXT_FLAGS_SHADER_SAVE 0x00002000 +/* shader can be restored from shadow */ +#define CTXT_FLAGS_SHADER_RESTORE 0x00004000 +/* Context has caused a GPU hang */ +#define CTXT_FLAGS_GPU_HANG 0x00008000 + +struct kgsl_device; +struct adreno_device; +struct kgsl_device_private; +struct kgsl_context; + +/* draw context */ +struct gmem_shadow_t { + struct kgsl_memdesc gmemshadow; /* Shadow buffer address */ + + /* 256 KB GMEM surface = 4 bytes-per-pixel x 256 pixels/row x + * 256 rows. */ + /* width & height must be a multiples of 32, in case tiled textures + * are used. */ + enum COLORFORMATX format; + unsigned int size; /* Size of surface used to store GMEM */ + unsigned int width; /* Width of surface used to store GMEM */ + unsigned int height; /* Height of surface used to store GMEM */ + unsigned int pitch; /* Pitch of surface used to store GMEM */ + unsigned int gmem_pitch; /* Pitch value used for GMEM */ + unsigned int *gmem_save_commands; + unsigned int *gmem_restore_commands; + unsigned int gmem_save[3]; + unsigned int gmem_restore[3]; + struct kgsl_memdesc quad_vertices; + struct kgsl_memdesc quad_texcoords; +}; + +struct adreno_context { + uint32_t flags; + struct kgsl_pagetable *pagetable; + struct kgsl_memdesc gpustate; + unsigned int reg_save[3]; + unsigned int reg_restore[3]; + unsigned int shader_save[3]; + unsigned int shader_fixup[3]; + unsigned int shader_restore[3]; + unsigned int chicken_restore[3]; + unsigned int bin_base_offset; + /* Information of the GMEM shadow that is created in context create */ + struct gmem_shadow_t context_gmem_shadow; +}; + +int adreno_drawctxt_create(struct kgsl_device *device, + struct kgsl_pagetable *pagetable, + struct kgsl_context *context, + uint32_t flags); + +void adreno_drawctxt_destroy(struct kgsl_device *device, + struct kgsl_context *context); + +void adreno_drawctxt_switch(struct adreno_device *adreno_dev, + struct adreno_context *drawctxt, + unsigned int flags); +void adreno_drawctxt_set_bin_base_offset(struct kgsl_device *device, + struct kgsl_context *context, + unsigned int offset); + +#endif /* __ADRENO_DRAWCTXT_H */ diff --git a/drivers/gpu/msm/adreno_pm4types.h b/drivers/gpu/msm/adreno_pm4types.h new file mode 100644 index 00000000000..4d6f70a20ea --- /dev/null +++ b/drivers/gpu/msm/adreno_pm4types.h @@ -0,0 +1,187 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ADRENO_PM4TYPES_H +#define __ADRENO_PM4TYPES_H + + +#define PM4_PKT_MASK 0xc0000000 + +#define PM4_TYPE0_PKT ((unsigned int)0 << 30) +#define PM4_TYPE1_PKT ((unsigned int)1 << 30) +#define PM4_TYPE2_PKT ((unsigned int)2 << 30) +#define PM4_TYPE3_PKT ((unsigned int)3 << 30) + + +/* type3 packets */ +/* initialize CP's micro-engine */ +#define PM4_ME_INIT 0x48 + +/* skip N 32-bit words to get to the next packet */ +#define PM4_NOP 0x10 + +/* indirect buffer dispatch. prefetch parser uses this packet type to determine +* whether to pre-fetch the IB +*/ +#define PM4_INDIRECT_BUFFER 0x3f + +/* indirect buffer dispatch. same as IB, but init is pipelined */ +#define PM4_INDIRECT_BUFFER_PFD 0x37 + +/* wait for the IDLE state of the engine */ +#define PM4_WAIT_FOR_IDLE 0x26 + +/* wait until a register or memory location is a specific value */ +#define PM4_WAIT_REG_MEM 0x3c + +/* wait until a register location is equal to a specific value */ +#define PM4_WAIT_REG_EQ 0x52 + +/* wait until a register location is >= a specific value */ +#define PM4_WAT_REG_GTE 0x53 + +/* wait until a read completes */ +#define PM4_WAIT_UNTIL_READ 0x5c + +/* wait until all base/size writes from an IB_PFD packet have completed */ +#define PM4_WAIT_IB_PFD_COMPLETE 0x5d + +/* register read/modify/write */ +#define PM4_REG_RMW 0x21 + +/* reads register in chip and writes to memory */ +#define PM4_REG_TO_MEM 0x3e + +/* write N 32-bit words to memory */ +#define PM4_MEM_WRITE 0x3d + +/* write CP_PROG_COUNTER value to memory */ +#define PM4_MEM_WRITE_CNTR 0x4f + +/* conditional execution of a sequence of packets */ +#define PM4_COND_EXEC 0x44 + +/* conditional write to memory or register */ +#define PM4_COND_WRITE 0x45 + +/* generate an event that creates a write to memory when completed */ +#define PM4_EVENT_WRITE 0x46 + +/* generate a VS|PS_done event */ +#define PM4_EVENT_WRITE_SHD 0x58 + +/* generate a cache flush done event */ +#define PM4_EVENT_WRITE_CFL 0x59 + +/* generate a z_pass done event */ +#define PM4_EVENT_WRITE_ZPD 0x5b + + +/* initiate fetch of index buffer and draw */ +#define PM4_DRAW_INDX 0x22 + +/* draw using supplied indices in packet */ +#define PM4_DRAW_INDX_2 0x36 + +/* initiate fetch of index buffer and binIDs and draw */ +#define PM4_DRAW_INDX_BIN 0x34 + +/* initiate fetch of bin IDs and draw using supplied indices */ +#define PM4_DRAW_INDX_2_BIN 0x35 + + +/* begin/end initiator for viz query extent processing */ +#define PM4_VIZ_QUERY 0x23 + +/* fetch state sub-blocks and initiate shader code DMAs */ +#define PM4_SET_STATE 0x25 + +/* load constant into chip and to memory */ +#define PM4_SET_CONSTANT 0x2d + +/* load sequencer instruction memory (pointer-based) */ +#define PM4_IM_LOAD 0x27 + +/* load sequencer instruction memory (code embedded in packet) */ +#define PM4_IM_LOAD_IMMEDIATE 0x2b + +/* load constants from a location in memory */ +#define PM4_LOAD_CONSTANT_CONTEXT 0x2e + +/* selective invalidation of state pointers */ +#define PM4_INVALIDATE_STATE 0x3b + + +/* dynamically changes shader instruction memory partition */ +#define PM4_SET_SHADER_BASES 0x4A + +/* sets the 64-bit BIN_MASK register in the PFP */ +#define PM4_SET_BIN_MASK 0x50 + +/* sets the 64-bit BIN_SELECT register in the PFP */ +#define PM4_SET_BIN_SELECT 0x51 + + +/* updates the current context, if needed */ +#define PM4_CONTEXT_UPDATE 0x5e + +/* generate interrupt from the command stream */ +#define PM4_INTERRUPT 0x40 + + +/* copy sequencer instruction memory to system memory */ +#define PM4_IM_STORE 0x2c + +/* + * for a20x + * program an offset that will added to the BIN_BASE value of + * the 3D_DRAW_INDX_BIN packet + */ +#define PM4_SET_BIN_BASE_OFFSET 0x4B + +/* + * for a22x + * sets draw initiator flags register in PFP, gets bitwise-ORed into + * every draw initiator + */ +#define PM4_SET_DRAW_INIT_FLAGS 0x4B + +#define PM4_SET_PROTECTED_MODE 0x5f /* sets the register protection mode */ + + +/* packet header building macros */ +#define pm4_type0_packet(regindx, cnt) \ + (PM4_TYPE0_PKT | (((cnt)-1) << 16) | ((regindx) & 0x7FFF)) + +#define pm4_type0_packet_for_sameregister(regindx, cnt) \ + ((PM4_TYPE0_PKT | (((cnt)-1) << 16) | ((1 << 15) | \ + ((regindx) & 0x7FFF))) + +#define pm4_type1_packet(reg0, reg1) \ + (PM4_TYPE1_PKT | ((reg1) << 12) | (reg0)) + +#define pm4_type3_packet(opcode, cnt) \ + (PM4_TYPE3_PKT | (((cnt)-1) << 16) | (((opcode) & 0xFF) << 8)) + +#define pm4_predicated_type3_packet(opcode, cnt) \ + (PM4_TYPE3_PKT | (((cnt)-1) << 16) | (((opcode) & 0xFF) << 8) | 0x1) + +#define pm4_nop_packet(cnt) \ + (PM4_TYPE3_PKT | (((cnt)-1) << 16) | (PM4_NOP << 8)) + + +/* packet headers */ +#define PM4_HDR_ME_INIT pm4_type3_packet(PM4_ME_INIT, 18) +#define PM4_HDR_INDIRECT_BUFFER_PFD pm4_type3_packet(PM4_INDIRECT_BUFFER_PFD, 2) +#define PM4_HDR_INDIRECT_BUFFER pm4_type3_packet(PM4_INDIRECT_BUFFER, 2) + +#endif /* __ADRENO_PM4TYPES_H */ diff --git a/drivers/gpu/msm/adreno_postmortem.c b/drivers/gpu/msm/adreno_postmortem.c new file mode 100644 index 00000000000..76db7fae400 --- /dev/null +++ b/drivers/gpu/msm/adreno_postmortem.c @@ -0,0 +1,846 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include + +#include "kgsl.h" + +#include "adreno.h" +#include "adreno_pm4types.h" +#include "adreno_ringbuffer.h" +#include "adreno_postmortem.h" +#include "adreno_debugfs.h" + +#include "a200_reg.h" + +#define INVALID_RB_CMD 0xaaaaaaaa + +struct pm_id_name { + uint32_t id; + char name[9]; +}; + +static const struct pm_id_name pm0_types[] = { + {REG_PA_SC_AA_CONFIG, "RPASCAAC"}, + {REG_RBBM_PM_OVERRIDE2, "RRBBPMO2"}, + {REG_SCRATCH_REG2, "RSCRTRG2"}, + {REG_SQ_GPR_MANAGEMENT, "RSQGPRMN"}, + {REG_SQ_INST_STORE_MANAGMENT, "RSQINSTS"}, + {REG_TC_CNTL_STATUS, "RTCCNTLS"}, + {REG_TP0_CHICKEN, "RTP0CHCK"}, + {REG_CP_TIMESTAMP, "CP_TM_ST"}, +}; + +static const struct pm_id_name pm3_types[] = { + {PM4_COND_EXEC, "CND_EXEC"}, + {PM4_CONTEXT_UPDATE, "CX__UPDT"}, + {PM4_DRAW_INDX, "DRW_NDX_"}, + {PM4_DRAW_INDX_BIN, "DRW_NDXB"}, + {PM4_EVENT_WRITE, "EVENT_WT"}, + {PM4_IM_LOAD, "IN__LOAD"}, + {PM4_IM_LOAD_IMMEDIATE, "IM_LOADI"}, + {PM4_IM_STORE, "IM_STORE"}, + {PM4_INDIRECT_BUFFER, "IND_BUF_"}, + {PM4_INDIRECT_BUFFER_PFD, "IND_BUFP"}, + {PM4_INTERRUPT, "PM4_INTR"}, + {PM4_INVALIDATE_STATE, "INV_STAT"}, + {PM4_LOAD_CONSTANT_CONTEXT, "LD_CN_CX"}, + {PM4_ME_INIT, "ME__INIT"}, + {PM4_NOP, "PM4__NOP"}, + {PM4_REG_RMW, "REG__RMW"}, + {PM4_REG_TO_MEM, "REG2_MEM"}, + {PM4_SET_BIN_BASE_OFFSET, "ST_BIN_O"}, + {PM4_SET_CONSTANT, "ST_CONST"}, + {PM4_SET_PROTECTED_MODE, "ST_PRT_M"}, + {PM4_SET_SHADER_BASES, "ST_SHD_B"}, + {PM4_WAIT_FOR_IDLE, "WAIT4IDL"}, +}; + +/* Offset address pairs: start, end of range to dump (inclusive) */ + +/* GPU < Z470 */ + +static const int a200_registers[] = { + 0x0000, 0x0008, 0x0010, 0x002c, 0x00ec, 0x00f4, + 0x0100, 0x0110, 0x0118, 0x011c, + 0x0700, 0x0704, 0x070c, 0x0720, 0x0754, 0x0764, + 0x0770, 0x0774, 0x07a8, 0x07a8, 0x07b8, 0x07cc, + 0x07d8, 0x07dc, 0x07f0, 0x07fc, 0x0e44, 0x0e48, + 0x0e6c, 0x0e78, 0x0ec8, 0x0ed4, 0x0edc, 0x0edc, + 0x0fe0, 0x0fec, 0x1100, 0x1100, + + 0x110c, 0x1110, 0x112c, 0x112c, 0x1134, 0x113c, + 0x1148, 0x1148, 0x1150, 0x116c, 0x11fc, 0x11fc, + 0x15e0, 0x161c, 0x1724, 0x1724, 0x1740, 0x1740, + 0x1804, 0x1810, 0x1818, 0x1824, 0x182c, 0x1838, + 0x184c, 0x1850, 0x28a4, 0x28ac, 0x28bc, 0x28c4, + 0x2900, 0x290c, 0x2914, 0x2914, 0x2938, 0x293c, + 0x30b0, 0x30b0, 0x30c0, 0x30c0, 0x30e0, 0x30f0, + 0x3100, 0x3100, 0x3110, 0x3110, 0x3200, 0x3218, + 0x3220, 0x3250, 0x3264, 0x3268, 0x3290, 0x3294, + 0x3400, 0x340c, 0x3418, 0x3418, 0x3420, 0x342c, + 0x34d0, 0x34d4, 0x36b8, 0x3704, 0x3720, 0x3750, + 0x3760, 0x3764, 0x3800, 0x3800, 0x3808, 0x3810, + 0x385c, 0x3878, 0x3b00, 0x3b24, 0x3b2c, 0x3b30, + 0x3b40, 0x3b40, 0x3b50, 0x3b5c, 0x3b80, 0x3b88, + 0x3c04, 0x3c08, 0x3c30, 0x3c30, 0x3c38, 0x3c48, + 0x3c98, 0x3ca8, 0x3cb0, 0x3cb0, + + 0x8000, 0x8008, 0x8018, 0x803c, 0x8200, 0x8208, + 0x8400, 0x8424, 0x8430, 0x8450, 0x8600, 0x8610, + 0x87d4, 0x87dc, 0x8800, 0x8820, 0x8a00, 0x8a0c, + 0x8a4c, 0x8a50, 0x8c00, 0x8c20, 0x8c48, 0x8c48, + 0x8c58, 0x8c74, 0x8c90, 0x8c98, 0x8e00, 0x8e0c, + + 0x9000, 0x9008, 0x9018, 0x903c, 0x9200, 0x9208, + 0x9400, 0x9424, 0x9430, 0x9450, 0x9600, 0x9610, + 0x97d4, 0x97dc, 0x9800, 0x9820, 0x9a00, 0x9a0c, + 0x9a4c, 0x9a50, 0x9c00, 0x9c20, 0x9c48, 0x9c48, + 0x9c58, 0x9c74, 0x9c90, 0x9c98, 0x9e00, 0x9e0c, + + 0x10000, 0x1000c, 0x12000, 0x12014, + 0x12400, 0x12400, 0x12420, 0x12420 +}; + +/* GPU = Z470 */ + +static const int a220_registers[] = { + 0x0000, 0x0008, 0x0010, 0x002c, 0x00ec, 0x00f4, + 0x0100, 0x0110, 0x0118, 0x011c, + 0x0700, 0x0704, 0x070c, 0x0720, 0x0754, 0x0764, + 0x0770, 0x0774, 0x07a8, 0x07a8, 0x07b8, 0x07cc, + 0x07d8, 0x07dc, 0x07f0, 0x07fc, 0x0e44, 0x0e48, + 0x0e6c, 0x0e78, 0x0ec8, 0x0ed4, 0x0edc, 0x0edc, + 0x0fe0, 0x0fec, 0x1100, 0x1100, + + 0x110c, 0x1110, 0x112c, 0x112c, 0x1134, 0x113c, + 0x1148, 0x1148, 0x1150, 0x116c, 0x11fc, 0x11fc, + 0x15e0, 0x161c, 0x1724, 0x1724, 0x1740, 0x1740, + 0x1804, 0x1810, 0x1818, 0x1824, 0x182c, 0x1838, + 0x184c, 0x1850, 0x28a4, 0x28ac, 0x28bc, 0x28c4, + 0x2900, 0x2900, 0x2908, 0x290c, 0x2914, 0x2914, + 0x2938, 0x293c, 0x30c0, 0x30c0, 0x30e0, 0x30e4, + 0x30f0, 0x30f0, 0x3200, 0x3204, 0x3220, 0x324c, + 0x3400, 0x340c, 0x3414, 0x3418, 0x3420, 0x342c, + 0x34d0, 0x34d4, 0x36b8, 0x3704, 0x3720, 0x3750, + 0x3760, 0x3764, 0x3800, 0x3800, 0x3808, 0x3810, + 0x385c, 0x3878, 0x3b00, 0x3b24, 0x3b2c, 0x3b30, + 0x3b40, 0x3b40, 0x3b50, 0x3b5c, 0x3b80, 0x3b88, + 0x3c04, 0x3c08, 0x8000, 0x8008, 0x8018, 0x803c, + 0x8200, 0x8208, 0x8400, 0x8408, 0x8410, 0x8424, + 0x8430, 0x8450, 0x8600, 0x8610, 0x87d4, 0x87dc, + 0x8800, 0x8808, 0x8810, 0x8810, 0x8820, 0x8820, + 0x8a00, 0x8a08, 0x8a50, 0x8a50, + 0x8c00, 0x8c20, 0x8c24, 0x8c28, 0x8c48, 0x8c48, + 0x8c58, 0x8c58, 0x8c60, 0x8c74, 0x8c90, 0x8c98, + 0x8e00, 0x8e0c, 0x9000, 0x9008, 0x9018, 0x903c, + 0x9200, 0x9208, 0x9400, 0x9408, 0x9410, 0x9424, + 0x9430, 0x9450, 0x9600, 0x9610, 0x97d4, 0x97dc, + 0x9800, 0x9808, 0x9810, 0x9818, 0x9820, 0x9820, + 0x9a00, 0x9a08, 0x9a50, 0x9a50, 0x9c00, 0x9c20, + 0x9c48, 0x9c48, 0x9c58, 0x9c58, 0x9c60, 0x9c74, + 0x9c90, 0x9c98, 0x9e00, 0x9e0c, + + 0x10000, 0x1000c, 0x12000, 0x12014, + 0x12400, 0x12400, 0x12420, 0x12420 +}; + +static uint32_t adreno_is_pm4_len(uint32_t word) +{ + if (word == INVALID_RB_CMD) + return 0; + + return (word >> 16) & 0x3FFF; +} + +static bool adreno_is_pm4_type(uint32_t word) +{ + int i; + + if (word == INVALID_RB_CMD) + return 1; + + if (adreno_is_pm4_len(word) > 16) + return 0; + + if ((word & (3<<30)) == PM4_TYPE0_PKT) { + for (i = 0; i < ARRAY_SIZE(pm0_types); ++i) { + if ((word & 0x7FFF) == pm0_types[i].id) + return 1; + } + return 0; + } + if ((word & (3<<30)) == PM4_TYPE3_PKT) { + for (i = 0; i < ARRAY_SIZE(pm3_types); ++i) { + if ((word & 0xFFFF) == (pm3_types[i].id << 8)) + return 1; + } + return 0; + } + return 0; +} + +static const char *adreno_pm4_name(uint32_t word) +{ + int i; + + if (word == INVALID_RB_CMD) + return "--------"; + + if ((word & (3<<30)) == PM4_TYPE0_PKT) { + for (i = 0; i < ARRAY_SIZE(pm0_types); ++i) { + if ((word & 0x7FFF) == pm0_types[i].id) + return pm0_types[i].name; + } + return "????????"; + } + if ((word & (3<<30)) == PM4_TYPE3_PKT) { + for (i = 0; i < ARRAY_SIZE(pm3_types); ++i) { + if ((word & 0xFFFF) == (pm3_types[i].id << 8)) + return pm3_types[i].name; + } + return "????????"; + } + return "????????"; +} + +static void adreno_dump_regs(struct kgsl_device *device, + const int *registers, int size) +{ + int range = 0, offset = 0; + + for (range = 0; range < size; range++) { + /* start and end are in dword offsets */ + int start = registers[range * 2] / 4; + int end = registers[range * 2 + 1] / 4; + + unsigned char linebuf[32 * 3 + 2 + 32 + 1]; + int linelen, i; + + for (offset = start; offset <= end; offset += linelen) { + unsigned int regvals[32/4]; + linelen = min(end+1-offset, 32/4); + + for (i = 0; i < linelen; ++i) + kgsl_regread(device, offset+i, regvals+i); + + hex_dump_to_buffer(regvals, linelen*4, 32, 4, + linebuf, sizeof(linebuf), 0); + KGSL_LOG_DUMP(device, + "REG: %5.5X: %s\n", offset<<2, linebuf); + } + } +} + +static void dump_ib(struct kgsl_device *device, char* buffId, uint32_t pt_base, + uint32_t base_offset, uint32_t ib_base, uint32_t ib_size, bool dump) +{ + unsigned int memsize; + uint8_t *base_addr = kgsl_sharedmem_convertaddr(device, pt_base, + ib_base, &memsize); + + if (base_addr && dump) + print_hex_dump(KERN_ERR, buffId, DUMP_PREFIX_OFFSET, + 32, 4, base_addr, ib_size*4, 0); + else + KGSL_LOG_DUMP(device, "%s base:%8.8X ib_size:%d " + "offset:%5.5X%s\n", + buffId, ib_base, ib_size*4, base_offset, + base_addr ? "" : " [Invalid]"); +} + +#define IB_LIST_SIZE 64 +struct ib_list { + int count; + uint32_t bases[IB_LIST_SIZE]; + uint32_t sizes[IB_LIST_SIZE]; + uint32_t offsets[IB_LIST_SIZE]; +}; + +static void dump_ib1(struct kgsl_device *device, uint32_t pt_base, + uint32_t base_offset, + uint32_t ib1_base, uint32_t ib1_size, + struct ib_list *ib_list, bool dump) +{ + int i, j; + uint32_t value; + uint32_t *ib1_addr; + unsigned int memsize; + + dump_ib(device, "IB1:", pt_base, base_offset, ib1_base, + ib1_size, dump); + + /* fetch virtual address for given IB base */ + ib1_addr = (uint32_t *)kgsl_sharedmem_convertaddr(device, pt_base, + ib1_base, &memsize); + if (!ib1_addr) + return; + + for (i = 0; i+3 < ib1_size; ) { + value = ib1_addr[i++]; + if (value == pm4_type3_packet(PM4_INDIRECT_BUFFER_PFD, 2)) { + uint32_t ib2_base = ib1_addr[i++]; + uint32_t ib2_size = ib1_addr[i++]; + + /* find previous match */ + for (j = 0; j < ib_list->count; ++j) + if (ib_list->sizes[j] == ib2_size + && ib_list->bases[j] == ib2_base) + break; + + if (j < ib_list->count || ib_list->count + >= IB_LIST_SIZE) + continue; + + /* store match */ + ib_list->sizes[ib_list->count] = ib2_size; + ib_list->bases[ib_list->count] = ib2_base; + ib_list->offsets[ib_list->count] = i<<2; + ++ib_list->count; + } + } +} + +static void adreno_dump_rb_buffer(const void *buf, size_t len, + char *linebuf, size_t linebuflen, int *argp) +{ + const u32 *ptr4 = buf; + const int ngroups = len; + int lx = 0, j; + bool nxsp = 1; + + for (j = 0; j < ngroups; j++) { + if (*argp < 0) { + lx += scnprintf(linebuf + lx, linebuflen - lx, " <"); + *argp = -*argp; + } else if (nxsp) + lx += scnprintf(linebuf + lx, linebuflen - lx, " "); + else + nxsp = 1; + if (!*argp && adreno_is_pm4_type(ptr4[j])) { + lx += scnprintf(linebuf + lx, linebuflen - lx, + "%s", adreno_pm4_name(ptr4[j])); + *argp = -(adreno_is_pm4_len(ptr4[j])+1); + } else { + lx += scnprintf(linebuf + lx, linebuflen - lx, + "%8.8X", ptr4[j]); + if (*argp > 1) + --*argp; + else if (*argp == 1) { + *argp = 0; + nxsp = 0; + lx += scnprintf(linebuf + lx, linebuflen - lx, + "> "); + } + } + } + linebuf[lx] = '\0'; +} + +static bool adreno_rb_use_hex(void) +{ +#ifdef CONFIG_MSM_KGSL_PSTMRTMDMP_RB_HEX + return 1; +#else + return 0; +#endif +} + +static void adreno_dump_rb(struct kgsl_device *device, const void *buf, + size_t len, int start, int size) +{ + const uint32_t *ptr = buf; + int i, remaining, args = 0; + unsigned char linebuf[32 * 3 + 2 + 32 + 1]; + const int rowsize = 8; + + len >>= 2; + remaining = len; + for (i = 0; i < len; i += rowsize) { + int linelen = min(remaining, rowsize); + remaining -= rowsize; + + if (adreno_rb_use_hex()) + hex_dump_to_buffer(ptr+i, linelen*4, rowsize*4, 4, + linebuf, sizeof(linebuf), 0); + else + adreno_dump_rb_buffer(ptr+i, linelen, linebuf, + sizeof(linebuf), &args); + KGSL_LOG_DUMP(device, + "RB: %4.4X:%s\n", (start+i)%size, linebuf); + } +} + +static bool adreno_ib_dump_enabled(void) +{ +#ifdef CONFIG_MSM_KGSL_PSTMRTMDMP_NO_IB_DUMP + return 0; +#else + return 1; +#endif +} + +struct log_field { + bool show; + const char *display; +}; + +static int adreno_dump_fields_line(struct kgsl_device *device, + const char *start, char *str, int slen, + const struct log_field **lines, + int num) +{ + const struct log_field *l = *lines; + int sptr, count = 0; + + sptr = snprintf(str, slen, "%s", start); + + for ( ; num && sptr < slen; num--, l++) { + int ilen = strlen(l->display); + + if (count) + ilen += strlen(" | "); + + if (ilen > (slen - sptr)) + break; + + if (count++) + sptr += snprintf(str + sptr, slen - sptr, " | "); + + sptr += snprintf(str + sptr, slen - sptr, "%s", l->display); + } + + KGSL_LOG_DUMP(device, "%s\n", str); + + *lines = l; + return num; +} + +static void adreno_dump_fields(struct kgsl_device *device, + const char *start, const struct log_field *lines, + int num) +{ + char lb[90]; + const char *sstr = start; + + lb[sizeof(lb) - 1] = '\0'; + + while (num) { + int ret = adreno_dump_fields_line(device, sstr, lb, + sizeof(lb) - 1, &lines, num); + + if (ret == num) + break; + + num = ret; + sstr = " "; + } +} + +static int adreno_dump(struct kgsl_device *device) +{ + unsigned int r1, r2, r3, rbbm_status; + unsigned int cp_ib1_base, cp_ib1_bufsz, cp_stat; + unsigned int cp_ib2_base, cp_ib2_bufsz; + unsigned int pt_base; + unsigned int cp_rb_base, rb_count; + unsigned int cp_rb_wptr, cp_rb_rptr; + unsigned int i; + int result = 0; + uint32_t *rb_copy; + const uint32_t *rb_vaddr; + int num_item = 0; + int read_idx, write_idx; + unsigned int ts_processed, rb_memsize; + + static struct ib_list ib_list; + + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + + mb(); + + KGSL_LOG_DUMP(device, "POWER: FLAGS = %08lX | ACTIVE POWERLEVEL = %08X", + pwr->power_flags, pwr->active_pwrlevel); + + KGSL_LOG_DUMP(device, "POWER: INTERVAL TIMEOUT = %08X ", + pwr->interval_timeout); + + KGSL_LOG_DUMP(device, "GRP_CLK = %lu ", + kgsl_get_clkrate(pwr->grp_clks[0])); + + KGSL_LOG_DUMP(device, "BUS CLK = %lu ", + kgsl_get_clkrate(pwr->ebi1_clk)); + + + kgsl_regread(device, REG_RBBM_STATUS, &rbbm_status); + kgsl_regread(device, REG_RBBM_PM_OVERRIDE1, &r2); + kgsl_regread(device, REG_RBBM_PM_OVERRIDE2, &r3); + KGSL_LOG_DUMP(device, "RBBM: STATUS = %08X | PM_OVERRIDE1 = %08X | " + "PM_OVERRIDE2 = %08X\n", rbbm_status, r2, r3); + + kgsl_regread(device, REG_RBBM_INT_CNTL, &r1); + kgsl_regread(device, REG_RBBM_INT_STATUS, &r2); + kgsl_regread(device, REG_RBBM_READ_ERROR, &r3); + KGSL_LOG_DUMP(device, " INT_CNTL = %08X | INT_STATUS = %08X | " + "READ_ERROR = %08X\n", r1, r2, r3); + + { + char cmdFifo[16]; + struct log_field lines[] = { + {rbbm_status & 0x000F, cmdFifo}, + {rbbm_status & BIT(5), "TC busy "}, + {rbbm_status & BIT(8), "HIRQ pending"}, + {rbbm_status & BIT(9), "CPRQ pending"}, + {rbbm_status & BIT(10), "CFRQ pending"}, + {rbbm_status & BIT(11), "PFRQ pending"}, + {rbbm_status & BIT(12), "VGT 0DMA bsy"}, + {rbbm_status & BIT(14), "RBBM WU busy"}, + {rbbm_status & BIT(16), "CP NRT busy "}, + {rbbm_status & BIT(18), "MH busy "}, + {rbbm_status & BIT(19), "MH chncy bsy"}, + {rbbm_status & BIT(21), "SX busy "}, + {rbbm_status & BIT(22), "TPC busy "}, + {rbbm_status & BIT(24), "SC CNTX busy"}, + {rbbm_status & BIT(25), "PA busy "}, + {rbbm_status & BIT(26), "VGT busy "}, + {rbbm_status & BIT(27), "SQ cntx1 bsy"}, + {rbbm_status & BIT(28), "SQ cntx0 bsy"}, + {rbbm_status & BIT(30), "RB busy "}, + {rbbm_status & BIT(31), "Grphs pp bsy"}, + }; + snprintf(cmdFifo, sizeof(cmdFifo), "CMD FIFO=%01X ", + rbbm_status & 0xf); + adreno_dump_fields(device, " STATUS=", lines, + ARRAY_SIZE(lines)); + } + + kgsl_regread(device, REG_CP_RB_BASE, &cp_rb_base); + kgsl_regread(device, REG_CP_RB_CNTL, &r2); + rb_count = 2 << (r2 & (BIT(6)-1)); + kgsl_regread(device, REG_CP_RB_RPTR_ADDR, &r3); + KGSL_LOG_DUMP(device, + "CP_RB: BASE = %08X | CNTL = %08X | RPTR_ADDR = %08X" + "\n", cp_rb_base, r2, r3); + + kgsl_regread(device, REG_CP_RB_RPTR, &cp_rb_rptr); + kgsl_regread(device, REG_CP_RB_WPTR, &cp_rb_wptr); + kgsl_regread(device, REG_CP_RB_RPTR_WR, &r3); + KGSL_LOG_DUMP(device, + " RPTR = %08X | WPTR = %08X | RPTR_WR = %08X" + "\n", cp_rb_rptr, cp_rb_wptr, r3); + + kgsl_regread(device, REG_CP_IB1_BASE, &cp_ib1_base); + kgsl_regread(device, REG_CP_IB1_BUFSZ, &cp_ib1_bufsz); + KGSL_LOG_DUMP(device, + "CP_IB1: BASE = %08X | BUFSZ = %d\n", cp_ib1_base, + cp_ib1_bufsz); + + kgsl_regread(device, REG_CP_IB2_BASE, &cp_ib2_base); + kgsl_regread(device, REG_CP_IB2_BUFSZ, &cp_ib2_bufsz); + KGSL_LOG_DUMP(device, + "CP_IB2: BASE = %08X | BUFSZ = %d\n", cp_ib2_base, + cp_ib2_bufsz); + + kgsl_regread(device, REG_CP_INT_CNTL, &r1); + kgsl_regread(device, REG_CP_INT_STATUS, &r2); + KGSL_LOG_DUMP(device, "CP_INT: CNTL = %08X | STATUS = %08X\n", r1, r2); + + kgsl_regread(device, REG_CP_ME_CNTL, &r1); + kgsl_regread(device, REG_CP_ME_STATUS, &r2); + kgsl_regread(device, REG_MASTER_INT_SIGNAL, &r3); + KGSL_LOG_DUMP(device, + "CP_ME: CNTL = %08X | STATUS = %08X | MSTR_INT_SGNL = " + "%08X\n", r1, r2, r3); + + kgsl_regread(device, REG_CP_STAT, &cp_stat); + KGSL_LOG_DUMP(device, "CP_STAT = %08X\n", cp_stat); +#ifndef CONFIG_MSM_KGSL_PSTMRTMDMP_CP_STAT_NO_DETAIL + { + struct log_field lns[] = { + {cp_stat & BIT(0), "WR_BSY 0"}, + {cp_stat & BIT(1), "RD_RQ_BSY 1"}, + {cp_stat & BIT(2), "RD_RTN_BSY 2"}, + }; + adreno_dump_fields(device, " MIU=", lns, ARRAY_SIZE(lns)); + } + { + struct log_field lns[] = { + {cp_stat & BIT(5), "RING_BUSY 5"}, + {cp_stat & BIT(6), "NDRCTS_BSY 6"}, + {cp_stat & BIT(7), "NDRCT2_BSY 7"}, + {cp_stat & BIT(9), "ST_BUSY 9"}, + {cp_stat & BIT(10), "BUSY 10"}, + }; + adreno_dump_fields(device, " CSF=", lns, ARRAY_SIZE(lns)); + } + { + struct log_field lns[] = { + {cp_stat & BIT(11), "RNG_Q_BSY 11"}, + {cp_stat & BIT(12), "NDRCTS_Q_B12"}, + {cp_stat & BIT(13), "NDRCT2_Q_B13"}, + {cp_stat & BIT(16), "ST_QUEUE_B16"}, + {cp_stat & BIT(17), "PFP_BUSY 17"}, + }; + adreno_dump_fields(device, " RING=", lns, ARRAY_SIZE(lns)); + } + { + struct log_field lns[] = { + {cp_stat & BIT(3), "RBIU_BUSY 3"}, + {cp_stat & BIT(4), "RCIU_BUSY 4"}, + {cp_stat & BIT(18), "MQ_RG_BSY 18"}, + {cp_stat & BIT(19), "MQ_NDRS_BS19"}, + {cp_stat & BIT(20), "MQ_NDR2_BS20"}, + {cp_stat & BIT(21), "MIU_WC_STL21"}, + {cp_stat & BIT(22), "CP_NRT_BSY22"}, + {cp_stat & BIT(23), "3D_BUSY 23"}, + {cp_stat & BIT(26), "ME_BUSY 26"}, + {cp_stat & BIT(29), "ME_WC_BSY 29"}, + {cp_stat & BIT(30), "MIU_FF EM 30"}, + {cp_stat & BIT(31), "CP_BUSY 31"}, + }; + adreno_dump_fields(device, " CP_STT=", lns, ARRAY_SIZE(lns)); + } +#endif + + kgsl_regread(device, REG_SCRATCH_REG0, &r1); + KGSL_LOG_DUMP(device, "SCRATCH_REG0 = %08X\n", r1); + + kgsl_regread(device, REG_COHER_SIZE_PM4, &r1); + kgsl_regread(device, REG_COHER_BASE_PM4, &r2); + kgsl_regread(device, REG_COHER_STATUS_PM4, &r3); + KGSL_LOG_DUMP(device, + "COHER: SIZE_PM4 = %08X | BASE_PM4 = %08X | STATUS_PM4" + " = %08X\n", r1, r2, r3); + + kgsl_regread(device, MH_AXI_ERROR, &r1); + KGSL_LOG_DUMP(device, "MH: AXI_ERROR = %08X\n", r1); + + kgsl_regread(device, MH_MMU_PAGE_FAULT, &r1); + kgsl_regread(device, MH_MMU_CONFIG, &r2); + kgsl_regread(device, MH_MMU_MPU_BASE, &r3); + KGSL_LOG_DUMP(device, + "MH_MMU: PAGE_FAULT = %08X | CONFIG = %08X | MPU_BASE =" + " %08X\n", r1, r2, r3); + + kgsl_regread(device, MH_MMU_MPU_END, &r1); + kgsl_regread(device, MH_MMU_VA_RANGE, &r2); + kgsl_regread(device, MH_MMU_PT_BASE, &pt_base); + KGSL_LOG_DUMP(device, + " MPU_END = %08X | VA_RANGE = %08X | PT_BASE =" + " %08X\n", r1, r2, pt_base); + + KGSL_LOG_DUMP(device, "PAGETABLE SIZE: %08X ", KGSL_PAGETABLE_SIZE); + + kgsl_regread(device, MH_MMU_TRAN_ERROR, &r1); + KGSL_LOG_DUMP(device, " TRAN_ERROR = %08X\n", r1); + + kgsl_regread(device, MH_INTERRUPT_MASK, &r1); + kgsl_regread(device, MH_INTERRUPT_STATUS, &r2); + KGSL_LOG_DUMP(device, + "MH_INTERRUPT: MASK = %08X | STATUS = %08X\n", r1, r2); + + ts_processed = device->ftbl->readtimestamp(device, + KGSL_TIMESTAMP_RETIRED); + KGSL_LOG_DUMP(device, "TIMESTM RTRD: %08X\n", ts_processed); + + num_item = adreno_ringbuffer_count(&adreno_dev->ringbuffer, + cp_rb_rptr); + if (num_item <= 0) + KGSL_LOG_POSTMORTEM_WRITE(device, "Ringbuffer is Empty.\n"); + + rb_copy = vmalloc(rb_count<<2); + if (!rb_copy) { + KGSL_LOG_POSTMORTEM_WRITE(device, + "vmalloc(%d) failed\n", rb_count << 2); + result = -ENOMEM; + goto end; + } + + KGSL_LOG_DUMP(device, "RB: rd_addr:%8.8x rb_size:%d num_item:%d\n", + cp_rb_base, rb_count<<2, num_item); + rb_vaddr = (const uint32_t *)kgsl_sharedmem_convertaddr(device, pt_base, + cp_rb_base, &rb_memsize); + if (!rb_vaddr) { + KGSL_LOG_POSTMORTEM_WRITE(device, + "Can't fetch vaddr for CP_RB_BASE\n"); + goto error_vfree; + } + + read_idx = (int)cp_rb_rptr - 64; + if (read_idx < 0) + read_idx += rb_count; + write_idx = (int)cp_rb_wptr + 16; + if (write_idx > rb_count) + write_idx -= rb_count; + num_item += 64+16; + if (num_item > rb_count) + num_item = rb_count; + if (write_idx >= read_idx) + memcpy(rb_copy, rb_vaddr+read_idx, num_item<<2); + else { + int part1_c = rb_count-read_idx; + memcpy(rb_copy, rb_vaddr+read_idx, part1_c<<2); + memcpy(rb_copy+part1_c, rb_vaddr, (num_item-part1_c)<<2); + } + + /* extract the latest ib commands from the buffer */ + ib_list.count = 0; + i = 0; + for (read_idx = 0; read_idx < num_item; ) { + uint32_t this_cmd = rb_copy[read_idx++]; + if (this_cmd == pm4_type3_packet(PM4_INDIRECT_BUFFER_PFD, 2)) { + uint32_t ib_addr = rb_copy[read_idx++]; + uint32_t ib_size = rb_copy[read_idx++]; + dump_ib1(device, pt_base, (read_idx-3)<<2, ib_addr, + ib_size, &ib_list, 0); + for (; i < ib_list.count; ++i) + dump_ib(device, "IB2:", pt_base, + ib_list.offsets[i], + ib_list.bases[i], + ib_list.sizes[i], 0); + } + } + + read_idx = (int)cp_rb_rptr - 64; + if (read_idx < 0) + read_idx += rb_count; + KGSL_LOG_DUMP(device, + "RB: addr=%8.8x window:%4.4x-%4.4x, start:%4.4x\n", + cp_rb_base, cp_rb_rptr, cp_rb_wptr, read_idx); + adreno_dump_rb(device, rb_copy, num_item<<2, read_idx, rb_count); + + if (adreno_ib_dump_enabled()) { + for (read_idx = 64; read_idx >= 0; --read_idx) { + uint32_t this_cmd = rb_copy[read_idx]; + if (this_cmd == pm4_type3_packet( + PM4_INDIRECT_BUFFER_PFD, 2)) { + uint32_t ib_addr = rb_copy[read_idx+1]; + uint32_t ib_size = rb_copy[read_idx+2]; + if (cp_ib1_bufsz && cp_ib1_base == ib_addr) { + KGSL_LOG_DUMP(device, + "IB1: base:%8.8X " + "count:%d\n", ib_addr, ib_size); + dump_ib(device, "IB1: ", pt_base, + read_idx<<2, ib_addr, ib_size, + 1); + } + } + } + for (i = 0; i < ib_list.count; ++i) { + if (cp_ib2_bufsz && cp_ib2_base == ib_list.bases[i]) { + uint32_t ib_size = ib_list.sizes[i]; + uint32_t ib_offset = ib_list.offsets[i]; + KGSL_LOG_DUMP(device, + "IB2: base:%8.8X count:%d\n", + cp_ib2_base, ib_size); + dump_ib(device, "IB2: ", pt_base, ib_offset, + ib_list.bases[i], ib_size, 1); + } + } + } + + /* Dump the registers if the user asked for it */ + + if (adreno_is_a20x(adreno_dev)) + adreno_dump_regs(device, a200_registers, + ARRAY_SIZE(a200_registers) / 2); + else if (adreno_is_a22x(adreno_dev)) + adreno_dump_regs(device, a220_registers, + ARRAY_SIZE(a220_registers) / 2); + +error_vfree: + vfree(rb_copy); +end: + return result; +} + +/** + * adreno_postmortem_dump - Dump the current GPU state + * @device - A pointer to the KGSL device to dump + * @manual - A flag that indicates if this was a manually triggered + * dump (from debugfs). If zero, then this is assumed to be a + * dump automaticlaly triggered from a hang +*/ + +int adreno_postmortem_dump(struct kgsl_device *device, int manual) +{ + bool saved_nap; + + BUG_ON(device == NULL); + + /* For a manual dump, make sure that the system is idle */ + + if (manual) { + if (device->active_cnt != 0) { + mutex_unlock(&device->mutex); + wait_for_completion(&device->suspend_gate); + mutex_lock(&device->mutex); + } + + if (device->state == KGSL_STATE_ACTIVE) + kgsl_idle(device, KGSL_TIMEOUT_DEFAULT); + + } + /* Disable the idle timer so we don't get interrupted */ + del_timer(&device->idle_timer); + + /* Turn off napping to make sure we have the clocks full + attention through the following process */ + saved_nap = device->pwrctrl.nap_allowed; + device->pwrctrl.nap_allowed = false; + + /* Force on the clocks */ + kgsl_pwrctrl_wake(device); + + /* Disable the irq */ + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + + /* If this is not a manual trigger, then set up the + state to try to recover */ + + if (!manual) { + device->state = KGSL_STATE_DUMP_AND_RECOVER; + KGSL_PWR_WARN(device, + "state -> DUMP_AND_RECOVER, device %d\n", + device->id); + } + + KGSL_DRV_ERR(device, + "wait for work in workqueue to complete\n"); + mutex_unlock(&device->mutex); + flush_workqueue(device->work_queue); + mutex_lock(&device->mutex); + adreno_dump(device); + + /* Restore nap mode */ + device->pwrctrl.nap_allowed = saved_nap; + + /* On a manual trigger, turn on the interrupts and put + the clocks to sleep. They will recover themselves + on the next event. For a hang, leave things as they + are until recovery kicks in. */ + + if (manual) { + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_ON); + + /* try to go into a sleep mode until the next event */ + device->requested_state = KGSL_STATE_SLEEP; + kgsl_pwrctrl_sleep(device); + } + + KGSL_DRV_ERR(device, "Dump Finished\n"); + + return 0; +} diff --git a/drivers/gpu/msm/adreno_postmortem.h b/drivers/gpu/msm/adreno_postmortem.h new file mode 100644 index 00000000000..b677800678f --- /dev/null +++ b/drivers/gpu/msm/adreno_postmortem.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __ADRENO_POSTMORTEM_H +#define __ADRENO_POSTMORTEM_H + +struct kgsl_device; + +int adreno_postmortem_dump(struct kgsl_device *device, int manual); + +#endif /* __ADRENO_POSTMORTEM_H */ diff --git a/drivers/gpu/msm/adreno_ringbuffer.c b/drivers/gpu/msm/adreno_ringbuffer.c new file mode 100644 index 00000000000..e7aaa142498 --- /dev/null +++ b/drivers/gpu/msm/adreno_ringbuffer.c @@ -0,0 +1,929 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_sharedmem.h" +#include "kgsl_cffdump.h" + +#include "adreno.h" +#include "adreno_pm4types.h" +#include "adreno_ringbuffer.h" + +#include "a200_reg.h" + +#define VALID_STATUS_COUNT_MAX 10 +#define GSL_RB_NOP_SIZEDWORDS 2 +/* protected mode error checking below register address 0x800 +* note: if CP_INTERRUPT packet is used then checking needs +* to change to below register address 0x7C8 +*/ +#define GSL_RB_PROTECTED_MODE_CONTROL 0x200001F2 + +/* Firmware file names + * Legacy names must remain but replacing macro names to + * match current kgsl model. + * a200 is yamato + * a220 is leia + */ +#define A200_PFP_FW "yamato_pfp.fw" +#define A200_PM4_FW "yamato_pm4.fw" +#define A220_PFP_470_FW "leia_pfp_470.fw" +#define A220_PM4_470_FW "leia_pm4_470.fw" +#define A225_PFP_FW "a225_pfp.fw" +#define A225_PM4_FW "a225_pm4.fw" + +/* functions */ +void kgsl_cp_intrcallback(struct kgsl_device *device) +{ + unsigned int status = 0, num_reads = 0, master_status = 0; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + + adreno_regread(device, REG_MASTER_INT_SIGNAL, &master_status); + while (!status && (num_reads < VALID_STATUS_COUNT_MAX) && + (master_status & MASTER_INT_SIGNAL__CP_INT_STAT)) { + adreno_regread(device, REG_CP_INT_STATUS, &status); + adreno_regread(device, REG_MASTER_INT_SIGNAL, + &master_status); + num_reads++; + } + if (num_reads > 1) + KGSL_DRV_WARN(device, + "Looped %d times to read REG_CP_INT_STATUS\n", + num_reads); + if (!status) { + if (master_status & MASTER_INT_SIGNAL__CP_INT_STAT) { + /* This indicates that we could not read CP_INT_STAT. + * As a precaution just wake up processes so + * they can check their timestamps. Since, we + * did not ack any interrupts this interrupt will + * be generated again */ + KGSL_DRV_WARN(device, "Unable to read CP_INT_STATUS\n"); + wake_up_interruptible_all(&device->wait_queue); + } else + KGSL_DRV_WARN(device, "Spurious interrput detected\n"); + return; + } + + if (status & CP_INT_CNTL__RB_INT_MASK) { + /* signal intr completion event */ + unsigned int enableflag = 0; + kgsl_sharedmem_writel(&rb->device->memstore, + KGSL_DEVICE_MEMSTORE_OFFSET(ts_cmp_enable), + enableflag); + wmb(); + KGSL_CMD_WARN(rb->device, "ringbuffer rb interrupt\n"); + } + + if (status & CP_INT_CNTL__T0_PACKET_IN_IB_MASK) { + KGSL_CMD_CRIT(rb->device, + "ringbuffer TO packet in IB interrupt\n"); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + } + if (status & CP_INT_CNTL__OPCODE_ERROR_MASK) { + KGSL_CMD_CRIT(rb->device, + "ringbuffer opcode error interrupt\n"); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + } + if (status & CP_INT_CNTL__PROTECTED_MODE_ERROR_MASK) { + KGSL_CMD_CRIT(rb->device, + "ringbuffer protected mode error interrupt\n"); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + } + if (status & CP_INT_CNTL__RESERVED_BIT_ERROR_MASK) { + KGSL_CMD_CRIT(rb->device, + "ringbuffer reserved bit error interrupt\n"); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + } + if (status & CP_INT_CNTL__IB_ERROR_MASK) { + KGSL_CMD_CRIT(rb->device, + "ringbuffer IB error interrupt\n"); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + } + if (status & CP_INT_CNTL__SW_INT_MASK) + KGSL_CMD_INFO(rb->device, "ringbuffer software interrupt\n"); + + if (status & CP_INT_CNTL__IB2_INT_MASK) + KGSL_CMD_INFO(rb->device, "ringbuffer ib2 interrupt\n"); + + if (status & (~KGSL_CP_INT_MASK)) + KGSL_CMD_WARN(rb->device, + "bad bits in REG_CP_INT_STATUS %08x\n", status); + + /* only ack bits we understand */ + status &= KGSL_CP_INT_MASK; + adreno_regwrite(device, REG_CP_INT_ACK, status); + + if (status & (CP_INT_CNTL__IB1_INT_MASK | CP_INT_CNTL__RB_INT_MASK)) { + KGSL_CMD_WARN(rb->device, "ringbuffer ib1/rb interrupt\n"); + wake_up_interruptible_all(&device->wait_queue); + atomic_notifier_call_chain(&(device->ts_notifier_list), + device->id, + NULL); + } +} + +static void adreno_ringbuffer_submit(struct adreno_ringbuffer *rb) +{ + BUG_ON(rb->wptr == 0); + + /*synchronize memory before informing the hardware of the + *new commands. + */ + mb(); + + adreno_regwrite(rb->device, REG_CP_RB_WPTR, rb->wptr); +} + +static int +adreno_ringbuffer_waitspace(struct adreno_ringbuffer *rb, unsigned int numcmds, + int wptr_ahead) +{ + int nopcount; + unsigned int freecmds; + unsigned int *cmds; + uint cmds_gpu; + + /* if wptr ahead, fill the remaining with NOPs */ + if (wptr_ahead) { + /* -1 for header */ + nopcount = rb->sizedwords - rb->wptr - 1; + + cmds = (unsigned int *)rb->buffer_desc.hostptr + rb->wptr; + cmds_gpu = rb->buffer_desc.gpuaddr + sizeof(uint)*rb->wptr; + + GSL_RB_WRITE(cmds, cmds_gpu, pm4_nop_packet(nopcount)); + + /* Make sure that rptr is not 0 before submitting + * commands at the end of ringbuffer. We do not + * want the rptr and wptr to become equal when + * the ringbuffer is not empty */ + do { + GSL_RB_GET_READPTR(rb, &rb->rptr); + } while (!rb->rptr); + + rb->wptr++; + + adreno_ringbuffer_submit(rb); + + rb->wptr = 0; + } + + /* wait for space in ringbuffer */ + do { + GSL_RB_GET_READPTR(rb, &rb->rptr); + + freecmds = rb->rptr - rb->wptr; + + } while ((freecmds != 0) && (freecmds <= numcmds)); + + return 0; +} + + +static unsigned int *adreno_ringbuffer_allocspace(struct adreno_ringbuffer *rb, + unsigned int numcmds) +{ + unsigned int *ptr = NULL; + int status = 0; + + BUG_ON(numcmds >= rb->sizedwords); + + GSL_RB_GET_READPTR(rb, &rb->rptr); + /* check for available space */ + if (rb->wptr >= rb->rptr) { + /* wptr ahead or equal to rptr */ + /* reserve dwords for nop packet */ + if ((rb->wptr + numcmds) > (rb->sizedwords - + GSL_RB_NOP_SIZEDWORDS)) + status = adreno_ringbuffer_waitspace(rb, numcmds, 1); + } else { + /* wptr behind rptr */ + if ((rb->wptr + numcmds) >= rb->rptr) + status = adreno_ringbuffer_waitspace(rb, numcmds, 0); + /* check for remaining space */ + /* reserve dwords for nop packet */ + if ((rb->wptr + numcmds) > (rb->sizedwords - + GSL_RB_NOP_SIZEDWORDS)) + status = adreno_ringbuffer_waitspace(rb, numcmds, 1); + } + + if (status == 0) { + ptr = (unsigned int *)rb->buffer_desc.hostptr + rb->wptr; + rb->wptr += numcmds; + } + + return ptr; +} + +static int _load_firmware(struct kgsl_device *device, const char *fwfile, + void **data, int *len) +{ + const struct firmware *fw = NULL; + int ret; + + ret = request_firmware(&fw, fwfile, device->dev); + + if (ret) { + KGSL_DRV_ERR(device, "request_firmware(%s) failed: %d\n", + fwfile, ret); + return ret; + } + + *data = kmalloc(fw->size, GFP_KERNEL); + + if (*data) { + memcpy(*data, fw->data, fw->size); + *len = fw->size; + } else + KGSL_MEM_ERR(device, "kmalloc(%d) failed\n", fw->size); + + release_firmware(fw); + return (*data != NULL) ? 0 : -ENOMEM; +} + +static int adreno_ringbuffer_load_pm4_ucode(struct kgsl_device *device) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + const char *fwfile; + int i, ret = 0; + + if (adreno_is_a220(adreno_dev)) { + fwfile = A220_PM4_470_FW; + } else if (adreno_is_a225(adreno_dev)) { + fwfile = A225_PM4_FW; + } else if (adreno_is_a20x(adreno_dev)) { + fwfile = A200_PM4_FW; + } else { + KGSL_DRV_ERR(device, "Could not load PM4 file\n"); + return -EINVAL; + } + + if (adreno_dev->pm4_fw == NULL) { + int len; + unsigned int *ptr; + + ret = _load_firmware(device, fwfile, (void *) &ptr, &len); + if (ret) + goto err; + + /* PM4 size is 3 dword aligned plus 1 dword of version */ + if (len % ((sizeof(uint32_t) * 3)) != sizeof(uint32_t)) { + KGSL_DRV_ERR(device, "Bad firmware size: %d\n", len); + ret = -EINVAL; + goto err; + } + + adreno_dev->pm4_fw_size = len / sizeof(uint32_t); + adreno_dev->pm4_fw = ptr; + } + + KGSL_DRV_INFO(device, "loading pm4 ucode version: %d\n", + adreno_dev->pm4_fw[0]); + + adreno_regwrite(device, REG_CP_DEBUG, 0x02000000); + adreno_regwrite(device, REG_CP_ME_RAM_WADDR, 0); + for (i = 1; i < adreno_dev->pm4_fw_size; i++) + adreno_regwrite(device, REG_CP_ME_RAM_DATA, + adreno_dev->pm4_fw[i]); +err: + return ret; +} + +static int adreno_ringbuffer_load_pfp_ucode(struct kgsl_device *device) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + const char *fwfile; + int i, ret = 0; + + if (adreno_is_a220(adreno_dev)) { + fwfile = A220_PFP_470_FW; + } else if (adreno_is_a225(adreno_dev)) { + fwfile = A225_PFP_FW; + } else if (adreno_is_a20x(adreno_dev)) { + fwfile = A200_PFP_FW; + } else { + KGSL_DRV_ERR(device, "Could not load PFP firmware\n"); + return -EINVAL; + } + + if (adreno_dev->pfp_fw == NULL) { + int len; + unsigned int *ptr; + + ret = _load_firmware(device, fwfile, (void *) &ptr, &len); + if (ret) + goto err; + + /* PFP size shold be dword aligned */ + if (len % sizeof(uint32_t) != 0) { + KGSL_DRV_ERR(device, "Bad firmware size: %d\n", len); + ret = -EINVAL; + goto err; + } + + adreno_dev->pfp_fw_size = len / sizeof(uint32_t); + adreno_dev->pfp_fw = ptr; + } + + KGSL_DRV_INFO(device, "loading pfp ucode version: %d\n", + adreno_dev->pfp_fw[0]); + + adreno_regwrite(device, REG_CP_PFP_UCODE_ADDR, 0); + for (i = 1; i < adreno_dev->pfp_fw_size; i++) + adreno_regwrite(device, REG_CP_PFP_UCODE_DATA, + adreno_dev->pfp_fw[i]); +err: + return ret; +} + +int adreno_ringbuffer_start(struct adreno_ringbuffer *rb, unsigned int init_ram) +{ + int status; + /*cp_rb_cntl_u cp_rb_cntl; */ + union reg_cp_rb_cntl cp_rb_cntl; + unsigned int *cmds, rb_cntl; + struct kgsl_device *device = rb->device; + uint cmds_gpu; + + if (rb->flags & KGSL_FLAGS_STARTED) + return 0; + + if (init_ram) { + rb->timestamp = 0; + GSL_RB_INIT_TIMESTAMP(rb); + } + + kgsl_sharedmem_set(&rb->memptrs_desc, 0, 0, + sizeof(struct kgsl_rbmemptrs)); + + kgsl_sharedmem_set(&rb->buffer_desc, 0, 0xAA, + (rb->sizedwords << 2)); + + adreno_regwrite(device, REG_CP_RB_WPTR_BASE, + (rb->memptrs_desc.gpuaddr + + GSL_RB_MEMPTRS_WPTRPOLL_OFFSET)); + + /* setup WPTR delay */ + adreno_regwrite(device, REG_CP_RB_WPTR_DELAY, 0 /*0x70000010 */); + + /*setup REG_CP_RB_CNTL */ + adreno_regread(device, REG_CP_RB_CNTL, &rb_cntl); + cp_rb_cntl.val = rb_cntl; + + /* + * The size of the ringbuffer in the hardware is the log2 + * representation of the size in quadwords (sizedwords / 2) + */ + cp_rb_cntl.f.rb_bufsz = ilog2(rb->sizedwords >> 1); + + /* + * Specify the quadwords to read before updating mem RPTR. + * Like above, pass the log2 representation of the blocksize + * in quadwords. + */ + cp_rb_cntl.f.rb_blksz = ilog2(KGSL_RB_BLKSIZE >> 3); + + cp_rb_cntl.f.rb_poll_en = GSL_RB_CNTL_POLL_EN; /* WPTR polling */ + /* mem RPTR writebacks */ + cp_rb_cntl.f.rb_no_update = GSL_RB_CNTL_NO_UPDATE; + + adreno_regwrite(device, REG_CP_RB_CNTL, cp_rb_cntl.val); + + adreno_regwrite(device, REG_CP_RB_BASE, rb->buffer_desc.gpuaddr); + + adreno_regwrite(device, REG_CP_RB_RPTR_ADDR, + rb->memptrs_desc.gpuaddr + + GSL_RB_MEMPTRS_RPTR_OFFSET); + + /* explicitly clear all cp interrupts */ + adreno_regwrite(device, REG_CP_INT_ACK, 0xFFFFFFFF); + + /* setup scratch/timestamp */ + adreno_regwrite(device, REG_SCRATCH_ADDR, + device->memstore.gpuaddr + + KGSL_DEVICE_MEMSTORE_OFFSET(soptimestamp)); + + adreno_regwrite(device, REG_SCRATCH_UMSK, + GSL_RB_MEMPTRS_SCRATCH_MASK); + + /* load the CP ucode */ + + status = adreno_ringbuffer_load_pm4_ucode(device); + if (status != 0) + return status; + + /* load the prefetch parser ucode */ + status = adreno_ringbuffer_load_pfp_ucode(device); + if (status != 0) + return status; + + adreno_regwrite(device, REG_CP_QUEUE_THRESHOLDS, 0x000C0804); + + rb->rptr = 0; + rb->wptr = 0; + + /* clear ME_HALT to start micro engine */ + adreno_regwrite(device, REG_CP_ME_CNTL, 0); + + /* ME_INIT */ + cmds = adreno_ringbuffer_allocspace(rb, 19); + cmds_gpu = rb->buffer_desc.gpuaddr + sizeof(uint)*(rb->wptr-19); + + GSL_RB_WRITE(cmds, cmds_gpu, PM4_HDR_ME_INIT); + /* All fields present (bits 9:0) */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x000003ff); + /* Disable/Enable Real-Time Stream processing (present but ignored) */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + /* Enable (2D <-> 3D) implicit synchronization (present but ignored) */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + + GSL_RB_WRITE(cmds, cmds_gpu, + GSL_HAL_SUBBLOCK_OFFSET(REG_RB_SURFACE_INFO)); + GSL_RB_WRITE(cmds, cmds_gpu, + GSL_HAL_SUBBLOCK_OFFSET(REG_PA_SC_WINDOW_OFFSET)); + GSL_RB_WRITE(cmds, cmds_gpu, + GSL_HAL_SUBBLOCK_OFFSET(REG_VGT_MAX_VTX_INDX)); + GSL_RB_WRITE(cmds, cmds_gpu, + GSL_HAL_SUBBLOCK_OFFSET(REG_SQ_PROGRAM_CNTL)); + GSL_RB_WRITE(cmds, cmds_gpu, + GSL_HAL_SUBBLOCK_OFFSET(REG_RB_DEPTHCONTROL)); + GSL_RB_WRITE(cmds, cmds_gpu, + GSL_HAL_SUBBLOCK_OFFSET(REG_PA_SU_POINT_SIZE)); + GSL_RB_WRITE(cmds, cmds_gpu, + GSL_HAL_SUBBLOCK_OFFSET(REG_PA_SC_LINE_CNTL)); + GSL_RB_WRITE(cmds, cmds_gpu, + GSL_HAL_SUBBLOCK_OFFSET(REG_PA_SU_POLY_OFFSET_FRONT_SCALE)); + + /* Vertex and Pixel Shader Start Addresses in instructions + * (3 DWORDS per instruction) */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x80000180); + /* Maximum Contexts */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000001); + /* Write Confirm Interval and The CP will wait the + * wait_interval * 16 clocks between polling */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + + /* NQ and External Memory Swap */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + /* Protected mode error checking */ + GSL_RB_WRITE(cmds, cmds_gpu, GSL_RB_PROTECTED_MODE_CONTROL); + /* Disable header dumping and Header dump address */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + /* Header dump size */ + GSL_RB_WRITE(cmds, cmds_gpu, 0x00000000); + + adreno_ringbuffer_submit(rb); + + /* idle device to validate ME INIT */ + status = adreno_idle(device, KGSL_TIMEOUT_DEFAULT); + + if (status == 0) + rb->flags |= KGSL_FLAGS_STARTED; + + return status; +} + +int adreno_ringbuffer_stop(struct adreno_ringbuffer *rb) +{ + if (rb->flags & KGSL_FLAGS_STARTED) { + /* ME_HALT */ + adreno_regwrite(rb->device, REG_CP_ME_CNTL, 0x10000000); + + rb->flags &= ~KGSL_FLAGS_STARTED; + } + + return 0; +} + +int adreno_ringbuffer_init(struct kgsl_device *device) +{ + int status; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + + rb->device = device; + /* + * It is silly to convert this to words and then back to bytes + * immediately below, but most of the rest of the code deals + * in words, so we might as well only do the math once + */ + rb->sizedwords = KGSL_RB_SIZE >> 2; + + /* allocate memory for ringbuffer */ + status = kgsl_allocate_contiguous(&rb->buffer_desc, + (rb->sizedwords << 2)); + + if (status != 0) { + adreno_ringbuffer_close(rb); + return status; + } + + /* allocate memory for polling and timestamps */ + /* This really can be at 4 byte alignment boundry but for using MMU + * we need to make it at page boundary */ + status = kgsl_allocate_contiguous(&rb->memptrs_desc, + sizeof(struct kgsl_rbmemptrs)); + + if (status != 0) { + adreno_ringbuffer_close(rb); + return status; + } + + /* overlay structure on memptrs memory */ + rb->memptrs = (struct kgsl_rbmemptrs *) rb->memptrs_desc.hostptr; + + return 0; +} + +int adreno_ringbuffer_close(struct adreno_ringbuffer *rb) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(rb->device); + + kgsl_sharedmem_free(&rb->buffer_desc); + kgsl_sharedmem_free(&rb->memptrs_desc); + + kfree(adreno_dev->pfp_fw); + kfree(adreno_dev->pm4_fw); + + adreno_dev->pfp_fw = NULL; + adreno_dev->pm4_fw = NULL; + + memset(rb, 0, sizeof(struct adreno_ringbuffer)); + + return 0; +} + +static uint32_t +adreno_ringbuffer_addcmds(struct adreno_ringbuffer *rb, + unsigned int flags, unsigned int *cmds, + int sizedwords) +{ + unsigned int *ringcmds; + unsigned int timestamp; + unsigned int total_sizedwords = sizedwords + 6; + unsigned int i; + unsigned int rcmd_gpu; + + /* reserve space to temporarily turn off protected mode + * error checking if needed + */ + total_sizedwords += flags & KGSL_CMD_FLAGS_PMODE ? 4 : 0; + total_sizedwords += !(flags & KGSL_CMD_FLAGS_NO_TS_CMP) ? 7 : 0; + total_sizedwords += !(flags & KGSL_CMD_FLAGS_NOT_KERNEL_CMD) ? 2 : 0; + + ringcmds = adreno_ringbuffer_allocspace(rb, total_sizedwords); + rcmd_gpu = rb->buffer_desc.gpuaddr + + sizeof(uint)*(rb->wptr-total_sizedwords); + + if (!(flags & KGSL_CMD_FLAGS_NOT_KERNEL_CMD)) { + GSL_RB_WRITE(ringcmds, rcmd_gpu, pm4_nop_packet(1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, KGSL_CMD_IDENTIFIER); + } + if (flags & KGSL_CMD_FLAGS_PMODE) { + /* disable protected mode error checking */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, + pm4_type3_packet(PM4_SET_PROTECTED_MODE, 1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, 0); + } + + for (i = 0; i < sizedwords; i++) { + GSL_RB_WRITE(ringcmds, rcmd_gpu, *cmds); + cmds++; + } + + if (flags & KGSL_CMD_FLAGS_PMODE) { + /* re-enable protected mode error checking */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, + pm4_type3_packet(PM4_SET_PROTECTED_MODE, 1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, 1); + } + + rb->timestamp++; + timestamp = rb->timestamp; + + /* start-of-pipeline and end-of-pipeline timestamps */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, pm4_type0_packet(REG_CP_TIMESTAMP, 1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, rb->timestamp); + GSL_RB_WRITE(ringcmds, rcmd_gpu, pm4_type3_packet(PM4_EVENT_WRITE, 3)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, CACHE_FLUSH_TS); + GSL_RB_WRITE(ringcmds, rcmd_gpu, + (rb->device->memstore.gpuaddr + + KGSL_DEVICE_MEMSTORE_OFFSET(eoptimestamp))); + GSL_RB_WRITE(ringcmds, rcmd_gpu, rb->timestamp); + + if (!(flags & KGSL_CMD_FLAGS_NO_TS_CMP)) { + /* Conditional execution based on memory values */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, + pm4_type3_packet(PM4_COND_EXEC, 4)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, (rb->device->memstore.gpuaddr + + KGSL_DEVICE_MEMSTORE_OFFSET(ts_cmp_enable)) >> 2); + GSL_RB_WRITE(ringcmds, rcmd_gpu, (rb->device->memstore.gpuaddr + + KGSL_DEVICE_MEMSTORE_OFFSET(ref_wait_ts)) >> 2); + GSL_RB_WRITE(ringcmds, rcmd_gpu, rb->timestamp); + /* # of conditional command DWORDs */ + GSL_RB_WRITE(ringcmds, rcmd_gpu, 2); + GSL_RB_WRITE(ringcmds, rcmd_gpu, + pm4_type3_packet(PM4_INTERRUPT, 1)); + GSL_RB_WRITE(ringcmds, rcmd_gpu, CP_INT_CNTL__RB_INT_MASK); + } + + adreno_ringbuffer_submit(rb); + + /* return timestamp of issued coREG_ands */ + return timestamp; +} + +void +adreno_ringbuffer_issuecmds(struct kgsl_device *device, + unsigned int flags, + unsigned int *cmds, + int sizedwords) +{ + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + struct adreno_ringbuffer *rb = &adreno_dev->ringbuffer; + + if (device->state & KGSL_STATE_HUNG) + return; + adreno_ringbuffer_addcmds(rb, flags, cmds, sizedwords); +} + +int +adreno_ringbuffer_issueibcmds(struct kgsl_device_private *dev_priv, + struct kgsl_context *context, + struct kgsl_ibdesc *ibdesc, + unsigned int numibs, + uint32_t *timestamp, + unsigned int flags) +{ + struct kgsl_device *device = dev_priv->device; + struct adreno_device *adreno_dev = ADRENO_DEVICE(device); + unsigned int *link; + unsigned int *cmds; + unsigned int i; + struct adreno_context *drawctxt = context->devctxt; + + if (device->state & KGSL_STATE_HUNG) + return -EBUSY; + if (!(adreno_dev->ringbuffer.flags & KGSL_FLAGS_STARTED) || + context == NULL) + return -EINVAL; + + BUG_ON(ibdesc == 0); + BUG_ON(numibs == 0); + + if (drawctxt->flags & CTXT_FLAGS_GPU_HANG) { + KGSL_CTXT_WARN(device, "Context %p caused a gpu hang.." + " will not accept commands for this context\n", + drawctxt); + return -EDEADLK; + } + link = kzalloc(sizeof(unsigned int) * numibs * 3, GFP_KERNEL); + cmds = link; + if (!link) { + KGSL_MEM_ERR(device, "Failed to allocate memory for for command" + " submission, size %x\n", numibs * 3); + return -ENOMEM; + } + for (i = 0; i < numibs; i++) { + (void)kgsl_cffdump_parse_ibs(dev_priv, NULL, + ibdesc[i].gpuaddr, ibdesc[i].sizedwords, false); + + *cmds++ = PM4_HDR_INDIRECT_BUFFER_PFD; + *cmds++ = ibdesc[i].gpuaddr; + *cmds++ = ibdesc[i].sizedwords; + } + + kgsl_setstate(device, + kgsl_pt_get_flags(device->mmu.hwpagetable, + device->id)); + + adreno_drawctxt_switch(adreno_dev, drawctxt, flags); + + *timestamp = adreno_ringbuffer_addcmds(&adreno_dev->ringbuffer, + KGSL_CMD_FLAGS_NOT_KERNEL_CMD, + &link[0], (cmds - link)); + + KGSL_CMD_INFO(device, "ctxt %d g %08x numibs %d ts %d\n", + context->id, (unsigned int)ibdesc, numibs, *timestamp); + + kfree(link); + +#ifdef CONFIG_MSM_KGSL_CFF_DUMP + /* + * insert wait for idle after every IB1 + * this is conservative but works reliably and is ok + * even for performance simulations + */ + adreno_idle(device, KGSL_TIMEOUT_DEFAULT); +#endif + + return 0; +} + +int adreno_ringbuffer_extract(struct adreno_ringbuffer *rb, + unsigned int *temp_rb_buffer, + int *rb_size) +{ + struct kgsl_device *device = rb->device; + unsigned int rb_rptr; + unsigned int retired_timestamp; + unsigned int temp_idx = 0; + unsigned int value; + unsigned int val1; + unsigned int val2; + unsigned int val3; + unsigned int copy_rb_contents = 0; + unsigned int cur_context; + unsigned int j; + + GSL_RB_GET_READPTR(rb, &rb->rptr); + + retired_timestamp = device->ftbl->readtimestamp(device, + KGSL_TIMESTAMP_RETIRED); + KGSL_DRV_ERR(device, "GPU successfully executed till ts: %x\n", + retired_timestamp); + /* + * We need to go back in history by 4 dwords from the current location + * of read pointer as 4 dwords are read to match the end of a command. + * Also, take care of wrap around when moving back + */ + if (rb->rptr >= 4) + rb_rptr = (rb->rptr - 4) * sizeof(unsigned int); + else + rb_rptr = rb->buffer_desc.size - + ((4 - rb->rptr) * sizeof(unsigned int)); + /* Read the rb contents going backwards to locate end of last + * sucessfully executed command */ + while ((rb_rptr / sizeof(unsigned int)) != rb->wptr) { + kgsl_sharedmem_readl(&rb->buffer_desc, &value, rb_rptr); + if (value == retired_timestamp) { + rb_rptr = adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size); + kgsl_sharedmem_readl(&rb->buffer_desc, &val1, rb_rptr); + rb_rptr = adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size); + kgsl_sharedmem_readl(&rb->buffer_desc, &val2, rb_rptr); + rb_rptr = adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size); + kgsl_sharedmem_readl(&rb->buffer_desc, &val3, rb_rptr); + /* match the pattern found at the end of a command */ + if ((val1 == 2 && + val2 == pm4_type3_packet(PM4_INTERRUPT, 1) + && val3 == CP_INT_CNTL__RB_INT_MASK) || + (val1 == pm4_type3_packet(PM4_EVENT_WRITE, 3) + && val2 == CACHE_FLUSH_TS && + val3 == (rb->device->memstore.gpuaddr + + KGSL_DEVICE_MEMSTORE_OFFSET(eoptimestamp)))) { + rb_rptr = adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size); + KGSL_DRV_ERR(device, + "Found end of last executed " + "command at offset: %x\n", + rb_rptr / sizeof(unsigned int)); + break; + } else { + if (rb_rptr < (3 * sizeof(unsigned int))) + rb_rptr = rb->buffer_desc.size - + (3 * sizeof(unsigned int)) + + rb_rptr; + else + rb_rptr -= (3 * sizeof(unsigned int)); + } + } + + if (rb_rptr == 0) + rb_rptr = rb->buffer_desc.size - sizeof(unsigned int); + else + rb_rptr -= sizeof(unsigned int); + } + + if ((rb_rptr / sizeof(unsigned int)) == rb->wptr) { + KGSL_DRV_ERR(device, + "GPU recovery from hang not possible because last" + " successful timestamp is overwritten\n"); + return -EINVAL; + } + /* rb_rptr is now pointing to the first dword of the command following + * the last sucessfully executed command sequence. Assumption is that + * GPU is hung in the command sequence pointed by rb_rptr */ + /* make sure the GPU is not hung in a command submitted by kgsl + * itself */ + kgsl_sharedmem_readl(&rb->buffer_desc, &val1, rb_rptr); + kgsl_sharedmem_readl(&rb->buffer_desc, &val2, + adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size)); + if (val1 == pm4_nop_packet(1) && val2 == KGSL_CMD_IDENTIFIER) { + KGSL_DRV_ERR(device, + "GPU recovery from hang not possible because " + "of hang in kgsl command\n"); + return -EINVAL; + } + + /* current_context is the context that is presently active in the + * GPU, i.e the context in which the hang is caused */ + kgsl_sharedmem_readl(&device->memstore, &cur_context, + KGSL_DEVICE_MEMSTORE_OFFSET(current_context)); + while ((rb_rptr / sizeof(unsigned int)) != rb->wptr) { + kgsl_sharedmem_readl(&rb->buffer_desc, &value, rb_rptr); + rb_rptr = adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size); + /* check for context switch indicator */ + if (value == KGSL_CONTEXT_TO_MEM_IDENTIFIER) { + kgsl_sharedmem_readl(&rb->buffer_desc, &value, rb_rptr); + rb_rptr = adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size); + BUG_ON(value != pm4_type3_packet(PM4_MEM_WRITE, 2)); + kgsl_sharedmem_readl(&rb->buffer_desc, &val1, rb_rptr); + rb_rptr = adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size); + BUG_ON(val1 != (device->memstore.gpuaddr + + KGSL_DEVICE_MEMSTORE_OFFSET(current_context))); + kgsl_sharedmem_readl(&rb->buffer_desc, &value, rb_rptr); + rb_rptr = adreno_ringbuffer_inc_wrapped(rb_rptr, + rb->buffer_desc.size); + BUG_ON((copy_rb_contents == 0) && + (value == cur_context)); + /* + * If we were copying the commands and got to this point + * then we need to remove the 3 commands that appear + * before KGSL_CONTEXT_TO_MEM_IDENTIFIER + */ + if (temp_idx) + temp_idx -= 3; + /* if context switches to a context that did not cause + * hang then start saving the rb contents as those + * commands can be executed */ + if (value != cur_context) { + copy_rb_contents = 1; + temp_rb_buffer[temp_idx++] = pm4_nop_packet(1); + temp_rb_buffer[temp_idx++] = + KGSL_CMD_IDENTIFIER; + temp_rb_buffer[temp_idx++] = pm4_nop_packet(1); + temp_rb_buffer[temp_idx++] = + KGSL_CONTEXT_TO_MEM_IDENTIFIER; + temp_rb_buffer[temp_idx++] = + pm4_type3_packet(PM4_MEM_WRITE, 2); + temp_rb_buffer[temp_idx++] = val1; + temp_rb_buffer[temp_idx++] = value; + } else { + copy_rb_contents = 0; + } + } else if (copy_rb_contents) + temp_rb_buffer[temp_idx++] = value; + } + + *rb_size = temp_idx; + KGSL_DRV_ERR(device, "Extracted rb contents, size: %x\n", *rb_size); + for (temp_idx = 0; temp_idx < *rb_size;) { + char str[80]; + int idx = 0; + if ((temp_idx + 8) <= *rb_size) + j = 8; + else + j = *rb_size - temp_idx; + for (; j != 0; j--) + idx += scnprintf(str + idx, 80 - idx, + "%8.8X ", temp_rb_buffer[temp_idx++]); + printk(KERN_ALERT "%s", str); + } + return 0; +} + +void +adreno_ringbuffer_restore(struct adreno_ringbuffer *rb, unsigned int *rb_buff, + int num_rb_contents) +{ + int i; + unsigned int *ringcmds; + unsigned int rcmd_gpu; + + if (!num_rb_contents) + return; + + if (num_rb_contents > (rb->buffer_desc.size - rb->wptr)) { + adreno_regwrite(rb->device, REG_CP_RB_RPTR, 0); + rb->rptr = 0; + BUG_ON(num_rb_contents > rb->buffer_desc.size); + } + ringcmds = (unsigned int *)rb->buffer_desc.hostptr + rb->wptr; + rcmd_gpu = rb->buffer_desc.gpuaddr + sizeof(unsigned int) * rb->wptr; + for (i = 0; i < num_rb_contents; i++) + GSL_RB_WRITE(ringcmds, rcmd_gpu, rb_buff[i]); + rb->wptr += num_rb_contents; + adreno_ringbuffer_submit(rb); +} diff --git a/drivers/gpu/msm/adreno_ringbuffer.h b/drivers/gpu/msm/adreno_ringbuffer.h new file mode 100644 index 00000000000..04432feb0ae --- /dev/null +++ b/drivers/gpu/msm/adreno_ringbuffer.h @@ -0,0 +1,156 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ADRENO_RINGBUFFER_H +#define __ADRENO_RINGBUFFER_H + +#define GSL_RB_USE_MEM_RPTR +#define GSL_RB_USE_MEM_TIMESTAMP +#define GSL_DEVICE_SHADOW_MEMSTORE_TO_USER + +/* + * Adreno ringbuffer sizes in bytes - these are converted to + * the appropriate log2 values in the code + */ + +#define KGSL_RB_SIZE (32 * 1024) +#define KGSL_RB_BLKSIZE 16 + +/* CP timestamp register */ +#define REG_CP_TIMESTAMP REG_SCRATCH_REG0 + + +struct kgsl_device; +struct kgsl_device_private; + +#define GSL_RB_MEMPTRS_SCRATCH_COUNT 8 +struct kgsl_rbmemptrs { + int rptr; + int wptr_poll; +}; + +#define GSL_RB_MEMPTRS_RPTR_OFFSET \ + (offsetof(struct kgsl_rbmemptrs, rptr)) + +#define GSL_RB_MEMPTRS_WPTRPOLL_OFFSET \ + (offsetof(struct kgsl_rbmemptrs, wptr_poll)) + +struct adreno_ringbuffer { + struct kgsl_device *device; + uint32_t flags; + + struct kgsl_memdesc buffer_desc; + + struct kgsl_memdesc memptrs_desc; + struct kgsl_rbmemptrs *memptrs; + + /*ringbuffer size */ + unsigned int sizedwords; + + unsigned int wptr; /* write pointer offset in dwords from baseaddr */ + unsigned int rptr; /* read pointer offset in dwords from baseaddr */ + uint32_t timestamp; +}; + +/* dword base address of the GFX decode space */ +#define GSL_HAL_SUBBLOCK_OFFSET(reg) ((unsigned int)((reg) - (0x2000))) + +#define GSL_RB_WRITE(ring, gpuaddr, data) \ + do { \ + writel_relaxed(data, ring); \ + wmb(); \ + kgsl_cffdump_setmem(gpuaddr, data, 4); \ + ring++; \ + gpuaddr += sizeof(uint); \ + } while (0) + +/* timestamp */ +#ifdef GSL_DEVICE_SHADOW_MEMSTORE_TO_USER +#define GSL_RB_USE_MEM_TIMESTAMP +#endif /* GSL_DEVICE_SHADOW_MEMSTORE_TO_USER */ + +#ifdef GSL_RB_USE_MEM_TIMESTAMP +/* enable timestamp (...scratch0) memory shadowing */ +#define GSL_RB_MEMPTRS_SCRATCH_MASK 0x1 +#define GSL_RB_INIT_TIMESTAMP(rb) + +#else +#define GSL_RB_MEMPTRS_SCRATCH_MASK 0x0 +#define GSL_RB_INIT_TIMESTAMP(rb) \ + adreno_regwrite((rb)->device->id, REG_CP_TIMESTAMP, 0) + +#endif /* GSL_RB_USE_MEMTIMESTAMP */ + +/* mem rptr */ +#ifdef GSL_RB_USE_MEM_RPTR +#define GSL_RB_CNTL_NO_UPDATE 0x0 /* enable */ +#define GSL_RB_GET_READPTR(rb, data) \ + do { \ + *(data) = readl_relaxed(&(rb)->memptrs->rptr); \ + } while (0) +#else +#define GSL_RB_CNTL_NO_UPDATE 0x1 /* disable */ +#define GSL_RB_GET_READPTR(rb, data) \ + do { \ + adreno_regread((rb)->device->id, REG_CP_RB_RPTR, (data)); \ + } while (0) +#endif /* GSL_RB_USE_MEMRPTR */ + +#define GSL_RB_CNTL_POLL_EN 0x0 /* disable */ + +int adreno_ringbuffer_issueibcmds(struct kgsl_device_private *dev_priv, + struct kgsl_context *context, + struct kgsl_ibdesc *ibdesc, + unsigned int numibs, + uint32_t *timestamp, + unsigned int flags); + +int adreno_ringbuffer_init(struct kgsl_device *device); + +int adreno_ringbuffer_start(struct adreno_ringbuffer *rb, + unsigned int init_ram); + +int adreno_ringbuffer_stop(struct adreno_ringbuffer *rb); + +int adreno_ringbuffer_close(struct adreno_ringbuffer *rb); + +void adreno_ringbuffer_issuecmds(struct kgsl_device *device, + unsigned int flags, + unsigned int *cmdaddr, + int sizedwords); + +void kgsl_cp_intrcallback(struct kgsl_device *device); + +int adreno_ringbuffer_extract(struct adreno_ringbuffer *rb, + unsigned int *temp_rb_buffer, + int *rb_size); + +void +adreno_ringbuffer_restore(struct adreno_ringbuffer *rb, unsigned int *rb_buff, + int num_rb_contents); + +static inline int adreno_ringbuffer_count(struct adreno_ringbuffer *rb, + unsigned int rptr) +{ + if (rb->wptr >= rptr) + return rb->wptr - rptr; + return rb->wptr + rb->sizedwords - rptr; +} + +/* Increment a value by 4 bytes with wrap-around based on size */ +static inline unsigned int adreno_ringbuffer_inc_wrapped(unsigned int val, + unsigned int size) +{ + return (val + sizeof(unsigned int)) % size; +} + +#endif /* __ADRENO_RINGBUFFER_H */ diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c new file mode 100644 index 00000000000..fef664b29ea --- /dev/null +++ b/drivers/gpu/msm/kgsl.c @@ -0,0 +1,2119 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kgsl.h" +#include "kgsl_debugfs.h" +#include "kgsl_cffdump.h" +#include "kgsl_log.h" +#include "kgsl_sharedmem.h" +#include "kgsl_device.h" + +#undef MODULE_PARAM_PREFIX +#define MODULE_PARAM_PREFIX "kgsl." + +static int kgsl_pagetable_count = KGSL_PAGETABLE_COUNT; +module_param_named(ptcount, kgsl_pagetable_count, int, 0); +MODULE_PARM_DESC(kgsl_pagetable_count, +"Minimum number of pagetables for KGSL to allocate at initialization time"); + +static inline struct kgsl_mem_entry * +kgsl_mem_entry_create(void) +{ + struct kgsl_mem_entry *entry = kzalloc(sizeof(*entry), GFP_KERNEL); + + if (!entry) + KGSL_CORE_ERR("kzalloc(%d) failed\n", sizeof(*entry)); + else + kref_init(&entry->refcount); + + return entry; +} + +void +kgsl_mem_entry_destroy(struct kref *kref) +{ + struct kgsl_mem_entry *entry = container_of(kref, + struct kgsl_mem_entry, + refcount); + size_t size = entry->memdesc.size; + + kgsl_sharedmem_free(&entry->memdesc); + + if (entry->memtype == KGSL_USER_MEMORY) + entry->priv->stats.user -= size; + else if (entry->memtype == KGSL_MAPPED_MEMORY) { + if (entry->file_ptr) + fput(entry->file_ptr); + + kgsl_driver.stats.mapped -= size; + entry->priv->stats.mapped -= size; + } + + kfree(entry); +} +EXPORT_SYMBOL(kgsl_mem_entry_destroy); + +static +void kgsl_mem_entry_attach_process(struct kgsl_mem_entry *entry, + struct kgsl_process_private *process) +{ + spin_lock(&process->mem_lock); + list_add(&entry->list, &process->mem_list); + spin_unlock(&process->mem_lock); + + entry->priv = process; +} + +/* Allocate a new context id */ + +static struct kgsl_context * +kgsl_create_context(struct kgsl_device_private *dev_priv) +{ + struct kgsl_context *context; + int ret, id; + + context = kzalloc(sizeof(*context), GFP_KERNEL); + + if (context == NULL) + return NULL; + + while (1) { + if (idr_pre_get(&dev_priv->device->context_idr, + GFP_KERNEL) == 0) { + kfree(context); + return NULL; + } + + ret = idr_get_new(&dev_priv->device->context_idr, + context, &id); + + if (ret != -EAGAIN) + break; + } + + if (ret) { + kfree(context); + return NULL; + } + + context->id = id; + context->dev_priv = dev_priv; + + return context; +} + +static void +kgsl_destroy_context(struct kgsl_device_private *dev_priv, + struct kgsl_context *context) +{ + int id; + + if (context == NULL) + return; + + /* Fire a bug if the devctxt hasn't been freed */ + BUG_ON(context->devctxt); + + id = context->id; + kfree(context); + + idr_remove(&dev_priv->device->context_idr, id); +} + +/* to be called when a process is destroyed, this walks the memqueue and + * frees any entryies that belong to the dying process + */ +static void kgsl_memqueue_cleanup(struct kgsl_device *device, + struct kgsl_process_private *private) +{ + struct kgsl_mem_entry *entry, *entry_tmp; + + if (!private) + return; + + BUG_ON(!mutex_is_locked(&device->mutex)); + + list_for_each_entry_safe(entry, entry_tmp, &device->memqueue, list) { + if (entry->priv == private) { + list_del(&entry->list); + kgsl_mem_entry_put(entry); + } + } +} + +static void kgsl_memqueue_freememontimestamp(struct kgsl_device *device, + struct kgsl_mem_entry *entry, + uint32_t timestamp, + enum kgsl_timestamp_type type) +{ + BUG_ON(!mutex_is_locked(&device->mutex)); + + entry->free_timestamp = timestamp; + + list_add_tail(&entry->list, &device->memqueue); +} + +static void kgsl_memqueue_drain(struct kgsl_device *device) +{ + struct kgsl_mem_entry *entry, *entry_tmp; + uint32_t ts_processed; + + BUG_ON(!mutex_is_locked(&device->mutex)); + + /* get current EOP timestamp */ + ts_processed = device->ftbl->readtimestamp(device, + KGSL_TIMESTAMP_RETIRED); + + list_for_each_entry_safe(entry, entry_tmp, &device->memqueue, list) { + KGSL_MEM_INFO(device, + "ts_processed %d ts_free %d gpuaddr %x)\n", + ts_processed, entry->free_timestamp, + entry->memdesc.gpuaddr); + if (!timestamp_cmp(ts_processed, entry->free_timestamp)) + break; + + list_del(&entry->list); + kgsl_mem_entry_put(entry); + } +} + +static void kgsl_memqueue_drain_unlocked(struct kgsl_device *device) +{ + mutex_lock(&device->mutex); + kgsl_check_suspended(device); + kgsl_memqueue_drain(device); + mutex_unlock(&device->mutex); +} + +static void kgsl_check_idle_locked(struct kgsl_device *device) +{ + if (device->pwrctrl.nap_allowed == true && + device->state == KGSL_STATE_ACTIVE && + device->requested_state == KGSL_STATE_NONE) { + device->requested_state = KGSL_STATE_NAP; + if (kgsl_pwrctrl_sleep(device) != 0) + mod_timer(&device->idle_timer, + jiffies + + device->pwrctrl.interval_timeout); + } +} + +static void kgsl_check_idle(struct kgsl_device *device) +{ + mutex_lock(&device->mutex); + kgsl_check_idle_locked(device); + mutex_unlock(&device->mutex); +} + +struct kgsl_device *kgsl_get_device(int dev_idx) +{ + int i; + struct kgsl_device *ret = NULL; + + mutex_lock(&kgsl_driver.devlock); + + for (i = 0; i < KGSL_DEVICE_MAX; i++) { + if (kgsl_driver.devp[i] && kgsl_driver.devp[i]->id == dev_idx) { + ret = kgsl_driver.devp[i]; + break; + } + } + + mutex_unlock(&kgsl_driver.devlock); + return ret; +} +EXPORT_SYMBOL(kgsl_get_device); + +static struct kgsl_device *kgsl_get_minor(int minor) +{ + struct kgsl_device *ret = NULL; + + if (minor < 0 || minor >= KGSL_DEVICE_MAX) + return NULL; + + mutex_lock(&kgsl_driver.devlock); + ret = kgsl_driver.devp[minor]; + mutex_unlock(&kgsl_driver.devlock); + + return ret; +} + +int kgsl_register_ts_notifier(struct kgsl_device *device, + struct notifier_block *nb) +{ + BUG_ON(device == NULL); + return atomic_notifier_chain_register(&device->ts_notifier_list, + nb); +} +EXPORT_SYMBOL(kgsl_register_ts_notifier); + +int kgsl_unregister_ts_notifier(struct kgsl_device *device, + struct notifier_block *nb) +{ + BUG_ON(device == NULL); + return atomic_notifier_chain_unregister(&device->ts_notifier_list, + nb); +} +EXPORT_SYMBOL(kgsl_unregister_ts_notifier); + +int kgsl_check_timestamp(struct kgsl_device *device, unsigned int timestamp) +{ + unsigned int ts_processed; + + ts_processed = device->ftbl->readtimestamp(device, + KGSL_TIMESTAMP_RETIRED); + + return timestamp_cmp(ts_processed, timestamp); +} +EXPORT_SYMBOL(kgsl_check_timestamp); + +static int kgsl_suspend_device(struct kgsl_device *device, pm_message_t state) +{ + int status = -EINVAL; + unsigned int nap_allowed_saved; + struct kgsl_pwrscale_policy *policy_saved; + + if (!device) + return -EINVAL; + + KGSL_PWR_WARN(device, "suspend start\n"); + + mutex_lock(&device->mutex); + nap_allowed_saved = device->pwrctrl.nap_allowed; + device->pwrctrl.nap_allowed = false; + policy_saved = device->pwrscale.policy; + device->pwrscale.policy = NULL; + device->requested_state = KGSL_STATE_SUSPEND; + /* Make sure no user process is waiting for a timestamp * + * before supending */ + if (device->active_cnt != 0) { + mutex_unlock(&device->mutex); + wait_for_completion(&device->suspend_gate); + mutex_lock(&device->mutex); + } + /* Don't let the timer wake us during suspended sleep. */ + del_timer(&device->idle_timer); + switch (device->state) { + case KGSL_STATE_INIT: + break; + case KGSL_STATE_ACTIVE: + /* Wait for the device to become idle */ + device->ftbl->idle(device, KGSL_TIMEOUT_DEFAULT); + case KGSL_STATE_NAP: + case KGSL_STATE_SLEEP: + /* Get the completion ready to be waited upon. */ + INIT_COMPLETION(device->hwaccess_gate); + device->ftbl->suspend_context(device); + device->ftbl->stop(device); + device->state = KGSL_STATE_SUSPEND; + KGSL_PWR_WARN(device, "state -> SUSPEND, device %d\n", + device->id); + break; + default: + KGSL_PWR_ERR(device, "suspend fail, device %d\n", + device->id); + goto end; + } + device->requested_state = KGSL_STATE_NONE; + device->pwrctrl.nap_allowed = nap_allowed_saved; + device->pwrscale.policy = policy_saved; + status = 0; + +end: + mutex_unlock(&device->mutex); + KGSL_PWR_WARN(device, "suspend end\n"); + return status; +} + +static int kgsl_resume_device(struct kgsl_device *device) +{ + int status = -EINVAL; + + if (!device) + return -EINVAL; + + KGSL_PWR_WARN(device, "resume start\n"); + mutex_lock(&device->mutex); + if (device->state == KGSL_STATE_SUSPEND) { + device->requested_state = KGSL_STATE_ACTIVE; + kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_NOMINAL); + status = device->ftbl->start(device, 0); + if (status == 0) { + device->state = KGSL_STATE_ACTIVE; + KGSL_PWR_WARN(device, + "state -> ACTIVE, device %d\n", + device->id); + } else { + KGSL_PWR_ERR(device, + "resume failed, device %d\n", + device->id); + device->state = KGSL_STATE_INIT; + goto end; + } + complete_all(&device->hwaccess_gate); + } + device->requested_state = KGSL_STATE_NONE; + +end: + mutex_unlock(&device->mutex); + kgsl_check_idle(device); + KGSL_PWR_WARN(device, "resume end\n"); + return status; +} + +static int kgsl_suspend(struct device *dev) +{ + + pm_message_t arg = {0}; + struct kgsl_device *device = dev_get_drvdata(dev); + return kgsl_suspend_device(device, arg); +} + +static int kgsl_resume(struct device *dev) +{ + struct kgsl_device *device = dev_get_drvdata(dev); + return kgsl_resume_device(device); +} + +static int kgsl_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int kgsl_runtime_resume(struct device *dev) +{ + return 0; +} + +const struct dev_pm_ops kgsl_pm_ops = { + .suspend = kgsl_suspend, + .resume = kgsl_resume, + .runtime_suspend = kgsl_runtime_suspend, + .runtime_resume = kgsl_runtime_resume, +}; +EXPORT_SYMBOL(kgsl_pm_ops); + +void kgsl_early_suspend_driver(struct early_suspend *h) +{ + struct kgsl_device *device = container_of(h, + struct kgsl_device, display_off); + kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_NOMINAL); +} +EXPORT_SYMBOL(kgsl_early_suspend_driver); + +int kgsl_suspend_driver(struct platform_device *pdev, + pm_message_t state) +{ + struct kgsl_device *device = dev_get_drvdata(&pdev->dev); + return kgsl_suspend_device(device, state); +} +EXPORT_SYMBOL(kgsl_suspend_driver); + +int kgsl_resume_driver(struct platform_device *pdev) +{ + struct kgsl_device *device = dev_get_drvdata(&pdev->dev); + return kgsl_resume_device(device); +} +EXPORT_SYMBOL(kgsl_resume_driver); + +void kgsl_late_resume_driver(struct early_suspend *h) +{ + struct kgsl_device *device = container_of(h, + struct kgsl_device, display_off); + kgsl_pwrctrl_pwrlevel_change(device, KGSL_PWRLEVEL_TURBO); +} +EXPORT_SYMBOL(kgsl_late_resume_driver); + +/* file operations */ +static struct kgsl_process_private * +kgsl_get_process_private(struct kgsl_device_private *cur_dev_priv) +{ + struct kgsl_process_private *private; + + mutex_lock(&kgsl_driver.process_mutex); + list_for_each_entry(private, &kgsl_driver.process_list, list) { + if (private->pid == task_tgid_nr(current)) { + private->refcnt++; + goto out; + } + } + + /* no existing process private found for this dev_priv, create one */ + private = kzalloc(sizeof(struct kgsl_process_private), GFP_KERNEL); + if (private == NULL) { + KGSL_DRV_ERR(cur_dev_priv->device, "kzalloc(%d) failed\n", + sizeof(struct kgsl_process_private)); + goto out; + } + + spin_lock_init(&private->mem_lock); + private->refcnt = 1; + private->pid = task_tgid_nr(current); + + INIT_LIST_HEAD(&private->mem_list); + +#ifdef CONFIG_MSM_KGSL_MMU + { + unsigned long pt_name; + +#ifdef CONFIG_KGSL_PER_PROCESS_PAGE_TABLE + pt_name = task_tgid_nr(current); +#else + pt_name = KGSL_MMU_GLOBAL_PT; +#endif + private->pagetable = kgsl_mmu_getpagetable(pt_name); + if (private->pagetable == NULL) { + kfree(private); + private = NULL; + goto out; + } + } +#endif + + list_add(&private->list, &kgsl_driver.process_list); + + kgsl_process_init_sysfs(private); + +out: + mutex_unlock(&kgsl_driver.process_mutex); + return private; +} + +static void +kgsl_put_process_private(struct kgsl_device *device, + struct kgsl_process_private *private) +{ + struct kgsl_mem_entry *entry = NULL; + struct kgsl_mem_entry *entry_tmp = NULL; + + if (!private) + return; + + mutex_lock(&kgsl_driver.process_mutex); + + if (--private->refcnt) + goto unlock; + + KGSL_MEM_INFO(device, + "Memory usage: user (%d/%d) mapped (%d/%d)\n", + private->stats.user, private->stats.user_max, + private->stats.mapped, private->stats.mapped_max); + + kgsl_process_uninit_sysfs(private); + + list_del(&private->list); + + list_for_each_entry_safe(entry, entry_tmp, &private->mem_list, list) { + list_del(&entry->list); + kgsl_mem_entry_put(entry); + } + + kgsl_mmu_putpagetable(private->pagetable); + kfree(private); +unlock: + mutex_unlock(&kgsl_driver.process_mutex); +} + +static int kgsl_release(struct inode *inodep, struct file *filep) +{ + int result = 0; + struct kgsl_device_private *dev_priv = NULL; + struct kgsl_process_private *private = NULL; + struct kgsl_device *device; + struct kgsl_context *context; + int next = 0; + + device = kgsl_driver.devp[iminor(inodep)]; + BUG_ON(device == NULL); + + dev_priv = (struct kgsl_device_private *) filep->private_data; + BUG_ON(dev_priv == NULL); + BUG_ON(device != dev_priv->device); + /* private could be null if kgsl_open is not successful */ + private = dev_priv->process_priv; + filep->private_data = NULL; + + mutex_lock(&device->mutex); + kgsl_check_suspended(device); + + while (1) { + context = idr_get_next(&dev_priv->device->context_idr, &next); + if (context == NULL) + break; + + if (context->dev_priv == dev_priv) { + device->ftbl->drawctxt_destroy(device, context); + kgsl_destroy_context(dev_priv, context); + } + + next = next + 1; + } + + device->open_count--; + if (device->open_count == 0) { + result = device->ftbl->stop(device); + device->state = KGSL_STATE_INIT; + KGSL_PWR_WARN(device, "state -> INIT, device %d\n", device->id); + } + /* clean up any to-be-freed entries that belong to this + * process and this device + */ + kgsl_memqueue_cleanup(device, private); + + mutex_unlock(&device->mutex); + kfree(dev_priv); + + kgsl_put_process_private(device, private); + + pm_runtime_put(device->parentdev); + return result; +} + +static int kgsl_open(struct inode *inodep, struct file *filep) +{ + int result; + struct kgsl_device_private *dev_priv; + struct kgsl_device *device; + unsigned int minor = iminor(inodep); + + device = kgsl_get_minor(minor); + BUG_ON(device == NULL); + + if (filep->f_flags & O_EXCL) { + KGSL_DRV_ERR(device, "O_EXCL not allowed\n"); + return -EBUSY; + } + + result = pm_runtime_get_sync(device->parentdev); + if (result < 0) { + KGSL_DRV_ERR(device, + "Runtime PM: Unable to wake up the device, rc = %d\n", + result); + return result; + } + result = 0; + + dev_priv = kzalloc(sizeof(struct kgsl_device_private), GFP_KERNEL); + if (dev_priv == NULL) { + KGSL_DRV_ERR(device, "kzalloc failed(%d)\n", + sizeof(struct kgsl_device_private)); + result = -ENOMEM; + goto err_pmruntime; + } + + dev_priv->device = device; + filep->private_data = dev_priv; + + /* Get file (per process) private struct */ + dev_priv->process_priv = kgsl_get_process_private(dev_priv); + if (dev_priv->process_priv == NULL) { + result = -ENOMEM; + goto err_freedevpriv; + } + + mutex_lock(&device->mutex); + kgsl_check_suspended(device); + + if (device->open_count == 0) { + result = device->ftbl->start(device, true); + + if (result) { + mutex_unlock(&device->mutex); + goto err_putprocess; + } + device->state = KGSL_STATE_ACTIVE; + KGSL_PWR_WARN(device, + "state -> ACTIVE, device %d\n", minor); + } + device->open_count++; + mutex_unlock(&device->mutex); + + KGSL_DRV_INFO(device, "Initialized %s: mmu=%s pagetable_count=%d\n", + device->name, kgsl_mmu_enabled() ? "on" : "off", + kgsl_pagetable_count); + + return result; + +err_putprocess: + kgsl_put_process_private(device, dev_priv->process_priv); +err_freedevpriv: + filep->private_data = NULL; + kfree(dev_priv); +err_pmruntime: + pm_runtime_put(device->parentdev); + return result; +} + + +/*call with private->mem_lock locked */ +static struct kgsl_mem_entry * +kgsl_sharedmem_find(struct kgsl_process_private *private, unsigned int gpuaddr) +{ + struct kgsl_mem_entry *entry = NULL, *result = NULL; + + BUG_ON(private == NULL); + + gpuaddr &= PAGE_MASK; + + list_for_each_entry(entry, &private->mem_list, list) { + if (entry->memdesc.gpuaddr == gpuaddr) { + result = entry; + break; + } + } + return result; +} + +/*call with private->mem_lock locked */ +struct kgsl_mem_entry * +kgsl_sharedmem_find_region(struct kgsl_process_private *private, + unsigned int gpuaddr, + size_t size) +{ + struct kgsl_mem_entry *entry = NULL, *result = NULL; + + BUG_ON(private == NULL); + + list_for_each_entry(entry, &private->mem_list, list) { + if (gpuaddr >= entry->memdesc.gpuaddr && + ((gpuaddr + size) <= + (entry->memdesc.gpuaddr + entry->memdesc.size))) { + result = entry; + break; + } + } + + return result; +} +EXPORT_SYMBOL(kgsl_sharedmem_find_region); + +uint8_t *kgsl_gpuaddr_to_vaddr(const struct kgsl_memdesc *memdesc, + unsigned int gpuaddr, unsigned int *size) +{ + BUG_ON(memdesc->hostptr == NULL); + + if (memdesc->gpuaddr == 0 || (gpuaddr < memdesc->gpuaddr || + gpuaddr >= memdesc->gpuaddr + memdesc->size)) + return NULL; + + *size = memdesc->size - (gpuaddr - memdesc->gpuaddr); + return memdesc->hostptr + (gpuaddr - memdesc->gpuaddr); +} +EXPORT_SYMBOL(kgsl_gpuaddr_to_vaddr); + +/*call all ioctl sub functions with driver locked*/ +static long kgsl_ioctl_device_getproperty(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + struct kgsl_device_getproperty *param = data; + + switch (param->type) { + case KGSL_PROP_VERSION: + { + struct kgsl_version version; + if (param->sizebytes != sizeof(version)) { + result = -EINVAL; + break; + } + + version.drv_major = KGSL_VERSION_MAJOR; + version.drv_minor = KGSL_VERSION_MINOR; + version.dev_major = dev_priv->device->ver_major; + version.dev_minor = dev_priv->device->ver_minor; + + if (copy_to_user(param->value, &version, sizeof(version))) + result = -EFAULT; + + break; + } + default: + result = dev_priv->device->ftbl->getproperty( + dev_priv->device, param->type, + param->value, param->sizebytes); + } + + + return result; +} + +static long kgsl_ioctl_device_waittimestamp(struct kgsl_device_private + *dev_priv, unsigned int cmd, + void *data) +{ + int result = 0; + struct kgsl_device_waittimestamp *param = data; + + /* Set the active count so that suspend doesn't do the + wrong thing */ + + dev_priv->device->active_cnt++; + + /* Don't wait forever, set a max value for now */ + if (param->timeout == -1) + param->timeout = 10 * MSEC_PER_SEC; + + result = dev_priv->device->ftbl->waittimestamp(dev_priv->device, + param->timestamp, + param->timeout); + + kgsl_memqueue_drain(dev_priv->device); + + /* Fire off any pending suspend operations that are in flight */ + + INIT_COMPLETION(dev_priv->device->suspend_gate); + dev_priv->device->active_cnt--; + complete(&dev_priv->device->suspend_gate); + + return result; +} +static bool check_ibdesc(struct kgsl_device_private *dev_priv, + struct kgsl_ibdesc *ibdesc, unsigned int numibs, + bool parse) +{ + bool result = true; + unsigned int i; + for (i = 0; i < numibs; i++) { + struct kgsl_mem_entry *entry; + spin_lock(&dev_priv->process_priv->mem_lock); + entry = kgsl_sharedmem_find_region(dev_priv->process_priv, + ibdesc[i].gpuaddr, ibdesc[i].sizedwords * sizeof(uint)); + spin_unlock(&dev_priv->process_priv->mem_lock); + if (entry == NULL) { + KGSL_DRV_ERR(dev_priv->device, + "invalid cmd buffer gpuaddr %08x " \ + "sizedwords %d\n", ibdesc[i].gpuaddr, + ibdesc[i].sizedwords); + result = false; + break; + } + + if (parse && !kgsl_cffdump_parse_ibs(dev_priv, &entry->memdesc, + ibdesc[i].gpuaddr, ibdesc[i].sizedwords, true)) { + KGSL_DRV_ERR(dev_priv->device, + "invalid cmd buffer gpuaddr %08x " \ + "sizedwords %d numibs %d/%d\n", + ibdesc[i].gpuaddr, + ibdesc[i].sizedwords, i+1, numibs); + result = false; + break; + } + } + return result; +} + +static long kgsl_ioctl_rb_issueibcmds(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + struct kgsl_ringbuffer_issueibcmds *param = data; + struct kgsl_ibdesc *ibdesc; + struct kgsl_context *context; + +#ifdef CONFIG_MSM_KGSL_DRM + kgsl_gpu_mem_flush(DRM_KGSL_GEM_CACHE_OP_TO_DEV); +#endif + + context = kgsl_find_context(dev_priv, param->drawctxt_id); + if (context == NULL) { + result = -EINVAL; + KGSL_DRV_ERR(dev_priv->device, + "invalid drawctxt drawctxt_id %d\n", + param->drawctxt_id); + goto done; + } + + if (param->flags & KGSL_CONTEXT_SUBMIT_IB_LIST) { + KGSL_DRV_INFO(dev_priv->device, + "Using IB list mode for ib submission, numibs: %d\n", + param->numibs); + if (!param->numibs) { + KGSL_DRV_ERR(dev_priv->device, + "Invalid numibs as parameter: %d\n", + param->numibs); + result = -EINVAL; + goto done; + } + + ibdesc = kzalloc(sizeof(struct kgsl_ibdesc) * param->numibs, + GFP_KERNEL); + if (!ibdesc) { + KGSL_MEM_ERR(dev_priv->device, + "kzalloc(%d) failed\n", + sizeof(struct kgsl_ibdesc) * param->numibs); + result = -ENOMEM; + goto done; + } + + if (copy_from_user(ibdesc, (void *)param->ibdesc_addr, + sizeof(struct kgsl_ibdesc) * param->numibs)) { + result = -EFAULT; + KGSL_DRV_ERR(dev_priv->device, + "copy_from_user failed\n"); + goto free_ibdesc; + } + } else { + KGSL_DRV_INFO(dev_priv->device, + "Using single IB submission mode for ib submission\n"); + /* If user space driver is still using the old mode of + * submitting single ib then we need to support that as well */ + ibdesc = kzalloc(sizeof(struct kgsl_ibdesc), GFP_KERNEL); + if (!ibdesc) { + KGSL_MEM_ERR(dev_priv->device, + "kzalloc(%d) failed\n", + sizeof(struct kgsl_ibdesc)); + result = -ENOMEM; + goto done; + } + ibdesc[0].gpuaddr = param->ibdesc_addr; + ibdesc[0].sizedwords = param->numibs; + param->numibs = 1; + } + + if (!check_ibdesc(dev_priv, ibdesc, param->numibs, true)) { + KGSL_DRV_ERR(dev_priv->device, "bad ibdesc"); + result = -EINVAL; + goto free_ibdesc; + } + + /* Let the pwrscale policy know that a new command buffer + is being issued */ + + kgsl_pwrscale_busy(dev_priv->device); + + result = dev_priv->device->ftbl->issueibcmds(dev_priv, + context, + ibdesc, + param->numibs, + ¶m->timestamp, + param->flags); + + if (result != 0) + goto free_ibdesc; + + /* this is a check to try to detect if a command buffer was freed + * during issueibcmds(). + */ + if (!check_ibdesc(dev_priv, ibdesc, param->numibs, false)) { + KGSL_DRV_ERR(dev_priv->device, "bad ibdesc AFTER issue"); + result = -EINVAL; + goto free_ibdesc; + } + +free_ibdesc: + kfree(ibdesc); +done: + +#ifdef CONFIG_MSM_KGSL_DRM + kgsl_gpu_mem_flush(DRM_KGSL_GEM_CACHE_OP_FROM_DEV); +#endif + + return result; +} + +static long kgsl_ioctl_cmdstream_readtimestamp(struct kgsl_device_private + *dev_priv, unsigned int cmd, + void *data) +{ + struct kgsl_cmdstream_readtimestamp *param = data; + + param->timestamp = + dev_priv->device->ftbl->readtimestamp(dev_priv->device, + param->type); + + return 0; +} + +static long kgsl_ioctl_cmdstream_freememontimestamp(struct kgsl_device_private + *dev_priv, unsigned int cmd, + void *data) +{ + int result = 0; + struct kgsl_cmdstream_freememontimestamp *param = data; + struct kgsl_mem_entry *entry = NULL; + + spin_lock(&dev_priv->process_priv->mem_lock); + entry = kgsl_sharedmem_find(dev_priv->process_priv, param->gpuaddr); + if (entry) + list_del(&entry->list); + spin_unlock(&dev_priv->process_priv->mem_lock); + + if (entry) { + kgsl_memqueue_freememontimestamp(dev_priv->device, entry, + param->timestamp, param->type); + kgsl_memqueue_drain(dev_priv->device); + } else { + KGSL_DRV_ERR(dev_priv->device, + "invalid gpuaddr %08x\n", param->gpuaddr); + result = -EINVAL; + } + + return result; +} + +static long kgsl_ioctl_drawctxt_create(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + struct kgsl_drawctxt_create *param = data; + struct kgsl_context *context = NULL; + + context = kgsl_create_context(dev_priv); + + if (context == NULL) { + result = -ENOMEM; + goto done; + } + + if (dev_priv->device->ftbl->drawctxt_create) + result = dev_priv->device->ftbl->drawctxt_create( + dev_priv->device, dev_priv->process_priv->pagetable, + context, param->flags); + + param->drawctxt_id = context->id; + +done: + if (result && context) + kgsl_destroy_context(dev_priv, context); + + return result; +} + +static long kgsl_ioctl_drawctxt_destroy(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + struct kgsl_drawctxt_destroy *param = data; + struct kgsl_context *context; + + context = kgsl_find_context(dev_priv, param->drawctxt_id); + + if (context == NULL) { + result = -EINVAL; + goto done; + } + + if (dev_priv->device->ftbl->drawctxt_destroy) + dev_priv->device->ftbl->drawctxt_destroy(dev_priv->device, + context); + + kgsl_destroy_context(dev_priv, context); + +done: + return result; +} + +static long kgsl_ioctl_sharedmem_free(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + struct kgsl_sharedmem_free *param = data; + struct kgsl_process_private *private = dev_priv->process_priv; + struct kgsl_mem_entry *entry = NULL; + + spin_lock(&private->mem_lock); + entry = kgsl_sharedmem_find(private, param->gpuaddr); + if (entry) + list_del(&entry->list); + spin_unlock(&private->mem_lock); + + if (entry) { + kgsl_mem_entry_put(entry); + } else { + KGSL_CORE_ERR("invalid gpuaddr %08x\n", param->gpuaddr); + result = -EINVAL; + } + + return result; +} + +static struct vm_area_struct *kgsl_get_vma_from_start_addr(unsigned int addr) +{ + struct vm_area_struct *vma; + int len; + + down_read(¤t->mm->mmap_sem); + vma = find_vma(current->mm, addr); + up_read(¤t->mm->mmap_sem); + if (!vma) { + KGSL_CORE_ERR("find_vma(%x) failed\n", addr); + return NULL; + } + len = vma->vm_end - vma->vm_start; + if (vma->vm_pgoff || !KGSL_IS_PAGE_ALIGNED(len) || + !KGSL_IS_PAGE_ALIGNED(vma->vm_start)) { + KGSL_CORE_ERR("address %x is not aligned\n", addr); + return NULL; + } + if (vma->vm_start != addr) { + KGSL_CORE_ERR("vma address does not match mmap address\n"); + return NULL; + } + return vma; +} + +static long +kgsl_ioctl_sharedmem_from_vmalloc(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0, len = 0; + struct kgsl_process_private *private = dev_priv->process_priv; + struct kgsl_sharedmem_from_vmalloc *param = data; + struct kgsl_mem_entry *entry = NULL; + struct vm_area_struct *vma; + + if (!kgsl_mmu_enabled()) + return -ENODEV; + + /* Make sure all pending freed memory is collected */ + kgsl_memqueue_drain_unlocked(dev_priv->device); + + if (!param->hostptr) { + KGSL_CORE_ERR("invalid hostptr %x\n", param->hostptr); + result = -EINVAL; + goto error; + } + + vma = kgsl_get_vma_from_start_addr(param->hostptr); + if (!vma) { + result = -EINVAL; + goto error; + } + len = vma->vm_end - vma->vm_start; + if (len == 0) { + KGSL_CORE_ERR("Invalid vma region length %d\n", len); + result = -EINVAL; + goto error; + } + + entry = kgsl_mem_entry_create(); + if (entry == NULL) { + result = -ENOMEM; + goto error; + } + + result = kgsl_sharedmem_vmalloc_user(&entry->memdesc, + private->pagetable, len, + param->flags); + if (result != 0) + goto error_free_entry; + + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + result = remap_vmalloc_range(vma, (void *) entry->memdesc.hostptr, 0); + if (result) { + KGSL_CORE_ERR("remap_vmalloc_range failed: %d\n", result); + goto error_free_vmalloc; + } + + param->gpuaddr = entry->memdesc.gpuaddr; + + entry->memtype = KGSL_USER_MEMORY; + + kgsl_mem_entry_attach_process(entry, private); + + /* Process specific statistics */ + KGSL_STATS_ADD(len, private->stats.user, + private->stats.user_max); + + kgsl_check_idle(dev_priv->device); + return 0; + +error_free_vmalloc: + kgsl_sharedmem_free(&entry->memdesc); + +error_free_entry: + kfree(entry); + +error: + kgsl_check_idle(dev_priv->device); + return result; +} + +static inline int _check_region(unsigned long start, unsigned long size, + uint64_t len) +{ + uint64_t end = ((uint64_t) start) + size; + return (end > len); +} + +#ifdef CONFIG_ANDROID_PMEM +static int kgsl_get_phys_file(int fd, unsigned long *start, unsigned long *len, + unsigned long *vstart, struct file **filep) +{ + struct file *fbfile; + int ret = 0; + dev_t rdev; + struct fb_info *info; + + *filep = NULL; + if (!get_pmem_file(fd, start, vstart, len, filep)) + return 0; + + fbfile = fget(fd); + if (fbfile == NULL) { + KGSL_CORE_ERR("fget_light failed\n"); + return -1; + } + + rdev = fbfile->f_dentry->d_inode->i_rdev; + info = MAJOR(rdev) == FB_MAJOR ? registered_fb[MINOR(rdev)] : NULL; + if (info) { + *start = info->fix.smem_start; + *len = info->fix.smem_len; + *vstart = (unsigned long)__va(info->fix.smem_start); + ret = 0; + } else { + KGSL_CORE_ERR("framebuffer minor %d not found\n", + MINOR(rdev)); + ret = -1; + } + + fput(fbfile); + + return ret; +} + +static int kgsl_setup_phys_file(struct kgsl_mem_entry *entry, + struct kgsl_pagetable *pagetable, + unsigned int fd, unsigned int offset, + size_t size) +{ + int ret; + unsigned long phys, virt, len; + struct file *filep; + + ret = kgsl_get_phys_file(fd, &phys, &len, &virt, &filep); + if (ret) + return ret; + + if (offset >= len) { + ret = -EINVAL; + goto err; + } + + if (size == 0) + size = len; + + /* Adjust the size of the region to account for the offset */ + size += offset & ~PAGE_MASK; + + size = ALIGN(size, PAGE_SIZE); + + if (_check_region(offset & PAGE_MASK, size, len)) { + KGSL_CORE_ERR("Offset (%ld) + size (%d) is larger" + "than pmem region length %ld\n", + offset & PAGE_MASK, size, len); + ret = -EINVAL; + goto err; + + } + + entry->file_ptr = filep; + + entry->memdesc.pagetable = pagetable; + entry->memdesc.size = size; + entry->memdesc.physaddr = phys + (offset & PAGE_MASK); + entry->memdesc.hostptr = (void *) (virt + (offset & PAGE_MASK)); + entry->memdesc.ops = &kgsl_contiguous_ops; + + return 0; +err: + put_pmem_file(filep); + return ret; +} +#else +static int kgsl_setup_phys_file(struct kgsl_mem_entry *entry, + struct kgsl_pagetable *pagetable, + unsigned int fd, unsigned int offset, + size_t size) +{ + return -EINVAL; +} +#endif + +static int kgsl_setup_hostptr(struct kgsl_mem_entry *entry, + struct kgsl_pagetable *pagetable, + void *hostptr, unsigned int offset, + size_t size) +{ + struct vm_area_struct *vma; + unsigned int len; + + down_read(¤t->mm->mmap_sem); + vma = find_vma(current->mm, (unsigned int) hostptr); + up_read(¤t->mm->mmap_sem); + + if (!vma) { + KGSL_CORE_ERR("find_vma(%p) failed\n", hostptr); + return -EINVAL; + } + + /* We don't necessarily start at vma->vm_start */ + len = vma->vm_end - (unsigned long) hostptr; + + if (offset >= len) + return -EINVAL; + + if (!KGSL_IS_PAGE_ALIGNED((unsigned long) hostptr) || + !KGSL_IS_PAGE_ALIGNED(len)) { + KGSL_CORE_ERR("user address len(%u)" + "and start(%p) must be page" + "aligned\n", len, hostptr); + return -EINVAL; + } + + if (size == 0) + size = len; + + /* Adjust the size of the region to account for the offset */ + size += offset & ~PAGE_MASK; + + size = ALIGN(size, PAGE_SIZE); + + if (_check_region(offset & PAGE_MASK, size, len)) { + KGSL_CORE_ERR("Offset (%ld) + size (%d) is larger" + "than region length %d\n", + offset & PAGE_MASK, size, len); + return -EINVAL; + } + + entry->memdesc.pagetable = pagetable; + entry->memdesc.size = size; + entry->memdesc.hostptr = hostptr + (offset & PAGE_MASK); + entry->memdesc.ops = &kgsl_userptr_ops; + + return 0; +} + +#ifdef CONFIG_ASHMEM +static int kgsl_setup_ashmem(struct kgsl_mem_entry *entry, + struct kgsl_pagetable *pagetable, + int fd, void *hostptr, size_t size) +{ + int ret; + struct vm_area_struct *vma; + struct file *filep, *vmfile; + unsigned long len; + + vma = kgsl_get_vma_from_start_addr((unsigned long) hostptr); + if (vma == NULL) + return -EINVAL; + + len = vma->vm_end - vma->vm_start; + + if (size == 0) + size = len; + + if (size != len) { + KGSL_CORE_ERR("Invalid size %d for vma region %p\n", + size, hostptr); + return -EINVAL; + } + + ret = get_ashmem_file(fd, &filep, &vmfile, &len); + + if (ret) { + KGSL_CORE_ERR("get_ashmem_file failed\n"); + return ret; + } + + if (vmfile != vma->vm_file) { + KGSL_CORE_ERR("ashmem shmem file does not match vma\n"); + ret = -EINVAL; + goto err; + } + + entry->file_ptr = filep; + + entry->memdesc.pagetable = pagetable; + entry->memdesc.size = ALIGN(size, PAGE_SIZE); + entry->memdesc.hostptr = hostptr; + entry->memdesc.ops = &kgsl_userptr_ops; + + return 0; + +err: + put_ashmem_file(filep); + return ret; +} +#else +static int kgsl_setup_ashmem(struct kgsl_mem_entry *entry, + struct kgsl_pagetable *pagetable, + int fd, void *hostptr, size_t size) +{ + return -EINVAL; +} +#endif + +static long kgsl_ioctl_map_user_mem(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = -EINVAL; + struct kgsl_map_user_mem *param = data; + struct kgsl_mem_entry *entry = NULL; + struct kgsl_process_private *private = dev_priv->process_priv; + + entry = kgsl_mem_entry_create(); + + if (entry == NULL) + return -ENOMEM; + + kgsl_memqueue_drain_unlocked(dev_priv->device); + + switch (param->memtype) { + case KGSL_USER_MEM_TYPE_PMEM: + if (param->fd == 0 || param->len == 0) + break; + + result = kgsl_setup_phys_file(entry, private->pagetable, + param->fd, param->offset, + param->len); + break; + + case KGSL_USER_MEM_TYPE_ADDR: + if (!kgsl_mmu_enabled()) { + KGSL_DRV_ERR(dev_priv->device, + "Cannot map paged memory with the " + "MMU disabled\n"); + break; + } + + if (param->hostptr == 0) + break; + + result = kgsl_setup_hostptr(entry, private->pagetable, + (void *) param->hostptr, + param->offset, param->len); + break; + + case KGSL_USER_MEM_TYPE_ASHMEM: + if (!kgsl_mmu_enabled()) { + KGSL_DRV_ERR(dev_priv->device, + "Cannot map paged memory with the " + "MMU disabled\n"); + break; + } + + if (param->hostptr == 0) + break; + + result = kgsl_setup_ashmem(entry, private->pagetable, + param->fd, (void *) param->hostptr, + param->len); + break; + default: + KGSL_CORE_ERR("Invalid memory type: %x\n", param->memtype); + break; + } + + if (result) + goto error; + + result = kgsl_mmu_map(private->pagetable, + &entry->memdesc, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + + if (result) + goto error_put_file_ptr; + + /* Adjust the returned value for a non 4k aligned offset */ + param->gpuaddr = entry->memdesc.gpuaddr + (param->offset & ~PAGE_MASK); + + entry->memtype = KGSL_MAPPED_MEMORY; + + KGSL_STATS_ADD(param->len, kgsl_driver.stats.mapped, + kgsl_driver.stats.mapped_max); + + /* Statistics */ + KGSL_STATS_ADD(param->len, private->stats.mapped, + private->stats.mapped_max); + + kgsl_mem_entry_attach_process(entry, private); + + kgsl_check_idle(dev_priv->device); + return result; + + error_put_file_ptr: + if (entry->file_ptr) + fput(entry->file_ptr); + +error: + kfree(entry); + kgsl_check_idle(dev_priv->device); + return result; +} + +/*This function flushes a graphics memory allocation from CPU cache + *when caching is enabled with MMU*/ +static long +kgsl_ioctl_sharedmem_flush_cache(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + int result = 0; + struct kgsl_mem_entry *entry; + struct kgsl_sharedmem_free *param = data; + struct kgsl_process_private *private = dev_priv->process_priv; + + spin_lock(&private->mem_lock); + entry = kgsl_sharedmem_find(private, param->gpuaddr); + if (!entry) { + KGSL_CORE_ERR("invalid gpuaddr %08x\n", param->gpuaddr); + result = -EINVAL; + } else { + if (!entry->memdesc.hostptr) + entry->memdesc.hostptr = + kgsl_gpuaddr_to_vaddr(&entry->memdesc, + param->gpuaddr, &entry->memdesc.size); + + if (!entry->memdesc.hostptr) { + KGSL_CORE_ERR("invalid hostptr with gpuaddr %08x\n", + param->gpuaddr); + goto done; + } + + kgsl_cache_range_op(&entry->memdesc, KGSL_CACHE_OP_CLEAN); + + /* Statistics - keep track of how many flushes each process + does */ + private->stats.flushes++; + } + spin_unlock(&private->mem_lock); +done: + return result; +} + +static long +kgsl_ioctl_gpumem_alloc(struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data) +{ + struct kgsl_process_private *private = dev_priv->process_priv; + struct kgsl_gpumem_alloc *param = data; + struct kgsl_mem_entry *entry; + int result; + + entry = kgsl_mem_entry_create(); + if (entry == NULL) + return -ENOMEM; + + /* Make sure all pending freed memory is collected */ + kgsl_memqueue_drain_unlocked(dev_priv->device); + + result = kgsl_allocate_user(&entry->memdesc, private->pagetable, + param->size, param->flags); + + if (result == 0) { + entry->memtype = KGSL_USER_MEMORY; + kgsl_mem_entry_attach_process(entry, private); + param->gpuaddr = entry->memdesc.gpuaddr; + + KGSL_STATS_ADD(entry->memdesc.size, private->stats.user, + private->stats.user_max); + } else + kfree(entry); + + kgsl_check_idle(dev_priv->device); + return result; +} + +typedef long (*kgsl_ioctl_func_t)(struct kgsl_device_private *, + unsigned int, void *); + +#define KGSL_IOCTL_FUNC(_cmd, _func, _lock) \ + [_IOC_NR(_cmd)] = { .cmd = _cmd, .func = _func, .lock = _lock } + +static const struct { + unsigned int cmd; + kgsl_ioctl_func_t func; + int lock; +} kgsl_ioctl_funcs[] = { + KGSL_IOCTL_FUNC(IOCTL_KGSL_DEVICE_GETPROPERTY, + kgsl_ioctl_device_getproperty, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_DEVICE_WAITTIMESTAMP, + kgsl_ioctl_device_waittimestamp, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_RINGBUFFER_ISSUEIBCMDS, + kgsl_ioctl_rb_issueibcmds, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_READTIMESTAMP, + kgsl_ioctl_cmdstream_readtimestamp, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP, + kgsl_ioctl_cmdstream_freememontimestamp, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_DRAWCTXT_CREATE, + kgsl_ioctl_drawctxt_create, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_DRAWCTXT_DESTROY, + kgsl_ioctl_drawctxt_destroy, 1), + KGSL_IOCTL_FUNC(IOCTL_KGSL_MAP_USER_MEM, + kgsl_ioctl_map_user_mem, 0), + KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FROM_PMEM, + kgsl_ioctl_map_user_mem, 0), + KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FREE, + kgsl_ioctl_sharedmem_free, 0), + KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FROM_VMALLOC, + kgsl_ioctl_sharedmem_from_vmalloc, 0), + KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FLUSH_CACHE, + kgsl_ioctl_sharedmem_flush_cache, 0), + KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_ALLOC, + kgsl_ioctl_gpumem_alloc, 0), +}; + +static long kgsl_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + struct kgsl_device_private *dev_priv = filep->private_data; + unsigned int nr = _IOC_NR(cmd); + kgsl_ioctl_func_t func; + int lock, ret; + char ustack[64]; + void *uptr = NULL; + + BUG_ON(dev_priv == NULL); + + /* Workaround for an previously incorrectly defined ioctl code. + This helps ensure binary compatability */ + + if (cmd == IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP_OLD) + cmd = IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP; + + if (cmd & (IOC_IN | IOC_OUT)) { + if (_IOC_SIZE(cmd) < sizeof(ustack)) + uptr = ustack; + else { + uptr = kzalloc(_IOC_SIZE(cmd), GFP_KERNEL); + if (uptr == NULL) { + KGSL_MEM_ERR(dev_priv->device, + "kzalloc(%d) failed\n", _IOC_SIZE(cmd)); + ret = -ENOMEM; + goto done; + } + } + + if (cmd & IOC_IN) { + if (copy_from_user(uptr, (void __user *) arg, + _IOC_SIZE(cmd))) { + ret = -EFAULT; + goto done; + } + } else + memset(uptr, 0, _IOC_SIZE(cmd)); + } + + if (nr < ARRAY_SIZE(kgsl_ioctl_funcs) && + kgsl_ioctl_funcs[nr].func != NULL) { + func = kgsl_ioctl_funcs[nr].func; + lock = kgsl_ioctl_funcs[nr].lock; + } else { + func = dev_priv->device->ftbl->ioctl; + if (!func) { + KGSL_DRV_INFO(dev_priv->device, + "invalid ioctl code %08x\n", cmd); + ret = -EINVAL; + goto done; + } + lock = 1; + } + + if (lock) { + mutex_lock(&dev_priv->device->mutex); + kgsl_check_suspended(dev_priv->device); + } + + ret = func(dev_priv, cmd, uptr); + + if (lock) { + kgsl_check_idle_locked(dev_priv->device); + mutex_unlock(&dev_priv->device->mutex); + } + + if (ret == 0 && (cmd & IOC_OUT)) { + if (copy_to_user((void __user *) arg, uptr, _IOC_SIZE(cmd))) + ret = -EFAULT; + } + +done: + if (_IOC_SIZE(cmd) >= sizeof(ustack)) + kfree(uptr); + + return ret; +} + +static int +kgsl_mmap_memstore(struct kgsl_device *device, struct vm_area_struct *vma) +{ + struct kgsl_memdesc *memdesc = &device->memstore; + int result; + unsigned int vma_size = vma->vm_end - vma->vm_start; + + /* The memstore can only be mapped as read only */ + + if (vma->vm_flags & VM_WRITE) + return -EPERM; + + if (memdesc->size != vma_size) { + KGSL_MEM_ERR(device, "memstore bad size: %d should be %d\n", + vma_size, memdesc->size); + return -EINVAL; + } + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + result = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma_size, vma->vm_page_prot); + if (result != 0) + KGSL_MEM_ERR(device, "remap_pfn_range failed: %d\n", + result); + + return result; +} + +static int +kgsl_gpumem_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct kgsl_mem_entry *entry = vma->vm_private_data; + + if (!entry->memdesc.ops->vmfault) + return VM_FAULT_SIGBUS; + + return entry->memdesc.ops->vmfault(&entry->memdesc, vma, vmf); +} + +static void +kgsl_gpumem_vm_close(struct vm_area_struct *vma) +{ + struct kgsl_mem_entry *entry = vma->vm_private_data; + kgsl_mem_entry_put(entry); +} + +static struct vm_operations_struct kgsl_gpumem_vm_ops = { + .fault = kgsl_gpumem_vm_fault, + .close = kgsl_gpumem_vm_close, +}; + +static int kgsl_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long vma_offset = vma->vm_pgoff << PAGE_SHIFT; + struct inode *inodep = file->f_path.dentry->d_inode; + struct kgsl_device_private *dev_priv = file->private_data; + struct kgsl_process_private *private = dev_priv->process_priv; + struct kgsl_mem_entry *entry; + struct kgsl_device *device; + + device = kgsl_driver.devp[iminor(inodep)]; + BUG_ON(device == NULL); + + /* Handle leagacy behavior for memstore */ + + if (vma_offset == device->memstore.physaddr) + return kgsl_mmap_memstore(device, vma); + + /* Find a chunk of GPU memory */ + + spin_lock(&private->mem_lock); + list_for_each_entry(entry, &private->mem_list, list) { + if (vma_offset == entry->memdesc.gpuaddr) { + kgsl_mem_entry_get(entry); + break; + } + } + spin_unlock(&private->mem_lock); + + if (entry == NULL) + return -EINVAL; + + if (!entry->memdesc.ops->vmflags || !entry->memdesc.ops->vmfault) + return -EINVAL; + + vma->vm_flags |= entry->memdesc.ops->vmflags(&entry->memdesc); + + vma->vm_private_data = entry; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &kgsl_gpumem_vm_ops; + vma->vm_file = file; + + return 0; +} + +static const struct file_operations kgsl_fops = { + .owner = THIS_MODULE, + .release = kgsl_release, + .open = kgsl_open, + .mmap = kgsl_mmap, + .unlocked_ioctl = kgsl_ioctl, +}; + +struct kgsl_driver kgsl_driver = { + .process_mutex = __MUTEX_INITIALIZER(kgsl_driver.process_mutex), + .ptlock = __SPIN_LOCK_UNLOCKED(kgsl_driver.ptlock), + .devlock = __MUTEX_INITIALIZER(kgsl_driver.devlock), +}; +EXPORT_SYMBOL(kgsl_driver); + +void kgsl_unregister_device(struct kgsl_device *device) +{ + int minor; + + mutex_lock(&kgsl_driver.devlock); + for (minor = 0; minor < KGSL_DEVICE_MAX; minor++) { + if (device == kgsl_driver.devp[minor]) + break; + } + + mutex_unlock(&kgsl_driver.devlock); + + if (minor == KGSL_DEVICE_MAX) + return; + + kgsl_cffdump_close(device->id); + kgsl_pwrctrl_uninit_sysfs(device); + + wake_lock_destroy(&device->idle_wakelock); + pm_qos_remove_request(&device->pm_qos_req_dma); + + idr_destroy(&device->context_idr); + + if (device->memstore.hostptr) + kgsl_sharedmem_free(&device->memstore); + + kgsl_mmu_close(device); + + if (device->work_queue) { + destroy_workqueue(device->work_queue); + device->work_queue = NULL; + } + + device_destroy(kgsl_driver.class, + MKDEV(MAJOR(kgsl_driver.major), minor)); + + mutex_lock(&kgsl_driver.devlock); + kgsl_driver.devp[minor] = NULL; + mutex_unlock(&kgsl_driver.devlock); +} +EXPORT_SYMBOL(kgsl_unregister_device); + +int +kgsl_register_device(struct kgsl_device *device) +{ + int minor, ret; + dev_t dev; + + /* Find a minor for the device */ + + mutex_lock(&kgsl_driver.devlock); + for (minor = 0; minor < KGSL_DEVICE_MAX; minor++) { + if (kgsl_driver.devp[minor] == NULL) { + kgsl_driver.devp[minor] = device; + break; + } + } + + mutex_unlock(&kgsl_driver.devlock); + + if (minor == KGSL_DEVICE_MAX) { + KGSL_CORE_ERR("minor devices exhausted\n"); + return -ENODEV; + } + + /* Create the device */ + dev = MKDEV(MAJOR(kgsl_driver.major), minor); + device->dev = device_create(kgsl_driver.class, + device->parentdev, + dev, device, + device->name); + + if (IS_ERR(device->dev)) { + ret = PTR_ERR(device->dev); + KGSL_CORE_ERR("device_create(%s): %d\n", device->name, ret); + goto err_devlist; + } + + dev_set_drvdata(device->parentdev, device); + + /* Generic device initialization */ + init_waitqueue_head(&device->wait_queue); + + kgsl_cffdump_open(device->id); + + init_completion(&device->hwaccess_gate); + init_completion(&device->suspend_gate); + + ATOMIC_INIT_NOTIFIER_HEAD(&device->ts_notifier_list); + + setup_timer(&device->idle_timer, kgsl_timer, (unsigned long) device); + ret = kgsl_create_device_workqueue(device); + if (ret) + goto err_devlist; + + INIT_WORK(&device->idle_check_ws, kgsl_idle_check); + + INIT_LIST_HEAD(&device->memqueue); + + ret = kgsl_mmu_init(device); + if (ret != 0) + goto err_dest_work_q; + + ret = kgsl_allocate_contiguous(&device->memstore, + sizeof(struct kgsl_devmemstore)); + + if (ret != 0) + goto err_close_mmu; + + kgsl_sharedmem_set(&device->memstore, 0, 0, device->memstore.size); + + wake_lock_init(&device->idle_wakelock, WAKE_LOCK_IDLE, device->name); + pm_qos_add_request(&device->pm_qos_req_dma, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + idr_init(&device->context_idr); + + /* sysfs and debugfs initalization - failure here is non fatal */ + + /* Initialize logging */ + kgsl_device_debugfs_init(device); + + /* Initialize common sysfs entries */ + kgsl_pwrctrl_init_sysfs(device); + + return 0; + +err_close_mmu: + kgsl_mmu_close(device); +err_dest_work_q: + destroy_workqueue(device->work_queue); + device->work_queue = NULL; +err_devlist: + mutex_lock(&kgsl_driver.devlock); + kgsl_driver.devp[minor] = NULL; + mutex_unlock(&kgsl_driver.devlock); + + return ret; +} +EXPORT_SYMBOL(kgsl_register_device); + +int kgsl_device_platform_probe(struct kgsl_device *device, + irqreturn_t (*dev_isr) (int, void*)) +{ + int status = -EINVAL; + struct kgsl_memregion *regspace = NULL; + struct resource *res; + struct platform_device *pdev = + container_of(device->parentdev, struct platform_device, dev); + + pm_runtime_enable(device->parentdev); + + status = kgsl_pwrctrl_init(device); + if (status) + goto error; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + device->iomemname); + if (res == NULL) { + KGSL_DRV_ERR(device, "platform_get_resource_byname failed\n"); + status = -EINVAL; + goto error_pwrctrl_close; + } + if (res->start == 0 || resource_size(res) == 0) { + KGSL_DRV_ERR(device, "dev %d invalid regspace\n", device->id); + status = -EINVAL; + goto error_pwrctrl_close; + } + + regspace = &device->regspace; + regspace->mmio_phys_base = res->start; + regspace->sizebytes = resource_size(res); + + if (!request_mem_region(regspace->mmio_phys_base, + regspace->sizebytes, device->name)) { + KGSL_DRV_ERR(device, "request_mem_region failed\n"); + status = -ENODEV; + goto error_pwrctrl_close; + } + + regspace->mmio_virt_base = ioremap(regspace->mmio_phys_base, + regspace->sizebytes); + + if (regspace->mmio_virt_base == NULL) { + KGSL_DRV_ERR(device, "ioremap failed\n"); + status = -ENODEV; + goto error_release_mem; + } + + status = request_irq(device->pwrctrl.interrupt_num, dev_isr, + IRQF_TRIGGER_HIGH, device->name, device); + if (status) { + KGSL_DRV_ERR(device, "request_irq(%d) failed: %d\n", + device->pwrctrl.interrupt_num, status); + goto error_iounmap; + } + device->pwrctrl.have_irq = 1; + disable_irq(device->pwrctrl.interrupt_num); + + KGSL_DRV_INFO(device, + "dev_id %d regs phys 0x%08x size 0x%08x virt %p\n", + device->id, regspace->mmio_phys_base, + regspace->sizebytes, regspace->mmio_virt_base); + + + status = kgsl_register_device(device); + if (!status) + return status; + + free_irq(device->pwrctrl.interrupt_num, NULL); + device->pwrctrl.have_irq = 0; +error_iounmap: + iounmap(regspace->mmio_virt_base); + regspace->mmio_virt_base = NULL; +error_release_mem: + release_mem_region(regspace->mmio_phys_base, regspace->sizebytes); +error_pwrctrl_close: + kgsl_pwrctrl_close(device); +error: + return status; +} +EXPORT_SYMBOL(kgsl_device_platform_probe); + +void kgsl_device_platform_remove(struct kgsl_device *device) +{ + struct kgsl_memregion *regspace = &device->regspace; + + kgsl_unregister_device(device); + + if (regspace->mmio_virt_base != NULL) { + iounmap(regspace->mmio_virt_base); + regspace->mmio_virt_base = NULL; + release_mem_region(regspace->mmio_phys_base, + regspace->sizebytes); + } + kgsl_pwrctrl_close(device); + + pm_runtime_disable(device->parentdev); +} +EXPORT_SYMBOL(kgsl_device_platform_remove); + +static int __devinit +kgsl_ptdata_init(void) +{ + INIT_LIST_HEAD(&kgsl_driver.pagetable_list); + + return kgsl_ptpool_init(&kgsl_driver.ptpool, KGSL_PAGETABLE_SIZE, + kgsl_pagetable_count); +} + +static void kgsl_core_exit(void) +{ + unregister_chrdev_region(kgsl_driver.major, KGSL_DEVICE_MAX); + + kgsl_ptpool_destroy(&kgsl_driver.ptpool); + + device_unregister(&kgsl_driver.virtdev); + + if (kgsl_driver.class) { + class_destroy(kgsl_driver.class); + kgsl_driver.class = NULL; + } + + kgsl_drm_exit(); + kgsl_cffdump_destroy(); +} + +static int __init kgsl_core_init(void) +{ + int result = 0; + + /* alloc major and minor device numbers */ + result = alloc_chrdev_region(&kgsl_driver.major, 0, KGSL_DEVICE_MAX, + KGSL_NAME); + if (result < 0) { + KGSL_CORE_ERR("alloc_chrdev_region failed err = %d\n", result); + goto err; + } + + cdev_init(&kgsl_driver.cdev, &kgsl_fops); + kgsl_driver.cdev.owner = THIS_MODULE; + kgsl_driver.cdev.ops = &kgsl_fops; + result = cdev_add(&kgsl_driver.cdev, MKDEV(MAJOR(kgsl_driver.major), 0), + KGSL_DEVICE_MAX); + + if (result) { + KGSL_CORE_ERR("kgsl: cdev_add() failed, dev_num= %d," + " result= %d\n", kgsl_driver.major, result); + goto err; + } + + kgsl_driver.class = class_create(THIS_MODULE, KGSL_NAME); + + if (IS_ERR(kgsl_driver.class)) { + result = PTR_ERR(kgsl_driver.class); + KGSL_CORE_ERR("failed to create class %s", KGSL_NAME); + goto err; + } + + /* Make a virtual device for managing core related things + in sysfs */ + kgsl_driver.virtdev.class = kgsl_driver.class; + dev_set_name(&kgsl_driver.virtdev, "kgsl"); + result = device_register(&kgsl_driver.virtdev); + if (result) { + KGSL_CORE_ERR("driver_register failed\n"); + goto err; + } + + /* Make kobjects in the virtual device for storing statistics */ + + kgsl_driver.ptkobj = + kobject_create_and_add("pagetables", + &kgsl_driver.virtdev.kobj); + + kgsl_driver.prockobj = + kobject_create_and_add("proc", + &kgsl_driver.virtdev.kobj); + + kgsl_core_debugfs_init(); + + kgsl_sharedmem_init_sysfs(); + kgsl_cffdump_init(); + + /* Generic device initialization */ + INIT_LIST_HEAD(&kgsl_driver.process_list); + + result = kgsl_ptdata_init(); + if (result) + goto err; + + result = kgsl_drm_init(NULL); + + if (result) + goto err; + + return 0; + +err: + kgsl_core_exit(); + return result; +} + +module_init(kgsl_core_init); +module_exit(kgsl_core_exit); + +MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); +MODULE_DESCRIPTION("MSM GPU driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/msm/kgsl.h b/drivers/gpu/msm/kgsl.h new file mode 100644 index 00000000000..cd11bd9db16 --- /dev/null +++ b/drivers/gpu/msm/kgsl.h @@ -0,0 +1,220 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __KGSL_H +#define __KGSL_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#define KGSL_NAME "kgsl" + +/* Flags to control whether to flush or invalidate a cached memory range */ +#define KGSL_CACHE_INV 0x00000000 +#define KGSL_CACHE_CLEAN 0x00000001 +#define KGSL_CACHE_FLUSH 0x00000002 + +#define KGSL_CACHE_USER_ADDR 0x00000010 +#define KGSL_CACHE_VMALLOC_ADDR 0x00000020 + +/*cache coherency ops */ +#define DRM_KGSL_GEM_CACHE_OP_TO_DEV 0x0001 +#define DRM_KGSL_GEM_CACHE_OP_FROM_DEV 0x0002 + +/* The size of each entry in a page table */ +#define KGSL_PAGETABLE_ENTRY_SIZE 4 + +/* Pagetable Virtual Address base */ +#define KGSL_PAGETABLE_BASE 0x66000000 + +/* Extra accounting entries needed in the pagetable */ +#define KGSL_PT_EXTRA_ENTRIES 16 + +#define KGSL_PAGETABLE_ENTRIES(_sz) (((_sz) >> PAGE_SHIFT) + \ + KGSL_PT_EXTRA_ENTRIES) + +#ifdef CONFIG_MSM_KGSL_MMU +#define KGSL_PAGETABLE_SIZE \ +ALIGN(KGSL_PAGETABLE_ENTRIES(CONFIG_MSM_KGSL_PAGE_TABLE_SIZE) * \ +KGSL_PAGETABLE_ENTRY_SIZE, PAGE_SIZE) +#else +#define KGSL_PAGETABLE_SIZE 0 +#endif + +#ifdef CONFIG_KGSL_PER_PROCESS_PAGE_TABLE +#define KGSL_PAGETABLE_COUNT (CONFIG_MSM_KGSL_PAGE_TABLE_COUNT) +#else +#define KGSL_PAGETABLE_COUNT 1 +#endif + +/* Casting using container_of() for structures that kgsl owns. */ +#define KGSL_CONTAINER_OF(ptr, type, member) \ + container_of(ptr, type, member) + +/* A macro for memory statistics - add the new size to the stat and if + the statisic is greater then _max, set _max +*/ + +#define KGSL_STATS_ADD(_size, _stat, _max) \ + do { _stat += (_size); if (_stat > _max) _max = _stat; } while (0) + +struct kgsl_device; + +struct kgsl_ptpool { + size_t ptsize; + struct mutex lock; + struct list_head list; + int entries; + int static_entries; + int chunks; +}; + +struct kgsl_driver { + struct cdev cdev; + dev_t major; + struct class *class; + /* Virtual device for managing the core */ + struct device virtdev; + /* Kobjects for storing pagetable and process statistics */ + struct kobject *ptkobj; + struct kobject *prockobj; + struct kgsl_device *devp[KGSL_DEVICE_MAX]; + + uint32_t flags_debug; + + /* Global lilst of open processes */ + struct list_head process_list; + /* Global list of pagetables */ + struct list_head pagetable_list; + /* Spinlock for accessing the pagetable list */ + spinlock_t ptlock; + /* Mutex for accessing the process list */ + struct mutex process_mutex; + + /* Mutex for protecting the device list */ + struct mutex devlock; + + struct kgsl_ptpool ptpool; + + struct { + unsigned int vmalloc; + unsigned int vmalloc_max; + unsigned int coherent; + unsigned int coherent_max; + unsigned int mapped; + unsigned int mapped_max; + unsigned int histogram[16]; + } stats; +}; + +extern struct kgsl_driver kgsl_driver; + +#define KGSL_USER_MEMORY 1 +#define KGSL_MAPPED_MEMORY 2 + +struct kgsl_pagetable; +struct kgsl_memdesc_ops; + +/* shared memory allocation */ +struct kgsl_memdesc { + struct kgsl_pagetable *pagetable; + void *hostptr; + unsigned int gpuaddr; + unsigned int physaddr; + unsigned int size; + unsigned int priv; + struct kgsl_memdesc_ops *ops; +}; + +struct kgsl_mem_entry { + struct kref refcount; + struct kgsl_memdesc memdesc; + int memtype; + struct file *file_ptr; + struct list_head list; + uint32_t free_timestamp; + /* back pointer to private structure under whose context this + * allocation is made */ + struct kgsl_process_private *priv; +}; + +#ifdef CONFIG_MSM_KGSL_MMU_PAGE_FAULT +#define MMU_CONFIG 2 +#else +#define MMU_CONFIG 1 +#endif + +void kgsl_mem_entry_destroy(struct kref *kref); +uint8_t *kgsl_gpuaddr_to_vaddr(const struct kgsl_memdesc *memdesc, + unsigned int gpuaddr, unsigned int *size); +struct kgsl_mem_entry *kgsl_sharedmem_find_region( + struct kgsl_process_private *private, unsigned int gpuaddr, + size_t size); + +extern const struct dev_pm_ops kgsl_pm_ops; + +struct early_suspend; +int kgsl_suspend_driver(struct platform_device *pdev, pm_message_t state); +int kgsl_resume_driver(struct platform_device *pdev); +void kgsl_early_suspend_driver(struct early_suspend *h); +void kgsl_late_resume_driver(struct early_suspend *h); + +#ifdef CONFIG_MSM_KGSL_DRM +extern int kgsl_drm_init(struct platform_device *dev); +extern void kgsl_drm_exit(void); +extern void kgsl_gpu_mem_flush(int op); +#else +static inline int kgsl_drm_init(struct platform_device *dev) +{ + return 0; +} + +static inline void kgsl_drm_exit(void) +{ +} +#endif + +static inline int kgsl_gpuaddr_in_memdesc(const struct kgsl_memdesc *memdesc, + unsigned int gpuaddr) +{ + if (gpuaddr >= memdesc->gpuaddr && (gpuaddr + sizeof(unsigned int)) <= + (memdesc->gpuaddr + memdesc->size)) { + return 1; + } + return 0; +} + +static inline bool timestamp_cmp(unsigned int new, unsigned int old) +{ + int ts_diff = new - old; + return (ts_diff >= 0) || (ts_diff < -20000); +} + +static inline void +kgsl_mem_entry_get(struct kgsl_mem_entry *entry) +{ + kref_get(&entry->refcount); +} + +static inline void +kgsl_mem_entry_put(struct kgsl_mem_entry *entry) +{ + kref_put(&entry->refcount, kgsl_mem_entry_destroy); +} + +#endif /* __KGSL_H */ diff --git a/drivers/gpu/msm/kgsl_cffdump.c b/drivers/gpu/msm/kgsl_cffdump.c new file mode 100644 index 00000000000..4349316bc5e --- /dev/null +++ b/drivers/gpu/msm/kgsl_cffdump.c @@ -0,0 +1,711 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* #define DEBUG */ +#define ALIGN_CPU + +#include +#include +#include +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_cffdump.h" +#include "kgsl_debugfs.h" + +static struct rchan *chan; +static struct dentry *dir; +static int suspended; +static size_t dropped; +static size_t subbuf_size = 256*1024; +static size_t n_subbufs = 64; + +/* forward declarations */ +static void destroy_channel(void); +static struct rchan *create_channel(unsigned subbuf_size, unsigned n_subbufs); + +static spinlock_t cffdump_lock; +static ulong serial_nr; +static ulong total_bytes; +static ulong total_syncmem; +static long last_sec; + +#define MEMBUF_SIZE 64 + +#define CFF_OP_WRITE_REG 0x00000002 +struct cff_op_write_reg { + unsigned char op; + uint addr; + uint value; +} __attribute__((packed)); + +#define CFF_OP_POLL_REG 0x00000004 +struct cff_op_poll_reg { + unsigned char op; + uint addr; + uint value; + uint mask; +} __attribute__((packed)); + +#define CFF_OP_WAIT_IRQ 0x00000005 +struct cff_op_wait_irq { + unsigned char op; +} __attribute__((packed)); + +#define CFF_OP_VERIFY_MEM_FILE 0x00000007 +#define CFF_OP_RMW 0x0000000a + +#define CFF_OP_WRITE_MEM 0x0000000b +struct cff_op_write_mem { + unsigned char op; + uint addr; + uint value; +} __attribute__((packed)); + +#define CFF_OP_WRITE_MEMBUF 0x0000000c +struct cff_op_write_membuf { + unsigned char op; + uint addr; + ushort count; + uint buffer[MEMBUF_SIZE]; +} __attribute__((packed)); + +#define CFF_OP_EOF 0xffffffff +struct cff_op_eof { + unsigned char op; +} __attribute__((packed)); + + +static void b64_encodeblock(unsigned char in[3], unsigned char out[4], int len) +{ + static const char tob64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmno" + "pqrstuvwxyz0123456789+/"; + + out[0] = tob64[in[0] >> 2]; + out[1] = tob64[((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4)]; + out[2] = (unsigned char) (len > 1 ? tob64[((in[1] & 0x0f) << 2) + | ((in[2] & 0xc0) >> 6)] : '='); + out[3] = (unsigned char) (len > 2 ? tob64[in[2] & 0x3f] : '='); +} + +static void b64_encode(const unsigned char *in_buf, int in_size, + unsigned char *out_buf, int out_bufsize, int *out_size) +{ + unsigned char in[3], out[4]; + int i, len; + + *out_size = 0; + while (in_size > 0) { + len = 0; + for (i = 0; i < 3; ++i) { + if (in_size-- > 0) { + in[i] = *in_buf++; + ++len; + } else + in[i] = 0; + } + if (len) { + b64_encodeblock(in, out, len); + if (out_bufsize < 4) { + pr_warn("kgsl: cffdump: %s: out of buffer\n", + __func__); + return; + } + for (i = 0; i < 4; ++i) + *out_buf++ = out[i]; + *out_size += 4; + out_bufsize -= 4; + } + } +} + +#define KLOG_TMPBUF_SIZE (1024) +static void klog_printk(const char *fmt, ...) +{ + /* per-cpu klog formatting temporary buffer */ + static char klog_buf[NR_CPUS][KLOG_TMPBUF_SIZE]; + + va_list args; + int len; + char *cbuf; + unsigned long flags; + + local_irq_save(flags); + cbuf = klog_buf[smp_processor_id()]; + va_start(args, fmt); + len = vsnprintf(cbuf, KLOG_TMPBUF_SIZE, fmt, args); + total_bytes += len; + va_end(args); + relay_write(chan, cbuf, len); + local_irq_restore(flags); +} + +static struct cff_op_write_membuf cff_op_write_membuf; +static void cffdump_membuf(int id, unsigned char *out_buf, int out_bufsize) +{ + void *data; + int len, out_size; + struct cff_op_write_mem cff_op_write_mem; + + uint addr = cff_op_write_membuf.addr + - sizeof(uint)*cff_op_write_membuf.count; + + if (!cff_op_write_membuf.count) { + pr_warn("kgsl: cffdump: membuf: count == 0, skipping"); + return; + } + + if (cff_op_write_membuf.count != 1) { + cff_op_write_membuf.op = CFF_OP_WRITE_MEMBUF; + cff_op_write_membuf.addr = addr; + len = sizeof(cff_op_write_membuf) - + sizeof(uint)*(MEMBUF_SIZE - cff_op_write_membuf.count); + data = &cff_op_write_membuf; + } else { + cff_op_write_mem.op = CFF_OP_WRITE_MEM; + cff_op_write_mem.addr = addr; + cff_op_write_mem.value = cff_op_write_membuf.buffer[0]; + data = &cff_op_write_mem; + len = sizeof(cff_op_write_mem); + } + b64_encode(data, len, out_buf, out_bufsize, &out_size); + out_buf[out_size] = 0; + klog_printk("%ld:%d;%s\n", ++serial_nr, id, out_buf); + cff_op_write_membuf.count = 0; + cff_op_write_membuf.addr = 0; +} + +static void cffdump_printline(int id, uint opcode, uint op1, uint op2, + uint op3) +{ + struct cff_op_write_reg cff_op_write_reg; + struct cff_op_poll_reg cff_op_poll_reg; + struct cff_op_wait_irq cff_op_wait_irq; + struct cff_op_eof cff_op_eof; + unsigned char out_buf[sizeof(cff_op_write_membuf)/3*4 + 16]; + void *data; + int len = 0, out_size; + long cur_secs; + + spin_lock(&cffdump_lock); + if (opcode == CFF_OP_WRITE_MEM) { + if (op1 < 0x40000000 || op1 >= 0x60000000) + KGSL_CORE_ERR("addr out-of-range: op1=%08x", op1); + if ((cff_op_write_membuf.addr != op1 && + cff_op_write_membuf.count) + || (cff_op_write_membuf.count == MEMBUF_SIZE)) + cffdump_membuf(id, out_buf, sizeof(out_buf)); + + cff_op_write_membuf.buffer[cff_op_write_membuf.count++] = op2; + cff_op_write_membuf.addr = op1 + sizeof(uint); + spin_unlock(&cffdump_lock); + return; + } else if (cff_op_write_membuf.count) + cffdump_membuf(id, out_buf, sizeof(out_buf)); + spin_unlock(&cffdump_lock); + + switch (opcode) { + case CFF_OP_WRITE_REG: + cff_op_write_reg.op = opcode; + cff_op_write_reg.addr = op1; + cff_op_write_reg.value = op2; + data = &cff_op_write_reg; + len = sizeof(cff_op_write_reg); + break; + + case CFF_OP_POLL_REG: + cff_op_poll_reg.op = opcode; + cff_op_poll_reg.addr = op1; + cff_op_poll_reg.value = op2; + cff_op_poll_reg.mask = op3; + data = &cff_op_poll_reg; + len = sizeof(cff_op_poll_reg); + break; + + case CFF_OP_WAIT_IRQ: + cff_op_wait_irq.op = opcode; + data = &cff_op_wait_irq; + len = sizeof(cff_op_wait_irq); + break; + + case CFF_OP_EOF: + cff_op_eof.op = opcode; + data = &cff_op_eof; + len = sizeof(cff_op_eof); + break; + } + + if (len) { + b64_encode(data, len, out_buf, sizeof(out_buf), &out_size); + out_buf[out_size] = 0; + klog_printk("%ld:%d;%s\n", ++serial_nr, id, out_buf); + } else + pr_warn("kgsl: cffdump: unhandled opcode: %d\n", opcode); + + cur_secs = get_seconds(); + if ((cur_secs - last_sec) > 10 || (last_sec - cur_secs) > 10) { + pr_info("kgsl: cffdump: total [bytes:%lu kB, syncmem:%lu kB], " + "seq#: %lu\n", total_bytes/1024, total_syncmem/1024, + serial_nr); + last_sec = cur_secs; + } +} + +void kgsl_cffdump_init() +{ + struct dentry *debugfs_dir = kgsl_get_debugfs_dir(); + +#ifdef ALIGN_CPU + cpumask_t mask; + + cpumask_clear(&mask); + cpumask_set_cpu(1, &mask); + sched_setaffinity(0, &mask); +#endif + if (!debugfs_dir || IS_ERR(debugfs_dir)) { + KGSL_CORE_ERR("Debugfs directory is bad\n"); + return; + } + + kgsl_cff_dump_enable = 1; + + spin_lock_init(&cffdump_lock); + + dir = debugfs_create_dir("cff", debugfs_dir); + if (!dir) { + KGSL_CORE_ERR("debugfs_create_dir failed\n"); + return; + } + + chan = create_channel(subbuf_size, n_subbufs); +} + +void kgsl_cffdump_destroy() +{ + if (chan) + relay_flush(chan); + destroy_channel(); + if (dir) + debugfs_remove(dir); +} + +void kgsl_cffdump_open(enum kgsl_deviceid device_id) +{ +} + +void kgsl_cffdump_close(enum kgsl_deviceid device_id) +{ + cffdump_printline(device_id, CFF_OP_EOF, 0, 0, 0); +} + +void kgsl_cffdump_syncmem(struct kgsl_device_private *dev_priv, + const struct kgsl_memdesc *memdesc, uint gpuaddr, uint sizebytes, + bool clean_cache) +{ + const void *src; + uint host_size; + uint physaddr; + + if (!kgsl_cff_dump_enable) + return; + + total_syncmem += sizebytes; + + if (memdesc == NULL) { + struct kgsl_mem_entry *entry; + spin_lock(&dev_priv->process_priv->mem_lock); + entry = kgsl_sharedmem_find_region(dev_priv->process_priv, + gpuaddr, sizebytes); + spin_unlock(&dev_priv->process_priv->mem_lock); + if (entry == NULL) { + KGSL_CORE_ERR("did not find mapping " + "for gpuaddr: 0x%08x\n", gpuaddr); + return; + } + memdesc = &entry->memdesc; + } + BUG_ON(memdesc->gpuaddr == 0); + BUG_ON(gpuaddr == 0); + physaddr = kgsl_get_realaddr(memdesc) + (gpuaddr - memdesc->gpuaddr); + + src = kgsl_gpuaddr_to_vaddr(memdesc, gpuaddr, &host_size); + if (src == NULL || host_size < sizebytes) { + KGSL_CORE_ERR(("did not find mapping for " + "gpuaddr: 0x%08x, m->host: 0x%p, phys: 0x%08x\n", + gpuaddr, memdesc->hostptr, memdesc->physaddr); + return; + } + + if (clean_cache) { + /* Ensure that this memory region is not read from the + * cache but fetched fresh */ + + mb(); + + kgsl_cache_range_op(memdesc->hostptr, memdesc->size, + memdesc->type, KGSL_CACHE_OP_INV); + } + + BUG_ON(physaddr > 0x66000000 && physaddr < 0x66ffffff); + while (sizebytes > 3) { + cffdump_printline(-1, CFF_OP_WRITE_MEM, physaddr, *(uint *)src, + 0); + physaddr += 4; + src += 4; + sizebytes -= 4; + } + if (sizebytes > 0) + cffdump_printline(-1, CFF_OP_WRITE_MEM, physaddr, *(uint *)src, + 0); +} + +void kgsl_cffdump_setmem(uint addr, uint value, uint sizebytes) +{ + if (!kgsl_cff_dump_enable) + return; + + BUG_ON(addr > 0x66000000 && addr < 0x66ffffff); + while (sizebytes > 3) { + /* Use 32bit memory writes as long as there's at least + * 4 bytes left */ + cffdump_printline(-1, CFF_OP_WRITE_MEM, addr, value, 0); + addr += 4; + sizebytes -= 4; + } + if (sizebytes > 0) + cffdump_printline(-1, CFF_OP_WRITE_MEM, addr, value, 0); +} + +void kgsl_cffdump_regwrite(enum kgsl_deviceid device_id, uint addr, + uint value) +{ + if (!kgsl_cff_dump_enable) + return; + + cffdump_printline(device_id, CFF_OP_WRITE_REG, addr, value, 0); +} + +void kgsl_cffdump_regpoll(enum kgsl_deviceid device_id, uint addr, + uint value, uint mask) +{ + if (!kgsl_cff_dump_enable) + return; + + cffdump_printline(device_id, CFF_OP_POLL_REG, addr, value, mask); +} + +void kgsl_cffdump_slavewrite(uint addr, uint value) +{ + if (!kgsl_cff_dump_enable) + return; + + cffdump_printline(-1, CFF_OP_WRITE_REG, addr, value, 0); +} + +int kgsl_cffdump_waitirq(void) +{ + if (!kgsl_cff_dump_enable) + return 0; + + cffdump_printline(-1, CFF_OP_WAIT_IRQ, 0, 0, 0); + + return 1; +} +EXPORT_SYMBOL(kgsl_cffdump_waitirq); + +#define ADDRESS_STACK_SIZE 256 +#define GET_PM4_TYPE3_OPCODE(x) ((*(x) >> 8) & 0xFF) +static unsigned int kgsl_cffdump_addr_count; + +static bool kgsl_cffdump_handle_type3(struct kgsl_device_private *dev_priv, + uint *hostaddr, bool check_only) +{ + static uint addr_stack[ADDRESS_STACK_SIZE]; + static uint size_stack[ADDRESS_STACK_SIZE]; + + switch (GET_PM4_TYPE3_OPCODE(hostaddr)) { + case PM4_INDIRECT_BUFFER_PFD: + case PM4_INDIRECT_BUFFER: + { + /* traverse indirect buffers */ + int i; + uint ibaddr = hostaddr[1]; + uint ibsize = hostaddr[2]; + + /* is this address already in encountered? */ + for (i = 0; + i < kgsl_cffdump_addr_count && addr_stack[i] != ibaddr; + ++i) + ; + + if (kgsl_cffdump_addr_count == i) { + addr_stack[kgsl_cffdump_addr_count] = ibaddr; + size_stack[kgsl_cffdump_addr_count++] = ibsize; + + if (kgsl_cffdump_addr_count >= ADDRESS_STACK_SIZE) { + KGSL_CORE_ERR("stack overflow\n"); + return false; + } + + return kgsl_cffdump_parse_ibs(dev_priv, NULL, + ibaddr, ibsize, check_only); + } else if (size_stack[i] != ibsize) { + KGSL_CORE_ERR("gpuaddr: 0x%08x, " + "wc: %u, with size wc: %u already on the " + "stack\n", ibaddr, ibsize, size_stack[i]); + return false; + } + } + break; + } + + return true; +} + +/* + * Traverse IBs and dump them to test vector. Detect swap by inspecting + * register writes, keeping note of the current state, and dump + * framebuffer config to test vector + */ +bool kgsl_cffdump_parse_ibs(struct kgsl_device_private *dev_priv, + const struct kgsl_memdesc *memdesc, uint gpuaddr, int sizedwords, + bool check_only) +{ + static uint level; /* recursion level */ + bool ret = true; + uint host_size; + uint *hostaddr, *hoststart; + int dwords_left = sizedwords; /* dwords left in the current command + buffer */ + + if (level == 0) + kgsl_cffdump_addr_count = 0; + + if (memdesc == NULL) { + struct kgsl_mem_entry *entry; + spin_lock(&dev_priv->process_priv->mem_lock); + entry = kgsl_sharedmem_find_region(dev_priv->process_priv, + gpuaddr, sizedwords * sizeof(uint)); + spin_unlock(&dev_priv->process_priv->mem_lock); + if (entry == NULL) { + KGSL_CORE_ERR("did not find mapping " + "for gpuaddr: 0x%08x\n", gpuaddr); + return true; + } + memdesc = &entry->memdesc; + } + + hostaddr = (uint *)kgsl_gpuaddr_to_vaddr(memdesc, gpuaddr, &host_size); + if (hostaddr == NULL) { + KGSL_CORE_ERR("did not find mapping for " + "gpuaddr: 0x%08x\n", gpuaddr); + return true; + } + + hoststart = hostaddr; + + level++; + + if (!memdesc->physaddr) { + KGSL_CORE_ERR("no physaddr"); + return true; + } else { + mb(); + kgsl_cache_range_op(memdesc->hostptr, memdesc->size, + memdesc->type, KGSL_CACHE_OP_INV); + } + +#ifdef DEBUG + pr_info("kgsl: cffdump: ib: gpuaddr:0x%08x, wc:%d, hptr:%p\n", + gpuaddr, sizedwords, hostaddr); +#endif + + while (dwords_left > 0) { + int count = 0; /* dword count including packet header */ + bool cur_ret = true; + + switch (*hostaddr >> 30) { + case 0x0: /* type-0 */ + count = (*hostaddr >> 16)+2; + break; + case 0x1: /* type-1 */ + count = 2; + break; + case 0x3: /* type-3 */ + count = ((*hostaddr >> 16) & 0x3fff) + 2; + cur_ret = kgsl_cffdump_handle_type3(dev_priv, + hostaddr, check_only); + break; + default: + pr_warn("kgsl: cffdump: parse-ib: unexpected type: " + "type:%d, word:0x%08x @ 0x%p, gpu:0x%08x\n", + *hostaddr >> 30, *hostaddr, hostaddr, + gpuaddr+4*(sizedwords-dwords_left)); + cur_ret = false; + count = dwords_left; + break; + } + +#ifdef DEBUG + if (!cur_ret) { + pr_info("kgsl: cffdump: bad sub-type: #:%d/%d, v:0x%08x" + " @ 0x%p[gb:0x%08x], level:%d\n", + sizedwords-dwords_left, sizedwords, *hostaddr, + hostaddr, gpuaddr+4*(sizedwords-dwords_left), + level); + + print_hex_dump(KERN_ERR, level == 1 ? "IB1:" : "IB2:", + DUMP_PREFIX_OFFSET, 32, 4, hoststart, + sizedwords*4, 0); + } +#endif + ret = ret && cur_ret; + + /* jump to next packet */ + dwords_left -= count; + hostaddr += count; + cur_ret = dwords_left >= 0; + +#ifdef DEBUG + if (!cur_ret) { + pr_info("kgsl: cffdump: bad count: c:%d, #:%d/%d, " + "v:0x%08x @ 0x%p[gb:0x%08x], level:%d\n", + count, sizedwords-(dwords_left+count), + sizedwords, *(hostaddr-count), hostaddr-count, + gpuaddr+4*(sizedwords-(dwords_left+count)), + level); + + print_hex_dump(KERN_ERR, level == 1 ? "IB1:" : "IB2:", + DUMP_PREFIX_OFFSET, 32, 4, hoststart, + sizedwords*4, 0); + } +#endif + + ret = ret && cur_ret; + } + + if (!ret) + pr_info("kgsl: cffdump: parsing failed: gpuaddr:0x%08x, " + "host:0x%p, wc:%d\n", gpuaddr, hoststart, sizedwords); + + if (!check_only) { +#ifdef DEBUG + uint offset = gpuaddr - memdesc->gpuaddr; + pr_info("kgsl: cffdump: ib-dump: hostptr:%p, gpuaddr:%08x, " + "physaddr:%08x, offset:%d, size:%d", hoststart, + gpuaddr, memdesc->physaddr + offset, offset, + sizedwords*4); +#endif + kgsl_cffdump_syncmem(dev_priv, memdesc, gpuaddr, sizedwords*4, + false); + } + + level--; + + return ret; +} + +static int subbuf_start_handler(struct rchan_buf *buf, + void *subbuf, void *prev_subbuf, uint prev_padding) +{ + pr_debug("kgsl: cffdump: subbuf_start_handler(subbuf=%p, prev_subbuf" + "=%p, prev_padding=%08x)\n", subbuf, prev_subbuf, prev_padding); + + if (relay_buf_full(buf)) { + if (!suspended) { + suspended = 1; + pr_warn("kgsl: cffdump: relay: cpu %d buffer full!!!\n", + smp_processor_id()); + } + dropped++; + return 0; + } else if (suspended) { + suspended = 0; + pr_warn("kgsl: cffdump: relay: cpu %d buffer no longer full.\n", + smp_processor_id()); + } + + subbuf_start_reserve(buf, 0); + return 1; +} + +static struct dentry *create_buf_file_handler(const char *filename, + struct dentry *parent, int mode, struct rchan_buf *buf, + int *is_global) +{ + return debugfs_create_file(filename, mode, parent, buf, + &relay_file_operations); +} + +/* + * file_remove() default callback. Removes relay file in debugfs. + */ +static int remove_buf_file_handler(struct dentry *dentry) +{ + pr_info("kgsl: cffdump: %s()\n", __func__); + debugfs_remove(dentry); + return 0; +} + +/* + * relay callbacks + */ +static struct rchan_callbacks relay_callbacks = { + .subbuf_start = subbuf_start_handler, + .create_buf_file = create_buf_file_handler, + .remove_buf_file = remove_buf_file_handler, +}; + +/** + * create_channel - creates channel /debug/klog/cpuXXX + * + * Creates channel along with associated produced/consumed control files + * + * Returns channel on success, NULL otherwise + */ +static struct rchan *create_channel(unsigned subbuf_size, unsigned n_subbufs) +{ + struct rchan *chan; + + pr_info("kgsl: cffdump: relay: create_channel: subbuf_size %u, " + "n_subbufs %u, dir 0x%p\n", subbuf_size, n_subbufs, dir); + + chan = relay_open("cpu", dir, subbuf_size, + n_subbufs, &relay_callbacks, NULL); + if (!chan) { + KGSL_CORE_ERR("relay_open failed\n"); + return NULL; + } + + suspended = 0; + dropped = 0; + + return chan; +} + +/** + * destroy_channel - destroys channel /debug/kgsl/cff/cpuXXX + * + * Destroys channel along with associated produced/consumed control files + */ +static void destroy_channel(void) +{ + pr_info("kgsl: cffdump: relay: destroy_channel\n"); + if (chan) { + relay_close(chan); + chan = NULL; + } +} + diff --git a/drivers/gpu/msm/kgsl_cffdump.h b/drivers/gpu/msm/kgsl_cffdump.h new file mode 100644 index 00000000000..aca7a7c8ee6 --- /dev/null +++ b/drivers/gpu/msm/kgsl_cffdump.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __KGSL_CFFDUMP_H +#define __KGSL_CFFDUMP_H + +#ifdef CONFIG_MSM_KGSL_CFF_DUMP + +#include + +#include "kgsl_device.h" + +void kgsl_cffdump_init(void); +void kgsl_cffdump_destroy(void); +void kgsl_cffdump_open(enum kgsl_deviceid device_id); +void kgsl_cffdump_close(enum kgsl_deviceid device_id); +void kgsl_cffdump_syncmem(struct kgsl_device_private *dev_priv, + const struct kgsl_memdesc *memdesc, uint physaddr, uint sizebytes, + bool clean_cache); +void kgsl_cffdump_setmem(uint addr, uint value, uint sizebytes); +void kgsl_cffdump_regwrite(enum kgsl_deviceid device_id, uint addr, + uint value); +void kgsl_cffdump_regpoll(enum kgsl_deviceid device_id, uint addr, + uint value, uint mask); +bool kgsl_cffdump_parse_ibs(struct kgsl_device_private *dev_priv, + const struct kgsl_memdesc *memdesc, uint gpuaddr, int sizedwords, + bool check_only); +static inline bool kgsl_cffdump_flags_no_memzero(void) { return true; } + +#else + +#define kgsl_cffdump_init() (void)0 +#define kgsl_cffdump_destroy() (void)0 +#define kgsl_cffdump_open(device_id) (void)0 +#define kgsl_cffdump_close(device_id) (void)0 +#define kgsl_cffdump_syncmem(dev_priv, memdesc, addr, sizebytes, clean_cache) \ + (void) 0 +#define kgsl_cffdump_setmem(addr, value, sizebytes) (void)0 +#define kgsl_cffdump_regwrite(device_id, addr, value) (void)0 +#define kgsl_cffdump_regpoll(device_id, addr, value, mask) (void)0 +#define kgsl_cffdump_parse_ibs(dev_priv, memdesc, gpuaddr, \ + sizedwords, check_only) true +#define kgsl_cffdump_flags_no_memzero() true + +#endif /* CONFIG_MSM_KGSL_CFF_DUMP */ + +#endif /* __KGSL_CFFDUMP_H */ diff --git a/drivers/gpu/msm/kgsl_debugfs.c b/drivers/gpu/msm/kgsl_debugfs.c new file mode 100644 index 00000000000..9da3096a5b9 --- /dev/null +++ b/drivers/gpu/msm/kgsl_debugfs.c @@ -0,0 +1,82 @@ +/* Copyright (c) 2002,2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include + +#include "kgsl.h" +#include "kgsl_device.h" + +/*default log levels is error for everything*/ +#define KGSL_LOG_LEVEL_DEFAULT 3 +#define KGSL_LOG_LEVEL_MAX 7 + +struct dentry *kgsl_debugfs_dir; + +static inline int kgsl_log_set(unsigned int *log_val, void *data, u64 val) +{ + *log_val = min((unsigned int)val, (unsigned int)KGSL_LOG_LEVEL_MAX); + return 0; +} + +#define KGSL_DEBUGFS_LOG(__log) \ +static int __log ## _set(void *data, u64 val) \ +{ \ + struct kgsl_device *device = data; \ + return kgsl_log_set(&device->__log, data, val); \ +} \ +static int __log ## _get(void *data, u64 *val) \ +{ \ + struct kgsl_device *device = data; \ + *val = device->__log; \ + return 0; \ +} \ +DEFINE_SIMPLE_ATTRIBUTE(__log ## _fops, \ +__log ## _get, __log ## _set, "%llu\n"); \ + +KGSL_DEBUGFS_LOG(drv_log); +KGSL_DEBUGFS_LOG(cmd_log); +KGSL_DEBUGFS_LOG(ctxt_log); +KGSL_DEBUGFS_LOG(mem_log); +KGSL_DEBUGFS_LOG(pwr_log); + +void kgsl_device_debugfs_init(struct kgsl_device *device) +{ + if (kgsl_debugfs_dir && !IS_ERR(kgsl_debugfs_dir)) + device->d_debugfs = debugfs_create_dir(device->name, + kgsl_debugfs_dir); + + if (!device->d_debugfs || IS_ERR(device->d_debugfs)) + return; + + device->cmd_log = KGSL_LOG_LEVEL_DEFAULT; + device->ctxt_log = KGSL_LOG_LEVEL_DEFAULT; + device->drv_log = KGSL_LOG_LEVEL_DEFAULT; + device->mem_log = KGSL_LOG_LEVEL_DEFAULT; + device->pwr_log = KGSL_LOG_LEVEL_DEFAULT; + + debugfs_create_file("log_level_cmd", 0644, device->d_debugfs, device, + &cmd_log_fops); + debugfs_create_file("log_level_ctxt", 0644, device->d_debugfs, device, + &ctxt_log_fops); + debugfs_create_file("log_level_drv", 0644, device->d_debugfs, device, + &drv_log_fops); + debugfs_create_file("log_level_mem", 0644, device->d_debugfs, device, + &mem_log_fops); + debugfs_create_file("log_level_pwr", 0644, device->d_debugfs, device, + &pwr_log_fops); +} + +void kgsl_core_debugfs_init(void) +{ + kgsl_debugfs_dir = debugfs_create_dir("kgsl", 0); +} diff --git a/drivers/gpu/msm/kgsl_debugfs.h b/drivers/gpu/msm/kgsl_debugfs.h new file mode 100644 index 00000000000..1e36fabba65 --- /dev/null +++ b/drivers/gpu/msm/kgsl_debugfs.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2002,2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _KGSL_DEBUGFS_H +#define _KGSL_DEBUGFS_H + +struct kgsl_device; + +#ifdef CONFIG_DEBUG_FS +void kgsl_core_debugfs_init(void); +void kgsl_device_debugfs_init(struct kgsl_device *device); + +extern struct dentry *kgsl_debugfs_dir; +static inline struct dentry *kgsl_get_debugfs_dir(void) +{ + return kgsl_debugfs_dir; +} + +#else +static inline void kgsl_core_debugfs_init(void) { } +static inline void kgsl_device_debugfs_init(struct kgsl_device *device) { } + +static inline struct dentry *kgsl_get_debugfs_dir(void) { return NULL; } + +#endif + +#endif diff --git a/drivers/gpu/msm/kgsl_device.h b/drivers/gpu/msm/kgsl_device.h new file mode 100644 index 00000000000..692a9ec3f16 --- /dev/null +++ b/drivers/gpu/msm/kgsl_device.h @@ -0,0 +1,289 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __KGSL_DEVICE_H +#define __KGSL_DEVICE_H + +#include +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_mmu.h" +#include "kgsl_pwrctrl.h" +#include "kgsl_log.h" +#include "kgsl_pwrscale.h" + +#define KGSL_TIMEOUT_NONE 0 +#define KGSL_TIMEOUT_DEFAULT 0xFFFFFFFF + +#define FIRST_TIMEOUT (HZ / 2) + + +/* KGSL device state is initialized to INIT when platform_probe * + * sucessfully initialized the device. Once a device has been opened * + * (started) it becomes active. NAP implies that only low latency * + * resources (for now clocks on some platforms) are off. SLEEP implies * + * that the KGSL module believes a device is idle (has been inactive * + * past its timer) and all system resources are released. SUSPEND is * + * requested by the kernel and will be enforced upon all open devices. */ + +#define KGSL_STATE_NONE 0x00000000 +#define KGSL_STATE_INIT 0x00000001 +#define KGSL_STATE_ACTIVE 0x00000002 +#define KGSL_STATE_NAP 0x00000004 +#define KGSL_STATE_SLEEP 0x00000008 +#define KGSL_STATE_SUSPEND 0x00000010 +#define KGSL_STATE_HUNG 0x00000020 +#define KGSL_STATE_DUMP_AND_RECOVER 0x00000040 + +#define KGSL_GRAPHICS_MEMORY_LOW_WATERMARK 0x1000000 + +#define KGSL_IS_PAGE_ALIGNED(addr) (!((addr) & (~PAGE_MASK))) + +struct kgsl_device; +struct platform_device; +struct kgsl_device_private; +struct kgsl_context; +struct kgsl_power_stats; + +struct kgsl_functable { + /* Mandatory functions - these functions must be implemented + by the client device. The driver will not check for a NULL + pointer before calling the hook. + */ + void (*regread) (struct kgsl_device *device, + unsigned int offsetwords, unsigned int *value); + void (*regwrite) (struct kgsl_device *device, + unsigned int offsetwords, unsigned int value); + int (*idle) (struct kgsl_device *device, unsigned int timeout); + unsigned int (*isidle) (struct kgsl_device *device); + int (*suspend_context) (struct kgsl_device *device); + int (*start) (struct kgsl_device *device, unsigned int init_ram); + int (*stop) (struct kgsl_device *device); + int (*getproperty) (struct kgsl_device *device, + enum kgsl_property_type type, void *value, + unsigned int sizebytes); + int (*waittimestamp) (struct kgsl_device *device, + unsigned int timestamp, unsigned int msecs); + unsigned int (*readtimestamp) (struct kgsl_device *device, + enum kgsl_timestamp_type type); + int (*issueibcmds) (struct kgsl_device_private *dev_priv, + struct kgsl_context *context, struct kgsl_ibdesc *ibdesc, + unsigned int sizedwords, uint32_t *timestamp, + unsigned int flags); + int (*setup_pt)(struct kgsl_device *device, + struct kgsl_pagetable *pagetable); + int (*cleanup_pt)(struct kgsl_device *device, + struct kgsl_pagetable *pagetable); + void (*power_stats)(struct kgsl_device *device, + struct kgsl_power_stats *stats); + void (*irqctrl)(struct kgsl_device *device, int state); + /* Optional functions - these functions are not mandatory. The + driver will check that the function pointer is not NULL before + calling the hook */ + void (*setstate) (struct kgsl_device *device, uint32_t flags); + int (*drawctxt_create) (struct kgsl_device *device, + struct kgsl_pagetable *pagetable, struct kgsl_context *context, + uint32_t flags); + void (*drawctxt_destroy) (struct kgsl_device *device, + struct kgsl_context *context); + long (*ioctl) (struct kgsl_device_private *dev_priv, + unsigned int cmd, void *data); +}; + +struct kgsl_memregion { + unsigned char *mmio_virt_base; + unsigned int mmio_phys_base; + uint32_t gpu_base; + unsigned int sizebytes; +}; + +struct kgsl_device { + struct device *dev; + const char *name; + unsigned int ver_major; + unsigned int ver_minor; + uint32_t flags; + enum kgsl_deviceid id; + struct kgsl_memregion regspace; + struct kgsl_memdesc memstore; + const char *iomemname; + + struct kgsl_mmu mmu; + struct completion hwaccess_gate; + const struct kgsl_functable *ftbl; + struct work_struct idle_check_ws; + struct timer_list idle_timer; + struct kgsl_pwrctrl pwrctrl; + int open_count; + + struct atomic_notifier_head ts_notifier_list; + struct mutex mutex; + uint32_t state; + uint32_t requested_state; + + struct list_head memqueue; + unsigned int active_cnt; + struct completion suspend_gate; + + wait_queue_head_t wait_queue; + struct workqueue_struct *work_queue; + struct device *parentdev; + struct completion recovery_gate; + struct dentry *d_debugfs; + struct idr context_idr; + struct early_suspend display_off; + + /* Logging levels */ + int cmd_log; + int ctxt_log; + int drv_log; + int mem_log; + int pwr_log; + struct wake_lock idle_wakelock; + struct kgsl_pwrscale pwrscale; + struct kobject pwrscale_kobj; + struct pm_qos_request_list pm_qos_req_dma; +}; + +struct kgsl_context { + uint32_t id; + + /* Pointer to the owning device instance */ + struct kgsl_device_private *dev_priv; + + /* Pointer to the device specific context information */ + void *devctxt; +}; + +struct kgsl_process_private { + unsigned int refcnt; + pid_t pid; + spinlock_t mem_lock; + struct list_head mem_list; + struct kgsl_pagetable *pagetable; + struct list_head list; + struct kobject *kobj; + + struct { + unsigned int user; + unsigned int user_max; + unsigned int mapped; + unsigned int mapped_max; + unsigned int flushes; + } stats; +}; + +struct kgsl_device_private { + struct kgsl_device *device; + struct kgsl_process_private *process_priv; +}; + +struct kgsl_power_stats { + s64 total_time; + s64 busy_time; +}; + +struct kgsl_device *kgsl_get_device(int dev_idx); + +static inline void kgsl_regread(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int *value) +{ + device->ftbl->regread(device, offsetwords, value); +} + +static inline void kgsl_regwrite(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int value) +{ + device->ftbl->regwrite(device, offsetwords, value); +} + +static inline int kgsl_idle(struct kgsl_device *device, unsigned int timeout) +{ + return device->ftbl->idle(device, timeout); +} + +static inline int kgsl_create_device_sysfs_files(struct device *root, + const struct device_attribute **list) +{ + int ret = 0, i; + for (i = 0; list[i] != NULL; i++) + ret |= device_create_file(root, list[i]); + return ret; +} + +static inline void kgsl_remove_device_sysfs_files(struct device *root, + const struct device_attribute **list) +{ + int i; + for (i = 0; list[i] != NULL; i++) + device_remove_file(root, list[i]); +} + +static inline struct kgsl_mmu * +kgsl_get_mmu(struct kgsl_device *device) +{ + return (struct kgsl_mmu *) (device ? &device->mmu : NULL); +} + +static inline struct kgsl_device *kgsl_device_from_dev(struct device *dev) +{ + int i; + + for (i = 0; i < KGSL_DEVICE_MAX; i++) { + if (kgsl_driver.devp[i] && kgsl_driver.devp[i]->dev == dev) + return kgsl_driver.devp[i]; + } + + return NULL; +} + +static inline int kgsl_create_device_workqueue(struct kgsl_device *device) +{ + device->work_queue = create_workqueue(device->name); + if (!device->work_queue) { + KGSL_DRV_ERR(device, "create_workqueue(%s) failed\n", + device->name); + return -EINVAL; + } + return 0; +} + +static inline struct kgsl_context * +kgsl_find_context(struct kgsl_device_private *dev_priv, uint32_t id) +{ + struct kgsl_context *ctxt = + idr_find(&dev_priv->device->context_idr, id); + + /* Make sure that the context belongs to the current instance so + that other processes can't guess context IDs and mess things up */ + + return (ctxt && ctxt->dev_priv == dev_priv) ? ctxt : NULL; +} + +int kgsl_check_timestamp(struct kgsl_device *device, unsigned int timestamp); + +int kgsl_register_ts_notifier(struct kgsl_device *device, + struct notifier_block *nb); + +int kgsl_unregister_ts_notifier(struct kgsl_device *device, + struct notifier_block *nb); + +int kgsl_device_platform_probe(struct kgsl_device *device, + irqreturn_t (*dev_isr) (int, void*)); +void kgsl_device_platform_remove(struct kgsl_device *device); + +#endif /* __KGSL_DEVICE_H */ diff --git a/drivers/gpu/msm/kgsl_drm.c b/drivers/gpu/msm/kgsl_drm.c new file mode 100644 index 00000000000..202783b3dc7 --- /dev/null +++ b/drivers/gpu/msm/kgsl_drm.c @@ -0,0 +1,1690 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* Implements an interface between KGSL and the DRM subsystem. For now this + * is pretty simple, but it will take on more of the workload as time goes + * on + */ +#include "drmP.h" +#include "drm.h" +#include +#include + +#include "kgsl.h" +#include "kgsl_device.h" +#include "kgsl_drm.h" +#include "kgsl_mmu.h" +#include "kgsl_sharedmem.h" + +#define DRIVER_AUTHOR "Qualcomm" +#define DRIVER_NAME "kgsl" +#define DRIVER_DESC "KGSL DRM" +#define DRIVER_DATE "20100127" + +#define DRIVER_MAJOR 2 +#define DRIVER_MINOR 1 +#define DRIVER_PATCHLEVEL 1 + +#define DRM_KGSL_GEM_FLAG_MAPPED (1 << 0) + +#define ENTRY_EMPTY -1 +#define ENTRY_NEEDS_CLEANUP -2 + +#define DRM_KGSL_NUM_FENCE_ENTRIES (DRM_KGSL_HANDLE_WAIT_ENTRIES << 2) +#define DRM_KGSL_HANDLE_WAIT_ENTRIES 5 + +/* Returns true if the memory type is in PMEM */ + +#ifdef CONFIG_KERNEL_PMEM_SMI_REGION +#define TYPE_IS_PMEM(_t) \ + (((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_EBI) || \ + ((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_SMI) || \ + ((_t) & DRM_KGSL_GEM_TYPE_PMEM)) +#else +#define TYPE_IS_PMEM(_t) \ + (((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_EBI) || \ + ((_t) & (DRM_KGSL_GEM_TYPE_PMEM | DRM_KGSL_GEM_PMEM_EBI))) +#endif + +/* Returns true if the memory type is regular */ + +#define TYPE_IS_MEM(_t) \ + (((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_KMEM) || \ + ((_t & DRM_KGSL_GEM_TYPE_MEM_MASK) == DRM_KGSL_GEM_TYPE_KMEM_NOCACHE) || \ + ((_t) & DRM_KGSL_GEM_TYPE_MEM)) + +#define TYPE_IS_FD(_t) ((_t) & DRM_KGSL_GEM_TYPE_FD_MASK) + +/* Returns true if KMEM region is uncached */ + +#define IS_MEM_UNCACHED(_t) \ + ((_t == DRM_KGSL_GEM_TYPE_KMEM_NOCACHE) || \ + (_t == DRM_KGSL_GEM_TYPE_KMEM) || \ + (TYPE_IS_MEM(_t) && (_t & DRM_KGSL_GEM_CACHE_WCOMBINE))) + +struct drm_kgsl_gem_object_wait_list_entry { + struct list_head list; + int pid; + int in_use; + wait_queue_head_t process_wait_q; +}; + +struct drm_kgsl_gem_object_fence { + int32_t fence_id; + unsigned int num_buffers; + int ts_valid; + unsigned int timestamp; + int ts_device; + int lockpid; + struct list_head buffers_in_fence; +}; + +struct drm_kgsl_gem_object_fence_list_entry { + struct list_head list; + int in_use; + struct drm_gem_object *gem_obj; +}; + +static int32_t fence_id = 0x1; + +static struct drm_kgsl_gem_object_fence + gem_buf_fence[DRM_KGSL_NUM_FENCE_ENTRIES]; + +struct drm_kgsl_gem_object { + struct drm_gem_object *obj; + uint32_t type; + struct kgsl_memdesc memdesc; + struct kgsl_pagetable *pagetable; + uint64_t mmap_offset; + int bufcount; + int flags; + struct list_head list; + int active; + + struct { + uint32_t offset; + uint32_t gpuaddr; + } bufs[DRM_KGSL_GEM_MAX_BUFFERS]; + + int bound; + int lockpid; + /* Put these here to avoid allocing all the time */ + struct drm_kgsl_gem_object_wait_list_entry + wait_entries[DRM_KGSL_HANDLE_WAIT_ENTRIES]; + /* Each object can only appear in a single fence */ + struct drm_kgsl_gem_object_fence_list_entry + fence_entries[DRM_KGSL_NUM_FENCE_ENTRIES]; + + struct list_head wait_list; +}; + +/* This is a global list of all the memory currently mapped in the MMU */ +static struct list_head kgsl_mem_list; + +static void kgsl_gem_mem_flush(struct kgsl_memdesc *memdesc, int type, int op) +{ + int cacheop = 0; + + switch (op) { + case DRM_KGSL_GEM_CACHE_OP_TO_DEV: + if (type & (DRM_KGSL_GEM_CACHE_WBACK | + DRM_KGSL_GEM_CACHE_WBACKWA)) + cacheop = KGSL_CACHE_OP_CLEAN; + + break; + + case DRM_KGSL_GEM_CACHE_OP_FROM_DEV: + if (type & (DRM_KGSL_GEM_CACHE_WBACK | + DRM_KGSL_GEM_CACHE_WBACKWA | + DRM_KGSL_GEM_CACHE_WTHROUGH)) + cacheop = KGSL_CACHE_OP_INV; + } + + kgsl_cache_range_op(memdesc, cacheop); +} + +/* Flush all the memory mapped in the MMU */ + +void kgsl_gpu_mem_flush(int op) +{ + struct drm_kgsl_gem_object *entry; + + list_for_each_entry(entry, &kgsl_mem_list, list) { + kgsl_gem_mem_flush(&entry->memdesc, entry->type, op); + } + + /* Takes care of WT/WC case. + * More useful when we go barrierless + */ + dmb(); +} + +/* TODO: + * Add vsync wait */ + +static int kgsl_drm_load(struct drm_device *dev, unsigned long flags) +{ + return 0; +} + +static int kgsl_drm_unload(struct drm_device *dev) +{ + return 0; +} + +struct kgsl_drm_device_priv { + struct kgsl_device *device[KGSL_DEVICE_MAX]; + struct kgsl_device_private *devpriv[KGSL_DEVICE_MAX]; +}; + +static int kgsl_ts_notifier_cb(struct notifier_block *blk, + unsigned long code, void *_param); + +static struct notifier_block kgsl_ts_nb[KGSL_DEVICE_MAX]; + +static int kgsl_drm_firstopen(struct drm_device *dev) +{ + int i; + + for (i = 0; i < KGSL_DEVICE_MAX; i++) { + struct kgsl_device *device = kgsl_get_device(i); + + if (device == NULL) + continue; + + kgsl_ts_nb[i].notifier_call = kgsl_ts_notifier_cb; + kgsl_register_ts_notifier(device, &kgsl_ts_nb[i]); + } + + return 0; +} + +void kgsl_drm_lastclose(struct drm_device *dev) +{ + int i; + + for (i = 0; i < KGSL_DEVICE_MAX; i++) { + struct kgsl_device *device = kgsl_get_device(i); + if (device == NULL) + continue; + + kgsl_unregister_ts_notifier(device, &kgsl_ts_nb[i]); + } +} + +void kgsl_drm_preclose(struct drm_device *dev, struct drm_file *file_priv) +{ +} + +static int kgsl_drm_suspend(struct drm_device *dev, pm_message_t state) +{ + return 0; +} + +static int kgsl_drm_resume(struct drm_device *dev) +{ + return 0; +} + +static void +kgsl_gem_free_mmap_offset(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + struct drm_gem_mm *mm = dev->mm_private; + struct drm_kgsl_gem_object *priv = obj->driver_private; + struct drm_map_list *list; + + list = &obj->map_list; + drm_ht_remove_item(&mm->offset_hash, &list->hash); + if (list->file_offset_node) { + drm_mm_put_block(list->file_offset_node); + list->file_offset_node = NULL; + } + + kfree(list->map); + list->map = NULL; + + priv->mmap_offset = 0; +} + +static int +kgsl_gem_memory_allocated(struct drm_gem_object *obj) +{ + struct drm_kgsl_gem_object *priv = obj->driver_private; + return priv->memdesc.size ? 1 : 0; +} + +static int +kgsl_gem_alloc_memory(struct drm_gem_object *obj) +{ + struct drm_kgsl_gem_object *priv = obj->driver_private; + int index; + + /* Return if the memory is already allocated */ + + if (kgsl_gem_memory_allocated(obj) || TYPE_IS_FD(priv->type)) + return 0; + + if (TYPE_IS_PMEM(priv->type)) { + int type; + + if (priv->type == DRM_KGSL_GEM_TYPE_EBI || + priv->type & DRM_KGSL_GEM_PMEM_EBI) + type = PMEM_MEMTYPE_EBI1; + else + type = PMEM_MEMTYPE_SMI; + + priv->memdesc.physaddr = + pmem_kalloc(obj->size * priv->bufcount, + type | PMEM_ALIGNMENT_4K); + + if (IS_ERR((void *) priv->memdesc.physaddr)) { + DRM_ERROR("Unable to allocate PMEM memory\n"); + return -ENOMEM; + } + + priv->memdesc.size = obj->size * priv->bufcount; + priv->memdesc.ops = &kgsl_contiguous_ops; + + } else if (TYPE_IS_MEM(priv->type)) { + priv->memdesc.hostptr = + vmalloc_user(obj->size * priv->bufcount); + + if (priv->memdesc.hostptr == NULL) { + DRM_ERROR("Unable to allocate vmalloc memory\n"); + return -ENOMEM; + } + + priv->memdesc.size = obj->size * priv->bufcount; + priv->memdesc.ops = &kgsl_vmalloc_ops; + } else + return -EINVAL; + + for (index = 0; index < priv->bufcount; index++) + priv->bufs[index].offset = index * obj->size; + + + return 0; +} + +#ifdef CONFIG_MSM_KGSL_MMU +static void +kgsl_gem_unmap(struct drm_gem_object *obj) +{ + struct drm_kgsl_gem_object *priv = obj->driver_private; + + if (!priv->flags & DRM_KGSL_GEM_FLAG_MAPPED) + return; + + kgsl_mmu_unmap(priv->pagetable, &priv->memdesc); + + kgsl_mmu_putpagetable(priv->pagetable); + priv->pagetable = NULL; + + if ((priv->type == DRM_KGSL_GEM_TYPE_KMEM) || + (priv->type & DRM_KGSL_GEM_CACHE_MASK)) + list_del(&priv->list); + + priv->flags &= ~DRM_KGSL_GEM_FLAG_MAPPED; +} +#else +static void +kgsl_gem_unmap(struct drm_gem_object *obj) +{ +} +#endif + +static void +kgsl_gem_free_memory(struct drm_gem_object *obj) +{ + struct drm_kgsl_gem_object *priv = obj->driver_private; + + if (!kgsl_gem_memory_allocated(obj) || TYPE_IS_FD(priv->type)) + return; + + kgsl_gem_mem_flush(&priv->memdesc, priv->type, + DRM_KGSL_GEM_CACHE_OP_FROM_DEV); + + kgsl_gem_unmap(obj); + + if (TYPE_IS_PMEM(priv->type)) + pmem_kfree(priv->memdesc.physaddr); + + kgsl_sharedmem_free(&priv->memdesc); +} + +int +kgsl_gem_init_object(struct drm_gem_object *obj) +{ + struct drm_kgsl_gem_object *priv; + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + DRM_ERROR("Unable to create GEM object\n"); + return -ENOMEM; + } + + obj->driver_private = priv; + priv->obj = obj; + + return 0; +} + +void +kgsl_gem_free_object(struct drm_gem_object *obj) +{ + kgsl_gem_free_memory(obj); + kgsl_gem_free_mmap_offset(obj); + drm_gem_object_release(obj); + kfree(obj->driver_private); +} + +static int +kgsl_gem_create_mmap_offset(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + struct drm_gem_mm *mm = dev->mm_private; + struct drm_kgsl_gem_object *priv = obj->driver_private; + struct drm_map_list *list; + int msize; + + list = &obj->map_list; + list->map = kzalloc(sizeof(struct drm_map_list), GFP_KERNEL); + if (list->map == NULL) { + DRM_ERROR("Unable to allocate drm_map_list\n"); + return -ENOMEM; + } + + msize = obj->size * priv->bufcount; + + list->map->type = _DRM_GEM; + list->map->size = msize; + list->map->handle = obj; + + /* Allocate a mmap offset */ + list->file_offset_node = drm_mm_search_free(&mm->offset_manager, + msize / PAGE_SIZE, + 0, 0); + + if (!list->file_offset_node) { + DRM_ERROR("Failed to allocate offset for %d\n", obj->name); + kfree(list->map); + return -ENOMEM; + } + + list->file_offset_node = drm_mm_get_block(list->file_offset_node, + msize / PAGE_SIZE, 0); + + if (!list->file_offset_node) { + DRM_ERROR("Unable to create the file_offset_node\n"); + kfree(list->map); + return -ENOMEM; + } + + list->hash.key = list->file_offset_node->start; + if (drm_ht_insert_item(&mm->offset_hash, &list->hash)) { + DRM_ERROR("Failed to add to map hash\n"); + drm_mm_put_block(list->file_offset_node); + kfree(list->map); + return -ENOMEM; + } + + priv->mmap_offset = ((uint64_t) list->hash.key) << PAGE_SHIFT; + + return 0; +} + +int +kgsl_gem_obj_addr(int drm_fd, int handle, unsigned long *start, + unsigned long *len) +{ + struct file *filp; + struct drm_device *dev; + struct drm_file *file_priv; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + int ret = 0; + + filp = fget(drm_fd); + if (unlikely(filp == NULL)) { + DRM_ERROR("Unable to ghet the DRM file descriptor\n"); + return -EINVAL; + } + file_priv = filp->private_data; + if (unlikely(file_priv == NULL)) { + DRM_ERROR("Unable to get the file private data\n"); + fput(filp); + return -EINVAL; + } + dev = file_priv->minor->dev; + if (unlikely(dev == NULL)) { + DRM_ERROR("Unable to get the minor device\n"); + fput(filp); + return -EINVAL; + } + + obj = drm_gem_object_lookup(dev, file_priv, handle); + if (unlikely(obj == NULL)) { + DRM_ERROR("Invalid GEM handle %x\n", handle); + fput(filp); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + /* We can only use the MDP for PMEM regions */ + + if (TYPE_IS_PMEM(priv->type)) { + *start = priv->memdesc.physaddr + + priv->bufs[priv->active].offset; + + *len = priv->memdesc.size; + + kgsl_gem_mem_flush(&priv->memdesc, + priv->type, DRM_KGSL_GEM_CACHE_OP_TO_DEV); + } else { + *start = 0; + *len = 0; + ret = -EINVAL; + } + + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + fput(filp); + return ret; +} + +static int +kgsl_gem_init_obj(struct drm_device *dev, + struct drm_file *file_priv, + struct drm_gem_object *obj, + int *handle) +{ + struct drm_kgsl_gem_object *priv; + int ret, i; + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + memset(&priv->memdesc, 0, sizeof(priv->memdesc)); + priv->bufcount = 1; + priv->active = 0; + priv->bound = 0; + + /* To preserve backwards compatability, the default memory source + is EBI */ + + priv->type = DRM_KGSL_GEM_TYPE_PMEM | DRM_KGSL_GEM_PMEM_EBI; + + ret = drm_gem_handle_create(file_priv, obj, handle); + + drm_gem_object_handle_unreference(obj); + INIT_LIST_HEAD(&priv->wait_list); + + for (i = 0; i < DRM_KGSL_HANDLE_WAIT_ENTRIES; i++) { + INIT_LIST_HEAD((struct list_head *) &priv->wait_entries[i]); + priv->wait_entries[i].pid = 0; + init_waitqueue_head(&priv->wait_entries[i].process_wait_q); + } + + for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { + INIT_LIST_HEAD((struct list_head *) &priv->fence_entries[i]); + priv->fence_entries[i].in_use = 0; + priv->fence_entries[i].gem_obj = obj; + } + + mutex_unlock(&dev->struct_mutex); + return ret; +} + +int +kgsl_gem_create_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_create *create = data; + struct drm_gem_object *obj; + int ret, handle; + + /* Page align the size so we can allocate multiple buffers */ + create->size = ALIGN(create->size, 4096); + + obj = drm_gem_object_alloc(dev, create->size); + + if (obj == NULL) { + DRM_ERROR("Unable to allocate the GEM object\n"); + return -ENOMEM; + } + + ret = kgsl_gem_init_obj(dev, file_priv, obj, &handle); + if (ret) + return ret; + + create->handle = handle; + return 0; +} + +int +kgsl_gem_create_fd_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_create_fd *args = data; + struct file *file; + dev_t rdev; + struct fb_info *info; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + int ret, put_needed, handle; + + file = fget_light(args->fd, &put_needed); + + if (file == NULL) { + DRM_ERROR("Unable to get the file object\n"); + return -EBADF; + } + + rdev = file->f_dentry->d_inode->i_rdev; + + /* Only framebuffer objects are supported ATM */ + + if (MAJOR(rdev) != FB_MAJOR) { + DRM_ERROR("File descriptor is not a framebuffer\n"); + ret = -EBADF; + goto error_fput; + } + + info = registered_fb[MINOR(rdev)]; + + if (info == NULL) { + DRM_ERROR("Framebuffer minor %d is not registered\n", + MINOR(rdev)); + ret = -EBADF; + goto error_fput; + } + + obj = drm_gem_object_alloc(dev, info->fix.smem_len); + + if (obj == NULL) { + DRM_ERROR("Unable to allocate GEM object\n"); + ret = -ENOMEM; + goto error_fput; + } + + ret = kgsl_gem_init_obj(dev, file_priv, obj, &handle); + + if (ret) + goto error_fput; + + mutex_lock(&dev->struct_mutex); + + priv = obj->driver_private; + priv->memdesc.physaddr = info->fix.smem_start; + priv->type = DRM_KGSL_GEM_TYPE_FD_FBMEM; + + mutex_unlock(&dev->struct_mutex); + args->handle = handle; + +error_fput: + fput_light(file, put_needed); + + return ret; +} + +int +kgsl_gem_setmemtype_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_memtype *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + int ret = 0; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + if (TYPE_IS_FD(priv->type)) + ret = -EINVAL; + else { + if (TYPE_IS_PMEM(args->type) || TYPE_IS_MEM(args->type)) + priv->type = args->type; + else + ret = -EINVAL; + } + + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + return ret; +} + +int +kgsl_gem_getmemtype_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_memtype *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + args->type = priv->type; + + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + return 0; +} + +int +kgsl_gem_unbind_gpu_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_bind_gpu *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + if (--priv->bound == 0) + kgsl_gem_unmap(obj); + + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + return 0; +} + +#ifdef CONFIG_MSM_KGSL_MMU +static int +kgsl_gem_map(struct drm_gem_object *obj) +{ + struct drm_kgsl_gem_object *priv = obj->driver_private; + int index; + int ret = -EINVAL; + + if (priv->flags & DRM_KGSL_GEM_FLAG_MAPPED) + return 0; + + /* Get the global page table */ + + if (priv->pagetable == NULL) { + priv->pagetable = kgsl_mmu_getpagetable(KGSL_MMU_GLOBAL_PT); + + if (priv->pagetable == NULL) { + DRM_ERROR("Unable to get the GPU MMU pagetable\n"); + return -EINVAL; + } + } + + priv->memdesc.pagetable = priv->pagetable; + + ret = kgsl_mmu_map(priv->pagetable, &priv->memdesc, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + + if (!ret) { + for (index = 0; index < priv->bufcount; index++) { + priv->bufs[index].gpuaddr = + priv->memdesc.gpuaddr + + priv->bufs[index].offset; + } + } + + /* Add cached memory to the list to be cached */ + + if (priv->type == DRM_KGSL_GEM_TYPE_KMEM || + priv->type & DRM_KGSL_GEM_CACHE_MASK) + list_add(&priv->list, &kgsl_mem_list); + + priv->flags |= DRM_KGSL_GEM_FLAG_MAPPED; + + return ret; +} +#else +static int +kgsl_gem_map(struct drm_gem_object *obj) +{ + struct drm_kgsl_gem_object *priv = obj->driver_private; + int index; + + if (TYPE_IS_PMEM(priv->type)) { + for (index = 0; index < priv->bufcount; index++) + priv->bufs[index].gpuaddr = + priv->memdesc.physaddr + priv->bufs[index].offset; + + return 0; + } + + return -EINVAL; +} +#endif + +int +kgsl_gem_bind_gpu_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_bind_gpu *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + int ret = 0; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + if (priv->bound++ == 0) { + + if (!kgsl_gem_memory_allocated(obj)) { + DRM_ERROR("Memory not allocated for this object\n"); + ret = -ENOMEM; + goto out; + } + + ret = kgsl_gem_map(obj); + + /* This is legacy behavior - use GET_BUFFERINFO instead */ + args->gpuptr = priv->bufs[0].gpuaddr; + } +out: + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + return ret; +} + +/* Allocate the memory and prepare it for CPU mapping */ + +int +kgsl_gem_alloc_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_alloc *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + int ret; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + ret = kgsl_gem_alloc_memory(obj); + + if (ret) { + DRM_ERROR("Unable to allocate object memory\n"); + } else if (!priv->mmap_offset) { + ret = kgsl_gem_create_mmap_offset(obj); + if (ret) + DRM_ERROR("Unable to create a mmap offset\n"); + } + + args->offset = priv->mmap_offset; + + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + return ret; +} + +int +kgsl_gem_mmap_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_mmap *args = data; + struct drm_gem_object *obj; + unsigned long addr; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + down_write(¤t->mm->mmap_sem); + + addr = do_mmap(obj->filp, 0, args->size, + PROT_READ | PROT_WRITE, MAP_SHARED, + args->offset); + + up_write(¤t->mm->mmap_sem); + + mutex_lock(&dev->struct_mutex); + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + if (IS_ERR((void *) addr)) + return addr; + + args->hostptr = (uint32_t) addr; + return 0; +} + +/* This function is deprecated */ + +int +kgsl_gem_prep_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_prep *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + int ret; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + ret = kgsl_gem_alloc_memory(obj); + if (ret) { + DRM_ERROR("Unable to allocate object memory\n"); + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + return ret; + } + + if (priv->mmap_offset == 0) { + ret = kgsl_gem_create_mmap_offset(obj); + if (ret) { + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + return ret; + } + } + + args->offset = priv->mmap_offset; + args->phys = priv->memdesc.physaddr; + + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + return 0; +} + +int +kgsl_gem_get_bufinfo_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_bufinfo *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + int ret = -EINVAL; + int index; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + if (!kgsl_gem_memory_allocated(obj)) { + DRM_ERROR("Memory not allocated for this object\n"); + goto out; + } + + for (index = 0; index < priv->bufcount; index++) { + args->offset[index] = priv->bufs[index].offset; + args->gpuaddr[index] = priv->bufs[index].gpuaddr; + } + + args->count = priv->bufcount; + args->active = priv->active; + + ret = 0; + +out: + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + return ret; +} + +int +kgsl_gem_set_bufcount_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_bufcount *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + int ret = -EINVAL; + + if (args->bufcount < 1 || args->bufcount > DRM_KGSL_GEM_MAX_BUFFERS) + return -EINVAL; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + /* It is too much math to worry about what happens if we are already + allocated, so just bail if we are */ + + if (kgsl_gem_memory_allocated(obj)) { + DRM_ERROR("Memory already allocated - cannot change" + "number of buffers\n"); + goto out; + } + + priv->bufcount = args->bufcount; + ret = 0; + +out: + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + return ret; +} + +int +kgsl_gem_set_active_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_active *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + int ret = -EINVAL; + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", args->handle); + return -EBADF; + } + + mutex_lock(&dev->struct_mutex); + priv = obj->driver_private; + + if (args->active < 0 || args->active >= priv->bufcount) { + DRM_ERROR("Invalid active buffer %d\n", args->active); + goto out; + } + + priv->active = args->active; + ret = 0; + +out: + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + return ret; +} + +int kgsl_gem_kmem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct drm_gem_object *obj = vma->vm_private_data; + struct drm_device *dev = obj->dev; + struct drm_kgsl_gem_object *priv; + unsigned long offset, pg; + struct page *page; + + mutex_lock(&dev->struct_mutex); + + priv = obj->driver_private; + + offset = (unsigned long) vmf->virtual_address - vma->vm_start; + pg = (unsigned long) priv->memdesc.hostptr + offset; + + page = vmalloc_to_page((void *) pg); + if (!page) { + mutex_unlock(&dev->struct_mutex); + return VM_FAULT_SIGBUS; + } + + get_page(page); + vmf->page = page; + + mutex_unlock(&dev->struct_mutex); + return 0; +} + +int kgsl_gem_phys_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct drm_gem_object *obj = vma->vm_private_data; + struct drm_device *dev = obj->dev; + struct drm_kgsl_gem_object *priv; + unsigned long offset, pfn; + int ret = 0; + + offset = ((unsigned long) vmf->virtual_address - vma->vm_start) >> + PAGE_SHIFT; + + mutex_lock(&dev->struct_mutex); + + priv = obj->driver_private; + + pfn = (priv->memdesc.physaddr >> PAGE_SHIFT) + offset; + ret = vm_insert_pfn(vma, + (unsigned long) vmf->virtual_address, pfn); + mutex_unlock(&dev->struct_mutex); + + switch (ret) { + case -ENOMEM: + case -EAGAIN: + return VM_FAULT_OOM; + case -EFAULT: + return VM_FAULT_SIGBUS; + default: + return VM_FAULT_NOPAGE; + } +} + +static struct vm_operations_struct kgsl_gem_kmem_vm_ops = { + .fault = kgsl_gem_kmem_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static struct vm_operations_struct kgsl_gem_phys_vm_ops = { + .fault = kgsl_gem_phys_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +/* This is a clone of the standard drm_gem_mmap function modified to allow + us to properly map KMEM regions as well as the PMEM regions */ + +int msm_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct drm_file *priv = filp->private_data; + struct drm_device *dev = priv->minor->dev; + struct drm_gem_mm *mm = dev->mm_private; + struct drm_local_map *map = NULL; + struct drm_gem_object *obj; + struct drm_hash_item *hash; + struct drm_kgsl_gem_object *gpriv; + int ret = 0; + + mutex_lock(&dev->struct_mutex); + + if (drm_ht_find_item(&mm->offset_hash, vma->vm_pgoff, &hash)) { + mutex_unlock(&dev->struct_mutex); + return drm_mmap(filp, vma); + } + + map = drm_hash_entry(hash, struct drm_map_list, hash)->map; + if (!map || + ((map->flags & _DRM_RESTRICTED) && !capable(CAP_SYS_ADMIN))) { + ret = -EPERM; + goto out_unlock; + } + + /* Check for valid size. */ + if (map->size < vma->vm_end - vma->vm_start) { + ret = -EINVAL; + goto out_unlock; + } + + obj = map->handle; + + gpriv = obj->driver_private; + + /* VM_PFNMAP is only for memory that doesn't use struct page + * in other words, not "normal" memory. If you try to use it + * with "normal" memory then the mappings don't get flushed. */ + + if (TYPE_IS_MEM(gpriv->type)) { + vma->vm_flags |= VM_RESERVED | VM_DONTEXPAND; + vma->vm_ops = &kgsl_gem_kmem_vm_ops; + } else { + vma->vm_flags |= VM_RESERVED | VM_IO | VM_PFNMAP | + VM_DONTEXPAND; + vma->vm_ops = &kgsl_gem_phys_vm_ops; + } + + vma->vm_private_data = map->handle; + + + /* Take care of requested caching policy */ + if (gpriv->type == DRM_KGSL_GEM_TYPE_KMEM || + gpriv->type & DRM_KGSL_GEM_CACHE_MASK) { + if (gpriv->type & DRM_KGSL_GEM_CACHE_WBACKWA) + vma->vm_page_prot = + pgprot_writebackwacache(vma->vm_page_prot); + else if (gpriv->type & DRM_KGSL_GEM_CACHE_WBACK) + vma->vm_page_prot = + pgprot_writebackcache(vma->vm_page_prot); + else if (gpriv->type & DRM_KGSL_GEM_CACHE_WTHROUGH) + vma->vm_page_prot = + pgprot_writethroughcache(vma->vm_page_prot); + else + vma->vm_page_prot = + pgprot_writecombine(vma->vm_page_prot); + } else { + if (gpriv->type == DRM_KGSL_GEM_TYPE_KMEM_NOCACHE) + vma->vm_page_prot = + pgprot_noncached(vma->vm_page_prot); + else + /* default pmem is WC */ + vma->vm_page_prot = + pgprot_writecombine(vma->vm_page_prot); + } + + /* flush out existing KMEM cached mappings if new ones are + * of uncached type */ + if (IS_MEM_UNCACHED(gpriv->type)) + kgsl_cache_range_op(&gpriv->memdesc, + KGSL_CACHE_OP_FLUSH); + + /* Add the other memory types here */ + + /* Take a ref for this mapping of the object, so that the fault + * handler can dereference the mmap offset's pointer to the object. + * This reference is cleaned up by the corresponding vm_close + * (which should happen whether the vma was created by this call, or + * by a vm_open due to mremap or partial unmap or whatever). + */ + drm_gem_object_reference(obj); + + vma->vm_file = filp; /* Needed for drm_vm_open() */ + drm_vm_open_locked(vma); + +out_unlock: + mutex_unlock(&dev->struct_mutex); + + return ret; +} + +void +cleanup_fence(struct drm_kgsl_gem_object_fence *fence, int check_waiting) +{ + int j; + struct drm_kgsl_gem_object_fence_list_entry *this_fence_entry = NULL; + struct drm_kgsl_gem_object *unlock_obj; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object_wait_list_entry *lock_next; + + fence->ts_valid = 0; + fence->timestamp = -1; + fence->ts_device = -1; + + /* Walk the list of buffers in this fence and clean up the */ + /* references. Note that this can cause memory allocations */ + /* to be freed */ + for (j = fence->num_buffers; j > 0; j--) { + this_fence_entry = + (struct drm_kgsl_gem_object_fence_list_entry *) + fence->buffers_in_fence.prev; + + this_fence_entry->in_use = 0; + obj = this_fence_entry->gem_obj; + unlock_obj = obj->driver_private; + + /* Delete it from the list */ + + list_del(&this_fence_entry->list); + + /* we are unlocking - see if there are other pids waiting */ + if (check_waiting) { + if (!list_empty(&unlock_obj->wait_list)) { + lock_next = + (struct drm_kgsl_gem_object_wait_list_entry *) + unlock_obj->wait_list.prev; + + list_del((struct list_head *)&lock_next->list); + + unlock_obj->lockpid = 0; + wake_up_interruptible( + &lock_next->process_wait_q); + lock_next->pid = 0; + + } else { + /* List is empty so set pid to 0 */ + unlock_obj->lockpid = 0; + } + } + + drm_gem_object_unreference(obj); + } + /* here all the buffers in the fence are released */ + /* clear the fence entry */ + fence->fence_id = ENTRY_EMPTY; +} + +int +find_empty_fence(void) +{ + int i; + + for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { + if (gem_buf_fence[i].fence_id == ENTRY_EMPTY) { + gem_buf_fence[i].fence_id = fence_id++; + gem_buf_fence[i].ts_valid = 0; + INIT_LIST_HEAD(&(gem_buf_fence[i].buffers_in_fence)); + if (fence_id == 0xFFFFFFF0) + fence_id = 1; + return i; + } else { + + /* Look for entries to be cleaned up */ + if (gem_buf_fence[i].fence_id == ENTRY_NEEDS_CLEANUP) + cleanup_fence(&gem_buf_fence[i], 0); + } + } + + return ENTRY_EMPTY; +} + +int +find_fence(int index) +{ + int i; + + for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { + if (gem_buf_fence[i].fence_id == index) + return i; + } + + return ENTRY_EMPTY; +} + +void +wakeup_fence_entries(struct drm_kgsl_gem_object_fence *fence) +{ + struct drm_kgsl_gem_object_fence_list_entry *this_fence_entry = NULL; + struct drm_kgsl_gem_object_wait_list_entry *lock_next; + struct drm_kgsl_gem_object *unlock_obj; + struct drm_gem_object *obj; + + /* TS has expired when we get here */ + fence->ts_valid = 0; + fence->timestamp = -1; + fence->ts_device = -1; + + list_for_each_entry(this_fence_entry, &fence->buffers_in_fence, list) { + obj = this_fence_entry->gem_obj; + unlock_obj = obj->driver_private; + + if (!list_empty(&unlock_obj->wait_list)) { + lock_next = + (struct drm_kgsl_gem_object_wait_list_entry *) + unlock_obj->wait_list.prev; + + /* Unblock the pid */ + lock_next->pid = 0; + + /* Delete it from the list */ + list_del((struct list_head *)&lock_next->list); + + unlock_obj->lockpid = 0; + wake_up_interruptible(&lock_next->process_wait_q); + + } else { + /* List is empty so set pid to 0 */ + unlock_obj->lockpid = 0; + } + } + fence->fence_id = ENTRY_NEEDS_CLEANUP; /* Mark it as needing cleanup */ +} + +static int kgsl_ts_notifier_cb(struct notifier_block *blk, + unsigned long code, void *_param) +{ + struct drm_kgsl_gem_object_fence *fence; + struct kgsl_device *device = kgsl_get_device(code); + int i; + + /* loop through the fences to see what things can be processed */ + + for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { + fence = &gem_buf_fence[i]; + if (!fence->ts_valid || fence->ts_device != code) + continue; + + if (kgsl_check_timestamp(device, fence->timestamp)) + wakeup_fence_entries(fence); + } + + return 0; +} + +int +kgsl_gem_lock_handle_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + /* The purpose of this function is to lock a given set of handles. */ + /* The driver will maintain a list of locked handles. */ + /* If a request comes in for a handle that's locked the thread will */ + /* block until it's no longer in use. */ + + struct drm_kgsl_gem_lock_handles *args = data; + struct drm_gem_object *obj; + struct drm_kgsl_gem_object *priv; + struct drm_kgsl_gem_object_fence_list_entry *this_fence_entry = NULL; + struct drm_kgsl_gem_object_fence *fence; + struct drm_kgsl_gem_object_wait_list_entry *lock_item; + int i, j; + int result = 0; + uint32_t *lock_list; + uint32_t *work_list = NULL; + int32_t fence_index; + + /* copy in the data from user space */ + lock_list = kzalloc(sizeof(uint32_t) * args->num_handles, GFP_KERNEL); + if (!lock_list) { + DRM_ERROR("Unable allocate memory for lock list\n"); + result = -ENOMEM; + goto error; + } + + if (copy_from_user(lock_list, args->handle_list, + sizeof(uint32_t) * args->num_handles)) { + DRM_ERROR("Unable to copy the lock list from the user\n"); + result = -EFAULT; + goto free_handle_list; + } + + + work_list = lock_list; + mutex_lock(&dev->struct_mutex); + + /* build the fence for this group of handles */ + fence_index = find_empty_fence(); + if (fence_index == ENTRY_EMPTY) { + DRM_ERROR("Unable to find a empty fence\n"); + args->lock_id = 0xDEADBEEF; + result = -EFAULT; + goto out_unlock; + } + + fence = &gem_buf_fence[fence_index]; + gem_buf_fence[fence_index].num_buffers = args->num_handles; + args->lock_id = gem_buf_fence[fence_index].fence_id; + + for (j = args->num_handles; j > 0; j--, lock_list++) { + obj = drm_gem_object_lookup(dev, file_priv, *lock_list); + + if (obj == NULL) { + DRM_ERROR("Invalid GEM handle %x\n", *lock_list); + result = -EBADF; + goto out_unlock; + } + + priv = obj->driver_private; + this_fence_entry = NULL; + + /* get a fence entry to hook into the fence */ + for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { + if (!priv->fence_entries[i].in_use) { + this_fence_entry = &priv->fence_entries[i]; + this_fence_entry->in_use = 1; + break; + } + } + + if (this_fence_entry == NULL) { + fence->num_buffers = 0; + fence->fence_id = ENTRY_EMPTY; + args->lock_id = 0xDEADBEAD; + result = -EFAULT; + drm_gem_object_unreference(obj); + goto out_unlock; + } + + /* We're trying to lock - add to a fence */ + list_add((struct list_head *)this_fence_entry, + &gem_buf_fence[fence_index].buffers_in_fence); + if (priv->lockpid) { + + if (priv->lockpid == args->pid) { + /* now that things are running async this */ + /* happens when an op isn't done */ + /* so it's already locked by the calling pid */ + continue; + } + + + /* if a pid already had it locked */ + /* create and add to wait list */ + for (i = 0; i < DRM_KGSL_HANDLE_WAIT_ENTRIES; i++) { + if (priv->wait_entries[i].in_use == 0) { + /* this one is empty */ + lock_item = &priv->wait_entries[i]; + lock_item->in_use = 1; + lock_item->pid = args->pid; + INIT_LIST_HEAD((struct list_head *) + &priv->wait_entries[i]); + break; + } + } + + if (i == DRM_KGSL_HANDLE_WAIT_ENTRIES) { + + result = -EFAULT; + drm_gem_object_unreference(obj); + goto out_unlock; + } + + list_add_tail((struct list_head *)&lock_item->list, + &priv->wait_list); + mutex_unlock(&dev->struct_mutex); + /* here we need to block */ + wait_event_interruptible_timeout( + priv->wait_entries[i].process_wait_q, + (priv->lockpid == 0), + msecs_to_jiffies(64)); + mutex_lock(&dev->struct_mutex); + lock_item->in_use = 0; + } + + /* Getting here means no one currently holds the lock */ + priv->lockpid = args->pid; + + args->lock_id = gem_buf_fence[fence_index].fence_id; + } + fence->lockpid = args->pid; + +out_unlock: + mutex_unlock(&dev->struct_mutex); + +free_handle_list: + kfree(work_list); + +error: + return result; +} + +int +kgsl_gem_unlock_handle_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_unlock_handles *args = data; + int result = 0; + int32_t fence_index; + + mutex_lock(&dev->struct_mutex); + fence_index = find_fence(args->lock_id); + if (fence_index == ENTRY_EMPTY) { + DRM_ERROR("Invalid lock ID: %x\n", args->lock_id); + result = -EFAULT; + goto out_unlock; + } + + cleanup_fence(&gem_buf_fence[fence_index], 1); + +out_unlock: + mutex_unlock(&dev->struct_mutex); + + return result; +} + + +int +kgsl_gem_unlock_on_ts_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_kgsl_gem_unlock_on_ts *args = data; + int result = 0; + int ts_done = 0; + int32_t fence_index, ts_device; + struct drm_kgsl_gem_object_fence *fence; + struct kgsl_device *device; + + if (args->type == DRM_KGSL_GEM_TS_3D) + ts_device = KGSL_DEVICE_3D0; + else if (args->type == DRM_KGSL_GEM_TS_2D) + ts_device = KGSL_DEVICE_2D0; + else { + result = -EINVAL; + goto error; + } + + device = kgsl_get_device(ts_device); + ts_done = kgsl_check_timestamp(device, args->timestamp); + + mutex_lock(&dev->struct_mutex); + + fence_index = find_fence(args->lock_id); + if (fence_index == ENTRY_EMPTY) { + DRM_ERROR("Invalid lock ID: %x\n", args->lock_id); + result = -EFAULT; + goto out_unlock; + } + + fence = &gem_buf_fence[fence_index]; + fence->ts_device = ts_device; + + if (!ts_done) + fence->ts_valid = 1; + else + cleanup_fence(fence, 1); + + +out_unlock: + mutex_unlock(&dev->struct_mutex); + +error: + return result; +} + +struct drm_ioctl_desc kgsl_drm_ioctls[] = { + DRM_IOCTL_DEF_DRV(KGSL_GEM_CREATE, kgsl_gem_create_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_PREP, kgsl_gem_prep_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_SETMEMTYPE, kgsl_gem_setmemtype_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_GETMEMTYPE, kgsl_gem_getmemtype_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_BIND_GPU, kgsl_gem_bind_gpu_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_UNBIND_GPU, kgsl_gem_unbind_gpu_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_ALLOC, kgsl_gem_alloc_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_MMAP, kgsl_gem_mmap_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_GET_BUFINFO, kgsl_gem_get_bufinfo_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_SET_BUFCOUNT, + kgsl_gem_set_bufcount_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_SET_ACTIVE, kgsl_gem_set_active_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_LOCK_HANDLE, + kgsl_gem_lock_handle_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_UNLOCK_HANDLE, + kgsl_gem_unlock_handle_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_UNLOCK_ON_TS, + kgsl_gem_unlock_on_ts_ioctl, 0), + DRM_IOCTL_DEF_DRV(KGSL_GEM_CREATE_FD, kgsl_gem_create_fd_ioctl, + DRM_MASTER), +}; + +static struct drm_driver driver = { + .driver_features = DRIVER_USE_PLATFORM_DEVICE | DRIVER_GEM, + .load = kgsl_drm_load, + .unload = kgsl_drm_unload, + .firstopen = kgsl_drm_firstopen, + .lastclose = kgsl_drm_lastclose, + .preclose = kgsl_drm_preclose, + .suspend = kgsl_drm_suspend, + .resume = kgsl_drm_resume, + .reclaim_buffers = drm_core_reclaim_buffers, + .gem_init_object = kgsl_gem_init_object, + .gem_free_object = kgsl_gem_free_object, + .ioctls = kgsl_drm_ioctls, + + .fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, + .mmap = msm_drm_gem_mmap, + .poll = drm_poll, + .fasync = drm_fasync, + }, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, +}; + +int kgsl_drm_init(struct platform_device *dev) +{ + int i; + + driver.num_ioctls = DRM_ARRAY_SIZE(kgsl_drm_ioctls); + driver.platform_device = dev; + + INIT_LIST_HEAD(&kgsl_mem_list); + + for (i = 0; i < DRM_KGSL_NUM_FENCE_ENTRIES; i++) { + gem_buf_fence[i].num_buffers = 0; + gem_buf_fence[i].ts_valid = 0; + gem_buf_fence[i].fence_id = ENTRY_EMPTY; + } + + return drm_init(&driver); +} + +void kgsl_drm_exit(void) +{ + drm_exit(&driver); +} diff --git a/drivers/gpu/msm/kgsl_log.h b/drivers/gpu/msm/kgsl_log.h new file mode 100644 index 00000000000..9fafcf4d684 --- /dev/null +++ b/drivers/gpu/msm/kgsl_log.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2002,2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __KGSL_LOG_H +#define __KGSL_LOG_H + +extern unsigned int kgsl_cff_dump_enable; + +#define KGSL_LOG_INFO(dev, lvl, fmt, args...) \ + do { \ + if ((lvl) >= 6) \ + dev_info(dev, "|%s| " fmt, \ + __func__, ##args);\ + } while (0) + +#define KGSL_LOG_WARN(dev, lvl, fmt, args...) \ + do { \ + if ((lvl) >= 4) \ + dev_warn(dev, "|%s| " fmt, \ + __func__, ##args);\ + } while (0) + +#define KGSL_LOG_ERR(dev, lvl, fmt, args...) \ + do { \ + if ((lvl) >= 3) \ + dev_err(dev, "|%s| " fmt, \ + __func__, ##args);\ + } while (0) + +#define KGSL_LOG_CRIT(dev, lvl, fmt, args...) \ + do { \ + if ((lvl) >= 2) \ + dev_crit(dev, "|%s| " fmt, \ + __func__, ##args);\ + } while (0) + +#define KGSL_LOG_POSTMORTEM_WRITE(_dev, fmt, args...) \ + do { dev_crit(_dev->dev, fmt, ##args); } while (0) + +#define KGSL_LOG_DUMP(_dev, fmt, args...) dev_err(_dev->dev, fmt, ##args) + +#define KGSL_DRV_INFO(_dev, fmt, args...) \ +KGSL_LOG_INFO(_dev->dev, _dev->drv_log, fmt, ##args) +#define KGSL_DRV_WARN(_dev, fmt, args...) \ +KGSL_LOG_WARN(_dev->dev, _dev->drv_log, fmt, ##args) +#define KGSL_DRV_ERR(_dev, fmt, args...) \ +KGSL_LOG_ERR(_dev->dev, _dev->drv_log, fmt, ##args) +#define KGSL_DRV_CRIT(_dev, fmt, args...) \ +KGSL_LOG_CRIT(_dev->dev, _dev->drv_log, fmt, ##args) + +#define KGSL_CMD_INFO(_dev, fmt, args...) \ +KGSL_LOG_INFO(_dev->dev, _dev->cmd_log, fmt, ##args) +#define KGSL_CMD_WARN(_dev, fmt, args...) \ +KGSL_LOG_WARN(_dev->dev, _dev->cmd_log, fmt, ##args) +#define KGSL_CMD_ERR(_dev, fmt, args...) \ +KGSL_LOG_ERR(_dev->dev, _dev->cmd_log, fmt, ##args) +#define KGSL_CMD_CRIT(_dev, fmt, args...) \ +KGSL_LOG_CRIT(_dev->dev, _dev->cmd_log, fmt, ##args) + +#define KGSL_CTXT_INFO(_dev, fmt, args...) \ +KGSL_LOG_INFO(_dev->dev, _dev->ctxt_log, fmt, ##args) +#define KGSL_CTXT_WARN(_dev, fmt, args...) \ +KGSL_LOG_WARN(_dev->dev, _dev->ctxt_log, fmt, ##args) +#define KGSL_CTXT_ERR(_dev, fmt, args...) \ +KGSL_LOG_ERR(_dev->dev, _dev->ctxt_log, fmt, ##args) +#define KGSL_CTXT_CRIT(_dev, fmt, args...) \ +KGSL_LOG_CRIT(_dev->dev, _dev->ctxt_log, fmt, ##args) + +#define KGSL_MEM_INFO(_dev, fmt, args...) \ +KGSL_LOG_INFO(_dev->dev, _dev->mem_log, fmt, ##args) +#define KGSL_MEM_WARN(_dev, fmt, args...) \ +KGSL_LOG_WARN(_dev->dev, _dev->mem_log, fmt, ##args) +#define KGSL_MEM_ERR(_dev, fmt, args...) \ +KGSL_LOG_ERR(_dev->dev, _dev->mem_log, fmt, ##args) +#define KGSL_MEM_CRIT(_dev, fmt, args...) \ +KGSL_LOG_CRIT(_dev->dev, _dev->mem_log, fmt, ##args) + +#define KGSL_PWR_INFO(_dev, fmt, args...) \ +KGSL_LOG_INFO(_dev->dev, _dev->pwr_log, fmt, ##args) +#define KGSL_PWR_WARN(_dev, fmt, args...) \ +KGSL_LOG_WARN(_dev->dev, _dev->pwr_log, fmt, ##args) +#define KGSL_PWR_ERR(_dev, fmt, args...) \ +KGSL_LOG_ERR(_dev->dev, _dev->pwr_log, fmt, ##args) +#define KGSL_PWR_CRIT(_dev, fmt, args...) \ +KGSL_LOG_CRIT(_dev->dev, _dev->pwr_log, fmt, ##args) + +/* Core error messages - these are for core KGSL functions that have + no device associated with them (such as memory) */ + +#define KGSL_CORE_ERR(fmt, args...) \ +pr_err("kgsl: %s: " fmt, __func__, ##args) + +#endif /* __KGSL_LOG_H */ diff --git a/drivers/gpu/msm/kgsl_mmu.c b/drivers/gpu/msm/kgsl_mmu.c new file mode 100644 index 00000000000..15ec0ece48e --- /dev/null +++ b/drivers/gpu/msm/kgsl_mmu.c @@ -0,0 +1,1151 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_mmu.h" +#include "kgsl_device.h" +#include "kgsl_sharedmem.h" + +#define KGSL_MMU_ALIGN_SHIFT 13 +#define KGSL_MMU_ALIGN_MASK (~((1 << KGSL_MMU_ALIGN_SHIFT) - 1)) + +#define GSL_PT_PAGE_BITS_MASK 0x00000007 +#define GSL_PT_PAGE_ADDR_MASK PAGE_MASK + +static void pagetable_remove_sysfs_objects(struct kgsl_pagetable *pagetable); + +static ssize_t +sysfs_show_ptpool_entries(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", kgsl_driver.ptpool.entries); +} + +static ssize_t +sysfs_show_ptpool_min(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", kgsl_driver.ptpool.static_entries); +} + +static ssize_t +sysfs_show_ptpool_chunks(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", kgsl_driver.ptpool.chunks); +} + +static ssize_t +sysfs_show_ptpool_ptsize(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", kgsl_driver.ptpool.ptsize); +} + +static struct kobj_attribute attr_ptpool_entries = { + .attr = { .name = "ptpool_entries", .mode = 0444 }, + .show = sysfs_show_ptpool_entries, + .store = NULL, +}; + +static struct kobj_attribute attr_ptpool_min = { + .attr = { .name = "ptpool_min", .mode = 0444 }, + .show = sysfs_show_ptpool_min, + .store = NULL, +}; + +static struct kobj_attribute attr_ptpool_chunks = { + .attr = { .name = "ptpool_chunks", .mode = 0444 }, + .show = sysfs_show_ptpool_chunks, + .store = NULL, +}; + +static struct kobj_attribute attr_ptpool_ptsize = { + .attr = { .name = "ptpool_ptsize", .mode = 0444 }, + .show = sysfs_show_ptpool_ptsize, + .store = NULL, +}; + +static struct attribute *ptpool_attrs[] = { + &attr_ptpool_entries.attr, + &attr_ptpool_min.attr, + &attr_ptpool_chunks.attr, + &attr_ptpool_ptsize.attr, + NULL, +}; + +static struct attribute_group ptpool_attr_group = { + .attrs = ptpool_attrs, +}; + +static int +_kgsl_ptpool_add_entries(struct kgsl_ptpool *pool, int count, int dynamic) +{ + struct kgsl_ptpool_chunk *chunk; + size_t size = ALIGN(count * pool->ptsize, PAGE_SIZE); + + BUG_ON(count == 0); + + if (get_order(size) >= MAX_ORDER) { + KGSL_CORE_ERR("ptpool allocation is too big: %d\n", size); + return -EINVAL; + } + + chunk = kzalloc(sizeof(*chunk), GFP_KERNEL); + if (chunk == NULL) { + KGSL_CORE_ERR("kzalloc(%d) failed\n", sizeof(*chunk)); + return -ENOMEM; + } + + chunk->size = size; + chunk->count = count; + chunk->dynamic = dynamic; + + chunk->data = dma_alloc_coherent(NULL, size, + &chunk->phys, GFP_KERNEL); + + if (chunk->data == NULL) { + KGSL_CORE_ERR("dma_alloc_coherent(%d) failed\n", size); + goto err; + } + + chunk->bitmap = kzalloc(BITS_TO_LONGS(count) * 4, GFP_KERNEL); + + if (chunk->bitmap == NULL) { + KGSL_CORE_ERR("kzalloc(%d) failed\n", + BITS_TO_LONGS(count) * 4); + goto err_dma; + } + + list_add_tail(&chunk->list, &pool->list); + + pool->chunks++; + pool->entries += count; + + if (!dynamic) + pool->static_entries += count; + + return 0; + +err_dma: + dma_free_coherent(NULL, chunk->size, chunk->data, chunk->phys); +err: + kfree(chunk); + return -ENOMEM; +} + +static void * +_kgsl_ptpool_get_entry(struct kgsl_ptpool *pool, unsigned int *physaddr) +{ + struct kgsl_ptpool_chunk *chunk; + + list_for_each_entry(chunk, &pool->list, list) { + int bit = find_first_zero_bit(chunk->bitmap, chunk->count); + + if (bit >= chunk->count) + continue; + + set_bit(bit, chunk->bitmap); + *physaddr = chunk->phys + (bit * pool->ptsize); + + return chunk->data + (bit * pool->ptsize); + } + + return NULL; +} + +/** + * kgsl_ptpool_add + * @pool: A pointer to a ptpool structure + * @entries: Number of entries to add + * + * Add static entries to the pagetable pool. + */ + +int +kgsl_ptpool_add(struct kgsl_ptpool *pool, int count) +{ + int ret = 0; + BUG_ON(count == 0); + + mutex_lock(&pool->lock); + + /* Only 4MB can be allocated in one chunk, so larger allocations + need to be split into multiple sections */ + + while (count) { + int entries = ((count * pool->ptsize) > SZ_4M) ? + SZ_4M / pool->ptsize : count; + + /* Add the entries as static, i.e. they don't ever stand + a chance of being removed */ + + ret = _kgsl_ptpool_add_entries(pool, entries, 0); + if (ret) + break; + + count -= entries; + } + + mutex_unlock(&pool->lock); + return ret; +} + +/** + * kgsl_ptpool_alloc + * @pool: A pointer to a ptpool structure + * @addr: A pointer to store the physical address of the chunk + * + * Allocate a pagetable from the pool. Returns the virtual address + * of the pagetable, the physical address is returned in physaddr + */ + +void *kgsl_ptpool_alloc(struct kgsl_ptpool *pool, unsigned int *physaddr) +{ + void *addr = NULL; + int ret; + + mutex_lock(&pool->lock); + addr = _kgsl_ptpool_get_entry(pool, physaddr); + if (addr) + goto done; + + /* Add a chunk for 1 more pagetable and mark it as dynamic */ + ret = _kgsl_ptpool_add_entries(pool, 1, 1); + + if (ret) + goto done; + + addr = _kgsl_ptpool_get_entry(pool, physaddr); +done: + mutex_unlock(&pool->lock); + return addr; +} + +static inline void _kgsl_ptpool_rm_chunk(struct kgsl_ptpool_chunk *chunk) +{ + list_del(&chunk->list); + + if (chunk->data) + dma_free_coherent(NULL, chunk->size, chunk->data, + chunk->phys); + kfree(chunk->bitmap); + kfree(chunk); +} + +/** + * kgsl_ptpool_free + * @pool: A pointer to a ptpool structure + * @addr: A pointer to the virtual address to free + * + * Free a pagetable allocated from the pool + */ + +void kgsl_ptpool_free(struct kgsl_ptpool *pool, void *addr) +{ + struct kgsl_ptpool_chunk *chunk, *tmp; + + if (pool == NULL || addr == NULL) + return; + + mutex_lock(&pool->lock); + list_for_each_entry_safe(chunk, tmp, &pool->list, list) { + if (addr >= chunk->data && + addr < chunk->data + chunk->size) { + int bit = ((unsigned long) (addr - chunk->data)) / + pool->ptsize; + + clear_bit(bit, chunk->bitmap); + memset(addr, 0, pool->ptsize); + + if (chunk->dynamic && + bitmap_empty(chunk->bitmap, chunk->count)) + _kgsl_ptpool_rm_chunk(chunk); + + break; + } + } + + mutex_unlock(&pool->lock); +} + +void kgsl_ptpool_destroy(struct kgsl_ptpool *pool) +{ + struct kgsl_ptpool_chunk *chunk, *tmp; + + if (pool == NULL) + return; + + mutex_lock(&pool->lock); + list_for_each_entry_safe(chunk, tmp, &pool->list, list) + _kgsl_ptpool_rm_chunk(chunk); + mutex_unlock(&pool->lock); + + memset(pool, 0, sizeof(*pool)); +} + +/** + * kgsl_ptpool_init + * @pool: A pointer to a ptpool structure to initialize + * @ptsize: The size of each pagetable entry + * @entries: The number of inital entries to add to the pool + * + * Initalize a pool and allocate an initial chunk of entries. + */ + +int kgsl_ptpool_init(struct kgsl_ptpool *pool, int ptsize, int entries) +{ + int ret = 0; + BUG_ON(ptsize == 0); + + pool->ptsize = ptsize; + mutex_init(&pool->lock); + INIT_LIST_HEAD(&pool->list); + + if (entries) { + ret = kgsl_ptpool_add(pool, entries); + if (ret) + return ret; + } + + return sysfs_create_group(kgsl_driver.ptkobj, &ptpool_attr_group); +} + +static int kgsl_cleanup_pt(struct kgsl_pagetable *pt) +{ + int i; + for (i = 0; i < KGSL_DEVICE_MAX; i++) { + struct kgsl_device *device = kgsl_driver.devp[i]; + if (device) + device->ftbl->cleanup_pt(device, pt); + } + return 0; +} + +static void kgsl_destroy_pagetable(struct kref *kref) +{ + struct kgsl_pagetable *pagetable = container_of(kref, + struct kgsl_pagetable, refcount); + unsigned long flags; + + spin_lock_irqsave(&kgsl_driver.ptlock, flags); + list_del(&pagetable->list); + spin_unlock_irqrestore(&kgsl_driver.ptlock, flags); + + pagetable_remove_sysfs_objects(pagetable); + + kgsl_cleanup_pt(pagetable); + + kgsl_ptpool_free(&kgsl_driver.ptpool, pagetable->base.hostptr); + + kgsl_driver.stats.coherent -= KGSL_PAGETABLE_SIZE; + + if (pagetable->pool) + gen_pool_destroy(pagetable->pool); + + kfree(pagetable->tlbflushfilter.base); + kfree(pagetable); +} + +static inline void kgsl_put_pagetable(struct kgsl_pagetable *pagetable) +{ + if (pagetable) + kref_put(&pagetable->refcount, kgsl_destroy_pagetable); +} + +static struct kgsl_pagetable * +kgsl_get_pagetable(unsigned long name) +{ + struct kgsl_pagetable *pt, *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&kgsl_driver.ptlock, flags); + list_for_each_entry(pt, &kgsl_driver.pagetable_list, list) { + if (pt->name == name) { + ret = pt; + kref_get(&ret->refcount); + break; + } + } + + spin_unlock_irqrestore(&kgsl_driver.ptlock, flags); + return ret; +} + +static struct kgsl_pagetable * +_get_pt_from_kobj(struct kobject *kobj) +{ + unsigned long ptname; + + if (!kobj) + return NULL; + + if (sscanf(kobj->name, "%ld", &ptname) != 1) + return NULL; + + return kgsl_get_pagetable(ptname); +} + +static ssize_t +sysfs_show_entries(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_pagetable *pt; + int ret = 0; + + pt = _get_pt_from_kobj(kobj); + + if (pt) + ret += sprintf(buf, "%d\n", pt->stats.entries); + + kgsl_put_pagetable(pt); + return ret; +} + +static ssize_t +sysfs_show_mapped(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_pagetable *pt; + int ret = 0; + + pt = _get_pt_from_kobj(kobj); + + if (pt) + ret += sprintf(buf, "%d\n", pt->stats.mapped); + + kgsl_put_pagetable(pt); + return ret; +} + +static ssize_t +sysfs_show_va_range(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_pagetable *pt; + int ret = 0; + + pt = _get_pt_from_kobj(kobj); + + if (pt) + ret += sprintf(buf, "0x%x\n", pt->va_range); + + kgsl_put_pagetable(pt); + return ret; +} + +static ssize_t +sysfs_show_max_mapped(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_pagetable *pt; + int ret = 0; + + pt = _get_pt_from_kobj(kobj); + + if (pt) + ret += sprintf(buf, "%d\n", pt->stats.max_mapped); + + kgsl_put_pagetable(pt); + return ret; +} + +static ssize_t +sysfs_show_max_entries(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_pagetable *pt; + int ret = 0; + + pt = _get_pt_from_kobj(kobj); + + if (pt) + ret += sprintf(buf, "%d\n", pt->stats.max_entries); + + kgsl_put_pagetable(pt); + return ret; +} + +static struct kobj_attribute attr_entries = { + .attr = { .name = "entries", .mode = 0444 }, + .show = sysfs_show_entries, + .store = NULL, +}; + +static struct kobj_attribute attr_mapped = { + .attr = { .name = "mapped", .mode = 0444 }, + .show = sysfs_show_mapped, + .store = NULL, +}; + +static struct kobj_attribute attr_va_range = { + .attr = { .name = "va_range", .mode = 0444 }, + .show = sysfs_show_va_range, + .store = NULL, +}; + +static struct kobj_attribute attr_max_mapped = { + .attr = { .name = "max_mapped", .mode = 0444 }, + .show = sysfs_show_max_mapped, + .store = NULL, +}; + +static struct kobj_attribute attr_max_entries = { + .attr = { .name = "max_entries", .mode = 0444 }, + .show = sysfs_show_max_entries, + .store = NULL, +}; + +static struct attribute *pagetable_attrs[] = { + &attr_entries.attr, + &attr_mapped.attr, + &attr_va_range.attr, + &attr_max_mapped.attr, + &attr_max_entries.attr, + NULL, +}; + +static struct attribute_group pagetable_attr_group = { + .attrs = pagetable_attrs, +}; + +static void +pagetable_remove_sysfs_objects(struct kgsl_pagetable *pagetable) +{ + if (pagetable->kobj) + sysfs_remove_group(pagetable->kobj, + &pagetable_attr_group); + + kobject_put(pagetable->kobj); +} + +static int +pagetable_add_sysfs_objects(struct kgsl_pagetable *pagetable) +{ + char ptname[16]; + int ret = -ENOMEM; + + snprintf(ptname, sizeof(ptname), "%d", pagetable->name); + pagetable->kobj = kobject_create_and_add(ptname, + kgsl_driver.ptkobj); + if (pagetable->kobj == NULL) + goto err; + + ret = sysfs_create_group(pagetable->kobj, &pagetable_attr_group); + +err: + if (ret) { + if (pagetable->kobj) + kobject_put(pagetable->kobj); + + pagetable->kobj = NULL; + } + + return ret; +} + +static inline uint32_t +kgsl_pt_entry_get(struct kgsl_pagetable *pt, uint32_t va) +{ + return (va - pt->va_base) >> PAGE_SHIFT; +} + +static inline void +kgsl_pt_map_set(struct kgsl_pagetable *pt, uint32_t pte, uint32_t val) +{ + uint32_t *baseptr = (uint32_t *)pt->base.hostptr; + + writel_relaxed(val, &baseptr[pte]); +} + +static inline uint32_t +kgsl_pt_map_getaddr(struct kgsl_pagetable *pt, uint32_t pte) +{ + uint32_t *baseptr = (uint32_t *)pt->base.hostptr; + uint32_t ret = readl_relaxed(&baseptr[pte]) & GSL_PT_PAGE_ADDR_MASK; + return ret; +} + +void kgsl_mh_intrcallback(struct kgsl_device *device) +{ + unsigned int status = 0; + unsigned int reg; + + kgsl_regread(device, MH_INTERRUPT_STATUS, &status); + kgsl_regread(device, MH_AXI_ERROR, ®); + + if (status & MH_INTERRUPT_MASK__AXI_READ_ERROR) + KGSL_MEM_CRIT(device, "axi read error interrupt: %08x\n", reg); + else if (status & MH_INTERRUPT_MASK__AXI_WRITE_ERROR) + KGSL_MEM_CRIT(device, "axi write error interrupt: %08x\n", reg); + else if (status & MH_INTERRUPT_MASK__MMU_PAGE_FAULT) { + unsigned int ptbase; + struct kgsl_pagetable *pt; + int ptid = -1; + + kgsl_regread(device, MH_MMU_PAGE_FAULT, ®); + kgsl_regread(device, MH_MMU_PT_BASE, &ptbase); + + spin_lock(&kgsl_driver.ptlock); + list_for_each_entry(pt, &kgsl_driver.pagetable_list, list) { + if (ptbase == pt->base.gpuaddr) { + ptid = (int) pt->name; + break; + } + } + spin_unlock(&kgsl_driver.ptlock); + + KGSL_MEM_CRIT(device, + "mmu page fault: page=0x%lx pt=%d op=%s axi=%d\n", + reg & ~(PAGE_SIZE - 1), ptid, + reg & 0x02 ? "WRITE" : "READ", (reg >> 4) & 0xF); + } else + KGSL_MEM_WARN(device, + "bad bits in REG_MH_INTERRUPT_STATUS %08x\n", status); + + kgsl_regwrite(device, MH_INTERRUPT_CLEAR, status); + + /*TODO: figure out how to handle errror interupts. + * specifically, page faults should probably nuke the client that + * caused them, but we don't have enough info to figure that out yet. + */ +} +EXPORT_SYMBOL(kgsl_mh_intrcallback); + +static int kgsl_setup_pt(struct kgsl_pagetable *pt) +{ + int i = 0; + int status = 0; + + for (i = 0; i < KGSL_DEVICE_MAX; i++) { + struct kgsl_device *device = kgsl_driver.devp[i]; + if (device) { + status = device->ftbl->setup_pt(device, pt); + if (status) + goto error_pt; + } + } + return status; +error_pt: + while (i >= 0) { + struct kgsl_device *device = kgsl_driver.devp[i]; + if (device) + device->ftbl->cleanup_pt(device, pt); + i--; + } + return status; +} + +static struct kgsl_pagetable *kgsl_mmu_createpagetableobject( + unsigned int name) +{ + int status = 0; + struct kgsl_pagetable *pagetable = NULL; + unsigned long flags; + + pagetable = kzalloc(sizeof(struct kgsl_pagetable), GFP_KERNEL); + if (pagetable == NULL) { + KGSL_CORE_ERR("kzalloc(%d) failed\n", + sizeof(struct kgsl_pagetable)); + return NULL; + } + + kref_init(&pagetable->refcount); + + spin_lock_init(&pagetable->lock); + pagetable->tlb_flags = 0; + pagetable->name = name; + pagetable->va_base = KGSL_PAGETABLE_BASE; + pagetable->va_range = CONFIG_MSM_KGSL_PAGE_TABLE_SIZE; + pagetable->last_superpte = 0; + pagetable->max_entries = KGSL_PAGETABLE_ENTRIES(pagetable->va_range); + + pagetable->tlbflushfilter.size = (pagetable->va_range / + (PAGE_SIZE * GSL_PT_SUPER_PTE * 8)) + 1; + pagetable->tlbflushfilter.base = (unsigned int *) + kzalloc(pagetable->tlbflushfilter.size, GFP_KERNEL); + if (!pagetable->tlbflushfilter.base) { + KGSL_CORE_ERR("kzalloc(%d) failed\n", + pagetable->tlbflushfilter.size); + goto err_alloc; + } + GSL_TLBFLUSH_FILTER_RESET(); + + pagetable->pool = gen_pool_create(PAGE_SHIFT, -1); + if (pagetable->pool == NULL) { + KGSL_CORE_ERR("gen_pool_create(%d) failed\n", PAGE_SHIFT); + goto err_flushfilter; + } + + if (gen_pool_add(pagetable->pool, pagetable->va_base, + pagetable->va_range, -1)) { + KGSL_CORE_ERR("gen_pool_add failed\n"); + goto err_pool; + } + + pagetable->base.hostptr = kgsl_ptpool_alloc(&kgsl_driver.ptpool, + &pagetable->base.physaddr); + + if (pagetable->base.hostptr == NULL) + goto err_pool; + + /* ptpool allocations are from coherent memory, so update the + device statistics acordingly */ + + KGSL_STATS_ADD(KGSL_PAGETABLE_SIZE, kgsl_driver.stats.coherent, + kgsl_driver.stats.coherent_max); + + pagetable->base.gpuaddr = pagetable->base.physaddr; + pagetable->base.size = KGSL_PAGETABLE_SIZE; + + status = kgsl_setup_pt(pagetable); + if (status) + goto err_free_sharedmem; + + spin_lock_irqsave(&kgsl_driver.ptlock, flags); + list_add(&pagetable->list, &kgsl_driver.pagetable_list); + spin_unlock_irqrestore(&kgsl_driver.ptlock, flags); + + /* Create the sysfs entries */ + pagetable_add_sysfs_objects(pagetable); + + return pagetable; + +err_free_sharedmem: + kgsl_ptpool_free(&kgsl_driver.ptpool, &pagetable->base.hostptr); +err_pool: + gen_pool_destroy(pagetable->pool); +err_flushfilter: + kfree(pagetable->tlbflushfilter.base); +err_alloc: + kfree(pagetable); + + return NULL; +} + +struct kgsl_pagetable *kgsl_mmu_getpagetable(unsigned long name) +{ + struct kgsl_pagetable *pt; + + pt = kgsl_get_pagetable(name); + + if (pt == NULL) + pt = kgsl_mmu_createpagetableobject(name); + + return pt; +} + +void kgsl_mmu_putpagetable(struct kgsl_pagetable *pagetable) +{ + kgsl_put_pagetable(pagetable); +} + +void kgsl_default_setstate(struct kgsl_device *device, uint32_t flags) +{ + if (!kgsl_mmu_enabled()) + return; + + if (flags & KGSL_MMUFLAGS_PTUPDATE) { + kgsl_idle(device, KGSL_TIMEOUT_DEFAULT); + kgsl_regwrite(device, MH_MMU_PT_BASE, + device->mmu.hwpagetable->base.gpuaddr); + } + + if (flags & KGSL_MMUFLAGS_TLBFLUSH) { + /* Invalidate all and tc */ + kgsl_regwrite(device, MH_MMU_INVALIDATE, 0x00000003); + } +} +EXPORT_SYMBOL(kgsl_default_setstate); + +void kgsl_setstate(struct kgsl_device *device, uint32_t flags) +{ + if (device->ftbl->setstate) + device->ftbl->setstate(device, flags); +} +EXPORT_SYMBOL(kgsl_setstate); + +void kgsl_mmu_setstate(struct kgsl_device *device, + struct kgsl_pagetable *pagetable) +{ + struct kgsl_mmu *mmu = &device->mmu; + + if (mmu->flags & KGSL_FLAGS_STARTED) { + /* page table not current, then setup mmu to use new + * specified page table + */ + if (mmu->hwpagetable != pagetable) { + mmu->hwpagetable = pagetable; + spin_lock(&mmu->hwpagetable->lock); + mmu->hwpagetable->tlb_flags &= ~(1<id); + spin_unlock(&mmu->hwpagetable->lock); + + /* call device specific set page table */ + kgsl_setstate(mmu->device, KGSL_MMUFLAGS_TLBFLUSH | + KGSL_MMUFLAGS_PTUPDATE); + } + } +} +EXPORT_SYMBOL(kgsl_mmu_setstate); + +int kgsl_mmu_init(struct kgsl_device *device) +{ + /* + * intialize device mmu + * + * call this with the global lock held + */ + int status = 0; + struct kgsl_mmu *mmu = &device->mmu; + + mmu->device = device; + + /* make sure aligned to pagesize */ + BUG_ON(mmu->mpu_base & (PAGE_SIZE - 1)); + BUG_ON((mmu->mpu_base + mmu->mpu_range) & (PAGE_SIZE - 1)); + + /* sub-client MMU lookups require address translation */ + if ((mmu->config & ~0x1) > 0) { + /*make sure virtual address range is a multiple of 64Kb */ + BUG_ON(CONFIG_MSM_KGSL_PAGE_TABLE_SIZE & ((1 << 16) - 1)); + + /* allocate memory used for completing r/w operations that + * cannot be mapped by the MMU + */ + status = kgsl_allocate_contiguous(&mmu->dummyspace, 64); + if (!status) + kgsl_sharedmem_set(&mmu->dummyspace, 0, 0, + mmu->dummyspace.size); + } + + return status; +} + +int kgsl_mmu_start(struct kgsl_device *device) +{ + /* + * intialize device mmu + * + * call this with the global lock held + */ + + struct kgsl_mmu *mmu = &device->mmu; + + if (mmu->flags & KGSL_FLAGS_STARTED) + return 0; + + /* MMU not enabled */ + if ((mmu->config & 0x1) == 0) + return 0; + + mmu->flags |= KGSL_FLAGS_STARTED; + + /* setup MMU and sub-client behavior */ + kgsl_regwrite(device, MH_MMU_CONFIG, mmu->config); + + /* + * Interrupts are enabled on a per-device level when + * kgsl_pwrctrl_irq() is called + */ + + /* idle device */ + kgsl_idle(device, KGSL_TIMEOUT_DEFAULT); + + /* define physical memory range accessible by the core */ + kgsl_regwrite(device, MH_MMU_MPU_BASE, mmu->mpu_base); + kgsl_regwrite(device, MH_MMU_MPU_END, + mmu->mpu_base + mmu->mpu_range); + + /* sub-client MMU lookups require address translation */ + if ((mmu->config & ~0x1) > 0) { + + kgsl_sharedmem_set(&mmu->dummyspace, 0, 0, + mmu->dummyspace.size); + + /* TRAN_ERROR needs a 32 byte (32 byte aligned) chunk of memory + * to complete transactions in case of an MMU fault. Note that + * we'll leave the bottom 32 bytes of the dummyspace for other + * purposes (e.g. use it when dummy read cycles are needed + * for other blocks */ + kgsl_regwrite(device, MH_MMU_TRAN_ERROR, + mmu->dummyspace.physaddr + 32); + + if (mmu->defaultpagetable == NULL) + mmu->defaultpagetable = + kgsl_mmu_getpagetable(KGSL_MMU_GLOBAL_PT); + + /* Return error if the default pagetable doesn't exist */ + if (mmu->defaultpagetable == NULL) + return -ENOMEM; + + mmu->hwpagetable = mmu->defaultpagetable; + + kgsl_regwrite(device, MH_MMU_PT_BASE, + mmu->hwpagetable->base.gpuaddr); + kgsl_regwrite(device, MH_MMU_VA_RANGE, + (mmu->hwpagetable->va_base | + (mmu->hwpagetable->va_range >> 16))); + kgsl_setstate(device, KGSL_MMUFLAGS_TLBFLUSH); + } + + return 0; +} +EXPORT_SYMBOL(kgsl_mmu_start); + +unsigned int kgsl_virtaddr_to_physaddr(void *virtaddr) +{ + unsigned int physaddr = 0; + pgd_t *pgd_ptr = NULL; + pmd_t *pmd_ptr = NULL; + pte_t *pte_ptr = NULL, pte; + + pgd_ptr = pgd_offset(current->mm, (unsigned long) virtaddr); + if (pgd_none(*pgd) || pgd_bad(*pgd)) { + KGSL_CORE_ERR("Invalid pgd entry\n"); + return 0; + } + + pmd_ptr = pmd_offset(pgd_ptr, (unsigned long) virtaddr); + if (pmd_none(*pmd_ptr) || pmd_bad(*pmd_ptr)) { + KGSL_CORE_ERR("Invalid pmd entry\n"); + return 0; + } + + pte_ptr = pte_offset_map(pmd_ptr, (unsigned long) virtaddr); + if (!pte_ptr) { + KGSL_CORE_ERR("pt_offset_map failed\n"); + return 0; + } + pte = *pte_ptr; + physaddr = pte_pfn(pte); + pte_unmap(pte_ptr); + physaddr <<= PAGE_SHIFT; + return physaddr; +} + +int +kgsl_mmu_map(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc, + unsigned int protflags) +{ + int numpages; + unsigned int pte, ptefirst, ptelast, physaddr; + int flushtlb; + unsigned int offset = 0; + + BUG_ON(protflags & ~(GSL_PT_PAGE_RV | GSL_PT_PAGE_WV)); + BUG_ON(protflags == 0); + + memdesc->gpuaddr = gen_pool_alloc_aligned(pagetable->pool, + memdesc->size, KGSL_MMU_ALIGN_SHIFT); + + if (memdesc->gpuaddr == 0) { + KGSL_CORE_ERR("gen_pool_alloc(%d) failed\n", memdesc->size); + KGSL_CORE_ERR(" [%d] allocated=%d, entries=%d\n", + pagetable->name, pagetable->stats.mapped, + pagetable->stats.entries); + return -ENOMEM; + } + + numpages = (memdesc->size >> PAGE_SHIFT); + + ptefirst = kgsl_pt_entry_get(pagetable, memdesc->gpuaddr); + ptelast = ptefirst + numpages; + + pte = ptefirst; + flushtlb = 0; + + /* tlb needs to be flushed when the first and last pte are not at + * superpte boundaries */ + if ((ptefirst & (GSL_PT_SUPER_PTE - 1)) != 0 || + ((ptelast + 1) & (GSL_PT_SUPER_PTE-1)) != 0) + flushtlb = 1; + + spin_lock(&pagetable->lock); + for (pte = ptefirst; pte < ptelast; pte++, offset += PAGE_SIZE) { +#ifdef VERBOSE_DEBUG + /* check if PTE exists */ + uint32_t val = kgsl_pt_map_getaddr(pagetable, pte); + BUG_ON(val != 0 && val != GSL_PT_PAGE_DIRTY); +#endif + if ((pte & (GSL_PT_SUPER_PTE-1)) == 0) + if (GSL_TLBFLUSH_FILTER_ISDIRTY(pte / GSL_PT_SUPER_PTE)) + flushtlb = 1; + /* mark pte as in use */ + + physaddr = memdesc->ops->physaddr(memdesc, offset); + BUG_ON(physaddr == 0); + kgsl_pt_map_set(pagetable, pte, physaddr | protflags); + } + + /* Keep track of the statistics for the sysfs files */ + + KGSL_STATS_ADD(1, pagetable->stats.entries, + pagetable->stats.max_entries); + + KGSL_STATS_ADD(memdesc->size, pagetable->stats.mapped, + pagetable->stats.max_mapped); + + /* Post all writes to the pagetable */ + wmb(); + + /* Invalidate tlb only if current page table used by GPU is the + * pagetable that we used to allocate */ + if (flushtlb) { + /*set all devices as needing flushing*/ + pagetable->tlb_flags = UINT_MAX; + GSL_TLBFLUSH_FILTER_RESET(); + } + spin_unlock(&pagetable->lock); + + return 0; +} + +int +kgsl_mmu_unmap(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc) +{ + unsigned int numpages; + unsigned int pte, ptefirst, ptelast, superpte; + unsigned int range = memdesc->size; + + /* All GPU addresses as assigned are page aligned, but some + functions purturb the gpuaddr with an offset, so apply the + mask here to make sure we have the right address */ + + unsigned int gpuaddr = memdesc->gpuaddr & KGSL_MMU_ALIGN_MASK; + + if (range == 0 || gpuaddr == 0) + return 0; + + numpages = (range >> PAGE_SHIFT); + if (range & (PAGE_SIZE - 1)) + numpages++; + + ptefirst = kgsl_pt_entry_get(pagetable, gpuaddr); + ptelast = ptefirst + numpages; + + spin_lock(&pagetable->lock); + superpte = ptefirst - (ptefirst & (GSL_PT_SUPER_PTE-1)); + GSL_TLBFLUSH_FILTER_SETDIRTY(superpte / GSL_PT_SUPER_PTE); + for (pte = ptefirst; pte < ptelast; pte++) { +#ifdef VERBOSE_DEBUG + /* check if PTE exists */ + BUG_ON(!kgsl_pt_map_getaddr(pagetable, pte)); +#endif + kgsl_pt_map_set(pagetable, pte, GSL_PT_PAGE_DIRTY); + superpte = pte - (pte & (GSL_PT_SUPER_PTE - 1)); + if (pte == superpte) + GSL_TLBFLUSH_FILTER_SETDIRTY(superpte / + GSL_PT_SUPER_PTE); + } + + /* Remove the statistics */ + pagetable->stats.entries--; + pagetable->stats.mapped -= range; + + /* Post all writes to the pagetable */ + wmb(); + + spin_unlock(&pagetable->lock); + + gen_pool_free(pagetable->pool, gpuaddr, range); + + return 0; +} +EXPORT_SYMBOL(kgsl_mmu_unmap); + +int kgsl_mmu_map_global(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc, unsigned int protflags) +{ + int result = -EINVAL; + unsigned int gpuaddr = 0; + + if (memdesc == NULL) { + KGSL_CORE_ERR("invalid memdesc\n"); + goto error; + } + + gpuaddr = memdesc->gpuaddr; + + result = kgsl_mmu_map(pagetable, memdesc, protflags); + if (result) + goto error; + + /*global mappings must have the same gpu address in all pagetables*/ + if (gpuaddr && gpuaddr != memdesc->gpuaddr) { + KGSL_CORE_ERR("pt %p addr mismatch phys 0x%08x" + "gpu 0x%0x 0x%08x", pagetable, memdesc->physaddr, + gpuaddr, memdesc->gpuaddr); + goto error_unmap; + } + return result; +error_unmap: + kgsl_mmu_unmap(pagetable, memdesc); +error: + return result; +} +EXPORT_SYMBOL(kgsl_mmu_map_global); + +int kgsl_mmu_stop(struct kgsl_device *device) +{ + /* + * stop device mmu + * + * call this with the global lock held + */ + struct kgsl_mmu *mmu = &device->mmu; + + if (mmu->flags & KGSL_FLAGS_STARTED) { + /* disable MMU */ + kgsl_regwrite(device, MH_MMU_CONFIG, 0x00000000); + + mmu->flags &= ~KGSL_FLAGS_STARTED; + } + + return 0; +} +EXPORT_SYMBOL(kgsl_mmu_stop); + +int kgsl_mmu_close(struct kgsl_device *device) +{ + /* + * close device mmu + * + * call this with the global lock held + */ + struct kgsl_mmu *mmu = &device->mmu; + + if (mmu->dummyspace.gpuaddr) + kgsl_sharedmem_free(&mmu->dummyspace); + + if (mmu->defaultpagetable) + kgsl_mmu_putpagetable(mmu->defaultpagetable); + + return 0; +} diff --git a/drivers/gpu/msm/kgsl_mmu.h b/drivers/gpu/msm/kgsl_mmu.h new file mode 100644 index 00000000000..34252770392 --- /dev/null +++ b/drivers/gpu/msm/kgsl_mmu.h @@ -0,0 +1,273 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __KGSL_MMU_H +#define __KGSL_MMU_H + +/* Identifier for the global page table */ +/* Per process page tables will probably pass in the thread group + as an identifier */ + +#define KGSL_MMU_GLOBAL_PT 0 + +#define GSL_PT_SUPER_PTE 8 +#define GSL_PT_PAGE_WV 0x00000001 +#define GSL_PT_PAGE_RV 0x00000002 +#define GSL_PT_PAGE_DIRTY 0x00000004 + +/* MMU registers - the register locations for all cores are the + same. The method for getting to those locations differs between + 2D and 3D, but the 2D and 3D register functions do that magic + for us */ + +#define MH_MMU_CONFIG 0x0040 +#define MH_MMU_VA_RANGE 0x0041 +#define MH_MMU_PT_BASE 0x0042 +#define MH_MMU_PAGE_FAULT 0x0043 +#define MH_MMU_TRAN_ERROR 0x0044 +#define MH_MMU_INVALIDATE 0x0045 +#define MH_MMU_MPU_BASE 0x0046 +#define MH_MMU_MPU_END 0x0047 + +#define MH_INTERRUPT_MASK 0x0A42 +#define MH_INTERRUPT_STATUS 0x0A43 +#define MH_INTERRUPT_CLEAR 0x0A44 +#define MH_AXI_ERROR 0x0A45 + +/* MH_MMU_CONFIG bit definitions */ + +#define MH_MMU_CONFIG__RB_W_CLNT_BEHAVIOR__SHIFT 0x00000004 +#define MH_MMU_CONFIG__CP_W_CLNT_BEHAVIOR__SHIFT 0x00000006 +#define MH_MMU_CONFIG__CP_R0_CLNT_BEHAVIOR__SHIFT 0x00000008 +#define MH_MMU_CONFIG__CP_R1_CLNT_BEHAVIOR__SHIFT 0x0000000a +#define MH_MMU_CONFIG__CP_R2_CLNT_BEHAVIOR__SHIFT 0x0000000c +#define MH_MMU_CONFIG__CP_R3_CLNT_BEHAVIOR__SHIFT 0x0000000e +#define MH_MMU_CONFIG__CP_R4_CLNT_BEHAVIOR__SHIFT 0x00000010 +#define MH_MMU_CONFIG__VGT_R0_CLNT_BEHAVIOR__SHIFT 0x00000012 +#define MH_MMU_CONFIG__VGT_R1_CLNT_BEHAVIOR__SHIFT 0x00000014 +#define MH_MMU_CONFIG__TC_R_CLNT_BEHAVIOR__SHIFT 0x00000016 +#define MH_MMU_CONFIG__PA_W_CLNT_BEHAVIOR__SHIFT 0x00000018 + +/* MMU Flags */ +#define KGSL_MMUFLAGS_TLBFLUSH 0x10000000 +#define KGSL_MMUFLAGS_PTUPDATE 0x20000000 + +#define MH_INTERRUPT_MASK__AXI_READ_ERROR 0x00000001L +#define MH_INTERRUPT_MASK__AXI_WRITE_ERROR 0x00000002L +#define MH_INTERRUPT_MASK__MMU_PAGE_FAULT 0x00000004L + +#ifdef CONFIG_MSM_KGSL_MMU +#define KGSL_MMU_INT_MASK \ + (MH_INTERRUPT_MASK__AXI_READ_ERROR | \ + MH_INTERRUPT_MASK__AXI_WRITE_ERROR | \ + MH_INTERRUPT_MASK__MMU_PAGE_FAULT) +#else +#define KGSL_MMU_INT_MASK \ + (MH_INTERRUPT_MASK__AXI_READ_ERROR | \ + MH_INTERRUPT_MASK__AXI_WRITE_ERROR) +#endif + +/* Macros to manage TLB flushing */ +#define GSL_TLBFLUSH_FILTER_ENTRY_NUMBITS (sizeof(unsigned char) * 8) +#define GSL_TLBFLUSH_FILTER_GET(superpte) \ + (*((unsigned char *) \ + (((unsigned int)pagetable->tlbflushfilter.base) \ + + (superpte / GSL_TLBFLUSH_FILTER_ENTRY_NUMBITS)))) +#define GSL_TLBFLUSH_FILTER_SETDIRTY(superpte) \ + (GSL_TLBFLUSH_FILTER_GET((superpte)) |= 1 << \ + (superpte % GSL_TLBFLUSH_FILTER_ENTRY_NUMBITS)) +#define GSL_TLBFLUSH_FILTER_ISDIRTY(superpte) \ + (GSL_TLBFLUSH_FILTER_GET((superpte)) & \ + (1 << (superpte % GSL_TLBFLUSH_FILTER_ENTRY_NUMBITS))) +#define GSL_TLBFLUSH_FILTER_RESET() memset(pagetable->tlbflushfilter.base,\ + 0, pagetable->tlbflushfilter.size) + + +struct kgsl_device; + +struct kgsl_tlbflushfilter { + unsigned int *base; + unsigned int size; +}; + +struct kgsl_pagetable { + spinlock_t lock; + struct kref refcount; + struct kgsl_memdesc base; + uint32_t va_base; + unsigned int va_range; + unsigned int last_superpte; + unsigned int max_entries; + struct gen_pool *pool; + struct list_head list; + unsigned int name; + /* Maintain filter to manage tlb flushing */ + struct kgsl_tlbflushfilter tlbflushfilter; + unsigned int tlb_flags; + struct kobject *kobj; + + struct { + unsigned int entries; + unsigned int mapped; + unsigned int max_mapped; + unsigned int max_entries; + } stats; +}; + +struct kgsl_mmu { + unsigned int refcnt; + uint32_t flags; + struct kgsl_device *device; + unsigned int config; + uint32_t mpu_base; + int mpu_range; + struct kgsl_memdesc dummyspace; + /* current page table object being used by device mmu */ + struct kgsl_pagetable *defaultpagetable; + struct kgsl_pagetable *hwpagetable; +}; + +struct kgsl_ptpool_chunk { + size_t size; + unsigned int count; + int dynamic; + + void *data; + unsigned int phys; + + unsigned long *bitmap; + struct list_head list; +}; + +struct kgsl_pagetable *kgsl_mmu_getpagetable(unsigned long name); + +#ifdef CONFIG_MSM_KGSL_MMU + +int kgsl_mmu_init(struct kgsl_device *device); +int kgsl_mmu_start(struct kgsl_device *device); +int kgsl_mmu_stop(struct kgsl_device *device); +int kgsl_mmu_close(struct kgsl_device *device); +void kgsl_mmu_setstate(struct kgsl_device *device, + struct kgsl_pagetable *pagetable); +int kgsl_mmu_map(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc, + unsigned int protflags); +int kgsl_mmu_map_global(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc, unsigned int protflags); +int kgsl_mmu_unmap(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc); +void kgsl_ptpool_destroy(struct kgsl_ptpool *pool); +int kgsl_ptpool_init(struct kgsl_ptpool *pool, int ptsize, int entries); +void kgsl_mh_intrcallback(struct kgsl_device *device); +void kgsl_mmu_putpagetable(struct kgsl_pagetable *pagetable); +unsigned int kgsl_virtaddr_to_physaddr(void *virtaddr); +void kgsl_setstate(struct kgsl_device *device, uint32_t flags); +void kgsl_default_setstate(struct kgsl_device *device, uint32_t flags); + +static inline int kgsl_mmu_enabled(void) +{ + return 1; +} + +#else + +static inline int kgsl_mmu_enabled(void) +{ + return 0; +} + +static inline int kgsl_mmu_init(struct kgsl_device *device) +{ + return 0; +} + +static inline int kgsl_mmu_start(struct kgsl_device *device) +{ + return 0; +} + +static inline int kgsl_mmu_stop(struct kgsl_device *device) +{ + return 0; +} + +static inline int kgsl_mmu_close(struct kgsl_device *device) +{ + return 0; +} + +static inline void kgsl_mmu_setstate(struct kgsl_device *device, + struct kgsl_pagetable *pagetable) { } + +static inline int kgsl_mmu_map(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc, + unsigned int protflags) +{ + memdesc->gpuaddr = memdesc->physaddr; + return 0; +} + +static inline int kgsl_mmu_unmap(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc) +{ + return 0; +} + +static inline int kgsl_ptpool_init(struct kgsl_ptpool *pool, int ptsize, + int entries) +{ + return 0; +} + +static inline int kgsl_mmu_map_global(struct kgsl_pagetable *pagetable, + struct kgsl_memdesc *memdesc, unsigned int protflags) +{ + memdesc->gpuaddr = memdesc->physaddr; + return 0; +} + +static inline void kgsl_ptpool_destroy(struct kgsl_ptpool *pool) { } + +static inline void kgsl_mh_intrcallback(struct kgsl_device *device) { } + +static inline void kgsl_mmu_putpagetable(struct kgsl_pagetable *pagetable) { } + +static inline unsigned int kgsl_virtaddr_to_physaddr(void *virtaddr) +{ + return 0; +} + +static inline void kgsl_setstate(struct kgsl_device *device, uint32_t flags) +{ } + +static inline void kgsl_default_setstate(struct kgsl_device *device, + uint32_t flags) { } +#endif + +static inline unsigned int kgsl_pt_get_flags(struct kgsl_pagetable *pt, + enum kgsl_deviceid id) +{ + unsigned int result = 0; + + if (pt == NULL) + return 0; + + spin_lock(&pt->lock); + if (pt->tlb_flags && (1<tlb_flags &= ~(1<lock); + return result; +} + +#endif /* __KGSL_MMU_H */ diff --git a/drivers/gpu/msm/kgsl_pwrctrl.c b/drivers/gpu/msm/kgsl_pwrctrl.c new file mode 100644 index 00000000000..572e0e8e8ab --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrctrl.c @@ -0,0 +1,643 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_pwrscale.h" +#include "kgsl_device.h" + +#define GPU_SWFI_LATENCY 3 + +void kgsl_pwrctrl_pwrlevel_change(struct kgsl_device *device, + unsigned int new_level) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + if (new_level < (pwr->num_pwrlevels - 1) && + new_level >= pwr->thermal_pwrlevel && + new_level != pwr->active_pwrlevel) { + pwr->active_pwrlevel = new_level; + if (test_bit(KGSL_PWRFLAGS_CLK_ON, &pwr->power_flags)) + clk_set_rate(pwr->grp_clks[0], + pwr->pwrlevels[pwr->active_pwrlevel]. + gpu_freq); + if (test_bit(KGSL_PWRFLAGS_AXI_ON, &pwr->power_flags)) + if (pwr->pcl) + msm_bus_scale_client_update_request(pwr->pcl, + pwr->pwrlevels[pwr->active_pwrlevel]. + bus_freq); + KGSL_PWR_WARN(device, "kgsl pwr level changed to %d\n", + pwr->active_pwrlevel); + } +} +EXPORT_SYMBOL(kgsl_pwrctrl_pwrlevel_change); + +static int __gpuclk_store(int max, struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ int ret, i, delta = 5000000; + unsigned long val; + struct kgsl_device *device = kgsl_device_from_dev(dev); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + + ret = sscanf(buf, "%ld", &val); + if (ret != 1) + return count; + + mutex_lock(&device->mutex); + for (i = 0; i < pwr->num_pwrlevels; i++) { + if (abs(pwr->pwrlevels[i].gpu_freq - val) < delta) { + if (max) + pwr->thermal_pwrlevel = i; + break; + } + } + + if (i == pwr->num_pwrlevels) + goto done; + + /* + * If the current or requested clock speed is greater than the + * thermal limit, bump down immediately. + */ + + if (pwr->pwrlevels[pwr->active_pwrlevel].gpu_freq > + pwr->pwrlevels[pwr->thermal_pwrlevel].gpu_freq) + kgsl_pwrctrl_pwrlevel_change(device, pwr->thermal_pwrlevel); + else if (!max) + kgsl_pwrctrl_pwrlevel_change(device, i); + +done: + mutex_unlock(&device->mutex); + return count; +} + +static int kgsl_pwrctrl_max_gpuclk_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return __gpuclk_store(1, dev, attr, buf, count); +} + +static int kgsl_pwrctrl_max_gpuclk_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kgsl_device *device = kgsl_device_from_dev(dev); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + return snprintf(buf, PAGE_SIZE, "%d\n", + pwr->pwrlevels[pwr->thermal_pwrlevel].gpu_freq); +} + +static int kgsl_pwrctrl_gpuclk_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return __gpuclk_store(0, dev, attr, buf, count); +} + +static int kgsl_pwrctrl_gpuclk_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kgsl_device *device = kgsl_device_from_dev(dev); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + return snprintf(buf, PAGE_SIZE, "%d\n", + pwr->pwrlevels[pwr->active_pwrlevel].gpu_freq); +} + +static int kgsl_pwrctrl_pwrnap_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char temp[20]; + unsigned long val; + struct kgsl_device *device = kgsl_device_from_dev(dev); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + int rc; + + snprintf(temp, sizeof(temp), "%.*s", + (int)min(count, sizeof(temp) - 1), buf); + rc = strict_strtoul(temp, 0, &val); + if (rc) + return rc; + + mutex_lock(&device->mutex); + + if (val == 1) + pwr->nap_allowed = true; + else if (val == 0) + pwr->nap_allowed = false; + + mutex_unlock(&device->mutex); + + return count; +} + +static int kgsl_pwrctrl_pwrnap_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kgsl_device *device = kgsl_device_from_dev(dev); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + return sprintf(buf, "%d\n", pwr->nap_allowed); +} + + +static int kgsl_pwrctrl_idle_timer_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + char temp[20]; + unsigned long val; + struct kgsl_device *device = kgsl_device_from_dev(dev); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + const long div = 1000/HZ; + static unsigned int org_interval_timeout = 1; + int rc; + + snprintf(temp, sizeof(temp), "%.*s", + (int)min(count, sizeof(temp) - 1), buf); + rc = strict_strtoul(temp, 0, &val); + if (rc) + return rc; + + if (org_interval_timeout == 1) + org_interval_timeout = pwr->interval_timeout; + + mutex_lock(&device->mutex); + + /* Let the timeout be requested in ms, but convert to jiffies. */ + val /= div; + if (val >= org_interval_timeout) + pwr->interval_timeout = val; + + mutex_unlock(&device->mutex); + + return count; +} + +static int kgsl_pwrctrl_idle_timer_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kgsl_device *device = kgsl_device_from_dev(dev); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + return sprintf(buf, "%d\n", pwr->interval_timeout); +} + +DEVICE_ATTR(gpuclk, 0644, kgsl_pwrctrl_gpuclk_show, kgsl_pwrctrl_gpuclk_store); +DEVICE_ATTR(max_gpuclk, 0644, kgsl_pwrctrl_max_gpuclk_show, + kgsl_pwrctrl_max_gpuclk_store); +DEVICE_ATTR(pwrnap, 0644, kgsl_pwrctrl_pwrnap_show, kgsl_pwrctrl_pwrnap_store); +DEVICE_ATTR(idle_timer, 0644, kgsl_pwrctrl_idle_timer_show, + kgsl_pwrctrl_idle_timer_store); + +static const struct device_attribute *pwrctrl_attr_list[] = { + &dev_attr_gpuclk, + &dev_attr_max_gpuclk, + &dev_attr_pwrnap, + &dev_attr_idle_timer, + NULL +}; + +int kgsl_pwrctrl_init_sysfs(struct kgsl_device *device) +{ + return kgsl_create_device_sysfs_files(device->dev, pwrctrl_attr_list); +} + +void kgsl_pwrctrl_uninit_sysfs(struct kgsl_device *device) +{ + kgsl_remove_device_sysfs_files(device->dev, pwrctrl_attr_list); +} + +void kgsl_pwrctrl_clk(struct kgsl_device *device, int state) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + int i = 0; + if (state == KGSL_PWRFLAGS_OFF) { + if (test_and_clear_bit(KGSL_PWRFLAGS_CLK_ON, + &pwr->power_flags)) { + KGSL_PWR_INFO(device, + "clocks off, device %d\n", device->id); + for (i = KGSL_MAX_CLKS - 1; i > 0; i--) + if (pwr->grp_clks[i]) + clk_disable(pwr->grp_clks[i]); + if ((pwr->pwrlevels[0].gpu_freq > 0) && + (device->requested_state != KGSL_STATE_NAP)) + clk_set_rate(pwr->grp_clks[0], + pwr->pwrlevels[pwr->num_pwrlevels - 1]. + gpu_freq); + } + } else if (state == KGSL_PWRFLAGS_ON) { + if (!test_and_set_bit(KGSL_PWRFLAGS_CLK_ON, + &pwr->power_flags)) { + KGSL_PWR_INFO(device, + "clocks on, device %d\n", device->id); + + if ((pwr->pwrlevels[0].gpu_freq > 0) && + (device->state != KGSL_STATE_NAP)) + clk_set_rate(pwr->grp_clks[0], + pwr->pwrlevels[pwr->active_pwrlevel]. + gpu_freq); + + /* as last step, enable grp_clk + this is to let GPU interrupt to come */ + for (i = KGSL_MAX_CLKS - 1; i > 0; i--) + if (pwr->grp_clks[i]) + clk_enable(pwr->grp_clks[i]); + } + } +} +EXPORT_SYMBOL(kgsl_pwrctrl_clk); + +void kgsl_pwrctrl_axi(struct kgsl_device *device, int state) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + + if (state == KGSL_PWRFLAGS_OFF) { + if (test_and_clear_bit(KGSL_PWRFLAGS_AXI_ON, + &pwr->power_flags)) { + KGSL_PWR_INFO(device, + "axi off, device %d\n", device->id); + if (pwr->ebi1_clk) + clk_disable(pwr->ebi1_clk); + if (pwr->pcl) + msm_bus_scale_client_update_request(pwr->pcl, + 0); + } + } else if (state == KGSL_PWRFLAGS_ON) { + if (!test_and_set_bit(KGSL_PWRFLAGS_AXI_ON, + &pwr->power_flags)) { + KGSL_PWR_INFO(device, + "axi on, device %d\n", device->id); + if (pwr->ebi1_clk) + clk_enable(pwr->ebi1_clk); + if (pwr->pcl) + msm_bus_scale_client_update_request(pwr->pcl, + pwr->pwrlevels[pwr->active_pwrlevel]. + bus_freq); + } + } +} +EXPORT_SYMBOL(kgsl_pwrctrl_axi); + + +void kgsl_pwrctrl_pwrrail(struct kgsl_device *device, int state) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + + if (state == KGSL_PWRFLAGS_OFF) { + if (test_and_clear_bit(KGSL_PWRFLAGS_POWER_ON, + &pwr->power_flags)) { + KGSL_PWR_INFO(device, + "power off, device %d\n", device->id); + if (pwr->gpu_reg) + regulator_disable(pwr->gpu_reg); + } + } else if (state == KGSL_PWRFLAGS_ON) { + if (!test_and_set_bit(KGSL_PWRFLAGS_POWER_ON, + &pwr->power_flags)) { + KGSL_PWR_INFO(device, + "power on, device %d\n", device->id); + if (pwr->gpu_reg) + regulator_enable(pwr->gpu_reg); + } + } +} +EXPORT_SYMBOL(kgsl_pwrctrl_pwrrail); + +void kgsl_pwrctrl_irq(struct kgsl_device *device, int state) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + + if (state == KGSL_PWRFLAGS_ON) { + if (!test_and_set_bit(KGSL_PWRFLAGS_IRQ_ON, + &pwr->power_flags)) { + KGSL_PWR_INFO(device, + "irq on, device %d\n", device->id); + enable_irq(pwr->interrupt_num); + device->ftbl->irqctrl(device, 1); + } + } else if (state == KGSL_PWRFLAGS_OFF) { + if (test_and_clear_bit(KGSL_PWRFLAGS_IRQ_ON, + &pwr->power_flags)) { + KGSL_PWR_INFO(device, + "irq off, device %d\n", device->id); + device->ftbl->irqctrl(device, 0); + disable_irq(pwr->interrupt_num); + } + } +} +EXPORT_SYMBOL(kgsl_pwrctrl_irq); + +int kgsl_pwrctrl_init(struct kgsl_device *device) +{ + int i, result = 0; + struct clk *clk; + struct platform_device *pdev = + container_of(device->parentdev, struct platform_device, dev); + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + struct kgsl_device_platform_data *pdata_dev = pdev->dev.platform_data; + struct kgsl_device_pwr_data *pdata_pwr = &pdata_dev->pwr_data; + const char *clk_names[KGSL_MAX_CLKS] = {pwr->src_clk_name, + pdata_dev->clk.name.clk, + pdata_dev->clk.name.pclk, + pdata_dev->imem_clk_name.clk, + pdata_dev->imem_clk_name.pclk}; + + /*acquire clocks */ + for (i = 1; i < KGSL_MAX_CLKS; i++) { + if (clk_names[i]) { + clk = clk_get(&pdev->dev, clk_names[i]); + if (IS_ERR(clk)) + goto clk_err; + pwr->grp_clks[i] = clk; + } + } + /* Make sure we have a source clk for freq setting */ + clk = clk_get(&pdev->dev, clk_names[0]); + pwr->grp_clks[0] = (IS_ERR(clk)) ? pwr->grp_clks[1] : clk; + + /* put the AXI bus into asynchronous mode with the graphics cores */ + if (pdata_pwr->set_grp_async != NULL) + pdata_pwr->set_grp_async(); + + if (pdata_pwr->num_levels > KGSL_MAX_PWRLEVELS) { + KGSL_PWR_ERR(device, "invalid power level count: %d\n", + pdata_pwr->num_levels); + result = -EINVAL; + goto done; + } + pwr->num_pwrlevels = pdata_pwr->num_levels; + pwr->active_pwrlevel = pdata_pwr->init_level; + for (i = 0; i < pdata_pwr->num_levels; i++) { + pwr->pwrlevels[i].gpu_freq = + (pdata_pwr->pwrlevel[i].gpu_freq > 0) ? + clk_round_rate(pwr->grp_clks[0], + pdata_pwr->pwrlevel[i]. + gpu_freq) : 0; + pwr->pwrlevels[i].bus_freq = + pdata_pwr->pwrlevel[i].bus_freq; + } + /* Do not set_rate for targets in sync with AXI */ + if (pwr->pwrlevels[0].gpu_freq > 0) + clk_set_rate(pwr->grp_clks[0], pwr-> + pwrlevels[pwr->num_pwrlevels - 1].gpu_freq); + + pwr->gpu_reg = regulator_get(NULL, pwr->regulator_name); + if (IS_ERR(pwr->gpu_reg)) + pwr->gpu_reg = NULL; + + pwr->power_flags = 0; + + pwr->nap_allowed = pdata_pwr->nap_allowed; + pwr->interval_timeout = pdata_pwr->idle_timeout; + pwr->ebi1_clk = clk_get(NULL, "ebi1_kgsl_clk"); + if (IS_ERR(pwr->ebi1_clk)) + pwr->ebi1_clk = NULL; + else + clk_set_rate(pwr->ebi1_clk, + pwr->pwrlevels[pwr->active_pwrlevel]. + bus_freq); + if (pdata_dev->clk.bus_scale_table != NULL) { + pwr->pcl = + msm_bus_scale_register_client(pdata_dev->clk. + bus_scale_table); + if (!pwr->pcl) { + KGSL_PWR_ERR(device, + "msm_bus_scale_register_client failed: " + "id %d table %p", device->id, + pdata_dev->clk.bus_scale_table); + result = -EINVAL; + goto done; + } + } + + /*acquire interrupt */ + pwr->interrupt_num = + platform_get_irq_byname(pdev, pwr->irq_name); + + if (pwr->interrupt_num <= 0) { + KGSL_PWR_ERR(device, "platform_get_irq_byname failed: %d\n", + pwr->interrupt_num); + result = -EINVAL; + goto done; + } + + register_early_suspend(&device->display_off); + return result; + +clk_err: + result = PTR_ERR(clk); + KGSL_PWR_ERR(device, "clk_get(%s) failed: %d\n", + clk_names[i], result); + +done: + return result; +} + +void kgsl_pwrctrl_close(struct kgsl_device *device) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + int i; + + KGSL_PWR_INFO(device, "close device %d\n", device->id); + + unregister_early_suspend(&device->display_off); + + if (pwr->interrupt_num > 0) { + if (pwr->have_irq) { + free_irq(pwr->interrupt_num, NULL); + pwr->have_irq = 0; + } + pwr->interrupt_num = 0; + } + + clk_put(pwr->ebi1_clk); + + if (pwr->pcl) + msm_bus_scale_unregister_client(pwr->pcl); + + pwr->pcl = 0; + + if (pwr->gpu_reg) { + regulator_put(pwr->gpu_reg); + pwr->gpu_reg = NULL; + } + + for (i = 1; i < KGSL_MAX_CLKS; i++) + if (pwr->grp_clks[i]) { + clk_put(pwr->grp_clks[i]); + pwr->grp_clks[i] = NULL; + } + + pwr->grp_clks[0] = NULL; + pwr->power_flags = 0; +} + +void kgsl_idle_check(struct work_struct *work) +{ + struct kgsl_device *device = container_of(work, struct kgsl_device, + idle_check_ws); + + mutex_lock(&device->mutex); + if (device->requested_state != KGSL_STATE_SLEEP) + kgsl_pwrscale_idle(device); + + if (device->state & (KGSL_STATE_ACTIVE | KGSL_STATE_NAP)) { + if (kgsl_pwrctrl_sleep(device) != 0) + mod_timer(&device->idle_timer, + jiffies + + device->pwrctrl.interval_timeout); + } else if (device->state & (KGSL_STATE_HUNG | + KGSL_STATE_DUMP_AND_RECOVER)) { + device->requested_state = KGSL_STATE_NONE; + } + + mutex_unlock(&device->mutex); +} + +void kgsl_timer(unsigned long data) +{ + struct kgsl_device *device = (struct kgsl_device *) data; + + KGSL_PWR_INFO(device, "idle timer expired device %d\n", device->id); + if (device->requested_state != KGSL_STATE_SUSPEND) { + device->requested_state = KGSL_STATE_SLEEP; + /* Have work run in a non-interrupt context. */ + queue_work(device->work_queue, &device->idle_check_ws); + } +} + +void kgsl_pre_hwaccess(struct kgsl_device *device) +{ + BUG_ON(!mutex_is_locked(&device->mutex)); + if (device->state & (KGSL_STATE_SLEEP | KGSL_STATE_NAP)) + kgsl_pwrctrl_wake(device); +} +EXPORT_SYMBOL(kgsl_pre_hwaccess); + +void kgsl_check_suspended(struct kgsl_device *device) +{ + if (device->requested_state == KGSL_STATE_SUSPEND || + device->state == KGSL_STATE_SUSPEND) { + mutex_unlock(&device->mutex); + wait_for_completion(&device->hwaccess_gate); + mutex_lock(&device->mutex); + } + if (device->state == KGSL_STATE_DUMP_AND_RECOVER) { + mutex_unlock(&device->mutex); + wait_for_completion(&device->recovery_gate); + mutex_lock(&device->mutex); + } + } + + +/******************************************************************/ +/* Caller must hold the device mutex. */ +int kgsl_pwrctrl_sleep(struct kgsl_device *device) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + KGSL_PWR_INFO(device, "sleep device %d\n", device->id); + + /* Work through the legal state transitions */ + if (device->requested_state == KGSL_STATE_NAP) { + if (device->ftbl->isidle(device)) + goto nap; + } else if (device->requested_state == KGSL_STATE_SLEEP) { + if (device->state == KGSL_STATE_NAP || + device->ftbl->isidle(device)) + goto sleep; + } + + device->requested_state = KGSL_STATE_NONE; + return -EBUSY; + +sleep: + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + kgsl_pwrctrl_axi(device, KGSL_PWRFLAGS_OFF); + if (pwr->pwrlevels[0].gpu_freq > 0) + clk_set_rate(pwr->grp_clks[0], + pwr->pwrlevels[pwr->num_pwrlevels - 1]. + gpu_freq); + device->pwrctrl.time = 0; + + kgsl_pwrscale_sleep(device); + goto clk_off; + +nap: + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); +clk_off: + kgsl_pwrctrl_clk(device, KGSL_PWRFLAGS_OFF); + + device->state = device->requested_state; + device->requested_state = KGSL_STATE_NONE; + wake_unlock(&device->idle_wakelock); + pm_qos_update_request(&device->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + KGSL_PWR_WARN(device, "state -> NAP/SLEEP(%d), device %d\n", + device->state, device->id); + + return 0; +} +EXPORT_SYMBOL(kgsl_pwrctrl_sleep); + +/******************************************************************/ +/* Caller must hold the device mutex. */ +void kgsl_pwrctrl_wake(struct kgsl_device *device) +{ + if (device->state == KGSL_STATE_SUSPEND) + return; + + if (device->state != KGSL_STATE_NAP) { + kgsl_pwrctrl_axi(device, KGSL_PWRFLAGS_ON); + kgsl_pwrscale_wake(device); + } + + /* Turn on the core clocks */ + kgsl_pwrctrl_clk(device, KGSL_PWRFLAGS_ON); + + /* Enable state before turning on irq */ + device->state = KGSL_STATE_ACTIVE; + KGSL_PWR_WARN(device, "state -> ACTIVE, device %d\n", device->id); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_ON); + + /* Re-enable HW access */ + mod_timer(&device->idle_timer, + jiffies + device->pwrctrl.interval_timeout); + + wake_lock(&device->idle_wakelock); + pm_qos_update_request(&device->pm_qos_req_dma, GPU_SWFI_LATENCY); + KGSL_PWR_INFO(device, "wake return for device %d\n", device->id); +} +EXPORT_SYMBOL(kgsl_pwrctrl_wake); + +void kgsl_pwrctrl_enable(struct kgsl_device *device) +{ + /* Order pwrrail/clk sequence based upon platform */ + kgsl_pwrctrl_pwrrail(device, KGSL_PWRFLAGS_ON); + kgsl_pwrctrl_clk(device, KGSL_PWRFLAGS_ON); + kgsl_pwrctrl_axi(device, KGSL_PWRFLAGS_ON); +} +EXPORT_SYMBOL(kgsl_pwrctrl_enable); + +void kgsl_pwrctrl_disable(struct kgsl_device *device) +{ + /* Order pwrrail/clk sequence based upon platform */ + kgsl_pwrctrl_axi(device, KGSL_PWRFLAGS_OFF); + kgsl_pwrctrl_clk(device, KGSL_PWRFLAGS_OFF); + kgsl_pwrctrl_pwrrail(device, KGSL_PWRFLAGS_OFF); +} +EXPORT_SYMBOL(kgsl_pwrctrl_disable); diff --git a/drivers/gpu/msm/kgsl_pwrctrl.h b/drivers/gpu/msm/kgsl_pwrctrl.h new file mode 100644 index 00000000000..fff5769da3d --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrctrl.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __KGSL_PWRCTRL_H +#define __KGSL_PWRCTRL_H + +/***************************************************************************** +** power flags +*****************************************************************************/ +#define KGSL_PWRFLAGS_POWER_ON 0 +#define KGSL_PWRFLAGS_CLK_ON 1 +#define KGSL_PWRFLAGS_AXI_ON 2 +#define KGSL_PWRFLAGS_IRQ_ON 3 + +#define KGSL_PWRFLAGS_ON 1 +#define KGSL_PWRFLAGS_OFF 0 + +#define KGSL_PWRLEVEL_TURBO 0 +#define KGSL_PWRLEVEL_NOMINAL 1 +#define KGSL_MAX_CLKS 5 + +struct platform_device; + +struct kgsl_pwrctrl { + int interrupt_num; + int have_irq; + struct clk *ebi1_clk; + struct clk *grp_clks[KGSL_MAX_CLKS]; + unsigned long power_flags; + struct kgsl_pwrlevel pwrlevels[KGSL_MAX_PWRLEVELS]; + unsigned int active_pwrlevel; + int thermal_pwrlevel; + unsigned int num_pwrlevels; + unsigned int interval_timeout; + struct regulator *gpu_reg; + uint32_t pcl; + unsigned int nap_allowed; + const char *regulator_name; + const char *irq_name; + const char *src_clk_name; + s64 time; +}; + +void kgsl_pwrctrl_clk(struct kgsl_device *device, int state); +void kgsl_pwrctrl_axi(struct kgsl_device *device, int state); +void kgsl_pwrctrl_pwrrail(struct kgsl_device *device, int state); +void kgsl_pwrctrl_irq(struct kgsl_device *device, int state); +int kgsl_pwrctrl_init(struct kgsl_device *device); +void kgsl_pwrctrl_close(struct kgsl_device *device); +void kgsl_timer(unsigned long data); +void kgsl_idle_check(struct work_struct *work); +void kgsl_pre_hwaccess(struct kgsl_device *device); +void kgsl_check_suspended(struct kgsl_device *device); +int kgsl_pwrctrl_sleep(struct kgsl_device *device); +void kgsl_pwrctrl_wake(struct kgsl_device *device); +void kgsl_pwrctrl_pwrlevel_change(struct kgsl_device *device, + unsigned int level); +int kgsl_pwrctrl_init_sysfs(struct kgsl_device *device); +void kgsl_pwrctrl_uninit_sysfs(struct kgsl_device *device); +void kgsl_pwrctrl_enable(struct kgsl_device *device); +void kgsl_pwrctrl_disable(struct kgsl_device *device); +static inline unsigned long kgsl_get_clkrate(struct clk *clk) +{ + return (clk != NULL) ? clk_get_rate(clk) : 0; +} + +#endif /* __KGSL_PWRCTRL_H */ diff --git a/drivers/gpu/msm/kgsl_pwrscale.c b/drivers/gpu/msm/kgsl_pwrscale.c new file mode 100644 index 00000000000..0d3120fe02f --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrscale.c @@ -0,0 +1,327 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include + +#include "kgsl.h" +#include "kgsl_pwrscale.h" +#include "kgsl_device.h" + +struct kgsl_pwrscale_attribute { + struct attribute attr; + ssize_t (*show)(struct kgsl_device *device, char *buf); + ssize_t (*store)(struct kgsl_device *device, const char *buf, + size_t count); +}; + +#define to_pwrscale(k) container_of(k, struct kgsl_pwrscale, kobj) +#define pwrscale_to_device(p) container_of(p, struct kgsl_device, pwrscale) +#define to_device(k) container_of(k, struct kgsl_device, pwrscale_kobj) +#define to_pwrscale_attr(a) \ +container_of(a, struct kgsl_pwrscale_attribute, attr) +#define to_policy_attr(a) \ +container_of(a, struct kgsl_pwrscale_policy_attribute, attr) + +#define PWRSCALE_ATTR(_name, _mode, _show, _store) \ +struct kgsl_pwrscale_attribute pwrscale_attr_##_name = \ +__ATTR(_name, _mode, _show, _store) + +/* Master list of available policies */ + +static struct kgsl_pwrscale_policy *kgsl_pwrscale_policies[] = { +#ifdef CONFIG_MSM_SCM + &kgsl_pwrscale_policy_tz, +#endif + NULL +}; + +static ssize_t pwrscale_policy_store(struct kgsl_device *device, + const char *buf, size_t count) +{ + int i; + struct kgsl_pwrscale_policy *policy = NULL; + + /* The special keyword none allows the user to detach all + policies */ + if (!strncmp("none", buf, 4)) { + kgsl_pwrscale_detach_policy(device); + return count; + } + + for (i = 0; kgsl_pwrscale_policies[i]; i++) { + if (!strncmp(kgsl_pwrscale_policies[i]->name, buf, + strnlen(kgsl_pwrscale_policies[i]->name, + PAGE_SIZE))) { + policy = kgsl_pwrscale_policies[i]; + break; + } + } + + if (policy) + if (kgsl_pwrscale_attach_policy(device, policy)) + return -EIO; + + return count; +} + +static ssize_t pwrscale_policy_show(struct kgsl_device *device, char *buf) +{ + int ret; + + if (device->pwrscale.policy) + ret = snprintf(buf, PAGE_SIZE, "%s\n", + device->pwrscale.policy->name); + else + ret = snprintf(buf, PAGE_SIZE, "none\n"); + + return ret; +} + +PWRSCALE_ATTR(policy, 0644, pwrscale_policy_show, pwrscale_policy_store); + +static ssize_t pwrscale_avail_policies_show(struct kgsl_device *device, + char *buf) +{ + int i, ret = 0; + + for (i = 0; kgsl_pwrscale_policies[i]; i++) { + ret += snprintf(buf + ret, PAGE_SIZE - ret, "%s ", + kgsl_pwrscale_policies[i]->name); + } + + ret += snprintf(buf + ret, PAGE_SIZE - ret, "none\n"); + return ret; +} +PWRSCALE_ATTR(avail_policies, 0444, pwrscale_avail_policies_show, NULL); + +static struct attribute *pwrscale_attrs[] = { + &pwrscale_attr_policy.attr, + &pwrscale_attr_avail_policies.attr, + NULL +}; + +static ssize_t policy_sysfs_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct kgsl_pwrscale *pwrscale = to_pwrscale(kobj); + struct kgsl_device *device = pwrscale_to_device(pwrscale); + struct kgsl_pwrscale_policy_attribute *pattr = to_policy_attr(attr); + ssize_t ret; + + if (pattr->show) + ret = pattr->show(device, pwrscale, buf); + else + ret = -EIO; + + return ret; +} + +static ssize_t policy_sysfs_store(struct kobject *kobj, + struct attribute *attr, + const char *buf, size_t count) +{ + struct kgsl_pwrscale *pwrscale = to_pwrscale(kobj); + struct kgsl_device *device = pwrscale_to_device(pwrscale); + struct kgsl_pwrscale_policy_attribute *pattr = to_policy_attr(attr); + ssize_t ret; + + if (pattr->store) + ret = pattr->store(device, pwrscale, buf, count); + else + ret = -EIO; + + return ret; +} + +static void policy_sysfs_release(struct kobject *kobj) +{ +} + +static ssize_t pwrscale_sysfs_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct kgsl_device *device = to_device(kobj); + struct kgsl_pwrscale_attribute *pattr = to_pwrscale_attr(attr); + ssize_t ret; + + if (pattr->show) + ret = pattr->show(device, buf); + else + ret = -EIO; + + return ret; +} + +static ssize_t pwrscale_sysfs_store(struct kobject *kobj, + struct attribute *attr, + const char *buf, size_t count) +{ + struct kgsl_device *device = to_device(kobj); + struct kgsl_pwrscale_attribute *pattr = to_pwrscale_attr(attr); + ssize_t ret; + + if (pattr->store) + ret = pattr->store(device, buf, count); + else + ret = -EIO; + + return ret; +} + +static void pwrscale_sysfs_release(struct kobject *kobj) +{ +} + +static const struct sysfs_ops policy_sysfs_ops = { + .show = policy_sysfs_show, + .store = policy_sysfs_store +}; + +static const struct sysfs_ops pwrscale_sysfs_ops = { + .show = pwrscale_sysfs_show, + .store = pwrscale_sysfs_store +}; + +static struct kobj_type ktype_pwrscale_policy = { + .sysfs_ops = &policy_sysfs_ops, + .default_attrs = NULL, + .release = policy_sysfs_release +}; + +static struct kobj_type ktype_pwrscale = { + .sysfs_ops = &pwrscale_sysfs_ops, + .default_attrs = pwrscale_attrs, + .release = pwrscale_sysfs_release +}; + +void kgsl_pwrscale_sleep(struct kgsl_device *device) +{ + if (device->pwrscale.policy && device->pwrscale.policy->sleep) + device->pwrscale.policy->sleep(device, &device->pwrscale); +} +EXPORT_SYMBOL(kgsl_pwrscale_sleep); + +void kgsl_pwrscale_wake(struct kgsl_device *device) +{ + if (device->pwrscale.policy && device->pwrscale.policy->wake) + device->pwrscale.policy->wake(device, &device->pwrscale); +} +EXPORT_SYMBOL(kgsl_pwrscale_wake); + +void kgsl_pwrscale_busy(struct kgsl_device *device) +{ + if (device->pwrscale.policy && device->pwrscale.policy->busy) + device->pwrscale.policy->busy(device, &device->pwrscale); +} + +void kgsl_pwrscale_idle(struct kgsl_device *device) +{ + if (device->pwrscale.policy && device->pwrscale.policy->idle) + device->pwrscale.policy->idle(device, &device->pwrscale); +} +EXPORT_SYMBOL(kgsl_pwrscale_idle); + +int kgsl_pwrscale_policy_add_files(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale, + struct attribute_group *attr_group) +{ + int ret; + + ret = kobject_add(&pwrscale->kobj, &device->pwrscale_kobj, + "%s", pwrscale->policy->name); + + if (ret) + return ret; + + ret = sysfs_create_group(&pwrscale->kobj, attr_group); + + if (ret) { + kobject_del(&pwrscale->kobj); + kobject_put(&pwrscale->kobj); + } + + return ret; +} + +void kgsl_pwrscale_policy_remove_files(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale, + struct attribute_group *attr_group) +{ + sysfs_remove_group(&pwrscale->kobj, attr_group); + kobject_del(&pwrscale->kobj); + kobject_put(&pwrscale->kobj); +} + +static void _kgsl_pwrscale_detach_policy(struct kgsl_device *device) +{ + if (device->pwrscale.policy != NULL) + device->pwrscale.policy->close(device, &device->pwrscale); + device->pwrscale.policy = NULL; +} + +void kgsl_pwrscale_detach_policy(struct kgsl_device *device) +{ + mutex_lock(&device->mutex); + _kgsl_pwrscale_detach_policy(device); + mutex_unlock(&device->mutex); +} +EXPORT_SYMBOL(kgsl_pwrscale_detach_policy); + +int kgsl_pwrscale_attach_policy(struct kgsl_device *device, + struct kgsl_pwrscale_policy *policy) +{ + int ret = 0; + + mutex_lock(&device->mutex); + + if (device->pwrscale.policy == policy) + goto done; + + if (device->pwrscale.policy != NULL) + _kgsl_pwrscale_detach_policy(device); + + device->pwrscale.policy = policy; + + if (policy) { + ret = device->pwrscale.policy->init(device, &device->pwrscale); + if (ret) + device->pwrscale.policy = NULL; + } + +done: + mutex_unlock(&device->mutex); + + return ret; +} +EXPORT_SYMBOL(kgsl_pwrscale_attach_policy); + +int kgsl_pwrscale_init(struct kgsl_device *device) +{ + int ret; + + ret = kobject_init_and_add(&device->pwrscale_kobj, &ktype_pwrscale, + &device->dev->kobj, "pwrscale"); + + if (ret) + return ret; + + kobject_init(&device->pwrscale.kobj, &ktype_pwrscale_policy); + return ret; +} +EXPORT_SYMBOL(kgsl_pwrscale_init); + +void kgsl_pwrscale_close(struct kgsl_device *device) +{ + kobject_put(&device->pwrscale_kobj); +} +EXPORT_SYMBOL(kgsl_pwrscale_close); diff --git a/drivers/gpu/msm/kgsl_pwrscale.h b/drivers/gpu/msm/kgsl_pwrscale.h new file mode 100644 index 00000000000..69046087313 --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrscale.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __KGSL_PWRSCALE_H +#define __KGSL_PWRSCALE_H + +struct kgsl_pwrscale; + +struct kgsl_pwrscale_policy { + const char *name; + int (*init)(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale); + void (*close)(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale); + void (*idle)(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale); + void (*busy)(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale); + void (*sleep)(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale); + void (*wake)(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale); +}; + +struct kgsl_pwrscale { + struct kgsl_pwrscale_policy *policy; + struct kobject kobj; + void *priv; +}; + +struct kgsl_pwrscale_policy_attribute { + struct attribute attr; + ssize_t (*show)(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale, char *buf); + ssize_t (*store)(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale, const char *buf, + size_t count); +}; + +#define PWRSCALE_POLICY_ATTR(_name, _mode, _show, _store) \ + struct kgsl_pwrscale_policy_attribute policy_attr_##_name = \ + __ATTR(_name, _mode, _show, _store) + +extern struct kgsl_pwrscale_policy kgsl_pwrscale_policy_tz; + +int kgsl_pwrscale_init(struct kgsl_device *device); +void kgsl_pwrscale_close(struct kgsl_device *device); + +int kgsl_pwrscale_attach_policy(struct kgsl_device *device, + struct kgsl_pwrscale_policy *policy); +void kgsl_pwrscale_detach_policy(struct kgsl_device *device); + +void kgsl_pwrscale_idle(struct kgsl_device *device); +void kgsl_pwrscale_busy(struct kgsl_device *device); +void kgsl_pwrscale_sleep(struct kgsl_device *device); +void kgsl_pwrscale_wake(struct kgsl_device *device); + +int kgsl_pwrscale_policy_add_files(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale, + struct attribute_group *attr_group); + +void kgsl_pwrscale_policy_remove_files(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale, + struct attribute_group *attr_group); +#endif diff --git a/drivers/gpu/msm/kgsl_pwrscale_trustzone.c b/drivers/gpu/msm/kgsl_pwrscale_trustzone.c new file mode 100644 index 00000000000..a6fae3021f6 --- /dev/null +++ b/drivers/gpu/msm/kgsl_pwrscale_trustzone.c @@ -0,0 +1,197 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_pwrscale.h" +#include "kgsl_device.h" + +#define TZ_GOVERNOR_PERFORMANCE 0 +#define TZ_GOVERNOR_ONDEMAND 1 + +struct tz_priv { + int governor; + unsigned int no_switch_cnt; +}; + +#define SWITCH_OFF 200 +#define TZ_UPDATE_ID 0x01404000 +#define TZ_RESET_ID 0x01403000 + +#ifdef CONFIG_MSM_SECURE_IO +/* Trap into the TrustZone, and call funcs there. */ +static int __secure_tz_entry(u32 cmd, u32 val) +{ + register u32 r0 asm("r0") = cmd; + register u32 r1 asm("r1") = 0x0; + register u32 r2 asm("r2") = val; + + __iowmb(); + asm( + __asmeq("%0", "r0") + __asmeq("%1", "r0") + __asmeq("%2", "r1") + __asmeq("%3", "r2") + "smc #0 @ switch to secure world\n" + : "=r" (r0) + : "r" (r0), "r" (r1), "r" (r2) + ); + return r0; +} +#else +static int __secure_tz_entry(u32 cmd, u32 val) +{ + return 0; +} +#endif /* CONFIG_MSM_SECURE_IO */ + +static ssize_t tz_governor_show(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale, + char *buf) +{ + struct tz_priv *priv = pwrscale->priv; + int ret; + + if (priv->governor == TZ_GOVERNOR_ONDEMAND) + ret = snprintf(buf, 10, "ondemand\n"); + else + ret = snprintf(buf, 13, "performance\n"); + + return ret; +} + +static ssize_t tz_governor_store(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale, + const char *buf, size_t count) +{ + char str[20]; + struct tz_priv *priv = pwrscale->priv; + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + int ret; + + ret = sscanf(buf, "%20s", str); + if (ret != 1) + return -EINVAL; + + mutex_lock(&device->mutex); + + if (!strncmp(str, "ondemand", 8)) + priv->governor = TZ_GOVERNOR_ONDEMAND; + else if (!strncmp(str, "performance", 11)) + priv->governor = TZ_GOVERNOR_PERFORMANCE; + + if (priv->governor == TZ_GOVERNOR_PERFORMANCE) + kgsl_pwrctrl_pwrlevel_change(device, pwr->thermal_pwrlevel); + + mutex_unlock(&device->mutex); + return count; +} + +PWRSCALE_POLICY_ATTR(governor, 0644, tz_governor_show, tz_governor_store); + +static struct attribute *tz_attrs[] = { + &policy_attr_governor.attr, + NULL +}; + +static struct attribute_group tz_attr_group = { + .attrs = tz_attrs, +}; + +static void tz_wake(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale) +{ + struct tz_priv *priv = pwrscale->priv; + if (device->state != KGSL_STATE_NAP && + priv->governor == TZ_GOVERNOR_ONDEMAND) + kgsl_pwrctrl_pwrlevel_change(device, + device->pwrctrl.thermal_pwrlevel); +} + +static void tz_idle(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale) +{ + struct kgsl_pwrctrl *pwr = &device->pwrctrl; + struct tz_priv *priv = pwrscale->priv; + struct kgsl_power_stats stats; + int val; + + /* In "performance" mode the clock speed always stays + the same */ + + if (priv->governor == TZ_GOVERNOR_PERFORMANCE) + return; + + device->ftbl->power_stats(device, &stats); + if (stats.total_time == 0) + return; + + /* If the GPU has stayed in turbo mode for a while, * + * stop writing out values. */ + if (pwr->active_pwrlevel) + priv->no_switch_cnt = 0; + else if (priv->no_switch_cnt > SWITCH_OFF) + return; + priv->no_switch_cnt++; + val = __secure_tz_entry(TZ_UPDATE_ID, + stats.total_time - stats.busy_time); + if (val) + kgsl_pwrctrl_pwrlevel_change(device, + pwr->active_pwrlevel + val); +} + +static void tz_sleep(struct kgsl_device *device, + struct kgsl_pwrscale *pwrscale) +{ + struct tz_priv *priv = pwrscale->priv; + + __secure_tz_entry(TZ_RESET_ID, 0); + priv->no_switch_cnt = 0; +} + +static int tz_init(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale) +{ + struct tz_priv *priv; + + /* Trustzone is only valid for some SOCs */ + if (!cpu_is_msm8x60()) + return -EINVAL; + + priv = pwrscale->priv = kzalloc(sizeof(struct tz_priv), GFP_KERNEL); + if (pwrscale->priv == NULL) + return -ENOMEM; + + priv->governor = TZ_GOVERNOR_ONDEMAND; + kgsl_pwrscale_policy_add_files(device, pwrscale, &tz_attr_group); + + return 0; +} + +static void tz_close(struct kgsl_device *device, struct kgsl_pwrscale *pwrscale) +{ + kgsl_pwrscale_policy_remove_files(device, pwrscale, &tz_attr_group); + kfree(pwrscale->priv); + pwrscale->priv = NULL; +} + +struct kgsl_pwrscale_policy kgsl_pwrscale_policy_tz = { + .name = "trustzone", + .init = tz_init, + .idle = tz_idle, + .sleep = tz_sleep, + .wake = tz_wake, + .close = tz_close +}; +EXPORT_SYMBOL(kgsl_pwrscale_policy_tz); diff --git a/drivers/gpu/msm/kgsl_sharedmem.c b/drivers/gpu/msm/kgsl_sharedmem.c new file mode 100644 index 00000000000..61e148c81fc --- /dev/null +++ b/drivers/gpu/msm/kgsl_sharedmem.c @@ -0,0 +1,639 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include + +#include "kgsl.h" +#include "kgsl_sharedmem.h" +#include "kgsl_cffdump.h" +#include "kgsl_device.h" + +static struct kgsl_process_private * +_get_priv_from_kobj(struct kobject *kobj) +{ + struct kgsl_process_private *private; + unsigned long name; + + if (!kobj) + return NULL; + + if (sscanf(kobj->name, "%ld", &name) != 1) + return NULL; + + list_for_each_entry(private, &kgsl_driver.process_list, list) { + if (private->pid == name) + return private; + } + + return NULL; +} + +/* sharedmem / memory sysfs files */ + +static ssize_t +process_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct kgsl_process_private *priv; + unsigned int val = 0; + + mutex_lock(&kgsl_driver.process_mutex); + priv = _get_priv_from_kobj(kobj); + + if (priv == NULL) { + mutex_unlock(&kgsl_driver.process_mutex); + return 0; + } + + if (!strncmp(attr->attr.name, "user", 4)) + val = priv->stats.user; + if (!strncmp(attr->attr.name, "user_max", 8)) + val = priv->stats.user_max; + if (!strncmp(attr->attr.name, "mapped", 6)) + val = priv->stats.mapped; + if (!strncmp(attr->attr.name, "mapped_max", 10)) + val = priv->stats.mapped_max; + if (!strncmp(attr->attr.name, "flushes", 7)) + val = priv->stats.flushes; + + mutex_unlock(&kgsl_driver.process_mutex); + return snprintf(buf, PAGE_SIZE, "%u\n", val); +} + +#define KGSL_MEMSTAT_ATTR(_name, _show) \ + static struct kobj_attribute attr_##_name = \ + __ATTR(_name, 0444, _show, NULL) + +KGSL_MEMSTAT_ATTR(user, process_show); +KGSL_MEMSTAT_ATTR(user_max, process_show); +KGSL_MEMSTAT_ATTR(mapped, process_show); +KGSL_MEMSTAT_ATTR(mapped_max, process_show); +KGSL_MEMSTAT_ATTR(flushes, process_show); + +static struct attribute *process_attrs[] = { + &attr_user.attr, + &attr_user_max.attr, + &attr_mapped.attr, + &attr_mapped_max.attr, + &attr_flushes.attr, + NULL +}; + +static struct attribute_group process_attr_group = { + .attrs = process_attrs, +}; + +void +kgsl_process_uninit_sysfs(struct kgsl_process_private *private) +{ + /* Remove the sysfs entry */ + if (private->kobj) { + sysfs_remove_group(private->kobj, &process_attr_group); + kobject_put(private->kobj); + } +} + +void +kgsl_process_init_sysfs(struct kgsl_process_private *private) +{ + unsigned char name[16]; + + /* Add a entry to the sysfs device */ + snprintf(name, sizeof(name), "%d", private->pid); + private->kobj = kobject_create_and_add(name, kgsl_driver.prockobj); + + /* sysfs failure isn't fatal, just annoying */ + if (private->kobj != NULL) { + if (sysfs_create_group(private->kobj, &process_attr_group)) { + kobject_put(private->kobj); + private->kobj = NULL; + } + } +} + +static int kgsl_drv_memstat_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned int val = 0; + + if (!strncmp(attr->attr.name, "vmalloc", 7)) + val = kgsl_driver.stats.vmalloc; + else if (!strncmp(attr->attr.name, "vmalloc_max", 11)) + val = kgsl_driver.stats.vmalloc_max; + else if (!strncmp(attr->attr.name, "coherent", 8)) + val = kgsl_driver.stats.coherent; + else if (!strncmp(attr->attr.name, "coherent_max", 12)) + val = kgsl_driver.stats.coherent_max; + else if (!strncmp(attr->attr.name, "mapped", 6)) + val = kgsl_driver.stats.mapped; + else if (!strncmp(attr->attr.name, "mapped_max", 10)) + val = kgsl_driver.stats.mapped_max; + + return snprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static int kgsl_drv_histogram_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len = 0; + int i; + + for (i = 0; i < 16; i++) + len += sprintf(buf + len, "%d ", + kgsl_driver.stats.histogram[i]); + + len += sprintf(buf + len, "\n"); + return len; +} + +DEVICE_ATTR(vmalloc, 0444, kgsl_drv_memstat_show, NULL); +DEVICE_ATTR(vmalloc_max, 0444, kgsl_drv_memstat_show, NULL); +DEVICE_ATTR(coherent, 0444, kgsl_drv_memstat_show, NULL); +DEVICE_ATTR(coherent_max, 0444, kgsl_drv_memstat_show, NULL); +DEVICE_ATTR(mapped, 0444, kgsl_drv_memstat_show, NULL); +DEVICE_ATTR(mapped_max, 0444, kgsl_drv_memstat_show, NULL); +DEVICE_ATTR(histogram, 0444, kgsl_drv_histogram_show, NULL); + +static const struct device_attribute *drv_attr_list[] = { + &dev_attr_vmalloc, + &dev_attr_vmalloc_max, + &dev_attr_coherent, + &dev_attr_coherent_max, + &dev_attr_mapped, + &dev_attr_mapped_max, + &dev_attr_histogram, + NULL +}; + +void +kgsl_sharedmem_uninit_sysfs(void) +{ + kgsl_remove_device_sysfs_files(&kgsl_driver.virtdev, drv_attr_list); +} + +int +kgsl_sharedmem_init_sysfs(void) +{ + return kgsl_create_device_sysfs_files(&kgsl_driver.virtdev, + drv_attr_list); +} + +#ifdef CONFIG_OUTER_CACHE +static void _outer_cache_range_op(int op, unsigned long addr, size_t size) +{ + switch (op) { + case KGSL_CACHE_OP_FLUSH: + outer_flush_range(addr, addr + size); + break; + case KGSL_CACHE_OP_CLEAN: + outer_clean_range(addr, addr + size); + break; + case KGSL_CACHE_OP_INV: + outer_inv_range(addr, addr + size); + break; + } +} +#endif + +static unsigned long kgsl_vmalloc_physaddr(struct kgsl_memdesc *memdesc, + unsigned int offset) +{ + unsigned int addr; + + if (offset > memdesc->size) + return 0; + + addr = vmalloc_to_pfn(memdesc->hostptr + offset); + return addr << PAGE_SHIFT; +} + +#ifdef CONFIG_OUTER_CACHE +static void kgsl_vmalloc_outer_cache(struct kgsl_memdesc *memdesc, int op) +{ + void *vaddr = memdesc->hostptr; + for (; vaddr < (memdesc->hostptr + memdesc->size); vaddr += PAGE_SIZE) { + unsigned long paddr = page_to_phys(vmalloc_to_page(vaddr)); + _outer_cache_range_op(op, paddr, PAGE_SIZE); + } +} +#endif + +static int kgsl_vmalloc_vmfault(struct kgsl_memdesc *memdesc, + struct vm_area_struct *vma, + struct vm_fault *vmf) +{ + unsigned long offset, pg; + struct page *page; + + offset = (unsigned long) vmf->virtual_address - vma->vm_start; + pg = (unsigned long) memdesc->hostptr + offset; + + page = vmalloc_to_page((void *) pg); + if (page == NULL) + return VM_FAULT_SIGBUS; + + get_page(page); + + vmf->page = page; + return 0; +} + +static int kgsl_vmalloc_vmflags(struct kgsl_memdesc *memdesc) +{ + return VM_RESERVED | VM_DONTEXPAND; +} + +static void kgsl_vmalloc_free(struct kgsl_memdesc *memdesc) +{ + kgsl_driver.stats.vmalloc -= memdesc->size; + vfree(memdesc->hostptr); +} + +static int kgsl_contiguous_vmflags(struct kgsl_memdesc *memdesc) +{ + return VM_RESERVED | VM_IO | VM_PFNMAP | VM_DONTEXPAND; +} + +static int kgsl_contiguous_vmfault(struct kgsl_memdesc *memdesc, + struct vm_area_struct *vma, + struct vm_fault *vmf) +{ + unsigned long offset, pfn; + int ret; + + offset = ((unsigned long) vmf->virtual_address - vma->vm_start) >> + PAGE_SHIFT; + + pfn = (memdesc->physaddr >> PAGE_SHIFT) + offset; + ret = vm_insert_pfn(vma, (unsigned long) vmf->virtual_address, pfn); + + if (ret == -ENOMEM || ret == -EAGAIN) + return VM_FAULT_OOM; + else if (ret == -EFAULT) + return VM_FAULT_SIGBUS; + + return VM_FAULT_NOPAGE; +} + +static void kgsl_ebimem_free(struct kgsl_memdesc *memdesc) + +{ + kgsl_driver.stats.coherent -= memdesc->size; + if (memdesc->hostptr) + iounmap(memdesc->hostptr); + + free_contiguous_memory_by_paddr(memdesc->physaddr); +} + +static void kgsl_coherent_free(struct kgsl_memdesc *memdesc) +{ + kgsl_driver.stats.coherent -= memdesc->size; + dma_free_coherent(NULL, memdesc->size, + memdesc->hostptr, memdesc->physaddr); +} + +static unsigned long kgsl_contiguous_physaddr(struct kgsl_memdesc *memdesc, + unsigned int offset) +{ + if (offset > memdesc->size) + return 0; + + return memdesc->physaddr + offset; +} + +#ifdef CONFIG_OUTER_CACHE +static void kgsl_contiguous_outer_cache(struct kgsl_memdesc *memdesc, int op) +{ + _outer_cache_range_op(op, memdesc->physaddr, memdesc->size); +} +#endif + +#ifdef CONFIG_OUTER_CACHE +static void kgsl_userptr_outer_cache(struct kgsl_memdesc *memdesc, int op) +{ + void *vaddr = memdesc->hostptr; + for (; vaddr < (memdesc->hostptr + memdesc->size); vaddr += PAGE_SIZE) { + unsigned long paddr = kgsl_virtaddr_to_physaddr(vaddr); + if (paddr) + _outer_cache_range_op(op, paddr, PAGE_SIZE); + } +} +#endif + +static unsigned long kgsl_userptr_physaddr(struct kgsl_memdesc *memdesc, + unsigned int offset) +{ + return kgsl_virtaddr_to_physaddr(memdesc->hostptr + offset); +} + +/* Global - also used by kgsl_drm.c */ +struct kgsl_memdesc_ops kgsl_vmalloc_ops = { + .physaddr = kgsl_vmalloc_physaddr, + .free = kgsl_vmalloc_free, + .vmflags = kgsl_vmalloc_vmflags, + .vmfault = kgsl_vmalloc_vmfault, +#ifdef CONFIG_OUTER_CACHE + .outer_cache = kgsl_vmalloc_outer_cache, +#endif +}; +EXPORT_SYMBOL(kgsl_vmalloc_ops); + +static struct kgsl_memdesc_ops kgsl_ebimem_ops = { + .physaddr = kgsl_contiguous_physaddr, + .free = kgsl_ebimem_free, + .vmflags = kgsl_contiguous_vmflags, + .vmfault = kgsl_contiguous_vmfault, +#ifdef CONFIG_OUTER_CACHE + .outer_cache = kgsl_contiguous_outer_cache, +#endif +}; + +static struct kgsl_memdesc_ops kgsl_coherent_ops = { + .physaddr = kgsl_contiguous_physaddr, + .free = kgsl_coherent_free, +#ifdef CONFIG_OUTER_CACHE + .outer_cache = kgsl_contiguous_outer_cache, +#endif +}; + +/* Global - also used by kgsl.c and kgsl_drm.c */ +struct kgsl_memdesc_ops kgsl_contiguous_ops = { + .physaddr = kgsl_contiguous_physaddr, +#ifdef CONFIG_OUTER_CACHE + .outer_cache = kgsl_contiguous_outer_cache +#endif +}; +EXPORT_SYMBOL(kgsl_contiguous_ops); + +/* Global - also used by kgsl.c */ +struct kgsl_memdesc_ops kgsl_userptr_ops = { + .physaddr = kgsl_userptr_physaddr, +#ifdef CONFIG_OUTER_CACHE + .outer_cache = kgsl_userptr_outer_cache, +#endif +}; +EXPORT_SYMBOL(kgsl_userptr_ops); + +void kgsl_cache_range_op(struct kgsl_memdesc *memdesc, int op) +{ + void *addr = memdesc->hostptr; + int size = memdesc->size; + + switch (op) { + case KGSL_CACHE_OP_FLUSH: + dmac_flush_range(addr, addr + size); + break; + case KGSL_CACHE_OP_CLEAN: + dmac_clean_range(addr, addr + size); + break; + case KGSL_CACHE_OP_INV: + dmac_inv_range(addr, addr + size); + break; + } + + if (memdesc->ops->outer_cache) + memdesc->ops->outer_cache(memdesc, op); +} +EXPORT_SYMBOL(kgsl_cache_range_op); + +static int +_kgsl_sharedmem_vmalloc(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + void *ptr, size_t size, unsigned int protflags) +{ + int result; + + memdesc->size = size; + memdesc->pagetable = pagetable; + memdesc->priv = KGSL_MEMFLAGS_CACHED; + memdesc->ops = &kgsl_vmalloc_ops; + memdesc->hostptr = (void *) ptr; + + kgsl_cache_range_op(memdesc, KGSL_CACHE_OP_INV); + + result = kgsl_mmu_map(pagetable, memdesc, protflags); + + if (result) { + kgsl_sharedmem_free(memdesc); + } else { + int order; + + KGSL_STATS_ADD(size, kgsl_driver.stats.vmalloc, + kgsl_driver.stats.vmalloc_max); + + order = get_order(size); + + if (order < 16) + kgsl_driver.stats.histogram[order]++; + } + + return result; +} + +int +kgsl_sharedmem_vmalloc(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, size_t size) +{ + void *ptr; + + BUG_ON(size == 0); + + size = ALIGN(size, PAGE_SIZE * 2); + ptr = vmalloc(size); + + if (ptr == NULL) { + KGSL_CORE_ERR("vmalloc(%d) failed\n", size); + return -ENOMEM; + } + + return _kgsl_sharedmem_vmalloc(memdesc, pagetable, ptr, size, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); +} +EXPORT_SYMBOL(kgsl_sharedmem_vmalloc); + +int +kgsl_sharedmem_vmalloc_user(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + size_t size, int flags) +{ + void *ptr; + unsigned int protflags; + + BUG_ON(size == 0); + ptr = vmalloc_user(size); + + if (ptr == NULL) { + KGSL_CORE_ERR("vmalloc_user(%d) failed: allocated=%d\n", + size, kgsl_driver.stats.vmalloc); + return -ENOMEM; + } + + protflags = GSL_PT_PAGE_RV; + if (!(flags & KGSL_MEMFLAGS_GPUREADONLY)) + protflags |= GSL_PT_PAGE_WV; + + return _kgsl_sharedmem_vmalloc(memdesc, pagetable, ptr, size, + protflags); +} +EXPORT_SYMBOL(kgsl_sharedmem_vmalloc_user); + +int +kgsl_sharedmem_alloc_coherent(struct kgsl_memdesc *memdesc, size_t size) +{ + size = ALIGN(size, PAGE_SIZE); + + memdesc->hostptr = dma_alloc_coherent(NULL, size, &memdesc->physaddr, + GFP_KERNEL); + if (memdesc->hostptr == NULL) { + KGSL_CORE_ERR("dma_alloc_coherent(%d) failed\n", size); + return -ENOMEM; + } + + memdesc->size = size; + memdesc->ops = &kgsl_coherent_ops; + + /* Record statistics */ + + KGSL_STATS_ADD(size, kgsl_driver.stats.coherent, + kgsl_driver.stats.coherent_max); + + return 0; +} +EXPORT_SYMBOL(kgsl_sharedmem_alloc_coherent); + +void kgsl_sharedmem_free(struct kgsl_memdesc *memdesc) +{ + if (memdesc == NULL || memdesc->size == 0) + return; + + if (memdesc->gpuaddr) + kgsl_mmu_unmap(memdesc->pagetable, memdesc); + + if (memdesc->ops->free) + memdesc->ops->free(memdesc); + + memset(memdesc, 0, sizeof(*memdesc)); +} +EXPORT_SYMBOL(kgsl_sharedmem_free); + +static int +_kgsl_sharedmem_ebimem(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, size_t size) +{ + int result; + + memdesc->physaddr = allocate_contiguous_ebi_nomap(size, SZ_8K); + + if (memdesc->physaddr == 0) { + KGSL_CORE_ERR("allocate_contiguous_ebi_nomap(%d) failed\n", + size); + return -ENOMEM; + } + + memdesc->size = size; + memdesc->pagetable = pagetable; + memdesc->ops = &kgsl_ebimem_ops; + + result = kgsl_mmu_map(pagetable, memdesc, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + + if (result) + kgsl_sharedmem_free(memdesc); + + KGSL_STATS_ADD(size, kgsl_driver.stats.coherent, + kgsl_driver.stats.coherent_max); + + return result; +} + +int +kgsl_sharedmem_ebimem_user(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + size_t size, int flags) +{ + size = ALIGN(size, PAGE_SIZE); + return _kgsl_sharedmem_ebimem(memdesc, pagetable, size); +} +EXPORT_SYMBOL(kgsl_sharedmem_ebimem_user); + +int +kgsl_sharedmem_ebimem(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, size_t size) +{ + int result; + size = ALIGN(size, 8192); + result = _kgsl_sharedmem_ebimem(memdesc, pagetable, size); + + if (result) + return result; + + memdesc->hostptr = ioremap(memdesc->physaddr, size); + + if (memdesc->hostptr == NULL) { + KGSL_CORE_ERR("ioremap failed\n"); + kgsl_sharedmem_free(memdesc); + return -ENOMEM; + } + + return 0; +} +EXPORT_SYMBOL(kgsl_sharedmem_ebimem); + +int +kgsl_sharedmem_readl(const struct kgsl_memdesc *memdesc, + uint32_t *dst, + unsigned int offsetbytes) +{ + BUG_ON(memdesc == NULL || memdesc->hostptr == NULL || dst == NULL); + WARN_ON(offsetbytes + sizeof(unsigned int) > memdesc->size); + + if (offsetbytes + sizeof(unsigned int) > memdesc->size) + return -ERANGE; + + *dst = readl_relaxed(memdesc->hostptr + offsetbytes); + return 0; +} +EXPORT_SYMBOL(kgsl_sharedmem_readl); + +int +kgsl_sharedmem_writel(const struct kgsl_memdesc *memdesc, + unsigned int offsetbytes, + uint32_t src) +{ + BUG_ON(memdesc == NULL || memdesc->hostptr == NULL); + BUG_ON(offsetbytes + sizeof(unsigned int) > memdesc->size); + + kgsl_cffdump_setmem(memdesc->physaddr + offsetbytes, + src, sizeof(uint)); + writel_relaxed(src, memdesc->hostptr + offsetbytes); + return 0; +} +EXPORT_SYMBOL(kgsl_sharedmem_writel); + +int +kgsl_sharedmem_set(const struct kgsl_memdesc *memdesc, unsigned int offsetbytes, + unsigned int value, unsigned int sizebytes) +{ + BUG_ON(memdesc == NULL || memdesc->hostptr == NULL); + BUG_ON(offsetbytes + sizebytes > memdesc->size); + + kgsl_cffdump_setmem(memdesc->physaddr + offsetbytes, value, + sizebytes); + memset(memdesc->hostptr + offsetbytes, value, sizebytes); + return 0; +} +EXPORT_SYMBOL(kgsl_sharedmem_set); diff --git a/drivers/gpu/msm/kgsl_sharedmem.h b/drivers/gpu/msm/kgsl_sharedmem.h new file mode 100644 index 00000000000..9e57e78c061 --- /dev/null +++ b/drivers/gpu/msm/kgsl_sharedmem.h @@ -0,0 +1,114 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __KGSL_SHAREDMEM_H +#define __KGSL_SHAREDMEM_H + +#include + +struct kgsl_device; +struct kgsl_process_private; + +#define KGSL_CACHE_OP_INV 0x01 +#define KGSL_CACHE_OP_FLUSH 0x02 +#define KGSL_CACHE_OP_CLEAN 0x03 + +/** Set if the memdesc describes cached memory */ +#define KGSL_MEMFLAGS_CACHED 0x00000001 + +struct kgsl_memdesc_ops { + unsigned long (*physaddr)(struct kgsl_memdesc *, unsigned int); + void (*outer_cache)(struct kgsl_memdesc *, int); + int (*vmflags)(struct kgsl_memdesc *); + int (*vmfault)(struct kgsl_memdesc *, struct vm_area_struct *, + struct vm_fault *); + void (*free)(struct kgsl_memdesc *memdesc); +}; + +extern struct kgsl_memdesc_ops kgsl_vmalloc_ops; +extern struct kgsl_memdesc_ops kgsl_contiguous_ops; +extern struct kgsl_memdesc_ops kgsl_userptr_ops; + +int kgsl_sharedmem_vmalloc(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, size_t size); + +int kgsl_sharedmem_vmalloc_user(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + size_t size, int flags); + +int kgsl_sharedmem_alloc_coherent(struct kgsl_memdesc *memdesc, size_t size); + +int kgsl_sharedmem_ebimem_user(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + size_t size, int flags); + +int kgsl_sharedmem_ebimem(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + size_t size); + +void kgsl_sharedmem_free(struct kgsl_memdesc *memdesc); + +int kgsl_sharedmem_readl(const struct kgsl_memdesc *memdesc, + uint32_t *dst, + unsigned int offsetbytes); + +int kgsl_sharedmem_writel(const struct kgsl_memdesc *memdesc, + unsigned int offsetbytes, + uint32_t src); + +int kgsl_sharedmem_set(const struct kgsl_memdesc *memdesc, + unsigned int offsetbytes, unsigned int value, + unsigned int sizebytes); + +void kgsl_cache_range_op(struct kgsl_memdesc *memdesc, int op); + +void kgsl_process_init_sysfs(struct kgsl_process_private *private); +void kgsl_process_uninit_sysfs(struct kgsl_process_private *private); + +int kgsl_sharedmem_init_sysfs(void); +void kgsl_sharedmem_uninit_sysfs(void); + +static inline int +kgsl_allocate(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, size_t size) +{ +#ifdef CONFIG_MSM_KGSL_MMU + return kgsl_sharedmem_vmalloc(memdesc, pagetable, size); +#else + return kgsl_sharedmem_ebimem(memdesc, pagetable, size); +#endif +} + +static inline int +kgsl_allocate_user(struct kgsl_memdesc *memdesc, + struct kgsl_pagetable *pagetable, + size_t size, unsigned int flags) +{ +#ifdef CONFIG_MSM_KGSL_MMU + return kgsl_sharedmem_vmalloc_user(memdesc, pagetable, size, flags); +#else + return kgsl_sharedmem_ebimem_user(memdesc, pagetable, size, flags); +#endif +} + +static inline int +kgsl_allocate_contiguous(struct kgsl_memdesc *memdesc, size_t size) +{ + int ret = kgsl_sharedmem_alloc_coherent(memdesc, size); +#ifndef CONFIG_MSM_KGSL_MMU + if (!ret) + memdesc->gpuaddr = memdesc->physaddr; +#endif + return ret; +} + +#endif /* __KGSL_SHAREDMEM_H */ diff --git a/drivers/gpu/msm/z180.c b/drivers/gpu/msm/z180.c new file mode 100644 index 00000000000..2d3f5bd5f89 --- /dev/null +++ b/drivers/gpu/msm/z180.c @@ -0,0 +1,938 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include + +#include "kgsl.h" +#include "kgsl_cffdump.h" +#include "kgsl_sharedmem.h" + +#include "z180.h" +#include "z180_reg.h" + +#define DRIVER_VERSION_MAJOR 3 +#define DRIVER_VERSION_MINOR 1 + +#define Z180_DEVICE(device) \ + KGSL_CONTAINER_OF(device, struct z180_device, dev) + +#define GSL_VGC_INT_MASK \ + (REG_VGC_IRQSTATUS__MH_MASK | \ + REG_VGC_IRQSTATUS__G2D_MASK | \ + REG_VGC_IRQSTATUS__FIFO_MASK) + +#define VGV3_NEXTCMD_JUMP 0x01 + +#define VGV3_NEXTCMD_NEXTCMD_FSHIFT 12 +#define VGV3_NEXTCMD_NEXTCMD_FMASK 0x7 + +#define VGV3_CONTROL_MARKADD_FSHIFT 0 +#define VGV3_CONTROL_MARKADD_FMASK 0xfff + +#define Z180_PACKET_SIZE 15 +#define Z180_MARKER_SIZE 10 +#define Z180_CALL_CMD 0x1000 +#define Z180_MARKER_CMD 0x8000 +#define Z180_STREAM_END_CMD 0x9000 +#define Z180_STREAM_PACKET 0x7C000176 +#define Z180_STREAM_PACKET_CALL 0x7C000275 +#define Z180_PACKET_COUNT 8 +#define Z180_RB_SIZE (Z180_PACKET_SIZE*Z180_PACKET_COUNT \ + *sizeof(uint32_t)) + +#define NUMTEXUNITS 4 +#define TEXUNITREGCOUNT 25 +#define VG_REGCOUNT 0x39 + +#define PACKETSIZE_BEGIN 3 +#define PACKETSIZE_G2DCOLOR 2 +#define PACKETSIZE_TEXUNIT (TEXUNITREGCOUNT * 2) +#define PACKETSIZE_REG (VG_REGCOUNT * 2) +#define PACKETSIZE_STATE (PACKETSIZE_TEXUNIT * NUMTEXUNITS + \ + PACKETSIZE_REG + PACKETSIZE_BEGIN + \ + PACKETSIZE_G2DCOLOR) +#define PACKETSIZE_STATESTREAM (ALIGN((PACKETSIZE_STATE * \ + sizeof(unsigned int)), 32) / \ + sizeof(unsigned int)) + +#define Z180_INVALID_CONTEXT UINT_MAX + +/* z180 MH arbiter config*/ +#define Z180_CFG_MHARB \ + (0x10 \ + | (0 << MH_ARBITER_CONFIG__SAME_PAGE_GRANULARITY__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__L1_ARB_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__L1_ARB_HOLD_ENABLE__SHIFT) \ + | (0 << MH_ARBITER_CONFIG__L2_ARB_CONTROL__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__PAGE_SIZE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__TC_REORDER_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__TC_ARB_HOLD_ENABLE__SHIFT) \ + | (0 << MH_ARBITER_CONFIG__IN_FLIGHT_LIMIT_ENABLE__SHIFT) \ + | (0x8 << MH_ARBITER_CONFIG__IN_FLIGHT_LIMIT__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__CP_CLNT_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__VGT_CLNT_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__TC_CLNT_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__RB_CLNT_ENABLE__SHIFT) \ + | (1 << MH_ARBITER_CONFIG__PA_CLNT_ENABLE__SHIFT)) + +#define Z180_TIMESTAMP_EPSILON 20000 +#define Z180_IDLE_COUNT_MAX 1000000 + +enum z180_cmdwindow_type { + Z180_CMDWINDOW_2D = 0x00000000, + Z180_CMDWINDOW_MMU = 0x00000002, +}; + +#define Z180_CMDWINDOW_TARGET_MASK 0x000000FF +#define Z180_CMDWINDOW_ADDR_MASK 0x00FFFF00 +#define Z180_CMDWINDOW_TARGET_SHIFT 0 +#define Z180_CMDWINDOW_ADDR_SHIFT 8 + +static int z180_start(struct kgsl_device *device, unsigned int init_ram); +static int z180_stop(struct kgsl_device *device); +static int z180_wait(struct kgsl_device *device, + unsigned int timestamp, + unsigned int msecs); +static void z180_regread(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int *value); +static void z180_regwrite(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int value); +static void z180_cmdwindow_write(struct kgsl_device *device, + unsigned int addr, + unsigned int data); + +#define Z180_MMU_CONFIG \ + (0x01 \ + | (MMU_CONFIG << MH_MMU_CONFIG__RB_W_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_W_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R0_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R1_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R2_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R3_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__CP_R4_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__VGT_R0_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__VGT_R1_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__TC_R_CLNT_BEHAVIOR__SHIFT) \ + | (MMU_CONFIG << MH_MMU_CONFIG__PA_W_CLNT_BEHAVIOR__SHIFT)) + +static const struct kgsl_functable z180_functable; + +static struct z180_device device_2d0 = { + .dev = { + .name = DEVICE_2D0_NAME, + .id = KGSL_DEVICE_2D0, + .ver_major = DRIVER_VERSION_MAJOR, + .ver_minor = DRIVER_VERSION_MINOR, + .mmu = { + .config = Z180_MMU_CONFIG, + /* turn off memory protection unit by setting + acceptable physical address range to include + all pages. */ + .mpu_base = 0x00000000, + .mpu_range = 0xFFFFF000, + }, + .pwrctrl = { + .regulator_name = "fs_gfx2d0", + .irq_name = KGSL_2D0_IRQ, + }, + .mutex = __MUTEX_INITIALIZER(device_2d0.dev.mutex), + .state = KGSL_STATE_INIT, + .active_cnt = 0, + .iomemname = KGSL_2D0_REG_MEMORY, + .ftbl = &z180_functable, + .display_off = { +#ifdef CONFIG_HAS_EARLYSUSPEND + .level = EARLY_SUSPEND_LEVEL_STOP_DRAWING, + .suspend = kgsl_early_suspend_driver, + .resume = kgsl_late_resume_driver, +#endif + }, + }, +}; + +static struct z180_device device_2d1 = { + .dev = { + .name = DEVICE_2D1_NAME, + .id = KGSL_DEVICE_2D1, + .ver_major = DRIVER_VERSION_MAJOR, + .ver_minor = DRIVER_VERSION_MINOR, + .mmu = { + .config = Z180_MMU_CONFIG, + /* turn off memory protection unit by setting + acceptable physical address range to include + all pages. */ + .mpu_base = 0x00000000, + .mpu_range = 0xFFFFF000, + }, + .pwrctrl = { + .regulator_name = "fs_gfx2d1", + .irq_name = KGSL_2D1_IRQ, + }, + .mutex = __MUTEX_INITIALIZER(device_2d1.dev.mutex), + .state = KGSL_STATE_INIT, + .active_cnt = 0, + .iomemname = KGSL_2D1_REG_MEMORY, + .ftbl = &z180_functable, + .display_off = { +#ifdef CONFIG_HAS_EARLYSUSPEND + .level = EARLY_SUSPEND_LEVEL_STOP_DRAWING, + .suspend = kgsl_early_suspend_driver, + .resume = kgsl_late_resume_driver, +#endif + }, + }, +}; + +static irqreturn_t z180_isr(int irq, void *data) +{ + irqreturn_t result = IRQ_NONE; + unsigned int status; + struct kgsl_device *device = (struct kgsl_device *) data; + struct z180_device *z180_dev = Z180_DEVICE(device); + + z180_regread(device, ADDR_VGC_IRQSTATUS >> 2, &status); + + if (status & GSL_VGC_INT_MASK) { + z180_regwrite(device, + ADDR_VGC_IRQSTATUS >> 2, status & GSL_VGC_INT_MASK); + + result = IRQ_HANDLED; + + if (status & REG_VGC_IRQSTATUS__FIFO_MASK) + KGSL_DRV_ERR(device, "z180 fifo interrupt\n"); + if (status & REG_VGC_IRQSTATUS__MH_MASK) + kgsl_mh_intrcallback(device); + if (status & REG_VGC_IRQSTATUS__G2D_MASK) { + int count; + + z180_regread(device, + ADDR_VGC_IRQ_ACTIVE_CNT >> 2, + &count); + + count >>= 8; + count &= 255; + z180_dev->timestamp += count; + + wake_up_interruptible(&device->wait_queue); + + atomic_notifier_call_chain( + &(device->ts_notifier_list), + device->id, NULL); + } + } + + if ((device->pwrctrl.nap_allowed == true) && + (device->requested_state == KGSL_STATE_NONE)) { + device->requested_state = KGSL_STATE_NAP; + queue_work(device->work_queue, &device->idle_check_ws); + } + mod_timer(&device->idle_timer, + jiffies + device->pwrctrl.interval_timeout); + + return result; +} + +static int z180_cleanup_pt(struct kgsl_device *device, + struct kgsl_pagetable *pagetable) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + + kgsl_mmu_unmap(pagetable, &device->mmu.dummyspace); + + kgsl_mmu_unmap(pagetable, &device->memstore); + + kgsl_mmu_unmap(pagetable, &z180_dev->ringbuffer.cmdbufdesc); + + return 0; +} + +static int z180_setup_pt(struct kgsl_device *device, + struct kgsl_pagetable *pagetable) +{ + int result = 0; + struct z180_device *z180_dev = Z180_DEVICE(device); + + result = kgsl_mmu_map_global(pagetable, &device->mmu.dummyspace, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + + if (result) + goto error; + + result = kgsl_mmu_map_global(pagetable, &device->memstore, + GSL_PT_PAGE_RV | GSL_PT_PAGE_WV); + if (result) + goto error_unmap_dummy; + + result = kgsl_mmu_map_global(pagetable, + &z180_dev->ringbuffer.cmdbufdesc, + GSL_PT_PAGE_RV); + if (result) + goto error_unmap_memstore; + return result; + +error_unmap_dummy: + kgsl_mmu_unmap(pagetable, &device->mmu.dummyspace); + +error_unmap_memstore: + kgsl_mmu_unmap(pagetable, &device->memstore); + +error: + return result; +} + +static inline unsigned int rb_offset(unsigned int index) +{ + return index*sizeof(unsigned int)*(Z180_PACKET_SIZE); +} + +static void addmarker(struct z180_ringbuffer *rb, unsigned int index) +{ + char *ptr = (char *)(rb->cmdbufdesc.hostptr); + unsigned int *p = (unsigned int *)(ptr + rb_offset(index)); + + *p++ = Z180_STREAM_PACKET; + *p++ = (Z180_MARKER_CMD | 5); + *p++ = ADDR_VGV3_LAST << 24; + *p++ = ADDR_VGV3_LAST << 24; + *p++ = ADDR_VGV3_LAST << 24; + *p++ = Z180_STREAM_PACKET; + *p++ = 5; + *p++ = ADDR_VGV3_LAST << 24; + *p++ = ADDR_VGV3_LAST << 24; + *p++ = ADDR_VGV3_LAST << 24; +} + +static void addcmd(struct z180_ringbuffer *rb, unsigned int index, + unsigned int cmd, unsigned int nextcnt) +{ + char * ptr = (char *)(rb->cmdbufdesc.hostptr); + unsigned int *p = (unsigned int *)(ptr + (rb_offset(index) + + (Z180_MARKER_SIZE * sizeof(unsigned int)))); + + *p++ = Z180_STREAM_PACKET_CALL; + *p++ = cmd; + *p++ = Z180_CALL_CMD | nextcnt; + *p++ = ADDR_VGV3_LAST << 24; + *p++ = ADDR_VGV3_LAST << 24; +} + +static void z180_cmdstream_start(struct kgsl_device *device) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + unsigned int cmd = VGV3_NEXTCMD_JUMP << VGV3_NEXTCMD_NEXTCMD_FSHIFT; + + z180_dev->timestamp = 0; + z180_dev->current_timestamp = 0; + + addmarker(&z180_dev->ringbuffer, 0); + + z180_cmdwindow_write(device, ADDR_VGV3_MODE, 4); + + z180_cmdwindow_write(device, ADDR_VGV3_NEXTADDR, + z180_dev->ringbuffer.cmdbufdesc.gpuaddr); + + z180_cmdwindow_write(device, ADDR_VGV3_NEXTCMD, cmd | 5); + + z180_cmdwindow_write(device, ADDR_VGV3_WRITEADDR, + device->memstore.gpuaddr); + + cmd = (int)(((1) & VGV3_CONTROL_MARKADD_FMASK) + << VGV3_CONTROL_MARKADD_FSHIFT); + + z180_cmdwindow_write(device, ADDR_VGV3_CONTROL, cmd); + + z180_cmdwindow_write(device, ADDR_VGV3_CONTROL, 0); +} + +static int room_in_rb(struct z180_device *device) +{ + int ts_diff; + + ts_diff = device->current_timestamp - device->timestamp; + + return ts_diff < Z180_PACKET_COUNT; +} + +static int z180_idle(struct kgsl_device *device, unsigned int timeout) +{ + int status = 0; + struct z180_device *z180_dev = Z180_DEVICE(device); + + if (z180_dev->current_timestamp > z180_dev->timestamp) + status = z180_wait(device, z180_dev->current_timestamp, + timeout); + + if (status) + KGSL_DRV_ERR(device, "z180_waittimestamp() timed out\n"); + + return status; +} + +static void z180_setstate(struct kgsl_device *device, uint32_t flags) +{ + kgsl_default_setstate(device, flags); +} + +int +z180_cmdstream_issueibcmds(struct kgsl_device_private *dev_priv, + struct kgsl_context *context, + struct kgsl_ibdesc *ibdesc, + unsigned int numibs, + uint32_t *timestamp, + unsigned int ctrl) +{ + unsigned int result = 0; + unsigned int ofs = PACKETSIZE_STATESTREAM * sizeof(unsigned int); + unsigned int cnt = 5; + unsigned int nextaddr = 0; + unsigned int index = 0; + unsigned int nextindex; + unsigned int nextcnt = Z180_STREAM_END_CMD | 5; + struct kgsl_memdesc tmp = {0}; + unsigned int cmd; + struct kgsl_device *device = dev_priv->device; + struct kgsl_pagetable *pagetable = dev_priv->process_priv->pagetable; + struct z180_device *z180_dev = Z180_DEVICE(device); + unsigned int sizedwords; + + if (device->state & KGSL_STATE_HUNG) { + return -EINVAL; + goto error; + } + if (numibs != 1) { + KGSL_DRV_ERR(device, "Invalid number of ibs: %d\n", numibs); + result = -EINVAL; + goto error; + } + cmd = ibdesc[0].gpuaddr; + sizedwords = ibdesc[0].sizedwords; + + tmp.hostptr = (void *)*timestamp; + + KGSL_CMD_INFO(device, "ctxt %d ibaddr 0x%08x sizedwords %d\n", + context->id, cmd, sizedwords); + /* context switch */ + if ((context->id != (int)z180_dev->ringbuffer.prevctx) || + (ctrl & KGSL_CONTEXT_CTX_SWITCH)) { + KGSL_CMD_INFO(device, "context switch %d -> %d\n", + context->id, z180_dev->ringbuffer.prevctx); + kgsl_mmu_setstate(device, pagetable); + cnt = PACKETSIZE_STATESTREAM; + ofs = 0; + } + z180_setstate(device, kgsl_pt_get_flags(device->mmu.hwpagetable, + device->id)); + + result = wait_event_interruptible_timeout(device->wait_queue, + room_in_rb(z180_dev), + msecs_to_jiffies(KGSL_TIMEOUT_DEFAULT)); + if (result < 0) { + KGSL_CMD_ERR(device, "wait_event_interruptible_timeout " + "failed: %d\n", result); + goto error; + } + result = 0; + + index = z180_dev->current_timestamp % Z180_PACKET_COUNT; + z180_dev->current_timestamp++; + nextindex = z180_dev->current_timestamp % Z180_PACKET_COUNT; + *timestamp = z180_dev->current_timestamp; + + z180_dev->ringbuffer.prevctx = context->id; + + addcmd(&z180_dev->ringbuffer, index, cmd + ofs, cnt); + + /* Make sure the next ringbuffer entry has a marker */ + addmarker(&z180_dev->ringbuffer, nextindex); + + nextaddr = z180_dev->ringbuffer.cmdbufdesc.gpuaddr + + rb_offset(nextindex); + + tmp.hostptr = (void *)(tmp.hostptr + + (sizedwords * sizeof(unsigned int))); + tmp.size = 12; + + kgsl_sharedmem_writel(&tmp, 4, nextaddr); + kgsl_sharedmem_writel(&tmp, 8, nextcnt); + + /* sync memory before activating the hardware for the new command*/ + mb(); + + cmd = (int)(((2) & VGV3_CONTROL_MARKADD_FMASK) + << VGV3_CONTROL_MARKADD_FSHIFT); + + z180_cmdwindow_write(device, ADDR_VGV3_CONTROL, cmd); + z180_cmdwindow_write(device, ADDR_VGV3_CONTROL, 0); +error: + return result; +} + +static int z180_ringbuffer_init(struct kgsl_device *device) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + memset(&z180_dev->ringbuffer, 0, sizeof(struct z180_ringbuffer)); + z180_dev->ringbuffer.prevctx = Z180_INVALID_CONTEXT; + return kgsl_allocate_contiguous(&z180_dev->ringbuffer.cmdbufdesc, + Z180_RB_SIZE); +} + +static void z180_ringbuffer_close(struct kgsl_device *device) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + kgsl_sharedmem_free(&z180_dev->ringbuffer.cmdbufdesc); + memset(&z180_dev->ringbuffer, 0, sizeof(struct z180_ringbuffer)); +} + +static int __devinit z180_probe(struct platform_device *pdev) +{ + int status = -EINVAL; + struct kgsl_device *device = NULL; + struct z180_device *z180_dev; + + device = (struct kgsl_device *)pdev->id_entry->driver_data; + device->parentdev = &pdev->dev; + + z180_dev = Z180_DEVICE(device); + spin_lock_init(&z180_dev->cmdwin_lock); + + status = z180_ringbuffer_init(device); + if (status != 0) + goto error; + + status = kgsl_device_platform_probe(device, z180_isr); + if (status) + goto error_close_ringbuffer; + + return status; + +error_close_ringbuffer: + z180_ringbuffer_close(device); +error: + device->parentdev = NULL; + return status; +} + +static int __devexit z180_remove(struct platform_device *pdev) +{ + struct kgsl_device *device = NULL; + + device = (struct kgsl_device *)pdev->id_entry->driver_data; + + kgsl_device_platform_remove(device); + + z180_ringbuffer_close(device); + + return 0; +} + +static int z180_start(struct kgsl_device *device, unsigned int init_ram) +{ + int status = 0; + + device->state = KGSL_STATE_INIT; + device->requested_state = KGSL_STATE_NONE; + KGSL_PWR_WARN(device, "state -> INIT, device %d\n", device->id); + + kgsl_pwrctrl_enable(device); + + /* Set up MH arbiter. MH offsets are considered to be dword + * based, therefore no down shift. */ + z180_regwrite(device, ADDR_MH_ARBITER_CONFIG, Z180_CFG_MHARB); + + z180_regwrite(device, ADDR_MH_CLNT_INTF_CTRL_CONFIG1, 0x00030F27); + z180_regwrite(device, ADDR_MH_CLNT_INTF_CTRL_CONFIG2, 0x004B274F); + + /* Set interrupts to 0 to ensure a good state */ + z180_regwrite(device, (ADDR_VGC_IRQENABLE >> 2), 0x0); + + status = kgsl_mmu_start(device); + if (status) + goto error_clk_off; + + z180_cmdstream_start(device); + + mod_timer(&device->idle_timer, jiffies + FIRST_TIMEOUT); + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_IRQ_ON); + return 0; + +error_clk_off: + z180_regwrite(device, (ADDR_VGC_IRQENABLE >> 2), 0); + kgsl_pwrctrl_disable(device); + return status; +} + +static int z180_stop(struct kgsl_device *device) +{ + z180_idle(device, KGSL_TIMEOUT_DEFAULT); + + del_timer(&device->idle_timer); + + kgsl_mmu_stop(device); + + /* Disable the clocks before the power rail. */ + kgsl_pwrctrl_irq(device, KGSL_PWRFLAGS_OFF); + + kgsl_pwrctrl_disable(device); + + return 0; +} + +static int z180_getproperty(struct kgsl_device *device, + enum kgsl_property_type type, + void *value, + unsigned int sizebytes) +{ + int status = -EINVAL; + + switch (type) { + case KGSL_PROP_DEVICE_INFO: + { + struct kgsl_devinfo devinfo; + + if (sizebytes != sizeof(devinfo)) { + status = -EINVAL; + break; + } + + memset(&devinfo, 0, sizeof(devinfo)); + devinfo.device_id = device->id+1; + devinfo.chip_id = 0; + devinfo.mmu_enabled = kgsl_mmu_enabled(); + + if (copy_to_user(value, &devinfo, sizeof(devinfo)) != + 0) { + status = -EFAULT; + break; + } + status = 0; + } + break; + case KGSL_PROP_MMU_ENABLE: + { +#ifdef CONFIG_MSM_KGSL_MMU + int mmuProp = 1; +#else + int mmuProp = 0; +#endif + if (sizebytes != sizeof(int)) { + status = -EINVAL; + break; + } + if (copy_to_user(value, &mmuProp, sizeof(mmuProp))) { + status = -EFAULT; + break; + } + status = 0; + } + break; + + default: + KGSL_DRV_ERR(device, "invalid property: %d\n", type); + status = -EINVAL; + } + return status; +} + +static unsigned int z180_isidle(struct kgsl_device *device) +{ + int status = false; + struct z180_device *z180_dev = Z180_DEVICE(device); + + int timestamp = z180_dev->timestamp; + + if (timestamp == z180_dev->current_timestamp) + status = true; + + return status; +} + +static int z180_suspend_context(struct kgsl_device *device) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + + z180_dev->ringbuffer.prevctx = Z180_INVALID_CONTEXT; + + return 0; +} + +/* Not all Z180 registers are directly accessible. + * The _z180_(read|write)_simple functions below handle the ones that are. + */ +static void _z180_regread_simple(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int *value) +{ + unsigned int *reg; + + BUG_ON(offsetwords * sizeof(uint32_t) >= device->regspace.sizebytes); + + reg = (unsigned int *)(device->regspace.mmio_virt_base + + (offsetwords << 2)); + + /*ensure this read finishes before the next one. + * i.e. act like normal readl() */ + *value = __raw_readl(reg); + rmb(); + +} + +static void _z180_regwrite_simple(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int value) +{ + unsigned int *reg; + + BUG_ON(offsetwords*sizeof(uint32_t) >= device->regspace.sizebytes); + + reg = (unsigned int *)(device->regspace.mmio_virt_base + + (offsetwords << 2)); + kgsl_cffdump_regwrite(device->id, offsetwords << 2, value); + /*ensure previous writes post before this one, + * i.e. act like normal writel() */ + wmb(); + __raw_writel(value, reg); +} + + +/* The MH registers must be accessed through via a 2 step write, (read|write) + * process. These registers may be accessed from interrupt context during + * the handling of MH or MMU error interrupts. Therefore a spin lock is used + * to ensure that the 2 step sequence is not interrupted. + */ +static void _z180_regread_mmu(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int *value) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + unsigned long flags; + + spin_lock_irqsave(&z180_dev->cmdwin_lock, flags); + _z180_regwrite_simple(device, (ADDR_VGC_MH_READ_ADDR >> 2), + offsetwords); + _z180_regread_simple(device, (ADDR_VGC_MH_DATA_ADDR >> 2), value); + spin_unlock_irqrestore(&z180_dev->cmdwin_lock, flags); +} + + +static void _z180_regwrite_mmu(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int value) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + unsigned int cmdwinaddr; + unsigned long flags; + + cmdwinaddr = ((Z180_CMDWINDOW_MMU << Z180_CMDWINDOW_TARGET_SHIFT) & + Z180_CMDWINDOW_TARGET_MASK); + cmdwinaddr |= ((offsetwords << Z180_CMDWINDOW_ADDR_SHIFT) & + Z180_CMDWINDOW_ADDR_MASK); + + spin_lock_irqsave(&z180_dev->cmdwin_lock, flags); + _z180_regwrite_simple(device, ADDR_VGC_MMUCOMMANDSTREAM >> 2, + cmdwinaddr); + _z180_regwrite_simple(device, ADDR_VGC_MMUCOMMANDSTREAM >> 2, value); + spin_unlock_irqrestore(&z180_dev->cmdwin_lock, flags); +} + +/* the rest of the code doesn't want to think about if it is writing mmu + * registers or normal registers so handle it here + */ +static void z180_regread(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int *value) +{ + if (!in_interrupt()) + kgsl_pre_hwaccess(device); + + if ((offsetwords >= ADDR_MH_ARBITER_CONFIG && + offsetwords <= ADDR_MH_AXI_HALT_CONTROL) || + (offsetwords >= MH_MMU_CONFIG && + offsetwords <= MH_MMU_MPU_END)) { + _z180_regread_mmu(device, offsetwords, value); + } else { + _z180_regread_simple(device, offsetwords, value); + } +} + +static void z180_regwrite(struct kgsl_device *device, + unsigned int offsetwords, + unsigned int value) +{ + if (!in_interrupt()) + kgsl_pre_hwaccess(device); + + if ((offsetwords >= ADDR_MH_ARBITER_CONFIG && + offsetwords <= ADDR_MH_CLNT_INTF_CTRL_CONFIG2) || + (offsetwords >= MH_MMU_CONFIG && + offsetwords <= MH_MMU_MPU_END)) { + _z180_regwrite_mmu(device, offsetwords, value); + } else { + _z180_regwrite_simple(device, offsetwords, value); + } +} + +static void z180_cmdwindow_write(struct kgsl_device *device, + unsigned int addr, unsigned int data) +{ + unsigned int cmdwinaddr; + + cmdwinaddr = ((Z180_CMDWINDOW_2D << Z180_CMDWINDOW_TARGET_SHIFT) & + Z180_CMDWINDOW_TARGET_MASK); + cmdwinaddr |= ((addr << Z180_CMDWINDOW_ADDR_SHIFT) & + Z180_CMDWINDOW_ADDR_MASK); + + z180_regwrite(device, ADDR_VGC_COMMANDSTREAM >> 2, cmdwinaddr); + z180_regwrite(device, ADDR_VGC_COMMANDSTREAM >> 2, data); +} + +static unsigned int z180_readtimestamp(struct kgsl_device *device, + enum kgsl_timestamp_type type) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + /* get current EOP timestamp */ + return z180_dev->timestamp; +} + +static int z180_waittimestamp(struct kgsl_device *device, + unsigned int timestamp, + unsigned int msecs) +{ + int status = -EINVAL; + mutex_unlock(&device->mutex); + status = z180_wait(device, timestamp, msecs); + mutex_lock(&device->mutex); + + return status; +} + +static int z180_wait(struct kgsl_device *device, + unsigned int timestamp, + unsigned int msecs) +{ + int status = -EINVAL; + long timeout = 0; + + timeout = wait_io_event_interruptible_timeout( + device->wait_queue, + kgsl_check_timestamp(device, timestamp), + msecs_to_jiffies(msecs)); + + if (timeout > 0) + status = 0; + else if (timeout == 0) { + status = -ETIMEDOUT; + device->state = KGSL_STATE_HUNG; + KGSL_PWR_WARN(device, "state -> HUNG, device %d\n", device->id); + } else + status = timeout; + + return status; +} + +static void +z180_drawctxt_destroy(struct kgsl_device *device, + struct kgsl_context *context) +{ + struct z180_device *z180_dev = Z180_DEVICE(device); + + z180_idle(device, KGSL_TIMEOUT_DEFAULT); + + if (z180_dev->ringbuffer.prevctx == context->id) { + z180_dev->ringbuffer.prevctx = Z180_INVALID_CONTEXT; + device->mmu.hwpagetable = device->mmu.defaultpagetable; + kgsl_setstate(device, KGSL_MMUFLAGS_PTUPDATE); + } +} + +static void z180_power_stats(struct kgsl_device *device, + struct kgsl_power_stats *stats) +{ + stats->total_time = 0; + stats->busy_time = 0; +} + +static void z180_irqctrl(struct kgsl_device *device, int state) +{ + /* Control interrupts for Z180 and the Z180 MMU */ + + if (state) { + z180_regwrite(device, (ADDR_VGC_IRQENABLE >> 2), 3); + z180_regwrite(device, MH_INTERRUPT_MASK, KGSL_MMU_INT_MASK); + } else { + z180_regwrite(device, (ADDR_VGC_IRQENABLE >> 2), 0); + z180_regwrite(device, MH_INTERRUPT_MASK, 0); + } +} + +static const struct kgsl_functable z180_functable = { + /* Mandatory functions */ + .regread = z180_regread, + .regwrite = z180_regwrite, + .idle = z180_idle, + .isidle = z180_isidle, + .suspend_context = z180_suspend_context, + .start = z180_start, + .stop = z180_stop, + .getproperty = z180_getproperty, + .waittimestamp = z180_waittimestamp, + .readtimestamp = z180_readtimestamp, + .issueibcmds = z180_cmdstream_issueibcmds, + .setup_pt = z180_setup_pt, + .cleanup_pt = z180_cleanup_pt, + .power_stats = z180_power_stats, + .irqctrl = z180_irqctrl, + /* Optional functions */ + .setstate = z180_setstate, + .drawctxt_create = NULL, + .drawctxt_destroy = z180_drawctxt_destroy, + .ioctl = NULL, +}; + +static struct platform_device_id z180_id_table[] = { + { DEVICE_2D0_NAME, (kernel_ulong_t)&device_2d0.dev, }, + { DEVICE_2D1_NAME, (kernel_ulong_t)&device_2d1.dev, }, + { }, +}; +MODULE_DEVICE_TABLE(platform, z180_id_table); + +static struct platform_driver z180_platform_driver = { + .probe = z180_probe, + .remove = __devexit_p(z180_remove), + .suspend = kgsl_suspend_driver, + .resume = kgsl_resume_driver, + .id_table = z180_id_table, + .driver = { + .owner = THIS_MODULE, + .name = DEVICE_2D_NAME, + .pm = &kgsl_pm_ops, + } +}; + +static int __init kgsl_2d_init(void) +{ + return platform_driver_register(&z180_platform_driver); +} + +static void __exit kgsl_2d_exit(void) +{ + platform_driver_unregister(&z180_platform_driver); +} + +module_init(kgsl_2d_init); +module_exit(kgsl_2d_exit); + +MODULE_DESCRIPTION("2D Graphics driver"); +MODULE_VERSION("1.2"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:kgsl_2d"); diff --git a/drivers/gpu/msm/z180.h b/drivers/gpu/msm/z180.h new file mode 100644 index 00000000000..28b1cc6b336 --- /dev/null +++ b/drivers/gpu/msm/z180.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __Z180_H +#define __Z180_H + +#include "kgsl_device.h" + +#define DEVICE_2D_NAME "kgsl-2d" +#define DEVICE_2D0_NAME "kgsl-2d0" +#define DEVICE_2D1_NAME "kgsl-2d1" + +struct z180_ringbuffer { + unsigned int prevctx; + struct kgsl_memdesc cmdbufdesc; +}; + +struct z180_device { + struct kgsl_device dev; /* Must be first field in this struct */ + int current_timestamp; + int timestamp; + struct z180_ringbuffer ringbuffer; + spinlock_t cmdwin_lock; +}; + +#endif /* __Z180_H */ diff --git a/drivers/gpu/msm/z180_reg.h b/drivers/gpu/msm/z180_reg.h new file mode 100644 index 00000000000..a3b0412b9b0 --- /dev/null +++ b/drivers/gpu/msm/z180_reg.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2002,2007-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __Z80_REG_H +#define __Z80_REG_H + +#define REG_VGC_IRQSTATUS__MH_MASK 0x00000001L +#define REG_VGC_IRQSTATUS__G2D_MASK 0x00000002L +#define REG_VGC_IRQSTATUS__FIFO_MASK 0x00000004L + +#define MH_ARBITER_CONFIG__SAME_PAGE_GRANULARITY__SHIFT 0x00000006 +#define MH_ARBITER_CONFIG__L1_ARB_ENABLE__SHIFT 0x00000007 +#define MH_ARBITER_CONFIG__L1_ARB_HOLD_ENABLE__SHIFT 0x00000008 +#define MH_ARBITER_CONFIG__L2_ARB_CONTROL__SHIFT 0x00000009 +#define MH_ARBITER_CONFIG__PAGE_SIZE__SHIFT 0x0000000a +#define MH_ARBITER_CONFIG__TC_REORDER_ENABLE__SHIFT 0x0000000d +#define MH_ARBITER_CONFIG__TC_ARB_HOLD_ENABLE__SHIFT 0x0000000e +#define MH_ARBITER_CONFIG__IN_FLIGHT_LIMIT_ENABLE__SHIFT 0x0000000f +#define MH_ARBITER_CONFIG__IN_FLIGHT_LIMIT__SHIFT 0x00000010 +#define MH_ARBITER_CONFIG__CP_CLNT_ENABLE__SHIFT 0x00000016 +#define MH_ARBITER_CONFIG__VGT_CLNT_ENABLE__SHIFT 0x00000017 +#define MH_ARBITER_CONFIG__TC_CLNT_ENABLE__SHIFT 0x00000018 +#define MH_ARBITER_CONFIG__RB_CLNT_ENABLE__SHIFT 0x00000019 +#define MH_ARBITER_CONFIG__PA_CLNT_ENABLE__SHIFT 0x0000001a + +#define ADDR_MH_ARBITER_CONFIG 0x0A40 +#define ADDR_MH_AXI_HALT_CONTROL 0x0A50 +#define ADDR_MH_CLNT_INTF_CTRL_CONFIG1 0x0A54 +#define ADDR_MH_CLNT_INTF_CTRL_CONFIG2 0x0A55 +#define ADDR_VGC_MH_READ_ADDR 0x0510 +#define ADDR_VGC_MH_DATA_ADDR 0x0518 +#define ADDR_VGC_COMMANDSTREAM 0x0000 +#define ADDR_VGC_IRQENABLE 0x0438 +#define ADDR_VGC_IRQSTATUS 0x0418 +#define ADDR_VGC_IRQ_ACTIVE_CNT 0x04E0 +#define ADDR_VGC_MMUCOMMANDSTREAM 0x03FC +#define ADDR_VGV3_CONTROL 0x0070 +#define ADDR_VGV3_LAST 0x007F +#define ADDR_VGV3_MODE 0x0071 +#define ADDR_VGV3_NEXTADDR 0x0075 +#define ADDR_VGV3_NEXTCMD 0x0076 +#define ADDR_VGV3_WRITEADDR 0x0072 + +#endif /* __Z180_REG_H */ diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 5f888f7e7dc..ef3cb2e89d5 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -28,6 +28,14 @@ config HWMON_VID tristate default n +config SENSORS_WPCE775X + bool "Winbond WPCE775X" + depends on I2C + default n + help + This driver provides support for the Winbond WPCE775XX Embedded + Controller, which provides lcd backlight, LEDs, and Battery control. + config HWMON_DEBUG_CHIP bool "Hardware Monitoring Chip debugging messages" default n @@ -767,6 +775,19 @@ config SENSORS_MAX6650 This driver can also be built as a module. If so, the module will be called max6650. +config SENSORS_MSM_ADC + tristate "MSM ADC Driver for current measurement" + depends on ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_FSM9XXX + default n + help + Provides interface for measuring the ADC's on AMUX channels of XOADC, + MPP's and the XOTHERM on pmic8058 for msm8x60 and provides post processing + of the channel for the ADC Raw Data. For reading LTC and EPM ADC channels + say yes here to include support for measuring current in real-time + from various power-rails on the Fluid board. The ADC circuit + internally uses an array of LTC2499 and EPM ADCs in a differential + configuration to provide a flat set of channels that can be addressed. + config SENSORS_PC87360 tristate "National Semiconductor PC87360 family" select HWMON_VID diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 28061cfa0cd..eacfcb5430c 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -118,6 +118,8 @@ obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o +obj-$(CONFIG_SENSORS_WPCE775X) += wpce775x.o +obj-$(CONFIG_SENSORS_MSM_ADC) += msm_adc.o m_adcproc.o # PMBus drivers obj-$(CONFIG_PMBUS) += pmbus_core.o diff --git a/drivers/hwmon/m_adcproc.c b/drivers/hwmon/m_adcproc.c new file mode 100644 index 00000000000..70e505e734e --- /dev/null +++ b/drivers/hwmon/m_adcproc.c @@ -0,0 +1,469 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include + +#define KELVINMIL_DEGMIL 273160 + +static const struct adc_map_pt adcmap_batttherm[] = { + {2020, -30}, + {1923, -20}, + {1796, -10}, + {1640, 0}, + {1459, 10}, + {1260, 20}, + {1159, 25}, + {1059, 30}, + {871, 40}, + {706, 50}, + {567, 60}, + {453, 70}, + {364, 80} +}; + +static const struct adc_map_pt adcmap_msmtherm[] = { + {2150, -30}, + {2107, -20}, + {2037, -10}, + {1929, 0}, + {1776, 10}, + {1579, 20}, + {1467, 25}, + {1349, 30}, + {1108, 40}, + {878, 50}, + {677, 60}, + {513, 70}, + {385, 80}, + {287, 90}, + {215, 100}, + {186, 110}, + {107, 120} +}; + +static const struct adc_map_pt adcmap_ntcg104ef104fb[] = { + {696483, -40960}, + {649148, -39936}, + {605368, -38912}, + {564809, -37888}, + {527215, -36864}, + {492322, -35840}, + {460007, -34816}, + {429982, -33792}, + {402099, -32768}, + {376192, -31744}, + {352075, -30720}, + {329714, -29696}, + {308876, -28672}, + {289480, -27648}, + {271417, -26624}, + {254574, -25600}, + {238903, -24576}, + {224276, -23552}, + {210631, -22528}, + {197896, -21504}, + {186007, -20480}, + {174899, -19456}, + {164521, -18432}, + {154818, -17408}, + {145744, -16384}, + {137265, -15360}, + {129307, -14336}, + {121866, -13312}, + {114896, -12288}, + {108365, -11264}, + {102252, -10240}, + {96499, -9216}, + {91111, -8192}, + {86055, -7168}, + {81308, -6144}, + {76857, -5120}, + {72660, -4096}, + {68722, -3072}, + {65020, -2048}, + {61538, -1024}, + {58261, 0}, + {55177, 1024}, + {52274, 2048}, + {49538, 3072}, + {46962, 4096}, + {44531, 5120}, + {42243, 6144}, + {40083, 7168}, + {38045, 8192}, + {36122, 9216}, + {34308, 10240}, + {32592, 11264}, + {30972, 12288}, + {29442, 13312}, + {27995, 14336}, + {26624, 15360}, + {25333, 16384}, + {24109, 17408}, + {22951, 18432}, + {21854, 19456}, + {20807, 20480}, + {19831, 21504}, + {18899, 22528}, + {18016, 23552}, + {17178, 24576}, + {16384, 25600}, + {15631, 26624}, + {14916, 27648}, + {14237, 28672}, + {13593, 29696}, + {12976, 30720}, + {12400, 31744}, + {11848, 32768}, + {11324, 33792}, + {10825, 34816}, + {10354, 35840}, + {9900, 36864}, + {9471, 37888}, + {9062, 38912}, + {8674, 39936}, + {8306, 40960}, + {7951, 41984}, + {7616, 43008}, + {7296, 44032}, + {6991, 45056}, + {6701, 46080}, + {6424, 47104}, + {6160, 48128}, + {5908, 49152}, + {5667, 50176}, + {5439, 51200}, + {5219, 52224}, + {5010, 53248}, + {4810, 54272}, + {4619, 55296}, + {4440, 56320}, + {4263, 57344}, + {4097, 58368}, + {3938, 59392}, + {3785, 60416}, + {3637, 61440}, + {3501, 62464}, + {3368, 63488}, + {3240, 64512}, + {3118, 65536}, + {2998, 66560}, + {2889, 67584}, + {2782, 68608}, + {2680, 69632}, + {2581, 70656}, + {2490, 71680}, + {2397, 72704}, + {2310, 73728}, + {2227, 74752}, + {2147, 75776}, + {2064, 76800}, + {1998, 77824}, + {1927, 78848}, + {1860, 79872}, + {1795, 80896}, + {1736, 81920}, + {1673, 82944}, + {1615, 83968}, + {1560, 84992}, + {1507, 86016}, + {1456, 87040}, + {1407, 88064}, + {1360, 89088}, + {1314, 90112}, + {1271, 91136}, + {1228, 92160}, + {1189, 93184}, + {1150, 94208}, + {1112, 95232}, + {1076, 96256}, + {1042, 97280}, + {1008, 98304}, + {976, 99328}, + {945, 100352}, + {915, 101376}, + {886, 102400}, + {859, 103424}, + {832, 104448}, + {807, 105472}, + {782, 106496}, + {756, 107520}, + {735, 108544}, + {712, 109568}, + {691, 110592}, + {670, 111616}, + {650, 112640}, + {631, 113664}, + {612, 114688}, + {594, 115712}, + {577, 116736}, + {560, 117760}, + {544, 118784}, + {528, 119808}, + {513, 120832}, + {498, 121856}, + {483, 122880}, + {470, 123904}, + {457, 124928}, + {444, 125952}, + {431, 126976}, + {419, 128000} +}; + +static int32_t + adc_map_linear(const struct adc_map_pt *pts, + uint32_t tablesize, int32_t input, int64_t *output) +{ + bool descending = 1; + uint32_t i = 0; + + if ((pts == NULL) || (output == NULL)) + return -EINVAL; + + /* Check if table is descending or ascending */ + if (tablesize > 1) { + if (pts[0].x < pts[1].x) + descending = 0; + } + + while (i < tablesize) { + if ((descending == 1) && (pts[i].x < input)) { + /* table entry is less than measured + value and table is descending, stop */ + break; + } else if ((descending == 0) && + (pts[i].x > input)) { + /* table entry is greater than measured + value and table is ascending, stop */ + break; + } else + i++; + } + + if (i == 0) + *output = pts[0].y; + else if (i == tablesize) + *output = pts[tablesize-1].y; + else { + /* result is between search_index and search_index-1 */ + /* interpolate linearly */ + *output = (((int32_t) ((pts[i].y - pts[i-1].y)* + (input - pts[i-1].x))/ + (pts[i].x - pts[i-1].x))+ + pts[i-1].y); + } + + return 0; +} + +int32_t scale_default(int32_t adc_code, + const struct adc_properties *adc_properties, + const struct chan_properties *chan_properties, + struct adc_chan_result *adc_chan_result) +{ + bool negative_rawfromoffset = 0; + int32_t rawfromoffset = adc_code - chan_properties->adc_graph->offset; + + if (!chan_properties->gain_numerator || + !chan_properties->gain_denominator) + return -EINVAL; + + adc_chan_result->adc_code = adc_code; + if (rawfromoffset < 0) { + if (adc_properties->bipolar) { + rawfromoffset = (rawfromoffset ^ -1) + 1; + negative_rawfromoffset = 1; + } else + rawfromoffset = 0; + } + + if (rawfromoffset >= 1 << adc_properties->bitresolution) + rawfromoffset = (1 << adc_properties->bitresolution) - 1; + + adc_chan_result->measurement = (int64_t)rawfromoffset* + chan_properties->adc_graph->dx* + chan_properties->gain_denominator; + + /* do_div only perform positive integer division! */ + do_div(adc_chan_result->measurement, chan_properties->adc_graph->dy* + chan_properties->gain_numerator); + + if (negative_rawfromoffset) + adc_chan_result->measurement = + (adc_chan_result->measurement ^ -1) + 1; + + /* Note: adc_chan_result->measurement is in the unit of + * adc_properties.adc_reference. For generic channel processing, + * channel measurement is a scale/ratio relative to the adc + * reference input */ + adc_chan_result->physical = (int32_t) adc_chan_result->measurement; + + return 0; +} + +int32_t scale_batt_therm(int32_t adc_code, + const struct adc_properties *adc_properties, + const struct chan_properties *chan_properties, + struct adc_chan_result *adc_chan_result) +{ + scale_default(adc_code, adc_properties, chan_properties, + adc_chan_result); + /* convert mV ---> degC using the table */ + return adc_map_linear( + adcmap_batttherm, + sizeof(adcmap_batttherm)/sizeof(adcmap_batttherm[0]), + adc_chan_result->physical, + &adc_chan_result->physical); +} + +int32_t scale_msm_therm(int32_t adc_code, + const struct adc_properties *adc_properties, + const struct chan_properties *chan_properties, + struct adc_chan_result *adc_chan_result) +{ + scale_default(adc_code, adc_properties, chan_properties, + adc_chan_result); + /* convert mV ---> degC using the table */ + return adc_map_linear( + adcmap_msmtherm, + sizeof(adcmap_msmtherm)/sizeof(adcmap_msmtherm[0]), + adc_chan_result->physical, + &adc_chan_result->physical); +} + +int32_t scale_pmic_therm(int32_t adc_code, + const struct adc_properties *adc_properties, + const struct chan_properties *chan_properties, + struct adc_chan_result *adc_chan_result) +{ + /* 2mV/K */ + int32_t rawfromoffset = adc_code - chan_properties->adc_graph->offset; + + if (!chan_properties->gain_numerator || + !chan_properties->gain_denominator) + return -EINVAL; + + adc_chan_result->adc_code = adc_code; + if (rawfromoffset > 0) { + if (rawfromoffset >= 1 << adc_properties->bitresolution) + rawfromoffset = (1 << adc_properties->bitresolution) + - 1; + adc_chan_result->measurement = (int64_t)rawfromoffset* + chan_properties->adc_graph->dx* + chan_properties->gain_denominator*1000; + do_div(adc_chan_result->measurement, + chan_properties->adc_graph->dy* + chan_properties->gain_numerator*2); + } else { + adc_chan_result->measurement = 0; + } + /* Note: adc_chan_result->measurement is in the unit of + adc_properties.adc_reference */ + adc_chan_result->physical = (int32_t)adc_chan_result->measurement; + /* Change to .001 deg C */ + adc_chan_result->physical -= KELVINMIL_DEGMIL; + adc_chan_result->measurement <<= 1; + + return 0; +} + +/* Scales the ADC code to 0.001 degrees C using the map + * table for the XO thermistor. + */ +int32_t tdkntcgtherm(int32_t adc_code, + const struct adc_properties *adc_properties, + const struct chan_properties *chan_properties, + struct adc_chan_result *adc_chan_result) +{ + int32_t offset = chan_properties->adc_graph->offset, + dy = chan_properties->adc_graph->dy, + dx = chan_properties->adc_graph->dx, + fullscale_calibrated_adc_code; + + uint32_t rt_r25; + uint32_t num1, num2, denom; + + adc_chan_result->adc_code = adc_code; + fullscale_calibrated_adc_code = dy + offset; + /* The above is a short cut in math that would reduce a lot of + computation whereas the below expression + (adc_properties->adc_reference*dy+dx*offset+(dx>>1))/dx + is a more generic formula when the 2 reference voltages are + different than 0 and full scale voltage. */ + + if ((dy == 0) || (dx == 0) || + (offset >= fullscale_calibrated_adc_code)) { + return -EINVAL; + } else { + if (adc_code >= fullscale_calibrated_adc_code) { + rt_r25 = (uint32_t)-1; + } else if (adc_code <= offset) { + rt_r25 = 0; + } else { + /* The formula used is (adc_code of current reading - offset)/ + * (the calibrated fullscale adc code - adc_code of current reading). + * For this channel, at this time, chan_properties->gain_numerator = + * chan_properties->gain_denominator = 1, so no need to incorporate + * into the formula even though we could and multiply/divide by 1 + * which yields the same result but expensive on computation. */ + num1 = (adc_code - offset) << 14; + num2 = (fullscale_calibrated_adc_code - adc_code) >> 1; + denom = fullscale_calibrated_adc_code - adc_code; + + if ((int)denom <= 0) + rt_r25 = 0x7FFFFFFF; + else + rt_r25 = (num1 + num2) / denom; + } + + if (rt_r25 > 0x7FFFFFFF) + rt_r25 = 0x7FFFFFFF; + + adc_map_linear(adcmap_ntcg104ef104fb, + sizeof(adcmap_ntcg104ef104fb)/sizeof(adcmap_ntcg104ef104fb[0]), + (int32_t)rt_r25, &adc_chan_result->physical); + } + + return 0; +} + +int32_t scale_xtern_chgr_cur(int32_t adc_code, + const struct adc_properties *adc_properties, + const struct chan_properties *chan_properties, + struct adc_chan_result *adc_chan_result) +{ + int32_t rawfromoffset = adc_code - chan_properties->adc_graph->offset; + + if (!chan_properties->gain_numerator || + !chan_properties->gain_denominator) + return -EINVAL; + + adc_chan_result->adc_code = adc_code; + if (rawfromoffset > 0) { + if (rawfromoffset >= 1 << adc_properties->bitresolution) + rawfromoffset = (1 << adc_properties->bitresolution) + - 1; + adc_chan_result->measurement = ((int64_t)rawfromoffset * 5)* + chan_properties->adc_graph->dx* + chan_properties->gain_denominator; + do_div(adc_chan_result->measurement, + chan_properties->adc_graph->dy* + chan_properties->gain_numerator); + } else { + adc_chan_result->measurement = 0; + } + adc_chan_result->physical = (int32_t) adc_chan_result->measurement; + + return 0; +} diff --git a/drivers/hwmon/msm_adc.c b/drivers/hwmon/msm_adc.c new file mode 100644 index 00000000000..42bcd07731c --- /dev/null +++ b/drivers/hwmon/msm_adc.c @@ -0,0 +1,1538 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MSM_ADC_DRIVER_NAME "msm_adc" +#define MSM_ADC_MAX_FNAME 15 + +#define MSM_ADC_DALRPC_DEVICEID 0x02000067 +#define MSM_ADC_DALRPC_PORT_NAME "DAL00" +#define MSM_ADC_DALRPC_CPU SMD_APPS_MODEM + +#define MSM_ADC_DALRPC_CMD_REQ_CONV 9 +#define MSM_ADC_DALRPC_CMD_INPUT_PROP 11 + +#define MSM_ADC_DALRC_CONV_TIMEOUT (5 * HZ) /* 5 seconds */ + +enum dal_error { + DAL_ERROR_INVALID_DEVICE_IDX = 1, + DAL_ERROR_INVALID_CHANNEL_IDX, + DAL_ERROR_NULL_POINTER, + DAL_ERROR_DEVICE_QUEUE_FULL, + DAL_ERROR_INVALID_PROPERTY_LENGTH, + DAL_ERROR_REMOTE_EVENT_POOL_FULL +}; + +enum dal_result_status { + DAL_RESULT_STATUS_INVALID, + DAL_RESULT_STATUS_VALID +}; + +struct dal_conv_state { + struct dal_conv_slot context[MSM_ADC_DEV_MAX_INFLIGHT]; + struct list_head slots; + struct mutex list_lock; + struct semaphore slot_count; +}; + +struct adc_dev { + char *name; + uint32_t nchans; + struct dal_conv_state conv; + struct dal_translation transl; + struct sensor_device_attribute *sens_attr; + char **fnames; +}; + +struct msm_adc_drv { + /* Common to both XOADC and EPM */ + struct platform_device *pdev; + struct device *hwmon; + struct miscdevice misc; + /* XOADC variables */ + struct sensor_device_attribute *sens_attr; + struct workqueue_struct *wq; + atomic_t online; + atomic_t total_outst; + wait_queue_head_t total_outst_wait; + + /* EPM variables */ + void *dev_h; + struct adc_dev *devs[MSM_ADC_MAX_NUM_DEVS]; + struct mutex prop_lock; + atomic_t rpc_online; + atomic_t rpc_total_outst; + wait_queue_head_t rpc_total_outst_wait; +}; + +static bool epm_init; +static bool epm_fluid_enabled; + +/* Needed to support file_op interfaces */ +static struct msm_adc_drv *msm_adc_drv; + +static ssize_t msm_adc_show_curr(struct device *dev, + struct device_attribute *devattr, char *buf); + +static int msm_rpc_adc_blocking_conversion(struct msm_adc_drv *msm_adc, + uint32_t chan, struct adc_chan_result *result); + +static int msm_adc_blocking_conversion(struct msm_adc_drv *msm_adc, + uint32_t chan, struct adc_chan_result *result); + +static int msm_adc_open(struct inode *inode, struct file *file) +{ + struct msm_client_data *client; + struct msm_adc_drv *msm_adc = msm_adc_drv; + struct platform_device *pdev = msm_adc->pdev; + + client = kzalloc(sizeof(struct msm_client_data), GFP_KERNEL); + if (!client) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + if (!try_module_get(THIS_MODULE)) { + kfree(client); + return -EACCES; + } + + mutex_init(&client->lock); + INIT_LIST_HEAD(&client->complete_list); + init_waitqueue_head(&client->data_wait); + init_waitqueue_head(&client->outst_wait); + + client->online = 1; + + file->private_data = client; + + return nonseekable_open(inode, file); +} + +static inline void msm_adc_restore_slot(struct dal_conv_state *conv_s, + struct dal_conv_slot *slot) +{ + mutex_lock(&conv_s->list_lock); + list_add(&slot->list, &conv_s->slots); + mutex_unlock(&conv_s->list_lock); + + up(&conv_s->slot_count); +} + +static int no_pending_client_requests(struct msm_client_data *client) +{ + mutex_lock(&client->lock); + + if (client->num_outstanding == 0) { + mutex_unlock(&client->lock); + return 1; + } + + mutex_unlock(&client->lock); + + return 0; +} + +static int data_avail(struct msm_client_data *client, uint32_t *pending) +{ + uint32_t completed; + + mutex_lock(&client->lock); + completed = client->num_complete; + mutex_unlock(&client->lock); + + if (completed > 0) { + if (pending != NULL) + *pending = completed; + return 1; + } + + return 0; +} + +static int msm_adc_release(struct inode *inode, struct file *file) +{ + struct msm_client_data *client = file->private_data; + struct adc_conv_slot *slot, *tmp; + int rc; + struct msm_adc_platform_data *pdata = + msm_adc_drv->pdev->dev.platform_data; + struct msm_adc_channels *channel = pdata->channel; + + module_put(THIS_MODULE); + + mutex_lock(&client->lock); + + /* prevent any further requests while we teardown the client */ + client->online = 0; + + mutex_unlock(&client->lock); + + /* + * We may still have outstanding transactions in flight from this + * client that have not completed. Make sure they're completed + * before removing the client. + */ + rc = wait_event_interruptible(client->outst_wait, + no_pending_client_requests(client)); + if (rc) { + pr_err("%s: wait_event_interruptible failed rc = %d\n", + __func__, rc); + return rc; + } + + /* + * All transactions have completed. Add slot resources back to the + * appropriate devices. + */ + list_for_each_entry_safe(slot, tmp, &client->complete_list, list) { + slot->client = NULL; + list_del(&slot->list); + channel[slot->conv.result.chan].adc_access_fn->adc_restore_slot( + channel[slot->conv.result.chan].adc_dev_instance, slot); + } + + kfree(client); + + return 0; +} + +static int msm_adc_translate_dal_to_hwmon(struct msm_adc_drv *msm_adc, + uint32_t chan, + struct adc_dev_spec *dest) +{ + struct dal_translation *transl; + struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; + int i; + + for (i = 0; i < pdata->num_adc; i++) { + transl = &msm_adc->devs[i]->transl; + if (chan >= transl->hwmon_start && + chan <= transl->hwmon_end) { + dest->dal.dev_idx = transl->dal_dev_idx; + dest->hwmon_dev_idx = transl->hwmon_dev_idx; + dest->dal.chan_idx = chan - transl->hwmon_start; + return 0; + } + } + return -EINVAL; +} + +static int msm_adc_translate_hwmon_to_dal(struct msm_adc_drv *msm_adc, + struct adc_dev_spec *source, + uint32_t *chan) +{ + struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; + struct dal_translation *transl; + int i; + + for (i = 0; i < pdata->num_adc; i++) { + transl = &msm_adc->devs[i]->transl; + if (source->dal.dev_idx != transl->dal_dev_idx) + continue; + *chan = transl->hwmon_start + source->dal.chan_idx; + return 0; + } + return -EINVAL; +} + +static int msm_adc_getinputproperties(struct msm_adc_drv *msm_adc, + const char *lookup_name, + struct adc_dev_spec *result) +{ + struct device *dev = &msm_adc->pdev->dev; + int rc; + + mutex_lock(&msm_adc->prop_lock); + + rc = dalrpc_fcn_8(MSM_ADC_DALRPC_CMD_INPUT_PROP, msm_adc->dev_h, + lookup_name, strlen(lookup_name) + 1, + &result->dal, sizeof(struct dal_dev_spec)); + if (rc) { + dev_err(dev, "DAL getprop request failed: rc = %d\n", rc); + mutex_unlock(&msm_adc->prop_lock); + return -EIO; + } + + mutex_unlock(&msm_adc->prop_lock); + return rc; +} + +static int msm_adc_lookup(struct msm_adc_drv *msm_adc, + struct msm_adc_lookup *lookup) +{ + struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; + struct adc_dev_spec target; + int rc = 0, i = 0; + uint32_t len = 0; + + len = strnlen(lookup->name, MSM_ADC_MAX_CHAN_STR); + while (i < pdata->num_chan_supported) { + if (strncmp(lookup->name, pdata->channel[i].name, len)) + i++; + else + break; + } + + if (pdata->num_chan_supported > 0 && i < pdata->num_chan_supported) { + lookup->chan_idx = i; + } else if (msm_adc->dev_h) { + rc = msm_adc_getinputproperties(msm_adc, lookup->name, &target); + if (rc) { + pr_err("%s: Lookup failed for %s\n", __func__, + lookup->name); + return rc; + } + rc = msm_adc_translate_hwmon_to_dal(msm_adc, &target, + &lookup->chan_idx); + if (rc) + pr_err("%s: Translation failed for %s\n", __func__, + lookup->name); + } else { + pr_err("%s: Lookup failed for %s\n", __func__, lookup->name); + rc = -EINVAL; + } + return rc; +} + +static int msm_adc_aio_conversion(struct msm_adc_drv *msm_adc, + struct adc_chan_result *request, + struct msm_client_data *client) +{ + struct msm_adc_platform_data *pdata = + msm_adc_drv->pdev->dev.platform_data; + struct msm_adc_channels *channel = &pdata->channel[request->chan]; + struct adc_conv_slot *slot; + + /* we could block here, but only for a bounded time */ + channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance, + &slot); + + if (slot) { + atomic_inc(&msm_adc->total_outst); + mutex_lock(&client->lock); + client->num_outstanding++; + mutex_unlock(&client->lock); + + /* indicates non blocking request to callback handler */ + slot->blocking = 0; + slot->compk = NULL;/*For kernel space usage; n/a for usr space*/ + slot->conv.result.chan = client->adc_chan = request->chan; + slot->client = client; + slot->adc_request = START_OF_CONV; + slot->chan_path = channel->chan_path_type; + slot->chan_adc_config = channel->adc_config_type; + slot->chan_adc_calib = channel->adc_calib_type; + queue_work(msm_adc->wq, &slot->work); + return 0; + } + return -EBUSY; +} + +static int msm_adc_fluid_hw_deinit(struct msm_adc_drv *msm_adc) +{ + struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; + + if (!epm_init) + return -EINVAL; + + if (pdata->gpio_config == APROC_CONFIG && + epm_fluid_enabled && pdata->adc_fluid_disable != NULL) { + pdata->adc_fluid_disable(); + epm_fluid_enabled = false; + } + + return 0; +} + +static int msm_adc_fluid_hw_init(struct msm_adc_drv *msm_adc) +{ + struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; + + if (!epm_init) + return -EINVAL; + + if (!pdata->adc_fluid_enable) + return -ENODEV; + + printk(KERN_DEBUG "msm_adc_fluid_hw_init: Calling adc_fluid_enable.\n"); + + if (pdata->gpio_config == APROC_CONFIG && !epm_fluid_enabled) { + pdata->adc_fluid_enable(); + epm_fluid_enabled = true; + } + + /* return success for now but check for errors from hw init configuration */ + return 0; +} + +static int msm_adc_poll_complete(struct msm_adc_drv *msm_adc, + struct msm_client_data *client, uint32_t *pending) +{ + int rc; + + /* + * Don't proceed if there there's nothing queued on this client. + * We could deadlock otherwise in a single threaded scenario. + */ + if (no_pending_client_requests(client) && !data_avail(client, pending)) + return -EDEADLK; + + rc = wait_event_interruptible(client->data_wait, + data_avail(client, pending)); + if (rc) + return rc; + + return 0; +} + +static int msm_adc_read_result(struct msm_adc_drv *msm_adc, + struct msm_client_data *client, + struct adc_chan_result *result) +{ + struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; + struct msm_adc_channels *channel = pdata->channel; + struct adc_conv_slot *slot; + int rc = 0; + + mutex_lock(&client->lock); + + slot = list_first_entry(&client->complete_list, + struct adc_conv_slot, list); + if (!slot) { + mutex_unlock(&client->lock); + return -ENOMSG; + } + + slot->client = NULL; + list_del(&slot->list); + + client->num_complete--; + + mutex_unlock(&client->lock); + + *result = slot->conv.result; + + /* restore this slot to reserve */ + channel[slot->conv.result.chan].adc_access_fn->adc_restore_slot( + channel[slot->conv.result.chan].adc_dev_instance, slot); + + return rc; +} + +static long msm_adc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct msm_client_data *client = file->private_data; + struct msm_adc_drv *msm_adc = msm_adc_drv; + struct platform_device *pdev = msm_adc->pdev; + struct msm_adc_platform_data *pdata = pdev->dev.platform_data; + uint32_t block_res = 0; + + int rc; + + switch (cmd) { + case MSM_ADC_REQUEST: + { + struct adc_chan_result conv; + + if (copy_from_user(&conv, (void __user *)arg, + sizeof(struct adc_chan_result))) + return -EFAULT; + + if (conv.chan < pdata->num_chan_supported) { + rc = msm_adc_blocking_conversion(msm_adc, + conv.chan, &conv); + } else { + if (!msm_adc->dev_h) + return -EAGAIN; + + rc = msm_rpc_adc_blocking_conversion(msm_adc, + conv.chan, &conv); + } + if (rc) { + dev_dbg(&pdev->dev, "BLK conversion failed\n"); + return rc; + } + + if (copy_to_user((void __user *)arg, &conv, + sizeof(struct adc_chan_result))) + return -EFAULT; + break; + } + case MSM_ADC_AIO_REQUEST_BLOCK_RES: + block_res = 1; + case MSM_ADC_AIO_REQUEST: + { + struct adc_chan_result conv; + + if (copy_from_user(&conv, (void __user *)arg, + sizeof(struct adc_chan_result))) + return -EFAULT; + + if (conv.chan >= pdata->num_chan_supported) + return -EINVAL; + + rc = msm_adc_aio_conversion(msm_adc, &conv, client); + if (rc) { + dev_dbg(&pdev->dev, "AIO conversion failed\n"); + return rc; + } + if (copy_to_user((void __user *)arg, &conv, + sizeof(struct adc_chan_result))) + return -EFAULT; + break; + } + case MSM_ADC_AIO_POLL: + { + uint32_t completed; + + rc = msm_adc_poll_complete(msm_adc, client, &completed); + if (rc) { + dev_dbg(&pdev->dev, "poll request failed\n"); + return rc; + } + + if (copy_to_user((void __user *)arg, &completed, + sizeof(uint32_t))) + return -EFAULT; + + break; + } + case MSM_ADC_AIO_READ: + { + struct adc_chan_result result; + + rc = msm_adc_read_result(msm_adc, client, &result); + if (rc) { + dev_dbg(&pdev->dev, "read result failed\n"); + return rc; + } + + if (copy_to_user((void __user *)arg, &result, + sizeof(struct adc_chan_result))) + return -EFAULT; + break; + } + case MSM_ADC_LOOKUP: + { + struct msm_adc_lookup lookup; + + if (copy_from_user(&lookup, (void __user *)arg, + sizeof(struct msm_adc_lookup))) + return -EFAULT; + + rc = msm_adc_lookup(msm_adc, &lookup); + if (rc) { + dev_dbg(&pdev->dev, "No such channel: %s\n", + lookup.name); + return rc; + } + + if (copy_to_user((void __user *)arg, &lookup, + sizeof(struct msm_adc_lookup))) + return -EFAULT; + break; + } + case MSM_ADC_FLUID_INIT: + { + uint32_t result; + + result = msm_adc_fluid_hw_init(msm_adc); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) { + printk(KERN_ERR "MSM_ADC_FLUID_INIT: " + "copy_to_user returned an error.\n"); + return -EFAULT; + } + printk(KERN_DEBUG "MSM_ADC_FLUID_INIT: Success.\n"); + break; + } + case MSM_ADC_FLUID_DEINIT: + { + uint32_t result; + + result = msm_adc_fluid_hw_deinit(msm_adc); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) + return -EFAULT; + break; + } + default: + return -EINVAL; + } + + return 0; +} + +const struct file_operations msm_adc_fops = { + .open = msm_adc_open, + .release = msm_adc_release, + .unlocked_ioctl = msm_adc_ioctl, +}; + +static ssize_t msm_adc_show_curr(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct msm_adc_drv *msm_adc = dev_get_drvdata(dev); + struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; + struct adc_chan_result result; + int rc; + +#ifdef CONFIG_PMIC8058_XOADC + rc = pm8058_xoadc_registered(); + if (rc <= 0) + return -ENODEV; +#endif + if (attr->index < pdata->num_chan_supported) { + rc = msm_adc_blocking_conversion(msm_adc, + attr->index, &result); + } else { + if (pdata->gpio_config == APROC_CONFIG && !epm_fluid_enabled + && pdata->adc_fluid_enable != NULL) { + printk(KERN_DEBUG "This is to read ADC value for " + "Fluid EPM and init. Do it only once.\n"); + pdata->adc_fluid_enable(); + epm_fluid_enabled = true; + } + rc = msm_rpc_adc_blocking_conversion(msm_adc, + attr->index, &result); + } + if (rc) + return 0; + + return sprintf(buf, "Result: %lld Raw: %d\n", result.physical, + result.adc_code); +} + +static int msm_rpc_adc_blocking_conversion(struct msm_adc_drv *msm_adc, + uint32_t hwmon_chan, struct adc_chan_result *result) +{ + struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; + struct dal_conv_request params; + struct device *dev = &msm_adc->pdev->dev; + struct adc_dev *adc_dev; + struct dal_conv_state *conv_s; + struct dal_conv_slot *slot; + struct adc_dev_spec dest; + int timeout, rc = 0; + + if (pdata->gpio_config == APROC_CONFIG && + pdata->adc_gpio_enable != NULL) + pdata->adc_gpio_enable(hwmon_chan-pdata->num_chan_supported); + + rc = msm_adc_translate_dal_to_hwmon(msm_adc, hwmon_chan, &dest); + if (rc) { + dev_err(dev, "%s: translation from chan %u failed\n", + __func__, hwmon_chan); + if (pdata->gpio_config == APROC_CONFIG && + pdata->adc_gpio_disable != NULL) + pdata->adc_gpio_disable(hwmon_chan + -pdata->num_chan_supported); + return -EINVAL; + } + + adc_dev = msm_adc->devs[dest.hwmon_dev_idx]; + conv_s = &adc_dev->conv; + + down(&conv_s->slot_count); + + mutex_lock(&conv_s->list_lock); + + slot = list_first_entry(&conv_s->slots, struct dal_conv_slot, list); + list_del(&slot->list); + BUG_ON(!slot); + + mutex_unlock(&conv_s->list_lock); + + /* indicates blocking request to callback handler */ + slot->blocking = 1; + + params.target.dev_idx = dest.dal.dev_idx; + params.target.chan_idx = dest.dal.chan_idx; + params.cb_h = slot->cb_h; + + rc = dalrpc_fcn_8(MSM_ADC_DALRPC_CMD_REQ_CONV, msm_adc->dev_h, + ¶ms, sizeof(params), NULL, 0); + if (rc) { + dev_err(dev, "%s: Conversion for device = %u channel = %u" + " failed\n", __func__, params.target.dev_idx, + params.target.chan_idx); + + rc = -EIO; + goto blk_conv_err; + } + + timeout = wait_for_completion_interruptible_timeout(&slot->comp, + MSM_ADC_DALRC_CONV_TIMEOUT); + if (timeout == 0) { + dev_err(dev, "read for device = %u channel = %u timed out\n", + params.target.dev_idx, params.target.chan_idx); + rc = -ETIMEDOUT; + goto blk_conv_err; + } else if (timeout < 0) { + rc = -EINTR; + goto blk_conv_err; + } + + result->physical = (int64_t)slot->result.physical; + + if (slot->result.status == DAL_RESULT_STATUS_INVALID) + rc = -ENODATA; + +blk_conv_err: + if (pdata->gpio_config == APROC_CONFIG && + pdata->adc_gpio_disable != NULL) + pdata->adc_gpio_disable(hwmon_chan-pdata->num_chan_supported); + msm_adc_restore_slot(conv_s, slot); + + return rc; +} + +static int msm_adc_blocking_conversion(struct msm_adc_drv *msm_adc, + uint32_t hwmon_chan, struct adc_chan_result *result) +{ + struct adc_conv_slot *slot; + struct msm_adc_platform_data *pdata = + msm_adc_drv->pdev->dev.platform_data; + struct msm_adc_channels *channel = &pdata->channel[hwmon_chan]; + + channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance, + &slot); + if (slot) { + slot->conv.result.chan = hwmon_chan; + /* indicates blocking request to callback handler */ + slot->blocking = 1; + slot->adc_request = START_OF_CONV; + slot->chan_path = channel->chan_path_type; + slot->chan_adc_config = channel->adc_config_type; + slot->chan_adc_calib = channel->adc_calib_type; + queue_work(msm_adc_drv->wq, &slot->work); + + wait_for_completion_interruptible(&slot->comp); + *result = slot->conv.result; + channel->adc_access_fn->adc_restore_slot( + channel->adc_dev_instance, slot); + return 0; + } + return -EBUSY; +} + +int32_t adc_channel_open(uint32_t channel, void **h) +{ + struct msm_client_data *client; + struct msm_adc_drv *msm_adc = msm_adc_drv; + struct msm_adc_platform_data *pdata; + struct platform_device *pdev; + int i = 0; + + if (!msm_adc_drv) + return -EFAULT; + +#ifdef CONFIG_PMIC8058_XOADC + if (pm8058_xoadc_registered() <= 0) + return -ENODEV; +#endif + pdata = msm_adc->pdev->dev.platform_data; + pdev = msm_adc->pdev; + + while (i < pdata->num_chan_supported) { + if (channel == pdata->channel[i].channel_name) + break; + else + i++; + } + + if (i == pdata->num_chan_supported) + return -EBADF; /* unknown channel */ + + client = kzalloc(sizeof(struct msm_client_data), GFP_KERNEL); + if (!client) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + if (!try_module_get(THIS_MODULE)) { + kfree(client); + return -EACCES; + } + + mutex_init(&client->lock); + INIT_LIST_HEAD(&client->complete_list); + init_waitqueue_head(&client->data_wait); + init_waitqueue_head(&client->outst_wait); + + client->online = 1; + client->adc_chan = i; + *h = (void *)client; + return 0; +} + +int32_t adc_channel_close(void *h) +{ + struct msm_client_data *client = (struct msm_client_data *)h; + + kfree(client); + return 0; +} + +int32_t adc_channel_request_conv(void *h, struct completion *conv_complete_evt) +{ + struct msm_client_data *client = (struct msm_client_data *)h; + struct msm_adc_platform_data *pdata = + msm_adc_drv->pdev->dev.platform_data; + struct msm_adc_channels *channel = &pdata->channel[client->adc_chan]; + struct adc_conv_slot *slot; + + channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance, + &slot); + + if (slot) { + atomic_inc(&msm_adc_drv->total_outst); + mutex_lock(&client->lock); + client->num_outstanding++; + mutex_unlock(&client->lock); + + slot->conv.result.chan = client->adc_chan; + slot->blocking = 0; + slot->compk = conv_complete_evt; + slot->client = client; + slot->adc_request = START_OF_CONV; + slot->chan_path = channel->chan_path_type; + slot->chan_adc_config = channel->adc_config_type; + slot->chan_adc_calib = channel->adc_calib_type; + queue_work(msm_adc_drv->wq, &slot->work); + return 0; + } + return -EBUSY; +} + +int32_t adc_channel_read_result(void *h, struct adc_chan_result *chan_result) +{ + struct msm_client_data *client = (struct msm_client_data *)h; + struct msm_adc_platform_data *pdata = + msm_adc_drv->pdev->dev.platform_data; + struct msm_adc_channels *channel = pdata->channel; + struct adc_conv_slot *slot; + int rc = 0; + + mutex_lock(&client->lock); + + slot = list_first_entry(&client->complete_list, + struct adc_conv_slot, list); + if (!slot) { + mutex_unlock(&client->lock); + return -ENOMSG; + } + + slot->client = NULL; + list_del(&slot->list); + + client->num_complete--; + + mutex_unlock(&client->lock); + + *chan_result = slot->conv.result; + + /* restore this slot to reserve */ + channel[slot->conv.result.chan].adc_access_fn->adc_restore_slot( + channel[slot->conv.result.chan].adc_dev_instance, slot); + + return rc; +} + +int32_t adc_calib_request(void *h, struct completion *calib_complete_evt) +{ + struct msm_client_data *client = (struct msm_client_data *)h; + struct msm_adc_platform_data *pdata = + msm_adc_drv->pdev->dev.platform_data; + struct msm_adc_channels *channel = &pdata->channel[client->adc_chan]; + struct adc_conv_slot *slot; + int rc, calib_status; + + channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance, + &slot); + if (slot) { + slot->conv.result.chan = client->adc_chan; + slot->blocking = 0; + slot->compk = calib_complete_evt; + slot->adc_request = START_OF_CALIBRATION; + slot->chan_path = channel->chan_path_type; + slot->chan_adc_config = channel->adc_config_type; + slot->chan_adc_calib = channel->adc_calib_type; + rc = channel->adc_access_fn->adc_calibrate( + channel->adc_dev_instance, slot, &calib_status); + + if (calib_status == CALIB_NOT_REQUIRED) { + channel->adc_access_fn->adc_restore_slot( + channel->adc_dev_instance, slot); + /* client will always wait in case when + calibration is not required */ + complete(calib_complete_evt); + } else { + atomic_inc(&msm_adc_drv->total_outst); + mutex_lock(&client->lock); + client->num_outstanding++; + mutex_unlock(&client->lock); + } + + return rc; + } + return -EBUSY; +} + +static void msm_rpc_adc_conv_cb(void *context, u32 param, + void *evt_buf, u32 len) +{ + struct dal_adc_result *result = evt_buf; + struct dal_conv_slot *slot = context; + struct msm_adc_drv *msm_adc = msm_adc_drv; + + memcpy(&slot->result, result, sizeof(slot->result)); + + /* for blocking requests, signal complete */ + if (slot->blocking) + complete(&slot->comp); + + /* for non-blocking requests, add slot to the client completed list */ + else { + struct msm_client_data *client = slot->client; + + mutex_lock(&client->lock); + + list_add(&slot->list, &client->complete_list); + client->num_complete++; + client->num_outstanding--; + + /* + * if the client release has been invoked and this is call + * corresponds to the last request, then signal release + * to complete. + */ + if (slot->client->online == 0 && client->num_outstanding == 0) + wake_up_interruptible_all(&client->outst_wait); + + mutex_unlock(&client->lock); + + wake_up_interruptible_all(&client->data_wait); + + atomic_dec(&msm_adc->total_outst); + + /* verify driver remove has not been invoked */ + if (atomic_read(&msm_adc->online) == 0 && + atomic_read(&msm_adc->total_outst) == 0) + wake_up_interruptible_all(&msm_adc->total_outst_wait); + } +} + +void msm_adc_conv_cb(void *context, u32 param, + void *evt_buf, u32 len) +{ + struct adc_conv_slot *slot = context; + struct msm_adc_drv *msm_adc = msm_adc_drv; + + switch (slot->adc_request) { + case START_OF_CONV: + slot->adc_request = END_OF_CONV; + break; + case START_OF_CALIBRATION: + slot->adc_request = END_OF_CALIBRATION; + break; + case END_OF_CALIBRATION: + case END_OF_CONV: + break; + } + queue_work(msm_adc->wq, &slot->work); +} + +static void msm_adc_teardown_device_conv(struct platform_device *pdev, + struct adc_dev *adc_dev) +{ + struct dal_conv_state *conv_s = &adc_dev->conv; + struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); + struct dal_conv_slot *slot; + int i; + + for (i = 0; i < MSM_ADC_DEV_MAX_INFLIGHT; i++) { + slot = &conv_s->context[i]; + if (slot->cb_h) { + dalrpc_dealloc_cb(msm_adc->dev_h, slot->cb_h); + slot->cb_h = NULL; + } + } +} + +static void msm_rpc_adc_teardown_device(struct platform_device *pdev, + struct adc_dev *adc_dev) +{ + struct dal_translation *transl = &adc_dev->transl; + int i, num_chans = transl->hwmon_end - transl->hwmon_start + 1; + + if (adc_dev->sens_attr) + for (i = 0; i < num_chans; i++) + device_remove_file(&pdev->dev, + &adc_dev->sens_attr[i].dev_attr); + + msm_adc_teardown_device_conv(pdev, adc_dev); + + kfree(adc_dev->fnames); + kfree(adc_dev->sens_attr); + kfree(adc_dev); +} + +static void msm_rpc_adc_teardown_devices(struct platform_device *pdev) +{ + struct msm_adc_platform_data *pdata = pdev->dev.platform_data; + struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); + int i, rc = 0; + + for (i = 0; i < pdata->num_adc; i++) { + if (msm_adc->devs[i]) { + msm_rpc_adc_teardown_device(pdev, msm_adc->devs[i]); + msm_adc->devs[i] = NULL; + } else + break; + } + + if (msm_adc->dev_h) { + rc = daldevice_detach(msm_adc->dev_h); + if (rc) + dev_err(&pdev->dev, "Cannot detach from dal device\n"); + msm_adc->dev_h = NULL; + } + +} + +static void msm_adc_teardown_device(struct platform_device *pdev, + struct msm_adc_drv *msm_adc) +{ + struct msm_adc_platform_data *pdata = pdev->dev.platform_data; + int i, num_chans = pdata->num_chan_supported; + + if (pdata->num_chan_supported > 0) { + if (msm_adc->sens_attr) + for (i = 0; i < num_chans; i++) + device_remove_file(&pdev->dev, + &msm_adc->sens_attr[i].dev_attr); + kfree(msm_adc->sens_attr); + } +} + +static void msm_adc_teardown(struct platform_device *pdev) +{ + struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); + + if (!msm_adc) + return; + + misc_deregister(&msm_adc->misc); + + if (msm_adc->hwmon) + hwmon_device_unregister(msm_adc->hwmon); + + msm_rpc_adc_teardown_devices(pdev); + msm_adc_teardown_device(pdev, msm_adc); + + kfree(msm_adc); + platform_set_drvdata(pdev, NULL); +} + +static int __devinit msm_adc_device_conv_init(struct msm_adc_drv *msm_adc, + struct adc_dev *adc_dev) +{ + struct platform_device *pdev = msm_adc->pdev; + struct dal_conv_state *conv_s = &adc_dev->conv; + struct dal_conv_slot *slot = conv_s->context; + int rc, i; + + sema_init(&conv_s->slot_count, MSM_ADC_DEV_MAX_INFLIGHT); + mutex_init(&conv_s->list_lock); + INIT_LIST_HEAD(&conv_s->slots); + + for (i = 0; i < MSM_ADC_DEV_MAX_INFLIGHT; i++) { + list_add(&slot->list, &conv_s->slots); + slot->cb_h = dalrpc_alloc_cb(msm_adc->dev_h, + msm_rpc_adc_conv_cb, slot); + if (!slot->cb_h) { + dev_err(&pdev->dev, "Unable to allocate DAL callback" + " for slot %d\n", i); + rc = -ENOMEM; + goto dal_err_cb; + } + init_completion(&slot->comp); + slot->idx = i; + slot++; + } + + return 0; + +dal_err_cb: + msm_adc_teardown_device_conv(pdev, adc_dev); + + return rc; +} + +static struct sensor_device_attribute msm_rpc_adc_curr_in_attr = + SENSOR_ATTR(NULL, S_IRUGO, msm_adc_show_curr, NULL, 0); + +static int __devinit msm_rpc_adc_device_init_hwmon(struct platform_device *pdev, + struct adc_dev *adc_dev) +{ + struct dal_translation *transl = &adc_dev->transl; + int i, rc, num_chans = transl->hwmon_end - transl->hwmon_start + 1; + const char prefix[] = "curr", postfix[] = "_input"; + char tmpbuf[5]; + + adc_dev->fnames = kzalloc(num_chans * MSM_ADC_MAX_FNAME + + num_chans * sizeof(char *), GFP_KERNEL); + if (!adc_dev->fnames) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + adc_dev->sens_attr = kzalloc(num_chans * + sizeof(struct sensor_device_attribute), GFP_KERNEL); + if (!adc_dev->sens_attr) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + rc = -ENOMEM; + goto hwmon_err_fnames; + } + + for (i = 0; i < num_chans; i++) { + adc_dev->fnames[i] = (char *)adc_dev->fnames + + i * MSM_ADC_MAX_FNAME + num_chans * sizeof(char *); + strcpy(adc_dev->fnames[i], prefix); + sprintf(tmpbuf, "%d", transl->hwmon_start + i); + strcat(adc_dev->fnames[i], tmpbuf); + strcat(adc_dev->fnames[i], postfix); + + msm_rpc_adc_curr_in_attr.index = transl->hwmon_start + i; + msm_rpc_adc_curr_in_attr.dev_attr.attr.name = + adc_dev->fnames[i]; + memcpy(&adc_dev->sens_attr[i], &msm_rpc_adc_curr_in_attr, + sizeof(msm_rpc_adc_curr_in_attr)); + + rc = device_create_file(&pdev->dev, + &adc_dev->sens_attr[i].dev_attr); + if (rc) { + dev_err(&pdev->dev, "device_create_file failed for " + "dal dev %u chan %d\n", + adc_dev->transl.dal_dev_idx, i); + goto hwmon_err_sens; + } + } + + return 0; + +hwmon_err_sens: + kfree(adc_dev->sens_attr); +hwmon_err_fnames: + kfree(adc_dev->fnames); + + return rc; +} + +static int __devinit msm_rpc_adc_device_init(struct platform_device *pdev) +{ + struct msm_adc_platform_data *pdata = pdev->dev.platform_data; + struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); + struct adc_dev *adc_dev; + struct adc_dev_spec target; + int i, rc; + int hwmon_cntr = pdata->num_chan_supported; + + for (i = 0; i < pdata->num_adc; i++) { + adc_dev = kzalloc(sizeof(struct adc_dev), GFP_KERNEL); + if (!adc_dev) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + rc = -ENOMEM; + goto dev_init_err; + } + + msm_adc->devs[i] = adc_dev; + adc_dev->name = pdata->dev_names[i]; + + rc = msm_adc_device_conv_init(msm_adc, adc_dev); + if (rc) { + dev_err(&pdev->dev, "DAL device[%s] failed conv init\n", + adc_dev->name); + goto dev_init_err; + } + + /* DAL device lookup */ + rc = msm_adc_getinputproperties(msm_adc, adc_dev->name, + &target); + if (rc) { + dev_err(&pdev->dev, "No such DAL device[%s]\n", + adc_dev->name); + goto dev_init_err; + } + + adc_dev->transl.dal_dev_idx = target.dal.dev_idx; + adc_dev->transl.hwmon_dev_idx = i; + adc_dev->nchans = target.dal.chan_idx; + adc_dev->transl.hwmon_start = hwmon_cntr; + adc_dev->transl.hwmon_end = hwmon_cntr + adc_dev->nchans - 1; + hwmon_cntr += adc_dev->nchans; + + rc = msm_rpc_adc_device_init_hwmon(pdev, adc_dev); + if (rc) + goto dev_init_err; + } + + return 0; + +dev_init_err: + msm_rpc_adc_teardown_devices(pdev); + return rc; +} + +static int __devinit msm_rpc_adc_init(struct platform_device *pdev1) +{ + struct msm_adc_drv *msm_adc = msm_adc_drv; + struct platform_device *pdev = msm_adc->pdev; + struct msm_adc_platform_data *pdata = pdev->dev.platform_data; + int rc = 0; + + dev_dbg(&pdev->dev, "msm_rpc_adc_init called\n"); + + if (!pdata) { + dev_err(&pdev->dev, "no platform data?\n"); + return -EINVAL; + } + + mutex_init(&msm_adc->prop_lock); + + rc = daldevice_attach(MSM_ADC_DALRPC_DEVICEID, + MSM_ADC_DALRPC_PORT_NAME, + MSM_ADC_DALRPC_CPU, + &msm_adc->dev_h); + if (rc) { + dev_err(&pdev->dev, "Cannot attach to dal device\n"); + return rc; + } + + dev_dbg(&pdev->dev, "Attach to dal device Succeeded\n"); + + rc = msm_rpc_adc_device_init(pdev); + if (rc) { + dev_err(&pdev->dev, "msm_adc_dev_init failed\n"); + goto err_cleanup; + } + + init_waitqueue_head(&msm_adc->rpc_total_outst_wait); + atomic_set(&msm_adc->rpc_online, 1); + atomic_set(&msm_adc->rpc_total_outst, 0); + epm_init = true; + pr_info("msm_adc successfully registered\n"); + + return 0; + +err_cleanup: + msm_rpc_adc_teardown_devices(pdev); + + return rc; +} + +/* + * Process the deferred job + */ +void msm_adc_wq_work(struct work_struct *work) +{ + struct adc_properties *adc_properties; + struct adc_conv_slot *slot = container_of(work, + struct adc_conv_slot, work); + uint32_t idx = slot->conv.result.chan; + struct msm_adc_platform_data *pdata = + msm_adc_drv->pdev->dev.platform_data; + struct msm_adc_channels *channel = &pdata->channel[idx]; + int32_t adc_code; + + switch (slot->adc_request) { + case START_OF_CONV: + channel->adc_access_fn->adc_select_chan_and_start_conv( + channel->adc_dev_instance, slot); + break; + case END_OF_CONV: + adc_properties = channel->adc_access_fn->adc_get_properties( + channel->adc_dev_instance); + if (channel->adc_access_fn->adc_read_adc_code) + channel->adc_access_fn->adc_read_adc_code( + channel->adc_dev_instance, &adc_code); + if (channel->chan_processor) + channel->chan_processor(adc_code, adc_properties, + &slot->chan_properties, &slot->conv.result); + /* Intentionally a fall thru here. Calibraton does not need + to perform channel processing, etc. However, both + end of conversion and end of calibration requires the below + fall thru code to be executed. */ + case END_OF_CALIBRATION: + /* for blocking requests, signal complete */ + if (slot->blocking) + complete(&slot->comp); + else { + struct msm_client_data *client = slot->client; + + mutex_lock(&client->lock); + + if (slot->adc_request == END_OF_CONV) { + list_add(&slot->list, &client->complete_list); + client->num_complete++; + } + client->num_outstanding--; + + /* + * if the client release has been invoked and this is call + * corresponds to the last request, then signal release + * to complete. + */ + if (slot->client->online == 0 && + client->num_outstanding == 0) + wake_up_interruptible_all(&client->outst_wait); + + mutex_unlock(&client->lock); + + wake_up_interruptible_all(&client->data_wait); + + atomic_dec(&msm_adc_drv->total_outst); + + /* verify driver remove has not been invoked */ + if (atomic_read(&msm_adc_drv->online) == 0 && + atomic_read(&msm_adc_drv->total_outst) == 0) + wake_up_interruptible_all( + &msm_adc_drv->total_outst_wait); + + if (slot->compk) /* Kernel space request */ + complete(slot->compk); + if (slot->adc_request == END_OF_CALIBRATION) + channel->adc_access_fn->adc_restore_slot( + channel->adc_dev_instance, slot); + } + break; + case START_OF_CALIBRATION: /* code here to please code reviewers + to satisfy silly compiler warnings */ + break; + } +} + +static struct sensor_device_attribute msm_adc_curr_in_attr = + SENSOR_ATTR(NULL, S_IRUGO, msm_adc_show_curr, NULL, 0); + +static int __devinit msm_adc_init_hwmon(struct platform_device *pdev, + struct msm_adc_drv *msm_adc) +{ + struct msm_adc_platform_data *pdata = pdev->dev.platform_data; + struct msm_adc_channels *channel = pdata->channel; + int i, rc, num_chans = pdata->num_chan_supported; + + if (!channel) + return -EINVAL; + + msm_adc->sens_attr = kzalloc(num_chans * + sizeof(struct sensor_device_attribute), GFP_KERNEL); + if (!msm_adc->sens_attr) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + rc = -ENOMEM; + goto hwmon_err_sens; + } + + for (i = 0; i < num_chans; i++) { + msm_adc_curr_in_attr.index = i; + msm_adc_curr_in_attr.dev_attr.attr.name = channel[i].name; + memcpy(&msm_adc->sens_attr[i], &msm_adc_curr_in_attr, + sizeof(msm_adc_curr_in_attr)); + + rc = device_create_file(&pdev->dev, + &msm_adc->sens_attr[i].dev_attr); + if (rc) { + dev_err(&pdev->dev, "device_create_file failed for " + "dal dev %s\n", + channel[i].name); + goto hwmon_err_sens; + } + } + + return 0; + +hwmon_err_sens: + kfree(msm_adc->sens_attr); + + return rc; +} + +static struct platform_driver msm_adc_rpcrouter_remote_driver = { + .probe = msm_rpc_adc_init, + .driver = { + .name = MSM_ADC_DALRPC_PORT_NAME, + .owner = THIS_MODULE, + }, +}; + +static int msm_adc_probe(struct platform_device *pdev) +{ + struct msm_adc_platform_data *pdata = pdev->dev.platform_data; + struct msm_adc_drv *msm_adc; + int rc = 0; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data?\n"); + return -EINVAL; + } + + msm_adc = kzalloc(sizeof(struct msm_adc_drv), GFP_KERNEL); + if (!msm_adc) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, msm_adc); + msm_adc_drv = msm_adc; + msm_adc->pdev = pdev; + + if (pdata->target_hw == MSM_8x60 || pdata->target_hw == FSM_9xxx) { + rc = msm_adc_init_hwmon(pdev, msm_adc); + if (rc) { + dev_err(&pdev->dev, "msm_adc_dev_init failed\n"); + goto err_cleanup; + } + } + + msm_adc->hwmon = hwmon_device_register(&pdev->dev); + if (IS_ERR(msm_adc->hwmon)) { + dev_err(&pdev->dev, "hwmon_device_register failed\n"); + rc = PTR_ERR(msm_adc->hwmon); + goto err_cleanup; + } + + msm_adc->misc.name = MSM_ADC_DRIVER_NAME; + msm_adc->misc.minor = MISC_DYNAMIC_MINOR; + msm_adc->misc.fops = &msm_adc_fops; + + if (misc_register(&msm_adc->misc)) { + dev_err(&pdev->dev, "Unable to register misc device!\n"); + goto err_cleanup; + } + + init_waitqueue_head(&msm_adc->total_outst_wait); + atomic_set(&msm_adc->online, 1); + atomic_set(&msm_adc->total_outst, 0); + + msm_adc->wq = create_singlethread_workqueue("msm_adc"); + if (!msm_adc->wq) + goto err_cleanup; + + if (pdata->num_adc > 0) { + if (pdata->target_hw == MSM_8x60) + platform_driver_register( + &msm_adc_rpcrouter_remote_driver); + else + msm_rpc_adc_init(pdev); + } + + pr_info("msm_adc successfully registered\n"); + + return 0; + +err_cleanup: + msm_adc_teardown(pdev); + + return rc; +} + +static int __devexit msm_adc_remove(struct platform_device *pdev) +{ + int rc; + + struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); + + atomic_set(&msm_adc->online, 0); + + atomic_set(&msm_adc->rpc_online, 0); + + misc_deregister(&msm_adc->misc); + + hwmon_device_unregister(msm_adc->hwmon); + msm_adc->hwmon = NULL; + + /* + * We may still have outstanding transactions in flight that have not + * completed. Make sure they're completed before tearing down. + */ + rc = wait_event_interruptible(msm_adc->total_outst_wait, + atomic_read(&msm_adc->total_outst) == 0); + if (rc) { + pr_err("%s: wait_event_interruptible failed rc = %d\n", + __func__, rc); + return rc; + } + + rc = wait_event_interruptible(msm_adc->rpc_total_outst_wait, + atomic_read(&msm_adc->rpc_total_outst) == 0); + if (rc) { + pr_err("%s: wait_event_interruptible failed rc = %d\n", + __func__, rc); + return rc; + } + + msm_adc_teardown(pdev); + + pr_info("msm_adc unregistered\n"); + + return 0; +} + +static struct platform_driver msm_adc_driver = { + .probe = msm_adc_probe, + .remove = __devexit_p(msm_adc_remove), + .driver = { + .name = MSM_ADC_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_adc_init(void) +{ + return platform_driver_register(&msm_adc_driver); +} +module_init(msm_adc_init); + +static void __exit msm_adc_exit(void) +{ + platform_driver_unregister(&msm_adc_driver); +} +module_exit(msm_adc_exit); + +MODULE_DESCRIPTION("MSM ADC Driver"); +MODULE_ALIAS("platform:msm_adc"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); diff --git a/drivers/hwmon/wpce775x.c b/drivers/hwmon/wpce775x.c new file mode 100644 index 00000000000..2d007003cc2 --- /dev/null +++ b/drivers/hwmon/wpce775x.c @@ -0,0 +1,167 @@ +/* Quanta EC driver for the Winbond Embedded Controller + * + * Copyright (C) 2009 Quanta Computer Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include + +#define EC_ID_NAME "qci-i2cec" +#define EC_BUFFER_LEN 16 +#define EC_CMD_POWER_OFF 0xAC +#define EC_CMD_RESTART 0xAB + +static struct i2c_client *g_i2cec_client; + +/* General structure to hold the driver data */ +struct i2cec_drv_data { + struct i2c_client *i2cec_client; + struct work_struct work; + char ec_data[EC_BUFFER_LEN+1]; +}; + +static int __devinit wpce_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int __devexit wpce_remove(struct i2c_client *kbd); + +#ifdef CONFIG_PM +static int wpce_suspend(struct device *dev) +{ + return 0; +} + +static int wpce_resume(struct device *dev) +{ + return 0; +} +#endif + +#ifdef CONFIG_PM +static struct dev_pm_ops wpce_pm_ops = { + .suspend = wpce_suspend, + .resume = wpce_resume, +}; +#endif + +static const struct i2c_device_id wpce_idtable[] = { + { EC_ID_NAME, 0 }, + { } +}; + +static struct i2c_driver wpce_driver = { + .driver = { + .owner = THIS_MODULE, + .name = EC_ID_NAME, +#ifdef CONFIG_PM + .pm = &wpce_pm_ops, +#endif + }, + .probe = wpce_probe, + .remove = __devexit_p(wpce_remove), + .id_table = wpce_idtable, +}; + +static int __devinit wpce_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err = -ENOMEM; + struct i2cec_drv_data *context = 0; + + /* there is no need to call i2c_check_functionality() since it is the + client's job to use the interface (I2C vs SMBUS) appropriate for it. */ + client->driver = &wpce_driver; + context = kzalloc(sizeof(struct i2cec_drv_data), GFP_KERNEL); + if (!context) + return err; + + context->i2cec_client = client; + g_i2cec_client = client; + i2c_set_clientdata(context->i2cec_client, context); + + return 0; +} + +static int __devexit wpce_remove(struct i2c_client *dev) +{ + struct i2cec_drv_data *context = i2c_get_clientdata(dev); + g_i2cec_client = NULL; + kfree(context); + + return 0; +} + +static int __init wpce_init(void) +{ + return i2c_add_driver(&wpce_driver); +} + +static void __exit wpce_exit(void) +{ + i2c_del_driver(&wpce_driver); +} + +struct i2c_client *wpce_get_i2c_client(void) +{ + return g_i2cec_client; +} +EXPORT_SYMBOL_GPL(wpce_get_i2c_client); + +void wpce_poweroff(void) +{ + if (g_i2cec_client == NULL) + return; + i2c_smbus_write_byte(g_i2cec_client, EC_CMD_POWER_OFF); +} +EXPORT_SYMBOL_GPL(wpce_poweroff); + +void wpce_restart(void) +{ + if (g_i2cec_client == NULL) + return; + i2c_smbus_write_byte(g_i2cec_client, EC_CMD_RESTART); +} +EXPORT_SYMBOL_GPL(wpce_restart); + +int wpce_i2c_transfer(struct i2c_msg *msg) +{ + if (g_i2cec_client == NULL) + return -1; + msg->addr = g_i2cec_client->addr; + return i2c_transfer(g_i2cec_client->adapter, msg, 1); +} +EXPORT_SYMBOL_GPL(wpce_i2c_transfer); + +int wpce_smbus_write_word_data(u8 command, u16 value) +{ + if (g_i2cec_client == NULL) + return -1; + return i2c_smbus_write_word_data(g_i2cec_client, command, value); +} +EXPORT_SYMBOL_GPL(wpce_smbus_write_word_data); + +int wpce_smbus_write_byte_data(u8 command, u8 value) +{ + if (g_i2cec_client == NULL) + return -1; + return i2c_smbus_write_byte_data(g_i2cec_client, command, value); +} +EXPORT_SYMBOL_GPL(wpce_smbus_write_byte_data); + +module_init(wpce_init); +module_exit(wpce_exit); + +MODULE_AUTHOR("Quanta Computer Inc."); +MODULE_DESCRIPTION("Quanta Embedded Controller I2C Bridge Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 646068e5100..ce4ac8cd075 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -445,6 +445,32 @@ config I2C_MPC This driver can also be built as a module. If so, the module will be called i2c-mpc. +config I2C_MSM + tristate "MSM" + depends on I2C && (ARCH_MSM || ARCH_QSD) + default y + help + If you say yes to this option, support will be included for the + built-in I2C interface on the MSM or QSD family processors. + +config I2C_QUP + tristate "I2C_QUP" + depends on ARCH_MSM + help + If you say yes to this option, support will be included for the + built-in I2C interface on the MSM family processors. + +config I2C_SSBI + tristate "Qualcomm Single-wire Serial Bus Interface (SSBI)" + depends on I2C && (ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_FSM9XXX) + default n + help + If you say yes to this option, support will be included for the + built-in SSBI interface on the MSM family processors. + + Note that SSBI is not an I2C device, but is functionally related + enough such that it is able to leverages the I2C framework. + config I2C_MV64XXX tristate "Marvell mv64xxx I2C Controller" depends on (MV64X60 || PLAT_ORION) && EXPERIMENTAL diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index e6cf294d372..cf47b6aee2a 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -42,6 +42,9 @@ obj-$(CONFIG_I2C_INTEL_MID) += i2c-intel-mid.o obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o obj-$(CONFIG_I2C_IXP2000) += i2c-ixp2000.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o +obj-$(CONFIG_I2C_MSM) += i2c-msm.o +obj-$(CONFIG_I2C_QUP) += i2c-qup.o +obj-$(CONFIG_I2C_SSBI) += i2c-ssbi.o obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o obj-$(CONFIG_I2C_MXS) += i2c-mxs.o obj-$(CONFIG_I2C_NOMADIK) += i2c-nomadik.o diff --git a/drivers/i2c/busses/i2c-msm.c b/drivers/i2c/busses/i2c-msm.c new file mode 100644 index 00000000000..7bf5314bce8 --- /dev/null +++ b/drivers/i2c/busses/i2c-msm.c @@ -0,0 +1,797 @@ +/* drivers/i2c/busses/i2c-msm.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. 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. + * + */ + +/* #define DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +enum { + I2C_WRITE_DATA = 0x00, + I2C_CLK_CTL = 0x04, + I2C_STATUS = 0x08, + I2C_READ_DATA = 0x0c, + I2C_INTERFACE_SELECT = 0x10, + + I2C_WRITE_DATA_DATA_BYTE = 0xff, + I2C_WRITE_DATA_ADDR_BYTE = 1U << 8, + I2C_WRITE_DATA_LAST_BYTE = 1U << 9, + + I2C_CLK_CTL_FS_DIVIDER_VALUE = 0xff, + I2C_CLK_CTL_HS_DIVIDER_VALUE = 7U << 8, + + I2C_STATUS_WR_BUFFER_FULL = 1U << 0, + I2C_STATUS_RD_BUFFER_FULL = 1U << 1, + I2C_STATUS_BUS_ERROR = 1U << 2, + I2C_STATUS_PACKET_NACKED = 1U << 3, + I2C_STATUS_ARB_LOST = 1U << 4, + I2C_STATUS_INVALID_WRITE = 1U << 5, + I2C_STATUS_FAILED = 3U << 6, + I2C_STATUS_BUS_ACTIVE = 1U << 8, + I2C_STATUS_BUS_MASTER = 1U << 9, + I2C_STATUS_ERROR_MASK = 0xfc, + + I2C_INTERFACE_SELECT_INTF_SELECT = 1U << 0, + I2C_INTERFACE_SELECT_SCL = 1U << 8, + I2C_INTERFACE_SELECT_SDA = 1U << 9, + I2C_STATUS_RX_DATA_STATE = 3U << 11, + I2C_STATUS_LOW_CLK_STATE = 3U << 13, +}; + +struct msm_i2c_dev { + struct device *dev; + void __iomem *base; /* virtual */ + int irq; + struct clk *clk; + struct i2c_adapter adap_pri; + struct i2c_adapter adap_aux; + + spinlock_t lock; + + struct i2c_msg *msg; + int rem; + int pos; + int cnt; + int err; + int flush_cnt; + int rd_acked; + int one_bit_t; + remote_mutex_t r_lock; + int suspended; + struct mutex mlock; + struct msm_i2c_platform_data *pdata; + struct timer_list pwr_timer; + int clk_state; + void *complete; + + struct pm_qos_request_list pm_qos_req; +}; + +static void +msm_i2c_pwr_mgmt(struct msm_i2c_dev *dev, unsigned int state) +{ + dev->clk_state = state; + if (state != 0) + clk_enable(dev->clk); + else + clk_disable(dev->clk); +} + +static void +msm_i2c_pwr_timer(unsigned long data) +{ + struct msm_i2c_dev *dev = (struct msm_i2c_dev *) data; + dev_dbg(dev->dev, "I2C_Power: Inactivity based power management\n"); + if (dev->clk_state == 1) + msm_i2c_pwr_mgmt(dev, 0); +} + +#ifdef DEBUG +static void +dump_status(uint32_t status) +{ + printk("STATUS (0x%.8x): ", status); + if (status & I2C_STATUS_BUS_MASTER) + printk("MST "); + if (status & I2C_STATUS_BUS_ACTIVE) + printk("ACT "); + if (status & I2C_STATUS_INVALID_WRITE) + printk("INV_WR "); + if (status & I2C_STATUS_ARB_LOST) + printk("ARB_LST "); + if (status & I2C_STATUS_PACKET_NACKED) + printk("NAK "); + if (status & I2C_STATUS_BUS_ERROR) + printk("BUS_ERR "); + if (status & I2C_STATUS_RD_BUFFER_FULL) + printk("RD_FULL "); + if (status & I2C_STATUS_WR_BUFFER_FULL) + printk("WR_FULL "); + if (status & I2C_STATUS_FAILED) + printk("FAIL 0x%x", (status & I2C_STATUS_FAILED)); + printk("\n"); +} +#endif + +static irqreturn_t +msm_i2c_interrupt(int irq, void *devid) +{ + struct msm_i2c_dev *dev = devid; + uint32_t status = readl(dev->base + I2C_STATUS); + int err = 0; + +#ifdef DEBUG + dump_status(status); +#endif + + spin_lock(&dev->lock); + if (!dev->msg) { + printk(KERN_ERR "%s: IRQ but nothing to do!\n", __func__); + spin_unlock(&dev->lock); + return IRQ_HANDLED; + } + + if (status & I2C_STATUS_ERROR_MASK) { + err = -EIO; + goto out_err; + } + + if (dev->msg->flags & I2C_M_RD) { + if (status & I2C_STATUS_RD_BUFFER_FULL) { + + /* + * Theres something in the FIFO. + * Are we expecting data or flush crap? + */ + if (dev->cnt) { /* DATA */ + uint8_t *data = &dev->msg->buf[dev->pos]; + + /* This is in spin-lock. So there will be no + * scheduling between reading the second-last + * byte and writing LAST_BYTE to the controller. + * So extra read-cycle-clock won't be generated + * Per I2C MSM HW Specs: Write LAST_BYTE befure + * reading 2nd last byte + */ + if (dev->cnt == 2) + writel(I2C_WRITE_DATA_LAST_BYTE, + dev->base + I2C_WRITE_DATA); + *data = readl(dev->base + I2C_READ_DATA); + dev->cnt--; + dev->pos++; + if (dev->msg->len == 1) + dev->rd_acked = 0; + if (dev->cnt == 0) + goto out_complete; + + } else { + /* Now that extra read-cycle-clocks aren't + * generated, this becomes error condition + */ + dev_err(dev->dev, + "read did not stop, status - %x\n", + status); + err = -EIO; + goto out_err; + } + } else if (dev->msg->len == 1 && dev->rd_acked == 0 && + ((status & I2C_STATUS_RX_DATA_STATE) == + I2C_STATUS_RX_DATA_STATE)) + writel(I2C_WRITE_DATA_LAST_BYTE, + dev->base + I2C_WRITE_DATA); + } else { + uint16_t data; + + if (status & I2C_STATUS_WR_BUFFER_FULL) { + dev_err(dev->dev, + "Write buffer full in ISR on write?\n"); + err = -EIO; + goto out_err; + } + + if (dev->cnt) { + /* Ready to take a byte */ + data = dev->msg->buf[dev->pos]; + if (dev->cnt == 1 && dev->rem == 1) + data |= I2C_WRITE_DATA_LAST_BYTE; + + status = readl(dev->base + I2C_STATUS); + /* + * Due to a hardware timing issue, data line setup time + * may be reduced to less than recommended 250 ns. + * This happens when next byte is written in a + * particular window of clock line being low and master + * not stretching the clock line. Due to setup time + * violation, some slaves may miss first-bit of data, or + * misinterprete data as start condition. + * We introduce delay of just over 1/2 clock cycle to + * ensure master stretches the clock line thereby + * avoiding setup time violation. Delay is introduced + * only if I2C clock FSM is LOW. The delay is not needed + * if I2C clock FSM is HIGH or FORCED_LOW. + */ + if ((status & I2C_STATUS_LOW_CLK_STATE) == + I2C_STATUS_LOW_CLK_STATE) + udelay((dev->one_bit_t >> 1) + 1); + writel(data, dev->base + I2C_WRITE_DATA); + dev->pos++; + dev->cnt--; + } else + goto out_complete; + } + + spin_unlock(&dev->lock); + return IRQ_HANDLED; + + out_err: + dev->err = err; + out_complete: + complete(dev->complete); + spin_unlock(&dev->lock); + return IRQ_HANDLED; +} + +static int +msm_i2c_poll_writeready(struct msm_i2c_dev *dev) +{ + uint32_t retries = 0; + + while (retries != 2000) { + uint32_t status = readl(dev->base + I2C_STATUS); + + if (!(status & I2C_STATUS_WR_BUFFER_FULL)) + return 0; + if (retries++ > 1000) + usleep_range(100, 200); + } + return -ETIMEDOUT; +} + +static int +msm_i2c_poll_notbusy(struct msm_i2c_dev *dev) +{ + uint32_t retries = 0; + + while (retries != 2000) { + uint32_t status = readl(dev->base + I2C_STATUS); + + if (!(status & I2C_STATUS_BUS_ACTIVE)) + return 0; + if (retries++ > 1000) + usleep_range(100, 200); + } + return -ETIMEDOUT; +} + +static int +msm_i2c_recover_bus_busy(struct msm_i2c_dev *dev, struct i2c_adapter *adap) +{ + int i; + int gpio_clk; + int gpio_dat; + uint32_t status = readl(dev->base + I2C_STATUS); + bool gpio_clk_status = false; + + if (!(status & (I2C_STATUS_BUS_ACTIVE | I2C_STATUS_WR_BUFFER_FULL))) + return 0; + + dev->pdata->msm_i2c_config_gpio(adap->nr, 0); + /* Even adapter is primary and Odd adapter is AUX */ + if (adap->nr % 2) { + gpio_clk = dev->pdata->aux_clk; + gpio_dat = dev->pdata->aux_dat; + } else { + gpio_clk = dev->pdata->pri_clk; + gpio_dat = dev->pdata->pri_dat; + } + + disable_irq(dev->irq); + if (status & I2C_STATUS_RD_BUFFER_FULL) { + dev_warn(dev->dev, "Read buffer full, status %x, intf %x\n", + status, readl(dev->base + I2C_INTERFACE_SELECT)); + writel(I2C_WRITE_DATA_LAST_BYTE, dev->base + I2C_WRITE_DATA); + readl(dev->base + I2C_READ_DATA); + } else if (status & I2C_STATUS_BUS_MASTER) { + dev_warn(dev->dev, "Still the bus master, status %x, intf %x\n", + status, readl(dev->base + I2C_INTERFACE_SELECT)); + writel(I2C_WRITE_DATA_LAST_BYTE | 0xff, + dev->base + I2C_WRITE_DATA); + } + + for (i = 0; i < 9; i++) { + if (gpio_get_value(gpio_dat) && gpio_clk_status) + break; + gpio_direction_output(gpio_clk, 0); + udelay(5); + gpio_direction_output(gpio_dat, 0); + udelay(5); + gpio_direction_input(gpio_clk); + udelay(5); + if (!gpio_get_value(gpio_clk)) + usleep_range(20, 30); + if (!gpio_get_value(gpio_clk)) + msleep(10); + gpio_clk_status = gpio_get_value(gpio_clk); + gpio_direction_input(gpio_dat); + udelay(5); + } + dev->pdata->msm_i2c_config_gpio(adap->nr, 1); + udelay(10); + + status = readl(dev->base + I2C_STATUS); + if (!(status & I2C_STATUS_BUS_ACTIVE)) { + dev_info(dev->dev, "Bus busy cleared after %d clock cycles, " + "status %x, intf %x\n", + i, status, readl(dev->base + I2C_INTERFACE_SELECT)); + enable_irq(dev->irq); + return 0; + } + + dev_err(dev->dev, "Bus still busy, status %x, intf %x\n", + status, readl(dev->base + I2C_INTERFACE_SELECT)); + enable_irq(dev->irq); + return -EBUSY; +} + +static int +msm_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + DECLARE_COMPLETION_ONSTACK(complete); + struct msm_i2c_dev *dev = i2c_get_adapdata(adap); + int ret; + int rem = num; + uint16_t addr; + long timeout; + unsigned long flags; + int check_busy = 1; + + del_timer_sync(&dev->pwr_timer); + mutex_lock(&dev->mlock); + if (dev->suspended) { + mutex_unlock(&dev->mlock); + return -EIO; + } + + if (dev->clk_state == 0) { + dev_dbg(dev->dev, "I2C_Power: Enable I2C clock(s)\n"); + msm_i2c_pwr_mgmt(dev, 1); + } + + /* Don't allow power collapse until we release remote spinlock */ + pm_qos_update_request(&dev->pm_qos_req, dev->pdata->pm_lat); + if (dev->pdata->rmutex) { + remote_mutex_lock(&dev->r_lock); + /* If other processor did some transactions, we may have + * interrupt pending. Clear it + */ + irq_get_chip(dev->irq)->irq_ack(irq_get_irq_data(dev->irq)); + } + + if (adap == &dev->adap_pri) + writel(0, dev->base + I2C_INTERFACE_SELECT); + else + writel(I2C_INTERFACE_SELECT_INTF_SELECT, + dev->base + I2C_INTERFACE_SELECT); + enable_irq(dev->irq); + while (rem) { + addr = msgs->addr << 1; + if (msgs->flags & I2C_M_RD) + addr |= 1; + + spin_lock_irqsave(&dev->lock, flags); + dev->msg = msgs; + dev->rem = rem; + dev->pos = 0; + dev->err = 0; + dev->flush_cnt = 0; + dev->cnt = msgs->len; + dev->complete = &complete; + spin_unlock_irqrestore(&dev->lock, flags); + + if (check_busy) { + ret = msm_i2c_poll_notbusy(dev); + if (ret) + ret = msm_i2c_recover_bus_busy(dev, adap); + if (ret) { + dev_err(dev->dev, + "Error waiting for notbusy\n"); + goto out_err; + } + check_busy = 0; + } + + if (rem == 1 && msgs->len == 0) + addr |= I2C_WRITE_DATA_LAST_BYTE; + + /* Wait for WR buffer not full */ + ret = msm_i2c_poll_writeready(dev); + if (ret) { + ret = msm_i2c_recover_bus_busy(dev, adap); + if (ret) { + dev_err(dev->dev, + "Error waiting for write ready before addr\n"); + goto out_err; + } + } + + /* special case for doing 1 byte read. + * There should be no scheduling between I2C controller becoming + * ready to read and writing LAST-BYTE to I2C controller + * This will avoid potential of I2C controller starting to latch + * another extra byte. + */ + if ((msgs->len == 1) && (msgs->flags & I2C_M_RD)) { + uint32_t retries = 0; + spin_lock_irqsave(&dev->lock, flags); + + writel(I2C_WRITE_DATA_ADDR_BYTE | addr, + dev->base + I2C_WRITE_DATA); + + /* Poll for I2C controller going into RX_DATA mode to + * ensure controller goes into receive mode. + * Just checking write_buffer_full may not work since + * there is delay between the write-buffer becoming + * empty and the slave sending ACK to ensure I2C + * controller goes in receive mode to receive data. + */ + while (retries != 2000) { + uint32_t status = readl(dev->base + I2C_STATUS); + + if ((status & I2C_STATUS_RX_DATA_STATE) + == I2C_STATUS_RX_DATA_STATE) + break; + retries++; + } + if (retries >= 2000) { + dev->rd_acked = 0; + spin_unlock_irqrestore(&dev->lock, flags); + /* 1-byte-reads from slow devices in interrupt + * context + */ + goto wait_for_int; + } + + dev->rd_acked = 1; + writel(I2C_WRITE_DATA_LAST_BYTE, + dev->base + I2C_WRITE_DATA); + spin_unlock_irqrestore(&dev->lock, flags); + } else { + writel(I2C_WRITE_DATA_ADDR_BYTE | addr, + dev->base + I2C_WRITE_DATA); + } + /* Polling and waiting for write_buffer_empty is not necessary. + * Even worse, if we do, it can result in invalid status and + * error if interrupt(s) occur while polling. + */ + + /* + * Now that we've setup the xfer, the ISR will transfer the data + * and wake us up with dev->err set if there was an error + */ +wait_for_int: + + timeout = wait_for_completion_timeout(&complete, HZ); + if (!timeout) { + dev_err(dev->dev, "Transaction timed out\n"); + writel(I2C_WRITE_DATA_LAST_BYTE, + dev->base + I2C_WRITE_DATA); + msleep(100); + /* FLUSH */ + readl(dev->base + I2C_READ_DATA); + readl(dev->base + I2C_STATUS); + ret = -ETIMEDOUT; + goto out_err; + } + if (dev->err) { + dev_err(dev->dev, + "(%04x) Error during data xfer (%d)\n", + addr, dev->err); + ret = dev->err; + goto out_err; + } + + if (msgs->flags & I2C_M_RD) + check_busy = 1; + + msgs++; + rem--; + } + + ret = num; + out_err: + spin_lock_irqsave(&dev->lock, flags); + dev->complete = NULL; + dev->msg = NULL; + dev->rem = 0; + dev->pos = 0; + dev->err = 0; + dev->flush_cnt = 0; + dev->cnt = 0; + spin_unlock_irqrestore(&dev->lock, flags); + disable_irq(dev->irq); + if (dev->pdata->rmutex) + remote_mutex_unlock(&dev->r_lock); + pm_qos_update_request(&dev->pm_qos_req, + PM_QOS_DEFAULT_VALUE); + mod_timer(&dev->pwr_timer, (jiffies + 3*HZ)); + mutex_unlock(&dev->mlock); + return ret; +} + +static u32 +msm_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +static const struct i2c_algorithm msm_i2c_algo = { + .master_xfer = msm_i2c_xfer, + .functionality = msm_i2c_func, +}; + +static int +msm_i2c_probe(struct platform_device *pdev) +{ + struct msm_i2c_dev *dev; + struct resource *mem, *irq, *ioarea; + int ret; + int fs_div; + int hs_div; + int i2c_clk; + int clk_ctl; + struct clk *clk; + struct msm_i2c_platform_data *pdata; + + printk(KERN_INFO "msm_i2c_probe\n"); + + /* NOTE: driver uses the static register mapping */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -ENODEV; + } + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq) { + dev_err(&pdev->dev, "no irq resource?\n"); + return -ENODEV; + } + + ioarea = request_mem_region(mem->start, (mem->end - mem->start) + 1, + pdev->name); + if (!ioarea) { + dev_err(&pdev->dev, "I2C region already claimed\n"); + return -EBUSY; + } + clk = clk_get(&pdev->dev, "i2c_clk"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Could not get clock\n"); + ret = PTR_ERR(clk); + goto err_clk_get_failed; + } + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "platform data not initialized\n"); + ret = -ENOSYS; + goto err_clk_get_failed; + } + if (!pdata->msm_i2c_config_gpio) { + dev_err(&pdev->dev, "config_gpio function not initialized\n"); + ret = -ENOSYS; + goto err_clk_get_failed; + } + /* We support frequencies upto FAST Mode(400KHz) */ + if (pdata->clk_freq <= 0 || pdata->clk_freq > 400000) { + dev_err(&pdev->dev, "clock frequency not supported\n"); + ret = -EIO; + goto err_clk_get_failed; + } + + dev = kzalloc(sizeof(struct msm_i2c_dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err_alloc_dev_failed; + } + + dev->dev = &pdev->dev; + dev->irq = irq->start; + dev->clk = clk; + dev->pdata = pdata; + dev->base = ioremap(mem->start, (mem->end - mem->start) + 1); + if (!dev->base) { + ret = -ENOMEM; + goto err_ioremap_failed; + } + + dev->one_bit_t = USEC_PER_SEC/pdata->clk_freq; + spin_lock_init(&dev->lock); + platform_set_drvdata(pdev, dev); + + clk_enable(clk); + + if (pdata->rmutex) { + struct remote_mutex_id rmid; + rmid.r_spinlock_id = pdata->rsl_id; + rmid.delay_us = 10000000/pdata->clk_freq; + if (remote_mutex_init(&dev->r_lock, &rmid) != 0) + pdata->rmutex = 0; + } + /* I2C_HS_CLK = I2C_CLK/(3*(HS_DIVIDER_VALUE+1) */ + /* I2C_FS_CLK = I2C_CLK/(2*(FS_DIVIDER_VALUE+3) */ + /* FS_DIVIDER_VALUE = ((I2C_CLK / I2C_FS_CLK) / 2) - 3 */ + i2c_clk = 19200000; /* input clock */ + fs_div = ((i2c_clk / pdata->clk_freq) / 2) - 3; + hs_div = 3; + clk_ctl = ((hs_div & 0x7) << 8) | (fs_div & 0xff); + writel(clk_ctl, dev->base + I2C_CLK_CTL); + printk(KERN_INFO "msm_i2c_probe: clk_ctl %x, %d Hz\n", + clk_ctl, i2c_clk / (2 * ((clk_ctl & 0xff) + 3))); + + i2c_set_adapdata(&dev->adap_pri, dev); + dev->adap_pri.algo = &msm_i2c_algo; + strlcpy(dev->adap_pri.name, + "MSM I2C adapter-PRI", + sizeof(dev->adap_pri.name)); + + dev->adap_pri.nr = pdev->id; + ret = i2c_add_numbered_adapter(&dev->adap_pri); + if (ret) { + dev_err(&pdev->dev, "Primary i2c_add_adapter failed\n"); + goto err_i2c_add_adapter_failed; + } + + i2c_set_adapdata(&dev->adap_aux, dev); + dev->adap_aux.algo = &msm_i2c_algo; + strlcpy(dev->adap_aux.name, + "MSM I2C adapter-AUX", + sizeof(dev->adap_aux.name)); + + dev->adap_aux.nr = pdev->id + 1; + ret = i2c_add_numbered_adapter(&dev->adap_aux); + if (ret) { + dev_err(&pdev->dev, "auxiliary i2c_add_adapter failed\n"); + i2c_del_adapter(&dev->adap_pri); + goto err_i2c_add_adapter_failed; + } + ret = request_irq(dev->irq, msm_i2c_interrupt, + IRQF_TRIGGER_RISING, pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "request_irq failed\n"); + goto err_request_irq_failed; + } + pm_qos_add_request(&dev->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + disable_irq(dev->irq); + dev->suspended = 0; + mutex_init(&dev->mlock); + dev->clk_state = 0; + /* Config GPIOs for primary and secondary lines */ + pdata->msm_i2c_config_gpio(dev->adap_pri.nr, 1); + pdata->msm_i2c_config_gpio(dev->adap_aux.nr, 1); + clk_disable(dev->clk); + setup_timer(&dev->pwr_timer, msm_i2c_pwr_timer, (unsigned long) dev); + + return 0; + +err_request_irq_failed: + i2c_del_adapter(&dev->adap_pri); + i2c_del_adapter(&dev->adap_aux); +err_i2c_add_adapter_failed: + clk_disable(clk); + iounmap(dev->base); +err_ioremap_failed: + kfree(dev); +err_alloc_dev_failed: + clk_put(clk); +err_clk_get_failed: + release_mem_region(mem->start, (mem->end - mem->start) + 1); + return ret; +} + +static int +msm_i2c_remove(struct platform_device *pdev) +{ + struct msm_i2c_dev *dev = platform_get_drvdata(pdev); + struct resource *mem; + + /* Grab mutex to ensure ongoing transaction is over */ + mutex_lock(&dev->mlock); + dev->suspended = 1; + mutex_unlock(&dev->mlock); + mutex_destroy(&dev->mlock); + del_timer_sync(&dev->pwr_timer); + if (dev->clk_state != 0) + msm_i2c_pwr_mgmt(dev, 0); + platform_set_drvdata(pdev, NULL); + pm_qos_remove_request(&dev->pm_qos_req); + free_irq(dev->irq, dev); + i2c_del_adapter(&dev->adap_pri); + i2c_del_adapter(&dev->adap_aux); + clk_put(dev->clk); + iounmap(dev->base); + kfree(dev); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem) + release_mem_region(mem->start, (mem->end - mem->start) + 1); + return 0; +} + +static int msm_i2c_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct msm_i2c_dev *dev = platform_get_drvdata(pdev); + /* Wait until current transaction finishes + * Make sure remote lock is released before we suspend + */ + if (dev) { + /* Grab mutex to ensure ongoing transaction is over */ + mutex_lock(&dev->mlock); + dev->suspended = 1; + mutex_unlock(&dev->mlock); + del_timer_sync(&dev->pwr_timer); + if (dev->clk_state != 0) + msm_i2c_pwr_mgmt(dev, 0); + } + + return 0; +} + +static int msm_i2c_resume(struct platform_device *pdev) +{ + struct msm_i2c_dev *dev = platform_get_drvdata(pdev); + dev->suspended = 0; + return 0; +} + +static struct platform_driver msm_i2c_driver = { + .probe = msm_i2c_probe, + .remove = msm_i2c_remove, + .suspend = msm_i2c_suspend, + .resume = msm_i2c_resume, + .driver = { + .name = "msm_i2c", + .owner = THIS_MODULE, + }, +}; + +/* I2C may be needed to bring up other drivers */ +static int __init +msm_i2c_init_driver(void) +{ + return platform_driver_register(&msm_i2c_driver); +} +subsys_initcall(msm_i2c_init_driver); + +static void __exit msm_i2c_exit_driver(void) +{ + platform_driver_unregister(&msm_i2c_driver); +} +module_exit(msm_i2c_exit_driver); + diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c new file mode 100644 index 00000000000..edb643bf7a6 --- /dev/null +++ b/drivers/i2c/busses/i2c-qup.c @@ -0,0 +1,1305 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * QUP driver for Qualcomm MSM platforms + * + */ + +/* #define DEBUG */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.2"); +MODULE_ALIAS("platform:i2c_qup"); + +/* QUP Registers */ +enum { + QUP_CONFIG = 0x0, + QUP_STATE = 0x4, + QUP_IO_MODE = 0x8, + QUP_SW_RESET = 0xC, + QUP_OPERATIONAL = 0x18, + QUP_ERROR_FLAGS = 0x1C, + QUP_ERROR_FLAGS_EN = 0x20, + QUP_MX_READ_CNT = 0x208, + QUP_MX_INPUT_CNT = 0x200, + QUP_MX_WR_CNT = 0x100, + QUP_OUT_DEBUG = 0x108, + QUP_OUT_FIFO_CNT = 0x10C, + QUP_OUT_FIFO_BASE = 0x110, + QUP_IN_READ_CUR = 0x20C, + QUP_IN_DEBUG = 0x210, + QUP_IN_FIFO_CNT = 0x214, + QUP_IN_FIFO_BASE = 0x218, + QUP_I2C_CLK_CTL = 0x400, + QUP_I2C_STATUS = 0x404, +}; + +/* QUP States and reset values */ +enum { + QUP_RESET_STATE = 0, + QUP_RUN_STATE = 1U, + QUP_STATE_MASK = 3U, + QUP_PAUSE_STATE = 3U, + QUP_STATE_VALID = 1U << 2, + QUP_I2C_MAST_GEN = 1U << 4, + QUP_OPERATIONAL_RESET = 0xFF0, + QUP_I2C_STATUS_RESET = 0xFFFFFC, +}; + +/* QUP OPERATIONAL FLAGS */ +enum { + QUP_OUT_SVC_FLAG = 1U << 8, + QUP_IN_SVC_FLAG = 1U << 9, + QUP_MX_INPUT_DONE = 1U << 11, +}; + +/* I2C mini core related values */ +enum { + I2C_MINI_CORE = 2U << 8, + I2C_N_VAL = 0xF, + +}; + +/* Packing Unpacking words in FIFOs , and IO modes*/ +enum { + QUP_WR_BLK_MODE = 1U << 10, + QUP_RD_BLK_MODE = 1U << 12, + QUP_UNPACK_EN = 1U << 14, + QUP_PACK_EN = 1U << 15, +}; + +/* QUP tags */ +enum { + QUP_OUT_NOP = 0, + QUP_OUT_START = 1U << 8, + QUP_OUT_DATA = 2U << 8, + QUP_OUT_STOP = 3U << 8, + QUP_OUT_REC = 4U << 8, + QUP_IN_DATA = 5U << 8, + QUP_IN_STOP = 6U << 8, + QUP_IN_NACK = 7U << 8, +}; + +/* Status, Error flags */ +enum { + I2C_STATUS_WR_BUFFER_FULL = 1U << 0, + I2C_STATUS_BUS_ACTIVE = 1U << 8, + I2C_STATUS_ERROR_MASK = 0x38000FC, + QUP_I2C_NACK_FLAG = 1U << 3, + QUP_IN_NOT_EMPTY = 1U << 5, + QUP_STATUS_ERROR_FLAGS = 0x7C, +}; + +/* Master status clock states */ +enum { + I2C_CLK_RESET_BUSIDLE_STATE = 0, + I2C_CLK_FORCED_LOW_STATE = 5, +}; + +#define QUP_MAX_CLK_STATE_RETRIES 300 + +static char const * const i2c_rsrcs[] = {"i2c_clk", "i2c_sda"}; + +struct qup_i2c_dev { + struct device *dev; + void __iomem *base; /* virtual */ + void __iomem *gsbi; /* virtual */ + int in_irq; + int out_irq; + int err_irq; + int num_irqs; + struct clk *clk; + struct clk *pclk; + struct i2c_adapter adapter; + + struct i2c_msg *msg; + int pos; + int cnt; + int err; + int mode; + int clk_ctl; + int one_bit_t; + int out_fifo_sz; + int in_fifo_sz; + int out_blk_sz; + int in_blk_sz; + int wr_sz; + struct msm_i2c_platform_data *pdata; + int suspended; + int clk_state; + struct timer_list pwr_timer; + struct mutex mlock; + void *complete; + int i2c_gpios[ARRAY_SIZE(i2c_rsrcs)]; +}; + +#ifdef DEBUG +static void +qup_print_status(struct qup_i2c_dev *dev) +{ + uint32_t val; + val = readl_relaxed(dev->base+QUP_CONFIG); + dev_dbg(dev->dev, "Qup config is :0x%x\n", val); + val = readl_relaxed(dev->base+QUP_STATE); + dev_dbg(dev->dev, "Qup state is :0x%x\n", val); + val = readl_relaxed(dev->base+QUP_IO_MODE); + dev_dbg(dev->dev, "Qup mode is :0x%x\n", val); +} +#else +static inline void qup_print_status(struct qup_i2c_dev *dev) +{ +} +#endif + +static irqreturn_t +qup_i2c_interrupt(int irq, void *devid) +{ + struct qup_i2c_dev *dev = devid; + uint32_t status = readl_relaxed(dev->base + QUP_I2C_STATUS); + uint32_t status1 = readl_relaxed(dev->base + QUP_ERROR_FLAGS); + uint32_t op_flgs = readl_relaxed(dev->base + QUP_OPERATIONAL); + int err = 0; + + if (!dev->msg || !dev->complete) { + /* Clear Error interrupt if it's a level triggered interrupt*/ + if (dev->num_irqs == 1) { + writel_relaxed(QUP_RESET_STATE, dev->base+QUP_STATE); + /* Ensure that state is written before ISR exits */ + mb(); + } + return IRQ_HANDLED; + } + + if (status & I2C_STATUS_ERROR_MASK) { + dev_err(dev->dev, "QUP: I2C status flags :0x%x, irq:%d\n", + status, irq); + err = status; + /* Clear Error interrupt if it's a level triggered interrupt*/ + if (dev->num_irqs == 1) { + writel_relaxed(QUP_RESET_STATE, dev->base+QUP_STATE); + /* Ensure that state is written before ISR exits */ + mb(); + } + goto intr_done; + } + + if (status1 & 0x7F) { + dev_err(dev->dev, "QUP: QUP status flags :0x%x\n", status1); + err = -status1; + /* Clear Error interrupt if it's a level triggered interrupt*/ + if (dev->num_irqs == 1) { + writel_relaxed((status1 & QUP_STATUS_ERROR_FLAGS), + dev->base + QUP_ERROR_FLAGS); + /* Ensure that error flags are cleared before ISR + * exits + */ + mb(); + } + goto intr_done; + } + + if ((dev->num_irqs == 3) && (dev->msg->flags == I2C_M_RD) + && (irq == dev->out_irq)) + return IRQ_HANDLED; + if (op_flgs & QUP_OUT_SVC_FLAG) { + writel_relaxed(QUP_OUT_SVC_FLAG, dev->base + QUP_OPERATIONAL); + /* Ensure that service flag is acknowledged before ISR exits */ + mb(); + } + if (dev->msg->flags == I2C_M_RD) { + if ((op_flgs & QUP_MX_INPUT_DONE) || + (op_flgs & QUP_IN_SVC_FLAG)) { + writel_relaxed(QUP_IN_SVC_FLAG, dev->base + + QUP_OPERATIONAL); + /* Ensure that service flag is acknowledged before ISR + * exits + */ + mb(); + } else + return IRQ_HANDLED; + } + +intr_done: + dev_dbg(dev->dev, "QUP intr= %d, i2c status=0x%x, qup status = 0x%x\n", + irq, status, status1); + qup_print_status(dev); + dev->err = err; + complete(dev->complete); + return IRQ_HANDLED; +} + +static void +qup_i2c_pwr_mgmt(struct qup_i2c_dev *dev, unsigned int state) +{ + dev->clk_state = state; + if (state != 0) { + clk_enable(dev->clk); + if (dev->pclk) + clk_enable(dev->pclk); + } else { + clk_disable(dev->clk); + if (dev->pclk) + clk_disable(dev->pclk); + } +} + +static void +qup_i2c_pwr_timer(unsigned long data) +{ + struct qup_i2c_dev *dev = (struct qup_i2c_dev *) data; + dev_dbg(dev->dev, "QUP_Power: Inactivity based power management\n"); + if (dev->clk_state == 1) + qup_i2c_pwr_mgmt(dev, 0); +} + +static int +qup_i2c_poll_writeready(struct qup_i2c_dev *dev, int rem) +{ + uint32_t retries = 0; + + while (retries != 2000) { + uint32_t status = readl_relaxed(dev->base + QUP_I2C_STATUS); + + if (!(status & I2C_STATUS_WR_BUFFER_FULL)) { + if (((dev->msg->flags & I2C_M_RD) || (rem == 0)) && + !(status & I2C_STATUS_BUS_ACTIVE)) + return 0; + else if ((dev->msg->flags == 0) && (rem > 0)) + return 0; + else /* 1-bit delay before we check for bus busy */ + udelay(dev->one_bit_t); + } + if (retries++ == 1000) + udelay(100); + } + qup_print_status(dev); + return -ETIMEDOUT; +} + +static int qup_i2c_poll_clock_ready(struct qup_i2c_dev *dev) +{ + uint32_t retries = 0; + + /* + * Wait for the clock state to transition to either IDLE or FORCED + * LOW. This will usually happen within one cycle of the i2c clock. + */ + + while (retries++ < QUP_MAX_CLK_STATE_RETRIES) { + uint32_t status = readl_relaxed(dev->base + QUP_I2C_STATUS); + uint32_t clk_state = (status >> 13) & 0x7; + + if (clk_state == I2C_CLK_RESET_BUSIDLE_STATE || + clk_state == I2C_CLK_FORCED_LOW_STATE) + return 0; + /* 1-bit delay before we check again */ + udelay(dev->one_bit_t); + } + + dev_err(dev->dev, "Error waiting for clk ready\n"); + return -ETIMEDOUT; +} + +static int +qup_i2c_poll_state(struct qup_i2c_dev *dev, uint32_t state) +{ + uint32_t retries = 0; + + dev_dbg(dev->dev, "Polling Status for state:0x%x\n", state); + + while (retries != 2000) { + uint32_t status = readl_relaxed(dev->base + QUP_STATE); + + if ((status & (QUP_STATE_VALID | state)) == + (QUP_STATE_VALID | state)) + return 0; + else if (retries++ == 1000) + udelay(100); + } + return -ETIMEDOUT; +} + +static inline int qup_i2c_request_gpios(struct qup_i2c_dev *dev) +{ + int i; + int result = 0; + + for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) { + if (dev->i2c_gpios[i] >= 0) { + result = gpio_request(dev->i2c_gpios[i], i2c_rsrcs[i]); + if (result) { + dev_err(dev->dev, + "gpio_request for pin %d failed\ + with error %d\n", dev->i2c_gpios[i], + result); + goto error; + } + } + } + return 0; + +error: + for (; --i >= 0;) { + if (dev->i2c_gpios[i] >= 0) + gpio_free(dev->i2c_gpios[i]); + } + return result; +} + +static inline void qup_i2c_free_gpios(struct qup_i2c_dev *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) { + if (dev->i2c_gpios[i] >= 0) + gpio_free(dev->i2c_gpios[i]); + } +} + +#ifdef DEBUG +static void qup_verify_fifo(struct qup_i2c_dev *dev, uint32_t val, + uint32_t addr, int rdwr) +{ + if (rdwr) + dev_dbg(dev->dev, "RD:Wrote 0x%x to out_ff:0x%x\n", val, addr); + else + dev_dbg(dev->dev, "WR:Wrote 0x%x to out_ff:0x%x\n", val, addr); +} +#else +static inline void qup_verify_fifo(struct qup_i2c_dev *dev, uint32_t val, + uint32_t addr, int rdwr) +{ +} +#endif + +static void +qup_issue_read(struct qup_i2c_dev *dev, struct i2c_msg *msg, int *idx, + uint32_t carry_over) +{ + uint16_t addr = (msg->addr << 1) | 1; + /* QUP limit 256 bytes per read. By HW design, 0 in the 8-bit field + * is treated as 256 byte read. + */ + uint16_t rd_len = ((dev->cnt == 256) ? 0 : dev->cnt); + + if (*idx % 4) { + writel_relaxed(carry_over | ((QUP_OUT_START | addr) << 16), + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx-2)); */ + + qup_verify_fifo(dev, carry_over | + ((QUP_OUT_START | addr) << 16), (uint32_t)dev->base + + QUP_OUT_FIFO_BASE + (*idx - 2), 1); + writel_relaxed((QUP_OUT_REC | rd_len), + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx+2)); */ + + qup_verify_fifo(dev, (QUP_OUT_REC | rd_len), + (uint32_t)dev->base + QUP_OUT_FIFO_BASE + (*idx + 2), 1); + } else { + writel_relaxed(((QUP_OUT_REC | rd_len) << 16) + | QUP_OUT_START | addr, + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx)); */ + + qup_verify_fifo(dev, QUP_OUT_REC << 16 | rd_len << 16 | + QUP_OUT_START | addr, + (uint32_t)dev->base + QUP_OUT_FIFO_BASE + (*idx), 1); + } + *idx += 4; +} + +static void +qup_issue_write(struct qup_i2c_dev *dev, struct i2c_msg *msg, int rem, + int *idx, uint32_t *carry_over) +{ + int entries = dev->cnt; + int empty_sl = dev->wr_sz - ((*idx) >> 1); + int i = 0; + uint32_t val = 0; + uint32_t last_entry = 0; + uint16_t addr = msg->addr << 1; + + if (dev->pos == 0) { + if (*idx % 4) { + writel_relaxed(*carry_over | ((QUP_OUT_START | + addr) << 16), + dev->base + QUP_OUT_FIFO_BASE); + + qup_verify_fifo(dev, *carry_over | QUP_OUT_DATA << 16 | + addr << 16, (uint32_t)dev->base + + QUP_OUT_FIFO_BASE + (*idx) - 2, 0); + } else + val = QUP_OUT_START | addr; + *idx += 2; + i++; + entries++; + } else { + /* Avoid setp time issue by adding 1 NOP when number of bytes + * are more than FIFO/BLOCK size. setup time issue can't appear + * otherwise since next byte to be written will always be ready + */ + val = (QUP_OUT_NOP | 1); + *idx += 2; + i++; + entries++; + } + if (entries > empty_sl) + entries = empty_sl; + + for (; i < (entries - 1); i++) { + if (*idx % 4) { + writel_relaxed(val | ((QUP_OUT_DATA | + msg->buf[dev->pos]) << 16), + dev->base + QUP_OUT_FIFO_BASE); + + qup_verify_fifo(dev, val | QUP_OUT_DATA << 16 | + msg->buf[dev->pos] << 16, (uint32_t)dev->base + + QUP_OUT_FIFO_BASE + (*idx) - 2, 0); + } else + val = QUP_OUT_DATA | msg->buf[dev->pos]; + (*idx) += 2; + dev->pos++; + } + if (dev->pos < (msg->len - 1)) + last_entry = QUP_OUT_DATA; + else if (rem > 1) /* not last array entry */ + last_entry = QUP_OUT_DATA; + else + last_entry = QUP_OUT_STOP; + if ((*idx % 4) == 0) { + /* + * If read-start and read-command end up in different fifos, it + * may result in extra-byte being read due to extra-read cycle. + * Avoid that by inserting NOP as the last entry of fifo only + * if write command(s) leave 1 space in fifo. + */ + if (rem > 1) { + struct i2c_msg *next = msg + 1; + if (next->addr == msg->addr && (next->flags | I2C_M_RD) + && *idx == ((dev->wr_sz*2) - 4)) { + writel_relaxed(((last_entry | + msg->buf[dev->pos]) | + ((1 | QUP_OUT_NOP) << 16)), dev->base + + QUP_OUT_FIFO_BASE);/* + (*idx) - 2); */ + + qup_verify_fifo(dev, + ((last_entry | msg->buf[dev->pos]) | + ((1 | QUP_OUT_NOP) << 16)), + (uint32_t)dev->base + + QUP_OUT_FIFO_BASE + (*idx), 0); + *idx += 2; + } else if (next->flags == 0 && dev->pos == msg->len - 1 + && *idx < (dev->wr_sz*2)) { + /* Last byte of an intermittent write */ + writel_relaxed((last_entry | + msg->buf[dev->pos]), + dev->base + QUP_OUT_FIFO_BASE); + + qup_verify_fifo(dev, + last_entry | msg->buf[dev->pos], + (uint32_t)dev->base + + QUP_OUT_FIFO_BASE + (*idx), 0); + *idx += 2; + } else + *carry_over = (last_entry | msg->buf[dev->pos]); + } else { + writel_relaxed((last_entry | msg->buf[dev->pos]), + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx) - 2); */ + + qup_verify_fifo(dev, last_entry | msg->buf[dev->pos], + (uint32_t)dev->base + QUP_OUT_FIFO_BASE + + (*idx), 0); + } + } else { + writel_relaxed(val | ((last_entry | msg->buf[dev->pos]) << 16), + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx) - 2); */ + + qup_verify_fifo(dev, val | (last_entry << 16) | + (msg->buf[dev->pos] << 16), (uint32_t)dev->base + + QUP_OUT_FIFO_BASE + (*idx) - 2, 0); + } + + *idx += 2; + dev->pos++; + dev->cnt = msg->len - dev->pos; +} + +static int +qup_update_state(struct qup_i2c_dev *dev, uint32_t state) +{ + if (qup_i2c_poll_state(dev, 0) != 0) + return -EIO; + writel_relaxed(state, dev->base + QUP_STATE); + if (qup_i2c_poll_state(dev, state) != 0) + return -EIO; + return 0; +} + +static void +qup_set_read_mode(struct qup_i2c_dev *dev, int rd_len) +{ + uint32_t wr_mode = (dev->wr_sz < dev->out_fifo_sz) ? + QUP_WR_BLK_MODE : 0; + if (rd_len > 256) { + dev_dbg(dev->dev, "HW limit: Breaking reads in chunk of 256\n"); + rd_len = 256; + } + if (rd_len <= dev->in_fifo_sz) { + writel_relaxed(wr_mode | QUP_PACK_EN | QUP_UNPACK_EN, + dev->base + QUP_IO_MODE); + writel_relaxed(rd_len, dev->base + QUP_MX_READ_CNT); + } else { + writel_relaxed(wr_mode | QUP_RD_BLK_MODE | + QUP_PACK_EN | QUP_UNPACK_EN, dev->base + QUP_IO_MODE); + writel_relaxed(rd_len, dev->base + QUP_MX_INPUT_CNT); + } +} + +static int +qup_set_wr_mode(struct qup_i2c_dev *dev, int rem) +{ + int total_len = 0; + int ret = 0; + if (dev->msg->len >= (dev->out_fifo_sz - 1)) { + total_len = dev->msg->len + 1 + + (dev->msg->len/(dev->out_blk_sz-1)); + writel_relaxed(QUP_WR_BLK_MODE | QUP_PACK_EN | QUP_UNPACK_EN, + dev->base + QUP_IO_MODE); + dev->wr_sz = dev->out_blk_sz; + } else + writel_relaxed(QUP_PACK_EN | QUP_UNPACK_EN, + dev->base + QUP_IO_MODE); + + if (rem > 1) { + struct i2c_msg *next = dev->msg + 1; + if (next->addr == dev->msg->addr && + next->flags == I2C_M_RD) { + qup_set_read_mode(dev, next->len); + /* make sure read start & read command are in 1 blk */ + if ((total_len % dev->out_blk_sz) == + (dev->out_blk_sz - 1)) + total_len += 3; + else + total_len += 2; + } + } + /* WRITE COUNT register valid/used only in block mode */ + if (dev->wr_sz == dev->out_blk_sz) + writel_relaxed(total_len, dev->base + QUP_MX_WR_CNT); + return ret; +} + +static int +qup_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + DECLARE_COMPLETION_ONSTACK(complete); + struct qup_i2c_dev *dev = i2c_get_adapdata(adap); + int ret; + int rem = num; + long timeout; + int err; + + del_timer_sync(&dev->pwr_timer); + mutex_lock(&dev->mlock); + + if (dev->suspended) { + mutex_unlock(&dev->mlock); + return -EIO; + } + + if (dev->clk_state == 0) { + if (dev->clk_ctl == 0) { + if (dev->pdata->src_clk_rate > 0) + clk_set_rate(dev->clk, + dev->pdata->src_clk_rate); + else + dev->pdata->src_clk_rate = 19200000; + } + qup_i2c_pwr_mgmt(dev, 1); + } + /* Initialize QUP registers during first transfer */ + if (dev->clk_ctl == 0) { + int fs_div; + int hs_div; + uint32_t fifo_reg; + + if (dev->gsbi) { + writel_relaxed(0x2 << 4, dev->gsbi); + /* GSBI memory is not in the same 1K region as other + * QUP registers. mb() here ensures that the GSBI + * register is updated in correct order and that the + * write has gone through before programming QUP core + * registers + */ + mb(); + } + + fs_div = ((dev->pdata->src_clk_rate + / dev->pdata->clk_freq) / 2) - 3; + hs_div = 3; + dev->clk_ctl = ((hs_div & 0x7) << 8) | (fs_div & 0xff); + fifo_reg = readl_relaxed(dev->base + QUP_IO_MODE); + if (fifo_reg & 0x3) + dev->out_blk_sz = (fifo_reg & 0x3) * 16; + else + dev->out_blk_sz = 16; + if (fifo_reg & 0x60) + dev->in_blk_sz = ((fifo_reg & 0x60) >> 5) * 16; + else + dev->in_blk_sz = 16; + /* + * The block/fifo size w.r.t. 'actual data' is 1/2 due to 'tag' + * associated with each byte written/received + */ + dev->out_blk_sz /= 2; + dev->in_blk_sz /= 2; + dev->out_fifo_sz = dev->out_blk_sz * + (2 << ((fifo_reg & 0x1C) >> 2)); + dev->in_fifo_sz = dev->in_blk_sz * + (2 << ((fifo_reg & 0x380) >> 7)); + dev_dbg(dev->dev, "QUP IN:bl:%d, ff:%d, OUT:bl:%d, ff:%d\n", + dev->in_blk_sz, dev->in_fifo_sz, + dev->out_blk_sz, dev->out_fifo_sz); + } + + writel_relaxed(1, dev->base + QUP_SW_RESET); + ret = qup_i2c_poll_state(dev, QUP_RESET_STATE); + if (ret) { + dev_err(dev->dev, "QUP Busy:Trying to recover\n"); + goto out_err; + } + + if (dev->num_irqs == 3) { + enable_irq(dev->in_irq); + enable_irq(dev->out_irq); + } + enable_irq(dev->err_irq); + + /* Initialize QUP registers */ + writel_relaxed(0, dev->base + QUP_CONFIG); + writel_relaxed(QUP_OPERATIONAL_RESET, dev->base + QUP_OPERATIONAL); + writel_relaxed(QUP_STATUS_ERROR_FLAGS, dev->base + QUP_ERROR_FLAGS_EN); + + writel_relaxed(I2C_MINI_CORE | I2C_N_VAL, dev->base + QUP_CONFIG); + + /* Initialize I2C mini core registers */ + writel_relaxed(0, dev->base + QUP_I2C_CLK_CTL); + writel_relaxed(QUP_I2C_STATUS_RESET, dev->base + QUP_I2C_STATUS); + + while (rem) { + bool filled = false; + + dev->cnt = msgs->len - dev->pos; + dev->msg = msgs; + + dev->wr_sz = dev->out_fifo_sz; + dev->err = 0; + dev->complete = &complete; + + if (qup_i2c_poll_state(dev, QUP_I2C_MAST_GEN) != 0) { + ret = -EIO; + goto out_err; + } + + qup_print_status(dev); + /* HW limits Read upto 256 bytes in 1 read without stop */ + if (dev->msg->flags & I2C_M_RD) { + qup_set_read_mode(dev, dev->cnt); + if (dev->cnt > 256) + dev->cnt = 256; + } else { + ret = qup_set_wr_mode(dev, rem); + if (ret != 0) + goto out_err; + /* Don't fill block till we get interrupt */ + if (dev->wr_sz == dev->out_blk_sz) + filled = true; + } + + err = qup_update_state(dev, QUP_RUN_STATE); + if (err < 0) { + ret = err; + goto out_err; + } + + qup_print_status(dev); + writel_relaxed(dev->clk_ctl, dev->base + QUP_I2C_CLK_CTL); + /* CLK_CTL register is not in the same 1K region as other QUP + * registers. Ensure that clock control is written before + * programming other QUP registers + */ + mb(); + + do { + int idx = 0; + uint32_t carry_over = 0; + + /* Transition to PAUSE state only possible from RUN */ + err = qup_update_state(dev, QUP_PAUSE_STATE); + if (err < 0) { + ret = err; + goto out_err; + } + + qup_print_status(dev); + /* This operation is Write, check the next operation + * and decide mode + */ + while (filled == false) { + if ((msgs->flags & I2C_M_RD)) + qup_issue_read(dev, msgs, &idx, + carry_over); + else if (!(msgs->flags & I2C_M_RD)) + qup_issue_write(dev, msgs, rem, &idx, + &carry_over); + if (idx >= (dev->wr_sz << 1)) + filled = true; + /* Start new message */ + if (filled == false) { + if (msgs->flags & I2C_M_RD) + filled = true; + else if (rem > 1) { + /* Only combine operations with + * same address + */ + struct i2c_msg *next = msgs + 1; + if (next->addr != msgs->addr) + filled = true; + else { + rem--; + msgs++; + dev->msg = msgs; + dev->pos = 0; + dev->cnt = msgs->len; + if (msgs->len > 256) + dev->cnt = 256; + } + } else + filled = true; + } + } + err = qup_update_state(dev, QUP_RUN_STATE); + if (err < 0) { + ret = err; + goto out_err; + } + dev_dbg(dev->dev, "idx:%d, rem:%d, num:%d, mode:%d\n", + idx, rem, num, dev->mode); + + qup_print_status(dev); + timeout = wait_for_completion_timeout(&complete, HZ); + if (!timeout) { + uint32_t istatus = readl_relaxed(dev->base + + QUP_I2C_STATUS); + uint32_t qstatus = readl_relaxed(dev->base + + QUP_ERROR_FLAGS); + uint32_t op_flgs = readl_relaxed(dev->base + + QUP_OPERATIONAL); + + dev_err(dev->dev, "Transaction timed out\n"); + dev_err(dev->dev, "I2C Status: %x\n", istatus); + dev_err(dev->dev, "QUP Status: %x\n", qstatus); + dev_err(dev->dev, "OP Flags: %x\n", op_flgs); + writel_relaxed(1, dev->base + QUP_SW_RESET); + /* Make sure that the write has gone through + * before returning from the function + */ + mb(); + ret = -ETIMEDOUT; + goto out_err; + } + if (dev->err) { + if (dev->err > 0 && + dev->err & QUP_I2C_NACK_FLAG) + dev_err(dev->dev, + "I2C slave addr:0x%x not connected\n", + dev->msg->addr); + else if (dev->err < 0) { + dev_err(dev->dev, + "QUP data xfer error %d\n", dev->err); + ret = dev->err; + goto out_err; + } + ret = -dev->err; + goto out_err; + } + if (dev->msg->flags & I2C_M_RD) { + int i; + uint32_t dval = 0; + for (i = 0; dev->pos < dev->msg->len; i++, + dev->pos++) { + uint32_t rd_status = + readl_relaxed(dev->base + + QUP_OPERATIONAL); + if (i % 2 == 0) { + if ((rd_status & + QUP_IN_NOT_EMPTY) == 0) + break; + dval = readl_relaxed(dev->base + + QUP_IN_FIFO_BASE); + dev->msg->buf[dev->pos] = + dval & 0xFF; + } else + dev->msg->buf[dev->pos] = + ((dval & 0xFF0000) >> + 16); + } + dev->cnt -= i; + } else + filled = false; /* refill output FIFO */ + dev_dbg(dev->dev, "pos:%d, len:%d, cnt:%d\n", + dev->pos, msgs->len, dev->cnt); + } while (dev->cnt > 0); + if (dev->cnt == 0) { + if (msgs->len == dev->pos) { + rem--; + msgs++; + dev->pos = 0; + } + if (rem) { + err = qup_i2c_poll_clock_ready(dev); + if (err < 0) { + ret = err; + goto out_err; + } + err = qup_update_state(dev, QUP_RESET_STATE); + if (err < 0) { + ret = err; + goto out_err; + } + } + } + /* Wait for I2C bus to be idle */ + ret = qup_i2c_poll_writeready(dev, rem); + if (ret) { + dev_err(dev->dev, + "Error waiting for write ready\n"); + goto out_err; + } + } + + ret = num; + out_err: + disable_irq(dev->err_irq); + if (dev->num_irqs == 3) { + disable_irq(dev->in_irq); + disable_irq(dev->out_irq); + } + dev->complete = NULL; + dev->msg = NULL; + dev->pos = 0; + dev->err = 0; + dev->cnt = 0; + dev->pwr_timer.expires = jiffies + 3*HZ; + add_timer(&dev->pwr_timer); + mutex_unlock(&dev->mlock); + return ret; +} + +static u32 +qup_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +static const struct i2c_algorithm qup_i2c_algo = { + .master_xfer = qup_i2c_xfer, + .functionality = qup_i2c_func, +}; + +static int __devinit +qup_i2c_probe(struct platform_device *pdev) +{ + struct qup_i2c_dev *dev; + struct resource *qup_mem, *gsbi_mem, *qup_io, *gsbi_io, *res; + struct resource *in_irq, *out_irq, *err_irq; + struct clk *clk, *pclk; + int ret = 0; + int i; + struct msm_i2c_platform_data *pdata; + const char *qup_apps_clk_name = "qup_clk"; + + gsbi_mem = NULL; + dev_dbg(&pdev->dev, "qup_i2c_probe\n"); + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "platform data not initialized\n"); + return -ENOSYS; + } + qup_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "qup_phys_addr"); + if (!qup_mem) { + dev_err(&pdev->dev, "no qup mem resource?\n"); + return -ENODEV; + } + + /* + * We only have 1 interrupt for new hardware targets and in_irq, + * out_irq will be NULL for those platforms + */ + in_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "qup_in_intr"); + + out_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "qup_out_intr"); + + err_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "qup_err_intr"); + if (!err_irq) { + dev_err(&pdev->dev, "no error irq resource?\n"); + return -ENODEV; + } + + qup_io = request_mem_region(qup_mem->start, resource_size(qup_mem), + pdev->name); + if (!qup_io) { + dev_err(&pdev->dev, "QUP region already claimed\n"); + return -EBUSY; + } + if (!pdata->use_gsbi_shared_mode) { + gsbi_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "gsbi_qup_i2c_addr"); + if (!gsbi_mem) { + dev_err(&pdev->dev, "no gsbi mem resource?\n"); + return -ENODEV; + } + gsbi_io = request_mem_region(gsbi_mem->start, + resource_size(gsbi_mem), + pdev->name); + if (!gsbi_io) { + dev_err(&pdev->dev, "GSBI region already claimed\n"); + return -EBUSY; + } + } + + if (pdata->clk != NULL) + qup_apps_clk_name = pdata->clk; + + clk = clk_get(&pdev->dev, qup_apps_clk_name); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Could not get clock\n"); + ret = PTR_ERR(clk); + goto err_clk_get_failed; + } + + if (pdata->pclk != NULL) { + pclk = clk_get(&pdev->dev, pdata->pclk); + if (IS_ERR(pclk)) { + dev_err(&pdev->dev, "Could not get pclock\n"); + ret = PTR_ERR(pclk); + clk_put(clk); + goto err_clk_get_failed; + } + } else + pclk = NULL; + + if (!(pdata->msm_i2c_config_gpio)) { + dev_err(&pdev->dev, "config_gpio function not initialized\n"); + ret = -ENOSYS; + goto err_config_failed; + } + + /* We support frequencies upto FAST Mode(400KHz) */ + if (pdata->clk_freq <= 0 || + pdata->clk_freq > 400000) { + dev_err(&pdev->dev, "clock frequency not supported\n"); + ret = -EIO; + goto err_config_failed; + } + + dev = kzalloc(sizeof(struct qup_i2c_dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err_alloc_dev_failed; + } + + dev->dev = &pdev->dev; + if (in_irq) + dev->in_irq = in_irq->start; + if (out_irq) + dev->out_irq = out_irq->start; + dev->err_irq = err_irq->start; + if (in_irq && out_irq) + dev->num_irqs = 3; + else + dev->num_irqs = 1; + dev->clk = clk; + dev->pclk = pclk; + dev->base = ioremap(qup_mem->start, resource_size(qup_mem)); + if (!dev->base) { + ret = -ENOMEM; + goto err_ioremap_failed; + } + + /* Configure GSBI block to use I2C functionality */ + if (gsbi_mem) { + dev->gsbi = ioremap(gsbi_mem->start, resource_size(gsbi_mem)); + if (!dev->gsbi) { + ret = -ENOMEM; + goto err_gsbi_failed; + } + } + + for (i = 0; i < ARRAY_SIZE(i2c_rsrcs); ++i) { + res = platform_get_resource_byname(pdev, IORESOURCE_IO, + i2c_rsrcs[i]); + dev->i2c_gpios[i] = res ? res->start : -1; + } + + ret = qup_i2c_request_gpios(dev); + if (ret) + goto err_request_gpio_failed; + + platform_set_drvdata(pdev, dev); + + dev->one_bit_t = USEC_PER_SEC/pdata->clk_freq; + dev->pdata = pdata; + dev->clk_ctl = 0; + dev->pos = 0; + + /* + * We use num_irqs to also indicate if we got 3 interrupts or just 1. + * If we have just 1, we use err_irq as the general purpose irq + * and handle the changes in ISR accordingly + * Per Hardware guidelines, if we have 3 interrupts, they are always + * edge triggering, and if we have 1, it's always level-triggering + */ + if (dev->num_irqs == 3) { + ret = request_irq(dev->in_irq, qup_i2c_interrupt, + IRQF_TRIGGER_RISING, "qup_in_intr", dev); + if (ret) { + dev_err(&pdev->dev, "request_in_irq failed\n"); + goto err_request_irq_failed; + } + /* + * We assume out_irq exists if in_irq does since platform + * configuration either has 3 interrupts assigned to QUP or 1 + */ + ret = request_irq(dev->out_irq, qup_i2c_interrupt, + IRQF_TRIGGER_RISING, "qup_out_intr", dev); + if (ret) { + dev_err(&pdev->dev, "request_out_irq failed\n"); + free_irq(dev->in_irq, dev); + goto err_request_irq_failed; + } + ret = request_irq(dev->err_irq, qup_i2c_interrupt, + IRQF_TRIGGER_RISING, "qup_err_intr", dev); + if (ret) { + dev_err(&pdev->dev, "request_err_irq failed\n"); + free_irq(dev->out_irq, dev); + free_irq(dev->in_irq, dev); + goto err_request_irq_failed; + } + } else { + ret = request_irq(dev->err_irq, qup_i2c_interrupt, + IRQF_TRIGGER_HIGH, "qup_err_intr", dev); + if (ret) { + dev_err(&pdev->dev, "request_err_irq failed\n"); + goto err_request_irq_failed; + } + } + disable_irq(dev->err_irq); + if (dev->num_irqs == 3) { + disable_irq(dev->in_irq); + disable_irq(dev->out_irq); + } + i2c_set_adapdata(&dev->adapter, dev); + dev->adapter.algo = &qup_i2c_algo; + strlcpy(dev->adapter.name, + "QUP I2C adapter", + sizeof(dev->adapter.name)); + dev->adapter.nr = pdev->id; + pdata->msm_i2c_config_gpio(dev->adapter.nr, 1); + + dev->suspended = 0; + mutex_init(&dev->mlock); + dev->clk_state = 0; + setup_timer(&dev->pwr_timer, qup_i2c_pwr_timer, (unsigned long) dev); + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = i2c_add_numbered_adapter(&dev->adapter); + if (ret) { + dev_err(&pdev->dev, "i2c_add_adapter failed\n"); + if (dev->num_irqs == 3) { + free_irq(dev->out_irq, dev); + free_irq(dev->in_irq, dev); + } + free_irq(dev->err_irq, dev); + } else + return 0; + + +err_request_irq_failed: + qup_i2c_free_gpios(dev); + if (dev->gsbi) + iounmap(dev->gsbi); +err_request_gpio_failed: +err_gsbi_failed: + iounmap(dev->base); +err_ioremap_failed: + kfree(dev); +err_alloc_dev_failed: +err_config_failed: + clk_put(clk); + if (pclk) + clk_put(pclk); +err_clk_get_failed: + if (gsbi_mem) + release_mem_region(gsbi_mem->start, resource_size(gsbi_mem)); + release_mem_region(qup_mem->start, resource_size(qup_mem)); + return ret; +} + +static int __devexit +qup_i2c_remove(struct platform_device *pdev) +{ + struct qup_i2c_dev *dev = platform_get_drvdata(pdev); + struct resource *qup_mem, *gsbi_mem; + + /* Grab mutex to ensure ongoing transaction is over */ + mutex_lock(&dev->mlock); + dev->suspended = 1; + mutex_unlock(&dev->mlock); + mutex_destroy(&dev->mlock); + del_timer_sync(&dev->pwr_timer); + if (dev->clk_state != 0) + qup_i2c_pwr_mgmt(dev, 0); + platform_set_drvdata(pdev, NULL); + if (dev->num_irqs == 3) { + free_irq(dev->out_irq, dev); + free_irq(dev->in_irq, dev); + } + free_irq(dev->err_irq, dev); + i2c_del_adapter(&dev->adapter); + clk_put(dev->clk); + if (dev->pclk) + clk_put(dev->pclk); + qup_i2c_free_gpios(dev); + if (dev->gsbi) + iounmap(dev->gsbi); + iounmap(dev->base); + + pm_runtime_disable(&pdev->dev); + + kfree(dev); + if (!(dev->pdata->use_gsbi_shared_mode)) { + gsbi_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "gsbi_qup_i2c_addr"); + release_mem_region(gsbi_mem->start, resource_size(gsbi_mem)); + } + qup_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "qup_phys_addr"); + release_mem_region(qup_mem->start, resource_size(qup_mem)); + return 0; +} + +#ifdef CONFIG_PM +static int qup_i2c_suspend(struct device *device) +{ + struct platform_device *pdev = to_platform_device(device); + struct qup_i2c_dev *dev = platform_get_drvdata(pdev); + + /* Grab mutex to ensure ongoing transaction is over */ + mutex_lock(&dev->mlock); + dev->suspended = 1; + mutex_unlock(&dev->mlock); + del_timer_sync(&dev->pwr_timer); + if (dev->clk_state != 0) + qup_i2c_pwr_mgmt(dev, 0); + qup_i2c_free_gpios(dev); + return 0; +} + +static int qup_i2c_resume(struct device *device) +{ + struct platform_device *pdev = to_platform_device(device); + struct qup_i2c_dev *dev = platform_get_drvdata(pdev); + BUG_ON(qup_i2c_request_gpios(dev) != 0); + dev->suspended = 0; + return 0; +} +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME +static int i2c_qup_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: idle...\n"); + return 0; +} + +static int i2c_qup_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int i2c_qup_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} +#endif + +static const struct dev_pm_ops i2c_qup_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS( + qup_i2c_suspend, + qup_i2c_resume + ) + SET_RUNTIME_PM_OPS( + i2c_qup_runtime_suspend, + i2c_qup_runtime_resume, + i2c_qup_runtime_idle + ) +}; + +static struct platform_driver qup_i2c_driver = { + .probe = qup_i2c_probe, + .remove = __devexit_p(qup_i2c_remove), + .driver = { + .name = "qup_i2c", + .owner = THIS_MODULE, + .pm = &i2c_qup_dev_pm_ops, + }, +}; + +/* QUP may be needed to bring up other drivers */ +static int __init +qup_i2c_init_driver(void) +{ + return platform_driver_register(&qup_i2c_driver); +} +arch_initcall(qup_i2c_init_driver); + +static void __exit qup_i2c_exit_driver(void) +{ + platform_driver_unregister(&qup_i2c_driver); +} +module_exit(qup_i2c_exit_driver); + diff --git a/drivers/i2c/busses/i2c-ssbi.c b/drivers/i2c/busses/i2c-ssbi.c new file mode 100644 index 00000000000..b371d47c70c --- /dev/null +++ b/drivers/i2c/busses/i2c-ssbi.c @@ -0,0 +1,516 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * SSBI driver for Qualcomm MSM platforms + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SSBI 2.0 controller registers */ +#define SSBI2_CMD 0x0008 +#define SSBI2_RD 0x0010 +#define SSBI2_STATUS 0x0014 +#define SSBI2_MODE2 0x001C + +/* SSBI_CMD fields */ +#define SSBI_CMD_RDWRN (0x01 << 24) +#define SSBI_CMD_REG_ADDR_SHFT (0x10) +#define SSBI_CMD_REG_ADDR_MASK (0xFF << SSBI_CMD_REG_ADDR_SHFT) +#define SSBI_CMD_REG_DATA_SHFT (0x00) +#define SSBI_CMD_REG_DATA_MASK (0xFF << SSBI_CMD_REG_DATA_SHFT) + +/* SSBI_STATUS fields */ +#define SSBI_STATUS_DATA_IN 0x10 +#define SSBI_STATUS_RD_CLOBBERED 0x08 +#define SSBI_STATUS_RD_READY 0x04 +#define SSBI_STATUS_READY 0x02 +#define SSBI_STATUS_MCHN_BUSY 0x01 + +/* SSBI_RD fields */ +#define SSBI_RD_RDWRN 0x01000000 +#define SSBI_RD_REG_ADDR_SHFT 0x10 +#define SSBI_RD_REG_ADDR_MASK (0xFF << SSBI_RD_REG_ADDR_SHFT) +#define SSBI_RD_REG_DATA_SHFT (0x00) +#define SSBI_RD_REG_DATA_MASK (0xFF << SSBI_RD_REG_DATA_SHFT) + +/* SSBI_MODE2 fields */ +#define SSBI_MODE2_REG_ADDR_15_8_SHFT 0x04 +#define SSBI_MODE2_REG_ADDR_15_8_MASK (0x7F << SSBI_MODE2_REG_ADDR_15_8_SHFT) +#define SSBI_MODE2_ADDR_WIDTH_SHFT 0x01 +#define SSBI_MODE2_ADDR_WIDTH_MASK (0x07 << SSBI_MODE2_ADDR_WIDTH_SHFT) +#define SSBI_MODE2_SSBI2_MODE 0x00000001 + +#define SSBI_MODE2_REG_ADDR_15_8(MD, AD) \ + (((MD) & 0x0F) | ((((AD) >> 8) << SSBI_MODE2_REG_ADDR_15_8_SHFT) & \ + SSBI_MODE2_REG_ADDR_15_8_MASK)) + +#define SSBI_MODE2_ADDR_WIDTH(N) \ + ((((N) - 8) << SSBI_MODE2_ADDR_WIDTH_SHFT) & SSBI_MODE2_ADDR_WIDTH_MASK) + +#define SSBI_TIMEOUT_US 100 + +#define SSBI_CMD_READ(AD) \ + (SSBI_CMD_RDWRN | (((AD) & 0xFF) << SSBI_CMD_REG_ADDR_SHFT)) + +#define SSBI_CMD_WRITE(AD, DT) \ + ((((AD) & 0xFF) << SSBI_CMD_REG_ADDR_SHFT) | \ + (((DT) & 0xFF) << SSBI_CMD_REG_DATA_SHFT)) + +/* SSBI PMIC Arbiter command registers */ +#define SSBI_PA_CMD 0x0000 +#define SSBI_PA_RD_STATUS 0x0004 + +/* SSBI_PA_CMD fields */ +#define SSBI_PA_CMD_RDWRN (0x01 << 24) +#define SSBI_PA_CMD_REG_ADDR_14_8_SHFT (0x10) +#define SSBI_PA_CMD_REG_ADDR_14_8_MASK (0x7F << SSBI_PA_CMD_REG_ADDR_14_8_SHFT) +#define SSBI_PA_CMD_REG_ADDR_7_0_SHFT (0x08) +#define SSBI_PA_CMD_REG_ADDR_7_0_MASK (0xFF << SSBI_PA_CMD_REG_ADDR_7_0_SHFT) +#define SSBI_PA_CMD_REG_DATA_SHFT (0x00) +#define SSBI_PA_CMD_REG_DATA_MASK (0xFF << SSBI_PA_CMD_REG_DATA_SHFT) + +#define SSBI_PA_CMD_REG_DATA(DT) \ + (((DT) << SSBI_PA_CMD_REG_DATA_SHFT) & SSBI_PA_CMD_REG_DATA_MASK) + +#define SSBI_PA_CMD_REG_ADDR(AD) \ + (((AD) << SSBI_PA_CMD_REG_ADDR_7_0_SHFT) & \ + (SSBI_PA_CMD_REG_ADDR_14_8_MASK|SSBI_PA_CMD_REG_ADDR_7_0_MASK)) + +/* SSBI_PA_RD_STATUS fields */ +#define SSBI_PA_RD_STATUS_TRANS_DONE (0x01 << 27) +#define SSBI_PA_RD_STATUS_TRANS_DENIED (0x01 << 26) +#define SSBI_PA_RD_STATUS_REG_DATA_SHFT (0x00) +#define SSBI_PA_RD_STATUS_REG_DATA_MASK (0xFF << SSBI_PA_CMD_REG_DATA_SHFT) +#define SSBI_PA_RD_STATUS_TRANS_COMPLETE \ + (SSBI_PA_RD_STATUS_TRANS_DONE|SSBI_PA_RD_STATUS_TRANS_DENIED) + +/* SSBI_FSM Read and Write commands for the FSM9xxx SSBI implementation */ +#define SSBI_FSM_CMD_REG_ADDR_SHFT (0x08) + +#define SSBI_FSM_CMD_READ(AD) \ + (SSBI_CMD_RDWRN | (((AD) & 0xFFFF) << SSBI_FSM_CMD_REG_ADDR_SHFT)) + +#define SSBI_FSM_CMD_WRITE(AD, DT) \ + ((((AD) & 0xFFFF) << SSBI_FSM_CMD_REG_ADDR_SHFT) | \ + (((DT) & 0xFF) << SSBI_CMD_REG_DATA_SHFT)) + +#define SSBI_MSM_NAME "i2c_ssbi" + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("2.0"); +MODULE_ALIAS("platform:i2c_ssbi"); + +struct i2c_ssbi_dev { + void __iomem *base; + struct device *dev; + struct i2c_adapter adapter; + unsigned long mem_phys_addr; + size_t mem_size; + bool use_rlock; + remote_spinlock_t rspin_lock; + enum msm_ssbi_controller_type controller_type; + int (*read)(struct i2c_ssbi_dev *, struct i2c_msg *); + int (*write)(struct i2c_ssbi_dev *, struct i2c_msg *); +}; + +static inline u32 ssbi_readl(struct i2c_ssbi_dev *ssbi, u32 reg) +{ + return readl_relaxed(ssbi->base + reg); +} + +static inline void ssbi_writel(struct i2c_ssbi_dev *ssbi, u32 reg, u32 val) +{ + writel_relaxed(val, ssbi->base + reg); +} + +static inline int +i2c_ssbi_poll_for_device_ready(struct i2c_ssbi_dev *ssbi) +{ + u32 timeout = SSBI_TIMEOUT_US; + + while (!(ssbi_readl(ssbi, SSBI2_STATUS) & SSBI_STATUS_READY)) { + if (--timeout == 0) { + dev_err(ssbi->dev, "%s: timeout, status %x\n", __func__, + ssbi_readl(ssbi, SSBI2_STATUS)); + return -ETIMEDOUT; + } + udelay(1); + } + + return 0; +} + +static inline int +i2c_ssbi_poll_for_read_completed(struct i2c_ssbi_dev *ssbi) +{ + u32 timeout = SSBI_TIMEOUT_US; + + while (!(ssbi_readl(ssbi, SSBI2_STATUS) & SSBI_STATUS_RD_READY)) { + if (--timeout == 0) { + dev_err(ssbi->dev, "%s: timeout, status %x\n", __func__, + ssbi_readl(ssbi, SSBI2_STATUS)); + return -ETIMEDOUT; + } + udelay(1); + } + + return 0; +} + +static inline int +i2c_ssbi_poll_for_transfer_completed(struct i2c_ssbi_dev *ssbi) +{ + u32 timeout = SSBI_TIMEOUT_US; + + while ((ssbi_readl(ssbi, SSBI2_STATUS) & SSBI_STATUS_MCHN_BUSY)) { + if (--timeout == 0) { + dev_err(ssbi->dev, "%s: timeout, status %x\n", __func__, + ssbi_readl(ssbi, SSBI2_STATUS)); + return -ETIMEDOUT; + } + udelay(1); + } + + return 0; +} + +static int +i2c_ssbi_read_bytes(struct i2c_ssbi_dev *ssbi, struct i2c_msg *msg) +{ + int ret = 0; + u8 *buf = msg->buf; + u16 len = msg->len; + u16 addr = msg->addr; + u32 read_cmd; + + if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { + u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); + ssbi_writel(ssbi, SSBI2_MODE2, + SSBI_MODE2_REG_ADDR_15_8(mode2, addr)); + } + + if (ssbi->controller_type == FSM_SBI_CTRL_SSBI) + read_cmd = SSBI_FSM_CMD_READ(addr); + else + read_cmd = SSBI_CMD_READ(addr); + + while (len) { + ret = i2c_ssbi_poll_for_device_ready(ssbi); + if (ret) + goto read_failed; + + ssbi_writel(ssbi, SSBI2_CMD, read_cmd); + + ret = i2c_ssbi_poll_for_read_completed(ssbi); + if (ret) + goto read_failed; + + *buf++ = ssbi_readl(ssbi, SSBI2_RD) & SSBI_RD_REG_DATA_MASK; + len--; + } + +read_failed: + return ret; +} + +static int +i2c_ssbi_write_bytes(struct i2c_ssbi_dev *ssbi, struct i2c_msg *msg) +{ + int ret = 0; + u8 *buf = msg->buf; + u16 len = msg->len; + u16 addr = msg->addr; + + if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { + u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); + ssbi_writel(ssbi, SSBI2_MODE2, + SSBI_MODE2_REG_ADDR_15_8(mode2, addr)); + } + + while (len) { + ret = i2c_ssbi_poll_for_device_ready(ssbi); + if (ret) + goto write_failed; + + if (ssbi->controller_type == FSM_SBI_CTRL_SSBI) + ssbi_writel(ssbi, SSBI2_CMD, + SSBI_FSM_CMD_WRITE(addr, *buf++)); + else + ssbi_writel(ssbi, SSBI2_CMD, + SSBI_CMD_WRITE(addr, *buf++)); + + ret = i2c_ssbi_poll_for_transfer_completed(ssbi); + if (ret) + goto write_failed; + + len--; + } + +write_failed: + return ret; +} + +static inline int +i2c_ssbi_pa_transfer(struct i2c_ssbi_dev *ssbi, u32 cmd, u8 *data) +{ + u32 rd_status; + u32 timeout = SSBI_TIMEOUT_US; + + ssbi_writel(ssbi, SSBI_PA_CMD, cmd); + rd_status = ssbi_readl(ssbi, SSBI_PA_RD_STATUS); + + while ((rd_status & (SSBI_PA_RD_STATUS_TRANS_COMPLETE)) == 0) { + + if (--timeout == 0) { + dev_err(ssbi->dev, "%s: timeout, status %x\n", + __func__, rd_status); + return -ETIMEDOUT; + } + udelay(1); + rd_status = ssbi_readl(ssbi, SSBI_PA_RD_STATUS); + } + + if (rd_status & SSBI_PA_RD_STATUS_TRANS_DENIED) { + dev_err(ssbi->dev, "%s: transaction denied, status %x\n", + __func__, rd_status); + return -EPERM; + } + + if (data) + *data = (rd_status & SSBI_PA_RD_STATUS_REG_DATA_MASK) >> + SSBI_PA_CMD_REG_DATA_SHFT; + return 0; +} + +static int +i2c_ssbi_pa_read_bytes(struct i2c_ssbi_dev *ssbi, struct i2c_msg *msg) +{ + int ret = 0; + u8 data; + u8 *buf = msg->buf; + u16 len = msg->len; + u32 read_cmd = (SSBI_PA_CMD_RDWRN | SSBI_PA_CMD_REG_ADDR(msg->addr)); + + while (len) { + + ret = i2c_ssbi_pa_transfer(ssbi, read_cmd, &data); + if (ret) + goto read_failed; + + *buf++ = data; + len--; + } + +read_failed: + return ret; +} + +static int +i2c_ssbi_pa_write_bytes(struct i2c_ssbi_dev *ssbi, struct i2c_msg *msg) +{ + int ret = 0; + u8 *buf = msg->buf; + u16 len = msg->len; + u32 addr = SSBI_PA_CMD_REG_ADDR(msg->addr); + + while (len) { + + u32 write_cmd = addr | (*buf++ & SSBI_PA_CMD_REG_DATA_MASK); + + ret = i2c_ssbi_pa_transfer(ssbi, write_cmd, NULL); + if (ret) + goto write_failed; + len--; + } + +write_failed: + return ret; +} + +static int +i2c_ssbi_transfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + int ret = 0; + int rem = num; + unsigned long flags = 0; + struct i2c_ssbi_dev *ssbi = i2c_get_adapdata(adap); + + if (ssbi->use_rlock) + remote_spin_lock_irqsave(&ssbi->rspin_lock, flags); + + while (rem) { + if (msgs->flags & I2C_M_RD) { + ret = ssbi->read(ssbi, msgs); + if (ret) + goto transfer_failed; + } else { + ret = ssbi->write(ssbi, msgs); + if (ret) + goto transfer_failed; + } + + msgs++; + rem--; + } + + if (ssbi->use_rlock) + remote_spin_unlock_irqrestore(&ssbi->rspin_lock, flags); + + return num; + +transfer_failed: + if (ssbi->use_rlock) + remote_spin_unlock_irqrestore(&ssbi->rspin_lock, flags); + return ret; +} + +static u32 i2c_ssbi_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm msm_i2c_algo = { + .master_xfer = i2c_ssbi_transfer, + .functionality = i2c_ssbi_i2c_func, +}; + +static int __init i2c_ssbi_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *ssbi_res; + struct i2c_ssbi_dev *ssbi; + const struct msm_i2c_ssbi_platform_data *pdata; + + pdata = pdev->dev.platform_data; + if (!pdata) { + ret = -ENXIO; + dev_err(&pdev->dev, "platform data not initialized\n"); + goto err_probe_exit; + } + + ssbi = kzalloc(sizeof(struct i2c_ssbi_dev), GFP_KERNEL); + if (!ssbi) { + ret = -ENOMEM; + dev_err(&pdev->dev, "allocation failed\n"); + goto err_probe_exit; + } + + ssbi_res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "ssbi_base"); + if (!ssbi_res) { + ret = -ENXIO; + dev_err(&pdev->dev, "get_resource_byname failed\n"); + goto err_probe_res; + } + + ssbi->mem_phys_addr = ssbi_res->start; + ssbi->mem_size = resource_size(ssbi_res); + if (!request_mem_region(ssbi->mem_phys_addr, ssbi->mem_size, + SSBI_MSM_NAME)) { + ret = -ENXIO; + dev_err(&pdev->dev, "request_mem_region failed\n"); + goto err_probe_reqmem; + } + + ssbi->base = ioremap(ssbi->mem_phys_addr, ssbi->mem_size); + if (!ssbi->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + goto err_probe_ioremap; + } + + ssbi->dev = &pdev->dev; + platform_set_drvdata(pdev, ssbi); + + ssbi->controller_type = pdata->controller_type; + if (ssbi->controller_type == MSM_SBI_CTRL_PMIC_ARBITER) { + ssbi->read = i2c_ssbi_pa_read_bytes; + ssbi->write = i2c_ssbi_pa_write_bytes; + } else { + ssbi->read = i2c_ssbi_read_bytes; + ssbi->write = i2c_ssbi_write_bytes; + } + + i2c_set_adapdata(&ssbi->adapter, ssbi); + ssbi->adapter.algo = &msm_i2c_algo; + strlcpy(ssbi->adapter.name, + "MSM SSBI adapter", + sizeof(ssbi->adapter.name)); + + if (pdata->rsl_id) { + ret = remote_spin_lock_init(&ssbi->rspin_lock, pdata->rsl_id); + if (ret) { + dev_err(&pdev->dev, "remote spinlock init failed\n"); + goto err_remote_spinlock_init_failed; + } + ssbi->use_rlock = 1; + } + + ssbi->adapter.nr = pdev->id; + ret = i2c_add_numbered_adapter(&ssbi->adapter); + if (ret) { + dev_err(&pdev->dev, "i2c_add_numbered_adapter failed\n"); + goto err_add_adapter_failed; + } + return 0; + +err_add_adapter_failed: +err_remote_spinlock_init_failed: + iounmap(ssbi->base); + platform_set_drvdata(pdev, NULL); +err_probe_ioremap: + release_mem_region(ssbi->mem_phys_addr, ssbi->mem_size); +err_probe_reqmem: +err_probe_res: + kfree(ssbi); +err_probe_exit: + return ret; +} + +static int __devexit i2c_ssbi_remove(struct platform_device *pdev) +{ + struct i2c_ssbi_dev *ssbi = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + i2c_del_adapter(&ssbi->adapter); + iounmap(ssbi->base); + release_mem_region(ssbi->mem_phys_addr, ssbi->mem_size); + kfree(ssbi); + return 0; +} + +static struct platform_driver i2c_ssbi_driver = { + .driver = { + .name = "i2c_ssbi", + .owner = THIS_MODULE, + }, + .remove = __exit_p(i2c_ssbi_remove), +}; + +static int __init i2c_ssbi_init(void) +{ + return platform_driver_probe(&i2c_ssbi_driver, i2c_ssbi_probe); +} +arch_initcall(i2c_ssbi_init); + +static void __exit i2c_ssbi_exit(void) +{ + platform_driver_unregister(&i2c_ssbi_driver); +} +module_exit(i2c_ssbi_exit); diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 07b6c815391..d73f49c59d7 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -61,6 +61,7 @@ static void evdev_pass_event(struct evdev_client *client, { /* Interrupts are disabled, just acquire the lock. */ spin_lock(&client->buffer_lock); + wake_lock_timeout(&client->wake_lock, 5 * HZ); wake_lock_timeout(&client->wake_lock, 5 * HZ); client->buffer[client->head++] = *event; diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig index 56eb471b557..aaee4481197 100644 --- a/drivers/input/joystick/Kconfig +++ b/drivers/input/joystick/Kconfig @@ -329,4 +329,15 @@ config JOYSTICK_MAPLE To compile this as a module choose M here: the module will be called maplecontrol. +config TOUCHDISC_VTD518_SHINETSU + tristate "ShinEtsu VTD518 TouchDisc" + depends on I2C + default n + help + Say Y here if you have the ShinEtsu VTD518 Touchdisc connected. It + provides the detection of absolute and relative motions and dpad + like buttons. + + To compile this as a module choose M here: the module will be called + tdisc_vtd518_shinetsu. endif diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile index 92dc0de9dfe..7009c38e79f 100644 --- a/drivers/input/joystick/Makefile +++ b/drivers/input/joystick/Makefile @@ -32,4 +32,4 @@ obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o - +obj-$(CONFIG_TOUCHDISC_VTD518_SHINETSU) += tdisc_vtd518_shinetsu.o \ No newline at end of file diff --git a/drivers/input/joystick/tdisc_vtd518_shinetsu.c b/drivers/input/joystick/tdisc_vtd518_shinetsu.c new file mode 100644 index 00000000000..efbe97474b8 --- /dev/null +++ b/drivers/input/joystick/tdisc_vtd518_shinetsu.c @@ -0,0 +1,528 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include +/* Early-suspend level */ +#define TDISC_SUSPEND_LEVEL 1 +#endif + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); +MODULE_DESCRIPTION("Shinetsu Touchdisc driver"); +MODULE_ALIAS("platform:tdisc-shinetsu"); + +#define TDSIC_BLK_READ_CMD 0x00 +#define TDISC_READ_DELAY msecs_to_jiffies(25) +#define X_MAX (32) +#define X_MIN (-32) +#define Y_MAX (32) +#define Y_MIN (-32) +#define PRESSURE_MAX (32) +#define PRESSURE_MIN (0) +#define TDISC_USER_ACTIVE_MASK 0x40 +#define TDISC_NORTH_SWITCH_MASK 0x20 +#define TDISC_SOUTH_SWITCH_MASK 0x10 +#define TDISC_EAST_SWITCH_MASK 0x08 +#define TDISC_WEST_SWITCH_MASK 0x04 +#define TDISC_CENTER_SWITCH 0x01 +#define TDISC_BUTTON_PRESS_MASK 0x3F + +#define DRIVER_NAME "tdisc-shinetsu" +#define DEVICE_NAME "vtd518" +#define TDISC_NAME "tdisc_shinetsu" +#define TDISC_INT "tdisc_interrupt" + +struct tdisc_data { + struct input_dev *tdisc_device; + struct i2c_client *clientp; + struct tdisc_platform_data *pdata; + struct delayed_work tdisc_work; +#if defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend tdisc_early_suspend; +#endif +}; + +static void process_tdisc_data(struct tdisc_data *dd, u8 *data) +{ + int i; + static bool button_press; + s8 x, y; + + /* Check if the user is actively navigating */ + if (!(data[7] & TDISC_USER_ACTIVE_MASK)) { + pr_debug(" TDISC ! No Data to report ! False positive \n"); + return; + } + + for (i = 0; i < 8 ; i++) + pr_debug(" Data[%d] = %x\n", i, data[i]); + + /* Check if there is a button press */ + if (dd->pdata->tdisc_report_keys) + if (data[7] & TDISC_BUTTON_PRESS_MASK || button_press == true) { + input_report_key(dd->tdisc_device, KEY_UP, + (data[7] & TDISC_NORTH_SWITCH_MASK)); + + input_report_key(dd->tdisc_device, KEY_DOWN, + (data[7] & TDISC_SOUTH_SWITCH_MASK)); + + input_report_key(dd->tdisc_device, KEY_RIGHT, + (data[7] & TDISC_EAST_SWITCH_MASK)); + + input_report_key(dd->tdisc_device, KEY_LEFT, + (data[7] & TDISC_WEST_SWITCH_MASK)); + + input_report_key(dd->tdisc_device, KEY_ENTER, + (data[7] & TDISC_CENTER_SWITCH)); + + if (data[7] & TDISC_BUTTON_PRESS_MASK) + button_press = true; + else + button_press = false; + } + + if (dd->pdata->tdisc_report_relative) { + /* Report relative motion values */ + x = (s8) data[0]; + y = (s8) data[1]; + + if (dd->pdata->tdisc_reverse_x) + x *= -1; + if (dd->pdata->tdisc_reverse_y) + y *= -1; + + input_report_rel(dd->tdisc_device, REL_X, x); + input_report_rel(dd->tdisc_device, REL_Y, y); + } + + if (dd->pdata->tdisc_report_absolute) { + input_report_abs(dd->tdisc_device, ABS_X, data[2]); + input_report_abs(dd->tdisc_device, ABS_Y, data[3]); + input_report_abs(dd->tdisc_device, ABS_PRESSURE, data[4]); + } + + if (dd->pdata->tdisc_report_wheel) + input_report_rel(dd->tdisc_device, REL_WHEEL, (s8) data[6]); + + input_sync(dd->tdisc_device); +} + +static void tdisc_work_f(struct work_struct *work) +{ + int rc; + u8 data[8]; + struct tdisc_data *dd = + container_of(work, struct tdisc_data, tdisc_work.work); + + /* + * Read the value of the interrupt pin. If low, perform + * an I2C read of 8 bytes to get the touch values and then + * reschedule the work after 25ms. If pin is high, exit + * and wait for next interrupt. + */ + rc = gpio_get_value_cansleep(dd->pdata->tdisc_gpio); + if (rc < 0) { + rc = pm_runtime_put_sync(&dd->clientp->dev); + if (rc < 0) + dev_dbg(&dd->clientp->dev, "%s: pm_runtime_put_sync" + " failed\n", __func__); + enable_irq(dd->clientp->irq); + return; + } + + pr_debug("%s: TDISC gpio_get_value = %d\n", __func__, rc); + if (rc == 0) { + /* We have data to read */ + rc = i2c_smbus_read_i2c_block_data(dd->clientp, + TDSIC_BLK_READ_CMD, 8, data); + if (rc < 0) { + pr_debug("%s:I2C read failed,trying again\n", __func__); + rc = i2c_smbus_read_i2c_block_data(dd->clientp, + TDSIC_BLK_READ_CMD, 8, data); + if (rc < 0) { + pr_err("%s:I2C read failed again, exiting\n", + __func__); + goto fail_i2c_read; + } + } + pr_debug("%s: TDISC: I2C read success\n", __func__); + process_tdisc_data(dd, data); + } else { + /* + * We have no data to read. + * Enable the IRQ to receive further interrupts. + */ + enable_irq(dd->clientp->irq); + + rc = pm_runtime_put_sync(&dd->clientp->dev); + if (rc < 0) + dev_dbg(&dd->clientp->dev, "%s: pm_runtime_put_sync" + " failed\n", __func__); + return; + } + +fail_i2c_read: + schedule_delayed_work(&dd->tdisc_work, TDISC_READ_DELAY); +} + +static irqreturn_t tdisc_interrupt(int irq, void *dev_id) +{ + /* + * The touch disc intially generates an interrupt on any + * touch. The interrupt line is pulled low and remains low + * untill there are touch operations being performed. In case + * there are no further touch operations, the line goes high. The + * same process repeats again the next time,when the disc is touched. + * + * We do the following operations once we receive an interrupt. + * 1. Disable the IRQ for any further interrutps. + * 2. Schedule work every 25ms if the GPIO is still low. + * 3. In the work queue do a I2C read to get the touch data. + * 4. If the GPIO is pulled high, enable the IRQ and cancel the work. + */ + struct tdisc_data *dd = dev_id; + int rc; + + rc = pm_runtime_get(&dd->clientp->dev); + if (rc < 0) + dev_dbg(&dd->clientp->dev, "%s: pm_runtime_get" + " failed\n", __func__); + pr_debug("%s: TDISC IRQ ! :-)\n", __func__); + + /* Schedule the work immediately */ + disable_irq_nosync(dd->clientp->irq); + schedule_delayed_work(&dd->tdisc_work, 0); + return IRQ_HANDLED; +} + +static int tdisc_open(struct input_dev *dev) +{ + int rc; + struct tdisc_data *dd = input_get_drvdata(dev); + + if (!dd->clientp) { + /* Check if a valid i2c client is present */ + pr_err("%s: no i2c adapter present \n", __func__); + return -ENODEV; + } + + /* Enable the device */ + if (dd->pdata->tdisc_enable != NULL) { + rc = dd->pdata->tdisc_enable(); + if (rc) + goto fail_open; + } + rc = request_any_context_irq(dd->clientp->irq, tdisc_interrupt, + IRQF_TRIGGER_FALLING, TDISC_INT, dd); + if (rc < 0) { + pr_err("%s: request IRQ failed\n", __func__); + goto fail_irq_open; + } + + return 0; + +fail_irq_open: + if (dd->pdata->tdisc_disable != NULL) + dd->pdata->tdisc_disable(); +fail_open: + return rc; +} + +static void tdisc_close(struct input_dev *dev) +{ + struct tdisc_data *dd = input_get_drvdata(dev); + + free_irq(dd->clientp->irq, dd); + cancel_delayed_work_sync(&dd->tdisc_work); + if (dd->pdata->tdisc_disable != NULL) + dd->pdata->tdisc_disable(); +} + +static int __devexit tdisc_remove(struct i2c_client *client) +{ + struct tdisc_data *dd; + + pm_runtime_disable(&client->dev); + dd = i2c_get_clientdata(client); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&dd->tdisc_early_suspend); +#endif + input_unregister_device(dd->tdisc_device); + if (dd->pdata->tdisc_release != NULL) + dd->pdata->tdisc_release(); + i2c_set_clientdata(client, NULL); + kfree(dd); + + return 0; +} + +#ifdef CONFIG_PM +static int tdisc_suspend(struct device *dev) +{ + int rc; + struct tdisc_data *dd; + + dd = dev_get_drvdata(dev); + if (device_may_wakeup(&dd->clientp->dev)) + enable_irq_wake(dd->clientp->irq); + else { + disable_irq(dd->clientp->irq); + + if (cancel_delayed_work_sync(&dd->tdisc_work)) + enable_irq(dd->clientp->irq); + + if (dd->pdata->tdisc_disable) { + rc = dd->pdata->tdisc_disable(); + if (rc) { + pr_err("%s: Suspend failed\n", __func__); + return rc; + } + } + } + + return 0; +} + +static int tdisc_resume(struct device *dev) +{ + int rc; + struct tdisc_data *dd; + + dd = dev_get_drvdata(dev); + if (device_may_wakeup(&dd->clientp->dev)) + disable_irq_wake(dd->clientp->irq); + else { + if (dd->pdata->tdisc_enable) { + rc = dd->pdata->tdisc_enable(); + if (rc) { + pr_err("%s: Resume failed\n", __func__); + return rc; + } + } + enable_irq(dd->clientp->irq); + } + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void tdisc_early_suspend(struct early_suspend *h) +{ + struct tdisc_data *dd = container_of(h, struct tdisc_data, + tdisc_early_suspend); + + tdisc_suspend(&dd->clientp->dev); +} + +static void tdisc_late_resume(struct early_suspend *h) +{ + struct tdisc_data *dd = container_of(h, struct tdisc_data, + tdisc_early_suspend); + + tdisc_resume(&dd->clientp->dev); +} +#endif + +static struct dev_pm_ops tdisc_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = tdisc_suspend, + .resume = tdisc_resume, +#endif +}; +#endif + +static const struct i2c_device_id tdisc_id[] = { + { DEVICE_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tdisc_id); + +static int __devinit tdisc_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = -1; + int x_max, x_min, y_max, y_min, pressure_min, pressure_max; + struct tdisc_platform_data *pd; + struct tdisc_data *dd; + + /* Check if the I2C adapter supports the BLOCK READ functionality */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) + return -ENODEV; + + /* Enable runtime PM ops, start in ACTIVE mode */ + rc = pm_runtime_set_active(&client->dev); + if (rc < 0) + dev_dbg(&client->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&client->dev); + + dd = kzalloc(sizeof *dd, GFP_KERNEL); + if (!dd) { + rc = -ENOMEM; + goto probe_exit; + } + + i2c_set_clientdata(client, dd); + dd->clientp = client; + pd = client->dev.platform_data; + if (!pd) { + pr_err("%s: platform data not set \n", __func__); + rc = -EFAULT; + goto probe_free_exit; + } + + dd->pdata = pd; + + dd->tdisc_device = input_allocate_device(); + if (!dd->tdisc_device) { + rc = -ENOMEM; + goto probe_free_exit; + } + + input_set_drvdata(dd->tdisc_device, dd); + dd->tdisc_device->open = tdisc_open; + dd->tdisc_device->close = tdisc_close; + dd->tdisc_device->name = TDISC_NAME; + dd->tdisc_device->id.bustype = BUS_I2C; + dd->tdisc_device->id.product = 1; + dd->tdisc_device->id.version = 1; + + if (pd->tdisc_abs) { + x_max = pd->tdisc_abs->x_max; + x_min = pd->tdisc_abs->x_min; + y_max = pd->tdisc_abs->y_max; + y_min = pd->tdisc_abs->y_min; + pressure_max = pd->tdisc_abs->pressure_max; + pressure_min = pd->tdisc_abs->pressure_min; + } else { + x_max = X_MAX; + x_min = X_MIN; + y_max = Y_MAX; + y_min = Y_MIN; + pressure_max = PRESSURE_MAX; + pressure_min = PRESSURE_MIN; + } + + /* Device capablities for relative motion */ + input_set_capability(dd->tdisc_device, EV_REL, REL_X); + input_set_capability(dd->tdisc_device, EV_REL, REL_Y); + input_set_capability(dd->tdisc_device, EV_KEY, BTN_MOUSE); + + /* Device capablities for absolute motion */ + input_set_capability(dd->tdisc_device, EV_ABS, ABS_X); + input_set_capability(dd->tdisc_device, EV_ABS, ABS_Y); + input_set_capability(dd->tdisc_device, EV_ABS, ABS_PRESSURE); + + input_set_abs_params(dd->tdisc_device, ABS_X, x_min, x_max, 0, 0); + input_set_abs_params(dd->tdisc_device, ABS_Y, y_min, y_max, 0, 0); + input_set_abs_params(dd->tdisc_device, ABS_PRESSURE, pressure_min, + pressure_max, 0, 0); + + /* Device capabilities for scroll and buttons */ + input_set_capability(dd->tdisc_device, EV_REL, REL_WHEEL); + input_set_capability(dd->tdisc_device, EV_KEY, KEY_LEFT); + input_set_capability(dd->tdisc_device, EV_KEY, KEY_RIGHT); + input_set_capability(dd->tdisc_device, EV_KEY, KEY_UP); + input_set_capability(dd->tdisc_device, EV_KEY, KEY_DOWN); + input_set_capability(dd->tdisc_device, EV_KEY, KEY_ENTER); + + /* Setup the device for operation */ + if (dd->pdata->tdisc_setup != NULL) { + rc = dd->pdata->tdisc_setup(); + if (rc) { + pr_err("%s: Setup failed \n", __func__); + goto probe_unreg_free_exit; + } + } + + /* Setup wakeup capability */ + device_init_wakeup(&dd->clientp->dev, dd->pdata->tdisc_wakeup); + + INIT_DELAYED_WORK(&dd->tdisc_work, tdisc_work_f); + + rc = input_register_device(dd->tdisc_device); + if (rc) { + pr_err("%s: input register device failed \n", __func__); + rc = -EINVAL; + goto probe_register_fail; + } + + pm_runtime_set_suspended(&client->dev); + +#ifdef CONFIG_HAS_EARLYSUSPEND + dd->tdisc_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + TDISC_SUSPEND_LEVEL; + dd->tdisc_early_suspend.suspend = tdisc_early_suspend; + dd->tdisc_early_suspend.resume = tdisc_late_resume; + register_early_suspend(&dd->tdisc_early_suspend); +#endif + return 0; + +probe_register_fail: + if (dd->pdata->tdisc_release != NULL) + dd->pdata->tdisc_release(); +probe_unreg_free_exit: + input_free_device(dd->tdisc_device); +probe_free_exit: + i2c_set_clientdata(client, NULL); + kfree(dd); +probe_exit: + pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(&client->dev); + return rc; +} + +static struct i2c_driver tdisc_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &tdisc_pm_ops, +#endif + }, + .probe = tdisc_probe, + .remove = __devexit_p(tdisc_remove), + .id_table = tdisc_id, +}; + +static int __init tdisc_init(void) +{ + int rc; + + rc = i2c_add_driver(&tdisc_driver); + if (rc) + pr_err("%s: i2c add driver failed \n", __func__); + return rc; +} + +static void __exit tdisc_exit(void) +{ + i2c_del_driver(&tdisc_driver); +} + +module_init(tdisc_init); +module_exit(tdisc_exit); diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index b4dee9d5a05..0822866d2e9 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -394,6 +394,24 @@ config KEYBOARD_OPENCORES To compile this driver as a module, choose M here; the module will be called opencores-kbd. +config KEYBOARD_PM8058 + bool "Qualcomm PM8058 Matrix Keypad support" + depends on PM8058 + help + Say Y here to enable the driver for the keypad matrix interface + on the Qualcomm PM8058 power management I/C device. + +config KEYBOARD_PMIC8XXX + tristate "Qualcomm PMIC8XXX keypad support" + depends on MFD_PM8XXX + help + Say Y here if you want to enable the driver for the PMIC8XXX + keypad provided as a reference design from Qualcomm. This is intended + to support upto 18x8 matrix based keypad design. + + To compile this driver as a module, choose M here: the module will + be called pmic8xxx-keypad. + config KEYBOARD_PXA27x tristate "PXA27x/PXA3xx keypad support" depends on PXA27x || PXA3xx || ARCH_MMP @@ -553,6 +571,30 @@ config KEYBOARD_XTKBD To compile this driver as a module, choose M here: the module will be called xtkbd. +config KEYBOARD_QCIKBD + tristate "Quanta Computer Inc. keyboard" + depends on I2C + default n + help + Say Y here if you want to use the Quanta keyboard driver for ST 1.5 + platform. + +config KEYBOARD_QCIKBD_REPEAT + bool "Enable Quanta Computer Inc. keyboard key repeat feature" + depends on KEYBOARD_QCIKBD + default n + help + Say Y here if you want to enable Quanta keyboard driver's key repeat + feature. + +config KEYBOARD_QCIKBD_LID + bool "Enable lid event for Quanta Computer Inc. keyboard" + depends on KEYBOARD_QCIKBD + default n + help + Say Y here if you want to register lid event in Quanta keyboard + driver. + config KEYBOARD_W90P910 tristate "W90P910 Matrix Keypad support" depends on ARCH_W90X900 @@ -563,4 +605,28 @@ config KEYBOARD_W90P910 To compile this driver as a module, choose M here: the module will be called w90p910_keypad. +config KEYBOARD_PMIC8058 + tristate "Qualcomm PMIC8058 keypad" + depends on PMIC8058 + default y + help + Say Y here if you want to enable the driver for the PMIC8058 + keypad provided as a reference design from Qualcomm. This is intended + to support upto 18x8 matrix based keypad design. + + To compile this driver as a module, choose M here: the module will + be called pmic8058-keypad. endif +config KEYBOARD_PMIC8058 + tristate "Qualcomm PMIC8058 keypad" + depends on PMIC8058 + default y + help + Say Y here if you want to enable the driver for the PMIC8058 + keypad provided as a reference design from Qualcomm. This is intended + to support upto 18x8 matrix based keypad design. + + To compile this driver as a module, choose M here: the module will + be called pmic8058-keypad. + + diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index ddde0fd476f..cf4c0582ceb 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_KEYBOARD_NOMADIK) += nomadik-ske-keypad.o obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_OMAP4) += omap4-keypad.o obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o +obj-$(CONFIG_KEYBOARD_PM8058) += pm8058-keypad.o obj-$(CONFIG_KEYBOARD_PMIC8XXX) += pmic8xxx-keypad.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o @@ -50,4 +51,6 @@ obj-$(CONFIG_KEYBOARD_TEGRA) += tegra-kbc.o obj-$(CONFIG_KEYBOARD_TNETV107X) += tnetv107x-keypad.o obj-$(CONFIG_KEYBOARD_TWL4030) += twl4030_keypad.o obj-$(CONFIG_KEYBOARD_XTKBD) += xtkbd.o +obj-$(CONFIG_KEYBOARD_QCIKBD) += qci_kbd.o obj-$(CONFIG_KEYBOARD_W90P910) += w90p910_keypad.o +obj-$(CONFIG_KEYBOARD_PMIC8058) += pmic8058-keypad.o diff --git a/drivers/input/keyboard/matrix_keypad.c b/drivers/input/keyboard/matrix_keypad.c index b02e4268e18..c7aa2ced37b 100644 --- a/drivers/input/keyboard/matrix_keypad.c +++ b/drivers/input/keyboard/matrix_keypad.c @@ -334,20 +334,21 @@ static int __devinit init_matrix_gpio(struct platform_device *pdev, matrix_keypad_interrupt, pdata->clustered_irq_flags, "matrix-keypad", keypad); - if (err) { + if (err < 0) { dev_err(&pdev->dev, "Unable to acquire clustered interrupt\n"); goto err_free_rows; } } else { for (i = 0; i < pdata->num_row_gpios; i++) { - err = request_irq(gpio_to_irq(pdata->row_gpios[i]), + err = request_any_context_irq( + gpio_to_irq(pdata->row_gpios[i]), matrix_keypad_interrupt, IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "matrix-keypad", keypad); - if (err) { + if (err < 0) { dev_err(&pdev->dev, "Unable to acquire interrupt " "for GPIO line %i\n", diff --git a/drivers/input/keyboard/pmic8058-keypad.c b/drivers/input/keyboard/pmic8058-keypad.c new file mode 100644 index 00000000000..9c7588ef40a --- /dev/null +++ b/drivers/input/keyboard/pmic8058-keypad.c @@ -0,0 +1,948 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define PM8058_MAX_ROWS 18 +#define PM8058_MAX_COLS 8 +#define PM8058_ROW_SHIFT 3 +#define PM8058_MATRIX_MAX_SIZE (PM8058_MAX_ROWS * PM8058_MAX_COLS) + +#define PM8058_MIN_ROWS 5 +#define PM8058_MIN_COLS 5 + +#define MAX_SCAN_DELAY 128 +#define MIN_SCAN_DELAY 1 + +/* in nanoseconds */ +#define MAX_ROW_HOLD_DELAY 122000 +#define MIN_ROW_HOLD_DELAY 30500 + +#define MAX_DEBOUNCE_B0_TIME 20 +#define MIN_DEBOUNCE_B0_TIME 5 + +#define MAX_DEBOUNCE_A0_TIME 8 +#define MIN_DEBOUNCE_A0_TIME 1 + +#define KEYP_CTRL 0x148 + +#define KEYP_CTRL_EVNTS BIT(0) +#define KEYP_CTRL_EVNTS_MASK 0x3 + +#define KEYP_CTRL_SCAN_COLS_SHIFT 5 +#define KEYP_CTRL_SCAN_COLS_MIN 5 +#define KEYP_CTRL_SCAN_COLS_BITS 0x3 + +#define KEYP_CTRL_SCAN_ROWS_SHIFT 2 +#define KEYP_CTRL_SCAN_ROWS_MIN 5 +#define KEYP_CTRL_SCAN_ROWS_BITS 0x7 + +#define KEYP_CTRL_KEYP_EN BIT(7) + +#define KEYP_SCAN 0x149 + +#define KEYP_SCAN_READ_STATE BIT(0) +#define KEYP_SCAN_DBOUNCE_SHIFT 1 +#define KEYP_SCAN_PAUSE_SHIFT 3 +#define KEYP_SCAN_ROW_HOLD_SHIFT 6 + +#define KEYP_TEST 0x14A + +#define KEYP_TEST_CLEAR_RECENT_SCAN BIT(6) +#define KEYP_TEST_CLEAR_OLD_SCAN BIT(5) +#define KEYP_TEST_READ_RESET BIT(4) +#define KEYP_TEST_DTEST_EN BIT(3) +#define KEYP_TEST_ABORT_READ BIT(0) + +#define KEYP_TEST_DBG_SELECT_SHIFT 1 + +/* bits of these register represent + * '0' for key press + * '1' for key release + */ +#define KEYP_RECENT_DATA 0x14B +#define KEYP_OLD_DATA 0x14C + +#define KEYP_CLOCK_FREQ 32768 + +/* Internal flags */ +#define KEYF_FIX_LAST_ROW 0x01 + + +/* ---------------------------------------------------------------------*/ +struct pmic8058_kp { + const struct pmic8058_keypad_data *pdata; + struct input_dev *input; + int key_sense_irq; + int key_stuck_irq; + + unsigned short *keycodes; + + struct device *dev; + u16 keystate[PM8058_MAX_ROWS]; + u16 stuckstate[PM8058_MAX_ROWS]; + + u32 flags; + struct pm8058_chip *pm_chip; + + /* protect read/write */ + struct mutex mutex; + bool user_disabled; + u32 disable_depth; + + u8 ctrl_reg; +}; + +static int pmic8058_kp_write_u8(struct pmic8058_kp *kp, + u8 data, u16 reg) +{ + int rc; + + rc = pm8058_write(kp->pm_chip, reg, &data, 1); + if (rc < 0) + dev_warn(kp->dev, "Error writing pmic8058: %X - ret %X\n", + reg, rc); + return rc; +} + +static int pmic8058_kp_read(struct pmic8058_kp *kp, + u8 *data, u16 reg, unsigned num_bytes) +{ + int rc; + + rc = pm8058_read(kp->pm_chip, reg, data, num_bytes); + if (rc < 0) + dev_warn(kp->dev, "Error reading pmic8058: %X - ret %X\n", + reg, rc); + + return rc; +} + +static int pmic8058_kp_read_u8(struct pmic8058_kp *kp, + u8 *data, u16 reg) +{ + int rc; + + rc = pmic8058_kp_read(kp, data, reg, 1); + if (rc < 0) + dev_warn(kp->dev, "Error reading pmic8058: %X - ret %X\n", + reg, rc); + return rc; +} + +static u8 pmic8058_col_state(struct pmic8058_kp *kp, u8 col) +{ + /* all keys pressed on that particular row? */ + if (col == 0x00) + return 1 << kp->pdata->num_cols; + else + return col & ((1 << kp->pdata->num_cols) - 1); +} +/* REVISIT: just for debugging, will be removed in final working version */ +static void __dump_kp_regs(struct pmic8058_kp *kp, char *msg) +{ + u8 temp; + + dev_dbg(kp->dev, "%s\n", msg); + + pmic8058_kp_read_u8(kp, &temp, KEYP_CTRL); + dev_dbg(kp->dev, "KEYP_CTRL - %X\n", temp); + pmic8058_kp_read_u8(kp, &temp, KEYP_SCAN); + dev_dbg(kp->dev, "KEYP_SCAN - %X\n", temp); + pmic8058_kp_read_u8(kp, &temp, KEYP_TEST); + dev_dbg(kp->dev, "KEYP_TEST - %X\n", temp); +} + +/* H/W constraint: + * One should read recent/old data registers equal to the + * number of columns programmed in the keyp_control register, + * otherwise h/w state machine may get stuck. In order to avoid this + * situation one should check readstate bit in keypad scan + * register to be '0' at the end of data read, to make sure + * the keypad state machine is not in READ state. + */ +static int pmic8058_chk_read_state(struct pmic8058_kp *kp, u16 data_reg) +{ + u8 temp, scan_val; + int retries = 10, rc; + + do { + rc = pmic8058_kp_read_u8(kp, &scan_val, KEYP_SCAN); + if (scan_val & 0x1) + rc = pmic8058_kp_read_u8(kp, &temp, data_reg); + } while ((scan_val & 0x1) && (--retries > 0)); + + if (retries == 0) + dev_dbg(kp->dev, "Unable to clear read state bit\n"); + + return 0; +} +/* + * Synchronous read protocol for RevB0 onwards: + * + * 1. Write '1' to ReadState bit in KEYP_SCAN register + * 2. Wait 2*32KHz clocks, so that HW can successfully enter read mode + * synchronously + * 3. Read rows in old array first if events are more than one + * 4. Read rows in recent array + * 5. Wait 4*32KHz clocks + * 6. Write '0' to ReadState bit of KEYP_SCAN register so that hw can + * synchronously exit read mode. + */ +static int pmic8058_chk_sync_read(struct pmic8058_kp *kp) +{ + int rc; + u8 scan_val; + + rc = pmic8058_kp_read_u8(kp, &scan_val, KEYP_SCAN); + scan_val |= 0x1; + rc = pmic8058_kp_write_u8(kp, scan_val, KEYP_SCAN); + + /* 2 * 32KHz clocks */ + udelay((2 * USEC_PER_SEC / KEYP_CLOCK_FREQ) + 1); + + return rc; +} + +static int pmic8058_kp_read_data(struct pmic8058_kp *kp, u16 *state, + u16 data_reg, int read_rows) +{ + int rc, row; + u8 new_data[PM8058_MAX_ROWS]; + + rc = pmic8058_kp_read(kp, new_data, data_reg, read_rows); + + if (!rc) { + if (pm8058_rev(kp->pm_chip) == PM_8058_REV_1p0) + pmic8058_chk_read_state(kp, data_reg); + for (row = 0; row < kp->pdata->num_rows; row++) { + dev_dbg(kp->dev, "new_data[%d] = %d\n", row, + new_data[row]); + state[row] = pmic8058_col_state(kp, new_data[row]); + } + } + + return rc; +} + +static int pmic8058_kp_read_matrix(struct pmic8058_kp *kp, u16 *new_state, + u16 *old_state) +{ + int rc, read_rows; + u8 scan_val; + static u8 rows[] = { + 5, 6, 7, 8, 10, 10, 12, 12, 15, 15, 15, 18, 18, 18 + }; + + if (kp->flags & KEYF_FIX_LAST_ROW && + (kp->pdata->num_rows != PM8058_MAX_ROWS)) + read_rows = rows[kp->pdata->num_rows - KEYP_CTRL_SCAN_ROWS_MIN + + 1]; + else + read_rows = kp->pdata->num_rows; + + if (pm8058_rev(kp->pm_chip) > PM_8058_REV_1p0) + pmic8058_chk_sync_read(kp); + + if (old_state) + rc = pmic8058_kp_read_data(kp, old_state, KEYP_OLD_DATA, + read_rows); + + rc = pmic8058_kp_read_data(kp, new_state, KEYP_RECENT_DATA, + read_rows); + + if (pm8058_rev(kp->pm_chip) > PM_8058_REV_1p0) { + /* 4 * 32KHz clocks */ + udelay((4 * USEC_PER_SEC / KEYP_CLOCK_FREQ) + 1); + + rc = pmic8058_kp_read(kp, &scan_val, KEYP_SCAN, 1); + scan_val &= 0xFE; + rc = pmic8058_kp_write_u8(kp, scan_val, KEYP_SCAN); + } + + return rc; +} + +static int __pmic8058_kp_scan_matrix(struct pmic8058_kp *kp, u16 *new_state, + u16 *old_state) +{ + int row, col, code; + + for (row = 0; row < kp->pdata->num_rows; row++) { + int bits_changed = new_state[row] ^ old_state[row]; + + if (!bits_changed) + continue; + + for (col = 0; col < kp->pdata->num_cols; col++) { + if (!(bits_changed & (1 << col))) + continue; + + dev_dbg(kp->dev, "key [%d:%d] %s\n", row, col, + !(new_state[row] & (1 << col)) ? + "pressed" : "released"); + + code = MATRIX_SCAN_CODE(row, col, PM8058_ROW_SHIFT); + input_event(kp->input, EV_MSC, MSC_SCAN, code); + input_report_key(kp->input, + kp->keycodes[code], + !(new_state[row] & (1 << col))); + + input_sync(kp->input); + } + } + + return 0; +} + +static int pmic8058_detect_ghost_keys(struct pmic8058_kp *kp, u16 *new_state) +{ + int row, found_first = -1; + u16 check, row_state; + + check = 0; + for (row = 0; row < kp->pdata->num_rows; row++) { + row_state = (~new_state[row]) & + ((1 << kp->pdata->num_cols) - 1); + + if (hweight16(row_state) > 1) { + if (found_first == -1) + found_first = row; + if (check & row_state) { + dev_dbg(kp->dev, "detected ghost key on row[%d]" + "row[%d]\n", found_first, row); + return 1; + } + } + check |= row_state; + } + return 0; +} + +static int pmic8058_kp_scan_matrix(struct pmic8058_kp *kp, unsigned int events) +{ + u16 new_state[PM8058_MAX_ROWS]; + u16 old_state[PM8058_MAX_ROWS]; + int rc; + + switch (events) { + case 0x1: + rc = pmic8058_kp_read_matrix(kp, new_state, NULL); + if (pmic8058_detect_ghost_keys(kp, new_state)) + return -EINVAL; + __pmic8058_kp_scan_matrix(kp, new_state, kp->keystate); + memcpy(kp->keystate, new_state, sizeof(new_state)); + break; + case 0x3: /* two events - eventcounter is gray-coded */ + rc = pmic8058_kp_read_matrix(kp, new_state, old_state); + __pmic8058_kp_scan_matrix(kp, old_state, kp->keystate); + __pmic8058_kp_scan_matrix(kp, new_state, old_state); + memcpy(kp->keystate, new_state, sizeof(new_state)); + break; + case 0x2: + dev_dbg(kp->dev, "Some key events are missed\n"); + rc = pmic8058_kp_read_matrix(kp, new_state, old_state); + __pmic8058_kp_scan_matrix(kp, old_state, kp->keystate); + __pmic8058_kp_scan_matrix(kp, new_state, old_state); + memcpy(kp->keystate, new_state, sizeof(new_state)); + break; + default: + rc = -1; + } + return rc; +} + +static inline int pmic8058_kp_disabled(struct pmic8058_kp *kp) +{ + return kp->disable_depth != 0; +} + +static void pmic8058_kp_enable(struct pmic8058_kp *kp) +{ + if (!pmic8058_kp_disabled(kp)) + return; + + if (--kp->disable_depth == 0) { + + kp->ctrl_reg |= KEYP_CTRL_KEYP_EN; + pmic8058_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL); + + enable_irq(kp->key_sense_irq); + enable_irq(kp->key_stuck_irq); + } +} + +static void pmic8058_kp_disable(struct pmic8058_kp *kp) +{ + if (kp->disable_depth++ == 0) { + disable_irq(kp->key_sense_irq); + disable_irq(kp->key_stuck_irq); + + kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN; + pmic8058_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL); + } +} + +static ssize_t pmic8058_kp_disable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pmic8058_kp *kp = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", pmic8058_kp_disabled(kp)); +} + +static ssize_t pmic8058_kp_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pmic8058_kp *kp = dev_get_drvdata(dev); + long i = 0; + int rc; + + rc = strict_strtoul(buf, 10, &i); + if (rc) + return -EINVAL; + + i = !!i; + + mutex_lock(&kp->mutex); + if (i == kp->user_disabled) { + mutex_unlock(&kp->mutex); + return count; + } + + kp->user_disabled = i; + + if (i) + pmic8058_kp_disable(kp); + else + pmic8058_kp_enable(kp); + mutex_unlock(&kp->mutex); + + return count; +} + +static DEVICE_ATTR(disable_kp, 0664, pmic8058_kp_disable_show, + pmic8058_kp_disable_store); + + +/* + * NOTE: We are reading recent and old data registers blindly + * whenever key-stuck interrupt happens, because events counter doesn't + * get updated when this interrupt happens due to key stuck doesn't get + * considered as key state change. + * + * We are not using old data register contents after they are being read + * because it might report the key which was pressed before the key being stuck + * as stuck key because it's pressed status is stored in the old data + * register. + */ +static irqreturn_t pmic8058_kp_stuck_irq(int irq, void *data) +{ + u16 new_state[PM8058_MAX_ROWS]; + u16 old_state[PM8058_MAX_ROWS]; + int rc; + struct pmic8058_kp *kp = data; + + rc = pmic8058_kp_read_matrix(kp, new_state, old_state); + __pmic8058_kp_scan_matrix(kp, new_state, kp->stuckstate); + + return IRQ_HANDLED; +} + +/* + * NOTE: Any row multiple interrupt issue - PMIC4 Rev A0 + * + * If the S/W responds to the key-event interrupt too early and reads the + * recent data, the keypad FSM will mistakenly go to the IDLE state, instead + * of the scan pause state as it is supposed too. Since the key is still + * pressed, the keypad scanner will go through the debounce, scan, and generate + * another key event interrupt. The workaround for this issue is to add delay + * of 1ms between servicing the key event interrupt and reading the recent data. + */ +static irqreturn_t pmic8058_kp_irq(int irq, void *data) +{ + struct pmic8058_kp *kp = data; + u8 ctrl_val, events; + int rc; + + if (pm8058_rev(kp->pm_chip) == PM_8058_REV_1p0) + mdelay(1); + + dev_dbg(kp->dev, "key sense irq\n"); + __dump_kp_regs(kp, "pmic8058_kp_irq"); + + rc = pmic8058_kp_read(kp, &ctrl_val, KEYP_CTRL, 1); + events = ctrl_val & KEYP_CTRL_EVNTS_MASK; + + rc = pmic8058_kp_scan_matrix(kp, events); + + return IRQ_HANDLED; +} +/* + * NOTE: Last row multi-interrupt issue + * + * In PMIC Rev A0, if any key in the last row of the keypad matrix + * is pressed and held, the H/W keeps on generating interrupts. + * Software work-arounds it by programming the keypad controller next level + * up rows (for 8x12 matrix it is 15 rows) so the keypad controller + * thinks of more-rows than the actual ones, so the actual last-row + * in the matrix won't generate multiple interrupts. + */ +static int pmic8058_kpd_init(struct pmic8058_kp *kp) +{ + int bits, rc, cycles; + u8 scan_val = 0, ctrl_val = 0; + static u8 row_bits[] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7, + }; + + /* Find column bits */ + if (kp->pdata->num_cols < KEYP_CTRL_SCAN_COLS_MIN) + bits = 0; + else + bits = kp->pdata->num_cols - KEYP_CTRL_SCAN_COLS_MIN; + ctrl_val = (bits & KEYP_CTRL_SCAN_COLS_BITS) << + KEYP_CTRL_SCAN_COLS_SHIFT; + + /* Find row bits */ + if (kp->pdata->num_rows < KEYP_CTRL_SCAN_ROWS_MIN) + bits = 0; + else if (kp->pdata->num_rows > PM8058_MAX_ROWS) + bits = KEYP_CTRL_SCAN_ROWS_BITS; + else + bits = row_bits[kp->pdata->num_rows - KEYP_CTRL_SCAN_ROWS_MIN]; + + /* Use max rows to fix last row problem if actual rows are less */ + if (kp->flags & KEYF_FIX_LAST_ROW && + (kp->pdata->num_rows != PM8058_MAX_ROWS)) + bits = row_bits[kp->pdata->num_rows - KEYP_CTRL_SCAN_ROWS_MIN + + 1]; + + ctrl_val |= (bits << KEYP_CTRL_SCAN_ROWS_SHIFT); + + rc = pmic8058_kp_write_u8(kp, ctrl_val, KEYP_CTRL); + + if (pm8058_rev(kp->pm_chip) == PM_8058_REV_1p0) + bits = fls(kp->pdata->debounce_ms[0]) - 1; + else + bits = (kp->pdata->debounce_ms[1] / 5) - 1; + + scan_val |= (bits << KEYP_SCAN_DBOUNCE_SHIFT); + + bits = fls(kp->pdata->scan_delay_ms) - 1; + scan_val |= (bits << KEYP_SCAN_PAUSE_SHIFT); + + /* Row hold time is a multiple of 32KHz cycles. */ + cycles = (kp->pdata->row_hold_ns * KEYP_CLOCK_FREQ) / NSEC_PER_SEC; + + scan_val |= (cycles << KEYP_SCAN_ROW_HOLD_SHIFT); + + rc = pmic8058_kp_write_u8(kp, scan_val, KEYP_SCAN); + + return rc; +} + +static int pm8058_kp_config_drv(int gpio_start, int num_gpios) +{ + int rc; + struct pm8058_gpio kypd_drv = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_OPEN_DRAIN, + .output_value = 0, + .pull = PM_GPIO_PULL_NO, + .vin_sel = 2, + .out_strength = PM_GPIO_STRENGTH_LOW, + .function = PM_GPIO_FUNC_1, + .inv_int_pol = 1, + }; + + if (gpio_start < 0 || num_gpios < 0 || num_gpios > PM8058_GPIOS) + return -EINVAL; + + while (num_gpios--) { + rc = pm8058_gpio_config(gpio_start++, &kypd_drv); + if (rc) { + pr_err("%s: FAIL pm8058_gpio_config(): rc=%d.\n", + __func__, rc); + return rc; + } + } + + return 0; +} + +static int pm8058_kp_config_sns(int gpio_start, int num_gpios) +{ + int rc; + struct pm8058_gpio kypd_sns = { + .direction = PM_GPIO_DIR_IN, + .pull = PM_GPIO_PULL_UP_31P5, + .vin_sel = 2, + .out_strength = PM_GPIO_STRENGTH_NO, + .function = PM_GPIO_FUNC_NORMAL, + .inv_int_pol = 1, + }; + + if (gpio_start < 0 || num_gpios < 0 || num_gpios > PM8058_GPIOS) + return -EINVAL; + + while (num_gpios--) { + rc = pm8058_gpio_config(gpio_start++, &kypd_sns); + if (rc) { + pr_err("%s: FAIL pm8058_gpio_config(): rc=%d.\n", + __func__, rc); + return rc; + } + } + + return 0; +} + +/* + * keypad controller should be initialized in the following sequence + * only, otherwise it might get into FSM stuck state. + * + * - Initialize keypad control parameters, like no. of rows, columns, + * timing values etc., + * - configure rows and column gpios pull up/down. + * - set irq edge type. + * - enable the keypad controller. + */ +static int __devinit pmic8058_kp_probe(struct platform_device *pdev) +{ + struct pmic8058_keypad_data *pdata = pdev->dev.platform_data; + const struct matrix_keymap_data *keymap_data; + struct pmic8058_kp *kp; + int rc; + unsigned short *keycodes; + u8 ctrl_val; + struct pm8058_chip *pm_chip; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + dev_err(&pdev->dev, "no parent data passed in\n"); + return -EFAULT; + } + + if (!pdata || !pdata->num_cols || !pdata->num_rows || + pdata->num_cols > PM8058_MAX_COLS || + pdata->num_rows > PM8058_MAX_ROWS || + pdata->num_cols < PM8058_MIN_COLS || + pdata->num_rows < PM8058_MIN_ROWS) { + dev_err(&pdev->dev, "invalid platform data\n"); + return -EINVAL; + } + + if (pdata->rows_gpio_start < 0 || pdata->cols_gpio_start < 0) { + dev_err(&pdev->dev, "invalid gpio_start platform data\n"); + return -EINVAL; + } + + if (!pdata->scan_delay_ms || pdata->scan_delay_ms > MAX_SCAN_DELAY + || pdata->scan_delay_ms < MIN_SCAN_DELAY || + !is_power_of_2(pdata->scan_delay_ms)) { + dev_err(&pdev->dev, "invalid keypad scan time supplied\n"); + return -EINVAL; + } + + if (!pdata->row_hold_ns || pdata->row_hold_ns > MAX_ROW_HOLD_DELAY + || pdata->row_hold_ns < MIN_ROW_HOLD_DELAY || + ((pdata->row_hold_ns % MIN_ROW_HOLD_DELAY) != 0)) { + dev_err(&pdev->dev, "invalid keypad row hold time supplied\n"); + return -EINVAL; + } + + if (pm8058_rev(pm_chip) == PM_8058_REV_1p0) { + if (!pdata->debounce_ms + || !is_power_of_2(pdata->debounce_ms[0]) + || pdata->debounce_ms[0] > MAX_DEBOUNCE_A0_TIME + || pdata->debounce_ms[0] < MIN_DEBOUNCE_A0_TIME) { + dev_err(&pdev->dev, "invalid debounce time supplied\n"); + return -EINVAL; + } + } else { + if (!pdata->debounce_ms + || ((pdata->debounce_ms[1] % 5) != 0) + || pdata->debounce_ms[1] > MAX_DEBOUNCE_B0_TIME + || pdata->debounce_ms[1] < MIN_DEBOUNCE_B0_TIME) { + dev_err(&pdev->dev, "invalid debounce time supplied\n"); + return -EINVAL; + } + } + + keymap_data = pdata->keymap_data; + if (!keymap_data) { + dev_err(&pdev->dev, "no keymap data supplied\n"); + return -EINVAL; + } + + kp = kzalloc(sizeof(*kp), GFP_KERNEL); + if (!kp) + return -ENOMEM; + + keycodes = kzalloc(PM8058_MATRIX_MAX_SIZE * sizeof(*keycodes), + GFP_KERNEL); + if (!keycodes) { + rc = -ENOMEM; + goto err_alloc_mem; + } + + platform_set_drvdata(pdev, kp); + mutex_init(&kp->mutex); + + kp->pdata = pdata; + kp->dev = &pdev->dev; + kp->keycodes = keycodes; + kp->pm_chip = pm_chip; + + if (pm8058_rev(pm_chip) == PM_8058_REV_1p0) + kp->flags |= KEYF_FIX_LAST_ROW; + + kp->input = input_allocate_device(); + if (!kp->input) { + dev_err(&pdev->dev, "unable to allocate input device\n"); + rc = -ENOMEM; + goto err_alloc_device; + } + + /* Enable runtime PM ops, start in ACTIVE mode */ + rc = pm_runtime_set_active(&pdev->dev); + if (rc < 0) + dev_dbg(&pdev->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&pdev->dev); + + kp->key_sense_irq = platform_get_irq(pdev, 0); + if (kp->key_sense_irq < 0) { + dev_err(&pdev->dev, "unable to get keypad sense irq\n"); + rc = -ENXIO; + goto err_get_irq; + } + + kp->key_stuck_irq = platform_get_irq(pdev, 1); + if (kp->key_stuck_irq < 0) { + dev_err(&pdev->dev, "unable to get keypad stuck irq\n"); + rc = -ENXIO; + goto err_get_irq; + } + + if (pdata->input_name) + kp->input->name = pdata->input_name; + else + kp->input->name = "PMIC8058 keypad"; + + if (pdata->input_phys_device) + kp->input->phys = pdata->input_phys_device; + else + kp->input->phys = "pmic8058_keypad/input0"; + + kp->input->dev.parent = &pdev->dev; + + kp->input->id.bustype = BUS_HOST; + kp->input->id.version = 0x0001; + kp->input->id.product = 0x0001; + kp->input->id.vendor = 0x0001; + + kp->input->evbit[0] = BIT_MASK(EV_KEY); + + if (pdata->rep) + __set_bit(EV_REP, kp->input->evbit); + + kp->input->keycode = keycodes; + kp->input->keycodemax = PM8058_MATRIX_MAX_SIZE; + kp->input->keycodesize = sizeof(*keycodes); + + matrix_keypad_build_keymap(keymap_data, PM8058_ROW_SHIFT, + kp->input->keycode, kp->input->keybit); + + input_set_capability(kp->input, EV_MSC, MSC_SCAN); + input_set_drvdata(kp->input, kp); + + rc = input_register_device(kp->input); + if (rc < 0) { + dev_err(&pdev->dev, "unable to register keypad input device\n"); + goto err_get_irq; + } + + /* initialize keypad state */ + memset(kp->keystate, 0xff, sizeof(kp->keystate)); + memset(kp->stuckstate, 0xff, sizeof(kp->stuckstate)); + + rc = pmic8058_kpd_init(kp); + if (rc < 0) { + dev_err(&pdev->dev, "unable to initialize keypad controller\n"); + goto err_kpd_init; + } + + rc = pm8058_kp_config_sns(pdata->cols_gpio_start, + pdata->num_cols); + if (rc < 0) { + dev_err(&pdev->dev, "unable to configure keypad sense lines\n"); + goto err_gpio_config; + } + + rc = pm8058_kp_config_drv(pdata->rows_gpio_start, + pdata->num_rows); + if (rc < 0) { + dev_err(&pdev->dev, "unable to configure keypad drive lines\n"); + goto err_gpio_config; + } + + rc = request_threaded_irq(kp->key_sense_irq, NULL, pmic8058_kp_irq, + IRQF_TRIGGER_RISING, "pmic-keypad", kp); + if (rc < 0) { + dev_err(&pdev->dev, "failed to request keypad sense irq\n"); + goto err_req_sense_irq; + } + + rc = request_threaded_irq(kp->key_stuck_irq, NULL, + pmic8058_kp_stuck_irq, IRQF_TRIGGER_RISING, + "pmic-keypad-stuck", kp); + if (rc < 0) { + dev_err(&pdev->dev, "failed to request keypad stuck irq\n"); + goto err_req_stuck_irq; + } + + rc = pmic8058_kp_read_u8(kp, &ctrl_val, KEYP_CTRL); + ctrl_val |= KEYP_CTRL_KEYP_EN; + rc = pmic8058_kp_write_u8(kp, ctrl_val, KEYP_CTRL); + + kp->ctrl_reg = ctrl_val; + + __dump_kp_regs(kp, "probe"); + + rc = device_create_file(&pdev->dev, &dev_attr_disable_kp); + if (rc < 0) + goto err_create_file; + + device_init_wakeup(&pdev->dev, pdata->wakeup); + + return 0; + +err_create_file: + free_irq(kp->key_stuck_irq, NULL); +err_req_stuck_irq: + free_irq(kp->key_sense_irq, NULL); +err_req_sense_irq: +err_gpio_config: +err_kpd_init: + input_unregister_device(kp->input); + kp->input = NULL; +err_get_irq: + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + input_free_device(kp->input); +err_alloc_device: + kfree(keycodes); +err_alloc_mem: + kfree(kp); + return rc; +} + +static int __devexit pmic8058_kp_remove(struct platform_device *pdev) +{ + struct pmic8058_kp *kp = platform_get_drvdata(pdev); + + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + device_remove_file(&pdev->dev, &dev_attr_disable_kp); + device_init_wakeup(&pdev->dev, 0); + free_irq(kp->key_stuck_irq, NULL); + free_irq(kp->key_sense_irq, NULL); + input_unregister_device(kp->input); + platform_set_drvdata(pdev, NULL); + kfree(kp->input->keycode); + kfree(kp); + + return 0; +} + +#ifdef CONFIG_PM +static int pmic8058_kp_suspend(struct device *dev) +{ + struct pmic8058_kp *kp = dev_get_drvdata(dev); + + if (device_may_wakeup(dev) && !pmic8058_kp_disabled(kp)) { + enable_irq_wake(kp->key_sense_irq); + } else { + mutex_lock(&kp->mutex); + pmic8058_kp_disable(kp); + mutex_unlock(&kp->mutex); + } + + return 0; +} + +static int pmic8058_kp_resume(struct device *dev) +{ + struct pmic8058_kp *kp = dev_get_drvdata(dev); + + if (device_may_wakeup(dev) && !pmic8058_kp_disabled(kp)) { + disable_irq_wake(kp->key_sense_irq); + } else { + mutex_lock(&kp->mutex); + pmic8058_kp_enable(kp); + mutex_unlock(&kp->mutex); + } + + return 0; +} + +static struct dev_pm_ops pm8058_kp_pm_ops = { + .suspend = pmic8058_kp_suspend, + .resume = pmic8058_kp_resume, +}; +#endif + +static struct platform_driver pmic8058_kp_driver = { + .probe = pmic8058_kp_probe, + .remove = __devexit_p(pmic8058_kp_remove), + .driver = { + .name = "pm8058-keypad", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &pm8058_kp_pm_ops, +#endif + }, +}; + +static int __init pmic8058_kp_init(void) +{ + return platform_driver_register(&pmic8058_kp_driver); +} +module_init(pmic8058_kp_init); + +static void __exit pmic8058_kp_exit(void) +{ + platform_driver_unregister(&pmic8058_kp_driver); +} +module_exit(pmic8058_kp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 keypad driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8058_keypad"); diff --git a/drivers/input/keyboard/pmic8xxx-keypad.c b/drivers/input/keyboard/pmic8xxx-keypad.c index 6229c3e8e78..f0629ce3cba 100644 --- a/drivers/input/keyboard/pmic8xxx-keypad.c +++ b/drivers/input/keyboard/pmic8xxx-keypad.c @@ -116,6 +116,9 @@ static int pmic8xxx_kp_write_u8(struct pmic8xxx_kp *kp, int rc; rc = pm8xxx_writeb(kp->dev->parent, reg, data); + if (rc < 0) + dev_warn(kp->dev, "Error writing pmic8xxx: %X - ret %X\n", + reg, rc); return rc; } @@ -125,6 +128,10 @@ static int pmic8xxx_kp_read(struct pmic8xxx_kp *kp, int rc; rc = pm8xxx_read_buf(kp->dev->parent, reg, data, num_bytes); + if (rc < 0) + dev_warn(kp->dev, "Error reading pmic8xxx: %X - ret %X\n", + reg, rc); + return rc; } @@ -134,6 +141,9 @@ static int pmic8xxx_kp_read_u8(struct pmic8xxx_kp *kp, int rc; rc = pmic8xxx_kp_read(kp, data, reg, 1); + if (rc < 0) + dev_warn(kp->dev, "Error reading pmic8xxx: %X - ret %X\n", + reg, rc); return rc; } @@ -463,7 +473,7 @@ static int __devinit pmic8xxx_kp_config_gpio(int gpio_start, int num_gpios, __func__, gpio_start + i, rc); return rc; } - } + } return 0; } @@ -532,7 +542,7 @@ static int __devinit pmic8xxx_kp_probe(struct platform_device *pdev) .output_buffer = PM_GPIO_OUT_BUF_OPEN_DRAIN, .output_value = 0, .pull = PM_GPIO_PULL_NO, - .vin_sel = PM_GPIO_VIN_S3, + .vin_sel = PM_GPIO_VIN_S4, .out_strength = PM_GPIO_STRENGTH_LOW, .function = PM_GPIO_FUNC_1, .inv_int_pol = 1, @@ -541,7 +551,7 @@ static int __devinit pmic8xxx_kp_probe(struct platform_device *pdev) struct pm_gpio kypd_sns = { .direction = PM_GPIO_DIR_IN, .pull = PM_GPIO_PULL_UP_31P5, - .vin_sel = PM_GPIO_VIN_S3, + .vin_sel = PM_GPIO_VIN_S4, .out_strength = PM_GPIO_STRENGTH_NO, .function = PM_GPIO_FUNC_NORMAL, .inv_int_pol = 1, diff --git a/drivers/input/keyboard/qci_kbd.c b/drivers/input/keyboard/qci_kbd.c new file mode 100644 index 00000000000..d7350121fdc --- /dev/null +++ b/drivers/input/keyboard/qci_kbd.c @@ -0,0 +1,721 @@ +/* Quanta I2C Keyboard Driver + * + * Copyright (C) 2009 Quanta Computer Inc. + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * Author: Hsin Wu + * Author: Austin Lai + * + * 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. + * + */ + + /* + * + * The Driver with I/O communications via the I2C Interface for ON2 of AP BU. + * And it is only working on the nuvoTon WPCE775x Embedded Controller. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Keyboard special scancode */ +#define RC_KEY_FN 0x70 +#define RC_KEY_BREAK 0x80 +#define KEY_ACK_FA 0xFA +#define SCAN_EMUL0 0xE0 +#define SCAN_EMUL1 0xE1 +#define SCAN_PAUSE1 0x1D +#define SCAN_PAUSE2 0x45 +#define SCAN_LIDSW_OPEN 0x70 +#define SCAN_LIDSW_CLOSE 0x71 + +/* Keyboard keycodes */ +#define NOKEY KEY_RESERVED +#define KEY_LEFTWIN KEY_LEFTMETA +#define KEY_RIGHTWIN KEY_RIGHTMETA +#define KEY_APPS KEY_COMPOSE +#define KEY_PRINTSCR KEY_SYSRQ + +#define KEYBOARD_ID_NAME "qci-i2ckbd" +#define KEYBOARD_NAME "Quanta Keyboard" +#define KEYBOARD_DEVICE "/i2c/input0" +#define KEYBOARD_CMD_ENABLE 0xF4 +#define KEYBOARD_CMD_SET_LED 0xED + +/*----------------------------------------------------------------------------- + * Keyboard scancode to linux keycode translation table + *---------------------------------------------------------------------------*/ + +static const unsigned char on2_keycode[256] = { + [0] = NOKEY, + [1] = NOKEY, + [2] = NOKEY, + [3] = KEY_5, + [4] = KEY_7, + [5] = KEY_9, + [6] = KEY_MINUS, + [7] = NOKEY, + [8] = NOKEY, + [9] = NOKEY, + [10] = NOKEY, + [11] = KEY_LEFTBRACE, + [12] = KEY_F10, + [13] = KEY_INSERT, + [14] = KEY_F11, + [15] = KEY_ESC, + [16] = NOKEY, + [17] = NOKEY, + [18] = NOKEY, + [19] = KEY_4, + [20] = KEY_6, + [21] = KEY_8, + [22] = KEY_0, + [23] = KEY_EQUAL, + [24] = NOKEY, + [25] = NOKEY, + [26] = NOKEY, + [27] = KEY_P, + [28] = KEY_F9, + [29] = KEY_DELETE, + [30] = KEY_F12, + [31] = KEY_GRAVE, + [32] = KEY_W, + [33] = NOKEY, + [34] = NOKEY, + [35] = KEY_R, + [36] = KEY_T, + [37] = KEY_U, + [38] = KEY_O, + [39] = KEY_RIGHTBRACE, + [40] = NOKEY, + [41] = NOKEY, + [42] = NOKEY, + [43] = KEY_APOSTROPHE, + [44] = KEY_BACKSPACE, + [45] = NOKEY, + [46] = KEY_F8, + [47] = KEY_F5, + [48] = KEY_S, + [49] = NOKEY, + [50] = NOKEY, + [51] = KEY_E, + [52] = KEY_H, + [53] = KEY_Y, + [54] = KEY_I, + [55] = KEY_ENTER, + [56] = NOKEY, + [57] = NOKEY, + [58] = NOKEY, + [59] = KEY_SEMICOLON, + [60] = KEY_3, + [61] = KEY_PAGEUP, + [62] = KEY_Q, + [63] = KEY_TAB, + [64] = KEY_A, + [65] = NOKEY, + [66] = NOKEY, + [67] = KEY_F, + [68] = KEY_G, + [69] = KEY_J, + [70] = KEY_L, + [71] = NOKEY, + [72] = KEY_RIGHTSHIFT, + [73] = NOKEY, + [74] = NOKEY, + [75] = KEY_SLASH, + [76] = KEY_2, + [77] = KEY_PAGEDOWN, + [78] = KEY_F4, + [79] = KEY_F1, + [80] = KEY_Z, + [81] = NOKEY, + [82] = NOKEY, + [83] = KEY_D, + [84] = KEY_V, + [85] = KEY_N, + [86] = KEY_K, + [87] = NOKEY, + [88] = KEY_LEFTSHIFT, + [89] = KEY_RIGHTCTRL, + [90] = NOKEY, + [91] = KEY_DOT, + [92] = KEY_UP, + [93] = KEY_RIGHT, + [94] = KEY_F3, + [95] = KEY_F2, + [96] = NOKEY, + [97] = NOKEY, + [98] = KEY_RIGHTALT, + [99] = KEY_X, + [100] = KEY_C, + [101] = KEY_B, + [102] = KEY_COMMA, + [103] = NOKEY, + [104] = NOKEY, + [105] = NOKEY, + [106] = NOKEY, + [107] = NOKEY, + [108] = KEY_PRINTSCR, + [109] = KEY_DOWN, + [110] = KEY_1, + [111] = KEY_CAPSLOCK, + [112] = KEY_F24, + [113] = KEY_HOME, + [114] = KEY_LEFTALT, + [115] = NOKEY, + [116] = KEY_SPACE, + [117] = KEY_BACKSLASH, + [118] = KEY_M, + [119] = KEY_COMPOSE, + [120] = NOKEY, + [121] = KEY_LEFTCTRL, + [122] = NOKEY, + [123] = NOKEY, + [124] = KEY_PAUSE, + [125] = KEY_LEFT, + [126] = KEY_F7, + [127] = KEY_F6, + [128] = NOKEY, + [129] = NOKEY, + [130] = NOKEY, + [131] = NOKEY, + [132] = NOKEY, + [133] = NOKEY, + [134] = NOKEY, + [135] = NOKEY, + [136] = NOKEY, + [137] = NOKEY, + [138] = NOKEY, + [139] = NOKEY, + [140] = NOKEY, + [141] = NOKEY, + [142] = NOKEY, + [143] = NOKEY, + [144] = NOKEY, + [145] = NOKEY, + [146] = NOKEY, + [147] = NOKEY, + [148] = NOKEY, + [149] = NOKEY, + [150] = NOKEY, + [151] = NOKEY, + [152] = NOKEY, + [153] = NOKEY, + [154] = NOKEY, + [155] = NOKEY, + [156] = NOKEY, + [157] = NOKEY, + [158] = NOKEY, + [159] = NOKEY, + [160] = NOKEY, + [161] = NOKEY, + [162] = NOKEY, + [163] = NOKEY, + [164] = NOKEY, + [165] = NOKEY, + [166] = NOKEY, + [167] = NOKEY, + [168] = NOKEY, + [169] = NOKEY, + [170] = NOKEY, + [171] = NOKEY, + [172] = NOKEY, + [173] = NOKEY, + [174] = NOKEY, + [175] = NOKEY, + [176] = NOKEY, + [177] = NOKEY, + [178] = NOKEY, + [179] = NOKEY, + [180] = NOKEY, + [181] = NOKEY, + [182] = NOKEY, + [183] = NOKEY, + [184] = NOKEY, + [185] = NOKEY, + [186] = NOKEY, + [187] = NOKEY, + [188] = NOKEY, + [189] = KEY_HOME, + [190] = NOKEY, + [191] = NOKEY, + [192] = NOKEY, + [193] = NOKEY, + [194] = NOKEY, + [195] = NOKEY, + [196] = NOKEY, + [197] = NOKEY, + [198] = NOKEY, + [199] = NOKEY, + [200] = NOKEY, + [201] = NOKEY, + [202] = NOKEY, + [203] = NOKEY, + [204] = NOKEY, + [205] = KEY_END, + [206] = NOKEY, + [207] = NOKEY, + [208] = NOKEY, + [209] = NOKEY, + [210] = NOKEY, + [211] = NOKEY, + [212] = NOKEY, + [213] = NOKEY, + [214] = NOKEY, + [215] = NOKEY, + [216] = NOKEY, + [217] = NOKEY, + [218] = NOKEY, + [219] = NOKEY, + [220] = KEY_VOLUMEUP, + [221] = KEY_BRIGHTNESSUP, + [222] = NOKEY, + [223] = NOKEY, + [224] = NOKEY, + [225] = NOKEY, + [226] = NOKEY, + [227] = NOKEY, + [228] = NOKEY, + [229] = NOKEY, + [230] = NOKEY, + [231] = NOKEY, + [232] = NOKEY, + [233] = NOKEY, + [234] = NOKEY, + [235] = NOKEY, + [236] = NOKEY, + [237] = KEY_VOLUMEDOWN, + [238] = NOKEY, + [239] = NOKEY, + [240] = NOKEY, + [241] = NOKEY, + [242] = NOKEY, + [243] = NOKEY, + [244] = NOKEY, + [245] = NOKEY, + [246] = NOKEY, + [247] = NOKEY, + [248] = NOKEY, + [249] = NOKEY, + [250] = NOKEY, + [251] = NOKEY, + [252] = NOKEY, + [253] = KEY_BRIGHTNESSDOWN, + [254] = NOKEY, + [255] = NOKEY, +}; + +static const u8 emul0_map[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 97, 0, 0, + 113, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, + 115, 0, 0, 0, 0, 98, 0, 99, 100, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 102, 103, 104, 0, 105, 0, 106, 0, 107, + 108, 109, 110, 111, 0, 0, 0, 0, 0, 0, 0, 139, 0, 150, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +/*----------------------------------------------------------------------------- + * Global variables + *---------------------------------------------------------------------------*/ + +struct input_dev *g_qci_keyboard_dev; + +/* General structure to hold the driver data */ +struct i2ckbd_drv_data { + struct i2c_client *ki2c_client; + struct work_struct work; + struct input_dev *qcikbd_dev; + struct mutex kb_mutex; + unsigned int qcikbd_gpio; /* GPIO used for interrupt */ + unsigned int qcikbd_irq; + unsigned int key_down; + unsigned int escape; + unsigned int pause_seq; + unsigned int fn; + unsigned char led_status; + bool standard_scancodes; + bool kb_leds; + bool event_led; + bool emul0; + bool emul1; + bool pause1; +}; +#ifdef CONFIG_PM +static int qcikbd_suspend(struct device *dev) +{ + struct i2ckbd_drv_data *context = input_get_drvdata(g_qci_keyboard_dev); + + enable_irq_wake(context->qcikbd_irq); + return 0; +} + +static int qcikbd_resume(struct device *dev) +{ + struct i2ckbd_drv_data *context = input_get_drvdata(g_qci_keyboard_dev); + struct i2c_client *ikbdclient = context->ki2c_client; + + disable_irq_wake(context->qcikbd_irq); + + /* consume any keypress generated while suspended */ + i2c_smbus_read_byte(ikbdclient); + return 0; +} +#endif +static int __devinit qcikbd_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int __devexit qcikbd_remove(struct i2c_client *kbd); + +static const struct i2c_device_id qcikbd_idtable[] = { + { KEYBOARD_ID_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, qcikbd_idtable); + +#ifdef CONFIG_PM +static struct dev_pm_ops qcikbd_pm_ops = { + .suspend = qcikbd_suspend, + .resume = qcikbd_resume, +}; +#endif +static struct i2c_driver i2ckbd_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KEYBOARD_ID_NAME, +#ifdef CONFIG_PM + .pm = &qcikbd_pm_ops, +#endif + }, + .probe = qcikbd_probe, + .remove = __devexit_p(qcikbd_remove), + .id_table = qcikbd_idtable, +}; + +/*----------------------------------------------------------------------------- + * Driver functions + *---------------------------------------------------------------------------*/ + +#ifdef CONFIG_KEYBOARD_QCIKBD_LID +static void process_lid(struct input_dev *ikbdev, unsigned char scancode) +{ + if (scancode == SCAN_LIDSW_OPEN) + input_report_switch(ikbdev, SW_LID, 0); + else if (scancode == SCAN_LIDSW_CLOSE) + input_report_switch(ikbdev, SW_LID, 1); + else + return; + input_sync(ikbdev); +} +#endif + +static irqreturn_t qcikbd_interrupt(int irq, void *dev_id) +{ + struct i2ckbd_drv_data *ikbd_drv_data = dev_id; + schedule_work(&ikbd_drv_data->work); + return IRQ_HANDLED; +} + +static void qcikbd_work_handler(struct work_struct *_work) +{ + unsigned char scancode; + unsigned char scancode_only; + unsigned int keycode; + + struct i2ckbd_drv_data *ikbd_drv_data = + container_of(_work, struct i2ckbd_drv_data, work); + + struct i2c_client *ikbdclient = ikbd_drv_data->ki2c_client; + struct input_dev *ikbdev = ikbd_drv_data->qcikbd_dev; + + mutex_lock(&ikbd_drv_data->kb_mutex); + + if ((ikbd_drv_data->kb_leds) && (ikbd_drv_data->event_led)) { + i2c_smbus_write_byte(ikbdclient, KEYBOARD_CMD_SET_LED); + i2c_smbus_write_byte(ikbdclient, ikbd_drv_data->led_status); + ikbd_drv_data->event_led = 0; + goto work_exit; + } + + scancode = i2c_smbus_read_byte(ikbdclient); + + if (scancode == KEY_ACK_FA) + goto work_exit; + + if (ikbd_drv_data->standard_scancodes) { + /* pause key is E1 1D 45 */ + if (scancode == SCAN_EMUL1) { + ikbd_drv_data->emul1 = 1; + goto work_exit; + } + if (ikbd_drv_data->emul1) { + ikbd_drv_data->emul1 = 0; + if ((scancode & 0x7f) == SCAN_PAUSE1) + ikbd_drv_data->pause1 = 1; + goto work_exit; + } + if (ikbd_drv_data->pause1) { + ikbd_drv_data->pause1 = 0; + if ((scancode & 0x7f) == SCAN_PAUSE2) { + input_report_key(ikbdev, KEY_PAUSE, + !(scancode & 0x80)); + input_sync(ikbdev); + } + goto work_exit; + } + + if (scancode == SCAN_EMUL0) { + ikbd_drv_data->emul0 = 1; + goto work_exit; + } + if (ikbd_drv_data->emul0) { + ikbd_drv_data->emul0 = 0; + scancode_only = scancode & 0x7f; +#ifdef CONFIG_KEYBOARD_QCIKBD_LID + if ((scancode_only == SCAN_LIDSW_OPEN) || + (scancode_only == SCAN_LIDSW_CLOSE)) { + process_lid(ikbdev, scancode); + goto work_exit; + } +#endif + keycode = emul0_map[scancode_only]; + if (!keycode) { + dev_err(&ikbdev->dev, + "Unrecognized scancode %02x %02x\n", + SCAN_EMUL0, scancode); + goto work_exit; + } + } else { + keycode = scancode & 0x7f; + } + /* MS bit of scancode indicates direction of keypress */ + ikbd_drv_data->key_down = !(scancode & 0x80); + if (keycode) { + input_event(ikbdev, EV_MSC, MSC_SCAN, scancode); + input_report_key(ikbdev, keycode, + ikbd_drv_data->key_down); + input_sync(ikbdev); + } + goto work_exit; + } + + mutex_unlock(&ikbd_drv_data->kb_mutex); + + if (scancode == RC_KEY_FN) { + ikbd_drv_data->fn = 0x80; /* select keycode table > 0x7F */ + } else { + ikbd_drv_data->key_down = 1; + if (scancode & RC_KEY_BREAK) { + ikbd_drv_data->key_down = 0; + if ((scancode & 0x7F) == RC_KEY_FN) + ikbd_drv_data->fn = 0; + } + keycode = on2_keycode[(scancode & 0x7F) | ikbd_drv_data->fn]; + if (keycode != NOKEY) { + input_report_key(ikbdev, + keycode, + ikbd_drv_data->key_down); + input_sync(ikbdev); + } + } + return; + +work_exit: + mutex_unlock(&ikbd_drv_data->kb_mutex); +} + +static int qcikbd_input_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + struct i2ckbd_drv_data *ikbd_drv_data = input_get_drvdata(dev); + struct input_dev *ikbdev = ikbd_drv_data->qcikbd_dev; + + if (type != EV_LED) + return -EINVAL; + + ikbd_drv_data->led_status = + (test_bit(LED_SCROLLL, ikbdev->led) ? 1 : 0) | + (test_bit(LED_NUML, ikbdev->led) ? 2 : 0) | + (test_bit(LED_CAPSL, ikbdev->led) ? 4 : 0); + ikbd_drv_data->event_led = 1; + + schedule_work(&ikbd_drv_data->work); + return 0; +} + +static int qcikbd_open(struct input_dev *dev) +{ + struct i2ckbd_drv_data *ikbd_drv_data = input_get_drvdata(dev); + struct i2c_client *ikbdclient = ikbd_drv_data->ki2c_client; + + /* Send F4h - enable keyboard */ + i2c_smbus_write_byte(ikbdclient, KEYBOARD_CMD_ENABLE); + return 0; +} + +static int __devinit qcikbd_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err; + int i; + struct i2ckbd_drv_data *context; + struct qci_kbd_platform_data *pdata = client->dev.platform_data; + + if (!pdata) { + pr_err("[KBD] platform data not supplied\n"); + return -EINVAL; + } + + context = kzalloc(sizeof(struct i2ckbd_drv_data), GFP_KERNEL); + if (!context) + return -ENOMEM; + i2c_set_clientdata(client, context); + context->ki2c_client = client; + context->qcikbd_gpio = client->irq; + client->driver = &i2ckbd_driver; + + INIT_WORK(&context->work, qcikbd_work_handler); + mutex_init(&context->kb_mutex); + + err = gpio_request(context->qcikbd_gpio, "qci-kbd"); + if (err) { + pr_err("[KBD] err gpio request\n"); + goto gpio_request_fail; + } + + context->qcikbd_irq = gpio_to_irq(context->qcikbd_gpio); + err = request_irq(context->qcikbd_irq, + qcikbd_interrupt, + IRQF_TRIGGER_FALLING, + KEYBOARD_ID_NAME, + context); + if (err) { + pr_err("[KBD] err unable to get IRQ\n"); + goto request_irq_fail; + } + + context->standard_scancodes = pdata->standard_scancodes; + context->kb_leds = pdata->kb_leds; + context->qcikbd_dev = input_allocate_device(); + if (!context->qcikbd_dev) { + pr_err("[KBD]allocting memory err\n"); + err = -ENOMEM; + goto allocate_fail; + } + + context->qcikbd_dev->name = KEYBOARD_NAME; + context->qcikbd_dev->phys = KEYBOARD_DEVICE; + context->qcikbd_dev->id.bustype = BUS_I2C; + context->qcikbd_dev->id.vendor = 0x1050; + context->qcikbd_dev->id.product = 0x0006; + context->qcikbd_dev->id.version = 0x0004; + context->qcikbd_dev->open = qcikbd_open; + set_bit(EV_KEY, context->qcikbd_dev->evbit); + __set_bit(MSC_SCAN, context->qcikbd_dev->mscbit); + + if (pdata->repeat) + set_bit(EV_REP, context->qcikbd_dev->evbit); + + /* Enable all supported keys */ + for (i = 1; i < ARRAY_SIZE(on2_keycode) ; i++) + set_bit(on2_keycode[i], context->qcikbd_dev->keybit); + + set_bit(KEY_POWER, context->qcikbd_dev->keybit); + set_bit(KEY_END, context->qcikbd_dev->keybit); + set_bit(KEY_VOLUMEUP, context->qcikbd_dev->keybit); + set_bit(KEY_VOLUMEDOWN, context->qcikbd_dev->keybit); + set_bit(KEY_ZOOMIN, context->qcikbd_dev->keybit); + set_bit(KEY_ZOOMOUT, context->qcikbd_dev->keybit); + +#ifdef CONFIG_KEYBOARD_QCIKBD_LID + set_bit(EV_SW, context->qcikbd_dev->evbit); + set_bit(SW_LID, context->qcikbd_dev->swbit); +#endif + + if (context->kb_leds) { + context->qcikbd_dev->event = qcikbd_input_event; + __set_bit(EV_LED, context->qcikbd_dev->evbit); + __set_bit(LED_NUML, context->qcikbd_dev->ledbit); + __set_bit(LED_CAPSL, context->qcikbd_dev->ledbit); + __set_bit(LED_SCROLLL, context->qcikbd_dev->ledbit); + } + + input_set_drvdata(context->qcikbd_dev, context); + err = input_register_device(context->qcikbd_dev); + if (err) { + pr_err("[KBD] err input register device\n"); + goto register_fail; + } + g_qci_keyboard_dev = context->qcikbd_dev; + return 0; +register_fail: + input_free_device(context->qcikbd_dev); + +allocate_fail: + free_irq(context->qcikbd_irq, context); + +request_irq_fail: + gpio_free(context->qcikbd_gpio); + +gpio_request_fail: + i2c_set_clientdata(client, NULL); + kfree(context); + return err; +} + +static int __devexit qcikbd_remove(struct i2c_client *dev) +{ + struct i2ckbd_drv_data *context = i2c_get_clientdata(dev); + + free_irq(context->qcikbd_irq, context); + gpio_free(context->qcikbd_gpio); + input_free_device(context->qcikbd_dev); + input_unregister_device(context->qcikbd_dev); + kfree(context); + + return 0; +} + +static int __init qcikbd_init(void) +{ + return i2c_add_driver(&i2ckbd_driver); +} + +static void __exit qcikbd_exit(void) +{ + i2c_del_driver(&i2ckbd_driver); +} + +struct input_dev *nkbc_keypad_get_input_dev(void) +{ + return g_qci_keyboard_dev; +} +EXPORT_SYMBOL(nkbc_keypad_get_input_dev); +module_init(qcikbd_init); +module_exit(qcikbd_exit); + +MODULE_AUTHOR("Quanta Computer Inc."); +MODULE_DESCRIPTION("Quanta Embedded Controller I2C Keyboard Driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index ce28bf6c8fd..9acebc01901 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -321,6 +321,18 @@ config INPUT_GPIO help Say Y here if you want to support gpio based keys, wheels etc... +config INPUT_ISA1200_FF_MEMLESS + tristate "ISA1200 haptic ff-memless support" + depends on I2C + select INPUT_FF_MEMLESS + help + ISA1200 is a high performance enhanced haptic chip. + Say Y here if you want to support ISA1200 connected via I2C, + and select N if you are unsure. + + To compile this driver as a module, choose M here: the + module will be called isa1200-ff-memless. + config HP_SDC_RTC tristate "HP SDC Real Time Clock" depends on (GSC || HP300) && SERIO @@ -505,4 +517,43 @@ config INPUT_XEN_KBDDEV_FRONTEND To compile this driver as a module, choose M here: the module will be called xen-kbdfront. +config PMIC8058_PWRKEY + tristate "PMIC8058 power key support" + default n + depends on PMIC8058 + help + Say Y here if you want support for the PMIC8058 power key. + + To compile this driver as a module, choose M here: the + module will be called pmic8058-pwrkey. + +config PMIC8058_OTHC + tristate "Qualcomm PMIC8058 OTHC support" + default n + depends on PMIC8058 + help + Say Y here if you want support PMIC8058 OTHC. + + To compile this driver as a module, choose M here: the + module will be called pmic8058-othc. + +config INPUT_PMIC8058_VIBRA_MEMLESS + tristate "Qualcomm PM8058 vibrator support (ff-memless)" + depends on PMIC8058 && INPUT_FF_MEMLESS + default n + help + This option enables device driver support for the vibrator + on Qualcomm PM8058 chip. This driver supports ff-memless interface + from input framework. + + To compile this driver as module, choose M here: the + module will be called pmic8058-vib-memless. + +config BOSCH_BMA150 + tristate "SMB380/BMA150 acceleration sensor support" + depends on I2C=y + help + If you say yes here you get support for Bosch Sensortec's + acceleration sensors SMB380/BMA150. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 014d45f95e9..770eb96aacd 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o obj-$(CONFIG_INPUT_PCF8574) += pcf8574_keypad.o obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o +obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o obj-$(CONFIG_INPUT_POWERMATE) += powermate.o obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o @@ -48,4 +49,8 @@ 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_PMIC8058_PWRKEY) += pmic8058-pwrkey.o +obj-$(CONFIG_PMIC8058_OTHC) += pmic8058-othc.o +obj-$(CONFIG_INPUT_PMIC8058_VIBRA_MEMLESS) += pmic8058-vib-memless.o +obj-$(CONFIG_BOSCH_BMA150) += bma150.o diff --git a/drivers/input/misc/bma150.c b/drivers/input/misc/bma150.c new file mode 100644 index 00000000000..8911c0b2b89 --- /dev/null +++ b/drivers/input/misc/bma150.c @@ -0,0 +1,791 @@ +/* Date: 2011/3/7 11:00:00 + * Revision: 2.11 + */ + +/* + * This software program is licensed subject to the GNU General Public License + * (GPL).Version 2,June 1991, available at http://www.fsf.org/copyleft/gpl.html + + * (C) Copyright 2011 Bosch Sensortec GmbH + * All Rights Reserved + */ + + +/* file BMA150.c + brief This file contains all function implementations for the BMA150 in linux + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SENSOR_NAME "bma150" +#define GRAVITY_EARTH 9806550 +#define ABSMIN_2G (-GRAVITY_EARTH * 2) +#define ABSMAX_2G (GRAVITY_EARTH * 2) +#define BMA150_MAX_DELAY 200 +#define BMA150_CHIP_ID 2 +#define BMA150_RANGE_SET 0 +#define BMA150_BW_SET 4 + + + +#define BMA150_CHIP_ID_REG 0x00 +#define BMA150_X_AXIS_LSB_REG 0x02 +#define BMA150_X_AXIS_MSB_REG 0x03 +#define BMA150_Y_AXIS_LSB_REG 0x04 +#define BMA150_Y_AXIS_MSB_REG 0x05 +#define BMA150_Z_AXIS_LSB_REG 0x06 +#define BMA150_Z_AXIS_MSB_REG 0x07 +#define BMA150_STATUS_REG 0x09 +#define BMA150_CTRL_REG 0x0a +#define BMA150_CONF1_REG 0x0b + +#define BMA150_CUSTOMER1_REG 0x12 +#define BMA150_CUSTOMER2_REG 0x13 +#define BMA150_RANGE_BWIDTH_REG 0x14 +#define BMA150_CONF2_REG 0x15 + +#define BMA150_OFFS_GAIN_X_REG 0x16 +#define BMA150_OFFS_GAIN_Y_REG 0x17 +#define BMA150_OFFS_GAIN_Z_REG 0x18 +#define BMA150_OFFS_GAIN_T_REG 0x19 +#define BMA150_OFFSET_X_REG 0x1a +#define BMA150_OFFSET_Y_REG 0x1b +#define BMA150_OFFSET_Z_REG 0x1c +#define BMA150_OFFSET_T_REG 0x1d + +#define BMA150_CHIP_ID__POS 0 +#define BMA150_CHIP_ID__MSK 0x07 +#define BMA150_CHIP_ID__LEN 3 +#define BMA150_CHIP_ID__REG BMA150_CHIP_ID_REG + +/* DATA REGISTERS */ + +#define BMA150_NEW_DATA_X__POS 0 +#define BMA150_NEW_DATA_X__LEN 1 +#define BMA150_NEW_DATA_X__MSK 0x01 +#define BMA150_NEW_DATA_X__REG BMA150_X_AXIS_LSB_REG + +#define BMA150_ACC_X_LSB__POS 6 +#define BMA150_ACC_X_LSB__LEN 2 +#define BMA150_ACC_X_LSB__MSK 0xC0 +#define BMA150_ACC_X_LSB__REG BMA150_X_AXIS_LSB_REG + +#define BMA150_ACC_X_MSB__POS 0 +#define BMA150_ACC_X_MSB__LEN 8 +#define BMA150_ACC_X_MSB__MSK 0xFF +#define BMA150_ACC_X_MSB__REG BMA150_X_AXIS_MSB_REG + +#define BMA150_ACC_Y_LSB__POS 6 +#define BMA150_ACC_Y_LSB__LEN 2 +#define BMA150_ACC_Y_LSB__MSK 0xC0 +#define BMA150_ACC_Y_LSB__REG BMA150_Y_AXIS_LSB_REG + +#define BMA150_ACC_Y_MSB__POS 0 +#define BMA150_ACC_Y_MSB__LEN 8 +#define BMA150_ACC_Y_MSB__MSK 0xFF +#define BMA150_ACC_Y_MSB__REG BMA150_Y_AXIS_MSB_REG + +#define BMA150_ACC_Z_LSB__POS 6 +#define BMA150_ACC_Z_LSB__LEN 2 +#define BMA150_ACC_Z_LSB__MSK 0xC0 +#define BMA150_ACC_Z_LSB__REG BMA150_Z_AXIS_LSB_REG + +#define BMA150_ACC_Z_MSB__POS 0 +#define BMA150_ACC_Z_MSB__LEN 8 +#define BMA150_ACC_Z_MSB__MSK 0xFF +#define BMA150_ACC_Z_MSB__REG BMA150_Z_AXIS_MSB_REG + +/* CONTROL BITS */ + +#define BMA150_SLEEP__POS 0 +#define BMA150_SLEEP__LEN 1 +#define BMA150_SLEEP__MSK 0x01 +#define BMA150_SLEEP__REG BMA150_CTRL_REG + +#define BMA150_SOFT_RESET__POS 1 +#define BMA150_SOFT_RESET__LEN 1 +#define BMA150_SOFT_RESET__MSK 0x02 +#define BMA150_SOFT_RESET__REG BMA150_CTRL_REG + +#define BMA150_EE_W__POS 4 +#define BMA150_EE_W__LEN 1 +#define BMA150_EE_W__MSK 0x10 +#define BMA150_EE_W__REG BMA150_CTRL_REG + +#define BMA150_UPDATE_IMAGE__POS 5 +#define BMA150_UPDATE_IMAGE__LEN 1 +#define BMA150_UPDATE_IMAGE__MSK 0x20 +#define BMA150_UPDATE_IMAGE__REG BMA150_CTRL_REG + +#define BMA150_RESET_INT__POS 6 +#define BMA150_RESET_INT__LEN 1 +#define BMA150_RESET_INT__MSK 0x40 +#define BMA150_RESET_INT__REG BMA150_CTRL_REG + +/* BANDWIDTH dependend definitions */ + +#define BMA150_BANDWIDTH__POS 0 +#define BMA150_BANDWIDTH__LEN 3 +#define BMA150_BANDWIDTH__MSK 0x07 +#define BMA150_BANDWIDTH__REG BMA150_RANGE_BWIDTH_REG + +/* RANGE */ + +#define BMA150_RANGE__POS 3 +#define BMA150_RANGE__LEN 2 +#define BMA150_RANGE__MSK 0x18 +#define BMA150_RANGE__REG BMA150_RANGE_BWIDTH_REG + +/* WAKE UP */ + +#define BMA150_WAKE_UP__POS 0 +#define BMA150_WAKE_UP__LEN 1 +#define BMA150_WAKE_UP__MSK 0x01 +#define BMA150_WAKE_UP__REG BMA150_CONF2_REG + +#define BMA150_WAKE_UP_PAUSE__POS 1 +#define BMA150_WAKE_UP_PAUSE__LEN 2 +#define BMA150_WAKE_UP_PAUSE__MSK 0x06 +#define BMA150_WAKE_UP_PAUSE__REG BMA150_CONF2_REG + +#define BMA150_GET_BITSLICE(regvar, bitname)\ + ((regvar & bitname##__MSK) >> bitname##__POS) + + +#define BMA150_SET_BITSLICE(regvar, bitname, val)\ + ((regvar & ~bitname##__MSK) | ((val<>1)); + comres += bma150_smbus_write_byte(client, + BMA150_WAKE_UP__REG, &data1); + comres += bma150_smbus_write_byte(client, + BMA150_SLEEP__REG, &data2); + mutex_lock(&bma150->mode_mutex); + bma150->mode = (unsigned char) Mode; + mutex_unlock(&bma150->mode_mutex); + + } else{ + comres = -1; + } + } + + return comres; +} + + +static int bma150_set_range(struct i2c_client *client, unsigned char Range) +{ + int comres = 0; + unsigned char data = 0; + + if (client == NULL) { + comres = -1; + } else{ + if (Range < 3) { + + comres = bma150_smbus_read_byte(client, + BMA150_RANGE__REG, &data); + data = BMA150_SET_BITSLICE(data, BMA150_RANGE, Range); + comres += bma150_smbus_write_byte(client, + BMA150_RANGE__REG, &data); + + } else{ + comres = -1; + } + } + + return comres; +} + +static int bma150_get_range(struct i2c_client *client, unsigned char *Range) +{ + int comres = 0; + unsigned char data; + + if (client == NULL) { + comres = -1; + } else{ + comres = bma150_smbus_read_byte(client, + BMA150_RANGE__REG, &data); + + *Range = BMA150_GET_BITSLICE(data, BMA150_RANGE); + + } + + return comres; +} + + + +static int bma150_set_bandwidth(struct i2c_client *client, unsigned char BW) +{ + int comres = 0; + unsigned char data = 0; + + if (client == NULL) { + comres = -1; + } else{ + if (BW < 8) { + comres = bma150_smbus_read_byte(client, + BMA150_BANDWIDTH__REG, &data); + data = BMA150_SET_BITSLICE(data, BMA150_BANDWIDTH, BW); + comres += bma150_smbus_write_byte(client, + BMA150_BANDWIDTH__REG, &data); + + } else{ + comres = -1; + } + } + + return comres; +} + +static int bma150_get_bandwidth(struct i2c_client *client, unsigned char *BW) +{ + int comres = 0; + unsigned char data; + + if (client == NULL) { + comres = -1; + } else{ + + + comres = bma150_smbus_read_byte(client, + BMA150_BANDWIDTH__REG, &data); + + *BW = BMA150_GET_BITSLICE(data, BMA150_BANDWIDTH); + + + } + + return comres; +} + +static int bma150_read_accel_xyz(struct i2c_client *client, + struct bma150acc *acc) +{ + int comres; + unsigned char data[6]; + if (client == NULL) { + comres = -1; + } else{ + + + comres = bma150_smbus_read_byte_block(client, + BMA150_ACC_X_LSB__REG, &data[0], 6); + + acc->x = BMA150_GET_BITSLICE(data[0], BMA150_ACC_X_LSB) | + (BMA150_GET_BITSLICE(data[1], BMA150_ACC_X_MSB)<< + BMA150_ACC_X_LSB__LEN); + acc->x = acc->x << (sizeof(short)*8-(BMA150_ACC_X_LSB__LEN+ + BMA150_ACC_X_MSB__LEN)); + acc->x = acc->x >> (sizeof(short)*8-(BMA150_ACC_X_LSB__LEN+ + BMA150_ACC_X_MSB__LEN)); + + acc->y = BMA150_GET_BITSLICE(data[2], BMA150_ACC_Y_LSB) | + (BMA150_GET_BITSLICE(data[3], BMA150_ACC_Y_MSB)<< + BMA150_ACC_Y_LSB__LEN); + acc->y = acc->y << (sizeof(short)*8-(BMA150_ACC_Y_LSB__LEN + + BMA150_ACC_Y_MSB__LEN)); + acc->y = acc->y >> (sizeof(short)*8-(BMA150_ACC_Y_LSB__LEN + + BMA150_ACC_Y_MSB__LEN)); + + + acc->z = BMA150_GET_BITSLICE(data[4], BMA150_ACC_Z_LSB); + acc->z |= (BMA150_GET_BITSLICE(data[5], BMA150_ACC_Z_MSB)<< + BMA150_ACC_Z_LSB__LEN); + acc->z = acc->z << (sizeof(short)*8-(BMA150_ACC_Z_LSB__LEN+ + BMA150_ACC_Z_MSB__LEN)); + acc->z = acc->z >> (sizeof(short)*8-(BMA150_ACC_Z_LSB__LEN+ + BMA150_ACC_Z_MSB__LEN)); + + } + + return comres; +} + +static void bma150_work_func(struct work_struct *work) +{ + struct bma150_data *bma150 = container_of((struct delayed_work *)work, + struct bma150_data, work); + static struct bma150acc acc; + unsigned long delay = msecs_to_jiffies(atomic_read(&bma150->delay)); + + + + bma150_read_accel_xyz(bma150->bma150_client, &acc); + input_report_abs(bma150->input, ABS_X, acc.x); + input_report_abs(bma150->input, ABS_Y, acc.y); + input_report_abs(bma150->input, ABS_Z, acc.z); + input_sync(bma150->input); + mutex_lock(&bma150->value_mutex); + bma150->value = acc; + mutex_unlock(&bma150->value_mutex); + schedule_delayed_work(&bma150->work, delay); +} + +static ssize_t bma150_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data; + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + mutex_lock(&bma150->mode_mutex); + data = bma150->mode; + mutex_unlock(&bma150->mode_mutex); + + return sprintf(buf, "%d\n", data); +} + +static ssize_t bma150_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + error = strict_strtoul(buf, 10, &data); + if (error) + return error; + if (bma150_set_mode(bma150->bma150_client, (unsigned char) data) < 0) + return -EINVAL; + + + return count; +} +static ssize_t bma150_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data; + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + if (bma150_get_range(bma150->bma150_client, &data) < 0) + return sprintf(buf, "Read error\n"); + + return sprintf(buf, "%d\n", data); +} + +static ssize_t bma150_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + error = strict_strtoul(buf, 10, &data); + if (error) + return error; + if (bma150_set_range(bma150->bma150_client, (unsigned char) data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma150_bandwidth_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data; + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + if (bma150_get_bandwidth(bma150->bma150_client, &data) < 0) + return sprintf(buf, "Read error\n"); + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma150_bandwidth_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + error = strict_strtoul(buf, 10, &data); + if (error) + return error; + if (bma150_set_bandwidth(bma150->bma150_client, + (unsigned char) data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma150_value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct input_dev *input = to_input_dev(dev); + struct bma150_data *bma150 = input_get_drvdata(input); + struct bma150acc acc_value; + + mutex_lock(&bma150->value_mutex); + acc_value = bma150->value; + mutex_unlock(&bma150->value_mutex); + + return sprintf(buf, "%d %d %d\n", acc_value.x, acc_value.y, + acc_value.z); +} + + + +static ssize_t bma150_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + return sprintf(buf, "%d\n", atomic_read(&bma150->delay)); + +} + +static ssize_t bma150_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + error = strict_strtoul(buf, 10, &data); + if (error) + return error; + if (data > BMA150_MAX_DELAY) + data = BMA150_MAX_DELAY; + atomic_set(&bma150->delay, (unsigned int) data); + + return count; +} + +static DEVICE_ATTR(range, S_IRUGO|S_IWUSR|S_IWGRP, + bma150_range_show, bma150_range_store); +static DEVICE_ATTR(bandwidth, S_IRUGO|S_IWUSR|S_IWGRP, + bma150_bandwidth_show, bma150_bandwidth_store); +static DEVICE_ATTR(mode, S_IRUGO|S_IWUSR|S_IWGRP, + bma150_mode_show, bma150_mode_store); +static DEVICE_ATTR(value, S_IRUGO|S_IWUSR|S_IWGRP, + bma150_value_show, NULL); +static DEVICE_ATTR(delay, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma150_delay_show, bma150_delay_store); + +static struct attribute *bma150_attributes[] = { + &dev_attr_range.attr, + &dev_attr_bandwidth.attr, + &dev_attr_mode.attr, + &dev_attr_value.attr, + &dev_attr_delay.attr, + NULL +}; + +static struct attribute_group bma150_attribute_group = { + .attrs = bma150_attributes +}; + +static int bma150_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return -ENODEV; + + strlcpy(info->type, SENSOR_NAME, I2C_NAME_SIZE); + + return 0; +} + +static int bma150_input_init(struct bma150_data *bma150) +{ + struct input_dev *dev; + int err; + + dev = input_allocate_device(); + if (!dev) + return -ENOMEM; + dev->name = SENSOR_NAME; + dev->id.bustype = BUS_I2C; + + input_set_capability(dev, EV_ABS, ABS_MISC); + input_set_abs_params(dev, ABS_X, ABSMIN_2G, ABSMAX_2G, 0, 0); + input_set_abs_params(dev, ABS_Y, ABSMIN_2G, ABSMAX_2G, 0, 0); + input_set_abs_params(dev, ABS_Z, ABSMIN_2G, ABSMAX_2G, 0, 0); + input_set_drvdata(dev, bma150); + + err = input_register_device(dev); + if (err < 0) { + input_free_device(dev); + return err; + } + bma150->input = dev; + + return 0; +} + +static void bma150_input_delete(struct bma150_data *bma150) +{ + struct input_dev *dev = bma150->input; + + input_unregister_device(dev); + input_free_device(dev); +} + +static int bma150_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err = 0; + int tempvalue; + struct bma150_data *data; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + printk(KERN_INFO "i2c_check_functionality error\n"); + goto exit; + } + data = kzalloc(sizeof(struct bma150_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + data->platform_data = client->dev.platform_data; + + if (data->platform_data->power_on) + data->platform_data->power_on(); + else + printk(KERN_ERR "power_on function not defined!!\n"); + + tempvalue = 0; + tempvalue = i2c_smbus_read_word_data(client, BMA150_CHIP_ID_REG); + + if ((tempvalue&0x00FF) == BMA150_CHIP_ID) { + printk(KERN_INFO "Bosch Sensortec Device detected!\n" \ + "BMA150 registered I2C driver!\n"); + } else{ + printk(KERN_INFO "Bosch Sensortec Device not found" \ + "i2c error %d\n", tempvalue); + err = -1; + goto kfree_exit; + } + i2c_set_clientdata(client, data); + data->bma150_client = client; + mutex_init(&data->value_mutex); + mutex_init(&data->mode_mutex); + bma150_set_bandwidth(client, BMA150_BW_SET); + bma150_set_range(client, BMA150_RANGE_SET); + + + INIT_DELAYED_WORK(&data->work, bma150_work_func); + atomic_set(&data->delay, BMA150_MAX_DELAY); + err = bma150_input_init(data); + if (err < 0) + goto kfree_exit; + + err = sysfs_create_group(&data->input->dev.kobj, + &bma150_attribute_group); + if (err < 0) + goto error_sysfs; + + schedule_delayed_work(&data->work, + msecs_to_jiffies(atomic_read(&data->delay))); + + return 0; + +error_sysfs: + bma150_input_delete(data); + +kfree_exit: + kfree(data); +exit: + return err; +} + +static int bma150_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct bma150_data *data = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&data->work); + + bma150_set_mode(client, BMA150_MODE_SLEEP); + + if ((data->platform_data) && (data->platform_data->power_off)) + data->platform_data->power_off(); + + return 0; +} + +static int bma150_resume(struct i2c_client *client) +{ + struct bma150_data *data = i2c_get_clientdata(client); + + if ((data->platform_data) && (data->platform_data->power_on)) + data->platform_data->power_on(); + + bma150_set_mode(client, BMA150_MODE_NORMAL); + + schedule_delayed_work(&data->work, + msecs_to_jiffies(atomic_read(&data->delay))); + + return 0; +} + +static int bma150_remove(struct i2c_client *client) +{ + struct bma150_data *data = i2c_get_clientdata(client); + + if (data->platform_data->power_off) + data->platform_data->power_off(); + else + printk(KERN_ERR "power_off function not defined!!\n"); + + sysfs_remove_group(&data->input->dev.kobj, &bma150_attribute_group); + bma150_input_delete(data); + free_irq(data->IRQ, data); + kfree(data); + + return 0; +} + +static const struct i2c_device_id bma150_id[] = { + { SENSOR_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, bma150_id); + +static struct i2c_driver bma150_driver = { + .driver = { + .owner = THIS_MODULE, + .name = SENSOR_NAME, + }, + .class = I2C_CLASS_HWMON, + .id_table = bma150_id, + .probe = bma150_probe, + .remove = bma150_remove, + .detect = bma150_detect, + .suspend = bma150_suspend, + .resume = bma150_resume, +}; + +static int __init BMA150_init(void) +{ + return i2c_add_driver(&bma150_driver); +} + +static void __exit BMA150_exit(void) +{ + i2c_del_driver(&bma150_driver); +} + +MODULE_DESCRIPTION("BMA150 driver"); + +module_init(BMA150_init); +module_exit(BMA150_exit); + diff --git a/drivers/input/misc/gpio_matrix.c b/drivers/input/misc/gpio_matrix.c index eaa9e89d473..56e91fdc35e 100644 --- a/drivers/input/misc/gpio_matrix.c +++ b/drivers/input/misc/gpio_matrix.c @@ -181,12 +181,14 @@ static enum hrtimer_restart gpio_keypad_timer_func(struct hrtimer *timer) gpio_set_value(gpio, polarity); else gpio_direction_output(gpio, polarity); - hrtimer_start(timer, mi->settle_time, HRTIMER_MODE_REL); + hrtimer_start(timer, timespec_to_ktime(mi->settle_time), + HRTIMER_MODE_REL); return HRTIMER_NORESTART; } if (gpio_keypad_flags & GPIOKPF_DEBOUNCE) { if (kp->key_state_changed) { - hrtimer_start(&kp->timer, mi->debounce_delay, + hrtimer_start(&kp->timer, + timespec_to_ktime(mi->debounce_delay), HRTIMER_MODE_REL); return HRTIMER_NORESTART; } @@ -202,7 +204,8 @@ static enum hrtimer_restart gpio_keypad_timer_func(struct hrtimer *timer) report_sync(kp); } if (!kp->use_irq || kp->some_keys_pressed) { - hrtimer_start(timer, mi->poll_time, HRTIMER_MODE_REL); + hrtimer_start(timer, timespec_to_ktime(mi->poll_time), + HRTIMER_MODE_REL); return HRTIMER_NORESTART; } diff --git a/drivers/input/misc/isa1200-ff-memless.c b/drivers/input/misc/isa1200-ff-memless.c new file mode 100644 index 00000000000..f4e2c354f5a --- /dev/null +++ b/drivers/input/misc/isa1200-ff-memless.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2009 Samsung Electronics + * Kyungmin Park + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ISA1200_HCTRL0 0x30 +#define HCTRL0_MODE_CTRL_BIT (3) +#define HCTRL0_OVERDRIVE_HIGH_BIT (5) +#define HCTRL0_OVERDRIVE_EN_BIT (6) +#define HCTRL0_HAP_EN (7) +#define HCTRL0_RESET 0x01 +#define HCTRL1_RESET 0x4B + +#define ISA1200_HCTRL1 0x31 +#define HCTRL1_SMART_ENABLE_BIT (3) +#define HCTRL1_ERM_BIT (5) +#define HCTRL1_EXT_CLK_ENABLE_BIT (7) + +#define ISA1200_HCTRL5 0x35 +#define HCTRL5_VIB_STRT 0xD5 +#define HCTRL5_VIB_STOP 0x6B + +#define DIVIDER_128 (128) +#define DIVIDER_1024 (1024) +#define DIVIDE_SHIFTER_128 (7) + +#define FREQ_22400 (22400) +#define FREQ_172600 (172600) + +#define POR_DELAY_USEC 250 + +struct isa1200_chip { + const struct isa1200_platform_data *pdata; + struct i2c_client *client; + struct input_dev *input_device; + struct pwm_device *pwm; + unsigned int period_ns; + unsigned int state; + struct work_struct work; +}; + +static void isa1200_vib_set(struct isa1200_chip *haptic, int enable) +{ + int rc; + + if (enable) { + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { + int period_us = haptic->period_ns / NSEC_PER_USEC; + rc = pwm_config(haptic->pwm, + (period_us * haptic->pdata->duty) / 100, + period_us); + if (rc < 0) + pr_err("pwm_config fail\n"); + rc = pwm_enable(haptic->pwm); + if (rc < 0) + pr_err("pwm_enable fail\n"); + } else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + rc = i2c_smbus_write_byte_data(haptic->client, + ISA1200_HCTRL5, + HCTRL5_VIB_STRT); + if (rc < 0) + pr_err("start vibration fail\n"); + } + } else { + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) + pwm_disable(haptic->pwm); + else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + rc = i2c_smbus_write_byte_data(haptic->client, + ISA1200_HCTRL5, + HCTRL5_VIB_STOP); + if (rc < 0) + pr_err("stop vibration fail\n"); + } + } +} + +static int isa1200_setup(struct i2c_client *client) +{ + struct isa1200_chip *haptic = i2c_get_clientdata(client); + int value, temp, rc; + + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + udelay(POR_DELAY_USEC); + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 1); + + value = (haptic->pdata->smart_en << HCTRL1_SMART_ENABLE_BIT) | + (haptic->pdata->is_erm << HCTRL1_ERM_BIT) | + (haptic->pdata->ext_clk_en << HCTRL1_EXT_CLK_ENABLE_BIT); + + rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, value); + if (rc < 0) { + pr_err("i2c write failure\n"); + return rc; + } + + if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + temp = haptic->pdata->pwm_fd.pwm_div; + if (temp < DIVIDER_128 || temp > DIVIDER_1024 || + temp % DIVIDER_128) { + pr_err("Invalid divider\n"); + rc = -EINVAL; + goto reset_hctrl1; + } + value = ((temp >> DIVIDE_SHIFTER_128) - 1); + } else if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { + temp = haptic->pdata->pwm_fd.pwm_freq; + if (temp < FREQ_22400 || temp > FREQ_172600 || + temp % FREQ_22400) { + pr_err("Invalid frequency\n"); + rc = -EINVAL; + goto reset_hctrl1; + } + value = ((temp / FREQ_22400) - 1); + haptic->period_ns = NSEC_PER_SEC / temp; + } + value |= (haptic->pdata->mode_ctrl << HCTRL0_MODE_CTRL_BIT) | + (haptic->pdata->overdrive_high << HCTRL0_OVERDRIVE_HIGH_BIT) | + (haptic->pdata->overdrive_en << HCTRL0_OVERDRIVE_EN_BIT) | + (haptic->pdata->chip_en << HCTRL0_HAP_EN); + + rc = i2c_smbus_write_byte_data(client, ISA1200_HCTRL0, value); + if (rc < 0) { + pr_err("i2c write failure\n"); + goto reset_hctrl1; + } + + return 0; + +reset_hctrl1: + i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, + HCTRL1_RESET); + return rc; +} + +static void isa1200_worker(struct work_struct *work) +{ + struct isa1200_chip *haptic; + + haptic = container_of(work, struct isa1200_chip, work); + isa1200_vib_set(haptic, !!haptic->state); +} + +static int isa1200_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct isa1200_chip *haptic = input_get_drvdata(dev); + + /* support basic vibration */ + haptic->state = effect->u.rumble.strong_magnitude >> 8; + if (!haptic->state) + haptic->state = effect->u.rumble.weak_magnitude >> 9; + + schedule_work(&haptic->work); + + return 0; +} + +#ifdef CONFIG_PM +static int isa1200_suspend(struct device *dev) +{ + struct isa1200_chip *haptic = dev_get_drvdata(dev); + int rc; + + cancel_work_sync(&haptic->work); + /* turn-off current vibration */ + isa1200_vib_set(haptic, 0); + + if (haptic->pdata->power_on) { + rc = haptic->pdata->power_on(0); + if (rc) { + pr_err("power-down failed\n"); + return rc; + } + } + + return 0; +} + +static int isa1200_resume(struct device *dev) +{ + struct isa1200_chip *haptic = dev_get_drvdata(dev); + int rc; + + if (haptic->pdata->power_on) { + rc = haptic->pdata->power_on(1); + if (rc) { + pr_err("power-up failed\n"); + return rc; + } + } + + isa1200_setup(haptic->client); + return 0; +} +#else +#define isa1200_suspend NULL +#define isa1200_resume NULL +#endif + +static int isa1200_open(struct input_dev *dev) +{ + struct isa1200_chip *haptic = input_get_drvdata(dev); + int rc; + + /* device setup */ + if (haptic->pdata->dev_setup) { + rc = haptic->pdata->dev_setup(true); + if (rc < 0) { + pr_err("setup failed!\n"); + return rc; + } + } + + /* power on */ + if (haptic->pdata->power_on) { + rc = haptic->pdata->power_on(true); + if (rc < 0) { + pr_err("power failed\n"); + goto err_setup; + } + } + + /* request gpio */ + rc = gpio_is_valid(haptic->pdata->hap_en_gpio); + if (rc) { + rc = gpio_request(haptic->pdata->hap_en_gpio, "haptic_gpio"); + if (rc) { + pr_err("gpio %d request failed\n", + haptic->pdata->hap_en_gpio); + goto err_power_on; + } + } else { + pr_err("Invalid gpio %d\n", + haptic->pdata->hap_en_gpio); + goto err_power_on; + } + + rc = gpio_direction_output(haptic->pdata->hap_en_gpio, 0); + if (rc) { + pr_err("gpio %d set direction failed\n", + haptic->pdata->hap_en_gpio); + goto err_gpio_free; + } + + /* setup registers */ + rc = isa1200_setup(haptic->client); + if (rc < 0) { + pr_err("setup fail %d\n", rc); + goto err_gpio_free; + } + + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { + haptic->pwm = pwm_request(haptic->pdata->pwm_ch_id, + haptic->client->driver->id_table->name); + if (IS_ERR(haptic->pwm)) { + pr_err("pwm request failed\n"); + rc = PTR_ERR(haptic->pwm); + goto err_reset_hctrl0; + } + } + + /* init workqeueue */ + INIT_WORK(&haptic->work, isa1200_worker); + return 0; + +err_reset_hctrl0: + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0, + HCTRL0_RESET); +err_gpio_free: + gpio_free(haptic->pdata->hap_en_gpio); +err_power_on: + if (haptic->pdata->power_on) + haptic->pdata->power_on(0); +err_setup: + if (haptic->pdata->dev_setup) + haptic->pdata->dev_setup(false); + + return rc; +} + +static void isa1200_close(struct input_dev *dev) +{ + struct isa1200_chip *haptic = input_get_drvdata(dev); + + /* turn-off current vibration */ + isa1200_vib_set(haptic, 0); + + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) + pwm_free(haptic->pwm); + + gpio_free(haptic->pdata->hap_en_gpio); + + /* reset hardware registers */ + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL0, + HCTRL0_RESET); + i2c_smbus_write_byte_data(haptic->client, ISA1200_HCTRL1, + HCTRL1_RESET); + + if (haptic->pdata->dev_setup) + haptic->pdata->dev_setup(false); + + /* power-off the chip */ + if (haptic->pdata->power_on) + haptic->pdata->power_on(0); +} + +static int __devinit isa1200_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isa1200_chip *haptic; + int rc; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + pr_err("i2c is not supported\n"); + return -EIO; + } + + if (!client->dev.platform_data) { + pr_err("pdata is not avaiable\n"); + return -EINVAL; + } + + haptic = kzalloc(sizeof(struct isa1200_chip), GFP_KERNEL); + if (!haptic) { + pr_err("no memory\n"); + return -ENOMEM; + } + + haptic->pdata = client->dev.platform_data; + haptic->client = client; + + i2c_set_clientdata(client, haptic); + + haptic->input_device = input_allocate_device(); + if (!haptic->input_device) { + pr_err("input device alloc failed\n"); + rc = -ENOMEM; + goto err_mem_alloc; + } + + input_set_drvdata(haptic->input_device, haptic); + haptic->input_device->name = haptic->pdata->name ? : + "isa1200-ff-memless"; + + haptic->input_device->dev.parent = &client->dev; + + input_set_capability(haptic->input_device, EV_FF, FF_RUMBLE); + + haptic->input_device->open = isa1200_open; + haptic->input_device->close = isa1200_close; + + rc = input_ff_create_memless(haptic->input_device, NULL, + isa1200_play_effect); + if (rc < 0) { + pr_err("unable to register with ff\n"); + goto err_free_dev; + } + + rc = input_register_device(haptic->input_device); + if (rc < 0) { + pr_err("unable to register input device\n"); + goto err_ff_destroy; + } + + return 0; + +err_ff_destroy: + input_ff_destroy(haptic->input_device); +err_free_dev: + input_free_device(haptic->input_device); +err_mem_alloc: + kfree(haptic); + return rc; +} + +static int __devexit isa1200_remove(struct i2c_client *client) +{ + struct isa1200_chip *haptic = i2c_get_clientdata(client); + + input_unregister_device(haptic->input_device); + kfree(haptic); + + return 0; +} + +static const struct i2c_device_id isa1200_id_table[] = { + {"isa1200_1", 0}, + { }, +}; +MODULE_DEVICE_TABLE(i2c, isa1200_id_table); + +static const struct dev_pm_ops isa1200_pm_ops = { + .suspend = isa1200_suspend, + .resume = isa1200_resume, +}; + +static struct i2c_driver isa1200_driver = { + .driver = { + .name = "isa1200-ff-memless", + .owner = THIS_MODULE, + .pm = &isa1200_pm_ops, + }, + .probe = isa1200_probe, + .remove = __devexit_p(isa1200_remove), + .id_table = isa1200_id_table, +}; + +static int __init isa1200_init(void) +{ + return i2c_add_driver(&isa1200_driver); +} +module_init(isa1200_init); + +static void __exit isa1200_exit(void) +{ + i2c_del_driver(&isa1200_driver); +} +module_exit(isa1200_exit); + +MODULE_DESCRIPTION("isa1200 based vibrator chip driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Kyungmin Park "); diff --git a/drivers/input/misc/pmic8058-othc.c b/drivers/input/misc/pmic8058-othc.c new file mode 100644 index 00000000000..c6be1198559 --- /dev/null +++ b/drivers/input/misc/pmic8058-othc.c @@ -0,0 +1,1199 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define PM8058_OTHC_LOW_CURR_MASK 0xF0 +#define PM8058_OTHC_HIGH_CURR_MASK 0x0F +#define PM8058_OTHC_EN_SIG_MASK 0x3F +#define PM8058_OTHC_HYST_PREDIV_MASK 0xC7 +#define PM8058_OTHC_CLK_PREDIV_MASK 0xF8 +#define PM8058_OTHC_HYST_CLK_MASK 0x0F +#define PM8058_OTHC_PERIOD_CLK_MASK 0xF0 + +#define PM8058_OTHC_LOW_CURR_SHIFT 0x4 +#define PM8058_OTHC_EN_SIG_SHIFT 0x6 +#define PM8058_OTHC_HYST_PREDIV_SHIFT 0x3 +#define PM8058_OTHC_HYST_CLK_SHIFT 0x4 + +#define OTHC_GPIO_MAX_LEN 25 + +struct pm8058_othc { + bool othc_sw_state; + bool switch_reject; + bool othc_support_n_switch; + bool accessory_support; + bool accessories_adc_support; + int othc_base; + int othc_irq_sw; + int othc_irq_ir; + int othc_ir_state; + int num_accessories; + int curr_accessory_code; + int curr_accessory; + int video_out_gpio; + u32 sw_key_code; + u32 accessories_adc_channel; + int ir_gpio; + unsigned long switch_debounce_ms; + unsigned long detection_delay_ms; + void *adc_handle; + void *accessory_adc_handle; + spinlock_t lock; + struct regulator *othc_vreg; + struct input_dev *othc_ipd; + struct switch_dev othc_sdev; + struct pmic8058_othc_config_pdata *othc_pdata; + struct othc_accessory_info *accessory_info; + struct hrtimer timer; + struct othc_n_switch_config *switch_config; + struct pm8058_chip *pm_chip; + struct work_struct switch_work; + struct delayed_work detect_work; + struct delayed_work hs_work; +}; + +static struct pm8058_othc *config[OTHC_MICBIAS_MAX]; + +static void hs_worker(struct work_struct *work) +{ + int rc; + struct pm8058_othc *dd = + container_of(work, struct pm8058_othc, hs_work.work); + + rc = gpio_get_value_cansleep(dd->ir_gpio); + if (rc < 0) { + pr_err("Unable to read IR GPIO\n"); + enable_irq(dd->othc_irq_ir); + return; + } + + dd->othc_ir_state = !rc; + schedule_delayed_work(&dd->detect_work, + msecs_to_jiffies(dd->detection_delay_ms)); +} + +static irqreturn_t ir_gpio_irq(int irq, void *dev_id) +{ + unsigned long flags; + struct pm8058_othc *dd = dev_id; + + spin_lock_irqsave(&dd->lock, flags); + /* Enable the switch reject flag */ + dd->switch_reject = true; + spin_unlock_irqrestore(&dd->lock, flags); + + /* Start the HR timer if one is not active */ + if (hrtimer_active(&dd->timer)) + hrtimer_cancel(&dd->timer); + + hrtimer_start(&dd->timer, + ktime_set((dd->switch_debounce_ms / 1000), + (dd->switch_debounce_ms % 1000) * 1000000), HRTIMER_MODE_REL); + + /* disable irq, this gets enabled in the workqueue */ + disable_irq_nosync(dd->othc_irq_ir); + schedule_delayed_work(&dd->hs_work, 0); + + return IRQ_HANDLED; +} +/* + * The API pm8058_micbias_enable() allows to configure + * the MIC_BIAS. Only the lines which are not used for + * headset detection can be configured using this API. + * The API returns an error code if it fails to configure + * the specified MIC_BIAS line, else it returns 0. + */ +int pm8058_micbias_enable(enum othc_micbias micbias, + enum othc_micbias_enable enable) +{ + int rc; + u8 reg; + struct pm8058_othc *dd = config[micbias]; + + if (dd == NULL) { + pr_err("MIC_BIAS not registered, cannot enable\n"); + return -ENODEV; + } + + if (dd->othc_pdata->micbias_capability != OTHC_MICBIAS) { + pr_err("MIC_BIAS enable capability not supported\n"); + return -EINVAL; + } + + rc = pm8058_read(dd->pm_chip, dd->othc_base + 1, ®, 1); + if (rc < 0) { + pr_err("PM8058 read failed\n"); + return rc; + } + + reg &= PM8058_OTHC_EN_SIG_MASK; + reg |= (enable << PM8058_OTHC_EN_SIG_SHIFT); + + rc = pm8058_write(dd->pm_chip, dd->othc_base + 1, ®, 1); + if (rc < 0) { + pr_err("PM8058 write failed\n"); + return rc; + } + + return rc; +} +EXPORT_SYMBOL(pm8058_micbias_enable); + +int pm8058_othc_svideo_enable(enum othc_micbias micbias, bool enable) +{ + struct pm8058_othc *dd = config[micbias]; + + if (dd == NULL) { + pr_err("MIC_BIAS not registered, cannot enable\n"); + return -ENODEV; + } + + if (dd->othc_pdata->micbias_capability != OTHC_MICBIAS_HSED) { + pr_err("MIC_BIAS enable capability not supported\n"); + return -EINVAL; + } + + if (dd->accessories_adc_support) { + /* GPIO state for MIC_IN = 0, SVIDEO = 1 */ + gpio_set_value_cansleep(dd->video_out_gpio, !!enable); + if (enable) { + pr_debug("Enable the video path\n"); + switch_set_state(&dd->othc_sdev, dd->curr_accessory); + input_report_switch(dd->othc_ipd, + dd->curr_accessory_code, 1); + input_sync(dd->othc_ipd); + } else { + pr_debug("Disable the video path\n"); + switch_set_state(&dd->othc_sdev, 0); + input_report_switch(dd->othc_ipd, + dd->curr_accessory_code, 0); + input_sync(dd->othc_ipd); + } + } + + return 0; +} +EXPORT_SYMBOL(pm8058_othc_svideo_enable); + +#ifdef CONFIG_PM +static int pm8058_othc_suspend(struct device *dev) +{ + int rc = 0; + struct pm8058_othc *dd = dev_get_drvdata(dev); + + if (dd->othc_pdata->micbias_capability == OTHC_MICBIAS_HSED) { + if (device_may_wakeup(dev)) { + enable_irq_wake(dd->othc_irq_sw); + enable_irq_wake(dd->othc_irq_ir); + } + } + + if (!device_may_wakeup(dev)) { + rc = regulator_disable(dd->othc_vreg); + if (rc) + pr_err("othc micbais power off failed\n"); + } + + return rc; +} + +static int pm8058_othc_resume(struct device *dev) +{ + int rc = 0; + struct pm8058_othc *dd = dev_get_drvdata(dev); + + if (dd->othc_pdata->micbias_capability == OTHC_MICBIAS_HSED) { + if (device_may_wakeup(dev)) { + disable_irq_wake(dd->othc_irq_sw); + disable_irq_wake(dd->othc_irq_ir); + } + } + + if (!device_may_wakeup(dev)) { + rc = regulator_enable(dd->othc_vreg); + if (rc) + pr_err("othc micbais power on failed\n"); + } + + return rc; +} + +static struct dev_pm_ops pm8058_othc_pm_ops = { + .suspend = pm8058_othc_suspend, + .resume = pm8058_othc_resume, +}; +#endif + +static int __devexit pm8058_othc_remove(struct platform_device *pd) +{ + struct pm8058_othc *dd = platform_get_drvdata(pd); + + pm_runtime_set_suspended(&pd->dev); + pm_runtime_disable(&pd->dev); + + if (dd->othc_pdata->micbias_capability == OTHC_MICBIAS_HSED) { + device_init_wakeup(&pd->dev, 0); + if (dd->othc_support_n_switch == true) { + adc_channel_close(dd->adc_handle); + cancel_work_sync(&dd->switch_work); + } + + if (dd->accessory_support == true) { + int i; + for (i = 0; i < dd->num_accessories; i++) { + if (dd->accessory_info[i].detect_flags & + OTHC_GPIO_DETECT) + gpio_free(dd->accessory_info[i].gpio); + } + } + cancel_delayed_work_sync(&dd->detect_work); + cancel_delayed_work_sync(&dd->hs_work); + free_irq(dd->othc_irq_sw, dd); + free_irq(dd->othc_irq_ir, dd); + if (dd->ir_gpio != -1) + gpio_free(dd->ir_gpio); + input_unregister_device(dd->othc_ipd); + } + regulator_disable(dd->othc_vreg); + regulator_put(dd->othc_vreg); + + kfree(dd); + + return 0; +} + +static enum hrtimer_restart pm8058_othc_timer(struct hrtimer *timer) +{ + unsigned long flags; + struct pm8058_othc *dd = container_of(timer, + struct pm8058_othc, timer); + + spin_lock_irqsave(&dd->lock, flags); + dd->switch_reject = false; + spin_unlock_irqrestore(&dd->lock, flags); + + return HRTIMER_NORESTART; +} + +static void othc_report_switch(struct pm8058_othc *dd, u32 res) +{ + u8 i; + struct othc_switch_info *sw_info = dd->switch_config->switch_info; + + for (i = 0; i < dd->switch_config->num_keys; i++) { + if (res >= sw_info[i].min_adc_threshold && + res <= sw_info[i].max_adc_threshold) { + dd->othc_sw_state = true; + dd->sw_key_code = sw_info[i].key_code; + input_report_key(dd->othc_ipd, sw_info[i].key_code, 1); + input_sync(dd->othc_ipd); + return; + } + } + + /* + * If the switch is not present in a specified ADC range + * report a default switch press. + */ + if (dd->switch_config->default_sw_en) { + dd->othc_sw_state = true; + dd->sw_key_code = + sw_info[dd->switch_config->default_sw_idx].key_code; + input_report_key(dd->othc_ipd, dd->sw_key_code, 1); + input_sync(dd->othc_ipd); + } +} + +static void switch_work_f(struct work_struct *work) +{ + int rc, i; + u32 res = 0; + struct adc_chan_result adc_result; + struct pm8058_othc *dd = + container_of(work, struct pm8058_othc, switch_work); + DECLARE_COMPLETION_ONSTACK(adc_wait); + u8 num_adc_samples = dd->switch_config->num_adc_samples; + + /* sleep for settling time */ + msleep(dd->switch_config->voltage_settling_time_ms); + + for (i = 0; i < num_adc_samples; i++) { + rc = adc_channel_request_conv(dd->adc_handle, &adc_wait); + if (rc) { + pr_err("adc_channel_request_conv failed\n"); + goto bail_out; + } + rc = wait_for_completion_interruptible(&adc_wait); + if (rc) { + pr_err("wait_for_completion_interruptible failed\n"); + goto bail_out; + } + rc = adc_channel_read_result(dd->adc_handle, &adc_result); + if (rc) { + pr_err("adc_channel_read_result failed\n"); + goto bail_out; + } + res += adc_result.physical; + } +bail_out: + if (i == num_adc_samples && num_adc_samples != 0) { + res /= num_adc_samples; + othc_report_switch(dd, res); + } else + pr_err("Insufficient ADC samples\n"); + + enable_irq(dd->othc_irq_sw); +} + +static int accessory_adc_detect(struct pm8058_othc *dd, int accessory) +{ + int rc; + u32 res; + struct adc_chan_result accessory_adc_result; + DECLARE_COMPLETION_ONSTACK(accessory_adc_wait); + + rc = adc_channel_request_conv(dd->accessory_adc_handle, + &accessory_adc_wait); + if (rc) { + pr_err("adc_channel_request_conv failed\n"); + goto adc_failed; + } + rc = wait_for_completion_interruptible(&accessory_adc_wait); + if (rc) { + pr_err("wait_for_completion_interruptible failed\n"); + goto adc_failed; + } + rc = adc_channel_read_result(dd->accessory_adc_handle, + &accessory_adc_result); + if (rc) { + pr_err("adc_channel_read_result failed\n"); + goto adc_failed; + } + + res = accessory_adc_result.physical; + + if (res >= dd->accessory_info[accessory].adc_thres.min_threshold && + res <= dd->accessory_info[accessory].adc_thres.max_threshold) { + pr_debug("Accessory on ADC detected!, ADC Value = %u\n", res); + return 1; + } + +adc_failed: + return 0; +} + + +static int pm8058_accessory_report(struct pm8058_othc *dd, int status) +{ + int i, rc, detected = 0; + u8 micbias_status, switch_status; + + if (dd->accessory_support == false) { + /* Report default headset */ + switch_set_state(&dd->othc_sdev, !!status); + input_report_switch(dd->othc_ipd, SW_HEADPHONE_INSERT, + !!status); + input_sync(dd->othc_ipd); + return 0; + } + + /* For accessory */ + if (dd->accessory_support == true && status == 0) { + /* Report removal of the accessory. */ + + /* + * If the current accessory is video cable, reject the removal + * interrupt. + */ + pr_info("Accessory [%d] removed\n", dd->curr_accessory); + if (dd->curr_accessory == OTHC_SVIDEO_OUT) + return 0; + + switch_set_state(&dd->othc_sdev, 0); + input_report_switch(dd->othc_ipd, dd->curr_accessory_code, 0); + input_sync(dd->othc_ipd); + return 0; + } + + if (dd->ir_gpio < 0) { + /* Check the MIC_BIAS status */ + rc = pm8058_irq_get_rt_status(dd->pm_chip, dd->othc_irq_ir); + if (rc < 0) { + pr_err("Unable to read IR status from PMIC\n"); + goto fail_ir_accessory; + } + micbias_status = !!rc; + } else { + rc = gpio_get_value_cansleep(dd->ir_gpio); + if (rc < 0) { + pr_err("Unable to read IR status from GPIO\n"); + goto fail_ir_accessory; + } + micbias_status = !rc; + } + + /* Check the switch status */ + rc = pm8058_irq_get_rt_status(dd->pm_chip, dd->othc_irq_sw); + if (rc < 0) { + pr_err("Unable to read SWITCH status\n"); + goto fail_ir_accessory; + } + switch_status = !!rc; + + /* Loop through to check which accessory is connected */ + for (i = 0; i < dd->num_accessories; i++) { + detected = 0; + if (dd->accessory_info[i].enabled == false) + continue; + + if (dd->accessory_info[i].detect_flags & OTHC_MICBIAS_DETECT) { + if (micbias_status) + detected = 1; + else + continue; + } + if (dd->accessory_info[i].detect_flags & OTHC_SWITCH_DETECT) { + if (switch_status) + detected = 1; + else + continue; + } + if (dd->accessory_info[i].detect_flags & OTHC_GPIO_DETECT) { + rc = gpio_get_value_cansleep( + dd->accessory_info[i].gpio); + if (rc < 0) + continue; + + if (rc ^ dd->accessory_info[i].active_low) + detected = 1; + else + continue; + } + if (dd->accessory_info[i].detect_flags & OTHC_ADC_DETECT) + detected = accessory_adc_detect(dd, i); + + if (detected) + break; + } + + if (detected) { + dd->curr_accessory = dd->accessory_info[i].accessory; + dd->curr_accessory_code = dd->accessory_info[i].key_code; + + /* if Video out cable detected enable the video path*/ + if (dd->curr_accessory == OTHC_SVIDEO_OUT) { + pm8058_othc_svideo_enable( + dd->othc_pdata->micbias_select, true); + + } else { + switch_set_state(&dd->othc_sdev, dd->curr_accessory); + input_report_switch(dd->othc_ipd, + dd->curr_accessory_code, 1); + input_sync(dd->othc_ipd); + } + pr_info("Accessory [%d] inserted\n", dd->curr_accessory); + } else + pr_info("Unable to detect accessory. False interrupt!\n"); + + return 0; + +fail_ir_accessory: + return rc; +} + +static void detect_work_f(struct work_struct *work) +{ + int rc; + struct pm8058_othc *dd = + container_of(work, struct pm8058_othc, detect_work.work); + + if (dd->othc_ir_state) { + /* inserted */ + rc = pm8058_accessory_report(dd, 1); + if (rc) + pr_err("Accessory could not be detected\n"); + } else { + /* removed */ + rc = pm8058_accessory_report(dd, 0); + if (rc) + pr_err("Accessory could not be detected\n"); + /* Clear existing switch state */ + dd->othc_sw_state = false; + } + enable_irq(dd->othc_irq_ir); +} + +/* + * The pm8058_no_sw detects the switch press and release operation. + * The odd number call is press and even number call is release. + * The current state of the button is maintained in othc_sw_state variable. + * This isr gets called only for NO type headsets. + */ +static irqreturn_t pm8058_no_sw(int irq, void *dev_id) +{ + int level; + struct pm8058_othc *dd = dev_id; + unsigned long flags; + + /* Check if headset has been inserted, else return */ + if (!dd->othc_ir_state) + return IRQ_HANDLED; + + spin_lock_irqsave(&dd->lock, flags); + if (dd->switch_reject == true) { + pr_debug("Rejected switch interrupt\n"); + spin_unlock_irqrestore(&dd->lock, flags); + return IRQ_HANDLED; + } + spin_unlock_irqrestore(&dd->lock, flags); + + level = pm8058_irq_get_rt_status(dd->pm_chip, dd->othc_irq_sw); + if (level < 0) { + pr_err("Unable to read IRQ status register\n"); + return IRQ_HANDLED; + } + + if (dd->othc_support_n_switch == true) { + if (level == 0) { + dd->othc_sw_state = false; + input_report_key(dd->othc_ipd, dd->sw_key_code, 0); + input_sync(dd->othc_ipd); + } else { + disable_irq_nosync(dd->othc_irq_sw); + schedule_work(&dd->switch_work); + } + return IRQ_HANDLED; + } + /* + * It is necessary to check the software state and the hardware state + * to make sure that the residual interrupt after the debounce time does + * not disturb the software state machine. + */ + if (level == 1 && dd->othc_sw_state == false) { + /* Switch has been pressed */ + dd->othc_sw_state = true; + input_report_key(dd->othc_ipd, KEY_MEDIA, 1); + } else if (level == 0 && dd->othc_sw_state == true) { + /* Switch has been released */ + dd->othc_sw_state = false; + input_report_key(dd->othc_ipd, KEY_MEDIA, 0); + } + input_sync(dd->othc_ipd); + + return IRQ_HANDLED; +} + +/* + * The pm8058_nc_ir detects insert / remove of the headset (for NO), + * The current state of the headset is maintained in othc_ir_state variable. + * Due to a hardware bug, false switch interrupts are seen during headset + * insert. This is handled in the software by rejecting the switch interrupts + * for a small period of time after the headset has been inserted. + */ +static irqreturn_t pm8058_nc_ir(int irq, void *dev_id) +{ + unsigned long flags, rc; + struct pm8058_othc *dd = dev_id; + + spin_lock_irqsave(&dd->lock, flags); + /* Enable the switch reject flag */ + dd->switch_reject = true; + spin_unlock_irqrestore(&dd->lock, flags); + + /* Start the HR timer if one is not active */ + if (hrtimer_active(&dd->timer)) + hrtimer_cancel(&dd->timer); + + hrtimer_start(&dd->timer, + ktime_set((dd->switch_debounce_ms / 1000), + (dd->switch_debounce_ms % 1000) * 1000000), HRTIMER_MODE_REL); + + /* disable irq, this gets enabled in the workqueue */ + disable_irq_nosync(dd->othc_irq_ir); + + /* Check the MIC_BIAS status, to check if inserted or removed */ + rc = pm8058_irq_get_rt_status(dd->pm_chip, dd->othc_irq_ir); + if (rc < 0) { + pr_err("Unable to read IR status\n"); + goto fail_ir; + } + + dd->othc_ir_state = rc; + schedule_delayed_work(&dd->detect_work, + msecs_to_jiffies(dd->detection_delay_ms)); + +fail_ir: + return IRQ_HANDLED; +} + +static int pm8058_configure_micbias(struct pm8058_othc *dd) +{ + int rc; + u8 reg, value; + u32 value1; + u16 base_addr = dd->othc_base; + struct hsed_bias_config *hsed_config = + dd->othc_pdata->hsed_config->hsed_bias_config; + + /* Intialize the OTHC module */ + /* Control Register 1*/ + rc = pm8058_read(dd->pm_chip, base_addr, ®, 1); + if (rc < 0) { + pr_err("PM8058 read failed\n"); + return rc; + } + + /* set iDAC high current threshold */ + value = (hsed_config->othc_highcurr_thresh_uA / 100) - 2; + reg = (reg & PM8058_OTHC_HIGH_CURR_MASK) | value; + + rc = pm8058_write(dd->pm_chip, base_addr, ®, 1); + if (rc < 0) { + pr_err("PM8058 read failed\n"); + return rc; + } + + /* Control register 2*/ + rc = pm8058_read(dd->pm_chip, base_addr + 1, ®, 1); + if (rc < 0) { + pr_err("PM8058 read failed\n"); + return rc; + } + + value = dd->othc_pdata->micbias_enable; + reg &= PM8058_OTHC_EN_SIG_MASK; + reg |= (value << PM8058_OTHC_EN_SIG_SHIFT); + + value = 0; + value1 = (hsed_config->othc_hyst_prediv_us << 10) / USEC_PER_SEC; + while (value1 != 0) { + value1 = value1 >> 1; + value++; + } + if (value > 7) { + pr_err("Invalid input argument - othc_hyst_prediv_us\n"); + return -EINVAL; + } + reg &= PM8058_OTHC_HYST_PREDIV_MASK; + reg |= (value << PM8058_OTHC_HYST_PREDIV_SHIFT); + + value = 0; + value1 = (hsed_config->othc_period_clkdiv_us << 10) / USEC_PER_SEC; + while (value1 != 1) { + value1 = value1 >> 1; + value++; + } + if (value > 8) { + pr_err("Invalid input argument - othc_period_clkdiv_us\n"); + return -EINVAL; + } + reg = (reg & PM8058_OTHC_CLK_PREDIV_MASK) | (value - 1); + + rc = pm8058_write(dd->pm_chip, base_addr + 1, ®, 1); + if (rc < 0) { + pr_err("PM8058 read failed\n"); + return rc; + } + + /* Control register 3 */ + rc = pm8058_read(dd->pm_chip, base_addr + 2 , ®, 1); + if (rc < 0) { + pr_err("PM8058 read failed\n"); + return rc; + } + + value = hsed_config->othc_hyst_clk_us / + hsed_config->othc_hyst_prediv_us; + if (value > 15) { + pr_err("Invalid input argument - othc_hyst_prediv_us\n"); + return -EINVAL; + } + reg &= PM8058_OTHC_HYST_CLK_MASK; + reg |= value << PM8058_OTHC_HYST_CLK_SHIFT; + + value = hsed_config->othc_period_clk_us / + hsed_config->othc_period_clkdiv_us; + if (value > 15) { + pr_err("Invalid input argument - othc_hyst_prediv_us\n"); + return -EINVAL; + } + reg = (reg & PM8058_OTHC_PERIOD_CLK_MASK) | value; + + rc = pm8058_write(dd->pm_chip, base_addr + 2, ®, 1); + if (rc < 0) { + pr_err("PM8058 read failed\n"); + return rc; + } + + return 0; +} + +static ssize_t othc_headset_print_name(struct switch_dev *sdev, char *buf) +{ + switch (switch_get_state(sdev)) { + case OTHC_NO_DEVICE: + return sprintf(buf, "No Device\n"); + case OTHC_HEADSET: + case OTHC_HEADPHONE: + case OTHC_MICROPHONE: + case OTHC_ANC_HEADSET: + case OTHC_ANC_HEADPHONE: + case OTHC_ANC_MICROPHONE: + return sprintf(buf, "Headset\n"); + } + return -EINVAL; +} + +static int pm8058_configure_switch(struct pm8058_othc *dd) +{ + int rc, i; + + if (dd->othc_support_n_switch == true) { + /* n-switch support */ + rc = adc_channel_open(dd->switch_config->adc_channel, + &dd->adc_handle); + if (rc) { + pr_err("Unable to open ADC channel\n"); + return -ENODEV; + } + + for (i = 0; i < dd->switch_config->num_keys; i++) { + input_set_capability(dd->othc_ipd, EV_KEY, + dd->switch_config->switch_info[i].key_code); + } + } else /* Only single switch supported */ + input_set_capability(dd->othc_ipd, EV_KEY, KEY_MEDIA); + + return 0; +} + +static int +pm8058_configure_accessory(struct pm8058_othc *dd) +{ + int i, rc; + char name[OTHC_GPIO_MAX_LEN]; + + /* + * Not bailing out if the gpio_* configure calls fail. This is required + * as multiple accessories are detected by the same gpio. + */ + for (i = 0; i < dd->num_accessories; i++) { + if (dd->accessory_info[i].enabled == false) + continue; + if (dd->accessory_info[i].detect_flags & OTHC_GPIO_DETECT) { + snprintf(name, OTHC_GPIO_MAX_LEN, "%s%d", + "othc_acc_gpio_", i); + rc = gpio_request(dd->accessory_info[i].gpio, name); + if (rc) { + pr_debug("Unable to request GPIO [%d]\n", + dd->accessory_info[i].gpio); + continue; + } + rc = gpio_direction_input(dd->accessory_info[i].gpio); + if (rc) { + pr_debug("Unable to set-direction GPIO [%d]\n", + dd->accessory_info[i].gpio); + gpio_free(dd->accessory_info[i].gpio); + continue; + } + } + input_set_capability(dd->othc_ipd, EV_SW, + dd->accessory_info[i].key_code); + } + + if (dd->accessories_adc_support) { + /* + * Check if 3 switch is supported. If both are using the same + * ADC channel, the same handle can be used. + */ + if (dd->othc_support_n_switch) { + if (dd->adc_handle != NULL && + (dd->accessories_adc_channel == + dd->switch_config->adc_channel)) + dd->accessory_adc_handle = dd->adc_handle; + } else { + rc = adc_channel_open(dd->accessories_adc_channel, + &dd->accessory_adc_handle); + if (rc) { + pr_err("Unable to open ADC channel\n"); + rc = -ENODEV; + goto accessory_adc_fail; + } + } + if (dd->video_out_gpio != 0) { + rc = gpio_request(dd->video_out_gpio, "vout_enable"); + if (rc < 0) { + pr_err("request VOUT gpio failed (%d)\n", rc); + goto accessory_adc_fail; + } + rc = gpio_direction_output(dd->video_out_gpio, 0); + if (rc < 0) { + pr_err("direction_out failed (%d)\n", rc); + goto accessory_adc_fail; + } + } + + } + + return 0; + +accessory_adc_fail: + for (i = 0; i < dd->num_accessories; i++) { + if (dd->accessory_info[i].enabled == false) + continue; + gpio_free(dd->accessory_info[i].gpio); + } + return rc; +} + +static int +othc_configure_hsed(struct pm8058_othc *dd, struct platform_device *pd) +{ + int rc; + struct input_dev *ipd; + struct pmic8058_othc_config_pdata *pdata = pd->dev.platform_data; + struct othc_hsed_config *hsed_config = pdata->hsed_config; + + dd->othc_sdev.name = "h2w"; + dd->othc_sdev.print_name = othc_headset_print_name; + + rc = switch_dev_register(&dd->othc_sdev); + if (rc) { + pr_err("Unable to register switch device\n"); + return rc; + } + + ipd = input_allocate_device(); + if (ipd == NULL) { + pr_err("Unable to allocate memory\n"); + rc = -ENOMEM; + goto fail_input_alloc; + } + + /* Get the IRQ for Headset Insert-remove and Switch-press */ + dd->othc_irq_sw = platform_get_irq(pd, 0); + dd->othc_irq_ir = platform_get_irq(pd, 1); + if (dd->othc_irq_ir < 0 || dd->othc_irq_sw < 0) { + pr_err("othc resource:IRQs absent\n"); + rc = -ENXIO; + goto fail_micbias_config; + } + + if (pdata->hsed_name != NULL) + ipd->name = pdata->hsed_name; + else + ipd->name = "pmic8058_othc"; + + ipd->phys = "pmic8058_othc/input0"; + ipd->dev.parent = &pd->dev; + + dd->othc_ipd = ipd; + dd->ir_gpio = hsed_config->ir_gpio; + dd->othc_sw_state = false; + dd->switch_debounce_ms = hsed_config->switch_debounce_ms; + dd->othc_support_n_switch = hsed_config->othc_support_n_switch; + dd->accessory_support = pdata->hsed_config->accessories_support; + dd->detection_delay_ms = pdata->hsed_config->detection_delay_ms; + + if (dd->othc_support_n_switch == true) + dd->switch_config = hsed_config->switch_config; + + if (dd->accessory_support == true) { + dd->accessory_info = pdata->hsed_config->accessories; + dd->num_accessories = pdata->hsed_config->othc_num_accessories; + dd->accessories_adc_support = + pdata->hsed_config->accessories_adc_support; + dd->accessories_adc_channel = + pdata->hsed_config->accessories_adc_channel; + dd->video_out_gpio = pdata->hsed_config->video_out_gpio; + } + + /* Configure the MIC_BIAS line for headset detection */ + rc = pm8058_configure_micbias(dd); + if (rc < 0) + goto fail_micbias_config; + + /* Configure for the switch events */ + rc = pm8058_configure_switch(dd); + if (rc < 0) + goto fail_micbias_config; + + /* Configure the accessory */ + if (dd->accessory_support == true) { + rc = pm8058_configure_accessory(dd); + if (rc < 0) + goto fail_micbias_config; + } + + input_set_drvdata(ipd, dd); + spin_lock_init(&dd->lock); + + rc = input_register_device(ipd); + if (rc) { + pr_err("Unable to register OTHC device\n"); + goto fail_micbias_config; + } + + hrtimer_init(&dd->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + dd->timer.function = pm8058_othc_timer; + + /* Request the HEADSET IR interrupt */ + if (dd->ir_gpio < 0) { + rc = request_threaded_irq(dd->othc_irq_ir, NULL, pm8058_nc_ir, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_DISABLED, + "pm8058_othc_ir", dd); + if (rc < 0) { + pr_err("Unable to request pm8058_othc_ir IRQ\n"); + goto fail_ir_irq; + } + } else { + rc = gpio_request(dd->ir_gpio, "othc_ir_gpio"); + if (rc) { + pr_err("Unable to request IR GPIO\n"); + goto fail_ir_gpio_req; + } + rc = gpio_direction_input(dd->ir_gpio); + if (rc) { + pr_err("GPIO %d set_direction failed\n", dd->ir_gpio); + goto fail_ir_irq; + } + dd->othc_irq_ir = gpio_to_irq(dd->ir_gpio); + rc = request_any_context_irq(dd->othc_irq_ir, ir_gpio_irq, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "othc_gpio_ir_irq", dd); + if (rc < 0) { + pr_err("could not request hs irq err=%d\n", rc); + goto fail_ir_irq; + } + } + /* Request the SWITCH press/release interrupt */ + rc = request_threaded_irq(dd->othc_irq_sw, NULL, pm8058_no_sw, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_DISABLED, + "pm8058_othc_sw", dd); + if (rc < 0) { + pr_err("Unable to request pm8058_othc_sw IRQ\n"); + goto fail_sw_irq; + } + + /* Check if the accessory is already inserted during boot up */ + if (dd->ir_gpio < 0) { + rc = pm8058_irq_get_rt_status(dd->pm_chip, dd->othc_irq_ir); + if (rc < 0) { + pr_err("Unable to get accessory status at boot\n"); + goto fail_ir_status; + } + } else { + rc = gpio_get_value_cansleep(dd->ir_gpio); + if (rc < 0) { + pr_err("Unable to get accessory status at boot\n"); + goto fail_ir_status; + } + rc = !rc; + } + if (rc) { + pr_debug("Accessory inserted during boot up\n"); + /* process the data and report the inserted accessory */ + rc = pm8058_accessory_report(dd, 1); + if (rc) + pr_debug("Unabele to detect accessory at boot up\n"); + } + + device_init_wakeup(&pd->dev, + hsed_config->hsed_bias_config->othc_wakeup); + + INIT_DELAYED_WORK(&dd->detect_work, detect_work_f); + + INIT_DELAYED_WORK(&dd->hs_work, hs_worker); + + if (dd->othc_support_n_switch == true) + INIT_WORK(&dd->switch_work, switch_work_f); + + + return 0; + +fail_ir_status: + free_irq(dd->othc_irq_sw, dd); +fail_sw_irq: + free_irq(dd->othc_irq_ir, dd); +fail_ir_irq: + if (dd->ir_gpio != -1) + gpio_free(dd->ir_gpio); +fail_ir_gpio_req: + input_unregister_device(ipd); + dd->othc_ipd = NULL; +fail_micbias_config: + input_free_device(ipd); +fail_input_alloc: + switch_dev_unregister(&dd->othc_sdev); + return rc; +} + +static int __devinit pm8058_othc_probe(struct platform_device *pd) +{ + int rc; + struct pm8058_othc *dd; + struct pm8058_chip *chip; + struct resource *res; + struct pmic8058_othc_config_pdata *pdata = pd->dev.platform_data; + + chip = dev_get_drvdata(pd->dev.parent); + if (chip == NULL) { + pr_err("Invalid driver information\n"); + return -EINVAL; + } + + /* Check PMIC8058 version. A0 version is not supported */ + if (pm8058_rev(chip) == PM_8058_REV_1p0) { + pr_err("PMIC8058 version not supported\n"); + return -ENODEV; + } + + if (pdata == NULL) { + pr_err("Platform data not present\n"); + return -EINVAL; + } + + dd = kzalloc(sizeof(*dd), GFP_KERNEL); + if (dd == NULL) { + pr_err("Unable to allocate memory\n"); + return -ENOMEM; + } + + /* Enable runtime PM ops, start in ACTIVE mode */ + rc = pm_runtime_set_active(&pd->dev); + if (rc < 0) + dev_dbg(&pd->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&pd->dev); + + res = platform_get_resource_byname(pd, IORESOURCE_IO, "othc_base"); + if (res == NULL) { + pr_err("othc resource:Base address absent\n"); + rc = -ENXIO; + goto fail_get_res; + } + + dd->othc_pdata = pdata; + dd->pm_chip = chip; + dd->othc_base = res->start; + if (pdata->micbias_regulator == NULL) { + pr_err("OTHC regulator not specified\n"); + goto fail_get_res; + } + + dd->othc_vreg = regulator_get(NULL, + pdata->micbias_regulator->regulator); + if (IS_ERR(dd->othc_vreg)) { + pr_err("regulator get failed\n"); + rc = PTR_ERR(dd->othc_vreg); + goto fail_get_res; + } + + rc = regulator_set_voltage(dd->othc_vreg, + pdata->micbias_regulator->min_uV, + pdata->micbias_regulator->max_uV); + if (rc) { + pr_err("othc regulator set voltage failed\n"); + goto fail_reg_enable; + } + + rc = regulator_enable(dd->othc_vreg); + if (rc) { + pr_err("othc regulator enable failed\n"); + goto fail_reg_enable; + } + + platform_set_drvdata(pd, dd); + + if (pdata->micbias_capability == OTHC_MICBIAS_HSED) { + /* HSED to be supported on this MICBIAS line */ + if (pdata->hsed_config != NULL) { + rc = othc_configure_hsed(dd, pd); + if (rc < 0) + goto fail_othc_hsed; + } else { + pr_err("HSED config data not present\n"); + rc = -EINVAL; + goto fail_othc_hsed; + } + } + + /* Store the local driver data structure */ + if (dd->othc_pdata->micbias_select < OTHC_MICBIAS_MAX) + config[dd->othc_pdata->micbias_select] = dd; + + pr_debug("Device %s:%d successfully registered\n", + pd->name, pd->id); + return 0; + +fail_othc_hsed: + regulator_disable(dd->othc_vreg); +fail_reg_enable: + regulator_put(dd->othc_vreg); +fail_get_res: + pm_runtime_set_suspended(&pd->dev); + pm_runtime_disable(&pd->dev); + + kfree(dd); + return rc; +} + +static struct platform_driver pm8058_othc_driver = { + .driver = { + .name = "pm8058-othc", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &pm8058_othc_pm_ops, +#endif + }, + .probe = pm8058_othc_probe, + .remove = __devexit_p(pm8058_othc_remove), +}; + +static int __init pm8058_othc_init(void) +{ + return platform_driver_register(&pm8058_othc_driver); +} + +static void __exit pm8058_othc_exit(void) +{ + platform_driver_unregister(&pm8058_othc_driver); +} +/* + * Move to late_initcall, to make sure that the ADC driver registration is + * completed before we open a ADC channel. + */ +late_initcall(pm8058_othc_init); +module_exit(pm8058_othc_exit); + +MODULE_ALIAS("platform:pmic8058_othc"); +MODULE_DESCRIPTION("PMIC 8058 OTHC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/misc/pmic8058-pwrkey.c b/drivers/input/misc/pmic8058-pwrkey.c new file mode 100644 index 00000000000..a9810135642 --- /dev/null +++ b/drivers/input/misc/pmic8058-pwrkey.c @@ -0,0 +1,375 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PON_CNTL_1 0x1C +#define PON_CNTL_PULL_UP BIT(7) +#define PON_CNTL_TRIG_DELAY_MASK (0x7) + +struct pmic8058_pwrkey { + struct input_dev *pwr; + int key_press_irq; + int key_release_irq; + struct pm8058_chip *pm_chip; + struct hrtimer timer; + bool key_pressed; + bool pressed_first; + struct pmic8058_pwrkey_pdata *pdata; + spinlock_t lock; +}; + +static enum hrtimer_restart pmic8058_pwrkey_timer(struct hrtimer *timer) +{ + unsigned long flags; + struct pmic8058_pwrkey *pwrkey = container_of(timer, + struct pmic8058_pwrkey, timer); + + spin_lock_irqsave(&pwrkey->lock, flags); + pwrkey->key_pressed = true; + + input_report_key(pwrkey->pwr, KEY_POWER, 1); + input_sync(pwrkey->pwr); + spin_unlock_irqrestore(&pwrkey->lock, flags); + + return HRTIMER_NORESTART; +} + +static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey) +{ + struct pmic8058_pwrkey *pwrkey = _pwrkey; + struct pmic8058_pwrkey_pdata *pdata = pwrkey->pdata; + unsigned long flags; + + spin_lock_irqsave(&pwrkey->lock, flags); + if (pwrkey->pressed_first) { + /* + * If pressed_first flag is set already then release interrupt + * has occured first. Events are handled in the release IRQ so + * return. + */ + pwrkey->pressed_first = false; + spin_unlock_irqrestore(&pwrkey->lock, flags); + return IRQ_HANDLED; + } else { + pwrkey->pressed_first = true; + /*no pwrkey time duration, means no end key simulation*/ + if (!pwrkey->pdata->pwrkey_time_ms) { + input_report_key(pwrkey->pwr, KEY_POWER, 1); + input_sync(pwrkey->pwr); + spin_unlock_irqrestore(&pwrkey->lock, flags); + return IRQ_HANDLED; + } + + input_report_key(pwrkey->pwr, KEY_END, 1); + input_sync(pwrkey->pwr); + + hrtimer_start(&pwrkey->timer, + ktime_set(pdata->pwrkey_time_ms / 1000, + (pdata->pwrkey_time_ms % 1000) * 1000000), + HRTIMER_MODE_REL); + } + spin_unlock_irqrestore(&pwrkey->lock, flags); + + return IRQ_HANDLED; +} + +static irqreturn_t pwrkey_release_irq(int irq, void *_pwrkey) +{ + struct pmic8058_pwrkey *pwrkey = _pwrkey; + unsigned long flags; + + spin_lock_irqsave(&pwrkey->lock, flags); + if (pwrkey->pressed_first) { + pwrkey->pressed_first = false; + /* no pwrkey time, means no delay in pwr key reporting */ + if (!pwrkey->pdata->pwrkey_time_ms) { + input_report_key(pwrkey->pwr, KEY_POWER, 0); + input_sync(pwrkey->pwr); + spin_unlock_irqrestore(&pwrkey->lock, flags); + return IRQ_HANDLED; + } + + hrtimer_cancel(&pwrkey->timer); + + if (pwrkey->key_pressed) { + pwrkey->key_pressed = false; + input_report_key(pwrkey->pwr, KEY_POWER, 0); + input_sync(pwrkey->pwr); + } + + input_report_key(pwrkey->pwr, KEY_END, 0); + input_sync(pwrkey->pwr); + } else { + /* + * Set this flag true so that in the subsequent interrupt of + * press we can know release interrupt came first + */ + pwrkey->pressed_first = true; + /* no pwrkey time, means no delay in pwr key reporting */ + if (!pwrkey->pdata->pwrkey_time_ms) { + input_report_key(pwrkey->pwr, KEY_POWER, 1); + input_sync(pwrkey->pwr); + input_report_key(pwrkey->pwr, KEY_POWER, 0); + input_sync(pwrkey->pwr); + spin_unlock_irqrestore(&pwrkey->lock, flags); + return IRQ_HANDLED; + } + input_report_key(pwrkey->pwr, KEY_END, 1); + input_sync(pwrkey->pwr); + input_report_key(pwrkey->pwr, KEY_END, 0); + input_sync(pwrkey->pwr); + } + spin_unlock_irqrestore(&pwrkey->lock, flags); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM +static int pmic8058_pwrkey_suspend(struct device *dev) +{ + struct pmic8058_pwrkey *pwrkey = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) { + enable_irq_wake(pwrkey->key_press_irq); + enable_irq_wake(pwrkey->key_release_irq); + } + + return 0; +} + +static int pmic8058_pwrkey_resume(struct device *dev) +{ + struct pmic8058_pwrkey *pwrkey = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) { + disable_irq_wake(pwrkey->key_press_irq); + disable_irq_wake(pwrkey->key_release_irq); + } + + return 0; +} + +static struct dev_pm_ops pm8058_pwr_key_pm_ops = { + .suspend = pmic8058_pwrkey_suspend, + .resume = pmic8058_pwrkey_resume, +}; +#endif + +static int __devinit pmic8058_pwrkey_probe(struct platform_device *pdev) +{ + struct input_dev *pwr; + int key_release_irq = platform_get_irq(pdev, 0); + int key_press_irq = platform_get_irq(pdev, 1); + int err; + unsigned int delay; + u8 pon_cntl; + struct pmic8058_pwrkey *pwrkey; + struct pmic8058_pwrkey_pdata *pdata = pdev->dev.platform_data; + struct pm8058_chip *pm_chip; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + dev_err(&pdev->dev, "no parent data passed in\n"); + return -EFAULT; + } + + if (!pdata) { + dev_err(&pdev->dev, "power key platform data not supplied\n"); + return -EINVAL; + } + + if (pdata->kpd_trigger_delay_us > 62500) { + dev_err(&pdev->dev, "invalid pwr key trigger delay\n"); + return -EINVAL; + } + + if (pdata->pwrkey_time_ms && + (pdata->pwrkey_time_ms < 500 || pdata->pwrkey_time_ms > 1000)) { + dev_err(&pdev->dev, "invalid pwr key time supplied\n"); + return -EINVAL; + } + + pwrkey = kzalloc(sizeof(*pwrkey), GFP_KERNEL); + if (!pwrkey) + return -ENOMEM; + + pwrkey->pm_chip = pm_chip; + pwrkey->pdata = pdata; + pwrkey->pressed_first = false; + /* Enable runtime PM ops, start in ACTIVE mode */ + err = pm_runtime_set_active(&pdev->dev); + if (err < 0) + dev_dbg(&pdev->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&pdev->dev); + + pwr = input_allocate_device(); + if (!pwr) { + dev_dbg(&pdev->dev, "Can't allocate power button\n"); + err = -ENOMEM; + goto free_pwrkey; + } + + input_set_capability(pwr, EV_KEY, KEY_POWER); + input_set_capability(pwr, EV_KEY, KEY_END); + + pwr->name = "pmic8058_pwrkey"; + pwr->phys = "pmic8058_pwrkey/input0"; + pwr->dev.parent = &pdev->dev; + + delay = (pdata->kpd_trigger_delay_us << 10) / USEC_PER_SEC; + delay = 1 + ilog2(delay); + + err = pm8058_read(pwrkey->pm_chip, PON_CNTL_1, &pon_cntl, 1); + if (err < 0) { + dev_err(&pdev->dev, "failed reading PON_CNTL_1 err=%d\n", err); + goto free_input_dev; + } + + + pon_cntl &= ~PON_CNTL_TRIG_DELAY_MASK; + pon_cntl |= (delay & PON_CNTL_TRIG_DELAY_MASK); + pon_cntl |= (pdata->pull_up ? PON_CNTL_PULL_UP : ~PON_CNTL_PULL_UP); + err = pm8058_write(pwrkey->pm_chip, PON_CNTL_1, &pon_cntl, 1); + if (err < 0) { + dev_err(&pdev->dev, "failed writing PON_CNTL_1 err=%d\n", err); + goto free_input_dev; + } + + hrtimer_init(&pwrkey->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + pwrkey->timer.function = pmic8058_pwrkey_timer; + + spin_lock_init(&pwrkey->lock); + + err = input_register_device(pwr); + if (err) { + dev_dbg(&pdev->dev, "Can't register power key: %d\n", err); + goto free_input_dev; + } + + pwrkey->key_press_irq = key_press_irq; + pwrkey->key_release_irq = key_release_irq; + pwrkey->pwr = pwr; + + platform_set_drvdata(pdev, pwrkey); + + /* Check if power-key is pressed at boot up */ + err = pm8058_irq_get_rt_status(pwrkey->pm_chip, key_press_irq); + if (err < 0) { + dev_err(&pdev->dev, "Key-press status at boot failed rc=%d\n", + err); + goto unreg_input_dev; + } + if (err) { + if (!pwrkey->pdata->pwrkey_time_ms) + input_report_key(pwrkey->pwr, KEY_POWER, 1); + else + input_report_key(pwrkey->pwr, KEY_END, 1); + input_sync(pwrkey->pwr); + pwrkey->pressed_first = true; + } + + err = request_threaded_irq(key_press_irq, NULL, pwrkey_press_irq, + IRQF_TRIGGER_RISING, "pmic8058_pwrkey_press", pwrkey); + if (err < 0) { + dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n", + key_press_irq, err); + goto unreg_input_dev; + } + + err = request_threaded_irq(key_release_irq, NULL, pwrkey_release_irq, + IRQF_TRIGGER_RISING, "pmic8058_pwrkey_release", + pwrkey); + if (err < 0) { + dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n", + key_release_irq, err); + + goto free_press_irq; + } + + device_init_wakeup(&pdev->dev, pdata->wakeup); + + return 0; + +free_press_irq: + free_irq(key_press_irq, NULL); +unreg_input_dev: + input_unregister_device(pwr); + pwr = NULL; +free_input_dev: + input_free_device(pwr); +free_pwrkey: + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + kfree(pwrkey); + return err; +} + +static int __devexit pmic8058_pwrkey_remove(struct platform_device *pdev) +{ + struct pmic8058_pwrkey *pwrkey = platform_get_drvdata(pdev); + int key_release_irq = platform_get_irq(pdev, 0); + int key_press_irq = platform_get_irq(pdev, 1); + + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + device_init_wakeup(&pdev->dev, 0); + + free_irq(key_press_irq, pwrkey); + free_irq(key_release_irq, pwrkey); + input_unregister_device(pwrkey->pwr); + kfree(pwrkey); + + return 0; +} + +static struct platform_driver pmic8058_pwrkey_driver = { + .probe = pmic8058_pwrkey_probe, + .remove = __devexit_p(pmic8058_pwrkey_remove), + .driver = { + .name = "pm8058-pwrkey", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &pm8058_pwr_key_pm_ops, +#endif + }, +}; + +static int __init pmic8058_pwrkey_init(void) +{ + return platform_driver_register(&pmic8058_pwrkey_driver); +} +module_init(pmic8058_pwrkey_init); + +static void __exit pmic8058_pwrkey_exit(void) +{ + platform_driver_unregister(&pmic8058_pwrkey_driver); +} +module_exit(pmic8058_pwrkey_exit); + +MODULE_ALIAS("platform:pmic8058_pwrkey"); +MODULE_DESCRIPTION("PMIC8058 Power Key"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/misc/pmic8058-vib-memless.c b/drivers/input/misc/pmic8058-vib-memless.c new file mode 100644 index 00000000000..ba054000410 --- /dev/null +++ b/drivers/input/misc/pmic8058-vib-memless.c @@ -0,0 +1,282 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VIB_DRV 0x4A + +#define VIB_DRV_SEL_MASK 0xf8 +#define VIB_DRV_SEL_SHIFT 0x03 +#define VIB_DRV_EN_MANUAL_MASK 0xfc + +#define VIB_MAX_LEVEL_mV (3100) +#define VIB_MIN_LEVEL_mV (1200) +#define VIB_MAX_LEVELS (VIB_MAX_LEVEL_mV - VIB_MIN_LEVEL_mV) + +#define MAX_FF_SPEED 0xff + +struct pmic8058_vib { + struct input_dev *info; + spinlock_t lock; + struct work_struct work; + + bool enabled; + int speed; + struct device *dev; + struct pmic8058_vibrator_pdata *pdata; + int state; + int level; + u8 reg_vib_drv; + + struct pm8058_chip *pm_chip; +}; + +/* REVISIT: just for debugging, will be removed in final working version */ +static void __dump_vib_regs(struct pmic8058_vib *vib, char *msg) +{ + u8 temp; + + dev_dbg(vib->dev, "%s\n", msg); + + pm8058_read(vib->pm_chip, VIB_DRV, &temp, 1); + dev_dbg(vib->dev, "VIB_DRV - %X\n", temp); +} + +static int pmic8058_vib_read_u8(struct pmic8058_vib *vib, + u8 *data, u16 reg) +{ + int rc; + + rc = pm8058_read(vib->pm_chip, reg, data, 1); + if (rc < 0) + dev_warn(vib->dev, "Error reading pmic8058: %X - ret %X\n", + reg, rc); + + return rc; +} + +static int pmic8058_vib_write_u8(struct pmic8058_vib *vib, + u8 data, u16 reg) +{ + int rc; + + rc = pm8058_write(vib->pm_chip, reg, &data, 1); + if (rc < 0) + dev_warn(vib->dev, "Error writing pmic8058: %X - ret %X\n", + reg, rc); + return rc; +} + +static int pmic8058_vib_set(struct pmic8058_vib *vib, int on) +{ + int rc; + u8 val; + + if (on) { + val = vib->reg_vib_drv; + val |= ((vib->level << VIB_DRV_SEL_SHIFT) & VIB_DRV_SEL_MASK); + rc = pmic8058_vib_write_u8(vib, val, VIB_DRV); + if (rc < 0) + return rc; + vib->reg_vib_drv = val; + vib->enabled = 1; + + } else { + val = vib->reg_vib_drv; + val &= ~VIB_DRV_SEL_MASK; + rc = pmic8058_vib_write_u8(vib, val, VIB_DRV); + if (rc < 0) + return rc; + vib->reg_vib_drv = val; + vib->enabled = 0; + } + __dump_vib_regs(vib, "vib_set_end"); + + return rc; +} + +static void pmic8058_work_handler(struct work_struct *work) +{ + u8 val; + int rc; + struct pmic8058_vib *info; + + info = container_of(work, struct pmic8058_vib, work); + + rc = pmic8058_vib_read_u8(info, &val, VIB_DRV); + if (rc < 0) + return; + + /* + * Vibrator support voltage ranges from 1.2 to 3.1V, so + * scale the FF speed to these range. + */ + if (info->speed) { + info->state = 1; + info->level = ((VIB_MAX_LEVELS * info->speed) / MAX_FF_SPEED) + + VIB_MIN_LEVEL_mV; + info->level /= 100; + } else { + info->state = 0; + info->level = VIB_MIN_LEVEL_mV / 100; + } + pmic8058_vib_set(info, info->state); +} + +static int pmic8058_vib_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct pmic8058_vib *info = input_get_drvdata(dev); + + info->speed = effect->u.rumble.strong_magnitude >> 8; + if (!info->speed) + info->speed = effect->u.rumble.weak_magnitude >> 9; + schedule_work(&info->work); + return 0; +} + +static int __devinit pmic8058_vib_probe(struct platform_device *pdev) + +{ + struct pmic8058_vibrator_pdata *pdata = pdev->dev.platform_data; + struct pmic8058_vib *vib; + u8 val; + int rc; + + struct pm8058_chip *pm_chip; + + pm_chip = dev_get_drvdata(pdev->parent.dev); + if (pm_chip == NULL) { + dev_err(&pdev->dev, "no parent data passed in\n"); + return -EFAULT; + } + + if (!pdata) + return -EINVAL; + + if (pdata->level_mV < VIB_MIN_LEVEL_mV || + pdata->level_mV > VIB_MAX_LEVEL_mV) + return -EINVAL; + + vib = kzalloc(sizeof(*vib), GFP_KERNEL); + if (!vib) + return -ENOMEM; + + vib->pm_chip = pm_chip; + vib->enabled = 0; + vib->pdata = pdata; + vib->level = pdata->level_mV / 100; + vib->dev = &pdev->dev; + + spin_lock_init(&vib->lock); + INIT_WORK(&vib->work, pmic8058_work_handler); + + vib->info = input_allocate_device(); + + if (vib->info == NULL) { + dev_err(&pdev->dev, "couldn't allocate input device\n"); + return -ENOMEM; + } + + input_set_drvdata(vib->info, vib); + + vib->info->name = "pmic8058:vibrator"; + vib->info->id.version = 1; + vib->info->dev.parent = pdev->dev.parent; + + __set_bit(FF_RUMBLE, vib->info->ffbit); + __dump_vib_regs(vib, "boot_vib_default"); + + /* operate in manual mode */ + rc = pmic8058_vib_read_u8(vib, &val, VIB_DRV); + if (rc < 0) + goto err_read_vib; + val &= ~VIB_DRV_EN_MANUAL_MASK; + rc = pmic8058_vib_write_u8(vib, val, VIB_DRV); + if (rc < 0) + goto err_read_vib; + + vib->reg_vib_drv = val; + + rc = input_ff_create_memless(vib->info, NULL, pmic8058_vib_play_effect); + if (rc < 0) { + dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n"); + goto create_memless_err; + } + + platform_set_drvdata(pdev, vib); + + rc = input_register_device(vib->info); + if (rc < 0) { + dev_dbg(&pdev->dev, "couldn't register input device\n"); + goto reg_err; + } + + return 0; + +reg_err: + input_ff_destroy(vib->info); +create_memless_err: + input_free_device(vib->info); +err_read_vib: + kfree(vib); + return rc; +} + +static int __devexit pmic8058_vib_remove(struct platform_device *pdev) +{ + struct pmic8058_vib *vib = platform_get_drvdata(pdev); + + cancel_work_sync(&vib->work); + if (vib->enabled) + pmic8058_vib_set(vib, 0); + + input_unregister_device(vib->info); + kfree(vib); + + return 0; +} + +static struct platform_driver pmic8058_vib_driver = { + .probe = pmic8058_vib_probe, + .remove = __devexit_p(pmic8058_vib_remove), + .driver = { + .name = "pm8058-vib", + .owner = THIS_MODULE, + }, +}; + +static int __init pmic8058_vib_init(void) +{ + return platform_driver_register(&pmic8058_vib_driver); +} +module_init(pmic8058_vib_init); + +static void __exit pmic8058_vib_exit(void) +{ + platform_driver_unregister(&pmic8058_vib_driver); +} +module_exit(pmic8058_vib_exit); + +MODULE_ALIAS("platform:pmic8058_vib"); +MODULE_DESCRIPTION("PMIC8058 vibrator driver memless framework"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/misc/pmic8xxx-pwrkey.c b/drivers/input/misc/pmic8xxx-pwrkey.c index b3cfb9c71e6..02838e31c69 100644 --- a/drivers/input/misc/pmic8xxx-pwrkey.c +++ b/drivers/input/misc/pmic8xxx-pwrkey.c @@ -30,10 +30,12 @@ /** * struct pmic8xxx_pwrkey - pmic8xxx pwrkey information * @key_press_irq: key press irq number + * @pdata: platform data */ struct pmic8xxx_pwrkey { struct input_dev *pwr; int key_press_irq; + const struct pm8xxx_pwrkey_platform_data *pdata; }; static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey) @@ -107,6 +109,8 @@ static int __devinit pmic8xxx_pwrkey_probe(struct platform_device *pdev) if (!pwrkey) return -ENOMEM; + pwrkey->pdata = pdata; + pwr = input_allocate_device(); if (!pwr) { dev_dbg(&pdev->dev, "Can't allocate power button\n"); @@ -153,7 +157,7 @@ static int __devinit pmic8xxx_pwrkey_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pwrkey); - err = request_irq(key_press_irq, pwrkey_press_irq, + err = request_threaded_irq(key_press_irq, NULL, pwrkey_press_irq, IRQF_TRIGGER_RISING, "pmic8xxx_pwrkey_press", pwrkey); if (err < 0) { dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n", @@ -161,7 +165,7 @@ static int __devinit pmic8xxx_pwrkey_probe(struct platform_device *pdev) goto unreg_input_dev; } - err = request_irq(key_release_irq, pwrkey_release_irq, + err = request_threaded_irq(key_release_irq, NULL, pwrkey_release_irq, IRQF_TRIGGER_RISING, "pmic8xxx_pwrkey_release", pwrkey); if (err < 0) { dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n", diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig index 9c1e6ee8353..9f74e889002 100644 --- a/drivers/input/mouse/Kconfig +++ b/drivers/input/mouse/Kconfig @@ -322,4 +322,13 @@ config MOUSE_SYNAPTICS_I2C To compile this driver as a module, choose M here: the module will be called synaptics_i2c. +config MOUSE_QCITP + tristate "Quanta Computer Inc. Touchpad" + depends on I2C + default n + help + Say Y here if you want to use the Quanta touchpad driver for Quanta + smartbook platforms. + + endif diff --git a/drivers/input/mouse/qci_touchpad.c b/drivers/input/mouse/qci_touchpad.c new file mode 100644 index 00000000000..ef93a7ea872 --- /dev/null +++ b/drivers/input/mouse/qci_touchpad.c @@ -0,0 +1,309 @@ +/* Quanta I2C Touchpad Driver + * + * Copyright (C) 2009 Quanta Computer Inc. + * Author: Hsin Wu + * Author: Austin Lai + * + * 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. + * + */ + + /* + * + * The Driver with I/O communications via the I2C Interface for ON2 of AP BU. + * And it is only working on the nuvoTon WPCE775x Embedded Controller. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TOUCHPAD_ID_NAME "qci-i2cpad" +#define TOUCHPAD_NAME "PS2 Touchpad" +#define TOUCHPAD_DEVICE "/i2c/input1" +#define TOUCHPAD_CMD_ENABLE 0xF4 +#define TOUCHPAD_INIT_DELAY_MS 100 + +static int __devinit qcitp_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int __devexit qcitp_remove(struct i2c_client *kbd); + +/* General structure to hold the driver data */ +struct i2ctpad_drv_data { + struct i2c_client *ti2c_client; + struct work_struct work; + struct input_dev *qcitp_dev; + struct kobject *tp_kobj; + unsigned int qcitp_gpio; + unsigned int qcitp_irq; + char ecdata[8]; +}; + +static int tp_sense_val = 10; +static ssize_t tp_sensitive_show(struct kobject *kobj, + struct kobj_attribute *attr, char * buf) +{ + return sprintf(buf, "%d\n", tp_sense_val); +} + +static ssize_t tp_sensitive_store(struct kobject *kobj, + struct kobj_attribute *attr, const char* buf, size_t n) +{ + unsigned int val = 0; + sscanf(buf, "%d", &val); + + if (val >= 1 && val <= 10) + tp_sense_val = val; + else + return -ENOSYS; + + return sizeof(buf); +} + +static struct kobj_attribute tp_sensitivity = __ATTR(tp_sensitivity , + 0644 , + tp_sensitive_show , + tp_sensitive_store); + +static struct attribute *g_tp[] = { + &tp_sensitivity.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = g_tp, +}; + +/*----------------------------------------------------------------------------- + * Driver functions + *---------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +static int qcitp_suspend(struct device *dev) +{ + return 0; +} + +static int qcitp_resume(struct device *dev) +{ + return 0; +} +#endif + +static const struct i2c_device_id qcitp_idtable[] = { + { TOUCHPAD_ID_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, qcitp_idtable); +#ifdef CONFIG_PM +static const struct dev_pm_ops qcitp_pm_ops = { + .suspend = qcitp_suspend, + .resume = qcitp_resume, +}; +#endif +static struct i2c_driver i2ctp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = TOUCHPAD_ID_NAME, +#ifdef CONFIG_PM + .pm = &qcitp_pm_ops, +#endif + }, + .probe = qcitp_probe, + .remove = __devexit_p(qcitp_remove), + .id_table = qcitp_idtable, +}; + +static void qcitp_fetch_data(struct i2c_client *tpad_client, + char *ec_data) +{ + struct i2c_msg tp_msg; + int ret; + tp_msg.addr = tpad_client->addr; + tp_msg.flags = I2C_M_RD; + tp_msg.len = 3; + tp_msg.buf = (char *)&ec_data[0]; + ret = i2c_transfer(tpad_client->adapter, &tp_msg, 1); +} + +static void qcitp_report_key(struct input_dev *tpad_dev, char *ec_data) +{ + int dx = 0; + int dy = 0; + + if (ec_data[1]) + dx = (int) ec_data[1] - + (int) ((ec_data[0] << 4) & 0x100); + + if (ec_data[2]) + dy = (int) ((ec_data[0] << 3) & 0x100) - + (int) ec_data[2]; + + dx = (dx * tp_sense_val)/10; + dy = (dy * tp_sense_val)/10; + + input_report_key(tpad_dev, BTN_LEFT, ec_data[0] & 0x01); + input_report_key(tpad_dev, BTN_RIGHT, ec_data[0] & 0x02); + input_report_key(tpad_dev, BTN_MIDDLE, ec_data[0] & 0x04); + input_report_rel(tpad_dev, REL_X, dx); + input_report_rel(tpad_dev, REL_Y, dy); + input_sync(tpad_dev); +} + +static void qcitp_work_handler(struct work_struct *_work) +{ + struct i2ctpad_drv_data *itpad_drv_data = + container_of(_work, struct i2ctpad_drv_data, work); + + struct i2c_client *itpad_client = itpad_drv_data->ti2c_client; + struct input_dev *itpad_dev = itpad_drv_data->qcitp_dev; + + qcitp_fetch_data(itpad_client, itpad_drv_data->ecdata); + qcitp_report_key(itpad_dev, itpad_drv_data->ecdata); +} + +static irqreturn_t qcitp_interrupt(int irq, void *dev_id) +{ + struct i2ctpad_drv_data *itpad_drv_data = dev_id; + schedule_work(&itpad_drv_data->work); + return IRQ_HANDLED; +} + +static int __devinit qcitp_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err = -ENOMEM; + struct i2ctpad_drv_data *context = 0; + + context = kzalloc(sizeof(struct i2ctpad_drv_data), GFP_KERNEL); + if (!context) + return err; + i2c_set_clientdata(client, context); + context->ti2c_client = client; + context->qcitp_gpio = client->irq; + + /* Enable mouse */ + i2c_smbus_write_byte(client, TOUCHPAD_CMD_ENABLE); + msleep(TOUCHPAD_INIT_DELAY_MS); + i2c_smbus_read_byte(client); + /*allocate and register input device*/ + context->qcitp_dev = input_allocate_device(); + if (!context->qcitp_dev) { + pr_err("[TouchPad] allocting memory fail\n"); + err = -ENOMEM; + goto allocate_fail; + } + context->qcitp_dev->name = TOUCHPAD_NAME; + context->qcitp_dev->phys = TOUCHPAD_DEVICE; + context->qcitp_dev->id.bustype = BUS_I2C; + context->qcitp_dev->id.vendor = 0x1050; + context->qcitp_dev->id.product = 0x1; + context->qcitp_dev->id.version = 0x1; + context->qcitp_dev->evbit[0] = BIT_MASK(EV_KEY) | + BIT_MASK(EV_REL); + context->qcitp_dev->relbit[0] = BIT_MASK(REL_X) | + BIT_MASK(REL_Y); + context->qcitp_dev->keybit[BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_MIDDLE) | + BIT_MASK(BTN_RIGHT); + + input_set_drvdata(context->qcitp_dev, context); + err = input_register_device(context->qcitp_dev); + if (err) { + pr_err("[TouchPad] register device fail\n"); + goto register_fail; + } + + /*request intterrupt*/ + INIT_WORK(&context->work, qcitp_work_handler); + + err = gpio_request(context->qcitp_gpio, "qci-pad"); + if (err) { + pr_err("[TouchPad]err gpio request\n"); + goto gpio_request_fail; + } + + context->qcitp_irq = gpio_to_irq(context->qcitp_gpio); + err = request_irq(context->qcitp_irq, + qcitp_interrupt, + IRQF_TRIGGER_FALLING, + TOUCHPAD_ID_NAME, + context); + if (err) { + pr_err("[TouchPad] unable to get IRQ\n"); + goto request_irq_fail; + } + /*create touchpad kobject*/ + context->tp_kobj = kobject_create_and_add("touchpad", NULL); + + err = sysfs_create_group(context->tp_kobj, &attr_group); + if (err) + pr_warning("[TouchPad] sysfs create fail\n"); + + tp_sense_val = 10; + + return 0; + +request_irq_fail: + gpio_free(context->qcitp_gpio); + +gpio_request_fail: + input_unregister_device(context->qcitp_dev); + +register_fail: + input_free_device(context->qcitp_dev); + +allocate_fail: + i2c_set_clientdata(client, NULL); + kfree(context); + return err; +} + +static int __devexit qcitp_remove(struct i2c_client *dev) +{ + struct i2ctpad_drv_data *context = i2c_get_clientdata(dev); + + free_irq(context->qcitp_irq, context); + gpio_free(context->qcitp_gpio); + input_free_device(context->qcitp_dev); + input_unregister_device(context->qcitp_dev); + kfree(context); + + return 0; +} + +static int __init qcitp_init(void) +{ + return i2c_add_driver(&i2ctp_driver); +} + + +static void __exit qcitp_exit(void) +{ + i2c_del_driver(&i2ctp_driver); +} + +module_init(qcitp_init); +module_exit(qcitp_exit); + +MODULE_AUTHOR("Quanta Computer Inc."); +MODULE_DESCRIPTION("Quanta Embedded Controller I2C Touch Pad Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index 41041039670..aaa650bdcf0 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -55,6 +55,22 @@ config TOUCHSCREEN_AD7877 To compile this driver as a module, choose M here: the module will be called ad7877. +config TOUCHSCREEN_AD7879 + tristate + default n + +config TOUCHSCREEN_ATMEL_MAXTOUCH + tristate "Atmel maXTouch based touchscreens" + depends on I2C + default n + help + Say Y here if you have an Atmel Maxtouch based touchscreen. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called maXTouch. + config TOUCHSCREEN_AD7879 tristate "Analog Devices AD7879-1/AD7889-1 touchscreen interface" help @@ -363,6 +379,15 @@ config TOUCHSCREEN_PENMOUNT To compile this driver as a module, choose M here: the module will be called penmount. +config TOUCHSCREEN_MSM + bool "Qualcomm MSM touchscreen controller" + depends on ARCH_MSM7X30 && MARIMBA_TSADC + default n + help + Say Y here if you have a 4-wire resistive touchscreen panel + connected to the TSSC touchscreen controller on a + Qualcomm MSM/QSD based SoC. + config TOUCHSCREEN_MIGOR tristate "Renesas MIGO-R touchscreen" depends on SH_MIGOR && I2C @@ -679,6 +704,27 @@ config TOUCHSCREEN_TSC2007 To compile this driver as a module, choose M here: the module will be called tsc2007. +config TOUCHSCREEN_MSM_LEGACY + default n + tristate "MSM Touchscreen" + depends on ARCH_MSM && !ARCH_MSM7X30 + help + Say Y here if you have a touchscreen interface using MSM + touchscreen controller. + + To compile this driver as a module, choose M here: the + module will be called msm_touch. + +config ANDROID_TOUCHSCREEN_MSM_HACKS + default y + depends on TOUCHSCREEN_MSM_LEGACY + bool "Android MSM Touchscreen hacks" + help + Say Y here if you are running Android framework on Qualcomm + MSM/QSD based Surf or FFAs. These hacks are required inorder + to Android framework to receive adjusted x, y co-ordinates + until proper calibration framework is in place. + config TOUCHSCREEN_W90X900 tristate "W90P910 touchscreen driver" depends on HAVE_CLK @@ -732,4 +778,31 @@ config TOUCHSCREEN_TPS6507X To compile this driver as a module, choose M here: the module will be called tps6507x_ts. +config TOUCHSCREEN_CY8C_TS + tristate "Cypress TMA300-TMG200 based touchscreens" + depends on I2C + default n + help + Say Y here if you have a Cypress TMA300/TMG200 based touchscreen. + TMA300 is a multi-touch screen which can report upto 10 + touches at a time. TMG200 supports 2 touches. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called cy8c_ts. + +config TOUCHSCREEN_CYTTSP_I2C + tristate "Cypress TTSP based touchscreens" + depends on I2C + default n + help + Say Y here if you have a Cypress TTSP based touchscreen. + TMA300 is a multi-touch screen which can report upto 10 + touches at a time. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called cyttsp-i2c. endif diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 0738f19633b..bfe9dafe05f 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -12,16 +12,19 @@ obj-$(CONFIG_TOUCHSCREEN_AD7879) += ad7879.o obj-$(CONFIG_TOUCHSCREEN_AD7879_I2C) += ad7879-i2c.o obj-$(CONFIG_TOUCHSCREEN_AD7879_SPI) += ad7879-spi.o obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846.o +obj-$(CONFIG_TOUCHSCREEN_ATMEL_MAXTOUCH) += atmel_maxtouch.o obj-$(CONFIG_TOUCHSCREEN_ATMEL_MXT) += atmel_mxt_ts.o obj-$(CONFIG_TOUCHSCREEN_ATMEL_TSADCC) += atmel_tsadcc.o obj-$(CONFIG_TOUCHSCREEN_BITSY) += h3600_ts_input.o obj-$(CONFIG_TOUCHSCREEN_BU21013) += bu21013_ts.o +obj-$(CONFIG_TOUCHSCREEN_CYPRESS_TMG) += cy8c_tmg_ts.o obj-$(CONFIG_TOUCHSCREEN_CY8CTMG110) += cy8ctmg110_ts.o obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034-ts.o obj-$(CONFIG_TOUCHSCREEN_DYNAPRO) += dynapro.o obj-$(CONFIG_TOUCHSCREEN_HAMPSHIRE) += hampshire.o obj-$(CONFIG_TOUCHSCREEN_GUNZE) += gunze.o obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o +obj-$(CONFIG_TOUCHSCREEN_ELAN_I2C_8232) += elan8232_i2c.o obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o obj-$(CONFIG_TOUCHSCREEN_INEXIO) += inexio.o @@ -33,6 +36,7 @@ obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.o obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o +obj-$(CONFIG_TOUCHSCREEN_MSM) += msm_ts.o obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o obj-$(CONFIG_TOUCHSCREEN_HTCPEN) += htcpen.o @@ -61,3 +65,6 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o +obj-$(CONFIG_TOUCHSCREEN_MSM_LEGACY) += msm_touch.o +obj-$(CONFIG_TOUCHSCREEN_CY8C_TS) += cy8c_ts.o +obj-$(CONFIG_TOUCHSCREEN_CYTTSP_I2C) += cyttsp-i2c.o diff --git a/drivers/input/touchscreen/atmel_maxtouch.c b/drivers/input/touchscreen/atmel_maxtouch.c new file mode 100644 index 00000000000..de1834e47a6 --- /dev/null +++ b/drivers/input/touchscreen/atmel_maxtouch.c @@ -0,0 +1,2338 @@ +/* + * Atmel maXTouch Touchscreen Controller Driver + * + * + * Copyright (C) 2010 Atmel Corporation + * Copyright (C) 2010 Ulf Samuelsson (ulf@atmel.com) + * Copyright (C) 2009 Raphael Derosso Pereira + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * + * Driver for Atmel maXTouch family of touch controllers. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include + +/* Early-suspend level */ +#define MXT_SUSPEND_LEVEL 1 +#endif + + +#define DRIVER_VERSION "0.91a_mod" + +static int debug = DEBUG_INFO; +static int comms = 0; +module_param(debug, int, 0644); +module_param(comms, int, 0644); + +MODULE_PARM_DESC(debug, "Activate debugging output"); +MODULE_PARM_DESC(comms, "Select communications mode"); + +#define T7_DATA_SIZE 3 + +/* Device Info descriptor */ +/* Parsed from maXTouch "Id information" inside device */ +struct mxt_device_info { + u8 family_id; + u8 variant_id; + u8 major; + u8 minor; + u8 build; + u8 num_objs; + u8 x_size; + u8 y_size; + char family_name[16]; /* Family name */ + char variant_name[16]; /* Variant name */ + u16 num_nodes; /* Number of sensor nodes */ +}; + +/* object descriptor table, parsed from maXTouch "object table" */ +struct mxt_object { + u16 chip_addr; + u8 type; + u8 size; + u8 instances; + u8 num_report_ids; +}; + + +/* Mapping from report id to object type and instance */ +struct report_id_map { + u8 object; + u8 instance; +/* + * This is the first report ID belonging to object. It enables us to + * find out easily the touch number: each touch has different report + * ID (which are assigned to touches in increasing order). By + * subtracting the first report ID from current, we get the touch + * number. + */ + u8 first_rid; +}; + + +/* Driver datastructure */ +struct mxt_data { + struct i2c_client *client; + struct input_dev *input; + char phys_name[32]; + int irq; + + u16 last_read_addr; + bool new_msgs; + u8 *last_message; + + int valid_irq_counter; + int invalid_irq_counter; + int irq_counter; + int message_counter; + int read_fail_counter; + + + int bytes_to_read; + + struct delayed_work dwork; + u8 xpos_format; + u8 ypos_format; + + u8 numtouch; + + struct mxt_device_info device_info; + + u32 info_block_crc; + u32 configuration_crc; + u16 report_id_count; + struct report_id_map *rid_map; + struct mxt_object *object_table; + + u16 msg_proc_addr; + u8 message_size; + + u16 min_x_val; + u16 min_y_val; + u16 max_x_val; + u16 max_y_val; + + int (*init_hw)(struct i2c_client *client); + int (*exit_hw)(struct i2c_client *client); + int (*power_on)(bool on); + u8 (*valid_interrupt)(void); + u8 (*read_chg)(void); + + /* debugfs variables */ + struct dentry *debug_dir; + int current_debug_datap; + + struct mutex debug_mutex; + u16 *debug_data; + + /* Character device variables */ + struct cdev cdev; + struct cdev cdev_messages; /* 2nd Char dev for messages */ + dev_t dev_num; + struct class *mxt_class; + + + u16 address_pointer; + bool valid_ap; + + /* Message buffer & pointers */ + char *messages; + int msg_buffer_startp, msg_buffer_endp; + /* Put only non-touch messages to buffer if this is set */ + char nontouch_msg_only; + struct mutex msg_mutex; +#if defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif + u8 t7_data[T7_DATA_SIZE]; + bool is_suspended; +}; +/*default value, enough to read versioning*/ +#define CONFIG_DATA_SIZE 6 +static u16 t38_size = CONFIG_DATA_SIZE; +static int mxt_read_block(struct i2c_client *client, u16 addr, u16 length, + u8 *value); +static int mxt_write_byte(struct i2c_client *client, u16 addr, u8 value); +static int mxt_write_block(struct i2c_client *client, u16 addr, u16 length, + u8 *value); +static u8 mxt_valid_interrupt_dummy(void) +{ + return 1; +} + +#define I2C_RETRY_COUNT 5 +#define I2C_PAYLOAD_SIZE 254 + +/* Returns the start address of object in mXT memory. */ +#define MXT_BASE_ADDR(object_type, mxt) \ + get_object_address(object_type, 0, mxt->object_table, \ + mxt->device_info.num_objs) + +/* Maps a report ID to an object type (object type number). */ +#define REPORT_ID_TO_OBJECT(rid, mxt) \ + (((rid) == 0xff) ? 0 : mxt->rid_map[rid].object) + +/* Maps a report ID to an object type (string). */ +#define REPORT_ID_TO_OBJECT_NAME(rid, mxt) \ + object_type_name[REPORT_ID_TO_OBJECT(rid, mxt)] + +/* Returns non-zero if given object is a touch object */ +#define IS_TOUCH_OBJECT(object) \ + ((object == MXT_TOUCH_MULTITOUCHSCREEN_T9) || \ + (object == MXT_TOUCH_KEYARRAY_T15) || \ + (object == MXT_TOUCH_PROXIMITY_T23) || \ + (object == MXT_TOUCH_SINGLETOUCHSCREEN_T10) || \ + (object == MXT_TOUCH_XSLIDER_T11) || \ + (object == MXT_TOUCH_YSLIDER_T12) || \ + (object == MXT_TOUCH_XWHEEL_T13) || \ + (object == MXT_TOUCH_YWHEEL_T14) || \ + (object == MXT_TOUCH_KEYSET_T31) || \ + (object == MXT_TOUCH_XSLIDERSET_T32) ? 1 : 0) + +#define mxt_debug(level, ...) \ + do { \ + if (debug >= (level)) \ + pr_debug(__VA_ARGS__); \ + } while (0) + + +/* + * Check whether we have multi-touch enabled kernel; if not, report just the + * first touch (on mXT224, the maximum is 10 simultaneous touches). + * Because just the 1st one is reported, it might seem that the screen is not + * responding to touch if the first touch is removed while the screen is being + * touched by another finger, so beware. + * + */ + +#ifdef ABS_MT_TRACKING_ID +static inline void report_mt(int touch_number, int size, int x, int y, struct + mxt_data *mxt) { + input_report_abs(mxt->input, ABS_MT_TRACKING_ID, touch_number); + input_report_abs(mxt->input, ABS_MT_TOUCH_MAJOR, size); + input_report_abs(mxt->input, ABS_MT_POSITION_X, x); + input_report_abs(mxt->input, ABS_MT_POSITION_Y, y); + input_mt_sync(mxt->input); +} +#else +static inline void report_mt(int touch_number, int size, int x, int y, struct + mxt_data *mxt) { + if (touch_number == 0) { + input_report_abs(mxt->input, ABS_TOOL_WIDTH, size); + input_report_abs(mxt->input, ABS_X, x); + input_report_abs(mxt->input, ABS_Y, y); + } +} +#endif + + +static inline void report_gesture(int data, struct mxt_data *mxt) +{ + input_event(mxt->input, EV_MSC, MSC_GESTURE, data); +} + + +static const u8 *object_type_name[] = { + [0] = "Reserved", + [5] = "GEN_MESSAGEPROCESSOR_T5", + [6] = "GEN_COMMANDPROCESSOR_T6", + [7] = "GEN_POWERCONFIG_T7", + [8] = "GEN_ACQUIRECONFIG_T8", + [9] = "TOUCH_MULTITOUCHSCREEN_T9", + [15] = "TOUCH_KEYARRAY_T15", + [17] = "SPT_COMMSCONFIG_T18", + [19] = "SPT_GPIOPWM_T19", + [20] = "PROCI_GRIPFACESUPPRESSION_T20", + [22] = "PROCG_NOISESUPPRESSION_T22", + [23] = "TOUCH_PROXIMITY_T23", + [24] = "PROCI_ONETOUCHGESTUREPROCESSOR_T24", + [25] = "SPT_SELFTEST_T25", + [27] = "PROCI_TWOTOUCHGESTUREPROCESSOR_T27", + [28] = "SPT_CTECONFIG_T28", + [37] = "DEBUG_DIAGNOSTICS_T37", + [38] = "SPT_USER_DATA_T38", + [40] = "PROCI_GRIPSUPPRESSION_T40", + [41] = "PROCI_PALMSUPPRESSION_T41", + [42] = "PROCI_FACESUPPRESSION_T42", + [43] = "SPT_DIGITIZER_T43", + [44] = "SPT_MESSAGECOUNT_T44", +}; + + +static u16 get_object_address(uint8_t object_type, + uint8_t instance, + struct mxt_object *object_table, + int max_objs); + +int mxt_write_ap(struct mxt_data *mxt, u16 ap); + +static int mxt_read_block_wo_addr(struct i2c_client *client, + u16 length, + u8 *value); + +ssize_t debug_data_read(struct mxt_data *mxt, char *buf, size_t count, + loff_t *ppos, u8 debug_command){ + int i; + u16 *data; + u16 diagnostics_reg; + int offset = 0; + int size; + int read_size; + int error; + char *buf_start; + u16 debug_data_addr; + u16 page_address; + u8 page; + u8 debug_command_reg; + + data = mxt->debug_data; + if (data == NULL) + return -EIO; + + /* If first read after open, read all data to buffer. */ + if (mxt->current_debug_datap == 0){ + + diagnostics_reg = MXT_BASE_ADDR(MXT_GEN_COMMANDPROCESSOR_T6, + mxt) + + MXT_ADR_T6_DIAGNOSTIC; + if (count > (mxt->device_info.num_nodes * 2)) + count = mxt->device_info.num_nodes; + + debug_data_addr = MXT_BASE_ADDR(MXT_DEBUG_DIAGNOSTIC_T37, mxt)+ + MXT_ADR_T37_DATA; + page_address = MXT_BASE_ADDR(MXT_DEBUG_DIAGNOSTIC_T37, mxt) + + MXT_ADR_T37_PAGE; + error = mxt_read_block(mxt->client, page_address, 1, &page); + if (error < 0) + return error; + mxt_debug(DEBUG_TRACE, "debug data page = %d\n", page); + while (page != 0) { + error = mxt_write_byte(mxt->client, + diagnostics_reg, + MXT_CMD_T6_PAGE_DOWN); + if (error < 0) + return error; + /* Wait for command to be handled; when it has, the + register will be cleared. */ + debug_command_reg = 1; + while (debug_command_reg != 0) { + error = mxt_read_block(mxt->client, + diagnostics_reg, 1, + &debug_command_reg); + if (error < 0) + return error; + mxt_debug(DEBUG_TRACE, + "Waiting for debug diag command " + "to propagate...\n"); + + } + error = mxt_read_block(mxt->client, page_address, 1, + &page); + if (error < 0) + return error; + mxt_debug(DEBUG_TRACE, "debug data page = %d\n", page); + } + + /* + * Lock mutex to prevent writing some unwanted data to debug + * command register. User can still write through the char + * device interface though. TODO: fix? + */ + + mutex_lock(&mxt->debug_mutex); + /* Configure Debug Diagnostics object to show deltas/refs */ + error = mxt_write_byte(mxt->client, diagnostics_reg, + debug_command); + + /* Wait for command to be handled; when it has, the + * register will be cleared. */ + debug_command_reg = 1; + while (debug_command_reg != 0) { + error = mxt_read_block(mxt->client, + diagnostics_reg, 1, + &debug_command_reg); + if (error < 0) + return error; + mxt_debug(DEBUG_TRACE, "Waiting for debug diag command " + "to propagate...\n"); + + } + + if (error < 0) { + printk (KERN_WARNING + "Error writing to maXTouch device!\n"); + return error; + } + + size = mxt->device_info.num_nodes * sizeof(u16); + + while (size > 0) { + read_size = size > 128 ? 128 : size; + mxt_debug(DEBUG_TRACE, + "Debug data read loop, reading %d bytes...\n", + read_size); + error = mxt_read_block(mxt->client, + debug_data_addr, + read_size, + (u8 *) &data[offset]); + if (error < 0) { + printk(KERN_WARNING + "Error reading debug data\n"); + goto error; + } + offset += read_size/2; + size -= read_size; + + /* Select next page */ + error = mxt_write_byte(mxt->client, diagnostics_reg, + MXT_CMD_T6_PAGE_UP); + if (error < 0) { + printk(KERN_WARNING + "Error writing to maXTouch device!\n"); + goto error; + } + } + mutex_unlock(&mxt->debug_mutex); + } + + buf_start = buf; + i = mxt->current_debug_datap; + + while (((buf- buf_start) < (count - 6)) && + (i < mxt->device_info.num_nodes)){ + + mxt->current_debug_datap++; + if (debug_command == MXT_CMD_T6_REFERENCES_MODE) + buf += sprintf(buf, "%d: %5d\n", i, + (u16) le16_to_cpu(data[i])); + else if (debug_command == MXT_CMD_T6_DELTAS_MODE) + buf += sprintf(buf, "%d: %5d\n", i, + (s16) le16_to_cpu(data[i])); + i++; + } + + return (buf - buf_start); +error: + mutex_unlock(&mxt->debug_mutex); + return error; +} + +ssize_t deltas_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + return debug_data_read(file->private_data, buf, count, ppos, + MXT_CMD_T6_DELTAS_MODE); +} + +ssize_t refs_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + return debug_data_read(file->private_data, buf, count, ppos, + MXT_CMD_T6_REFERENCES_MODE); +} + +int debug_data_open(struct inode *inode, struct file *file) +{ + struct mxt_data *mxt; + int i; + mxt = inode->i_private; + if (mxt == NULL) + return -EIO; + mxt->current_debug_datap = 0; + mxt->debug_data = kmalloc(mxt->device_info.num_nodes * sizeof(u16), + GFP_KERNEL); + if (mxt->debug_data == NULL) + return -ENOMEM; + + + for (i = 0; i < mxt->device_info.num_nodes; i++) + mxt->debug_data[i] = 7777; + + + file->private_data = mxt; + return 0; +} + +int debug_data_release(struct inode *inode, struct file *file) +{ + struct mxt_data *mxt; + mxt = file->private_data; + kfree(mxt->debug_data); + return 0; +} + +static struct file_operations delta_fops = { + .owner = THIS_MODULE, + .open = debug_data_open, + .release = debug_data_release, + .read = deltas_read, +}; + +static struct file_operations refs_fops = { + .owner = THIS_MODULE, + .open = debug_data_open, + .release = debug_data_release, + .read = refs_read, +}; + + +int mxt_memory_open(struct inode *inode, struct file *file) +{ + struct mxt_data *mxt; + mxt = container_of(inode->i_cdev, struct mxt_data, cdev); + if (mxt == NULL) + return -EIO; + file->private_data = mxt; + return 0; +} + +int mxt_message_open(struct inode *inode, struct file *file) +{ + struct mxt_data *mxt; + mxt = container_of(inode->i_cdev, struct mxt_data, cdev_messages); + if (mxt == NULL) + return -EIO; + file->private_data = mxt; + return 0; +} + + +ssize_t mxt_memory_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + int i; + struct mxt_data *mxt; + + mxt = file->private_data; + if (mxt->valid_ap){ + mxt_debug(DEBUG_TRACE, "Reading %d bytes from current ap\n", + (int) count); + i = mxt_read_block_wo_addr(mxt->client, count, (u8 *) buf); + } else { + mxt_debug(DEBUG_TRACE, "Address pointer changed since set;" + "writing AP (%d) before reading %d bytes", + mxt->address_pointer, (int) count); + i = mxt_read_block(mxt->client, mxt->address_pointer, count, + buf); + } + + return i; +} + +ssize_t mxt_memory_write(struct file *file, const char *buf, size_t count, + loff_t *ppos) +{ + int i; + int whole_blocks; + int last_block_size; + struct mxt_data *mxt; + u16 address; + + mxt = file->private_data; + address = mxt->address_pointer; + + mxt_debug(DEBUG_TRACE, "mxt_memory_write entered\n"); + whole_blocks = count / I2C_PAYLOAD_SIZE; + last_block_size = count % I2C_PAYLOAD_SIZE; + + for (i = 0; i < whole_blocks; i++) { + mxt_debug(DEBUG_TRACE, "About to write to %d...", + address); + mxt_write_block(mxt->client, address, I2C_PAYLOAD_SIZE, + (u8 *) buf); + address += I2C_PAYLOAD_SIZE; + buf += I2C_PAYLOAD_SIZE; + } + + mxt_write_block(mxt->client, address, last_block_size, (u8 *) buf); + + return count; +} + +static long mxt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int retval; + struct mxt_data *mxt; + + retval = 0; + mxt = file->private_data; + + switch (cmd) { + case MXT_SET_ADDRESS_IOCTL: + retval = mxt_write_ap(mxt, (u16) arg); + if (retval >= 0) { + mxt->address_pointer = (u16) arg; + mxt->valid_ap = 1; + } + break; + case MXT_RESET_IOCTL: + retval = mxt_write_byte(mxt->client, + MXT_BASE_ADDR(MXT_GEN_COMMANDPROCESSOR_T6, mxt) + + MXT_ADR_T6_RESET, + 1); + break; + case MXT_CALIBRATE_IOCTL: + retval = mxt_write_byte(mxt->client, + MXT_BASE_ADDR(MXT_GEN_COMMANDPROCESSOR_T6, mxt) + + MXT_ADR_T6_CALIBRATE, + 1); + + break; + case MXT_BACKUP_IOCTL: + retval = mxt_write_byte(mxt->client, + MXT_BASE_ADDR(MXT_GEN_COMMANDPROCESSOR_T6, mxt) + + MXT_ADR_T6_BACKUPNV, + MXT_CMD_T6_BACKUP); + break; + case MXT_NONTOUCH_MSG_IOCTL: + mxt->nontouch_msg_only = 1; + break; + case MXT_ALL_MSG_IOCTL: + mxt->nontouch_msg_only = 0; + break; + default: + return -EIO; + } + + return retval; +} + +/* + * Copies messages from buffer to user space. + * + * NOTE: if less than (mxt->message_size * 5 + 1) bytes requested, + * this will return 0! + * + */ +ssize_t mxt_message_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + int i; + struct mxt_data *mxt; + char *buf_start; + + mxt = file->private_data; + if (mxt == NULL) + return -EIO; + buf_start = buf; + + mutex_lock(&mxt->msg_mutex); + /* Copy messages until buffer empty, or 'count' bytes written */ + while ((mxt->msg_buffer_startp != mxt->msg_buffer_endp) && + ((buf - buf_start) < (count - (5 * mxt->message_size) - 1))){ + + for (i = 0; i < mxt->message_size; i++){ + buf += sprintf(buf, "[%2X] ", + *(mxt->messages + mxt->msg_buffer_endp * + mxt->message_size + i)); + } + buf += sprintf(buf, "\n"); + if (mxt->msg_buffer_endp < MXT_MESSAGE_BUFFER_SIZE) + mxt->msg_buffer_endp++; + else + mxt->msg_buffer_endp = 0; + } + mutex_unlock(&mxt->msg_mutex); + return (buf - buf_start); +} + +static struct file_operations mxt_message_fops = { + .owner = THIS_MODULE, + .open = mxt_message_open, + .read = mxt_message_read, +}; + +static struct file_operations mxt_memory_fops = { + .owner = THIS_MODULE, + .open = mxt_memory_open, + .read = mxt_memory_read, + .write = mxt_memory_write, + .unlocked_ioctl = mxt_ioctl, +}; + + +/* Writes the address pointer (to set up following reads). */ + +int mxt_write_ap(struct mxt_data *mxt, u16 ap) +{ + struct i2c_client *client; + __le16 le_ap = cpu_to_le16(ap); + client = mxt->client; + if (mxt != NULL) + mxt->last_read_addr = -1; + if (i2c_master_send(client, (u8 *) &le_ap, 2) == 2) { + mxt_debug(DEBUG_TRACE, "Address pointer set to %d\n", ap); + return 0; + } else { + mxt_debug(DEBUG_INFO, "Error writing address pointer!\n"); + return -EIO; + } +} + + + +/* Calculates the 24-bit CRC sum. */ +static u32 CRC_24(u32 crc, u8 byte1, u8 byte2) +{ + static const u32 crcpoly = 0x80001B; + u32 result; + u32 data_word; + + data_word = ((((u16) byte2) << 8u) | byte1); + result = ((crc << 1u) ^ data_word); + if (result & 0x1000000) + result ^= crcpoly; + return result; +} + +/* Returns object address in mXT chip, or zero if object is not found */ +static u16 get_object_address(uint8_t object_type, + uint8_t instance, + struct mxt_object *object_table, + int max_objs) +{ + uint8_t object_table_index = 0; + uint8_t address_found = 0; + uint16_t address = 0; + struct mxt_object *obj; + + while ((object_table_index < max_objs) && !address_found) { + obj = &object_table[object_table_index]; + if (obj->type == object_type) { + address_found = 1; + /* Are there enough instances defined in the FW? */ + if (obj->instances >= instance) { + address = obj->chip_addr + + (obj->size + 1) * instance; + } else { + return 0; + } + } + object_table_index++; + } + return address; +} + + +/* + * Reads a block of bytes from given address from mXT chip. If we are + * reading from message window, and previous read was from message window, + * there's no need to write the address pointer: the mXT chip will + * automatically set the address pointer back to message window start. + */ + +static int mxt_read_block(struct i2c_client *client, + u16 addr, + u16 length, + u8 *value) +{ + struct i2c_adapter *adapter = client->adapter; + struct i2c_msg msg[2]; + __le16 le_addr; + struct mxt_data *mxt; + + mxt = i2c_get_clientdata(client); + + if (mxt != NULL) { + if ((mxt->last_read_addr == addr) && + (addr == mxt->msg_proc_addr)) { + if (i2c_master_recv(client, value, length) == length) + return length; + else + return -EIO; + } else { + mxt->last_read_addr = addr; + } + } + + mxt_debug(DEBUG_TRACE, "Writing address pointer & reading %d bytes " + "in on i2c transaction...\n", length); + + le_addr = cpu_to_le16(addr); + msg[0].addr = client->addr; + msg[0].flags = 0x00; + msg[0].len = 2; + msg[0].buf = (u8 *) &le_addr; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = length; + msg[1].buf = (u8 *) value; + if (i2c_transfer(adapter, msg, 2) == 2) + return length; + else + return -EIO; + +} + +/* Reads a block of bytes from current address from mXT chip. */ + +static int mxt_read_block_wo_addr(struct i2c_client *client, + u16 length, + u8 *value) +{ + + + if (i2c_master_recv(client, value, length) == length) { + mxt_debug(DEBUG_TRACE, "I2C block read ok\n"); + return length; + } else { + mxt_debug(DEBUG_INFO, "I2C block read failed\n"); + return -EIO; + } + +} + + +/* Writes one byte to given address in mXT chip. */ + +static int mxt_write_byte(struct i2c_client *client, u16 addr, u8 value) +{ + struct { + __le16 le_addr; + u8 data; + + } i2c_byte_transfer; + + struct mxt_data *mxt; + + mxt = i2c_get_clientdata(client); + if (mxt != NULL) + mxt->last_read_addr = -1; + i2c_byte_transfer.le_addr = cpu_to_le16(addr); + i2c_byte_transfer.data = value; + if (i2c_master_send(client, (u8 *) &i2c_byte_transfer, 3) == 3) + return 0; + else + return -EIO; +} + + +/* Writes a block of bytes (max 256) to given address in mXT chip. */ +static int mxt_write_block(struct i2c_client *client, + u16 addr, + u16 length, + u8 *value) +{ + int i; + struct { + __le16 le_addr; + u8 data[256]; + + } i2c_block_transfer; + + struct mxt_data *mxt; + + mxt_debug(DEBUG_TRACE, "Writing %d bytes to %d...", length, addr); + if (length > 256) + return -EINVAL; + mxt = i2c_get_clientdata(client); + if (mxt != NULL) + mxt->last_read_addr = -1; + for (i = 0; i < length; i++) + i2c_block_transfer.data[i] = *value++; + i2c_block_transfer.le_addr = cpu_to_le16(addr); + i = i2c_master_send(client, (u8 *) &i2c_block_transfer, length + 2); + if (i == (length + 2)) + return length; + else + return -EIO; +} + +/* Calculates the CRC value for mXT infoblock. */ +int calculate_infoblock_crc(u32 *crc_result, u8 *data, int crc_area_size) +{ + u32 crc = 0; + int i; + + for (i = 0; i < (crc_area_size - 1); i = i + 2) + crc = CRC_24(crc, *(data + i), *(data + i + 1)); + /* If uneven size, pad with zero */ + if (crc_area_size & 0x0001) + crc = CRC_24(crc, *(data + i), 0); + /* Return only 24 bits of CRC. */ + *crc_result = (crc & 0x00FFFFFF); + + return 0; +} + +/* Processes a touchscreen message. */ +void process_T9_message(u8 *message, struct mxt_data *mxt, int last_touch) +{ + + struct input_dev *input; + u8 status; + u16 xpos = 0xFFFF; + u16 ypos = 0xFFFF; + u8 touch_size = 255; + u8 touch_number; + u8 amplitude; + u8 report_id; + + static int stored_size[10]; + static int stored_x[10]; + static int stored_y[10]; + int i; + int active_touches = 0; + /* + * If the 'last_touch' flag is set, we have received all the touch + * messages + * there are available in this cycle, so send the events for touches + * that are + * active. + */ + if (last_touch){ + /* TODO: For compatibility with single-touch systems, send ABS_X & + * ABS_Y */ + /* + if (stored_size[0]){ + input_report_abs(mxt->input, ABS_X, stored_x[0]); + input_report_abs(mxt->input, ABS_Y, stored_y[0]); + }*/ + + + for (i = 0; i < 10; i++){ + if (stored_size[i]){ + active_touches++; + input_report_abs(mxt->input, + ABS_MT_TRACKING_ID, + i); + input_report_abs(mxt->input, + ABS_MT_TOUCH_MAJOR, + stored_size[i]); + input_report_abs(mxt->input, + ABS_MT_POSITION_X, + stored_x[i]); + input_report_abs(mxt->input, + ABS_MT_POSITION_Y, + stored_y[i]); + input_mt_sync(mxt->input); + } + } + if (active_touches == 0) + input_mt_sync(mxt->input); + input_sync(mxt->input); + + }else{ + + input = mxt->input; + status = message[MXT_MSG_T9_STATUS]; + report_id = message[0]; + + if (status & MXT_MSGB_T9_SUPPRESS) { + /* Touch has been suppressed by grip/face */ + /* detection */ + mxt_debug(DEBUG_TRACE, "SUPRESS"); + } else { + /* Put together the 10-/12-bit coordinate values. */ + xpos = message[MXT_MSG_T9_XPOSMSB] * 16 + + ((message[MXT_MSG_T9_XYPOSLSB] >> 4) & 0xF); + ypos = message[MXT_MSG_T9_YPOSMSB] * 16 + + ((message[MXT_MSG_T9_XYPOSLSB] >> 0) & 0xF); + + if (mxt->max_x_val < 1024) + xpos >>= 2; + if (mxt->max_y_val < 1024) + ypos >>= 2; + + touch_number = message[MXT_MSG_REPORTID] - + mxt->rid_map[report_id].first_rid; + + stored_x[touch_number] = xpos; + stored_y[touch_number] = ypos; + + if (status & MXT_MSGB_T9_DETECT) { + /* + * mXT224 reports the number of touched nodes, + * so the exact value for touch ellipse major + * axis length in nodes would be 2*sqrt(touch_size/pi) + * (assuming round touch shape), which would then need + * to be scaled using information about how many sensor + * lines we do have. So this is very much simplified, + * but sufficient for most if not all apps? + */ + touch_size = message[MXT_MSG_T9_TCHAREA]; + touch_size = touch_size >> 2; + if (!touch_size) + touch_size = 1; + /* + * report_mt(touch_number, touch_size, xpos, ypos, mxt); + */ + + stored_size[touch_number] = touch_size; + + if (status & MXT_MSGB_T9_AMP) + /* Amplitude of touch has changed */ + amplitude = message[MXT_MSG_T9_TCHAMPLITUDE]; + } + + if (status & MXT_MSGB_T9_RELEASE) { + /* The previously reported touch has been removed.*/ + /* report_mt(touch_number, 0, xpos, ypos, mxt); */ + stored_size[touch_number] = 0; + } + + /* input_sync(input); */ + } + + if (status & MXT_MSGB_T9_SUPPRESS) { + mxt_debug(DEBUG_TRACE, "SUPRESS"); + } else { + if (status & MXT_MSGB_T9_DETECT) { + mxt_debug(DEBUG_TRACE, "DETECT:%s%s%s%s", + ((status & MXT_MSGB_T9_PRESS) ? " PRESS" : ""), + ((status & MXT_MSGB_T9_MOVE) ? " MOVE" : ""), + ((status & MXT_MSGB_T9_AMP) ? " AMP" : ""), + ((status & MXT_MSGB_T9_VECTOR) ? " VECT" : "")); + + } else if (status & MXT_MSGB_T9_RELEASE) { + mxt_debug(DEBUG_TRACE, "RELEASE"); + } + } + mxt_debug(DEBUG_TRACE, "X=%d, Y=%d, TOUCHSIZE=%d", + xpos, ypos, touch_size); + } + return; +} + + +int process_message(u8 *message, u8 object, struct mxt_data *mxt) +{ + struct i2c_client *client; + u8 status; + u16 xpos = 0xFFFF; + u16 ypos = 0xFFFF; + u8 event; + u8 direction; + u16 distance; + u8 length; + u8 report_id; + static u8 error_cond = 0; + + client = mxt->client; + length = mxt->message_size; + report_id = message[0]; + + if ((mxt->nontouch_msg_only == 0) || + (!IS_TOUCH_OBJECT(object))){ + mutex_lock(&mxt->msg_mutex); + /* Copy the message to buffer */ + if (mxt->msg_buffer_startp < MXT_MESSAGE_BUFFER_SIZE) { + mxt->msg_buffer_startp++; + } else { + mxt->msg_buffer_startp = 0; + } + + if (mxt->msg_buffer_startp == mxt->msg_buffer_endp) { + mxt_debug(DEBUG_TRACE, + "Message buf full, discarding last entry.\n"); + if (mxt->msg_buffer_endp < MXT_MESSAGE_BUFFER_SIZE) { + mxt->msg_buffer_endp++; + } else { + mxt->msg_buffer_endp = 0; + } + } + memcpy((mxt->messages + mxt->msg_buffer_startp * length), + message, + length); + mutex_unlock(&mxt->msg_mutex); + } + + switch (object) { + case MXT_GEN_COMMANDPROCESSOR_T6: + status = message[1]; + + if (status & MXT_MSGB_T6_COMSERR) { + if ((!error_cond) & MXT_MSGB_T6_COMSERR){ + dev_err(&client->dev, + "maXTouch checksum error\n"); + error_cond |= MXT_MSGB_T6_COMSERR; + } + } + if (status & MXT_MSGB_T6_CFGERR) { + /* + * Configuration error. A proper configuration + * needs to be written to chip and backed up. + */ + if ((!error_cond) & MXT_MSGB_T6_CFGERR){ + dev_err(&client->dev, + "maXTouch configuration error\n"); + error_cond |= MXT_MSGB_T6_CFGERR; + } + } + if (status & MXT_MSGB_T6_CAL) { + /* Calibration in action, no need to react */ + dev_dbg(&client->dev, + "maXTouch calibration in progress\n"); + } + if (status & MXT_MSGB_T6_SIGERR) { + /* + * Signal acquisition error, something is seriously + * wrong, not much we can in the driver to correct + * this + */ + if ((!error_cond) & MXT_MSGB_T6_SIGERR){ + dev_err(&client->dev, + "maXTouch acquisition error\n"); + error_cond |= MXT_MSGB_T6_SIGERR; + } + } + if (status & MXT_MSGB_T6_OFL) { + /* + * Cycle overflow, the acquisition interval is too + * short. + */ + dev_err(&client->dev, + "maXTouch cycle overflow\n"); + } + if (status & MXT_MSGB_T6_RESET) { + /* Chip has reseted, no need to react. */ + dev_dbg(&client->dev, + "maXTouch chip reset\n"); + } + if (status == 0) { + /* Chip status back to normal. */ + dev_dbg(&client->dev, + "maXTouch status normal\n"); + error_cond = 0; + } + break; + + case MXT_TOUCH_MULTITOUCHSCREEN_T9: + process_T9_message(message, mxt, 0); + break; + + case MXT_SPT_GPIOPWM_T19: + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "Receiving GPIO message\n"); + break; + + case MXT_PROCI_GRIPFACESUPPRESSION_T20: + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "Receiving face suppression msg\n"); + break; + + case MXT_PROCG_NOISESUPPRESSION_T22: + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "Receiving noise suppression msg\n"); + status = message[MXT_MSG_T22_STATUS]; + if (status & MXT_MSGB_T22_FHCHG) { + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "maXTouch: Freq changed\n"); + } + if (status & MXT_MSGB_T22_GCAFERR) { + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "maXTouch: High noise " + "level\n"); + } + if (status & MXT_MSGB_T22_FHERR) { + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "maXTouch: Freq changed - " + "Noise level too high\n"); + } + break; + + case MXT_PROCI_ONETOUCHGESTUREPROCESSOR_T24: + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "Receiving one-touch gesture msg\n"); + + event = message[MXT_MSG_T24_STATUS] & 0x0F; + xpos = message[MXT_MSG_T24_XPOSMSB] * 16 + + ((message[MXT_MSG_T24_XYPOSLSB] >> 4) & 0x0F); + ypos = message[MXT_MSG_T24_YPOSMSB] * 16 + + ((message[MXT_MSG_T24_XYPOSLSB] >> 0) & 0x0F); + if (mxt->max_x_val < 1024) + xpos >>= 2; + if (mxt->max_y_val < 1024) + ypos >>= 2; + direction = message[MXT_MSG_T24_DIR]; + distance = message[MXT_MSG_T24_DIST] + + (message[MXT_MSG_T24_DIST + 1] << 16); + + report_gesture((event << 24) | (direction << 16) | distance, + mxt); + report_gesture((xpos << 16) | ypos, mxt); + + break; + + case MXT_SPT_SELFTEST_T25: + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "Receiving Self-Test msg\n"); + + if (message[MXT_MSG_T25_STATUS] == MXT_MSGR_T25_OK) { + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "maXTouch: Self-Test OK\n"); + + } else { + dev_err(&client->dev, + "maXTouch: Self-Test Failed [%02x]:" + "{%02x,%02x,%02x,%02x,%02x}\n", + message[MXT_MSG_T25_STATUS], + message[MXT_MSG_T25_STATUS + 0], + message[MXT_MSG_T25_STATUS + 1], + message[MXT_MSG_T25_STATUS + 2], + message[MXT_MSG_T25_STATUS + 3], + message[MXT_MSG_T25_STATUS + 4] + ); + } + break; + + case MXT_PROCI_TWOTOUCHGESTUREPROCESSOR_T27: + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "Receiving 2-touch gesture message\n"); + + event = message[MXT_MSG_T27_STATUS] & 0xF0; + xpos = message[MXT_MSG_T27_XPOSMSB] * 16 + + ((message[MXT_MSG_T27_XYPOSLSB] >> 4) & 0x0F); + ypos = message[MXT_MSG_T27_YPOSMSB] * 16 + + ((message[MXT_MSG_T27_XYPOSLSB] >> 0) & 0x0F); + if (mxt->max_x_val < 1024) + xpos >>= 2; + if (mxt->max_y_val < 1024) + ypos >>= 2; + + direction = message[MXT_MSG_T27_ANGLE]; + distance = message[MXT_MSG_T27_SEPARATION] + + (message[MXT_MSG_T27_SEPARATION + 1] << 16); + + report_gesture((event << 24) | (direction << 16) | distance, + mxt); + report_gesture((xpos << 16) | ypos, mxt); + + + break; + + case MXT_SPT_CTECONFIG_T28: + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "Receiving CTE message...\n"); + status = message[MXT_MSG_T28_STATUS]; + if (status & MXT_MSGB_T28_CHKERR) + dev_err(&client->dev, + "maXTouch: Power-Up CRC failure\n"); + + break; + default: + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, + "maXTouch: Unknown message!\n"); + + break; + } + + return 0; +} + + +/* + * Processes messages when the interrupt line (CHG) is asserted. Keeps + * reading messages until a message with report ID 0xFF is received, + * which indicates that there is no more new messages. + * + */ + +static void mxt_worker(struct work_struct *work) +{ + struct mxt_data *mxt; + struct i2c_client *client; + + u8 *message; + u16 message_length; + u16 message_addr; + u8 report_id; + u8 object; + int error; + int i; + char *message_string; + char *message_start; + + message = NULL; + mxt = container_of(work, struct mxt_data, dwork.work); + disable_irq(mxt->irq); + client = mxt->client; + message_addr = mxt->msg_proc_addr; + message_length = mxt->message_size; + + if (message_length < 256) { + message = kmalloc(message_length, GFP_KERNEL); + if (message == NULL) { + dev_err(&client->dev, "Error allocating memory\n"); + return; + } + } else { + dev_err(&client->dev, + "Message length larger than 256 bytes not supported\n"); + return; + } + + mxt_debug(DEBUG_TRACE, "maXTouch worker active: \n"); + + do { + /* Read next message, reread on failure. */ + /* TODO: message length, CRC included? */ + mxt->message_counter++; + for (i = 1; i < I2C_RETRY_COUNT; i++) { + error = mxt_read_block(client, + message_addr, + message_length - 1, + message); + if (error >= 0) + break; + mxt->read_fail_counter++; + dev_err(&client->dev, + "Failure reading maxTouch device\n"); + } + if (error < 0) { + kfree(message); + return; + } + + if (mxt->address_pointer != message_addr) + mxt->valid_ap = 0; + report_id = message[0]; + + if (debug >= DEBUG_RAW) { + mxt_debug(DEBUG_RAW, "%s message [msg count: %08x]:", + REPORT_ID_TO_OBJECT_NAME(report_id, mxt), + mxt->message_counter + ); + /* 5 characters per one byte */ + message_string = kmalloc(message_length * 5, + GFP_KERNEL); + if (message_string == NULL) { + dev_err(&client->dev, + "Error allocating memory\n"); + kfree(message); + return; + } + message_start = message_string; + for (i = 0; i < message_length; i++) { + message_string += + sprintf(message_string, + "0x%02X ", message[i]); + } + mxt_debug(DEBUG_RAW, "%s", message_start); + kfree(message_start); + } + + if ((report_id != MXT_END_OF_MESSAGES) && (report_id != 0)) { + memcpy(mxt->last_message, message, message_length); + mxt->new_msgs = 1; + smp_wmb(); + /* Get type of object and process the message */ + object = mxt->rid_map[report_id].object; + process_message(message, object, mxt); + } + + mxt_debug(DEBUG_TRACE, "chgline: %d\n", mxt->read_chg()); + } while (comms ? (mxt->read_chg() == 0) : + ((report_id != MXT_END_OF_MESSAGES) && (report_id != 0))); + + /* All messages processed, send the events) */ + process_T9_message(NULL, mxt, 1); + + + kfree(message); + enable_irq(mxt->irq); + /* Make sure we just didn't miss a interrupt. */ + if (mxt->read_chg() == 0){ + schedule_delayed_work(&mxt->dwork, 0); + } + +} + + +/* + * The maXTouch device will signal the host about a new message by asserting + * the CHG line. This ISR schedules a worker routine to read the message when + * that happens. + */ + +static irqreturn_t mxt_irq_handler(int irq, void *_mxt) +{ + struct mxt_data *mxt = _mxt; + + mxt->irq_counter++; + if (mxt->valid_interrupt()) { + /* Send the signal only if falling edge generated the irq. */ + cancel_delayed_work(&mxt->dwork); + schedule_delayed_work(&mxt->dwork, 0); + mxt->valid_irq_counter++; + } else { + mxt->invalid_irq_counter++; + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + + + +/******************************************************************************/ +/* Initialization of driver */ +/******************************************************************************/ + +static int __devinit mxt_identify(struct i2c_client *client, + struct mxt_data *mxt, + u8 *id_block_data) +{ + u8 buf[MXT_ID_BLOCK_SIZE]; + int error; + int identified; + + identified = 0; + + /* Read Device info to check if chip is valid */ + error = mxt_read_block(client, MXT_ADDR_INFO_BLOCK, MXT_ID_BLOCK_SIZE, + (u8 *) buf); + + if (error < 0) { + mxt->read_fail_counter++; + dev_err(&client->dev, "Failure accessing maXTouch device\n"); + return -EIO; + } + + memcpy(id_block_data, buf, MXT_ID_BLOCK_SIZE); + + mxt->device_info.family_id = buf[0]; + mxt->device_info.variant_id = buf[1]; + mxt->device_info.major = ((buf[2] >> 4) & 0x0F); + mxt->device_info.minor = (buf[2] & 0x0F); + mxt->device_info.build = buf[3]; + mxt->device_info.x_size = buf[4]; + mxt->device_info.y_size = buf[5]; + mxt->device_info.num_objs = buf[6]; + mxt->device_info.num_nodes = mxt->device_info.x_size * + mxt->device_info.y_size; + + /* + * Check Family & Variant Info; warn if not recognized but + * still continue. + */ + + /* MXT224 */ + if (mxt->device_info.family_id == MXT224_FAMILYID) { + strcpy(mxt->device_info.family_name, "mXT224"); + + if (mxt->device_info.variant_id == MXT224_CAL_VARIANTID) { + strcpy(mxt->device_info.variant_name, "Calibrated"); + } else if (mxt->device_info.variant_id == + MXT224_UNCAL_VARIANTID) { + strcpy(mxt->device_info.variant_name, "Uncalibrated"); + } else { + dev_err(&client->dev, + "Warning: maXTouch Variant ID [%d] not " + "supported\n", + mxt->device_info.variant_id); + strcpy(mxt->device_info.variant_name, "UNKNOWN"); + /* identified = -ENXIO; */ + } + + /* MXT1386 */ + } else if (mxt->device_info.family_id == MXT1386_FAMILYID) { + strcpy(mxt->device_info.family_name, "mXT1386"); + + if (mxt->device_info.variant_id == MXT1386_CAL_VARIANTID) { + strcpy(mxt->device_info.variant_name, "Calibrated"); + } else { + dev_err(&client->dev, + "Warning: maXTouch Variant ID [%d] not " + "supported\n", + mxt->device_info.variant_id); + strcpy(mxt->device_info.variant_name, "UNKNOWN"); + /* identified = -ENXIO; */ + } + /* Unknown family ID! */ + } else { + dev_err(&client->dev, + "Warning: maXTouch Family ID [%d] not supported\n", + mxt->device_info.family_id); + strcpy(mxt->device_info.family_name, "UNKNOWN"); + strcpy(mxt->device_info.variant_name, "UNKNOWN"); + /* identified = -ENXIO; */ + } + + dev_info( + &client->dev, + "Atmel maXTouch (Family %s (%X), Variant %s (%X)) Firmware " + "version [%d.%d] Build %d\n", + mxt->device_info.family_name, + mxt->device_info.family_id, + mxt->device_info.variant_name, + mxt->device_info.variant_id, + mxt->device_info.major, + mxt->device_info.minor, + mxt->device_info.build + ); + dev_dbg( + &client->dev, + "Atmel maXTouch Configuration " + "[X: %d] x [Y: %d]\n", + mxt->device_info.x_size, + mxt->device_info.y_size + ); + return identified; +} + +/* + * Reads the object table from maXTouch chip to get object data like + * address, size, report id. For Info Block CRC calculation, already read + * id data is passed to this function too (Info Block consists of the ID + * block and object table). + * + */ +static int __devinit mxt_read_object_table(struct i2c_client *client, + struct mxt_data *mxt, + u8 *raw_id_data) +{ + u16 report_id_count; + u8 buf[MXT_OBJECT_TABLE_ELEMENT_SIZE]; + u8 *raw_ib_data; + u8 object_type; + u16 object_address; + u16 object_size; + u8 object_instances; + u8 object_report_ids; + u16 object_info_address; + u32 crc; + u32 calculated_crc; + int i; + int error; + + u8 object_instance; + u8 object_report_id; + u8 report_id; + int first_report_id; + int ib_pointer; + struct mxt_object *object_table; + + mxt_debug(DEBUG_TRACE, "maXTouch driver reading configuration\n"); + + object_table = kzalloc(sizeof(struct mxt_object) * + mxt->device_info.num_objs, + GFP_KERNEL); + if (object_table == NULL) { + printk(KERN_WARNING "maXTouch: Memory allocation failed!\n"); + error = -ENOMEM; + goto err_object_table_alloc; + } + + raw_ib_data = kmalloc(MXT_OBJECT_TABLE_ELEMENT_SIZE * + mxt->device_info.num_objs + MXT_ID_BLOCK_SIZE, + GFP_KERNEL); + if (raw_ib_data == NULL) { + printk(KERN_WARNING "maXTouch: Memory allocation failed!\n"); + error = -ENOMEM; + goto err_ib_alloc; + } + + /* Copy the ID data for CRC calculation. */ + memcpy(raw_ib_data, raw_id_data, MXT_ID_BLOCK_SIZE); + ib_pointer = MXT_ID_BLOCK_SIZE; + + mxt->object_table = object_table; + + mxt_debug(DEBUG_TRACE, "maXTouch driver Memory allocated\n"); + + object_info_address = MXT_ADDR_OBJECT_TABLE; + + report_id_count = 0; + for (i = 0; i < mxt->device_info.num_objs; i++) { + mxt_debug(DEBUG_TRACE, "Reading maXTouch at [0x%04x]: ", + object_info_address); + + error = mxt_read_block(client, object_info_address, + MXT_OBJECT_TABLE_ELEMENT_SIZE, buf); + + if (error < 0) { + mxt->read_fail_counter++; + dev_err(&client->dev, + "maXTouch Object %d could not be read\n", i); + error = -EIO; + goto err_object_read; + } + + memcpy(raw_ib_data + ib_pointer, buf, + MXT_OBJECT_TABLE_ELEMENT_SIZE); + ib_pointer += MXT_OBJECT_TABLE_ELEMENT_SIZE; + + object_type = buf[0]; + object_address = (buf[2] << 8) + buf[1]; + object_size = buf[3] + 1; + object_instances = buf[4] + 1; + object_report_ids = buf[5]; + mxt_debug(DEBUG_TRACE, "Type=%03d, Address=0x%04x, " + "Size=0x%02x, %d instances, %d report id's\n", + object_type, + object_address, + object_size, + object_instances, + object_report_ids + ); + + if (object_type == 38) + t38_size = object_size; + /* TODO: check whether object is known and supported? */ + + /* Save frequently needed info. */ + if (object_type == MXT_GEN_MESSAGEPROCESSOR_T5) { + mxt->msg_proc_addr = object_address; + mxt->message_size = object_size; + } + + object_table[i].type = object_type; + object_table[i].chip_addr = object_address; + object_table[i].size = object_size; + object_table[i].instances = object_instances; + object_table[i].num_report_ids = object_report_ids; + report_id_count += object_instances * object_report_ids; + + object_info_address += MXT_OBJECT_TABLE_ELEMENT_SIZE; + } + + mxt->rid_map = + kzalloc(sizeof(struct report_id_map) * (report_id_count + 1), + /* allocate for report_id 0, even if not used */ + GFP_KERNEL); + if (mxt->rid_map == NULL) { + printk(KERN_WARNING "maXTouch: Can't allocate memory!\n"); + error = -ENOMEM; + goto err_rid_map_alloc; + } + + mxt->messages = kzalloc(mxt->message_size * MXT_MESSAGE_BUFFER_SIZE, + GFP_KERNEL); + if (mxt->messages == NULL) { + printk(KERN_WARNING "maXTouch: Can't allocate memory!\n"); + error = -ENOMEM; + goto err_msg_alloc; + } + + mxt->last_message = kzalloc(mxt->message_size, GFP_KERNEL); + if (mxt->last_message == NULL) { + printk(KERN_WARNING "maXTouch: Can't allocate memory!\n"); + error = -ENOMEM; + goto err_msg_alloc; + } + + mxt->report_id_count = report_id_count; + if (report_id_count > 254) { /* 0 & 255 are reserved */ + dev_err(&client->dev, + "Too many maXTouch report id's [%d]\n", + report_id_count); + error = -ENXIO; + goto err_max_rid; + } + + /* Create a mapping from report id to object type */ + report_id = 1; /* Start from 1, 0 is reserved. */ + + /* Create table associating report id's with objects & instances */ + for (i = 0; i < mxt->device_info.num_objs; i++) { + for (object_instance = 0; + object_instance < object_table[i].instances; + object_instance++){ + first_report_id = report_id; + for (object_report_id = 0; + object_report_id < object_table[i].num_report_ids; + object_report_id++) { + mxt->rid_map[report_id].object = + object_table[i].type; + mxt->rid_map[report_id].instance = + object_instance; + mxt->rid_map[report_id].first_rid = + first_report_id; + report_id++; + } + } + } + + /* Read 3 byte CRC */ + error = mxt_read_block(client, object_info_address, 3, buf); + if (error < 0) { + mxt->read_fail_counter++; + dev_err(&client->dev, "Error reading CRC\n"); + } + + crc = (buf[2] << 16) | (buf[1] << 8) | buf[0]; + + if (calculate_infoblock_crc(&calculated_crc, raw_ib_data, + ib_pointer)) { + printk(KERN_WARNING "Error while calculating CRC!\n"); + calculated_crc = 0; + } + kfree(raw_ib_data); + + mxt_debug(DEBUG_TRACE, "\nReported info block CRC = 0x%6X\n", crc); + mxt_debug(DEBUG_TRACE, "Calculated info block CRC = 0x%6X\n\n", + calculated_crc); + + if (crc == calculated_crc) { + mxt->info_block_crc = crc; + } else { + mxt->info_block_crc = 0; + printk(KERN_ALERT "maXTouch: Info block CRC invalid!\n"); + } + + if (debug >= DEBUG_VERBOSE) { + + dev_info(&client->dev, "maXTouch: %d Objects\n", + mxt->device_info.num_objs); + + for (i = 0; i < mxt->device_info.num_objs; i++) { + dev_info(&client->dev, "Type:\t\t\t[%d]: %s\n", + object_table[i].type, + object_type_name[object_table[i].type]); + dev_info(&client->dev, "\tAddress:\t0x%04X\n", + object_table[i].chip_addr); + dev_info(&client->dev, "\tSize:\t\t%d Bytes\n", + object_table[i].size); + dev_info(&client->dev, "\tInstances:\t%d\n", + object_table[i].instances); + dev_info(&client->dev, "\tReport Id's:\t%d\n", + object_table[i].num_report_ids); + } + } + + return 0; + + +err_max_rid: + kfree(mxt->last_message); +err_msg_alloc: + kfree(mxt->rid_map); +err_rid_map_alloc: +err_object_read: + kfree(raw_ib_data); +err_ib_alloc: + kfree(object_table); +err_object_table_alloc: + return error; +} + +#if defined(CONFIG_PM) +static int mxt_suspend(struct device *dev) +{ + struct mxt_data *mxt = dev_get_drvdata(dev); + int error, i; + u8 t7_deepsl_data[T7_DATA_SIZE]; + u16 t7_addr; + + if (device_may_wakeup(dev)) { + enable_irq_wake(mxt->irq); + return 0; + } + + disable_irq(mxt->irq); + + flush_delayed_work_sync(&mxt->dwork); + + for (i = 0; i < T7_DATA_SIZE; i++) + t7_deepsl_data[i] = 0; + + t7_addr = MXT_BASE_ADDR(MXT_GEN_POWERCONFIG_T7, mxt); + /* save current power state values */ + error = mxt_read_block(mxt->client, t7_addr, + ARRAY_SIZE(mxt->t7_data), mxt->t7_data); + if (error < 0) + goto err_enable_irq; + + /* configure deep sleep mode */ + error = mxt_write_block(mxt->client, t7_addr, + ARRAY_SIZE(t7_deepsl_data), t7_deepsl_data); + if (error < 0) + goto err_enable_irq; + + /* power off the device */ + if (mxt->power_on) { + error = mxt->power_on(false); + if (error) { + dev_err(dev, "power off failed"); + goto err_write_block; + } + } + mxt->is_suspended = true; + return 0; + +err_write_block: + mxt_write_block(mxt->client, t7_addr, + ARRAY_SIZE(mxt->t7_data), mxt->t7_data); +err_enable_irq: + enable_irq(mxt->irq); + return error; +} + +static int mxt_resume(struct device *dev) +{ + struct mxt_data *mxt = dev_get_drvdata(dev); + int error; + u16 t7_addr; + + if (device_may_wakeup(dev)) { + disable_irq_wake(mxt->irq); + return 0; + } + + if (!mxt->is_suspended) + return 0; + + /* power on the device */ + if (mxt->power_on) { + error = mxt->power_on(true); + if (error) { + dev_err(dev, "power on failed"); + return error; + } + } + + t7_addr = MXT_BASE_ADDR(MXT_GEN_POWERCONFIG_T7, mxt); + /* restore the old power state values */ + error = mxt_write_block(mxt->client, t7_addr, + ARRAY_SIZE(mxt->t7_data), mxt->t7_data); + if (error < 0) + goto err_write_block; + + enable_irq(mxt->irq); + + mxt->is_suspended = false; + + /* Make sure we just didn't miss a interrupt. */ + if (mxt->read_chg() == 0) + schedule_delayed_work(&mxt->dwork, 0); + + return 0; + +err_write_block: + if (mxt->power_on) + mxt->power_on(false); + return error; +} + +#if defined(CONFIG_HAS_EARLYSUSPEND) +static void mxt_early_suspend(struct early_suspend *h) +{ + struct mxt_data *mxt = container_of(h, struct mxt_data, early_suspend); + + mxt_suspend(&mxt->client->dev); +} + +static void mxt_late_resume(struct early_suspend *h) +{ + struct mxt_data *mxt = container_of(h, struct mxt_data, early_suspend); + + mxt_resume(&mxt->client->dev); +} +#endif + +static const struct dev_pm_ops mxt_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = mxt_suspend, + .resume = mxt_resume, +#endif +}; +#endif + +static int __devinit mxt_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mxt_data *mxt; + struct mxt_platform_data *pdata; + struct input_dev *input; + u8 *id_data; + u8 *t38_data; + u16 t38_addr; + int error; + + mxt_debug(DEBUG_INFO, "mXT224: mxt_probe\n"); + + if (client == NULL) { + pr_debug("maXTouch: client == NULL\n"); + return -EINVAL; + } else if (client->adapter == NULL) { + pr_debug("maXTouch: client->adapter == NULL\n"); + return -EINVAL; + } else if (&client->dev == NULL) { + pr_debug("maXTouch: client->dev == NULL\n"); + return -EINVAL; + } else if (&client->adapter->dev == NULL) { + pr_debug("maXTouch: client->adapter->dev == NULL\n"); + return -EINVAL; + } else if (id == NULL) { + pr_debug("maXTouch: id == NULL\n"); + return -EINVAL; + } + + /* Enable runtime PM ops, start in ACTIVE mode */ + error = pm_runtime_set_active(&client->dev); + if (error < 0) + dev_dbg(&client->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&client->dev); + + mxt_debug(DEBUG_INFO, "maXTouch driver v. %s\n", DRIVER_VERSION); + mxt_debug(DEBUG_INFO, "\t \"%s\"\n", client->name); + mxt_debug(DEBUG_INFO, "\taddr:\t0x%04x\n", client->addr); + mxt_debug(DEBUG_INFO, "\tirq:\t%d\n", client->irq); + mxt_debug(DEBUG_INFO, "\tflags:\t0x%04x\n", client->flags); + mxt_debug(DEBUG_INFO, "\tadapter:\"%s\"\n", client->adapter->name); + mxt_debug(DEBUG_INFO, "\tdevice:\t\"%s\"\n", client->dev.init_name); + + mxt_debug(DEBUG_TRACE, "maXTouch driver functionality OK\n"); + + /* Allocate structure - we need it to identify device */ + mxt = kzalloc(sizeof(struct mxt_data), GFP_KERNEL); + if (mxt == NULL) { + dev_err(&client->dev, "insufficient memory\n"); + error = -ENOMEM; + goto err_mxt_alloc; + } + + id_data = kmalloc(MXT_ID_BLOCK_SIZE, GFP_KERNEL); + if (id_data == NULL) { + dev_err(&client->dev, "insufficient memory\n"); + error = -ENOMEM; + goto err_id_alloc; + } + + input = input_allocate_device(); + if (!input) { + dev_err(&client->dev, "error allocating input device\n"); + error = -ENOMEM; + goto err_input_dev_alloc; + } + + /* Initialize Platform data */ + + pdata = client->dev.platform_data; + if (pdata == NULL) { + dev_err(&client->dev, "platform data is required!\n"); + error = -EINVAL; + goto err_pdata; + } + if (debug >= DEBUG_TRACE) + printk(KERN_INFO "Platform OK: pdata = 0x%08x\n", + (unsigned int) pdata); + + mxt->is_suspended = false; + mxt->read_fail_counter = 0; + mxt->message_counter = 0; + + if (pdata->min_x) + mxt->min_x_val = pdata->min_x; + else + mxt->min_x_val = 0; + + if (pdata->min_y) + mxt->min_y_val = pdata->min_y; + else + mxt->min_y_val = 0; + + mxt->max_x_val = pdata->max_x; + mxt->max_y_val = pdata->max_y; + + /* Get data that is defined in board specific code. */ + mxt->init_hw = pdata->init_platform_hw; + mxt->exit_hw = pdata->exit_platform_hw; + mxt->power_on = pdata->power_on; + mxt->read_chg = pdata->read_chg; + + if (pdata->valid_interrupt != NULL) + mxt->valid_interrupt = pdata->valid_interrupt; + else + mxt->valid_interrupt = mxt_valid_interrupt_dummy; + + if (mxt->init_hw) { + error = mxt->init_hw(client); + if (error) { + dev_err(&client->dev, "hw init failed"); + goto err_init_hw; + } + } + + /* power on the device */ + if (mxt->power_on) { + error = mxt->power_on(true); + if (error) { + dev_err(&client->dev, "power on failed"); + goto err_pwr_on; + } + } + + if (debug >= DEBUG_TRACE) + printk(KERN_INFO "maXTouch driver identifying chip\n"); + + if (mxt_identify(client, mxt, id_data) < 0) { + dev_err(&client->dev, "Chip could not be identified\n"); + error = -ENODEV; + goto err_identify; + } + /* Chip is valid and active. */ + if (debug >= DEBUG_TRACE) + printk(KERN_INFO "maXTouch driver allocating input device\n"); + + mxt->client = client; + mxt->input = input; + + INIT_DELAYED_WORK(&mxt->dwork, mxt_worker); + mutex_init(&mxt->debug_mutex); + mutex_init(&mxt->msg_mutex); + mxt_debug(DEBUG_TRACE, "maXTouch driver creating device name\n"); + + snprintf( + mxt->phys_name, + sizeof(mxt->phys_name), + "%s/input0", + dev_name(&client->dev) + ); + input->name = "Atmel maXTouch Touchscreen controller"; + input->phys = mxt->phys_name; + input->id.bustype = BUS_I2C; + input->dev.parent = &client->dev; + + mxt_debug(DEBUG_INFO, "maXTouch name: \"%s\"\n", input->name); + mxt_debug(DEBUG_INFO, "maXTouch phys: \"%s\"\n", input->phys); + mxt_debug(DEBUG_INFO, "maXTouch driver setting abs parameters\n"); + + set_bit(BTN_TOUCH, input->keybit); + + /* Single touch */ + input_set_abs_params(input, ABS_X, mxt->min_x_val, + mxt->max_x_val, 0, 0); + input_set_abs_params(input, ABS_Y, mxt->min_y_val, + mxt->max_y_val, 0, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, MXT_MAX_REPORTED_PRESSURE, + 0, 0); + input_set_abs_params(input, ABS_TOOL_WIDTH, 0, MXT_MAX_REPORTED_WIDTH, + 0, 0); + + /* Multitouch */ + input_set_abs_params(input, ABS_MT_POSITION_X, mxt->min_x_val, + mxt->max_x_val, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, mxt->min_y_val, + mxt->max_y_val, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, MXT_MAX_TOUCH_SIZE, + 0, 0); + input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, MXT_MAX_NUM_TOUCHES, + 0, 0); + + __set_bit(EV_ABS, input->evbit); + __set_bit(EV_SYN, input->evbit); + __set_bit(EV_KEY, input->evbit); + __set_bit(EV_MSC, input->evbit); + input->mscbit[0] = BIT_MASK(MSC_GESTURE); + + mxt_debug(DEBUG_TRACE, "maXTouch driver setting client data\n"); + i2c_set_clientdata(client, mxt); + mxt_debug(DEBUG_TRACE, "maXTouch driver setting drv data\n"); + input_set_drvdata(input, mxt); + mxt_debug(DEBUG_TRACE, "maXTouch driver input register device\n"); + error = input_register_device(mxt->input); + if (error < 0) { + dev_err(&client->dev, + "Failed to register input device\n"); + goto err_register_device; + } + + error = mxt_read_object_table(client, mxt, id_data); + if (error < 0) + goto err_read_ot; + + + /* Create debugfs entries. */ + mxt->debug_dir = debugfs_create_dir("maXTouch", NULL); + if (mxt->debug_dir == ERR_PTR(-ENODEV)) { + /* debugfs is not enabled. */ + printk(KERN_WARNING "debugfs not enabled in kernel\n"); + } else if (mxt->debug_dir == NULL) { + printk(KERN_WARNING "error creating debugfs dir\n"); + } else { + mxt_debug(DEBUG_TRACE, "created \"maXTouch\" debugfs dir\n"); + + debugfs_create_file("deltas", S_IRUSR, mxt->debug_dir, mxt, + &delta_fops); + debugfs_create_file("refs", S_IRUSR, mxt->debug_dir, mxt, + &refs_fops); + } + + /* Create character device nodes for reading & writing registers */ + mxt->mxt_class = class_create(THIS_MODULE, "maXTouch_memory"); + if (IS_ERR(mxt->mxt_class)){ + printk(KERN_WARNING "class create failed! exiting..."); + goto err_class_create; + + } + /* 2 numbers; one for memory and one for messages */ + error = alloc_chrdev_region(&mxt->dev_num, 0, 2, + "maXTouch_memory"); + mxt_debug(DEBUG_VERBOSE, + "device number %d allocated!\n", MAJOR(mxt->dev_num)); + if (error){ + printk(KERN_WARNING "Error registering device\n"); + } + cdev_init(&mxt->cdev, &mxt_memory_fops); + cdev_init(&mxt->cdev_messages, &mxt_message_fops); + + mxt_debug(DEBUG_VERBOSE, "cdev initialized\n"); + mxt->cdev.owner = THIS_MODULE; + mxt->cdev_messages.owner = THIS_MODULE; + + error = cdev_add(&mxt->cdev, mxt->dev_num, 1); + if (error){ + printk(KERN_WARNING "Bad cdev\n"); + } + + error = cdev_add(&mxt->cdev_messages, mxt->dev_num + 1, 1); + if (error){ + printk(KERN_WARNING "Bad cdev\n"); + } + + mxt_debug(DEBUG_VERBOSE, "cdev added\n"); + + device_create(mxt->mxt_class, NULL, MKDEV(MAJOR(mxt->dev_num), 0), NULL, + "maXTouch"); + + device_create(mxt->mxt_class, NULL, MKDEV(MAJOR(mxt->dev_num), 1), NULL, + "maXTouch_messages"); + + mxt->msg_buffer_startp = 0; + mxt->msg_buffer_endp = 0; + + /* Allocate the interrupt */ + mxt_debug(DEBUG_TRACE, "maXTouch driver allocating interrupt...\n"); + mxt->irq = client->irq; + mxt->valid_irq_counter = 0; + mxt->invalid_irq_counter = 0; + mxt->irq_counter = 0; + if (mxt->irq) { + /* Try to request IRQ with falling edge first. This is + * not always supported. If it fails, try with any edge. */ + error = request_irq(mxt->irq, + mxt_irq_handler, + IRQF_TRIGGER_FALLING, + client->dev.driver->name, + mxt); + if (error < 0) { + /* TODO: why only 0 works on STK1000? */ + error = request_irq(mxt->irq, + mxt_irq_handler, + 0, + client->dev.driver->name, + mxt); + } + + if (error < 0) { + dev_err(&client->dev, + "failed to allocate irq %d\n", mxt->irq); + goto err_irq; + } + } + + if (debug > DEBUG_INFO) + dev_info(&client->dev, "touchscreen, irq %d\n", mxt->irq); + + t38_data = kmalloc(t38_size*sizeof(u8), GFP_KERNEL); + + if (t38_data == NULL) { + dev_err(&client->dev, "insufficient memory\n"); + error = -ENOMEM; + goto err_t38; + } + + t38_addr = MXT_BASE_ADDR(MXT_USER_INFO_T38, mxt); + mxt_read_block(client, t38_addr, t38_size, t38_data); + dev_info(&client->dev, "VERSION:%02x.%02x.%02x, DATE: %d/%d/%d\n", + t38_data[0], t38_data[1], t38_data[2], + t38_data[3], t38_data[4], t38_data[5]); + + /* Schedule a worker routine to read any messages that might have + * been sent before interrupts were enabled. */ + cancel_delayed_work(&mxt->dwork); + schedule_delayed_work(&mxt->dwork, 0); + kfree(t38_data); + kfree(id_data); + + device_init_wakeup(&client->dev, pdata->wakeup); +#if defined(CONFIG_HAS_EARLYSUSPEND) + mxt->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + MXT_SUSPEND_LEVEL; + mxt->early_suspend.suspend = mxt_early_suspend; + mxt->early_suspend.resume = mxt_late_resume; + register_early_suspend(&mxt->early_suspend); +#endif + + return 0; + +err_t38: + free_irq(mxt->irq, mxt); +err_irq: + kfree(mxt->rid_map); + kfree(mxt->object_table); + kfree(mxt->last_message); +err_class_create: + if (mxt->debug_dir) + debugfs_remove(mxt->debug_dir); + kfree(mxt->last_message); + kfree(mxt->rid_map); + kfree(mxt->object_table); +err_read_ot: + input_unregister_device(mxt->input); + mxt->input = NULL; +err_register_device: + mutex_destroy(&mxt->debug_mutex); + mutex_destroy(&mxt->msg_mutex); +err_identify: + if (mxt->power_on) + mxt->power_on(false); +err_pwr_on: + if (mxt->exit_hw != NULL) + mxt->exit_hw(client); +err_init_hw: +err_pdata: + input_free_device(input); +err_input_dev_alloc: + kfree(id_data); +err_id_alloc: + kfree(mxt); +err_mxt_alloc: + pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(&client->dev); + return error; +} + +static int __devexit mxt_remove(struct i2c_client *client) +{ + struct mxt_data *mxt; + + pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(&client->dev); + + mxt = i2c_get_clientdata(client); + + /* Remove debug dir entries */ + debugfs_remove_recursive(mxt->debug_dir); + + device_init_wakeup(&client->dev, 0); +#if defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&mxt->early_suspend); +#endif + + if (mxt != NULL) { + if (mxt->power_on) + mxt->power_on(false); + + if (mxt->exit_hw != NULL) + mxt->exit_hw(client); + + if (mxt->irq) { + free_irq(mxt->irq, mxt); + } + + unregister_chrdev_region(mxt->dev_num, 2); + device_destroy(mxt->mxt_class, MKDEV(MAJOR(mxt->dev_num), 0)); + device_destroy(mxt->mxt_class, MKDEV(MAJOR(mxt->dev_num), 1)); + cdev_del(&mxt->cdev); + cdev_del(&mxt->cdev_messages); + cancel_delayed_work_sync(&mxt->dwork); + input_unregister_device(mxt->input); + class_destroy(mxt->mxt_class); + debugfs_remove(mxt->debug_dir); + + kfree(mxt->rid_map); + kfree(mxt->object_table); + kfree(mxt->last_message); + } + kfree(mxt); + + i2c_set_clientdata(client, NULL); + if (debug >= DEBUG_TRACE) + dev_info(&client->dev, "Touchscreen unregistered\n"); + + return 0; +} + +static const struct i2c_device_id mxt_idtable[] = { + {"maXTouch", 0,}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, mxt_idtable); + +static struct i2c_driver mxt_driver = { + .driver = { + .name = "maXTouch", + .owner = THIS_MODULE, +#if defined(CONFIG_PM) + .pm = &mxt_pm_ops, +#endif + }, + + .id_table = mxt_idtable, + .probe = mxt_probe, + .remove = __devexit_p(mxt_remove), +}; + +static int __init mxt_init(void) +{ + int err; + err = i2c_add_driver(&mxt_driver); + if (err) { + printk(KERN_WARNING "Adding maXTouch driver failed " + "(errno = %d)\n", err); + } else { + mxt_debug(DEBUG_TRACE, "Successfully added driver %s\n", + mxt_driver.driver.name); + } + return err; +} + +static void __exit mxt_cleanup(void) +{ + i2c_del_driver(&mxt_driver); +} + + +module_init(mxt_init); +module_exit(mxt_cleanup); + +MODULE_AUTHOR("Iiro Valkonen"); +MODULE_DESCRIPTION("Driver for Atmel maXTouch Touchscreen Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/cy8c_tmg_ts.c b/drivers/input/touchscreen/cy8c_tmg_ts.c new file mode 100644 index 00000000000..f48374eb30e --- /dev/null +++ b/drivers/input/touchscreen/cy8c_tmg_ts.c @@ -0,0 +1,467 @@ +/* drivers/input/touchscreen/cy8c_tmg_ts.c + * + * Copyright (C) 2007-2008 HTC Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CY8C_REG_START_NEW_SCAN 0x0F +#define CY8C_REG_INTR_STATUS 0x3C +#define CY8C_REG_VERSION 0x3E + +struct cy8c_ts_data { + struct i2c_client *client; + struct input_dev *input_dev; + int use_irq; + struct hrtimer timer; + struct work_struct work; + uint16_t version; + int (*power) (int on); + struct early_suspend early_suspend; +}; + +struct workqueue_struct *cypress_touch_wq; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void cy8c_ts_early_suspend(struct early_suspend *h); +static void cy8c_ts_late_resume(struct early_suspend *h); +#endif + +uint16_t sample_count, X_mean, Y_mean, first_touch; + +static s32 cy8c_read_word_data(struct i2c_client *client, + u8 command, uint16_t * data) +{ + s32 ret = i2c_smbus_read_word_data(client, command); + if (ret != -1) { + *data = (u16) ((ret << 8) | (ret >> 8)); + } + return ret; +} + +static int cy8c_init_panel(struct cy8c_ts_data *ts) +{ + int ret; + sample_count = X_mean = Y_mean = first_touch = 0; + + /* clean intr busy */ + ret = i2c_smbus_write_byte_data(ts->client, CY8C_REG_INTR_STATUS, + 0x00); + if (ret < 0) { + dev_err(&ts->client->dev, + "cy8c_init_panel failed for clean intr busy\n"); + goto exit; + } + + /* start new scan */ + ret = i2c_smbus_write_byte_data(ts->client, CY8C_REG_START_NEW_SCAN, + 0x01); + if (ret < 0) { + dev_err(&ts->client->dev, + "cy8c_init_panel failed for start new scan\n"); + goto exit; + } + +exit: + return ret; +} + +static void cy8c_ts_reset(struct i2c_client *client) +{ + struct cy8c_ts_data *ts = i2c_get_clientdata(client); + + if (ts->power) { + ts->power(0); + msleep(10); + ts->power(1); + msleep(10); + } + + cy8c_init_panel(ts); +} + +static void cy8c_ts_work_func(struct work_struct *work) +{ + struct cy8c_ts_data *ts = container_of(work, struct cy8c_ts_data, work); + uint16_t x1, y1, x2, y2; + uint8_t is_touch, start_reg, force, area, finger2_pressed; + uint8_t buf[11]; + struct i2c_msg msg[2]; + int ret = 0; + + x2 = y2 = 0; + + /*printk("%s: enter\n",__func__);*/ + is_touch = i2c_smbus_read_byte_data(ts->client, 0x20); + dev_dbg(&ts->client->dev, "fIsTouch %d,\n", is_touch); + if (is_touch < 0 || is_touch > 3) { + pr_err("%s: invalid is_touch = %d\n", __func__, is_touch); + cy8c_ts_reset(ts->client); + msleep(10); + goto done; + } + + msg[0].addr = ts->client->addr; + msg[0].flags = 0; + msg[0].len = 1; + start_reg = 0x16; + msg[0].buf = &start_reg; + + msg[1].addr = ts->client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = sizeof(buf); + msg[1].buf = buf; + + ret = i2c_transfer(ts->client->adapter, msg, 2); + if (ret < 0) + goto done; + + /* parse data */ + force = buf[0]; + area = buf[1]; + x1 = (buf[2] << 8) | buf[3]; + y1 = (buf[6] << 8) | buf[7]; + is_touch = buf[10]; + + if (is_touch == 2) { + x2 = (buf[4] << 8) | buf[5]; + y2 = (buf[8] << 8) | buf[9]; + finger2_pressed = 1; + } + + dev_dbg(&ts->client->dev, + "bFingerForce %d, bFingerArea %d \n", force, area); + dev_dbg(&ts->client->dev, "x1: %d, y1: %d \n", x1, y1); + if (finger2_pressed) + dev_dbg(&ts->client->dev, "x2: %d, y2: %d \n", x2, y2); + + /* drop the first one? */ + if ((is_touch == 1) && (first_touch == 0)) { + first_touch = 1; + goto done; + } + + if (!first_touch) + goto done; + + if (is_touch == 2) + finger2_pressed = 1; + + input_report_abs(ts->input_dev, ABS_X, x1); + input_report_abs(ts->input_dev, ABS_Y, y1); + input_report_abs(ts->input_dev, ABS_PRESSURE, force); + input_report_abs(ts->input_dev, ABS_TOOL_WIDTH, area); + input_report_key(ts->input_dev, BTN_TOUCH, is_touch); + input_report_key(ts->input_dev, BTN_2, finger2_pressed); + + if (finger2_pressed) { + input_report_abs(ts->input_dev, ABS_HAT0X, x2); + input_report_abs(ts->input_dev, ABS_HAT0Y, y2); + } + input_sync(ts->input_dev); + +done: + if (is_touch == 0) + first_touch = sample_count = 0; + + /* prepare for next intr */ + i2c_smbus_write_byte_data(ts->client, CY8C_REG_INTR_STATUS, 0x00); + if (!ts->use_irq) + hrtimer_start(&ts->timer, ktime_set(0, 12500000), HRTIMER_MODE_REL); + else + enable_irq(ts->client->irq); +} + +static enum hrtimer_restart cy8c_ts_timer_func(struct hrtimer *timer) +{ + struct cy8c_ts_data *ts; + + ts = container_of(timer, struct cy8c_ts_data, timer); + queue_work(cypress_touch_wq, &ts->work); + return HRTIMER_NORESTART; +} + +static irqreturn_t cy8c_ts_irq_handler(int irq, void *dev_id) +{ + struct cy8c_ts_data *ts = dev_id; + + disable_irq_nosync(ts->client->irq); + queue_work(cypress_touch_wq, &ts->work); + return IRQ_HANDLED; +} + +static int cy8c_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cy8c_ts_data *ts; + struct cy8c_i2c_platform_data *pdata; + uint16_t panel_version; + int ret = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "need I2C_FUNC_I2C\n"); + ret = -ENODEV; + goto err_check_functionality_failed; + } + + ts = kzalloc(sizeof(struct cy8c_ts_data), GFP_KERNEL); + if (ts == NULL) { + dev_err(&client->dev, "allocate cy8c_ts_data failed\n"); + ret = -ENOMEM; + goto err_alloc_data_failed; + } + + INIT_WORK(&ts->work, cy8c_ts_work_func); + ts->client = client; + i2c_set_clientdata(client, ts); + + pdata = client->dev.platform_data; + if (pdata) { + ts->version = pdata->version; + ts->power = pdata->power; + } + + if (ts->power) { + ret = ts->power(1); + msleep(10); + if (ret < 0) { + dev_err(&client->dev, "power on failed\n"); + goto err_power_failed; + } + } + + ret = cy8c_read_word_data(ts->client, CY8C_REG_VERSION, &panel_version); + if (ret < 0) { + dev_err(&client->dev, "init panel failed\n"); + goto err_detect_failed; + } + dev_info(&client->dev, "Panel Version %04X\n", panel_version); + if (pdata) { + while (pdata->version > panel_version) { + dev_info(&client->dev, "old tp detected, " + "panel version = %x\n", panel_version); + pdata++; + } + } + + ret = cy8c_init_panel(ts); + if (ret < 0) { + dev_err(&client->dev, "init panel failed\n"); + goto err_detect_failed; + } + + ts->input_dev = input_allocate_device(); + if (ts->input_dev == NULL) { + ret = -ENOMEM; + dev_err(&client->dev, "Failed to allocate input device\n"); + goto err_input_dev_alloc_failed; + } + ts->input_dev->name = "cy8c-touchscreen"; + + set_bit(EV_SYN, ts->input_dev->evbit); + set_bit(EV_ABS, ts->input_dev->evbit); + set_bit(EV_KEY, ts->input_dev->evbit); + input_set_capability(ts->input_dev, EV_KEY, BTN_TOUCH); + input_set_capability(ts->input_dev, EV_KEY, BTN_2); + + input_set_abs_params(ts->input_dev, ABS_X, + pdata->abs_x_min, pdata->abs_x_max, 5, 0); + input_set_abs_params(ts->input_dev, ABS_Y, + pdata->abs_y_min, pdata->abs_y_max, 5, 0); + input_set_abs_params(ts->input_dev, ABS_HAT0X, + pdata->abs_x_min, pdata->abs_x_max, 0, 0); + input_set_abs_params(ts->input_dev, ABS_HAT0Y, + pdata->abs_y_min, pdata->abs_y_max, 0, 0); + input_set_abs_params(ts->input_dev, ABS_PRESSURE, + pdata->abs_pressure_min, pdata->abs_pressure_max, + 0, 0); + input_set_abs_params(ts->input_dev, ABS_TOOL_WIDTH, + pdata->abs_width_min, pdata->abs_width_max, 0, 0); + + ret = input_register_device(ts->input_dev); + if (ret) { + dev_err(&client->dev, + "cy8c_ts_probe: Unable to register %s input device\n", + ts->input_dev->name); + goto err_input_register_device_failed; + } + + if (client->irq) { + ret = request_irq(client->irq, cy8c_ts_irq_handler, + IRQF_TRIGGER_LOW, CYPRESS_TMG_NAME, ts); + if (ret == 0) + ts->use_irq = 1; + else + dev_err(&client->dev, "request_irq failed\n"); + } + + if (!ts->use_irq) { + hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ts->timer.function = cy8c_ts_timer_func; + hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ts->early_suspend.suspend = cy8c_ts_early_suspend; + ts->early_suspend.resume = cy8c_ts_late_resume; + register_early_suspend(&ts->early_suspend); +#endif + + dev_info(&client->dev, "Start touchscreen %s in %s mode\n", + ts->input_dev->name, (ts->use_irq ? "interrupt" : "polling")); + + return 0; + +err_input_register_device_failed: + input_free_device(ts->input_dev); + +err_input_dev_alloc_failed: + if (ts->power) + ts->power(0); + +err_detect_failed: +err_power_failed: + kfree(ts); + +err_alloc_data_failed: +err_check_functionality_failed: + return ret; +} + +static int cy8c_ts_remove(struct i2c_client *client) +{ + struct cy8c_ts_data *ts = i2c_get_clientdata(client); + + unregister_early_suspend(&ts->early_suspend); + + if (ts->use_irq) + free_irq(client->irq, ts); + else + hrtimer_cancel(&ts->timer); + + input_unregister_device(ts->input_dev); + kfree(ts); + + return 0; +} + +static int cy8c_ts_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cy8c_ts_data *ts = i2c_get_clientdata(client); + int ret; + + if (ts->use_irq) + disable_irq_nosync(client->irq); + else + hrtimer_cancel(&ts->timer); + + ret = cancel_work_sync(&ts->work); + if (ret && ts->use_irq) + enable_irq(client->irq); + + if (ts->power) + ts->power(0); + + return 0; +} + +static int cy8c_ts_resume(struct i2c_client *client) +{ + int ret; + struct cy8c_ts_data *ts = i2c_get_clientdata(client); + + if (ts->power) { + ret = ts->power(1); + if (ret < 0) + dev_err(&client->dev, + "cy8c_ts_resume power on failed\n"); + msleep(10); + + cy8c_init_panel(ts); + } + + if (ts->use_irq) + enable_irq(client->irq); + else + hrtimer_start(&ts->timer, ktime_set(1, 0), HRTIMER_MODE_REL); + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void cy8c_ts_early_suspend(struct early_suspend *h) +{ + struct cy8c_ts_data *ts; + ts = container_of(h, struct cy8c_ts_data, early_suspend); + cy8c_ts_suspend(ts->client, PMSG_SUSPEND); +} + +static void cy8c_ts_late_resume(struct early_suspend *h) +{ + struct cy8c_ts_data *ts; + ts = container_of(h, struct cy8c_ts_data, early_suspend); + cy8c_ts_resume(ts->client); +} +#endif + +static const struct i2c_device_id cy8c_ts_i2c_id[] = { + {CYPRESS_TMG_NAME, 0}, + {} +}; + +static struct i2c_driver cy8c_ts_driver = { + .id_table = cy8c_ts_i2c_id, + .probe = cy8c_ts_probe, + .remove = cy8c_ts_remove, +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = cy8c_ts_suspend, + .resume = cy8c_ts_resume, +#endif + .driver = { + .name = CYPRESS_TMG_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __devinit cy8c_ts_init(void) +{ + cypress_touch_wq = create_singlethread_workqueue("cypress_touch_wq"); + if (!cypress_touch_wq) + return -ENOMEM; + + return i2c_add_driver(&cy8c_ts_driver); +} + +static void __exit cy8c_ts_exit(void) +{ + if (cypress_touch_wq) + destroy_workqueue(cypress_touch_wq); + + i2c_del_driver(&cy8c_ts_driver); +} + +module_init(cy8c_ts_init); +module_exit(cy8c_ts_exit); + +MODULE_DESCRIPTION("Cypress TMG Touchscreen Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/touchscreen/cy8c_ts.c b/drivers/input/touchscreen/cy8c_ts.c new file mode 100644 index 00000000000..0b6406cc46b --- /dev/null +++ b/drivers/input/touchscreen/cy8c_ts.c @@ -0,0 +1,783 @@ +/* Source for: + * Cypress CY8CTMA300 Prototype touchscreen driver. + * drivers/input/touchscreen/cy8c_ts.c + * + * Copyright (C) 2009, 2010 Cypress Semiconductor, Inc. + * Copyright (c) 2010, 2011 Code Aurora Forum. 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, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Cypress reserves the right to make changes without further notice + * to the materials described herein. Cypress does not assume any + * liability arising out of the application described herein. + * + * Contact Cypress Semiconductor at www.cypress.com + * + * History: + * (C) 2010 Cypress - Update for GPL distribution + * (C) 2009 Cypress - Assume maintenance ownership + * (C) 2009 Enea - Original prototype + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include + +/* Early-suspend level */ +#define CY8C_TS_SUSPEND_LEVEL 1 +#endif + +#define CY8CTMA300 0x0 +#define CY8CTMG200 0x1 + +#define INVALID_DATA 0xff + +#define TOUCHSCREEN_TIMEOUT (msecs_to_jiffies(10)) +#define INITIAL_DELAY (msecs_to_jiffies(25000)) + +struct cy8c_ts_data { + u8 x_index; + u8 y_index; + u8 z_index; + u8 id_index; + u8 touch_index; + u8 data_reg; + u8 status_reg; + u8 data_size; + u8 touch_bytes; + u8 update_data; + u8 touch_meta_data; + u8 finger_size; +}; + +static struct cy8c_ts_data devices[] = { + [0] = { + .x_index = 6, + .y_index = 4, + .z_index = 3, + .id_index = 0, + .data_reg = 0x3, + .status_reg = 0x1, + .update_data = 0x4, + .touch_bytes = 8, + .touch_meta_data = 3, + .finger_size = 70, + }, + [1] = { + .x_index = 2, + .y_index = 4, + .id_index = 6, + .data_reg = 0x6, + .status_reg = 0x5, + .update_data = 0x1, + .touch_bytes = 12, + .finger_size = 70, + }, +}; + +struct cy8c_ts { + struct i2c_client *client; + struct input_dev *input; + struct delayed_work work; + struct workqueue_struct *wq; + struct cy8c_ts_platform_data *pdata; + struct cy8c_ts_data *dd; + u8 *touch_data; + u8 device_id; + u8 prev_touches; + bool is_suspended; + bool int_pending; + struct mutex sus_lock; + u32 pen_irq; +#if defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif +}; + +static inline u16 join_bytes(u8 a, u8 b) +{ + u16 ab = 0; + ab = ab | a; + ab = ab << 8 | b; + return ab; +} + +static s32 cy8c_ts_write_reg_u8(struct i2c_client *client, u8 reg, u8 val) +{ + s32 data; + + data = i2c_smbus_write_byte_data(client, reg, val); + if (data < 0) + dev_err(&client->dev, "error %d in writing reg 0x%x\n", + data, reg); + + return data; +} + +static s32 cy8c_ts_read_reg_u8(struct i2c_client *client, u8 reg) +{ + s32 data; + + data = i2c_smbus_read_byte_data(client, reg); + if (data < 0) + dev_err(&client->dev, "error %d in reading reg 0x%x\n", + data, reg); + + return data; +} + +static int cy8c_ts_read(struct i2c_client *client, u8 reg, u8 *buf, int num) +{ + struct i2c_msg xfer_msg[2]; + + xfer_msg[0].addr = client->addr; + xfer_msg[0].len = 1; + xfer_msg[0].flags = 0; + xfer_msg[0].buf = ® + + xfer_msg[1].addr = client->addr; + xfer_msg[1].len = num; + xfer_msg[1].flags = I2C_M_RD; + xfer_msg[1].buf = buf; + + return i2c_transfer(client->adapter, xfer_msg, 2); +} + +static void report_data(struct cy8c_ts *ts, u16 x, u16 y, u8 pressure, u8 id) +{ + if (ts->pdata->swap_xy) + swap(x, y); + + /* handle inverting coordinates */ + if (ts->pdata->invert_x) + x = ts->pdata->res_x - x; + if (ts->pdata->invert_y) + y = ts->pdata->res_y - y; + + input_report_abs(ts->input, ABS_MT_TRACKING_ID, id); + input_report_abs(ts->input, ABS_MT_POSITION_X, x); + input_report_abs(ts->input, ABS_MT_POSITION_Y, y); + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, pressure); + input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, ts->dd->finger_size); + input_mt_sync(ts->input); +} + +static void process_tma300_data(struct cy8c_ts *ts) +{ + u8 id, pressure, touches, i; + u16 x, y; + + touches = ts->touch_data[ts->dd->touch_index]; + + for (i = 0; i < touches; i++) { + id = ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->id_index]; + pressure = ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->z_index]; + x = join_bytes(ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->x_index], + ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->x_index + 1]); + y = join_bytes(ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->y_index], + ts->touch_data[i * ts->dd->touch_bytes + + ts->dd->y_index + 1]); + + report_data(ts, x, y, pressure, id); + } + + for (i = 0; i < ts->prev_touches - touches; i++) { + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, 0); + input_mt_sync(ts->input); + } + + ts->prev_touches = touches; + input_sync(ts->input); +} + +static void process_tmg200_data(struct cy8c_ts *ts) +{ + u8 id, touches, i; + u16 x, y; + + touches = ts->touch_data[ts->dd->touch_index]; + + if (touches > 0) { + x = join_bytes(ts->touch_data[ts->dd->x_index], + ts->touch_data[ts->dd->x_index+1]); + y = join_bytes(ts->touch_data[ts->dd->y_index], + ts->touch_data[ts->dd->y_index+1]); + id = ts->touch_data[ts->dd->id_index]; + + report_data(ts, x, y, 255, id - 1); + + if (touches == 2) { + x = join_bytes(ts->touch_data[ts->dd->x_index+5], + ts->touch_data[ts->dd->x_index+6]); + y = join_bytes(ts->touch_data[ts->dd->y_index+5], + ts->touch_data[ts->dd->y_index+6]); + id = ts->touch_data[ts->dd->id_index+5]; + + report_data(ts, x, y, 255, id - 1); + } + } else { + for (i = 0; i < ts->prev_touches; i++) { + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, 0); + input_report_abs(ts->input, ABS_MT_WIDTH_MAJOR, 0); + input_mt_sync(ts->input); + } + } + + input_sync(ts->input); + ts->prev_touches = touches; +} + +static void cy8c_ts_xy_worker(struct work_struct *work) +{ + int rc; + struct cy8c_ts *ts = container_of(work, struct cy8c_ts, + work.work); + + mutex_lock(&ts->sus_lock); + if (ts->is_suspended == true) { + dev_dbg(&ts->client->dev, "TS is supended\n"); + ts->int_pending = true; + mutex_unlock(&ts->sus_lock); + return; + } + mutex_unlock(&ts->sus_lock); + + /* read data from DATA_REG */ + rc = cy8c_ts_read(ts->client, ts->dd->data_reg, ts->touch_data, + ts->dd->data_size); + if (rc < 0) { + dev_err(&ts->client->dev, "read failed\n"); + goto schedule; + } + + if (ts->touch_data[ts->dd->touch_index] == INVALID_DATA) + goto schedule; + + if (ts->device_id == CY8CTMA300) + process_tma300_data(ts); + else + process_tmg200_data(ts); + +schedule: + enable_irq(ts->pen_irq); + + /* write to STATUS_REG to update coordinates*/ + rc = cy8c_ts_write_reg_u8(ts->client, ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) { + dev_err(&ts->client->dev, "write failed, try once more\n"); + + rc = cy8c_ts_write_reg_u8(ts->client, ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) + dev_err(&ts->client->dev, "write failed, exiting\n"); + } +} + +static irqreturn_t cy8c_ts_irq(int irq, void *dev_id) +{ + struct cy8c_ts *ts = dev_id; + + disable_irq_nosync(irq); + + queue_delayed_work(ts->wq, &ts->work, 0); + + return IRQ_HANDLED; +} + +static int cy8c_ts_init_ts(struct i2c_client *client, struct cy8c_ts *ts) +{ + struct input_dev *input_device; + int rc = 0; + + ts->dd = &devices[ts->device_id]; + + if (!ts->pdata->nfingers) { + dev_err(&client->dev, "Touches information not specified\n"); + return -EINVAL; + } + + if (ts->device_id == CY8CTMA300) { + if (ts->pdata->nfingers > 10) { + dev_err(&client->dev, "Touches >=1 & <= 10\n"); + return -EINVAL; + } + ts->dd->data_size = ts->pdata->nfingers * ts->dd->touch_bytes + + ts->dd->touch_meta_data; + ts->dd->touch_index = ts->pdata->nfingers * + ts->dd->touch_bytes; + } else if (ts->device_id == CY8CTMG200) { + if (ts->pdata->nfingers > 2) { + dev_err(&client->dev, "Touches >=1 & <= 2\n"); + return -EINVAL; + } + ts->dd->data_size = ts->dd->touch_bytes; + ts->dd->touch_index = 0x0; + } + + ts->touch_data = kzalloc(ts->dd->data_size, GFP_KERNEL); + if (!ts->touch_data) { + pr_err("%s: Unable to allocate memory\n", __func__); + return -ENOMEM; + } + + ts->prev_touches = 0; + + input_device = input_allocate_device(); + if (!input_device) { + rc = -ENOMEM; + goto error_alloc_dev; + } + + ts->input = input_device; + input_device->name = ts->pdata->ts_name; + input_device->id.bustype = BUS_I2C; + input_device->dev.parent = &client->dev; + input_set_drvdata(input_device, ts); + + __set_bit(EV_ABS, input_device->evbit); + + input_set_abs_params(input_device, ABS_MT_POSITION_X, + ts->pdata->dis_min_x, ts->pdata->dis_max_x, 0, 0); + input_set_abs_params(input_device, ABS_MT_POSITION_Y, + ts->pdata->dis_min_y, ts->pdata->dis_max_y, 0, 0); + input_set_abs_params(input_device, ABS_MT_TOUCH_MAJOR, + ts->pdata->min_touch, ts->pdata->max_touch, 0, 0); + input_set_abs_params(input_device, ABS_MT_WIDTH_MAJOR, + ts->pdata->min_width, ts->pdata->max_width, 0, 0); + input_set_abs_params(input_device, ABS_MT_TRACKING_ID, + ts->pdata->min_tid, ts->pdata->max_tid, 0, 0); + + ts->wq = create_singlethread_workqueue("kworkqueue_ts"); + if (!ts->wq) { + dev_err(&client->dev, "Could not create workqueue\n"); + goto error_wq_create; + } + + INIT_DELAYED_WORK(&ts->work, cy8c_ts_xy_worker); + + rc = input_register_device(input_device); + if (rc) + goto error_unreg_device; + + return 0; + +error_unreg_device: + destroy_workqueue(ts->wq); +error_wq_create: + input_free_device(input_device); +error_alloc_dev: + kfree(ts->touch_data); + return rc; +} + +#ifdef CONFIG_PM +static int cy8c_ts_suspend(struct device *dev) +{ + struct cy8c_ts *ts = dev_get_drvdata(dev); + int rc = 0; + + if (device_may_wakeup(dev)) { + /* mark suspend flag */ + mutex_lock(&ts->sus_lock); + ts->is_suspended = true; + mutex_unlock(&ts->sus_lock); + + enable_irq_wake(ts->pen_irq); + } else { + disable_irq_nosync(ts->pen_irq); + + rc = cancel_delayed_work_sync(&ts->work); + + if (rc) { + /* missed the worker, write to STATUS_REG to + acknowledge interrupt */ + rc = cy8c_ts_write_reg_u8(ts->client, + ts->dd->status_reg, ts->dd->update_data); + if (rc < 0) { + dev_err(&ts->client->dev, + "write failed, try once more\n"); + + rc = cy8c_ts_write_reg_u8(ts->client, + ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) + dev_err(&ts->client->dev, + "write failed, exiting\n"); + } + + enable_irq(ts->pen_irq); + } + + gpio_free(ts->pdata->irq_gpio); + + if (ts->pdata->power_on) { + rc = ts->pdata->power_on(0); + if (rc) { + dev_err(dev, "unable to goto suspend\n"); + return rc; + } + } + } + return 0; +} + +static int cy8c_ts_resume(struct device *dev) +{ + struct cy8c_ts *ts = dev_get_drvdata(dev); + int rc = 0; + + if (device_may_wakeup(dev)) { + disable_irq_wake(ts->pen_irq); + + mutex_lock(&ts->sus_lock); + ts->is_suspended = false; + + if (ts->int_pending == true) { + ts->int_pending = false; + + /* start a delayed work */ + queue_delayed_work(ts->wq, &ts->work, 0); + } + mutex_unlock(&ts->sus_lock); + + } else { + if (ts->pdata->power_on) { + rc = ts->pdata->power_on(1); + if (rc) { + dev_err(dev, "unable to resume\n"); + return rc; + } + } + + /* configure touchscreen interrupt gpio */ + rc = gpio_request(ts->pdata->irq_gpio, "cy8c_irq_gpio"); + if (rc) { + pr_err("%s: unable to request gpio %d\n", + __func__, ts->pdata->irq_gpio); + goto err_power_off; + } + + rc = gpio_direction_input(ts->pdata->irq_gpio); + if (rc) { + pr_err("%s: unable to set direction for gpio %d\n", + __func__, ts->pdata->irq_gpio); + goto err_gpio_free; + } + + enable_irq(ts->pen_irq); + } + return 0; +err_gpio_free: + gpio_free(ts->pdata->irq_gpio); +err_power_off: + if (ts->pdata->power_on) + rc = ts->pdata->power_on(0); + return rc; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void cy8c_ts_early_suspend(struct early_suspend *h) +{ + struct cy8c_ts *ts = container_of(h, struct cy8c_ts, early_suspend); + + cy8c_ts_suspend(&ts->client->dev); +} + +static void cy8c_ts_late_resume(struct early_suspend *h) +{ + struct cy8c_ts *ts = container_of(h, struct cy8c_ts, early_suspend); + + cy8c_ts_resume(&ts->client->dev); +} +#endif + +static struct dev_pm_ops cy8c_ts_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = cy8c_ts_suspend, + .resume = cy8c_ts_resume, +#endif +}; +#endif + +static int __devinit cy8c_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cy8c_ts *ts; + struct cy8c_ts_platform_data *pdata = client->dev.platform_data; + int rc, temp_reg; + + if (!pdata) { + dev_err(&client->dev, "platform data is required!\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) { + dev_err(&client->dev, "I2C functionality not supported\n"); + return -EIO; + } + + ts = kzalloc(sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + /* Enable runtime PM ops, start in ACTIVE mode */ + rc = pm_runtime_set_active(&client->dev); + if (rc < 0) + dev_dbg(&client->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&client->dev); + + ts->client = client; + ts->pdata = pdata; + i2c_set_clientdata(client, ts); + ts->device_id = id->driver_data; + + if (ts->pdata->dev_setup) { + rc = ts->pdata->dev_setup(1); + if (rc < 0) { + dev_err(&client->dev, "dev setup failed\n"); + goto error_touch_data_alloc; + } + } + + /* power on the device */ + if (ts->pdata->power_on) { + rc = ts->pdata->power_on(1); + if (rc) { + pr_err("%s: Unable to power on the device\n", __func__); + goto error_dev_setup; + } + } + + /* read one byte to make sure i2c device exists */ + if (id->driver_data == CY8CTMA300) + temp_reg = 0x01; + else + temp_reg = 0x05; + + rc = cy8c_ts_read_reg_u8(client, temp_reg); + if (rc < 0) { + dev_err(&client->dev, "i2c sanity check failed\n"); + goto error_power_on; + } + + ts->is_suspended = false; + ts->int_pending = false; + mutex_init(&ts->sus_lock); + + rc = cy8c_ts_init_ts(client, ts); + if (rc < 0) { + dev_err(&client->dev, "CY8CTMG200-TMA300 init failed\n"); + goto error_mutex_destroy; + } + + if (ts->pdata->resout_gpio < 0) + goto config_irq_gpio; + + /* configure touchscreen reset out gpio */ + rc = gpio_request(ts->pdata->resout_gpio, "cy8c_resout_gpio"); + if (rc) { + pr_err("%s: unable to request gpio %d\n", + __func__, ts->pdata->resout_gpio); + goto error_uninit_ts; + } + + rc = gpio_direction_output(ts->pdata->resout_gpio, 0); + if (rc) { + pr_err("%s: unable to set direction for gpio %d\n", + __func__, ts->pdata->resout_gpio); + goto error_resout_gpio_dir; + } + /* reset gpio stabilization time */ + msleep(20); + +config_irq_gpio: + /* configure touchscreen interrupt gpio */ + rc = gpio_request(ts->pdata->irq_gpio, "cy8c_irq_gpio"); + if (rc) { + pr_err("%s: unable to request gpio %d\n", + __func__, ts->pdata->irq_gpio); + goto error_irq_gpio_req; + } + + rc = gpio_direction_input(ts->pdata->irq_gpio); + if (rc) { + pr_err("%s: unable to set direction for gpio %d\n", + __func__, ts->pdata->irq_gpio); + goto error_irq_gpio_dir; + } + + ts->pen_irq = gpio_to_irq(ts->pdata->irq_gpio); + rc = request_irq(ts->pen_irq, cy8c_ts_irq, + IRQF_TRIGGER_FALLING, + ts->client->dev.driver->name, ts); + if (rc) { + dev_err(&ts->client->dev, "could not request irq\n"); + goto error_req_irq_fail; + } + + /* Clear the status register of the TS controller */ + rc = cy8c_ts_write_reg_u8(ts->client, ts->dd->status_reg, + ts->dd->update_data); + if (rc < 0) { + /* Do multiple writes in case of failure */ + dev_err(&ts->client->dev, "%s: write failed %d" + "trying again\n", __func__, rc); + rc = cy8c_ts_write_reg_u8(ts->client, + ts->dd->status_reg, ts->dd->update_data); + if (rc < 0) { + dev_err(&ts->client->dev, "%s: write failed" + "second time(%d)\n", __func__, rc); + } + } + + device_init_wakeup(&client->dev, ts->pdata->wakeup); + +#ifdef CONFIG_HAS_EARLYSUSPEND + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + CY8C_TS_SUSPEND_LEVEL; + ts->early_suspend.suspend = cy8c_ts_early_suspend; + ts->early_suspend.resume = cy8c_ts_late_resume; + register_early_suspend(&ts->early_suspend); +#endif + + return 0; +error_req_irq_fail: +error_irq_gpio_dir: + gpio_free(ts->pdata->irq_gpio); +error_irq_gpio_req: +error_resout_gpio_dir: + if (ts->pdata->resout_gpio >= 0) + gpio_free(ts->pdata->resout_gpio); +error_uninit_ts: + destroy_workqueue(ts->wq); + input_unregister_device(ts->input); + kfree(ts->touch_data); +error_mutex_destroy: + mutex_destroy(&ts->sus_lock); +error_power_on: + if (ts->pdata->power_on) + ts->pdata->power_on(0); +error_dev_setup: + if (ts->pdata->dev_setup) + ts->pdata->dev_setup(0); +error_touch_data_alloc: + pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(&client->dev); + kfree(ts); + return rc; +} + +static int __devexit cy8c_ts_remove(struct i2c_client *client) +{ + struct cy8c_ts *ts = i2c_get_clientdata(client); + +#if defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&ts->early_suspend); +#endif + pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(&client->dev); + + device_init_wakeup(&client->dev, 0); + + cancel_delayed_work_sync(&ts->work); + + free_irq(ts->pen_irq, ts); + + gpio_free(ts->pdata->irq_gpio); + + if (ts->pdata->resout_gpio >= 0) + gpio_free(ts->pdata->resout_gpio); + + destroy_workqueue(ts->wq); + + input_unregister_device(ts->input); + + mutex_destroy(&ts->sus_lock); + + if (ts->pdata->power_on) + ts->pdata->power_on(0); + + if (ts->pdata->dev_setup) + ts->pdata->dev_setup(0); + + kfree(ts->touch_data); + kfree(ts); + + return 0; +} + +static const struct i2c_device_id cy8c_ts_id[] = { + {"cy8ctma300", CY8CTMA300}, + {"cy8ctmg200", CY8CTMG200}, + {} +}; +MODULE_DEVICE_TABLE(i2c, cy8c_ts_id); + + +static struct i2c_driver cy8c_ts_driver = { + .driver = { + .name = "cy8c_ts", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &cy8c_ts_pm_ops, +#endif + }, + .probe = cy8c_ts_probe, + .remove = __devexit_p(cy8c_ts_remove), + .id_table = cy8c_ts_id, +}; + +static int __init cy8c_ts_init(void) +{ + return i2c_add_driver(&cy8c_ts_driver); +} +/* Making this as late init to avoid power fluctuations + * during LCD initialization. + */ +late_initcall(cy8c_ts_init); + +static void __exit cy8c_ts_exit(void) +{ + return i2c_del_driver(&cy8c_ts_driver); +} +module_exit(cy8c_ts_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CY8CTMA300-CY8CTMG200 touchscreen controller driver"); +MODULE_AUTHOR("Cypress"); +MODULE_ALIAS("platform:cy8c_ts"); diff --git a/drivers/input/touchscreen/cyttsp-i2c.c b/drivers/input/touchscreen/cyttsp-i2c.c new file mode 100644 index 00000000000..7c7518a59a0 --- /dev/null +++ b/drivers/input/touchscreen/cyttsp-i2c.c @@ -0,0 +1,3040 @@ +/* Source for: + * Cypress TrueTouch(TM) Standard Product I2C touchscreen driver. + * drivers/input/touchscreen/cyttsp-i2c.c + * + * Copyright (C) 2009, 2010 Cypress Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Cypress reserves the right to make changes without further notice + * to the materials described herein. Cypress does not assume any + * liability arising out of the application described herein. + * + * Contact Cypress Semiconductor at www.cypress.com + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif /* CONFIG_HAS_EARLYSUSPEND */ + +#define CY_DECLARE_GLOBALS + +#include + +uint32_t cyttsp_tsdebug1 = 0xff; +module_param_named(tsdebug1, cyttsp_tsdebug1, uint, 0664); + +#define FW_FNAME_LEN 40 + +/* CY TTSP I2C Driver private data */ +struct cyttsp { + struct i2c_client *client; + struct input_dev *input; + struct work_struct work; + struct timer_list timer; + struct mutex mutex; + char phys[32]; + struct cyttsp_platform_data *platform_data; + u8 num_prv_st_tch; + u16 act_trk[CY_NUM_TRK_ID]; + u16 prv_st_tch[CY_NUM_ST_TCH_ID]; + u16 prv_mt_tch[CY_NUM_MT_TCH_ID]; + u16 prv_mt_pos[CY_NUM_TRK_ID][2]; + atomic_t irq_enabled; + bool cyttsp_update_fw; + bool cyttsp_fwloader_mode; + bool is_suspended; + struct regulator **vdd; + char fw_fname[FW_FNAME_LEN]; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif /* CONFIG_HAS_EARLYSUSPEND */ +}; +static u8 irq_cnt; /* comparison counter with register valuw */ +static u32 irq_cnt_total; /* total interrupts */ +static u32 irq_err_cnt; /* count number of touch interrupts with err */ +#define CY_IRQ_CNT_MASK 0x000000FF /* mapped for sizeof count in reg */ +#define CY_IRQ_CNT_REG 0x00 /* tt_undef[0]=reg 0x1B - Gen3 only */ + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void cyttsp_early_suspend(struct early_suspend *handler); +static void cyttsp_late_resume(struct early_suspend *handler); +#endif /* CONFIG_HAS_EARLYSUSPEND */ + +static struct workqueue_struct *cyttsp_ts_wq; + + +/* **************************************************************************** + * Prototypes for static functions + * ************************************************************************** */ +static void cyttsp_xy_worker(struct work_struct *work); +static irqreturn_t cyttsp_irq(int irq, void *handle); +static int cyttsp_inlist(u16 prev_track[], + u8 cur_trk_id, u8 *prev_loc, u8 num_touches); +static int cyttsp_next_avail_inlist(u16 cur_trk[], + u8 *new_loc, u8 num_touches); +static int cyttsp_putbl(struct cyttsp *ts, int show, + int show_status, int show_version, int show_cid); +static int __devinit cyttsp_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int __devexit cyttsp_remove(struct i2c_client *client); +static int cyttsp_resume(struct device *dev); +static int cyttsp_suspend(struct device *dev); + +/* Static variables */ +static struct cyttsp_gen3_xydata_t g_xy_data; +static struct cyttsp_bootloader_data_t g_bl_data; +static struct cyttsp_sysinfo_data_t g_sysinfo_data; +static const struct i2c_device_id cyttsp_id[] = { + { CY_I2C_NAME, 0 }, { } +}; +static u8 bl_cmd[] = { + CY_BL_FILE0, CY_BL_CMD, CY_BL_EXIT, + CY_BL_KEY0, CY_BL_KEY1, CY_BL_KEY2, + CY_BL_KEY3, CY_BL_KEY4, CY_BL_KEY5, + CY_BL_KEY6, CY_BL_KEY7}; + +MODULE_DEVICE_TABLE(i2c, cyttsp_id); + +static const struct dev_pm_ops cyttsp_pm_ops = { + .suspend = cyttsp_suspend, + .resume = cyttsp_resume, +}; + +static struct i2c_driver cyttsp_driver = { + .driver = { + .name = CY_I2C_NAME, + .owner = THIS_MODULE, + .pm = &cyttsp_pm_ops, + }, + .probe = cyttsp_probe, + .remove = __devexit_p(cyttsp_remove), + .id_table = cyttsp_id, +}; + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard touchscreen driver"); +MODULE_AUTHOR("Cypress"); + +static ssize_t cyttsp_irq_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct cyttsp *ts = i2c_get_clientdata(client); + return sprintf(buf, "%u\n", atomic_read(&ts->irq_enabled)); +} + +static ssize_t cyttsp_irq_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct i2c_client *client = container_of(dev, struct i2c_client, dev); + struct cyttsp *ts = i2c_get_clientdata(client); + int err = 0; + unsigned long value; + + if (size > 2) + return -EINVAL; + + err = strict_strtoul(buf, 10, &value); + if (err != 0) + return err; + + switch (value) { + case 0: + if (atomic_cmpxchg(&ts->irq_enabled, 1, 0)) { + pr_info("touch irq disabled!\n"); + disable_irq_nosync(ts->client->irq); + } + err = size; + break; + case 1: + if (!atomic_cmpxchg(&ts->irq_enabled, 0, 1)) { + pr_info("touch irq enabled!\n"); + enable_irq(ts->client->irq); + } + err = size; + break; + default: + pr_info("cyttsp_irq_enable failed -> irq_enabled = %d\n", + atomic_read(&ts->irq_enabled)); + err = -EINVAL; + break; + } + + return err; +} + +static DEVICE_ATTR(irq_enable, 0777, cyttsp_irq_status, cyttsp_irq_enable); + +static ssize_t cyttsp_fw_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d.%d.%d\n", g_bl_data.appid_lo, + g_bl_data.appver_hi, g_bl_data.appver_lo); +} + +static DEVICE_ATTR(cyttsp_fw_ver, 0777, cyttsp_fw_show, NULL); + +/* firmware flashing block */ +#define BLK_SIZE 16 +#define DATA_REC_LEN 64 +#define START_ADDR 0x0b00 +#define BLK_SEED 0xff +#define RECAL_REG 0x1b + +enum bl_commands { + BL_CMD_WRBLK = 0x39, + BL_CMD_INIT = 0x38, + BL_CMD_TERMINATE = 0x3b, +}; +/* TODO: Add key as part of platform data */ +#define KEY_CS (0 + 1 + 2 + 3 + 4 + 5 + 6 + 7) +#define KEY {0, 1, 2, 3, 4, 5, 6, 7} + +static const char _key[] = KEY; +#define KEY_LEN sizeof(_key) + +static int rec_cnt; +struct fw_record { + u8 seed; + u8 cmd; + u8 key[KEY_LEN]; + u8 blk_hi; + u8 blk_lo; + u8 data[DATA_REC_LEN]; + u8 data_cs; + u8 rec_cs; +}; +#define fw_rec_size (sizeof(struct fw_record)) + +struct cmd_record { + u8 reg; + u8 seed; + u8 cmd; + u8 key[KEY_LEN]; +}; +#define cmd_rec_size (sizeof(struct cmd_record)) + +static struct fw_record data_record = { + .seed = BLK_SEED, + .cmd = BL_CMD_WRBLK, + .key = KEY, +}; + +static const struct cmd_record terminate_rec = { + .reg = 0, + .seed = BLK_SEED, + .cmd = BL_CMD_TERMINATE, + .key = KEY, +}; +static const struct cmd_record initiate_rec = { + .reg = 0, + .seed = BLK_SEED, + .cmd = BL_CMD_INIT, + .key = KEY, +}; + +#define BL_REC1_ADDR 0x0780 +#define BL_REC2_ADDR 0x07c0 + +#define ID_INFO_REC ":40078000" +#define ID_INFO_OFFSET_IN_REC 77 + +#define REC_START_CHR ':' +#define REC_LEN_OFFSET 1 +#define REC_ADDR_HI_OFFSET 3 +#define REC_ADDR_LO_OFFSET 5 +#define REC_TYPE_OFFSET 7 +#define REC_DATA_OFFSET 9 +#define REC_LINE_SIZE 141 + +static int cyttsp_soft_reset(struct cyttsp *ts) +{ + int retval = 0, tries = 0; + u8 host_reg = CY_SOFT_RESET_MODE; + + do { + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, sizeof(host_reg), &host_reg); + if (retval < 0) + msleep(20); + } while (tries++ < 10 && (retval < 0)); + + if (retval < 0) { + pr_err("%s: failed\n", __func__); + return retval; + } + + tries = 0; + do { + msleep(20); + cyttsp_putbl(ts, 1, true, true, false); + } while (g_bl_data.bl_status != 0x10 && + g_bl_data.bl_status != 0x11 && + tries++ < 100); + + if (g_bl_data.bl_status != 0x11 && g_bl_data.bl_status != 0x10) + return -EINVAL; + + return 0; +} + +static void cyttsp_exit_bl_mode(struct cyttsp *ts) +{ + int retval, tries = 0; + + do { + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, sizeof(bl_cmd), bl_cmd); + if (retval < 0) + msleep(20); + } while (tries++ < 10 && (retval < 0)); +} + +static void cyttsp_set_sysinfo_mode(struct cyttsp *ts) +{ + int retval, tries = 0; + u8 host_reg = CY_SYSINFO_MODE; + + do { + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, sizeof(host_reg), &host_reg); + if (retval < 0) + msleep(20); + } while (tries++ < 10 && (retval < 0)); + + /* wait for TTSP Device to complete switch to SysInfo mode */ + if (!(retval < 0)) { + retval = i2c_smbus_read_i2c_block_data(ts->client, + CY_REG_BASE, + sizeof(struct cyttsp_sysinfo_data_t), + (u8 *)&g_sysinfo_data); + } else + pr_err("%s: failed\n", __func__); +} + +static void cyttsp_set_opmode(struct cyttsp *ts) +{ + int retval, tries = 0; + u8 host_reg = CY_OP_MODE; + + do { + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, sizeof(host_reg), &host_reg); + if (retval < 0) + msleep(20); + } while (tries++ < 10 && (retval < 0)); +} + +static int str2uc(char *str, u8 *val) +{ + char substr[3]; + unsigned long ulval; + int rc; + + if (!str && strlen(str) < 2) + return -EINVAL; + + substr[0] = str[0]; + substr[1] = str[1]; + substr[2] = '\0'; + + rc = strict_strtoul(substr, 16, &ulval); + if (rc != 0) + return rc; + + *val = (u8) ulval; + + return 0; +} + +static int flash_block(struct cyttsp *ts, u8 *blk, int len) +{ + int retval, i, tries = 0; + char buf[(2 * (BLK_SIZE + 1)) + 1]; + char *p = buf; + + for (i = 0; i < len; i++, p += 2) + sprintf(p, "%02x", blk[i]); + pr_debug("%s: size %d, pos %ld payload %s\n", + __func__, len, (long)0, buf); + + do { + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, len, blk); + if (retval < 0) + msleep(20); + } while (tries++ < 20 && (retval < 0)); + + if (retval < 0) { + pr_err("%s: failed\n", __func__); + return retval; + } + + return 0; +} + +static int flash_command(struct cyttsp *ts, const struct cmd_record *record) +{ + return flash_block(ts, (u8 *)record, cmd_rec_size); +} + +static void init_data_record(struct fw_record *rec, unsigned short addr) +{ + addr >>= 6; + rec->blk_hi = (addr >> 8) & 0xff; + rec->blk_lo = addr & 0xff; + rec->rec_cs = rec->blk_hi + rec->blk_lo + + (unsigned char)(BLK_SEED + BL_CMD_WRBLK + KEY_CS); + rec->data_cs = 0; +} + +static int check_record(u8 *rec) +{ + int rc; + u16 addr; + u8 r_len, type, hi_off, lo_off; + + rc = str2uc(rec + REC_LEN_OFFSET, &r_len); + if (rc < 0) + return rc; + + rc = str2uc(rec + REC_TYPE_OFFSET, &type); + if (rc < 0) + return rc; + + if (*rec != REC_START_CHR || r_len != DATA_REC_LEN || type != 0) + return -EINVAL; + + rc = str2uc(rec + REC_ADDR_HI_OFFSET, &hi_off); + if (rc < 0) + return rc; + + rc = str2uc(rec + REC_ADDR_LO_OFFSET, &lo_off); + if (rc < 0) + return rc; + + addr = (hi_off << 8) | lo_off; + + if (addr >= START_ADDR || addr == BL_REC1_ADDR || addr == BL_REC2_ADDR) + return 0; + + return -EINVAL; +} + +static struct fw_record *prepare_record(u8 *rec) +{ + int i, rc; + u16 addr; + u8 hi_off, lo_off; + u8 *p; + + rc = str2uc(rec + REC_ADDR_HI_OFFSET, &hi_off); + if (rc < 0) + return ERR_PTR((long) rc); + + rc = str2uc(rec + REC_ADDR_LO_OFFSET, &lo_off); + if (rc < 0) + return ERR_PTR((long) rc); + + addr = (hi_off << 8) | lo_off; + + init_data_record(&data_record, addr); + p = rec + REC_DATA_OFFSET; + for (i = 0; i < DATA_REC_LEN; i++) { + rc = str2uc(p, &data_record.data[i]); + if (rc < 0) + return ERR_PTR((long) rc); + data_record.data_cs += data_record.data[i]; + data_record.rec_cs += data_record.data[i]; + p += 2; + } + data_record.rec_cs += data_record.data_cs; + + return &data_record; +} + +static int flash_record(struct cyttsp *ts, const struct fw_record *record) +{ + int len = fw_rec_size; + int blk_len, rc; + u8 *rec = (u8 *)record; + u8 data[BLK_SIZE + 1]; + u8 blk_offset; + + for (blk_offset = 0; len; len -= blk_len) { + data[0] = blk_offset; + blk_len = len > BLK_SIZE ? BLK_SIZE : len; + memcpy(data + 1, rec, blk_len); + rec += blk_len; + rc = flash_block(ts, data, blk_len + 1); + if (rc < 0) + return rc; + blk_offset += blk_len; + } + return 0; +} + +static int flash_data_rec(struct cyttsp *ts, u8 *buf) +{ + struct fw_record *rec; + int rc, tries; + + if (!buf) + return -EINVAL; + + rc = check_record(buf); + + if (rc < 0) { + pr_debug("%s: record ignored %s", __func__, buf); + return 0; + } + + rec = prepare_record(buf); + if (IS_ERR_OR_NULL(rec)) + return PTR_ERR(rec); + + rc = flash_record(ts, rec); + if (rc < 0) + return rc; + + tries = 0; + do { + if (rec_cnt%2) + msleep(20); + cyttsp_putbl(ts, 4, true, false, false); + } while (g_bl_data.bl_status != 0x10 && + g_bl_data.bl_status != 0x11 && + tries++ < 100); + rec_cnt++; + return rc; +} + +static int cyttspfw_flash_firmware(struct cyttsp *ts, const u8 *data, + int data_len) +{ + u8 *buf; + int i, j; + int rc, tries = 0; + + /* initiate bootload: this will erase all the existing data */ + rc = flash_command(ts, &initiate_rec); + if (rc < 0) + return rc; + + do { + msleep(100); + cyttsp_putbl(ts, 4, true, false, false); + } while (g_bl_data.bl_status != 0x10 && + g_bl_data.bl_status != 0x11 && + tries++ < 100); + + buf = kzalloc(REC_LINE_SIZE + 1, GFP_KERNEL); + if (!buf) { + pr_err("%s: no memory\n", __func__); + return -ENOMEM; + } + + rec_cnt = 0; + /* flash data records */ + for (i = 0, j = 0; i < data_len; i++, j++) { + if ((data[i] == REC_START_CHR) && j) { + buf[j] = 0; + rc = flash_data_rec(ts, buf); + if (rc < 0) + return rc; + j = 0; + } + buf[j] = data[i]; + } + + /* flash last data record */ + if (j) { + buf[j] = 0; + rc = flash_data_rec(ts, buf); + if (rc < 0) + return rc; + } + + kfree(buf); + + /* termiate bootload */ + tries = 0; + rc = flash_command(ts, &terminate_rec); + do { + msleep(100); + cyttsp_putbl(ts, 4, true, false, false); + } while (g_bl_data.bl_status != 0x10 && + g_bl_data.bl_status != 0x11 && + tries++ < 100); + + return rc; +} + +static int get_hex_fw_ver(u8 *p, u8 *ttspver_hi, u8 *ttspver_lo, + u8 *appid_hi, u8 *appid_lo, u8 *appver_hi, + u8 *appver_lo, u8 *cid_0, u8 *cid_1, u8 *cid_2) +{ + int rc; + + p = p + ID_INFO_OFFSET_IN_REC; + rc = str2uc(p, ttspver_hi); + if (rc < 0) + return rc; + p += 2; + rc = str2uc(p, ttspver_lo); + if (rc < 0) + return rc; + p += 2; + rc = str2uc(p, appid_hi); + if (rc < 0) + return rc; + p += 2; + rc = str2uc(p, appid_lo); + if (rc < 0) + return rc; + p += 2; + rc = str2uc(p, appver_hi); + if (rc < 0) + return rc; + p += 2; + rc = str2uc(p, appver_lo); + if (rc < 0) + return rc; + p += 2; + rc = str2uc(p, cid_0); + if (rc < 0) + return rc; + p += 2; + rc = str2uc(p, cid_1); + if (rc < 0) + return rc; + p += 2; + rc = str2uc(p, cid_2); + if (rc < 0) + return rc; + + return 0; +} + +static void cyttspfw_flash_start(struct cyttsp *ts, const u8 *data, + int data_len, u8 *buf, bool force) +{ + int rc; + u8 ttspver_hi = 0, ttspver_lo = 0, fw_upgrade = 0; + u8 appid_hi = 0, appid_lo = 0; + u8 appver_hi = 0, appver_lo = 0; + u8 cid_0 = 0, cid_1 = 0, cid_2 = 0; + char *p = buf; + + /* get hex firmware version */ + rc = get_hex_fw_ver(p, &ttspver_hi, &ttspver_lo, + &appid_hi, &appid_lo, &appver_hi, + &appver_lo, &cid_0, &cid_1, &cid_2); + + if (rc < 0) { + pr_err("%s: unable to get hex firmware version\n", __func__); + return; + } + + /* disable interrupts before flashing */ + if (ts->client->irq == 0) + del_timer(&ts->timer); + else + disable_irq(ts->client->irq); + + rc = cancel_work_sync(&ts->work); + + if (rc && ts->client->irq) + enable_irq(ts->client->irq); + + /* enter bootloader idle mode */ + rc = cyttsp_soft_reset(ts); + + if (rc < 0) { + pr_err("%s: try entering into idle mode" + " second time\n", __func__); + msleep(1000); + rc = cyttsp_soft_reset(ts); + } + + if (rc < 0) { + pr_err("%s: try again later\n", __func__); + return; + } + + + pr_info("Current firmware: %d.%d.%d", g_bl_data.appid_lo, + g_bl_data.appver_hi, g_bl_data.appver_lo); + pr_info("New firmware: %d.%d.%d", appid_lo, appver_hi, appver_lo); + + if (force) + fw_upgrade = 1; + else + if ((appid_hi == g_bl_data.appid_hi) && + (appid_lo == g_bl_data.appid_lo)) { + if (appver_hi > g_bl_data.appver_hi) { + fw_upgrade = 1; + } else if ((appver_hi == g_bl_data.appver_hi) && + (appver_lo > g_bl_data.appver_lo)) { + fw_upgrade = 1; + } else { + fw_upgrade = 0; + pr_info("%s: Firmware version " + "lesser/equal to existing firmware, " + "upgrade not needed\n", __func__); + } + } else { + fw_upgrade = 0; + pr_info("%s: Firware versions do not match, " + "cannot upgrade\n", __func__); + } + + if (fw_upgrade) { + pr_info("%s: Starting firmware upgrade\n", __func__); + rc = cyttspfw_flash_firmware(ts, data, data_len); + if (rc < 0) + pr_err("%s: firmware upgrade failed\n", __func__); + else + pr_info("%s: firmware upgrade success\n", __func__); + } + + /* enter bootloader idle mode */ + cyttsp_soft_reset(ts); + /* exit bootloader mode */ + cyttsp_exit_bl_mode(ts); + msleep(100); + /* set sysinfo details */ + cyttsp_set_sysinfo_mode(ts); + /* enter application mode */ + cyttsp_set_opmode(ts); + + /* enable interrupts */ + if (ts->client->irq == 0) + mod_timer(&ts->timer, jiffies + TOUCHSCREEN_TIMEOUT); + else + enable_irq(ts->client->irq); +} + +static void cyttspfw_upgrade_start(struct cyttsp *ts, const u8 *data, + int data_len, bool force) +{ + int i, j; + u8 *buf; + + buf = kzalloc(REC_LINE_SIZE + 1, GFP_KERNEL); + if (!buf) { + pr_err("%s: no memory\n", __func__); + return; + } + + for (i = 0, j = 0; i < data_len; i++, j++) { + if ((data[i] == REC_START_CHR) && j) { + buf[j] = 0; + j = 0; + if (!strncmp(buf, ID_INFO_REC, strlen(ID_INFO_REC))) { + cyttspfw_flash_start(ts, data, data_len, + buf, force); + break; + } + } + buf[j] = data[i]; + } + + /* check in the last record of firmware */ + if (j) { + buf[j] = 0; + if (!strncmp(buf, ID_INFO_REC, strlen(ID_INFO_REC))) { + cyttspfw_flash_start(ts, data, data_len, + buf, force); + } + } + + kfree(buf); +} + +static void cyttspfw_upgrade(struct device *dev, bool force) +{ + struct cyttsp *ts = dev_get_drvdata(dev); + const struct firmware *cyttsp_fw; + int retval = 0; + + if (ts->is_suspended == true) { + pr_err("%s: in suspend state, resume it\n", __func__); + retval = cyttsp_resume(dev); + if (retval < 0) { + pr_err("%s: unable to resume\n", __func__); + return; + } + } + + retval = request_firmware(&cyttsp_fw, ts->fw_fname, dev); + if (retval < 0) { + pr_err("%s: %s request failed(%d)\n", __func__, + ts->fw_fname, retval); + } else { + /* check and start upgrade */ + cyttspfw_upgrade_start(ts, cyttsp_fw->data, + cyttsp_fw->size, force); + release_firmware(cyttsp_fw); + } +} + +static ssize_t cyttsp_update_fw_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp *ts = dev_get_drvdata(dev); + return snprintf(buf, 2, "%d\n", ts->cyttsp_fwloader_mode); +} + +static ssize_t cyttsp_force_update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct cyttsp *ts = dev_get_drvdata(dev); + unsigned long val; + int rc; + + if (size > 2) + return -EINVAL; + + rc = strict_strtoul(buf, 10, &val); + if (rc != 0) + return rc; + + mutex_lock(&ts->mutex); + if (!ts->cyttsp_fwloader_mode && val) { + ts->cyttsp_fwloader_mode = 1; + cyttspfw_upgrade(dev, true); + ts->cyttsp_fwloader_mode = 0; + } + mutex_unlock(&ts->mutex); + return size; +} + +static DEVICE_ATTR(cyttsp_force_update_fw, 0777, cyttsp_update_fw_show, + cyttsp_force_update_fw_store); + +static ssize_t cyttsp_update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct cyttsp *ts = dev_get_drvdata(dev); + unsigned long val; + int rc; + + if (size > 2) + return -EINVAL; + + rc = strict_strtoul(buf, 10, &val); + if (rc != 0) + return rc; + + mutex_lock(&ts->mutex); + if (!ts->cyttsp_fwloader_mode && val) { + ts->cyttsp_fwloader_mode = 1; + cyttspfw_upgrade(dev, false); + ts->cyttsp_fwloader_mode = 0; + } + mutex_unlock(&ts->mutex); + + return size; +} + +static DEVICE_ATTR(cyttsp_update_fw, 0777, cyttsp_update_fw_show, + cyttsp_update_fw_store); + +static ssize_t cyttsp_fw_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cyttsp *ts = dev_get_drvdata(dev); + return snprintf(buf, FW_FNAME_LEN - 1, "%s\n", ts->fw_fname); +} + +static ssize_t cyttsp_fw_name_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct cyttsp *ts = dev_get_drvdata(dev); + + if (size > FW_FNAME_LEN - 1) + return -EINVAL; + + strncpy(ts->fw_fname, buf, size); + if (ts->fw_fname[size-1] == '\n') + ts->fw_fname[size-1] = 0; + + return size; +} + +static DEVICE_ATTR(cyttsp_fw_name, 0777, cyttsp_fw_name_show, + cyttsp_fw_name_store); + +/* The cyttsp_xy_worker function reads the XY coordinates and sends them to + * the input layer. It is scheduled from the interrupt (or timer). + */ +void cyttsp_xy_worker(struct work_struct *work) +{ + struct cyttsp *ts = container_of(work, struct cyttsp, work); + u8 id, tilt, rev_x, rev_y; + u8 i, loc; + u8 prv_tch; /* number of previous touches */ + u8 cur_tch; /* number of current touches */ + u16 tmp_trk[CY_NUM_MT_TCH_ID]; + u16 snd_trk[CY_NUM_MT_TCH_ID]; + u16 cur_trk[CY_NUM_TRK_ID]; + u16 cur_st_tch[CY_NUM_ST_TCH_ID]; + u16 cur_mt_tch[CY_NUM_MT_TCH_ID]; + /* if NOT CY_USE_TRACKING_ID then + * only uses CY_NUM_MT_TCH_ID positions */ + u16 cur_mt_pos[CY_NUM_TRK_ID][2]; + /* if NOT CY_USE_TRACKING_ID then + * only uses CY_NUM_MT_TCH_ID positions */ + u8 cur_mt_z[CY_NUM_TRK_ID]; + u8 curr_tool_width; + u16 st_x1, st_y1; + u8 st_z1; + u16 st_x2, st_y2; + u8 st_z2; + s32 retval; + + cyttsp_xdebug("TTSP worker start 1:\n"); + + /* get event data from CYTTSP device */ + i = CY_NUM_RETRY; + do { + retval = i2c_smbus_read_i2c_block_data(ts->client, + CY_REG_BASE, + sizeof(struct cyttsp_gen3_xydata_t), (u8 *)&g_xy_data); + } while ((retval < CY_OK) && --i); + + if (retval < CY_OK) { + /* return immediately on + * failure to read device on the i2c bus */ + goto exit_xy_worker; + } + + cyttsp_xdebug("TTSP worker start 2:\n"); + + /* compare own irq counter with the device irq counter */ + if (ts->client->irq) { + u8 host_reg; + u8 cur_cnt; + if (ts->platform_data->use_hndshk) { + + host_reg = g_xy_data.hst_mode & CY_HNDSHK_BIT ? + g_xy_data.hst_mode & ~CY_HNDSHK_BIT : + g_xy_data.hst_mode | CY_HNDSHK_BIT; + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, sizeof(host_reg), &host_reg); + } + cur_cnt = g_xy_data.tt_undef[CY_IRQ_CNT_REG]; + irq_cnt_total++; + irq_cnt++; + if (irq_cnt != cur_cnt) { + irq_err_cnt++; + cyttsp_debug("i_c_ER: dv=%d fw=%d hm=%02X t=%lu te=%lu\n", \ + irq_cnt, \ + cur_cnt, g_xy_data.hst_mode, \ + (unsigned long)irq_cnt_total, \ + (unsigned long)irq_err_cnt); + } else { + cyttsp_debug("i_c_ok: dv=%d fw=%d hm=%02X t=%lu te=%lu\n", \ + irq_cnt, \ + cur_cnt, g_xy_data.hst_mode, \ + (unsigned long)irq_cnt_total, \ + (unsigned long)irq_err_cnt); + } + irq_cnt = cur_cnt; + } + + /* Get the current num touches and return if there are no touches */ + if ((GET_BOOTLOADERMODE(g_xy_data.tt_mode) == 1) || + (GET_HSTMODE(g_xy_data.hst_mode) != CY_OK)) { + u8 host_reg, tries; + /* the TTSP device has suffered spurious reset or mode switch */ + cyttsp_debug( \ + "Spurious err opmode (tt_mode=%02X hst_mode=%02X)\n", \ + g_xy_data.tt_mode, g_xy_data.hst_mode); + cyttsp_debug("Reset TTSP Device; Terminating active tracks\n"); + /* terminate all active tracks */ + cur_tch = CY_NTCH; + /* reset TTSP part and take it back out of Bootloader mode */ + /* reset TTSP Device back to bootloader mode */ + host_reg = CY_SOFT_RESET_MODE; + retval = i2c_smbus_write_i2c_block_data(ts->client, CY_REG_BASE, + sizeof(host_reg), &host_reg); + /* wait for TTSP Device to complete reset back to bootloader */ + tries = 0; + do { + mdelay(1); + cyttsp_putbl(ts, 1, false, false, false); + } while (g_bl_data.bl_status != 0x10 && + g_bl_data.bl_status != 0x11 && + tries++ < 100); + retval = cyttsp_putbl(ts, 1, true, true, true); + /* switch back to operational mode */ + /* take TTSP device out of bootloader mode; + * switch back to TrueTouch operational mode */ + if (!(retval < CY_OK)) { + int tries; + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, + sizeof(bl_cmd), bl_cmd); + /* wait for TTSP Device to complete + * switch to Operational mode */ + tries = 0; + do { + mdelay(100); + cyttsp_putbl(ts, 2, false, false, false); + } while (GET_BOOTLOADERMODE(g_bl_data.bl_status) && + tries++ < 100); + cyttsp_putbl(ts, 2, true, false, false); + } + goto exit_xy_worker; + } else { + cur_tch = GET_NUM_TOUCHES(g_xy_data.tt_stat); + if (IS_LARGE_AREA(g_xy_data.tt_stat)) { + /* terminate all active tracks */ + cur_tch = CY_NTCH; + cyttsp_debug("Large obj detect (tt_stat=0x%02X). Terminate act trks\n", \ + g_xy_data.tt_stat); + } else if (cur_tch > CY_NUM_MT_TCH_ID) { + /* if the number of fingers on the touch surface + * is more than the maximum then + * there will be no new track information + * even for the original touches. + * Therefore, terminate all active tracks. + */ + cur_tch = CY_NTCH; + cyttsp_debug("Num touch err (tt_stat=0x%02X). Terminate act trks\n", \ + g_xy_data.tt_stat); + } + } + + /* set tool size */ + curr_tool_width = CY_SMALL_TOOL_WIDTH; + + /* translate Gen2 interface data into comparable Gen3 data */ + if (ts->platform_data->gen == CY_GEN2) { + struct cyttsp_gen2_xydata_t *pxy_gen2_data; + pxy_gen2_data = (struct cyttsp_gen2_xydata_t *)(&g_xy_data); + + /* use test data? */ + cyttsp_testdat(&g_xy_data, &tt_gen2_testray, \ + sizeof(struct cyttsp_gen3_xydata_t)); + + if (pxy_gen2_data->evnt_idx == CY_GEN2_NOTOUCH) { + cur_tch = 0; + } else if (cur_tch == CY_GEN2_GHOST) { + cur_tch = 0; + } else if (cur_tch == CY_GEN2_2TOUCH) { + /* stuff artificial track ID1 and ID2 */ + g_xy_data.touch12_id = 0x12; + g_xy_data.z1 = CY_MAXZ; + g_xy_data.z2 = CY_MAXZ; + cur_tch--; /* 2 touches */ + } else if (cur_tch == CY_GEN2_1TOUCH) { + /* stuff artificial track ID1 and ID2 */ + g_xy_data.touch12_id = 0x12; + g_xy_data.z1 = CY_MAXZ; + g_xy_data.z2 = CY_NTCH; + if (pxy_gen2_data->evnt_idx == CY_GEN2_TOUCH2) { + /* push touch 2 data into touch1 + * (first finger up; second finger down) */ + /* stuff artificial track ID1 for touch2 info */ + g_xy_data.touch12_id = 0x20; + /* stuff touch 1 with touch 2 coordinate data */ + g_xy_data.x1 = g_xy_data.x2; + g_xy_data.y1 = g_xy_data.y2; + } + } else { + cur_tch = 0; + } + } else { + /* use test data? */ + cyttsp_testdat(&g_xy_data, &tt_gen3_testray, \ + sizeof(struct cyttsp_gen3_xydata_t)); + } + + + + /* clear current active track ID array and count previous touches */ + for (id = 0, prv_tch = CY_NTCH; + id < CY_NUM_TRK_ID; id++) { + cur_trk[id] = CY_NTCH; + prv_tch += ts->act_trk[id]; + } + + /* send no events if no previous touches and no new touches */ + if ((prv_tch == CY_NTCH) && + ((cur_tch == CY_NTCH) || + (cur_tch > CY_NUM_MT_TCH_ID))) { + goto exit_xy_worker; + } + + cyttsp_debug("prev=%d curr=%d\n", prv_tch, cur_tch); + + for (id = 0; id < CY_NUM_ST_TCH_ID; id++) { + /* clear current single touches array */ + cur_st_tch[id] = CY_IGNR_TCH; + } + + /* clear single touch positions */ + st_x1 = CY_NTCH; + st_y1 = CY_NTCH; + st_z1 = CY_NTCH; + st_x2 = CY_NTCH; + st_y2 = CY_NTCH; + st_z2 = CY_NTCH; + + for (id = 0; id < CY_NUM_MT_TCH_ID; id++) { + /* clear current multi-touches array and + * multi-touch positions/z */ + cur_mt_tch[id] = CY_IGNR_TCH; + } + + if (ts->platform_data->use_trk_id) { + for (id = 0; id < CY_NUM_MT_TCH_ID; id++) { + cur_mt_pos[id][CY_XPOS] = 0; + cur_mt_pos[id][CY_YPOS] = 0; + cur_mt_z[id] = 0; + } + } else { + for (id = 0; id < CY_NUM_TRK_ID; id++) { + cur_mt_pos[id][CY_XPOS] = 0; + cur_mt_pos[id][CY_YPOS] = 0; + cur_mt_z[id] = 0; + } + } + + /* Determine if display is tilted */ + if (FLIP_DATA(ts->platform_data->flags)) + tilt = true; + else + tilt = false; + + /* Check for switch in origin */ + if (REVERSE_X(ts->platform_data->flags)) + rev_x = true; + else + rev_x = false; + + if (REVERSE_Y(ts->platform_data->flags)) + rev_y = true; + else + rev_y = false; + + if (cur_tch) { + struct cyttsp_gen2_xydata_t *pxy_gen2_data; + struct cyttsp_gen3_xydata_t *pxy_gen3_data; + switch (ts->platform_data->gen) { + case CY_GEN2: { + pxy_gen2_data = + (struct cyttsp_gen2_xydata_t *)(&g_xy_data); + cyttsp_xdebug("TTSP Gen2 report:\n"); + cyttsp_xdebug("%02X %02X %02X\n", \ + pxy_gen2_data->hst_mode, \ + pxy_gen2_data->tt_mode, \ + pxy_gen2_data->tt_stat); + cyttsp_xdebug("%04X %04X %02X %02X\n", \ + pxy_gen2_data->x1, \ + pxy_gen2_data->y1, \ + pxy_gen2_data->z1, \ + pxy_gen2_data->evnt_idx); + cyttsp_xdebug("%04X %04X %02X\n", \ + pxy_gen2_data->x2, \ + pxy_gen2_data->y2, \ + pxy_gen2_data->tt_undef1); + cyttsp_xdebug("%02X %02X %02X\n", \ + pxy_gen2_data->gest_cnt, \ + pxy_gen2_data->gest_id, \ + pxy_gen2_data->gest_set); + break; + } + case CY_GEN3: + default: { + pxy_gen3_data = + (struct cyttsp_gen3_xydata_t *)(&g_xy_data); + cyttsp_xdebug("TTSP Gen3 report:\n"); + cyttsp_xdebug("%02X %02X %02X\n", \ + pxy_gen3_data->hst_mode, + pxy_gen3_data->tt_mode, + pxy_gen3_data->tt_stat); + cyttsp_xdebug("%04X %04X %02X %02X", \ + pxy_gen3_data->x1, + pxy_gen3_data->y1, + pxy_gen3_data->z1, \ + pxy_gen3_data->touch12_id); + cyttsp_xdebug("%04X %04X %02X\n", \ + pxy_gen3_data->x2, \ + pxy_gen3_data->y2, \ + pxy_gen3_data->z2); + cyttsp_xdebug("%02X %02X %02X\n", \ + pxy_gen3_data->gest_cnt, \ + pxy_gen3_data->gest_id, \ + pxy_gen3_data->gest_set); + cyttsp_xdebug("%04X %04X %02X %02X\n", \ + pxy_gen3_data->x3, \ + pxy_gen3_data->y3, \ + pxy_gen3_data->z3, \ + pxy_gen3_data->touch34_id); + cyttsp_xdebug("%04X %04X %02X\n", \ + pxy_gen3_data->x4, \ + pxy_gen3_data->y4, \ + pxy_gen3_data->z4); + break; + } + } + } + + /* process the touches */ + switch (cur_tch) { + case 4: { + g_xy_data.x4 = be16_to_cpu(g_xy_data.x4); + g_xy_data.y4 = be16_to_cpu(g_xy_data.y4); + if (tilt) + FLIP_XY(g_xy_data.x4, g_xy_data.y4); + + if (rev_x) { + g_xy_data.x4 = INVERT_X(g_xy_data.x4, + ts->platform_data->panel_maxx); + if (g_xy_data.x4 < 0) + pr_debug("X value is negative. Please configure" + " maxx in platform data structure\n"); + } + if (rev_y) { + g_xy_data.y4 = INVERT_X(g_xy_data.y4, + ts->platform_data->panel_maxy); + if (g_xy_data.y4 < 0) + pr_debug("Y value is negative. Please configure" + " maxy in platform data structure\n"); + + } + id = GET_TOUCH4_ID(g_xy_data.touch34_id); + if (ts->platform_data->use_trk_id) { + cur_mt_pos[CY_MT_TCH4_IDX][CY_XPOS] = + g_xy_data.x4; + cur_mt_pos[CY_MT_TCH4_IDX][CY_YPOS] = + g_xy_data.y4; + cur_mt_z[CY_MT_TCH4_IDX] = g_xy_data.z4; + } else { + cur_mt_pos[id][CY_XPOS] = g_xy_data.x4; + cur_mt_pos[id][CY_YPOS] = g_xy_data.y4; + cur_mt_z[id] = g_xy_data.z4; + } + cur_mt_tch[CY_MT_TCH4_IDX] = id; + cur_trk[id] = CY_TCH; + if (ts->prv_st_tch[CY_ST_FNGR1_IDX] < + CY_NUM_TRK_ID) { + if (ts->prv_st_tch[CY_ST_FNGR1_IDX] == id) { + st_x1 = g_xy_data.x4; + st_y1 = g_xy_data.y4; + st_z1 = g_xy_data.z4; + cur_st_tch[CY_ST_FNGR1_IDX] = id; + } else if (ts->prv_st_tch[CY_ST_FNGR2_IDX] == id) { + st_x2 = g_xy_data.x4; + st_y2 = g_xy_data.y4; + st_z2 = g_xy_data.z4; + cur_st_tch[CY_ST_FNGR2_IDX] = id; + } + } + cyttsp_xdebug("4th XYZ:% 3d,% 3d,% 3d ID:% 2d\n\n", \ + g_xy_data.x4, g_xy_data.y4, g_xy_data.z4, \ + (g_xy_data.touch34_id & 0x0F)); + /* do not break */ + } + case 3: { + g_xy_data.x3 = be16_to_cpu(g_xy_data.x3); + g_xy_data.y3 = be16_to_cpu(g_xy_data.y3); + if (tilt) + FLIP_XY(g_xy_data.x3, g_xy_data.y3); + + if (rev_x) { + g_xy_data.x3 = INVERT_X(g_xy_data.x3, + ts->platform_data->panel_maxx); + if (g_xy_data.x3 < 0) + pr_debug("X value is negative. Please configure" + " maxx in platform data structure\n"); + + } + if (rev_y) { + g_xy_data.y3 = INVERT_X(g_xy_data.y3, + ts->platform_data->panel_maxy); + if (g_xy_data.y3 < 0) + pr_debug("Y value is negative. Please configure" + " maxy in platform data structure\n"); + + } + id = GET_TOUCH3_ID(g_xy_data.touch34_id); + if (ts->platform_data->use_trk_id) { + cur_mt_pos[CY_MT_TCH3_IDX][CY_XPOS] = + g_xy_data.x3; + cur_mt_pos[CY_MT_TCH3_IDX][CY_YPOS] = + g_xy_data.y3; + cur_mt_z[CY_MT_TCH3_IDX] = g_xy_data.z3; + } else { + cur_mt_pos[id][CY_XPOS] = g_xy_data.x3; + cur_mt_pos[id][CY_YPOS] = g_xy_data.y3; + cur_mt_z[id] = g_xy_data.z3; + } + cur_mt_tch[CY_MT_TCH3_IDX] = id; + cur_trk[id] = CY_TCH; + if (ts->prv_st_tch[CY_ST_FNGR1_IDX] < + CY_NUM_TRK_ID) { + if (ts->prv_st_tch[CY_ST_FNGR1_IDX] == id) { + st_x1 = g_xy_data.x3; + st_y1 = g_xy_data.y3; + st_z1 = g_xy_data.z3; + cur_st_tch[CY_ST_FNGR1_IDX] = id; + } else if (ts->prv_st_tch[CY_ST_FNGR2_IDX] == id) { + st_x2 = g_xy_data.x3; + st_y2 = g_xy_data.y3; + st_z2 = g_xy_data.z3; + cur_st_tch[CY_ST_FNGR2_IDX] = id; + } + } + cyttsp_xdebug("3rd XYZ:% 3d,% 3d,% 3d ID:% 2d\n", \ + g_xy_data.x3, g_xy_data.y3, g_xy_data.z3, \ + ((g_xy_data.touch34_id >> 4) & 0x0F)); + /* do not break */ + } + case 2: { + g_xy_data.x2 = be16_to_cpu(g_xy_data.x2); + g_xy_data.y2 = be16_to_cpu(g_xy_data.y2); + if (tilt) + FLIP_XY(g_xy_data.x2, g_xy_data.y2); + + if (rev_x) { + g_xy_data.x2 = INVERT_X(g_xy_data.x2, + ts->platform_data->panel_maxx); + if (g_xy_data.x2 < 0) + pr_debug("X value is negative. Please configure" + " maxx in platform data structure\n"); + } + if (rev_y) { + g_xy_data.y2 = INVERT_X(g_xy_data.y2, + ts->platform_data->panel_maxy); + if (g_xy_data.y2 < 0) + pr_debug("Y value is negative. Please configure" + " maxy in platform data structure\n"); + } + id = GET_TOUCH2_ID(g_xy_data.touch12_id); + if (ts->platform_data->use_trk_id) { + cur_mt_pos[CY_MT_TCH2_IDX][CY_XPOS] = + g_xy_data.x2; + cur_mt_pos[CY_MT_TCH2_IDX][CY_YPOS] = + g_xy_data.y2; + cur_mt_z[CY_MT_TCH2_IDX] = g_xy_data.z2; + } else { + cur_mt_pos[id][CY_XPOS] = g_xy_data.x2; + cur_mt_pos[id][CY_YPOS] = g_xy_data.y2; + cur_mt_z[id] = g_xy_data.z2; + } + cur_mt_tch[CY_MT_TCH2_IDX] = id; + cur_trk[id] = CY_TCH; + if (ts->prv_st_tch[CY_ST_FNGR1_IDX] < + CY_NUM_TRK_ID) { + if (ts->prv_st_tch[CY_ST_FNGR1_IDX] == id) { + st_x1 = g_xy_data.x2; + st_y1 = g_xy_data.y2; + st_z1 = g_xy_data.z2; + cur_st_tch[CY_ST_FNGR1_IDX] = id; + } else if (ts->prv_st_tch[CY_ST_FNGR2_IDX] == id) { + st_x2 = g_xy_data.x2; + st_y2 = g_xy_data.y2; + st_z2 = g_xy_data.z2; + cur_st_tch[CY_ST_FNGR2_IDX] = id; + } + } + cyttsp_xdebug("2nd XYZ:% 3d,% 3d,% 3d ID:% 2d\n", \ + g_xy_data.x2, g_xy_data.y2, g_xy_data.z2, \ + (g_xy_data.touch12_id & 0x0F)); + /* do not break */ + } + case 1: { + g_xy_data.x1 = be16_to_cpu(g_xy_data.x1); + g_xy_data.y1 = be16_to_cpu(g_xy_data.y1); + if (tilt) + FLIP_XY(g_xy_data.x1, g_xy_data.y1); + + if (rev_x) { + g_xy_data.x1 = INVERT_X(g_xy_data.x1, + ts->platform_data->panel_maxx); + if (g_xy_data.x1 < 0) + pr_debug("X value is negative. Please configure" + " maxx in platform data structure\n"); + } + if (rev_y) { + g_xy_data.y1 = INVERT_X(g_xy_data.y1, + ts->platform_data->panel_maxy); + if (g_xy_data.y1 < 0) + pr_debug("Y value is negative. Please configure" + " maxy in platform data structure"); + } + id = GET_TOUCH1_ID(g_xy_data.touch12_id); + if (ts->platform_data->use_trk_id) { + cur_mt_pos[CY_MT_TCH1_IDX][CY_XPOS] = + g_xy_data.x1; + cur_mt_pos[CY_MT_TCH1_IDX][CY_YPOS] = + g_xy_data.y1; + cur_mt_z[CY_MT_TCH1_IDX] = g_xy_data.z1; + } else { + cur_mt_pos[id][CY_XPOS] = g_xy_data.x1; + cur_mt_pos[id][CY_YPOS] = g_xy_data.y1; + cur_mt_z[id] = g_xy_data.z1; + } + cur_mt_tch[CY_MT_TCH1_IDX] = id; + cur_trk[id] = CY_TCH; + if (ts->prv_st_tch[CY_ST_FNGR1_IDX] < + CY_NUM_TRK_ID) { + if (ts->prv_st_tch[CY_ST_FNGR1_IDX] == id) { + st_x1 = g_xy_data.x1; + st_y1 = g_xy_data.y1; + st_z1 = g_xy_data.z1; + cur_st_tch[CY_ST_FNGR1_IDX] = id; + } else if (ts->prv_st_tch[CY_ST_FNGR2_IDX] == id) { + st_x2 = g_xy_data.x1; + st_y2 = g_xy_data.y1; + st_z2 = g_xy_data.z1; + cur_st_tch[CY_ST_FNGR2_IDX] = id; + } + } + cyttsp_xdebug("1st XYZ:% 3d,% 3d,% 3d ID:% 2d\n", \ + g_xy_data.x1, g_xy_data.y1, g_xy_data.z1, \ + ((g_xy_data.touch12_id >> 4) & 0x0F)); + break; + } + case 0: + default:{ + break; + } + } + + /* handle Single Touch signals */ + if (ts->platform_data->use_st) { + cyttsp_xdebug("ST STEP 0 - ST1 ID=%d ST2 ID=%d\n", \ + cur_st_tch[CY_ST_FNGR1_IDX], \ + cur_st_tch[CY_ST_FNGR2_IDX]); + if (cur_st_tch[CY_ST_FNGR1_IDX] > CY_NUM_TRK_ID) { + /* reassign finger 1 and 2 positions to new tracks */ + if (cur_tch > 0) { + /* reassign st finger1 */ + if (ts->platform_data->use_trk_id) { + id = CY_MT_TCH1_IDX; + cur_st_tch[CY_ST_FNGR1_IDX] = cur_mt_tch[id]; + } else { + id = GET_TOUCH1_ID(g_xy_data.touch12_id); + cur_st_tch[CY_ST_FNGR1_IDX] = id; + } + st_x1 = cur_mt_pos[id][CY_XPOS]; + st_y1 = cur_mt_pos[id][CY_YPOS]; + st_z1 = cur_mt_z[id]; + cyttsp_xdebug("ST STEP 1 - ST1 ID=%3d\n", \ + cur_st_tch[CY_ST_FNGR1_IDX]); + if ((cur_tch > 1) && + (cur_st_tch[CY_ST_FNGR2_IDX] > + CY_NUM_TRK_ID)) { + /* reassign st finger2 */ + if (cur_tch > 1) { + if (ts->platform_data->use_trk_id) { + id = CY_MT_TCH2_IDX; + cur_st_tch[CY_ST_FNGR2_IDX] = cur_mt_tch[id]; + } else { + id = GET_TOUCH2_ID(g_xy_data.touch12_id); + cur_st_tch[CY_ST_FNGR2_IDX] = id; + } + st_x2 = cur_mt_pos[id][CY_XPOS]; + st_y2 = cur_mt_pos[id][CY_YPOS]; + st_z2 = cur_mt_z[id]; + cyttsp_xdebug("ST STEP 2 - ST2 ID=%3d\n", \ + cur_st_tch[CY_ST_FNGR2_IDX]); + } + } + } + } else if (cur_st_tch[CY_ST_FNGR2_IDX] > CY_NUM_TRK_ID) { + if (cur_tch > 1) { + /* reassign st finger2 */ + if (ts->platform_data->use_trk_id) { + /* reassign st finger2 */ + id = CY_MT_TCH2_IDX; + cur_st_tch[CY_ST_FNGR2_IDX] = + cur_mt_tch[id]; + } else { + /* reassign st finger2 */ + id = GET_TOUCH2_ID(g_xy_data.touch12_id); + cur_st_tch[CY_ST_FNGR2_IDX] = id; + } + st_x2 = cur_mt_pos[id][CY_XPOS]; + st_y2 = cur_mt_pos[id][CY_YPOS]; + st_z2 = cur_mt_z[id]; + cyttsp_xdebug("ST STEP 3 - ST2 ID=%3d\n", \ + cur_st_tch[CY_ST_FNGR2_IDX]); + } + } + /* if the 1st touch is missing and there is a 2nd touch, + * then set the 1st touch to 2nd touch and terminate 2nd touch + */ + if ((cur_st_tch[CY_ST_FNGR1_IDX] > CY_NUM_TRK_ID) && + (cur_st_tch[CY_ST_FNGR2_IDX] < CY_NUM_TRK_ID)) { + st_x1 = st_x2; + st_y1 = st_y2; + st_z1 = st_z2; + cur_st_tch[CY_ST_FNGR1_IDX] = + cur_st_tch[CY_ST_FNGR2_IDX]; + cur_st_tch[CY_ST_FNGR2_IDX] = + CY_IGNR_TCH; + } + /* if the 2nd touch ends up equal to the 1st touch, + * then just report a single touch */ + if (cur_st_tch[CY_ST_FNGR1_IDX] == + cur_st_tch[CY_ST_FNGR2_IDX]) { + cur_st_tch[CY_ST_FNGR2_IDX] = + CY_IGNR_TCH; + } + /* set Single Touch current event signals */ + if (cur_st_tch[CY_ST_FNGR1_IDX] < CY_NUM_TRK_ID) { + input_report_abs(ts->input, + ABS_X, st_x1); + input_report_abs(ts->input, + ABS_Y, st_y1); + input_report_abs(ts->input, + ABS_PRESSURE, st_z1); + input_report_key(ts->input, + BTN_TOUCH, + CY_TCH); + input_report_abs(ts->input, + ABS_TOOL_WIDTH, + curr_tool_width); + cyttsp_debug("ST->F1:%3d X:%3d Y:%3d Z:%3d\n", \ + cur_st_tch[CY_ST_FNGR1_IDX], \ + st_x1, st_y1, st_z1); + if (cur_st_tch[CY_ST_FNGR2_IDX] < CY_NUM_TRK_ID) { + input_report_key(ts->input, BTN_2, CY_TCH); + input_report_abs(ts->input, ABS_HAT0X, st_x2); + input_report_abs(ts->input, ABS_HAT0Y, st_y2); + cyttsp_debug("ST->F2:%3d X:%3d Y:%3d Z:%3d\n", \ + cur_st_tch[CY_ST_FNGR2_IDX], + st_x2, st_y2, st_z2); + } else { + input_report_key(ts->input, + BTN_2, + CY_NTCH); + } + } else { + input_report_abs(ts->input, ABS_PRESSURE, CY_NTCH); + input_report_key(ts->input, BTN_TOUCH, CY_NTCH); + input_report_key(ts->input, BTN_2, CY_NTCH); + } + /* update platform data for the current single touch info */ + ts->prv_st_tch[CY_ST_FNGR1_IDX] = cur_st_tch[CY_ST_FNGR1_IDX]; + ts->prv_st_tch[CY_ST_FNGR2_IDX] = cur_st_tch[CY_ST_FNGR2_IDX]; + + } + + /* handle Multi-touch signals */ + if (ts->platform_data->use_mt) { + if (ts->platform_data->use_trk_id) { + /* terminate any previous touch where the track + * is missing from the current event */ + for (id = 0; id < CY_NUM_TRK_ID; id++) { + if ((ts->act_trk[id] != CY_NTCH) && + (cur_trk[id] == CY_NTCH)) { + input_report_abs(ts->input, + ABS_MT_TRACKING_ID, + id); + input_report_abs(ts->input, + ABS_MT_TOUCH_MAJOR, + CY_NTCH); + input_report_abs(ts->input, + ABS_MT_WIDTH_MAJOR, + curr_tool_width); + input_report_abs(ts->input, + ABS_MT_POSITION_X, + ts->prv_mt_pos[id][CY_XPOS]); + input_report_abs(ts->input, + ABS_MT_POSITION_Y, + ts->prv_mt_pos[id][CY_YPOS]); + CY_MT_SYNC(ts->input); + ts->act_trk[id] = CY_NTCH; + ts->prv_mt_pos[id][CY_XPOS] = 0; + ts->prv_mt_pos[id][CY_YPOS] = 0; + } + } + /* set Multi-Touch current event signals */ + for (id = 0; id < CY_NUM_MT_TCH_ID; id++) { + if (cur_mt_tch[id] < CY_NUM_TRK_ID) { + input_report_abs(ts->input, + ABS_MT_TRACKING_ID, + cur_mt_tch[id]); + input_report_abs(ts->input, + ABS_MT_TOUCH_MAJOR, + cur_mt_z[id]); + input_report_abs(ts->input, + ABS_MT_WIDTH_MAJOR, + curr_tool_width); + input_report_abs(ts->input, + ABS_MT_POSITION_X, + cur_mt_pos[id][CY_XPOS]); + input_report_abs(ts->input, + ABS_MT_POSITION_Y, + cur_mt_pos[id][CY_YPOS]); + CY_MT_SYNC(ts->input); + ts->act_trk[id] = CY_TCH; + ts->prv_mt_pos[id][CY_XPOS] = + cur_mt_pos[id][CY_XPOS]; + ts->prv_mt_pos[id][CY_YPOS] = + cur_mt_pos[id][CY_YPOS]; + } + } + } else { + /* set temporary track array elements to voids */ + for (id = 0; id < CY_NUM_MT_TCH_ID; id++) { + tmp_trk[id] = CY_IGNR_TCH; + snd_trk[id] = CY_IGNR_TCH; + } + + /* get what is currently active */ + for (i = 0, id = 0; + id < CY_NUM_TRK_ID && i < CY_NUM_MT_TCH_ID; + id++) { + if (cur_trk[id] == CY_TCH) { + /* only incr counter if track found */ + tmp_trk[i] = id; + i++; + } + } + cyttsp_xdebug("T1: t0=%d, t1=%d, t2=%d, t3=%d\n", \ + tmp_trk[0], tmp_trk[1], tmp_trk[2], \ + tmp_trk[3]); + cyttsp_xdebug("T1: p0=%d, p1=%d, p2=%d, p3=%d\n", \ + ts->prv_mt_tch[0], ts->prv_mt_tch[1], \ + ts->prv_mt_tch[2], ts->prv_mt_tch[3]); + + /* pack in still active previous touches */ + for (id = 0, prv_tch = 0; + id < CY_NUM_MT_TCH_ID; id++) { + if (tmp_trk[id] < CY_NUM_TRK_ID) { + if (cyttsp_inlist(ts->prv_mt_tch, + tmp_trk[id], &loc, + CY_NUM_MT_TCH_ID)) { + loc &= CY_NUM_MT_TCH_ID - 1; + snd_trk[loc] = tmp_trk[id]; + prv_tch++; + cyttsp_xdebug("inlist s[%d]=%d t[%d]=%d l=%d p=%d\n", \ + loc, snd_trk[loc], \ + id, tmp_trk[id], \ + loc, prv_tch); + } else { + cyttsp_xdebug("not inlist s[%d]=%d t[%d]=%d l=%d \n", \ + id, snd_trk[id], \ + id, tmp_trk[id], \ + loc); + } + } + } + cyttsp_xdebug("S1: s0=%d, s1=%d, s2=%d, s3=%d p=%d\n", \ + snd_trk[0], snd_trk[1], snd_trk[2], \ + snd_trk[3], prv_tch); + + /* pack in new touches */ + for (id = 0; id < CY_NUM_MT_TCH_ID; id++) { + if (tmp_trk[id] < CY_NUM_TRK_ID) { + if (!cyttsp_inlist(snd_trk, tmp_trk[id], &loc, CY_NUM_MT_TCH_ID)) { + cyttsp_xdebug("not inlist t[%d]=%d l=%d\n", \ + id, tmp_trk[id], loc); + if (cyttsp_next_avail_inlist(snd_trk, &loc, CY_NUM_MT_TCH_ID)) { + loc &= CY_NUM_MT_TCH_ID - 1; + snd_trk[loc] = tmp_trk[id]; + cyttsp_xdebug("put inlist s[%d]=%d t[%d]=%d\n", + loc, snd_trk[loc], id, tmp_trk[id]); + } + } else { + cyttsp_xdebug("is in list s[%d]=%d t[%d]=%d loc=%d\n", \ + id, snd_trk[id], id, tmp_trk[id], loc); + } + } + } + cyttsp_xdebug("S2: s0=%d, s1=%d, s2=%d, s3=%d\n", \ + snd_trk[0], snd_trk[1], + snd_trk[2], snd_trk[3]); + + /* sync motion event signals for each current touch */ + for (id = 0; id < CY_NUM_MT_TCH_ID; id++) { + /* z will either be 0 (NOTOUCH) or + * some pressure (TOUCH) */ + cyttsp_xdebug("MT0 prev[%d]=%d temp[%d]=%d send[%d]=%d\n", \ + id, ts->prv_mt_tch[id], \ + id, tmp_trk[id], \ + id, snd_trk[id]); + if (snd_trk[id] < CY_NUM_TRK_ID) { + input_report_abs(ts->input, + ABS_MT_TOUCH_MAJOR, + cur_mt_z[snd_trk[id]]); + input_report_abs(ts->input, + ABS_MT_WIDTH_MAJOR, + curr_tool_width); + input_report_abs(ts->input, + ABS_MT_POSITION_X, + cur_mt_pos[snd_trk[id]][CY_XPOS]); + input_report_abs(ts->input, + ABS_MT_POSITION_Y, + cur_mt_pos[snd_trk[id]][CY_YPOS]); + CY_MT_SYNC(ts->input); + cyttsp_debug("MT1->TID:%2d X:%3d Y:%3d Z:%3d touch-sent\n", \ + snd_trk[id], \ + cur_mt_pos[snd_trk[id]][CY_XPOS], \ + cur_mt_pos[snd_trk[id]][CY_YPOS], \ + cur_mt_z[snd_trk[id]]); + } else if (ts->prv_mt_tch[id] < CY_NUM_TRK_ID) { + /* void out this touch */ + input_report_abs(ts->input, + ABS_MT_TOUCH_MAJOR, + CY_NTCH); + input_report_abs(ts->input, + ABS_MT_WIDTH_MAJOR, + curr_tool_width); + input_report_abs(ts->input, + ABS_MT_POSITION_X, + ts->prv_mt_pos[ts->prv_mt_tch[id]][CY_XPOS]); + input_report_abs(ts->input, + ABS_MT_POSITION_Y, + ts->prv_mt_pos[ts->prv_mt_tch[id]][CY_YPOS]); + CY_MT_SYNC(ts->input); + cyttsp_debug("MT2->TID:%2d X:%3d Y:%3d Z:%3d lift off-sent\n", \ + ts->prv_mt_tch[id], \ + ts->prv_mt_pos[ts->prv_mt_tch[id]][CY_XPOS], \ + ts->prv_mt_pos[ts->prv_mt_tch[id]][CY_YPOS], \ + CY_NTCH); + } else { + /* do not stuff any signals for this + * previously and currently + * void touches */ + cyttsp_xdebug("MT3->send[%d]=%d - No touch - NOT sent\n", \ + id, snd_trk[id]); + } + } + + /* save current posted tracks to + * previous track memory */ + for (id = 0; id < CY_NUM_MT_TCH_ID; id++) { + ts->prv_mt_tch[id] = snd_trk[id]; + if (snd_trk[id] < CY_NUM_TRK_ID) { + ts->prv_mt_pos[snd_trk[id]][CY_XPOS] = + cur_mt_pos[snd_trk[id]][CY_XPOS]; + ts->prv_mt_pos[snd_trk[id]][CY_YPOS] = + cur_mt_pos[snd_trk[id]][CY_YPOS]; + cyttsp_xdebug("MT4->TID:%2d X:%3d Y:%3d Z:%3d save for previous\n", \ + snd_trk[id], \ + ts->prv_mt_pos[snd_trk[id]][CY_XPOS], \ + ts->prv_mt_pos[snd_trk[id]][CY_YPOS], \ + CY_NTCH); + } + } + for (id = 0; id < CY_NUM_TRK_ID; id++) + ts->act_trk[id] = CY_NTCH; + for (id = 0; id < CY_NUM_MT_TCH_ID; id++) { + if (snd_trk[id] < CY_NUM_TRK_ID) + ts->act_trk[snd_trk[id]] = CY_TCH; + } + } + } + + /* handle gestures */ + if (ts->platform_data->use_gestures) { + if (g_xy_data.gest_id) { + input_report_key(ts->input, + BTN_3, CY_TCH); + input_report_abs(ts->input, + ABS_HAT1X, g_xy_data.gest_id); + input_report_abs(ts->input, + ABS_HAT2Y, g_xy_data.gest_cnt); + } + } + + /* signal the view motion event */ + input_sync(ts->input); + + for (id = 0; id < CY_NUM_TRK_ID; id++) { + /* update platform data for the current MT information */ + ts->act_trk[id] = cur_trk[id]; + } + +exit_xy_worker: + if (cyttsp_disable_touch) { + /* Turn off the touch interrupts */ + cyttsp_debug("Not enabling touch\n"); + } else { + if (ts->client->irq == 0) { + /* restart event timer */ + mod_timer(&ts->timer, jiffies + TOUCHSCREEN_TIMEOUT); + } else { + /* re-enable the interrupt after processing */ + enable_irq(ts->client->irq); + } + } + return; +} + +static int cyttsp_inlist(u16 prev_track[], u8 cur_trk_id, + u8 *prev_loc, u8 num_touches) +{ + u8 id = 0; + + *prev_loc = CY_IGNR_TCH; + + cyttsp_xdebug("IN p[%d]=%d c=%d n=%d loc=%d\n", \ + id, prev_track[id], cur_trk_id, \ + num_touches, *prev_loc); + for (id = 0, *prev_loc = CY_IGNR_TCH; + (id < num_touches); id++) { + cyttsp_xdebug("p[%d]=%d c=%d n=%d loc=%d\n", \ + id, prev_track[id], cur_trk_id, \ + num_touches, *prev_loc); + if (prev_track[id] == cur_trk_id) { + *prev_loc = id; + break; + } + } + cyttsp_xdebug("OUT p[%d]=%d c=%d n=%d loc=%d\n", \ + id, prev_track[id], cur_trk_id, num_touches, *prev_loc); + + return ((*prev_loc < CY_NUM_TRK_ID) ? true : false); +} + +static int cyttsp_next_avail_inlist(u16 cur_trk[], + u8 *new_loc, u8 num_touches) +{ + u8 id; + + for (id = 0, *new_loc = CY_IGNR_TCH; + (id < num_touches); id++) { + if (cur_trk[id] > CY_NUM_TRK_ID) { + *new_loc = id; + break; + } + } + + return ((*new_loc < CY_NUM_TRK_ID) ? true : false); +} + +/* Timer function used as dummy interrupt driver */ +static void cyttsp_timer(unsigned long handle) +{ + struct cyttsp *ts = (struct cyttsp *) handle; + + cyttsp_xdebug("TTSP Device timer event\n"); + + /* schedule motion signal handling */ + queue_work(cyttsp_ts_wq, &ts->work); + + return; +} + + + +/* ************************************************************************ + * ISR function. This function is general, initialized in drivers init + * function + * ************************************************************************ */ +static irqreturn_t cyttsp_irq(int irq, void *handle) +{ + struct cyttsp *ts = (struct cyttsp *) handle; + + cyttsp_xdebug("%s: Got IRQ\n", CY_I2C_NAME); + + /* disable further interrupts until this interrupt is processed */ + disable_irq_nosync(ts->client->irq); + + /* schedule motion signal handling */ + queue_work(cyttsp_ts_wq, &ts->work); + return IRQ_HANDLED; +} + +/* ************************************************************************ + * Probe initialization functions + * ************************************************************************ */ +static int cyttsp_putbl(struct cyttsp *ts, int show, + int show_status, int show_version, int show_cid) +{ + int retval = CY_OK; + + int num_bytes = (show_status * 3) + (show_version * 6) + (show_cid * 3); + + if (show_cid) + num_bytes = sizeof(struct cyttsp_bootloader_data_t); + else if (show_version) + num_bytes = sizeof(struct cyttsp_bootloader_data_t) - 3; + else + num_bytes = sizeof(struct cyttsp_bootloader_data_t) - 9; + + if (show) { + retval = i2c_smbus_read_i2c_block_data(ts->client, + CY_REG_BASE, num_bytes, (u8 *)&g_bl_data); + if (show_status) { + cyttsp_debug("BL%d: f=%02X s=%02X err=%02X bl=%02X%02X bld=%02X%02X\n", \ + show, \ + g_bl_data.bl_file, \ + g_bl_data.bl_status, \ + g_bl_data.bl_error, \ + g_bl_data.blver_hi, g_bl_data.blver_lo, \ + g_bl_data.bld_blver_hi, g_bl_data.bld_blver_lo); + } + if (show_version) { + cyttsp_debug("BL%d: ttspver=0x%02X%02X appid=0x%02X%02X appver=0x%02X%02X\n", \ + show, \ + g_bl_data.ttspver_hi, g_bl_data.ttspver_lo, \ + g_bl_data.appid_hi, g_bl_data.appid_lo, \ + g_bl_data.appver_hi, g_bl_data.appver_lo); + } + if (show_cid) { + cyttsp_debug("BL%d: cid=0x%02X%02X%02X\n", \ + show, \ + g_bl_data.cid_0, \ + g_bl_data.cid_1, \ + g_bl_data.cid_2); + } + } + + return retval; +} + +#ifdef CY_INCLUDE_LOAD_FILE +#define CY_MAX_I2C_LEN 256 +#define CY_MAX_TRY 10 +#define CY_BL_PAGE_SIZE 16 +#define CY_BL_NUM_PAGES 5 +static int cyttsp_i2c_wr_blk_chunks(struct cyttsp *ts, u8 command, + u8 length, const u8 *values) +{ + int retval = CY_OK; + int block = 1; + + u8 dataray[CY_MAX_I2C_LEN]; + + /* first page already includes the bl page offset */ + retval = i2c_smbus_write_i2c_block_data(ts->client, CY_REG_BASE, + CY_BL_PAGE_SIZE+1, values); + values += CY_BL_PAGE_SIZE+1; + length -= CY_BL_PAGE_SIZE+1; + + /* rem blocks require bl page offset stuffing */ + while (length && + (block < CY_BL_NUM_PAGES) && + !(retval < CY_OK)) { + udelay(43*2); /* TRM * 2 */ + dataray[0] = CY_BL_PAGE_SIZE*block; + memcpy(&dataray[1], values, + length >= CY_BL_PAGE_SIZE ? + CY_BL_PAGE_SIZE : length); + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, + length >= CY_BL_PAGE_SIZE ? + CY_BL_PAGE_SIZE + 1 : length+1, dataray); + values += CY_BL_PAGE_SIZE; + length = length >= CY_BL_PAGE_SIZE ? + length - CY_BL_PAGE_SIZE : 0; + block++; + } + + return retval; +} + +static int cyttsp_bootload_app(struct cyttsp *ts) +{ + int retval = CY_OK; + int i, tries; + u8 host_reg; + + cyttsp_debug("load new firmware \n"); + /* reset TTSP Device back to bootloader mode */ + host_reg = CY_SOFT_RESET_MODE; + retval = i2c_smbus_write_i2c_block_data(ts->client, CY_REG_BASE, + sizeof(host_reg), &host_reg); + /* wait for TTSP Device to complete reset back to bootloader */ + tries = 0; + do { + mdelay(1); + cyttsp_putbl(ts, 3, false, false, false); + } while (g_bl_data.bl_status != 0x10 && + g_bl_data.bl_status != 0x11 && + tries++ < 100); + cyttsp_debug("load file - tver=0x%02X%02X a_id=0x%02X%02X aver=0x%02X%02X\n", \ + cyttsp_fw_tts_verh, cyttsp_fw_tts_verl, \ + cyttsp_fw_app_idh, cyttsp_fw_app_idl, \ + cyttsp_fw_app_verh, cyttsp_fw_app_verl); + + /* download new TTSP Application to the Bootloader */ + if (!(retval < CY_OK)) { + i = 0; + /* send bootload initiation command */ + if (cyttsp_fw[i].Command == CY_BL_INIT_LOAD) { + g_bl_data.bl_file = 0; + g_bl_data.bl_status = 0; + g_bl_data.bl_error = 0; + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, + cyttsp_fw[i].Length, cyttsp_fw[i].Block); + /* delay to allow bl to get ready for block writes */ + i++; + tries = 0; + do { + mdelay(100); + cyttsp_putbl(ts, 4, false, false, false); + } while (g_bl_data.bl_status != 0x10 && + g_bl_data.bl_status != 0x11 && + tries++ < 100); + cyttsp_debug("wait init f=%02X, s=%02X, e=%02X t=%d\n", \ + g_bl_data.bl_file, g_bl_data.bl_status, \ + g_bl_data.bl_error, tries); + /* send bootload firmware load blocks */ + if (!(retval < CY_OK)) { + while (cyttsp_fw[i].Command == CY_BL_WRITE_BLK) { + retval = cyttsp_i2c_wr_blk_chunks(ts, + CY_REG_BASE, + cyttsp_fw[i].Length, + cyttsp_fw[i].Block); + cyttsp_xdebug("BL DNLD Rec=% 3d Len=% 3d Addr=%04X\n", \ + cyttsp_fw[i].Record, \ + cyttsp_fw[i].Length, \ + cyttsp_fw[i].Address); + i++; + if (retval < CY_OK) { + cyttsp_debug("BL fail Rec=%3d retval=%d\n", \ + cyttsp_fw[i-1].Record, \ + retval); + break; + } else { + tries = 0; + cyttsp_putbl(ts, 5, false, false, false); + while (!((g_bl_data.bl_status == 0x10) && + (g_bl_data.bl_error == 0x20)) && + !((g_bl_data.bl_status == 0x11) && + (g_bl_data.bl_error == 0x20)) && + (tries++ < 100)) { + mdelay(1); + cyttsp_putbl(ts, 5, false, false, false); + } + } + } + + if (!(retval < CY_OK)) { + while (i < cyttsp_fw_records) { + retval = i2c_smbus_write_i2c_block_data(ts->client, CY_REG_BASE, + cyttsp_fw[i].Length, + cyttsp_fw[i].Block); + i++; + tries = 0; + do { + mdelay(100); + cyttsp_putbl(ts, 6, true, false, false); + } while (g_bl_data.bl_status != 0x10 && + g_bl_data.bl_status != 0x11 && + tries++ < 100); + cyttsp_debug("wait term f=%02X, s=%02X, e=%02X t=%d\n", \ + g_bl_data.bl_file, \ + g_bl_data.bl_status, \ + g_bl_data.bl_error, \ + tries); + if (retval < CY_OK) + break; + } + } + } + } + } + + /* reset TTSP Device back to bootloader mode */ + host_reg = CY_SOFT_RESET_MODE; + retval = i2c_smbus_write_i2c_block_data(ts->client, CY_REG_BASE, + sizeof(host_reg), &host_reg); + /* wait for TTSP Device to complete reset back to bootloader */ + tries = 0; + do { + mdelay(1); + cyttsp_putbl(ts, 3, false, false, false); + } while (g_bl_data.bl_status != 0x10 && + g_bl_data.bl_status != 0x11 && + tries++ < 100); + + /* set arg2 to non-0 to activate */ + retval = cyttsp_putbl(ts, 8, true, true, true); + + return retval; +} +#else +static int cyttsp_bootload_app(struct cyttsp *ts) +{ + cyttsp_debug("no-load new firmware \n"); + return CY_OK; +} +#endif /* CY_INCLUDE_LOAD_FILE */ + + +static int cyttsp_power_on(struct cyttsp *ts) +{ + int retval = CY_OK; + u8 host_reg; + int tries; + + cyttsp_debug("Power up \n"); + + /* check if the TTSP device has a bootloader installed */ + host_reg = CY_SOFT_RESET_MODE; + retval = i2c_smbus_write_i2c_block_data(ts->client, CY_REG_BASE, + sizeof(host_reg), &host_reg); + tries = 0; + do { + mdelay(1); + + /* set arg2 to non-0 to activate */ + retval = cyttsp_putbl(ts, 1, true, true, true); + cyttsp_info("BL%d: f=%02X s=%02X err=%02X bl=%02X%02X bld=%02X%02X R=%d\n", \ + 101, \ + g_bl_data.bl_file, g_bl_data.bl_status, \ + g_bl_data.bl_error, \ + g_bl_data.blver_hi, g_bl_data.blver_lo, \ + g_bl_data.bld_blver_hi, g_bl_data.bld_blver_lo, + retval); + cyttsp_info("BL%d: tver=%02X%02X a_id=%02X%02X aver=%02X%02X\n", \ + 102, \ + g_bl_data.ttspver_hi, g_bl_data.ttspver_lo, \ + g_bl_data.appid_hi, g_bl_data.appid_lo, \ + g_bl_data.appver_hi, g_bl_data.appver_lo); + cyttsp_info("BL%d: c_id=%02X%02X%02X\n", \ + 103, \ + g_bl_data.cid_0, g_bl_data.cid_1, g_bl_data.cid_2); + } while (!(retval < CY_OK) && + !GET_BOOTLOADERMODE(g_bl_data.bl_status) && + !(g_bl_data.bl_file == CY_OP_MODE + CY_LOW_PWR_MODE) && + tries++ < 100); + + /* is bootloader missing? */ + if (!(retval < CY_OK)) { + cyttsp_xdebug("Ret=%d Check if bootloader is missing...\n", \ + retval); + if (!GET_BOOTLOADERMODE(g_bl_data.bl_status)) { + /* skip all bl and sys info and go to op mode */ + if (!(retval < CY_OK)) { + cyttsp_xdebug("Bl is missing (ret=%d)\n", \ + retval); + host_reg = CY_OP_MODE/* + CY_LOW_PWR_MODE*/; + retval = i2c_smbus_write_i2c_block_data(ts->client, CY_REG_BASE, + sizeof(host_reg), &host_reg); + /* wait for TTSP Device to complete switch to + * Operational mode */ + mdelay(1000); + goto bypass; + } + } + } + + + /* take TTSP out of bootloader mode; go to TrueTouch operational mode */ + if (!(retval < CY_OK)) { + cyttsp_xdebug1("exit bootloader; go operational\n"); + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, sizeof(bl_cmd), bl_cmd); + tries = 0; + do { + mdelay(100); + cyttsp_putbl(ts, 4, true, false, false); + cyttsp_info("BL%d: f=%02X s=%02X err=%02X bl=%02X%02X bld=%02X%02X\n", \ + 104, \ + g_bl_data.bl_file, g_bl_data.bl_status, \ + g_bl_data.bl_error, \ + g_bl_data.blver_hi, g_bl_data.blver_lo, \ + g_bl_data.bld_blver_hi, g_bl_data.bld_blver_lo); + } while (GET_BOOTLOADERMODE(g_bl_data.bl_status) && + tries++ < 100); + } + + + + if (!(retval < CY_OK) && + cyttsp_app_load()) { + if (CY_DIFF(g_bl_data.ttspver_hi, cyttsp_tts_verh()) || + CY_DIFF(g_bl_data.ttspver_lo, cyttsp_tts_verl()) || + CY_DIFF(g_bl_data.appid_hi, cyttsp_app_idh()) || + CY_DIFF(g_bl_data.appid_lo, cyttsp_app_idl()) || + CY_DIFF(g_bl_data.appver_hi, cyttsp_app_verh()) || + CY_DIFF(g_bl_data.appver_lo, cyttsp_app_verl()) || + CY_DIFF(g_bl_data.cid_0, cyttsp_cid_0()) || + CY_DIFF(g_bl_data.cid_1, cyttsp_cid_1()) || + CY_DIFF(g_bl_data.cid_2, cyttsp_cid_2()) || + cyttsp_force_fw_load()) { + cyttsp_debug("blttsp=0x%02X%02X flttsp=0x%02X%02X force=%d\n", \ + g_bl_data.ttspver_hi, g_bl_data.ttspver_lo, \ + cyttsp_tts_verh(), cyttsp_tts_verl(), \ + cyttsp_force_fw_load()); + cyttsp_debug("blappid=0x%02X%02X flappid=0x%02X%02X\n", \ + g_bl_data.appid_hi, g_bl_data.appid_lo, \ + cyttsp_app_idh(), cyttsp_app_idl()); + cyttsp_debug("blappver=0x%02X%02X flappver=0x%02X%02X\n", \ + g_bl_data.appver_hi, g_bl_data.appver_lo, \ + cyttsp_app_verh(), cyttsp_app_verl()); + cyttsp_debug("blcid=0x%02X%02X%02X flcid=0x%02X%02X%02X\n", \ + g_bl_data.cid_0, \ + g_bl_data.cid_1, \ + g_bl_data.cid_2, \ + cyttsp_cid_0(), \ + cyttsp_cid_1(), \ + cyttsp_cid_2()); + /* enter bootloader to load new app into TTSP Device */ + retval = cyttsp_bootload_app(ts); + /* take TTSP device out of bootloader mode; + * switch back to TrueTouch operational mode */ + if (!(retval < CY_OK)) { + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, + sizeof(bl_cmd), bl_cmd); + /* wait for TTSP Device to complete + * switch to Operational mode */ + tries = 0; + do { + mdelay(100); + cyttsp_putbl(ts, 9, false, false, false); + } while (GET_BOOTLOADERMODE(g_bl_data.bl_status) && + tries++ < 100); + cyttsp_putbl(ts, 9, true, false, false); + } + } + } + +bypass: + /* switch to System Information mode to read versions + * and set interval registers */ + if (!(retval < CY_OK)) { + cyttsp_debug("switch to sysinfo mode \n"); + host_reg = CY_SYSINFO_MODE; + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, sizeof(host_reg), &host_reg); + /* wait for TTSP Device to complete switch to SysInfo mode */ + mdelay(100); + if (!(retval < CY_OK)) { + retval = i2c_smbus_read_i2c_block_data(ts->client, + CY_REG_BASE, + sizeof(struct cyttsp_sysinfo_data_t), + (u8 *)&g_sysinfo_data); + cyttsp_debug("SI2: hst_mode=0x%02X mfg_cmd=0x%02X mfg_stat=0x%02X\n", \ + g_sysinfo_data.hst_mode, \ + g_sysinfo_data.mfg_cmd, \ + g_sysinfo_data.mfg_stat); + cyttsp_debug("SI2: bl_ver=0x%02X%02X\n", \ + g_sysinfo_data.bl_verh, \ + g_sysinfo_data.bl_verl); + cyttsp_debug("SI2: sysinfo act_int=0x%02X tch_tmout=0x%02X lp_int=0x%02X\n", \ + g_sysinfo_data.act_intrvl, \ + g_sysinfo_data.tch_tmout, \ + g_sysinfo_data.lp_intrvl); + cyttsp_info("SI%d: tver=%02X%02X a_id=%02X%02X aver=%02X%02X\n", \ + 102, \ + g_sysinfo_data.tts_verh, \ + g_sysinfo_data.tts_verl, \ + g_sysinfo_data.app_idh, \ + g_sysinfo_data.app_idl, \ + g_sysinfo_data.app_verh, \ + g_sysinfo_data.app_verl); + cyttsp_info("SI%d: c_id=%02X%02X%02X\n", \ + 103, \ + g_sysinfo_data.cid[0], \ + g_sysinfo_data.cid[1], \ + g_sysinfo_data.cid[2]); + if (!(retval < CY_OK) && + (CY_DIFF(ts->platform_data->act_intrvl, + CY_ACT_INTRVL_DFLT) || + CY_DIFF(ts->platform_data->tch_tmout, + CY_TCH_TMOUT_DFLT) || + CY_DIFF(ts->platform_data->lp_intrvl, + CY_LP_INTRVL_DFLT))) { + if (!(retval < CY_OK)) { + u8 intrvl_ray[sizeof(ts->platform_data->act_intrvl) + + sizeof(ts->platform_data->tch_tmout) + + sizeof(ts->platform_data->lp_intrvl)]; + u8 i = 0; + + intrvl_ray[i++] = + ts->platform_data->act_intrvl; + intrvl_ray[i++] = + ts->platform_data->tch_tmout; + intrvl_ray[i++] = + ts->platform_data->lp_intrvl; + + cyttsp_debug("SI2: platinfo act_intrvl=0x%02X tch_tmout=0x%02X lp_intrvl=0x%02X\n", \ + ts->platform_data->act_intrvl, \ + ts->platform_data->tch_tmout, \ + ts->platform_data->lp_intrvl); + /* set intrvl registers */ + retval = i2c_smbus_write_i2c_block_data( + ts->client, + CY_REG_ACT_INTRVL, + sizeof(intrvl_ray), intrvl_ray); + mdelay(CY_DLY_SYSINFO); + } + } + } + /* switch back to Operational mode */ + cyttsp_debug("switch back to operational mode \n"); + if (!(retval < CY_OK)) { + host_reg = CY_OP_MODE/* + CY_LOW_PWR_MODE*/; + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, + sizeof(host_reg), &host_reg); + /* wait for TTSP Device to complete + * switch to Operational mode */ + mdelay(100); + } + } + /* init gesture setup; + * this is required even if not using gestures + * in order to set the active distance */ + if (!(retval < CY_OK)) { + u8 gesture_setup; + cyttsp_debug("init gesture setup \n"); + gesture_setup = ts->platform_data->gest_set; + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_GEST_SET, + sizeof(gesture_setup), &gesture_setup); + mdelay(CY_DLY_DFLT); + } + + if (!(retval < CY_OK)) + ts->platform_data->power_state = CY_ACTIVE_STATE; + else + ts->platform_data->power_state = CY_IDLE_STATE; + + cyttsp_debug("Retval=%d Power state is %s\n", \ + retval, \ + ts->platform_data->power_state == CY_ACTIVE_STATE ? \ + "ACTIVE" : "IDLE"); + + return retval; +} + +static int cyttsp_power_device(struct cyttsp *ts, bool on) +{ + int rc = 0, i; + const struct cyttsp_regulator *reg_info = + ts->platform_data->regulator_info; + u8 num_reg = ts->platform_data->num_regulators; + + if (!reg_info) { + pr_err("regulator pdata not specified\n"); + return -EINVAL; + } + + if (on == false) /* Turn off the regulators */ + goto ts_reg_disable; + + ts->vdd = kzalloc(num_reg * sizeof(struct regulator *), GFP_KERNEL); + if (!ts->vdd) { + pr_err("unable to allocate memory\n"); + return -ENOMEM; + } + + for (i = 0; i < num_reg; i++) { + ts->vdd[i] = regulator_get(&ts->client->dev, reg_info[i].name); + if (IS_ERR(ts->vdd[i])) { + rc = PTR_ERR(ts->vdd[i]); + pr_err("%s:regulator get failed rc=%d\n", + __func__, rc); + goto error_vdd; + } + + if (regulator_count_voltages(ts->vdd[i]) > 0) { + rc = regulator_set_voltage(ts->vdd[i], + reg_info[i].min_uV, reg_info[i].max_uV); + if (rc) { + pr_err("%s: regulator_set_voltage" + "failed rc =%d\n", __func__, rc); + regulator_put(ts->vdd[i]); + goto error_vdd; + } + } + + rc = regulator_set_optimum_mode(ts->vdd[i], + reg_info[i].load_uA); + if (rc < 0) { + pr_err("%s: regulator_set_optimum_mode failed rc=%d\n", + __func__, rc); + + regulator_set_voltage(ts->vdd[i], 0, + reg_info[i].max_uV); + regulator_put(ts->vdd[i]); + goto error_vdd; + } + + rc = regulator_enable(ts->vdd[i]); + if (rc) { + pr_err("%s: regulator_enable failed rc =%d\n", + __func__, rc); + regulator_set_optimum_mode(ts->vdd[i], 0); + regulator_set_voltage(ts->vdd[i], 0, + reg_info[i].max_uV); + regulator_put(ts->vdd[i]); + goto error_vdd; + } + } + + return rc; + +ts_reg_disable: + i = ts->platform_data->num_regulators; +error_vdd: + while (--i >= 0) { + if (regulator_count_voltages(ts->vdd[i]) > 0) + regulator_set_voltage(ts->vdd[i], 0, + reg_info[i].max_uV); + regulator_set_optimum_mode(ts->vdd[i], 0); + regulator_disable(ts->vdd[i]); + regulator_put(ts->vdd[i]); + } + kfree(ts->vdd); + return rc; +} + +/* cyttsp_initialize: Driver Initialization. This function takes + * care of the following tasks: + * 1. Create and register an input device with input layer + * 2. Take CYTTSP device out of bootloader mode; go operational + * 3. Start any timers/Work queues. */ +static int cyttsp_initialize(struct i2c_client *client, struct cyttsp *ts) +{ + struct input_dev *input_device; + int error = 0; + int retval = CY_OK; + u8 id; + + /* Create the input device and register it. */ + input_device = input_allocate_device(); + if (!input_device) { + error = -ENOMEM; + cyttsp_xdebug1("err input allocate device\n"); + goto error_free_device; + } + + if (!client) { + error = ~ENODEV; + cyttsp_xdebug1("err client is Null\n"); + goto error_free_device; + } + + if (!ts) { + error = ~ENODEV; + cyttsp_xdebug1("err context is Null\n"); + goto error_free_device; + } + + ts->input = input_device; + input_device->name = CY_I2C_NAME; + input_device->phys = ts->phys; + input_device->dev.parent = &client->dev; + + /* init the touch structures */ + ts->num_prv_st_tch = CY_NTCH; + for (id = 0; id < CY_NUM_TRK_ID; id++) { + ts->act_trk[id] = CY_NTCH; + ts->prv_mt_pos[id][CY_XPOS] = 0; + ts->prv_mt_pos[id][CY_YPOS] = 0; + } + + for (id = 0; id < CY_NUM_MT_TCH_ID; id++) + ts->prv_mt_tch[id] = CY_IGNR_TCH; + + for (id = 0; id < CY_NUM_ST_TCH_ID; id++) + ts->prv_st_tch[id] = CY_IGNR_TCH; + + set_bit(EV_SYN, input_device->evbit); + set_bit(EV_KEY, input_device->evbit); + set_bit(EV_ABS, input_device->evbit); + set_bit(BTN_TOUCH, input_device->keybit); + set_bit(BTN_2, input_device->keybit); + if (ts->platform_data->use_gestures) + set_bit(BTN_3, input_device->keybit); + + input_set_abs_params(input_device, ABS_X, ts->platform_data->disp_minx, + ts->platform_data->disp_maxx, 0, 0); + input_set_abs_params(input_device, ABS_Y, ts->platform_data->disp_miny, + ts->platform_data->disp_maxy, 0, 0); + input_set_abs_params(input_device, + ABS_TOOL_WIDTH, 0, CY_LARGE_TOOL_WIDTH, 0 , 0); + input_set_abs_params(input_device, + ABS_PRESSURE, 0, CY_MAXZ, 0, 0); + input_set_abs_params(input_device, + ABS_HAT0X, 0, ts->platform_data->panel_maxx, 0, 0); + input_set_abs_params(input_device, + ABS_HAT0Y, 0, ts->platform_data->panel_maxy, 0, 0); + if (ts->platform_data->use_gestures) { + input_set_abs_params(input_device, + ABS_HAT1X, 0, CY_MAXZ, 0, 0); + input_set_abs_params(input_device, + ABS_HAT1Y, 0, CY_MAXZ, 0, 0); + } + if (ts->platform_data->use_mt) { + input_set_abs_params(input_device, ABS_MT_POSITION_X, + ts->platform_data->disp_minx, + ts->platform_data->disp_maxx, 0, 0); + input_set_abs_params(input_device, ABS_MT_POSITION_Y, + ts->platform_data->disp_miny, + ts->platform_data->disp_maxy, 0, 0); + input_set_abs_params(input_device, + ABS_MT_TOUCH_MAJOR, 0, CY_MAXZ, 0, 0); + input_set_abs_params(input_device, + ABS_MT_WIDTH_MAJOR, 0, CY_LARGE_TOOL_WIDTH, 0, 0); + if (ts->platform_data->use_trk_id) { + input_set_abs_params(input_device, + ABS_MT_TRACKING_ID, 0, CY_NUM_TRK_ID, 0, 0); + } + } + + /* set dummy key to make driver work with virtual keys */ + input_set_capability(input_device, EV_KEY, KEY_PROG1); + + cyttsp_info("%s: Register input device\n", CY_I2C_NAME); + error = input_register_device(input_device); + if (error) { + cyttsp_alert("%s: Failed to register input device\n", \ + CY_I2C_NAME); + retval = error; + goto error_free_device; + } + + /* Prepare our worker structure prior to setting up the timer/ISR */ + INIT_WORK(&ts->work, cyttsp_xy_worker); + + if (gpio_is_valid(ts->platform_data->resout_gpio)) { + /* configure touchscreen reset out gpio */ + retval = gpio_request(ts->platform_data->resout_gpio, + "cyttsp_resout_gpio"); + if (retval) { + pr_err("%s: unable to request reset gpio %d\n", + __func__, ts->platform_data->resout_gpio); + goto error_free_device; + } + + retval = gpio_direction_output( + ts->platform_data->resout_gpio, 1); + if (retval) { + pr_err("%s: unable to set direction for gpio %d\n", + __func__, ts->platform_data->resout_gpio); + goto error_resout_gpio_dir; + } + } + + if (gpio_is_valid(ts->platform_data->sleep_gpio)) { + /* configure touchscreen reset out gpio */ + retval = gpio_request(ts->platform_data->sleep_gpio, + "cy8c_sleep_gpio"); + if (retval) { + pr_err("%s: unable to request sleep gpio %d\n", + __func__, ts->platform_data->sleep_gpio); + goto error_sleep_gpio_req; + } + + retval = gpio_direction_output( + ts->platform_data->sleep_gpio, 0); + if (retval) { + pr_err("%s: unable to set direction for gpio %d\n", + __func__, ts->platform_data->resout_gpio); + goto error_sleep_gpio_dir; + } + } + + if (gpio_is_valid(ts->platform_data->irq_gpio)) { + /* configure touchscreen irq gpio */ + retval = gpio_request(ts->platform_data->irq_gpio, + "ts_irq_gpio"); + if (retval) { + pr_err("%s: unable to request gpio [%d]\n", __func__, + ts->platform_data->irq_gpio); + goto error_irq_gpio_req; + } + retval = gpio_direction_input(ts->platform_data->irq_gpio); + if (retval) { + pr_err("%s: unable to set_direction for gpio [%d]\n", + __func__, ts->platform_data->irq_gpio); + goto error_irq_gpio_dir; + } + } + + if (ts->platform_data->regulator_info) { + retval = cyttsp_power_device(ts, true); + if (retval) { + pr_err("%s: Unable to power device %d\n", + __func__, retval); + goto error_irq_gpio_dir; + } + } + + /* Power on the chip and make sure that I/Os are set as specified + * in the platform */ + if (ts->platform_data->init) { + retval = ts->platform_data->init(client); + if (retval) { + pr_err("%s: ts init failed\n", __func__); + goto error_power_device; + } + } + + msleep(100); + + /* check this device active by reading first byte/register */ + retval = i2c_smbus_read_byte_data(ts->client, 0x01); + if (retval < 0) { + pr_err("%s: i2c sanity check failed\n", __func__); + goto error_power_device; + } + + retval = cyttsp_power_on(ts); + if (retval < 0) { + pr_err("%s: cyttsp_power_on failed\n", __func__); + goto error_power_device; + } + + /* Timer or Interrupt setup */ + if (ts->client->irq == 0) { + cyttsp_info("Setting up timer\n"); + setup_timer(&ts->timer, cyttsp_timer, (unsigned long) ts); + mod_timer(&ts->timer, jiffies + TOUCHSCREEN_TIMEOUT); + } else { + cyttsp_info("Setting up interrupt\n"); + /* request_irq() will also call enable_irq() */ + error = request_irq(client->irq, cyttsp_irq, + IRQF_TRIGGER_FALLING, + client->dev.driver->name, ts); + if (error) { + cyttsp_alert("error: could not request irq\n"); + retval = error; + goto error_power_device; + } + } + + irq_cnt = 0; + irq_cnt_total = 0; + irq_err_cnt = 0; + + atomic_set(&ts->irq_enabled, 1); + retval = device_create_file(&ts->client->dev, &dev_attr_irq_enable); + if (retval < CY_OK) { + cyttsp_alert("File device creation failed: %d\n", retval); + retval = -ENODEV; + goto error_free_irq; + } + + retval = device_create_file(&client->dev, &dev_attr_cyttsp_fw_ver); + if (retval) { + cyttsp_alert("sysfs entry for firmware version failed\n"); + goto error_rm_dev_file_irq_en; + } + + ts->cyttsp_fwloader_mode = 0; + retval = device_create_file(&client->dev, &dev_attr_cyttsp_update_fw); + if (retval) { + cyttsp_alert("sysfs entry for firmware update failed\n"); + goto error_rm_dev_file_fw_ver; + } + + retval = device_create_file(&client->dev, + &dev_attr_cyttsp_force_update_fw); + if (retval) { + cyttsp_alert("sysfs entry for force firmware update failed\n"); + goto error_rm_dev_file_update_fw; + } + if (ts->platform_data->correct_fw_ver) { + if (g_bl_data.appid_lo != ts->platform_data->correct_fw_ver) + printk(KERN_INFO "Please update touchscreen firmware\n"); + } + + retval = device_create_file(&client->dev, + &dev_attr_cyttsp_fw_name); + if (retval) { + cyttsp_alert("sysfs entry for file name selection failed\n"); + goto error_rm_dev_file_fupdate_fw; + } + + cyttsp_info("%s: Successful registration\n", CY_I2C_NAME); + + goto success; + +error_rm_dev_file_fupdate_fw: + device_remove_file(&client->dev, &dev_attr_cyttsp_force_update_fw); +error_rm_dev_file_update_fw: + device_remove_file(&client->dev, &dev_attr_cyttsp_update_fw); +error_rm_dev_file_fw_ver: + device_remove_file(&client->dev, &dev_attr_cyttsp_fw_ver); +error_rm_dev_file_irq_en: + device_remove_file(&client->dev, &dev_attr_irq_enable); +error_free_irq: + if (ts->client->irq) + free_irq(client->irq, ts); +error_power_device: + if (ts->platform_data->regulator_info) + cyttsp_power_device(ts, false); +error_irq_gpio_dir: + if (gpio_is_valid(ts->platform_data->irq_gpio)) + gpio_free(ts->platform_data->irq_gpio); +error_irq_gpio_req: + if (gpio_is_valid(ts->platform_data->sleep_gpio)) + gpio_direction_output(ts->platform_data->sleep_gpio, 1); +error_sleep_gpio_dir: + if (gpio_is_valid(ts->platform_data->sleep_gpio)) + gpio_free(ts->platform_data->sleep_gpio); +error_sleep_gpio_req: + if (gpio_is_valid(ts->platform_data->resout_gpio)) + gpio_direction_output(ts->platform_data->resout_gpio, 0); +error_resout_gpio_dir: + if (gpio_is_valid(ts->platform_data->resout_gpio)) + gpio_free(ts->platform_data->resout_gpio); +error_free_device: + if (input_device) + input_free_device(input_device); + +success: + return retval; +} + +/* I2C driver probe function */ +static int __devinit cyttsp_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cyttsp *ts; + int error; + int retval = CY_OK; + + cyttsp_info("Start Probe 1.2\n"); + + /* allocate and clear memory */ + ts = kzalloc(sizeof(struct cyttsp), GFP_KERNEL); + if (ts == NULL) { + cyttsp_xdebug1("err kzalloc for cyttsp\n"); + retval = -ENOMEM; + } + + /* Enable runtime PM ops, start in ACTIVE mode */ + error = pm_runtime_set_active(&client->dev); + if (error < 0) + dev_dbg(&client->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&client->dev); + + if (!(retval < CY_OK)) { + /* register driver_data */ + ts->client = client; + ts->platform_data = client->dev.platform_data; + + if (ts->platform_data->fw_fname) + strncpy(ts->fw_fname, ts->platform_data->fw_fname, + FW_FNAME_LEN - 1); + else + strncpy(ts->fw_fname, "cyttsp.hex", FW_FNAME_LEN - 1); + + i2c_set_clientdata(client, ts); + + error = cyttsp_initialize(client, ts); + if (error) { + cyttsp_xdebug1("err cyttsp_initialize\n"); + if (ts != NULL) { + /* deallocate memory */ + kfree(ts); + } +/* + i2c_del_driver(&cyttsp_driver); +*/ + retval = -ENODEV; + } else + cyttsp_openlog(); + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + if (!(retval < CY_OK)) { + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ts->early_suspend.suspend = cyttsp_early_suspend; + ts->early_suspend.resume = cyttsp_late_resume; + register_early_suspend(&ts->early_suspend); + } +#endif /* CONFIG_HAS_EARLYSUSPEND */ + device_init_wakeup(&client->dev, ts->platform_data->wakeup); + mutex_init(&ts->mutex); + + cyttsp_info("Start Probe %s\n", \ + (retval < CY_OK) ? "FAIL" : "PASS"); + + return retval; +} + +/* Function to manage power-on resume */ +static int cyttsp_resume(struct device *dev) +{ + struct cyttsp *ts = dev_get_drvdata(dev); + int retval = CY_OK; + + cyttsp_debug("Wake Up\n"); + + if (ts->is_suspended == false) { + pr_err("%s: in wakeup state\n", __func__); + return 0; + } + + if (device_may_wakeup(dev)) { + if (ts->client->irq) + disable_irq_wake(ts->client->irq); + return 0; + } + + /* re-enable the interrupt prior to wake device */ + if (ts->client->irq) + enable_irq(ts->client->irq); + + if (ts->platform_data->use_sleep && + (ts->platform_data->power_state != CY_ACTIVE_STATE)) { + if (ts->platform_data->resume) + retval = ts->platform_data->resume(ts->client); + if (!(retval < CY_OK)) { + /* take TTSP device out of bootloader mode; + * switch back to TrueTouch operational mode */ + if (!(retval < CY_OK)) { + int tries; + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, + sizeof(bl_cmd), bl_cmd); + /* wait for TTSP Device to complete + * switch to Operational mode */ + tries = 0; + do { + mdelay(100); + cyttsp_putbl(ts, 16, false, false, false); + } while (GET_BOOTLOADERMODE(g_bl_data.bl_status) && + tries++ < 100); + cyttsp_putbl(ts, 16, true, false, false); + } + } + } + + if (!(retval < CY_OK) && + (GET_HSTMODE(g_bl_data.bl_file) == CY_OK)) { + ts->platform_data->power_state = CY_ACTIVE_STATE; + + /* re-enable the timer after resuming */ + if (ts->client->irq == 0) + mod_timer(&ts->timer, jiffies + TOUCHSCREEN_TIMEOUT); + } else + retval = -ENODEV; + + ts->is_suspended = false; + cyttsp_debug("Wake Up %s\n", \ + (retval < CY_OK) ? "FAIL" : "PASS"); + + return retval; +} + + +/* Function to manage low power suspend */ +static int cyttsp_suspend(struct device *dev) +{ + struct cyttsp *ts = dev_get_drvdata(dev); + u8 sleep_mode = CY_OK; + int retval = CY_OK; + + cyttsp_debug("Enter Sleep\n"); + + if (ts->is_suspended == true) { + pr_err("%s: in sleep state\n", __func__); + return 0; + } + + mutex_lock(&ts->mutex); + if (ts->cyttsp_fwloader_mode) { + pr_err("%s:firmware upgrade mode:" + "suspend not allowed\n", __func__); + mutex_unlock(&ts->mutex); + return -EBUSY; + } + mutex_unlock(&ts->mutex); + + if (device_may_wakeup(dev)) { + if (ts->client->irq) + enable_irq_wake(ts->client->irq); + return 0; + } + + /* disable worker */ + if (ts->client->irq == 0) + del_timer(&ts->timer); + else + disable_irq_nosync(ts->client->irq); + retval = cancel_work_sync(&ts->work); + + if (retval) + enable_irq(ts->client->irq); + + if (!(retval < CY_OK)) { + if (ts->platform_data->use_sleep && + (ts->platform_data->power_state == CY_ACTIVE_STATE)) { + if (ts->platform_data->use_sleep & CY_USE_DEEP_SLEEP_SEL) + sleep_mode = CY_DEEP_SLEEP_MODE; + else + sleep_mode = CY_LOW_PWR_MODE; + + retval = i2c_smbus_write_i2c_block_data(ts->client, + CY_REG_BASE, + sizeof(sleep_mode), &sleep_mode); + } + } + + if (!(retval < CY_OK)) { + if (sleep_mode == CY_DEEP_SLEEP_MODE) + ts->platform_data->power_state = CY_SLEEP_STATE; + else if (sleep_mode == CY_LOW_PWR_MODE) + ts->platform_data->power_state = CY_LOW_PWR_STATE; + } + + ts->is_suspended = true; + cyttsp_debug("Sleep Power state is %s\n", \ + (ts->platform_data->power_state == CY_ACTIVE_STATE) ? \ + "ACTIVE" : \ + ((ts->platform_data->power_state == CY_SLEEP_STATE) ? \ + "SLEEP" : "LOW POWER")); + + return retval; +} + +/* registered in driver struct */ +static int __devexit cyttsp_remove(struct i2c_client *client) +{ + /* clientdata registered on probe */ + struct cyttsp *ts = i2c_get_clientdata(client); + int err; + + cyttsp_alert("Unregister\n"); + + pm_runtime_set_suspended(&client->dev); + pm_runtime_disable(&client->dev); + + device_init_wakeup(&client->dev, 0); + device_remove_file(&ts->client->dev, &dev_attr_irq_enable); + device_remove_file(&client->dev, &dev_attr_cyttsp_fw_ver); + device_remove_file(&client->dev, &dev_attr_cyttsp_update_fw); + device_remove_file(&client->dev, &dev_attr_cyttsp_force_update_fw); + device_remove_file(&client->dev, &dev_attr_cyttsp_fw_name); + + /* Start cleaning up by removing any delayed work and the timer */ + if (cancel_delayed_work((struct delayed_work *)&ts->work) < CY_OK) + cyttsp_alert("error: could not remove work from workqueue\n"); + + /* free up timer or irq */ + if (ts->client->irq == 0) { + err = del_timer(&ts->timer); + if (err < CY_OK) + cyttsp_alert("error: failed to delete timer\n"); + } else + free_irq(client->irq, ts); + + if (ts->platform_data->regulator_info) + cyttsp_power_device(ts, false); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&ts->early_suspend); +#endif /* CONFIG_HAS_EARLYSUSPEND */ + + mutex_destroy(&ts->mutex); + + if (gpio_is_valid(ts->platform_data->sleep_gpio)) { + gpio_direction_output(ts->platform_data->sleep_gpio, 1); + gpio_free(ts->platform_data->sleep_gpio); + } + + if (gpio_is_valid(ts->platform_data->resout_gpio)) { + gpio_direction_output(ts->platform_data->resout_gpio, 0); + gpio_free(ts->platform_data->resout_gpio); + } + + if (gpio_is_valid(ts->platform_data->irq_gpio)) + gpio_free(ts->platform_data->irq_gpio); + + /* housekeeping */ + if (ts != NULL) + kfree(ts); + + cyttsp_alert("Leaving\n"); + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void cyttsp_early_suspend(struct early_suspend *handler) +{ + struct cyttsp *ts; + + ts = container_of(handler, struct cyttsp, early_suspend); + cyttsp_suspend(&ts->client->dev); +} + +static void cyttsp_late_resume(struct early_suspend *handler) +{ + struct cyttsp *ts; + + ts = container_of(handler, struct cyttsp, early_suspend); + cyttsp_resume(&ts->client->dev); +} +#endif /* CONFIG_HAS_EARLYSUSPEND */ + +static int cyttsp_init(void) +{ + int ret; + + cyttsp_info("Cypress TrueTouch(R) Standard Product\n"); + cyttsp_info("I2C Touchscreen Driver (Built %s @ %s)\n", \ + __DATE__, __TIME__); + + cyttsp_ts_wq = create_singlethread_workqueue("cyttsp_ts_wq"); + if (cyttsp_ts_wq == NULL) { + cyttsp_debug("No memory for cyttsp_ts_wq\n"); + return -ENOMEM; + } + + ret = i2c_add_driver(&cyttsp_driver); + + return ret; +} + +static void cyttsp_exit(void) +{ + if (cyttsp_ts_wq) + destroy_workqueue(cyttsp_ts_wq); + return i2c_del_driver(&cyttsp_driver); +} + +module_init(cyttsp_init); +module_exit(cyttsp_exit); +MODULE_FIRMWARE("cyttsp.fw"); + diff --git a/drivers/input/touchscreen/cyttsp_fw.h b/drivers/input/touchscreen/cyttsp_fw.h new file mode 100755 index 00000000000..f14153e0dec --- /dev/null +++ b/drivers/input/touchscreen/cyttsp_fw.h @@ -0,0 +1,4307 @@ +/* Header file for: + * Cypress TrueTouch(TM) Standard Product touchscreen drivers. + * drivers/input/touchscreen/cyttsp_fw.h + * + * Copyright (C) 2009, 2010 Cypress Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Cypress reserves the right to make changes without further notice + * to the materials described herein. Cypress does not assume any + * liability arising out of the application described herein. + * + * Contact Cypress Semiconductor at www.cypress.com + * + */ + +#define CYTTSP_BL_OST_LEN 1 +#define CYTTSP_BL_CMD_LEN 2 +#define CYTTSP_BL_KEY_LEN 8 +#define CYTTSP_LD_ADR_LEN 2 +#define CYTTSP_LD_DAT_LEN 64 +#define CYTTSP_LD_CHK_LEN 2 +#define CYTTSP_LD_BLK_LEN (CYTTSP_BL_OST_LEN + CYTTSP_BL_CMD_LEN + CYTTSP_BL_KEY_LEN + \ + CYTTSP_LD_ADR_LEN + CYTTSP_LD_DAT_LEN + CYTTSP_LD_CHK_LEN) + +typedef struct cyttsp_ld_blk_ray_t { + unsigned short Record; + unsigned short Length; + unsigned char Command; + unsigned short Address; + unsigned char Block[CYTTSP_LD_BLK_LEN]; +} cyttsp_ld_blk_ray, *pcyttsp_ld_blk_ray; + +cyttsp_ld_blk_ray cyttsp_fw[] = { + { + 0, + 11, + 0x38, + -1, + { + 0x00, 0xFF, 0x38, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + } + }, + { + 1, + 79, + 0x39, + 0x002C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x2C, 0x40, 0x7D, 0x0B, 0x68, 0x30, 0x30, 0x30, 0x30, 0x7E, 0x30, 0x30, 0x30, 0x7D, 0x10, 0x12, 0x7E, 0x7D, 0x10, 0x36, 0x7E, 0x7E, 0x30, 0x30, 0x30, 0x7E, 0x30, 0x30, 0x30, 0x7D, 0x1F, 0x2A, 0x7E, 0x7E, 0x30, 0x30, 0x30, 0x7E, 0x30, 0x30, 0x30, 0x7E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x7E, 0x30, 0x30, 0x30, 0x7E, 0x30, 0x30, 0x30, 0x7D, 0x20, 0x70, 0x7E, 0x7E, 0x30, 0x30, 0x30, 0x5B, 0x36 + } + }, + { + 2, + 79, + 0x39, + 0x002D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x2D, 0x7E, 0x30, 0x30, 0x30, 0x7E, 0x30, 0x30, 0x30, 0x7E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x40, 0x43, 0xE6, 0x02, 0x40, 0x70, 0xCF, 0x71, 0x10, 0x62, 0xE3, 0x02, 0x70, 0xCF, 0x41, 0xFF, 0xEF, 0x50, 0x80, 0x4E, 0x5D, 0xD5, 0x08, 0x62, 0x44, 0x09 + } + }, + { + 3, + 79, + 0x39, + 0x002E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x2E, 0xD5, 0x00, 0x55, 0xFA, 0x01, 0x40, 0x50, 0x06, 0x55, 0xF8, 0x3A, 0x7C, 0x00, 0x60, 0x40, 0x40, 0x70, 0xCF, 0x71, 0x10, 0x51, 0xFA, 0x60, 0xE8, 0x70, 0xCF, 0x18, 0x60, 0xD5, 0x55, 0xF8, 0x00, 0x55, 0xF9, 0x00, 0x70, 0xCF, 0x71, 0x10, 0x41, 0x9F, 0xFE, 0x70, 0xCF, 0x62, 0xE3, 0x38, 0x62, 0xD1, 0x0F, 0x50, 0x00, 0x4E, 0x62, 0xD3, 0x0F, 0x62, 0xD0, 0x00, 0x62, 0xD5, 0x00, 0x62, 0xD4, 0x35, 0xEC + } + }, + { + 4, + 79, + 0x39, + 0x002F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x2F, 0x00, 0x71, 0xC0, 0x7C, 0x0F, 0x76, 0x62, 0xD0, 0x00, 0x50, 0x0F, 0x57, 0x74, 0x08, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x28, 0x4B, 0x51, 0xE9, 0x80, 0x04, 0x75, 0x09, 0x00, 0x62, 0xE3, 0x00, 0x08, 0x28, 0x60, 0xD5, 0x74, 0xA0, 0x4B, 0x18, 0x75, 0x09, 0x00, 0x08, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x08, 0x28, 0xA0, 0x1C, 0x53, 0xE8, 0x18, 0x75, 0x09, 0x00, 0x08, 0x28, 0xCD, 0x1D + } + }, + { + 5, + 79, + 0x39, + 0x0030, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x30, 0x3F, 0xE9, 0x47, 0xE9, 0xFF, 0xB0, 0x06, 0x5D, 0xD5, 0x74, 0x60, 0xD5, 0x18, 0x7A, 0xE8, 0xBF, 0xEB, 0x8F, 0xC9, 0x18, 0x75, 0x09, 0x00, 0x08, 0x28, 0x53, 0xE8, 0x50, 0x00, 0x3F, 0xE9, 0x47, 0xE9, 0xFF, 0xB0, 0x08, 0x5D, 0xD5, 0x74, 0x60, 0xD5, 0x50, 0x00, 0x7A, 0xE8, 0xBF, 0xEF, 0x18, 0x8F, 0xAA, 0x18, 0x70, 0xCF, 0x71, 0x10, 0x62, 0xEC, 0x10, 0x43, 0xE3, 0x00, 0x70, 0xCF, 0x62, 0x4D, 0x1E + } + }, + { + 6, + 79, + 0x39, + 0x0031, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x31, 0xE0, 0x00, 0x41, 0xFE, 0xE7, 0x43, 0xFE, 0x10, 0x70, 0xCF, 0x71, 0x10, 0x62, 0xE0, 0x53, 0x70, 0xCF, 0x62, 0xE2, 0x00, 0x7C, 0x3E, 0xD3, 0x8F, 0xFF, 0x7F, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xE9, 0x57 + } + }, + { + 7, + 79, + 0x39, + 0x0032, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x32, 0x5D, 0x04, 0x73, 0x21, 0xA0, 0xBF, 0xFA, 0x5D, 0x04, 0x73, 0x21, 0xA0, 0xBF, 0xF3, 0x5D, 0x04, 0x73, 0x21, 0xA0, 0xBF, 0xEC, 0x50, 0x18, 0x49, 0x04, 0x20, 0xAF, 0xE5, 0x60, 0xFF, 0x49, 0xC9, 0x01, 0xB0, 0x1A, 0x41, 0xD6, 0xFE, 0x70, 0xCF, 0x71, 0x10, 0x41, 0x04, 0x5F, 0x70, 0xCF, 0x43, 0xD6, 0x01, 0x40, 0x70, 0xCF, 0x71, 0x10, 0x43, 0x04, 0xA0, 0x70, 0xCF, 0x7F, 0x30, 0x30, 0x30, 0x81, 0x88 + } + }, + { + 8, + 79, + 0x39, + 0x0033, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x87 + } + }, + { + 9, + 79, + 0x39, + 0x0034, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x34, 0x0A, 0x20, 0x20, 0x51, 0x55, 0x41, 0x4C, 0x43, 0x4F, 0x4D, 0x4D, 0x20, 0x56, 0x50, 0x30, 0x34, 0x33, 0x2D, 0x48, 0x32, 0x20, 0x54, 0x4D, 0x41, 0x33, 0x30, 0x30, 0x45, 0x20, 0x46, 0x69, 0x72, 0x6D, 0x77, 0x61, 0x72, 0x65, 0x20, 0x49, 0x64, 0x65, 0x6E, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x56, 0x99, 0xBA + } + }, + { + 10, + 79, + 0x39, + 0x0035, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x35, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x30, 0x32, 0x2E, 0x30, 0x34, 0x2E, 0x30, 0x30, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x69, 0x6C, 0x65, 0x64, 0x20, 0x4A, 0x75, 0x6C, 0x20, 0x31, 0x34, 0x20, 0x32, 0x30, 0x31, 0x30, 0x20, 0x31, 0x32, 0x3A, 0x35, 0x33, 0x3A, 0x31, 0x33, 0x0A, 0x20, 0x20, 0x45, 0x6E, 0x64, 0x20, 0x6F, 0x66, 0x20, 0x49, 0x44, 0x20, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x0A, 0x0D, 0xA3 + } + }, + { + 11, + 79, + 0x39, + 0x0036, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x36, 0x00, 0x03, 0x09, 0x10, 0x16, 0x06, 0x02, 0x02, 0x02, 0x01, 0xF4, 0x00, 0x0A, 0x01, 0xF4, 0x00, 0x0A, 0x01, 0xF4, 0x00, 0x0A, 0x14, 0x19, 0x19, 0x00, 0x32, 0x02, 0x14, 0x01, 0x01, 0xE0, 0x03, 0x98, 0x0C, 0x0C, 0x00, 0x10, 0x10, 0x08, 0x00, 0x04, 0x08, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x00, 0x04, 0x08, 0x00, 0x00, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x01, 0x80, 0x10, 0x01, 0x80, 0x50, 0x2A + } + }, + { + 12, + 79, + 0x39, + 0x0037, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x37, 0x01, 0x40, 0x04, 0x02, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x02, 0x40, 0x08, 0x80, 0x20, 0x80, 0x08, 0x04, 0x02, 0x40, 0x20, 0x23, 0x04, 0x21, 0x20, 0x22, 0x00, 0x61, 0x00, 0xFD, 0x00, 0xA0, 0x00, 0xA1, 0x00, 0xA2, 0x00, 0xA3, 0x00, 0xA8, 0x00, 0xA7, 0x00, 0x7C, 0x00, 0x7A, 0x00, 0x7B, 0x00, 0x79, 0x00, 0xCA, 0x24, 0xD6, 0x04, 0xCF, 0x00, 0xC8, 0x00, 0xA9, 0x00, 0xB7, 0x00, 0xB0, 0xB3, 0xF1 + } + }, + { + 13, + 79, + 0x39, + 0x0038, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x38, 0xCA, 0xB1, 0x0B, 0xB2, 0x00, 0xB3, 0x33, 0xB4, 0x33, 0xB5, 0x80, 0xB6, 0x00, 0x6C, 0x00, 0x6D, 0x00, 0x6E, 0x00, 0x6F, 0x00, 0xE6, 0x00, 0xE9, 0x00, 0xEC, 0x00, 0xE8, 0x20, 0xEB, 0x00, 0xEE, 0x00, 0xE7, 0x00, 0xEA, 0x00, 0xED, 0x00, 0xFF, 0x23, 0x00, 0x20, 0x20, 0x21, 0x07, 0x22, 0x40, 0x76, 0x00, 0xAF, 0x00, 0xD1, 0x00, 0xA1, 0x00, 0xD3, 0x00, 0xA3, 0x00, 0xD0, 0x00, 0xA0, 0x00, 0x69, 0x5E + } + }, + { + 14, + 79, + 0x39, + 0x0039, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x39, 0xD2, 0x00, 0xA2, 0x00, 0xDC, 0x08, 0xE1, 0xFF, 0xE2, 0x01, 0xDF, 0xFF, 0xDE, 0x02, 0xDD, 0x00, 0x99, 0x00, 0x9C, 0x00, 0xD8, 0x00, 0xD9, 0x00, 0xDA, 0x00, 0xDB, 0x00, 0x9E, 0x00, 0xAC, 0x00, 0xFF, 0x70, 0xCF, 0x62, 0x00, 0x04, 0x70, 0xCF, 0x71, 0x10, 0x62, 0x00, 0xFF, 0x62, 0x01, 0xF6, 0x70, 0xCF, 0x62, 0x02, 0x00, 0x62, 0x01, 0x00, 0x62, 0x04, 0xAB, 0x70, 0xCF, 0x71, 0x10, 0x62, 0xF2, 0x71 + } + }, + { + 15, + 79, + 0x39, + 0x003A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x3A, 0x04, 0xEF, 0x62, 0x05, 0xFC, 0x70, 0xCF, 0x62, 0x06, 0x00, 0x62, 0x05, 0x00, 0x62, 0x08, 0x04, 0x70, 0xCF, 0x71, 0x10, 0x62, 0x08, 0xFF, 0x62, 0x09, 0x8F, 0x70, 0xCF, 0x62, 0x0A, 0x00, 0x62, 0x09, 0x00, 0x62, 0x0C, 0x00, 0x70, 0xCF, 0x71, 0x10, 0x62, 0x0C, 0xFF, 0x62, 0x0D, 0xFF, 0x70, 0xCF, 0x62, 0x0E, 0x00, 0x62, 0x0D, 0x00, 0x62, 0x10, 0x00, 0x70, 0xCF, 0x71, 0x10, 0x62, 0x10, 0xD6, 0x3A + } + }, + { + 16, + 79, + 0x39, + 0x003B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x3B, 0xFF, 0x62, 0x11, 0xEF, 0x70, 0xCF, 0x62, 0x12, 0x00, 0x62, 0x11, 0x00, 0x70, 0xCF, 0x7F, 0xC1, 0x00, 0xC2, 0x00, 0xC3, 0x00, 0x80, 0x00, 0x81, 0x00, 0x82, 0x00, 0x83, 0x00, 0xA8, 0x00, 0xA9, 0x00, 0xAA, 0x00, 0x84, 0x00, 0x85, 0x00, 0x86, 0x00, 0x87, 0x00, 0xAB, 0x00, 0xAC, 0x00, 0xAD, 0x00, 0x88, 0x00, 0x89, 0x00, 0x8A, 0x00, 0x8B, 0x00, 0xAE, 0x00, 0xAF, 0x00, 0xB0, 0x00, 0x8C, 0x65, 0x59 + } + }, + { + 17, + 79, + 0x39, + 0x003C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x3C, 0x00, 0x8D, 0x00, 0x8E, 0x00, 0x8F, 0x00, 0xB1, 0x00, 0xB2, 0x00, 0xB3, 0x00, 0x90, 0x00, 0x91, 0x00, 0x92, 0x00, 0x93, 0x00, 0xB4, 0x00, 0xB5, 0x00, 0xB6, 0x00, 0x94, 0x00, 0x95, 0x00, 0x96, 0x00, 0x97, 0x00, 0xB7, 0x00, 0xB8, 0x00, 0xB9, 0x00, 0x98, 0x00, 0x99, 0x00, 0x9A, 0x00, 0x9B, 0x00, 0xBA, 0x00, 0xBB, 0x00, 0xBC, 0x00, 0x9C, 0x00, 0x9D, 0x00, 0x9E, 0x00, 0x9F, 0x00, 0xBD, 0x6D, 0x6A + } + }, + { + 18, + 79, + 0x39, + 0x003D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x3D, 0x00, 0xBE, 0x00, 0xBF, 0x00, 0xA4, 0x00, 0xC0, 0x00, 0xFF, 0x11, 0x06, 0x12, 0x02, 0x13, 0x87, 0x14, 0x03, 0x1B, 0x30, 0x1C, 0x00, 0x19, 0x24, 0x1A, 0x30, 0x0A, 0x3C, 0x0B, 0x3C, 0xFF, 0x01, 0x02, 0x06, 0x00, 0x01, 0x02, 0x01, 0x00, 0x02, 0x00, 0x02, 0x01, 0x02, 0x00, 0x01, 0x01, 0x02, 0x00, 0x02, 0x01, 0x00, 0x73, 0x97, 0x55, 0xAE, 0x04, 0x55, 0xAF, 0xAB, 0x55, 0xB0, 0x04, 0x55, 0x6F, 0x6F + } + }, + { + 19, + 79, + 0x39, + 0x003E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x3E, 0xB1, 0x00, 0x55, 0xB2, 0x00, 0x7C, 0x0F, 0x8C, 0x7C, 0x0E, 0x61, 0x7F, 0x10, 0x70, 0xCF, 0x50, 0x00, 0x08, 0x50, 0x0D, 0x57, 0xD5, 0x7C, 0x0F, 0xBF, 0x18, 0x50, 0x01, 0x08, 0x50, 0x0E, 0x57, 0x28, 0x7C, 0x0F, 0xBF, 0x18, 0x50, 0x02, 0x08, 0x50, 0x0E, 0x57, 0xCF, 0x7C, 0x0F, 0xBF, 0x18, 0x50, 0x03, 0x08, 0x50, 0x0F, 0x57, 0x4A, 0x7C, 0x0F, 0xBF, 0x18, 0x70, 0xCF, 0x20, 0x7F, 0x38, 0x76, 0x7E + } + }, + { + 20, + 79, + 0x39, + 0x003F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x3F, 0x02, 0x10, 0x08, 0x4F, 0x52, 0xF9, 0x64, 0x08, 0x64, 0x03, 0x00, 0x54, 0xFC, 0x18, 0x18, 0x20, 0x70, 0xCF, 0x62, 0xE3, 0x00, 0x10, 0x08, 0x28, 0x39, 0xFF, 0xA0, 0x30, 0x4F, 0x54, 0xFD, 0x52, 0xFC, 0x39, 0x00, 0xA0, 0x13, 0x11, 0x06, 0xE0, 0x01, 0x70, 0xCF, 0x71, 0x10, 0x80, 0x09, 0x70, 0xCF, 0x71, 0x20, 0x80, 0x03, 0x71, 0x30, 0x18, 0x20, 0x75, 0x09, 0x00, 0x10, 0x08, 0x28, 0x4F, 0x47, 0x21 + } + }, + { + 21, + 79, + 0x39, + 0x0040, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x40, 0x59, 0xFD, 0x61, 0x00, 0x18, 0x20, 0x75, 0x09, 0x00, 0x8F, 0xC6, 0x38, 0xFC, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x08, 0x10, 0x5D, 0xD0, 0x08, 0x5D, 0xD3, 0x08, 0x5D, 0xD4, 0x08, 0x5D, 0xD5, 0x08, 0x70, 0x3F, 0x71, 0x80, 0x62, 0xD0, 0x00, 0x18, 0x60, 0xD5, 0x18, 0x60, 0xD4, 0x18, 0x60, 0xD3, 0x18, 0x60, 0xD0, 0x20, 0x18, 0x7E, 0x08, 0x51, 0x54, 0x04, 0x01, 0x51, 0x53, 0x0C, 0x00, 0x51, 0xB4, 0xFC + } + }, + { + 22, + 79, + 0x39, + 0x0041, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x41, 0x54, 0x04, 0x03, 0x51, 0x53, 0x0C, 0x02, 0x51, 0x54, 0x04, 0x05, 0x51, 0x53, 0x0C, 0x04, 0x51, 0x54, 0x04, 0x07, 0x51, 0x53, 0x0C, 0x06, 0x18, 0x08, 0x51, 0x0B, 0x04, 0x0D, 0x51, 0x0A, 0x0C, 0x0C, 0x41, 0x23, 0xFE, 0x55, 0xBB, 0x00, 0x51, 0x55, 0x60, 0x21, 0x62, 0xDB, 0xFE, 0x43, 0x23, 0x01, 0x18, 0x7E, 0x05, 0x62, 0xD0, 0x00, 0x51, 0x42, 0x08, 0x26, 0x42, 0xEF, 0x7C, 0x19, 0x73, 0xD7, 0x43 + } + }, + { + 23, + 79, + 0x39, + 0x0042, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x42, 0x7C, 0x19, 0x73, 0x62, 0xD0, 0x00, 0x18, 0x53, 0x42, 0x70, 0xBF, 0x57, 0x98, 0x62, 0xD3, 0x05, 0x52, 0x00, 0x73, 0x54, 0x00, 0x62, 0xD3, 0x05, 0x54, 0x00, 0x79, 0xDF, 0xF1, 0x7C, 0x19, 0x64, 0x7C, 0x19, 0x64, 0x70, 0xBF, 0x57, 0x98, 0x62, 0xD3, 0x05, 0x52, 0x00, 0x62, 0xD3, 0x08, 0x54, 0x00, 0x62, 0xD3, 0x07, 0x56, 0x00, 0x00, 0x79, 0xDF, 0xEE, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x7F, 0x12, 0xBA + } + }, + { + 24, + 79, + 0x39, + 0x0043, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x43, 0x5C, 0x51, 0x41, 0xE0, 0x01, 0x80, 0x13, 0x80, 0x08, 0x80, 0x01, 0x5B, 0x9F, 0xF1, 0x80, 0x77, 0x62, 0xD3, 0x05, 0x51, 0x57, 0x54, 0x00, 0x80, 0x6E, 0x62, 0xD3, 0x05, 0x51, 0x57, 0x73, 0x53, 0x46, 0x47, 0x42, 0x07, 0xB0, 0x05, 0x54, 0x00, 0x80, 0x15, 0x47, 0x42, 0x04, 0xA0, 0x10, 0x62, 0xD3, 0x05, 0x3B, 0x00, 0xA0, 0x09, 0xC0, 0x04, 0x78, 0x80, 0x02, 0x74, 0x54, 0x00, 0x62, 0xD3, 0xA3, 0xDD + } + }, + { + 25, + 79, + 0x39, + 0x0044, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x44, 0x08, 0x13, 0x00, 0xD0, 0x0E, 0x62, 0xD3, 0x02, 0x56, 0x00, 0x00, 0x3C, 0x0E, 0x00, 0xB0, 0x37, 0x80, 0x16, 0x62, 0xD3, 0x02, 0x08, 0x11, 0x05, 0xD0, 0x03, 0x50, 0x00, 0x54, 0x00, 0x18, 0x3A, 0x15, 0xD0, 0x24, 0x51, 0x0E, 0xB0, 0x20, 0x62, 0xD3, 0x08, 0x52, 0x00, 0x53, 0x45, 0x51, 0x47, 0x12, 0x45, 0x1E, 0x45, 0x00, 0x62, 0xD3, 0x07, 0x03, 0x00, 0x0E, 0x45, 0x00, 0x54, 0x00, 0x51, 0x53, 0x3E + } + }, + { + 26, + 79, + 0x39, + 0x0045, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x45, 0x45, 0x62, 0xD3, 0x08, 0x54, 0x00, 0x7F, 0x7C, 0x21, 0xD2, 0x08, 0x18, 0x7F, 0x50, 0xFF, 0x3C, 0x10, 0x80, 0xC0, 0x11, 0x34, 0x12, 0x76, 0x12, 0x34, 0x11, 0x0E, 0x11, 0x00, 0x34, 0x10, 0x0E, 0x10, 0x00, 0x53, 0x0F, 0x50, 0x00, 0x53, 0x48, 0x53, 0x49, 0x53, 0x4A, 0x53, 0x4B, 0x55, 0x46, 0x18, 0x65, 0x12, 0x6B, 0x11, 0x6B, 0x10, 0x6B, 0x4B, 0x6B, 0x4A, 0x6B, 0x49, 0x51, 0x4B, 0x1A, 0xFD, 0x93 + } + }, + { + 27, + 79, + 0x39, + 0x0046, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x46, 0x14, 0x51, 0x4A, 0x1A, 0x13, 0x51, 0x49, 0x19, 0x00, 0xC0, 0x0D, 0x53, 0x49, 0x51, 0x14, 0x14, 0x4B, 0x51, 0x13, 0x1C, 0x4A, 0x76, 0x12, 0x7A, 0x46, 0xBF, 0xD7, 0x50, 0xFF, 0x3C, 0x0F, 0x80, 0xC0, 0x11, 0x34, 0x12, 0x76, 0x12, 0x34, 0x11, 0x0E, 0x11, 0x00, 0x34, 0x10, 0x0E, 0x10, 0x00, 0x34, 0x0F, 0x7F, 0x50, 0x00, 0x53, 0x48, 0x53, 0x49, 0x53, 0x4A, 0x53, 0x4B, 0x51, 0x12, 0x04, 0xCE, 0x36 + } + }, + { + 28, + 79, + 0x39, + 0x0047, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x47, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x90, 0x54, 0x90, 0x52, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x90, 0x48, 0x90, 0x46, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x90, 0x3C, 0x90, 0x3A, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x90, 0x30, 0x90, 0x2E, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x90, 0x24, 0x90, 0x22, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0xA3, 0xE1 + } + }, + { + 29, + 79, + 0x39, + 0x0048, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x48, 0x48, 0x90, 0x18, 0x90, 0x16, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x90, 0x0C, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x90, 0x02, 0x90, 0x00, 0x70, 0xFB, 0x6E, 0x48, 0x6E, 0x49, 0x6E, 0x4A, 0x6E, 0x4B, 0x7F, 0x50, 0x00, 0x53, 0x48, 0x53, 0x49, 0x53, 0x4A, 0x53, 0x4B, 0x9F, 0xE9, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x9F, 0xDF, 0x51, 0x12, 0x04, 0x49, 0xC6, 0x28 + } + }, + { + 30, + 79, + 0x39, + 0x0049, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x9F, 0xD5, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x9F, 0xCB, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x9F, 0xC1, 0x9F, 0xBF, 0x9F, 0xBD, 0x9F, 0xBB, 0x9F, 0xB9, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x9F, 0xAF, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x9F, 0xA5, 0x9F, 0xA3, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x32, 0x01 + } + }, + { + 31, + 79, + 0x39, + 0x004A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x4A, 0x9F, 0x99, 0x51, 0x12, 0x04, 0x49, 0x51, 0x11, 0x0C, 0x48, 0x9F, 0x8F, 0x9F, 0x8D, 0x8F, 0x8C, 0x70, 0xBF, 0x62, 0xD0, 0x00, 0x53, 0x44, 0x55, 0x0F, 0x80, 0x55, 0x10, 0x60, 0x55, 0x11, 0x00, 0x62, 0xD3, 0x02, 0x50, 0x10, 0x57, 0x98, 0x54, 0x00, 0x79, 0xDF, 0xFC, 0x62, 0xD3, 0x01, 0x51, 0x0F, 0x57, 0x1A, 0x54, 0xA0, 0x79, 0xDF, 0xFC, 0x55, 0x3D, 0x00, 0x7C, 0x17, 0x18, 0x55, 0x45, 0x6E, 0x7A + } + }, + { + 32, + 79, + 0x39, + 0x004B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x4B, 0x00, 0x62, 0xD3, 0x01, 0x58, 0xA1, 0x51, 0x10, 0x54, 0xC5, 0x62, 0xD3, 0x01, 0x58, 0xA1, 0x51, 0x0F, 0x54, 0xA0, 0x55, 0x4A, 0x80, 0x52, 0xC5, 0x70, 0xCF, 0x71, 0x20, 0x60, 0xA5, 0x70, 0xCF, 0x62, 0xD3, 0x01, 0x58, 0xA1, 0x52, 0xA0, 0x60, 0xFD, 0x55, 0x4B, 0x10, 0x7C, 0x1B, 0x87, 0x51, 0xA0, 0x01, 0x00, 0x5C, 0x62, 0xD3, 0x02, 0x51, 0x45, 0x7C, 0x19, 0x8F, 0x43, 0xA4, 0x08, 0x47, 0x37, 0x0D + } + }, + { + 33, + 79, + 0x39, + 0x004C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x4C, 0x9F, 0x01, 0xA0, 0x03, 0x71, 0x01, 0x70, 0xCF, 0x7C, 0x1C, 0x8E, 0x7C, 0x1C, 0xE3, 0x55, 0x56, 0x00, 0x55, 0x57, 0xFF, 0x55, 0x48, 0x07, 0x62, 0xD3, 0x00, 0x58, 0x48, 0x3D, 0x70, 0x00, 0xA0, 0x2A, 0x52, 0x68, 0x08, 0x51, 0x48, 0x64, 0x5C, 0x52, 0x59, 0x20, 0x62, 0xD3, 0x02, 0x3A, 0x44, 0xD0, 0x06, 0x51, 0x4B, 0x73, 0x25, 0x00, 0x51, 0x4B, 0x67, 0x2D, 0x00, 0x52, 0x00, 0x3A, 0x56, 0x92, 0xC4 + } + }, + { + 34, + 79, + 0x39, + 0x004D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x4D, 0xC0, 0x03, 0x53, 0x56, 0x3A, 0x57, 0xD0, 0x03, 0x53, 0x57, 0x7A, 0x48, 0xDF, 0xCA, 0x68, 0x4B, 0xDF, 0x9B, 0x51, 0x4A, 0xA0, 0x42, 0x47, 0x11, 0x01, 0xB0, 0x3D, 0x58, 0xA1, 0x62, 0xD3, 0x01, 0x51, 0x57, 0x02, 0x56, 0x39, 0x1F, 0xA0, 0x30, 0xD0, 0x06, 0x51, 0x4A, 0x73, 0x25, 0xA0, 0x51, 0x4A, 0x67, 0x21, 0x7F, 0x2D, 0xA0, 0x68, 0x4A, 0x26, 0x4A, 0x7F, 0x55, 0x48, 0x07, 0x62, 0xD3, 0xBE, 0x1D + } + }, + { + 35, + 79, + 0x39, + 0x004E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x4E, 0x00, 0x58, 0x48, 0x3D, 0x70, 0x00, 0xA0, 0x0A, 0x52, 0x68, 0x5C, 0x62, 0xD3, 0x02, 0x56, 0x00, 0x10, 0x7A, 0x48, 0xDF, 0xEA, 0x8F, 0x4A, 0x47, 0x11, 0x02, 0xB0, 0x32, 0x3C, 0x56, 0x1F, 0xC0, 0x2D, 0x62, 0xD3, 0x01, 0x58, 0xA1, 0x3D, 0xC5, 0x40, 0xA0, 0x23, 0x17, 0xC5, 0x20, 0x55, 0x48, 0x07, 0x62, 0xD3, 0x00, 0x62, 0xD3, 0x00, 0x58, 0x48, 0x3D, 0x70, 0x00, 0xA0, 0x0A, 0x52, 0x68, 0xD7, 0x50 + } + }, + { + 36, + 79, + 0x39, + 0x004F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x4F, 0x5C, 0x62, 0xD3, 0x02, 0x56, 0x00, 0x10, 0x7A, 0x48, 0xDF, 0xEA, 0x8E, 0xFE, 0x76, 0xA1, 0x51, 0x45, 0x7C, 0x1A, 0xE9, 0x76, 0x45, 0x3C, 0x45, 0x03, 0xCE, 0xE7, 0x7C, 0x17, 0x55, 0x76, 0x3D, 0x3C, 0x3D, 0x09, 0xCE, 0xD7, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x70, 0xBF, 0x62, 0xD0, 0x00, 0x55, 0x44, 0x99, 0x53, 0x0F, 0x5A, 0x10, 0x55, 0x11, 0x03, 0x8E, 0xA6, 0x70, 0xBF, 0x62, 0xD0, 0x00, 0xAE, 0xFF + } + }, + { + 37, + 79, + 0x39, + 0x0050, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x50, 0x55, 0x44, 0x99, 0x53, 0x0F, 0x55, 0x10, 0x60, 0x55, 0x11, 0x01, 0x8E, 0x94, 0x70, 0xBF, 0x62, 0xD0, 0x00, 0x55, 0x44, 0x99, 0x55, 0x0F, 0x80, 0x53, 0x10, 0x55, 0x11, 0x02, 0x8E, 0x82, 0x90, 0x11, 0x55, 0x38, 0x03, 0x51, 0x38, 0x90, 0x1A, 0x76, 0x38, 0x3C, 0x38, 0x0A, 0xCF, 0xF6, 0x90, 0x66, 0x7F, 0x62, 0xD5, 0x02, 0x62, 0xD0, 0x00, 0x55, 0x34, 0x99, 0x55, 0x0E, 0x00, 0x55, 0x33, 0x76, 0x90 + } + }, + { + 38, + 79, + 0x39, + 0x0051, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x51, 0x01, 0x7F, 0x70, 0xBF, 0x62, 0xD3, 0x02, 0x62, 0xD5, 0x02, 0x62, 0xD0, 0x00, 0x11, 0x02, 0x53, 0x45, 0x51, 0x33, 0x02, 0x4C, 0x53, 0x33, 0x53, 0x32, 0x55, 0x44, 0x01, 0x58, 0x32, 0x52, 0x00, 0x3A, 0x16, 0xC0, 0x24, 0x3B, 0x12, 0xC0, 0x20, 0x3B, 0x11, 0xC0, 0x1C, 0x3B, 0x10, 0xC0, 0x18, 0x3B, 0x01, 0xC0, 0x14, 0x78, 0x3B, 0xFF, 0xC0, 0x0F, 0x3B, 0xF0, 0xC0, 0x0B, 0x3B, 0xEF, 0xC0, 0x6C, 0x7D + } + }, + { + 39, + 79, + 0x39, + 0x0052, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x52, 0x07, 0x3B, 0xEE, 0xC0, 0x03, 0x91, 0x84, 0x76, 0x32, 0x76, 0x44, 0x51, 0x4C, 0x78, 0x3A, 0x44, 0xBF, 0xCB, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x70, 0xBF, 0x62, 0xD3, 0x02, 0x62, 0xD5, 0x02, 0x62, 0xD0, 0x00, 0x55, 0x32, 0x01, 0x55, 0x44, 0x01, 0x55, 0x45, 0x00, 0x58, 0x32, 0x52, 0x00, 0x3A, 0x16, 0xC0, 0x18, 0x3B, 0x12, 0xC0, 0x14, 0x3B, 0x11, 0xC0, 0x10, 0x3B, 0x10, 0xC0, 0x0C, 0x3B, 0x06, 0xB2 + } + }, + { + 40, + 79, + 0x39, + 0x0053, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x53, 0x01, 0xC0, 0x08, 0x78, 0x3B, 0xFF, 0xC0, 0x03, 0x91, 0x41, 0x76, 0x32, 0x76, 0x44, 0x51, 0x4C, 0x78, 0x3A, 0x44, 0xBF, 0xD7, 0x55, 0x44, 0x01, 0x51, 0x4D, 0x78, 0x53, 0x45, 0x51, 0x4E, 0x12, 0x4C, 0x74, 0x74, 0x53, 0x32, 0x58, 0x32, 0x52, 0x00, 0x3A, 0x16, 0xC0, 0x18, 0x3B, 0x01, 0xC0, 0x14, 0x78, 0x3B, 0xFF, 0xC0, 0x0F, 0x3B, 0xF0, 0xC0, 0x0B, 0x3B, 0xEF, 0xC0, 0x07, 0x3B, 0xEE, 0xF2, 0x8B + } + }, + { + 41, + 79, + 0x39, + 0x0054, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x54, 0xC0, 0x03, 0x91, 0x07, 0x76, 0x32, 0x76, 0x44, 0x51, 0x4C, 0x78, 0x3A, 0x44, 0xBF, 0xD7, 0x51, 0x4C, 0x78, 0x53, 0x44, 0x55, 0x45, 0x01, 0x02, 0x4C, 0x53, 0x32, 0x58, 0x32, 0x52, 0x00, 0x3A, 0x16, 0xC0, 0x18, 0x3B, 0x11, 0xC0, 0x14, 0x3B, 0x10, 0xC0, 0x10, 0x78, 0x3B, 0xFF, 0xC0, 0x0B, 0x3B, 0xEF, 0xC0, 0x07, 0x3B, 0xEE, 0xC0, 0x03, 0x90, 0xD1, 0x51, 0x4C, 0x04, 0x32, 0x76, 0x45, 0x88, 0xB8 + } + }, + { + 42, + 79, + 0x39, + 0x0055, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x55, 0x51, 0x4D, 0x78, 0x3A, 0x45, 0xBF, 0xD5, 0x55, 0x44, 0x00, 0x55, 0x45, 0x01, 0x51, 0x4C, 0x53, 0x32, 0x58, 0x32, 0x52, 0x00, 0x3A, 0x16, 0xC0, 0x18, 0x3B, 0x12, 0xC0, 0x14, 0x3B, 0x11, 0xC0, 0x10, 0x3B, 0x01, 0xC0, 0x0C, 0x78, 0x3B, 0xF0, 0xC0, 0x07, 0x3B, 0xEF, 0xC0, 0x03, 0x90, 0x9B, 0x51, 0x4C, 0x04, 0x32, 0x76, 0x45, 0x51, 0x4D, 0x78, 0x3A, 0x45, 0xBF, 0xD5, 0x50, 0x00, 0x53, 0xA4, 0xF1 + } + }, + { + 43, + 79, + 0x39, + 0x0056, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x56, 0x44, 0x53, 0x45, 0x55, 0x32, 0x00, 0x5C, 0x52, 0x00, 0x3A, 0x16, 0xC0, 0x0F, 0x3B, 0x12, 0xC0, 0x0B, 0x3B, 0x11, 0xC0, 0x07, 0x3B, 0x01, 0xC0, 0x03, 0x90, 0x70, 0x55, 0x44, 0x00, 0x51, 0x4D, 0x78, 0x53, 0x45, 0x51, 0x4E, 0x12, 0x4C, 0x74, 0x53, 0x32, 0x5C, 0x52, 0x00, 0x3A, 0x16, 0xC0, 0x10, 0x3B, 0x01, 0xC0, 0x0C, 0x78, 0x3B, 0xF0, 0xC0, 0x07, 0x3B, 0xEF, 0xC0, 0x03, 0x90, 0x4B, 0x9F, 0xE8 + } + }, + { + 44, + 79, + 0x39, + 0x0057, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x57, 0x55, 0x45, 0x00, 0x51, 0x4C, 0x78, 0x53, 0x44, 0x53, 0x32, 0x5C, 0x52, 0x00, 0x3A, 0x16, 0xC0, 0x10, 0x3B, 0x11, 0xC0, 0x0C, 0x3B, 0x10, 0xC0, 0x08, 0x78, 0x3B, 0xFF, 0xC0, 0x03, 0x90, 0x2B, 0x51, 0x4D, 0x53, 0x45, 0x51, 0x4C, 0x53, 0x44, 0x51, 0x4E, 0x7A, 0x44, 0x7A, 0x45, 0x53, 0x32, 0x5C, 0x52, 0x00, 0x3A, 0x16, 0xC0, 0x10, 0x78, 0x3B, 0xFF, 0xC0, 0x0B, 0x3B, 0xEF, 0xC0, 0x07, 0x3B, 0x21 + } + }, + { + 45, + 79, + 0x39, + 0x0058, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x58, 0x3B, 0xEE, 0xC0, 0x03, 0x90, 0x05, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x51, 0x43, 0x01, 0x03, 0x3A, 0x0E, 0xC0, 0x0F, 0x51, 0x45, 0x3F, 0x34, 0x51, 0x44, 0x3F, 0x34, 0x51, 0x32, 0x3F, 0x34, 0x76, 0x0E, 0x7F, 0x84, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0x9C, 0x9C, 0x10, 0x80, 0x10, 0x80, 0x10, 0x80, 0x10, 0x20, 0x40, 0x62, 0xD0, 0x00, 0x55, 0x4C, 0x11, 0x55, 0x4D, 0x09, 0x55, 0x4E, 0x98, 0xAB, 0x02 + } + }, + { + 46, + 79, + 0x39, + 0x0059, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x59, 0x55, 0x4F, 0x03, 0x55, 0x50, 0x97, 0x55, 0x51, 0x01, 0x55, 0x52, 0xDF, 0x55, 0x15, 0x08, 0x55, 0x16, 0x08, 0x55, 0x17, 0x08, 0x55, 0x42, 0x1C, 0x55, 0x43, 0x04, 0x55, 0xA2, 0x00, 0x55, 0xA3, 0x00, 0x55, 0xA4, 0x48, 0x55, 0xA5, 0x04, 0x55, 0xA6, 0x08, 0x55, 0xA9, 0x01, 0x55, 0xA7, 0x0C, 0x55, 0xA8, 0x05, 0x55, 0x18, 0x04, 0x55, 0xAD, 0x02, 0x55, 0x40, 0x00, 0x55, 0x3F, 0x00, 0x51, 0xE1, 0x6F + } + }, + { + 47, + 79, + 0x39, + 0x005A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x5A, 0xA9, 0xA0, 0x08, 0x51, 0xA7, 0x58, 0xA8, 0x7C, 0x18, 0xF9, 0x70, 0xBF, 0x62, 0xD3, 0x01, 0x57, 0x3F, 0x50, 0x09, 0x28, 0x54, 0xA0, 0x79, 0xDF, 0xF9, 0x70, 0x3F, 0x71, 0xC0, 0x5D, 0xFC, 0x70, 0xCF, 0x71, 0x10, 0x62, 0x76, 0x07, 0x43, 0xE2, 0x08, 0x70, 0xCF, 0x71, 0x20, 0x62, 0xA4, 0x01, 0x62, 0xC0, 0x00, 0x39, 0x04, 0xD0, 0x04, 0x43, 0xC8, 0x04, 0x7C, 0x19, 0x41, 0x70, 0xCF, 0x71, 0x3B, 0x24 + } + }, + { + 48, + 79, + 0x39, + 0x005B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x5B, 0x20, 0x43, 0x81, 0x0E, 0x43, 0x85, 0x0E, 0x43, 0x89, 0x0E, 0x43, 0x8D, 0x0E, 0x43, 0x91, 0x0E, 0x43, 0x95, 0x0E, 0x43, 0x99, 0x0E, 0x43, 0x9D, 0x0E, 0x70, 0xCF, 0x55, 0x9A, 0x07, 0x55, 0x9C, 0x02, 0x55, 0x9E, 0x06, 0x55, 0x9D, 0x00, 0x50, 0x48, 0x57, 0x00, 0x7C, 0x18, 0xB7, 0x71, 0x30, 0x62, 0x1B, 0x40, 0x70, 0xCF, 0x62, 0xA2, 0x10, 0x7C, 0x2A, 0xD8, 0x50, 0x04, 0x7C, 0x18, 0xE7, 0x6B, 0x85 + } + }, + { + 49, + 79, + 0x39, + 0x005C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x5C, 0x70, 0xCF, 0x7C, 0x19, 0x4E, 0x7F, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x00, 0x03, 0x06, 0x09, 0x0C, 0x0F, 0x12, 0x15, 0x18, 0x62, 0xD0, 0x00, 0x70, 0xCF, 0x71, 0x10, 0x51, 0x3D, 0xF0, 0x60, 0x5C, 0x51, 0x3D, 0xF0, 0x75, 0x73, 0x53, 0x09, 0x5E, 0x00, 0x22, 0x09, 0x61, 0x00, 0x70, 0xCF, 0x71, 0x20, 0x51, 0x3D, 0xFE, 0xE9, 0x5C, 0x51, 0x3D, 0xFE, 0xED, 0x53, 0x09, 0xBF, 0x2E + } + }, + { + 50, + 79, + 0x39, + 0x005D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x5D, 0x5E, 0x00, 0x2A, 0x09, 0x61, 0x00, 0x70, 0xCF, 0x51, 0x3D, 0xFF, 0xBA, 0x53, 0xA0, 0x51, 0x3D, 0xFF, 0xBD, 0x53, 0xA1, 0x7F, 0x70, 0xCF, 0x71, 0x20, 0x51, 0x3D, 0xFE, 0xC5, 0x5C, 0x51, 0x3D, 0xFE, 0xC9, 0x73, 0x53, 0x09, 0x5E, 0x00, 0x22, 0x09, 0x61, 0x00, 0x70, 0xCF, 0x71, 0x10, 0x51, 0x3D, 0xF0, 0x10, 0x5C, 0x51, 0x3D, 0xF0, 0x25, 0x53, 0x09, 0x5E, 0x00, 0x2A, 0x09, 0x61, 0x00, 0x4E, 0x4D + } + }, + { + 51, + 79, + 0x39, + 0x005E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x5E, 0x70, 0xCF, 0x7F, 0x0C, 0x0C, 0x00, 0x10, 0x10, 0x08, 0x00, 0x04, 0x08, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x00, 0x04, 0x08, 0x00, 0x00, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x01, 0x80, 0x10, 0x01, 0x80, 0x01, 0x40, 0x04, 0x02, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x02, 0x40, 0x08, 0x80, 0x20, 0x80, 0x08, 0x04, 0x02, 0x40, 0x20, 0x62, 0xD0, 0x00, 0x55, 0x9D, 0x00, 0x51, 0xA6, 0x91, 0x11, 0xD4 + } + }, + { + 52, + 79, + 0x39, + 0x005F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x5F, 0x76, 0x51, 0xA4, 0x58, 0xA3, 0x7C, 0x18, 0xB7, 0x51, 0xA5, 0x7C, 0x18, 0xE7, 0x70, 0xCF, 0x71, 0x20, 0x50, 0x00, 0x60, 0x80, 0x60, 0x84, 0x60, 0x88, 0x60, 0x8C, 0x60, 0x90, 0x60, 0x94, 0x60, 0x98, 0x60, 0x9C, 0x60, 0x82, 0x60, 0x86, 0x60, 0x8A, 0x60, 0x8E, 0x60, 0x92, 0x60, 0x96, 0x60, 0x9A, 0x60, 0x9E, 0x60, 0xC0, 0x43, 0x81, 0x04, 0x43, 0x85, 0x04, 0x43, 0x89, 0x04, 0x43, 0x8D, 0x86, 0xBF + } + }, + { + 53, + 79, + 0x39, + 0x0060, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x60, 0x04, 0x43, 0x91, 0x04, 0x43, 0x95, 0x04, 0x43, 0x99, 0x04, 0x43, 0x9D, 0x04, 0x71, 0x30, 0x71, 0x30, 0x62, 0x1F, 0x00, 0x62, 0x1B, 0x70, 0x62, 0x13, 0x87, 0x70, 0xCF, 0x71, 0x10, 0x55, 0x09, 0x19, 0x51, 0x09, 0xFF, 0x5E, 0x5C, 0x51, 0x09, 0xFF, 0x73, 0x53, 0x45, 0x5E, 0x00, 0x2A, 0x45, 0x61, 0x00, 0x7A, 0x09, 0xDF, 0xEC, 0x70, 0xCF, 0x41, 0xA2, 0x3F, 0x55, 0x40, 0x00, 0x55, 0x3F, 0xDC, 0x6C + } + }, + { + 54, + 79, + 0x39, + 0x0061, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x61, 0x00, 0x51, 0xA9, 0xA0, 0x08, 0x51, 0xA7, 0x58, 0xA8, 0x7C, 0x18, 0xF9, 0x7F, 0x41, 0xE0, 0xFB, 0x71, 0x30, 0x41, 0x1B, 0xBF, 0x70, 0xCF, 0x71, 0x20, 0x41, 0x81, 0xFD, 0x41, 0x85, 0xFD, 0x41, 0x89, 0xFD, 0x41, 0x8D, 0xFD, 0x41, 0x91, 0xFD, 0x41, 0x95, 0xFD, 0x41, 0x99, 0xFD, 0x41, 0x9D, 0xFD, 0x70, 0xCF, 0x41, 0xA2, 0xEF, 0x7C, 0x19, 0x5A, 0x70, 0xCF, 0x71, 0x10, 0x41, 0xE2, 0xF7, 0x90, 0xD5 + } + }, + { + 55, + 79, + 0x39, + 0x0062, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x62, 0x70, 0xCF, 0x7F, 0x7C, 0x19, 0x41, 0x70, 0xCF, 0x71, 0x10, 0x43, 0xE2, 0x08, 0x71, 0x30, 0x43, 0x1B, 0x40, 0x70, 0xCF, 0x71, 0x20, 0x43, 0x81, 0x02, 0x43, 0x85, 0x02, 0x43, 0x89, 0x02, 0x43, 0x8D, 0x02, 0x43, 0x91, 0x02, 0x43, 0x95, 0x02, 0x43, 0x99, 0x02, 0x43, 0x9D, 0x02, 0x70, 0xCF, 0x43, 0xA2, 0x10, 0x7C, 0x19, 0x4E, 0x7F, 0x62, 0xD0, 0x00, 0x53, 0x49, 0x5A, 0x48, 0x53, 0x9B, 0x24, 0xFE + } + }, + { + 56, + 79, + 0x39, + 0x0063, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x63, 0x5B, 0x21, 0x01, 0xA0, 0x06, 0x2E, 0x9E, 0x01, 0x80, 0x04, 0x26, 0x9E, 0xFE, 0x68, 0x48, 0x6E, 0x49, 0x51, 0x49, 0x78, 0x70, 0xCF, 0x71, 0x20, 0x60, 0xC9, 0x70, 0xCF, 0x7C, 0x2A, 0xBA, 0x7F, 0x00, 0x04, 0x0C, 0x1C, 0x3C, 0x7C, 0xFC, 0x62, 0xD0, 0x00, 0x53, 0x3E, 0x76, 0x3E, 0xFF, 0xF0, 0x26, 0x9C, 0x03, 0x2C, 0x9C, 0x7C, 0x2A, 0xBA, 0x7F, 0x62, 0xD0, 0x00, 0x53, 0xA7, 0x5A, 0xA8, 0xA6, 0x03 + } + }, + { + 57, + 79, + 0x39, + 0x0064, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x64, 0x90, 0x01, 0x7F, 0x62, 0xD0, 0x00, 0x08, 0x5A, 0x3E, 0x55, 0x40, 0xFF, 0x55, 0x3F, 0x01, 0x78, 0xA0, 0x0A, 0x06, 0x40, 0xFF, 0x0E, 0x3F, 0x01, 0x78, 0xBF, 0xF8, 0x51, 0x3E, 0x68, 0x3F, 0x6E, 0x40, 0x78, 0xDF, 0xFA, 0x16, 0x40, 0x7F, 0x1E, 0x3F, 0x00, 0x18, 0x78, 0x64, 0x64, 0x26, 0x9C, 0x03, 0x2C, 0x9C, 0x7C, 0x2A, 0xBA, 0x7F, 0x62, 0xD0, 0x00, 0x78, 0x53, 0x9A, 0x7C, 0x2A, 0xBA, 0x11, 0xDA + } + }, + { + 58, + 79, + 0x39, + 0x0065, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x65, 0x7F, 0x70, 0xCF, 0x71, 0x20, 0x62, 0xA7, 0x89, 0x62, 0xA7, 0x49, 0x70, 0xCF, 0x7F, 0x70, 0xCF, 0x71, 0x20, 0x49, 0xC8, 0x08, 0xAF, 0xFC, 0x70, 0xCF, 0x7F, 0x70, 0xCF, 0x71, 0x20, 0x62, 0xA7, 0x09, 0x70, 0xCF, 0x7F, 0x70, 0xBF, 0x62, 0xD0, 0x00, 0x55, 0x41, 0x00, 0x93, 0xA8, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x70, 0xBF, 0x62, 0xD0, 0x00, 0x55, 0x41, 0x02, 0x93, 0x99, 0x70, 0x3F, 0x71, 0xB9, 0x2B + } + }, + { + 59, + 79, + 0x39, + 0x0066, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x66, 0xC0, 0x7F, 0x57, 0x98, 0x50, 0x0A, 0x28, 0x21, 0xE0, 0xB0, 0x04, 0x79, 0xDF, 0xF7, 0x7F, 0x70, 0xCF, 0x71, 0x10, 0x64, 0xE0, 0x01, 0x80, 0x09, 0x80, 0x75, 0x80, 0xE1, 0x81, 0x3D, 0x81, 0x49, 0x41, 0x00, 0xFD, 0x41, 0x0C, 0xF7, 0x41, 0x0C, 0xBF, 0x41, 0x00, 0x7F, 0x41, 0x10, 0xF7, 0x41, 0x10, 0xBF, 0x70, 0xCF, 0x71, 0x20, 0x5D, 0xF7, 0x53, 0x9F, 0x70, 0xFE, 0x43, 0x80, 0x04, 0x52, 0xE2, 0x7E + } + }, + { + 60, + 79, + 0x39, + 0x0067, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x67, 0x06, 0x60, 0x83, 0x06, 0x68, 0x06, 0x2E, 0x70, 0x01, 0x43, 0x84, 0x08, 0x52, 0x03, 0x60, 0x87, 0x06, 0x69, 0x03, 0x2E, 0x71, 0x01, 0x43, 0x88, 0x04, 0x52, 0x00, 0x60, 0x8B, 0x06, 0x6A, 0x00, 0x2E, 0x72, 0x01, 0x43, 0x8C, 0x02, 0x52, 0x09, 0x60, 0x8F, 0x06, 0x6B, 0x09, 0x2E, 0x73, 0x01, 0x43, 0x90, 0x01, 0x52, 0x0C, 0x60, 0x93, 0x06, 0x6C, 0x0C, 0x2E, 0x74, 0x01, 0x43, 0x94, 0x02, 0x8C, 0xD3 + } + }, + { + 61, + 79, + 0x39, + 0x0068, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x68, 0x52, 0x0F, 0x60, 0x97, 0x06, 0x6D, 0x0F, 0x2E, 0x75, 0x01, 0x43, 0xA3, 0x3F, 0x7F, 0x41, 0x04, 0xBF, 0x41, 0x0C, 0xFB, 0x41, 0x0C, 0xDF, 0x41, 0x00, 0xDF, 0x41, 0x10, 0xFB, 0x41, 0x10, 0xDF, 0x70, 0xCF, 0x71, 0x20, 0x5D, 0xF7, 0x53, 0x9F, 0x70, 0xFE, 0x43, 0x80, 0x02, 0x52, 0x07, 0x60, 0x83, 0x06, 0x68, 0x07, 0x2E, 0x70, 0x01, 0x43, 0x84, 0x04, 0x52, 0x04, 0x60, 0x87, 0x06, 0x69, 0x46, 0x48 + } + }, + { + 62, + 79, + 0x39, + 0x0069, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x69, 0x04, 0x2E, 0x71, 0x01, 0x43, 0x88, 0x02, 0x52, 0x01, 0x60, 0x8B, 0x06, 0x6A, 0x01, 0x2E, 0x72, 0x01, 0x43, 0x8C, 0x04, 0x52, 0x0A, 0x60, 0x8F, 0x06, 0x6B, 0x0A, 0x2E, 0x73, 0x01, 0x43, 0x90, 0x02, 0x52, 0x0D, 0x60, 0x93, 0x06, 0x6C, 0x0D, 0x2E, 0x74, 0x01, 0x43, 0x94, 0x04, 0x52, 0x10, 0x60, 0x97, 0x06, 0x6D, 0x10, 0x2E, 0x75, 0x01, 0x43, 0xA3, 0x3F, 0x7F, 0x41, 0x08, 0xF7, 0x41, 0xC5, 0x47 + } + }, + { + 63, + 79, + 0x39, + 0x006A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x6A, 0x0C, 0xFD, 0x41, 0x0C, 0xEF, 0x41, 0x08, 0x7F, 0x41, 0x10, 0xFD, 0x70, 0xCF, 0x71, 0x20, 0x5D, 0xF7, 0x53, 0x9F, 0x70, 0xFE, 0x43, 0x80, 0x01, 0x52, 0x08, 0x60, 0x83, 0x06, 0x68, 0x08, 0x2E, 0x70, 0x01, 0x43, 0x84, 0x02, 0x52, 0x05, 0x60, 0x87, 0x06, 0x69, 0x05, 0x2E, 0x71, 0x01, 0x43, 0x88, 0x01, 0x52, 0x02, 0x60, 0x8B, 0x06, 0x6A, 0x02, 0x2E, 0x72, 0x01, 0x43, 0x8C, 0x08, 0x52, 0x57, 0x6C + } + }, + { + 64, + 79, + 0x39, + 0x006B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x6B, 0x0B, 0x60, 0x8F, 0x06, 0x6B, 0x0B, 0x2E, 0x73, 0x01, 0x43, 0x90, 0x04, 0x52, 0x0E, 0x60, 0x93, 0x06, 0x6C, 0x0E, 0x2E, 0x74, 0x01, 0x43, 0xA3, 0x1F, 0x7F, 0x70, 0xCF, 0x71, 0x20, 0x5D, 0xF7, 0x53, 0x9F, 0x70, 0xFE, 0x43, 0xA3, 0x00, 0x7F, 0x7F, 0x70, 0xCF, 0x71, 0x10, 0x64, 0xE0, 0x01, 0x80, 0x09, 0x80, 0x34, 0x80, 0x5F, 0x80, 0x84, 0x80, 0x8B, 0x43, 0x00, 0x02, 0x43, 0x0C, 0x08, 0x1D, 0xF9 + } + }, + { + 65, + 79, + 0x39, + 0x006C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x6C, 0x43, 0x0C, 0x40, 0x43, 0x00, 0x80, 0x43, 0x10, 0x08, 0x43, 0x10, 0x40, 0x70, 0xCF, 0x71, 0x20, 0x41, 0x80, 0xFB, 0x41, 0x84, 0xF7, 0x41, 0x88, 0xFB, 0x41, 0x8C, 0xFD, 0x41, 0x90, 0xFE, 0x41, 0x94, 0xFD, 0x62, 0xA3, 0x00, 0x80, 0x5E, 0x43, 0x04, 0x40, 0x43, 0x0C, 0x04, 0x43, 0x0C, 0x20, 0x43, 0x00, 0x20, 0x43, 0x10, 0x04, 0x43, 0x10, 0x20, 0x70, 0xCF, 0x71, 0x20, 0x41, 0x80, 0xFD, 0x9E, 0xFC + } + }, + { + 66, + 79, + 0x39, + 0x006D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x6D, 0x41, 0x84, 0xFB, 0x41, 0x88, 0xFD, 0x41, 0x8C, 0xFB, 0x41, 0x90, 0xFD, 0x41, 0x94, 0xFB, 0x62, 0xA3, 0x00, 0x80, 0x31, 0x43, 0x08, 0x08, 0x43, 0x0C, 0x02, 0x43, 0x0C, 0x10, 0x43, 0x08, 0x80, 0x43, 0x10, 0x02, 0x70, 0xCF, 0x71, 0x20, 0x41, 0x80, 0xFE, 0x41, 0x84, 0xFD, 0x41, 0x88, 0xFE, 0x41, 0x8C, 0xF7, 0x41, 0x90, 0xFB, 0x62, 0xA3, 0x00, 0x80, 0x0A, 0x70, 0xCF, 0x71, 0x20, 0x62, 0x2E, 0x1D + } + }, + { + 67, + 79, + 0x39, + 0x006E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x6E, 0xA3, 0x00, 0x80, 0x01, 0x70, 0xCF, 0x7F, 0x62, 0xD3, 0x00, 0x57, 0x07, 0x52, 0x70, 0x54, 0x80, 0x52, 0x68, 0x54, 0x78, 0x51, 0xA0, 0x56, 0x70, 0x00, 0x54, 0x68, 0x79, 0xDF, 0xEF, 0x7F, 0x62, 0xD5, 0x00, 0x62, 0xD3, 0x00, 0x58, 0xA0, 0x55, 0x09, 0x88, 0x50, 0x0A, 0x28, 0x21, 0x1F, 0x3F, 0x09, 0x75, 0x3C, 0x09, 0x99, 0xCF, 0xF4, 0x7F, 0x62, 0xD0, 0x00, 0x55, 0xAA, 0x00, 0x55, 0xAC, 0xE5, 0x8C + } + }, + { + 68, + 79, + 0x39, + 0x006F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x6F, 0x00, 0x57, 0x07, 0x62, 0xD3, 0x00, 0x5B, 0x3D, 0x80, 0x00, 0xA0, 0x5A, 0x10, 0x64, 0x5C, 0x52, 0x58, 0x53, 0x56, 0x52, 0x59, 0x53, 0x57, 0x51, 0x3E, 0x6E, 0x56, 0x6E, 0x57, 0x78, 0xDF, 0xFA, 0x51, 0x40, 0x14, 0x57, 0x51, 0x3F, 0x1C, 0x56, 0xA0, 0x0E, 0xD0, 0x06, 0x55, 0x57, 0x00, 0x80, 0x04, 0x55, 0x57, 0xFF, 0x55, 0x56, 0x00, 0x20, 0x10, 0x52, 0x78, 0x5C, 0x62, 0xD3, 0x02, 0x51, 0x6C, 0x9B + } + }, + { + 69, + 79, + 0x39, + 0x0070, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x70, 0x57, 0x73, 0x54, 0x00, 0x39, 0xDC, 0xD0, 0x05, 0x39, 0x23, 0xD0, 0x04, 0x55, 0xAB, 0x01, 0x62, 0xD3, 0x08, 0x13, 0x00, 0xC0, 0x07, 0x39, 0x0F, 0xD0, 0x0B, 0x80, 0x05, 0x39, 0xF1, 0xC0, 0x05, 0x04, 0xAA, 0x76, 0xAC, 0x20, 0x79, 0xDF, 0x9C, 0x51, 0xAC, 0x47, 0xAA, 0x80, 0xA0, 0x03, 0x76, 0xAA, 0x68, 0xAA, 0x39, 0x02, 0xC0, 0x18, 0x47, 0xAA, 0x80, 0xA0, 0x03, 0x76, 0xAA, 0x68, 0xAA, 0x67, 0x92 + } + }, + { + 70, + 79, + 0x39, + 0x0071, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x71, 0x67, 0x39, 0x02, 0xC0, 0x0A, 0x47, 0xAA, 0x80, 0xA0, 0x03, 0x76, 0xAA, 0x68, 0xAA, 0x57, 0x07, 0x62, 0xD3, 0x00, 0x3D, 0x80, 0x00, 0xA0, 0x33, 0x10, 0x52, 0x78, 0x5C, 0x62, 0xD3, 0x02, 0x52, 0x00, 0x53, 0x47, 0x47, 0x42, 0x10, 0xA0, 0x1B, 0x51, 0xAA, 0x15, 0x00, 0xD0, 0x0B, 0x47, 0xAA, 0x80, 0xB0, 0x0E, 0x56, 0x00, 0x00, 0x80, 0x09, 0x47, 0xAA, 0x80, 0xA0, 0x04, 0x56, 0x00, 0xFF, 0xE5, 0x8F + } + }, + { + 71, + 79, + 0x39, + 0x0072, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x72, 0x52, 0x00, 0x73, 0x53, 0x57, 0x5B, 0x7C, 0x10, 0xC0, 0x20, 0x79, 0xDF, 0xC4, 0x7F, 0x62, 0xD0, 0x00, 0x70, 0xCF, 0x71, 0x20, 0x49, 0xC4, 0x01, 0xAF, 0xFC, 0x41, 0xA4, 0xF7, 0x41, 0xC4, 0xFE, 0x5D, 0xA8, 0x53, 0x59, 0x5D, 0xA9, 0x53, 0x58, 0x5D, 0xAB, 0x53, 0x5B, 0x5D, 0xAC, 0x53, 0x5A, 0x5D, 0xAE, 0x53, 0x5D, 0x5D, 0xAF, 0x53, 0x5C, 0x5D, 0xB1, 0x53, 0x5F, 0x5D, 0xB2, 0x53, 0x5E, 0x2F, 0x24 + } + }, + { + 72, + 79, + 0x39, + 0x0073, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x73, 0x5D, 0xB4, 0x53, 0x61, 0x5D, 0xB5, 0x53, 0x60, 0x5D, 0xB7, 0x53, 0x63, 0x5D, 0xB8, 0x53, 0x62, 0x5D, 0xBA, 0x53, 0x65, 0x5D, 0xBB, 0x53, 0x64, 0x5D, 0xBD, 0x53, 0x67, 0x5D, 0xBE, 0x53, 0x66, 0x70, 0xCF, 0x7F, 0x62, 0xD3, 0x00, 0x57, 0x07, 0x5B, 0x3D, 0x70, 0x00, 0xA0, 0x25, 0x10, 0x64, 0x5C, 0x51, 0x3E, 0x6F, 0x58, 0x6F, 0x59, 0x78, 0xDF, 0xFA, 0x51, 0x40, 0x15, 0x59, 0x51, 0x3F, 0x50, 0x67 + } + }, + { + 73, + 79, + 0x39, + 0x0074, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x74, 0x1D, 0x58, 0xA0, 0x0E, 0xD0, 0x06, 0x56, 0x59, 0x00, 0x80, 0x04, 0x56, 0x59, 0xFF, 0x56, 0x58, 0x00, 0x20, 0x79, 0xDF, 0xD4, 0x7F, 0x55, 0x3D, 0x00, 0x55, 0x37, 0x01, 0x7C, 0x17, 0x18, 0x9E, 0x7E, 0x58, 0xA1, 0x62, 0xD3, 0x01, 0x52, 0xA0, 0x60, 0xFD, 0x52, 0xC5, 0x70, 0xCF, 0x71, 0x20, 0x60, 0xA5, 0x70, 0xCF, 0x9E, 0x51, 0x50, 0x00, 0x57, 0x88, 0x9C, 0x53, 0x43, 0xA4, 0x08, 0x47, 0x25, 0x12 + } + }, + { + 74, + 79, + 0x39, + 0x0075, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x75, 0x9F, 0x01, 0xA0, 0x03, 0x71, 0x01, 0x70, 0xCF, 0x51, 0x3D, 0xA0, 0x05, 0x9E, 0x6A, 0x51, 0x3D, 0x9F, 0x3C, 0x50, 0x00, 0x9D, 0x93, 0x62, 0xD3, 0x01, 0x58, 0xA1, 0x52, 0xA1, 0x60, 0xFD, 0x52, 0xC6, 0x70, 0xCF, 0x71, 0x20, 0x60, 0xA5, 0x70, 0xCF, 0x9E, 0x1C, 0x50, 0x01, 0x57, 0x88, 0x9C, 0x1E, 0x43, 0xA4, 0x08, 0x47, 0x9F, 0x01, 0xA0, 0x03, 0x71, 0x01, 0x70, 0xCF, 0x9E, 0x39, 0x9F, 0x07, 0xD7 + } + }, + { + 75, + 79, + 0x39, + 0x0076, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x76, 0x0D, 0x50, 0x01, 0x9D, 0x64, 0x62, 0xD3, 0x01, 0x58, 0xA1, 0x52, 0xA2, 0x60, 0xFD, 0x52, 0xC7, 0x70, 0xCF, 0x71, 0x20, 0x60, 0xA5, 0x70, 0xCF, 0x9D, 0xED, 0x50, 0x02, 0x57, 0x88, 0x9B, 0xEF, 0x43, 0xA4, 0x08, 0x47, 0x9F, 0x01, 0xA0, 0x03, 0x71, 0x01, 0x70, 0xCF, 0x9E, 0x0A, 0x9E, 0xDE, 0x50, 0x02, 0x9D, 0x35, 0x7C, 0x17, 0x55, 0x76, 0x3D, 0x3C, 0x3D, 0x09, 0xCF, 0x5F, 0x62, 0xD3, 0x43, 0x50 + } + }, + { + 76, + 79, + 0x39, + 0x0077, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x77, 0x00, 0x9D, 0xC4, 0x9D, 0xF3, 0x7C, 0x14, 0x1F, 0x55, 0x37, 0x00, 0x7F, 0x43, 0xE0, 0x08, 0x7F, 0x41, 0xE0, 0xF7, 0x7F, 0x62, 0xE6, 0x04, 0x62, 0xD0, 0x00, 0x5A, 0x53, 0x53, 0x54, 0x10, 0x08, 0x51, 0x55, 0x08, 0x38, 0x03, 0x4F, 0x50, 0x00, 0x54, 0xFE, 0x54, 0xFD, 0x01, 0x08, 0x54, 0xFF, 0x48, 0xFC, 0x01, 0xA0, 0x09, 0x52, 0xFB, 0x05, 0xFE, 0x52, 0xFA, 0x0D, 0xFD, 0x6F, 0xFD, 0x6F, 0xCC, 0x63 + } + }, + { + 77, + 79, + 0x39, + 0x0078, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x78, 0xFE, 0x6F, 0xFC, 0x7B, 0xFF, 0xBF, 0xEA, 0x52, 0xFC, 0x60, 0xE8, 0x52, 0xFE, 0x60, 0xE7, 0x62, 0xE6, 0x00, 0x62, 0xE6, 0x01, 0x38, 0xFA, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x5D, 0xF7, 0x08, 0x70, 0xFE, 0x57, 0xF0, 0x50, 0x00, 0x62, 0xE6, 0x04, 0x62, 0xE8, 0x01, 0x62, 0xE7, 0x00, 0x62, 0xE6, 0x00, 0x62, 0xE6, 0x01, 0x62, 0xDA, 0xF7, 0x49, 0xDA, 0x08, 0xAF, 0xFC, 0x62, 0xDA, 0xF7, 0x08, 0xF1, 0xAE + } + }, + { + 78, + 79, + 0x39, + 0x0079, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x79, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x51, 0x00, 0x18, 0x49, 0xDA, 0x08, 0xB0, 0x04, 0x40, 0x80, 0x05, 0x74, 0x62, 0xDA, 0xF7, 0x79, 0xBF, 0xE0, 0x49, 0xDA, 0x08, 0xA0, 0x02, 0x74, 0x62, 0xE6, 0x04, 0x60, 0xE8, 0x62, 0xE7, 0x00, 0x62, 0xE6, 0x00, 0x62, 0xE6, 0x01, 0x62, 0xD0, 0x00, 0x53, 0x55, 0x55, 0x53, 0x00, 0x55, 0x54, 0x01, 0x7E, 0x55, 0x77 + } + }, + { + 79, + 79, + 0x39, + 0x007A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x7A, 0x5D, 0xF7, 0x08, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x51, 0x05, 0x58, 0x04, 0x7E, 0x5D, 0xF7, 0x08, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x51, 0x01, 0x58, 0x00, 0x7E, 0x5D, 0xF7, 0x08, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x51, 0x03, 0x58, 0x02, 0x7E, 0x5D, 0xF7, 0x08, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x51, 0x07, 0x58, 0x06, 0x7E, 0x08, 0x08, 0x10, 0x4F, 0x5D, 0xF7, 0x54, 0xFD, 0x70, 0x3F, 0x71, 0xC0, 0x9C, 0x06 + } + }, + { + 80, + 79, + 0x39, + 0x007B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x7B, 0x20, 0x18, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x53, 0x05, 0x5A, 0x04, 0x7E, 0x08, 0x08, 0x10, 0x4F, 0x5D, 0xF7, 0x54, 0xFD, 0x70, 0x3F, 0x71, 0xC0, 0x20, 0x18, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x53, 0x01, 0x5A, 0x00, 0x7E, 0x08, 0x08, 0x10, 0x4F, 0x5D, 0xF7, 0x54, 0xFD, 0x70, 0x3F, 0x71, 0xC0, 0x20, 0x18, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x53, 0x03, 0x5A, 0x02, 0x7E, 0x0E, 0x1E, 0x3D, 0x7A, 0xE3, 0x95 + } + }, + { + 81, + 79, + 0x39, + 0x007C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x7C, 0x07, 0x03, 0x00, 0x00, 0x07, 0x0E, 0x1E, 0x3D, 0x03, 0x01, 0x00, 0x00, 0x1E, 0x3D, 0x7A, 0xF6, 0x0E, 0x07, 0x01, 0x00, 0x58, 0x45, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x79, 0xDF, 0xF6, 0x7A, 0x44, 0xBF, 0xF0, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x08, 0x10, 0x70, 0x3F, 0x71, 0x80, 0x5D, 0xD3, 0x08, 0x5D, 0xD0, 0x08, 0x62, 0xD0, 0x00, 0x51, 0xB6, 0x60, 0xD3, 0x2E, 0xB3, 0x80, 0x48, 0x60 + } + }, + { + 82, + 79, + 0x39, + 0x007D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x7D, 0x49, 0xD7, 0x08, 0xA0, 0x09, 0x26, 0xB3, 0xF0, 0x2E, 0xB3, 0x00, 0x80, 0x08, 0x49, 0xD7, 0x20, 0xA0, 0x03, 0x80, 0xA6, 0x51, 0xB3, 0x21, 0x0E, 0xE0, 0x01, 0x80, 0x11, 0x80, 0x67, 0x80, 0x79, 0x80, 0x47, 0x80, 0x96, 0x80, 0x94, 0x80, 0x92, 0x80, 0x90, 0x80, 0x97, 0x5D, 0xD8, 0x21, 0xFE, 0x39, 0x48, 0xA0, 0x06, 0x62, 0xD7, 0x00, 0x80, 0x8A, 0x49, 0xD8, 0x01, 0xB0, 0x0F, 0x55, 0xBA, 0x69, 0xA3 + } + }, + { + 83, + 79, + 0x39, + 0x007E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x7E, 0x02, 0x26, 0xB3, 0xF0, 0x2E, 0xB3, 0x02, 0x62, 0xD7, 0x10, 0x80, 0x77, 0x55, 0xBA, 0x01, 0x26, 0xB3, 0xF0, 0x2E, 0xB3, 0x06, 0x5F, 0xB5, 0xB4, 0x51, 0xB7, 0x02, 0xB5, 0x5C, 0x52, 0x00, 0x60, 0xD8, 0x76, 0xB5, 0x62, 0xD7, 0x14, 0x80, 0x5B, 0x51, 0xB8, 0x78, 0x3A, 0xB5, 0xC0, 0x0F, 0x51, 0xB7, 0x02, 0xB5, 0x5C, 0x52, 0x00, 0x60, 0xD8, 0x76, 0xB5, 0x2E, 0xB3, 0x20, 0x60, 0xD8, 0x62, 0x18, 0x02 + } + }, + { + 84, + 79, + 0x39, + 0x007F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x7F, 0xD7, 0x04, 0x80, 0x3F, 0x5D, 0xD8, 0x3A, 0xB8, 0xD0, 0x2B, 0xA0, 0x29, 0x53, 0xB5, 0x53, 0xB4, 0x26, 0xB3, 0xF0, 0x2E, 0xB3, 0x04, 0x80, 0x18, 0x51, 0xB9, 0x78, 0x3A, 0xB5, 0xC0, 0x16, 0x51, 0xB7, 0x02, 0xB5, 0x5C, 0x5D, 0xD8, 0x54, 0x00, 0x2E, 0xB3, 0x10, 0x76, 0xB5, 0x80, 0x01, 0x62, 0xD7, 0x10, 0x80, 0x0F, 0x62, 0xD7, 0x00, 0x80, 0x0A, 0x26, 0xB3, 0xF0, 0x2E, 0xB3, 0x00, 0x55, 0xFC, 0xCB + } + }, + { + 85, + 79, + 0x39, + 0x0080, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x80, 0xBA, 0x00, 0x18, 0x60, 0xD0, 0x18, 0x60, 0xD3, 0x20, 0x18, 0x7E, 0x62, 0xD0, 0x00, 0x70, 0xCF, 0x71, 0x10, 0x41, 0x04, 0x5F, 0x43, 0x05, 0xA0, 0x70, 0xCF, 0x26, 0xAF, 0x5F, 0x51, 0xAF, 0x60, 0x04, 0x55, 0xBA, 0x00, 0x90, 0x1F, 0x90, 0x24, 0x40, 0x40, 0x40, 0x40, 0x40, 0x50, 0x00, 0x53, 0xB4, 0x70, 0xCF, 0x71, 0x10, 0x43, 0x04, 0xA0, 0x43, 0x05, 0xA0, 0x70, 0xCF, 0x2E, 0xAF, 0xA0, 0xAC, 0x2C + } + }, + { + 86, + 79, + 0x39, + 0x0081, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x81, 0x51, 0xAF, 0x60, 0x04, 0x7F, 0x41, 0xE0, 0x7F, 0x43, 0xE0, 0x80, 0x7F, 0x43, 0xD6, 0x31, 0x7F, 0x41, 0xE0, 0x7F, 0x41, 0xD6, 0xFE, 0x7F, 0x62, 0xD0, 0x00, 0x4F, 0x52, 0xFD, 0x53, 0xB8, 0x52, 0xFC, 0x53, 0xB9, 0x52, 0xFB, 0x53, 0xB7, 0x52, 0xFA, 0x53, 0xB6, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x76, 0xBB, 0xD0, 0x04, 0x55, 0xBB, 0xFF, 0x7E, 0x43, 0xE1, 0x01, 0x7F, 0x41, 0xE1, 0xFE, 0x7F, 0xB7, 0x43 + } + }, + { + 87, + 79, + 0x39, + 0x0082, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x82, 0x43, 0x23, 0x01, 0x7F, 0x54, 0x00, 0x70, 0xFE, 0x41, 0x23, 0xFE, 0x18, 0x60, 0x22, 0x18, 0x60, 0x23, 0x18, 0x70, 0x3F, 0x71, 0xC0, 0x7E, 0x30, 0x62, 0xD0, 0x00, 0x53, 0xF8, 0x5D, 0xF7, 0x08, 0x21, 0xC0, 0xB0, 0x07, 0x56, 0x01, 0x00, 0x55, 0xF8, 0x00, 0x51, 0xF8, 0x70, 0x3F, 0x71, 0x80, 0x60, 0xD3, 0x55, 0xFD, 0x01, 0x3C, 0xFD, 0x01, 0xB0, 0xAE, 0x70, 0xCF, 0x71, 0x10, 0x5D, 0xE0, 0xFE, 0xD2 + } + }, + { + 88, + 79, + 0x39, + 0x0083, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x83, 0x08, 0x21, 0xF8, 0x49, 0xFE, 0x08, 0xB0, 0x0A, 0x49, 0xFE, 0x10, 0xB0, 0x09, 0x29, 0x01, 0x80, 0x07, 0x29, 0x02, 0x80, 0x03, 0x29, 0x00, 0x60, 0xE0, 0x70, 0xCF, 0x80, 0x01, 0x65, 0xFD, 0x3C, 0xFD, 0x02, 0xB0, 0x84, 0x65, 0xFD, 0x70, 0xCF, 0x71, 0x10, 0x49, 0xE4, 0x08, 0xA0, 0x05, 0x70, 0xCF, 0x80, 0x20, 0x70, 0xCF, 0x52, 0x00, 0x53, 0xFA, 0x51, 0xFD, 0x39, 0x04, 0xB0, 0x69, 0x08, 0xF8, 0xC7 + } + }, + { + 89, + 79, + 0x39, + 0x0084, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x84, 0x10, 0x50, 0x03, 0x55, 0xF8, 0x3A, 0x7C, 0x00, 0x60, 0x20, 0x18, 0x53, 0xFD, 0x3C, 0xF8, 0x00, 0xA0, 0x09, 0x55, 0xFF, 0x00, 0x55, 0xFD, 0x10, 0x80, 0x37, 0x65, 0xFD, 0x52, 0x00, 0x53, 0xFA, 0x52, 0x02, 0x53, 0xFB, 0x52, 0x01, 0x60, 0xD4, 0x52, 0x05, 0x53, 0xFC, 0x55, 0xFE, 0x56, 0x51, 0xFD, 0x39, 0x08, 0xB0, 0x33, 0x08, 0x10, 0x50, 0x02, 0x55, 0xF8, 0x3A, 0x7C, 0x00, 0x60, 0x20, 0x70, 0xB8 + } + }, + { + 90, + 79, + 0x39, + 0x0085, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x85, 0x18, 0x53, 0xFD, 0x55, 0xFF, 0x01, 0x3C, 0xF8, 0x00, 0xA0, 0x04, 0x55, 0xFF, 0x00, 0x65, 0xFD, 0x3C, 0xFD, 0x10, 0xB0, 0x13, 0x18, 0x70, 0xCF, 0x71, 0x10, 0x60, 0xE0, 0x70, 0xCF, 0x65, 0xFD, 0x51, 0xFF, 0x3C, 0xFD, 0x20, 0xA0, 0x04, 0x30, 0x8F, 0xFE, 0x62, 0xD0, 0x00, 0x62, 0xD5, 0x00, 0x62, 0xD4, 0x00, 0x7E, 0x30, 0x30, 0x30, 0x51, 0xF8, 0x70, 0x3F, 0x71, 0x80, 0x60, 0xD3, 0x52, 0x35, 0x43 + } + }, + { + 91, + 79, + 0x39, + 0x0086, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x86, 0x02, 0x53, 0xFB, 0x52, 0x01, 0x60, 0xD5, 0x52, 0x03, 0x74, 0x53, 0xFD, 0x52, 0x04, 0x53, 0xFE, 0x50, 0x00, 0x6C, 0x00, 0x6A, 0x6C, 0x00, 0x6A, 0x6C, 0x00, 0x6A, 0x6C, 0x00, 0x6A, 0x6C, 0x00, 0x6A, 0x6C, 0x00, 0x6A, 0x6C, 0x00, 0x6A, 0x08, 0x52, 0x00, 0x5C, 0x18, 0x08, 0x28, 0x3F, 0xFB, 0x18, 0x75, 0xB0, 0x02, 0x74, 0x7A, 0xFE, 0xB0, 0x05, 0x7A, 0xFD, 0xA0, 0x0F, 0x3C, 0xFB, 0x00, 0x37, 0x48 + } + }, + { + 92, + 79, + 0x39, + 0x0087, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x87, 0xBF, 0xEB, 0x08, 0x5D, 0xD5, 0x74, 0x60, 0xD5, 0x18, 0x8F, 0xE2, 0x62, 0xD0, 0x00, 0x62, 0xD5, 0x00, 0x7E, 0x70, 0xBF, 0x62, 0xD0, 0x00, 0x47, 0x36, 0x40, 0xB0, 0x0F, 0x47, 0x36, 0x80, 0xA0, 0x0A, 0x26, 0x36, 0x3F, 0x51, 0x36, 0x3A, 0x0E, 0xA0, 0x01, 0x70, 0xBF, 0x51, 0x0E, 0xA1, 0x1A, 0x55, 0xBE, 0x00, 0x3C, 0x0E, 0x02, 0xC0, 0x04, 0x55, 0xBE, 0x01, 0x5F, 0x36, 0x0E, 0x62, 0xD4, 0xE5, 0xA5 + } + }, + { + 93, + 79, + 0x39, + 0x0088, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x88, 0x02, 0x62, 0xD5, 0x01, 0x55, 0x0E, 0x00, 0x55, 0x35, 0x99, 0x55, 0x34, 0xE0, 0x3E, 0x35, 0x53, 0x45, 0x3E, 0x35, 0x53, 0x44, 0x3E, 0x35, 0x53, 0x32, 0x3C, 0x45, 0x02, 0xC0, 0x94, 0x51, 0x4D, 0x11, 0x03, 0x3A, 0x45, 0xC0, 0x8C, 0x3C, 0x44, 0x02, 0xC0, 0x87, 0x51, 0x4C, 0x11, 0x03, 0x3A, 0x44, 0xC0, 0x7F, 0x62, 0xD3, 0x02, 0x58, 0x32, 0x52, 0xFE, 0x53, 0x23, 0x52, 0xFF, 0x53, 0x24, 0x10, 0xFC + } + }, + { + 94, + 79, + 0x39, + 0x0089, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x89, 0x52, 0x00, 0x53, 0x25, 0x52, 0x01, 0x53, 0x26, 0x52, 0x02, 0x53, 0x27, 0x5B, 0x12, 0x4C, 0x5C, 0x52, 0xFE, 0x53, 0x1E, 0x52, 0xFF, 0x53, 0x1F, 0x52, 0x00, 0x53, 0x20, 0x52, 0x01, 0x53, 0x21, 0x52, 0x02, 0x53, 0x22, 0x5B, 0x12, 0x4C, 0x5C, 0x52, 0xFE, 0x53, 0x19, 0x52, 0xFF, 0x53, 0x1A, 0x52, 0x00, 0x53, 0x1B, 0x52, 0x01, 0x53, 0x1C, 0x52, 0x02, 0x53, 0x1D, 0x51, 0x32, 0x02, 0x4C, 0xF8, 0xCD + } + }, + { + 95, + 79, + 0x39, + 0x008A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x8A, 0x5C, 0x52, 0xFE, 0x53, 0x28, 0x52, 0xFF, 0x53, 0x29, 0x52, 0x00, 0x53, 0x2A, 0x52, 0x01, 0x53, 0x2B, 0x52, 0x02, 0x53, 0x2C, 0x5B, 0x02, 0x4C, 0x5C, 0x52, 0xFE, 0x53, 0x2D, 0x52, 0xFF, 0x53, 0x2E, 0x52, 0x00, 0x53, 0x2F, 0x52, 0x01, 0x53, 0x30, 0x52, 0x02, 0x53, 0x31, 0x90, 0x62, 0x80, 0x44, 0x7C, 0x25, 0x70, 0x90, 0x5B, 0x55, 0xBD, 0x00, 0x51, 0x45, 0xA0, 0x18, 0x51, 0x4D, 0x78, 0xB8, 0x4E + } + }, + { + 96, + 79, + 0x39, + 0x008B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x8B, 0x3A, 0x45, 0xA0, 0x11, 0x51, 0x44, 0xA0, 0x1A, 0x51, 0x4C, 0x78, 0x3A, 0x44, 0xA0, 0x13, 0x7C, 0x26, 0x0E, 0x80, 0x21, 0x51, 0x44, 0xA0, 0x17, 0x51, 0x4C, 0x78, 0x3A, 0x44, 0xA0, 0x10, 0x80, 0x11, 0x51, 0x45, 0xA0, 0x0A, 0x51, 0x4D, 0x78, 0x3A, 0x45, 0xA0, 0x03, 0x80, 0x04, 0x55, 0xBD, 0x01, 0x7C, 0x26, 0x94, 0x51, 0x0E, 0x3A, 0x43, 0xC0, 0x05, 0x50, 0xFF, 0x80, 0x0C, 0x7C, 0x23, 0x96, 0x0B + } + }, + { + 97, + 79, + 0x39, + 0x008C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x8C, 0x48, 0x7A, 0x36, 0x51, 0x36, 0xBF, 0x07, 0x51, 0x0E, 0x55, 0x36, 0x00, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x55, 0x13, 0x00, 0x51, 0x1F, 0x02, 0x20, 0x0E, 0x13, 0x00, 0x02, 0x21, 0x0E, 0x13, 0x00, 0x02, 0x24, 0x0E, 0x13, 0x00, 0x02, 0x25, 0x0E, 0x13, 0x00, 0x02, 0x26, 0x0E, 0x13, 0x00, 0x02, 0x29, 0x0E, 0x13, 0x00, 0x02, 0x2A, 0x0E, 0x13, 0x00, 0x02, 0x2B, 0x0E, 0x13, 0x00, 0x3C, 0x13, 0xFB, 0xD6 + } + }, + { + 98, + 79, + 0x39, + 0x008D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x8D, 0x00, 0xA0, 0x03, 0x50, 0xFF, 0x53, 0xBC, 0x7F, 0x3C, 0xBE, 0x01, 0xB0, 0x23, 0x50, 0x00, 0x53, 0x19, 0x53, 0x1A, 0x53, 0x1B, 0x53, 0x1C, 0x53, 0x1D, 0x53, 0x1E, 0x53, 0x22, 0x53, 0x23, 0x53, 0x27, 0x53, 0x28, 0x53, 0x2C, 0x53, 0x2D, 0x53, 0x2E, 0x53, 0x2F, 0x53, 0x30, 0x53, 0x31, 0x62, 0xD5, 0x01, 0x06, 0x34, 0x03, 0x50, 0x00, 0x53, 0x0F, 0x53, 0x10, 0x53, 0x12, 0x53, 0x13, 0x62, 0xD5, 0x8B + } + }, + { + 99, + 79, + 0x39, + 0x008E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x8E, 0xD3, 0x00, 0x10, 0x51, 0x31, 0x57, 0x17, 0x03, 0x19, 0x0E, 0x13, 0x00, 0x79, 0xDF, 0xF9, 0x53, 0x14, 0x20, 0x51, 0xBC, 0x80, 0x08, 0x3C, 0x13, 0x00, 0xA0, 0x03, 0x50, 0xFF, 0x3F, 0x34, 0x51, 0x31, 0x02, 0x2C, 0x0E, 0x10, 0x00, 0x02, 0x27, 0x0E, 0x10, 0x00, 0x02, 0x22, 0x0E, 0x10, 0x00, 0x02, 0x1D, 0x0E, 0x10, 0x00, 0x12, 0x19, 0x1E, 0x10, 0x00, 0x12, 0x1E, 0x1E, 0x10, 0x00, 0x12, 0x8E, 0xFE + } + }, + { + 100, + 79, + 0x39, + 0x008F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x8F, 0x23, 0x1E, 0x10, 0x00, 0x12, 0x28, 0x1E, 0x10, 0x00, 0x12, 0x2D, 0x1E, 0x10, 0x00, 0x64, 0x6B, 0x10, 0x02, 0x30, 0x0E, 0x10, 0x00, 0x02, 0x2B, 0x0E, 0x10, 0x00, 0x02, 0x26, 0x0E, 0x10, 0x00, 0x02, 0x21, 0x0E, 0x10, 0x00, 0x02, 0x1C, 0x0E, 0x10, 0x00, 0x12, 0x1A, 0x1E, 0x10, 0x00, 0x12, 0x1F, 0x1E, 0x10, 0x00, 0x12, 0x24, 0x1E, 0x10, 0x00, 0x12, 0x29, 0x1E, 0x10, 0x00, 0x12, 0x2E, 0x29, 0x35 + } + }, + { + 101, + 79, + 0x39, + 0x0090, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x90, 0x1E, 0x10, 0x00, 0x53, 0x11, 0x7C, 0x11, 0x4D, 0x51, 0x44, 0x06, 0x12, 0x80, 0x0C, 0x11, 0x0E, 0x10, 0x00, 0x47, 0x10, 0x80, 0xA0, 0x0A, 0x55, 0x10, 0x00, 0x55, 0x11, 0x00, 0x55, 0x12, 0x00, 0x7C, 0x12, 0x26, 0x47, 0x42, 0x08, 0xA0, 0x36, 0x62, 0xD3, 0x01, 0x4D, 0x34, 0x51, 0x48, 0x3B, 0x00, 0xC0, 0x1E, 0xB0, 0x09, 0x51, 0x49, 0x3B, 0x01, 0xA0, 0x21, 0xC0, 0x14, 0x51, 0x48, 0x3A, 0x02, 0xE8 + } + }, + { + 102, + 79, + 0x39, + 0x0091, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x91, 0x4F, 0xB0, 0x07, 0x51, 0x49, 0x3A, 0x50, 0xA0, 0x13, 0x7A, 0x49, 0x1E, 0x48, 0x00, 0x80, 0x0C, 0x51, 0x48, 0x2A, 0x49, 0xA0, 0x06, 0x76, 0x49, 0x0E, 0x48, 0x00, 0x4D, 0x34, 0x51, 0x48, 0x3A, 0x4F, 0xC0, 0x0B, 0xB0, 0x13, 0x51, 0x49, 0x3A, 0x50, 0xC0, 0x03, 0xB0, 0x0B, 0x51, 0x48, 0x3F, 0x34, 0x51, 0x49, 0x3F, 0x34, 0x80, 0x09, 0x51, 0x4F, 0x3F, 0x34, 0x51, 0x50, 0x3F, 0x34, 0x50, 0x45, 0x6F + } + }, + { + 103, + 79, + 0x39, + 0x0092, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x92, 0x00, 0x53, 0x10, 0x53, 0x12, 0x51, 0x2D, 0x02, 0x2E, 0x0E, 0x10, 0x00, 0x02, 0x2F, 0x0E, 0x10, 0x00, 0x02, 0x30, 0x0E, 0x10, 0x00, 0x02, 0x31, 0x0E, 0x10, 0x00, 0x12, 0x19, 0x1E, 0x10, 0x00, 0x12, 0x1A, 0x1E, 0x10, 0x00, 0x12, 0x1B, 0x1E, 0x10, 0x00, 0x12, 0x1C, 0x1E, 0x10, 0x00, 0x12, 0x1D, 0x1E, 0x10, 0x00, 0x64, 0x6B, 0x10, 0x02, 0x28, 0x0E, 0x10, 0x00, 0x02, 0x29, 0x0E, 0x10, 0xBB, 0x5C + } + }, + { + 104, + 79, + 0x39, + 0x0093, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x93, 0x00, 0x02, 0x2A, 0x0E, 0x10, 0x00, 0x02, 0x2B, 0x0E, 0x10, 0x00, 0x02, 0x2C, 0x0E, 0x10, 0x00, 0x12, 0x1E, 0x1E, 0x10, 0x00, 0x12, 0x1F, 0x1E, 0x10, 0x00, 0x12, 0x20, 0x1E, 0x10, 0x00, 0x12, 0x21, 0x1E, 0x10, 0x00, 0x12, 0x22, 0x1E, 0x10, 0x00, 0x53, 0x11, 0x7C, 0x11, 0x4D, 0x51, 0x45, 0x06, 0x12, 0x80, 0x0C, 0x11, 0x0E, 0x10, 0x00, 0x47, 0x10, 0x80, 0xA0, 0x0A, 0x55, 0x10, 0x00, 0x4E, 0x83 + } + }, + { + 105, + 79, + 0x39, + 0x0094, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x94, 0x55, 0x11, 0x00, 0x55, 0x12, 0x00, 0x7C, 0x11, 0xB3, 0x47, 0x42, 0x08, 0xA0, 0x36, 0x62, 0xD3, 0x01, 0x4D, 0x34, 0x51, 0x48, 0x3B, 0x00, 0xC0, 0x1E, 0xB0, 0x09, 0x51, 0x49, 0x3B, 0x01, 0xA0, 0x21, 0xC0, 0x14, 0x51, 0x48, 0x3A, 0x51, 0xB0, 0x07, 0x51, 0x49, 0x3A, 0x52, 0xA0, 0x13, 0x7A, 0x49, 0x1E, 0x48, 0x00, 0x80, 0x0C, 0x51, 0x48, 0x2A, 0x49, 0xA0, 0x06, 0x76, 0x49, 0x0E, 0x48, 0x31, 0x4A + } + }, + { + 106, + 79, + 0x39, + 0x0095, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x95, 0x00, 0x4D, 0x34, 0x51, 0x48, 0x3A, 0x51, 0xC0, 0x0B, 0xB0, 0x13, 0x51, 0x49, 0x3A, 0x52, 0xC0, 0x03, 0xB0, 0x0B, 0x51, 0x48, 0x3F, 0x34, 0x51, 0x49, 0x3F, 0x34, 0x80, 0x09, 0x51, 0x51, 0x3F, 0x34, 0x51, 0x52, 0x3F, 0x34, 0x62, 0xD3, 0x02, 0x76, 0x0E, 0x51, 0x0E, 0x55, 0xBC, 0x00, 0x7F, 0x55, 0x12, 0x00, 0x5F, 0x11, 0x45, 0x06, 0x11, 0xFE, 0x5F, 0x10, 0x44, 0x06, 0x10, 0xFE, 0x51, 0x97, 0x17 + } + }, + { + 107, + 79, + 0x39, + 0x0096, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x96, 0x32, 0x08, 0x51, 0x4C, 0x14, 0x32, 0x14, 0x32, 0x16, 0x32, 0x02, 0x55, 0x0F, 0x06, 0x7A, 0x0F, 0x51, 0x0F, 0xA0, 0x74, 0x47, 0x11, 0x80, 0xB0, 0x44, 0x51, 0x4D, 0x78, 0x3A, 0x11, 0xC0, 0x3D, 0x55, 0x13, 0x06, 0x7A, 0x13, 0x51, 0x13, 0xA0, 0x4F, 0x47, 0x10, 0x80, 0xB0, 0x1E, 0x51, 0x4C, 0x78, 0x3A, 0x10, 0xC0, 0x17, 0x58, 0x32, 0x62, 0xD3, 0x02, 0x52, 0x00, 0x58, 0x12, 0x62, 0xD3, 0x19, 0x1C + } + }, + { + 108, + 79, + 0x39, + 0x0097, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x97, 0x00, 0x54, 0x19, 0x76, 0x12, 0x76, 0x10, 0x76, 0x32, 0x8F, 0xD9, 0x58, 0x12, 0x62, 0xD3, 0x00, 0x56, 0x19, 0x00, 0x75, 0x5A, 0x12, 0x76, 0x10, 0x76, 0x32, 0x8F, 0xC8, 0x58, 0x12, 0x62, 0xD3, 0x00, 0x50, 0x00, 0x54, 0x19, 0x75, 0x54, 0x19, 0x75, 0x54, 0x19, 0x75, 0x54, 0x19, 0x75, 0x54, 0x19, 0x75, 0x5A, 0x12, 0x06, 0x32, 0x05, 0x76, 0x11, 0x5F, 0x10, 0x44, 0x06, 0x10, 0xFE, 0x51, 0xA0, 0x2B + } + }, + { + 109, + 79, + 0x39, + 0x0098, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x98, 0x4C, 0x11, 0x05, 0x04, 0x32, 0x8F, 0x88, 0x62, 0xD3, 0x02, 0x18, 0x53, 0x32, 0x7F, 0x62, 0xD3, 0x00, 0x3C, 0x45, 0x01, 0xB0, 0x1B, 0x57, 0x05, 0x52, 0x19, 0x6D, 0x6D, 0x6D, 0x21, 0x1F, 0x53, 0x0F, 0x6D, 0x21, 0x7F, 0x02, 0x0F, 0x54, 0x14, 0x75, 0x5B, 0x39, 0x0A, 0xBF, 0xEB, 0x80, 0x21, 0x51, 0x4D, 0x11, 0x02, 0x3A, 0x45, 0xB0, 0x19, 0x57, 0x0F, 0x52, 0x19, 0x6D, 0x6D, 0x6D, 0x21, 0x4A, 0x80 + } + }, + { + 110, + 79, + 0x39, + 0x0099, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x99, 0x1F, 0x53, 0x0F, 0x6D, 0x21, 0x7F, 0x02, 0x0F, 0x54, 0x1E, 0x75, 0x5B, 0x39, 0x14, 0xBF, 0xEB, 0x3C, 0x44, 0x01, 0xB0, 0x1D, 0x57, 0x01, 0x52, 0x19, 0x6D, 0x6D, 0x6D, 0x21, 0x1F, 0x53, 0x0F, 0x6D, 0x21, 0x7F, 0x02, 0x0F, 0x54, 0x18, 0x5B, 0x01, 0x05, 0x5C, 0x39, 0x15, 0xBF, 0xE9, 0x80, 0x23, 0x51, 0x4C, 0x11, 0x02, 0x3A, 0x44, 0xB0, 0x1B, 0x57, 0x03, 0x52, 0x19, 0x6D, 0x6D, 0x6D, 0xB7, 0x5B + } + }, + { + 111, + 79, + 0x39, + 0x009A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x9A, 0x21, 0x1F, 0x53, 0x0F, 0x6D, 0x21, 0x7F, 0x02, 0x0F, 0x54, 0x1A, 0x5B, 0x01, 0x05, 0x5C, 0x39, 0x17, 0xBF, 0xE9, 0x7F, 0x62, 0xD3, 0x00, 0x51, 0x45, 0xB0, 0x94, 0x55, 0x19, 0x04, 0x55, 0x1A, 0x10, 0x55, 0x1B, 0x10, 0x55, 0x1C, 0x10, 0x55, 0x1D, 0x04, 0x51, 0xBD, 0xB0, 0x23, 0x51, 0x25, 0xA0, 0x0D, 0x51, 0x24, 0x5F, 0x12, 0x26, 0x5F, 0x14, 0x25, 0x92, 0x47, 0x53, 0x1B, 0x51, 0x2A, 0x43, 0x74 + } + }, + { + 112, + 79, + 0x39, + 0x009B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x9B, 0xA0, 0x0F, 0x51, 0x29, 0x5F, 0x12, 0x2B, 0x5F, 0x14, 0x2A, 0x92, 0x37, 0x53, 0x1A, 0x53, 0x1C, 0x57, 0x04, 0x52, 0x23, 0x53, 0x4A, 0x52, 0x19, 0x53, 0x4B, 0x7C, 0x29, 0x2D, 0x50, 0x04, 0x6E, 0x4A, 0x6E, 0x4B, 0x78, 0xBF, 0xFA, 0x52, 0x28, 0x14, 0x4B, 0x1E, 0x4A, 0x00, 0x47, 0x4A, 0x80, 0xA0, 0x07, 0x55, 0x4A, 0x00, 0x55, 0x4B, 0x00, 0x47, 0x4A, 0x7F, 0xA0, 0x04, 0x55, 0x4B, 0xFF, 0xD0, 0x8F + } + }, + { + 113, + 79, + 0x39, + 0x009C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x9C, 0x51, 0x4B, 0x3C, 0xAD, 0x02, 0xA0, 0x0D, 0x3C, 0xAD, 0x03, 0xA0, 0x03, 0x80, 0x10, 0x6D, 0x21, 0x7F, 0x80, 0x0B, 0x6D, 0x21, 0x7F, 0x53, 0x4B, 0x6D, 0x21, 0x7F, 0x02, 0x4B, 0x54, 0x1E, 0x6D, 0x6D, 0x6D, 0x21, 0x1F, 0x54, 0x19, 0x6D, 0x21, 0x7F, 0x05, 0x19, 0x79, 0xDF, 0xA5, 0x51, 0x4D, 0x11, 0x01, 0x3A, 0x45, 0xB0, 0x94, 0x55, 0x2D, 0x04, 0x55, 0x2E, 0x10, 0x55, 0x2F, 0x10, 0x55, 0xF3, 0xD6 + } + }, + { + 114, + 79, + 0x39, + 0x009D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x9D, 0x30, 0x10, 0x55, 0x31, 0x04, 0x51, 0xBD, 0xB0, 0x23, 0x51, 0x25, 0xA0, 0x0D, 0x51, 0x24, 0x5F, 0x12, 0x26, 0x5F, 0x14, 0x25, 0x91, 0xAC, 0x53, 0x2F, 0x51, 0x20, 0xA0, 0x0F, 0x51, 0x1F, 0x5F, 0x12, 0x21, 0x5F, 0x14, 0x20, 0x91, 0x9C, 0x53, 0x2E, 0x53, 0x30, 0x57, 0x04, 0x52, 0x23, 0x53, 0x4A, 0x52, 0x2D, 0x53, 0x4B, 0x7C, 0x29, 0x2D, 0x50, 0x04, 0x6E, 0x4A, 0x6E, 0x4B, 0x78, 0xBF, 0x6F, 0xCF + } + }, + { + 115, + 79, + 0x39, + 0x009E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x9E, 0xFA, 0x52, 0x1E, 0x14, 0x4B, 0x1E, 0x4A, 0x00, 0x47, 0x4A, 0x80, 0xA0, 0x07, 0x55, 0x4A, 0x00, 0x55, 0x4B, 0x00, 0x47, 0x4A, 0x7F, 0xA0, 0x04, 0x55, 0x4B, 0xFF, 0x51, 0x4B, 0x3C, 0xAD, 0x02, 0xA0, 0x0D, 0x3C, 0xAD, 0x03, 0xA0, 0x03, 0x80, 0x10, 0x6D, 0x21, 0x7F, 0x80, 0x0B, 0x6D, 0x21, 0x7F, 0x53, 0x4B, 0x6D, 0x21, 0x7F, 0x02, 0x4B, 0x54, 0x28, 0x6D, 0x6D, 0x6D, 0x21, 0x1F, 0x54, 0xC2, 0x76 + } + }, + { + 116, + 79, + 0x39, + 0x009F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x9F, 0x2D, 0x6D, 0x21, 0x7F, 0x05, 0x2D, 0x79, 0xDF, 0xA5, 0x3C, 0x44, 0x00, 0xB0, 0x97, 0x55, 0x19, 0x04, 0x55, 0x1E, 0x10, 0x55, 0x23, 0x10, 0x55, 0x28, 0x10, 0x55, 0x2D, 0x04, 0x51, 0xBD, 0xB0, 0x23, 0x51, 0x25, 0xA0, 0x0D, 0x51, 0x20, 0x5F, 0x12, 0x2A, 0x5F, 0x14, 0x25, 0x91, 0x14, 0x53, 0x23, 0x51, 0x26, 0xA0, 0x0F, 0x51, 0x21, 0x5F, 0x12, 0x2B, 0x5F, 0x14, 0x26, 0x91, 0x04, 0x53, 0x38, 0x63 + } + }, + { + 117, + 79, + 0x39, + 0x00A0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA0, 0x1E, 0x53, 0x28, 0x57, 0x14, 0x52, 0x1B, 0x53, 0x4A, 0x52, 0x19, 0x53, 0x4B, 0x7C, 0x29, 0x2D, 0x50, 0x04, 0x6E, 0x4A, 0x6E, 0x4B, 0x78, 0xBF, 0xFA, 0x52, 0x1C, 0x14, 0x4B, 0x1E, 0x4A, 0x00, 0x47, 0x4A, 0x80, 0xA0, 0x07, 0x55, 0x4A, 0x00, 0x55, 0x4B, 0x00, 0x47, 0x4A, 0x7F, 0xA0, 0x04, 0x55, 0x4B, 0xFF, 0x51, 0x4B, 0x3C, 0xAD, 0x02, 0xA0, 0x0D, 0x3C, 0xAD, 0x03, 0xA0, 0x03, 0x80, 0xA1, 0x36 + } + }, + { + 118, + 79, + 0x39, + 0x00A1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA1, 0x10, 0x6D, 0x21, 0x7F, 0x80, 0x0B, 0x6D, 0x21, 0x7F, 0x53, 0x4B, 0x6D, 0x21, 0x7F, 0x02, 0x4B, 0x54, 0x1A, 0x6D, 0x6D, 0x6D, 0x21, 0x1F, 0x54, 0x19, 0x6D, 0x21, 0x7F, 0x05, 0x19, 0x5B, 0x11, 0x05, 0x5C, 0xDF, 0xA2, 0x51, 0x4C, 0x11, 0x01, 0x3A, 0x44, 0xB0, 0x97, 0x55, 0x1D, 0x04, 0x55, 0x22, 0x10, 0x55, 0x27, 0x10, 0x55, 0x2C, 0x10, 0x55, 0x31, 0x04, 0x51, 0xBD, 0xB0, 0x23, 0x51, 0xD2, 0x99 + } + }, + { + 119, + 79, + 0x39, + 0x00A2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA2, 0x25, 0xA0, 0x0D, 0x51, 0x20, 0x5F, 0x12, 0x2A, 0x5F, 0x14, 0x25, 0x90, 0x76, 0x53, 0x27, 0x51, 0x24, 0xA0, 0x0F, 0x51, 0x1F, 0x5F, 0x12, 0x29, 0x5F, 0x14, 0x24, 0x90, 0x66, 0x53, 0x22, 0x53, 0x2C, 0x57, 0x14, 0x52, 0x1B, 0x53, 0x4A, 0x52, 0x1D, 0x53, 0x4B, 0x7C, 0x29, 0x2D, 0x50, 0x04, 0x6E, 0x4A, 0x6E, 0x4B, 0x78, 0xBF, 0xFA, 0x52, 0x1A, 0x14, 0x4B, 0x1E, 0x4A, 0x00, 0x47, 0x4A, 0xB3, 0x5C + } + }, + { + 120, + 79, + 0x39, + 0x00A3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA3, 0x80, 0xA0, 0x07, 0x55, 0x4A, 0x00, 0x55, 0x4B, 0x00, 0x47, 0x4A, 0x7F, 0xA0, 0x04, 0x55, 0x4B, 0xFF, 0x51, 0x4B, 0x3C, 0xAD, 0x02, 0xA0, 0x0D, 0x3C, 0xAD, 0x03, 0xA0, 0x03, 0x80, 0x10, 0x6D, 0x21, 0x7F, 0x80, 0x0B, 0x6D, 0x21, 0x7F, 0x53, 0x4B, 0x6D, 0x21, 0x7F, 0x02, 0x4B, 0x54, 0x1C, 0x6D, 0x6D, 0x6D, 0x21, 0x1F, 0x54, 0x1D, 0x6D, 0x21, 0x7F, 0x05, 0x1D, 0x5B, 0x11, 0x05, 0x5C, 0x0D, 0x11 + } + }, + { + 121, + 79, + 0x39, + 0x00A4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA4, 0xDF, 0xA2, 0x7F, 0x55, 0x11, 0x00, 0x04, 0x12, 0x0E, 0x11, 0x00, 0x65, 0x12, 0x6B, 0x11, 0x65, 0x12, 0x6B, 0x11, 0x65, 0x12, 0x6B, 0x11, 0x65, 0x12, 0x6B, 0x11, 0x55, 0x10, 0x00, 0x55, 0x13, 0x00, 0x7C, 0x11, 0x4D, 0x51, 0x12, 0x39, 0x10, 0xD0, 0x03, 0x50, 0x10, 0x7F, 0x12, 0x4B, 0x55, 0x10, 0x08, 0x47, 0x4B, 0x01, 0xA0, 0x03, 0x02, 0x4A, 0x6D, 0x6E, 0x4B, 0x7A, 0x10, 0xBF, 0xF3, 0x1A, 0x2C + } + }, + { + 122, + 79, + 0x39, + 0x00A5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA5, 0x53, 0x4A, 0x7F, 0x62, 0xD0, 0x00, 0x3C, 0x0E, 0x02, 0xC0, 0x0E, 0x55, 0x36, 0x00, 0x90, 0x09, 0x47, 0x36, 0x40, 0xA0, 0x04, 0x7C, 0x14, 0x1F, 0x7F, 0x70, 0xBF, 0x62, 0xD4, 0x02, 0x62, 0xD3, 0x02, 0x50, 0x00, 0x53, 0x32, 0x53, 0x35, 0x53, 0x44, 0x53, 0x45, 0x55, 0x34, 0x99, 0x3E, 0x34, 0x53, 0x19, 0x3E, 0x34, 0x53, 0x1A, 0x3E, 0x34, 0x53, 0x1B, 0x76, 0x45, 0x51, 0x45, 0x3A, 0x0E, 0x9D, 0x33 + } + }, + { + 123, + 79, + 0x39, + 0x00A6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA6, 0xD1, 0x1F, 0x3E, 0x34, 0x53, 0x1C, 0x3E, 0x34, 0x53, 0x1D, 0x3E, 0x34, 0x53, 0x1E, 0x51, 0x19, 0x12, 0x1C, 0xD0, 0x03, 0x73, 0x74, 0x53, 0x1F, 0x51, 0x1A, 0x12, 0x1D, 0xD0, 0x03, 0x73, 0x74, 0x53, 0x20, 0x51, 0x1F, 0xA0, 0x07, 0x39, 0x02, 0xA0, 0x03, 0x80, 0x2D, 0x51, 0x20, 0xA0, 0x05, 0x39, 0x02, 0xB0, 0x25, 0x51, 0x19, 0x3A, 0x1C, 0xC0, 0x0B, 0xA0, 0x11, 0x51, 0x1B, 0x12, 0x4C, 0xEF, 0xD8 + } + }, + { + 124, + 79, + 0x39, + 0x00A7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA7, 0x53, 0x21, 0x80, 0x0C, 0x51, 0x1B, 0x02, 0x4C, 0x53, 0x21, 0x80, 0x04, 0x5F, 0x21, 0x1B, 0x51, 0x1A, 0x12, 0x1D, 0x67, 0x14, 0x21, 0x80, 0x8D, 0x3C, 0x1F, 0x02, 0xB0, 0x41, 0x3C, 0x20, 0x01, 0xB0, 0x3C, 0x51, 0x19, 0x3A, 0x1C, 0xC0, 0x0B, 0xA0, 0x11, 0x51, 0x1B, 0x12, 0x4C, 0x53, 0x21, 0x80, 0x0C, 0x51, 0x1B, 0x02, 0x4C, 0x53, 0x21, 0x80, 0x04, 0x5F, 0x21, 0x1B, 0x51, 0x1A, 0x3A, 0x1F, 0x39 + } + }, + { + 125, + 79, + 0x39, + 0x00A8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA8, 0x1D, 0xC0, 0x0E, 0x58, 0x21, 0x52, 0x00, 0x79, 0x3B, 0x00, 0xD0, 0x10, 0x7A, 0x21, 0x80, 0x0C, 0x58, 0x21, 0x52, 0x00, 0x75, 0x3B, 0x00, 0xD0, 0x03, 0x76, 0x21, 0x80, 0x48, 0x3C, 0x1F, 0x01, 0xB0, 0x41, 0x3C, 0x20, 0x02, 0xB0, 0x3C, 0x51, 0x1A, 0x3A, 0x1D, 0xC0, 0x08, 0x51, 0x1B, 0x78, 0x53, 0x21, 0x80, 0x06, 0x51, 0x1B, 0x74, 0x53, 0x21, 0x51, 0x19, 0x3A, 0x1C, 0xC0, 0x0B, 0xA0, 0x9C, 0x34 + } + }, + { + 126, + 79, + 0x39, + 0x00A9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xA9, 0x11, 0x51, 0x21, 0x12, 0x4C, 0x53, 0x1F, 0x80, 0x0C, 0x51, 0x21, 0x02, 0x4C, 0x53, 0x1F, 0x80, 0x04, 0x55, 0x1F, 0x00, 0x58, 0x21, 0x52, 0x00, 0x58, 0x1F, 0x3B, 0x00, 0xD0, 0x03, 0x5A, 0x21, 0x80, 0x03, 0x8F, 0x17, 0x58, 0x1B, 0x52, 0x00, 0x58, 0x21, 0x13, 0x00, 0xCF, 0x0D, 0x3A, 0x18, 0xDF, 0x09, 0x58, 0x1E, 0x52, 0x00, 0x58, 0x21, 0x13, 0x00, 0xCE, 0xFF, 0x3A, 0x18, 0xDE, 0xFB, 0xB0, 0x5D + } + }, + { + 127, + 79, + 0x39, + 0x00AA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xAA, 0x58, 0x1B, 0x52, 0x00, 0x58, 0x1E, 0x3B, 0x00, 0xD0, 0x0A, 0x52, 0x00, 0x01, 0x01, 0x55, 0x36, 0x40, 0x80, 0x06, 0x01, 0x01, 0x55, 0x36, 0x40, 0x58, 0x21, 0x54, 0x00, 0x76, 0x32, 0x8E, 0xDB, 0x76, 0x44, 0x5F, 0x45, 0x44, 0x06, 0x35, 0x03, 0x51, 0x35, 0x55, 0x34, 0x99, 0x04, 0x34, 0x51, 0x44, 0x3A, 0x0E, 0xCE, 0xBA, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x62, 0xD0, 0x00, 0x70, 0xCF, 0x71, 0x06, 0x0A + } + }, + { + 128, + 79, + 0x39, + 0x00AB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xAB, 0x20, 0x51, 0x9A, 0x60, 0xA0, 0x51, 0x9C, 0x60, 0xA2, 0x51, 0x9B, 0x60, 0xA1, 0x51, 0x9E, 0x60, 0xC7, 0x51, 0x9D, 0x60, 0xA4, 0x70, 0xCF, 0x7F, 0x62, 0xD0, 0x00, 0x62, 0xD5, 0x00, 0x62, 0xD4, 0x00, 0x62, 0xD3, 0x00, 0x55, 0xFA, 0x00, 0x50, 0x06, 0x55, 0xF8, 0x3A, 0x7C, 0x00, 0x60, 0x3C, 0xF8, 0x05, 0xB0, 0x12, 0x70, 0xCF, 0x71, 0x20, 0x62, 0xA6, 0x00, 0x71, 0x30, 0x62, 0x1B, 0x30, 0xAA, 0x53 + } + }, + { + 129, + 79, + 0x39, + 0x00AC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xAC, 0x43, 0x1B, 0x40, 0x70, 0xCF, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x62, 0xD0, 0x03, 0x51, 0xE1, 0x54, 0x01, 0x51, 0xE0, 0x54, 0x00, 0x38, 0xFE, 0x20, 0x7F, 0x7F, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x62, 0xD0, 0x00, 0x26, 0xAF, 0xFD, 0x7C, 0x72, 0x51, 0x26, 0xAE, 0xFB, 0x51, 0xAE, 0x60, 0x00, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0xEF, 0xDE + } + }, + { + 130, + 79, + 0x39, + 0x00AD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xAD, 0x10, 0x4F, 0x20, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x10, 0x4F, 0x62, 0xD0, 0x00, 0x51, 0xAF, 0x29, 0x02, 0x7C, 0x6F, 0x20, 0x62, 0xD0, 0x00, 0x51, 0xAE, 0x29, 0x04, 0x53, 0xAE, 0x51, 0xAE, 0x60, 0x00, 0x20, 0x7F, 0x7F, 0x7F, 0x08, 0x62, 0xD0, 0x00, 0x55, 0xFA, 0x00, 0x62, 0xD5, 0x00, 0x62, 0xD4, 0x00, 0x4F, 0x5B, 0x01, 0x03, 0x53, 0xF9, 0x55, 0xF8, 0x3A, 0x50, 0xE3, 0xC7 + } + }, + { + 131, + 79, + 0x39, + 0x00AE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xAE, 0x06, 0x00, 0x20, 0x70, 0xBF, 0x62, 0xD3, 0x00, 0x52, 0xF8, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x62, 0xD0, 0x00, 0x55, 0xFA, 0x00, 0x62, 0xD5, 0x00, 0x62, 0xD4, 0x00, 0x4F, 0x5B, 0x01, 0x03, 0x53, 0xF9, 0x55, 0xF8, 0x3A, 0x50, 0x06, 0x00, 0x7F, 0x11, 0x04, 0x4B, 0xD0, 0x04, 0x78, 0xC0, 0x09, 0x3A, 0x80, 0x40, 0x79, 0x19, 0x00, 0xDF, 0xF9, 0x7F, 0x71, 0x40, 0xA0, 0x05, 0x70, 0xCF, 0x71, 0xD5, 0xAC + } + }, + { + 132, + 79, + 0x39, + 0x00AF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xAF, 0x10, 0x5E, 0x00, 0x70, 0xCF, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x56, 0x00, 0x00, 0x80, 0x13, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xC0, 0x7C, 0x6F, 0x54, 0x52, 0x00, 0x3F, 0xE8, 0x77, 0x00, 0x3D, 0x00, 0x04, 0xCF, 0xEA, 0x62, 0xD0, 0x04, 0x55, 0xB6, 0x00, 0x62, 0xD0, 0x04, 0x55, 0xB5, 0x00, 0x7C, 0x73, 0x74, 0x38, 0xFF, 0x20, 0x7F, 0x7F, 0x10, 0x4F, 0xBC, 0x7B + } + }, + { + 133, + 79, + 0x39, + 0x00B0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB0, 0x38, 0x01, 0x10, 0x7C, 0x11, 0x47, 0x62, 0xD0, 0x00, 0x20, 0x54, 0x00, 0x50, 0x0F, 0x08, 0x10, 0x7C, 0x2B, 0x38, 0x38, 0xFE, 0x52, 0x00, 0x62, 0xD0, 0x00, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x56, 0x01, 0x00, 0x9F, 0xD7, 0x62, 0xD0, 0x00, 0x54, 0x00, 0x52, 0x00, 0x08, 0x7C, 0x47, 0x34, 0x38, 0xFF, 0x52, 0x00, 0x08, 0x90, 0x46, 0x52, 0x00, 0x08, 0x62, 0xD0, 0x04, 0x51, 0x2E, 0x60 + } + }, + { + 134, + 79, + 0x39, + 0x00B1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB1, 0xB5, 0x08, 0x7C, 0x3A, 0x9B, 0x38, 0xFD, 0x62, 0xD0, 0x00, 0x54, 0x01, 0x5A, 0xE8, 0x06, 0xE8, 0x01, 0x50, 0x0F, 0x08, 0x51, 0xE8, 0x08, 0x52, 0x00, 0x08, 0x91, 0x47, 0x62, 0xD0, 0x00, 0x5A, 0xE8, 0x06, 0xE8, 0x01, 0x50, 0x0F, 0x08, 0x51, 0xE8, 0x08, 0x7C, 0x2B, 0x3C, 0x38, 0xFB, 0x52, 0x00, 0x62, 0xD0, 0x04, 0x53, 0xB5, 0x52, 0x01, 0x62, 0xD0, 0x00, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0xF0, 0xE5 + } + }, + { + 135, + 79, + 0x39, + 0x00B2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB2, 0x4F, 0x38, 0x06, 0x50, 0x04, 0x3B, 0xFC, 0xD0, 0x04, 0x56, 0xFC, 0x04, 0x56, 0x05, 0x00, 0x56, 0x04, 0x00, 0x80, 0x67, 0x56, 0x02, 0xE0, 0x56, 0x01, 0x01, 0x56, 0x00, 0x00, 0x80, 0x23, 0x7C, 0x6F, 0x4C, 0x52, 0x01, 0x7C, 0x70, 0xCD, 0x7C, 0x6F, 0x44, 0x06, 0xE8, 0xC4, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x3B, 0x03, 0xB0, 0x03, 0x80, 0x0F, 0x07, 0x02, 0x08, 0x0F, 0x01, 0x00, 0x77, 0x09, 0x18 + } + }, + { + 136, + 79, + 0x39, + 0x00B3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB3, 0x00, 0x52, 0x00, 0x3B, 0xFC, 0xCF, 0xD9, 0x52, 0x00, 0x3B, 0xFC, 0xA0, 0x2C, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x18, 0x06, 0xE8, 0xC0, 0x7C, 0x6F, 0x54, 0x52, 0x00, 0x3F, 0xE8, 0x7C, 0x6F, 0x18, 0x06, 0xE8, 0xC4, 0x7C, 0x6F, 0x54, 0x52, 0x03, 0x3F, 0xE8, 0x52, 0x02, 0x53, 0xE8, 0x52, 0x01, 0x60, 0xD5, 0x50, 0xFF, 0x3F, 0xE8, 0x77, 0x05, 0x77, 0x04, 0x62, 0xD0, 0x04, 0x52, 0x04, 0x3A, 0xDB, 0xBD + } + }, + { + 137, + 79, + 0x39, + 0x00B4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB4, 0xB6, 0xCF, 0x92, 0x52, 0x05, 0x62, 0xD0, 0x04, 0x53, 0xB6, 0x3D, 0x05, 0x04, 0xD0, 0x55, 0x56, 0x02, 0xE0, 0x56, 0x01, 0x01, 0x56, 0x00, 0x00, 0x80, 0x44, 0x7C, 0x6F, 0x4C, 0x52, 0x01, 0x7C, 0x70, 0xCD, 0x3D, 0x03, 0xFF, 0xA0, 0x2F, 0x62, 0xD0, 0x04, 0x51, 0xB6, 0x7C, 0x70, 0x0E, 0x06, 0xE8, 0xC0, 0x7C, 0x6F, 0x54, 0x52, 0x00, 0x7C, 0x72, 0xE6, 0x7C, 0x70, 0x0E, 0x06, 0xE8, 0xC4, 0x09, 0x1A + } + }, + { + 138, + 79, + 0x39, + 0x00B5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB5, 0x7C, 0x6F, 0x54, 0x52, 0x03, 0x7C, 0x72, 0xE6, 0x01, 0x01, 0x53, 0xB6, 0x62, 0xD0, 0x00, 0x39, 0x04, 0xC0, 0x03, 0x80, 0x0F, 0x07, 0x02, 0x08, 0x0F, 0x01, 0x00, 0x77, 0x00, 0x52, 0x00, 0x3B, 0xFC, 0xCF, 0xB8, 0x56, 0x04, 0x00, 0x80, 0x32, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x44, 0x06, 0xE8, 0xC4, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x7C, 0x72, 0x31, 0x06, 0xE6, 0xC0, 0x0E, 0x76, 0xF5 + } + }, + { + 139, + 79, + 0x39, + 0x00B6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB6, 0xE7, 0x03, 0x51, 0xE7, 0x60, 0xD4, 0x3E, 0xE6, 0x7C, 0x6E, 0xB6, 0x7C, 0x70, 0x1E, 0x06, 0xE6, 0xE0, 0x0E, 0xE7, 0x01, 0x7C, 0x6D, 0xEA, 0x77, 0x04, 0x52, 0x04, 0x3B, 0x05, 0xCF, 0xCA, 0x38, 0xFA, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x3D, 0xFC, 0x00, 0xB0, 0x06, 0x7C, 0x73, 0x74, 0x80, 0x28, 0x90, 0x29, 0x54, 0x00, 0x3D, 0x00, 0x00, 0xA0, 0x1F, 0x62, 0xD0, 0x04, 0x3C, 0xB4, 0x00, 0xF5, 0xF4 + } + }, + { + 140, + 79, + 0x39, + 0x00B7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB7, 0xB0, 0x10, 0x62, 0xD0, 0x00, 0x52, 0xFB, 0x53, 0xE8, 0x52, 0xFA, 0x60, 0xD5, 0x50, 0x01, 0x3F, 0xE8, 0x52, 0x00, 0x62, 0xD0, 0x04, 0x53, 0xB4, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x7C, 0x6F, 0xC9, 0x80, 0x22, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x62, 0xD0, 0x00, 0x51, 0x16, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0x04, 0x13 + } + }, + { + 141, + 79, + 0x39, + 0x00B8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB8, 0xD0, 0x03, 0x77, 0x01, 0x77, 0x00, 0x3D, 0x00, 0x99, 0xD0, 0x07, 0x50, 0x28, 0x3B, 0x01, 0xDF, 0xD5, 0x50, 0x28, 0x3B, 0x01, 0xD0, 0x08, 0x62, 0xD0, 0x00, 0x50, 0x10, 0x80, 0x06, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x7C, 0x6F, 0x2F, 0xB0, 0x0A, 0x3D, 0xFC, 0x00, 0xA0, 0x4E, 0x91, 0x78, 0x80, 0x4A, 0x3D, 0x00, 0x10, 0xB0, 0x03, 0x80, 0x43, 0xFB, 0x02 + } + }, + { + 142, + 79, + 0x39, + 0x00B9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xB9, 0x3D, 0x00, 0x20, 0xB0, 0x2B, 0x62, 0xD0, 0x04, 0x51, 0xB5, 0x21, 0x0F, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0xB4, 0x21, 0xF0, 0x62, 0xD0, 0x00, 0x2A, 0xE9, 0x62, 0xD0, 0x03, 0x53, 0x9A, 0x51, 0x9A, 0x08, 0x50, 0x02, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x80, 0x14, 0x3D, 0x00, 0x30, 0xB0, 0x05, 0x90, 0x10, 0x80, 0x0B, 0x62, 0xD0, 0x03, 0x47, 0x99, 0x40, 0xA0, 0x03, 0xDB, 0xC3 + } + }, + { + 143, + 79, + 0x39, + 0x00BA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xBA, 0x90, 0xE4, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x04, 0x62, 0xD0, 0x04, 0x51, 0xB5, 0x08, 0x50, 0x23, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x62, 0xD0, 0x04, 0x50, 0x04, 0x3A, 0xB5, 0xC0, 0xB7, 0x56, 0x03, 0x00, 0x80, 0xA9, 0x62, 0xD0, 0x00, 0x52, 0x03, 0x7C, 0x6D, 0x8A, 0x51, 0xE8, 0x01, 0xE0, 0x54, 0x02, 0x51, 0xE9, 0x09, 0x01, 0x54, 0x01, 0x52, 0x03, 0x64, 0x64, 0x64, 0x01, 0x8B, 0x24 + } + }, + { + 144, + 79, + 0x39, + 0x00BB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xBB, 0x03, 0x54, 0x00, 0x7C, 0x6F, 0xF6, 0x08, 0x52, 0x00, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x01, 0x01, 0x53, 0xE8, 0x52, 0x01, 0x09, 0x00, 0x7C, 0x6D, 0xE3, 0x3E, 0xE8, 0x53, 0xE8, 0x51, 0xE9, 0x08, 0x51, 0xE8, 0x08, 0x52, 0x00, 0x01, 0x01, 0x08, 0x7C, 0x32, 0x52, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x01, 0x03, 0x7C, 0x6E, 0xA3, 0x08, 0x52, 0x00, 0x01, 0x03, 0x2A, 0x63 + } + }, + { + 145, + 79, + 0x39, + 0x00BC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xBC, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFB, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x01, 0x04, 0x53, 0xE8, 0x52, 0x01, 0x09, 0x00, 0x7C, 0x6D, 0xE3, 0x3E, 0xE8, 0x53, 0xE8, 0x51, 0xE9, 0x08, 0x51, 0xE8, 0x08, 0x52, 0x00, 0x01, 0x04, 0x08, 0x7C, 0x32, 0x52, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x01, 0x06, 0x53, 0xE8, 0x52, 0x01, 0x09, 0x00, 0x7C, 0x6D, 0xE3, 0x3E, 0xE8, 0x53, 0xE8, 0x51, 0xE9, 0x08, 0x51, 0x0A, 0x24 + } + }, + { + 146, + 79, + 0x39, + 0x00BD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xBD, 0xE8, 0x08, 0x52, 0x00, 0x01, 0x06, 0x08, 0x7C, 0x32, 0x52, 0x38, 0xFA, 0x77, 0x03, 0x62, 0xD0, 0x04, 0x52, 0x03, 0x3A, 0xB5, 0xCF, 0x50, 0x50, 0x00, 0x08, 0x50, 0x25, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x38, 0xFC, 0x20, 0x7F, 0x62, 0xD0, 0x04, 0x51, 0xB5, 0x21, 0x0F, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0xB4, 0x21, 0xF0, 0x62, 0xD0, 0x00, 0x2A, 0xE9, 0x62, 0xD0, 0xCB, 0xA7 + } + }, + { + 147, + 79, + 0x39, + 0x00BE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xBE, 0x03, 0x53, 0x9A, 0x51, 0x9A, 0x08, 0x50, 0x02, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x62, 0xD0, 0x01, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x50, 0x03, 0x08, 0x7C, 0x32, 0x52, 0x62, 0xD0, 0x01, 0x51, 0xE4, 0x08, 0x51, 0xE5, 0x08, 0x50, 0x05, 0x08, 0x7C, 0x32, 0x52, 0x38, 0xFA, 0x7F, 0x10, 0x4F, 0x38, 0x07, 0x62, 0xD0, 0x04, 0x51, 0xB5, 0x21, 0x0F, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x62, 0xD0, 0xB2 + } + }, + { + 148, + 79, + 0x39, + 0x00BF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xBF, 0xD0, 0x04, 0x51, 0xB4, 0x21, 0xF0, 0x62, 0xD0, 0x00, 0x2A, 0xE9, 0x62, 0xD0, 0x03, 0x53, 0x9A, 0x51, 0x9A, 0x08, 0x50, 0x02, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x56, 0x00, 0x00, 0x80, 0xCA, 0x56, 0x04, 0x00, 0x62, 0xD0, 0x04, 0x52, 0x00, 0x3A, 0xB5, 0xD0, 0x12, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xC0, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x54, 0x04, 0x62, 0xD0, 0x00, 0x3F, 0x91 + } + }, + { + 149, + 79, + 0x39, + 0x00C0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC0, 0x52, 0x04, 0x7C, 0x6D, 0x8A, 0x51, 0xE8, 0x01, 0xE0, 0x54, 0x02, 0x51, 0xE9, 0x09, 0x01, 0x54, 0x01, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0x81, 0x0E, 0xE9, 0x0D, 0x7C, 0x6F, 0x5C, 0x54, 0x03, 0x52, 0x02, 0x01, 0x06, 0x53, 0xE8, 0x52, 0x01, 0x09, 0x00, 0x7C, 0x6D, 0xE3, 0x3E, 0xE8, 0x53, 0xE8, 0x51, 0xE9, 0x08, 0x51, 0xE8, 0x08, 0x52, 0x03, 0x08, 0x7C, 0x32, 0x52, 0x38, 0xFD, 0x62, 0xD0, 0xD7, 0xC2 + } + }, + { + 150, + 79, + 0x39, + 0x00C1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC1, 0x00, 0x52, 0x02, 0x01, 0x04, 0x53, 0xE8, 0x52, 0x01, 0x09, 0x00, 0x7C, 0x6D, 0xE3, 0x3E, 0xE8, 0x53, 0xE8, 0x51, 0xE9, 0x08, 0x51, 0xE8, 0x08, 0x52, 0x03, 0x01, 0x02, 0x08, 0x7C, 0x32, 0x52, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x01, 0x03, 0x7C, 0x6E, 0xA3, 0x08, 0x52, 0x03, 0x01, 0x04, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFB, 0x7C, 0x6F, 0x4C, 0x52, 0x01, 0x60, 0xD4, 0x3E, 0xE8, 0x54, 0x05, 0xAB, 0x6B + } + }, + { + 151, + 79, + 0x39, + 0x00C2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC2, 0x48, 0x00, 0x01, 0xA0, 0x18, 0x52, 0x05, 0x21, 0x0F, 0x53, 0xE9, 0x52, 0x06, 0x2A, 0xE9, 0x08, 0x52, 0x03, 0x11, 0x01, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x80, 0x0C, 0x52, 0x05, 0x62, 0xD0, 0x00, 0x64, 0x64, 0x64, 0x64, 0x54, 0x06, 0x77, 0x00, 0x3D, 0x00, 0x04, 0xCF, 0x33, 0x38, 0xF9, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x71, 0x10, 0x41, 0x04, 0x5F, 0x70, 0xCF, 0x62, 0xD0, 0x00, 0x4D, 0xB0 + } + }, + { + 152, + 79, + 0x39, + 0x00C3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC3, 0x51, 0xAF, 0x29, 0xA0, 0x7C, 0x6F, 0x20, 0x10, 0x7C, 0x20, 0x0B, 0x7C, 0x20, 0x50, 0x20, 0x10, 0x50, 0x01, 0x08, 0x50, 0x00, 0x08, 0x50, 0xA0, 0x08, 0x08, 0x7C, 0x20, 0x57, 0x38, 0xFC, 0x20, 0x62, 0xC8, 0x0B, 0x62, 0xCA, 0x24, 0x43, 0xD6, 0x01, 0x62, 0xCD, 0x00, 0x56, 0x00, 0x20, 0x80, 0x06, 0x62, 0xCF, 0x00, 0x7B, 0x00, 0x3D, 0x00, 0x00, 0xBF, 0xF7, 0x41, 0xD6, 0xFE, 0x38, 0xFF, 0x54, 0xBF + } + }, + { + 153, + 79, + 0x39, + 0x00C4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC4, 0x20, 0x7F, 0x10, 0x4F, 0x3D, 0xFC, 0x21, 0xD0, 0x0C, 0x41, 0xD6, 0xEF, 0x41, 0xE0, 0x7F, 0x62, 0xC8, 0x0B, 0x80, 0x0A, 0x62, 0xC8, 0x00, 0x43, 0xD6, 0x10, 0x43, 0xE0, 0x80, 0x20, 0x7F, 0x43, 0xD6, 0x01, 0x40, 0x62, 0xD0, 0x00, 0x51, 0xAF, 0x29, 0xA0, 0x7C, 0x6F, 0x20, 0x71, 0x10, 0x43, 0x04, 0xA0, 0x70, 0xCF, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x5D, 0xF7, 0x54, 0x00, 0x70, 0xFE, 0x7C, 0xDE, 0xD4 + } + }, + { + 154, + 79, + 0x39, + 0x00C5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC5, 0x70, 0xF8, 0xB0, 0x13, 0x7C, 0x73, 0x90, 0xBF, 0xFC, 0x71, 0x01, 0x40, 0x70, 0xFE, 0x62, 0xE3, 0x38, 0x41, 0xD6, 0xFE, 0x80, 0x06, 0x10, 0x7C, 0x33, 0x60, 0x20, 0x71, 0x10, 0x41, 0x04, 0x5F, 0x70, 0xCF, 0x62, 0xD0, 0x00, 0x51, 0xAF, 0x29, 0xA0, 0x7C, 0x6F, 0x20, 0x48, 0x00, 0x01, 0xA0, 0x03, 0x71, 0x01, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x7C, 0x33, 0x60, 0x20, 0x71, 0x10, 0x41, 0x04, 0x7F, 0x17 + } + }, + { + 155, + 79, + 0x39, + 0x00C6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC6, 0x5F, 0x70, 0xCF, 0x62, 0xD0, 0x00, 0x51, 0xAF, 0x29, 0xA0, 0x7C, 0x6F, 0x20, 0x71, 0x10, 0x43, 0xEC, 0x02, 0x70, 0xCF, 0x62, 0xDA, 0x7F, 0x43, 0xE0, 0x80, 0x9F, 0x83, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x9F, 0x92, 0x71, 0x10, 0x43, 0xEC, 0x02, 0x70, 0xCF, 0x62, 0xDA, 0x7F, 0x43, 0xE0, 0x80, 0x9F, 0x6D, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x7C, 0x73, 0x89, 0x38, 0xFF, 0x20, 0xF5, 0x04 + } + }, + { + 156, + 79, + 0x39, + 0x00C7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC7, 0x7F, 0x7C, 0x73, 0x89, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x5D, 0xC8, 0x39, 0x00, 0xB0, 0x18, 0x7C, 0x73, 0x90, 0xA0, 0x09, 0x56, 0x01, 0x01, 0x56, 0x00, 0x00, 0x80, 0x04, 0x7C, 0x6F, 0xC9, 0x62, 0xD0, 0x00, 0x52, 0x01, 0x80, 0x1D, 0x5D, 0xC9, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x47, 0xE9, 0x01, 0xA0, 0x09, 0x56, 0x01, 0x01, 0x56, 0x00, 0x00, 0x80, 0x04, 0x7C, 0x6F, 0xC9, 0x62, 0xD0, 0x00, 0xEB, 0xF1 + } + }, + { + 157, + 79, + 0x39, + 0x00C8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC8, 0x52, 0x01, 0x38, 0xFE, 0x20, 0x7F, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x7C, 0x70, 0xF8, 0xA0, 0x25, 0x5D, 0xD6, 0x53, 0xE9, 0x2E, 0xE9, 0xFE, 0x51, 0xE9, 0x54, 0x00, 0x43, 0xD6, 0x01, 0x52, 0xFC, 0x60, 0xCD, 0x52, 0xFB, 0x60, 0xCF, 0x5D, 0xD6, 0x53, 0xE9, 0x52, 0x00, 0x24, 0xE9, 0x51, 0xE9, 0x60, 0xD6, 0x80, 0x16, 0x3D, 0xFC, 0xA0, 0xD0, 0x11, 0x7C, 0x6F, 0x06, 0x28 + } + }, + { + 158, + 79, + 0x39, + 0x00C9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xC9, 0xB9, 0x55, 0xE9, 0x00, 0x06, 0xE8, 0x00, 0x7C, 0x71, 0x08, 0x52, 0xFB, 0x3F, 0xE8, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x03, 0x52, 0xFB, 0x54, 0x01, 0x52, 0xFA, 0x54, 0x00, 0x7C, 0x70, 0xF8, 0xA0, 0x1C, 0x7C, 0x71, 0x65, 0x60, 0xCD, 0x52, 0x00, 0x60, 0xCF, 0x52, 0x01, 0x60, 0xCF, 0x5D, 0xD6, 0x53, 0xE9, 0x52, 0x02, 0x24, 0xE9, 0x51, 0xE9, 0x60, 0xD6, 0x80, 0x26, 0x3D, 0xFC, 0x41, 0x9F + } + }, + { + 159, + 79, + 0x39, + 0x00CA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xCA, 0x9F, 0xD0, 0x21, 0x7C, 0x6F, 0xB9, 0x55, 0xE9, 0x00, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x01, 0x7C, 0x6F, 0xD9, 0x52, 0xFC, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x06, 0xE8, 0x01, 0x7C, 0x71, 0x08, 0x52, 0x01, 0x3F, 0xE8, 0x38, 0xFD, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x7C, 0x70, 0xF8, 0xA0, 0x29, 0x5D, 0xD6, 0x53, 0xE9, 0x2E, 0xE9, 0xFE, 0x51, 0xE9, 0x54, 0x01, 0x43, 0xD6, 0x01, 0x10, 0x52, 0xEA, 0xF2 + } + }, + { + 160, + 79, + 0x39, + 0x00CB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xCB, 0xFC, 0x7C, 0x33, 0x49, 0x62, 0xD0, 0x00, 0x20, 0x54, 0x00, 0x5D, 0xD6, 0x53, 0xE9, 0x52, 0x01, 0x24, 0xE9, 0x51, 0xE9, 0x60, 0xD6, 0x80, 0x17, 0x3D, 0xFC, 0xA0, 0xD0, 0x12, 0x7C, 0x6F, 0xB9, 0x55, 0xE9, 0x00, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x01, 0x7C, 0x6D, 0x83, 0x54, 0x00, 0x52, 0x00, 0x62, 0xD0, 0x00, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x03, 0x7C, 0x70, 0xF8, 0xA0, 0x1B, 0x45, 0xA9 + } + }, + { + 161, + 79, + 0x39, + 0x00CC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xCC, 0x7C, 0x71, 0x65, 0x08, 0x7C, 0x33, 0x53, 0x38, 0xFF, 0x7C, 0x72, 0x25, 0x5D, 0xD6, 0x53, 0xE9, 0x52, 0x02, 0x24, 0xE9, 0x51, 0xE9, 0x60, 0xD6, 0x80, 0x29, 0x3D, 0xFC, 0x9F, 0xD0, 0x24, 0x7C, 0x6F, 0xB9, 0x55, 0xE9, 0x00, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x01, 0x7C, 0x6D, 0x83, 0x54, 0x00, 0x52, 0xFC, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x06, 0xE8, 0x01, 0x0E, 0xE9, 0x01, 0x7C, 0x6D, 0x83, 0xFD, 0x1A + } + }, + { + 162, + 79, + 0x39, + 0x00CD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xCD, 0x54, 0x01, 0x7C, 0x71, 0x2E, 0x38, 0xFD, 0x20, 0x7F, 0x60, 0xCD, 0x5D, 0xF7, 0x08, 0x70, 0xFE, 0x5D, 0xCF, 0x7E, 0x60, 0xCD, 0x5D, 0xF7, 0x08, 0x70, 0xFE, 0x5D, 0xCF, 0x5C, 0x5D, 0xCF, 0x7E, 0x49, 0xC9, 0x01, 0xBF, 0xFC, 0x41, 0xD6, 0xFE, 0x7F, 0x41, 0x05, 0xF7, 0x7C, 0x73, 0x82, 0x62, 0xD0, 0x00, 0x51, 0xAF, 0x29, 0x08, 0x7C, 0x6F, 0x20, 0x71, 0x10, 0x43, 0x05, 0x08, 0x43, 0x04, 0xA4, 0x69 + } + }, + { + 163, + 79, + 0x39, + 0x00CE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xCE, 0x08, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x55, 0xB9, 0x00, 0x62, 0xD0, 0x03, 0x55, 0x99, 0x04, 0x55, 0x9A, 0x00, 0x55, 0x9B, 0xF8, 0x55, 0x9C, 0x00, 0x55, 0x9E, 0x64, 0x55, 0x9D, 0x32, 0x55, 0x9F, 0x00, 0x55, 0xA0, 0x00, 0x7C, 0x30, 0xB2, 0x90, 0x10, 0x7C, 0x6F, 0x64, 0x10, 0x57, 0x01, 0x50, 0xF4, 0x7C, 0x2B, 0xA8, 0x20, 0x7C, 0x6E, 0xE8, 0x7F, 0x10, 0x4F, 0x38, 0x03, 0x7C, 0x31, 0x35, 0x13, 0x48 + } + }, + { + 164, + 79, + 0x39, + 0x00CF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xCF, 0x7C, 0x6F, 0x2F, 0xB0, 0x06, 0x56, 0x01, 0x20, 0x80, 0x04, 0x56, 0x01, 0xA0, 0x52, 0x01, 0x08, 0x7C, 0x31, 0x02, 0x38, 0xFF, 0x62, 0xD0, 0x03, 0x51, 0x99, 0x21, 0xFC, 0x62, 0xD0, 0x00, 0x08, 0x50, 0x00, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x20, 0x08, 0x50, 0x01, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x3D, 0x00, 0x00, 0xB0, 0x28, 0x62, 0xD0, 0x04, 0x51, 0xB9, 0x08, 0x50, 0x01, 0x08, 0x7C, 0x7C, 0x1B + } + }, + { + 165, + 79, + 0x39, + 0x00D0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD0, 0x32, 0x0C, 0x38, 0xFE, 0x50, 0x00, 0x08, 0x50, 0x02, 0x08, 0x7C, 0x32, 0x0C, 0x62, 0xD0, 0x03, 0x51, 0x9B, 0x08, 0x50, 0x1E, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x82, 0x52, 0x3D, 0x00, 0x10, 0xB1, 0x87, 0x50, 0x00, 0x08, 0x50, 0x02, 0x08, 0x7C, 0x32, 0x0C, 0x7C, 0x40, 0x1F, 0x62, 0xD0, 0x00, 0x08, 0x50, 0x01, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0xC0, 0x08, 0x50, 0x03, 0x08, 0x01, 0x26 + } + }, + { + 166, + 79, + 0x39, + 0x00D1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD1, 0x7C, 0x32, 0x0C, 0x50, 0xC1, 0x08, 0x50, 0x04, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0xC2, 0x08, 0x50, 0x05, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x00, 0x08, 0x50, 0x06, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x10, 0x50, 0x00, 0x7C, 0x2B, 0x69, 0x62, 0xD0, 0x00, 0x20, 0x08, 0x50, 0x07, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x10, 0x50, 0x01, 0x7C, 0x2B, 0x69, 0x62, 0xD0, 0x00, 0x20, 0x08, 0xD6, 0xD1 + } + }, + { + 167, + 79, + 0x39, + 0x00D2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD2, 0x50, 0x08, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x10, 0x50, 0x02, 0x7C, 0x2B, 0x69, 0x62, 0xD0, 0x00, 0x20, 0x08, 0x50, 0x09, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x10, 0x50, 0x03, 0x7C, 0x2B, 0x69, 0x62, 0xD0, 0x00, 0x20, 0x08, 0x50, 0x0A, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x10, 0x50, 0x04, 0x7C, 0x2B, 0x69, 0x62, 0xD0, 0x00, 0x20, 0x08, 0x50, 0x0B, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xAF, 0x84 + } + }, + { + 168, + 79, + 0x39, + 0x00D3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD3, 0xFE, 0x10, 0x50, 0x05, 0x7C, 0x2B, 0x69, 0x62, 0xD0, 0x00, 0x20, 0x08, 0x50, 0x0C, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x10, 0x50, 0x06, 0x7C, 0x2B, 0x69, 0x62, 0xD0, 0x00, 0x20, 0x08, 0x50, 0x0D, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x10, 0x50, 0x07, 0x7C, 0x2B, 0x69, 0x62, 0xD0, 0x00, 0x20, 0x08, 0x50, 0x0E, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x50, 0x07, 0x10, 0x06, 0x33 + } + }, + { + 169, + 79, + 0x39, + 0x00D4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD4, 0x08, 0x57, 0xA0, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x28, 0x53, 0xE8, 0x20, 0x51, 0xE9, 0x08, 0x51, 0xE8, 0x08, 0x50, 0x0F, 0x08, 0x7C, 0x32, 0x52, 0x38, 0xFD, 0x50, 0x10, 0x08, 0x50, 0x12, 0x08, 0x50, 0x11, 0x08, 0x7C, 0x32, 0x52, 0x50, 0xA0, 0x08, 0x50, 0x02, 0x08, 0x50, 0x13, 0x08, 0x7C, 0x32, 0x52, 0x38, 0xFA, 0x50, 0x04, 0x08, 0x50, 0x00, 0x08, 0x50, 0x15, 0x08, 0x7C, 0x62, 0xEC + } + }, + { + 170, + 79, + 0x39, + 0x00D5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD5, 0x32, 0x52, 0x50, 0x00, 0x08, 0x50, 0x17, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFB, 0x50, 0x00, 0x08, 0x50, 0x18, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x00, 0x08, 0x50, 0x19, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0x00, 0x08, 0x50, 0x1A, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x00, 0x08, 0x50, 0x1B, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0x00, 0x08, 0x50, 0x1C, 0x08, 0x7C, 0x32, 0x0C, 0x62, 0xD0, 0x66, 0xF5 + } + }, + { + 171, + 79, + 0x39, + 0x00D6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD6, 0x03, 0x51, 0x9C, 0x08, 0x50, 0x1D, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x62, 0xD0, 0x03, 0x51, 0x9E, 0x08, 0x50, 0x1E, 0x08, 0x7C, 0x32, 0x0C, 0x62, 0xD0, 0x03, 0x51, 0x9D, 0x08, 0x50, 0x1F, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x80, 0xC7, 0x3D, 0x00, 0x20, 0xB0, 0x03, 0x80, 0xC0, 0x3D, 0x00, 0x30, 0xB0, 0xBB, 0x50, 0x01, 0x08, 0x50, 0x00, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x04, 0x08, 0x7D, 0x24 + } + }, + { + 172, + 79, + 0x39, + 0x00D7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD7, 0x50, 0x01, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0x01, 0x08, 0x50, 0x02, 0x08, 0x7C, 0x32, 0x0C, 0x62, 0xD0, 0x03, 0x51, 0x9F, 0x08, 0x50, 0x29, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0x04, 0x08, 0x50, 0x2A, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x08, 0x08, 0x50, 0x2B, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0x08, 0x08, 0x50, 0x2C, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x00, 0x08, 0x50, 0xFF, 0x29 + } + }, + { + 173, + 79, + 0x39, + 0x00D8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD8, 0x48, 0x08, 0x50, 0x2D, 0x08, 0x7C, 0x32, 0x52, 0x38, 0xFB, 0x50, 0x1C, 0x08, 0x50, 0x2F, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x00, 0x08, 0x50, 0x30, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0x08, 0x08, 0x50, 0x31, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x08, 0x08, 0x50, 0x32, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0x5A, 0x08, 0x50, 0x33, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x00, 0x08, 0x50, 0x34, 0xD9, 0xDE + } + }, + { + 174, + 79, + 0x39, + 0x00D9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xD9, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0x04, 0x08, 0x50, 0x35, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x0C, 0x08, 0x50, 0x36, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x50, 0x05, 0x08, 0x50, 0x37, 0x08, 0x7C, 0x32, 0x0C, 0x50, 0x01, 0x08, 0x50, 0x38, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x7C, 0x2B, 0x1A, 0x7C, 0x31, 0x1F, 0x38, 0xFD, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x7C, 0x32, 0x06, 0x7C, 0x20, 0x6D + } + }, + { + 175, + 79, + 0x39, + 0x00DA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xDA, 0x6F, 0x3C, 0x54, 0x00, 0x3D, 0x00, 0x30, 0xB0, 0x05, 0x90, 0xE8, 0x80, 0x03, 0x90, 0x4B, 0x62, 0xD0, 0x03, 0x51, 0x99, 0x21, 0x01, 0x62, 0xD0, 0x00, 0x39, 0x01, 0xB0, 0x19, 0x7C, 0x31, 0x35, 0x62, 0xD4, 0x00, 0x62, 0xD5, 0x00, 0x62, 0xD1, 0x00, 0x62, 0xD3, 0x00, 0x62, 0xD0, 0x00, 0x62, 0xE3, 0x38, 0x50, 0x00, 0x00, 0x7C, 0x6F, 0x3C, 0x54, 0x01, 0x52, 0x01, 0x3B, 0x00, 0xA0, 0x17, 0xE4, 0xF6 + } + }, + { + 176, + 79, + 0x39, + 0x00DB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xDB, 0x52, 0x01, 0x08, 0x52, 0x00, 0x08, 0x7C, 0x2B, 0x1B, 0x9C, 0xEE, 0x52, 0x01, 0x08, 0x52, 0x00, 0x08, 0x7C, 0x2B, 0x1F, 0x38, 0xFC, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x7C, 0x6F, 0x2F, 0xB0, 0x30, 0x50, 0x00, 0x08, 0x7C, 0x32, 0xA7, 0x62, 0xD0, 0x03, 0x53, 0x99, 0x50, 0x1E, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFE, 0x62, 0xD0, 0x03, 0x53, 0x9B, 0x47, 0x99, 0x02, 0xA0, 0x70, 0xFB, 0x25 + } + }, + { + 177, + 79, + 0x39, + 0x00DC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xDC, 0x51, 0x99, 0x21, 0xFD, 0x62, 0xD0, 0x00, 0x08, 0x50, 0x00, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x80, 0x5E, 0x3D, 0x00, 0x10, 0xB0, 0x33, 0x50, 0x00, 0x08, 0x7C, 0x32, 0xA7, 0x62, 0xD0, 0x03, 0x53, 0x99, 0x50, 0x1D, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFE, 0x62, 0xD0, 0x03, 0x53, 0x9C, 0x50, 0x1F, 0x08, 0x7C, 0x32, 0xA7, 0x62, 0xD0, 0x03, 0x53, 0x9D, 0x50, 0x1E, 0x08, 0x7C, 0x32, 0xA7, 0x46, 0xBC + } + }, + { + 178, + 79, + 0x39, + 0x00DD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xDD, 0x38, 0xFE, 0x62, 0xD0, 0x03, 0x53, 0x9E, 0x80, 0x27, 0x3D, 0x00, 0x20, 0xB0, 0x10, 0x50, 0x00, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFF, 0x62, 0xD0, 0x03, 0x53, 0x99, 0x80, 0x13, 0x48, 0x00, 0x40, 0xA0, 0x0E, 0x50, 0x00, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFF, 0x62, 0xD0, 0x03, 0x53, 0x99, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x04, 0x50, 0x00, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFF, 0x62, 0xAA, 0x85 + } + }, + { + 179, + 79, + 0x39, + 0x00DE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xDE, 0xD0, 0x00, 0x54, 0x00, 0x3D, 0x00, 0x01, 0xA0, 0x1F, 0x52, 0x00, 0x21, 0x70, 0x39, 0x30, 0xB0, 0x0E, 0x50, 0x01, 0x08, 0x50, 0x00, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x80, 0xE7, 0x52, 0x00, 0x62, 0xD0, 0x03, 0x53, 0x99, 0x80, 0xDE, 0x50, 0x29, 0x08, 0x7C, 0x32, 0xA7, 0x62, 0xD0, 0x03, 0x53, 0x9F, 0x50, 0x02, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x39, 0x81, 0xB0, 0x82, 0x36 + } + }, + { + 180, + 79, + 0x39, + 0x00DF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xDF, 0xC4, 0x50, 0x2A, 0x08, 0x7C, 0x32, 0xA7, 0x62, 0xD0, 0x00, 0x53, 0xA5, 0x50, 0x2B, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x53, 0x15, 0x50, 0x2C, 0x08, 0x7C, 0x32, 0xA7, 0x62, 0xD0, 0x00, 0x53, 0x16, 0x50, 0x2D, 0x08, 0x7C, 0x32, 0xF7, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x00, 0x53, 0xA3, 0x18, 0x53, 0xA4, 0x50, 0x2F, 0x08, 0x7C, 0x28, 0x83 + } + }, + { + 181, + 79, + 0x39, + 0x00E0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE0, 0x32, 0xA7, 0x38, 0xFF, 0x62, 0xD0, 0x00, 0x53, 0x42, 0x50, 0x30, 0x08, 0x7C, 0x32, 0xA7, 0x62, 0xD0, 0x04, 0x53, 0xB7, 0x50, 0x31, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x53, 0xA6, 0x50, 0x32, 0x08, 0x7C, 0x32, 0xA7, 0x62, 0xD0, 0x00, 0x53, 0x17, 0x50, 0x36, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x53, 0xA7, 0x50, 0x37, 0x08, 0x7C, 0x32, 0xA7, 0x62, 0x39, 0xA6 + } + }, + { + 182, + 79, + 0x39, + 0x00E1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE1, 0xD0, 0x00, 0x53, 0xA8, 0x50, 0x38, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x53, 0xA9, 0x10, 0x7C, 0x18, 0x83, 0x7C, 0x17, 0xB7, 0x20, 0x80, 0x04, 0x62, 0xE3, 0x38, 0x7C, 0x31, 0xC5, 0x62, 0xD0, 0x00, 0x39, 0x00, 0xBF, 0xF4, 0x7C, 0x71, 0x10, 0x7C, 0x49, 0x07, 0x62, 0xE3, 0x38, 0x52, 0x01, 0x71, 0x10, 0x60, 0xE0, 0x50, 0x01, 0x08, 0x50, 0x02, 0x08, 0x70, 0xCF, 0x7C, 0xFE, 0x31 + } + }, + { + 183, + 79, + 0x39, + 0x00E2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE2, 0x32, 0x0C, 0x38, 0xFE, 0x38, 0xFC, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x7C, 0x6F, 0x2F, 0xB0, 0x06, 0x3D, 0xFC, 0x00, 0xB0, 0x0B, 0x3D, 0x00, 0x20, 0xA0, 0x06, 0x48, 0x00, 0x40, 0xA0, 0x26, 0x62, 0xD0, 0x04, 0x06, 0xB9, 0x40, 0x51, 0xB9, 0x29, 0x20, 0x62, 0xD0, 0x00, 0x08, 0x50, 0x01, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x80, 0x04, 0x62, 0xE3, 0x38, 0x7C, 0x31, 0xC5, 0x62, 0xD0, 0xA4, 0x7E + } + }, + { + 184, + 79, + 0x39, + 0x00E3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE3, 0x00, 0x39, 0x00, 0xBF, 0xF4, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x7C, 0x6F, 0x2F, 0xB0, 0x06, 0x3D, 0xFC, 0x00, 0xB0, 0x0B, 0x3D, 0x00, 0x20, 0xA0, 0x06, 0x48, 0x00, 0x40, 0xA0, 0x0F, 0x62, 0xD0, 0x04, 0x51, 0xB9, 0x08, 0x50, 0x01, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x50, 0x00, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFF, 0x21, 0x80, 0x62, 0xD0, 0x04, 0x53, 0xB8, 0x38, 0xFF, 0xE6, 0x03 + } + }, + { + 185, + 79, + 0x39, + 0x00E4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE4, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x7C, 0x6F, 0x2F, 0xB0, 0x0A, 0x3D, 0xFC, 0x01, 0xB0, 0x19, 0x90, 0xC1, 0x80, 0x15, 0x3D, 0x00, 0x10, 0xB0, 0x05, 0x90, 0x63, 0x80, 0x0C, 0x3D, 0x00, 0x30, 0xB0, 0x05, 0x90, 0x21, 0x80, 0x03, 0x90, 0x56, 0x52, 0xFC, 0x08, 0x7C, 0x2B, 0x4C, 0x38, 0xFF, 0x38, 0xFF, 0x20, 0x7F, 0x62, 0xD0, 0x04, 0x51, 0xB9, 0x62, 0xD0, 0x00, 0x67, 0x67, 0x67, 0x67, 0x15, 0x62 + } + }, + { + 186, + 79, + 0x39, + 0x00E5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE5, 0x67, 0x67, 0x21, 0x03, 0x7F, 0x50, 0x84, 0x08, 0x50, 0x01, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x7C, 0x6F, 0x64, 0x7C, 0x32, 0x06, 0x62, 0xD0, 0x00, 0x62, 0xE3, 0x38, 0x50, 0x01, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFF, 0x62, 0xD0, 0x00, 0x39, 0x04, 0xA0, 0x10, 0x50, 0x00, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFF, 0x62, 0xD0, 0x00, 0x39, 0x01, 0xAF, 0xDA, 0x7C, 0x6E, 0xE8, 0x7F, 0x10, 0x4F, 0xD5, 0xE3 + } + }, + { + 187, + 79, + 0x39, + 0x00E6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE6, 0x38, 0x02, 0x7C, 0x6F, 0x64, 0x56, 0x01, 0xFA, 0x56, 0x00, 0x00, 0x80, 0x36, 0x7C, 0x32, 0x06, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x08, 0x7C, 0x32, 0xA7, 0x38, 0xFF, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x26, 0xE9, 0x80, 0x62, 0xD0, 0x04, 0x51, 0xB8, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0xA0, 0x03, 0x80, 0x1C, 0x10, 0x57, 0x03, 0x50, 0xE3, 0x7C, 0x2B, 0xA8, 0x20, 0x62, 0xE3, 0x38, 0x7B, 0x01, 0x1F, 0xA4, 0x82 + } + }, + { + 188, + 79, + 0x39, + 0x00E7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE7, 0x00, 0x00, 0x3D, 0x00, 0x00, 0xBF, 0xC7, 0x3D, 0x01, 0x00, 0xBF, 0xC2, 0x7C, 0x6E, 0xE8, 0x38, 0xFE, 0x20, 0x7F, 0x7C, 0x6F, 0x64, 0x10, 0x57, 0x01, 0x50, 0xF4, 0x7C, 0x2B, 0xA8, 0x20, 0x7C, 0x6E, 0xE8, 0x7C, 0x32, 0x06, 0x62, 0xD0, 0x00, 0x7F, 0x7C, 0x31, 0x77, 0x7F, 0x7C, 0x31, 0xC1, 0x7F, 0x43, 0x05, 0x08, 0x62, 0xD0, 0x00, 0x26, 0xB0, 0xFB, 0x51, 0xB0, 0x60, 0x00, 0x62, 0xDA, 0x4A, 0xCF + } + }, + { + 189, + 79, + 0x39, + 0x00E8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE8, 0xEF, 0x43, 0xE0, 0x10, 0x7C, 0x31, 0x9D, 0x7F, 0x7C, 0x31, 0xB6, 0x7C, 0x73, 0x82, 0x41, 0x05, 0xF7, 0x62, 0xD0, 0x00, 0x51, 0xB0, 0x29, 0x04, 0x53, 0xB0, 0x51, 0xB0, 0x60, 0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xC3, 0x00, 0x62, 0xD0, 0x04, 0x55, 0xC2, 0x00, 0x62, 0xD0, 0x04, 0x09, 0x4E + } + }, + { + 190, + 79, + 0x39, + 0x00E9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xE9, 0x55, 0xC1, 0x00, 0x7C, 0x3A, 0x1F, 0x10, 0x7C, 0x49, 0x5B, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x62, 0xD0, 0x03, 0x51, 0x9B, 0x21, 0xF0, 0x62, 0xD0, 0x04, 0x53, 0xC2, 0x7C, 0x6F, 0x2F, 0xA0, 0x06, 0x3D, 0x00, 0x30, 0xB0, 0x0B, 0x7C, 0x73, 0x26, 0x62, 0xD0, 0x00, 0x53, 0x39, 0x80, 0x07, 0x62, 0xD0, 0x00, 0x55, 0x39, 0x00, 0x62, 0xD0, 0x00, 0x51, 0x39, 0x62, 0xD0, 0x00, 0x53, 0x3A, 0x4C, 0xD5 + } + }, + { + 191, + 79, + 0x39, + 0x00EA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xEA, 0x62, 0xD0, 0x00, 0x51, 0x39, 0x62, 0xD0, 0x00, 0x53, 0x3B, 0x62, 0xD0, 0x00, 0x51, 0x39, 0x62, 0xD0, 0x00, 0x53, 0x3C, 0x7C, 0x3A, 0x21, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x62, 0xD0, 0x04, 0x51, 0xCF, 0x54, 0x01, 0x10, 0x52, 0xFB, 0x7C, 0x49, 0x92, 0x20, 0x62, 0xD0, 0x04, 0x53, 0xC3, 0x3C, 0xC3, 0x00, 0xB0, 0x4F, 0x52, 0xFB, 0x3B, 0xFC, 0xA0, 0x49, 0x52, 0xFC, 0x3B, 0xFE, 0x3A + } + }, + { + 192, + 79, + 0x39, + 0x00EB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xEB, 0xFB, 0xD0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xC3, 0x2F, 0x80, 0x21, 0x62, 0xD0, 0x04, 0x55, 0xC3, 0x4F, 0x3D, 0xFB, 0x00, 0xB0, 0x16, 0x7C, 0x3A, 0x2C, 0x7C, 0x3A, 0x22, 0x62, 0xD0, 0x00, 0x39, 0x00, 0xA0, 0x09, 0x7C, 0x3A, 0x22, 0x62, 0xD0, 0x04, 0x53, 0xC3, 0x62, 0xD0, 0x04, 0x51, 0xC1, 0x62, 0xD0, 0x04, 0x3A, 0xC3, 0xB0, 0x0C, 0x62, 0xD0, 0x04, 0x52, 0x01, 0x01, 0x01, 0x53, 0xCF, 0x63, 0x05 + } + }, + { + 193, + 79, + 0x39, + 0x00EC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xEC, 0x80, 0x04, 0x7C, 0x70, 0x5B, 0x62, 0xD0, 0x04, 0x3C, 0xC3, 0x00, 0xA0, 0x09, 0x52, 0xFB, 0x08, 0x7C, 0x3A, 0x28, 0x38, 0xFF, 0x62, 0xD0, 0x04, 0x51, 0xC3, 0x08, 0x52, 0xFB, 0x08, 0x91, 0x86, 0x38, 0xFE, 0x39, 0x00, 0xA0, 0x21, 0x62, 0xD0, 0x04, 0x55, 0xC3, 0xFF, 0x62, 0xD0, 0x04, 0x51, 0xC1, 0x62, 0xD0, 0x04, 0x3A, 0xC3, 0xB0, 0x0C, 0x62, 0xD0, 0x04, 0x52, 0x01, 0x01, 0x01, 0x53, 0x63, 0x06 + } + }, + { + 194, + 79, + 0x39, + 0x00ED, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xED, 0xCF, 0x80, 0x04, 0x7C, 0x70, 0x5B, 0x62, 0xD0, 0x04, 0x3C, 0xC3, 0x00, 0xA0, 0x06, 0x56, 0x00, 0x01, 0x80, 0x04, 0x56, 0x00, 0x00, 0x52, 0xFB, 0x08, 0x52, 0xFC, 0x08, 0x50, 0x04, 0x08, 0x50, 0xC3, 0x08, 0x62, 0xD0, 0x00, 0x50, 0x0F, 0x08, 0x10, 0x7C, 0x2B, 0x40, 0x38, 0xFA, 0x62, 0xD0, 0x04, 0x3C, 0xC3, 0x00, 0xA0, 0x2B, 0x90, 0x31, 0x62, 0xD0, 0x04, 0x3C, 0xC3, 0x00, 0xA0, 0x21, 0x41, 0xC3 + } + }, + { + 195, + 79, + 0x39, + 0x00EE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xEE, 0x62, 0xD0, 0x04, 0x3C, 0xCF, 0x00, 0xB0, 0x0F, 0x62, 0xD0, 0x04, 0x3C, 0xC3, 0xFF, 0xA0, 0x07, 0x62, 0xD0, 0x04, 0x55, 0xCF, 0x80, 0x62, 0xD0, 0x04, 0x51, 0xC3, 0x62, 0xD0, 0x04, 0x53, 0xC1, 0x52, 0x00, 0x62, 0xD0, 0x00, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x62, 0xD0, 0x04, 0x51, 0xC3, 0x54, 0x00, 0x3C, 0xC3, 0x00, 0xA0, 0x0B, 0x7C, 0x6F, 0x3C, 0x62, 0xD0, 0x00, 0x39, 0x14, 0x6A + } + }, + { + 196, + 79, + 0x39, + 0x00EF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xEF, 0x30, 0xB0, 0x03, 0x80, 0xB1, 0x50, 0x10, 0x08, 0x50, 0x29, 0x08, 0x50, 0x28, 0x08, 0x90, 0xA9, 0x50, 0x20, 0x08, 0x50, 0x3F, 0x08, 0x50, 0x30, 0x08, 0x90, 0x9E, 0x38, 0xFA, 0x50, 0x40, 0x08, 0x50, 0x49, 0x08, 0x50, 0x48, 0x08, 0x90, 0x91, 0x50, 0x80, 0x08, 0x50, 0x9F, 0x08, 0x50, 0x90, 0x08, 0x90, 0x86, 0x38, 0xFA, 0x62, 0xD0, 0x04, 0x51, 0xC3, 0x3B, 0x00, 0xA0, 0x6E, 0x3D, 0x00, 0x76, 0x2F + } + }, + { + 197, + 79, + 0x39, + 0x00F0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF0, 0x28, 0xC0, 0x69, 0x50, 0x29, 0x3B, 0x00, 0xC0, 0x63, 0x62, 0xD0, 0x04, 0x51, 0xDF, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x65, 0xE9, 0x51, 0xE9, 0x01, 0x10, 0x62, 0xD0, 0x04, 0x53, 0xC3, 0x62, 0xD0, 0x04, 0x51, 0xC1, 0x62, 0xD0, 0x04, 0x3A, 0xC3, 0xB0, 0x0F, 0x62, 0xD0, 0x04, 0x51, 0xBF, 0x01, 0x01, 0x62, 0xD0, 0x04, 0x53, 0xCF, 0x80, 0x04, 0x7C, 0x70, 0x5B, 0x50, 0x10, 0x08, 0x50, 0x29, 0x67, 0x12 + } + }, + { + 198, + 79, + 0x39, + 0x00F1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF1, 0x08, 0x50, 0x28, 0x08, 0x90, 0x33, 0x50, 0x20, 0x08, 0x50, 0x3F, 0x08, 0x50, 0x30, 0x08, 0x90, 0x28, 0x38, 0xFA, 0x50, 0x40, 0x08, 0x50, 0x49, 0x08, 0x50, 0x48, 0x08, 0x90, 0x1B, 0x50, 0x80, 0x08, 0x50, 0x9F, 0x08, 0x50, 0x90, 0x08, 0x90, 0x10, 0x38, 0xFA, 0x62, 0xD0, 0x04, 0x51, 0xCF, 0x62, 0xD0, 0x04, 0x53, 0xBF, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x62, 0xD0, 0x04, 0x51, 0xC3, 0xF6, 0x31 + } + }, + { + 199, + 79, + 0x39, + 0x00F2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF2, 0x3B, 0xFC, 0xC0, 0x21, 0x62, 0xD0, 0x04, 0x52, 0xFB, 0x3A, 0xC3, 0xC0, 0x18, 0x62, 0xD0, 0x04, 0x51, 0xC2, 0x23, 0xFA, 0x39, 0x00, 0xB0, 0x0D, 0x62, 0xD0, 0x04, 0x55, 0xC3, 0x00, 0x62, 0xD0, 0x04, 0x55, 0xCF, 0x00, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x0A, 0x50, 0x02, 0x3B, 0xFC, 0xD1, 0xA6, 0x7C, 0x73, 0x26, 0x54, 0x03, 0x56, 0x00, 0x00, 0x80, 0xCA, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x80, 0x46 + } + }, + { + 200, + 79, + 0x39, + 0x00F3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF3, 0x06, 0xE8, 0xC0, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x7C, 0x6D, 0x8A, 0x51, 0xE8, 0x01, 0xE0, 0x54, 0x02, 0x51, 0xE9, 0x09, 0x01, 0x54, 0x01, 0x7C, 0x6D, 0xA5, 0x65, 0xE8, 0x6B, 0xE9, 0x06, 0xE8, 0xDD, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x7C, 0x6E, 0xE1, 0x52, 0x02, 0x53, 0xE6, 0x52, 0x01, 0x60, 0xD4, 0x3E, 0xE6, 0x53, 0xE6, 0x50, 0x00, 0x3A, 0xE9, 0xB0, 0x07, 0x51, 0xE6, 0x3A, 0xC6, 0xD3 + } + }, + { + 201, + 79, + 0x39, + 0x00F4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF4, 0xE8, 0xA0, 0x03, 0x80, 0x84, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x01, 0x06, 0x7C, 0x6E, 0xA3, 0x54, 0x04, 0x3E, 0xE8, 0x54, 0x05, 0x52, 0x02, 0x01, 0x04, 0x7C, 0x6E, 0xA3, 0x54, 0x06, 0x3E, 0xE8, 0x54, 0x07, 0x7C, 0x6D, 0xA5, 0x65, 0xE8, 0x6B, 0xE9, 0x06, 0xE8, 0xF5, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x7C, 0x6E, 0xE1, 0x51, 0xE9, 0x08, 0x51, 0xE8, 0x08, 0x52, 0x04, 0x08, 0x52, 0x05, 0xBB, 0xBE + } + }, + { + 202, + 79, + 0x39, + 0x00F5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF5, 0x08, 0x91, 0x1B, 0x38, 0xFC, 0x62, 0xD0, 0x00, 0x52, 0x03, 0x7C, 0x71, 0xD1, 0xC0, 0x31, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x65, 0xE8, 0x6B, 0xE9, 0x06, 0xE8, 0xA1, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x7C, 0x6E, 0xE1, 0x51, 0xE9, 0x08, 0x51, 0xE8, 0x08, 0x52, 0x06, 0x08, 0x52, 0x07, 0x08, 0x90, 0xEB, 0x38, 0xFC, 0x62, 0xD0, 0x00, 0x52, 0x03, 0x7C, 0x71, 0xD1, 0xD0, 0x03, 0x80, 0xCA, 0xDD + } + }, + { + 203, + 79, + 0x39, + 0x00F6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF6, 0x08, 0x77, 0x00, 0x7C, 0x72, 0x41, 0xCF, 0x33, 0x50, 0x04, 0x3B, 0xFC, 0xD0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xC0, 0x04, 0x80, 0x08, 0x52, 0xFC, 0x62, 0xD0, 0x04, 0x53, 0xC0, 0x7C, 0x72, 0x41, 0xA0, 0xAD, 0x56, 0x00, 0x00, 0x80, 0x89, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xC0, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x7C, 0x6D, 0x8A, 0x51, 0xE8, 0x01, 0xE0, 0x54, 0x02, 0x51, 0x65, 0x14 + } + }, + { + 204, + 79, + 0x39, + 0x00F7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF7, 0xE9, 0x09, 0x01, 0x54, 0x01, 0x7C, 0x6F, 0xF6, 0x53, 0xE8, 0x7C, 0x6E, 0xAE, 0x65, 0xE6, 0x6B, 0xE7, 0x06, 0xE6, 0xDD, 0x0E, 0xE7, 0x02, 0x51, 0xE7, 0x60, 0xD5, 0x50, 0x00, 0x3F, 0xE6, 0x51, 0xE8, 0x3F, 0xE6, 0x52, 0x02, 0x01, 0x06, 0x53, 0xE8, 0x52, 0x01, 0x09, 0x00, 0x7C, 0x6D, 0xE3, 0x3E, 0xE8, 0x53, 0xE8, 0x7C, 0x6E, 0xAE, 0x65, 0xE6, 0x6B, 0xE7, 0x06, 0xE6, 0xF5, 0x0E, 0xE7, 0xC3, 0xD1 + } + }, + { + 205, + 79, + 0x39, + 0x00F8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF8, 0x02, 0x7C, 0x6D, 0xEA, 0x51, 0xE8, 0x3F, 0xE6, 0x52, 0x02, 0x01, 0x04, 0x53, 0xE8, 0x52, 0x01, 0x09, 0x00, 0x7C, 0x6D, 0xE3, 0x3E, 0xE8, 0x53, 0xE8, 0x7C, 0x6E, 0xAE, 0x65, 0xE6, 0x6B, 0xE7, 0x06, 0xE6, 0xA1, 0x0E, 0xE7, 0x03, 0x7C, 0x6D, 0xEA, 0x51, 0xE8, 0x3F, 0xE6, 0x77, 0x00, 0x7C, 0x72, 0x41, 0xCF, 0x74, 0x3D, 0xFB, 0x00, 0xB0, 0x09, 0x56, 0x09, 0x01, 0x56, 0x08, 0x00, 0x80, 0x53, 0xF2 + } + }, + { + 206, + 79, + 0x39, + 0x00F9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xF9, 0x07, 0x56, 0x09, 0x00, 0x56, 0x08, 0x00, 0x62, 0xD0, 0x00, 0x52, 0x09, 0x80, 0x0D, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x80, 0x06, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x38, 0xF6, 0x20, 0x7F, 0x10, 0x4F, 0x52, 0xFC, 0x13, 0xFA, 0x52, 0xFB, 0x1B, 0xF9, 0xC0, 0x12, 0x62, 0xD0, 0x00, 0x52, 0xFC, 0x13, 0xFA, 0x53, 0xE8, 0x52, 0xFB, 0x1B, 0xF9, 0x53, 0xE9, 0x80, 0x10, 0x62, 0xD0, 0x00, 0x52, 0xFA, 0x95, 0x77 + } + }, + { + 207, + 79, + 0x39, + 0x00FA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xFA, 0x13, 0xFC, 0x53, 0xE8, 0x52, 0xF9, 0x1B, 0xFB, 0x53, 0xE9, 0x20, 0x7F, 0x10, 0x4F, 0x7C, 0x72, 0x93, 0xB0, 0x22, 0x3D, 0xFC, 0x01, 0xB0, 0x32, 0x62, 0xD0, 0x04, 0x51, 0xCF, 0x08, 0x50, 0x0E, 0x08, 0x7C, 0x32, 0x0C, 0x62, 0xD0, 0x04, 0x51, 0xC3, 0x08, 0x50, 0x0F, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x80, 0x16, 0x7C, 0x6F, 0x3C, 0x39, 0x30, 0xB0, 0x0F, 0x62, 0xD0, 0x04, 0x51, 0xC3, 0xA8, 0x9E + } + }, + { + 208, + 79, + 0x39, + 0x00FB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xFB, 0x08, 0x50, 0x24, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x52, 0xFC, 0x08, 0x7C, 0x3A, 0x2D, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x62, 0xE3, 0x38, 0x7C, 0x2B, 0x06, 0x71, 0x01, 0x90, 0x67, 0x90, 0xC5, 0x80, 0x5D, 0x62, 0xD0, 0x00, 0x26, 0xAE, 0xFB, 0x51, 0xAE, 0x60, 0x00, 0x7C, 0x70, 0x4B, 0x51, 0xAE, 0x29, 0x04, 0x53, 0xAE, 0x51, 0xAE, 0x60, 0x00, 0x7C, 0x70, 0x4B, 0x26, 0x61, 0x11 + } + }, + { + 209, + 79, + 0x39, + 0x00FC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xFC, 0xAF, 0xFD, 0x51, 0xAF, 0x60, 0x04, 0x7C, 0x70, 0x4B, 0x51, 0xAF, 0x29, 0x02, 0x7C, 0x6F, 0x20, 0x56, 0x01, 0x00, 0x80, 0x03, 0x77, 0x01, 0x3D, 0x01, 0x0A, 0xCF, 0xFA, 0x62, 0xE3, 0x38, 0x90, 0x9D, 0x62, 0xD0, 0x04, 0x3C, 0xC4, 0x00, 0xA0, 0x0C, 0x7C, 0x45, 0x53, 0x7C, 0x2C, 0x1E, 0x62, 0xD0, 0x00, 0x54, 0x00, 0x52, 0x00, 0x08, 0x90, 0x9B, 0x52, 0x00, 0x08, 0x7C, 0x39, 0x02, 0x38, 0x9A, 0x84 + } + }, + { + 210, + 79, + 0x39, + 0x00FD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xFD, 0xFE, 0x8F, 0xA3, 0x38, 0xFE, 0x20, 0x8F, 0xFF, 0x10, 0x4F, 0x38, 0x01, 0x10, 0x7C, 0x16, 0x34, 0x20, 0x10, 0x57, 0x13, 0x50, 0x88, 0x7C, 0x2B, 0xA8, 0x20, 0x62, 0xD0, 0x04, 0x55, 0xC4, 0x01, 0x62, 0xD0, 0x00, 0x55, 0xA7, 0x0C, 0x62, 0xD0, 0x00, 0x55, 0xA8, 0x05, 0x62, 0xD0, 0x00, 0x55, 0xA9, 0x01, 0x10, 0x7C, 0x17, 0xB7, 0x7C, 0x19, 0x82, 0x62, 0xD0, 0x00, 0x20, 0x39, 0x00, 0xA0, 0xEE, 0x2D + } + }, + { + 211, + 79, + 0x39, + 0x00FE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xFE, 0x1F, 0x71, 0x10, 0x5D, 0xE0, 0x54, 0x00, 0x41, 0xE0, 0xE7, 0x43, 0xE0, 0x18, 0x70, 0xCF, 0x62, 0xE3, 0x38, 0x7C, 0x49, 0x07, 0x62, 0xE3, 0x38, 0x52, 0x00, 0x7C, 0x72, 0xB2, 0x80, 0x06, 0x10, 0x7C, 0x10, 0x74, 0x20, 0x38, 0xFF, 0x20, 0x7F, 0x7C, 0x33, 0x69, 0x7C, 0x40, 0x59, 0x7C, 0x45, 0x51, 0x7C, 0x2B, 0xCA, 0x7C, 0x3A, 0x31, 0x7C, 0x40, 0x10, 0x7C, 0x2B, 0x19, 0x7F, 0x7C, 0x40, 0x55, 0xFC + } + }, + { + 212, + 79, + 0x39, + 0x00FF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0xFF, 0x98, 0x7C, 0x36, 0x78, 0x7C, 0x45, 0x52, 0x7C, 0x2B, 0xFD, 0x7C, 0x3A, 0x4C, 0x7C, 0x40, 0x1E, 0x7C, 0x2B, 0x23, 0x7F, 0x10, 0x4F, 0x52, 0xFC, 0x08, 0x7C, 0x38, 0x88, 0x52, 0xFC, 0x08, 0x7C, 0x45, 0x4D, 0x38, 0xFE, 0x52, 0xFC, 0x08, 0x7C, 0x45, 0x76, 0x52, 0xFC, 0x08, 0x7C, 0x2E, 0x27, 0x38, 0xFE, 0x52, 0xFC, 0x08, 0x7C, 0x3E, 0x8C, 0x52, 0xFC, 0x08, 0x7C, 0x40, 0x28, 0x38, 0xFE, 0x1F, 0x91 + } + }, + { + 213, + 79, + 0x39, + 0x0100, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x00, 0x52, 0xFC, 0x08, 0x7C, 0x2B, 0x48, 0x52, 0xFC, 0x08, 0x7C, 0x38, 0xC9, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x62, 0xD0, 0x04, 0x55, 0xC5, 0x04, 0x38, 0xFF, 0x20, 0x7F, 0x7F, 0x62, 0xD0, 0x04, 0x51, 0xC5, 0x62, 0xD0, 0x00, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x62, 0xD0, 0x00, 0x51, 0x54, 0x54, 0x01, 0x51, 0x53, 0x54, 0x00, 0x70, 0xFE, 0x10, 0x7C, 0x1E, 0xFE, 0x51 + } + }, + { + 214, + 79, + 0x39, + 0x0101, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x01, 0x1C, 0x62, 0xD0, 0x00, 0x20, 0x10, 0x52, 0x00, 0x08, 0x52, 0x01, 0x20, 0x7C, 0x1D, 0xD4, 0x20, 0x62, 0xDA, 0xF7, 0x71, 0x01, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x7C, 0x1E, 0x1C, 0x20, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x55, 0x0D, 0x00, 0x55, 0x0C, 0x00, 0x7C, 0x72, 0x19, 0x92, 0x21, 0x7C, 0x73, 0x6D, 0x10, 0x7C, 0x1D, 0xCC, 0x7C, 0x20, 0x80, 0x7C, 0x20, 0x78, 0x20, 0x62, 0xD0, 0x00, 0x55, 0xFD, 0x50 + } + }, + { + 215, + 79, + 0x39, + 0x0102, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x02, 0xEF, 0x00, 0x55, 0xEE, 0x00, 0x62, 0xD0, 0x03, 0x55, 0xC8, 0x00, 0x55, 0xC9, 0x00, 0x55, 0xCA, 0x00, 0x55, 0xCB, 0x00, 0x7C, 0x2B, 0x67, 0x7F, 0x10, 0x4F, 0x38, 0x03, 0x7C, 0x72, 0x93, 0xB0, 0xF9, 0x7C, 0x70, 0xE8, 0x54, 0x01, 0x51, 0x0C, 0x54, 0x00, 0x71, 0x01, 0x62, 0xD0, 0x03, 0x47, 0x99, 0x02, 0xA0, 0x3F, 0x93, 0x58, 0x9F, 0x74, 0x7C, 0x70, 0xE8, 0x08, 0x51, 0x0C, 0x62, 0xD0, 0xDD, 0x11 + } + }, + { + 216, + 79, + 0x39, + 0x0103, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x03, 0x00, 0x53, 0xEE, 0x18, 0x53, 0xEF, 0x71, 0x01, 0x62, 0xD0, 0x03, 0x55, 0xC8, 0x00, 0x55, 0xC9, 0x00, 0x55, 0xCA, 0x00, 0x55, 0xCB, 0x00, 0x7C, 0x2B, 0x67, 0x10, 0x7C, 0x18, 0x83, 0x20, 0x10, 0x57, 0x13, 0x50, 0x88, 0x7C, 0x2B, 0xA8, 0x7C, 0x10, 0x74, 0x20, 0x91, 0x3B, 0x7C, 0x6F, 0xEA, 0x81, 0x33, 0x62, 0xD0, 0x03, 0x51, 0x9A, 0x21, 0x0F, 0x54, 0x02, 0x62, 0xD0, 0x04, 0x3C, 0xCB, 0x0A, 0x6C + } + }, + { + 217, + 79, + 0x39, + 0x0104, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x04, 0xF1, 0xB0, 0x45, 0x62, 0xD0, 0x03, 0x3C, 0x9C, 0x00, 0xA0, 0x03, 0x93, 0x67, 0x3D, 0x02, 0x00, 0xA0, 0x06, 0x7C, 0x6F, 0xEA, 0x80, 0x97, 0x7C, 0x73, 0x2E, 0xA0, 0x27, 0x62, 0xD0, 0x03, 0x52, 0x01, 0x12, 0xE5, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x52, 0x00, 0x62, 0xD0, 0x03, 0x1A, 0xE4, 0x7C, 0x72, 0x49, 0x62, 0xD0, 0x03, 0x12, 0xDB, 0x7C, 0x70, 0xA1, 0x1A, 0xDA, 0xC0, 0x70, 0x91, 0x2A, 0x7B, 0x4F + } + }, + { + 218, + 79, + 0x39, + 0x0105, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x05, 0x80, 0x6C, 0x7C, 0x6F, 0xEA, 0x80, 0x67, 0x62, 0xD0, 0x04, 0x3C, 0xCB, 0xF2, 0xB0, 0x1E, 0x3D, 0x02, 0x00, 0xB0, 0x06, 0x7C, 0x73, 0x2E, 0xB0, 0x08, 0x90, 0xCD, 0x7C, 0x6F, 0xEA, 0x80, 0x4E, 0x62, 0xD0, 0x03, 0x3C, 0x9D, 0x00, 0xA0, 0x46, 0x93, 0x0A, 0x80, 0x42, 0x9E, 0xBE, 0x7C, 0x70, 0xE8, 0x08, 0x51, 0x0C, 0x62, 0xD0, 0x00, 0x53, 0xEE, 0x18, 0x53, 0xEF, 0x71, 0x01, 0x62, 0xD0, 0xC8, 0xEA + } + }, + { + 219, + 79, + 0x39, + 0x0106, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x06, 0x03, 0x55, 0xC8, 0x00, 0x55, 0xC9, 0x00, 0x55, 0xCA, 0x00, 0x55, 0xCB, 0x00, 0x7C, 0x2B, 0x67, 0x90, 0xFD, 0x90, 0x94, 0x7C, 0x6F, 0xEA, 0x80, 0x15, 0x62, 0xD0, 0x04, 0x3C, 0xCB, 0xF4, 0xA0, 0x0D, 0x10, 0x57, 0x00, 0x50, 0x01, 0x7C, 0x1D, 0xD4, 0x20, 0x7C, 0x73, 0x6D, 0x7C, 0x72, 0x93, 0xB0, 0x70, 0x7C, 0x70, 0xE8, 0x54, 0x01, 0x51, 0x0C, 0x54, 0x00, 0x71, 0x01, 0x62, 0xD0, 0x00, 0x44, 0xE3 + } + }, + { + 220, + 79, + 0x39, + 0x0107, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x07, 0x52, 0x01, 0x12, 0xEF, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x52, 0x00, 0x62, 0xD0, 0x00, 0x1A, 0xEE, 0x7C, 0x72, 0x49, 0x53, 0xE6, 0x51, 0xE9, 0x53, 0xE7, 0x51, 0xE6, 0x62, 0xD0, 0x03, 0x04, 0xCB, 0x62, 0xD0, 0x00, 0x51, 0xE7, 0x62, 0xD0, 0x03, 0x0C, 0xCA, 0x0E, 0xC9, 0x00, 0x0E, 0xC8, 0x00, 0x62, 0xD0, 0x00, 0x52, 0x01, 0x53, 0xEF, 0x52, 0x00, 0x53, 0xEE, 0x62, 0xD0, 0x03, 0x51, 0xCB, 0x39, 0xCE + } + }, + { + 221, + 79, + 0x39, + 0x0108, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x08, 0x11, 0x30, 0x51, 0xCA, 0x19, 0x75, 0x51, 0xC9, 0x19, 0x00, 0x51, 0xC8, 0x19, 0x00, 0xC0, 0x12, 0x9E, 0x1A, 0x62, 0xD0, 0x03, 0x55, 0xC8, 0x00, 0x55, 0xC9, 0x00, 0x55, 0xCA, 0x00, 0x55, 0xCB, 0x00, 0x7C, 0x2B, 0x68, 0x38, 0xFD, 0x20, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xCB, 0xF1, 0x62, 0xD0, 0x03, 0x51, 0x9C, 0x08, 0x62, 0xD0, 0x03, 0x55, 0xD8, 0x00, 0x18, 0x53, 0xD9, 0x62, 0xD0, 0x03, 0xB7, 0xCB + } + }, + { + 222, + 79, + 0x39, + 0x0109, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x09, 0x3C, 0x9C, 0x00, 0xA0, 0x14, 0x10, 0x62, 0xD0, 0x03, 0x51, 0xD8, 0x08, 0x51, 0xD9, 0x20, 0x7C, 0x1D, 0xD4, 0x20, 0x7C, 0x70, 0xAA, 0x80, 0x0F, 0x10, 0x57, 0x00, 0x50, 0x01, 0x7C, 0x1D, 0xD4, 0x20, 0x70, 0xFE, 0x7C, 0x72, 0x19, 0x7C, 0x71, 0x54, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xCB, 0xF2, 0x62, 0xD0, 0x03, 0x51, 0xD7, 0x08, 0x51, 0xD6, 0x62, 0xD0, 0x03, 0x53, 0xD8, 0x18, 0x53, 0xD9, 0xF0, 0x3E + } + }, + { + 223, + 79, + 0x39, + 0x010A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x0A, 0x10, 0x51, 0xD8, 0x08, 0x51, 0xD9, 0x20, 0x7C, 0x1D, 0xD4, 0x20, 0x7C, 0x70, 0xAA, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x62, 0xD0, 0x03, 0x50, 0x78, 0x3A, 0x9D, 0xD0, 0x07, 0x62, 0xD0, 0x03, 0x55, 0x9D, 0x78, 0x7C, 0x71, 0xD8, 0x53, 0xE9, 0x65, 0xE9, 0x7C, 0x71, 0xD8, 0x64, 0x64, 0x64, 0x02, 0xE9, 0x54, 0x00, 0x80, 0x09, 0x62, 0xD0, 0x03, 0x76, 0x9D, 0x07, 0x00, 0x0A, 0x62, 0xD0, 0x03, 0xA5, 0xA9 + } + }, + { + 224, + 79, + 0x39, + 0x010B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x0B, 0x3C, 0x9D, 0x1A, 0xD0, 0x0A, 0x62, 0xD0, 0x03, 0x52, 0x00, 0x3A, 0x9C, 0xCF, 0xE8, 0x62, 0xD0, 0x03, 0x51, 0x9D, 0x08, 0x62, 0xD0, 0x03, 0x55, 0xD6, 0x00, 0x18, 0x53, 0xD7, 0x62, 0xD0, 0x03, 0x51, 0x9D, 0x7C, 0x70, 0x0E, 0x7C, 0x6D, 0x9C, 0x62, 0xD0, 0x03, 0x51, 0xD7, 0x62, 0xD0, 0x00, 0x04, 0xE8, 0x62, 0xD0, 0x03, 0x51, 0xD6, 0x62, 0xD0, 0x00, 0x0C, 0xE9, 0x7C, 0x70, 0x17, 0x08, 0x59, 0x12 + } + }, + { + 225, + 79, + 0x39, + 0x010C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x0C, 0x51, 0xE9, 0x62, 0xD0, 0x03, 0x53, 0xD6, 0x18, 0x53, 0xD7, 0x62, 0xD0, 0x03, 0x51, 0x9E, 0x08, 0x62, 0xD0, 0x03, 0x55, 0xDA, 0x00, 0x18, 0x53, 0xDB, 0x51, 0xDB, 0x08, 0x51, 0xDA, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x18, 0x53, 0xE8, 0x7C, 0x6D, 0x9C, 0x62, 0xD0, 0x03, 0x51, 0xDB, 0x62, 0xD0, 0x00, 0x04, 0xE8, 0x62, 0xD0, 0x03, 0x51, 0xDA, 0x62, 0xD0, 0x00, 0x0C, 0xE9, 0x7C, 0x70, 0x17, 0x5E, 0x1D + } + }, + { + 226, + 79, + 0x39, + 0x010D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x0D, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x03, 0x53, 0xDA, 0x18, 0x53, 0xDB, 0x38, 0xFF, 0x20, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0xAE, 0x21, 0x0D, 0x60, 0x00, 0x62, 0xD0, 0x00, 0x51, 0xAF, 0x21, 0xBB, 0x60, 0x04, 0x62, 0xD0, 0x00, 0x51, 0xB0, 0x21, 0x74, 0x7C, 0x73, 0x1E, 0x21, 0x00, 0x7C, 0x73, 0x16, 0x21, 0x10, 0x60, 0x10, 0x71, 0x10, 0x5D, 0x00, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x53, 0xCA, 0x71, 0x6E, 0x3E + } + }, + { + 227, + 79, + 0x39, + 0x010E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x0E, 0x10, 0x5D, 0x04, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x53, 0xC9, 0x71, 0x10, 0x5D, 0x08, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x53, 0xC8, 0x71, 0x10, 0x5D, 0x0C, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x53, 0xC7, 0x71, 0x10, 0x5D, 0x10, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x53, 0xC6, 0x71, 0x10, 0x43, 0x00, 0xF2, 0x43, 0x04, 0x44, 0x43, 0x08, 0x8B, 0x43, 0x0C, 0xFF, 0x43, 0x10, 0xEF, 0x70, 0xCF, 0x7F, 0x62, 0x34, 0xCB + } + }, + { + 228, + 79, + 0x39, + 0x010F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x0F, 0xD0, 0x04, 0x51, 0xCA, 0x71, 0x10, 0x60, 0x00, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x51, 0xC9, 0x71, 0x10, 0x60, 0x04, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x51, 0xC8, 0x71, 0x10, 0x60, 0x08, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x51, 0xC7, 0x71, 0x10, 0x60, 0x0C, 0x70, 0xCF, 0x62, 0xD0, 0x04, 0x51, 0xC6, 0x71, 0x10, 0x60, 0x10, 0x70, 0xCF, 0x62, 0xD0, 0x00, 0x51, 0xAE, 0x60, 0x00, 0x62, 0xD0, 0x00, 0xB4, 0xCC + } + }, + { + 229, + 79, + 0x39, + 0x0110, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x10, 0x7C, 0x72, 0x51, 0x51, 0xB0, 0x7C, 0x73, 0x1E, 0x7C, 0x73, 0x16, 0x60, 0x10, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x71, 0x10, 0x43, 0xEC, 0x01, 0x70, 0xFE, 0x70, 0xCF, 0x9F, 0x32, 0x7C, 0x31, 0xC5, 0x62, 0xD0, 0x00, 0x39, 0x00, 0xB0, 0x3A, 0x7C, 0x72, 0xCB, 0x10, 0x70, 0xCF, 0x7C, 0x1D, 0xD0, 0x7C, 0x20, 0x7C, 0x20, 0x62, 0xDB, 0xFE, 0x7C, 0x39, 0xF1, 0x71, 0x10, 0x43, 0xD7, 0x20, 0x43, 0x58, 0x15 + } + }, + { + 230, + 79, + 0x39, + 0x0111, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x11, 0xC9, 0x80, 0x70, 0xCF, 0x43, 0xFF, 0x08, 0x71, 0x10, 0x41, 0xC9, 0x7F, 0x41, 0xD7, 0xDF, 0x40, 0x7C, 0x73, 0x7B, 0x70, 0xCF, 0x7C, 0x3A, 0x08, 0x10, 0x7C, 0x20, 0x78, 0x7C, 0x1D, 0xCC, 0x20, 0x62, 0xD0, 0x00, 0x55, 0xBB, 0xFF, 0x62, 0xD0, 0x03, 0x26, 0x99, 0xFD, 0x9F, 0x51, 0x71, 0x01, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x56, 0x00, 0x00, 0x71, 0x10, 0x41, 0xEC, 0xFE, 0x27, 0xB4 + } + }, + { + 231, + 79, + 0x39, + 0x0112, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x12, 0x70, 0xFE, 0x70, 0xCF, 0x9E, 0xC9, 0x7C, 0x71, 0x10, 0x80, 0x6B, 0x7C, 0x32, 0x06, 0x62, 0xD0, 0x00, 0x7C, 0x31, 0xC5, 0x39, 0x00, 0xB0, 0x5A, 0x3D, 0x00, 0x00, 0xB0, 0x55, 0x62, 0xD0, 0x00, 0x3C, 0xBB, 0xFF, 0xA0, 0x4D, 0x7C, 0x39, 0xE9, 0x62, 0xD0, 0x03, 0x51, 0xD9, 0x11, 0x02, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x62, 0xD0, 0x03, 0x51, 0xD8, 0x19, 0x00, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x50, 0x07 + } + }, + { + 232, + 79, + 0x39, + 0x0113, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x13, 0x62, 0xD0, 0x00, 0x51, 0xBB, 0x62, 0xD0, 0x00, 0x7C, 0x70, 0xBF, 0xC0, 0x22, 0x7C, 0x72, 0xCB, 0x56, 0x00, 0x01, 0x10, 0x70, 0xCF, 0x7C, 0x20, 0x7C, 0x20, 0x62, 0xDB, 0xFE, 0x10, 0x7C, 0x0C, 0x80, 0x20, 0x71, 0x10, 0x7C, 0x73, 0x7B, 0x10, 0x70, 0xCF, 0x7C, 0x20, 0x78, 0x20, 0x7C, 0x39, 0xED, 0x71, 0x01, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x51, 0x0D, 0x62, 0xD0, 0x03, 0x12, 0xD5, 0x62, 0x05, 0x72 + } + }, + { + 233, + 79, + 0x39, + 0x0114, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x14, 0xD0, 0x00, 0x53, 0xE8, 0x62, 0xD0, 0x00, 0x51, 0x0C, 0x62, 0xD0, 0x03, 0x1A, 0xD4, 0x7C, 0x72, 0x49, 0x62, 0xD0, 0x03, 0x12, 0xD9, 0x7C, 0x70, 0xA1, 0x1A, 0xD8, 0xCF, 0x6F, 0x52, 0x01, 0x7C, 0x72, 0xB2, 0x9E, 0x9B, 0x7C, 0x71, 0x54, 0x71, 0x01, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x62, 0xD0, 0x04, 0x3C, 0xCB, 0xF2, 0xB0, 0x09, 0x56, 0x01, 0x01, 0x56, 0x00, 0x00, 0x80, 0x34, 0xD1 + } + }, + { + 234, + 79, + 0x39, + 0x0115, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x15, 0x04, 0x7C, 0x6F, 0xC9, 0x62, 0xD0, 0x00, 0x52, 0x01, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x7F, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x56, 0x00, 0x00, 0x10, 0x7C, 0x18, 0x83, 0x7C, 0x19, 0x64, 0x20, 0x56, 0x00, 0x01, 0x52, 0x00, 0x08, 0x7C, 0x2B, 0x34, 0x38, 0xFF, 0x10, 0x7C, 0x18, 0x4D, 0x20, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x04, 0x7C, 0x6F, 0x3C, 0x54, 0x01, 0x3D, 0x3A, 0xDE + } + }, + { + 235, + 79, + 0x39, + 0x0116, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x16, 0x01, 0x50, 0xB0, 0x29, 0x56, 0x00, 0x00, 0x80, 0x1D, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x08, 0x52, 0x00, 0x01, 0x07, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x77, 0x00, 0x3D, 0x00, 0x99, 0xCF, 0xE0, 0x81, 0x85, 0x3D, 0x01, 0x40, 0xB0, 0x29, 0x56, 0x00, 0x00, 0x80, 0x1D, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0x00, 0x0E, 0x92, 0x8F + } + }, + { + 236, + 79, + 0x39, + 0x0117, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x17, 0xE9, 0x05, 0x7C, 0x6D, 0x83, 0x08, 0x52, 0x00, 0x01, 0x07, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x77, 0x00, 0x3D, 0x00, 0x99, 0xCF, 0xE0, 0x81, 0x58, 0x3D, 0x01, 0x70, 0xB0, 0x61, 0x7C, 0x39, 0x34, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x26, 0xE9, 0x01, 0x3C, 0xE9, 0x01, 0xB0, 0x29, 0x56, 0x00, 0x00, 0x80, 0x1D, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x05, 0x7C, 0xC7, 0xFA + } + }, + { + 237, + 79, + 0x39, + 0x0118, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x18, 0x6D, 0x83, 0x08, 0x52, 0x00, 0x01, 0x07, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x77, 0x00, 0x3D, 0x00, 0x99, 0xCF, 0xE0, 0x81, 0x1B, 0x56, 0x00, 0x00, 0x80, 0x1D, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x08, 0x7C, 0x6D, 0x83, 0x08, 0x52, 0x00, 0x01, 0x07, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x77, 0x00, 0x3D, 0x00, 0x99, 0xCF, 0xE0, 0x80, 0xF3, 0x3D, 0x01, 0xF5, 0x57 + } + }, + { + 238, + 79, + 0x39, + 0x0119, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x19, 0x60, 0xB0, 0x95, 0x7C, 0x39, 0x34, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x26, 0xE9, 0x01, 0x3C, 0xE9, 0x01, 0xB0, 0x5D, 0x50, 0x1B, 0x08, 0x50, 0x07, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x56, 0x00, 0x00, 0x80, 0x1D, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x09, 0x7C, 0x6F, 0x5C, 0x08, 0x52, 0x00, 0x01, 0x08, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x77, 0x00, 0x3D, 0xF8, 0x5E + } + }, + { + 239, + 79, + 0x39, + 0x011A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x1A, 0x00, 0x1B, 0xCF, 0xE0, 0x56, 0x02, 0x23, 0x56, 0x00, 0x00, 0x80, 0x1D, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0x25, 0x0E, 0xE9, 0x09, 0x7C, 0x6F, 0x5C, 0x08, 0x52, 0x02, 0x03, 0x00, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x77, 0x00, 0x3D, 0x00, 0x1B, 0xCF, 0xE0, 0x80, 0x82, 0x56, 0x00, 0x00, 0x80, 0x1D, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x0A, 0xCB, 0x05 + } + }, + { + 240, + 79, + 0x39, + 0x011B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x1B, 0x7C, 0x6F, 0x5C, 0x08, 0x52, 0x00, 0x01, 0x07, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x77, 0x00, 0x3D, 0x00, 0x99, 0xCF, 0xE0, 0x80, 0x5A, 0x3D, 0x01, 0x30, 0xB0, 0x55, 0x62, 0xD0, 0x03, 0x3C, 0x9F, 0x99, 0xD0, 0x4D, 0x62, 0xD0, 0x03, 0x51, 0x9F, 0x7C, 0x70, 0x0E, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x08, 0x7C, 0x6D, 0x83, 0x08, 0x50, 0x26, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFE, 0x62, 0xD0, 0xD0, 0x10 + } + }, + { + 241, + 79, + 0x39, + 0x011C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x1C, 0x03, 0x51, 0x9F, 0x7C, 0x70, 0x0E, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x08, 0x50, 0x27, 0x08, 0x7C, 0x32, 0x0C, 0x62, 0xD0, 0x03, 0x51, 0x9F, 0x7C, 0x70, 0x0E, 0x06, 0xE8, 0x00, 0x0E, 0xE9, 0x05, 0x7C, 0x6D, 0x83, 0x08, 0x50, 0x28, 0x08, 0x7C, 0x32, 0x0C, 0x38, 0xFC, 0x38, 0xFC, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x03, 0x56, 0x00, 0x00, 0x80, 0x5A, 0x62, 0xD0, 0x00, 0x3B, 0xE7 + } + }, + { + 242, + 79, + 0x39, + 0x011D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x1D, 0x52, 0x00, 0x7C, 0x6D, 0x8A, 0x06, 0xE8, 0xE6, 0x0E, 0xE9, 0x01, 0x7C, 0x6D, 0x83, 0x54, 0x01, 0x3E, 0xE8, 0x54, 0x02, 0x7C, 0x70, 0x01, 0x7C, 0x70, 0xD4, 0x52, 0x02, 0x53, 0xE9, 0x7C, 0x6E, 0xAE, 0x06, 0xE6, 0xB8, 0x0E, 0xE7, 0x03, 0x7C, 0x6D, 0xEA, 0x52, 0x00, 0x7C, 0x6D, 0x8A, 0x06, 0xE8, 0xE4, 0x0E, 0xE9, 0x01, 0x7C, 0x6D, 0x83, 0x54, 0x01, 0x3E, 0xE8, 0x54, 0x02, 0x7C, 0x70, 0xCA, 0x06 + } + }, + { + 243, + 79, + 0x39, + 0x011E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x1E, 0xD4, 0x7C, 0x70, 0x01, 0x52, 0x02, 0x53, 0xE9, 0x7C, 0x6E, 0xAE, 0x06, 0xE6, 0xB4, 0x0E, 0xE7, 0x03, 0x7C, 0x6D, 0xEA, 0x77, 0x00, 0x7C, 0x6F, 0xC1, 0xCF, 0xA3, 0x52, 0xFC, 0x08, 0x7C, 0x5F, 0x91, 0x38, 0xFF, 0x38, 0xFD, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x7C, 0x6F, 0xB9, 0x52, 0xFB, 0x60, 0xD5, 0x50, 0x04, 0x3F, 0xE8, 0x52, 0xFC, 0x01, 0x01, 0x7C, 0x71, 0x76, 0x50, 0x01, 0x3F, 0x32, 0xD7 + } + }, + { + 244, + 79, + 0x39, + 0x011F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x1F, 0xE8, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x03, 0x7C, 0x6F, 0xB9, 0x52, 0xFB, 0x7C, 0x6D, 0xE3, 0x47, 0xE9, 0x80, 0xBF, 0xF4, 0x52, 0xFC, 0x53, 0xE8, 0x52, 0xFB, 0x60, 0xD4, 0x3E, 0xE8, 0x39, 0x04, 0xB0, 0x73, 0x56, 0x00, 0x00, 0x80, 0x3F, 0x62, 0xD0, 0x00, 0x52, 0x00, 0x7C, 0x6D, 0x8A, 0x52, 0xFC, 0x01, 0x02, 0x53, 0xE6, 0x52, 0xFB, 0x09, 0x00, 0x53, 0xE7, 0x51, 0xE8, 0x02, 0x69, 0x46 + } + }, + { + 245, + 79, + 0x39, + 0x0120, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x20, 0xE6, 0x53, 0xE6, 0x51, 0xE9, 0x0A, 0xE7, 0x53, 0xE7, 0x06, 0xE8, 0xE0, 0x0E, 0xE9, 0x01, 0x51, 0xE9, 0x60, 0xD4, 0x51, 0xE7, 0x60, 0xD5, 0x10, 0x57, 0x08, 0x62, 0xD0, 0x00, 0x3E, 0xE8, 0x3F, 0xE6, 0x79, 0xBF, 0xF7, 0x20, 0x77, 0x00, 0x7C, 0x6F, 0xC1, 0xCF, 0xBE, 0x62, 0xD0, 0x00, 0x52, 0xFC, 0x01, 0x22, 0x7C, 0x71, 0x76, 0x52, 0xF9, 0x3F, 0xE8, 0x52, 0xFC, 0x01, 0x23, 0x7C, 0x71, 0xE7, 0x43 + } + }, + { + 246, + 79, + 0x39, + 0x0121, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x21, 0x76, 0x52, 0xFA, 0x3F, 0xE8, 0x52, 0xFC, 0x01, 0x24, 0x7C, 0x71, 0x76, 0x62, 0xD0, 0x00, 0x51, 0xA2, 0x62, 0xD0, 0x00, 0x3F, 0xE8, 0x7C, 0x6F, 0xB9, 0x52, 0xFB, 0x60, 0xD5, 0x50, 0x84, 0x3F, 0xE8, 0x38, 0xFD, 0x20, 0x7F, 0x10, 0x4F, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x62, 0xD0, 0x03, 0x55, 0xAC, 0x19, 0x55, 0xAB, 0x00, 0x55, 0xAA, 0x02, 0x62, 0xD0, 0x00, 0x55, 0xE8, 0x00, 0x55, 0xDD, 0x30 + } + }, + { + 247, + 79, + 0x39, + 0x0122, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x22, 0xE9, 0x0A, 0x7C, 0x6D, 0xAD, 0x7C, 0x6D, 0xC6, 0x51, 0xE8, 0x62, 0xD0, 0x03, 0x53, 0xA9, 0x10, 0x50, 0x03, 0x08, 0x50, 0xA9, 0x5C, 0x18, 0x7C, 0x20, 0x98, 0x20, 0x62, 0xD0, 0x03, 0x50, 0x00, 0x01, 0x80, 0x53, 0xAB, 0x50, 0x02, 0x09, 0x00, 0x53, 0xAA, 0x62, 0xD0, 0x00, 0x55, 0xE8, 0x00, 0x55, 0xE9, 0x0A, 0x7C, 0x6D, 0xC6, 0x7C, 0x6D, 0xAD, 0x7C, 0x73, 0x3C, 0x51, 0xE8, 0x62, 0xD0, 0xBC, 0xEF + } + }, + { + 248, + 79, + 0x39, + 0x0123, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x23, 0x03, 0x53, 0xA9, 0x10, 0x50, 0x03, 0x08, 0x50, 0xA9, 0x5C, 0x18, 0x7C, 0x20, 0x98, 0x20, 0x62, 0xD0, 0x03, 0x55, 0xAB, 0xA0, 0x55, 0xAA, 0x01, 0x62, 0xD0, 0x00, 0x55, 0xE8, 0x00, 0x55, 0xE9, 0x0A, 0x7C, 0x6D, 0xC6, 0x7C, 0x6D, 0xAD, 0x16, 0xE8, 0x02, 0x1E, 0xE9, 0x00, 0x51, 0xE8, 0x62, 0xD0, 0x03, 0x53, 0xA9, 0x10, 0x50, 0x03, 0x08, 0x50, 0xA9, 0x5C, 0x18, 0x7C, 0x20, 0x98, 0x62, 0xDB, 0x2E + } + }, + { + 249, + 79, + 0x39, + 0x0124, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x24, 0xD0, 0x00, 0x20, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x50, 0x99, 0x7C, 0x12, 0x90, 0x20, 0x9F, 0x59, 0x10, 0x7C, 0x10, 0x74, 0x20, 0x7F, 0x10, 0x4F, 0x10, 0x52, 0xFB, 0x08, 0x52, 0xFC, 0x20, 0x7C, 0x13, 0xEA, 0x20, 0x9F, 0x44, 0x10, 0x7C, 0x10, 0x74, 0x20, 0x20, 0x7F, 0x10, 0x4F, 0x10, 0x52, 0xFC, 0x7C, 0x13, 0xFB, 0x20, 0x9F, 0x32, 0x10, 0x7C, 0x10, 0x74, 0x20, 0x20, 0x7F, 0x10, 0x4F, 0x86, 0x85 + } + }, + { + 250, + 79, + 0x39, + 0x0125, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x25, 0x10, 0x52, 0xFC, 0x7C, 0x14, 0x0D, 0x20, 0x9F, 0x20, 0x10, 0x7C, 0x10, 0x74, 0x20, 0x20, 0x7F, 0x10, 0x4F, 0x52, 0xFC, 0x62, 0xD0, 0x00, 0x53, 0xA9, 0x20, 0x7F, 0x62, 0xD0, 0x00, 0x55, 0x39, 0x08, 0x55, 0x3A, 0x08, 0x55, 0x3B, 0x08, 0x55, 0x3C, 0x08, 0x55, 0x01, 0x00, 0x55, 0x00, 0x00, 0x55, 0x03, 0x00, 0x55, 0x02, 0x00, 0x55, 0x05, 0x00, 0x55, 0x04, 0x00, 0x55, 0x07, 0x00, 0x55, 0x71, 0x5C + } + }, + { + 251, + 79, + 0x39, + 0x0126, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x26, 0x06, 0x00, 0x55, 0x55, 0x20, 0x55, 0x54, 0x01, 0x55, 0x53, 0x00, 0x43, 0xE6, 0x01, 0x43, 0xE0, 0x08, 0x7F, 0x49, 0xE0, 0x08, 0xB0, 0x05, 0x50, 0x00, 0x80, 0x07, 0x08, 0x7C, 0x54, 0x59, 0x38, 0xFF, 0x7F, 0x08, 0x7C, 0x54, 0xA4, 0x38, 0xFF, 0x7F, 0x08, 0x7C, 0x54, 0xB5, 0x38, 0xFF, 0x7F, 0x08, 0x7C, 0x54, 0xE2, 0x38, 0xFF, 0x7F, 0x08, 0x7C, 0x54, 0xF3, 0x38, 0xFF, 0x7F, 0x08, 0x7C, 0x84, 0x83 + } + }, + { + 252, + 79, + 0x39, + 0x0127, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x27, 0x55, 0x04, 0x38, 0xFF, 0x7F, 0x08, 0x7C, 0x55, 0x15, 0x38, 0xFF, 0x7F, 0x08, 0x7C, 0x5B, 0xE0, 0x38, 0xFF, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x50, 0x00, 0x3D, 0xF9, 0x80, 0xC0, 0x06, 0x7C, 0x4A, 0x8B, 0x50, 0xC0, 0x3D, 0xF5, 0x80, 0xC0, 0x0C, 0x10, 0x4B, 0x11, 0x04, 0x4B, 0x7C, 0x4A, 0x8B, 0x31, 0x80, 0x20, 0x08, 0x7C, 0x4A, 0x28, 0x18, 0x6A, 0xD0, 0x04, 0x7C, 0x4A, 0x8B, 0x6A, 0xD0, 0x1F, 0xBA + } + }, + { + 253, + 79, + 0x39, + 0x0128, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x28, 0x08, 0x4B, 0x11, 0x04, 0x4B, 0x7C, 0x4A, 0x8B, 0x38, 0xFF, 0x20, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x10, 0x4F, 0x5D, 0xD0, 0x08, 0x62, 0xD0, 0x00, 0x7C, 0x4A, 0xA3, 0x51, 0xE1, 0x54, 0xFB, 0x18, 0x60, 0xD0, 0x20, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x5D, 0xD0, 0x08, 0x62, 0xD0, 0x00, 0x51, 0xE9, 0x08, 0x50, 0x00, 0x53, 0xE9, 0x53, 0xE1, 0x53, 0xE0, 0x53, 0xDF, 0x56, 0x00, 0x20, 0x66, 0xFC, 0xD7, 0x2B + } + }, + { + 254, + 79, + 0x39, + 0x0129, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x29, 0x6C, 0xFB, 0x6C, 0xFA, 0x6C, 0xF9, 0x6B, 0xDF, 0x6B, 0xE0, 0x6B, 0xE1, 0x6B, 0xE9, 0x51, 0xDF, 0x1B, 0xF8, 0x51, 0xE0, 0x1B, 0xF7, 0x51, 0xE1, 0x1B, 0xF6, 0x51, 0xE9, 0x1B, 0xF5, 0xC0, 0x11, 0x53, 0xE9, 0x52, 0xF8, 0x14, 0xDF, 0x52, 0xF7, 0x1C, 0xE0, 0x52, 0xF6, 0x1C, 0xE1, 0x77, 0xFC, 0x7B, 0x00, 0xBF, 0xCB, 0x51, 0xDF, 0x54, 0xF8, 0x51, 0xE0, 0x54, 0xF7, 0x51, 0xE1, 0x54, 0xF6, 0x3A, 0xF2 + } + }, + { + 255, + 79, + 0x39, + 0x012A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x2A, 0x51, 0xE9, 0x54, 0xF5, 0x18, 0x53, 0xE9, 0x18, 0x60, 0xD0, 0x7F, 0x37, 0xFC, 0xFF, 0x77, 0xFC, 0x37, 0xFB, 0xFF, 0x0F, 0xFB, 0x00, 0x37, 0xFA, 0xFF, 0x0F, 0xFA, 0x00, 0x37, 0xF9, 0xFF, 0x0F, 0xF9, 0x00, 0x7F, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x08, 0x66, 0xFC, 0x6B, 0xE1, 0x51, 0xE1, 0x1B, 0xFB, 0xC0, 0x05, 0x53, 0xE1, 0x77, 0xFC, 0x7A, 0xE0, 0xBF, 0xEF, 0x7F, 0x08, 0x10, 0x4F, 0x50, 0x80, 0x7F + } + }, + { + 256, + 79, + 0x39, + 0x012B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x2B, 0x00, 0x6F, 0xFF, 0xD0, 0x03, 0x03, 0xFE, 0x66, 0xFE, 0xBF, 0xF7, 0x38, 0xFE, 0x70, 0x3F, 0x71, 0xC0, 0x7F, 0x7C, 0x6E, 0x82, 0x7C, 0x6E, 0xC0, 0x7F, 0x62, 0xD0, 0x01, 0x51, 0xE7, 0x08, 0x51, 0xE6, 0x62, 0xD0, 0x03, 0x53, 0xFE, 0x18, 0x53, 0xFF, 0x62, 0xD0, 0x01, 0x51, 0xE5, 0x08, 0x51, 0xE4, 0x62, 0xD0, 0x03, 0x53, 0xFC, 0x18, 0x53, 0xFD, 0x62, 0xD0, 0x01, 0x51, 0xEF, 0x08, 0x51, 0x7E, 0x7C + } + }, + { + 257, + 79, + 0x39, + 0x012C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x2C, 0xEE, 0x62, 0xD0, 0x03, 0x53, 0xFA, 0x18, 0x53, 0xFB, 0x62, 0xD0, 0x01, 0x51, 0xED, 0x08, 0x51, 0xEC, 0x62, 0xD0, 0x03, 0x53, 0xF8, 0x18, 0x53, 0xF9, 0x7F, 0x62, 0xD0, 0x01, 0x51, 0xE7, 0x02, 0xEF, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x62, 0xD0, 0x01, 0x51, 0xE6, 0x0A, 0xEE, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x7C, 0x6D, 0xF3, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0xA3, 0x18, 0x23, 0xC7 + } + }, + { + 258, + 79, + 0x39, + 0x012D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x2D, 0x53, 0xA4, 0x62, 0xD0, 0x01, 0x51, 0xE5, 0x02, 0xED, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x62, 0xD0, 0x01, 0x51, 0xE4, 0x0A, 0xEC, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x7C, 0x6D, 0xF3, 0x7C, 0x71, 0xC3, 0x7F, 0x10, 0x4F, 0x52, 0xFA, 0x13, 0xFC, 0x52, 0xF9, 0x1B, 0xFB, 0xD0, 0x12, 0x62, 0xD0, 0x00, 0x52, 0xFC, 0x13, 0xFA, 0x53, 0xE8, 0x52, 0xFB, 0x1B, 0xF9, 0x53, 0xE9, 0x80, 0x10, 0x62, 0xD0, 0xB7, 0xF0 + } + }, + { + 259, + 79, + 0x39, + 0x012E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x2E, 0x00, 0x52, 0xFA, 0x13, 0xFC, 0x53, 0xE8, 0x52, 0xF9, 0x1B, 0xFB, 0x53, 0xE9, 0x20, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xCD, 0x00, 0x7C, 0x70, 0x41, 0x7F, 0x62, 0xD0, 0x03, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x9F, 0xB4, 0x38, 0xFC, 0x7C, 0x6F, 0xA8, 0x62, 0xD0, 0x03, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xEE, 0x8C, 0x9B + } + }, + { + 260, + 79, + 0x39, + 0x012F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x2F, 0x08, 0x51, 0xEF, 0x08, 0x9F, 0x9B, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x62, 0xD0, 0x04, 0x04, 0xA0, 0x7C, 0x71, 0x7F, 0x0C, 0x9F, 0x62, 0xD0, 0x03, 0x51, 0xEA, 0x08, 0x51, 0xEB, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE4, 0x08, 0x51, 0xE5, 0x08, 0x9F, 0x78, 0x38, 0xF8, 0x7C, 0x6F, 0x76, 0x62, 0xD0, 0x03, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x6A, 0x58 + } + }, + { + 261, + 79, + 0x39, + 0x0130, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x30, 0x9F, 0x5F, 0x38, 0xFC, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x62, 0xD0, 0x04, 0x04, 0x9E, 0x7C, 0x71, 0x7F, 0x0C, 0x9D, 0x7F, 0x10, 0x7C, 0x1E, 0x80, 0x62, 0xD0, 0x00, 0x5A, 0xE9, 0x20, 0x7C, 0x71, 0x22, 0x51, 0xAE, 0x62, 0xD0, 0x03, 0x12, 0xDD, 0x62, 0xD0, 0x04, 0x53, 0xA6, 0x62, 0xD0, 0x04, 0x51, 0xAD, 0x62, 0xD0, 0x03, 0x1A, 0xDC, 0x62, 0xD0, 0x04, 0x53, 0xA5, 0x62, 0xD0, 0x04, 0x51, 0x3D, 0xFF + } + }, + { + 262, + 79, + 0x39, + 0x0131, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x31, 0xAE, 0x08, 0x51, 0xAD, 0x62, 0xD0, 0x03, 0x53, 0xDC, 0x18, 0x53, 0xDD, 0x62, 0xD0, 0x04, 0x51, 0xA6, 0x62, 0xD0, 0x00, 0x53, 0xE6, 0x62, 0xD0, 0x04, 0x51, 0xA5, 0x62, 0xD0, 0x00, 0x53, 0xE7, 0x62, 0xD0, 0x04, 0x51, 0xA0, 0x08, 0x62, 0xD0, 0x00, 0x18, 0x53, 0xE5, 0x55, 0xE4, 0x00, 0x65, 0xE5, 0x65, 0xE4, 0x6B, 0xE5, 0x51, 0xE4, 0x53, 0xE2, 0x51, 0xE5, 0x53, 0xE3, 0x50, 0x00, 0x08, 0x8B, 0x9C + } + }, + { + 263, + 79, + 0x39, + 0x0132, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x32, 0x08, 0x51, 0xE7, 0x08, 0x51, 0xE6, 0x08, 0x50, 0x00, 0x08, 0x08, 0x51, 0xE3, 0x08, 0x51, 0xE2, 0x08, 0x7C, 0x49, 0xD3, 0x18, 0x53, 0xE6, 0x18, 0x53, 0xE7, 0x18, 0x18, 0x38, 0xFC, 0x51, 0xE6, 0x53, 0xE8, 0x51, 0xE7, 0x53, 0xE9, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0x9B, 0x18, 0x53, 0x9C, 0x62, 0xD0, 0x04, 0x51, 0xA6, 0x62, 0xD0, 0x00, 0x53, 0xE6, 0x62, 0xD0, 0x04, 0x57, 0x35 + } + }, + { + 264, + 79, + 0x39, + 0x0133, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x33, 0x51, 0xA5, 0x62, 0xD0, 0x00, 0x53, 0xE7, 0x62, 0xD0, 0x04, 0x51, 0x9E, 0x08, 0x62, 0xD0, 0x00, 0x18, 0x53, 0xE5, 0x55, 0xE4, 0x00, 0x65, 0xE5, 0x65, 0xE4, 0x6B, 0xE5, 0x51, 0xE4, 0x53, 0xE2, 0x51, 0xE5, 0x53, 0xE3, 0x50, 0x00, 0x08, 0x08, 0x51, 0xE7, 0x08, 0x51, 0xE6, 0x08, 0x50, 0x00, 0x08, 0x08, 0x51, 0xE3, 0x08, 0x51, 0xE2, 0x08, 0x7C, 0x49, 0xD3, 0x18, 0x53, 0xE6, 0x18, 0x53, 0x0D, 0xA2 + } + }, + { + 265, + 79, + 0x39, + 0x0134, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x34, 0xE7, 0x18, 0x18, 0x38, 0xFC, 0x51, 0xE6, 0x53, 0xE8, 0x51, 0xE7, 0x53, 0xE9, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0x99, 0x18, 0x53, 0x9A, 0x7C, 0x6E, 0x82, 0x7F, 0x10, 0x4F, 0x38, 0x08, 0x62, 0xD0, 0x04, 0x55, 0xDE, 0x00, 0x62, 0xD0, 0x04, 0x55, 0xDD, 0x00, 0x52, 0xFC, 0x03, 0xFA, 0x54, 0x01, 0x52, 0xFB, 0x0B, 0xF9, 0x54, 0x00, 0x52, 0xF8, 0x03, 0xF6, 0x54, 0x03, 0x0D, 0xA3 + } + }, + { + 266, + 79, + 0x39, + 0x0135, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x35, 0x52, 0xF7, 0x0B, 0xF5, 0x54, 0x02, 0x52, 0xFC, 0x03, 0xF6, 0x54, 0x05, 0x52, 0xFB, 0x0B, 0xF5, 0x54, 0x04, 0x52, 0xFA, 0x03, 0xF8, 0x54, 0x07, 0x52, 0xF9, 0x0B, 0xF7, 0x54, 0x06, 0x52, 0xFC, 0x13, 0xF8, 0x52, 0xFB, 0x1B, 0xF7, 0xC0, 0x43, 0x7C, 0x72, 0xD4, 0xD0, 0x1E, 0x62, 0xD0, 0x04, 0x55, 0xDE, 0x01, 0x52, 0x01, 0x13, 0x03, 0x52, 0x00, 0x1B, 0x02, 0xD0, 0x06, 0x7C, 0x73, 0x66, 0x05, 0x94 + } + }, + { + 267, + 79, + 0x39, + 0x0136, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x36, 0x80, 0x69, 0x62, 0xD0, 0x04, 0x55, 0xDD, 0x02, 0x80, 0x61, 0x62, 0xD0, 0x04, 0x55, 0xDE, 0x02, 0x52, 0x07, 0x13, 0x05, 0x52, 0x06, 0x1B, 0x04, 0xD0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xDD, 0x02, 0x80, 0x49, 0x62, 0xD0, 0x04, 0x55, 0xDD, 0x03, 0x80, 0x41, 0x7C, 0x72, 0xD4, 0xC0, 0x21, 0x62, 0xD0, 0x04, 0x55, 0xDE, 0x03, 0x52, 0x03, 0x13, 0x01, 0x52, 0x02, 0x1B, 0x00, 0xD0, 0x09, 0x62, 0xE1, 0x4D + } + }, + { + 268, + 79, + 0x39, + 0x0137, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x37, 0xD0, 0x04, 0x55, 0xDD, 0x03, 0x80, 0x24, 0x62, 0xD0, 0x04, 0x55, 0xDD, 0x04, 0x80, 0x1C, 0x62, 0xD0, 0x04, 0x55, 0xDE, 0x04, 0x52, 0x05, 0x13, 0x07, 0x52, 0x04, 0x1B, 0x06, 0xD0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xDD, 0x04, 0x80, 0x04, 0x7C, 0x73, 0x66, 0x62, 0xD0, 0x04, 0x3C, 0xDD, 0x01, 0xB0, 0x29, 0x62, 0xD0, 0x00, 0x52, 0xF3, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x52, 0xFA, 0x02, 0xE8, 0x77, 0x7A + } + }, + { + 269, + 79, + 0x39, + 0x0138, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x38, 0x53, 0xE8, 0x52, 0xF9, 0x0A, 0xE9, 0x53, 0xE9, 0x51, 0xE8, 0x13, 0xF6, 0x51, 0xE9, 0x1B, 0xF5, 0xD0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xE0, 0x01, 0x80, 0x97, 0x62, 0xD0, 0x04, 0x3C, 0xDD, 0x02, 0xB0, 0x29, 0x62, 0xD0, 0x00, 0x52, 0xF4, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x52, 0xF8, 0x02, 0xE8, 0x53, 0xE8, 0x52, 0xF7, 0x0A, 0xE9, 0x53, 0xE9, 0x51, 0xE8, 0x13, 0xFC, 0x51, 0xE9, 0x1B, 0xFB, 0x37, 0xFB + } + }, + { + 270, + 79, + 0x39, + 0x0139, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x39, 0xD0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xE0, 0x02, 0x80, 0x67, 0x62, 0xD0, 0x04, 0x3C, 0xDD, 0x03, 0xB0, 0x29, 0x62, 0xD0, 0x00, 0x52, 0xF3, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x52, 0xF6, 0x02, 0xE8, 0x53, 0xE8, 0x52, 0xF5, 0x0A, 0xE9, 0x53, 0xE9, 0x51, 0xE8, 0x13, 0xFA, 0x51, 0xE9, 0x1B, 0xF9, 0xD0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xE0, 0x03, 0x80, 0x37, 0x62, 0xD0, 0x04, 0x3C, 0xDD, 0x04, 0x10, 0xAE + } + }, + { + 271, + 79, + 0x39, + 0x013A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x3A, 0xB0, 0x29, 0x62, 0xD0, 0x00, 0x52, 0xF4, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x52, 0xFC, 0x02, 0xE8, 0x53, 0xE8, 0x52, 0xFB, 0x0A, 0xE9, 0x53, 0xE9, 0x51, 0xE8, 0x13, 0xF8, 0x51, 0xE9, 0x1B, 0xF7, 0xD0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xE0, 0x04, 0x80, 0x07, 0x62, 0xD0, 0x04, 0x55, 0xE0, 0x00, 0x62, 0xD0, 0x04, 0x3C, 0xDE, 0x04, 0xB0, 0x15, 0x62, 0xD0, 0x04, 0x3C, 0xDD, 0x01, 0xB0, 0x0D, 0xA9, 0xE1 + } + }, + { + 272, + 79, + 0x39, + 0x013B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x3B, 0x62, 0xD0, 0x04, 0x51, 0xDE, 0x01, 0x03, 0x62, 0xD0, 0x00, 0x80, 0x15, 0x62, 0xD0, 0x04, 0x51, 0xDE, 0x62, 0xD0, 0x04, 0x02, 0xDD, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x16, 0xE9, 0x02, 0x51, 0xE9, 0x38, 0xF8, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x09, 0x52, 0xF7, 0x3B, 0xFB, 0xB0, 0x1B, 0x52, 0xF8, 0x3B, 0xFC, 0xB0, 0x15, 0x52, 0xF5, 0x3B, 0xF9, 0xB0, 0x0F, 0x52, 0xF6, 0x3B, 0xFA, 0xB0, 0x09, 0xC2, 0x14 + } + }, + { + 273, + 79, + 0x39, + 0x013C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x3C, 0x62, 0xD0, 0x04, 0x55, 0xDF, 0xFE, 0x81, 0x17, 0x52, 0xF7, 0x3B, 0xFB, 0xB0, 0x07, 0x52, 0xF8, 0x3B, 0xFC, 0xA0, 0x0D, 0x52, 0xF5, 0x3B, 0xF9, 0xB0, 0x4E, 0x52, 0xF6, 0x3B, 0xFA, 0xB0, 0x48, 0x62, 0xD0, 0x04, 0x3C, 0xCE, 0x00, 0xA0, 0x06, 0x3C, 0xCE, 0x07, 0xB0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xDF, 0x00, 0x80, 0xEA, 0x62, 0xD0, 0x04, 0x3C, 0xCE, 0x01, 0xA0, 0x06, 0x3C, 0xCE, 0x02, 0xC9, 0x23 + } + }, + { + 274, + 79, + 0x39, + 0x013D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x3D, 0xB0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xDF, 0x02, 0x80, 0xD5, 0x62, 0xD0, 0x04, 0x3C, 0xCE, 0x03, 0xA0, 0x06, 0x3C, 0xCE, 0x04, 0xB0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xDF, 0x04, 0x80, 0xC0, 0x62, 0xD0, 0x04, 0x55, 0xDF, 0x06, 0x80, 0xB8, 0x52, 0xFB, 0x08, 0x52, 0xFC, 0x08, 0x52, 0xF7, 0x08, 0x52, 0xF8, 0x08, 0x9B, 0xEC, 0x7C, 0x72, 0x25, 0x52, 0xF9, 0x08, 0x52, 0xFA, 0x08, 0x52, 0xF5, 0x56, 0x3E + } + }, + { + 275, + 79, + 0x39, + 0x013E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x3E, 0x08, 0x52, 0xF6, 0x08, 0x9B, 0xDB, 0x38, 0xF8, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x54, 0x03, 0x51, 0xE9, 0x54, 0x02, 0x52, 0x01, 0x13, 0x03, 0x52, 0x00, 0x1B, 0x02, 0xD0, 0x23, 0x7C, 0x71, 0x2E, 0x7C, 0x70, 0x17, 0x7C, 0x73, 0x4A, 0xD0, 0x09, 0x56, 0x06, 0x01, 0x56, 0x05, 0x00, 0x80, 0x07, 0x56, 0x06, 0x00, 0x56, 0x05, 0x00, 0x62, 0xD0, 0x00, 0x52, 0x06, 0x54, 0x04, 0x80, 0x2C, 0x62, 0x31, 0xF5 + } + }, + { + 276, + 79, + 0x39, + 0x013F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x3F, 0xD0, 0x00, 0x52, 0x03, 0x53, 0xE8, 0x52, 0x02, 0x53, 0xE9, 0x7C, 0x70, 0x17, 0x13, 0x01, 0x51, 0xE9, 0x1B, 0x00, 0xD0, 0x09, 0x56, 0x08, 0x01, 0x56, 0x07, 0x00, 0x80, 0x07, 0x56, 0x08, 0x00, 0x56, 0x07, 0x00, 0x62, 0xD0, 0x00, 0x52, 0x08, 0x54, 0x04, 0x62, 0xD0, 0x04, 0x47, 0xCE, 0x01, 0xB0, 0x1B, 0x62, 0xD0, 0x00, 0x52, 0x04, 0x31, 0x01, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0xCE, 0x79, 0x86 + } + }, + { + 277, + 79, + 0x39, + 0x0140, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x40, 0x62, 0xD0, 0x00, 0x02, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0xDF, 0x80, 0x0D, 0x62, 0xD0, 0x04, 0x51, 0xCE, 0x03, 0x04, 0x62, 0xD0, 0x04, 0x53, 0xDF, 0x62, 0xD0, 0x04, 0x26, 0xDF, 0x07, 0x62, 0xD0, 0x04, 0x51, 0xDF, 0x01, 0x01, 0x62, 0xD0, 0x00, 0x38, 0xF7, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x04, 0x56, 0x00, 0x00, 0x7C, 0x72, 0xAB, 0x10, 0x7C, 0x1E, 0x80, 0x62, 0xD0, 0x00, 0x5A, 0xE9, 0x20, 0xC9, 0x27 + } + }, + { + 278, + 79, + 0x39, + 0x0141, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x41, 0x7C, 0x71, 0x22, 0x3D, 0xFC, 0x01, 0xA0, 0x62, 0x3D, 0xFC, 0x00, 0xB0, 0x41, 0x62, 0xD0, 0x04, 0x55, 0xD0, 0x00, 0x62, 0xD0, 0x04, 0x7C, 0x71, 0xEE, 0x3C, 0xAD, 0x00, 0xB0, 0x06, 0x3C, 0xAE, 0x00, 0xA0, 0x22, 0x62, 0xD0, 0x04, 0x51, 0xAE, 0x08, 0x51, 0xAD, 0x62, 0xD0, 0x04, 0x53, 0xAF, 0x18, 0x53, 0xB0, 0x10, 0x50, 0x00, 0x5C, 0x7C, 0x1E, 0xB4, 0x20, 0x62, 0xD0, 0x03, 0x55, 0xDD, 0x4B, 0x2C + } + }, + { + 279, + 79, + 0x39, + 0x0142, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x42, 0x00, 0x55, 0xDC, 0x00, 0x62, 0xD0, 0x04, 0x55, 0xA6, 0x00, 0x55, 0xA5, 0x00, 0x7C, 0x72, 0x85, 0x62, 0xD0, 0x03, 0x55, 0xE3, 0x00, 0x55, 0xE2, 0x00, 0x7C, 0x73, 0x5F, 0x62, 0xD0, 0x04, 0x55, 0xD4, 0x00, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x83, 0xAD, 0x62, 0xD0, 0x04, 0x3C, 0xDC, 0x01, 0xA0, 0x2A, 0x62, 0xD0, 0x01, 0x51, 0xE7, 0x08, 0x51, 0xE6, 0x62, 0xD0, 0x03, 0x53, 0xFE, 0x18, 0x53, 0x86, 0xA3 + } + }, + { + 280, + 79, + 0x39, + 0x0143, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x43, 0xFF, 0x62, 0xD0, 0x01, 0x51, 0xE5, 0x08, 0x51, 0xE4, 0x62, 0xD0, 0x03, 0x53, 0xFC, 0x18, 0x53, 0xFD, 0x7C, 0x6E, 0x82, 0x62, 0xD0, 0x04, 0x55, 0xDC, 0x01, 0x62, 0xD0, 0x01, 0x51, 0xE7, 0x08, 0x51, 0xE6, 0x62, 0xD0, 0x03, 0x53, 0xF6, 0x18, 0x53, 0xF7, 0x62, 0xD0, 0x01, 0x51, 0xE5, 0x08, 0x51, 0xE4, 0x62, 0xD0, 0x03, 0x53, 0xF4, 0x18, 0x53, 0xF5, 0x62, 0xD0, 0x00, 0x51, 0x3A, 0x08, 0x36, 0x04 + } + }, + { + 281, + 79, + 0x39, + 0x0144, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x44, 0x62, 0xD0, 0x00, 0x51, 0x39, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xEA, 0x08, 0x51, 0xEB, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE4, 0x08, 0x51, 0xE5, 0x08, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x9B, 0xF5, 0x38, 0xF6, 0x7C, 0x71, 0x88, 0x54, 0x00, 0x3D, 0x00, 0x00, 0xA1, 0x23, 0x3D, 0xFB, 0x00, 0xA1, 0x1E, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0xAC, 0xF1 + } + }, + { + 282, + 79, + 0x39, + 0x0145, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x45, 0x62, 0xD0, 0x03, 0x51, 0xE3, 0x21, 0x0F, 0x62, 0xD0, 0x00, 0x53, 0xE6, 0x62, 0xD0, 0x03, 0x51, 0xE2, 0x21, 0x00, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0xB0, 0x07, 0x51, 0xE6, 0x3A, 0xE8, 0xA0, 0xA4, 0x62, 0xD0, 0x03, 0x65, 0xE3, 0x6B, 0xE2, 0x65, 0xE3, 0x6B, 0xE2, 0x65, 0xE3, 0x6B, 0xE2, 0x65, 0xE3, 0x6B, 0xE2, 0x52, 0x00, 0x2C, 0xE3, 0x3C, 0xE2, 0x12, 0xB0, 0x06, 0x3C, 0xE3, 0x34, 0xA0, 0xF9, 0x8C + } + }, + { + 283, + 79, + 0x39, + 0x0146, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x46, 0x28, 0x62, 0xD0, 0x03, 0x3C, 0xE2, 0x23, 0xB0, 0x06, 0x3C, 0xE3, 0x41, 0xA0, 0x1B, 0x62, 0xD0, 0x03, 0x3C, 0xE2, 0x34, 0xB0, 0x06, 0x3C, 0xE3, 0x12, 0xA0, 0x0E, 0x62, 0xD0, 0x03, 0x3C, 0xE2, 0x41, 0xB0, 0x14, 0x3C, 0xE3, 0x23, 0xB0, 0x0F, 0x62, 0xD0, 0x04, 0x55, 0xD2, 0x01, 0x62, 0xD0, 0x04, 0x55, 0xD3, 0x01, 0x80, 0x49, 0x62, 0xD0, 0x03, 0x3C, 0xE2, 0x43, 0xB0, 0x06, 0x3C, 0xE3, 0xD5, 0x45 + } + }, + { + 284, + 79, + 0x39, + 0x0147, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x47, 0x21, 0xA0, 0x28, 0x62, 0xD0, 0x03, 0x3C, 0xE2, 0x32, 0xB0, 0x06, 0x3C, 0xE3, 0x14, 0xA0, 0x1B, 0x62, 0xD0, 0x03, 0x3C, 0xE2, 0x21, 0xB0, 0x06, 0x3C, 0xE3, 0x43, 0xA0, 0x0E, 0x62, 0xD0, 0x03, 0x3C, 0xE2, 0x14, 0xB0, 0x14, 0x3C, 0xE3, 0x32, 0xB0, 0x0F, 0x62, 0xD0, 0x04, 0x55, 0xD2, 0x00, 0x62, 0xD0, 0x04, 0x55, 0xD3, 0x01, 0x80, 0x07, 0x62, 0xD0, 0x04, 0x55, 0xD3, 0x00, 0x7C, 0x72, 0x16, 0xC8 + } + }, + { + 285, + 79, + 0x39, + 0x0148, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x48, 0x85, 0x80, 0x3D, 0x7C, 0x73, 0x0E, 0x39, 0x00, 0xA0, 0x36, 0x62, 0xD0, 0x04, 0x3C, 0xD3, 0x01, 0xB0, 0x2E, 0x62, 0xD0, 0x00, 0x7C, 0x73, 0x0E, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0xD1, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0xA0, 0x08, 0x62, 0xD0, 0x04, 0x76, 0xD1, 0x80, 0x13, 0x62, 0xD0, 0x03, 0x55, 0xE3, 0x00, 0x55, 0xE2, 0x00, 0x7C, 0x72, 0x85, 0x62, 0xD0, 0x04, 0x55, 0xD3, 0x00, 0x62, 0x19, 0xCF + } + }, + { + 286, + 79, + 0x39, + 0x0149, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x49, 0xD0, 0x04, 0x3C, 0xD3, 0x01, 0xB0, 0x11, 0x62, 0xD0, 0x04, 0x3C, 0xD2, 0x01, 0xB0, 0x06, 0x56, 0x00, 0x28, 0x80, 0x04, 0x56, 0x00, 0x29, 0x3D, 0x00, 0x00, 0xA0, 0x3E, 0x62, 0xD0, 0x03, 0x51, 0xEA, 0x08, 0x51, 0xEB, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE4, 0x08, 0x51, 0xE5, 0x08, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x9C, 0x65, 0x38, 0x1E, 0xDA + } + }, + { + 287, + 79, + 0x39, + 0x014A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x4A, 0xF8, 0x54, 0x03, 0x3D, 0xFB, 0x00, 0xA0, 0x09, 0x62, 0xD0, 0x04, 0x3C, 0xD3, 0x00, 0xB0, 0x0A, 0x52, 0x03, 0x54, 0x00, 0x66, 0x00, 0x07, 0x00, 0x0E, 0x7C, 0x72, 0xF6, 0x39, 0x00, 0xA1, 0x5B, 0x3D, 0x00, 0x00, 0xA1, 0x56, 0x3D, 0x00, 0x28, 0xA1, 0x51, 0x3D, 0x00, 0x29, 0xA1, 0x4C, 0x62, 0xD0, 0x03, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0xB4, 0x07 + } + }, + { + 288, + 79, + 0x39, + 0x014B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x4B, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x7C, 0x6F, 0xA8, 0x62, 0xD0, 0x03, 0x51, 0xEA, 0x08, 0x51, 0xEB, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE4, 0x08, 0x51, 0xE5, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x7C, 0x6F, 0x76, 0x62, 0xD0, 0x04, 0x52, 0x00, 0x3A, 0xD4, 0xB0, 0x04, 0x56, 0x01, 0x01, 0x3D, 0x01, 0x00, 0xB0, 0xD4, 0x62, 0xD0, 0x00, 0x3C, 0x39, 0x00, 0xB0, 0x73, 0x62, 0xD0, 0x00, 0x3C, 0xBA, 0x14 + } + }, + { + 289, + 79, + 0x39, + 0x014C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x4C, 0x3A, 0x00, 0xB0, 0x6B, 0x62, 0xD0, 0x04, 0x3C, 0x9F, 0x00, 0xB0, 0x06, 0x3C, 0xA0, 0x00, 0xA0, 0x0E, 0x62, 0xD0, 0x04, 0x3C, 0x9D, 0x00, 0xB0, 0x56, 0x3C, 0x9E, 0x00, 0xB0, 0x51, 0x3D, 0x00, 0x10, 0xB0, 0x13, 0x62, 0xD0, 0x04, 0x3C, 0xD4, 0x14, 0xA0, 0x06, 0x3C, 0xD4, 0x1C, 0xB0, 0x3F, 0x56, 0x01, 0x01, 0x80, 0x3A, 0x3D, 0x00, 0x1C, 0xB0, 0x13, 0x62, 0xD0, 0x04, 0x3C, 0xD4, 0x18, 0x4D, 0x3B + } + }, + { + 290, + 79, + 0x39, + 0x014D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x4D, 0xA0, 0x06, 0x3C, 0xD4, 0x10, 0xB0, 0x28, 0x56, 0x01, 0x01, 0x80, 0x23, 0x3D, 0x00, 0x18, 0xA0, 0x06, 0x3D, 0x00, 0x14, 0xB0, 0x19, 0x62, 0xD0, 0x00, 0x52, 0x00, 0x11, 0x04, 0x7C, 0x70, 0x27, 0xA0, 0x0A, 0x52, 0x00, 0x01, 0x04, 0x7C, 0x70, 0x27, 0xB0, 0x04, 0x56, 0x01, 0x01, 0x3D, 0x00, 0x10, 0xB0, 0x18, 0x62, 0xD0, 0x04, 0x3C, 0xD4, 0x10, 0xA0, 0x0B, 0x3C, 0xD4, 0x1E, 0xA0, 0x06, 0x64, 0x6A + } + }, + { + 291, + 79, + 0x39, + 0x014E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x4E, 0x3C, 0xD4, 0x12, 0xB0, 0x43, 0x56, 0x01, 0x01, 0x80, 0x3E, 0x3D, 0x00, 0x1E, 0xB0, 0x18, 0x62, 0xD0, 0x04, 0x3C, 0xD4, 0x1E, 0xA0, 0x0B, 0x3C, 0xD4, 0x10, 0xA0, 0x06, 0x3C, 0xD4, 0x1C, 0xB0, 0x27, 0x56, 0x01, 0x01, 0x80, 0x22, 0x62, 0xD0, 0x04, 0x51, 0xD4, 0x3B, 0x00, 0xA0, 0x16, 0x62, 0xD0, 0x00, 0x52, 0x00, 0x11, 0x02, 0x7C, 0x70, 0x27, 0xA0, 0x0A, 0x52, 0x00, 0x01, 0x02, 0x7C, 0x91, 0xC5 + } + }, + { + 292, + 79, + 0x39, + 0x014F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x4F, 0x70, 0x27, 0xB0, 0x04, 0x56, 0x01, 0x01, 0x3D, 0x01, 0x01, 0xB0, 0x22, 0x62, 0xD0, 0x00, 0x7C, 0x72, 0xF6, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0xD5, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0xA0, 0x1B, 0x7C, 0x6E, 0x82, 0x62, 0xD0, 0x04, 0x76, 0xD5, 0x56, 0x00, 0x00, 0x80, 0x0E, 0x7C, 0x73, 0x5F, 0x52, 0x00, 0x62, 0xD0, 0x04, 0x53, 0xD4, 0x56, 0x00, 0x00, 0x3D, 0x00, 0x00, 0xA0, 0x52, 0x62, 0x27, 0xF2 + } + }, + { + 293, + 79, + 0x39, + 0x0150, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x50, 0xD0, 0x03, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x7C, 0x6F, 0xA8, 0x62, 0xD0, 0x03, 0x51, 0xEA, 0x08, 0x51, 0xEB, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE4, 0x08, 0x51, 0xE5, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x7C, 0x6F, 0x76, 0x7C, 0x4C, 0x14, 0x62, 0xD0, 0x04, 0x52, 0x00, 0x3A, 0xD0, 0xB0, 0x08, 0x62, 0xAE, 0x01 + } + }, + { + 294, + 79, + 0x39, + 0x0151, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x51, 0xD0, 0x04, 0x76, 0xCF, 0x80, 0x04, 0x7C, 0x70, 0x5B, 0x52, 0x00, 0x62, 0xD0, 0x04, 0x53, 0xD0, 0x52, 0x00, 0x62, 0xD0, 0x00, 0x38, 0xFC, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x03, 0x7C, 0x72, 0xAB, 0x56, 0x00, 0x00, 0x52, 0xFC, 0x08, 0x7C, 0x5B, 0xE0, 0x38, 0xFF, 0x62, 0xD0, 0x00, 0x54, 0x00, 0x52, 0xFC, 0x08, 0x90, 0x6D, 0x62, 0xD0, 0x00, 0x54, 0x01, 0x52, 0xFC, 0x08, 0x90, 0x96, 0x38, 0xC7, 0x34 + } + }, + { + 295, + 79, + 0x39, + 0x0152, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x52, 0xFE, 0x62, 0xD0, 0x00, 0x54, 0x02, 0x3D, 0x00, 0x00, 0xA0, 0x05, 0x52, 0x00, 0x80, 0x12, 0x3D, 0x01, 0x00, 0xA0, 0x08, 0x52, 0x01, 0x62, 0xD0, 0x00, 0x80, 0x06, 0x52, 0x02, 0x62, 0xD0, 0x00, 0x38, 0xFD, 0x20, 0x7F, 0x10, 0x4F, 0x50, 0x00, 0x08, 0x52, 0xFC, 0x08, 0x9B, 0x7E, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x01, 0x56, 0x00, 0x00, 0x50, 0x01, 0x08, 0x52, 0x5D, 0x61 + } + }, + { + 296, + 79, + 0x39, + 0x0153, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x53, 0xFC, 0x08, 0x9B, 0x68, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x54, 0x00, 0x3D, 0x00, 0x28, 0xA0, 0x0A, 0x3D, 0x00, 0x29, 0xA0, 0x05, 0x50, 0x00, 0x80, 0x06, 0x52, 0x00, 0x62, 0xD0, 0x00, 0x38, 0xFF, 0x20, 0x7F, 0x10, 0x4F, 0x50, 0x01, 0x08, 0x52, 0xFC, 0x08, 0x9B, 0x40, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x20, 0x7F, 0x10, 0x4F, 0x50, 0x00, 0x08, 0x52, 0xFC, 0x08, 0x90, 0x29, 0x38, 0xFE, 0x62, 0x5B, 0x5E + } + }, + { + 297, + 79, + 0x39, + 0x0154, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x54, 0xD0, 0x00, 0x20, 0x7F, 0x10, 0x4F, 0x50, 0x01, 0x08, 0x52, 0xFC, 0x08, 0x90, 0x18, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x20, 0x7F, 0x10, 0x4F, 0x50, 0x02, 0x08, 0x52, 0xFC, 0x08, 0x90, 0x07, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x0F, 0x56, 0x00, 0x00, 0x56, 0x04, 0x00, 0x7C, 0x72, 0xAB, 0x56, 0x03, 0x00, 0x10, 0x7C, 0x1E, 0x80, 0x62, 0xD0, 0x00, 0x5A, 0xE9, 0x20, 0xE0, 0x69 + } + }, + { + 298, + 79, + 0x39, + 0x0155, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x55, 0x7C, 0x71, 0x22, 0x10, 0x7C, 0x1E, 0x8D, 0x62, 0xD0, 0x00, 0x5A, 0xE9, 0x20, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0xAB, 0x18, 0x53, 0xAC, 0x3D, 0xFC, 0x02, 0xD0, 0x61, 0x3D, 0xFC, 0x00, 0xB0, 0x41, 0x62, 0xD0, 0x04, 0x55, 0xD0, 0x00, 0x62, 0xD0, 0x04, 0x7C, 0x71, 0xEE, 0x3C, 0xAD, 0x00, 0xB0, 0x06, 0x3C, 0xAE, 0x00, 0xA0, 0x22, 0x62, 0xD0, 0x04, 0x51, 0xAE, 0x08, 0x51, 0xAD, 0xF0, 0x8A + } + }, + { + 299, + 79, + 0x39, + 0x0156, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x56, 0x62, 0xD0, 0x04, 0x53, 0xAF, 0x18, 0x53, 0xB0, 0x10, 0x50, 0x00, 0x5C, 0x7C, 0x1E, 0xB4, 0x20, 0x62, 0xD0, 0x03, 0x55, 0xDD, 0x00, 0x55, 0xDC, 0x00, 0x62, 0xD0, 0x04, 0x55, 0xA6, 0x00, 0x55, 0xA5, 0x00, 0x62, 0xD0, 0x04, 0x55, 0xDB, 0x00, 0x7C, 0x73, 0x58, 0x7C, 0x73, 0x51, 0x10, 0x50, 0x00, 0x5C, 0x7C, 0x1E, 0xCC, 0x20, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x83, 0x71, 0x3D, 0xFC, 0x02, 0x20, 0xEB + } + }, + { + 300, + 79, + 0x39, + 0x0157, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x57, 0xB3, 0x0F, 0x62, 0xD0, 0x04, 0x3C, 0xDC, 0x02, 0xD0, 0x55, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x7C, 0x72, 0x01, 0x20, 0x3C, 0xE9, 0x00, 0xB0, 0x05, 0x39, 0x00, 0xA0, 0x33, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x7C, 0x72, 0x01, 0x53, 0xE8, 0x20, 0x62, 0xD0, 0x04, 0x51, 0xAC, 0x62, 0xD0, 0x00, 0x12, 0xE8, 0x62, 0xD0, 0x04, 0x51, 0xAB, 0x62, 0xD0, 0x00, 0x1A, 0xE9, 0xD0, 0x1A, 0x7C, 0xE0, 0x6C + } + }, + { + 301, + 79, + 0x39, + 0x0158, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x58, 0x4A, 0xD9, 0x7C, 0x4A, 0xD2, 0x7C, 0x4B, 0x1A, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x83, 0x1F, 0x7C, 0x4A, 0xD9, 0x7C, 0x4A, 0xD2, 0x7C, 0x4B, 0x1A, 0x62, 0xD0, 0x04, 0x55, 0xDC, 0x02, 0x3D, 0xFB, 0x01, 0xA0, 0x06, 0x3D, 0xFB, 0x02, 0xB1, 0x49, 0x62, 0xD0, 0x01, 0x51, 0xEE, 0x08, 0x51, 0xEF, 0x08, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x7C, 0x4B, 0x61, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x54, 0x7C, 0xA5 + } + }, + { + 302, + 79, + 0x39, + 0x0159, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x59, 0x08, 0x51, 0xE9, 0x54, 0x07, 0x62, 0xD0, 0x01, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x51, 0xE4, 0x08, 0x51, 0xE5, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xF8, 0x7C, 0x71, 0xF5, 0x62, 0xD0, 0x03, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x7C, 0x4B, 0x61, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x54, 0x0C, 0x51, 0xE9, 0x54, 0x0B, 0x62, 0xD0, 0x03, 0x51, 0x9A, 0xE2 + } + }, + { + 303, + 79, + 0x39, + 0x015A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x5A, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xEA, 0x08, 0x51, 0xEB, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xF8, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x54, 0x0E, 0x51, 0xE9, 0x54, 0x0D, 0x52, 0x08, 0x13, 0x0C, 0x54, 0x06, 0x52, 0x07, 0x1B, 0x0B, 0x54, 0x05, 0x62, 0xD0, 0x00, 0x51, 0x3B, 0x62, 0xD0, 0x00, 0x13, 0x06, 0x52, 0x05, 0x31, 0x80, 0x53, 0xE1, 0x50, 0x00, 0x31, 0x80, 0x1A, 0xE1, 0x96, 0xDB + } + }, + { + 304, + 79, + 0x39, + 0x015B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x5B, 0xD0, 0x06, 0x56, 0x00, 0x48, 0x80, 0x41, 0x52, 0x06, 0x11, 0x00, 0x52, 0x05, 0x31, 0x80, 0x19, 0x80, 0xD0, 0x35, 0x62, 0xD0, 0x00, 0x52, 0x06, 0x73, 0x53, 0xE8, 0x52, 0x05, 0x73, 0x53, 0xE9, 0x51, 0xE8, 0x01, 0x01, 0x54, 0x06, 0x51, 0xE9, 0x09, 0x00, 0x54, 0x05, 0x62, 0xD0, 0x00, 0x51, 0x3B, 0x62, 0xD0, 0x00, 0x13, 0x06, 0x52, 0x05, 0x31, 0x80, 0x53, 0xE1, 0x50, 0x00, 0x31, 0x80, 0xF9, 0xA2 + } + }, + { + 305, + 79, + 0x39, + 0x015C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x5C, 0x1A, 0xE1, 0xD0, 0x04, 0x56, 0x00, 0x49, 0x52, 0x0A, 0x13, 0x0E, 0x54, 0x06, 0x52, 0x09, 0x1B, 0x0D, 0x54, 0x05, 0x62, 0xD0, 0x00, 0x51, 0x3C, 0x62, 0xD0, 0x00, 0x13, 0x06, 0x52, 0x05, 0x31, 0x80, 0x53, 0xE1, 0x50, 0x00, 0x31, 0x80, 0x1A, 0xE1, 0xD0, 0x06, 0x56, 0x00, 0x48, 0x80, 0x41, 0x52, 0x06, 0x11, 0x00, 0x52, 0x05, 0x31, 0x80, 0x19, 0x80, 0xD0, 0x35, 0x62, 0xD0, 0x00, 0x52, 0x2B, 0x07 + } + }, + { + 306, + 79, + 0x39, + 0x015D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x5D, 0x06, 0x73, 0x53, 0xE8, 0x52, 0x05, 0x73, 0x53, 0xE9, 0x51, 0xE8, 0x01, 0x01, 0x54, 0x06, 0x51, 0xE9, 0x09, 0x00, 0x54, 0x05, 0x62, 0xD0, 0x00, 0x51, 0x3C, 0x62, 0xD0, 0x00, 0x13, 0x06, 0x52, 0x05, 0x31, 0x80, 0x53, 0xE1, 0x50, 0x00, 0x31, 0x80, 0x1A, 0xE1, 0xD0, 0x04, 0x56, 0x00, 0x49, 0x3D, 0xFB, 0x00, 0xA0, 0x06, 0x3D, 0xFB, 0x02, 0xB1, 0x57, 0x62, 0xD0, 0x00, 0x51, 0x3A, 0x08, 0x85, 0xBC + } + }, + { + 307, + 79, + 0x39, + 0x015E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x5E, 0x62, 0xD0, 0x00, 0x51, 0x39, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xEA, 0x08, 0x51, 0xEB, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE4, 0x08, 0x51, 0xE5, 0x08, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x7C, 0x4D, 0x1E, 0x7C, 0x71, 0x88, 0x54, 0x01, 0x62, 0xD0, 0x03, 0x51, 0xEA, 0x08, 0x51, 0xEB, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x92, 0xD7 + } + }, + { + 308, + 79, + 0x39, + 0x015F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x5F, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xE4, 0x08, 0x51, 0xE5, 0x08, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x7C, 0x4E, 0xE4, 0x38, 0xEE, 0x54, 0x02, 0x62, 0xD0, 0x00, 0x51, 0x3A, 0x08, 0x62, 0xD0, 0x00, 0x51, 0x39, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x51, 0xEE, 0x51, 0x56 + } + }, + { + 309, + 79, + 0x39, + 0x0160, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x60, 0x08, 0x51, 0xEF, 0x08, 0x7C, 0x4D, 0x1E, 0x7C, 0x71, 0x88, 0x05, 0x01, 0x62, 0xD0, 0x03, 0x51, 0xE6, 0x08, 0x51, 0xE7, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x08, 0x62, 0xD0, 0x01, 0x51, 0xEC, 0x08, 0x51, 0xED, 0x08, 0x51, 0xEE, 0x08, 0x51, 0xEF, 0x08, 0x7C, 0x4E, 0xE4, 0x38, 0xEE, 0x62, 0xD0, 0x00, 0x54, 0x03, 0x3D, 0x01, 0x00, 0xA0, 0x95, 0x3D, 0x02, 0xFF, 0xA0, 0x19, 0xE7 + } + }, + { + 310, + 79, + 0x39, + 0x0161, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x61, 0x90, 0x3D, 0x03, 0xFF, 0xA0, 0x8B, 0x52, 0x02, 0x3B, 0x03, 0xA0, 0x0B, 0x3D, 0x02, 0x08, 0xB0, 0x0B, 0x3D, 0x03, 0x01, 0xB0, 0x06, 0x7C, 0x71, 0xB9, 0x80, 0x76, 0x3D, 0x03, 0x08, 0xB0, 0x11, 0x3D, 0x02, 0x01, 0xB0, 0x0C, 0x52, 0x03, 0x03, 0x03, 0x54, 0x00, 0x07, 0x00, 0x2E, 0x80, 0x61, 0x52, 0x03, 0x3B, 0x02, 0xD0, 0x2C, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x13, 0x03, 0x39, 0x01, 0xB0, 0xAA, 0x0A + } + }, + { + 311, + 79, + 0x39, + 0x0162, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x62, 0x21, 0x50, 0x02, 0x08, 0x52, 0x02, 0x08, 0x7C, 0x4A, 0x10, 0x38, 0xFF, 0x18, 0x39, 0x00, 0xB0, 0x06, 0x7C, 0x71, 0xB9, 0x80, 0x3B, 0x52, 0x03, 0x03, 0x03, 0x54, 0x00, 0x07, 0x00, 0x2E, 0x80, 0x30, 0x52, 0x02, 0x3B, 0x03, 0xD0, 0x2A, 0x62, 0xD0, 0x00, 0x52, 0x03, 0x13, 0x02, 0x39, 0x01, 0xB0, 0x1F, 0x50, 0x02, 0x08, 0x52, 0x03, 0x08, 0x7C, 0x4A, 0x10, 0x38, 0xFF, 0x18, 0x39, 0x00, 0x26, 0x03 + } + }, + { + 312, + 79, + 0x39, + 0x0163, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x63, 0xB0, 0x0C, 0x52, 0x03, 0x03, 0x03, 0x54, 0x00, 0x07, 0x00, 0x2E, 0x80, 0x04, 0x7C, 0x71, 0xB9, 0x3D, 0x00, 0x00, 0xA0, 0x54, 0x3D, 0xFB, 0x01, 0xB0, 0x0D, 0x52, 0x00, 0x08, 0x90, 0x52, 0x38, 0xFF, 0x62, 0xD0, 0x00, 0x80, 0x48, 0x3D, 0xFB, 0x00, 0xB0, 0x12, 0x62, 0xD0, 0x00, 0x52, 0x00, 0x29, 0x30, 0x08, 0x91, 0x16, 0x38, 0xFF, 0x62, 0xD0, 0x00, 0x80, 0x32, 0x3D, 0xFB, 0x02, 0xB0, 0xE9, 0x8A + } + }, + { + 313, + 79, + 0x39, + 0x0164, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x64, 0x28, 0x3D, 0x00, 0x48, 0xA0, 0x06, 0x3D, 0x00, 0x49, 0xB0, 0x0D, 0x52, 0x00, 0x08, 0x90, 0x21, 0x38, 0xFF, 0x62, 0xD0, 0x00, 0x80, 0x17, 0x62, 0xD0, 0x00, 0x52, 0x00, 0x29, 0x30, 0x08, 0x90, 0xEA, 0x38, 0xFF, 0x62, 0xD0, 0x00, 0x80, 0x06, 0x62, 0xD0, 0x00, 0x50, 0x00, 0x38, 0xF1, 0x20, 0x7F, 0x10, 0x4F, 0x7C, 0x72, 0xEE, 0x39, 0x00, 0xA0, 0x43, 0x3D, 0xFC, 0x00, 0xA0, 0x3E, 0x62, 0x09, 0xCB + } + }, + { + 314, + 79, + 0x39, + 0x0165, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x65, 0xD0, 0x04, 0x52, 0xFC, 0x3A, 0xD7, 0xB0, 0x2C, 0x62, 0xD0, 0x00, 0x7C, 0x72, 0xEE, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0xD8, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0xA0, 0x21, 0x7C, 0x72, 0x39, 0x39, 0x00, 0xA0, 0x04, 0x7C, 0x72, 0x8C, 0x7C, 0x4A, 0xD2, 0x62, 0xD0, 0x04, 0x76, 0xD8, 0x56, 0xFC, 0x00, 0x80, 0x0A, 0x62, 0xD0, 0x04, 0x55, 0xD8, 0x00, 0x7C, 0x72, 0x59, 0x7C, 0x72, 0x39, 0x39, 0x4E, 0x56 + } + }, + { + 315, + 79, + 0x39, + 0x0166, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x66, 0x00, 0xA0, 0x4D, 0x3D, 0xFC, 0x00, 0xA0, 0x48, 0x62, 0xD0, 0x04, 0x3C, 0xDB, 0x00, 0xA0, 0x3D, 0x3C, 0xDB, 0x48, 0xA0, 0x38, 0x3C, 0xDB, 0x49, 0xA0, 0x33, 0x62, 0xD0, 0x04, 0x52, 0xFC, 0x3A, 0xD7, 0xB0, 0x22, 0x62, 0xD0, 0x00, 0x7C, 0x72, 0x39, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0xD6, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0xA0, 0x19, 0x7C, 0x4A, 0xD2, 0x62, 0xD0, 0x04, 0x76, 0xD6, 0x56, 0x79, 0xAD + } + }, + { + 316, + 79, + 0x39, + 0x0167, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x67, 0xFC, 0x00, 0x80, 0x0C, 0x7C, 0x72, 0x8C, 0x7C, 0x72, 0x59, 0x80, 0x04, 0x7C, 0x72, 0x8C, 0x3D, 0xFC, 0x00, 0xA0, 0x31, 0x7C, 0x4B, 0x1A, 0x7C, 0x4B, 0x99, 0x7C, 0x4C, 0x14, 0x7C, 0x6E, 0xC0, 0x62, 0xD0, 0x04, 0x52, 0xFC, 0x3A, 0xD0, 0xB0, 0x08, 0x62, 0xD0, 0x04, 0x76, 0xCF, 0x80, 0x04, 0x7C, 0x70, 0x5B, 0x52, 0xFC, 0x62, 0xD0, 0x04, 0x53, 0xD0, 0x52, 0xFC, 0x62, 0xD0, 0x04, 0x53, 0x22, 0x00 + } + }, + { + 317, + 79, + 0x39, + 0x0168, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x68, 0xDB, 0x7C, 0x73, 0x58, 0x52, 0xFC, 0x62, 0xD0, 0x00, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x05, 0x7C, 0x72, 0x64, 0x39, 0x00, 0xA1, 0x10, 0x56, 0x00, 0x00, 0x3D, 0xFC, 0x00, 0xA1, 0x08, 0x7C, 0x4B, 0x99, 0x62, 0xD0, 0x04, 0x52, 0xFC, 0x3A, 0xD9, 0xB0, 0x04, 0x56, 0x00, 0x01, 0x3D, 0x00, 0x00, 0xB0, 0xC1, 0x62, 0xD0, 0x00, 0x3C, 0x39, 0x00, 0xB0, 0x73, 0x62, 0xD0, 0x00, 0x3C, 0x3A, 0x00, 0x73, 0xA3 + } + }, + { + 318, + 79, + 0x39, + 0x0169, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x69, 0xB0, 0x6B, 0x62, 0xD0, 0x04, 0x3C, 0x9F, 0x00, 0xB0, 0x06, 0x3C, 0xA0, 0x00, 0xA0, 0x0E, 0x62, 0xD0, 0x04, 0x3C, 0x9D, 0x00, 0xB0, 0x56, 0x3C, 0x9E, 0x00, 0xB0, 0x51, 0x3D, 0xFC, 0x30, 0xB0, 0x13, 0x62, 0xD0, 0x04, 0x3C, 0xD9, 0x34, 0xA0, 0x06, 0x3C, 0xD9, 0x3C, 0xB0, 0x3F, 0x56, 0x00, 0x01, 0x80, 0x3A, 0x3D, 0xFC, 0x3C, 0xB0, 0x13, 0x62, 0xD0, 0x04, 0x3C, 0xD9, 0x38, 0xA0, 0x06, 0x5F, 0x7C + } + }, + { + 319, + 79, + 0x39, + 0x016A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x6A, 0x3C, 0xD9, 0x30, 0xB0, 0x28, 0x56, 0x00, 0x01, 0x80, 0x23, 0x3D, 0xFC, 0x38, 0xA0, 0x06, 0x3D, 0xFC, 0x34, 0xB0, 0x19, 0x62, 0xD0, 0x00, 0x52, 0xFC, 0x11, 0x04, 0x7C, 0x70, 0x34, 0xA0, 0x0A, 0x52, 0xFC, 0x01, 0x04, 0x7C, 0x70, 0x34, 0xB0, 0x04, 0x56, 0x00, 0x01, 0x3D, 0xFC, 0x30, 0xB0, 0x13, 0x62, 0xD0, 0x04, 0x3C, 0xD9, 0x3E, 0xA0, 0x06, 0x3C, 0xD9, 0x32, 0xB0, 0x35, 0x56, 0x00, 0x1E, 0xFB + } + }, + { + 320, + 79, + 0x39, + 0x016B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x6B, 0x01, 0x80, 0x30, 0x3D, 0xFC, 0x3E, 0xB0, 0x13, 0x62, 0xD0, 0x04, 0x3C, 0xD9, 0x30, 0xA0, 0x06, 0x3C, 0xD9, 0x3C, 0xB0, 0x1E, 0x56, 0x00, 0x01, 0x80, 0x19, 0x62, 0xD0, 0x00, 0x52, 0xFC, 0x11, 0x02, 0x7C, 0x70, 0x34, 0xA0, 0x0A, 0x52, 0xFC, 0x01, 0x02, 0x7C, 0x70, 0x34, 0xB0, 0x04, 0x56, 0x00, 0x01, 0x3D, 0x00, 0x01, 0xB0, 0x1F, 0x7C, 0x72, 0x64, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0x16, 0xEC + } + }, + { + 321, + 79, + 0x39, + 0x016C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x6C, 0xDA, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0xA0, 0x1E, 0x7C, 0x4A, 0xD2, 0x62, 0xD0, 0x04, 0x76, 0xDA, 0x56, 0xFC, 0x00, 0x80, 0x11, 0x62, 0xD0, 0x04, 0x55, 0xDA, 0x00, 0x52, 0xFC, 0x62, 0xD0, 0x04, 0x53, 0xD9, 0x56, 0xFC, 0x00, 0x3D, 0xFC, 0x00, 0xA0, 0xAE, 0x7C, 0x4B, 0x1A, 0x62, 0xD0, 0x03, 0x51, 0xED, 0x62, 0xD0, 0x03, 0x02, 0xE9, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x62, 0xD0, 0x03, 0x51, 0x43, 0x47 + } + }, + { + 322, + 79, + 0x39, + 0x016D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x6D, 0xEC, 0x62, 0xD0, 0x03, 0x0A, 0xE8, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x7C, 0x6D, 0xF3, 0x51, 0xE8, 0x54, 0x02, 0x51, 0xE9, 0x54, 0x01, 0x62, 0xD0, 0x03, 0x51, 0xEB, 0x62, 0xD0, 0x03, 0x02, 0xE7, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x62, 0xD0, 0x03, 0x51, 0xEA, 0x62, 0xD0, 0x03, 0x0A, 0xE6, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x7C, 0x6D, 0xF3, 0x51, 0xE8, 0x54, 0x04, 0x51, 0xE9, 0x54, 0x03, 0x52, 0xCA, 0x56 + } + }, + { + 323, + 79, + 0x39, + 0x016E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x6E, 0x01, 0x08, 0x52, 0x02, 0x08, 0x62, 0xD0, 0x04, 0x51, 0xA3, 0x08, 0x51, 0xA4, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x7C, 0x6F, 0xA8, 0x52, 0x03, 0x08, 0x52, 0x04, 0x08, 0x62, 0xD0, 0x04, 0x51, 0xA1, 0x08, 0x51, 0xA2, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x7C, 0x6F, 0x76, 0x7C, 0x4C, 0x14, 0x7C, 0x6E, 0xC0, 0x62, 0xD0, 0x04, 0x52, 0xFC, 0x3A, 0xD0, 0xB0, 0x08, 0x62, 0xD0, 0x04, 0x76, 0x9E, 0xFF + } + }, + { + 324, + 79, + 0x39, + 0x016F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x6F, 0xCF, 0x80, 0x04, 0x7C, 0x70, 0x5B, 0x52, 0xFC, 0x62, 0xD0, 0x04, 0x53, 0xD0, 0x52, 0xFC, 0x62, 0xD0, 0x04, 0x53, 0xDB, 0x7C, 0x73, 0x51, 0x52, 0xFC, 0x62, 0xD0, 0x00, 0x38, 0xFB, 0x20, 0x7F, 0x10, 0x4F, 0x50, 0x00, 0x08, 0x52, 0xFC, 0x08, 0x90, 0x07, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x04, 0x7C, 0x6F, 0xC9, 0x3D, 0xFB, 0x00, 0xA0, 0x37, 0x62, 0xD0, 0x04, 0xC4, 0x4C + } + }, + { + 325, + 79, + 0x39, + 0x0170, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x70, 0x55, 0xCD, 0x00, 0x7C, 0x70, 0x62, 0x50, 0x0D, 0x10, 0x08, 0x57, 0x8D, 0x28, 0x53, 0xE7, 0x18, 0x75, 0x09, 0x00, 0x28, 0x20, 0x02, 0xE8, 0x53, 0xE8, 0x51, 0xE7, 0x0A, 0xE9, 0x10, 0x08, 0x51, 0xE8, 0x20, 0x7C, 0x1E, 0xE4, 0x20, 0x7C, 0x73, 0x43, 0x7C, 0x70, 0x41, 0x52, 0xFB, 0x62, 0xD0, 0x00, 0x83, 0x5B, 0x3D, 0xFC, 0x00, 0xB2, 0xB8, 0x7C, 0x70, 0x5B, 0x10, 0x7C, 0x1E, 0x9A, 0x62, 0xB4, 0x2D + } + }, + { + 326, + 79, + 0x39, + 0x0171, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x71, 0xD0, 0x00, 0x5A, 0xE9, 0x20, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0xA9, 0x18, 0x53, 0xAA, 0x10, 0x7C, 0x1E, 0xA7, 0x62, 0xD0, 0x00, 0x5A, 0xE9, 0x20, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0xA7, 0x18, 0x53, 0xA8, 0x62, 0xD0, 0x04, 0x3C, 0xA9, 0x00, 0xB0, 0x06, 0x3C, 0xAA, 0x00, 0xA1, 0x37, 0x62, 0xD0, 0x04, 0x3C, 0xCC, 0x01, 0xB0, 0xFB, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x3F, 0x44 + } + }, + { + 327, + 79, + 0x39, + 0x0172, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x72, 0x08, 0x57, 0x8F, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x28, 0x20, 0x62, 0xD0, 0x04, 0x12, 0xAA, 0x7C, 0x71, 0x7F, 0x1A, 0xA9, 0xD0, 0xDD, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x08, 0x57, 0x8D, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x28, 0x53, 0xE8, 0x20, 0x7C, 0x70, 0x77, 0xD0, 0xC4, 0x7C, 0x72, 0xB9, 0x3A, 0xFE, 0xB0, 0x08, 0x7C, 0x72, 0xC2, 0x3A, 0xFF, 0xA0, 0x96, 0x62, 0x15, 0xF1 + } + }, + { + 328, + 79, + 0x39, + 0x0173, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x73, 0xD0, 0x03, 0x51, 0xF4, 0x62, 0xD0, 0x03, 0x3A, 0xFC, 0xB0, 0x0D, 0x62, 0xD0, 0x03, 0x51, 0xF5, 0x62, 0xD0, 0x03, 0x3A, 0xFD, 0xA0, 0x7E, 0x62, 0xD0, 0x03, 0x51, 0xF6, 0x08, 0x51, 0xF7, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xFE, 0x08, 0x51, 0xFF, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x57, 0x96, 0x28, 0x20, 0x7C, 0x71, 0xD1, 0xC0, 0x27, 0x62, 0xD0, 0x03, 0x07, 0xD6 + } + }, + { + 329, + 79, + 0x39, + 0x0174, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x74, 0x51, 0xF4, 0x08, 0x51, 0xF5, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xFC, 0x08, 0x51, 0xFD, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x57, 0x97, 0x28, 0x20, 0x7C, 0x71, 0xD1, 0xD0, 0x32, 0x56, 0x01, 0x01, 0x62, 0xD0, 0x04, 0x55, 0xCD, 0x00, 0x7C, 0x70, 0x41, 0x62, 0xD0, 0x03, 0x50, 0x0D, 0x10, 0x08, 0x57, 0x9D, 0x28, 0x53, 0xBC, 0x18, 0x75, 0x09, 0x00, 0x28, 0xDC, 0x81 + } + }, + { + 330, + 79, + 0x39, + 0x0175, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x75, 0x53, 0xBD, 0x20, 0x50, 0x0D, 0x10, 0x08, 0x57, 0x9F, 0x28, 0x53, 0xBE, 0x18, 0x75, 0x09, 0x00, 0x28, 0x53, 0xBF, 0x20, 0x3D, 0x01, 0x00, 0xB0, 0x1C, 0x62, 0xD0, 0x04, 0x3C, 0xCD, 0x00, 0xB0, 0x07, 0x7C, 0x6F, 0x87, 0x56, 0x00, 0x20, 0x62, 0xD0, 0x04, 0x76, 0xCD, 0x3C, 0xCD, 0x01, 0xB0, 0x04, 0x7C, 0x71, 0x3A, 0x62, 0xD0, 0x04, 0x3C, 0xCC, 0x02, 0xB0, 0x2D, 0x62, 0xD0, 0x00, 0x50, 0xA9, 0x1C + } + }, + { + 331, + 79, + 0x39, + 0x0176, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x76, 0x0D, 0x10, 0x08, 0x57, 0x8B, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x28, 0x20, 0x62, 0xD0, 0x04, 0x12, 0xAA, 0x7C, 0x71, 0x7F, 0x1A, 0xA9, 0xD0, 0x0F, 0x7C, 0x70, 0x62, 0x7C, 0x70, 0x77, 0xD0, 0x07, 0x56, 0x00, 0x40, 0x7C, 0x4B, 0x8F, 0x62, 0xD0, 0x03, 0x3C, 0xEE, 0x00, 0xB0, 0x06, 0x3C, 0xEF, 0x00, 0xA1, 0x26, 0x62, 0xD0, 0x04, 0x3C, 0xCC, 0x00, 0xB0, 0x42, 0x62, 0xD0, 0x04, 0x2A, 0x1F + } + }, + { + 332, + 79, + 0x39, + 0x0177, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x77, 0x3C, 0xCD, 0x01, 0xB0, 0x3A, 0x62, 0xD0, 0x03, 0x51, 0xEF, 0x62, 0xD0, 0x04, 0x12, 0xA8, 0x62, 0xD0, 0x03, 0x51, 0xEE, 0x62, 0xD0, 0x04, 0x1A, 0xA7, 0xD0, 0x0D, 0x7C, 0x72, 0x6F, 0x54, 0x03, 0x7C, 0x72, 0x7A, 0x54, 0x02, 0x80, 0x04, 0x7C, 0x70, 0x41, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x7C, 0x72, 0x0D, 0x20, 0x7C, 0x73, 0x4A, 0xD0, 0xEA, 0x7C, 0x4B, 0x8F, 0x80, 0xE5, 0x62, 0xD0, 0x5E, 0x88 + } + }, + { + 333, + 79, + 0x39, + 0x0178, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x78, 0x04, 0x3C, 0xCC, 0x01, 0xB0, 0xDD, 0x7C, 0x73, 0x35, 0xB0, 0xD8, 0x62, 0xD0, 0x03, 0x51, 0xEF, 0x62, 0xD0, 0x04, 0x12, 0xA8, 0x62, 0xD0, 0x03, 0x51, 0xEE, 0x62, 0xD0, 0x04, 0x1A, 0xA7, 0xD0, 0x0D, 0x7C, 0x72, 0x6F, 0x53, 0xEF, 0x7C, 0x72, 0x7A, 0x53, 0xEE, 0x80, 0x04, 0x7C, 0x70, 0x41, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x08, 0x57, 0x93, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x77, 0xBB + } + }, + { + 334, + 79, + 0x39, + 0x0179, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x79, 0x28, 0x20, 0x62, 0xD0, 0x03, 0x12, 0xEF, 0x7C, 0x70, 0xA1, 0x1A, 0xEE, 0xD0, 0x88, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x7C, 0x72, 0x0D, 0x53, 0xE8, 0x20, 0x62, 0xD0, 0x03, 0x51, 0xEF, 0x62, 0xD0, 0x00, 0x12, 0xE8, 0x62, 0xD0, 0x03, 0x51, 0xEE, 0x62, 0xD0, 0x00, 0x1A, 0xE9, 0xD0, 0x66, 0x62, 0xD0, 0x03, 0x51, 0xF6, 0x08, 0x51, 0xF7, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xBC, 0x08, 0x51, 0x4F, 0x6C + } + }, + { + 335, + 79, + 0x39, + 0x017A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x7A, 0xBD, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x57, 0x95, 0x28, 0x20, 0x7C, 0x70, 0xBF, 0xD0, 0x2F, 0x62, 0xD0, 0x03, 0x51, 0xF4, 0x08, 0x51, 0xF5, 0x08, 0x62, 0xD0, 0x03, 0x51, 0xBE, 0x08, 0x51, 0xBF, 0x08, 0x7C, 0x4B, 0x61, 0x38, 0xFC, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x57, 0x95, 0x28, 0x20, 0x7C, 0x70, 0xBF, 0xD0, 0x09, 0x56, 0x00, 0x22, 0x7C, 0x79, 0xC1 + } + }, + { + 336, + 79, + 0x39, + 0x017B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x7B, 0x4B, 0x8F, 0x80, 0x1F, 0x7C, 0x6F, 0x87, 0x62, 0xD0, 0x04, 0x55, 0xCD, 0x01, 0x7C, 0x71, 0x3A, 0x56, 0x00, 0x20, 0x80, 0x0E, 0x7C, 0x4B, 0x8F, 0x80, 0x09, 0x7C, 0x73, 0x35, 0xB0, 0x04, 0x7C, 0x4B, 0x8F, 0x10, 0x50, 0x00, 0x5C, 0x7C, 0x1E, 0xE4, 0x20, 0x7C, 0x73, 0x43, 0x80, 0x53, 0x62, 0xD0, 0x04, 0x3C, 0xCC, 0x00, 0xB0, 0x04, 0x7C, 0x4A, 0xD9, 0x52, 0xFC, 0x62, 0xD0, 0x04, 0x53, 0x43, 0x56 + } + }, + { + 337, + 79, + 0x39, + 0x017C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x7C, 0xCC, 0x62, 0xD0, 0x01, 0x51, 0xE7, 0x08, 0x51, 0xE6, 0x62, 0xD0, 0x03, 0x53, 0xF6, 0x18, 0x53, 0xF7, 0x62, 0xD0, 0x01, 0x51, 0xE5, 0x08, 0x51, 0xE4, 0x62, 0xD0, 0x03, 0x53, 0xF4, 0x18, 0x53, 0xF5, 0x62, 0xD0, 0x01, 0x51, 0xEF, 0x08, 0x51, 0xEE, 0x62, 0xD0, 0x03, 0x53, 0xF2, 0x18, 0x53, 0xF3, 0x62, 0xD0, 0x01, 0x51, 0xED, 0x08, 0x51, 0xEC, 0x62, 0xD0, 0x03, 0x53, 0xF0, 0x18, 0x53, 0x03, 0xD7 + } + }, + { + 338, + 79, + 0x39, + 0x017D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x7D, 0xF1, 0x3D, 0x00, 0x40, 0xB0, 0x43, 0x7C, 0x72, 0xC2, 0x02, 0xF3, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x7C, 0x72, 0xB9, 0x0A, 0xF2, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x7C, 0x6D, 0xF3, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0xA3, 0x18, 0x53, 0xA4, 0x62, 0xD0, 0x03, 0x51, 0xF5, 0x02, 0xF5, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x62, 0xD0, 0x03, 0x51, 0xF4, 0x0A, 0xF4, 0x62, 0xD0, 0x00, 0x9F, 0x10 + } + }, + { + 339, + 79, + 0x39, + 0x017E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x7E, 0x53, 0xE9, 0x7C, 0x6D, 0xF3, 0x7C, 0x71, 0xC3, 0x52, 0x00, 0x62, 0xD0, 0x00, 0x38, 0xFC, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x06, 0x62, 0xD0, 0x00, 0x3C, 0x0E, 0x00, 0xA0, 0x06, 0x3D, 0xFC, 0xFF, 0xB0, 0x09, 0x62, 0xD0, 0x04, 0x55, 0xE2, 0x01, 0x85, 0xED, 0x62, 0xD0, 0x04, 0x3C, 0xE1, 0x00, 0xA0, 0x06, 0x3C, 0xE1, 0xFF, 0xB0, 0x74, 0x56, 0x00, 0x00, 0x56, 0x00, 0x00, 0x80, 0x65, 0x62, 0xDB, 0x89 + } + }, + { + 340, + 79, + 0x39, + 0x017F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x7F, 0xD0, 0x00, 0x52, 0x00, 0x7C, 0x6D, 0x8A, 0x06, 0xE8, 0xE0, 0x7C, 0x71, 0x08, 0x62, 0xD0, 0x04, 0x51, 0xE2, 0x62, 0xD0, 0x00, 0x3F, 0xE8, 0x52, 0x00, 0x7C, 0x6D, 0x8A, 0x06, 0xE8, 0xE1, 0x7C, 0x71, 0x08, 0x7C, 0x72, 0x9A, 0x62, 0xD0, 0x04, 0x76, 0xE2, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xB8, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x7C, 0x6E, 0xAE, 0x06, 0xE6, 0x45, 0x5E + } + }, + { + 341, + 79, + 0x39, + 0x0180, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x80, 0xB0, 0x0E, 0xE7, 0x03, 0x7C, 0x6D, 0xEA, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xB4, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x7C, 0x6E, 0xAE, 0x06, 0xE6, 0xEA, 0x0E, 0xE7, 0x00, 0x7C, 0x6D, 0xEA, 0x77, 0x00, 0x7C, 0x6F, 0xC1, 0xCF, 0x98, 0x85, 0x6D, 0x62, 0xD0, 0x04, 0x50, 0x03, 0x3A, 0xE1, 0xC0, 0x0A, 0x62, 0xD0, 0x00, 0x50, 0x03, 0x3A, 0x0E, 0xD4, 0xAD, 0x7C, 0x71, 0x93, 0xC2, 0xFD, 0xCF + } + }, + { + 342, + 79, + 0x39, + 0x0181, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x81, 0x0D, 0x7C, 0x6F, 0xC9, 0x80, 0x13, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xCC, 0x7C, 0x6F, 0x54, 0x50, 0xFF, 0x3F, 0xE8, 0x77, 0x00, 0x7C, 0x71, 0xB1, 0xCF, 0xEA, 0x56, 0x00, 0x00, 0x80, 0x51, 0x56, 0x02, 0x00, 0x80, 0x41, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x27, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x47, 0xE9, 0x0F, 0xA0, 0x29, 0x52, 0x02, 0x08, 0x95, 0x00 + } + }, + { + 343, + 79, + 0x39, + 0x0182, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x82, 0x52, 0x00, 0x08, 0x95, 0xA1, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x54, 0x03, 0x50, 0x0D, 0x10, 0x57, 0x85, 0x28, 0x20, 0x3B, 0x03, 0xC0, 0x0F, 0x52, 0x02, 0x08, 0x52, 0x00, 0x08, 0x95, 0x0D, 0x38, 0xFE, 0x77, 0x01, 0x80, 0x0C, 0x77, 0x02, 0x62, 0xD0, 0x04, 0x52, 0x02, 0x3A, 0xE1, 0xCF, 0xB8, 0x77, 0x00, 0x7C, 0x6F, 0xC1, 0xCF, 0xAC, 0x7C, 0x71, 0xE1, 0x3D, 0x00, 0x02, 0xA0, 0x06, 0x3D, 0xB2, 0x3B + } + }, + { + 344, + 79, + 0x39, + 0x0183, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x83, 0x00, 0x03, 0xB1, 0x09, 0x7C, 0x68, 0xBB, 0x52, 0x01, 0x08, 0x7C, 0x66, 0xDE, 0x52, 0x01, 0x08, 0x7C, 0x6A, 0x7F, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x51, 0x0E, 0x13, 0x01, 0x62, 0xD0, 0x00, 0x54, 0x00, 0x56, 0x02, 0x00, 0x80, 0xDF, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x27, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0x73, 0xBE + } + }, + { + 345, + 79, + 0x39, + 0x0184, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x84, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xCE, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x54, 0x04, 0x7C, 0x6F, 0x27, 0x06, 0xE8, 0xFD, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x54, 0x03, 0x7C, 0x71, 0x00, 0xCF, 0x77 + } + }, + { + 346, + 79, + 0x39, + 0x0185, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x85, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xC5, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x25, 0x24 + } + }, + { + 347, + 79, + 0x39, + 0x0186, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x86, 0x83, 0x54, 0x03, 0x52, 0x03, 0x7C, 0x6D, 0x8A, 0x06, 0xE8, 0xE0, 0x0E, 0xE9, 0x01, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x7C, 0x72, 0x31, 0x06, 0xE6, 0xD0, 0x0E, 0xE7, 0x03, 0x7C, 0x6D, 0xEA, 0x52, 0x03, 0x7C, 0x6D, 0x8A, 0x06, 0xE8, 0xE1, 0x0E, 0xE9, 0x01, 0x7C, 0x6D, 0x83, 0x7C, 0x6E, 0xE1, 0x52, 0x04, 0x7C, 0x6E, 0xB6, 0x06, 0xE6, 0xD5, 0x0E, 0xE7, 0x02, 0x7C, 0x6D, 0xEA, 0x51, 0xE8, 0x36, 0x47 + } + }, + { + 348, + 79, + 0x39, + 0x0187, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x87, 0x3F, 0xE6, 0x77, 0x02, 0x52, 0x02, 0x3B, 0x00, 0xCF, 0x1D, 0x83, 0x3A, 0x56, 0x00, 0x00, 0x80, 0x76, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x47, 0xE9, 0xF0, 0xA0, 0x5E, 0x56, 0x03, 0x00, 0x56, 0x04, 0xFF, 0x56, 0x02, 0x00, 0x80, 0x3B, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x27, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x02, 0xE0 + } + }, + { + 349, + 79, + 0x39, + 0x0188, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x88, 0x83, 0x53, 0xE9, 0x47, 0xE9, 0x0F, 0xA0, 0x23, 0x52, 0x02, 0x08, 0x52, 0x00, 0x08, 0x94, 0x16, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x54, 0x05, 0x3D, 0x03, 0x00, 0xB0, 0x06, 0x7C, 0x71, 0x4B, 0x80, 0x0A, 0x52, 0x05, 0x3B, 0x03, 0xD0, 0x04, 0x7C, 0x71, 0x4B, 0x77, 0x02, 0x62, 0xD0, 0x04, 0x52, 0x02, 0x3A, 0xE1, 0xCF, 0xBE, 0x3D, 0x04, 0xFF, 0xA0, 0x0B, 0x52, 0x04, 0x08, 0x52, 0x00, 0x08, 0x89, 0xEF + } + }, + { + 350, + 79, + 0x39, + 0x0189, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x89, 0x93, 0x6A, 0x38, 0xFE, 0x77, 0x00, 0x7C, 0x6F, 0xC1, 0xCF, 0x87, 0x82, 0xB9, 0x7C, 0x6F, 0xC9, 0x80, 0x13, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xCC, 0x7C, 0x6F, 0x54, 0x50, 0xFF, 0x3F, 0xE8, 0x77, 0x00, 0x7C, 0x6F, 0xC1, 0xCF, 0xEA, 0x56, 0x00, 0x00, 0x80, 0x51, 0x56, 0x02, 0x00, 0x80, 0x41, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x27, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x7C, 0xED, 0xB8 + } + }, + { + 351, + 79, + 0x39, + 0x018A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x8A, 0x6D, 0x83, 0x53, 0xE9, 0x47, 0xE9, 0xF0, 0xA0, 0x29, 0x52, 0x00, 0x08, 0x52, 0x02, 0x08, 0x93, 0x95, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x54, 0x03, 0x50, 0x0D, 0x10, 0x57, 0x85, 0x28, 0x20, 0x3B, 0x03, 0xC0, 0x0F, 0x52, 0x00, 0x08, 0x52, 0x02, 0x08, 0x93, 0x01, 0x38, 0xFE, 0x77, 0x01, 0x80, 0x0C, 0x77, 0x02, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x3A, 0x0E, 0xCF, 0xB8, 0x77, 0x00, 0x7C, 0x71, 0x06, 0xEB + } + }, + { + 352, + 79, + 0x39, + 0x018B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x8B, 0xB1, 0xCF, 0xAC, 0x7C, 0x71, 0xE1, 0x50, 0x00, 0x3B, 0x00, 0xC0, 0x06, 0x3D, 0x00, 0x04, 0xD1, 0x24, 0x62, 0xD0, 0x00, 0x51, 0x0E, 0x13, 0x01, 0x62, 0xD0, 0x00, 0x39, 0x04, 0xD1, 0x16, 0x7C, 0x68, 0xBB, 0x52, 0x01, 0x08, 0x93, 0xF7, 0x52, 0x01, 0x08, 0x7C, 0x6B, 0x94, 0x38, 0xFE, 0x56, 0x02, 0x00, 0x80, 0xF9, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x27, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x9B, 0x16 + } + }, + { + 353, + 79, + 0x39, + 0x018C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x8C, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xC5, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x54, 0x04, 0x7C, 0x6F, 0x27, 0x98, 0x11 + } + }, + { + 354, + 79, + 0x39, + 0x018D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x8D, 0x06, 0xE8, 0xFD, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x54, 0x03, 0x7C, 0x6F, 0x44, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x7A, 0xE8, 0x53, 0xE7, 0x26, 0xE7, 0xF0, 0x7C, 0x6F, 0xD0, 0x7C, 0x71, 0x00, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xE5, 0xAC + } + }, + { + 355, + 79, + 0x39, + 0x018E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x8E, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xCE, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x54, 0x03, 0x52, 0x04, 0x7C, 0x6D, 0x8A, 0x06, 0xE8, 0xE0, 0x0E, 0xE9, 0x01, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x52, 0x03, 0x53, 0xE6, 0x55, 0xE7, 0x00, 0x06, 0xE6, 0x47, 0x71 + } + }, + { + 356, + 79, + 0x39, + 0x018F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x8F, 0xD0, 0x0E, 0xE7, 0x03, 0x7C, 0x6D, 0xEA, 0x52, 0x04, 0x7C, 0x6D, 0x8A, 0x06, 0xE8, 0xE1, 0x0E, 0xE9, 0x01, 0x7C, 0x6D, 0x83, 0x7C, 0x6E, 0xE1, 0x52, 0x03, 0x7C, 0x6E, 0xB6, 0x06, 0xE6, 0xD5, 0x0E, 0xE7, 0x02, 0x7C, 0x6D, 0xEA, 0x51, 0xE8, 0x3F, 0xE6, 0x77, 0x02, 0x52, 0x02, 0x3B, 0x00, 0xCF, 0x03, 0x80, 0x80, 0x56, 0x00, 0x00, 0x80, 0x76, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0x2C, 0x3C + } + }, + { + 357, + 79, + 0x39, + 0x0190, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x90, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x47, 0xE9, 0x0F, 0xA0, 0x5E, 0x56, 0x03, 0x00, 0x56, 0x04, 0xFF, 0x56, 0x02, 0x00, 0x80, 0x3B, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x27, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x47, 0xE9, 0xF0, 0xA0, 0x23, 0x52, 0x00, 0x08, 0x52, 0x02, 0x08, 0x91, 0xEE, 0x38, 0xFE, 0x62, 0xD0, 0x00, 0x54, 0x05, 0x3D, 0x0E, 0x01 + } + }, + { + 358, + 79, + 0x39, + 0x0191, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x91, 0x03, 0x00, 0xB0, 0x06, 0x7C, 0x71, 0x4B, 0x80, 0x0A, 0x52, 0x05, 0x3B, 0x03, 0xD0, 0x04, 0x7C, 0x71, 0x4B, 0x77, 0x02, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x3A, 0x0E, 0xCF, 0xBE, 0x3D, 0x04, 0xFF, 0xA0, 0x0B, 0x52, 0x00, 0x08, 0x52, 0x04, 0x08, 0x91, 0x42, 0x38, 0xFE, 0x77, 0x00, 0x7C, 0x71, 0xB1, 0xCF, 0x87, 0x7C, 0x71, 0x93, 0xD0, 0x8E, 0x7C, 0x72, 0xDD, 0x12, 0xE1, 0x62, 0xD0, 0x00, 0xD5, 0x90 + } + }, + { + 359, + 79, + 0x39, + 0x0192, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x92, 0x54, 0x00, 0x56, 0x02, 0x00, 0x80, 0x57, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x27, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x26, 0xE9, 0xF0, 0x3C, 0xE9, 0xF0, 0xB0, 0x35, 0x7C, 0x6F, 0x27, 0x06, 0xE8, 0xD0, 0x7C, 0x6F, 0x54, 0x62, 0xD0, 0x04, 0x51, 0xE2, 0x62, 0xD0, 0x00, 0x3F, 0xE8, 0x52, 0x02, 0x7C, 0x6D, 0xD9, 0x06, 0xE8, 0xD5, 0x7C, 0x6F, 0xE2, 0x7C, 0x72, 0x81, 0xE9 + } + }, + { + 360, + 79, + 0x39, + 0x0193, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x93, 0x9A, 0x62, 0xD0, 0x04, 0x51, 0xE2, 0x08, 0x91, 0xCC, 0x38, 0xFF, 0x62, 0xD0, 0x04, 0x53, 0xE2, 0x7B, 0x00, 0x80, 0x08, 0x3D, 0x00, 0x00, 0xB0, 0x03, 0x80, 0x2B, 0x77, 0x02, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x3A, 0x0E, 0xCF, 0xA2, 0x80, 0x1E, 0x50, 0x00, 0x08, 0x91, 0xF1, 0x38, 0xFF, 0x7C, 0x71, 0x93, 0xC0, 0x0A, 0x50, 0x00, 0x08, 0x95, 0x86, 0x38, 0xFF, 0x80, 0x09, 0x50, 0x00, 0x08, 0xE4, 0xB0 + } + }, + { + 361, + 79, + 0x39, + 0x0194, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x94, 0x7C, 0x6B, 0x94, 0x38, 0xFF, 0x56, 0x00, 0x00, 0x80, 0x88, 0x62, 0xD0, 0x00, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xD0, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x7C, 0x6E, 0xAE, 0x65, 0xE6, 0x6B, 0xE7, 0x7C, 0x70, 0x1E, 0x06, 0xE6, 0xE0, 0x0E, 0xE7, 0x01, 0x7C, 0x6D, 0xEA, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xB8, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x7C, 0x6E, 0xAE, 0x06, 0x78, 0xD9 + } + }, + { + 362, + 79, + 0x39, + 0x0195, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x95, 0xE6, 0xB0, 0x0E, 0xE7, 0x03, 0x7C, 0x6D, 0xEA, 0x7C, 0x6D, 0xA5, 0x06, 0xE8, 0xB4, 0x0E, 0xE9, 0x03, 0x7C, 0x6D, 0x83, 0x53, 0xE9, 0x7C, 0x6E, 0xAE, 0x06, 0xE6, 0xEA, 0x0E, 0xE7, 0x00, 0x7C, 0x6D, 0xEA, 0x7C, 0x6D, 0xA5, 0x65, 0xE8, 0x6B, 0xE9, 0x06, 0xE8, 0xD5, 0x0E, 0xE9, 0x02, 0x7C, 0x6D, 0x83, 0x7C, 0x6E, 0xE1, 0x7C, 0x73, 0x3C, 0x7C, 0x6E, 0xAE, 0x65, 0xE6, 0x6B, 0xE7, 0x7C, 0x67, 0xB8 + } + }, + { + 363, + 79, + 0x39, + 0x0196, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x96, 0x70, 0x1E, 0x06, 0xE6, 0xE1, 0x0E, 0xE7, 0x01, 0x7C, 0x6D, 0xEA, 0x51, 0xE8, 0x3F, 0xE6, 0x77, 0x00, 0x7C, 0x6F, 0xC1, 0xCF, 0x75, 0x3D, 0xFC, 0xFF, 0xA0, 0x08, 0x7C, 0x72, 0xDD, 0x53, 0xE1, 0x80, 0x07, 0x62, 0xD0, 0x04, 0x55, 0xE1, 0xFF, 0x38, 0xFA, 0x20, 0x7F, 0x10, 0x4F, 0x62, 0xD0, 0x00, 0x52, 0xFB, 0x97, 0xD5, 0x40, 0x06, 0xE8, 0xE0, 0x0E, 0xE9, 0x01, 0x97, 0xC5, 0x40, 0x53, 0xCA, 0x7F + } + }, + { + 364, + 79, + 0x39, + 0x0197, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x97, 0xE9, 0x52, 0xFC, 0x53, 0xE6, 0x55, 0xE7, 0x00, 0x06, 0xE6, 0xD0, 0x0E, 0xE7, 0x03, 0x7C, 0x6D, 0xEA, 0x52, 0xFB, 0x97, 0xB5, 0x40, 0x06, 0xE8, 0xE1, 0x0E, 0xE9, 0x01, 0x97, 0xA5, 0x40, 0x7C, 0x6E, 0xE1, 0x52, 0xFC, 0x7C, 0x6E, 0xB6, 0x06, 0xE6, 0xD5, 0x0E, 0xE7, 0x02, 0x97, 0xFB, 0x40, 0x51, 0xE8, 0x3F, 0xE6, 0x7C, 0x70, 0xF0, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x97, 0x84, 0x40, 0xDB, 0xA2 + } + }, + { + 365, + 79, + 0x39, + 0x0198, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x98, 0x7A, 0xE8, 0x53, 0xE7, 0x26, 0xE7, 0xF0, 0x7C, 0x6F, 0xD0, 0x52, 0xFC, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x97, 0x6A, 0x40, 0x7A, 0xE8, 0x53, 0xE7, 0x26, 0xE7, 0x0F, 0x7C, 0x6F, 0xD0, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x03, 0x7C, 0x6F, 0xB9, 0x55, 0xE9, 0x00, 0x06, 0xE8, 0xB8, 0x0E, 0xE9, 0x03, 0x97, 0x4B, 0x40, 0x54, 0x00, 0x7C, 0x70, 0xF0, 0x06, 0xE8, 0x7E, 0xE9 + } + }, + { + 366, + 79, + 0x39, + 0x0199, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x99, 0xB0, 0x0E, 0xE9, 0x03, 0x97, 0x3D, 0x40, 0x54, 0x01, 0x52, 0x00, 0x3B, 0x01, 0xD0, 0x08, 0x7C, 0x73, 0x06, 0x54, 0x02, 0x80, 0x06, 0x7C, 0x72, 0xFE, 0x54, 0x02, 0x7C, 0x6F, 0xB9, 0x55, 0xE9, 0x00, 0x06, 0xE8, 0xB4, 0x0E, 0xE9, 0x03, 0x97, 0x1A, 0x40, 0x54, 0x00, 0x7C, 0x70, 0xF0, 0x06, 0xE8, 0xEA, 0x0E, 0xE9, 0x00, 0x97, 0x0C, 0x40, 0x54, 0x01, 0x52, 0x00, 0x3B, 0x01, 0xD0, 0x08, 0x98, 0x1E + } + }, + { + 367, + 79, + 0x39, + 0x019A, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x9A, 0x7C, 0x73, 0x06, 0x05, 0x02, 0x80, 0x06, 0x7C, 0x72, 0xFE, 0x05, 0x02, 0x52, 0x02, 0x62, 0xD0, 0x00, 0x38, 0xFD, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x77, 0xFC, 0xB0, 0x03, 0x77, 0xFC, 0x50, 0x0F, 0x3B, 0xFC, 0xD0, 0x04, 0x56, 0xFC, 0x01, 0x52, 0xFC, 0x54, 0x01, 0x56, 0x00, 0x00, 0x80, 0x1A, 0x62, 0xD0, 0x00, 0x52, 0x00, 0x96, 0xD2, 0x40, 0x06, 0xE8, 0xE0, 0x0E, 0xE9, 0x01, 0x96, 0xD9, 0xA1 + } + }, + { + 368, + 79, + 0x39, + 0x019B, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x9B, 0xC2, 0x40, 0x3B, 0xFC, 0xB0, 0x03, 0x77, 0xFC, 0x77, 0x00, 0x7C, 0x6F, 0xC1, 0xCF, 0xE3, 0x52, 0xFC, 0x3B, 0x01, 0xBF, 0xD4, 0x52, 0xFC, 0x62, 0xD0, 0x00, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x0B, 0x3D, 0xFC, 0x00, 0xB0, 0x11, 0x62, 0xD0, 0x04, 0x51, 0xE1, 0x54, 0x03, 0x62, 0xD0, 0x00, 0x51, 0x0E, 0x54, 0x02, 0x80, 0x0D, 0x62, 0xD0, 0x04, 0x51, 0xE1, 0x13, 0xFC, 0x54, 0x03, 0x43, 0x76 + } + }, + { + 369, + 79, + 0x39, + 0x019C, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x9C, 0x7C, 0x70, 0xDB, 0x56, 0x00, 0x00, 0x56, 0x01, 0x00, 0x81, 0xA7, 0x56, 0x04, 0x00, 0x81, 0x97, 0x3D, 0xFC, 0x00, 0xB0, 0x22, 0x62, 0xD0, 0x00, 0x7C, 0x6F, 0x44, 0x06, 0xE8, 0xB8, 0x0E, 0xE9, 0x03, 0x96, 0x60, 0x40, 0x54, 0x05, 0x96, 0x7D, 0x40, 0x06, 0xE8, 0xB0, 0x0E, 0xE9, 0x03, 0x96, 0x52, 0x40, 0x54, 0x06, 0x80, 0x54, 0x62, 0xD0, 0x00, 0x52, 0x04, 0x96, 0xBD, 0x40, 0x96, 0x43, 0xDE, 0xAD + } + }, + { + 370, + 79, + 0x39, + 0x019D, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x9D, 0x40, 0x54, 0x05, 0x96, 0x60, 0x40, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0x4D, 0x8C + } + }, + { + 371, + 79, + 0x39, + 0x019E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x9E, 0xC3, 0x0E, 0xE9, 0x02, 0x95, 0xFD, 0x40, 0x54, 0x06, 0x52, 0x05, 0x3B, 0x06, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x52, 0x06, 0x13, 0x05, 0x54, 0x07, 0x80, 0x0A, 0x62, 0xD0, 0x00, 0x52, 0x05, 0x13, 0x06, 0x54, 0x07, 0x3D, 0xFC, 0x00, 0xB0, 0x22, 0x62, 0xD0, 0x00, 0x97, 0x97, 0x40, 0x06, 0xE8, 0xB4, 0x0E, 0xE9, 0x03, 0x95, 0xCD, 0x40, 0x54, 0x05, 0x95, 0xEA, 0x40, 0x06, 0xE8, 0xEA, 0x0E, 0xC7, 0x81 + } + }, + { + 372, + 79, + 0x39, + 0x019F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0x9F, 0xE9, 0x00, 0x95, 0xBF, 0x40, 0x54, 0x06, 0x80, 0x54, 0x62, 0xD0, 0x00, 0x52, 0x04, 0x96, 0x6E, 0x40, 0x95, 0xB0, 0x40, 0x54, 0x05, 0x95, 0xCD, 0x40, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0B, 0x0A + } + }, + { + 373, + 79, + 0x39, + 0x01A0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA0, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xC4, 0x0E, 0xE9, 0x02, 0x95, 0x6A, 0x40, 0x54, 0x06, 0x52, 0x05, 0x3B, 0x06, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x52, 0x06, 0x13, 0x05, 0x54, 0x08, 0x80, 0x0A, 0x62, 0xD0, 0x00, 0x52, 0x05, 0x13, 0x06, 0x54, 0x08, 0x62, 0xD0, 0x00, 0x52, 0x07, 0x53, 0xE8, 0x50, 0x00, 0x08, 0xD8, 0xA5 + } + }, + { + 374, + 79, + 0x39, + 0x01A1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA1, 0x51, 0xE8, 0x08, 0x52, 0x07, 0x08, 0x95, 0x13, 0x7C, 0x71, 0xF5, 0x52, 0x08, 0x53, 0xE6, 0x50, 0x00, 0x08, 0x51, 0xE6, 0x08, 0x52, 0x08, 0x08, 0x95, 0x01, 0x38, 0xFA, 0x62, 0xD0, 0x00, 0x52, 0x0A, 0x02, 0xE8, 0x62, 0xD0, 0x03, 0x53, 0xDF, 0x52, 0x09, 0x62, 0xD0, 0x00, 0x0A, 0xE9, 0x62, 0xD0, 0x03, 0x53, 0xDE, 0x62, 0xD0, 0x00, 0x96, 0xCB, 0x40, 0x52, 0x01, 0x02, 0xE8, 0x53, 0xE8, 0x90, 0x16 + } + }, + { + 375, + 79, + 0x39, + 0x01A2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA2, 0x50, 0x00, 0x0A, 0xE9, 0x53, 0xE9, 0x65, 0xE8, 0x6B, 0xE9, 0x06, 0xE8, 0xB1, 0x97, 0x53, 0x40, 0x62, 0xD0, 0x03, 0x51, 0xDE, 0x62, 0xD0, 0x00, 0x3F, 0xE8, 0x62, 0xD0, 0x03, 0x51, 0xDF, 0x62, 0xD0, 0x00, 0x3F, 0xE8, 0x77, 0x04, 0x52, 0x04, 0x3B, 0x02, 0xCE, 0x65, 0x77, 0x00, 0x07, 0x01, 0x03, 0x52, 0x00, 0x3B, 0x03, 0xCE, 0x55, 0x38, 0xF5, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x97, 0xD6, 0xA3 + } + }, + { + 376, + 79, + 0x39, + 0x01A3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA3, 0x08, 0x40, 0x80, 0x95, 0x62, 0xD0, 0x00, 0x94, 0xDC, 0x40, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x94, 0xB1, 0x40, 0x53, 0xE9, 0x47, 0xE9, 0xF0, 0xA0, 0x7D, 0x52, 0x01, 0x95, 0x1C, 0x40, 0x95, 0xCD, 0x40, 0x06, 0xE6, 0xB8, 0x0E, 0xE7, 0x03, 0x51, 0xE7, 0x60, 0xD4, 0x3E, 0xE6, 0x53, 0xE7, 0x96, 0xDE, 0x40, 0x52, 0x01, 0x95, 0x47, 0x40, 0x95, 0xB4, 0x40, 0x06, 0xE6, 0xB4, 0x0E, 0xE7, 0x7E, 0xF4 + } + }, + { + 377, + 79, + 0x39, + 0x01A4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA4, 0x03, 0x51, 0xE7, 0x60, 0xD4, 0x3E, 0xE6, 0x53, 0xE7, 0x96, 0xC5, 0x40, 0x52, 0x01, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0xD5, 0xA3 + } + }, + { + 378, + 79, + 0x39, + 0x01A5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA5, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xCE, 0x0E, 0xE9, 0x02, 0x96, 0x86, 0x40, 0x77, 0x01, 0x77, 0x00, 0x96, 0x67, 0x40, 0xCF, 0x68, 0x96, 0x6A, 0x40, 0x81, 0x15, 0x62, 0xD0, 0x00, 0x94, 0x3E, 0x40, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x03, 0x94, 0x13, 0x40, 0x53, 0xE9, 0x47, 0xE9, 0x0F, 0xA0, 0xFD, 0x52, 0x01, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x55, 0x51, 0x9C + } + }, + { + 379, + 79, + 0x39, + 0x01A6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA6, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xC3, 0x0E, 0xE9, 0x02, 0x94, 0xEF, 0x40, 0xA8, 0x4B + } + }, + { + 380, + 79, + 0x39, + 0x01A7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA7, 0x06, 0xE6, 0xB0, 0x0E, 0xE7, 0x03, 0x51, 0xE7, 0x60, 0xD4, 0x3E, 0xE6, 0x53, 0xE7, 0x96, 0x00, 0x40, 0x52, 0x01, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0x20, 0x3C + } + }, + { + 381, + 79, + 0x39, + 0x01A8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA8, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xC4, 0x0E, 0xE9, 0x02, 0x94, 0x96, 0x40, 0x06, 0xE6, 0xEA, 0x0E, 0xE7, 0x00, 0x51, 0xE7, 0x60, 0xD4, 0x3E, 0xE6, 0x53, 0xE7, 0x95, 0xA7, 0x40, 0x52, 0x01, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0x6A, 0xD1 + } + }, + { + 382, + 79, + 0x39, + 0x01A9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xA9, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xC5, 0x0E, 0xE9, 0x02, 0x95, 0x68, 0x40, 0x77, 0x01, 0x77, 0x00, 0x97, 0x39, 0x40, 0xCE, 0xE8, 0x38, 0xFE, 0x20, 0x7F, 0x10, 0x0B, 0x14 + } + }, + { + 383, + 79, + 0x39, + 0x01AA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xAA, 0x4F, 0x38, 0x07, 0x3D, 0xFC, 0x00, 0xB0, 0x11, 0x62, 0xD0, 0x04, 0x51, 0xE1, 0x54, 0x05, 0x62, 0xD0, 0x00, 0x51, 0x0E, 0x54, 0x02, 0x80, 0x0D, 0x62, 0xD0, 0x04, 0x51, 0xE1, 0x13, 0xFC, 0x54, 0x05, 0x96, 0x38, 0x40, 0x62, 0xD0, 0x00, 0x94, 0x6F, 0x40, 0x06, 0xE8, 0x5E, 0x0E, 0xE9, 0x0F, 0x94, 0xAA, 0x40, 0x54, 0x04, 0x56, 0x03, 0x00, 0x56, 0x01, 0x00, 0x97, 0xE4, 0x40, 0x80, 0xCB, 0xEE, 0xDB + } + }, + { + 384, + 79, + 0x39, + 0x01AB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xAB, 0x62, 0xD0, 0x03, 0x55, 0xDF, 0x00, 0x55, 0xDE, 0x00, 0x56, 0x06, 0x00, 0x80, 0x34, 0x96, 0xCE, 0x40, 0x52, 0x01, 0x94, 0x2B, 0x40, 0x10, 0x57, 0x03, 0x7C, 0x4A, 0xBC, 0x20, 0x03, 0x06, 0x54, 0x00, 0x92, 0xC2, 0x40, 0x65, 0xE8, 0x6B, 0xE9, 0x06, 0xE8, 0xB1, 0x0E, 0xE9, 0x02, 0x92, 0x93, 0x40, 0x53, 0xE9, 0x3E, 0xE8, 0x62, 0xD0, 0x03, 0x04, 0xDF, 0x95, 0xA5, 0x40, 0x0C, 0xDE, 0x77, 0x92, 0x24 + } + }, + { + 385, + 79, + 0x39, + 0x01AC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xAC, 0x06, 0x52, 0x06, 0x3B, 0x02, 0xCF, 0xC8, 0x95, 0x83, 0x40, 0xD0, 0x7A, 0x62, 0xD0, 0x03, 0x51, 0xDF, 0x08, 0x51, 0xDE, 0x62, 0xD0, 0x04, 0x53, 0xB1, 0x18, 0x53, 0xB2, 0x56, 0x06, 0x00, 0x80, 0x5F, 0x96, 0x7B, 0x40, 0x52, 0x01, 0x93, 0xD8, 0x40, 0x54, 0x00, 0x3D, 0xFC, 0x00, 0xB0, 0x42, 0x52, 0x00, 0x92, 0x56, 0x40, 0x06, 0xE8, 0xE0, 0x0E, 0xE9, 0x01, 0x92, 0x46, 0x40, 0x53, 0xE9, 0x64, 0xC9 + } + }, + { + 386, + 79, + 0x39, + 0x01AD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xAD, 0x96, 0x67, 0x40, 0x06, 0xE6, 0xD0, 0x0E, 0xE7, 0x03, 0x92, 0x9F, 0x40, 0x52, 0x00, 0x92, 0x3A, 0x40, 0x06, 0xE8, 0xE1, 0x0E, 0xE9, 0x01, 0x92, 0x2A, 0x40, 0x93, 0x85, 0x40, 0x52, 0x06, 0x93, 0x55, 0x40, 0x06, 0xE6, 0xD5, 0x0E, 0xE7, 0x02, 0x92, 0x80, 0x40, 0x51, 0xE8, 0x3F, 0xE6, 0x80, 0x0D, 0x96, 0x2B, 0x40, 0x06, 0xE8, 0xFD, 0x0E, 0xE9, 0x02, 0x94, 0x5D, 0x40, 0x77, 0x06, 0x52, 0x35, 0x6C + } + }, + { + 387, + 79, + 0x39, + 0x01AE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xAE, 0x06, 0x3B, 0x02, 0xCF, 0x9D, 0x77, 0x03, 0x07, 0x01, 0x03, 0x52, 0x03, 0x3B, 0x04, 0xCF, 0x31, 0x38, 0xF9, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x07, 0x3D, 0xFC, 0x00, 0xB0, 0x11, 0x62, 0xD0, 0x04, 0x51, 0xE1, 0x54, 0x01, 0x62, 0xD0, 0x00, 0x51, 0x0E, 0x54, 0x02, 0x80, 0x0D, 0x62, 0xD0, 0x04, 0x51, 0xE1, 0x13, 0xFC, 0x54, 0x01, 0x95, 0x23, 0x40, 0x62, 0xD0, 0x00, 0x93, 0x69, 0x40, 0x06, 0x99, 0x35 + } + }, + { + 388, + 79, + 0x39, + 0x01AF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xAF, 0xE8, 0x5E, 0x0E, 0xE9, 0x0F, 0x93, 0x95, 0x40, 0x54, 0x04, 0x56, 0x03, 0x00, 0x56, 0x00, 0x00, 0x96, 0xCF, 0x40, 0x81, 0x6E, 0x62, 0xD0, 0x03, 0x55, 0xDF, 0x00, 0x55, 0xDE, 0x00, 0x56, 0x05, 0x00, 0x80, 0x39, 0x62, 0xD0, 0x00, 0x93, 0x30, 0x40, 0x52, 0x00, 0x93, 0x13, 0x40, 0x53, 0xE9, 0x10, 0x52, 0x05, 0x57, 0x03, 0x7C, 0x4A, 0xBC, 0x20, 0x02, 0xE9, 0x54, 0x06, 0x52, 0x06, 0x91, 0x39, 0x76 + } + }, + { + 389, + 79, + 0x39, + 0x01B0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB0, 0xD8, 0x40, 0x06, 0xE8, 0xB1, 0x0E, 0xE9, 0x02, 0x91, 0x79, 0x40, 0x53, 0xE9, 0x3E, 0xE8, 0x62, 0xD0, 0x03, 0x04, 0xDF, 0x94, 0x8B, 0x40, 0x0C, 0xDE, 0x77, 0x05, 0x52, 0x05, 0x3B, 0x01, 0xCF, 0xC3, 0x94, 0x69, 0x40, 0xD1, 0x18, 0x62, 0xD0, 0x03, 0x51, 0xDF, 0x08, 0x51, 0xDE, 0x62, 0xD0, 0x04, 0x53, 0xB1, 0x18, 0x53, 0xB2, 0x56, 0x05, 0x00, 0x80, 0x2A, 0x3D, 0xFC, 0x00, 0xB0, 0x13, 0x78, 0xF5 + } + }, + { + 390, + 79, + 0x39, + 0x01B1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB1, 0x62, 0xD0, 0x00, 0x92, 0xD3, 0x40, 0x06, 0xE8, 0xD0, 0x93, 0x09, 0x40, 0x50, 0x00, 0x3F, 0xE8, 0x80, 0x11, 0x62, 0xD0, 0x00, 0x92, 0xC1, 0x40, 0x06, 0xE8, 0xFD, 0x93, 0x85, 0x40, 0x50, 0xFF, 0x3F, 0xE8, 0x77, 0x05, 0x52, 0x05, 0x3B, 0x02, 0xCF, 0xD2, 0x56, 0x05, 0x00, 0x80, 0x66, 0x62, 0xD0, 0x00, 0x92, 0xA4, 0x40, 0x52, 0x00, 0x92, 0x87, 0x40, 0x54, 0x06, 0x3D, 0xFC, 0x00, 0xB0, 0x7F, 0x04 + } + }, + { + 391, + 79, + 0x39, + 0x01B2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB2, 0x42, 0x52, 0x05, 0x91, 0x05, 0x40, 0x06, 0xE8, 0xE0, 0x0E, 0xE9, 0x01, 0x90, 0xF5, 0x40, 0x53, 0xE9, 0x95, 0x16, 0x40, 0x06, 0xE6, 0xD0, 0x0E, 0xE7, 0x03, 0x91, 0x4E, 0x40, 0x52, 0x05, 0x90, 0xE9, 0x40, 0x06, 0xE8, 0xE1, 0x0E, 0xE9, 0x01, 0x90, 0xD9, 0x40, 0x92, 0x34, 0x40, 0x52, 0x06, 0x92, 0x04, 0x40, 0x06, 0xE6, 0xD5, 0x0E, 0xE7, 0x02, 0x91, 0x2F, 0x40, 0x51, 0xE8, 0x3F, 0xE6, 0xBE, 0x83 + } + }, + { + 392, + 79, + 0x39, + 0x01B3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB3, 0x80, 0x11, 0x62, 0xD0, 0x00, 0x92, 0x51, 0x40, 0x06, 0xE8, 0xFD, 0x93, 0x15, 0x40, 0x52, 0x06, 0x3F, 0xE8, 0x77, 0x05, 0x52, 0x05, 0x3B, 0x01, 0xCF, 0x96, 0x3D, 0xFC, 0x00, 0xB0, 0x5F, 0x62, 0xD0, 0x04, 0x51, 0xE2, 0x62, 0xD0, 0x04, 0x53, 0xE3, 0x56, 0x05, 0x00, 0x80, 0x4A, 0x62, 0xD0, 0x00, 0x92, 0x25, 0x40, 0x06, 0xE8, 0xD0, 0x0E, 0xE9, 0x03, 0x90, 0x87, 0x40, 0x39, 0x00, 0xB0, 0x0F, 0x26 + } + }, + { + 393, + 79, + 0x39, + 0x01B4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB4, 0x35, 0x92, 0x15, 0x40, 0x06, 0xE8, 0xD0, 0x92, 0x4B, 0x40, 0x62, 0xD0, 0x04, 0x51, 0xE3, 0x62, 0xD0, 0x00, 0x3F, 0xE8, 0x62, 0xD0, 0x04, 0x51, 0xE3, 0x08, 0x7C, 0x66, 0x95, 0x38, 0xFF, 0x62, 0xD0, 0x04, 0x53, 0xE3, 0x62, 0xD0, 0x00, 0x52, 0x05, 0x90, 0xAE, 0x40, 0x06, 0xE8, 0xD5, 0x92, 0xB1, 0x40, 0x95, 0x66, 0x40, 0x77, 0x05, 0x52, 0x05, 0x3B, 0x02, 0xCF, 0xB2, 0x77, 0x03, 0x07, 0xE0, 0xC9 + } + }, + { + 394, + 79, + 0x39, + 0x01B5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB5, 0x00, 0x03, 0x52, 0x03, 0x3B, 0x04, 0xCE, 0x8E, 0x3D, 0xFC, 0x00, 0xB0, 0x0B, 0x62, 0xD0, 0x04, 0x51, 0xE3, 0x62, 0xD0, 0x04, 0x53, 0xE2, 0x38, 0xF9, 0x20, 0x7F, 0x10, 0x4F, 0x38, 0x02, 0x92, 0x68, 0x40, 0x48, 0xFC, 0x01, 0xA0, 0x09, 0x52, 0xFB, 0x05, 0x01, 0x52, 0xFA, 0x0D, 0x00, 0x66, 0xFB, 0x6C, 0xFA, 0x70, 0xFB, 0x6F, 0xFC, 0x3D, 0xFC, 0x00, 0xBF, 0xE7, 0x93, 0xB0, 0x40, 0x38, 0x30, 0x6A + } + }, + { + 395, + 79, + 0x39, + 0x01B6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB6, 0xFE, 0x20, 0x7F, 0x51, 0xE9, 0x60, 0xD4, 0x3E, 0xE8, 0x7F, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x65, 0xE8, 0x6B, 0xE9, 0x65, 0xE8, 0x6B, 0xE9, 0x65, 0xE8, 0x6B, 0xE9, 0x7F, 0x65, 0xE8, 0x6B, 0xE9, 0x65, 0xE8, 0x6B, 0xE9, 0x7F, 0x52, 0x00, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x7F, 0x70, 0xFB, 0x6E, 0xE9, 0x6E, 0xE8, 0x70, 0xFB, 0x6E, 0xE9, 0x6E, 0xE8, 0x70, 0xFB, 0x6E, 0xE9, 0x6E, 0xE8, 0x70, 0x4E, 0xA7 + } + }, + { + 396, + 79, + 0x39, + 0x01B7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB7, 0xFB, 0x6E, 0xE9, 0x6E, 0xE8, 0x7F, 0x70, 0xFB, 0x6E, 0xE9, 0x6E, 0xE8, 0x70, 0xFB, 0x6E, 0xE9, 0x6E, 0xE8, 0x70, 0xFB, 0x6E, 0xE9, 0x6E, 0xE8, 0x7F, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x65, 0xE8, 0x6B, 0xE9, 0x7F, 0x60, 0xD4, 0x3E, 0xE8, 0x53, 0xE9, 0x7F, 0x51, 0xE7, 0x60, 0xD5, 0x51, 0xE9, 0x3F, 0xE6, 0x7F, 0x70, 0xFB, 0x6E, 0xE9, 0x6E, 0xE8, 0x7F, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x55, 0x4C, 0xA4 + } + }, + { + 397, + 79, + 0x39, + 0x01B8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB8, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xCC, 0x0E, 0xE9, 0x02, 0x7F, 0x53, 0xE8, 0xA8, 0x5D + } + }, + { + 398, + 79, + 0x39, + 0x01B9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xB9, 0x55, 0xE9, 0x00, 0x55, 0xE6, 0x03, 0x55, 0xE7, 0x00, 0x55, 0xE1, 0x00, 0x55, 0xE0, 0x00, 0x3C, 0xE7, 0x00, 0xB0, 0x06, 0x3C, 0xE6, 0x00, 0xA0, 0x1A, 0x70, 0xFB, 0x6E, 0xE7, 0x6E, 0xE6, 0xD0, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x04, 0xE1, 0x51, 0xE9, 0x0C, 0xE0, 0x65, 0xE8, 0x6B, 0xE9, 0x8F, 0xDE, 0x5F, 0xE8, 0xE1, 0x5F, 0xE9, 0xE0, 0x62, 0xD0, 0x00, 0x06, 0xE8, 0xCD, 0x0E, 0xE9, 0x80, 0x0E + } + }, + { + 399, + 79, + 0x39, + 0x01BA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xBA, 0x02, 0x7F, 0x62, 0xD0, 0x01, 0x51, 0xE7, 0x08, 0x51, 0xE6, 0x62, 0xD0, 0x03, 0x53, 0xEC, 0x18, 0x53, 0xED, 0x62, 0xD0, 0x01, 0x51, 0xE5, 0x08, 0x51, 0xE4, 0x62, 0xD0, 0x03, 0x53, 0xEA, 0x18, 0x53, 0xEB, 0x7F, 0x53, 0xE8, 0x52, 0x01, 0x09, 0x00, 0x60, 0xD4, 0x3E, 0xE8, 0x7F, 0x52, 0x00, 0x53, 0xE6, 0x55, 0xE7, 0x00, 0x7F, 0x53, 0xE6, 0x55, 0xE7, 0x00, 0x65, 0xE6, 0x6B, 0xE7, 0x7F, 0x2B, 0x65 + } + }, + { + 400, + 79, + 0x39, + 0x01BB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xBB, 0x62, 0xD0, 0x01, 0x51, 0xEF, 0x08, 0x51, 0xEE, 0x62, 0xD0, 0x03, 0x53, 0xE8, 0x18, 0x53, 0xE9, 0x62, 0xD0, 0x01, 0x51, 0xED, 0x08, 0x51, 0xEC, 0x62, 0xD0, 0x03, 0x53, 0xE6, 0x18, 0x53, 0xE7, 0x7F, 0x53, 0xE9, 0x3E, 0xE8, 0x53, 0xE8, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0xAF, 0x29, 0x01, 0x53, 0xAF, 0x51, 0xAF, 0x60, 0x04, 0x51, 0xAF, 0x29, 0x08, 0x53, 0xAF, 0x51, 0xAF, 0x60, 0x04, 0x7F, 0x67, 0xDE + } + }, + { + 401, + 79, + 0x39, + 0x01BC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xBC, 0x02, 0xE8, 0x53, 0xE8, 0x50, 0x00, 0x0A, 0xE9, 0x53, 0xE9, 0x06, 0xE8, 0x62, 0x0E, 0xE9, 0x0F, 0x51, 0xE9, 0x10, 0x58, 0xE8, 0x28, 0x20, 0x7F, 0x52, 0x05, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x7F, 0x53, 0xAF, 0x51, 0xAF, 0x60, 0x04, 0x7F, 0x52, 0x02, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x7F, 0x62, 0xD0, 0x03, 0x51, 0x99, 0x21, 0x70, 0x54, 0x00, 0x3D, 0x00, 0x00, 0x7F, 0x62, 0xD0, 0x03, 0x51, 0x11, 0x33 + } + }, + { + 402, + 79, + 0x39, + 0x01BD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xBD, 0x99, 0x21, 0x70, 0x7F, 0x52, 0x04, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x7F, 0x62, 0xD0, 0x00, 0x52, 0x02, 0x53, 0xE8, 0x7F, 0x0E, 0xE9, 0x03, 0x51, 0xE9, 0x60, 0xD5, 0x7F, 0x51, 0xE9, 0x10, 0x58, 0xE8, 0x28, 0x20, 0x7F, 0x62, 0xD0, 0x00, 0x26, 0xAF, 0xF7, 0x51, 0xAF, 0x60, 0x04, 0x26, 0xAF, 0xFE, 0x51, 0xAF, 0x60, 0x04, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x67, 0xE0 + } + }, + { + 403, + 79, + 0x39, + 0x01BE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xBE, 0x04, 0x53, 0x9D, 0x18, 0x53, 0x9E, 0x7F, 0x62, 0xD0, 0x03, 0x51, 0xF7, 0x08, 0x51, 0xF6, 0x62, 0xD0, 0x03, 0x53, 0xBC, 0x18, 0x53, 0xBD, 0x62, 0xD0, 0x03, 0x51, 0xF5, 0x08, 0x51, 0xF4, 0x62, 0xD0, 0x03, 0x53, 0xBE, 0x18, 0x53, 0xBF, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0x9F, 0x18, 0x53, 0xA0, 0x7F, 0x62, 0xD0, 0x00, 0x52, 0xFC, 0x53, 0xE8, 0x85, 0x1D + } + }, + { + 404, + 79, + 0x39, + 0x01BF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xBF, 0x7F, 0x62, 0xD0, 0x00, 0x52, 0x00, 0x3A, 0x0E, 0x7F, 0x56, 0x01, 0x00, 0x56, 0x00, 0x00, 0x7F, 0x51, 0xE9, 0x60, 0xD5, 0x51, 0xE7, 0x3F, 0xE8, 0x7F, 0x51, 0xE9, 0x60, 0xD5, 0x52, 0x00, 0x3F, 0xE8, 0x7F, 0x0E, 0xE9, 0x02, 0x51, 0xE9, 0x60, 0xD5, 0x7F, 0x62, 0xD0, 0x03, 0x52, 0x01, 0x53, 0xE5, 0x52, 0x00, 0x53, 0xE4, 0x7F, 0x52, 0x02, 0x53, 0xE8, 0x52, 0x01, 0x60, 0xD4, 0x3E, 0xE8, 0x95, 0x3E + } + }, + { + 405, + 79, + 0x39, + 0x01C0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC0, 0x7F, 0x70, 0xFB, 0x6F, 0x01, 0x6F, 0x02, 0x70, 0xFB, 0x6F, 0x01, 0x6F, 0x02, 0x7F, 0x62, 0xD0, 0x00, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x7F, 0x65, 0xE8, 0x6B, 0xE9, 0x51, 0xE8, 0x7F, 0x65, 0xE6, 0x6B, 0xE7, 0x65, 0xE6, 0x6B, 0xE7, 0x7F, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0xD4, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0x7F, 0x53, 0xE9, 0x62, 0xD0, 0x04, 0x51, 0xD9, 0x62, 0xD0, 0x00, 0x3A, 0xE9, 0x2E, 0x71 + } + }, + { + 406, + 79, + 0x39, + 0x01C1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC1, 0x7F, 0x62, 0xD0, 0x03, 0x55, 0xEF, 0x00, 0x55, 0xEE, 0x00, 0x7F, 0x56, 0x01, 0x00, 0x80, 0x03, 0x77, 0x01, 0x3D, 0x01, 0x0A, 0xCF, 0xFA, 0x62, 0xD0, 0x00, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xCF, 0x01, 0x7F, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x08, 0x57, 0x89, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x28, 0x53, 0xE8, 0x20, 0x7F, 0x62, 0xD0, 0x04, 0x51, 0xAA, 0x62, 0xD0, 0x00, 0x12, 0xA0, 0x56 + } + }, + { + 407, + 79, + 0x39, + 0x01C2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC2, 0xE8, 0x62, 0xD0, 0x04, 0x51, 0xA9, 0x62, 0xD0, 0x00, 0x1A, 0xE9, 0x7F, 0x62, 0xD0, 0x03, 0x51, 0xDF, 0x62, 0xD0, 0x04, 0x12, 0xB2, 0x62, 0xD0, 0x03, 0x51, 0xDE, 0x62, 0xD0, 0x04, 0x1A, 0xB1, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0xE9, 0x62, 0xD0, 0x03, 0x7F, 0x70, 0xFE, 0x62, 0xD0, 0x03, 0x51, 0xD9, 0x08, 0x51, 0xD8, 0x62, 0xD0, 0x00, 0x53, 0x0A, 0x18, 0x53, 0x0B, 0x71, 0x01, 0x7F, 0x53, 0x76, 0x03 + } + }, + { + 408, + 79, + 0x39, + 0x01C3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC3, 0xE6, 0x55, 0xE7, 0x00, 0x51, 0xE8, 0x12, 0xE6, 0x51, 0xE9, 0x1A, 0xE7, 0x7F, 0x60, 0xD4, 0x3E, 0xE8, 0x54, 0x03, 0x7F, 0x70, 0xFB, 0x6F, 0x01, 0x6F, 0x02, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0x0E, 0x13, 0xFC, 0x62, 0xD0, 0x00, 0x54, 0x02, 0x7F, 0x70, 0xFE, 0x62, 0xD0, 0x00, 0x51, 0x0D, 0x7F, 0x52, 0xFB, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x7F, 0x5D, 0xC8, 0x62, 0xD0, 0x00, 0x39, 0x00, 0x7F, 0x80, 0x18 + } + }, + { + 409, + 79, + 0x39, + 0x01C4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC4, 0x52, 0x03, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x7F, 0x0E, 0xE9, 0x01, 0x51, 0xE9, 0x60, 0xD5, 0x7F, 0x71, 0x10, 0x5D, 0xE0, 0x54, 0x01, 0x41, 0xE0, 0xE7, 0x43, 0xE0, 0x18, 0x70, 0xCF, 0x62, 0xE3, 0x38, 0x7F, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0xAD, 0x18, 0x53, 0xAE, 0x7F, 0x62, 0xD0, 0x00, 0x52, 0x01, 0x53, 0xE8, 0x52, 0x00, 0x53, 0xE9, 0x7F, 0x62, 0xD0, 0x04, 0x51, 0xA8, 0x08, 0xD8, 0xC9 + } + }, + { + 410, + 79, + 0x39, + 0x01C5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC5, 0x51, 0xA7, 0x62, 0xD0, 0x03, 0x53, 0xEE, 0x18, 0x53, 0xEF, 0x7F, 0x52, 0x05, 0x54, 0x03, 0x52, 0x02, 0x54, 0x04, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0x0D, 0x08, 0x51, 0x0C, 0x62, 0xD0, 0x03, 0x53, 0xD4, 0x18, 0x53, 0xD5, 0x7F, 0x5D, 0xD6, 0x53, 0xE9, 0x2E, 0xE9, 0xFE, 0x51, 0xE9, 0x54, 0x02, 0x43, 0xD6, 0x01, 0x52, 0xFC, 0x7F, 0x53, 0xE8, 0x52, 0xFB, 0x09, 0x00, 0x60, 0xD5, 0x7F, 0x62, 0xD2, 0xBE + } + }, + { + 411, + 79, + 0x39, + 0x01C6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC6, 0xD0, 0x00, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x7F, 0x62, 0xD0, 0x04, 0x53, 0xCE, 0x62, 0xD0, 0x04, 0x51, 0xE0, 0x7F, 0x62, 0xD0, 0x04, 0x51, 0xE1, 0x62, 0xD0, 0x00, 0x3A, 0x0E, 0x7F, 0x62, 0xD0, 0x00, 0x52, 0x06, 0x53, 0xE8, 0x55, 0xE9, 0x00, 0x7F, 0x52, 0x06, 0x53, 0xE6, 0x55, 0xE7, 0x00, 0x7F, 0x62, 0xD0, 0x04, 0x52, 0x00, 0x3A, 0xE1, 0x7F, 0x52, 0x02, 0x03, 0x02, 0x54, 0x00, 0x07, 0x01, 0x1D + } + }, + { + 412, + 79, + 0x39, + 0x01C7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC7, 0x00, 0x2E, 0x7F, 0x51, 0xE8, 0x08, 0x51, 0xE9, 0x62, 0xD0, 0x04, 0x53, 0xA1, 0x18, 0x53, 0xA2, 0x7F, 0x12, 0xE8, 0x50, 0x00, 0x1A, 0xE9, 0x7F, 0x62, 0xD0, 0x03, 0x51, 0x9D, 0x62, 0xD0, 0x00, 0x7F, 0x62, 0xD0, 0x04, 0x51, 0xE1, 0x13, 0x01, 0x62, 0xD0, 0x00, 0x54, 0x00, 0x7F, 0x55, 0xDC, 0x00, 0x62, 0xD0, 0x04, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x54, 0x0A, 0x51, 0xE9, 0x54, 0x09, 0x45, 0xA6 + } + }, + { + 413, + 79, + 0x39, + 0x01C8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC8, 0x7F, 0x08, 0x57, 0x98, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x28, 0x7F, 0x08, 0x57, 0x91, 0x28, 0x53, 0xE9, 0x18, 0x75, 0x09, 0x00, 0x28, 0x7F, 0x62, 0xD0, 0x00, 0x55, 0x0B, 0x01, 0x55, 0x0A, 0x00, 0x71, 0x01, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0xE8, 0x54, 0x01, 0x51, 0xE9, 0x54, 0x00, 0x7F, 0x52, 0x04, 0x53, 0xE6, 0x55, 0xE7, 0x00, 0x7F, 0x50, 0x0D, 0x10, 0x57, 0x88, 0x28, 0x20, 0x36, 0x89 + } + }, + { + 414, + 79, + 0x39, + 0x01C9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xC9, 0x7F, 0x62, 0xD0, 0x04, 0x52, 0x00, 0x3A, 0xC0, 0x7F, 0x62, 0xD0, 0x00, 0x53, 0xE9, 0x51, 0xE8, 0x7F, 0x51, 0xAF, 0x60, 0x04, 0x62, 0xD0, 0x00, 0x7F, 0x52, 0xFC, 0x62, 0xD0, 0x04, 0x53, 0xD7, 0x56, 0xFC, 0x00, 0x7F, 0x62, 0xD0, 0x00, 0x50, 0x0D, 0x10, 0x57, 0x87, 0x28, 0x20, 0x7F, 0x62, 0xD0, 0x04, 0x51, 0xA8, 0x62, 0xD0, 0x03, 0x12, 0xEF, 0x7F, 0x62, 0xD0, 0x04, 0x51, 0xA7, 0x62, 0xF2, 0x02 + } + }, + { + 415, + 79, + 0x39, + 0x01CA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xCA, 0xD0, 0x03, 0x1A, 0xEE, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xD1, 0x00, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xD6, 0x00, 0x7F, 0x62, 0xD0, 0x03, 0x47, 0x99, 0x70, 0x7F, 0x50, 0x00, 0x3F, 0xE8, 0x3F, 0xE8, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xB2, 0xFF, 0x55, 0xB1, 0xFF, 0x7F, 0x56, 0x01, 0x00, 0x56, 0x02, 0x00, 0x7F, 0x71, 0x10, 0x60, 0xE0, 0x70, 0xCF, 0x7F, 0x62, 0xD0, 0x03, 0x51, 0xF6, 0x62, 0xD0, 0x51, 0xC1 + } + }, + { + 416, + 79, + 0x39, + 0x01CB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xCB, 0x03, 0x7F, 0x62, 0xD0, 0x03, 0x51, 0xF7, 0x62, 0xD0, 0x03, 0x7F, 0x71, 0x10, 0x43, 0xD7, 0x20, 0x43, 0xE0, 0x40, 0x7F, 0x52, 0xFA, 0x13, 0xF6, 0x52, 0xF9, 0x1B, 0xF5, 0x7F, 0x62, 0xD0, 0x00, 0x51, 0x0E, 0x62, 0xD0, 0x04, 0x7F, 0x3F, 0xE8, 0x62, 0xD0, 0x04, 0x51, 0xB6, 0x7F, 0x50, 0x0D, 0x10, 0x57, 0x86, 0x28, 0x20, 0x7F, 0x50, 0x0D, 0x10, 0x57, 0x9A, 0x28, 0x20, 0x7F, 0x62, 0xD0, 0x10, 0x40 + } + }, + { + 417, + 79, + 0x39, + 0x01CC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xCC, 0x00, 0x52, 0x00, 0x13, 0x01, 0x7F, 0x62, 0xD0, 0x00, 0x52, 0x01, 0x13, 0x00, 0x7F, 0x50, 0x0D, 0x10, 0x57, 0x9B, 0x28, 0x20, 0x7F, 0x60, 0x0C, 0x62, 0xD0, 0x00, 0x51, 0xB2, 0x7F, 0x60, 0x08, 0x62, 0xD0, 0x00, 0x51, 0xB1, 0x7F, 0x62, 0xD0, 0x03, 0x51, 0x9B, 0x21, 0x0F, 0x7F, 0x62, 0xD0, 0x03, 0x47, 0x99, 0x04, 0x7F, 0x62, 0xD0, 0x04, 0x3C, 0xCD, 0x02, 0x7F, 0x06, 0xE8, 0x01, 0x0E, 0x82, 0x25 + } + }, + { + 418, + 79, + 0x39, + 0x01CD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xCD, 0xE9, 0x00, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xCC, 0x00, 0x7F, 0x13, 0x03, 0x51, 0xE9, 0x1B, 0x02, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xD7, 0x00, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xD9, 0x00, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xD5, 0x00, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xDD, 0x01, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xCB, 0xF4, 0x7F, 0x62, 0xD0, 0x04, 0x55, 0xB4, 0x00, 0x7F, 0x41, 0xD7, 0xDF, 0x41, 0xE0, 0x54, 0xCA + } + }, + { + 419, + 79, + 0x39, + 0x01CE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xCE, 0xBF, 0x7F, 0x41, 0xE0, 0xEF, 0x62, 0xDA, 0xEF, 0x7F, 0x41, 0xE0, 0x7F, 0x62, 0xDA, 0x7F, 0x7F, 0x62, 0xD0, 0x00, 0x3C, 0xBA, 0x00, 0x7F, 0x00, 0xBF, 0x00, 0x20, 0x00, 0xEA, 0x00, 0x06, 0x01, 0x00, 0x00, 0xA0, 0x02, 0xB1, 0x00, 0x4F, 0x03, 0x99, 0x00, 0x47, 0x03, 0xE0, 0x01, 0x0D, 0x03, 0xE1, 0x00, 0x1F, 0x04, 0x99, 0x00, 0x49, 0x04, 0xE2, 0x02, 0x01, 0x00, 0xFF, 0x00, 0x30, 0x30, 0xF0, 0x03 + } + }, + { + 420, + 79, + 0x39, + 0x01CF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xCF, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x24 + } + }, + { + 421, + 79, + 0x39, + 0x01D0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x25 + } + }, + { + 422, + 79, + 0x39, + 0x01D1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD1, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x26 + } + }, + { + 423, + 79, + 0x39, + 0x01D2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD2, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x27 + } + }, + { + 424, + 79, + 0x39, + 0x01D3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD3, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x28 + } + }, + { + 425, + 79, + 0x39, + 0x01D4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD4, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x29 + } + }, + { + 426, + 79, + 0x39, + 0x01D5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD5, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x2A + } + }, + { + 427, + 79, + 0x39, + 0x01D6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD6, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x2B + } + }, + { + 428, + 79, + 0x39, + 0x01D7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD7, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x2C + } + }, + { + 429, + 79, + 0x39, + 0x01D8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD8, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x2D + } + }, + { + 430, + 79, + 0x39, + 0x01D9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xD9, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x2E + } + }, + { + 431, + 79, + 0x39, + 0x01DA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xDA, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x2F + } + }, + { + 432, + 79, + 0x39, + 0x01DB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xDB, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30 + } + }, + { + 433, + 79, + 0x39, + 0x01DC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xDC, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x31 + } + }, + { + 434, + 79, + 0x39, + 0x01DD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xDD, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x32 + } + }, + { + 435, + 79, + 0x39, + 0x01DE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xDE, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x33 + } + }, + { + 436, + 79, + 0x39, + 0x01DF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xDF, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x34 + } + }, + { + 437, + 79, + 0x39, + 0x01E0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x35 + } + }, + { + 438, + 79, + 0x39, + 0x01E1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE1, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x36 + } + }, + { + 439, + 79, + 0x39, + 0x01E2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE2, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x37 + } + }, + { + 440, + 79, + 0x39, + 0x01E3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE3, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x38 + } + }, + { + 441, + 79, + 0x39, + 0x01E4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE4, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x39 + } + }, + { + 442, + 79, + 0x39, + 0x01E5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE5, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x3A + } + }, + { + 443, + 79, + 0x39, + 0x01E6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE6, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x3B + } + }, + { + 444, + 79, + 0x39, + 0x01E7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE7, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x3C + } + }, + { + 445, + 79, + 0x39, + 0x01E8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE8, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x3D + } + }, + { + 446, + 79, + 0x39, + 0x01E9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xE9, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x3E + } + }, + { + 447, + 79, + 0x39, + 0x01EA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xEA, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x3F + } + }, + { + 448, + 79, + 0x39, + 0x01EB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xEB, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x40 + } + }, + { + 449, + 79, + 0x39, + 0x01EC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xEC, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x41 + } + }, + { + 450, + 79, + 0x39, + 0x01ED, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xED, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x42 + } + }, + { + 451, + 79, + 0x39, + 0x01EE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xEE, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x43 + } + }, + { + 452, + 79, + 0x39, + 0x01EF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xEF, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x44 + } + }, + { + 453, + 79, + 0x39, + 0x01F0, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF0, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x45 + } + }, + { + 454, + 79, + 0x39, + 0x01F1, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF1, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x46 + } + }, + { + 455, + 79, + 0x39, + 0x01F2, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF2, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x47 + } + }, + { + 456, + 79, + 0x39, + 0x01F3, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF3, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x48 + } + }, + { + 457, + 79, + 0x39, + 0x01F4, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF4, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x49 + } + }, + { + 458, + 79, + 0x39, + 0x01F5, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF5, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x4A + } + }, + { + 459, + 79, + 0x39, + 0x01F6, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF6, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x4B + } + }, + { + 460, + 79, + 0x39, + 0x01F7, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF7, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x4C + } + }, + { + 461, + 79, + 0x39, + 0x01F8, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF8, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x4D + } + }, + { + 462, + 79, + 0x39, + 0x01F9, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xF9, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x4E + } + }, + { + 463, + 79, + 0x39, + 0x01FA, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xFA, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x4F + } + }, + { + 464, + 79, + 0x39, + 0x01FB, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xFB, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x50 + } + }, + { + 465, + 79, + 0x39, + 0x01FC, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xFC, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x51 + } + }, + { + 466, + 79, + 0x39, + 0x01FD, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xFD, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x52 + } + }, + { + 467, + 79, + 0x39, + 0x01FE, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xFE, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x53 + } + }, + { + 468, + 79, + 0x39, + 0x01FF, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01, 0xFF, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x54 + } + }, + { + 469, + 79, + 0x39, + 0x001E, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x1E, 0x19, 0xE5, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x01, 0x0B, 0x10, 0x12, 0xA0, 0x02, 0x04, 0x00, 0xC0, 0xC1, 0xC2, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xA5, 0xBC + } + }, + { + 470, + 79, + 0x39, + 0x001F, + { + 0x00, 0xFF, 0x39, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x1F, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xA0, 0x07, 0x5F, 0xF8, 0x3E, 0xEF + } + }, + { + 471, + 11, + 0x3B, + -1, + { + 0x00, 0xFF, 0x3B, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + } + }, +}; + +unsigned short cyttsp_fw_records = 472; + +unsigned char cyttsp_fw_tts_verh = 0x10; +unsigned char cyttsp_fw_tts_verl = 0x12; +unsigned char cyttsp_fw_app_idh = 0xA0; +unsigned char cyttsp_fw_app_idl = 0x02; +unsigned char cyttsp_fw_app_verh = 0x04; +unsigned char cyttsp_fw_app_verl = 0x00; +unsigned char cyttsp_fw_cid_0 = 0xC0; +unsigned char cyttsp_fw_cid_1 = 0xC1; +unsigned char cyttsp_fw_cid_2 = 0xC2; diff --git a/drivers/input/touchscreen/msm_touch.c b/drivers/input/touchscreen/msm_touch.c new file mode 100644 index 00000000000..7ba896a3888 --- /dev/null +++ b/drivers/input/touchscreen/msm_touch.c @@ -0,0 +1,317 @@ +/* drivers/input/touchscreen/msm_touch.c + * + * Copyright (c) 2008-2009, 2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +/* HW register map */ +#define TSSC_CTL_REG 0x100 +#define TSSC_SI_REG 0x108 +#define TSSC_OPN_REG 0x104 +#define TSSC_STATUS_REG 0x10C +#define TSSC_AVG12_REG 0x110 + +/* status bits */ +#define TSSC_STS_OPN_SHIFT 0x6 +#define TSSC_STS_OPN_BMSK 0x1C0 +#define TSSC_STS_NUMSAMP_SHFT 0x1 +#define TSSC_STS_NUMSAMP_BMSK 0x3E + +/* CTL bits */ +#define TSSC_CTL_EN (0x1 << 0) +#define TSSC_CTL_SW_RESET (0x1 << 2) +#define TSSC_CTL_MASTER_MODE (0x3 << 3) +#define TSSC_CTL_AVG_EN (0x1 << 5) +#define TSSC_CTL_DEB_EN (0x1 << 6) +#define TSSC_CTL_DEB_12_MS (0x2 << 7) /* 1.2 ms */ +#define TSSC_CTL_DEB_16_MS (0x3 << 7) /* 1.6 ms */ +#define TSSC_CTL_DEB_2_MS (0x4 << 7) /* 2 ms */ +#define TSSC_CTL_DEB_3_MS (0x5 << 7) /* 3 ms */ +#define TSSC_CTL_DEB_4_MS (0x6 << 7) /* 4 ms */ +#define TSSC_CTL_DEB_6_MS (0x7 << 7) /* 6 ms */ +#define TSSC_CTL_INTR_FLAG1 (0x1 << 10) +#define TSSC_CTL_DATA (0x1 << 11) +#define TSSC_CTL_SSBI_CTRL_EN (0x1 << 13) + +/* control reg's default state */ +#define TSSC_CTL_STATE ( \ + TSSC_CTL_DEB_12_MS | \ + TSSC_CTL_DEB_EN | \ + TSSC_CTL_AVG_EN | \ + TSSC_CTL_MASTER_MODE | \ + TSSC_CTL_EN) + +#define TSSC_NUMBER_OF_OPERATIONS 2 +#define TS_PENUP_TIMEOUT_MS 20 + +#define TS_DRIVER_NAME "msm_touchscreen" + +#define X_MAX 1024 +#define Y_MAX 1024 +#define P_MAX 256 + +struct ts { + struct input_dev *input; + struct timer_list timer; + int irq; + unsigned int x_max; + unsigned int y_max; +}; + +static void __iomem *virt; +#define TSSC_REG(reg) (virt + TSSC_##reg##_REG) + +static void ts_update_pen_state(struct ts *ts, int x, int y, int pressure) +{ + if (pressure) { + input_report_abs(ts->input, ABS_X, x); + input_report_abs(ts->input, ABS_Y, y); + input_report_abs(ts->input, ABS_PRESSURE, pressure); + input_report_key(ts->input, BTN_TOUCH, !!pressure); + } else { + input_report_abs(ts->input, ABS_PRESSURE, 0); + input_report_key(ts->input, BTN_TOUCH, 0); + } + + input_sync(ts->input); +} + +static void ts_timer(unsigned long arg) +{ + struct ts *ts = (struct ts *)arg; + + ts_update_pen_state(ts, 0, 0, 0); +} + +static irqreturn_t ts_interrupt(int irq, void *dev_id) +{ + u32 avgs, x, y, lx, ly; + u32 num_op, num_samp; + u32 status; + + struct ts *ts = dev_id; + + status = readl_relaxed(TSSC_REG(STATUS)); + avgs = readl_relaxed(TSSC_REG(AVG12)); + x = avgs & 0xFFFF; + y = avgs >> 16; + + /* For pen down make sure that the data just read is still valid. + * The DATA bit will still be set if the ARM9 hasn't clobbered + * the TSSC. If it's not set, then it doesn't need to be cleared + * here, so just return. + */ + if (!(readl_relaxed(TSSC_REG(CTL)) & TSSC_CTL_DATA)) + goto out; + + /* Data has been read, OK to clear the data flag */ + writel_relaxed(TSSC_CTL_STATE, TSSC_REG(CTL)); + /* barrier: Write to complete before the next sample */ + mb(); + /* Valid samples are indicated by the sample number in the status + * register being the number of expected samples and the number of + * samples collected being zero (this check is due to ADC contention). + */ + num_op = (status & TSSC_STS_OPN_BMSK) >> TSSC_STS_OPN_SHIFT; + num_samp = (status & TSSC_STS_NUMSAMP_BMSK) >> TSSC_STS_NUMSAMP_SHFT; + + if ((num_op == TSSC_NUMBER_OF_OPERATIONS) && (num_samp == 0)) { + /* TSSC can do Z axis measurment, but driver doesn't support + * this yet. + */ + + /* + * REMOVE THIS: + * These x, y co-ordinates adjustments will be removed once + * Android framework adds calibration framework. + */ +#ifdef CONFIG_ANDROID_TOUCHSCREEN_MSM_HACKS + lx = ts->x_max - x; + ly = ts->y_max - y; +#else + lx = x; + ly = y; +#endif + ts_update_pen_state(ts, lx, ly, 255); + /* kick pen up timer - to make sure it expires again(!) */ + mod_timer(&ts->timer, + jiffies + msecs_to_jiffies(TS_PENUP_TIMEOUT_MS)); + + } else + printk(KERN_INFO "Ignored interrupt: {%3d, %3d}," + " op = %3d samp = %3d\n", + x, y, num_op, num_samp); + +out: + return IRQ_HANDLED; +} + +static int __devinit ts_probe(struct platform_device *pdev) +{ + int result; + struct input_dev *input_dev; + struct resource *res, *ioarea; + struct ts *ts; + unsigned int x_max, y_max, pressure_max; + struct msm_ts_platform_data *pdata = pdev->dev.platform_data; + + /* The primary initialization of the TS Hardware + * is taken care of by the ADC code on the modem side + */ + + ts = kzalloc(sizeof(struct ts), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!input_dev || !ts) { + result = -ENOMEM; + goto fail_alloc_mem; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); + result = -ENOENT; + goto fail_alloc_mem; + } + + ts->irq = platform_get_irq(pdev, 0); + if (!ts->irq) { + dev_err(&pdev->dev, "Could not get IORESOURCE_IRQ\n"); + result = -ENODEV; + goto fail_alloc_mem; + } + + ioarea = request_mem_region(res->start, resource_size(res), pdev->name); + if (!ioarea) { + dev_err(&pdev->dev, "Could not allocate io region\n"); + result = -EBUSY; + goto fail_alloc_mem; + } + + virt = ioremap(res->start, resource_size(res)); + if (!virt) { + dev_err(&pdev->dev, "Could not ioremap region\n"); + result = -ENOMEM; + goto fail_ioremap; + } + + input_dev->name = TS_DRIVER_NAME; + input_dev->phys = "msm_touch/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0002; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE); + input_dev->absbit[BIT_WORD(ABS_MISC)] = BIT_MASK(ABS_MISC); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + if (pdata) { + x_max = pdata->x_max ? : X_MAX; + y_max = pdata->y_max ? : Y_MAX; + pressure_max = pdata->pressure_max ? : P_MAX; + } else { + x_max = X_MAX; + y_max = Y_MAX; + pressure_max = P_MAX; + } + + ts->x_max = x_max; + ts->y_max = y_max; + + input_set_abs_params(input_dev, ABS_X, 0, x_max, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, y_max, 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, pressure_max, 0, 0); + + result = input_register_device(input_dev); + if (result) + goto fail_ip_reg; + + ts->input = input_dev; + + setup_timer(&ts->timer, ts_timer, (unsigned long)ts); + result = request_irq(ts->irq, ts_interrupt, IRQF_TRIGGER_RISING, + "touchscreen", ts); + if (result) + goto fail_req_irq; + + platform_set_drvdata(pdev, ts); + + return 0; + +fail_req_irq: + input_unregister_device(input_dev); + input_dev = NULL; +fail_ip_reg: + iounmap(virt); +fail_ioremap: + release_mem_region(res->start, resource_size(res)); +fail_alloc_mem: + input_free_device(input_dev); + kfree(ts); + return result; +} + +static int __devexit ts_remove(struct platform_device *pdev) +{ + struct resource *res; + struct ts *ts = platform_get_drvdata(pdev); + + free_irq(ts->irq, ts); + del_timer_sync(&ts->timer); + + input_unregister_device(ts->input); + iounmap(virt); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + platform_set_drvdata(pdev, NULL); + kfree(ts); + + return 0; +} + +static struct platform_driver ts_driver = { + .probe = ts_probe, + .remove = __devexit_p(ts_remove), + .driver = { + .name = TS_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ts_init(void) +{ + return platform_driver_register(&ts_driver); +} +module_init(ts_init); + +static void __exit ts_exit(void) +{ + platform_driver_unregister(&ts_driver); +} +module_exit(ts_exit); + +MODULE_DESCRIPTION("MSM Touch Screen driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:msm_touchscreen"); diff --git a/drivers/input/touchscreen/msm_ts.c b/drivers/input/touchscreen/msm_ts.c new file mode 100644 index 00000000000..122b45d9ab6 --- /dev/null +++ b/drivers/input/touchscreen/msm_ts.c @@ -0,0 +1,513 @@ +/* drivers/input/touchscreen/msm_ts.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2010-2011, Code Aurora Forum. 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. + * + * TODO: + * - Add a timer to simulate a pen_up in case there's a timeout. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include +#endif + +#include + +#define TSSC_CTL 0x100 +#define TSSC_CTL_PENUP_IRQ (1 << 12) +#define TSSC_CTL_DATA_FLAG (1 << 11) +#define TSSC_CTL_DEBOUNCE_EN (1 << 6) +#define TSSC_CTL_EN_AVERAGE (1 << 5) +#define TSSC_CTL_MODE_MASTER (3 << 3) +#define TSSC_CTL_SW_RESET (1 << 2) +#define TSSC_CTL_ENABLE (1 << 0) +#define TSSC_OPN 0x104 +#define TSSC_OPN_NOOP 0x00 +#define TSSC_OPN_4WIRE_X 0x01 +#define TSSC_OPN_4WIRE_Y 0x02 +#define TSSC_OPN_4WIRE_Z1 0x03 +#define TSSC_OPN_4WIRE_Z2 0x04 +#define TSSC_SAMPLING_INT 0x108 +#define TSSC_STATUS 0x10c +#define TSSC_AVG_12 0x110 +#define TSSC_AVG_34 0x114 +#define TSSC_SAMPLE(op,samp) ((0x118 + ((op & 0x3) * 0x20)) + \ + ((samp & 0x7) * 0x4)) +#define TSSC_TEST_1 0x198 + #define TSSC_TEST_1_EN_GATE_DEBOUNCE (1 << 2) +#define TSSC_TEST_2 0x19c + +struct msm_ts { + struct msm_ts_platform_data *pdata; + struct input_dev *input_dev; + void __iomem *tssc_base; + uint32_t ts_down:1; + struct ts_virt_key *vkey_down; + struct marimba_tsadc_client *ts_client; + + unsigned int sample_irq; + unsigned int pen_up_irq; + +#if defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif + struct device *dev; +}; + +static uint32_t msm_tsdebug; +module_param_named(tsdebug, msm_tsdebug, uint, 0664); + +#define tssc_readl(t, a) (readl_relaxed(((t)->tssc_base) + (a))) +#define tssc_writel(t, v, a) do {writel_relaxed(v, \ + ((t)->tssc_base) + (a)); } \ + while (0) + +static void setup_next_sample(struct msm_ts *ts) +{ + uint32_t tmp; + + /* 1.2ms debounce time */ + tmp = ((2 << 7) | TSSC_CTL_DEBOUNCE_EN | TSSC_CTL_EN_AVERAGE | + TSSC_CTL_MODE_MASTER | TSSC_CTL_ENABLE); + tssc_writel(ts, tmp, TSSC_CTL); + /* barrier: Make sure the write completes before the next sample */ + mb(); +} + +static struct ts_virt_key *find_virt_key(struct msm_ts *ts, + struct msm_ts_virtual_keys *vkeys, + uint32_t val) +{ + int i; + + if (!vkeys) + return NULL; + + for (i = 0; i < vkeys->num_keys; ++i) + if ((val >= vkeys->keys[i].min) && (val <= vkeys->keys[i].max)) + return &vkeys->keys[i]; + return NULL; +} + + +static irqreturn_t msm_ts_irq(int irq, void *dev_id) +{ + struct msm_ts *ts = dev_id; + struct msm_ts_platform_data *pdata = ts->pdata; + + uint32_t tssc_avg12, tssc_avg34, tssc_status, tssc_ctl; + int x, y, z1, z2; + int was_down; + int down; + + tssc_ctl = tssc_readl(ts, TSSC_CTL); + tssc_status = tssc_readl(ts, TSSC_STATUS); + tssc_avg12 = tssc_readl(ts, TSSC_AVG_12); + tssc_avg34 = tssc_readl(ts, TSSC_AVG_34); + + setup_next_sample(ts); + + x = tssc_avg12 & 0xffff; + y = tssc_avg12 >> 16; + z1 = tssc_avg34 & 0xffff; + z2 = tssc_avg34 >> 16; + + /* invert the inputs if necessary */ + if (pdata->inv_x) x = pdata->inv_x - x; + if (pdata->inv_y) y = pdata->inv_y - y; + if (x < 0) x = 0; + if (y < 0) y = 0; + + down = !(tssc_ctl & TSSC_CTL_PENUP_IRQ); + was_down = ts->ts_down; + ts->ts_down = down; + + /* no valid data */ + if (down && !(tssc_ctl & TSSC_CTL_DATA_FLAG)) + return IRQ_HANDLED; + + if (msm_tsdebug & 2) + printk("%s: down=%d, x=%d, y=%d, z1=%d, z2=%d, status %x\n", + __func__, down, x, y, z1, z2, tssc_status); + + if (!was_down && down) { + struct ts_virt_key *vkey = NULL; + + if (pdata->vkeys_y && (y > pdata->virt_y_start)) + vkey = find_virt_key(ts, pdata->vkeys_y, x); + if (!vkey && ts->pdata->vkeys_x && (x > pdata->virt_x_start)) + vkey = find_virt_key(ts, pdata->vkeys_x, y); + + if (vkey) { + WARN_ON(ts->vkey_down != NULL); + if(msm_tsdebug) + printk("%s: virtual key down %d\n", __func__, + vkey->key); + ts->vkey_down = vkey; + input_report_key(ts->input_dev, vkey->key, 1); + input_sync(ts->input_dev); + return IRQ_HANDLED; + } + } else if (ts->vkey_down != NULL) { + if (!down) { + if(msm_tsdebug) + printk("%s: virtual key up %d\n", __func__, + ts->vkey_down->key); + input_report_key(ts->input_dev, ts->vkey_down->key, 0); + input_sync(ts->input_dev); + ts->vkey_down = NULL; + } + return IRQ_HANDLED; + } + + if (down) { + input_report_abs(ts->input_dev, ABS_X, x); + input_report_abs(ts->input_dev, ABS_Y, y); + input_report_abs(ts->input_dev, ABS_PRESSURE, z1); + } + input_report_key(ts->input_dev, BTN_TOUCH, down); + input_sync(ts->input_dev); + + return IRQ_HANDLED; +} + +static void dump_tssc_regs(struct msm_ts *ts) +{ +#define __dump_tssc_reg(r) \ + do { printk(#r " %x\n", tssc_readl(ts, (r))); } while(0) + + __dump_tssc_reg(TSSC_CTL); + __dump_tssc_reg(TSSC_OPN); + __dump_tssc_reg(TSSC_SAMPLING_INT); + __dump_tssc_reg(TSSC_STATUS); + __dump_tssc_reg(TSSC_AVG_12); + __dump_tssc_reg(TSSC_AVG_34); + __dump_tssc_reg(TSSC_TEST_1); +#undef __dump_tssc_reg +} + +static int __devinit msm_ts_hw_init(struct msm_ts *ts) +{ + uint32_t tmp; + + /* Enable the register clock to tssc so we can configure it. */ + tssc_writel(ts, TSSC_CTL_ENABLE, TSSC_CTL); + /* Enable software reset*/ + tssc_writel(ts, TSSC_CTL_SW_RESET, TSSC_CTL); + + /* op1 - measure X, 1 sample, 12bit resolution */ + tmp = (TSSC_OPN_4WIRE_X << 16) | (2 << 8) | (2 << 0); + /* op2 - measure Y, 1 sample, 12bit resolution */ + tmp |= (TSSC_OPN_4WIRE_Y << 20) | (2 << 10) | (2 << 2); + /* op3 - measure Z1, 1 sample, 8bit resolution */ + tmp |= (TSSC_OPN_4WIRE_Z1 << 24) | (2 << 12) | (0 << 4); + + /* XXX: we don't actually need to measure Z2 (thus 0 samples) when + * doing voltage-driven measurement */ + /* op4 - measure Z2, 0 samples, 8bit resolution */ + tmp |= (TSSC_OPN_4WIRE_Z2 << 28) | (0 << 14) | (0 << 6); + tssc_writel(ts, tmp, TSSC_OPN); + + /* 16ms sampling interval */ + tssc_writel(ts, 16, TSSC_SAMPLING_INT); + /* Enable gating logic to fix the timing delays caused because of + * enabling debounce logic */ + tssc_writel(ts, TSSC_TEST_1_EN_GATE_DEBOUNCE, TSSC_TEST_1); + + setup_next_sample(ts); + + return 0; +} + +static void msm_ts_enable(struct msm_ts *ts, bool enable) +{ + uint32_t val; + + if (enable == true) + msm_ts_hw_init(ts); + else { + val = tssc_readl(ts, TSSC_CTL); + val &= ~TSSC_CTL_ENABLE; + tssc_writel(ts, val, TSSC_CTL); + } +} + +#ifdef CONFIG_PM +static int +msm_ts_suspend(struct device *dev) +{ + struct msm_ts *ts = dev_get_drvdata(dev); + + if (device_may_wakeup(dev) && + device_may_wakeup(dev->parent)) + enable_irq_wake(ts->sample_irq); + else { + disable_irq(ts->sample_irq); + disable_irq(ts->pen_up_irq); + msm_ts_enable(ts, false); + } + + return 0; +} + +static int +msm_ts_resume(struct device *dev) +{ + struct msm_ts *ts = dev_get_drvdata(dev); + + if (device_may_wakeup(dev) && + device_may_wakeup(dev->parent)) + disable_irq_wake(ts->sample_irq); + else { + msm_ts_enable(ts, true); + enable_irq(ts->sample_irq); + enable_irq(ts->pen_up_irq); + } + + return 0; +} + +static struct dev_pm_ops msm_touchscreen_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = msm_ts_suspend, + .resume = msm_ts_resume, +#endif +}; +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void msm_ts_early_suspend(struct early_suspend *h) +{ + struct msm_ts *ts = container_of(h, struct msm_ts, early_suspend); + + msm_ts_suspend(ts->dev); +} + +static void msm_ts_late_resume(struct early_suspend *h) +{ + struct msm_ts *ts = container_of(h, struct msm_ts, early_suspend); + + msm_ts_resume(ts->dev); +} +#endif + + +static int __devinit msm_ts_probe(struct platform_device *pdev) +{ + struct msm_ts_platform_data *pdata = pdev->dev.platform_data; + struct msm_ts *ts; + struct resource *tssc_res; + struct resource *irq1_res; + struct resource *irq2_res; + int err = 0; + int i; + struct marimba_tsadc_client *ts_client; + + printk("%s\n", __func__); + + tssc_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tssc"); + irq1_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "tssc1"); + irq2_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "tssc2"); + + if (!tssc_res || !irq1_res || !irq2_res) { + pr_err("%s: required resources not defined\n", __func__); + return -ENODEV; + } + + if (pdata == NULL) { + pr_err("%s: missing platform_data\n", __func__); + return -ENODEV; + } + + ts = kzalloc(sizeof(struct msm_ts), GFP_KERNEL); + if (ts == NULL) { + pr_err("%s: No memory for struct msm_ts\n", __func__); + return -ENOMEM; + } + ts->pdata = pdata; + ts->dev = &pdev->dev; + + ts->sample_irq = irq1_res->start; + ts->pen_up_irq = irq2_res->start; + + ts->tssc_base = ioremap(tssc_res->start, resource_size(tssc_res)); + if (ts->tssc_base == NULL) { + pr_err("%s: Can't ioremap region (0x%08x - 0x%08x)\n", __func__, + (uint32_t)tssc_res->start, (uint32_t)tssc_res->end); + err = -ENOMEM; + goto err_ioremap_tssc; + } + + ts_client = marimba_tsadc_register(pdev, 1); + if (IS_ERR(ts_client)) { + pr_err("%s: Unable to register with TSADC\n", __func__); + err = -ENOMEM; + goto err_tsadc_register; + } + ts->ts_client = ts_client; + + err = marimba_tsadc_start(ts_client); + if (err) { + pr_err("%s: Unable to start TSADC\n", __func__); + err = -EINVAL; + goto err_start_tsadc; + } + + ts->input_dev = input_allocate_device(); + if (ts->input_dev == NULL) { + pr_err("failed to allocate touchscreen input device\n"); + err = -ENOMEM; + goto err_alloc_input_dev; + } + ts->input_dev->name = "msm-touchscreen"; + ts->input_dev->dev.parent = &pdev->dev; + + input_set_drvdata(ts->input_dev, ts); + + input_set_capability(ts->input_dev, EV_KEY, BTN_TOUCH); + set_bit(EV_ABS, ts->input_dev->evbit); + + input_set_abs_params(ts->input_dev, ABS_X, pdata->min_x, pdata->max_x, + 0, 0); + input_set_abs_params(ts->input_dev, ABS_Y, pdata->min_y, pdata->max_y, + 0, 0); + input_set_abs_params(ts->input_dev, ABS_PRESSURE, pdata->min_press, + pdata->max_press, 0, 0); + + for (i = 0; pdata->vkeys_x && (i < pdata->vkeys_x->num_keys); ++i) + input_set_capability(ts->input_dev, EV_KEY, + pdata->vkeys_x->keys[i].key); + for (i = 0; pdata->vkeys_y && (i < pdata->vkeys_y->num_keys); ++i) + input_set_capability(ts->input_dev, EV_KEY, + pdata->vkeys_y->keys[i].key); + + err = input_register_device(ts->input_dev); + if (err != 0) { + pr_err("%s: failed to register input device\n", __func__); + goto err_input_dev_reg; + } + + msm_ts_hw_init(ts); + + err = request_irq(ts->sample_irq, msm_ts_irq, + (irq1_res->flags & ~IORESOURCE_IRQ) | IRQF_DISABLED, + "msm_touchscreen", ts); + if (err != 0) { + pr_err("%s: Cannot register irq1 (%d)\n", __func__, err); + goto err_request_irq1; + } + + err = request_irq(ts->pen_up_irq, msm_ts_irq, + (irq2_res->flags & ~IORESOURCE_IRQ) | IRQF_DISABLED, + "msm_touchscreen", ts); + if (err != 0) { + pr_err("%s: Cannot register irq2 (%d)\n", __func__, err); + goto err_request_irq2; + } + + platform_set_drvdata(pdev, ts); + +#ifdef CONFIG_HAS_EARLYSUSPEND + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + TSSC_SUSPEND_LEVEL; + ts->early_suspend.suspend = msm_ts_early_suspend; + ts->early_suspend.resume = msm_ts_late_resume; + register_early_suspend(&ts->early_suspend); +#endif + + device_init_wakeup(&pdev->dev, pdata->can_wakeup); + pr_info("%s: tssc_base=%p irq1=%d irq2=%d\n", __func__, + ts->tssc_base, (int)ts->sample_irq, (int)ts->pen_up_irq); + dump_tssc_regs(ts); + return 0; + +err_request_irq2: + free_irq(ts->sample_irq, ts); + +err_request_irq1: + /* disable the tssc */ + tssc_writel(ts, TSSC_CTL_ENABLE, TSSC_CTL); + +err_input_dev_reg: + input_set_drvdata(ts->input_dev, NULL); + input_free_device(ts->input_dev); + +err_alloc_input_dev: +err_start_tsadc: + marimba_tsadc_unregister(ts->ts_client); + +err_tsadc_register: + iounmap(ts->tssc_base); + +err_ioremap_tssc: + kfree(ts); + return err; +} + +static int __devexit msm_ts_remove(struct platform_device *pdev) +{ + struct msm_ts *ts = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + marimba_tsadc_unregister(ts->ts_client); + free_irq(ts->sample_irq, ts); + free_irq(ts->pen_up_irq, ts); + input_unregister_device(ts->input_dev); + iounmap(ts->tssc_base); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&ts->early_suspend); +#endif + platform_set_drvdata(pdev, NULL); + kfree(ts); + + return 0; +} + +static struct platform_driver msm_touchscreen_driver = { + .driver = { + .name = "msm_touchscreen", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &msm_touchscreen_pm_ops, +#endif + }, + .probe = msm_ts_probe, + .remove = __devexit_p(msm_ts_remove), +}; + +static int __init msm_ts_init(void) +{ + return platform_driver_register(&msm_touchscreen_driver); +} + +static void __exit msm_ts_exit(void) +{ + platform_driver_unregister(&msm_touchscreen_driver); +} + +module_init(msm_ts_init); +module_exit(msm_ts_exit); +MODULE_DESCRIPTION("Qualcomm MSM/QSD Touchscreen controller driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:msm_touchscreen"); diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c index fadc11545b1..ac76cde61e9 100644 --- a/drivers/input/touchscreen/tsc2007.c +++ b/drivers/input/touchscreen/tsc2007.c @@ -26,6 +26,12 @@ #include #include #include +#include + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include +#define TSC2007_SUSPEND_LEVEL 1 +#endif #define TSC2007_MEASURE_TEMP0 (0x0 << 4) #define TSC2007_MEASURE_AUX (0x2 << 4) @@ -79,8 +85,17 @@ struct tsc2007 { bool pendown; int irq; + bool invert_x; + bool invert_y; + bool invert_z1; + bool invert_z2; + int (*get_pendown_state)(void); void (*clear_penirq)(void); + int (*power_shutdown)(bool); +#if defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif }; static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd) @@ -117,6 +132,18 @@ static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc) tc->z1 = tsc2007_xfer(tsc, READ_Z1); tc->z2 = tsc2007_xfer(tsc, READ_Z2); + if (tsc->invert_x == true) + tc->x = MAX_12BIT - tc->x; + + if (tsc->invert_y == true) + tc->y = MAX_12BIT - tc->y; + + if (tsc->invert_z1 == true) + tc->z1 = MAX_12BIT - tc->z1; + + if (tsc->invert_z2 == true) + tc->z2 = MAX_12BIT - tc->z2; + /* Prepare for next touch reading - power down ADC, enable PENIRQ */ tsc2007_xfer(tsc, PWRDOWN); } @@ -263,6 +290,72 @@ static void tsc2007_free_irq(struct tsc2007 *ts) } } +#ifdef CONFIG_PM +static int tsc2007_suspend(struct device *dev) +{ + int rc; + struct tsc2007 *ts = dev_get_drvdata(dev); + + disable_irq(ts->irq); + + if (cancel_delayed_work_sync(&ts->work)) + enable_irq(ts->irq); + + if (ts->power_shutdown) { + rc = ts->power_shutdown(true); + if (rc) { + pr_err("%s: Power off failed, suspend failed (%d)\n", + __func__, rc); + return rc; + } + } + + return 0; +} + +static int tsc2007_resume(struct device *dev) +{ + int rc; + struct tsc2007 *ts = dev_get_drvdata(dev); + + if (ts->power_shutdown) { + rc = ts->power_shutdown(false); + if (rc) { + pr_err("%s: Power on failed, resume failed (%d)\n", + __func__, rc); + return rc; + } + } + + enable_irq(ts->irq); + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void tsc2007_early_suspend(struct early_suspend *h) +{ + struct tsc2007 *ts = container_of(h, struct tsc2007, early_suspend); + + tsc2007_suspend(&ts->client->dev); +} + +static void tsc2007_late_resume(struct early_suspend *h) +{ + struct tsc2007 *ts = container_of(h, struct tsc2007, early_suspend); + + tsc2007_resume(&ts->client->dev); +} +#endif + +static const struct dev_pm_ops tsc2007_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = tsc2007_suspend, + .resume = tsc2007_resume, +#endif +}; +#endif + static int __devinit tsc2007_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -299,6 +392,11 @@ static int __devinit tsc2007_probe(struct i2c_client *client, ts->poll_period = pdata->poll_period ? : 1; ts->get_pendown_state = pdata->get_pendown_state; ts->clear_penirq = pdata->clear_penirq; + ts->invert_x = pdata->invert_x; + ts->invert_y = pdata->invert_y; + ts->invert_z1 = pdata->invert_z1; + ts->invert_z2 = pdata->invert_z2; + ts->power_shutdown = pdata->power_shutdown; snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&client->dev)); @@ -318,7 +416,7 @@ static int __devinit tsc2007_probe(struct i2c_client *client, if (pdata->init_platform_hw) pdata->init_platform_hw(); - err = request_irq(ts->irq, tsc2007_irq, 0, + err = request_irq(ts->irq, tsc2007_irq, pdata->irq_flags, client->dev.driver->name, ts); if (err < 0) { dev_err(&client->dev, "irq %d busy?\n", ts->irq); @@ -334,6 +432,14 @@ static int __devinit tsc2007_probe(struct i2c_client *client, if (err) goto err_free_irq; +#ifdef CONFIG_HAS_EARLYSUSPEND + ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + TSC2007_SUSPEND_LEVEL; + ts->early_suspend.suspend = tsc2007_early_suspend; + ts->early_suspend.resume = tsc2007_late_resume; + register_early_suspend(&ts->early_suspend); +#endif + i2c_set_clientdata(client, ts); return 0; @@ -358,6 +464,9 @@ static int __devexit tsc2007_remove(struct i2c_client *client) if (pdata->exit_platform_hw) pdata->exit_platform_hw(); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&ts->early_suspend); +#endif input_unregister_device(ts->input); kfree(ts); @@ -374,7 +483,10 @@ MODULE_DEVICE_TABLE(i2c, tsc2007_idtable); static struct i2c_driver tsc2007_driver = { .driver = { .owner = THIS_MODULE, - .name = "tsc2007" + .name = "tsc2007", +#ifdef CONFIG_PM + .pm = &tsc2007_pm_ops, +#endif }, .id_table = tsc2007_idtable, .probe = tsc2007_probe, diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b84e46bdedc..feb345d2073 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -182,6 +182,18 @@ config LEDS_GPIO defined as platform devices and/or OpenFirmware platform devices. The code to use these bindings can be selected below. +config LEDS_MSM_PDM + tristate "LED Support through PDM" + depends on LEDS_CLASS + help + This option enables support for the LEDs operated through Pulse + Denisty Modulation. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called leds-msm-pdm. + config LEDS_GPIO_PLATFORM bool "Platform device bindings for GPIO LEDs" depends on LEDS_GPIO @@ -211,6 +223,12 @@ config LEDS_LP3944 To compile this driver as a module, choose M here: the module will be called leds-lp3944. +config LEDS_CPLD + tristate "LED Support for CPLD connected LEDs" + depends on LEDS_CLASS + help + This option enables support for the LEDs connected to CPLD + config LEDS_LP5521 tristate "LED Support for N.S. LP5521 LED driver chip" depends on LEDS_CLASS && I2C @@ -269,6 +287,17 @@ config LEDS_PCA955X LED driver chips accessed via the I2C bus. Supported devices include PCA9550, PCA9551, PCA9552, and PCA9553. +config LEDS_PM8XXX + tristate "LED Support for Qualcomm PMIC8XXX" + depends on MFD_PM8XXX + help + This option enables support for LEDs connected over PMIC8XXX + (Power Management IC) chip on Qualcomm reference boards, + for example SURF and FFAs. + + To compile this driver as a module, choose M here: the module will + be called leds-pmic8xxx. + config LEDS_WM831X_STATUS tristate "LED support for status LEDs on WM831x PMICs" depends on LEDS_CLASS @@ -323,6 +352,24 @@ config LEDS_BD2802 This option enables support for BD2802GU RGB LED driver chips accessed via the I2C bus. +config LEDS_MSM_PMIC + tristate "LED Support for Qualcomm PMIC connected LEDs" + default y + depends on ARCH_MSM + help + This option enables support for LEDs connected over PMIC + (Power Management IC) chip on Qualcomm reference boards, + for example SURF and FFAs. + +config LEDS_PMIC8058 + tristate "LED Support for Qualcomm PMIC8058" + default n + depends on PMIC8058 + help + This option enables support for LEDs connected over PMIC8058 + (Power Management IC) chip on Qualcomm reference boards, + for example SURF and FFAs. + config LEDS_INTEL_SS4200 tristate "LED driver for Intel NAS SS4200 series" depends on LEDS_CLASS @@ -369,6 +416,14 @@ config LEDS_MC13783 This option enable support for on-chip LED drivers found on Freescale Semiconductor MC13783 PMIC. +config LEDS_QCIBL + tristate "LED Support for Quanta LCD backlight" + depends on SENSORS_WPCE775X && ARCH_MSM_SCORPION + default n + help + Say Y here if you want to use the Quanta backlight driver for ST15 + platform. + config LEDS_NS2 tristate "LED support for Network Space v2 GPIO LEDs" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index cb77b9bb2f9..819abc0e4d7 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -21,15 +21,18 @@ obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o +obj-$(CONFIG_LEDS_PM8XXX) += leds-pm8xxx.o obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o +obj-$(CONFIG_LEDS_CPLD) += leds-cpld.o obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o obj-$(CONFIG_LEDS_FSG) += leds-fsg.o obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o +obj-$(CONFIG_LEDS_MSM_PMIC) += leds-msm-pmic.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o @@ -43,6 +46,9 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o obj-$(CONFIG_LEDS_NS2) += leds-ns2.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o +obj-$(CONFIG_LEDS_PMIC8058) += leds-pmic8058.o +obj-$(CONFIG_LEDS_QCIBL) += leds-qci-backlight.o +obj-$(CONFIG_LEDS_MSM_PDM) += leds-msm-pdm.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index dc3d3d83191..43273fee369 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -20,10 +20,51 @@ #include #include #include +#include #include "leds.h" static struct class *leds_class; +#ifdef CONFIG_HAS_EARLYSUSPEND + +static void change_brightness(struct work_struct *brightness_change_data) +{ + struct deferred_brightness_change *brightness_change = container_of( + brightness_change_data, + struct deferred_brightness_change, + brightness_change_work); + struct led_classdev *led_cdev = brightness_change->led_cdev; + enum led_brightness value = brightness_change->value; + + led_cdev->brightness_set(led_cdev, value); + + /* Free up memory for the brightness_change structure. */ + kfree(brightness_change); +} + +int queue_brightness_change(struct led_classdev *led_cdev, + enum led_brightness value) +{ + /* Initialize the brightness_change_work and its super-struct. */ + struct deferred_brightness_change *brightness_change = + kzalloc(sizeof(struct deferred_brightness_change), GFP_KERNEL); + + if (!brightness_change) + return -ENOMEM; + + brightness_change->led_cdev = led_cdev; + brightness_change->value = value; + + INIT_WORK(&(brightness_change->brightness_change_work), + change_brightness); + queue_work(suspend_work_queue, + &(brightness_change->brightness_change_work)); + + return 0; +} + +#endif + static void led_update_brightness(struct led_classdev *led_cdev) { if (led_cdev->brightness_get) @@ -64,6 +105,25 @@ static ssize_t led_brightness_store(struct device *dev, return ret; } +static ssize_t led_max_brightness_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + ssize_t ret = -EINVAL; + unsigned long state = 0; + + ret = strict_strtoul(buf, 10, &state); + if (!ret) { + ret = size; + if (state > LED_FULL) + state = LED_FULL; + led_cdev->max_brightness = state; + led_set_brightness(led_cdev, led_cdev->brightness); + } + + return ret; +} + static ssize_t led_max_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -74,7 +134,8 @@ static ssize_t led_max_brightness_show(struct device *dev, static struct device_attribute led_class_attrs[] = { __ATTR(brightness, 0644, led_brightness_show, led_brightness_store), - __ATTR(max_brightness, 0444, led_max_brightness_show, NULL), + __ATTR(max_brightness, 0644, led_max_brightness_show, + led_max_brightness_store), #ifdef CONFIG_LEDS_TRIGGERS __ATTR(trigger, 0644, led_trigger_show, led_trigger_store), #endif diff --git a/drivers/leds/leds-cpld.c b/drivers/leds/leds-cpld.c new file mode 100644 index 00000000000..eab004c3d75 --- /dev/null +++ b/drivers/leds/leds-cpld.c @@ -0,0 +1,405 @@ +/* include/asm/mach-msm/leds-cpld.c + * + * Copyright (C) 2008 HTC Corporation. + * + * Author: Farmer Tseng + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_LED_CHANGE 0 + +static int _g_cpld_led_addr; + +struct CPLD_LED_data { + spinlock_t data_lock; + struct led_classdev leds[4]; /* blue, green, red */ +}; + +static ssize_t led_blink_solid_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct CPLD_LED_data *CPLD_LED; + int idx = 2; + uint8_t reg_val; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + ssize_t ret = 0; + + if (!strcmp(led_cdev->name, "red")) + idx = 0; + else if (!strcmp(led_cdev->name, "green")) + idx = 1; + else + idx = 2; + + CPLD_LED = container_of(led_cdev, struct CPLD_LED_data, leds[idx]); + + spin_lock(&CPLD_LED->data_lock); + reg_val = readb(_g_cpld_led_addr); + reg_val = reg_val >> (2 * idx + 1); + reg_val &= 0x1; + spin_unlock(&CPLD_LED->data_lock); + + /* no lock needed for this */ + sprintf(buf, "%u\n", reg_val); + ret = strlen(buf) + 1; + + return ret; +} + +static ssize_t led_blink_solid_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct CPLD_LED_data *CPLD_LED; + int idx = 2; + uint8_t reg_val; + char *after; + unsigned long state; + ssize_t ret = -EINVAL; + size_t count; + + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + if (!strcmp(led_cdev->name, "red")) + idx = 0; + else if (!strcmp(led_cdev->name, "green")) + idx = 1; + else + idx = 2; + + CPLD_LED = container_of(led_cdev, struct CPLD_LED_data, leds[idx]); + + state = simple_strtoul(buf, &after, 10); + + count = after - buf; + + if (*after && isspace(*after)) + count++; + + if (count == size) { + ret = count; + spin_lock(&CPLD_LED->data_lock); + reg_val = readb(_g_cpld_led_addr); + if (state) + reg_val |= 1 << (2 * idx + 1); + else + reg_val &= ~(1 << (2 * idx + 1)); + + writeb(reg_val, _g_cpld_led_addr); + spin_unlock(&CPLD_LED->data_lock); + } + + return ret; +} + +static DEVICE_ATTR(blink, 0644, led_blink_solid_show, led_blink_solid_store); + +static ssize_t cpldled_blink_all_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + uint8_t reg_val; + struct CPLD_LED_data *CPLD_LED = dev_get_drvdata(dev); + ssize_t ret = 0; + + spin_lock(&CPLD_LED->data_lock); + reg_val = readb(_g_cpld_led_addr); + reg_val &= 0x2A; + if (reg_val == 0x2A) + reg_val = 1; + else + reg_val = 0; + spin_unlock(&CPLD_LED->data_lock); + + /* no lock needed for this */ + sprintf(buf, "%u\n", reg_val); + ret = strlen(buf) + 1; + + return ret; +} + +static ssize_t cpldled_blink_all_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + uint8_t reg_val; + char *after; + unsigned long state; + ssize_t ret = -EINVAL; + size_t count; + struct CPLD_LED_data *CPLD_LED = dev_get_drvdata(dev); + + state = simple_strtoul(buf, &after, 10); + + count = after - buf; + + if (*after && isspace(*after)) + count++; + + if (count == size) { + ret = count; + spin_lock(&CPLD_LED->data_lock); + reg_val = readb(_g_cpld_led_addr); + if (state) + reg_val |= 0x2A; + else + reg_val &= ~0x2A; + + writeb(reg_val, _g_cpld_led_addr); + spin_unlock(&CPLD_LED->data_lock); + } + + return ret; +} + +static struct device_attribute dev_attr_blink_all = { + .attr = { + .name = "blink", + .mode = 0644, + }, + .show = cpldled_blink_all_show, + .store = cpldled_blink_all_store, +}; + +static void led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct CPLD_LED_data *CPLD_LED; + int idx = 2; + struct led_classdev *led; + uint8_t reg_val; + + if (!strcmp(led_cdev->name, "jogball-backlight")) { + if (brightness > 7) + reg_val = 1; + else + reg_val = brightness; + writeb(0, _g_cpld_led_addr + 0x8); + writeb(reg_val, _g_cpld_led_addr + 0x8); +#if DEBUG_LED_CHANGE + printk(KERN_INFO "LED change: jogball backlight = %d \n", + reg_val); +#endif + return; + } else if (!strcmp(led_cdev->name, "red")) { + idx = 0; + } else if (!strcmp(led_cdev->name, "green")) { + idx = 1; + } else { + idx = 2; + } + + CPLD_LED = container_of(led_cdev, struct CPLD_LED_data, leds[idx]); + spin_lock(&CPLD_LED->data_lock); + reg_val = readb(_g_cpld_led_addr); + led = &CPLD_LED->leds[idx]; + + if (led->brightness > LED_OFF) + reg_val |= 1 << (2 * idx); + else + reg_val &= ~(1 << (2 * idx)); + + writeb(reg_val, _g_cpld_led_addr); +#if DEBUG_LED_CHANGE + printk(KERN_INFO "LED change: %s = %d \n", led_cdev->name, led->brightness); +#endif + spin_unlock(&CPLD_LED->data_lock); +} + +static ssize_t cpldled_grpfreq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", 0); +} + +static ssize_t cpldled_grpfreq_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} + +static DEVICE_ATTR(grpfreq, 0644, cpldled_grpfreq_show, cpldled_grpfreq_store); + +static ssize_t cpldled_grppwm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%u\n", 0); +} + +static ssize_t cpldled_grppwm_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return 0; +} + +static DEVICE_ATTR(grppwm, 0644, cpldled_grppwm_show, cpldled_grppwm_store); + +static int CPLD_LED_probe(struct platform_device *pdev) +{ + int ret = 0; + int i, j; + struct resource *res; + struct CPLD_LED_data *CPLD_LED; + + CPLD_LED = kzalloc(sizeof(struct CPLD_LED_data), GFP_KERNEL); + if (CPLD_LED == NULL) { + printk(KERN_ERR "CPLD_LED_probe: no memory for device\n"); + ret = -ENOMEM; + goto err_alloc_failed; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENOMEM; + goto err_alloc_failed; + } + + _g_cpld_led_addr = res->start; + if (!_g_cpld_led_addr) { + ret = -ENOMEM; + goto err_alloc_failed; + } + + memset(CPLD_LED, 0, sizeof(struct CPLD_LED_data)); + writeb(0x00, _g_cpld_led_addr); + + CPLD_LED->leds[0].name = "red"; + CPLD_LED->leds[0].brightness_set = led_brightness_set; + + CPLD_LED->leds[1].name = "green"; + CPLD_LED->leds[1].brightness_set = led_brightness_set; + + CPLD_LED->leds[2].name = "blue"; + CPLD_LED->leds[2].brightness_set = led_brightness_set; + + CPLD_LED->leds[3].name = "jogball-backlight"; + CPLD_LED->leds[3].brightness_set = led_brightness_set; + + spin_lock_init(&CPLD_LED->data_lock); + + for (i = 0; i < 4; i++) { /* red, green, blue jogball */ + ret = led_classdev_register(&pdev->dev, &CPLD_LED->leds[i]); + if (ret) { + printk(KERN_ERR + "CPLD_LED: led_classdev_register failed\n"); + goto err_led_classdev_register_failed; + } + } + + for (i = 0; i < 3; i++) { + ret = + device_create_file(CPLD_LED->leds[i].dev, &dev_attr_blink); + if (ret) { + printk(KERN_ERR + "CPLD_LED: device_create_file failed\n"); + goto err_out_attr_blink; + } + } + + dev_set_drvdata(&pdev->dev, CPLD_LED); + ret = device_create_file(&pdev->dev, &dev_attr_blink_all); + if (ret) { + printk(KERN_ERR + "CPLD_LED: create dev_attr_blink_all failed\n"); + goto err_out_attr_blink; + } + ret = device_create_file(&pdev->dev, &dev_attr_grppwm); + if (ret) { + printk(KERN_ERR + "CPLD_LED: create dev_attr_grppwm failed\n"); + goto err_out_attr_grppwm; + } + ret = device_create_file(&pdev->dev, &dev_attr_grpfreq); + if (ret) { + printk(KERN_ERR + "CPLD_LED: create dev_attr_grpfreq failed\n"); + goto err_out_attr_grpfreq; + } + + return 0; + +err_out_attr_grpfreq: + device_remove_file(&pdev->dev, &dev_attr_grppwm); +err_out_attr_grppwm: + device_remove_file(&pdev->dev, &dev_attr_blink_all); +err_out_attr_blink: + for (j = 0; j < i; j++) + device_remove_file(CPLD_LED->leds[j].dev, &dev_attr_blink); + i = 3; + +err_led_classdev_register_failed: + for (j = 0; j < i; j++) + led_classdev_unregister(&CPLD_LED->leds[j]); + +err_alloc_failed: + kfree(CPLD_LED); + + return ret; +} + +static int __devexit CPLD_LED_remove(struct platform_device *pdev) +{ + struct CPLD_LED_data *CPLD_LED; + int i; + + CPLD_LED = platform_get_drvdata(pdev); + + for (i = 0; i < 3; i++) { + device_remove_file(CPLD_LED->leds[i].dev, &dev_attr_blink); + led_classdev_unregister(&CPLD_LED->leds[i]); + } + + device_remove_file(&pdev->dev, &dev_attr_blink_all); + device_remove_file(&pdev->dev, &dev_attr_grppwm); + device_remove_file(&pdev->dev, &dev_attr_grpfreq); + + kfree(CPLD_LED); + return 0; +} + +static struct platform_driver CPLD_LED_driver = { + .probe = CPLD_LED_probe, + .remove = __devexit_p(CPLD_LED_remove), + .driver = { + .name = "leds-cpld", + .owner = THIS_MODULE, + }, +}; + +static int __init CPLD_LED_init(void) +{ + return platform_driver_register(&CPLD_LED_driver); +} + +static void __exit CPLD_LED_exit(void) +{ + platform_driver_unregister(&CPLD_LED_driver); +} + +MODULE_AUTHOR("Farmer Tseng"); +MODULE_DESCRIPTION("CPLD_LED driver"); +MODULE_LICENSE("GPL"); + +module_init(CPLD_LED_init); +module_exit(CPLD_LED_exit); diff --git a/drivers/leds/leds-msm-pdm.c b/drivers/leds/leds-msm-pdm.c new file mode 100644 index 00000000000..9509514898a --- /dev/null +++ b/drivers/leds/leds-msm-pdm.c @@ -0,0 +1,233 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include + +/* Early-suspend level */ +#define LED_SUSPEND_LEVEL 1 +#endif + +#define PDM_DUTY_MAXVAL BIT(16) +#define PDM_DUTY_REFVAL BIT(15) + +struct pdm_led_data { + struct led_classdev cdev; + void __iomem *perph_base; + int pdm_offset; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif +}; + +static void msm_led_brightness_set_percent(struct pdm_led_data *led, + int duty_per) +{ + u16 duty_val; + + duty_val = PDM_DUTY_REFVAL - ((PDM_DUTY_MAXVAL * duty_per) / 100); + + if (!duty_per) + duty_val--; + + writel_relaxed(duty_val, led->perph_base + led->pdm_offset); +} + +static void msm_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct pdm_led_data *led = + container_of(led_cdev, struct pdm_led_data, cdev); + + msm_led_brightness_set_percent(led, (value * 100) / LED_FULL); +} + +#ifdef CONFIG_PM_SLEEP +static int msm_led_pdm_suspend(struct device *dev) +{ + struct pdm_led_data *led = dev_get_drvdata(dev); + + msm_led_brightness_set_percent(led, 0); + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void msm_led_pdm_early_suspend(struct early_suspend *h) +{ + struct pdm_led_data *led = container_of(h, + struct pdm_led_data, early_suspend); + + msm_led_pdm_suspend(led->cdev.dev->parent); +} + +#endif + +static const struct dev_pm_ops msm_led_pdm_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = msm_led_pdm_suspend, +#endif +}; +#endif + +static int __devinit msm_pdm_led_probe(struct platform_device *pdev) +{ + const struct led_info *pdata = pdev->dev.platform_data; + struct pdm_led_data *led; + struct resource *res, *ioregion; + u32 tcxo_pdm_ctl; + int rc; + + if (!pdata) { + pr_err("platform data is invalid\n"); + return -EINVAL; + } + + if (pdev->id > 2) { + pr_err("pdm id is invalid\n"); + return -EINVAL; + } + + led = kzalloc(sizeof(struct pdm_led_data), GFP_KERNEL); + if (!led) + return -ENOMEM; + + /* Enable runtime PM ops, start in ACTIVE mode */ + rc = pm_runtime_set_active(&pdev->dev); + if (rc < 0) + dev_dbg(&pdev->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&pdev->dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + pr_err("get resource failed\n"); + rc = -EINVAL; + goto err_get_res; + } + + ioregion = request_mem_region(res->start, resource_size(res), + pdev->name); + if (!ioregion) { + pr_err("request for mem region failed\n"); + rc = -ENOMEM; + goto err_get_res; + } + + led->perph_base = ioremap(res->start, resource_size(res)); + if (!led->perph_base) { + pr_err("ioremap failed\n"); + rc = -ENOMEM; + goto err_ioremap; + } + + /* Pulse Density Modulation(PDM) ids start with 0 and + * every PDM register takes 4 bytes + */ + led->pdm_offset = ((pdev->id) + 1) * 4; + + /* program tcxo_pdm_ctl register to enable pdm*/ + tcxo_pdm_ctl = readl_relaxed(led->perph_base); + tcxo_pdm_ctl |= (1 << pdev->id); + writel_relaxed(tcxo_pdm_ctl, led->perph_base); + + /* Start with LED in off state */ + msm_led_brightness_set_percent(led, 0); + + led->cdev.brightness_set = msm_led_brightness_set; + led->cdev.name = pdata->name ? : "leds-msm-pdm"; + + rc = led_classdev_register(&pdev->dev, &led->cdev); + if (rc) { + pr_err("led class registration failed\n"); + goto err_led_reg; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + led->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + LED_SUSPEND_LEVEL; + led->early_suspend.suspend = msm_led_pdm_early_suspend; + register_early_suspend(&led->early_suspend); +#endif + + platform_set_drvdata(pdev, led); + return 0; + +err_led_reg: + iounmap(led->perph_base); +err_ioremap: + release_mem_region(res->start, resource_size(res)); +err_get_res: + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + kfree(led); + return rc; +} + +static int __devexit msm_pdm_led_remove(struct platform_device *pdev) +{ + struct pdm_led_data *led = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&led->early_suspend); +#endif + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + led_classdev_unregister(&led->cdev); + msm_led_brightness_set_percent(led, 0); + iounmap(led->perph_base); + release_mem_region(res->start, resource_size(res)); + kfree(led); + + return 0; +} + +static struct platform_driver msm_pdm_led_driver = { + .probe = msm_pdm_led_probe, + .remove = __devexit_p(msm_pdm_led_remove), + .driver = { + .name = "leds-msm-pdm", + .owner = THIS_MODULE, +#ifdef CONFIG_PM_SLEEP + .pm = &msm_led_pdm_pm_ops, +#endif + }, +}; + +static int __init msm_pdm_led_init(void) +{ + return platform_driver_register(&msm_pdm_led_driver); +} +module_init(msm_pdm_led_init); + +static void __exit msm_pdm_led_exit(void) +{ + platform_driver_unregister(&msm_pdm_led_driver); +} +module_exit(msm_pdm_led_exit); + +MODULE_DESCRIPTION("MSM PDM LEDs driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:leds-msm-pdm"); diff --git a/drivers/leds/leds-msm-pmic.c b/drivers/leds/leds-msm-pmic.c new file mode 100644 index 00000000000..b9c6a531fd7 --- /dev/null +++ b/drivers/leds/leds-msm-pmic.c @@ -0,0 +1,105 @@ +/* + * leds-msm-pmic.c - MSM PMIC LEDs driver. + * + * Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include + +#include + +#define MAX_KEYPAD_BL_LEVEL 16 + +static void msm_keypad_bl_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + int ret; + + ret = pmic_set_led_intensity(LED_KEYPAD, value / MAX_KEYPAD_BL_LEVEL); + if (ret) + dev_err(led_cdev->dev, "can't set keypad backlight\n"); +} + +static struct led_classdev msm_kp_bl_led = { + .name = "keyboard-backlight", + .brightness_set = msm_keypad_bl_led_set, + .brightness = LED_OFF, +}; + +static int msm_pmic_led_probe(struct platform_device *pdev) +{ + int rc; + + rc = led_classdev_register(&pdev->dev, &msm_kp_bl_led); + if (rc) { + dev_err(&pdev->dev, "unable to register led class driver\n"); + return rc; + } + msm_keypad_bl_led_set(&msm_kp_bl_led, LED_OFF); + return rc; +} + +static int __devexit msm_pmic_led_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&msm_kp_bl_led); + + return 0; +} + +#ifdef CONFIG_PM +static int msm_pmic_led_suspend(struct platform_device *dev, + pm_message_t state) +{ + led_classdev_suspend(&msm_kp_bl_led); + + return 0; +} + +static int msm_pmic_led_resume(struct platform_device *dev) +{ + led_classdev_resume(&msm_kp_bl_led); + + return 0; +} +#else +#define msm_pmic_led_suspend NULL +#define msm_pmic_led_resume NULL +#endif + +static struct platform_driver msm_pmic_led_driver = { + .probe = msm_pmic_led_probe, + .remove = __devexit_p(msm_pmic_led_remove), + .suspend = msm_pmic_led_suspend, + .resume = msm_pmic_led_resume, + .driver = { + .name = "pmic-leds", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_pmic_led_init(void) +{ + return platform_driver_register(&msm_pmic_led_driver); +} +module_init(msm_pmic_led_init); + +static void __exit msm_pmic_led_exit(void) +{ + platform_driver_unregister(&msm_pmic_led_driver); +} +module_exit(msm_pmic_led_exit); + +MODULE_DESCRIPTION("MSM PMIC LEDs driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pmic-leds"); diff --git a/drivers/leds/leds-pm8xxx.c b/drivers/leds/leds-pm8xxx.c new file mode 100644 index 00000000000..c34bf30e392 --- /dev/null +++ b/drivers/leds/leds-pm8xxx.c @@ -0,0 +1,339 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SSBI_REG_ADDR_DRV_KEYPAD 0x48 +#define PM8XXX_DRV_KEYPAD_BL_MASK 0xf0 +#define PM8XXX_DRV_KEYPAD_BL_SHIFT 0x04 + +#define SSBI_REG_ADDR_FLASH_DRV0 0x49 +#define PM8XXX_DRV_FLASH_MASK 0xf0 +#define PM8XXX_DRV_FLASH_SHIFT 0x04 + +#define SSBI_REG_ADDR_FLASH_DRV1 0xFB + +#define SSBI_REG_ADDR_LED_CTRL_BASE 0x131 +#define SSBI_REG_ADDR_LED_CTRL(n) (SSBI_REG_ADDR_LED_CTRL_BASE + (n)) +#define PM8XXX_DRV_LED_CTRL_MASK 0xf8 +#define PM8XXX_DRV_LED_CTRL_SHIFT 0x03 + +#define MAX_FLASH_LED_CURRENT 300 +#define MAX_LC_LED_CURRENT 40 +#define MAX_KP_BL_LED_CURRENT 300 + +#define MAX_KEYPAD_BL_LEVEL (1 << 4) +#define MAX_LED_DRV_LEVEL 20 /* 2 * 20 mA */ + +#define PM8XXX_LED_OFFSET(id) ((id) - PM8XXX_ID_LED_0) + +#define MAX_KB_LED_BRIGHTNESS 15 +#define MAX_LC_LED_BRIGHTNESS 20 +#define MAX_FLASH_LED_BRIGHTNESS 15 + +/** + * struct pm8xxx_led_data - internal led data structure + * @led_classdev - led class device + * @id - led index + * @led_brightness - led brightness levels + * @work - workqueue for led + * @lock - to protect the transactions + * @reg - cached value of led register + */ +struct pm8xxx_led_data { + struct led_classdev cdev; + int id; + u8 reg; + enum led_brightness brightness; + struct device *dev; + struct work_struct work; + struct mutex lock; +}; + +static void led_kp_set(struct pm8xxx_led_data *led, enum led_brightness value) +{ + int rc; + u8 level; + + level = (value << PM8XXX_DRV_KEYPAD_BL_SHIFT) & + PM8XXX_DRV_KEYPAD_BL_MASK; + + led->reg &= ~PM8XXX_DRV_KEYPAD_BL_MASK; + led->reg |= level; + + rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_DRV_KEYPAD, + led->reg); + if (rc < 0) + dev_err(led->cdev.dev, + "can't set keypad backlight level rc=%d\n", rc); +} + +static void led_lc_set(struct pm8xxx_led_data *led, enum led_brightness value) +{ + int rc, offset; + u8 level; + + level = (value << PM8XXX_DRV_LED_CTRL_SHIFT) & + PM8XXX_DRV_LED_CTRL_MASK; + + offset = PM8XXX_LED_OFFSET(led->id); + + led->reg &= ~PM8XXX_DRV_LED_CTRL_MASK; + led->reg |= level; + + rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), + led->reg); + if (rc) + dev_err(led->cdev.dev, "can't set (%d) led value rc=%d\n", + led->id, rc); +} + +static void +led_flash_set(struct pm8xxx_led_data *led, enum led_brightness value) +{ + int rc; + u8 level; + u16 reg_addr; + + level = (value << PM8XXX_DRV_FLASH_SHIFT) & + PM8XXX_DRV_FLASH_MASK; + + led->reg &= ~PM8XXX_DRV_FLASH_MASK; + led->reg |= level; + + if (led->id == PM8XXX_ID_FLASH_LED_0) + reg_addr = SSBI_REG_ADDR_FLASH_DRV0; + else + reg_addr = SSBI_REG_ADDR_FLASH_DRV1; + + rc = pm8xxx_writeb(led->dev->parent, reg_addr, led->reg); + if (rc < 0) + dev_err(led->cdev.dev, "can't set flash led%d level rc=%d\n", + led->id, rc); +} + +static void pm8xxx_led_work(struct work_struct *work) +{ + struct pm8xxx_led_data *led = container_of(work, + struct pm8xxx_led_data, work); + + mutex_lock(&led->lock); + + switch (led->id) { + case PM8XXX_ID_LED_KB_LIGHT: + led_kp_set(led, led->brightness); + break; + case PM8XXX_ID_LED_0: + case PM8XXX_ID_LED_1: + case PM8XXX_ID_LED_2: + led_lc_set(led, led->brightness); + break; + case PM8XXX_ID_FLASH_LED_0: + case PM8XXX_ID_FLASH_LED_1: + led_flash_set(led, led->brightness); + break; + } + + mutex_unlock(&led->lock); +} + +static void pm8xxx_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct pm8xxx_led_data *led; + + led = container_of(led_cdev, struct pm8xxx_led_data, cdev); + + led->brightness = value; + schedule_work(&led->work); +} + +static enum led_brightness pm8xxx_led_get(struct led_classdev *led_cdev) +{ + struct pm8xxx_led_data *led; + + led = container_of(led_cdev, struct pm8xxx_led_data, cdev); + + return led->brightness; +} + +static int __devinit get_max_brightness(enum pm8xxx_leds id) +{ + switch (id) { + case PM8XXX_ID_LED_KB_LIGHT: + return MAX_KB_LED_BRIGHTNESS; + case PM8XXX_ID_LED_0: + case PM8XXX_ID_LED_1: + case PM8XXX_ID_LED_2: + return MAX_LC_LED_BRIGHTNESS; + case PM8XXX_ID_FLASH_LED_0: + case PM8XXX_ID_FLASH_LED_1: + return MAX_FLASH_LED_CURRENT; + default: + return 0; + } +} + +static int __devinit get_init_value(struct pm8xxx_led_data *led, u8 *val) +{ + int rc, offset; + u16 addr; + + switch (led->id) { + case PM8XXX_ID_LED_KB_LIGHT: + addr = SSBI_REG_ADDR_DRV_KEYPAD; + break; + case PM8XXX_ID_LED_0: + case PM8XXX_ID_LED_1: + case PM8XXX_ID_LED_2: + offset = PM8XXX_LED_OFFSET(led->id); + addr = SSBI_REG_ADDR_LED_CTRL(offset); + break; + case PM8XXX_ID_FLASH_LED_0: + addr = SSBI_REG_ADDR_FLASH_DRV0; + break; + case PM8XXX_ID_FLASH_LED_1: + addr = SSBI_REG_ADDR_FLASH_DRV1; + break; + } + + rc = pm8xxx_readb(led->dev->parent, addr, val); + if (rc) + dev_err(led->cdev.dev, "can't get led(%d) level rc=%d\n", + led->id, rc); + + return rc; +} + +static int __devinit pm8xxx_led_probe(struct platform_device *pdev) +{ + const struct led_platform_data *pdata = pdev->dev.platform_data; + struct led_info *curr_led; + struct pm8xxx_led_data *led, *led_dat; + int rc, i; + + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data not supplied\n"); + return -EINVAL; + } + + led = kcalloc(pdata->num_leds, sizeof(*led), GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + + for (i = 0; i < pdata->num_leds; i++) { + curr_led = &pdata->leds[i]; + led_dat = &led[i]; + /* the flags variable is used for led-id */ + led_dat->id = curr_led->flags; + + if (!((led_dat->id >= PM8XXX_ID_LED_KB_LIGHT) && + (led_dat->id <= PM8XXX_ID_FLASH_LED_1))) { + dev_err(&pdev->dev, "invalid LED ID (%d) specified\n", + led_dat->id); + rc = -EINVAL; + goto fail_id_check; + } + + led_dat->cdev.name = curr_led->name; + led_dat->cdev.default_trigger = curr_led->default_trigger; + led_dat->cdev.brightness_set = pm8xxx_led_set; + led_dat->cdev.brightness_get = pm8xxx_led_get; + led_dat->cdev.brightness = LED_OFF; + led_dat->cdev.flags = LED_CORE_SUSPENDRESUME; + + led_dat->cdev.max_brightness = get_max_brightness(led_dat->id); + led_dat->dev = &pdev->dev; + + rc = get_init_value(led_dat, &led_dat->reg); + if (rc < 0) + goto fail_id_check; + + mutex_init(&led_dat->lock); + INIT_WORK(&led_dat->work, pm8xxx_led_work); + + rc = led_classdev_register(&pdev->dev, &led_dat->cdev); + if (rc) { + dev_err(&pdev->dev, "unable to register led %d,rc=%d\n", + led_dat->id, rc); + goto fail_id_check; + } + } + + platform_set_drvdata(pdev, led); + + return 0; + +fail_id_check: + if (i > 0) { + for (i = i - 1; i >= 0; i--) { + mutex_destroy(&led[i].lock); + led_classdev_unregister(&led[i].cdev); + } + } + kfree(led); + return rc; +} + +static int __devexit pm8xxx_led_remove(struct platform_device *pdev) +{ + int i; + const struct led_platform_data *pdata = + pdev->dev.platform_data; + struct pm8xxx_led_data *led = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) { + cancel_work_sync(&led[i].work); + mutex_destroy(&led[i].lock); + led_classdev_unregister(&led[i].cdev); + } + + kfree(led); + + return 0; +} + +static struct platform_driver pm8xxx_led_driver = { + .probe = pm8xxx_led_probe, + .remove = __devexit_p(pm8xxx_led_remove), + .driver = { + .name = PM8XXX_LEDS_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_led_init(void) +{ + return platform_driver_register(&pm8xxx_led_driver); +} +module_init(pm8xxx_led_init); + +static void __exit pm8xxx_led_exit(void) +{ + platform_driver_unregister(&pm8xxx_led_driver); +} +module_exit(pm8xxx_led_exit); + +MODULE_DESCRIPTION("PM8XXX LEDs driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8xxx-led"); diff --git a/drivers/leds/leds-pmic8058.c b/drivers/leds/leds-pmic8058.c new file mode 100644 index 00000000000..d1aed3f7a0c --- /dev/null +++ b/drivers/leds/leds-pmic8058.c @@ -0,0 +1,434 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define SSBI_REG_ADDR_DRV_KEYPAD 0x48 +#define PM8058_DRV_KEYPAD_BL_MASK 0xf0 +#define PM8058_DRV_KEYPAD_BL_SHIFT 0x04 + +#define SSBI_REG_ADDR_FLASH_DRV0 0x49 +#define PM8058_DRV_FLASH_MASK 0xf0 +#define PM8058_DRV_FLASH_SHIFT 0x04 + +#define SSBI_REG_ADDR_FLASH_DRV1 0xFB + +#define SSBI_REG_ADDR_LED_CTRL_BASE 0x131 +#define SSBI_REG_ADDR_LED_CTRL(n) (SSBI_REG_ADDR_LED_CTRL_BASE + (n)) +#define PM8058_DRV_LED_CTRL_MASK 0xf8 +#define PM8058_DRV_LED_CTRL_SHIFT 0x03 + +#define MAX_FLASH_CURRENT 300 +#define MAX_KEYPAD_CURRENT 300 +#define MAX_KEYPAD_BL_LEVEL (1 << 4) +#define MAX_LED_DRV_LEVEL 20 /* 2 * 20 mA */ + +#define PMIC8058_LED_OFFSET(id) ((id) - PMIC8058_ID_LED_0) + +struct pmic8058_led_data { + struct led_classdev cdev; + int id; + enum led_brightness brightness; + u8 flags; + struct pm8058_chip *pm_chip; + struct work_struct work; + struct mutex lock; + spinlock_t value_lock; + u8 reg_kp; + u8 reg_led_ctrl[3]; + u8 reg_flash_led0; + u8 reg_flash_led1; +}; + +#define PM8058_MAX_LEDS 7 +static struct pmic8058_led_data led_data[PM8058_MAX_LEDS]; + +static void kp_bl_set(struct pmic8058_led_data *led, enum led_brightness value) +{ + int rc; + u8 level; + unsigned long flags; + + spin_lock_irqsave(&led->value_lock, flags); + level = (value << PM8058_DRV_KEYPAD_BL_SHIFT) & + PM8058_DRV_KEYPAD_BL_MASK; + + led->reg_kp &= ~PM8058_DRV_KEYPAD_BL_MASK; + led->reg_kp |= level; + spin_unlock_irqrestore(&led->value_lock, flags); + + rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_DRV_KEYPAD, + &led->reg_kp, 1); + if (rc) + pr_err("%s: can't set keypad backlight level\n", __func__); +} + +static enum led_brightness kp_bl_get(struct pmic8058_led_data *led) +{ + if ((led->reg_kp & PM8058_DRV_KEYPAD_BL_MASK) >> + PM8058_DRV_KEYPAD_BL_SHIFT) + return LED_FULL; + else + return LED_OFF; +} + +static void led_lc_set(struct pmic8058_led_data *led, enum led_brightness value) +{ + unsigned long flags; + int rc, offset; + u8 level, tmp; + + spin_lock_irqsave(&led->value_lock, flags); + + level = (led->brightness << PM8058_DRV_LED_CTRL_SHIFT) & + PM8058_DRV_LED_CTRL_MASK; + + offset = PMIC8058_LED_OFFSET(led->id); + tmp = led->reg_led_ctrl[offset]; + + tmp &= ~PM8058_DRV_LED_CTRL_MASK; + tmp |= level; + spin_unlock_irqrestore(&led->value_lock, flags); + + rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_LED_CTRL(offset), + &tmp, 1); + if (rc) { + dev_err(led->cdev.dev, "can't set (%d) led value\n", + led->id); + return; + } + + spin_lock_irqsave(&led->value_lock, flags); + led->reg_led_ctrl[offset] = tmp; + spin_unlock_irqrestore(&led->value_lock, flags); +} + +static enum led_brightness led_lc_get(struct pmic8058_led_data *led) +{ + int offset; + u8 value; + + offset = PMIC8058_LED_OFFSET(led->id); + value = led->reg_led_ctrl[offset]; + + if ((value & PM8058_DRV_LED_CTRL_MASK) >> + PM8058_DRV_LED_CTRL_SHIFT) + return LED_FULL; + else + return LED_OFF; +} + +static void +led_flash_set(struct pmic8058_led_data *led, enum led_brightness value) +{ + int rc; + u8 level; + unsigned long flags; + u8 reg_flash_led; + u16 reg_addr; + + spin_lock_irqsave(&led->value_lock, flags); + level = (value << PM8058_DRV_FLASH_SHIFT) & + PM8058_DRV_FLASH_MASK; + + if (led->id == PMIC8058_ID_FLASH_LED_0) { + led->reg_flash_led0 &= ~PM8058_DRV_FLASH_MASK; + led->reg_flash_led0 |= level; + reg_flash_led = led->reg_flash_led0; + reg_addr = SSBI_REG_ADDR_FLASH_DRV0; + } else { + led->reg_flash_led1 &= ~PM8058_DRV_FLASH_MASK; + led->reg_flash_led1 |= level; + reg_flash_led = led->reg_flash_led1; + reg_addr = SSBI_REG_ADDR_FLASH_DRV1; + } + spin_unlock_irqrestore(&led->value_lock, flags); + + rc = pm8058_write(led->pm_chip, reg_addr, ®_flash_led, 1); + if (rc) + pr_err("%s: can't set flash led%d level %d\n", __func__, + led->id, rc); +} + +int pm8058_set_flash_led_current(enum pmic8058_leds id, unsigned mA) +{ + struct pmic8058_led_data *led; + + if ((id < PMIC8058_ID_FLASH_LED_0) || (id > PMIC8058_ID_FLASH_LED_1)) { + pr_err("%s: invalid LED ID (%d) specified\n", __func__, id); + return -EINVAL; + } + + led = &led_data[id]; + if (!led) { + pr_err("%s: flash led not available\n", __func__); + return -EINVAL; + } + + if (mA > MAX_FLASH_CURRENT) + return -EINVAL; + + led_flash_set(led, mA / 20); + + return 0; +} +EXPORT_SYMBOL(pm8058_set_flash_led_current); + +int pm8058_set_led_current(enum pmic8058_leds id, unsigned mA) +{ + struct pmic8058_led_data *led; + int brightness = 0; + + if ((id < PMIC8058_ID_LED_KB_LIGHT) || (id > PMIC8058_ID_FLASH_LED_1)) { + pr_err("%s: invalid LED ID (%d) specified\n", __func__, id); + return -EINVAL; + } + + led = &led_data[id]; + if (!led) { + pr_err("%s: flash led not available\n", __func__); + return -EINVAL; + } + + switch (id) { + case PMIC8058_ID_LED_0: + case PMIC8058_ID_LED_1: + case PMIC8058_ID_LED_2: + brightness = mA / 2; + if (brightness > led->cdev.max_brightness) + return -EINVAL; + led_lc_set(led, brightness); + break; + + case PMIC8058_ID_LED_KB_LIGHT: + case PMIC8058_ID_FLASH_LED_0: + case PMIC8058_ID_FLASH_LED_1: + brightness = mA / 20; + if (brightness > led->cdev.max_brightness) + return -EINVAL; + if (id == PMIC8058_ID_LED_KB_LIGHT) + kp_bl_set(led, brightness); + else + led_flash_set(led, brightness); + break; + } + + return 0; +} +EXPORT_SYMBOL(pm8058_set_led_current); + +static void pmic8058_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct pmic8058_led_data *led; + unsigned long flags; + + led = container_of(led_cdev, struct pmic8058_led_data, cdev); + + spin_lock_irqsave(&led->value_lock, flags); + led->brightness = value; + schedule_work(&led->work); + spin_unlock_irqrestore(&led->value_lock, flags); +} + +static void pmic8058_led_work(struct work_struct *work) +{ + struct pmic8058_led_data *led = container_of(work, + struct pmic8058_led_data, work); + + mutex_lock(&led->lock); + + switch (led->id) { + case PMIC8058_ID_LED_KB_LIGHT: + kp_bl_set(led, led->brightness); + break; + case PMIC8058_ID_LED_0: + case PMIC8058_ID_LED_1: + case PMIC8058_ID_LED_2: + led_lc_set(led, led->brightness); + break; + case PMIC8058_ID_FLASH_LED_0: + case PMIC8058_ID_FLASH_LED_1: + led_flash_set(led, led->brightness); + break; + } + + mutex_unlock(&led->lock); +} + +static enum led_brightness pmic8058_led_get(struct led_classdev *led_cdev) +{ + struct pmic8058_led_data *led; + + led = container_of(led_cdev, struct pmic8058_led_data, cdev); + + switch (led->id) { + case PMIC8058_ID_LED_KB_LIGHT: + return kp_bl_get(led); + case PMIC8058_ID_LED_0: + case PMIC8058_ID_LED_1: + case PMIC8058_ID_LED_2: + return led_lc_get(led); + } + return LED_OFF; +} + +static int pmic8058_led_probe(struct platform_device *pdev) +{ + struct pmic8058_leds_platform_data *pdata = pdev->dev.platform_data; + struct pmic8058_led_data *led_dat; + struct pmic8058_led *curr_led; + int rc, i = 0; + struct pm8058_chip *pm_chip; + u8 reg_kp; + u8 reg_led_ctrl[3]; + u8 reg_flash_led0; + u8 reg_flash_led1; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + dev_err(&pdev->dev, "no parent data passed in\n"); + return -EFAULT; + } + + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data not supplied\n"); + return -EINVAL; + } + + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_DRV_KEYPAD, ®_kp, + 1); + if (rc) { + dev_err(&pdev->dev, "can't get keypad backlight level\n"); + goto err_reg_read; + } + + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_LED_CTRL_BASE, + reg_led_ctrl, 3); + if (rc) { + dev_err(&pdev->dev, "can't get led levels\n"); + goto err_reg_read; + } + + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_FLASH_DRV0, + ®_flash_led0, 1); + if (rc) { + dev_err(&pdev->dev, "can't read flash led0\n"); + goto err_reg_read; + } + + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_FLASH_DRV1, + ®_flash_led1, 1); + if (rc) { + dev_err(&pdev->dev, "can't get flash led1\n"); + goto err_reg_read; + } + + for (i = 0; i < pdata->num_leds; i++) { + curr_led = &pdata->leds[i]; + led_dat = &led_data[curr_led->id]; + + led_dat->cdev.name = curr_led->name; + led_dat->cdev.default_trigger = curr_led->default_trigger; + led_dat->cdev.brightness_set = pmic8058_led_set; + led_dat->cdev.brightness_get = pmic8058_led_get; + led_dat->cdev.brightness = LED_OFF; + led_dat->cdev.max_brightness = curr_led->max_brightness; + led_dat->cdev.flags = LED_CORE_SUSPENDRESUME; + + led_dat->id = curr_led->id; + led_dat->reg_kp = reg_kp; + memcpy(led_data->reg_led_ctrl, reg_led_ctrl, + sizeof(reg_led_ctrl)); + led_dat->reg_flash_led0 = reg_flash_led0; + led_dat->reg_flash_led1 = reg_flash_led1; + + if (!((led_dat->id >= PMIC8058_ID_LED_KB_LIGHT) && + (led_dat->id <= PMIC8058_ID_FLASH_LED_1))) { + dev_err(&pdev->dev, "invalid LED ID (%d) specified\n", + led_dat->id); + rc = -EINVAL; + goto fail_id_check; + } + + led_dat->pm_chip = pm_chip; + + mutex_init(&led_dat->lock); + spin_lock_init(&led_dat->value_lock); + INIT_WORK(&led_dat->work, pmic8058_led_work); + + rc = led_classdev_register(&pdev->dev, &led_dat->cdev); + if (rc) { + dev_err(&pdev->dev, "unable to register led %d\n", + led_dat->id); + goto fail_id_check; + } + } + + platform_set_drvdata(pdev, led_data); + + return 0; + +err_reg_read: +fail_id_check: + if (i > 0) { + for (i = i - 1; i >= 0; i--) + led_classdev_unregister(&led_data[i].cdev); + } + return rc; +} + +static int __devexit pmic8058_led_remove(struct platform_device *pdev) +{ + int i; + struct pmic8058_leds_platform_data *pdata = pdev->dev.platform_data; + struct pmic8058_led_data *led = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) { + led_classdev_unregister(&led[led->id].cdev); + cancel_work_sync(&led[led->id].work); + } + + return 0; +} + +static struct platform_driver pmic8058_led_driver = { + .probe = pmic8058_led_probe, + .remove = __devexit_p(pmic8058_led_remove), + .driver = { + .name = "pm8058-led", + .owner = THIS_MODULE, + }, +}; + +static int __init pmic8058_led_init(void) +{ + return platform_driver_register(&pmic8058_led_driver); +} +module_init(pmic8058_led_init); + +static void __exit pmic8058_led_exit(void) +{ + platform_driver_unregister(&pmic8058_led_driver); +} +module_exit(pmic8058_led_exit); + +MODULE_DESCRIPTION("PMIC8058 LEDs driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8058-led"); diff --git a/drivers/leds/leds-qci-backlight.c b/drivers/leds/leds-qci-backlight.c new file mode 100644 index 00000000000..67502e89f63 --- /dev/null +++ b/drivers/leds/leds-qci-backlight.c @@ -0,0 +1,71 @@ +/* Quanta I2C Backlight Driver + * + * Copyright (C) 2009 Quanta Computer 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. + * + */ + +/* + * + * The Driver with I/O communications via the I2C Interface for ST15 platform. + * And it is only working on the nuvoTon WPCE775x Embedded Controller. + * + */ + +#include +#include +#include +#include +#include + +#define EC_CMD_SET_BACKLIGHT 0xB1 + +static void qci_backlight_store(struct led_classdev *led_cdev, + enum led_brightness val); + +static struct platform_device *bl_pdev; +static struct led_classdev lcd_backlight = { + .name = "lcd-backlight", + .brightness = 147, + .brightness_set = qci_backlight_store, +}; + +static void qci_backlight_store(struct led_classdev *led_cdev, + enum led_brightness val) +{ + u16 value = val; + wpce_smbus_write_word_data(EC_CMD_SET_BACKLIGHT, value); + msleep(10); + + dev_dbg(&bl_pdev->dev, "[backlight_store] : value = %d\n", value); +} + +static int __init qci_backlight_init(void) +{ + int err = 0; + bl_pdev = platform_device_register_simple("backlight", 0, NULL, 0); + err = led_classdev_register(&bl_pdev->dev, &lcd_backlight); + return err; +} + +static void __exit qci_backlight_exit(void) +{ + led_classdev_unregister(&lcd_backlight); + platform_device_unregister(bl_pdev); +} + +module_init(qci_backlight_init); +module_exit(qci_backlight_exit); + +MODULE_AUTHOR("Quanta Computer Inc."); +MODULE_DESCRIPTION("Quanta Embedded Controller I2C Backlight Driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h index e77c7f8dcdd..593a63c744f 100644 --- a/drivers/leds/leds.h +++ b/drivers/leds/leds.h @@ -16,6 +16,21 @@ #include #include #include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND + +extern struct workqueue_struct *suspend_work_queue; +extern int queue_brightness_change(struct led_classdev *led_cdev, + enum led_brightness value); + +struct deferred_brightness_change { + struct work_struct brightness_change_work; + struct led_classdev *led_cdev; + enum led_brightness value; +}; + +#endif static inline void led_set_brightness(struct led_classdev *led_cdev, enum led_brightness value) @@ -23,8 +38,12 @@ static inline void led_set_brightness(struct led_classdev *led_cdev, if (value > led_cdev->max_brightness) value = led_cdev->max_brightness; led_cdev->brightness = value; - if (!(led_cdev->flags & LED_SUSPENDED)) - led_cdev->brightness_set(led_cdev, value); + if (!(led_cdev->flags & LED_SUSPENDED)) { +#ifdef CONFIG_HAS_EARLYSUSPEND + if (queue_brightness_change(led_cdev, value) != 0) +#endif + led_cdev->brightness_set(led_cdev, value); + } } static inline int led_get_brightness(struct led_classdev *led_cdev) diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index 52798a111e1..4ba9432709f 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -443,4 +443,38 @@ config RADIO_WL1273 # TI's ST based wl128x FM radio source "drivers/media/radio/wl128x/Kconfig" +config RADIO_TAVARUA + tristate "Qualcomm Tavaraua I2C FM support" + depends on I2C && VIDEO_V4L2 && MARIMBA_CORE + default n + ---help--- + Say Y here if you want to use the Qualcomm FM chip (Tavarua). + This FM chip uses I2C interface. + + To compile this driver as a module, choose M here: the + module will be called radio-tavarua. + +config RADIO_IRIS + tristate "Qualcomm IRIS FM support" + depends on VIDEO_V4L2 + default n + ---help--- + Say Y here if you want to use the Qualcomm FM chip (IRIS). + This FM chip uses SMD interface + + To compile this driver as a module, choose M here: the + module will be called radio-iris. + + +config RADIO_IRIS_TRANSPORT + tristate "Qualcomm IRIS Transport" + depends on RADIO_IRIS + default n + ---help--- + Say Y here if you want to use the Qualcomm FM chip (IRIS). + with SMD as transport. + + To compile this driver as a module, choose M here: the + module will be called radio-iris-transport. + endif # RADIO_ADAPTERS diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index f484a6e04eb..3337f4bb77f 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -26,5 +26,8 @@ obj-$(CONFIG_RADIO_TEF6862) += tef6862.o obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o obj-$(CONFIG_RADIO_WL128X) += wl128x/ +obj-$(CONFIG_RADIO_TAVARUA) += radio-tavarua.o +obj-$(CONFIG_RADIO_IRIS) += radio-iris.o +obj-$(CONFIG_RADIO_IRIS_TRANSPORT) += radio-iris-transport.o EXTRA_CFLAGS += -Isound diff --git a/drivers/media/radio/radio-iris-transport.c b/drivers/media/radio/radio-iris-transport.c new file mode 100644 index 00000000000..6628c9d6031 --- /dev/null +++ b/drivers/media/radio/radio-iris-transport.c @@ -0,0 +1,165 @@ +/* + * Qualcomm's FM Shared Memory Transport Driver + * + * FM HCI_SMD ( FM HCI Shared Memory Driver) is Qualcomm's Shared memory driver + * for the HCI protocol. This file is based on drivers/bluetooth/hci_vhci.c + * + * Copyright (c) 2000-2001, 2011 Code Aurora Forum. All rights reserved. + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2006 Marcel Holtmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct radio_data { + struct radio_hci_dev *hdev; + struct tasklet_struct rx_task; + struct smd_channel *fm_channel; +}; +struct radio_data hs; + +static void radio_hci_smd_destruct(struct radio_hci_dev *hdev) +{ + radio_hci_unregister_dev(hs.hdev); +} + + +static void radio_hci_smd_recv_event(unsigned long temp) +{ + int len; + int rc; + struct sk_buff *skb; + unsigned char *buf; + struct radio_data *hsmd = &hs; + len = smd_read_avail(hsmd->fm_channel); + + while (len) { + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) { + FMDERR("Memory not allocated for the socket"); + return; + } + + buf = kmalloc(len, GFP_KERNEL); + if (!buf) { + kfree_skb(skb); + FMDERR("Error in allocating buffer memory"); + return; + } + + rc = smd_read_from_cb(hsmd->fm_channel, (void *)buf, len); + + memcpy(skb_put(skb, len), buf, len); + + skb_orphan(skb); + skb->dev = (struct net_device *)hs.hdev; + + rc = radio_hci_recv_frame(skb); + + kfree(buf); + len = smd_read_avail(hsmd->fm_channel); + } +} + +static int radio_hci_smd_send_frame(struct sk_buff *skb) +{ + int len = 0; + + len = smd_write(hs.fm_channel, skb->data, skb->len); + if (len < skb->len) { + FMDERR("Failed to write Data %d", len); + return -ENODEV; + } + return 0; +} + +static void radio_hci_smd_notify_cmd(void *data, unsigned int event) +{ + struct radio_hci_dev *hdev = hs.hdev; + + if (!hdev) { + FMDERR("Frame for unknown HCI device (hdev=NULL)"); + return; + } + + switch (event) { + case SMD_EVENT_DATA: + tasklet_schedule(&hs.rx_task); + break; + case SMD_EVENT_OPEN: + case SMD_EVENT_CLOSE: + break; + default: + break; + } +} + +static int radio_hci_smd_register_dev(struct radio_data *hsmd) +{ + struct radio_hci_dev *hdev; + int rc; + + hdev = kmalloc(sizeof(struct radio_hci_dev), GFP_KERNEL); + hsmd->hdev = hdev; + tasklet_init(&hsmd->rx_task, radio_hci_smd_recv_event, + (unsigned long) hsmd); + hdev->send = radio_hci_smd_send_frame; + hdev->destruct = radio_hci_smd_destruct; + + /* Open the SMD Channel and device and register the callback function */ + rc = smd_named_open_on_edge("APPS_FM", SMD_APPS_WCNSS, + &hsmd->fm_channel, hdev, radio_hci_smd_notify_cmd); + + if (rc < 0) { + FMDERR("Cannot open the command channel"); + return -ENODEV; + } + + smd_disable_read_intr(hsmd->fm_channel); + + if (radio_hci_register_dev(hdev) < 0) { + FMDERR("Can't register HCI device"); + return -ENODEV; + } + + return 0; +} + +static void radio_hci_smd_deregister(void) +{ + smd_close(hs.fm_channel); + hs.fm_channel = 0; +} + +static int radio_hci_smd_init(void) +{ + return radio_hci_smd_register_dev(&hs); +} +module_init(radio_hci_smd_init); + +static void __exit radio_hci_smd_exit(void) +{ + radio_hci_smd_deregister(); +} +module_exit(radio_hci_smd_exit); + +MODULE_DESCRIPTION("Bluetooth SMD driver"); +MODULE_AUTHOR("Ankur Nandwani "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/radio/radio-iris.c b/drivers/media/radio/radio-iris.c new file mode 100644 index 00000000000..fe53ca8df74 --- /dev/null +++ b/drivers/media/radio/radio-iris.c @@ -0,0 +1,2220 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define DRIVER_AUTHOR "Archana Ramchandran " +#define DRIVER_NAME "radio-iris" +#define DRIVER_CARD "Qualcomm FM Radio Transceiver" +#define DRIVER_DESC "Driver for Qualcomm FM Radio Transceiver " + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned int rds_buf = 100; +module_param(rds_buf, uint, 0); +MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*"); + +static void radio_hci_cmd_task(unsigned long arg); +static void radio_hci_rx_task(unsigned long arg); +static struct video_device *video_get_dev(void); +static DEFINE_RWLOCK(hci_task_lock); + +struct iris_device { + struct device *dev; + struct kfifo data_buf[IRIS_BUF_MAX]; + + int pending_xfrs[IRIS_XFR_MAX]; + int xfr_bytes_left; + int xfr_in_progress; + struct completion sync_xfr_start; + int tune_req; + + struct video_device *videodev; + + struct mutex lock; + spinlock_t buf_lock[IRIS_BUF_MAX]; + wait_queue_head_t event_queue; + wait_queue_head_t read_queue; + + struct radio_hci_dev *fm_hdev; + + struct v4l2_capability *g_cap; + struct v4l2_control *g_ctl; + + struct hci_fm_mute_mode_req mute_mode; + struct hci_fm_stereo_mode_req stereo_mode; + struct hci_fm_station_rsp fm_st_rsp; + struct hci_fm_search_station_req srch_st; + struct hci_fm_search_rds_station_req srch_rds; + struct hci_fm_search_station_list_req srch_st_list; + struct hci_fm_recv_conf_req recv_conf; + struct hci_fm_rds_grp_req rds_grp; + unsigned char g_search_mode; + unsigned char g_scan_time; + unsigned int g_antenna; + unsigned int g_rds_grp_proc_ps; + enum iris_region_t region; + struct hci_fm_dbg_param_rsp st_dbg_param; + struct hci_ev_srch_list_compl srch_st_result; +}; + +static struct video_device *priv_videodev; + +static struct v4l2_queryctrl iris_v4l2_queryctrl[] = { + { + .id = V4L2_CID_AUDIO_VOLUME, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Volume", + .minimum = 0, + .maximum = 15, + .step = 1, + .default_value = 15, + }, + { + .id = V4L2_CID_AUDIO_BALANCE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BASS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_TREBLE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_AUDIO_LOUDNESS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCHMODE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search mode", + .minimum = 0, + .maximum = 7, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SCANDWELL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search dwell time", + .minimum = 0, + .maximum = 7, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCHON, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Search on/off", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + + }, + { + .id = V4L2_CID_PRIVATE_IRIS_STATE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "radio 0ff/rx/tx/reset", + .minimum = 0, + .maximum = 3, + .step = 1, + .default_value = 1, + + }, + { + .id = V4L2_CID_PRIVATE_IRIS_REGION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "radio standard", + .minimum = 0, + .maximum = 2, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SIGNAL_TH, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Signal Threshold", + .minimum = 0x80, + .maximum = 0x7F, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCH_PTY, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search PTY", + .minimum = 0, + .maximum = 31, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCH_PI, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search PI", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SRCH_CNT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Preset num", + .minimum = 0, + .maximum = 12, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_EMPHASIS, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Emphasis", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDS_STD, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS standard", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_SPACING, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Channel spacing", + .minimum = 0, + .maximum = 2, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDSON, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS on/off", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDSGROUP_MASK, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS group mask", + .minimum = 0, + .maximum = 0xFFFFFFFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDSGROUP_PROC, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS processing", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_RDSD_BUF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS data groups to buffer", + .minimum = 1, + .maximum = 21, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_PSALL, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "pass all ps strings", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_LP_MODE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Low power mode", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_ANTENNA, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "headset/internal", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + + { + .id = V4L2_CID_PRIVATE_IRIS_TX_SETPSREPEATCOUNT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Set PS REPEATCOUNT", + .minimum = 0, + .maximum = 15, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_PS_NAME, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Stop PS NAME", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_RT, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Stop RT", + .minimum = 0, + .maximum = 1, + }, + +}; + +static void iris_q_event(struct iris_device *radio, + enum iris_evt_t event) +{ + struct kfifo *data_b = &radio->data_buf[IRIS_BUF_EVENTS]; + unsigned char evt = event; + if (kfifo_in_locked(data_b, &evt, 1, &radio->buf_lock[IRIS_BUF_EVENTS])) + wake_up_interruptible(&radio->event_queue); +} + +static int hci_send_frame(struct sk_buff *skb) +{ + struct radio_hci_dev *hdev = (struct radio_hci_dev *) skb->dev; + + if (!hdev) { + kfree_skb(skb); + return -ENODEV; + } + + __net_timestamp(skb); + + skb_orphan(skb); + return hdev->send(skb); +} + +static void radio_hci_cmd_task(unsigned long arg) +{ + struct radio_hci_dev *hdev = (struct radio_hci_dev *) arg; + struct sk_buff *skb; + if (!(atomic_read(&hdev->cmd_cnt)) + && time_after(jiffies, hdev->cmd_last_tx + HZ)) { + FMDERR("%s command tx timeout", hdev->name); + atomic_set(&hdev->cmd_cnt, 1); + } + + skb = skb_dequeue(&hdev->cmd_q); + if (atomic_read(&hdev->cmd_cnt) && skb) { + kfree_skb(hdev->sent_cmd); + hdev->sent_cmd = skb_clone(skb, GFP_ATOMIC); + if (hdev->sent_cmd) { + atomic_dec(&hdev->cmd_cnt); + hci_send_frame(skb); + hdev->cmd_last_tx = jiffies; + } else { + skb_queue_head(&hdev->cmd_q, skb); + tasklet_schedule(&hdev->cmd_task); + } + } + +} + +static void radio_hci_rx_task(unsigned long arg) +{ + struct radio_hci_dev *hdev = (struct radio_hci_dev *) arg; + struct sk_buff *skb; + + read_lock(&hci_task_lock); + + skb = skb_dequeue(&hdev->rx_q); + radio_hci_event_packet(hdev, skb); + + read_unlock(&hci_task_lock); +} + +int radio_hci_register_dev(struct radio_hci_dev *hdev) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + if (!radio) { + FMDERR(":radio is null"); + return -EINVAL; + } + + if (!hdev) { + FMDERR("hdev is null"); + return -EINVAL; + } + + hdev->flags = 0; + + tasklet_init(&hdev->cmd_task, radio_hci_cmd_task, (unsigned long) + hdev); + tasklet_init(&hdev->rx_task, radio_hci_rx_task, (unsigned long) + hdev); + + init_waitqueue_head(&hdev->req_wait_q); + + skb_queue_head_init(&hdev->rx_q); + skb_queue_head_init(&hdev->cmd_q); + skb_queue_head_init(&hdev->raw_q); + + if (!radio) + FMDERR(":radio is null"); + + radio->fm_hdev = hdev; + + return 0; +} +EXPORT_SYMBOL(radio_hci_register_dev); + +int radio_hci_unregister_dev(struct radio_hci_dev *hdev) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + if (!radio) { + FMDERR(":radio is null"); + return -EINVAL; + } + + tasklet_kill(&hdev->rx_task); + tasklet_kill(&hdev->cmd_task); + skb_queue_purge(&hdev->rx_q); + skb_queue_purge(&hdev->cmd_q); + skb_queue_purge(&hdev->raw_q); + kfree(radio->fm_hdev); + kfree(radio->videodev); + + return 0; +} +EXPORT_SYMBOL(radio_hci_unregister_dev); + +int radio_hci_recv_frame(struct sk_buff *skb) +{ + struct radio_hci_dev *hdev = (struct radio_hci_dev *) skb->dev; + if (!hdev) { + FMDERR("%s hdev is null while receiving frame", hdev->name); + kfree_skb(skb); + return -ENXIO; + } + + __net_timestamp(skb); + + radio_hci_event_packet(hdev, skb); + + return 0; +} +EXPORT_SYMBOL(radio_hci_recv_frame); + +int radio_hci_send_cmd(struct radio_hci_dev *hdev, __u16 opcode, __u32 plen, + void *param) +{ + int len = RADIO_HCI_COMMAND_HDR_SIZE + plen; + struct radio_hci_command_hdr *hdr; + struct sk_buff *skb; + int ret = 0; + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + FMDERR("%s no memory for command", hdev->name); + return -ENOMEM; + } + + hdr = (struct radio_hci_command_hdr *) skb_put(skb, + RADIO_HCI_COMMAND_HDR_SIZE); + hdr->opcode = cpu_to_le16(opcode); + hdr->plen = plen; + + if (plen) + memcpy(skb_put(skb, plen), param, plen); + + skb->dev = (void *) hdev; + + ret = hci_send_frame(skb); + + return ret; +} +EXPORT_SYMBOL(radio_hci_send_cmd); + +static int hci_fm_enable_recv_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_ENABLE_RECV_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_disable_recv_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_DISABLE_RECV_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_get_fm_recv_conf_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_RECV_CONF_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_set_fm_recv_conf_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + struct hci_fm_recv_conf_req *recv_conf_req = + (struct hci_fm_recv_conf_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_RECV_CONF_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*recv_conf_req)), + recv_conf_req); +} + +static int hci_fm_get_station_param_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_GET_STATION_PARAM_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_set_fm_mute_mode_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_mute_mode_req *mute_mode_req = + (struct hci_fm_mute_mode_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_MUTE_MODE_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*mute_mode_req)), + mute_mode_req); +} + +static int hci_set_fm_stereo_mode_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_stereo_mode_req *stereo_mode_req = + (struct hci_fm_stereo_mode_req *) param; + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_STEREO_MODE_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*stereo_mode_req)), + stereo_mode_req); +} + +static int hci_fm_set_antenna_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u8 antenna = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SET_ANTENNA); + return radio_hci_send_cmd(hdev, opcode, sizeof(antenna), &antenna); +} + +static int hci_fm_set_sig_threshold_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u8 sig_threshold = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_SET_SIGNAL_THRESHOLD); + return radio_hci_send_cmd(hdev, opcode, sizeof(sig_threshold), + &sig_threshold); +} + +static int hci_fm_get_sig_threshold_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_GET_SIGNAL_THRESHOLD); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_get_program_service_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_GET_PROGRAM_SERVICE_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_get_radio_text_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_GET_RADIO_TEXT_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_get_af_list_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_GET_AF_LIST_REQ); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_search_stations_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_search_station_req *srch_stations = + (struct hci_fm_search_station_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SEARCH_STATIONS); + return radio_hci_send_cmd(hdev, opcode, sizeof((*srch_stations)), + srch_stations); +} + +static int hci_fm_srch_rds_stations_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_search_rds_station_req *srch_stations = + (struct hci_fm_search_rds_station_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SEARCH_RDS_STATIONS); + return radio_hci_send_cmd(hdev, opcode, sizeof((*srch_stations)), + srch_stations); +} + +static int hci_fm_srch_station_list_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_search_station_list_req *srch_list = + (struct hci_fm_search_station_list_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SEARCH_STATIONS_LIST); + return radio_hci_send_cmd(hdev, opcode, sizeof((*srch_list)), + srch_list); +} + +static int hci_fm_cancel_search_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_CANCEL_SEARCH); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_rds_grp_process_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u32 fm_grps_process = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_RDS_GRP_PROCESS); + return radio_hci_send_cmd(hdev, opcode, sizeof(fm_grps_process), + &fm_grps_process); +} + +static int hci_fm_tune_station_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u32 tune_freq = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_TUNE_STATION_REQ); + return radio_hci_send_cmd(hdev, opcode, sizeof(tune_freq), &tune_freq); +} + +static int hci_def_data_read_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_def_data_rd_req *def_data_rd = + (struct hci_fm_def_data_rd_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_DEFAULT_DATA_READ); + return radio_hci_send_cmd(hdev, opcode, sizeof((*def_data_rd)), + def_data_rd); +} + +static int hci_def_data_write_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_def_data_wr_req *def_data_wr = + (struct hci_fm_def_data_wr_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_DEFAULT_DATA_WRITE); + return radio_hci_send_cmd(hdev, opcode, sizeof((*def_data_wr)), + def_data_wr); +} + +static int hci_fm_reset_req(struct radio_hci_dev *hdev, unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_RESET); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_get_feature_lists_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_GET_FEATURE_LIST); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int hci_fm_do_calibration_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u8 mode = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_DO_CALIBRATION); + return radio_hci_send_cmd(hdev, opcode, sizeof(mode), &mode); +} + +static int hci_read_grp_counters_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + __u8 reset_counters = param; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_READ_GRP_COUNTERS); + return radio_hci_send_cmd(hdev, opcode, sizeof(reset_counters), + &reset_counters); +} + +static int hci_peek_data_req(struct radio_hci_dev *hdev, unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_peek_req *peek_data = (struct hci_fm_peek_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_PEEK_DATA); + return radio_hci_send_cmd(hdev, opcode, sizeof((*peek_data)), + peek_data); +} + +static int hci_poke_data_req(struct radio_hci_dev *hdev, unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_poke_req *poke_data = (struct hci_fm_poke_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_POKE_DATA); + return radio_hci_send_cmd(hdev, opcode, sizeof((*poke_data)), + poke_data); +} + +static int hci_ssbi_peek_reg_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_ssbi_req *ssbi_peek = (struct hci_fm_ssbi_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SSBI_PEEK_REG); + return radio_hci_send_cmd(hdev, opcode, sizeof((*ssbi_peek)), + ssbi_peek); +} + +static int hci_ssbi_poke_reg_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + struct hci_fm_ssbi_req *ssbi_poke = (struct hci_fm_ssbi_req *) param; + + opcode = hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, + HCI_OCF_FM_SSBI_POKE_REG); + return radio_hci_send_cmd(hdev, opcode, sizeof((*ssbi_poke)), + ssbi_poke); +} + +static int hci_fm_get_station_dbg_param_req(struct radio_hci_dev *hdev, + unsigned long param) +{ + __u16 opcode = 0; + + opcode = hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, + HCI_OCF_FM_STATION_DBG_PARAM); + return radio_hci_send_cmd(hdev, opcode, 0, NULL); +} + +static int radio_hci_err(__u16 code) +{ + switch (code) { + case 0: + return 0; + case 0x01: + return -EBADRQC; + case 0x02: + return -ENOTCONN; + case 0x03: + return -EIO; + case 0x07: + return -ENOMEM; + case 0x0c: + return -EBUSY; + case 0x11: + return -EOPNOTSUPP; + case 0x12: + return -EINVAL; + default: + return -ENOSYS; + } +} + +static int __radio_hci_request(struct radio_hci_dev *hdev, + int (*req)(struct radio_hci_dev *hdev, + unsigned long param), + unsigned long param, __u32 timeout) +{ + int err = 0; + + DECLARE_WAITQUEUE(wait, current); + + hdev->req_status = HCI_REQ_PEND; + + add_wait_queue(&hdev->req_wait_q, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + err = req(hdev, param); + + schedule_timeout(timeout); + + remove_wait_queue(&hdev->req_wait_q, &wait); + + if (signal_pending(current)) + return -EINTR; + + switch (hdev->req_status) { + case HCI_REQ_DONE: + case HCI_REQ_STATUS: + err = radio_hci_err(hdev->req_result); + break; + + case HCI_REQ_CANCELED: + err = -hdev->req_result; + break; + + default: + err = -ETIMEDOUT; + break; + } + + hdev->req_status = hdev->req_result = 0; + + return err; +} + +static inline int radio_hci_request(struct radio_hci_dev *hdev, + int (*req)(struct + radio_hci_dev * hdev, unsigned long param), + unsigned long param, __u32 timeout) +{ + int ret = 0; + + ret = __radio_hci_request(hdev, req, param, timeout); + + return ret; +} + +static int hci_set_fm_recv_conf(struct hci_fm_recv_conf_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_recv_conf_req *set_recv_conf = arg; + + ret = radio_hci_request(hdev, hci_set_fm_recv_conf_req, (unsigned + long)set_recv_conf, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_tune_station(__u32 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u32 tune_freq = *arg; + + ret = radio_hci_request(hdev, hci_fm_tune_station_req, tune_freq, + RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_set_fm_mute_mode(struct hci_fm_mute_mode_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_mute_mode_req *set_mute_conf = arg; + + ret = radio_hci_request(hdev, hci_set_fm_mute_mode_req, (unsigned + long)set_mute_conf, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_set_fm_stereo_mode(struct hci_fm_stereo_mode_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_stereo_mode_req *set_stereo_conf = arg; + + ret = radio_hci_request(hdev, hci_set_fm_stereo_mode_req, (unsigned + long)set_stereo_conf, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_set_antenna(__u8 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 antenna = *arg; + + ret = radio_hci_request(hdev, hci_fm_set_antenna_req, antenna, + RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_set_signal_threshold(__u8 *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 sig_threshold = *arg; + + ret = radio_hci_request(hdev, hci_fm_set_sig_threshold_req, + sig_threshold, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_search_stations(struct hci_fm_search_station_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_search_station_req *srch_stations = arg; + + ret = radio_hci_request(hdev, hci_fm_search_stations_req, (unsigned + long)srch_stations, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_search_rds_stations(struct hci_fm_search_rds_station_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_search_rds_station_req *srch_stations = arg; + + ret = radio_hci_request(hdev, hci_fm_srch_rds_stations_req, (unsigned + long)srch_stations, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_search_station_list + (struct hci_fm_search_station_list_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_search_station_list_req *srch_list = arg; + + ret = radio_hci_request(hdev, hci_fm_srch_station_list_req, (unsigned + long)srch_list, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_fm_rds_grp(struct hci_fm_rds_grp_req *arg, + struct radio_hci_dev *hdev) +{ + return 0; +} + +static int hci_fm_rds_grps_process(__u32 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u32 fm_grps_process = *arg; + + ret = radio_hci_request(hdev, hci_fm_rds_grp_process_req, + fm_grps_process, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_def_data_read(struct hci_fm_def_data_rd_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_def_data_rd_req *def_data_rd = arg; + + ret = radio_hci_request(hdev, hci_def_data_read_req, (unsigned + long)def_data_rd, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_def_data_write(struct hci_fm_def_data_wr_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_def_data_wr_req *def_data_wr = arg; + + ret = radio_hci_request(hdev, hci_def_data_write_req, (unsigned + long)def_data_wr, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_fm_do_calibration(__u8 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 mode = *arg; + + ret = radio_hci_request(hdev, hci_fm_do_calibration_req, mode, + RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_read_grp_counters(__u8 *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + __u8 reset_counters = *arg; + + ret = radio_hci_request(hdev, hci_read_grp_counters_req, + reset_counters, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_peek_data(struct hci_fm_peek_req *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_peek_req *peek_data = arg; + + ret = radio_hci_request(hdev, hci_peek_data_req, (unsigned + long)peek_data, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_poke_data(struct hci_fm_poke_req *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_poke_req *poke_data = arg; + + ret = radio_hci_request(hdev, hci_poke_data_req, (unsigned + long)poke_data, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_ssbi_peek_reg(struct hci_fm_ssbi_req *arg, + struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_ssbi_req *ssbi_peek_reg = arg; + + ret = radio_hci_request(hdev, hci_ssbi_peek_reg_req, (unsigned + long)ssbi_peek_reg, RADIO_HCI_TIMEOUT); + + return ret; +} + +int hci_ssbi_poke_reg(struct hci_fm_ssbi_req *arg, struct radio_hci_dev *hdev) +{ + int ret = 0; + struct hci_fm_ssbi_req *ssbi_poke_reg = arg; + + ret = radio_hci_request(hdev, hci_ssbi_poke_reg_req, (unsigned + long)ssbi_poke_reg, RADIO_HCI_TIMEOUT); + + return ret; +} + +static int hci_cmd(unsigned int cmd, struct radio_hci_dev *hdev) +{ + int ret = 0; + unsigned long arg = 0; + + switch (cmd) { + case HCI_FM_ENABLE_RECV_CMD: + ret = radio_hci_request(hdev, hci_fm_enable_recv_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_DISABLE_RECV_CMD: + ret = radio_hci_request(hdev, hci_fm_disable_recv_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_GET_RECV_CONF_CMD: + ret = radio_hci_request(hdev, hci_get_fm_recv_conf_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_GET_STATION_PARAM_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_station_param_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_GET_SIGNAL_TH_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_sig_threshold_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_GET_PROGRAM_SERVICE_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_program_service_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_GET_RADIO_TEXT_CMD: + ret = radio_hci_request(hdev, hci_fm_get_radio_text_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_GET_AF_LIST_CMD: + ret = radio_hci_request(hdev, hci_fm_get_af_list_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_CANCEL_SEARCH_CMD: + ret = radio_hci_request(hdev, hci_fm_cancel_search_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_RESET_CMD: + ret = radio_hci_request(hdev, hci_fm_reset_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_GET_FEATURES_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_feature_lists_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + case HCI_FM_STATION_DBG_PARAM_CMD: + ret = radio_hci_request(hdev, + hci_fm_get_station_dbg_param_req, arg, + msecs_to_jiffies(RADIO_HCI_TIMEOUT)); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static void radio_hci_req_complete(struct radio_hci_dev *hdev, int result) +{ + hdev->req_result = result; + hdev->req_status = HCI_REQ_DONE; + wake_up_interruptible(&hdev->req_wait_q); +} + +static void radio_hci_status_complete(struct radio_hci_dev *hdev, int result) +{ + hdev->req_result = result; + hdev->req_status = HCI_REQ_STATUS; + wake_up_interruptible(&hdev->req_wait_q); +} + +static void hci_cc_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + __u8 status = *((__u8 *) skb->data); + + if (status) + return; + + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_fm_disable_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + __u8 status = *((__u8 *) skb->data); + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (status) + return; + + iris_q_event(radio, IRIS_EVT_RADIO_READY); + + radio_hci_req_complete(hdev, status); +} + +static void hci_cc_conf_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_fm_conf_rsp *rsp = (void *)skb->data; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (rsp->status) + return; + + radio->recv_conf = rsp->recv_conf_rsp; + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_fm_enable_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_fm_conf_rsp *rsp = (void *)skb->data; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + if (rsp->status) + return; + + iris_q_event(radio, IRIS_EVT_RADIO_READY); + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_sig_threshold_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_fm_sig_threshold_rsp *rsp = (void *)skb->data; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct v4l2_control *v4l_ctl = radio->g_ctl; + + if (rsp->status) + return; + + v4l_ctl->value = rsp->sig_threshold; + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_station_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct hci_fm_station_rsp *rsp = (void *)skb->data; + radio->fm_st_rsp = *(rsp); + + /* Tune is always succesful */ + radio_hci_req_complete(hdev, 0); +} + +static void hci_cc_prg_srv_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_fm_prgm_srv_rsp *rsp = (void *)skb->data; + + if (rsp->status) + return; + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_rd_txt_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_fm_radio_txt_rsp *rsp = (void *)skb->data; + + if (rsp->status) + return; + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_af_list_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_fm_af_list_rsp *rsp = (void *)skb->data; + + if (rsp->status) + return; + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_data_rd_rsp(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_fm_data_rd_rsp *rsp = (void *)skb->data; + + if (rsp->status) + return; + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_feature_list_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_fm_feature_list_rsp *rsp = (void *)skb->data; + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct v4l2_capability *v4l_cap = radio->g_cap; + + if (rsp->status) + return; + v4l_cap->capabilities = (rsp->feature_mask & 0x000002) | + (rsp->feature_mask & 0x000001); + + radio_hci_req_complete(hdev, rsp->status); +} + +static void hci_cc_dbg_param_rsp(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct hci_fm_dbg_param_rsp *rsp = (void *)skb->data; + radio->st_dbg_param = *(rsp); + + if (radio->st_dbg_param.status) + return; + + radio_hci_req_complete(hdev, radio->st_dbg_param.status); +} + +static inline void hci_cmd_complete_event(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_cmd_complete *cmd_compl_ev = (void *) skb->data; + __u16 opcode; + + skb_pull(skb, sizeof(*cmd_compl_ev)); + + opcode = __le16_to_cpu(cmd_compl_ev->cmd_opcode); + + switch (opcode) { + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_ENABLE_RECV_REQ): + hci_cc_fm_enable_rsp(hdev, skb); + break; + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_RECV_CONF_REQ): + hci_cc_conf_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_DISABLE_RECV_REQ): + hci_cc_fm_disable_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_RECV_CONF_REQ): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_MUTE_MODE_REQ): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_STEREO_MODE_REQ): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_ANTENNA): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_SET_SIGNAL_THRESHOLD): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_CANCEL_SEARCH): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_RDS_GRP): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_RDS_GRP_PROCESS): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_EN_WAN_AVD_CTRL): + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_EN_NOTCH_CTRL): + case hci_common_cmd_op_pack(HCI_OCF_FM_DEFAULT_DATA_WRITE): + case hci_common_cmd_op_pack(HCI_OCF_FM_RESET): + case hci_status_param_op_pack(HCI_OCF_FM_READ_GRP_COUNTERS): + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_POKE_DATA): + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_SSBI_PEEK_REG): + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_SSBI_POKE_REG): + hci_cc_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_SIGNAL_THRESHOLD): + hci_cc_sig_threshold_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_STATION_PARAM_REQ): + hci_cc_station_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_PROGRAM_SERVICE_REQ): + hci_cc_prg_srv_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_RADIO_TEXT_REQ): + hci_cc_rd_txt_rsp(hdev, skb); + break; + + case hci_recv_ctrl_cmd_op_pack(HCI_OCF_FM_GET_AF_LIST_REQ): + hci_cc_af_list_rsp(hdev, skb); + break; + + case hci_common_cmd_op_pack(HCI_OCF_FM_DEFAULT_DATA_READ): + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_PEEK_DATA): + hci_cc_data_rd_rsp(hdev, skb); + break; + + case hci_common_cmd_op_pack(HCI_OCF_FM_GET_FEATURE_LIST): + hci_cc_feature_list_rsp(hdev, skb); + break; + + case hci_diagnostic_cmd_op_pack(HCI_OCF_FM_STATION_DBG_PARAM): + hci_cc_dbg_param_rsp(hdev, skb); + break; + + default: + FMDERR("%s opcode 0x%x", hdev->name, opcode); + break; + } + +} + +static inline void hci_cmd_status_event(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_cmd_status *ev = (void *) skb->data; + radio_hci_status_complete(hdev, ev->status); +} + +static inline void hci_ev_tune_status(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + int i; + int len; + + struct iris_device *radio = video_get_drvdata(video_get_dev()); + + len = sizeof(struct hci_fm_station_rsp); + + memcpy(&radio->fm_st_rsp.station_rsp, skb_pull(skb, len), len); + + iris_q_event(radio, IRIS_EVT_TUNE_SUCC); + + for (i = 0; i < IRIS_BUF_MAX; i++) { + if (i >= IRIS_BUF_RT_RDS) + kfifo_reset(&radio->data_buf[i]); + } + + if (radio->fm_st_rsp.station_rsp.rssi) + iris_q_event(radio, IRIS_EVT_ABOVE_TH); + else + iris_q_event(radio, IRIS_EVT_BELOW_TH); + + if (radio->fm_st_rsp.station_rsp.stereo_prg) + iris_q_event(radio, IRIS_EVT_STEREO); + + if (radio->fm_st_rsp.station_rsp.mute_mode) + iris_q_event(radio, IRIS_EVT_MONO); + + if (radio->fm_st_rsp.station_rsp.rds_sync_status) + iris_q_event(radio, IRIS_EVT_RDS_AVAIL); + else + iris_q_event(radio, IRIS_EVT_RDS_NOT_AVAIL); +} + +static inline void hci_ev_search_compl(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + iris_q_event(radio, IRIS_EVT_SEEK_COMPLETE); +} + +static inline void hci_ev_srch_st_list_compl(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + struct hci_ev_srch_list_compl *ev = (void *) skb->data; + radio->srch_st_result = *ev; +} + +static inline void hci_ev_search_next(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + iris_q_event(radio, IRIS_EVT_SCAN_NEXT); +} + +static inline void hci_ev_stereo_status(struct radio_hci_dev *hdev, + struct sk_buff *skb) +{ + struct iris_device *radio = video_get_drvdata(video_get_dev()); + __u8 st_status = *((__u8 *) skb->data); + if (st_status) + iris_q_event(radio, IRIS_EVT_STEREO); + else + iris_q_event(radio, IRIS_EVT_MONO); +} + +void radio_hci_event_packet(struct radio_hci_dev *hdev, struct sk_buff *skb) +{ + struct radio_hci_event_hdr *hdr = (void *) skb->data; + __u8 event = hdr->evt; + + skb_pull(skb, RADIO_HCI_EVENT_HDR_SIZE); + + switch (event) { + case HCI_EV_TUNE_STATUS: + hci_ev_tune_status(hdev, skb); + break; + case HCI_EV_SEARCH_PROGRESS: + case HCI_EV_SEARCH_RDS_PROGRESS: + case HCI_EV_SEARCH_LIST_PROGRESS: + hci_ev_search_next(hdev, skb); + break; + case HCI_EV_STEREO_STATUS: + hci_ev_stereo_status(hdev, skb); + break; + case HCI_EV_RDS_LOCK_STATUS: + case HCI_EV_SERVICE_AVAILABLE: + case HCI_EV_RDS_RX_DATA: + case HCI_EV_PROGRAM_SERVICE: + case HCI_EV_RADIO_TEXT: + case HCI_EV_FM_AF_LIST: + case HCI_EV_TX_RDS_GRP_COMPL: + case HCI_EV_TX_RDS_CONT_GRP_COMPL: + break; + + case HCI_EV_CMD_COMPLETE: + hci_cmd_complete_event(hdev, skb); + break; + + case HCI_EV_CMD_STATUS: + hci_cmd_status_event(hdev, skb); + break; + + case HCI_EV_SEARCH_COMPLETE: + case HCI_EV_SEARCH_RDS_COMPLETE: + hci_ev_search_compl(hdev, skb); + break; + + case HCI_EV_SEARCH_LIST_COMPLETE: + break; + + default: + break; + } +} + +/* + * fops/IOCTL helper functions + */ + +static int iris_search(struct iris_device *radio, int on, int dir) +{ + int retval = 0; + enum search_t srch = radio->g_search_mode & SRCH_MODE; + + if (on) { + switch (srch) { + case SCAN_FOR_STRONG: + case SCAN_FOR_WEAK: + radio->srch_st_list.srch_list_dir = dir; + radio->srch_st_list.srch_list_mode = srch; + radio->srch_st_list.srch_list_max = 0; + retval = hci_fm_search_station_list( + &radio->srch_st_list, radio->fm_hdev); + break; + case RDS_SEEK_PTY: + case RDS_SCAN_PTY: + case RDS_SEEK_PI: + radio->srch_rds.srch_station.srch_mode = srch; + radio->srch_rds.srch_station.srch_dir = dir; + radio->srch_rds.srch_station.scan_time = + radio->g_scan_time; + retval = hci_fm_search_rds_stations(&radio->srch_rds, + radio->fm_hdev); + break; + default: + radio->srch_st.srch_mode = srch; + radio->srch_st.scan_time = radio->g_scan_time; + radio->srch_st.srch_dir = dir; + retval = hci_fm_search_stations( + &radio->srch_st, radio->fm_hdev); + break; + } + + } else { + retval = hci_cmd(HCI_FM_CANCEL_SEARCH_CMD, radio->fm_hdev); + } + + return retval; +} + +static int iris_set_region(struct iris_device *radio, int req_region) +{ + int retval; + radio->region = req_region; + + switch (radio->region) { + case IRIS_REGION_US: + { + radio->recv_conf.band_low_limit = 88100; + radio->recv_conf.band_high_limit = 108000; + radio->recv_conf.emphasis = 0; + radio->recv_conf.hlsi = 0; + radio->recv_conf.ch_spacing = 0; + radio->recv_conf.rds_std = 0; + } + break; + case IRIS_REGION_EU: + { + radio->recv_conf.band_low_limit = 88100; + radio->recv_conf.band_high_limit = 108000; + radio->recv_conf.emphasis = 0; + radio->recv_conf.hlsi = 0; + radio->recv_conf.ch_spacing = 0; + radio->recv_conf.rds_std = 0; + } + break; + case IRIS_REGION_JAPAN: + { + radio->recv_conf.band_low_limit = 76000; + radio->recv_conf.band_high_limit = 108000; + radio->recv_conf.emphasis = 0; + radio->recv_conf.hlsi = 0; + radio->recv_conf.ch_spacing = 0; + } + break; + default: + { + radio->recv_conf.emphasis = 0; + radio->recv_conf.hlsi = 0; + radio->recv_conf.ch_spacing = 0; + radio->recv_conf.rds_std = 0; + } + break; + } + + + retval = hci_set_fm_recv_conf( + &radio->recv_conf, + radio->fm_hdev); + + return retval; +} + +static int iris_set_freq(struct iris_device *radio, unsigned int freq) +{ + + int retval; + retval = hci_fm_tune_station(&freq, radio->fm_hdev); + if (retval < 0) + FMDERR("Error while setting the frequency : %d\n", retval); + return retval; +} + + +static int iris_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + unsigned char i; + int retval = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(iris_v4l2_queryctrl); i++) { + if (qc->id && qc->id == iris_v4l2_queryctrl[i].id) { + memcpy(qc, &(iris_v4l2_queryctrl[i]), sizeof(*qc)); + retval = 0; + break; + } + } + + return retval; +} + +static int iris_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + break; + case V4L2_CID_AUDIO_MUTE: + ctrl->value = radio->mute_mode.hard_mute; + break; + case V4L2_CID_PRIVATE_IRIS_SRCHMODE: + ctrl->value = radio->g_search_mode; + break; + case V4L2_CID_PRIVATE_IRIS_SCANDWELL: + ctrl->value = radio->g_scan_time; + break; + case V4L2_CID_PRIVATE_IRIS_SRCHON: + break; + case V4L2_CID_PRIVATE_IRIS_STATE: + break; + case V4L2_CID_PRIVATE_IRIS_IOVERC: + retval = hci_cmd(HCI_FM_STATION_DBG_PARAM_CMD, radio->fm_hdev); + if (retval < 0) + return retval; + ctrl->value = radio->st_dbg_param.io_verc; + break; + case V4L2_CID_PRIVATE_IRIS_INTDET: + retval = hci_cmd(HCI_FM_STATION_DBG_PARAM_CMD, radio->fm_hdev); + if (retval < 0) + return retval; + ctrl->value = radio->st_dbg_param.in_det_out; + break; + case V4L2_CID_PRIVATE_IRIS_REGION: + ctrl->value = radio->region; + break; + case V4L2_CID_PRIVATE_IRIS_SIGNAL_TH: + retval = hci_cmd(HCI_FM_GET_SIGNAL_TH_CMD, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PTY: + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PI: + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_CNT: + break; + case V4L2_CID_PRIVATE_IRIS_EMPHASIS: + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error get FM recv conf" + " %d\n", retval); + else + ctrl->value = radio->recv_conf.emphasis; + break; + case V4L2_CID_PRIVATE_IRIS_RDS_STD: + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error get FM recv conf" + " %d\n", retval); + else + ctrl->value = radio->recv_conf.rds_std; + break; + case V4L2_CID_PRIVATE_IRIS_SPACING: + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error get FM recv conf" + " %d\n", retval); + else + ctrl->value = radio->recv_conf.ch_spacing; + break; + case V4L2_CID_PRIVATE_IRIS_RDSON: + retval = hci_cmd(HCI_FM_GET_RECV_CONF_CMD, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error get FM recv conf" + " %d\n", retval); + else + ctrl->value = radio->recv_conf.rds_std; + break; + case V4L2_CID_PRIVATE_IRIS_RDSGROUP_MASK: + ctrl->value = radio->rds_grp.rds_grp_enable_mask; + break; + case V4L2_CID_PRIVATE_IRIS_RDSGROUP_PROC: + break; + case V4L2_CID_PRIVATE_IRIS_RDSD_BUF: + ctrl->value = radio->rds_grp.rds_buf_size; + break; + case V4L2_CID_PRIVATE_IRIS_PSALL: + ctrl->value = radio->g_rds_grp_proc_ps; + break; + case V4L2_CID_PRIVATE_IRIS_LP_MODE: + break; + case V4L2_CID_PRIVATE_IRIS_ANTENNA: + ctrl->value = radio->g_antenna; + break; + default: + retval = -EINVAL; + } + if (retval < 0) + FMDERR("get control failed with %d, id: %d\n", + retval, ctrl->id); + return retval; +} + +static int iris_vidioc_s_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrl) +{ + return -ENOTSUPP; +} + +static int iris_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + unsigned int rds_grps_proc = 0; + __u8 temp_val = 0; + radio->recv_conf.emphasis = 0; + radio->recv_conf.ch_spacing = 0; + radio->recv_conf.hlsi = 0; + radio->recv_conf.band_low_limit = 87500; + radio->recv_conf.band_high_limit = 108000; + radio->recv_conf.rds_std = 0; + + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + break; + case V4L2_CID_AUDIO_MUTE: + radio->mute_mode.hard_mute = ctrl->value; + radio->mute_mode.soft_mute = IOC_SFT_MUTE; + retval = hci_set_fm_mute_mode( + &radio->mute_mode, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error while set FM hard mute"" %d\n", + retval); + break; + case V4L2_CID_PRIVATE_IRIS_SRCHMODE: + radio->g_search_mode = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_SCANDWELL: + radio->g_scan_time = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_SRCHON: + iris_search(radio, ctrl->value, SRCH_DIR_UP); + break; + case V4L2_CID_PRIVATE_IRIS_STATE: + if (ctrl->value == FM_RECV) { + retval = hci_cmd(HCI_FM_ENABLE_RECV_CMD, + radio->fm_hdev); + } else { + if (ctrl->value == FM_OFF) { + retval = hci_cmd( + HCI_FM_DISABLE_RECV_CMD, + radio->fm_hdev); + if (retval < 0) + FMDERR("Error on disable FM" + " %d\n", retval); + } + } + break; + case V4L2_CID_PRIVATE_IRIS_REGION: + retval = iris_set_region(radio, ctrl->value); + break; + case V4L2_CID_PRIVATE_IRIS_SIGNAL_TH: + temp_val = ctrl->value; + retval = hci_fm_set_signal_threshold( + &temp_val, + radio->fm_hdev); + if (retval < 0) { + FMDERR("Error while setting signal threshold\n"); + break; + } + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PTY: + radio->srch_rds.srch_pty = ctrl->value; + radio->srch_st_list.srch_pty = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_PI: + radio->srch_rds.srch_pi = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_SRCH_CNT: + break; + case V4L2_CID_PRIVATE_IRIS_SPACING: + radio->recv_conf.ch_spacing = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_EMPHASIS: + radio->recv_conf.emphasis = ctrl->value; + retval = + hci_set_fm_recv_conf(&radio->recv_conf, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_RDS_STD: + radio->recv_conf.rds_std = ctrl->value; + retval = + hci_set_fm_recv_conf(&radio->recv_conf, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_RDSON: + radio->recv_conf.rds_std = ctrl->value; + retval = + hci_set_fm_recv_conf(&radio->recv_conf, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_RDSGROUP_MASK: + radio->rds_grp.rds_grp_enable_mask = ctrl->value; + retval = hci_fm_rds_grp(&radio->rds_grp, radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_RDSGROUP_PROC: + rds_grps_proc = radio->g_rds_grp_proc_ps | ctrl->value; + retval = hci_fm_rds_grps_process( + &rds_grps_proc, + radio->fm_hdev); + break; + case V4L2_CID_PRIVATE_IRIS_RDSD_BUF: + radio->rds_grp.rds_buf_size = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_PSALL: + radio->g_rds_grp_proc_ps = ctrl->value; + break; + case V4L2_CID_PRIVATE_IRIS_LP_MODE: + break; + case V4L2_CID_PRIVATE_IRIS_ANTENNA: + temp_val = ctrl->value; + retval = hci_fm_set_antenna(&temp_val, radio->fm_hdev); + break; + case V4L2_CID_RDS_TX_PTY: + break; + case V4L2_CID_RDS_TX_PI: + break; + case V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_PS_NAME: + break; + case V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_RT: + break; + case V4L2_CID_PRIVATE_IRIS_TX_SETPSREPEATCOUNT: + break; + case V4L2_CID_TUNE_POWER_LEVEL: + break; + default: + retval = -EINVAL; + } + return retval; +} + +static int iris_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval; + if (tuner->index > 0) + return -EINVAL; + + retval = hci_cmd(HCI_FM_GET_STATION_PARAM_CMD, radio->fm_hdev); + if (retval < 0) + return retval; + + tuner->type = V4L2_TUNER_RADIO; + tuner->rangelow = radio->recv_conf.band_low_limit * TUNE_PARAM; + tuner->rangehigh = radio->recv_conf.band_high_limit * TUNE_PARAM; + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + tuner->capability = V4L2_TUNER_CAP_LOW; + tuner->signal = radio->fm_st_rsp.station_rsp.rssi; + tuner->audmode = radio->fm_st_rsp.station_rsp.stereo_prg; + tuner->afc = 0; + + return 0; +} + +static int iris_vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval; + if (tuner->index > 0) + return -EINVAL; + + radio->recv_conf.band_low_limit = tuner->rangelow / TUNE_PARAM; + radio->recv_conf.band_high_limit = tuner->rangehigh / TUNE_PARAM; + if (tuner->audmode == V4L2_TUNER_MODE_MONO) { + radio->stereo_mode.stereo_mode = 0x01; + retval = hci_set_fm_stereo_mode( + &radio->stereo_mode, + radio->fm_hdev); + } else { + radio->stereo_mode.stereo_mode = 0x00; + retval = hci_set_fm_stereo_mode( + &radio->stereo_mode, + radio->fm_hdev); + } + if (retval < 0) + FMDERR(": set tuner failed with %d\n", retval); + return retval; +} + +static int iris_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval; + + freq->type = V4L2_TUNER_RADIO; + retval = hci_cmd(HCI_FM_GET_STATION_PARAM_CMD, radio->fm_hdev); + if (retval < 0) + FMDERR("get frequency failed %d\n", retval); + else + freq->frequency = + radio->fm_st_rsp.station_rsp.station_freq * TUNE_PARAM; + return retval; +} + +static int iris_vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int retval = -1; + freq->frequency = freq->frequency / TUNE_PARAM; + + if (freq->type != V4L2_TUNER_RADIO) + return -EINVAL; + + retval = iris_set_freq(radio, freq->frequency); + if (retval < 0) + FMDERR(" set frequency failed with %d\n", retval); + return retval; +} + +static int iris_vidioc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buffer) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + enum iris_buf_t buf_type = buffer->index; + struct kfifo *data_fifo; + unsigned char *buf = (unsigned char *)buffer->m.userptr; + unsigned int len = buffer->length; + if (!access_ok(VERIFY_WRITE, buf, len)) + return -EFAULT; + if ((buf_type < IRIS_BUF_MAX) && (buf_type >= 0)) { + data_fifo = &radio->data_buf[buf_type]; + if (buf_type == IRIS_BUF_EVENTS) + if (wait_event_interruptible(radio->event_queue, + kfifo_len(data_fifo)) < 0) + return -EINTR; + } else { + FMDERR("invalid buffer type\n"); + return -EINVAL; + } + buffer->bytesused = kfifo_out_locked(data_fifo, buf, len, + &radio->buf_lock[buf_type]); + + return 0; +} + +static int iris_vidioc_g_fmt_type_private(struct file *file, void *priv, + struct v4l2_format *f) +{ + return 0; + +} + +static int iris_vidioc_s_hw_freq_seek(struct file *file, void *priv, + struct v4l2_hw_freq_seek *seek) +{ + struct iris_device *radio = video_get_drvdata(video_devdata(file)); + int dir; + if (seek->seek_upward) + dir = SRCH_DIR_UP; + else + dir = SRCH_DIR_DOWN; + return iris_search(radio, CTRL_ON, dir); +} + +static int iris_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct iris_device *radio; + radio = video_get_drvdata(video_devdata(file)); + strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + radio->g_cap = capability; + return 0; +} + + +static const struct v4l2_ioctl_ops iris_ioctl_ops = { + .vidioc_querycap = iris_vidioc_querycap, + .vidioc_queryctrl = iris_vidioc_queryctrl, + .vidioc_g_ctrl = iris_vidioc_g_ctrl, + .vidioc_s_ctrl = iris_vidioc_s_ctrl, + .vidioc_g_tuner = iris_vidioc_g_tuner, + .vidioc_s_tuner = iris_vidioc_s_tuner, + .vidioc_g_frequency = iris_vidioc_g_frequency, + .vidioc_s_frequency = iris_vidioc_s_frequency, + .vidioc_s_hw_freq_seek = iris_vidioc_s_hw_freq_seek, + .vidioc_dqbuf = iris_vidioc_dqbuf, + .vidioc_g_fmt_type_private = iris_vidioc_g_fmt_type_private, + .vidioc_s_ext_ctrls = iris_vidioc_s_ext_ctrls, +}; + +static const struct v4l2_file_operations iris_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, +}; + +static struct video_device iris_viddev_template = { + .fops = &iris_fops, + .ioctl_ops = &iris_ioctl_ops, + .name = DRIVER_NAME, + .release = video_device_release, +}; + +static struct video_device *video_get_dev(void) +{ + return priv_videodev; +} + +static int __init iris_probe(struct platform_device *pdev) +{ + struct iris_device *radio; + int retval; + int radio_nr = -1; + int i; + + if (!pdev) { + FMDERR(": pdev is null\n"); + return -ENOMEM; + } + + radio = kzalloc(sizeof(struct iris_device), GFP_KERNEL); + if (!radio) { + FMDERR(": Could not allocate radio device\n"); + return -ENOMEM; + } + + radio->dev = &pdev->dev; + platform_set_drvdata(pdev, radio); + + radio->videodev = video_device_alloc(); + if (!radio->videodev) { + FMDERR(": Could not allocate V4L device\n"); + kfree(radio); + return -ENOMEM; + } + + memcpy(radio->videodev, &iris_viddev_template, + sizeof(iris_viddev_template)); + + for (i = 0; i < IRIS_BUF_MAX; i++) { + int kfifo_alloc_rc = 0; + spin_lock_init(&radio->buf_lock[i]); + + if (i == IRIS_BUF_RAW_RDS) + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + rds_buf*3, GFP_KERNEL); + else + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + STD_BUF_SIZE, GFP_KERNEL); + + if (kfifo_alloc_rc != 0) { + FMDERR("failed allocating buffers %d\n", + kfifo_alloc_rc); + for (; i > -1; i--) { + kfifo_free(&radio->data_buf[i]); + kfree(radio); + return -ENOMEM; + } + } + } + + mutex_init(&radio->lock); + init_completion(&radio->sync_xfr_start); + radio->tune_req = 0; + init_waitqueue_head(&radio->event_queue); + init_waitqueue_head(&radio->read_queue); + + video_set_drvdata(radio->videodev, radio); + + if (NULL == video_get_drvdata(radio->videodev)) + FMDERR(": video_get_drvdata failed\n"); + + retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, + radio_nr); + if (retval) { + FMDERR(": Could not register video device\n"); + video_device_release(radio->videodev); + for (; i > -1; i--) + kfifo_free(&radio->data_buf[i]); + kfree(radio); + return retval; + } else { + priv_videodev = kzalloc(sizeof(struct video_device), + GFP_KERNEL); + memcpy(priv_videodev, radio->videodev, + sizeof(struct video_device)); + } + return 0; +} + + +static int __devexit iris_remove(struct platform_device *pdev) +{ + int i; + struct iris_device *radio = platform_get_drvdata(pdev); + + video_unregister_device(radio->videodev); + + for (i = 0; i < IRIS_BUF_MAX; i++) + kfifo_free(&radio->data_buf[i]); + + kfree(radio); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver iris_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "iris_fm", + }, + .remove = __devexit_p(iris_remove), +}; + +static int __init iris_radio_init(void) +{ + return platform_driver_probe(&iris_driver, iris_probe); +} +module_init(iris_radio_init); + +static void __exit iris_radio_exit(void) +{ + platform_driver_unregister(&iris_driver); +} +module_exit(iris_radio_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/media/radio/radio-tavarua.c b/drivers/media/radio/radio-tavarua.c new file mode 100644 index 00000000000..f04dfe5f4dd --- /dev/null +++ b/drivers/media/radio/radio-tavarua.c @@ -0,0 +1,3755 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm Tavarua FM core driver + */ + +/* driver definitions */ +#define DRIVER_AUTHOR "Qualcomm" +#define DRIVER_NAME "radio-tavarua" +#define DRIVER_CARD "Qualcomm FM Radio Transceiver" +#define DRIVER_DESC "I2C radio driver for Qualcomm FM Radio Transceiver " +#define DRIVER_VERSION "1.0.0" + +#include +#include /* Initdata */ +#include /* udelay */ +#include /* copy to/from user */ +#include /* lock free circular buffer */ +#include +#include +#include +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* +regional parameters for radio device +*/ +struct region_params_t { + enum tavarua_region_t region; + unsigned int band_high; + unsigned int band_low; + char emphasis; + char rds_std; + char spacing; +}; + +struct srch_params_t { + unsigned short srch_pi; + unsigned char srch_pty; + unsigned int preset_num; + int get_list; +}; + +/* Main radio device structure, +acts as a shadow copy of the +actual tavaura registers */ +struct tavarua_device { + struct video_device *videodev; + /* driver management */ + int users; + /* top level driver data */ + struct marimba *marimba; + struct device *dev; + /* platform specific functionality */ + struct marimba_fm_platform_data *pdata; + unsigned int chipID; + /*RDS buffers + Radio event buffer*/ + struct kfifo data_buf[TAVARUA_BUF_MAX]; + /* search paramters */ + struct srch_params_t srch_params; + /* keep track of pending xfrs */ + int pending_xfrs[TAVARUA_XFR_MAX]; + int xfr_bytes_left; + int xfr_in_progress; + /* Transmit data */ + enum tavarua_xfr_ctrl_t tx_mode; + /* synchrnous xfr data */ + unsigned char sync_xfr_regs[XFR_REG_NUM]; + struct completion sync_xfr_start; + struct completion sync_req_done; + int tune_req; + /* internal register status */ + unsigned char registers[RADIO_REGISTERS]; + /* regional settings */ + struct region_params_t region_params; + /* power mode */ + int lp_mode; + int handle_irq; + /* global lock */ + struct mutex lock; + /* buffer locks*/ + spinlock_t buf_lock[TAVARUA_BUF_MAX]; + /* work queue */ + struct workqueue_struct *wqueue; + struct delayed_work work; + /* wait queue for blocking event read */ + wait_queue_head_t event_queue; + /* wait queue for raw rds read */ + wait_queue_head_t read_queue; + /* PTY for FM Tx */ + int pty; + /* PI for FM TX */ + int pi; + /*PS repeatcount for PS Tx */ + int ps_repeatcount; +}; + +/************************************************************************** + * Module Parameters + **************************************************************************/ + +/* Radio Nr */ +static int radio_nr = -1; +module_param(radio_nr, int, 0); +MODULE_PARM_DESC(radio_nr, "Radio Nr"); +static int wait_timeout = WAIT_TIMEOUT; +/* Bahama's version*/ +static u8 bahama_version; +/* RDS buffer blocks */ +static unsigned int rds_buf = 100; +module_param(rds_buf, uint, 0); +MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*"); +/* static variables */ +static struct tavarua_device *private_data; +/* forward declerations */ +static int tavarua_disable_interrupts(struct tavarua_device *radio); +static int tavarua_setup_interrupts(struct tavarua_device *radio, + enum radio_state_t state); +static int tavarua_start(struct tavarua_device *radio, + enum radio_state_t state); +static int tavarua_request_irq(struct tavarua_device *radio); +static void start_pending_xfr(struct tavarua_device *radio); +/* work function */ +static void read_int_stat(struct work_struct *work); + +static int is_bahama(void) +{ + int id = 0; + + switch (id = adie_get_detected_connectivity_type()) { + case BAHAMA_ID: + FMDBG("It is Bahama\n"); + return 1; + + case MARIMBA_ID: + FMDBG("It is Marimba\n"); + return 0; + default: + printk(KERN_ERR "%s: unexpected adie connectivity type: %d\n", + __func__, id); + return -ENODEV; + } +} + +static int set_fm_slave_id(struct tavarua_device *radio) +{ + int bahama_present = is_bahama(); + + if (bahama_present == -ENODEV) + return -ENODEV; + + if (bahama_present) + radio->marimba->mod_id = SLAVE_ID_BAHAMA_FM; + else + radio->marimba->mod_id = MARIMBA_SLAVE_ID_FM; + + return 0; +} + +/*============================================================================= +FUNCTION: tavarua_isr +=============================================================================*/ +/** + This function is called when GPIO is toggled. This functions queues the event + to interrupt queue, which is later handled by isr handling funcion. + i.e. INIT_DELAYED_WORK(&radio->work, read_int_stat); + + @param irq: irq that is toggled. + @param dev_id: structure pointer passed by client. + + @return IRQ_HANDLED. +*/ +static irqreturn_t tavarua_isr(int irq, void *dev_id) +{ + struct tavarua_device *radio = dev_id; + /* schedule a tasklet to handle host intr */ + /* The call to queue_delayed_work ensures that a minimum delay (in jiffies) + * passes before the work is actually executed. The return value from the + * function is nonzero if the work_struct was actually added to queue + * (otherwise, it may have already been there and will not be added a second + * time). + */ + queue_delayed_work(radio->wqueue, &radio->work, + msecs_to_jiffies(TAVARUA_DELAY)); + return IRQ_HANDLED; +} + +/************************************************************************** + * Interface to radio internal registers over top level marimba driver + *************************************************************************/ + +/*============================================================================= +FUNCTION: tavarua_read_registers +=============================================================================*/ +/** + This function is called to read a number of bytes from an I2C interface. + The bytes read are stored in internal register status (shadow copy). + + @param radio: structure pointer passed by client. + @param offset: register offset. + @param len: num of bytes. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_read_registers(struct tavarua_device *radio, + unsigned char offset, int len) +{ + int retval = 0, i = 0; + retval = set_fm_slave_id(radio); + + if (retval == -ENODEV) + return retval; + + FMDBG_I2C("I2C Slave: %x, Read Offset(%x): Data [", + radio->marimba->mod_id, + offset); + + retval = marimba_read(radio->marimba, offset, + &radio->registers[offset], len); + + if (retval > 0) { + for (i = 0; i < len; i++) + FMDBG_I2C("%02x ", radio->registers[offset+i]); + FMDBG_I2C(" ]\n"); + + } + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_write_register +=============================================================================*/ +/** + This function is called to write a byte over the I2C interface. + The corresponding shadow copy is stored in internal register status. + + @param radio: structure pointer passed by client. + @param offset: register offset. + @param value: buffer to be written to the registers. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_write_register(struct tavarua_device *radio, + unsigned char offset, unsigned char value) +{ + int retval; + retval = set_fm_slave_id(radio); + + if (retval == -ENODEV) + return retval; + + FMDBG_I2C("I2C Slave: %x, Write Offset(%x): Data[", + radio->marimba->mod_id, + offset); + retval = marimba_write(radio->marimba, offset, &value, 1); + if (retval > 0) { + if (offset < RADIO_REGISTERS) { + radio->registers[offset] = value; + FMDBG_I2C("%02x ", radio->registers[offset]); + } + FMDBG_I2C(" ]\n"); + } + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_write_registers +=============================================================================*/ +/** + This function is called to write a number of bytes over the I2C interface. + The corresponding shadow copy is stored in internal register status. + + @param radio: structure pointer passed by client. + @param offset: register offset. + @param buf: buffer to be written to the registers. + @param len: num of bytes. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_write_registers(struct tavarua_device *radio, + unsigned char offset, unsigned char *buf, int len) +{ + + int i; + int retval; + retval = set_fm_slave_id(radio); + + if (retval == -ENODEV) + return retval; + + FMDBG_I2C("I2C Slave: %x, Write Offset(%x): Data[", + radio->marimba->mod_id, + offset); + retval = marimba_write(radio->marimba, offset, buf, len); + if (retval > 0) { /* if write successful, update internal state too */ + for (i = 0; i < len; i++) { + if ((offset+i) < RADIO_REGISTERS) { + radio->registers[offset+i] = buf[i]; + FMDBG_I2C("%x ", radio->registers[offset+i]); + } + } + FMDBG_I2C(" ]\n"); + } + return retval; +} + +/*============================================================================= +FUNCTION: read_data_blocks +=============================================================================*/ +/** + This function reads Raw RDS blocks from Core regs to driver + internal regs (shadow copy). + + @param radio: structure pointer passed by client. + @param offset: register offset. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int read_data_blocks(struct tavarua_device *radio, unsigned char offset) +{ + /* read all 3 RDS blocks */ + return tavarua_read_registers(radio, offset, RDS_BLOCK*4); +} + +/*============================================================================= +FUNCTION: tavarua_rds_read +=============================================================================*/ +/** + This is a rds processing function reads that reads Raw RDS blocks from Core + regs to driver internal regs (shadow copy). It then fills the V4L2 RDS buffer, + which is read by App using JNI interface. + + @param radio: structure pointer passed by client. + + @return None. +*/ +static void tavarua_rds_read(struct tavarua_device *radio) +{ + struct kfifo *rds_buf = &radio->data_buf[TAVARUA_BUF_RAW_RDS]; + unsigned char blocknum; + unsigned char tmp[3]; + + if (read_data_blocks(radio, RAW_RDS) < 0) + return; + /* copy all four RDS blocks to internal buffer */ + for (blocknum = 0; blocknum < RDS_BLOCKS_NUM; blocknum++) { + /* Fill the V4L2 RDS buffer */ + put_unaligned(cpu_to_le16(radio->registers[RAW_RDS + + blocknum*RDS_BLOCK]), (unsigned short *) tmp); + tmp[2] = blocknum; /* offset name */ + tmp[2] |= blocknum << 3; /* received offset */ + tmp[2] |= 0x40; /* corrected error(s) */ + + /* copy RDS block to internal buffer */ + kfifo_in_locked(rds_buf, tmp, 3, &radio->buf_lock[TAVARUA_BUF_RAW_RDS]); + } + /* wake up read queue */ + if (kfifo_len(rds_buf)) + wake_up_interruptible(&radio->read_queue); + +} + +/*============================================================================= +FUNCTION: request_read_xfr +=============================================================================*/ +/** + This function sets the desired MODE in the XFRCTRL register and also sets the + CTRL field to read. + This is an asynchronous way of reading the XFR registers. Client would request + by setting the desired mode in the XFRCTRL register and then would initiate + the actual data register read by calling copy_from_xfr up on SOC signals + success. + + NOTE: + + The Data Transfer (XFR) registers are used to pass various data and + configuration parameters between the Core and host processor. + + To read from the XFR registers, the host processor must set the desired MODE + in the XFRCTRL register and set the CTRL field to read. The Core will then + populate the XFRDAT0 - XFRDAT15 registers with the defined mode bytes. The + Core will set the TRANSFER interrupt status bit and interrupt the host if the + TRANSFERCTRL interrupt control bit is set. The host can then extract the XFR + mode bytes once it detects that the Core has updated the registers. + + @param radio: structure pointer passed by client. + + @return Always returns 0. +*/ +static int request_read_xfr(struct tavarua_device *radio, + enum tavarua_xfr_ctrl_t mode){ + + tavarua_write_register(radio, XFRCTRL, mode); + msleep(TAVARUA_DELAY); + return 0; +} + +/*============================================================================= +FUNCTION: copy_from_xfr +=============================================================================*/ +/** + This function is used to read XFR mode bytes once it detects that the Core + has updated the registers. It also updates XFR regs to the appropriate + internal buffer n bytes. + + NOTE: + + This function should be used in conjuction with request_read_xfr. Refer + request_read_xfr for XFR mode transaction details. + + @param radio: structure pointer passed by client. + @param buf_type: Index into RDS/Radio event buffer to use. + @param len: num of bytes. + + @return Always returns 0. +*/ +static int copy_from_xfr(struct tavarua_device *radio, + enum tavarua_buf_t buf_type, unsigned int n){ + + struct kfifo *data_fifo = &radio->data_buf[buf_type]; + unsigned char *xfr_regs = &radio->registers[XFRCTRL+1]; + kfifo_in_locked(data_fifo, xfr_regs, n, &radio->buf_lock[buf_type]); + return 0; +} + +/*============================================================================= +FUNCTION: write_to_xfr +=============================================================================*/ +/** + This function sets the desired MODE in the XFRCTRL register and it also sets + the CTRL field and data to write. + This also writes all the XFRDATx registers with the desired input buffer. + + NOTE: + + The Data Transfer (XFR) registers are used to pass various data and + configuration parameters between the Core and host processor. + + To write data to the Core, the host processor updates XFRDAT0 - XFRDAT15 with + the appropriate mode bytes. The host processor must then set the desired MODE + in the XFRCTRL register and set the CTRL field to write. The core will detect + that the XFRCTRL register was written to and will read the XFR mode bytes. + After reading all the mode bytes, the Core will set the TRANSFER interrupt + status bit and interrupt the host if the TRANSFERCTRL interrupt control bit + is set. + + @param radio: structure pointer passed by client. + @param mode: XFR mode to write in XFRCTRL register. + @param buf: buffer to be written to the registers. + @param len: num of bytes. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int write_to_xfr(struct tavarua_device *radio, unsigned char mode, + char *buf, int len) +{ + char buffer[len+1]; + memcpy(buffer+1, buf, len); + /* buffer[0] corresponds to XFRCTRL register + set the CTRL bit to 1 for write mode + */ + buffer[0] = ((1<<7) | mode); + return tavarua_write_registers(radio, XFRCTRL, buffer, sizeof(buffer)); +} + +/*============================================================================= +FUNCTION: xfr_intf_own +=============================================================================*/ +/** + This function is used to check if there is any pending XFR mode operation. + If yes, wait for it to complete, else update the flag to indicate XFR + operation is in progress + + @param radio: structure pointer passed by client. + + @return 0 on success. + -ETIME on timeout. +*/ +static int xfr_intf_own(struct tavarua_device *radio) +{ + + mutex_lock(&radio->lock); + if (radio->xfr_in_progress) { + radio->pending_xfrs[TAVARUA_XFR_SYNC] = 1; + mutex_unlock(&radio->lock); + if (!wait_for_completion_timeout(&radio->sync_xfr_start, + msecs_to_jiffies(wait_timeout))) + return -ETIME; + } else { + FMDBG("gained ownership of xfr\n"); + radio->xfr_in_progress = 1; + mutex_unlock(&radio->lock); + } + return 0; +} + +/*============================================================================= +FUNCTION: sync_read_xfr +=============================================================================*/ +/** + This function is used to do synchronous XFR read operation. + + @param radio: structure pointer passed by client. + @param xfr_type: XFR mode to write in XFRCTRL register. + @param buf: buffer to be read from the core. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int sync_read_xfr(struct tavarua_device *radio, + enum tavarua_xfr_ctrl_t xfr_type, unsigned char *buf) +{ + int retval; + retval = xfr_intf_own(radio); + if (retval < 0) + return retval; + retval = tavarua_write_register(radio, XFRCTRL, xfr_type); + + if (retval >= 0) { + /* Wait for interrupt i.e. complete + (&radio->sync_req_done); call */ + if (!wait_for_completion_timeout(&radio->sync_req_done, + msecs_to_jiffies(wait_timeout)) || (retval < 0)) { + retval = -ETIME; + } else { + memcpy(buf, radio->sync_xfr_regs, XFR_REG_NUM); + } + } + radio->xfr_in_progress = 0; + start_pending_xfr(radio); + FMDBG("%s: %d\n", __func__, retval); + return retval; +} + +/*============================================================================= +FUNCTION: sync_write_xfr +=============================================================================*/ +/** + This function is used to do synchronous XFR write operation. + + @param radio: structure pointer passed by client. + @param xfr_type: XFR mode to write in XFRCTRL register. + @param buf: buffer to be written to the core. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int sync_write_xfr(struct tavarua_device *radio, + enum tavarua_xfr_ctrl_t xfr_type, unsigned char *buf) +{ + int retval; + retval = xfr_intf_own(radio); + if (retval < 0) + return retval; + retval = write_to_xfr(radio, xfr_type, buf, XFR_REG_NUM); + + if (retval >= 0) { + /* Wait for interrupt i.e. complete + (&radio->sync_req_done); call */ + if (!wait_for_completion_timeout(&radio->sync_req_done, + msecs_to_jiffies(wait_timeout)) || (retval < 0)) { + FMDBG("Write xfr timeout"); + } + } + radio->xfr_in_progress = 0; + start_pending_xfr(radio); + FMDBG("%s: %d\n", __func__, retval); + return retval; +} + + +/*============================================================================= +FUNCTION: start_pending_xfr +=============================================================================*/ +/** + This function checks if their are any pending xfr interrupts and if + the interrupts are either RDS PS, RDS RT, RDS AF, SCANNEXT, SEARCH or SYNC + then initiates corresponding read operation. Preference is given to RAW RDS + data (SYNC) over processed data (PS, RT, AF, etc) from core. + + @param radio: structure pointer passed by client. + + @return None. +*/ +static void start_pending_xfr(struct tavarua_device *radio) +{ + int i; + enum tavarua_xfr_t xfr; + for (i = 0; i < TAVARUA_XFR_MAX; i++) { + if (radio->pending_xfrs[i]) { + radio->xfr_in_progress = 1; + xfr = (enum tavarua_xfr_t)i; + switch (xfr) { + /* priority given to synchronous xfrs */ + case TAVARUA_XFR_SYNC: + complete(&radio->sync_xfr_start); + break; + /* asynchrnous xfrs */ + case TAVARUA_XFR_SRCH_LIST: + request_read_xfr(radio, RX_STATIONS_0); + break; + case TAVARUA_XFR_RT_RDS: + request_read_xfr(radio, RDS_RT_0); + break; + case TAVARUA_XFR_PS_RDS: + request_read_xfr(radio, RDS_PS_0); + break; + case TAVARUA_XFR_AF_LIST: + request_read_xfr(radio, RDS_AF_0); + break; + default: + FMDERR("%s: Unsupported XFR %d\n", + __func__, xfr); + } + radio->pending_xfrs[i] = 0; + FMDBG("resurrect xfr %d\n", i); + } + } + return; +} + +/*============================================================================= +FUNCTION: tavarua_q_event +=============================================================================*/ +/** + This function is called to queue an event for user. + + NOTE: + Applications call the VIDIOC_QBUF ioctl to enqueue an empty (capturing) or + filled (output) buffer in the driver's incoming queue. + + Pleaes refer tavarua_probe where we register different ioctl's for FM. + + @param radio: structure pointer passed by client. + @param event: event to be queued. + + @return None. +*/ +static void tavarua_q_event(struct tavarua_device *radio, + enum tavarua_evt_t event) +{ + + struct kfifo *data_b = &radio->data_buf[TAVARUA_BUF_EVENTS]; + unsigned char evt = event; + FMDBG("updating event_q with event %x\n", event); + if (kfifo_in_locked(data_b, &evt, 1, &radio->buf_lock[TAVARUA_BUF_EVENTS])) + wake_up_interruptible(&radio->event_queue); +} + +/*============================================================================= +FUNCTION: tavarua_start_xfr +=============================================================================*/ +/** + This function is called to process interrupts which require multiple XFR + operations (RDS search, RDS PS, RDS RT, etc). if any XFR operation is + already in progress we store information about pending interrupt, which + will be processed in future when current pending operation is done. + + @param radio: structure pointer passed by client. + @param pending_id: XFR operation (which requires multiple XFR operations in + steps) to start. + @param xfr_id: XFR mode to write in XFRCTRL register. + + @return None. +*/ +static void tavarua_start_xfr(struct tavarua_device *radio, + enum tavarua_xfr_t pending_id, enum tavarua_xfr_ctrl_t xfr_id) +{ + if (radio->xfr_in_progress) + radio->pending_xfrs[pending_id] = 1; + else { + radio->xfr_in_progress = 1; + request_read_xfr(radio, xfr_id); + } +} + +/*============================================================================= +FUNCTION: tavarua_handle_interrupts +=============================================================================*/ +/** + This function processes the interrupts. + + NOTE: + tavarua_q_event is used to queue events in App buffer. i.e. App calls the + VIDIOC_QBUF ioctl to enqueue an empty (capturing) buffer, which is filled + by tavarua_q_event call. + + Any async event that requires multiple steps, i.e. search, RT, PS, etc is + handled one at a time. (We preserve other interrupts when processing one). + Sync interrupts are given priority. + + @param radio: structure pointer passed by client. + + @return None. +*/ +static void tavarua_handle_interrupts(struct tavarua_device *radio) +{ + int i; + int retval; + unsigned char xfr_status; + if (!radio->handle_irq) { + FMDBG("IRQ happend, but I wont handle it\n"); + return; + } + mutex_lock(&radio->lock); + tavarua_read_registers(radio, STATUS_REG1, STATUS_REG_NUM); + + FMDBG("INTSTAT1 <%x>\n", radio->registers[STATUS_REG1]); + FMDBG("INTSTAT2 <%x>\n", radio->registers[STATUS_REG2]); + FMDBG("INTSTAT3 <%x>\n", radio->registers[STATUS_REG3]); + + if (radio->registers[STATUS_REG1] & READY) { + complete(&radio->sync_req_done); + tavarua_q_event(radio, TAVARUA_EVT_RADIO_READY); + } + + /* Tune completed */ + if (radio->registers[STATUS_REG1] & TUNE) { + if (radio->tune_req) { + complete(&radio->sync_req_done); + radio->tune_req = 0; + } + tavarua_q_event(radio, TAVARUA_EVT_TUNE_SUCC); + if (radio->srch_params.get_list) { + tavarua_start_xfr(radio, TAVARUA_XFR_SRCH_LIST, + RX_STATIONS_0); + } + radio->srch_params.get_list = 0; + radio->xfr_in_progress = 0; + radio->xfr_bytes_left = 0; + for (i = 0; i < TAVARUA_BUF_MAX; i++) { + if (i >= TAVARUA_BUF_RT_RDS) + kfifo_reset(&radio->data_buf[i]); + } + for (i = 0; i < TAVARUA_XFR_MAX; i++) { + if (i >= TAVARUA_XFR_RT_RDS) + radio->pending_xfrs[i] = 0; + } + retval = tavarua_read_registers(radio, TUNECTRL, 1); + /* send to user station parameters */ + if (retval > -1) { + /* Signal strength */ + if (!(radio->registers[TUNECTRL] & SIGSTATE)) + tavarua_q_event(radio, TAVARUA_EVT_BELOW_TH); + else + tavarua_q_event(radio, TAVARUA_EVT_ABOVE_TH); + /* mono/stereo */ + if ((radio->registers[TUNECTRL] & MOSTSTATE)) + tavarua_q_event(radio, TAVARUA_EVT_STEREO); + else + tavarua_q_event(radio, TAVARUA_EVT_MONO); + /* is RDS available */ + if ((radio->registers[TUNECTRL] & RDSSYNC)) + tavarua_q_event(radio, TAVARUA_EVT_RDS_AVAIL); + else + tavarua_q_event(radio, + TAVARUA_EVT_RDS_NOT_AVAIL); + } + + } else { + if (radio->tune_req) { + FMDERR("Tune INT is pending\n"); + mutex_unlock(&radio->lock); + return; + } + } + /* Search completed (read FREQ) */ + if (radio->registers[STATUS_REG1] & SEARCH) + tavarua_q_event(radio, TAVARUA_EVT_SEEK_COMPLETE); + + /* Scanning for next station */ + if (radio->registers[STATUS_REG1] & SCANNEXT) + tavarua_q_event(radio, TAVARUA_EVT_SCAN_NEXT); + + /* Signal indicator change (read SIGSTATE) */ + if (radio->registers[STATUS_REG1] & SIGNAL) { + retval = tavarua_read_registers(radio, TUNECTRL, 1); + if (retval > -1) { + if (!(radio->registers[TUNECTRL] & SIGSTATE)) + tavarua_q_event(radio, TAVARUA_EVT_BELOW_TH); + else + tavarua_q_event(radio, TAVARUA_EVT_ABOVE_TH); + } + } + + /* RDS synchronization state change (read RDSSYNC) */ + if (radio->registers[STATUS_REG1] & SYNC) { + retval = tavarua_read_registers(radio, TUNECTRL, 1); + if (retval > -1) { + if ((radio->registers[TUNECTRL] & RDSSYNC)) + tavarua_q_event(radio, TAVARUA_EVT_RDS_AVAIL); + else + tavarua_q_event(radio, + TAVARUA_EVT_RDS_NOT_AVAIL); + } + } + + /* Audio Control indicator (read AUDIOIND) */ + if (radio->registers[STATUS_REG1] & AUDIO) { + retval = tavarua_read_registers(radio, AUDIOIND, 1); + if (retval > -1) { + if ((radio->registers[AUDIOIND] & 0x01)) + tavarua_q_event(radio, TAVARUA_EVT_STEREO); + else + tavarua_q_event(radio, TAVARUA_EVT_MONO); + } + } + + /* interrupt register 2 */ + + /* New unread RDS data group available */ + if (radio->registers[STATUS_REG2] & RDSDAT) { + FMDBG("Raw RDS Available\n"); + tavarua_rds_read(radio); + tavarua_q_event(radio, TAVARUA_EVT_NEW_RAW_RDS); + } + + /* New RDS Program Service Table available */ + if (radio->registers[STATUS_REG2] & RDSPS) { + FMDBG("New PS RDS\n"); + tavarua_start_xfr(radio, TAVARUA_XFR_PS_RDS, RDS_PS_0); + } + + /* New RDS Radio Text available */ + if (radio->registers[STATUS_REG2] & RDSRT) { + FMDBG("New RT RDS\n"); + tavarua_start_xfr(radio, TAVARUA_XFR_RT_RDS, RDS_RT_0); + } + + /* New RDS Radio Text available */ + if (radio->registers[STATUS_REG2] & RDSAF) { + FMDBG("New AF RDS\n"); + tavarua_start_xfr(radio, TAVARUA_XFR_AF_LIST, RDS_AF_0); + } + /* Trasmitter an RDS Group */ + if (radio->registers[STATUS_REG2] & TXRDSDAT) { + FMDBG("New TXRDSDAT\n"); + tavarua_q_event(radio, TAVARUA_EVT_TXRDSDAT); + } + + /* Complete RDS buffer is available for transmission */ + if (radio->registers[STATUS_REG2] & TXRDSDONE) { + FMDBG("New TXRDSDAT\n"); + tavarua_q_event(radio, TAVARUA_EVT_TXRDSDONE); + } + /* interrupt register 3 */ + + /* Data transfer (XFR) completed */ + if (radio->registers[STATUS_REG3] & TRANSFER) { + FMDBG("XFR Interrupt\n"); + tavarua_read_registers(radio, XFRCTRL, XFR_REG_NUM+1); + FMDBG("XFRCTRL IS: %x\n", radio->registers[XFRCTRL]); + xfr_status = radio->registers[XFRCTRL]; + switch (xfr_status) { + case RDS_PS_0: + FMDBG("PS Header\n"); + copy_from_xfr(radio, TAVARUA_BUF_PS_RDS, 5); + radio->xfr_bytes_left = (radio->registers[XFRCTRL+1] & + 0x0F) * 8; + FMDBG("PS RDS Length: %d\n", radio->xfr_bytes_left); + if ((radio->xfr_bytes_left > 0) && + (radio->xfr_bytes_left < 97)) + request_read_xfr(radio, RDS_PS_1); + else + radio->xfr_in_progress = 0; + break; + case RDS_PS_1: + case RDS_PS_2: + case RDS_PS_3: + case RDS_PS_4: + case RDS_PS_5: + case RDS_PS_6: + FMDBG("PS Data\n"); + copy_from_xfr(radio, TAVARUA_BUF_PS_RDS, XFR_REG_NUM); + radio->xfr_bytes_left -= XFR_REG_NUM; + if (radio->xfr_bytes_left > 0) { + if ((xfr_status + 1) > RDS_PS_6) + request_read_xfr(radio, RDS_PS_6); + else + request_read_xfr(radio, xfr_status+1); + } else { + radio->xfr_in_progress = 0; + tavarua_q_event(radio, TAVARUA_EVT_NEW_PS_RDS); + } + break; + case RDS_RT_0: + FMDBG("RT Header\n"); + copy_from_xfr(radio, TAVARUA_BUF_RT_RDS, 5); + radio->xfr_bytes_left = radio->registers[XFRCTRL+1] + & 0x7F; + FMDBG("RT RDS Length: %d\n", radio->xfr_bytes_left); + /*RT_1 to RT_4 16 byte registers so 64 bytes */ + if ((radio->xfr_bytes_left > 0) + && (radio->xfr_bytes_left < 65)) + request_read_xfr(radio, RDS_RT_1); + break; + case RDS_RT_1: + case RDS_RT_2: + case RDS_RT_3: + case RDS_RT_4: + FMDBG("xfr interrupt RT data\n"); + copy_from_xfr(radio, TAVARUA_BUF_RT_RDS, XFR_REG_NUM); + radio->xfr_bytes_left -= XFR_REG_NUM; + if (radio->xfr_bytes_left > 0) + request_read_xfr(radio, xfr_status+1); + else { + radio->xfr_in_progress = 0; + tavarua_q_event(radio, TAVARUA_EVT_NEW_RT_RDS); + } + break; + case RDS_AF_0: + copy_from_xfr(radio, TAVARUA_BUF_AF_LIST, + XFR_REG_NUM); + radio->xfr_bytes_left = radio->registers[XFRCTRL+5]-11; + if (radio->xfr_bytes_left > 0) + request_read_xfr(radio, RDS_AF_1); + else + radio->xfr_in_progress = 0; + break; + case RDS_AF_1: + copy_from_xfr(radio, TAVARUA_BUF_AF_LIST, + radio->xfr_bytes_left); + tavarua_q_event(radio, TAVARUA_EVT_NEW_AF_LIST); + radio->xfr_in_progress = 0; + break; + case RX_CONFIG: + case RADIO_CONFIG: + case RDS_CONFIG: + memcpy(radio->sync_xfr_regs, + &radio->registers[XFRCTRL+1], XFR_REG_NUM); + complete(&radio->sync_req_done); + break; + case RX_STATIONS_0: + FMDBG("Search list has %d stations\n", + radio->registers[XFRCTRL+1]); + radio->xfr_bytes_left = radio->registers[XFRCTRL+1]*2; + if (radio->xfr_bytes_left > 14) { + copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST, + XFR_REG_NUM); + request_read_xfr(radio, RX_STATIONS_1); + } else if (radio->xfr_bytes_left) { + FMDBG("In else RX_STATIONS_0\n"); + copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST, + radio->xfr_bytes_left+1); + tavarua_q_event(radio, + TAVARUA_EVT_NEW_SRCH_LIST); + radio->xfr_in_progress = 0; + } + break; + case RX_STATIONS_1: + FMDBG("In RX_STATIONS_1"); + copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST, + radio->xfr_bytes_left); + tavarua_q_event(radio, TAVARUA_EVT_NEW_SRCH_LIST); + radio->xfr_in_progress = 0; + break; + case PHY_TXGAIN: + FMDBG("read PHY_TXGAIN is successful"); + complete(&radio->sync_req_done); + break; + case (0x80 | RX_CONFIG): + case (0x80 | RADIO_CONFIG): + case (0x80 | RDS_CONFIG): + case (0x80 | INT_CTRL): + complete(&radio->sync_req_done); + break; + case (0x80 | RDS_RT_0): + FMDBG("RT Header Sent\n"); + complete(&radio->sync_req_done); + break; + case (0x80 | RDS_RT_1): + case (0x80 | RDS_RT_2): + case (0x80 | RDS_RT_3): + case (0x80 | RDS_RT_4): + FMDBG("xfr interrupt RT data Sent\n"); + complete(&radio->sync_req_done); + break; + /*TX Specific transfer */ + case (0x80 | RDS_PS_0): + FMDBG("PS Header Sent\n"); + complete(&radio->sync_req_done); + break; + case (0x80 | RDS_PS_1): + case (0x80 | RDS_PS_2): + case (0x80 | RDS_PS_3): + case (0x80 | RDS_PS_4): + case (0x80 | RDS_PS_5): + case (0x80 | RDS_PS_6): + FMDBG("xfr interrupt PS data Sent\n"); + complete(&radio->sync_req_done); + break; + case (0x80 | PHY_TXGAIN): + FMDBG("write PHY_TXGAIN is successful"); + complete(&radio->sync_req_done); + break; + default: + FMDERR("UNKNOWN XFR = %d\n", xfr_status); + } + if (!radio->xfr_in_progress) + start_pending_xfr(radio); + + } + + /* Error occurred. Read ERRCODE to determine cause */ + if (radio->registers[STATUS_REG3] & ERROR) { +#ifdef FM_DEBUG + unsigned char xfr_buf[XFR_REG_NUM]; + int retval = sync_read_xfr(radio, ERROR_CODE, xfr_buf); + FMDBG("retval of ERROR_CODE read : %d\n", retval); +#endif + FMDERR("ERROR STATE\n"); + } + + mutex_unlock(&radio->lock); + FMDBG("Work is done\n"); + +} + +/*============================================================================= +FUNCTION: read_int_stat +=============================================================================*/ +/** + This function is scheduled whenever there is an interrupt pending in interrupt + queue. i.e. kfmradio. + + Whenever there is a GPIO interrupt, a delayed work will be queued in to the + 'kfmradio' work queue. Upon execution of this work in the queue, a a call + to read_int_stat function will be made , which would in turn handle the + interrupts by reading the INTSTATx registers. + NOTE: + Tasks to be run out of a workqueue need to be packaged in a struct + work_struct structure. + + @param work: work_struct structure. + + @return None. +*/ +static void read_int_stat(struct work_struct *work) +{ + struct tavarua_device *radio = container_of(work, + struct tavarua_device, work.work); + tavarua_handle_interrupts(radio); +} + +/************************************************************************* + * irq helper functions + ************************************************************************/ + +/*============================================================================= +FUNCTION: tavarua_request_irq +=============================================================================*/ +/** + This function is called to acquire a FM GPIO and enable FM interrupts. + + @param radio: structure pointer passed by client. + + @return 0 if success else otherwise. +*/ +static int tavarua_request_irq(struct tavarua_device *radio) +{ + int retval; + int irq = radio->pdata->irq; + if (radio == NULL) + return -EINVAL; + + /* A workqueue created with create_workqueue() will have one worker thread + * for each CPU on the system; create_singlethread_workqueue(), instead, + * creates a workqueue with a single worker process. The name of the queue + * is limited to ten characters; it is only used for generating the "command" + * for the kernel thread(s) (which can be seen in ps or top). + */ + radio->wqueue = create_singlethread_workqueue("kfmradio"); + if (!radio->wqueue) + return -ENOMEM; + /* allocate an interrupt line */ + /* On success, request_irq() returns 0 if everything goes as + planned. Your interrupt handler will start receiving its + interrupts immediately. On failure, request_irq() + returns: + -EINVAL + The IRQ number you requested was either + invalid or reserved, or your passed a NULL + pointer for the handler() parameter. + + -EBUSY The IRQ you requested is already being + handled, and the IRQ cannot be shared. + + -ENXIO The m68k returns this value for an invalid + IRQ number. + */ + /* Use request_any_context_irq, So that it might work for nested or + nested interrupts. in MSM8x60, FM is connected to PMIC GPIO and it + is a nested interrupt*/ + retval = request_any_context_irq(irq, tavarua_isr, + IRQ_TYPE_EDGE_FALLING, "fm interrupt", radio); + if (retval < 0) { + FMDERR("Couldn't acquire FM gpio %d\n", irq); + return retval; + } else { + FMDBG("FM GPIO %d registered\n", irq); + } + retval = enable_irq_wake(irq); + if (retval < 0) { + FMDERR("Could not enable FM interrupt\n "); + free_irq(irq , radio); + } + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_disable_irq +=============================================================================*/ +/** + This function is called to disable FM irq and free up FM interrupt handling + resources. + + @param radio: structure pointer passed by client. + + @return 0 if success else otherwise. +*/ +static int tavarua_disable_irq(struct tavarua_device *radio) +{ + int irq; + if (!radio) + return -EINVAL; + irq = radio->pdata->irq; + disable_irq_wake(irq); + cancel_delayed_work_sync(&radio->work); + flush_workqueue(radio->wqueue); + free_irq(irq, radio); + destroy_workqueue(radio->wqueue); + return 0; +} + +/************************************************************************* + * fops/IOCTL helper functions + ************************************************************************/ + +/*============================================================================= +FUNCTION: tavarua_search +=============================================================================*/ +/** + This interface sets the search control features. + + @param radio: structure pointer passed by client. + @param on: The value of a control. + @param dir: FM search direction. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_search(struct tavarua_device *radio, int on, int dir) +{ + enum search_t srch = radio->registers[SRCHCTRL] & SRCH_MODE; + + FMDBG("In tavarua_search\n"); + if (on) { + radio->registers[SRCHRDS1] = 0x00; + radio->registers[SRCHRDS2] = 0x00; + /* Set freq band */ + switch (srch) { + case SCAN_FOR_STRONG: + case SCAN_FOR_WEAK: + radio->srch_params.get_list = 1; + radio->registers[SRCHRDS2] = + radio->srch_params.preset_num; + break; + case RDS_SEEK_PTY: + case RDS_SCAN_PTY: + radio->registers[SRCHRDS2] = + radio->srch_params.srch_pty; + break; + case RDS_SEEK_PI: + radio->registers[SRCHRDS1] = + (radio->srch_params.srch_pi & 0xFF00) >> 8; + radio->registers[SRCHRDS2] = + (radio->srch_params.srch_pi & 0x00FF); + break; + default: + break; + } + radio->registers[SRCHCTRL] |= SRCH_ON; + } else { + radio->registers[SRCHCTRL] &= ~SRCH_ON; + radio->srch_params.get_list = 0; + } + radio->registers[SRCHCTRL] = (dir << 3) | + (radio->registers[SRCHCTRL] & 0xF7); + + FMDBG("SRCHCTRL <%x>\n", radio->registers[SRCHCTRL]); + FMDBG("Search Started\n"); + return tavarua_write_registers(radio, SRCHRDS1, + &radio->registers[SRCHRDS1], 3); +} + +/*============================================================================= +FUNCTION: tavarua_set_region +=============================================================================*/ +/** + This interface configures the FM radio. + + @param radio: structure pointer passed by client. + @param req_region: FM band types. These types defines the FM band minimum and + maximum frequencies in the FM band. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_set_region(struct tavarua_device *radio, + int req_region) +{ + int retval = 0; + unsigned char xfr_buf[XFR_REG_NUM]; + unsigned char value; + unsigned int spacing = 0.100 * FREQ_MUL; + unsigned int band_low, band_high; + unsigned int low_band_limit = 76.0 * FREQ_MUL; + enum tavarua_region_t region = req_region; + + /* Set freq band */ + switch (region) { + case TAVARUA_REGION_US: + case TAVARUA_REGION_EU: + case TAVARUA_REGION_JAPAN_WIDE: + SET_REG_FIELD(radio->registers[RDCTRL], 0, + RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK); + break; + case TAVARUA_REGION_JAPAN: + SET_REG_FIELD(radio->registers[RDCTRL], 1, + RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK); + break; + default: + retval = sync_read_xfr(radio, RADIO_CONFIG, xfr_buf); + if (retval < 0) { + FMDERR("failed to get RADIO_CONFIG\n"); + return retval; + } + band_low = (radio->region_params.band_low - + low_band_limit) / spacing; + band_high = (radio->region_params.band_high - + low_band_limit) / spacing; + FMDBG("low_band: %x, high_band: %x\n", band_low, band_high); + xfr_buf[0] = band_low >> 8; + xfr_buf[1] = band_low & 0xFF; + xfr_buf[2] = band_high >> 8; + xfr_buf[3] = band_high & 0xFF; + retval = sync_write_xfr(radio, RADIO_CONFIG, xfr_buf); + if (retval < 0) { + FMDERR("Could not set regional settings\n"); + return retval; + } + break; + } + + /* Set channel spacing */ + switch (region) { + case TAVARUA_REGION_US: + case TAVARUA_REGION_EU: + value = 0; + break; + case TAVARUA_REGION_JAPAN: + value = 1; + break; + case TAVARUA_REGION_JAPAN_WIDE: + value = 2; + break; + default: + value = radio->region_params.spacing; + } + + SET_REG_FIELD(radio->registers[RDCTRL], value, + RDCTRL_CHSPACE_OFFSET, RDCTRL_CHSPACE_MASK); + + /* Set De-emphasis and soft band range*/ + switch (region) { + case TAVARUA_REGION_US: + case TAVARUA_REGION_JAPAN: + case TAVARUA_REGION_JAPAN_WIDE: + value = 0; + break; + case TAVARUA_REGION_EU: + value = 1; + break; + default: + value = radio->region_params.emphasis; + } + + SET_REG_FIELD(radio->registers[RDCTRL], value, + RDCTRL_DEEMPHASIS_OFFSET, RDCTRL_DEEMPHASIS_MASK); + + /* set RDS standard */ + switch (region) { + default: + value = radio->region_params.rds_std; + break; + case TAVARUA_REGION_US: + value = 0; + break; + case TAVARUA_REGION_EU: + value = 1; + break; + } + SET_REG_FIELD(radio->registers[RDSCTRL], value, + RDSCTRL_STANDARD_OFFSET, RDSCTRL_STANDARD_MASK); + + FMDBG("RDSCTRLL %x\n", radio->registers[RDSCTRL]); + retval = tavarua_write_register(radio, RDSCTRL, + radio->registers[RDSCTRL]); + if (retval < 0) + return retval; + + FMDBG("RDCTRL: %x\n", radio->registers[RDCTRL]); + retval = tavarua_write_register(radio, RDCTRL, + radio->registers[RDCTRL]); + if (retval < 0) { + FMDERR("Could not set region in rdctrl\n"); + return retval; + } + + /* setting soft band */ + switch (region) { + case TAVARUA_REGION_US: + case TAVARUA_REGION_EU: + radio->region_params.band_low = 87.5 * FREQ_MUL; + radio->region_params.band_high = 108 * FREQ_MUL; + break; + case TAVARUA_REGION_JAPAN: + radio->region_params.band_low = 76 * FREQ_MUL; + radio->region_params.band_high = 90 * FREQ_MUL; + break; + case TAVARUA_REGION_JAPAN_WIDE: + radio->region_params.band_low = 90 * FREQ_MUL; + radio->region_params.band_high = 108 * FREQ_MUL; + break; + default: + break; + } + radio->region_params.region = region; + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_get_freq +=============================================================================*/ +/** + This interface gets the current frequency. + + @param radio: structure pointer passed by client. + @param freq: struct v4l2_frequency. This will be set to the resultant + frequency in units of 62.5 kHz on success. + + NOTE: + To get the current tuner or modulator radio frequency applications set the + tuner field of a struct v4l2_frequency to the respective tuner or modulator + number (only input devices have tuners, only output devices have modulators), + zero out the reserved array and call the VIDIOC_G_FREQUENCY ioctl with a + pointer to this structure. The driver stores the current frequency in the + frequency field. + + Tuning frequency is in units of 62.5 kHz, or if the struct v4l2_tuner or + struct v4l2_modulator capabilities flag V4L2_TUNER_CAP_LOW is set, in + units of 62.5 Hz. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_get_freq(struct tavarua_device *radio, + struct v4l2_frequency *freq) +{ + int retval; + unsigned short chan; + unsigned int band_bottom; + unsigned int spacing; + band_bottom = radio->region_params.band_low; + spacing = 0.100 * FREQ_MUL; + /* read channel */ + retval = tavarua_read_registers(radio, FREQ, 2); + chan = radio->registers[FREQ]; + + /* Frequency (MHz) = 100 (kHz) x Channel + Bottom of Band (MHz) */ + freq->frequency = spacing * chan + band_bottom; + if (radio->registers[TUNECTRL] & ADD_OFFSET) + freq->frequency += 800; + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_set_freq +=============================================================================*/ +/** + This interface sets the current frequency. + + @param radio: structure pointer passed by client. + @param freq: desired frequency sent by the client in 62.5 kHz units. + + NOTE: + To change the current tuner or modulator radio frequency, applications + initialize the tuner, type and frequency fields, and the reserved array of a + struct v4l2_frequency and call the VIDIOC_S_FREQUENCY ioctl with a pointer to + this structure. When the requested frequency is not possible the driver + assumes the closest possible value. However VIDIOC_S_FREQUENCY is a + write-only ioctl, it does not return the actual new frequency. + + Tuning frequency is in units of 62.5 kHz, or if the struct v4l2_tuner + or struct v4l2_modulator capabilities flag V4L2_TUNER_CAP_LOW is set, + in units of 62.5 Hz. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_set_freq(struct tavarua_device *radio, unsigned int freq) +{ + + unsigned int band_bottom; + unsigned char chan; + unsigned char cmd[] = {0x00, 0x00}; + unsigned int spacing; + int retval; + band_bottom = radio->region_params.band_low; + spacing = 0.100 * FREQ_MUL; + if ((freq % 1600) == 800) { + cmd[1] = ADD_OFFSET; + freq -= 800; + } + /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / 100 (kHz) */ + chan = (freq - band_bottom) / spacing; + + cmd[0] = chan; + cmd[1] |= TUNE_STATION; + radio->tune_req = 1; + retval = tavarua_write_registers(radio, FREQ, cmd, 2); + if (retval < 0) + radio->tune_req = 0; + return retval; + +} + +/************************************************************************** + * File Operations Interface + *************************************************************************/ + +/*============================================================================= +FUNCTION: tavarua_fops_read +=============================================================================*/ +/** + This function is called when a process, which already opened the dev file, + attempts to read from it. + + In case of tavarua driver, it is called to read RDS data. + + @param file: file descriptor. + @param buf: The buffer to fill with data. + @param count: The length of the buffer in bytes. + @param ppos: Our offset in the file. + + @return The number of bytes put into the buffer on sucess. + -EFAULT if there is no access to user buffer +*/ +static ssize_t tavarua_fops_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + struct kfifo *rds_buf = &radio->data_buf[TAVARUA_BUF_RAW_RDS]; + + /* block if no new data available */ + while (!kfifo_len(rds_buf)) { + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + if (wait_event_interruptible(radio->read_queue, + kfifo_len(rds_buf)) < 0) + return -EINTR; + } + + /* calculate block count from byte count */ + count /= BYTES_PER_BLOCK; + + + /* check if we can write to the user buffer */ + if (!access_ok(VERIFY_WRITE, buf, count*BYTES_PER_BLOCK)) + return -EFAULT; + + /* copy RDS block out of internal buffer and to user buffer */ + return kfifo_out_locked(rds_buf, buf, count*BYTES_PER_BLOCK, + &radio->buf_lock[TAVARUA_BUF_RAW_RDS]); +} + +/*============================================================================= +FUNCTION: tavarua_fops_write +=============================================================================*/ +/** + This function is called when a process, which already opened the dev file, + attempts to write to it. + + In case of tavarua driver, it is called to write RDS data to host. + + @param file: file descriptor. + @param buf: The buffer which has data to write. + @param count: The length of the buffer. + @param ppos: Our offset in the file. + + @return The number of bytes written from the buffer. +*/ +static ssize_t tavarua_fops_write(struct file *file, const char __user *data, + size_t count, loff_t *ppos) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + int bytes_to_copy; + int bytes_copied = 0; + int bytes_left; + int chunk_index = 0; + unsigned char tx_data[XFR_REG_NUM]; + /* Disable TX of this type first */ + switch (radio->tx_mode) { + case TAVARUA_TX_RT: + bytes_left = min((int)count, MAX_RT_LENGTH); + tx_data[1] = 0; + break; + case TAVARUA_TX_PS: + bytes_left = min((int)count, MAX_PS_LENGTH); + tx_data[4] = 0; + break; + default: + FMDERR("%s: Unknown TX mode\n", __func__); + return -1; + } + retval = sync_write_xfr(radio, radio->tx_mode, tx_data); + if (retval < 0) + return retval; + + /* send payload to FM hardware */ + while (bytes_left) { + chunk_index++; + bytes_to_copy = min(bytes_left, XFR_REG_NUM); + if (copy_from_user(tx_data, data + bytes_copied, bytes_to_copy)) + return -EFAULT; + retval = sync_write_xfr(radio, radio->tx_mode + + chunk_index, tx_data); + if (retval < 0) + return retval; + + bytes_copied += bytes_to_copy; + bytes_left -= bytes_to_copy; + } + + /* send the header */ + switch (radio->tx_mode) { + case TAVARUA_TX_RT: + FMDBG("Writing RT header\n"); + tx_data[0] = bytes_copied; + tx_data[1] = TX_ON | 0x03; /* on | PTY */ + tx_data[2] = 0x12; /* PI high */ + tx_data[3] = 0x34; /* PI low */ + break; + case TAVARUA_TX_PS: + FMDBG("Writing PS header\n"); + tx_data[0] = chunk_index; + tx_data[1] = 0x03; /* PTY */ + tx_data[2] = 0x12; /* PI high */ + tx_data[3] = 0x34; /* PI low */ + tx_data[4] = TX_ON | 0x01; + break; + default: + FMDERR("%s: Unknown TX mode\n", __func__); + return -1; + } + retval = sync_write_xfr(radio, radio->tx_mode, tx_data); + if (retval < 0) + return retval; + FMDBG("done writing: %d\n", retval); + return bytes_copied; +} + +/*============================================================================= +FUNCTION: tavarua_fops_open +=============================================================================*/ +/** + This function is called when a process tries to open the device file, like + "cat /dev/mycharfile" + + @param file: file descriptor. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_fops_open(struct file *file) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + int retval = -ENODEV; + unsigned char value; + /* FM core bring up */ + int i = 0; + char fm_ctl0_part1[] = { 0xCA, 0xCE, 0xD6 }; + char fm_ctl1[] = { 0x03 }; + char fm_ctl0_part2[] = { 0xB6, 0xB7 }; + char buffer[] = {0x00, 0x48, 0x8A, 0x8E, 0x97, 0xB7}; + int bahama_present = -ENODEV; + + mutex_lock(&radio->lock); + if (radio->users) { + mutex_unlock(&radio->lock); + return -EBUSY; + } else { + radio->users++; + } + mutex_unlock(&radio->lock); + + /* initial gpio pin config & Power up */ + retval = radio->pdata->fm_setup(radio->pdata); + if (retval) { + printk(KERN_ERR "%s: failed config gpio & pmic\n", __func__); + goto open_err_setup; + } + if (radio->pdata->config_i2s_gpio != NULL) { + retval = radio->pdata->config_i2s_gpio(FM_I2S_ON); + if (retval) { + printk(KERN_ERR "%s: failed config gpio\n", __func__); + goto config_i2s_err; + } + } + /* enable irq */ + retval = tavarua_request_irq(radio); + if (retval < 0) { + printk(KERN_ERR "%s: failed to request irq\n", __func__); + goto open_err_req_irq; + } + /* call top level marimba interface here to enable FM core */ + FMDBG("initializing SoC\n"); + + bahama_present = is_bahama(); + + if (bahama_present == -ENODEV) + return -ENODEV; + + if (bahama_present) + radio->marimba->mod_id = SLAVE_ID_BAHAMA; + else + radio->marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA; + + value = FM_ENABLE; + retval = marimba_write_bit_mask(radio->marimba, + MARIMBA_XO_BUFF_CNTRL, &value, 1, value); + if (retval < 0) { + printk(KERN_ERR "%s:XO_BUFF_CNTRL write failed\n", + __func__); + goto open_err_all; + } + + + /* Bring up FM core */ + if (bahama_present) { + + radio->marimba->mod_id = SLAVE_ID_BAHAMA; + /* Read the Bahama version*/ + retval = marimba_read_bit_mask(radio->marimba, + 0x00, &bahama_version, 1, 0x1F); + if (retval < 0) { + printk(KERN_ERR "%s: version read failed", + __func__); + goto open_err_all; + } + /* Check for Bahama V2 variant*/ + if (bahama_version == 0x09) { + + /* In case of Bahama v2, forcefully enable the + * internal analog and digital voltage controllers + */ + value = 0x06; + /* value itself used as mask in these writes*/ + retval = marimba_write_bit_mask(radio->marimba, + BAHAMA_LDO_DREG_CTL0, &value, 1, value); + if (retval < 0) { + printk(KERN_ERR "%s:0xF0 write failed\n", + __func__); + goto open_err_all; + } + value = 0x86; + retval = marimba_write_bit_mask(radio->marimba, + BAHAMA_LDO_AREG_CTL0, &value, 1, value); + if (retval < 0) { + printk(KERN_ERR "%s:0xF4 write failed\n", + __func__); + goto open_err_all; + } + } + + /*write FM mode*/ + retval = tavarua_write_register(radio, BAHAMA_FM_MODE_REG, + BAHAMA_FM_MODE_NORMAL); + if (retval < 0) { + printk(KERN_ERR "failed to set the FM mode: %d\n", + retval); + goto open_err_all; + } + /*Write first sequence of bytes to FM_CTL0*/ + for (i = 0; i < 3; i++) { + retval = tavarua_write_register(radio, + BAHAMA_FM_CTL0_REG, fm_ctl0_part1[i]); + if (retval < 0) { + printk(KERN_ERR "FM_CTL0:set-1 failure: %d\n", + retval); + goto open_err_all; + } + } + /*Write the FM_CTL1 sequence*/ + for (i = 0; i < 1; i++) { + retval = tavarua_write_register(radio, + BAHAMA_FM_CTL1_REG, fm_ctl1[i]); + if (retval < 0) { + printk(KERN_ERR "FM_CTL1 write failure: %d\n", + retval); + goto open_err_all; + } + } + /*Write second sequence of bytes to FM_CTL0*/ + for (i = 0; i < 2; i++) { + retval = tavarua_write_register(radio, + BAHAMA_FM_CTL0_REG, fm_ctl0_part2[i]); + if (retval < 0) { + printk(KERN_ERR "FM_CTL0:set-2 failure: %d\n", + retval); + goto open_err_all; + } + } + } else { + retval = tavarua_write_registers(radio, LEAKAGE_CNTRL, + buffer, 6); + if (retval < 0) { + printk(KERN_ERR "%s: failed to bring up FM Core\n", + __func__); + goto open_err_all; + } + } + /* Wait for interrupt i.e. complete(&radio->sync_req_done); call */ + /*Initialize the completion variable for + for the proper behavior*/ + init_completion(&radio->sync_req_done); + if (!wait_for_completion_timeout(&radio->sync_req_done, + msecs_to_jiffies(wait_timeout))) { + retval = -1; + FMDERR("Timeout waiting for initialization\n"); + } + + /* get Chip ID */ + retval = tavarua_write_register(radio, XFRCTRL, CHIPID); + if (retval < 0) + goto open_err_all; + msleep(TAVARUA_DELAY); + tavarua_read_registers(radio, XFRCTRL, XFR_REG_NUM+1); + if (radio->registers[XFRCTRL] != CHIPID) + goto open_err_all; + + radio->chipID = (radio->registers[XFRCTRL+2] << 24) | + (radio->registers[XFRCTRL+5] << 16) | + (radio->registers[XFRCTRL+6] << 8) | + (radio->registers[XFRCTRL+7]); + + printk(KERN_WARNING DRIVER_NAME ": Chip ID %x\n", radio->chipID); + if (radio->chipID == MARIMBA_A0) { + printk(KERN_WARNING DRIVER_NAME ": Unsupported hardware: %x\n", + radio->chipID); + retval = -1; + goto open_err_all; + } + + radio->handle_irq = 0; + radio->marimba->mod_id = SLAVE_ID_BAHAMA; + marimba_set_fm_status(radio->marimba, true); + return 0; + + +open_err_all: + /*Disable FM in case of error*/ + value = 0x00; + marimba_write_bit_mask(radio->marimba, MARIMBA_XO_BUFF_CNTRL, + &value, 1, value); + tavarua_disable_irq(radio); +open_err_req_irq: + if (radio->pdata->config_i2s_gpio != NULL) + radio->pdata->config_i2s_gpio(FM_I2S_OFF); +config_i2s_err: + radio->pdata->fm_shutdown(radio->pdata); +open_err_setup: + radio->handle_irq = 1; + radio->users = 0; + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_fops_release +=============================================================================*/ +/** + This function is called when a process closes the device file. + + @param file: file descriptor. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_fops_release(struct file *file) +{ + int retval; + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + unsigned char value; + int i = 0; + /*FM Core shutdown sequence for Bahama*/ + char fm_ctl0_part1[] = { 0xB7 }; + char fm_ctl1[] = { 0x03 }; + char fm_ctl0_part2[] = { 0x9F, 0x48, 0x02 }; + int bahama_present = -ENODEV; + /*FM Core shutdown sequence for Marimba*/ + char buffer[] = {0x18, 0xB7, 0x48}; + bool bt_status = false; + int index; + /* internal regulator controllers DREG_CTL0, AREG_CTL0 + * has to be kept in the valid state based on the bt status. + * 1st row is the state when no clients are active, + * and the second when bt is in on state. + */ + char internal_vreg_ctl[2][2] = { + { 0x04, 0x84 }, + { 0x00, 0x80 } + }; + + if (!radio) + return -ENODEV; + FMDBG("In %s", __func__); + + /* disable radio ctrl */ + retval = tavarua_write_register(radio, RDCTRL, 0x00); + + FMDBG("%s, Disable IRQs\n", __func__); + /* disable irq */ + retval = tavarua_disable_irq(radio); + if (retval < 0) { + printk(KERN_ERR "%s: failed to disable irq\n", __func__); + return retval; + } + + bahama_present = is_bahama(); + + if (bahama_present == -ENODEV) + return -ENODEV; + + if (bahama_present) { + /*Write first sequence of bytes to FM_CTL0*/ + for (i = 0; i < 1; i++) { + retval = tavarua_write_register(radio, + BAHAMA_FM_CTL0_REG, fm_ctl0_part1[i]); + if (retval < 0) { + printk(KERN_ERR "FM_CTL0:Set-1 failure: %d\n", + retval); + break; + } + } + /*Write the FM_CTL1 sequence*/ + for (i = 0; i < 1; i++) { + retval = tavarua_write_register(radio, + BAHAMA_FM_CTL1_REG, fm_ctl1[i]); + if (retval < 0) { + printk(KERN_ERR "FM_CTL1 failure: %d\n", + retval); + break; + } + } + /*Write second sequence of bytes to FM_CTL0*/ + for (i = 0; i < 3; i++) { + retval = tavarua_write_register(radio, + BAHAMA_FM_CTL0_REG, fm_ctl0_part2[i]); + if (retval < 0) { + printk(KERN_ERR "FM_CTL0:Set-2 failure: %d\n", + retval); + break; + } + } + } else { + + retval = tavarua_write_registers(radio, FM_CTL0, + buffer, sizeof(buffer)/sizeof(buffer[0])); + if (retval < 0) { + printk(KERN_ERR "%s: failed to bring down the FM Core\n", + __func__); + return retval; + } + } + radio->marimba->mod_id = SLAVE_ID_BAHAMA; + bt_status = marimba_get_bt_status(radio->marimba); + /* Set the index based on the bt status*/ + index = bt_status ? 1 : 0; + /* Check for Bahama's existance and Bahama V2 variant*/ + if (bahama_present && (bahama_version == 0x09)) { + radio->marimba->mod_id = SLAVE_ID_BAHAMA; + /* actual value itself used as mask*/ + retval = marimba_write_bit_mask(radio->marimba, + BAHAMA_LDO_DREG_CTL0, &internal_vreg_ctl[bt_status][0], + 1, internal_vreg_ctl[index][0]); + if (retval < 0) { + printk(KERN_ERR "%s:0xF0 write failed\n", __func__); + return retval; + } + /* actual value itself used as mask*/ + retval = marimba_write_bit_mask(radio->marimba, + BAHAMA_LDO_AREG_CTL0, &internal_vreg_ctl[bt_status][1], + 1, internal_vreg_ctl[index][1]); + if (retval < 0) { + printk(KERN_ERR "%s:0xF4 write failed\n", __func__); + return retval; + } + } else { + /* disable fm core */ + radio->marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA; + } + + value = 0x00; + retval = marimba_write_bit_mask(radio->marimba, MARIMBA_XO_BUFF_CNTRL, + &value, 1, FM_ENABLE); + if (retval < 0) { + printk(KERN_ERR "%s:XO_BUFF_CNTRL write failed\n", __func__); + return retval; + } + FMDBG("%s, Calling fm_shutdown\n", __func__); + /* teardown gpio and pmic */ + radio->pdata->fm_shutdown(radio->pdata); + if (radio->pdata->config_i2s_gpio != NULL) + radio->pdata->config_i2s_gpio(FM_I2S_OFF); + radio->handle_irq = 1; + radio->users = 0; + radio->marimba->mod_id = SLAVE_ID_BAHAMA; + marimba_set_fm_status(radio->marimba, false); + return 0; +} + +/* + * tavarua_fops - file operations interface + */ +static const struct v4l2_file_operations tavarua_fops = { + .owner = THIS_MODULE, + .read = tavarua_fops_read, + .write = tavarua_fops_write, + .ioctl = video_ioctl2, + .open = tavarua_fops_open, + .release = tavarua_fops_release, +}; + +/************************************************************************* + * Video4Linux Interface + *************************************************************************/ + +/* + * tavarua_v4l2_queryctrl - query control + */ +static struct v4l2_queryctrl tavarua_v4l2_queryctrl[] = { + { + .id = V4L2_CID_AUDIO_VOLUME, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Volume", + .minimum = 0, + .maximum = 15, + .step = 1, + .default_value = 15, + }, + { + .id = V4L2_CID_AUDIO_BALANCE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BASS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_TREBLE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_AUDIO_LOUDNESS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_SRCHMODE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search mode", + .minimum = 0, + .maximum = 7, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_SCANDWELL, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search dwell time", + .minimum = 0, + .maximum = 7, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_SRCHON, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Search on/off", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_STATE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "radio 0ff/rx/tx/reset", + .minimum = 0, + .maximum = 3, + .step = 1, + .default_value = 1, + + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_REGION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "radio standard", + .minimum = 0, + .maximum = 2, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Signal Threshold", + .minimum = 0x80, + .maximum = 0x7F, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search PTY", + .minimum = 0, + .maximum = 31, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_SRCH_PI, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Search PI", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Preset num", + .minimum = 0, + .maximum = 12, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_EMPHASIS, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Emphasis", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_RDS_STD, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS standard", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_SPACING, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Channel spacing", + .minimum = 0, + .maximum = 2, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_RDSON, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "RDS on/off", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS group mask", + .minimum = 0, + .maximum = 0xFFFFFFFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS processing", + .minimum = 0, + .maximum = 0xFF, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "RDS data groups to buffer", + .minimum = 1, + .maximum = 21, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_PSALL, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "pass all ps strings", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_LP_MODE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Low power mode", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_ANTENNA, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "headset/internal", + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + /* Private controls for FM TX*/ + { + .id = V4L2_CID_PRIVATE_TAVARUA_TX_SETPSREPEATCOUNT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Set PS REPEATCOUNT", + .minimum = 0, + .maximum = 15, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_PS_NAME, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Stop PS NAME", + .minimum = 0, + .maximum = 1, + }, + { + .id = V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_RT, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Stop RT", + .minimum = 0, + .maximum = 1, + }, + +}; + +/*============================================================================= +FUNCTION: tavarua_vidioc_querycap +=============================================================================*/ +/** + This function is called to query device capabilities. + + NOTE: + All V4L2 devices support the VIDIOC_QUERYCAP ioctl. It is used to identify + kernel devices compatible with this specification and to obtain information + about driver and hardware capabilities. The ioctl takes a pointer to a struct + v4l2_capability which is filled by the driver. When the driver is not + compatible with this specification the ioctl returns an EINVAL error code. + + @param file: File descriptor returned by open(). + @param capability: pointer to struct v4l2_capability. + + @return On success 0 is returned, else error code. + @return EINVAL: The device is not compatible with this specification. +*/ +static int tavarua_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + + strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + sprintf(capability->bus_info, "I2C"); + capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + + capability->version = radio->chipID; + + return 0; +} + +/*============================================================================= +FUNCTION: tavarua_vidioc_queryctrl +=============================================================================*/ +/** + This function is called to query the device and driver for supported video + controls (enumerate control items). + + NOTE: + To query the attributes of a control, the applications set the id field of + a struct v4l2_queryctrl and call the VIDIOC_QUERYCTRL ioctl with a pointer + to this structure. The driver fills the rest of the structure or returns an + EINVAL error code when the id is invalid. + + @param file: File descriptor returned by open(). + @param qc: pointer to struct v4l2_queryctrl. + + @return On success 0 is returned, else error code. + @return EINVAL: The struct v4l2_queryctrl id is invalid. +*/ +static int tavarua_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + unsigned char i; + int retval = -EINVAL; + + for (i = 0; i < ARRAY_SIZE(tavarua_v4l2_queryctrl); i++) { + if (qc->id && qc->id == tavarua_v4l2_queryctrl[i].id) { + memcpy(qc, &(tavarua_v4l2_queryctrl[i]), sizeof(*qc)); + retval = 0; + break; + } + } + if (retval < 0) + printk(KERN_WARNING DRIVER_NAME + ": query conv4ltrol failed with %d\n", retval); + + return retval; +} +static int peek_MPX_DCC(struct tavarua_device *radio) +{ + int retval = 0; + unsigned char xfr_buf[XFR_REG_NUM]; + int MPX_DCC[] = { 0 }; + int DCC = 0; + int ct = 0; + unsigned char size = 0; + + /* + Poking the MPX_DCC_BYPASS register to freeze the + value of MPX_DCC from changing while we access it + */ + + /*Poking the MPX_DCC_BYPASS register : 0x88C0 */ + size = 0x01; + xfr_buf[0] = (XFR_POKE_MODE | (size << 1)); + xfr_buf[1] = MPX_DCC_BYPASS_POKE_MSB; + xfr_buf[2] = MPX_DCC_BYPASS_POKE_LSB; + xfr_buf[3] = 0x01; + + retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 4); + if (retval < 0) { + FMDBG("Failed to write\n"); + return retval; + } + /*Wait for the XFR interrupt */ + msleep(TAVARUA_DELAY*15); + + for (ct = 0; ct < 5; ct++) + xfr_buf[ct] = 0; + + /* Peeking Regs 0x88C2-0x88C4 */ + size = 0x03; + xfr_buf[0] = (XFR_PEEK_MODE | (size << 1)); + xfr_buf[1] = MPX_DCC_PEEK_MSB_REG1; + xfr_buf[2] = MPX_DCC_PEEK_LSB_REG1; + retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3); + if (retval < 0) { + FMDBG("Failed to write\n"); + return retval; + } + /*Wait for the XFR interrupt */ + msleep(TAVARUA_DELAY*10); + retval = tavarua_read_registers(radio, XFRDAT0, 3); + if (retval < 0) { + printk(KERN_INFO "INT_DET: Read failure\n"); + return retval; + } + MPX_DCC[0] = (int)radio->registers[XFRDAT0]; + MPX_DCC[1] = (int)radio->registers[XFRDAT1]; + MPX_DCC[2] = (int)radio->registers[XFRDAT2]; + + /* + Form the final MPX_DCC parameter + MPX_DCC[0] will form the LSB part + MPX_DCC[1] will be the middle part and 4 bits of + MPX_DCC[2] will be the MSB par of the 20-bit signed MPX_DCC + */ + + DCC = ((int)MPX_DCC[2] << 16) | ((int)MPX_DCC[1] << 8) | + ((int)MPX_DCC[0]); + + /* + if bit-19 is '1',set remaining bits to '1' & make it -tive + */ + if (DCC & 0x00080000) { + FMDBG(KERN_INFO "bit-19 is '1'\n"); + DCC |= 0xFFF00000; + } + + /* + Poking the MPX_DCC_BYPASS register to be back to normal + */ + + /*Poking the MPX_DCC_BYPASS register : 0x88C0 */ + size = 0x01; + xfr_buf[0] = (XFR_POKE_MODE | (size << 1)); + xfr_buf[1] = MPX_DCC_BYPASS_POKE_MSB; + xfr_buf[2] = MPX_DCC_BYPASS_POKE_LSB; + xfr_buf[3] = 0x00; + + retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 4); + if (retval < 0) { + FMDBG("Failed to write\n"); + return retval; + } + /*Wait for the XFR interrupt */ + msleep(TAVARUA_DELAY*10); + + return DCC; +} +/*============================================================================= +FUNCTION: tavarua_vidioc_g_ctrl +=============================================================================*/ +/** + This function is called to get the value of a control. + + NOTE: + To get the current value of a control, applications initialize the id field + of a struct v4l2_control and call the VIDIOC_G_CTRL ioctl with a pointer to + this structure. + + When the id is invalid drivers return an EINVAL error code. When the value is + out of bounds drivers can choose to take the closest valid value or return an + ERANGE error code, whatever seems more appropriate. + + @param file: File descriptor returned by open(). + @param ctrl: pointer to struct v4l2_control. + + @return On success 0 is returned, else error code. + @return EINVAL: The struct v4l2_control id is invalid. + @return ERANGE: The struct v4l2_control value is out of bounds. + @return EBUSY: The control is temporarily not changeable, possibly because + another applications took over control of the device function this control + belongs to. +*/ +static int tavarua_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + unsigned char xfr_buf[XFR_REG_NUM]; + signed char cRmssiThreshold; + signed char ioc; + unsigned char size = 0; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + break; + case V4L2_CID_AUDIO_MUTE: + ctrl->value = radio->registers[IOCTRL] & 0x03 ; + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCHMODE: + ctrl->value = radio->registers[SRCHCTRL] & SRCH_MODE; + break; + case V4L2_CID_PRIVATE_TAVARUA_SCANDWELL: + ctrl->value = (radio->registers[SRCHCTRL] & SCAN_DWELL) >> 4; + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCHON: + ctrl->value = (radio->registers[SRCHCTRL] & SRCH_ON) >> 7 ; + break; + case V4L2_CID_PRIVATE_TAVARUA_STATE: + ctrl->value = (radio->registers[RDCTRL] & 0x03); + break; + case V4L2_CID_PRIVATE_TAVARUA_IOVERC: + retval = tavarua_read_registers(radio, IOVERC, 1); + if (retval < 0) + return retval; + ioc = radio->registers[IOVERC]; + ctrl->value = ioc; + break; + case V4L2_CID_PRIVATE_TAVARUA_INTDET: + size = 0x1; + xfr_buf[0] = (XFR_PEEK_MODE | (size << 1)); + xfr_buf[1] = INTDET_PEEK_MSB; + xfr_buf[2] = INTDET_PEEK_LSB; + retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3); + if (retval < 0) { + FMDBG("Failed to write\n"); + return retval; + } + FMDBG("INT_DET:Sync write success\n"); + /*Wait for the XFR interrupt */ + msleep(TAVARUA_DELAY*10); + /* Read the XFRDAT0 register populated by FM SoC */ + retval = tavarua_read_registers(radio, XFRDAT0, 3); + if (retval < 0) { + FMDBG("INT_DET: Read failure\n"); + return retval; + } + ctrl->value = radio->registers[XFRDAT0]; + break; + case V4L2_CID_PRIVATE_TAVARUA_MPX_DCC: + ctrl->value = peek_MPX_DCC(radio); + break; + case V4L2_CID_PRIVATE_TAVARUA_REGION: + ctrl->value = radio->region_params.region; + break; + case V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH: + retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf); + if (retval < 0) { + FMDBG("[G IOCTL=V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH]\n"); + FMDBG("sync_read_xfr error: [retval=%d]\n", retval); + break; + } + /* Since RMSSI Threshold is signed value */ + cRmssiThreshold = (signed char)xfr_buf[0]; + ctrl->value = cRmssiThreshold; + FMDBG("cRmssiThreshold: %d\n", cRmssiThreshold); + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY: + ctrl->value = radio->srch_params.srch_pty; + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCH_PI: + ctrl->value = radio->srch_params.srch_pi; + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT: + ctrl->value = radio->srch_params.preset_num; + break; + case V4L2_CID_PRIVATE_TAVARUA_EMPHASIS: + ctrl->value = radio->region_params.emphasis; + break; + case V4L2_CID_PRIVATE_TAVARUA_RDS_STD: + ctrl->value = radio->region_params.rds_std; + break; + case V4L2_CID_PRIVATE_TAVARUA_SPACING: + ctrl->value = radio->region_params.spacing; + break; + case V4L2_CID_PRIVATE_TAVARUA_RDSON: + ctrl->value = radio->registers[RDSCTRL] & RDS_ON; + break; + case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK: + retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf); + if (retval > -1) + ctrl->value = (xfr_buf[8] << 24) | + (xfr_buf[9] << 16) | + (xfr_buf[10] << 8) | + xfr_buf[11]; + break; + case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC: + retval = tavarua_read_registers(radio, ADVCTRL, 1); + if (retval > -1) + ctrl->value = radio->registers[ADVCTRL]; + break; + case V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF: + retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf); + if (retval > -1) + ctrl->value = xfr_buf[1]; + break; + case V4L2_CID_PRIVATE_TAVARUA_PSALL: + retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf); + if (retval > -1) + ctrl->value = xfr_buf[12] & RDS_CONFIG_PSALL; + break; + case V4L2_CID_PRIVATE_TAVARUA_LP_MODE: + ctrl->value = radio->lp_mode; + break; + case V4L2_CID_PRIVATE_TAVARUA_ANTENNA: + ctrl->value = GET_REG_FIELD(radio->registers[IOCTRL], + IOC_ANTENNA_OFFSET, IOC_ANTENNA_MASK); + break; + default: + retval = -EINVAL; + } + if (retval < 0) + printk(KERN_WARNING DRIVER_NAME + ": get control failed with %d, id: %d\n", retval, ctrl->id); + + return retval; +} + +static int tavarua_vidioc_s_ext_ctrls(struct file *file, void *priv, + struct v4l2_ext_controls *ctrl) +{ + int retval = 0; + int bytes_to_copy; + int bytes_copied = 0; + int bytes_left = 0; + int chunk_index = 0; + char tx_data[XFR_REG_NUM]; + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + char *data = NULL; + int extra_name_byte = 0; + int name_bytes = 0; + + switch ((ctrl->controls[0]).id) { + case V4L2_CID_RDS_TX_PS_NAME: { + FMDBG("In V4L2_CID_RDS_TX_PS_NAME\n"); + /*Pass a sample PS string */ + + chunk_index = 0; + bytes_copied = 0; + bytes_left = min((int)(ctrl->controls[0]).size, + MAX_PS_LENGTH); + data = (ctrl->controls[0]).string; + + /* send payload to FM hardware */ + while (bytes_left) { + chunk_index++; + FMDBG("chunk is %d", chunk_index); + bytes_to_copy = min(bytes_left, XFR_REG_NUM); + /*Clear the tx_data */ + memset(tx_data, 0, XFR_REG_NUM); + if (copy_from_user(tx_data, + data + bytes_copied, bytes_to_copy)) + return -EFAULT; + retval = sync_write_xfr(radio, + RDS_PS_0 + chunk_index, tx_data); + if (retval < 0) { + FMDBG("sync_write_xfr: %d", retval); + return retval; + } + bytes_copied += bytes_to_copy; + bytes_left -= bytes_to_copy; + } + memset(tx_data, 0, XFR_REG_NUM); + /*Write the PS Header*/ + FMDBG("Writing PS header\n"); + extra_name_byte = (bytes_copied%8) ? 1 : 0; + name_bytes = (bytes_copied/8) + extra_name_byte; + /*8 bytes are grouped as 1 name */ + tx_data[0] = (name_bytes) & MASK_TXREPCOUNT; + tx_data[1] = radio->pty & MASK_PTY; /* PTY */ + tx_data[2] = ((radio->pi & MASK_PI_MSB) >> 8); + tx_data[3] = radio->pi & MASK_PI_LSB; + /* TX ctrl + repeatCount*/ + tx_data[4] = TX_ON | + (radio->ps_repeatcount & MASK_TXREPCOUNT); + retval = sync_write_xfr(radio, RDS_PS_0, tx_data); + if (retval < 0) { + FMDBG("sync_write_xfr returned %d", retval); + return retval; + } + } break; + case V4L2_CID_RDS_TX_RADIO_TEXT: { + chunk_index = 0; + bytes_copied = 0; + FMDBG("In V4L2_CID_RDS_TX_RADIO_TEXT\n"); + /*Pass a sample PS string */ + FMDBG("Passed RT String : %s\n", + (ctrl->controls[0]).string); + bytes_left = + min((int)(ctrl->controls[0]).size, MAX_RT_LENGTH); + data = (ctrl->controls[0]).string; + /* send payload to FM hardware */ + while (bytes_left) { + chunk_index++; + bytes_to_copy = min(bytes_left, XFR_REG_NUM); + memset(tx_data, 0, XFR_REG_NUM); + if (copy_from_user(tx_data, + data + bytes_copied, bytes_to_copy)) + return -EFAULT; + retval = sync_write_xfr(radio, + RDS_RT_0 + chunk_index, tx_data); + if (retval < 0) + return retval; + bytes_copied += bytes_to_copy; + bytes_left -= bytes_to_copy; + } + /*Write the RT Header */ + tx_data[0] = bytes_copied; + /* PTY */ + tx_data[1] = TX_ON | ((radio->pty & MASK_PTY) >> 8); + /* PI high */ + tx_data[2] = ((radio->pi & MASK_PI_MSB) >> 8); + /* PI low */ + tx_data[3] = radio->pi & MASK_PI_LSB; + retval = sync_write_xfr(radio, RDS_RT_0 , tx_data); + if (retval < 0) + return retval; + FMDBG("done RT writing: %d\n", retval); + } break; + default: + { + FMDBG("Shouldn't reach here\n"); + retval = -1; + } + } + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_vidioc_s_ctrl +=============================================================================*/ +/** + This function is called to set the value of a control. + + NOTE: + To change the value of a control, applications initialize the id and value + fields of a struct v4l2_control and call the VIDIOC_S_CTRL ioctl. + + When the id is invalid drivers return an EINVAL error code. When the value is + out of bounds drivers can choose to take the closest valid value or return an + ERANGE error code, whatever seems more appropriate. + + @param file: File descriptor returned by open(). + @param ctrl: pointer to struct v4l2_control. + + @return On success 0 is returned, else error code. + @return EINVAL: The struct v4l2_control id is invalid. + @return ERANGE: The struct v4l2_control value is out of bounds. + @return EBUSY: The control is temporarily not changeable, possibly because + another applications took over control of the device function this control + belongs to. +*/ +static int tavarua_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + int retval = 0; + unsigned char value; + unsigned char xfr_buf[XFR_REG_NUM]; + unsigned char tx_data[XFR_REG_NUM]; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_VOLUME: + break; + case V4L2_CID_AUDIO_MUTE: + value = (radio->registers[IOCTRL] & ~IOC_HRD_MUTE) | + (ctrl->value & 0x03); + retval = tavarua_write_register(radio, IOCTRL, value); + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCHMODE: + value = (radio->registers[SRCHCTRL] & ~SRCH_MODE) | + ctrl->value; + radio->registers[SRCHCTRL] = value; + break; + case V4L2_CID_PRIVATE_TAVARUA_SCANDWELL: + value = (radio->registers[SRCHCTRL] & ~SCAN_DWELL) | + (ctrl->value << 4); + radio->registers[SRCHCTRL] = value; + break; + /* start/stop search */ + case V4L2_CID_PRIVATE_TAVARUA_SRCHON: + FMDBG("starting search\n"); + tavarua_search(radio, ctrl->value, SRCH_DIR_UP); + break; + case V4L2_CID_PRIVATE_TAVARUA_STATE: + /* check if already on */ + radio->handle_irq = 1; + if (((ctrl->value == FM_RECV) || (ctrl->value == FM_TRANS)) + && !(radio->registers[RDCTRL] & + ctrl->value)) { + FMDBG("clearing flags\n"); + init_completion(&radio->sync_xfr_start); + init_completion(&radio->sync_req_done); + radio->xfr_in_progress = 0; + radio->xfr_bytes_left = 0; + FMDBG("turning on ..\n"); + retval = tavarua_start(radio, ctrl->value); + if (retval >= 0) { + FMDBG("Setting audio path ...\n"); + retval = tavarua_set_audio_path( + TAVARUA_AUDIO_OUT_DIGITAL_ON, + TAVARUA_AUDIO_OUT_ANALOG_OFF); + if (retval < 0) { + FMDERR("Error in tavarua_set_audio_path" + " %d\n", retval); + } + /* Enabling 'SoftMute' and 'SignalBlending' features */ + value = (radio->registers[IOCTRL] | + IOC_SFT_MUTE | IOC_SIG_BLND); + retval = tavarua_write_register(radio, IOCTRL, value); + if (retval < 0) + FMDBG("SMute and SBlending not enabled\n"); + } + } + /* check if off */ + else if ((ctrl->value == FM_OFF) && radio->registers[RDCTRL]) { + FMDBG("turning off...\n"); + retval = tavarua_write_register(radio, RDCTRL, + ctrl->value); + /*Make it synchronous + Block it till READY interrupt + Wait for interrupt i.e. + complete(&radio->sync_req_done) + */ + + if (retval >= 0) { + + if (!wait_for_completion_timeout( + &radio->sync_req_done, + msecs_to_jiffies(wait_timeout))) + FMDBG("turning off timedout...\n"); + } + } + break; + case V4L2_CID_PRIVATE_TAVARUA_REGION: + retval = tavarua_set_region(radio, ctrl->value); + break; + case V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH: + retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf); + if (retval < 0) { + FMDERR("V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH]\n"); + FMDERR("sync_read_xfr [retval=%d]\n", retval); + break; + } + /* RMSSI Threshold is a signed 8 bit value */ + xfr_buf[0] = (unsigned char)ctrl->value; + xfr_buf[1] = (unsigned char)ctrl->value; + xfr_buf[4] = 0x01; + retval = sync_write_xfr(radio, RX_CONFIG, xfr_buf); + if (retval < 0) { + FMDERR("V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH]\n"); + FMDERR("sync_write_xfr [retval=%d]\n", retval); + break; + } + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY: + radio->srch_params.srch_pty = ctrl->value; + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCH_PI: + radio->srch_params.srch_pi = ctrl->value; + break; + case V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT: + radio->srch_params.preset_num = ctrl->value; + break; + case V4L2_CID_PRIVATE_TAVARUA_EMPHASIS: + radio->region_params.emphasis = ctrl->value; + break; + case V4L2_CID_PRIVATE_TAVARUA_RDS_STD: + radio->region_params.rds_std = ctrl->value; + break; + case V4L2_CID_PRIVATE_TAVARUA_SPACING: + radio->region_params.spacing = ctrl->value; + break; + case V4L2_CID_PRIVATE_TAVARUA_RDSON: + retval = 0; + if (ctrl->value != (radio->registers[RDSCTRL] & RDS_ON)) { + value = radio->registers[RDSCTRL] | ctrl->value; + retval = tavarua_write_register(radio, RDSCTRL, value); + } + break; + case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK: + retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf); + if (retval < 0) + break; + xfr_buf[8] = (ctrl->value & 0xFF000000) >> 24; + xfr_buf[9] = (ctrl->value & 0x00FF0000) >> 16; + xfr_buf[10] = (ctrl->value & 0x0000FF00) >> 8; + xfr_buf[11] = (ctrl->value & 0x000000FF); + retval = sync_write_xfr(radio, RDS_CONFIG, xfr_buf); + break; + case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC: + value = radio->registers[ADVCTRL] | ctrl->value ; + retval = tavarua_write_register(radio, ADVCTRL, value); + break; + case V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF: + retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf); + if (retval < 0) + break; + xfr_buf[1] = ctrl->value; + retval = sync_write_xfr(radio, RDS_CONFIG, xfr_buf); + break; + case V4L2_CID_PRIVATE_TAVARUA_PSALL: + retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf); + value = ctrl->value & RDS_CONFIG_PSALL; + if (retval < 0) + break; + xfr_buf[12] &= ~RDS_CONFIG_PSALL; + xfr_buf[12] |= value; + retval = sync_write_xfr(radio, RDS_CONFIG, xfr_buf); + break; + case V4L2_CID_PRIVATE_TAVARUA_LP_MODE: + retval = 0; + if (ctrl->value == radio->lp_mode) + break; + if (ctrl->value) { + FMDBG("going into low power mode\n"); + retval = tavarua_disable_interrupts(radio); + } else { + FMDBG("going into normal power mode\n"); + tavarua_setup_interrupts(radio, + (radio->registers[RDCTRL] & 0x03)); + } + break; + case V4L2_CID_PRIVATE_TAVARUA_ANTENNA: + SET_REG_FIELD(radio->registers[IOCTRL], ctrl->value, + IOC_ANTENNA_OFFSET, IOC_ANTENNA_MASK); + break; + /* TX Controls */ + + case V4L2_CID_RDS_TX_PTY: { + radio->pty = ctrl->value; + } break; + case V4L2_CID_RDS_TX_PI: { + radio->pi = ctrl->value; + } break; + case V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_PS_NAME: { + FMDBG("In STOP_RDS_TX_PS_NAME\n"); + /*Pass a sample PS string */ + memset(tx_data, '0', XFR_REG_NUM); + FMDBG("Writing PS header\n"); + retval = sync_write_xfr(radio, RDS_PS_0, tx_data); + FMDBG("retval of PS Header write: %d", retval); + + } break; + + case V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_RT: { + memset(tx_data, '0', XFR_REG_NUM); + FMDBG("Writing RT header\n"); + retval = sync_write_xfr(radio, RDS_RT_0, tx_data); + FMDBG("retval of Header write: %d", retval); + + } break; + + case V4L2_CID_PRIVATE_TAVARUA_TX_SETPSREPEATCOUNT: { + radio->ps_repeatcount = ctrl->value; + } break; + case V4L2_CID_TUNE_POWER_LEVEL: { + unsigned char tx_power_lvl_config[FM_TX_PWR_LVL_MAX+1] = { + 0x85, /* tx_da<5:3> = 0 lpf<2:0> = 5*/ + 0x95, /* tx_da<5:3> = 2 lpf<2:0> = 5*/ + 0x9D, /* tx_da<5:3> = 3 lpf<2:0> = 5*/ + 0xA5, /* tx_da<5:3> = 4 lpf<2:0> = 5*/ + 0xAD, /* tx_da<5:3> = 5 lpf<2:0> = 5*/ + 0xB5, /* tx_da<5:3> = 6 lpf<2:0> = 5*/ + 0xBD, /* tx_da<5:3> = 7 lpf<2:0> = 5*/ + 0xBF /* tx_da<5:3> = 7 lpf<2:0> = 7*/ + }; + if (ctrl->value > FM_TX_PWR_LVL_MAX) + ctrl->value = FM_TX_PWR_LVL_MAX; + if (ctrl->value < FM_TX_PWR_LVL_0) + ctrl->value = FM_TX_PWR_LVL_0; + retval = sync_read_xfr(radio, PHY_TXGAIN, xfr_buf); + FMDBG("return for PHY_TXGAIN is %d", retval); + if (retval < 0) { + FMDBG("read failed"); + break; + } + xfr_buf[2] = tx_power_lvl_config[ctrl->value]; + retval = sync_write_xfr(radio, PHY_TXGAIN, xfr_buf); + FMDBG("return for write PHY_TXGAIN is %d", retval); + if (retval < 0) + FMDBG("write failed"); + } break; + + default: + retval = -EINVAL; + } + if (retval < 0) + printk(KERN_WARNING DRIVER_NAME + ": set control failed with %d, id : %d\n", retval, ctrl->id); + + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_vidioc_g_tuner +=============================================================================*/ +/** + This function is called to get tuner attributes. + + NOTE: + To query the attributes of a tuner, applications initialize the index field + and zero out the reserved array of a struct v4l2_tuner and call the + VIDIOC_G_TUNER ioctl with a pointer to this structure. Drivers fill the rest + of the structure or return an EINVAL error code when the index is out of + bounds. To enumerate all tuners applications shall begin at index zero, + incrementing by one until the driver returns EINVAL. + + @param file: File descriptor returned by open(). + @param tuner: pointer to struct v4l2_tuner. + + @return On success 0 is returned, else error code. + @return EINVAL: The struct v4l2_tuner index is out of bounds. +*/ +static int tavarua_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + int retval; + unsigned char xfr_buf[XFR_REG_NUM]; + char rmssi = 0; + unsigned char size = 0; + + if (tuner->index > 0) + return -EINVAL; + + /* read status rssi */ + retval = tavarua_read_registers(radio, IOCTRL, 1); + if (retval < 0) + return retval; + /* read RMSSI */ + size = 0x1; + xfr_buf[0] = (XFR_PEEK_MODE | (size << 1)); + xfr_buf[1] = RMSSI_PEEK_MSB; + xfr_buf[2] = RMSSI_PEEK_LSB; + retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3); + msleep(TAVARUA_DELAY*10); + retval = tavarua_read_registers(radio, XFRDAT0, 3); + rmssi = radio->registers[XFRDAT0]; + tuner->signal = rmssi; + + strcpy(tuner->name, "FM"); + tuner->type = V4L2_TUNER_RADIO; + tuner->rangelow = radio->region_params.band_low; + tuner->rangehigh = radio->region_params.band_high; + tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + tuner->capability = V4L2_TUNER_CAP_LOW; + + /* Stereo indicator == Stereo (instead of Mono) */ + if (radio->registers[IOCTRL] & IOC_MON_STR) + tuner->audmode = V4L2_TUNER_MODE_STEREO; + else + tuner->audmode = V4L2_TUNER_MODE_MONO; + + /* automatic frequency control: -1: freq to low, 1 freq to high */ + tuner->afc = 0; + + return 0; +} + +/*============================================================================= +FUNCTION: tavarua_vidioc_s_tuner +=============================================================================*/ +/** + This function is called to set tuner attributes. Used to set mono/stereo mode. + + NOTE: + Tuners have two writable properties, the audio mode and the radio frequency. + To change the audio mode, applications initialize the index, audmode and + reserved fields and call the VIDIOC_S_TUNER ioctl. This will not change the + current tuner, which is determined by the current video input. Drivers may + choose a different audio mode if the requested mode is invalid or unsupported. + Since this is a write-only ioctl, it does not return the actually selected + audio mode. + + To change the radio frequency the VIDIOC_S_FREQUENCY ioctl is available. + + @param file: File descriptor returned by open(). + @param tuner: pointer to struct v4l2_tuner. + + @return On success 0 is returned, else error code. + @return -EINVAL: The struct v4l2_tuner index is out of bounds. +*/ +static int tavarua_vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + int retval; + int audmode; + if (tuner->index > 0) + return -EINVAL; + + FMDBG("%s: set low to %d\n", __func__, tuner->rangelow); + radio->region_params.band_low = tuner->rangelow; + radio->region_params.band_high = tuner->rangehigh; + if (tuner->audmode == V4L2_TUNER_MODE_MONO) + /* Mono */ + audmode = (radio->registers[IOCTRL] | IOC_MON_STR); + else + /* Stereo */ + audmode = (radio->registers[IOCTRL] & ~IOC_MON_STR); + retval = tavarua_write_register(radio, IOCTRL, audmode); + if (retval < 0) + printk(KERN_WARNING DRIVER_NAME + ": set tuner failed with %d\n", retval); + + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_vidioc_g_frequency +=============================================================================*/ +/** + This function is called to get tuner or modulator radio frequency. + + NOTE: + To get the current tuner or modulator radio frequency applications set the + tuner field of a struct v4l2_frequency to the respective tuner or modulator + number (only input devices have tuners, only output devices have modulators), + zero out the reserved array and call the VIDIOC_G_FREQUENCY ioctl with a + pointer to this structure. The driver stores the current frequency in the + frequency field. + + @param file: File descriptor returned by open(). + @param freq: pointer to struct v4l2_frequency. This will be set to the + resultant + frequency in 62.5 khz on success. + + @return On success 0 is returned, else error code. + @return EINVAL: The tuner index is out of bounds or the value in the type + field is wrong. +*/ +static int tavarua_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + freq->type = V4L2_TUNER_RADIO; + return tavarua_get_freq(radio, freq); + +} + +/*============================================================================= +FUNCTION: tavarua_vidioc_s_frequency +=============================================================================*/ +/** + This function is called to set tuner or modulator radio frequency. + + NOTE: + To change the current tuner or modulator radio frequency applications + initialize the tuner, type and frequency fields, and the reserved array of + a struct v4l2_frequency and call the VIDIOC_S_FREQUENCY ioctl with a pointer + to this structure. When the requested frequency is not possible the driver + assumes the closest possible value. However VIDIOC_S_FREQUENCY is a + write-only ioctl, it does not return the actual new frequency. + + @param file: File descriptor returned by open(). + @param freq: pointer to struct v4l2_frequency. + + @return On success 0 is returned, else error code. + @return EINVAL: The tuner index is out of bounds or the value in the type + field is wrong. +*/ +static int tavarua_vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + int retval = -1; + struct v4l2_frequency getFreq; + + FMDBG("%s\n", __func__); + + if (freq->type != V4L2_TUNER_RADIO) + return -EINVAL; + + FMDBG("Calling tavarua_set_freq\n"); + + INIT_COMPLETION(radio->sync_req_done); + retval = tavarua_set_freq(radio, freq->frequency); + if (retval < 0) { + printk(KERN_WARNING DRIVER_NAME + ": set frequency failed with %d\n", retval); + } else { + /* Wait for interrupt i.e. complete + (&radio->sync_req_done); call */ + if (!wait_for_completion_timeout(&radio->sync_req_done, + msecs_to_jiffies(wait_timeout))) { + FMDERR("Timeout: No Tune response"); + retval = tavarua_get_freq(radio, &getFreq); + radio->tune_req = 0; + if (retval > 0) { + if (getFreq.frequency == freq->frequency) { + /** This is success, queut the event*/ + tavarua_q_event(radio, + TAVARUA_EVT_TUNE_SUCC); + return 0; + } else { + return -EIO; + } + } + } + } + radio->tune_req = 0; + return retval; +} + +/*============================================================================= +FUNCTION: tavarua_vidioc_dqbuf +=============================================================================*/ +/** + This function is called to exchange a buffer with the driver. + This is main buffer function, in essense its equivalent to a blocking + read call. + + Applications call the VIDIOC_DQBUF ioctl to dequeue a filled (capturing) or + displayed (output) buffer from the driver's outgoing queue. They just set + the type and memory fields of a struct v4l2_buffer as above, when VIDIOC_DQBUF + is called with a pointer to this structure the driver fills the remaining + fields or returns an error code. + + NOTE: + By default VIDIOC_DQBUF blocks when no buffer is in the outgoing queue. + When the O_NONBLOCK flag was given to the open() function, VIDIOC_DQBUF + returns immediately with an EAGAIN error code when no buffer is available. + + @param file: File descriptor returned by open(). + @param buffer: pointer to struct v4l2_buffer. + + @return On success 0 is returned, else error code. + @return EAGAIN: Non-blocking I/O has been selected using O_NONBLOCK and no + buffer was in the outgoing queue. + @return EINVAL: The buffer type is not supported, or the index is out of + bounds, or no buffers have been allocated yet, or the userptr or length are + invalid. + @return ENOMEM: Not enough physical or virtual memory was available to enqueue + a user pointer buffer. + @return EIO: VIDIOC_DQBUF failed due to an internal error. Can also indicate + temporary problems like signal loss. Note the driver might dequeue an (empty) + buffer despite returning an error, or even stop capturing. +*/ +static int tavarua_vidioc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buffer) +{ + + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + enum tavarua_buf_t buf_type = buffer->index; + struct kfifo *data_fifo; + unsigned char *buf = (unsigned char *)buffer->m.userptr; + unsigned int len = buffer->length; + FMDBG("%s: requesting buffer %d\n", __func__, buf_type); + /* check if we can access the user buffer */ + if (!access_ok(VERIFY_WRITE, buf, len)) + return -EFAULT; + if ((buf_type < TAVARUA_BUF_MAX) && (buf_type >= 0)) { + data_fifo = &radio->data_buf[buf_type]; + if (buf_type == TAVARUA_BUF_EVENTS) { + if (wait_event_interruptible(radio->event_queue, + kfifo_len(data_fifo)) < 0) { + return -EINTR; + } + } + } else { + FMDERR("invalid buffer type\n"); + return -EINVAL; + } + buffer->bytesused = kfifo_out_locked(data_fifo, buf, len, + &radio->buf_lock[buf_type]); + + return 0; +} + +/*============================================================================= +FUNCTION: tavarua_vidioc_g_fmt_type_private +=============================================================================*/ +/** + This function is here to make the v4l2 framework happy. + We cannot use private buffers without it. + + @param file: File descriptor returned by open(). + @param f: pointer to struct v4l2_format. + + @return On success 0 is returned, else error code. + @return EINVAL: The tuner index is out of bounds or the value in the type + field is wrong. +*/ +static int tavarua_vidioc_g_fmt_type_private(struct file *file, void *priv, + struct v4l2_format *f) +{ + return 0; + +} + +/*============================================================================= +FUNCTION: tavarua_vidioc_s_hw_freq_seek +=============================================================================*/ +/** + This function is called to perform a hardware frequency seek. + + Start a hardware frequency seek from the current frequency. To do this + applications initialize the tuner, type, seek_upward and wrap_around fields, + and zero out the reserved array of a struct v4l2_hw_freq_seek and call the + VIDIOC_S_HW_FREQ_SEEK ioctl with a pointer to this structure. + + This ioctl is supported if the V4L2_CAP_HW_FREQ_SEEK capability is set. + + @param file: File descriptor returned by open(). + @param seek: pointer to struct v4l2_hw_freq_seek. + + @return On success 0 is returned, else error code. + @return EINVAL: The tuner index is out of bounds or the value in the type + field is wrong. + @return EAGAIN: The ioctl timed-out. Try again. +*/ +static int tavarua_vidioc_s_hw_freq_seek(struct file *file, void *priv, + struct v4l2_hw_freq_seek *seek) +{ + struct tavarua_device *radio = video_get_drvdata(video_devdata(file)); + int dir; + if (seek->seek_upward) + dir = SRCH_DIR_UP; + else + dir = SRCH_DIR_DOWN; + FMDBG("starting search\n"); + return tavarua_search(radio, CTRL_ON, dir); +} + +/* + * tavarua_viddev_tamples - video device interface + */ +static const struct v4l2_ioctl_ops tavarua_ioctl_ops = { + .vidioc_querycap = tavarua_vidioc_querycap, + .vidioc_queryctrl = tavarua_vidioc_queryctrl, + .vidioc_g_ctrl = tavarua_vidioc_g_ctrl, + .vidioc_s_ctrl = tavarua_vidioc_s_ctrl, + .vidioc_g_tuner = tavarua_vidioc_g_tuner, + .vidioc_s_tuner = tavarua_vidioc_s_tuner, + .vidioc_g_frequency = tavarua_vidioc_g_frequency, + .vidioc_s_frequency = tavarua_vidioc_s_frequency, + .vidioc_s_hw_freq_seek = tavarua_vidioc_s_hw_freq_seek, + .vidioc_dqbuf = tavarua_vidioc_dqbuf, + .vidioc_g_fmt_type_private = tavarua_vidioc_g_fmt_type_private, + .vidioc_s_ext_ctrls = tavarua_vidioc_s_ext_ctrls, +}; + +static struct video_device tavarua_viddev_template = { + .fops = &tavarua_fops, + .ioctl_ops = &tavarua_ioctl_ops, + .name = DRIVER_NAME, + .release = video_device_release, +}; + +/*============================================================== +FUNCTION: FmQSocCom_EnableInterrupts +==============================================================*/ +/** + This function enable interrupts. + + @param radio: structure pointer passed by client. + @param state: FM radio state (receiver/transmitter/off/reset). + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_setup_interrupts(struct tavarua_device *radio, + enum radio_state_t state) +{ + int retval; + unsigned char int_ctrl[XFR_REG_NUM]; + + if (!radio->lp_mode) + return 0; + + int_ctrl[STATUS_REG1] = READY | TUNE | SEARCH | SCANNEXT | + SIGNAL | INTF | SYNC | AUDIO; + if (state == FM_RECV) + int_ctrl[STATUS_REG2] = RDSDAT | RDSRT | RDSPS | RDSAF; + else + int_ctrl[STATUS_REG2] = TXRDSDAT | TXRDSDONE; + + int_ctrl[STATUS_REG3] = TRANSFER | ERROR; + + /* use xfr for interrupt setup */ + if (radio->chipID == MARIMBA_2_1 || radio->chipID == BAHAMA_1_0 + || radio->chipID == BAHAMA_2_0) { + FMDBG("Setting interrupts\n"); + retval = sync_write_xfr(radio, INT_CTRL, int_ctrl); + /* use register write to setup interrupts */ + } else { + retval = tavarua_write_register(radio, + STATUS_REG1, int_ctrl[STATUS_REG1]); + if (retval < 0) + return retval; + + retval = tavarua_write_register(radio, + STATUS_REG2, int_ctrl[STATUS_REG2]); + if (retval < 0) + return retval; + + retval = tavarua_write_register(radio, + STATUS_REG3, int_ctrl[STATUS_REG3]); + if (retval < 0) + return retval; + } + + radio->lp_mode = 0; + /* tavarua_handle_interrupts force reads all the interrupt status + * registers and it is not valid for MBA 2.1 + */ + if ((radio->chipID != MARIMBA_2_1) && (radio->chipID != BAHAMA_1_0) + && (radio->chipID != BAHAMA_2_0)) + tavarua_handle_interrupts(radio); + + return retval; + +} + +/*============================================================== +FUNCTION: tavarua_disable_interrupts +==============================================================*/ +/** + This function disables interrupts. + + @param radio: structure pointer passed by client. + + @return => 0 if successful. + @return < 0 if failure. +*/ +static int tavarua_disable_interrupts(struct tavarua_device *radio) +{ + unsigned char lpm_buf[XFR_REG_NUM]; + int retval; + if (radio->lp_mode) + return 0; + FMDBG("%s\n", __func__); + /* In Low power mode, disable all the interrupts that are not being + waited by the Application */ + lpm_buf[STATUS_REG1] = TUNE | SEARCH | SCANNEXT; + lpm_buf[STATUS_REG2] = 0x00; + lpm_buf[STATUS_REG3] = TRANSFER; + /* use xfr for interrupt setup */ + wait_timeout = 100; + if (radio->chipID == MARIMBA_2_1 || radio->chipID == BAHAMA_1_0 + || radio->chipID == BAHAMA_2_0) + retval = sync_write_xfr(radio, INT_CTRL, lpm_buf); + /* use register write to setup interrupts */ + else + retval = tavarua_write_registers(radio, STATUS_REG1, lpm_buf, + ARRAY_SIZE(lpm_buf)); + + /*INT_CTL writes may fail with TIME_OUT as all the + interrupts have been disabled + */ + if (retval > -1 || retval == -ETIME) { + radio->lp_mode = 1; + /*Consider timeout as a valid case here*/ + retval = 0; + } + wait_timeout = WAIT_TIMEOUT; + return retval; + +} + +/*============================================================== +FUNCTION: tavarua_start +==============================================================*/ +/** + Starts/enables the device (FM radio). + + @param radio: structure pointer passed by client. + @param state: FM radio state (receiver/transmitter/off/reset). + + @return On success 0 is returned, else error code. +*/ +static int tavarua_start(struct tavarua_device *radio, + enum radio_state_t state) +{ + + int retval; + FMDBG("%s <%d>\n", __func__, state); + /* set geographic region */ + radio->region_params.region = TAVARUA_REGION_US; + + /* set radio mode */ + retval = tavarua_write_register(radio, RDCTRL, state); + if (retval < 0) + return retval; + /* wait for radio to init */ + msleep(RADIO_INIT_TIME); + /* enable interrupts */ + tavarua_setup_interrupts(radio, state); + /* default region is US */ + radio->region_params.band_low = US_LOW_BAND * FREQ_MUL; + radio->region_params.band_high = US_HIGH_BAND * FREQ_MUL; + + return 0; +} + +/*============================================================== +FUNCTION: tavarua_suspend +==============================================================*/ +/** + Save state and stop all devices in system. + + @param pdev: platform device to be suspended. + @param state: Power state to put each device in. + + @return On success 0 is returned, else error code. +*/ +static int tavarua_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct tavarua_device *radio = platform_get_drvdata(pdev); + int retval; + int users = 0; + printk(KERN_INFO DRIVER_NAME "%s: radio suspend\n\n", __func__); + if (radio) { + mutex_lock(&radio->lock); + users = radio->users; + mutex_unlock(&radio->lock); + if (users) { + retval = tavarua_disable_interrupts(radio); + if (retval < 0) { + printk(KERN_INFO DRIVER_NAME + "tavarua_suspend error %d\n", retval); + return -EIO; + } + } + } + return 0; +} + +/*============================================================== +FUNCTION: tavarua_resume +==============================================================*/ +/** + Restore state of each device in system. + + @param pdev: platform device to be resumed. + + @return On success 0 is returned, else error code. +*/ +static int tavarua_resume(struct platform_device *pdev) +{ + + struct tavarua_device *radio = platform_get_drvdata(pdev); + int retval; + int users = 0; + printk(KERN_INFO DRIVER_NAME "%s: radio resume\n\n", __func__); + if (radio) { + mutex_lock(&radio->lock); + users = radio->users; + mutex_unlock(&radio->lock); + + if (users) { + retval = tavarua_setup_interrupts(radio, + (radio->registers[RDCTRL] & 0x03)); + if (retval < 0) { + printk(KERN_INFO DRIVER_NAME "Error in \ + tavarua_resume %d\n", retval); + return -EIO; + } + } + } + return 0; +} + +/*============================================================== +FUNCTION: tavarua_set_audio_path +==============================================================*/ +/** + This function will configure the audio path to and from the + FM core. + + This interface is expected to be called from the multimedia + driver's thread. This interface should only be called when + the FM hardware is enabled. If the FM hardware is not + currently enabled, this interface will return an error. + + @param digital_on: Digital audio from the FM core should be enabled/disbled. + @param analog_on: Analog audio from the FM core should be enabled/disbled. + + @return On success 0 is returned, else error code. +*/ +int tavarua_set_audio_path(int digital_on, int analog_on) +{ + struct tavarua_device *radio = private_data; + int rx_on = radio->registers[RDCTRL] & FM_RECV; + if (!radio) + return -ENOMEM; + /* RX */ + FMDBG("%s: digital: %d analog: %d\n", __func__, digital_on, analog_on); + SET_REG_FIELD(radio->registers[AUDIOCTRL], + ((rx_on && analog_on) ? 1 : 0), + AUDIORX_ANALOG_OFFSET, + AUDIORX_ANALOG_MASK); + SET_REG_FIELD(radio->registers[AUDIOCTRL], + ((rx_on && digital_on) ? 1 : 0), + AUDIORX_DIGITAL_OFFSET, + AUDIORX_DIGITAL_MASK); + SET_REG_FIELD(radio->registers[AUDIOCTRL], + (rx_on ? 0 : 1), + AUDIOTX_OFFSET, + AUDIOTX_MASK); + /* + + I2S Master/Slave configuration: + Setting the FM SoC as I2S Master/Slave + 'false' - FM SoC is I2S Slave + 'true' - FM SoC is I2S Master + + We get this infomation from the respective target's board file : + MSM7x30 - FM SoC is I2S Slave + MSM8x60 - FM SoC is I2S Slave + MSM7x27A - FM SoC is I2S Master + */ + + if (!radio->pdata->is_fm_soc_i2s_master) { + FMDBG("FM SoC is I2S Slave\n"); + SET_REG_FIELD(radio->registers[AUDIOCTRL], + (0), + I2SCTRL_OFFSET, + I2SCTRL_MASK); + } else { + FMDBG("FM SoC is I2S Master\n"); + SET_REG_FIELD(radio->registers[AUDIOCTRL], + (1), + I2SCTRL_OFFSET, + I2SCTRL_MASK); + } + FMDBG("%s: %x\n", __func__, radio->registers[AUDIOCTRL]); + return tavarua_write_register(radio, AUDIOCTRL, + radio->registers[AUDIOCTRL]); + +} + +/*============================================================== +FUNCTION: tavarua_probe +==============================================================*/ +/** + Once called this functions initiates, allocates resources and registers video + tuner device with the v4l2 framework. + + NOTE: + probe() should verify that the specified device hardware + actually exists; sometimes platform setup code can't be sure. The probing + can use device resources, including clocks, and device platform_data. + + @param pdev: platform device to be probed. + + @return On success 0 is returned, else error code. + -ENOMEM in low memory cases +*/ +static int __init tavarua_probe(struct platform_device *pdev) +{ + + struct marimba_fm_platform_data *tavarua_pdata; + struct tavarua_device *radio; + int retval; + int i; + FMDBG("%s: probe called\n", __func__); + /* private data allocation */ + radio = kzalloc(sizeof(struct tavarua_device), GFP_KERNEL); + if (!radio) { + retval = -ENOMEM; + goto err_initial; + } + + radio->marimba = platform_get_drvdata(pdev); + tavarua_pdata = pdev->dev.platform_data; + radio->pdata = tavarua_pdata; + radio->dev = &pdev->dev; + platform_set_drvdata(pdev, radio); + + /* video device allocation */ + radio->videodev = video_device_alloc(); + if (!radio->videodev) + goto err_radio; + + /* initial configuration */ + memcpy(radio->videodev, &tavarua_viddev_template, + sizeof(tavarua_viddev_template)); + + /*allocate internal buffers for decoded rds and event buffer*/ + for (i = 0; i < TAVARUA_BUF_MAX; i++) { + int kfifo_alloc_rc=0; + spin_lock_init(&radio->buf_lock[i]); + + if (i == TAVARUA_BUF_RAW_RDS) + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + rds_buf*3, GFP_KERNEL); + else + kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i], + STD_BUF_SIZE, GFP_KERNEL); + + if (kfifo_alloc_rc!=0) { + printk(KERN_ERR "%s: failed allocating buffers %d\n", + __func__, kfifo_alloc_rc); + goto err_bufs; + } + } + /* init xfr status */ + radio->users = 0; + radio->xfr_in_progress = 0; + radio->xfr_bytes_left = 0; + for (i = 0; i < TAVARUA_XFR_MAX; i++) + radio->pending_xfrs[i] = 0; + + /* init transmit data */ + radio->tx_mode = TAVARUA_TX_RT; + /* Init RT and PS Tx datas*/ + radio->pty = 0; + radio->pi = 0; + radio->ps_repeatcount = 0; + /* init search params */ + radio->srch_params.srch_pty = 0; + radio->srch_params.srch_pi = 0; + radio->srch_params.preset_num = 0; + radio->srch_params.get_list = 0; + /* radio initializes to low power mode */ + radio->lp_mode = 1; + radio->handle_irq = 1; + /* init lock */ + mutex_init(&radio->lock); + /* init completion flags */ + init_completion(&radio->sync_xfr_start); + init_completion(&radio->sync_req_done); + radio->tune_req = 0; + /* initialize wait queue for event read */ + init_waitqueue_head(&radio->event_queue); + /* initialize wait queue for raw rds read */ + init_waitqueue_head(&radio->read_queue); + + video_set_drvdata(radio->videodev, radio); + /*Start the worker thread for event handling and register read_int_stat + as worker function*/ + INIT_DELAYED_WORK(&radio->work, read_int_stat); + + /* register video device */ + if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) { + printk(KERN_WARNING DRIVER_NAME + ": Could not register video device\n"); + goto err_all; + } + private_data = radio; + return 0; + +err_all: + video_device_release(radio->videodev); +err_bufs: + for (; i > -1; i--) + kfifo_free(&radio->data_buf[i]); +err_radio: + kfree(radio); +err_initial: + return retval; +} + +/*============================================================== +FUNCTION: tavarua_remove +==============================================================*/ +/** + Removes the device. + + @param pdev: platform device to be removed. + + @return On success 0 is returned, else error code. +*/ +static int __devexit tavarua_remove(struct platform_device *pdev) +{ + int i; + struct tavarua_device *radio = platform_get_drvdata(pdev); + + /* disable irq */ + tavarua_disable_irq(radio); + + video_unregister_device(radio->videodev); + + /* free internal buffers */ + for (i = 0; i < TAVARUA_BUF_MAX; i++) + kfifo_free(&radio->data_buf[i]); + + /* free state struct */ + kfree(radio); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +/* + Platform drivers follow the standard driver model convention, where + discovery/enumeration is handled outside the drivers, and drivers + provide probe() and remove() methods. They support power management + and shutdown notifications using the standard conventions. +*/ +static struct platform_driver tavarua_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "marimba_fm", + }, + .remove = __devexit_p(tavarua_remove), + .suspend = tavarua_suspend, + .resume = tavarua_resume, +}; /* platform device we're adding */ + + +/************************************************************************* + * Module Interface + ************************************************************************/ + +/*============================================================== +FUNCTION: radio_module_init +==============================================================*/ +/** + Module entry - add a platform-level device. + + @return Returns zero if the driver registered and bound to a device, else + returns a negative error code when the driver not registered. +*/ +static int __init radio_module_init(void) +{ + printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n"); + return platform_driver_probe(&tavarua_driver, tavarua_probe); +} + +/*============================================================== +FUNCTION: radio_module_exit +==============================================================*/ +/** + Module exit - removes a platform-level device. + + NOTE: + Note that this function will also release all memory- and port-based + resources owned by the device (dev->resource). + + @return none. +*/ +static void __exit radio_module_exit(void) +{ + platform_driver_unregister(&tavarua_driver); +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); + +module_init(radio_module_init); +module_exit(radio_module_exit); + diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index bb53de7fe40..95255fe7bab 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -1031,6 +1031,31 @@ config USB_S2255 This driver can be compiled as a module, called s2255drv. endif # V4L_USB_DRIVERS + +# +# MSM camera configuration +# + +comment "Qualcomm MSM Camera And Video" + +menuconfig MSM_CAMERA + bool "Qualcomm MSM camera and video capture support" + depends on ARCH_MSM && VIDEO_V4L2 && I2C + default y + help + Say Y here to enable selecting the video adapters for + Qualcomm msm camera and video encoding + +config MSM_CAMERA_DEBUG + bool "Qualcomm MSM camera debugging with printk" + depends on MSM_CAMERA + default n + help + Enable printk() debug for msm camera + + +source "drivers/media/video/msm/Kconfig" + endif # VIDEO_CAPTURE_DRIVERS menuconfig V4L_MEM2MEM_DRIVERS diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index f0fecd6f6a3..724c7a33cc2 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -10,8 +10,8 @@ stkwebcam-objs := stk-webcam.o stk-sensor.o omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o -videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \ - v4l2-event.o v4l2-ctrls.o v4l2-subdev.o +videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \ + v4l2-event.o v4l2-ctrls.o v4l2-subdev.o videobuf-core.o videobuf-msm-mem.o videobuf-dma-contig.o # V4L2 core modules @@ -182,6 +182,7 @@ obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-y += davinci/ +obj-$(CONFIG_MSM_CAMERA) += msm/ obj-$(CONFIG_ARCH_OMAP) += omap/ EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core diff --git a/drivers/media/video/msm/Kconfig b/drivers/media/video/msm/Kconfig new file mode 100644 index 00000000000..6ba1abd2b38 --- /dev/null +++ b/drivers/media/video/msm/Kconfig @@ -0,0 +1,194 @@ +config MSM_CAMERA_V4L2 + bool "MSM Camera V4L2 Interface" + depends on MSM_CAMERA + default n + ---help--- + This flag enables V4L2 interface of MSM + camera driver. If enabled, application interacts + with /dev/video0 through V4L2 APIs. Otherwise, + native APIs are used through /dev/config0, /dev/frame0, + and /dev/control0. + +comment "Camera Sensor Selection" +config MT9T013 + bool "Sensor mt9t013 (BAYER 3M)" + depends on MSM_CAMERA && !ARCH_MSM8X60 && !ARCH_MSM8960 && !MSM_CAMERA_V4L2 + default y + ---help--- + MICRON 3M Bayer Sensor with AutoFocus +config MT9D113 + bool "Sensor mt9d113 (YUV 2M)" + depends on MSM_CAMERA && ARCH_MSM8X60 && !MSM_CAMERA_V4L2 + default y + ---help--- + MICRON 2M YUV Sensor + This sensor is the front camera on QT8660. + This uses csi mipi interface. + This sensor is used only on QT device. +config MT9D112 + bool "Sensor mt9d112 (YUV 2M)" + depends on MSM_CAMERA && !ARCH_MSM8X60 && !ARCH_MSM8960 && !MSM_CAMERA_V4L2 + default y + ---help--- + MICRON 2M YUV Sensor +config IMX074 + bool "Sensor IMX074 (BAYER 13.5M)" + depends on MSM_CAMERA && (ARCH_MSM8X60 || ARCH_MSM8960) + default y + ---help--- + SONY 13.5 MP Bayer Sensor +config WEBCAM_OV7692 + bool "Sensor OV7692 (VGA YUV)" + depends on MSM_CAMERA && ARCH_MSM8X60 && !MSM_CAMERA_V4L2 + default y + ---help--- + Omni Vision VGA YUV Sensor. +config WEBCAM_OV9726 + bool "Sensor OV9726 (VGA Bayer)" + depends on MSM_CAMERA && (ARCH_MSM8X60 || ARCH_MSM7X30 || ARCH_MSM7X27A) && !MSM_CAMERA_V4L2 + default n + ---help--- + Omni Vision VGA Bayer Sensor. +# This Senosr is used as a webcam. +# This uses the CSI interface. +config VX6953 + bool "Sensor VX6953 (BAYER 5M)" + depends on MSM_CAMERA && ARCH_MSM7X30 + default y + ---help--- + STM 5M Bayer Sensor with EDOF +config SN12M0PZ + bool "Sensor sn12m0pz (Bayer 12 MP)" + depends on MSM_CAMERA && ARCH_MSM7X30 && !MSM_CAMERA_V4L2 + default y + ---help--- + Sony 12 MP Bayer Sensor +config MT9P012 + bool "Sensor mt9p012 (BAYER 5M)" + depends on MSM_CAMERA && !ARCH_MSM8X60 && !MSM_CAMERA_V4L2 + default y + ---help--- + MICRON 5M Bayer Sensor with Autofocus + +choice + prompt "AF module" + depends on MT9P012 && !ARCH_MSM8X60 && !MSM_CAMERA_V4L2 + default MSM_CAMERA_AF_FOXCONN + +config MSM_CAMERA_AF_FOXCONN + bool "FOXCONN Module" + help + This driver supports FOXCONN AF module for 5M Bayer sensor + +config MSM_CAMERA_AF_BAM + bool "BAM Module" + help + This driver supports BAM AF module for 5M Bayer sensor + +endchoice + +config MT9P012_KM + bool "Sensor mt9p012 KM module (BAYER 5M)" + depends on MSM_CAMERA && !ARCH_MSM8X60 && !MSM_CAMERA_V4L2 + default y + ---help--- + MICRON 5M Bayer Sensor KM modules with Autofocus + +config MT9E013 + bool "Sensor mt9e013 module (BAYER 8M)" + depends on MSM_CAMERA && (ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_MSM7X27A) && !MSM_CAMERA_V4L2 + default n + ---help--- + Aptina 8M Bayer Sensor modules with Autofocus + +config S5K3E2FX + bool "Sensor s5k3e2fx (Samsung 5M)" + depends on MSM_CAMERA && !ARCH_MSM8X60 && !MSM_CAMERA_V4L2 + default y + ---help--- + Samsung 5M with Autofocus + +config QS_S5K4E1 + bool "Sensor qs_s5k4e1 (Samsung 5M)" + depends on MSM_CAMERA && ARCH_MSM8X60 && !MSM_CAMERA_V4L2 + default y + ---help--- + Samsung 5M with Autofocus + +config S5K4E1 + bool "Sensor Sensor s5k4e1 (Samsung 5M)" + depends on MSM_CAMERA + default n + ---help--- + Support for S5k4E1 samsung sensor driver. + It is a Bayer 5MP sensor with auto focus and it supports + two mipi lanes, required for msm7x2xA platform. + Say Y here if this is msm7x2xA variant platform. + +config MSM_CAMERA_FLASH_SC628A + bool "Qualcomm MSM camera sc628a flash support" + depends on MSM_CAMERA + default n + ---help--- + Enable support for LED flash for msm camera. + It is a samtech charge pump flash driver and it + supports spotlight and flash light modes with + differrent current levels. + +config IMX072 + bool "Sensor imx072 (Sony 5M)" + default n + ---help--- + Support for IMX072 sony sensor driver. + It is a Bayer 5MP sensor with auto focus and it supports + two mipi lanes, required for msm7x2xA platform. + Say Y here if this is msm7x2xA variant platform. + +config OV2720 + bool "Sensor ov2720 (Omnivision 2MP)" + depends on MSM_CAMERA && ARCH_MSM8960 + default y + +config VB6801 + bool "Sensor vb6801" + depends on MSM_CAMERA && !ARCH_MSM8X60 && !MSM_CAMERA_V4L2 + ---help--- + 5M with flash + +config MSM_CAMERA_FLASH + bool "Qualcomm MSM camera flash support" + depends on MSM_CAMERA + default y + ---help--- + Enable support for LED flash for msm camera + +config MSM_CAMERA_SENSOR + bool "Qualcomm MSM camera sensor support" + depends on MSM_CAMERA + default y + +config MSM_GEMINI + tristate "Qualcomm MSM Gemini Jpeg Engine support" + depends on MSM_CAMERA && (ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_MSM8960) + default n + ---help--- + Enable support for Gemini Jpeg Engine + +config MSM_VPE + tristate "Qualcomm MSM Video Pre-processing Engine support" + depends on MSM_CAMERA && (ARCH_MSM7X30 || ARCH_MSM8X60) + default y + ---help--- + Enable support for Video Pre-processing Engine + +config QUP_EXCLUSIVE_TO_CAMERA + bool "QUP exclusive to camera" + depends on MSM_CAMERA + default y + ---help--- + This flag enabled states that QUP + is exclusive to camera. In case this + is disabled, the lvs1 voltage is enabled + by QUP in the board file as QUP is used by + applications other than camera. + diff --git a/drivers/media/video/msm/Makefile b/drivers/media/video/msm/Makefile new file mode 100644 index 00000000000..c36683463e7 --- /dev/null +++ b/drivers/media/video/msm/Makefile @@ -0,0 +1,46 @@ +GCC_VERSION := $(shell $(CONFIG_SHELL) $(PWD)/scripts/gcc-version.sh $(CROSS_COMPILE)gcc) +ifeq ($(GCC_VERSION),0404) +CFLAGS_REMOVE_msm_vfe8x.o = -Wframe-larger-than=1024 +endif + +ifeq ($(CONFIG_MSM_CAMERA_V4L2),y) + obj-$(CONFIG_MSM_CAMERA) += msm_isp.o msm.o msm_mem.o msm_mctl.o +else + obj-$(CONFIG_MSM_CAMERA) += msm_camera.o +endif +obj-$(CONFIG_MSM_CAMERA) += msm_axi_qos.o +obj-$(CONFIG_MSM_CAMERA_FLASH) += flash.o +obj-$(CONFIG_MSM_CAMERA_SENSOR) += msm_sensor.o +obj-$(CONFIG_ARCH_MSM_ARM11) += msm_vfe7x.o msm_io7x.o +obj-$(CONFIG_ARCH_MSM7X27A) += msm_vfe7x27a.o msm_io_7x27a.o +obj-$(CONFIG_ARCH_MSM7X30) += msm_vfe31.o msm_io_vfe31.o msm_vpe1.o +obj-$(CONFIG_ARCH_QSD8X50) += msm_vfe8x.o msm_vfe8x_proc.o msm_io8x.o +obj-$(CONFIG_ARCH_MSM8X60) += msm_vfe31.o msm_io_8x60.o msm_vpe1.o +obj-$(CONFIG_ARCH_MSM8960) += msm_io_8960.o msm_ispif.o msm_vfe32.o msm_vpe1.o +obj-$(CONFIG_MT9T013) += mt9t013.o mt9t013_reg.o +obj-$(CONFIG_SN12M0PZ) += sn12m0pz.o sn12m0pz_reg.o +obj-$(CONFIG_MT9P012) += mt9p012_reg.o +obj-$(CONFIG_MSM_CAMERA_AF_FOXCONN) += mt9p012_fox.o +obj-$(CONFIG_MSM_CAMERA_AF_BAM) += mt9p012_bam.o +obj-$(CONFIG_MT9P012_KM) += mt9p012_km.o mt9p012_km_reg.o +obj-$(CONFIG_MT9E013) += mt9e013.o mt9e013_reg.o +obj-$(CONFIG_S5K3E2FX) += s5k3e2fx.o +obj-$(CONFIG_S5K4E1) += s5k4e1.o s5k4e1_reg.o +#FIXME: Merge the two ifeq causes VX6953 preview not coming up. +ifeq ($(CONFIG_MSM_CAMERA_V4L2),y) + obj-$(CONFIG_VX6953) += vx6953_v4l2.o vx6953_reg_v4l2.o + obj-$(CONFIG_IMX074) += imx074_v4l2.o imx074_reg.o +else + obj-$(CONFIG_VX6953) += vx6953.o vx6953_reg.o + obj-$(CONFIG_IMX074) += imx074.o imx074_reg.o +endif +obj-$(CONFIG_QS_S5K4E1) += qs_s5k4e1.o qs_s5k4e1_reg.o +obj-$(CONFIG_VB6801) += vb6801.o +obj-$(CONFIG_IMX072) += imx072.o imx072_reg.o +obj-$(CONFIG_OV2720) += ov2720.o +obj-$(CONFIG_WEBCAM_OV9726) += ov9726.o ov9726_reg.o +obj-$(CONFIG_WEBCAM_OV7692) += ov7692.o +obj-$(CONFIG_MT9D112) += mt9d112.o mt9d112_reg.o + +obj-$(CONFIG_MT9D113) += mt9d113.o mt9d113_reg.o +obj-$(CONFIG_MSM_GEMINI) += msm_gemini_dev.o msm_gemini_sync.o msm_gemini_core.o msm_gemini_hw.o msm_gemini_platform.o diff --git a/drivers/media/video/msm/flash.c b/drivers/media/video/msm/flash.c new file mode 100644 index 00000000000..cd81125da1d --- /dev/null +++ b/drivers/media/video/msm/flash.c @@ -0,0 +1,563 @@ + +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct timer_list timer_flash; + +enum msm_cam_flash_stat{ + MSM_CAM_FLASH_OFF, + MSM_CAM_FLASH_ON, +}; + +#if defined CONFIG_MSM_CAMERA_FLASH_SC628A +static struct sc628a_work_t *sc628a_flash; +static struct i2c_client *sc628a_client; +static DECLARE_WAIT_QUEUE_HEAD(sc628a_wait_queue); + +struct sc628a_work_t { + struct work_struct work; +}; + +static const struct i2c_device_id sc628a_i2c_id[] = { + {"sc628a", 0}, + { } +}; + +static int32_t sc628a_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(sc628a_client->adapter, msg, 1) < 0) { + pr_err("sc628a_i2c_txdata faild 0x%x\n", saddr); + return -EIO; + } + + return 0; +} + +static int32_t sc628a_i2c_write_b_flash(uint8_t waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[2]; + + memset(buf, 0, sizeof(buf)); + buf[0] = waddr; + buf[1] = bdata; + + rc = sc628a_i2c_txdata(sc628a_client->addr, buf, 2); + if (rc < 0) { + pr_err("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + return rc; +} + +static int sc628a_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&sc628a_wait_queue); + return 0; +} + +static int sc628a_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("sc628a_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("i2c_check_functionality failed\n"); + goto probe_failure; + } + + sc628a_flash = kzalloc(sizeof(struct sc628a_work_t), GFP_KERNEL); + if (!sc628a_flash) { + pr_err("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, sc628a_flash); + sc628a_init_client(client); + sc628a_client = client; + + msleep(50); + + CDBG("sc628a_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + pr_err("sc628a_probe failed! rc = %d\n", rc); + return rc; +} + +static struct i2c_driver sc628a_i2c_driver = { + .id_table = sc628a_i2c_id, + .probe = sc628a_i2c_probe, + .remove = __exit_p(sc628a_i2c_remove), + .driver = { + .name = "sc628a", + }, +}; +#endif + +static int config_flash_gpio_table(enum msm_cam_flash_stat stat, + struct msm_camera_sensor_strobe_flash_data *sfdata) +{ + int rc = 0, i = 0; + int msm_cam_flash_gpio_tbl[][2] = { + {sfdata->flash_trigger, 1}, + {sfdata->flash_charge, 1}, + {sfdata->flash_charge_done, 0} + }; + + if (stat == MSM_CAM_FLASH_ON) { + for (i = 0; i < ARRAY_SIZE(msm_cam_flash_gpio_tbl); i++) { + rc = gpio_request(msm_cam_flash_gpio_tbl[i][0], + "CAM_FLASH_GPIO"); + if (unlikely(rc < 0)) { + pr_err("%s not able to get gpio\n", __func__); + for (i--; i >= 0; i--) + gpio_free(msm_cam_flash_gpio_tbl[i][0]); + break; + } + if (msm_cam_flash_gpio_tbl[i][1]) + gpio_direction_output( + msm_cam_flash_gpio_tbl[i][0], 0); + else + gpio_direction_input( + msm_cam_flash_gpio_tbl[i][0]); + } + } else { + for (i = 0; i < ARRAY_SIZE(msm_cam_flash_gpio_tbl); i++) { + gpio_direction_input(msm_cam_flash_gpio_tbl[i][0]); + gpio_free(msm_cam_flash_gpio_tbl[i][0]); + } + } + return rc; +} + +int msm_camera_flash_current_driver( + struct msm_camera_sensor_flash_current_driver *current_driver, + unsigned led_state) +{ + int rc = 0; +#if defined CONFIG_LEDS_PMIC8058 + int idx; + const struct pmic8058_leds_platform_data *driver_channel = + current_driver->driver_channel; + int num_leds = driver_channel->num_leds; + + CDBG("%s: led_state = %d\n", __func__, led_state); + + /* Evenly distribute current across all channels */ + switch (led_state) { + case MSM_CAMERA_LED_OFF: + for (idx = 0; idx < num_leds; ++idx) { + rc = pm8058_set_led_current( + driver_channel->leds[idx].id, 0); + if (rc < 0) + pr_err( + "%s: FAIL name = %s, rc = %d\n", + __func__, + driver_channel->leds[idx].name, + rc); + } + break; + + case MSM_CAMERA_LED_LOW: + for (idx = 0; idx < num_leds; ++idx) { + rc = pm8058_set_led_current( + driver_channel->leds[idx].id, + current_driver->low_current/num_leds); + if (rc < 0) + pr_err( + "%s: FAIL name = %s, rc = %d\n", + __func__, + driver_channel->leds[idx].name, + rc); + } + break; + + case MSM_CAMERA_LED_HIGH: + for (idx = 0; idx < num_leds; ++idx) { + rc = pm8058_set_led_current( + driver_channel->leds[idx].id, + current_driver->high_current/num_leds); + if (rc < 0) + pr_err( + "%s: FAIL name = %s, rc = %d\n", + __func__, + driver_channel->leds[idx].name, + rc); + } + break; + + default: + rc = -EFAULT; + break; + } + CDBG("msm_camera_flash_led_pmic8058: return %d\n", rc); +#endif /* CONFIG_LEDS_PMIC8058 */ +#if defined CONFIG_MSM_CAMERA_FLASH_SC628A + if (!sc628a_client) { + rc = i2c_add_driver(&sc628a_i2c_driver); + if (rc < 0 || sc628a_client == NULL) { + rc = -ENOTSUPP; + pr_err("I2C add driver failed"); + return rc; + } + rc = gpio_request(current_driver->led1, "sc628a"); + if (!rc) { + gpio_direction_output(current_driver->led1, 0); + gpio_set_value_cansleep(current_driver->led1, 1); + } else + i2c_del_driver(&sc628a_i2c_driver); + rc = gpio_request(current_driver->led2, "sc628a"); + if (!rc) { + gpio_direction_output(current_driver->led2, 0); + gpio_set_value_cansleep(current_driver->led2, 1); + } else { + i2c_del_driver(&sc628a_i2c_driver); + gpio_free(current_driver->led1); + } + } + switch (led_state) { + case MSM_CAMERA_LED_OFF: + sc628a_i2c_write_b_flash(0x02, 0x0); + break; + case MSM_CAMERA_LED_LOW: + sc628a_i2c_write_b_flash(0x02, 0x06); + break; + case MSM_CAMERA_LED_HIGH: + sc628a_i2c_write_b_flash(0x02, 0x49); + break; + default: + rc = -EFAULT; + break; + } +#endif + + return rc; +} + + +static int msm_camera_flash_pwm( + struct msm_camera_sensor_flash_pwm *pwm, + unsigned led_state) +{ + int rc = 0; + int PWM_PERIOD = USEC_PER_SEC / pwm->freq; + + static struct pwm_device *flash_pwm; + + if (!flash_pwm) { + flash_pwm = pwm_request(pwm->channel, "camera-flash"); + if (flash_pwm == NULL || IS_ERR(flash_pwm)) { + pr_err("%s: FAIL pwm_request(): flash_pwm=%p\n", + __func__, flash_pwm); + flash_pwm = NULL; + return -ENXIO; + } + } + + switch (led_state) { + case MSM_CAMERA_LED_LOW: + rc = pwm_config(flash_pwm, + (PWM_PERIOD/pwm->max_load)*pwm->low_load, + PWM_PERIOD); + if (rc >= 0) + rc = pwm_enable(flash_pwm); + break; + + case MSM_CAMERA_LED_HIGH: + rc = pwm_config(flash_pwm, + (PWM_PERIOD/pwm->max_load)*pwm->high_load, + PWM_PERIOD); + if (rc >= 0) + rc = pwm_enable(flash_pwm); + break; + + case MSM_CAMERA_LED_OFF: + pwm_disable(flash_pwm); + break; + + default: + rc = -EFAULT; + break; + } + + return rc; +} + +int msm_camera_flash_pmic( + struct msm_camera_sensor_flash_pmic *pmic, + unsigned led_state) +{ + int rc = 0; + + switch (led_state) { + case MSM_CAMERA_LED_OFF: + rc = pmic->pmic_set_current(pmic->led_src_1, 0); + if (pmic->num_of_src > 1) + rc = pmic->pmic_set_current(pmic->led_src_2, 0); + break; + + case MSM_CAMERA_LED_LOW: + rc = pmic->pmic_set_current(pmic->led_src_1, + pmic->low_current); + if (pmic->num_of_src > 1) + rc = pmic->pmic_set_current(pmic->led_src_2, 0); + break; + + case MSM_CAMERA_LED_HIGH: + rc = pmic->pmic_set_current(pmic->led_src_1, + pmic->high_current); + if (pmic->num_of_src > 1) + rc = pmic->pmic_set_current(pmic->led_src_2, + pmic->high_current); + break; + + default: + rc = -EFAULT; + break; + } + CDBG("flash_set_led_state: return %d\n", rc); + + return rc; +} + +int32_t msm_camera_flash_set_led_state( + struct msm_camera_sensor_flash_data *fdata, unsigned led_state) +{ + int32_t rc; + + CDBG("flash_set_led_state: %d flash_sr_type=%d\n", led_state, + fdata->flash_src->flash_sr_type); + + if (fdata->flash_type != MSM_CAMERA_FLASH_LED) + return -ENODEV; + + switch (fdata->flash_src->flash_sr_type) { + case MSM_CAMERA_FLASH_SRC_PMIC: + rc = msm_camera_flash_pmic(&fdata->flash_src->_fsrc.pmic_src, + led_state); + break; + + case MSM_CAMERA_FLASH_SRC_PWM: + rc = msm_camera_flash_pwm(&fdata->flash_src->_fsrc.pwm_src, + led_state); + break; + + case MSM_CAMERA_FLASH_SRC_CURRENT_DRIVER: + rc = msm_camera_flash_current_driver( + &fdata->flash_src->_fsrc.current_driver_src, + led_state); + break; + + default: + rc = -ENODEV; + break; + } + + return rc; +} + +static int msm_strobe_flash_xenon_charge(int32_t flash_charge, + int32_t charge_enable, uint32_t flash_recharge_duration) +{ + gpio_set_value_cansleep(flash_charge, charge_enable); + if (charge_enable) { + timer_flash.expires = jiffies + + msecs_to_jiffies(flash_recharge_duration); + /* add timer for the recharge */ + if (!timer_pending(&timer_flash)) + add_timer(&timer_flash); + } else + del_timer_sync(&timer_flash); + return 0; +} + +static void strobe_flash_xenon_recharge_handler(unsigned long data) +{ + unsigned long flags; + struct msm_camera_sensor_strobe_flash_data *sfdata = + (struct msm_camera_sensor_strobe_flash_data *)data; + + spin_lock_irqsave(&sfdata->timer_lock, flags); + msm_strobe_flash_xenon_charge(sfdata->flash_charge, 1, + sfdata->flash_recharge_duration); + spin_unlock_irqrestore(&sfdata->timer_lock, flags); + + return; +} + +static irqreturn_t strobe_flash_charge_ready_irq(int irq_num, void *data) +{ + struct msm_camera_sensor_strobe_flash_data *sfdata = + (struct msm_camera_sensor_strobe_flash_data *)data; + + /* put the charge signal to low */ + gpio_set_value_cansleep(sfdata->flash_charge, 0); + + return IRQ_HANDLED; +} + +static int msm_strobe_flash_xenon_init( + struct msm_camera_sensor_strobe_flash_data *sfdata) +{ + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&sfdata->spin_lock, flags); + if (!sfdata->state) { + + rc = config_flash_gpio_table(MSM_CAM_FLASH_ON, sfdata); + if (rc < 0) { + pr_err("%s: gpio_request failed\n", __func__); + goto go_out; + } + rc = request_irq(sfdata->irq, strobe_flash_charge_ready_irq, + IRQF_TRIGGER_RISING, "charge_ready", sfdata); + if (rc < 0) { + pr_err("%s: request_irq failed %d\n", __func__, rc); + goto go_out; + } + + spin_lock_init(&sfdata->timer_lock); + /* setup timer */ + init_timer(&timer_flash); + timer_flash.function = strobe_flash_xenon_recharge_handler; + timer_flash.data = (unsigned long)sfdata; + } + sfdata->state++; +go_out: + spin_unlock_irqrestore(&sfdata->spin_lock, flags); + + return rc; +} + +static int msm_strobe_flash_xenon_release +(struct msm_camera_sensor_strobe_flash_data *sfdata, int32_t final_release) +{ + unsigned long flags; + + spin_lock_irqsave(&sfdata->spin_lock, flags); + if (sfdata->state > 0) { + if (final_release) + sfdata->state = 0; + else + sfdata->state--; + + if (!sfdata->state) { + free_irq(sfdata->irq, sfdata); + config_flash_gpio_table(MSM_CAM_FLASH_OFF, sfdata); + if (timer_pending(&timer_flash)) + del_timer_sync(&timer_flash); + } + } + spin_unlock_irqrestore(&sfdata->spin_lock, flags); + return 0; +} + +static void msm_strobe_flash_xenon_fn_init + (struct msm_strobe_flash_ctrl *strobe_flash_ptr) +{ + strobe_flash_ptr->strobe_flash_init = + msm_strobe_flash_xenon_init; + strobe_flash_ptr->strobe_flash_charge = + msm_strobe_flash_xenon_charge; + strobe_flash_ptr->strobe_flash_release = + msm_strobe_flash_xenon_release; +} + +int msm_strobe_flash_init(struct msm_sync *sync, uint32_t sftype) +{ + int rc = 0; + switch (sftype) { + case MSM_CAMERA_STROBE_FLASH_XENON: + if (sync->sdata->strobe_flash_data) { + msm_strobe_flash_xenon_fn_init(&sync->sfctrl); + rc = sync->sfctrl.strobe_flash_init( + sync->sdata->strobe_flash_data); + } else + return -ENODEV; + break; + default: + rc = -ENODEV; + } + return rc; +} + +int msm_strobe_flash_ctrl(struct msm_camera_sensor_strobe_flash_data *sfdata, + struct strobe_flash_ctrl_data *strobe_ctrl) +{ + int rc = 0; + switch (strobe_ctrl->type) { + case STROBE_FLASH_CTRL_INIT: + if (!sfdata) + return -ENODEV; + rc = msm_strobe_flash_xenon_init(sfdata); + break; + case STROBE_FLASH_CTRL_CHARGE: + rc = msm_strobe_flash_xenon_charge(sfdata->flash_charge, + strobe_ctrl->charge_en, + sfdata->flash_recharge_duration); + break; + case STROBE_FLASH_CTRL_RELEASE: + if (sfdata) + rc = msm_strobe_flash_xenon_release(sfdata, 0); + break; + default: + pr_err("Invalid Strobe Flash State\n"); + rc = -EINVAL; + } + return rc; +} + +int msm_flash_ctrl(struct msm_camera_sensor_info *sdata, + struct flash_ctrl_data *flash_info) +{ + int rc = 0; + switch (flash_info->flashtype) { + case LED_FLASH: + rc = msm_camera_flash_set_led_state(sdata->flash_data, + flash_info->ctrl_data.led_state); + break; + case STROBE_FLASH: + rc = msm_strobe_flash_ctrl(sdata->strobe_flash_data, + &(flash_info->ctrl_data.strobe_ctrl)); + break; + default: + pr_err("Invalid Flash MODE\n"); + rc = -EINVAL; + } + return rc; +} diff --git a/drivers/media/video/msm/imx072.c b/drivers/media/video/msm/imx072.c new file mode 100644 index 00000000000..d9ee051330c --- /dev/null +++ b/drivers/media/video/msm/imx072.c @@ -0,0 +1,1164 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "imx072.h" + +/* SENSOR REGISTER DEFINES */ +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD_OFF 0x00 +#define GROUPED_PARAMETER_HOLD 0x01 +/* Integration Time */ +#define REG_COARSE_INTEGRATION_TIME 0x0202 +/* Gain */ +#define REG_GLOBAL_GAIN 0x0204 + +/* PLL registers */ +#define REG_FRAME_LENGTH_LINES 0x0340 +#define REG_LINE_LENGTH_PCK 0x0342 + +/* 16bit address - 8 bit context register structure */ +#define Q8 0x00000100 +#define Q10 0x00000400 +#define IMX072_MASTER_CLK_RATE 24000000 +#define IMX072_OFFSET 3 + +/* AF Total steps parameters */ +#define IMX072_AF_I2C_ADDR 0x18 +#define IMX072_TOTAL_STEPS_NEAR_TO_FAR 30 + +static uint16_t imx072_step_position_table[IMX072_TOTAL_STEPS_NEAR_TO_FAR+1]; +static uint16_t imx072_nl_region_boundary1; +static uint16_t imx072_nl_region_code_per_step1; +static uint16_t imx072_l_region_code_per_step = 12; +static uint16_t imx072_sw_damping_time_wait = 8; +static uint16_t imx072_af_initial_code = 350; +static uint16_t imx072_damping_threshold = 10; + +struct imx072_work_t { + struct work_struct work; +}; + +static struct imx072_work_t *imx072_sensorw; +static struct i2c_client *imx072_client; + +struct imx072_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + + uint32_t sensormode; + uint32_t fps_divider;/* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider;/* init to 1 * 0x00000400 */ + uint16_t fps; + + uint16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + + enum imx072_resolution_t prev_res; + enum imx072_resolution_t pict_res; + enum imx072_resolution_t curr_res; + enum imx072_test_mode_t set_test; + enum imx072_cam_mode_t cam_mode; +}; + +static uint16_t prev_line_length_pck; +static uint16_t prev_frame_length_lines; +static uint16_t snap_line_length_pck; +static uint16_t snap_frame_length_lines; + +static bool CSI_CONFIG; +static struct imx072_ctrl_t *imx072_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(imx072_wait_queue); +DEFINE_MUTEX(imx072_mut); + +#ifdef CONFIG_DEBUG_FS +static int cam_debug_init(void); +static struct dentry *debugfs_base; +#endif + +static int imx072_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + if (i2c_transfer(imx072_client->adapter, msgs, 2) < 0) { + pr_err("imx072_i2c_rxdata faild 0x%x\n", saddr); + return -EIO; + } + return 0; +} + +static int32_t imx072_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(imx072_client->adapter, msg, 1) < 0) { + pr_err("imx072_i2c_txdata faild 0x%x\n", saddr); + return -EIO; + } + + return 0; +} + +static int32_t imx072_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = imx072_i2c_rxdata(imx072_client->addr>>1, buf, rlen); + if (rc < 0) { + pr_err("imx072_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + CDBG("imx072_i2c_read 0x%x val = 0x%x!\n", raddr, *rdata); + return rc; +} + +static int32_t imx072_i2c_write_w_sensor(unsigned short waddr, + uint16_t wdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[4]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, wdata); + rc = imx072_i2c_txdata(imx072_client->addr>>1, buf, 4); + if (rc < 0) { + pr_err("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + } + return rc; +} + +static int32_t imx072_i2c_write_b_sensor(unsigned short waddr, + uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = imx072_i2c_txdata(imx072_client->addr>>1, buf, 3); + if (rc < 0) + pr_err("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + return rc; +} + +static int32_t imx072_i2c_write_b_af(uint8_t msb, uint8_t lsb) +{ + int32_t rc = -EFAULT; + unsigned char buf[2]; + + buf[0] = msb; + buf[1] = lsb; + rc = imx072_i2c_txdata(IMX072_AF_I2C_ADDR>>1, buf, 2); + if (rc < 0) + pr_err("af_i2c_write faield msb = 0x%x lsb = 0x%x", + msb, lsb); + return rc; +} + +static int32_t imx072_i2c_write_w_table(struct imx072_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < num; i++) { + rc = imx072_i2c_write_b_sensor(reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +static void imx072_group_hold_on(void) +{ + imx072_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); +} + +static void imx072_group_hold_off(void) +{ + imx072_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF); +} + +static void imx072_start_stream(void) +{ + imx072_i2c_write_b_sensor(0x0100, 0x01); +} + +static void imx072_stop_stream(void) +{ + imx072_i2c_write_b_sensor(0x0100, 0x00); +} + +static void imx072_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider, d1, d2; + + d1 = prev_frame_length_lines * 0x00000400 / snap_frame_length_lines; + d2 = prev_line_length_pck * 0x00000400 / snap_line_length_pck; + divider = d1 * d2 / 0x400; + + /*Verify PCLK settings and frame sizes.*/ + *pfps = (uint16_t) (fps * divider / 0x400); +} + +static uint16_t imx072_get_prev_lines_pf(void) +{ + return prev_frame_length_lines; +} + +static uint16_t imx072_get_prev_pixels_pl(void) +{ + return prev_line_length_pck; +} + +static uint16_t imx072_get_pict_lines_pf(void) +{ + return snap_frame_length_lines; +} + +static uint16_t imx072_get_pict_pixels_pl(void) +{ + return snap_line_length_pck; +} + +static uint32_t imx072_get_pict_max_exp_lc(void) +{ + return snap_frame_length_lines * 24; +} + +static int32_t imx072_set_fps(struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + total_lines_per_frame = (uint16_t) + ((prev_frame_length_lines * + imx072_ctrl->fps_divider)/0x400); + imx072_ctrl->fps_divider = fps->fps_div; + imx072_ctrl->pict_fps_divider = fps->pict_fps_div; + + imx072_group_hold_on(); + rc = imx072_i2c_write_w_sensor(REG_FRAME_LENGTH_LINES, + total_lines_per_frame); + imx072_group_hold_off(); + return rc; +} + +static int32_t imx072_write_exp_gain(uint16_t gain, uint32_t line) +{ + uint32_t fl_lines = 0; + uint8_t offset; + int32_t rc = 0; + if (imx072_ctrl->curr_res == imx072_ctrl->prev_res) + fl_lines = prev_frame_length_lines; + else if (imx072_ctrl->curr_res == imx072_ctrl->pict_res) + fl_lines = snap_frame_length_lines; + line = (line * imx072_ctrl->fps_divider) / Q10; + offset = IMX072_OFFSET; + if (line > (fl_lines - offset)) + fl_lines = line + offset; + + imx072_group_hold_on(); + rc = imx072_i2c_write_w_sensor(REG_FRAME_LENGTH_LINES, fl_lines); + rc = imx072_i2c_write_w_sensor(REG_COARSE_INTEGRATION_TIME, line); + rc = imx072_i2c_write_w_sensor(REG_GLOBAL_GAIN, gain); + imx072_group_hold_off(); + return rc; +} + +static int32_t imx072_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + rc = imx072_write_exp_gain(gain, line); + return rc; +} + +static int32_t imx072_sensor_setting(int update_type, int rt) +{ + + int32_t rc = 0; + struct msm_camera_csi_params imx072_csi_params; + + imx072_stop_stream(); + msleep(30); + if (update_type == REG_INIT) { + msleep(20); + CSI_CONFIG = 0; + imx072_i2c_write_w_table(imx072_regs.rec_settings, + imx072_regs.rec_size); + } else if (update_type == UPDATE_PERIODIC) { +#ifdef CONFIG_DEBUG_FS + cam_debug_init(); +#endif + msleep(20); + if (!CSI_CONFIG) { + imx072_csi_params.lane_cnt = 2; + imx072_csi_params.data_format = CSI_10BIT; + imx072_csi_params.lane_assign = 0xe4; + imx072_csi_params.dpcm_scheme = 0; + imx072_csi_params.settle_cnt = 0x18; + msm_camio_vfe_clk_rate_set(192000000); + rc = msm_camio_csi_config(&imx072_csi_params); + msleep(100); + CSI_CONFIG = 1; + } + imx072_i2c_write_w_table( + imx072_regs.conf_array[rt].conf, + imx072_regs.conf_array[rt].size); + imx072_start_stream(); + msleep(30); + } + return rc; +} + +static int32_t imx072_video_config(int mode) +{ + + int32_t rc = 0; + /* change sensor resolution if needed */ + if (imx072_sensor_setting(UPDATE_PERIODIC, + imx072_ctrl->prev_res) < 0) + return rc; + + imx072_ctrl->curr_res = imx072_ctrl->prev_res; + imx072_ctrl->sensormode = mode; + return rc; +} + +static int32_t imx072_snapshot_config(int mode) +{ + int32_t rc = 0; + /*change sensor resolution if needed */ + if (imx072_ctrl->curr_res != imx072_ctrl->pict_res) { + if (imx072_sensor_setting(UPDATE_PERIODIC, + imx072_ctrl->pict_res) < 0) + return rc; + } + + imx072_ctrl->curr_res = imx072_ctrl->pict_res; + imx072_ctrl->sensormode = mode; + return rc; +} + +static int32_t imx072_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + /* change sensor resolution if needed */ + if (imx072_ctrl->curr_res != imx072_ctrl->pict_res) { + if (imx072_sensor_setting(UPDATE_PERIODIC, + imx072_ctrl->pict_res) < 0) + return rc; + } + + imx072_ctrl->curr_res = imx072_ctrl->pict_res; + imx072_ctrl->sensormode = mode; + return rc; +} + +static int32_t imx072_mode_init(int mode, struct sensor_init_cfg init_info) +{ + int32_t rc = 0; + CDBG("%s: %d\n", __func__, __LINE__); + if (mode != imx072_ctrl->cam_mode) { + imx072_ctrl->prev_res = init_info.prev_res; + imx072_ctrl->pict_res = init_info.pict_res; + imx072_ctrl->cam_mode = mode; + + prev_frame_length_lines = + imx072_regs.conf_array[imx072_ctrl->prev_res]. + conf[IMX072_FRAME_LENGTH_LINES_HI].wdata << 8 | + imx072_regs.conf_array[imx072_ctrl->prev_res]. + conf[IMX072_FRAME_LENGTH_LINES_LO].wdata; + prev_line_length_pck = + imx072_regs.conf_array[imx072_ctrl->prev_res]. + conf[IMX072_LINE_LENGTH_PCK_HI].wdata << 8 | + imx072_regs.conf_array[imx072_ctrl->prev_res]. + conf[IMX072_LINE_LENGTH_PCK_LO].wdata; + snap_frame_length_lines = + imx072_regs.conf_array[imx072_ctrl->pict_res]. + conf[IMX072_FRAME_LENGTH_LINES_HI].wdata << 8 | + imx072_regs.conf_array[imx072_ctrl->pict_res]. + conf[IMX072_FRAME_LENGTH_LINES_LO].wdata; + snap_line_length_pck = + imx072_regs.conf_array[imx072_ctrl->pict_res]. + conf[IMX072_LINE_LENGTH_PCK_HI].wdata << 8 | + imx072_regs.conf_array[imx072_ctrl->pict_res]. + conf[IMX072_LINE_LENGTH_PCK_LO].wdata; + + rc = imx072_sensor_setting(REG_INIT, + imx072_ctrl->prev_res); + } + return rc; +} + +static int32_t imx072_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + imx072_ctrl->prev_res = res; + rc = imx072_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + imx072_ctrl->pict_res = res; + rc = imx072_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + imx072_ctrl->pict_res = res; + rc = imx072_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +#define DIV_CEIL(x, y) ((x/y + ((x%y) ? 1 : 0))) +static int32_t imx072_move_focus(int direction, + int32_t num_steps) +{ + int32_t rc = 0; + int16_t step_direction, dest_lens_position, dest_step_position; + uint8_t code_val_msb, code_val_lsb; + int16_t next_lens_position, target_dist, small_step; + + if (direction == MOVE_NEAR) + step_direction = 1; + else if (direction == MOVE_FAR) + step_direction = -1; + else { + pr_err("Illegal focus direction\n"); + return -EINVAL; + } + dest_step_position = imx072_ctrl->curr_step_pos + + (step_direction * num_steps); + + if (dest_step_position < 0) + dest_step_position = 0; + else if (dest_step_position > IMX072_TOTAL_STEPS_NEAR_TO_FAR) + dest_step_position = IMX072_TOTAL_STEPS_NEAR_TO_FAR; + + if (dest_step_position == imx072_ctrl->curr_step_pos) { + CDBG("imx072 same position No-Move exit\n"); + return rc; + } + CDBG("%s Index = [%d]\n", __func__, dest_step_position); + + dest_lens_position = imx072_step_position_table[dest_step_position]; + CDBG("%s lens_position value = %d\n", __func__, dest_lens_position); + target_dist = step_direction * (dest_lens_position - + imx072_ctrl->curr_lens_pos); + if (step_direction < 0 && (target_dist >= + (imx072_step_position_table[imx072_damping_threshold] + - imx072_af_initial_code))) { + small_step = DIV_CEIL(target_dist, 10); + imx072_sw_damping_time_wait = 30; + } else { + small_step = DIV_CEIL(target_dist, 4); + imx072_sw_damping_time_wait = 20; + } + + CDBG("%s: small_step:%d, wait_time:%d\n", __func__, small_step, + imx072_sw_damping_time_wait); + for (next_lens_position = imx072_ctrl->curr_lens_pos + + (step_direction * small_step); + (step_direction * next_lens_position) <= + (step_direction * dest_lens_position); + next_lens_position += (step_direction * small_step)) { + + code_val_msb = ((next_lens_position & 0x03F0) >> 4); + code_val_lsb = ((next_lens_position & 0x000F) << 4); + CDBG("position value = %d\n", next_lens_position); + CDBG("movefocus vcm_msb = %d\n", code_val_msb); + CDBG("movefocus vcm_lsb = %d\n", code_val_lsb); + rc = imx072_i2c_write_b_af(code_val_msb, code_val_lsb); + if (rc < 0) { + pr_err("imx072_move_focus failed writing i2c\n"); + return rc; + } + imx072_ctrl->curr_lens_pos = next_lens_position; + usleep(imx072_sw_damping_time_wait*100); + } + if (imx072_ctrl->curr_lens_pos != dest_lens_position) { + code_val_msb = ((dest_lens_position & 0x03F0) >> 4); + code_val_lsb = ((dest_lens_position & 0x000F) << 4); + CDBG("position value = %d\n", dest_lens_position); + CDBG("movefocus vcm_msb = %d\n", code_val_msb); + CDBG("movefocus vcm_lsb = %d\n", code_val_lsb); + rc = imx072_i2c_write_b_af(code_val_msb, code_val_lsb); + if (rc < 0) { + pr_err("imx072_move_focus failed writing i2c\n"); + return rc; + } + usleep(imx072_sw_damping_time_wait * 100); + } + imx072_ctrl->curr_lens_pos = dest_lens_position; + imx072_ctrl->curr_step_pos = dest_step_position; + return rc; + +} + +static int32_t imx072_init_focus(void) +{ + uint8_t i; + int32_t rc = 0; + + imx072_step_position_table[0] = imx072_af_initial_code; + for (i = 1; i <= IMX072_TOTAL_STEPS_NEAR_TO_FAR; i++) { + if (i <= imx072_nl_region_boundary1) + imx072_step_position_table[i] = + imx072_step_position_table[i-1] + + imx072_nl_region_code_per_step1; + else + imx072_step_position_table[i] = + imx072_step_position_table[i-1] + + imx072_l_region_code_per_step; + + if (imx072_step_position_table[i] > 1023) + imx072_step_position_table[i] = 1023; + } + imx072_ctrl->curr_lens_pos = 0; + + return rc; +} + +static int32_t imx072_set_default_focus(void) +{ + int32_t rc = 0; + uint8_t code_val_msb, code_val_lsb; + int16_t dest_lens_position = 0; + + CDBG("%s Index = [%d]\n", __func__, 0); + if (imx072_ctrl->curr_step_pos != 0) + rc = imx072_move_focus(MOVE_FAR, + imx072_ctrl->curr_step_pos); + else { + dest_lens_position = imx072_af_initial_code; + code_val_msb = ((dest_lens_position & 0x03F0) >> 4); + code_val_lsb = ((dest_lens_position & 0x000F) << 4); + + CDBG("position value = %d\n", dest_lens_position); + CDBG("movefocus vcm_msb = %d\n", code_val_msb); + CDBG("movefocus vcm_lsb = %d\n", code_val_lsb); + rc = imx072_i2c_write_b_af(code_val_msb, code_val_lsb); + if (rc < 0) { + pr_err("imx072_set_default_focus failed writing i2c\n"); + return rc; + } + + imx072_ctrl->curr_lens_pos = dest_lens_position; + imx072_ctrl->curr_step_pos = 0; + + } + usleep(5000); + return rc; +} + +static int32_t imx072_af_power_down(void) +{ + int32_t rc = 0; + int32_t i = 0; + int16_t dest_lens_position = imx072_af_initial_code; + + if (imx072_ctrl->curr_lens_pos != 0) { + rc = imx072_set_default_focus(); + CDBG("%s after imx072_set_default_focus\n", __func__); + msleep(40); + /*to avoid the sound during the power off. + brings the actuator to mechanical infinity gradually.*/ + for (i = 0; i < IMX072_TOTAL_STEPS_NEAR_TO_FAR; i++) { + dest_lens_position = dest_lens_position - + (imx072_af_initial_code / + IMX072_TOTAL_STEPS_NEAR_TO_FAR); + CDBG("position value = %d\n", dest_lens_position); + rc = imx072_i2c_write_b_af( + ((dest_lens_position & 0x03F0) >> 4), + ((dest_lens_position & 0x000F) << 4)); + CDBG("count = %d\n", i); + msleep(20); + if (rc < 0) { + pr_err("imx072_set_default_focus failed writing i2c\n"); + return rc; + } + } + rc = imx072_i2c_write_b_af(0x00, 00); + msleep(40); + } + rc = imx072_i2c_write_b_af(0x80, 00); + return rc; +} + +static int32_t imx072_power_down(void) +{ + int32_t rc = 0; + + rc = imx072_af_power_down(); + return rc; +} + +static int imx072_probe_init_done(const struct msm_camera_sensor_info *data) +{ + pr_err("probe done\n"); + gpio_free(data->sensor_reset); + return 0; +} + +static int imx072_probe_init_sensor( + const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + uint16_t chipid = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + rc = gpio_request(data->sensor_reset, "imx072"); + CDBG(" imx072_probe_init_sensor\n"); + if (!rc) { + pr_err("sensor_reset = %d\n", rc); + gpio_direction_output(data->sensor_reset, 0); + msleep(50); + gpio_set_value_cansleep(data->sensor_reset, 1); + msleep(20); + } else { + goto init_probe_done; + } + + CDBG(" imx072_probe_init_sensor is called\n"); + rc = imx072_i2c_read(0x0, &chipid, 2); + CDBG("ID: %d\n", chipid); + /* 4. Compare sensor ID to IMX072 ID: */ + if (chipid != 0x0045) { + rc = -ENODEV; + pr_err("imx072_probe_init_sensor chip id doesnot match\n"); + goto init_probe_fail; + } + goto init_probe_done; +init_probe_fail: + pr_err(" imx072_probe_init_sensor fails\n"); + gpio_set_value_cansleep(data->sensor_reset, 0); + imx072_probe_init_done(data); +init_probe_done: + pr_err(" imx072_probe_init_sensor finishes\n"); + return rc; +} + +int imx072_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + imx072_ctrl = kzalloc(sizeof(struct imx072_ctrl_t), GFP_KERNEL); + if (!imx072_ctrl) { + pr_err("imx072_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + imx072_ctrl->fps_divider = 1 * 0x00000400; + imx072_ctrl->pict_fps_divider = 1 * 0x00000400; + imx072_ctrl->set_test = TEST_OFF; + imx072_ctrl->cam_mode = MODE_INVALID; + + if (data) + imx072_ctrl->sensordata = data; + if (rc < 0) { + pr_err("Calling imx072_sensor_open_init fail1\n"); + return rc; + } + CDBG("%s: %d\n", __func__, __LINE__); + /* enable mclk first */ + msm_camio_clk_rate_set(IMX072_MASTER_CLK_RATE); + rc = imx072_probe_init_sensor(data); + if (rc < 0) + goto init_fail; + + imx072_init_focus(); + imx072_ctrl->fps = 30*Q8; + if (rc < 0) { + gpio_set_value_cansleep(data->sensor_reset, 0); + goto init_fail; + } else + goto init_done; +init_fail: + pr_err("init_fail\n"); + imx072_probe_init_done(data); +init_done: + pr_err("init_done\n"); + return rc; +} + +static int imx072_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&imx072_wait_queue); + return 0; +} + +static const struct i2c_device_id imx072_i2c_id[] = { + {"imx072", 0}, + { } +}; + +static int imx072_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("imx072_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("i2c_check_functionality failed\n"); + goto probe_failure; + } + + imx072_sensorw = kzalloc(sizeof(struct imx072_work_t), + GFP_KERNEL); + if (!imx072_sensorw) { + pr_err("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, imx072_sensorw); + imx072_init_client(client); + imx072_client = client; + + msleep(50); + + CDBG("imx072_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + pr_err("imx072_probe failed! rc = %d\n", rc); + return rc; +} + +static int imx072_send_wb_info(struct wb_info_cfg *wb) +{ + return 0; + +} + +static int __exit imx072_remove(struct i2c_client *client) +{ + struct imx072_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + imx072_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver imx072_i2c_driver = { + .id_table = imx072_i2c_id, + .probe = imx072_i2c_probe, + .remove = __exit_p(imx072_i2c_remove), + .driver = { + .name = "imx072", + }, +}; + +int imx072_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&imx072_mut); + CDBG("imx072_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + imx072_get_pict_fps( + cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + imx072_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + imx072_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + imx072_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + imx072_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + imx072_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = imx072_set_fps(&(cdata.cfg.fps)); + break; + case CFG_SET_EXP_GAIN: + rc = imx072_write_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_PICT_EXP_GAIN: + rc = imx072_set_pict_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_MODE: + rc = imx072_set_sensor_mode(cdata.mode, cdata.rs); + break; + case CFG_PWR_DOWN: + rc = imx072_power_down(); + break; + case CFG_MOVE_FOCUS: + rc = imx072_move_focus(cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + case CFG_SET_DEFAULT_FOCUS: + imx072_set_default_focus(); + break; + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = IMX072_TOTAL_STEPS_NEAR_TO_FAR; + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_SET_EFFECT: + break; + case CFG_SEND_WB_INFO: + rc = imx072_send_wb_info( + &(cdata.cfg.wb_info)); + break; + case CFG_SENSOR_INIT: + rc = imx072_mode_init(cdata.mode, + cdata.cfg.init_info); + break; + case CFG_SET_LENS_SHADING: + break; + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&imx072_mut); + + return rc; +} + +static int imx072_sensor_release(void) +{ + int rc = -EBADF; + mutex_lock(&imx072_mut); + imx072_power_down(); + gpio_set_value_cansleep(imx072_ctrl->sensordata->sensor_reset, 0); + msleep(20); + gpio_free(imx072_ctrl->sensordata->sensor_reset); + kfree(imx072_ctrl); + imx072_ctrl = NULL; + pr_err("imx072_release completed\n"); + mutex_unlock(&imx072_mut); + + return rc; +} + +static int imx072_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(&imx072_i2c_driver); + if (rc < 0 || imx072_client == NULL) { + rc = -ENOTSUPP; + pr_err("I2C add driver failed"); + goto probe_fail; + } + msm_camio_clk_rate_set(IMX072_MASTER_CLK_RATE); + rc = imx072_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + s->s_init = imx072_sensor_open_init; + s->s_release = imx072_sensor_release; + s->s_config = imx072_sensor_config; + s->s_mount_angle = info->sensor_platform_info->mount_angle; + + gpio_set_value_cansleep(info->sensor_reset, 0); + imx072_probe_init_done(info); + pr_info("imx072_sensor_probe : SUCCESS\n"); + return rc; + +probe_fail: + pr_err("imx072_sensor_probe: SENSOR PROBE FAILS!\n"); + return rc; +} + +static int __imx072_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, imx072_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __imx072_probe, + .driver = { + .name = "msm_camera_imx072", + .owner = THIS_MODULE, + }, +}; + +static int __init imx072_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(imx072_init); +void imx072_exit(void) +{ + i2c_del_driver(&imx072_i2c_driver); +} +MODULE_DESCRIPTION("Aptina 8 MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + +#ifdef CONFIG_DEBUG_FS +static bool streaming = 1; + +static int cam_debug_stream_set(void *data, u64 val) +{ + int rc = 0; + + if (val) { + imx072_start_stream(); + streaming = 1; + } else { + imx072_stop_stream(); + streaming = 0; + } + + return rc; +} + +static int cam_debug_stream_get(void *data, u64 *val) +{ + *val = streaming; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(cam_stream, cam_debug_stream_get, + cam_debug_stream_set, "%llu\n"); + + + +static int imx072_set_af_codestep(void *data, u64 val) +{ + imx072_l_region_code_per_step = val; + imx072_init_focus(); + return 0; +} + +static int imx072_get_af_codestep(void *data, u64 *val) +{ + *val = imx072_l_region_code_per_step; + return 0; +} + +static uint16_t imx072_linear_total_step = IMX072_TOTAL_STEPS_NEAR_TO_FAR; +static int imx072_set_linear_total_step(void *data, u64 val) +{ + imx072_linear_total_step = val; + return 0; +} + +static int imx072_af_linearity_test(void *data, u64 *val) +{ + int i = 0; + + imx072_set_default_focus(); + msleep(3000); + for (i = 0; i < imx072_linear_total_step; i++) { + imx072_move_focus(MOVE_NEAR, 1); + CDBG("moved to index =[%d]\n", i); + msleep(1000); + } + + for (i = 0; i < imx072_linear_total_step; i++) { + imx072_move_focus(MOVE_FAR, 1); + CDBG("moved to index =[%d]\n", i); + msleep(1000); + } + return 0; +} + +static uint16_t imx072_step_val = IMX072_TOTAL_STEPS_NEAR_TO_FAR; +static uint8_t imx072_step_dir = MOVE_NEAR; +static int imx072_af_step_config(void *data, u64 val) +{ + imx072_step_val = val & 0xFFFF; + imx072_step_dir = (val >> 16) & 0x1; + return 0; +} + +static int imx072_af_step(void *data, u64 *val) +{ + int i = 0; + int dir = MOVE_NEAR; + imx072_set_default_focus(); + msleep(3000); + if (imx072_step_dir == 1) + dir = MOVE_FAR; + + for (i = 0; i < imx072_step_val; i += 4) { + imx072_move_focus(dir, 4); + msleep(1000); + } + imx072_set_default_focus(); + msleep(3000); + return 0; +} + +static int imx072_af_set_resolution(void *data, u64 val) +{ + imx072_init_focus(); + return 0; +} + +static int imx072_af_get_resolution(void *data, u64 *val) +{ + *val = 0xFF; + return 0; +} + + + +DEFINE_SIMPLE_ATTRIBUTE(af_codeperstep, imx072_get_af_codestep, + imx072_set_af_codestep, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(af_linear, imx072_af_linearity_test, + imx072_set_linear_total_step, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(af_step, imx072_af_step, + imx072_af_step_config, "%llu\n"); + +DEFINE_SIMPLE_ATTRIBUTE(af_step_res, imx072_af_get_resolution, + imx072_af_set_resolution, "%llu\n"); + +static int cam_debug_init(void) +{ + struct dentry *cam_dir; + debugfs_base = debugfs_create_dir("sensor", NULL); + if (!debugfs_base) + return -ENOMEM; + + cam_dir = debugfs_create_dir("imx072", debugfs_base); + if (!cam_dir) + return -ENOMEM; + + if (!debugfs_create_file("stream", S_IRUGO | S_IWUSR, cam_dir, + NULL, &cam_stream)) + return -ENOMEM; + + if (!debugfs_create_file("af_codeperstep", S_IRUGO | S_IWUSR, cam_dir, + NULL, &af_codeperstep)) + return -ENOMEM; + if (!debugfs_create_file("af_linear", S_IRUGO | S_IWUSR, cam_dir, + NULL, &af_linear)) + return -ENOMEM; + if (!debugfs_create_file("af_step", S_IRUGO | S_IWUSR, cam_dir, + NULL, &af_step)) + return -ENOMEM; + + if (!debugfs_create_file("af_step_res", S_IRUGO | S_IWUSR, cam_dir, + NULL, &af_step_res)) + return -ENOMEM; + + return 0; +} +#endif diff --git a/drivers/media/video/msm/imx072.h b/drivers/media/video/msm/imx072.h new file mode 100644 index 00000000000..e3d279fe353 --- /dev/null +++ b/drivers/media/video/msm/imx072.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef IMX072_H +#define IMX072_H +#include +#include +extern struct imx072_reg imx072_regs; + +struct imx072_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +struct imx072_i2c_conf_array { + struct imx072_i2c_reg_conf *conf; + unsigned short size; +}; + +enum imx072_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum imx072_resolution_t { + QTR_2D_SIZE, + FULL_2D_SIZE, + QTR_3D_SIZE, + FULL_3D_SIZE, + INVALID_SIZE +}; +enum imx072_setting { + RES_PREVIEW, + RES_CAPTURE, + RES_3D_PREVIEW, + RES_3D_CAPTURE +}; +enum imx072_cam_mode_t { + MODE_2D_RIGHT, + MODE_2D_LEFT, + MODE_3D, + MODE_INVALID +}; +enum imx072_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +enum imx072_reg_mode { + IMX072_FRAME_LENGTH_LINES_HI = 0, + IMX072_FRAME_LENGTH_LINES_LO, + IMX072_LINE_LENGTH_PCK_HI, + IMX072_LINE_LENGTH_PCK_LO, +}; + +struct imx072_reg { + const struct imx072_i2c_reg_conf *rec_settings; + const unsigned short rec_size; + const struct imx072_i2c_conf_array *conf_array; +}; +#endif /* IMX072_H */ diff --git a/drivers/media/video/msm/imx072_reg.c b/drivers/media/video/msm/imx072_reg.c new file mode 100644 index 00000000000..ea7554815b7 --- /dev/null +++ b/drivers/media/video/msm/imx072_reg.c @@ -0,0 +1,153 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "imx072.h" + +struct imx072_i2c_reg_conf imx072_prev_settings[] = { + {0x0340, 0x03},/*frame_length*/ + {0x0341, 0xF7},/*frame_length*/ + {0x0342, 0x0A},/*line_length*/ + {0x0343, 0xE0},/*line_length*/ + {0x0344, 0x00},/*x_addr_start*/ + {0x0345, 0x00},/*x_addr_start*/ + {0x0346, 0x00},/*y_addr_start*/ + {0x0347, 0x00},/*y_addr_start*/ + {0x0348, 0x0A},/*x_addr_end*/ + {0x0349, 0x2F},/*x_addr_end*/ + {0x034A, 0x07},/*y_addr_end*/ + {0x034B, 0xA7},/*y_addr_end*/ + {0x034C, 0x05},/*x_out_size*/ + {0x034D, 0x18},/*x_out_size*/ + {0x034E, 0x03},/*y_out_size*/ + {0x034F, 0xD4},/*y_out_size*/ + {0x0381, 0x01},/*x_even_inc*/ + {0x0383, 0x03},/*x_odd_inc*/ + {0x0385, 0x01},/*y_even_inc*/ + {0x0387, 0x03},/*y_odd_inc*/ + {0x3016, 0x06},/*VMODEADD*/ + {0x3017, 0x40}, + {0x3069, 0x24}, + {0x306A, 0x00}, + {0x306B, 0xCB}, + {0x306C, 0x07}, + {0x30E8, 0x86}, + {0x3304, 0x03}, + {0x3305, 0x02}, + {0x3306, 0x0A}, + {0x3307, 0x02}, + {0x3308, 0x11}, + {0x3309, 0x04}, + {0x330A, 0x05}, + {0x330B, 0x04}, + {0x330C, 0x05}, + {0x330D, 0x04}, + {0x330E, 0x01}, + {0x3301, 0x80}, +}; + +struct imx072_i2c_reg_conf imx072_snap_settings[] = { + {0x0340, 0x07},/*frame_length*/ + {0x0341, 0xEE},/*frame_length*/ + {0x0342, 0x0A},/*line_length*/ + {0x0343, 0xE0},/*line_length*/ + {0x0344, 0x00},/*x_addr_start*/ + {0x0345, 0x00},/*x_addr_start*/ + {0x0346, 0x00},/*y_addr_start*/ + {0x0347, 0x00},/*y_addr_start*/ + {0x0348, 0x0A},/*x_addr_end*/ + {0x0349, 0x2F},/*x_addr_end*/ + {0x034A, 0x07},/*y_addr_end*/ + {0x034B, 0xA7},/*y_addr_end*/ + {0x034C, 0x0A},/*x_out_size*/ + {0x034D, 0x30},/*x_out_size*/ + {0x034E, 0x07},/*y_out_size*/ + {0x034F, 0xA8},/*y_out_size*/ + {0x0381, 0x01},/*x_even_inc*/ + {0x0383, 0x01},/*x_odd_inc*/ + {0x0385, 0x01},/*y_even_inc*/ + {0x0387, 0x01},/*y_odd_inc*/ + {0x3016, 0x06},/*VMODEADD*/ + {0x3017, 0x40}, + {0x3069, 0x24}, + {0x306A, 0x00}, + {0x306B, 0xCB}, + {0x306C, 0x07}, + {0x30E8, 0x06}, + {0x3304, 0x05}, + {0x3305, 0x04}, + {0x3306, 0x15}, + {0x3307, 0x02}, + {0x3308, 0x11}, + {0x3309, 0x07}, + {0x330A, 0x05}, + {0x330B, 0x04}, + {0x330C, 0x05}, + {0x330D, 0x04}, + {0x330E, 0x01}, + {0x3301, 0x00}, +}; + +struct imx072_i2c_reg_conf imx072_recommend_settings[] = { + {0x0307, 0x12}, + {0x302B, 0x4B}, + {0x0101, 0x03}, + {0x300A, 0x80}, + {0x3014, 0x08}, + {0x3015, 0x37}, + {0x3017, 0x40}, + {0x301C, 0x01}, + {0x3031, 0x28}, + {0x3040, 0x00}, + {0x3041, 0x60}, + {0x3051, 0x24}, + {0x3053, 0x34}, + {0x3055, 0x3B}, + {0x3057, 0xC0}, + {0x3060, 0x30}, + {0x3065, 0x00}, + {0x30AA, 0x88}, + {0x30AB, 0x1C}, + {0x30B0, 0x32}, + {0x30B2, 0x83}, + {0x30D3, 0x04}, + {0x310E, 0xDD}, + {0x31A4, 0xD8}, + {0x31A6, 0x17}, + {0x31AC, 0xCF}, + {0x31AE, 0xF1}, + {0x31B4, 0xD8}, + {0x31B6, 0x17}, + {0x3304, 0x05}, + {0x3305, 0x04}, + {0x3306, 0x15}, + {0x3307, 0x02}, + {0x3308, 0x11}, + {0x3309, 0x07}, + {0x330A, 0x05}, + {0x330B, 0x04}, + {0x330C, 0x05}, + {0x330D, 0x04}, + {0x330E, 0x01}, + {0x30d8, 0x20}, +}; + +struct imx072_i2c_conf_array imx072_confs[] = { + {&imx072_prev_settings[0], ARRAY_SIZE(imx072_prev_settings)}, + {&imx072_snap_settings[0], ARRAY_SIZE(imx072_snap_settings)}, +}; + +struct imx072_reg imx072_regs = { + .rec_settings = &imx072_recommend_settings[0], + .rec_size = ARRAY_SIZE(imx072_recommend_settings), + .conf_array = &imx072_confs[0], +}; diff --git a/drivers/media/video/msm/imx074.c b/drivers/media/video/msm/imx074.c new file mode 100644 index 00000000000..636b40283ba --- /dev/null +++ b/drivers/media/video/msm/imx074.c @@ -0,0 +1,1414 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "imx074.h" + +/*SENSOR REGISTER DEFINES*/ +#define IMX074_EEPROM_SLAVE_ADDR 0x52 +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD_OFF 0x00 +#define GROUPED_PARAMETER_HOLD 0x01 +#define REG_MODE_SELECT 0x100 +#define MODE_SELECT_STANDBY_MODE 0x00 +#define MODE_SELECT_STREAM 0x01 +/* Integration Time */ +#define REG_COARSE_INTEGRATION_TIME_HI 0x0202 +#define REG_COARSE_INTEGRATION_TIME_LO 0x0203 +/* Gain */ +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_HI 0x0204 +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_LO 0x0205 +/* PLL registers */ +#define REG_PLL_MULTIPLIER 0x0307 +#define REG_PRE_PLL_CLK_DIV 0x0305 +#define REG_PLSTATIM 0x302b +#define REG_3024 0x3024 +#define REG_IMAGE_ORIENTATION 0x0101 +#define REG_VNDMY_ABLMGSHLMT 0x300a +#define REG_Y_OPBADDR_START_DI 0x3014 +#define REG_3015 0x3015 +#define REG_301C 0x301C +#define REG_302C 0x302C +#define REG_3031 0x3031 +#define REG_3041 0x3041 +#define REG_3051 0x3051 +#define REG_3053 0x3053 +#define REG_3057 0x3057 +#define REG_305C 0x305C +#define REG_305D 0x305D +#define REG_3060 0x3060 +#define REG_3065 0x3065 +#define REG_30AA 0x30AA +#define REG_30AB 0x30AB +#define REG_30B0 0x30B0 +#define REG_30B2 0x30B2 +#define REG_30D3 0x30D3 +#define REG_3106 0x3106 +#define REG_310C 0x310C +#define REG_3304 0x3304 +#define REG_3305 0x3305 +#define REG_3306 0x3306 +#define REG_3307 0x3307 +#define REG_3308 0x3308 +#define REG_3309 0x3309 +#define REG_330A 0x330A +#define REG_330B 0x330B +#define REG_330C 0x330C +#define REG_330D 0x330D +#define REG_330F 0x330F +#define REG_3381 0x3381 + +/* mode setting */ +#define REG_FRAME_LENGTH_LINES_HI 0x0340 +#define REG_FRAME_LENGTH_LINES_LO 0x0341 +#define REG_YADDR_START 0x0347 +#define REG_YAAAR_END 0x034b +#define REG_X_OUTPUT_SIZE_MSB 0x034c +#define REG_X_OUTPUT_SIZE_LSB 0x034d +#define REG_Y_OUTPUT_SIZE_MSB 0x034e +#define REG_Y_OUTPUT_SIZE_LSB 0x034f +#define REG_X_EVEN_INC 0x0381 +#define REG_X_ODD_INC 0x0383 +#define REG_Y_EVEN_INC 0x0385 +#define REG_Y_ODD_INC 0x0387 +#define REG_HMODEADD 0x3001 +#define REG_VMODEADD 0x3016 +#define REG_VAPPLINE_START 0x3069 +#define REG_VAPPLINE_END 0x306b +#define REG_SHUTTER 0x3086 +#define REG_HADDAVE 0x30e8 +#define REG_LANESEL 0x3301 +/* Test Pattern */ +#define REG_TEST_PATTERN_MODE 0x0601 + +#define REG_LINE_LENGTH_PCK_HI 0x0342 +#define REG_LINE_LENGTH_PCK_LO 0x0343 +/*..... TYPE DECLARATIONS.....*/ +#define IMX074_OFFSET 3 +#define IMX074_DEFAULT_MASTER_CLK_RATE 24000000 +/* Full Size */ +#define IMX074_FULL_SIZE_WIDTH 4208 +#define IMX074_FULL_SIZE_HEIGHT 3120 +#define IMX074_FULL_SIZE_DUMMY_PIXELS 0 +#define IMX074_FULL_SIZE_DUMMY_LINES 0 +/* Quarter Size */ +#define IMX074_QTR_SIZE_WIDTH 2104 +#define IMX074_QTR_SIZE_HEIGHT 1560 +#define IMX074_QTR_SIZE_DUMMY_PIXELS 0 +#define IMX074_QTR_SIZE_DUMMY_LINES 0 +/* Blanking as measured on the scope */ +/* Full Size */ +#define IMX074_HRZ_FULL_BLK_PIXELS 264 +#define IMX074_VER_FULL_BLK_LINES 96 +/* Quarter Size */ +#define IMX074_HRZ_QTR_BLK_PIXELS 2368 +#define IMX074_VER_QTR_BLK_LINES 21 +#define Q8 0x100 +#define Q10 0x400 +#define IMX074_AF_I2C_SLAVE_ID 0x72 +#define IMX074_STEPS_NEAR_TO_CLOSEST_INF 52 +#define IMX074_TOTAL_STEPS_NEAR_TO_FAR 52 +static uint32_t imx074_l_region_code_per_step = 2; + +struct imx074_work_t { + struct work_struct work; +}; + +static struct imx074_work_t *imx074_sensorw; +static struct i2c_client *imx074_client; + +struct imx074_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + uint32_t sensormode; + uint32_t fps_divider;/* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider;/* init to 1 * 0x00000400 */ + uint16_t fps; + int16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + enum imx074_resolution_t prev_res; + enum imx074_resolution_t pict_res; + enum imx074_resolution_t curr_res; + enum imx074_test_mode_t set_test; + unsigned short imgaddr; +}; +static uint8_t imx074_delay_msecs_stdby = 5; +static uint16_t imx074_delay_msecs_stream = 5; +static int32_t config_csi; + +static struct imx074_ctrl_t *imx074_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(imx074_wait_queue); +DEFINE_MUTEX(imx074_mut); + +/*=============================================================*/ + +static int imx074_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 2, + .buf = rxdata, + }, + }; + if (i2c_transfer(imx074_client->adapter, msgs, 2) < 0) { + CDBG("imx074_i2c_rxdata failed!\n"); + return -EIO; + } + return 0; +} +static int32_t imx074_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(imx074_client->adapter, msg, 1) < 0) { + CDBG("imx074_i2c_txdata faild 0x%x\n", imx074_client->addr); + return -EIO; + } + + return 0; +} + + +static int32_t imx074_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = imx074_i2c_rxdata(imx074_client->addr, buf, rlen); + if (rc < 0) { + CDBG("imx074_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + return rc; +} + +static int imx074_af_i2c_rxdata_b(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 1, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 1, + .buf = rxdata, + }, + }; + + if (i2c_transfer(imx074_client->adapter, msgs, 2) < 0) { + CDBG("imx074_i2c_rxdata_b failed!\n"); + return -EIO; + } + + return 0; +} + +static int32_t imx074_i2c_read_w_eeprom(unsigned short raddr, + unsigned short *rdata) +{ + int32_t rc; + unsigned char buf; + if (!rdata) + return -EIO; + /* Read 2 bytes in sequence */ + buf = (raddr & 0x00FF); + rc = imx074_af_i2c_rxdata_b(IMX074_EEPROM_SLAVE_ADDR, &buf, 1); + if (rc < 0) { + CDBG("imx074_i2c_read_eeprom 0x%x failed!\n", raddr); + return rc; + } + *rdata = buf<<8; + + /* Read Second byte of data */ + buf = (raddr & 0x00FF) + 1; + rc = imx074_af_i2c_rxdata_b(IMX074_EEPROM_SLAVE_ADDR, &buf, 1); + if (rc < 0) { + CDBG("imx074_i2c_read_eeprom 0x%x failed!\n", raddr); + return rc; + } + *rdata |= buf; + return rc; +} + +static int32_t imx074_i2c_write_b_sensor(unsigned short waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = imx074_i2c_txdata(imx074_client->addr, buf, 3); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + return rc; +} +static int16_t imx074_i2c_write_b_af(unsigned short saddr, + unsigned short baddr, unsigned short bdata) +{ + int32_t rc; + unsigned char buf[2]; + memset(buf, 0, sizeof(buf)); + buf[0] = baddr; + buf[1] = bdata; + rc = imx074_i2c_txdata(saddr, buf, 2); + if (rc < 0) + CDBG("AFi2c_write failed, saddr = 0x%x addr = 0x%x, val =0x%x!", + saddr, baddr, bdata); + return rc; +} + +static int32_t imx074_i2c_write_w_table(struct imx074_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < num; i++) { + rc = imx074_i2c_write_b_sensor(reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} +static int16_t imx074_af_init(void) +{ + int32_t rc; + /* Initialize waveform */ + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x01, 0xA9); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x02, 0xD2); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x03, 0x0C); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x04, 0x14); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x05, 0xB6); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x06, 0x4F); + return rc; +} + +static void imx074_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint16_t preview_frame_length_lines, snapshot_frame_length_lines; + uint32_t divider, d1; + uint32_t pclk_mult;/*Q10 */ + /* Total frame_length_lines and line_length_pck for preview */ + preview_frame_length_lines = IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES; + /* Total frame_length_lines and line_length_pck for snapshot */ + snapshot_frame_length_lines = IMX074_FULL_SIZE_HEIGHT + + IMX074_VER_FULL_BLK_LINES; + d1 = preview_frame_length_lines * 0x00010000 / + snapshot_frame_length_lines; + pclk_mult = + (uint32_t) ((imx074_regs.reg_pat[RES_CAPTURE].pll_multiplier * + 0x00010000) / + (imx074_regs.reg_pat[RES_PREVIEW].pll_multiplier)); + divider = d1 * pclk_mult / 0x00010000; + *pfps = (uint16_t) (fps * divider / 0x00010000); +} + +static uint16_t imx074_get_prev_lines_pf(void) +{ + if (imx074_ctrl->prev_res == QTR_SIZE) + return IMX074_QTR_SIZE_HEIGHT + IMX074_VER_QTR_BLK_LINES; + else + return IMX074_FULL_SIZE_HEIGHT + IMX074_VER_FULL_BLK_LINES; + +} + +static uint16_t imx074_get_prev_pixels_pl(void) +{ + if (imx074_ctrl->prev_res == QTR_SIZE) + return IMX074_QTR_SIZE_WIDTH + IMX074_HRZ_QTR_BLK_PIXELS; + else + return IMX074_FULL_SIZE_WIDTH + IMX074_HRZ_FULL_BLK_PIXELS; +} + +static uint16_t imx074_get_pict_lines_pf(void) +{ + if (imx074_ctrl->pict_res == QTR_SIZE) + return IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES; + else + return IMX074_FULL_SIZE_HEIGHT + + IMX074_VER_FULL_BLK_LINES; +} + +static uint16_t imx074_get_pict_pixels_pl(void) +{ + if (imx074_ctrl->pict_res == QTR_SIZE) + return IMX074_QTR_SIZE_WIDTH + + IMX074_HRZ_QTR_BLK_PIXELS; + else + return IMX074_FULL_SIZE_WIDTH + + IMX074_HRZ_FULL_BLK_PIXELS; +} + +static uint32_t imx074_get_pict_max_exp_lc(void) +{ + if (imx074_ctrl->pict_res == QTR_SIZE) + return (IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES)*24; + else + return (IMX074_FULL_SIZE_HEIGHT + + IMX074_VER_FULL_BLK_LINES)*24; +} + +static int32_t imx074_set_fps(struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + imx074_ctrl->fps_divider = fps->fps_div; + imx074_ctrl->pict_fps_divider = fps->pict_fps_div; + if (imx074_ctrl->curr_res == QTR_SIZE) { + total_lines_per_frame = (uint16_t)(((IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES) * + imx074_ctrl->fps_divider) / 0x400); + } else { + total_lines_per_frame = (uint16_t)(((IMX074_FULL_SIZE_HEIGHT + + IMX074_VER_FULL_BLK_LINES) * + imx074_ctrl->pict_fps_divider) / 0x400); + } + if (imx074_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_HI, + ((total_lines_per_frame & 0xFF00) >> 8)) < 0) + return rc; + if (imx074_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_LO, + (total_lines_per_frame & 0x00FF)) < 0) + return rc; + return rc; +} + +static int32_t imx074_write_exp_gain(uint16_t gain, uint32_t line) +{ + static uint16_t max_legal_gain = 0x00E0; + uint8_t gain_msb, gain_lsb; + uint8_t intg_time_msb, intg_time_lsb; + uint8_t frame_length_line_msb, frame_length_line_lsb; + uint16_t frame_length_lines; + int32_t rc = -1; + + CDBG("imx074_write_exp_gain : gain = %d line = %d", gain, line); + if (imx074_ctrl->curr_res == QTR_SIZE) { + frame_length_lines = IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES; + frame_length_lines = frame_length_lines * + imx074_ctrl->fps_divider / 0x400; + } else { + frame_length_lines = IMX074_FULL_SIZE_HEIGHT + + IMX074_VER_FULL_BLK_LINES; + frame_length_lines = frame_length_lines * + imx074_ctrl->pict_fps_divider / 0x400; + } + if (line > (frame_length_lines - IMX074_OFFSET)) + frame_length_lines = line + IMX074_OFFSET; + + CDBG("imx074 setting line = %d\n", line); + + + CDBG("imx074 setting frame_length_lines = %d\n", + frame_length_lines); + + if (gain > max_legal_gain) + /* range: 0 to 224 */ + gain = max_legal_gain; + + /* update gain registers */ + gain_msb = (uint8_t) ((gain & 0xFF00) >> 8); + gain_lsb = (uint8_t) (gain & 0x00FF); + + frame_length_line_msb = (uint8_t) ((frame_length_lines & 0xFF00) >> 8); + frame_length_line_lsb = (uint8_t) (frame_length_lines & 0x00FF); + + /* update line count registers */ + intg_time_msb = (uint8_t) ((line & 0xFF00) >> 8); + intg_time_lsb = (uint8_t) (line & 0x00FF); + + rc = imx074_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + CDBG("imx074 setting REG_ANALOGUE_GAIN_CODE_GLOBAL_HI = 0x%X\n", + gain_msb); + rc = imx074_i2c_write_b_sensor(REG_ANALOGUE_GAIN_CODE_GLOBAL_HI, + gain_msb); + if (rc < 0) + return rc; + CDBG("imx074 setting REG_ANALOGUE_GAIN_CODE_GLOBAL_LO = 0x%X\n", + gain_lsb); + rc = imx074_i2c_write_b_sensor(REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + gain_lsb); + if (rc < 0) + return rc; + + CDBG("imx074 setting REG_FRAME_LENGTH_LINES_HI = 0x%X\n", + frame_length_line_msb); + rc = imx074_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_HI, + frame_length_line_msb); + if (rc < 0) + return rc; + + CDBG("imx074 setting REG_FRAME_LENGTH_LINES_LO = 0x%X\n", + frame_length_line_lsb); + rc = imx074_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_LO, + frame_length_line_lsb); + if (rc < 0) + return rc; + + CDBG("imx074 setting REG_COARSE_INTEGRATION_TIME_HI = 0x%X\n", + intg_time_msb); + rc = imx074_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_HI, + intg_time_msb); + if (rc < 0) + return rc; + + CDBG("imx074 setting REG_COARSE_INTEGRATION_TIME_LO = 0x%X\n", + intg_time_lsb); + rc = imx074_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_LO, + intg_time_lsb); + if (rc < 0) + return rc; + + rc = imx074_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t imx074_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + rc = imx074_write_exp_gain(gain, line); + return rc; +} + +static int32_t imx074_move_focus(int direction, + int32_t num_steps) +{ + int32_t step_direction, dest_step_position, bit_mask; + int32_t rc = 0; + + if (num_steps == 0) + return rc; + + if (direction == MOVE_NEAR) { + step_direction = 1; + bit_mask = 0x80; + } else if (direction == MOVE_FAR) { + step_direction = -1; + bit_mask = 0x00; + } else { + CDBG("imx074_move_focus: Illegal focus direction"); + return -EINVAL; + } + dest_step_position = imx074_ctrl->curr_step_pos + + (step_direction * num_steps); + if (dest_step_position < 0) + dest_step_position = 0; + else if (dest_step_position > IMX074_TOTAL_STEPS_NEAR_TO_FAR) + dest_step_position = IMX074_TOTAL_STEPS_NEAR_TO_FAR; + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x00, + ((num_steps * imx074_l_region_code_per_step) | bit_mask)); + CDBG("%s: Index: %d\n", __func__, dest_step_position); + imx074_ctrl->curr_step_pos = dest_step_position; + return rc; +} + + +static int32_t imx074_set_default_focus(uint8_t af_step) +{ + int32_t rc; + /* Initialize to infinity */ + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x00, 0x7F); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x00, 0x7F); + imx074_ctrl->curr_step_pos = 0; + return rc; +} +static int32_t imx074_test(enum imx074_test_mode_t mo) +{ + int32_t rc = 0; + if (mo == TEST_OFF) + return rc; + else { + /* Set mo to 2 inorder to enable test pattern*/ + if (imx074_i2c_write_b_sensor(REG_TEST_PATTERN_MODE, + (uint8_t) mo) < 0) { + return rc; + } + } + return rc; +} +static int32_t imx074_sensor_setting(int update_type, int rt) +{ + int32_t rc = 0; + struct msm_camera_csi_params imx074_csi_params; + switch (update_type) { + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct imx074_i2c_reg_conf init_tbl[] = { + {REG_PRE_PLL_CLK_DIV, + imx074_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLSTATIM, + imx074_regs.reg_pat_init[0]. + plstatim}, + {REG_3024, + imx074_regs.reg_pat_init[0]. + reg_3024}, + {REG_IMAGE_ORIENTATION, + imx074_regs.reg_pat_init[0]. + image_orientation}, + {REG_VNDMY_ABLMGSHLMT, + imx074_regs.reg_pat_init[0]. + vndmy_ablmgshlmt}, + {REG_Y_OPBADDR_START_DI, + imx074_regs.reg_pat_init[0]. + y_opbaddr_start_di}, + {REG_3015, + imx074_regs.reg_pat_init[0]. + reg_0x3015}, + {REG_301C, + imx074_regs.reg_pat_init[0]. + reg_0x301c}, + {REG_302C, + imx074_regs.reg_pat_init[0]. + reg_0x302c}, + {REG_3031, + imx074_regs.reg_pat_init[0].reg_0x3031}, + {REG_3041, + imx074_regs.reg_pat_init[0].reg_0x3041}, + {REG_3051, + imx074_regs.reg_pat_init[0].reg_0x3051}, + {REG_3053, + imx074_regs.reg_pat_init[0].reg_0x3053}, + {REG_3057, + imx074_regs.reg_pat_init[0].reg_0x3057}, + {REG_305C, + imx074_regs.reg_pat_init[0].reg_0x305c}, + {REG_305D, + imx074_regs.reg_pat_init[0].reg_0x305d}, + {REG_3060, + imx074_regs.reg_pat_init[0].reg_0x3060}, + {REG_3065, + imx074_regs.reg_pat_init[0].reg_0x3065}, + {REG_30AA, + imx074_regs.reg_pat_init[0].reg_0x30aa}, + {REG_30AB, + imx074_regs.reg_pat_init[0].reg_0x30ab}, + {REG_30B0, + imx074_regs.reg_pat_init[0].reg_0x30b0}, + {REG_30B2, + imx074_regs.reg_pat_init[0].reg_0x30b2}, + {REG_30D3, + imx074_regs.reg_pat_init[0].reg_0x30d3}, + {REG_3106, + imx074_regs.reg_pat_init[0].reg_0x3106}, + {REG_310C, + imx074_regs.reg_pat_init[0].reg_0x310c}, + {REG_3304, + imx074_regs.reg_pat_init[0].reg_0x3304}, + {REG_3305, + imx074_regs.reg_pat_init[0].reg_0x3305}, + {REG_3306, + imx074_regs.reg_pat_init[0].reg_0x3306}, + {REG_3307, + imx074_regs.reg_pat_init[0].reg_0x3307}, + {REG_3308, + imx074_regs.reg_pat_init[0].reg_0x3308}, + {REG_3309, + imx074_regs.reg_pat_init[0].reg_0x3309}, + {REG_330A, + imx074_regs.reg_pat_init[0].reg_0x330a}, + {REG_330B, + imx074_regs.reg_pat_init[0].reg_0x330b}, + {REG_330C, + imx074_regs.reg_pat_init[0].reg_0x330c}, + {REG_330D, + imx074_regs.reg_pat_init[0].reg_0x330d}, + {REG_330F, + imx074_regs.reg_pat_init[0].reg_0x330f}, + {REG_3381, + imx074_regs.reg_pat_init[0].reg_0x3381}, + }; + struct imx074_i2c_reg_conf init_mode_tbl[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + {REG_PLL_MULTIPLIER, + imx074_regs.reg_pat[rt]. + pll_multiplier}, + {REG_FRAME_LENGTH_LINES_HI, + imx074_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + imx074_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_YADDR_START , + imx074_regs.reg_pat[rt]. + y_addr_start}, + {REG_YAAAR_END, + imx074_regs.reg_pat[rt]. + y_add_end}, + {REG_X_OUTPUT_SIZE_MSB, + imx074_regs.reg_pat[rt]. + x_output_size_msb}, + {REG_X_OUTPUT_SIZE_LSB, + imx074_regs.reg_pat[rt]. + x_output_size_lsb}, + {REG_Y_OUTPUT_SIZE_MSB, + imx074_regs.reg_pat[rt]. + y_output_size_msb}, + {REG_Y_OUTPUT_SIZE_LSB , + imx074_regs.reg_pat[rt]. + y_output_size_lsb}, + {REG_X_EVEN_INC, + imx074_regs.reg_pat[rt]. + x_even_inc}, + {REG_X_ODD_INC, + imx074_regs.reg_pat[rt]. + x_odd_inc}, + {REG_Y_EVEN_INC, + imx074_regs.reg_pat[rt]. + y_even_inc}, + {REG_Y_ODD_INC, + imx074_regs.reg_pat[rt]. + y_odd_inc}, + {REG_HMODEADD, + imx074_regs.reg_pat[rt]. + hmodeadd}, + {REG_VMODEADD, + imx074_regs.reg_pat[rt]. + vmodeadd}, + {REG_VAPPLINE_START, + imx074_regs.reg_pat[rt]. + vapplinepos_start}, + {REG_VAPPLINE_END, + imx074_regs.reg_pat[rt]. + vapplinepos_end}, + {REG_SHUTTER, + imx074_regs.reg_pat[rt]. + shutter}, + {REG_HADDAVE, + imx074_regs.reg_pat[rt]. + haddave}, + {REG_LANESEL, + imx074_regs.reg_pat[rt]. + lanesel}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF}, + + }; + /* reset fps_divider */ + imx074_ctrl->fps = 30 * Q8; + imx074_ctrl->fps_divider = 1 * 0x400; + /* stop streaming */ + rc = imx074_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE); + if (rc < 0) + return rc; + msleep(imx074_delay_msecs_stdby); + rc = imx074_i2c_write_w_table(&init_tbl[0], + ARRAY_SIZE(init_tbl)); + if (rc < 0) + return rc; + rc = imx074_i2c_write_w_table(&init_mode_tbl[0], + ARRAY_SIZE(init_mode_tbl)); + if (rc < 0) + return rc; + rc = imx074_test(imx074_ctrl->set_test); + return rc; + } + break; + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct imx074_i2c_reg_conf mode_tbl[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + {REG_PLL_MULTIPLIER, + imx074_regs.reg_pat[rt]. + pll_multiplier}, + {REG_FRAME_LENGTH_LINES_HI, + imx074_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + imx074_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_YADDR_START , + imx074_regs.reg_pat[rt]. + y_addr_start}, + {REG_YAAAR_END, + imx074_regs.reg_pat[rt]. + y_add_end}, + {REG_X_OUTPUT_SIZE_MSB, + imx074_regs.reg_pat[rt]. + x_output_size_msb}, + {REG_X_OUTPUT_SIZE_LSB, + imx074_regs.reg_pat[rt]. + x_output_size_lsb}, + {REG_Y_OUTPUT_SIZE_MSB, + imx074_regs.reg_pat[rt]. + y_output_size_msb}, + {REG_Y_OUTPUT_SIZE_LSB , + imx074_regs.reg_pat[rt]. + y_output_size_lsb}, + {REG_X_EVEN_INC, + imx074_regs.reg_pat[rt]. + x_even_inc}, + {REG_X_ODD_INC, + imx074_regs.reg_pat[rt]. + x_odd_inc}, + {REG_Y_EVEN_INC, + imx074_regs.reg_pat[rt]. + y_even_inc}, + {REG_Y_ODD_INC, + imx074_regs.reg_pat[rt]. + y_odd_inc}, + {REG_HMODEADD, + imx074_regs.reg_pat[rt]. + hmodeadd}, + {REG_VMODEADD, + imx074_regs.reg_pat[rt]. + vmodeadd}, + {REG_VAPPLINE_START, + imx074_regs.reg_pat[rt]. + vapplinepos_start}, + {REG_VAPPLINE_END, + imx074_regs.reg_pat[rt]. + vapplinepos_end}, + {REG_SHUTTER, + imx074_regs.reg_pat[rt]. + shutter}, + {REG_HADDAVE, + imx074_regs.reg_pat[rt]. + haddave}, + {REG_LANESEL, + imx074_regs.reg_pat[rt]. + lanesel}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF}, + }; + + /* stop streaming */ + rc = imx074_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE); + msleep(imx074_delay_msecs_stdby); + if (config_csi == 0) { + imx074_csi_params.lane_cnt = 4; + imx074_csi_params.data_format = CSI_10BIT; + imx074_csi_params.lane_assign = 0xe4; + imx074_csi_params.dpcm_scheme = 0; + imx074_csi_params.settle_cnt = 0x14; + rc = msm_camio_csi_config(&imx074_csi_params); + /*imx074_delay_msecs_stdby*/ + msleep(imx074_delay_msecs_stream); + config_csi = 1; + } + rc = imx074_i2c_write_w_table(&mode_tbl[0], + ARRAY_SIZE(mode_tbl)); + if (rc < 0) + return rc; + rc = imx074_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STREAM); + if (rc < 0) + return rc; + msleep(imx074_delay_msecs_stream); + } + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + + +static int32_t imx074_video_config(int mode) +{ + + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + if (imx074_ctrl->prev_res == QTR_SIZE) { + rt = RES_PREVIEW; + } else { + rt = RES_CAPTURE; + } + if (imx074_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + imx074_ctrl->curr_res = imx074_ctrl->prev_res; + imx074_ctrl->sensormode = mode; + return rc; +} + +static int32_t imx074_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt = RES_PREVIEW; /* TODO: Used without initialization, guessing. */ + /* change sensor resolution if needed */ + if (imx074_ctrl->curr_res != imx074_ctrl->pict_res) { + if (imx074_ctrl->pict_res == QTR_SIZE) { + rt = RES_PREVIEW; + } else { + rt = RES_CAPTURE; + } + } + if (imx074_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + imx074_ctrl->curr_res = imx074_ctrl->pict_res; + imx074_ctrl->sensormode = mode; + return rc; +} +static int32_t imx074_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt = RES_PREVIEW; /* TODO: Used without initialization, guessing. */ + /* change sensor resolution if needed */ + if (imx074_ctrl->curr_res != imx074_ctrl->pict_res) { + if (imx074_ctrl->pict_res == QTR_SIZE) { + rt = RES_PREVIEW; + } else { + rt = RES_CAPTURE; + } + } + if (imx074_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + imx074_ctrl->curr_res = imx074_ctrl->pict_res; + imx074_ctrl->sensormode = mode; + return rc; +} +static int32_t imx074_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = imx074_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + rc = imx074_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + rc = imx074_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} +static int32_t imx074_power_down(void) +{ + imx074_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE); + msleep(imx074_delay_msecs_stdby); + return 0; +} +static int imx074_probe_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_set_value_cansleep(data->sensor_reset, 0); + gpio_direction_input(data->sensor_reset); + gpio_free(data->sensor_reset); + return 0; +} + +static int imx074_read_eeprom_data(struct sensor_cfg_data *cfg) +{ + int32_t rc = 0; + uint16_t eepromdata = 0; + uint8_t addr = 0; + + addr = 0x10; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.r_over_g = eepromdata; + + addr = 0x12; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.b_over_g = eepromdata; + + addr = 0x14; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.gr_over_gb = eepromdata; + + addr = 0x1A; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.macro_2_inf = eepromdata; + + addr = 0x1C; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.inf_2_macro = eepromdata; + + addr = 0x1E; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.stroke_amt = eepromdata; + + addr = 0x20; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.af_pos_1m = eepromdata; + + addr = 0x22; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.af_pos_inf = eepromdata; + + return rc; +} + +static int imx074_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + unsigned short chipidl, chipidh; + CDBG("%s: %d\n", __func__, __LINE__); + rc = gpio_request(data->sensor_reset, "imx074"); + CDBG(" imx074_probe_init_sensor \n"); + if (!rc) { + CDBG("sensor_reset = %d\n", rc); + gpio_direction_output(data->sensor_reset, 0); + usleep_range(5000, 6000); + gpio_set_value_cansleep(data->sensor_reset, 1); + usleep_range(5000, 6000); + } else { + CDBG("gpio reset fail"); + goto init_probe_done; + } + CDBG("imx074_probe_init_sensor is called\n"); + /* 3. Read sensor Model ID: */ + rc = imx074_i2c_read(0x0000, &chipidh, 1); + if (rc < 0) { + CDBG("Model read failed\n"); + goto init_probe_fail; + } + rc = imx074_i2c_read(0x0001, &chipidl, 1); + if (rc < 0) { + CDBG("Model read failed\n"); + goto init_probe_fail; + } + CDBG("imx074 model_id = 0x%x 0x%x\n", chipidh, chipidl); + /* 4. Compare sensor ID to IMX074 ID: */ + if (chipidh != 0x00 || chipidl != 0x74) { + rc = -ENODEV; + CDBG("imx074_probe_init_sensor fail chip id doesnot match\n"); + goto init_probe_fail; + } + goto init_probe_done; +init_probe_fail: + CDBG("imx074_probe_init_sensor fails\n"); + imx074_probe_init_done(data); +init_probe_done: + CDBG(" imx074_probe_init_sensor finishes\n"); + return rc; + } +static int32_t imx074_poweron_af(void) +{ + int32_t rc = 0; + CDBG("imx074 enable AF actuator, gpio = %d\n", + imx074_ctrl->sensordata->vcm_pwd); + rc = gpio_request(imx074_ctrl->sensordata->vcm_pwd, "imx074"); + if (!rc) { + gpio_direction_output(imx074_ctrl->sensordata->vcm_pwd, 1); + msleep(20); + rc = imx074_af_init(); + if (rc < 0) + CDBG("imx074 AF initialisation failed\n"); + } else { + CDBG("%s: AF PowerON gpio_request failed %d\n", __func__, rc); + } + return rc; +} +static void imx074_poweroff_af(void) +{ + gpio_set_value_cansleep(imx074_ctrl->sensordata->vcm_pwd, 0); + gpio_free(imx074_ctrl->sensordata->vcm_pwd); +} +/* camsensor_iu060f_imx074_reset */ +int imx074_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + CDBG("%s: %d\n", __func__, __LINE__); + CDBG("Calling imx074_sensor_open_init\n"); + imx074_ctrl = kzalloc(sizeof(struct imx074_ctrl_t), GFP_KERNEL); + if (!imx074_ctrl) { + CDBG("imx074_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + imx074_ctrl->fps_divider = 1 * 0x00000400; + imx074_ctrl->pict_fps_divider = 1 * 0x00000400; + imx074_ctrl->fps = 30 * Q8; + imx074_ctrl->set_test = TEST_OFF; + imx074_ctrl->prev_res = QTR_SIZE; + imx074_ctrl->pict_res = FULL_SIZE; + imx074_ctrl->curr_res = INVALID_SIZE; + config_csi = 0; + + if (data) + imx074_ctrl->sensordata = data; + + /* enable mclk first */ + msm_camio_clk_rate_set(IMX074_DEFAULT_MASTER_CLK_RATE); + usleep_range(1000, 2000); + rc = imx074_probe_init_sensor(data); + if (rc < 0) { + CDBG("Calling imx074_sensor_open_init fail\n"); + goto probe_fail; + } + + rc = imx074_sensor_setting(REG_INIT, RES_PREVIEW); + if (rc < 0) { + CDBG("imx074_sensor_setting failed\n"); + goto init_fail; + } + if (machine_is_msm8x60_fluid()) + rc = imx074_poweron_af(); + else + rc = imx074_af_init(); + if (rc < 0) { + CDBG("AF initialisation failed\n"); + goto init_fail; + } else + goto init_done; +probe_fail: + CDBG(" imx074_sensor_open_init probe fail\n"); + kfree(imx074_ctrl); + return rc; +init_fail: + CDBG(" imx074_sensor_open_init fail\n"); + imx074_probe_init_done(data); + kfree(imx074_ctrl); +init_done: + CDBG("imx074_sensor_open_init done\n"); + return rc; +} +static int imx074_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&imx074_wait_queue); + return 0; +} + +static const struct i2c_device_id imx074_i2c_id[] = { + {"imx074", 0}, + { } +}; + +static int imx074_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("imx074_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + imx074_sensorw = kzalloc(sizeof(struct imx074_work_t), GFP_KERNEL); + if (!imx074_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, imx074_sensorw); + imx074_init_client(client); + imx074_client = client; + + + CDBG("imx074_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("imx074_probe failed! rc = %d\n", rc); + return rc; +} + +static int __exit imx074_remove(struct i2c_client *client) +{ + struct imx074_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + imx074_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver imx074_i2c_driver = { + .id_table = imx074_i2c_id, + .probe = imx074_i2c_probe, + .remove = __exit_p(imx074_i2c_remove), + .driver = { + .name = "imx074", + }, +}; + +int imx074_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&imx074_mut); + CDBG("imx074_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + imx074_get_pict_fps( + cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + imx074_get_prev_lines_pf(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + imx074_get_prev_pixels_pl(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + imx074_get_pict_lines_pf(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + imx074_get_pict_pixels_pl(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + imx074_get_pict_max_exp_lc(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = imx074_set_fps(&(cdata.cfg.fps)); + break; + case CFG_SET_EXP_GAIN: + rc = + imx074_write_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_PICT_EXP_GAIN: + rc = + imx074_set_pict_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_MODE: + rc = imx074_set_sensor_mode(cdata.mode, + cdata.rs); + break; + case CFG_PWR_DOWN: + rc = imx074_power_down(); + break; + case CFG_GET_CALIB_DATA: + rc = imx074_read_eeprom_data(&cdata); + if (rc < 0) + break; + if (copy_to_user((void *)argp, + &cdata, + sizeof(cdata))) + rc = -EFAULT; + break; + case CFG_MOVE_FOCUS: + rc = + imx074_move_focus( + cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + case CFG_SET_DEFAULT_FOCUS: + rc = + imx074_set_default_focus( + cdata.cfg.focus.steps); + break; + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = IMX074_STEPS_NEAR_TO_CLOSEST_INF; + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_SET_EFFECT: + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&imx074_mut); + + return rc; +} +static int imx074_sensor_release(void) +{ + int rc = -EBADF; + mutex_lock(&imx074_mut); + if (machine_is_msm8x60_fluid()) + imx074_poweroff_af(); + imx074_power_down(); + gpio_set_value_cansleep(imx074_ctrl->sensordata->sensor_reset, 0); + msleep(5); + gpio_direction_input(imx074_ctrl->sensordata->sensor_reset); + gpio_free(imx074_ctrl->sensordata->sensor_reset); + kfree(imx074_ctrl); + imx074_ctrl = NULL; + CDBG("imx074_release completed\n"); + mutex_unlock(&imx074_mut); + + return rc; +} + +static int imx074_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(&imx074_i2c_driver); + if (rc < 0 || imx074_client == NULL) { + rc = -ENOTSUPP; + goto probe_fail; + } + msm_camio_clk_rate_set(IMX074_DEFAULT_MASTER_CLK_RATE); + rc = imx074_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + s->s_init = imx074_sensor_open_init; + s->s_release = imx074_sensor_release; + s->s_config = imx074_sensor_config; + s->s_mount_angle = info->sensor_platform_info->mount_angle; + imx074_probe_init_done(info); + return rc; + +probe_fail: + CDBG("imx074_sensor_probe: SENSOR PROBE FAILS!\n"); + i2c_del_driver(&imx074_i2c_driver); + return rc; +} + +static int __imx074_probe(struct platform_device *pdev) +{ + + return msm_camera_drv_start(pdev, imx074_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __imx074_probe, + .driver = { + .name = "msm_camera_imx074", + .owner = THIS_MODULE, + }, +}; + +static int __init imx074_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(imx074_init); + +MODULE_DESCRIPTION("Sony 13 MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/media/video/msm/imx074.h b/drivers/media/video/msm/imx074.h new file mode 100644 index 00000000000..8be0fb70d86 --- /dev/null +++ b/drivers/media/video/msm/imx074.h @@ -0,0 +1,118 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef IMX074_H +#define IMX074_H +#include +#include +extern struct imx074_reg imx074_regs; +struct reg_struct_init { + /* PLL setting */ + uint8_t pre_pll_clk_div; /* 0x0305 */ + uint8_t plstatim; /* 0x302b */ + uint8_t reg_3024; /*ox3024*/ + uint8_t image_orientation; /* 0x0101*/ + uint8_t vndmy_ablmgshlmt; /*0x300a*/ + uint8_t y_opbaddr_start_di; /*0x3014*/ + uint8_t reg_0x3015; /*0x3015*/ + uint8_t reg_0x301c; /*0x301c*/ + uint8_t reg_0x302c; /*0x302c*/ + uint8_t reg_0x3031; /*0x3031*/ + uint8_t reg_0x3041; /* 0x3041 */ + uint8_t reg_0x3051; /* 0x3051 */ + uint8_t reg_0x3053; /* 0x3053 */ + uint8_t reg_0x3057; /* 0x3057 */ + uint8_t reg_0x305c; /* 0x305c */ + uint8_t reg_0x305d; /* 0x305d */ + uint8_t reg_0x3060; /* 0x3060 */ + uint8_t reg_0x3065; /* 0x3065 */ + uint8_t reg_0x30aa; /* 0x30aa */ + uint8_t reg_0x30ab; + uint8_t reg_0x30b0; + uint8_t reg_0x30b2; + uint8_t reg_0x30d3; + uint8_t reg_0x3106; + uint8_t reg_0x310c; + uint8_t reg_0x3304; + uint8_t reg_0x3305; + uint8_t reg_0x3306; + uint8_t reg_0x3307; + uint8_t reg_0x3308; + uint8_t reg_0x3309; + uint8_t reg_0x330a; + uint8_t reg_0x330b; + uint8_t reg_0x330c; + uint8_t reg_0x330d; + uint8_t reg_0x330f; + uint8_t reg_0x3381; +}; + +struct reg_struct { + uint8_t pll_multiplier; /* 0x0307 */ + uint8_t frame_length_lines_hi; /* 0x0340*/ + uint8_t frame_length_lines_lo; /* 0x0341*/ + uint8_t y_addr_start; /* 0x347 */ + uint8_t y_add_end; /* 0x034b */ + uint8_t x_output_size_msb; /* 0x034c */ + uint8_t x_output_size_lsb; /* 0x034d */ + uint8_t y_output_size_msb; /* 0x034e */ + uint8_t y_output_size_lsb; /* 0x034f */ + uint8_t x_even_inc; /* 0x0381 */ + uint8_t x_odd_inc; /* 0x0383 */ + uint8_t y_even_inc; /* 0x0385 */ + uint8_t y_odd_inc; /* 0x0387 */ + uint8_t hmodeadd; /* 0x3001 */ + uint8_t vmodeadd; /* 0x3016 */ + uint8_t vapplinepos_start;/*ox3069*/ + uint8_t vapplinepos_end;/*306b*/ + uint8_t shutter; /* 0x3086 */ + uint8_t haddave; /* 0x30e8 */ + uint8_t lanesel; /* 0x3301 */ +}; + +struct imx074_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +enum imx074_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum imx074_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; +enum imx074_setting { + RES_PREVIEW, + RES_CAPTURE +}; +enum mt9p012_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +struct imx074_reg { + const struct reg_struct_init *reg_pat_init; + const struct reg_struct *reg_pat; +}; +#endif /* IMX074_H */ diff --git a/drivers/media/video/msm/imx074_reg.c b/drivers/media/video/msm/imx074_reg.c new file mode 100644 index 00000000000..ccc9b2f1092 --- /dev/null +++ b/drivers/media/video/msm/imx074_reg.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "imx074.h" +const struct reg_struct_init imx074_reg_init[1] = { + { + /* PLL setting */ + 0x02, /* pll_divider 0x0305 */ + 0x4B, /* plstatim 0x302b */ + 0x03, /* reg_3024 */ + 0x00, /* image_orientation 0x0101 */ + 0x80, /* vndmy_ablmgshlmt 0x300a*/ + 0x08, /* y_opbaddr_start_di 3014*/ + 0x37, /* 0x3015*/ + 0x01, /* 0x301c*/ + 0x05, /* 0x302c*/ + 0x26, /* 0x3031*/ + 0x60, /* 0x3041*/ + 0x24, /* 0x3051 CLK DIV*/ + 0x34, /* 0x3053*/ + 0xc0, /* 0x3057*/ + 0x09, /* 0x305c*/ + 0x07, /* 0x305d */ + 0x30, /* 0x3060 */ + 0x00, /* 0x3065 */ + 0x08, /* 0x30aa */ + 0x1c, /* 0x30ab */ + 0x32, /* 0x30b0 */ + 0x83, /* 0x30b2 */ + 0x04, /* 0x30d3 */ + 0x78, /* 0x3106 */ + 0x82, /* 0x310c */ + 0x05, /* 0x3304 */ + 0x04, /* 0x3305 */ + 0x11, /* 0x3306 */ + 0x02, /* 0x3307 */ + 0x0c, /* 0x3308 */ + 0x06, /* 0x3309 */ + 0x08, /* 0x330a */ + 0x04, /* 0x330b */ + 0x08, /* 0x330c */ + 0x06, /* 0x330d */ + 0x01, /* 0x330f */ + 0x00, /* 0x3381 */ + + } +}; + +/* Preview / Snapshot register settings */ +const struct reg_struct imx074_reg_pat[2] = { + /*preview*/ + { + 0x2D, /*pll_multiplier*/ + 0x06, /*frame_length_lines_hi 0x0340*/ + 0x2D, /* frame_length_lines_lo 0x0341*/ + 0x00, /* y_addr_start 0x347 */ + 0x2F, /* y_add_end 0x034b */ + 0x08, /* x_output_size_msb0x034c */ + 0x38, /* x_output_size_lsb0x034d */ + 0x06, /* y_output_size_msb0x034e */ + 0x18, /* y_output_size_lsb0x034f */ + 0x01, /* x_even_inc 0x0381 */ + 0x03, /* x_odd_inc 0x0383 */ + 0x01, /* y_even_inc 0x0385 */ + 0x03, /* y_odd_inc 0x0387 */ + 0x80, /* hmodeadd0x3001 */ + 0x16, /* vmodeadd0x3016 */ + 0x24, /* vapplinepos_startox3069*/ + 0x53, /* vapplinepos_end306b*/ + 0x00,/* shutter 0x3086 */ + 0x80, /* haddave 0x30e8 */ + 0x83, /* lanesel 0x3301 */ + }, + + /*snapshot*/ + { + 0x26, /*pll_multiplier*/ + 0x0C, /* frame_length_lines_hi 0x0340*/ + 0x90, /* frame_length_lines_lo 0x0341*/ + 0x00, /* y_addr_start 0x347 */ + 0x2F, /* y_add_end 0x034b */ + 0x10, /* x_output_size_msb0x034c */ + 0x70, /* x_output_size_lsb0x034d */ + 0x0c, /* y_output_size_msb0x034e */ + 0x30, /* y_output_size_lsb0x034f */ + 0x01, /* x_even_inc 0x0381 */ + 0x01, /* x_odd_inc 0x0383 */ + 0x01, /* y_even_inc 0x0385 */ + 0x01, /* y_odd_inc 0x0387 */ + 0x00, /* hmodeadd0x3001 */ + 0x06, /* vmodeadd0x3016 */ + 0x24, /* vapplinepos_startox3069*/ + 0x53, /* vapplinepos_end306b*/ + 0x00, /* shutter 0x3086 */ + 0x00, /* haddave 0x30e8 */ + 0x03, /* lanesel 0x3301 */ + } +}; +struct imx074_reg imx074_regs = { + .reg_pat_init = &imx074_reg_init[0], + .reg_pat = &imx074_reg_pat[0], +}; diff --git a/drivers/media/video/msm/imx074_v4l2.c b/drivers/media/video/msm/imx074_v4l2.c new file mode 100644 index 00000000000..18a653da3cc --- /dev/null +++ b/drivers/media/video/msm/imx074_v4l2.c @@ -0,0 +1,1476 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "imx074.h" +#include "msm.h" + +/*SENSOR REGISTER DEFINES*/ +#define IMX074_EEPROM_SLAVE_ADDR 0x52 +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD_OFF 0x00 +#define GROUPED_PARAMETER_HOLD 0x01 +#define REG_MODE_SELECT 0x100 +#define MODE_SELECT_STANDBY_MODE 0x00 +#define MODE_SELECT_STREAM 0x01 +/* Integration Time */ +#define REG_COARSE_INTEGRATION_TIME_HI 0x0202 +#define REG_COARSE_INTEGRATION_TIME_LO 0x0203 +/* Gain */ +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_HI 0x0204 +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_LO 0x0205 +/* PLL registers */ +#define REG_PLL_MULTIPLIER 0x0307 +#define REG_PRE_PLL_CLK_DIV 0x0305 +#define REG_PLSTATIM 0x302b +#define REG_3024 0x3024 +#define REG_IMAGE_ORIENTATION 0x0101 +#define REG_VNDMY_ABLMGSHLMT 0x300a +#define REG_Y_OPBADDR_START_DI 0x3014 +#define REG_3015 0x3015 +#define REG_301C 0x301C +#define REG_302C 0x302C +#define REG_3031 0x3031 +#define REG_3041 0x3041 +#define REG_3051 0x3051 +#define REG_3053 0x3053 +#define REG_3057 0x3057 +#define REG_305C 0x305C +#define REG_305D 0x305D +#define REG_3060 0x3060 +#define REG_3065 0x3065 +#define REG_30AA 0x30AA +#define REG_30AB 0x30AB +#define REG_30B0 0x30B0 +#define REG_30B2 0x30B2 +#define REG_30D3 0x30D3 +#define REG_3106 0x3106 +#define REG_310C 0x310C +#define REG_3304 0x3304 +#define REG_3305 0x3305 +#define REG_3306 0x3306 +#define REG_3307 0x3307 +#define REG_3308 0x3308 +#define REG_3309 0x3309 +#define REG_330A 0x330A +#define REG_330B 0x330B +#define REG_330C 0x330C +#define REG_330D 0x330D +#define REG_330F 0x330F +#define REG_3381 0x3381 + +/* mode setting */ +#define REG_FRAME_LENGTH_LINES_HI 0x0340 +#define REG_FRAME_LENGTH_LINES_LO 0x0341 +#define REG_YADDR_START 0x0347 +#define REG_YAAAR_END 0x034b +#define REG_X_OUTPUT_SIZE_MSB 0x034c +#define REG_X_OUTPUT_SIZE_LSB 0x034d +#define REG_Y_OUTPUT_SIZE_MSB 0x034e +#define REG_Y_OUTPUT_SIZE_LSB 0x034f +#define REG_X_EVEN_INC 0x0381 +#define REG_X_ODD_INC 0x0383 +#define REG_Y_EVEN_INC 0x0385 +#define REG_Y_ODD_INC 0x0387 +#define REG_HMODEADD 0x3001 +#define REG_VMODEADD 0x3016 +#define REG_VAPPLINE_START 0x3069 +#define REG_VAPPLINE_END 0x306b +#define REG_SHUTTER 0x3086 +#define REG_HADDAVE 0x30e8 +#define REG_LANESEL 0x3301 +/* Test Pattern */ +#define REG_TEST_PATTERN_MODE 0x0601 + +#define REG_LINE_LENGTH_PCK_HI 0x0342 +#define REG_LINE_LENGTH_PCK_LO 0x0343 +/*..... TYPE DECLARATIONS.....*/ +#define IMX074_OFFSET 3 +#define IMX074_DEFAULT_MASTER_CLK_RATE 24000000 +/* Full Size */ +#define IMX074_FULL_SIZE_WIDTH 4208 +#define IMX074_FULL_SIZE_HEIGHT 3120 +#define IMX074_FULL_SIZE_DUMMY_PIXELS 0 +#define IMX074_FULL_SIZE_DUMMY_LINES 0 +/* Quarter Size */ +#define IMX074_QTR_SIZE_WIDTH 2104 +#define IMX074_QTR_SIZE_HEIGHT 1560 +#define IMX074_QTR_SIZE_DUMMY_PIXELS 0 +#define IMX074_QTR_SIZE_DUMMY_LINES 0 +/* Blanking as measured on the scope */ +/* Full Size */ +#define IMX074_HRZ_FULL_BLK_PIXELS 264 +#define IMX074_VER_FULL_BLK_LINES 96 +/* Quarter Size */ +#define IMX074_HRZ_QTR_BLK_PIXELS 2368 +#define IMX074_VER_QTR_BLK_LINES 21 +#define Q8 0x100 +#define Q10 0x400 +#define IMX074_AF_I2C_SLAVE_ID 0x72 +#define IMX074_STEPS_NEAR_TO_CLOSEST_INF 52 +#define IMX074_TOTAL_STEPS_NEAR_TO_FAR 52 +static uint32_t imx074_l_region_code_per_step = 2; + +struct imx074_work_t { + struct work_struct work; +}; + +static struct imx074_work_t *imx074_sensorw; +static struct i2c_client *imx074_client; + +struct imx074_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + uint32_t sensormode; + uint32_t fps_divider;/* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider;/* init to 1 * 0x00000400 */ + uint16_t fps; + int16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + enum imx074_resolution_t prev_res; + enum imx074_resolution_t pict_res; + enum imx074_resolution_t curr_res; + enum imx074_test_mode_t set_test; + unsigned short imgaddr; + + struct v4l2_subdev *sensor_dev; + struct imx074_format *fmt; +}; +static uint8_t imx074_delay_msecs_stdby = 5; +static uint16_t imx074_delay_msecs_stream = 5; +static int32_t config_csi; + +static struct imx074_ctrl_t *imx074_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(imx074_wait_queue); +DEFINE_MUTEX(imx074_mut); + +struct imx074_format { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; + u16 fmt; + u16 order; +}; +/*=============================================================*/ + +static int imx074_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 2, + .buf = rxdata, + }, + }; + if (i2c_transfer(imx074_client->adapter, msgs, 2) < 0) { + CDBG("imx074_i2c_rxdata failed!\n"); + return -EIO; + } + return 0; +} +static int32_t imx074_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(imx074_client->adapter, msg, 1) < 0) { + CDBG("imx074_i2c_txdata faild 0x%x\n", imx074_client->addr); + return -EIO; + } + + return 0; +} + + +static int32_t imx074_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = imx074_i2c_rxdata(imx074_client->addr, buf, rlen); + if (rc < 0) { + CDBG("imx074_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + return rc; +} + +static int imx074_af_i2c_rxdata_b(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 1, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 1, + .buf = rxdata, + }, + }; + + if (i2c_transfer(imx074_client->adapter, msgs, 2) < 0) { + CDBG("imx074_i2c_rxdata_b failed!\n"); + return -EIO; + } + + return 0; +} + +static int32_t imx074_i2c_read_w_eeprom(unsigned short raddr, + unsigned short *rdata) +{ + int32_t rc; + unsigned char buf; + if (!rdata) + return -EIO; + /* Read 2 bytes in sequence */ + buf = (raddr & 0x00FF); + rc = imx074_af_i2c_rxdata_b(IMX074_EEPROM_SLAVE_ADDR, &buf, 1); + if (rc < 0) { + CDBG("imx074_i2c_read_eeprom 0x%x failed!\n", raddr); + return rc; + } + *rdata = buf<<8; + + /* Read Second byte of data */ + buf = (raddr & 0x00FF) + 1; + rc = imx074_af_i2c_rxdata_b(IMX074_EEPROM_SLAVE_ADDR, &buf, 1); + if (rc < 0) { + CDBG("imx074_i2c_read_eeprom 0x%x failed!\n", raddr); + return rc; + } + *rdata |= buf; + return rc; +} + +static int32_t imx074_i2c_write_b_sensor(unsigned short waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = imx074_i2c_txdata(imx074_client->addr, buf, 3); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + return rc; +} +static int16_t imx074_i2c_write_b_af(unsigned short saddr, + unsigned short baddr, unsigned short bdata) +{ + int32_t rc; + unsigned char buf[2]; + memset(buf, 0, sizeof(buf)); + buf[0] = baddr; + buf[1] = bdata; + rc = imx074_i2c_txdata(saddr, buf, 2); + if (rc < 0) + CDBG("AFi2c_write failed, saddr = 0x%x addr = 0x%x, val =0x%x!", + saddr, baddr, bdata); + return rc; +} + +static int32_t imx074_i2c_write_w_table(struct imx074_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < num; i++) { + rc = imx074_i2c_write_b_sensor(reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} +static int16_t imx074_af_init(void) +{ + int32_t rc; + /* Initialize waveform */ + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x01, 0xA9); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x02, 0xD2); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x03, 0x0C); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x04, 0x14); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x05, 0xB6); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x06, 0x4F); + return rc; +} + +static void imx074_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint16_t preview_frame_length_lines, snapshot_frame_length_lines; + uint32_t divider, d1; + uint32_t pclk_mult;/*Q10 */ + /* Total frame_length_lines and line_length_pck for preview */ + preview_frame_length_lines = IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES; + /* Total frame_length_lines and line_length_pck for snapshot */ + snapshot_frame_length_lines = IMX074_FULL_SIZE_HEIGHT + + IMX074_VER_FULL_BLK_LINES; + d1 = preview_frame_length_lines * 0x00010000 / + snapshot_frame_length_lines; + pclk_mult = + (uint32_t) ((imx074_regs.reg_pat[RES_CAPTURE].pll_multiplier * + 0x00010000) / + (imx074_regs.reg_pat[RES_PREVIEW].pll_multiplier)); + divider = d1 * pclk_mult / 0x00010000; + *pfps = (uint16_t) (fps * divider / 0x00010000); +} + +static uint16_t imx074_get_prev_lines_pf(void) +{ + if (imx074_ctrl->prev_res == QTR_SIZE) + return IMX074_QTR_SIZE_HEIGHT + IMX074_VER_QTR_BLK_LINES; + else + return IMX074_FULL_SIZE_HEIGHT + IMX074_VER_FULL_BLK_LINES; + +} + +static uint16_t imx074_get_prev_pixels_pl(void) +{ + if (imx074_ctrl->prev_res == QTR_SIZE) + return IMX074_QTR_SIZE_WIDTH + IMX074_HRZ_QTR_BLK_PIXELS; + else + return IMX074_FULL_SIZE_WIDTH + IMX074_HRZ_FULL_BLK_PIXELS; +} + +static uint16_t imx074_get_pict_lines_pf(void) +{ + if (imx074_ctrl->pict_res == QTR_SIZE) + return IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES; + else + return IMX074_FULL_SIZE_HEIGHT + + IMX074_VER_FULL_BLK_LINES; +} + +static uint16_t imx074_get_pict_pixels_pl(void) +{ + if (imx074_ctrl->pict_res == QTR_SIZE) + return IMX074_QTR_SIZE_WIDTH + + IMX074_HRZ_QTR_BLK_PIXELS; + else + return IMX074_FULL_SIZE_WIDTH + + IMX074_HRZ_FULL_BLK_PIXELS; +} + +static uint32_t imx074_get_pict_max_exp_lc(void) +{ + if (imx074_ctrl->pict_res == QTR_SIZE) + return (IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES)*24; + else + return (IMX074_FULL_SIZE_HEIGHT + + IMX074_VER_FULL_BLK_LINES)*24; +} + +static int32_t imx074_set_fps(struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + imx074_ctrl->fps_divider = fps->fps_div; + imx074_ctrl->pict_fps_divider = fps->pict_fps_div; + total_lines_per_frame = (uint16_t)(((IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES) * imx074_ctrl->fps_divider) / 0x400); + if (imx074_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_HI, + ((total_lines_per_frame & 0xFF00) >> 8)) < 0) + return rc; + if (imx074_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_LO, + (total_lines_per_frame & 0x00FF)) < 0) + return rc; + return rc; +} + +static int32_t imx074_write_exp_gain(uint16_t gain, uint32_t line) +{ + static uint16_t max_legal_gain = 0x00E0; + uint8_t gain_msb, gain_lsb; + uint8_t intg_time_msb, intg_time_lsb; + uint8_t frame_length_line_msb, frame_length_line_lsb; + uint16_t frame_length_lines; + int32_t rc = -1; + CDBG("imx074_write_exp_gain : gain = %d line = %d", gain, line); + if (imx074_ctrl->curr_res == QTR_SIZE) { + frame_length_lines = IMX074_QTR_SIZE_HEIGHT + + IMX074_VER_QTR_BLK_LINES; + frame_length_lines = frame_length_lines * + imx074_ctrl->fps_divider / 0x400; + } else { + frame_length_lines = IMX074_FULL_SIZE_HEIGHT + + IMX074_VER_FULL_BLK_LINES; + frame_length_lines = frame_length_lines * + imx074_ctrl->pict_fps_divider / 0x400; + } + if (line > (frame_length_lines - IMX074_OFFSET)) + frame_length_lines = line + IMX074_OFFSET; + + CDBG("imx074 setting line = %d\n", line); + + + CDBG("imx074 setting frame_length_lines = %d\n", + frame_length_lines); + + if (gain > max_legal_gain) + /* range: 0 to 224 */ + gain = max_legal_gain; + + /* update gain registers */ + gain_msb = (uint8_t) ((gain & 0xFF00) >> 8); + gain_lsb = (uint8_t) (gain & 0x00FF); + + frame_length_line_msb = (uint8_t) ((frame_length_lines & 0xFF00) >> 8); + frame_length_line_lsb = (uint8_t) (frame_length_lines & 0x00FF); + + /* update line count registers */ + intg_time_msb = (uint8_t) ((line & 0xFF00) >> 8); + intg_time_lsb = (uint8_t) (line & 0x00FF); + + rc = imx074_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + CDBG("imx074 setting REG_ANALOGUE_GAIN_CODE_GLOBAL_HI = 0x%X\n", + gain_msb); + rc = imx074_i2c_write_b_sensor(REG_ANALOGUE_GAIN_CODE_GLOBAL_HI, + gain_msb); + if (rc < 0) + return rc; + CDBG("imx074 setting REG_ANALOGUE_GAIN_CODE_GLOBAL_LO = 0x%X\n", + gain_lsb); + rc = imx074_i2c_write_b_sensor(REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + gain_lsb); + if (rc < 0) + return rc; + + CDBG("imx074 setting REG_FRAME_LENGTH_LINES_HI = 0x%X\n", + frame_length_line_msb); + rc = imx074_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_HI, + frame_length_line_msb); + if (rc < 0) + return rc; + + CDBG("imx074 setting REG_FRAME_LENGTH_LINES_LO = 0x%X\n", + frame_length_line_lsb); + rc = imx074_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_LO, + frame_length_line_lsb); + if (rc < 0) + return rc; + + CDBG("imx074 setting REG_COARSE_INTEGRATION_TIME_HI = 0x%X\n", + intg_time_msb); + rc = imx074_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_HI, + intg_time_msb); + if (rc < 0) + return rc; + + CDBG("imx074 setting REG_COARSE_INTEGRATION_TIME_LO = 0x%X\n", + intg_time_lsb); + rc = imx074_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_LO, + intg_time_lsb); + if (rc < 0) + return rc; + + rc = imx074_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t imx074_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + rc = imx074_write_exp_gain(gain, line); + return rc; +} + +static int32_t imx074_move_focus(int direction, + int32_t num_steps) +{ + int32_t step_direction, dest_step_position, bit_mask; + int32_t rc = 0; + + if (num_steps == 0) + return rc; + + if (direction == MOVE_NEAR) { + step_direction = 1; + bit_mask = 0x80; + } else if (direction == MOVE_FAR) { + step_direction = -1; + bit_mask = 0x00; + } else { + CDBG("imx074_move_focus: Illegal focus direction"); + return -EINVAL; + } + dest_step_position = imx074_ctrl->curr_step_pos + + (step_direction * num_steps); + if (dest_step_position < 0) + dest_step_position = 0; + else if (dest_step_position > IMX074_TOTAL_STEPS_NEAR_TO_FAR) + dest_step_position = IMX074_TOTAL_STEPS_NEAR_TO_FAR; + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x00, + ((num_steps * imx074_l_region_code_per_step) | bit_mask)); + CDBG("%s: Index: %d\n", __func__, dest_step_position); + imx074_ctrl->curr_step_pos = dest_step_position; + return rc; +} + + +static int32_t imx074_set_default_focus(uint8_t af_step) +{ + int32_t rc; + /* Initialize to infinity */ + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x00, 0x7F); + rc = imx074_i2c_write_b_af(IMX074_AF_I2C_SLAVE_ID, 0x00, 0x7F); + imx074_ctrl->curr_step_pos = 0; + return rc; +} +static int32_t imx074_test(enum imx074_test_mode_t mo) +{ + int32_t rc = 0; + if (mo == TEST_OFF) + return rc; + else { + /* Set mo to 2 inorder to enable test pattern*/ + if (imx074_i2c_write_b_sensor(REG_TEST_PATTERN_MODE, + (uint8_t) mo) < 0) { + return rc; + } + } + return rc; +} +static int32_t imx074_sensor_setting(int update_type, int rt) +{ + int32_t rc = 0; + struct msm_camera_csid_params imx074_csid_params; + struct msm_camera_csiphy_params imx074_csiphy_params; + switch (update_type) { + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct imx074_i2c_reg_conf init_tbl[] = { + {REG_PRE_PLL_CLK_DIV, + imx074_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLSTATIM, + imx074_regs.reg_pat_init[0]. + plstatim}, + {REG_3024, + imx074_regs.reg_pat_init[0]. + reg_3024}, + {REG_IMAGE_ORIENTATION, + imx074_regs.reg_pat_init[0]. + image_orientation}, + {REG_VNDMY_ABLMGSHLMT, + imx074_regs.reg_pat_init[0]. + vndmy_ablmgshlmt}, + {REG_Y_OPBADDR_START_DI, + imx074_regs.reg_pat_init[0]. + y_opbaddr_start_di}, + {REG_3015, + imx074_regs.reg_pat_init[0]. + reg_0x3015}, + {REG_301C, + imx074_regs.reg_pat_init[0]. + reg_0x301c}, + {REG_302C, + imx074_regs.reg_pat_init[0]. + reg_0x302c}, + {REG_3031, + imx074_regs.reg_pat_init[0].reg_0x3031}, + {REG_3041, + imx074_regs.reg_pat_init[0].reg_0x3041}, + {REG_3051, + imx074_regs.reg_pat_init[0].reg_0x3051}, + {REG_3053, + imx074_regs.reg_pat_init[0].reg_0x3053}, + {REG_3057, + imx074_regs.reg_pat_init[0].reg_0x3057}, + {REG_305C, + imx074_regs.reg_pat_init[0].reg_0x305c}, + {REG_305D, + imx074_regs.reg_pat_init[0].reg_0x305d}, + {REG_3060, + imx074_regs.reg_pat_init[0].reg_0x3060}, + {REG_3065, + imx074_regs.reg_pat_init[0].reg_0x3065}, + {REG_30AA, + imx074_regs.reg_pat_init[0].reg_0x30aa}, + {REG_30AB, + imx074_regs.reg_pat_init[0].reg_0x30ab}, + {REG_30B0, + imx074_regs.reg_pat_init[0].reg_0x30b0}, + {REG_30B2, + imx074_regs.reg_pat_init[0].reg_0x30b2}, + {REG_30D3, + imx074_regs.reg_pat_init[0].reg_0x30d3}, + {REG_3106, + imx074_regs.reg_pat_init[0].reg_0x3106}, + {REG_310C, + imx074_regs.reg_pat_init[0].reg_0x310c}, + {REG_3304, + imx074_regs.reg_pat_init[0].reg_0x3304}, + {REG_3305, + imx074_regs.reg_pat_init[0].reg_0x3305}, + {REG_3306, + imx074_regs.reg_pat_init[0].reg_0x3306}, + {REG_3307, + imx074_regs.reg_pat_init[0].reg_0x3307}, + {REG_3308, + imx074_regs.reg_pat_init[0].reg_0x3308}, + {REG_3309, + imx074_regs.reg_pat_init[0].reg_0x3309}, + {REG_330A, + imx074_regs.reg_pat_init[0].reg_0x330a}, + {REG_330B, + imx074_regs.reg_pat_init[0].reg_0x330b}, + {REG_330C, + imx074_regs.reg_pat_init[0].reg_0x330c}, + {REG_330D, + imx074_regs.reg_pat_init[0].reg_0x330d}, + {REG_330F, + imx074_regs.reg_pat_init[0].reg_0x330f}, + {REG_3381, + imx074_regs.reg_pat_init[0].reg_0x3381}, + }; + struct imx074_i2c_reg_conf init_mode_tbl[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + {REG_PLL_MULTIPLIER, + imx074_regs.reg_pat[rt]. + pll_multiplier}, + {REG_FRAME_LENGTH_LINES_HI, + imx074_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + imx074_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_YADDR_START , + imx074_regs.reg_pat[rt]. + y_addr_start}, + {REG_YAAAR_END, + imx074_regs.reg_pat[rt]. + y_add_end}, + {REG_X_OUTPUT_SIZE_MSB, + imx074_regs.reg_pat[rt]. + x_output_size_msb}, + {REG_X_OUTPUT_SIZE_LSB, + imx074_regs.reg_pat[rt]. + x_output_size_lsb}, + {REG_Y_OUTPUT_SIZE_MSB, + imx074_regs.reg_pat[rt]. + y_output_size_msb}, + {REG_Y_OUTPUT_SIZE_LSB , + imx074_regs.reg_pat[rt]. + y_output_size_lsb}, + {REG_X_EVEN_INC, + imx074_regs.reg_pat[rt]. + x_even_inc}, + {REG_X_ODD_INC, + imx074_regs.reg_pat[rt]. + x_odd_inc}, + {REG_Y_EVEN_INC, + imx074_regs.reg_pat[rt]. + y_even_inc}, + {REG_Y_ODD_INC, + imx074_regs.reg_pat[rt]. + y_odd_inc}, + {REG_HMODEADD, + imx074_regs.reg_pat[rt]. + hmodeadd}, + {REG_VMODEADD, + imx074_regs.reg_pat[rt]. + vmodeadd}, + {REG_VAPPLINE_START, + imx074_regs.reg_pat[rt]. + vapplinepos_start}, + {REG_VAPPLINE_END, + imx074_regs.reg_pat[rt]. + vapplinepos_end}, + {REG_SHUTTER, + imx074_regs.reg_pat[rt]. + shutter}, + {REG_HADDAVE, + imx074_regs.reg_pat[rt]. + haddave}, + {REG_LANESEL, + imx074_regs.reg_pat[rt]. + lanesel}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF}, + + }; + /* reset fps_divider */ + imx074_ctrl->fps = 30 * Q8; + imx074_ctrl->fps_divider = 1 * 0x400; + /* stop streaming */ + rc = imx074_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE); + if (rc < 0) + return rc; + msleep(imx074_delay_msecs_stdby); + rc = imx074_i2c_write_w_table(&init_tbl[0], + ARRAY_SIZE(init_tbl)); + if (rc < 0) + return rc; + rc = imx074_i2c_write_w_table(&init_mode_tbl[0], + ARRAY_SIZE(init_mode_tbl)); + if (rc < 0) + return rc; + rc = imx074_test(imx074_ctrl->set_test); + return rc; + } + break; + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct imx074_i2c_reg_conf mode_tbl[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + {REG_PLL_MULTIPLIER, + imx074_regs.reg_pat[rt]. + pll_multiplier}, + {REG_FRAME_LENGTH_LINES_HI, + imx074_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + imx074_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_YADDR_START , + imx074_regs.reg_pat[rt]. + y_addr_start}, + {REG_YAAAR_END, + imx074_regs.reg_pat[rt]. + y_add_end}, + {REG_X_OUTPUT_SIZE_MSB, + imx074_regs.reg_pat[rt]. + x_output_size_msb}, + {REG_X_OUTPUT_SIZE_LSB, + imx074_regs.reg_pat[rt]. + x_output_size_lsb}, + {REG_Y_OUTPUT_SIZE_MSB, + imx074_regs.reg_pat[rt]. + y_output_size_msb}, + {REG_Y_OUTPUT_SIZE_LSB , + imx074_regs.reg_pat[rt]. + y_output_size_lsb}, + {REG_X_EVEN_INC, + imx074_regs.reg_pat[rt]. + x_even_inc}, + {REG_X_ODD_INC, + imx074_regs.reg_pat[rt]. + x_odd_inc}, + {REG_Y_EVEN_INC, + imx074_regs.reg_pat[rt]. + y_even_inc}, + {REG_Y_ODD_INC, + imx074_regs.reg_pat[rt]. + y_odd_inc}, + {REG_HMODEADD, + imx074_regs.reg_pat[rt]. + hmodeadd}, + {REG_VMODEADD, + imx074_regs.reg_pat[rt]. + vmodeadd}, + {REG_VAPPLINE_START, + imx074_regs.reg_pat[rt]. + vapplinepos_start}, + {REG_VAPPLINE_END, + imx074_regs.reg_pat[rt]. + vapplinepos_end}, + {REG_SHUTTER, + imx074_regs.reg_pat[rt]. + shutter}, + {REG_HADDAVE, + imx074_regs.reg_pat[rt]. + haddave}, + {REG_LANESEL, + imx074_regs.reg_pat[rt]. + lanesel}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF}, + }; + + /* stop streaming */ + rc = imx074_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE); + msleep(imx074_delay_msecs_stdby); + rc = imx074_i2c_write_w_table(&mode_tbl[0], + ARRAY_SIZE(mode_tbl)); + if (config_csi == 0) { + struct msm_camera_csid_vc_cfg imx074_vccfg[] = { + {0, CSI_RAW10, CSI_DECODE_10BIT}, + {1, CSI_EMBED_DATA, CSI_DECODE_8BIT}, + }; + imx074_csid_params.lane_cnt = 4; + imx074_csid_params.lane_assign = 0xe4; + imx074_csid_params.lut_params.num_cid = + ARRAY_SIZE(imx074_vccfg); + imx074_csid_params.lut_params.vc_cfg = + &imx074_vccfg[0]; + imx074_csiphy_params.lane_cnt = 4; + imx074_csiphy_params.settle_cnt = 0x1B; + rc = msm_camio_csid_config(&imx074_csid_params); + v4l2_subdev_notify(imx074_ctrl->sensor_dev, + NOTIFY_CID_CHANGE, NULL); + mb(); + rc = msm_camio_csiphy_config + (&imx074_csiphy_params); + mb(); + /*imx074_delay_msecs_stdby*/ + msleep(imx074_delay_msecs_stream); + config_csi = 1; + } + if (rc < 0) + return rc; + rc = imx074_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STREAM); + if (rc < 0) + return rc; + msleep(imx074_delay_msecs_stream); + } + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + + +static int32_t imx074_video_config(int mode) +{ + + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + if (imx074_ctrl->prev_res == QTR_SIZE) + rt = RES_PREVIEW; + else + rt = RES_CAPTURE; + + if (imx074_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + imx074_ctrl->curr_res = imx074_ctrl->prev_res; + imx074_ctrl->sensormode = mode; + return rc; +} + +static int32_t imx074_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt = RES_PREVIEW; /* TODO: Used without initialization, guessing. */ + /* change sensor resolution if needed */ + if (imx074_ctrl->curr_res != imx074_ctrl->pict_res) { + if (imx074_ctrl->pict_res == QTR_SIZE) + rt = RES_PREVIEW; + else + rt = RES_CAPTURE; + } + if (imx074_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + imx074_ctrl->curr_res = imx074_ctrl->pict_res; + imx074_ctrl->sensormode = mode; + return rc; +} +static int32_t imx074_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt = RES_PREVIEW; /* TODO: Used without initialization, guessing. */ + /* change sensor resolution if needed */ + if (imx074_ctrl->curr_res != imx074_ctrl->pict_res) { + if (imx074_ctrl->pict_res == QTR_SIZE) + rt = RES_PREVIEW; + else + rt = RES_CAPTURE; + } + if (imx074_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + imx074_ctrl->curr_res = imx074_ctrl->pict_res; + imx074_ctrl->sensormode = mode; + return rc; +} +static int32_t imx074_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = imx074_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + rc = imx074_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + rc = imx074_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} +static int32_t imx074_power_down(void) +{ + imx074_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE); + msleep(imx074_delay_msecs_stdby); + return 0; +} +static int imx074_probe_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_set_value_cansleep(data->sensor_reset, 0); + gpio_direction_input(data->sensor_reset); + gpio_free(data->sensor_reset); + return 0; +} + +static int imx074_read_eeprom_data(struct sensor_cfg_data *cfg) +{ + int32_t rc = 0; + uint16_t eepromdata = 0; + uint8_t addr = 0; + + addr = 0x10; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.r_over_g = eepromdata; + + addr = 0x12; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.b_over_g = eepromdata; + + addr = 0x14; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.gr_over_gb = eepromdata; + + addr = 0x1A; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.macro_2_inf = eepromdata; + + addr = 0x1C; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.inf_2_macro = eepromdata; + + addr = 0x1E; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.stroke_amt = eepromdata; + + addr = 0x20; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.af_pos_1m = eepromdata; + + addr = 0x22; + rc = imx074_i2c_read_w_eeprom(addr, &eepromdata); + if (rc < 0) { + CDBG("%s: Error Reading EEPROM @ 0x%x\n", __func__, addr); + return rc; + } + cfg->cfg.calib_info.af_pos_inf = eepromdata; + + return rc; +} + +static int imx074_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + unsigned short chipidl, chipidh; + CDBG("%s: %d\n", __func__, __LINE__); + rc = gpio_request(data->sensor_reset, "imx074"); + CDBG("imx074_probe_init_sensor\n"); + if (!rc) { + CDBG("sensor_reset = %d\n", rc); + gpio_direction_output(data->sensor_reset, 0); + usleep_range(5000, 6000); + gpio_set_value_cansleep(data->sensor_reset, 1); + usleep_range(5000, 6000); + } else { + CDBG("gpio reset fail"); + goto init_probe_done; + } + CDBG("imx074_probe_init_sensor is called\n"); + /* 3. Read sensor Model ID: */ + rc = imx074_i2c_read(0x0000, &chipidh, 1); + if (rc < 0) { + CDBG("Model read failed\n"); + goto init_probe_fail; + } + rc = imx074_i2c_read(0x0001, &chipidl, 1); + if (rc < 0) { + CDBG("Model read failed\n"); + goto init_probe_fail; + } + CDBG("imx074 model_id = 0x%x 0x%x\n", chipidh, chipidl); + /* 4. Compare sensor ID to IMX074 ID: */ + if (chipidh != 0x00 || chipidl != 0x74) { + rc = -ENODEV; + CDBG("imx074_probe_init_sensor fail chip id doesnot match\n"); + goto init_probe_fail; + } + goto init_probe_done; +init_probe_fail: + CDBG("imx074_probe_init_sensor fails\n"); + imx074_probe_init_done(data); +init_probe_done: + CDBG(" imx074_probe_init_sensor finishes\n"); + return rc; + } +static int32_t imx074_poweron_af(void) +{ + int32_t rc = 0; + CDBG("imx074 enable AF actuator, gpio = %d\n", + imx074_ctrl->sensordata->vcm_pwd); + rc = gpio_request(imx074_ctrl->sensordata->vcm_pwd, "imx074"); + if (!rc) { + gpio_direction_output(imx074_ctrl->sensordata->vcm_pwd, 1); + msleep(20); + rc = imx074_af_init(); + if (rc < 0) + CDBG("imx074 AF initialisation failed\n"); + } else { + CDBG("%s: AF PowerON gpio_request failed %d\n", __func__, rc); + } + return rc; +} +static void imx074_poweroff_af(void) +{ + gpio_set_value_cansleep(imx074_ctrl->sensordata->vcm_pwd, 0); + gpio_free(imx074_ctrl->sensordata->vcm_pwd); +} +/* camsensor_iu060f_imx074_reset */ +int imx074_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + CDBG("%s: %d\n", __func__, __LINE__); + CDBG("Calling imx074_sensor_open_init\n"); + imx074_ctrl->fps_divider = 1 * 0x00000400; + imx074_ctrl->pict_fps_divider = 1 * 0x00000400; + imx074_ctrl->fps = 30 * Q8; + imx074_ctrl->set_test = TEST_OFF; + imx074_ctrl->prev_res = QTR_SIZE; + imx074_ctrl->pict_res = FULL_SIZE; + imx074_ctrl->curr_res = INVALID_SIZE; + config_csi = 0; + + if (data) + imx074_ctrl->sensordata = data; + + /* enable mclk first */ + msm_camio_clk_rate_set(IMX074_DEFAULT_MASTER_CLK_RATE); + usleep_range(1000, 2000); + rc = imx074_probe_init_sensor(data); + if (rc < 0) { + CDBG("Calling imx074_sensor_open_init fail\n"); + goto probe_fail; + } + + rc = imx074_sensor_setting(REG_INIT, RES_PREVIEW); + if (rc < 0) { + CDBG("imx074_sensor_setting failed\n"); + goto init_fail; + } + if (machine_is_msm8x60_fluid()) + rc = imx074_poweron_af(); + else + rc = imx074_af_init(); + if (rc < 0) { + CDBG("AF initialisation failed\n"); + goto init_fail; + } else + goto init_done; +probe_fail: + CDBG(" imx074_sensor_open_init probe fail\n"); + kfree(imx074_ctrl); + return rc; +init_fail: + CDBG(" imx074_sensor_open_init fail\n"); + imx074_probe_init_done(data); + kfree(imx074_ctrl); +init_done: + CDBG("imx074_sensor_open_init done\n"); + return rc; +} +static int imx074_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&imx074_wait_queue); + return 0; +} + +static const struct i2c_device_id imx074_i2c_id[] = { + {"imx074", 0}, + { } +}; + +static int imx074_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("imx074_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + imx074_sensorw = kzalloc(sizeof(struct imx074_work_t), GFP_KERNEL); + if (!imx074_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, imx074_sensorw); + imx074_init_client(client); + imx074_client = client; + + + CDBG("imx074_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("imx074_probe failed! rc = %d\n", rc); + return rc; +} + +static int __exit imx074_remove(struct i2c_client *client) +{ + struct imx074_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + imx074_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver imx074_i2c_driver = { + .id_table = imx074_i2c_id, + .probe = imx074_i2c_probe, + .remove = __exit_p(imx074_i2c_remove), + .driver = { + .name = "imx074", + }, +}; + +int imx074_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&imx074_mut); + CDBG("imx074_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + imx074_get_pict_fps( + cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + imx074_get_prev_lines_pf(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + imx074_get_prev_pixels_pl(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + imx074_get_pict_lines_pf(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + imx074_get_pict_pixels_pl(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + imx074_get_pict_max_exp_lc(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = imx074_set_fps(&(cdata.cfg.fps)); + break; + case CFG_SET_EXP_GAIN: + rc = + imx074_write_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_PICT_EXP_GAIN: + rc = + imx074_set_pict_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_MODE: + rc = imx074_set_sensor_mode(cdata.mode, + cdata.rs); + break; + case CFG_PWR_DOWN: + rc = imx074_power_down(); + break; + case CFG_GET_CALIB_DATA: + rc = imx074_read_eeprom_data(&cdata); + if (rc < 0) + break; + if (copy_to_user((void *)argp, + &cdata, + sizeof(cdata))) + rc = -EFAULT; + break; + case CFG_MOVE_FOCUS: + rc = + imx074_move_focus( + cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + case CFG_SET_DEFAULT_FOCUS: + rc = + imx074_set_default_focus( + cdata.cfg.focus.steps); + break; + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = IMX074_STEPS_NEAR_TO_CLOSEST_INF; + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_SET_EFFECT: + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&imx074_mut); + + return rc; +} +static int imx074_sensor_release(void) +{ + int rc = -EBADF; + mutex_lock(&imx074_mut); + if (machine_is_msm8x60_fluid()) + imx074_poweroff_af(); + imx074_power_down(); + gpio_set_value_cansleep(imx074_ctrl->sensordata->sensor_reset, 0); + usleep_range(5000, 6000); + gpio_direction_input(imx074_ctrl->sensordata->sensor_reset); + gpio_free(imx074_ctrl->sensordata->sensor_reset); + CDBG("imx074_release completed\n"); + mutex_unlock(&imx074_mut); + + return rc; +} + +static int imx074_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(&imx074_i2c_driver); + if (rc < 0 || imx074_client == NULL) { + rc = -ENOTSUPP; + goto probe_fail; + } + msm_camio_clk_rate_set(IMX074_DEFAULT_MASTER_CLK_RATE); + rc = imx074_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + s->s_init = imx074_sensor_open_init; + s->s_release = imx074_sensor_release; + s->s_config = imx074_sensor_config; + s->s_mount_angle = info->sensor_platform_info->mount_angle; + imx074_probe_init_done(info); + return rc; + +probe_fail: + CDBG("imx074_sensor_probe: SENSOR PROBE FAILS!\n"); + i2c_del_driver(&imx074_i2c_driver); + return rc; +} + +static struct imx074_format imx074_subdev_info[] = { + { + .code = V4L2_MBUS_FMT_SBGGR10_1X10, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 0, + }, + /* more can be supported, to be added later */ +}; + +static int imx074_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + printk(KERN_DEBUG "Index is %d\n", index); + if ((unsigned int)index >= ARRAY_SIZE(imx074_subdev_info)) + return -EINVAL; + + *code = imx074_subdev_info[index].code; + return 0; +} + +static struct v4l2_subdev_core_ops imx074_subdev_core_ops; +static struct v4l2_subdev_video_ops imx074_subdev_video_ops = { + .enum_mbus_fmt = imx074_enum_fmt, +}; + +static struct v4l2_subdev_ops imx074_subdev_ops = { + .core = &imx074_subdev_core_ops, + .video = &imx074_subdev_video_ops, +}; + + +static int imx074_sensor_probe_cb(const struct msm_camera_sensor_info *info, + struct v4l2_subdev *sdev, struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = imx074_sensor_probe(info, s); + if (rc < 0) + return rc; + + imx074_ctrl = kzalloc(sizeof(struct imx074_ctrl_t), GFP_KERNEL); + if (!imx074_ctrl) { + CDBG("imx074_sensor_probe failed!\n"); + return -ENOMEM; + } + + /* probe is successful, init a v4l2 subdevice */ + printk(KERN_DEBUG "going into v4l2_i2c_subdev_init\n"); + if (sdev) { + v4l2_i2c_subdev_init(sdev, imx074_client, + &imx074_subdev_ops); + imx074_ctrl->sensor_dev = sdev; + } + return rc; +} + +static int __imx074_probe(struct platform_device *pdev) +{ + return msm_sensor_register(pdev, imx074_sensor_probe_cb); +} + +static struct platform_driver msm_camera_driver = { + .probe = __imx074_probe, + .driver = { + .name = "msm_camera_imx074", + .owner = THIS_MODULE, + }, +}; + +static int __init imx074_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(imx074_init); + +MODULE_DESCRIPTION("Sony 13 MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/media/video/msm/msm.c b/drivers/media/video/msm/msm.c new file mode 100644 index 00000000000..da284035ba0 --- /dev/null +++ b/drivers/media/video/msm/msm.c @@ -0,0 +1,2080 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "msm.h" + + +#define MSM_MAX_CAMERA_SENSORS 5 + +#ifdef CONFIG_MSM_CAMERA_DEBUG +#define D(fmt, args...) pr_debug("msm: " fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif + +static unsigned msm_camera_v4l2_nr = -1; +static struct msm_cam_server_dev g_server_dev; +static struct class *msm_class; +static dev_t msm_devno; +static int vnode_count; + +module_param(msm_camera_v4l2_nr, uint, 0644); +MODULE_PARM_DESC(msm_camera_v4l2_nr, "videoX start number, -1 is autodetect"); + +static int msm_setup_v4l2_event_queue(struct v4l2_fh *eventHandle, + struct video_device *pvdev); + +static void msm_queue_init(struct msm_device_queue *queue, const char *name) +{ + D("%s\n", __func__); + spin_lock_init(&queue->lock); + queue->len = 0; + queue->max = 0; + queue->name = name; + INIT_LIST_HEAD(&queue->list); + init_waitqueue_head(&queue->wait); +} + +static void msm_enqueue(struct msm_device_queue *queue, + struct list_head *entry) +{ + unsigned long flags; + spin_lock_irqsave(&queue->lock, flags); + queue->len++; + if (queue->len > queue->max) { + queue->max = queue->len; + pr_info("%s: queue %s new max is %d\n", __func__, + queue->name, queue->max); + } + list_add_tail(entry, &queue->list); + wake_up(&queue->wait); + D("%s: woke up %s\n", __func__, queue->name); + spin_unlock_irqrestore(&queue->lock, flags); +} + +/* callback function from all subdevices of a msm_cam_v4l2_device */ +static void msm_cam_v4l2_subdev_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + struct msm_cam_v4l2_device *pcam; + + if (sd == NULL) + return; + + pcam = to_pcam(sd->v4l2_dev); + + if (pcam == NULL) + return; + + /* forward to media controller for any changes*/ + if (pcam->mctl.mctl_notify) { + pcam->mctl.mctl_notify(&pcam->mctl, notification, arg); + } +} + +static int msm_ctrl_cmd_done(void __user *arg) +{ + void __user *uptr; + struct msm_queue_cmd *qcmd; + struct msm_ctrl_cmd *command = &g_server_dev.ctrl; + + D("%s\n", __func__); + + if (copy_from_user(command, arg, + sizeof(struct msm_ctrl_cmd))) + return -EINVAL; + + qcmd = kzalloc(sizeof(struct msm_queue_cmd), GFP_KERNEL); + atomic_set(&qcmd->on_heap, 0); + uptr = command->value; + qcmd->command = command; + + if (command->length > 0) { + command->value = g_server_dev.ctrl_data; + if (command->length > sizeof(g_server_dev.ctrl_data)) { + pr_err("%s: user data %d is too big (max %d)\n", + __func__, command->length, + sizeof(g_server_dev.ctrl_data)); + return -EINVAL; + } + if (copy_from_user(command->value, uptr, command->length)) + return -EINVAL; + } + + msm_enqueue(&g_server_dev.ctrl_q, &qcmd->list_control); + return 0; +} + +/* send control command to config and wait for results*/ +static int msm_server_control(struct msm_cam_server_dev *server_dev, + struct msm_ctrl_cmd *out) +{ + int rc = 0; + void *value; + struct msm_queue_cmd *rcmd; + struct msm_ctrl_cmd *ctrlcmd; + struct msm_device_queue *queue = &server_dev->ctrl_q; + + struct v4l2_event v4l2_evt; + struct msm_isp_stats_event_ctrl *isp_event; + D("%s\n", __func__); + + v4l2_evt.type = V4L2_EVENT_PRIVATE_START + MSM_CAM_RESP_V4L2; + + /* setup event object to transfer the command; */ + isp_event = (struct msm_isp_stats_event_ctrl *)v4l2_evt.u.data; + isp_event->resptype = MSM_CAM_RESP_V4L2; + isp_event->isp_data.ctrl = *out; + + /* now send command to config thread in usersspace, + * and wait for results */ + v4l2_event_queue(server_dev->server_command_queue.pvdev, + &v4l2_evt); + + D("%s v4l2_event_queue: type = 0x%x\n", __func__, v4l2_evt.type); + + /* wait for config return status */ + D("Waiting for config status\n"); + rc = wait_event_interruptible_timeout(queue->wait, + !list_empty_careful(&queue->list), + out->timeout_ms); + D("Waiting is over for config status\n"); + if (list_empty_careful(&queue->list)) { + if (!rc) + rc = -ETIMEDOUT; + if (rc < 0) { + pr_err("%s: wait_event error %d\n", __func__, rc); + return rc; + } + } + + rcmd = msm_dequeue(queue, list_control); + BUG_ON(!rcmd); + D("%s Finished servicing ioctl\n", __func__); + + ctrlcmd = (struct msm_ctrl_cmd *)(rcmd->command); + value = out->value; + if (ctrlcmd->length > 0) + memcpy(value, ctrlcmd->value, ctrlcmd->length); + + memcpy(out, ctrlcmd, sizeof(struct msm_ctrl_cmd)); + out->value = value; + + free_qcmd(rcmd); + D("%s: rc %d\n", __func__, rc); + /* rc is the time elapsed. */ + if (rc >= 0) { + /* TODO: Refactor msm_ctrl_cmd::status field */ + if (out->status == 0) + rc = -1; + else if (out->status == 1) + rc = 0; + else + rc = -EINVAL; + } + return rc; +} + +/*send open command to server*/ +static int msm_send_open_server(void) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + D("%s\n", __func__); + ctrlcmd.type = MSM_V4L2_OPEN; + ctrlcmd.timeout_ms = 10000; + ctrlcmd.length = 0; + ctrlcmd.value = NULL; + + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + + return rc; +} + +static int msm_send_close_server(void) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + D("%s\n", __func__); + ctrlcmd.type = MSM_V4L2_CLOSE; + ctrlcmd.timeout_ms = 10000; + ctrlcmd.length = 0; + ctrlcmd.value = NULL; + + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + + return rc; +} + +static int msm_server_set_fmt(struct msm_cam_v4l2_device *pcam, int idx, + struct v4l2_format *pfmt) +{ + int rc = 0; + int i = 0; + struct v4l2_pix_format *pix = &pfmt->fmt.pix; + struct msm_ctrl_cmd ctrlcmd; + + D("%s: %d, %d, 0x%x\n", __func__, + pfmt->fmt.pix.width, pfmt->fmt.pix.height, + pfmt->fmt.pix.pixelformat); + + if (pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + D("%s, Attention! Wrong buf-type %d\n", __func__, pfmt->type); + + for (i = 0; i < pcam->num_fmts; i++) + if (pcam->usr_fmts[i].fourcc == pix->pixelformat) + break; + if (i == pcam->num_fmts) { + pr_err("%s: User requested pixelformat %x not supported\n", + __func__, pix->pixelformat); + return -EINVAL; + } + + ctrlcmd.type = MSM_V4L2_VID_CAP_TYPE; + ctrlcmd.length = MSM_V4L2_DIMENSION_SIZE; + ctrlcmd.value = (void *)pfmt->fmt.pix.priv; + ctrlcmd.timeout_ms = 10000; + + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + + if (rc >= 0) { + pcam->dev_inst[idx]->vid_fmt.fmt.pix.width = pix->width; + pcam->dev_inst[idx]->vid_fmt.fmt.pix.height = pix->height; + pcam->dev_inst[idx]->vid_fmt.fmt.pix.field = pix->field; + pcam->dev_inst[idx]->vid_fmt.fmt.pix.pixelformat = + pix->pixelformat; + pcam->dev_inst[idx]->vid_fmt.fmt.pix.bytesperline = + pix->bytesperline; + pcam->dev_inst[idx]->vid_bufq.field = pix->field; + pcam->dev_inst[idx]->sensor_pxlcode + = pcam->usr_fmts[i].pxlcode; + D("%s:inst=0x%x,idx=%d,width=%d,heigth=%d\n", + __func__, (u32)pcam->dev_inst[idx], idx, + pcam->dev_inst[idx]->vid_fmt.fmt.pix.width, + pcam->dev_inst[idx]->vid_fmt.fmt.pix.height); + } + + return rc; +} + +static int msm_server_streamon(struct msm_cam_v4l2_device *pcam, int idx) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + D("%s\n", __func__); + ctrlcmd.type = MSM_V4L2_STREAM_ON; + ctrlcmd.timeout_ms = 10000; + ctrlcmd.length = 0; + ctrlcmd.value = NULL; + ctrlcmd.stream_type = pcam->dev_inst[idx]->image_mode; + ctrlcmd.vnode_id = pcam->vnode_id; + + + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + + return rc; +} + +static int msm_server_streamoff(struct msm_cam_v4l2_device *pcam, int idx) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + + D("%s, pcam = 0x%x\n", __func__, (u32)pcam); + ctrlcmd.type = MSM_V4L2_STREAM_OFF; + ctrlcmd.timeout_ms = 10000; + ctrlcmd.length = 0; + ctrlcmd.value = NULL; + ctrlcmd.stream_type = pcam->dev_inst[idx]->image_mode; + ctrlcmd.vnode_id = pcam->vnode_id; + + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + + return rc; +} + +static int msm_server_proc_ctrl_cmd(struct msm_cam_v4l2_device *pcam, + struct v4l2_control *ctrl, int is_set_cmd) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd, *tmp_cmd; + uint8_t *ctrl_data = NULL; + void __user *uptr_cmd; + void __user *uptr_value; + uint32_t cmd_len = sizeof(struct msm_ctrl_cmd); + uint32_t value_len; + + tmp_cmd = (struct msm_ctrl_cmd *)ctrl->value; + uptr_cmd = (void __user *)ctrl->value; + uptr_value = (void __user *)tmp_cmd->value; + value_len = tmp_cmd->length; + + D("%s: cmd type = %d, up1=0x%x, ulen1=%d, up2=0x%x, ulen2=%d\n", + __func__, tmp_cmd->type, (uint32_t)uptr_cmd, cmd_len, + (uint32_t)uptr_value, tmp_cmd->length); + + ctrl_data = kzalloc(value_len+cmd_len, GFP_KERNEL); + if (ctrl_data == 0) { + pr_err("%s could not allocate memory\n", __func__); + rc = -ENOMEM; + goto end; + } + tmp_cmd = (struct msm_ctrl_cmd *)ctrl_data; + if (copy_from_user((void *)ctrl_data, uptr_cmd, + cmd_len)) { + pr_err("%s: copy_from_user failed.\n", __func__); + rc = -EINVAL; + goto end; + } + tmp_cmd->value = (void *)(ctrl_data+cmd_len); + if (uptr_value && tmp_cmd->length > 0) { + if (copy_from_user((void *)tmp_cmd->value, uptr_value, + value_len)) { + pr_err("%s: copy_from_user failed, size=%d\n", + __func__, value_len); + rc = -EINVAL; + goto end; + } + } else + tmp_cmd->value = NULL; + + ctrlcmd.type = MSM_V4L2_SET_CTRL_CMD; + ctrlcmd.length = cmd_len + value_len; + ctrlcmd.value = (void *)ctrl_data; + ctrlcmd.timeout_ms = 1000; + ctrlcmd.vnode_id = pcam->vnode_id; + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + pr_err("%s: msm_server_control rc=%d\n", __func__, rc); + if (rc == 0) { + if (uptr_value && tmp_cmd->length > 0 && + copy_to_user((void __user *)uptr_value, + (void *)(ctrl_data+cmd_len), tmp_cmd->length)) { + pr_err("%s: copy_to_user failed, size=%d\n", + __func__, tmp_cmd->length); + rc = -EINVAL; + goto end; + } + tmp_cmd->value = uptr_value; + if (copy_to_user((void __user *)uptr_cmd, + (void *)tmp_cmd, cmd_len)) { + pr_err("%s: copy_to_user failed in cpy, size=%d\n", + __func__, cmd_len); + rc = -EINVAL; + goto end; + } + } +end: + pr_err("%s: END, type = %d, vaddr = 0x%x, vlen = %d, status = %d, rc = %d\n", + __func__, tmp_cmd->type, (uint32_t)tmp_cmd->value, + tmp_cmd->length, tmp_cmd->status, rc); + kfree(ctrl_data); + return rc; +} + +static int msm_server_s_ctrl(struct msm_cam_v4l2_device *pcam, + struct v4l2_control *ctrl) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + uint8_t ctrl_data[max_control_command_size]; + + WARN_ON(ctrl == NULL); + + if (ctrl && ctrl->id == MSM_V4L2_PID_CTRL_CMD) + return msm_server_proc_ctrl_cmd(pcam, ctrl, 1); + + memset(ctrl_data, 0, sizeof(ctrl_data)); + + ctrlcmd.type = MSM_V4L2_SET_CTRL; + ctrlcmd.length = sizeof(struct v4l2_control); + ctrlcmd.value = (void *)ctrl_data; + memcpy(ctrlcmd.value, ctrl, ctrlcmd.length); + ctrlcmd.timeout_ms = 1000; + ctrlcmd.vnode_id = pcam->vnode_id; + + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + + return rc; +} + +static int msm_server_g_ctrl(struct msm_cam_v4l2_device *pcam, + struct v4l2_control *ctrl) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + uint8_t ctrl_data[max_control_command_size]; + + WARN_ON(ctrl == NULL); + if (ctrl && ctrl->id == MSM_V4L2_PID_CTRL_CMD) + return msm_server_proc_ctrl_cmd(pcam, ctrl, 0); + + memset(ctrl_data, 0, sizeof(ctrl_data)); + + ctrlcmd.type = MSM_V4L2_GET_CTRL; + ctrlcmd.length = sizeof(struct v4l2_control); + ctrlcmd.value = (void *)ctrl_data; + memcpy(ctrlcmd.value, ctrl, ctrlcmd.length); + ctrlcmd.timeout_ms = 1000; + + /* send command to config thread in usersspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + + ctrl->value = ((struct v4l2_control *)ctrlcmd.value)->value; + + return rc; +} + +static int msm_server_q_ctrl(struct msm_cam_v4l2_device *pcam, + struct v4l2_queryctrl *queryctrl) +{ + int rc = 0; + struct msm_ctrl_cmd ctrlcmd; + uint8_t ctrl_data[max_control_command_size]; + + WARN_ON(queryctrl == NULL); + memset(ctrl_data, 0, sizeof(ctrl_data)); + + ctrlcmd.type = MSM_V4L2_QUERY_CTRL; + ctrlcmd.length = sizeof(struct v4l2_queryctrl); + ctrlcmd.value = (void *)ctrl_data; + memcpy(ctrlcmd.value, queryctrl, ctrlcmd.length); + ctrlcmd.timeout_ms = 1000; + + /* send command to config thread in userspace, and get return value */ + rc = msm_server_control(&g_server_dev, &ctrlcmd); + D("%s: rc = %d\n", __func__, rc); + + if (rc >= 0) + memcpy(queryctrl, ctrlcmd.value, sizeof(struct v4l2_queryctrl)); + + return rc; +} + +static int msm_server_get_fmt(struct msm_cam_v4l2_device *pcam, + int idx, struct v4l2_format *pfmt) +{ + struct v4l2_pix_format *pix = &pfmt->fmt.pix; + + pix->width = pcam->dev_inst[idx]->vid_fmt.fmt.pix.width; + pix->height = pcam->dev_inst[idx]->vid_fmt.fmt.pix.height; + pix->field = pcam->dev_inst[idx]->vid_fmt.fmt.pix.field; + pix->pixelformat = pcam->dev_inst[idx]->vid_fmt.fmt.pix.pixelformat; + pix->bytesperline = pcam->dev_inst[idx]->vid_fmt.fmt.pix.bytesperline; + pix->colorspace = pcam->dev_inst[idx]->vid_fmt.fmt.pix.colorspace; + if (pix->bytesperline < 0) + return pix->bytesperline; + + pix->sizeimage = pix->height * pix->bytesperline; + + return 0; +} + +static int msm_server_try_fmt(struct msm_cam_v4l2_device *pcam, + struct v4l2_format *pfmt) +{ + int rc = 0; + int i = 0; + struct v4l2_pix_format *pix = &pfmt->fmt.pix; + struct v4l2_mbus_framefmt sensor_fmt; + + D("%s: 0x%x\n", __func__, pix->pixelformat); + + if (pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pr_err("%s: pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE!\n", + __func__); + return -EINVAL; + } + + /* check if the format is supported by this host-sensor combo */ + for (i = 0; i < pcam->num_fmts; i++) { + D("%s: usr_fmts.fourcc: 0x%x\n", __func__, + pcam->usr_fmts[i].fourcc); + if (pcam->usr_fmts[i].fourcc == pix->pixelformat) + break; + } + + if (i == pcam->num_fmts) { + pr_err("%s: Format %x not found\n", __func__, pix->pixelformat); + return -EINVAL; + } + + sensor_fmt.width = pix->width; + sensor_fmt.height = pix->height; + sensor_fmt.field = pix->field; + sensor_fmt.colorspace = pix->colorspace; + sensor_fmt.code = pcam->usr_fmts[i].pxlcode; + + pix->width = sensor_fmt.width; + pix->height = sensor_fmt.height; + pix->field = sensor_fmt.field; + pix->colorspace = sensor_fmt.colorspace; + + return rc; +} + +/* + * + * implementation of v4l2_ioctl_ops + * + */ +static int msm_camera_v4l2_querycap(struct file *f, void *pctx, + struct v4l2_capability *pcaps) +{ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + /* some other day, some other time */ + /*cap->version = LINUX_VERSION_CODE; */ + strlcpy(pcaps->driver, pcam->pdev->name, sizeof(pcaps->driver)); + pcaps->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + return 0; +} + +static int msm_camera_v4l2_queryctrl(struct file *f, void *pctx, + struct v4l2_queryctrl *pqctrl) +{ + int rc = 0; + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + mutex_lock(&pcam->vid_lock); + rc = msm_server_q_ctrl(pcam, pqctrl); + mutex_unlock(&pcam->vid_lock); + return rc; +} + +static int msm_camera_v4l2_g_ctrl(struct file *f, void *pctx, + struct v4l2_control *c) +{ + int rc = 0; + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + mutex_lock(&pcam->vid_lock); + rc = msm_server_g_ctrl(pcam, c); + mutex_unlock(&pcam->vid_lock); + + return rc; +} + +static int msm_camera_v4l2_s_ctrl(struct file *f, void *pctx, + struct v4l2_control *ctrl) +{ + int rc = 0; + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + mutex_lock(&pcam->vid_lock); + if (ctrl->id == MSM_V4L2_PID_CAM_MODE) + pcam->op_mode = ctrl->value; + rc = msm_server_s_ctrl(pcam, ctrl); + mutex_unlock(&pcam->vid_lock); + + return rc; +} + +static int msm_camera_v4l2_reqbufs(struct file *f, void *pctx, + struct v4l2_requestbuffers *pb) +{ + int rc = 0; + int i = 0; + /*struct msm_cam_v4l2_device *pcam = video_drvdata(f);*/ + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + if (!pb->count) { + if (pcam_inst->vid_bufq.streaming) + videobuf_stop(&pcam_inst->vid_bufq); + else + videobuf_queue_cancel(&pcam_inst->vid_bufq); + + /* free the queue: function name is ambiguous it frees all + types of buffers (mmap or userptr - it doesn't matter) */ + rc = videobuf_mmap_free(&pcam_inst->vid_bufq); + } else { + rc = videobuf_reqbufs(&pcam_inst->vid_bufq, pb); + if (rc < 0) + return rc; + /* Now initialize the local msm_frame_buffer structure */ + for (i = 0; i < pb->count; i++) { + struct msm_frame_buffer *buf = container_of( + pcam_inst->vid_bufq.bufs[i], + struct msm_frame_buffer, + vidbuf); + buf->inuse = 0; + INIT_LIST_HEAD(&buf->vidbuf.queue); + } + } + pcam_inst->buf_count = pb->count; + return rc; +} + +static int msm_camera_v4l2_querybuf(struct file *f, void *pctx, + struct v4l2_buffer *pb) +{ + /* get the video device */ + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return videobuf_querybuf(&pcam_inst->vid_bufq, pb); +} + +static int msm_camera_v4l2_qbuf(struct file *f, void *pctx, + struct v4l2_buffer *pb) +{ + int rc = 0; + /* get the camera device */ + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + rc = videobuf_qbuf(&pcam_inst->vid_bufq, pb); + D("%s, videobuf_qbuf returns %d\n", __func__, rc); + + return rc; +} + +static int msm_camera_v4l2_dqbuf(struct file *f, void *pctx, + struct v4l2_buffer *pb) +{ + int rc = 0; + /* get the camera device */ + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + rc = videobuf_dqbuf(&pcam_inst->vid_bufq, pb, f->f_flags & O_NONBLOCK); + D("%s, videobuf_dqbuf returns %d\n", __func__, rc); + + return rc; +} + +static int msm_camera_v4l2_streamon(struct file *f, void *pctx, + enum v4l2_buf_type i) +{ + int rc = 0; + struct videobuf_buffer *buf; + int cnt = 0; + /* get the camera device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + D("%s Calling videobuf_streamon", __func__); + /* if HW streaming on is successful, start buffer streaming */ + rc = videobuf_streamon(&pcam_inst->vid_bufq); + D("%s, videobuf_streamon returns %d\n", __func__, rc); + + mutex_lock(&pcam->vid_lock); + /* turn HW (VFE/sensor) streaming */ + rc = msm_server_streamon(pcam, pcam_inst->my_index); + mutex_unlock(&pcam->vid_lock); + D("%s rc = %d\n", __func__, rc); + if (rc < 0) { + pr_err("%s: hw failed to start streaming\n", __func__); + return rc; + } + + list_for_each_entry(buf, &pcam_inst->vid_bufq.stream, stream) { + D("%s index %d, state %d\n", __func__, cnt, buf->state); + cnt++; + } + + return rc; +} + +static int msm_camera_v4l2_streamoff(struct file *f, void *pctx, + enum v4l2_buf_type i) +{ + int rc = 0; + /* get the camera device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + /* first turn of HW (VFE/sensor) streaming so that buffers are + not in use when we free the buffers */ + mutex_lock(&pcam->vid_lock); + rc = msm_server_streamoff(pcam, pcam_inst->my_index); + mutex_unlock(&pcam->vid_lock); + if (rc < 0) + pr_err("%s: hw failed to stop streaming\n", __func__); + + /* stop buffer streaming */ + rc = videobuf_streamoff(&pcam_inst->vid_bufq); + D("%s, videobuf_streamoff returns %d\n", __func__, rc); + + return rc; +} + +static int msm_camera_v4l2_enum_fmt_cap(struct file *f, void *pctx, + struct v4l2_fmtdesc *pfmtdesc) +{ + /* get the video device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + const struct msm_isp_color_fmt *isp_fmt; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + if (pfmtdesc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (pfmtdesc->index >= pcam->num_fmts) + return -EINVAL; + + isp_fmt = &pcam->usr_fmts[pfmtdesc->index]; + + if (isp_fmt->name) + strlcpy(pfmtdesc->description, isp_fmt->name, + sizeof(pfmtdesc->description)); + + pfmtdesc->pixelformat = isp_fmt->fourcc; + + D("%s: [%d] 0x%x, %s\n", __func__, pfmtdesc->index, + isp_fmt->fourcc, isp_fmt->name); + return 0; +} + +static int msm_camera_v4l2_g_fmt_cap(struct file *f, + void *pctx, struct v4l2_format *pfmt) +{ + int rc = 0; + /* get the video device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + if (pfmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + rc = msm_server_get_fmt(pcam, pcam_inst->my_index, pfmt); + + D("%s: current_fmt->fourcc: 0x%08x, rc = %d\n", __func__, + pfmt->fmt.pix.pixelformat, rc); + return rc; +} + +/* This function will readjust the format parameters based in HW + capabilities. Called by s_fmt_cap +*/ +static int msm_camera_v4l2_try_fmt_cap(struct file *f, void *pctx, + struct v4l2_format *pfmt) +{ + int rc = 0; + /* get the video device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + rc = msm_server_try_fmt(pcam, pfmt); + if (rc) + pr_err("Format %x not found, rc = %d\n", + pfmt->fmt.pix.pixelformat, rc); + + return rc; +} + +/* This function will reconfig the v4l2 driver and HW device, it should be + called after the streaming is stopped. +*/ +static int msm_camera_v4l2_s_fmt_cap(struct file *f, void *pctx, + struct v4l2_format *pfmt) +{ + int rc; + void __user *uptr; + /* get the video device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + D("%s, inst=0x%x,idx=%d,priv = 0x%p\n", + __func__, (u32)pcam_inst, pcam_inst->my_index, + (void *)pfmt->fmt.pix.priv); + WARN_ON(pctx != f->private_data); + + uptr = (void __user *)pfmt->fmt.pix.priv; + pfmt->fmt.pix.priv = (__u32)kzalloc(MSM_V4L2_DIMENSION_SIZE, + GFP_KERNEL); + + if (!pfmt->fmt.pix.priv) { + pr_err("%s could not allocate memory\n", __func__); + return -ENOMEM; + } + D("%s Copying priv data:n", __func__); + if (copy_from_user((void *)pfmt->fmt.pix.priv, uptr, + MSM_V4L2_DIMENSION_SIZE)) { + pr_err("%s: copy_from_user failed.\n", __func__); + kfree((void *)pfmt->fmt.pix.priv); + return -EINVAL; + } + D("%s Done Copying priv data\n", __func__); + + mutex_lock(&pcam->vid_lock); + + rc = msm_server_set_fmt(pcam, pcam_inst->my_index, pfmt); + if (rc < 0) { + pr_err("%s: msm_server_set_fmt Error: %d\n", + __func__, rc); + goto done; + } + + if (copy_to_user(uptr, (const void *)pfmt->fmt.pix.priv, + MSM_V4L2_DIMENSION_SIZE)) { + pr_err("%s: copy_to_user failed\n", __func__); + rc = -EINVAL; + } + +done: + kfree((void *)pfmt->fmt.pix.priv); + pfmt->fmt.pix.priv = (__u32)uptr; + + mutex_unlock(&pcam->vid_lock); + + return rc; +} + +static int msm_camera_v4l2_g_jpegcomp(struct file *f, void *pctx, + struct v4l2_jpegcompression *pcomp) +{ + int rc = -EINVAL; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return rc; +} + +static int msm_camera_v4l2_s_jpegcomp(struct file *f, void *pctx, + struct v4l2_jpegcompression *pcomp) +{ + int rc = -EINVAL; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return rc; +} + + +static int msm_camera_v4l2_g_crop(struct file *f, void *pctx, + struct v4l2_crop *a) +{ + int rc = -EINVAL; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return rc; +} + +static int msm_camera_v4l2_s_crop(struct file *f, void *pctx, + struct v4l2_crop *a) +{ + int rc = -EINVAL; + + D("%s\n", __func__); + WARN_ON(pctx != f->private_data); + + return rc; +} + +/* Stream type-dependent parameter ioctls */ +static int msm_camera_v4l2_g_parm(struct file *f, void *pctx, + struct v4l2_streamparm *a) +{ + int rc = -EINVAL; + return rc; +} +static int msm_vidbuf_get_path(u32 extendedmode) +{ + switch (extendedmode) { + case MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL: + return OUTPUT_TYPE_T; + case MSM_V4L2_EXT_CAPTURE_MODE_MAIN: + return OUTPUT_TYPE_S; + case MSM_V4L2_EXT_CAPTURE_MODE_VIDEO: + return OUTPUT_TYPE_V; + case MSM_V4L2_EXT_CAPTURE_MODE_DEFAULT: + case MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW: + default: + return OUTPUT_TYPE_P; + } +} + +static int msm_camera_v4l2_s_parm(struct file *f, void *pctx, + struct v4l2_streamparm *a) +{ + int rc = 0; + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + pcam_inst->image_mode = a->parm.capture.extendedmode; + pcam_inst->pcam->dev_inst_map[pcam_inst->image_mode] = pcam_inst; + pcam_inst->path = msm_vidbuf_get_path(pcam_inst->image_mode); + D("%spath=%d,rc=%d\n", __func__, + pcam_inst->path, rc); + return rc; +} + +static int msm_camera_v4l2_subscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int rc = 0; + + D("%s\n", __func__); + D("fh = 0x%x\n", (u32)fh); + + /* handle special case where user wants to subscribe to all + the events */ + D("sub->type = 0x%x\n", sub->type); + + if (sub->type == V4L2_EVENT_ALL) { + /*sub->type = MSM_ISP_EVENT_START;*/ + sub->type = V4L2_EVENT_PRIVATE_START + MSM_CAM_RESP_CTRL; + + D("sub->type start = 0x%x\n", sub->type); + do { + rc = v4l2_event_subscribe(fh, sub); + if (rc < 0) { + D("%s: failed for evtType = 0x%x, rc = %d\n", + __func__, sub->type, rc); + /* unsubscribe all events here and return */ + sub->type = V4L2_EVENT_ALL; + v4l2_event_unsubscribe(fh, sub); + return rc; + } else + D("%s: subscribed evtType = 0x%x, rc = %d\n", + __func__, sub->type, rc); + sub->type++; + D("sub->type while = 0x%x\n", sub->type); + } while (sub->type != + V4L2_EVENT_PRIVATE_START + MSM_CAM_RESP_MAX); + } else { + D("sub->type not V4L2_EVENT_ALL = 0x%x\n", sub->type); + rc = v4l2_event_subscribe(fh, sub); + if (rc < 0) + D("%s: failed for evtType = 0x%x, rc = %d\n", + __func__, sub->type, rc); + } + + D("%s: rc = %d\n", __func__, rc); + return rc; +} + +static int msm_camera_v4l2_unsubscribe_event(struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + int rc = 0; + + D("%s\n", __func__); + D("fh = 0x%x\n", (u32)fh); + + rc = v4l2_event_unsubscribe(fh, sub); + + D("%s: rc = %d\n", __func__, rc); + return rc; +} + +/* v4l2_ioctl_ops */ +static const struct v4l2_ioctl_ops g_msm_ioctl_ops = { + .vidioc_querycap = msm_camera_v4l2_querycap, + + .vidioc_s_crop = msm_camera_v4l2_s_crop, + .vidioc_g_crop = msm_camera_v4l2_g_crop, + + .vidioc_queryctrl = msm_camera_v4l2_queryctrl, + .vidioc_g_ctrl = msm_camera_v4l2_g_ctrl, + .vidioc_s_ctrl = msm_camera_v4l2_s_ctrl, + + .vidioc_reqbufs = msm_camera_v4l2_reqbufs, + .vidioc_querybuf = msm_camera_v4l2_querybuf, + .vidioc_qbuf = msm_camera_v4l2_qbuf, + .vidioc_dqbuf = msm_camera_v4l2_dqbuf, + + .vidioc_streamon = msm_camera_v4l2_streamon, + .vidioc_streamoff = msm_camera_v4l2_streamoff, + + /* format ioctls */ + .vidioc_enum_fmt_vid_cap = msm_camera_v4l2_enum_fmt_cap, + .vidioc_try_fmt_vid_cap = msm_camera_v4l2_try_fmt_cap, + .vidioc_g_fmt_vid_cap = msm_camera_v4l2_g_fmt_cap, + .vidioc_s_fmt_vid_cap = msm_camera_v4l2_s_fmt_cap, + + .vidioc_g_jpegcomp = msm_camera_v4l2_g_jpegcomp, + .vidioc_s_jpegcomp = msm_camera_v4l2_s_jpegcomp, + + /* Stream type-dependent parameter ioctls */ + .vidioc_g_parm = msm_camera_v4l2_g_parm, + .vidioc_s_parm = msm_camera_v4l2_s_parm, + + /* event subscribe/unsubscribe */ + .vidioc_subscribe_event = msm_camera_v4l2_subscribe_event, + .vidioc_unsubscribe_event = msm_camera_v4l2_unsubscribe_event, +}; + +/* open an active camera session to manage the streaming logic */ +static int msm_cam_server_open_session(struct msm_cam_server_dev *ps, + struct msm_cam_v4l2_device *pcam) +{ + int rc = 0; + D("%s\n", __func__); + + if (!ps || !pcam) { + pr_err("%s NULL pointer passed in!\n", __func__); + return rc; + } + + /* book keeping this camera session*/ + ps->pcam_active = pcam; + atomic_inc(&ps->number_pcam_active); + + D("config pcam = 0x%p\n", ps->pcam_active); + + /* initialization the media controller module*/ + msm_mctl_init_module(pcam); + + /*yyan: for single VFE msms (8660, 8960v1), just populate the session + with our VFE devices that registered*/ + pcam->mctl.sensor_sdev = &(pcam->sensor_sdev); + + pcam->mctl.isp_sdev = ps->isp_subdev[0]; + pcam->mctl.ispif_fns = &ps->ispif_fns; + + + /*yyan: 8960 bring up - no VPE and flash; populate later*/ + pcam->mctl.vpe_sdev = NULL; + pcam->mctl.flash_sdev = NULL; + + return rc; + +} + +/* close an active camera session to server */ +static int msm_cam_server_close_session(struct msm_cam_server_dev *ps, + struct msm_cam_v4l2_device *pcam) +{ + int rc = 0; + D("%s\n", __func__); + + if (!ps || !pcam) { + D("%s NULL pointer passed in!\n", __func__); + return rc; + } + + + atomic_dec(&ps->number_pcam_active); + ps->pcam_active = NULL; + + return rc; +} +/* v4l2_file_operations */ +static int msm_open(struct file *f) +{ + int i; + int rc = -EINVAL; + /*struct msm_isp_ops *p_isp = 0;*/ + /* get the video device */ + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst; + + D("%s\n", __func__); + + if (!pcam) { + pr_err("%s NULL pointer passed in!\n", __func__); + return rc; + } + mutex_lock(&pcam->vid_lock); + for (i = 0; i < MSM_DEV_INST_MAX; i++) { + if (pcam->dev_inst[i] == NULL) { + mutex_unlock(&pcam->vid_lock); + break; + } + } + /* if no instance is available, return error */ + if (i == MSM_DEV_INST_MAX) { + mutex_unlock(&pcam->vid_lock); + return rc; + } + pcam_inst = kzalloc(sizeof(struct msm_cam_v4l2_dev_inst), GFP_KERNEL); + if (!pcam_inst) { + mutex_unlock(&pcam->vid_lock); + return rc; + } + pcam_inst->sensor_pxlcode = pcam->usr_fmts[0].pxlcode; + pcam_inst->my_index = i; + pcam_inst->pcam = pcam; + pcam->dev_inst[i] = pcam_inst; + + D("%s for %s\n", __func__, pcam->pdev->name); + pcam->use_count++; + if (pcam->use_count == 1) { + + rc = msm_cam_server_open_session(&g_server_dev, pcam); + if (rc < 0) { + pr_err("%s: cam_server_open_session failed %d\n", + __func__, rc); + mutex_unlock(&pcam->vid_lock); + return rc; + } + + /* Should be set to sensor ops if any but right now its OK!! */ + if (!pcam->mctl.mctl_open) { + D("%s: media contoller is not inited\n", + __func__); + mutex_unlock(&pcam->vid_lock); + return -ENODEV; + } + + /* Now we really have to activate the camera */ + D("%s: call mctl_open\n", __func__); + rc = pcam->mctl.mctl_open(&(pcam->mctl), MSM_APPS_ID_V4L2); + + if (rc < 0) { + mutex_unlock(&pcam->vid_lock); + pr_err("%s: HW open failed rc = 0x%x\n", __func__, rc); + return rc; + } + pcam->mctl.sync.pcam_sync = pcam; + + /* Register isp subdev */ + rc = v4l2_device_register_subdev(&pcam->v4l2_dev, + &pcam->mctl.isp_sdev->sd); + if (rc < 0) { + mutex_unlock(&pcam->vid_lock); + pr_err("%s: v4l2_device_register_subdev failed rc = %d\n", + __func__, rc); + return rc; + } + } + + /* Initialize the video queue */ + rc = pcam->mctl.mctl_vidbuf_init(pcam_inst, &pcam_inst->vid_bufq); + if (rc < 0) { + mutex_unlock(&pcam->vid_lock); + return rc; + } + + + f->private_data = pcam_inst; + + D("f->private_data = 0x%x, pcam = 0x%x\n", + (u32)f->private_data, (u32)pcam_inst); + + + if (pcam->use_count == 1) { + rc = msm_send_open_server(); + if (rc < 0) { + mutex_unlock(&pcam->vid_lock); + pr_err("%s failed\n", __func__); + return rc; + } + } + mutex_unlock(&pcam->vid_lock); + /* rc = msm_cam_server_open_session(g_server_dev, pcam);*/ + return rc; +} + +static int msm_mmap(struct file *f, struct vm_area_struct *vma) +{ + int rc = 0; + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("mmap called, vma=0x%08lx\n", (unsigned long)vma); + + rc = videobuf_mmap_mapper(&pcam_inst->vid_bufq, vma); + + D("vma start=0x%08lx, size=%ld, ret=%d\n", + (unsigned long)vma->vm_start, + (unsigned long)vma->vm_end - (unsigned long)vma->vm_start, + rc); + + return rc; +} + +static int msm_close(struct file *f) +{ + int rc = 0; + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + if (!pcam) { + pr_err("%s NULL pointer of camera device!\n", __func__); + return -EINVAL; + } + + + mutex_lock(&pcam->vid_lock); + pcam->use_count--; + pcam->dev_inst_map[pcam_inst->image_mode] = NULL; + videobuf_stop(&pcam_inst->vid_bufq); + /* free the queue: function name is ambiguous it frees all + types of buffers (mmap or userptr - it doesn't matter) */ + rc = videobuf_mmap_free(&pcam_inst->vid_bufq); + if (rc < 0) + pr_err("%s: unable to free buffers\n", __func__); + pcam->dev_inst[pcam_inst->my_index] = NULL; + kfree(pcam_inst); + f->private_data = NULL; + + if (pcam->use_count == 0) { + if (pcam->mctl.mctl_release) { + rc = pcam->mctl.mctl_release(&(pcam->mctl)); + if (rc < 0) + pr_err("mctl_release fails %d\n", rc); + } + + v4l2_device_unregister_subdev(&pcam->mctl.isp_sdev->sd); + + rc = msm_cam_server_close_session(&g_server_dev, pcam); + if (rc < 0) + pr_err("msm_cam_server_close_session fails %d\n", rc); + + rc = msm_send_close_server(); + if (rc < 0) + pr_err("msm_send_close_server failed %d\n", rc); + + dma_release_declared_memory(&pcam->pdev->dev); + } + mutex_unlock(&pcam->vid_lock); + return rc; +} + +static unsigned int msm_poll(struct file *f, struct poll_table_struct *wait) +{ + int rc = 0; + struct msm_cam_v4l2_device *pcam = video_drvdata(f); + struct msm_cam_v4l2_dev_inst *pcam_inst = f->private_data; + + D("%s\n", __func__); + if (!pcam) { + pr_err("%s NULL pointer of camera device!\n", __func__); + return -EINVAL; + } + + if (!pcam_inst->vid_bufq.streaming) { + D("%s vid_bufq.streaming is off, inst=0x%x\n", + __func__, (u32)pcam_inst); + return -EINVAL; + } + + rc |= videobuf_poll_stream(f, &pcam_inst->vid_bufq, wait); + D("%s returns, rc = 0x%x\n", __func__, rc); + + return rc; +} + +static unsigned int msm_poll_server(struct file *fp, + struct poll_table_struct *wait) +{ + int rc = 0; + + D("%s\n", __func__); + poll_wait(fp, + &g_server_dev.server_command_queue.eventHandle.events->wait, + wait); + if (v4l2_event_pending(&g_server_dev.server_command_queue.eventHandle)) + rc |= POLLPRI; + + return rc; +} +static long msm_ioctl_server(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + int rc = -EINVAL; + struct v4l2_event ev; + struct msm_camera_info temp_cam_info; + struct msm_cam_config_dev_info temp_config_info; + struct v4l2_event_subscription temp_sub; + int i; + + D("%s: cmd %d\n", __func__, _IOC_NR(cmd)); + + switch (cmd) { + case MSM_CAM_IOCTL_GET_CAMERA_INFO: + if (copy_from_user(&temp_cam_info, (void __user *)arg, + sizeof(struct msm_camera_info))) { + rc = -EINVAL; + return rc; + } + for (i = 0; i < g_server_dev.camera_info.num_cameras; i++) { + if (copy_to_user((void __user *) + temp_cam_info.video_dev_name[i], + g_server_dev.camera_info.video_dev_name[i], + strlen(g_server_dev.camera_info.video_dev_name[i]))) { + rc = -EINVAL; + return rc; + } + temp_cam_info.has_3d_support[i] = + g_server_dev.camera_info.has_3d_support[i]; + temp_cam_info.is_internal_cam[i] = + g_server_dev.camera_info.is_internal_cam[i]; + temp_cam_info.s_mount_angle[i] = + g_server_dev.camera_info.s_mount_angle[i]; + temp_cam_info.sensor_type[i] = + g_server_dev.camera_info.sensor_type[i]; + + } + temp_cam_info.num_cameras = + g_server_dev.camera_info.num_cameras; + if (copy_to_user((void __user *)arg, + &temp_cam_info, + sizeof(struct msm_camera_info))) { + rc = -EINVAL; + return rc; + } + rc = 0; + break; + + case MSM_CAM_IOCTL_GET_CONFIG_INFO: + if (copy_from_user(&temp_config_info, (void __user *)arg, + sizeof(struct msm_cam_config_dev_info))) { + rc = -EINVAL; + return rc; + } + for (i = 0; + i < g_server_dev.config_info.num_config_nodes; i++) { + if (copy_to_user( + (void __user *)temp_config_info.config_dev_name[i], + g_server_dev.config_info.config_dev_name[i], + strlen(g_server_dev.config_info.config_dev_name[i]))) { + rc = -EINVAL; + return rc; + } + } + temp_config_info.num_config_nodes = + g_server_dev.config_info.num_config_nodes; + if (copy_to_user((void __user *)arg, + &temp_config_info, + sizeof(struct msm_cam_config_dev_info))) { + rc = -EINVAL; + return rc; + } + rc = 0; + break; + + case VIDIOC_SUBSCRIBE_EVENT: + if (copy_from_user(&temp_sub, (void __user *)arg, + sizeof(struct v4l2_event_subscription))) { + rc = -EINVAL; + return rc; + } + rc = msm_camera_v4l2_subscribe_event + (&g_server_dev.server_command_queue.eventHandle, + &temp_sub); + if (rc < 0) + return rc; + + break; + + case VIDIOC_DQEVENT: { + void __user *u_ctrl_value = NULL; + struct msm_isp_stats_event_ctrl *u_isp_event; + struct msm_isp_stats_event_ctrl *k_isp_event; + + /* Make a copy of control value and event data pointer */ + D("%s: VIDIOC_DQEVENT\n", __func__); + if (copy_from_user(&ev, (void __user *)arg, + sizeof(struct v4l2_event))) + break; + u_isp_event = (struct msm_isp_stats_event_ctrl *)ev.u.data; + u_ctrl_value = u_isp_event->isp_data.ctrl.value; + + rc = v4l2_event_dequeue( + &g_server_dev.server_command_queue.eventHandle, + &ev, fp->f_flags & O_NONBLOCK); + if (rc < 0) { + pr_err("no pending events?"); + break; + } + + k_isp_event = (struct msm_isp_stats_event_ctrl *)ev.u.data; + if (ev.type == V4L2_EVENT_PRIVATE_START+MSM_CAM_RESP_V4L2 && + k_isp_event->isp_data.ctrl.length > 0) { + void *k_ctrl_value = k_isp_event->isp_data.ctrl.value; + if (copy_to_user(u_ctrl_value, k_ctrl_value, + u_isp_event->isp_data.ctrl.length)) { + rc = -EINVAL; + break; + } + } + k_isp_event->isp_data.ctrl.value = u_ctrl_value; + + if (copy_to_user((void __user *)arg, &ev, + sizeof(struct v4l2_event))) { + rc = -EINVAL; + break; + } + } + + break; + + case MSM_CAM_IOCTL_CTRL_CMD_DONE: + D("%s: MSM_CAM_IOCTL_CTRL_CMD_DONE\n", __func__); + rc = msm_ctrl_cmd_done((void __user *)arg); + break; + + default: + break; + } + return rc; +} + +static int msm_open_server(struct inode *inode, struct file *fp) +{ + int rc; + D("%s: open %s\n", __func__, fp->f_path.dentry->d_name.name); + + rc = nonseekable_open(inode, fp); + if (rc < 0) { + pr_err("%s: nonseekable_open error %d\n", __func__, rc); + return rc; + } + g_server_dev.use_count++; + if (g_server_dev.use_count == 1) + msm_queue_init(&g_server_dev.ctrl_q, "control"); + + return rc; +} + +static unsigned int msm_poll_config(struct file *fp, + struct poll_table_struct *wait) +{ + int rc = 0; + struct msm_cam_config_dev *config = fp->private_data; + if (config == NULL) + return -EINVAL; + + D("%s\n", __func__); + + poll_wait(fp, + &config->config_stat_event_queue.eventHandle.events->wait, wait); + if (v4l2_event_pending(&config->config_stat_event_queue.eventHandle)) + rc |= POLLPRI; + return rc; +} + +static long msm_ioctl_config(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + + int rc = 0; + struct v4l2_event ev; + struct msm_cam_config_dev *config_cam = fp->private_data; + struct v4l2_event_subscription temp_sub; + + D("%s: cmd %d\n", __func__, _IOC_NR(cmd)); + + switch (cmd) { + /* memory management shall be handeld here*/ + case MSM_CAM_IOCTL_REGISTER_PMEM: + return msm_register_pmem( + &config_cam->p_mctl->sync.pmem_stats, + (void __user *)arg); + break; + + case MSM_CAM_IOCTL_UNREGISTER_PMEM: + return msm_pmem_table_del( + &config_cam->p_mctl->sync.pmem_stats, + (void __user *)arg); + break; + case VIDIOC_SUBSCRIBE_EVENT: + if (copy_from_user(&temp_sub, + (void __user *)arg, + sizeof(struct v4l2_event_subscription))) { + rc = -EINVAL; + return rc; + } + rc = msm_camera_v4l2_subscribe_event + (&config_cam->config_stat_event_queue.eventHandle, + &temp_sub); + if (rc < 0) + return rc; + + break; + + case VIDIOC_UNSUBSCRIBE_EVENT: + if (copy_from_user(&temp_sub, (void __user *)arg, + sizeof(struct v4l2_event_subscription))) { + rc = -EINVAL; + return rc; + } + rc = msm_camera_v4l2_unsubscribe_event + (&config_cam->config_stat_event_queue.eventHandle, + &temp_sub); + if (rc < 0) + return rc; + + break; + + case VIDIOC_DQEVENT: { + void __user *u_msg_value = NULL; + struct msm_isp_stats_event_ctrl *u_isp_event; + struct msm_isp_stats_event_ctrl *k_isp_event; + + /* Make a copy of control value and event data pointer */ + D("%s: VIDIOC_DQEVENT\n", __func__); + if (copy_from_user(&ev, (void __user *)arg, + sizeof(struct v4l2_event))) + break; + u_isp_event = (struct msm_isp_stats_event_ctrl *)ev.u.data; + u_msg_value = u_isp_event->isp_data.isp_msg.data; + + rc = v4l2_event_dequeue( + &config_cam->config_stat_event_queue.eventHandle, + &ev, fp->f_flags & O_NONBLOCK); + if (rc < 0) { + pr_err("no pending events?"); + break; + } + + k_isp_event = (struct msm_isp_stats_event_ctrl *)ev.u.data; + if (ev.type == + V4L2_EVENT_PRIVATE_START+MSM_CAM_RESP_STAT_EVT_MSG && + k_isp_event->isp_data.isp_msg.len > 0) { + void *k_msg_value = k_isp_event->isp_data.isp_msg.data; + if (copy_to_user(u_msg_value, k_msg_value, + k_isp_event->isp_data.isp_msg.len)) { + rc = -EINVAL; + break; + } + kfree(k_msg_value); + } + k_isp_event->isp_data.isp_msg.data = u_msg_value; + + if (copy_to_user((void __user *)arg, &ev, + sizeof(struct v4l2_event))) { + rc = -EINVAL; + break; + } + } + + break; + + default:{ + /* For the rest of config command, forward to media controller*/ + struct msm_cam_media_controller *p_mctl = config_cam->p_mctl; + if (p_mctl && p_mctl->mctl_cmd) { + rc = config_cam->p_mctl->mctl_cmd(p_mctl, cmd, arg); + } else { + rc = -EINVAL; + pr_err("%s: media controller is null\n", __func__); + } + + break; + } /* end of default*/ + } /* end of switch*/ + return rc; +} + +static int msm_open_config(struct inode *inode, struct file *fp) +{ + int rc; + + struct msm_cam_config_dev *config_cam = + container_of(inode->i_cdev, struct msm_cam_config_dev, config_cdev); + + D("%s: open %s\n", __func__, fp->f_path.dentry->d_name.name); + + rc = nonseekable_open(inode, fp); + if (rc < 0) { + pr_err("%s: nonseekable_open error %d\n", __func__, rc); + return rc; + } + config_cam->use_count++; + + /*config_cam->isp_subdev = g_server_dev.pcam_active->mctl.isp_sdev;*/ + /* assume there is only one active camera possible*/ + config_cam->p_mctl = &g_server_dev.pcam_active->mctl; + + INIT_HLIST_HEAD(&config_cam->p_mctl->sync.pmem_stats); + spin_lock_init(&config_cam->p_mctl->sync.pmem_stats_spinlock); + + config_cam->p_mctl->config_device = config_cam; + fp->private_data = config_cam; + return rc; +} + +static struct v4l2_file_operations g_msm_fops = { + .owner = THIS_MODULE, + .open = msm_open, + .poll = msm_poll, + .mmap = msm_mmap, + .release = msm_close, + .ioctl = video_ioctl2, +}; + +/* Init a config node for ISP control, + which will create a config device (/dev/config0/ and plug in + ISP's operation "v4l2_ioctl_ops*" +*/ +static const struct file_operations msm_fops_server = { + .owner = THIS_MODULE, + .open = msm_open_server, + .poll = msm_poll_server, + .unlocked_ioctl = msm_ioctl_server, +}; + +static const struct file_operations msm_fops_config = { + .owner = THIS_MODULE, + .open = msm_open_config, + .poll = msm_poll_config, + .unlocked_ioctl = msm_ioctl_config, +}; + +static int msm_setup_v4l2_event_queue(struct v4l2_fh *eventHandle, + struct video_device *pvdev) +{ + int rc = 0; + /* v4l2_fh support */ + spin_lock_init(&pvdev->fh_lock); + INIT_LIST_HEAD(&pvdev->fh_list); + + rc = v4l2_fh_init(eventHandle, pvdev); + if (rc < 0) + return rc; + + rc = v4l2_event_init(eventHandle); + if (rc < 0) + return rc; + + /* queue of max size 30 */ + rc = v4l2_event_alloc(eventHandle, 30); + if (rc < 0) + return rc; + + v4l2_fh_add(eventHandle); + return rc; + +} + +static int msm_setup_config_dev(int node, char *device_name) +{ + int rc = -ENODEV; + struct device *device_config; + int dev_num = node; + dev_t devno; + struct msm_cam_config_dev *config_cam; + + config_cam = kzalloc(sizeof(*config_cam), GFP_KERNEL); + if (!config_cam) { + pr_err("%s: could not allocate memory for msm_cam_config_device\n", + __func__); + return -ENOMEM; + } + + D("%s\n", __func__); + + devno = MKDEV(MAJOR(msm_devno), dev_num+1); + device_config = device_create(msm_class, NULL, devno, NULL, + "%s%d", device_name, dev_num); + + if (IS_ERR(device_config)) { + rc = PTR_ERR(device_config); + pr_err("%s: error creating device: %d\n", __func__, rc); + return rc; + } + + cdev_init(&config_cam->config_cdev, + &msm_fops_config); + config_cam->config_cdev.owner = THIS_MODULE; + + rc = cdev_add(&config_cam->config_cdev, devno, 1); + if (rc < 0) { + pr_err("%s: error adding cdev: %d\n", __func__, rc); + device_destroy(msm_class, devno); + return rc; + } + g_server_dev.config_info.config_dev_name[dev_num] + = dev_name(device_config); + D("%s Connected config device %s\n", __func__, + g_server_dev.config_info.config_dev_name[dev_num]); + + config_cam->config_stat_event_queue.pvdev = video_device_alloc(); + if (config_cam->config_stat_event_queue.pvdev == NULL) { + pr_err("%s: video_device_alloc failed\n", __func__); + return -ENOMEM; + } + + rc = msm_setup_v4l2_event_queue( + &config_cam->config_stat_event_queue.eventHandle, + config_cam->config_stat_event_queue.pvdev); + if (rc < 0) + pr_err("%s failed to initialize event queue\n", __func__); + + return rc; +} + +static int msm_setup_server_dev(int node, char *device_name) +{ + int rc = -ENODEV; + struct device *device_server; + int dev_num = node; + dev_t devno; + + D("%s\n", __func__); + + devno = MKDEV(MAJOR(msm_devno), dev_num); + device_server = device_create(msm_class, NULL, + devno, NULL, "%s", device_name); + + if (IS_ERR(device_server)) { + rc = PTR_ERR(device_server); + pr_err("%s: error creating device: %d\n", __func__, rc); + return rc; + } + + cdev_init(&g_server_dev.server_cdev, &msm_fops_server); + g_server_dev.server_cdev.owner = THIS_MODULE; + + rc = cdev_add(&g_server_dev.server_cdev, devno, 1); + if (rc < 0) { + pr_err("%s: error adding cdev: %d\n", __func__, rc); + device_destroy(msm_class, devno); + return rc; + } + + g_server_dev.pcam_active = NULL; + g_server_dev.camera_info.num_cameras = 0; + atomic_set(&g_server_dev.number_pcam_active, 0); + g_server_dev.ispif_fns.ispif_config = NULL; + + /*initialize fake video device and event queue*/ + + g_server_dev.server_command_queue.pvdev = video_device_alloc(); + if (g_server_dev.server_command_queue.pvdev == NULL) { + pr_err("%s: video_device_alloc failed\n", __func__); + return -ENOMEM; + } + rc = msm_setup_v4l2_event_queue( + &g_server_dev.server_command_queue.eventHandle, + g_server_dev.server_command_queue.pvdev); + if (rc < 0) + pr_err("%s failed to initialize event queue\n", __func__); + + return rc; +} + +static int msm_cam_dev_init(struct msm_cam_v4l2_device *pcam) +{ + int rc = -ENOMEM; + struct video_device *pvdev = NULL; + D("%s\n", __func__); + + /* first register the v4l2 device */ + pcam->v4l2_dev.dev = &pcam->pdev->dev; + rc = v4l2_device_register(pcam->v4l2_dev.dev, &pcam->v4l2_dev); + if (rc < 0) + return -EINVAL; + else + pcam->v4l2_dev.notify = msm_cam_v4l2_subdev_notify; + + + /* now setup video device */ + pvdev = video_device_alloc(); + if (pvdev == NULL) { + pr_err("%s: video_device_alloc failed\n", __func__); + return rc; + } + + /* init video device's driver interface */ + D("sensor name = %s, sizeof(pvdev->name)=%d\n", + pcam->pdev->name, sizeof(pvdev->name)); + + /* device info - strlcpy is safer than strncpy but + only if architecture supports*/ + strlcpy(pvdev->name, pcam->pdev->name, sizeof(pvdev->name)); + + pvdev->release = video_device_release; + pvdev->fops = &g_msm_fops; + pvdev->ioctl_ops = &g_msm_ioctl_ops; + pvdev->minor = -1; + pvdev->vfl_type = 1; + + /* register v4l2 video device to kernel as /dev/videoXX */ + D("video_register_device\n"); + rc = video_register_device(pvdev, + VFL_TYPE_GRABBER, + msm_camera_v4l2_nr); + if (rc) { + pr_err("%s: video_register_device failed\n", __func__); + goto reg_fail; + } + D("%s: video device registered as /dev/video%d\n", + __func__, pvdev->num); + + /* connect pcam and video dev to each other */ + pcam->pvdev = pvdev; + video_set_drvdata(pcam->pvdev, pcam); + + /* If isp HW registeration is successful, then create event queue to + receievent event froms HW + */ + /* yyan: no global - each sensor will create a new vidoe node! */ + /* g_pmsm_camera_v4l2_dev = pmsm_camera_v4l2_dev; */ + /* g_pmsm_camera_v4l2_dev->pvdev = pvdev; */ + + return rc ; + +reg_fail: + video_device_release(pvdev); + v4l2_device_unregister(&pcam->v4l2_dev); + pcam->v4l2_dev.dev = NULL; + return rc; +} + +static int msm_sync_destroy(struct msm_sync *sync) +{ + if (sync) + wake_lock_destroy(&sync->wake_lock); + return 0; +} +static int msm_sync_init(struct msm_sync *sync, + struct platform_device *pdev, struct msm_sensor_ctrl *sctrl) +{ + int rc = 0; + + sync->sdata = pdev->dev.platform_data; + + wake_lock_init(&sync->wake_lock, WAKE_LOCK_IDLE, "msm_camera"); + + sync->pdev = pdev; + sync->sctrl = *sctrl; + sync->opencnt = 0; + mutex_init(&sync->lock); + D("%s: initialized %s\n", __func__, sync->sdata->sensor_name); + return rc; +} + +int msm_ispif_register(struct msm_ispif_fns *ispif) +{ + int rc = -EINVAL; + if (ispif != NULL) { + /*save ispif into server dev*/ + g_server_dev.ispif_fns.ispif_config = ispif->ispif_config; + g_server_dev.ispif_fns.ispif_start_intf_transfer + = ispif->ispif_start_intf_transfer; + rc = 0; + } + return rc; +} +EXPORT_SYMBOL(msm_ispif_register); + +/* register a msm sensor into the msm device, which will probe the + sensor HW. if the HW exist then create a video device (/dev/videoX/) + to represent this sensor */ +int msm_sensor_register(struct platform_device *pdev, + int (*sensor_probe)(const struct msm_camera_sensor_info *, + struct v4l2_subdev *, struct msm_sensor_ctrl *)) +{ + + int rc = -EINVAL; + struct msm_camera_sensor_info *sdata = pdev->dev.platform_data; + struct msm_cam_v4l2_device *pcam; + struct v4l2_subdev *sdev; + struct msm_sensor_ctrl sctrl; + + D("%s for %s\n", __func__, pdev->name); + + /* allocate the memory for the camera device first */ + pcam = kzalloc(sizeof(*pcam), GFP_KERNEL); + if (!pcam) { + pr_err("%s: could not allocate memory for msm_cam_v4l2_device\n", + __func__); + return -ENOMEM; + } else{ + sdev = &(pcam->sensor_sdev); + snprintf(sdev->name, sizeof(sdev->name), "%s", pdev->name); + } + + /* come sensor probe logic */ + rc = msm_camio_probe_on(pdev); + if (rc < 0) { + kzfree(pcam); + return rc; + } + + rc = sensor_probe(sdata, sdev, &sctrl); + + msm_camio_probe_off(pdev); + if (rc < 0) { + pr_err("%s: failed to detect %s\n", + __func__, + sdata->sensor_name); + kzfree(pcam); + return rc; + } + + /* if the probe is successfull, allocat the camera driver object + for this sensor */ + + pcam->sync = kzalloc(sizeof(struct msm_sync), GFP_ATOMIC); + if (!pcam->sync) { + pr_err("%s: could not allocate memory for msm_sync object\n", + __func__); + kzfree(pcam); + return -ENOMEM; + } + + /* setup a manager object*/ + rc = msm_sync_init(pcam->sync, pdev, &sctrl); + if (rc < 0) + goto failure; + D("%s: pcam =0x%p\n", __func__, pcam); + D("%s: pcam->sync =0x%p\n", __func__, pcam->sync); + + (pcam->sync)->pcam_sync = pcam; + /* bind the driver device to the sensor device */ + pcam->pdev = pdev; + pcam->sctrl = sctrl; + + /* init the user count and lock*/ + pcam->use_count = 0; + mutex_init(&pcam->vid_lock); + + /* Initialize the formats supported */ + rc = msm_mctl_init_user_formats(pcam); + if (rc < 0) + goto failure; + + /* now initialize the camera device object */ + rc = msm_cam_dev_init(pcam); + if (rc < 0) + goto failure; + + g_server_dev.camera_info.video_dev_name + [g_server_dev.camera_info.num_cameras] + = video_device_node_name(pcam->pvdev); + D("%s Connected video device %s\n", __func__, + g_server_dev.camera_info.video_dev_name + [g_server_dev.camera_info.num_cameras]); + g_server_dev.camera_info.num_cameras++; + + D("%s done, rc = %d\n", __func__, rc); + D("%s number of sensors connected is %d\n", __func__, + g_server_dev.camera_info.num_cameras); +/* + if (g_server_dev.camera_info.num_cameras == 1) { + rc = add_axi_qos(); + if (rc < 0) + goto failure; + } +*/ + /* register the subdevice, must be done for callbacks */ + rc = v4l2_device_register_subdev(&pcam->v4l2_dev, sdev); + pcam->vnode_id = vnode_count++; + return rc; + +failure: + /* mutex_destroy not needed at this moment as the associated + implemenation of mutex_init is not consuming resources */ + msm_sync_destroy(pcam->sync); + pcam->pdev = NULL; + kzfree(pcam->sync); + kzfree(pcam); + return rc; +} +EXPORT_SYMBOL(msm_sensor_register); + +static int __init msm_camera_init(void) +{ + int rc = 0, i; + /*for now just create a config 0 node + put logic here later to know how many configs to create*/ + g_server_dev.config_info.num_config_nodes = 1; + + rc = msm_isp_init_module(g_server_dev.config_info.num_config_nodes); + if (rc < 0) { + pr_err("Failed to initialize isp\n"); + return rc; + } + + if (!msm_class) { + rc = alloc_chrdev_region(&msm_devno, 0, + g_server_dev.config_info.num_config_nodes+1, "msm_camera"); + if (rc < 0) { + pr_err("%s: failed to allocate chrdev: %d\n", __func__, + rc); + return rc; + } + + msm_class = class_create(THIS_MODULE, "msm_camera"); + if (IS_ERR(msm_class)) { + rc = PTR_ERR(msm_class); + pr_err("%s: create device class failed: %d\n", + __func__, rc); + return rc; + } + } + + D("creating server and config nodes\n"); + rc = msm_setup_server_dev(0, "video_msm"); + if (rc < 0) { + pr_err("%s: failed to create server dev: %d\n", __func__, + rc); + return rc; + } + + for (i = 0; i < g_server_dev.config_info.num_config_nodes; i++) { + rc = msm_setup_config_dev(i, "config"); + if (rc < 0) { + pr_err("%s:failed to create config dev: %d\n", + __func__, rc); + return rc; + } + } + + msm_isp_register(&g_server_dev); + return rc; +} + +static void __exit msm_camera_exit(void) +{ + msm_isp_unregister(&g_server_dev); +} + +module_init(msm_camera_init); +module_exit(msm_camera_exit); diff --git a/drivers/media/video/msm/msm.h b/drivers/media/video/msm/msm.h new file mode 100644 index 00000000000..0049cc133cd --- /dev/null +++ b/drivers/media/video/msm/msm.h @@ -0,0 +1,372 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _MSM_H +#define _MSM_H + +#ifdef __KERNEL__ + +/* Header files */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSM_V4L2_DIMENSION_SIZE 96 + +#define ERR_USER_COPY(to) pr_err("%s(%d): copy %s user\n", \ + __func__, __LINE__, ((to) ? "to" : "from")) +#define ERR_COPY_FROM_USER() ERR_USER_COPY(0) +#define ERR_COPY_TO_USER() ERR_USER_COPY(1) + +/* msm queue management APIs*/ + +#define msm_dequeue(queue, member) ({ \ + unsigned long flags; \ + struct msm_device_queue *__q = (queue); \ + struct msm_queue_cmd *qcmd = 0; \ + spin_lock_irqsave(&__q->lock, flags); \ + if (!list_empty(&__q->list)) { \ + __q->len--; \ + qcmd = list_first_entry(&__q->list, \ + struct msm_queue_cmd, member); \ + list_del_init(&qcmd->member); \ + } \ + spin_unlock_irqrestore(&__q->lock, flags); \ + qcmd; \ +}) + +#define msm_queue_drain(queue, member) do { \ + unsigned long flags; \ + struct msm_device_queue *__q = (queue); \ + struct msm_queue_cmd *qcmd; \ + spin_lock_irqsave(&__q->lock, flags); \ + while (!list_empty(&__q->list)) { \ + qcmd = list_first_entry(&__q->list, \ + struct msm_queue_cmd, member); \ + list_del_init(&qcmd->member); \ + free_qcmd(qcmd); \ + }; \ + spin_unlock_irqrestore(&__q->lock, flags); \ +} while (0) + +static inline void free_qcmd(struct msm_queue_cmd *qcmd) +{ + if (!qcmd || !atomic_read(&qcmd->on_heap)) + return; + if (!atomic_sub_return(1, &qcmd->on_heap)) + kfree(qcmd); +} + +/* message id for v4l2_subdev_notify*/ +enum msm_camera_v4l2_subdev_notify { + NOTIFY_CID_CHANGE, /* arg = msm_camera_csid_params */ + NOTIFY_VFE_MSG_EVT, /* arg = msm_vfe_resp */ + NOTIFY_INVALID +}; + +enum isp_vfe_cmd_id { + /* + *Important! Command_ID are arranged in order. + *Don't change!*/ + ISP_VFE_CMD_ID_STREAM_ON, + ISP_VFE_CMD_ID_STREAM_OFF, + ISP_VFE_CMD_ID_FRAME_BUF_RELEASE +}; + +struct msm_cam_v4l2_device; +struct msm_cam_v4l2_dev_inst; + +/* buffer for one video frame */ +struct msm_frame_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vidbuf; + enum v4l2_mbus_pixelcode pxlcode; + int inuse; + int active; +}; + +struct msm_isp_color_fmt { + char *name; + int depth; + int bitsperpxl; + u32 fourcc; + enum v4l2_mbus_pixelcode pxlcode; + enum v4l2_colorspace colorspace; +}; + +enum ispif_op_id { + /* + *Important! Command_ID are arranged in order. + *Don't change!*/ + ISPIF_ENABLE, + ISPIF_DISABLE, + ISPIF_RESET, + ISPIF_CONFIG +}; + +struct msm_ispif_ops { + + int (*ispif_op)(struct msm_ispif_ops *p_ispif, + enum ispif_op_id ispif_op_id_used, unsigned long arg); +}; + +struct msm_ispif_fns { + int (*ispif_config)(struct msm_ispif_params *ispif_params, + uint8_t num_of_intf); + int (*ispif_start_intf_transfer) + (struct msm_ispif_params *ispif_params); +}; + +extern int msm_ispif_init_module(struct msm_ispif_ops *p_ispif); + +/*"Media Controller" represents a camera steaming session, which consists + of a "sensor" device and an "isp" device (such as VFE, if needed), + connected via an "IO" device, (such as IPIF on 8960, or none on 8660) + plus other extra sub devices such as VPE and flash +*/ + +struct msm_cam_media_controller { + + int (*mctl_open)(struct msm_cam_media_controller *p_mctl, + const char *const apps_id); + int (*mctl_cb)(void); + int (*mctl_notify)(struct msm_cam_media_controller *p_mctl, + unsigned int notification, void *arg); + int (*mctl_cmd)(struct msm_cam_media_controller *p_mctl, + unsigned int cmd, unsigned long arg); + int (*mctl_release)(struct msm_cam_media_controller *p_mctl); + int (*mctl_vidbuf_init)(struct msm_cam_v4l2_dev_inst *pcam, + struct videobuf_queue *); + int (*mctl_ufmt_init)(struct msm_cam_media_controller *p_mctl); + + struct v4l2_device v4l2_dev; + struct v4l2_fh eventHandle; /* event queue to export events */ + /* most-frequently accessed manager object*/ + struct msm_sync sync; + + + /* the following reflect the HW topology information*/ + /*mandatory*/ + struct v4l2_subdev *sensor_sdev; /* sensor sub device */ + struct v4l2_subdev mctl_sdev; /* media control sub device */ + struct platform_device *plat_dev; + /*optional*/ + struct msm_isp_ops *isp_sdev; /* isp sub device : camif/VFE */ + struct v4l2_subdev *vpe_sdev; /* vpe sub device : VPE */ + struct v4l2_subdev *flash_sdev; /* vpe sub device : VPE */ + struct msm_cam_config_dev *config_device; + struct msm_ispif_fns *ispif_fns; + + struct pm_qos_request_list pm_qos_req_list; +}; + +/* abstract camera device represents a VFE and connected sensor */ +struct msm_isp_ops { + char *config_dev_name; + + /*int (*isp_init)(struct msm_cam_v4l2_device *pcam);*/ + int (*isp_open)(struct v4l2_subdev *sd, struct msm_sync *sync); + int (*isp_config)(struct msm_cam_media_controller *pmctl, + unsigned int cmd, unsigned long arg); + int (*isp_enqueue)(struct msm_cam_media_controller *pcam, + struct msm_vfe_resp *data, + enum msm_queue qtype); + int (*isp_notify)(struct v4l2_subdev *sd, void *arg); + + void (*isp_release)(struct msm_sync *psync); + + /* vfe subdevice */ + struct v4l2_subdev sd; +}; + +struct msm_isp_buf_info { + int type; + unsigned long buffer; + int fd; +}; +#define MSM_DEV_INST_MAX 16 +struct msm_cam_v4l2_dev_inst { + struct videobuf_queue vid_bufq; + spinlock_t vb_irqlock; + struct v4l2_format vid_fmt; + /* senssor pixel code*/ + enum v4l2_mbus_pixelcode sensor_pxlcode; + struct msm_cam_v4l2_device *pcam; + int my_index; + int image_mode; + int path; + int buf_count; +}; +#define MSM_MAX_IMG_MODE 5 +/* abstract camera device for each sensor successfully probed*/ +struct msm_cam_v4l2_device { + /* standard device interfaces */ + /* parent of video device to trace back */ + struct device dev; + /* sensor's platform device*/ + struct platform_device *pdev; + /* V4l2 device */ + struct v4l2_device v4l2_dev; + /* will be registered as /dev/video*/ + struct video_device *pvdev; + int use_count; + /* will be used to init/release HW */ + struct msm_cam_media_controller mctl; + /* sensor subdevice */ + struct v4l2_subdev sensor_sdev; + struct msm_sensor_ctrl sctrl; + + /* parent device */ + struct device *parent_dev; + + struct mutex vid_lock; + /* v4l2 format support */ + struct msm_isp_color_fmt *usr_fmts; + int num_fmts; + /* preview or snapshot */ + u32 mode; + u32 memsize; + + int op_mode; + int vnode_id; + struct msm_cam_v4l2_dev_inst *dev_inst[MSM_DEV_INST_MAX]; + struct msm_cam_v4l2_dev_inst *dev_inst_map[MSM_MAX_IMG_MODE]; + /* native config device */ + struct cdev cdev; + + /* most-frequently accessed manager object*/ + struct msm_sync *sync; + + /* The message queue is used by the control thread to send commands + * to the config thread, and also by the HW to send messages to the + * config thread. Thus it is the only queue that is accessed from + * both interrupt and process context. + */ + /* struct msm_device_queue event_q; */ + + /* This queue used by the config thread to send responses back to the + * control thread. It is accessed only from a process context. + * TO BE REMOVED + */ + struct msm_device_queue ctrl_q; + + struct mutex lock; + uint8_t ctrl_data[max_control_command_size]; + struct msm_ctrl_cmd ctrl; +}; +static inline struct msm_cam_v4l2_device *to_pcam( + struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct msm_cam_v4l2_device, v4l2_dev); +} + +/*pseudo v4l2 device and v4l2 event queue + for server and config cdevs*/ +struct v4l2_queue_util { + struct video_device *pvdev; + struct v4l2_fh eventHandle; +}; + +/* abstract config device for all sensor successfully probed*/ +struct msm_cam_config_dev { + struct cdev config_cdev; + struct v4l2_queue_util config_stat_event_queue; + int use_count; + /*struct msm_isp_ops* isp_subdev;*/ + struct msm_cam_media_controller *p_mctl; +}; + +/* abstract camera server device for all sensor successfully probed*/ +struct msm_cam_server_dev { + + /* config node device*/ + struct cdev server_cdev; + /* info of sensors successfully probed*/ + struct msm_camera_info camera_info; + /* info of configs successfully created*/ + struct msm_cam_config_dev_info config_info; + /* active working camera device - only one allowed at this time*/ + struct msm_cam_v4l2_device *pcam_active; + /* number of camera devices opened*/ + atomic_t number_pcam_active; + struct v4l2_queue_util server_command_queue; + /* This queue used by the config thread to send responses back to the + * control thread. It is accessed only from a process context. + */ + struct msm_device_queue ctrl_q; + uint8_t ctrl_data[max_control_command_size]; + struct msm_ctrl_cmd ctrl; + int use_count; + /* all the registered ISP subdevice*/ + struct msm_isp_ops *isp_subdev[MSM_MAX_CAMERA_CONFIGS]; + struct msm_ispif_fns ispif_fns; + +}; + +/* camera server related functions */ + + +/* ISP related functions */ +void msm_isp_vfe_dev_init(struct v4l2_subdev *vd); +/* +int msm_isp_register(struct msm_cam_v4l2_device *pcam); +*/ +int msm_isp_register(struct msm_cam_server_dev *psvr); +void msm_isp_unregister(struct msm_cam_server_dev *psvr); +int msm_ispif_register(struct msm_ispif_fns *ispif); +int msm_sensor_register(struct platform_device *pdev, + int (*sensor_probe)(const struct msm_camera_sensor_info *, + struct v4l2_subdev *, struct msm_sensor_ctrl *)); +int msm_isp_init_module(int g_num_config_nodes); + +int msm_mctl_init_module(struct msm_cam_v4l2_device *pcam); +int msm_mctl_init_user_formats(struct msm_cam_v4l2_device *pcam); +int msm_mctl_buf_done(struct msm_cam_media_controller *pmctl, + int msg_type, uint32_t y_phy); +/*Memory(PMEM) functions*/ +int msm_register_pmem(struct hlist_head *ptype, void __user *arg); +int msm_pmem_table_del(struct hlist_head *ptype, void __user *arg); +uint8_t msm_pmem_region_lookup(struct hlist_head *ptype, + int pmem_type, struct msm_pmem_region *reg, uint8_t maxcount); +uint8_t msm_pmem_region_lookup_2(struct hlist_head *ptype, + int pmem_type, + struct msm_pmem_region *reg, + uint8_t maxcount); +uint8_t msm_pmem_region_lookup_3(struct msm_cam_v4l2_device *pcam, int idx, + struct msm_pmem_region *reg, + int mem_type); +unsigned long msm_pmem_stats_vtop_lookup( + struct msm_sync *sync, + unsigned long buffer, + int fd); +unsigned long msm_pmem_stats_ptov_lookup(struct msm_sync *sync, + unsigned long addr, int *fd); + +int msm_vfe_subdev_init(struct v4l2_subdev *sd, void *data, + struct platform_device *pdev); +void msm_vfe_subdev_release(struct platform_device *pdev); + +int msm_isp_subdev_ioctl(struct v4l2_subdev *sd, + struct msm_vfe_cfg_cmd *cfgcmd, void *data); +#endif /* __KERNEL__ */ + +#endif /* _MSM_H */ diff --git a/drivers/media/video/msm/msm_axi_qos.c b/drivers/media/video/msm/msm_axi_qos.c new file mode 100644 index 00000000000..3969547cd17 --- /dev/null +++ b/drivers/media/video/msm/msm_axi_qos.c @@ -0,0 +1,47 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#define MSM_AXI_QOS_NAME "msm_camera" + +static struct clk *ebi1_clk; + +int add_axi_qos(void) +{ + ebi1_clk = clk_get(NULL, "ebi1_vfe_clk"); + if (IS_ERR(ebi1_clk)) + ebi1_clk = NULL; + else + clk_enable(ebi1_clk); + + return 0; +} + +int update_axi_qos(uint32_t rate) +{ + if (!ebi1_clk) + return 0; + + return clk_set_rate(ebi1_clk, rate * 1000); +} + +void release_axi_qos(void) +{ + if (!ebi1_clk) + return; + + clk_disable(ebi1_clk); + clk_put(ebi1_clk); + ebi1_clk = NULL; +} diff --git a/drivers/media/video/msm/msm_camera.c b/drivers/media/video/msm/msm_camera.c new file mode 100644 index 00000000000..3fddb8ebe85 --- /dev/null +++ b/drivers/media/video/msm/msm_camera.c @@ -0,0 +1,4049 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +//FIXME: most allocations need not be GFP_ATOMIC +/* FIXME: management of mutexes */ +/* FIXME: msm_pmem_region_lookup return values */ +/* FIXME: way too many copy to/from user */ +/* FIXME: does region->active mean free */ +/* FIXME: check limits on command lenghts passed from userspace */ +/* FIXME: __msm_release: which queues should we flush when opencnt != 0 */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +DEFINE_MUTEX(ctrl_cmd_lock); + +#define CAMERA_STOP_VIDEO 58 +spinlock_t pp_prev_spinlock; +spinlock_t pp_stereocam_spinlock; +spinlock_t st_frame_spinlock; + +#define ERR_USER_COPY(to) pr_err("%s(%d): copy %s user\n", \ + __func__, __LINE__, ((to) ? "to" : "from")) +#define ERR_COPY_FROM_USER() ERR_USER_COPY(0) +#define ERR_COPY_TO_USER() ERR_USER_COPY(1) +#define MAX_PMEM_CFG_BUFFERS 10 + +static struct class *msm_class; +static dev_t msm_devno; +static LIST_HEAD(msm_sensors); +struct msm_control_device *g_v4l2_control_device; +int g_v4l2_opencnt; +static int camera_node; +static enum msm_camera_type camera_type[MSM_MAX_CAMERA_SENSORS]; +static uint32_t sensor_mount_angle[MSM_MAX_CAMERA_SENSORS]; + +static const char *vfe_config_cmd[] = { + "CMD_GENERAL", /* 0 */ + "CMD_AXI_CFG_OUT1", + "CMD_AXI_CFG_SNAP_O1_AND_O2", + "CMD_AXI_CFG_OUT2", + "CMD_PICT_T_AXI_CFG", + "CMD_PICT_M_AXI_CFG", /* 5 */ + "CMD_RAW_PICT_AXI_CFG", + "CMD_FRAME_BUF_RELEASE", + "CMD_PREV_BUF_CFG", + "CMD_SNAP_BUF_RELEASE", + "CMD_SNAP_BUF_CFG", /* 10 */ + "CMD_STATS_DISABLE", + "CMD_STATS_AEC_AWB_ENABLE", + "CMD_STATS_AF_ENABLE", + "CMD_STATS_AEC_ENABLE", + "CMD_STATS_AWB_ENABLE", /* 15 */ + "CMD_STATS_ENABLE", + "CMD_STATS_AXI_CFG", + "CMD_STATS_AEC_AXI_CFG", + "CMD_STATS_AF_AXI_CFG", + "CMD_STATS_AWB_AXI_CFG", /* 20 */ + "CMD_STATS_RS_AXI_CFG", + "CMD_STATS_CS_AXI_CFG", + "CMD_STATS_IHIST_AXI_CFG", + "CMD_STATS_SKIN_AXI_CFG", + "CMD_STATS_BUF_RELEASE", /* 25 */ + "CMD_STATS_AEC_BUF_RELEASE", + "CMD_STATS_AF_BUF_RELEASE", + "CMD_STATS_AWB_BUF_RELEASE", + "CMD_STATS_RS_BUF_RELEASE", + "CMD_STATS_CS_BUF_RELEASE", /* 30 */ + "CMD_STATS_IHIST_BUF_RELEASE", + "CMD_STATS_SKIN_BUF_RELEASE", + "UPDATE_STATS_INVALID", + "CMD_AXI_CFG_SNAP_GEMINI", + "CMD_AXI_CFG_SNAP", /* 35 */ + "CMD_AXI_CFG_PREVIEW", + "CMD_AXI_CFG_VIDEO", + "CMD_STATS_IHIST_ENABLE", + "CMD_STATS_RS_ENABLE", + "CMD_STATS_CS_ENABLE", /* 40 */ + "CMD_VPE", + "CMD_AXI_CFG_VPE", + "CMD_AXI_CFG_SNAP_VPE", + "CMD_AXI_CFG_SNAP_THUMB_VPE", +}; +#define __CONTAINS(r, v, l, field) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = __v >= __r->field && \ + __e <= __r->field + __r->len; \ + res; \ +}) + +#define CONTAINS(r1, r2, field) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->field, __r2->len, field); \ +}) + +#define IN_RANGE(r, v, field) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->field) && \ + (__vv < (__r->field + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2, field) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->field) __v = __r2->field; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v, field) || \ + IN_RANGE(__r1, __e, field)); \ + res; \ +}) + +static inline void free_qcmd(struct msm_queue_cmd *qcmd) +{ + if (!qcmd || !atomic_read(&qcmd->on_heap)) + return; + if (!atomic_sub_return(1, &qcmd->on_heap)) + kfree(qcmd); +} + +static void msm_region_init(struct msm_sync *sync) +{ + INIT_HLIST_HEAD(&sync->pmem_frames); + INIT_HLIST_HEAD(&sync->pmem_stats); + spin_lock_init(&sync->pmem_frame_spinlock); + spin_lock_init(&sync->pmem_stats_spinlock); +} + +static void msm_queue_init(struct msm_device_queue *queue, const char *name) +{ + spin_lock_init(&queue->lock); + queue->len = 0; + queue->max = 0; + queue->name = name; + INIT_LIST_HEAD(&queue->list); + init_waitqueue_head(&queue->wait); +} + +static void msm_enqueue(struct msm_device_queue *queue, + struct list_head *entry) +{ + unsigned long flags; + spin_lock_irqsave(&queue->lock, flags); + queue->len++; + if (queue->len > queue->max) { + queue->max = queue->len; + CDBG("%s: queue %s new max is %d\n", __func__, + queue->name, queue->max); + } + list_add_tail(entry, &queue->list); + wake_up(&queue->wait); + CDBG("%s: woke up %s\n", __func__, queue->name); + spin_unlock_irqrestore(&queue->lock, flags); +} + +static void msm_enqueue_vpe(struct msm_device_queue *queue, + struct list_head *entry) +{ + unsigned long flags; + spin_lock_irqsave(&queue->lock, flags); + queue->len++; + if (queue->len > queue->max) { + queue->max = queue->len; + CDBG("%s: queue %s new max is %d\n", __func__, + queue->name, queue->max); + } + list_add_tail(entry, &queue->list); + CDBG("%s: woke up %s\n", __func__, queue->name); + spin_unlock_irqrestore(&queue->lock, flags); +} + +#define msm_dequeue(queue, member) ({ \ + unsigned long flags; \ + struct msm_device_queue *__q = (queue); \ + struct msm_queue_cmd *qcmd = 0; \ + spin_lock_irqsave(&__q->lock, flags); \ + if (!list_empty(&__q->list)) { \ + __q->len--; \ + qcmd = list_first_entry(&__q->list, \ + struct msm_queue_cmd, member); \ + if ((qcmd) && (&qcmd->member) && (&qcmd->member.next)) \ + list_del_init(&qcmd->member); \ + } \ + spin_unlock_irqrestore(&__q->lock, flags); \ + qcmd; \ +}) + +#define msm_delete_entry(queue, member, q_cmd) ({ \ + unsigned long flags; \ + struct msm_device_queue *__q = (queue); \ + struct msm_queue_cmd *qcmd = 0; \ + spin_lock_irqsave(&__q->lock, flags); \ + if (!list_empty(&__q->list)) { \ + list_for_each_entry(qcmd, &__q->list, member) \ + if (qcmd == q_cmd) { \ + __q->len--; \ + list_del_init(&qcmd->member); \ + CDBG("msm_delete_entry, match found\n");\ + kfree(q_cmd); \ + q_cmd = NULL; \ + break; \ + } \ + } \ + spin_unlock_irqrestore(&__q->lock, flags); \ + q_cmd; \ +}) + +#define msm_queue_drain(queue, member) do { \ + unsigned long flags; \ + struct msm_device_queue *__q = (queue); \ + struct msm_queue_cmd *qcmd; \ + spin_lock_irqsave(&__q->lock, flags); \ + while (!list_empty(&__q->list)) { \ + __q->len--; \ + qcmd = list_first_entry(&__q->list, \ + struct msm_queue_cmd, member); \ + if (qcmd) { \ + if ((&qcmd->member) && (&qcmd->member.next)) \ + list_del_init(&qcmd->member); \ + free_qcmd(qcmd); \ + } \ + } \ + spin_unlock_irqrestore(&__q->lock, flags); \ +} while (0) + +static int check_overlap(struct hlist_head *ptype, + unsigned long paddr, + unsigned long len) +{ + struct msm_pmem_region *region; + struct msm_pmem_region t = { .paddr = paddr, .len = len }; + struct hlist_node *node; + + hlist_for_each_entry(region, node, ptype, list) { + if (CONTAINS(region, &t, paddr) || + CONTAINS(&t, region, paddr) || + OVERLAPS(region, &t, paddr)) { + CDBG(" region (PHYS %p len %ld)" + " clashes with registered region" + " (paddr %p len %ld)\n", + (void *)t.paddr, t.len, + (void *)region->paddr, region->len); + return -1; + } + } + + return 0; +} + +static int check_pmem_info(struct msm_pmem_info *info, int len) +{ + if (info->offset < len && + info->offset + info->len <= len && + info->y_off < len && + info->cbcr_off < len) + return 0; + + pr_err("%s: check failed: off %d len %d y %d cbcr %d (total len %d)\n", + __func__, + info->offset, + info->len, + info->y_off, + info->cbcr_off, + len); + return -EINVAL; +} +static int msm_pmem_table_add(struct hlist_head *ptype, + struct msm_pmem_info *info, spinlock_t* pmem_spinlock, + struct msm_sync *sync) +{ + struct file *file; + unsigned long paddr; + unsigned long kvstart; + unsigned long len; + int rc; + struct msm_pmem_region *region; + unsigned long flags; + + + rc = get_pmem_file(info->fd, &paddr, &kvstart, &len, &file); + if (rc < 0) { + pr_err("%s: get_pmem_file fd %d error %d\n", + __func__, + info->fd, rc); + return rc; + } + + if (!info->len) + info->len = len; + + rc = check_pmem_info(info, len); + if (rc < 0) + return rc; + + paddr += info->offset; + len = info->len; + + spin_lock_irqsave(pmem_spinlock, flags); + if (check_overlap(ptype, paddr, len) < 0) { + spin_unlock_irqrestore(pmem_spinlock, flags); + return -EINVAL; + } + spin_unlock_irqrestore(pmem_spinlock, flags); + + + region = kmalloc(sizeof(struct msm_pmem_region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + spin_lock_irqsave(pmem_spinlock, flags); + INIT_HLIST_NODE(®ion->list); + + region->paddr = paddr; + region->len = len; + region->file = file; + memcpy(®ion->info, info, sizeof(region->info)); + + hlist_add_head(&(region->list), ptype); + spin_unlock_irqrestore(pmem_spinlock, flags); + CDBG("%s: type %d, paddr 0x%lx, vaddr 0x%lx\n", + __func__, info->type, paddr, (unsigned long)info->vaddr); + + return 0; +} + +/* return of 0 means failure */ +static uint8_t msm_pmem_region_lookup(struct hlist_head *ptype, + int pmem_type, struct msm_pmem_region *reg, uint8_t maxcount, + spinlock_t *pmem_spinlock) +{ + struct msm_pmem_region *region; + struct msm_pmem_region *regptr; + struct hlist_node *node, *n; + unsigned long flags = 0; + + uint8_t rc = 0; + + regptr = reg; + spin_lock_irqsave(pmem_spinlock, flags); + hlist_for_each_entry_safe(region, node, n, ptype, list) { + if (region->info.type == pmem_type && region->info.active) { + *regptr = *region; + rc += 1; + if (rc >= maxcount) + break; + regptr++; + } + } + spin_unlock_irqrestore(pmem_spinlock, flags); + /* After lookup failure, dump all the list entries...*/ + if (rc == 0) { + pr_err("%s: pmem_type = %d\n", __func__, pmem_type); + hlist_for_each_entry_safe(region, node, n, ptype, list) { + pr_err("listed region->info.type = %d, active = %d", + region->info.type, region->info.active); + } + + } + return rc; +} + +static uint8_t msm_pmem_region_lookup_2(struct hlist_head *ptype, + int pmem_type, + struct msm_pmem_region *reg, + uint8_t maxcount, + spinlock_t *pmem_spinlock) +{ + struct msm_pmem_region *region; + struct msm_pmem_region *regptr; + struct hlist_node *node, *n; + uint8_t rc = 0; + unsigned long flags = 0; + regptr = reg; + spin_lock_irqsave(pmem_spinlock, flags); + hlist_for_each_entry_safe(region, node, n, ptype, list) { + CDBG("%s:info.type=%d, pmem_type = %d," + "info.active = %d\n", + __func__, region->info.type, pmem_type, region->info.active); + + if (region->info.type == pmem_type && region->info.active) { + CDBG("%s:info.type=%d, pmem_type = %d," + "info.active = %d,\n", + __func__, region->info.type, pmem_type, + region->info.active); + *regptr = *region; + region->info.type = MSM_PMEM_VIDEO; + rc += 1; + if (rc >= maxcount) + break; + regptr++; + } + } + spin_unlock_irqrestore(pmem_spinlock, flags); + return rc; +} + +static int msm_pmem_frame_ptov_lookup(struct msm_sync *sync, + unsigned long pyaddr, + unsigned long pcbcraddr, + struct msm_pmem_info *pmem_info, + int clear_active) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + unsigned long flags = 0; + + spin_lock_irqsave(&sync->pmem_frame_spinlock, flags); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_frames, list) { + if (pyaddr == (region->paddr + region->info.y_off) && + pcbcraddr == (region->paddr + + region->info.cbcr_off) && + region->info.active) { + /* offset since we could pass vaddr inside + * a registerd pmem buffer + */ + memcpy(pmem_info, ®ion->info, sizeof(*pmem_info)); + if (clear_active) + region->info.active = 0; + spin_unlock_irqrestore(&sync->pmem_frame_spinlock, + flags); + return 0; + } + } + /* After lookup failure, dump all the list entries... */ + pr_err("%s, for pyaddr 0x%lx, pcbcraddr 0x%lx\n", + __func__, pyaddr, pcbcraddr); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_frames, list) { + pr_err("listed pyaddr 0x%lx, pcbcraddr 0x%lx, active = %d", + (region->paddr + region->info.y_off), + (region->paddr + region->info.cbcr_off), + region->info.active); + } + + spin_unlock_irqrestore(&sync->pmem_frame_spinlock, flags); + return -EINVAL; +} + +static int msm_pmem_frame_ptov_lookup2(struct msm_sync *sync, + unsigned long pyaddr, + struct msm_pmem_info *pmem_info, + int clear_active) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + unsigned long flags = 0; + + spin_lock_irqsave(&sync->pmem_frame_spinlock, flags); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_frames, list) { + if (pyaddr == (region->paddr + region->info.y_off) && + region->info.active) { + /* offset since we could pass vaddr inside + * a registerd pmem buffer + */ + memcpy(pmem_info, ®ion->info, sizeof(*pmem_info)); + if (clear_active) + region->info.active = 0; + spin_unlock_irqrestore(&sync->pmem_frame_spinlock, + flags); + return 0; + } + } + + spin_unlock_irqrestore(&sync->pmem_frame_spinlock, flags); + return -EINVAL; +} + +static unsigned long msm_pmem_stats_ptov_lookup(struct msm_sync *sync, + unsigned long addr, int *fd) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + unsigned long flags = 0; + + spin_lock_irqsave(&sync->pmem_stats_spinlock, flags); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_stats, list) { + if (addr == region->paddr && region->info.active) { + /* offset since we could pass vaddr inside a + * registered pmem buffer */ + *fd = region->info.fd; + region->info.active = 0; + spin_unlock_irqrestore(&sync->pmem_stats_spinlock, + flags); + return (unsigned long)(region->info.vaddr); + } + } + /* After lookup failure, dump all the list entries... */ + pr_err("%s, lookup failure, for paddr 0x%lx\n", + __func__, addr); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_stats, list) { + pr_err("listed paddr 0x%lx, active = %d", + region->paddr, + region->info.active); + } + spin_unlock_irqrestore(&sync->pmem_stats_spinlock, flags); + + return 0; +} + +static unsigned long msm_pmem_frame_vtop_lookup(struct msm_sync *sync, + unsigned long buffer, + uint32_t yoff, uint32_t cbcroff, int fd, int change_flag) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + unsigned long flags = 0; + + spin_lock_irqsave(&sync->pmem_frame_spinlock, flags); + hlist_for_each_entry_safe(region, + node, n, &sync->pmem_frames, list) { + if (((unsigned long)(region->info.vaddr) == buffer) && + (region->info.y_off == yoff) && + (region->info.cbcr_off == cbcroff) && + (region->info.fd == fd) && + (region->info.active == 0)) { + if (change_flag) + region->info.active = 1; + spin_unlock_irqrestore(&sync->pmem_frame_spinlock, + flags); + return region->paddr; + } + } + /* After lookup failure, dump all the list entries... */ + pr_err("%s, failed for vaddr 0x%lx, yoff %d cbcroff %d\n", + __func__, buffer, yoff, cbcroff); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_frames, list) { + pr_err("listed vaddr 0x%p, cbcroff %d, active = %d", + (region->info.vaddr), + (region->info.cbcr_off), + region->info.active); + } + + spin_unlock_irqrestore(&sync->pmem_frame_spinlock, flags); + + return 0; +} + +static unsigned long msm_pmem_stats_vtop_lookup( + struct msm_sync *sync, + unsigned long buffer, + int fd) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + unsigned long flags = 0; + + spin_lock_irqsave(&sync->pmem_stats_spinlock, flags); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_stats, list) { + if (((unsigned long)(region->info.vaddr) == buffer) && + (region->info.fd == fd) && + region->info.active == 0) { + region->info.active = 1; + spin_unlock_irqrestore(&sync->pmem_stats_spinlock, + flags); + return region->paddr; + } + } + /* After lookup failure, dump all the list entries... */ + pr_err("%s,look up error for vaddr %ld\n", + __func__, buffer); + hlist_for_each_entry_safe(region, node, n, &sync->pmem_stats, list) { + pr_err("listed vaddr 0x%p, active = %d", + region->info.vaddr, + region->info.active); + } + spin_unlock_irqrestore(&sync->pmem_stats_spinlock, flags); + + return 0; +} + +static int __msm_pmem_table_del(struct msm_sync *sync, + struct msm_pmem_info *pinfo) +{ + int rc = 0; + struct msm_pmem_region *region; + struct hlist_node *node, *n; + unsigned long flags = 0; + + switch (pinfo->type) { + case MSM_PMEM_PREVIEW: + case MSM_PMEM_THUMBNAIL: + case MSM_PMEM_MAINIMG: + case MSM_PMEM_RAW_MAINIMG: + case MSM_PMEM_C2D: + case MSM_PMEM_MAINIMG_VPE: + case MSM_PMEM_THUMBNAIL_VPE: + spin_lock_irqsave(&sync->pmem_frame_spinlock, flags); + hlist_for_each_entry_safe(region, node, n, + &sync->pmem_frames, list) { + + if (pinfo->type == region->info.type && + pinfo->vaddr == region->info.vaddr && + pinfo->fd == region->info.fd) { + hlist_del(node); + put_pmem_file(region->file); + kfree(region); + CDBG("%s: type %d, vaddr 0x%p\n", + __func__, pinfo->type, pinfo->vaddr); + } + } + spin_unlock_irqrestore(&sync->pmem_frame_spinlock, flags); + break; + + case MSM_PMEM_VIDEO: + case MSM_PMEM_VIDEO_VPE: + spin_lock_irqsave(&sync->pmem_frame_spinlock, flags); + hlist_for_each_entry_safe(region, node, n, + &sync->pmem_frames, list) { + + if (((region->info.type == MSM_PMEM_VIDEO) || + (region->info.type == MSM_PMEM_VIDEO_VPE)) && + pinfo->vaddr == region->info.vaddr && + pinfo->fd == region->info.fd) { + hlist_del(node); + put_pmem_file(region->file); + kfree(region); + CDBG("%s: type %d, vaddr 0x%p\n", + __func__, pinfo->type, pinfo->vaddr); + } + } + spin_unlock_irqrestore(&sync->pmem_frame_spinlock, flags); + break; + + case MSM_PMEM_AEC_AWB: + case MSM_PMEM_AF: + spin_lock_irqsave(&sync->pmem_stats_spinlock, flags); + hlist_for_each_entry_safe(region, node, n, + &sync->pmem_stats, list) { + + if (pinfo->type == region->info.type && + pinfo->vaddr == region->info.vaddr && + pinfo->fd == region->info.fd) { + hlist_del(node); + put_pmem_file(region->file); + kfree(region); + CDBG("%s: type %d, vaddr 0x%p\n", + __func__, pinfo->type, pinfo->vaddr); + } + } + spin_unlock_irqrestore(&sync->pmem_stats_spinlock, flags); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int msm_pmem_table_del(struct msm_sync *sync, void __user *arg) +{ + struct msm_pmem_info info; + + if (copy_from_user(&info, arg, sizeof(info))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + return __msm_pmem_table_del(sync, &info); +} + +static int __msm_get_frame(struct msm_sync *sync, + struct msm_frame *frame) +{ + int rc = 0; + + struct msm_pmem_info pmem_info; + struct msm_queue_cmd *qcmd = NULL; + struct msm_vfe_resp *vdata; + struct msm_vfe_phy_info *pphy; + + qcmd = msm_dequeue(&sync->frame_q, list_frame); + + if (!qcmd) { + pr_err("%s: no preview frame.\n", __func__); + return -EAGAIN; + } + + if ((!qcmd->command) && (qcmd->error_code & MSM_CAMERA_ERR_MASK)) { + frame->error_code = qcmd->error_code; + pr_err("%s: fake frame with camera error code = %d\n", + __func__, frame->error_code); + goto err; + } + + vdata = (struct msm_vfe_resp *)(qcmd->command); + pphy = &vdata->phy; + + rc = msm_pmem_frame_ptov_lookup(sync, + pphy->y_phy, + pphy->cbcr_phy, + &pmem_info, + 1); /* Clear the active flag */ + + if (rc < 0) { + pr_err("%s: cannot get frame, invalid lookup address " + "y %x cbcr %x\n", + __func__, + pphy->y_phy, + pphy->cbcr_phy); + goto err; + } + + frame->ts = qcmd->ts; + frame->buffer = (unsigned long)pmem_info.vaddr; + frame->y_off = pmem_info.y_off; + frame->cbcr_off = pmem_info.cbcr_off; + frame->fd = pmem_info.fd; + frame->path = vdata->phy.output_id; + frame->frame_id = vdata->phy.frame_id; + + CDBG("%s: y %x, cbcr %x, qcmd %x, virt_addr %x\n", + __func__, + pphy->y_phy, pphy->cbcr_phy, (int) qcmd, (int) frame->buffer); + +err: + free_qcmd(qcmd); + return rc; +} + +static int msm_get_frame(struct msm_sync *sync, void __user *arg) +{ + int rc = 0; + struct msm_frame frame; + + if (copy_from_user(&frame, + arg, + sizeof(struct msm_frame))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + rc = __msm_get_frame(sync, &frame); + if (rc < 0) + return rc; + + mutex_lock(&sync->lock); + if (sync->croplen && (!sync->stereocam_enabled)) { + if (frame.croplen != sync->croplen) { + pr_err("%s: invalid frame croplen %d," + "expecting %d\n", + __func__, + frame.croplen, + sync->croplen); + mutex_unlock(&sync->lock); + return -EINVAL; + } + + if (copy_to_user((void *)frame.cropinfo, + sync->cropinfo, + sync->croplen)) { + ERR_COPY_TO_USER(); + mutex_unlock(&sync->lock); + return -EFAULT; + } + } + + if (sync->fdroiinfo.info) { + if (copy_to_user((void *)frame.roi_info.info, + sync->fdroiinfo.info, + sync->fdroiinfo.info_len)) { + ERR_COPY_TO_USER(); + mutex_unlock(&sync->lock); + return -EFAULT; + } + } + + if (sync->stereocam_enabled) { + frame.stcam_conv_value = sync->stcam_conv_value; + frame.stcam_quality_ind = sync->stcam_quality_ind; + } + + if (copy_to_user((void *)arg, + &frame, sizeof(struct msm_frame))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + + mutex_unlock(&sync->lock); + CDBG("%s: got frame\n", __func__); + + return rc; +} + +static int msm_enable_vfe(struct msm_sync *sync, void __user *arg) +{ + int rc = -EIO; + struct camera_enable_cmd cfg; + + if (copy_from_user(&cfg, + arg, + sizeof(struct camera_enable_cmd))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + if (sync->vfefn.vfe_enable) + rc = sync->vfefn.vfe_enable(&cfg); + + return rc; +} + +static int msm_disable_vfe(struct msm_sync *sync, void __user *arg) +{ + int rc = -EIO; + struct camera_enable_cmd cfg; + + if (copy_from_user(&cfg, + arg, + sizeof(struct camera_enable_cmd))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + if (sync->vfefn.vfe_disable) + rc = sync->vfefn.vfe_disable(&cfg, NULL); + + return rc; +} + +static struct msm_queue_cmd *__msm_control(struct msm_sync *sync, + struct msm_device_queue *queue, + struct msm_queue_cmd *qcmd, + int timeout) +{ + int rc; + + CDBG("Inside __msm_control\n"); + if (sync->event_q.len <= 100 && sync->frame_q.len <= 100) { + /* wake up config thread */ + msm_enqueue(&sync->event_q, &qcmd->list_config); + } else { + pr_err("%s, Error Queue limit exceeded e_q = %d, f_q = %d\n", + __func__, sync->event_q.len, sync->frame_q.len); + free_qcmd(qcmd); + return NULL; + } + if (!queue) + return NULL; + + /* wait for config status */ + CDBG("Waiting for config status \n"); + rc = wait_event_interruptible_timeout( + queue->wait, + !list_empty_careful(&queue->list), + timeout); + CDBG("Waiting over for config status\n"); + if (list_empty_careful(&queue->list)) { + if (!rc) { + rc = -ETIMEDOUT; + pr_err("%s: wait_event error %d\n", __func__, rc); + return ERR_PTR(rc); + } else if (rc < 0) { + pr_err("%s: wait_event error %d\n", __func__, rc); + if (msm_delete_entry(&sync->event_q, + list_config, qcmd)) { + sync->ignore_qcmd = true; + sync->ignore_qcmd_type = + (int16_t)((struct msm_ctrl_cmd *) + (qcmd->command))->type; + } + return ERR_PTR(rc); + } + } + qcmd = msm_dequeue(queue, list_control); + BUG_ON(!qcmd); + CDBG("__msm_control done \n"); + return qcmd; +} + +static struct msm_queue_cmd *__msm_control_nb(struct msm_sync *sync, + struct msm_queue_cmd *qcmd_to_copy) +{ + /* Since this is a non-blocking command, we cannot use qcmd_to_copy and + * its data, since they are on the stack. We replicate them on the heap + * and mark them on_heap so that they get freed when the config thread + * dequeues them. + */ + + struct msm_ctrl_cmd *udata; + struct msm_ctrl_cmd *udata_to_copy = qcmd_to_copy->command; + + struct msm_queue_cmd *qcmd = + kmalloc(sizeof(*qcmd_to_copy) + + sizeof(*udata_to_copy) + + udata_to_copy->length, + GFP_KERNEL); + if (!qcmd) { + pr_err("%s: out of memory\n", __func__); + return ERR_PTR(-ENOMEM); + } + *qcmd = *qcmd_to_copy; + udata = qcmd->command = qcmd + 1; + memcpy(udata, udata_to_copy, sizeof(*udata)); + udata->value = udata + 1; + memcpy(udata->value, udata_to_copy->value, udata_to_copy->length); + + atomic_set(&qcmd->on_heap, 1); + + /* qcmd_resp will be set to NULL */ + return __msm_control(sync, NULL, qcmd, 0); +} + +static int msm_control(struct msm_control_device *ctrl_pmsm, + int block, + void __user *arg) +{ + int rc = 0; + + struct msm_sync *sync = ctrl_pmsm->pmsm->sync; + void __user *uptr; + struct msm_ctrl_cmd udata_resp; + struct msm_queue_cmd *qcmd_resp = NULL; + uint8_t data[max_control_command_size]; + struct msm_ctrl_cmd *udata; + struct msm_queue_cmd *qcmd = + kmalloc(sizeof(struct msm_queue_cmd) + + sizeof(struct msm_ctrl_cmd), GFP_ATOMIC); + if (!qcmd) { + pr_err("%s: out of memory\n", __func__); + return -ENOMEM; + } + udata = (struct msm_ctrl_cmd *)(qcmd + 1); + atomic_set(&(qcmd->on_heap), 1); + CDBG("Inside msm_control\n"); + if (copy_from_user(udata, arg, sizeof(struct msm_ctrl_cmd))) { + ERR_COPY_FROM_USER(); + rc = -EFAULT; + goto end; + } + + uptr = udata->value; + udata->value = data; + qcmd->type = MSM_CAM_Q_CTRL; + qcmd->command = udata; + + if (udata->length) { + if (udata->length > sizeof(data)) { + pr_err("%s: user data too large (%d, max is %d)\n", + __func__, + udata->length, + sizeof(data)); + rc = -EIO; + goto end; + } + if (copy_from_user(udata->value, uptr, udata->length)) { + ERR_COPY_FROM_USER(); + rc = -EFAULT; + goto end; + } + } + + if (unlikely(!block)) { + qcmd_resp = __msm_control_nb(sync, qcmd); + goto end; + } + + qcmd_resp = __msm_control(sync, + &ctrl_pmsm->ctrl_q, + qcmd, msecs_to_jiffies(10000)); + + /* ownership of qcmd will be transfered to event queue */ + qcmd = NULL; + + if (!qcmd_resp || IS_ERR(qcmd_resp)) { + /* Do not free qcmd_resp here. If the config thread read it, + * then it has already been freed, and we timed out because + * we did not receive a MSM_CAM_IOCTL_CTRL_CMD_DONE. If the + * config thread itself is blocked and not dequeueing commands, + * then it will either eventually unblock and process them, + * or when it is killed, qcmd will be freed in + * msm_release_config. + */ + rc = PTR_ERR(qcmd_resp); + qcmd_resp = NULL; + goto end; + } + + if (qcmd_resp->command) { + udata_resp = *(struct msm_ctrl_cmd *)qcmd_resp->command; + if (udata_resp.length > 0) { + if (copy_to_user(uptr, + udata_resp.value, + udata_resp.length)) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + goto end; + } + } + udata_resp.value = uptr; + + if (copy_to_user((void *)arg, &udata_resp, + sizeof(struct msm_ctrl_cmd))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + goto end; + } + } + +end: + free_qcmd(qcmd); + CDBG("%s: done rc = %d\n", __func__, rc); + return rc; +} + +/* Divert frames for post-processing by delivering them to the config thread; + * when post-processing is done, it will return the frame to the frame thread. + */ +static int msm_divert_frame(struct msm_sync *sync, + struct msm_vfe_resp *data, + struct msm_stats_event_ctrl *se) +{ + struct msm_pmem_info pinfo; + struct msm_postproc buf; + int rc; + + CDBG("%s: Frame PP sync->pp_mask %d\n", __func__, sync->pp_mask); + + if (!(sync->pp_mask & PP_PREV) && !(sync->pp_mask & PP_SNAP)) { + pr_err("%s: diverting frame, not in PP_PREV or PP_SNAP!\n", + __func__); + return -EINVAL; + } + + rc = msm_pmem_frame_ptov_lookup(sync, data->phy.y_phy, + data->phy.cbcr_phy, &pinfo, + 0); /* do not clear the active flag */ + + if (rc < 0) { + pr_err("%s: msm_pmem_frame_ptov_lookup failed\n", __func__); + return rc; + } + + buf.fmain.buffer = (unsigned long)pinfo.vaddr; + buf.fmain.y_off = pinfo.y_off; + buf.fmain.cbcr_off = pinfo.cbcr_off; + buf.fmain.fd = pinfo.fd; + + CDBG("%s: buf 0x%x fd %d\n", __func__, (unsigned int)buf.fmain.buffer, + buf.fmain.fd); + if (copy_to_user((void *)(se->stats_event.data), + &(buf.fmain), sizeof(struct msm_frame))) { + ERR_COPY_TO_USER(); + return -EFAULT; + } + return 0; +} + +/* Divert stereo frames for post-processing by delivering + * them to the config thread. + */ +static int msm_divert_st_frame(struct msm_sync *sync, + struct msm_vfe_resp *data, struct msm_stats_event_ctrl *se, int path) +{ + struct msm_pmem_info pinfo; + struct msm_st_frame buf; + struct video_crop_t *crop = NULL; + int rc = 0; + + if (se->stats_event.msg_id == OUTPUT_TYPE_ST_L) { + buf.type = OUTPUT_TYPE_ST_L; + } else if (se->stats_event.msg_id == OUTPUT_TYPE_ST_R) { + buf.type = OUTPUT_TYPE_ST_R; + } else { + if (se->resptype == MSM_CAM_RESP_STEREO_OP_1) { + rc = msm_pmem_frame_ptov_lookup(sync, data->phy.y_phy, + data->phy.cbcr_phy, &pinfo, + 1); /* do clear the active flag */ + buf.buf_info.path = path; + } else if (se->resptype == MSM_CAM_RESP_STEREO_OP_2) { + rc = msm_pmem_frame_ptov_lookup(sync, data->phy.y_phy, + data->phy.cbcr_phy, &pinfo, + 0); /* do not clear the active flag */ + buf.buf_info.path = path; + } else + CDBG("%s: Invalid resptype = %d\n", __func__, + se->resptype); + + if (rc < 0) { + CDBG("%s: msm_pmem_frame_ptov_lookup failed\n", + __func__); + return rc; + } + + buf.type = OUTPUT_TYPE_ST_D; + + if (sync->cropinfo != NULL) { + crop = sync->cropinfo; + switch (path) { + case OUTPUT_TYPE_P: + case OUTPUT_TYPE_T: { + buf.L.stCropInfo.in_w = crop->in1_w; + buf.L.stCropInfo.in_h = crop->in1_h; + buf.L.stCropInfo.out_w = crop->out1_w; + buf.L.stCropInfo.out_h = crop->out1_h; + buf.R.stCropInfo = buf.L.stCropInfo; + break; + } + + case OUTPUT_TYPE_V: + case OUTPUT_TYPE_S: { + buf.L.stCropInfo.in_w = crop->in2_w; + buf.L.stCropInfo.in_h = crop->in2_h; + buf.L.stCropInfo.out_w = crop->out2_w; + buf.L.stCropInfo.out_h = crop->out2_h; + buf.R.stCropInfo = buf.L.stCropInfo; + break; + } + default: { + pr_warning("%s: invalid frame path %d\n", + __func__, path); + break; + } + } + } else { + buf.L.stCropInfo.in_w = 0; + buf.L.stCropInfo.in_h = 0; + buf.L.stCropInfo.out_w = 0; + buf.L.stCropInfo.out_h = 0; + buf.R.stCropInfo = buf.L.stCropInfo; + } + + /* hardcode for now. */ + if ((path == OUTPUT_TYPE_S) || (path == OUTPUT_TYPE_T)) + buf.packing = sync->sctrl.s_snap_packing; + else + buf.packing = sync->sctrl.s_video_packing; + + buf.buf_info.buffer = (unsigned long)pinfo.vaddr; + buf.buf_info.phy_offset = pinfo.offset; + buf.buf_info.y_off = pinfo.y_off; + buf.buf_info.cbcr_off = pinfo.cbcr_off; + buf.buf_info.fd = pinfo.fd; + + CDBG("%s: buf 0x%x fd %d\n", __func__, + (unsigned int)buf.buf_info.buffer, buf.buf_info.fd); + } + + if (copy_to_user((void *)(se->stats_event.data), + &buf, sizeof(struct msm_st_frame))) { + ERR_COPY_TO_USER(); + return -EFAULT; + } + return 0; +} + +static int msm_get_stats(struct msm_sync *sync, void __user *arg) +{ + int rc = 0; + + struct msm_stats_event_ctrl se; + + struct msm_queue_cmd *qcmd = NULL; + struct msm_ctrl_cmd *ctrl = NULL; + struct msm_vfe_resp *data = NULL; + struct msm_vpe_resp *vpe_data = NULL; + struct msm_stats_buf stats; + + if (copy_from_user(&se, arg, + sizeof(struct msm_stats_event_ctrl))) { + ERR_COPY_FROM_USER(); + pr_err("%s, ERR_COPY_FROM_USER\n", __func__); + return -EFAULT; + } + + rc = 0; + + qcmd = msm_dequeue(&sync->event_q, list_config); + if (!qcmd) { + /* Should be associated with wait_event + error -512 from __msm_control*/ + pr_err("%s, qcmd is Null\n", __func__); + rc = -ETIMEDOUT; + return rc; + } + + CDBG("%s: received from DSP %d\n", __func__, qcmd->type); + + switch (qcmd->type) { + case MSM_CAM_Q_VPE_MSG: + /* Complete VPE response. */ + vpe_data = (struct msm_vpe_resp *)(qcmd->command); + se.resptype = MSM_CAM_RESP_STEREO_OP_2; + se.stats_event.type = vpe_data->evt_msg.type; + se.stats_event.msg_id = vpe_data->evt_msg.msg_id; + se.stats_event.len = vpe_data->evt_msg.len; + + if (vpe_data->type == VPE_MSG_OUTPUT_ST_L) { + CDBG("%s: Change msg_id to OUTPUT_TYPE_ST_L\n", + __func__); + se.stats_event.msg_id = OUTPUT_TYPE_ST_L; + rc = msm_divert_st_frame(sync, data, &se, + OUTPUT_TYPE_V); + } else if (vpe_data->type == VPE_MSG_OUTPUT_ST_R) { + CDBG("%s: Change msg_id to OUTPUT_TYPE_ST_R\n", + __func__); + se.stats_event.msg_id = OUTPUT_TYPE_ST_R; + rc = msm_divert_st_frame(sync, data, &se, + OUTPUT_TYPE_V); + } else { + pr_warning("%s: invalid vpe_data->type = %d\n", + __func__, vpe_data->type); + } + break; + + case MSM_CAM_Q_VFE_EVT: + case MSM_CAM_Q_VFE_MSG: + data = (struct msm_vfe_resp *)(qcmd->command); + + /* adsp event and message */ + se.resptype = MSM_CAM_RESP_STAT_EVT_MSG; + + /* 0 - msg from aDSP, 1 - event from mARM */ + se.stats_event.type = data->evt_msg.type; + se.stats_event.msg_id = data->evt_msg.msg_id; + se.stats_event.len = data->evt_msg.len; + se.stats_event.frame_id = data->evt_msg.frame_id; + + CDBG("%s: qcmd->type %d length %d msd_id %d\n", __func__, + qcmd->type, + se.stats_event.len, + se.stats_event.msg_id); + + if (data->type == VFE_MSG_COMMON) { + stats.status_bits = data->stats_msg.status_bits; + if (data->stats_msg.aec_buff) { + stats.aec.buff = + msm_pmem_stats_ptov_lookup(sync, + data->stats_msg.aec_buff, + &(stats.aec.fd)); + if (!stats.aec.buff) { + pr_err("%s: msm_pmem_stats_ptov_lookup error\n", + __func__); + rc = -EINVAL; + goto failure; + } + + } else { + stats.aec.buff = 0; + } + if (data->stats_msg.awb_buff) { + stats.awb.buff = + msm_pmem_stats_ptov_lookup(sync, + data->stats_msg.awb_buff, + &(stats.awb.fd)); + if (!stats.awb.buff) { + pr_err("%s: msm_pmem_stats_ptov_lookup error\n", + __func__); + rc = -EINVAL; + goto failure; + } + + } else { + stats.awb.buff = 0; + } + if (data->stats_msg.af_buff) { + stats.af.buff = + msm_pmem_stats_ptov_lookup(sync, + data->stats_msg.af_buff, + &(stats.af.fd)); + if (!stats.af.buff) { + pr_err("%s: msm_pmem_stats_ptov_lookup error\n", + __func__); + rc = -EINVAL; + goto failure; + } + + } else { + stats.af.buff = 0; + } + if (data->stats_msg.ihist_buff) { + stats.ihist.buff = + msm_pmem_stats_ptov_lookup(sync, + data->stats_msg.ihist_buff, + &(stats.ihist.fd)); + if (!stats.ihist.buff) { + pr_err("%s: msm_pmem_stats_ptov_lookup error\n", + __func__); + rc = -EINVAL; + goto failure; + } + + } else { + stats.ihist.buff = 0; + } + + if (data->stats_msg.rs_buff) { + stats.rs.buff = + msm_pmem_stats_ptov_lookup(sync, + data->stats_msg.rs_buff, + &(stats.rs.fd)); + if (!stats.rs.buff) { + pr_err("%s: msm_pmem_stats_ptov_lookup error\n", + __func__); + rc = -EINVAL; + goto failure; + } + + } else { + stats.rs.buff = 0; + } + + if (data->stats_msg.cs_buff) { + stats.cs.buff = + msm_pmem_stats_ptov_lookup(sync, + data->stats_msg.cs_buff, + &(stats.cs.fd)); + if (!stats.cs.buff) { + pr_err("%s: msm_pmem_stats_ptov_lookup error\n", + __func__); + rc = -EINVAL; + goto failure; + } + } else { + stats.cs.buff = 0; + } + + se.stats_event.frame_id = data->phy.frame_id; + if (copy_to_user((void *)(se.stats_event.data), + &stats, + sizeof(struct msm_stats_buf))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + goto failure; + } + } else if ((data->type >= VFE_MSG_STATS_AEC) && + (data->type <= VFE_MSG_STATS_WE)) { + /* the check above includes all stats type. */ + stats.buffer = + msm_pmem_stats_ptov_lookup(sync, + data->phy.sbuf_phy, + &(stats.fd)); + if (!stats.buffer) { + pr_err("%s: msm_pmem_stats_ptov_lookup error\n", + __func__); + rc = -EINVAL; + goto failure; + } + se.stats_event.frame_id = data->phy.frame_id; + if (copy_to_user((void *)(se.stats_event.data), + &stats, + sizeof(struct msm_stats_buf))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + goto failure; + } + } else if ((data->evt_msg.len > 0) && + (data->type == VFE_MSG_GENERAL)) { + if (copy_to_user((void *)(se.stats_event.data), + data->evt_msg.data, + data->evt_msg.len)) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + goto failure; + } + } else { + if (sync->stereocam_enabled) { + if (data->type == VFE_MSG_OUTPUT_P) { + CDBG("%s: Preview mark as st op 1\n", + __func__); + se.resptype = MSM_CAM_RESP_STEREO_OP_1; + rc = msm_divert_st_frame(sync, data, + &se, OUTPUT_TYPE_P); + break; + } else if (data->type == VFE_MSG_OUTPUT_V) { + CDBG("%s: Video mark as st op 2\n", + __func__); + se.resptype = MSM_CAM_RESP_STEREO_OP_2; + rc = msm_divert_st_frame(sync, data, + &se, OUTPUT_TYPE_V); + break; + } else if (data->type == VFE_MSG_OUTPUT_S) { + CDBG("%s: Main img mark as st op 2\n", + __func__); + se.resptype = MSM_CAM_RESP_STEREO_OP_2; + rc = msm_divert_st_frame(sync, data, + &se, OUTPUT_TYPE_S); + break; + } else if (data->type == VFE_MSG_OUTPUT_T) { + CDBG("%s: Thumb img mark as st op 2\n", + __func__); + se.resptype = MSM_CAM_RESP_STEREO_OP_2; + rc = msm_divert_st_frame(sync, data, + &se, OUTPUT_TYPE_T); + break; + } else + CDBG("%s: VFE_MSG Fall Through\n", + __func__); + } + if ((sync->pp_frame_avail == 1) && + (sync->pp_mask & PP_PREV) && + (data->type == VFE_MSG_OUTPUT_P)) { + CDBG("%s:%d:preiew PP\n", + __func__, __LINE__); + se.stats_event.frame_id = + data->phy.frame_id; + rc = msm_divert_frame(sync, data, &se); + sync->pp_frame_avail = 0; + } else { + if ((sync->pp_mask & PP_PREV) && + (data->type == VFE_MSG_OUTPUT_P)) { + se.stats_event.frame_id = + data->phy.frame_id; + free_qcmd(qcmd); + return 0; + } else + CDBG("%s:indication type is %d\n", + __func__, data->type); + } + if (sync->pp_mask & PP_SNAP) + if (data->type == VFE_MSG_OUTPUT_S || + data->type == VFE_MSG_OUTPUT_T) + rc = msm_divert_frame(sync, data, &se); + } + break; + + case MSM_CAM_Q_CTRL: + /* control command from control thread */ + ctrl = (struct msm_ctrl_cmd *)(qcmd->command); + + CDBG("%s: qcmd->type %d length %d\n", __func__, + qcmd->type, ctrl->length); + + if (ctrl->length > 0) { + if (copy_to_user((void *)(se.ctrl_cmd.value), + ctrl->value, + ctrl->length)) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + goto failure; + } + } + + se.resptype = MSM_CAM_RESP_CTRL; + + /* what to control */ + se.ctrl_cmd.type = ctrl->type; + se.ctrl_cmd.length = ctrl->length; + se.ctrl_cmd.resp_fd = ctrl->resp_fd; + break; + + case MSM_CAM_Q_V4L2_REQ: + /* control command from v4l2 client */ + ctrl = (struct msm_ctrl_cmd *)(qcmd->command); + if (ctrl->length > 0) { + if (copy_to_user((void *)(se.ctrl_cmd.value), + ctrl->value, ctrl->length)) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + goto failure; + } + } + + /* 2 tells config thread this is v4l2 request */ + se.resptype = MSM_CAM_RESP_V4L2; + + /* what to control */ + se.ctrl_cmd.type = ctrl->type; + se.ctrl_cmd.length = ctrl->length; + break; + + default: + rc = -EFAULT; + goto failure; + } /* switch qcmd->type */ + if (copy_to_user((void *)arg, &se, sizeof(se))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + goto failure; + } + +failure: + free_qcmd(qcmd); + + CDBG("%s: %d\n", __func__, rc); + return rc; +} + +static int msm_ctrl_cmd_done(struct msm_control_device *ctrl_pmsm, + void __user *arg) +{ + void __user *uptr; + struct msm_queue_cmd *qcmd = &ctrl_pmsm->qcmd; + struct msm_ctrl_cmd *command = &ctrl_pmsm->ctrl; + + if (copy_from_user(command, arg, sizeof(*command))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + atomic_set(&qcmd->on_heap, 0); + qcmd->command = command; + uptr = command->value; + + if (command->length > 0) { + command->value = ctrl_pmsm->ctrl_data; + if (command->length > sizeof(ctrl_pmsm->ctrl_data)) { + pr_err("%s: user data %d is too big (max %d)\n", + __func__, command->length, + sizeof(ctrl_pmsm->ctrl_data)); + return -EINVAL; + } + + if (copy_from_user(command->value, + uptr, + command->length)) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + } else + command->value = NULL; + + /* Ignore the command if the ctrl cmd has + return back due to signaling */ + /* Should be associated with wait_event + error -512 from __msm_control*/ + if (ctrl_pmsm->pmsm->sync->ignore_qcmd == true && + ctrl_pmsm->pmsm->sync->ignore_qcmd_type == (int16_t)command->type) { + ctrl_pmsm->pmsm->sync->ignore_qcmd = false; + ctrl_pmsm->pmsm->sync->ignore_qcmd_type = -1; + } else /* wake up control thread */ + msm_enqueue(&ctrl_pmsm->ctrl_q, &qcmd->list_control); + + return 0; +} + +static int msm_config_vpe(struct msm_sync *sync, void __user *arg) +{ + struct msm_vpe_cfg_cmd cfgcmd; + if (copy_from_user(&cfgcmd, arg, sizeof(cfgcmd))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + CDBG("%s: cmd_type %s\n", __func__, vfe_config_cmd[cfgcmd.cmd_type]); + switch (cfgcmd.cmd_type) { + case CMD_VPE: + return sync->vpefn.vpe_config(&cfgcmd, NULL); + default: + pr_err("%s: unknown command type %d\n", + __func__, cfgcmd.cmd_type); + } + return -EINVAL; +} + +static int msm_config_vfe(struct msm_sync *sync, void __user *arg) +{ + struct msm_vfe_cfg_cmd cfgcmd; + struct msm_pmem_region region[8]; + struct axidata axi_data; + + if (!sync->vfefn.vfe_config) { + pr_err("%s: no vfe_config!\n", __func__); + return -EIO; + } + + if (copy_from_user(&cfgcmd, arg, sizeof(cfgcmd))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + memset(&axi_data, 0, sizeof(axi_data)); + CDBG("%s: cmd_type %s\n", __func__, vfe_config_cmd[cfgcmd.cmd_type]); + switch (cfgcmd.cmd_type) { + case CMD_STATS_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_AEC_AWB, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + axi_data.bufnum2 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_AF, ®ion[axi_data.bufnum1], + NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + if (!axi_data.bufnum1 || !axi_data.bufnum2) { + pr_err("%s: pmem region lookup error\n", __func__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return sync->vfefn.vfe_config(&cfgcmd, &axi_data); + case CMD_STATS_AF_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_AF, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return sync->vfefn.vfe_config(&cfgcmd, &axi_data); + case CMD_STATS_AEC_AWB_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_AEC_AWB, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return sync->vfefn.vfe_config(&cfgcmd, &axi_data); + case CMD_STATS_AEC_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_AEC, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return sync->vfefn.vfe_config(&cfgcmd, &axi_data); + case CMD_STATS_AWB_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_AWB, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return sync->vfefn.vfe_config(&cfgcmd, &axi_data); + + + case CMD_STATS_IHIST_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_IHIST, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return sync->vfefn.vfe_config(&cfgcmd, &axi_data); + + case CMD_STATS_RS_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_RS, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return sync->vfefn.vfe_config(&cfgcmd, &axi_data); + + case CMD_STATS_CS_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_CS, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return sync->vfefn.vfe_config(&cfgcmd, &axi_data); + + case CMD_GENERAL: + case CMD_STATS_DISABLE: + return sync->vfefn.vfe_config(&cfgcmd, NULL); + default: + pr_err("%s: unknown command type %d\n", + __func__, cfgcmd.cmd_type); + } + + return -EINVAL; +} +static int msm_vpe_frame_cfg(struct msm_sync *sync, + void *cfgcmdin) +{ + int rc = -EIO; + struct axidata axi_data; + void *data = &axi_data; + struct msm_pmem_region region[8]; + int pmem_type; + + struct msm_vpe_cfg_cmd *cfgcmd; + cfgcmd = (struct msm_vpe_cfg_cmd *)cfgcmdin; + + memset(&axi_data, 0, sizeof(axi_data)); + CDBG("In vpe_frame_cfg cfgcmd->cmd_type = %s\n", + vfe_config_cmd[cfgcmd->cmd_type]); + switch (cfgcmd->cmd_type) { + case CMD_AXI_CFG_VPE: + pmem_type = MSM_PMEM_VIDEO_VPE; + axi_data.bufnum1 = + msm_pmem_region_lookup_2(&sync->pmem_frames, pmem_type, + ®ion[0], 8, &sync->pmem_frame_spinlock); + CDBG("axi_data.bufnum1 = %d\n", axi_data.bufnum1); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + pmem_type = MSM_PMEM_VIDEO; + break; + case CMD_AXI_CFG_SNAP_THUMB_VPE: + CDBG("%s: CMD_AXI_CFG_SNAP_THUMB_VPE", __func__); + pmem_type = MSM_PMEM_THUMBNAIL_VPE; + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[0], 8, &sync->pmem_frame_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s: THUMBNAIL_VPE pmem region lookup error\n", + __func__); + return -EINVAL; + } + break; + case CMD_AXI_CFG_SNAP_VPE: + CDBG("%s: CMD_AXI_CFG_SNAP_VPE", __func__); + pmem_type = MSM_PMEM_MAINIMG_VPE; + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[0], 8, &sync->pmem_frame_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s: MAINIMG_VPE pmem region lookup error\n", + __func__); + return -EINVAL; + } + break; + default: + pr_err("%s: unknown command type %d\n", + __func__, cfgcmd->cmd_type); + break; + } + axi_data.region = ®ion[0]; + CDBG("out vpe_frame_cfg cfgcmd->cmd_type = %s\n", + vfe_config_cmd[cfgcmd->cmd_type]); + /* send the AXI configuration command to driver */ + if (sync->vpefn.vpe_config) + rc = sync->vpefn.vpe_config(cfgcmd, data); + return rc; +} + +static int msm_frame_axi_cfg(struct msm_sync *sync, + struct msm_vfe_cfg_cmd *cfgcmd) +{ + int rc = -EIO; + struct axidata axi_data; + void *data = &axi_data; + struct msm_pmem_region region[MAX_PMEM_CFG_BUFFERS]; + int pmem_type; + + memset(&axi_data, 0, sizeof(axi_data)); + + switch (cfgcmd->cmd_type) { + + case CMD_AXI_CFG_PREVIEW: + pmem_type = MSM_PMEM_PREVIEW; + axi_data.bufnum2 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[0], MAX_PMEM_CFG_BUFFERS, + &sync->pmem_frame_spinlock); + if (!axi_data.bufnum2) { + pr_err("%s %d: pmem region lookup error (empty %d)\n", + __func__, __LINE__, + hlist_empty(&sync->pmem_frames)); + return -EINVAL; + } + break; + + case CMD_AXI_CFG_VIDEO: + pmem_type = MSM_PMEM_PREVIEW; + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[0], MAX_PMEM_CFG_BUFFERS, + &sync->pmem_frame_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + + pmem_type = MSM_PMEM_VIDEO; + axi_data.bufnum2 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[axi_data.bufnum1], + (MAX_PMEM_CFG_BUFFERS-(axi_data.bufnum1)), + &sync->pmem_frame_spinlock); + if (!axi_data.bufnum2) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + break; + + case CMD_AXI_CFG_SNAP: + CDBG("%s, CMD_AXI_CFG_SNAP, type=%d\n", __func__, + cfgcmd->cmd_type); + pmem_type = MSM_PMEM_THUMBNAIL; + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[0], MAX_PMEM_CFG_BUFFERS, + &sync->pmem_frame_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + + pmem_type = MSM_PMEM_MAINIMG; + axi_data.bufnum2 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[axi_data.bufnum1], + (MAX_PMEM_CFG_BUFFERS-(axi_data.bufnum1)), + &sync->pmem_frame_spinlock); + if (!axi_data.bufnum2) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + break; + + case CMD_AXI_CFG_ZSL: + CDBG("%s, CMD_AXI_CFG_ZSL, type = %d\n", __func__, + cfgcmd->cmd_type); + pmem_type = MSM_PMEM_PREVIEW; + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[0], MAX_PMEM_CFG_BUFFERS, + &sync->pmem_frame_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + + pmem_type = MSM_PMEM_THUMBNAIL; + axi_data.bufnum2 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[axi_data.bufnum1], + (MAX_PMEM_CFG_BUFFERS-(axi_data.bufnum1)), + &sync->pmem_frame_spinlock); + if (!axi_data.bufnum2) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + + pmem_type = MSM_PMEM_MAINIMG; + axi_data.bufnum3 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[axi_data.bufnum1 + axi_data.bufnum2], + (MAX_PMEM_CFG_BUFFERS - axi_data.bufnum1 - + axi_data.bufnum2), &sync->pmem_frame_spinlock); + if (!axi_data.bufnum3) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + break; + + case CMD_RAW_PICT_AXI_CFG: + pmem_type = MSM_PMEM_RAW_MAINIMG; + axi_data.bufnum2 = + msm_pmem_region_lookup(&sync->pmem_frames, pmem_type, + ®ion[0], MAX_PMEM_CFG_BUFFERS, + &sync->pmem_frame_spinlock); + if (!axi_data.bufnum2) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + break; + + case CMD_GENERAL: + data = NULL; + break; + + default: + pr_err("%s: unknown command type %d\n", + __func__, cfgcmd->cmd_type); + return -EINVAL; + } + + axi_data.region = ®ion[0]; + + /* send the AXI configuration command to driver */ + if (sync->vfefn.vfe_config) + rc = sync->vfefn.vfe_config(cfgcmd, data); + + return rc; +} + +static int msm_get_sensor_info(struct msm_sync *sync, void __user *arg) +{ + int rc = 0; + struct msm_camsensor_info info; + struct msm_camera_sensor_info *sdata; + + if (copy_from_user(&info, + arg, + sizeof(struct msm_camsensor_info))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + sdata = sync->pdev->dev.platform_data; + if (sync->sctrl.s_camera_type == BACK_CAMERA_3D) + info.support_3d = true; + else + info.support_3d = false; + memcpy(&info.name[0], + sdata->sensor_name, + MAX_SENSOR_NAME); + info.flash_enabled = sdata->flash_data->flash_type != + MSM_CAMERA_FLASH_NONE; + + /* copy back to user space */ + if (copy_to_user((void *)arg, + &info, + sizeof(struct msm_camsensor_info))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + + return rc; +} + +static int msm_get_camera_info(void __user *arg) +{ + int rc = 0; + int i = 0; + struct msm_camera_info info; + + if (copy_from_user(&info, arg, sizeof(struct msm_camera_info))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + CDBG("%s: camera_node %d\n", __func__, camera_node); + info.num_cameras = camera_node; + + for (i = 0; i < camera_node; i++) { + info.has_3d_support[i] = 0; + info.is_internal_cam[i] = 0; + info.s_mount_angle[i] = sensor_mount_angle[i]; + switch (camera_type[i]) { + case FRONT_CAMERA_2D: + info.is_internal_cam[i] = 1; + break; + case BACK_CAMERA_3D: + info.has_3d_support[i] = 1; + break; + case BACK_CAMERA_2D: + default: + break; + } + } + /* copy back to user space */ + if (copy_to_user((void *)arg, &info, sizeof(struct msm_camera_info))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + return rc; +} + +static int __msm_put_frame_buf(struct msm_sync *sync, + struct msm_frame *pb) +{ + unsigned long pphy; + struct msm_vfe_cfg_cmd cfgcmd; + + int rc = -EIO; + + /* Change the active flag. */ + pphy = msm_pmem_frame_vtop_lookup(sync, + pb->buffer, + pb->y_off, pb->cbcr_off, pb->fd, 1); + + if (pphy != 0) { + CDBG("%s: rel: vaddr %lx, paddr %lx\n", + __func__, + pb->buffer, pphy); + cfgcmd.cmd_type = CMD_FRAME_BUF_RELEASE; + cfgcmd.value = (void *)pb; + if (sync->vfefn.vfe_config) + rc = sync->vfefn.vfe_config(&cfgcmd, &pphy); + } else { + pr_err("%s: msm_pmem_frame_vtop_lookup failed\n", + __func__); + rc = -EINVAL; + } + + return rc; +} +static int __msm_put_pic_buf(struct msm_sync *sync, + struct msm_frame *pb) +{ + unsigned long pphy; + struct msm_vfe_cfg_cmd cfgcmd; + + int rc = -EIO; + + pphy = msm_pmem_frame_vtop_lookup(sync, + pb->buffer, + pb->y_off, pb->cbcr_off, pb->fd, 1); + + if (pphy != 0) { + CDBG("%s: rel: vaddr %lx, paddr %lx\n", + __func__, + pb->buffer, pphy); + cfgcmd.cmd_type = CMD_SNAP_BUF_RELEASE; + cfgcmd.value = (void *)pb; + if (sync->vfefn.vfe_config) + rc = sync->vfefn.vfe_config(&cfgcmd, &pphy); + } else { + pr_err("%s: msm_pmem_frame_vtop_lookup failed\n", + __func__); + rc = -EINVAL; + } + + return rc; +} + + +static int msm_put_frame_buffer(struct msm_sync *sync, void __user *arg) +{ + struct msm_frame buf_t; + + if (copy_from_user(&buf_t, + arg, + sizeof(struct msm_frame))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + return __msm_put_frame_buf(sync, &buf_t); +} + + +static int msm_put_pic_buffer(struct msm_sync *sync, void __user *arg) +{ + struct msm_frame buf_t; + + if (copy_from_user(&buf_t, + arg, + sizeof(struct msm_frame))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + return __msm_put_pic_buf(sync, &buf_t); +} + +static int __msm_register_pmem(struct msm_sync *sync, + struct msm_pmem_info *pinfo) +{ + int rc = 0; + + switch (pinfo->type) { + case MSM_PMEM_VIDEO: + case MSM_PMEM_PREVIEW: + case MSM_PMEM_THUMBNAIL: + case MSM_PMEM_MAINIMG: + case MSM_PMEM_RAW_MAINIMG: + case MSM_PMEM_VIDEO_VPE: + case MSM_PMEM_C2D: + case MSM_PMEM_MAINIMG_VPE: + case MSM_PMEM_THUMBNAIL_VPE: + rc = msm_pmem_table_add(&sync->pmem_frames, pinfo, + &sync->pmem_frame_spinlock, sync); + break; + + case MSM_PMEM_AEC_AWB: + case MSM_PMEM_AF: + case MSM_PMEM_AEC: + case MSM_PMEM_AWB: + case MSM_PMEM_RS: + case MSM_PMEM_CS: + case MSM_PMEM_IHIST: + case MSM_PMEM_SKIN: + + rc = msm_pmem_table_add(&sync->pmem_stats, pinfo, + &sync->pmem_stats_spinlock, sync); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int msm_register_pmem(struct msm_sync *sync, void __user *arg) +{ + struct msm_pmem_info info; + + if (copy_from_user(&info, arg, sizeof(info))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + return __msm_register_pmem(sync, &info); +} + +static int msm_stats_axi_cfg(struct msm_sync *sync, + struct msm_vfe_cfg_cmd *cfgcmd) +{ + int rc = -EIO; + struct axidata axi_data; + void *data = &axi_data; + + struct msm_pmem_region region[3]; + int pmem_type = MSM_PMEM_MAX; + + memset(&axi_data, 0, sizeof(axi_data)); + + switch (cfgcmd->cmd_type) { + case CMD_STATS_AXI_CFG: + pmem_type = MSM_PMEM_AEC_AWB; + break; + case CMD_STATS_AF_AXI_CFG: + pmem_type = MSM_PMEM_AF; + break; + case CMD_GENERAL: + data = NULL; + break; + default: + pr_err("%s: unknown command type %d\n", + __func__, cfgcmd->cmd_type); + return -EINVAL; + } + + if (cfgcmd->cmd_type != CMD_GENERAL) { + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, pmem_type, + ®ion[0], NUM_STAT_OUTPUT_BUFFERS, + &sync->pmem_stats_spinlock); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + } + + /* send the AEC/AWB STATS configuration command to driver */ + if (sync->vfefn.vfe_config) + rc = sync->vfefn.vfe_config(cfgcmd, &axi_data); + + return rc; +} + +static int msm_put_stats_buffer(struct msm_sync *sync, void __user *arg) +{ + int rc = -EIO; + + struct msm_stats_buf buf; + unsigned long pphy; + struct msm_vfe_cfg_cmd cfgcmd; + + if (copy_from_user(&buf, arg, + sizeof(struct msm_stats_buf))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + CDBG("%s\n", __func__); + pphy = msm_pmem_stats_vtop_lookup(sync, buf.buffer, buf.fd); + + if (pphy != 0) { + if (buf.type == STAT_AEAW) + cfgcmd.cmd_type = CMD_STATS_BUF_RELEASE; + else if (buf.type == STAT_AF) + cfgcmd.cmd_type = CMD_STATS_AF_BUF_RELEASE; + else if (buf.type == STAT_AEC) + cfgcmd.cmd_type = CMD_STATS_AEC_BUF_RELEASE; + else if (buf.type == STAT_AWB) + cfgcmd.cmd_type = CMD_STATS_AWB_BUF_RELEASE; + else if (buf.type == STAT_IHIST) + cfgcmd.cmd_type = CMD_STATS_IHIST_BUF_RELEASE; + else if (buf.type == STAT_RS) + cfgcmd.cmd_type = CMD_STATS_RS_BUF_RELEASE; + else if (buf.type == STAT_CS) + cfgcmd.cmd_type = CMD_STATS_CS_BUF_RELEASE; + + else { + pr_err("%s: invalid buf type %d\n", + __func__, + buf.type); + rc = -EINVAL; + goto put_done; + } + + cfgcmd.value = (void *)&buf; + + if (sync->vfefn.vfe_config) { + rc = sync->vfefn.vfe_config(&cfgcmd, &pphy); + if (rc < 0) + pr_err("%s: vfe_config error %d\n", + __func__, rc); + } else + pr_err("%s: vfe_config is NULL\n", __func__); + } else { + pr_err("%s: NULL physical address\n", __func__); + rc = -EINVAL; + } + +put_done: + return rc; +} + +static int msm_axi_config(struct msm_sync *sync, void __user *arg) +{ + struct msm_vfe_cfg_cmd cfgcmd; + + if (copy_from_user(&cfgcmd, arg, sizeof(cfgcmd))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + switch (cfgcmd.cmd_type) { + case CMD_AXI_CFG_VIDEO: + case CMD_AXI_CFG_PREVIEW: + case CMD_AXI_CFG_SNAP: + case CMD_RAW_PICT_AXI_CFG: + case CMD_AXI_CFG_ZSL: + CDBG("%s, cfgcmd.cmd_type = %d\n", __func__, cfgcmd.cmd_type); + return msm_frame_axi_cfg(sync, &cfgcmd); + + case CMD_AXI_CFG_VPE: + case CMD_AXI_CFG_SNAP_VPE: + case CMD_AXI_CFG_SNAP_THUMB_VPE: + return msm_vpe_frame_cfg(sync, (void *)&cfgcmd); + + case CMD_STATS_AXI_CFG: + case CMD_STATS_AF_AXI_CFG: + return msm_stats_axi_cfg(sync, &cfgcmd); + + default: + pr_err("%s: unknown command type %d\n", + __func__, + cfgcmd.cmd_type); + return -EINVAL; + } + + return 0; +} + +static int __msm_get_pic(struct msm_sync *sync, + struct msm_frame *frame) +{ + + int rc = 0; + struct msm_queue_cmd *qcmd = NULL; + struct msm_vfe_resp *vdata; + struct msm_vfe_phy_info *pphy; + struct msm_pmem_info pmem_info; + struct msm_frame *pframe; + + qcmd = msm_dequeue(&sync->pict_q, list_pict); + + if (!qcmd) { + pr_err("%s: no pic frame.\n", __func__); + return -EAGAIN; + } + + if (MSM_CAM_Q_PP_MSG != qcmd->type) { + vdata = (struct msm_vfe_resp *)(qcmd->command); + pphy = &vdata->phy; + + rc = msm_pmem_frame_ptov_lookup2(sync, + pphy->y_phy, + &pmem_info, + 1); /* mark pic frame in use */ + + if (rc < 0) { + pr_err("%s: cannot get pic frame, invalid lookup" + " address y %x cbcr %x\n", + __func__, pphy->y_phy, pphy->cbcr_phy); + goto err; + } + + frame->ts = qcmd->ts; + frame->buffer = (unsigned long)pmem_info.vaddr; + frame->y_off = pmem_info.y_off; + frame->cbcr_off = pmem_info.cbcr_off; + frame->fd = pmem_info.fd; + if (sync->stereocam_enabled && + sync->stereo_state != STEREO_RAW_SNAP_STARTED) { + if (pmem_info.type == MSM_PMEM_THUMBNAIL_VPE) + frame->path = OUTPUT_TYPE_T; + else + frame->path = OUTPUT_TYPE_S; + } else + frame->path = vdata->phy.output_id; + + CDBG("%s: y %x, cbcr %x, qcmd %x, virt_addr %x\n", + __func__, pphy->y_phy, + pphy->cbcr_phy, (int) qcmd, (int) frame->buffer); + } else { /* PP */ + pframe = (struct msm_frame *)(qcmd->command); + frame->ts = qcmd->ts; + frame->buffer = pframe->buffer; + frame->y_off = pframe->y_off; + frame->cbcr_off = pframe->cbcr_off; + frame->fd = pframe->fd; + frame->path = pframe->path; + CDBG("%s: PP y_off %x, cbcr_off %x, path %d vaddr 0x%x\n", + __func__, frame->y_off, frame->cbcr_off, frame->path, + (int) frame->buffer); + } + +err: + free_qcmd(qcmd); + + return rc; +} + +static int msm_get_pic(struct msm_sync *sync, void __user *arg) +{ + int rc = 0; + struct msm_frame frame; + + if (copy_from_user(&frame, + arg, + sizeof(struct msm_frame))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + rc = __msm_get_pic(sync, &frame); + if (rc < 0) + return rc; + + if (sync->croplen && (!sync->stereocam_enabled)) { + if (frame.croplen != sync->croplen) { + pr_err("%s: invalid frame croplen %d," + "expecting %d\n", + __func__, + frame.croplen, + sync->croplen); + return -EINVAL; + } + + if (copy_to_user((void *)frame.cropinfo, + sync->cropinfo, + sync->croplen)) { + ERR_COPY_TO_USER(); + return -EFAULT; + } + } + CDBG("%s: copy snapshot frame to user\n", __func__); + if (copy_to_user((void *)arg, + &frame, sizeof(struct msm_frame))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + + CDBG("%s: got pic frame\n", __func__); + + return rc; +} + +static int msm_set_crop(struct msm_sync *sync, void __user *arg) +{ + struct crop_info crop; + + mutex_lock(&sync->lock); + if (copy_from_user(&crop, + arg, + sizeof(struct crop_info))) { + ERR_COPY_FROM_USER(); + mutex_unlock(&sync->lock); + return -EFAULT; + } + + if (crop.len != CROP_LEN) { + mutex_unlock(&sync->lock); + return -EINVAL; + } + + if (!sync->croplen) { + sync->cropinfo = kmalloc(crop.len, GFP_KERNEL); + if (!sync->cropinfo) { + mutex_unlock(&sync->lock); + return -ENOMEM; + } + } + + if (copy_from_user(sync->cropinfo, + crop.info, + crop.len)) { + ERR_COPY_FROM_USER(); + sync->croplen = 0; + kfree(sync->cropinfo); + mutex_unlock(&sync->lock); + return -EFAULT; + } + + sync->croplen = crop.len; + + mutex_unlock(&sync->lock); + return 0; +} + +static int msm_error_config(struct msm_sync *sync, void __user *arg) +{ + struct msm_queue_cmd *qcmd = + kmalloc(sizeof(struct msm_queue_cmd), GFP_KERNEL); + + qcmd->command = NULL; + + if (qcmd) + atomic_set(&(qcmd->on_heap), 1); + + if (copy_from_user(&(qcmd->error_code), arg, sizeof(uint32_t))) { + ERR_COPY_FROM_USER(); + free_qcmd(qcmd); + return -EFAULT; + } + + pr_err("%s: Enqueue Fake Frame with error code = %d\n", __func__, + qcmd->error_code); + msm_enqueue(&sync->frame_q, &qcmd->list_frame); + return 0; +} + +static int msm_set_fd_roi(struct msm_sync *sync, void __user *arg) +{ + struct fd_roi_info fd_roi; + + mutex_lock(&sync->lock); + if (copy_from_user(&fd_roi, + arg, + sizeof(struct fd_roi_info))) { + ERR_COPY_FROM_USER(); + mutex_unlock(&sync->lock); + return -EFAULT; + } + if (fd_roi.info_len <= 0) { + mutex_unlock(&sync->lock); + return -EFAULT; + } + + if (!sync->fdroiinfo.info) { + sync->fdroiinfo.info = kmalloc(fd_roi.info_len, GFP_KERNEL); + if (!sync->fdroiinfo.info) { + mutex_unlock(&sync->lock); + return -ENOMEM; + } + sync->fdroiinfo.info_len = fd_roi.info_len; + } else if (sync->fdroiinfo.info_len < fd_roi.info_len) { + mutex_unlock(&sync->lock); + return -EINVAL; + } + + if (copy_from_user(sync->fdroiinfo.info, + fd_roi.info, + fd_roi.info_len)) { + ERR_COPY_FROM_USER(); + kfree(sync->fdroiinfo.info); + sync->fdroiinfo.info = NULL; + mutex_unlock(&sync->lock); + return -EFAULT; + } + mutex_unlock(&sync->lock); + return 0; +} + +static int msm_pp_grab(struct msm_sync *sync, void __user *arg) +{ + uint32_t enable; + if (copy_from_user(&enable, arg, sizeof(enable))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } else { + enable &= PP_MASK; + if (enable & (enable - 1)) { + pr_err("%s: error: more than one PP request!\n", + __func__); + return -EINVAL; + } + if (sync->pp_mask) { + if (enable) { + pr_err("%s: postproc %x is already enabled\n", + __func__, sync->pp_mask & enable); + return -EINVAL; + } else { + sync->pp_mask &= enable; + CDBG("%s: sync->pp_mask %d enable %d\n", + __func__, sync->pp_mask, enable); + return 0; + } + } + + CDBG("%s: sync->pp_mask %d enable %d\n", __func__, + sync->pp_mask, enable); + sync->pp_mask |= enable; + } + + return 0; +} + +static int msm_put_st_frame(struct msm_sync *sync, void __user *arg) +{ + unsigned long flags; + unsigned long st_pphy; + if (sync->stereocam_enabled) { + /* Make stereo frame ready for VPE. */ + struct msm_st_frame stereo_frame_half; + + if (copy_from_user(&stereo_frame_half, arg, + sizeof(stereo_frame_half))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + if (stereo_frame_half.type == OUTPUT_TYPE_ST_L) { + struct msm_vfe_resp *vfe_rp; + struct msm_queue_cmd *qcmd; + + spin_lock_irqsave(&pp_stereocam_spinlock, flags); + if (!sync->pp_stereocam) { + pr_warning("%s: no stereo frame to deliver!\n", + __func__); + spin_unlock_irqrestore(&pp_stereocam_spinlock, + flags); + return -EINVAL; + } + CDBG("%s: delivering left frame to VPE\n", __func__); + + qcmd = sync->pp_stereocam; + sync->pp_stereocam = NULL; + spin_unlock_irqrestore(&pp_stereocam_spinlock, flags); + + vfe_rp = (struct msm_vfe_resp *)qcmd->command; + + CDBG("%s: Left Py = 0x%x y_off = %d cbcr_off = %d\n", + __func__, vfe_rp->phy.y_phy, + stereo_frame_half.L.buf_y_off, + stereo_frame_half.L.buf_cbcr_off); + + sync->vpefn.vpe_cfg_offset(stereo_frame_half.packing, + vfe_rp->phy.y_phy + stereo_frame_half.L.buf_y_off, + vfe_rp->phy.y_phy + stereo_frame_half.L.buf_cbcr_off, + &(qcmd->ts), OUTPUT_TYPE_ST_L, stereo_frame_half.L, + stereo_frame_half.frame_id); + + free_qcmd(qcmd); + } else if (stereo_frame_half.type == OUTPUT_TYPE_ST_R) { + CDBG("%s: delivering right frame to VPE\n", __func__); + spin_lock_irqsave(&st_frame_spinlock, flags); + + sync->stcam_conv_value = + stereo_frame_half.buf_info.stcam_conv_value; + sync->stcam_quality_ind = + stereo_frame_half.buf_info.stcam_quality_ind; + + st_pphy = msm_pmem_frame_vtop_lookup(sync, + stereo_frame_half.buf_info.buffer, + stereo_frame_half.buf_info.y_off, + stereo_frame_half.buf_info.cbcr_off, + stereo_frame_half.buf_info.fd, + 0); /* Do not change the active flag. */ + + sync->vpefn.vpe_cfg_offset(stereo_frame_half.packing, + st_pphy + stereo_frame_half.R.buf_y_off, + st_pphy + stereo_frame_half.R.buf_cbcr_off, + NULL, OUTPUT_TYPE_ST_R, stereo_frame_half.R, + stereo_frame_half.frame_id); + + spin_unlock_irqrestore(&st_frame_spinlock, flags); + } else { + CDBG("%s: Invalid Msg\n", __func__); + } + } + + return 0; +} + +static struct msm_queue_cmd *msm_get_pp_qcmd(struct msm_frame* frame) +{ + struct msm_queue_cmd *qcmd = + kmalloc(sizeof(struct msm_queue_cmd) + + sizeof(struct msm_frame), GFP_ATOMIC); + qcmd->command = (struct msm_frame *)(qcmd + 1); + + qcmd->type = MSM_CAM_Q_PP_MSG; + + ktime_get_ts(&(qcmd->ts)); + memcpy(qcmd->command, frame, sizeof(struct msm_frame)); + atomic_set(&(qcmd->on_heap), 1); + return qcmd; +} + +static int msm_pp_release(struct msm_sync *sync, void __user *arg) +{ + unsigned long flags; + + if (!sync->pp_mask) { + pr_warning("%s: pp not in progress for\n", __func__); + return -EINVAL; + } + if (sync->pp_mask & PP_PREV) { + + + spin_lock_irqsave(&pp_prev_spinlock, flags); + if (!sync->pp_prev) { + pr_err("%s: no preview frame to deliver!\n", + __func__); + spin_unlock_irqrestore(&pp_prev_spinlock, + flags); + return -EINVAL; + } + CDBG("%s: delivering pp_prev\n", __func__); + + if (sync->frame_q.len <= 100 && + sync->event_q.len <= 100) { + msm_enqueue(&sync->frame_q, + &sync->pp_prev->list_frame); + } else { + pr_err("%s, Error Queue limit exceeded f_q=%d,\ + e_q = %d\n", + __func__, sync->frame_q.len, + sync->event_q.len); + free_qcmd(sync->pp_prev); + goto done; + } + + sync->pp_prev = NULL; + spin_unlock_irqrestore(&pp_prev_spinlock, flags); + goto done; + } + + if ((sync->pp_mask & PP_SNAP) || + (sync->pp_mask & PP_RAW_SNAP)) { + struct msm_frame frame; + struct msm_queue_cmd *qcmd; + + if (copy_from_user(&frame, + arg, + sizeof(struct msm_frame))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + qcmd = msm_get_pp_qcmd(&frame); + if (!qcmd) { + pr_err("%s: no snapshot to deliver!\n", __func__); + return -EINVAL; + } + CDBG("%s: delivering pp snap\n", __func__); + msm_enqueue(&sync->pict_q, &qcmd->list_pict); + } + +done: + return 0; +} + +static long msm_ioctl_common(struct msm_cam_device *pmsm, + unsigned int cmd, + void __user *argp) +{ + switch (cmd) { + case MSM_CAM_IOCTL_REGISTER_PMEM: + CDBG("%s cmd = MSM_CAM_IOCTL_REGISTER_PMEM\n", __func__); + return msm_register_pmem(pmsm->sync, argp); + case MSM_CAM_IOCTL_UNREGISTER_PMEM: + CDBG("%s cmd = MSM_CAM_IOCTL_UNREGISTER_PMEM\n", __func__); + return msm_pmem_table_del(pmsm->sync, argp); + case MSM_CAM_IOCTL_RELEASE_FRAME_BUFFER: + CDBG("%s cmd = MSM_CAM_IOCTL_RELEASE_FRAME_BUFFER\n", __func__); + return msm_put_frame_buffer(pmsm->sync, argp); + break; + default: + CDBG("%s cmd invalid\n", __func__); + return -EINVAL; + } +} + +static long msm_ioctl_config(struct file *filep, unsigned int cmd, + unsigned long arg) +{ + int rc = -EINVAL; + void __user *argp = (void __user *)arg; + struct msm_cam_device *pmsm = filep->private_data; + + CDBG("%s: cmd %d\n", __func__, _IOC_NR(cmd)); + + switch (cmd) { + case MSM_CAM_IOCTL_GET_SENSOR_INFO: + rc = msm_get_sensor_info(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_CONFIG_VFE: + /* Coming from config thread for update */ + rc = msm_config_vfe(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_CONFIG_VPE: + /* Coming from config thread for update */ + rc = msm_config_vpe(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_GET_STATS: + /* Coming from config thread wait + * for vfe statistics and control requests */ + rc = msm_get_stats(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_ENABLE_VFE: + /* This request comes from control thread: + * enable either QCAMTASK or VFETASK */ + rc = msm_enable_vfe(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_DISABLE_VFE: + /* This request comes from control thread: + * disable either QCAMTASK or VFETASK */ + rc = msm_disable_vfe(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_VFE_APPS_RESET: + msm_camio_vfe_blk_reset(); + rc = 0; + break; + + case MSM_CAM_IOCTL_RELEASE_STATS_BUFFER: + rc = msm_put_stats_buffer(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_AXI_CONFIG: + case MSM_CAM_IOCTL_AXI_VPE_CONFIG: + rc = msm_axi_config(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_SET_CROP: + rc = msm_set_crop(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_SET_FD_ROI: + rc = msm_set_fd_roi(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_PICT_PP: + /* Grab one preview frame or one snapshot + * frame. + */ + rc = msm_pp_grab(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_PICT_PP_DONE: + /* Release the preview of snapshot frame + * that was grabbed. + */ + rc = msm_pp_release(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_PUT_ST_FRAME: + /* Release the left or right frame + * that was sent for stereo processing. + */ + rc = msm_put_st_frame(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_SENSOR_IO_CFG: + rc = pmsm->sync->sctrl.s_config(argp); + break; + + case MSM_CAM_IOCTL_FLASH_LED_CFG: { + uint32_t led_state; + if (copy_from_user(&led_state, argp, sizeof(led_state))) { + ERR_COPY_FROM_USER(); + rc = -EFAULT; + } else + rc = msm_camera_flash_set_led_state(pmsm->sync-> + sdata->flash_data, led_state); + break; + } + + case MSM_CAM_IOCTL_STROBE_FLASH_CFG: { + uint32_t flash_type; + if (copy_from_user(&flash_type, argp, sizeof(flash_type))) { + pr_err("msm_strobe_flash_init failed"); + ERR_COPY_FROM_USER(); + rc = -EFAULT; + } else { + CDBG("msm_strobe_flash_init enter"); + rc = msm_strobe_flash_init(pmsm->sync, flash_type); + } + break; + } + + case MSM_CAM_IOCTL_STROBE_FLASH_RELEASE: + if (pmsm->sync->sdata->strobe_flash_data) { + rc = pmsm->sync->sfctrl.strobe_flash_release( + pmsm->sync->sdata->strobe_flash_data, 0); + } + break; + + case MSM_CAM_IOCTL_STROBE_FLASH_CHARGE: { + uint32_t charge_en; + if (copy_from_user(&charge_en, argp, sizeof(charge_en))) { + ERR_COPY_FROM_USER(); + rc = -EFAULT; + } else + rc = pmsm->sync->sfctrl.strobe_flash_charge( + pmsm->sync->sdata->strobe_flash_data->flash_charge, + charge_en, pmsm->sync->sdata->strobe_flash_data-> + flash_recharge_duration); + break; + } + + case MSM_CAM_IOCTL_FLASH_CTRL: { + struct flash_ctrl_data flash_info; + if (copy_from_user(&flash_info, argp, sizeof(flash_info))) { + ERR_COPY_FROM_USER(); + rc = -EFAULT; + } else + rc = msm_flash_ctrl(pmsm->sync->sdata, &flash_info); + + break; + } + + case MSM_CAM_IOCTL_ERROR_CONFIG: + rc = msm_error_config(pmsm->sync, argp); + break; + + case MSM_CAM_IOCTL_ABORT_CAPTURE: { + unsigned long flags = 0; + CDBG("get_pic:MSM_CAM_IOCTL_ABORT_CAPTURE\n"); + spin_lock_irqsave(&pmsm->sync->abort_pict_lock, flags); + pmsm->sync->get_pic_abort = 1; + spin_unlock_irqrestore(&pmsm->sync->abort_pict_lock, flags); + wake_up(&(pmsm->sync->pict_q.wait)); + rc = 0; + break; + } + + default: + rc = msm_ioctl_common(pmsm, cmd, argp); + break; + } + + CDBG("%s: cmd %d DONE\n", __func__, _IOC_NR(cmd)); + return rc; +} + +static int msm_unblock_poll_frame(struct msm_sync *); + +static long msm_ioctl_frame(struct file *filep, unsigned int cmd, + unsigned long arg) +{ + int rc = -EINVAL; + void __user *argp = (void __user *)arg; + struct msm_cam_device *pmsm = filep->private_data; + + + switch (cmd) { + case MSM_CAM_IOCTL_GETFRAME: + /* Coming from frame thread to get frame + * after SELECT is done */ + rc = msm_get_frame(pmsm->sync, argp); + break; + case MSM_CAM_IOCTL_RELEASE_FRAME_BUFFER: + rc = msm_put_frame_buffer(pmsm->sync, argp); + break; + case MSM_CAM_IOCTL_UNBLOCK_POLL_FRAME: + rc = msm_unblock_poll_frame(pmsm->sync); + break; + default: + break; + } + + return rc; +} + +static int msm_unblock_poll_pic(struct msm_sync *sync); +static long msm_ioctl_pic(struct file *filep, unsigned int cmd, + unsigned long arg) +{ + int rc = -EINVAL; + void __user *argp = (void __user *)arg; + struct msm_cam_device *pmsm = filep->private_data; + + + switch (cmd) { + case MSM_CAM_IOCTL_GET_PICTURE: + rc = msm_get_pic(pmsm->sync, argp); + break; + case MSM_CAM_IOCTL_RELEASE_PIC_BUFFER: + rc = msm_put_pic_buffer(pmsm->sync, argp); + break; + case MSM_CAM_IOCTL_UNBLOCK_POLL_PIC_FRAME: + rc = msm_unblock_poll_pic(pmsm->sync); + break; + default: + break; + } + + return rc; +} + + +static long msm_ioctl_control(struct file *filep, unsigned int cmd, + unsigned long arg) +{ + int rc = -EINVAL; + void __user *argp = (void __user *)arg; + struct msm_control_device *ctrl_pmsm = filep->private_data; + struct msm_cam_device *pmsm = ctrl_pmsm->pmsm; + + switch (cmd) { + case MSM_CAM_IOCTL_CTRL_COMMAND: + /* Coming from control thread, may need to wait for + * command status */ + CDBG("calling msm_control kernel msm_ioctl_control\n"); + mutex_lock(&ctrl_cmd_lock); + rc = msm_control(ctrl_pmsm, 1, argp); + mutex_unlock(&ctrl_cmd_lock); + break; + case MSM_CAM_IOCTL_CTRL_COMMAND_2: + /* Sends a message, returns immediately */ + rc = msm_control(ctrl_pmsm, 0, argp); + break; + case MSM_CAM_IOCTL_CTRL_CMD_DONE: + /* Config thread calls the control thread to notify it + * of the result of a MSM_CAM_IOCTL_CTRL_COMMAND. + */ + rc = msm_ctrl_cmd_done(ctrl_pmsm, argp); + break; + case MSM_CAM_IOCTL_GET_SENSOR_INFO: + rc = msm_get_sensor_info(pmsm->sync, argp); + break; + case MSM_CAM_IOCTL_GET_CAMERA_INFO: + rc = msm_get_camera_info(argp); + break; + default: + rc = msm_ioctl_common(pmsm, cmd, argp); + break; + } + + return rc; +} + +static int __msm_release(struct msm_sync *sync) +{ + struct msm_pmem_region *region; + struct hlist_node *hnode; + struct hlist_node *n; + + mutex_lock(&sync->lock); + if (sync->opencnt) + sync->opencnt--; + pr_info("%s, open count =%d\n", __func__, sync->opencnt); + if (!sync->opencnt) { + /* need to clean up system resource */ + pr_info("%s, release VFE\n", __func__); + if (sync->core_powered_on) { + if (sync->vfefn.vfe_release) + sync->vfefn.vfe_release(sync->pdev); + /*sensor release */ + pr_info("%s, release Sensor\n", __func__); + sync->sctrl.s_release(); + CDBG("%s, msm_camio_sensor_clk_off\n", __func__); + msm_camio_sensor_clk_off(sync->pdev); + if (sync->sfctrl.strobe_flash_release) { + CDBG("%s, strobe_flash_release\n", __func__); + sync->sfctrl.strobe_flash_release( + sync->sdata->strobe_flash_data, 1); + } + } + kfree(sync->cropinfo); + sync->cropinfo = NULL; + sync->croplen = 0; + CDBG("%s, free frame pmem region\n", __func__); + hlist_for_each_entry_safe(region, hnode, n, + &sync->pmem_frames, list) { + hlist_del(hnode); + put_pmem_file(region->file); + kfree(region); + } + CDBG("%s, free stats pmem region\n", __func__); + hlist_for_each_entry_safe(region, hnode, n, + &sync->pmem_stats, list) { + hlist_del(hnode); + put_pmem_file(region->file); + kfree(region); + } + msm_queue_drain(&sync->pict_q, list_pict); + msm_queue_drain(&sync->event_q, list_config); + + wake_unlock(&sync->wake_lock); + sync->apps_id = NULL; + sync->core_powered_on = 0; + } + mutex_unlock(&sync->lock); + + return 0; +} + +static int msm_release_config(struct inode *node, struct file *filep) +{ + int rc; + struct msm_cam_device *pmsm = filep->private_data; + pr_info("%s: %s\n", __func__, filep->f_path.dentry->d_name.name); + rc = __msm_release(pmsm->sync); + if (!rc) { + msm_queue_drain(&pmsm->sync->event_q, list_config); + atomic_set(&pmsm->opened, 0); + } + return rc; +} + +static int msm_release_control(struct inode *node, struct file *filep) +{ + int rc; + struct msm_control_device *ctrl_pmsm = filep->private_data; + struct msm_cam_device *pmsm = ctrl_pmsm->pmsm; + pr_info("%s: %s\n", __func__, filep->f_path.dentry->d_name.name); + g_v4l2_opencnt--; + mutex_lock(&pmsm->sync->lock); + if (pmsm->sync->core_powered_on && pmsm->sync->vfefn.vfe_stop) { + pr_info("%s, stop vfe if active\n", __func__); + pmsm->sync->vfefn.vfe_stop(); + } + mutex_unlock(&pmsm->sync->lock); + rc = __msm_release(pmsm->sync); + if (!rc) { + msm_queue_drain(&ctrl_pmsm->ctrl_q, list_control); + kfree(ctrl_pmsm); + } + return rc; +} + +static int msm_release_frame(struct inode *node, struct file *filep) +{ + int rc; + struct msm_cam_device *pmsm = filep->private_data; + pr_info("%s: %s\n", __func__, filep->f_path.dentry->d_name.name); + rc = __msm_release(pmsm->sync); + if (!rc) { + msm_queue_drain(&pmsm->sync->frame_q, list_frame); + atomic_set(&pmsm->opened, 0); + } + return rc; +} + + +static int msm_release_pic(struct inode *node, struct file *filep) +{ + int rc; + struct msm_cam_device *pmsm = filep->private_data; + CDBG("%s: %s\n", __func__, filep->f_path.dentry->d_name.name); + rc = __msm_release(pmsm->sync); + if (!rc) { + msm_queue_drain(&pmsm->sync->pict_q, list_pict); + atomic_set(&pmsm->opened, 0); + } + return rc; +} + +static int msm_unblock_poll_pic(struct msm_sync *sync) +{ + unsigned long flags; + CDBG("%s\n", __func__); + spin_lock_irqsave(&sync->pict_q.lock, flags); + sync->unblock_poll_pic_frame = 1; + wake_up(&sync->pict_q.wait); + spin_unlock_irqrestore(&sync->pict_q.lock, flags); + return 0; +} + +static int msm_unblock_poll_frame(struct msm_sync *sync) +{ + unsigned long flags; + CDBG("%s\n", __func__); + spin_lock_irqsave(&sync->frame_q.lock, flags); + sync->unblock_poll_frame = 1; + wake_up(&sync->frame_q.wait); + spin_unlock_irqrestore(&sync->frame_q.lock, flags); + return 0; +} + +static unsigned int __msm_poll_frame(struct msm_sync *sync, + struct file *filep, + struct poll_table_struct *pll_table) +{ + int rc = 0; + unsigned long flags; + + poll_wait(filep, &sync->frame_q.wait, pll_table); + + spin_lock_irqsave(&sync->frame_q.lock, flags); + if (!list_empty_careful(&sync->frame_q.list)) + /* frame ready */ + rc = POLLIN | POLLRDNORM; + if (sync->unblock_poll_frame) { + CDBG("%s: sync->unblock_poll_frame is true\n", __func__); + rc |= POLLPRI; + sync->unblock_poll_frame = 0; + } + spin_unlock_irqrestore(&sync->frame_q.lock, flags); + + return rc; +} + +static unsigned int __msm_poll_pic(struct msm_sync *sync, + struct file *filep, + struct poll_table_struct *pll_table) +{ + int rc = 0; + unsigned long flags; + + poll_wait(filep, &sync->pict_q.wait , pll_table); + spin_lock_irqsave(&sync->abort_pict_lock, flags); + if (sync->get_pic_abort == 1) { + /* TODO: need to pass an error case */ + sync->get_pic_abort = 0; + } + spin_unlock_irqrestore(&sync->abort_pict_lock, flags); + + spin_lock_irqsave(&sync->pict_q.lock, flags); + if (!list_empty_careful(&sync->pict_q.list)) + /* frame ready */ + rc = POLLIN | POLLRDNORM; + if (sync->unblock_poll_pic_frame) { + CDBG("%s: sync->unblock_poll_pic_frame is true\n", __func__); + rc |= POLLPRI; + sync->unblock_poll_pic_frame = 0; + } + spin_unlock_irqrestore(&sync->pict_q.lock, flags); + + return rc; +} + +static unsigned int msm_poll_frame(struct file *filep, + struct poll_table_struct *pll_table) +{ + struct msm_cam_device *pmsm = filep->private_data; + return __msm_poll_frame(pmsm->sync, filep, pll_table); +} + +static unsigned int msm_poll_pic(struct file *filep, + struct poll_table_struct *pll_table) +{ + struct msm_cam_device *pmsm = filep->private_data; + return __msm_poll_pic(pmsm->sync, filep, pll_table); +} + +static unsigned int __msm_poll_config(struct msm_sync *sync, + struct file *filep, + struct poll_table_struct *pll_table) +{ + int rc = 0; + unsigned long flags; + + poll_wait(filep, &sync->event_q.wait, pll_table); + + spin_lock_irqsave(&sync->event_q.lock, flags); + if (!list_empty_careful(&sync->event_q.list)) + /* event ready */ + rc = POLLIN | POLLRDNORM; + spin_unlock_irqrestore(&sync->event_q.lock, flags); + + return rc; +} + +static unsigned int msm_poll_config(struct file *filep, + struct poll_table_struct *pll_table) +{ + struct msm_cam_device *pmsm = filep->private_data; + return __msm_poll_config(pmsm->sync, filep, pll_table); +} + +/* + * This function executes in interrupt context. + */ + +static void *msm_vfe_sync_alloc(int size, + void *syncdata __attribute__((unused)), + gfp_t gfp) +{ + struct msm_queue_cmd *qcmd = + kzalloc(sizeof(struct msm_queue_cmd) + size, gfp); + if (qcmd) { + atomic_set(&qcmd->on_heap, 1); + return qcmd + 1; + } + return NULL; +} + +static void *msm_vpe_sync_alloc(int size, + void *syncdata __attribute__((unused)), + gfp_t gfp) +{ + struct msm_queue_cmd *qcmd = + kzalloc(sizeof(struct msm_queue_cmd) + size, gfp); + if (qcmd) { + atomic_set(&qcmd->on_heap, 1); + return qcmd + 1; + } + return NULL; +} + +static void msm_vfe_sync_free(void *ptr) +{ + if (ptr) { + struct msm_queue_cmd *qcmd = + (struct msm_queue_cmd *)ptr; + qcmd--; + if (atomic_read(&qcmd->on_heap)) + kfree(qcmd); + } +} + +static void msm_vpe_sync_free(void *ptr) +{ + if (ptr) { + struct msm_queue_cmd *qcmd = + (struct msm_queue_cmd *)ptr; + qcmd--; + if (atomic_read(&qcmd->on_heap)) + kfree(qcmd); + } +} + +/* + * This function executes in interrupt context. + */ + +static void msm_vfe_sync(struct msm_vfe_resp *vdata, + enum msm_queue qtype, void *syncdata, + gfp_t gfp) +{ + struct msm_queue_cmd *qcmd = NULL; + struct msm_sync *sync = (struct msm_sync *)syncdata; + unsigned long flags; + + if (!sync) { + pr_err("%s: no context in dsp callback.\n", __func__); + return; + } + + qcmd = ((struct msm_queue_cmd *)vdata) - 1; + qcmd->type = qtype; + qcmd->command = vdata; + + ktime_get_ts(&(qcmd->ts)); + + if (qtype != MSM_CAM_Q_VFE_MSG) + goto vfe_for_config; + + CDBG("%s: vdata->type %d\n", __func__, vdata->type); + + switch (vdata->type) { + case VFE_MSG_OUTPUT_P: + if (sync->pp_mask & PP_PREV) { + CDBG("%s: PP_PREV in progress: phy_y %x phy_cbcr %x\n", + __func__, + vdata->phy.y_phy, + vdata->phy.cbcr_phy); + spin_lock_irqsave(&pp_prev_spinlock, flags); + if (sync->pp_prev) + CDBG("%s: overwriting pp_prev!\n", + __func__); + CDBG("%s: sending preview to config\n", __func__); + sync->pp_prev = qcmd; + spin_unlock_irqrestore(&pp_prev_spinlock, flags); + sync->pp_frame_avail = 1; + if (atomic_read(&qcmd->on_heap)) + atomic_add(1, &qcmd->on_heap); + break; + } + CDBG("%s: msm_enqueue frame_q\n", __func__); + if (sync->stereocam_enabled) + CDBG("%s: Enqueue VFE_MSG_OUTPUT_P to event_q for " + "stereo processing\n", __func__); + else { + if (sync->frame_q.len <= 100 && + sync->event_q.len <= 100) { + if (atomic_read(&qcmd->on_heap)) + atomic_add(1, &qcmd->on_heap); + msm_enqueue(&sync->frame_q, &qcmd->list_frame); + } else { + pr_err("%s, Error Queue limit exceeded " + "f_q = %d, e_q = %d\n", __func__, + sync->frame_q.len, sync->event_q.len); + free_qcmd(qcmd); + return; + } + } + break; + + case VFE_MSG_OUTPUT_T: + if (sync->stereocam_enabled) { + spin_lock_irqsave(&pp_stereocam_spinlock, flags); + + /* if out1/2 is currently in progress, save the qcmd + and issue only ionce the 1st one completes the 3D + pipeline */ + if (STEREO_SNAP_BUFFER1_PROCESSING == + sync->stereo_state) { + sync->pp_stereocam2 = qcmd; + spin_unlock_irqrestore(&pp_stereocam_spinlock, + flags); + if (atomic_read(&qcmd->on_heap)) + atomic_add(1, &qcmd->on_heap); + CDBG("%s: snapshot stereo in progress\n", + __func__); + return; + } + + if (sync->pp_stereocam) + CDBG("%s: overwriting pp_stereocam!\n", + __func__); + + CDBG("%s: sending stereo frame to config\n", __func__); + sync->pp_stereocam = qcmd; + sync->stereo_state = + STEREO_SNAP_BUFFER1_PROCESSING; + + spin_unlock_irqrestore(&pp_stereocam_spinlock, flags); + + /* Increament on_heap by one because the same qcmd will + be used for VPE in msm_pp_release. */ + if (atomic_read(&qcmd->on_heap)) + atomic_add(1, &qcmd->on_heap); + CDBG("%s: Enqueue VFE_MSG_OUTPUT_T to event_q for " + "stereo processing.\n", __func__); + break; + } + if (sync->pp_mask & PP_SNAP) { + CDBG("%s: pp sending thumbnail to config\n", + __func__); + } else { + msm_enqueue(&sync->pict_q, &qcmd->list_pict); + return; + } + + case VFE_MSG_OUTPUT_S: + if (sync->stereocam_enabled && + sync->stereo_state != STEREO_RAW_SNAP_STARTED) { + spin_lock_irqsave(&pp_stereocam_spinlock, flags); + + /* if out1/2 is currently in progress, save the qcmd + and issue only once the 1st one completes the 3D + pipeline */ + if (STEREO_SNAP_BUFFER1_PROCESSING == + sync->stereo_state) { + sync->pp_stereocam2 = qcmd; + spin_unlock_irqrestore(&pp_stereocam_spinlock, + flags); + if (atomic_read(&qcmd->on_heap)) + atomic_add(1, &qcmd->on_heap); + CDBG("%s: snapshot stereo in progress\n", + __func__); + return; + } + if (sync->pp_stereocam) + CDBG("%s: overwriting pp_stereocam!\n", + __func__); + + CDBG("%s: sending stereo frame to config\n", __func__); + sync->pp_stereocam = qcmd; + sync->stereo_state = + STEREO_SNAP_BUFFER1_PROCESSING; + + spin_unlock_irqrestore(&pp_stereocam_spinlock, flags); + + /* Increament on_heap by one because the same qcmd will + be used for VPE in msm_pp_release. */ + if (atomic_read(&qcmd->on_heap)) + atomic_add(1, &qcmd->on_heap); + CDBG("%s: Enqueue VFE_MSG_OUTPUT_S to event_q for " + "stereo processing.\n", __func__); + break; + } + if (sync->pp_mask & PP_SNAP) { + CDBG("%s: pp sending main image to config\n", + __func__); + } else { + CDBG("%s: enqueue to picture queue\n", __func__); + msm_enqueue(&sync->pict_q, &qcmd->list_pict); + return; + } + break; + + case VFE_MSG_OUTPUT_V: + if (sync->stereocam_enabled) { + spin_lock_irqsave(&pp_stereocam_spinlock, flags); + + if (sync->pp_stereocam) + CDBG("%s: overwriting pp_stereocam!\n", + __func__); + + CDBG("%s: sending stereo frame to config\n", __func__); + sync->pp_stereocam = qcmd; + + spin_unlock_irqrestore(&pp_stereocam_spinlock, flags); + + /* Increament on_heap by one because the same qcmd will + be used for VPE in msm_pp_release. */ + if (atomic_read(&qcmd->on_heap)) + atomic_add(1, &qcmd->on_heap); + CDBG("%s: Enqueue VFE_MSG_OUTPUT_V to event_q for " + "stereo processing.\n", __func__); + break; + } + if (sync->vpefn.vpe_cfg_update) { + CDBG("dis_en = %d\n", *sync->vpefn.dis); + if (*(sync->vpefn.dis)) { + memset(&(vdata->vpe_bf), 0, + sizeof(vdata->vpe_bf)); + + if (sync->cropinfo != NULL) + vdata->vpe_bf.vpe_crop = + *(struct video_crop_t *)(sync->cropinfo); + + vdata->vpe_bf.y_phy = vdata->phy.y_phy; + vdata->vpe_bf.cbcr_phy = vdata->phy.cbcr_phy; + vdata->vpe_bf.ts = (qcmd->ts); + vdata->vpe_bf.frame_id = vdata->phy.frame_id; + qcmd->command = vdata; + msm_enqueue_vpe(&sync->vpe_q, + &qcmd->list_vpe_frame); + return; + } else if (sync->vpefn.vpe_cfg_update(sync->cropinfo)) { + CDBG("%s: msm_enqueue video frame to vpe time " + "= %ld\n", __func__, qcmd->ts.tv_nsec); + + sync->vpefn.send_frame_to_vpe( + vdata->phy.y_phy, + vdata->phy.cbcr_phy, + &(qcmd->ts), OUTPUT_TYPE_V); + + free_qcmd(qcmd); + return; + } else { + CDBG("%s: msm_enqueue video frame_q\n", + __func__); + if (sync->liveshot_enabled) { + CDBG("%s: msm_enqueue liveshot\n", + __func__); + vdata->phy.output_id |= OUTPUT_TYPE_L; + sync->liveshot_enabled = false; + } + if (sync->frame_q.len <= 100 && + sync->event_q.len <= 100) { + msm_enqueue(&sync->frame_q, + &qcmd->list_frame); + } else { + pr_err("%s, Error Queue limit exceeded\ + f_q = %d, e_q = %d\n", + __func__, sync->frame_q.len, + sync->event_q.len); + free_qcmd(qcmd); + } + + return; + } + } else { + CDBG("%s: msm_enqueue video frame_q\n", __func__); + if (sync->frame_q.len <= 100 && + sync->event_q.len <= 100) { + msm_enqueue(&sync->frame_q, &qcmd->list_frame); + } else { + pr_err("%s, Error Queue limit exceeded\ + f_q = %d, e_q = %d\n", + __func__, sync->frame_q.len, + sync->event_q.len); + free_qcmd(qcmd); + } + + return; + } + + case VFE_MSG_SNAPSHOT: + if (sync->pp_mask & (PP_SNAP | PP_RAW_SNAP)) { + CDBG("%s: PP_SNAP in progress: pp_mask %x\n", + __func__, sync->pp_mask); + } else { + if (atomic_read(&qcmd->on_heap)) + atomic_add(1, &qcmd->on_heap); + CDBG("%s: VFE_MSG_SNAPSHOT store\n", + __func__); + if (sync->stereocam_enabled && + sync->stereo_state != STEREO_RAW_SNAP_STARTED) { + sync->pp_stereosnap = qcmd; + return; + } + } + break; + + case VFE_MSG_COMMON: + CDBG("%s: qtype %d, comp stats, enqueue event_q.\n", + __func__, vdata->type); + break; + + case VFE_MSG_GENERAL: + CDBG("%s: qtype %d, general msg, enqueue event_q.\n", + __func__, vdata->type); + break; + + default: + CDBG("%s: qtype %d not handled\n", __func__, vdata->type); + /* fall through, send to config. */ + } + +vfe_for_config: + CDBG("%s: msm_enqueue event_q\n", __func__); + if (sync->frame_q.len <= 100 && sync->event_q.len <= 100) { + msm_enqueue(&sync->event_q, &qcmd->list_config); + } else { + pr_err("%s, Error Queue limit exceeded f_q = %d, e_q = %d\n", + __func__, sync->frame_q.len, sync->event_q.len); + free_qcmd(qcmd); + } + +} + +static void msm_vpe_sync(struct msm_vpe_resp *vdata, + enum msm_queue qtype, void *syncdata, void *ts, gfp_t gfp) +{ + struct msm_queue_cmd *qcmd = NULL; + unsigned long flags; + + struct msm_sync *sync = (struct msm_sync *)syncdata; + if (!sync) { + pr_err("%s: no context in dsp callback.\n", __func__); + return; + } + + qcmd = ((struct msm_queue_cmd *)vdata) - 1; + qcmd->type = qtype; + qcmd->command = vdata; + qcmd->ts = *((struct timespec *)ts); + + if (qtype != MSM_CAM_Q_VPE_MSG) { + pr_err("%s: Invalid qcmd type = %d.\n", __func__, qcmd->type); + free_qcmd(qcmd); + return; + } + + CDBG("%s: vdata->type %d\n", __func__, vdata->type); + switch (vdata->type) { + case VPE_MSG_OUTPUT_V: + if (sync->liveshot_enabled) { + CDBG("%s: msm_enqueue liveshot %d\n", __func__, + sync->liveshot_enabled); + vdata->phy.output_id |= OUTPUT_TYPE_L; + sync->liveshot_enabled = false; + } + if (sync->frame_q.len <= 100 && sync->event_q.len <= 100) { + CDBG("%s: enqueue to frame_q from VPE\n", __func__); + msm_enqueue(&sync->frame_q, &qcmd->list_frame); + } else { + pr_err("%s, Error Queue limit exceeded f_q = %d, " + "e_q = %d\n", __func__, sync->frame_q.len, + sync->event_q.len); + free_qcmd(qcmd); + } + return; + + case VPE_MSG_OUTPUT_ST_L: + CDBG("%s: enqueue left frame done msg to event_q from VPE\n", + __func__); + msm_enqueue(&sync->event_q, &qcmd->list_config); + return; + + case VPE_MSG_OUTPUT_ST_R: + spin_lock_irqsave(&pp_stereocam_spinlock, flags); + CDBG("%s: received VPE_MSG_OUTPUT_ST_R state %d\n", __func__, + sync->stereo_state); + + if (STEREO_SNAP_BUFFER1_PROCESSING == sync->stereo_state) { + msm_enqueue(&sync->pict_q, &qcmd->list_pict); + qcmd = sync->pp_stereocam2; + sync->pp_stereocam = sync->pp_stereocam2; + sync->pp_stereocam2 = NULL; + msm_enqueue(&sync->event_q, &qcmd->list_config); + sync->stereo_state = + STEREO_SNAP_BUFFER2_PROCESSING; + } else if (STEREO_SNAP_BUFFER2_PROCESSING == + sync->stereo_state) { + sync->stereo_state = STEREO_SNAP_IDLE; + /* Send snapshot DONE */ + msm_enqueue(&sync->pict_q, &qcmd->list_pict); + qcmd = sync->pp_stereosnap; + sync->pp_stereosnap = NULL; + CDBG("%s: send SNAPSHOT_DONE message\n", __func__); + msm_enqueue(&sync->event_q, &qcmd->list_config); + } else { + if (atomic_read(&qcmd->on_heap)) + atomic_add(1, &qcmd->on_heap); + msm_enqueue(&sync->event_q, &qcmd->list_config); + if (sync->stereo_state == STEREO_VIDEO_ACTIVE) { + CDBG("%s: st frame to frame_q from VPE\n", + __func__); + msm_enqueue(&sync->frame_q, &qcmd->list_frame); + } + } + spin_unlock_irqrestore(&pp_stereocam_spinlock, flags); + return; + + default: + pr_err("%s: qtype %d not handled\n", __func__, vdata->type); + } + pr_err("%s: Should not come here. Error.\n", __func__); +} + +static struct msm_vpe_callback msm_vpe_s = { + .vpe_resp = msm_vpe_sync, + .vpe_alloc = msm_vpe_sync_alloc, + .vpe_free = msm_vpe_sync_free, +}; + +static struct msm_vfe_callback msm_vfe_s = { + .vfe_resp = msm_vfe_sync, + .vfe_alloc = msm_vfe_sync_alloc, + .vfe_free = msm_vfe_sync_free, +}; + +static int __msm_open(struct msm_cam_device *pmsm, const char *const apps_id, + int is_controlnode) +{ + int rc = 0; + struct msm_sync *sync = pmsm->sync; + + mutex_lock(&sync->lock); + if (sync->apps_id && strcmp(sync->apps_id, apps_id) + && (!strcmp(MSM_APPS_ID_V4L2, apps_id))) { + pr_err("%s(%s): sensor %s is already opened for %s\n", + __func__, + apps_id, + sync->sdata->sensor_name, + sync->apps_id); + rc = -EBUSY; + goto msm_open_done; + } + + sync->apps_id = apps_id; + + if (!sync->core_powered_on && !is_controlnode) { + wake_lock(&sync->wake_lock); + + msm_camvfe_fn_init(&sync->vfefn, sync); + if (sync->vfefn.vfe_init) { + sync->pp_frame_avail = 0; + sync->get_pic_abort = 0; + rc = msm_camio_sensor_clk_on(sync->pdev); + if (rc < 0) { + pr_err("%s: setting sensor clocks failed: %d\n", + __func__, rc); + goto msm_open_err; + } + rc = sync->sctrl.s_init(sync->sdata); + if (rc < 0) { + pr_err("%s: sensor init failed: %d\n", + __func__, rc); + msm_camio_sensor_clk_off(sync->pdev); + goto msm_open_err; + } + rc = sync->vfefn.vfe_init(&msm_vfe_s, + sync->pdev); + if (rc < 0) { + pr_err("%s: vfe_init failed at %d\n", + __func__, rc); + sync->sctrl.s_release(); + msm_camio_sensor_clk_off(sync->pdev); + goto msm_open_err; + } + } else { + pr_err("%s: no sensor init func\n", __func__); + rc = -ENODEV; + goto msm_open_err; + } + msm_camvpe_fn_init(&sync->vpefn, sync); + + spin_lock_init(&sync->abort_pict_lock); + spin_lock_init(&pp_prev_spinlock); + spin_lock_init(&pp_stereocam_spinlock); + spin_lock_init(&st_frame_spinlock); + if (rc >= 0) { + msm_region_init(sync); + if (sync->vpefn.vpe_reg) + sync->vpefn.vpe_reg(&msm_vpe_s); + sync->unblock_poll_frame = 0; + sync->unblock_poll_pic_frame = 0; + } + sync->core_powered_on = 1; + } + sync->opencnt++; + +msm_open_done: + mutex_unlock(&sync->lock); + return rc; + +msm_open_err: + atomic_set(&pmsm->opened, 0); + mutex_unlock(&sync->lock); + return rc; +} + +static int msm_open_common(struct inode *inode, struct file *filep, + int once, int is_controlnode) +{ + int rc; + struct msm_cam_device *pmsm = + container_of(inode->i_cdev, struct msm_cam_device, cdev); + + CDBG("%s: open %s\n", __func__, filep->f_path.dentry->d_name.name); + + if (atomic_cmpxchg(&pmsm->opened, 0, 1) && once) { + pr_err("%s: %s is already opened.\n", + __func__, + filep->f_path.dentry->d_name.name); + return -EBUSY; + } + + rc = nonseekable_open(inode, filep); + if (rc < 0) { + pr_err("%s: nonseekable_open error %d\n", __func__, rc); + return rc; + } + + rc = __msm_open(pmsm, MSM_APPS_ID_PROP, is_controlnode); + if (rc < 0) + return rc; + filep->private_data = pmsm; + CDBG("%s: rc %d\n", __func__, rc); + return rc; +} + +static int msm_open(struct inode *inode, struct file *filep) +{ + return msm_open_common(inode, filep, 1, 0); +} + +static int msm_open_control(struct inode *inode, struct file *filep) +{ + int rc; + + struct msm_control_device *ctrl_pmsm = + kmalloc(sizeof(struct msm_control_device), GFP_KERNEL); + if (!ctrl_pmsm) + return -ENOMEM; + + rc = msm_open_common(inode, filep, 0, 1); + if (rc < 0) { + kfree(ctrl_pmsm); + return rc; + } + ctrl_pmsm->pmsm = filep->private_data; + filep->private_data = ctrl_pmsm; + + msm_queue_init(&ctrl_pmsm->ctrl_q, "control"); + + if (!g_v4l2_opencnt) + g_v4l2_control_device = ctrl_pmsm; + g_v4l2_opencnt++; + CDBG("%s: rc %d\n", __func__, rc); + return rc; +} + +static const struct file_operations msm_fops_config = { + .owner = THIS_MODULE, + .open = msm_open, + .unlocked_ioctl = msm_ioctl_config, + .release = msm_release_config, + .poll = msm_poll_config, +}; + +static const struct file_operations msm_fops_control = { + .owner = THIS_MODULE, + .open = msm_open_control, + .unlocked_ioctl = msm_ioctl_control, + .release = msm_release_control, +}; + +static const struct file_operations msm_fops_frame = { + .owner = THIS_MODULE, + .open = msm_open, + .unlocked_ioctl = msm_ioctl_frame, + .release = msm_release_frame, + .poll = msm_poll_frame, +}; + +static const struct file_operations msm_fops_pic = { + .owner = THIS_MODULE, + .open = msm_open, + .unlocked_ioctl = msm_ioctl_pic, + .release = msm_release_pic, + .poll = msm_poll_pic, +}; + +static int msm_setup_cdev(struct msm_cam_device *msm, + int node, + dev_t devno, + const char *suffix, + const struct file_operations *fops) +{ + int rc = -ENODEV; + + struct device *device = + device_create(msm_class, NULL, + devno, NULL, + "%s%d", suffix, node); + + if (IS_ERR(device)) { + rc = PTR_ERR(device); + pr_err("%s: error creating device: %d\n", __func__, rc); + return rc; + } + + cdev_init(&msm->cdev, fops); + msm->cdev.owner = THIS_MODULE; + + rc = cdev_add(&msm->cdev, devno, 1); + if (rc < 0) { + pr_err("%s: error adding cdev: %d\n", __func__, rc); + device_destroy(msm_class, devno); + return rc; + } + + return rc; +} + +static int msm_tear_down_cdev(struct msm_cam_device *msm, dev_t devno) +{ + cdev_del(&msm->cdev); + device_destroy(msm_class, devno); + return 0; +} + +static int msm_sync_init(struct msm_sync *sync, + struct platform_device *pdev, + int (*sensor_probe)(const struct msm_camera_sensor_info *, + struct msm_sensor_ctrl *)) +{ + int rc = 0; + struct msm_sensor_ctrl sctrl; + sync->sdata = pdev->dev.platform_data; + + msm_queue_init(&sync->event_q, "event"); + msm_queue_init(&sync->frame_q, "frame"); + msm_queue_init(&sync->pict_q, "pict"); + msm_queue_init(&sync->vpe_q, "vpe"); + + wake_lock_init(&sync->wake_lock, WAKE_LOCK_IDLE, "msm_camera"); + + rc = msm_camio_probe_on(pdev); + if (rc < 0) { + wake_lock_destroy(&sync->wake_lock); + return rc; + } + rc = sensor_probe(sync->sdata, &sctrl); + if (rc >= 0) { + sync->pdev = pdev; + sync->sctrl = sctrl; + } + msm_camio_probe_off(pdev); + if (rc < 0) { + pr_err("%s: failed to initialize %s\n", + __func__, + sync->sdata->sensor_name); + wake_lock_destroy(&sync->wake_lock); + return rc; + } + + sync->opencnt = 0; + sync->core_powered_on = 0; + sync->ignore_qcmd = false; + sync->ignore_qcmd_type = -1; + mutex_init(&sync->lock); + if (sync->sdata->strobe_flash_data) { + sync->sdata->strobe_flash_data->state = 0; + spin_lock_init(&sync->sdata->strobe_flash_data->spin_lock); + } + CDBG("%s: initialized %s\n", __func__, sync->sdata->sensor_name); + return rc; +} + +static int msm_sync_destroy(struct msm_sync *sync) +{ + wake_lock_destroy(&sync->wake_lock); + return 0; +} + +static int msm_device_init(struct msm_cam_device *pmsm, + struct msm_sync *sync, + int node) +{ + int dev_num = 4 * node; + int rc = msm_setup_cdev(pmsm, node, + MKDEV(MAJOR(msm_devno), dev_num), + "control", &msm_fops_control); + if (rc < 0) { + pr_err("%s: error creating control node: %d\n", __func__, rc); + return rc; + } + + rc = msm_setup_cdev(pmsm + 1, node, + MKDEV(MAJOR(msm_devno), dev_num + 1), + "config", &msm_fops_config); + if (rc < 0) { + pr_err("%s: error creating config node: %d\n", __func__, rc); + msm_tear_down_cdev(pmsm, MKDEV(MAJOR(msm_devno), + dev_num)); + return rc; + } + + rc = msm_setup_cdev(pmsm + 2, node, + MKDEV(MAJOR(msm_devno), dev_num + 2), + "frame", &msm_fops_frame); + if (rc < 0) { + pr_err("%s: error creating frame node: %d\n", __func__, rc); + msm_tear_down_cdev(pmsm, + MKDEV(MAJOR(msm_devno), dev_num)); + msm_tear_down_cdev(pmsm + 1, + MKDEV(MAJOR(msm_devno), dev_num + 1)); + return rc; + } + + rc = msm_setup_cdev(pmsm + 3, node, + MKDEV(MAJOR(msm_devno), dev_num + 3), + "pic", &msm_fops_pic); + if (rc < 0) { + pr_err("%s: error creating pic node: %d\n", __func__, rc); + msm_tear_down_cdev(pmsm, + MKDEV(MAJOR(msm_devno), dev_num)); + msm_tear_down_cdev(pmsm + 1, + MKDEV(MAJOR(msm_devno), dev_num + 1)); + msm_tear_down_cdev(pmsm + 2, + MKDEV(MAJOR(msm_devno), dev_num + 2)); + return rc; + } + + + atomic_set(&pmsm[0].opened, 0); + atomic_set(&pmsm[1].opened, 0); + atomic_set(&pmsm[2].opened, 0); + atomic_set(&pmsm[3].opened, 0); + + pmsm[0].sync = sync; + pmsm[1].sync = sync; + pmsm[2].sync = sync; + pmsm[3].sync = sync; + + return rc; +} + +int msm_camera_drv_start(struct platform_device *dev, + int (*sensor_probe)(const struct msm_camera_sensor_info *, + struct msm_sensor_ctrl *)) +{ + struct msm_cam_device *pmsm = NULL; + struct msm_sync *sync; + int rc = -ENODEV; + + if (camera_node >= MSM_MAX_CAMERA_SENSORS) { + pr_err("%s: too many camera sensors\n", __func__); + return rc; + } + + if (!msm_class) { + /* There are three device nodes per sensor */ + rc = alloc_chrdev_region(&msm_devno, 0, + 4 * MSM_MAX_CAMERA_SENSORS, + "msm_camera"); + if (rc < 0) { + pr_err("%s: failed to allocate chrdev: %d\n", __func__, + rc); + return rc; + } + + msm_class = class_create(THIS_MODULE, "msm_camera"); + if (IS_ERR(msm_class)) { + rc = PTR_ERR(msm_class); + pr_err("%s: create device class failed: %d\n", + __func__, rc); + return rc; + } + } + + pmsm = kzalloc(sizeof(struct msm_cam_device) * 4 + + sizeof(struct msm_sync), GFP_ATOMIC); + if (!pmsm) + return -ENOMEM; + sync = (struct msm_sync *)(pmsm + 4); + + rc = msm_sync_init(sync, dev, sensor_probe); + if (rc < 0) { + kfree(pmsm); + return rc; + } + + CDBG("%s: setting camera node %d\n", __func__, camera_node); + rc = msm_device_init(pmsm, sync, camera_node); + if (rc < 0) { + msm_sync_destroy(sync); + kfree(pmsm); + return rc; + } + + camera_type[camera_node] = sync->sctrl.s_camera_type; + sensor_mount_angle[camera_node] = sync->sctrl.s_mount_angle; + camera_node++; + + list_add(&sync->list, &msm_sensors); + return rc; +} +EXPORT_SYMBOL(msm_camera_drv_start); diff --git a/drivers/media/video/msm/msm_gemini_common.h b/drivers/media/video/msm/msm_gemini_common.h new file mode 100644 index 00000000000..0ddedc5013f --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_common.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MSM_GEMINI_COMMON_H +#define MSM_GEMINI_COMMON_H + +#define MSM_GEMINI_DEBUG +#ifdef MSM_GEMINI_DEBUG +#define GMN_DBG(fmt, args...) pr_debug(fmt, ##args) +#else +#define GMN_DBG(fmt, args...) do { } while (0) +#endif + +#define GMN_PR_ERR pr_err + +enum GEMINI_MODE { + GEMINI_MODE_DISABLE, + GEMINI_MODE_OFFLINE, + GEMINI_MODE_REALTIME, + GEMINI_MODE_REALTIME_ROTATION +}; + +enum GEMINI_ROTATION { + GEMINI_ROTATION_0, + GEMINI_ROTATION_90, + GEMINI_ROTATION_180, + GEMINI_ROTATION_270 +}; + +#endif /* MSM_GEMINI_COMMON_H */ diff --git a/drivers/media/video/msm/msm_gemini_core.c b/drivers/media/video/msm/msm_gemini_core.c new file mode 100644 index 00000000000..58c2e7c1b8a --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_core.c @@ -0,0 +1,249 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "msm_gemini_hw.h" +#include "msm_gemini_core.h" +#include "msm_gemini_platform.h" +#include "msm_gemini_common.h" + +static struct msm_gemini_hw_pingpong fe_pingpong_buf; +static struct msm_gemini_hw_pingpong we_pingpong_buf; +static int we_pingpong_index; +static int reset_done_ack; +static spinlock_t reset_lock; +static wait_queue_head_t reset_wait; + +int msm_gemini_core_reset(uint8_t op_mode, void *base, int size) +{ + unsigned long flags; + int rc = 0; + int tm = 500; /*500ms*/ + memset(&fe_pingpong_buf, 0, sizeof(fe_pingpong_buf)); + fe_pingpong_buf.is_fe = 1; + we_pingpong_index = 0; + memset(&we_pingpong_buf, 0, sizeof(we_pingpong_buf)); + spin_lock_irqsave(&reset_lock, flags); + reset_done_ack = 0; + msm_gemini_hw_reset(base, size); + spin_unlock_irqrestore(&reset_lock, flags); + rc = wait_event_interruptible_timeout( + reset_wait, + reset_done_ack, + msecs_to_jiffies(tm)); + + if (!reset_done_ack) { + GMN_DBG("%s: reset ACK failed %d", __func__, rc); + return -EBUSY; + } + + GMN_DBG("%s: reset_done_ack rc %d", __func__, rc); + spin_lock_irqsave(&reset_lock, flags); + reset_done_ack = 0; + spin_unlock_irqrestore(&reset_lock, flags); + + if (op_mode == MSM_GEMINI_MODE_REALTIME_ENCODE) { + /* Nothing needed for fe buffer cfg, config we only */ + msm_gemini_hw_we_buffer_cfg(1); + } else { + /* Nothing needed for fe buffer cfg, config we only */ + msm_gemini_hw_we_buffer_cfg(0); + } + + /* @todo wait for reset done irq */ + + return 0; +} + +void msm_gemini_core_release(int release_buf) +{ + int i = 0; + for (i = 0; i < 2; i++) { + if (we_pingpong_buf.buf_status[i] && release_buf) + msm_gemini_platform_p2v(we_pingpong_buf.buf[i].file); + we_pingpong_buf.buf_status[i] = 0; + } +} + +void msm_gemini_core_init(void) +{ + init_waitqueue_head(&reset_wait); + spin_lock_init(&reset_lock); +} + +int msm_gemini_core_fe_start(void) +{ + msm_gemini_hw_fe_start(); + return 0; +} + +/* fetch engine */ +int msm_gemini_core_fe_buf_update(struct msm_gemini_core_buf *buf) +{ + GMN_DBG("%s:%d] 0x%08x %d 0x%08x %d\n", __func__, __LINE__, + (int) buf->y_buffer_addr, buf->y_len, + (int) buf->cbcr_buffer_addr, buf->cbcr_len); + return msm_gemini_hw_pingpong_update(&fe_pingpong_buf, buf); +} + +void *msm_gemini_core_fe_pingpong_irq(int gemini_irq_status, void *context) +{ + return msm_gemini_hw_pingpong_irq(&fe_pingpong_buf); +} + +/* write engine */ +int msm_gemini_core_we_buf_update(struct msm_gemini_core_buf *buf) +{ + int rc; + GMN_DBG("%s:%d] 0x%08x 0x%08x %d\n", __func__, __LINE__, + (int) buf->y_buffer_addr, (int) buf->cbcr_buffer_addr, + buf->y_len); + we_pingpong_buf.buf_status[we_pingpong_index] = 0; + we_pingpong_index = (we_pingpong_index + 1)%2; + rc = msm_gemini_hw_pingpong_update(&we_pingpong_buf, buf); + return 0; +} + +int msm_gemini_core_we_buf_reset(struct msm_gemini_hw_buf *buf) +{ + int i = 0; + for (i = 0; i < 2; i++) { + if (we_pingpong_buf.buf[i].y_buffer_addr + == buf->y_buffer_addr) + we_pingpong_buf.buf_status[i] = 0; + } + return 0; +} + +void *msm_gemini_core_we_pingpong_irq(int gemini_irq_status, void *context) +{ + GMN_DBG("%s:%d]\n", __func__, __LINE__); + + return msm_gemini_hw_pingpong_irq(&we_pingpong_buf); +} + +void *msm_gemini_core_framedone_irq(int gemini_irq_status, void *context) +{ + struct msm_gemini_hw_buf *buf_p; + + GMN_DBG("%s:%d]\n", __func__, __LINE__); + + buf_p = msm_gemini_hw_pingpong_active_buffer(&we_pingpong_buf); + if (buf_p) { + buf_p->framedone_len = msm_gemini_hw_encode_output_size(); + GMN_DBG("%s:%d] framedone_len %d\n", __func__, __LINE__, + buf_p->framedone_len); + } + + return buf_p; +} + +void *msm_gemini_core_reset_ack_irq(int gemini_irq_status, void *context) +{ + /* @todo return the status back to msm_gemini_core_reset */ + GMN_DBG("%s:%d]\n", __func__, __LINE__); + return NULL; +} + +void *msm_gemini_core_err_irq(int gemini_irq_status, void *context) +{ + GMN_PR_ERR("%s:%d]\n", __func__, gemini_irq_status); + return NULL; +} + +static int (*msm_gemini_irq_handler) (int, void *, void *); + +irqreturn_t msm_gemini_core_irq(int irq_num, void *context) +{ + void *data = NULL; + unsigned long flags; + int gemini_irq_status; + + GMN_DBG("%s:%d] irq_num = %d\n", __func__, __LINE__, irq_num); + + spin_lock_irqsave(&reset_lock, flags); + reset_done_ack = 1; + spin_unlock_irqrestore(&reset_lock, flags); + gemini_irq_status = msm_gemini_hw_irq_get_status(); + + GMN_DBG("%s:%d] gemini_irq_status = %0x\n", __func__, __LINE__, + gemini_irq_status); + + /*For reset and framedone IRQs, clear all bits*/ + if (gemini_irq_status & 0x400) { + wake_up(&reset_wait); + msm_gemini_hw_irq_clear(HWIO_JPEG_IRQ_CLEAR_RMSK, + JPEG_IRQ_CLEAR_ALL); + } else if (gemini_irq_status & 0x1) { + msm_gemini_hw_irq_clear(HWIO_JPEG_IRQ_CLEAR_RMSK, + JPEG_IRQ_CLEAR_ALL); + } else { + msm_gemini_hw_irq_clear(HWIO_JPEG_IRQ_CLEAR_RMSK, + gemini_irq_status); + } + + if (msm_gemini_hw_irq_is_frame_done(gemini_irq_status)) { + data = msm_gemini_core_framedone_irq(gemini_irq_status, + context); + if (msm_gemini_irq_handler) + msm_gemini_irq_handler( + MSM_GEMINI_HW_MASK_COMP_FRAMEDONE, + context, data); + } + + if (msm_gemini_hw_irq_is_fe_pingpong(gemini_irq_status)) { + data = msm_gemini_core_fe_pingpong_irq(gemini_irq_status, + context); + if (msm_gemini_irq_handler) + msm_gemini_irq_handler(MSM_GEMINI_HW_MASK_COMP_FE, + context, data); + } + + if (msm_gemini_hw_irq_is_we_pingpong(gemini_irq_status) && + !msm_gemini_hw_irq_is_frame_done(gemini_irq_status)) { + data = msm_gemini_core_we_pingpong_irq(gemini_irq_status, + context); + if (msm_gemini_irq_handler) + msm_gemini_irq_handler(MSM_GEMINI_HW_MASK_COMP_WE, + context, data); + } + + if (msm_gemini_hw_irq_is_reset_ack(gemini_irq_status)) { + data = msm_gemini_core_reset_ack_irq(gemini_irq_status, + context); + if (msm_gemini_irq_handler) + msm_gemini_irq_handler( + MSM_GEMINI_HW_MASK_COMP_RESET_ACK, + context, data); + } + + /* Unexpected/unintended HW interrupt */ + if (msm_gemini_hw_irq_is_err(gemini_irq_status)) { + data = msm_gemini_core_err_irq(gemini_irq_status, context); + if (msm_gemini_irq_handler) + msm_gemini_irq_handler(MSM_GEMINI_HW_MASK_COMP_ERR, + context, data); + } + + return IRQ_HANDLED; +} + +void msm_gemini_core_irq_install(int (*irq_handler) (int, void *, void *)) +{ + msm_gemini_irq_handler = irq_handler; +} + +void msm_gemini_core_irq_remove(void) +{ + msm_gemini_irq_handler = NULL; +} diff --git a/drivers/media/video/msm/msm_gemini_core.h b/drivers/media/video/msm/msm_gemini_core.h new file mode 100644 index 00000000000..f240505fc2d --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_core.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MSM_GEMINI_CORE_H +#define MSM_GEMINI_CORE_H + +#include +#include "msm_gemini_hw.h" + +#define msm_gemini_core_buf msm_gemini_hw_buf + +irqreturn_t msm_gemini_core_irq(int irq_num, void *context); + +void msm_gemini_core_irq_install(int (*irq_handler) (int, void *, void *)); +void msm_gemini_core_irq_remove(void); + +int msm_gemini_core_fe_buf_update(struct msm_gemini_core_buf *buf); +int msm_gemini_core_we_buf_update(struct msm_gemini_core_buf *buf); +int msm_gemini_core_we_buf_reset(struct msm_gemini_hw_buf *buf); + +int msm_gemini_core_reset(uint8_t op_mode, void *base, int size); +int msm_gemini_core_fe_start(void); + +void msm_gemini_core_release(int); +void msm_gemini_core_init(void); +#endif /* MSM_GEMINI_CORE_H */ diff --git a/drivers/media/video/msm/msm_gemini_dev.c b/drivers/media/video/msm/msm_gemini_dev.c new file mode 100644 index 00000000000..1156bb0a26c --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_dev.c @@ -0,0 +1,208 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "msm_gemini_sync.h" +#include "msm_gemini_common.h" + +#define MSM_GEMINI_NAME "gemini" + +static int msm_gemini_open(struct inode *inode, struct file *filp) +{ + int rc; + + struct msm_gemini_device *pgmn_dev = container_of(inode->i_cdev, + struct msm_gemini_device, cdev); + filp->private_data = pgmn_dev; + + GMN_DBG("%s:%d]\n", __func__, __LINE__); + + rc = __msm_gemini_open(pgmn_dev); + + GMN_DBG(KERN_INFO "%s:%d] %s open_count = %d\n", __func__, __LINE__, + filp->f_path.dentry->d_name.name, pgmn_dev->open_count); + + return rc; +} + +static int msm_gemini_release(struct inode *inode, struct file *filp) +{ + int rc; + + struct msm_gemini_device *pgmn_dev = filp->private_data; + + GMN_DBG(KERN_INFO "%s:%d]\n", __func__, __LINE__); + + rc = __msm_gemini_release(pgmn_dev); + + GMN_DBG(KERN_INFO "%s:%d] %s open_count = %d\n", __func__, __LINE__, + filp->f_path.dentry->d_name.name, pgmn_dev->open_count); + return rc; +} + +static long msm_gemini_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + int rc; + struct msm_gemini_device *pgmn_dev = filp->private_data; + + GMN_DBG(KERN_INFO "%s:%d] cmd = %d\n", __func__, __LINE__, + _IOC_NR(cmd)); + + rc = __msm_gemini_ioctl(pgmn_dev, cmd, arg); + + GMN_DBG("%s:%d]\n", __func__, __LINE__); + return rc; +} + +static const struct file_operations msm_gemini_fops = { + .owner = THIS_MODULE, + .open = msm_gemini_open, + .release = msm_gemini_release, + .unlocked_ioctl = msm_gemini_ioctl, +}; + +static struct class *msm_gemini_class; +static dev_t msm_gemini_devno; +static struct msm_gemini_device *msm_gemini_device_p; + +static int msm_gemini_init(struct platform_device *pdev) +{ + int rc = -1; + struct device *dev; + + GMN_DBG("%s:%d]\n", __func__, __LINE__); + + msm_gemini_device_p = __msm_gemini_init(pdev); + if (msm_gemini_device_p == NULL) { + GMN_PR_ERR("%s: initialization failed\n", __func__); + goto fail; + } + + rc = alloc_chrdev_region(&msm_gemini_devno, 0, 1, MSM_GEMINI_NAME); + if (rc < 0) { + GMN_PR_ERR("%s: failed to allocate chrdev\n", __func__); + goto fail_1; + } + + if (!msm_gemini_class) { + msm_gemini_class = class_create(THIS_MODULE, MSM_GEMINI_NAME); + if (IS_ERR(msm_gemini_class)) { + rc = PTR_ERR(msm_gemini_class); + GMN_PR_ERR("%s: create device class failed\n", + __func__); + goto fail_2; + } + } + + dev = device_create(msm_gemini_class, NULL, + MKDEV(MAJOR(msm_gemini_devno), MINOR(msm_gemini_devno)), NULL, + "%s%d", MSM_GEMINI_NAME, 0); + + if (IS_ERR(dev)) { + GMN_PR_ERR("%s: error creating device\n", __func__); + rc = -ENODEV; + goto fail_3; + } + + cdev_init(&msm_gemini_device_p->cdev, &msm_gemini_fops); + msm_gemini_device_p->cdev.owner = THIS_MODULE; + msm_gemini_device_p->cdev.ops = + (const struct file_operations *) &msm_gemini_fops; + rc = cdev_add(&msm_gemini_device_p->cdev, msm_gemini_devno, 1); + if (rc < 0) { + GMN_PR_ERR("%s: error adding cdev\n", __func__); + rc = -ENODEV; + goto fail_4; + } + + GMN_DBG("%s %s: success\n", __func__, MSM_GEMINI_NAME); + + return rc; + +fail_4: + device_destroy(msm_gemini_class, msm_gemini_devno); + +fail_3: + class_destroy(msm_gemini_class); + +fail_2: + unregister_chrdev_region(msm_gemini_devno, 1); + +fail_1: + __msm_gemini_exit(msm_gemini_device_p); + +fail: + return rc; +} + +static void msm_gemini_exit(void) +{ + cdev_del(&msm_gemini_device_p->cdev); + device_destroy(msm_gemini_class, msm_gemini_devno); + class_destroy(msm_gemini_class); + unregister_chrdev_region(msm_gemini_devno, 1); + + __msm_gemini_exit(msm_gemini_device_p); +} + +static int __msm_gemini_probe(struct platform_device *pdev) +{ + int rc; + rc = msm_gemini_init(pdev); + return rc; +} + +static int __msm_gemini_remove(struct platform_device *pdev) +{ + msm_gemini_exit(); + return 0; +} + +static struct platform_driver msm_gemini_driver = { + .probe = __msm_gemini_probe, + .remove = __msm_gemini_remove, + .driver = { + .name = "msm_gemini", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_gemini_driver_init(void) +{ + int rc; + rc = platform_driver_register(&msm_gemini_driver); + return rc; +} + +static void __exit msm_gemini_driver_exit(void) +{ + platform_driver_unregister(&msm_gemini_driver); +} + +MODULE_DESCRIPTION("msm gemini jpeg driver"); +MODULE_VERSION("msm gemini 0.1"); + +module_init(msm_gemini_driver_init); +module_exit(msm_gemini_driver_exit); + diff --git a/drivers/media/video/msm/msm_gemini_hw.c b/drivers/media/video/msm/msm_gemini_hw.c new file mode 100644 index 00000000000..ba8f353e58f --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_hw.c @@ -0,0 +1,525 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "msm_gemini_hw.h" +#include "msm_gemini_common.h" + +#include + +static void *gemini_region_base; +static uint32_t gemini_region_size; + +int msm_gemini_hw_pingpong_update(struct msm_gemini_hw_pingpong *pingpong_hw, + struct msm_gemini_hw_buf *buf) +{ + int buf_free_index = -1; + + if (!pingpong_hw->buf_status[0]) { + buf_free_index = 0; + } else if (!pingpong_hw->buf_status[1]) { + buf_free_index = 1; + } else { + GMN_PR_ERR("%s:%d: pingpong buffer busy\n", __func__, __LINE__); + return -1; + } + + pingpong_hw->buf[buf_free_index] = *buf; + pingpong_hw->buf_status[buf_free_index] = 1; + + if (pingpong_hw->is_fe) { + /* it is fe */ + msm_gemini_hw_fe_buffer_update( + &pingpong_hw->buf[buf_free_index], buf_free_index); + } else { + /* it is we */ + msm_gemini_hw_we_buffer_update( + &pingpong_hw->buf[buf_free_index], buf_free_index); + } + return 0; +} + +void *msm_gemini_hw_pingpong_irq(struct msm_gemini_hw_pingpong *pingpong_hw) +{ + struct msm_gemini_hw_buf *buf_p = NULL; + + if (pingpong_hw->buf_status[pingpong_hw->buf_active_index]) { + buf_p = &pingpong_hw->buf[pingpong_hw->buf_active_index]; + pingpong_hw->buf_status[pingpong_hw->buf_active_index] = 0; + } + + pingpong_hw->buf_active_index = !pingpong_hw->buf_active_index; + + return (void *) buf_p; +} + +void *msm_gemini_hw_pingpong_active_buffer( + struct msm_gemini_hw_pingpong *pingpong_hw) +{ + struct msm_gemini_hw_buf *buf_p = NULL; + + if (pingpong_hw->buf_status[pingpong_hw->buf_active_index]) + buf_p = &pingpong_hw->buf[pingpong_hw->buf_active_index]; + + return (void *) buf_p; +} + +struct msm_gemini_hw_cmd hw_cmd_irq_get_status[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_READ, 1, HWIO_JPEG_IRQ_STATUS_ADDR, + HWIO_JPEG_IRQ_STATUS_RMSK, {0} }, +}; + +int msm_gemini_hw_irq_get_status(void) +{ + uint32_t n_irq_status = 0; + rmb(); + n_irq_status = msm_gemini_hw_read(&hw_cmd_irq_get_status[0]); + rmb(); + return n_irq_status; +} + +struct msm_gemini_hw_cmd hw_cmd_encode_output_size[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_READ, 1, + HWIO_JPEG_STATUS_ENCODE_OUTPUT_SIZE_ADDR, + HWIO_JPEG_STATUS_ENCODE_OUTPUT_SIZE_RMSK, {0} }, +}; + +long msm_gemini_hw_encode_output_size(void) +{ + uint32_t encode_output_size = 0; + + encode_output_size = msm_gemini_hw_read(&hw_cmd_encode_output_size[0]); + + return encode_output_size; +} + +struct msm_gemini_hw_cmd hw_cmd_irq_clear[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_IRQ_CLEAR_ADDR, + HWIO_JPEG_IRQ_CLEAR_RMSK, {JPEG_IRQ_CLEAR_ALL} }, +}; + +void msm_gemini_hw_irq_clear(uint32_t mask, uint32_t data) +{ + GMN_DBG("%s:%d] mask %0x data %0x", __func__, __LINE__, mask, data); + hw_cmd_irq_clear[0].mask = mask; + hw_cmd_irq_clear[0].data = data; + msm_gemini_hw_write(&hw_cmd_irq_clear[0]); +} + +struct msm_gemini_hw_cmd hw_cmd_fe_ping_update[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_FE_BUFFER_CFG_ADDR, + HWIO_JPEG_FE_BUFFER_CFG_RMSK, {0} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_FE_Y_PING_ADDR_ADDR, + HWIO_JPEG_FE_Y_PING_ADDR_RMSK, {0} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_FE_CBCR_PING_ADDR_ADDR, + HWIO_JPEG_FE_CBCR_PING_ADDR_RMSK, {0} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_FE_CMD_ADDR, + HWIO_JPEG_FE_CMD_RMSK, {JPEG_FE_CMD_BUFFERRELOAD} }, +}; + +struct msm_gemini_hw_cmd hw_cmd_fe_pong_update[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_FE_BUFFER_CFG_ADDR, + HWIO_JPEG_FE_BUFFER_CFG_RMSK, {0} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_FE_Y_PONG_ADDR_ADDR, + HWIO_JPEG_FE_Y_PONG_ADDR_RMSK, {0} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_FE_CBCR_PONG_ADDR_ADDR, + HWIO_JPEG_FE_CBCR_PONG_ADDR_RMSK, {0} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_FE_CMD_ADDR, + HWIO_JPEG_FE_CMD_RMSK, {JPEG_FE_CMD_BUFFERRELOAD} }, +}; + +void msm_gemini_hw_fe_buffer_update(struct msm_gemini_hw_buf *p_input, + uint8_t pingpong_index) +{ + uint32_t n_reg_val = 0; + + struct msm_gemini_hw_cmd *hw_cmd_p; + + if (pingpong_index == 0) { + hw_cmd_p = &hw_cmd_fe_ping_update[0]; + n_reg_val = ((((p_input->num_of_mcu_rows - 1) << + HWIO_JPEG_FE_BUFFER_CFG_CBCR_MCU_ROWS_SHFT) & + HWIO_JPEG_FE_BUFFER_CFG_CBCR_MCU_ROWS_BMSK) | + (((p_input->num_of_mcu_rows - 1) << + HWIO_JPEG_FE_BUFFER_CFG_Y_MCU_ROWS_SHFT) & + HWIO_JPEG_FE_BUFFER_CFG_Y_MCU_ROWS_BMSK)); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + + n_reg_val = ((p_input->y_buffer_addr << + HWIO_JPEG_FE_Y_PING_ADDR_FE_Y_PING_START_ADDR_SHFT) & + HWIO_JPEG_FE_Y_PING_ADDR_FE_Y_PING_START_ADDR_BMSK); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + + n_reg_val = ((p_input->cbcr_buffer_addr<< + HWIO_JPEG_FE_CBCR_PING_ADDR_FE_CBCR_PING_START_ADDR_SHFT) & + HWIO_JPEG_FE_CBCR_PING_ADDR_FE_CBCR_PING_START_ADDR_BMSK); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + + msm_gemini_hw_write(hw_cmd_p); + } else if (pingpong_index == 1) { + hw_cmd_p = &hw_cmd_fe_pong_update[0]; + n_reg_val = ((((p_input->num_of_mcu_rows - 1) << + HWIO_JPEG_FE_BUFFER_CFG_CBCR_MCU_ROWS_SHFT) & + HWIO_JPEG_FE_BUFFER_CFG_CBCR_MCU_ROWS_BMSK) | + (((p_input->num_of_mcu_rows - 1) << + HWIO_JPEG_FE_BUFFER_CFG_Y_MCU_ROWS_SHFT) & + HWIO_JPEG_FE_BUFFER_CFG_Y_MCU_ROWS_BMSK)); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + + n_reg_val = ((p_input->y_buffer_addr << + HWIO_JPEG_FE_Y_PONG_ADDR_FE_Y_PONG_START_ADDR_SHFT) & + HWIO_JPEG_FE_Y_PONG_ADDR_FE_Y_PONG_START_ADDR_BMSK); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + + n_reg_val = ((p_input->cbcr_buffer_addr<< + HWIO_JPEG_FE_CBCR_PONG_ADDR_FE_CBCR_PONG_START_ADDR_SHFT) & + HWIO_JPEG_FE_CBCR_PONG_ADDR_FE_CBCR_PONG_START_ADDR_BMSK); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + + msm_gemini_hw_write(hw_cmd_p); + } else { + /* shall not get to here */ + } + + return; +} + +struct msm_gemini_hw_cmd hw_cmd_fe_start[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_FE_CMD_ADDR, + HWIO_JPEG_FE_CMD_RMSK, {JPEG_OFFLINE_CMD_START} }, +}; + +void msm_gemini_hw_fe_start(void) +{ + msm_gemini_hw_write(&hw_cmd_fe_start[0]); + + return; +} + +struct msm_gemini_hw_cmd hw_cmd_we_buffer_cfg[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_WE_Y_THRESHOLD_ADDR, + HWIO_JPEG_WE_Y_THRESHOLD_RMSK, {0} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_WE_Y_UB_CFG_ADDR, + HWIO_JPEG_WE_Y_UB_CFG_RMSK, {JPEG_WE_YUB_ENCODE} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_WE_CBCR_THRESHOLD_ADDR, + HWIO_JPEG_WE_CBCR_THRESHOLD_RMSK, {0} }, +}; + +/* first dimension is WE_ASSERT_STALL_TH and WE_DEASSERT_STALL_TH + second dimension is for offline and real-time settings + */ +static const uint32_t GEMINI_WE_Y_THRESHOLD[2][2] = { + { 0x00000190, 0x000001ff }, + { 0x0000016a, 0x000001ff } +}; + +/* first dimension is WE_ASSERT_STALL_TH and WE_DEASSERT_STALL_TH + second dimension is for offline and real-time settings + */ +static const uint32_t GEMINI_WE_CBCR_THRESHOLD[2][2] = { + { 0x00000190, 0x000001ff }, + { 0x0000016a, 0x000001ff } +}; + +void msm_gemini_hw_we_buffer_cfg(uint8_t is_realtime) +{ + uint32_t n_reg_val = 0; + + struct msm_gemini_hw_cmd *hw_cmd_p = &hw_cmd_we_buffer_cfg[0]; + + n_reg_val = (((GEMINI_WE_Y_THRESHOLD[1][is_realtime] << + HWIO_JPEG_WE_Y_THRESHOLD_WE_DEASSERT_STALL_TH_SHFT) & + HWIO_JPEG_WE_Y_THRESHOLD_WE_DEASSERT_STALL_TH_BMSK) | + ((GEMINI_WE_Y_THRESHOLD[0][is_realtime] << + HWIO_JPEG_WE_Y_THRESHOLD_WE_ASSERT_STALL_TH_SHFT) & + HWIO_JPEG_WE_Y_THRESHOLD_WE_ASSERT_STALL_TH_BMSK)); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + + msm_gemini_hw_write(hw_cmd_p++); + + /* @todo maybe not for realtime? */ + n_reg_val = (((GEMINI_WE_CBCR_THRESHOLD[1][is_realtime] << + HWIO_JPEG_WE_CBCR_THRESHOLD_WE_DEASSERT_STALL_TH_SHFT) & + HWIO_JPEG_WE_CBCR_THRESHOLD_WE_DEASSERT_STALL_TH_BMSK) | + ((GEMINI_WE_CBCR_THRESHOLD[0][is_realtime] << + HWIO_JPEG_WE_CBCR_THRESHOLD_WE_ASSERT_STALL_TH_SHFT) & + HWIO_JPEG_WE_CBCR_THRESHOLD_WE_ASSERT_STALL_TH_BMSK)); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p); + + return; +} + +struct msm_gemini_hw_cmd hw_cmd_we_ping_update[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_WE_Y_PING_BUFFER_CFG_ADDR, + HWIO_JPEG_WE_Y_PING_BUFFER_CFG_RMSK, {0} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_WE_Y_PING_ADDR_ADDR, + HWIO_JPEG_WE_Y_PING_ADDR_RMSK, {0} }, +}; + +struct msm_gemini_hw_cmd hw_cmd_we_pong_update[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_WE_Y_PONG_BUFFER_CFG_ADDR, + HWIO_JPEG_WE_Y_PONG_BUFFER_CFG_RMSK, {0} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_WE_Y_PONG_ADDR_ADDR, + HWIO_JPEG_WE_Y_PONG_ADDR_RMSK, {0} }, +}; + +void msm_gemini_hw_we_buffer_update(struct msm_gemini_hw_buf *p_input, + uint8_t pingpong_index) +{ + uint32_t n_reg_val = 0; + + struct msm_gemini_hw_cmd *hw_cmd_p; + + if (pingpong_index == 0) { + hw_cmd_p = &hw_cmd_we_ping_update[0]; + + n_reg_val = ((p_input->y_len << + HWIO_JPEG_WE_Y_PING_BUFFER_CFG_WE_BUFFER_LENGTH_SHFT) & + HWIO_JPEG_WE_Y_PING_BUFFER_CFG_WE_BUFFER_LENGTH_BMSK); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + + n_reg_val = p_input->y_buffer_addr; + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + } else if (pingpong_index == 1) { + hw_cmd_p = &hw_cmd_we_pong_update[0]; + + n_reg_val = ((p_input->y_len << + HWIO_JPEG_WE_Y_PONG_BUFFER_CFG_WE_BUFFER_LENGTH_SHFT) & + HWIO_JPEG_WE_Y_PONG_BUFFER_CFG_WE_BUFFER_LENGTH_BMSK); + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + + n_reg_val = p_input->y_buffer_addr; + hw_cmd_p->data = n_reg_val; + msm_gemini_hw_write(hw_cmd_p++); + } else { + /* shall not get to here */ + } + + return; +} + +struct msm_gemini_hw_cmd hw_cmd_reset[] = { + /* type, repeat n times, offset, mask, data or pdata */ + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_IRQ_MASK_ADDR, + HWIO_JPEG_IRQ_MASK_RMSK, {JPEG_IRQ_DISABLE_ALL} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_IRQ_CLEAR_ADDR, + HWIO_JPEG_IRQ_MASK_RMSK, {JPEG_IRQ_CLEAR_ALL} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_IRQ_MASK_ADDR, + HWIO_JPEG_IRQ_MASK_RMSK, {JPEG_IRQ_ALLSOURCES_ENABLE} }, + {MSM_GEMINI_HW_CMD_TYPE_WRITE, 1, HWIO_JPEG_RESET_CMD_ADDR, + HWIO_JPEG_RESET_CMD_RMSK, {JPEG_RESET_DEFAULT} }, +}; + +void msm_gemini_hw_init(void *base, int size) +{ + gemini_region_base = base; + gemini_region_size = size; +} + +void msm_gemini_hw_reset(void *base, int size) +{ + struct msm_gemini_hw_cmd *hw_cmd_p; + + hw_cmd_p = &hw_cmd_reset[0]; + + wmb(); + msm_gemini_hw_write(hw_cmd_p++); + msm_gemini_hw_write(hw_cmd_p++); + msm_gemini_hw_write(hw_cmd_p++); + msm_gemini_hw_write(hw_cmd_p); + wmb(); + + return; +} + +uint32_t msm_gemini_hw_read(struct msm_gemini_hw_cmd *hw_cmd_p) +{ + uint32_t *paddr; + uint32_t data; + + paddr = gemini_region_base + hw_cmd_p->offset; + + data = readl(paddr); + data &= hw_cmd_p->mask; + + GMN_DBG("%s:%d] type-%d n-%d offset-0x%4x mask-0x%8x data-0x%8x\n", + __func__, __LINE__, hw_cmd_p->type, hw_cmd_p->n, + hw_cmd_p->offset, hw_cmd_p->mask, data); + return data; +} + +void msm_gemini_hw_write(struct msm_gemini_hw_cmd *hw_cmd_p) +{ + uint32_t *paddr; + uint32_t old_data, new_data; + + /* type, repeat n times, offset, mask, data or pdata */ + GMN_DBG("%s:%d] type-%d n-%d offset-0x%4x mask-0x%8x data-0x%8x\n", + __func__, __LINE__, hw_cmd_p->type, hw_cmd_p->n, + hw_cmd_p->offset, hw_cmd_p->mask, hw_cmd_p->data); + + paddr = gemini_region_base + hw_cmd_p->offset; + + if (hw_cmd_p->mask == 0xffffffff) { + old_data = 0; + } else { + old_data = readl(paddr); + old_data &= ~hw_cmd_p->mask; + } + + new_data = hw_cmd_p->data & hw_cmd_p->mask; + new_data |= old_data; + writel(new_data, paddr); +} + +int msm_gemini_hw_wait(struct msm_gemini_hw_cmd *hw_cmd_p, int m_us) +{ + int tm = hw_cmd_p->n; + uint32_t data; + uint32_t wait_data = hw_cmd_p->data & hw_cmd_p->mask; + + data = msm_gemini_hw_read(hw_cmd_p); + if (data != wait_data) { + while (tm) { + udelay(m_us); + data = msm_gemini_hw_read(hw_cmd_p); + if (data == wait_data) + break; + tm--; + } + } + hw_cmd_p->data = data; + return tm; +} + +void msm_gemini_hw_delay(struct msm_gemini_hw_cmd *hw_cmd_p, int m_us) +{ + int tm = hw_cmd_p->n; + while (tm) { + udelay(m_us); + tm--; + } +} + +int msm_gemini_hw_exec_cmds(struct msm_gemini_hw_cmd *hw_cmd_p, int m_cmds) +{ + int is_copy_to_user = -1; + uint32_t data; + + while (m_cmds--) { + if (hw_cmd_p->offset > gemini_region_size) { + GMN_PR_ERR("%s:%d] %d exceed hw region %d\n", __func__, + __LINE__, hw_cmd_p->offset, gemini_region_size); + return -EFAULT; + } + + switch (hw_cmd_p->type) { + case MSM_GEMINI_HW_CMD_TYPE_READ: + hw_cmd_p->data = msm_gemini_hw_read(hw_cmd_p); + is_copy_to_user = 1; + break; + + case MSM_GEMINI_HW_CMD_TYPE_WRITE: + msm_gemini_hw_write(hw_cmd_p); + break; + + case MSM_GEMINI_HW_CMD_TYPE_WRITE_OR: + data = msm_gemini_hw_read(hw_cmd_p); + hw_cmd_p->data = (hw_cmd_p->data & hw_cmd_p->mask) | + data; + msm_gemini_hw_write(hw_cmd_p); + break; + + case MSM_GEMINI_HW_CMD_TYPE_UWAIT: + msm_gemini_hw_wait(hw_cmd_p, 1); + break; + + case MSM_GEMINI_HW_CMD_TYPE_MWAIT: + msm_gemini_hw_wait(hw_cmd_p, 1000); + break; + + case MSM_GEMINI_HW_CMD_TYPE_UDELAY: + msm_gemini_hw_delay(hw_cmd_p, 1); + break; + + case MSM_GEMINI_HW_CMD_TYPE_MDELAY: + msm_gemini_hw_delay(hw_cmd_p, 1000); + break; + + default: + GMN_PR_ERR("wrong hw command type\n"); + break; + } + + hw_cmd_p++; + } + return is_copy_to_user; +} + +void msm_gemini_hw_region_dump(int size) +{ + uint32_t *p; + uint8_t *p8; + + if (size > gemini_region_size) + GMN_PR_ERR("%s:%d] wrong region dump size\n", + __func__, __LINE__); + + p = (uint32_t *) gemini_region_base; + while (size >= 16) { + GMN_DBG("0x%08X] %08X %08X %08X %08X\n", + gemini_region_size - size, + readl(p), readl(p+1), readl(p+2), readl(p+3)); + p += 4; + size -= 16; + } + + if (size > 0) { + uint32_t d; + GMN_DBG("0x%08X] ", gemini_region_size - size); + while (size >= 4) { + GMN_DBG("%08X ", readl(p++)); + size -= 4; + } + + d = readl(p); + p8 = (uint8_t *) &d; + while (size) { + GMN_DBG("%02X", *p8++); + size--; + } + + GMN_DBG("\n"); + } +} + diff --git a/drivers/media/video/msm/msm_gemini_hw.h b/drivers/media/video/msm/msm_gemini_hw.h new file mode 100644 index 00000000000..ee1eac3cf6e --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_hw.h @@ -0,0 +1,101 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MSM_GEMINI_HW_H +#define MSM_GEMINI_HW_H + +#include +#include "msm_gemini_hw_reg.h" + +struct msm_gemini_hw_buf { + struct msm_gemini_buf vbuf; + struct file *file; + uint32_t framedone_len; + uint32_t y_buffer_addr; + uint32_t y_len; + uint32_t cbcr_buffer_addr; + uint32_t cbcr_len; + uint32_t num_of_mcu_rows; +}; + +struct msm_gemini_hw_pingpong { + uint8_t is_fe; /* 1: fe; 0: we */ + struct msm_gemini_hw_buf buf[2]; + int buf_status[2]; + int buf_active_index; +}; + +int msm_gemini_hw_pingpong_update(struct msm_gemini_hw_pingpong *pingpong_hw, + struct msm_gemini_hw_buf *buf); +void *msm_gemini_hw_pingpong_irq(struct msm_gemini_hw_pingpong *pingpong_hw); +void *msm_gemini_hw_pingpong_active_buffer(struct msm_gemini_hw_pingpong + *pingpong_hw); + +void msm_gemini_hw_irq_clear(uint32_t, uint32_t); +int msm_gemini_hw_irq_get_status(void); +long msm_gemini_hw_encode_output_size(void); +#define MSM_GEMINI_HW_MASK_COMP_FRAMEDONE \ + MSM_GEMINI_HW_IRQ_STATUS_FRAMEDONE_MASK +#define MSM_GEMINI_HW_MASK_COMP_FE \ + MSM_GEMINI_HW_IRQ_STATUS_FE_RD_DONE_MASK +#define MSM_GEMINI_HW_MASK_COMP_WE \ + (MSM_GEMINI_HW_IRQ_STATUS_WE_Y_PINGPONG_MASK | \ + MSM_GEMINI_HW_IRQ_STATUS_WE_CBCR_PINGPONG_MASK) +#define MSM_GEMINI_HW_MASK_COMP_RESET_ACK \ + MSM_GEMINI_HW_IRQ_STATUS_RESET_ACK_MASK +#define MSM_GEMINI_HW_MASK_COMP_ERR \ + (MSM_GEMINI_HW_IRQ_STATUS_FE_RTOVF_MASK | \ + MSM_GEMINI_HW_IRQ_STATUS_FE_VFE_OVERFLOW_MASK | \ + MSM_GEMINI_HW_IRQ_STATUS_WE_Y_BUFFER_OVERFLOW_MASK | \ + MSM_GEMINI_HW_IRQ_STATUS_WE_CBCR_BUFFER_OVERFLOW_MASK | \ + MSM_GEMINI_HW_IRQ_STATUS_WE_CH0_DATAFIFO_OVERFLOW_MASK | \ + MSM_GEMINI_HW_IRQ_STATUS_WE_CH1_DATAFIFO_OVERFLOW_MASK | \ + MSM_GEMINI_HW_IRQ_STATUS_BUS_ERROR_MASK | \ + MSM_GEMINI_HW_IRQ_STATUS_VIOLATION_MASK) + +#define msm_gemini_hw_irq_is_frame_done(gemini_irq_status) \ + (gemini_irq_status & MSM_GEMINI_HW_MASK_COMP_FRAMEDONE) +#define msm_gemini_hw_irq_is_fe_pingpong(gemini_irq_status) \ + (gemini_irq_status & MSM_GEMINI_HW_MASK_COMP_FE) +#define msm_gemini_hw_irq_is_we_pingpong(gemini_irq_status) \ + (gemini_irq_status & MSM_GEMINI_HW_MASK_COMP_WE) +#define msm_gemini_hw_irq_is_reset_ack(gemini_irq_status) \ + (gemini_irq_status & MSM_GEMINI_HW_MASK_COMP_RESET_ACK) +#define msm_gemini_hw_irq_is_err(gemini_irq_status) \ + (gemini_irq_status & MSM_GEMINI_HW_MASK_COMP_ERR) + +void msm_gemini_hw_fe_buffer_update(struct msm_gemini_hw_buf *p_input, + uint8_t pingpong_index); +void msm_gemini_hw_we_buffer_update(struct msm_gemini_hw_buf *p_input, + uint8_t pingpong_index); + +void msm_gemini_hw_we_buffer_cfg(uint8_t is_realtime); + +void msm_gemini_hw_fe_start(void); +void msm_gemini_hw_clk_cfg(void); + +void msm_gemini_hw_reset(void *base, int size); +void msm_gemini_hw_irq_cfg(void); +void msm_gemini_hw_init(void *base, int size); + +uint32_t msm_gemini_hw_read(struct msm_gemini_hw_cmd *hw_cmd_p); +void msm_gemini_hw_write(struct msm_gemini_hw_cmd *hw_cmd_p); +int msm_gemini_hw_wait(struct msm_gemini_hw_cmd *hw_cmd_p, int m_us); +void msm_gemini_hw_delay(struct msm_gemini_hw_cmd *hw_cmd_p, int m_us); +int msm_gemini_hw_exec_cmds(struct msm_gemini_hw_cmd *hw_cmd_p, int m_cmds); +void msm_gemini_hw_region_dump(int size); + +#define MSM_GEMINI_PIPELINE_CLK_128MHZ 128 /* 8MP 128MHz */ +#define MSM_GEMINI_PIPELINE_CLK_140MHZ 140 /* 9MP 140MHz */ +#define MSM_GEMINI_PIPELINE_CLK_200MHZ 153 /* 12MP 153MHz */ + +#endif /* MSM_GEMINI_HW_H */ diff --git a/drivers/media/video/msm/msm_gemini_hw_reg.h b/drivers/media/video/msm/msm_gemini_hw_reg.h new file mode 100644 index 00000000000..4bddfbba4a9 --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_hw_reg.h @@ -0,0 +1,176 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MSM_GEMINI_HW_REG_H +#define MSM_GEMINI_HW_REG_H + +#define GEMINI_REG_BASE 0 + +#define MSM_GEMINI_HW_IRQ_MASK_ADDR 0x00000014 +#define MSM_GEMINI_HW_IRQ_MASK_RMSK 0xffffffff +#define MSM_GEMINI_HW_IRQ_MASK_SHFT 0 +#define MSM_GEMINI_HW_IRQ_DISABLE 0 +#define MSM_GEMINI_HW_IRQ_ENABLE 0xffffffff + +#define MSM_GEMINI_HW_IRQ_CLEAR_ADDR 0x00000018 +#define MSM_GEMINI_HW_IRQ_CLEAR_RMSK 0xffffffff +#define MSM_GEMINI_HW_IRQ_CLEAR_SHFT 0 +#define MSM_GEMINI_HW_IRQ_CLEAR 0xffffffff + +#define MSM_GEMINI_HW_IRQ_STATUS_FRAMEDONE_MASK 0x00000001 +#define MSM_GEMINI_HW_IRQ_STATUS_FRAMEDONE_SHIFT 0x00000000 + +#define MSM_GEMINI_HW_IRQ_STATUS_FE_RD_DONE_MASK 0x00000002 +#define MSM_GEMINI_HW_IRQ_STATUS_FE_RD_DONE_SHIFT 0x00000001 + +#define MSM_GEMINI_HW_IRQ_STATUS_FE_RTOVF_MASK 0x00000004 +#define MSM_GEMINI_HW_IRQ_STATUS_FE_RTOVF_SHIFT 0x00000002 + +#define MSM_GEMINI_HW_IRQ_STATUS_FE_VFE_OVERFLOW_MASK 0x00000008 +#define MSM_GEMINI_HW_IRQ_STATUS_FE_VFE_OVERFLOW_SHIFT 0x00000003 + +#define MSM_GEMINI_HW_IRQ_STATUS_WE_Y_PINGPONG_MASK 0x00000010 +#define MSM_GEMINI_HW_IRQ_STATUS_WE_Y_PINGPONG_SHIFT 0x00000004 + +#define MSM_GEMINI_HW_IRQ_STATUS_WE_CBCR_PINGPONG_MASK 0x00000020 +#define MSM_GEMINI_HW_IRQ_STATUS_WE_CBCR_PINGPONG_SHIFT 0x00000005 + +#define MSM_GEMINI_HW_IRQ_STATUS_WE_Y_BUFFER_OVERFLOW_MASK 0x00000040 +#define MSM_GEMINI_HW_IRQ_STATUS_WE_Y_BUFFER_OVERFLOW_SHIFT 0x00000006 + +#define MSM_GEMINI_HW_IRQ_STATUS_WE_CBCR_BUFFER_OVERFLOW_MASK 0x00000080 +#define MSM_GEMINI_HW_IRQ_STATUS_WE_CBCR_BUFFER_OVERFLOW_SHIFT 0x00000007 + +#define MSM_GEMINI_HW_IRQ_STATUS_WE_CH0_DATAFIFO_OVERFLOW_MASK 0x00000100 +#define MSM_GEMINI_HW_IRQ_STATUS_WE_CH0_DATAFIFO_OVERFLOW_SHIFT 0x00000008 + +#define MSM_GEMINI_HW_IRQ_STATUS_WE_CH1_DATAFIFO_OVERFLOW_MASK 0x00000200 +#define MSM_GEMINI_HW_IRQ_STATUS_WE_CH1_DATAFIFO_OVERFLOW_SHIFT 0x00000009 + +#define MSM_GEMINI_HW_IRQ_STATUS_RESET_ACK_MASK 0x00000400 +#define MSM_GEMINI_HW_IRQ_STATUS_RESET_ACK_SHIFT 0x0000000a + +#define MSM_GEMINI_HW_IRQ_STATUS_BUS_ERROR_MASK 0x00000800 +#define MSM_GEMINI_HW_IRQ_STATUS_BUS_ERROR_SHIFT 0x0000000b + +#define MSM_GEMINI_HW_IRQ_STATUS_VIOLATION_MASK 0x00001000 +#define MSM_GEMINI_HW_IRQ_STATUS_VIOLATION_SHIFT 0x0000000c + +#define JPEG_BUS_CMD_HALT_REQ 0x00000001 + +#define JPEG_REALTIME_CMD_STOP_FB 0x00000000 +#define JPEG_REALTIME_CMD_STOP_IM 0x00000003 +#define JPEG_REALTIME_CMD_START 0x00000001 + +#define JPEG_OFFLINE_CMD_START 0x00000003 + +#define JPEG_DMI_CFG_DISABLE 0x00000000 +#define JPEG_DMI_ADDR_START 0x00000000 + +#define JPEG_FE_CMD_BUFFERRELOAD 0x00000001 + +#define JPEG_WE_YUB_ENCODE 0x01ff0000 + +#define JPEG_RESET_DEFAULT 0x0004ffff /* cfff? */ + +#define JPEG_IRQ_DISABLE_ALL 0x00000000 +#define JPEG_IRQ_CLEAR_ALL 0xffffffff +#define JPEG_IRQ_ALLSOURCES_ENABLE 0xffffffff + +#define HWIO_JPEG_FE_BUFFER_CFG_ADDR (GEMINI_REG_BASE + 0x00000080) +#define HWIO_JPEG_FE_BUFFER_CFG_RMSK 0x1fff1fff + +#define HWIO_JPEG_FE_Y_PING_ADDR_ADDR (GEMINI_REG_BASE + 0x00000084) +#define HWIO_JPEG_FE_Y_PING_ADDR_RMSK 0xffffffff + +#define HWIO_JPEG_FE_Y_PONG_ADDR_ADDR (GEMINI_REG_BASE + 0x00000088) +#define HWIO_JPEG_FE_Y_PONG_ADDR_RMSK 0xffffffff + +#define HWIO_JPEG_FE_CBCR_PING_ADDR_ADDR (GEMINI_REG_BASE + 0x0000008c) +#define HWIO_JPEG_FE_CBCR_PING_ADDR_RMSK 0xffffffff + +#define HWIO_JPEG_FE_CBCR_PONG_ADDR_ADDR (GEMINI_REG_BASE + 0x00000090) +#define HWIO_JPEG_FE_CBCR_PONG_ADDR_RMSK 0xffffffff + +#define HWIO_JPEG_FE_CMD_ADDR (GEMINI_REG_BASE + 0x00000094) +#define HWIO_JPEG_FE_CMD_RMSK 0x3 + +#define HWIO_JPEG_FE_BUFFER_CFG_CBCR_MCU_ROWS_BMSK 0x1fff0000 +#define HWIO_JPEG_FE_BUFFER_CFG_CBCR_MCU_ROWS_SHFT 0x10 +#define HWIO_JPEG_FE_BUFFER_CFG_Y_MCU_ROWS_BMSK 0x1fff +#define HWIO_JPEG_FE_BUFFER_CFG_Y_MCU_ROWS_SHFT 0 + +#define HWIO_JPEG_FE_Y_PING_ADDR_FE_Y_PING_START_ADDR_BMSK 0xffffffff +#define HWIO_JPEG_FE_Y_PING_ADDR_FE_Y_PING_START_ADDR_SHFT 0 + +#define HWIO_JPEG_FE_CBCR_PING_ADDR_FE_CBCR_PING_START_ADDR_BMSK 0xffffffff +#define HWIO_JPEG_FE_CBCR_PING_ADDR_FE_CBCR_PING_START_ADDR_SHFT 0 + +#define HWIO_JPEG_FE_Y_PONG_ADDR_FE_Y_PONG_START_ADDR_BMSK 0xffffffff +#define HWIO_JPEG_FE_Y_PONG_ADDR_FE_Y_PONG_START_ADDR_SHFT 0 + +#define HWIO_JPEG_FE_CBCR_PONG_ADDR_FE_CBCR_PONG_START_ADDR_BMSK 0xffffffff +#define HWIO_JPEG_FE_CBCR_PONG_ADDR_FE_CBCR_PONG_START_ADDR_SHFT 0 + +#define HWIO_JPEG_WE_Y_THRESHOLD_ADDR (GEMINI_REG_BASE + 0x000000c0) +#define HWIO_JPEG_WE_Y_THRESHOLD_RMSK 0x1ff01ff + +#define HWIO_JPEG_WE_CBCR_THRESHOLD_ADDR (GEMINI_REG_BASE + 0x000000c4) +#define HWIO_JPEG_WE_CBCR_THRESHOLD_RMSK 0x1ff01ff + +#define HWIO_JPEG_WE_Y_UB_CFG_ADDR (GEMINI_REG_BASE + 0x000000e8) +#define HWIO_JPEG_WE_Y_UB_CFG_RMSK 0x1ff01ff + +#define HWIO_JPEG_WE_Y_THRESHOLD_WE_DEASSERT_STALL_TH_BMSK 0x1ff0000 +#define HWIO_JPEG_WE_Y_THRESHOLD_WE_DEASSERT_STALL_TH_SHFT 0x10 +#define HWIO_JPEG_WE_Y_THRESHOLD_WE_ASSERT_STALL_TH_BMSK 0x1ff +#define HWIO_JPEG_WE_Y_THRESHOLD_WE_ASSERT_STALL_TH_SHFT 0 + +#define HWIO_JPEG_WE_CBCR_THRESHOLD_WE_DEASSERT_STALL_TH_BMSK 0x1ff0000 +#define HWIO_JPEG_WE_CBCR_THRESHOLD_WE_DEASSERT_STALL_TH_SHFT 0x10 +#define HWIO_JPEG_WE_CBCR_THRESHOLD_WE_ASSERT_STALL_TH_BMSK 0x1ff +#define HWIO_JPEG_WE_CBCR_THRESHOLD_WE_ASSERT_STALL_TH_SHFT 0 + +#define HWIO_JPEG_WE_Y_PING_BUFFER_CFG_ADDR (GEMINI_REG_BASE + 0x000000c8) +#define HWIO_JPEG_WE_Y_PING_BUFFER_CFG_RMSK 0x7fffff + +#define HWIO_JPEG_WE_Y_PING_ADDR_ADDR (GEMINI_REG_BASE + 0x000000d8) +#define HWIO_JPEG_WE_Y_PING_ADDR_RMSK 0xfffffff8 + +#define HWIO_JPEG_WE_Y_PONG_BUFFER_CFG_ADDR (GEMINI_REG_BASE + 0x000000cc) +#define HWIO_JPEG_WE_Y_PONG_BUFFER_CFG_RMSK 0x7fffff + +#define HWIO_JPEG_WE_Y_PONG_ADDR_ADDR (GEMINI_REG_BASE + 0x000000dc) +#define HWIO_JPEG_WE_Y_PONG_ADDR_RMSK 0xfffffff8 + +#define HWIO_JPEG_WE_Y_PING_BUFFER_CFG_WE_BUFFER_LENGTH_BMSK 0x7fffff +#define HWIO_JPEG_WE_Y_PING_BUFFER_CFG_WE_BUFFER_LENGTH_SHFT 0 + +#define HWIO_JPEG_WE_Y_PONG_BUFFER_CFG_WE_BUFFER_LENGTH_BMSK 0x7fffff +#define HWIO_JPEG_WE_Y_PONG_BUFFER_CFG_WE_BUFFER_LENGTH_SHFT 0 + +#define HWIO_JPEG_IRQ_MASK_ADDR (GEMINI_REG_BASE + 0x00000014) +#define HWIO_JPEG_IRQ_MASK_RMSK 0xffffffff + +#define HWIO_JPEG_IRQ_CLEAR_ADDR (GEMINI_REG_BASE + 0x00000018) +#define HWIO_JPEG_IRQ_CLEAR_RMSK 0xffffffff + +#define HWIO_JPEG_RESET_CMD_ADDR (GEMINI_REG_BASE + 0x00000004) +#define HWIO_JPEG_RESET_CMD_RMSK 0xe004ffff + +#define HWIO_JPEG_IRQ_STATUS_ADDR (GEMINI_REG_BASE + 0x0000001c) +#define HWIO_JPEG_IRQ_STATUS_RMSK 0xffffffff + +#define HWIO_JPEG_STATUS_ENCODE_OUTPUT_SIZE_ADDR (GEMINI_REG_BASE + 0x00000034) +#define HWIO_JPEG_STATUS_ENCODE_OUTPUT_SIZE_RMSK 0xffffff + +#endif /* MSM_GEMINI_HW_REG_H */ diff --git a/drivers/media/video/msm/msm_gemini_platform.c b/drivers/media/video/msm/msm_gemini_platform.c new file mode 100644 index 00000000000..140d5d0e56b --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_platform.c @@ -0,0 +1,154 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_gemini_platform.h" +#include "msm_gemini_common.h" +#include "msm_gemini_hw.h" + +#ifdef CONFIG_MSM_NPA_SYSTEM_BUS +/* NPA Flow ID */ +#define MSM_SYSTEM_BUS_RATE MSM_AXI_FLOW_JPEG_12MP +#else +/* AXI rate in KHz */ +#define MSM_SYSTEM_BUS_RATE 160000 +#endif + +void msm_gemini_platform_p2v(struct file *file) +{ +#ifdef CONFIG_ANDROID_PMEM + put_pmem_file(file); +#endif +} + +uint32_t msm_gemini_platform_v2p(int fd, uint32_t len, struct file **file_p) +{ + unsigned long paddr; + unsigned long size; + int rc; + +#ifdef CONFIG_ANDROID_PMEM + unsigned long kvstart; + rc = get_pmem_file(fd, &paddr, &kvstart, &size, file_p); +#else + rc = 0; + paddr = 0; + size = 0; +#endif + if (rc < 0) { + GMN_PR_ERR("%s: get_pmem_file fd %d error %d\n", __func__, fd, + rc); + return 0; + } + + /* validate user input */ + if (len > size) { + GMN_PR_ERR("%s: invalid offset + len\n", __func__); + return 0; + } + + return paddr; +} + +int msm_gemini_platform_init(struct platform_device *pdev, + struct resource **mem, + void **base, + int *irq, + irqreturn_t (*handler) (int, void *), + void *context) +{ + int rc = -1; + int gemini_irq; + struct resource *gemini_mem, *gemini_io, *gemini_irq_res; + void *gemini_base; + + gemini_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!gemini_mem) { + GMN_PR_ERR("%s: no mem resource?\n", __func__); + return -ENODEV; + } + + gemini_irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!gemini_irq_res) { + GMN_PR_ERR("no irq resource?\n"); + return -ENODEV; + } + gemini_irq = gemini_irq_res->start; + + gemini_io = request_mem_region(gemini_mem->start, + resource_size(gemini_mem), pdev->name); + if (!gemini_io) { + GMN_PR_ERR("%s: region already claimed\n", __func__); + return -EBUSY; + } + + gemini_base = ioremap(gemini_mem->start, resource_size(gemini_mem)); + if (!gemini_base) { + rc = -ENOMEM; + GMN_PR_ERR("%s: ioremap failed\n", __func__); + goto fail1; + } + + rc = msm_camio_jpeg_clk_enable(); + if (rc) { + GMN_PR_ERR("%s: clk failed rc = %d\n", __func__, rc); + goto fail2; + } + + msm_gemini_hw_init(gemini_base, resource_size(gemini_mem)); + rc = request_irq(gemini_irq, handler, IRQF_TRIGGER_RISING, "gemini", + context); + if (rc) { + GMN_PR_ERR("%s: request_irq failed, %d\n", __func__, + gemini_irq); + goto fail3; + } + + *mem = gemini_mem; + *base = gemini_base; + *irq = gemini_irq; + GMN_DBG("%s:%d] success\n", __func__, __LINE__); + + return rc; + +fail3: + msm_camio_jpeg_clk_disable(); +fail2: + iounmap(gemini_base); +fail1: + release_mem_region(gemini_mem->start, resource_size(gemini_mem)); + GMN_DBG("%s:%d] fail\n", __func__, __LINE__); + return rc; +} + +int msm_gemini_platform_release(struct resource *mem, void *base, int irq, + void *context) +{ + int result; + + free_irq(irq, context); + result = msm_camio_jpeg_clk_disable(); + iounmap(base); + release_mem_region(mem->start, resource_size(mem)); + + GMN_DBG("%s:%d] success\n", __func__, __LINE__); + return result; +} + diff --git a/drivers/media/video/msm/msm_gemini_platform.h b/drivers/media/video/msm/msm_gemini_platform.h new file mode 100644 index 00000000000..49b1db600e0 --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_platform.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MSM_GEMINI_PLATFORM_H +#define MSM_GEMINI_PLATFORM_H + +#include +#include + +void msm_gemini_platform_p2v(struct file *file); +uint32_t msm_gemini_platform_v2p(int fd, uint32_t len, struct file **file); + +int msm_gemini_platform_clk_enable(void); +int msm_gemini_platform_clk_disable(void); + +int msm_gemini_platform_init(struct platform_device *pdev, + struct resource **mem, + void **base, + int *irq, + irqreturn_t (*handler) (int, void *), + void *context); +int msm_gemini_platform_release(struct resource *mem, void *base, int irq, + void *context); + +#endif /* MSM_GEMINI_PLATFORM_H */ diff --git a/drivers/media/video/msm/msm_gemini_sync.c b/drivers/media/video/msm/msm_gemini_sync.c new file mode 100644 index 00000000000..2ad046775be --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_sync.c @@ -0,0 +1,839 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include "msm_gemini_sync.h" +#include "msm_gemini_core.h" +#include "msm_gemini_platform.h" +#include "msm_gemini_common.h" + +static int release_buf; + +/*************** queue helper ****************/ +inline void msm_gemini_q_init(char const *name, struct msm_gemini_q *q_p) +{ + GMN_DBG("%s:%d] %s\n", __func__, __LINE__, name); + q_p->name = name; + spin_lock_init(&q_p->lck); + INIT_LIST_HEAD(&q_p->q); + init_waitqueue_head(&q_p->wait); + q_p->unblck = 0; +} + +inline void *msm_gemini_q_out(struct msm_gemini_q *q_p) +{ + unsigned long flags; + struct msm_gemini_q_entry *q_entry_p = NULL; + void *data = NULL; + + GMN_DBG("%s:%d] %s\n", __func__, __LINE__, q_p->name); + spin_lock_irqsave(&q_p->lck, flags); + if (!list_empty(&q_p->q)) { + q_entry_p = list_first_entry(&q_p->q, struct msm_gemini_q_entry, + list); + list_del_init(&q_entry_p->list); + } + spin_unlock_irqrestore(&q_p->lck, flags); + + if (q_entry_p) { + data = q_entry_p->data; + kfree(q_entry_p); + } else { + GMN_DBG("%s:%d] %s no entry\n", __func__, __LINE__, + q_p->name); + } + + return data; +} + +inline int msm_gemini_q_in(struct msm_gemini_q *q_p, void *data) +{ + unsigned long flags; + + struct msm_gemini_q_entry *q_entry_p; + + GMN_DBG("%s:%d] %s\n", __func__, __LINE__, q_p->name); + + q_entry_p = kmalloc(sizeof(struct msm_gemini_q_entry), GFP_ATOMIC); + if (!q_entry_p) { + GMN_PR_ERR("%s: no mem\n", __func__); + return -1; + } + q_entry_p->data = data; + + spin_lock_irqsave(&q_p->lck, flags); + list_add_tail(&q_entry_p->list, &q_p->q); + spin_unlock_irqrestore(&q_p->lck, flags); + + return 0; +} + +inline int msm_gemini_q_in_buf(struct msm_gemini_q *q_p, + struct msm_gemini_core_buf *buf) +{ + struct msm_gemini_core_buf *buf_p; + + GMN_DBG("%s:%d]\n", __func__, __LINE__); + buf_p = kmalloc(sizeof(struct msm_gemini_core_buf), GFP_ATOMIC); + if (!buf_p) { + GMN_PR_ERR("%s: no mem\n", __func__); + return -1; + } + + memcpy(buf_p, buf, sizeof(struct msm_gemini_core_buf)); + + msm_gemini_q_in(q_p, buf_p); + return 0; +} + +inline int msm_gemini_q_wait(struct msm_gemini_q *q_p) +{ + int tm = MAX_SCHEDULE_TIMEOUT; /* 500ms */ + int rc; + + GMN_DBG("%s:%d] %s wait\n", __func__, __LINE__, q_p->name); + rc = wait_event_interruptible_timeout(q_p->wait, + (!list_empty_careful(&q_p->q) || q_p->unblck), + msecs_to_jiffies(tm)); + GMN_DBG("%s:%d] %s wait done\n", __func__, __LINE__, q_p->name); + if (list_empty_careful(&q_p->q)) { + if (rc == 0) { + rc = -ETIMEDOUT; + GMN_PR_ERR("%s:%d] %s timeout\n", __func__, __LINE__, + q_p->name); + } else if (q_p->unblck) { + GMN_DBG("%s:%d] %s unblock is true\n", __func__, + __LINE__, q_p->name); + q_p->unblck = 0; + rc = -ECANCELED; + } else if (rc < 0) { + GMN_PR_ERR("%s:%d] %s rc %d\n", __func__, __LINE__, + q_p->name, rc); + } + } + return rc; +} + +inline int msm_gemini_q_wakeup(struct msm_gemini_q *q_p) +{ + GMN_DBG("%s:%d] %s\n", __func__, __LINE__, q_p->name); + wake_up(&q_p->wait); + return 0; +} + +inline int msm_gemini_q_unblock(struct msm_gemini_q *q_p) +{ + GMN_DBG("%s:%d] %s\n", __func__, __LINE__, q_p->name); + q_p->unblck = 1; + wake_up(&q_p->wait); + return 0; +} + +inline void msm_gemini_outbuf_q_cleanup(struct msm_gemini_q *q_p) +{ + struct msm_gemini_core_buf *buf_p; + GMN_DBG("%s:%d] %s\n", __func__, __LINE__, q_p->name); + do { + buf_p = msm_gemini_q_out(q_p); + if (buf_p) { + msm_gemini_platform_p2v(buf_p->file); + GMN_DBG("%s:%d] %s\n", __func__, __LINE__, q_p->name); + kfree(buf_p); + } + } while (buf_p); + q_p->unblck = 0; +} + +inline void msm_gemini_q_cleanup(struct msm_gemini_q *q_p) +{ + void *data; + GMN_DBG("%s:%d] %s\n", __func__, __LINE__, q_p->name); + do { + data = msm_gemini_q_out(q_p); + if (data) { + GMN_DBG("%s:%d] %s\n", __func__, __LINE__, q_p->name); + kfree(data); + } + } while (data); + q_p->unblck = 0; +} + +/*************** event queue ****************/ + +int msm_gemini_framedone_irq(struct msm_gemini_device *pgmn_dev, + struct msm_gemini_core_buf *buf_in) +{ + int rc = 0; + + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + + if (buf_in) { + buf_in->vbuf.framedone_len = buf_in->framedone_len; + buf_in->vbuf.type = MSM_GEMINI_EVT_FRAMEDONE; + GMN_DBG("%s:%d] 0x%08x %d framedone_len %d\n", + __func__, __LINE__, + (int) buf_in->y_buffer_addr, buf_in->y_len, + buf_in->vbuf.framedone_len); + rc = msm_gemini_q_in_buf(&pgmn_dev->evt_q, buf_in); + } else { + GMN_PR_ERR("%s:%d] no output return buffer\n", + __func__, __LINE__); + rc = -1; + } + + if (buf_in) + rc = msm_gemini_q_wakeup(&pgmn_dev->evt_q); + + return rc; +} + +int msm_gemini_evt_get(struct msm_gemini_device *pgmn_dev, + void __user *to) +{ + struct msm_gemini_core_buf *buf_p; + struct msm_gemini_ctrl_cmd ctrl_cmd; + + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + + msm_gemini_q_wait(&pgmn_dev->evt_q); + buf_p = msm_gemini_q_out(&pgmn_dev->evt_q); + + if (!buf_p) { + GMN_DBG("%s:%d] no buffer\n", __func__, __LINE__); + return -EAGAIN; + } + + ctrl_cmd.type = buf_p->vbuf.type; + kfree(buf_p); + + GMN_DBG("%s:%d] 0x%08x %d\n", __func__, __LINE__, + (int) ctrl_cmd.value, ctrl_cmd.len); + + if (copy_to_user(to, &ctrl_cmd, sizeof(ctrl_cmd))) { + GMN_PR_ERR("%s:%d]\n", __func__, __LINE__); + return -EFAULT; + } + + return 0; +} + +int msm_gemini_evt_get_unblock(struct msm_gemini_device *pgmn_dev) +{ + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + msm_gemini_q_unblock(&pgmn_dev->evt_q); + return 0; +} + +void msm_gemini_reset_ack_irq(struct msm_gemini_device *pgmn_dev) +{ + GMN_DBG("%s:%d]\n", __func__, __LINE__); +} + +void msm_gemini_err_irq(struct msm_gemini_device *pgmn_dev, + int event) +{ + int rc = 0; + struct msm_gemini_core_buf buf; + + GMN_PR_ERR("%s:%d] error: %d\n", __func__, __LINE__, event); + + buf.vbuf.type = MSM_GEMINI_EVT_ERR; + rc = msm_gemini_q_in_buf(&pgmn_dev->evt_q, &buf); + if (!rc) + rc = msm_gemini_q_wakeup(&pgmn_dev->evt_q); + + if (!rc) + GMN_PR_ERR("%s:%d] err err\n", __func__, __LINE__); + + return; +} + +/*************** output queue ****************/ + +int msm_gemini_we_pingpong_irq(struct msm_gemini_device *pgmn_dev, + struct msm_gemini_core_buf *buf_in) +{ + int rc = 0; + struct msm_gemini_core_buf *buf_out; + + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + if (buf_in) { + GMN_DBG("%s:%d] 0x%08x %d\n", __func__, __LINE__, + (int) buf_in->y_buffer_addr, buf_in->y_len); + rc = msm_gemini_q_in_buf(&pgmn_dev->output_rtn_q, buf_in); + } else { + GMN_DBG("%s:%d] no output return buffer\n", __func__, + __LINE__); + rc = -1; + } + + buf_out = msm_gemini_q_out(&pgmn_dev->output_buf_q); + + if (buf_out) { + rc = msm_gemini_core_we_buf_update(buf_out); + kfree(buf_out); + } else { + msm_gemini_core_we_buf_reset(buf_in); + GMN_DBG("%s:%d] no output buffer\n", __func__, __LINE__); + rc = -2; + } + + if (buf_in) + rc = msm_gemini_q_wakeup(&pgmn_dev->output_rtn_q); + + return rc; +} + +int msm_gemini_output_get(struct msm_gemini_device *pgmn_dev, void __user *to) +{ + struct msm_gemini_core_buf *buf_p; + struct msm_gemini_buf buf_cmd; + + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + + msm_gemini_q_wait(&pgmn_dev->output_rtn_q); + buf_p = msm_gemini_q_out(&pgmn_dev->output_rtn_q); + + if (!buf_p) { + GMN_DBG("%s:%d] no output buffer return\n", + __func__, __LINE__); + return -EAGAIN; + } + + buf_cmd = buf_p->vbuf; + msm_gemini_platform_p2v(buf_p->file); + kfree(buf_p); + + GMN_DBG("%s:%d] 0x%08x %d\n", __func__, __LINE__, + (int) buf_cmd.vaddr, buf_cmd.y_len); + + if (copy_to_user(to, &buf_cmd, sizeof(buf_cmd))) { + GMN_PR_ERR("%s:%d]", __func__, __LINE__); + return -EFAULT; + } + + return 0; +} + +int msm_gemini_output_get_unblock(struct msm_gemini_device *pgmn_dev) +{ + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + msm_gemini_q_unblock(&pgmn_dev->output_rtn_q); + return 0; +} + +int msm_gemini_output_buf_enqueue(struct msm_gemini_device *pgmn_dev, + void __user *arg) +{ + struct msm_gemini_buf buf_cmd; + struct msm_gemini_core_buf *buf_p; + + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + if (copy_from_user(&buf_cmd, arg, sizeof(struct msm_gemini_buf))) { + GMN_PR_ERR("%s:%d] failed\n", __func__, __LINE__); + return -EFAULT; + } + + buf_p = kmalloc(sizeof(struct msm_gemini_core_buf), GFP_ATOMIC); + if (!buf_p) { + GMN_PR_ERR("%s:%d] no mem\n", __func__, __LINE__); + return -1; + } + + GMN_DBG("%s:%d] 0x%08x %d\n", __func__, __LINE__, (int) buf_cmd.vaddr, + buf_cmd.y_len); + + buf_p->y_buffer_addr = msm_gemini_platform_v2p(buf_cmd.fd, + buf_cmd.y_len, &buf_p->file); + if (!buf_p->y_buffer_addr) { + GMN_PR_ERR("%s:%d] v2p wrong\n", __func__, __LINE__); + kfree(buf_p); + return -1; + } + buf_p->y_len = buf_cmd.y_len; + buf_p->vbuf = buf_cmd; + + msm_gemini_q_in(&pgmn_dev->output_buf_q, buf_p); + return 0; +} + +/*************** input queue ****************/ + +int msm_gemini_fe_pingpong_irq(struct msm_gemini_device *pgmn_dev, + struct msm_gemini_core_buf *buf_in) +{ + struct msm_gemini_core_buf *buf_out; + int rc = 0; + + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + if (buf_in) { + GMN_DBG("%s:%d] 0x%08x %d\n", __func__, __LINE__, + (int) buf_in->y_buffer_addr, buf_in->y_len); + rc = msm_gemini_q_in_buf(&pgmn_dev->input_rtn_q, buf_in); + } else { + GMN_DBG("%s:%d] no input return buffer\n", __func__, + __LINE__); + rc = -1; + } + + buf_out = msm_gemini_q_out(&pgmn_dev->input_buf_q); + + if (buf_out) { + rc = msm_gemini_core_fe_buf_update(buf_out); + kfree(buf_out); + msm_gemini_core_fe_start(); + } else { + GMN_DBG("%s:%d] no input buffer\n", __func__, __LINE__); + rc = -2; + } + + if (buf_in) + rc = msm_gemini_q_wakeup(&pgmn_dev->input_rtn_q); + + return rc; +} + +int msm_gemini_input_get(struct msm_gemini_device *pgmn_dev, void __user * to) +{ + struct msm_gemini_core_buf *buf_p; + struct msm_gemini_buf buf_cmd; + + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + msm_gemini_q_wait(&pgmn_dev->input_rtn_q); + buf_p = msm_gemini_q_out(&pgmn_dev->input_rtn_q); + + if (!buf_p) { + GMN_DBG("%s:%d] no input buffer return\n", + __func__, __LINE__); + return -EAGAIN; + } + + buf_cmd = buf_p->vbuf; + msm_gemini_platform_p2v(buf_p->file); + kfree(buf_p); + + GMN_DBG("%s:%d] 0x%08x %d\n", __func__, __LINE__, + (int) buf_cmd.vaddr, buf_cmd.y_len); + + if (copy_to_user(to, &buf_cmd, sizeof(buf_cmd))) { + GMN_PR_ERR("%s:%d]\n", __func__, __LINE__); + return -EFAULT; + } + + return 0; +} + +int msm_gemini_input_get_unblock(struct msm_gemini_device *pgmn_dev) +{ + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + msm_gemini_q_unblock(&pgmn_dev->input_rtn_q); + return 0; +} + +int msm_gemini_input_buf_enqueue(struct msm_gemini_device *pgmn_dev, + void __user *arg) +{ + struct msm_gemini_core_buf *buf_p; + struct msm_gemini_buf buf_cmd; + + if (copy_from_user(&buf_cmd, arg, sizeof(struct msm_gemini_buf))) { + GMN_PR_ERR("%s:%d] failed\n", __func__, __LINE__); + return -EFAULT; + } + + buf_p = kmalloc(sizeof(struct msm_gemini_core_buf), GFP_ATOMIC); + if (!buf_p) { + GMN_PR_ERR("%s:%d] no mem\n", __func__, __LINE__); + return -1; + } + + GMN_DBG("%s:%d] 0x%08x %d\n", __func__, __LINE__, + (int) buf_cmd.vaddr, buf_cmd.y_len); + + buf_p->y_buffer_addr = msm_gemini_platform_v2p(buf_cmd.fd, + buf_cmd.y_len + buf_cmd.cbcr_len, &buf_p->file) + + buf_cmd.offset; + buf_p->y_len = buf_cmd.y_len; + + buf_p->cbcr_buffer_addr = buf_p->y_buffer_addr + buf_cmd.y_len; + buf_p->cbcr_len = buf_cmd.cbcr_len; + + buf_p->num_of_mcu_rows = buf_cmd.num_of_mcu_rows; + + if (!buf_p->y_buffer_addr || !buf_p->cbcr_buffer_addr) { + GMN_PR_ERR("%s:%d] v2p wrong\n", __func__, __LINE__); + kfree(buf_p); + return -1; + } + buf_p->vbuf = buf_cmd; + + msm_gemini_q_in(&pgmn_dev->input_buf_q, buf_p); + + return 0; +} + +int msm_gemini_irq(int event, void *context, void *data) +{ + struct msm_gemini_device *pgmn_dev = + (struct msm_gemini_device *) context; + + switch (event) { + case MSM_GEMINI_HW_MASK_COMP_FRAMEDONE: + msm_gemini_framedone_irq(pgmn_dev, data); + msm_gemini_we_pingpong_irq(pgmn_dev, data); + break; + + case MSM_GEMINI_HW_MASK_COMP_FE: + msm_gemini_fe_pingpong_irq(pgmn_dev, data); + break; + + case MSM_GEMINI_HW_MASK_COMP_WE: + msm_gemini_we_pingpong_irq(pgmn_dev, data); + break; + + case MSM_GEMINI_HW_MASK_COMP_RESET_ACK: + msm_gemini_reset_ack_irq(pgmn_dev); + break; + + case MSM_GEMINI_HW_MASK_COMP_ERR: + default: + msm_gemini_err_irq(pgmn_dev, event); + break; + } + + return 0; +} + +int __msm_gemini_open(struct msm_gemini_device *pgmn_dev) +{ + int rc; + + mutex_lock(&pgmn_dev->lock); + if (pgmn_dev->open_count) { + /* only open once */ + GMN_PR_ERR("%s:%d] busy\n", __func__, __LINE__); + mutex_unlock(&pgmn_dev->lock); + return -EBUSY; + } + pgmn_dev->open_count++; + mutex_unlock(&pgmn_dev->lock); + + msm_gemini_core_irq_install(msm_gemini_irq); + rc = msm_gemini_platform_init(pgmn_dev->pdev, + &pgmn_dev->mem, &pgmn_dev->base, + &pgmn_dev->irq, msm_gemini_core_irq, pgmn_dev); + if (rc) { + GMN_PR_ERR("%s:%d] platform_init fail %d\n", __func__, + __LINE__, rc); + return rc; + } + + GMN_DBG("%s:%d] platform resources - mem %p, base %p, irq %d\n", + __func__, __LINE__, + pgmn_dev->mem, pgmn_dev->base, pgmn_dev->irq); + + msm_gemini_q_cleanup(&pgmn_dev->evt_q); + msm_gemini_q_cleanup(&pgmn_dev->output_rtn_q); + msm_gemini_outbuf_q_cleanup(&pgmn_dev->output_buf_q); + msm_gemini_q_cleanup(&pgmn_dev->input_rtn_q); + msm_gemini_q_cleanup(&pgmn_dev->input_buf_q); + msm_gemini_core_init(); + + GMN_DBG("%s:%d] success\n", __func__, __LINE__); + return rc; +} + +int __msm_gemini_release(struct msm_gemini_device *pgmn_dev) +{ + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + mutex_lock(&pgmn_dev->lock); + if (!pgmn_dev->open_count) { + GMN_PR_ERR(KERN_ERR "%s: not opened\n", __func__); + mutex_unlock(&pgmn_dev->lock); + return -EINVAL; + } + pgmn_dev->open_count--; + mutex_unlock(&pgmn_dev->lock); + + msm_gemini_core_release(release_buf); + msm_gemini_q_cleanup(&pgmn_dev->evt_q); + msm_gemini_q_cleanup(&pgmn_dev->output_rtn_q); + msm_gemini_outbuf_q_cleanup(&pgmn_dev->output_buf_q); + msm_gemini_q_cleanup(&pgmn_dev->input_rtn_q); + msm_gemini_outbuf_q_cleanup(&pgmn_dev->input_buf_q); + + if (pgmn_dev->open_count) + GMN_PR_ERR(KERN_ERR "%s: multiple opens\n", __func__); + + msm_gemini_platform_release(pgmn_dev->mem, pgmn_dev->base, + pgmn_dev->irq, pgmn_dev); + + return 0; +} + +int msm_gemini_ioctl_hw_cmd(struct msm_gemini_device *pgmn_dev, + void * __user arg) +{ + struct msm_gemini_hw_cmd hw_cmd; + int is_copy_to_user; + + if (copy_from_user(&hw_cmd, arg, sizeof(struct msm_gemini_hw_cmd))) { + GMN_PR_ERR("%s:%d] failed\n", __func__, __LINE__); + return -EFAULT; + } + + is_copy_to_user = msm_gemini_hw_exec_cmds(&hw_cmd, 1); + GMN_DBG("%s:%d] type %d, n %d, offset %d, mask %x, data %x, pdata %x\n", + __func__, __LINE__, hw_cmd.type, hw_cmd.n, hw_cmd.offset, + hw_cmd.mask, hw_cmd.data, (int) hw_cmd.pdata); + + if (is_copy_to_user >= 0) { + if (copy_to_user(arg, &hw_cmd, sizeof(hw_cmd))) { + GMN_PR_ERR("%s:%d] failed\n", __func__, __LINE__); + return -EFAULT; + } + } + + return 0; +} + +int msm_gemini_ioctl_hw_cmds(struct msm_gemini_device *pgmn_dev, + void * __user arg) +{ + int is_copy_to_user; + int len; + uint32_t m; + struct msm_gemini_hw_cmds *hw_cmds_p; + struct msm_gemini_hw_cmd *hw_cmd_p; + + if (copy_from_user(&m, arg, sizeof(m))) { + GMN_PR_ERR("%s:%d] failed\n", __func__, __LINE__); + return -EFAULT; + } + + len = sizeof(struct msm_gemini_hw_cmds) + + sizeof(struct msm_gemini_hw_cmd) * (m - 1); + hw_cmds_p = kmalloc(len, GFP_KERNEL); + if (!hw_cmds_p) { + GMN_PR_ERR("%s:%d] no mem %d\n", __func__, __LINE__, len); + return -EFAULT; + } + + if (copy_from_user(hw_cmds_p, arg, len)) { + GMN_PR_ERR("%s:%d] failed\n", __func__, __LINE__); + kfree(hw_cmds_p); + return -EFAULT; + } + + hw_cmd_p = (struct msm_gemini_hw_cmd *) &(hw_cmds_p->hw_cmd); + + is_copy_to_user = msm_gemini_hw_exec_cmds(hw_cmd_p, m); + + if (is_copy_to_user >= 0) { + if (copy_to_user(arg, hw_cmds_p, len)) { + GMN_PR_ERR("%s:%d] failed\n", __func__, __LINE__); + kfree(hw_cmds_p); + return -EFAULT; + } + } + kfree(hw_cmds_p); + return 0; +} + +int msm_gemini_start(struct msm_gemini_device *pgmn_dev, void * __user arg) +{ + struct msm_gemini_core_buf *buf_out; + struct msm_gemini_core_buf *buf_out_free[2] = {NULL, NULL}; + int i, rc; + + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + + release_buf = 1; + for (i = 0; i < 2; i++) { + buf_out = msm_gemini_q_out(&pgmn_dev->input_buf_q); + + if (buf_out) { + msm_gemini_core_fe_buf_update(buf_out); + kfree(buf_out); + } else { + GMN_DBG("%s:%d] no input buffer\n", __func__, __LINE__); + break; + } + } + + for (i = 0; i < 2; i++) { + buf_out_free[i] = msm_gemini_q_out(&pgmn_dev->output_buf_q); + + if (buf_out_free[i]) { + msm_gemini_core_we_buf_update(buf_out_free[i]); + } else if (i == 1) { + /* set the pong to same address as ping */ + buf_out_free[0]->y_len >>= 1; + buf_out_free[0]->y_buffer_addr += + buf_out_free[0]->y_len; + msm_gemini_core_we_buf_update(buf_out_free[0]); + /* since ping and pong are same buf release only once*/ + release_buf = 0; + } else { + GMN_DBG("%s:%d] no output buffer\n", + __func__, __LINE__); + break; + } + } + + for (i = 0; i < 2; i++) + kfree(buf_out_free[i]); + + rc = msm_gemini_ioctl_hw_cmds(pgmn_dev, arg); + GMN_DBG("%s:%d]\n", __func__, __LINE__); + return rc; +} + +int msm_gemini_ioctl_reset(struct msm_gemini_device *pgmn_dev, + void * __user arg) +{ + int rc; + struct msm_gemini_ctrl_cmd ctrl_cmd; + + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + if (copy_from_user(&ctrl_cmd, arg, sizeof(ctrl_cmd))) { + GMN_PR_ERR("%s:%d] failed\n", __func__, __LINE__); + return -EFAULT; + } + + pgmn_dev->op_mode = ctrl_cmd.type; + + rc = msm_gemini_core_reset(pgmn_dev->op_mode, pgmn_dev->base, + resource_size(pgmn_dev->mem)); + return rc; +} + +int msm_gemini_ioctl_test_dump_region(struct msm_gemini_device *pgmn_dev, + unsigned long arg) +{ + GMN_DBG("%s:%d] Enter\n", __func__, __LINE__); + msm_gemini_hw_region_dump(arg); + return 0; +} + +long __msm_gemini_ioctl(struct msm_gemini_device *pgmn_dev, + unsigned int cmd, unsigned long arg) +{ + int rc = 0; + switch (cmd) { + case MSM_GMN_IOCTL_GET_HW_VERSION: + GMN_DBG("%s:%d] VERSION 1\n", __func__, __LINE__); + rc = msm_gemini_ioctl_hw_cmd(pgmn_dev, (void __user *) arg); + break; + + case MSM_GMN_IOCTL_RESET: + rc = msm_gemini_ioctl_reset(pgmn_dev, (void __user *) arg); + break; + + case MSM_GMN_IOCTL_STOP: + rc = msm_gemini_ioctl_hw_cmds(pgmn_dev, (void __user *) arg); + break; + + case MSM_GMN_IOCTL_START: + rc = msm_gemini_start(pgmn_dev, (void __user *) arg); + break; + + case MSM_GMN_IOCTL_INPUT_BUF_ENQUEUE: + rc = msm_gemini_input_buf_enqueue(pgmn_dev, + (void __user *) arg); + break; + + case MSM_GMN_IOCTL_INPUT_GET: + rc = msm_gemini_input_get(pgmn_dev, (void __user *) arg); + break; + + case MSM_GMN_IOCTL_INPUT_GET_UNBLOCK: + rc = msm_gemini_input_get_unblock(pgmn_dev); + break; + + case MSM_GMN_IOCTL_OUTPUT_BUF_ENQUEUE: + rc = msm_gemini_output_buf_enqueue(pgmn_dev, + (void __user *) arg); + break; + + case MSM_GMN_IOCTL_OUTPUT_GET: + rc = msm_gemini_output_get(pgmn_dev, (void __user *) arg); + break; + + case MSM_GMN_IOCTL_OUTPUT_GET_UNBLOCK: + rc = msm_gemini_output_get_unblock(pgmn_dev); + break; + + case MSM_GMN_IOCTL_EVT_GET: + rc = msm_gemini_evt_get(pgmn_dev, (void __user *) arg); + break; + + case MSM_GMN_IOCTL_EVT_GET_UNBLOCK: + rc = msm_gemini_evt_get_unblock(pgmn_dev); + break; + + case MSM_GMN_IOCTL_HW_CMD: + rc = msm_gemini_ioctl_hw_cmd(pgmn_dev, (void __user *) arg); + break; + + case MSM_GMN_IOCTL_HW_CMDS: + rc = msm_gemini_ioctl_hw_cmds(pgmn_dev, (void __user *) arg); + break; + + case MSM_GMN_IOCTL_TEST_DUMP_REGION: + rc = msm_gemini_ioctl_test_dump_region(pgmn_dev, arg); + break; + + default: + GMN_PR_ERR(KERN_INFO "%s:%d] cmd = %d not supported\n", + __func__, __LINE__, _IOC_NR(cmd)); + rc = -EINVAL; + break; + } + return rc; +} + +struct msm_gemini_device *__msm_gemini_init(struct platform_device *pdev) +{ + struct msm_gemini_device *pgmn_dev; + + pgmn_dev = kzalloc(sizeof(struct msm_gemini_device), GFP_ATOMIC); + if (!pgmn_dev) { + GMN_PR_ERR("%s:%d]no mem\n", __func__, __LINE__); + return NULL; + } + + mutex_init(&pgmn_dev->lock); + + pgmn_dev->pdev = pdev; + + msm_gemini_q_init("evt_q", &pgmn_dev->evt_q); + msm_gemini_q_init("output_rtn_q", &pgmn_dev->output_rtn_q); + msm_gemini_q_init("output_buf_q", &pgmn_dev->output_buf_q); + msm_gemini_q_init("input_rtn_q", &pgmn_dev->input_rtn_q); + msm_gemini_q_init("input_buf_q", &pgmn_dev->input_buf_q); + + return pgmn_dev; +} + +int __msm_gemini_exit(struct msm_gemini_device *pgmn_dev) +{ + mutex_destroy(&pgmn_dev->lock); + kfree(pgmn_dev); + return 0; +} + diff --git a/drivers/media/video/msm/msm_gemini_sync.h b/drivers/media/video/msm/msm_gemini_sync.h new file mode 100644 index 00000000000..6c69a9212d4 --- /dev/null +++ b/drivers/media/video/msm/msm_gemini_sync.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MSM_GEMINI_SYNC_H +#define MSM_GEMINI_SYNC_H + +#include +#include +#include +#include +#include "msm_gemini_core.h" + +struct msm_gemini_q { + char const *name; + struct list_head q; + spinlock_t lck; + wait_queue_head_t wait; + int unblck; +}; + +struct msm_gemini_q_entry { + struct list_head list; + void *data; +}; + +struct msm_gemini_device { + struct platform_device *pdev; + struct resource *mem; + int irq; + void *base; + + struct device *device; + struct cdev cdev; + struct mutex lock; + char open_count; + uint8_t op_mode; + + /* event queue including frame done & err indications + */ + struct msm_gemini_q evt_q; + + /* output return queue + */ + struct msm_gemini_q output_rtn_q; + + /* output buf queue + */ + struct msm_gemini_q output_buf_q; + + /* input return queue + */ + struct msm_gemini_q input_rtn_q; + + /* input buf queue + */ + struct msm_gemini_q input_buf_q; +}; + +int __msm_gemini_open(struct msm_gemini_device *pgmn_dev); +int __msm_gemini_release(struct msm_gemini_device *pgmn_dev); + +long __msm_gemini_ioctl(struct msm_gemini_device *pgmn_dev, + unsigned int cmd, unsigned long arg); + +struct msm_gemini_device *__msm_gemini_init(struct platform_device *pdev); +int __msm_gemini_exit(struct msm_gemini_device *pgmn_dev); + +#endif /* MSM_GEMINI_SYNC_H */ diff --git a/drivers/media/video/msm/msm_io7x.c b/drivers/media/video/msm/msm_io7x.c new file mode 100644 index 00000000000..1befec693cb --- /dev/null +++ b/drivers/media/video/msm/msm_io7x.c @@ -0,0 +1,318 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define CAMIF_CFG_RMSK 0x1fffff +#define CAM_SEL_BMSK 0x2 +#define CAM_PCLK_SRC_SEL_BMSK 0x60000 +#define CAM_PCLK_INVERT_BMSK 0x80000 +#define CAM_PAD_REG_SW_RESET_BMSK 0x100000 + +#define EXT_CAM_HSYNC_POL_SEL_BMSK 0x10000 +#define EXT_CAM_VSYNC_POL_SEL_BMSK 0x8000 +#define MDDI_CLK_CHICKEN_BIT_BMSK 0x80 + +#define CAM_SEL_SHFT 0x1 +#define CAM_PCLK_SRC_SEL_SHFT 0x11 +#define CAM_PCLK_INVERT_SHFT 0x13 +#define CAM_PAD_REG_SW_RESET_SHFT 0x14 + +#define EXT_CAM_HSYNC_POL_SEL_SHFT 0x10 +#define EXT_CAM_VSYNC_POL_SEL_SHFT 0xF +#define MDDI_CLK_CHICKEN_BIT_SHFT 0x7 +#define APPS_RESET_OFFSET 0x00000210 + +static struct clk *camio_vfe_mdc_clk; +static struct clk *camio_mdc_clk; +static struct clk *camio_vfe_clk; + +static struct msm_camera_io_ext camio_ext; +static struct resource *appio, *mdcio; +void __iomem *appbase, *mdcbase; + +static struct resource *appio, *mdcio; +void __iomem *appbase, *mdcbase; + +int msm_camio_clk_enable(enum msm_camio_clk_type clktype) +{ + int rc = -1; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_VFE_MDC_CLK: + clk = camio_vfe_mdc_clk = clk_get(NULL, "vfe_mdc_clk"); + break; + + case CAMIO_MDC_CLK: + clk = camio_mdc_clk = clk_get(NULL, "mdc_clk"); + break; + + case CAMIO_VFE_CLK: + clk = camio_vfe_clk = clk_get(NULL, "vfe_clk"); + break; + + default: + break; + } + + if (!IS_ERR(clk)) { + clk_enable(clk); + rc = 0; + } + + return rc; +} + +int msm_camio_clk_disable(enum msm_camio_clk_type clktype) +{ + int rc = -1; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_VFE_MDC_CLK: + clk = camio_vfe_mdc_clk; + break; + + case CAMIO_MDC_CLK: + clk = camio_mdc_clk; + break; + + case CAMIO_VFE_CLK: + clk = camio_vfe_clk; + break; + + default: + break; + } + + if (!IS_ERR(clk)) { + clk_disable(clk); + clk_put(clk); + rc = 0; + } + + return rc; +} + +void msm_camio_clk_rate_set(int rate) +{ + struct clk *clk = camio_vfe_clk; + + if (clk != ERR_PTR(-ENOENT)) + clk_set_rate(clk, rate); +} + +int msm_camio_enable(struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + + camio_ext = camdev->ioext; + + appio = request_mem_region(camio_ext.appphy, + camio_ext.appsz, pdev->name); + if (!appio) { + rc = -EBUSY; + goto enable_fail; + } + + appbase = ioremap(camio_ext.appphy, + camio_ext.appsz); + if (!appbase) { + rc = -ENOMEM; + goto apps_no_mem; + } + + msm_camio_clk_enable(CAMIO_VFE_CLK); + msm_camio_clk_enable(CAMIO_MDC_CLK); + return 0; +apps_no_mem: + release_mem_region(camio_ext.appphy, camio_ext.appsz); +enable_fail: + return rc; +} + +int msm_camio_sensor_clk_on(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + int32_t rc = 0; + camio_ext = camdev->ioext; + mdcio = request_mem_region(camio_ext.mdcphy, + camio_ext.mdcsz, pdev->name); + if (!mdcio) + rc = -EBUSY; + mdcbase = ioremap(camio_ext.mdcphy, + camio_ext.mdcsz); + if (!mdcbase) { + rc = -EINVAL; + goto mdc_no_mem; + } + camdev->camera_gpio_on(); + return msm_camio_clk_enable(CAMIO_VFE_MDC_CLK); + +mdc_no_mem: + release_mem_region(camio_ext.mdcphy, camio_ext.mdcsz); + return rc; +} + +int msm_camio_sensor_clk_off(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camdev->camera_gpio_off(); + iounmap(mdcbase); + release_mem_region(camio_ext.mdcphy, camio_ext.mdcsz); + return msm_camio_clk_disable(CAMIO_VFE_MDC_CLK); +} + +void msm_camio_disable(struct platform_device *pdev) +{ + iounmap(appbase); + release_mem_region(camio_ext.appphy, camio_ext.appsz); + msm_camio_clk_disable(CAMIO_VFE_CLK); + msm_camio_clk_disable(CAMIO_MDC_CLK); +} + +void msm_disable_io_gpio_clk(struct platform_device *pdev) +{ + return; +} + +void msm_camio_camif_pad_reg_reset(void) +{ + uint32_t reg; + uint32_t mask, value; + + /* select CLKRGM_VFE_SRC_CAM_VFE_SRC: internal source */ + msm_camio_clk_sel(MSM_CAMIO_CLK_SRC_INTERNAL); + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + + mask = CAM_SEL_BMSK | + CAM_PCLK_SRC_SEL_BMSK | + CAM_PCLK_INVERT_BMSK; + + value = 1 << CAM_SEL_SHFT | + 3 << CAM_PCLK_SRC_SEL_SHFT | + 0 << CAM_PCLK_INVERT_SHFT; + + writel((reg & (~mask)) | (value & mask), mdcbase); + msleep(10); + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 1 << CAM_PAD_REG_SW_RESET_SHFT; + writel((reg & (~mask)) | (value & mask), mdcbase); + msleep(10); + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 0 << CAM_PAD_REG_SW_RESET_SHFT; + writel((reg & (~mask)) | (value & mask), mdcbase); + msleep(10); + + msm_camio_clk_sel(MSM_CAMIO_CLK_SRC_EXTERNAL); + msleep(10); +} + +void msm_camio_vfe_blk_reset(void) +{ + uint32_t val; + + /* do apps reset */ + val = readl(appbase + 0x00000210); + val |= 0x1; + writel(val, appbase + 0x00000210); + mdelay(10); + + val = readl(appbase + 0x00000210); + val &= ~0x1; + writel(val, appbase + 0x00000210); + mdelay(10); + + /* do axi reset */ + val = readl(appbase + 0x00000208); + val |= 0x1; + writel(val, appbase + 0x00000208); + mdelay(10); + + val = readl(appbase + 0x00000208); + val &= ~0x1; + writel(val, appbase + 0x00000208); + mdelay(10); +} + +void msm_camio_camif_pad_reg_reset_2(void) +{ + uint32_t reg; + uint32_t mask, value; + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 1 << CAM_PAD_REG_SW_RESET_SHFT; + writel((reg & (~mask)) | (value & mask), mdcbase); + mdelay(10); + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 0 << CAM_PAD_REG_SW_RESET_SHFT; + writel((reg & (~mask)) | (value & mask), mdcbase); + mdelay(10); +} + +void msm_camio_clk_sel(enum msm_camio_clk_src_type srctype) +{ + struct clk *clk = NULL; + + clk = camio_vfe_clk; + + if (clk != NULL && clk != ERR_PTR(-ENOENT)) { + switch (srctype) { + case MSM_CAMIO_CLK_SRC_INTERNAL: + clk_set_flags(clk, 0x00000100 << 1); + break; + + case MSM_CAMIO_CLK_SRC_EXTERNAL: + clk_set_flags(clk, 0x00000100); + break; + + default: + break; + } + } +} + +int msm_camio_probe_on(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camdev->camera_gpio_on(); + return msm_camio_clk_enable(CAMIO_VFE_CLK); +} + +int msm_camio_probe_off(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camdev->camera_gpio_off(); + return msm_camio_clk_disable(CAMIO_VFE_CLK); +} diff --git a/drivers/media/video/msm/msm_io8x.c b/drivers/media/video/msm/msm_io8x.c new file mode 100644 index 00000000000..6bc92b0d7dd --- /dev/null +++ b/drivers/media/video/msm/msm_io8x.c @@ -0,0 +1,331 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define CAMIF_CFG_RMSK 0x1fffff +#define CAM_SEL_BMSK 0x2 +#define CAM_PCLK_SRC_SEL_BMSK 0x60000 +#define CAM_PCLK_INVERT_BMSK 0x80000 +#define CAM_PAD_REG_SW_RESET_BMSK 0x100000 + +#define EXT_CAM_HSYNC_POL_SEL_BMSK 0x10000 +#define EXT_CAM_VSYNC_POL_SEL_BMSK 0x8000 +#define MDDI_CLK_CHICKEN_BIT_BMSK 0x80 + +#define CAM_SEL_SHFT 0x1 +#define CAM_PCLK_SRC_SEL_SHFT 0x11 +#define CAM_PCLK_INVERT_SHFT 0x13 +#define CAM_PAD_REG_SW_RESET_SHFT 0x14 + +#define EXT_CAM_HSYNC_POL_SEL_SHFT 0x10 +#define EXT_CAM_VSYNC_POL_SEL_SHFT 0xF +#define MDDI_CLK_CHICKEN_BIT_SHFT 0x7 +#define APPS_RESET_OFFSET 0x00000214 + +static struct clk *camio_vfe_mdc_clk; +static struct clk *camio_mdc_clk; +static struct clk *camio_vfe_clk; +static struct clk *camio_vfe_axi_clk; +static struct msm_camera_io_ext camio_ext; +static struct resource *appio, *mdcio; + +void __iomem *appbase, *mdcbase; + + +int msm_camio_clk_enable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_VFE_MDC_CLK: + camio_vfe_mdc_clk = clk = clk_get(NULL, "vfe_mdc_clk"); + break; + + case CAMIO_MDC_CLK: + camio_mdc_clk = clk = clk_get(NULL, "mdc_clk"); + break; + + case CAMIO_VFE_CLK: + camio_vfe_clk = clk = clk_get(NULL, "vfe_clk"); + break; + + case CAMIO_VFE_AXI_CLK: + camio_vfe_axi_clk = clk = clk_get(NULL, "vfe_axi_clk"); + break; + + default: + break; + } + + if (!IS_ERR(clk)) + clk_enable(clk); + else + rc = -1; + + return rc; +} + +int msm_camio_clk_disable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_VFE_MDC_CLK: + clk = camio_vfe_mdc_clk; + break; + + case CAMIO_MDC_CLK: + clk = camio_mdc_clk; + break; + + case CAMIO_VFE_CLK: + clk = camio_vfe_clk; + break; + + case CAMIO_VFE_AXI_CLK: + clk = camio_vfe_axi_clk; + break; + + default: + break; + } + + if (!IS_ERR(clk)) { + clk_disable(clk); + clk_put(clk); + } else + rc = -1; + + return rc; +} + +void msm_camio_clk_rate_set(int rate) +{ + struct clk *clk = camio_vfe_mdc_clk; + + /* TODO: check return */ + clk_set_rate(clk, rate); +} + +int msm_camio_enable(struct platform_device *pdev) +{ + int rc = 0; + + appio = request_mem_region(camio_ext.appphy, + camio_ext.appsz, pdev->name); + if (!appio) { + rc = -EBUSY; + goto enable_fail; + } + + appbase = ioremap(camio_ext.appphy, camio_ext.appsz); + if (!appbase) { + rc = -ENOMEM; + goto apps_no_mem; + } + msm_camio_clk_enable(CAMIO_MDC_CLK); + msm_camio_clk_enable(CAMIO_VFE_AXI_CLK); + return 0; + +apps_no_mem: + release_mem_region(camio_ext.appphy, camio_ext.appsz); +enable_fail: + return rc; +} + +void msm_camio_disable(struct platform_device *pdev) +{ + iounmap(appbase); + release_mem_region(camio_ext.appphy, camio_ext.appsz); + msm_camio_clk_disable(CAMIO_MDC_CLK); + msm_camio_clk_disable(CAMIO_VFE_AXI_CLK); +} + +int msm_camio_sensor_clk_on(struct platform_device *pdev) +{ + + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + int32_t rc = 0; + camio_ext = camdev->ioext; + + mdcio = request_mem_region(camio_ext.mdcphy, + camio_ext.mdcsz, pdev->name); + if (!mdcio) + rc = -EBUSY; + mdcbase = ioremap(camio_ext.mdcphy, + camio_ext.mdcsz); + if (!mdcbase) + goto mdc_no_mem; + camdev->camera_gpio_on(); + + msm_camio_clk_enable(CAMIO_VFE_CLK); + msm_camio_clk_enable(CAMIO_VFE_MDC_CLK); + return rc; + + +mdc_no_mem: + release_mem_region(camio_ext.mdcphy, camio_ext.mdcsz); + return -EINVAL; +} + +int msm_camio_sensor_clk_off(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camdev->camera_gpio_off(); + iounmap(mdcbase); + release_mem_region(camio_ext.mdcphy, camio_ext.mdcsz); + msm_camio_clk_disable(CAMIO_VFE_CLK); + return msm_camio_clk_disable(CAMIO_VFE_MDC_CLK); + +} + +void msm_disable_io_gpio_clk(struct platform_device *pdev) +{ + return; +} + +void msm_camio_camif_pad_reg_reset(void) +{ + uint32_t reg; + uint32_t mask, value; + + /* select CLKRGM_VFE_SRC_CAM_VFE_SRC: internal source */ + msm_camio_clk_sel(MSM_CAMIO_CLK_SRC_INTERNAL); + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + + mask = CAM_SEL_BMSK | + CAM_PCLK_SRC_SEL_BMSK | + CAM_PCLK_INVERT_BMSK | + EXT_CAM_HSYNC_POL_SEL_BMSK | + EXT_CAM_VSYNC_POL_SEL_BMSK | MDDI_CLK_CHICKEN_BIT_BMSK; + + value = 1 << CAM_SEL_SHFT | + 3 << CAM_PCLK_SRC_SEL_SHFT | + 0 << CAM_PCLK_INVERT_SHFT | + 0 << EXT_CAM_HSYNC_POL_SEL_SHFT | + 0 << EXT_CAM_VSYNC_POL_SEL_SHFT | 0 << MDDI_CLK_CHICKEN_BIT_SHFT; + writel((reg & (~mask)) | (value & mask), mdcbase); + msleep(10); + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 1 << CAM_PAD_REG_SW_RESET_SHFT; + writel((reg & (~mask)) | (value & mask), mdcbase); + msleep(10); + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 0 << CAM_PAD_REG_SW_RESET_SHFT; + writel((reg & (~mask)) | (value & mask), mdcbase); + msleep(10); + + msm_camio_clk_sel(MSM_CAMIO_CLK_SRC_EXTERNAL); + + msleep(10); + + /* todo: check return */ + if (camio_vfe_clk) + clk_set_rate(camio_vfe_clk, 96000000); +} + +void msm_camio_vfe_blk_reset(void) +{ + uint32_t val; + + val = readl(appbase + APPS_RESET_OFFSET); + val |= 0x1; + writel(val, appbase + APPS_RESET_OFFSET); + mdelay(10); + + val = readl(appbase + APPS_RESET_OFFSET); + val &= ~0x1; + writel(val, appbase + APPS_RESET_OFFSET); + mdelay(10); +} + +void msm_camio_camif_pad_reg_reset_2(void) +{ + uint32_t reg; + uint32_t mask, value; + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 1 << CAM_PAD_REG_SW_RESET_SHFT; + writel((reg & (~mask)) | (value & mask), mdcbase); + mdelay(10); + + reg = (readl(mdcbase)) & CAMIF_CFG_RMSK; + mask = CAM_PAD_REG_SW_RESET_BMSK; + value = 0 << CAM_PAD_REG_SW_RESET_SHFT; + writel((reg & (~mask)) | (value & mask), mdcbase); + mdelay(10); +} + +void msm_camio_clk_sel(enum msm_camio_clk_src_type srctype) +{ + struct clk *clk = NULL; + + clk = camio_vfe_clk; + + if (clk != NULL) { + switch (srctype) { + case MSM_CAMIO_CLK_SRC_INTERNAL: + clk_set_flags(clk, 0x00000100 << 1); + break; + + case MSM_CAMIO_CLK_SRC_EXTERNAL: + clk_set_flags(clk, 0x00000100); + break; + + default: + break; + } + } +} + +void msm_camio_clk_axi_rate_set(int rate) +{ + struct clk *clk = camio_vfe_axi_clk; + /* todo: check return */ + clk_set_rate(clk, rate); +} + +int msm_camio_probe_on(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + + camdev->camera_gpio_on(); + return msm_camio_clk_enable(CAMIO_VFE_MDC_CLK); +} + +int msm_camio_probe_off(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + + camdev->camera_gpio_off(); + return msm_camio_clk_disable(CAMIO_VFE_MDC_CLK); +} diff --git a/drivers/media/video/msm/msm_io_7x27a.c b/drivers/media/video/msm/msm_io_7x27a.c new file mode 100644 index 00000000000..c70cfcac14c --- /dev/null +++ b/drivers/media/video/msm/msm_io_7x27a.c @@ -0,0 +1,612 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* MIPI CSI controller registers */ +#define MIPI_PHY_CONTROL 0x00000000 +#define MIPI_PROTOCOL_CONTROL 0x00000004 +#define MIPI_INTERRUPT_STATUS 0x00000008 +#define MIPI_INTERRUPT_MASK 0x0000000C +#define MIPI_CAMERA_CNTL 0x00000024 +#define MIPI_CALIBRATION_CONTROL 0x00000018 +#define MIPI_PHY_D0_CONTROL2 0x00000038 +#define MIPI_PHY_D1_CONTROL2 0x0000003C +#define MIPI_PHY_D2_CONTROL2 0x00000040 +#define MIPI_PHY_D3_CONTROL2 0x00000044 +#define MIPI_PHY_CL_CONTROL 0x00000048 +#define MIPI_PHY_D0_CONTROL 0x00000034 +#define MIPI_PHY_D1_CONTROL 0x00000020 +#define MIPI_PHY_D2_CONTROL 0x0000002C +#define MIPI_PHY_D3_CONTROL 0x00000030 +#define MIPI_PWR_CNTL 0x00000054 + +/* + * MIPI_PROTOCOL_CONTROL register bits to enable/disable the features of + * CSI Rx Block + */ + +/* DPCM scheme */ +#define MIPI_PROTOCOL_CONTROL_DPCM_SCHEME_SHFT 0x1e +/* SW_RST to issue a SW reset to the CSI core */ +#define MIPI_PROTOCOL_CONTROL_SW_RST_BMSK 0x8000000 +/* To Capture Long packet Header Info in MIPI_PROTOCOL_STATUS register */ +#define MIPI_PROTOCOL_CONTROL_LONG_PACKET_HEADER_CAPTURE_BMSK 0x200000 +/* Data format for unpacking purpose */ +#define MIPI_PROTOCOL_CONTROL_DATA_FORMAT_SHFT 0x13 +/* Enable decoding of payload based on data type filed of packet hdr */ +#define MIPI_PROTOCOL_CONTROL_DECODE_ID_BMSK 0x00000 +/* Enable error correction on packet headers */ +#define MIPI_PROTOCOL_CONTROL_ECC_EN_BMSK 0x20000 + +/* + * MIPI_CALIBRATION_CONTROL register contains control info for + * calibration impledence controller +*/ + +/* Enable bit for calibration pad */ +#define MIPI_CALIBRATION_CONTROL_SWCAL_CAL_EN_SHFT 0x16 +/* With SWCAL_STRENGTH_OVERRIDE_EN, SW_CAL_EN and MANUAL_OVERRIDE_EN + * the hardware calibration circuitry associated with CAL_SW_HW_MODE + * is bypassed +*/ +#define MIPI_CALIBRATION_CONTROL_SWCAL_STRENGTH_OVERRIDE_EN_SHFT 0x15 +/* To indicate the Calibration process is in the control of HW/SW */ +#define MIPI_CALIBRATION_CONTROL_CAL_SW_HW_MODE_SHFT 0x14 +/* When this is set the strength value of the data and clk lane impedence + * termination is updated with MANUAL_STRENGTH settings and calibration + * sensing logic is idle. +*/ +#define MIPI_CALIBRATION_CONTROL_MANUAL_OVERRIDE_EN_SHFT 0x7 + +/* Data lane0 control */ +/* T-hs Settle count value for Rx */ +#define MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT 0x18 +/* Rx termination control */ +#define MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT 0x10 +/* LP Rx enable */ +#define MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT 0x4 +/* + * Enable for error in sync sequence + * 1 - one bit error in sync seq + * 0 - requires all 8 bit correct seq +*/ +#define MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 + +/* Comments are same as D0 */ +#define MIPI_PHY_D1_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D1_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D1_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D1_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 + +/* Comments are same as D0 */ +#define MIPI_PHY_D2_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D2_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D2_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D2_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 + +/* Comments are same as D0 */ +#define MIPI_PHY_D3_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D3_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D3_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D3_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 + +/* PHY_CL_CTRL programs the parameters of clk lane of CSIRXPHY */ +/* HS Rx termination control */ +#define MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT 0x18 +/* Start signal for T-hs delay */ +#define MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT 0x2 + +/* PHY DATA lane 0 control */ +/* + * HS RX equalizer strength control + * 00 - 0db 01 - 3db 10 - 5db 11 - 7db +*/ +#define MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT 0x1c + +/* PHY DATA lane 1 control */ +/* Shutdown signal for MIPI clk phy line */ +#define MIPI_PHY_D1_CONTROL_MIPI_CLK_PHY_SHUTDOWNB_SHFT 0x9 +/* Shutdown signal for MIPI data phy line */ +#define MIPI_PHY_D1_CONTROL_MIPI_DATA_PHY_SHUTDOWNB_SHFT 0x8 + +#define MSM_AXI_QOS_PREVIEW 200000 +#define MSM_AXI_QOS_SNAPSHOT 200000 +#define MSM_AXI_QOS_RECORDING 200000 + +#define MIPI_PWR_CNTL_ENA 0x07 +#define MIPI_PWR_CNTL_DIS 0x0 + +static struct clk *camio_cam_clk; +static struct clk *camio_vfe_clk; +static struct clk *camio_csi_src_clk; +static struct clk *camio_csi0_vfe_clk; +static struct clk *camio_csi1_vfe_clk; +static struct clk *camio_csi0_clk; +static struct clk *camio_csi1_clk; +static struct clk *camio_csi0_pclk; +static struct clk *camio_csi1_pclk; + +static struct msm_camera_io_ext camio_ext; +static struct msm_camera_io_clk camio_clk; +static struct platform_device *camio_dev; +void __iomem *csibase; +void __iomem *appbase; + + + +void msm_io_w(u32 data, void __iomem *addr) +{ + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + writel((data), (addr)); +} + +u32 msm_io_r(void __iomem *addr) +{ + uint32_t data = readl(addr); + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + return data; +} + +void msm_camio_vfe_clk_rate_set(int rate) +{ + struct clk *clk = camio_vfe_clk; + if (rate > clk_get_rate(clk)) + clk_set_rate(clk, rate); +} + +int msm_camio_clk_enable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_CAM_MCLK_CLK: + clk = clk_get(NULL, "cam_m_clk"); + camio_cam_clk = clk; + msm_camio_clk_rate_set_2(clk, camio_clk.mclk_clk_rate); + break; + case CAMIO_VFE_CLK: + clk = clk_get(NULL, "vfe_clk"); + camio_vfe_clk = clk; + msm_camio_clk_rate_set_2(clk, camio_clk.vfe_clk_rate); + break; + case CAMIO_CSI0_VFE_CLK: + clk = clk_get(&camio_dev->dev, "csi_vfe_clk"); + camio_csi0_vfe_clk = clk; + break; + case CAMIO_CSI1_VFE_CLK: + clk = clk_get(NULL, "csi_vfe_clk"); + camio_csi1_vfe_clk = clk; + break; + case CAMIO_CSI_SRC_CLK: + clk = clk_get(NULL, "csi_src_clk"); + camio_csi_src_clk = clk; + break; + case CAMIO_CSI0_CLK: + clk = clk_get(&camio_dev->dev, "csi_clk"); + camio_csi0_clk = clk; + msm_camio_clk_rate_set_2(clk, 400000000); + break; + case CAMIO_CSI1_CLK: + clk = clk_get(NULL, "csi_clk"); + camio_csi1_clk = clk; + break; + case CAMIO_CSI0_PCLK: + clk = clk_get(&camio_dev->dev, "csi_pclk"); + camio_csi0_pclk = clk; + break; + case CAMIO_CSI1_PCLK: + clk = clk_get(NULL, "csi_pclk"); + camio_csi1_pclk = clk; + break; + default: + break; + } + + if (!IS_ERR(clk)) + clk_enable(clk); + else + rc = -1; + return rc; +} + +int msm_camio_clk_disable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_CAM_MCLK_CLK: + clk = camio_cam_clk; + break; + case CAMIO_VFE_CLK: + clk = camio_vfe_clk; + break; + case CAMIO_CSI_SRC_CLK: + clk = camio_csi_src_clk; + break; + case CAMIO_CSI0_VFE_CLK: + clk = camio_csi0_vfe_clk; + break; + case CAMIO_CSI1_VFE_CLK: + clk = camio_csi1_vfe_clk; + break; + case CAMIO_CSI0_CLK: + clk = camio_csi0_clk; + break; + case CAMIO_CSI1_CLK: + clk = camio_csi1_clk; + break; + case CAMIO_CSI0_PCLK: + clk = camio_csi0_pclk; + break; + case CAMIO_CSI1_PCLK: + clk = camio_csi1_pclk; + break; + default: + break; + } + + if (!IS_ERR(clk)) { + clk_disable(clk); + clk_put(clk); + } else + rc = -1; + return rc; +} + +void msm_camio_clk_rate_set(int rate) +{ + struct clk *clk = camio_cam_clk; + clk_set_rate(clk, rate); +} + +void msm_camio_clk_rate_set_2(struct clk *clk, int rate) +{ + clk_set_rate(clk, rate); +} + +void msm_camio_clk_set_min_rate(struct clk *clk, int rate) +{ + clk_set_min_rate(clk, rate); +} + +static irqreturn_t msm_io_csi_irq(int irq_num, void *data) +{ + uint32_t irq; + + irq = msm_io_r(csibase + MIPI_INTERRUPT_STATUS); + CDBG("%s MIPI_INTERRUPT_STATUS = 0x%x\n", __func__, irq); + msm_io_w(irq, csibase + MIPI_INTERRUPT_STATUS); + + /* TODO: Needs to send this info to upper layers */ + if ((irq >> 19) & 0x1) + pr_info("Unsupported packet format is received\n"); + return IRQ_HANDLED; +} + +int msm_camio_enable(struct platform_device *pdev) +{ + int rc = 0; + const struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + uint32_t val; + + camio_dev = pdev; + camio_ext = camdev->ioext; + camio_clk = camdev->ioclk; + + msm_camio_clk_enable(CAMIO_VFE_CLK); + msm_camio_clk_enable(CAMIO_CSI0_VFE_CLK); + msm_camio_clk_enable(CAMIO_CSI1_VFE_CLK); + msm_camio_clk_enable(CAMIO_CSI0_CLK); + msm_camio_clk_enable(CAMIO_CSI1_CLK); + msm_camio_clk_enable(CAMIO_CSI0_PCLK); + msm_camio_clk_enable(CAMIO_CSI1_PCLK); + + csibase = ioremap(camio_ext.csiphy, camio_ext.csisz); + if (!csibase) { + rc = -ENOMEM; + goto csi_busy; + } + rc = request_irq(camio_ext.csiirq, msm_io_csi_irq, + IRQF_TRIGGER_RISING, "csi", 0); + if (rc < 0) + goto csi_irq_fail; + + msleep(20); + val = (20 << + MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT) | + (0x0F << MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT); + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + + val = (0x0F << MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + + appbase = ioremap(camio_ext.appphy, + camio_ext.appsz); + if (!appbase) { + rc = -ENOMEM; + goto csi_irq_fail; + } + return 0; + +csi_irq_fail: + iounmap(csibase); +csi_busy: + msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); + msm_camio_clk_disable(CAMIO_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI0_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI1_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI0_CLK); + msm_camio_clk_disable(CAMIO_CSI1_CLK); + msm_camio_clk_disable(CAMIO_CSI0_PCLK); + msm_camio_clk_disable(CAMIO_CSI1_PCLK); + camdev->camera_gpio_off(); + return rc; +} + +void msm_camio_disable(struct platform_device *pdev) +{ + uint32_t val; + + val = (20 << + MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT) | + (0x0F << MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT); + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + + val = (0x0F << MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + msleep(20); + + free_irq(camio_ext.csiirq, 0); + iounmap(csibase); + iounmap(appbase); + CDBG("disable clocks\n"); + + msm_camio_clk_disable(CAMIO_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI0_CLK); + msm_camio_clk_disable(CAMIO_CSI1_CLK); + msm_camio_clk_disable(CAMIO_CSI0_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI1_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI0_PCLK); + msm_camio_clk_disable(CAMIO_CSI1_PCLK); +} + +int msm_camio_sensor_clk_on(struct platform_device *pdev) +{ + int rc = 0; + const struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camio_dev = pdev; + camio_ext = camdev->ioext; + camio_clk = camdev->ioclk; + + rc = camdev->camera_gpio_on(); + if (rc < 0) + return rc; + return msm_camio_clk_enable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_sensor_clk_off(struct platform_device *pdev) +{ + const struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camdev->camera_gpio_off(); + return msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); + +} + +void msm_camio_vfe_blk_reset(void) +{ + uint32_t val; + + /* do apps reset */ + val = readl_relaxed(appbase + 0x00000210); + val |= 0x1; + writel_relaxed(val, appbase + 0x00000210); + usleep_range(10000, 11000); + + val = readl_relaxed(appbase + 0x00000210); + val &= ~0x1; + writel_relaxed(val, appbase + 0x00000210); + usleep_range(10000, 11000); + + /* do axi reset */ + val = readl_relaxed(appbase + 0x00000208); + val |= 0x1; + writel_relaxed(val, appbase + 0x00000208); + usleep_range(10000, 11000); + + val = readl_relaxed(appbase + 0x00000208); + val &= ~0x1; + writel_relaxed(val, appbase + 0x00000208); + mb(); + usleep_range(10000, 11000); + return; +} + +int msm_camio_probe_on(struct platform_device *pdev) +{ + int rc = 0; + const struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camio_dev = pdev; + camio_ext = camdev->ioext; + camio_clk = camdev->ioclk; + + msm_camio_clk_enable(CAMIO_CSI0_PCLK); + msm_camio_clk_enable(CAMIO_CSI1_PCLK); + + rc = camdev->camera_gpio_on(); + if (rc < 0) + return rc; + return msm_camio_clk_enable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_probe_off(struct platform_device *pdev) +{ + const struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camdev->camera_gpio_off(); + + csibase = ioremap(camdev->ioext.csiphy, camdev->ioext.csisz); + if (!csibase) { + pr_err("ioremap failed for CSIBASE\n"); + goto ioremap_fail; + } + msm_io_w(MIPI_PWR_CNTL_DIS, csibase + MIPI_PWR_CNTL); + iounmap(csibase); +ioremap_fail: + msm_camio_clk_disable(CAMIO_CSI0_PCLK); + msm_camio_clk_disable(CAMIO_CSI1_PCLK); + return msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_csi_config(struct msm_camera_csi_params *csi_params) +{ + int rc = 0; + uint32_t val = 0; + + CDBG("msm_camio_csi_config\n"); + + /* Enable error correction for DATA lane. Applies to all data lanes */ + msm_io_w(0x4, csibase + MIPI_PHY_CONTROL); + + msm_io_w(MIPI_PROTOCOL_CONTROL_SW_RST_BMSK, + csibase + MIPI_PROTOCOL_CONTROL); + + val = MIPI_PROTOCOL_CONTROL_LONG_PACKET_HEADER_CAPTURE_BMSK | + MIPI_PROTOCOL_CONTROL_DECODE_ID_BMSK | + MIPI_PROTOCOL_CONTROL_ECC_EN_BMSK; + val |= (uint32_t)(csi_params->data_format) << + MIPI_PROTOCOL_CONTROL_DATA_FORMAT_SHFT; + val |= csi_params->dpcm_scheme << + MIPI_PROTOCOL_CONTROL_DPCM_SCHEME_SHFT; + CDBG("%s MIPI_PROTOCOL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PROTOCOL_CONTROL); + + val = (0x1 << MIPI_CALIBRATION_CONTROL_SWCAL_CAL_EN_SHFT) | + (0x1 << + MIPI_CALIBRATION_CONTROL_SWCAL_STRENGTH_OVERRIDE_EN_SHFT) | + (0x1 << MIPI_CALIBRATION_CONTROL_CAL_SW_HW_MODE_SHFT) | + (0x1 << MIPI_CALIBRATION_CONTROL_MANUAL_OVERRIDE_EN_SHFT); + CDBG("%s MIPI_CALIBRATION_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_CALIBRATION_CONTROL); + + val = (csi_params->settle_cnt << + MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT) | + (0x0F << MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT); + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + + + val = (0x0F << MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT) | + (0x1 << MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + + val = 0 << MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT; + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL); + + val = (0x1 << MIPI_PHY_D1_CONTROL_MIPI_CLK_PHY_SHUTDOWNB_SHFT) | + (0x1 << MIPI_PHY_D1_CONTROL_MIPI_DATA_PHY_SHUTDOWNB_SHFT); + CDBG("%s MIPI_PHY_D1_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL); + + msm_io_w(0x00000000, csibase + MIPI_PHY_D2_CONTROL); + msm_io_w(0x00000000, csibase + MIPI_PHY_D3_CONTROL); + + /* program number of lanes and lane mapping */ + switch (csi_params->lane_cnt) { + case 1: + msm_io_w(csi_params->lane_assign << 8 | 0x4, + csibase + MIPI_CAMERA_CNTL); + break; + case 2: + msm_io_w(csi_params->lane_assign << 8 | 0x5, + csibase + MIPI_CAMERA_CNTL); + break; + case 3: + msm_io_w(csi_params->lane_assign << 8 | 0x6, + csibase + MIPI_CAMERA_CNTL); + break; + case 4: + msm_io_w(csi_params->lane_assign << 8 | 0x7, + csibase + MIPI_CAMERA_CNTL); + break; + } + + msm_io_w(0xFFFFF3FF, csibase + MIPI_INTERRUPT_MASK); + /*clear IRQ bits - write 1 clears the status*/ + msm_io_w(0xFFFFF3FF, csibase + MIPI_INTERRUPT_STATUS); + + return rc; +} + +void msm_camio_set_perf_lvl(enum msm_bus_perf_setting perf_setting) +{ + switch (perf_setting) { + case S_INIT: + add_axi_qos(); + break; + case S_PREVIEW: + update_axi_qos(MSM_AXI_QOS_PREVIEW); + break; + case S_VIDEO: + update_axi_qos(MSM_AXI_QOS_RECORDING); + break; + case S_CAPTURE: + update_axi_qos(MSM_AXI_QOS_SNAPSHOT); + break; + case S_DEFAULT: + update_axi_qos(PM_QOS_DEFAULT_VALUE); + break; + case S_EXIT: + release_axi_qos(); + break; + default: + CDBG("%s: INVALID CASE\n", __func__); + } +} diff --git a/drivers/media/video/msm/msm_io_8960.c b/drivers/media/video/msm/msm_io_8960.c new file mode 100644 index 00000000000..eb29d08ec05 --- /dev/null +++ b/drivers/media/video/msm/msm_io_8960.c @@ -0,0 +1,1359 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_ispif.h" + +#define DBG_CSID 0 +#define DBG_CSIPHY 0 + +/* MIPI CSI PHY registers */ +#define MIPI_CSIPHY_LNn_CFG1_ADDR 0x0 +#define MIPI_CSIPHY_LNn_CFG2_ADDR 0x4 +#define MIPI_CSIPHY_LNn_CFG3_ADDR 0x8 +#define MIPI_CSIPHY_LNn_CFG4_ADDR 0xC +#define MIPI_CSIPHY_LNn_CFG5_ADDR 0x10 +#define MIPI_CSIPHY_LNCK_CFG1_ADDR 0x100 +#define MIPI_CSIPHY_LNCK_CFG2_ADDR 0x104 +#define MIPI_CSIPHY_LNCK_CFG3_ADDR 0x108 +#define MIPI_CSIPHY_LNCK_CFG4_ADDR 0x10C +#define MIPI_CSIPHY_LNCK_CFG5_ADDR 0x110 +#define MIPI_CSIPHY_LNCK_MISC1_ADDR 0x128 +#define MIPI_CSIPHY_GLBL_T_INIT_CFG0_ADDR 0x1E0 +#define MIPI_CSIPHY_T_WAKEUP_CFG0_ADDR 0x1E8 +#define MIPI_CSIPHY_GLBL_PWR_CFG_ADDR 0x0144 +#define MIPI_CSIPHY_INTERRUPT_STATUS0_ADDR 0x0180 +#define MIPI_CSIPHY_INTERRUPT_STATUS1_ADDR 0x0184 +#define MIPI_CSIPHY_INTERRUPT_STATUS2_ADDR 0x0188 +#define MIPI_CSIPHY_INTERRUPT_STATUS3_ADDR 0x018C +#define MIPI_CSIPHY_INTERRUPT_STATUS4_ADDR 0x0190 +#define MIPI_CSIPHY_INTERRUPT_MASK0_ADDR 0x01A0 +#define MIPI_CSIPHY_INTERRUPT_MASK1_ADDR 0x01A4 +#define MIPI_CSIPHY_INTERRUPT_MASK2_ADDR 0x01A8 +#define MIPI_CSIPHY_INTERRUPT_MASK3_ADDR 0x01AC +#define MIPI_CSIPHY_INTERRUPT_MASK4_ADDR 0x01B0 +#define MIPI_CSIPHY_INTERRUPT_CLEAR0_ADDR 0x01C0 +#define MIPI_CSIPHY_INTERRUPT_CLEAR1_ADDR 0x01C4 +#define MIPI_CSIPHY_INTERRUPT_CLEAR2_ADDR 0x01C8 +#define MIPI_CSIPHY_INTERRUPT_CLEAR3_ADDR 0x01CC +#define MIPI_CSIPHY_INTERRUPT_CLEAR4_ADDR 0x01D0 + +/* MIPI CSID registers */ +#define CSID_CORE_CTRL_ADDR 0x4 +#define CSID_RST_CMD_ADDR 0x8 +#define CSID_CID_LUT_VC_0_ADDR 0xc +#define CSID_CID_LUT_VC_1_ADDR 0x10 +#define CSID_CID_LUT_VC_2_ADDR 0x14 +#define CSID_CID_LUT_VC_3_ADDR 0x18 +#define CSID_CID_n_CFG_ADDR 0x1C +#define CSID_IRQ_CLEAR_CMD_ADDR 0x5c +#define CSID_IRQ_MASK_ADDR 0x60 +#define CSID_IRQ_STATUS_ADDR 0x64 +#define CSID_CAPTURED_UNMAPPED_LONG_PKT_HDR_ADDR 0x68 +#define CSID_CAPTURED_MMAPPED_LONG_PKT_HDR_ADDR 0x6c +#define CSID_CAPTURED_SHORT_PKT_ADDR 0x70 +#define CSID_CAPTURED_LONG_PKT_HDR_ADDR 0x74 +#define CSID_CAPTURED_LONG_PKT_FTR_ADDR 0x78 +#define CSID_PIF_MISR_DL0_ADDR 0x7C +#define CSID_PIF_MISR_DL1_ADDR 0x80 +#define CSID_PIF_MISR_DL2_ADDR 0x84 +#define CSID_PIF_MISR_DL3_ADDR 0x88 +#define CSID_STATS_TOTAL_PKTS_RCVD_ADDR 0x8C +#define CSID_STATS_ECC_ADDR 0x90 +#define CSID_STATS_CRC_ADDR 0x94 +#define CSID_TG_CTRL_ADDR 0x9C +#define CSID_TG_VC_CFG_ADDR 0xA0 +#define CSID_TG_DT_n_CFG_0_ADDR 0xA8 +#define CSID_TG_DT_n_CFG_1_ADDR 0xAC +#define CSID_TG_DT_n_CFG_2_ADDR 0xB0 +#define CSID_TG_DT_n_CFG_3_ADDR 0xD8 + +/* Regulator Voltage and Current */ + +#define CAM_VAF_MINUV 2800000 +#define CAM_VAF_MAXUV 2800000 +#define CAM_VDIG_MINUV 1200000 +#define CAM_VDIG_MAXUV 1200000 +#define CAM_VANA_MINUV 2800000 +#define CAM_VANA_MAXUV 2850000 +#define CAM_CSI_VDD_MINUV 1200000 +#define CAM_CSI_VDD_MAXUV 1200000 + +#define CAM_VAF_LOAD_UA 300000 +#define CAM_VDIG_LOAD_UA 105000 +#define CAM_VANA_LOAD_UA 85600 +#define CAM_CSI_LOAD_UA 20000 + +static struct clk *camio_cam_clk; +static struct clk *camio_vfe_clk; +static struct clk *camio_csi_src_clk; +static struct clk *camio_csi1_src_clk; +static struct clk *camio_csi0_vfe_clk; +static struct clk *camio_csi0_clk; +static struct clk *camio_csi0_pclk; +static struct clk *camio_csi_pix_clk; +static struct clk *camio_csi_rdi_clk; +static struct clk *camio_csiphy0_timer_clk; +static struct clk *camio_csiphy1_timer_clk; +static struct clk *camio_vfe_axi_clk; +static struct clk *camio_vfe_pclk; +static struct clk *camio_csi0_phy_clk; +static struct clk *camio_csiphy_timer_src_clk; + +/*static struct clk *camio_vfe_pclk;*/ +static struct clk *camio_jpeg_clk; +static struct clk *camio_jpeg_pclk; +static struct clk *camio_vpe_clk; +static struct clk *camio_vpe_pclk; +static struct regulator *fs_vfe; +static struct regulator *fs_ijpeg; +static struct regulator *fs_vpe; +static struct regulator *cam_vana; +static struct regulator *cam_vio; +static struct regulator *cam_vdig; +static struct regulator *cam_vaf; +static struct regulator *mipi_csi_vdd; + +static struct msm_camera_io_clk camio_clk; +static struct platform_device *camio_dev; +static struct resource *csidio, *csiphyio; +static struct resource *csid_mem, *csiphy_mem; +static struct resource *csid_irq, *csiphy_irq; +void __iomem *csidbase, *csiphybase; + +static struct msm_bus_vectors cam_init_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_preview_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1521190000, + .ib = 1521190000, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_video_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1521190000, + .ib = 1521190000, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1521190000, + .ib = 1521190000, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, +}; + +static struct msm_bus_vectors cam_snapshot_vectors[] = { + { + .src = MSM_BUS_MASTER_VFE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1521190000, + .ib = 1521190000, + }, + { + .src = MSM_BUS_MASTER_VPE, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 0, + .ib = 0, + }, + { + .src = MSM_BUS_MASTER_JPEG_ENC, + .dst = MSM_BUS_SLAVE_EBI_CH0, + .ab = 1521190000, + .ib = 1521190000, + }, +}; + +static struct msm_bus_paths cam_bus_client_config[] = { + { + ARRAY_SIZE(cam_init_vectors), + cam_init_vectors, + }, + { + ARRAY_SIZE(cam_preview_vectors), + cam_preview_vectors, + }, + { + ARRAY_SIZE(cam_video_vectors), + cam_video_vectors, + }, + { + ARRAY_SIZE(cam_snapshot_vectors), + cam_snapshot_vectors, + }, +}; + +static struct msm_bus_scale_pdata cam_bus_client_pdata = { + cam_bus_client_config, + ARRAY_SIZE(cam_bus_client_config), + .name = "msm_camera", +}; + + +void msm_io_w(u32 data, void __iomem *addr) +{ + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + writel_relaxed((data), (addr)); +} + +void msm_io_w_mb(u32 data, void __iomem *addr) +{ + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + wmb(); + writel_relaxed((data), (addr)); + wmb(); +} + +u32 msm_io_r(void __iomem *addr) +{ + uint32_t data = readl_relaxed(addr); + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + return data; +} + +u32 msm_io_r_mb(void __iomem *addr) +{ + uint32_t data; + rmb(); + data = readl_relaxed(addr); + rmb(); + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + return data; +} + +void msm_io_memcpy_toio(void __iomem *dest_addr, + void __iomem *src_addr, u32 len) +{ + int i; + u32 *d = (u32 *) dest_addr; + u32 *s = (u32 *) src_addr; + /* memcpy_toio does not work. Use writel_relaxed for now */ + for (i = 0; i < len; i++) + writel_relaxed(*s++, d++); +} + +void msm_io_dump(void __iomem *addr, int size) +{ + char line_str[128], *p_str; + int i; + u32 *p = (u32 *) addr; + u32 data; + CDBG("%s: %p %d\n", __func__, addr, size); + line_str[0] = '\0'; + p_str = line_str; + for (i = 0; i < size/4; i++) { + if (i % 4 == 0) { + sprintf(p_str, "%08x: ", (u32) p); + p_str += 10; + } + data = readl_relaxed(p++); + sprintf(p_str, "%08x ", data); + p_str += 9; + if ((i + 1) % 4 == 0) { + CDBG("%s\n", line_str); + line_str[0] = '\0'; + p_str = line_str; + } + } + if (line_str[0] != '\0') + CDBG("%s\n", line_str); +} + +void msm_io_memcpy(void __iomem *dest_addr, void __iomem *src_addr, u32 len) +{ + CDBG("%s: %p %p %d\n", __func__, dest_addr, src_addr, len); + msm_io_memcpy_toio(dest_addr, src_addr, len / 4); + msm_io_dump(dest_addr, len); +} + +static int msm_camera_vreg_enable(struct platform_device *pdev) +{ + if (mipi_csi_vdd == NULL) { + mipi_csi_vdd = regulator_get(&pdev->dev, "mipi_csi_vdd"); + if (IS_ERR(mipi_csi_vdd)) { + CDBG("%s: VREG MIPI CSI VDD get failed\n", __func__); + mipi_csi_vdd = NULL; + return -ENODEV; + } + if (regulator_set_voltage(mipi_csi_vdd, CAM_CSI_VDD_MINUV, + CAM_CSI_VDD_MAXUV)) { + CDBG("%s: VREG MIPI CSI VDD set voltage failed\n", + __func__); + goto mipi_csi_vdd_put; + } + if (regulator_set_optimum_mode(mipi_csi_vdd, + CAM_CSI_LOAD_UA) < 0) { + CDBG("%s: VREG MIPI CSI set optimum mode failed\n", + __func__); + goto mipi_csi_vdd_release; + } + if (regulator_enable(mipi_csi_vdd)) { + CDBG("%s: VREG MIPI CSI VDD enable failed\n", + __func__); + goto mipi_csi_vdd_disable; + } + } + if (cam_vana == NULL) { + cam_vana = regulator_get(&pdev->dev, "cam_vana"); + if (IS_ERR(cam_vana)) { + CDBG("%s: VREG CAM VANA get failed\n", __func__); + cam_vana = NULL; + goto mipi_csi_vdd_disable; + } + if (regulator_set_voltage(cam_vana, CAM_VANA_MINUV, + CAM_VANA_MAXUV)) { + CDBG("%s: VREG CAM VANA set voltage failed\n", + __func__); + goto cam_vana_put; + } + if (regulator_set_optimum_mode(cam_vana, + CAM_VANA_LOAD_UA) < 0) { + CDBG("%s: VREG CAM VANA set optimum mode failed\n", + __func__); + goto cam_vana_release; + } + if (regulator_enable(cam_vana)) { + CDBG("%s: VREG CAM VANA enable failed\n", __func__); + goto cam_vana_disable; + } + } + if (cam_vio == NULL) { + cam_vio = regulator_get(&pdev->dev, "cam_vio"); + if (IS_ERR(cam_vio)) { + CDBG("%s: VREG VIO get failed\n", __func__); + cam_vio = NULL; + goto cam_vana_disable; + } + if (regulator_enable(cam_vio)) { + CDBG("%s: VREG VIO enable failed\n", __func__); + goto cam_vio_put; + } + } + if (cam_vdig == NULL) { + cam_vdig = regulator_get(&pdev->dev, "cam_vdig"); + if (IS_ERR(cam_vdig)) { + CDBG("%s: VREG CAM VDIG get failed\n", __func__); + cam_vdig = NULL; + goto cam_vio_disable; + } + if (regulator_set_voltage(cam_vdig, CAM_VDIG_MINUV, + CAM_VDIG_MAXUV)) { + CDBG("%s: VREG CAM VDIG set voltage failed\n", + __func__); + goto cam_vdig_put; + } + if (regulator_set_optimum_mode(cam_vdig, + CAM_VDIG_LOAD_UA) < 0) { + CDBG("%s: VREG CAM VDIG set optimum mode failed\n", + __func__); + goto cam_vdig_release; + } + if (regulator_enable(cam_vdig)) { + CDBG("%s: VREG CAM VDIG enable failed\n", __func__); + goto cam_vdig_disable; + } + } + if (cam_vaf == NULL) { + cam_vaf = regulator_get(&pdev->dev, "cam_vaf"); + if (IS_ERR(cam_vaf)) { + CDBG("%s: VREG CAM VAF get failed\n", __func__); + cam_vaf = NULL; + goto cam_vdig_disable; + } + if (regulator_set_voltage(cam_vaf, CAM_VAF_MINUV, + CAM_VAF_MAXUV)) { + CDBG("%s: VREG CAM VAF set voltage failed\n", + __func__); + goto cam_vaf_put; + } + if (regulator_set_optimum_mode(cam_vaf, + CAM_VAF_LOAD_UA) < 0) { + CDBG("%s: VREG CAM VAF set optimum mode failed\n", + __func__); + goto cam_vaf_release; + } + if (regulator_enable(cam_vaf)) { + CDBG("%s: VREG CAM VAF enable failed\n", __func__); + goto cam_vaf_disable; + } + } + if (fs_vfe == NULL) { + fs_vfe = regulator_get(&pdev->dev, "fs_vfe"); + if (IS_ERR(fs_vfe)) { + CDBG("%s: Regulator FS_VFE get failed %ld\n", __func__, + PTR_ERR(fs_vfe)); + fs_vfe = NULL; + } else if (regulator_enable(fs_vfe)) { + CDBG("%s: Regulator FS_VFE enable failed\n", __func__); + regulator_put(fs_vfe); + } + } + return 0; + +cam_vaf_disable: + regulator_set_optimum_mode(cam_vaf, 0); +cam_vaf_release: + regulator_set_voltage(cam_vaf, 0, CAM_VAF_MAXUV); + regulator_disable(cam_vaf); +cam_vaf_put: + regulator_put(cam_vaf); + cam_vaf = NULL; +cam_vdig_disable: + regulator_set_optimum_mode(cam_vdig, 0); +cam_vdig_release: + regulator_set_voltage(cam_vdig, 0, CAM_VDIG_MAXUV); + regulator_disable(cam_vdig); +cam_vdig_put: + regulator_put(cam_vdig); + cam_vdig = NULL; +cam_vio_disable: + regulator_disable(cam_vio); +cam_vio_put: + regulator_put(cam_vio); + cam_vio = NULL; +cam_vana_disable: + regulator_set_optimum_mode(cam_vana, 0); +cam_vana_release: + regulator_set_voltage(cam_vana, 0, CAM_VANA_MAXUV); + regulator_disable(cam_vana); +cam_vana_put: + regulator_put(cam_vana); + cam_vana = NULL; +mipi_csi_vdd_disable: + regulator_set_optimum_mode(mipi_csi_vdd, 0); +mipi_csi_vdd_release: + regulator_set_voltage(mipi_csi_vdd, 0, CAM_CSI_VDD_MAXUV); + regulator_disable(mipi_csi_vdd); + +mipi_csi_vdd_put: + regulator_put(mipi_csi_vdd); + mipi_csi_vdd = NULL; + return -ENODEV; +} + +static void msm_camera_vreg_disable(void) +{ + if (mipi_csi_vdd) { + regulator_set_voltage(mipi_csi_vdd, 0, CAM_CSI_VDD_MAXUV); + regulator_set_optimum_mode(mipi_csi_vdd, 0); + regulator_disable(mipi_csi_vdd); + regulator_put(mipi_csi_vdd); + mipi_csi_vdd = NULL; + } + + if (cam_vana) { + regulator_set_voltage(cam_vana, 0, CAM_VANA_MAXUV); + regulator_set_optimum_mode(cam_vana, 0); + regulator_disable(cam_vana); + regulator_put(cam_vana); + cam_vana = NULL; + } + + if (cam_vio) { + regulator_disable(cam_vio); + regulator_put(cam_vio); + cam_vio = NULL; + } + + if (cam_vdig) { + regulator_set_voltage(cam_vdig, 0, CAM_VDIG_MAXUV); + regulator_set_optimum_mode(cam_vdig, 0); + regulator_disable(cam_vdig); + regulator_put(cam_vdig); + cam_vdig = NULL; + } + + if (cam_vaf) { + regulator_set_voltage(cam_vaf, 0, CAM_VAF_MAXUV); + regulator_set_optimum_mode(cam_vaf, 0); + regulator_disable(cam_vaf); + regulator_put(cam_vaf); + cam_vaf = NULL; + } + + if (fs_vfe) { + regulator_disable(fs_vfe); + regulator_put(fs_vfe); + fs_vfe = NULL; + } +} + +int msm_camio_clk_enable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + struct msm_camera_sensor_info *sinfo = camio_dev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + uint8_t csid_core = camdev->csid_core; + + switch (clktype) { + case CAMIO_CAM_MCLK_CLK: + camio_cam_clk = + clk = clk_get(&camio_dev->dev, "cam_clk"); + msm_camio_clk_rate_set_2(clk, camio_clk.mclk_clk_rate); + break; + + case CAMIO_VFE_CLK: + camio_vfe_clk = + clk = clk_get(NULL, "vfe_clk"); + msm_camio_clk_rate_set_2(clk, camio_clk.vfe_clk_rate); + break; + + case CAMIO_VFE_AXI_CLK: + camio_vfe_axi_clk = + clk = clk_get(NULL, "vfe_axi_clk"); + break; + + case CAMIO_VFE_PCLK: + camio_vfe_pclk = + clk = clk_get(NULL, "vfe_pclk"); + break; + + case CAMIO_CSI0_VFE_CLK: + camio_csi0_vfe_clk = + clk = clk_get(NULL, "csi_vfe_clk"); + break; +/* + case CAMIO_CSI1_VFE_CLK: + camio_csi1_vfe_clk = + clk = clk_get(&camio_dev->dev, "csi_vfe_clk"); + break; +*/ + case CAMIO_CSI_SRC_CLK: + camio_csi_src_clk = + clk = clk_get(NULL, "csi_src_clk"); + msm_camio_clk_rate_set_2(clk, 177780000); + break; + + case CAMIO_CSI1_SRC_CLK: + camio_csi1_src_clk = + clk = clk_get(&camio_dev->dev, "csi_src_clk"); + msm_camio_clk_rate_set_2(clk, 177780000); + break; + + case CAMIO_CSI0_CLK: + camio_csi0_clk = + clk = clk_get(&camio_dev->dev, "csi_clk"); + break; + + case CAMIO_CSI0_PHY_CLK: + camio_csi0_phy_clk = + clk = clk_get(&camio_dev->dev, "csi_phy_clk"); + break; + + case CAMIO_CSI_PIX_CLK: + camio_csi_pix_clk = + clk = clk_get(NULL, "csi_pix_clk"); + /* mux to select between csid0 and csid1 */ + msm_camio_clk_rate_set_2(clk, csid_core); + break; + + case CAMIO_CSI_RDI_CLK: + camio_csi_rdi_clk = + clk = clk_get(NULL, "csi_rdi_clk"); + /* mux to select between csid0 and csid1 */ + msm_camio_clk_rate_set_2(clk, csid_core); + break; + + case CAMIO_CSIPHY0_TIMER_CLK: + camio_csiphy0_timer_clk = + clk = clk_get(NULL, "csi0phy_timer_clk"); + break; + + case CAMIO_CSIPHY1_TIMER_CLK: + camio_csiphy1_timer_clk = + clk = clk_get(NULL, "csi1phy_timer_clk"); + break; + + case CAMIO_CSIPHY_TIMER_SRC_CLK: + camio_csiphy_timer_src_clk = + clk = clk_get(NULL, "csiphy_timer_src_clk"); + msm_camio_clk_rate_set_2(clk, 177780000); + break; + + case CAMIO_CSI0_PCLK: + camio_csi0_pclk = + clk = clk_get(NULL, "csi_pclk"); + break; + + case CAMIO_JPEG_CLK: + camio_jpeg_clk = + clk = clk_get(NULL, "ijpeg_clk"); + clk_set_min_rate(clk, 144000000); + break; + + case CAMIO_JPEG_PCLK: + camio_jpeg_pclk = + clk = clk_get(NULL, "ijpeg_pclk"); + break; + + case CAMIO_VPE_CLK: + camio_vpe_clk = + clk = clk_get(NULL, "vpe_clk"); + msm_camio_clk_set_min_rate(clk, 150000000); + break; + + case CAMIO_VPE_PCLK: + camio_vpe_pclk = + clk = clk_get(NULL, "vpe_pclk"); + break; + + default: + break; + } + + if (!IS_ERR(clk)) + rc = clk_enable(clk); + else + rc = PTR_ERR(clk); + + if (rc < 0) + pr_err("%s(%d) failed %d\n", __func__, clktype, rc); + + return rc; +} + +int msm_camio_clk_disable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_CAM_MCLK_CLK: + clk = camio_cam_clk; + break; + + case CAMIO_VFE_CLK: + clk = camio_vfe_clk; + break; + + case CAMIO_VFE_AXI_CLK: + clk = camio_vfe_axi_clk; + break; + + case CAMIO_VFE_PCLK: + clk = camio_vfe_pclk; + break; + + case CAMIO_CSI0_VFE_CLK: + clk = camio_csi0_vfe_clk; + break; + + case CAMIO_CSI_SRC_CLK: + clk = camio_csi_src_clk; + break; + + case CAMIO_CSI0_CLK: + clk = camio_csi0_clk; + break; + + case CAMIO_CSI0_PHY_CLK: + clk = camio_csi0_phy_clk; + break; + + case CAMIO_CSI_PIX_CLK: + clk = camio_csi_pix_clk; + break; + + case CAMIO_CSI_RDI_CLK: + clk = camio_csi_rdi_clk; + break; + + case CAMIO_CSIPHY0_TIMER_CLK: + clk = camio_csiphy0_timer_clk; + break; + + case CAMIO_CSIPHY_TIMER_SRC_CLK: + clk = camio_csiphy_timer_src_clk; + break; + + case CAMIO_CSI0_PCLK: + clk = camio_csi0_pclk; + break; + + case CAMIO_JPEG_CLK: + clk = camio_jpeg_clk; + break; + + case CAMIO_JPEG_PCLK: + clk = camio_jpeg_pclk; + break; + + case CAMIO_VPE_CLK: + clk = camio_vpe_clk; + break; + + case CAMIO_VPE_PCLK: + clk = camio_vpe_pclk; + break; + + default: + break; + } + + if (!IS_ERR(clk)) { + clk_disable(clk); + clk_put(clk); + } else + rc = PTR_ERR(clk); + + if (rc < 0) + pr_err("%s(%d) failed %d\n", __func__, clktype, rc); + + return rc; +} + +void msm_camio_vfe_clk_rate_set(int rate) +{ + struct clk *clk = camio_vfe_clk; + if (rate > clk_get_rate(clk)) + clk_set_rate(clk, rate); +} + +void msm_camio_clk_rate_set(int rate) +{ + struct clk *clk = camio_cam_clk; + clk_set_rate(clk, rate); +} + +void msm_camio_clk_rate_set_2(struct clk *clk, int rate) +{ + clk_set_rate(clk, rate); +} + +void msm_camio_clk_set_min_rate(struct clk *clk, int rate) +{ + clk_set_min_rate(clk, rate); +} + +#if DBG_CSID +static irqreturn_t msm_io_csi_irq(int irq_num, void *data) +{ + uint32_t irq; + irq = msm_io_r(csidbase + CSID_IRQ_STATUS_ADDR); + CDBG("%s CSID_IRQ_STATUS_ADDR = 0x%x\n", __func__, irq); + msm_io_w(irq, csidbase + CSID_IRQ_CLEAR_CMD_ADDR); + irq = msm_io_r(csidbase + 0x7C); + CDBG("%s CSID_PIF_MISR_DL0 = 0x%x\n", __func__, irq); + irq = msm_io_r(csidbase + 0x80); + CDBG("%s CSID_PIF_MISR_DL1 = 0x%x\n", __func__, irq); + irq = msm_io_r(csidbase + 0x84); + CDBG("%s CSID_PIF_MISR_DL2 = 0x%x\n", __func__, irq); + irq = msm_io_r(csidbase + 0x88); + CDBG("%s CSID_PIF_MISR_DL3 = 0x%x\n", __func__, irq); + irq = msm_io_r(csidbase + 0x8C); + CDBG("%s PACKET Count = %d\n", __func__, irq); + return IRQ_HANDLED; +} +#endif +/* +void msm_io_read_interrupt(void) +{ + uint32_t irq; + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS0_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS0 = 0x%x\n", __func__, irq); + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS0_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS0 = 0x%x\n", __func__, irq); + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS1_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS1 = 0x%x\n", __func__, irq); + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS2_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS2 = 0x%x\n", __func__, irq); + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS3_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS3 = 0x%x\n", __func__, irq); + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS4_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS4 = 0x%x\n", __func__, irq); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR0_ADDR); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR1_ADDR); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR2_ADDR); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR3_ADDR); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR4_ADDR); + msm_io_w(0x1, csiphybase + 0x164); + msm_io_w(0x0, csiphybase + 0x164); + return; +} +*/ +#if DBG_CSIPHY +static irqreturn_t msm_io_csiphy_irq(int irq_num, void *data) +{ + uint32_t irq; + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS0_ADDR); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR0_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS0 = 0x%x\n", __func__, irq); + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS1_ADDR); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR1_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS1 = 0x%x\n", __func__, irq); + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS2_ADDR); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR2_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS2 = 0x%x\n", __func__, irq); + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS3_ADDR); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR3_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS3 = 0x%x\n", __func__, irq); + irq = msm_io_r(csiphybase + MIPI_CSIPHY_INTERRUPT_STATUS4_ADDR); + msm_io_w(irq, csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR4_ADDR); + CDBG("%s MIPI_CSIPHY_INTERRUPT_STATUS4 = 0x%x\n", __func__, irq); + msm_io_w(0x1, csiphybase + 0x164); + msm_io_w(0x0, csiphybase + 0x164); + return IRQ_HANDLED; +} +#endif +static int msm_camio_enable_all_clks(uint8_t csid_core) +{ + int rc = 0; + + rc = msm_camio_clk_enable(CAMIO_CSI_SRC_CLK); + if (rc < 0) + goto csi_src_fail; + if (csid_core == 1) { + rc = msm_camio_clk_enable(CAMIO_CSI1_SRC_CLK); + if (rc < 0) + goto csi1_src_fail; + } + rc = msm_camio_clk_enable(CAMIO_CSI0_CLK); + if (rc < 0) + goto csi0_fail; + rc = msm_camio_clk_enable(CAMIO_CSI0_PHY_CLK); + if (rc < 0) + goto csi0_phy_fail; + rc = msm_camio_clk_enable(CAMIO_CSIPHY_TIMER_SRC_CLK); + if (rc < 0) + goto csiphy_timer_src_fail; + if (csid_core == 0) { + rc = msm_camio_clk_enable(CAMIO_CSIPHY0_TIMER_CLK); + if (rc < 0) + goto csiphy0_timer_fail; + } else if (csid_core == 1) { + rc = msm_camio_clk_enable(CAMIO_CSIPHY1_TIMER_CLK); + if (rc < 0) + goto csiphy1_timer_fail; + } + rc = msm_camio_clk_enable(CAMIO_CSI0_PCLK); + if (rc < 0) + goto csi0p_fail; + + rc = msm_camio_clk_enable(CAMIO_VFE_CLK); + if (rc < 0) + goto vfe_fail; + rc = msm_camio_clk_enable(CAMIO_VFE_AXI_CLK); + if (rc < 0) + goto axi_fail; + rc = msm_camio_clk_enable(CAMIO_VFE_PCLK); + if (rc < 0) + goto vfep_fail; + + rc = msm_camio_clk_enable(CAMIO_CSI0_VFE_CLK); + if (rc < 0) + goto csi0_vfe_fail; + rc = msm_camio_clk_enable(CAMIO_CSI_PIX_CLK); + if (rc < 0) + goto csi_pix_fail; + rc = msm_camio_clk_enable(CAMIO_CSI_RDI_CLK); + if (rc < 0) + goto csi_rdi_fail; + return rc; + +csi_rdi_fail: + msm_camio_clk_disable(CAMIO_CSI_PIX_CLK); +csi_pix_fail: + msm_camio_clk_disable(CAMIO_CSI0_VFE_CLK); +csi0_vfe_fail: + msm_camio_clk_disable(CAMIO_VFE_PCLK); +vfep_fail: + msm_camio_clk_disable(CAMIO_VFE_AXI_CLK); +axi_fail: + msm_camio_clk_disable(CAMIO_VFE_CLK); +vfe_fail: + msm_camio_clk_disable(CAMIO_CSI0_PCLK); +csi0p_fail: + msm_camio_clk_disable(CAMIO_CSIPHY0_TIMER_CLK); +csiphy1_timer_fail: + msm_camio_clk_disable(CAMIO_CSIPHY1_TIMER_CLK); +csiphy0_timer_fail: + msm_camio_clk_disable(CAMIO_CSIPHY_TIMER_SRC_CLK); +csiphy_timer_src_fail: + msm_camio_clk_disable(CAMIO_CSI0_PHY_CLK); +csi0_phy_fail: + msm_camio_clk_disable(CAMIO_CSI0_CLK); +csi0_fail: + msm_camio_clk_disable(CAMIO_CSI1_SRC_CLK); +csi1_src_fail: + msm_camio_clk_disable(CAMIO_CSI_SRC_CLK); +csi_src_fail: + return rc; +} + +static void msm_camio_disable_all_clks(uint8_t csid_core) +{ + msm_camio_clk_disable(CAMIO_CSI_RDI_CLK); + msm_camio_clk_disable(CAMIO_CSI_PIX_CLK); + msm_camio_clk_disable(CAMIO_CSI0_VFE_CLK); + msm_camio_clk_disable(CAMIO_VFE_PCLK); + msm_camio_clk_disable(CAMIO_VFE_AXI_CLK); + msm_camio_clk_disable(CAMIO_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI0_PCLK); + if (csid_core == 0) + msm_camio_clk_disable(CAMIO_CSIPHY0_TIMER_CLK); + else if (csid_core == 1) + msm_camio_clk_disable(CAMIO_CSIPHY1_TIMER_CLK); + msm_camio_clk_disable(CAMIO_CSIPHY_TIMER_SRC_CLK); + msm_camio_clk_disable(CAMIO_CSI0_PHY_CLK); + msm_camio_clk_disable(CAMIO_CSI0_CLK); + if (csid_core == 1) + msm_camio_clk_disable(CAMIO_CSI1_SRC_CLK); + msm_camio_clk_disable(CAMIO_CSI_SRC_CLK); +} + +int msm_camio_jpeg_clk_disable(void) +{ + int rc = 0; + if (fs_ijpeg) { + rc = regulator_disable(fs_ijpeg); + if (rc < 0) { + CDBG("%s: Regulator disable failed %d\n", __func__, rc); + return rc; + } + regulator_put(fs_ijpeg); + } + rc = msm_camio_clk_disable(CAMIO_JPEG_PCLK); + if (rc < 0) + return rc; + rc = msm_camio_clk_disable(CAMIO_JPEG_CLK); + CDBG("%s: exit %d\n", __func__, rc); + return rc; +} + +int msm_camio_jpeg_clk_enable(void) +{ + int rc = 0; + rc = msm_camio_clk_enable(CAMIO_JPEG_CLK); + if (rc < 0) + return rc; + rc = msm_camio_clk_enable(CAMIO_JPEG_PCLK); + if (rc < 0) + return rc; + fs_ijpeg = regulator_get(NULL, "fs_ijpeg"); + if (IS_ERR(fs_ijpeg)) { + CDBG("%s: Regulator FS_IJPEG get failed %ld\n", __func__, + PTR_ERR(fs_ijpeg)); + fs_ijpeg = NULL; + } else if (regulator_enable(fs_ijpeg)) { + CDBG("%s: Regulator FS_IJPEG enable failed\n", __func__); + regulator_put(fs_ijpeg); + } + CDBG("%s: exit %d\n", __func__, rc); + return rc; +} + +int msm_camio_vpe_clk_disable(void) +{ + int rc = 0; + if (fs_vpe) { + regulator_disable(fs_vpe); + regulator_put(fs_vpe); + } + + rc = msm_camio_clk_disable(CAMIO_VPE_CLK); + if (rc < 0) + return rc; + rc = msm_camio_clk_disable(CAMIO_VPE_PCLK); + return rc; +} + +int msm_camio_vpe_clk_enable(uint32_t clk_rate) +{ + int rc = 0; + (void)clk_rate; + fs_vpe = regulator_get(NULL, "fs_vpe"); + if (IS_ERR(fs_vpe)) { + CDBG("%s: Regulator FS_VPE get failed %ld\n", __func__, + PTR_ERR(fs_vpe)); + fs_vpe = NULL; + } else if (regulator_enable(fs_vpe)) { + CDBG("%s: Regulator FS_VPE enable failed\n", __func__); + regulator_put(fs_vpe); + } + + rc = msm_camio_clk_enable(CAMIO_VPE_CLK); + if (rc < 0) + return rc; + rc = msm_camio_clk_enable(CAMIO_VPE_PCLK); + return rc; +} + +int msm_camio_enable(struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + uint8_t csid_core = camdev->csid_core; + char csid[] = "csid0"; + char csiphy[] = "csiphy0"; + if (csid_core < 0 || csid_core > 2) + return -ENODEV; + + csid[4] = '0' + csid_core; + csiphy[6] = '0' + csid_core; + + camio_dev = pdev; + camio_clk = camdev->ioclk; + + rc = msm_camio_enable_all_clks(csid_core); + if (rc < 0) + return rc; + + csid_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, csid); + if (!csid_mem) { + pr_err("%s: no mem resource?\n", __func__); + return -ENODEV; + } + csid_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, csid); + if (!csid_irq) { + pr_err("%s: no irq resource?\n", __func__); + return -ENODEV; + } + csiphy_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, csiphy); + if (!csiphy_mem) { + pr_err("%s: no mem resource?\n", __func__); + return -ENODEV; + } + csiphy_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, csiphy); + if (!csiphy_irq) { + pr_err("%s: no irq resource?\n", __func__); + return -ENODEV; + } + + csidio = request_mem_region(csid_mem->start, + resource_size(csid_mem), pdev->name); + if (!csidio) { + rc = -EBUSY; + goto common_fail; + } + csidbase = ioremap(csid_mem->start, + resource_size(csid_mem)); + if (!csidbase) { + rc = -ENOMEM; + goto csi_busy; + } +#if DBG_CSID + rc = request_irq(csid_irq->start, msm_io_csi_irq, + IRQF_TRIGGER_RISING, "csid", 0); + if (rc < 0) + goto csi_irq_fail; +#endif + csiphyio = request_mem_region(csiphy_mem->start, + resource_size(csiphy_mem), pdev->name); + if (!csidio) { + rc = -EBUSY; + goto csi_irq_fail; + } + csiphybase = ioremap(csiphy_mem->start, + resource_size(csiphy_mem)); + if (!csiphybase) { + rc = -ENOMEM; + goto csiphy_busy; + } +#if DBG_CSIPHY + rc = request_irq(csiphy_irq->start, msm_io_csiphy_irq, + IRQF_TRIGGER_RISING, "csiphy", 0); + if (rc < 0) + goto csiphy_irq_fail; +#endif + rc = msm_ispif_init(pdev); + if (rc < 0) + goto csiphy_irq_fail; + CDBG("camio enable done\n"); + return 0; +csiphy_irq_fail: + iounmap(csiphybase); +csiphy_busy: + release_mem_region(csiphy_mem->start, resource_size(csiphy_mem)); +csi_irq_fail: + iounmap(csidbase); +csi_busy: + release_mem_region(csid_mem->start, resource_size(csid_mem)); +common_fail: + msm_camio_disable_all_clks(csid_core); + msm_camera_vreg_disable(); + camdev->camera_gpio_off(); + return rc; +} + +void msm_camio_disable(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + uint8_t csid_core = camdev->csid_core; +#if DBG_CSIPHY + free_irq(csiphy_irq->start, 0); +#endif + iounmap(csiphybase); + release_mem_region(csiphy_mem->start, resource_size(csiphy_mem)); + +#if DBG_CSID + free_irq(csid_irq, 0); +#endif + iounmap(csidbase); + release_mem_region(csid_mem->start, resource_size(csid_mem)); + + msm_camio_disable_all_clks(csid_core); + msm_ispif_release(pdev); +} + +int msm_camio_sensor_clk_on(struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camio_dev = pdev; + camio_clk = camdev->ioclk; + + msm_camera_vreg_enable(pdev); + msleep(20); + rc = camdev->camera_gpio_on(); + if (rc < 0) + return rc; + return msm_camio_clk_enable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_sensor_clk_off(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + msm_camera_vreg_disable(); + camdev->camera_gpio_off(); + return msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); +} + +void msm_camio_vfe_blk_reset(void) +{ + return; +} + +int msm_camio_probe_on(struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camio_dev = pdev; + camio_clk = camdev->ioclk; + + rc = camdev->camera_gpio_on(); + if (rc < 0) + return rc; + msm_camera_vreg_enable(pdev); + return msm_camio_clk_enable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_probe_off(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + msm_camera_vreg_disable(); + camdev->camera_gpio_off(); + return msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_csid_cid_lut(struct msm_camera_csid_lut_params *csid_lut_params) +{ + int rc = 0, i = 0; + uint32_t val = 0; + + for (i = 0; i < csid_lut_params->num_cid && i < 4; i++) { + if (csid_lut_params->vc_cfg[i].dt < 0x12 || + csid_lut_params->vc_cfg[i].dt > 0x37) { + CDBG("%s: unsupported data type 0x%x\n", + __func__, csid_lut_params->vc_cfg[i].dt); + return rc; + } + val = msm_io_r(csidbase + CSID_CID_LUT_VC_0_ADDR + + (csid_lut_params->vc_cfg[i].cid >> 2) * 4) + & ~(0xFF << csid_lut_params->vc_cfg[i].cid * 8); + val |= csid_lut_params->vc_cfg[i].dt << + csid_lut_params->vc_cfg[i].cid * 8; + msm_io_w(val, csidbase + CSID_CID_LUT_VC_0_ADDR + + (csid_lut_params->vc_cfg[i].cid >> 2) * 4); + val = csid_lut_params->vc_cfg[i].decode_format << 4 | 0x3; + msm_io_w(val, csidbase + CSID_CID_n_CFG_ADDR + + (csid_lut_params->vc_cfg[i].cid * 4)); + } + return rc; +} + +int msm_camio_csid_config(struct msm_camera_csid_params *csid_params) +{ + int rc = 0; + uint32_t val = 0; + val = csid_params->lane_cnt - 1; + val |= csid_params->lane_assign << 2; + val |= 0x1 << 10; + val |= 0x1 << 11; + val |= 0x1 << 12; + val |= 0x1 << 28; + msm_io_w(val, csidbase + CSID_CORE_CTRL_ADDR); + + rc = msm_camio_csid_cid_lut(&csid_params->lut_params); + if (rc < 0) + return rc; + + msm_io_w(0xFFFFFFFF, csidbase + CSID_IRQ_MASK_ADDR); + msm_io_w(0xFFFFFFFF, csidbase + CSID_IRQ_CLEAR_CMD_ADDR); + + msleep(20); + return rc; +} + +int msm_camio_csiphy_config(struct msm_camera_csiphy_params *csiphy_params) +{ + int rc = 0; + int i = 0; + uint32_t val = 0; + if (csiphy_params->lane_cnt < 1 || csiphy_params->lane_cnt > 4) { + CDBG("%s: unsupported lane cnt %d\n", + __func__, csiphy_params->lane_cnt); + return rc; + } + + val = 0x3; + msm_io_w((((1 << csiphy_params->lane_cnt) - 1) << 2) | val, + csiphybase + MIPI_CSIPHY_GLBL_PWR_CFG_ADDR); + msm_io_w(0x1, csiphybase + MIPI_CSIPHY_GLBL_T_INIT_CFG0_ADDR); + msm_io_w(0x1, csiphybase + MIPI_CSIPHY_T_WAKEUP_CFG0_ADDR); + + for (i = 0; i < csiphy_params->lane_cnt; i++) { + msm_io_w(0x10, csiphybase + MIPI_CSIPHY_LNn_CFG1_ADDR + 0x40*i); + msm_io_w(0x5F, csiphybase + MIPI_CSIPHY_LNn_CFG2_ADDR + 0x40*i); + msm_io_w(csiphy_params->settle_cnt, + csiphybase + MIPI_CSIPHY_LNn_CFG3_ADDR + 0x40*i); + msm_io_w(0x00000052, + csiphybase + MIPI_CSIPHY_LNn_CFG5_ADDR + 0x40*i); + } + + msm_io_w(0x00000000, csiphybase + MIPI_CSIPHY_LNCK_CFG1_ADDR); + msm_io_w(0x5F, csiphybase + MIPI_CSIPHY_LNCK_CFG2_ADDR); + msm_io_w(csiphy_params->settle_cnt, + csiphybase + MIPI_CSIPHY_LNCK_CFG3_ADDR); + msm_io_w(0x5, csiphybase + MIPI_CSIPHY_LNCK_CFG4_ADDR); + msm_io_w(0x2, csiphybase + MIPI_CSIPHY_LNCK_CFG5_ADDR); + msm_io_w(0x0, csiphybase + 0x128); + + for (i = 0; i <= csiphy_params->lane_cnt; i++) { + msm_io_w(0xFFFFFFFF, + csiphybase + MIPI_CSIPHY_INTERRUPT_MASK0_ADDR + 0x4*i); + msm_io_w(0xFFFFFFFF, + csiphybase + MIPI_CSIPHY_INTERRUPT_CLEAR0_ADDR + 0x4*i); + } + return rc; +} + +void msm_camio_set_perf_lvl(enum msm_bus_perf_setting perf_setting) +{ + static uint32_t bus_perf_client; + int rc = 0; + switch (perf_setting) { + case S_INIT: + bus_perf_client = + msm_bus_scale_register_client(&cam_bus_client_pdata); + if (!bus_perf_client) { + CDBG("%s: Registration Failed!!!\n", __func__); + bus_perf_client = 0; + return; + } + CDBG("%s: S_INIT rc = %u\n", __func__, bus_perf_client); + break; + case S_EXIT: + if (bus_perf_client) { + CDBG("%s: S_EXIT\n", __func__); + msm_bus_scale_unregister_client(bus_perf_client); + } else + CDBG("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_PREVIEW: + if (bus_perf_client) { + rc = msm_bus_scale_client_update_request( + bus_perf_client, 1); + CDBG("%s: S_PREVIEW rc = %d\n", __func__, rc); + } else + CDBG("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_VIDEO: + if (bus_perf_client) { + rc = msm_bus_scale_client_update_request( + bus_perf_client, 2); + CDBG("%s: S_VIDEO rc = %d\n", __func__, rc); + } else + CDBG("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_CAPTURE: + if (bus_perf_client) { + rc = msm_bus_scale_client_update_request( + bus_perf_client, 3); + CDBG("%s: S_CAPTURE rc = %d\n", __func__, rc); + } else + CDBG("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_DEFAULT: + break; + default: + pr_warning("%s: INVALID CASE\n", __func__); + } +} diff --git a/drivers/media/video/msm/msm_io_8x60.c b/drivers/media/video/msm/msm_io_8x60.c new file mode 100644 index 00000000000..845777d9812 --- /dev/null +++ b/drivers/media/video/msm/msm_io_8x60.c @@ -0,0 +1,900 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* MIPI CSI controller registers */ +#define MIPI_PHY_CONTROL 0x00000000 +#define MIPI_PROTOCOL_CONTROL 0x00000004 +#define MIPI_INTERRUPT_STATUS 0x00000008 +#define MIPI_INTERRUPT_MASK 0x0000000C +#define MIPI_CAMERA_CNTL 0x00000024 +#define MIPI_CALIBRATION_CONTROL 0x00000018 +#define MIPI_PHY_D0_CONTROL2 0x00000038 +#define MIPI_PHY_D1_CONTROL2 0x0000003C +#define MIPI_PHY_D2_CONTROL2 0x00000040 +#define MIPI_PHY_D3_CONTROL2 0x00000044 +#define MIPI_PHY_CL_CONTROL 0x00000048 +#define MIPI_PHY_D0_CONTROL 0x00000034 +#define MIPI_PHY_D1_CONTROL 0x00000020 +#define MIPI_PHY_D2_CONTROL 0x0000002C +#define MIPI_PHY_D3_CONTROL 0x00000030 +#define MIPI_PROTOCOL_CONTROL_SW_RST_BMSK 0x8000000 +#define MIPI_PROTOCOL_CONTROL_LONG_PACKET_HEADER_CAPTURE_BMSK 0x200000 +#define MIPI_PROTOCOL_CONTROL_DATA_FORMAT_BMSK 0x180000 +#define MIPI_PROTOCOL_CONTROL_DECODE_ID_BMSK 0x40000 +#define MIPI_PROTOCOL_CONTROL_ECC_EN_BMSK 0x20000 +#define MIPI_CALIBRATION_CONTROL_SWCAL_CAL_EN_SHFT 0x16 +#define MIPI_CALIBRATION_CONTROL_SWCAL_STRENGTH_OVERRIDE_EN_SHFT 0x15 +#define MIPI_CALIBRATION_CONTROL_CAL_SW_HW_MODE_SHFT 0x14 +#define MIPI_CALIBRATION_CONTROL_MANUAL_OVERRIDE_EN_SHFT 0x7 +#define MIPI_PROTOCOL_CONTROL_DATA_FORMAT_SHFT 0x13 +#define MIPI_PROTOCOL_CONTROL_DPCM_SCHEME_SHFT 0x1e +#define MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 +#define MIPI_PHY_D1_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D1_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D1_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D1_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 +#define MIPI_PHY_D2_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D2_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D2_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D2_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 +#define MIPI_PHY_D3_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D3_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D3_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D3_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 +#define MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT 0x18 +#define MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT 0x2 +#define MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT 0x1c +#define MIPI_PHY_D1_CONTROL_MIPI_CLK_PHY_SHUTDOWNB_SHFT 0x9 +#define MIPI_PHY_D1_CONTROL_MIPI_DATA_PHY_SHUTDOWNB_SHFT 0x8 + +static struct clk *camio_cam_clk; +static struct clk *camio_vfe_clk; +static struct clk *camio_csi_src_clk; +static struct clk *camio_csi0_vfe_clk; +static struct clk *camio_csi1_vfe_clk; +static struct clk *camio_csi0_clk; +static struct clk *camio_csi1_clk; +static struct clk *camio_csi0_pclk; +static struct clk *camio_csi1_pclk; +static struct clk *camio_vfe_pclk; +static struct clk *camio_jpeg_clk; +static struct clk *camio_jpeg_pclk; +static struct clk *camio_vpe_clk; +static struct clk *camio_vpe_pclk; +static struct regulator *fs_vfe; +static struct regulator *fs_ijpeg; +static struct regulator *fs_vpe; +static struct regulator *ldo15; +static struct regulator *lvs0; +static struct regulator *ldo25; + +static struct msm_camera_io_ext camio_ext; +static struct msm_camera_io_clk camio_clk; +static struct platform_device *camio_dev; +static struct resource *csiio; +void __iomem *csibase; +static int vpe_clk_rate; +struct msm_bus_scale_pdata *cam_bus_scale_table; + +void msm_io_w(u32 data, void __iomem *addr) +{ + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + writel_relaxed((data), (addr)); +} + +void msm_io_w_mb(u32 data, void __iomem *addr) +{ + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + wmb(); + writel_relaxed((data), (addr)); + wmb(); +} + +u32 msm_io_r(void __iomem *addr) +{ + uint32_t data = readl_relaxed(addr); + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + return data; +} + +u32 msm_io_r_mb(void __iomem *addr) +{ + uint32_t data; + rmb(); + data = readl_relaxed(addr); + rmb(); + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + return data; +} + +void msm_io_memcpy_toio(void __iomem *dest_addr, + void __iomem *src_addr, u32 len) +{ + int i; + u32 *d = (u32 *) dest_addr; + u32 *s = (u32 *) src_addr; + /* memcpy_toio does not work. Use writel for now */ + for (i = 0; i < len; i++) + writel_relaxed(*s++, d++); +} + +void msm_io_dump(void __iomem *addr, int size) +{ + char line_str[128], *p_str; + int i; + u32 *p = (u32 *) addr; + u32 data; + CDBG("%s: %p %d\n", __func__, addr, size); + line_str[0] = '\0'; + p_str = line_str; + for (i = 0; i < size/4; i++) { + if (i % 4 == 0) { + sprintf(p_str, "%08x: ", (u32) p); + p_str += 10; + } + data = readl_relaxed(p++); + sprintf(p_str, "%08x ", data); + p_str += 9; + if ((i + 1) % 4 == 0) { + CDBG("%s\n", line_str); + line_str[0] = '\0'; + p_str = line_str; + } + } + if (line_str[0] != '\0') + CDBG("%s\n", line_str); +} + +void msm_io_memcpy(void __iomem *dest_addr, void __iomem *src_addr, u32 len) +{ + CDBG("%s: %p %p %d\n", __func__, dest_addr, src_addr, len); + msm_io_memcpy_toio(dest_addr, src_addr, len / 4); + msm_io_dump(dest_addr, len); +} + +static void msm_camera_vreg_enable(void) +{ + ldo15 = regulator_get(NULL, "8058_l15"); + if (IS_ERR(ldo15)) { + pr_err("%s: VREG LDO15 get failed\n", __func__); + ldo15 = NULL; + return; + } + if (regulator_set_voltage(ldo15, 2850000, 2850000)) { + pr_err("%s: VREG LDO15 set voltage failed\n", __func__); + goto ldo15_disable; + } + if (regulator_enable(ldo15)) { + pr_err("%s: VREG LDO15 enable failed\n", __func__); + goto ldo15_put; + } + + lvs0 = regulator_get(NULL, "8058_lvs0"); + if (IS_ERR(lvs0)) { + pr_err("%s: VREG LVS0 get failed\n", __func__); + lvs0 = NULL; + goto ldo15_disable; + } + if (regulator_enable(lvs0)) { + pr_err("%s: VREG LVS0 enable failed\n", __func__); + goto lvs0_put; + } + + ldo25 = regulator_get(NULL, "8058_l25"); + if (IS_ERR(ldo25)) { + pr_err("%s: VREG LDO25 get failed\n", __func__); + ldo25 = NULL; + goto lvs0_disable; + } + if (regulator_set_voltage(ldo25, 1200000, 1200000)) { + pr_err("%s: VREG LDO25 set voltage failed\n", __func__); + goto ldo25_disable; + } + if (regulator_enable(ldo25)) { + pr_err("%s: VREG LDO25 enable failed\n", __func__); + goto ldo25_put; + } + + fs_vfe = regulator_get(NULL, "fs_vfe"); + if (IS_ERR(fs_vfe)) { + CDBG("%s: Regulator FS_VFE get failed %ld\n", __func__, + PTR_ERR(fs_vfe)); + fs_vfe = NULL; + } else if (regulator_enable(fs_vfe)) { + CDBG("%s: Regulator FS_VFE enable failed\n", __func__); + regulator_put(fs_vfe); + } + return; + +ldo25_disable: + regulator_disable(ldo25); +ldo25_put: + regulator_put(ldo25); +lvs0_disable: + regulator_disable(lvs0); +lvs0_put: + regulator_put(lvs0); +ldo15_disable: + regulator_disable(ldo15); +ldo15_put: + regulator_put(ldo15); +} + +static void msm_camera_vreg_disable(void) +{ + if (ldo15) { + regulator_disable(ldo15); + regulator_put(ldo15); + } + + if (lvs0) { + regulator_disable(lvs0); + regulator_put(lvs0); + } + + if (ldo25) { + regulator_disable(ldo25); + regulator_put(ldo25); + } + + if (fs_vfe) { + regulator_disable(fs_vfe); + regulator_put(fs_vfe); + } +} + +int msm_camio_clk_enable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_CAM_MCLK_CLK: + camio_cam_clk = + clk = clk_get(NULL, "cam_clk"); + msm_camio_clk_rate_set_2(clk, camio_clk.mclk_clk_rate); + break; + + case CAMIO_VFE_CLK: + camio_vfe_clk = + clk = clk_get(NULL, "vfe_clk"); + msm_camio_clk_rate_set_2(clk, camio_clk.vfe_clk_rate); + break; + + case CAMIO_CSI0_VFE_CLK: + camio_csi0_vfe_clk = + clk = clk_get(NULL, "csi_vfe_clk"); + break; + + case CAMIO_CSI1_VFE_CLK: + camio_csi1_vfe_clk = + clk = clk_get(&camio_dev->dev, "csi_vfe_clk"); + break; + + case CAMIO_CSI_SRC_CLK: + camio_csi_src_clk = + clk = clk_get(NULL, "csi_src_clk"); + msm_camio_clk_rate_set_2(clk, 384000000); + break; + + case CAMIO_CSI0_CLK: + camio_csi0_clk = + clk = clk_get(NULL, "csi_clk"); + break; + + case CAMIO_CSI1_CLK: + camio_csi1_clk = + clk = clk_get(&camio_dev->dev, "csi_clk"); + break; + + case CAMIO_VFE_PCLK: + camio_vfe_pclk = + clk = clk_get(NULL, "vfe_pclk"); + break; + + case CAMIO_CSI0_PCLK: + camio_csi0_pclk = + clk = clk_get(NULL, "csi_pclk"); + break; + + case CAMIO_CSI1_PCLK: + camio_csi1_pclk = + clk = clk_get(&camio_dev->dev, "csi_pclk"); + break; + + case CAMIO_JPEG_CLK: + camio_jpeg_clk = + clk = clk_get(NULL, "ijpeg_clk"); + msm_camio_clk_rate_set_2(clk, 228571000); + break; + + case CAMIO_JPEG_PCLK: + camio_jpeg_pclk = + clk = clk_get(NULL, "ijpeg_pclk"); + break; + + case CAMIO_VPE_CLK: + camio_vpe_clk = + clk = clk_get(NULL, "vpe_clk"); + msm_camio_clk_set_min_rate(camio_vpe_clk, vpe_clk_rate); + break; + + case CAMIO_VPE_PCLK: + camio_vpe_pclk = + clk = clk_get(NULL, "vpe_pclk"); + break; + + default: + break; + } + + if (!IS_ERR(clk)) + clk_enable(clk); + else + rc = -1; + return rc; +} + +int msm_camio_clk_disable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_CAM_MCLK_CLK: + clk = camio_cam_clk; + break; + + case CAMIO_VFE_CLK: + clk = camio_vfe_clk; + break; + + case CAMIO_CSI_SRC_CLK: + clk = camio_csi_src_clk; + break; + + case CAMIO_CSI0_VFE_CLK: + clk = camio_csi0_vfe_clk; + break; + + case CAMIO_CSI1_VFE_CLK: + clk = camio_csi1_vfe_clk; + break; + + case CAMIO_CSI0_CLK: + clk = camio_csi0_clk; + break; + + case CAMIO_CSI1_CLK: + clk = camio_csi1_clk; + break; + + case CAMIO_VFE_PCLK: + clk = camio_vfe_pclk; + break; + + case CAMIO_CSI0_PCLK: + clk = camio_csi0_pclk; + break; + + case CAMIO_CSI1_PCLK: + clk = camio_csi1_pclk; + break; + + case CAMIO_JPEG_CLK: + clk = camio_jpeg_clk; + break; + + case CAMIO_JPEG_PCLK: + clk = camio_jpeg_pclk; + break; + + case CAMIO_VPE_CLK: + clk = camio_vpe_clk; + break; + + case CAMIO_VPE_PCLK: + clk = camio_vpe_pclk; + break; + + default: + break; + } + + if (!IS_ERR(clk)) { + clk_disable(clk); + clk_put(clk); + } else + rc = -1; + return rc; +} + +void msm_camio_vfe_clk_rate_set(int rate) +{ + struct clk *clk = camio_vfe_clk; + if (rate > clk_get_rate(clk)) + clk_set_rate(clk, rate); +} + +void msm_camio_clk_rate_set(int rate) +{ + struct clk *clk = camio_cam_clk; + clk_set_rate(clk, rate); +} + +void msm_camio_clk_rate_set_2(struct clk *clk, int rate) +{ + clk_set_rate(clk, rate); +} + +void msm_camio_clk_set_min_rate(struct clk *clk, int rate) +{ + clk_set_min_rate(clk, rate); +} + +static irqreturn_t msm_io_csi_irq(int irq_num, void *data) +{ + uint32_t irq; + irq = msm_io_r(csibase + MIPI_INTERRUPT_STATUS); + CDBG("%s MIPI_INTERRUPT_STATUS = 0x%x\n", __func__, irq); + msm_io_w(irq, csibase + MIPI_INTERRUPT_STATUS); + return IRQ_HANDLED; +} + +int msm_camio_jpeg_clk_disable(void) +{ + int rc = 0; + if (fs_ijpeg) { + rc = regulator_disable(fs_ijpeg); + if (rc < 0) { + CDBG("%s: Regulator disable failed %d\n", __func__, rc); + return rc; + } + regulator_put(fs_ijpeg); + } + rc = msm_camio_clk_disable(CAMIO_JPEG_PCLK); + if (rc < 0) + return rc; + rc = msm_camio_clk_disable(CAMIO_JPEG_CLK); + CDBG("%s: exit %d\n", __func__, rc); + return rc; +} + +int msm_camio_jpeg_clk_enable(void) +{ + int rc = 0; + rc = msm_camio_clk_enable(CAMIO_JPEG_CLK); + if (rc < 0) + return rc; + rc = msm_camio_clk_enable(CAMIO_JPEG_PCLK); + if (rc < 0) + return rc; + fs_ijpeg = regulator_get(NULL, "fs_ijpeg"); + if (IS_ERR(fs_ijpeg)) { + CDBG("%s: Regulator FS_IJPEG get failed %ld\n", __func__, + PTR_ERR(fs_ijpeg)); + fs_ijpeg = NULL; + } else if (regulator_enable(fs_ijpeg)) { + CDBG("%s: Regulator FS_IJPEG enable failed\n", __func__); + regulator_put(fs_ijpeg); + } + CDBG("%s: exit %d\n", __func__, rc); + return rc; +} + +int msm_camio_vpe_clk_disable(void) +{ + int rc = 0; + if (fs_vpe) { + regulator_disable(fs_vpe); + regulator_put(fs_vpe); + } + + rc = msm_camio_clk_disable(CAMIO_VPE_CLK); + if (rc < 0) + return rc; + rc = msm_camio_clk_disable(CAMIO_VPE_PCLK); + return rc; +} + +int msm_camio_vpe_clk_enable(uint32_t clk_rate) +{ + int rc = 0; + fs_vpe = regulator_get(NULL, "fs_vpe"); + if (IS_ERR(fs_vpe)) { + CDBG("%s: Regulator FS_VPE get failed %ld\n", __func__, + PTR_ERR(fs_vpe)); + fs_vpe = NULL; + } else if (regulator_enable(fs_vpe)) { + CDBG("%s: Regulator FS_VPE enable failed\n", __func__); + regulator_put(fs_vpe); + } + + vpe_clk_rate = clk_rate; + rc = msm_camio_clk_enable(CAMIO_VPE_CLK); + if (rc < 0) + return rc; + + rc = msm_camio_clk_enable(CAMIO_VPE_PCLK); + return rc; +} + +int msm_camio_enable(struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + uint32_t val; + + camio_dev = pdev; + camio_ext = camdev->ioext; + camio_clk = camdev->ioclk; + cam_bus_scale_table = camdev->cam_bus_scale_table; + + msm_camio_clk_enable(CAMIO_VFE_CLK); + msm_camio_clk_enable(CAMIO_CSI0_VFE_CLK); + msm_camio_clk_enable(CAMIO_CSI1_VFE_CLK); + msm_camio_clk_enable(CAMIO_CSI_SRC_CLK); + msm_camio_clk_enable(CAMIO_CSI0_CLK); + msm_camio_clk_enable(CAMIO_CSI1_CLK); + msm_camio_clk_enable(CAMIO_VFE_PCLK); + msm_camio_clk_enable(CAMIO_CSI0_PCLK); + msm_camio_clk_enable(CAMIO_CSI1_PCLK); + + csiio = request_mem_region(camio_ext.csiphy, + camio_ext.csisz, pdev->name); + if (!csiio) { + rc = -EBUSY; + goto common_fail; + } + csibase = ioremap(camio_ext.csiphy, + camio_ext.csisz); + if (!csibase) { + rc = -ENOMEM; + goto csi_busy; + } + rc = request_irq(camio_ext.csiirq, msm_io_csi_irq, + IRQF_TRIGGER_RISING, "csi", 0); + if (rc < 0) + goto csi_irq_fail; + + msleep(10); + val = (20 << + MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT) | + (0x0F << MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT); + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + + val = (0x0F << MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + return 0; + +csi_irq_fail: + iounmap(csibase); +csi_busy: + release_mem_region(camio_ext.csiphy, camio_ext.csisz); +common_fail: + msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); + msm_camio_clk_disable(CAMIO_CSI0_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI0_CLK); + msm_camio_clk_disable(CAMIO_CSI1_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI1_CLK); + msm_camio_clk_disable(CAMIO_VFE_PCLK); + msm_camio_clk_disable(CAMIO_CSI0_PCLK); + msm_camio_clk_disable(CAMIO_CSI1_PCLK); + msm_camera_vreg_disable(); + camdev->camera_gpio_off(); + return rc; +} + +void msm_camio_disable(struct platform_device *pdev) +{ + uint32_t val; + val = (0x0 << MIPI_CALIBRATION_CONTROL_SWCAL_CAL_EN_SHFT) | + (0x0 << + MIPI_CALIBRATION_CONTROL_SWCAL_STRENGTH_OVERRIDE_EN_SHFT) | + (0x0 << MIPI_CALIBRATION_CONTROL_CAL_SW_HW_MODE_SHFT) | + (0x0 << MIPI_CALIBRATION_CONTROL_MANUAL_OVERRIDE_EN_SHFT); + CDBG("%s MIPI_CALIBRATION_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_CALIBRATION_CONTROL); + + val = (20 << + MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT) | + (0x0F << MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT); + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + + val = (0x0F << MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + msleep(10); + + val = msm_io_r(csibase + MIPI_PHY_D1_CONTROL); + val &= ~((0x1 << MIPI_PHY_D1_CONTROL_MIPI_CLK_PHY_SHUTDOWNB_SHFT) | + (0x1 << MIPI_PHY_D1_CONTROL_MIPI_DATA_PHY_SHUTDOWNB_SHFT)); + CDBG("%s MIPI_PHY_D1_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL); + usleep_range(5000, 6000); + free_irq(camio_ext.csiirq, 0); + iounmap(csibase); + release_mem_region(camio_ext.csiphy, camio_ext.csisz); + CDBG("disable clocks\n"); + + msm_camio_clk_disable(CAMIO_CSI0_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI0_CLK); + msm_camio_clk_disable(CAMIO_CSI1_VFE_CLK); + msm_camio_clk_disable(CAMIO_CSI1_CLK); + msm_camio_clk_disable(CAMIO_VFE_PCLK); + msm_camio_clk_disable(CAMIO_CSI0_PCLK); + msm_camio_clk_disable(CAMIO_CSI1_PCLK); + msm_camio_clk_disable(CAMIO_CSI_SRC_CLK); + msm_camio_clk_disable(CAMIO_VFE_CLK); +} + +int msm_camio_sensor_clk_on(struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camio_dev = pdev; + camio_ext = camdev->ioext; + camio_clk = camdev->ioclk; + + msm_camera_vreg_enable(); + msleep(10); + rc = camdev->camera_gpio_on(); + if (rc < 0) + return rc; + return msm_camio_clk_enable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_sensor_clk_off(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + msm_camera_vreg_disable(); + camdev->camera_gpio_off(); + return msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); + +} + +void msm_camio_vfe_blk_reset(void) +{ + return; +} + +int msm_camio_probe_on(struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camio_dev = pdev; + camio_ext = camdev->ioext; + camio_clk = camdev->ioclk; + + rc = camdev->camera_gpio_on(); + if (rc < 0) + return rc; + msm_camera_vreg_enable(); + return msm_camio_clk_enable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_probe_off(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + msm_camera_vreg_disable(); + camdev->camera_gpio_off(); + return msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_csi_config(struct msm_camera_csi_params *csi_params) +{ + int rc = 0; + uint32_t val = 0; + + CDBG("msm_camio_csi_config \n"); + + /* SOT_ECC_EN enable error correction for SYNC (data-lane) */ + msm_io_w(0x4, csibase + MIPI_PHY_CONTROL); + + /* SW_RST to the CSI core */ + msm_io_w(MIPI_PROTOCOL_CONTROL_SW_RST_BMSK, + csibase + MIPI_PROTOCOL_CONTROL); + + /* PROTOCOL CONTROL */ + val = MIPI_PROTOCOL_CONTROL_LONG_PACKET_HEADER_CAPTURE_BMSK | + MIPI_PROTOCOL_CONTROL_DECODE_ID_BMSK | + MIPI_PROTOCOL_CONTROL_ECC_EN_BMSK; + val |= (uint32_t)(csi_params->data_format) << + MIPI_PROTOCOL_CONTROL_DATA_FORMAT_SHFT; + val |= csi_params->dpcm_scheme << + MIPI_PROTOCOL_CONTROL_DPCM_SCHEME_SHFT; + CDBG("%s MIPI_PROTOCOL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PROTOCOL_CONTROL); + + /* SW CAL EN */ + val = (0x1 << MIPI_CALIBRATION_CONTROL_SWCAL_CAL_EN_SHFT) | + (0x1 << + MIPI_CALIBRATION_CONTROL_SWCAL_STRENGTH_OVERRIDE_EN_SHFT) | + (0x1 << MIPI_CALIBRATION_CONTROL_CAL_SW_HW_MODE_SHFT) | + (0x1 << MIPI_CALIBRATION_CONTROL_MANUAL_OVERRIDE_EN_SHFT); + CDBG("%s MIPI_CALIBRATION_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_CALIBRATION_CONTROL); + + /* settle_cnt is very sensitive to speed! + increase this value to run at higher speeds */ + val = (csi_params->settle_cnt << + MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT) | + (0x0F << MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT); + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + + + val = (0x0F << MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT) | + (0x1 << MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + + val = 0 << MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT; + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL); + + val = (0x1 << MIPI_PHY_D1_CONTROL_MIPI_CLK_PHY_SHUTDOWNB_SHFT) | + (0x1 << MIPI_PHY_D1_CONTROL_MIPI_DATA_PHY_SHUTDOWNB_SHFT); + CDBG("%s MIPI_PHY_D1_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL); + + msm_io_w(0x00000000, csibase + MIPI_PHY_D2_CONTROL); + msm_io_w(0x00000000, csibase + MIPI_PHY_D3_CONTROL); + + /* halcyon only supports 1 or 2 lane */ + switch (csi_params->lane_cnt) { + case 1: + msm_io_w(csi_params->lane_assign << 8 | 0x4, + csibase + MIPI_CAMERA_CNTL); + break; + case 2: + msm_io_w(csi_params->lane_assign << 8 | 0x5, + csibase + MIPI_CAMERA_CNTL); + break; + case 3: + msm_io_w(csi_params->lane_assign << 8 | 0x6, + csibase + MIPI_CAMERA_CNTL); + break; + case 4: + msm_io_w(csi_params->lane_assign << 8 | 0x7, + csibase + MIPI_CAMERA_CNTL); + break; + } + + /* mask out ID_ERROR[19], DATA_CMM_ERR[11] + and CLK_CMM_ERR[10] - de-featured */ + msm_io_w(0xFFF7F3FF, csibase + MIPI_INTERRUPT_MASK); + /*clear IRQ bits*/ + msm_io_w(0xFFF7F3FF, csibase + MIPI_INTERRUPT_STATUS); + + return rc; +} + +void msm_camio_set_perf_lvl(enum msm_bus_perf_setting perf_setting) +{ + static uint32_t bus_perf_client; + int rc = 0; + switch (perf_setting) { + case S_INIT: + bus_perf_client = + msm_bus_scale_register_client(cam_bus_scale_table); + if (!bus_perf_client) { + pr_err("%s: Registration Failed!!!\n", __func__); + bus_perf_client = 0; + return; + } + CDBG("%s: S_INIT rc = %u\n", __func__, bus_perf_client); + break; + case S_EXIT: + if (bus_perf_client) { + CDBG("%s: S_EXIT\n", __func__); + msm_bus_scale_unregister_client(bus_perf_client); + } else + pr_err("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_PREVIEW: + if (bus_perf_client) { + rc = msm_bus_scale_client_update_request( + bus_perf_client, 1); + CDBG("%s: S_PREVIEW rc = %d\n", __func__, rc); + } else + pr_err("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_VIDEO: + if (bus_perf_client) { + rc = msm_bus_scale_client_update_request( + bus_perf_client, 2); + CDBG("%s: S_VIDEO rc = %d\n", __func__, rc); + } else + pr_err("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_CAPTURE: + if (bus_perf_client) { + rc = msm_bus_scale_client_update_request( + bus_perf_client, 3); + CDBG("%s: S_CAPTURE rc = %d\n", __func__, rc); + } else + pr_err("%s: Bus Client NOT Registered!!!\n", __func__); + break; + + case S_ZSL: + if (bus_perf_client) { + rc = msm_bus_scale_client_update_request( + bus_perf_client, 4); + CDBG("%s: S_ZSL rc = %d\n", __func__, rc); + } else + pr_err("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_STEREO_VIDEO: + if (bus_perf_client) { + rc = msm_bus_scale_client_update_request( + bus_perf_client, 5); + CDBG("%s: S_STEREO_VIDEO rc = %d\n", __func__, rc); + } else + pr_err("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_STEREO_CAPTURE: + if (bus_perf_client) { + rc = msm_bus_scale_client_update_request( + bus_perf_client, 6); + CDBG("%s: S_STEREO_VIDEO rc = %d\n", __func__, rc); + } else + pr_err("%s: Bus Client NOT Registered!!!\n", __func__); + break; + case S_DEFAULT: + break; + default: + pr_warning("%s: INVALID CASE\n", __func__); + } +} diff --git a/drivers/media/video/msm/msm_io_vfe31.c b/drivers/media/video/msm/msm_io_vfe31.c new file mode 100644 index 00000000000..6279b36aa2c --- /dev/null +++ b/drivers/media/video/msm/msm_io_vfe31.c @@ -0,0 +1,924 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CAMIF_CFG_RMSK 0x1fffff +#define CAM_SEL_BMSK 0x2 +#define CAM_PCLK_SRC_SEL_BMSK 0x60000 +#define CAM_PCLK_INVERT_BMSK 0x80000 +#define CAM_PAD_REG_SW_RESET_BMSK 0x100000 + +#define EXT_CAM_HSYNC_POL_SEL_BMSK 0x10000 +#define EXT_CAM_VSYNC_POL_SEL_BMSK 0x8000 +#define MDDI_CLK_CHICKEN_BIT_BMSK 0x80 + +#define CAM_SEL_SHFT 0x1 +#define CAM_PCLK_SRC_SEL_SHFT 0x11 +#define CAM_PCLK_INVERT_SHFT 0x13 +#define CAM_PAD_REG_SW_RESET_SHFT 0x14 + +#define EXT_CAM_HSYNC_POL_SEL_SHFT 0x10 +#define EXT_CAM_VSYNC_POL_SEL_SHFT 0xF +#define MDDI_CLK_CHICKEN_BIT_SHFT 0x7 + +/* MIPI CSI controller registers */ +#define MIPI_PHY_CONTROL 0x00000000 +#define MIPI_PROTOCOL_CONTROL 0x00000004 +#define MIPI_INTERRUPT_STATUS 0x00000008 +#define MIPI_INTERRUPT_MASK 0x0000000C +#define MIPI_CAMERA_CNTL 0x00000024 +#define MIPI_CALIBRATION_CONTROL 0x00000018 +#define MIPI_PHY_D0_CONTROL2 0x00000038 +#define MIPI_PHY_D1_CONTROL2 0x0000003C +#define MIPI_PHY_D2_CONTROL2 0x00000040 +#define MIPI_PHY_D3_CONTROL2 0x00000044 +#define MIPI_PHY_CL_CONTROL 0x00000048 +#define MIPI_PHY_D0_CONTROL 0x00000034 +#define MIPI_PHY_D1_CONTROL 0x00000020 +#define MIPI_PHY_D2_CONTROL 0x0000002C +#define MIPI_PHY_D3_CONTROL 0x00000030 +#define MIPI_PROTOCOL_CONTROL_SW_RST_BMSK 0x8000000 +#define MIPI_PROTOCOL_CONTROL_LONG_PACKET_HEADER_CAPTURE_BMSK 0x200000 +#define MIPI_PROTOCOL_CONTROL_DATA_FORMAT_BMSK 0x180000 +#define MIPI_PROTOCOL_CONTROL_DECODE_ID_BMSK 0x40000 +#define MIPI_PROTOCOL_CONTROL_ECC_EN_BMSK 0x20000 +#define MIPI_CALIBRATION_CONTROL_SWCAL_CAL_EN_SHFT 0x16 +#define MIPI_CALIBRATION_CONTROL_SWCAL_STRENGTH_OVERRIDE_EN_SHFT 0x15 +#define MIPI_CALIBRATION_CONTROL_CAL_SW_HW_MODE_SHFT 0x14 +#define MIPI_CALIBRATION_CONTROL_MANUAL_OVERRIDE_EN_SHFT 0x7 +#define MIPI_PROTOCOL_CONTROL_DATA_FORMAT_SHFT 0x13 +#define MIPI_PROTOCOL_CONTROL_DPCM_SCHEME_SHFT 0x1e +#define MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 +#define MIPI_PHY_D1_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D1_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D1_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D1_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 +#define MIPI_PHY_D2_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D2_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D2_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D2_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 +#define MIPI_PHY_D3_CONTROL2_SETTLE_COUNT_SHFT 0x18 +#define MIPI_PHY_D3_CONTROL2_HS_TERM_IMP_SHFT 0x10 +#define MIPI_PHY_D3_CONTROL2_LP_REC_EN_SHFT 0x4 +#define MIPI_PHY_D3_CONTROL2_ERR_SOT_HS_EN_SHFT 0x3 +#define MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT 0x18 +#define MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT 0x2 +#define MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT 0x1c +#define MIPI_PHY_D1_CONTROL_MIPI_CLK_PHY_SHUTDOWNB_SHFT 0x9 +#define MIPI_PHY_D1_CONTROL_MIPI_DATA_PHY_SHUTDOWNB_SHFT 0x8 + +#define CAMIO_VFE_CLK_SNAP 122880000 +#define CAMIO_VFE_CLK_PREV 122880000 + +#ifdef CONFIG_MSM_NPA_SYSTEM_BUS +/* NPA Flow IDs */ +#define MSM_AXI_QOS_PREVIEW MSM_AXI_FLOW_CAMERA_PREVIEW_HIGH +#define MSM_AXI_QOS_SNAPSHOT MSM_AXI_FLOW_CAMERA_SNAPSHOT_12MP +#define MSM_AXI_QOS_RECORDING MSM_AXI_FLOW_CAMERA_RECORDING_720P +#else +/* AXI rates in KHz */ +#define MSM_AXI_QOS_PREVIEW 192000 +#define MSM_AXI_QOS_SNAPSHOT 192000 +#define MSM_AXI_QOS_RECORDING 192000 +#endif + +static struct clk *camio_vfe_mdc_clk; +static struct clk *camio_mdc_clk; +static struct clk *camio_vfe_clk; +static struct clk *camio_vfe_camif_clk; +static struct clk *camio_vfe_pbdg_clk; +static struct clk *camio_cam_m_clk; +static struct clk *camio_camif_pad_pbdg_clk; +static struct clk *camio_csi_clk; +static struct clk *camio_csi_pclk; +static struct clk *camio_csi_vfe_clk; +static struct clk *camio_jpeg_clk; +static struct clk *camio_jpeg_pclk; +static struct clk *camio_vpe_clk; +static struct vreg *vreg_gp2; +static struct vreg *vreg_lvsw1; +static struct vreg *vreg_gp6; +static struct vreg *vreg_gp16; +static struct regulator *fs_vfe; +static struct regulator *fs_vpe; +static struct msm_camera_io_ext camio_ext; +static struct msm_camera_io_clk camio_clk; +static struct resource *camifpadio, *csiio; +void __iomem *camifpadbase, *csibase; +static uint32_t vpe_clk_rate; + +void msm_io_w(u32 data, void __iomem *addr) +{ + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + writel_relaxed((data), (addr)); +} + +void msm_io_w_mb(u32 data, void __iomem *addr) +{ + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + wmb(); + writel_relaxed((data), (addr)); + wmb(); +} + +u32 msm_io_r(void __iomem *addr) +{ + uint32_t data = readl_relaxed(addr); + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + return data; +} + +u32 msm_io_r_mb(void __iomem *addr) +{ + uint32_t data; + rmb(); + data = readl_relaxed(addr); + rmb(); + CDBG("%s: %08x %08x\n", __func__, (int) (addr), (data)); + return data; +} + +void msm_io_memcpy_toio(void __iomem *dest_addr, + void __iomem *src_addr, u32 len) +{ + int i; + u32 *d = (u32 *) dest_addr; + u32 *s = (u32 *) src_addr; + /* memcpy_toio does not work. Use writel for now */ + for (i = 0; i < len; i++) + writel_relaxed(*s++, d++); +} + +void msm_io_dump(void __iomem *addr, int size) +{ + char line_str[128], *p_str; + int i; + u32 *p = (u32 *) addr; + u32 data; + CDBG("%s: %p %d\n", __func__, addr, size); + line_str[0] = '\0'; + p_str = line_str; + for (i = 0; i < size/4; i++) { + if (i % 4 == 0) { + sprintf(p_str, "%08x: ", (u32) p); + p_str += 10; + } + data = readl_relaxed(p++); + sprintf(p_str, "%08x ", data); + p_str += 9; + if ((i + 1) % 4 == 0) { + CDBG("%s\n", line_str); + line_str[0] = '\0'; + p_str = line_str; + } + } + if (line_str[0] != '\0') + CDBG("%s\n", line_str); +} + +void msm_io_memcpy(void __iomem *dest_addr, void __iomem *src_addr, u32 len) +{ + CDBG("%s: %p %p %d\n", __func__, dest_addr, src_addr, len); + msm_io_memcpy_toio(dest_addr, src_addr, len / 4); + msm_io_dump(dest_addr, len); +} + +static void msm_camera_vreg_enable(struct platform_device *pdev) +{ + vreg_gp2 = vreg_get(NULL, "gp2"); + if (IS_ERR(vreg_gp2)) { + pr_err("%s: VREG GP2 get failed %ld\n", __func__, + PTR_ERR(vreg_gp2)); + vreg_gp2 = NULL; + return; + } + + if (vreg_set_level(vreg_gp2, 2600)) { + pr_err("%s: VREG GP2 set failed\n", __func__); + goto gp2_put; + } + + if (vreg_enable(vreg_gp2)) { + pr_err("%s: VREG GP2 enable failed\n", __func__); + goto gp2_put; + } + + vreg_lvsw1 = vreg_get(NULL, "lvsw1"); + if (IS_ERR(vreg_lvsw1)) { + pr_err("%s: VREG LVSW1 get failed %ld\n", __func__, + PTR_ERR(vreg_lvsw1)); + vreg_lvsw1 = NULL; + goto gp2_disable; + } + if (vreg_set_level(vreg_lvsw1, 1800)) { + pr_err("%s: VREG LVSW1 set failed\n", __func__); + goto lvsw1_put; + } + if (vreg_enable(vreg_lvsw1)) { + pr_err("%s: VREG LVSW1 enable failed\n", __func__); + goto lvsw1_put; + } + + if (!strcmp(pdev->name, "msm_camera_sn12m0pz")) { + vreg_gp6 = vreg_get(NULL, "gp6"); + if (IS_ERR(vreg_gp6)) { + pr_err("%s: VREG GP6 get failed %ld\n", __func__, + PTR_ERR(vreg_gp6)); + vreg_gp6 = NULL; + goto lvsw1_disable; + } + + if (vreg_set_level(vreg_gp6, 3050)) { + pr_err("%s: VREG GP6 set failed\n", __func__); + goto gp6_put; + } + + if (vreg_enable(vreg_gp6)) { + pr_err("%s: VREG GP6 enable failed\n", __func__); + goto gp6_put; + } + vreg_gp16 = vreg_get(NULL, "gp16"); + if (IS_ERR(vreg_gp16)) { + pr_err("%s: VREG GP16 get failed %ld\n", __func__, + PTR_ERR(vreg_gp16)); + vreg_gp16 = NULL; + goto gp6_disable; + } + + if (vreg_set_level(vreg_gp16, 1200)) { + pr_err("%s: VREG GP16 set failed\n", __func__); + goto gp16_put; + } + + if (vreg_enable(vreg_gp16)) { + pr_err("%s: VREG GP16 enable failed\n", __func__); + goto gp16_put; + } + } + + fs_vfe = regulator_get(NULL, "fs_vfe"); + if (IS_ERR(fs_vfe)) { + pr_err("%s: Regulator FS_VFE get failed %ld\n", __func__, + PTR_ERR(fs_vfe)); + fs_vfe = NULL; + } else if (regulator_enable(fs_vfe)) { + pr_err("%s: Regulator FS_VFE enable failed\n", __func__); + regulator_put(fs_vfe); + } + + return; + +gp16_put: + vreg_put(vreg_gp16); + vreg_gp16 = NULL; +gp6_disable: + vreg_disable(vreg_gp6); +gp6_put: + vreg_put(vreg_gp6); + vreg_gp6 = NULL; +lvsw1_disable: + vreg_disable(vreg_lvsw1); +lvsw1_put: + vreg_put(vreg_lvsw1); + vreg_lvsw1 = NULL; +gp2_disable: + vreg_disable(vreg_gp2); +gp2_put: + vreg_put(vreg_gp2); + vreg_gp2 = NULL; +} + +static void msm_camera_vreg_disable(void) +{ + if (vreg_gp2) { + vreg_disable(vreg_gp2); + vreg_put(vreg_gp2); + vreg_gp2 = NULL; + } + if (vreg_lvsw1) { + vreg_disable(vreg_lvsw1); + vreg_put(vreg_lvsw1); + vreg_lvsw1 = NULL; + } + if (vreg_gp6) { + vreg_disable(vreg_gp6); + vreg_put(vreg_gp6); + vreg_gp6 = NULL; + } + if (vreg_gp16) { + vreg_disable(vreg_gp16); + vreg_put(vreg_gp16); + vreg_gp16 = NULL; + } + if (fs_vfe) { + regulator_disable(fs_vfe); + regulator_put(fs_vfe); + } +} + +int msm_camio_clk_enable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_VFE_MDC_CLK: + camio_vfe_mdc_clk = + clk = clk_get(NULL, "vfe_mdc_clk"); + break; + + case CAMIO_MDC_CLK: + camio_mdc_clk = + clk = clk_get(NULL, "mdc_clk"); + break; + + case CAMIO_VFE_CLK: + camio_vfe_clk = + clk = clk_get(NULL, "vfe_clk"); + msm_camio_clk_rate_set_2(clk, camio_clk.vfe_clk_rate); + break; + + case CAMIO_VFE_CAMIF_CLK: + camio_vfe_camif_clk = + clk = clk_get(NULL, "vfe_camif_clk"); + break; + + case CAMIO_VFE_PBDG_CLK: + camio_vfe_pbdg_clk = + clk = clk_get(NULL, "vfe_pclk"); + break; + + case CAMIO_CAM_MCLK_CLK: + camio_cam_m_clk = + clk = clk_get(NULL, "cam_m_clk"); + msm_camio_clk_rate_set_2(clk, camio_clk.mclk_clk_rate); + break; + + case CAMIO_CAMIF_PAD_PBDG_CLK: + camio_camif_pad_pbdg_clk = + clk = clk_get(NULL, "camif_pad_pclk"); + break; + + case CAMIO_CSI0_CLK: + camio_csi_clk = + clk = clk_get(NULL, "csi_clk"); + msm_camio_clk_rate_set_2(clk, 153600000); + break; + case CAMIO_CSI0_VFE_CLK: + camio_csi_vfe_clk = + clk = clk_get(NULL, "csi_vfe_clk"); + break; + case CAMIO_CSI0_PCLK: + camio_csi_pclk = + clk = clk_get(NULL, "csi_pclk"); + break; + + case CAMIO_JPEG_CLK: + camio_jpeg_clk = + clk = clk_get(NULL, "jpeg_clk"); + clk_set_min_rate(clk, 144000000); + break; + case CAMIO_JPEG_PCLK: + camio_jpeg_pclk = + clk = clk_get(NULL, "jpeg_pclk"); + break; + case CAMIO_VPE_CLK: + camio_vpe_clk = + clk = clk_get(NULL, "vpe_clk"); + msm_camio_clk_set_min_rate(clk, vpe_clk_rate); + break; + default: + break; + } + + if (!IS_ERR(clk)) + clk_enable(clk); + else + rc = -1; + return rc; +} + +int msm_camio_clk_disable(enum msm_camio_clk_type clktype) +{ + int rc = 0; + struct clk *clk = NULL; + + switch (clktype) { + case CAMIO_VFE_MDC_CLK: + clk = camio_vfe_mdc_clk; + break; + + case CAMIO_MDC_CLK: + clk = camio_mdc_clk; + break; + + case CAMIO_VFE_CLK: + clk = camio_vfe_clk; + break; + + case CAMIO_VFE_CAMIF_CLK: + clk = camio_vfe_camif_clk; + break; + + case CAMIO_VFE_PBDG_CLK: + clk = camio_vfe_pbdg_clk; + break; + + case CAMIO_CAM_MCLK_CLK: + clk = camio_cam_m_clk; + break; + + case CAMIO_CAMIF_PAD_PBDG_CLK: + clk = camio_camif_pad_pbdg_clk; + break; + case CAMIO_CSI0_CLK: + clk = camio_csi_clk; + break; + case CAMIO_CSI0_VFE_CLK: + clk = camio_csi_vfe_clk; + break; + case CAMIO_CSI0_PCLK: + clk = camio_csi_pclk; + break; + case CAMIO_JPEG_CLK: + clk = camio_jpeg_clk; + break; + case CAMIO_JPEG_PCLK: + clk = camio_jpeg_pclk; + break; + case CAMIO_VPE_CLK: + clk = camio_vpe_clk; + break; + default: + break; + } + + if (!IS_ERR(clk)) { + clk_disable(clk); + clk_put(clk); + } else + rc = -1; + + return rc; +} + +void msm_camio_clk_rate_set(int rate) +{ + struct clk *clk = camio_cam_m_clk; + clk_set_rate(clk, rate); +} + +void msm_camio_vfe_clk_rate_set(int rate) +{ + struct clk *clk = camio_vfe_clk; + clk_set_rate(clk, rate); +} + +void msm_camio_clk_rate_set_2(struct clk *clk, int rate) +{ + clk_set_rate(clk, rate); +} + +void msm_camio_clk_set_min_rate(struct clk *clk, int rate) +{ + clk_set_min_rate(clk, rate); +} + +static irqreturn_t msm_io_csi_irq(int irq_num, void *data) +{ + uint32_t irq; + irq = msm_io_r(csibase + MIPI_INTERRUPT_STATUS); + CDBG("%s MIPI_INTERRUPT_STATUS = 0x%x\n", __func__, irq); + msm_io_w(irq, csibase + MIPI_INTERRUPT_STATUS); + return IRQ_HANDLED; +} + +int msm_camio_jpeg_clk_disable(void) +{ + msm_camio_clk_disable(CAMIO_JPEG_CLK); + msm_camio_clk_disable(CAMIO_JPEG_PCLK); + /* Need to add the code for remove PM QOS requirement */ + return 0; +} + + +int msm_camio_jpeg_clk_enable(void) +{ + msm_camio_clk_enable(CAMIO_JPEG_CLK); + msm_camio_clk_enable(CAMIO_JPEG_PCLK); + return 0; +} + +int msm_camio_vpe_clk_disable(void) +{ + msm_camio_clk_disable(CAMIO_VPE_CLK); + + if (fs_vpe) { + regulator_disable(fs_vpe); + regulator_put(fs_vpe); + } + + return 0; +} + +int msm_camio_vpe_clk_enable(uint32_t clk_rate) +{ + fs_vpe = regulator_get(NULL, "fs_vpe"); + if (IS_ERR(fs_vpe)) { + pr_err("%s: Regulator FS_VPE get failed %ld\n", __func__, + PTR_ERR(fs_vpe)); + fs_vpe = NULL; + } else if (regulator_enable(fs_vpe)) { + pr_err("%s: Regulator FS_VPE enable failed\n", __func__); + regulator_put(fs_vpe); + } + + vpe_clk_rate = clk_rate; + msm_camio_clk_enable(CAMIO_VPE_CLK); + return 0; +} + +int msm_camio_enable(struct platform_device *pdev) +{ + int rc = 0; + uint32_t val; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + msm_camio_clk_enable(CAMIO_VFE_PBDG_CLK); + if (!sinfo->csi_if) + msm_camio_clk_enable(CAMIO_VFE_CAMIF_CLK); + else { + msm_camio_clk_enable(CAMIO_VFE_CLK); + csiio = request_mem_region(camio_ext.csiphy, + camio_ext.csisz, pdev->name); + if (!csiio) { + rc = -EBUSY; + goto common_fail; + } + csibase = ioremap(camio_ext.csiphy, + camio_ext.csisz); + if (!csibase) { + rc = -ENOMEM; + goto csi_busy; + } + rc = request_irq(camio_ext.csiirq, msm_io_csi_irq, + IRQF_TRIGGER_RISING, "csi", 0); + if (rc < 0) + goto csi_irq_fail; + /* enable required clocks for CSI */ + msm_camio_clk_enable(CAMIO_CSI0_PCLK); + msm_camio_clk_enable(CAMIO_CSI0_VFE_CLK); + msm_camio_clk_enable(CAMIO_CSI0_CLK); + + msleep(10); + val = (20 << + MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT) | + (0x0F << MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT); + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + + val = (0x0F << MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT) | + (0x0 << MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + } + return 0; +csi_irq_fail: + iounmap(csibase); +csi_busy: + release_mem_region(camio_ext.csiphy, camio_ext.csisz); +common_fail: + msm_camio_clk_disable(CAMIO_VFE_PBDG_CLK); + msm_camio_clk_disable(CAMIO_VFE_CLK); + return rc; +} + +void msm_camio_disable(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + uint32_t val; + if (!sinfo->csi_if) { + msm_camio_clk_disable(CAMIO_VFE_CAMIF_CLK); + } else { + val = (0x0 << MIPI_CALIBRATION_CONTROL_SWCAL_CAL_EN_SHFT) | + (0x0<dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camio_clk = camdev->ioclk; + camio_ext = camdev->ioext; + camdev->camera_gpio_on(); + msm_camera_vreg_enable(pdev); + return msm_camio_clk_enable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_probe_off(struct platform_device *pdev) +{ + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + msm_camera_vreg_disable(); + camdev->camera_gpio_off(); + return msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); +} + +int msm_camio_sensor_clk_on(struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camio_clk = camdev->ioclk; + camio_ext = camdev->ioext; + camdev->camera_gpio_on(); + msm_camera_vreg_enable(pdev); + msm_camio_clk_enable(CAMIO_CAM_MCLK_CLK); + msm_camio_clk_enable(CAMIO_CAMIF_PAD_PBDG_CLK); + if (!sinfo->csi_if) { + camifpadio = request_mem_region(camio_ext.camifpadphy, + camio_ext.camifpadsz, pdev->name); + msm_camio_clk_enable(CAMIO_VFE_CLK); + if (!camifpadio) { + rc = -EBUSY; + goto common_fail; + } + camifpadbase = ioremap(camio_ext.camifpadphy, + camio_ext.camifpadsz); + if (!camifpadbase) { + CDBG("msm_camio_sensor_clk_on fail\n"); + rc = -ENOMEM; + goto parallel_busy; + } + } + return rc; +parallel_busy: + release_mem_region(camio_ext.camifpadphy, camio_ext.camifpadsz); + goto common_fail; +common_fail: + msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); + msm_camio_clk_disable(CAMIO_VFE_CLK); + msm_camio_clk_disable(CAMIO_CAMIF_PAD_PBDG_CLK); + msm_camera_vreg_disable(); + camdev->camera_gpio_off(); + return rc; +} + +int msm_camio_sensor_clk_off(struct platform_device *pdev) +{ + uint32_t rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + camdev->camera_gpio_off(); + msm_camera_vreg_disable(); + rc = msm_camio_clk_disable(CAMIO_CAM_MCLK_CLK); + rc = msm_camio_clk_disable(CAMIO_CAMIF_PAD_PBDG_CLK); + if (!sinfo->csi_if) { + iounmap(camifpadbase); + release_mem_region(camio_ext.camifpadphy, camio_ext.camifpadsz); + rc = msm_camio_clk_disable(CAMIO_VFE_CLK); + } + return rc; +} + +int msm_camio_csi_config(struct msm_camera_csi_params *csi_params) +{ + int rc = 0; + uint32_t val = 0; + + CDBG("msm_camio_csi_config \n"); + + /* SOT_ECC_EN enable error correction for SYNC (data-lane) */ + msm_io_w(0x4, csibase + MIPI_PHY_CONTROL); + + /* SW_RST to the CSI core */ + msm_io_w(MIPI_PROTOCOL_CONTROL_SW_RST_BMSK, + csibase + MIPI_PROTOCOL_CONTROL); + + /* PROTOCOL CONTROL */ + val = MIPI_PROTOCOL_CONTROL_LONG_PACKET_HEADER_CAPTURE_BMSK | + MIPI_PROTOCOL_CONTROL_DECODE_ID_BMSK | + MIPI_PROTOCOL_CONTROL_ECC_EN_BMSK; + val |= (uint32_t)(csi_params->data_format) << + MIPI_PROTOCOL_CONTROL_DATA_FORMAT_SHFT; + val |= csi_params->dpcm_scheme << + MIPI_PROTOCOL_CONTROL_DPCM_SCHEME_SHFT; + CDBG("%s MIPI_PROTOCOL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PROTOCOL_CONTROL); + + /* SW CAL EN */ + val = (0x1 << MIPI_CALIBRATION_CONTROL_SWCAL_CAL_EN_SHFT) | + (0x1 << + MIPI_CALIBRATION_CONTROL_SWCAL_STRENGTH_OVERRIDE_EN_SHFT) | + (0x1 << MIPI_CALIBRATION_CONTROL_CAL_SW_HW_MODE_SHFT) | + (0x1 << MIPI_CALIBRATION_CONTROL_MANUAL_OVERRIDE_EN_SHFT); + CDBG("%s MIPI_CALIBRATION_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_CALIBRATION_CONTROL); + + /* settle_cnt is very sensitive to speed! + increase this value to run at higher speeds */ + val = (csi_params->settle_cnt << + MIPI_PHY_D0_CONTROL2_SETTLE_COUNT_SHFT) | + (0x0F << MIPI_PHY_D0_CONTROL2_HS_TERM_IMP_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_LP_REC_EN_SHFT) | + (0x1 << MIPI_PHY_D0_CONTROL2_ERR_SOT_HS_EN_SHFT); + CDBG("%s MIPI_PHY_D0_CONTROL2 val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D2_CONTROL2); + msm_io_w(val, csibase + MIPI_PHY_D3_CONTROL2); + + + val = (0x0F << MIPI_PHY_CL_CONTROL_HS_TERM_IMP_SHFT) | + (0x1 << MIPI_PHY_CL_CONTROL_LP_REC_EN_SHFT); + CDBG("%s MIPI_PHY_CL_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_CL_CONTROL); + + val = 0 << MIPI_PHY_D0_CONTROL_HS_REC_EQ_SHFT; + msm_io_w(val, csibase + MIPI_PHY_D0_CONTROL); + + val = (0x1 << MIPI_PHY_D1_CONTROL_MIPI_CLK_PHY_SHUTDOWNB_SHFT) | + (0x1 << MIPI_PHY_D1_CONTROL_MIPI_DATA_PHY_SHUTDOWNB_SHFT); + CDBG("%s MIPI_PHY_D1_CONTROL val=0x%x\n", __func__, val); + msm_io_w(val, csibase + MIPI_PHY_D1_CONTROL); + + msm_io_w(0x00000000, csibase + MIPI_PHY_D2_CONTROL); + msm_io_w(0x00000000, csibase + MIPI_PHY_D3_CONTROL); + + /* halcyon only supports 1 or 2 lane */ + switch (csi_params->lane_cnt) { + case 1: + msm_io_w(csi_params->lane_assign << 8 | 0x4, + csibase + MIPI_CAMERA_CNTL); + break; + case 2: + msm_io_w(csi_params->lane_assign << 8 | 0x5, + csibase + MIPI_CAMERA_CNTL); + break; + case 3: + msm_io_w(csi_params->lane_assign << 8 | 0x6, + csibase + MIPI_CAMERA_CNTL); + break; + case 4: + msm_io_w(csi_params->lane_assign << 8 | 0x7, + csibase + MIPI_CAMERA_CNTL); + break; + } + + /* mask out ID_ERROR[19], DATA_CMM_ERR[11] + and CLK_CMM_ERR[10] - de-featured */ + msm_io_w(0xFFF7F3FF, csibase + MIPI_INTERRUPT_MASK); + /*clear IRQ bits*/ + msm_io_w(0xFFF7F3FF, csibase + MIPI_INTERRUPT_STATUS); + + return rc; +} +void msm_camio_set_perf_lvl(enum msm_bus_perf_setting perf_setting) +{ + switch (perf_setting) { + case S_INIT: + add_axi_qos(); + break; + case S_PREVIEW: + update_axi_qos(MSM_AXI_QOS_PREVIEW); + break; + case S_VIDEO: + update_axi_qos(MSM_AXI_QOS_RECORDING); + break; + case S_CAPTURE: + update_axi_qos(MSM_AXI_QOS_SNAPSHOT); + break; + case S_DEFAULT: + update_axi_qos(PM_QOS_DEFAULT_VALUE); + break; + case S_EXIT: + release_axi_qos(); + break; + default: + CDBG("%s: INVALID CASE\n", __func__); + } +} diff --git a/drivers/media/video/msm/msm_isp.c b/drivers/media/video/msm/msm_isp.c new file mode 100644 index 00000000000..480c6263fd3 --- /dev/null +++ b/drivers/media/video/msm/msm_isp.c @@ -0,0 +1,814 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "msm.h" + +#ifdef CONFIG_MSM_CAMERA_DEBUG +#define D(fmt, args...) pr_debug("msm_isp: " fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif +#define ERR_USER_COPY(to) pr_err("%s(%d): copy %s user\n", \ + __func__, __LINE__, ((to) ? "to" : "from")) +#define ERR_COPY_FROM_USER() ERR_USER_COPY(0) +#define ERR_COPY_TO_USER() ERR_USER_COPY(1) + +#define MSM_FRAME_AXI_MAX_BUF 16 +/* This will enqueue ISP events or signal buffer completion */ +static int msm_isp_enqueue(struct msm_cam_media_controller *pmctl, + struct msm_vfe_resp *data, + enum msm_queue qtype) +{ + struct v4l2_event v4l2_evt; + + struct msm_stats_buf stats; + struct msm_isp_stats_event_ctrl *isp_event; + isp_event = (struct msm_isp_stats_event_ctrl *)v4l2_evt.u.data; + if (!data) { + pr_err("%s !!!!data = 0x%p\n", __func__, data); + return -EINVAL; + } + + D("%s data->type = %d\n", __func__, data->type); + + switch (qtype) { + case MSM_CAM_Q_VFE_EVT: + case MSM_CAM_Q_VFE_MSG: + /* adsp event and message */ + v4l2_evt.type = V4L2_EVENT_PRIVATE_START + + MSM_CAM_RESP_STAT_EVT_MSG; + + isp_event->resptype = MSM_CAM_RESP_STAT_EVT_MSG; + + /* 0 - msg from aDSP, 1 - event from mARM */ + isp_event->isp_data.isp_msg.type = data->evt_msg.type; + isp_event->isp_data.isp_msg.msg_id = data->evt_msg.msg_id; + isp_event->isp_data.isp_msg.len = 0; + + D("%s: qtype %d length %d msd_id %d\n", __func__, + qtype, + isp_event->isp_data.isp_msg.len, + isp_event->isp_data.isp_msg.msg_id); + + if ((data->type >= VFE_MSG_STATS_AEC) && + (data->type <= VFE_MSG_STATS_WE)) { + + D("%s data->phy.sbuf_phy = 0x%x\n", __func__, + data->phy.sbuf_phy); + stats.buffer = msm_pmem_stats_ptov_lookup(&pmctl->sync, + data->phy.sbuf_phy, + &(stats.fd)); + if (!stats.buffer) { + pr_err("%s: msm_pmem_stats_ptov_lookup error\n", + __func__); + isp_event->isp_data.isp_msg.len = 0; + } else { + struct msm_stats_buf *stats_buf = + kmalloc(sizeof(struct msm_stats_buf), + GFP_ATOMIC); + if (!stats_buf) { + pr_err("%s: out of memory.\n", + __func__); + return -ENOMEM; + } + + *stats_buf = stats; + isp_event->isp_data.isp_msg.len = + sizeof(struct msm_stats_buf); + isp_event->isp_data.isp_msg.data = stats_buf; + } + + } else if ((data->evt_msg.len > 0) && + (data->type == VFE_MSG_GENERAL)) { + isp_event->isp_data.isp_msg.data = + kmalloc(data->evt_msg.len, GFP_ATOMIC); + if (!isp_event->isp_data.isp_msg.data) { + pr_err("%s: out of memory.\n", __func__); + return -ENOMEM; + } + memcpy(isp_event->isp_data.isp_msg.data, + data->evt_msg.data, + data->evt_msg.len); + } else if (data->type == VFE_MSG_OUTPUT_P || + data->type == VFE_MSG_OUTPUT_V || + data->type == VFE_MSG_OUTPUT_S || + data->type == VFE_MSG_OUTPUT_T) { + msm_mctl_buf_done(pmctl, data->type, + (u32)data->phy.y_phy); + } + break; + default: + break; + } + + /* now queue the event */ + v4l2_event_queue(pmctl->config_device->config_stat_event_queue.pvdev, + &v4l2_evt); + return 0; +} + +/* + * This function executes in interrupt context. + */ + +void *msm_isp_sync_alloc(int size, + void *syncdata __attribute__((unused)), + gfp_t gfp) +{ + struct msm_queue_cmd *qcmd = + kmalloc(sizeof(struct msm_queue_cmd) + size, gfp); + + if (qcmd) { + atomic_set(&qcmd->on_heap, 1); + return qcmd + 1; + } + return NULL; +} + +void msm_isp_sync_free(void *ptr) +{ + if (ptr) { + struct msm_queue_cmd *qcmd = + (struct msm_queue_cmd *)ptr; + qcmd--; + if (atomic_read(&qcmd->on_heap)) + kfree(qcmd); + } +} + +/* + * This function executes in interrupt context. + */ +static int msm_isp_notify(struct v4l2_subdev *sd, void *arg) +{ + int rc = -EINVAL; + struct msm_queue_cmd *qcmd = NULL; + struct msm_sync *sync = + (struct msm_sync *)v4l2_get_subdev_hostdata(sd); + struct msm_vfe_resp *vdata = (struct msm_vfe_resp *)arg; + + if (!sync) { + pr_err("%s: no context in dsp callback.\n", __func__); + return rc; + } + + qcmd = ((struct msm_queue_cmd *)vdata) - 1; + qcmd->type = MSM_CAM_Q_VFE_MSG; + qcmd->command = vdata; + + D("%s: vdata->type %d\n", __func__, vdata->type); + switch (vdata->type) { + case VFE_MSG_STATS_AWB: + D("%s: qtype %d, AWB stats, enqueue event_q.\n", + __func__, vdata->type); + break; + + case VFE_MSG_STATS_AEC: + D("%s: qtype %d, AEC stats, enqueue event_q.\n", + __func__, vdata->type); + break; + + case VFE_MSG_STATS_IHIST: + D("%s: qtype %d, ihist stats, enqueue event_q.\n", + __func__, vdata->type); + break; + + case VFE_MSG_STATS_RS: + D("%s: qtype %d, rs stats, enqueue event_q.\n", + __func__, vdata->type); + break; + + case VFE_MSG_STATS_CS: + D("%s: qtype %d, cs stats, enqueue event_q.\n", + __func__, vdata->type); + break; + + case VFE_MSG_GENERAL: + D("%s: qtype %d, general msg, enqueue event_q.\n", + __func__, vdata->type); + break; + default: + D("%s: qtype %d not handled\n", __func__, vdata->type); + /* fall through, send to config. */ + } + + D("%s: msm_enqueue event_q\n", __func__); + rc = msm_isp_enqueue(&sync->pcam_sync->mctl, vdata, MSM_CAM_Q_VFE_MSG); + + msm_isp_sync_free(vdata); + + return rc; +} + +/* This function is called by open() function, so we need to init HW*/ +static int msm_isp_open(struct v4l2_subdev *sd, struct msm_sync *sync) +{ + /* init vfe and senor, register sync callbacks for init*/ + int rc = 0; + D("%s\n", __func__); + if (!sync) { + pr_err("%s: param is NULL", __func__); + return -EINVAL; + } + + rc = msm_vfe_subdev_init(sd, sync, sync->pdev); + if (rc < 0) { + pr_err("%s: vfe_init failed at %d\n", + __func__, rc); + } + + return rc; +} + +static void msm_isp_release(struct msm_sync *psync) +{ + D("%s\n", __func__); + msm_vfe_subdev_release(psync->pdev); +} + +static int msm_config_vfe(struct v4l2_subdev *sd, + struct msm_sync *sync, void __user *arg) +{ + struct msm_vfe_cfg_cmd cfgcmd; + struct msm_pmem_region region[8]; + struct axidata axi_data; + + if (copy_from_user(&cfgcmd, arg, sizeof(cfgcmd))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + memset(&axi_data, 0, sizeof(axi_data)); + CDBG("%s: cmd_type %d\n", __func__, cfgcmd.cmd_type); + switch (cfgcmd.cmd_type) { + case CMD_STATS_AF_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_AF, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return msm_isp_subdev_ioctl(sd, &cfgcmd, + &axi_data); + case CMD_STATS_AEC_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_AEC, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return msm_isp_subdev_ioctl(sd, &cfgcmd, + &axi_data); + case CMD_STATS_AWB_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_AWB, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return msm_isp_subdev_ioctl(sd, &cfgcmd, + &axi_data); + case CMD_STATS_IHIST_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_IHIST, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return msm_isp_subdev_ioctl(sd, &cfgcmd, + &axi_data); + case CMD_STATS_RS_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_RS, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return msm_isp_subdev_ioctl(sd, &cfgcmd, + &axi_data); + case CMD_STATS_CS_ENABLE: + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, + MSM_PMEM_CS, ®ion[0], + NUM_STAT_OUTPUT_BUFFERS); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + return msm_isp_subdev_ioctl(sd, &cfgcmd, + &axi_data); + case CMD_GENERAL: + case CMD_STATS_DISABLE: + return msm_isp_subdev_ioctl(sd, &cfgcmd, + &axi_data); + default: + pr_err("%s: unknown command type %d\n", + __func__, cfgcmd.cmd_type); + } + + return -EINVAL; +} + +static int msm_vpe_frame_cfg(struct msm_sync *sync, + void *cfgcmdin) +{ + int rc = -EIO; + struct axidata axi_data; + void *data = &axi_data; + struct msm_pmem_region region[8]; + int pmem_type; + + struct msm_vpe_cfg_cmd *cfgcmd; + cfgcmd = (struct msm_vpe_cfg_cmd *)cfgcmdin; + + memset(&axi_data, 0, sizeof(axi_data)); + CDBG("In vpe_frame_cfg cfgcmd->cmd_type = %d\n", + cfgcmd->cmd_type); + switch (cfgcmd->cmd_type) { + case CMD_AXI_CFG_VPE: + pmem_type = MSM_PMEM_VIDEO_VPE; + axi_data.bufnum1 = + msm_pmem_region_lookup_2(&sync->pmem_frames, pmem_type, + ®ion[0], 8); + CDBG("axi_data.bufnum1 = %d\n", axi_data.bufnum1); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + pmem_type = MSM_PMEM_VIDEO; + break; + default: + pr_err("%s: unknown command type %d\n", + __func__, cfgcmd->cmd_type); + break; + } + axi_data.region = ®ion[0]; + CDBG("out vpe_frame_cfg cfgcmd->cmd_type = %d\n", + cfgcmd->cmd_type); + /* send the AXI configuration command to driver */ + if (sync->vpefn.vpe_config) + rc = sync->vpefn.vpe_config(cfgcmd, data); + return rc; +} + +static int msm_stats_axi_cfg(struct v4l2_subdev *sd, + struct msm_sync *sync, struct msm_vfe_cfg_cmd *cfgcmd) +{ + int rc = -EIO; + struct axidata axi_data; + void *data = &axi_data; + struct msm_pmem_region region[3]; + int pmem_type = MSM_PMEM_MAX; + + memset(&axi_data, 0, sizeof(axi_data)); + + switch (cfgcmd->cmd_type) { + case CMD_STATS_AXI_CFG: + pmem_type = MSM_PMEM_AEC_AWB; + break; + case CMD_STATS_AF_AXI_CFG: + pmem_type = MSM_PMEM_AF; + break; + case CMD_GENERAL: + data = NULL; + break; + default: + pr_err("%s: unknown command type %d\n", + __func__, cfgcmd->cmd_type); + return -EINVAL; + } + + if (cfgcmd->cmd_type != CMD_GENERAL) { + axi_data.bufnum1 = + msm_pmem_region_lookup(&sync->pmem_stats, pmem_type, + ®ion[0], NUM_STAT_OUTPUT_BUFFERS); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + axi_data.region = ®ion[0]; + } + + /* send the AEC/AWB STATS configuration command to driver */ + rc = msm_isp_subdev_ioctl(sd, cfgcmd, data); + return rc; +} + +static int msm_frame_axi_cfg(struct v4l2_subdev *sd, + struct msm_sync *sync, struct msm_vfe_cfg_cmd *cfgcmd) +{ + int rc = -EIO; + struct axidata axi_data; + void *data = &axi_data; + struct msm_pmem_region region[MSM_FRAME_AXI_MAX_BUF]; + int pmem_type; + int i = 0; + int idx = 0; + struct msm_cam_v4l2_device *pcam = sync->pcam_sync; + struct msm_cam_v4l2_dev_inst *pcam_inst; + + memset(&axi_data, 0, sizeof(axi_data)); + + switch (cfgcmd->cmd_type) { + + case CMD_AXI_CFG_PREVIEW: + pcam_inst = + pcam->dev_inst_map[MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW]; + if (pcam_inst) + idx = pcam_inst->my_index; + else + return rc; + pmem_type = MSM_PMEM_PREVIEW; + axi_data.bufnum2 = + msm_pmem_region_lookup_3(sync->pcam_sync, idx, + ®ion[0], pmem_type); + if (!axi_data.bufnum2) { + pr_err("%s %d: pmem region 3 lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + D("%s __func__ axi_data.bufnum2 = %d\n", __func__, + axi_data.bufnum2); + break; + + case CMD_AXI_CFG_VIDEO: + pcam_inst = + pcam->dev_inst_map[MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW]; + if (pcam_inst) + idx = pcam_inst->my_index; + else + return rc; + pmem_type = MSM_PMEM_PREVIEW; + axi_data.bufnum1 = + msm_pmem_region_lookup_3(sync->pcam_sync, idx, + ®ion[0], pmem_type); + D("%s bufnum1 = %d\n", __func__, axi_data.bufnum1); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + + pcam_inst + = pcam->dev_inst_map[MSM_V4L2_EXT_CAPTURE_MODE_VIDEO]; + if (pcam_inst) + idx = pcam_inst->my_index; + pmem_type = MSM_PMEM_VIDEO; + axi_data.bufnum2 = + msm_pmem_region_lookup_3(sync->pcam_sync, idx, + ®ion[axi_data.bufnum1], pmem_type); + D("%s bufnum2 = %d\n", __func__, axi_data.bufnum2); + if (!axi_data.bufnum2) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + break; + + + case CMD_AXI_CFG_SNAP: + pcam_inst + = pcam->dev_inst_map[MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL]; + if (pcam_inst) + idx = pcam_inst->my_index; + else + return rc; + pmem_type = MSM_PMEM_THUMBNAIL; + axi_data.bufnum1 = + msm_pmem_region_lookup_3(sync->pcam_sync, idx, + ®ion[0], pmem_type); + if (!axi_data.bufnum1) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + + pcam_inst + = pcam->dev_inst_map[MSM_V4L2_EXT_CAPTURE_MODE_MAIN]; + if (pcam_inst) + idx = pcam_inst->my_index; + else + return rc; + pmem_type = MSM_PMEM_MAINIMG; + axi_data.bufnum2 = + msm_pmem_region_lookup_3(sync->pcam_sync, idx, + ®ion[axi_data.bufnum1], pmem_type); + if (!axi_data.bufnum2) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + break; + + case CMD_RAW_PICT_AXI_CFG: + pcam_inst + = pcam->dev_inst_map[MSM_V4L2_EXT_CAPTURE_MODE_MAIN]; + if (pcam_inst) + idx = pcam_inst->my_index; + else + return rc; + pmem_type = MSM_PMEM_RAW_MAINIMG; + axi_data.bufnum2 = + msm_pmem_region_lookup_3(sync->pcam_sync, idx, + ®ion[0], pmem_type); + if (!axi_data.bufnum2) { + pr_err("%s %d: pmem region lookup error\n", + __func__, __LINE__); + return -EINVAL; + } + break; + + case CMD_GENERAL: + data = NULL; + break; + + default: + pr_err("%s: unknown command type %d\n", + __func__, cfgcmd->cmd_type); + return -EINVAL; + } + + axi_data.region = ®ion[0]; + D("%s bufnum1 = %d, bufnum2 = %d\n", __func__, + axi_data.bufnum1, axi_data.bufnum2); + for (i = 0; i < MSM_FRAME_AXI_MAX_BUF; i++) { + D("%s region %d paddr = 0x%p\n", __func__, i, + (void *)region[i].paddr); + D("%s region y_off = %d cbcr_off = %d\n", __func__, + region[i].info.y_off, region[i].info.cbcr_off); + } + /* send the AXI configuration command to driver */ + rc = msm_isp_subdev_ioctl(sd, cfgcmd, data); + return rc; +} + +static int msm_axi_config(struct v4l2_subdev *sd, + struct msm_sync *sync, void __user *arg) +{ + struct msm_vfe_cfg_cmd cfgcmd; + + if (copy_from_user(&cfgcmd, arg, sizeof(cfgcmd))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + switch (cfgcmd.cmd_type) { + case CMD_AXI_CFG_VIDEO: + case CMD_AXI_CFG_PREVIEW: + case CMD_AXI_CFG_SNAP: + case CMD_RAW_PICT_AXI_CFG: + return msm_frame_axi_cfg(sd, sync, &cfgcmd); + case CMD_AXI_CFG_VPE: + return 0; + return msm_vpe_frame_cfg(sync, (void *)&cfgcmd); + + case CMD_STATS_AXI_CFG: + case CMD_STATS_AF_AXI_CFG: + return msm_stats_axi_cfg(sd, sync, &cfgcmd); + + default: + pr_err("%s: unknown command type %d\n", + __func__, + cfgcmd.cmd_type); + return -EINVAL; + } + + return 0; +} + +static int msm_set_crop(struct msm_sync *sync, void __user *arg) +{ + struct crop_info crop; + + if (copy_from_user(&crop, + arg, + sizeof(struct crop_info))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + if (!sync->croplen) { + sync->cropinfo = kmalloc(crop.len, GFP_KERNEL); + if (!sync->cropinfo) + return -ENOMEM; + } else if (sync->croplen < crop.len) + return -EINVAL; + + if (copy_from_user(sync->cropinfo, + crop.info, + crop.len)) { + ERR_COPY_FROM_USER(); + kfree(sync->cropinfo); + return -EFAULT; + } + + sync->croplen = crop.len; + + return 0; +} + +static int msm_put_stats_buffer(struct v4l2_subdev *sd, + struct msm_sync *sync, void __user *arg) +{ + int rc = -EIO; + + struct msm_stats_buf buf; + unsigned long pphy; + struct msm_vfe_cfg_cmd cfgcmd; + + if (copy_from_user(&buf, arg, + sizeof(struct msm_stats_buf))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + CDBG("%s\n", __func__); + pphy = msm_pmem_stats_vtop_lookup(sync, buf.buffer, buf.fd); + + if (pphy != 0) { + if (buf.type == STAT_AF) + cfgcmd.cmd_type = CMD_STATS_AF_BUF_RELEASE; + else if (buf.type == STAT_AEC) + cfgcmd.cmd_type = CMD_STATS_AEC_BUF_RELEASE; + else if (buf.type == STAT_AWB) + cfgcmd.cmd_type = CMD_STATS_AWB_BUF_RELEASE; + else if (buf.type == STAT_IHIST) + cfgcmd.cmd_type = CMD_STATS_IHIST_BUF_RELEASE; + else if (buf.type == STAT_RS) + cfgcmd.cmd_type = CMD_STATS_RS_BUF_RELEASE; + else if (buf.type == STAT_CS) + cfgcmd.cmd_type = CMD_STATS_CS_BUF_RELEASE; + + else { + pr_err("%s: invalid buf type %d\n", + __func__, + buf.type); + rc = -EINVAL; + goto put_done; + } + + cfgcmd.value = (void *)&buf; + + rc = msm_isp_subdev_ioctl(sd, &cfgcmd, &pphy); + } else { + pr_err("%s: NULL physical address\n", __func__); + rc = -EINVAL; + } + +put_done: + return rc; +} + +/* config function simliar to origanl msm_ioctl_config*/ +static int msm_isp_config(struct msm_cam_media_controller *pmctl, + unsigned int cmd, unsigned long arg) +{ + + int rc = -EINVAL; + void __user *argp = (void __user *)arg; + struct v4l2_subdev *sd = &pmctl->isp_sdev->sd; + + D("%s: cmd %d\n", __func__, _IOC_NR(cmd)); + switch (cmd) { + case MSM_CAM_IOCTL_PICT_PP_DONE: + /* Release the preview of snapshot frame + * that was grabbed. + */ + /*rc = msm_pp_release(pmsm->sync, arg);*/ + break; + + case MSM_CAM_IOCTL_CONFIG_VFE: + /* Coming from config thread for update */ + rc = msm_config_vfe(sd, &pmctl->sync, argp); + break; + + case MSM_CAM_IOCTL_CONFIG_VPE: + /* Coming from config thread for update */ + /*rc = msm_config_vpe(pmsm->sync, argp);*/ + rc = 0; + break; + + case MSM_CAM_IOCTL_AXI_CONFIG: + case MSM_CAM_IOCTL_AXI_VPE_CONFIG: + D("Received MSM_CAM_IOCTL_AXI_CONFIG\n"); + rc = msm_axi_config(sd, &pmctl->sync, argp); + break; + + case MSM_CAM_IOCTL_SET_CROP: + rc = msm_set_crop(&pmctl->sync, argp); + break; + + case MSM_CAM_IOCTL_RELEASE_STATS_BUFFER: + rc = msm_put_stats_buffer(sd, &pmctl->sync, argp); + break; + + default: + break; + } + + D("%s: cmd %d DONE\n", __func__, _IOC_NR(cmd)); + + return rc; +} + +static struct msm_isp_ops isp_subdev[MSM_MAX_CAMERA_CONFIGS]; + +/**/ +int msm_isp_init_module(int g_num_config_nodes) +{ + int i = 0; + + for (i = 0; i < g_num_config_nodes; i++) { + isp_subdev[i].isp_open = msm_isp_open; + isp_subdev[i].isp_config = msm_isp_config; + isp_subdev[i].isp_release = msm_isp_release; + isp_subdev[i].isp_enqueue = msm_isp_enqueue; + isp_subdev[i].isp_notify = msm_isp_notify; + } + return 0; +} +EXPORT_SYMBOL(msm_isp_init_module); + +/* +*/ +int msm_isp_register(struct msm_cam_server_dev *psvr) +{ + int i = 0; + + D("%s\n", __func__); + + BUG_ON(!psvr); + + /* Initialize notify function for v4l2_dev */ + for (i = 0; i < psvr->config_info.num_config_nodes; i++) + psvr->isp_subdev[i] = &(isp_subdev[i]); + + return 0; +} +EXPORT_SYMBOL(msm_isp_register); + +/**/ +void msm_isp_unregister(struct msm_cam_server_dev *psvr) +{ + int i = 0; + for (i = 0; i < psvr->config_info.num_config_nodes; i++) + psvr->isp_subdev[i] = NULL; +} + +int msm_isp_subdev_ioctl(struct v4l2_subdev *isp_subdev, + struct msm_vfe_cfg_cmd *cfgcmd, void *data) +{ + struct msm_camvfe_params vfe_params; + vfe_params.vfe_cfg = cfgcmd; + vfe_params.data = data; + return v4l2_subdev_call(isp_subdev, core, ioctl, 0, &vfe_params); +} diff --git a/drivers/media/video/msm/msm_ispif.c b/drivers/media/video/msm/msm_ispif.c new file mode 100644 index 00000000000..4611d062eef --- /dev/null +++ b/drivers/media/video/msm/msm_ispif.c @@ -0,0 +1,397 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_ispif.h" +#include "msm.h" + +#define DBG_ISPIF 0 +/* ISPIF registers */ + +#define ISPIF_RST_CMD_ADDR 0X00 +#define ISPIF_INTF_CMD_ADDR 0X04 +#define ISPIF_CTRL_ADDR 0X08 +#define ISPIF_INPUT_SEL_ADDR 0X0C +#define ISPIF_PIX_INTF_CID_MASK_ADDR 0X10 +#define ISPIF_RDI_INTF_CID_MASK_ADDR 0X14 +#define ISPIF_PIX_1_INTF_CID_MASK_ADDR 0X38 +#define ISPIF_RDI_1_INTF_CID_MASK_ADDR 0X3C +#define ISPIF_PIX_STATUS_ADDR 0X24 +#define ISPIF_RDI_STATUS_ADDR 0X28 +#define ISPIF_RDI_1_STATUS_ADDR 0X64 +#define ISPIF_IRQ_MASK_ADDR 0X0100 +#define ISPIF_IRQ_CLEAR_ADDR 0X0104 +#define ISPIF_IRQ_STATUS_ADDR 0X0108 +#define ISPIF_IRQ_MASK_1_ADDR 0X010C +#define ISPIF_IRQ_CLEAR_1_ADDR 0X0110 +#define ISPIF_IRQ_STATUS_1_ADDR 0X0114 + +/*ISPIF RESET BITS*/ + +#define VFE_CLK_DOMAIN_RST 31 +#define RDI_CLK_DOMAIN_RST 30 +#define PIX_CLK_DOMAIN_RST 29 +#define AHB_CLK_DOMAIN_RST 28 +#define RDI_1_CLK_DOMAIN_RST 27 +#define RDI_1_VFE_RST_STB 13 +#define RDI_1_CSID_RST_STB 12 +#define RDI_VFE_RST_STB 7 +#define RDI_CSID_RST_STB 6 +#define PIX_VFE_RST_STB 4 +#define PIX_CSID_RST_STB 3 +#define SW_REG_RST_STB 2 +#define MISC_LOGIC_RST_STB 1 +#define STROBED_RST_EN 0 + +#define PIX_INTF_0_OVERFLOW_IRQ 12 +#define RAW_INTF_0_OVERFLOW_IRQ 25 +#define RAW_INTF_1_OVERFLOW_IRQ 25 +#define RESET_DONE_IRQ 27 + +#define MAX_CID 15 +DEFINE_MUTEX(msm_ispif_mut); + +static struct resource *ispif_mem; +static struct resource *ispif_irq; +static struct resource *ispifio; +void __iomem *ispifbase; +static uint32_t global_intf_cmd_mask = 0xFFFFFFFF; +#if DBG_ISPIF +static inline void msm_ispif_read_irq_status(struct ispif_irq_status *out) +{ + uint32_t *temp; + memset(out, 0, sizeof(struct ispif_irq_status)); + temp = (uint32_t *)(ispifbase + ISPIF_IRQ_STATUS_ADDR); + out->ispifIrqStatus0 = msm_io_r(temp); + pr_err("ispif_irq: Irq_status0 = 0x%x\n", + out->ispifIrqStatus0); + msm_io_w(out->ispifIrqStatus0, ispifbase + ISPIF_IRQ_CLEAR_ADDR); +} + +static irqreturn_t msm_io_ispif_irq(int irq_num, void *data) +{ + struct ispif_irq_status irq; + msm_ispif_read_irq_status(&irq); + return IRQ_HANDLED; +} +#endif +int msm_ispif_init(struct platform_device *pdev) +{ + int rc = 0; + struct msm_ispif_fns ispif_fns; + + ispif_mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "ispif"); + if (!ispif_mem) { + pr_err("%s: no mem resource?\n", __func__); + return -ENODEV; + } + ispif_irq = platform_get_resource_byname(pdev, + IORESOURCE_IRQ, "ispif"); + if (!ispif_irq) { + pr_err("%s: no irq resource?\n", __func__); + return -ENODEV; + } + + ispifio = request_mem_region(ispif_mem->start, + resource_size(ispif_mem), pdev->name); + if (!ispifio) + return -EBUSY; + ispifbase = ioremap(ispif_mem->start, + resource_size(ispif_mem)); + if (!ispifbase) { + rc = -ENOMEM; + goto ispif_no_mem; + } +#if DBG_ISPIF + rc = request_irq(ispif_irq->start, msm_io_ispif_irq, + IRQF_TRIGGER_RISING, "ispif", 0); + if (rc < 0) + goto ispif_irq_fail; +#endif + global_intf_cmd_mask = 0xFFFFFFFF; + ispif_fns.ispif_config = msm_ispif_config; + ispif_fns.ispif_start_intf_transfer = + msm_ispif_start_intf_transfer; + rc = msm_ispif_register(&ispif_fns); + if (rc < 0) + goto ispif_irq_fail; + + msm_ispif_reset(); + return 0; + +ispif_irq_fail: + iounmap(ispifbase); +ispif_no_mem: + release_mem_region(ispif_mem->start, resource_size(ispif_mem)); + return rc; +} + +void msm_ispif_release(struct platform_device *pdev) +{ + CDBG("%s, free_irq\n", __func__); +#if DBG_ISPIF + free_irq(ispif_irq->start, 0); +#endif + iounmap(ispifbase); + release_mem_region(ispif_mem->start, resource_size(ispif_mem)); +} + +void msm_ispif_intf_reset(uint8_t intftype) +{ + uint32_t data = 0x01 , data1 = 0x01; + + msm_io_w(0x1<>(vc*2)) & 0x3; + global_intf_cmd_mask &= ~((0x3 & ~val) + <<((vc*2)+(intftype*8))); + CDBG("intf cmd 0x%x\n", global_intf_cmd_mask); + msm_io_w(global_intf_cmd_mask, + ispifbase + ISPIF_INTF_CMD_ADDR); + } + vc++; + cid_mask >>= 4; + } +} + +void msm_ispif_enable_intf_cids(uint8_t intftype, uint16_t cid_mask) +{ + uint32_t data; + mutex_lock(&msm_ispif_mut); + switch (intftype) { + case PIX0: + data = msm_io_r(ispifbase + ISPIF_PIX_INTF_CID_MASK_ADDR); + data |= cid_mask; + msm_io_w(data, ispifbase + ISPIF_PIX_INTF_CID_MASK_ADDR); + break; + + case RDI0: + data = msm_io_r(ispifbase + ISPIF_RDI_INTF_CID_MASK_ADDR); + data |= cid_mask; + msm_io_w(data, ispifbase + ISPIF_RDI_INTF_CID_MASK_ADDR); + break; + + case RDI1: + data = msm_io_r(ispifbase + ISPIF_RDI_1_INTF_CID_MASK_ADDR); + data |= cid_mask; + msm_io_w(data, ispifbase + ISPIF_RDI_1_INTF_CID_MASK_ADDR); + break; + } + mutex_unlock(&msm_ispif_mut); +} + +int msm_ispif_abort_intf_transfer(struct msm_ispif_params *ispif_params) +{ + int rc = 0; + uint8_t intf_cmd_mask = 0xAA; + + CDBG("abort stream request\n"); + mutex_lock(&msm_ispif_mut); + msm_ispif_intf_cmd(ispif_params->intftype, ispif_params->cid_mask, + intf_cmd_mask); + msm_ispif_intf_reset(ispif_params->intftype); + global_intf_cmd_mask |= 0xFF<<(ispif_params->intftype * 8); + mutex_unlock(&msm_ispif_mut); + return rc; +} + +int msm_ispif_start_intf_transfer(struct msm_ispif_params *ispif_params) +{ + uint32_t data; + uint8_t intf_cmd_mask = 0x55; + int rc = 0; + + CDBG("start stream request\n"); + mutex_lock(&msm_ispif_mut); + switch (ispif_params->intftype) { + case PIX0: + data = msm_io_r(ispifbase + ISPIF_PIX_STATUS_ADDR); + if ((data & 0xf) != 0xf) { + CDBG("interface is busy\n"); + mutex_unlock(&msm_ispif_mut); + return -EBUSY; + } + break; + + case RDI0: + data = msm_io_r(ispifbase + ISPIF_RDI_STATUS_ADDR); + break; + + case RDI1: + data = msm_io_r(ispifbase + ISPIF_RDI_1_STATUS_ADDR); + break; + } + msm_ispif_intf_cmd(ispif_params->intftype, + ispif_params->cid_mask, intf_cmd_mask); + mutex_unlock(&msm_ispif_mut); + return rc; +} + +int msm_ispif_stop_intf_transfer(struct msm_ispif_params *ispif_params) +{ + int rc = 0; + uint8_t intf_cmd_mask = 0x00; + CDBG("stop stream request\n"); + mutex_lock(&msm_ispif_mut); + msm_ispif_intf_cmd(ispif_params->intftype, + ispif_params->cid_mask, intf_cmd_mask); + switch (ispif_params->intftype) { + case PIX0: + while ((msm_io_r(ispifbase + ISPIF_PIX_STATUS_ADDR) & 0xf) + != 0xf) { + CDBG("Wait for Idle\n"); + } + break; + + case RDI0: + while ((msm_io_r(ispifbase + ISPIF_RDI_STATUS_ADDR) & 0xf) + != 0xf) { + CDBG("Wait for Idle\n"); + } + break; + default: + break; + } + global_intf_cmd_mask |= 0xFF<<(ispif_params->intftype * 8); + mutex_unlock(&msm_ispif_mut); + return rc; +} + +int msm_ispif_config(struct msm_ispif_params *ispif_params, uint8_t num_of_intf) +{ + uint32_t data, data1; + int rc = 0, i = 0; + CDBG("Enable interface\n"); + data = msm_io_r(ispifbase + ISPIF_PIX_STATUS_ADDR); + data1 = msm_io_r(ispifbase + ISPIF_RDI_STATUS_ADDR); + if (((data & 0xf) != 0xf) || ((data1 & 0xf) != 0xf)) + return -EBUSY; + msm_io_w(0x00000000, ispifbase + ISPIF_IRQ_MASK_ADDR); + for (i = 0; i < num_of_intf; i++) { + msm_ispif_sel_csid_core(ispif_params[i].intftype, + ispif_params[i].csid); + msm_ispif_enable_intf_cids(ispif_params[i].intftype, + ispif_params[i].cid_mask); + } + msm_io_w(0x0BFFFFFF, ispifbase + ISPIF_IRQ_MASK_ADDR); + msm_io_w(0x0BFFFFFF, ispifbase + ISPIF_IRQ_CLEAR_ADDR); + return rc; +} + +void msm_ispif_vfe_get_cid(uint8_t intftype, char *cids, int *num) +{ + uint32_t data = 0; + int i = 0, j = 0; + switch (intftype) { + case PIX0: + data = msm_io_r(ispifbase + + ISPIF_PIX_INTF_CID_MASK_ADDR); + break; + + case RDI0: + data = msm_io_r(ispifbase + + ISPIF_RDI_INTF_CID_MASK_ADDR); + break; + + case RDI1: + data = msm_io_r(ispifbase + + ISPIF_RDI_1_INTF_CID_MASK_ADDR); + break; + + default: + break; + } + for (i = 0; i <= MAX_CID; i++) { + if ((data & 0x1) == 0x1) { + cids[j++] = i; + (*num)++; + } + data >>= 1; + } +} diff --git a/drivers/media/video/msm/msm_ispif.h b/drivers/media/video/msm/msm_ispif.h new file mode 100644 index 00000000000..e5e0c2345b5 --- /dev/null +++ b/drivers/media/video/msm/msm_ispif.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MSM_ISPIF_H +#define MSM_ISPIF_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct ispif_irq_status { + uint32_t ispifIrqStatus0; + uint32_t ispifIrqStatus1; +}; + +int msm_ispif_init(struct platform_device *pdev); +void msm_ispif_release(struct platform_device *pdev); +void msm_ispif_intf_reset(uint8_t intftype); +void msm_ispif_swreg_misc_reset(void); +void msm_ispif_reset(void); +void msm_ispif_sel_csid_core(uint8_t intftype, uint8_t csid); +void msm_ispif_enable_intf_cids(uint8_t intftype, uint16_t cid_mask); +int msm_ispif_start_intf_transfer(struct msm_ispif_params *ispif_params); +int msm_ispif_stop_intf_transfer(struct msm_ispif_params *ispif_params); +int msm_ispif_abort_intf_transfer(struct msm_ispif_params *ispif_params); +int msm_ispif_config(struct msm_ispif_params *ispif_params, \ + uint8_t num_of_intf); +void msm_ispif_vfe_get_cid(uint8_t intftype, char *cids, int *num); + +#endif diff --git a/drivers/media/video/msm/msm_mctl.c b/drivers/media/video/msm/msm_mctl.c new file mode 100644 index 00000000000..35cb68a300f --- /dev/null +++ b/drivers/media/video/msm/msm_mctl.c @@ -0,0 +1,838 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "msm.h" +#include "msm_ispif.h" + +#ifdef CONFIG_MSM_CAMERA_DEBUG +#define D(fmt, args...) pr_debug("msm_mctl: " fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif + +#define MSM_V4L2_SWFI_LATENCY 3 + +/* VFE required buffer number for streaming */ +static struct msm_isp_color_fmt msm_isp_formats[] = { + { + .name = "NV12YUV", + .depth = 12, + .bitsperpxl = 8, + .fourcc = V4L2_PIX_FMT_NV12, + .pxlcode = V4L2_MBUS_FMT_YUYV8_2X8, /* YUV sensor */ + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + .name = "NV21YUV", + .depth = 12, + .bitsperpxl = 8, + .fourcc = V4L2_PIX_FMT_NV21, + .pxlcode = V4L2_MBUS_FMT_YUYV8_2X8, /* YUV sensor */ + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + .name = "NV12BAYER", + .depth = 8, + .bitsperpxl = 8, + .fourcc = V4L2_PIX_FMT_NV12, + .pxlcode = V4L2_MBUS_FMT_SBGGR10_1X10, /* Bayer sensor */ + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + .name = "NV21BAYER", + .depth = 8, + .bitsperpxl = 8, + .fourcc = V4L2_PIX_FMT_NV21, + .pxlcode = V4L2_MBUS_FMT_SBGGR10_1X10, /* Bayer sensor */ + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + .name = "RAWBAYER", + .depth = 10, + .bitsperpxl = 10, + .fourcc = V4L2_PIX_FMT_SBGGR10, + .pxlcode = V4L2_MBUS_FMT_SBGGR10_1X10, /* Bayer sensor */ + .colorspace = V4L2_COLORSPACE_JPEG, + }, + +}; +/* master controller instance counter +static atomic_t mctl_instance = ATOMIC_INIT(0); +*/ + +static int buffer_size(int width, int height, int pixelformat) +{ + int size; + + switch (pixelformat) { + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV12: + size = width * height * 3/2; + break; + case V4L2_PIX_FMT_SBGGR10: + case V4L2_PIX_FMT_SGBRG10: + case V4L2_PIX_FMT_SGRBG10: + case V4L2_PIX_FMT_SRGGB10: + size = width * height; + break; + default: + pr_err("%s: pixelformat %d not supported.\n", + __func__, pixelformat); + size = -EINVAL; + } + + return size; +} +/* + * Videobuf operations + */ + +static void free_buffer(struct videobuf_queue *vq, + struct msm_frame_buffer *buf) +{ + struct videobuf_buffer *vb = &buf->vidbuf; + + BUG_ON(in_interrupt()); + + D("%s (vb=0x%p) 0x%08lx %d\n", __func__, + vb, vb->baddr, vb->bsize); + + /* This waits until this buffer is out of danger, i.e., + * until it is no longer in STATE_QUEUED or STATE_ACTIVE */ + videobuf_waiton(vq, vb, 0, 0); + videobuf_pmem_contig_free(vq, vb); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +/* Setup # of buffers and size of each buffer for the videobuf_queue. + This is called when videobuf_reqbufs() is called, so this function + should tell how many buffer should be used and how big the size is. + + The caller will allocate the real buffers, either in user space or + in kernel */ +static int msm_vidbuf_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + /* get the video device */ + struct msm_cam_v4l2_dev_inst *pcam_inst = vq->priv_data; + struct msm_cam_v4l2_device *pcam = NULL; + + pcam = pcam_inst->pcam; + + D("%s\n", __func__); + if (!pcam || !count || !size) { + pr_err("%s error : invalid input\n", __func__); + return -EINVAL; + } + + D("%s, inst=0x%x,idx=%d, width = %d\n", __func__, + (u32)pcam_inst, pcam_inst->my_index, + pcam_inst->vid_fmt.fmt.pix.width); + D("%s, inst=0x%x,idx=%d, height = %d\n", __func__, + (u32)pcam_inst, pcam_inst->my_index, + pcam_inst->vid_fmt.fmt.pix.height); + *size = buffer_size(pcam_inst->vid_fmt.fmt.pix.width, + pcam_inst->vid_fmt.fmt.pix.height, + pcam_inst->vid_fmt.fmt.pix.pixelformat); + D("%s:inst=0x%x,idx=%d,count=%d, size=%d\n", __func__, + (u32)pcam_inst, pcam_inst->my_index, *count, *size); + return 0; +} + +/* Prepare the buffer before it is put into the videobuf_queue for streaming. + This is called when videobuf_qbuf() is called, so this function should + setup the video buffer to receieve the VFE output. */ +static int msm_vidbuf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + int rc = 0; + /*struct msm_cam_v4l2_device *pcam = vq->priv_data;*/ + struct msm_cam_v4l2_dev_inst *pcam_inst = NULL; + struct msm_cam_v4l2_device *pcam = NULL; + struct msm_frame_buffer *buf = NULL; + + D("%s\n", __func__); + if (!vb || !vq) { + pr_err("%s error : input is NULL\n", __func__); + return -EINVAL; + } + pcam_inst = vq->priv_data; + pcam = pcam_inst->pcam; + buf = container_of(vb, struct msm_frame_buffer, vidbuf); + + if (!pcam || !buf) { + pr_err("%s error : pointer is NULL\n", __func__); + return -EINVAL; + } + + D("%s (vb=0x%p) 0x%08lx %d\n", __func__, + vb, vb->baddr, vb->bsize); + + /* by this time vid_fmt should be already set */ + /* return error if it is not */ + if ((pcam_inst->vid_fmt.fmt.pix.width == 0) || + (pcam_inst->vid_fmt.fmt.pix.height == 0)) { + pr_err("%s error : pcam vid_fmt is not set\n", __func__); + return -EINVAL; + } + + buf->inuse = 1; + + D("buf->pxlcode=%d, pcam->sensor_pxlcode=%d, vb->width=%d," + "pcam->vid_fmt.fmt.pix.width = %d, vb->height = %d," + "pcam->vid_fmt.fmt.pix.height=%d, vb->field=%d, field=%d\n", + buf->pxlcode, pcam_inst->sensor_pxlcode, vb->width, + pcam_inst->vid_fmt.fmt.pix.width, vb->height, + pcam_inst->vid_fmt.fmt.pix.height, vb->field, field); + + if (buf->pxlcode != pcam_inst->sensor_pxlcode || + vb->width != pcam_inst->vid_fmt.fmt.pix.width || + vb->height != pcam_inst->vid_fmt.fmt.pix.height || + vb->field != field) { + buf->pxlcode = pcam_inst->sensor_pxlcode; + vb->width = pcam_inst->vid_fmt.fmt.pix.width; + vb->height = pcam_inst->vid_fmt.fmt.pix.height; + vb->field = field; + vb->state = VIDEOBUF_NEEDS_INIT; + D("VIDEOBUF_NEEDS_INIT\n"); + } + + vb->size = buffer_size(pcam_inst->vid_fmt.fmt.pix.width, vb->height, + pcam_inst->vid_fmt.fmt.pix.pixelformat); + + D("vb->size=%lu, vb->bsize=%u, vb->baddr=0x%x\n", + vb->size, vb->bsize, (uint32_t)vb->baddr); + + if (0 != vb->baddr && vb->bsize < vb->size) { + pr_err("Something wrong vb->size=%lu, vb->bsize=%u,\ + vb->baddr=0x%x\n", + vb->size, vb->bsize, + (uint32_t)vb->baddr); + rc = -EINVAL; + goto out; + } + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + rc = videobuf_iolock(vq, vb, NULL); + if (rc) + goto fail; + D("%s: setting buffer state to prepared\n", __func__); + vb->state = VIDEOBUF_PREPARED; + } + + buf->inuse = 0; + + /* finally if everything is oK, set the VIDEOBUF_PREPARED state*/ + if (0 == rc) + vb->state = VIDEOBUF_PREPARED; + return rc; + +fail: + free_buffer(vq, buf); + +out: + buf->inuse = 0; + return rc; +} + +/* Called under spin_lock_irqsave(q->irqlock, flags) in videobuf-core.c*/ +static void msm_vidbuf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + /*struct msm_cam_v4l2_device *pcam = vq->priv_data;*/ + struct msm_cam_v4l2_dev_inst *pcam_inst = NULL; + struct msm_cam_v4l2_device *pcam = NULL; + unsigned long phyaddr = 0; + int rc; + + D("%s\n", __func__); + if (!vb || !vq) { + pr_err("%s error : input is NULL\n", __func__); + return ; + } + pcam_inst = vq->priv_data; + pcam = pcam_inst->pcam; + if (!pcam) { + pr_err("%s error : pcam is NULL\n", __func__); + return; + } + D("%s (vb=0x%p) 0x%08lx %d\n", __func__, vb, vb->baddr, vb->bsize); + + + vb->state = VIDEOBUF_QUEUED; + if (vq->streaming) { + struct msm_frame frame; + struct msm_vfe_cfg_cmd cfgcmd; + /* we are returning a buffer to the queue */ + struct videobuf_contig_pmem *mem = vb->priv; + /* get the physcial address of the buffer */ + phyaddr = (unsigned long) videobuf_to_pmem_contig(vb); + + D("%s buffer type is %d\n", __func__, mem->buffer_type); + frame.path = pcam_inst->path; + frame.buffer = 0; + frame.y_off = mem->y_off; + frame.cbcr_off = mem->cbcr_off; + + /* now release frame to vfe */ + cfgcmd.cmd_type = CMD_FRAME_BUF_RELEASE; + cfgcmd.value = (void *)&frame; + /* yyan: later change to mctl APIs*/ + rc = msm_isp_subdev_ioctl(&pcam->mctl.isp_sdev->sd, + &cfgcmd, &phyaddr); + } +} + +/* This will be called when streamingoff is called. */ +static void msm_vidbuf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct msm_cam_v4l2_dev_inst *pcam_inst = vq->priv_data; + struct msm_cam_v4l2_device *pcam = pcam_inst->pcam; + struct msm_frame_buffer *buf = container_of(vb, struct msm_frame_buffer, + vidbuf); + + D("%s\n", __func__); + if (!pcam || !vb || !vq) { + pr_err("%s error : input is NULL\n", __func__); + return ; + } +#ifdef DEBUG + D("%s (vb=0x%p) 0x%08lx %d\n", __func__, + vb, vb->baddr, vb->bsize); + + switch (vb->state) { + case VIDEOBUF_ACTIVE: + D("%s (active)\n", __func__); + break; + case VIDEOBUF_QUEUED: + D("%s (queued)\n", __func__); + break; + case VIDEOBUF_PREPARED: + D("%s (prepared)\n", __func__); + break; + default: + D("%s (unknown) state = %d\n", __func__, vb->state); + break; + } +#endif + + /* free the buffer */ + free_buffer(vq, buf); +} + + +static struct videobuf_queue_ops msm_vidbuf_ops = { + .buf_setup = msm_vidbuf_setup, + .buf_prepare = msm_vidbuf_prepare, + .buf_queue = msm_vidbuf_queue, + .buf_release = msm_vidbuf_release, +}; + + + +/* prepare a video buffer queue for a vl42 device*/ +static int msm_vidbuf_init(struct msm_cam_v4l2_dev_inst *pcam_inst, + struct videobuf_queue *q) +{ + int rc = 0; + struct resource *res; + struct platform_device *pdev = NULL; + struct msm_cam_v4l2_device *pcam = pcam_inst->pcam; + D("%s\n", __func__); + if (!pcam || !q) { + pr_err("%s error : input is NULL\n", __func__); + return -EINVAL; + } else + pdev = pcam->mctl.sync.pdev; + + if (!pdev) { + pr_err("%s error : pdev is NULL\n", __func__); + return -EINVAL; + } + if (pcam->use_count == 1) { + /* first check if we have resources */ + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (res) { + D("res->start = 0x%x\n", (u32)res->start); + D("res->size = 0x%x\n", (u32)resource_size(res)); + D("res->end = 0x%x\n", (u32)res->end); + rc = dma_declare_coherent_memory(&pdev->dev, res->start, + res->start, + resource_size(res), + DMA_MEMORY_MAP | + DMA_MEMORY_EXCLUSIVE); + if (!rc) { + pr_err("%s: Unable to declare coherent memory.\n", + __func__); + rc = -ENXIO; + return rc; + } + + /*pcam->memsize = resource_size(res);*/ + D("%s: found DMA capable resource\n", __func__); + } else { + pr_err("%s: no DMA capable resource\n", __func__); + return -ENOMEM; + } + } + spin_lock_init(&pcam_inst->vb_irqlock); + + videobuf_queue_pmem_contig_init(q, &msm_vidbuf_ops, &pdev->dev, + &pcam_inst->vb_irqlock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_NONE, + sizeof(struct msm_frame_buffer), pcam_inst, NULL); + + + return 0; +} + +/* + * V4l2 subdevice operations + */ +static int mctl_subdev_log_status(struct v4l2_subdev *sd) +{ + return -EINVAL; +} + +static long mctl_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int cmd, void *arg) +{ + struct msm_cam_media_controller *pmctl = NULL; + if (!sd) { + pr_err("%s: param is NULL", __func__); + return -EINVAL; + } else + pmctl = (struct msm_cam_media_controller *) + v4l2_get_subdevdata(sd); + + + return -EINVAL; +} + + +static int mctl_subdev_g_mbus_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + return -EINVAL; +} + +static struct v4l2_subdev_core_ops mctl_subdev_core_ops = { + .log_status = mctl_subdev_log_status, + .ioctl = mctl_subdev_ioctl, +}; + +static struct v4l2_subdev_video_ops mctl_subdev_video_ops = { + .g_mbus_fmt = mctl_subdev_g_mbus_fmt, +}; + +static struct v4l2_subdev_ops mctl_subdev_ops = { + .core = &mctl_subdev_core_ops, + .video = &mctl_subdev_video_ops, +}; + +static int msm_get_sensor_info(struct msm_sync *sync, void __user *arg) +{ + int rc = 0; + struct msm_camsensor_info info; + struct msm_camera_sensor_info *sdata; + + if (copy_from_user(&info, + arg, + sizeof(struct msm_camsensor_info))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + sdata = sync->pdev->dev.platform_data; + D("%s: sensor_name %s\n", __func__, sdata->sensor_name); + + memcpy(&info.name[0], sdata->sensor_name, MAX_SENSOR_NAME); + info.flash_enabled = sdata->flash_data->flash_type != + MSM_CAMERA_FLASH_NONE; + + /* copy back to user space */ + if (copy_to_user((void *)arg, + &info, + sizeof(struct msm_camsensor_info))) { + ERR_COPY_TO_USER(); + rc = -EFAULT; + } + + return rc; +} + +/* called by other subdev to notify any changes*/ + +static int msm_mctl_notify(struct msm_cam_media_controller *p_mctl, + unsigned int notification, void *arg) +{ + int rc = -EINVAL; + struct msm_ispif_params ispif_params; + struct msm_camera_sensor_info *sinfo = + p_mctl->plat_dev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + uint8_t csid_core = camdev->csid_core; + switch (notification) { + case NOTIFY_CID_CHANGE: + /* reconfig the ISPIF*/ + if (p_mctl->ispif_fns->ispif_config) { + ispif_params.intftype = PIX0; + ispif_params.cid_mask = 0x0001; + ispif_params.csid = csid_core; + + rc = p_mctl->ispif_fns->ispif_config(&ispif_params, 1); + if (rc < 0) + return rc; + rc = p_mctl->ispif_fns->ispif_start_intf_transfer + (&ispif_params); + if (rc < 0) + return rc; + msleep(20); + } + break; + case NOTIFY_VFE_MSG_EVT: + if (p_mctl->isp_sdev && p_mctl->isp_sdev->isp_notify) { + rc = p_mctl->isp_sdev->isp_notify( + &p_mctl->isp_sdev->sd, arg); + } + break; + default: + break; + } + + return rc; +} + +/* called by the server or the config nodes to handle user space + commands*/ +static int msm_mctl_cmd(struct msm_cam_media_controller *p_mctl, + unsigned int cmd, unsigned long arg) +{ + int rc = -EINVAL; + void __user *argp = (void __user *)arg; + if (!p_mctl) { + pr_err("%s: param is NULL", __func__); + return -EINVAL; + } + D("%s start cmd = %d\n", __func__, _IOC_NR(cmd)); + + /* ... call sensor, ISPIF or VEF subdev*/ + switch (cmd) { + /* sensor config*/ + case MSM_CAM_IOCTL_GET_SENSOR_INFO: + rc = msm_get_sensor_info(&p_mctl->sync, argp); + break; + + case MSM_CAM_IOCTL_SENSOR_IO_CFG: + rc = p_mctl->sync.sctrl.s_config(argp); + break; + + case MSM_CAM_IOCTL_FLASH_CTRL: { + struct flash_ctrl_data flash_info; + if (copy_from_user(&flash_info, argp, sizeof(flash_info))) { + ERR_COPY_FROM_USER(); + rc = -EFAULT; + } else { + rc = msm_flash_ctrl(p_mctl->sync.sdata, &flash_info); + } + break; + } + + /* ISFIF config*/ + + default: + /* ISP config*/ + rc = p_mctl->isp_sdev->isp_config(p_mctl, cmd, arg); + break; + } + D("%s: !!! cmd = %d, rc = %d\n", __func__, _IOC_NR(cmd), rc); + return rc; +} + +static int msm_sync_init(struct msm_sync *sync, + struct platform_device *pdev, struct msm_sensor_ctrl *sctrl) +{ + int rc = 0; + if (!sync) { + pr_err("%s: param is NULL", __func__); + return -EINVAL; + } + + sync->sdata = pdev->dev.platform_data; + + wake_lock_init(&sync->wake_lock, WAKE_LOCK_IDLE, "msm_camera"); + + sync->pdev = pdev; + sync->sctrl = *sctrl; + sync->opencnt = 0; + mutex_init(&sync->lock); + D("%s: initialized %s\n", __func__, sync->sdata->sensor_name); + return rc; +} + +static int msm_mctl_open(struct msm_cam_media_controller *p_mctl, + const char *const apps_id) +{ + int rc = 0; + struct msm_sync *sync = NULL; + D("%s\n", __func__); + if (!p_mctl) { + pr_err("%s: param is NULL", __func__); + return -EINVAL; + } + + /* msm_sync_init() muct be called before*/ + sync = &(p_mctl->sync); + + mutex_lock(&sync->lock); + /* open sub devices - once only*/ + if (!sync->opencnt) { + wake_lock(&sync->wake_lock); + + /* turn on clock */ + rc = msm_camio_sensor_clk_on(sync->pdev); + if (rc < 0) { + pr_err("%s: msm_camio_sensor_clk_on failed:%d\n", + __func__, rc); + goto msm_open_done; + } + + /* ISP first*/ + if (p_mctl->isp_sdev && p_mctl->isp_sdev->isp_open) + rc = p_mctl->isp_sdev->isp_open( + &p_mctl->isp_sdev->sd, sync); + + if (rc < 0) { + pr_err("%s: isp init failed: %d\n", __func__, rc); + goto msm_open_done; + } + + /* then sensor - move sub dev later*/ + if (sync->sctrl.s_init) + rc = sync->sctrl.s_init(sync->sdata); + + if (rc < 0) { + pr_err("%s: isp init failed: %d\n", __func__, rc); + goto msm_open_done; + } + + pm_qos_add_request(&p_mctl->pm_qos_req_list, + PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + pm_qos_update_request(&p_mctl->pm_qos_req_list, + MSM_V4L2_SWFI_LATENCY); + + sync->apps_id = apps_id; + sync->opencnt++; + } + +msm_open_done: + mutex_unlock(&sync->lock); + return rc; +} + +static int msm_mctl_release(struct msm_cam_media_controller *p_mctl) +{ + struct msm_sync *sync = NULL; + int rc = 0; + + sync = &(p_mctl->sync); + + if (p_mctl->isp_sdev && p_mctl->isp_sdev->isp_release) + p_mctl->isp_sdev->isp_release(&p_mctl->sync); + + if (p_mctl->sync.sctrl.s_release) + p_mctl->sync.sctrl.s_release(); + + rc = msm_camio_sensor_clk_off(sync->pdev); + if (rc < 0) + pr_err("%s: msm_camio_sensor_clk_off failed:%d\n", + __func__, rc); + + pm_qos_update_request(&p_mctl->pm_qos_req_list, + PM_QOS_DEFAULT_VALUE); + pm_qos_remove_request(&p_mctl->pm_qos_req_list); + + return rc; +} + +int msm_mctl_init_user_formats(struct msm_cam_v4l2_device *pcam) +{ + struct v4l2_subdev *sd = &pcam->sensor_sdev; + enum v4l2_mbus_pixelcode pxlcode; + int numfmt_sensor = 0; + int numfmt = 0; + int rc = 0; + int i, j; + + D("%s\n", __func__); + while (!v4l2_subdev_call(sd, video, enum_mbus_fmt, numfmt_sensor, + &pxlcode)) + numfmt_sensor++; + + D("%s, numfmt_sensor = %d\n", __func__, numfmt_sensor); + if (!numfmt_sensor) + return -ENXIO; + + pcam->usr_fmts = vmalloc(numfmt_sensor * ARRAY_SIZE(msm_isp_formats) * + sizeof(struct msm_isp_color_fmt)); + if (!pcam->usr_fmts) + return -ENOMEM; + + /* from sensor to ISP.. fill the data structure */ + for (i = 0; i < numfmt_sensor; i++) { + rc = v4l2_subdev_call(sd, video, enum_mbus_fmt, i, &pxlcode); + D("rc is %d\n", rc); + if (rc < 0) { + vfree(pcam->usr_fmts); + return rc; + } + + for (j = 0; j < ARRAY_SIZE(msm_isp_formats); j++) { + /* find the corresponding format */ + if (pxlcode == msm_isp_formats[j].pxlcode) { + pcam->usr_fmts[numfmt] = msm_isp_formats[j]; + D("pcam->usr_fmts=0x%x\n", (u32)pcam->usr_fmts); + D("format pxlcode 0x%x (0x%x) found\n", + pcam->usr_fmts[numfmt].pxlcode, + pcam->usr_fmts[numfmt].fourcc); + numfmt++; + } + } + } + + pcam->num_fmts = numfmt; + + if (numfmt == 0) { + pr_err("%s: No supported formats.\n", __func__); + vfree(pcam->usr_fmts); + return -EINVAL; + } + + D("Found %d supported formats.\n", pcam->num_fmts); + /* set the default pxlcode, in any case, it will be set through + * setfmt */ + return 0; +} + +/* this function plug in the implementation of a v4l2_subdev */ +int msm_mctl_init_module(struct msm_cam_v4l2_device *pcam) +{ + + struct msm_cam_media_controller *pmctl = NULL; + D("%s\n", __func__); + if (!pcam) { + pr_err("%s: param is NULL", __func__); + return -EINVAL; + } else + pmctl = &pcam->mctl; + + /* init module sync object*/ + msm_sync_init(&pmctl->sync, pcam->pdev, &pcam->sctrl); + + /* init module operations*/ + pmctl->mctl_open = msm_mctl_open; + pmctl->mctl_cmd = msm_mctl_cmd; + pmctl->mctl_notify = msm_mctl_notify; + pmctl->mctl_vidbuf_init = msm_vidbuf_init; + pmctl->mctl_release = msm_mctl_release; + + pmctl->plat_dev = pcam->pdev; + /* init sub device*/ + v4l2_subdev_init(&(pmctl->mctl_sdev), &mctl_subdev_ops); + v4l2_set_subdevdata(&(pmctl->mctl_sdev), pmctl); + + return 0; +} +static int msm_mctl_out_type_to_inst_index(struct msm_cam_v4l2_device *pcam, + int out_type) +{ + switch (out_type) { + case VFE_MSG_OUTPUT_P: + return pcam->dev_inst_map + [MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW]->my_index; + case VFE_MSG_OUTPUT_V: + return pcam->dev_inst_map + [MSM_V4L2_EXT_CAPTURE_MODE_VIDEO]->my_index; + case VFE_MSG_OUTPUT_S: + return pcam->dev_inst_map + [MSM_V4L2_EXT_CAPTURE_MODE_MAIN]->my_index; + case VFE_MSG_OUTPUT_T: + return pcam->dev_inst_map + [MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL]->my_index; + default: + return 0; + } + return 0; +} + +int msm_mctl_buf_done(struct msm_cam_media_controller *pmctl, + int msg_type, uint32_t y_phy) +{ + struct videobuf_queue *q; + struct videobuf_buffer *buf = NULL; + uint32_t buf_phyaddr = 0; + int i, idx; + unsigned long flags = 0; + + idx = msm_mctl_out_type_to_inst_index(pmctl->sync.pcam_sync, msg_type); + q = &(pmctl->sync.pcam_sync->dev_inst[idx]->vid_bufq); + + D("q=0x%x\n", (u32)q); + + /* find the videobuf which is done */ + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + buf = q->bufs[i]; + buf_phyaddr = videobuf_to_pmem_contig(buf); + D("buf_phyaddr=0x%x\n", (u32)buf_phyaddr); + D("data->phy.y_phy=0x%x\n", + y_phy); + D("buf = 0x%x\n", (u32)buf); + if (buf_phyaddr == y_phy) { + /* signal that buffer is done */ + /* get the buf lock first */ + spin_lock_irqsave(q->irqlock, flags); + buf->state = VIDEOBUF_DONE; + D("queuedequeue video_buffer 0x%x," + "phyaddr = 0x%x\n", + (u32)buf, y_phy); + + do_gettimeofday(&buf->ts); + buf->field_count++; + wake_up(&buf->done); + spin_unlock_irqrestore(q->irqlock, flags); + break; + } + } + return 0; +} diff --git a/drivers/media/video/msm/msm_mem.c b/drivers/media/video/msm/msm_mem.c new file mode 100644 index 00000000000..5232f7c458c --- /dev/null +++ b/drivers/media/video/msm/msm_mem.c @@ -0,0 +1,403 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "msm.h" +#include "msm_vfe31.h" + +#ifdef CONFIG_MSM_CAMERA_DEBUG +#define D(fmt, args...) pr_debug("msm_isp: " fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif + + +#define ERR_USER_COPY(to) pr_err("%s(%d): copy %s user\n", \ + __func__, __LINE__, ((to) ? "to" : "from")) +#define ERR_COPY_FROM_USER() ERR_USER_COPY(0) +#define ERR_COPY_TO_USER() ERR_USER_COPY(1) + + +#define PAD_TO_WORD(a) (((a) + 3) & ~3) + +#define __CONTAINS(r, v, l, field) ({ \ + typeof(r) __r = r; \ + typeof(v) __v = v; \ + typeof(v) __e = __v + l; \ + int res = __v >= __r->field && \ + __e <= __r->field + __r->len; \ + res; \ +}) + +#define CONTAINS(r1, r2, field) ({ \ + typeof(r2) __r2 = r2; \ + __CONTAINS(r1, __r2->field, __r2->len, field); \ +}) + +#define IN_RANGE(r, v, field) ({ \ + typeof(r) __r = r; \ + typeof(v) __vv = v; \ + int res = ((__vv >= __r->field) && \ + (__vv < (__r->field + __r->len))); \ + res; \ +}) + +#define OVERLAPS(r1, r2, field) ({ \ + typeof(r1) __r1 = r1; \ + typeof(r2) __r2 = r2; \ + typeof(__r2->field) __v = __r2->field; \ + typeof(__v) __e = __v + __r2->len - 1; \ + int res = (IN_RANGE(__r1, __v, field) || \ + IN_RANGE(__r1, __e, field)); \ + res; \ +}) + +static DEFINE_MUTEX(hlist_mut); + +#ifdef CONFIG_ANDROID_PMEM +static int check_pmem_info(struct msm_pmem_info *info, int len) +{ + if (info->offset < len && + info->offset + info->len <= len && + info->y_off < len && + info->cbcr_off < len) + return 0; + + pr_err("%s: check failed: off %d len %d y %d cbcr %d (total len %d)\n", + __func__, + info->offset, + info->len, + info->y_off, + info->cbcr_off, + len); + return -EINVAL; +} +#endif + +static int check_overlap(struct hlist_head *ptype, + unsigned long paddr, + unsigned long len) +{ + struct msm_pmem_region *region; + struct msm_pmem_region t = { .paddr = paddr, .len = len }; + struct hlist_node *node; + + hlist_for_each_entry(region, node, ptype, list) { + if (CONTAINS(region, &t, paddr) || + CONTAINS(&t, region, paddr) || + OVERLAPS(region, &t, paddr)) { + CDBG(" region (PHYS %p len %ld)" + " clashes with registered region" + " (paddr %p len %ld)\n", + (void *)t.paddr, t.len, + (void *)region->paddr, region->len); + return -EINVAL; + } + } + + return 0; +} + +static int msm_pmem_table_add(struct hlist_head *ptype, + struct msm_pmem_info *info) +{ + struct file *file; + unsigned long paddr; +#ifdef CONFIG_ANDROID_PMEM + unsigned long kvstart; + int rc; +#endif + unsigned long len; + struct msm_pmem_region *region; +#ifdef CONFIG_ANDROID_PMEM + rc = get_pmem_file(info->fd, &paddr, &kvstart, &len, &file); + if (rc < 0) { + pr_err("%s: get_pmem_file fd %d error %d\n", + __func__, + info->fd, rc); + return rc; + } + if (!info->len) + info->len = len; + + rc = check_pmem_info(info, len); + if (rc < 0) + return rc; +#else + paddr = 0; + file = NULL; +#endif + paddr += info->offset; + len = info->len; + + if (check_overlap(ptype, paddr, len) < 0) + return -EINVAL; + + CDBG("%s: type %d, active flag %d, paddr 0x%lx, vaddr 0x%lx\n", + __func__, info->type, info->active, paddr, + (unsigned long)info->vaddr); + + region = kmalloc(sizeof(struct msm_pmem_region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + INIT_HLIST_NODE(®ion->list); + + region->paddr = paddr; + region->len = len; + region->file = file; + memcpy(®ion->info, info, sizeof(region->info)); + D("%s Adding region to list with type %d\n", __func__, + region->info.type); + D("%s pmem_stats address is 0x%p\n", __func__, ptype); + hlist_add_head(&(region->list), ptype); + + return 0; +} + +static int __msm_register_pmem(struct hlist_head *ptype, + struct msm_pmem_info *pinfo) +{ + int rc = 0; + + switch (pinfo->type) { + case MSM_PMEM_AEC_AWB: + case MSM_PMEM_AF: + case MSM_PMEM_AEC: + case MSM_PMEM_AWB: + case MSM_PMEM_RS: + case MSM_PMEM_CS: + case MSM_PMEM_IHIST: + case MSM_PMEM_SKIN: + rc = msm_pmem_table_add(ptype, pinfo); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int __msm_pmem_table_del(struct hlist_head *ptype, + struct msm_pmem_info *pinfo) +{ + int rc = 0; + struct msm_pmem_region *region; + struct hlist_node *node, *n; + + switch (pinfo->type) { + case MSM_PMEM_AEC_AWB: + case MSM_PMEM_AF: + hlist_for_each_entry_safe(region, node, n, + ptype, list) { + + if (pinfo->type == region->info.type && + pinfo->vaddr == region->info.vaddr && + pinfo->fd == region->info.fd) { + hlist_del(node); +#ifdef CONFIG_ANDROID_PMEM + put_pmem_file(region->file); +#else + +#endif + kfree(region); + } + } + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +/* return of 0 means failure */ +uint8_t msm_pmem_region_lookup(struct hlist_head *ptype, + int pmem_type, struct msm_pmem_region *reg, uint8_t maxcount) +{ + struct msm_pmem_region *region; + struct msm_pmem_region *regptr; + struct hlist_node *node, *n; + + uint8_t rc = 0; + D("%s\n", __func__); + regptr = reg; + mutex_lock(&hlist_mut); + hlist_for_each_entry_safe(region, node, n, ptype, list) { + if (region->info.type == pmem_type && region->info.active) { + *regptr = *region; + rc += 1; + if (rc >= maxcount) + break; + regptr++; + } + } + D("%s finished, rc=%d\n", __func__, rc); + mutex_unlock(&hlist_mut); + return rc; +} + +uint8_t msm_pmem_region_lookup_2(struct hlist_head *ptype, + int pmem_type, + struct msm_pmem_region *reg, + uint8_t maxcount) +{ + struct msm_pmem_region *region; + struct msm_pmem_region *regptr; + struct hlist_node *node, *n; + uint8_t rc = 0; + regptr = reg; + mutex_lock(&hlist_mut); + hlist_for_each_entry_safe(region, node, n, ptype, list) { + D("Mio: info.type=%d, pmem_type = %d," + "info.active = %d\n", + region->info.type, pmem_type, region->info.active); + + if (region->info.type == pmem_type && region->info.active) { + D("info.type=%d, pmem_type = %d," + "info.active = %d,\n", + region->info.type, pmem_type, + region->info.active); + *regptr = *region; + region->info.type = MSM_PMEM_VIDEO; + rc += 1; + if (rc >= maxcount) + break; + regptr++; + } + } + mutex_unlock(&hlist_mut); + return rc; +} + +uint8_t msm_pmem_region_lookup_3(struct msm_cam_v4l2_device *pcam, int idx, + struct msm_pmem_region *reg, + int mem_type) +{ + struct videobuf_contig_pmem *mem; + uint8_t rc = 0; + struct videobuf_queue *q = &pcam->dev_inst[idx]->vid_bufq; + struct videobuf_buffer *buf = NULL; + + videobuf_queue_lock(q); + list_for_each_entry(buf, &q->stream, stream) { + mem = buf->priv; + reg->paddr = mem->phyaddr; + D("%s paddr for buf %d is 0x%p\n", __func__, + buf->i, + (void *)reg->paddr); + reg->len = sizeof(struct msm_pmem_info); + reg->file = NULL; + reg->info.len = mem->size; + + reg->info.vaddr = + (void *)(buf->baddr); + + reg->info.type = mem_type; + + reg->info.offset = 0; + reg->info.y_off = mem->y_off; + reg->info.cbcr_off = PAD_TO_WORD(mem->cbcr_off); + D("%s y_off = %d, cbcr_off = %d\n", __func__, + reg->info.y_off, reg->info.cbcr_off); + rc += 1; + reg++; + } + videobuf_queue_unlock(q); + + return rc; +} + +unsigned long msm_pmem_stats_vtop_lookup( + struct msm_sync *sync, + unsigned long buffer, + int fd) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + + hlist_for_each_entry_safe(region, node, n, &sync->pmem_stats, list) { + if (((unsigned long)(region->info.vaddr) == buffer) && + (region->info.fd == fd) && + region->info.active == 0) { + region->info.active = 1; + return region->paddr; + } + } + + return 0; +} + +unsigned long msm_pmem_stats_ptov_lookup(struct msm_sync *sync, + unsigned long addr, int *fd) +{ + struct msm_pmem_region *region; + struct hlist_node *node, *n; + + hlist_for_each_entry_safe(region, node, n, &sync->pmem_stats, list) { + if (addr == region->paddr && region->info.active) { + /* offset since we could pass vaddr inside a + * registered pmem buffer */ + *fd = region->info.fd; + region->info.active = 0; + return (unsigned long)(region->info.vaddr); + } + } + + return 0; +} + +int msm_register_pmem(struct hlist_head *ptype, void __user *arg) +{ + struct msm_pmem_info info; + + if (copy_from_user(&info, arg, sizeof(info))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + return __msm_register_pmem(ptype, &info); +} +EXPORT_SYMBOL(msm_register_pmem); + +int msm_pmem_table_del(struct hlist_head *ptype, void __user *arg) +{ + struct msm_pmem_info info; + + if (copy_from_user(&info, arg, sizeof(info))) { + ERR_COPY_FROM_USER(); + return -EFAULT; + } + + return __msm_pmem_table_del(ptype, &info); +} +EXPORT_SYMBOL(msm_pmem_table_del); diff --git a/drivers/media/video/msm/msm_sensor.c b/drivers/media/video/msm/msm_sensor.c new file mode 100644 index 00000000000..83fe643cff0 --- /dev/null +++ b/drivers/media/video/msm/msm_sensor.c @@ -0,0 +1,814 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_sensor.h" + +/*=============================================================*/ + +int32_t msm_sensor_i2c_rxdata(struct msm_sensor_ctrl_t *s_ctrl, + unsigned char *rxdata, int length) +{ + uint16_t saddr = s_ctrl->msm_sensor_client->addr >> 1; + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + if (i2c_transfer(s_ctrl->msm_sensor_client->adapter, msgs, 2) < 0) { + CDBG("msm_sensor_i2c_rxdata faild 0x%x\n", saddr); + return -EIO; + } + return 0; +} + +int32_t msm_sensor_i2c_txdata(struct msm_sensor_ctrl_t *s_ctrl, + unsigned char *txdata, int length) +{ + uint16_t saddr = s_ctrl->msm_sensor_client->addr >> 1; + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(s_ctrl->msm_sensor_client->adapter, msg, 1) < 0) { + CDBG("msm_sensor_i2c_txdata faild 0x%x\n", saddr); + return -EIO; + } + return 0; +} + +int32_t msm_sensor_i2c_waddr_write_b(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + CDBG("%s waddr = 0x%x, wdata = 0x%x\n", __func__, waddr, bdata); + rc = msm_sensor_i2c_txdata(s_ctrl, buf, 3); + if (rc < 0) + CDBG("%s fail\n", __func__); + return rc; +} + +int32_t msm_sensor_i2c_waddr_write_w(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t waddr, uint16_t wdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[4]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + CDBG("%s waddr = 0x%x, wdata = 0x%x\n", __func__, waddr, wdata); + rc = msm_sensor_i2c_txdata(s_ctrl, buf, 4); + if (rc < 0) + CDBG("%s fail\n", __func__); + return rc; +} + +int32_t msm_sensor_i2c_waddr_write_b_tbl(struct msm_sensor_ctrl_t *s_ctrl, + struct msm_sensor_i2c_reg_conf const *reg_conf_tbl, uint8_t size) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < size; i++) { + rc = msm_sensor_i2c_waddr_write_b( + s_ctrl, + reg_conf_tbl->reg_addr, + reg_conf_tbl->reg_data); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +int32_t msm_sensor_i2c_waddr_write_w_tbl(struct msm_sensor_ctrl_t *s_ctrl, + struct msm_sensor_i2c_reg_conf const *reg_conf_tbl, uint8_t size) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < size; i++) { + rc = msm_sensor_i2c_waddr_write_w( + s_ctrl, + reg_conf_tbl->reg_addr, + reg_conf_tbl->reg_data); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +int32_t msm_sensor_i2c_waddr_read_w(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t waddr, uint16_t *data) +{ + int32_t rc = 0; + unsigned char buf[2]; + if (!data) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + rc = msm_sensor_i2c_rxdata(s_ctrl, buf, 2); + if (rc < 0) { + CDBG("%s fail\n", __func__); + return rc; + } + *data = (buf[0] << 8 | buf[1]); + CDBG("%s waddr = 0x%x val = 0x%x!\n", __func__, + waddr, *data); + return rc; +} + +int msm_sensor_write_b_conf_array(struct msm_sensor_ctrl_t *s_ctrl, + struct msm_sensor_i2c_conf_array *array, uint16_t index) +{ + return msm_sensor_i2c_waddr_write_b_tbl( + s_ctrl, array[index].conf, array[index].size); +} + +int msm_sensor_write_w_conf_array(struct msm_sensor_ctrl_t *s_ctrl, + struct msm_sensor_i2c_conf_array *array, uint16_t index) +{ + return msm_sensor_i2c_waddr_write_w_tbl( + s_ctrl, array[index].conf, array[index].size); +} + +int msm_sensor_write_b_init_settings(struct msm_sensor_ctrl_t *s_ctrl) +{ + int rc = 0, i; + for (i = 0; i < s_ctrl->msm_sensor_reg.init_size; i++) { + rc = msm_sensor_write_b_conf_array( + s_ctrl, s_ctrl->msm_sensor_reg.init_settings, i); + msleep(s_ctrl->msm_sensor_reg.init_settings[i].delay); + if (rc < 0) + break; + } + return rc; +} + +int msm_sensor_write_w_init_settings(struct msm_sensor_ctrl_t *s_ctrl) +{ + int rc = 0, i; + for (i = 0; i < s_ctrl->msm_sensor_reg.init_size; i++) { + rc = msm_sensor_write_w_conf_array( + s_ctrl, s_ctrl->msm_sensor_reg.init_settings, i); + msleep(s_ctrl->msm_sensor_reg.init_settings[i].delay); + if (rc < 0) + break; + } + return rc; +} + +int msm_sensor_write_b_res_settings(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t res) +{ + int rc = 0; + rc = msm_sensor_write_b_conf_array( + s_ctrl, s_ctrl->msm_sensor_reg.res_settings, res); + msleep(s_ctrl->msm_sensor_reg.res_settings[res].delay); + return rc; +} + +int msm_sensor_write_w_res_settings(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t res) +{ + int rc = 0; + rc = msm_sensor_write_w_conf_array( + s_ctrl, s_ctrl->msm_sensor_reg.res_settings, res); + msleep(s_ctrl->msm_sensor_reg.res_settings[res].delay); + return rc; +} + +uint16_t msm_sensor_read_b_conf_wdata(struct msm_sensor_ctrl_t *s_ctrl, + enum msm_sensor_resolution_t res, int8_t array_addr) +{ + return + s_ctrl->msm_sensor_reg.res_settings[res]. + conf[array_addr].reg_data << 8 | + s_ctrl->msm_sensor_reg.res_settings[res]. + conf[array_addr+1].reg_data; +} + +uint16_t msm_sensor_read_w_conf_wdata(struct msm_sensor_ctrl_t *s_ctrl, + enum msm_sensor_resolution_t res, int8_t array_addr) +{ + return + s_ctrl->msm_sensor_reg.res_settings[res]. + conf[array_addr].reg_data; +} + +void msm_sensor_start_stream(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_sensor_i2c_waddr_write_b_tbl(s_ctrl, + s_ctrl->msm_sensor_reg.start_stream_conf, + s_ctrl->msm_sensor_reg.start_stream_conf_size); +} + +void msm_sensor_stop_stream(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_sensor_i2c_waddr_write_b_tbl(s_ctrl, + s_ctrl->msm_sensor_reg.stop_stream_conf, + s_ctrl->msm_sensor_reg.stop_stream_conf_size); +} + +void msm_sensor_group_hold_on(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_sensor_i2c_waddr_write_b_tbl(s_ctrl, + s_ctrl->msm_sensor_reg.group_hold_on_conf, + s_ctrl->msm_sensor_reg.group_hold_on_conf_size); +} + +void msm_sensor_group_hold_off(struct msm_sensor_ctrl_t *s_ctrl) +{ + msm_sensor_i2c_waddr_write_b_tbl(s_ctrl, + s_ctrl->msm_sensor_reg.group_hold_off_conf, + s_ctrl->msm_sensor_reg.group_hold_off_conf_size); +} + +uint16_t msm_sensor_get_prev_lines_pf(struct msm_sensor_ctrl_t *s_ctrl) +{ + return s_ctrl->prev_frame_length_lines; +} + +uint16_t msm_sensor_get_prev_pixels_pl(struct msm_sensor_ctrl_t *s_ctrl) +{ + return s_ctrl->prev_line_length_pck; +} + +uint16_t msm_sensor_get_pict_lines_pf(struct msm_sensor_ctrl_t *s_ctrl) +{ + return s_ctrl->snap_frame_length_lines; +} + +uint16_t msm_sensor_get_pict_pixels_pl(struct msm_sensor_ctrl_t *s_ctrl) +{ + return s_ctrl->snap_line_length_pck; +} + +uint32_t msm_sensor_get_pict_max_exp_lc(struct msm_sensor_ctrl_t *s_ctrl) +{ + return s_ctrl->snap_frame_length_lines * 24; +} + +void msm_sensor_get_pict_fps(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t fps, uint16_t *pfps) +{ + uint32_t divider, d1, d2; + d1 = s_ctrl->prev_frame_length_lines * Q10 / + s_ctrl->snap_frame_length_lines; + d2 = s_ctrl->prev_line_length_pck * Q10 / + s_ctrl->snap_line_length_pck; + divider = d1 * d2 / Q10; + *pfps = (uint16_t) (fps * divider / Q10); +} + +int32_t msm_sensor_set_fps(struct msm_sensor_ctrl_t *s_ctrl, + struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + s_ctrl->fps_divider = fps->fps_div; + + total_lines_per_frame = (uint16_t) + ((s_ctrl->prev_frame_length_lines) * + s_ctrl->fps_divider/Q10); + + rc = msm_sensor_i2c_waddr_write_w(s_ctrl, + s_ctrl->frame_length_lines_addr, + total_lines_per_frame); + return rc; +} + +int32_t msm_sensor_write_exp_gain1(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line) +{ + uint32_t fl_lines; + uint8_t offset; + fl_lines = s_ctrl->curr_frame_length_lines; + line = (line * s_ctrl->fps_divider) / Q10; + offset = s_ctrl->vert_offset; + if (line > (fl_lines - offset)) + fl_lines = line + offset; + + s_ctrl->func_tbl.sensor_group_hold_on(s_ctrl); + msm_sensor_i2c_waddr_write_w(s_ctrl, + s_ctrl->frame_length_lines_addr, fl_lines); + msm_sensor_i2c_waddr_write_w(s_ctrl, + s_ctrl->coarse_int_time_addr, line); + msm_sensor_i2c_waddr_write_w(s_ctrl, s_ctrl->global_gain_addr, gain); + s_ctrl->func_tbl.sensor_group_hold_off(s_ctrl); + return 0; +} + +int32_t msm_sensor_write_exp_gain2(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line) +{ + uint32_t fl_lines, ll_pclk, ll_ratio; + uint8_t offset; + fl_lines = s_ctrl->curr_frame_length_lines; + ll_pclk = s_ctrl->curr_line_length_pck; + line = (line * s_ctrl->fps_divider) / Q10; + offset = s_ctrl->vert_offset; + if (line > (fl_lines - offset)) { + ll_ratio = (line * Q10) / (fl_lines - offset); + ll_pclk = ll_pclk * ll_ratio; + line = fl_lines - offset; + } + + s_ctrl->func_tbl.sensor_group_hold_on(s_ctrl); + msm_sensor_i2c_waddr_write_w(s_ctrl, + s_ctrl->line_length_pck_addr, ll_pclk); + msm_sensor_i2c_waddr_write_w(s_ctrl, + s_ctrl->coarse_int_time_addr, line); + msm_sensor_i2c_waddr_write_w(s_ctrl, s_ctrl->global_gain_addr, gain); + s_ctrl->func_tbl.sensor_group_hold_off(s_ctrl); + return 0; +} + +int32_t msm_sensor_set_sensor_mode_b(struct msm_sensor_ctrl_t *s_ctrl, + int mode, int res) +{ + int32_t rc = 0; + + if (s_ctrl->curr_res != res) { + switch (mode) { + case SENSOR_PREVIEW_MODE: + s_ctrl->prev_res = res; + break; + case SENSOR_SNAPSHOT_MODE: + case SENSOR_RAW_SNAPSHOT_MODE: + s_ctrl->pict_res = res; + break; + default: + rc = -EINVAL; + break; + } + s_ctrl->curr_frame_length_lines = + msm_sensor_read_b_conf_wdata + (s_ctrl, res, s_ctrl->frame_length_lines_array_addr); + s_ctrl->curr_line_length_pck = + msm_sensor_read_b_conf_wdata + (s_ctrl, res, s_ctrl->line_length_pck_array_addr); + + if (s_ctrl->func_tbl.sensor_setting + (s_ctrl, MSM_SENSOR_UPDATE_PERIODIC, res) < 0) + return rc; + } + s_ctrl->curr_res = res; + return rc; +} + +int32_t msm_sensor_set_sensor_mode_w(struct msm_sensor_ctrl_t *s_ctrl, + int mode, int res) +{ + int32_t rc = 0; + + if (s_ctrl->curr_res != res) { + switch (mode) { + case SENSOR_PREVIEW_MODE: + s_ctrl->prev_res = res; + break; + case SENSOR_SNAPSHOT_MODE: + case SENSOR_RAW_SNAPSHOT_MODE: + s_ctrl->pict_res = res; + break; + default: + rc = -EINVAL; + break; + } + s_ctrl->curr_frame_length_lines = + msm_sensor_read_w_conf_wdata + (s_ctrl, res, s_ctrl->frame_length_lines_array_addr); + s_ctrl->curr_line_length_pck = + msm_sensor_read_w_conf_wdata + (s_ctrl, res, s_ctrl->line_length_pck_array_addr); + + if (s_ctrl->func_tbl.sensor_setting + (s_ctrl, MSM_SENSOR_UPDATE_PERIODIC, res) < 0) + return rc; + } + s_ctrl->curr_res = res; + return rc; +} + +int32_t msm_sensor_mode_init_bdata(struct msm_sensor_ctrl_t *s_ctrl, + int mode, struct sensor_init_cfg *init_info) +{ + int32_t rc = 0; + CDBG("%s: %d\n", __func__, __LINE__); + if (mode != s_ctrl->cam_mode) { + if (init_info->prev_res >= + s_ctrl->msm_sensor_reg.num_conf || + init_info->pict_res >= + s_ctrl->msm_sensor_reg.num_conf) { + CDBG("Resolution does not exist"); + return -EINVAL; + } + + s_ctrl->prev_res = init_info->prev_res; + s_ctrl->pict_res = init_info->pict_res; + s_ctrl->curr_res = MSM_SENSOR_INVALID_RES; + s_ctrl->cam_mode = mode; + + s_ctrl->prev_frame_length_lines = + msm_sensor_read_b_conf_wdata(s_ctrl, + s_ctrl->prev_res, + s_ctrl->frame_length_lines_array_addr); + s_ctrl->prev_line_length_pck = + msm_sensor_read_b_conf_wdata(s_ctrl, + s_ctrl->prev_res, + s_ctrl->line_length_pck_array_addr); + + s_ctrl->snap_frame_length_lines = + msm_sensor_read_b_conf_wdata(s_ctrl, + s_ctrl->pict_res, + s_ctrl->frame_length_lines_array_addr); + + s_ctrl->snap_line_length_pck = + msm_sensor_read_b_conf_wdata(s_ctrl, + s_ctrl->pict_res, + s_ctrl->line_length_pck_array_addr); + + + rc = s_ctrl->func_tbl.sensor_setting(s_ctrl, + MSM_SENSOR_REG_INIT, s_ctrl->prev_res); + } + return rc; +} + +int32_t msm_sensor_mode_init_wdata(struct msm_sensor_ctrl_t *s_ctrl, + int mode, struct sensor_init_cfg *init_info) +{ + int32_t rc = 0; + CDBG("%s: %d\n", __func__, __LINE__); + if (mode != s_ctrl->cam_mode) { + if (init_info->prev_res >= + s_ctrl->msm_sensor_reg.num_conf || + init_info->pict_res >= + s_ctrl->msm_sensor_reg.num_conf) { + CDBG("Resolution does not exist"); + return -EINVAL; + } + + s_ctrl->prev_res = init_info->prev_res; + s_ctrl->pict_res = init_info->pict_res; + s_ctrl->curr_res = MSM_SENSOR_INVALID_RES; + s_ctrl->cam_mode = mode; + + s_ctrl->prev_frame_length_lines = + msm_sensor_read_w_conf_wdata(s_ctrl, + s_ctrl->prev_res, + s_ctrl->frame_length_lines_array_addr); + s_ctrl->prev_line_length_pck = + msm_sensor_read_w_conf_wdata(s_ctrl, + s_ctrl->prev_res, + s_ctrl->line_length_pck_array_addr); + + s_ctrl->snap_frame_length_lines = + msm_sensor_read_w_conf_wdata(s_ctrl, + s_ctrl->pict_res, + s_ctrl->frame_length_lines_array_addr); + + s_ctrl->snap_line_length_pck = + msm_sensor_read_w_conf_wdata(s_ctrl, + s_ctrl->pict_res, + s_ctrl->line_length_pck_array_addr); + + + rc = s_ctrl->func_tbl.sensor_setting(s_ctrl, + MSM_SENSOR_REG_INIT, s_ctrl->prev_res); + } + return rc; +} + +int32_t msm_sensor_config(struct msm_sensor_ctrl_t *s_ctrl, void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(s_ctrl->msm_sensor_mutex); + CDBG("msm_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + s_ctrl->func_tbl. + sensor_get_pict_fps( + s_ctrl, + cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + s_ctrl->func_tbl. + sensor_get_prev_lines_pf + (s_ctrl); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + s_ctrl->func_tbl. + sensor_get_prev_pixels_pl + (s_ctrl); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + s_ctrl->func_tbl. + sensor_get_pict_lines_pf + (s_ctrl); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + s_ctrl->func_tbl. + sensor_get_pict_pixels_pl + (s_ctrl); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + s_ctrl->func_tbl. + sensor_get_pict_max_exp_lc + (s_ctrl); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = s_ctrl->func_tbl. + sensor_set_fps( + s_ctrl, + &(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = + s_ctrl->func_tbl. + sensor_write_exp_gain( + s_ctrl, + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + rc = + s_ctrl->func_tbl. + sensor_write_exp_gain( + s_ctrl, + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = s_ctrl->func_tbl. + sensor_set_sensor_mode( + s_ctrl, + cdata.mode, + cdata.rs); + break; + + case CFG_PWR_DOWN: + break; + + case CFG_MOVE_FOCUS: + break; + + case CFG_SET_DEFAULT_FOCUS: + break; + + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = 32; + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_EFFECT: + break; + + + case CFG_SEND_WB_INFO: + break; + + case CFG_SENSOR_INIT: + rc = s_ctrl->func_tbl. + sensor_mode_init( + s_ctrl, + cdata.mode, + &(cdata.cfg.init_info)); + break; + + default: + rc = -EFAULT; + break; + } + + mutex_unlock(s_ctrl->msm_sensor_mutex); + + return rc; +} + +int16_t msm_sensor_match_id(struct msm_sensor_ctrl_t *s_ctrl) +{ + int rc = 0; + uint16_t chipid = 0; + rc = msm_sensor_i2c_waddr_read_w(s_ctrl, + s_ctrl->sensor_id_addr, &chipid); + CDBG("msm_sensor id: %d\n", chipid); + if (chipid != s_ctrl->sensor_id) { + CDBG("msm_sensor_match_id chip id doesnot match\n"); + return -ENODEV; + } + return rc; +} + +int msm_sensor_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + struct msm_sensor_ctrl_t *this_ctrl; + CDBG("%s_i2c_probe called\n", client->name); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + this_ctrl = (struct msm_sensor_ctrl_t *)(id->driver_data); + this_ctrl->msm_sensor_client = client; + return 0; + +probe_failure: + CDBG("%s_i2c_probe failed\n", client->name); + return rc; +} + +int msm_sensor_probe(struct msm_sensor_ctrl_t *s_ctrl, + const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(s_ctrl->msm_sensor_i2c_driver); + if (rc < 0 || s_ctrl->msm_sensor_client == NULL) { + rc = -ENOTSUPP; + CDBG("I2C add driver failed"); + goto probe_fail; + } + + rc = s_ctrl->func_tbl.sensor_power_up(info); + if (rc < 0) + goto probe_fail; + s->s_init = s_ctrl->func_tbl.sensor_open_init; + s->s_release = s_ctrl->func_tbl.sensor_release; + s->s_config = s_ctrl->func_tbl.sensor_config; + s->s_camera_type = s_ctrl->camera_type; + s->s_mount_angle = 0; + s_ctrl->func_tbl.sensor_power_down(info); + return rc; +probe_fail: + return rc; +} + +int msm_sensor_v4l2_probe(struct msm_sensor_ctrl_t *s_ctrl, + const struct msm_camera_sensor_info *info, + struct v4l2_subdev *sdev, struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = s_ctrl->func_tbl.sensor_probe(s_ctrl, info, s); + if (rc < 0) + return rc; + + s_ctrl->sensor_v4l2_subdev = sdev; + v4l2_i2c_subdev_init(s_ctrl->sensor_v4l2_subdev, + s_ctrl->msm_sensor_client, s_ctrl->sensor_v4l2_subdev_ops); + s_ctrl->sensor_v4l2_subdev->dev_priv = (void *) s_ctrl; + return rc; +} + +int msm_sensor_v4l2_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + struct msm_sensor_ctrl_t *s_ctrl = + (struct msm_sensor_ctrl_t *) sd->dev_priv; + if ((unsigned int)index >= s_ctrl->sensor_v4l2_subdev_info_size) + return -EINVAL; + + *code = s_ctrl->sensor_v4l2_subdev_info[index].code; + return 0; +} + +static int msm_sensor_debugfs_stream_s(void *data, u64 val) +{ + struct msm_sensor_ctrl_t *s_ctrl = (struct msm_sensor_ctrl_t *) data; + if (val) + s_ctrl->func_tbl.sensor_start_stream(s_ctrl); + else + s_ctrl->func_tbl.sensor_stop_stream(s_ctrl); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(sensor_debugfs_stream, NULL, + msm_sensor_debugfs_stream_s, "%llu\n"); + +static int msm_sensor_debugfs_test_s(void *data, u64 val) +{ + CDBG("val: %llu\n", val); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(sensor_debugfs_test, NULL, + msm_sensor_debugfs_test_s, "%llu\n"); + +int msm_sensor_enable_debugfs(struct msm_sensor_ctrl_t *s_ctrl) +{ + struct dentry *debugfs_base, *sensor_dir; + debugfs_base = debugfs_create_dir("msm_sensor", NULL); + if (!debugfs_base) + return -ENOMEM; + + sensor_dir = debugfs_create_dir + (s_ctrl->sensordata->sensor_name, debugfs_base); + if (!sensor_dir) + return -ENOMEM; + + if (!debugfs_create_file("stream", S_IRUGO | S_IWUSR, sensor_dir, + (void *) s_ctrl, &sensor_debugfs_stream)) + return -ENOMEM; + + if (!debugfs_create_file("test", S_IRUGO | S_IWUSR, sensor_dir, + (void *) s_ctrl, &sensor_debugfs_test)) + return -ENOMEM; + + return 0; +} diff --git a/drivers/media/video/msm/msm_sensor.h b/drivers/media/video/msm/msm_sensor.h new file mode 100644 index 00000000000..d8224cea91f --- /dev/null +++ b/drivers/media/video/msm/msm_sensor.h @@ -0,0 +1,246 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#define Q8 0x00000100 +#define Q10 0x00000400 + +enum msm_sensor_resolution_t { + MSM_SENSOR_RES_0, + MSM_SENSOR_RES_1, + MSM_SENSOR_RES_2, + MSM_SENSOR_RES_3, + MSM_SENSOR_RES_4, + MSM_SENSOR_RES_5, + MSM_SENSOR_RES_6, + MSM_SENSOR_RES_7, + MSM_SENSOR_INVALID_RES, +}; + +#define MSM_SENSOR_MCLK_8HZ 8000000 +#define MSM_SENSOR_MCLK_16HZ 16000000 +#define MSM_SENSOR_MCLK_24HZ 24000000 + +enum msm_sensor_reg_update { + /* Sensor egisters that need to be updated during initialization */ + MSM_SENSOR_REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + MSM_SENSOR_UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + MSM_SENSOR_UPDATE_ALL, + /* Not valid update */ + MSM_SENSOR_UPDATE_INVALID +}; + +enum msm_sensor_cam_mode_t { + MSM_SENSOR_MODE_2D_RIGHT, + MSM_SENSOR_MODE_2D_LEFT, + MSM_SENSOR_MODE_3D, + MSM_SENSOR_MODE_INVALID +}; + +struct msm_sensor_i2c_reg_conf { + unsigned short reg_addr; + unsigned short reg_data; +}; + +struct msm_sensor_i2c_conf_array { + struct msm_sensor_i2c_reg_conf *conf; + unsigned short size; + unsigned short delay; +}; + +struct msm_sensor_reg_t { + struct msm_sensor_i2c_reg_conf *start_stream_conf; + uint8_t start_stream_conf_size; + struct msm_sensor_i2c_reg_conf *stop_stream_conf; + uint8_t stop_stream_conf_size; + struct msm_sensor_i2c_reg_conf *group_hold_on_conf; + uint8_t group_hold_on_conf_size; + struct msm_sensor_i2c_reg_conf *group_hold_off_conf; + uint8_t group_hold_off_conf_size; + struct msm_sensor_i2c_conf_array *init_settings; + uint8_t init_size; + struct msm_sensor_i2c_conf_array *res_settings; + uint8_t num_conf; +}; + +struct v4l2_subdev_info { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; + uint16_t fmt; + uint16_t order; +}; + +struct msm_sensor_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + struct i2c_client *msm_sensor_client; + struct i2c_driver *msm_sensor_i2c_driver; + struct msm_sensor_reg_t msm_sensor_reg; + + uint16_t sensor_id_addr; + uint16_t sensor_id; + uint16_t frame_length_lines_addr; + uint16_t line_length_pck_addr; + uint16_t global_gain_addr; + uint16_t coarse_int_time_addr; + + uint8_t frame_length_lines_array_addr; + uint8_t line_length_pck_array_addr; + + uint16_t curr_line_length_pck; + uint16_t curr_frame_length_lines; + uint16_t prev_line_length_pck; + uint16_t prev_frame_length_lines; + uint16_t snap_line_length_pck; + uint16_t snap_frame_length_lines; + uint16_t vert_offset; + + uint16_t fps; + uint32_t fps_divider; + enum msm_sensor_resolution_t prev_res; + enum msm_sensor_resolution_t pict_res; + enum msm_sensor_resolution_t curr_res; + enum msm_sensor_cam_mode_t cam_mode; + enum msm_camera_type camera_type; + + struct mutex *msm_sensor_mutex; + bool config_csi_flag; + struct msm_camera_csi_params *csi_params; + + /*To Do: Changing v4l2_subdev to a pointer according to yupeng*/ + struct v4l2_subdev *sensor_v4l2_subdev; + struct v4l2_subdev_info *sensor_v4l2_subdev_info; + uint8_t sensor_v4l2_subdev_info_size; + struct v4l2_subdev_ops *sensor_v4l2_subdev_ops; + + struct msm_sensor_fn_t { + void (*sensor_start_stream) (struct msm_sensor_ctrl_t *); + void (*sensor_stop_stream) (struct msm_sensor_ctrl_t *); + void (*sensor_group_hold_on) (struct msm_sensor_ctrl_t *); + void (*sensor_group_hold_off) (struct msm_sensor_ctrl_t *); + + uint16_t (*sensor_get_prev_lines_pf) + (struct msm_sensor_ctrl_t *); + uint16_t (*sensor_get_prev_pixels_pl) + (struct msm_sensor_ctrl_t *); + uint16_t (*sensor_get_pict_lines_pf) + (struct msm_sensor_ctrl_t *); + uint16_t (*sensor_get_pict_pixels_pl) + (struct msm_sensor_ctrl_t *); + uint32_t (*sensor_get_pict_max_exp_lc) + (struct msm_sensor_ctrl_t *); + void (*sensor_get_pict_fps) (struct msm_sensor_ctrl_t *, + uint16_t, uint16_t *); + int32_t (*sensor_set_fps) (struct msm_sensor_ctrl_t *, + struct fps_cfg *); + int32_t (*sensor_write_exp_gain) (struct msm_sensor_ctrl_t *, + uint16_t, uint32_t); + int32_t (*sensor_setting) (struct msm_sensor_ctrl_t *, + int update_type, int rt); + int32_t (*sensor_set_sensor_mode) + (struct msm_sensor_ctrl_t *, int, int); + int32_t (*sensor_mode_init) (struct msm_sensor_ctrl_t *, + int, struct sensor_init_cfg *); + int (*sensor_config) (void __user *); + int (*sensor_open_init) (const struct msm_camera_sensor_info *); + int (*sensor_release) (void); + int (*sensor_power_down) + (const struct msm_camera_sensor_info *); + int (*sensor_power_up) (const struct msm_camera_sensor_info *); + int (*sensor_probe) (struct msm_sensor_ctrl_t *s_ctrl, + const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s); + } func_tbl; +}; + +int32_t msm_sensor_i2c_rxdata(struct msm_sensor_ctrl_t *s_ctrl, + unsigned char *rxdata, int length); + +int32_t msm_sensor_i2c_txdata(struct msm_sensor_ctrl_t *s_ctrl, + unsigned char *txdata, int length); + +int32_t msm_sensor_i2c_waddr_write_b(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t waddr, uint8_t bdata); + +int32_t msm_sensor_i2c_waddr_write_w(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t waddr, uint16_t wdata); + +int32_t msm_sensor_i2c_waddr_read_w(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t waddr, uint16_t *data); + +int32_t msm_sensor_i2c_waddr_write_b_tbl(struct msm_sensor_ctrl_t *s_ctrl, + struct msm_sensor_i2c_reg_conf const *reg_conf_tbl, uint8_t size); + +int32_t msm_sensor_i2c_waddr_write_w_tbl(struct msm_sensor_ctrl_t *s_ctrl, + struct msm_sensor_i2c_reg_conf const *reg_conf_tbl, uint8_t size); + +void msm_sensor_start_stream(struct msm_sensor_ctrl_t *s_ctrl); +void msm_sensor_stop_stream(struct msm_sensor_ctrl_t *s_ctrl); +void msm_sensor_group_hold_on(struct msm_sensor_ctrl_t *s_ctrl); +void msm_sensor_group_hold_off(struct msm_sensor_ctrl_t *s_ctrl); + +uint16_t msm_sensor_get_prev_lines_pf(struct msm_sensor_ctrl_t *s_ctrl); +uint16_t msm_sensor_get_prev_pixels_pl(struct msm_sensor_ctrl_t *s_ctrl); +uint16_t msm_sensor_get_pict_lines_pf(struct msm_sensor_ctrl_t *s_ctrl); +uint16_t msm_sensor_get_pict_pixels_pl(struct msm_sensor_ctrl_t *s_ctrl); +uint32_t msm_sensor_get_pict_max_exp_lc(struct msm_sensor_ctrl_t *s_ctrl); +void msm_sensor_get_pict_fps(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t fps, uint16_t *pfps); +int32_t msm_sensor_set_fps(struct msm_sensor_ctrl_t *s_ctrl, + struct fps_cfg *fps); +int32_t msm_sensor_write_exp_gain1(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line); +int32_t msm_sensor_write_exp_gain2(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line); +int32_t msm_sensor_set_sensor_mode_b(struct msm_sensor_ctrl_t *s_ctrl, + int mode, int res); +int32_t msm_sensor_set_sensor_mode_w(struct msm_sensor_ctrl_t *s_ctrl, + int mode, int res); +int32_t msm_sensor_mode_init_bdata(struct msm_sensor_ctrl_t *s_ctrl, + int mode, struct sensor_init_cfg *init_info); +int32_t msm_sensor_mode_init_wdata(struct msm_sensor_ctrl_t *s_ctrl, + int mode, struct sensor_init_cfg *init_info); +int32_t msm_sensor_config(struct msm_sensor_ctrl_t *s_ctrl, + void __user *argp); +int16_t msm_sensor_match_id(struct msm_sensor_ctrl_t *s_ctrl); +uint16_t msm_sensor_read_b_conf_wdata(struct msm_sensor_ctrl_t *s_ctrl, + enum msm_sensor_resolution_t res, int8_t array_addr); +uint16_t msm_sensor_read_w_conf_wdata(struct msm_sensor_ctrl_t *s_ctrl, + enum msm_sensor_resolution_t res, int8_t array_addr); + +int msm_sensor_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id); + +int msm_sensor_probe(struct msm_sensor_ctrl_t *s_ctrl, + const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s); + +int msm_sensor_v4l2_probe(struct msm_sensor_ctrl_t *s_ctrl, + const struct msm_camera_sensor_info *info, + struct v4l2_subdev *sdev, struct msm_sensor_ctrl *s); + +int msm_sensor_v4l2_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code); + +int msm_sensor_write_b_init_settings(struct msm_sensor_ctrl_t *s_ctrl); +int msm_sensor_write_w_init_settings(struct msm_sensor_ctrl_t *s_ctrl); +int msm_sensor_write_b_res_settings + (struct msm_sensor_ctrl_t *s_ctrl, uint16_t res); +int msm_sensor_write_w_res_settings + (struct msm_sensor_ctrl_t *s_ctrl, uint16_t res); + +int msm_sensor_enable_debugfs(struct msm_sensor_ctrl_t *s_ctrl); diff --git a/drivers/media/video/msm/msm_vfe31.c b/drivers/media/video/msm/msm_vfe31.c new file mode 100644 index 00000000000..aa35096bae8 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe31.c @@ -0,0 +1,3729 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_vfe31.h" +#include "msm_vpe1.h" + +atomic_t irq_cnt; + +#define CHECKED_COPY_FROM_USER(in) { \ + if (copy_from_user((in), (void __user *)cmd->value, \ + cmd->length)) { \ + rc = -EFAULT; \ + break; \ + } \ +} + +static struct vfe31_ctrl_type *vfe31_ctrl; +static struct msm_camera_io_clk camio_clk; +static void *vfe_syncdata; +static void vfe31_send_msg_no_payload(enum VFE31_MESSAGE_ID id); +static void vfe31_reset_hist_cfg(void); + +struct vfe31_isr_queue_cmd { + struct list_head list; + uint32_t vfeInterruptStatus0; + uint32_t vfeInterruptStatus1; + uint32_t vfePingPongStatus; + struct vfe_frame_asf_info vfeAsfFrameInfo; + struct vfe_frame_bpc_info vfeBpcFrameInfo; + struct vfe_msg_camif_status vfeCamifStatusLocal; +}; + +static struct vfe31_cmd_type vfe31_cmd[] = { +/* 0*/ {V31_DUMMY_0}, + {V31_SET_CLK}, + {V31_RESET}, + {V31_START}, + {V31_TEST_GEN_START}, +/* 5*/ {V31_OPERATION_CFG, V31_OPERATION_CFG_LEN}, + {V31_AXI_OUT_CFG, V31_AXI_OUT_LEN, V31_AXI_OUT_OFF, 0xFF}, + {V31_CAMIF_CFG, V31_CAMIF_LEN, V31_CAMIF_OFF, 0xFF}, + {V31_AXI_INPUT_CFG}, + {V31_BLACK_LEVEL_CFG, V31_BLACK_LEVEL_LEN, V31_BLACK_LEVEL_OFF, + 0xFF}, +/*10*/ {V31_ROLL_OFF_CFG, V31_ROLL_OFF_CFG_LEN, V31_ROLL_OFF_CFG_OFF, + 0xFF}, + {V31_DEMUX_CFG, V31_DEMUX_LEN, V31_DEMUX_OFF, 0xFF}, + {V31_DEMOSAIC_0_CFG, V31_DEMOSAIC_0_LEN, V31_DEMOSAIC_0_OFF, + 0xFF}, + {V31_DEMOSAIC_1_CFG, V31_DEMOSAIC_1_LEN, V31_DEMOSAIC_1_OFF, + 0xFF}, + {V31_DEMOSAIC_2_CFG, V31_DEMOSAIC_2_LEN, V31_DEMOSAIC_2_OFF, + 0xFF}, +/*15*/ {V31_FOV_CFG, V31_FOV_LEN, V31_FOV_OFF, 0xFF}, + {V31_MAIN_SCALER_CFG, V31_MAIN_SCALER_LEN, V31_MAIN_SCALER_OFF, + 0xFF}, + {V31_WB_CFG, V31_WB_LEN, V31_WB_OFF, 0xFF}, + {V31_COLOR_COR_CFG, V31_COLOR_COR_LEN, V31_COLOR_COR_OFF, 0xFF}, + {V31_RGB_G_CFG, V31_RGB_G_LEN, V31_RGB_G_OFF, 0xFF}, +/*20*/ {V31_LA_CFG, V31_LA_LEN, V31_LA_OFF, 0xFF }, + {V31_CHROMA_EN_CFG, V31_CHROMA_EN_LEN, V31_CHROMA_EN_OFF, 0xFF}, + {V31_CHROMA_SUP_CFG, V31_CHROMA_SUP_LEN, V31_CHROMA_SUP_OFF, + 0xFF}, + {V31_MCE_CFG, V31_MCE_LEN, V31_MCE_OFF, 0xFF}, + {V31_SK_ENHAN_CFG, V31_SCE_LEN, V31_SCE_OFF, 0xFF}, +/*25*/ {V31_ASF_CFG, V31_ASF_LEN, V31_ASF_OFF, 0xFF}, + {V31_S2Y_CFG, V31_S2Y_LEN, V31_S2Y_OFF, 0xFF}, + {V31_S2CbCr_CFG, V31_S2CbCr_LEN, V31_S2CbCr_OFF, 0xFF}, + {V31_CHROMA_SUBS_CFG, V31_CHROMA_SUBS_LEN, V31_CHROMA_SUBS_OFF, + 0xFF}, + {V31_OUT_CLAMP_CFG, V31_OUT_CLAMP_LEN, V31_OUT_CLAMP_OFF, + 0xFF}, +/*30*/ {V31_FRAME_SKIP_CFG, V31_FRAME_SKIP_LEN, V31_FRAME_SKIP_OFF, + 0xFF}, + {V31_DUMMY_1}, + {V31_DUMMY_2}, + {V31_DUMMY_3}, + {V31_UPDATE}, +/*35*/ {V31_BL_LVL_UPDATE, V31_BLACK_LEVEL_LEN, V31_BLACK_LEVEL_OFF, + 0xFF}, + {V31_DEMUX_UPDATE, V31_DEMUX_LEN, V31_DEMUX_OFF, 0xFF}, + {V31_DEMOSAIC_1_UPDATE, V31_DEMOSAIC_1_LEN, V31_DEMOSAIC_1_OFF, + 0xFF}, + {V31_DEMOSAIC_2_UPDATE, V31_DEMOSAIC_2_LEN, V31_DEMOSAIC_2_OFF, + 0xFF}, + {V31_FOV_UPDATE, V31_FOV_LEN, V31_FOV_OFF, 0xFF}, +/*40*/ {V31_MAIN_SCALER_UPDATE, V31_MAIN_SCALER_LEN, V31_MAIN_SCALER_OFF, + 0xFF}, + {V31_WB_UPDATE, V31_WB_LEN, V31_WB_OFF, 0xFF}, + {V31_COLOR_COR_UPDATE, V31_COLOR_COR_LEN, V31_COLOR_COR_OFF, + 0xFF}, + {V31_RGB_G_UPDATE, V31_RGB_G_LEN, V31_CHROMA_EN_OFF, 0xFF}, + {V31_LA_UPDATE, V31_LA_LEN, V31_LA_OFF, 0xFF }, +/*45*/ {V31_CHROMA_EN_UPDATE, V31_CHROMA_EN_LEN, V31_CHROMA_EN_OFF, + 0xFF}, + {V31_CHROMA_SUP_UPDATE, V31_CHROMA_SUP_LEN, V31_CHROMA_SUP_OFF, + 0xFF}, + {V31_MCE_UPDATE, V31_MCE_LEN, V31_MCE_OFF, 0xFF}, + {V31_SK_ENHAN_UPDATE, V31_SCE_LEN, V31_SCE_OFF, 0xFF}, + {V31_S2CbCr_UPDATE, V31_S2CbCr_LEN, V31_S2CbCr_OFF, 0xFF}, +/*50*/ {V31_S2Y_UPDATE, V31_S2Y_LEN, V31_S2Y_OFF, 0xFF}, + {V31_ASF_UPDATE, V31_ASF_UPDATE_LEN, V31_ASF_OFF, 0xFF}, + {V31_FRAME_SKIP_UPDATE}, + {V31_CAMIF_FRAME_UPDATE}, + {V31_STATS_AF_UPDATE, V31_STATS_AF_LEN, V31_STATS_AF_OFF}, +/*55*/ {V31_STATS_AE_UPDATE, V31_STATS_AE_LEN, V31_STATS_AE_OFF}, + {V31_STATS_AWB_UPDATE, V31_STATS_AWB_LEN, V31_STATS_AWB_OFF}, + {V31_STATS_RS_UPDATE, V31_STATS_RS_LEN, V31_STATS_RS_OFF}, + {V31_STATS_CS_UPDATE, V31_STATS_CS_LEN, V31_STATS_CS_OFF}, + {V31_STATS_SKIN_UPDATE}, +/*60*/ {V31_STATS_IHIST_UPDATE, V31_STATS_IHIST_LEN, V31_STATS_IHIST_OFF}, + {V31_DUMMY_4}, + {V31_EPOCH1_ACK}, + {V31_EPOCH2_ACK}, + {V31_START_RECORDING}, +/*65*/ {V31_STOP_RECORDING}, + {V31_DUMMY_5}, + {V31_DUMMY_6}, + {V31_CAPTURE, V31_CAPTURE_LEN, 0xFF}, + {V31_DUMMY_7}, +/*70*/ {V31_STOP}, + {V31_GET_HW_VERSION}, + {V31_GET_FRAME_SKIP_COUNTS}, + {V31_OUTPUT1_BUFFER_ENQ}, + {V31_OUTPUT2_BUFFER_ENQ}, +/*75*/ {V31_OUTPUT3_BUFFER_ENQ}, + {V31_JPEG_OUT_BUF_ENQ}, + {V31_RAW_OUT_BUF_ENQ}, + {V31_RAW_IN_BUF_ENQ}, + {V31_STATS_AF_ENQ}, +/*80*/ {V31_STATS_AE_ENQ}, + {V31_STATS_AWB_ENQ}, + {V31_STATS_RS_ENQ}, + {V31_STATS_CS_ENQ}, + {V31_STATS_SKIN_ENQ}, +/*85*/ {V31_STATS_IHIST_ENQ}, + {V31_DUMMY_8}, + {V31_JPEG_ENC_CFG}, + {V31_DUMMY_9}, + {V31_STATS_AF_START, V31_STATS_AF_LEN, V31_STATS_AF_OFF}, +/*90*/ {V31_STATS_AF_STOP}, + {V31_STATS_AE_START, V31_STATS_AE_LEN, V31_STATS_AE_OFF}, + {V31_STATS_AE_STOP}, + {V31_STATS_AWB_START, V31_STATS_AWB_LEN, V31_STATS_AWB_OFF}, + {V31_STATS_AWB_STOP}, +/*95*/ {V31_STATS_RS_START, V31_STATS_RS_LEN, V31_STATS_RS_OFF}, + {V31_STATS_RS_STOP}, + {V31_STATS_CS_START, V31_STATS_CS_LEN, V31_STATS_CS_OFF}, + {V31_STATS_CS_STOP}, + {V31_STATS_SKIN_START}, +/*100*/ {V31_STATS_SKIN_STOP}, + {V31_STATS_IHIST_START, + V31_STATS_IHIST_LEN, V31_STATS_IHIST_OFF}, + {V31_STATS_IHIST_STOP}, + {V31_DUMMY_10}, + {V31_SYNC_TIMER_SETTING, V31_SYNC_TIMER_LEN, + V31_SYNC_TIMER_OFF}, +/*105*/ {V31_ASYNC_TIMER_SETTING, V31_ASYNC_TIMER_LEN, V31_ASYNC_TIMER_OFF}, + {V31_LIVESHOT}, + {V31_ZSL, V31_CAPTURE_LEN, 0xFF}, + {V31_STEREOCAM}, + {V31_LA_SETUP}, +/*110*/ {V31_XBAR_CFG, V31_XBAR_CFG_LEN, V31_XBAR_CFG_OFF}, +/*111*/ {V31_EZTUNE_CFG, V31_EZTUNE_CFG_LEN, V31_EZTUNE_CFG_OFF}, +}; + +uint32_t vfe31_AXI_WM_CFG[] = { + 0x0000004C, + 0x00000064, + 0x0000007C, + 0x00000094, + 0x000000AC, + 0x000000C4, + 0x000000DC, +}; + +static const char *vfe31_general_cmd[] = { + "DUMMY_0", /* 0 */ + "SET_CLK", + "RESET", + "START", + "TEST_GEN_START", + "OPERATION_CFG", /* 5 */ + "AXI_OUT_CFG", + "CAMIF_CFG", + "AXI_INPUT_CFG", + "BLACK_LEVEL_CFG", + "ROLL_OFF_CFG", /* 10 */ + "DEMUX_CFG", + "DEMOSAIC_0_CFG", /* general */ + "DEMOSAIC_1_CFG", /* ABF */ + "DEMOSAIC_2_CFG", /* BPC */ + "FOV_CFG", /* 15 */ + "MAIN_SCALER_CFG", + "WB_CFG", + "COLOR_COR_CFG", + "RGB_G_CFG", + "LA_CFG", /* 20 */ + "CHROMA_EN_CFG", + "CHROMA_SUP_CFG", + "MCE_CFG", + "SK_ENHAN_CFG", + "ASF_CFG", /* 25 */ + "S2Y_CFG", + "S2CbCr_CFG", + "CHROMA_SUBS_CFG", + "OUT_CLAMP_CFG", + "FRAME_SKIP_CFG", /* 30 */ + "DUMMY_1", + "DUMMY_2", + "DUMMY_3", + "UPDATE", + "BL_LVL_UPDATE", /* 35 */ + "DEMUX_UPDATE", + "DEMOSAIC_1_UPDATE", /* BPC */ + "DEMOSAIC_2_UPDATE", /* ABF */ + "FOV_UPDATE", + "MAIN_SCALER_UPDATE", /* 40 */ + "WB_UPDATE", + "COLOR_COR_UPDATE", + "RGB_G_UPDATE", + "LA_UPDATE", + "CHROMA_EN_UPDATE", /* 45 */ + "CHROMA_SUP_UPDATE", + "MCE_UPDATE", + "SK_ENHAN_UPDATE", + "S2CbCr_UPDATE", + "S2Y_UPDATE", /* 50 */ + "ASF_UPDATE", + "FRAME_SKIP_UPDATE", + "CAMIF_FRAME_UPDATE", + "STATS_AF_UPDATE", + "STATS_AE_UPDATE", /* 55 */ + "STATS_AWB_UPDATE", + "STATS_RS_UPDATE", + "STATS_CS_UPDATE", + "STATS_SKIN_UPDATE", + "STATS_IHIST_UPDATE", /* 60 */ + "DUMMY_4", + "EPOCH1_ACK", + "EPOCH2_ACK", + "START_RECORDING", + "STOP_RECORDING", /* 65 */ + "DUMMY_5", + "DUMMY_6", + "CAPTURE", + "DUMMY_7", + "STOP", /* 70 */ + "GET_HW_VERSION", + "GET_FRAME_SKIP_COUNTS", + "OUTPUT1_BUFFER_ENQ", + "OUTPUT2_BUFFER_ENQ", + "OUTPUT3_BUFFER_ENQ", /* 75 */ + "JPEG_OUT_BUF_ENQ", + "RAW_OUT_BUF_ENQ", + "RAW_IN_BUF_ENQ", + "STATS_AF_ENQ", + "STATS_AE_ENQ", /* 80 */ + "STATS_AWB_ENQ", + "STATS_RS_ENQ", + "STATS_CS_ENQ", + "STATS_SKIN_ENQ", + "STATS_IHIST_ENQ", /* 85 */ + "DUMMY_8", + "JPEG_ENC_CFG", + "DUMMY_9", + "STATS_AF_START", + "STATS_AF_STOP", /* 90 */ + "STATS_AE_START", + "STATS_AE_STOP", + "STATS_AWB_START", + "STATS_AWB_STOP", + "STATS_RS_START", /* 95 */ + "STATS_RS_STOP", + "STATS_CS_START", + "STATS_CS_STOP", + "STATS_SKIN_START", + "STATS_SKIN_STOP", /* 100 */ + "STATS_IHIST_START", + "STATS_IHIST_STOP", + "DUMMY_10", + "SYNC_TIMER_SETTING", + "ASYNC_TIMER_SETTING", /* 105 */ + "V31_LIVESHOT", + "V31_ZSL", + "V31_STEREOCAM", + "V31_LA_SETUP", + "V31_XBAR_CFG", +}; + +static void vfe_addr_convert(struct msm_vfe_phy_info *pinfo, + enum vfe_resp_msg type, void *data, void **ext, int32_t *elen) +{ + uint8_t outid; + switch (type) { + case VFE_MSG_OUTPUT_T: + case VFE_MSG_OUTPUT_P: + case VFE_MSG_OUTPUT_S: + case VFE_MSG_OUTPUT_V: + { + pinfo->output_id = + ((struct vfe_message *)data)->_u.msgOut.output_id; + + switch (type) { + case VFE_MSG_OUTPUT_P: + outid = OUTPUT_TYPE_P; + break; + case VFE_MSG_OUTPUT_V: + outid = OUTPUT_TYPE_V; + break; + case VFE_MSG_OUTPUT_T: + outid = OUTPUT_TYPE_T; + break; + case VFE_MSG_OUTPUT_S: + outid = OUTPUT_TYPE_S; + break; + default: + outid = 0xff; + break; + } + pinfo->output_id = outid; + pinfo->y_phy = + ((struct vfe_message *)data)->_u.msgOut.yBuffer; + pinfo->cbcr_phy = + ((struct vfe_message *)data)->_u.msgOut.cbcrBuffer; + + pinfo->frame_id = + ((struct vfe_message *)data)->_u.msgOut.frameCounter; + + ((struct vfe_msg_output *)(vfe31_ctrl->extdata))->bpcInfo = + ((struct vfe_message *)data)->_u.msgOut.bpcInfo; + ((struct vfe_msg_output *)(vfe31_ctrl->extdata))->asfInfo = + ((struct vfe_message *)data)->_u.msgOut.asfInfo; + ((struct vfe_msg_output *)(vfe31_ctrl->extdata))->frameCounter = + ((struct vfe_message *)data)->_u.msgOut.frameCounter; + *ext = vfe31_ctrl->extdata; + *elen = vfe31_ctrl->extlen; + } + break; + + default: + break; + } /* switch */ +} + + +static void vfe31_proc_ops(enum VFE31_MESSAGE_ID id, void *msg, size_t len) +{ + struct msm_vfe_resp *rp; + + rp = vfe31_ctrl->resp->vfe_alloc(sizeof(struct msm_vfe_resp), + vfe31_ctrl->syncdata, GFP_ATOMIC); + if (!rp) { + CDBG("rp: cannot allocate buffer\n"); + return; + } + CDBG("vfe31_proc_ops, msgId = %d\n", id); + rp->evt_msg.type = MSM_CAMERA_MSG; + rp->evt_msg.msg_id = id; + rp->evt_msg.len = len; + rp->evt_msg.data = msg; + + switch (rp->evt_msg.msg_id) { + case MSG_ID_SNAPSHOT_DONE: + rp->type = VFE_MSG_SNAPSHOT; + break; + + case MSG_ID_OUTPUT_P: + rp->type = VFE_MSG_OUTPUT_P; + vfe_addr_convert(&(rp->phy), VFE_MSG_OUTPUT_P, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_OUTPUT_T: + rp->type = VFE_MSG_OUTPUT_T; + vfe_addr_convert(&(rp->phy), VFE_MSG_OUTPUT_T, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_OUTPUT_S: + rp->type = VFE_MSG_OUTPUT_S; + vfe_addr_convert(&(rp->phy), VFE_MSG_OUTPUT_S, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_OUTPUT_V: + rp->type = VFE_MSG_OUTPUT_V; + vfe_addr_convert(&(rp->phy), VFE_MSG_OUTPUT_V, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_COMMON: + rp->type = VFE_MSG_COMMON; + rp->stats_msg.status_bits = ((struct vfe_message *) + rp->evt_msg.data)->_u.msgStats.status_bits; + rp->stats_msg.frame_id = ((struct vfe_message *) + rp->evt_msg.data)->_u.msgStats.frameCounter; + + rp->stats_msg.aec_buff = ((struct vfe_message *) + rp->evt_msg.data)->_u.msgStats.buff.aec; + rp->stats_msg.awb_buff = ((struct vfe_message *) + rp->evt_msg.data)->_u.msgStats.buff.awb; + rp->stats_msg.af_buff = ((struct vfe_message *) + rp->evt_msg.data)->_u.msgStats.buff.af; + rp->stats_msg.ihist_buff = ((struct vfe_message *) + rp->evt_msg.data)->_u.msgStats.buff.ihist; + rp->stats_msg.rs_buff = ((struct vfe_message *) + rp->evt_msg.data)->_u.msgStats.buff.rs; + rp->stats_msg.cs_buff = ((struct vfe_message *) + rp->evt_msg.data)->_u.msgStats.buff.cs; + break; + + case MSG_ID_SYNC_TIMER0_DONE: + rp->type = VFE_MSG_SYNC_TIMER0; + break; + + case MSG_ID_SYNC_TIMER1_DONE: + rp->type = VFE_MSG_SYNC_TIMER1; + break; + + case MSG_ID_SYNC_TIMER2_DONE: + rp->type = VFE_MSG_SYNC_TIMER2; + break; + + default: + rp->type = VFE_MSG_GENERAL; + break; + } + + /* save the frame id.*/ + rp->evt_msg.frame_id = rp->phy.frame_id; + + vfe31_ctrl->resp->vfe_resp(rp, MSM_CAM_Q_VFE_MSG, vfe31_ctrl->syncdata, + GFP_ATOMIC); +} + +static void vfe_send_outmsg(uint8_t msgid, uint32_t pyaddr, + uint32_t pcbcraddr) +{ + struct vfe_message msg; + uint8_t outid; + + msg._d = msgid; /* now the output mode is redundnat. */ + msg._u.msgOut.frameCounter = vfe31_ctrl->vfeFrameId; + + switch (msgid) { + case MSG_ID_OUTPUT_P: + outid = OUTPUT_TYPE_P; + break; + case MSG_ID_OUTPUT_V: + outid = OUTPUT_TYPE_V; + break; + case MSG_ID_OUTPUT_T: + outid = OUTPUT_TYPE_T; + break; + case MSG_ID_OUTPUT_S: + outid = OUTPUT_TYPE_S; + break; + default: + outid = 0xff; /* -1 for error condition.*/ + break; + } + msg._u.msgOut.output_id = msgid; + msg._u.msgOut.yBuffer = pyaddr; + msg._u.msgOut.cbcrBuffer = pcbcraddr; + + vfe31_proc_ops(msgid, &msg, sizeof(struct vfe_message)); + return; +} +static int vfe31_enable(struct camera_enable_cmd *enable) +{ + return 0; +} + +static void vfe31_stop(void) +{ + atomic_set(&vfe31_ctrl->vstate, 0); + atomic_set(&vfe31_ctrl->stop_ack_pending, 1); + + /* in either continuous or snapshot mode, stop command can be issued + * at any time. stop camif immediately. */ + msm_io_w_mb(CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe31_ctrl->vfebase + VFE_CAMIF_COMMAND); + + /* disable all interrupts. */ + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_io_w(VFE_CLEAR_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_io_w(VFE_CLEAR_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_1); + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, + vfe31_ctrl->vfebase + VFE_IRQ_CMD); + + /* now enable only halt_irq & reset_irq */ + msm_io_w(0xf0000000, /* this is for async timer. */ + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_io_w(VFE_IMASK_AXI_HALT, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* then apply axi halt command. */ + msm_io_w_mb(AXI_HALT, + vfe31_ctrl->vfebase + VFE_AXI_CMD); +} + +static int vfe31_disable(struct camera_enable_cmd *enable, + struct platform_device *dev) +{ + msm_camio_set_perf_lvl(S_EXIT); + msm_camio_disable(dev); + return 0; +} + +static int vfe31_add_free_buf2(struct vfe31_output_ch *outch, + uint32_t paddr, uint32_t y_off, uint32_t cbcr_off) +{ + struct vfe31_free_buf *free_buf = NULL; + unsigned long flags = 0; + free_buf = kmalloc(sizeof(struct vfe31_free_buf), GFP_KERNEL); + if (!free_buf) + return -ENOMEM; + + spin_lock_irqsave(&outch->free_buf_lock, flags); + free_buf->paddr = paddr; + free_buf->y_off = y_off; + free_buf->cbcr_off = cbcr_off; + list_add_tail(&free_buf->node, &outch->free_buf_head); + + CDBG("%s: free_buf paddr = 0x%x, y_off = %d, cbcr_off = %d\n", + __func__, free_buf->paddr, free_buf->y_off, + free_buf->cbcr_off); + spin_unlock_irqrestore(&outch->free_buf_lock, flags); + return 0; +} + +#define vfe31_add_free_buf(outch, regptr) \ + vfe31_add_free_buf2(outch, regptr->paddr, regptr->info.y_off, \ + regptr->info.cbcr_off) + +#define vfe31_free_buf_available(outch) \ + (!list_empty(&outch.free_buf_head)) + +static inline struct vfe31_free_buf *vfe31_get_free_buf( + struct vfe31_output_ch *outch) +{ + unsigned long flags = 0; + struct vfe31_free_buf *free_buf = NULL; + spin_lock_irqsave(&outch->free_buf_lock, flags); + if (!list_empty(&outch->free_buf_head)) { + free_buf = list_first_entry(&outch->free_buf_head, + struct vfe31_free_buf, node); + if (free_buf) + list_del_init(&free_buf->node); + } + spin_unlock_irqrestore(&outch->free_buf_lock, flags); + return free_buf; +} + +static inline void vfe31_reset_free_buf_queue( + struct vfe31_output_ch *outch) +{ + unsigned long flags = 0; + struct vfe31_free_buf *free_buf = NULL; + spin_lock_irqsave(&outch->free_buf_lock, flags); + while (!list_empty(&outch->free_buf_head)) { + free_buf = list_first_entry(&outch->free_buf_head, + struct vfe31_free_buf, node); + if (free_buf) { + list_del_init(&free_buf->node); + kfree(free_buf); + } + } + spin_unlock_irqrestore(&outch->free_buf_lock, flags); +} + +#define vfe31_init_free_buf_queue() do { \ + INIT_LIST_HEAD(&vfe31_ctrl->outpath.out0.free_buf_head); \ + INIT_LIST_HEAD(&vfe31_ctrl->outpath.out1.free_buf_head); \ + INIT_LIST_HEAD(&vfe31_ctrl->outpath.out2.free_buf_head); \ + spin_lock_init(&vfe31_ctrl->outpath.out0.free_buf_lock); \ + spin_lock_init(&vfe31_ctrl->outpath.out1.free_buf_lock); \ + spin_lock_init(&vfe31_ctrl->outpath.out2.free_buf_lock); \ +} while (0) + +#define vfe31_reset_free_buf_queue_all() do { \ + vfe31_reset_free_buf_queue(&vfe31_ctrl->outpath.out0); \ + vfe31_reset_free_buf_queue(&vfe31_ctrl->outpath.out1); \ + vfe31_reset_free_buf_queue(&vfe31_ctrl->outpath.out2); \ +} while (0) + +static int vfe31_config_axi(int mode, struct axidata *ad, uint32_t *ao) +{ + int i; + uint32_t *p, *p1, *p2, *p3; + int32_t *ch_info; + struct vfe31_output_ch *outp1, *outp2, *outp3; + struct msm_pmem_region *regp1 = NULL; + struct msm_pmem_region *regp2 = NULL; + struct msm_pmem_region *regp3 = NULL; + int ret; + struct msm_sync* p_sync = (struct msm_sync *)vfe_syncdata; + + outp1 = NULL; + outp2 = NULL; + outp3 = NULL; + + p = ao + 2; + + /* Update the corresponding write masters for each output*/ + ch_info = ao + V31_AXI_CFG_LEN; + vfe31_ctrl->outpath.out0.ch0 = 0x0000FFFF & *ch_info; + vfe31_ctrl->outpath.out0.ch1 = 0x0000FFFF & (*ch_info++ >> 16); + vfe31_ctrl->outpath.out0.ch2 = 0x0000FFFF & *ch_info++; + vfe31_ctrl->outpath.out1.ch0 = 0x0000FFFF & *ch_info; + vfe31_ctrl->outpath.out1.ch1 = 0x0000FFFF & (*ch_info++ >> 16); + vfe31_ctrl->outpath.out1.ch2 = 0x0000FFFF & *ch_info++; + vfe31_ctrl->outpath.out2.ch0 = 0x0000FFFF & *ch_info; + vfe31_ctrl->outpath.out2.ch1 = 0x0000FFFF & (*ch_info++ >> 16); + vfe31_ctrl->outpath.out2.ch2 = 0x0000FFFF & *ch_info++; + + CDBG("vfe31_config_axi: mode = %d, bufnum1 = %d, bufnum2 = %d" + "bufnum3 = %d", mode, ad->bufnum1, ad->bufnum2, ad->bufnum3); + + switch (mode) { + + case OUTPUT_2: { + if (ad->bufnum2 != 3) + return -EINVAL; + regp1 = &(ad->region[ad->bufnum1]); + outp1 = &(vfe31_ctrl->outpath.out0); + vfe31_ctrl->outpath.output_mode |= VFE31_OUTPUT_MODE_PT; + + for (i = 0; i < 2; i++) { + p1 = ao + 6 + i; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.y_off); + + p1 = ao + 12 + i; /* wm1 for cbcr */ + *p1 = (regp1->paddr + regp1->info.cbcr_off); + regp1++; + } + ret = vfe31_add_free_buf(outp1, regp1); + if (ret < 0) + return ret; + } + break; + + case OUTPUT_1_AND_2: + /* use wm0& 4 for thumbnail, wm1&5 for main image.*/ + if ((ad->bufnum1 < 1) || (ad->bufnum2 < 1)) + return -EINVAL; + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_S; /* main image.*/ + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_PT; /* thumbnail. */ + + /* this is thumbnail buffer. */ + regp1 = &(ad->region[ad->bufnum1-1]); + /* this is main image buffer. */ + regp2 = &(ad->region[ad->bufnum1+ad->bufnum2-1]); + + outp1 = &(vfe31_ctrl->outpath.out0); + outp2 = &(vfe31_ctrl->outpath.out1); /* snapshot */ + + /* Parse the buffers!!! */ + if (ad->bufnum2 == 1) { /* assuming bufnum1 = bufnum2 */ + p1 = ao + 6; /* wm0 ping */ + *p1++ = (regp1->paddr + regp1->info.y_off); + + /* this is to duplicate ping address to pong.*/ + *p1 = (regp1->paddr + regp1->info.y_off); + + p1 = ao + 30; /* wm4 ping */ + *p1++ = (regp1->paddr + regp1->info.cbcr_off); + CDBG("%s: regp1->info.cbcr_off = 0x%x\n", __func__, + regp1->info.cbcr_off); + + /* this is to duplicate ping address to pong.*/ + *p1 = (regp1->paddr + regp1->info.cbcr_off); + + p1 = ao + 12; /* wm1 ping */ + *p1++ = (regp2->paddr + regp2->info.y_off); + + /* pong = ping,*/ + *p1 = (regp2->paddr + regp2->info.y_off); + + p1 = ao + 36; /* wm5 */ + *p1++ = (regp2->paddr + regp2->info.cbcr_off); + CDBG("%s: regp2->info.cbcr_off = 0x%x\n", __func__, + regp2->info.cbcr_off); + + /* pong = ping,*/ + *p1 = (regp2->paddr + regp2->info.cbcr_off); + } else { /* more than one snapshot */ + /* first fill ping & pong */ + for (i = 0; i < 2; i++) { + p1 = ao + 6 + i; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.y_off); + p1 = ao + 30 + i; /* wm4 for cbcr */ + *p1 = (regp1->paddr + regp1->info.cbcr_off); + regp1--; + } + + for (i = 0; i < 2; i++) { + p2 = ao + 12 + i; /* wm1 for y */ + *p2 = (regp2->paddr + regp2->info.y_off); + p2 = ao + 36 + i; /* wm5 for cbcr */ + *p2 = (regp2->paddr + regp2->info.cbcr_off); + regp2--; + } + + for (i = 2; i < ad->bufnum1; i++) { + ret = vfe31_add_free_buf(outp1, regp1); + if (ret < 0) + return ret; + regp1--; + } + + for (i = 2; i < ad->bufnum2; i++) { + ret = vfe31_add_free_buf(outp2, regp2); + if (ret < 0) + return ret; + regp2--; + } + } + break; + + case OUTPUT_1_2_AND_3: + CDBG("%s: OUTPUT_1_2_AND_3", __func__); + CDBG("%s: %d %d %d", __func__, ad->bufnum1, ad->bufnum2, + ad->bufnum3); + /* use wm0& 4 for postview, wm1&5 for preview.*/ + /* use wm2& 6 for main img */ + if ((ad->bufnum1 < 1) || (ad->bufnum2 < 1) || (ad->bufnum3 < 1)) + return -EINVAL; + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_S; /* main image.*/ + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_P; /* preview. */ + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_T; /* thumbnail. */ + + /* this is preview buffer. */ + regp1 = &(ad->region[0]); + /* this is thumbnail buffer. */ + regp2 = &(ad->region[ad->bufnum1]); + /* this is main image buffer. */ + regp3 = &(ad->region[ad->bufnum1+ad->bufnum2]); + outp1 = &(vfe31_ctrl->outpath.out0); + outp2 = &(vfe31_ctrl->outpath.out1); + outp3 = &(vfe31_ctrl->outpath.out2); + + /* Parse the buffers!!! */ + /* first fill ping & pong */ + for (i = 0; i < 2; i++) { + p1 = ao + 6 + i; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.y_off); + p1 = ao + 30 + i; /* wm4 for cbcr */ + *p1 = (regp1->paddr + regp1->info.cbcr_off); + regp1++; + } + + for (i = 0; i < 2; i++) { + p2 = ao + 12 + i; /* wm1 for y */ + *p2 = (regp2->paddr + regp2->info.y_off); + p2 = ao + 36 + i; /* wm5 for cbcr */ + *p2 = (regp2->paddr + regp2->info.cbcr_off); + regp2++; + } + + for (i = 0; i < 2; i++) { + p3 = ao + 18 + i; /* wm2 for y */ + *p3 = (regp3->paddr + regp3->info.y_off); + p3 = ao + 42 + i; /* wm6 for cbcr */ + *p3 = (regp3->paddr + regp3->info.cbcr_off); + regp3++; + } + + for (i = 2; i < ad->bufnum1; i++) { + ret = vfe31_add_free_buf(outp1, regp1); + if (ret < 0) + return ret; + regp1++; + } + + for (i = 2; i < ad->bufnum2; i++) { + ret = vfe31_add_free_buf(outp2, regp2); + if (ret < 0) + return ret; + regp2++; + } + + for (i = 2; i < ad->bufnum3; i++) { + ret = vfe31_add_free_buf(outp3, regp3); + if (ret < 0) + return ret; + regp3++; + } + break; + + case OUTPUT_1_AND_3: { + /* use wm0&4 for preview, wm1&5 for video.*/ + if ((ad->bufnum1 < 2) || (ad->bufnum2 < 2)) + return -EINVAL; + +#ifdef CONFIG_MSM_CAMERA_V4L2 + *p++ = 0x1; /* xbar cfg0 */ + *p = 0x1a03; /* xbar cfg1 */ +#endif + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_V; /* video*/ + vfe31_ctrl->outpath.output_mode |= + VFE31_OUTPUT_MODE_PT; /* preview */ + + regp1 = &(ad->region[0]); /* this is preview buffer. */ + regp2 = &(ad->region[ad->bufnum1]);/* this is video buffer. */ + outp1 = &(vfe31_ctrl->outpath.out0); /* preview */ + outp2 = &(vfe31_ctrl->outpath.out2); /* video */ + + + for (i = 0; i < 2; i++) { + p1 = ao + 6 + i; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.y_off); + + p1 = ao + 30 + i; /* wm4 for cbcr */ + *p1 = (regp1->paddr + regp1->info.cbcr_off); + regp1++; + } + + for (i = 0; i < 2; i++) { + p2 = ao + 12 + i; /* wm1 for y */ + *p2 = (regp2->paddr + regp2->info.y_off); + + p2 = ao + 36 + i; /* wm5 for cbcr */ + *p2 = (regp2->paddr + regp2->info.cbcr_off); + regp2++; + } + for (i = 2; i < ad->bufnum1; i++) { + ret = vfe31_add_free_buf(outp1, regp1); + if (ret < 0) + return ret; + regp1++; + } + + for (i = 2; i < ad->bufnum2; i++) { + ret = vfe31_add_free_buf(outp2, regp2); + if (ret < 0) + return ret; + regp2++; + } + } + break; + case CAMIF_TO_AXI_VIA_OUTPUT_2: { /* use wm0 only */ + if (ad->bufnum2 < 1) + return -EINVAL; + CDBG("config axi for raw snapshot.\n"); + vfe31_ctrl->outpath.out1.ch0 = 0; /* raw */ + regp1 = &(ad->region[ad->bufnum1]); + vfe31_ctrl->outpath.output_mode |= VFE31_OUTPUT_MODE_S; + p1 = ao + 6; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.y_off); + if (p_sync->stereocam_enabled) + p_sync->stereo_state = STEREO_RAW_SNAP_IDLE; + } + break; + default: + break; + } + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[V31_AXI_OUT_CFG].offset, + ao, vfe31_cmd[V31_AXI_OUT_CFG].length - V31_AXI_CH_INF_LEN); + + return 0; +} + +static void vfe31_reset_internal_variables(void) +{ + unsigned long flags; + vfe31_ctrl->vfeImaskCompositePacked = 0; + /* state control variables */ + vfe31_ctrl->start_ack_pending = FALSE; + atomic_set(&irq_cnt, 0); + + spin_lock_irqsave(&vfe31_ctrl->xbar_lock, flags); + vfe31_ctrl->xbar_update_pending = 0; + spin_unlock_irqrestore(&vfe31_ctrl->xbar_lock, flags); + + atomic_set(&vfe31_ctrl->stop_ack_pending, 0); + atomic_set(&vfe31_ctrl->vstate, 0); + + vfe31_ctrl->aec_ack_pending = FALSE; + vfe31_ctrl->af_ack_pending = FALSE; + vfe31_ctrl->awb_ack_pending = FALSE; + vfe31_ctrl->ihist_ack_pending = FALSE; + vfe31_ctrl->rs_ack_pending = FALSE; + vfe31_ctrl->cs_ack_pending = FALSE; + + vfe31_ctrl->reset_ack_pending = FALSE; + + spin_lock_irqsave(&vfe31_ctrl->update_ack_lock, flags); + vfe31_ctrl->update_ack_pending = FALSE; + spin_unlock_irqrestore(&vfe31_ctrl->update_ack_lock, flags); + + vfe31_ctrl->recording_state = VFE_REC_STATE_IDLE; + + /* 0 for continuous mode, 1 for snapshot mode */ + vfe31_ctrl->operation_mode = VFE_MODE_OF_OPERATION_CONTINUOUS; + vfe31_ctrl->outpath.output_mode = 0; + vfe31_ctrl->vfe_capture_count = 0; + + /* this is unsigned 32 bit integer. */ + vfe31_ctrl->vfeFrameId = 0; + + vfe31_ctrl->output1Pattern = 0xffffffff; + vfe31_ctrl->output1Period = 31; + vfe31_ctrl->output2Pattern = 0xffffffff; + vfe31_ctrl->output2Period = 31; + vfe31_ctrl->vfeFrameSkipCount = 0; + vfe31_ctrl->vfeFrameSkipPeriod = 31; + + /* Stats control variables. */ + memset(&(vfe31_ctrl->afStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe31_ctrl->awbStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe31_ctrl->aecStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe31_ctrl->ihistStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe31_ctrl->rsStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe31_ctrl->csStatsControl), 0, + sizeof(struct vfe_stats_control)); +} + +static void vfe31_reset(void) +{ + uint32_t vfe_version; + vfe31_reset_free_buf_queue_all(); + vfe31_reset_internal_variables(); + + vfe31_reset_hist_cfg(); + vfe_version = msm_io_r(vfe31_ctrl->vfebase); + CDBG("vfe_version = 0x%x\n", vfe_version); + /* disable all interrupts. vfeImaskLocal is also reset to 0 + * to begin with. */ + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_io_w(VFE_CLEAR_ALL_IRQS, vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_io_w(VFE_CLEAR_ALL_IRQS, vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_1); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, vfe31_ctrl->vfebase + VFE_IRQ_CMD); + + /* enable reset_ack interrupt. */ + msm_io_w(VFE_IMASK_RESET, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* Write to VFE_GLOBAL_RESET_CMD to reset the vfe hardware. Once reset + * is done, hardware interrupt will be generated. VFE ist processes + * the interrupt to complete the function call. Note that the reset + * function is synchronous. */ + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(VFE_RESET_UPON_RESET_CMD, + vfe31_ctrl->vfebase + VFE_GLOBAL_RESET); +} + +static int vfe31_operation_config(uint32_t *cmd) +{ + uint32_t *p = cmd; + + vfe31_ctrl->operation_mode = *p; + vpe_ctrl->pad_2k_bool = (vfe31_ctrl->operation_mode & 1) ? + FALSE : TRUE; + + vfe31_ctrl->stats_comp = *(++p); + vfe31_ctrl->hfr_mode = *(++p); + + msm_io_w(*(++p), vfe31_ctrl->vfebase + VFE_CFG_OFF); + msm_io_w(*(++p), vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_w(*(++p), vfe31_ctrl->vfebase + VFE_REALIGN_BUF); + msm_io_w(*(++p), vfe31_ctrl->vfebase + VFE_CHROMA_UP); + msm_io_w(*(++p), vfe31_ctrl->vfebase + VFE_STATS_CFG); + wmb(); + return 0; +} +static uint32_t vfe_stats_awb_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PONG_ADDR); + vfe31_ctrl->awbStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + + +static uint32_t vfe_stats_aec_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_AEC_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_AEC_WR_PONG_ADDR); + + vfe31_ctrl->aecStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static uint32_t vfe_stats_af_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_AF_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_AF_WR_PONG_ADDR); + + vfe31_ctrl->afStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static uint32_t vfe_stats_ihist_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_HIST_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_HIST_WR_PONG_ADDR); + + vfe31_ctrl->ihistStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static uint32_t vfe_stats_rs_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_RS_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_RS_WR_PONG_ADDR); + + vfe31_ctrl->rsStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} +static uint32_t vfe_stats_cs_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_CS_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe31_ctrl->vfebase + VFE_BUS_STATS_CS_WR_PONG_ADDR); + + vfe31_ctrl->csStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static void vfe31_start_common(void) +{ + uint32_t irq_mask = 0x00E00021; + vfe31_ctrl->start_ack_pending = TRUE; + CDBG("VFE opertaion mode = 0x%x, output mode = 0x%x\n", + vfe31_ctrl->operation_mode, vfe31_ctrl->outpath.output_mode); + /* Enable IRQ for comp stats, Image master, SOF & Reg Update*/ + if (vfe31_ctrl->stats_comp) + irq_mask |= 0x01000000; + else /* Enable IRQ for Image masters, AF stats, SOF & Reg Update */ + irq_mask |= 0x00004000; + + /* Enable EOF for video mode */ + if (VFE_MODE_OF_OPERATION_VIDEO == vfe31_ctrl->operation_mode) + irq_mask |= 0x4; + + msm_io_w(irq_mask, vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + + msm_io_w(VFE_IMASK_RESET, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + /* enable out of order option */ + msm_io_w(0x80000000, vfe31_ctrl->vfebase + VFE_AXI_CFG); + /* enable performance monitor */ + msm_io_w(1, vfe31_ctrl->vfebase + VFE_BUS_PM_CFG); + msm_io_w(1, vfe31_ctrl->vfebase + VFE_BUS_PM_CMD); + + + msm_io_dump(vfe31_ctrl->vfebase, 0x600); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + msm_io_w(1, vfe31_ctrl->vfebase + VFE_CAMIF_COMMAND); + wmb(); + + atomic_set(&vfe31_ctrl->vstate, 1); +} + +static int vfe31_start_recording(void) +{ + msm_camio_set_perf_lvl(S_VIDEO); + usleep(1000); + vfe31_ctrl->recording_state = VFE_REC_STATE_START_REQUESTED; + msm_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + return 0; +} + +static int vfe31_stop_recording(void) +{ + vfe31_ctrl->recording_state = VFE_REC_STATE_STOP_REQUESTED; + msm_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + msm_camio_set_perf_lvl(S_PREVIEW); + return 0; +} + +static void vfe31_liveshot(void) +{ + struct msm_sync* p_sync = (struct msm_sync *)vfe_syncdata; + if (p_sync) + p_sync->liveshot_enabled = true; +} + +static void vfe31_stereocam(uint32_t enable) +{ + struct msm_sync* p_sync = (struct msm_sync *)vfe_syncdata; + if (p_sync) { + CDBG("%s: Enable StereoCam %d!!!\n", __func__, enable); + p_sync->stereocam_enabled = enable; + } +} + +static int vfe31_zsl(void) +{ + uint32_t irq_comp_mask = 0; + /* capture command is valid for both idle and active state. */ + irq_comp_mask = + msm_io_r(vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + CDBG("%s:op mode %d O/P Mode %d\n", __func__, + vfe31_ctrl->operation_mode, vfe31_ctrl->outpath.output_mode); + if ((vfe31_ctrl->operation_mode == VFE_MODE_OF_OPERATION_ZSL)) { + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_P) { + irq_comp_mask |= + ((0x1 << (vfe31_ctrl->outpath.out0.ch0)) | + (0x1 << (vfe31_ctrl->outpath.out0.ch1))); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_T) { + irq_comp_mask |= + ((0x1 << (vfe31_ctrl->outpath.out1.ch0 + 8)) | + (0x1 << (vfe31_ctrl->outpath.out1.ch1 + 8))); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_S) { + irq_comp_mask |= + ((0x1 << (vfe31_ctrl->outpath.out2.ch0 + 8)) | + (0x1 << (vfe31_ctrl->outpath.out2.ch1 + 8))); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_P) { + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_T) { + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch1]); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_S) { + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch0]); + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch1]); + } + } + msm_io_w(irq_comp_mask, vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + vfe31_start_common(); + msm_camio_set_perf_lvl(S_ZSL); + usleep(1000); + /* for debug */ + msm_io_w(1, vfe31_ctrl->vfebase + 0x18C); + msm_io_w(1, vfe31_ctrl->vfebase + 0x188); + return 0; +} + +static int vfe31_capture(uint32_t num_frames_capture) +{ + uint32_t irq_comp_mask = 0; + struct msm_sync* p_sync = (struct msm_sync *)vfe_syncdata; + + /* capture command is valid for both idle and active state. */ + vfe31_ctrl->vfe_capture_count = num_frames_capture; + if (p_sync) { + p_sync->snap_count = num_frames_capture; + p_sync->thumb_count = num_frames_capture; + } + + irq_comp_mask = + msm_io_r(vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if ((vfe31_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_SNAPSHOT) || + (vfe31_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_ZSL)){ + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_PT) { + irq_comp_mask |= + ((0x1 << (vfe31_ctrl->outpath.out0.ch0 + 8)) | + (0x1 << (vfe31_ctrl->outpath.out0.ch1 + 8))); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_S) { + irq_comp_mask |= + ((0x1 << (vfe31_ctrl->outpath.out1.ch0 + 8)) | + (0x1 << (vfe31_ctrl->outpath.out1.ch1 + 8))); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_PT) { + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + } + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_S) { + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch1]); + } + } else { /* this is raw snapshot mode. */ + CDBG("config the comp imask for raw snapshot mode.\n"); + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_S) { + irq_comp_mask |= + (0x1 << (vfe31_ctrl->outpath.out1.ch0 + 8)); + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out1.ch0]); + } + } + msm_io_w(irq_comp_mask, vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + if (p_sync->stereocam_enabled) + msm_camio_set_perf_lvl(S_STEREO_CAPTURE); + else + msm_camio_set_perf_lvl(S_CAPTURE); + + usleep(1000); + vfe31_start_common(); + return 0; +} + +static int vfe31_start(void) +{ + uint32_t irq_comp_mask = 0; + struct msm_sync* p_sync = (struct msm_sync *)vfe_syncdata; + /* start command now is only good for continuous mode. */ + if ((vfe31_ctrl->operation_mode != VFE_MODE_OF_OPERATION_CONTINUOUS) && + (vfe31_ctrl->operation_mode != VFE_MODE_OF_OPERATION_VIDEO)) + return 0; + irq_comp_mask = + msm_io_r(vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_PT) { + irq_comp_mask |= (0x1 << vfe31_ctrl->outpath.out0.ch0 | + 0x1 << vfe31_ctrl->outpath.out0.ch1); + } + + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_V) { + irq_comp_mask |= (0x1 << (vfe31_ctrl->outpath.out2.ch0 + 16)| + 0x1 << (vfe31_ctrl->outpath.out2.ch1 + 16)); + } + + msm_io_w(irq_comp_mask, vfe31_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_PT) { + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch0]); + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out0.ch1]); + } + if (p_sync->stereocam_enabled) + msm_camio_set_perf_lvl(S_STEREO_VIDEO); + else + msm_camio_set_perf_lvl(S_PREVIEW); + + usleep(1000); + vfe31_start_common(); + return 0; +} + +static void vfe31_update(void) +{ + unsigned long flags; + CDBG("vfe31_update\n"); + + if (vfe31_ctrl->update_gamma) { + if (!msm_io_r(vfe31_ctrl->vfebase + V31_GAMMA_CFG_OFF)) + msm_io_w(7, vfe31_ctrl->vfebase+V31_GAMMA_CFG_OFF); + else + msm_io_w(0, vfe31_ctrl->vfebase+V31_GAMMA_CFG_OFF); + vfe31_ctrl->update_gamma = false; + } + if (vfe31_ctrl->update_luma) { + if (!msm_io_r(vfe31_ctrl->vfebase + V31_LUMA_CFG_OFF)) + msm_io_w(1, vfe31_ctrl->vfebase + V31_LUMA_CFG_OFF); + else + msm_io_w(0, vfe31_ctrl->vfebase + V31_LUMA_CFG_OFF); + vfe31_ctrl->update_luma = false; + } + spin_lock_irqsave(&vfe31_ctrl->update_ack_lock, flags); + vfe31_ctrl->update_ack_pending = TRUE; + spin_unlock_irqrestore(&vfe31_ctrl->update_ack_lock, flags); + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + return; +} + +static void vfe31_sync_timer_stop(void) +{ + uint32_t value = 0; + vfe31_ctrl->sync_timer_state = 0; + if (vfe31_ctrl->sync_timer_number == 0) + value = 0x10000; + else if (vfe31_ctrl->sync_timer_number == 1) + value = 0x20000; + else if (vfe31_ctrl->sync_timer_number == 2) + value = 0x40000; + + /* Timer Stop */ + msm_io_w_mb(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF); +} + +static void vfe31_sync_timer_start(const uint32_t *tbl) +{ + /* set bit 8 for auto increment. */ + uint32_t value = 1; + uint32_t val; + + vfe31_ctrl->sync_timer_state = *tbl++; + vfe31_ctrl->sync_timer_repeat_count = *tbl++; + vfe31_ctrl->sync_timer_number = *tbl++; + CDBG("%s timer_state %d, repeat_cnt %d timer number %d\n", + __func__, vfe31_ctrl->sync_timer_state, + vfe31_ctrl->sync_timer_repeat_count, + vfe31_ctrl->sync_timer_number); + + if (vfe31_ctrl->sync_timer_state) { /* Start Timer */ + value = value << vfe31_ctrl->sync_timer_number; + } else { /* Stop Timer */ + CDBG("Failed to Start timer\n"); + return; + } + + /* Timer Start */ + msm_io_w(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF); + /* Sync Timer Line Start */ + value = *tbl++; + msm_io_w(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF + + 4 + ((vfe31_ctrl->sync_timer_number) * 12)); + /* Sync Timer Pixel Start */ + value = *tbl++; + msm_io_w(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF + + 8 + ((vfe31_ctrl->sync_timer_number) * 12)); + /* Sync Timer Pixel Duration */ + value = *tbl++; + val = camio_clk.vfe_clk_rate / 10000; + val = 10000000 / val; + val = value * 10000 / val; + CDBG("%s: Pixel Clk Cycles!!! %d \n", __func__, val); + msm_io_w(val, vfe31_ctrl->vfebase + V31_SYNC_TIMER_OFF + + 12 + ((vfe31_ctrl->sync_timer_number) * 12)); + /* Timer0 Active High/LOW */ + value = *tbl++; + msm_io_w(value, vfe31_ctrl->vfebase + V31_SYNC_TIMER_POLARITY_OFF); + /* Selects sync timer 0 output to drive onto timer1 port */ + value = 0; + msm_io_w(value, vfe31_ctrl->vfebase + V31_TIMER_SELECT_OFF); + wmb(); +} + +static void vfe31_program_dmi_cfg(enum VFE31_DMI_RAM_SEL bankSel) +{ + /* set bit 8 for auto increment. */ + uint32_t value = VFE_DMI_CFG_DEFAULT; + value += (uint32_t)bankSel; + + msm_io_w_mb(value, vfe31_ctrl->vfebase + VFE_DMI_CFG); + /* by default, always starts with offset 0.*/ + msm_io_w(0, vfe31_ctrl->vfebase + VFE_DMI_ADDR); + wmb(); +} +static void vfe31_write_gamma_cfg(enum VFE31_DMI_RAM_SEL channel_sel, + const uint32_t *tbl) +{ + int i; + uint32_t value, value1, value2; + vfe31_program_dmi_cfg(channel_sel); + /* for loop for extracting init table. */ + for (i = 0 ; i < (VFE31_GAMMA_NUM_ENTRIES/2) ; i++) { + value = *tbl++; + value1 = value & 0x0000FFFF; + value2 = (value & 0xFFFF0000)>>16; + msm_io_w((value1), vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_io_w((value2), vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + } + vfe31_program_dmi_cfg(NO_MEM_SELECTED); +} + +static void vfe31_reset_hist_cfg() +{ + uint32_t i; + uint32_t value = 0; + + vfe31_program_dmi_cfg(STATS_HIST_RAM); + for (i = 0 ; i < VFE31_HIST_TABLE_LENGTH ; i++) + msm_io_w(value, vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + vfe31_program_dmi_cfg(NO_MEM_SELECTED); +} + +static void vfe31_write_la_cfg(enum VFE31_DMI_RAM_SEL channel_sel, + const uint32_t *tbl) +{ + uint32_t i; + uint32_t value, value1, value2; + + vfe31_program_dmi_cfg(channel_sel); + /* for loop for extracting init table. */ + for (i = 0 ; i < (VFE31_LA_TABLE_LENGTH/2) ; i++) { + value = *tbl++; + value1 = value & 0x0000FFFF; + value2 = (value & 0xFFFF0000)>>16; + msm_io_w((value1), vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_io_w((value2), vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + } + vfe31_program_dmi_cfg(NO_MEM_SELECTED); +} + +static int vfe31_proc_general(struct msm_vfe31_cmd *cmd) +{ + int i , rc = 0; + uint32_t old_val = 0 , new_val = 0; + uint32_t *cmdp = NULL; + uint32_t *cmdp_local = NULL; + uint32_t snapshot_cnt = 0; + uint32_t stereo_cam_enable = 0; + struct msm_sync* p_sync = (struct msm_sync *)vfe_syncdata; + + CDBG("vfe31_proc_general: cmdID = %s, length = %d\n", + vfe31_general_cmd[cmd->id], cmd->length); + switch (cmd->id) { + case V31_RESET: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + vfe31_reset(); + break; + case V31_START: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + rc = vfe31_start(); + break; + case V31_UPDATE: + vfe31_update(); + break; + case V31_ZSL: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + vfe31_zsl(); + break; + case V31_CAPTURE: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + if (copy_from_user(&snapshot_cnt, (void __user *)(cmd->value), + sizeof(uint32_t))) { + rc = -EFAULT; + goto proc_general_done; + } + rc = vfe31_capture(snapshot_cnt); + break; + case V31_START_RECORDING: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + rc = vfe31_start_recording(); + if (p_sync->stereocam_enabled) + p_sync->stereo_state = STEREO_VIDEO_ACTIVE; + break; + case V31_STOP_RECORDING: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + rc = vfe31_stop_recording(); + if (p_sync->stereocam_enabled) + p_sync->stereo_state = STEREO_VIDEO_IDLE; + break; + case V31_OPERATION_CFG: { + if (cmd->length != V31_OPERATION_CFG_LEN) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kmalloc(V31_OPERATION_CFG_LEN, GFP_ATOMIC); + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + V31_OPERATION_CFG_LEN)) { + rc = -EFAULT; + goto proc_general_done; + } + rc = vfe31_operation_config(cmdp); + } + break; + + case V31_STATS_AE_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AE_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + } + break; + case V31_STATS_AF_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AF_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + } + break; + case V31_STATS_AWB_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AWB_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + } + break; + + case V31_STATS_IHIST_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= IHIST_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + } + break; + + case V31_XBAR_CFG: { + unsigned long flags = 0; + spin_lock_irqsave(&vfe31_ctrl->xbar_lock, flags); + if ((cmd->length != V31_XBAR_CFG_LEN) + || vfe31_ctrl->xbar_update_pending) { + rc = -EINVAL; + spin_unlock_irqrestore(&vfe31_ctrl->xbar_lock, flags); + goto proc_general_done; + } + spin_unlock_irqrestore(&vfe31_ctrl->xbar_lock, flags); + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + spin_lock_irqsave(&vfe31_ctrl->xbar_lock, flags); + vfe31_ctrl->xbar_cfg[0] = *cmdp; + vfe31_ctrl->xbar_cfg[1] = *(cmdp+1); + vfe31_ctrl->xbar_update_pending = 1; + spin_unlock_irqrestore(&vfe31_ctrl->xbar_lock, flags); + CDBG("%s: xbar0 0x%x xbar1 0x%x", __func__, + vfe31_ctrl->xbar_cfg[0], + vfe31_ctrl->xbar_cfg[1]); + } + break; + + case V31_STATS_RS_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + /* + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= RS_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + */ + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + } + break; + + case V31_STATS_CS_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + /* + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= CS_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + */ + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + } + break; + + case V31_MCE_UPDATE: + case V31_MCE_CFG:{ + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + /* Incrementing with 4 so as to point to the 2nd Register as + the 2nd register has the mce_enable bit */ + old_val = msm_io_r(vfe31_ctrl->vfebase + + V31_CHROMA_SUP_OFF + 4); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + new_val = *cmdp_local; + old_val &= MCE_EN_MASK; + new_val = new_val | old_val; + msm_io_memcpy(vfe31_ctrl->vfebase + V31_CHROMA_SUP_OFF + 4, + &new_val, 4); + cmdp_local += 1; + + old_val = msm_io_r(vfe31_ctrl->vfebase + + V31_CHROMA_SUP_OFF + 8); + new_val = *cmdp_local; + old_val &= MCE_Q_K_MASK; + new_val = new_val | old_val; + msm_io_memcpy(vfe31_ctrl->vfebase + V31_CHROMA_SUP_OFF + 8, + &new_val, 4); + cmdp_local += 1; + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp_local, (vfe31_cmd[cmd->id].length)); + } + break; + case V31_DEMOSAIC_2_UPDATE: /* 38 BPC update */ + case V31_DEMOSAIC_2_CFG: { /* 14 BPC config */ + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + new_val = *cmdp_local; + + old_val = msm_io_r(vfe31_ctrl->vfebase + V31_DEMOSAIC_0_OFF); + old_val &= BPC_MASK; + + new_val = new_val | old_val; + *cmdp_local = new_val; + msm_io_memcpy(vfe31_ctrl->vfebase + V31_DEMOSAIC_0_OFF, + cmdp_local, 4); + cmdp_local += 1; + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp_local, (vfe31_cmd[cmd->id].length)); + } + break; + case V31_DEMOSAIC_1_UPDATE:/* 37 ABF update */ + case V31_DEMOSAIC_1_CFG: { /* 13 ABF config */ + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + new_val = *cmdp_local; + + old_val = msm_io_r(vfe31_ctrl->vfebase + V31_DEMOSAIC_0_OFF); + old_val &= ABF_MASK; + new_val = new_val | old_val; + *cmdp_local = new_val; + + msm_io_memcpy(vfe31_ctrl->vfebase + V31_DEMOSAIC_0_OFF, + cmdp_local, 4); + + cmdp_local += 1; + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp_local, (vfe31_cmd[cmd->id].length)); + } + break; + case V31_ROLL_OFF_CFG: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value) , cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp_local, 16); + cmdp_local += 4; + vfe31_program_dmi_cfg(ROLLOFF_RAM); + /* for loop for extrcting init table. */ + for (i = 0 ; i < (VFE31_ROLL_OFF_INIT_TABLE_SIZE * 2) ; i++) { + msm_io_w(*cmdp_local , + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + cmdp_local++; + } + CDBG("done writing init table \n"); + /* by default, always starts with offset 0. */ + msm_io_w(LENS_ROLL_OFF_DELTA_TABLE_OFFSET, + vfe31_ctrl->vfebase + VFE_DMI_ADDR); + /* for loop for extracting delta table. */ + for (i = 0 ; i < (VFE31_ROLL_OFF_DELTA_TABLE_SIZE * 2) ; i++) { + msm_io_w(*cmdp_local, + vfe31_ctrl->vfebase + VFE_DMI_DATA_LO); + cmdp_local++; + } + vfe31_program_dmi_cfg(NO_MEM_SELECTED); + } + break; + + case V31_LA_CFG:{ + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + + rc = -EFAULT; + goto proc_general_done; + } + /* Select Bank 0*/ + *cmdp = 0; + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + cmdp += 1; + vfe31_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK0 , cmdp); + cmdp -= 1; + } + break; + + case V31_LA_UPDATE: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe31_ctrl->vfebase + V31_LUMA_CFG_OFF); + cmdp += 1; + if (old_val != 0x0) + vfe31_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK0 , cmdp); + else + vfe31_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK1 , cmdp); + vfe31_ctrl->update_luma = true; + cmdp -= 1; + } + break; + + case V31_SK_ENHAN_CFG: + case V31_SK_ENHAN_UPDATE:{ + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + msm_io_memcpy(vfe31_ctrl->vfebase + V31_SCE_OFF, + cmdp, V31_SCE_LEN); + } + break; + + case V31_LIVESHOT: + vfe31_liveshot(); + break; + + case V31_STEREOCAM: + if (copy_from_user(&stereo_cam_enable, + (void __user *)(cmd->value), sizeof(uint32_t))) { + rc = -EFAULT; + goto proc_general_done; + } + vfe31_stereocam(stereo_cam_enable); + break; + + case V31_RGB_G_CFG: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + /* Select Bank 0*/ + *cmdp = 0; + msm_io_memcpy(vfe31_ctrl->vfebase + V31_RGB_G_OFF, + cmdp, 4); + cmdp += 1; + vfe31_write_gamma_cfg(RGBLUT_CHX_BANK0, cmdp); + cmdp -= 1; + } + break; + + case V31_RGB_G_UPDATE: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe31_ctrl->vfebase + V31_GAMMA_CFG_OFF); + cmdp += 1; + + if (!old_val) { + vfe31_write_gamma_cfg(RGBLUT_CHX_BANK1, cmdp); + } else { + vfe31_write_gamma_cfg(RGBLUT_CHX_BANK0, cmdp); + } + vfe31_ctrl->update_gamma = true; + cmdp -= 1; + } + break; + + case V31_STATS_AWB_STOP: { + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AWB_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case V31_STATS_AE_STOP: { + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AE_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case V31_STATS_AF_STOP: { + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AF_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case V31_STATS_IHIST_STOP: { + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~IHIST_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case V31_STATS_RS_STOP: { + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~RS_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case V31_STATS_CS_STOP: { + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~CS_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case V31_STOP: + pr_info("vfe31_proc_general: cmdID = %s\n", + vfe31_general_cmd[cmd->id]); + vfe31_stop(); + break; + + case V31_SYNC_TIMER_SETTING: + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + vfe31_sync_timer_start(cmdp); + break; + + case V31_EZTUNE_CFG: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + *cmdp &= ~STATS_ENABLE_MASK; + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= STATS_ENABLE_MASK; + *cmdp |= old_val; + + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + } + break; + + default: { + if (cmd->length != vfe31_cmd[cmd->id].length) + return -EINVAL; + + cmdp = kmalloc(vfe31_cmd[cmd->id].length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + + CHECKED_COPY_FROM_USER(cmdp); + msm_io_memcpy(vfe31_ctrl->vfebase + vfe31_cmd[cmd->id].offset, + cmdp, (vfe31_cmd[cmd->id].length)); + } + break; + + } + +proc_general_done: + kfree(cmdp); + + return rc; +} + +static void vfe31_stats_af_ack(struct vfe_cmd_stats_ack *pAck) +{ + vfe31_ctrl->afStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->af_ack_pending = FALSE; +} + +static void vfe31_stats_awb_ack(struct vfe_cmd_stats_ack *pAck) +{ + vfe31_ctrl->awbStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->awb_ack_pending = FALSE; +} + +static void vfe31_stats_aec_ack(struct vfe_cmd_stats_ack *pAck) +{ + vfe31_ctrl->aecStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->aec_ack_pending = FALSE; +} + +static void vfe31_stats_ihist_ack(struct vfe_cmd_stats_ack *pAck) +{ + vfe31_ctrl->ihistStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->ihist_ack_pending = FALSE; +} + +static void vfe31_stats_rs_ack(struct vfe_cmd_stats_ack *pAck) +{ + vfe31_ctrl->rsStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->rs_ack_pending = FALSE; +} + +static void vfe31_stats_cs_ack(struct vfe_cmd_stats_ack *pAck) +{ + vfe31_ctrl->csStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe31_ctrl->cs_ack_pending = FALSE; +} + +static int vfe31_config(struct msm_vfe_cfg_cmd *cmd, void *data) +{ + struct msm_vfe31_cmd vfecmd; + + long rc = 0; + uint32_t i = 0; + struct vfe_cmd_stats_buf *scfg = NULL; + struct msm_pmem_region *regptr = NULL; + struct vfe_cmd_stats_ack *sack = NULL; + + if (cmd->cmd_type != CMD_FRAME_BUF_RELEASE && + cmd->cmd_type != CMD_SNAP_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AEC_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AWB_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_IHIST_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_RS_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_CS_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AF_BUF_RELEASE) { + if (copy_from_user(&vfecmd, + (void __user *)(cmd->value), + sizeof(vfecmd))) { + pr_err("%s %d: copy_from_user failed\n", __func__, + __LINE__); + return -EFAULT; + } + } else { + /* here eith stats release or frame release. */ + if (cmd->cmd_type != CMD_FRAME_BUF_RELEASE && + cmd->cmd_type != CMD_SNAP_BUF_RELEASE) { + /* then must be stats release. */ + if (!data) + return -EFAULT; + sack = kmalloc(sizeof(struct vfe_cmd_stats_ack), + GFP_ATOMIC); + if (!sack) + return -ENOMEM; + + sack->nextStatsBuf = *(uint32_t *)data; + } + } + + CDBG("%s: cmdType = %d\n", __func__, cmd->cmd_type); + + if ((cmd->cmd_type == CMD_STATS_AF_ENABLE) || + (cmd->cmd_type == CMD_STATS_AWB_ENABLE) || + (cmd->cmd_type == CMD_STATS_IHIST_ENABLE) || + (cmd->cmd_type == CMD_STATS_RS_ENABLE) || + (cmd->cmd_type == CMD_STATS_CS_ENABLE) || + (cmd->cmd_type == CMD_STATS_AEC_ENABLE)) { + struct axidata *axid; + axid = data; + if (!axid) { + rc = -EFAULT; + goto vfe31_config_done; + } + + scfg = + kmalloc(sizeof(struct vfe_cmd_stats_buf), + GFP_ATOMIC); + if (!scfg) { + rc = -ENOMEM; + goto vfe31_config_done; + } + regptr = axid->region; + if (axid->bufnum1 > 0) { + for (i = 0; i < axid->bufnum1; i++) { + scfg->statsBuf[i] = + (uint32_t)(regptr->paddr); + regptr++; + } + } + /* individual */ + switch (cmd->cmd_type) { + case CMD_STATS_AEC_ENABLE: + rc = vfe_stats_aec_buf_init(scfg); + break; + case CMD_STATS_AF_ENABLE: + rc = vfe_stats_af_buf_init(scfg); + break; + case CMD_STATS_AWB_ENABLE: + rc = vfe_stats_awb_buf_init(scfg); + break; + case CMD_STATS_IHIST_ENABLE: + rc = vfe_stats_ihist_buf_init(scfg); + break; + case CMD_STATS_RS_ENABLE: + rc = vfe_stats_rs_buf_init(scfg); + break; + case CMD_STATS_CS_ENABLE: + rc = vfe_stats_cs_buf_init(scfg); + break; + } + } + + switch (cmd->cmd_type) { + case CMD_GENERAL: + rc = vfe31_proc_general(&vfecmd); + break; + + case CMD_FRAME_BUF_RELEASE: { + struct msm_frame *b; + unsigned long p; + int ret; + struct vfe31_output_ch *outch = NULL; + if (!data) { + rc = -EFAULT; + break; + } + + b = (struct msm_frame *)(cmd->value); + p = *(unsigned long *)data; + + CDBG("CMD_FRAME_BUF_RELEASE b->path = %d\n", b->path); + + if (b->path & OUTPUT_TYPE_P) { + CDBG("CMD_FRAME_BUF_RELEASE got free buffer\n"); + outch = &vfe31_ctrl->outpath.out0; + } else if (b->path & OUTPUT_TYPE_S) { + outch = &vfe31_ctrl->outpath.out1; + } else if (b->path & OUTPUT_TYPE_V) { + outch = &vfe31_ctrl->outpath.out2; + } else { + rc = -EFAULT; + break; + } + + ret = vfe31_add_free_buf2(outch, p, b->y_off, b->cbcr_off); + if (ret < 0) + return ret; + break; + } + + case CMD_SNAP_BUF_RELEASE: { + struct msm_frame *b; + unsigned long p; + int ret; + struct vfe31_output_ch *outch = NULL; + if (!data) + return -EFAULT; + + b = (struct msm_frame *)(cmd->value); + p = *(unsigned long *)data; + + CDBG("CMD_PIC_BUF_RELEASE b->path = %d\n", b->path); + + if (b->path & OUTPUT_TYPE_T) { + CDBG("CMD_FRAME_BUF_RELEASE got free buffer\n"); + outch = &vfe31_ctrl->outpath.out1; + } else if (b->path & OUTPUT_TYPE_S) { + outch = &vfe31_ctrl->outpath.out2; + } else + return -EFAULT; + + ret = vfe31_add_free_buf2(outch, p, b->y_off, b->cbcr_off); + if (ret < 0) + return ret; + break; + } + + case CMD_STATS_AEC_BUF_RELEASE: + vfe31_stats_aec_ack(sack); + break; + + case CMD_STATS_AF_BUF_RELEASE: + vfe31_stats_af_ack(sack); + break; + + case CMD_STATS_AWB_BUF_RELEASE: + vfe31_stats_awb_ack(sack); + break; + + case CMD_STATS_IHIST_BUF_RELEASE: + vfe31_stats_ihist_ack(sack); + break; + + case CMD_STATS_RS_BUF_RELEASE: + vfe31_stats_rs_ack(sack); + break; + + case CMD_STATS_CS_BUF_RELEASE: + vfe31_stats_cs_ack(sack); + break; + + case CMD_AXI_CFG_PREVIEW: { + struct axidata *axid; + uint32_t *axio = NULL; + axid = data; + if (!axid) { + rc = -EFAULT; + break; + } + axio = + kmalloc(vfe31_cmd[V31_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[V31_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(OUTPUT_2, axid, axio); + kfree(axio); + break; + } + + case CMD_RAW_PICT_AXI_CFG: { + struct axidata *axid; + uint32_t *axio = NULL; + axid = data; + if (!axid) { + rc = -EFAULT; + break; + } + axio = + kmalloc(vfe31_cmd[V31_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[V31_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(CAMIF_TO_AXI_VIA_OUTPUT_2, axid, axio); + kfree(axio); + break; + } + + case CMD_AXI_CFG_SNAP: { + struct axidata *axid; + uint32_t *axio = NULL; + CDBG("%s, CMD_AXI_CFG_SNAP\n", __func__); + axid = data; + if (!axid) + return -EFAULT; + axio = + kmalloc(vfe31_cmd[V31_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[V31_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(OUTPUT_1_AND_2, axid, axio); + kfree(axio); + break; + } + + case CMD_AXI_CFG_ZSL: { + struct axidata *axid; + uint32_t *axio = NULL; + CDBG("%s, CMD_AXI_CFG_ZSL\n", __func__); + axid = data; + if (!axid) + return -EFAULT; + axio = + kmalloc(vfe31_cmd[V31_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[V31_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(OUTPUT_1_2_AND_3, axid, axio); + kfree(axio); + } + break; + + case CMD_AXI_CFG_VIDEO: { + struct axidata *axid; + uint32_t *axio = NULL; + axid = data; + if (!axid) { + rc = -EFAULT; + break; + } + + axio = + kmalloc(vfe31_cmd[V31_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe31_cmd[V31_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe31_config_axi(OUTPUT_1_AND_3, axid, axio); + kfree(axio); + break; + } + + default: + break; + } +vfe31_config_done: + kfree(scfg); + kfree(sack); + CDBG("%s done: rc = %d\n", __func__, (int) rc); + return rc; +} + +static void vfe31_send_msg_no_payload(enum VFE31_MESSAGE_ID id) +{ + struct vfe_message msg; + + CDBG("vfe31_send_msg_no_payload\n"); + msg._d = id; + vfe31_proc_ops(id, &msg, 0); +} + +static void vfe31_process_reg_update_irq(void) +{ + uint32_t temp, old_val; + unsigned long flags; + if (vfe31_ctrl->recording_state == VFE_REC_STATE_START_REQUESTED) { + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_V) { + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch0]); + msm_io_w(1, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch1]); + } + vfe31_ctrl->recording_state = VFE_REC_STATE_STARTED; + if (vpe_ctrl->dis_en) { + old_val = msm_io_r( + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= RS_CS_ENABLE_MASK; + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + } + msm_io_w_mb(1, vfe31_ctrl->vfebase + VFE_REG_UPDATE_CMD); + CDBG("start video triggered .\n"); + } else if (vfe31_ctrl->recording_state + == VFE_REC_STATE_STOP_REQUESTED) { + if (vfe31_ctrl->outpath.output_mode & VFE31_OUTPUT_MODE_V) { + msm_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch0]); + msm_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl->outpath.out2.ch1]); + } + + /*disable rs& cs when stop recording. */ + old_val = msm_io_r(vfe31_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= (~RS_CS_ENABLE_MASK); + msm_io_w(old_val, + vfe31_ctrl->vfebase + VFE_MODULE_CFG); + CDBG("stop video triggered\n"); + } + if (vfe31_ctrl->start_ack_pending == TRUE) { + vfe31_send_msg_no_payload(MSG_ID_START_ACK); + vfe31_ctrl->start_ack_pending = FALSE; + } else { + if (vfe31_ctrl->recording_state == + VFE_REC_STATE_STOP_REQUESTED) { + vfe31_ctrl->recording_state = VFE_REC_STATE_STOPPED; + msm_io_w_mb(1, vfe31_ctrl->vfebase + + VFE_REG_UPDATE_CMD); + } else if (vfe31_ctrl->recording_state == + VFE_REC_STATE_STOPPED) { + CDBG("sent stop video rec ACK"); + vfe31_send_msg_no_payload(MSG_ID_STOP_REC_ACK); + vfe31_ctrl->recording_state = VFE_REC_STATE_IDLE; + } + spin_lock_irqsave(&vfe31_ctrl->update_ack_lock, flags); + if (vfe31_ctrl->update_ack_pending == TRUE) { + vfe31_ctrl->update_ack_pending = FALSE; + spin_unlock_irqrestore( + &vfe31_ctrl->update_ack_lock, flags); + vfe31_send_msg_no_payload(MSG_ID_UPDATE_ACK); + } else { + spin_unlock_irqrestore( + &vfe31_ctrl->update_ack_lock, flags); + } + } + /* in snapshot mode */ + if (vfe31_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_SNAPSHOT) { + /* later we need to add check for live snapshot mode. */ + + if (vfe31_ctrl->vfe_capture_count) + vfe31_ctrl->vfe_capture_count--; + /* if last frame to be captured: */ + if (vfe31_ctrl->vfe_capture_count == 0) { + /* stop the bus output: write master enable = 0*/ + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_PT) { + msm_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[ + vfe31_ctrl->outpath.out0.ch0]); + msm_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl-> + outpath.out0.ch1]); + } + if (vfe31_ctrl->outpath.output_mode & + VFE31_OUTPUT_MODE_S) { + msm_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl-> + outpath.out1.ch0]); + msm_io_w(0, vfe31_ctrl->vfebase + + vfe31_AXI_WM_CFG[vfe31_ctrl-> + outpath.out1.ch1]); + } + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + vfe31_ctrl->vfebase + VFE_CAMIF_COMMAND); + + /* Ensure the read order while reading + to the command register using the barrier */ + temp = msm_io_r_mb(vfe31_ctrl->vfebase + + VFE_CAMIF_COMMAND); + } + /* then do reg_update. */ + msm_io_w_mb(1, vfe31_ctrl->vfebase + + VFE_REG_UPDATE_CMD); + } /* if snapshot mode. */ +} + +static void vfe31_set_default_reg_values(void) +{ + msm_io_w(0x800080, vfe31_ctrl->vfebase + VFE_DEMUX_GAIN_0); + msm_io_w(0x800080, vfe31_ctrl->vfebase + VFE_DEMUX_GAIN_1); + msm_io_w(0xFFFFF, vfe31_ctrl->vfebase + VFE_CGC_OVERRIDE); + + /* default frame drop period and pattern */ + msm_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_CFG); + msm_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_CFG); + msm_io_w(0xFFFFFFFF, vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_PATTERN); + msm_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_PATTERN); + msm_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y); + msm_io_w(0x1f, vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR); + msm_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y_PATTERN); + msm_io_w(0xFFFFFFFF, + vfe31_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR_PATTERN); + msm_io_w(0, vfe31_ctrl->vfebase + VFE_CLAMP_MIN); + msm_io_w(0xFFFFFF, vfe31_ctrl->vfebase + VFE_CLAMP_MAX); + + /* stats UB config */ + msm_io_w(0x3980007, vfe31_ctrl->vfebase + VFE_BUS_STATS_AEC_UB_CFG); + msm_io_w(0x3A00007, vfe31_ctrl->vfebase + VFE_BUS_STATS_AF_UB_CFG); + msm_io_w(0x3A8000F, vfe31_ctrl->vfebase + VFE_BUS_STATS_AWB_UB_CFG); + msm_io_w(0x3B80007, vfe31_ctrl->vfebase + VFE_BUS_STATS_RS_UB_CFG); + msm_io_w(0x3C0001F, vfe31_ctrl->vfebase + VFE_BUS_STATS_CS_UB_CFG); + msm_io_w(0x3E0001F, vfe31_ctrl->vfebase + VFE_BUS_STATS_HIST_UB_CFG); +} + +static void vfe31_process_reset_irq(void) +{ + atomic_set(&vfe31_ctrl->vstate, 0); + vfe31_ctrl->while_stopping_mask = VFE_IMASK_WHILE_STOPPING_1; + if (atomic_read(&vfe31_ctrl->stop_ack_pending)) { + /* this is from the stop command. */ + atomic_set(&vfe31_ctrl->stop_ack_pending, 0); + vfe31_send_msg_no_payload(MSG_ID_STOP_ACK); + } else { + /* this is from reset command. */ + vfe31_set_default_reg_values(); + + /* reload all write masters. (frame & line)*/ + msm_io_w_mb(0x7FFF, vfe31_ctrl->vfebase + VFE_BUS_CMD); + vfe31_send_msg_no_payload(MSG_ID_RESET_ACK); + } +} + + +static void vfe31_process_axi_halt_irq(void) +{ + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(AXI_HALT_CLEAR, + vfe31_ctrl->vfebase + VFE_AXI_CMD); + vfe31_ctrl->while_stopping_mask = VFE_IMASK_RESET; + + /* disable all interrupts. */ + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_io_w(VFE_CLEAR_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_io_w(VFE_CLEAR_ALL_IRQS, + vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_1); + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, + vfe31_ctrl->vfebase + VFE_IRQ_CMD); + + /* now enable only halt_irq & reset_irq */ + msm_io_w(0xf0000000, /* this is for async timer. */ + vfe31_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_io_w(VFE_IMASK_RESET, + vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* Ensure the write order while writing + to the command register using the barrier */ + CDBG("%s: about to reset vfe...\n", __func__); + msm_io_w_mb(VFE_RESET_UPON_STOP_CMD, + vfe31_ctrl->vfebase + VFE_GLOBAL_RESET); + +} + +static void vfe31_process_camif_sof_irq(void) +{ + uint32_t temp; + + /* in raw snapshot mode */ + if (vfe31_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_RAW_SNAPSHOT) { + if (vfe31_ctrl->start_ack_pending) { + vfe31_send_msg_no_payload(MSG_ID_START_ACK); + vfe31_ctrl->start_ack_pending = FALSE; + } + if (vfe31_ctrl->vfe_capture_count) + vfe31_ctrl->vfe_capture_count--; + /* if last frame to be captured: */ + if (vfe31_ctrl->vfe_capture_count == 0) { + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + vfe31_ctrl->vfebase + VFE_CAMIF_COMMAND); + temp = msm_io_r_mb(vfe31_ctrl->vfebase + + VFE_CAMIF_COMMAND); + } + } /* if raw snapshot mode. */ + + if ((vfe31_ctrl->hfr_mode != HFR_MODE_OFF) && + (vfe31_ctrl->operation_mode == VFE_MODE_OF_OPERATION_VIDEO) && + (vfe31_ctrl->vfeFrameId % vfe31_ctrl->hfr_mode != 0)) { + vfe31_ctrl->vfeFrameId++; + CDBG("Skip the SOF notification when HFR enabled\n"); + return; + } + vfe31_send_msg_no_payload(MSG_ID_SOF_ACK); + vfe31_ctrl->vfeFrameId++; + CDBG("camif_sof_irq, frameId = %d\n", vfe31_ctrl->vfeFrameId); + + if (vfe31_ctrl->sync_timer_state) { + if (vfe31_ctrl->sync_timer_repeat_count == 0) + vfe31_sync_timer_stop(); + else + vfe31_ctrl->sync_timer_repeat_count--; + } +} + +static void vfe31_process_error_irq(uint32_t errStatus) +{ + uint32_t camifStatus, read_val; + uint32_t *temp; + + if (errStatus & VFE31_IMASK_CAMIF_ERROR) { + pr_err("vfe31_irq: camif errors\n"); + temp = (uint32_t *)(vfe31_ctrl->vfebase + VFE_CAMIF_STATUS); + camifStatus = msm_io_r(temp); + pr_err("camifStatus = 0x%x\n", camifStatus); + vfe31_send_msg_no_payload(MSG_ID_CAMIF_ERROR); + } + + if (errStatus & VFE31_IMASK_STATS_CS_OVWR) + pr_err("vfe31_irq: stats cs overwrite\n"); + + if (errStatus & VFE31_IMASK_STATS_IHIST_OVWR) + pr_err("vfe31_irq: stats ihist overwrite\n"); + + if (errStatus & VFE31_IMASK_REALIGN_BUF_Y_OVFL) + pr_err("vfe31_irq: realign bug Y overflow\n"); + + if (errStatus & VFE31_IMASK_REALIGN_BUF_CB_OVFL) + pr_err("vfe31_irq: realign bug CB overflow\n"); + + if (errStatus & VFE31_IMASK_REALIGN_BUF_CR_OVFL) + pr_err("vfe31_irq: realign bug CR overflow\n"); + + if (errStatus & VFE31_IMASK_VIOLATION) + pr_err("vfe31_irq: violation interrupt\n"); + + if (errStatus & VFE31_IMASK_IMG_MAST_0_BUS_OVFL) + pr_err("vfe31_irq: image master 0 bus overflow\n"); + + if (errStatus & VFE31_IMASK_IMG_MAST_1_BUS_OVFL) + pr_err("vfe31_irq: image master 1 bus overflow\n"); + + if (errStatus & VFE31_IMASK_IMG_MAST_2_BUS_OVFL) + pr_err("vfe31_irq: image master 2 bus overflow\n"); + + if (errStatus & VFE31_IMASK_IMG_MAST_3_BUS_OVFL) + pr_err("vfe31_irq: image master 3 bus overflow\n"); + + if (errStatus & VFE31_IMASK_IMG_MAST_4_BUS_OVFL) + pr_err("vfe31_irq: image master 4 bus overflow\n"); + + if (errStatus & VFE31_IMASK_IMG_MAST_5_BUS_OVFL) + pr_err("vfe31_irq: image master 5 bus overflow\n"); + + if (errStatus & VFE31_IMASK_IMG_MAST_6_BUS_OVFL) + pr_err("vfe31_irq: image master 6 bus overflow\n"); + + if (errStatus & VFE31_IMASK_STATS_AE_BUS_OVFL) + pr_err("vfe31_irq: ae stats bus overflow\n"); + + if (errStatus & VFE31_IMASK_STATS_AF_BUS_OVFL) + pr_err("vfe31_irq: af stats bus overflow\n"); + + if (errStatus & VFE31_IMASK_STATS_AWB_BUS_OVFL) + pr_err("vfe31_irq: awb stats bus overflow\n"); + + if (errStatus & VFE31_IMASK_STATS_RS_BUS_OVFL) + pr_err("vfe31_irq: rs stats bus overflow\n"); + + if (errStatus & VFE31_IMASK_STATS_CS_BUS_OVFL) + pr_err("vfe31_irq: cs stats bus overflow\n"); + + if (errStatus & VFE31_IMASK_STATS_IHIST_BUS_OVFL) + pr_err("vfe31_irq: ihist stats bus overflow\n"); + + if (errStatus & VFE31_IMASK_STATS_SKIN_BUS_OVFL) + pr_err("vfe31_irq: skin stats bus overflow\n"); + + if (errStatus & VFE31_IMASK_AXI_ERROR) { + pr_err("vfe31_irq: axi error\n"); + /* read status too when overflow happens.*/ + read_val = msm_io_r(vfe31_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS); + pr_debug("VFE_BUS_PING_PONG_STATUS = 0x%x\n", read_val); + read_val = msm_io_r(vfe31_ctrl->vfebase + + VFE_BUS_OPERATION_STATUS); + pr_debug("VFE_BUS_OPERATION_STATUS = 0x%x\n", read_val); + read_val = msm_io_r(vfe31_ctrl->vfebase + + VFE_BUS_IMAGE_MASTER_0_WR_PM_STATS_0); + pr_debug("VFE_BUS_IMAGE_MASTER_0_WR_PM_STATS_0 = 0x%x\n", + read_val); + read_val = msm_io_r(vfe31_ctrl->vfebase + + VFE_BUS_IMAGE_MASTER_0_WR_PM_STATS_1); + pr_debug("VFE_BUS_IMAGE_MASTER_0_WR_PM_STATS_1 = 0x%x\n", + read_val); + read_val = msm_io_r(vfe31_ctrl->vfebase + + VFE_AXI_STATUS); + pr_debug("VFE_AXI_STATUS = 0x%x\n", read_val); + } +} + +#define VFE31_AXI_OFFSET 0x0050 +#define vfe31_get_ch_ping_addr(chn) \ + (msm_io_r(vfe31_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe31_get_ch_pong_addr(chn) \ + (msm_io_r(vfe31_ctrl->vfebase + 0x0050 + 0x18 * (chn) + 4)) +#define vfe31_get_ch_addr(ping_pong, chn) \ + (((ping_pong) & (1 << (chn))) == 0 ? \ + vfe31_get_ch_pong_addr(chn) : vfe31_get_ch_ping_addr(chn)) + +#define vfe31_put_ch_ping_addr(chn, addr) \ + (msm_io_w((addr), vfe31_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe31_put_ch_pong_addr(chn, addr) \ + (msm_io_w((addr), vfe31_ctrl->vfebase + 0x0050 + 0x18 * (chn) + 4)) +#define vfe31_put_ch_addr(ping_pong, chn, addr) \ + (((ping_pong) & (1 << (chn))) == 0 ? \ + vfe31_put_ch_pong_addr((chn), (addr)) : \ + vfe31_put_ch_ping_addr((chn), (addr))) + +static void vfe31_process_output_path_irq_0(uint32_t ping_pong) +{ + uint32_t pyaddr, pcbcraddr; +#ifdef CONFIG_MSM_CAMERA_V4L2 + uint32_t pyaddr_ping, pcbcraddr_ping, pyaddr_pong, pcbcraddr_pong; +#endif + struct vfe31_free_buf *free_buf = NULL; + /* we render frames in the following conditions: + 1. Continuous mode and the free buffer is avaialable. + */ + free_buf = vfe31_get_free_buf(&vfe31_ctrl->outpath.out0); + + if (free_buf) { + /* Y channel */ + pyaddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0); + /* Chroma channel */ + pcbcraddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch1); + + CDBG("output path 0, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + pyaddr, pcbcraddr); + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch1, + free_buf->paddr + free_buf->cbcr_off); + + kfree(free_buf); + /* if continuous mode, for display. (preview) */ + vfe_send_outmsg(MSG_ID_OUTPUT_P, pyaddr, pcbcraddr); + } else { + vfe31_ctrl->outpath.out0.frame_drop_cnt++; + pr_warning("path_irq_0 - no free buffer!\n"); +#ifdef CONFIG_MSM_CAMERA_V4L2 + pr_info("Swapping ping and pong\n"); + + /*get addresses*/ + /* Y channel */ + pyaddr_ping = vfe31_get_ch_ping_addr( + vfe31_ctrl->outpath.out0.ch0); + /* Chroma channel */ + pcbcraddr_ping = vfe31_get_ch_ping_addr( + vfe31_ctrl->outpath.out0.ch1); + /* Y channel */ + pyaddr_pong = vfe31_get_ch_pong_addr( + vfe31_ctrl->outpath.out0.ch0); + /* Chroma channel */ + pcbcraddr_pong = vfe31_get_ch_pong_addr( + vfe31_ctrl->outpath.out0.ch1); + + CDBG("ping = 0x%p, pong = 0x%p\n", (void *)pyaddr_ping, + (void *)pyaddr_pong); + CDBG("ping_cbcr = 0x%p, pong_cbcr = 0x%p\n", + (void *)pcbcraddr_ping, (void *)pcbcraddr_pong); + + /*put addresses*/ + /* SWAP y channel*/ + vfe31_put_ch_ping_addr(vfe31_ctrl->outpath.out0.ch0, + pyaddr_pong); + vfe31_put_ch_pong_addr(vfe31_ctrl->outpath.out0.ch0, + pyaddr_ping); + /* SWAP chroma channel*/ + vfe31_put_ch_ping_addr(vfe31_ctrl->outpath.out0.ch1, + pcbcraddr_pong); + vfe31_put_ch_pong_addr(vfe31_ctrl->outpath.out0.ch1, + pcbcraddr_ping); + CDBG("after swap: ping = 0x%p, pong = 0x%p\n", + (void *)pyaddr_pong, (void *)pyaddr_ping); +#endif + } +} + +static void vfe31_process_snapshot_frame(uint32_t ping_pong) +{ + uint32_t pyaddr, pcbcraddr; + struct vfe31_free_buf *free_buf = NULL; + /* Y channel- Main Image */ + pyaddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0); + /* Chroma channel - TN Image */ + pcbcraddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1); + + free_buf = vfe31_get_free_buf(&vfe31_ctrl->outpath.out1); + CDBG("%s: snapshot main, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + __func__, pyaddr, pcbcraddr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1, + free_buf->paddr + free_buf->cbcr_off); + kfree(free_buf); + } + vfe_send_outmsg(MSG_ID_OUTPUT_S, pyaddr, pcbcraddr); + + /* Y channel- TN Image */ + pyaddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0); + /* Chroma channel - TN Image */ + pcbcraddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch1); + + free_buf = vfe31_get_free_buf(&vfe31_ctrl->outpath.out0); + CDBG("%s: snapshot TN, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + __func__, pyaddr, pcbcraddr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out0.ch1, + free_buf->paddr + free_buf->cbcr_off); + kfree(free_buf); + } + + vfe_send_outmsg(MSG_ID_OUTPUT_T, pyaddr, pcbcraddr); + + /* in snapshot mode if done then send + snapshot done message */ + if (vfe31_ctrl->vfe_capture_count == 0) { + vfe31_send_msg_no_payload(MSG_ID_SNAPSHOT_DONE); + /* Ensure the write order while writing + to the cmd register using barrier */ + msm_io_w_mb(CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe31_ctrl->vfebase + + VFE_CAMIF_COMMAND); + } +} + +static void vfe31_process_raw_snapshot_frame(uint32_t ping_pong) +{ + uint32_t pyaddr, pcbcraddr; + struct vfe31_free_buf *free_buf = NULL; + struct msm_sync* p_sync = (struct msm_sync *)vfe_syncdata; + + if (p_sync->stereocam_enabled) + p_sync->stereo_state = STEREO_RAW_SNAP_STARTED; + + /* Y channel- Main Image */ + pyaddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0); + /* Chroma channel - Main Image */ + pcbcraddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1); + + free_buf = vfe31_get_free_buf(&vfe31_ctrl->outpath.out1); + CDBG("%s: snapshot raw, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + __func__, pyaddr, pcbcraddr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1, + free_buf->paddr + free_buf->cbcr_off); + kfree(free_buf); + } + vfe_send_outmsg(MSG_ID_OUTPUT_S, pyaddr, pcbcraddr); + + /* in snapshot mode if done then send + snapshot done message */ + if (vfe31_ctrl->vfe_capture_count == 0) { + vfe31_send_msg_no_payload(MSG_ID_SNAPSHOT_DONE); + /* Ensure the write order while writing + to the cmd register using barrier */ + msm_io_w_mb(CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe31_ctrl->vfebase + + VFE_CAMIF_COMMAND); + } +} +static void vfe31_process_zsl_frame(uint32_t ping_pong) +{ + uint32_t pyaddr, pcbcraddr; + struct vfe31_free_buf *free_buf = NULL; + /* Y channel- Main Image */ + pyaddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch0); + /* Chroma channel - Main Image */ + pcbcraddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch1); + + free_buf = vfe31_get_free_buf(&vfe31_ctrl->outpath.out2); + CDBG("%s: snapshot main, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + __func__, pyaddr, pcbcraddr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch1, + free_buf->paddr + free_buf->cbcr_off); + kfree(free_buf); + } + vfe_send_outmsg(MSG_ID_OUTPUT_S, pyaddr, pcbcraddr); + + /* Y channel- TN Image */ + pyaddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0); + /* Chroma channel - TN Image */ + pcbcraddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1); + + free_buf = vfe31_get_free_buf(&vfe31_ctrl->outpath.out1); + CDBG("%s: snapshot TN, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + __func__, pyaddr, pcbcraddr); + if (free_buf) { + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out1.ch1, + free_buf->paddr + free_buf->cbcr_off); + kfree(free_buf); + } + + vfe_send_outmsg(MSG_ID_OUTPUT_T, pyaddr, pcbcraddr); +} + +static void vfe31_process_output_path_irq_1(uint32_t ping_pong) +{ + +#ifdef CONFIG_MSM_CAMERA_V4L2 + uint32_t pyaddr_ping, pcbcraddr_ping, pyaddr_pong, pcbcraddr_pong; +#endif + CDBG("%s, operation_mode = %d, cap_cnt = %d\n", __func__, + vfe31_ctrl->operation_mode, vfe31_ctrl->vfe_capture_count); + + /* In Snapshot mode */ + if ((VFE_MODE_OF_OPERATION_SNAPSHOT == vfe31_ctrl->operation_mode) + && ((vfe31_ctrl->vfe_capture_count <= 1) + || (vfe31_free_buf_available(vfe31_ctrl->outpath.out0) && + vfe31_free_buf_available(vfe31_ctrl->outpath.out1)))) { + vfe31_process_snapshot_frame(ping_pong); + } else if ((VFE_MODE_OF_OPERATION_RAW_SNAPSHOT == + vfe31_ctrl->operation_mode) && + ((vfe31_ctrl->vfe_capture_count <= 1) || + vfe31_free_buf_available(vfe31_ctrl->outpath.out1))) { + vfe31_process_raw_snapshot_frame(ping_pong); + } else if ((VFE_MODE_OF_OPERATION_ZSL == vfe31_ctrl->operation_mode) + && (vfe31_free_buf_available(vfe31_ctrl->outpath.out1) + && vfe31_free_buf_available(vfe31_ctrl->outpath.out2))) { + vfe31_process_zsl_frame(ping_pong); + } else { + vfe31_ctrl->outpath.out1.frame_drop_cnt++; + pr_info("path_irq_1 - no free buffer!\n"); +#ifdef CONFIG_MSM_CAMERA_V4L2 + pr_info("Swapping ping and pong\n"); + + /*get addresses*/ + /* Y channel */ + pyaddr_ping = vfe31_get_ch_ping_addr( + vfe31_ctrl->outpath.out1.ch0); + /* Chroma channel */ + pcbcraddr_ping = vfe31_get_ch_ping_addr( + vfe31_ctrl->outpath.out1.ch1); + /* Y channel */ + pyaddr_pong = vfe31_get_ch_pong_addr( + vfe31_ctrl->outpath.out1.ch0); + /* Chroma channel */ + pcbcraddr_pong = vfe31_get_ch_pong_addr( + vfe31_ctrl->outpath.out1.ch1); + + CDBG("ping = 0x%p, pong = 0x%p\n", (void *)pyaddr_ping, + (void *)pyaddr_pong); + CDBG("ping_cbcr = 0x%p, pong_cbcr = 0x%p\n", + (void *)pcbcraddr_ping, (void *)pcbcraddr_pong); + + /*put addresses*/ + /* SWAP y channel*/ + vfe31_put_ch_ping_addr(vfe31_ctrl->outpath.out1.ch0, + pyaddr_pong); + vfe31_put_ch_pong_addr(vfe31_ctrl->outpath.out1.ch0, + pyaddr_ping); + /* SWAP chroma channel*/ + vfe31_put_ch_ping_addr(vfe31_ctrl->outpath.out1.ch1, + pcbcraddr_pong); + vfe31_put_ch_pong_addr(vfe31_ctrl->outpath.out1.ch1, + pcbcraddr_ping); + CDBG("after swap: ping = 0x%p, pong = 0x%p\n", + (void *)pyaddr_pong, (void *)pyaddr_ping); +#endif + } + +} + +static void vfe31_process_output_path_irq_2(uint32_t ping_pong) +{ + uint32_t pyaddr, pcbcraddr; + struct vfe31_free_buf *free_buf = NULL; + +#ifdef CONFIG_MSM_CAMERA_V4L2 + uint32_t pyaddr_ping, pcbcraddr_ping, pyaddr_pong, pcbcraddr_pong; +#endif + /* we render frames in the following conditions: + 1. Continuous mode and the free buffer is avaialable. + */ + CDBG("%s, operation_mode = %d, state %d\n", __func__, + vfe31_ctrl->operation_mode, + vfe31_ctrl->recording_state); + /* Ensure that both wm1 and wm5 ping and pong buffers are active*/ + if (!(((ping_pong & 0x22) == 0x22) || + ((ping_pong & 0x22) == 0x0))) { + pr_err(" Irq_2 - skip the frame pp_status is not proper" + "PP_status = 0x%x\n", ping_pong); + return; + } + if (vfe31_ctrl->recording_state == VFE_REC_STATE_STOPPED) { + vfe31_ctrl->outpath.out2.frame_drop_cnt++; + pr_warning("path_irq_2 - recording stopped\n"); + return; + } + + free_buf = vfe31_get_free_buf(&vfe31_ctrl->outpath.out2); + + if (free_buf) { + /* Y channel */ + pyaddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch0); + /* Chroma channel */ + pcbcraddr = vfe31_get_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch1); + + CDBG("video output, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + pyaddr, pcbcraddr); + + /* Y channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe31_put_ch_addr(ping_pong, + vfe31_ctrl->outpath.out2.ch1, + free_buf->paddr + free_buf->cbcr_off); + kfree(free_buf); + vfe_send_outmsg(MSG_ID_OUTPUT_V, pyaddr, pcbcraddr); + } else { + vfe31_ctrl->outpath.out2.frame_drop_cnt++; + pr_warning("path_irq_2 - no free buffer!\n"); + +#ifdef CONFIG_MSM_CAMERA_V4L2 + pr_info("Swapping ping and pong\n"); + + /*get addresses*/ + /* Y channel */ + pyaddr_ping = vfe31_get_ch_ping_addr( + vfe31_ctrl->outpath.out2.ch0); + /* Chroma channel */ + pcbcraddr_ping = vfe31_get_ch_ping_addr( + vfe31_ctrl->outpath.out2.ch1); + /* Y channel */ + pyaddr_pong = vfe31_get_ch_pong_addr( + vfe31_ctrl->outpath.out2.ch0); + /* Chroma channel */ + pcbcraddr_pong = vfe31_get_ch_pong_addr( + vfe31_ctrl->outpath.out2.ch1); + + CDBG("ping = 0x%p, pong = 0x%p\n", (void *)pyaddr_ping, + (void *)pyaddr_pong); + CDBG("ping_cbcr = 0x%p, pong_cbcr = 0x%p\n", + (void *)pcbcraddr_ping, (void *)pcbcraddr_pong); + + /*put addresses*/ + /* SWAP y channel*/ + vfe31_put_ch_ping_addr(vfe31_ctrl->outpath.out2.ch0, + pyaddr_pong); + vfe31_put_ch_pong_addr(vfe31_ctrl->outpath.out2.ch0, + pyaddr_ping); + /* SWAP chroma channel*/ + vfe31_put_ch_ping_addr(vfe31_ctrl->outpath.out2.ch1, + pcbcraddr_pong); + vfe31_put_ch_pong_addr(vfe31_ctrl->outpath.out2.ch1, + pcbcraddr_ping); + CDBG("after swap: ping = 0x%p, pong = 0x%p\n", + (void *)pyaddr_pong, (void *)pyaddr_ping); +#endif + } +} + + +static uint32_t vfe31_process_stats_irq_common(uint32_t statsNum, + uint32_t newAddr) { + + uint32_t pingpongStatus; + uint32_t returnAddr; + uint32_t pingpongAddr; + + /* must be 0=ping, 1=pong */ + pingpongStatus = + ((msm_io_r(vfe31_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS)) + & ((uint32_t)(1<<(statsNum + 7)))) >> (statsNum + 7); + /* stats bits starts at 7 */ + CDBG("statsNum %d, pingpongStatus %d\n", statsNum, pingpongStatus); + pingpongAddr = + ((uint32_t)(vfe31_ctrl->vfebase + + VFE_BUS_STATS_PING_PONG_BASE)) + + (3*statsNum)*4 + (1-pingpongStatus)*4; + returnAddr = msm_io_r((uint32_t *)pingpongAddr); + msm_io_w(newAddr, (uint32_t *)pingpongAddr); + return returnAddr; +} + +static void vfe_send_stats_msg(void) +{ + struct vfe_message msg; + /* fill message with right content. */ + msg._u.msgStats.frameCounter = vfe31_ctrl->vfeFrameId; + msg._u.msgStats.status_bits = vfe31_ctrl->status_bits; + msg._d = MSG_ID_COMMON; + + msg._u.msgStats.buff.aec = vfe31_ctrl->aecStatsControl.bufToRender; + msg._u.msgStats.buff.awb = vfe31_ctrl->awbStatsControl.bufToRender; + msg._u.msgStats.buff.af = vfe31_ctrl->afStatsControl.bufToRender; + + msg._u.msgStats.buff.ihist = vfe31_ctrl->ihistStatsControl.bufToRender; + msg._u.msgStats.buff.rs = vfe31_ctrl->rsStatsControl.bufToRender; + msg._u.msgStats.buff.cs = vfe31_ctrl->csStatsControl.bufToRender; + + vfe31_proc_ops(msg._d, + &msg, sizeof(struct vfe_message)); + return; +} + +static void vfe31_process_stats(void) +{ + int32_t process_stats = false; + + CDBG("%s, stats = 0x%x\n", __func__, vfe31_ctrl->status_bits); + + if (vfe31_ctrl->status_bits & VFE_IRQ_STATUS0_STATS_AEC) { + if (!vfe31_ctrl->aec_ack_pending) { + vfe31_ctrl->aec_ack_pending = TRUE; + vfe31_ctrl->aecStatsControl.bufToRender = + vfe31_process_stats_irq_common(statsAeNum, + vfe31_ctrl->aecStatsControl.nextFrameAddrBuf); + process_stats = true; + } else{ + vfe31_ctrl->aecStatsControl.bufToRender = 0; + vfe31_ctrl->aecStatsControl.droppedStatsFrameCount++; + } + } else { + vfe31_ctrl->aecStatsControl.bufToRender = 0; + } + + if (vfe31_ctrl->status_bits & VFE_IRQ_STATUS0_STATS_AWB) { + if (!vfe31_ctrl->awb_ack_pending) { + vfe31_ctrl->awb_ack_pending = TRUE; + vfe31_ctrl->awbStatsControl.bufToRender = + vfe31_process_stats_irq_common(statsAwbNum, + vfe31_ctrl->awbStatsControl.nextFrameAddrBuf); + process_stats = true; + } else{ + vfe31_ctrl->awbStatsControl.droppedStatsFrameCount++; + vfe31_ctrl->awbStatsControl.bufToRender = 0; + } + } else { + vfe31_ctrl->awbStatsControl.bufToRender = 0; + } + + + if (vfe31_ctrl->status_bits & VFE_IRQ_STATUS0_STATS_AF) { + if (!vfe31_ctrl->af_ack_pending) { + vfe31_ctrl->af_ack_pending = TRUE; + vfe31_ctrl->afStatsControl.bufToRender = + vfe31_process_stats_irq_common(statsAfNum, + vfe31_ctrl->afStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe31_ctrl->afStatsControl.bufToRender = 0; + vfe31_ctrl->afStatsControl.droppedStatsFrameCount++; + } + } else { + vfe31_ctrl->afStatsControl.bufToRender = 0; + } + + if (vfe31_ctrl->status_bits & VFE_IRQ_STATUS0_STATS_IHIST) { + if (!vfe31_ctrl->ihist_ack_pending) { + vfe31_ctrl->ihist_ack_pending = TRUE; + vfe31_ctrl->ihistStatsControl.bufToRender = + vfe31_process_stats_irq_common(statsIhistNum, + vfe31_ctrl->ihistStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe31_ctrl->ihistStatsControl.droppedStatsFrameCount++; + vfe31_ctrl->ihistStatsControl.bufToRender = 0; + } + } else { + vfe31_ctrl->ihistStatsControl.bufToRender = 0; + } + + if (vfe31_ctrl->status_bits & VFE_IRQ_STATUS0_STATS_RS) { + if (!vfe31_ctrl->rs_ack_pending) { + vfe31_ctrl->rs_ack_pending = TRUE; + vfe31_ctrl->rsStatsControl.bufToRender = + vfe31_process_stats_irq_common(statsRsNum, + vfe31_ctrl->rsStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe31_ctrl->rsStatsControl.droppedStatsFrameCount++; + vfe31_ctrl->rsStatsControl.bufToRender = 0; + } + } else { + vfe31_ctrl->rsStatsControl.bufToRender = 0; + } + + + if (vfe31_ctrl->status_bits & VFE_IRQ_STATUS0_STATS_CS) { + if (!vfe31_ctrl->cs_ack_pending) { + vfe31_ctrl->cs_ack_pending = TRUE; + vfe31_ctrl->csStatsControl.bufToRender = + vfe31_process_stats_irq_common(statsCsNum, + vfe31_ctrl->csStatsControl.nextFrameAddrBuf); + process_stats = true; + } else { + vfe31_ctrl->csStatsControl.droppedStatsFrameCount++; + vfe31_ctrl->csStatsControl.bufToRender = 0; + } + } else { + vfe31_ctrl->csStatsControl.bufToRender = 0; + } + + if (process_stats) + vfe_send_stats_msg(); + + return; +} + +static void vfe31_process_stats_irq(uint32_t *irqstatus) +{ + /* Subsample the stats according to the hfr speed*/ + if ((vfe31_ctrl->hfr_mode != HFR_MODE_OFF) && + (vfe31_ctrl->vfeFrameId % vfe31_ctrl->hfr_mode != 0)) { + CDBG("Skip the stats when HFR enabled\n"); + return; + } + + vfe31_ctrl->status_bits = VFE_COM_STATUS & *irqstatus; + vfe31_process_stats(); + return; +} + +static void vfe31_do_tasklet(unsigned long data) +{ + unsigned long flags; + + struct vfe31_isr_queue_cmd *qcmd = NULL; + + CDBG("=== vfe31_do_tasklet start === \n"); + + while (atomic_read(&irq_cnt)) { + spin_lock_irqsave(&vfe31_ctrl->tasklet_lock, flags); + qcmd = list_first_entry(&vfe31_ctrl->tasklet_q, + struct vfe31_isr_queue_cmd, list); + atomic_sub(1, &irq_cnt); + + if (!qcmd) { + spin_unlock_irqrestore(&vfe31_ctrl->tasklet_lock, + flags); + return; + } + + list_del(&qcmd->list); + spin_unlock_irqrestore(&vfe31_ctrl->tasklet_lock, + flags); + + /* interrupt to be processed, *qcmd has the payload. */ + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_REG_UPDATE_MASK) { + CDBG("irq regUpdateIrq\n"); + vfe31_process_reg_update_irq(); + } + + if (qcmd->vfeInterruptStatus1 & + VFE_IMASK_RESET) { + CDBG("irq resetAckIrq\n"); + vfe31_process_reset_irq(); + } + + + if (qcmd->vfeInterruptStatus1 & + VFE_IMASK_AXI_HALT) { + CDBG("irq axi halt irq\n"); + vfe31_process_axi_halt_irq(); + } + + if (atomic_read(&vfe31_ctrl->vstate)) { + if (qcmd->vfeInterruptStatus1 & + VFE31_IMASK_ERROR_ONLY_1) { + pr_err("irq errorIrq\n"); + vfe31_process_error_irq( + qcmd->vfeInterruptStatus1 & + VFE31_IMASK_ERROR_ONLY_1); + } + + /* irqs below are only valid when in active state. */ + /* next, check output path related interrupts. */ + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE0_MASK) { + CDBG("Image composite done 0 irq occured.\n"); + vfe31_process_output_path_irq_0( + qcmd->vfePingPongStatus); + } + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE1_MASK) { + CDBG("Image composite done 1 irq occured.\n"); + vfe31_process_output_path_irq_1( + qcmd->vfePingPongStatus); + } + + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE2_MASK) { + CDBG("Image composite done 2 irq occured.\n"); + vfe31_process_output_path_irq_2( + qcmd->vfePingPongStatus); + } + + /* then process stats irq. */ + if (vfe31_ctrl->stats_comp) { + /* process stats comb interrupt. */ + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_COMPOSIT_MASK) { + CDBG("Stats composite irq occured.\n"); + vfe31_process_stats_irq( + &qcmd->vfeInterruptStatus0); + } + } else { + /* process individual stats interrupt. */ + if (qcmd->vfeInterruptStatus0 & + VFE_COM_STATUS) { + CDBG("VFE stats occured.\n"); + vfe31_process_stats_irq( + &qcmd->vfeInterruptStatus0); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER0) { + CDBG("SYNC_TIMER 0 irq occured.\n"); + vfe31_send_msg_no_payload( + MSG_ID_SYNC_TIMER0_DONE); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER1) { + CDBG("SYNC_TIMER 1 irq occured.\n"); + vfe31_send_msg_no_payload( + MSG_ID_SYNC_TIMER1_DONE); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER2) { + CDBG("SYNC_TIMER 2 irq occured.\n"); + vfe31_send_msg_no_payload( + MSG_ID_SYNC_TIMER2_DONE); + } + } + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_CAMIF_SOF_MASK) { + CDBG("irq camifSofIrq\n"); + vfe31_process_camif_sof_irq(); + } + kfree(qcmd); + } + CDBG("=== vfe31_do_tasklet end === \n"); +} + +DECLARE_TASKLET(vfe31_tasklet, vfe31_do_tasklet, 0); + +static irqreturn_t vfe31_parse_irq(int irq_num, void *data) +{ + unsigned long flags; + struct vfe31_irq_status irq; + struct vfe31_isr_queue_cmd *qcmd; + uint32_t *val; + CDBG("vfe_parse_irq\n"); + memset(&irq, 0, sizeof(struct vfe31_irq_status)); + + val = (uint32_t *)(vfe31_ctrl->vfebase + VFE_IRQ_STATUS_0); + irq.vfeIrqStatus0 = msm_io_r(val); + + val = (uint32_t *)(vfe31_ctrl->vfebase + VFE_IRQ_STATUS_1); + irq.vfeIrqStatus1 = msm_io_r(val); + + if (irq.vfeIrqStatus1 & VFE_IMASK_AXI_HALT) { + msm_io_w(VFE_IMASK_RESET, vfe31_ctrl->vfebase + VFE_IRQ_MASK_1); + + msm_io_w_mb(AXI_HALT_CLEAR, + vfe31_ctrl->vfebase + VFE_AXI_CMD); + } + + val = (uint32_t *)(vfe31_ctrl->vfebase + VFE_CAMIF_STATUS); + irq.camifStatus = msm_io_r(val); + CDBG("camifStatus = 0x%x\n", irq.camifStatus); + + val = (uint32_t *)(vfe31_ctrl->vfebase + VFE_BUS_PING_PONG_STATUS); + irq.vfePingPongStatus = msm_io_r(val); + + /* clear the pending interrupt of the same kind.*/ + msm_io_w(irq.vfeIrqStatus0, vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_io_w(irq.vfeIrqStatus1, vfe31_ctrl->vfebase + VFE_IRQ_CLEAR_1); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, vfe31_ctrl->vfebase + VFE_IRQ_CMD); + + if ((irq.vfeIrqStatus0 == 0) && (irq.vfeIrqStatus1 == 0)) { + CDBG("vfe_parse_irq: vfeIrqStatus0 & 1 are both 0!\n"); + return IRQ_HANDLED; + } + + qcmd = kzalloc(sizeof(struct vfe31_isr_queue_cmd), + GFP_ATOMIC); + if (!qcmd) { + pr_err("vfe_parse_irq: qcmd malloc failed!\n"); + return IRQ_HANDLED; + } + + if (atomic_read(&vfe31_ctrl->stop_ack_pending)) { + irq.vfeIrqStatus0 &= VFE_IMASK_WHILE_STOPPING_0; + irq.vfeIrqStatus1 &= vfe31_ctrl->while_stopping_mask; + } + + spin_lock_irqsave(&vfe31_ctrl->xbar_lock, flags); + if ((irq.vfeIrqStatus0 & + VFE_IRQ_STATUS0_CAMIF_EOF_MASK) && + vfe31_ctrl->xbar_update_pending) { + CDBG("irq camifEofIrq\n"); + msm_io_memcpy(vfe31_ctrl->vfebase + V31_XBAR_CFG_OFF, + (void *)vfe31_ctrl->xbar_cfg, V31_XBAR_CFG_LEN); + vfe31_ctrl->xbar_update_pending = 0; + } + spin_unlock_irqrestore(&vfe31_ctrl->xbar_lock, flags); + CDBG("vfe_parse_irq: Irq_status0 = 0x%x, Irq_status1 = 0x%x.\n", + irq.vfeIrqStatus0, irq.vfeIrqStatus1); + + qcmd->vfeInterruptStatus0 = irq.vfeIrqStatus0; + qcmd->vfeInterruptStatus1 = irq.vfeIrqStatus1; + qcmd->vfePingPongStatus = irq.vfePingPongStatus; + + spin_lock_irqsave(&vfe31_ctrl->tasklet_lock, flags); + list_add_tail(&qcmd->list, &vfe31_ctrl->tasklet_q); + + atomic_add(1, &irq_cnt); + spin_unlock_irqrestore(&vfe31_ctrl->tasklet_lock, flags); + tasklet_schedule(&vfe31_tasklet); + return IRQ_HANDLED; +} + +static void vfe31_release(struct platform_device *pdev) +{ + struct resource *vfemem, *vfeio; + + vfe31_reset_free_buf_queue_all(); + CDBG("%s, free_irq\n", __func__); + free_irq(vfe31_ctrl->vfeirq, 0); + tasklet_kill(&vfe31_tasklet); + + if (atomic_read(&irq_cnt)) + pr_warning("%s, Warning IRQ Count not ZERO\n", __func__); + + vfemem = vfe31_ctrl->vfemem; + vfeio = vfe31_ctrl->vfeio; + + msm_vpe_release(); + + kfree(vfe31_ctrl->extdata); + iounmap(vfe31_ctrl->vfebase); + kfree(vfe31_ctrl); + vfe31_ctrl = NULL; + release_mem_region(vfemem->start, (vfemem->end - vfemem->start) + 1); + CDBG("%s, msm_camio_disable\n", __func__); + msm_camio_disable(pdev); + msm_camio_set_perf_lvl(S_EXIT); + + vfe_syncdata = NULL; +} + +static int vfe31_resource_init(struct msm_vfe_callback *presp, + struct platform_device *pdev, void *sdata) +{ + struct resource *vfemem, *vfeirq, *vfeio; + int rc; + struct msm_camera_sensor_info *s_info; + s_info = pdev->dev.platform_data; + + pdev->resource = s_info->resource; + pdev->num_resources = s_info->num_resources; + + vfemem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!vfemem) { + pr_err("%s: no mem resource?\n", __func__); + return -ENODEV; + } + + vfeirq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!vfeirq) { + pr_err("%s: no irq resource?\n", __func__); + return -ENODEV; + } + + vfeio = request_mem_region(vfemem->start, + resource_size(vfemem), pdev->name); + if (!vfeio) { + pr_err("%s: VFE region already claimed\n", __func__); + return -EBUSY; + } + + vfe31_ctrl = kzalloc(sizeof(struct vfe31_ctrl_type), GFP_KERNEL); + if (!vfe31_ctrl) { + rc = -ENOMEM; + goto cmd_init_failed1; + } + + vfe31_ctrl->vfeirq = vfeirq->start; + + vfe31_ctrl->vfebase = + ioremap(vfemem->start, (vfemem->end - vfemem->start) + 1); + if (!vfe31_ctrl->vfebase) { + rc = -ENOMEM; + pr_err("%s: vfe ioremap failed\n", __func__); + goto cmd_init_failed2; + } + + if (presp && presp->vfe_resp) + vfe31_ctrl->resp = presp; + else { + rc = -EINVAL; + goto cmd_init_failed3; + } + + vfe31_ctrl->extdata = + kmalloc(sizeof(struct vfe31_frame_extra), GFP_KERNEL); + if (!vfe31_ctrl->extdata) { + rc = -ENOMEM; + goto cmd_init_failed3; + } + + vfe31_ctrl->extlen = sizeof(struct vfe31_frame_extra); + + spin_lock_init(&vfe31_ctrl->io_lock); + spin_lock_init(&vfe31_ctrl->update_ack_lock); + spin_lock_init(&vfe31_ctrl->tasklet_lock); + + INIT_LIST_HEAD(&vfe31_ctrl->tasklet_q); + vfe31_init_free_buf_queue(); + + vfe31_ctrl->syncdata = sdata; + vfe31_ctrl->vfemem = vfemem; + vfe31_ctrl->vfeio = vfeio; + vfe31_ctrl->update_gamma = false; + vfe31_ctrl->update_luma = false; + vfe31_ctrl->s_info = s_info; + vfe31_ctrl->stats_comp = 0; + vfe31_ctrl->hfr_mode = HFR_MODE_OFF; + return 0; + +cmd_init_failed3: + free_irq(vfe31_ctrl->vfeirq, 0); + iounmap(vfe31_ctrl->vfebase); +cmd_init_failed2: + kfree(vfe31_ctrl); +cmd_init_failed1: + release_mem_region(vfemem->start, (vfemem->end - vfemem->start) + 1); + return rc; +} + +static int vfe31_init(struct msm_vfe_callback *presp, + struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + + camio_clk = camdev->ioclk; + + rc = vfe31_resource_init(presp, pdev, vfe_syncdata); + if (rc < 0) + return rc; + /* Bring up all the required GPIOs and Clocks */ + rc = msm_camio_enable(pdev); + msm_camio_set_perf_lvl(S_INIT); + if (msm_vpe_open() < 0) + CDBG("%s: vpe_open failed\n", __func__); + + /* TO DO: Need to release the VFE resources */ + rc = request_irq(vfe31_ctrl->vfeirq, vfe31_parse_irq, + IRQF_TRIGGER_RISING, "vfe", 0); + + return rc; +} + +void msm_camvfe_fn_init(struct msm_camvfe_fn *fptr, void *data) +{ + fptr->vfe_init = vfe31_init; + fptr->vfe_enable = vfe31_enable; + fptr->vfe_config = vfe31_config; + fptr->vfe_disable = vfe31_disable; + fptr->vfe_release = vfe31_release; + fptr->vfe_stop = vfe31_stop; + vfe_syncdata = data; +} + +void msm_camvpe_fn_init(struct msm_camvpe_fn *fptr, void *data) +{ + fptr->vpe_reg = msm_vpe_reg; + fptr->send_frame_to_vpe = msm_send_frame_to_vpe; + fptr->vpe_config = msm_vpe_config; + fptr->vpe_cfg_update = msm_vpe_cfg_update; + fptr->dis = &(vpe_ctrl->dis_en); + fptr->vpe_cfg_offset = msm_vpe_offset_update; + vpe_ctrl->syncdata = data; +} diff --git a/drivers/media/video/msm/msm_vfe31.h b/drivers/media/video/msm/msm_vfe31.h new file mode 100644 index 00000000000..e3c06eece20 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe31.h @@ -0,0 +1,1113 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_VFE31_H__ +#define __MSM_VFE31_H__ + +#define TRUE 1 +#define FALSE 0 + +/* at start of camif, bit 1:0 = 0x01:enable + * image data capture at frame boundary. */ +#define CAMIF_COMMAND_START 0x00000005 + +/* bit 2= 0x1:clear the CAMIF_STATUS register + * value. */ +#define CAMIF_COMMAND_CLEAR 0x00000004 + +/* at stop of vfe pipeline, for now it is assumed + * that camif will stop at any time. Bit 1:0 = 0x10: + * disable image data capture immediately. */ +#define CAMIF_COMMAND_STOP_IMMEDIATELY 0x00000002 + +/* at stop of vfe pipeline, for now it is assumed + * that camif will stop at any time. Bit 1:0 = 0x00: + * disable image data capture at frame boundary */ +#define CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY 0x00000000 + +/* to halt axi bridge */ +#define AXI_HALT 0x00000001 + +/* clear the halt bit. */ +#define AXI_HALT_CLEAR 0x00000000 + +/* clear axi_halt_irq */ +#define MASK_AXI_HALT_IRQ 0xFF7FFFFF + +/* reset the pipeline when stop command is issued. + * (without reset the register.) bit 26-31 = 0, + * domain reset, bit 0-9 = 1 for module reset, except + * register module. */ +#define VFE_RESET_UPON_STOP_CMD 0x000003ef + +/* reset the pipeline when reset command. + * bit 26-31 = 0, domain reset, bit 0-9 = 1 for module reset. */ +#define VFE_RESET_UPON_RESET_CMD 0x000003ff + +/* bit 5 is for axi status idle or busy. + * 1 = halted, 0 = busy */ +#define AXI_STATUS_BUSY_MASK 0x00000020 + +/* bit 0 & bit 1 = 1, both y and cbcr irqs need to be present + * for frame done interrupt */ +#define VFE_COMP_IRQ_BOTH_Y_CBCR 3 + +/* bit 1 = 1, only cbcr irq triggers frame done interrupt */ +#define VFE_COMP_IRQ_CBCR_ONLY 2 + +/* bit 0 = 1, only y irq triggers frame done interrupt */ +#define VFE_COMP_IRQ_Y_ONLY 1 + +/* bit 0 = 1, PM go; bit1 = 1, PM stop */ +#define VFE_PERFORMANCE_MONITOR_GO 0x00000001 +#define VFE_PERFORMANCE_MONITOR_STOP 0x00000002 + +/* bit 0 = 1, test gen go; bit1 = 1, test gen stop */ +#define VFE_TEST_GEN_GO 0x00000001 +#define VFE_TEST_GEN_STOP 0x00000002 + +/* the chroma is assumed to be interpolated between + * the luma samples. JPEG 4:2:2 */ +#define VFE_CHROMA_UPSAMPLE_INTERPOLATED 0 + +/* constants for irq registers */ +#define VFE_DISABLE_ALL_IRQS 0 +/* bit =1 is to clear the corresponding bit in VFE_IRQ_STATUS. */ +#define VFE_CLEAR_ALL_IRQS 0xffffffff + +#define VFE_IRQ_STATUS0_CAMIF_SOF_MASK 0x00000001 +#define VFE_IRQ_STATUS0_CAMIF_EOF_MASK 0x00000004 +#define VFE_IRQ_STATUS0_REG_UPDATE_MASK 0x00000020 +#define VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE0_MASK 0x00200000 +#define VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE1_MASK 0x00400000 +#define VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE2_MASK 0x00800000 +#define VFE_IRQ_STATUS1_RESET_AXI_HALT_ACK_MASK 0x00800000 +#define VFE_IRQ_STATUS0_STATS_COMPOSIT_MASK 0x01000000 + +#define VFE_IRQ_STATUS0_STATS_AEC 0x2000 /* bit 13 */ +#define VFE_IRQ_STATUS0_STATS_AF 0x4000 /* bit 14 */ +#define VFE_IRQ_STATUS0_STATS_AWB 0x8000 /* bit 15 */ +#define VFE_IRQ_STATUS0_STATS_RS 0x10000 /* bit 16 */ +#define VFE_IRQ_STATUS0_STATS_CS 0x20000 /* bit 17 */ +#define VFE_IRQ_STATUS0_STATS_IHIST 0x40000 /* bit 18 */ + +#define VFE_IRQ_STATUS0_SYNC_TIMER0 0x2000000 /* bit 25 */ +#define VFE_IRQ_STATUS0_SYNC_TIMER1 0x4000000 /* bit 26 */ +#define VFE_IRQ_STATUS0_SYNC_TIMER2 0x8000000 /* bit 27 */ +#define VFE_IRQ_STATUS0_ASYNC_TIMER0 0x10000000 /* bit 28 */ +#define VFE_IRQ_STATUS0_ASYNC_TIMER1 0x20000000 /* bit 29 */ +#define VFE_IRQ_STATUS0_ASYNC_TIMER2 0x40000000 /* bit 30 */ +#define VFE_IRQ_STATUS0_ASYNC_TIMER3 0x80000000 /* bit 31 */ + +/* imask for while waiting for stop ack, driver has already + * requested stop, waiting for reset irq, and async timer irq. + * For irq_status_0, bit 28-31 are for async timer. For + * irq_status_1, bit 22 for reset irq, bit 23 for axi_halt_ack + irq */ +#define VFE_IMASK_WHILE_STOPPING_0 0xF0000000 +#define VFE_IMASK_WHILE_STOPPING_1 0x00C00000 +#define VFE_IMASK_RESET 0x00400000 +#define VFE_IMASK_AXI_HALT 0x00800000 + + +/* no error irq in mask 0 */ +#define VFE_IMASK_ERROR_ONLY_0 0x0 +/* when normal case, don't want to block error status. */ +/* bit 0-21 are error irq bits */ +#define VFE_IMASK_ERROR_ONLY_1 0x003fffff + +/* For BPC bit 0,bit 12-17 and bit 26 -20 are set to zero and other's 1 */ +#define BPC_MASK 0xF80C0FFE + +/* For BPC bit 1 and 2 are set to zero and other's 1 */ +#define ABF_MASK 0xFFFFFFF9 + +/* For MCE enable bit 28 set to zero and other's 1 */ +#define MCE_EN_MASK 0xEFFFFFFF + +/* For MCE Q_K bit 28 to 31 set to zero and other's 1 */ +#define MCE_Q_K_MASK 0x0FFFFFFF + +#define AWB_ENABLE_MASK 0x00000080 /* bit 7 */ +#define AF_ENABLE_MASK 0x00000040 /* bit 6 */ +#define AE_ENABLE_MASK 0x00000020 /* bit 5 */ +#define IHIST_ENABLE_MASK 0x00008000 /* bit 15 */ +#define RS_ENABLE_MASK 0x00000100 /* bit 8 */ +#define CS_ENABLE_MASK 0x00000200 /* bit 9 */ +#define RS_CS_ENABLE_MASK 0x00000300 /* bit 8,9 */ +#define STATS_ENABLE_MASK 0x000483E0 /* bit 18,15,9,8,7,6,5*/ + +#define VFE_REG_UPDATE_TRIGGER 1 +#define VFE_PM_BUF_MAX_CNT_MASK 0xFF +#define VFE_DMI_CFG_DEFAULT 0x00000100 +#define LENS_ROLL_OFF_DELTA_TABLE_OFFSET 32 +#define VFE_AE_PINGPONG_STATUS_BIT 0x80 +#define VFE_AF_PINGPONG_STATUS_BIT 0x100 +#define VFE_AWB_PINGPONG_STATUS_BIT 0x200 + +#define HFR_MODE_OFF 1 + +enum VFE31_DMI_RAM_SEL { + NO_MEM_SELECTED = 0, + ROLLOFF_RAM = 0x1, + RGBLUT_RAM_CH0_BANK0 = 0x2, + RGBLUT_RAM_CH0_BANK1 = 0x3, + RGBLUT_RAM_CH1_BANK0 = 0x4, + RGBLUT_RAM_CH1_BANK1 = 0x5, + RGBLUT_RAM_CH2_BANK0 = 0x6, + RGBLUT_RAM_CH2_BANK1 = 0x7, + STATS_HIST_RAM = 0x8, + RGBLUT_CHX_BANK0 = 0x9, + RGBLUT_CHX_BANK1 = 0xa, + LUMA_ADAPT_LUT_RAM_BANK0 = 0xb, + LUMA_ADAPT_LUT_RAM_BANK1 = 0xc +}; + +enum VFE_STATE { + VFE_STATE_IDLE, + VFE_STATE_ACTIVE +}; + +enum vfe_recording_state { + VFE_REC_STATE_IDLE, + VFE_REC_STATE_START_REQUESTED, + VFE_REC_STATE_STARTED, + VFE_REC_STATE_STOP_REQUESTED, + VFE_REC_STATE_STOPPED, +}; + +#define V31_DUMMY_0 0 +#define V31_SET_CLK 1 +#define V31_RESET 2 +#define V31_START 3 +#define V31_TEST_GEN_START 4 +#define V31_OPERATION_CFG 5 +#define V31_AXI_OUT_CFG 6 +#define V31_CAMIF_CFG 7 +#define V31_AXI_INPUT_CFG 8 +#define V31_BLACK_LEVEL_CFG 9 +#define V31_ROLL_OFF_CFG 10 +#define V31_DEMUX_CFG 11 +#define V31_DEMOSAIC_0_CFG 12 /* general */ +#define V31_DEMOSAIC_1_CFG 13 /* ABF */ +#define V31_DEMOSAIC_2_CFG 14 /* BPC */ +#define V31_FOV_CFG 15 +#define V31_MAIN_SCALER_CFG 16 +#define V31_WB_CFG 17 +#define V31_COLOR_COR_CFG 18 +#define V31_RGB_G_CFG 19 +#define V31_LA_CFG 20 +#define V31_CHROMA_EN_CFG 21 +#define V31_CHROMA_SUP_CFG 22 +#define V31_MCE_CFG 23 +#define V31_SK_ENHAN_CFG 24 +#define V31_ASF_CFG 25 +#define V31_S2Y_CFG 26 +#define V31_S2CbCr_CFG 27 +#define V31_CHROMA_SUBS_CFG 28 +#define V31_OUT_CLAMP_CFG 29 +#define V31_FRAME_SKIP_CFG 30 +#define V31_DUMMY_1 31 +#define V31_DUMMY_2 32 +#define V31_DUMMY_3 33 +#define V31_UPDATE 34 +#define V31_BL_LVL_UPDATE 35 +#define V31_DEMUX_UPDATE 36 +#define V31_DEMOSAIC_1_UPDATE 37 /* BPC */ +#define V31_DEMOSAIC_2_UPDATE 38 /* ABF */ +#define V31_FOV_UPDATE 39 +#define V31_MAIN_SCALER_UPDATE 40 +#define V31_WB_UPDATE 41 +#define V31_COLOR_COR_UPDATE 42 +#define V31_RGB_G_UPDATE 43 +#define V31_LA_UPDATE 44 +#define V31_CHROMA_EN_UPDATE 45 +#define V31_CHROMA_SUP_UPDATE 46 +#define V31_MCE_UPDATE 47 +#define V31_SK_ENHAN_UPDATE 48 +#define V31_S2CbCr_UPDATE 49 +#define V31_S2Y_UPDATE 50 +#define V31_ASF_UPDATE 51 +#define V31_FRAME_SKIP_UPDATE 52 +#define V31_CAMIF_FRAME_UPDATE 53 +#define V31_STATS_AF_UPDATE 54 +#define V31_STATS_AE_UPDATE 55 +#define V31_STATS_AWB_UPDATE 56 +#define V31_STATS_RS_UPDATE 57 +#define V31_STATS_CS_UPDATE 58 +#define V31_STATS_SKIN_UPDATE 59 +#define V31_STATS_IHIST_UPDATE 60 +#define V31_DUMMY_4 61 +#define V31_EPOCH1_ACK 62 +#define V31_EPOCH2_ACK 63 +#define V31_START_RECORDING 64 +#define V31_STOP_RECORDING 65 +#define V31_DUMMY_5 66 +#define V31_DUMMY_6 67 +#define V31_CAPTURE 68 +#define V31_DUMMY_7 69 +#define V31_STOP 70 +#define V31_GET_HW_VERSION 71 +#define V31_GET_FRAME_SKIP_COUNTS 72 +#define V31_OUTPUT1_BUFFER_ENQ 73 +#define V31_OUTPUT2_BUFFER_ENQ 74 +#define V31_OUTPUT3_BUFFER_ENQ 75 +#define V31_JPEG_OUT_BUF_ENQ 76 +#define V31_RAW_OUT_BUF_ENQ 77 +#define V31_RAW_IN_BUF_ENQ 78 +#define V31_STATS_AF_ENQ 79 +#define V31_STATS_AE_ENQ 80 +#define V31_STATS_AWB_ENQ 81 +#define V31_STATS_RS_ENQ 82 +#define V31_STATS_CS_ENQ 83 +#define V31_STATS_SKIN_ENQ 84 +#define V31_STATS_IHIST_ENQ 85 +#define V31_DUMMY_8 86 +#define V31_JPEG_ENC_CFG 87 +#define V31_DUMMY_9 88 +#define V31_STATS_AF_START 89 +#define V31_STATS_AF_STOP 90 +#define V31_STATS_AE_START 91 +#define V31_STATS_AE_STOP 92 +#define V31_STATS_AWB_START 93 +#define V31_STATS_AWB_STOP 94 +#define V31_STATS_RS_START 95 +#define V31_STATS_RS_STOP 96 +#define V31_STATS_CS_START 97 +#define V31_STATS_CS_STOP 98 +#define V31_STATS_SKIN_START 99 +#define V31_STATS_SKIN_STOP 100 +#define V31_STATS_IHIST_START 101 +#define V31_STATS_IHIST_STOP 102 +#define V31_DUMMY_10 103 +#define V31_SYNC_TIMER_SETTING 104 +#define V31_ASYNC_TIMER_SETTING 105 +#define V31_LIVESHOT 106 +#define V31_ZSL 107 +#define V31_STEREOCAM 108 +#define V31_LA_SETUP 109 +#define V31_XBAR_CFG 110 +#define V31_EZTUNE_CFG 111 + +#define V31_CAMIF_OFF 0x000001E4 +#define V31_CAMIF_LEN 32 + +#define V31_DEMUX_OFF 0x00000284 +#define V31_DEMUX_LEN 20 + +#define V31_DEMOSAIC_0_OFF 0x00000298 +#define V31_DEMOSAIC_0_LEN 4 +/* ABF */ +#define V31_DEMOSAIC_1_OFF 0x000002A4 +#define V31_DEMOSAIC_1_LEN 180 +/* BPC */ +#define V31_DEMOSAIC_2_OFF 0x0000029C +#define V31_DEMOSAIC_2_LEN 8 + +/* gamma VFE_LUT_BANK_SEL*/ +#define V31_GAMMA_CFG_OFF 0x000003BC +#define V31_LUMA_CFG_OFF 0x000003C0 + +#define V31_OUT_CLAMP_OFF 0x00000524 +#define V31_OUT_CLAMP_LEN 8 + +#define V31_OPERATION_CFG_LEN 32 + +#define V31_AXI_OUT_OFF 0x00000038 +#define V31_AXI_OUT_LEN 212 +#define V31_AXI_CH_INF_LEN 24 +#define V31_AXI_CFG_LEN 47 + +#define V31_FRAME_SKIP_OFF 0x00000504 +#define V31_FRAME_SKIP_LEN 32 + +#define V31_CHROMA_SUBS_OFF 0x000004F8 +#define V31_CHROMA_SUBS_LEN 12 + +#define V31_FOV_OFF 0x00000360 +#define V31_FOV_LEN 8 + +#define V31_MAIN_SCALER_OFF 0x00000368 +#define V31_MAIN_SCALER_LEN 28 + +#define V31_S2Y_OFF 0x000004D0 +#define V31_S2Y_LEN 20 + +#define V31_S2CbCr_OFF 0x000004E4 +#define V31_S2CbCr_LEN 20 + +#define V31_CHROMA_EN_OFF 0x000003C4 +#define V31_CHROMA_EN_LEN 36 + +#define V31_SYNC_TIMER_OFF 0x0000020C +#define V31_SYNC_TIMER_POLARITY_OFF 0x00000234 +#define V31_TIMER_SELECT_OFF 0x0000025C +#define V31_SYNC_TIMER_LEN 28 + +#define V31_ASYNC_TIMER_OFF 0x00000238 +#define V31_ASYNC_TIMER_LEN 28 + +#define V31_BLACK_LEVEL_OFF 0x00000264 +#define V31_BLACK_LEVEL_LEN 16 + +#define V31_ROLL_OFF_CFG_OFF 0x00000274 +#define V31_ROLL_OFF_CFG_LEN 16 + +#define V31_COLOR_COR_OFF 0x00000388 +#define V31_COLOR_COR_LEN 52 + +#define V31_WB_OFF 0x00000384 +#define V31_WB_LEN 4 + +#define V31_RGB_G_OFF 0x000003BC +#define V31_RGB_G_LEN 4 + +#define V31_LA_OFF 0x000003C0 +#define V31_LA_LEN 4 + +#define V31_SCE_OFF 0x00000418 +#define V31_SCE_LEN 136 + +#define V31_CHROMA_SUP_OFF 0x000003E8 +#define V31_CHROMA_SUP_LEN 12 + +#define V31_MCE_OFF 0x000003F4 +#define V31_MCE_LEN 36 +#define V31_STATS_AF_OFF 0x0000053c +#define V31_STATS_AF_LEN 16 + +#define V31_STATS_AE_OFF 0x00000534 +#define V31_STATS_AE_LEN 8 + +#define V31_STATS_AWB_OFF 0x0000054c +#define V31_STATS_AWB_LEN 32 + +#define V31_STATS_IHIST_OFF 0x0000057c +#define V31_STATS_IHIST_LEN 8 + +#define V31_STATS_RS_OFF 0x0000056c +#define V31_STATS_RS_LEN 8 + +#define V31_STATS_CS_OFF 0x00000574 +#define V31_STATS_CS_LEN 8 + +#define V31_XBAR_CFG_OFF 0x00000040 +#define V31_XBAR_CFG_LEN 8 + +#define V31_EZTUNE_CFG_OFF 0x00000010 +#define V31_EZTUNE_CFG_LEN 4 + +#define V31_ASF_OFF 0x000004A0 +#define V31_ASF_LEN 48 +#define V31_ASF_UPDATE_LEN 36 + +#define V31_CAPTURE_LEN 4 + +struct vfe_cmd_hw_version { + uint32_t minorVersion; + uint32_t majorVersion; + uint32_t coreVersion; +}; + +enum VFE_AXI_OUTPUT_MODE { + VFE_AXI_OUTPUT_MODE_Output1, + VFE_AXI_OUTPUT_MODE_Output2, + VFE_AXI_OUTPUT_MODE_Output1AndOutput2, + VFE_AXI_OUTPUT_MODE_CAMIFToAXIViaOutput2, + VFE_AXI_OUTPUT_MODE_Output2AndCAMIFToAXIViaOutput1, + VFE_AXI_OUTPUT_MODE_Output1AndCAMIFToAXIViaOutput2, + VFE_AXI_LAST_OUTPUT_MODE_ENUM +}; + +enum VFE_RAW_WR_PATH_SEL { + VFE_RAW_OUTPUT_DISABLED, + VFE_RAW_OUTPUT_ENC_CBCR_PATH, + VFE_RAW_OUTPUT_VIEW_CBCR_PATH, + VFE_RAW_OUTPUT_PATH_INVALID +}; + + +#define VFE_AXI_OUTPUT_BURST_LENGTH 4 +#define VFE_MAX_NUM_FRAGMENTS_PER_FRAME 4 +#define VFE_AXI_OUTPUT_CFG_FRAME_COUNT 3 + +struct vfe_cmds_per_write_master { + uint16_t imageWidth; + uint16_t imageHeight; + uint16_t outRowCount; + uint16_t outRowIncrement; + uint32_t outFragments[VFE_AXI_OUTPUT_CFG_FRAME_COUNT] + [VFE_MAX_NUM_FRAGMENTS_PER_FRAME]; +}; + +struct vfe_cmds_axi_per_output_path { + uint8_t fragmentCount; + struct vfe_cmds_per_write_master firstWM; + struct vfe_cmds_per_write_master secondWM; +}; + +enum VFE_AXI_BURST_LENGTH { + VFE_AXI_BURST_LENGTH_IS_2 = 2, + VFE_AXI_BURST_LENGTH_IS_4 = 4, + VFE_AXI_BURST_LENGTH_IS_8 = 8, + VFE_AXI_BURST_LENGTH_IS_16 = 16 +}; + + +struct vfe_cmd_fov_crop_config { + uint8_t enable; + uint16_t firstPixel; + uint16_t lastPixel; + uint16_t firstLine; + uint16_t lastLine; +}; + +struct vfe_cmds_main_scaler_stripe_init { + uint16_t MNCounterInit; + uint16_t phaseInit; +}; + +struct vfe_cmds_scaler_one_dimension { + uint8_t enable; + uint16_t inputSize; + uint16_t outputSize; + uint32_t phaseMultiplicationFactor; + uint8_t interpolationResolution; +}; + +struct vfe_cmd_main_scaler_config { + uint8_t enable; + struct vfe_cmds_scaler_one_dimension hconfig; + struct vfe_cmds_scaler_one_dimension vconfig; + struct vfe_cmds_main_scaler_stripe_init MNInitH; + struct vfe_cmds_main_scaler_stripe_init MNInitV; +}; + +struct vfe_cmd_scaler2_config { + uint8_t enable; + struct vfe_cmds_scaler_one_dimension hconfig; + struct vfe_cmds_scaler_one_dimension vconfig; +}; + + +struct vfe_cmd_frame_skip_update { + uint32_t output1Pattern; + uint32_t output2Pattern; +}; + +struct vfe_cmd_output_clamp_config { + uint8_t minCh0; + uint8_t minCh1; + uint8_t minCh2; + uint8_t maxCh0; + uint8_t maxCh1; + uint8_t maxCh2; +}; + +struct vfe_cmd_chroma_subsample_config { + uint8_t enable; + uint8_t cropEnable; + uint8_t vsubSampleEnable; + uint8_t hsubSampleEnable; + uint8_t vCosited; + uint8_t hCosited; + uint8_t vCositedPhase; + uint8_t hCositedPhase; + uint16_t cropWidthFirstPixel; + uint16_t cropWidthLastPixel; + uint16_t cropHeightFirstLine; + uint16_t cropHeightLastLine; +}; + +enum VFE_START_INPUT_SOURCE { + VFE_START_INPUT_SOURCE_CAMIF, + VFE_START_INPUT_SOURCE_TESTGEN, + VFE_START_INPUT_SOURCE_AXI, + VFE_START_INPUT_SOURCE_INVALID +}; + +enum VFE_START_PIXEL_PATTERN { + VFE_BAYER_RGRGRG, + VFE_BAYER_GRGRGR, + VFE_BAYER_BGBGBG, + VFE_BAYER_GBGBGB, + VFE_YUV_YCbYCr, + VFE_YUV_YCrYCb, + VFE_YUV_CbYCrY, + VFE_YUV_CrYCbY +}; + +enum VFE_BUS_RD_INPUT_PIXEL_PATTERN { + VFE_BAYER_RAW, + VFE_YUV_INTERLEAVED, + VFE_YUV_PSEUDO_PLANAR_Y, + VFE_YUV_PSEUDO_PLANAR_CBCR +}; + +enum VFE_YUV_INPUT_COSITING_MODE { + VFE_YUV_COSITED, + VFE_YUV_INTERPOLATED +}; + + +/* 13*1 */ +#define VFE31_ROLL_OFF_INIT_TABLE_SIZE 13 +/* 13*16 */ +#define VFE31_ROLL_OFF_DELTA_TABLE_SIZE 208 + +#define VFE31_GAMMA_NUM_ENTRIES 64 + +#define VFE31_LA_TABLE_LENGTH 64 + +#define VFE31_HIST_TABLE_LENGTH 256 + +struct vfe_cmds_demosaic_abf { + uint8_t enable; + uint8_t forceOn; + uint8_t shift; + uint16_t lpThreshold; + uint16_t max; + uint16_t min; + uint8_t ratio; +}; + +struct vfe_cmds_demosaic_bpc { + uint8_t enable; + uint16_t fmaxThreshold; + uint16_t fminThreshold; + uint16_t redDiffThreshold; + uint16_t blueDiffThreshold; + uint16_t greenDiffThreshold; +}; + +struct vfe_cmd_demosaic_config { + uint8_t enable; + uint8_t slopeShift; + struct vfe_cmds_demosaic_abf abfConfig; + struct vfe_cmds_demosaic_bpc bpcConfig; +}; + +struct vfe_cmd_demosaic_bpc_update { + struct vfe_cmds_demosaic_bpc bpcUpdate; +}; + +struct vfe_cmd_demosaic_abf_update { + struct vfe_cmds_demosaic_abf abfUpdate; +}; + +struct vfe_cmd_white_balance_config { + uint8_t enable; + uint16_t ch2Gain; + uint16_t ch1Gain; + uint16_t ch0Gain; +}; + +enum VFE_COLOR_CORRECTION_COEF_QFACTOR { + COEF_IS_Q7_SIGNED, + COEF_IS_Q8_SIGNED, + COEF_IS_Q9_SIGNED, + COEF_IS_Q10_SIGNED +}; + +struct vfe_cmd_color_correction_config { + uint8_t enable; + enum VFE_COLOR_CORRECTION_COEF_QFACTOR coefQFactor; + int16_t C0; + int16_t C1; + int16_t C2; + int16_t C3; + int16_t C4; + int16_t C5; + int16_t C6; + int16_t C7; + int16_t C8; + int16_t K0; + int16_t K1; + int16_t K2; +}; + +#define VFE_LA_TABLE_LENGTH 64 + +struct vfe_cmd_la_config { + uint8_t enable; + int16_t table[VFE_LA_TABLE_LENGTH]; +}; + +#define VFE_GAMMA_TABLE_LENGTH 256 +enum VFE_RGB_GAMMA_TABLE_SELECT { + RGB_GAMMA_CH0_SELECTED, + RGB_GAMMA_CH1_SELECTED, + RGB_GAMMA_CH2_SELECTED, + RGB_GAMMA_CH0_CH1_SELECTED, + RGB_GAMMA_CH0_CH2_SELECTED, + RGB_GAMMA_CH1_CH2_SELECTED, + RGB_GAMMA_CH0_CH1_CH2_SELECTED +}; + +struct vfe_cmd_rgb_gamma_config { + uint8_t enable; + enum VFE_RGB_GAMMA_TABLE_SELECT channelSelect; + int16_t table[VFE_GAMMA_TABLE_LENGTH]; +}; + +struct vfe_cmd_chroma_enhan_config { + uint8_t enable; + int16_t am; + int16_t ap; + int16_t bm; + int16_t bp; + int16_t cm; + int16_t cp; + int16_t dm; + int16_t dp; + int16_t kcr; + int16_t kcb; + int16_t RGBtoYConversionV0; + int16_t RGBtoYConversionV1; + int16_t RGBtoYConversionV2; + uint8_t RGBtoYConversionOffset; +}; + +struct vfe_cmd_chroma_suppression_config { + uint8_t enable; + uint8_t m1; + uint8_t m3; + uint8_t n1; + uint8_t n3; + uint8_t nn1; + uint8_t mm1; +}; + +struct vfe_cmd_asf_config { + uint8_t enable; + uint8_t smoothFilterEnabled; + uint8_t sharpMode; + uint8_t smoothCoefCenter; + uint8_t smoothCoefSurr; + uint8_t normalizeFactor; + uint8_t sharpK1; + uint8_t sharpK2; + uint8_t sharpThreshE1; + int8_t sharpThreshE2; + int8_t sharpThreshE3; + int8_t sharpThreshE4; + int8_t sharpThreshE5; + int8_t filter1Coefficients[9]; + int8_t filter2Coefficients[9]; + uint8_t cropEnable; + uint16_t cropFirstPixel; + uint16_t cropLastPixel; + uint16_t cropFirstLine; + uint16_t cropLastLine; +}; + +struct vfe_cmd_asf_update { + uint8_t enable; + uint8_t smoothFilterEnabled; + uint8_t sharpMode; + uint8_t smoothCoefCenter; + uint8_t smoothCoefSurr; + uint8_t normalizeFactor; + uint8_t sharpK1; + uint8_t sharpK2; + uint8_t sharpThreshE1; + int8_t sharpThreshE2; + int8_t sharpThreshE3; + int8_t sharpThreshE4; + int8_t sharpThreshE5; + int8_t filter1Coefficients[9]; + int8_t filter2Coefficients[9]; + uint8_t cropEnable; +}; + +enum VFE_TEST_GEN_SYNC_EDGE { + VFE_TEST_GEN_SYNC_EDGE_ActiveHigh, + VFE_TEST_GEN_SYNC_EDGE_ActiveLow +}; + + +struct vfe_cmd_bus_pm_start { + uint8_t output2YWrPmEnable; + uint8_t output2CbcrWrPmEnable; + uint8_t output1YWrPmEnable; + uint8_t output1CbcrWrPmEnable; +}; + +struct vfe_frame_skip_counts { + uint32_t totalFrameCount; + uint32_t output1Count; + uint32_t output2Count; +}; + +enum VFE_AXI_RD_UNPACK_HBI_SEL { + VFE_AXI_RD_HBI_32_CLOCK_CYCLES, + VFE_AXI_RD_HBI_64_CLOCK_CYCLES, + VFE_AXI_RD_HBI_128_CLOCK_CYCLES, + VFE_AXI_RD_HBI_256_CLOCK_CYCLES, + VFE_AXI_RD_HBI_512_CLOCK_CYCLES, + VFE_AXI_RD_HBI_1024_CLOCK_CYCLES, + VFE_AXI_RD_HBI_2048_CLOCK_CYCLES, + VFE_AXI_RD_HBI_4096_CLOCK_CYCLES +}; + +enum VFE31_MESSAGE_ID { + MSG_ID_RESET_ACK, /* 0 */ + MSG_ID_START_ACK, + MSG_ID_STOP_ACK, + MSG_ID_UPDATE_ACK, + MSG_ID_OUTPUT_P, + MSG_ID_OUTPUT_T, + MSG_ID_OUTPUT_S, + MSG_ID_OUTPUT_V, + MSG_ID_SNAPSHOT_DONE, + MSG_ID_COMMON, + MSG_ID_EPOCH1, /* 10 */ + MSG_ID_EPOCH2, + MSG_ID_SYNC_TIMER0_DONE, + MSG_ID_SYNC_TIMER1_DONE, + MSG_ID_SYNC_TIMER2_DONE, + MSG_ID_ASYNC_TIMER0_DONE, + MSG_ID_ASYNC_TIMER1_DONE, + MSG_ID_ASYNC_TIMER2_DONE, + MSG_ID_ASYNC_TIMER3_DONE, + MSG_ID_AE_OVERFLOW, + MSG_ID_AF_OVERFLOW, /* 20 */ + MSG_ID_AWB_OVERFLOW, + MSG_ID_RS_OVERFLOW, + MSG_ID_CS_OVERFLOW, + MSG_ID_IHIST_OVERFLOW, + MSG_ID_SKIN_OVERFLOW, + MSG_ID_AXI_ERROR, + MSG_ID_CAMIF_OVERFLOW, + MSG_ID_VIOLATION, + MSG_ID_CAMIF_ERROR, + MSG_ID_BUS_OVERFLOW, /* 30 */ + MSG_ID_SOF_ACK, + MSG_ID_STOP_REC_ACK, +}; + +struct stats_buffer { + uint32_t aec; + uint32_t awb; + uint32_t af; + uint32_t ihist; + uint32_t rs; + uint32_t cs; + uint32_t skin; +}; + +struct vfe_msg_stats { + struct stats_buffer buff; + uint32_t frameCounter; + uint32_t status_bits; +}; + + +struct vfe_frame_bpc_info { + uint32_t greenDefectPixelCount; + uint32_t redBlueDefectPixelCount; +}; + +struct vfe_frame_asf_info { + uint32_t asfMaxEdge; + uint32_t asfHbiCount; +}; + +struct vfe_msg_camif_status { + uint8_t camifState; + uint32_t pixelCount; + uint32_t lineCount; +}; + + +struct vfe31_irq_status { + uint32_t vfeIrqStatus0; + uint32_t vfeIrqStatus1; + uint32_t camifStatus; + uint32_t demosaicStatus; + uint32_t asfMaxEdge; + uint32_t vfePingPongStatus; +}; + +struct vfe_msg_output { + uint8_t output_id; + uint32_t yBuffer; + uint32_t cbcrBuffer; + struct vfe_frame_bpc_info bpcInfo; + struct vfe_frame_asf_info asfInfo; + uint32_t frameCounter; +}; + +struct vfe_message { + enum VFE31_MESSAGE_ID _d; + union { + struct vfe_msg_output msgOut; + struct vfe_msg_stats msgStats; + struct vfe_msg_camif_status msgCamifError; + } _u; +}; + +/* New one for 7x30 */ +struct msm_vfe31_cmd { + int32_t id; + uint16_t length; + void *value; +}; + +#define V31_PREVIEW_AXI_FLAG 0x00000001 +#define V31_SNAPSHOT_AXI_FLAG (0x00000001<<1) + +struct vfe31_cmd_type { + uint16_t id; + uint32_t length; + uint32_t offset; + uint32_t flag; +}; + +struct vfe31_free_buf { + struct list_head node; + uint32_t paddr; + uint32_t y_off; + uint32_t cbcr_off; +}; + +struct vfe31_output_ch { + struct list_head free_buf_head; + spinlock_t free_buf_lock; + uint16_t output_fmt; + int8_t ch0; + int8_t ch1; + int8_t ch2; + uint32_t frame_drop_cnt; +}; + +/* no error irq in mask 0 */ +#define VFE31_IMASK_ERROR_ONLY_0 0x0 +/* when normal case, don't want to block error status. */ +/* bit 0-21 are error irq bits */ +#define VFE31_IMASK_ERROR_ONLY_1 0x003FFFFF +#define VFE31_IMASK_CAMIF_ERROR (0x00000001<<0) +#define VFE31_IMASK_STATS_CS_OVWR (0x00000001<<1) +#define VFE31_IMASK_STATS_IHIST_OVWR (0x00000001<<2) +#define VFE31_IMASK_REALIGN_BUF_Y_OVFL (0x00000001<<3) +#define VFE31_IMASK_REALIGN_BUF_CB_OVFL (0x00000001<<4) +#define VFE31_IMASK_REALIGN_BUF_CR_OVFL (0x00000001<<5) +#define VFE31_IMASK_VIOLATION (0x00000001<<6) +#define VFE31_IMASK_IMG_MAST_0_BUS_OVFL (0x00000001<<7) +#define VFE31_IMASK_IMG_MAST_1_BUS_OVFL (0x00000001<<8) +#define VFE31_IMASK_IMG_MAST_2_BUS_OVFL (0x00000001<<9) +#define VFE31_IMASK_IMG_MAST_3_BUS_OVFL (0x00000001<<10) +#define VFE31_IMASK_IMG_MAST_4_BUS_OVFL (0x00000001<<11) +#define VFE31_IMASK_IMG_MAST_5_BUS_OVFL (0x00000001<<12) +#define VFE31_IMASK_IMG_MAST_6_BUS_OVFL (0x00000001<<13) +#define VFE31_IMASK_STATS_AE_BUS_OVFL (0x00000001<<14) +#define VFE31_IMASK_STATS_AF_BUS_OVFL (0x00000001<<15) +#define VFE31_IMASK_STATS_AWB_BUS_OVFL (0x00000001<<16) +#define VFE31_IMASK_STATS_RS_BUS_OVFL (0x00000001<<17) +#define VFE31_IMASK_STATS_CS_BUS_OVFL (0x00000001<<18) +#define VFE31_IMASK_STATS_IHIST_BUS_OVFL (0x00000001<<19) +#define VFE31_IMASK_STATS_SKIN_BUS_OVFL (0x00000001<<20) +#define VFE31_IMASK_AXI_ERROR (0x00000001<<21) + +#define VFE_COM_STATUS 0x000FE000 + +struct vfe31_output_path { + uint16_t output_mode; /* bitmask */ + + struct vfe31_output_ch out0; /* preview and thumbnail */ + struct vfe31_output_ch out1; /* snapshot */ + struct vfe31_output_ch out2; /* video */ +}; + +struct vfe31_frame_extra { + uint32_t greenDefectPixelCount; + uint32_t redBlueDefectPixelCount; + + uint32_t asfMaxEdge; + uint32_t asfHbiCount; + + uint32_t yWrPmStats0; + uint32_t yWrPmStats1; + uint32_t cbcrWrPmStats0; + uint32_t cbcrWrPmStats1; + + uint32_t frameCounter; +}; + +#define VFE_DISABLE_ALL_IRQS 0 +#define VFE_CLEAR_ALL_IRQS 0xffffffff + +#define VFE_GLOBAL_RESET 0x00000004 +#define VFE_CGC_OVERRIDE 0x0000000C +#define VFE_MODULE_CFG 0x00000010 +#define VFE_CFG_OFF 0x00000014 +#define VFE_IRQ_CMD 0x00000018 +#define VFE_IRQ_MASK_0 0x0000001C +#define VFE_IRQ_MASK_1 0x00000020 +#define VFE_IRQ_CLEAR_0 0x00000024 +#define VFE_IRQ_CLEAR_1 0x00000028 +#define VFE_IRQ_STATUS_0 0x0000002C +#define VFE_IRQ_STATUS_1 0x00000030 +#define VFE_IRQ_COMP_MASK 0x00000034 +#define VFE_BUS_CMD 0x00000038 +#define VFE_BUS_PING_PONG_STATUS 0x00000180 +#define VFE_BUS_OPERATION_STATUS 0x00000184 + +#define VFE_BUS_IMAGE_MASTER_0_WR_PM_STATS_0 0x00000190 +#define VFE_BUS_IMAGE_MASTER_0_WR_PM_STATS_1 0x00000194 + +#define VFE_AXI_CMD 0x000001D8 +#define VFE_AXI_STATUS 0x000001DC +#define VFE_BUS_STATS_PING_PONG_BASE 0x000000F4 + +#define VFE_BUS_STATS_AEC_WR_PING_ADDR 0x000000F4 +#define VFE_BUS_STATS_AEC_WR_PONG_ADDR 0x000000F8 +#define VFE_BUS_STATS_AEC_UB_CFG 0x000000FC +#define VFE_BUS_STATS_AF_WR_PING_ADDR 0x00000100 +#define VFE_BUS_STATS_AF_WR_PONG_ADDR 0x00000104 +#define VFE_BUS_STATS_AF_UB_CFG 0x00000108 +#define VFE_BUS_STATS_AWB_WR_PING_ADDR 0x0000010C +#define VFE_BUS_STATS_AWB_WR_PONG_ADDR 0x00000110 +#define VFE_BUS_STATS_AWB_UB_CFG 0x00000114 +#define VFE_BUS_STATS_RS_WR_PING_ADDR 0x00000118 +#define VFE_BUS_STATS_RS_WR_PONG_ADDR 0x0000011C +#define VFE_BUS_STATS_RS_UB_CFG 0x00000120 + +#define VFE_BUS_STATS_CS_WR_PING_ADDR 0x00000124 +#define VFE_BUS_STATS_CS_WR_PONG_ADDR 0x00000128 +#define VFE_BUS_STATS_CS_UB_CFG 0x0000012C +#define VFE_BUS_STATS_HIST_WR_PING_ADDR 0x00000130 +#define VFE_BUS_STATS_HIST_WR_PONG_ADDR 0x00000134 +#define VFE_BUS_STATS_HIST_UB_CFG 0x00000138 +#define VFE_BUS_STATS_SKIN_WR_PING_ADDR 0x0000013C +#define VFE_BUS_STATS_SKIN_WR_PONG_ADDR 0x00000140 +#define VFE_BUS_STATS_SKIN_UB_CFG 0x00000144 +#define VFE_BUS_PM_CMD 0x00000188 +#define VFE_BUS_PM_CFG 0x0000018C +#define VFE_CAMIF_COMMAND 0x000001E0 +#define VFE_CAMIF_STATUS 0x00000204 +#define VFE_REG_UPDATE_CMD 0x00000260 +#define VFE_DEMUX_GAIN_0 0x00000288 +#define VFE_DEMUX_GAIN_1 0x0000028C +#define VFE_CHROMA_UP 0x0000035C +#define VFE_FRAMEDROP_ENC_Y_CFG 0x00000504 +#define VFE_FRAMEDROP_ENC_CBCR_CFG 0x00000508 +#define VFE_FRAMEDROP_ENC_Y_PATTERN 0x0000050C +#define VFE_FRAMEDROP_ENC_CBCR_PATTERN 0x00000510 +#define VFE_FRAMEDROP_VIEW_Y 0x00000514 +#define VFE_FRAMEDROP_VIEW_CBCR 0x00000518 +#define VFE_FRAMEDROP_VIEW_Y_PATTERN 0x0000051C +#define VFE_FRAMEDROP_VIEW_CBCR_PATTERN 0x00000520 +#define VFE_CLAMP_MAX 0x00000524 +#define VFE_CLAMP_MIN 0x00000528 +#define VFE_REALIGN_BUF 0x0000052C +#define VFE_STATS_CFG 0x00000530 +#define VFE_DMI_CFG 0x00000598 +#define VFE_DMI_ADDR 0x0000059C +#define VFE_DMI_DATA_LO 0x000005A4 +#define VFE_AXI_CFG 0x00000600 + +struct vfe_stats_control { + uint8_t ackPending; + uint32_t nextFrameAddrBuf; + uint32_t droppedStatsFrameCount; + uint32_t bufToRender; +}; + +struct vfe31_ctrl_type { + uint16_t operation_mode; /* streaming or snapshot */ + struct vfe31_output_path outpath; + + uint32_t vfeImaskCompositePacked; + + spinlock_t update_ack_lock; + spinlock_t io_lock; + + int8_t aec_ack_pending; + int8_t awb_ack_pending; + int8_t af_ack_pending; + int8_t ihist_ack_pending; + int8_t rs_ack_pending; + int8_t cs_ack_pending; + + struct msm_vfe_callback *resp; + uint32_t extlen; + void *extdata; + + int8_t start_ack_pending; + atomic_t stop_ack_pending; + int8_t reset_ack_pending; + int8_t update_ack_pending; + enum vfe_recording_state recording_state; + int8_t output0_available; + int8_t output1_available; + int8_t update_gamma; + int8_t update_luma; + spinlock_t tasklet_lock; + struct list_head tasklet_q; + int vfeirq; + void __iomem *vfebase; + void *syncdata; + + struct resource *vfemem; + struct resource *vfeio; + + uint32_t stats_comp; + uint32_t hfr_mode; + atomic_t vstate; + uint32_t vfe_capture_count; + uint32_t sync_timer_repeat_count; + uint32_t sync_timer_state; + uint32_t sync_timer_number; + + uint32_t vfeFrameId; + uint32_t output1Pattern; + uint32_t output1Period; + uint32_t output2Pattern; + uint32_t output2Period; + uint32_t vfeFrameSkipCount; + uint32_t vfeFrameSkipPeriod; + uint32_t status_bits; + struct vfe_stats_control afStatsControl; + struct vfe_stats_control awbStatsControl; + struct vfe_stats_control aecStatsControl; + struct vfe_stats_control ihistStatsControl; + struct vfe_stats_control rsStatsControl; + struct vfe_stats_control csStatsControl; + struct msm_camera_sensor_info *s_info; + struct vfe_message vMsgHold_Snap; + struct vfe_message vMsgHold_Thumb; + int8_t xbar_update_pending; + uint32_t xbar_cfg[2]; + spinlock_t xbar_lock; + uint32_t while_stopping_mask; +}; + +#define statsAeNum 0 +#define statsAfNum 1 +#define statsAwbNum 2 +#define statsRsNum 3 +#define statsCsNum 4 +#define statsIhistNum 5 +#define statsSkinNum 6 + +struct vfe_cmd_stats_ack{ + uint32_t nextStatsBuf; +}; + +#define VFE_STATS_BUFFER_COUNT 3 + +struct vfe_cmd_stats_buf{ + uint32_t statsBuf[VFE_STATS_BUFFER_COUNT]; +}; +#endif /* __MSM_VFE31_H__ */ diff --git a/drivers/media/video/msm/msm_vfe32.c b/drivers/media/video/msm/msm_vfe32.c new file mode 100644 index 00000000000..4ca62ce9107 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe32.c @@ -0,0 +1,3379 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm.h" +#include "msm_vfe32.h" +#include "msm_vpe1.h" + +atomic_t irq_cnt; + +#define CHECKED_COPY_FROM_USER(in) { \ + if (copy_from_user((in), (void __user *)cmd->value, \ + cmd->length)) { \ + rc = -EFAULT; \ + break; \ + } \ +} + +static struct vfe32_ctrl_type *vfe32_ctrl; +static struct msm_camera_io_clk camio_clk; +static void *vfe_syncdata; + +struct vfe32_isr_queue_cmd { + struct list_head list; + uint32_t vfeInterruptStatus0; + uint32_t vfeInterruptStatus1; + struct vfe_frame_asf_info vfeAsfFrameInfo; + struct vfe_frame_bpc_info vfeBpcFrameInfo; + struct vfe_msg_camif_status vfeCamifStatusLocal; +}; + +static struct vfe32_cmd_type vfe32_cmd[] = { +/* 0*/ {V32_DUMMY_0}, + {V32_SET_CLK}, + {V32_RESET}, + {V32_START}, + {V32_TEST_GEN_START}, +/* 5*/ {V32_OPERATION_CFG, V32_OPERATION_CFG_LEN}, + {V32_AXI_OUT_CFG, V32_AXI_OUT_LEN, V32_AXI_OUT_OFF, 0xFF}, + {V32_CAMIF_CFG, V32_CAMIF_LEN, V32_CAMIF_OFF, 0xFF}, + {V32_AXI_INPUT_CFG}, + {V32_BLACK_LEVEL_CFG, V32_BLACK_LEVEL_LEN, V32_BLACK_LEVEL_OFF, + 0xFF}, +/*10*/ {V32_ROLL_OFF_CFG, V32_ROLL_OFF_CFG_LEN, V32_ROLL_OFF_CFG_OFF, + 0xFF}, + {V32_DEMUX_CFG, V32_DEMUX_LEN, V32_DEMUX_OFF, 0xFF}, + {V32_FOV_CFG, V32_FOV_LEN, V32_FOV_OFF, 0xFF}, + {V32_MAIN_SCALER_CFG, V32_MAIN_SCALER_LEN, V32_MAIN_SCALER_OFF, + 0xFF}, + {V32_WB_CFG, V32_WB_LEN, V32_WB_OFF, 0xFF}, +/*15*/ {V32_COLOR_COR_CFG, V32_COLOR_COR_LEN, V32_COLOR_COR_OFF, 0xFF}, + {V32_RGB_G_CFG, V32_RGB_G_LEN, V32_RGB_G_OFF, 0xFF}, + {V32_LA_CFG, V32_LA_LEN, V32_LA_OFF, 0xFF }, + {V32_CHROMA_EN_CFG, V32_CHROMA_EN_LEN, V32_CHROMA_EN_OFF, 0xFF}, + {V32_CHROMA_SUP_CFG, V32_CHROMA_SUP_LEN, V32_CHROMA_SUP_OFF, + 0xFF}, +/*20*/ {V32_MCE_CFG, V32_MCE_LEN, V32_MCE_OFF, 0xFF}, + {V32_SK_ENHAN_CFG, V32_SCE_LEN, V32_SCE_OFF, 0xFF}, + {V32_ASF_CFG, V32_ASF_LEN, V32_ASF_OFF, 0xFF}, + {V32_S2Y_CFG, V32_S2Y_LEN, V32_S2Y_OFF, 0xFF}, + {V32_S2CbCr_CFG, V32_S2CbCr_LEN, V32_S2CbCr_OFF, 0xFF}, +/*25*/ {V32_CHROMA_SUBS_CFG, V32_CHROMA_SUBS_LEN, V32_CHROMA_SUBS_OFF, + 0xFF}, + {V32_OUT_CLAMP_CFG, V32_OUT_CLAMP_LEN, V32_OUT_CLAMP_OFF, + 0xFF}, + {V32_FRAME_SKIP_CFG, V32_FRAME_SKIP_LEN, V32_FRAME_SKIP_OFF, + 0xFF}, + {V32_DUMMY_1}, + {V32_DUMMY_2}, +/*30*/ {V32_DUMMY_3}, + {V32_UPDATE}, + {V32_BL_LVL_UPDATE, V32_BLACK_LEVEL_LEN, V32_BLACK_LEVEL_OFF, + 0xFF}, + {V32_DEMUX_UPDATE, V32_DEMUX_LEN, V32_DEMUX_OFF, 0xFF}, + {V32_FOV_UPDATE, V32_FOV_LEN, V32_FOV_OFF, 0xFF}, +/*35*/ {V32_MAIN_SCALER_UPDATE, V32_MAIN_SCALER_LEN, V32_MAIN_SCALER_OFF, + 0xFF}, + {V32_WB_UPDATE, V32_WB_LEN, V32_WB_OFF, 0xFF}, + {V32_COLOR_COR_UPDATE, V32_COLOR_COR_LEN, V32_COLOR_COR_OFF, + 0xFF}, + {V32_RGB_G_UPDATE, V32_RGB_G_LEN, V32_CHROMA_EN_OFF, 0xFF}, + {V32_LA_UPDATE, V32_LA_LEN, V32_LA_OFF, 0xFF }, +/*40*/ {V32_CHROMA_EN_UPDATE, V32_CHROMA_EN_LEN, V32_CHROMA_EN_OFF, + 0xFF}, + {V32_CHROMA_SUP_UPDATE, V32_CHROMA_SUP_LEN, V32_CHROMA_SUP_OFF, + 0xFF}, + {V32_MCE_UPDATE, V32_MCE_LEN, V32_MCE_OFF, 0xFF}, + {V32_SK_ENHAN_UPDATE, V32_SCE_LEN, V32_SCE_OFF, 0xFF}, + {V32_S2CbCr_UPDATE, V32_S2CbCr_LEN, V32_S2CbCr_OFF, 0xFF}, +/*45*/ {V32_S2Y_UPDATE, V32_S2Y_LEN, V32_S2Y_OFF, 0xFF}, + {V32_ASF_UPDATE, V32_ASF_UPDATE_LEN, V32_ASF_OFF, 0xFF}, + {V32_FRAME_SKIP_UPDATE}, + {V32_CAMIF_FRAME_UPDATE}, + {V32_STATS_AF_UPDATE, V32_STATS_AF_LEN, V32_STATS_AF_OFF}, +/*50*/ {V32_STATS_AE_UPDATE, V32_STATS_AE_LEN, V32_STATS_AE_OFF}, + {V32_STATS_AWB_UPDATE, V32_STATS_AWB_LEN, V32_STATS_AWB_OFF}, + {V32_STATS_RS_UPDATE, V32_STATS_RS_LEN, V32_STATS_RS_OFF}, + {V32_STATS_CS_UPDATE, V32_STATS_CS_LEN, V32_STATS_CS_OFF}, + {V32_STATS_SKIN_UPDATE}, +/*55*/ {V32_STATS_IHIST_UPDATE, V32_STATS_IHIST_LEN, V32_STATS_IHIST_OFF}, + {V32_DUMMY_4}, + {V32_EPOCH1_ACK}, + {V32_EPOCH2_ACK}, + {V32_START_RECORDING}, +/*60*/ {V32_STOP_RECORDING}, + {V32_DUMMY_5}, + {V32_DUMMY_6}, + {V32_CAPTURE, V32_CAPTURE_LEN, 0xFF}, + {V32_DUMMY_7}, +/*65*/ {V32_STOP}, + {V32_GET_HW_VERSION}, + {V32_GET_FRAME_SKIP_COUNTS}, + {V32_OUTPUT1_BUFFER_ENQ}, + {V32_OUTPUT2_BUFFER_ENQ}, +/*70*/ {V32_OUTPUT3_BUFFER_ENQ}, + {V32_JPEG_OUT_BUF_ENQ}, + {V32_RAW_OUT_BUF_ENQ}, + {V32_RAW_IN_BUF_ENQ}, + {V32_STATS_AF_ENQ}, +/*75*/ {V32_STATS_AE_ENQ}, + {V32_STATS_AWB_ENQ}, + {V32_STATS_RS_ENQ}, + {V32_STATS_CS_ENQ}, + {V32_STATS_SKIN_ENQ}, +/*80*/ {V32_STATS_IHIST_ENQ}, + {V32_DUMMY_8}, + {V32_JPEG_ENC_CFG}, + {V32_DUMMY_9}, + {V32_STATS_AF_START, V32_STATS_AF_LEN, V32_STATS_AF_OFF}, +/*85*/ {V32_STATS_AF_STOP}, + {V32_STATS_AE_START, V32_STATS_AE_LEN, V32_STATS_AE_OFF}, + {V32_STATS_AE_STOP}, + {V32_STATS_AWB_START, V32_STATS_AWB_LEN, V32_STATS_AWB_OFF}, + {V32_STATS_AWB_STOP}, +/*90*/ {V32_STATS_RS_START, V32_STATS_RS_LEN, V32_STATS_RS_OFF}, + {V32_STATS_RS_STOP}, + {V32_STATS_CS_START, V32_STATS_CS_LEN, V32_STATS_CS_OFF}, + {V32_STATS_CS_STOP}, + {V32_STATS_SKIN_START}, +/*95*/ {V32_STATS_SKIN_STOP}, + {V32_STATS_IHIST_START, + V32_STATS_IHIST_LEN, V32_STATS_IHIST_OFF}, + {V32_STATS_IHIST_STOP}, + {V32_DUMMY_10}, + {V32_SYNC_TIMER_SETTING, V32_SYNC_TIMER_LEN, + V32_SYNC_TIMER_OFF}, +/*100*/ {V32_ASYNC_TIMER_SETTING, V32_ASYNC_TIMER_LEN, V32_ASYNC_TIMER_OFF}, + {V32_LIVESHOT}, + {V32_LA_SETUP}, + {V32_LINEARIZATION, V32_LINEARIZATION_LEN1, + V32_LINEARIZATION_OFF1}, + {V32_DEMOSAICV3}, +/*105*/ {V32_DEMOSAICV3_ABCC_CFG}, + {V32_DEMOSAICV3_DBCC_CFG, V32_DEMOSAICV3_DBCC_LEN, + V32_DEMOSAICV3_DBCC_OFF}, + {V32_DEMOSAICV3_DBPC_CFG}, + {V32_DEMOSAICV3_ABF_CFG}, + {V32_DEMOSAICV3_ABCC_UPDATE}, + {V32_DEMOSAICV3_DBCC_UPDATE, V32_DEMOSAICV3_DBCC_LEN, + V32_DEMOSAICV3_DBCC_OFF}, + {V32_DEMOSAICV3_DBPC_UPDATE}, +}; + +uint32_t vfe32_AXI_WM_CFG[] = { + 0x0000004C, + 0x00000064, + 0x0000007C, + 0x00000094, + 0x000000AC, + 0x000000C4, + 0x000000DC, +}; + +static const char * const vfe32_general_cmd[] = { + "DUMMY_0", /* 0 */ + "SET_CLK", + "RESET", + "START", + "TEST_GEN_START", + "OPERATION_CFG", /* 5 */ + "AXI_OUT_CFG", + "CAMIF_CFG", + "AXI_INPUT_CFG", + "BLACK_LEVEL_CFG", + "ROLL_OFF_CFG", /* 10 */ + "DEMUX_CFG", + "FOV_CFG", + "MAIN_SCALER_CFG", + "WB_CFG", + "COLOR_COR_CFG", /* 15 */ + "RGB_G_CFG", + "LA_CFG", + "CHROMA_EN_CFG", + "CHROMA_SUP_CFG", + "MCE_CFG", /* 20 */ + "SK_ENHAN_CFG", + "ASF_CFG", + "S2Y_CFG", + "S2CbCr_CFG", + "CHROMA_SUBS_CFG", /* 25 */ + "OUT_CLAMP_CFG", + "FRAME_SKIP_CFG", + "DUMMY_1", + "DUMMY_2", + "DUMMY_3", /* 30 */ + "UPDATE", + "BL_LVL_UPDATE", + "DEMUX_UPDATE", + "FOV_UPDATE", + "MAIN_SCALER_UPDATE", /* 35 */ + "WB_UPDATE", + "COLOR_COR_UPDATE", + "RGB_G_UPDATE", + "LA_UPDATE", + "CHROMA_EN_UPDATE", /* 40 */ + "CHROMA_SUP_UPDATE", + "MCE_UPDATE", + "SK_ENHAN_UPDATE", + "S2CbCr_UPDATE", + "S2Y_UPDATE", /* 45 */ + "ASF_UPDATE", + "FRAME_SKIP_UPDATE", + "CAMIF_FRAME_UPDATE", + "STATS_AF_UPDATE", + "STATS_AE_UPDATE", /* 50 */ + "STATS_AWB_UPDATE", + "STATS_RS_UPDATE", + "STATS_CS_UPDATE", + "STATS_SKIN_UPDATE", + "STATS_IHIST_UPDATE", /* 55 */ + "DUMMY_4", + "EPOCH1_ACK", + "EPOCH2_ACK", + "START_RECORDING", + "STOP_RECORDING", /* 60 */ + "DUMMY_5", + "DUMMY_6", + "CAPTURE", + "DUMMY_7", + "STOP", /* 65 */ + "GET_HW_VERSION", + "GET_FRAME_SKIP_COUNTS", + "OUTPUT1_BUFFER_ENQ", + "OUTPUT2_BUFFER_ENQ", + "OUTPUT3_BUFFER_ENQ", /* 70 */ + "JPEG_OUT_BUF_ENQ", + "RAW_OUT_BUF_ENQ", + "RAW_IN_BUF_ENQ", + "STATS_AF_ENQ", + "STATS_AE_ENQ", /* 75 */ + "STATS_AWB_ENQ", + "STATS_RS_ENQ", + "STATS_CS_ENQ", + "STATS_SKIN_ENQ", + "STATS_IHIST_ENQ", /* 80 */ + "DUMMY_8", + "JPEG_ENC_CFG", + "DUMMY_9", + "STATS_AF_START", + "STATS_AF_STOP", /* 85 */ + "STATS_AE_START", + "STATS_AE_STOP", + "STATS_AWB_START", + "STATS_AWB_STOP", + "STATS_RS_START", /* 90 */ + "STATS_RS_STOP", + "STATS_CS_START", + "STATS_CS_STOP", + "STATS_SKIN_START", + "STATS_SKIN_STOP", /* 95 */ + "STATS_IHIST_START", + "STATS_IHIST_STOP", + "DUMMY_10", + "SYNC_TIMER_SETTING", + "ASYNC_TIMER_SETTING", /* 100 */ + "LIVESHOT", + "LA_SETUP", + "LINEARIZATION", + "DEMOSAICV3", + "DEMOSAICV3_ABCC_CFG", /* 105 */ + "DEMOSAICV3_DBCC_CFG", + "DEMOSAICV3_DBPC_CFG", + "DEMOSAICV3_ABF_CFG", /* 108 */ + "DEMOSAICV3_ABCC_UPDATE", + "DEMOSAICV3_DBCC_UPDATE", + "DEMOSAICV3_DBPC_UPDATE", + "EZTUNE_CFG", +}; + +static void vfe_addr_convert(struct msm_vfe_phy_info *pinfo, + enum vfe_resp_msg type, void *data, void **ext, int32_t *elen) +{ + uint8_t outid; + switch (type) { + case VFE_MSG_OUTPUT_T: + case VFE_MSG_OUTPUT_P: + case VFE_MSG_OUTPUT_S: + case VFE_MSG_OUTPUT_V: + pinfo->output_id = + ((struct vfe_message *)data)->_u.msgOut.output_id; + + switch (type) { + case VFE_MSG_OUTPUT_P: + outid = OUTPUT_TYPE_P; + break; + case VFE_MSG_OUTPUT_V: + outid = OUTPUT_TYPE_V; + break; + case VFE_MSG_OUTPUT_T: + outid = OUTPUT_TYPE_T; + break; + case VFE_MSG_OUTPUT_S: + outid = OUTPUT_TYPE_S; + break; + default: + outid = 0xff; + break; + } + pinfo->output_id = outid; + pinfo->y_phy = + ((struct vfe_message *)data)->_u.msgOut.yBuffer; + pinfo->cbcr_phy = + ((struct vfe_message *)data)->_u.msgOut.cbcrBuffer; + + pinfo->frame_id = + ((struct vfe_message *)data)->_u.msgOut.frameCounter; + + ((struct vfe_msg_output *)(vfe32_ctrl->extdata))->bpcInfo = + ((struct vfe_message *)data)->_u.msgOut.bpcInfo; + ((struct vfe_msg_output *)(vfe32_ctrl->extdata))->asfInfo = + ((struct vfe_message *)data)->_u.msgOut.asfInfo; + ((struct vfe_msg_output *)(vfe32_ctrl->extdata))->frameCounter = + ((struct vfe_message *)data)->_u.msgOut.frameCounter; + *ext = vfe32_ctrl->extdata; + *elen = vfe32_ctrl->extlen; + break; + case VFE_MSG_STATS_AF: + case VFE_MSG_STATS_AEC: + case VFE_MSG_STATS_AWB: + case VFE_MSG_STATS_IHIST: + case VFE_MSG_STATS_RS: + case VFE_MSG_STATS_CS: + pinfo->sbuf_phy = + ((struct vfe_message *)data)->_u.msgStats.buffer; + + pinfo->frame_id = + ((struct vfe_message *)data)->_u.msgStats.frameCounter; + + break; + default: + break; + } /* switch */ +} + +static void vfe32_proc_ops(enum VFE32_MESSAGE_ID id, void *msg, size_t len) +{ + struct msm_vfe_resp *rp; + + rp = msm_isp_sync_alloc(sizeof(struct msm_vfe_resp), + vfe32_ctrl->syncdata, GFP_ATOMIC); + if (!rp) { + CDBG("rp: cannot allocate buffer\n"); + return; + } + CDBG("vfe32_proc_ops, msgId = %d\n", id); + rp->evt_msg.type = MSM_CAMERA_MSG; + rp->evt_msg.msg_id = id; + rp->evt_msg.len = len; + rp->evt_msg.data = msg; + + switch (rp->evt_msg.msg_id) { + case MSG_ID_SNAPSHOT_DONE: + rp->type = VFE_MSG_SNAPSHOT; + break; + + case MSG_ID_OUTPUT_P: + rp->type = VFE_MSG_OUTPUT_P; + vfe_addr_convert(&(rp->phy), VFE_MSG_OUTPUT_P, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_OUTPUT_T: + rp->type = VFE_MSG_OUTPUT_T; + vfe_addr_convert(&(rp->phy), VFE_MSG_OUTPUT_T, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_OUTPUT_S: + rp->type = VFE_MSG_OUTPUT_S; + vfe_addr_convert(&(rp->phy), VFE_MSG_OUTPUT_S, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_OUTPUT_V: + rp->type = VFE_MSG_OUTPUT_V; + vfe_addr_convert(&(rp->phy), VFE_MSG_OUTPUT_V, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_STATS_AF: + rp->type = VFE_MSG_STATS_AF; + vfe_addr_convert(&(rp->phy), VFE_MSG_STATS_AF, + rp->evt_msg.data, NULL, NULL); + break; + + case MSG_ID_STATS_AWB: + rp->type = VFE_MSG_STATS_AWB; + vfe_addr_convert(&(rp->phy), VFE_MSG_STATS_AWB, + rp->evt_msg.data, NULL, NULL); + break; + + case MSG_ID_STATS_AEC: + rp->type = VFE_MSG_STATS_AEC; + vfe_addr_convert(&(rp->phy), VFE_MSG_STATS_AEC, + rp->evt_msg.data, NULL, NULL); + break; + + case MSG_ID_STATS_SKIN: + rp->type = VFE_MSG_STATS_SKIN; + vfe_addr_convert(&(rp->phy), VFE_MSG_STATS_SKIN, + rp->evt_msg.data, NULL, NULL); + break; + + case MSG_ID_STATS_IHIST: + rp->type = VFE_MSG_STATS_IHIST; + vfe_addr_convert(&(rp->phy), VFE_MSG_STATS_IHIST, + rp->evt_msg.data, NULL, NULL); + break; + + case MSG_ID_STATS_RS: + rp->type = VFE_MSG_STATS_RS; + vfe_addr_convert(&(rp->phy), VFE_MSG_STATS_RS, + rp->evt_msg.data, NULL, NULL); + break; + + case MSG_ID_STATS_CS: + rp->type = VFE_MSG_STATS_CS; + vfe_addr_convert(&(rp->phy), VFE_MSG_STATS_CS, + rp->evt_msg.data, NULL, NULL); + break; + + case MSG_ID_SYNC_TIMER0_DONE: + rp->type = VFE_MSG_SYNC_TIMER0; + break; + + case MSG_ID_SYNC_TIMER1_DONE: + rp->type = VFE_MSG_SYNC_TIMER1; + break; + + case MSG_ID_SYNC_TIMER2_DONE: + rp->type = VFE_MSG_SYNC_TIMER2; + break; + + default: + rp->type = VFE_MSG_GENERAL; + break; + } + + /* save the frame id.*/ + rp->evt_msg.frame_id = rp->phy.frame_id; + + v4l2_subdev_notify(vfe32_ctrl->subdev, NOTIFY_VFE_MSG_EVT, rp); +} + +static void vfe_send_outmsg(uint8_t msgid, uint32_t pyaddr, + uint32_t pcbcraddr) +{ + struct vfe_message msg; + uint8_t outid; + + msg._d = msgid; /* now the output mode is redundnat. */ + + switch (msgid) { + case MSG_ID_OUTPUT_P: + outid = OUTPUT_TYPE_P; + break; + case MSG_ID_OUTPUT_V: + outid = OUTPUT_TYPE_V; + break; + case MSG_ID_OUTPUT_T: + outid = OUTPUT_TYPE_T; + break; + case MSG_ID_OUTPUT_S: + outid = OUTPUT_TYPE_S; + break; + default: + outid = 0xff; /* -1 for error condition.*/ + break; + } + msg._u.msgOut.output_id = msgid; + msg._u.msgOut.yBuffer = pyaddr; + msg._u.msgOut.cbcrBuffer = pcbcraddr; + vfe32_proc_ops(msgid, &msg, sizeof(struct vfe_message)); + return; +} + +static void vfe32_stop(void) +{ + uint8_t axiBusyFlag = true; + unsigned long flags; + + atomic_set(&vfe32_ctrl->vstate, 0); + + /* for reset hw modules, and send msg when reset_irq comes.*/ + spin_lock_irqsave(&vfe32_ctrl->stop_flag_lock, flags); + vfe32_ctrl->stop_ack_pending = TRUE; + spin_unlock_irqrestore(&vfe32_ctrl->stop_flag_lock, flags); + + /* disable all interrupts. */ + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_io_w(VFE_CLEAR_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_io_w(VFE_CLEAR_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_CLEAR_1); + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, + vfe32_ctrl->vfebase + VFE_IRQ_CMD); + + /* in either continuous or snapshot mode, stop command can be issued + * at any time. stop camif immediately. */ + msm_io_w(CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe32_ctrl->vfebase + VFE_CAMIF_COMMAND); + wmb(); + /* axi halt command. */ + msm_io_w(AXI_HALT, + vfe32_ctrl->vfebase + VFE_AXI_CMD); + wmb(); + while (axiBusyFlag) { + if (msm_io_r(vfe32_ctrl->vfebase + VFE_AXI_STATUS) & 0x1) + axiBusyFlag = false; + } + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(AXI_HALT_CLEAR, + vfe32_ctrl->vfebase + VFE_AXI_CMD); + + /* after axi halt, then ok to apply global reset. */ + /* enable reset_ack and async timer interrupt only while + stopping the pipeline.*/ + msm_io_w(0xf0000000, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_io_w(VFE_IMASK_WHILE_STOPPING_1, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(VFE_RESET_UPON_STOP_CMD, + vfe32_ctrl->vfebase + VFE_GLOBAL_RESET); +} + +static int vfe32_enqueue_free_buf(struct vfe32_output_ch *outch, + uint32_t paddr, uint32_t y_off, uint32_t cbcr_off) +{ + struct vfe32_free_buf *free_buf = NULL; + unsigned long flags = 0; + free_buf = kmalloc(sizeof(struct vfe32_free_buf), GFP_KERNEL); + if (!free_buf) + return -ENOMEM; + + spin_lock_irqsave(&outch->free_buf_lock, flags); + free_buf->paddr = paddr; + free_buf->y_off = y_off; + free_buf->cbcr_off = cbcr_off; + list_add_tail(&free_buf->node, &outch->free_buf_queue); + CDBG("%s: free_buf paddr = 0x%x, y_off = %d, cbcr_off = %d\n", + __func__, free_buf->paddr, free_buf->y_off, + free_buf->cbcr_off); + spin_unlock_irqrestore(&outch->free_buf_lock, flags); + return 0; +} + +static struct vfe32_free_buf *vfe32_dequeue_free_buf( + struct vfe32_output_ch *outch) +{ + unsigned long flags = 0; + struct vfe32_free_buf *free_buf = NULL; + spin_lock_irqsave(&outch->free_buf_lock, flags); + if (!list_empty(&outch->free_buf_queue)) { + free_buf = list_first_entry(&outch->free_buf_queue, + struct vfe32_free_buf, node); + if (free_buf) + list_del_init(&free_buf->node); + } + spin_unlock_irqrestore(&outch->free_buf_lock, flags); + return free_buf; +} + +static void vfe32_reset_free_buf_queue( + struct vfe32_output_ch *outch) +{ + unsigned long flags = 0; + struct vfe32_free_buf *free_buf = NULL; + spin_lock_irqsave(&outch->free_buf_lock, flags); + while (!list_empty(&outch->free_buf_queue)) { + free_buf = list_first_entry(&outch->free_buf_queue, + struct vfe32_free_buf, node); + if (free_buf) { + list_del_init(&free_buf->node); + kfree(free_buf); + } + } + spin_unlock_irqrestore(&outch->free_buf_lock, flags); +} + +static void vfe32_init_free_buf_queues(void) +{ + INIT_LIST_HEAD(&vfe32_ctrl->outpath.out0.free_buf_queue); + INIT_LIST_HEAD(&vfe32_ctrl->outpath.out1.free_buf_queue); + INIT_LIST_HEAD(&vfe32_ctrl->outpath.out2.free_buf_queue); + spin_lock_init(&vfe32_ctrl->outpath.out0.free_buf_lock); + spin_lock_init(&vfe32_ctrl->outpath.out1.free_buf_lock); + spin_lock_init(&vfe32_ctrl->outpath.out2.free_buf_lock); +} + +static void vfe32_reset_free_buf_queues(void) +{ + vfe32_reset_free_buf_queue(&vfe32_ctrl->outpath.out0); + vfe32_reset_free_buf_queue(&vfe32_ctrl->outpath.out1); + vfe32_reset_free_buf_queue(&vfe32_ctrl->outpath.out2); +} + +static int vfe32_config_axi(int mode, struct axidata *ad, uint32_t *ao) +{ + int ret; + int i; + uint32_t *p, *p1, *p2; + int32_t *ch_info; + struct vfe32_output_ch *outp1, *outp2; + struct msm_pmem_region *regp1 = NULL; + struct msm_pmem_region *regp2 = NULL; + + outp1 = NULL; + outp2 = NULL; + + p = ao + 2; + + /* Update the corresponding write masters for each output*/ + ch_info = ao + V32_AXI_CFG_LEN; + vfe32_ctrl->outpath.out0.ch0 = 0x0000FFFF & *ch_info; + vfe32_ctrl->outpath.out0.ch1 = 0x0000FFFF & (*ch_info++ >> 16); + vfe32_ctrl->outpath.out0.ch2 = 0x0000FFFF & *ch_info++; + vfe32_ctrl->outpath.out1.ch0 = 0x0000FFFF & *ch_info; + vfe32_ctrl->outpath.out1.ch1 = 0x0000FFFF & (*ch_info++ >> 16); + vfe32_ctrl->outpath.out1.ch2 = 0x0000FFFF & *ch_info++; + vfe32_ctrl->outpath.out2.ch0 = 0x0000FFFF & *ch_info; + vfe32_ctrl->outpath.out2.ch1 = 0x0000FFFF & (*ch_info++ >> 16); + vfe32_ctrl->outpath.out2.ch2 = 0x0000FFFF & *ch_info++; + + CDBG("vfe32_config_axi: mode = %d, bufnum1 = %d, bufnum2 = %d\n", + mode, ad->bufnum1, ad->bufnum2); + + switch (mode) { + + case OUTPUT_2: { + if (ad->bufnum2 < 3) + return -EINVAL; + regp1 = &(ad->region[ad->bufnum1]); + outp1 = &(vfe32_ctrl->outpath.out0); + vfe32_ctrl->outpath.output_mode |= VFE32_OUTPUT_MODE_PT; + + for (i = 0; i < 2; i++) { + p1 = ao + 6 + i; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.y_off); + + p1 = ao + 12 + i; /* wm1 for cbcr */ + *p1 = (regp1->paddr + regp1->info.cbcr_off); + regp1++; + } + for (i = 2; i < ad->bufnum2; i++) { + ret = vfe32_enqueue_free_buf(outp1, regp1->paddr, + regp1->info.y_off, regp1->info.cbcr_off); + if (ret < 0) + return ret; + regp1++; + } + } + break; + + case OUTPUT_1_AND_2: + /* use wm0& 4 for thumbnail, wm1&5 for main image.*/ + if ((ad->bufnum1 < 1) || (ad->bufnum2 < 1)) + return -EINVAL; + vfe32_ctrl->outpath.output_mode |= + VFE32_OUTPUT_MODE_S; /* main image.*/ + vfe32_ctrl->outpath.output_mode |= + VFE32_OUTPUT_MODE_PT; /* thumbnail. */ + + regp1 = &(ad->region[0]); /* this is thumbnail buffer. */ + /* this is main image buffer. */ + regp2 = &(ad->region[ad->bufnum1]); + outp1 = &(vfe32_ctrl->outpath.out0); + outp2 = &(vfe32_ctrl->outpath.out1); /* snapshot */ + + /* Parse the buffers!!! */ + if (ad->bufnum2 == 1) { /* assuming bufnum1 = bufnum2 */ + p1 = ao + 6; /* wm0 ping */ + *p1++ = (regp1->paddr + regp1->info.y_off); + /* this is to duplicate ping address to pong.*/ + *p1 = (regp1->paddr + regp1->info.y_off); + p1 = ao + 30; /* wm4 ping */ + *p1++ = (regp1->paddr + regp1->info.cbcr_off); + /* this is to duplicate ping address to pong.*/ + *p1 = (regp1->paddr + regp1->info.cbcr_off); + p1 = ao + 12; /* wm1 ping */ + *p1++ = (regp2->paddr + regp2->info.y_off); + /* pong = ping,*/ + *p1 = (regp2->paddr + regp2->info.y_off); + p1 = ao + 36; /* wm5 */ + *p1++ = (regp2->paddr + regp2->info.cbcr_off); + *p1 = (regp2->paddr + regp2->info.cbcr_off); + + } else { /* more than one snapshot */ + /* first fill ping & pong */ + for (i = 0; i < 2; i++) { + p1 = ao + 6 + i; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.y_off); + p1 = ao + 30 + i; /* wm4 for cbcr */ + *p1 = (regp1->paddr + regp1->info.cbcr_off); + regp1++; + } + + for (i = 0; i < 2; i++) { + p2 = ao + 12 + i; /* wm1 for y */ + *p2 = (regp2->paddr + regp2->info.y_off); + p2 = ao + 36 + i; /* wm5 for cbcr */ + *p2 = (regp2->paddr + regp2->info.cbcr_off); + regp2++; + } + + for (i = 2; i < ad->bufnum1; i++) { + ret = vfe32_enqueue_free_buf(outp1, + regp1->paddr, + regp1->info.y_off, + regp1->info.cbcr_off); + if (ret < 0) + return ret; + regp1++; + } + for (i = 2; i < ad->bufnum2; i++) { + ret = vfe32_enqueue_free_buf(outp2, + regp2->paddr, + regp2->info.y_off, + regp2->info.cbcr_off); + if (ret < 0) + return ret; + regp2++; + } + } + break; + + case OUTPUT_1_AND_3: + /* use wm0& 4 for preview, wm1&5 for video.*/ + if ((ad->bufnum1 < 2) || (ad->bufnum2 < 2)) + return -EINVAL; + + vfe32_ctrl->outpath.output_mode |= + VFE32_OUTPUT_MODE_V; /* video*/ + vfe32_ctrl->outpath.output_mode |= + VFE32_OUTPUT_MODE_PT; /* preview */ + + regp1 = &(ad->region[0]); /* this is preview buffer. */ + regp2 = &(ad->region[ad->bufnum1]);/* this is video buffer. */ + outp1 = &(vfe32_ctrl->outpath.out0); /* preview */ + outp2 = &(vfe32_ctrl->outpath.out2); /* video */ + + + for (i = 0; i < 2; i++) { + p1 = ao + 6 + i; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.y_off); + + p1 = ao + 30 + i; /* wm1 for cbcr */ + *p1 = (regp1->paddr + regp1->info.cbcr_off); + regp1++; + } + + for (i = 0; i < 2; i++) { + p2 = ao + 12 + i; /* wm0 for y */ + *p2 = (regp2->paddr + regp2->info.y_off); + + p2 = ao + 36 + i; /* wm1 for cbcr */ + *p2 = (regp2->paddr + regp2->info.cbcr_off); + regp2++; + } + for (i = 2; i < ad->bufnum1; i++) { + ret = vfe32_enqueue_free_buf(outp1, regp1->paddr, + regp1->info.y_off, + regp1->info.cbcr_off); + if (ret < 0) + return ret; + regp1++; + } + for (i = 2; i < ad->bufnum2; i++) { + ret = vfe32_enqueue_free_buf(outp2, regp2->paddr, + regp2->info.y_off, + regp2->info.cbcr_off); + if (ret < 0) + return ret; + regp2++; + } + break; + case CAMIF_TO_AXI_VIA_OUTPUT_2: { /* use wm0 only */ + if (ad->bufnum2 < 1) + return -EINVAL; + CDBG("config axi for raw snapshot.\n"); + vfe32_ctrl->outpath.out1.ch0 = 0; /* raw */ + regp1 = &(ad->region[ad->bufnum1]); + vfe32_ctrl->outpath.output_mode |= VFE32_OUTPUT_MODE_S; + p1 = ao + 6; /* wm0 for y */ + *p1 = (regp1->paddr + regp1->info.y_off); + } + break; + default: + break; + } + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[V32_AXI_OUT_CFG].offset, + ao, vfe32_cmd[V32_AXI_OUT_CFG].length - V32_AXI_CH_INF_LEN); + return 0; +} + +static void vfe32_reset_internal_variables(void) +{ + unsigned long flags; + vfe32_ctrl->vfeImaskCompositePacked = 0; + /* state control variables */ + vfe32_ctrl->start_ack_pending = FALSE; + atomic_set(&irq_cnt, 0); + + spin_lock_irqsave(&vfe32_ctrl->stop_flag_lock, flags); + vfe32_ctrl->stop_ack_pending = FALSE; + spin_unlock_irqrestore(&vfe32_ctrl->stop_flag_lock, flags); + + vfe32_ctrl->reset_ack_pending = FALSE; + + spin_lock_irqsave(&vfe32_ctrl->update_ack_lock, flags); + vfe32_ctrl->update_ack_pending = FALSE; + spin_unlock_irqrestore(&vfe32_ctrl->update_ack_lock, flags); + + vfe32_ctrl->req_stop_video_rec = FALSE; + vfe32_ctrl->req_start_video_rec = FALSE; + + atomic_set(&vfe32_ctrl->vstate, 0); + + /* 0 for continuous mode, 1 for snapshot mode */ + vfe32_ctrl->operation_mode = 0; + vfe32_ctrl->outpath.output_mode = 0; + vfe32_ctrl->vfe_capture_count = 0; + + /* this is unsigned 32 bit integer. */ + vfe32_ctrl->vfeFrameId = 0; + /* Stats control variables. */ + memset(&(vfe32_ctrl->afStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe32_ctrl->awbStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe32_ctrl->aecStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe32_ctrl->ihistStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe32_ctrl->rsStatsControl), 0, + sizeof(struct vfe_stats_control)); + + memset(&(vfe32_ctrl->csStatsControl), 0, + sizeof(struct vfe_stats_control)); +} + +static void vfe32_reset(void) +{ + uint32_t vfe_version; + vfe32_reset_free_buf_queues(); + vfe32_reset_internal_variables(); + vfe_version = msm_io_r(vfe32_ctrl->vfebase); + CDBG("vfe_version = 0x%x\n", vfe_version); + /* disable all interrupts. vfeImaskLocal is also reset to 0 + * to begin with. */ + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_0); + + msm_io_w(VFE_DISABLE_ALL_IRQS, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* clear all pending interrupts*/ + msm_io_w(VFE_CLEAR_ALL_IRQS, vfe32_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_io_w(VFE_CLEAR_ALL_IRQS, vfe32_ctrl->vfebase + VFE_IRQ_CLEAR_1); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, vfe32_ctrl->vfebase + VFE_IRQ_CMD); + + /* enable reset_ack interrupt. */ + msm_io_w(VFE_IMASK_WHILE_STOPPING_1, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_1); + + /* Write to VFE_GLOBAL_RESET_CMD to reset the vfe hardware. Once reset + * is done, hardware interrupt will be generated. VFE ist processes + * the interrupt to complete the function call. Note that the reset + * function is synchronous. */ + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(VFE_RESET_UPON_RESET_CMD, + vfe32_ctrl->vfebase + VFE_GLOBAL_RESET); +} + +static int vfe32_operation_config(uint32_t *cmd) +{ + uint32_t *p = cmd; + + vfe32_ctrl->operation_mode = *p; + vfe32_ctrl->stats_comp = *(++p); + + msm_io_w(*(++p), vfe32_ctrl->vfebase + VFE_CFG); + msm_io_w(*(++p), vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_w(*(++p), vfe32_ctrl->vfebase + VFE_PIXEL_IF_CFG); + msm_io_w(*(++p), vfe32_ctrl->vfebase + VFE_REALIGN_BUF); + msm_io_w(*(++p), vfe32_ctrl->vfebase + VFE_CHROMA_UP); + msm_io_w(*(++p), vfe32_ctrl->vfebase + VFE_STATS_CFG); + return 0; +} + +static uint32_t vfe_stats_awb_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PONG_ADDR); + vfe32_ctrl->awbStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static uint32_t vfe_stats_aec_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_AEC_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_AEC_WR_PONG_ADDR); + + vfe32_ctrl->aecStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static uint32_t vfe_stats_af_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_AF_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_AF_WR_PONG_ADDR); + + vfe32_ctrl->afStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static uint32_t vfe_stats_ihist_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_HIST_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_HIST_WR_PONG_ADDR); + + vfe32_ctrl->ihistStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static uint32_t vfe_stats_rs_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_RS_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_RS_WR_PONG_ADDR); + + vfe32_ctrl->rsStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static uint32_t vfe_stats_cs_buf_init(struct vfe_cmd_stats_buf *in) +{ + uint32_t *ptr = in->statsBuf; + uint32_t addr; + + addr = ptr[0]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_CS_WR_PING_ADDR); + addr = ptr[1]; + msm_io_w(addr, vfe32_ctrl->vfebase + VFE_BUS_STATS_CS_WR_PONG_ADDR); + + vfe32_ctrl->csStatsControl.nextFrameAddrBuf = in->statsBuf[2]; + return 0; +} + +static void vfe32_start_common(void) +{ + + vfe32_ctrl->start_ack_pending = TRUE; + CDBG("VFE opertaion mode = 0x%x, output mode = 0x%x\n", + vfe32_ctrl->operation_mode, vfe32_ctrl->outpath.output_mode); + msm_io_w(0x00EFE021, vfe32_ctrl->vfebase + VFE_IRQ_MASK_0); + msm_io_w(VFE_IMASK_WHILE_STOPPING_1, + vfe32_ctrl->vfebase + VFE_IRQ_MASK_1); + + msm_io_dump(vfe32_ctrl->vfebase, 0x740); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + msm_io_w(1, vfe32_ctrl->vfebase + VFE_CAMIF_COMMAND); + wmb(); + + atomic_set(&vfe32_ctrl->vstate, 1); +} + +static int vfe32_start_recording(void) +{ + vfe32_ctrl->req_start_video_rec = TRUE; + /* Mask with 0x7 to extract the pixel pattern*/ + switch (msm_io_r(vfe32_ctrl->vfebase + VFE_CFG) & 0x7) { + case VFE_YUV_YCbYCr: + case VFE_YUV_YCrYCb: + case VFE_YUV_CbYCrY: + case VFE_YUV_CrYCbY: + msm_io_w_mb(1, + vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + break; + default: + break; + } + return 0; +} + +static int vfe32_stop_recording(void) +{ + vfe32_ctrl->req_stop_video_rec = TRUE; + /* Mask with 0x7 to extract the pixel pattern*/ + switch (msm_io_r(vfe32_ctrl->vfebase + VFE_CFG) & 0x7) { + case VFE_YUV_YCbYCr: + case VFE_YUV_YCrYCb: + case VFE_YUV_CbYCrY: + case VFE_YUV_CrYCbY: + msm_io_w_mb(1, + vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + break; + default: + break; + } + + return 0; +} + +static void vfe32_liveshot(void){ + struct msm_sync* p_sync = (struct msm_sync *)vfe_syncdata; + if (p_sync) + p_sync->liveshot_enabled = true; +} + +static int vfe32_capture(uint32_t num_frames_capture) +{ + uint32_t irq_comp_mask = 0; + struct msm_sync* p_sync = (struct msm_sync *)vfe_syncdata; + if (p_sync) { + p_sync->snap_count = num_frames_capture; + p_sync->thumb_count = num_frames_capture; + } + /* capture command is valid for both idle and active state. */ + vfe32_ctrl->outpath.out1.capture_cnt = num_frames_capture; + if (vfe32_ctrl->operation_mode == VFE_MODE_OF_OPERATION_SNAPSHOT) { + vfe32_ctrl->outpath.out0.capture_cnt = + num_frames_capture; + } + + vfe32_ctrl->vfe_capture_count = num_frames_capture; + irq_comp_mask = + msm_io_r(vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe32_ctrl->operation_mode == VFE_MODE_OF_OPERATION_SNAPSHOT) { + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_PT) { + irq_comp_mask |= (0x1 << vfe32_ctrl->outpath.out0.ch0 | + 0x1 << vfe32_ctrl->outpath.out0.ch1); + } + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_S) { + irq_comp_mask |= + (0x1 << (vfe32_ctrl->outpath.out1.ch0 + 8) | + 0x1 << (vfe32_ctrl->outpath.out1.ch1 + 8)); + } + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_PT) { + msm_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + } + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_S) { + msm_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch0]); + msm_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch1]); + } + } else { /* this is raw snapshot mode. */ + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_S) { + irq_comp_mask |= + (0x1 << (vfe32_ctrl->outpath.out1.ch0 + 8)); + msm_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out1.ch0]); + msm_io_w(0x1000, vfe32_ctrl->vfebase + + VFE_BUS_IO_FORMAT_CFG); + } + } + msm_io_w(irq_comp_mask, vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + msm_io_r(vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + vfe32_start_common(); + msm_io_r(vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + /* for debug */ + msm_io_w(1, vfe32_ctrl->vfebase + 0x18C); + msm_io_w(1, vfe32_ctrl->vfebase + 0x188); + return 0; +} + +static int vfe32_start(void) +{ + uint32_t irq_comp_mask = 0; + /* start command now is only good for continuous mode. */ + if ((vfe32_ctrl->operation_mode != VFE_MODE_OF_OPERATION_CONTINUOUS) && + (vfe32_ctrl->operation_mode != VFE_MODE_OF_OPERATION_VIDEO)) + return 0; + irq_comp_mask = + msm_io_r(vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_PT) { + irq_comp_mask |= (0x1 << vfe32_ctrl->outpath.out0.ch0 | + 0x1 << vfe32_ctrl->outpath.out0.ch1); + } + + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_V) { + irq_comp_mask |= (0x1 << (vfe32_ctrl->outpath.out2.ch0 + 16)| + 0x1 << (vfe32_ctrl->outpath.out2.ch1 + 16)); + } + + msm_io_w(irq_comp_mask, vfe32_ctrl->vfebase + VFE_IRQ_COMP_MASK); + + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_PT) { + msm_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch0]); + msm_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out0.ch1]); + } + vfe32_start_common(); + return 0; +} + +static void vfe32_update(void) +{ + unsigned long flags; + spin_lock_irqsave(&vfe32_ctrl->update_ack_lock, flags); + vfe32_ctrl->update_ack_pending = TRUE; + spin_unlock_irqrestore(&vfe32_ctrl->update_ack_lock, flags); + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + return; +} + +static void vfe32_sync_timer_stop(void) +{ + uint32_t value = 0; + vfe32_ctrl->sync_timer_state = 0; + if (vfe32_ctrl->sync_timer_number == 0) + value = 0x10000; + else if (vfe32_ctrl->sync_timer_number == 1) + value = 0x20000; + else if (vfe32_ctrl->sync_timer_number == 2) + value = 0x40000; + + /* Timer Stop */ + msm_io_w(value, vfe32_ctrl->vfebase + V32_SYNC_TIMER_OFF); +} + +static void vfe32_sync_timer_start(const uint32_t *tbl) +{ + /* set bit 8 for auto increment. */ + uint32_t value = 1; + uint32_t val; + + vfe32_ctrl->sync_timer_state = *tbl++; + vfe32_ctrl->sync_timer_repeat_count = *tbl++; + vfe32_ctrl->sync_timer_number = *tbl++; + CDBG("%s timer_state %d, repeat_cnt %d timer number %d\n", + __func__, vfe32_ctrl->sync_timer_state, + vfe32_ctrl->sync_timer_repeat_count, + vfe32_ctrl->sync_timer_number); + + if (vfe32_ctrl->sync_timer_state) { /* Start Timer */ + value = value << vfe32_ctrl->sync_timer_number; + } else { /* Stop Timer */ + CDBG("Failed to Start timer\n"); + return; + } + + /* Timer Start */ + msm_io_w(value, vfe32_ctrl->vfebase + V32_SYNC_TIMER_OFF); + /* Sync Timer Line Start */ + value = *tbl++; + msm_io_w(value, vfe32_ctrl->vfebase + V32_SYNC_TIMER_OFF + + 4 + ((vfe32_ctrl->sync_timer_number) * 12)); + /* Sync Timer Pixel Start */ + value = *tbl++; + msm_io_w(value, vfe32_ctrl->vfebase + V32_SYNC_TIMER_OFF + + 8 + ((vfe32_ctrl->sync_timer_number) * 12)); + /* Sync Timer Pixel Duration */ + value = *tbl++; + val = camio_clk.vfe_clk_rate / 10000; + val = 10000000 / val; + val = value * 10000 / val; + CDBG("%s: Pixel Clk Cycles!!! %d\n", __func__, val); + msm_io_w(val, vfe32_ctrl->vfebase + V32_SYNC_TIMER_OFF + + 12 + ((vfe32_ctrl->sync_timer_number) * 12)); + /* Timer0 Active High/LOW */ + value = *tbl++; + msm_io_w(value, vfe32_ctrl->vfebase + V32_SYNC_TIMER_POLARITY_OFF); + /* Selects sync timer 0 output to drive onto timer1 port */ + value = 0; + msm_io_w(value, vfe32_ctrl->vfebase + V32_TIMER_SELECT_OFF); +} + +static void vfe32_program_dmi_cfg(enum VFE32_DMI_RAM_SEL bankSel) +{ + /* set bit 8 for auto increment. */ + uint32_t value = VFE_DMI_CFG_DEFAULT; + value += (uint32_t)bankSel; + + msm_io_w(value, vfe32_ctrl->vfebase + VFE_DMI_CFG); + /* by default, always starts with offset 0.*/ + msm_io_w(0, vfe32_ctrl->vfebase + VFE_DMI_ADDR); +} +static void vfe32_write_gamma_cfg(enum VFE32_DMI_RAM_SEL channel_sel, + const uint32_t *tbl) +{ + int i; + uint32_t value, value1, value2; + vfe32_program_dmi_cfg(channel_sel); + /* for loop for extracting init table. */ + for (i = 0 ; i < (VFE32_GAMMA_NUM_ENTRIES/2) ; i++) { + value = *tbl++; + value1 = value & 0x0000FFFF; + value2 = (value & 0xFFFF0000)>>16; + msm_io_w((value1), vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_io_w((value2), vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + } + vfe32_program_dmi_cfg(NO_MEM_SELECTED); +} + +static void vfe32_write_la_cfg(enum VFE32_DMI_RAM_SEL channel_sel, + const uint32_t *tbl) +{ + uint32_t i; + uint32_t value, value1, value2; + + vfe32_program_dmi_cfg(channel_sel); + /* for loop for extracting init table. */ + for (i = 0 ; i < (VFE32_LA_TABLE_LENGTH/2) ; i++) { + value = *tbl++; + value1 = value & 0x0000FFFF; + value2 = (value & 0xFFFF0000)>>16; + msm_io_w((value1), vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + msm_io_w((value2), vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + } + vfe32_program_dmi_cfg(NO_MEM_SELECTED); +} + + +static int vfe32_proc_general(struct msm_vfe32_cmd *cmd) +{ + int i , rc = 0; + uint32_t old_val = 0 , new_val = 0; + uint32_t *cmdp = NULL; + uint32_t *cmdp_local = NULL; + uint32_t snapshot_cnt = 0; + + CDBG("vfe32_proc_general: cmdID = %s, length = %d\n", + vfe32_general_cmd[cmd->id], cmd->length); + switch (cmd->id) { + case V32_RESET: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + vfe32_reset(); + break; + case V32_START: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + rc = vfe32_start(); + break; + case V32_UPDATE: + vfe32_update(); + break; + case V32_CAPTURE: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + if (copy_from_user(&snapshot_cnt, (void __user *)(cmd->value), + sizeof(uint32_t))) { + rc = -EFAULT; + goto proc_general_done; + } + rc = vfe32_capture(snapshot_cnt); + break; + case V32_START_RECORDING: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + rc = vfe32_start_recording(); + break; + case V32_STOP_RECORDING: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + rc = vfe32_stop_recording(); + break; + case V32_OPERATION_CFG: { + if (cmd->length != V32_OPERATION_CFG_LEN) { + rc = -EINVAL; + goto proc_general_done; + } + cmdp = kmalloc(V32_OPERATION_CFG_LEN, GFP_ATOMIC); + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + V32_OPERATION_CFG_LEN)) { + rc = -EFAULT; + goto proc_general_done; + } + rc = vfe32_operation_config(cmdp); + } + break; + + case V32_STATS_AE_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AE_BG_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + case V32_STATS_AF_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AF_BF_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + case V32_STATS_AWB_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= AWB_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + case V32_STATS_IHIST_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= IHIST_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + + case V32_STATS_RS_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + /* + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= RS_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + */ + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + case V32_STATS_CS_START: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + /* + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= CS_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + */ + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + case V32_MCE_UPDATE: + case V32_MCE_CFG:{ + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + /* Incrementing with 4 so as to point to the 2nd Register as + the 2nd register has the mce_enable bit */ + old_val = msm_io_r(vfe32_ctrl->vfebase + V32_CHROMA_EN_OFF + 4); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + new_val = *cmdp_local; + old_val &= MCE_EN_MASK; + new_val = new_val | old_val; + msm_io_memcpy(vfe32_ctrl->vfebase + V32_CHROMA_EN_OFF + 4, + &new_val, 4); + cmdp_local += 1; + + old_val = msm_io_r(vfe32_ctrl->vfebase + V32_CHROMA_EN_OFF + 8); + new_val = *cmdp_local; + old_val &= MCE_Q_K_MASK; + new_val = new_val | old_val; + msm_io_memcpy(vfe32_ctrl->vfebase + V32_CHROMA_EN_OFF + 8, + &new_val, 4); + cmdp_local += 1; + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp_local, (vfe32_cmd[cmd->id].length)); + } + break; + case V32_BLACK_LEVEL_CFG: + rc = -EFAULT; + goto proc_general_done; + case V32_ROLL_OFF_CFG: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value) , cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp_local, 16); + cmdp_local += 4; + vfe32_program_dmi_cfg(ROLLOFF_RAM); + /* for loop for extrcting init table. */ + for (i = 0 ; i < (VFE32_ROLL_OFF_INIT_TABLE_SIZE * 2) ; i++) { + msm_io_w(*cmdp_local , + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + cmdp_local++; + } + CDBG("done writing init table\n"); + /* by default, always starts with offset 0. */ + msm_io_w(LENS_ROLL_OFF_DELTA_TABLE_OFFSET, + vfe32_ctrl->vfebase + VFE_DMI_ADDR); + /* for loop for extracting delta table. */ + for (i = 0 ; i < (VFE32_ROLL_OFF_DELTA_TABLE_SIZE * 2) ; i++) { + msm_io_w(*cmdp_local, + vfe32_ctrl->vfebase + VFE_DMI_DATA_LO); + cmdp_local++; + } + vfe32_program_dmi_cfg(NO_MEM_SELECTED); + } + break; + + case V32_LA_CFG: + case V32_LA_UPDATE: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + + rc = -EFAULT; + goto proc_general_done; + } + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + + old_val = *cmdp; + cmdp += 1; + if (old_val == 0x0) + vfe32_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK0 , cmdp); + else + vfe32_write_la_cfg(LUMA_ADAPT_LUT_RAM_BANK1 , cmdp); + cmdp -= 1; + } + break; + + case V32_SK_ENHAN_CFG: + case V32_SK_ENHAN_UPDATE:{ + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + msm_io_memcpy(vfe32_ctrl->vfebase + V32_SCE_OFF, + cmdp, V32_SCE_LEN); + } + break; + + case V32_LIVESHOT: + vfe32_liveshot(); + break; + + case V32_LINEARIZATION: + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + msm_io_memcpy(vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF1, + cmdp_local, V32_LINEARIZATION_LEN1); + cmdp_local += 4; + msm_io_memcpy(vfe32_ctrl->vfebase + V32_LINEARIZATION_OFF2, + cmdp_local, + V32_LINEARIZATION_LEN2); + break; + + case V32_DEMOSAICV3: + if (cmd->length != + V32_DEMOSAICV3_0_LEN+V32_DEMOSAICV3_1_LEN) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + + msm_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF, + cmdp_local, V32_DEMOSAICV3_0_LEN); + cmdp_local += 1; + msm_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_1_OFF, + cmdp_local, V32_DEMOSAICV3_1_LEN); + break; + + case V32_DEMOSAICV3_ABCC_CFG: + rc = -EFAULT; + break; + + case V32_DEMOSAICV3_DBCC_CFG: + case V32_DEMOSAICV3_DBCC_UPDATE: + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + new_val = *cmdp_local; + + old_val = msm_io_r(vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF); + old_val &= DBCC_MASK; + + new_val = new_val | old_val; + *cmdp_local = new_val; + msm_io_memcpy(vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF, + cmdp_local, 4); + cmdp_local += 1; + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp_local, (vfe32_cmd[cmd->id].length)); + break; + + case V32_DEMOSAICV3_DBPC_CFG: + case V32_DEMOSAICV3_DBPC_UPDATE: + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + cmdp_local = cmdp; + new_val = *cmdp_local; + + old_val = msm_io_r(vfe32_ctrl->vfebase + V32_DEMOSAICV3_0_OFF); + old_val &= DBPC_MASK; + + new_val = new_val | old_val; + *cmdp_local = new_val; + msm_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_0_OFF, + cmdp_local, V32_DEMOSAICV3_LEN); + cmdp_local += 1; + msm_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_DBPC_CFG_OFF, + cmdp_local, V32_DEMOSAICV3_DBPC_LEN); + cmdp_local += 1; + msm_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_DBPC_CFG_OFF0, + cmdp_local, V32_DEMOSAICV3_DBPC_LEN); + cmdp_local += 1; + msm_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_DBPC_CFG_OFF1, + cmdp_local, V32_DEMOSAICV3_DBPC_LEN); + cmdp_local += 1; + msm_io_memcpy(vfe32_ctrl->vfebase + + V32_DEMOSAICV3_DBPC_CFG_OFF2, + cmdp_local, V32_DEMOSAICV3_DBPC_LEN); + break; + + case V32_RGB_G_CFG: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + msm_io_memcpy(vfe32_ctrl->vfebase + V32_RGB_G_OFF, + cmdp, 4); + cmdp += 1; + vfe32_write_gamma_cfg(RGBLUT_RAM_CH0_BANK0 , cmdp); + vfe32_write_gamma_cfg(RGBLUT_RAM_CH1_BANK0 , cmdp); + vfe32_write_gamma_cfg(RGBLUT_RAM_CH2_BANK0 , cmdp); + cmdp -= 1; + } + break; + + case V32_RGB_G_UPDATE: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + + msm_io_memcpy(vfe32_ctrl->vfebase + V32_RGB_G_OFF, cmdp, 4); + old_val = *cmdp; + cmdp += 1; + + if (old_val) { + vfe32_write_gamma_cfg(RGBLUT_RAM_CH0_BANK1 , cmdp); + vfe32_write_gamma_cfg(RGBLUT_RAM_CH1_BANK1 , cmdp); + vfe32_write_gamma_cfg(RGBLUT_RAM_CH2_BANK1 , cmdp); + } else { + vfe32_write_gamma_cfg(RGBLUT_RAM_CH0_BANK0 , cmdp); + vfe32_write_gamma_cfg(RGBLUT_RAM_CH1_BANK0 , cmdp); + vfe32_write_gamma_cfg(RGBLUT_RAM_CH2_BANK0 , cmdp); + } + cmdp -= 1; + } + break; + + case V32_STATS_AWB_STOP: { + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AWB_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case V32_STATS_AE_STOP: { + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AE_BG_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case V32_STATS_AF_STOP: { + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~AF_BF_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case V32_STATS_IHIST_STOP: { + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~IHIST_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case V32_STATS_RS_STOP: { + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~RS_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + + case V32_STATS_CS_STOP: { + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= ~CS_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + break; + case V32_STOP: + pr_info("vfe32_proc_general: cmdID = %s\n", + vfe32_general_cmd[cmd->id]); + vfe32_stop(); + break; + + case V32_SYNC_TIMER_SETTING: + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + vfe32_sync_timer_start(cmdp); + break; + + case V32_EZTUNE_CFG: { + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto proc_general_done; + } + *cmdp &= ~STATS_ENABLE_MASK; + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= STATS_ENABLE_MASK; + *cmdp |= old_val; + + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + default: { + if (cmd->length != vfe32_cmd[cmd->id].length) + return -EINVAL; + + cmdp = kmalloc(vfe32_cmd[cmd->id].length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto proc_general_done; + } + + CHECKED_COPY_FROM_USER(cmdp); + msm_io_memcpy(vfe32_ctrl->vfebase + vfe32_cmd[cmd->id].offset, + cmdp, (vfe32_cmd[cmd->id].length)); + } + break; + + } + +proc_general_done: + kfree(cmdp); + + return rc; +} + +static void vfe32_stats_af_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spin_lock_irqsave(&vfe32_ctrl->af_ack_lock, flags); + vfe32_ctrl->afStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->afStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(&vfe32_ctrl->af_ack_lock, flags); +} + +static void vfe32_stats_awb_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spin_lock_irqsave(&vfe32_ctrl->awb_ack_lock, flags); + vfe32_ctrl->awbStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->awbStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(&vfe32_ctrl->awb_ack_lock, flags); +} + +static void vfe32_stats_aec_ack(struct vfe_cmd_stats_ack *pAck) +{ + unsigned long flags; + spin_lock_irqsave(&vfe32_ctrl->aec_ack_lock, flags); + vfe32_ctrl->aecStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->aecStatsControl.ackPending = FALSE; + spin_unlock_irqrestore(&vfe32_ctrl->aec_ack_lock, flags); +} + +static void vfe32_stats_ihist_ack(struct vfe_cmd_stats_ack *pAck) +{ + vfe32_ctrl->ihistStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->ihistStatsControl.ackPending = FALSE; +} +static void vfe32_stats_rs_ack(struct vfe_cmd_stats_ack *pAck) +{ + vfe32_ctrl->rsStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->rsStatsControl.ackPending = FALSE; +} +static void vfe32_stats_cs_ack(struct vfe_cmd_stats_ack *pAck) +{ + vfe32_ctrl->csStatsControl.nextFrameAddrBuf = pAck->nextStatsBuf; + vfe32_ctrl->csStatsControl.ackPending = FALSE; +} + + +static inline void vfe32_read_irq_status(struct vfe32_irq_status *out) +{ + uint32_t *temp; + memset(out, 0, sizeof(struct vfe32_irq_status)); + temp = (uint32_t *)(vfe32_ctrl->vfebase + VFE_IRQ_STATUS_0); + out->vfeIrqStatus0 = msm_io_r(temp); + + temp = (uint32_t *)(vfe32_ctrl->vfebase + VFE_IRQ_STATUS_1); + out->vfeIrqStatus1 = msm_io_r(temp); + + temp = (uint32_t *)(vfe32_ctrl->vfebase + VFE_CAMIF_STATUS); + out->camifStatus = msm_io_r(temp); + CDBG("camifStatus = 0x%x\n", out->camifStatus); + + /* clear the pending interrupt of the same kind.*/ + msm_io_w(out->vfeIrqStatus0, vfe32_ctrl->vfebase + VFE_IRQ_CLEAR_0); + msm_io_w(out->vfeIrqStatus1, vfe32_ctrl->vfebase + VFE_IRQ_CLEAR_1); + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(1, vfe32_ctrl->vfebase + VFE_IRQ_CMD); + +} + +static void vfe32_send_msg_no_payload(enum VFE32_MESSAGE_ID id) +{ + struct vfe_message msg; + + CDBG("vfe32_send_msg_no_payload\n"); + msg._d = id; + vfe32_proc_ops(id, &msg, 0); +} + +static void vfe32_process_reg_update_irq(void) +{ + uint32_t temp, old_val; + unsigned long flags; + if (vfe32_ctrl->req_start_video_rec) { + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_V) { + msm_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out2.ch0]); + msm_io_w(1, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out2.ch1]); + /* Mask with 0x7 to extract the pixel pattern*/ + switch (msm_io_r(vfe32_ctrl->vfebase + VFE_CFG) + & 0x7) { + case VFE_YUV_YCbYCr: + case VFE_YUV_YCrYCb: + case VFE_YUV_CbYCrY: + case VFE_YUV_CrYCbY: + msm_io_w_mb(1, + vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + break; + default: + break; + } + } + vfe32_ctrl->req_start_video_rec = FALSE; + if (vpe_ctrl && vpe_ctrl->dis_en) { + old_val = msm_io_r( + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val |= RS_CS_ENABLE_MASK; + msm_io_w(old_val, + vfe32_ctrl->vfebase + VFE_MODULE_CFG); + } + CDBG("start video triggered .\n"); + } else if (vfe32_ctrl->req_stop_video_rec) { + if (vfe32_ctrl->outpath.output_mode & VFE32_OUTPUT_MODE_V) { + msm_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out2.ch0]); + msm_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl->outpath.out2.ch1]); + /* Mask with 0x7 to extract the pixel pattern*/ + switch (msm_io_r(vfe32_ctrl->vfebase + VFE_CFG) + & 0x7) { + case VFE_YUV_YCbYCr: + case VFE_YUV_YCrYCb: + case VFE_YUV_CbYCrY: + case VFE_YUV_CrYCbY: + msm_io_w_mb(1, + vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + break; + default: + break; + } + } + vfe32_ctrl->req_stop_video_rec = FALSE; + vfe32_send_msg_no_payload(MSG_ID_STOP_REC_ACK); + + /*disable rs& cs when stop recording. */ + old_val = msm_io_r(vfe32_ctrl->vfebase + VFE_MODULE_CFG); + old_val &= (~RS_CS_ENABLE_MASK); + msm_io_w(old_val, vfe32_ctrl->vfebase + VFE_MODULE_CFG); + + CDBG("stop video triggered .\n"); + } + if (vfe32_ctrl->start_ack_pending == TRUE) { + vfe32_send_msg_no_payload(MSG_ID_START_ACK); + vfe32_ctrl->start_ack_pending = FALSE; + } else { + spin_lock_irqsave(&vfe32_ctrl->update_ack_lock, flags); + if (vfe32_ctrl->update_ack_pending == TRUE) { + vfe32_ctrl->update_ack_pending = FALSE; + spin_unlock_irqrestore( + &vfe32_ctrl->update_ack_lock, flags); + vfe32_send_msg_no_payload(MSG_ID_UPDATE_ACK); + } else { + spin_unlock_irqrestore( + &vfe32_ctrl->update_ack_lock, flags); + } + } + if (vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_SNAPSHOT) { /* in snapshot mode */ + /* later we need to add check for live snapshot mode. */ + vfe32_ctrl->vfe_capture_count--; + /* if last frame to be captured: */ + if (vfe32_ctrl->vfe_capture_count == 0) { + /* stop the bus output: write master enable = 0*/ + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_PT) { + msm_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl-> + outpath.out0.ch0]); + msm_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl-> + outpath.out0.ch1]); + } + if (vfe32_ctrl->outpath.output_mode & + VFE32_OUTPUT_MODE_S) { + msm_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl-> + outpath.out1.ch0]); + msm_io_w(0, vfe32_ctrl->vfebase + + vfe32_AXI_WM_CFG[vfe32_ctrl-> + outpath.out1.ch1]); + } + + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + vfe32_ctrl->vfebase + VFE_CAMIF_COMMAND); + + /* Ensure the read order while reading + to the command register using the barrier */ + temp = msm_io_r_mb(vfe32_ctrl->vfebase + + VFE_CAMIF_COMMAND); + /* then do reg_update. */ + msm_io_w(1, vfe32_ctrl->vfebase + VFE_REG_UPDATE_CMD); + } + } /* if snapshot mode. */ +} + +static void vfe32_set_default_reg_values(void) +{ + msm_io_w(0x800080, vfe32_ctrl->vfebase + VFE_DEMUX_GAIN_0); + msm_io_w(0x800080, vfe32_ctrl->vfebase + VFE_DEMUX_GAIN_1); + /* What value should we program CGC_OVERRIDE to? */ + msm_io_w(0xFFFFF, vfe32_ctrl->vfebase + VFE_CGC_OVERRIDE); + + /* default frame drop period and pattern */ + msm_io_w(0x1f, vfe32_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_CFG); + msm_io_w(0x1f, vfe32_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_CFG); + msm_io_w(0xFFFFFFFF, vfe32_ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_PATTERN); + msm_io_w(0xFFFFFFFF, + vfe32_ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_PATTERN); + msm_io_w(0x1f, vfe32_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y); + msm_io_w(0x1f, vfe32_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR); + msm_io_w(0xFFFFFFFF, + vfe32_ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y_PATTERN); + msm_io_w(0xFFFFFFFF, + vfe32_ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR_PATTERN); + msm_io_w(0, vfe32_ctrl->vfebase + VFE_CLAMP_MIN); + msm_io_w(0xFFFFFF, vfe32_ctrl->vfebase + VFE_CLAMP_MAX); + + /* stats UB config */ + msm_io_w(0x3980007, vfe32_ctrl->vfebase + VFE_BUS_STATS_AEC_UB_CFG); + msm_io_w(0x3A00007, vfe32_ctrl->vfebase + VFE_BUS_STATS_AF_UB_CFG); + msm_io_w(0x3A8000F, vfe32_ctrl->vfebase + VFE_BUS_STATS_AWB_UB_CFG); + msm_io_w(0x3B80007, vfe32_ctrl->vfebase + VFE_BUS_STATS_RS_UB_CFG); + msm_io_w(0x3C0001F, vfe32_ctrl->vfebase + VFE_BUS_STATS_CS_UB_CFG); + msm_io_w(0x3E0001F, vfe32_ctrl->vfebase + VFE_BUS_STATS_HIST_UB_CFG); +} + +static void vfe32_process_reset_irq(void) +{ + unsigned long flags; + + atomic_set(&vfe32_ctrl->vstate, 0); + + spin_lock_irqsave(&vfe32_ctrl->stop_flag_lock, flags); + if (vfe32_ctrl->stop_ack_pending) { + vfe32_ctrl->stop_ack_pending = FALSE; + spin_unlock_irqrestore(&vfe32_ctrl->stop_flag_lock, flags); + vfe32_send_msg_no_payload(MSG_ID_STOP_ACK); + } else { + spin_unlock_irqrestore(&vfe32_ctrl->stop_flag_lock, flags); + /* this is from reset command. */ + vfe32_set_default_reg_values(); + + /* reload all write masters. (frame & line)*/ + msm_io_w(0x7FFF, vfe32_ctrl->vfebase + VFE_BUS_CMD); + vfe32_send_msg_no_payload(MSG_ID_RESET_ACK); + } +} + +static void vfe32_process_camif_sof_irq(void) +{ + uint32_t temp; + + /* in raw snapshot mode */ + if (vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_RAW_SNAPSHOT) { + if (vfe32_ctrl->start_ack_pending) { + vfe32_send_msg_no_payload(MSG_ID_START_ACK); + vfe32_ctrl->start_ack_pending = FALSE; + } + vfe32_ctrl->vfe_capture_count--; + /* if last frame to be captured: */ + if (vfe32_ctrl->vfe_capture_count == 0) { + /* Ensure the write order while writing + to the command register using the barrier */ + msm_io_w_mb(CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + vfe32_ctrl->vfebase + VFE_CAMIF_COMMAND); + temp = msm_io_r_mb(vfe32_ctrl->vfebase + + VFE_CAMIF_COMMAND); + } + } /* if raw snapshot mode. */ + vfe32_send_msg_no_payload(MSG_ID_SOF_ACK); + vfe32_ctrl->vfeFrameId++; + CDBG("camif_sof_irq, frameId = %d\n", vfe32_ctrl->vfeFrameId); + + if (vfe32_ctrl->sync_timer_state) { + if (vfe32_ctrl->sync_timer_repeat_count == 0) + vfe32_sync_timer_stop(); + else + vfe32_ctrl->sync_timer_repeat_count--; + } +} + +static void vfe32_process_error_irq(uint32_t errStatus) +{ + uint32_t camifStatus; + uint32_t *temp; + + if (errStatus & VFE32_IMASK_CAMIF_ERROR) { + pr_err("vfe32_irq: camif errors\n"); + temp = (uint32_t *)(vfe32_ctrl->vfebase + VFE_CAMIF_STATUS); + camifStatus = msm_io_r(temp); + pr_err("camifStatus = 0x%x\n", camifStatus); + vfe32_send_msg_no_payload(MSG_ID_CAMIF_ERROR); + } + + if (errStatus & VFE32_IMASK_BHIST_OVWR) + pr_err("vfe32_irq: stats bhist overwrite\n"); + + if (errStatus & VFE32_IMASK_STATS_CS_OVWR) + pr_err("vfe32_irq: stats cs overwrite\n"); + + if (errStatus & VFE32_IMASK_STATS_IHIST_OVWR) + pr_err("vfe32_irq: stats ihist overwrite\n"); + + if (errStatus & VFE32_IMASK_REALIGN_BUF_Y_OVFL) + pr_err("vfe32_irq: realign bug Y overflow\n"); + + if (errStatus & VFE32_IMASK_REALIGN_BUF_CB_OVFL) + pr_err("vfe32_irq: realign bug CB overflow\n"); + + if (errStatus & VFE32_IMASK_REALIGN_BUF_CR_OVFL) + pr_err("vfe32_irq: realign bug CR overflow\n"); + + if (errStatus & VFE32_IMASK_VIOLATION) + pr_err("vfe32_irq: violation interrupt\n"); + + if (errStatus & VFE32_IMASK_IMG_MAST_0_BUS_OVFL) + pr_err("vfe32_irq: image master 0 bus overflow\n"); + + if (errStatus & VFE32_IMASK_IMG_MAST_1_BUS_OVFL) + pr_err("vfe32_irq: image master 1 bus overflow\n"); + + if (errStatus & VFE32_IMASK_IMG_MAST_2_BUS_OVFL) + pr_err("vfe32_irq: image master 2 bus overflow\n"); + + if (errStatus & VFE32_IMASK_IMG_MAST_3_BUS_OVFL) + pr_err("vfe32_irq: image master 3 bus overflow\n"); + + if (errStatus & VFE32_IMASK_IMG_MAST_4_BUS_OVFL) + pr_err("vfe32_irq: image master 4 bus overflow\n"); + + if (errStatus & VFE32_IMASK_IMG_MAST_5_BUS_OVFL) + pr_err("vfe32_irq: image master 5 bus overflow\n"); + + if (errStatus & VFE32_IMASK_IMG_MAST_6_BUS_OVFL) + pr_err("vfe32_irq: image master 6 bus overflow\n"); + + if (errStatus & VFE32_IMASK_STATS_AE_BG_BUS_OVFL) + pr_err("vfe32_irq: ae/bg stats bus overflow\n"); + + if (errStatus & VFE32_IMASK_STATS_AF_BF_BUS_OVFL) + pr_err("vfe32_irq: af/bf stats bus overflow\n"); + + if (errStatus & VFE32_IMASK_STATS_AWB_BUS_OVFL) + pr_err("vfe32_irq: awb stats bus overflow\n"); + + if (errStatus & VFE32_IMASK_STATS_RS_BUS_OVFL) + pr_err("vfe32_irq: rs stats bus overflow\n"); + + if (errStatus & VFE32_IMASK_STATS_CS_BUS_OVFL) + pr_err("vfe32_irq: cs stats bus overflow\n"); + + if (errStatus & VFE32_IMASK_STATS_IHIST_BUS_OVFL) + pr_err("vfe32_irq: ihist stats bus overflow\n"); + + if (errStatus & VFE32_IMASK_STATS_SKIN_BHIST_BUS_OVFL) + pr_err("vfe32_irq: skin/bhist stats bus overflow\n"); + + if (errStatus & VFE32_IMASK_AXI_ERROR) + pr_err("vfe32_irq: axi error\n"); +} + +#define VFE32_AXI_OFFSET 0x0050 +#define vfe32_get_ch_ping_addr(chn) \ + (msm_io_r(vfe32_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe32_get_ch_pong_addr(chn) \ + (msm_io_r(vfe32_ctrl->vfebase + 0x0050 + 0x18 * (chn) + 4)) +#define vfe32_get_ch_addr(ping_pong, chn) \ + (((ping_pong) & (1 << (chn))) == 0 ? \ + vfe32_get_ch_pong_addr(chn) : vfe32_get_ch_ping_addr(chn)) + +#define vfe32_put_ch_ping_addr(chn, addr) \ + (msm_io_w((addr), vfe32_ctrl->vfebase + 0x0050 + 0x18 * (chn))) +#define vfe32_put_ch_pong_addr(chn, addr) \ + (msm_io_w((addr), vfe32_ctrl->vfebase + 0x0050 + 0x18 * (chn) + 4)) +#define vfe32_put_ch_addr(ping_pong, chn, addr) \ + (((ping_pong) & (1 << (chn))) == 0 ? \ + vfe32_put_ch_pong_addr((chn), (addr)) : \ + vfe32_put_ch_ping_addr((chn), (addr))) + +static void vfe32_process_output_path_irq_0(void) +{ + uint32_t ping_pong; + uint32_t pyaddr, pcbcraddr; +#ifdef CONFIG_MSM_CAMERA_V4L2 + uint32_t pyaddr_ping, pcbcraddr_ping, pyaddr_pong, pcbcraddr_pong; +#endif + uint8_t out_bool = 0; + struct vfe32_free_buf *free_buf = NULL; + free_buf = vfe32_dequeue_free_buf(&vfe32_ctrl->outpath.out0); + /* we render frames in the following conditions: + 1. Continuous mode and the free buffer is avaialable. + 2. In snapshot shot mode, free buffer is not always available. + when pending snapshot count is <=1, then no need to use + free buffer. + */ + out_bool = + ((vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_SNAPSHOT || + vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_RAW_SNAPSHOT) && + (vfe32_ctrl->vfe_capture_count <= 1)) || + free_buf; + if (out_bool) { + ping_pong = msm_io_r(vfe32_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS); + + /* Y channel */ + pyaddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch0); + /* Chroma channel */ + pcbcraddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch1); + + CDBG("output path 0, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + pyaddr, pcbcraddr); + if (free_buf) { + /* Y channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out0.ch1, + free_buf->paddr + free_buf->cbcr_off); + } + if (vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_SNAPSHOT) { + /* will add message for multi-shot. */ + vfe32_ctrl->outpath.out0.capture_cnt--; + vfe_send_outmsg(MSG_ID_OUTPUT_T, pyaddr, + pcbcraddr); + } else { + /* always send message for continous mode. */ + /* if continuous mode, for display. (preview) */ + vfe_send_outmsg(MSG_ID_OUTPUT_P, pyaddr, + pcbcraddr); + } + } else { + vfe32_ctrl->outpath.out0.frame_drop_cnt++; + pr_warning("path_irq_0 - no free buffer!\n"); +#ifdef CONFIG_MSM_CAMERA_V4L2 + pr_info("Swapping ping and pong\n"); + + /*get addresses*/ + /* Y channel */ + pyaddr_ping = vfe32_get_ch_ping_addr( + vfe32_ctrl->outpath.out0.ch0); + /* Chroma channel */ + pcbcraddr_ping = vfe32_get_ch_ping_addr( + vfe32_ctrl->outpath.out0.ch1); + /* Y channel */ + pyaddr_pong = vfe32_get_ch_pong_addr( + vfe32_ctrl->outpath.out0.ch0); + /* Chroma channel */ + pcbcraddr_pong = vfe32_get_ch_pong_addr( + vfe32_ctrl->outpath.out0.ch1); + + CDBG("ping = 0x%p, pong = 0x%p\n", (void *)pyaddr_ping, + (void *)pyaddr_pong); + CDBG("ping_cbcr = 0x%p, pong_cbcr = 0x%p\n", + (void *)pcbcraddr_ping, (void *)pcbcraddr_pong); + + /*put addresses*/ + /* SWAP y channel*/ + vfe32_put_ch_ping_addr(vfe32_ctrl->outpath.out0.ch0, + pyaddr_pong); + vfe32_put_ch_pong_addr(vfe32_ctrl->outpath.out0.ch0, + pyaddr_ping); + /* SWAP chroma channel*/ + vfe32_put_ch_ping_addr(vfe32_ctrl->outpath.out0.ch1, + pcbcraddr_pong); + vfe32_put_ch_pong_addr(vfe32_ctrl->outpath.out0.ch1, + pcbcraddr_ping); + CDBG("after swap: ping = 0x%p, pong = 0x%p\n", + (void *)pyaddr_pong, (void *)pyaddr_ping); +#endif + } +} + +static void vfe32_process_output_path_irq_1(void) +{ + uint32_t ping_pong; + uint32_t pyaddr, pcbcraddr; +#ifdef CONFIG_MSM_CAMERA_V4L2 + uint32_t pyaddr_ping, pcbcraddr_ping, pyaddr_pong, pcbcraddr_pong; +#endif + /* this must be snapshot main image output. */ + uint8_t out_bool = 0; + struct vfe32_free_buf *free_buf = NULL; + free_buf = vfe32_dequeue_free_buf(&vfe32_ctrl->outpath.out1); + + /* we render frames in the following conditions: + 1. Continuous mode and the free buffer is avaialable. + 2. In snapshot shot mode, free buffer is not always available. + -- when pending snapshot count is <=1, then no need to use + free buffer. + */ + out_bool = + ((vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_SNAPSHOT || + vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_RAW_SNAPSHOT) && + (vfe32_ctrl->vfe_capture_count <= 1)) || free_buf; + if (out_bool) { + ping_pong = msm_io_r(vfe32_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS); + + /* Y channel */ + pyaddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch0); + /* Chroma channel */ + pcbcraddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch1); + + CDBG("snapshot main, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + pyaddr, pcbcraddr); + if (free_buf) { + /* Y channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out1.ch1, + free_buf->paddr + free_buf->cbcr_off); + } + if (vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_SNAPSHOT || + vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_RAW_SNAPSHOT) { + vfe32_ctrl->outpath.out1.capture_cnt--; + vfe_send_outmsg(MSG_ID_OUTPUT_S, pyaddr, + pcbcraddr); + } + } else { + vfe32_ctrl->outpath.out1.frame_drop_cnt++; + pr_warning("path_irq_1 - no free buffer!\n"); +#ifdef CONFIG_MSM_CAMERA_V4L2 + pr_info("Swapping ping and pong\n"); + + /*get addresses*/ + /* Y channel */ + pyaddr_ping = vfe32_get_ch_ping_addr( + vfe32_ctrl->outpath.out1.ch0); + /* Chroma channel */ + pcbcraddr_ping = vfe32_get_ch_ping_addr( + vfe32_ctrl->outpath.out1.ch1); + /* Y channel */ + pyaddr_pong = vfe32_get_ch_pong_addr( + vfe32_ctrl->outpath.out1.ch0); + /* Chroma channel */ + pcbcraddr_pong = vfe32_get_ch_pong_addr( + vfe32_ctrl->outpath.out1.ch1); + + CDBG("ping = 0x%p, pong = 0x%p\n", (void *)pyaddr_ping, + (void *)pyaddr_pong); + CDBG("ping_cbcr = 0x%p, pong_cbcr = 0x%p\n", + (void *)pcbcraddr_ping, (void *)pcbcraddr_pong); + + /*put addresses*/ + /* SWAP y channel*/ + vfe32_put_ch_ping_addr(vfe32_ctrl->outpath.out1.ch0, + pyaddr_pong); + vfe32_put_ch_pong_addr(vfe32_ctrl->outpath.out1.ch0, + pyaddr_ping); + /* SWAP chroma channel*/ + vfe32_put_ch_ping_addr(vfe32_ctrl->outpath.out1.ch1, + pcbcraddr_pong); + vfe32_put_ch_pong_addr(vfe32_ctrl->outpath.out1.ch1, + pcbcraddr_ping); + CDBG("after swap: ping = 0x%p, pong = 0x%p\n", + (void *)pyaddr_pong, (void *)pyaddr_ping); +#endif + } +} + +static void vfe32_process_output_path_irq_2(void) +{ + uint32_t ping_pong; + uint32_t pyaddr, pcbcraddr; +#ifdef CONFIG_MSM_CAMERA_V4L2 + uint32_t pyaddr_ping, pcbcraddr_ping, pyaddr_pong, pcbcraddr_pong; +#endif + uint8_t out_bool = 0; + struct vfe32_free_buf *free_buf = NULL; + free_buf = vfe32_dequeue_free_buf(&vfe32_ctrl->outpath.out2); + + /* we render frames in the following conditions: + 1. Continuous mode and the free buffer is avaialable. + 2. In snapshot shot mode, free buffer is not always available. + -- when pending snapshot count is <=1, then no need to use + free buffer. + */ + out_bool = + ((vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_SNAPSHOT) && + (vfe32_ctrl->vfe_capture_count <= 1)) || free_buf; + + CDBG("%s: op mode = %d, capture_cnt = %d\n", __func__, + vfe32_ctrl->operation_mode, vfe32_ctrl->vfe_capture_count); + + if (out_bool) { + ping_pong = msm_io_r(vfe32_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS); + + /* Y channel */ + pyaddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out2.ch0); + /* Chroma channel */ + pcbcraddr = vfe32_get_ch_addr(ping_pong, + vfe32_ctrl->outpath.out2.ch1); + + CDBG("video output, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + pyaddr, pcbcraddr); + + if (free_buf) { + /* Y channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out2.ch0, + free_buf->paddr + free_buf->y_off); + /* Chroma channel */ + vfe32_put_ch_addr(ping_pong, + vfe32_ctrl->outpath.out2.ch1, + free_buf->paddr + free_buf->cbcr_off); + } + vfe_send_outmsg(MSG_ID_OUTPUT_V, pyaddr, pcbcraddr); + } else { + vfe32_ctrl->outpath.out2.frame_drop_cnt++; + pr_warning("path_irq_2 - no free buffer!\n"); +#ifdef CONFIG_MSM_CAMERA_V4L2 + pr_info("Swapping ping and pong\n"); + + /*get addresses*/ + /* Y channel */ + pyaddr_ping = vfe32_get_ch_ping_addr( + vfe32_ctrl->outpath.out2.ch0); + /* Chroma channel */ + pcbcraddr_ping = vfe32_get_ch_ping_addr( + vfe32_ctrl->outpath.out2.ch1); + /* Y channel */ + pyaddr_pong = vfe32_get_ch_pong_addr( + vfe32_ctrl->outpath.out2.ch0); + /* Chroma channel */ + pcbcraddr_pong = vfe32_get_ch_pong_addr( + vfe32_ctrl->outpath.out2.ch1); + + CDBG("ping = 0x%p, pong = 0x%p\n", (void *)pyaddr_ping, + (void *)pyaddr_pong); + CDBG("ping_cbcr = 0x%p, pong_cbcr = 0x%p\n", + (void *)pcbcraddr_ping, (void *)pcbcraddr_pong); + + /*put addresses*/ + /* SWAP y channel*/ + vfe32_put_ch_ping_addr(vfe32_ctrl->outpath.out2.ch0, + pyaddr_pong); + vfe32_put_ch_pong_addr(vfe32_ctrl->outpath.out2.ch0, + pyaddr_ping); + /* SWAP chroma channel*/ + vfe32_put_ch_ping_addr(vfe32_ctrl->outpath.out2.ch1, + pcbcraddr_pong); + vfe32_put_ch_pong_addr(vfe32_ctrl->outpath.out2.ch1, + pcbcraddr_ping); + CDBG("after swap: ping = 0x%p, pong = 0x%p\n", + (void *)pyaddr_pong, (void *)pyaddr_ping); +#endif + } +} + +static void vfe32_process_stats_comb_irq(uint32_t *irqstatus) +{ + return; +} + +static uint32_t vfe32_process_stats_irq_common(uint32_t statsNum, + uint32_t newAddr) { + + uint32_t pingpongStatus; + uint32_t returnAddr; + uint32_t pingpongAddr; + + /* must be 0=ping, 1=pong */ + pingpongStatus = + ((msm_io_r(vfe32_ctrl->vfebase + + VFE_BUS_PING_PONG_STATUS)) + & ((uint32_t)(1<<(statsNum + 7)))) >> (statsNum + 7); + /* stats bits starts at 7 */ + CDBG("statsNum %d, pingpongStatus %d\n", statsNum, pingpongStatus); + pingpongAddr = + ((uint32_t)(vfe32_ctrl->vfebase + + VFE_BUS_STATS_PING_PONG_BASE)) + + (3*statsNum)*4 + (1-pingpongStatus)*4; + returnAddr = msm_io_r((uint32_t *)pingpongAddr); + msm_io_w(newAddr, (uint32_t *)pingpongAddr); + return returnAddr; +} + +static void +vfe_send_stats_msg(uint32_t bufAddress, uint32_t statsNum) +{ + unsigned long flags; + struct vfe_message msg; + /* fill message with right content. */ + /* @todo This is causing issues, need further investigate */ + /* spin_lock_irqsave(&ctrl->state_lock, flags); */ + msg._u.msgStats.frameCounter = vfe32_ctrl->vfeFrameId; + msg._u.msgStats.buffer = bufAddress; + + switch (statsNum) { + case statsAeNum:{ + msg._d = MSG_ID_STATS_AEC; + spin_lock_irqsave(&vfe32_ctrl->aec_ack_lock, flags); + vfe32_ctrl->aecStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe32_ctrl->aec_ack_lock, flags); + } + break; + case statsAfNum:{ + msg._d = MSG_ID_STATS_AF; + spin_lock_irqsave(&vfe32_ctrl->af_ack_lock, flags); + vfe32_ctrl->afStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe32_ctrl->af_ack_lock, flags); + } + break; + case statsAwbNum: { + msg._d = MSG_ID_STATS_AWB; + spin_lock_irqsave(&vfe32_ctrl->awb_ack_lock, flags); + vfe32_ctrl->awbStatsControl.ackPending = TRUE; + spin_unlock_irqrestore(&vfe32_ctrl->awb_ack_lock, flags); + } + break; + + case statsIhistNum: { + msg._d = MSG_ID_STATS_IHIST; + vfe32_ctrl->ihistStatsControl.ackPending = TRUE; + } + break; + case statsRsNum: { + msg._d = MSG_ID_STATS_RS; + vfe32_ctrl->rsStatsControl.ackPending = TRUE; + } + break; + case statsCsNum: { + msg._d = MSG_ID_STATS_CS; + vfe32_ctrl->csStatsControl.ackPending = TRUE; + } + break; + + default: + goto stats_done; + } + + vfe32_proc_ops(msg._d, + &msg, sizeof(struct vfe_message)); +stats_done: + /* spin_unlock_irqrestore(&ctrl->state_lock, flags); */ + return; +} + +static void vfe32_process_stats_ae_irq(void) +{ + unsigned long flags; + spin_lock_irqsave(&vfe32_ctrl->aec_ack_lock, flags); + if (!(vfe32_ctrl->aecStatsControl.ackPending)) { + spin_unlock_irqrestore(&vfe32_ctrl->aec_ack_lock, flags); + vfe32_ctrl->aecStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsAeNum, + vfe32_ctrl->aecStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe32_ctrl->aecStatsControl.bufToRender, + statsAeNum); + } else{ + spin_unlock_irqrestore(&vfe32_ctrl->aec_ack_lock, flags); + vfe32_ctrl->aecStatsControl.droppedStatsFrameCount++; + } +} + +static void vfe32_process_stats_awb_irq(void) +{ + unsigned long flags; + spin_lock_irqsave(&vfe32_ctrl->awb_ack_lock, flags); + if (!(vfe32_ctrl->awbStatsControl.ackPending)) { + spin_unlock_irqrestore(&vfe32_ctrl->awb_ack_lock, flags); + vfe32_ctrl->awbStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsAwbNum, + vfe32_ctrl->awbStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe32_ctrl->awbStatsControl.bufToRender, + statsAwbNum); + } else{ + spin_unlock_irqrestore(&vfe32_ctrl->awb_ack_lock, flags); + vfe32_ctrl->awbStatsControl.droppedStatsFrameCount++; + } +} + +static void vfe32_process_stats_af_irq(void) +{ + unsigned long flags; + spin_lock_irqsave(&vfe32_ctrl->af_ack_lock, flags); + if (!(vfe32_ctrl->afStatsControl.ackPending)) { + spin_unlock_irqrestore(&vfe32_ctrl->af_ack_lock, flags); + vfe32_ctrl->afStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsAfNum, + vfe32_ctrl->afStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe32_ctrl->afStatsControl.bufToRender, + statsAfNum); + } else{ + spin_unlock_irqrestore(&vfe32_ctrl->af_ack_lock, flags); + vfe32_ctrl->afStatsControl.droppedStatsFrameCount++; + } +} + +static void vfe32_process_stats_ihist_irq(void) +{ + if (!(vfe32_ctrl->ihistStatsControl.ackPending)) { + vfe32_ctrl->ihistStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsIhistNum, + vfe32_ctrl->ihistStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe32_ctrl->ihistStatsControl.bufToRender, + statsIhistNum); + } else + vfe32_ctrl->ihistStatsControl.droppedStatsFrameCount++; +} + +static void vfe32_process_stats_rs_irq(void) +{ + if (!(vfe32_ctrl->rsStatsControl.ackPending)) { + vfe32_ctrl->rsStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsRsNum, + vfe32_ctrl->rsStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe32_ctrl->rsStatsControl.bufToRender, + statsRsNum); + } else + vfe32_ctrl->rsStatsControl.droppedStatsFrameCount++; +} + +static void vfe32_process_stats_cs_irq(void) +{ + if (!(vfe32_ctrl->csStatsControl.ackPending)) { + vfe32_ctrl->csStatsControl.bufToRender = + vfe32_process_stats_irq_common(statsCsNum, + vfe32_ctrl->csStatsControl.nextFrameAddrBuf); + + vfe_send_stats_msg(vfe32_ctrl->csStatsControl.bufToRender, + statsCsNum); + } else + vfe32_ctrl->csStatsControl.droppedStatsFrameCount++; +} + +static void vfe32_do_tasklet(unsigned long data) +{ + unsigned long flags; + + struct vfe32_isr_queue_cmd *qcmd = NULL; + + CDBG("=== vfe32_do_tasklet start ===\n"); + + while (atomic_read(&irq_cnt)) { + spin_lock_irqsave(&vfe32_ctrl->tasklet_lock, flags); + qcmd = list_first_entry(&vfe32_ctrl->tasklet_q, + struct vfe32_isr_queue_cmd, list); + atomic_sub(1, &irq_cnt); + + if (!qcmd) { + spin_unlock_irqrestore(&vfe32_ctrl->tasklet_lock, + flags); + return; + } + + list_del(&qcmd->list); + spin_unlock_irqrestore(&vfe32_ctrl->tasklet_lock, + flags); + + /* interrupt to be processed, *qcmd has the payload. */ + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_REG_UPDATE_MASK) { + CDBG("irq regUpdateIrq\n"); + vfe32_process_reg_update_irq(); + } + + if (qcmd->vfeInterruptStatus1 & + VFE_IMASK_WHILE_STOPPING_1) { + CDBG("irq resetAckIrq\n"); + vfe32_process_reset_irq(); + } + + if (atomic_read(&vfe32_ctrl->vstate)) { + if (qcmd->vfeInterruptStatus1 & + VFE32_IMASK_ERROR_ONLY_1) { + pr_err("irq errorIrq\n"); + vfe32_process_error_irq( + qcmd->vfeInterruptStatus1 & + VFE32_IMASK_ERROR_ONLY_1); + } + /* next, check output path related interrupts. */ + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE0_MASK) { + CDBG("Image composite done 0 irq occured.\n"); + vfe32_process_output_path_irq_0(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE1_MASK) { + CDBG("Image composite done 1 irq occured.\n"); + vfe32_process_output_path_irq_1(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE2_MASK) { + CDBG("Image composite done 2 irq occured.\n"); + vfe32_process_output_path_irq_2(); + } + /* in snapshot mode if done then send + snapshot done message */ + if (vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_SNAPSHOT || + vfe32_ctrl->operation_mode == + VFE_MODE_OF_OPERATION_RAW_SNAPSHOT) { + if ((vfe32_ctrl->outpath.out0.capture_cnt == 0) + && (vfe32_ctrl->outpath.out1. + capture_cnt == 0)) { + vfe32_send_msg_no_payload( + MSG_ID_SNAPSHOT_DONE); + + /* Ensure the write order while writing + to the cmd register using barrier */ + msm_io_w_mb( + CAMIF_COMMAND_STOP_IMMEDIATELY, + vfe32_ctrl->vfebase + + VFE_CAMIF_COMMAND); + } + } + /* then process stats irq. */ + if (vfe32_ctrl->stats_comp) { + /* process stats comb interrupt. */ + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_COMPOSIT_MASK) { + CDBG("Stats composite irq occured.\n"); + vfe32_process_stats_comb_irq( + &qcmd->vfeInterruptStatus0); + } + } else { + /* process individual stats interrupt. */ + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_AEC) { + CDBG("Stats AEC irq occured.\n"); + vfe32_process_stats_ae_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_AWB) { + CDBG("Stats AWB irq occured.\n"); + vfe32_process_stats_awb_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_AF) { + CDBG("Stats AF irq occured.\n"); + vfe32_process_stats_af_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_IHIST) { + CDBG("Stats IHIST irq occured.\n"); + vfe32_process_stats_ihist_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_RS) { + CDBG("Stats RS irq occured.\n"); + vfe32_process_stats_rs_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_STATS_CS) { + CDBG("Stats CS irq occured.\n"); + vfe32_process_stats_cs_irq(); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER0) { + CDBG("SYNC_TIMER 0 irq occured.\n"); + vfe32_send_msg_no_payload( + MSG_ID_SYNC_TIMER0_DONE); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER1) { + CDBG("SYNC_TIMER 1 irq occured.\n"); + vfe32_send_msg_no_payload( + MSG_ID_SYNC_TIMER1_DONE); + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_SYNC_TIMER2) { + CDBG("SYNC_TIMER 2 irq occured.\n"); + vfe32_send_msg_no_payload( + MSG_ID_SYNC_TIMER2_DONE); + } + } + } + if (qcmd->vfeInterruptStatus0 & + VFE_IRQ_STATUS0_CAMIF_SOF_MASK) { + CDBG("irq camifSofIrq\n"); + vfe32_process_camif_sof_irq(); + } + kfree(qcmd); + } + CDBG("=== vfe32_do_tasklet end ===\n"); +} + +DECLARE_TASKLET(vfe32_tasklet, vfe32_do_tasklet, 0); + +static irqreturn_t vfe32_parse_irq(int irq_num, void *data) +{ + unsigned long flags; + struct vfe32_irq_status irq; + struct vfe32_isr_queue_cmd *qcmd; + + CDBG("vfe_parse_irq\n"); + + vfe32_read_irq_status(&irq); + + if ((irq.vfeIrqStatus0 == 0) && (irq.vfeIrqStatus1 == 0)) { + CDBG("vfe_parse_irq: vfeIrqStatus0 & 1 are both 0!\n"); + return IRQ_HANDLED; + } + + qcmd = kzalloc(sizeof(struct vfe32_isr_queue_cmd), + GFP_ATOMIC); + if (!qcmd) { + pr_err("vfe_parse_irq: qcmd malloc failed!\n"); + return IRQ_HANDLED; + } + + spin_lock_irqsave(&vfe32_ctrl->stop_flag_lock, flags); + if (vfe32_ctrl->stop_ack_pending) { + irq.vfeIrqStatus0 &= VFE_IMASK_WHILE_STOPPING_0; + irq.vfeIrqStatus1 &= VFE_IMASK_WHILE_STOPPING_1; + } + spin_unlock_irqrestore(&vfe32_ctrl->stop_flag_lock, flags); + + CDBG("vfe_parse_irq: Irq_status0 = 0x%x, Irq_status1 = 0x%x.\n", + irq.vfeIrqStatus0, irq.vfeIrqStatus1); + + qcmd->vfeInterruptStatus0 = irq.vfeIrqStatus0; + qcmd->vfeInterruptStatus1 = irq.vfeIrqStatus1; + + spin_lock_irqsave(&vfe32_ctrl->tasklet_lock, flags); + list_add_tail(&qcmd->list, &vfe32_ctrl->tasklet_q); + + atomic_add(1, &irq_cnt); + spin_unlock_irqrestore(&vfe32_ctrl->tasklet_lock, flags); + tasklet_schedule(&vfe32_tasklet); + return IRQ_HANDLED; +} + +static int vfe32_resource_init(struct platform_device *pdev, void *sdata) +{ + struct resource *vfemem, *vfeirq, *vfeio; + int rc; + struct msm_camera_sensor_info *s_info; + s_info = pdev->dev.platform_data; + + pdev->resource = s_info->resource; + pdev->num_resources = s_info->num_resources; + + vfemem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!vfemem) { + pr_err("%s: no mem resource?\n", __func__); + return -ENODEV; + } + + vfeirq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!vfeirq) { + pr_err("%s: no irq resource?\n", __func__); + return -ENODEV; + } + + vfeio = request_mem_region(vfemem->start, + resource_size(vfemem), pdev->name); + if (!vfeio) { + pr_err("%s: VFE region already claimed\n", __func__); + return -EBUSY; + } + + vfe32_ctrl = kzalloc(sizeof(struct vfe32_ctrl_type), GFP_KERNEL); + if (!vfe32_ctrl) { + rc = -ENOMEM; + goto cmd_init_failed1; + } + + vfe32_ctrl->vfeirq = vfeirq->start; + + vfe32_ctrl->vfebase = + ioremap(vfemem->start, (vfemem->end - vfemem->start) + 1); + if (!vfe32_ctrl->vfebase) { + rc = -ENOMEM; + pr_err("%s: vfe ioremap failed\n", __func__); + goto cmd_init_failed2; + } + + vfe32_ctrl->extdata = + kmalloc(sizeof(struct vfe32_frame_extra), GFP_KERNEL); + if (!vfe32_ctrl->extdata) { + rc = -ENOMEM; + goto cmd_init_failed3; + } + + vfe32_ctrl->extlen = sizeof(struct vfe32_frame_extra); + + spin_lock_init(&vfe32_ctrl->stop_flag_lock); + spin_lock_init(&vfe32_ctrl->state_lock); + spin_lock_init(&vfe32_ctrl->io_lock); + spin_lock_init(&vfe32_ctrl->update_ack_lock); + spin_lock_init(&vfe32_ctrl->tasklet_lock); + + spin_lock_init(&vfe32_ctrl->aec_ack_lock); + spin_lock_init(&vfe32_ctrl->awb_ack_lock); + spin_lock_init(&vfe32_ctrl->af_ack_lock); + INIT_LIST_HEAD(&vfe32_ctrl->tasklet_q); + vfe32_init_free_buf_queues(); + + vfe32_ctrl->syncdata = sdata; + vfe32_ctrl->vfemem = vfemem; + vfe32_ctrl->vfeio = vfeio; + return 0; + +cmd_init_failed3: + free_irq(vfe32_ctrl->vfeirq, 0); + iounmap(vfe32_ctrl->vfebase); +cmd_init_failed2: + kfree(vfe32_ctrl); +cmd_init_failed1: + release_mem_region(vfemem->start, (vfemem->end - vfemem->start) + 1); + return rc; +} + +static long msm_vfe_subdev_ioctl(struct v4l2_subdev *sd, + unsigned int subdev_cmd, void *arg) +{ + struct msm_vfe32_cmd vfecmd; + struct msm_camvfe_params *vfe_params = + (struct msm_camvfe_params *)arg; + struct msm_vfe_cfg_cmd *cmd = vfe_params->vfe_cfg; + void *data = vfe_params->data; + + long rc = 0; + uint32_t i = 0; + struct vfe_cmd_stats_buf *scfg = NULL; + struct msm_pmem_region *regptr = NULL; + struct vfe_cmd_stats_ack *sack = NULL; + if (cmd->cmd_type != CMD_FRAME_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AEC_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AWB_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_IHIST_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_RS_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_CS_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AF_BUF_RELEASE) { + if (copy_from_user(&vfecmd, + (void __user *)(cmd->value), + sizeof(vfecmd))) { + pr_err("%s %d: copy_from_user failed\n", __func__, + __LINE__); + return -EFAULT; + } + } else { + /* here eith stats release or frame release. */ + if (cmd->cmd_type != CMD_FRAME_BUF_RELEASE) { + /* then must be stats release. */ + if (!data) + return -EFAULT; + sack = kmalloc(sizeof(struct vfe_cmd_stats_ack), + GFP_ATOMIC); + if (!sack) + return -ENOMEM; + + sack->nextStatsBuf = *(uint32_t *)data; + } + } + + CDBG("%s: cmdType = %d\n", __func__, cmd->cmd_type); + + if ((cmd->cmd_type == CMD_STATS_AF_ENABLE) || + (cmd->cmd_type == CMD_STATS_AWB_ENABLE) || + (cmd->cmd_type == CMD_STATS_IHIST_ENABLE) || + (cmd->cmd_type == CMD_STATS_RS_ENABLE) || + (cmd->cmd_type == CMD_STATS_CS_ENABLE) || + (cmd->cmd_type == CMD_STATS_AEC_ENABLE)) { + struct axidata *axid; + axid = data; + if (!axid) { + rc = -EFAULT; + goto vfe32_config_done; + } + + scfg = + kmalloc(sizeof(struct vfe_cmd_stats_buf), + GFP_ATOMIC); + if (!scfg) { + rc = -ENOMEM; + goto vfe32_config_done; + } + regptr = axid->region; + if (axid->bufnum1 > 0) { + for (i = 0; i < axid->bufnum1; i++) { + scfg->statsBuf[i] = + (uint32_t)(regptr->paddr); + regptr++; + } + } + /* individual */ + switch (cmd->cmd_type) { + case CMD_STATS_AEC_ENABLE: + rc = vfe_stats_aec_buf_init(scfg); + break; + case CMD_STATS_AF_ENABLE: + rc = vfe_stats_af_buf_init(scfg); + break; + case CMD_STATS_AWB_ENABLE: + rc = vfe_stats_awb_buf_init(scfg); + break; + case CMD_STATS_IHIST_ENABLE: + rc = vfe_stats_ihist_buf_init(scfg); + break; + case CMD_STATS_RS_ENABLE: + rc = vfe_stats_rs_buf_init(scfg); + break; + case CMD_STATS_CS_ENABLE: + rc = vfe_stats_cs_buf_init(scfg); + break; + } + } + switch (cmd->cmd_type) { + case CMD_GENERAL: + rc = vfe32_proc_general(&vfecmd); + break; + case CMD_FRAME_BUF_RELEASE: { + struct msm_frame *b; + unsigned long p; + struct vfe32_output_ch *outch = NULL; + if (!data) { + rc = -EFAULT; + break; + } + + b = (struct msm_frame *)(cmd->value); + p = *(unsigned long *)data; + + CDBG("CMD_FRAME_BUF_RELEASE b->path = %d\n", b->path); + + if ((b->path & OUTPUT_TYPE_P) || (b->path & OUTPUT_TYPE_T)) { + CDBG("CMD_FRAME_BUF_RELEASE got free buffer\n"); + outch = &vfe32_ctrl->outpath.out0; + } else if (b->path & OUTPUT_TYPE_S) { + outch = &vfe32_ctrl->outpath.out1; + } else if (b->path & OUTPUT_TYPE_V) { + outch = &vfe32_ctrl->outpath.out2; + } else { + rc = -EFAULT; + break; + } + + rc = vfe32_enqueue_free_buf(outch, p, b->y_off, b->cbcr_off); + } + break; + + case CMD_SNAP_BUF_RELEASE: + break; + case CMD_STATS_AEC_BUF_RELEASE: + vfe32_stats_aec_ack(sack); + break; + case CMD_STATS_AF_BUF_RELEASE: + vfe32_stats_af_ack(sack); + break; + case CMD_STATS_AWB_BUF_RELEASE: + vfe32_stats_awb_ack(sack); + break; + + case CMD_STATS_IHIST_BUF_RELEASE: + vfe32_stats_ihist_ack(sack); + break; + case CMD_STATS_RS_BUF_RELEASE: + vfe32_stats_rs_ack(sack); + break; + case CMD_STATS_CS_BUF_RELEASE: + vfe32_stats_cs_ack(sack); + break; + + case CMD_AXI_CFG_PREVIEW: { + struct axidata *axid; + uint32_t *axio = NULL; + axid = data; + if (!axid) { + rc = -EFAULT; + break; + } + axio = + kmalloc(vfe32_cmd[V32_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe32_cmd[V32_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe32_config_axi(OUTPUT_2, axid, axio); + kfree(axio); + } + break; + + case CMD_RAW_PICT_AXI_CFG: { + struct axidata *axid; + uint32_t *axio = NULL; + axid = data; + if (!axid) { + rc = -EFAULT; + break; + } + axio = kmalloc(vfe32_cmd[V32_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe32_cmd[V32_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe32_config_axi(CAMIF_TO_AXI_VIA_OUTPUT_2, axid, axio); + kfree(axio); + } + break; + + case CMD_AXI_CFG_SNAP: { + struct axidata *axid; + uint32_t *axio = NULL; + axid = data; + if (!axid) + return -EFAULT; + axio = + kmalloc(vfe32_cmd[V32_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe32_cmd[V32_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe32_config_axi(OUTPUT_1_AND_2, axid, axio); + kfree(axio); + } + break; + + case CMD_AXI_CFG_VIDEO: { + struct axidata *axid; + uint32_t *axio = NULL; + axid = data; + if (!axid) { + rc = -EFAULT; + break; + } + + axio = kmalloc(vfe32_cmd[V32_AXI_OUT_CFG].length, + GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + break; + } + + if (copy_from_user(axio, (void __user *)(vfecmd.value), + vfe32_cmd[V32_AXI_OUT_CFG].length)) { + kfree(axio); + rc = -EFAULT; + break; + } + vfe32_config_axi(OUTPUT_1_AND_3, axid, axio); + kfree(axio); + } + break; + default: + break; + } +vfe32_config_done: + kfree(scfg); + kfree(sack); + CDBG("%s done: rc = %d\n", __func__, (int) rc); + return rc; +} + +static const struct v4l2_subdev_core_ops msm_vfe_subdev_core_ops = { + .ioctl = msm_vfe_subdev_ioctl, +}; + +static const struct v4l2_subdev_ops msm_vfe_subdev_ops = { + .core = &msm_vfe_subdev_core_ops, +}; + +int msm_vfe_subdev_init(struct v4l2_subdev *sd, void *data, + struct platform_device *pdev) +{ + int rc = 0; + struct msm_camera_sensor_info *sinfo = pdev->dev.platform_data; + struct msm_camera_device_platform_data *camdev = sinfo->pdata; + + v4l2_subdev_init(sd, &msm_vfe_subdev_ops); + v4l2_set_subdev_hostdata(sd, data); + snprintf(sd->name, sizeof(sd->name), "vfe3.2"); + + vfe_syncdata = data; + + camio_clk = camdev->ioclk; + + rc = vfe32_resource_init(pdev, vfe_syncdata); + if (rc < 0) + return rc; + + vfe32_ctrl->subdev = sd; + /* Bring up all the required GPIOs and Clocks */ + rc = msm_camio_enable(pdev); + msm_camio_set_perf_lvl(S_INIT); + msm_camio_set_perf_lvl(S_PREVIEW); + + /* TO DO: Need to release the VFE resources */ + rc = request_irq(vfe32_ctrl->vfeirq, vfe32_parse_irq, + IRQF_TRIGGER_RISING, "vfe", 0); + + return rc; +} + +void msm_vfe_subdev_release(struct platform_device *pdev) +{ + struct resource *vfemem, *vfeio; + + vfe32_reset_free_buf_queues(); + CDBG("%s, free_irq\n", __func__); + free_irq(vfe32_ctrl->vfeirq, 0); + tasklet_kill(&vfe32_tasklet); + + if (atomic_read(&irq_cnt)) + pr_warning("%s, Warning IRQ Count not ZERO\n", __func__); + + vfemem = vfe32_ctrl->vfemem; + vfeio = vfe32_ctrl->vfeio; + + kfree(vfe32_ctrl->extdata); + iounmap(vfe32_ctrl->vfebase); + kfree(vfe32_ctrl); + vfe32_ctrl = NULL; + release_mem_region(vfemem->start, (vfemem->end - vfemem->start) + 1); + CDBG("%s, msm_camio_disable\n", __func__); + msm_camio_disable(pdev); + msm_camio_set_perf_lvl(S_EXIT); + + vfe_syncdata = NULL; +} + +void msm_camvpe_fn_init(struct msm_camvpe_fn *fptr, void *data) +{ + fptr->vpe_reg = msm_vpe_reg; + fptr->send_frame_to_vpe = msm_send_frame_to_vpe; + fptr->vpe_config = msm_vpe_config; + fptr->vpe_cfg_update = msm_vpe_cfg_update; + fptr->dis = &(vpe_ctrl->dis_en); + vpe_ctrl->syncdata = data; +} diff --git a/drivers/media/video/msm/msm_vfe32.h b/drivers/media/video/msm/msm_vfe32.h new file mode 100644 index 00000000000..4d48c6b1fcc --- /dev/null +++ b/drivers/media/video/msm/msm_vfe32.h @@ -0,0 +1,1104 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MSM_VFE32_H__ +#define __MSM_VFE32_H__ + +#define TRUE 1 +#define FALSE 0 + +/* at start of camif, bit 1:0 = 0x01:enable + * image data capture at frame boundary. */ +#define CAMIF_COMMAND_START 0x00000005 + +/* bit 2= 0x1:clear the CAMIF_STATUS register + * value. */ +#define CAMIF_COMMAND_CLEAR 0x00000004 + +/* at stop of vfe pipeline, for now it is assumed + * that camif will stop at any time. Bit 1:0 = 0x10: + * disable image data capture immediately. */ +#define CAMIF_COMMAND_STOP_IMMEDIATELY 0x00000002 + +/* at stop of vfe pipeline, for now it is assumed + * that camif will stop at any time. Bit 1:0 = 0x00: + * disable image data capture at frame boundary */ +#define CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY 0x00000000 + +/* to halt axi bridge */ +#define AXI_HALT 0x00000001 + +/* clear the halt bit. */ +#define AXI_HALT_CLEAR 0x00000000 + +/* reset the pipeline when stop command is issued. + * (without reset the register.) bit 26-32 = 0, + * domain reset, bit 0-9 = 1 for module reset, except + * register module. */ +#define VFE_RESET_UPON_STOP_CMD 0x000003ef + +/* reset the pipeline when reset command. + * bit 26-32 = 0, domain reset, bit 0-9 = 1 for module reset. */ +#define VFE_RESET_UPON_RESET_CMD 0x000003ff + +/* bit 5 is for axi status idle or busy. + * 1 = halted, 0 = busy */ +#define AXI_STATUS_BUSY_MASK 0x00000020 + +/* bit 0 & bit 1 = 1, both y and cbcr irqs need to be present + * for frame done interrupt */ +#define VFE_COMP_IRQ_BOTH_Y_CBCR 3 + +/* bit 1 = 1, only cbcr irq triggers frame done interrupt */ +#define VFE_COMP_IRQ_CBCR_ONLY 2 + +/* bit 0 = 1, only y irq triggers frame done interrupt */ +#define VFE_COMP_IRQ_Y_ONLY 1 + +/* bit 0 = 1, PM go; bit1 = 1, PM stop */ +#define VFE_PERFORMANCE_MONITOR_GO 0x00000001 +#define VFE_PERFORMANCE_MONITOR_STOP 0x00000002 + +/* bit 0 = 1, test gen go; bit1 = 1, test gen stop */ +#define VFE_TEST_GEN_GO 0x00000001 +#define VFE_TEST_GEN_STOP 0x00000002 + +/* the chroma is assumed to be interpolated between + * the luma samples. JPEG 4:2:2 */ +#define VFE_CHROMA_UPSAMPLE_INTERPOLATED 0 + +/* constants for irq registers */ +#define VFE_DISABLE_ALL_IRQS 0 +/* bit =1 is to clear the corresponding bit in VFE_IRQ_STATUS. */ +#define VFE_CLEAR_ALL_IRQS 0xffffffff + +#define VFE_IRQ_STATUS0_CAMIF_SOF_MASK 0x00000001 +#define VFE_IRQ_STATUS0_REG_UPDATE_MASK 0x00000020 +#define VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE0_MASK 0x00200000 +#define VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE1_MASK 0x00400000 +#define VFE_IRQ_STATUS0_IMAGE_COMPOSIT_DONE2_MASK 0x00800000 +#define VFE_IRQ_STATUS1_RESET_AXI_HALT_ACK_MASK 0x00800000 +#define VFE_IRQ_STATUS0_STATS_COMPOSIT_MASK 0x01000000 + +#define VFE_IRQ_STATUS0_STATS_AEC 0x2000 /* bit 13 */ +#define VFE_IRQ_STATUS0_STATS_AF 0x4000 /* bit 14 */ +#define VFE_IRQ_STATUS0_STATS_AWB 0x8000 /* bit 15 */ +#define VFE_IRQ_STATUS0_STATS_RS 0x10000 /* bit 16 */ +#define VFE_IRQ_STATUS0_STATS_CS 0x20000 /* bit 17 */ +#define VFE_IRQ_STATUS0_STATS_IHIST 0x40000 /* bit 18 */ + +#define VFE_IRQ_STATUS0_SYNC_TIMER0 0x2000000 /* bit 25 */ +#define VFE_IRQ_STATUS0_SYNC_TIMER1 0x4000000 /* bit 26 */ +#define VFE_IRQ_STATUS0_SYNC_TIMER2 0x8000000 /* bit 27 */ +#define VFE_IRQ_STATUS0_ASYNC_TIMER0 0x10000000 /* bit 28 */ +#define VFE_IRQ_STATUS0_ASYNC_TIMER1 0x20000000 /* bit 29 */ +#define VFE_IRQ_STATUS0_ASYNC_TIMER2 0x40000000 /* bit 30 */ +#define VFE_IRQ_STATUS0_ASYNC_TIMER3 0x80000000 /* bit 32 */ + +/* imask for while waiting for stop ack, driver has already + * requested stop, waiting for reset irq, and async timer irq. + * For irq_status_0, bit 28-32 are for async timer. For + * irq_status_1, bit 22 for reset irq, bit 23 for axi_halt_ack + irq */ +#define VFE_IMASK_WHILE_STOPPING_0 0xF0000000 +#define VFE_IMASK_WHILE_STOPPING_1 0x00800000 + +/* no error irq in mask 0 */ +#define VFE_IMASK_ERROR_ONLY_0 0x0 +/* when normal case, don't want to block error status. */ +/* bit 0-21 are error irq bits */ +#define VFE_IMASK_ERROR_ONLY_1 0x003fffff + +/* For BPC bit 0,bit 12-17 and bit 26 -20 are set to zero and other's 1 */ +#define BPC_MASK 0xF80C0FFE + +/* For BPC bit 1 and 2 are set to zero and other's 1 */ +#define ABF_MASK 0xFFFFFFF9 + + +/* For DBPC bit 0 is set to zero and other's 1 */ +#define DBPC_MASK 0xFFFFFFFE + +/* For DBPC bit 1 is set to zero and other's 1 */ +#define DBCC_MASK 0xFFFFFFFD + +/* For MCE enable bit 28 set to zero and other's 1 */ +#define MCE_EN_MASK 0xEFFFFFFF + +/* For MCE Q_K bit 28 to 32 set to zero and other's 1 */ +#define MCE_Q_K_MASK 0x0FFFFFFF + +#define AE_BG_ENABLE_MASK 0x00000020 /* bit 5 */ +#define AF_BF_ENABLE_MASK 0x00000040 /* bit 6 */ +#define AWB_ENABLE_MASK 0x00000080 /* bit 7 */ +#define RS_ENABLE_MASK 0x00000100 /* bit 8 */ +#define CS_ENABLE_MASK 0x00000200 /* bit 9 */ +#define RS_CS_ENABLE_MASK 0x00000300 /* bit 8,9 */ +#define CLF_ENABLE_MASK 0x00002000 /* bit 13 */ +#define IHIST_ENABLE_MASK 0x00010000 /* bit 16 */ +#define STATS_ENABLE_MASK 0x000903E0 /* bit 19,16,9,8,7,6,5*/ + +#define VFE_REG_UPDATE_TRIGGER 1 +#define VFE_PM_BUF_MAX_CNT_MASK 0xFF +#define VFE_DMI_CFG_DEFAULT 0x00000100 +#define LENS_ROLL_OFF_DELTA_TABLE_OFFSET 32 +#define VFE_AE_PINGPONG_STATUS_BIT 0x80 +#define VFE_AF_PINGPONG_STATUS_BIT 0x100 +#define VFE_AWB_PINGPONG_STATUS_BIT 0x200 + + +enum VFE32_DMI_RAM_SEL { + NO_MEM_SELECTED = 0, + BLACK_LUT_RAM_BANK0 = 0x1, + BLACK_LUT_RAM_BANK1 = 0x2, + ROLLOFF_RAM = 0x3, + DEMOSAIC_LUT_RAM_BANK0 = 0x4, + DEMOSAIC_LUT_RAM_BANK1 = 0x5, + STATS_BHIST_RAM0 = 0x6, + STATS_BHIST_RAM1 = 0x7, + RGBLUT_RAM_CH0_BANK0 = 0x8, + RGBLUT_RAM_CH0_BANK1 = 0x9, + RGBLUT_RAM_CH1_BANK0 = 0xa, + RGBLUT_RAM_CH1_BANK1 = 0xb, + RGBLUT_RAM_CH2_BANK0 = 0xc, + RGBLUT_RAM_CH2_BANK1 = 0xd, + RGBLUT_CHX_BANK0 = 0xe, + RGBLUT_CHX_BANK1 = 0xf, + STATS_IHIST_RAM = 0x10, + LUMA_ADAPT_LUT_RAM_BANK0 = 0x11, + LUMA_ADAPT_LUT_RAM_BANK1 = 0x12 +}; + +enum VFE_STATE { + VFE_STATE_IDLE, + VFE_STATE_ACTIVE +}; + +#define V32_DUMMY_0 0 +#define V32_SET_CLK 1 +#define V32_RESET 2 +#define V32_START 3 +#define V32_TEST_GEN_START 4 +#define V32_OPERATION_CFG 5 +#define V32_AXI_OUT_CFG 6 +#define V32_CAMIF_CFG 7 +#define V32_AXI_INPUT_CFG 8 +#define V32_BLACK_LEVEL_CFG 9 +#define V32_ROLL_OFF_CFG 10 +#define V32_DEMUX_CFG 11 +#define V32_FOV_CFG 12 +#define V32_MAIN_SCALER_CFG 13 +#define V32_WB_CFG 14 +#define V32_COLOR_COR_CFG 15 +#define V32_RGB_G_CFG 16 +#define V32_LA_CFG 17 +#define V32_CHROMA_EN_CFG 18 +#define V32_CHROMA_SUP_CFG 19 +#define V32_MCE_CFG 20 +#define V32_SK_ENHAN_CFG 21 +#define V32_ASF_CFG 22 +#define V32_S2Y_CFG 23 +#define V32_S2CbCr_CFG 24 +#define V32_CHROMA_SUBS_CFG 25 +#define V32_OUT_CLAMP_CFG 26 +#define V32_FRAME_SKIP_CFG 27 +#define V32_DUMMY_1 28 +#define V32_DUMMY_2 29 +#define V32_DUMMY_3 30 +#define V32_UPDATE 31 +#define V32_BL_LVL_UPDATE 32 +#define V32_DEMUX_UPDATE 33 +#define V32_FOV_UPDATE 34 +#define V32_MAIN_SCALER_UPDATE 35 +#define V32_WB_UPDATE 36 +#define V32_COLOR_COR_UPDATE 37 +#define V32_RGB_G_UPDATE 38 +#define V32_LA_UPDATE 39 +#define V32_CHROMA_EN_UPDATE 40 +#define V32_CHROMA_SUP_UPDATE 41 +#define V32_MCE_UPDATE 42 +#define V32_SK_ENHAN_UPDATE 43 +#define V32_S2CbCr_UPDATE 44 +#define V32_S2Y_UPDATE 45 +#define V32_ASF_UPDATE 46 +#define V32_FRAME_SKIP_UPDATE 47 +#define V32_CAMIF_FRAME_UPDATE 48 +#define V32_STATS_AF_UPDATE 49 +#define V32_STATS_AE_UPDATE 50 +#define V32_STATS_AWB_UPDATE 51 +#define V32_STATS_RS_UPDATE 52 +#define V32_STATS_CS_UPDATE 53 +#define V32_STATS_SKIN_UPDATE 54 +#define V32_STATS_IHIST_UPDATE 55 +#define V32_DUMMY_4 56 +#define V32_EPOCH1_ACK 57 +#define V32_EPOCH2_ACK 58 +#define V32_START_RECORDING 59 +#define V32_STOP_RECORDING 60 +#define V32_DUMMY_5 61 +#define V32_DUMMY_6 62 +#define V32_CAPTURE 63 +#define V32_DUMMY_7 64 +#define V32_STOP 65 +#define V32_GET_HW_VERSION 66 +#define V32_GET_FRAME_SKIP_COUNTS 67 +#define V32_OUTPUT1_BUFFER_ENQ 68 +#define V32_OUTPUT2_BUFFER_ENQ 69 +#define V32_OUTPUT3_BUFFER_ENQ 70 +#define V32_JPEG_OUT_BUF_ENQ 71 +#define V32_RAW_OUT_BUF_ENQ 72 +#define V32_RAW_IN_BUF_ENQ 73 +#define V32_STATS_AF_ENQ 74 +#define V32_STATS_AE_ENQ 75 +#define V32_STATS_AWB_ENQ 76 +#define V32_STATS_RS_ENQ 77 +#define V32_STATS_CS_ENQ 78 +#define V32_STATS_SKIN_ENQ 79 +#define V32_STATS_IHIST_ENQ 80 +#define V32_DUMMY_8 81 +#define V32_JPEG_ENC_CFG 82 +#define V32_DUMMY_9 83 +#define V32_STATS_AF_START 84 +#define V32_STATS_AF_STOP 85 +#define V32_STATS_AE_START 86 +#define V32_STATS_AE_STOP 87 +#define V32_STATS_AWB_START 88 +#define V32_STATS_AWB_STOP 89 +#define V32_STATS_RS_START 90 +#define V32_STATS_RS_STOP 91 +#define V32_STATS_CS_START 92 +#define V32_STATS_CS_STOP 93 +#define V32_STATS_SKIN_START 94 +#define V32_STATS_SKIN_STOP 95 +#define V32_STATS_IHIST_START 96 +#define V32_STATS_IHIST_STOP 97 +#define V32_DUMMY_10 98 +#define V32_SYNC_TIMER_SETTING 99 +#define V32_ASYNC_TIMER_SETTING 100 +#define V32_LIVESHOT 101 +#define V32_LA_SETUP 102 + +#define V32_LINEARIZATION 103 +#define V32_DEMOSAICV3 104 +#define V32_DEMOSAICV3_ABCC_CFG 105 +#define V32_DEMOSAICV3_DBCC_CFG 106 +#define V32_DEMOSAICV3_DBPC_CFG 107 +#define V32_DEMOSAICV3_ABF_CFG 108 +#define V32_DEMOSAICV3_ABCC_UPDATE 109 +#define V32_DEMOSAICV3_DBCC_UPDATE 110 +#define V32_DEMOSAICV3_DBPC_UPDATE 111 +#define V32_EZTUNE_CFG 112 + +#define V32_CLF_CFG 118 +#define V32_CLF_UPDATE 119 +#define V32_STATS_IHIST3_2_START 120 +#define V32_STATS_IHIST3_2_UPDATE 121 +#define V32_CAMIF3_2_CONFIG 122 + +#define V32_CAMIF_OFF 0x000001E4 +#define V32_CAMIF_LEN 32 + +#define V32_DEMUX_OFF 0x00000284 +#define V32_DEMUX_LEN 20 + +#define V32_DEMOSAICV3_0_OFF 0x00000298 +#define V32_DEMOSAICV3_0_LEN 4 +#define V32_DEMOSAICV3_1_OFF 0x0000061C +#define V32_DEMOSAICV3_1_LEN 88 +/* BPC */ +#define V32_DEMOSAIC_2_OFF 0x0000029C +#define V32_DEMOSAIC_2_LEN 8 + +#define V32_OUT_CLAMP_OFF 0x00000524 +#define V32_OUT_CLAMP_LEN 8 + +#define V32_OPERATION_CFG_LEN 32 + +#define V32_AXI_OUT_OFF 0x00000038 +#define V32_AXI_OUT_LEN 212 +#define V32_AXI_CH_INF_LEN 24 +#define V32_AXI_CFG_LEN 47 + +#define V32_FRAME_SKIP_OFF 0x00000504 +#define V32_FRAME_SKIP_LEN 32 + +#define V32_CHROMA_SUBS_OFF 0x000004F8 +#define V32_CHROMA_SUBS_LEN 12 + +#define V32_FOV_OFF 0x00000360 +#define V32_FOV_LEN 8 + +#define V32_MAIN_SCALER_OFF 0x00000368 +#define V32_MAIN_SCALER_LEN 28 + +#define V32_S2Y_OFF 0x000004D0 +#define V32_S2Y_LEN 20 + +#define V32_S2CbCr_OFF 0x000004E4 +#define V32_S2CbCr_LEN 20 + +#define V32_CHROMA_EN_OFF 0x000003C4 +#define V32_CHROMA_EN_LEN 36 + +#define V32_SYNC_TIMER_OFF 0x0000020C +#define V32_SYNC_TIMER_POLARITY_OFF 0x00000234 +#define V32_TIMER_SELECT_OFF 0x0000025C +#define V32_SYNC_TIMER_LEN 28 + +#define V32_ASYNC_TIMER_OFF 0x00000238 +#define V32_ASYNC_TIMER_LEN 28 + +#define V32_BLACK_LEVEL_OFF 0x00000264 +#define V32_BLACK_LEVEL_LEN 16 + +#define V32_ROLL_OFF_CFG_OFF 0x00000274 +#define V32_ROLL_OFF_CFG_LEN 16 + +#define V32_COLOR_COR_OFF 0x00000388 +#define V32_COLOR_COR_LEN 52 + +#define V32_WB_OFF 0x00000384 +#define V32_WB_LEN 4 + +#define V32_RGB_G_OFF 0x000003BC +#define V32_RGB_G_LEN 4 + +#define V32_LA_OFF 0x000003C0 +#define V32_LA_LEN 4 + +#define V32_SCE_OFF 0x00000418 +#define V32_SCE_LEN 136 + +#define V32_CHROMA_SUP_OFF 0x000003E8 +#define V32_CHROMA_SUP_LEN 12 + +#define V32_MCE_OFF 0x000003E8 +#define V32_MCE_LEN 36 +#define V32_STATS_AF_OFF 0x0000053c +#define V32_STATS_AF_LEN 16 + +#define V32_STATS_AE_OFF 0x00000534 +#define V32_STATS_AE_LEN 8 + +#define V32_STATS_AWB_OFF 0x0000054c +#define V32_STATS_AWB_LEN 32 + +#define V32_STATS_IHIST_OFF 0x0000057c +#define V32_STATS_IHIST_LEN 8 + +#define V32_STATS_RS_OFF 0x0000056c +#define V32_STATS_RS_LEN 8 + +#define V32_STATS_CS_OFF 0x00000574 +#define V32_STATS_CS_LEN 8 + + +#define V32_ASF_OFF 0x000004A0 +#define V32_ASF_LEN 48 +#define V32_ASF_UPDATE_LEN 36 + +#define V32_CAPTURE_LEN 4 + +#define V32_LINEARIZATION_OFF1 0x00000264 +#define V32_LINEARIZATION_LEN1 16 + +#define V32_LINEARIZATION_OFF2 0x0000067C +#define V32_LINEARIZATION_LEN2 52 + +#define V32_DEMOSAICV3_OFF 0x00000298 +#define V32_DEMOSAICV3_LEN 4 + +#define V32_DEMOSAICV3_DBPC_CFG_OFF 0x0000029C +#define V32_DEMOSAICV3_DBPC_LEN 4 + +#define V32_DEMOSAICV3_DBPC_CFG_OFF0 0x000002a0 +#define V32_DEMOSAICV3_DBPC_CFG_OFF1 0x00000604 +#define V32_DEMOSAICV3_DBPC_CFG_OFF2 0x00000608 + +#define V32_DEMOSAICV3_DBCC_OFF 0x0000060C +#define V32_DEMOSAICV3_DBCC_LEN 16 + +#define V32_DEMOSAICV3_ABF_OFF 0x0000029C +#define V32_DEMOSAICV3_ABF_LEN + +#define V32_EZTUNE_CFG_OFF 0x00000010 +#define V32_EZTUNE_CFG_LEN 4 + +struct vfe_cmd_hw_version { + uint32_t minorVersion; + uint32_t majorVersion; + uint32_t coreVersion; +}; + +enum VFE_AXI_OUTPUT_MODE { + VFE_AXI_OUTPUT_MODE_Output1, + VFE_AXI_OUTPUT_MODE_Output2, + VFE_AXI_OUTPUT_MODE_Output1AndOutput2, + VFE_AXI_OUTPUT_MODE_CAMIFToAXIViaOutput2, + VFE_AXI_OUTPUT_MODE_Output2AndCAMIFToAXIViaOutput1, + VFE_AXI_OUTPUT_MODE_Output1AndCAMIFToAXIViaOutput2, + VFE_AXI_LAST_OUTPUT_MODE_ENUM +}; + +enum VFE_RAW_WR_PATH_SEL { + VFE_RAW_OUTPUT_DISABLED, + VFE_RAW_OUTPUT_ENC_CBCR_PATH, + VFE_RAW_OUTPUT_VIEW_CBCR_PATH, + VFE_RAW_OUTPUT_PATH_INVALID +}; + + +#define VFE_AXI_OUTPUT_BURST_LENGTH 4 +#define VFE_MAX_NUM_FRAGMENTS_PER_FRAME 4 +#define VFE_AXI_OUTPUT_CFG_FRAME_COUNT 3 + +struct vfe_cmds_per_write_master { + uint16_t imageWidth; + uint16_t imageHeight; + uint16_t outRowCount; + uint16_t outRowIncrement; + uint32_t outFragments[VFE_AXI_OUTPUT_CFG_FRAME_COUNT] + [VFE_MAX_NUM_FRAGMENTS_PER_FRAME]; +}; + +struct vfe_cmds_axi_per_output_path { + uint8_t fragmentCount; + struct vfe_cmds_per_write_master firstWM; + struct vfe_cmds_per_write_master secondWM; +}; + +enum VFE_AXI_BURST_LENGTH { + VFE_AXI_BURST_LENGTH_IS_2 = 2, + VFE_AXI_BURST_LENGTH_IS_4 = 4, + VFE_AXI_BURST_LENGTH_IS_8 = 8, + VFE_AXI_BURST_LENGTH_IS_16 = 16 +}; + + +struct vfe_cmd_fov_crop_config { + uint8_t enable; + uint16_t firstPixel; + uint16_t lastPixel; + uint16_t firstLine; + uint16_t lastLine; +}; + +struct vfe_cmds_main_scaler_stripe_init { + uint16_t MNCounterInit; + uint16_t phaseInit; +}; + +struct vfe_cmds_scaler_one_dimension { + uint8_t enable; + uint16_t inputSize; + uint16_t outputSize; + uint32_t phaseMultiplicationFactor; + uint8_t interpolationResolution; +}; + +struct vfe_cmd_main_scaler_config { + uint8_t enable; + struct vfe_cmds_scaler_one_dimension hconfig; + struct vfe_cmds_scaler_one_dimension vconfig; + struct vfe_cmds_main_scaler_stripe_init MNInitH; + struct vfe_cmds_main_scaler_stripe_init MNInitV; +}; + +struct vfe_cmd_scaler2_config { + uint8_t enable; + struct vfe_cmds_scaler_one_dimension hconfig; + struct vfe_cmds_scaler_one_dimension vconfig; +}; + + +struct vfe_cmd_frame_skip_update { + uint32_t output1Pattern; + uint32_t output2Pattern; +}; + +struct vfe_cmd_output_clamp_config { + uint8_t minCh0; + uint8_t minCh1; + uint8_t minCh2; + uint8_t maxCh0; + uint8_t maxCh1; + uint8_t maxCh2; +}; + +struct vfe_cmd_chroma_subsample_config { + uint8_t enable; + uint8_t cropEnable; + uint8_t vsubSampleEnable; + uint8_t hsubSampleEnable; + uint8_t vCosited; + uint8_t hCosited; + uint8_t vCositedPhase; + uint8_t hCositedPhase; + uint16_t cropWidthFirstPixel; + uint16_t cropWidthLastPixel; + uint16_t cropHeightFirstLine; + uint16_t cropHeightLastLine; +}; + +enum VFE_START_PIXEL_PATTERN { + VFE_BAYER_RGRGRG, + VFE_BAYER_GRGRGR, + VFE_BAYER_BGBGBG, + VFE_BAYER_GBGBGB, + VFE_YUV_YCbYCr, + VFE_YUV_YCrYCb, + VFE_YUV_CbYCrY, + VFE_YUV_CrYCbY +}; + +enum VFE_BUS_RD_INPUT_PIXEL_PATTERN { + VFE_BAYER_RAW, + VFE_YUV_INTERLEAVED, + VFE_YUV_PSEUDO_PLANAR_Y, + VFE_YUV_PSEUDO_PLANAR_CBCR +}; + +enum VFE_YUV_INPUT_COSITING_MODE { + VFE_YUV_COSITED, + VFE_YUV_INTERPOLATED +}; + + +/* 13*1 */ +#define VFE32_ROLL_OFF_INIT_TABLE_SIZE 13 +/* 13*16 */ +#define VFE32_ROLL_OFF_DELTA_TABLE_SIZE 208 + +#define VFE32_GAMMA_NUM_ENTRIES 64 + +#define VFE32_LA_TABLE_LENGTH 64 + +struct vfe_cmds_demosaic_abf { + uint8_t enable; + uint8_t forceOn; + uint8_t shift; + uint16_t lpThreshold; + uint16_t max; + uint16_t min; + uint8_t ratio; +}; + +struct vfe_cmds_demosaic_bpc { + uint8_t enable; + uint16_t fmaxThreshold; + uint16_t fminThreshold; + uint16_t redDiffThreshold; + uint16_t blueDiffThreshold; + uint16_t greenDiffThreshold; +}; + +struct vfe_cmd_demosaic_config { + uint8_t enable; + uint8_t slopeShift; + struct vfe_cmds_demosaic_abf abfConfig; + struct vfe_cmds_demosaic_bpc bpcConfig; +}; + +struct vfe_cmd_demosaic_bpc_update { + struct vfe_cmds_demosaic_bpc bpcUpdate; +}; + +struct vfe_cmd_demosaic_abf_update { + struct vfe_cmds_demosaic_abf abfUpdate; +}; + +struct vfe_cmd_white_balance_config { + uint8_t enable; + uint16_t ch2Gain; + uint16_t ch1Gain; + uint16_t ch0Gain; +}; + +enum VFE_COLOR_CORRECTION_COEF_QFACTOR { + COEF_IS_Q7_SIGNED, + COEF_IS_Q8_SIGNED, + COEF_IS_Q9_SIGNED, + COEF_IS_Q10_SIGNED +}; + +struct vfe_cmd_color_correction_config { + uint8_t enable; + enum VFE_COLOR_CORRECTION_COEF_QFACTOR coefQFactor; + int16_t C0; + int16_t C1; + int16_t C2; + int16_t C3; + int16_t C4; + int16_t C5; + int16_t C6; + int16_t C7; + int16_t C8; + int16_t K0; + int16_t K1; + int16_t K2; +}; + +#define VFE_LA_TABLE_LENGTH 64 + +struct vfe_cmd_la_config { + uint8_t enable; + int16_t table[VFE_LA_TABLE_LENGTH]; +}; + +#define VFE_GAMMA_TABLE_LENGTH 256 +enum VFE_RGB_GAMMA_TABLE_SELECT { + RGB_GAMMA_CH0_SELECTED, + RGB_GAMMA_CH1_SELECTED, + RGB_GAMMA_CH2_SELECTED, + RGB_GAMMA_CH0_CH1_SELECTED, + RGB_GAMMA_CH0_CH2_SELECTED, + RGB_GAMMA_CH1_CH2_SELECTED, + RGB_GAMMA_CH0_CH1_CH2_SELECTED +}; + +struct vfe_cmd_rgb_gamma_config { + uint8_t enable; + enum VFE_RGB_GAMMA_TABLE_SELECT channelSelect; + int16_t table[VFE_GAMMA_TABLE_LENGTH]; +}; + +struct vfe_cmd_chroma_enhan_config { + uint8_t enable; + int16_t am; + int16_t ap; + int16_t bm; + int16_t bp; + int16_t cm; + int16_t cp; + int16_t dm; + int16_t dp; + int16_t kcr; + int16_t kcb; + int16_t RGBtoYConversionV0; + int16_t RGBtoYConversionV1; + int16_t RGBtoYConversionV2; + uint8_t RGBtoYConversionOffset; +}; + +struct vfe_cmd_chroma_suppression_config { + uint8_t enable; + uint8_t m1; + uint8_t m3; + uint8_t n1; + uint8_t n3; + uint8_t nn1; + uint8_t mm1; +}; + +struct vfe_cmd_asf_config { + uint8_t enable; + uint8_t smoothFilterEnabled; + uint8_t sharpMode; + uint8_t smoothCoefCenter; + uint8_t smoothCoefSurr; + uint8_t normalizeFactor; + uint8_t sharpK1; + uint8_t sharpK2; + uint8_t sharpThreshE1; + int8_t sharpThreshE2; + int8_t sharpThreshE3; + int8_t sharpThreshE4; + int8_t sharpThreshE5; + int8_t filter1Coefficients[9]; + int8_t filter2Coefficients[9]; + uint8_t cropEnable; + uint16_t cropFirstPixel; + uint16_t cropLastPixel; + uint16_t cropFirstLine; + uint16_t cropLastLine; +}; + +struct vfe_cmd_asf_update { + uint8_t enable; + uint8_t smoothFilterEnabled; + uint8_t sharpMode; + uint8_t smoothCoefCenter; + uint8_t smoothCoefSurr; + uint8_t normalizeFactor; + uint8_t sharpK1; + uint8_t sharpK2; + uint8_t sharpThreshE1; + int8_t sharpThreshE2; + int8_t sharpThreshE3; + int8_t sharpThreshE4; + int8_t sharpThreshE5; + int8_t filter1Coefficients[9]; + int8_t filter2Coefficients[9]; + uint8_t cropEnable; +}; + +enum VFE_TEST_GEN_SYNC_EDGE { + VFE_TEST_GEN_SYNC_EDGE_ActiveHigh, + VFE_TEST_GEN_SYNC_EDGE_ActiveLow +}; + + +struct vfe_cmd_bus_pm_start { + uint8_t output2YWrPmEnable; + uint8_t output2CbcrWrPmEnable; + uint8_t output1YWrPmEnable; + uint8_t output1CbcrWrPmEnable; +}; + +struct vfe_frame_skip_counts { + uint32_t totalFrameCount; + uint32_t output1Count; + uint32_t output2Count; +}; + +enum VFE_AXI_RD_UNPACK_HBI_SEL { + VFE_AXI_RD_HBI_32_CLOCK_CYCLES, + VFE_AXI_RD_HBI_64_CLOCK_CYCLES, + VFE_AXI_RD_HBI_128_CLOCK_CYCLES, + VFE_AXI_RD_HBI_256_CLOCK_CYCLES, + VFE_AXI_RD_HBI_512_CLOCK_CYCLES, + VFE_AXI_RD_HBI_1024_CLOCK_CYCLES, + VFE_AXI_RD_HBI_2048_CLOCK_CYCLES, + VFE_AXI_RD_HBI_4096_CLOCK_CYCLES +}; + +enum VFE32_MESSAGE_ID { + MSG_ID_RESET_ACK, /* 0 */ + MSG_ID_START_ACK, + MSG_ID_STOP_ACK, + MSG_ID_UPDATE_ACK, + MSG_ID_OUTPUT_P, + MSG_ID_OUTPUT_T, + MSG_ID_OUTPUT_S, + MSG_ID_OUTPUT_V, + MSG_ID_SNAPSHOT_DONE, + MSG_ID_STATS_AEC, + MSG_ID_STATS_AF, /* 10 */ + MSG_ID_STATS_AWB, + MSG_ID_STATS_RS, + MSG_ID_STATS_CS, + MSG_ID_STATS_IHIST, + MSG_ID_STATS_SKIN, + MSG_ID_EPOCH1, + MSG_ID_EPOCH2, + MSG_ID_SYNC_TIMER0_DONE, + MSG_ID_SYNC_TIMER1_DONE, + MSG_ID_SYNC_TIMER2_DONE, /* 20 */ + MSG_ID_ASYNC_TIMER0_DONE, + MSG_ID_ASYNC_TIMER1_DONE, + MSG_ID_ASYNC_TIMER2_DONE, + MSG_ID_ASYNC_TIMER3_DONE, + MSG_ID_AE_OVERFLOW, + MSG_ID_AF_OVERFLOW, + MSG_ID_AWB_OVERFLOW, + MSG_ID_RS_OVERFLOW, + MSG_ID_CS_OVERFLOW, + MSG_ID_IHIST_OVERFLOW, /* 30 */ + MSG_ID_SKIN_OVERFLOW, + MSG_ID_AXI_ERROR, + MSG_ID_CAMIF_OVERFLOW, + MSG_ID_VIOLATION, + MSG_ID_CAMIF_ERROR, + MSG_ID_BUS_OVERFLOW, + MSG_ID_SOF_ACK, + MSG_ID_STOP_REC_ACK, +}; + +struct vfe_msg_stats { + uint32_t buffer; + uint32_t frameCounter; +}; + + +struct vfe_frame_bpc_info { + uint32_t greenDefectPixelCount; + uint32_t redBlueDefectPixelCount; +}; + +struct vfe_frame_asf_info { + uint32_t asfMaxEdge; + uint32_t asfHbiCount; +}; + +struct vfe_msg_camif_status { + uint8_t camifState; + uint32_t pixelCount; + uint32_t lineCount; +}; + + +struct vfe32_irq_status { + uint32_t vfeIrqStatus0; + uint32_t vfeIrqStatus1; + uint32_t camifStatus; + uint32_t demosaicStatus; + uint32_t asfMaxEdge; +}; + +struct vfe_msg_output { + uint8_t output_id; + uint32_t yBuffer; + uint32_t cbcrBuffer; + struct vfe_frame_bpc_info bpcInfo; + struct vfe_frame_asf_info asfInfo; + uint32_t frameCounter; +}; + +struct vfe_message { + enum VFE32_MESSAGE_ID _d; + union { + struct vfe_msg_output msgOut; + struct vfe_msg_stats msgStats; + struct vfe_msg_camif_status msgCamifError; + } _u; +}; + +/* New one for 7x30 */ +struct msm_vfe32_cmd { + int32_t id; + uint16_t length; + void *value; +}; + +#define V32_PREVIEW_AXI_FLAG 0x00000001 +#define V32_SNAPSHOT_AXI_FLAG (0x00000001<<1) + +struct vfe32_cmd_type { + uint16_t id; + uint32_t length; + uint32_t offset; + uint32_t flag; +}; + +struct vfe32_free_buf { + struct list_head node; + uint32_t paddr; + uint32_t y_off; + uint32_t cbcr_off; +}; + +struct vfe32_output_ch { + struct list_head free_buf_queue; + spinlock_t free_buf_lock; + uint16_t output_fmt; + int8_t ch0; + int8_t ch1; + int8_t ch2; + uint32_t capture_cnt; + uint32_t frame_drop_cnt; +}; + +/* no error irq in mask 0 */ +#define VFE32_IMASK_ERROR_ONLY_0 0x0 +/* when normal case, don't want to block error status. */ +/* bit 0-21 are error irq bits */ +#define VFE32_IMASK_ERROR_ONLY_1 0x005FFFFF +#define VFE32_IMASK_CAMIF_ERROR (0x00000001<<0) +#define VFE32_IMASK_BHIST_OVWR (0x00000001<<1) +#define VFE32_IMASK_STATS_CS_OVWR (0x00000001<<2) +#define VFE32_IMASK_STATS_IHIST_OVWR (0x00000001<<3) +#define VFE32_IMASK_REALIGN_BUF_Y_OVFL (0x00000001<<4) +#define VFE32_IMASK_REALIGN_BUF_CB_OVFL (0x00000001<<5) +#define VFE32_IMASK_REALIGN_BUF_CR_OVFL (0x00000001<<6) +#define VFE32_IMASK_VIOLATION (0x00000001<<7) +#define VFE32_IMASK_IMG_MAST_0_BUS_OVFL (0x00000001<<8) +#define VFE32_IMASK_IMG_MAST_1_BUS_OVFL (0x00000001<<9) +#define VFE32_IMASK_IMG_MAST_2_BUS_OVFL (0x00000001<<10) +#define VFE32_IMASK_IMG_MAST_3_BUS_OVFL (0x00000001<<11) +#define VFE32_IMASK_IMG_MAST_4_BUS_OVFL (0x00000001<<12) +#define VFE32_IMASK_IMG_MAST_5_BUS_OVFL (0x00000001<<13) +#define VFE32_IMASK_IMG_MAST_6_BUS_OVFL (0x00000001<<14) +#define VFE32_IMASK_STATS_AE_BG_BUS_OVFL (0x00000001<<15) +#define VFE32_IMASK_STATS_AF_BF_BUS_OVFL (0x00000001<<16) +#define VFE32_IMASK_STATS_AWB_BUS_OVFL (0x00000001<<17) +#define VFE32_IMASK_STATS_RS_BUS_OVFL (0x00000001<<18) +#define VFE32_IMASK_STATS_CS_BUS_OVFL (0x00000001<<19) +#define VFE32_IMASK_STATS_IHIST_BUS_OVFL (0x00000001<<20) +#define VFE32_IMASK_STATS_SKIN_BHIST_BUS_OVFL (0x00000001<<21) +#define VFE32_IMASK_AXI_ERROR (0x00000001<<22) + +struct vfe32_output_path { + uint16_t output_mode; /* bitmask */ + + struct vfe32_output_ch out0; /* preview and thumbnail */ + struct vfe32_output_ch out1; /* snapshot */ + struct vfe32_output_ch out2; /* video */ +}; + +struct vfe32_frame_extra { + uint32_t greenDefectPixelCount; + uint32_t redBlueDefectPixelCount; + + uint32_t asfMaxEdge; + uint32_t asfHbiCount; + + uint32_t yWrPmStats0; + uint32_t yWrPmStats1; + uint32_t cbcrWrPmStats0; + uint32_t cbcrWrPmStats1; + + uint32_t frameCounter; +}; + +#define VFE_DISABLE_ALL_IRQS 0 +#define VFE_CLEAR_ALL_IRQS 0xffffffff + +#define VFE_HW_VERSION 0x00000000 +#define VFE_GLOBAL_RESET 0x00000004 +#define VFE_MODULE_RESET 0x00000008 +#define VFE_CGC_OVERRIDE 0x0000000C +#define VFE_MODULE_CFG 0x00000010 +#define VFE_CFG 0x00000014 +#define VFE_IRQ_CMD 0x00000018 +#define VFE_IRQ_MASK_0 0x0000001C +#define VFE_IRQ_MASK_1 0x00000020 +#define VFE_IRQ_CLEAR_0 0x00000024 +#define VFE_IRQ_CLEAR_1 0x00000028 +#define VFE_IRQ_STATUS_0 0x0000002C +#define VFE_IRQ_STATUS_1 0x00000030 +#define VFE_IRQ_COMP_MASK 0x00000034 +#define VFE_BUS_CMD 0x00000038 +#define VFE_BUS_PING_PONG_STATUS 0x00000180 +#define VFE_AXI_CMD 0x000001D8 +#define VFE_AXI_STATUS 0x000001DC +#define VFE_BUS_STATS_PING_PONG_BASE 0x000000F4 + +#define VFE_BUS_STATS_AEC_WR_PING_ADDR 0x000000F4 +#define VFE_BUS_STATS_AEC_WR_PONG_ADDR 0x000000F8 +#define VFE_BUS_STATS_AEC_UB_CFG 0x000000FC +#define VFE_BUS_STATS_AF_WR_PING_ADDR 0x00000100 +#define VFE_BUS_STATS_AF_WR_PONG_ADDR 0x00000104 +#define VFE_BUS_STATS_AF_UB_CFG 0x00000108 +#define VFE_BUS_STATS_AWB_WR_PING_ADDR 0x0000010C +#define VFE_BUS_STATS_AWB_WR_PONG_ADDR 0x00000110 +#define VFE_BUS_STATS_AWB_UB_CFG 0x00000114 +#define VFE_BUS_STATS_RS_WR_PING_ADDR 0x00000118 +#define VFE_BUS_STATS_RS_WR_PONG_ADDR 0x0000011C +#define VFE_BUS_STATS_RS_UB_CFG 0x00000120 + +#define VFE_BUS_STATS_CS_WR_PING_ADDR 0x00000124 +#define VFE_BUS_STATS_CS_WR_PONG_ADDR 0x00000128 +#define VFE_BUS_STATS_CS_UB_CFG 0x0000012C +#define VFE_BUS_STATS_HIST_WR_PING_ADDR 0x00000130 +#define VFE_BUS_STATS_HIST_WR_PONG_ADDR 0x00000134 +#define VFE_BUS_STATS_HIST_UB_CFG 0x00000138 +#define VFE_BUS_STATS_SKIN_WR_PING_ADDR 0x0000013C +#define VFE_BUS_STATS_SKIN_WR_PONG_ADDR 0x00000140 +#define VFE_BUS_STATS_SKIN_UB_CFG 0x00000144 +#define VFE_CAMIF_COMMAND 0x000001E0 +#define VFE_CAMIF_STATUS 0x00000204 +#define VFE_REG_UPDATE_CMD 0x00000260 +#define VFE_DEMUX_GAIN_0 0x00000288 +#define VFE_DEMUX_GAIN_1 0x0000028C +#define VFE_CHROMA_UP 0x0000035C +#define VFE_FRAMEDROP_ENC_Y_CFG 0x00000504 +#define VFE_FRAMEDROP_ENC_CBCR_CFG 0x00000508 +#define VFE_FRAMEDROP_ENC_Y_PATTERN 0x0000050C +#define VFE_FRAMEDROP_ENC_CBCR_PATTERN 0x00000510 +#define VFE_FRAMEDROP_VIEW_Y 0x00000514 +#define VFE_FRAMEDROP_VIEW_CBCR 0x00000518 +#define VFE_FRAMEDROP_VIEW_Y_PATTERN 0x0000051C +#define VFE_FRAMEDROP_VIEW_CBCR_PATTERN 0x00000520 +#define VFE_CLAMP_MAX 0x00000524 +#define VFE_CLAMP_MIN 0x00000528 +#define VFE_REALIGN_BUF 0x0000052C +#define VFE_STATS_CFG 0x00000530 +#define VFE_DMI_CFG 0x00000598 +#define VFE_DMI_ADDR 0x0000059C +#define VFE_DMI_DATA_LO 0x000005A4 +#define VFE_BUS_IO_FORMAT_CFG 0x000006F8 +#define VFE_PIXEL_IF_CFG 0x000006FC + +struct vfe_stats_control { + uint8_t ackPending; + uint32_t nextFrameAddrBuf; + uint32_t droppedStatsFrameCount; + uint32_t bufToRender; +}; + +struct vfe32_ctrl_type { + uint16_t operation_mode; /* streaming or snapshot */ + struct vfe32_output_path outpath; + + uint32_t vfeImaskCompositePacked; + + spinlock_t stop_flag_lock; + spinlock_t update_ack_lock; + spinlock_t state_lock; + spinlock_t io_lock; + + spinlock_t aec_ack_lock; + spinlock_t awb_ack_lock; + spinlock_t af_ack_lock; + + uint32_t extlen; + void *extdata; + + int8_t start_ack_pending; + int8_t stop_ack_pending; + int8_t reset_ack_pending; + int8_t update_ack_pending; + int8_t req_start_video_rec; + int8_t req_stop_video_rec; + + spinlock_t tasklet_lock; + struct list_head tasklet_q; + int vfeirq; + void __iomem *vfebase; + void *syncdata; + + struct resource *vfemem; + struct resource *vfeio; + + uint32_t stats_comp; + atomic_t vstate; + uint32_t vfe_capture_count; + uint32_t sync_timer_repeat_count; + uint32_t sync_timer_state; + uint32_t sync_timer_number; + + uint32_t vfeFrameId; + uint32_t output1Pattern; + uint32_t output1Period; + uint32_t output2Pattern; + uint32_t output2Period; + uint32_t vfeFrameSkipCount; + uint32_t vfeFrameSkipPeriod; + struct vfe_stats_control afStatsControl; + struct vfe_stats_control awbStatsControl; + struct vfe_stats_control aecStatsControl; + struct vfe_stats_control ihistStatsControl; + struct vfe_stats_control rsStatsControl; + struct vfe_stats_control csStatsControl; + + /* v4l2 subdev */ + struct v4l2_subdev *subdev; +}; + +#define statsAeNum 0 +#define statsAfNum 1 +#define statsAwbNum 2 +#define statsRsNum 3 +#define statsCsNum 4 +#define statsIhistNum 5 +#define statsSkinNum 6 + +struct vfe_cmd_stats_ack { + uint32_t nextStatsBuf; +}; + +#define VFE_STATS_BUFFER_COUNT 3 + +struct vfe_cmd_stats_buf { + uint32_t statsBuf[VFE_STATS_BUFFER_COUNT]; +}; +#endif /* __MSM_VFE32_H__ */ diff --git a/drivers/media/video/msm/msm_vfe7x.c b/drivers/media/video/msm/msm_vfe7x.c new file mode 100644 index 00000000000..316aacfc361 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe7x.c @@ -0,0 +1,783 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_vfe7x.h" +#include + +#define QDSP_CMDQUEUE 25 + +#define VFE_RESET_CMD 0 +#define VFE_START_CMD 1 +#define VFE_STOP_CMD 2 +#define VFE_FRAME_ACK 20 +#define STATS_AF_ACK 21 +#define STATS_WE_ACK 22 + +#define MSG_STOP_ACK 1 +#define MSG_SNAPSHOT 2 +#define MSG_OUTPUT1 6 +#define MSG_OUTPUT2 7 +#define MSG_STATS_AF 8 +#define MSG_STATS_WE 9 +#define MSG_OUTPUT_S 10 +#define MSG_OUTPUT_T 11 + +#define VFE_ADSP_EVENT 0xFFFF +#define SNAPSHOT_MASK_MODE 0x00000002 +#define MSM_AXI_QOS_PREVIEW 192000 +#define MSM_AXI_QOS_SNAPSHOT 192000 + + +static struct msm_adsp_module *qcam_mod; +static struct msm_adsp_module *vfe_mod; +static struct msm_vfe_callback *resp; +static void *extdata; +static uint32_t extlen; + +struct mutex vfe_lock; +static void *vfe_syncdata; +static uint8_t vfestopped; +static uint32_t vfetask_state; +static int cnt; + +static struct stop_event stopevent; + +unsigned long paddr_s_y; +unsigned long paddr_s_cbcr; +unsigned long paddr_t_y; +unsigned long paddr_t_cbcr; + +static void vfe_7x_convert(struct msm_vfe_phy_info *pinfo, + enum vfe_resp_msg type, + void *data, void **ext, int32_t *elen) +{ + switch (type) { + case VFE_MSG_OUTPUT_P: { + pinfo->y_phy = ((struct vfe_endframe *)data)->y_address; + pinfo->cbcr_phy = + ((struct vfe_endframe *)data)->cbcr_address; + + pinfo->output_id = OUTPUT_TYPE_P; + + CDBG("vfe_7x_convert, y_phy = 0x%x, cbcr_phy = 0x%x\n", + pinfo->y_phy, pinfo->cbcr_phy); + + ((struct vfe_frame_extra *)extdata)->bl_evencol = + ((struct vfe_endframe *)data)->blacklevelevencolumn; + + ((struct vfe_frame_extra *)extdata)->bl_oddcol = + ((struct vfe_endframe *)data)->blackleveloddcolumn; + + ((struct vfe_frame_extra *)extdata)->g_def_p_cnt = + ((struct vfe_endframe *)data)->greendefectpixelcount; + + ((struct vfe_frame_extra *)extdata)->r_b_def_p_cnt = + ((struct vfe_endframe *)data)->redbluedefectpixelcount; + + *ext = extdata; + *elen = extlen; + } + break; + + case VFE_MSG_OUTPUT_S: { + pinfo->y_phy = paddr_s_y; + pinfo->cbcr_phy = paddr_s_cbcr; + pinfo->output_id = OUTPUT_TYPE_S; + CDBG("vfe_7x_convert: y_phy = 0x%x cbcr_phy = 0x%x\n", + pinfo->y_phy, pinfo->cbcr_phy); + } + break; + + case VFE_MSG_OUTPUT_T: { + pinfo->y_phy = paddr_t_y; + pinfo->cbcr_phy = paddr_t_cbcr; + pinfo->output_id = OUTPUT_TYPE_T; + CDBG("vfe_7x_convert: y_phy = 0x%x cbcr_phy = 0x%x\n", + pinfo->y_phy, pinfo->cbcr_phy); + } + break; + + case VFE_MSG_STATS_AF: + case VFE_MSG_STATS_WE: + pinfo->sbuf_phy = *(uint32_t *)data; + break; + + default: + break; + } /* switch */ +} + +static void vfe_7x_ops(void *driver_data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + uint32_t evt_buf[3]; + struct msm_vfe_resp *rp; + void *data; + CDBG("%s:id=%d\n", __func__, id); + + len = (id == VFE_ADSP_EVENT) ? 0 : len; + data = resp->vfe_alloc(sizeof(struct msm_vfe_resp) + len, + vfe_syncdata, GFP_ATOMIC); + + if (!data) { + pr_err("%s: rp: cannot allocate buffer\n", __func__); + return; + } + rp = (struct msm_vfe_resp *)data; + rp->evt_msg.len = len; + + if (id == VFE_ADSP_EVENT) { + /* event */ + rp->type = VFE_EVENT; + rp->evt_msg.type = MSM_CAMERA_EVT; + getevent(evt_buf, sizeof(evt_buf)); + rp->evt_msg.msg_id = evt_buf[0]; + CDBG("%s:event:msg_id=%d\n", __func__, rp->evt_msg.msg_id); + resp->vfe_resp(rp, MSM_CAM_Q_VFE_EVT, vfe_syncdata, + GFP_ATOMIC); + } else { + /* messages */ + rp->evt_msg.type = MSM_CAMERA_MSG; + rp->evt_msg.msg_id = id; + rp->evt_msg.data = rp + 1; + getevent(rp->evt_msg.data, len); + CDBG("%s:messages:msg_id=%d\n", __func__, rp->evt_msg.msg_id); + + switch (rp->evt_msg.msg_id) { + case MSG_SNAPSHOT: + update_axi_qos(MSM_AXI_QOS_PREVIEW); + vfe_7x_ops(driver_data, MSG_OUTPUT_S, len, getevent); + vfe_7x_ops(driver_data, MSG_OUTPUT_T, len, getevent); + rp->type = VFE_MSG_SNAPSHOT; + break; + + case MSG_OUTPUT_S: + rp->type = VFE_MSG_OUTPUT_S; + vfe_7x_convert(&(rp->phy), VFE_MSG_OUTPUT_S, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_OUTPUT_T: + rp->type = VFE_MSG_OUTPUT_T; + vfe_7x_convert(&(rp->phy), VFE_MSG_OUTPUT_T, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_OUTPUT1: + case MSG_OUTPUT2: + rp->type = VFE_MSG_OUTPUT_P; + vfe_7x_convert(&(rp->phy), VFE_MSG_OUTPUT_P, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_STATS_AF: + rp->type = VFE_MSG_STATS_AF; + vfe_7x_convert(&(rp->phy), VFE_MSG_STATS_AF, + rp->evt_msg.data, NULL, NULL); + break; + + case MSG_STATS_WE: + rp->type = VFE_MSG_STATS_WE; + vfe_7x_convert(&(rp->phy), VFE_MSG_STATS_WE, + rp->evt_msg.data, NULL, NULL); + + CDBG("MSG_STATS_WE: phy = 0x%x\n", rp->phy.sbuf_phy); + break; + + case MSG_STOP_ACK: + rp->type = VFE_MSG_GENERAL; + stopevent.state = 1; + wake_up(&stopevent.wait); + break; + + + default: + rp->type = VFE_MSG_GENERAL; + break; + } + resp->vfe_resp(rp, MSM_CAM_Q_VFE_MSG, vfe_syncdata, GFP_ATOMIC); + } +} + +static struct msm_adsp_ops vfe_7x_sync = { + .event = vfe_7x_ops, +}; + +static int vfe_7x_enable(struct camera_enable_cmd *enable) +{ + int rc = -EFAULT; + + if (!strcmp(enable->name, "QCAMTASK")) + rc = msm_adsp_enable(qcam_mod); + else if (!strcmp(enable->name, "VFETASK")) { + rc = msm_adsp_enable(vfe_mod); + vfetask_state = 1; + } + + if (!cnt) { + add_axi_qos(); + cnt++; + } + return rc; +} + +static int vfe_7x_disable(struct camera_enable_cmd *enable, + struct platform_device *dev __attribute__((unused))) +{ + int rc = -EFAULT; + + if (!strcmp(enable->name, "QCAMTASK")) + rc = msm_adsp_disable(qcam_mod); + else if (!strcmp(enable->name, "VFETASK")) { + rc = msm_adsp_disable(vfe_mod); + vfetask_state = 0; + } + + return rc; +} + +static int vfe_7x_stop(void) +{ + int rc = 0; + uint32_t stopcmd = VFE_STOP_CMD; + rc = msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, + &stopcmd, sizeof(uint32_t)); + if (rc < 0) { + CDBG("%s:%d: failed rc = %d \n", __func__, __LINE__, rc); + return rc; + } + + stopevent.state = 0; + rc = wait_event_timeout(stopevent.wait, + stopevent.state != 0, + msecs_to_jiffies(stopevent.timeout)); + + return rc; +} + +static void vfe_7x_release(struct platform_device *pdev) +{ + mutex_lock(&vfe_lock); + vfe_syncdata = NULL; + mutex_unlock(&vfe_lock); + + if (!vfestopped) { + CDBG("%s:%d:Calling vfe_7x_stop()\n", __func__, __LINE__); + vfe_7x_stop(); + } else + vfestopped = 0; + + msm_adsp_disable(qcam_mod); + msm_adsp_disable(vfe_mod); + vfetask_state = 0; + + msm_adsp_put(qcam_mod); + msm_adsp_put(vfe_mod); + + msm_camio_disable(pdev); + + kfree(extdata); + extlen = 0; + + /* Release AXI */ + release_axi_qos(); + cnt = 0; +} + +static int vfe_7x_init(struct msm_vfe_callback *presp, + struct platform_device *dev) +{ + int rc = 0; + + init_waitqueue_head(&stopevent.wait); + stopevent.timeout = 200; + stopevent.state = 0; + + if (presp && presp->vfe_resp) + resp = presp; + else + return -EFAULT; + + /* Bring up all the required GPIOs and Clocks */ + rc = msm_camio_enable(dev); + if (rc < 0) + return rc; + msm_camio_camif_pad_reg_reset(); + + extlen = sizeof(struct vfe_frame_extra); + + extdata = + kmalloc(extlen, GFP_ATOMIC); + if (!extdata) { + rc = -ENOMEM; + goto init_fail; + } + + rc = msm_adsp_get("QCAMTASK", &qcam_mod, &vfe_7x_sync, NULL); + if (rc) { + rc = -EBUSY; + goto get_qcam_fail; + } + + rc = msm_adsp_get("VFETASK", &vfe_mod, &vfe_7x_sync, NULL); + if (rc) { + rc = -EBUSY; + goto get_vfe_fail; + } + + return 0; + +get_vfe_fail: + msm_adsp_put(qcam_mod); +get_qcam_fail: + kfree(extdata); +init_fail: + extlen = 0; + return rc; +} + +static int vfe_7x_config_axi(int mode, + struct axidata *ad, struct axiout *ao) +{ + struct msm_pmem_region *regptr; + unsigned long *bptr; + int cnt; + + int rc = 0; + + if (mode == OUTPUT_1 || mode == OUTPUT_1_AND_2) { + regptr = ad->region; + + CDBG("bufnum1 = %d\n", ad->bufnum1); + if (mode == OUTPUT_1_AND_2) { + paddr_t_y = regptr->paddr + regptr->info.y_off; + paddr_t_cbcr = regptr->paddr + regptr->info.cbcr_off; + } + + CDBG("config_axi1: O1, phy = 0x%lx, y_off = %d, cbcr_off =%d\n", + regptr->paddr, regptr->info.y_off, + regptr->info.cbcr_off); + + bptr = &ao->output1buffer1_y_phy; + for (cnt = 0; cnt < ad->bufnum1; cnt++) { + *bptr = regptr->paddr + regptr->info.y_off; + bptr++; + *bptr = regptr->paddr + regptr->info.cbcr_off; + + bptr++; + regptr++; + } + + regptr--; + for (cnt = 0; cnt < (8 - ad->bufnum1); cnt++) { + *bptr = regptr->paddr + regptr->info.y_off; + bptr++; + *bptr = regptr->paddr + regptr->info.cbcr_off; + bptr++; + } + } /* if OUTPUT1 or Both */ + + if (mode == OUTPUT_2 || mode == OUTPUT_1_AND_2) { + regptr = &(ad->region[ad->bufnum1]); + + CDBG("bufnum2 = %d\n", ad->bufnum2); + paddr_s_y = regptr->paddr + regptr->info.y_off; + paddr_s_cbcr = regptr->paddr + regptr->info.cbcr_off; + CDBG("config_axi2: O2, phy = 0x%lx, y_off = %d, cbcr_off =%d\n", + regptr->paddr, regptr->info.y_off, regptr->info.cbcr_off); + + bptr = &ao->output2buffer1_y_phy; + for (cnt = 0; cnt < ad->bufnum2; cnt++) { + *bptr = regptr->paddr + regptr->info.y_off; + bptr++; + *bptr = regptr->paddr + regptr->info.cbcr_off; + + bptr++; + regptr++; + } + + regptr--; + for (cnt = 0; cnt < (8 - ad->bufnum2); cnt++) { + *bptr = regptr->paddr + regptr->info.y_off; + bptr++; + *bptr = regptr->paddr + regptr->info.cbcr_off; + bptr++; + } + } + + return rc; +} + +static int vfe_7x_config(struct msm_vfe_cfg_cmd *cmd, void *data) +{ + struct msm_pmem_region *regptr; + unsigned char buf[256]; + + struct vfe_stats_ack sack; + struct axidata *axid; + uint32_t i, op_mode; + uint32_t *_mode; + + struct vfe_stats_we_cfg *scfg = NULL; + struct vfe_stats_af_cfg *sfcfg = NULL; + + struct axiout *axio = NULL; + void *cmd_data = NULL; + void *cmd_data_alloc = NULL; + long rc = 0; + struct msm_vfe_command_7k *vfecmd; + + vfecmd = + kmalloc(sizeof(struct msm_vfe_command_7k), + GFP_ATOMIC); + if (!vfecmd) { + pr_err("vfecmd alloc failed!\n"); + return -ENOMEM; + } + + if (cmd->cmd_type != CMD_FRAME_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AF_BUF_RELEASE) { + if (copy_from_user(vfecmd, + (void __user *)(cmd->value), + sizeof(struct msm_vfe_command_7k))) { + rc = -EFAULT; + goto config_failure; + } + } + + switch (cmd->cmd_type) { + case CMD_STATS_AEC_AWB_ENABLE: + case CMD_STATS_AXI_CFG: { + axid = data; + if (!axid) { + rc = -EFAULT; + goto config_failure; + } + + scfg = + kmalloc(sizeof(struct vfe_stats_we_cfg), + GFP_ATOMIC); + if (!scfg) { + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user(scfg, + (void __user *)(vfecmd->value), + vfecmd->length)) { + + rc = -EFAULT; + goto config_done; + } + + CDBG("STATS_ENABLE: bufnum = %d, enabling = %d\n", + axid->bufnum1, scfg->wb_expstatsenable); + + if (axid->bufnum1 > 0) { + regptr = axid->region; + + for (i = 0; i < axid->bufnum1; i++) { + + CDBG("STATS_ENABLE, phy = 0x%lx\n", + regptr->paddr); + + scfg->wb_expstatoutputbuffer[i] = + (void *)regptr->paddr; + regptr++; + } + + cmd_data = scfg; + + } else { + rc = -EINVAL; + goto config_done; + } + } + break; + + case CMD_STATS_AF_ENABLE: + case CMD_STATS_AF_AXI_CFG: { + axid = data; + if (!axid) { + rc = -EFAULT; + goto config_failure; + } + + sfcfg = + kmalloc(sizeof(struct vfe_stats_af_cfg), + GFP_ATOMIC); + + if (!sfcfg) { + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user(sfcfg, + (void __user *)(vfecmd->value), + vfecmd->length)) { + + rc = -EFAULT; + goto config_done; + } + + CDBG("AF_ENABLE: bufnum = %d, enabling = %d\n", + axid->bufnum1, sfcfg->af_enable); + + if (axid->bufnum1 > 0) { + regptr = &axid->region[0]; + + for (i = 0; i < axid->bufnum1; i++) { + + CDBG("STATS_ENABLE, phy = 0x%lx\n", + regptr->paddr); + + sfcfg->af_outbuf[i] = + (void *)regptr->paddr; + + regptr++; + } + + cmd_data = sfcfg; + + } else { + rc = -EINVAL; + goto config_done; + } + } + break; + + case CMD_FRAME_BUF_RELEASE: { + struct msm_frame *b; + unsigned long p; + struct vfe_outputack fack; + if (!data) { + rc = -EFAULT; + goto config_failure; + } + + b = (struct msm_frame *)(cmd->value); + p = *(unsigned long *)data; + + fack.header = VFE_FRAME_ACK; + + fack.output2newybufferaddress = + (void *)(p + b->y_off); + + fack.output2newcbcrbufferaddress = + (void *)(p + b->cbcr_off); + + vfecmd->queue = QDSP_CMDQUEUE; + vfecmd->length = sizeof(struct vfe_outputack); + cmd_data = &fack; + } + break; + + case CMD_SNAP_BUF_RELEASE: + break; + + case CMD_STATS_BUF_RELEASE: { + CDBG("vfe_7x_config: CMD_STATS_BUF_RELEASE\n"); + if (!data) { + rc = -EFAULT; + goto config_failure; + } + + sack.header = STATS_WE_ACK; + sack.bufaddr = (void *)*(uint32_t *)data; + + vfecmd->queue = QDSP_CMDQUEUE; + vfecmd->length = sizeof(struct vfe_stats_ack); + cmd_data = &sack; + } + break; + + case CMD_STATS_AF_BUF_RELEASE: { + CDBG("vfe_7x_config: CMD_STATS_AF_BUF_RELEASE\n"); + if (!data) { + rc = -EFAULT; + goto config_failure; + } + + sack.header = STATS_AF_ACK; + sack.bufaddr = (void *)*(uint32_t *)data; + + vfecmd->queue = QDSP_CMDQUEUE; + vfecmd->length = sizeof(struct vfe_stats_ack); + cmd_data = &sack; + } + break; + + case CMD_GENERAL: + case CMD_STATS_DISABLE: { + if (vfecmd->length > 256) { + cmd_data_alloc = + cmd_data = kmalloc(vfecmd->length, GFP_ATOMIC); + if (!cmd_data) { + rc = -ENOMEM; + goto config_failure; + } + } else + cmd_data = buf; + + if (copy_from_user(cmd_data, + (void __user *)(vfecmd->value), + vfecmd->length)) { + + rc = -EFAULT; + goto config_done; + } + + if (vfecmd->queue == QDSP_CMDQUEUE) { + switch (*(uint32_t *)cmd_data) { + case VFE_RESET_CMD: + msm_camio_vfe_blk_reset(); + vfestopped = 0; + break; + + case VFE_START_CMD: + _mode = (uint32_t *)cmd_data; + op_mode = *(++_mode); + if (op_mode & SNAPSHOT_MASK_MODE) { + /* request AXI bus for snapshot */ + if (update_axi_qos(MSM_AXI_QOS_SNAPSHOT) + < 0) { + rc = -EFAULT; + goto config_failure; + } + } else { + /* request AXI bus for snapshot */ + if (update_axi_qos(MSM_AXI_QOS_PREVIEW) + < 0) { + rc = -EFAULT; + goto config_failure; + } + } + msm_camio_camif_pad_reg_reset_2(); + vfestopped = 0; + break; + + case VFE_STOP_CMD: + vfestopped = 1; + goto config_send; + + default: + break; + } + } /* QDSP_CMDQUEUE */ + } + break; + case CMD_AXI_CFG_PREVIEW: + case CMD_RAW_PICT_AXI_CFG: { + axid = data; + if (!axid) { + rc = -EFAULT; + goto config_failure; + } + + axio = kmalloc(sizeof(struct axiout), GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user(axio, (void __user *)(vfecmd->value), + sizeof(struct axiout))) { + rc = -EFAULT; + goto config_done; + } + + vfe_7x_config_axi(OUTPUT_2, axid, axio); + cmd_data = axio; + } + break; + + case CMD_AXI_CFG_SNAP: { + axid = data; + if (!axid) { + rc = -EFAULT; + goto config_failure; + } + + axio = kmalloc(sizeof(struct axiout), GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user(axio, (void __user *)(vfecmd->value), + sizeof(struct axiout))) { + rc = -EFAULT; + goto config_done; + } + + vfe_7x_config_axi(OUTPUT_1_AND_2, axid, axio); + + cmd_data = axio; + } + break; + + default: + break; + } /* switch */ + + if (vfestopped) + goto config_done; + +config_send: + CDBG("send adsp command = %d\n", *(uint32_t *)cmd_data); + if (vfetask_state) + rc = msm_adsp_write(vfe_mod, vfecmd->queue, + cmd_data, vfecmd->length); +config_done: + if (cmd_data_alloc != NULL) + kfree(cmd_data_alloc); + +config_failure: + kfree(scfg); + kfree(axio); + kfree(vfecmd); + return rc; +} + +void msm_camvfe_fn_init(struct msm_camvfe_fn *fptr, void *data) +{ + mutex_init(&vfe_lock); + fptr->vfe_init = vfe_7x_init; + fptr->vfe_enable = vfe_7x_enable; + fptr->vfe_config = vfe_7x_config; + fptr->vfe_disable = vfe_7x_disable; + fptr->vfe_release = vfe_7x_release; + vfe_syncdata = data; +} + +void msm_camvpe_fn_init(struct msm_camvpe_fn *fptr, void *data) +{ + fptr->vpe_reg = NULL; + fptr->send_frame_to_vpe = NULL; + fptr->vpe_config = NULL; + fptr->vpe_cfg_update = NULL; + fptr->dis = NULL; +} diff --git a/drivers/media/video/msm/msm_vfe7x.h b/drivers/media/video/msm/msm_vfe7x.h new file mode 100644 index 00000000000..dd3571fd331 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe7x.h @@ -0,0 +1,265 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_VFE7X_H__ +#define __MSM_VFE7X_H__ +#include +#include + +struct vfe_frame_extra { + uint32_t bl_evencol; + uint32_t bl_oddcol; + uint16_t g_def_p_cnt; + uint16_t r_b_def_p_cnt; +}; + +struct vfe_endframe { + uint32_t y_address; + uint32_t cbcr_address; + + unsigned int blacklevelevencolumn:23; + uint16_t reserved1:9; + unsigned int blackleveloddcolumn:23; + uint16_t reserved2:9; + + uint16_t greendefectpixelcount:8; + uint16_t reserved3:8; + uint16_t redbluedefectpixelcount:8; + uint16_t reserved4:8; +} __attribute__((packed, aligned(4))); + +struct vfe_outputack { + uint32_t header; + void *output2newybufferaddress; + void *output2newcbcrbufferaddress; +} __attribute__((packed, aligned(4))); + +struct vfe_stats_ack { + uint32_t header; + /* MUST BE 64 bit ALIGNED */ + void *bufaddr; +} __attribute__((packed, aligned(4))); + +/* AXI Output Config Command sent to DSP */ +struct axiout { + uint32_t cmdheader:32; + int outputmode:3; + uint8_t format:2; + uint32_t /* reserved */ : 27; + + /* AXI Output 1 Y Configuration, Part 1 */ + uint32_t out1yimageheight:12; + uint32_t /* reserved */ : 4; + uint32_t out1yimagewidthin64bitwords:10; + uint32_t /* reserved */ : 6; + + /* AXI Output 1 Y Configuration, Part 2 */ + uint8_t out1yburstlen:2; + uint32_t out1ynumrows:12; + uint32_t out1yrowincin64bitincs:12; + uint32_t /* reserved */ : 6; + + /* AXI Output 1 CbCr Configuration, Part 1 */ + uint32_t out1cbcrimageheight:12; + uint32_t /* reserved */ : 4; + uint32_t out1cbcrimagewidthin64bitwords:10; + uint32_t /* reserved */ : 6; + + /* AXI Output 1 CbCr Configuration, Part 2 */ + uint8_t out1cbcrburstlen:2; + uint32_t out1cbcrnumrows:12; + uint32_t out1cbcrrowincin64bitincs:12; + uint32_t /* reserved */ : 6; + + /* AXI Output 2 Y Configuration, Part 1 */ + uint32_t out2yimageheight:12; + uint32_t /* reserved */ : 4; + uint32_t out2yimagewidthin64bitwords:10; + uint32_t /* reserved */ : 6; + + /* AXI Output 2 Y Configuration, Part 2 */ + uint8_t out2yburstlen:2; + uint32_t out2ynumrows:12; + uint32_t out2yrowincin64bitincs:12; + uint32_t /* reserved */ : 6; + + /* AXI Output 2 CbCr Configuration, Part 1 */ + uint32_t out2cbcrimageheight:12; + uint32_t /* reserved */ : 4; + uint32_t out2cbcrimagewidtein64bitwords:10; + uint32_t /* reserved */ : 6; + + /* AXI Output 2 CbCr Configuration, Part 2 */ + uint8_t out2cbcrburstlen:2; + uint32_t out2cbcrnumrows:12; + uint32_t out2cbcrrowincin64bitincs:12; + uint32_t /* reserved */ : 6; + + /* Address configuration: + * output1 phisycal address */ + unsigned long output1buffer1_y_phy; + unsigned long output1buffer1_cbcr_phy; + unsigned long output1buffer2_y_phy; + unsigned long output1buffer2_cbcr_phy; + unsigned long output1buffer3_y_phy; + unsigned long output1buffer3_cbcr_phy; + unsigned long output1buffer4_y_phy; + unsigned long output1buffer4_cbcr_phy; + unsigned long output1buffer5_y_phy; + unsigned long output1buffer5_cbcr_phy; + unsigned long output1buffer6_y_phy; + unsigned long output1buffer6_cbcr_phy; + unsigned long output1buffer7_y_phy; + unsigned long output1buffer7_cbcr_phy; + unsigned long output1buffer8_y_phy; + unsigned long output1buffer8_cbcr_phy; + + /* output2 phisycal address */ + unsigned long output2buffer1_y_phy; + unsigned long output2buffer1_cbcr_phy; + unsigned long output2buffer2_y_phy; + unsigned long output2buffer2_cbcr_phy; + unsigned long output2buffer3_y_phy; + unsigned long output2buffer3_cbcr_phy; + unsigned long output2buffer4_y_phy; + unsigned long output2buffer4_cbcr_phy; + unsigned long output2buffer5_y_phy; + unsigned long output2buffer5_cbcr_phy; + unsigned long output2buffer6_y_phy; + unsigned long output2buffer6_cbcr_phy; + unsigned long output2buffer7_y_phy; + unsigned long output2buffer7_cbcr_phy; + unsigned long output2buffer8_y_phy; + unsigned long output2buffer8_cbcr_phy; +} __attribute__((packed, aligned(4))); + +struct vfe_stats_we_cfg { + uint32_t header; + + /* White Balance/Exposure Statistic Selection */ + uint8_t wb_expstatsenable:1; + uint8_t wb_expstatbuspriorityselection:1; + unsigned int wb_expstatbuspriorityvalue:4; + unsigned int /* reserved */ : 26; + + /* White Balance/Exposure Statistic Configuration, Part 1 */ + uint8_t exposurestatregions:1; + uint8_t exposurestatsubregions:1; + unsigned int /* reserved */ : 14; + + unsigned int whitebalanceminimumy:8; + unsigned int whitebalancemaximumy:8; + + /* White Balance/Exposure Statistic Configuration, Part 2 */ + uint8_t wb_expstatslopeofneutralregionline[ + NUM_WB_EXP_NEUTRAL_REGION_LINES]; + + /* White Balance/Exposure Statistic Configuration, Part 3 */ + unsigned int wb_expstatcrinterceptofneutralregionline2:12; + unsigned int /* reserved */ : 4; + unsigned int wb_expstatcbinterceptofneutralreginnline1:12; + unsigned int /* reserved */ : 4; + + /* White Balance/Exposure Statistic Configuration, Part 4 */ + unsigned int wb_expstatcrinterceptofneutralregionline4:12; + unsigned int /* reserved */ : 4; + unsigned int wb_expstatcbinterceptofneutralregionline3:12; + unsigned int /* reserved */ : 4; + + /* White Balance/Exposure Statistic Output Buffer Header */ + unsigned int wb_expmetricheaderpattern:8; + unsigned int /* reserved */ : 24; + + /* White Balance/Exposure Statistic Output Buffers-MUST + * BE 64 bit ALIGNED */ + void *wb_expstatoutputbuffer[NUM_WB_EXP_STAT_OUTPUT_BUFFERS]; +} __attribute__((packed, aligned(4))); + +struct vfe_stats_af_cfg { + uint32_t header; + + /* Autofocus Statistic Selection */ + uint8_t af_enable:1; + uint8_t af_busprioritysel:1; + unsigned int af_buspriorityval:4; + unsigned int /* reserved */ : 26; + + /* Autofocus Statistic Configuration, Part 1 */ + unsigned int af_singlewinvoffset:12; + unsigned int /* reserved */ : 4; + unsigned int af_singlewinhoffset:12; + unsigned int /* reserved */ : 3; + uint8_t af_winmode:1; + + /* Autofocus Statistic Configuration, Part 2 */ + unsigned int af_singglewinvh:11; + unsigned int /* reserved */ : 5; + unsigned int af_singlewinhw:11; + unsigned int /* reserved */ : 5; + + /* Autofocus Statistic Configuration, Parts 3-6 */ + uint8_t af_multiwingrid[NUM_AUTOFOCUS_MULTI_WINDOW_GRIDS]; + + /* Autofocus Statistic Configuration, Part 7 */ + signed int af_metrichpfcoefa00:5; + signed int af_metrichpfcoefa04:5; + unsigned int af_metricmaxval:11; + uint8_t af_metricsel:1; + unsigned int /* reserved */ : 10; + + /* Autofocus Statistic Configuration, Part 8 */ + signed int af_metrichpfcoefa20:5; + signed int af_metrichpfcoefa21:5; + signed int af_metrichpfcoefa22:5; + signed int af_metrichpfcoefa23:5; + signed int af_metrichpfcoefa24:5; + unsigned int /* reserved */ : 7; + + /* Autofocus Statistic Output Buffer Header */ + unsigned int af_metrichp:8; + unsigned int /* reserved */ : 24; + + /* Autofocus Statistic Output Buffers - MUST BE 64 bit ALIGNED!!! */ + void *af_outbuf[NUM_AF_STAT_OUTPUT_BUFFERS]; +} __attribute__((packed, aligned(4))); /* VFE_StatsAutofocusConfigCmdType */ + +struct msm_camera_frame_msg { + unsigned long output_y_address; + unsigned long output_cbcr_address; + + unsigned int blacklevelevenColumn:23; + uint16_t reserved1:9; + unsigned int blackleveloddColumn:23; + uint16_t reserved2:9; + + uint16_t greendefectpixelcount:8; + uint16_t reserved3:8; + uint16_t redbluedefectpixelcount:8; + uint16_t reserved4:8; +} __attribute__((packed, aligned(4))); + +/* New one for 7k */ +struct msm_vfe_command_7k { + uint16_t queue; + uint16_t length; + void *value; +}; + +struct stop_event { + wait_queue_head_t wait; + int state; + int timeout; +}; + + +#endif /* __MSM_VFE7X_H__ */ diff --git a/drivers/media/video/msm/msm_vfe7x27a.c b/drivers/media/video/msm/msm_vfe7x27a.c new file mode 100644 index 00000000000..c8bfaccfdb9 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe7x27a.c @@ -0,0 +1,742 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_vfe7x27a.h" + +#define QDSP_CMDQUEUE 25 + +#define VFE_RESET_CMD 0 +#define VFE_START_CMD 1 +#define VFE_STOP_CMD 2 +#define VFE_FRAME_ACK 20 +#define STATS_AF_ACK 21 +#define STATS_WE_ACK 22 + +#define MSG_STOP_ACK 1 +#define MSG_SNAPSHOT 2 +#define MSG_OUTPUT1 6 +#define MSG_OUTPUT2 7 +#define MSG_STATS_AF 8 +#define MSG_STATS_WE 9 +#define MSG_OUTPUT_S 23 +#define MSG_OUTPUT_T 22 +#define MSG_SOF 15 + +#define VFE_ADSP_EVENT 0xFFFF +#define SNAPSHOT_MASK_MODE 0x00000002 +#define MSM_AXI_QOS_PREVIEW 122000 +#define MSM_AXI_QOS_SNAPSHOT 192000 + + +static struct msm_adsp_module *qcam_mod; +static struct msm_adsp_module *vfe_mod; +static struct msm_vfe_callback *resp; +static void *extdata; +static uint32_t extlen; + +struct mutex vfe_lock; +static void *vfe_syncdata; +static uint8_t vfestopped; + +static struct stop_event stopevent; + +unsigned long paddr_s_y; +unsigned long paddr_s_cbcr; +unsigned long paddr_t_y; +unsigned long paddr_t_cbcr; +static uint32_t op_mode; + +static void vfe_7x_convert(struct msm_vfe_phy_info *pinfo, + enum vfe_resp_msg type, + void *data, void **ext, int32_t *elen) +{ + switch (type) { + case VFE_MSG_OUTPUT_P: { + pinfo->y_phy = ((struct vfe_endframe *)data)->y_address; + pinfo->cbcr_phy = + ((struct vfe_endframe *)data)->cbcr_address; + + pinfo->output_id = OUTPUT_TYPE_P; + + CDBG("vfe_7x_convert, y_phy = 0x%x, cbcr_phy = 0x%x\n", + pinfo->y_phy, pinfo->cbcr_phy); + + memcpy(((struct vfe_frame_extra *)extdata), + &((struct vfe_endframe *)data)->extra, + sizeof(struct vfe_frame_extra)); + + *ext = extdata; + *elen = extlen; + pinfo->frame_id = + ((struct vfe_frame_extra *)extdata)->frame_id; + } + break; + case VFE_MSG_OUTPUT_S: { + pinfo->y_phy = paddr_s_y; + pinfo->cbcr_phy = paddr_s_cbcr; + pinfo->output_id = OUTPUT_TYPE_S; + CDBG("vfe_7x_convert: y_phy = 0x%x cbcr_phy = 0x%x\n", + pinfo->y_phy, pinfo->cbcr_phy); + } + break; + case VFE_MSG_OUTPUT_T: { + pinfo->y_phy = paddr_t_y; + pinfo->cbcr_phy = paddr_t_cbcr; + pinfo->output_id = OUTPUT_TYPE_T; + CDBG("vfe_7x_convert: y_phy = 0x%x cbcr_phy = 0x%x\n", + pinfo->y_phy, pinfo->cbcr_phy); + } + break; + case VFE_MSG_STATS_AF: + case VFE_MSG_STATS_WE: + pinfo->sbuf_phy = *(uint32_t *)data; + pinfo->frame_id = *(((uint32_t *)data) + 1); + CDBG("frame id = %d\n", pinfo->frame_id); + break; + default: + break; + } +} + +static void vfe_7x_ops(void *driver_data, unsigned id, size_t len, + void (*getevent)(void *ptr, size_t len)) +{ + uint32_t evt_buf[3]; + struct msm_vfe_resp *rp; + void *data; + CDBG("%s:id=%d\n", __func__, id); + + len = (id == VFE_ADSP_EVENT) ? 0 : len; + data = resp->vfe_alloc(sizeof(struct msm_vfe_resp) + len, + vfe_syncdata, GFP_ATOMIC); + + if (!data) { + pr_err("%s: rp: cannot allocate buffer\n", __func__); + return; + } + rp = data; + rp->evt_msg.len = len; + + if (id == VFE_ADSP_EVENT) { + /* event */ + rp->type = VFE_EVENT; + rp->evt_msg.type = MSM_CAMERA_EVT; + getevent(evt_buf, sizeof(evt_buf)); + rp->evt_msg.msg_id = evt_buf[0]; + CDBG("%s:event:msg_id=%d\n", __func__, rp->evt_msg.msg_id); + resp->vfe_resp(rp, MSM_CAM_Q_VFE_EVT, vfe_syncdata, + GFP_ATOMIC); + } else { + /* messages */ + rp->evt_msg.type = MSM_CAMERA_MSG; + rp->evt_msg.msg_id = id; + rp->evt_msg.data = rp + 1; + getevent(rp->evt_msg.data, len); + CDBG("%s:messages:msg_id=%d\n", __func__, rp->evt_msg.msg_id); + + switch (rp->evt_msg.msg_id) { + case MSG_SNAPSHOT: + msm_camio_set_perf_lvl(S_PREVIEW); + vfe_7x_ops(driver_data, MSG_OUTPUT_S, len, getevent); + vfe_7x_ops(driver_data, MSG_OUTPUT_T, len, getevent); + rp->type = VFE_MSG_SNAPSHOT; + break; + case MSG_OUTPUT_S: + rp->type = VFE_MSG_OUTPUT_S; + vfe_7x_convert(&(rp->phy), VFE_MSG_OUTPUT_S, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + case MSG_OUTPUT_T: + rp->type = VFE_MSG_OUTPUT_T; + vfe_7x_convert(&(rp->phy), VFE_MSG_OUTPUT_T, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + case MSG_OUTPUT1: + case MSG_OUTPUT2: + if (op_mode & SNAPSHOT_MASK_MODE) { + resp->vfe_free(data); + return; + } + rp->type = VFE_MSG_OUTPUT_P; + vfe_7x_convert(&(rp->phy), VFE_MSG_OUTPUT_P, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + case MSG_STATS_AF: + rp->type = VFE_MSG_STATS_AF; + vfe_7x_convert(&(rp->phy), VFE_MSG_STATS_AF, + rp->evt_msg.data, NULL, NULL); + break; + case MSG_STATS_WE: + rp->type = VFE_MSG_STATS_WE; + vfe_7x_convert(&(rp->phy), VFE_MSG_STATS_WE, + rp->evt_msg.data, NULL, NULL); + + CDBG("MSG_STATS_WE: phy = 0x%x\n", rp->phy.sbuf_phy); + break; + case MSG_STOP_ACK: + rp->type = VFE_MSG_GENERAL; + stopevent.state = 1; + wake_up(&stopevent.wait); + break; + default: + rp->type = VFE_MSG_GENERAL; + break; + } + if (id != MSG_SOF) + resp->vfe_resp(rp, MSM_CAM_Q_VFE_MSG, + vfe_syncdata, GFP_ATOMIC); + } +} + +static struct msm_adsp_ops vfe_7x_sync = { + .event = vfe_7x_ops, +}; + +static int vfe_7x_enable(struct camera_enable_cmd *enable) +{ + int rc = -EFAULT; + static int cnt; + + if (!strcmp(enable->name, "QCAMTASK")) + rc = msm_adsp_enable(qcam_mod); + else if (!strcmp(enable->name, "VFETASK")) + rc = msm_adsp_enable(vfe_mod); + + if (!cnt) { + msm_camio_set_perf_lvl(S_INIT); + cnt++; + } + return rc; +} + +static int vfe_7x_disable(struct camera_enable_cmd *enable, + struct platform_device *dev __attribute__((unused))) +{ + int rc = -EFAULT; + + if (!strcmp(enable->name, "QCAMTASK")) + rc = msm_adsp_disable(qcam_mod); + else if (!strcmp(enable->name, "VFETASK")) + rc = msm_adsp_disable(vfe_mod); + + return rc; +} + +static int vfe_7x_stop(void) +{ + int rc = 0; + uint32_t stopcmd = VFE_STOP_CMD; + rc = msm_adsp_write(vfe_mod, QDSP_CMDQUEUE, + &stopcmd, sizeof(uint32_t)); + if (rc < 0) { + CDBG("%s:%d: failed rc = %d\n", __func__, __LINE__, rc); + return rc; + } + + stopevent.state = 0; + rc = wait_event_timeout(stopevent.wait, + stopevent.state != 0, + msecs_to_jiffies(stopevent.timeout)); + + return rc; +} + +static void vfe_7x_release(struct platform_device *pdev) +{ + mutex_lock(&vfe_lock); + vfe_syncdata = NULL; + mutex_unlock(&vfe_lock); + + if (!vfestopped) { + CDBG("%s:%d:Calling vfe_7x_stop()\n", __func__, __LINE__); + vfe_7x_stop(); + } else + vfestopped = 0; + + msm_adsp_disable(qcam_mod); + msm_adsp_disable(vfe_mod); + + msm_adsp_put(qcam_mod); + msm_adsp_put(vfe_mod); + + msm_camio_disable(pdev); + + kfree(extdata); + extlen = 0; + + /* set back the AXI frequency to default */ + /* TODO msm_camio_set_perf_lvl(S_DEFAULT); */ +} + +static int vfe_7x_init(struct msm_vfe_callback *presp, + struct platform_device *dev) +{ + int rc = 0; + + init_waitqueue_head(&stopevent.wait); + stopevent.timeout = 200; + stopevent.state = 0; + + if (presp && presp->vfe_resp) + resp = presp; + else + return -EFAULT; + + /* Bring up all the required GPIOs and Clocks */ + rc = msm_camio_enable(dev); + if (rc < 0) + return rc; + + extlen = sizeof(struct vfe_frame_extra); + + extdata = kmalloc(extlen, GFP_ATOMIC); + if (!extdata) { + rc = -ENOMEM; + goto init_fail; + } + + rc = msm_adsp_get("QCAMTASK", &qcam_mod, &vfe_7x_sync, NULL); + if (rc) { + rc = -EBUSY; + goto get_qcam_fail; + } + + rc = msm_adsp_get("VFETASK", &vfe_mod, &vfe_7x_sync, NULL); + if (rc) { + rc = -EBUSY; + goto get_vfe_fail; + } + + return 0; + +get_vfe_fail: + msm_adsp_put(qcam_mod); +get_qcam_fail: + kfree(extdata); +init_fail: + extlen = 0; + return rc; +} + +static int vfe_7x_config_axi(int mode, + struct axidata *ad, struct axiout *ao) +{ + struct msm_pmem_region *regptr; + unsigned long *bptr; + int cnt; + + int rc = 0; + + if (mode == OUTPUT_1 || mode == OUTPUT_1_AND_2) { + regptr = ad->region; + + CDBG("bufnum1 = %d\n", ad->bufnum1); + if (mode == OUTPUT_1_AND_2) { + paddr_t_y = regptr->paddr + regptr->info.y_off; + paddr_t_cbcr = regptr->paddr + regptr->info.cbcr_off; + } + + CDBG("config_axi1: O1, phy = 0x%lx, y_off = %d, cbcr_off =%d\n", + regptr->paddr, regptr->info.y_off, + regptr->info.cbcr_off); + + bptr = &ao->output1buffer1_y_phy; + for (cnt = 0; cnt < ad->bufnum1; cnt++) { + *bptr = regptr->paddr + regptr->info.y_off; + bptr++; + *bptr = regptr->paddr + regptr->info.cbcr_off; + + bptr++; + regptr++; + } + + regptr--; + for (cnt = 0; cnt < (8 - ad->bufnum1); cnt++) { + *bptr = regptr->paddr + regptr->info.y_off; + bptr++; + *bptr = regptr->paddr + regptr->info.cbcr_off; + bptr++; + } + } + + if (mode == OUTPUT_2 || mode == OUTPUT_1_AND_2) { + regptr = &(ad->region[ad->bufnum1]); + + CDBG("bufnum2 = %d\n", ad->bufnum2); + paddr_s_y = regptr->paddr + regptr->info.y_off; + paddr_s_cbcr = regptr->paddr + regptr->info.cbcr_off; + + CDBG("config_axi2: O2, phy = 0x%lx, y_off = %d, cbcr_off =%d\n", + regptr->paddr, regptr->info.y_off, regptr->info.cbcr_off); + + bptr = &ao->output2buffer1_y_phy; + for (cnt = 0; cnt < ad->bufnum2; cnt++) { + *bptr = regptr->paddr + regptr->info.y_off; + bptr++; + *bptr = regptr->paddr + regptr->info.cbcr_off; + + bptr++; + regptr++; + } + + regptr--; + for (cnt = 0; cnt < (8 - ad->bufnum2); cnt++) { + *bptr = regptr->paddr + regptr->info.y_off; + bptr++; + *bptr = regptr->paddr + regptr->info.cbcr_off; + bptr++; + } + } + + return rc; +} + +static int vfe_7x_config(struct msm_vfe_cfg_cmd *cmd, void *data) +{ + struct msm_pmem_region *regptr; + unsigned char buf[256]; + + struct vfe_stats_ack sack; + struct axidata *axid; + uint32_t i; + uint32_t *_mode; + + struct vfe_stats_we_cfg *scfg = NULL; + struct vfe_stats_af_cfg *sfcfg = NULL; + + struct axiout *axio = NULL; + void *cmd_data = NULL; + void *cmd_data_alloc = NULL; + long rc = 0; + struct msm_vfe_command_7k *vfecmd; + + vfecmd = kmalloc(sizeof(struct msm_vfe_command_7k), GFP_ATOMIC); + if (!vfecmd) { + pr_err("vfecmd alloc failed!\n"); + return -ENOMEM; + } + + if (cmd->cmd_type != CMD_FRAME_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AF_BUF_RELEASE) { + if (copy_from_user(vfecmd, + (void __user *)(cmd->value), + sizeof(struct msm_vfe_command_7k))) { + rc = -EFAULT; + goto config_failure; + } + } + + switch (cmd->cmd_type) { + case CMD_STATS_AEC_AWB_ENABLE: + case CMD_STATS_AXI_CFG: { + axid = data; + if (!axid) { + rc = -EFAULT; + goto config_failure; + } + + scfg = + kmalloc(sizeof(struct vfe_stats_we_cfg), + GFP_ATOMIC); + if (!scfg) { + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user(scfg, + (void __user *)(vfecmd->value), + vfecmd->length)) { + + rc = -EFAULT; + goto config_done; + } + + CDBG("STATS_ENABLE: bufnum = %d, enabling = %d\n", + axid->bufnum1, scfg->wb_expstatsenable); + + if (axid->bufnum1 > 0) { + regptr = axid->region; + + for (i = 0; i < axid->bufnum1; i++) { + + CDBG("STATS_ENABLE, phy = 0x%lx\n", + regptr->paddr); + + scfg->wb_expstatoutputbuffer[i] = + (void *)regptr->paddr; + regptr++; + } + + cmd_data = scfg; + + } else { + rc = -EINVAL; + goto config_done; + } + } + break; + case CMD_STATS_AF_ENABLE: + case CMD_STATS_AF_AXI_CFG: { + axid = data; + if (!axid) { + rc = -EFAULT; + goto config_failure; + } + + sfcfg = + kmalloc(sizeof(struct vfe_stats_af_cfg), + GFP_ATOMIC); + + if (!sfcfg) { + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user(sfcfg, + (void __user *)(vfecmd->value), + vfecmd->length)) { + + rc = -EFAULT; + goto config_done; + } + + CDBG("AF_ENABLE: bufnum = %d, enabling = %d\n", + axid->bufnum1, sfcfg->af_enable); + + if (axid->bufnum1 > 0) { + regptr = &axid->region[0]; + + for (i = 0; i < axid->bufnum1; i++) { + + CDBG("STATS_ENABLE, phy = 0x%lx\n", + regptr->paddr); + + sfcfg->af_outbuf[i] = + (void *)regptr->paddr; + + regptr++; + } + + cmd_data = sfcfg; + + } else { + rc = -EINVAL; + goto config_done; + } + } + break; + case CMD_FRAME_BUF_RELEASE: { + struct msm_frame *b; + unsigned long p; + struct vfe_outputack fack; + if (!data) { + rc = -EFAULT; + goto config_failure; + } + + b = (struct msm_frame *)(cmd->value); + p = *(unsigned long *)data; + + fack.header = VFE_FRAME_ACK; + + fack.output2newybufferaddress = + (void *)(p + b->y_off); + + fack.output2newcbcrbufferaddress = + (void *)(p + b->cbcr_off); + + vfecmd->queue = QDSP_CMDQUEUE; + vfecmd->length = sizeof(struct vfe_outputack); + cmd_data = &fack; + } + break; + case CMD_SNAP_BUF_RELEASE: + break; + case CMD_STATS_BUF_RELEASE: { + CDBG("vfe_7x_config: CMD_STATS_BUF_RELEASE\n"); + if (!data) { + rc = -EFAULT; + goto config_failure; + } + + sack.header = STATS_WE_ACK; + sack.bufaddr = (void *)*(uint32_t *)data; + + vfecmd->queue = QDSP_CMDQUEUE; + vfecmd->length = sizeof(struct vfe_stats_ack); + cmd_data = &sack; + } + break; + case CMD_STATS_AF_BUF_RELEASE: { + CDBG("vfe_7x_config: CMD_STATS_AF_BUF_RELEASE\n"); + if (!data) { + rc = -EFAULT; + goto config_failure; + } + + sack.header = STATS_AF_ACK; + sack.bufaddr = (void *)*(uint32_t *)data; + + vfecmd->queue = QDSP_CMDQUEUE; + vfecmd->length = sizeof(struct vfe_stats_ack); + cmd_data = &sack; + } + break; + case CMD_GENERAL: + case CMD_STATS_DISABLE: { + if (vfecmd->length > 256) { + cmd_data_alloc = + cmd_data = kmalloc(vfecmd->length, GFP_ATOMIC); + if (!cmd_data) { + rc = -ENOMEM; + goto config_failure; + } + } else + cmd_data = buf; + + if (copy_from_user(cmd_data, + (void __user *)(vfecmd->value), + vfecmd->length)) { + + rc = -EFAULT; + goto config_done; + } + + if (vfecmd->queue == QDSP_CMDQUEUE) { + switch (*(uint32_t *)cmd_data) { + case VFE_RESET_CMD: + msm_camio_vfe_blk_reset(); + vfestopped = 0; + break; + case VFE_START_CMD: + _mode = (uint32_t *)cmd_data; + op_mode = *(++_mode); + if (op_mode & SNAPSHOT_MASK_MODE) + msm_camio_set_perf_lvl(S_CAPTURE); + else + msm_camio_set_perf_lvl(S_PREVIEW); + vfestopped = 0; + break; + case VFE_STOP_CMD: + vfestopped = 1; + goto config_send; + + default: + break; + } + } /* QDSP_CMDQUEUE */ + } + break; + case CMD_AXI_CFG_PREVIEW: + case CMD_RAW_PICT_AXI_CFG: { + axid = data; + if (!axid) { + rc = -EFAULT; + goto config_failure; + } + + axio = kmalloc(sizeof(struct axiout), GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user(axio, (void __user *)(vfecmd->value), + sizeof(struct axiout))) { + rc = -EFAULT; + goto config_done; + } + + vfe_7x_config_axi(OUTPUT_2, axid, axio); + cmd_data = axio; + } + break; + case CMD_AXI_CFG_SNAP: { + axid = data; + if (!axid) { + rc = -EFAULT; + goto config_failure; + } + + axio = kmalloc(sizeof(struct axiout), GFP_ATOMIC); + if (!axio) { + rc = -ENOMEM; + goto config_failure; + } + + if (copy_from_user(axio, (void __user *)(vfecmd->value), + sizeof(struct axiout))) { + rc = -EFAULT; + goto config_done; + } + + vfe_7x_config_axi(OUTPUT_1_AND_2, axid, axio); + + cmd_data = axio; + } + break; + default: + break; + } + + if (vfestopped) + goto config_done; + +config_send: + CDBG("send adsp command = %d\n", *(uint32_t *)cmd_data); + rc = msm_adsp_write(vfe_mod, vfecmd->queue, + cmd_data, vfecmd->length); + +config_done: + kfree(cmd_data_alloc); + +config_failure: + kfree(scfg); + kfree(axio); + kfree(vfecmd); + return rc; +} + +void msm_camvfe_fn_init(struct msm_camvfe_fn *fptr, void *data) +{ + mutex_init(&vfe_lock); + fptr->vfe_init = vfe_7x_init; + fptr->vfe_enable = vfe_7x_enable; + fptr->vfe_config = vfe_7x_config; + fptr->vfe_disable = vfe_7x_disable; + fptr->vfe_release = vfe_7x_release; + vfe_syncdata = data; +} + +void msm_camvpe_fn_init(struct msm_camvpe_fn *fptr, void *data) +{ + fptr->vpe_reg = NULL; + fptr->send_frame_to_vpe = NULL; + fptr->vpe_config = NULL; + fptr->vpe_cfg_update = NULL; + fptr->dis = NULL; +} diff --git a/drivers/media/video/msm/msm_vfe7x27a.h b/drivers/media/video/msm/msm_vfe7x27a.h new file mode 100644 index 00000000000..a4882060efc --- /dev/null +++ b/drivers/media/video/msm/msm_vfe7x27a.h @@ -0,0 +1,300 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MSM_VFE7X_H__ +#define __MSM_VFE7X_H__ +#include +#include + +struct vfe_frame_extra { + uint32_t bl_evencol:23; + uint32_t rvd1:9; + uint32_t bl_oddcol:23; + uint32_t rvd2:9; + + uint32_t d_dbpc_stats_hot:16; + uint32_t d_dbpc_stats_cold:16; + + uint32_t d_dbpc_stats_0_hot:10; + uint32_t rvd3:6; + uint32_t d_dbpc_stats_0_cold:10; + uint32_t rvd4:6; + uint32_t d_dbpc_stats_1_hot:10; + uint32_t rvd5:6; + uint32_t d_dbpc_stats_1_cold:10; + uint32_t rvd6:6; + + uint32_t asf_max_edge; + + uint32_t e_y_wm_pm_stats_0:21; + uint32_t rvd7:11; + uint32_t e_y_wm_pm_stats_1_bl:8; + uint32_t rvd8:8; + uint32_t e_y_wm_pm_stats_1_nl:12; + uint32_t rvd9:4; + + uint32_t e_cbcr_wm_pm_stats_0:21; + uint32_t rvd10:11; + uint32_t e_cbcr_wm_pm_stats_1_bl:8; + uint32_t rvd11:8; + uint32_t e_cbcr_wm_pm_stats_1_nl:12; + uint32_t rvd12:4; + + uint32_t v_y_wm_pm_stats_0:21; + uint32_t rvd13:11; + uint32_t v_y_wm_pm_stats_1_bl:8; + uint32_t rvd14:8; + uint32_t v_y_wm_pm_stats_1_nl:12; + uint32_t rvd15:4; + + uint32_t v_cbcr_wm_pm_stats_0:21; + uint32_t rvd16:11; + uint32_t v_cbcr_wm_pm_stats_1_bl:8; + uint32_t rvd17:8; + uint32_t v_cbcr_wm_pm_stats_1_nl:12; + uint32_t rvd18:4; + + uint32_t frame_id; +}; + +struct vfe_endframe { + uint32_t y_address; + uint32_t cbcr_address; + + struct vfe_frame_extra extra; +} __packed; + +struct vfe_outputack { + uint32_t header; + void *output2newybufferaddress; + void *output2newcbcrbufferaddress; +} __packed; + +struct vfe_stats_ack { + uint32_t header; + /* MUST BE 64 bit ALIGNED */ + void *bufaddr; +} __packed; + +/* AXI Output Config Command sent to DSP */ +struct axiout { + uint32_t cmdheader:32; + int outputmode:3; + uint8_t format:2; + uint32_t /* reserved */ : 27; + + /* AXI Output 1 Y Configuration, Part 1 */ + uint32_t out1yimageheight:12; + uint32_t /* reserved */ : 4; + uint32_t out1yimagewidthin64bitwords:10; + uint32_t /* reserved */ : 6; + + /* AXI Output 1 Y Configuration, Part 2 */ + uint8_t out1yburstlen:2; + uint32_t out1ynumrows:12; + uint32_t out1yrowincin64bitincs:12; + uint32_t /* reserved */ : 6; + + /* AXI Output 1 CbCr Configuration, Part 1 */ + uint32_t out1cbcrimageheight:12; + uint32_t /* reserved */ : 4; + uint32_t out1cbcrimagewidthin64bitwords:10; + uint32_t /* reserved */ : 6; + + /* AXI Output 1 CbCr Configuration, Part 2 */ + uint8_t out1cbcrburstlen:2; + uint32_t out1cbcrnumrows:12; + uint32_t out1cbcrrowincin64bitincs:12; + uint32_t /* reserved */ : 6; + + /* AXI Output 2 Y Configuration, Part 1 */ + uint32_t out2yimageheight:12; + uint32_t /* reserved */ : 4; + uint32_t out2yimagewidthin64bitwords:10; + uint32_t /* reserved */ : 6; + + /* AXI Output 2 Y Configuration, Part 2 */ + uint8_t out2yburstlen:2; + uint32_t out2ynumrows:12; + uint32_t out2yrowincin64bitincs:12; + uint32_t /* reserved */ : 6; + + /* AXI Output 2 CbCr Configuration, Part 1 */ + uint32_t out2cbcrimageheight:12; + uint32_t /* reserved */ : 4; + uint32_t out2cbcrimagewidtein64bitwords:10; + uint32_t /* reserved */ : 6; + + /* AXI Output 2 CbCr Configuration, Part 2 */ + uint8_t out2cbcrburstlen:2; + uint32_t out2cbcrnumrows:12; + uint32_t out2cbcrrowincin64bitincs:12; + uint32_t /* reserved */ : 6; + + /* Address configuration: + * output1 phisycal address */ + unsigned long output1buffer1_y_phy; + unsigned long output1buffer1_cbcr_phy; + unsigned long output1buffer2_y_phy; + unsigned long output1buffer2_cbcr_phy; + unsigned long output1buffer3_y_phy; + unsigned long output1buffer3_cbcr_phy; + unsigned long output1buffer4_y_phy; + unsigned long output1buffer4_cbcr_phy; + unsigned long output1buffer5_y_phy; + unsigned long output1buffer5_cbcr_phy; + unsigned long output1buffer6_y_phy; + unsigned long output1buffer6_cbcr_phy; + unsigned long output1buffer7_y_phy; + unsigned long output1buffer7_cbcr_phy; + unsigned long output1buffer8_y_phy; + unsigned long output1buffer8_cbcr_phy; + + /* output2 phisycal address */ + unsigned long output2buffer1_y_phy; + unsigned long output2buffer1_cbcr_phy; + unsigned long output2buffer2_y_phy; + unsigned long output2buffer2_cbcr_phy; + unsigned long output2buffer3_y_phy; + unsigned long output2buffer3_cbcr_phy; + unsigned long output2buffer4_y_phy; + unsigned long output2buffer4_cbcr_phy; + unsigned long output2buffer5_y_phy; + unsigned long output2buffer5_cbcr_phy; + unsigned long output2buffer6_y_phy; + unsigned long output2buffer6_cbcr_phy; + unsigned long output2buffer7_y_phy; + unsigned long output2buffer7_cbcr_phy; + unsigned long output2buffer8_y_phy; + unsigned long output2buffer8_cbcr_phy; +} __packed; + +struct vfe_stats_we_cfg { + uint32_t header; + + /* White Balance/Exposure Statistic Selection */ + uint8_t wb_expstatsenable:1; + uint8_t wb_expstatbuspriorityselection:1; + unsigned int wb_expstatbuspriorityvalue:4; + unsigned int /* reserved */ : 26; + + /* White Balance/Exposure Statistic Configuration, Part 1 */ + uint8_t exposurestatregions:1; + uint8_t exposurestatsubregions:1; + unsigned int /* reserved */ : 14; + + unsigned int whitebalanceminimumy:8; + unsigned int whitebalancemaximumy:8; + + /* White Balance/Exposure Statistic Configuration, Part 2 */ + uint8_t wb_expstatslopeofneutralregionline[ + NUM_WB_EXP_NEUTRAL_REGION_LINES]; + + /* White Balance/Exposure Statistic Configuration, Part 3 */ + unsigned int wb_expstatcrinterceptofneutralregionline2:12; + unsigned int /* reserved */ : 4; + unsigned int wb_expstatcbinterceptofneutralreginnline1:12; + unsigned int /* reserved */ : 4; + + /* White Balance/Exposure Statistic Configuration, Part 4 */ + unsigned int wb_expstatcrinterceptofneutralregionline4:12; + unsigned int /* reserved */ : 4; + unsigned int wb_expstatcbinterceptofneutralregionline3:12; + unsigned int /* reserved */ : 4; + + /* White Balance/Exposure Statistic Output Buffer Header */ + unsigned int wb_expmetricheaderpattern:8; + unsigned int /* reserved */ : 24; + + /* White Balance/Exposure Statistic Output Buffers-MUST + * BE 64 bit ALIGNED */ + void *wb_expstatoutputbuffer[NUM_WB_EXP_STAT_OUTPUT_BUFFERS]; +} __packed; + +struct vfe_stats_af_cfg { + uint32_t header; + + /* Autofocus Statistic Selection */ + uint8_t af_enable:1; + uint8_t af_busprioritysel:1; + unsigned int af_buspriorityval:4; + unsigned int /* reserved */ : 26; + + /* Autofocus Statistic Configuration, Part 1 */ + unsigned int af_singlewinvoffset:12; + unsigned int /* reserved */ : 4; + unsigned int af_singlewinhoffset:12; + unsigned int /* reserved */ : 3; + uint8_t af_winmode:1; + + /* Autofocus Statistic Configuration, Part 2 */ + unsigned int af_singglewinvh:11; + unsigned int /* reserved */ : 5; + unsigned int af_singlewinhw:11; + unsigned int /* reserved */ : 5; + + /* Autofocus Statistic Configuration, Parts 3-6 */ + uint8_t af_multiwingrid[NUM_AUTOFOCUS_MULTI_WINDOW_GRIDS]; + + /* Autofocus Statistic Configuration, Part 7 */ + signed int af_metrichpfcoefa00:5; + signed int af_metrichpfcoefa04:5; + unsigned int af_metricmaxval:11; + uint8_t af_metricsel:1; + unsigned int /* reserved */ : 10; + + /* Autofocus Statistic Configuration, Part 8 */ + signed int af_metrichpfcoefa20:5; + signed int af_metrichpfcoefa21:5; + signed int af_metrichpfcoefa22:5; + signed int af_metrichpfcoefa23:5; + signed int af_metrichpfcoefa24:5; + unsigned int /* reserved */ : 7; + + /* Autofocus Statistic Output Buffer Header */ + unsigned int af_metrichp:8; + unsigned int /* reserved */ : 24; + + /* Autofocus Statistic Output Buffers - MUST BE 64 bit ALIGNED!!! */ + void *af_outbuf[NUM_AF_STAT_OUTPUT_BUFFERS]; +} __packed; /* VFE_StatsAutofocusConfigCmdType */ + +struct msm_camera_frame_msg { + unsigned long output_y_address; + unsigned long output_cbcr_address; + + unsigned int blacklevelevenColumn:23; + uint16_t reserved1:9; + unsigned int blackleveloddColumn:23; + uint16_t reserved2:9; + + uint16_t greendefectpixelcount:8; + uint16_t reserved3:8; + uint16_t redbluedefectpixelcount:8; + uint16_t reserved4:8; +} __packed; + +/* New one for 7k */ +struct msm_vfe_command_7k { + uint16_t queue; + uint16_t length; + void *value; +}; + +struct stop_event { + wait_queue_head_t wait; + int state; + int timeout; +}; + + +#endif /* __MSM_VFE7X_H__ */ diff --git a/drivers/media/video/msm/msm_vfe8x.c b/drivers/media/video/msm/msm_vfe8x.c new file mode 100644 index 00000000000..0bf1785a86c --- /dev/null +++ b/drivers/media/video/msm/msm_vfe8x.c @@ -0,0 +1,842 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include "msm_vfe8x_proc.h" +#include + +#define ON 1 +#define OFF 0 + +static const char *vfe_general_cmd[] = { + "START", /* 0 */ + "RESET", + "AXI_INPUT_CONFIG", + "CAMIF_CONFIG", + "AXI_OUTPUT_CONFIG", + "BLACK_LEVEL_CONFIG", /* 5 */ + "ROLL_OFF_CONFIG", + "DEMUX_CHANNEL_GAIN_CONFIG", + "DEMOSAIC_CONFIG", + "FOV_CROP_CONFIG", + "MAIN_SCALER_CONFIG", /* 10 */ + "WHITE_BALANCE_CONFIG", + "COLOR_CORRECTION_CONFIG", + "LA_CONFIG", + "RGB_GAMMA_CONFIG", + "CHROMA_ENHAN_CONFIG", /* 15 */ + "CHROMA_SUPPRESSION_CONFIG", + "ASF_CONFIG", + "SCALER2Y_CONFIG", + "SCALER2CbCr_CONFIG", + "CHROMA_SUBSAMPLE_CONFIG", /* 20 */ + "FRAME_SKIP_CONFIG", + "OUTPUT_CLAMP_CONFIG", + "TEST_GEN_START", + "UPDATE", + "OUTPUT1_ACK", /* 25 */ + "OUTPUT2_ACK", + "EPOCH1_ACK", + "EPOCH2_ACK", + "STATS_AUTOFOCUS_ACK", + "STATS_WB_EXP_ACK", /* 30 */ + "BLACK_LEVEL_UPDATE", + "DEMUX_CHANNEL_GAIN_UPDATE", + "DEMOSAIC_BPC_UPDATE", + "DEMOSAIC_ABF_UPDATE", + "FOV_CROP_UPDATE", /* 35 */ + "WHITE_BALANCE_UPDATE", + "COLOR_CORRECTION_UPDATE", + "LA_UPDATE", + "RGB_GAMMA_UPDATE", + "CHROMA_ENHAN_UPDATE", /* 40 */ + "CHROMA_SUPPRESSION_UPDATE", + "MAIN_SCALER_UPDATE", + "SCALER2CbCr_UPDATE", + "SCALER2Y_UPDATE", + "ASF_UPDATE", /* 45 */ + "FRAME_SKIP_UPDATE", + "CAMIF_FRAME_UPDATE", + "STATS_AUTOFOCUS_UPDATE", + "STATS_WB_EXP_UPDATE", + "STOP", /* 50 */ + "GET_HW_VERSION", + "STATS_SETTING", + "STATS_AUTOFOCUS_START", + "STATS_AUTOFOCUS_STOP", + "STATS_WB_EXP_START", /* 55 */ + "STATS_WB_EXP_STOP", + "ASYNC_TIMER_SETTING", +}; + +static void *vfe_syncdata; + +static int vfe_enable(struct camera_enable_cmd *enable) +{ + return 0; +} + +static int vfe_disable(struct camera_enable_cmd *enable, + struct platform_device *dev) +{ + vfe_stop(); + msm_camio_disable(dev); + return 0; +} + +static void vfe_release(struct platform_device *dev) +{ + msm_camio_disable(dev); + vfe_cmd_release(dev); + update_axi_qos(PM_QOS_DEFAULT_VALUE); + vfe_syncdata = NULL; +} + +static void vfe_config_axi(int mode, + struct axidata *ad, + struct vfe_cmd_axi_output_config *ao) +{ + struct msm_pmem_region *regptr, *regptr1; + int i, j; + uint32_t *p1, *p2; + + if (mode == OUTPUT_1 || mode == OUTPUT_1_AND_2) { + regptr = ad->region; + for (i = 0; i < ad->bufnum1; i++) { + + p1 = &(ao->output1.outputY.outFragments[i][0]); + p2 = &(ao->output1.outputCbcr.outFragments[i][0]); + + for (j = 0; j < ao->output1.fragmentCount; j++) { + + *p1 = regptr->paddr + regptr->info.y_off; + p1++; + + *p2 = regptr->paddr + regptr->info.cbcr_off; + p2++; + } + regptr++; + } + } /* if OUTPUT1 or Both */ + + if (mode == OUTPUT_2 || mode == OUTPUT_1_AND_2) { + + regptr = &(ad->region[ad->bufnum1]); + CDBG("bufnum2 = %d\n", ad->bufnum2); + + for (i = 0; i < ad->bufnum2; i++) { + + p1 = &(ao->output2.outputY.outFragments[i][0]); + p2 = &(ao->output2.outputCbcr.outFragments[i][0]); + + CDBG("config_axi: O2, phy = 0x%lx, y_off = %d, "\ + "cbcr_off = %d\n", regptr->paddr, + regptr->info.y_off, regptr->info.cbcr_off); + + for (j = 0; j < ao->output2.fragmentCount; j++) { + + *p1 = regptr->paddr + regptr->info.y_off; + CDBG("vfe_config_axi: p1 = 0x%x\n", *p1); + p1++; + + *p2 = regptr->paddr + regptr->info.cbcr_off; + CDBG("vfe_config_axi: p2 = 0x%x\n", *p2); + p2++; + } + regptr++; + } + } + /* For video configuration */ + if (mode == OUTPUT_1_AND_3) { + /* this is preview buffer. */ + regptr = &(ad->region[0]); + /* this is video buffer. */ + regptr1 = &(ad->region[ad->bufnum1]); + CDBG("bufnum1 = %d\n", ad->bufnum1); + CDBG("bufnum2 = %d\n", ad->bufnum2); + + for (i = 0; i < ad->bufnum1; i++) { + p1 = &(ao->output1.outputY.outFragments[i][0]); + p2 = &(ao->output1.outputCbcr.outFragments[i][0]); + + CDBG("config_axi: O1, phy = 0x%lx, y_off = %d, "\ + "cbcr_off = %d\n", regptr->paddr, + regptr->info.y_off, regptr->info.cbcr_off); + + for (j = 0; j < ao->output1.fragmentCount; j++) { + + *p1 = regptr->paddr + regptr->info.y_off; + CDBG("vfe_config_axi: p1 = 0x%x\n", *p1); + p1++; + + *p2 = regptr->paddr + regptr->info.cbcr_off; + CDBG("vfe_config_axi: p2 = 0x%x\n", *p2); + p2++; + } + regptr++; + } + for (i = 0; i < ad->bufnum2; i++) { + p1 = &(ao->output2.outputY.outFragments[i][0]); + p2 = &(ao->output2.outputCbcr.outFragments[i][0]); + + CDBG("config_axi: O2, phy = 0x%lx, y_off = %d, "\ + "cbcr_off = %d\n", regptr1->paddr, + regptr1->info.y_off, regptr1->info.cbcr_off); + + for (j = 0; j < ao->output2.fragmentCount; j++) { + + *p1 = regptr1->paddr + regptr1->info.y_off; + CDBG("vfe_config_axi: p1 = 0x%x\n", *p1); + p1++; + + *p2 = regptr1->paddr + regptr1->info.cbcr_off; + CDBG("vfe_config_axi: p2 = 0x%x\n", *p2); + p2++; + } + regptr1++; + } + } + +} + +#define CHECKED_COPY_FROM_USER(in) { \ + if (cmd->length != sizeof(*(in))) { \ + pr_err("msm_camera: %s:%d cmd %d: user data size %d " \ + "!= kernel data size %d\n", \ + __func__, __LINE__, \ + cmd->id, cmd->length, sizeof(*(in))); \ + rc = -EIO; \ + break; \ + } \ + if (copy_from_user((in), (void __user *)cmd->value, \ + sizeof(*(in)))) { \ + rc = -EFAULT; \ + break; \ + } \ +} + +static int vfe_proc_general(struct msm_vfe_command_8k *cmd) +{ + int rc = 0; + + CDBG("%s: cmdID = %s\n", __func__, vfe_general_cmd[cmd->id]); + + switch (cmd->id) { + case VFE_CMD_ID_RESET: + msm_camio_vfe_blk_reset(); + msm_camio_camif_pad_reg_reset_2(); + vfe_reset(); + break; + + case VFE_CMD_ID_START: { + struct vfe_cmd_start start; + CHECKED_COPY_FROM_USER(&start); + + /* msm_camio_camif_pad_reg_reset_2(); */ + msm_camio_camif_pad_reg_reset(); + vfe_start(&start); + } + break; + + case VFE_CMD_ID_CAMIF_CONFIG: { + struct vfe_cmd_camif_config camif; + CHECKED_COPY_FROM_USER(&camif); + + vfe_camif_config(&camif); + } + break; + + case VFE_CMD_ID_BLACK_LEVEL_CONFIG: { + struct vfe_cmd_black_level_config bl; + CHECKED_COPY_FROM_USER(&bl); + + vfe_black_level_config(&bl); + } + break; + + case VFE_CMD_ID_ROLL_OFF_CONFIG:{ + /* rolloff is too big to be on the stack */ + struct vfe_cmd_roll_off_config *rolloff = + kmalloc(sizeof(struct vfe_cmd_roll_off_config), + GFP_KERNEL); + if (!rolloff) { + pr_err("%s: out of memory\n", __func__); + rc = -ENOMEM; + break; + } + /* Wrap CHECKED_COPY_FROM_USER() in a do-while(0) loop + * to make sure we free rolloff when copy_from_user() + * fails. + */ + do { + CHECKED_COPY_FROM_USER(rolloff); + vfe_roll_off_config(rolloff); + } while (0); + kfree(rolloff); + } + break; + + case VFE_CMD_ID_DEMUX_CHANNEL_GAIN_CONFIG: { + struct vfe_cmd_demux_channel_gain_config demuxc; + CHECKED_COPY_FROM_USER(&demuxc); + + /* demux is always enabled. */ + vfe_demux_channel_gain_config(&demuxc); + } + break; + + case VFE_CMD_ID_DEMOSAIC_CONFIG: { + struct vfe_cmd_demosaic_config demosaic; + CHECKED_COPY_FROM_USER(&demosaic); + + vfe_demosaic_config(&demosaic); + } + break; + + case VFE_CMD_ID_FOV_CROP_CONFIG: + case VFE_CMD_ID_FOV_CROP_UPDATE: { + struct vfe_cmd_fov_crop_config fov; + CHECKED_COPY_FROM_USER(&fov); + + vfe_fov_crop_config(&fov); + } + break; + + case VFE_CMD_ID_MAIN_SCALER_CONFIG: + case VFE_CMD_ID_MAIN_SCALER_UPDATE: { + struct vfe_cmd_main_scaler_config mainds; + CHECKED_COPY_FROM_USER(&mainds); + + vfe_main_scaler_config(&mainds); + } + break; + + case VFE_CMD_ID_WHITE_BALANCE_CONFIG: + case VFE_CMD_ID_WHITE_BALANCE_UPDATE: { + struct vfe_cmd_white_balance_config wb; + CHECKED_COPY_FROM_USER(&wb); + + vfe_white_balance_config(&wb); + } + break; + + case VFE_CMD_ID_COLOR_CORRECTION_CONFIG: + case VFE_CMD_ID_COLOR_CORRECTION_UPDATE: { + struct vfe_cmd_color_correction_config cc; + CHECKED_COPY_FROM_USER(&cc); + + vfe_color_correction_config(&cc); + } + break; + + case VFE_CMD_ID_LA_CONFIG: { + struct vfe_cmd_la_config la; + CHECKED_COPY_FROM_USER(&la); + + vfe_la_config(&la); + } + break; + + case VFE_CMD_ID_RGB_GAMMA_CONFIG: { + struct vfe_cmd_rgb_gamma_config rgb; + CHECKED_COPY_FROM_USER(&rgb); + + rc = vfe_rgb_gamma_config(&rgb); + } + break; + + case VFE_CMD_ID_CHROMA_ENHAN_CONFIG: + case VFE_CMD_ID_CHROMA_ENHAN_UPDATE: { + struct vfe_cmd_chroma_enhan_config chrom; + CHECKED_COPY_FROM_USER(&chrom); + + vfe_chroma_enhan_config(&chrom); + } + break; + + case VFE_CMD_ID_CHROMA_SUPPRESSION_CONFIG: + case VFE_CMD_ID_CHROMA_SUPPRESSION_UPDATE: { + struct vfe_cmd_chroma_suppression_config chromsup; + CHECKED_COPY_FROM_USER(&chromsup); + + vfe_chroma_sup_config(&chromsup); + } + break; + + case VFE_CMD_ID_ASF_CONFIG: { + struct vfe_cmd_asf_config asf; + CHECKED_COPY_FROM_USER(&asf); + + vfe_asf_config(&asf); + } + break; + + case VFE_CMD_ID_SCALER2Y_CONFIG: + case VFE_CMD_ID_SCALER2Y_UPDATE: { + struct vfe_cmd_scaler2_config ds2y; + CHECKED_COPY_FROM_USER(&ds2y); + + vfe_scaler2y_config(&ds2y); + } + break; + + case VFE_CMD_ID_SCALER2CbCr_CONFIG: + case VFE_CMD_ID_SCALER2CbCr_UPDATE: { + struct vfe_cmd_scaler2_config ds2cbcr; + CHECKED_COPY_FROM_USER(&ds2cbcr); + + vfe_scaler2cbcr_config(&ds2cbcr); + } + break; + + case VFE_CMD_ID_CHROMA_SUBSAMPLE_CONFIG: { + struct vfe_cmd_chroma_subsample_config sub; + CHECKED_COPY_FROM_USER(&sub); + + vfe_chroma_subsample_config(&sub); + } + break; + + case VFE_CMD_ID_FRAME_SKIP_CONFIG: { + struct vfe_cmd_frame_skip_config fskip; + CHECKED_COPY_FROM_USER(&fskip); + + vfe_frame_skip_config(&fskip); + } + break; + + case VFE_CMD_ID_OUTPUT_CLAMP_CONFIG: { + struct vfe_cmd_output_clamp_config clamp; + CHECKED_COPY_FROM_USER(&clamp); + + vfe_output_clamp_config(&clamp); + } + break; + + /* module update commands */ + case VFE_CMD_ID_BLACK_LEVEL_UPDATE: { + struct vfe_cmd_black_level_config blk; + CHECKED_COPY_FROM_USER(&blk); + + vfe_black_level_update(&blk); + } + break; + + case VFE_CMD_ID_DEMUX_CHANNEL_GAIN_UPDATE: { + struct vfe_cmd_demux_channel_gain_config dmu; + CHECKED_COPY_FROM_USER(&dmu); + + vfe_demux_channel_gain_update(&dmu); + } + break; + + case VFE_CMD_ID_DEMOSAIC_BPC_UPDATE: { + struct vfe_cmd_demosaic_bpc_update demo_bpc; + CHECKED_COPY_FROM_USER(&demo_bpc); + + vfe_demosaic_bpc_update(&demo_bpc); + } + break; + + case VFE_CMD_ID_DEMOSAIC_ABF_UPDATE: { + struct vfe_cmd_demosaic_abf_update demo_abf; + CHECKED_COPY_FROM_USER(&demo_abf); + + vfe_demosaic_abf_update(&demo_abf); + } + break; + + case VFE_CMD_ID_LA_UPDATE: { + struct vfe_cmd_la_config la; + CHECKED_COPY_FROM_USER(&la); + + vfe_la_update(&la); + } + break; + + case VFE_CMD_ID_RGB_GAMMA_UPDATE: { + struct vfe_cmd_rgb_gamma_config rgb; + CHECKED_COPY_FROM_USER(&rgb); + + rc = vfe_rgb_gamma_update(&rgb); + } + break; + + case VFE_CMD_ID_ASF_UPDATE: { + struct vfe_cmd_asf_update asf; + CHECKED_COPY_FROM_USER(&asf); + + vfe_asf_update(&asf); + } + break; + + case VFE_CMD_ID_FRAME_SKIP_UPDATE: { + struct vfe_cmd_frame_skip_update fskip; + CHECKED_COPY_FROM_USER(&fskip); + /* Start recording */ + if (fskip.output2Pattern == 0xffffffff) + update_axi_qos(MSM_AXI_QOS_RECORDING); + else if (fskip.output2Pattern == 0) + update_axi_qos(MSM_AXI_QOS_PREVIEW); + + vfe_frame_skip_update(&fskip); + } + break; + + case VFE_CMD_ID_CAMIF_FRAME_UPDATE: { + struct vfe_cmds_camif_frame fup; + CHECKED_COPY_FROM_USER(&fup); + + vfe_camif_frame_update(&fup); + } + break; + + /* stats update commands */ + case VFE_CMD_ID_STATS_AUTOFOCUS_UPDATE: { + struct vfe_cmd_stats_af_update afup; + CHECKED_COPY_FROM_USER(&afup); + + vfe_stats_update_af(&afup); + } + break; + + case VFE_CMD_ID_STATS_WB_EXP_UPDATE: { + struct vfe_cmd_stats_wb_exp_update wbexp; + CHECKED_COPY_FROM_USER(&wbexp); + + vfe_stats_update_wb_exp(&wbexp); + } + break; + + /* control of start, stop, update, etc... */ + case VFE_CMD_ID_STOP: + vfe_stop(); + break; + + case VFE_CMD_ID_GET_HW_VERSION: + break; + + /* stats */ + case VFE_CMD_ID_STATS_SETTING: { + struct vfe_cmd_stats_setting stats; + CHECKED_COPY_FROM_USER(&stats); + + vfe_stats_setting(&stats); + } + break; + + case VFE_CMD_ID_STATS_AUTOFOCUS_START: { + struct vfe_cmd_stats_af_start af; + CHECKED_COPY_FROM_USER(&af); + + vfe_stats_start_af(&af); + } + break; + + case VFE_CMD_ID_STATS_AUTOFOCUS_STOP: + vfe_stats_af_stop(); + break; + + case VFE_CMD_ID_STATS_WB_EXP_START: { + struct vfe_cmd_stats_wb_exp_start awexp; + CHECKED_COPY_FROM_USER(&awexp); + + vfe_stats_start_wb_exp(&awexp); + } + break; + + case VFE_CMD_ID_STATS_WB_EXP_STOP: + vfe_stats_wb_exp_stop(); + break; + + case VFE_CMD_ID_ASYNC_TIMER_SETTING: + break; + + case VFE_CMD_ID_UPDATE: + vfe_update(); + break; + + /* test gen */ + case VFE_CMD_ID_TEST_GEN_START: + break; + +/* + acknowledge from upper layer + these are not in general command. + + case VFE_CMD_ID_OUTPUT1_ACK: + break; + case VFE_CMD_ID_OUTPUT2_ACK: + break; + case VFE_CMD_ID_EPOCH1_ACK: + break; + case VFE_CMD_ID_EPOCH2_ACK: + break; + case VFE_CMD_ID_STATS_AUTOFOCUS_ACK: + break; + case VFE_CMD_ID_STATS_WB_EXP_ACK: + break; +*/ + + default: + pr_err("%s: invalid cmd id %d\n", __func__, cmd->id); + rc = -EINVAL; + break; + } /* switch */ + + return rc; +} + +static int vfe_config(struct msm_vfe_cfg_cmd *cmd, void *data) +{ + struct msm_pmem_region *regptr; + struct msm_vfe_command_8k vfecmd; + struct vfe_cmd_axi_output_config axio; + struct axidata *axid = data; + + int rc = 0; + + + if (cmd->cmd_type != CMD_FRAME_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_BUF_RELEASE && + cmd->cmd_type != CMD_STATS_AF_BUF_RELEASE) { + + if (copy_from_user(&vfecmd, + (void __user *)(cmd->value), sizeof(vfecmd))) { + pr_err("%s %d: copy_from_user failed\n", + __func__, __LINE__); + return -EFAULT; + } + } + + CDBG("%s: cmdType = %d\n", __func__, cmd->cmd_type); + + switch (cmd->cmd_type) { + case CMD_GENERAL: + rc = vfe_proc_general(&vfecmd); + break; + + case CMD_STATS_ENABLE: + case CMD_STATS_AXI_CFG: { + int i; + struct vfe_cmd_stats_setting scfg; + + BUG_ON(!axid); + + if (vfecmd.length != sizeof(scfg)) { + pr_err + ("msm_camera: %s: cmd %d: user-space "\ + "data size %d != kernel data size %d\n", + __func__, + cmd->cmd_type, vfecmd.length, + sizeof(scfg)); + return -EIO; + } + + if (copy_from_user(&scfg, + (void __user *)(vfecmd.value), + sizeof(scfg))) { + pr_err("%s %d: copy_from_user failed\n", + __func__, __LINE__); + return -EFAULT; + } + + regptr = axid->region; + if (axid->bufnum1 > 0) { + for (i = 0; i < axid->bufnum1; i++) { + scfg.awbBuffer[i] = + (uint32_t)(regptr->paddr); + regptr++; + } + } + + if (axid->bufnum2 > 0) { + for (i = 0; i < axid->bufnum2; i++) { + scfg.afBuffer[i] = + (uint32_t)(regptr->paddr); + regptr++; + } + } + + vfe_stats_setting(&scfg); + } + break; + + case CMD_STATS_AF_AXI_CFG: + break; + + case CMD_FRAME_BUF_RELEASE: { + /* preview buffer release */ + struct msm_frame *b; + unsigned long p; + struct vfe_cmd_output_ack fack; + + BUG_ON(!data); + + b = (struct msm_frame *)(cmd->value); + p = *(unsigned long *)data; + + fack.ybufaddr[0] = (uint32_t) (p + b->y_off); + + fack.chromabufaddr[0] = (uint32_t) (p + b->cbcr_off); + + if (b->path == OUTPUT_TYPE_P) + vfe_output_p_ack(&fack); + + if ((b->path == OUTPUT_TYPE_V) + || (b->path == OUTPUT_TYPE_S)) + vfe_output_v_ack(&fack); + } + break; + + case CMD_SNAP_BUF_RELEASE: + break; + + case CMD_STATS_BUF_RELEASE: { + struct vfe_cmd_stats_wb_exp_ack sack; + + BUG_ON(!data); + + sack.nextWbExpOutputBufferAddr = *(uint32_t *)data; + vfe_stats_wb_exp_ack(&sack); + } + break; + + case CMD_STATS_AF_BUF_RELEASE: { + struct vfe_cmd_stats_af_ack ack; + + BUG_ON(!data); + + ack.nextAFOutputBufferAddr = *(uint32_t *)data; + vfe_stats_af_ack(&ack); + } + break; + + case CMD_AXI_CFG_PREVIEW: + case CMD_RAW_PICT_AXI_CFG: { + + BUG_ON(!axid); + + if (copy_from_user(&axio, (void __user *)(vfecmd.value), + sizeof(axio))) { + pr_err("%s %d: copy_from_user failed\n", + __func__, __LINE__); + return -EFAULT; + } + /* Validate the data from user space */ + if (axio.output2.fragmentCount < + VFE_MIN_NUM_FRAGMENTS_PER_FRAME || + axio.output2.fragmentCount > + VFE_MAX_NUM_FRAGMENTS_PER_FRAME) + return -EINVAL; + + vfe_config_axi(OUTPUT_2, axid, &axio); + axio.outputDataSize = 0; + vfe_axi_output_config(&axio); + } + break; + + case CMD_AXI_CFG_SNAP: { + + BUG_ON(!axid); + + if (copy_from_user(&axio, (void __user *)(vfecmd.value), + sizeof(axio))) { + pr_err("%s %d: copy_from_user failed\n", + __func__, __LINE__); + return -EFAULT; + } + /* Validate the data from user space */ + if (axio.output1.fragmentCount < + VFE_MIN_NUM_FRAGMENTS_PER_FRAME || + axio.output1.fragmentCount > + VFE_MAX_NUM_FRAGMENTS_PER_FRAME || + axio.output2.fragmentCount < + VFE_MIN_NUM_FRAGMENTS_PER_FRAME || + axio.output2.fragmentCount > + VFE_MAX_NUM_FRAGMENTS_PER_FRAME) + return -EINVAL; + + vfe_config_axi(OUTPUT_1_AND_2, axid, &axio); + vfe_axi_output_config(&axio); + } + break; + + case CMD_AXI_CFG_VIDEO: { + BUG_ON(!axid); + + if (copy_from_user(&axio, (void __user *)(vfecmd.value), + sizeof(axio))) { + pr_err("%s %d: copy_from_user failed\n", + __func__, __LINE__); + return -EFAULT; + } + /* Validate the data from user space */ + if (axio.output1.fragmentCount < + VFE_MIN_NUM_FRAGMENTS_PER_FRAME || + axio.output1.fragmentCount > + VFE_MAX_NUM_FRAGMENTS_PER_FRAME || + axio.output2.fragmentCount < + VFE_MIN_NUM_FRAGMENTS_PER_FRAME || + axio.output2.fragmentCount > + VFE_MAX_NUM_FRAGMENTS_PER_FRAME) + return -EINVAL; + + vfe_config_axi(OUTPUT_1_AND_3, axid, &axio); + axio.outputDataSize = 0; + vfe_axi_output_config(&axio); + } + break; + + default: + break; + } /* switch */ + + return rc; +} + +static int vfe_init(struct msm_vfe_callback *presp, struct platform_device *dev) +{ + int rc = 0; + + rc = vfe_cmd_init(presp, dev, vfe_syncdata); + if (rc < 0) + return rc; + + /* Bring up all the required GPIOs and Clocks */ + rc = msm_camio_enable(dev); + + return rc; +} + +void msm_camvfe_fn_init(struct msm_camvfe_fn *fptr, void *data) +{ + fptr->vfe_init = vfe_init; + fptr->vfe_enable = vfe_enable; + fptr->vfe_config = vfe_config; + fptr->vfe_disable = vfe_disable; + fptr->vfe_release = vfe_release; + vfe_syncdata = data; +} + +void msm_camvpe_fn_init(struct msm_camvpe_fn *fptr, void *data) +{ + fptr->vpe_reg = NULL; + fptr->send_frame_to_vpe = NULL; + fptr->vpe_config = NULL; + fptr->vpe_cfg_update = NULL; + fptr->dis = NULL; +} diff --git a/drivers/media/video/msm/msm_vfe8x.h b/drivers/media/video/msm/msm_vfe8x.h new file mode 100644 index 00000000000..1b3148f0e84 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe8x.h @@ -0,0 +1,909 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_VFE8X_H__ +#define __MSM_VFE8X_H__ + +#define TRUE 1 +#define FALSE 0 +#define boolean uint8_t + +enum VFE_STATE { + VFE_STATE_IDLE, + VFE_STATE_ACTIVE +}; + +enum vfe_cmd_id { + /* + *Important! Command_ID are arranged in order. + *Don't change!*/ + VFE_CMD_ID_START, + VFE_CMD_ID_RESET, + + /* bus and camif config */ + VFE_CMD_ID_AXI_INPUT_CONFIG, + VFE_CMD_ID_CAMIF_CONFIG, + VFE_CMD_ID_AXI_OUTPUT_CONFIG, + + /* module config */ + VFE_CMD_ID_BLACK_LEVEL_CONFIG, + VFE_CMD_ID_ROLL_OFF_CONFIG, + VFE_CMD_ID_DEMUX_CHANNEL_GAIN_CONFIG, + VFE_CMD_ID_DEMOSAIC_CONFIG, + VFE_CMD_ID_FOV_CROP_CONFIG, + VFE_CMD_ID_MAIN_SCALER_CONFIG, + VFE_CMD_ID_WHITE_BALANCE_CONFIG, + VFE_CMD_ID_COLOR_CORRECTION_CONFIG, + VFE_CMD_ID_LA_CONFIG, + VFE_CMD_ID_RGB_GAMMA_CONFIG, + VFE_CMD_ID_CHROMA_ENHAN_CONFIG, + VFE_CMD_ID_CHROMA_SUPPRESSION_CONFIG, + VFE_CMD_ID_ASF_CONFIG, + VFE_CMD_ID_SCALER2Y_CONFIG, + VFE_CMD_ID_SCALER2CbCr_CONFIG, + VFE_CMD_ID_CHROMA_SUBSAMPLE_CONFIG, + VFE_CMD_ID_FRAME_SKIP_CONFIG, + VFE_CMD_ID_OUTPUT_CLAMP_CONFIG, + + /* test gen */ + VFE_CMD_ID_TEST_GEN_START, + + VFE_CMD_ID_UPDATE, + + /* ackownledge from upper layer */ + VFE_CMD_ID_OUTPUT1_ACK, + VFE_CMD_ID_OUTPUT2_ACK, + VFE_CMD_ID_EPOCH1_ACK, + VFE_CMD_ID_EPOCH2_ACK, + VFE_CMD_ID_STATS_AUTOFOCUS_ACK, + VFE_CMD_ID_STATS_WB_EXP_ACK, + + /* module update commands */ + VFE_CMD_ID_BLACK_LEVEL_UPDATE, + VFE_CMD_ID_DEMUX_CHANNEL_GAIN_UPDATE, + VFE_CMD_ID_DEMOSAIC_BPC_UPDATE, + VFE_CMD_ID_DEMOSAIC_ABF_UPDATE, + VFE_CMD_ID_FOV_CROP_UPDATE, + VFE_CMD_ID_WHITE_BALANCE_UPDATE, + VFE_CMD_ID_COLOR_CORRECTION_UPDATE, + VFE_CMD_ID_LA_UPDATE, + VFE_CMD_ID_RGB_GAMMA_UPDATE, + VFE_CMD_ID_CHROMA_ENHAN_UPDATE, + VFE_CMD_ID_CHROMA_SUPPRESSION_UPDATE, + VFE_CMD_ID_MAIN_SCALER_UPDATE, + VFE_CMD_ID_SCALER2CbCr_UPDATE, + VFE_CMD_ID_SCALER2Y_UPDATE, + VFE_CMD_ID_ASF_UPDATE, + VFE_CMD_ID_FRAME_SKIP_UPDATE, + VFE_CMD_ID_CAMIF_FRAME_UPDATE, + + /* stats update commands */ + VFE_CMD_ID_STATS_AUTOFOCUS_UPDATE, + VFE_CMD_ID_STATS_WB_EXP_UPDATE, + + /* control of start, stop, update, etc... */ + VFE_CMD_ID_STOP, + VFE_CMD_ID_GET_HW_VERSION, + + /* stats */ + VFE_CMD_ID_STATS_SETTING, + VFE_CMD_ID_STATS_AUTOFOCUS_START, + VFE_CMD_ID_STATS_AUTOFOCUS_STOP, + VFE_CMD_ID_STATS_WB_EXP_START, + VFE_CMD_ID_STATS_WB_EXP_STOP, + + VFE_CMD_ID_ASYNC_TIMER_SETTING, + + /* max id */ + VFE_CMD_ID_MAX +}; + +struct vfe_cmd_hw_version { + uint32_t minorVersion; + uint32_t majorVersion; + uint32_t coreVersion; +}; + +enum VFE_CAMIF_SYNC_EDGE { + VFE_CAMIF_SYNC_EDGE_ActiveHigh, + VFE_CAMIF_SYNC_EDGE_ActiveLow +}; + +enum VFE_CAMIF_SYNC_MODE { + VFE_CAMIF_SYNC_MODE_APS, + VFE_CAMIF_SYNC_MODE_EFS, + VFE_CAMIF_SYNC_MODE_ELS, + VFE_CAMIF_SYNC_MODE_ILLEGAL +}; + +struct vfe_cmds_camif_efs { + uint8_t efsendofline; + uint8_t efsstartofline; + uint8_t efsendofframe; + uint8_t efsstartofframe; +}; + +struct vfe_cmds_camif_frame { + uint16_t pixelsPerLine; + uint16_t linesPerFrame; +}; + +struct vfe_cmds_camif_window { + uint16_t firstpixel; + uint16_t lastpixel; + uint16_t firstline; + uint16_t lastline; +}; + +enum CAMIF_SUBSAMPLE_FRAME_SKIP { + CAMIF_SUBSAMPLE_FRAME_SKIP_0, + CAMIF_SUBSAMPLE_FRAME_SKIP_AllFrames, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_2Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_3Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_4Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_5Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_6Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_7Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_8Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_9Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_10Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_11Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_12Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_13Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_14Frame, + CAMIF_SUBSAMPLE_FRAME_SKIP_ONE_OUT_OF_EVERY_15Frame +}; + +struct vfe_cmds_camif_subsample { + uint16_t pixelskipmask; + uint16_t lineskipmask; + enum CAMIF_SUBSAMPLE_FRAME_SKIP frameskip; + uint8_t frameskipmode; + uint8_t pixelskipwrap; +}; + +struct vfe_cmds_camif_epoch { + uint8_t enable; + uint16_t lineindex; +}; + +struct vfe_cmds_camif_cfg { + enum VFE_CAMIF_SYNC_EDGE vSyncEdge; + enum VFE_CAMIF_SYNC_EDGE hSyncEdge; + enum VFE_CAMIF_SYNC_MODE syncMode; + uint8_t vfeSubSampleEnable; + uint8_t busSubSampleEnable; + uint8_t irqSubSampleEnable; + uint8_t binningEnable; + uint8_t misrEnable; +}; + +struct vfe_cmd_camif_config { + struct vfe_cmds_camif_cfg camifConfig; + struct vfe_cmds_camif_efs EFS; + struct vfe_cmds_camif_frame frame; + struct vfe_cmds_camif_window window; + struct vfe_cmds_camif_subsample subsample; + struct vfe_cmds_camif_epoch epoch1; + struct vfe_cmds_camif_epoch epoch2; +}; + +enum VFE_AXI_OUTPUT_MODE { + VFE_AXI_OUTPUT_MODE_Output1, + VFE_AXI_OUTPUT_MODE_Output2, + VFE_AXI_OUTPUT_MODE_Output1AndOutput2, + VFE_AXI_OUTPUT_MODE_CAMIFToAXIViaOutput2, + VFE_AXI_OUTPUT_MODE_Output2AndCAMIFToAXIViaOutput1, + VFE_AXI_OUTPUT_MODE_Output1AndCAMIFToAXIViaOutput2, + VFE_AXI_LAST_OUTPUT_MODE_ENUM +}; + +enum VFE_RAW_WR_PATH_SEL { + VFE_RAW_OUTPUT_DISABLED, + VFE_RAW_OUTPUT_ENC_CBCR_PATH, + VFE_RAW_OUTPUT_VIEW_CBCR_PATH, + VFE_RAW_OUTPUT_PATH_INVALID +}; + +enum VFE_RAW_PIXEL_DATA_SIZE { + VFE_RAW_PIXEL_DATA_SIZE_8BIT, + VFE_RAW_PIXEL_DATA_SIZE_10BIT, + VFE_RAW_PIXEL_DATA_SIZE_12BIT, +}; + +#define VFE_AXI_OUTPUT_BURST_LENGTH 4 +#define VFE_MAX_NUM_FRAGMENTS_PER_FRAME 4 +#define VFE_MIN_NUM_FRAGMENTS_PER_FRAME 1 +#define VFE_AXI_OUTPUT_CFG_FRAME_COUNT 3 + +struct vfe_cmds_axi_out_per_component { + uint16_t imageWidth; + uint16_t imageHeight; + uint16_t outRowCount; + uint16_t outRowIncrement; + uint32_t outFragments[VFE_AXI_OUTPUT_CFG_FRAME_COUNT] + [VFE_MAX_NUM_FRAGMENTS_PER_FRAME]; +}; + +struct vfe_cmds_axi_per_output_path { + uint8_t fragmentCount; + struct vfe_cmds_axi_out_per_component outputY; + struct vfe_cmds_axi_out_per_component outputCbcr; +}; + +enum VFE_AXI_BURST_LENGTH { + VFE_AXI_BURST_LENGTH_IS_2 = 2, + VFE_AXI_BURST_LENGTH_IS_4 = 4, + VFE_AXI_BURST_LENGTH_IS_8 = 8, + VFE_AXI_BURST_LENGTH_IS_16 = 16 +}; + +struct vfe_cmd_axi_output_config { + enum VFE_AXI_BURST_LENGTH burstLength; + enum VFE_AXI_OUTPUT_MODE outputMode; + enum VFE_RAW_PIXEL_DATA_SIZE outputDataSize; + struct vfe_cmds_axi_per_output_path output1; + struct vfe_cmds_axi_per_output_path output2; +}; + +struct vfe_cmd_fov_crop_config { + uint8_t enable; + uint16_t firstPixel; + uint16_t lastPixel; + uint16_t firstLine; + uint16_t lastLine; +}; + +struct vfe_cmds_main_scaler_stripe_init { + uint16_t MNCounterInit; + uint16_t phaseInit; +}; + +struct vfe_cmds_scaler_one_dimension { + uint8_t enable; + uint16_t inputSize; + uint16_t outputSize; + uint32_t phaseMultiplicationFactor; + uint8_t interpolationResolution; +}; + +struct vfe_cmd_main_scaler_config { + uint8_t enable; + struct vfe_cmds_scaler_one_dimension hconfig; + struct vfe_cmds_scaler_one_dimension vconfig; + struct vfe_cmds_main_scaler_stripe_init MNInitH; + struct vfe_cmds_main_scaler_stripe_init MNInitV; +}; + +struct vfe_cmd_scaler2_config { + uint8_t enable; + struct vfe_cmds_scaler_one_dimension hconfig; + struct vfe_cmds_scaler_one_dimension vconfig; +}; + +struct vfe_cmd_frame_skip_config { + uint8_t output1Period; + uint32_t output1Pattern; + uint8_t output2Period; + uint32_t output2Pattern; +}; + +struct vfe_cmd_frame_skip_update { + uint32_t output1Pattern; + uint32_t output2Pattern; +}; + +struct vfe_cmd_output_clamp_config { + uint8_t minCh0; + uint8_t minCh1; + uint8_t minCh2; + uint8_t maxCh0; + uint8_t maxCh1; + uint8_t maxCh2; +}; + +struct vfe_cmd_chroma_subsample_config { + uint8_t enable; + uint8_t cropEnable; + uint8_t vsubSampleEnable; + uint8_t hsubSampleEnable; + uint8_t vCosited; + uint8_t hCosited; + uint8_t vCositedPhase; + uint8_t hCositedPhase; + uint16_t cropWidthFirstPixel; + uint16_t cropWidthLastPixel; + uint16_t cropHeightFirstLine; + uint16_t cropHeightLastLine; +}; + +enum VFE_START_INPUT_SOURCE { + VFE_START_INPUT_SOURCE_CAMIF, + VFE_START_INPUT_SOURCE_TESTGEN, + VFE_START_INPUT_SOURCE_AXI, + VFE_START_INPUT_SOURCE_INVALID +}; + +enum VFE_START_OPERATION_MODE { + VFE_START_OPERATION_MODE_CONTINUOUS, + VFE_START_OPERATION_MODE_SNAPSHOT +}; + +enum VFE_START_PIXEL_PATTERN { + VFE_BAYER_RGRGRG, + VFE_BAYER_GRGRGR, + VFE_BAYER_BGBGBG, + VFE_BAYER_GBGBGB, + VFE_YUV_YCbYCr, + VFE_YUV_YCrYCb, + VFE_YUV_CbYCrY, + VFE_YUV_CrYCbY +}; + +enum VFE_BUS_RD_INPUT_PIXEL_PATTERN { + VFE_BAYER_RAW, + VFE_YUV_INTERLEAVED, + VFE_YUV_PSEUDO_PLANAR_Y, + VFE_YUV_PSEUDO_PLANAR_CBCR +}; + +enum VFE_YUV_INPUT_COSITING_MODE { + VFE_YUV_COSITED, + VFE_YUV_INTERPOLATED +}; + +struct vfe_cmd_start { + enum VFE_START_INPUT_SOURCE inputSource; + enum VFE_START_OPERATION_MODE operationMode; + uint8_t snapshotCount; + enum VFE_START_PIXEL_PATTERN pixel; + enum VFE_YUV_INPUT_COSITING_MODE yuvInputCositingMode; +}; + +struct vfe_cmd_output_ack { + uint32_t ybufaddr[VFE_MAX_NUM_FRAGMENTS_PER_FRAME]; + uint32_t chromabufaddr[VFE_MAX_NUM_FRAGMENTS_PER_FRAME]; +}; + +#define VFE_STATS_BUFFER_COUNT 3 + +struct vfe_cmd_stats_setting { + uint16_t frameHDimension; + uint16_t frameVDimension; + uint8_t afBusPrioritySelection; + uint8_t afBusPriority; + uint8_t awbBusPrioritySelection; + uint8_t awbBusPriority; + uint8_t histBusPrioritySelection; + uint8_t histBusPriority; + uint32_t afBuffer[VFE_STATS_BUFFER_COUNT]; + uint32_t awbBuffer[VFE_STATS_BUFFER_COUNT]; + uint32_t histBuffer[VFE_STATS_BUFFER_COUNT]; +}; + +struct vfe_cmd_stats_af_start { + uint8_t enable; + uint8_t windowMode; + uint16_t windowHOffset; + uint16_t windowVOffset; + uint16_t windowWidth; + uint16_t windowHeight; + uint8_t gridForMultiWindows[16]; + uint8_t metricSelection; + int16_t metricMax; + int8_t highPassCoef[7]; + int8_t bufferHeader; +}; + +struct vfe_cmd_stats_af_update { + uint8_t windowMode; + uint16_t windowHOffset; + uint16_t windowVOffset; + uint16_t windowWidth; + uint16_t windowHeight; +}; + +struct vfe_cmd_stats_wb_exp_start { + uint8_t enable; + uint8_t wbExpRegions; + uint8_t wbExpSubRegion; + uint8_t awbYMin; + uint8_t awbYMax; + int8_t awbMCFG[4]; + int16_t awbCCFG[4]; + int8_t axwHeader; +}; + +struct vfe_cmd_stats_wb_exp_update { + uint8_t wbExpRegions; + uint8_t wbExpSubRegion; + int8_t awbYMin; + int8_t awbYMax; + int8_t awbMCFG[4]; + int16_t awbCCFG[4]; +}; + +struct vfe_cmd_stats_af_ack { + uint32_t nextAFOutputBufferAddr; +}; + +struct vfe_cmd_stats_wb_exp_ack { + uint32_t nextWbExpOutputBufferAddr; +}; + +struct vfe_cmd_black_level_config { + uint8_t enable; + uint16_t evenEvenAdjustment; + uint16_t evenOddAdjustment; + uint16_t oddEvenAdjustment; + uint16_t oddOddAdjustment; +}; + +/* 13*1 */ +#define VFE_ROLL_OFF_INIT_TABLE_SIZE 13 +/* 13*16 */ +#define VFE_ROLL_OFF_DELTA_TABLE_SIZE 208 + +struct vfe_cmd_roll_off_config { + uint8_t enable; + uint16_t gridWidth; + uint16_t gridHeight; + uint16_t yDelta; + uint8_t gridXIndex; + uint8_t gridYIndex; + uint16_t gridPixelXIndex; + uint16_t gridPixelYIndex; + uint16_t yDeltaAccum; + uint16_t initTableR[VFE_ROLL_OFF_INIT_TABLE_SIZE]; + uint16_t initTableGr[VFE_ROLL_OFF_INIT_TABLE_SIZE]; + uint16_t initTableB[VFE_ROLL_OFF_INIT_TABLE_SIZE]; + uint16_t initTableGb[VFE_ROLL_OFF_INIT_TABLE_SIZE]; + int16_t deltaTableR[VFE_ROLL_OFF_DELTA_TABLE_SIZE]; + int16_t deltaTableGr[VFE_ROLL_OFF_DELTA_TABLE_SIZE]; + int16_t deltaTableB[VFE_ROLL_OFF_DELTA_TABLE_SIZE]; + int16_t deltaTableGb[VFE_ROLL_OFF_DELTA_TABLE_SIZE]; +}; + +struct vfe_cmd_demux_channel_gain_config { + uint16_t ch0EvenGain; + uint16_t ch0OddGain; + uint16_t ch1Gain; + uint16_t ch2Gain; +}; + +struct vfe_cmds_demosaic_abf { + uint8_t enable; + uint8_t forceOn; + uint8_t shift; + uint16_t lpThreshold; + uint16_t max; + uint16_t min; + uint8_t ratio; +}; + +struct vfe_cmds_demosaic_bpc { + uint8_t enable; + uint16_t fmaxThreshold; + uint16_t fminThreshold; + uint16_t redDiffThreshold; + uint16_t blueDiffThreshold; + uint16_t greenDiffThreshold; +}; + +struct vfe_cmd_demosaic_config { + uint8_t enable; + uint8_t slopeShift; + struct vfe_cmds_demosaic_abf abfConfig; + struct vfe_cmds_demosaic_bpc bpcConfig; +}; + +struct vfe_cmd_demosaic_bpc_update { + struct vfe_cmds_demosaic_bpc bpcUpdate; +}; + +struct vfe_cmd_demosaic_abf_update { + struct vfe_cmds_demosaic_abf abfUpdate; +}; + +struct vfe_cmd_white_balance_config { + uint8_t enable; + uint16_t ch2Gain; + uint16_t ch1Gain; + uint16_t ch0Gain; +}; + +enum VFE_COLOR_CORRECTION_COEF_QFACTOR { + COEF_IS_Q7_SIGNED, + COEF_IS_Q8_SIGNED, + COEF_IS_Q9_SIGNED, + COEF_IS_Q10_SIGNED +}; + +struct vfe_cmd_color_correction_config { + uint8_t enable; + enum VFE_COLOR_CORRECTION_COEF_QFACTOR coefQFactor; + int16_t C0; + int16_t C1; + int16_t C2; + int16_t C3; + int16_t C4; + int16_t C5; + int16_t C6; + int16_t C7; + int16_t C8; + int16_t K0; + int16_t K1; + int16_t K2; +}; + +#define VFE_LA_TABLE_LENGTH 256 +struct vfe_cmd_la_config { + uint8_t enable; + int16_t table[VFE_LA_TABLE_LENGTH]; +}; + +#define VFE_GAMMA_TABLE_LENGTH 256 +enum VFE_RGB_GAMMA_TABLE_SELECT { + RGB_GAMMA_CH0_SELECTED, + RGB_GAMMA_CH1_SELECTED, + RGB_GAMMA_CH2_SELECTED, + RGB_GAMMA_CH0_CH1_SELECTED, + RGB_GAMMA_CH0_CH2_SELECTED, + RGB_GAMMA_CH1_CH2_SELECTED, + RGB_GAMMA_CH0_CH1_CH2_SELECTED +}; + +struct vfe_cmd_rgb_gamma_config { + uint8_t enable; + enum VFE_RGB_GAMMA_TABLE_SELECT channelSelect; + int16_t table[VFE_GAMMA_TABLE_LENGTH]; +}; + +struct vfe_cmd_chroma_enhan_config { + uint8_t enable; + int16_t am; + int16_t ap; + int16_t bm; + int16_t bp; + int16_t cm; + int16_t cp; + int16_t dm; + int16_t dp; + int16_t kcr; + int16_t kcb; + int16_t RGBtoYConversionV0; + int16_t RGBtoYConversionV1; + int16_t RGBtoYConversionV2; + uint8_t RGBtoYConversionOffset; +}; + +struct vfe_cmd_chroma_suppression_config { + uint8_t enable; + uint8_t m1; + uint8_t m3; + uint8_t n1; + uint8_t n3; + uint8_t nn1; + uint8_t mm1; +}; + +struct vfe_cmd_asf_config { + uint8_t enable; + uint8_t smoothFilterEnabled; + uint8_t sharpMode; + uint8_t smoothCoefCenter; + uint8_t smoothCoefSurr; + uint8_t normalizeFactor; + uint8_t sharpK1; + uint8_t sharpK2; + uint8_t sharpThreshE1; + int8_t sharpThreshE2; + int8_t sharpThreshE3; + int8_t sharpThreshE4; + int8_t sharpThreshE5; + int8_t filter1Coefficients[9]; + int8_t filter2Coefficients[9]; + uint8_t cropEnable; + uint16_t cropFirstPixel; + uint16_t cropLastPixel; + uint16_t cropFirstLine; + uint16_t cropLastLine; +}; + +struct vfe_cmd_asf_update { + uint8_t enable; + uint8_t smoothFilterEnabled; + uint8_t sharpMode; + uint8_t smoothCoefCenter; + uint8_t smoothCoefSurr; + uint8_t normalizeFactor; + uint8_t sharpK1; + uint8_t sharpK2; + uint8_t sharpThreshE1; + int8_t sharpThreshE2; + int8_t sharpThreshE3; + int8_t sharpThreshE4; + int8_t sharpThreshE5; + int8_t filter1Coefficients[9]; + int8_t filter2Coefficients[9]; + uint8_t cropEnable; +}; + +enum VFE_TEST_GEN_SYNC_EDGE { + VFE_TEST_GEN_SYNC_EDGE_ActiveHigh, + VFE_TEST_GEN_SYNC_EDGE_ActiveLow +}; + +struct vfe_cmd_test_gen_start { + uint8_t pixelDataSelect; + uint8_t systematicDataSelect; + enum VFE_TEST_GEN_SYNC_EDGE hsyncEdge; + enum VFE_TEST_GEN_SYNC_EDGE vsyncEdge; + uint16_t numFrame; + enum VFE_RAW_PIXEL_DATA_SIZE pixelDataSize; + uint16_t imageWidth; + uint16_t imageHeight; + uint32_t startOfFrameOffset; + uint32_t endOfFrameNOffset; + uint16_t startOfLineOffset; + uint16_t endOfLineNOffset; + uint16_t hbi; + uint8_t vblEnable; + uint16_t vbl; + uint8_t startOfFrameDummyLine; + uint8_t endOfFrameDummyLine; + uint8_t unicolorBarEnable; + uint8_t colorBarsSplitEnable; + uint8_t unicolorBarSelect; + enum VFE_START_PIXEL_PATTERN colorBarsPixelPattern; + uint8_t colorBarsRotatePeriod; + uint16_t testGenRandomSeed; +}; + +struct vfe_cmd_bus_pm_start { + uint8_t output2YWrPmEnable; + uint8_t output2CbcrWrPmEnable; + uint8_t output1YWrPmEnable; + uint8_t output1CbcrWrPmEnable; +}; + +struct vfe_cmd_camif_frame_update { + struct vfe_cmds_camif_frame camifFrame; +}; + +struct vfe_cmd_sync_timer_setting { + uint8_t whichSyncTimer; + uint8_t operation; + uint8_t polarity; + uint16_t repeatCount; + uint16_t hsyncCount; + uint32_t pclkCount; + uint32_t outputDuration; +}; + +struct vfe_cmd_async_timer_setting { + uint8_t whichAsyncTimer; + uint8_t operation; + uint8_t polarity; + uint16_t repeatCount; + uint16_t inactiveCount; + uint32_t activeCount; +}; + +struct vfe_frame_skip_counts { + uint32_t totalFrameCount; + uint32_t output1Count; + uint32_t output2Count; +}; + +enum VFE_AXI_RD_UNPACK_HBI_SEL { + VFE_AXI_RD_HBI_32_CLOCK_CYCLES, + VFE_AXI_RD_HBI_64_CLOCK_CYCLES, + VFE_AXI_RD_HBI_128_CLOCK_CYCLES, + VFE_AXI_RD_HBI_256_CLOCK_CYCLES, + VFE_AXI_RD_HBI_512_CLOCK_CYCLES, + VFE_AXI_RD_HBI_1024_CLOCK_CYCLES, + VFE_AXI_RD_HBI_2048_CLOCK_CYCLES, + VFE_AXI_RD_HBI_4096_CLOCK_CYCLES +}; + +struct vfe_cmd_axi_input_config { + uint32_t fragAddr[4]; + uint8_t totalFragmentCount; + uint16_t ySize; + uint16_t xOffset; + uint16_t xSize; + uint16_t rowIncrement; + uint16_t numOfRows; + enum VFE_AXI_BURST_LENGTH burstLength; + uint8_t unpackPhase; + enum VFE_AXI_RD_UNPACK_HBI_SEL unpackHbi; + enum VFE_RAW_PIXEL_DATA_SIZE pixelSize; + uint8_t padRepeatCountLeft; + uint8_t padRepeatCountRight; + uint8_t padRepeatCountTop; + uint8_t padRepeatCountBottom; + uint8_t padLeftComponentSelectCycle0; + uint8_t padLeftComponentSelectCycle1; + uint8_t padLeftComponentSelectCycle2; + uint8_t padLeftComponentSelectCycle3; + uint8_t padLeftStopCycle0; + uint8_t padLeftStopCycle1; + uint8_t padLeftStopCycle2; + uint8_t padLeftStopCycle3; + uint8_t padRightComponentSelectCycle0; + uint8_t padRightComponentSelectCycle1; + uint8_t padRightComponentSelectCycle2; + uint8_t padRightComponentSelectCycle3; + uint8_t padRightStopCycle0; + uint8_t padRightStopCycle1; + uint8_t padRightStopCycle2; + uint8_t padRightStopCycle3; + uint8_t padTopLineCount; + uint8_t padBottomLineCount; +}; + +struct vfe_interrupt_status { + uint8_t camifErrorIrq; + uint8_t camifSofIrq; + uint8_t camifEolIrq; + uint8_t camifEofIrq; + uint8_t camifEpoch1Irq; + uint8_t camifEpoch2Irq; + uint8_t camifOverflowIrq; + uint8_t ceIrq; + uint8_t regUpdateIrq; + uint8_t resetAckIrq; + uint8_t encYPingpongIrq; + uint8_t encCbcrPingpongIrq; + uint8_t viewYPingpongIrq; + uint8_t viewCbcrPingpongIrq; + uint8_t rdPingpongIrq; + uint8_t afPingpongIrq; + uint8_t awbPingpongIrq; + uint8_t histPingpongIrq; + uint8_t encIrq; + uint8_t viewIrq; + uint8_t busOverflowIrq; + uint8_t afOverflowIrq; + uint8_t awbOverflowIrq; + uint8_t syncTimer0Irq; + uint8_t syncTimer1Irq; + uint8_t syncTimer2Irq; + uint8_t asyncTimer0Irq; + uint8_t asyncTimer1Irq; + uint8_t asyncTimer2Irq; + uint8_t asyncTimer3Irq; + uint8_t axiErrorIrq; + uint8_t violationIrq; + uint8_t anyErrorIrqs; + uint8_t anyOutput1PathIrqs; + uint8_t anyOutput2PathIrqs; + uint8_t anyOutputPathIrqs; + uint8_t anyAsyncTimerIrqs; + uint8_t anySyncTimerIrqs; + uint8_t anyIrqForActiveStatesOnly; +}; + +enum VFE_MESSAGE_ID { + VFE_MSG_ID_RESET_ACK, + VFE_MSG_ID_START_ACK, + VFE_MSG_ID_STOP_ACK, + VFE_MSG_ID_UPDATE_ACK, + VFE_MSG_ID_OUTPUT_P, + VFE_MSG_ID_OUTPUT_V, + VFE_MSG_ID_OUTPUT_S, + VFE_MSG_ID_OUTPUT_T, + VFE_MSG_ID_SNAPSHOT_DONE, + VFE_MSG_ID_STATS_AUTOFOCUS, + VFE_MSG_ID_STATS_WB_EXP, + VFE_MSG_ID_EPOCH1, + VFE_MSG_ID_EPOCH2, + VFE_MSG_ID_SYNC_TIMER0_DONE, + VFE_MSG_ID_SYNC_TIMER1_DONE, + VFE_MSG_ID_SYNC_TIMER2_DONE, + VFE_MSG_ID_ASYNC_TIMER0_DONE, + VFE_MSG_ID_ASYNC_TIMER1_DONE, + VFE_MSG_ID_ASYNC_TIMER2_DONE, + VFE_MSG_ID_ASYNC_TIMER3_DONE, + VFE_MSG_ID_AF_OVERFLOW, + VFE_MSG_ID_AWB_OVERFLOW, + VFE_MSG_ID_AXI_ERROR, + VFE_MSG_ID_CAMIF_OVERFLOW, + VFE_MSG_ID_VIOLATION, + VFE_MSG_ID_CAMIF_ERROR, + VFE_MSG_ID_BUS_OVERFLOW, + VFE_MSG_ID_SOF_ACK, +}; + +struct vfe_msg_stats_autofocus { + uint32_t afBuffer; + uint32_t frameCounter; +}; + +struct vfe_msg_stats_wb_exp { + uint32_t awbBuffer; + uint32_t frameCounter; +}; + +struct vfe_frame_bpc_info { + uint32_t greenDefectPixelCount; + uint32_t redBlueDefectPixelCount; +}; + +struct vfe_frame_asf_info { + uint32_t asfMaxEdge; + uint32_t asfHbiCount; +}; + +struct vfe_msg_camif_status { + uint8_t camifState; + uint32_t pixelCount; + uint32_t lineCount; +}; + +struct vfe_bus_pm_per_path { + uint32_t yWrPmStats0; + uint32_t yWrPmStats1; + uint32_t cbcrWrPmStats0; + uint32_t cbcrWrPmStats1; +}; + +struct vfe_bus_performance_monitor { + struct vfe_bus_pm_per_path encPathPmInfo; + struct vfe_bus_pm_per_path viewPathPmInfo; +}; + +struct vfe_irq_thread_msg { + uint32_t vfeIrqStatus; + uint32_t camifStatus; + uint32_t demosaicStatus; + uint32_t asfMaxEdge; + struct vfe_bus_performance_monitor pmInfo; +}; + +struct vfe_msg_output { + uint32_t yBuffer; + uint32_t cbcrBuffer; + struct vfe_frame_bpc_info bpcInfo; + struct vfe_frame_asf_info asfInfo; + uint32_t frameCounter; + struct vfe_bus_pm_per_path pmData; +}; + +struct vfe_message { + enum VFE_MESSAGE_ID _d; + union { + struct vfe_msg_output msgOutput1; + struct vfe_msg_output msgOutput2; + struct vfe_msg_stats_autofocus msgStatsAf; + struct vfe_msg_stats_wb_exp msgStatsWbExp; + struct vfe_msg_camif_status msgCamifError; + struct vfe_bus_performance_monitor msgBusOverflow; + } _u; +}; + +/* New one for 8k */ +struct msm_vfe_command_8k { + int id; + uint16_t length; + void *value; +}; + +struct vfe_frame_extra { + struct vfe_frame_bpc_info bpcInfo; + struct vfe_frame_asf_info asfInfo; + uint32_t frameCounter; + struct vfe_bus_pm_per_path pmData; +}; +#endif /* __MSM_VFE8X_H__ */ diff --git a/drivers/media/video/msm/msm_vfe8x_proc.c b/drivers/media/video/msm/msm_vfe8x_proc.c new file mode 100644 index 00000000000..97645575054 --- /dev/null +++ b/drivers/media/video/msm/msm_vfe8x_proc.c @@ -0,0 +1,3888 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "msm_vfe8x_proc.h" +#include +#include + +struct isr_queue_cmd { + struct list_head list; + struct vfe_interrupt_status vfeInterruptStatus; + struct vfe_frame_asf_info vfeAsfFrameInfo; + struct vfe_frame_bpc_info vfeBpcFrameInfo; + struct vfe_msg_camif_status vfeCamifStatusLocal; + struct vfe_bus_performance_monitor vfePmData; +}; + +struct msm_vfe8x_ctrl { + /* bit 1:0 ENC_IRQ_MASK = 0x11: + * generate IRQ when both y and cbcr frame is ready. */ + + /* bit 1:0 VIEW_IRQ_MASK= 0x11: + * generate IRQ when both y and cbcr frame is ready. */ + struct vfe_irq_composite_mask_config vfeIrqCompositeMaskLocal; + struct vfe_module_enable vfeModuleEnableLocal; + struct vfe_camif_cfg_data vfeCamifConfigLocal; + struct vfe_interrupt_mask vfeImaskLocal; + struct vfe_stats_cmd_data vfeStatsCmdLocal; + struct vfe_bus_cfg_data vfeBusConfigLocal; + struct vfe_cmd_bus_pm_start vfeBusPmConfigLocal; + struct vfe_bus_cmd_data vfeBusCmdLocal; + enum vfe_interrupt_name vfeInterruptNameLocal; + uint32_t vfeLaBankSel; + struct vfe_gamma_lut_sel vfeGammaLutSel; + + boolean vfeStartAckPendingFlag; + boolean vfeStopAckPending; + boolean vfeResetAckPending; + boolean vfeUpdateAckPending; + + enum VFE_AXI_OUTPUT_MODE axiOutputMode; + enum VFE_START_OPERATION_MODE vfeOperationMode; + + atomic_t vfe_serv_interrupt; + + uint32_t vfeSnapShotCount; + uint32_t vfeRequestedSnapShotCount; + boolean vfeStatsPingPongReloadFlag; + uint32_t vfeFrameId; + + struct vfe_cmd_frame_skip_config vfeFrameSkip; + uint32_t vfeFrameSkipPattern; + uint8_t vfeFrameSkipCount; + uint8_t vfeFrameSkipPeriod; + + boolean vfeTestGenStartFlag; + uint32_t vfeImaskPacked; + uint32_t vfeImaskCompositePacked; + enum VFE_RAW_PIXEL_DATA_SIZE axiInputDataSize; + struct vfe_irq_thread_msg vfeIrqThreadMsgLocal; + + struct vfe_output_path_combo viewPath; + struct vfe_output_path_combo encPath; + struct vfe_frame_skip_counts vfeDroppedFrameCounts; + struct vfe_stats_control afStatsControl; + struct vfe_stats_control awbStatsControl; + + enum VFE_STATE vstate; + + struct msm_vfe_callback *resp; + struct vfe_frame_extra extdata; + + struct isr_queue_cmd irqs[10]; + spinlock_t irqs_lock; + int irq_get; + int irq_put; + + int vfeirq; + void __iomem *vfebase; + + void *syncdata; +}; + +static struct msm_vfe8x_ctrl *ctrl; +static spinlock_t msm_vfe_ctrl_lock; + +static void vfe_prog_hw(uint8_t *hwreg, uint32_t *inptr, uint32_t regcnt) +{ + /* unsigned long flags; */ + uint32_t i; + uint32_t *p; + + /* @todo This is causing issues, need further investigate */ + /* spin_lock_irqsave(&ctrl->io_lock, flags); */ + + p = (uint32_t *)(hwreg); + for (i = 0; i < (regcnt >> 2); i++) + writel(*inptr++, p++); + /* *p++ = *inptr++; */ + + /* spin_unlock_irqrestore(&ctrl->io_lock, flags); */ +} + +static void +vfe_set_bus_pipo_addr(struct vfe_output_path_combo *vpath, + struct vfe_output_path_combo *epath) +{ + vpath->yPath.hwRegPingAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_VIEW_Y_WR_PING_ADDR); + vpath->yPath.hwRegPongAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_VIEW_Y_WR_PONG_ADDR); + vpath->cbcrPath.hwRegPingAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_VIEW_CBCR_WR_PING_ADDR); + vpath->cbcrPath.hwRegPongAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_VIEW_CBCR_WR_PONG_ADDR); + + epath->yPath.hwRegPingAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_ENC_Y_WR_PING_ADDR); + epath->yPath.hwRegPongAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_ENC_Y_WR_PONG_ADDR); + epath->cbcrPath.hwRegPingAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_ENC_CBCR_WR_PING_ADDR); + epath->cbcrPath.hwRegPongAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_ENC_CBCR_WR_PONG_ADDR); +} + +static void vfe_axi_output(struct vfe_cmd_axi_output_config *in, + struct vfe_output_path_combo *out1, + struct vfe_output_path_combo *out2, uint16_t out) +{ + struct vfe_axi_out_cfg cmd; + + uint16_t temp; + uint32_t burstLength; + + memset(&cmd, 0, sizeof(cmd)); + /* force it to burst length 4, hardware does not support it. */ + burstLength = 1; + + /* AXI Output 2 Y Configuration*/ + /* VFE_BUS_ENC_Y_WR_PING_ADDR */ + cmd.out2YPingAddr = out2->yPath.addressBuffer[0]; + + /* VFE_BUS_ENC_Y_WR_PONG_ADDR */ + cmd.out2YPongAddr = out2->yPath.addressBuffer[1]; + + /* VFE_BUS_ENC_Y_WR_IMAGE_SIZE */ + cmd.out2YImageHeight = in->output2.outputY.imageHeight; + /* convert the image width and row increment to be in + * unit of 64bit (8 bytes) */ + temp = (in->output2.outputY.imageWidth + (out - 1)) / out; + cmd.out2YImageWidthin64bit = temp; + + /* VFE_BUS_ENC_Y_WR_BUFFER_CFG */ + cmd.out2YBurstLength = burstLength; + cmd.out2YNumRows = in->output2.outputY.outRowCount; + temp = (in->output2.outputY.outRowIncrement + (out - 1)) / out; + cmd.out2YRowIncrementIn64bit = temp; + + /* AXI Output 2 Cbcr Configuration*/ + /* VFE_BUS_ENC_Cbcr_WR_PING_ADDR */ + cmd.out2CbcrPingAddr = out2->cbcrPath.addressBuffer[0]; + + /* VFE_BUS_ENC_Cbcr_WR_PONG_ADDR */ + cmd.out2CbcrPongAddr = out2->cbcrPath.addressBuffer[1]; + + /* VFE_BUS_ENC_Cbcr_WR_IMAGE_SIZE */ + cmd.out2CbcrImageHeight = in->output2.outputCbcr.imageHeight; + temp = (in->output2.outputCbcr.imageWidth + (out - 1)) / out; + cmd.out2CbcrImageWidthIn64bit = temp; + + /* VFE_BUS_ENC_Cbcr_WR_BUFFER_CFG */ + cmd.out2CbcrBurstLength = burstLength; + cmd.out2CbcrNumRows = in->output2.outputCbcr.outRowCount; + temp = (in->output2.outputCbcr.outRowIncrement + (out - 1)) / out; + cmd.out2CbcrRowIncrementIn64bit = temp; + + /* AXI Output 1 Y Configuration */ + /* VFE_BUS_VIEW_Y_WR_PING_ADDR */ + cmd.out1YPingAddr = out1->yPath.addressBuffer[0]; + + /* VFE_BUS_VIEW_Y_WR_PONG_ADDR */ + cmd.out1YPongAddr = out1->yPath.addressBuffer[1]; + + /* VFE_BUS_VIEW_Y_WR_IMAGE_SIZE */ + cmd.out1YImageHeight = in->output1.outputY.imageHeight; + temp = (in->output1.outputY.imageWidth + (out - 1)) / out; + cmd.out1YImageWidthin64bit = temp; + + /* VFE_BUS_VIEW_Y_WR_BUFFER_CFG */ + cmd.out1YBurstLength = burstLength; + cmd.out1YNumRows = in->output1.outputY.outRowCount; + + temp = (in->output1.outputY.outRowIncrement + (out - 1)) / out; + cmd.out1YRowIncrementIn64bit = temp; + + /* AXI Output 1 Cbcr Configuration*/ + cmd.out1CbcrPingAddr = out1->cbcrPath.addressBuffer[0]; + + /* VFE_BUS_VIEW_Cbcr_WR_PONG_ADDR */ + cmd.out1CbcrPongAddr = out1->cbcrPath.addressBuffer[1]; + + /* VFE_BUS_VIEW_Cbcr_WR_IMAGE_SIZE */ + cmd.out1CbcrImageHeight = in->output1.outputCbcr.imageHeight; + temp = (in->output1.outputCbcr.imageWidth + (out - 1)) / out; + cmd.out1CbcrImageWidthIn64bit = temp; + + cmd.out1CbcrBurstLength = burstLength; + cmd.out1CbcrNumRows = in->output1.outputCbcr.outRowCount; + temp = (in->output1.outputCbcr.outRowIncrement + (out - 1)) / out; + + cmd.out1CbcrRowIncrementIn64bit = temp; + + vfe_prog_hw(ctrl->vfebase + VFE_BUS_ENC_Y_WR_PING_ADDR, + (uint32_t *)&cmd, sizeof(cmd)); +} + +static void vfe_reg_bus_cfg(struct vfe_bus_cfg_data *in) +{ + struct vfe_axi_bus_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.stripeRdPathEn = in->stripeRdPathEn; + cmd.encYWrPathEn = in->encYWrPathEn; + cmd.encCbcrWrPathEn = in->encCbcrWrPathEn; + cmd.viewYWrPathEn = in->viewYWrPathEn; + cmd.viewCbcrWrPathEn = in->viewCbcrWrPathEn; + cmd.rawPixelDataSize = (uint32_t)in->rawPixelDataSize; + cmd.rawWritePathSelect = (uint32_t)in->rawWritePathSelect; + + /* program vfe_bus_cfg */ + writel(*((uint32_t *)&cmd), ctrl->vfebase + VFE_BUS_CFG); +} + +static void vfe_reg_camif_config(struct vfe_camif_cfg_data *in) +{ + struct VFE_CAMIFConfigType cfg; + + memset(&cfg, 0, sizeof(cfg)); + + cfg.VSyncEdge = in->camifCfgFromCmd.vSyncEdge; + + cfg.HSyncEdge = in->camifCfgFromCmd.hSyncEdge; + + cfg.syncMode = in->camifCfgFromCmd.syncMode; + + cfg.vfeSubsampleEnable = in->camifCfgFromCmd.vfeSubSampleEnable; + + cfg.busSubsampleEnable = in->camifCfgFromCmd.busSubSampleEnable; + + cfg.camif2vfeEnable = in->camif2OutputEnable; + + cfg.camif2busEnable = in->camif2BusEnable; + + cfg.irqSubsampleEnable = in->camifCfgFromCmd.irqSubSampleEnable; + + cfg.binningEnable = in->camifCfgFromCmd.binningEnable; + + cfg.misrEnable = in->camifCfgFromCmd.misrEnable; + + /* program camif_config */ + writel(*((uint32_t *)&cfg), ctrl->vfebase + CAMIF_CONFIG); +} + +static void vfe_reg_bus_cmd(struct vfe_bus_cmd_data *in) +{ + struct vfe_buscmd cmd; + memset(&cmd, 0, sizeof(cmd)); + + cmd.stripeReload = in->stripeReload; + cmd.busPingpongReload = in->busPingpongReload; + cmd.statsPingpongReload = in->statsPingpongReload; + + writel(*((uint32_t *)&cmd), ctrl->vfebase + VFE_BUS_CMD); + + CDBG("bus command = 0x%x\n", (*((uint32_t *)&cmd))); + + /* this is needed, as the control bits are pulse based. + * Don't want to reload bus pingpong again. */ + in->busPingpongReload = 0; + in->statsPingpongReload = 0; + in->stripeReload = 0; +} + +static void vfe_reg_module_cfg(struct vfe_module_enable *in) +{ + struct vfe_mod_enable ena; + + memset(&ena, 0, sizeof(ena)); + + ena.blackLevelCorrectionEnable = in->blackLevelCorrectionEnable; + ena.lensRollOffEnable = in->lensRollOffEnable; + ena.demuxEnable = in->demuxEnable; + ena.chromaUpsampleEnable = in->chromaUpsampleEnable; + ena.demosaicEnable = in->demosaicEnable; + ena.statsEnable = in->statsEnable; + ena.cropEnable = in->cropEnable; + ena.mainScalerEnable = in->mainScalerEnable; + ena.whiteBalanceEnable = in->whiteBalanceEnable; + ena.colorCorrectionEnable = in->colorCorrectionEnable; + ena.yHistEnable = in->yHistEnable; + ena.skinToneEnable = in->skinToneEnable; + ena.lumaAdaptationEnable = in->lumaAdaptationEnable; + ena.rgbLUTEnable = in->rgbLUTEnable; + ena.chromaEnhanEnable = in->chromaEnhanEnable; + ena.asfEnable = in->asfEnable; + ena.chromaSuppressionEnable = in->chromaSuppressionEnable; + ena.chromaSubsampleEnable = in->chromaSubsampleEnable; + ena.scaler2YEnable = in->scaler2YEnable; + ena.scaler2CbcrEnable = in->scaler2CbcrEnable; + + writel(*((uint32_t *)&ena), ctrl->vfebase + VFE_MODULE_CFG); +} + +static void vfe_program_dmi_cfg(enum VFE_DMI_RAM_SEL bankSel) +{ + /* set bit 8 for auto increment. */ + uint32_t value = (uint32_t) ctrl->vfebase + VFE_DMI_CFG_DEFAULT; + + value += (uint32_t)bankSel; + /* CDBG("dmi cfg input bank is 0x%x\n", bankSel); */ + + writel(value, ctrl->vfebase + VFE_DMI_CFG); + writel(0, ctrl->vfebase + VFE_DMI_ADDR); +} + +static void vfe_write_lens_roll_off_table(struct vfe_cmd_roll_off_config *in) +{ + uint16_t i; + uint32_t data; + + uint16_t *initGr = in->initTableGr; + uint16_t *initGb = in->initTableGb; + uint16_t *initB = in->initTableB; + uint16_t *initR = in->initTableR; + + int16_t *pDeltaGr = in->deltaTableGr; + int16_t *pDeltaGb = in->deltaTableGb; + int16_t *pDeltaB = in->deltaTableB; + int16_t *pDeltaR = in->deltaTableR; + + vfe_program_dmi_cfg(ROLLOFF_RAM); + + /* first pack and write init table */ + for (i = 0; i < VFE_ROLL_OFF_INIT_TABLE_SIZE; i++) { + data = (((uint32_t)(*initR)) & 0x0000FFFF) | + (((uint32_t)(*initGr)) << 16); + initR++; + initGr++; + + writel(data, ctrl->vfebase + VFE_DMI_DATA_LO); + + data = (((uint32_t)(*initB)) & 0x0000FFFF) | + (((uint32_t)(*initGb))<<16); + initB++; + initGb++; + + writel(data, ctrl->vfebase + VFE_DMI_DATA_LO); + } + + /* there are gaps between the init table and delta table, + * set the offset for delta table. */ + writel(LENS_ROLL_OFF_DELTA_TABLE_OFFSET, ctrl->vfebase + VFE_DMI_ADDR); + + /* pack and write delta table */ + for (i = 0; i < VFE_ROLL_OFF_DELTA_TABLE_SIZE; i++) { + data = (((int)(*pDeltaR)) & 0x0000FFFF) | + (((int)(*pDeltaGr))<<16); + pDeltaR++; + pDeltaGr++; + + writel(data, ctrl->vfebase + VFE_DMI_DATA_LO); + + data = (((int)(*pDeltaB)) & 0x0000FFFF) | + (((int)(*pDeltaGb))<<16); + pDeltaB++; + pDeltaGb++; + + writel(data, ctrl->vfebase + VFE_DMI_DATA_LO); + } + + /* After DMI transfer, to make it safe, need to set the + * DMI_CFG to unselect any SRAM + */ + /* unselect the SRAM Bank. */ + writel(VFE_DMI_CFG_DEFAULT, ctrl->vfebase + VFE_DMI_CFG); +} + +static void vfe_set_default_reg_values(void) +{ + writel(0x800080, ctrl->vfebase + VFE_DEMUX_GAIN_0); + writel(0x800080, ctrl->vfebase + VFE_DEMUX_GAIN_1); + writel(0xFFFFF, ctrl->vfebase + VFE_CGC_OVERRIDE); + + /* default frame drop period and pattern */ + writel(0x1f, ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_CFG); + writel(0x1f, ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_CFG); + writel(0xFFFFFFFF, ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_PATTERN); + writel(0xFFFFFFFF, ctrl->vfebase + VFE_FRAMEDROP_ENC_CBCR_PATTERN); + writel(0x1f, ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y_CFG); + writel(0x1f, ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR_CFG); + writel(0xFFFFFFFF, ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y_PATTERN); + writel(0xFFFFFFFF, ctrl->vfebase + VFE_FRAMEDROP_VIEW_CBCR_PATTERN); + writel(0, ctrl->vfebase + VFE_CLAMP_MIN_CFG); + writel(0xFFFFFF, ctrl->vfebase + VFE_CLAMP_MAX_CFG); +} + +static void vfe_config_demux(uint32_t period, uint32_t even, uint32_t odd) +{ + writel(period, ctrl->vfebase + VFE_DEMUX_CFG); + writel(even, ctrl->vfebase + VFE_DEMUX_EVEN_CFG); + writel(odd, ctrl->vfebase + VFE_DEMUX_ODD_CFG); +} + +static void vfe_pm_stop(void) +{ + writel(VFE_PERFORMANCE_MONITOR_STOP, ctrl->vfebase + VFE_BUS_PM_CMD); +} + +static void vfe_camif_stop_immediately(void) +{ + writel(CAMIF_COMMAND_STOP_IMMEDIATELY, ctrl->vfebase + CAMIF_COMMAND); + writel(0, ctrl->vfebase + VFE_CGC_OVERRIDE); +} + +static void vfe_program_reg_update_cmd(uint32_t value) +{ + writel(value, ctrl->vfebase + VFE_REG_UPDATE_CMD); +} + +static void vfe_program_global_reset_cmd(uint32_t value) +{ + writel(value, ctrl->vfebase + VFE_GLOBAL_RESET_CMD); +} + +static void vfe_program_axi_cmd(uint32_t value) +{ + writel(value, ctrl->vfebase + VFE_AXI_CMD); +} + +static void vfe_program_irq_composite_mask(uint32_t value) +{ + writel(value, ctrl->vfebase + VFE_IRQ_COMPOSITE_MASK); +} + +static inline void vfe_program_irq_mask(uint32_t value) +{ + writel(value, ctrl->vfebase + VFE_IRQ_MASK); +} + +static uint32_t vfe_read_axi_status(void) +{ + return readl(ctrl->vfebase + VFE_AXI_STATUS); +} + +static void +vfe_set_stats_pingpong_address(struct vfe_stats_control *afControl, + struct vfe_stats_control *awbControl) +{ + afControl->hwRegPingAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_STATS_AF_WR_PING_ADDR); + afControl->hwRegPongAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_STATS_AF_WR_PONG_ADDR); + + awbControl->hwRegPingAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PING_ADDR); + awbControl->hwRegPongAddress = (uint8_t *) + (ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PONG_ADDR); +} + +static void vfe_program_lut_bank_sel(struct vfe_gamma_lut_sel *in) +{ + struct VFE_GammaLutSelect_ConfigCmdType cmd; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.ch0BankSelect = in->ch0BankSelect; + cmd.ch1BankSelect = in->ch1BankSelect; + cmd.ch2BankSelect = in->ch2BankSelect; + CDBG("VFE gamma lut bank selection is 0x%x\n", *((uint32_t *)&cmd)); + vfe_prog_hw(ctrl->vfebase + VFE_LUT_BANK_SEL, + (uint32_t *)&cmd, sizeof(cmd)); +} + +static void vfe_program_stats_cmd(struct vfe_stats_cmd_data *in) +{ + struct VFE_StatsCmdType stats; + memset(&stats, 0, sizeof(stats)); + + stats.autoFocusEnable = in->autoFocusEnable; + stats.axwEnable = in->axwEnable; + stats.histEnable = in->histEnable; + stats.clearHistEnable = in->clearHistEnable; + stats.histAutoClearEnable = in->histAutoClearEnable; + stats.colorConversionEnable = in->colorConversionEnable; + + writel(*((uint32_t *)&stats), ctrl->vfebase + VFE_STATS_CMD); +} + +static void vfe_pm_start(struct vfe_cmd_bus_pm_start *in) +{ + struct VFE_Bus_Pm_ConfigCmdType cmd; + memset(&cmd, 0, sizeof(struct VFE_Bus_Pm_ConfigCmdType)); + + cmd.output2YWrPmEnable = in->output2YWrPmEnable; + cmd.output2CbcrWrPmEnable = in->output2CbcrWrPmEnable; + cmd.output1YWrPmEnable = in->output1YWrPmEnable; + cmd.output1CbcrWrPmEnable = in->output1CbcrWrPmEnable; + + vfe_prog_hw(ctrl->vfebase + VFE_BUS_PM_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +static void vfe_8k_pm_start(struct vfe_cmd_bus_pm_start *in) +{ + in->output1CbcrWrPmEnable = ctrl->vfeBusConfigLocal.viewCbcrWrPathEn; + in->output1YWrPmEnable = ctrl->vfeBusConfigLocal.viewYWrPathEn; + in->output2CbcrWrPmEnable = ctrl->vfeBusConfigLocal.encCbcrWrPathEn; + in->output2YWrPmEnable = ctrl->vfeBusConfigLocal.encYWrPathEn; + + if (in->output1CbcrWrPmEnable || in->output1YWrPmEnable) + ctrl->viewPath.pmEnabled = TRUE; + + if (in->output2CbcrWrPmEnable || in->output2YWrPmEnable) + ctrl->encPath.pmEnabled = TRUE; + + vfe_pm_start(in); + + writel(VFE_PERFORMANCE_MONITOR_GO, ctrl->vfebase + VFE_BUS_PM_CMD); +} + +static uint32_t vfe_irq_pack(struct vfe_interrupt_mask data) +{ + struct vfe_irqenable packedData; + + memset(&packedData, 0, sizeof(packedData)); + + packedData.camifErrorIrq = data.camifErrorIrq; + packedData.camifSofIrq = data.camifSofIrq; + packedData.camifEolIrq = data.camifEolIrq; + packedData.camifEofIrq = data.camifEofIrq; + packedData.camifEpoch1Irq = data.camifEpoch1Irq; + packedData.camifEpoch2Irq = data.camifEpoch2Irq; + packedData.camifOverflowIrq = data.camifOverflowIrq; + packedData.ceIrq = data.ceIrq; + packedData.regUpdateIrq = data.regUpdateIrq; + packedData.resetAckIrq = data.resetAckIrq; + packedData.encYPingpongIrq = data.encYPingpongIrq; + packedData.encCbcrPingpongIrq = data.encCbcrPingpongIrq; + packedData.viewYPingpongIrq = data.viewYPingpongIrq; + packedData.viewCbcrPingpongIrq = data.viewCbcrPingpongIrq; + packedData.rdPingpongIrq = data.rdPingpongIrq; + packedData.afPingpongIrq = data.afPingpongIrq; + packedData.awbPingpongIrq = data.awbPingpongIrq; + packedData.histPingpongIrq = data.histPingpongIrq; + packedData.encIrq = data.encIrq; + packedData.viewIrq = data.viewIrq; + packedData.busOverflowIrq = data.busOverflowIrq; + packedData.afOverflowIrq = data.afOverflowIrq; + packedData.awbOverflowIrq = data.awbOverflowIrq; + packedData.syncTimer0Irq = data.syncTimer0Irq; + packedData.syncTimer1Irq = data.syncTimer1Irq; + packedData.syncTimer2Irq = data.syncTimer2Irq; + packedData.asyncTimer0Irq = data.asyncTimer0Irq; + packedData.asyncTimer1Irq = data.asyncTimer1Irq; + packedData.asyncTimer2Irq = data.asyncTimer2Irq; + packedData.asyncTimer3Irq = data.asyncTimer3Irq; + packedData.axiErrorIrq = data.axiErrorIrq; + packedData.violationIrq = data.violationIrq; + + return *((uint32_t *)&packedData); +} + +static uint32_t +vfe_irq_composite_pack(struct vfe_irq_composite_mask_config data) +{ + struct VFE_Irq_Composite_MaskType packedData; + + memset(&packedData, 0, sizeof(packedData)); + + packedData.encIrqComMaskBits = data.encIrqComMask; + packedData.viewIrqComMaskBits = data.viewIrqComMask; + packedData.ceDoneSelBits = data.ceDoneSel; + + return *((uint32_t *)&packedData); +} + +static void vfe_addr_convert(struct msm_vfe_phy_info *pinfo, + enum vfe_resp_msg type, void *data, void **ext, + int *elen) +{ + switch (type) { + case VFE_MSG_OUTPUT_P: + case VFE_MSG_OUTPUT_V:{ + pinfo->y_phy = + ((struct vfe_message *)data)->_u.msgOutput2.yBuffer; + pinfo->cbcr_phy = + ((struct vfe_message *)data)->_u.msgOutput2. + cbcrBuffer; + ctrl->extdata.bpcInfo = + ((struct vfe_message *)data)->_u.msgOutput2.bpcInfo; + ctrl->extdata.asfInfo = + ((struct vfe_message *)data)->_u.msgOutput2.asfInfo; + ctrl->extdata.frameCounter = + ((struct vfe_message *)data)->_u.msgOutput2. + frameCounter; + ctrl->extdata.pmData = + ((struct vfe_message *)data)->_u.msgOutput2.pmData; + *ext = &ctrl->extdata; + *elen = sizeof(ctrl->extdata); + } + break; + + case VFE_MSG_STATS_AF: + pinfo->sbuf_phy = + ((struct vfe_message *)data)->_u.msgStatsAf.afBuffer; + break; + + case VFE_MSG_STATS_WE: + pinfo->sbuf_phy = + ((struct vfe_message *)data)->_u.msgStatsWbExp.awbBuffer; + break; + + default: + break; + } /* switch */ +} + +static boolean vfe_send_preview_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data); +static boolean vfe_send_video_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data); +static boolean vfe_send_mainimage_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data); +static boolean vfe_send_thumbnail_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data); +static boolean vfe_send_af_stats_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data); +static boolean vfe_send_awb_stats_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data); +static boolean vfe_send_camif_error_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data); +static boolean vfe_send_bus_overflow_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data); +static boolean vfe_send_sof_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data); + +static boolean invalid(struct msm_vfe_resp *rp, + struct vfe_message *_m, void *_d) +{ + BUG_ON(1); /* this function should not be called. */ + return FALSE; +} + +static struct { + boolean (*fn)(struct msm_vfe_resp *rp, struct vfe_message *msg, + void *data); + enum vfe_resp_msg rt; /* reponse type */ +} vfe_funcs[] = { + [VFE_MSG_ID_RESET_ACK] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_START_ACK] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_STOP_ACK] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_UPDATE_ACK] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_OUTPUT_P] = { vfe_send_preview_msg, VFE_MSG_OUTPUT_P }, + [VFE_MSG_ID_OUTPUT_V] = { vfe_send_video_msg, VFE_MSG_OUTPUT_V }, + [VFE_MSG_ID_OUTPUT_S] = { vfe_send_mainimage_msg, VFE_MSG_OUTPUT_S }, + [VFE_MSG_ID_OUTPUT_T] = { vfe_send_thumbnail_msg, VFE_MSG_OUTPUT_T }, + [VFE_MSG_ID_SNAPSHOT_DONE] = { NULL, VFE_MSG_SNAPSHOT }, + [VFE_MSG_ID_STATS_AUTOFOCUS] = { vfe_send_af_stats_msg, + VFE_MSG_STATS_AF }, + [VFE_MSG_ID_STATS_WB_EXP] = { vfe_send_awb_stats_msg, + VFE_MSG_STATS_WE }, + [VFE_MSG_ID_EPOCH1] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_EPOCH2] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_SYNC_TIMER0_DONE] = { invalid }, + [VFE_MSG_ID_SYNC_TIMER1_DONE] = { invalid }, + [VFE_MSG_ID_SYNC_TIMER2_DONE] = { invalid }, + [VFE_MSG_ID_ASYNC_TIMER0_DONE] = { invalid }, + [VFE_MSG_ID_ASYNC_TIMER1_DONE] = { invalid }, + [VFE_MSG_ID_ASYNC_TIMER2_DONE] = { invalid }, + [VFE_MSG_ID_ASYNC_TIMER3_DONE] = { invalid }, + [VFE_MSG_ID_AF_OVERFLOW] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_AWB_OVERFLOW] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_AXI_ERROR] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_CAMIF_OVERFLOW] = { NULL, VFE_MSG_GENERAL }, + [VFE_MSG_ID_VIOLATION] = { invalid }, + [VFE_MSG_ID_CAMIF_ERROR] = { vfe_send_camif_error_msg, + VFE_MSG_GENERAL }, + [VFE_MSG_ID_BUS_OVERFLOW] = { vfe_send_bus_overflow_msg, + VFE_MSG_GENERAL }, + [VFE_MSG_ID_SOF_ACK] = { vfe_send_sof_msg, + VFE_MSG_GENERAL }, +}; + +static void vfe_proc_ops(enum VFE_MESSAGE_ID id, void *data) +{ + struct msm_vfe_resp *rp; + struct vfe_message *msg; + + if (id >= ARRAY_SIZE(vfe_funcs) || vfe_funcs[id].fn == invalid) { + pr_err("%s: invalid VFE message id %d\n", __func__, id); + return; + } + + /* In 8k, OUTPUT1 & OUTPUT2 messages arrive before SNAPSHOT_DONE. + * We don't send such messages to the user. Note that we can do + * this in the vfe_func[] callback, but that would cause us to + * allocate and then immediately free the msm_vfe_resp structure, + * which is wasteful. + */ + if ((ctrl->vfeOperationMode == VFE_START_OPERATION_MODE_SNAPSHOT) && + (id == VFE_MSG_ID_OUTPUT_T || + id == VFE_MSG_ID_OUTPUT_S)) + return; + + rp = ctrl->resp->vfe_alloc(sizeof(*rp) + + (vfe_funcs[id].fn ? sizeof(*msg) : 0), + ctrl->syncdata, + GFP_ATOMIC); + if (!rp) { + pr_err("%s: out of memory\n", __func__); + return; + } + + rp->type = vfe_funcs[id].rt; + rp->evt_msg.type = MSM_CAMERA_MSG; + rp->evt_msg.msg_id = id; + + if (!vfe_funcs[id].fn) { + rp->evt_msg.len = 0; + rp->evt_msg.data = 0; + } else { + /* populate the message accordingly */ + if (vfe_funcs[id].fn) + rp->evt_msg.data = msg = + (struct vfe_message *)(rp + 1); + else + rp->evt_msg.data = msg = 0; + rp->evt_msg.len = sizeof(*msg); + msg->_d = id; + if (vfe_funcs[id].fn(rp, msg, data) == FALSE) { + pr_warning("%s: freeing memory: handler for %d " + "returned false\n", __func__, id); + ctrl->resp->vfe_free(rp); + return; + } +} + + ctrl->resp->vfe_resp(rp, MSM_CAM_Q_VFE_MSG, ctrl->syncdata, GFP_KERNEL); +} + +static boolean vfe_send_bus_overflow_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, + void *data) +{ +#if 0 + memcpy(&(msg->_u.msgBusOverflow), + &ctrl->vfePmData, sizeof(ctrl->vfePmData)); +#endif + return TRUE; +} + +static boolean vfe_send_sof_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, + void *data) +{ + return TRUE; +} +static boolean vfe_send_camif_error_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, + void *data) +{ +#if 0 + memcpy(&(msg->_u.msgCamifError), + &ctrl->vfeCamifStatusLocal, sizeof(ctrl->vfeCamifStatusLocal)); +#endif + return TRUE; +} + +static void vfe_process_error_irq(struct vfe_interrupt_status *irqstatus) +{ + /* all possible error irq. Note error irqs are not enabled, it is + * checked only when other interrupts are present. */ + if (irqstatus->afOverflowIrq) + vfe_proc_ops(VFE_MSG_ID_AF_OVERFLOW, NULL); + + if (irqstatus->awbOverflowIrq) + vfe_proc_ops(VFE_MSG_ID_AWB_OVERFLOW, NULL); + + if (irqstatus->axiErrorIrq) + vfe_proc_ops(VFE_MSG_ID_AXI_ERROR, NULL); + + if (irqstatus->busOverflowIrq) + vfe_proc_ops(VFE_MSG_ID_BUS_OVERFLOW, NULL); + + if (irqstatus->camifErrorIrq) { + CDBG("vfe_irq: camif errors\n"); + vfe_proc_ops(VFE_MSG_ID_CAMIF_ERROR, NULL); + } + + if (irqstatus->camifOverflowIrq) + vfe_proc_ops(VFE_MSG_ID_CAMIF_OVERFLOW, NULL); + + if (irqstatus->violationIrq) + pr_err("%s: violation irq\n", __func__); +} + +static void vfe_process_camif_sof_irq(void) +{ + /* increment the frame id number. */ + ctrl->vfeFrameId++; + + CDBG("camif_sof_irq, frameId = %d\n", ctrl->vfeFrameId); + + /* In snapshot mode, if frame skip is programmed, + * need to check it accordingly to stop camif at + * correct frame boundary. For the dropped frames, + * there won't be any output path irqs, but there is + * still SOF irq, which can help us determine when + * to stop the camif. + */ + if (ctrl->vfeOperationMode) { + if ((1 << ctrl->vfeFrameSkipCount)&ctrl->vfeFrameSkipPattern) { + + ctrl->vfeSnapShotCount--; + if (ctrl->vfeSnapShotCount == 0) + /* terminate vfe pipeline at frame boundary. */ + writel(CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY, + ctrl->vfebase + CAMIF_COMMAND); + } + + /* update frame skip counter for bit checking. */ + ctrl->vfeFrameSkipCount++; + if (ctrl->vfeFrameSkipCount == (ctrl->vfeFrameSkipPeriod + 1)) + ctrl->vfeFrameSkipCount = 0; + } + vfe_proc_ops(VFE_MSG_ID_SOF_ACK, NULL); +} + +static boolean vfe_get_af_pingpong_status(void) +{ + uint32_t busPingPongStatus = + readl(ctrl->vfebase + VFE_BUS_PINGPONG_STATUS); + return !!(busPingPongStatus & VFE_AF_PINGPONG_STATUS_BIT); +} + +static uint32_t vfe_read_af_buf_addr(boolean pipo) +{ + if (pipo == FALSE) + return readl(ctrl->vfebase + VFE_BUS_STATS_AF_WR_PING_ADDR); + else + return readl(ctrl->vfebase + VFE_BUS_STATS_AF_WR_PONG_ADDR); +} + +static void vfe_update_af_buf_addr(boolean pipo, uint32_t addr) +{ + if (pipo == FALSE) + writel(addr, ctrl->vfebase + VFE_BUS_STATS_AF_WR_PING_ADDR); + else + writel(addr, ctrl->vfebase + VFE_BUS_STATS_AF_WR_PONG_ADDR); +} + +static boolean vfe_send_af_stats_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data) +{ + uint32_t afBufAddress = (uint32_t)data; + + /* fill message with right content. */ + /* @todo This is causing issues, need further investigate */ + /* spin_lock_irqsave(&ctrl->state_lock, flags); */ + if (ctrl->vstate != VFE_STATE_ACTIVE) + return FALSE; + + msg->_u.msgStatsAf.afBuffer = afBufAddress; + msg->_u.msgStatsAf.frameCounter = ctrl->vfeFrameId; + + ctrl->afStatsControl.ackPending = TRUE; + + vfe_addr_convert(&(rp->phy), rp->type, msg, NULL, NULL); + /* spin_unlock_irqrestore(&ctrl->state_lock, flags); */ + return TRUE; +} + +static void vfe_process_stats_af_irq(void) +{ + boolean bufferAvailable; + + if (!(ctrl->afStatsControl.ackPending)) { + + /* read hardware status. */ + ctrl->afStatsControl.pingPongStatus = + vfe_get_af_pingpong_status(); + + bufferAvailable = (ctrl->afStatsControl.pingPongStatus) ^ 1; + + ctrl->afStatsControl.bufToRender = + vfe_read_af_buf_addr(bufferAvailable); + + /* update the same buffer address (ping or pong) */ + vfe_update_af_buf_addr(bufferAvailable, + ctrl->afStatsControl.nextFrameAddrBuf); + + vfe_proc_ops(VFE_MSG_ID_STATS_AUTOFOCUS, + (void *)ctrl->afStatsControl.bufToRender); + } else + ctrl->afStatsControl.droppedStatsFrameCount++; +} + +static boolean vfe_get_awb_pingpong_status(void) +{ + uint32_t busPingPongStatus = + + readl(ctrl->vfebase + VFE_BUS_PINGPONG_STATUS); + + return !!(busPingPongStatus & VFE_AWB_PINGPONG_STATUS_BIT); + +} + +static uint32_t vfe_read_awb_buf_addr(boolean pingpong) +{ + if (pingpong == FALSE) + return readl(ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PING_ADDR); + else + return readl(ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PONG_ADDR); +} + +static void vfe_update_awb_buf_addr(boolean pingpong, uint32_t addr) +{ + if (pingpong == FALSE) + writel(addr, ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PING_ADDR); + else + writel(addr, ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PONG_ADDR); +} + +static boolean vfe_send_awb_stats_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data) +{ + uint32_t awbBufAddress = (uint32_t)data; + + /* fill message with right content. */ + /* @todo This is causing issues, need further investigate */ + /* spin_lock_irqsave(&ctrl->state_lock, flags); */ + if (ctrl->vstate != VFE_STATE_ACTIVE) + return FALSE; + + msg->_u.msgStatsWbExp.awbBuffer = awbBufAddress; + msg->_u.msgStatsWbExp.frameCounter = ctrl->vfeFrameId; + + + ctrl->awbStatsControl.ackPending = TRUE; + + vfe_addr_convert(&(rp->phy), + rp->type, msg, + NULL, NULL); + + return TRUE; +} + +static void vfe_process_stats_awb_irq(void) +{ + boolean bufferAvailable; + + if (!(ctrl->awbStatsControl.ackPending)) { + + ctrl->awbStatsControl.pingPongStatus = + vfe_get_awb_pingpong_status(); + + bufferAvailable = (ctrl->awbStatsControl.pingPongStatus) ^ 1; + + ctrl->awbStatsControl.bufToRender = + vfe_read_awb_buf_addr(bufferAvailable); + + vfe_update_awb_buf_addr(bufferAvailable, + ctrl->awbStatsControl.nextFrameAddrBuf); + + vfe_proc_ops(VFE_MSG_ID_STATS_WB_EXP, + (void *)ctrl->awbStatsControl.bufToRender); + + } else + ctrl->awbStatsControl.droppedStatsFrameCount++; +} + +static void vfe_write_gamma_table(uint8_t channel, + boolean bank, int16_t *pTable) +{ + uint16_t i; + + enum VFE_DMI_RAM_SEL dmiRamSel = NO_MEM_SELECTED; + + switch (channel) { + case 0: + if (bank == 0) + dmiRamSel = RGBLUT_RAM_CH0_BANK0; + else + dmiRamSel = RGBLUT_RAM_CH0_BANK1; + break; + + case 1: + if (bank == 0) + dmiRamSel = RGBLUT_RAM_CH1_BANK0; + else + dmiRamSel = RGBLUT_RAM_CH1_BANK1; + break; + + case 2: + if (bank == 0) + dmiRamSel = RGBLUT_RAM_CH2_BANK0; + else + dmiRamSel = RGBLUT_RAM_CH2_BANK1; + break; + + default: + break; + } + + vfe_program_dmi_cfg(dmiRamSel); + + for (i = 0; i < VFE_GAMMA_TABLE_LENGTH; i++) { + writel((uint32_t)(*pTable), ctrl->vfebase + VFE_DMI_DATA_LO); + pTable++; + } + + /* After DMI transfer, need to set the DMI_CFG to unselect any SRAM + unselect the SRAM Bank. */ + writel(VFE_DMI_CFG_DEFAULT, ctrl->vfebase + VFE_DMI_CFG); +} + +static void vfe_prog_hw_testgen_cmd(uint32_t value) +{ + writel(value, ctrl->vfebase + VFE_HW_TESTGEN_CMD); +} + +static inline void vfe_read_irq_status(struct vfe_irq_thread_msg *out) +{ + uint32_t *temp; + + memset(out, 0, sizeof(struct vfe_irq_thread_msg)); + + temp = (uint32_t *)(ctrl->vfebase + VFE_IRQ_STATUS); + out->vfeIrqStatus = readl(temp); + + temp = (uint32_t *)(ctrl->vfebase + CAMIF_STATUS); + out->camifStatus = readl(temp); + +/* this for YUV performance tuning + writel(0x7, ctrl->vfebase + CAMIF_COMMAND); + writel(0x3, ctrl->vfebase + CAMIF_COMMAND); + CDBG("camifStatus = 0x%x\n", out->camifStatus); +*/ +/* + temp = (uint32_t *)(ctrl->vfebase + VFE_DEMOSAIC_STATUS); + out->demosaicStatus = readl(temp); + + temp = (uint32_t *)(ctrl->vfebase + VFE_ASF_MAX_EDGE); + out->asfMaxEdge = readl(temp); + + temp = (uint32_t *)(ctrl->vfebase + VFE_BUS_ENC_Y_WR_PM_STATS_0); +*/ + +#if 0 + out->pmInfo.encPathPmInfo.yWrPmStats0 = readl(temp++); + out->pmInfo.encPathPmInfo.yWrPmStats1 = readl(temp++); + out->pmInfo.encPathPmInfo.cbcrWrPmStats0 = readl(temp++); + out->pmInfo.encPathPmInfo.cbcrWrPmStats1 = readl(temp++); + out->pmInfo.viewPathPmInfo.yWrPmStats0 = readl(temp++); + out->pmInfo.viewPathPmInfo.yWrPmStats1 = readl(temp++); + out->pmInfo.viewPathPmInfo.cbcrWrPmStats0 = readl(temp++); + out->pmInfo.viewPathPmInfo.cbcrWrPmStats1 = readl(temp); +#endif /* if 0 Jeff */ +} + +static void +vfe_parse_interrupt_status(struct vfe_interrupt_status *ret, +uint32_t irqStatusIn) +{ + struct vfe_irqenable hwstat; + boolean temp; + + memset(&hwstat, 0, sizeof(hwstat)); + memset(ret, 0, sizeof(*ret)); + + hwstat = *((struct vfe_irqenable *)(&irqStatusIn)); + + ret->camifErrorIrq = hwstat.camifErrorIrq; + ret->camifSofIrq = hwstat.camifSofIrq; + ret->camifEolIrq = hwstat.camifEolIrq; + ret->camifEofIrq = hwstat.camifEofIrq; + ret->camifEpoch1Irq = hwstat.camifEpoch1Irq; + ret->camifEpoch2Irq = hwstat.camifEpoch2Irq; + ret->camifOverflowIrq = hwstat.camifOverflowIrq; + ret->ceIrq = hwstat.ceIrq; + ret->regUpdateIrq = hwstat.regUpdateIrq; + ret->resetAckIrq = hwstat.resetAckIrq; + ret->encYPingpongIrq = hwstat.encYPingpongIrq; + ret->encCbcrPingpongIrq = hwstat.encCbcrPingpongIrq; + ret->viewYPingpongIrq = hwstat.viewYPingpongIrq; + ret->viewCbcrPingpongIrq = hwstat.viewCbcrPingpongIrq; + ret->rdPingpongIrq = hwstat.rdPingpongIrq; + ret->afPingpongIrq = hwstat.afPingpongIrq; + ret->awbPingpongIrq = hwstat.awbPingpongIrq; + ret->histPingpongIrq = hwstat.histPingpongIrq; + ret->encIrq = hwstat.encIrq; + ret->viewIrq = hwstat.viewIrq; + ret->busOverflowIrq = hwstat.busOverflowIrq; + ret->afOverflowIrq = hwstat.afOverflowIrq; + ret->awbOverflowIrq = hwstat.awbOverflowIrq; + ret->syncTimer0Irq = hwstat.syncTimer0Irq; + ret->syncTimer1Irq = hwstat.syncTimer1Irq; + ret->syncTimer2Irq = hwstat.syncTimer2Irq; + ret->asyncTimer0Irq = hwstat.asyncTimer0Irq; + ret->asyncTimer1Irq = hwstat.asyncTimer1Irq; + ret->asyncTimer2Irq = hwstat.asyncTimer2Irq; + ret->asyncTimer3Irq = hwstat.asyncTimer3Irq; + ret->axiErrorIrq = hwstat.axiErrorIrq; + ret->violationIrq = hwstat.violationIrq; + + /* logic OR of any error bits + * although each irq corresponds to a bit, the data type here is a + * boolean already. hence use logic operation. + */ + temp = + ret->camifErrorIrq || + ret->camifOverflowIrq || + ret->afOverflowIrq || + ret->awbOverflowIrq || + ret->awbPingpongIrq || + ret->afPingpongIrq || + ret->busOverflowIrq || ret->axiErrorIrq || ret->violationIrq; + + ret->anyErrorIrqs = temp; + + /* logic OR of any output path bits*/ + temp = ret->encYPingpongIrq || ret->encCbcrPingpongIrq || ret->encIrq; + + ret->anyOutput2PathIrqs = temp; + + temp = ret->viewYPingpongIrq || ret->viewCbcrPingpongIrq || + ret->viewIrq; + + ret->anyOutput1PathIrqs = temp; + + ret->anyOutputPathIrqs = + ret->anyOutput1PathIrqs || ret->anyOutput2PathIrqs; + + /* logic OR of any sync timer bits*/ + temp = ret->syncTimer0Irq || ret->syncTimer1Irq || ret->syncTimer2Irq; + + ret->anySyncTimerIrqs = temp; + + /* logic OR of any async timer bits*/ + temp = + ret->asyncTimer0Irq || + ret->asyncTimer1Irq || ret->asyncTimer2Irq || ret->asyncTimer3Irq; + + ret->anyAsyncTimerIrqs = temp; + + /* bool for all interrupts that are not allowed in idle state */ + temp = + ret->anyErrorIrqs || + ret->anyOutputPathIrqs || + ret->anySyncTimerIrqs || + ret->regUpdateIrq || + ret->awbPingpongIrq || + ret->afPingpongIrq || + ret->camifSofIrq || ret->camifEpoch2Irq || ret->camifEpoch1Irq; + + ret->anyIrqForActiveStatesOnly = temp; +} + +static void +vfe_get_asf_frame_info(struct vfe_frame_asf_info *rc, +struct vfe_irq_thread_msg *in) +{ + struct vfe_asf_info asfInfoTemp; + + memset(rc, 0, sizeof(*rc)); + memset(&asfInfoTemp, 0, sizeof(asfInfoTemp)); + + asfInfoTemp = *((struct vfe_asf_info *)(&(in->asfMaxEdge))); + + rc->asfHbiCount = asfInfoTemp.HBICount; + rc->asfMaxEdge = asfInfoTemp.maxEdge; +} + +static void +vfe_get_demosaic_frame_info(struct vfe_frame_bpc_info *rc, +struct vfe_irq_thread_msg *in) +{ + struct vfe_bps_info bpcInfoTemp; + + memset(rc, 0, sizeof(*rc)); + memset(&bpcInfoTemp, 0, sizeof(bpcInfoTemp)); + + bpcInfoTemp = *((struct vfe_bps_info *)(&(in->demosaicStatus))); + + rc->greenDefectPixelCount = bpcInfoTemp.greenBadPixelCount; + + rc->redBlueDefectPixelCount = bpcInfoTemp.RedBlueBadPixelCount; +} + +static void +vfe_get_camif_status(struct vfe_msg_camif_status *rc, +struct vfe_irq_thread_msg *in) +{ + struct vfe_camif_stats camifStatusTemp; + + memset(rc, 0, sizeof(*rc)); + memset(&camifStatusTemp, 0, sizeof(camifStatusTemp)); + + camifStatusTemp = *((struct vfe_camif_stats *)(&(in->camifStatus))); + + rc->camifState = (boolean) camifStatusTemp.camifHalt; + rc->lineCount = camifStatusTemp.lineCount; + rc->pixelCount = camifStatusTemp.pixelCount; +} + +static void +vfe_get_performance_monitor_data(struct vfe_bus_performance_monitor *rc, + struct vfe_irq_thread_msg *in) +{ + memset(rc, 0, sizeof(*rc)); + + rc->encPathPmInfo.yWrPmStats0 = in->pmInfo.encPathPmInfo.yWrPmStats0; + rc->encPathPmInfo.yWrPmStats1 = in->pmInfo.encPathPmInfo.yWrPmStats1; + rc->encPathPmInfo.cbcrWrPmStats0 = + in->pmInfo.encPathPmInfo.cbcrWrPmStats0; + rc->encPathPmInfo.cbcrWrPmStats1 = + in->pmInfo.encPathPmInfo.cbcrWrPmStats1; + rc->viewPathPmInfo.yWrPmStats0 = in->pmInfo.viewPathPmInfo.yWrPmStats0; + rc->viewPathPmInfo.yWrPmStats1 = in->pmInfo.viewPathPmInfo.yWrPmStats1; + rc->viewPathPmInfo.cbcrWrPmStats0 = + in->pmInfo.viewPathPmInfo.cbcrWrPmStats0; + rc->viewPathPmInfo.cbcrWrPmStats1 = + in->pmInfo.viewPathPmInfo.cbcrWrPmStats1; +} + +static void vfe_process_reg_update_irq(void) +{ + CDBG("vfe_process_reg_update_irq: ackPendingFlag is %d\n", + ctrl->vfeStartAckPendingFlag); + if (ctrl->vfeStartAckPendingFlag == TRUE) { + vfe_proc_ops(VFE_MSG_ID_START_ACK, NULL); + ctrl->vfeStartAckPendingFlag = FALSE; + } else + vfe_proc_ops(VFE_MSG_ID_UPDATE_ACK, NULL); +} + +static void vfe_process_reset_irq(void) +{ + /* unsigned long flags; */ + + /* @todo This is causing issues, need further investigate */ + /* spin_lock_irqsave(&ctrl->state_lock, flags); */ + ctrl->vstate = VFE_STATE_IDLE; + /* spin_unlock_irqrestore(&ctrl->state_lock, flags); */ + + if (ctrl->vfeStopAckPending == TRUE) { + ctrl->vfeStopAckPending = FALSE; + vfe_proc_ops(VFE_MSG_ID_STOP_ACK, NULL); + } else { + vfe_set_default_reg_values(); + vfe_proc_ops(VFE_MSG_ID_RESET_ACK, NULL); + } +} + +static void vfe_process_pingpong_irq(struct vfe_output_path *in, + uint8_t fragmentCount) +{ + uint16_t circularIndex; + uint32_t nextFragmentAddr; + + /* get next fragment address from circular buffer */ + circularIndex = (in->fragIndex) % (2 * fragmentCount); + nextFragmentAddr = in->addressBuffer[circularIndex]; + + in->fragIndex = circularIndex + 1; + + /* use next fragment to program hardware ping/pong address. */ + if (in->hwCurrentFlag == ping) { + writel(nextFragmentAddr, in->hwRegPingAddress); + in->hwCurrentFlag = pong; + + } else { + writel(nextFragmentAddr, in->hwRegPongAddress); + in->hwCurrentFlag = ping; + } +} + +static boolean vfe_send_video_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data) +{ + struct vfe_msg_output *pPayload = data; + + if (ctrl->vstate != VFE_STATE_ACTIVE) + return FALSE; + memcpy(&(msg->_u), + (void *)pPayload, sizeof(struct vfe_msg_output)); + + rp->phy.output_id = OUTPUT_TYPE_V; + CDBG("vfe_send_video_msg rp->type= %d\n", rp->type); + + vfe_addr_convert(&(rp->phy), + rp->type, msg, + &(rp->extdata), &(rp->extlen)); + return TRUE; +} + +static boolean vfe_send_preview_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data) +{ + struct vfe_msg_output *pPayload = data; + + if (ctrl->vstate != VFE_STATE_ACTIVE) + return FALSE; + + memcpy(&(msg->_u), (void *)pPayload, sizeof(struct vfe_msg_output)); + + rp->phy.output_id = OUTPUT_TYPE_P; + CDBG("vfe_send_preview_msg rp->type= %d\n", rp->type); + + vfe_addr_convert(&(rp->phy), + rp->type, msg, + &(rp->extdata), &(rp->extlen)); + + return TRUE; +} + + +static boolean vfe_send_thumbnail_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data) +{ + struct vfe_msg_output *pPayload = data; + + if (ctrl->vstate != VFE_STATE_ACTIVE) + return FALSE; + + memcpy(&(msg->_u), (void *)pPayload, sizeof(struct vfe_msg_output)); + + rp->phy.output_id = OUTPUT_TYPE_T; + CDBG("vfe_send_thumbnail_msg rp->type= %d\n", rp->type); + + if (ctrl->viewPath.snapshotPendingCount <= 1) + ctrl->viewPath.ackPending = FALSE; + + vfe_addr_convert(&(rp->phy), + rp->type, msg, + &(rp->extdata), &(rp->extlen)); + return TRUE; +} + +static boolean vfe_send_mainimage_msg(struct msm_vfe_resp *rp, + struct vfe_message *msg, void *data) +{ + struct vfe_msg_output *pPayload = data; + + if (ctrl->vstate != VFE_STATE_ACTIVE) + return FALSE; + + memcpy(&(msg->_u), (void *)pPayload, sizeof(struct vfe_msg_output)); + + rp->phy.output_id = OUTPUT_TYPE_S; + CDBG("vfe_send_mainimage_msg rp->type= %d\n", rp->type); + + if (ctrl->encPath.snapshotPendingCount <= 1) { + ctrl->encPath.ackPending = FALSE; + } + + vfe_addr_convert(&(rp->phy), + rp->type, msg, + &(rp->extdata), &(rp->extlen)); + + return TRUE; +} + +static void vfe_send_output_msg(boolean whichOutputPath, + uint32_t yPathAddr, uint32_t cbcrPathAddr) +{ + struct vfe_msg_output msgPayload; + + msgPayload.yBuffer = yPathAddr; + msgPayload.cbcrBuffer = cbcrPathAddr; + + /* asf info is common for both output1 and output2 */ +#if 0 + msgPayload.asfInfo.asfHbiCount = ctrl->vfeAsfFrameInfo.asfHbiCount; + msgPayload.asfInfo.asfMaxEdge = ctrl->vfeAsfFrameInfo.asfMaxEdge; + + /* demosaic info is common for both output1 and output2 */ + msgPayload.bpcInfo.greenDefectPixelCount = + ctrl->vfeBpcFrameInfo.greenDefectPixelCount; + msgPayload.bpcInfo.redBlueDefectPixelCount = + ctrl->vfeBpcFrameInfo.redBlueDefectPixelCount; +#endif /* if 0 */ + + /* frame ID is common for both paths. */ + msgPayload.frameCounter = ctrl->vfeFrameId; + + if (whichOutputPath) { + /* msgPayload.pmData = ctrl->vfePmData.encPathPmInfo; */ + ctrl->encPath.ackPending = TRUE; + + if (ctrl->vfeOperationMode == 0) { + if (ctrl->axiOutputMode == + VFE_AXI_OUTPUT_MODE_Output1AndOutput2) { + /* video mode */ + vfe_proc_ops(VFE_MSG_ID_OUTPUT_V, &msgPayload); + } else{ + /* preview mode */ + vfe_proc_ops(VFE_MSG_ID_OUTPUT_P, &msgPayload); + } + } else { + vfe_proc_ops(VFE_MSG_ID_OUTPUT_S, &msgPayload); + } + + } else { + /* physical output1 path from vfe */ + ctrl->viewPath.ackPending = TRUE; + + if (ctrl->vfeOperationMode == 0) { + vfe_proc_ops(VFE_MSG_ID_OUTPUT_P, &msgPayload); + CDBG(" video mode display output.\n"); + + } else{ + vfe_proc_ops(VFE_MSG_ID_OUTPUT_T, &msgPayload); + CDBG(" snapshot mode thumbnail output.\n"); + } + } +} + +static void vfe_process_frame_done_irq_multi_frag(struct vfe_output_path_combo + *in) +{ + uint32_t yAddress, cbcrAddress; + uint16_t idx; + uint32_t *ptrY; + uint32_t *ptrCbcr; + const uint32_t *ptrSrc; + uint8_t i; + + if (!in->ackPending) { + + idx = (in->currentFrame) * (in->fragCount); + + /* Send output message. */ + yAddress = in->yPath.addressBuffer[idx]; + cbcrAddress = in->cbcrPath.addressBuffer[idx]; + + /* copy next frame to current frame. */ + ptrSrc = in->nextFrameAddrBuf; + ptrY = (uint32_t *)&in->yPath.addressBuffer[idx]; + ptrCbcr = (uint32_t *)&in->cbcrPath.addressBuffer[idx]; + + /* Copy Y address */ + for (i = 0; i < in->fragCount; i++) + *ptrY++ = *ptrSrc++; + + /* Copy Cbcr address */ + for (i = 0; i < in->fragCount; i++) + *ptrCbcr++ = *ptrSrc++; + + vfe_send_output_msg(in->whichOutputPath, yAddress, cbcrAddress); + + } else { + if (in->whichOutputPath == 0) + ctrl->vfeDroppedFrameCounts.output1Count++; + + if (in->whichOutputPath == 1) + ctrl->vfeDroppedFrameCounts.output2Count++; + } + + /* toggle current frame. */ + in->currentFrame = in->currentFrame^1; + + if (ctrl->vfeOperationMode) + in->snapshotPendingCount--; +} + +static void vfe_process_frame_done_irq_no_frag_io( + struct vfe_output_path_combo *in, + uint32_t *pNextAddr, + uint32_t *pdestRenderAddr) +{ + uint32_t busPingPongStatus; + uint32_t tempAddress; + + /* 1. read hw status register. */ + busPingPongStatus = readl(ctrl->vfebase + VFE_BUS_PINGPONG_STATUS); + + CDBG("hardware status is 0x%x\n", busPingPongStatus); + + /* 2. determine ping or pong */ + /* use cbcr status */ + busPingPongStatus = busPingPongStatus & (1<<(in->cbcrStatusBit)); + + /* 3. read out address and update address */ + if (busPingPongStatus == 0) { + /* hw is working on ping, render pong buffer */ + /* a. read out pong address */ + /* read out y address. */ + tempAddress = readl(in->yPath.hwRegPongAddress); + + CDBG("pong 1 addr = 0x%x\n", tempAddress); + *pdestRenderAddr++ = tempAddress; + /* read out cbcr address. */ + tempAddress = readl(in->cbcrPath.hwRegPongAddress); + + CDBG("pong 2 addr = 0x%x\n", tempAddress); + *pdestRenderAddr = tempAddress; + + /* b. update pong address */ + writel(*pNextAddr++, in->yPath.hwRegPongAddress); + writel(*pNextAddr, in->cbcrPath.hwRegPongAddress); + } else { + /* hw is working on pong, render ping buffer */ + + /* a. read out ping address */ + tempAddress = readl(in->yPath.hwRegPingAddress); + CDBG("ping 1 addr = 0x%x\n", tempAddress); + *pdestRenderAddr++ = tempAddress; + tempAddress = readl(in->cbcrPath.hwRegPingAddress); + + CDBG("ping 2 addr = 0x%x\n", tempAddress); + *pdestRenderAddr = tempAddress; + + /* b. update ping address */ + writel(*pNextAddr++, in->yPath.hwRegPingAddress); + CDBG("NextAddress = 0x%x\n", *pNextAddr); + writel(*pNextAddr, in->cbcrPath.hwRegPingAddress); + } +} + +static void vfe_process_frame_done_irq_no_frag(struct vfe_output_path_combo *in) +{ + uint32_t addressToRender[2]; + + if (!in->ackPending) { + vfe_process_frame_done_irq_no_frag_io(in, + in->nextFrameAddrBuf, + addressToRender); + + /* use addressToRender to send out message. */ + vfe_send_output_msg(in->whichOutputPath, + addressToRender[0], addressToRender[1]); + + } else { + /* ackPending is still there, accumulate dropped frame count. + * These count can be read through ioctrl command. */ + CDBG("waiting frame ACK\n"); + + if (in->whichOutputPath == 0) + ctrl->vfeDroppedFrameCounts.output1Count++; + + if (in->whichOutputPath == 1) + ctrl->vfeDroppedFrameCounts.output2Count++; + } + + /* in case of multishot when upper layer did not ack, there will still + * be a snapshot done msg sent out, even though the number of frames + * sent out may be less than the desired number of frames. snapshot + * done msg would be helpful to indicate that vfe pipeline has stop, + * and in good known state. + */ + if (ctrl->vfeOperationMode) + in->snapshotPendingCount--; +} + +static void vfe_process_output_path_irq(struct vfe_interrupt_status *irqstatus) +{ + /* unsigned long flags; */ + + /* process the view path interrupts */ + if (irqstatus->anyOutput1PathIrqs) { + if (ctrl->viewPath.multiFrag) { + + if (irqstatus->viewCbcrPingpongIrq) + vfe_process_pingpong_irq(& + (ctrl->viewPath. + cbcrPath), + ctrl->viewPath. + fragCount); + + if (irqstatus->viewYPingpongIrq) + vfe_process_pingpong_irq(& + (ctrl->viewPath.yPath), + ctrl->viewPath. + fragCount); + + if (irqstatus->viewIrq) + vfe_process_frame_done_irq_multi_frag(&ctrl-> + viewPath); + + } else { + /* typical case for no fragment, + only frame done irq is enabled. */ + if (irqstatus->viewIrq) + vfe_process_frame_done_irq_no_frag(&ctrl-> + viewPath); + } + } + + /* process the encoder path interrupts */ + if (irqstatus->anyOutput2PathIrqs) { + if (ctrl->encPath.multiFrag) { + if (irqstatus->encCbcrPingpongIrq) + vfe_process_pingpong_irq(& + (ctrl->encPath. + cbcrPath), + ctrl->encPath. + fragCount); + + if (irqstatus->encYPingpongIrq) + vfe_process_pingpong_irq(&(ctrl->encPath.yPath), + ctrl->encPath. + fragCount); + + if (irqstatus->encIrq) + vfe_process_frame_done_irq_multi_frag(&ctrl-> + encPath); + + } else { + if (irqstatus->encIrq) + vfe_process_frame_done_irq_no_frag(&ctrl-> + encPath); + } + } + + if (ctrl->vfeOperationMode) { + if ((ctrl->encPath.snapshotPendingCount == 0) && + (ctrl->viewPath.snapshotPendingCount == 0)) { + + /* @todo This is causing issues, further investigate */ + /* spin_lock_irqsave(&ctrl->state_lock, flags); */ + ctrl->vstate = VFE_STATE_IDLE; + /* spin_unlock_irqrestore(&ctrl->state_lock, flags); */ + + vfe_proc_ops(VFE_MSG_ID_SNAPSHOT_DONE, NULL); + vfe_camif_stop_immediately(); + vfe_prog_hw_testgen_cmd(VFE_TEST_GEN_STOP); + vfe_pm_stop(); + } + } +} + +static void __vfe_do_tasklet(struct isr_queue_cmd *qcmd) +{ + if (qcmd->vfeInterruptStatus.regUpdateIrq) { + CDBG("irq regUpdateIrq\n"); + vfe_process_reg_update_irq(); + } + + if (qcmd->vfeInterruptStatus.resetAckIrq) { + CDBG("%s: process resetAckIrq\n", __func__); + vfe_process_reset_irq(); + } + + if (ctrl->vstate != VFE_STATE_ACTIVE) + return; + +#if 0 + if (qcmd->vfeInterruptStatus.camifEpoch1Irq) + vfe_proc_ops(VFE_MSG_ID_EPOCH1); + + if (qcmd->vfeInterruptStatus.camifEpoch2Irq) + vfe_proc_ops(VFE_MSG_ID_EPOCH2); +#endif /* Jeff */ + + /* next, check output path related interrupts. */ + if (qcmd->vfeInterruptStatus.anyOutputPathIrqs) { + CDBG("irq: anyOutputPathIrqs\n"); + vfe_process_output_path_irq(&qcmd->vfeInterruptStatus); + } + + if (qcmd->vfeInterruptStatus.afPingpongIrq) + vfe_process_stats_af_irq(); + + if (qcmd->vfeInterruptStatus.awbPingpongIrq) + vfe_process_stats_awb_irq(); + + /* any error irqs*/ + if (qcmd->vfeInterruptStatus.anyErrorIrqs) + vfe_process_error_irq(&qcmd->vfeInterruptStatus); + +#if 0 + if (qcmd->vfeInterruptStatus.anySyncTimerIrqs) + vfe_process_sync_timer_irq(); + + if (qcmd->vfeInterruptStatus.anyAsyncTimerIrqs) + vfe_process_async_timer_irq(); +#endif /* Jeff */ + + if (qcmd->vfeInterruptStatus.camifSofIrq) { + CDBG("irq: camifSofIrq\n"); + vfe_process_camif_sof_irq(); + } +} + +static struct isr_queue_cmd *get_irq_cmd_nosync(void) +{ + int old_get = ctrl->irq_get++; + ctrl->irq_get = ctrl->irq_get % ARRAY_SIZE(ctrl->irqs); + if (ctrl->irq_get == ctrl->irq_put) { + pr_err("%s: out of irq command packets\n", __func__); + ctrl->irq_get = old_get; + return NULL; + } + + return ctrl->irqs + old_get; +} + +static struct isr_queue_cmd *next_irq_cmd(void) +{ + unsigned long flags; + struct isr_queue_cmd *cmd; + spin_lock_irqsave(&ctrl->irqs_lock, flags); + if (ctrl->irq_get == ctrl->irq_put) { + spin_unlock_irqrestore(&ctrl->irqs_lock, flags); + return NULL; /* already empty */ + } + cmd = ctrl->irqs + ctrl->irq_put; + spin_unlock_irqrestore(&ctrl->irqs_lock, flags); + return cmd; +} + +static void put_irq_cmd(void) +{ + unsigned long flags; + spin_lock_irqsave(&ctrl->irqs_lock, flags); + if (ctrl->irq_get == ctrl->irq_put) { + spin_unlock_irqrestore(&ctrl->irqs_lock, flags); + return; /* already empty */ + } + ctrl->irq_put++; + ctrl->irq_put %= ARRAY_SIZE(ctrl->irqs); + spin_unlock_irqrestore(&ctrl->irqs_lock, flags); +} + +static void vfe_do_tasklet(unsigned long data) +{ + int cnt = 0; + unsigned long flags; + struct isr_queue_cmd *qcmd = NULL; + + spin_lock_irqsave(&msm_vfe_ctrl_lock, flags); + if (!ctrl) { + spin_unlock_irqrestore(&msm_vfe_ctrl_lock, flags); + return; + } + + CDBG("%s\n", __func__); + + while ((qcmd = next_irq_cmd())) { + __vfe_do_tasklet(qcmd); + put_irq_cmd(); + cnt++; + } + + if (cnt > ARRAY_SIZE(ctrl->irqs)/2) + CDBG("%s: serviced %d vfe interrupts\n", __func__, cnt); + + spin_unlock_irqrestore(&msm_vfe_ctrl_lock, flags); +} + +DECLARE_TASKLET(vfe_tasklet, vfe_do_tasklet, 0); + +static irqreturn_t vfe_parse_irq(int irq_num, void *data) +{ + unsigned long flags; + uint32_t irqStatusLocal; + struct vfe_irq_thread_msg irq; + struct isr_queue_cmd *qcmd; + + CDBG("vfe_parse_irq\n"); + + if (!atomic_read(&ctrl->vfe_serv_interrupt)) + return IRQ_HANDLED; + + vfe_read_irq_status(&irq); + + if (irq.vfeIrqStatus == 0) { + CDBG("vfe_parse_irq: irq.vfeIrqStatus is 0\n"); + return IRQ_HANDLED; + } + + if (ctrl->vfeStopAckPending) + irqStatusLocal = (VFE_IMASK_WHILE_STOPPING & irq.vfeIrqStatus); + else + irqStatusLocal = + ((ctrl->vfeImaskPacked | VFE_IMASK_ERROR_ONLY) & + irq.vfeIrqStatus); + + spin_lock_irqsave(&ctrl->irqs_lock, flags); + qcmd = get_irq_cmd_nosync(); + if (!qcmd) { + spin_unlock_irqrestore(&ctrl->irqs_lock, flags); + goto done; + } + /* first parse the interrupt status to local data structures. */ + vfe_parse_interrupt_status(&qcmd->vfeInterruptStatus, irqStatusLocal); + vfe_get_asf_frame_info(&qcmd->vfeAsfFrameInfo, &irq); + vfe_get_demosaic_frame_info(&qcmd->vfeBpcFrameInfo, &irq); + vfe_get_camif_status(&qcmd->vfeCamifStatusLocal, &irq); + vfe_get_performance_monitor_data(&qcmd->vfePmData, &irq); + spin_unlock_irqrestore(&ctrl->irqs_lock, flags); + tasklet_schedule(&vfe_tasklet); + +done: + /* clear the pending interrupt of the same kind.*/ + writel(irq.vfeIrqStatus, ctrl->vfebase + VFE_IRQ_CLEAR); + + return IRQ_HANDLED; +} + +int vfe_cmd_init(struct msm_vfe_callback *presp, + struct platform_device *pdev, void *sdata) +{ + struct resource *vfemem, *vfeirq, *vfeio; + int rc; + struct msm_camera_sensor_info *s_info; + s_info = pdev->dev.platform_data; + + pdev->resource = s_info->resource; + pdev->num_resources = s_info->num_resources; + + vfemem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!vfemem) { + pr_err("%s: no mem resource\n", __func__); + return -ENODEV; + } + + vfeirq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!vfeirq) { + pr_err("%s: no irq resource\n", __func__); + return -ENODEV; + } + + vfeio = request_mem_region(vfemem->start, + resource_size(vfemem), pdev->name); + if (!vfeio) { + pr_err("%s: VFE region already claimed\n", __func__); + return -EBUSY; + } + + ctrl = kzalloc(sizeof(struct msm_vfe8x_ctrl), GFP_KERNEL); + if (!ctrl) { + pr_err("%s: out of memory\n", __func__); + rc = -ENOMEM; + goto cmd_init_failed1; + } + atomic_set(&ctrl->vfe_serv_interrupt, 0); + ctrl->vfeirq = vfeirq->start; + + ctrl->vfebase = + ioremap(vfemem->start, (vfemem->end - vfemem->start) + 1); + if (!ctrl->vfebase) { + pr_err("%s: ioremap failed\n", __func__); + rc = -ENOMEM; + goto cmd_init_failed2; + } + + rc = request_irq(ctrl->vfeirq, vfe_parse_irq, + IRQF_TRIGGER_RISING, "vfe", 0); + if (rc < 0) { + pr_err("%s: request_irq(%d) failed\n", __func__, ctrl->vfeirq); + goto cmd_init_failed2; + } + + if (presp && presp->vfe_resp) + ctrl->resp = presp; + else { + pr_err("%s: no vfe_resp function\n", __func__); + + rc = -EIO; + goto cmd_init_failed3; + } + + ctrl->syncdata = sdata; + return 0; + +cmd_init_failed3: + disable_irq(ctrl->vfeirq); + free_irq(ctrl->vfeirq, 0); + iounmap(ctrl->vfebase); +cmd_init_failed2: + kfree(ctrl); +cmd_init_failed1: + release_mem_region(vfemem->start, (vfemem->end - vfemem->start) + 1); + return rc; +} + +void vfe_cmd_release(struct platform_device *dev) +{ + struct resource *mem; + unsigned long flags; + atomic_set(&ctrl->vfe_serv_interrupt, 0); + disable_irq(ctrl->vfeirq); + free_irq(ctrl->vfeirq, 0); + + iounmap(ctrl->vfebase); + mem = platform_get_resource(dev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, (mem->end - mem->start) + 1); + + spin_lock_irqsave(&msm_vfe_ctrl_lock, flags); + kfree(ctrl); + ctrl = 0; + spin_unlock_irqrestore(&msm_vfe_ctrl_lock, flags); +} + +void vfe_stats_af_stop(void) +{ + ctrl->vfeStatsCmdLocal.autoFocusEnable = FALSE; + ctrl->vfeImaskLocal.afPingpongIrq = FALSE; +} + +void vfe_stop(void) +{ + int spin_cnt = 0; + uint32_t vfeAxiStauts; + + /* for reset hw modules, and send msg when reset_irq comes.*/ + ctrl->vfeStopAckPending = TRUE; + + ctrl->vfeStatsPingPongReloadFlag = FALSE; + vfe_pm_stop(); + + /* disable all interrupts. */ + vfe_program_irq_mask(VFE_DISABLE_ALL_IRQS); + + /* in either continuous or snapshot mode, stop command can be issued + * at any time. + */ + vfe_camif_stop_immediately(); + vfe_program_axi_cmd(AXI_HALT); + vfe_prog_hw_testgen_cmd(VFE_TEST_GEN_STOP); + + do { + vfeAxiStauts = vfe_read_axi_status(); + spin_cnt++; + } while (!(vfeAxiStauts & AXI_STATUS_BUSY_MASK)); + if (spin_cnt > 1) + pr_warning("%s: spin_cnt %d\n", __func__, spin_cnt); + + vfe_program_axi_cmd(AXI_HALT_CLEAR); + + /* clear all pending interrupts */ + writel(VFE_CLEAR_ALL_IRQS, ctrl->vfebase + VFE_IRQ_CLEAR); + + /* enable reset_ack and async timer interrupt only while stopping + * the pipeline. + */ + vfe_program_irq_mask(VFE_IMASK_WHILE_STOPPING); + + vfe_program_global_reset_cmd(VFE_RESET_UPON_STOP_CMD); +} + +void vfe_update(void) +{ + ctrl->vfeModuleEnableLocal.statsEnable = + ctrl->vfeStatsCmdLocal.autoFocusEnable | + ctrl->vfeStatsCmdLocal.axwEnable; + + vfe_reg_module_cfg(&ctrl->vfeModuleEnableLocal); + + vfe_program_stats_cmd(&ctrl->vfeStatsCmdLocal); + + ctrl->vfeImaskPacked = vfe_irq_pack(ctrl->vfeImaskLocal); + vfe_program_irq_mask(ctrl->vfeImaskPacked); + + if ((ctrl->vfeModuleEnableLocal.statsEnable == TRUE) && + (ctrl->vfeStatsPingPongReloadFlag == FALSE)) { + ctrl->vfeStatsPingPongReloadFlag = TRUE; + + ctrl->vfeBusCmdLocal.statsPingpongReload = TRUE; + vfe_reg_bus_cmd(&ctrl->vfeBusCmdLocal); + } + + vfe_program_reg_update_cmd(VFE_REG_UPDATE_TRIGGER); +} + +int vfe_rgb_gamma_update(struct vfe_cmd_rgb_gamma_config *in) +{ + int rc = 0; + + ctrl->vfeModuleEnableLocal.rgbLUTEnable = in->enable; + + switch (in->channelSelect) { + case RGB_GAMMA_CH0_SELECTED: + ctrl->vfeGammaLutSel.ch0BankSelect ^= 1; + vfe_write_gamma_table(0, + ctrl->vfeGammaLutSel.ch0BankSelect, + in->table); + break; + + case RGB_GAMMA_CH1_SELECTED: + ctrl->vfeGammaLutSel.ch1BankSelect ^= 1; + vfe_write_gamma_table(1, + ctrl->vfeGammaLutSel.ch1BankSelect, + in->table); + break; + + case RGB_GAMMA_CH2_SELECTED: + ctrl->vfeGammaLutSel.ch2BankSelect ^= 1; + vfe_write_gamma_table(2, + ctrl->vfeGammaLutSel.ch2BankSelect, + in->table); + break; + + case RGB_GAMMA_CH0_CH1_SELECTED: + ctrl->vfeGammaLutSel.ch0BankSelect ^= 1; + ctrl->vfeGammaLutSel.ch1BankSelect ^= 1; + vfe_write_gamma_table(0, ctrl->vfeGammaLutSel.ch0BankSelect, + in->table); + vfe_write_gamma_table(1, ctrl->vfeGammaLutSel.ch1BankSelect, + in->table); + break; + + case RGB_GAMMA_CH0_CH2_SELECTED: + ctrl->vfeGammaLutSel.ch0BankSelect ^= 1; + ctrl->vfeGammaLutSel.ch2BankSelect ^= 1; + vfe_write_gamma_table(0, ctrl->vfeGammaLutSel.ch0BankSelect, + in->table); + vfe_write_gamma_table(2, ctrl->vfeGammaLutSel.ch2BankSelect, + in->table); + break; + + case RGB_GAMMA_CH1_CH2_SELECTED: + ctrl->vfeGammaLutSel.ch1BankSelect ^= 1; + ctrl->vfeGammaLutSel.ch2BankSelect ^= 1; + vfe_write_gamma_table(1, ctrl->vfeGammaLutSel.ch1BankSelect, + in->table); + vfe_write_gamma_table(2, ctrl->vfeGammaLutSel.ch2BankSelect, + in->table); + break; + + case RGB_GAMMA_CH0_CH1_CH2_SELECTED: + ctrl->vfeGammaLutSel.ch0BankSelect ^= 1; + ctrl->vfeGammaLutSel.ch1BankSelect ^= 1; + ctrl->vfeGammaLutSel.ch2BankSelect ^= 1; + vfe_write_gamma_table(0, ctrl->vfeGammaLutSel.ch0BankSelect, + in->table); + vfe_write_gamma_table(1, ctrl->vfeGammaLutSel.ch1BankSelect, + in->table); + vfe_write_gamma_table(2, ctrl->vfeGammaLutSel.ch2BankSelect, + in->table); + break; + + default: + pr_err("%s: invalid gamma channel %d\n", __func__, + in->channelSelect); + return -EINVAL; + } /* switch */ + + /* update the gammaLutSel register. */ + vfe_program_lut_bank_sel(&ctrl->vfeGammaLutSel); + + return rc; +} + +int vfe_rgb_gamma_config(struct vfe_cmd_rgb_gamma_config *in) +{ + int rc = 0; + + ctrl->vfeModuleEnableLocal.rgbLUTEnable = in->enable; + + switch (in->channelSelect) { + case RGB_GAMMA_CH0_SELECTED: +vfe_write_gamma_table(0, 0, in->table); +break; + + case RGB_GAMMA_CH1_SELECTED: + vfe_write_gamma_table(1, 0, in->table); + break; + + case RGB_GAMMA_CH2_SELECTED: + vfe_write_gamma_table(2, 0, in->table); + break; + + case RGB_GAMMA_CH0_CH1_SELECTED: + vfe_write_gamma_table(0, 0, in->table); + vfe_write_gamma_table(1, 0, in->table); + break; + + case RGB_GAMMA_CH0_CH2_SELECTED: + vfe_write_gamma_table(0, 0, in->table); + vfe_write_gamma_table(2, 0, in->table); + break; + + case RGB_GAMMA_CH1_CH2_SELECTED: + vfe_write_gamma_table(1, 0, in->table); + vfe_write_gamma_table(2, 0, in->table); + break; + + case RGB_GAMMA_CH0_CH1_CH2_SELECTED: + vfe_write_gamma_table(0, 0, in->table); + vfe_write_gamma_table(1, 0, in->table); + vfe_write_gamma_table(2, 0, in->table); + break; + + default: + pr_err("%s: invalid gamma channel %d\n", __func__, + in->channelSelect); + rc = -EINVAL; + break; + } /* switch */ + + return rc; +} + +void vfe_stats_af_ack(struct vfe_cmd_stats_af_ack *in) +{ + ctrl->afStatsControl.nextFrameAddrBuf = in->nextAFOutputBufferAddr; + ctrl->afStatsControl.ackPending = FALSE; +} + +void vfe_stats_wb_exp_ack(struct vfe_cmd_stats_wb_exp_ack *in) +{ + ctrl->awbStatsControl.nextFrameAddrBuf = in->nextWbExpOutputBufferAddr; + ctrl->awbStatsControl.ackPending = FALSE; +} + + +void vfe_output_v_ack(struct vfe_cmd_output_ack *in) +{ + const uint32_t *psrc; + uint32_t *pdest; + uint8_t i; + + pdest = ctrl->encPath.nextFrameAddrBuf; + + CDBG("video_frame_ack: ack addr = 0x%x\n", in->ybufaddr[0]); + + psrc = in->ybufaddr; + for (i = 0; i < ctrl->encPath.fragCount; i++) + *pdest++ = *psrc++; + + psrc = in->chromabufaddr; + for (i = 0; i < ctrl->encPath.fragCount; i++) + *pdest++ = *psrc++; + + ctrl->encPath.ackPending = FALSE; +} + +void vfe_output_p_ack(struct vfe_cmd_output_ack *in) +{ + const uint32_t *psrc; + uint32_t *pdest; + uint8_t i; + + if (ctrl->axiOutputMode == VFE_AXI_OUTPUT_MODE_Output1AndOutput2) { + /* video mode, preview comes from output1 path */ + + pdest = ctrl->viewPath.nextFrameAddrBuf; + + psrc = in->ybufaddr; + for (i = 0; i < ctrl->viewPath.fragCount; i++) + *pdest++ = *psrc++; + + psrc = in->chromabufaddr; + for (i = 0; i < ctrl->viewPath.fragCount; i++) + *pdest++ = *psrc++; + + ctrl->viewPath.ackPending = FALSE; + + } else { /* preview mode, preview comes from output2 path. */ + pdest = ctrl->encPath.nextFrameAddrBuf; + + psrc = in->ybufaddr; + for (i = 0; i < ctrl->encPath.fragCount; i++) + *pdest++ = *psrc++; + + psrc = in->chromabufaddr; + for (i = 0; i < ctrl->encPath.fragCount; i++) + *pdest++ = *psrc++; + + ctrl->encPath.ackPending = FALSE; + + } +} + +void vfe_start(struct vfe_cmd_start *in) +{ + uint32_t pmstatus = 0; + boolean rawmode; + uint32_t demperiod = 0; + uint32_t demeven = 0; + uint32_t demodd = 0; + + /* derived from other commands. (camif config, axi output config, + * etc) + */ + struct vfe_cfg hwcfg; + struct vfe_upsample_cfg chromupcfg; + + CDBG("vfe_start operationMode = %d\n", in->operationMode); + + memset(&hwcfg, 0, sizeof(hwcfg)); + memset(&chromupcfg, 0, sizeof(chromupcfg)); + + switch (in->pixel) { + case VFE_BAYER_RGRGRG: + demperiod = 1; + demeven = 0xC9; + demodd = 0xAC; + break; + + case VFE_BAYER_GRGRGR: + demperiod = 1; + demeven = 0x9C; + demodd = 0xCA; + break; + + case VFE_BAYER_BGBGBG: + demperiod = 1; + demeven = 0xCA; + demodd = 0x9C; + break; + + case VFE_BAYER_GBGBGB: + demperiod = 1; + demeven = 0xAC; + demodd = 0xC9; + break; + + case VFE_YUV_YCbYCr: + demperiod = 3; + demeven = 0x9CAC; + demodd = 0x9CAC; + break; + + case VFE_YUV_YCrYCb: + demperiod = 3; + demeven = 0xAC9C; + demodd = 0xAC9C; + break; + + case VFE_YUV_CbYCrY: + demperiod = 3; + demeven = 0xC9CA; + demodd = 0xC9CA; + break; + + case VFE_YUV_CrYCbY: + demperiod = 3; + demeven = 0xCAC9; + demodd = 0xCAC9; + break; + + default: + return; + } + + vfe_config_demux(demperiod, demeven, demodd); + + vfe_program_lut_bank_sel(&ctrl->vfeGammaLutSel); + + /* save variables to local. */ + ctrl->vfeOperationMode = in->operationMode; + if (ctrl->vfeOperationMode == VFE_START_OPERATION_MODE_SNAPSHOT) { + + update_axi_qos(MSM_AXI_QOS_SNAPSHOT); + /* in snapshot mode, initialize snapshot count*/ + ctrl->vfeSnapShotCount = in->snapshotCount; + + /* save the requested count, this is temporarily done, to + help with HJR / multishot. */ + ctrl->vfeRequestedSnapShotCount = ctrl->vfeSnapShotCount; + + CDBG("requested snapshot count = %d\n", ctrl->vfeSnapShotCount); + + /* Assumption is to have the same pattern and period for both + paths, if both paths are used. */ + if (ctrl->viewPath.pathEnabled) { + ctrl->viewPath.snapshotPendingCount = in->snapshotCount; + + ctrl->vfeFrameSkipPattern = + ctrl->vfeFrameSkip.output1Pattern; + ctrl->vfeFrameSkipPeriod = + ctrl->vfeFrameSkip.output1Period; + } + + if (ctrl->encPath.pathEnabled) { + ctrl->encPath.snapshotPendingCount = in->snapshotCount; + + ctrl->vfeFrameSkipPattern = + ctrl->vfeFrameSkip.output2Pattern; + ctrl->vfeFrameSkipPeriod = + ctrl->vfeFrameSkip.output2Period; + } + } else + update_axi_qos(MSM_AXI_QOS_PREVIEW); + + /* enable color conversion for bayer sensor + if stats enabled, need to do color conversion. */ + if (in->pixel <= VFE_BAYER_GBGBGB) + ctrl->vfeStatsCmdLocal.colorConversionEnable = TRUE; + + vfe_program_stats_cmd(&ctrl->vfeStatsCmdLocal); + + if (in->pixel >= VFE_YUV_YCbYCr) + ctrl->vfeModuleEnableLocal.chromaUpsampleEnable = TRUE; + + ctrl->vfeModuleEnableLocal.demuxEnable = TRUE; + + /* if any stats module is enabled, the main bit is enabled. */ + ctrl->vfeModuleEnableLocal.statsEnable = + ctrl->vfeStatsCmdLocal.autoFocusEnable | + ctrl->vfeStatsCmdLocal.axwEnable; + + vfe_reg_module_cfg(&ctrl->vfeModuleEnableLocal); + + /* in case of offline processing, do not need to config camif. Having + * bus output enabled in camif_config register might confuse the + * hardware? + */ + if (in->inputSource != VFE_START_INPUT_SOURCE_AXI) { + vfe_reg_camif_config(&ctrl->vfeCamifConfigLocal); + } else { + /* offline processing, enable axi read */ + ctrl->vfeBusConfigLocal.stripeRdPathEn = TRUE; + ctrl->vfeBusCmdLocal.stripeReload = TRUE; + ctrl->vfeBusConfigLocal.rawPixelDataSize = + ctrl->axiInputDataSize; + } + + vfe_reg_bus_cfg(&ctrl->vfeBusConfigLocal); + + /* directly from start command */ + hwcfg.pixelPattern = in->pixel; + hwcfg.inputSource = in->inputSource; + writel(*(uint32_t *)&hwcfg, ctrl->vfebase + VFE_CFG); + + /* regardless module enabled or not, it does not hurt + * to program the cositing mode. */ + chromupcfg.chromaCositingForYCbCrInputs = in->yuvInputCositingMode; + + writel(*(uint32_t *)&chromupcfg, + ctrl->vfebase + VFE_CHROMA_UPSAMPLE_CFG); + + /* MISR to monitor the axi read. */ + writel(0xd8, ctrl->vfebase + VFE_BUS_MISR_MAST_CFG_0); + + /* clear all pending interrupts. */ + writel(VFE_CLEAR_ALL_IRQS, ctrl->vfebase + VFE_IRQ_CLEAR); + + /* define how composite interrupt work. */ + ctrl->vfeImaskCompositePacked = + vfe_irq_composite_pack(ctrl->vfeIrqCompositeMaskLocal); + + vfe_program_irq_composite_mask(ctrl->vfeImaskCompositePacked); + + /* enable all necessary interrupts. */ + ctrl->vfeImaskLocal.camifSofIrq = TRUE; + ctrl->vfeImaskLocal.regUpdateIrq = TRUE; + ctrl->vfeImaskLocal.resetAckIrq = TRUE; + + ctrl->vfeImaskPacked = vfe_irq_pack(ctrl->vfeImaskLocal); + vfe_program_irq_mask(ctrl->vfeImaskPacked); + + /* enable bus performance monitor */ + vfe_8k_pm_start(&ctrl->vfeBusPmConfigLocal); + + /* trigger vfe reg update */ + ctrl->vfeStartAckPendingFlag = TRUE; + + /* write bus command to trigger reload of ping pong buffer. */ + ctrl->vfeBusCmdLocal.busPingpongReload = TRUE; + + if (ctrl->vfeModuleEnableLocal.statsEnable == TRUE) { + ctrl->vfeBusCmdLocal.statsPingpongReload = TRUE; + ctrl->vfeStatsPingPongReloadFlag = TRUE; + } + + writel(VFE_REG_UPDATE_TRIGGER, ctrl->vfebase + VFE_REG_UPDATE_CMD); + + /* program later than the reg update. */ + vfe_reg_bus_cmd(&ctrl->vfeBusCmdLocal); + + if ((in->inputSource == + VFE_START_INPUT_SOURCE_CAMIF) || + (in->inputSource == VFE_START_INPUT_SOURCE_TESTGEN)) + writel(CAMIF_COMMAND_START, ctrl->vfebase + CAMIF_COMMAND); + + /* start test gen if it is enabled */ + if (ctrl->vfeTestGenStartFlag == TRUE) { + ctrl->vfeTestGenStartFlag = FALSE; + vfe_prog_hw_testgen_cmd(VFE_TEST_GEN_GO); + } + + CDBG("ctrl->axiOutputMode = %d\n", ctrl->axiOutputMode); + if (ctrl->axiOutputMode == VFE_AXI_OUTPUT_MODE_CAMIFToAXIViaOutput2) { + /* raw dump mode */ + rawmode = TRUE; + + while (rawmode) { + pmstatus = + readl(ctrl->vfebase + + VFE_BUS_ENC_CBCR_WR_PM_STATS_1); + + if ((pmstatus & VFE_PM_BUF_MAX_CNT_MASK) != 0) + rawmode = FALSE; + } + + vfe_proc_ops(VFE_MSG_ID_START_ACK, NULL); + ctrl->vfeStartAckPendingFlag = FALSE; + } + + ctrl->vstate = VFE_STATE_ACTIVE; +} + +void vfe_la_update(struct vfe_cmd_la_config *in) +{ + int16_t *pTable; + enum VFE_DMI_RAM_SEL dmiRamSel; + int i; + + pTable = in->table; + ctrl->vfeModuleEnableLocal.lumaAdaptationEnable = in->enable; + + /* toggle the bank to be used. */ + ctrl->vfeLaBankSel ^= 1; + + if (ctrl->vfeLaBankSel == 0) + dmiRamSel = LUMA_ADAPT_LUT_RAM_BANK0; + else + dmiRamSel = LUMA_ADAPT_LUT_RAM_BANK1; + + /* configure the DMI_CFG to select right sram */ + vfe_program_dmi_cfg(dmiRamSel); + + for (i = 0; i < VFE_LA_TABLE_LENGTH; i++) { + writel((uint32_t)(*pTable), ctrl->vfebase + VFE_DMI_DATA_LO); + pTable++; + } + + /* After DMI transfer, to make it safe, need to set + * the DMI_CFG to unselect any SRAM */ + writel(VFE_DMI_CFG_DEFAULT, ctrl->vfebase + VFE_DMI_CFG); + writel(ctrl->vfeLaBankSel, ctrl->vfebase + VFE_LA_CFG); +} + +void vfe_la_config(struct vfe_cmd_la_config *in) +{ + uint16_t i; + int16_t *pTable; + enum VFE_DMI_RAM_SEL dmiRamSel; + + pTable = in->table; + ctrl->vfeModuleEnableLocal.lumaAdaptationEnable = in->enable; + + if (ctrl->vfeLaBankSel == 0) + dmiRamSel = LUMA_ADAPT_LUT_RAM_BANK0; + else + dmiRamSel = LUMA_ADAPT_LUT_RAM_BANK1; + + /* configure the DMI_CFG to select right sram */ + vfe_program_dmi_cfg(dmiRamSel); + + for (i = 0; i < VFE_LA_TABLE_LENGTH; i++) { + writel((uint32_t)(*pTable), ctrl->vfebase + VFE_DMI_DATA_LO); + pTable++; + } + + /* After DMI transfer, to make it safe, need to set the + * DMI_CFG to unselect any SRAM */ + writel(VFE_DMI_CFG_DEFAULT, ctrl->vfebase + VFE_DMI_CFG); + + /* can only be bank 0 or bank 1 for now. */ + writel(ctrl->vfeLaBankSel, ctrl->vfebase + VFE_LA_CFG); + CDBG("VFE Luma adaptation bank selection is 0x%x\n", + *(uint32_t *)&ctrl->vfeLaBankSel); +} + +void vfe_test_gen_start(struct vfe_cmd_test_gen_start *in) +{ + struct VFE_TestGen_ConfigCmdType cmd; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.numFrame = in->numFrame; + cmd.pixelDataSelect = in->pixelDataSelect; + cmd.systematicDataSelect = in->systematicDataSelect; + cmd.pixelDataSize = (uint32_t)in->pixelDataSize; + cmd.hsyncEdge = (uint32_t)in->hsyncEdge; + cmd.vsyncEdge = (uint32_t)in->vsyncEdge; + cmd.imageWidth = in->imageWidth; + cmd.imageHeight = in->imageHeight; + cmd.sofOffset = in->startOfFrameOffset; + cmd.eofNOffset = in->endOfFrameNOffset; + cmd.solOffset = in->startOfLineOffset; + cmd.eolNOffset = in->endOfLineNOffset; + cmd.hBlankInterval = in->hbi; + cmd.vBlankInterval = in->vbl; + cmd.vBlankIntervalEnable = in->vblEnable; + cmd.sofDummy = in->startOfFrameDummyLine; + cmd.eofDummy = in->endOfFrameDummyLine; + cmd.unicolorBarSelect = in->unicolorBarSelect; + cmd.unicolorBarEnable = in->unicolorBarEnable; + cmd.splitEnable = in->colorBarsSplitEnable; + cmd.pixelPattern = (uint32_t)in->colorBarsPixelPattern; + cmd.rotatePeriod = in->colorBarsRotatePeriod; + cmd.randomSeed = in->testGenRandomSeed; + + vfe_prog_hw(ctrl->vfebase + VFE_HW_TESTGEN_CFG, + (uint32_t *) &cmd, sizeof(cmd)); +} + +void vfe_frame_skip_update(struct vfe_cmd_frame_skip_update *in) +{ + struct VFE_FRAME_SKIP_UpdateCmdType cmd; + + cmd.yPattern = in->output1Pattern; + cmd.cbcrPattern = in->output1Pattern; + vfe_prog_hw(ctrl->vfebase + VFE_FRAMEDROP_VIEW_Y_PATTERN, + (uint32_t *)&cmd, sizeof(cmd)); + + cmd.yPattern = in->output2Pattern; + cmd.cbcrPattern = in->output2Pattern; + vfe_prog_hw(ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_PATTERN, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_frame_skip_config(struct vfe_cmd_frame_skip_config *in) +{ + struct vfe_frame_skip_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeFrameSkip = *in; + + cmd.output2YPeriod = in->output2Period; + cmd.output2CbCrPeriod = in->output2Period; + cmd.output2YPattern = in->output2Pattern; + cmd.output2CbCrPattern = in->output2Pattern; + cmd.output1YPeriod = in->output1Period; + cmd.output1CbCrPeriod = in->output1Period; + cmd.output1YPattern = in->output1Pattern; + cmd.output1CbCrPattern = in->output1Pattern; + + vfe_prog_hw(ctrl->vfebase + VFE_FRAMEDROP_ENC_Y_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_output_clamp_config(struct vfe_cmd_output_clamp_config *in) +{ + struct vfe_output_clamp_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + + cmd.yChanMax = in->maxCh0; + cmd.cbChanMax = in->maxCh1; + cmd.crChanMax = in->maxCh2; + + cmd.yChanMin = in->minCh0; + cmd.cbChanMin = in->minCh1; + cmd.crChanMin = in->minCh2; + + vfe_prog_hw(ctrl->vfebase + VFE_CLAMP_MAX_CFG, (uint32_t *)&cmd, + sizeof(cmd)); +} + +void vfe_camif_frame_update(struct vfe_cmds_camif_frame *in) +{ + struct vfe_camifframe_update cmd; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.pixelsPerLine = in->pixelsPerLine; + cmd.linesPerFrame = in->linesPerFrame; + + vfe_prog_hw(ctrl->vfebase + CAMIF_FRAME_CONFIG, (uint32_t *)&cmd, + sizeof(cmd)); +} + +void vfe_color_correction_config(struct vfe_cmd_color_correction_config *in) +{ + struct vfe_color_correction_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + ctrl->vfeModuleEnableLocal.colorCorrectionEnable = in->enable; + + cmd.c0 = in->C0; + cmd.c1 = in->C1; + cmd.c2 = in->C2; + cmd.c3 = in->C3; + cmd.c4 = in->C4; + cmd.c5 = in->C5; + cmd.c6 = in->C6; + cmd.c7 = in->C7; + cmd.c8 = in->C8; + + cmd.k0 = in->K0; + cmd.k1 = in->K1; + cmd.k2 = in->K2; + + cmd.coefQFactor = in->coefQFactor; + + vfe_prog_hw(ctrl->vfebase + VFE_COLOR_CORRECT_COEFF_0, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_demosaic_abf_update(struct vfe_cmd_demosaic_abf_update *in) +{ +struct vfe_demosaic_cfg cmd; + struct vfe_demosaic_abf_cfg cmdabf; + uint32_t temp; + + memset(&cmd, 0, sizeof(cmd)); + temp = readl(ctrl->vfebase + VFE_DEMOSAIC_CFG); + + cmd = *((struct vfe_demosaic_cfg *)(&temp)); + cmd.abfEnable = in->abfUpdate.enable; + cmd.forceAbfOn = in->abfUpdate.forceOn; + cmd.abfShift = in->abfUpdate.shift; + vfe_prog_hw(ctrl->vfebase + VFE_DEMOSAIC_CFG, + (uint32_t *)&cmd, sizeof(cmd)); + + cmdabf.lpThreshold = in->abfUpdate.lpThreshold; + cmdabf.ratio = in->abfUpdate.ratio; + cmdabf.minValue = in->abfUpdate.min; + cmdabf.maxValue = in->abfUpdate.max; + vfe_prog_hw(ctrl->vfebase + VFE_DEMOSAIC_ABF_CFG_0, + (uint32_t *)&cmdabf, sizeof(cmdabf)); +} + +void vfe_demosaic_bpc_update(struct vfe_cmd_demosaic_bpc_update *in) +{ + struct vfe_demosaic_cfg cmd; + struct vfe_demosaic_bpc_cfg cmdbpc; + uint32_t temp; + + memset(&cmd, 0, sizeof(cmd)); + + temp = readl(ctrl->vfebase + VFE_DEMOSAIC_CFG); + + cmd = *((struct vfe_demosaic_cfg *)(&temp)); + cmd.badPixelCorrEnable = in->bpcUpdate.enable; + cmd.fminThreshold = in->bpcUpdate.fminThreshold; + cmd.fmaxThreshold = in->bpcUpdate.fmaxThreshold; + + vfe_prog_hw(ctrl->vfebase + VFE_DEMOSAIC_CFG, + (uint32_t *)&cmd, sizeof(cmd)); + + cmdbpc.blueDiffThreshold = in->bpcUpdate.blueDiffThreshold; + cmdbpc.redDiffThreshold = in->bpcUpdate.redDiffThreshold; + cmdbpc.greenDiffThreshold = in->bpcUpdate.greenDiffThreshold; + + vfe_prog_hw(ctrl->vfebase + VFE_DEMOSAIC_BPC_CFG_0, + (uint32_t *)&cmdbpc, sizeof(cmdbpc)); +} + +void vfe_demosaic_config(struct vfe_cmd_demosaic_config *in) +{ + struct vfe_demosaic_cfg cmd; + struct vfe_demosaic_bpc_cfg cmd_bpc; + struct vfe_demosaic_abf_cfg cmd_abf; + + memset(&cmd, 0, sizeof(cmd)); + memset(&cmd_bpc, 0, sizeof(cmd_bpc)); + memset(&cmd_abf, 0, sizeof(cmd_abf)); + + ctrl->vfeModuleEnableLocal.demosaicEnable = in->enable; + + cmd.abfEnable = in->abfConfig.enable; + cmd.badPixelCorrEnable = in->bpcConfig.enable; + cmd.forceAbfOn = in->abfConfig.forceOn; + cmd.abfShift = in->abfConfig.shift; + cmd.fminThreshold = in->bpcConfig.fminThreshold; + cmd.fmaxThreshold = in->bpcConfig.fmaxThreshold; + cmd.slopeShift = in->slopeShift; + + vfe_prog_hw(ctrl->vfebase + VFE_DEMOSAIC_CFG, + (uint32_t *)&cmd, sizeof(cmd)); + + cmd_abf.lpThreshold = in->abfConfig.lpThreshold; + cmd_abf.ratio = in->abfConfig.ratio; + cmd_abf.minValue = in->abfConfig.min; + cmd_abf.maxValue = in->abfConfig.max; + + vfe_prog_hw(ctrl->vfebase + VFE_DEMOSAIC_ABF_CFG_0, + (uint32_t *)&cmd_abf, sizeof(cmd_abf)); + + cmd_bpc.blueDiffThreshold = in->bpcConfig.blueDiffThreshold; + cmd_bpc.redDiffThreshold = in->bpcConfig.redDiffThreshold; + cmd_bpc.greenDiffThreshold = in->bpcConfig.greenDiffThreshold; + + vfe_prog_hw(ctrl->vfebase + VFE_DEMOSAIC_BPC_CFG_0, + (uint32_t *)&cmd_bpc, sizeof(cmd_bpc)); +} + +void vfe_demux_channel_gain_update(struct vfe_cmd_demux_channel_gain_config *in) +{ + struct vfe_demux_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.ch0EvenGain = in->ch0EvenGain; + cmd.ch0OddGain = in->ch0OddGain; + cmd.ch1Gain = in->ch1Gain; + cmd.ch2Gain = in->ch2Gain; + + vfe_prog_hw(ctrl->vfebase + VFE_DEMUX_GAIN_0, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_demux_channel_gain_config(struct vfe_cmd_demux_channel_gain_config *in) +{ + struct vfe_demux_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.ch0EvenGain = in->ch0EvenGain; + cmd.ch0OddGain = in->ch0OddGain; + cmd.ch1Gain = in->ch1Gain; + cmd.ch2Gain = in->ch2Gain; + + vfe_prog_hw(ctrl->vfebase + VFE_DEMUX_GAIN_0, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_black_level_update(struct vfe_cmd_black_level_config *in) +{ + struct vfe_blacklevel_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + ctrl->vfeModuleEnableLocal.blackLevelCorrectionEnable = in->enable; + + cmd.evenEvenAdjustment = in->evenEvenAdjustment; + cmd.evenOddAdjustment = in->evenOddAdjustment; + cmd.oddEvenAdjustment = in->oddEvenAdjustment; + cmd.oddOddAdjustment = in->oddOddAdjustment; + + vfe_prog_hw(ctrl->vfebase + VFE_BLACK_EVEN_EVEN_VALUE, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_black_level_config(struct vfe_cmd_black_level_config *in) +{ + struct vfe_blacklevel_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.blackLevelCorrectionEnable = in->enable; + + cmd.evenEvenAdjustment = in->evenEvenAdjustment; + cmd.evenOddAdjustment = in->evenOddAdjustment; + cmd.oddEvenAdjustment = in->oddEvenAdjustment; + cmd.oddOddAdjustment = in->oddOddAdjustment; + + vfe_prog_hw(ctrl->vfebase + VFE_BLACK_EVEN_EVEN_VALUE, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_asf_update(struct vfe_cmd_asf_update *in) +{ + struct vfe_asf_update cmd; + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.asfEnable = in->enable; + + cmd.smoothEnable = in->smoothFilterEnabled; + cmd.sharpMode = in->sharpMode; + cmd.smoothCoeff0 = in->smoothCoefCenter; + cmd.smoothCoeff1 = in->smoothCoefSurr; + cmd.cropEnable = in->cropEnable; + cmd.sharpThresholdE1 = in->sharpThreshE1; + cmd.sharpDegreeK1 = in->sharpK1; + cmd.sharpDegreeK2 = in->sharpK2; + cmd.normalizeFactor = in->normalizeFactor; + cmd.sharpThresholdE2 = in->sharpThreshE2; + cmd.sharpThresholdE3 = in->sharpThreshE3; + cmd.sharpThresholdE4 = in->sharpThreshE4; + cmd.sharpThresholdE5 = in->sharpThreshE5; + cmd.F1Coeff0 = in->filter1Coefficients[0]; + cmd.F1Coeff1 = in->filter1Coefficients[1]; + cmd.F1Coeff2 = in->filter1Coefficients[2]; + cmd.F1Coeff3 = in->filter1Coefficients[3]; + cmd.F1Coeff4 = in->filter1Coefficients[4]; + cmd.F1Coeff5 = in->filter1Coefficients[5]; + cmd.F1Coeff6 = in->filter1Coefficients[6]; + cmd.F1Coeff7 = in->filter1Coefficients[7]; + cmd.F1Coeff8 = in->filter1Coefficients[8]; + cmd.F2Coeff0 = in->filter2Coefficients[0]; + cmd.F2Coeff1 = in->filter2Coefficients[1]; + cmd.F2Coeff2 = in->filter2Coefficients[2]; + cmd.F2Coeff3 = in->filter2Coefficients[3]; + cmd.F2Coeff4 = in->filter2Coefficients[4]; + cmd.F2Coeff5 = in->filter2Coefficients[5]; + cmd.F2Coeff6 = in->filter2Coefficients[6]; + cmd.F2Coeff7 = in->filter2Coefficients[7]; + cmd.F2Coeff8 = in->filter2Coefficients[8]; + + vfe_prog_hw(ctrl->vfebase + VFE_ASF_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_asf_config(struct vfe_cmd_asf_config *in) +{ + struct vfe_asf_update cmd; + struct vfe_asfcrop_cfg cmd2; + + memset(&cmd, 0, sizeof(cmd)); + memset(&cmd2, 0, sizeof(cmd2)); + + ctrl->vfeModuleEnableLocal.asfEnable = in->enable; + + cmd.smoothEnable = in->smoothFilterEnabled; + cmd.sharpMode = in->sharpMode; + cmd.smoothCoeff0 = in->smoothCoefCenter; + cmd.smoothCoeff1 = in->smoothCoefSurr; + cmd.cropEnable = in->cropEnable; + cmd.sharpThresholdE1 = in->sharpThreshE1; + cmd.sharpDegreeK1 = in->sharpK1; + cmd.sharpDegreeK2 = in->sharpK2; + cmd.normalizeFactor = in->normalizeFactor; + cmd.sharpThresholdE2 = in->sharpThreshE2; + cmd.sharpThresholdE3 = in->sharpThreshE3; + cmd.sharpThresholdE4 = in->sharpThreshE4; + cmd.sharpThresholdE5 = in->sharpThreshE5; + cmd.F1Coeff0 = in->filter1Coefficients[0]; + cmd.F1Coeff1 = in->filter1Coefficients[1]; + cmd.F1Coeff2 = in->filter1Coefficients[2]; + cmd.F1Coeff3 = in->filter1Coefficients[3]; + cmd.F1Coeff4 = in->filter1Coefficients[4]; + cmd.F1Coeff5 = in->filter1Coefficients[5]; + cmd.F1Coeff6 = in->filter1Coefficients[6]; + cmd.F1Coeff7 = in->filter1Coefficients[7]; + cmd.F1Coeff8 = in->filter1Coefficients[8]; + cmd.F2Coeff0 = in->filter2Coefficients[0]; + cmd.F2Coeff1 = in->filter2Coefficients[1]; + cmd.F2Coeff2 = in->filter2Coefficients[2]; + cmd.F2Coeff3 = in->filter2Coefficients[3]; + cmd.F2Coeff4 = in->filter2Coefficients[4]; + cmd.F2Coeff5 = in->filter2Coefficients[5]; + cmd.F2Coeff6 = in->filter2Coefficients[6]; + cmd.F2Coeff7 = in->filter2Coefficients[7]; + cmd.F2Coeff8 = in->filter2Coefficients[8]; + + vfe_prog_hw(ctrl->vfebase + VFE_ASF_CFG, + (uint32_t *)&cmd, sizeof(cmd)); + + cmd2.firstLine = in->cropFirstLine; + cmd2.lastLine = in->cropLastLine; + cmd2.firstPixel = in->cropFirstPixel; + cmd2.lastPixel = in->cropLastPixel; + + vfe_prog_hw(ctrl->vfebase + VFE_ASF_CROP_WIDTH_CFG, + (uint32_t *)&cmd2, sizeof(cmd2)); +} + +void vfe_white_balance_config(struct vfe_cmd_white_balance_config *in) +{ + struct vfe_wb_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.whiteBalanceEnable = in->enable; + + cmd.ch0Gain = in->ch0Gain; + cmd.ch1Gain = in->ch1Gain; + cmd.ch2Gain = in->ch2Gain; + + vfe_prog_hw(ctrl->vfebase + VFE_WB_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_chroma_sup_config(struct vfe_cmd_chroma_suppression_config *in) +{ + struct vfe_chroma_suppress_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.chromaSuppressionEnable = in->enable; + + cmd.m1 = in->m1; + cmd.m3 = in->m3; + cmd.n1 = in->n1; + cmd.n3 = in->n3; + cmd.mm1 = in->mm1; + cmd.nn1 = in->nn1; + + vfe_prog_hw(ctrl->vfebase + VFE_CHROMA_SUPPRESS_CFG_0, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_roll_off_config(struct vfe_cmd_roll_off_config *in) +{ + struct vfe_rolloff_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.lensRollOffEnable = in->enable; + + cmd.gridWidth = in->gridWidth; + cmd.gridHeight = in->gridHeight; + cmd.yDelta = in->yDelta; + cmd.gridX = in->gridXIndex; + cmd.gridY = in->gridYIndex; + cmd.pixelX = in->gridPixelXIndex; + cmd.pixelY = in->gridPixelYIndex; + cmd.yDeltaAccum = in->yDeltaAccum; + + vfe_prog_hw(ctrl->vfebase + VFE_ROLLOFF_CFG_0, + (uint32_t *)&cmd, sizeof(cmd)); + + vfe_write_lens_roll_off_table(in); +} + +void vfe_chroma_subsample_config(struct vfe_cmd_chroma_subsample_config *in) +{ + struct vfe_chromasubsample_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.chromaSubsampleEnable = in->enable; + + cmd.hCositedPhase = in->hCositedPhase; + cmd.vCositedPhase = in->vCositedPhase; + cmd.hCosited = in->hCosited; + cmd.vCosited = in->vCosited; + cmd.hsubSampleEnable = in->hsubSampleEnable; + cmd.vsubSampleEnable = in->vsubSampleEnable; + cmd.cropEnable = in->cropEnable; + cmd.cropWidthLastPixel = in->cropWidthLastPixel; + cmd.cropWidthFirstPixel = in->cropWidthFirstPixel; + cmd.cropHeightLastLine = in->cropHeightLastLine; + cmd.cropHeightFirstLine = in->cropHeightFirstLine; + + vfe_prog_hw(ctrl->vfebase + VFE_CHROMA_SUBSAMPLE_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_chroma_enhan_config(struct vfe_cmd_chroma_enhan_config *in) +{ + struct vfe_chroma_enhance_cfg cmd; + struct vfe_color_convert_cfg cmd2; + + memset(&cmd, 0, sizeof(cmd)); + memset(&cmd2, 0, sizeof(cmd2)); + + ctrl->vfeModuleEnableLocal.chromaEnhanEnable = in->enable; + + cmd.ap = in->ap; + cmd.am = in->am; + cmd.bp = in->bp; + cmd.bm = in->bm; + cmd.cp = in->cp; + cmd.cm = in->cm; + cmd.dp = in->dp; + cmd.dm = in->dm; + cmd.kcb = in->kcb; + cmd.kcr = in->kcr; + + cmd2.v0 = in->RGBtoYConversionV0; + cmd2.v1 = in->RGBtoYConversionV1; + cmd2.v2 = in->RGBtoYConversionV2; + cmd2.ConvertOffset = in->RGBtoYConversionOffset; + + vfe_prog_hw(ctrl->vfebase + VFE_CHROMA_ENHAN_A, + (uint32_t *)&cmd, sizeof(cmd)); + + vfe_prog_hw(ctrl->vfebase + VFE_COLOR_CONVERT_COEFF_0, + (uint32_t *)&cmd2, sizeof(cmd2)); +} + +void vfe_scaler2cbcr_config(struct vfe_cmd_scaler2_config *in) +{ + struct vfe_scaler2_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.scaler2CbcrEnable = in->enable; + + cmd.hEnable = in->hconfig.enable; + cmd.vEnable = in->vconfig.enable; + cmd.inWidth = in->hconfig.inputSize; + cmd.outWidth = in->hconfig.outputSize; + cmd.horizPhaseMult = in->hconfig.phaseMultiplicationFactor; + cmd.horizInterResolution = in->hconfig.interpolationResolution; + cmd.inHeight = in->vconfig.inputSize; + cmd.outHeight = in->vconfig.outputSize; + cmd.vertPhaseMult = in->vconfig.phaseMultiplicationFactor; + cmd.vertInterResolution = in->vconfig.interpolationResolution; + + vfe_prog_hw(ctrl->vfebase + VFE_SCALE_CBCR_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_scaler2y_config(struct vfe_cmd_scaler2_config *in) +{ + struct vfe_scaler2_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.scaler2YEnable = in->enable; + + cmd.hEnable = in->hconfig.enable; + cmd.vEnable = in->vconfig.enable; + cmd.inWidth = in->hconfig.inputSize; + cmd.outWidth = in->hconfig.outputSize; + cmd.horizPhaseMult = in->hconfig.phaseMultiplicationFactor; + cmd.horizInterResolution = in->hconfig.interpolationResolution; + cmd.inHeight = in->vconfig.inputSize; + cmd.outHeight = in->vconfig.outputSize; + cmd.vertPhaseMult = in->vconfig.phaseMultiplicationFactor; + cmd.vertInterResolution = in->vconfig.interpolationResolution; + + vfe_prog_hw(ctrl->vfebase + VFE_SCALE_Y_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_main_scaler_config(struct vfe_cmd_main_scaler_config *in) +{ + struct vfe_main_scaler_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.mainScalerEnable = in->enable; + + cmd.hEnable = in->hconfig.enable; + cmd.vEnable = in->vconfig.enable; + cmd.inWidth = in->hconfig.inputSize; + cmd.outWidth = in->hconfig.outputSize; + cmd.horizPhaseMult = in->hconfig.phaseMultiplicationFactor; + cmd.horizInterResolution = in->hconfig.interpolationResolution; + cmd.horizMNInit = in->MNInitH.MNCounterInit; + cmd.horizPhaseInit = in->MNInitH.phaseInit; + cmd.inHeight = in->vconfig.inputSize; + cmd.outHeight = in->vconfig.outputSize; + cmd.vertPhaseMult = in->vconfig.phaseMultiplicationFactor; + cmd.vertInterResolution = in->vconfig.interpolationResolution; + cmd.vertMNInit = in->MNInitV.MNCounterInit; + cmd.vertPhaseInit = in->MNInitV.phaseInit; + + vfe_prog_hw(ctrl->vfebase + VFE_SCALE_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_stats_wb_exp_stop(void) +{ + ctrl->vfeStatsCmdLocal.axwEnable = FALSE; + ctrl->vfeImaskLocal.awbPingpongIrq = FALSE; +} + +void vfe_stats_update_wb_exp(struct vfe_cmd_stats_wb_exp_update *in) +{ + struct vfe_statsawb_update cmd; + struct vfe_statsawbae_update cmd2; + + memset(&cmd, 0, sizeof(cmd)); + memset(&cmd2, 0, sizeof(cmd2)); + + cmd.m1 = in->awbMCFG[0]; + cmd.m2 = in->awbMCFG[1]; + cmd.m3 = in->awbMCFG[2]; + cmd.m4 = in->awbMCFG[3]; + cmd.c1 = in->awbCCFG[0]; + cmd.c2 = in->awbCCFG[1]; + cmd.c3 = in->awbCCFG[2]; + cmd.c4 = in->awbCCFG[3]; + vfe_prog_hw(ctrl->vfebase + VFE_STATS_AWB_MCFG, + (uint32_t *)&cmd, sizeof(cmd)); + + cmd2.aeRegionCfg = in->wbExpRegions; + cmd2.aeSubregionCfg = in->wbExpSubRegion; + cmd2.awbYMin = in->awbYMin; + cmd2.awbYMax = in->awbYMax; + vfe_prog_hw(ctrl->vfebase + VFE_STATS_AWBAE_CFG, + (uint32_t *)&cmd2, sizeof(cmd2)); +} + +void vfe_stats_update_af(struct vfe_cmd_stats_af_update *in) +{ + struct vfe_statsaf_update cmd; + memset(&cmd, 0, sizeof(cmd)); + + cmd.windowVOffset = in->windowVOffset; + cmd.windowHOffset = in->windowHOffset; + cmd.windowMode = in->windowMode; + cmd.windowHeight = in->windowHeight; + cmd.windowWidth = in->windowWidth; + + vfe_prog_hw(ctrl->vfebase + VFE_STATS_AF_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_stats_start_wb_exp(struct vfe_cmd_stats_wb_exp_start *in) +{ + struct vfe_statsawb_update cmd; + struct vfe_statsawbae_update cmd2; + struct vfe_statsaxw_hdr_cfg cmd3; + + ctrl->vfeStatsCmdLocal.axwEnable = in->enable; + ctrl->vfeImaskLocal.awbPingpongIrq = TRUE; + + memset(&cmd, 0, sizeof(cmd)); + memset(&cmd2, 0, sizeof(cmd2)); + memset(&cmd3, 0, sizeof(cmd3)); + + cmd.m1 = in->awbMCFG[0]; + cmd.m2 = in->awbMCFG[1]; + cmd.m3 = in->awbMCFG[2]; + cmd.m4 = in->awbMCFG[3]; + cmd.c1 = in->awbCCFG[0]; + cmd.c2 = in->awbCCFG[1]; + cmd.c3 = in->awbCCFG[2]; + cmd.c4 = in->awbCCFG[3]; + vfe_prog_hw(ctrl->vfebase + VFE_STATS_AWB_MCFG, + (uint32_t *)&cmd, sizeof(cmd)); + + cmd2.aeRegionCfg = in->wbExpRegions; + cmd2.aeSubregionCfg = in->wbExpSubRegion; + cmd2.awbYMin = in->awbYMin; + cmd2.awbYMax = in->awbYMax; + vfe_prog_hw(ctrl->vfebase + VFE_STATS_AWBAE_CFG, + (uint32_t *)&cmd2, sizeof(cmd2)); + + cmd3.axwHeader = in->axwHeader; + vfe_prog_hw(ctrl->vfebase + VFE_STATS_AXW_HEADER, + (uint32_t *)&cmd3, sizeof(cmd3)); +} + +void vfe_stats_start_af(struct vfe_cmd_stats_af_start *in) +{ + struct vfe_statsaf_update cmd; + struct vfe_statsaf_cfg cmd2; + + memset(&cmd, 0, sizeof(cmd)); + memset(&cmd2, 0, sizeof(cmd2)); + + ctrl->vfeStatsCmdLocal.autoFocusEnable = in->enable; + ctrl->vfeImaskLocal.afPingpongIrq = TRUE; + + cmd.windowVOffset = in->windowVOffset; + cmd.windowHOffset = in->windowHOffset; + cmd.windowMode = in->windowMode; + cmd.windowHeight = in->windowHeight; + cmd.windowWidth = in->windowWidth; + + vfe_prog_hw(ctrl->vfebase + VFE_STATS_AF_CFG, + (uint32_t *)&cmd, sizeof(cmd)); + + cmd2.a00 = in->highPassCoef[0]; + cmd2.a04 = in->highPassCoef[1]; + cmd2.a20 = in->highPassCoef[2]; + cmd2.a21 = in->highPassCoef[3]; + cmd2.a22 = in->highPassCoef[4]; + cmd2.a23 = in->highPassCoef[5]; + cmd2.a24 = in->highPassCoef[6]; + cmd2.fvMax = in->metricMax; + cmd2.fvMetric = in->metricSelection; + cmd2.afHeader = in->bufferHeader; + cmd2.entry00 = in->gridForMultiWindows[0]; + cmd2.entry01 = in->gridForMultiWindows[1]; + cmd2.entry02 = in->gridForMultiWindows[2]; + cmd2.entry03 = in->gridForMultiWindows[3]; + cmd2.entry10 = in->gridForMultiWindows[4]; + cmd2.entry11 = in->gridForMultiWindows[5]; + cmd2.entry12 = in->gridForMultiWindows[6]; + cmd2.entry13 = in->gridForMultiWindows[7]; + cmd2.entry20 = in->gridForMultiWindows[8]; + cmd2.entry21 = in->gridForMultiWindows[9]; + cmd2.entry22 = in->gridForMultiWindows[10]; + cmd2.entry23 = in->gridForMultiWindows[11]; + cmd2.entry30 = in->gridForMultiWindows[12]; + cmd2.entry31 = in->gridForMultiWindows[13]; + cmd2.entry32 = in->gridForMultiWindows[14]; + cmd2.entry33 = in->gridForMultiWindows[15]; + + vfe_prog_hw(ctrl->vfebase + VFE_STATS_AF_GRID_0, + (uint32_t *)&cmd2, sizeof(cmd2)); +} + +void vfe_stats_setting(struct vfe_cmd_stats_setting *in) +{ + struct vfe_statsframe cmd1; + struct vfe_busstats_wrprio cmd2; + + memset(&cmd1, 0, sizeof(cmd1)); + memset(&cmd2, 0, sizeof(cmd2)); + + ctrl->afStatsControl.addressBuffer[0] = in->afBuffer[0]; + ctrl->afStatsControl.addressBuffer[1] = in->afBuffer[1]; + ctrl->afStatsControl.nextFrameAddrBuf = in->afBuffer[2]; + + ctrl->awbStatsControl.addressBuffer[0] = in->awbBuffer[0]; + ctrl->awbStatsControl.addressBuffer[1] = in->awbBuffer[1]; + ctrl->awbStatsControl.nextFrameAddrBuf = in->awbBuffer[2]; + + cmd1.lastPixel = in->frameHDimension; + cmd1.lastLine = in->frameVDimension; + vfe_prog_hw(ctrl->vfebase + VFE_STATS_FRAME_SIZE, + (uint32_t *)&cmd1, sizeof(cmd1)); + + cmd2.afBusPriority = in->afBusPriority; + cmd2.awbBusPriority = in->awbBusPriority; + cmd2.histBusPriority = in->histBusPriority; + cmd2.afBusPriorityEn = in->afBusPrioritySelection; + cmd2.awbBusPriorityEn = in->awbBusPrioritySelection; + cmd2.histBusPriorityEn = in->histBusPrioritySelection; + + vfe_prog_hw(ctrl->vfebase + VFE_BUS_STATS_WR_PRIORITY, + (uint32_t *)&cmd2, sizeof(cmd2)); + + /* Program the bus ping pong address for statistics modules. */ + writel(in->afBuffer[0], ctrl->vfebase + VFE_BUS_STATS_AF_WR_PING_ADDR); + writel(in->afBuffer[1], ctrl->vfebase + VFE_BUS_STATS_AF_WR_PONG_ADDR); + writel(in->awbBuffer[0], + ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PING_ADDR); + writel(in->awbBuffer[1], + ctrl->vfebase + VFE_BUS_STATS_AWB_WR_PONG_ADDR); + writel(in->histBuffer[0], + ctrl->vfebase + VFE_BUS_STATS_HIST_WR_PING_ADDR); + writel(in->histBuffer[1], + ctrl->vfebase + VFE_BUS_STATS_HIST_WR_PONG_ADDR); +} + +void vfe_axi_input_config(struct vfe_cmd_axi_input_config *in) +{ + struct VFE_AxiInputCmdType cmd; + uint32_t xSizeWord, axiRdUnpackPattern; + uint8_t axiInputPpw; + uint32_t busPingpongRdIrqEnable; + + ctrl->vfeImaskLocal.rdPingpongIrq = TRUE; + + switch (in->pixelSize) { + case VFE_RAW_PIXEL_DATA_SIZE_10BIT: + ctrl->axiInputDataSize = VFE_RAW_PIXEL_DATA_SIZE_10BIT; + break; + + case VFE_RAW_PIXEL_DATA_SIZE_12BIT: + ctrl->axiInputDataSize = VFE_RAW_PIXEL_DATA_SIZE_12BIT; + break; + + case VFE_RAW_PIXEL_DATA_SIZE_8BIT: + default: + ctrl->axiInputDataSize = VFE_RAW_PIXEL_DATA_SIZE_8BIT; + break; + } + + memset(&cmd, 0, sizeof(cmd)); + + switch (in->pixelSize) { + case VFE_RAW_PIXEL_DATA_SIZE_10BIT: + axiInputPpw = 6; + axiRdUnpackPattern = 0xD43210; + break; + + case VFE_RAW_PIXEL_DATA_SIZE_12BIT: + axiInputPpw = 5; + axiRdUnpackPattern = 0xC3210; + break; + + case VFE_RAW_PIXEL_DATA_SIZE_8BIT: + default: + axiInputPpw = 8; + axiRdUnpackPattern = 0xF6543210; + break; + } + + xSizeWord = + ((((in->xOffset % axiInputPpw) + in->xSize) + + (axiInputPpw-1)) / axiInputPpw) - 1; + + cmd.stripeStartAddr0 = in->fragAddr[0]; + cmd.stripeStartAddr1 = in->fragAddr[1]; + cmd.stripeStartAddr2 = in->fragAddr[2]; + cmd.stripeStartAddr3 = in->fragAddr[3]; + cmd.ySize = in->ySize; + cmd.yOffsetDelta = 0; + cmd.xSizeWord = xSizeWord; + cmd.burstLength = 1; + cmd.NumOfRows = in->numOfRows; + cmd.RowIncrement = (in->rowIncrement + (axiInputPpw - 1)) / axiInputPpw; + cmd.mainUnpackHeight = in->ySize; + cmd.mainUnpackWidth = in->xSize - 1; + cmd.mainUnpackHbiSel = (uint32_t)in->unpackHbi; + cmd.mainUnpackPhase = in->unpackPhase; + cmd.unpackPattern = axiRdUnpackPattern; + cmd.padLeft = in->padRepeatCountLeft; + cmd.padRight = in->padRepeatCountRight; + cmd.padTop = in->padRepeatCountTop; + cmd.padBottom = in->padRepeatCountBottom; + cmd.leftUnpackPattern0 = in->padLeftComponentSelectCycle0; + cmd.leftUnpackPattern1 = in->padLeftComponentSelectCycle1; + cmd.leftUnpackPattern2 = in->padLeftComponentSelectCycle2; + cmd.leftUnpackPattern3 = in->padLeftComponentSelectCycle3; + cmd.leftUnpackStop0 = in->padLeftStopCycle0; + cmd.leftUnpackStop1 = in->padLeftStopCycle1; + cmd.leftUnpackStop2 = in->padLeftStopCycle2; + cmd.leftUnpackStop3 = in->padLeftStopCycle3; + cmd.rightUnpackPattern0 = in->padRightComponentSelectCycle0; + cmd.rightUnpackPattern1 = in->padRightComponentSelectCycle1; + cmd.rightUnpackPattern2 = in->padRightComponentSelectCycle2; + cmd.rightUnpackPattern3 = in->padRightComponentSelectCycle3; + cmd.rightUnpackStop0 = in->padRightStopCycle0; + cmd.rightUnpackStop1 = in->padRightStopCycle1; + cmd.rightUnpackStop2 = in->padRightStopCycle2; + cmd.rightUnpackStop3 = in->padRightStopCycle3; + cmd.topUnapckPattern = in->padTopLineCount; + cmd.bottomUnapckPattern = in->padBottomLineCount; + + /* program vfe_bus_cfg */ + vfe_prog_hw(ctrl->vfebase + VFE_BUS_STRIPE_RD_ADDR_0, + (uint32_t *)&cmd, sizeof(cmd)); + + /* hacking code, put it to default value */ + busPingpongRdIrqEnable = 0xf; + + writel(busPingpongRdIrqEnable, ctrl->vfebase + VFE_BUS_PINGPONG_IRQ_EN); +} + +void vfe_axi_output_config(struct vfe_cmd_axi_output_config *in) +{ + /* local variable */ + uint32_t *pcircle; + uint32_t *pdest; + uint32_t *psrc; + uint8_t i; + uint8_t fcnt; + uint16_t axioutpw = 8; + + /* parameters check, condition and usage mode check */ + ctrl->encPath.fragCount = in->output2.fragmentCount; + if (ctrl->encPath.fragCount > 1) + ctrl->encPath.multiFrag = TRUE; + + ctrl->viewPath.fragCount = in->output1.fragmentCount; + if (ctrl->viewPath.fragCount > 1) + ctrl->viewPath.multiFrag = TRUE; + + /* VFE_BUS_CFG. raw data size */ + ctrl->vfeBusConfigLocal.rawPixelDataSize = in->outputDataSize; + + switch (in->outputDataSize) { + case VFE_RAW_PIXEL_DATA_SIZE_8BIT: + axioutpw = 8; + break; + + case VFE_RAW_PIXEL_DATA_SIZE_10BIT: + axioutpw = 6; + break; + + case VFE_RAW_PIXEL_DATA_SIZE_12BIT: + axioutpw = 5; + break; + } + + ctrl->axiOutputMode = in->outputMode; + + CDBG("axiOutputMode = %d\n", ctrl->axiOutputMode); + + switch (ctrl->axiOutputMode) { + case VFE_AXI_OUTPUT_MODE_Output1: { + ctrl->vfeCamifConfigLocal.camif2BusEnable = FALSE; + ctrl->vfeCamifConfigLocal.camif2OutputEnable = TRUE; + ctrl->vfeBusConfigLocal.rawWritePathSelect = + VFE_RAW_OUTPUT_DISABLED; + + ctrl->encPath.pathEnabled = FALSE; + ctrl->vfeImaskLocal.encIrq = FALSE; + ctrl->vfeIrqCompositeMaskLocal.encIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vfeBusConfigLocal.encYWrPathEn = FALSE; + ctrl->vfeBusConfigLocal.encCbcrWrPathEn = FALSE; + ctrl->viewPath.pathEnabled = TRUE; + ctrl->vfeImaskLocal.viewIrq = TRUE; + ctrl->vfeIrqCompositeMaskLocal.viewIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vfeBusConfigLocal.viewYWrPathEn = TRUE; + ctrl->vfeBusConfigLocal.viewCbcrWrPathEn = TRUE; + + if (ctrl->vfeBusConfigLocal.encYWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.encCbcrWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encCbcrPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewYWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewCbcrWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewCbcrPingpongIrq = TRUE; + } /* VFE_AXI_OUTPUT_MODE_Output1 */ + break; + + case VFE_AXI_OUTPUT_MODE_Output2: { + ctrl->vfeCamifConfigLocal.camif2BusEnable = FALSE; + ctrl->vfeCamifConfigLocal.camif2OutputEnable = TRUE; + ctrl->vfeBusConfigLocal.rawWritePathSelect = + VFE_RAW_OUTPUT_DISABLED; + + ctrl->encPath.pathEnabled = TRUE; + ctrl->vfeImaskLocal.encIrq = TRUE; + ctrl->vfeIrqCompositeMaskLocal.encIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vfeBusConfigLocal.encYWrPathEn = TRUE; + ctrl->vfeBusConfigLocal.encCbcrWrPathEn = TRUE; + + ctrl->viewPath.pathEnabled = FALSE; + ctrl->vfeImaskLocal.viewIrq = FALSE; + ctrl->vfeIrqCompositeMaskLocal.viewIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vfeBusConfigLocal.viewYWrPathEn = FALSE; + ctrl->vfeBusConfigLocal.viewCbcrWrPathEn = FALSE; + + if (ctrl->vfeBusConfigLocal.encYWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.encCbcrWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encCbcrPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewYWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewCbcrWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewCbcrPingpongIrq = TRUE; + } /* VFE_AXI_OUTPUT_MODE_Output2 */ + break; + + case VFE_AXI_OUTPUT_MODE_Output1AndOutput2: { + ctrl->vfeCamifConfigLocal.camif2BusEnable = FALSE; + ctrl->vfeCamifConfigLocal.camif2OutputEnable = TRUE; + ctrl->vfeBusConfigLocal.rawWritePathSelect = + VFE_RAW_OUTPUT_DISABLED; + + ctrl->encPath.pathEnabled = TRUE; + ctrl->vfeImaskLocal.encIrq = TRUE; + ctrl->vfeIrqCompositeMaskLocal.encIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vfeBusConfigLocal.encYWrPathEn = TRUE; + ctrl->vfeBusConfigLocal.encCbcrWrPathEn = TRUE; + ctrl->viewPath.pathEnabled = TRUE; + ctrl->vfeImaskLocal.viewIrq = TRUE; + ctrl->vfeIrqCompositeMaskLocal.viewIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vfeBusConfigLocal.viewYWrPathEn = TRUE; + ctrl->vfeBusConfigLocal.viewCbcrWrPathEn = TRUE; + + if (ctrl->vfeBusConfigLocal.encYWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.encCbcrWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encCbcrPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewYWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewCbcrWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewCbcrPingpongIrq = TRUE; + } /* VFE_AXI_OUTPUT_MODE_Output1AndOutput2 */ + break; + + case VFE_AXI_OUTPUT_MODE_CAMIFToAXIViaOutput2: { + /* For raw snapshot, we need both ping and pong buffer + * initialized to the same address. Otherwise, if we + * leave the pong buffer to NULL, there will be axi_error. + * Note that ideally we should deal with this at upper layer, + * which is in msm_vfe8x.c */ + if (!in->output2.outputCbcr.outFragments[1][0]) { + in->output2.outputCbcr.outFragments[1][0] = + in->output2.outputCbcr.outFragments[0][0]; + } + + ctrl->vfeCamifConfigLocal.camif2BusEnable = TRUE; + ctrl->vfeCamifConfigLocal.camif2OutputEnable = FALSE; + ctrl->vfeBusConfigLocal.rawWritePathSelect = + VFE_RAW_OUTPUT_ENC_CBCR_PATH; + + ctrl->encPath.pathEnabled = TRUE; + ctrl->vfeImaskLocal.encIrq = TRUE; + ctrl->vfeIrqCompositeMaskLocal.encIrqComMask = + VFE_COMP_IRQ_CBCR_ONLY; + + ctrl->vfeBusConfigLocal.encYWrPathEn = FALSE; + ctrl->vfeBusConfigLocal.encCbcrWrPathEn = TRUE; + + ctrl->viewPath.pathEnabled = FALSE; + ctrl->vfeImaskLocal.viewIrq = FALSE; + ctrl->vfeIrqCompositeMaskLocal.viewIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vfeBusConfigLocal.viewYWrPathEn = FALSE; + ctrl->vfeBusConfigLocal.viewCbcrWrPathEn = FALSE; + + if (ctrl->vfeBusConfigLocal.encYWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.encCbcrWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encCbcrPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewYWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewCbcrWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewCbcrPingpongIrq = TRUE; + } /* VFE_AXI_OUTPUT_MODE_CAMIFToAXIViaOutput2 */ + break; + + case VFE_AXI_OUTPUT_MODE_Output2AndCAMIFToAXIViaOutput1: { + ctrl->vfeCamifConfigLocal.camif2BusEnable = TRUE; + ctrl->vfeCamifConfigLocal.camif2OutputEnable = TRUE; + ctrl->vfeBusConfigLocal.rawWritePathSelect = + VFE_RAW_OUTPUT_VIEW_CBCR_PATH; + + ctrl->encPath.pathEnabled = TRUE; + ctrl->vfeImaskLocal.encIrq = TRUE; + ctrl->vfeIrqCompositeMaskLocal.encIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vfeBusConfigLocal.encYWrPathEn = TRUE; + ctrl->vfeBusConfigLocal.encCbcrWrPathEn = TRUE; + + ctrl->viewPath.pathEnabled = TRUE; + ctrl->vfeImaskLocal.viewIrq = TRUE; + ctrl->vfeIrqCompositeMaskLocal.viewIrqComMask = + VFE_COMP_IRQ_CBCR_ONLY; + + ctrl->vfeBusConfigLocal.viewYWrPathEn = FALSE; + ctrl->vfeBusConfigLocal.viewCbcrWrPathEn = TRUE; + + if (ctrl->vfeBusConfigLocal.encYWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.encCbcrWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encCbcrPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewYWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewCbcrWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewCbcrPingpongIrq = TRUE; + } /* VFE_AXI_OUTPUT_MODE_Output2AndCAMIFToAXIViaOutput1 */ + break; + + case VFE_AXI_OUTPUT_MODE_Output1AndCAMIFToAXIViaOutput2: { + ctrl->vfeCamifConfigLocal.camif2BusEnable = TRUE; + ctrl->vfeCamifConfigLocal.camif2OutputEnable = TRUE; + ctrl->vfeBusConfigLocal.rawWritePathSelect = + VFE_RAW_OUTPUT_ENC_CBCR_PATH; + + ctrl->encPath.pathEnabled = TRUE; + ctrl->vfeImaskLocal.encIrq = TRUE; + ctrl->vfeIrqCompositeMaskLocal.encIrqComMask = + VFE_COMP_IRQ_CBCR_ONLY; + + ctrl->vfeBusConfigLocal.encYWrPathEn = FALSE; + ctrl->vfeBusConfigLocal.encCbcrWrPathEn = TRUE; + + ctrl->viewPath.pathEnabled = TRUE; + ctrl->vfeImaskLocal.viewIrq = TRUE; + + ctrl->vfeIrqCompositeMaskLocal.viewIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vfeBusConfigLocal.viewYWrPathEn = TRUE; + ctrl->vfeBusConfigLocal.viewCbcrWrPathEn = TRUE; + + if (ctrl->vfeBusConfigLocal.encYWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.encCbcrWrPathEn && + ctrl->encPath.multiFrag) + ctrl->vfeImaskLocal.encCbcrPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewYWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewYPingpongIrq = TRUE; + + if (ctrl->vfeBusConfigLocal.viewCbcrWrPathEn && + ctrl->viewPath.multiFrag) + ctrl->vfeImaskLocal.viewCbcrPingpongIrq = TRUE; + } /* VFE_AXI_OUTPUT_MODE_Output1AndCAMIFToAXIViaOutput2 */ + break; + + case VFE_AXI_LAST_OUTPUT_MODE_ENUM: + break; + } /* switch */ + + /* Save the addresses for each path. */ + /* output2 path */ + fcnt = ctrl->encPath.fragCount; + + pcircle = ctrl->encPath.yPath.addressBuffer; + pdest = ctrl->encPath.nextFrameAddrBuf; + + psrc = &(in->output2.outputY.outFragments[0][0]); + for (i = 0; i < fcnt; i++) + *pcircle++ = *psrc++; + + psrc = &(in->output2.outputY.outFragments[1][0]); + for (i = 0; i < fcnt; i++) + *pcircle++ = *psrc++; + + psrc = &(in->output2.outputY.outFragments[2][0]); + for (i = 0; i < fcnt; i++) + *pdest++ = *psrc++; + + pcircle = ctrl->encPath.cbcrPath.addressBuffer; + + psrc = &(in->output2.outputCbcr.outFragments[0][0]); + for (i = 0; i < fcnt; i++) + *pcircle++ = *psrc++; + + psrc = &(in->output2.outputCbcr.outFragments[1][0]); + for (i = 0; i < fcnt; i++) + *pcircle++ = *psrc++; + + psrc = &(in->output2.outputCbcr.outFragments[2][0]); + for (i = 0; i < fcnt; i++) + *pdest++ = *psrc++; + + vfe_set_bus_pipo_addr(&ctrl->viewPath, &ctrl->encPath); + + ctrl->encPath.ackPending = FALSE; + ctrl->encPath.currentFrame = ping; + ctrl->encPath.whichOutputPath = 1; + ctrl->encPath.yPath.fragIndex = 2; + ctrl->encPath.cbcrPath.fragIndex = 2; + ctrl->encPath.yPath.hwCurrentFlag = ping; + ctrl->encPath.cbcrPath.hwCurrentFlag = ping; + + /* output1 path */ + pcircle = ctrl->viewPath.yPath.addressBuffer; + pdest = ctrl->viewPath.nextFrameAddrBuf; + fcnt = ctrl->viewPath.fragCount; + + psrc = &(in->output1.outputY.outFragments[0][0]); + for (i = 0; i < fcnt; i++) + *pcircle++ = *psrc++; + + psrc = &(in->output1.outputY.outFragments[1][0]); + for (i = 0; i < fcnt; i++) + *pcircle++ = *psrc++; + + psrc = &(in->output1.outputY.outFragments[2][0]); + for (i = 0; i < fcnt; i++) + *pdest++ = *psrc++; + + pcircle = ctrl->viewPath.cbcrPath.addressBuffer; + + psrc = &(in->output1.outputCbcr.outFragments[0][0]); + for (i = 0; i < fcnt; i++) + *pcircle++ = *psrc++; + + psrc = &(in->output1.outputCbcr.outFragments[1][0]); + for (i = 0; i < fcnt; i++) + *pcircle++ = *psrc++; + + psrc = &(in->output1.outputCbcr.outFragments[2][0]); + for (i = 0; i < fcnt; i++) + *pdest++ = *psrc++; + + ctrl->viewPath.ackPending = FALSE; + ctrl->viewPath.currentFrame = ping; + ctrl->viewPath.whichOutputPath = 0; + ctrl->viewPath.yPath.fragIndex = 2; + ctrl->viewPath.cbcrPath.fragIndex = 2; + ctrl->viewPath.yPath.hwCurrentFlag = ping; + ctrl->viewPath.cbcrPath.hwCurrentFlag = ping; + + /* call to program the registers. */ + vfe_axi_output(in, &ctrl->viewPath, &ctrl->encPath, axioutpw); +} + +void vfe_camif_config(struct vfe_cmd_camif_config *in) +{ + struct vfe_camifcfg cmd; + memset(&cmd, 0, sizeof(cmd)); + + CDBG("camif.frame pixelsPerLine = %d\n", in->frame.pixelsPerLine); + CDBG("camif.frame linesPerFrame = %d\n", in->frame.linesPerFrame); + CDBG("camif.window firstpixel = %d\n", in->window.firstpixel); + CDBG("camif.window lastpixel = %d\n", in->window.lastpixel); + CDBG("camif.window firstline = %d\n", in->window.firstline); + CDBG("camif.window lastline = %d\n", in->window.lastline); + + /* determine if epoch interrupt needs to be enabled. */ + if ((in->epoch1.enable == TRUE) && + (in->epoch1.lineindex <= in->frame.linesPerFrame)) + ctrl->vfeImaskLocal.camifEpoch1Irq = 1; + + if ((in->epoch2.enable == TRUE) && + (in->epoch2.lineindex <= in->frame.linesPerFrame)) { + ctrl->vfeImaskLocal.camifEpoch2Irq = 1; + } + + /* save the content to program CAMIF_CONFIG seperately. */ + ctrl->vfeCamifConfigLocal.camifCfgFromCmd = in->camifConfig; + + /* EFS_Config */ + cmd.efsEndOfLine = in->EFS.efsendofline; + cmd.efsStartOfLine = in->EFS.efsstartofline; + cmd.efsEndOfFrame = in->EFS.efsendofframe; + cmd.efsStartOfFrame = in->EFS.efsstartofframe; + + /* Frame Config */ + cmd.frameConfigPixelsPerLine = in->frame.pixelsPerLine; + cmd.frameConfigLinesPerFrame = in->frame.linesPerFrame; + + /* Window Width Config */ + cmd.windowWidthCfgLastPixel = in->window.lastpixel; + cmd.windowWidthCfgFirstPixel = in->window.firstpixel; + + /* Window Height Config */ + cmd.windowHeightCfglastLine = in->window.lastline; + cmd.windowHeightCfgfirstLine = in->window.firstline; + + /* Subsample 1 Config */ + cmd.subsample1CfgPixelSkip = in->subsample.pixelskipmask; + cmd.subsample1CfgLineSkip = in->subsample.lineskipmask; + + /* Subsample 2 Config */ + cmd.subsample2CfgFrameSkip = in->subsample.frameskip; + cmd.subsample2CfgFrameSkipMode = in->subsample.frameskipmode; + cmd.subsample2CfgPixelSkipWrap = in->subsample.pixelskipwrap; + + /* Epoch Interrupt */ + cmd.epoch1Line = in->epoch1.lineindex; + cmd.epoch2Line = in->epoch2.lineindex; + + vfe_prog_hw(ctrl->vfebase + CAMIF_EFS_CONFIG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_fov_crop_config(struct vfe_cmd_fov_crop_config *in) +{ + struct vfe_fov_crop_cfg cmd; + memset(&cmd, 0, sizeof(cmd)); + + ctrl->vfeModuleEnableLocal.cropEnable = in->enable; + + /* FOV Corp, Part 1 */ + cmd.lastPixel = in->lastPixel; + cmd.firstPixel = in->firstPixel; + + /* FOV Corp, Part 2 */ + cmd.lastLine = in->lastLine; + cmd.firstLine = in->firstLine; + + vfe_prog_hw(ctrl->vfebase + VFE_CROP_WIDTH_CFG, + (uint32_t *)&cmd, sizeof(cmd)); +} + +void vfe_get_hw_version(struct vfe_cmd_hw_version *out) +{ + uint32_t vfeHwVersionPacked; + struct vfe_hw_ver ver; + + vfeHwVersionPacked = readl(ctrl->vfebase + VFE_HW_VERSION); + + ver = *((struct vfe_hw_ver *)&vfeHwVersionPacked); + + out->coreVersion = ver.coreVersion; + out->minorVersion = ver.minorVersion; + out->majorVersion = ver.majorVersion; +} + +static void vfe_reset_internal_variables(void) +{ + /* local variables to program the hardware. */ + ctrl->vfeImaskPacked = 0; + ctrl->vfeImaskCompositePacked = 0; + + /* FALSE = disable, 1 = enable. */ + memset(&ctrl->vfeModuleEnableLocal, 0, + sizeof(ctrl->vfeModuleEnableLocal)); + + /* 0 = disable, 1 = enable */ + memset(&ctrl->vfeCamifConfigLocal, 0, + sizeof(ctrl->vfeCamifConfigLocal)); + /* 0 = disable, 1 = enable */ + memset(&ctrl->vfeImaskLocal, 0, sizeof(ctrl->vfeImaskLocal)); + memset(&ctrl->vfeStatsCmdLocal, 0, sizeof(ctrl->vfeStatsCmdLocal)); + memset(&ctrl->vfeBusConfigLocal, 0, sizeof(ctrl->vfeBusConfigLocal)); + memset(&ctrl->vfeBusPmConfigLocal, 0, + sizeof(ctrl->vfeBusPmConfigLocal)); + memset(&ctrl->vfeBusCmdLocal, 0, sizeof(ctrl->vfeBusCmdLocal)); + memset(&ctrl->vfeInterruptNameLocal, 0, + sizeof(ctrl->vfeInterruptNameLocal)); + memset(&ctrl->vfeDroppedFrameCounts, 0, + sizeof(ctrl->vfeDroppedFrameCounts)); + memset(&ctrl->vfeIrqThreadMsgLocal, 0, + sizeof(ctrl->vfeIrqThreadMsgLocal)); + + /* state control variables */ + ctrl->vfeStartAckPendingFlag = FALSE; + ctrl->vfeStopAckPending = FALSE; + ctrl->vfeIrqCompositeMaskLocal.ceDoneSel = 0; + ctrl->vfeIrqCompositeMaskLocal.encIrqComMask = VFE_COMP_IRQ_BOTH_Y_CBCR; + ctrl->vfeIrqCompositeMaskLocal.viewIrqComMask = + VFE_COMP_IRQ_BOTH_Y_CBCR; + + ctrl->vstate = VFE_STATE_IDLE; + + ctrl->axiOutputMode = VFE_AXI_LAST_OUTPUT_MODE_ENUM; + /* 0 for continuous mode, 1 for snapshot mode */ + ctrl->vfeOperationMode = VFE_START_OPERATION_MODE_CONTINUOUS; + ctrl->vfeSnapShotCount = 0; + ctrl->vfeStatsPingPongReloadFlag = FALSE; + /* this is unsigned 32 bit integer. */ + ctrl->vfeFrameId = 0; + ctrl->vfeFrameSkip.output1Pattern = 0xffffffff; + ctrl->vfeFrameSkip.output1Period = 31; + ctrl->vfeFrameSkip.output2Pattern = 0xffffffff; + ctrl->vfeFrameSkip.output2Period = 31; + ctrl->vfeFrameSkipPattern = 0xffffffff; + ctrl->vfeFrameSkipCount = 0; + ctrl->vfeFrameSkipPeriod = 31; + + memset((void *)&ctrl->encPath, 0, sizeof(ctrl->encPath)); + memset((void *)&ctrl->viewPath, 0, sizeof(ctrl->viewPath)); + + ctrl->encPath.whichOutputPath = 1; + ctrl->encPath.cbcrStatusBit = 5; + ctrl->viewPath.whichOutputPath = 0; + ctrl->viewPath.cbcrStatusBit = 7; + + ctrl->vfeTestGenStartFlag = FALSE; + + /* default to bank 0. */ + ctrl->vfeLaBankSel = 0; + + /* default to bank 0 for all channels. */ + memset(&ctrl->vfeGammaLutSel, 0, sizeof(ctrl->vfeGammaLutSel)); + + /* Stats control variables. */ + memset(&ctrl->afStatsControl, 0, sizeof(ctrl->afStatsControl)); + memset(&ctrl->awbStatsControl, 0, sizeof(ctrl->awbStatsControl)); + vfe_set_stats_pingpong_address(&ctrl->afStatsControl, + &ctrl->awbStatsControl); +} + +void vfe_reset(void) +{ + spin_lock_init(&msm_vfe_ctrl_lock); + vfe_reset_internal_variables(); + + atomic_set(&ctrl->vfe_serv_interrupt, 1); + ctrl->vfeImaskLocal.resetAckIrq = TRUE; + ctrl->vfeImaskPacked = vfe_irq_pack(ctrl->vfeImaskLocal); + + /* disable all interrupts. */ + writel(VFE_DISABLE_ALL_IRQS, ctrl->vfebase + VFE_IRQ_COMPOSITE_MASK); + + /* clear all pending interrupts*/ + writel(VFE_CLEAR_ALL_IRQS, ctrl->vfebase + VFE_IRQ_CLEAR); + + /* enable reset_ack interrupt. */ + writel(ctrl->vfeImaskPacked, ctrl->vfebase + VFE_IRQ_MASK); + + writel(VFE_RESET_UPON_RESET_CMD, ctrl->vfebase + VFE_GLOBAL_RESET_CMD); +} diff --git a/drivers/media/video/msm/msm_vfe8x_proc.h b/drivers/media/video/msm/msm_vfe8x_proc.h new file mode 100644 index 00000000000..da00e8fb17f --- /dev/null +++ b/drivers/media/video/msm/msm_vfe8x_proc.h @@ -0,0 +1,1563 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_VFE8X_REG_H__ +#define __MSM_VFE8X_REG_H__ + +#include +#include +#include "msm_vfe8x.h" + + +#define MSM_AXI_QOS_PREVIEW 128000 +#define MSM_AXI_QOS_SNAPSHOT 128000 +#define MSM_AXI_QOS_RECORDING 128000 + + +/* at start of camif, bit 1:0 = 0x01:enable + * image data capture at frame boundary. */ +#define CAMIF_COMMAND_START 0x00000005 + +/* bit 2= 0x1:clear the CAMIF_STATUS register + * value. */ +#define CAMIF_COMMAND_CLEAR 0x00000004 + +/* at stop of vfe pipeline, for now it is assumed + * that camif will stop at any time. Bit 1:0 = 0x10: + * disable image data capture immediately. */ +#define CAMIF_COMMAND_STOP_IMMEDIATELY 0x00000002 + +/* at stop of vfe pipeline, for now it is assumed + * that camif will stop at any time. Bit 1:0 = 0x00: + * disable image data capture at frame boundary */ +#define CAMIF_COMMAND_STOP_AT_FRAME_BOUNDARY 0x00000000 + +/* to halt axi bridge */ +#define AXI_HALT 0x00000001 + +/* clear the halt bit. */ +#define AXI_HALT_CLEAR 0x00000000 + +/* reset the pipeline when stop command is issued. + * (without reset the register.) bit 26-31 = 0, + * domain reset, bit 0-9 = 1 for module reset, except + * register module. */ +#define VFE_RESET_UPON_STOP_CMD 0x000003ef + +/* reset the pipeline when reset command. + * bit 26-31 = 0, domain reset, bit 0-9 = 1 for module reset. */ +#define VFE_RESET_UPON_RESET_CMD 0x000003ff + +/* bit 5 is for axi status idle or busy. + * 1 = halted, 0 = busy */ +#define AXI_STATUS_BUSY_MASK 0x00000020 + +/* bit 0 & bit 1 = 1, both y and cbcr irqs need to be present + * for frame done interrupt */ +#define VFE_COMP_IRQ_BOTH_Y_CBCR 3 + +/* bit 1 = 1, only cbcr irq triggers frame done interrupt */ +#define VFE_COMP_IRQ_CBCR_ONLY 2 + +/* bit 0 = 1, only y irq triggers frame done interrupt */ +#define VFE_COMP_IRQ_Y_ONLY 1 + +/* bit 0 = 1, PM go; bit1 = 1, PM stop */ +#define VFE_PERFORMANCE_MONITOR_GO 0x00000001 +#define VFE_PERFORMANCE_MONITOR_STOP 0x00000002 + +/* bit 0 = 1, test gen go; bit1 = 1, test gen stop */ +#define VFE_TEST_GEN_GO 0x00000001 +#define VFE_TEST_GEN_STOP 0x00000002 + +/* the chroma is assumed to be interpolated between + * the luma samples. JPEG 4:2:2 */ +#define VFE_CHROMA_UPSAMPLE_INTERPOLATED 0 + +/* constants for irq registers */ +#define VFE_DISABLE_ALL_IRQS 0 +/* bit =1 is to clear the corresponding bit in VFE_IRQ_STATUS. */ +#define VFE_CLEAR_ALL_IRQS 0xffffffff +/* imask for while waiting for stop ack, driver has already + * requested stop, waiting for reset irq, + * bit 29,28,27,26 for async timer, bit 9 for reset */ +#define VFE_IMASK_WHILE_STOPPING 0x3c000200 + +/* when normal case, don't want to block error status. + * bit 0,6,20,21,22,30,31 */ +#define VFE_IMASK_ERROR_ONLY 0xC0700041 +#define VFE_REG_UPDATE_TRIGGER 1 +#define VFE_PM_BUF_MAX_CNT_MASK 0xFF +#define VFE_DMI_CFG_DEFAULT 0x00000100 +#define LENS_ROLL_OFF_DELTA_TABLE_OFFSET 32 +#define VFE_AF_PINGPONG_STATUS_BIT 0x100 +#define VFE_AWB_PINGPONG_STATUS_BIT 0x200 + +/* VFE I/O registers */ +enum { + VFE_HW_VERSION = 0x00000000, + VFE_GLOBAL_RESET_CMD = 0x00000004, + VFE_MODULE_RESET = 0x00000008, + VFE_CGC_OVERRIDE = 0x0000000C, + VFE_MODULE_CFG = 0x00000010, + VFE_CFG = 0x00000014, + VFE_IRQ_MASK = 0x00000018, + VFE_IRQ_CLEAR = 0x0000001C, +VFE_IRQ_STATUS = 0x00000020, +VFE_IRQ_COMPOSITE_MASK = 0x00000024, +VFE_BUS_CMD = 0x00000028, +VFE_BUS_CFG = 0x0000002C, +VFE_BUS_ENC_Y_WR_PING_ADDR = 0x00000030, +VFE_BUS_ENC_Y_WR_PONG_ADDR = 0x00000034, +VFE_BUS_ENC_Y_WR_IMAGE_SIZE = 0x00000038, +VFE_BUS_ENC_Y_WR_BUFFER_CFG = 0x0000003C, +VFE_BUS_ENC_CBCR_WR_PING_ADDR = 0x00000040, +VFE_BUS_ENC_CBCR_WR_PONG_ADDR = 0x00000044, +VFE_BUS_ENC_CBCR_WR_IMAGE_SIZE = 0x00000048, +VFE_BUS_ENC_CBCR_WR_BUFFER_CFG = 0x0000004C, +VFE_BUS_VIEW_Y_WR_PING_ADDR = 0x00000050, +VFE_BUS_VIEW_Y_WR_PONG_ADDR = 0x00000054, +VFE_BUS_VIEW_Y_WR_IMAGE_SIZE = 0x00000058, +VFE_BUS_VIEW_Y_WR_BUFFER_CFG = 0x0000005C, +VFE_BUS_VIEW_CBCR_WR_PING_ADDR = 0x00000060, +VFE_BUS_VIEW_CBCR_WR_PONG_ADDR = 0x00000064, +VFE_BUS_VIEW_CBCR_WR_IMAGE_SIZE = 0x00000068, +VFE_BUS_VIEW_CBCR_WR_BUFFER_CFG = 0x0000006C, +VFE_BUS_STATS_AF_WR_PING_ADDR = 0x00000070, +VFE_BUS_STATS_AF_WR_PONG_ADDR = 0x00000074, +VFE_BUS_STATS_AWB_WR_PING_ADDR = 0x00000078, +VFE_BUS_STATS_AWB_WR_PONG_ADDR = 0x0000007C, +VFE_BUS_STATS_HIST_WR_PING_ADDR = 0x00000080, +VFE_BUS_STATS_HIST_WR_PONG_ADDR = 0x00000084, +VFE_BUS_STATS_WR_PRIORITY = 0x00000088, +VFE_BUS_STRIPE_RD_ADDR_0 = 0x0000008C, +VFE_BUS_STRIPE_RD_ADDR_1 = 0x00000090, +VFE_BUS_STRIPE_RD_ADDR_2 = 0x00000094, +VFE_BUS_STRIPE_RD_ADDR_3 = 0x00000098, +VFE_BUS_STRIPE_RD_VSIZE = 0x0000009C, +VFE_BUS_STRIPE_RD_HSIZE = 0x000000A0, +VFE_BUS_STRIPE_RD_BUFFER_CFG = 0x000000A4, +VFE_BUS_STRIPE_RD_UNPACK_CFG = 0x000000A8, +VFE_BUS_STRIPE_RD_UNPACK = 0x000000AC, +VFE_BUS_STRIPE_RD_PAD_SIZE = 0x000000B0, +VFE_BUS_STRIPE_RD_PAD_L_UNPACK = 0x000000B4, +VFE_BUS_STRIPE_RD_PAD_R_UNPACK = 0x000000B8, +VFE_BUS_STRIPE_RD_PAD_TB_UNPACK = 0x000000BC, +VFE_BUS_PINGPONG_IRQ_EN = 0x000000C0, +VFE_BUS_PINGPONG_STATUS = 0x000000C4, +VFE_BUS_PM_CMD = 0x000000C8, +VFE_BUS_PM_CFG = 0x000000CC, +VFE_BUS_ENC_Y_WR_PM_STATS_0 = 0x000000D0, +VFE_BUS_ENC_Y_WR_PM_STATS_1 = 0x000000D4, +VFE_BUS_ENC_CBCR_WR_PM_STATS_0 = 0x000000D8, +VFE_BUS_ENC_CBCR_WR_PM_STATS_1 = 0x000000DC, +VFE_BUS_VIEW_Y_WR_PM_STATS_0 = 0x000000E0, +VFE_BUS_VIEW_Y_WR_PM_STATS_1 = 0x000000E4, +VFE_BUS_VIEW_CBCR_WR_PM_STATS_0 = 0x000000E8, +VFE_BUS_VIEW_CBCR_WR_PM_STATS_1 = 0x000000EC, +VFE_BUS_MISR_CFG = 0x000000F4, +VFE_BUS_MISR_MAST_CFG_0 = 0x000000F8, +VFE_BUS_MISR_MAST_CFG_1 = 0x000000FC, +VFE_BUS_MISR_RD_VAL = 0x00000100, +VFE_AXI_CMD = 0x00000104, +VFE_AXI_CFG = 0x00000108, +VFE_AXI_STATUS = 0x0000010C, +CAMIF_COMMAND = 0x00000110, +CAMIF_CONFIG = 0x00000114, +CAMIF_EFS_CONFIG = 0x00000118, +CAMIF_FRAME_CONFIG = 0x0000011C, +CAMIF_WINDOW_WIDTH_CONFIG = 0x00000120, +CAMIF_WINDOW_HEIGHT_CONFIG = 0x00000124, +CAMIF_SUBSAMPLE1_CONFIG = 0x00000128, +CAMIF_SUBSAMPLE2_CONFIG = 0x0000012C, +CAMIF_EPOCH_IRQ = 0x00000130, +CAMIF_STATUS = 0x00000134, +CAMIF_MISR = 0x00000138, +VFE_SYNC_TIMER_CMD = 0x0000013C, +VFE_SYNC_TIMER0_LINE_START = 0x00000140, +VFE_SYNC_TIMER0_PIXEL_START = 0x00000144, +VFE_SYNC_TIMER0_PIXEL_DURATION = 0x00000148, +VFE_SYNC_TIMER1_LINE_START = 0x0000014C, +VFE_SYNC_TIMER1_PIXEL_START = 0x00000150, +VFE_SYNC_TIMER1_PIXEL_DURATION = 0x00000154, +VFE_SYNC_TIMER2_LINE_START = 0x00000158, +VFE_SYNC_TIMER2_PIXEL_START = 0x0000015C, +VFE_SYNC_TIMER2_PIXEL_DURATION = 0x00000160, +VFE_SYNC_TIMER_POLARITY = 0x00000164, +VFE_ASYNC_TIMER_CMD = 0x00000168, +VFE_ASYNC_TIMER0_CFG_0 = 0x0000016C, +VFE_ASYNC_TIMER0_CFG_1 = 0x00000170, +VFE_ASYNC_TIMER1_CFG_0 = 0x00000174, +VFE_ASYNC_TIMER1_CFG_1 = 0x00000178, +VFE_ASYNC_TIMER2_CFG_0 = 0x0000017C, +VFE_ASYNC_TIMER2_CFG_1 = 0x00000180, +VFE_ASYNC_TIMER3_CFG_0 = 0x00000184, +VFE_ASYNC_TIMER3_CFG_1 = 0x00000188, +VFE_TIMER_SEL = 0x0000018C, +VFE_REG_UPDATE_CMD = 0x00000190, +VFE_BLACK_EVEN_EVEN_VALUE = 0x00000194, +VFE_BLACK_EVEN_ODD_VALUE = 0x00000198, +VFE_BLACK_ODD_EVEN_VALUE = 0x0000019C, +VFE_BLACK_ODD_ODD_VALUE = 0x000001A0, +VFE_ROLLOFF_CFG_0 = 0x000001A4, +VFE_ROLLOFF_CFG_1 = 0x000001A8, +VFE_ROLLOFF_CFG_2 = 0x000001AC, +VFE_DEMUX_CFG = 0x000001B0, +VFE_DEMUX_GAIN_0 = 0x000001B4, +VFE_DEMUX_GAIN_1 = 0x000001B8, +VFE_DEMUX_EVEN_CFG = 0x000001BC, +VFE_DEMUX_ODD_CFG = 0x000001C0, +VFE_DEMOSAIC_CFG = 0x000001C4, +VFE_DEMOSAIC_ABF_CFG_0 = 0x000001C8, +VFE_DEMOSAIC_ABF_CFG_1 = 0x000001CC, +VFE_DEMOSAIC_BPC_CFG_0 = 0x000001D0, +VFE_DEMOSAIC_BPC_CFG_1 = 0x000001D4, +VFE_DEMOSAIC_STATUS = 0x000001D8, +VFE_CHROMA_UPSAMPLE_CFG = 0x000001DC, +VFE_CROP_WIDTH_CFG = 0x000001E0, +VFE_CROP_HEIGHT_CFG = 0x000001E4, +VFE_COLOR_CORRECT_COEFF_0 = 0x000001E8, +VFE_COLOR_CORRECT_COEFF_1 = 0x000001EC, +VFE_COLOR_CORRECT_COEFF_2 = 0x000001F0, +VFE_COLOR_CORRECT_COEFF_3 = 0x000001F4, +VFE_COLOR_CORRECT_COEFF_4 = 0x000001F8, +VFE_COLOR_CORRECT_COEFF_5 = 0x000001FC, +VFE_COLOR_CORRECT_COEFF_6 = 0x00000200, +VFE_COLOR_CORRECT_COEFF_7 = 0x00000204, +VFE_COLOR_CORRECT_COEFF_8 = 0x00000208, +VFE_COLOR_CORRECT_OFFSET_0 = 0x0000020C, +VFE_COLOR_CORRECT_OFFSET_1 = 0x00000210, +VFE_COLOR_CORRECT_OFFSET_2 = 0x00000214, +VFE_COLOR_CORRECT_COEFF_Q = 0x00000218, +VFE_LA_CFG = 0x0000021C, +VFE_LUT_BANK_SEL = 0x00000220, +VFE_CHROMA_ENHAN_A = 0x00000224, +VFE_CHROMA_ENHAN_B = 0x00000228, +VFE_CHROMA_ENHAN_C = 0x0000022C, +VFE_CHROMA_ENHAN_D = 0x00000230, +VFE_CHROMA_ENHAN_K = 0x00000234, +VFE_COLOR_CONVERT_COEFF_0 = 0x00000238, +VFE_COLOR_CONVERT_COEFF_1 = 0x0000023C, +VFE_COLOR_CONVERT_COEFF_2 = 0x00000240, +VFE_COLOR_CONVERT_OFFSET = 0x00000244, +VFE_ASF_CFG = 0x00000248, +VFE_ASF_SHARP_CFG_0 = 0x0000024C, +VFE_ASF_SHARP_CFG_1 = 0x00000250, +VFE_ASF_SHARP_COEFF_0 = 0x00000254, +VFE_ASF_SHARP_COEFF_1 = 0x00000258, +VFE_ASF_SHARP_COEFF_2 = 0x0000025C, +VFE_ASF_SHARP_COEFF_3 = 0x00000260, +VFE_ASF_MAX_EDGE = 0x00000264, +VFE_ASF_CROP_WIDTH_CFG = 0x00000268, +VFE_ASF_CROP_HEIGHT_CFG = 0x0000026C, +VFE_SCALE_CFG = 0x00000270, +VFE_SCALE_H_IMAGE_SIZE_CFG = 0x00000274, +VFE_SCALE_H_PHASE_CFG = 0x00000278, +VFE_SCALE_H_STRIPE_CFG = 0x0000027C, +VFE_SCALE_V_IMAGE_SIZE_CFG = 0x00000280, +VFE_SCALE_V_PHASE_CFG = 0x00000284, +VFE_SCALE_V_STRIPE_CFG = 0x00000288, +VFE_SCALE_Y_CFG = 0x0000028C, +VFE_SCALE_Y_H_IMAGE_SIZE_CFG = 0x00000290, +VFE_SCALE_Y_H_PHASE_CFG = 0x00000294, +VFE_SCALE_Y_V_IMAGE_SIZE_CFG = 0x00000298, +VFE_SCALE_Y_V_PHASE_CFG = 0x0000029C, +VFE_SCALE_CBCR_CFG = 0x000002A0, +VFE_SCALE_CBCR_H_IMAGE_SIZE_CFG = 0x000002A4, +VFE_SCALE_CBCR_H_PHASE_CFG = 0x000002A8, +VFE_SCALE_CBCR_V_IMAGE_SIZE_CFG = 0x000002AC, +VFE_SCALE_CBCR_V_PHASE_CFG = 0x000002B0, +VFE_WB_CFG = 0x000002B4, +VFE_CHROMA_SUPPRESS_CFG_0 = 0x000002B8, +VFE_CHROMA_SUPPRESS_CFG_1 = 0x000002BC, +VFE_CHROMA_SUBSAMPLE_CFG = 0x000002C0, +VFE_CHROMA_SUB_CROP_WIDTH_CFG = 0x000002C4, +VFE_CHROMA_SUB_CROP_HEIGHT_CFG = 0x000002C8, +VFE_FRAMEDROP_ENC_Y_CFG = 0x000002CC, +VFE_FRAMEDROP_ENC_CBCR_CFG = 0x000002D0, +VFE_FRAMEDROP_ENC_Y_PATTERN = 0x000002D4, +VFE_FRAMEDROP_ENC_CBCR_PATTERN = 0x000002D8, +VFE_FRAMEDROP_VIEW_Y_CFG = 0x000002DC, +VFE_FRAMEDROP_VIEW_CBCR_CFG = 0x000002E0, +VFE_FRAMEDROP_VIEW_Y_PATTERN = 0x000002E4, +VFE_FRAMEDROP_VIEW_CBCR_PATTERN = 0x000002E8, +VFE_CLAMP_MAX_CFG = 0x000002EC, +VFE_CLAMP_MIN_CFG = 0x000002F0, +VFE_STATS_CMD = 0x000002F4, +VFE_STATS_AF_CFG = 0x000002F8, +VFE_STATS_AF_DIM = 0x000002FC, +VFE_STATS_AF_GRID_0 = 0x00000300, +VFE_STATS_AF_GRID_1 = 0x00000304, +VFE_STATS_AF_GRID_2 = 0x00000308, +VFE_STATS_AF_GRID_3 = 0x0000030C, +VFE_STATS_AF_HEADER = 0x00000310, +VFE_STATS_AF_COEF0 = 0x00000314, +VFE_STATS_AF_COEF1 = 0x00000318, +VFE_STATS_AWBAE_CFG = 0x0000031C, +VFE_STATS_AXW_HEADER = 0x00000320, +VFE_STATS_AWB_MCFG = 0x00000324, +VFE_STATS_AWB_CCFG1 = 0x00000328, +VFE_STATS_AWB_CCFG2 = 0x0000032C, +VFE_STATS_HIST_HEADER = 0x00000330, +VFE_STATS_HIST_INNER_OFFSET = 0x00000334, +VFE_STATS_HIST_INNER_DIM = 0x00000338, +VFE_STATS_FRAME_SIZE = 0x0000033C, +VFE_DMI_CFG = 0x00000340, +VFE_DMI_ADDR = 0x00000344, +VFE_DMI_DATA_HI = 0x00000348, +VFE_DMI_DATA_LO = 0x0000034C, +VFE_DMI_RAM_AUTO_LOAD_CMD = 0x00000350, +VFE_DMI_RAM_AUTO_LOAD_STATUS = 0x00000354, +VFE_DMI_RAM_AUTO_LOAD_CFG = 0x00000358, +VFE_DMI_RAM_AUTO_LOAD_SEED = 0x0000035C, +VFE_TESTBUS_SEL = 0x00000360, +VFE_TESTGEN_CFG = 0x00000364, +VFE_SW_TESTGEN_CMD = 0x00000368, +VFE_HW_TESTGEN_CMD = 0x0000036C, +VFE_HW_TESTGEN_CFG = 0x00000370, +VFE_HW_TESTGEN_IMAGE_CFG = 0x00000374, +VFE_HW_TESTGEN_SOF_OFFSET_CFG = 0x00000378, +VFE_HW_TESTGEN_EOF_NOFFSET_CFG = 0x0000037C, +VFE_HW_TESTGEN_SOL_OFFSET_CFG = 0x00000380, +VFE_HW_TESTGEN_EOL_NOFFSET_CFG = 0x00000384, +VFE_HW_TESTGEN_HBI_CFG = 0x00000388, +VFE_HW_TESTGEN_VBL_CFG = 0x0000038C, +VFE_HW_TESTGEN_SOF_DUMMY_LINE_CFG2 = 0x00000390, +VFE_HW_TESTGEN_EOF_DUMMY_LINE_CFG2 = 0x00000394, +VFE_HW_TESTGEN_COLOR_BARS_CFG = 0x00000398, +VFE_HW_TESTGEN_RANDOM_CFG = 0x0000039C, +VFE_SPARE = 0x000003A0, +}; + +#define ping 0x0 +#define pong 0x1 + +struct vfe_bus_cfg_data { + boolean stripeRdPathEn; + boolean encYWrPathEn; + boolean encCbcrWrPathEn; + boolean viewYWrPathEn; + boolean viewCbcrWrPathEn; + enum VFE_RAW_PIXEL_DATA_SIZE rawPixelDataSize; + enum VFE_RAW_WR_PATH_SEL rawWritePathSelect; +}; + +struct vfe_camif_cfg_data { + boolean camif2OutputEnable; + boolean camif2BusEnable; + struct vfe_cmds_camif_cfg camifCfgFromCmd; +}; + +struct vfe_irq_composite_mask_config { + uint8_t encIrqComMask; + uint8_t viewIrqComMask; + uint8_t ceDoneSel; +}; + +/* define a structure for each output path.*/ +struct vfe_output_path { + uint32_t addressBuffer[8]; + uint16_t fragIndex; + boolean hwCurrentFlag; + uint8_t *hwRegPingAddress; + uint8_t *hwRegPongAddress; +}; + +struct vfe_output_path_combo { + boolean whichOutputPath; + boolean pathEnabled; + boolean multiFrag; + uint8_t fragCount; + boolean ackPending; + uint8_t currentFrame; + uint32_t nextFrameAddrBuf[8]; + struct vfe_output_path yPath; + struct vfe_output_path cbcrPath; + uint8_t snapshotPendingCount; + boolean pmEnabled; + uint8_t cbcrStatusBit; +}; + +struct vfe_stats_control { + boolean ackPending; + uint32_t addressBuffer[2]; + uint32_t nextFrameAddrBuf; + boolean pingPongStatus; + uint8_t *hwRegPingAddress; + uint8_t *hwRegPongAddress; + uint32_t droppedStatsFrameCount; + uint32_t bufToRender; +}; + +struct vfe_gamma_lut_sel { + boolean ch0BankSelect; + boolean ch1BankSelect; + boolean ch2BankSelect; +}; + +struct vfe_interrupt_mask { + boolean camifErrorIrq; + boolean camifSofIrq; + boolean camifEolIrq; + boolean camifEofIrq; + boolean camifEpoch1Irq; + boolean camifEpoch2Irq; + boolean camifOverflowIrq; + boolean ceIrq; + boolean regUpdateIrq; + boolean resetAckIrq; + boolean encYPingpongIrq; + boolean encCbcrPingpongIrq; + boolean viewYPingpongIrq; + boolean viewCbcrPingpongIrq; + boolean rdPingpongIrq; + boolean afPingpongIrq; + boolean awbPingpongIrq; + boolean histPingpongIrq; + boolean encIrq; + boolean viewIrq; + boolean busOverflowIrq; + boolean afOverflowIrq; + boolean awbOverflowIrq; + boolean syncTimer0Irq; + boolean syncTimer1Irq; + boolean syncTimer2Irq; + boolean asyncTimer0Irq; + boolean asyncTimer1Irq; + boolean asyncTimer2Irq; + boolean asyncTimer3Irq; + boolean axiErrorIrq; + boolean violationIrq; +}; + +enum vfe_interrupt_name { + CAMIF_ERROR_IRQ, + CAMIF_SOF_IRQ, + CAMIF_EOL_IRQ, + CAMIF_EOF_IRQ, + CAMIF_EPOCH1_IRQ, + CAMIF_EPOCH2_IRQ, + CAMIF_OVERFLOW_IRQ, + CE_IRQ, + REG_UPDATE_IRQ, + RESET_ACK_IRQ, + ENC_Y_PINGPONG_IRQ, + ENC_CBCR_PINGPONG_IRQ, + VIEW_Y_PINGPONG_IRQ, + VIEW_CBCR_PINGPONG_IRQ, + RD_PINGPONG_IRQ, + AF_PINGPONG_IRQ, + AWB_PINGPONG_IRQ, + HIST_PINGPONG_IRQ, + ENC_IRQ, + VIEW_IRQ, + BUS_OVERFLOW_IRQ, + AF_OVERFLOW_IRQ, + AWB_OVERFLOW_IRQ, + SYNC_TIMER0_IRQ, + SYNC_TIMER1_IRQ, + SYNC_TIMER2_IRQ, + ASYNC_TIMER0_IRQ, + ASYNC_TIMER1_IRQ, + ASYNC_TIMER2_IRQ, + ASYNC_TIMER3_IRQ, + AXI_ERROR_IRQ, + VIOLATION_IRQ +}; + +enum VFE_DMI_RAM_SEL { + NO_MEM_SELECTED = 0, + ROLLOFF_RAM = 0x1, + RGBLUT_RAM_CH0_BANK0 = 0x2, + RGBLUT_RAM_CH0_BANK1 = 0x3, + RGBLUT_RAM_CH1_BANK0 = 0x4, + RGBLUT_RAM_CH1_BANK1 = 0x5, + RGBLUT_RAM_CH2_BANK0 = 0x6, + RGBLUT_RAM_CH2_BANK1 = 0x7, + STATS_HIST_CB_EVEN_RAM = 0x8, + STATS_HIST_CB_ODD_RAM = 0x9, + STATS_HIST_CR_EVEN_RAM = 0xa, + STATS_HIST_CR_ODD_RAM = 0xb, + RGBLUT_CHX_BANK0 = 0xc, + RGBLUT_CHX_BANK1 = 0xd, + LUMA_ADAPT_LUT_RAM_BANK0 = 0xe, + LUMA_ADAPT_LUT_RAM_BANK1 = 0xf +}; + +struct vfe_module_enable { + boolean blackLevelCorrectionEnable; + boolean lensRollOffEnable; + boolean demuxEnable; + boolean chromaUpsampleEnable; + boolean demosaicEnable; + boolean statsEnable; + boolean cropEnable; + boolean mainScalerEnable; + boolean whiteBalanceEnable; + boolean colorCorrectionEnable; + boolean yHistEnable; + boolean skinToneEnable; + boolean lumaAdaptationEnable; + boolean rgbLUTEnable; + boolean chromaEnhanEnable; + boolean asfEnable; + boolean chromaSuppressionEnable; + boolean chromaSubsampleEnable; + boolean scaler2YEnable; + boolean scaler2CbcrEnable; +}; + +struct vfe_bus_cmd_data { + boolean stripeReload; + boolean busPingpongReload; + boolean statsPingpongReload; +}; + +struct vfe_stats_cmd_data { + boolean autoFocusEnable; + boolean axwEnable; + boolean histEnable; + boolean clearHistEnable; + boolean histAutoClearEnable; + boolean colorConversionEnable; +}; + +struct vfe_hw_ver { + uint32_t minorVersion:8; + uint32_t majorVersion:8; + uint32_t coreVersion:4; + uint32_t /* reserved */ : 12; +} __attribute__((packed, aligned(4))); + +struct vfe_cfg { + uint32_t pixelPattern:3; + uint32_t /* reserved */ : 13; + uint32_t inputSource:2; + uint32_t /* reserved */ : 14; +} __attribute__((packed, aligned(4))); + +struct vfe_buscmd { + uint32_t stripeReload:1; + uint32_t /* reserved */ : 3; + uint32_t busPingpongReload:1; + uint32_t statsPingpongReload:1; + uint32_t /* reserved */ : 26; +} __attribute__((packed, aligned(4))); + +struct VFE_Irq_Composite_MaskType { + uint32_t encIrqComMaskBits:2; + uint32_t viewIrqComMaskBits:2; + uint32_t ceDoneSelBits:5; + uint32_t /* reserved */ : 23; +} __attribute__((packed, aligned(4))); + +struct vfe_mod_enable { + uint32_t blackLevelCorrectionEnable:1; + uint32_t lensRollOffEnable:1; + uint32_t demuxEnable:1; + uint32_t chromaUpsampleEnable:1; + uint32_t demosaicEnable:1; + uint32_t statsEnable:1; + uint32_t cropEnable:1; + uint32_t mainScalerEnable:1; + uint32_t whiteBalanceEnable:1; + uint32_t colorCorrectionEnable:1; + uint32_t yHistEnable:1; + uint32_t skinToneEnable:1; + uint32_t lumaAdaptationEnable:1; + uint32_t rgbLUTEnable:1; + uint32_t chromaEnhanEnable:1; + uint32_t asfEnable:1; + uint32_t chromaSuppressionEnable:1; + uint32_t chromaSubsampleEnable:1; + uint32_t scaler2YEnable:1; + uint32_t scaler2CbcrEnable:1; + uint32_t /* reserved */ : 14; +} __attribute__((packed, aligned(4))); + +struct vfe_irqenable { + uint32_t camifErrorIrq:1; + uint32_t camifSofIrq:1; + uint32_t camifEolIrq:1; + uint32_t camifEofIrq:1; + uint32_t camifEpoch1Irq:1; + uint32_t camifEpoch2Irq:1; + uint32_t camifOverflowIrq:1; + uint32_t ceIrq:1; + uint32_t regUpdateIrq:1; + uint32_t resetAckIrq:1; + uint32_t encYPingpongIrq:1; + uint32_t encCbcrPingpongIrq:1; + uint32_t viewYPingpongIrq:1; + uint32_t viewCbcrPingpongIrq:1; + uint32_t rdPingpongIrq:1; + uint32_t afPingpongIrq:1; + uint32_t awbPingpongIrq:1; + uint32_t histPingpongIrq:1; + uint32_t encIrq:1; + uint32_t viewIrq:1; + uint32_t busOverflowIrq:1; + uint32_t afOverflowIrq:1; + uint32_t awbOverflowIrq:1; + uint32_t syncTimer0Irq:1; + uint32_t syncTimer1Irq:1; + uint32_t syncTimer2Irq:1; + uint32_t asyncTimer0Irq:1; + uint32_t asyncTimer1Irq:1; + uint32_t asyncTimer2Irq:1; + uint32_t asyncTimer3Irq:1; + uint32_t axiErrorIrq:1; + uint32_t violationIrq:1; +} __attribute__((packed, aligned(4))); + +struct vfe_upsample_cfg { + uint32_t chromaCositingForYCbCrInputs:1; + uint32_t /* reserved */ : 31; +} __attribute__((packed, aligned(4))); + +struct VFE_CAMIFConfigType { + /* CAMIF Config */ + uint32_t /* reserved */ : 1; + uint32_t VSyncEdge:1; + uint32_t HSyncEdge:1; + uint32_t syncMode:2; + uint32_t vfeSubsampleEnable:1; + uint32_t /* reserved */ : 1; + uint32_t busSubsampleEnable:1; + uint32_t camif2vfeEnable:1; + uint32_t /* reserved */ : 1; + uint32_t camif2busEnable:1; + uint32_t irqSubsampleEnable:1; + uint32_t binningEnable:1; + uint32_t /* reserved */ : 18; + uint32_t misrEnable:1; +} __attribute__((packed, aligned(4))); + +struct vfe_camifcfg { + /* EFS_Config */ + uint32_t efsEndOfLine:8; + uint32_t efsStartOfLine:8; + uint32_t efsEndOfFrame:8; + uint32_t efsStartOfFrame:8; + /* Frame Config */ + uint32_t frameConfigPixelsPerLine:14; + uint32_t /* reserved */ : 2; + uint32_t frameConfigLinesPerFrame:14; + uint32_t /* reserved */ : 2; + /* Window Width Config */ + uint32_t windowWidthCfgLastPixel:14; + uint32_t /* reserved */ : 2; + uint32_t windowWidthCfgFirstPixel:14; + uint32_t /* reserved */ : 2; + /* Window Height Config */ + uint32_t windowHeightCfglastLine:14; + uint32_t /* reserved */ : 2; + uint32_t windowHeightCfgfirstLine:14; + uint32_t /* reserved */ : 2; + /* Subsample 1 Config */ + uint32_t subsample1CfgPixelSkip:16; + uint32_t subsample1CfgLineSkip:16; + /* Subsample 2 Config */ + uint32_t subsample2CfgFrameSkip:4; + uint32_t subsample2CfgFrameSkipMode:1; + uint32_t subsample2CfgPixelSkipWrap:1; + uint32_t /* reserved */ : 26; + /* Epoch Interrupt */ + uint32_t epoch1Line:14; + uint32_t /* reserved */ : 2; + uint32_t epoch2Line:14; + uint32_t /* reserved */ : 2; +} __attribute__((packed, aligned(4))); + +struct vfe_camifframe_update { + uint32_t pixelsPerLine:14; + uint32_t /* reserved */ : 2; + uint32_t linesPerFrame:14; + uint32_t /* reserved */ : 2; +} __attribute__((packed, aligned(4))); + +struct vfe_axi_bus_cfg { + uint32_t stripeRdPathEn:1; + uint32_t /* reserved */ : 3; + uint32_t encYWrPathEn:1; + uint32_t encCbcrWrPathEn:1; + uint32_t viewYWrPathEn:1; + uint32_t viewCbcrWrPathEn:1; + uint32_t rawPixelDataSize:2; + uint32_t rawWritePathSelect:2; + uint32_t /* reserved */ : 20; +} __attribute__((packed, aligned(4))); + +struct vfe_axi_out_cfg { + uint32_t out2YPingAddr:32; + uint32_t out2YPongAddr:32; + uint32_t out2YImageHeight:12; + uint32_t /* reserved */ : 4; + uint32_t out2YImageWidthin64bit:10; + uint32_t /* reserved */ : 6; + uint32_t out2YBurstLength:2; + uint32_t /* reserved */ : 2; + uint32_t out2YNumRows:12; + uint32_t out2YRowIncrementIn64bit:12; + uint32_t /* reserved */ : 4; + uint32_t out2CbcrPingAddr:32; + uint32_t out2CbcrPongAddr:32; + uint32_t out2CbcrImageHeight:12; + uint32_t /* reserved */ : 4; + uint32_t out2CbcrImageWidthIn64bit:10; + uint32_t /* reserved */ : 6; + uint32_t out2CbcrBurstLength:2; + uint32_t /* reserved */ : 2; + uint32_t out2CbcrNumRows:12; + uint32_t out2CbcrRowIncrementIn64bit:12; + uint32_t /* reserved */ : 4; + uint32_t out1YPingAddr:32; + uint32_t out1YPongAddr:32; + uint32_t out1YImageHeight:12; + uint32_t /* reserved */ : 4; + uint32_t out1YImageWidthin64bit:10; + uint32_t /* reserved */ : 6; + uint32_t out1YBurstLength:2; + uint32_t /* reserved */ : 2; + uint32_t out1YNumRows:12; + uint32_t out1YRowIncrementIn64bit:12; + uint32_t /* reserved */ : 4; + uint32_t out1CbcrPingAddr:32; + uint32_t out1CbcrPongAddr:32; + uint32_t out1CbcrImageHeight:12; + uint32_t /* reserved */ : 4; + uint32_t out1CbcrImageWidthIn64bit:10; + uint32_t /* reserved */ : 6; + uint32_t out1CbcrBurstLength:2; + uint32_t /* reserved */ : 2; + uint32_t out1CbcrNumRows:12; + uint32_t out1CbcrRowIncrementIn64bit:12; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct vfe_output_clamp_cfg { + /* Output Clamp Maximums */ + uint32_t yChanMax:8; + uint32_t cbChanMax:8; + uint32_t crChanMax:8; + uint32_t /* reserved */ : 8; + /* Output Clamp Minimums */ + uint32_t yChanMin:8; + uint32_t cbChanMin:8; + uint32_t crChanMin:8; + uint32_t /* reserved */ : 8; +} __attribute__((packed, aligned(4))); + +struct vfe_fov_crop_cfg { + uint32_t lastPixel:12; + uint32_t /* reserved */ : 4; + uint32_t firstPixel:12; + uint32_t /* reserved */ : 4; + + /* FOV Corp, Part 2 */ + uint32_t lastLine:12; + uint32_t /* reserved */ : 4; + uint32_t firstLine:12; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct VFE_FRAME_SKIP_UpdateCmdType { + uint32_t yPattern:32; + uint32_t cbcrPattern:32; +} __attribute__((packed, aligned(4))); + +struct vfe_frame_skip_cfg { + /* Frame Drop Enc (output2) */ + uint32_t output2YPeriod:5; + uint32_t /* reserved */ : 27; + uint32_t output2CbCrPeriod:5; + uint32_t /* reserved */ : 27; + uint32_t output2YPattern:32; + uint32_t output2CbCrPattern:32; + /* Frame Drop View (output1) */ + uint32_t output1YPeriod:5; + uint32_t /* reserved */ : 27; + uint32_t output1CbCrPeriod:5; + uint32_t /* reserved */ : 27; + uint32_t output1YPattern:32; + uint32_t output1CbCrPattern:32; +} __attribute__((packed, aligned(4))); + +struct vfe_main_scaler_cfg { + /* Scaler Enable Config */ + uint32_t hEnable:1; + uint32_t vEnable:1; + uint32_t /* reserved */ : 30; + /* Scale H Image Size Config */ + uint32_t inWidth:12; + uint32_t /* reserved */ : 4; + uint32_t outWidth:12; + uint32_t /* reserved */ : 4; + /* Scale H Phase Config */ + uint32_t horizPhaseMult:18; + uint32_t /* reserved */ : 2; + uint32_t horizInterResolution:2; + uint32_t /* reserved */ : 10; + /* Scale H Stripe Config */ + uint32_t horizMNInit:12; + uint32_t /* reserved */ : 4; + uint32_t horizPhaseInit:15; + uint32_t /* reserved */ : 1; + /* Scale V Image Size Config */ + uint32_t inHeight:12; + uint32_t /* reserved */ : 4; + uint32_t outHeight:12; + uint32_t /* reserved */ : 4; + /* Scale V Phase Config */ + uint32_t vertPhaseMult:18; + uint32_t /* reserved */ : 2; + uint32_t vertInterResolution:2; + uint32_t /* reserved */ : 10; + /* Scale V Stripe Config */ + uint32_t vertMNInit:12; + uint32_t /* reserved */ : 4; + uint32_t vertPhaseInit:15; + uint32_t /* reserved */ : 1; +} __attribute__((packed, aligned(4))); + +struct vfe_scaler2_cfg { + /* Scaler Enable Config */ + uint32_t hEnable:1; + uint32_t vEnable:1; + uint32_t /* reserved */ : 30; + /* Scaler H Image Size Config */ + uint32_t inWidth:12; + uint32_t /* reserved */ : 4; + uint32_t outWidth:12; + uint32_t /* reserved */ : 4; + /* Scaler H Phase Config */ + uint32_t horizPhaseMult:18; + uint32_t /* reserved */ : 2; + uint32_t horizInterResolution:2; + uint32_t /* reserved */ : 10; + /* Scaler V Image Size Config */ + uint32_t inHeight:12; + uint32_t /* reserved */ : 4; + uint32_t outHeight:12; + uint32_t /* reserved */ : 4; + /* Scaler V Phase Config */ + uint32_t vertPhaseMult:18; + uint32_t /* reserved */ : 2; + uint32_t vertInterResolution:2; + uint32_t /* reserved */ : 10; +} __attribute__((packed, aligned(4))); + +struct vfe_rolloff_cfg { + /* Rolloff 0 Config */ + uint32_t gridWidth:9; + uint32_t gridHeight:9; + uint32_t yDelta:9; + uint32_t /* reserved */ : 5; + /* Rolloff 1 Config*/ + uint32_t gridX:4; + uint32_t gridY:4; + uint32_t pixelX:9; + uint32_t /* reserved */ : 3; + uint32_t pixelY:9; + uint32_t /* reserved */ : 3; + /* Rolloff 2 Config */ + uint32_t yDeltaAccum:12; + uint32_t /* reserved */ : 20; +} __attribute__((packed, aligned(4))); + +struct vfe_asf_update { + /* ASF Config Command */ + uint32_t smoothEnable:1; + uint32_t sharpMode:2; + uint32_t /* reserved */ : 1; + uint32_t smoothCoeff1:4; + uint32_t smoothCoeff0:8; + uint32_t pipeFlushCount:12; + uint32_t pipeFlushOvd:1; + uint32_t flushHaltOvd:1; + uint32_t cropEnable:1; + uint32_t /* reserved */ : 1; + /* Sharpening Config 0 */ + uint32_t sharpThresholdE1:7; + uint32_t /* reserved */ : 1; + uint32_t sharpDegreeK1:5; + uint32_t /* reserved */ : 3; + uint32_t sharpDegreeK2:5; + uint32_t /* reserved */ : 3; + uint32_t normalizeFactor:7; + uint32_t /* reserved */ : 1; + /* Sharpening Config 1 */ + uint32_t sharpThresholdE2:8; + uint32_t sharpThresholdE3:8; + uint32_t sharpThresholdE4:8; + uint32_t sharpThresholdE5:8; + /* Sharpening Coefficients 0 */ + uint32_t F1Coeff0:6; + uint32_t F1Coeff1:6; + uint32_t F1Coeff2:6; + uint32_t F1Coeff3:6; + uint32_t F1Coeff4:6; + uint32_t /* reserved */ : 2; + /* Sharpening Coefficients 1 */ + uint32_t F1Coeff5:6; + uint32_t F1Coeff6:6; + uint32_t F1Coeff7:6; + uint32_t F1Coeff8:7; + uint32_t /* reserved */ : 7; + /* Sharpening Coefficients 2 */ + uint32_t F2Coeff0:6; + uint32_t F2Coeff1:6; + uint32_t F2Coeff2:6; + uint32_t F2Coeff3:6; + uint32_t F2Coeff4:6; + uint32_t /* reserved */ : 2; + /* Sharpening Coefficients 3 */ + uint32_t F2Coeff5:6; + uint32_t F2Coeff6:6; + uint32_t F2Coeff7:6; + uint32_t F2Coeff8:7; + uint32_t /* reserved */ : 7; +} __attribute__((packed, aligned(4))); + +struct vfe_asfcrop_cfg { + /* ASF Crop Width Config */ + uint32_t lastPixel:12; + uint32_t /* reserved */ : 4; + uint32_t firstPixel:12; + uint32_t /* reserved */ : 4; + /* ASP Crop Height Config */ + uint32_t lastLine:12; + uint32_t /* reserved */ : 4; + uint32_t firstLine:12; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct vfe_chroma_suppress_cfg { + /* Chroma Suppress 0 Config */ + uint32_t m1:8; + uint32_t m3:8; + uint32_t n1:3; + uint32_t /* reserved */ : 1; + uint32_t n3:3; + uint32_t /* reserved */ : 9; + /* Chroma Suppress 1 Config */ + uint32_t mm1:8; + uint32_t nn1:3; + uint32_t /* reserved */ : 21; +} __attribute__((packed, aligned(4))); + +struct vfe_chromasubsample_cfg { + /* Chroma Subsample Selection */ + uint32_t hCositedPhase:1; + uint32_t vCositedPhase:1; + uint32_t hCosited:1; + uint32_t vCosited:1; + uint32_t hsubSampleEnable:1; + uint32_t vsubSampleEnable:1; + uint32_t cropEnable:1; + uint32_t /* reserved */ : 25; + uint32_t cropWidthLastPixel:12; + uint32_t /* reserved */ : 4; + uint32_t cropWidthFirstPixel:12; + uint32_t /* reserved */ : 4; + uint32_t cropHeightLastLine:12; + uint32_t /* reserved */ : 4; + uint32_t cropHeightFirstLine:12; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct vfe_blacklevel_cfg { + /* Black Even-Even Value Config */ + uint32_t evenEvenAdjustment:9; + uint32_t /* reserved */ : 23; + /* Black Even-Odd Value Config */ + uint32_t evenOddAdjustment:9; + uint32_t /* reserved */ : 23; + /* Black Odd-Even Value Config */ + uint32_t oddEvenAdjustment:9; + uint32_t /* reserved */ : 23; + /* Black Odd-Odd Value Config */ + uint32_t oddOddAdjustment:9; + uint32_t /* reserved */ : 23; +} __attribute__((packed, aligned(4))); + +struct vfe_demux_cfg { + /* Demux Gain 0 Config */ + uint32_t ch0EvenGain:10; + uint32_t /* reserved */ : 6; + uint32_t ch0OddGain:10; + uint32_t /* reserved */ : 6; + /* Demux Gain 1 Config */ + uint32_t ch1Gain:10; + uint32_t /* reserved */ : 6; + uint32_t ch2Gain:10; + uint32_t /* reserved */ : 6; +} __attribute__((packed, aligned(4))); + +struct vfe_bps_info { + uint32_t greenBadPixelCount:8; + uint32_t /* reserved */ : 8; + uint32_t RedBlueBadPixelCount:8; + uint32_t /* reserved */ : 8; +} __attribute__((packed, aligned(4))); + +struct vfe_demosaic_cfg { + /* Demosaic Config */ + uint32_t abfEnable:1; + uint32_t badPixelCorrEnable:1; + uint32_t forceAbfOn:1; + uint32_t /* reserved */ : 1; + uint32_t abfShift:4; + uint32_t fminThreshold:7; + uint32_t /* reserved */ : 1; + uint32_t fmaxThreshold:7; + uint32_t /* reserved */ : 5; + uint32_t slopeShift:3; + uint32_t /* reserved */ : 1; +} __attribute__((packed, aligned(4))); + +struct vfe_demosaic_bpc_cfg { + /* Demosaic BPC Config 0 */ + uint32_t blueDiffThreshold:12; + uint32_t redDiffThreshold:12; + uint32_t /* reserved */ : 8; + /* Demosaic BPC Config 1 */ + uint32_t greenDiffThreshold:12; + uint32_t /* reserved */ : 20; +} __attribute__((packed, aligned(4))); + +struct vfe_demosaic_abf_cfg { + /* Demosaic ABF Config 0 */ + uint32_t lpThreshold:10; + uint32_t /* reserved */ : 22; + /* Demosaic ABF Config 1 */ + uint32_t ratio:4; + uint32_t minValue:10; + uint32_t /* reserved */ : 2; + uint32_t maxValue:10; + uint32_t /* reserved */ : 6; +} __attribute__((packed, aligned(4))); + +struct vfe_color_correction_cfg { + /* Color Corr. Coefficient 0 Config */ + uint32_t c0:12; + uint32_t /* reserved */ : 20; + /* Color Corr. Coefficient 1 Config */ + uint32_t c1:12; + uint32_t /* reserved */ : 20; + /* Color Corr. Coefficient 2 Config */ + uint32_t c2:12; + uint32_t /* reserved */ : 20; + /* Color Corr. Coefficient 3 Config */ + uint32_t c3:12; + uint32_t /* reserved */ : 20; + /* Color Corr. Coefficient 4 Config */ + uint32_t c4:12; + uint32_t /* reserved */ : 20; + /* Color Corr. Coefficient 5 Config */ + uint32_t c5:12; + uint32_t /* reserved */ : 20; + /* Color Corr. Coefficient 6 Config */ + uint32_t c6:12; + uint32_t /* reserved */ : 20; + /* Color Corr. Coefficient 7 Config */ + uint32_t c7:12; + uint32_t /* reserved */ : 20; + /* Color Corr. Coefficient 8 Config */ + uint32_t c8:12; + uint32_t /* reserved */ : 20; + /* Color Corr. Offset 0 Config */ + uint32_t k0:11; + uint32_t /* reserved */ : 21; + /* Color Corr. Offset 1 Config */ + uint32_t k1:11; + uint32_t /* reserved */ : 21; + /* Color Corr. Offset 2 Config */ + uint32_t k2:11; + uint32_t /* reserved */ : 21; + /* Color Corr. Coefficient Q Config */ + uint32_t coefQFactor:2; + uint32_t /* reserved */ : 30; +} __attribute__((packed, aligned(4))); + +struct VFE_LumaAdaptation_ConfigCmdType { + /* LA Config */ + uint32_t lutBankSelect:1; + uint32_t /* reserved */ : 31; +} __attribute__((packed, aligned(4))); + +struct vfe_wb_cfg { + /* WB Config */ + uint32_t ch0Gain:9; + uint32_t ch1Gain:9; + uint32_t ch2Gain:9; + uint32_t /* reserved */ : 5; +} __attribute__((packed, aligned(4))); + +struct VFE_GammaLutSelect_ConfigCmdType { + /* LUT Bank Select Config */ + uint32_t ch0BankSelect:1; + uint32_t ch1BankSelect:1; + uint32_t ch2BankSelect:1; + uint32_t /* reserved */ : 29; +} __attribute__((packed, aligned(4))); + +struct vfe_chroma_enhance_cfg { + /* Chroma Enhance A Config */ + uint32_t ap:11; + uint32_t /* reserved */ : 5; + uint32_t am:11; + uint32_t /* reserved */ : 5; + /* Chroma Enhance B Config */ + uint32_t bp:11; + uint32_t /* reserved */ : 5; + uint32_t bm:11; + uint32_t /* reserved */ : 5; + /* Chroma Enhance C Config */ + uint32_t cp:11; + uint32_t /* reserved */ : 5; + uint32_t cm:11; + uint32_t /* reserved */ : 5; + /* Chroma Enhance D Config */ + uint32_t dp:11; + uint32_t /* reserved */ : 5; + uint32_t dm:11; + uint32_t /* reserved */ : 5; + /* Chroma Enhance K Config */ + uint32_t kcb:11; + uint32_t /* reserved */ : 5; + uint32_t kcr:11; + uint32_t /* reserved */ : 5; +} __attribute__((packed, aligned(4))); + +struct vfe_color_convert_cfg { + /* Conversion Coefficient 0 */ + uint32_t v0:12; + uint32_t /* reserved */ : 20; + /* Conversion Coefficient 1 */ + uint32_t v1:12; + uint32_t /* reserved */ : 20; + /* Conversion Coefficient 2 */ + uint32_t v2:12; + uint32_t /* reserved */ : 20; + /* Conversion Offset */ + uint32_t ConvertOffset:8; + uint32_t /* reserved */ : 24; +} __attribute__((packed, aligned(4))); + +struct VFE_SyncTimer_ConfigCmdType { + /* Timer Line Start Config */ + uint32_t timerLineStart:12; + uint32_t /* reserved */ : 20; + /* Timer Pixel Start Config */ + uint32_t timerPixelStart:18; + uint32_t /* reserved */ : 14; + /* Timer Pixel Duration Config */ + uint32_t timerPixelDuration:28; + uint32_t /* reserved */ : 4; + /* Sync Timer Polarity Config */ + uint32_t timer0Polarity:1; + uint32_t timer1Polarity:1; + uint32_t timer2Polarity:1; + uint32_t /* reserved */ : 29; +} __attribute__((packed, aligned(4))); + +struct VFE_AsyncTimer_ConfigCmdType { + /* Async Timer Config 0 */ + uint32_t inactiveLength:20; + uint32_t numRepetition:10; + uint32_t /* reserved */ : 1; + uint32_t polarity:1; + /* Async Timer Config 1 */ + uint32_t activeLength:20; + uint32_t /* reserved */ : 12; +} __attribute__((packed, aligned(4))); + +struct VFE_AWBAEStatistics_ConfigCmdType { + /* AWB autoexposure Config */ + uint32_t aeRegionConfig:1; + uint32_t aeSubregionConfig:1; + uint32_t /* reserved */ : 14; + uint32_t awbYMin:8; + uint32_t awbYMax:8; + /* AXW Header */ + uint32_t axwHeader:8; + uint32_t /* reserved */ : 24; + /* AWB Mconfig */ + uint32_t m4:8; + uint32_t m3:8; + uint32_t m2:8; + uint32_t m1:8; + /* AWB Cconfig */ + uint32_t c2:12; + uint32_t /* reserved */ : 4; + uint32_t c1:12; + uint32_t /* reserved */ : 4; + /* AWB Cconfig 2 */ + uint32_t c4:12; + uint32_t /* reserved */ : 4; + uint32_t c3:12; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct VFE_TestGen_ConfigCmdType { + /* HW Test Gen Config */ + uint32_t numFrame:10; + uint32_t /* reserved */ : 2; + uint32_t pixelDataSelect:1; + uint32_t systematicDataSelect:1; + uint32_t /* reserved */ : 2; + uint32_t pixelDataSize:2; + uint32_t hsyncEdge:1; + uint32_t vsyncEdge:1; + uint32_t /* reserved */ : 12; + /* HW Test Gen Image Config */ + uint32_t imageWidth:14; + uint32_t /* reserved */ : 2; + uint32_t imageHeight:14; + uint32_t /* reserved */ : 2; + /* SOF Offset Config */ + uint32_t sofOffset:24; + uint32_t /* reserved */ : 8; + /* EOF NOffset Config */ + uint32_t eofNOffset:24; + uint32_t /* reserved */ : 8; + /* SOL Offset Config */ + uint32_t solOffset:9; + uint32_t /* reserved */ : 23; + /* EOL NOffset Config */ + uint32_t eolNOffset:9; + uint32_t /* reserved */ : 23; + /* HBI Config */ + uint32_t hBlankInterval:14; + uint32_t /* reserved */ : 18; + /* VBL Config */ + uint32_t vBlankInterval:14; + uint32_t /* reserved */ : 2; + uint32_t vBlankIntervalEnable:1; + uint32_t /* reserved */ : 15; + /* SOF Dummy Line Config */ + uint32_t sofDummy:8; + uint32_t /* reserved */ : 24; + /* EOF Dummy Line Config */ + uint32_t eofDummy:8; + uint32_t /* reserved */ : 24; + /* Color Bars Config */ + uint32_t unicolorBarSelect:3; + uint32_t /* reserved */ : 1; + uint32_t unicolorBarEnable:1; + uint32_t splitEnable:1; + uint32_t pixelPattern:2; + uint32_t rotatePeriod:6; + uint32_t /* reserved */ : 18; + /* Random Config */ + uint32_t randomSeed:16; + uint32_t /* reserved */ : 16; +} __attribute__((packed, aligned(4))); + +struct VFE_Bus_Pm_ConfigCmdType { + /* VFE Bus Performance Monitor Config */ + uint32_t output2YWrPmEnable:1; + uint32_t output2CbcrWrPmEnable:1; + uint32_t output1YWrPmEnable:1; + uint32_t output1CbcrWrPmEnable:1; + uint32_t /* reserved */ : 28; +} __attribute__((packed, aligned(4))); + +struct vfe_asf_info { + /* asf max edge */ + uint32_t maxEdge:13; + uint32_t /* reserved */ : 3; + /* HBi count */ + uint32_t HBICount:12; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct vfe_camif_stats { + uint32_t pixelCount:14; + uint32_t /* reserved */ : 2; + uint32_t lineCount:14; + uint32_t /* reserved */ : 1; + uint32_t camifHalt:1; +} __attribute__((packed, aligned(4))); + +struct VFE_StatsCmdType { + uint32_t autoFocusEnable:1; + uint32_t axwEnable:1; + uint32_t histEnable:1; + uint32_t clearHistEnable:1; + uint32_t histAutoClearEnable:1; + uint32_t colorConversionEnable:1; + uint32_t /* reserved */ : 26; +} __attribute__((packed, aligned(4))); + + +struct vfe_statsframe { + uint32_t lastPixel:12; + uint32_t /* reserved */ : 4; + uint32_t lastLine:12; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct vfe_busstats_wrprio { + uint32_t afBusPriority:4; + uint32_t awbBusPriority:4; + uint32_t histBusPriority:4; + uint32_t afBusPriorityEn:1; + uint32_t awbBusPriorityEn:1; + uint32_t histBusPriorityEn:1; + uint32_t /* reserved */ : 17; +} __attribute__((packed, aligned(4))); + +struct vfe_statsaf_update { + /* VFE_STATS_AF_CFG */ + uint32_t windowVOffset:12; + uint32_t /* reserved */ : 4; + uint32_t windowHOffset:12; + uint32_t /* reserved */ : 3; + uint32_t windowMode:1; + + /* VFE_STATS_AF_DIM */ + uint32_t windowHeight:12; + uint32_t /* reserved */ : 4; + uint32_t windowWidth:12; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct vfe_statsaf_cfg { + /* VFE_STATS_AF_GRID_0 */ + uint32_t entry00:8; + uint32_t entry01:8; + uint32_t entry02:8; + uint32_t entry03:8; + + /* VFE_STATS_AF_GRID_1 */ + uint32_t entry10:8; + uint32_t entry11:8; + uint32_t entry12:8; + uint32_t entry13:8; + + /* VFE_STATS_AF_GRID_2 */ + uint32_t entry20:8; + uint32_t entry21:8; + uint32_t entry22:8; + uint32_t entry23:8; + + /* VFE_STATS_AF_GRID_3 */ + uint32_t entry30:8; + uint32_t entry31:8; + uint32_t entry32:8; + uint32_t entry33:8; + + /* VFE_STATS_AF_HEADER */ + uint32_t afHeader:8; + uint32_t /* reserved */ : 24; + /* VFE_STATS_AF_COEF0 */ + uint32_t a00:5; + uint32_t a04:5; + uint32_t fvMax:11; + uint32_t fvMetric:1; + uint32_t /* reserved */ : 10; + + /* VFE_STATS_AF_COEF1 */ + uint32_t a20:5; + uint32_t a21:5; + uint32_t a22:5; + uint32_t a23:5; + uint32_t a24:5; + uint32_t /* reserved */ : 7; +} __attribute__((packed, aligned(4))); + +struct vfe_statsawbae_update { + uint32_t aeRegionCfg:1; + uint32_t aeSubregionCfg:1; + uint32_t /* reserved */ : 14; + uint32_t awbYMin:8; + uint32_t awbYMax:8; +} __attribute__((packed, aligned(4))); + +struct vfe_statsaxw_hdr_cfg { + /* Stats AXW Header Config */ + uint32_t axwHeader:8; + uint32_t /* reserved */ : 24; +} __attribute__((packed, aligned(4))); + +struct vfe_statsawb_update { + /* AWB MConfig */ + uint32_t m4:8; + uint32_t m3:8; + uint32_t m2:8; + uint32_t m1:8; + + /* AWB CConfig1 */ + uint32_t c2:12; + uint32_t /* reserved */ : 4; + uint32_t c1:12; + uint32_t /* reserved */ : 4; + + /* AWB CConfig2 */ + uint32_t c4:12; + uint32_t /* reserved */ : 4; + uint32_t c3:12; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct VFE_SyncTimerCmdType { + uint32_t hsyncCount:12; + uint32_t /* reserved */ : 20; + uint32_t pclkCount:18; + uint32_t /* reserved */ : 14; + uint32_t outputDuration:28; + uint32_t /* reserved */ : 4; +} __attribute__((packed, aligned(4))); + +struct VFE_AsyncTimerCmdType { + /* config 0 */ + uint32_t inactiveCount:20; + uint32_t repeatCount:10; + uint32_t /* reserved */ : 1; + uint32_t polarity:1; + /* config 1 */ + uint32_t activeCount:20; + uint32_t /* reserved */ : 12; +} __attribute__((packed, aligned(4))); + +struct VFE_AxiInputCmdType { + uint32_t stripeStartAddr0:32; + uint32_t stripeStartAddr1:32; + uint32_t stripeStartAddr2:32; + uint32_t stripeStartAddr3:32; + + uint32_t ySize:12; + uint32_t yOffsetDelta:12; + uint32_t /* reserved */ : 8; + + /* bus_stripe_rd_hSize */ + uint32_t /* reserved */ : 16; + uint32_t xSizeWord:10; + uint32_t /* reserved */ : 6; + + /* bus_stripe_rd_buffer_cfg */ + uint32_t burstLength:2; + uint32_t /* reserved */ : 2; + uint32_t NumOfRows:12; + uint32_t RowIncrement:12; + uint32_t /* reserved */ : 4; + + /* bus_stripe_rd_unpack_cfg */ + uint32_t mainUnpackHeight:12; + uint32_t mainUnpackWidth:13; + uint32_t mainUnpackHbiSel:3; + uint32_t mainUnpackPhase:3; + uint32_t /* reserved */ : 1; + + /* bus_stripe_rd_unpack */ + uint32_t unpackPattern:32; + + /* bus_stripe_rd_pad_size */ + uint32_t padLeft:7; + uint32_t /* reserved */ : 1; + uint32_t padRight:7; + uint32_t /* reserved */ : 1; + uint32_t padTop:7; + uint32_t /* reserved */ : 1; + uint32_t padBottom:7; + uint32_t /* reserved */ : 1; + + /* bus_stripe_rd_pad_L_unpack */ + uint32_t leftUnpackPattern0:4; + uint32_t leftUnpackPattern1:4; + uint32_t leftUnpackPattern2:4; + uint32_t leftUnpackPattern3:4; + uint32_t leftUnpackStop0:1; + uint32_t leftUnpackStop1:1; + uint32_t leftUnpackStop2:1; + uint32_t leftUnpackStop3:1; + uint32_t /* reserved */ : 12; + + /* bus_stripe_rd_pad_R_unpack */ + uint32_t rightUnpackPattern0:4; + uint32_t rightUnpackPattern1:4; + uint32_t rightUnpackPattern2:4; + uint32_t rightUnpackPattern3:4; + uint32_t rightUnpackStop0:1; + uint32_t rightUnpackStop1:1; + uint32_t rightUnpackStop2:1; + uint32_t rightUnpackStop3:1; + uint32_t /* reserved */ : 12; + + /* bus_stripe_rd_pad_tb_unpack */ + uint32_t topUnapckPattern:4; + uint32_t /* reserved */ : 12; + uint32_t bottomUnapckPattern:4; + uint32_t /* reserved */ : 12; +} __attribute__((packed, aligned(4))); + +struct VFE_AxiRdFragIrqEnable { + uint32_t stripeRdFragirq0Enable:1; + uint32_t stripeRdFragirq1Enable:1; + uint32_t stripeRdFragirq2Enable:1; + uint32_t stripeRdFragirq3Enable:1; + uint32_t /* reserved */ : 28; +} __attribute__((packed, aligned(4))); + +int vfe_cmd_init(struct msm_vfe_callback *, struct platform_device *, void *); +void vfe_stats_af_stop(void); +void vfe_stop(void); +void vfe_update(void); +int vfe_rgb_gamma_update(struct vfe_cmd_rgb_gamma_config *); +int vfe_rgb_gamma_config(struct vfe_cmd_rgb_gamma_config *); +void vfe_stats_wb_exp_ack(struct vfe_cmd_stats_wb_exp_ack *); +void vfe_stats_af_ack(struct vfe_cmd_stats_af_ack *); +void vfe_start(struct vfe_cmd_start *); +void vfe_la_update(struct vfe_cmd_la_config *); +void vfe_la_config(struct vfe_cmd_la_config *); +void vfe_test_gen_start(struct vfe_cmd_test_gen_start *); +void vfe_frame_skip_update(struct vfe_cmd_frame_skip_update *); +void vfe_frame_skip_config(struct vfe_cmd_frame_skip_config *); +void vfe_output_clamp_config(struct vfe_cmd_output_clamp_config *); +void vfe_camif_frame_update(struct vfe_cmds_camif_frame *); +void vfe_color_correction_config(struct vfe_cmd_color_correction_config *); +void vfe_demosaic_abf_update(struct vfe_cmd_demosaic_abf_update *); +void vfe_demosaic_bpc_update(struct vfe_cmd_demosaic_bpc_update *); +void vfe_demosaic_config(struct vfe_cmd_demosaic_config *); +void vfe_demux_channel_gain_update(struct vfe_cmd_demux_channel_gain_config *); +void vfe_demux_channel_gain_config(struct vfe_cmd_demux_channel_gain_config *); +void vfe_black_level_update(struct vfe_cmd_black_level_config *); +void vfe_black_level_config(struct vfe_cmd_black_level_config *); +void vfe_asf_update(struct vfe_cmd_asf_update *); +void vfe_asf_config(struct vfe_cmd_asf_config *); +void vfe_white_balance_config(struct vfe_cmd_white_balance_config *); +void vfe_chroma_sup_config(struct vfe_cmd_chroma_suppression_config *); +void vfe_roll_off_config(struct vfe_cmd_roll_off_config *); +void vfe_chroma_subsample_config(struct vfe_cmd_chroma_subsample_config *); +void vfe_chroma_enhan_config(struct vfe_cmd_chroma_enhan_config *); +void vfe_scaler2cbcr_config(struct vfe_cmd_scaler2_config *); +void vfe_scaler2y_config(struct vfe_cmd_scaler2_config *); +void vfe_main_scaler_config(struct vfe_cmd_main_scaler_config *); +void vfe_stats_wb_exp_stop(void); +void vfe_stats_update_wb_exp(struct vfe_cmd_stats_wb_exp_update *); +void vfe_stats_update_af(struct vfe_cmd_stats_af_update *); +void vfe_stats_start_wb_exp(struct vfe_cmd_stats_wb_exp_start *); +void vfe_stats_start_af(struct vfe_cmd_stats_af_start *); +void vfe_stats_setting(struct vfe_cmd_stats_setting *); +void vfe_axi_input_config(struct vfe_cmd_axi_input_config *); +void vfe_axi_output_config(struct vfe_cmd_axi_output_config *); +void vfe_camif_config(struct vfe_cmd_camif_config *); +void vfe_fov_crop_config(struct vfe_cmd_fov_crop_config *); +void vfe_get_hw_version(struct vfe_cmd_hw_version *); +void vfe_reset(void); +void vfe_cmd_release(struct platform_device *); +void vfe_output_p_ack(struct vfe_cmd_output_ack *); +void vfe_output_v_ack(struct vfe_cmd_output_ack *); +#endif /* __MSM_VFE8X_REG_H__ */ diff --git a/drivers/media/video/msm/msm_vpe1.c b/drivers/media/video/msm/msm_vpe1.c new file mode 100644 index 00000000000..70b94486995 --- /dev/null +++ b/drivers/media/video/msm/msm_vpe1.c @@ -0,0 +1,1446 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include "msm_vpe1.h" +#include +#include +#include +#include +#include + +static int vpe_enable(uint32_t); +static int vpe_disable(void); +static int vpe_update_scaler(struct video_crop_t *pcrop); +static struct vpe_device_type vpe_device_data; +static struct vpe_device_type *vpe_device; +struct vpe_ctrl_type *vpe_ctrl; +char *vpe_general_cmd[] = { + "VPE_DUMMY_0", /* 0 */ + "VPE_SET_CLK", + "VPE_RESET", + "VPE_START", + "VPE_ABORT", + "VPE_OPERATION_MODE_CFG", /* 5 */ + "VPE_INPUT_PLANE_CFG", + "VPE_OUTPUT_PLANE_CFG", + "VPE_INPUT_PLANE_UPDATE", + "VPE_SCALE_CFG_TYPE", + "VPE_ROTATION_CFG_TYPE", /* 10 */ + "VPE_AXI_OUT_CFG", + "VPE_CMD_DIS_OFFSET_CFG", + "VPE_ENABLE", + "VPE_DISABLE", +}; +static uint32_t orig_src_y, orig_src_cbcr; + +#define CHECKED_COPY_FROM_USER(in) { \ + if (copy_from_user((in), (void __user *)cmd->value, \ + cmd->length)) { \ + rc = -EFAULT; \ + break; \ + } \ +} + +#define msm_dequeue_vpe(queue, member) ({ \ + unsigned long flags; \ + struct msm_device_queue *__q = (queue); \ + struct msm_queue_cmd *qcmd = 0; \ + spin_lock_irqsave(&__q->lock, flags); \ + if (!list_empty(&__q->list)) { \ + __q->len--; \ + qcmd = list_first_entry(&__q->list, \ + struct msm_queue_cmd, member); \ + list_del_init(&qcmd->member); \ + } \ + spin_unlock_irqrestore(&__q->lock, flags); \ + qcmd; \ +}) + +/* +static struct vpe_cmd_type vpe_cmd[] = { + {VPE_DUMMY_0, 0}, + {VPE_SET_CLK, 0}, + {VPE_RESET, 0}, + {VPE_START, 0}, + {VPE_ABORT, 0}, + {VPE_OPERATION_MODE_CFG, VPE_OPERATION_MODE_CFG_LEN}, + {VPE_INPUT_PLANE_CFG, VPE_INPUT_PLANE_CFG_LEN}, + {VPE_OUTPUT_PLANE_CFG, VPE_OUTPUT_PLANE_CFG_LEN}, + {VPE_INPUT_PLANE_UPDATE, VPE_INPUT_PLANE_UPDATE_LEN}, + {VPE_SCALE_CFG_TYPE, VPE_SCALER_CONFIG_LEN}, + {VPE_ROTATION_CFG_TYPE, 0}, + {VPE_AXI_OUT_CFG, 0}, + {VPE_CMD_DIS_OFFSET_CFG, VPE_DIS_OFFSET_CFG_LEN}, +}; +*/ + +static long long vpe_do_div(long long num, long long den) +{ + do_div(num, den); + return num; +} + +static int vpe_start(void) +{ + /* enable the frame irq, bit 0 = Display list 0 ROI done */ + msm_io_w(1, vpe_device->vpebase + VPE_INTR_ENABLE_OFFSET); + msm_io_dump(vpe_device->vpebase + 0x10000, 0x250); + /* this triggers the operation. */ + msm_io_w(1, vpe_device->vpebase + VPE_DL0_START_OFFSET); + + return 0; +} + +void vpe_reset_state_variables(void) +{ + /* initialize local variables for state control, etc.*/ + vpe_ctrl->op_mode = 0; + vpe_ctrl->state = VPE_STATE_INIT; + spin_lock_init(&vpe_ctrl->tasklet_lock); + spin_lock_init(&vpe_ctrl->state_lock); + INIT_LIST_HEAD(&vpe_ctrl->tasklet_q); +} + +static void vpe_config_axi_default(void) +{ + msm_io_w(0x25, vpe_device->vpebase + VPE_AXI_ARB_2_OFFSET); + + CDBG("%s: yaddr %ld cbcraddr %ld", __func__, + vpe_ctrl->out_y_addr, vpe_ctrl->out_cbcr_addr); + + if (!vpe_ctrl->out_y_addr || !vpe_ctrl->out_cbcr_addr) + return; + + msm_io_w(vpe_ctrl->out_y_addr, + vpe_device->vpebase + VPE_OUTP0_ADDR_OFFSET); + /* for video CbCr address */ + msm_io_w(vpe_ctrl->out_cbcr_addr, + vpe_device->vpebase + VPE_OUTP1_ADDR_OFFSET); + +} + +static int vpe_reset(void) +{ + uint32_t vpe_version; + uint32_t rc; + + vpe_reset_state_variables(); + vpe_version = msm_io_r(vpe_device->vpebase + VPE_HW_VERSION_OFFSET); + CDBG("vpe_version = 0x%x\n", vpe_version); + + /* disable all interrupts.*/ + msm_io_w(0, vpe_device->vpebase + VPE_INTR_ENABLE_OFFSET); + /* clear all pending interrupts*/ + msm_io_w(0x1fffff, vpe_device->vpebase + VPE_INTR_CLEAR_OFFSET); + + /* write sw_reset to reset the core. */ + msm_io_w(0x10, vpe_device->vpebase + VPE_SW_RESET_OFFSET); + + /* then poll the reset bit, it should be self-cleared. */ + while (1) { + rc = + msm_io_r(vpe_device->vpebase + VPE_SW_RESET_OFFSET) & 0x10; + if (rc == 0) + break; + } + + /* at this point, hardware is reset. Then pogram to default + values. */ + msm_io_w(VPE_AXI_RD_ARB_CONFIG_VALUE, + vpe_device->vpebase + VPE_AXI_RD_ARB_CONFIG_OFFSET); + + msm_io_w(VPE_CGC_ENABLE_VALUE, + vpe_device->vpebase + VPE_CGC_EN_OFFSET); + + msm_io_w(1, vpe_device->vpebase + VPE_CMD_MODE_OFFSET); + + msm_io_w(VPE_DEFAULT_OP_MODE_VALUE, + vpe_device->vpebase + VPE_OP_MODE_OFFSET); + + msm_io_w(VPE_DEFAULT_SCALE_CONFIG, + vpe_device->vpebase + VPE_SCALE_CONFIG_OFFSET); + + vpe_config_axi_default(); + return 0; +} + +int msm_vpe_cfg_update(void *pinfo) +{ + uint32_t rot_flag, rc = 0; + struct video_crop_t *pcrop = (struct video_crop_t *)pinfo; + + rot_flag = msm_io_r(vpe_device->vpebase + + VPE_OP_MODE_OFFSET) & 0xE00; + if (pinfo != NULL) { + CDBG("Crop info in2_w = %d, in2_h = %d " + "out2_h = %d out2_w = %d \n", pcrop->in2_w, + pcrop->in2_h, + pcrop->out2_h, pcrop->out2_w); + rc = vpe_update_scaler(pcrop); + } + CDBG("return rc = %d rot_flag = %d\n", rc, rot_flag); + rc |= rot_flag; + + return rc; +} + +void vpe_update_scale_coef(uint32_t *p) +{ + uint32_t i, offset; + offset = *p; + for (i = offset; i < (VPE_SCALE_COEFF_NUM + offset); i++) { + msm_io_w(*(++p), vpe_device->vpebase + VPE_SCALE_COEFF_LSBn(i)); + msm_io_w(*(++p), vpe_device->vpebase + VPE_SCALE_COEFF_MSBn(i)); + } +} + +void vpe_input_plane_config(uint32_t *p) +{ + msm_io_w(*p, vpe_device->vpebase + VPE_SRC_FORMAT_OFFSET); + msm_io_w(*(++p), vpe_device->vpebase + VPE_SRC_UNPACK_PATTERN1_OFFSET); + msm_io_w(*(++p), vpe_device->vpebase + VPE_SRC_IMAGE_SIZE_OFFSET); + msm_io_w(*(++p), vpe_device->vpebase + VPE_SRC_YSTRIDE1_OFFSET); + msm_io_w(*(++p), vpe_device->vpebase + VPE_SRC_SIZE_OFFSET); + vpe_ctrl->in_h_w = *p; + msm_io_w(*(++p), vpe_device->vpebase + VPE_SRC_XY_OFFSET); +} + +void vpe_output_plane_config(uint32_t *p) +{ + msm_io_w(*p, vpe_device->vpebase + VPE_OUT_FORMAT_OFFSET); + msm_io_w(*(++p), vpe_device->vpebase + VPE_OUT_PACK_PATTERN1_OFFSET); + msm_io_w(*(++p), vpe_device->vpebase + VPE_OUT_YSTRIDE1_OFFSET); + msm_io_w(*(++p), vpe_device->vpebase + VPE_OUT_SIZE_OFFSET); + msm_io_w(*(++p), vpe_device->vpebase + VPE_OUT_XY_OFFSET); + vpe_ctrl->pcbcr_dis_offset = *(++p); +} + +static int vpe_operation_config(uint32_t *p) +{ + uint32_t outw, outh, temp; + msm_io_w(*p, vpe_device->vpebase + VPE_OP_MODE_OFFSET); + + temp = msm_io_r(vpe_device->vpebase + VPE_OUT_SIZE_OFFSET); + outw = temp & 0xFFF; + outh = (temp & 0xFFF0000) >> 16; + + if (*p++ & 0xE00) { + /* rotation enabled. */ + vpe_ctrl->out_w = outh; + vpe_ctrl->out_h = outw; + } else { + vpe_ctrl->out_w = outw; + vpe_ctrl->out_h = outh; + } + vpe_ctrl->dis_en = *p; + return 0; +} + +/* Later we can separate the rotation and scaler calc. If +* rotation is enabled, simply swap the destination dimension. +* And then pass the already swapped output size to this +* function. */ +static int vpe_update_scaler(struct video_crop_t *pcrop) +{ + uint32_t out_ROI_width, out_ROI_height; + uint32_t src_ROI_width, src_ROI_height; + + uint32_t rc = 0; /* default to no zoom. */ + /* + * phase_step_x, phase_step_y, phase_init_x and phase_init_y + * are represented in fixed-point, unsigned 3.29 format + */ + uint32_t phase_step_x = 0; + uint32_t phase_step_y = 0; + uint32_t phase_init_x = 0; + uint32_t phase_init_y = 0; + + uint32_t src_roi, src_x, src_y, src_xy, temp; + uint32_t yscale_filter_sel, xscale_filter_sel; + uint32_t scale_unit_sel_x, scale_unit_sel_y; + uint64_t numerator, denominator; + + if ((pcrop->in2_w >= pcrop->out2_w) && + (pcrop->in2_h >= pcrop->out2_h)) { + CDBG(" =======VPE no zoom needed.\n"); + + temp = msm_io_r(vpe_device->vpebase + VPE_OP_MODE_OFFSET) + & 0xfffffffc; + msm_io_w(temp, vpe_device->vpebase + VPE_OP_MODE_OFFSET); + + + msm_io_w(0, vpe_device->vpebase + VPE_SRC_XY_OFFSET); + + CDBG("vpe_ctrl->in_h_w = %d \n", vpe_ctrl->in_h_w); + msm_io_w(vpe_ctrl->in_h_w , vpe_device->vpebase + + VPE_SRC_SIZE_OFFSET); + + return rc; + } + /* If fall through then scaler is needed.*/ + + CDBG("========VPE zoom needed.\n"); + /* assumption is both direction need zoom. this can be + improved. */ + temp = + msm_io_r(vpe_device->vpebase + VPE_OP_MODE_OFFSET) | 0x3; + msm_io_w(temp, vpe_device->vpebase + VPE_OP_MODE_OFFSET); + + src_ROI_width = pcrop->in2_w; + src_ROI_height = pcrop->in2_h; + out_ROI_width = pcrop->out2_w; + out_ROI_height = pcrop->out2_h; + + CDBG("src w = 0x%x, h=0x%x, dst w = 0x%x, h =0x%x.\n", + src_ROI_width, src_ROI_height, out_ROI_width, + out_ROI_height); + src_roi = (src_ROI_height << 16) + src_ROI_width; + + msm_io_w(src_roi, vpe_device->vpebase + VPE_SRC_SIZE_OFFSET); + + src_x = (out_ROI_width - src_ROI_width)/2; + src_y = (out_ROI_height - src_ROI_height)/2; + + CDBG("src_x = %d, src_y=%d.\n", src_x, src_y); + + src_xy = src_y*(1<<16) + src_x; + msm_io_w(src_xy, vpe_device->vpebase + + VPE_SRC_XY_OFFSET); + CDBG("src_xy = %d, src_roi=%d.\n", src_xy, src_roi); + + /* decide whether to use FIR or M/N for scaling */ + if ((out_ROI_width == 1 && src_ROI_width < 4) || + (src_ROI_width < 4 * out_ROI_width - 3)) + scale_unit_sel_x = 0;/* use FIR scalar */ + else + scale_unit_sel_x = 1;/* use M/N scalar */ + + if ((out_ROI_height == 1 && src_ROI_height < 4) || + (src_ROI_height < 4 * out_ROI_height - 3)) + scale_unit_sel_y = 0;/* use FIR scalar */ + else + scale_unit_sel_y = 1;/* use M/N scalar */ + + /* calculate phase step for the x direction */ + + /* if destination is only 1 pixel wide, + the value of phase_step_x + is unimportant. Assigning phase_step_x to + src ROI width as an arbitrary value. */ + if (out_ROI_width == 1) + phase_step_x = (uint32_t) ((src_ROI_width) << + SCALER_PHASE_BITS); + + /* if using FIR scalar */ + else if (scale_unit_sel_x == 0) { + + /* Calculate the quotient ( src_ROI_width - 1 ) + / ( out_ROI_width - 1) + with u3.29 precision. Quotient is rounded up to + the larger 29th decimal point. */ + numerator = (uint64_t)(src_ROI_width - 1) << + SCALER_PHASE_BITS; + /* never equals to 0 because of the + "(out_ROI_width == 1 )"*/ + denominator = (uint64_t)(out_ROI_width - 1); + /* divide and round up to the larger 29th + decimal point. */ + phase_step_x = (uint32_t) vpe_do_div((numerator + + denominator - 1), denominator); + } else if (scale_unit_sel_x == 1) { /* if M/N scalar */ + /* Calculate the quotient ( src_ROI_width ) / + ( out_ROI_width) + with u3.29 precision. Quotient is rounded down to the + smaller 29th decimal point. */ + numerator = (uint64_t)(src_ROI_width) << + SCALER_PHASE_BITS; + denominator = (uint64_t)(out_ROI_width); + phase_step_x = + (uint32_t) vpe_do_div(numerator, denominator); + } + /* calculate phase step for the y direction */ + + /* if destination is only 1 pixel wide, the value of + phase_step_x is unimportant. Assigning phase_step_x + to src ROI width as an arbitrary value. */ + if (out_ROI_height == 1) + phase_step_y = + (uint32_t) ((src_ROI_height) << SCALER_PHASE_BITS); + + /* if FIR scalar */ + else if (scale_unit_sel_y == 0) { + /* Calculate the quotient ( src_ROI_height - 1 ) / + ( out_ROI_height - 1) + with u3.29 precision. Quotient is rounded up to the + larger 29th decimal point. */ + numerator = (uint64_t)(src_ROI_height - 1) << + SCALER_PHASE_BITS; + /* never equals to 0 because of the " + ( out_ROI_height == 1 )" case */ + denominator = (uint64_t)(out_ROI_height - 1); + /* Quotient is rounded up to the larger + 29th decimal point. */ + phase_step_y = + (uint32_t) vpe_do_div( + (numerator + denominator - 1), denominator); + } else if (scale_unit_sel_y == 1) { /* if M/N scalar */ + /* Calculate the quotient ( src_ROI_height ) + / ( out_ROI_height) + with u3.29 precision. Quotient is rounded down + to the smaller 29th decimal point. */ + numerator = (uint64_t)(src_ROI_height) << + SCALER_PHASE_BITS; + denominator = (uint64_t)(out_ROI_height); + phase_step_y = (uint32_t) vpe_do_div( + numerator, denominator); + } + + /* decide which set of FIR coefficients to use */ + if (phase_step_x > HAL_MDP_PHASE_STEP_2P50) + xscale_filter_sel = 0; + else if (phase_step_x > HAL_MDP_PHASE_STEP_1P66) + xscale_filter_sel = 1; + else if (phase_step_x > HAL_MDP_PHASE_STEP_1P25) + xscale_filter_sel = 2; + else + xscale_filter_sel = 3; + + if (phase_step_y > HAL_MDP_PHASE_STEP_2P50) + yscale_filter_sel = 0; + else if (phase_step_y > HAL_MDP_PHASE_STEP_1P66) + yscale_filter_sel = 1; + else if (phase_step_y > HAL_MDP_PHASE_STEP_1P25) + yscale_filter_sel = 2; + else + yscale_filter_sel = 3; + + /* calculate phase init for the x direction */ + + /* if using FIR scalar */ + if (scale_unit_sel_x == 0) { + if (out_ROI_width == 1) + phase_init_x = + (uint32_t) ((src_ROI_width - 1) << + SCALER_PHASE_BITS); + else + phase_init_x = 0; + } else if (scale_unit_sel_x == 1) /* M over N scalar */ + phase_init_x = 0; + + /* calculate phase init for the y direction + if using FIR scalar */ + if (scale_unit_sel_y == 0) { + if (out_ROI_height == 1) + phase_init_y = + (uint32_t) ((src_ROI_height - + 1) << SCALER_PHASE_BITS); + else + phase_init_y = 0; + } else if (scale_unit_sel_y == 1) /* M over N scalar */ + phase_init_y = 0; + + CDBG("phase step x = %d, step y = %d.\n", + phase_step_x, phase_step_y); + CDBG("phase init x = %d, init y = %d.\n", + phase_init_x, phase_init_y); + + msm_io_w(phase_step_x, vpe_device->vpebase + + VPE_SCALE_PHASEX_STEP_OFFSET); + msm_io_w(phase_step_y, vpe_device->vpebase + + VPE_SCALE_PHASEY_STEP_OFFSET); + + msm_io_w(phase_init_x, vpe_device->vpebase + + VPE_SCALE_PHASEX_INIT_OFFSET); + + msm_io_w(phase_init_y, vpe_device->vpebase + + VPE_SCALE_PHASEY_INIT_OFFSET); + + return 1; +} + +static int vpe_update_scaler_with_dis(struct video_crop_t *pcrop, + struct dis_offset_type *dis_offset) +{ + uint32_t out_ROI_width, out_ROI_height; + uint32_t src_ROI_width, src_ROI_height; + + uint32_t rc = 0; /* default to no zoom. */ + /* + * phase_step_x, phase_step_y, phase_init_x and phase_init_y + * are represented in fixed-point, unsigned 3.29 format + */ + uint32_t phase_step_x = 0; + uint32_t phase_step_y = 0; + uint32_t phase_init_x = 0; + uint32_t phase_init_y = 0; + + uint32_t src_roi, temp; + int32_t src_x, src_y, src_xy; + uint32_t yscale_filter_sel, xscale_filter_sel; + uint32_t scale_unit_sel_x, scale_unit_sel_y; + uint64_t numerator, denominator; + int32_t zoom_dis_x, zoom_dis_y; + + CDBG("%s: pcrop->in2_w = %d, pcrop->in2_h = %d\n", __func__, + pcrop->in2_w, pcrop->in2_h); + CDBG("%s: pcrop->out2_w = %d, pcrop->out2_h = %d\n", __func__, + pcrop->out2_w, pcrop->out2_h); + + if ((pcrop->in2_w >= pcrop->out2_w) && + (pcrop->in2_h >= pcrop->out2_h)) { + CDBG(" =======VPE no zoom needed, DIS is still enabled. \n"); + + temp = msm_io_r(vpe_device->vpebase + VPE_OP_MODE_OFFSET) + & 0xfffffffc; + msm_io_w(temp, vpe_device->vpebase + VPE_OP_MODE_OFFSET); + + /* no zoom, use dis offset directly. */ + src_xy = dis_offset->dis_offset_y * (1<<16) + + dis_offset->dis_offset_x; + + msm_io_w(src_xy, vpe_device->vpebase + VPE_SRC_XY_OFFSET); + + CDBG("vpe_ctrl->in_h_w = 0x%x \n", vpe_ctrl->in_h_w); + msm_io_w(vpe_ctrl->in_h_w, vpe_device->vpebase + + VPE_SRC_SIZE_OFFSET); + return rc; + } + /* If fall through then scaler is needed.*/ + + CDBG("========VPE zoom needed + DIS enabled.\n"); + /* assumption is both direction need zoom. this can be + improved. */ + temp = msm_io_r(vpe_device->vpebase + + VPE_OP_MODE_OFFSET) | 0x3; + msm_io_w(temp, vpe_device->vpebase + + VPE_OP_MODE_OFFSET); + zoom_dis_x = dis_offset->dis_offset_x * + pcrop->in2_w / pcrop->out2_w; + zoom_dis_y = dis_offset->dis_offset_y * + pcrop->in2_h / pcrop->out2_h; + + src_x = zoom_dis_x + (pcrop->out2_w-pcrop->in2_w)/2; + src_y = zoom_dis_y + (pcrop->out2_h-pcrop->in2_h)/2; + + out_ROI_width = vpe_ctrl->out_w; + out_ROI_height = vpe_ctrl->out_h; + + src_ROI_width = out_ROI_width * pcrop->in2_w / pcrop->out2_w; + src_ROI_height = out_ROI_height * pcrop->in2_h / pcrop->out2_h; + + /* clamp to output size. This is because along + processing, we mostly do truncation, therefore + dis_offset tends to be + smaller values. The intention was to make sure that the + offset does not exceed margin. But in the case it could + result src_roi bigger, due to subtract a smaller value. */ + CDBG("src w = 0x%x, h=0x%x, dst w = 0x%x, h =0x%x.\n", + src_ROI_width, src_ROI_height, out_ROI_width, + out_ROI_height); + + src_roi = (src_ROI_height << 16) + src_ROI_width; + + msm_io_w(src_roi, vpe_device->vpebase + VPE_SRC_SIZE_OFFSET); + + CDBG("src_x = %d, src_y=%d.\n", src_x, src_y); + + src_xy = src_y*(1<<16) + src_x; + msm_io_w(src_xy, vpe_device->vpebase + + VPE_SRC_XY_OFFSET); + CDBG("src_xy = 0x%x, src_roi=0x%x.\n", src_xy, src_roi); + + /* decide whether to use FIR or M/N for scaling */ + if ((out_ROI_width == 1 && src_ROI_width < 4) || + (src_ROI_width < 4 * out_ROI_width - 3)) + scale_unit_sel_x = 0;/* use FIR scalar */ + else + scale_unit_sel_x = 1;/* use M/N scalar */ + + if ((out_ROI_height == 1 && src_ROI_height < 4) || + (src_ROI_height < 4 * out_ROI_height - 3)) + scale_unit_sel_y = 0;/* use FIR scalar */ + else + scale_unit_sel_y = 1;/* use M/N scalar */ + /* calculate phase step for the x direction */ + + /* if destination is only 1 pixel wide, the value of + phase_step_x is unimportant. Assigning phase_step_x + to src ROI width as an arbitrary value. */ + if (out_ROI_width == 1) + phase_step_x = (uint32_t) ((src_ROI_width) << + SCALER_PHASE_BITS); + else if (scale_unit_sel_x == 0) { /* if using FIR scalar */ + /* Calculate the quotient ( src_ROI_width - 1 ) + / ( out_ROI_width - 1)with u3.29 precision. + Quotient is rounded up to the larger + 29th decimal point. */ + numerator = + (uint64_t)(src_ROI_width - 1) << + SCALER_PHASE_BITS; + /* never equals to 0 because of the " + (out_ROI_width == 1 )"*/ + denominator = (uint64_t)(out_ROI_width - 1); + /* divide and round up to the larger 29th + decimal point. */ + phase_step_x = (uint32_t) vpe_do_div( + (numerator + denominator - 1), denominator); + } else if (scale_unit_sel_x == 1) { /* if M/N scalar */ + /* Calculate the quotient + ( src_ROI_width ) / ( out_ROI_width) + with u3.29 precision. Quotient is rounded + down to the smaller 29th decimal point. */ + numerator = (uint64_t)(src_ROI_width) << + SCALER_PHASE_BITS; + denominator = (uint64_t)(out_ROI_width); + phase_step_x = + (uint32_t) vpe_do_div(numerator, denominator); + } + /* calculate phase step for the y direction */ + + /* if destination is only 1 pixel wide, the value of + phase_step_x is unimportant. Assigning phase_step_x + to src ROI width as an arbitrary value. */ + if (out_ROI_height == 1) + phase_step_y = + (uint32_t) ((src_ROI_height) << SCALER_PHASE_BITS); + else if (scale_unit_sel_y == 0) { /* if FIR scalar */ + /* Calculate the quotient + ( src_ROI_height - 1 ) / ( out_ROI_height - 1) + with u3.29 precision. Quotient is rounded up to the + larger 29th decimal point. */ + numerator = (uint64_t)(src_ROI_height - 1) << + SCALER_PHASE_BITS; + /* never equals to 0 because of the + "( out_ROI_height == 1 )" case */ + denominator = (uint64_t)(out_ROI_height - 1); + /* Quotient is rounded up to the larger 29th + decimal point. */ + phase_step_y = + (uint32_t) vpe_do_div( + (numerator + denominator - 1), denominator); + } else if (scale_unit_sel_y == 1) { /* if M/N scalar */ + /* Calculate the quotient ( src_ROI_height ) / ( out_ROI_height) + with u3.29 precision. Quotient is rounded down to the smaller + 29th decimal point. */ + numerator = (uint64_t)(src_ROI_height) << + SCALER_PHASE_BITS; + denominator = (uint64_t)(out_ROI_height); + phase_step_y = (uint32_t) vpe_do_div( + numerator, denominator); + } + + /* decide which set of FIR coefficients to use */ + if (phase_step_x > HAL_MDP_PHASE_STEP_2P50) + xscale_filter_sel = 0; + else if (phase_step_x > HAL_MDP_PHASE_STEP_1P66) + xscale_filter_sel = 1; + else if (phase_step_x > HAL_MDP_PHASE_STEP_1P25) + xscale_filter_sel = 2; + else + xscale_filter_sel = 3; + + if (phase_step_y > HAL_MDP_PHASE_STEP_2P50) + yscale_filter_sel = 0; + else if (phase_step_y > HAL_MDP_PHASE_STEP_1P66) + yscale_filter_sel = 1; + else if (phase_step_y > HAL_MDP_PHASE_STEP_1P25) + yscale_filter_sel = 2; + else + yscale_filter_sel = 3; + + /* calculate phase init for the x direction */ + + /* if using FIR scalar */ + if (scale_unit_sel_x == 0) { + if (out_ROI_width == 1) + phase_init_x = + (uint32_t) ((src_ROI_width - 1) << + SCALER_PHASE_BITS); + else + phase_init_x = 0; + + } else if (scale_unit_sel_x == 1) /* M over N scalar */ + phase_init_x = 0; + + /* calculate phase init for the y direction + if using FIR scalar */ + if (scale_unit_sel_y == 0) { + if (out_ROI_height == 1) + phase_init_y = + (uint32_t) ((src_ROI_height - + 1) << SCALER_PHASE_BITS); + else + phase_init_y = 0; + + } else if (scale_unit_sel_y == 1) /* M over N scalar */ + phase_init_y = 0; + + CDBG("phase step x = %d, step y = %d.\n", + phase_step_x, phase_step_y); + CDBG("phase init x = %d, init y = %d.\n", + phase_init_x, phase_init_y); + + msm_io_w(phase_step_x, vpe_device->vpebase + + VPE_SCALE_PHASEX_STEP_OFFSET); + + msm_io_w(phase_step_y, vpe_device->vpebase + + VPE_SCALE_PHASEY_STEP_OFFSET); + + msm_io_w(phase_init_x, vpe_device->vpebase + + VPE_SCALE_PHASEX_INIT_OFFSET); + + msm_io_w(phase_init_y, vpe_device->vpebase + + VPE_SCALE_PHASEY_INIT_OFFSET); + + return 1; +} + +void msm_send_frame_to_vpe(uint32_t pyaddr, uint32_t pcbcraddr, + struct timespec *ts, int output_type) +{ + uint32_t temp_pyaddr = 0, temp_pcbcraddr = 0; + + CDBG("vpe input, pyaddr = 0x%x, pcbcraddr = 0x%x\n", + pyaddr, pcbcraddr); + msm_io_w(pyaddr, vpe_device->vpebase + VPE_SRCP0_ADDR_OFFSET); + msm_io_w(pcbcraddr, vpe_device->vpebase + VPE_SRCP1_ADDR_OFFSET); + + if (vpe_ctrl->state == VPE_STATE_ACTIVE) + CDBG(" =====VPE is busy!!! Wrong!========\n"); + + if (output_type != OUTPUT_TYPE_ST_R) + vpe_ctrl->ts = *ts; + + if (output_type == OUTPUT_TYPE_ST_L) { + vpe_ctrl->pcbcr_before_dis = msm_io_r(vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + temp_pyaddr = msm_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + temp_pcbcraddr = temp_pyaddr + PAD_TO_2K(vpe_ctrl->out_w * + vpe_ctrl->out_h * 2, vpe_ctrl->pad_2k_bool); + msm_io_w(temp_pcbcraddr, vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + } + + if (vpe_ctrl->dis_en) { + /* Changing the VPE output CBCR address, + to make Y/CBCR continuous */ + vpe_ctrl->pcbcr_before_dis = msm_io_r(vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + temp_pyaddr = msm_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + temp_pcbcraddr = temp_pyaddr + vpe_ctrl->pcbcr_dis_offset; + msm_io_w(temp_pcbcraddr, vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + } + + vpe_ctrl->output_type = output_type; + vpe_ctrl->state = VPE_STATE_ACTIVE; + vpe_start(); +} + +static int vpe_proc_general(struct msm_vpe_cmd *cmd) +{ + int rc = 0; + uint32_t *cmdp = NULL; + struct msm_queue_cmd *qcmd = NULL; + struct msm_vpe_buf_info *vpe_buf; + int turbo_mode = 0; + struct msm_sync *sync = (struct msm_sync *)vpe_ctrl->syncdata; + CDBG("vpe_proc_general: cmdID = %s, length = %d\n", + vpe_general_cmd[cmd->id], cmd->length); + switch (cmd->id) { + case VPE_ENABLE: + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto vpe_proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto vpe_proc_general_done; + } + turbo_mode = *((int *)(cmd->value)); + rc = turbo_mode ? vpe_enable(VPE_TURBO_MODE_CLOCK_RATE) + : vpe_enable(VPE_NORMAL_MODE_CLOCK_RATE); + break; + case VPE_DISABLE: + rc = vpe_disable(); + break; + case VPE_RESET: + case VPE_ABORT: + rc = vpe_reset(); + break; + case VPE_START: + rc = vpe_start(); + break; + + case VPE_INPUT_PLANE_CFG: + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto vpe_proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto vpe_proc_general_done; + } + vpe_input_plane_config(cmdp); + break; + + case VPE_OPERATION_MODE_CFG: + CDBG("cmd->length = %d \n", cmd->length); + if (cmd->length != VPE_OPERATION_MODE_CFG_LEN) { + rc = -EINVAL; + goto vpe_proc_general_done; + } + cmdp = kmalloc(VPE_OPERATION_MODE_CFG_LEN, + GFP_ATOMIC); + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + VPE_OPERATION_MODE_CFG_LEN)) { + rc = -EFAULT; + goto vpe_proc_general_done; + } + rc = vpe_operation_config(cmdp); + CDBG("rc = %d \n", rc); + break; + + case VPE_OUTPUT_PLANE_CFG: + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto vpe_proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto vpe_proc_general_done; + } + vpe_output_plane_config(cmdp); + break; + + case VPE_SCALE_CFG_TYPE: + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto vpe_proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto vpe_proc_general_done; + } + vpe_update_scale_coef(cmdp); + break; + + case VPE_CMD_DIS_OFFSET_CFG: { + struct msm_vfe_resp *vdata; + /* first get the dis offset and frame id. */ + cmdp = kmalloc(cmd->length, GFP_ATOMIC); + if (!cmdp) { + rc = -ENOMEM; + goto vpe_proc_general_done; + } + if (copy_from_user(cmdp, + (void __user *)(cmd->value), + cmd->length)) { + rc = -EFAULT; + goto vpe_proc_general_done; + } + /* get the offset. */ + vpe_ctrl->dis_offset = *(struct dis_offset_type *)cmdp; + qcmd = msm_dequeue_vpe(&sync->vpe_q, list_vpe_frame); + if (!qcmd) { + pr_err("%s: no video frame.\n", __func__); + kfree(cmdp); + return -EAGAIN; + } + vdata = (struct msm_vfe_resp *)(qcmd->command); + vpe_buf = &vdata->vpe_bf; + vpe_update_scaler_with_dis(&(vpe_buf->vpe_crop), + &(vpe_ctrl->dis_offset)); + + msm_send_frame_to_vpe(vpe_buf->y_phy, vpe_buf->cbcr_phy, + &(vpe_buf->ts), OUTPUT_TYPE_V); + + if (!qcmd || !atomic_read(&qcmd->on_heap)) { + kfree(cmdp); + return -EAGAIN; + } + if (!atomic_sub_return(1, &qcmd->on_heap)) + kfree(qcmd); + break; + } + + default: + break; + } +vpe_proc_general_done: + kfree(cmdp); + return rc; +} + +static void vpe_addr_convert(struct msm_vpe_phy_info *pinfo, + enum vpe_resp_msg type, void *data, void **ext, int32_t *elen) +{ + CDBG("In vpe_addr_convert type = %d\n", type); + switch (type) { + case VPE_MSG_OUTPUT_V: + pinfo->output_id = OUTPUT_TYPE_V; + break; + case VPE_MSG_OUTPUT_ST_R: + /* output_id will be used by user space only. */ + pinfo->output_id = OUTPUT_TYPE_V; + break; + default: + break; + } /* switch */ + + CDBG("In vpe_addr_convert output_id = %d\n", pinfo->output_id); + + pinfo->y_phy = + ((struct vpe_message *)data)->_u.msgOut.yBuffer; + pinfo->cbcr_phy = + ((struct vpe_message *)data)->_u.msgOut.cbcrBuffer; + *ext = vpe_ctrl->extdata; + *elen = vpe_ctrl->extlen; +} + +void vpe_proc_ops(uint8_t id, void *msg, size_t len) +{ + struct msm_vpe_resp *rp; + + rp = vpe_ctrl->resp->vpe_alloc(sizeof(struct msm_vpe_resp), + vpe_ctrl->syncdata, GFP_ATOMIC); + if (!rp) { + CDBG("rp: cannot allocate buffer\n"); + return; + } + + CDBG("vpe_proc_ops, msgId = %d rp->evt_msg.msg_id = %d\n", + id, rp->evt_msg.msg_id); + rp->evt_msg.type = MSM_CAMERA_MSG; + rp->evt_msg.msg_id = id; + rp->evt_msg.len = len; + rp->evt_msg.data = msg; + + switch (rp->evt_msg.msg_id) { + case MSG_ID_VPE_OUTPUT_V: + rp->type = VPE_MSG_OUTPUT_V; + vpe_addr_convert(&(rp->phy), VPE_MSG_OUTPUT_V, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_VPE_OUTPUT_ST_R: + rp->type = VPE_MSG_OUTPUT_ST_R; + vpe_addr_convert(&(rp->phy), VPE_MSG_OUTPUT_ST_R, + rp->evt_msg.data, &(rp->extdata), + &(rp->extlen)); + break; + + case MSG_ID_VPE_OUTPUT_ST_L: + rp->type = VPE_MSG_OUTPUT_ST_L; + break; + + default: + rp->type = VPE_MSG_GENERAL; + break; + } + CDBG("%s: time = %ld\n", + __func__, vpe_ctrl->ts.tv_nsec); + + vpe_ctrl->resp->vpe_resp(rp, MSM_CAM_Q_VPE_MSG, + vpe_ctrl->syncdata, + &(vpe_ctrl->ts), GFP_ATOMIC); +} + +int vpe_config_axi(struct axidata *ad) +{ + uint32_t p1; + struct msm_pmem_region *regp1 = NULL; + CDBG("vpe_config_axi:bufnum1 = %d.\n", ad->bufnum1); + + if (ad->bufnum1 != 1) + return -EINVAL; + + regp1 = &(ad->region[0]); + /* for video Y address */ + p1 = (regp1->paddr + regp1->info.y_off); + msm_io_w(p1, vpe_device->vpebase + VPE_OUTP0_ADDR_OFFSET); + /* for video CbCr address */ + p1 = (regp1->paddr + regp1->info.cbcr_off); + msm_io_w(p1, vpe_device->vpebase + VPE_OUTP1_ADDR_OFFSET); + + return 0; +} + +int msm_vpe_config(struct msm_vpe_cfg_cmd *cmd, void *data) +{ + struct msm_vpe_cmd vpecmd; + int rc = 0; + if (copy_from_user(&vpecmd, + (void __user *)(cmd->value), + sizeof(vpecmd))) { + pr_err("%s %d: copy_from_user failed\n", __func__, + __LINE__); + return -EFAULT; + } + CDBG("%s: cmd_type %d\n", __func__, cmd->cmd_type); + switch (cmd->cmd_type) { + case CMD_VPE: + rc = vpe_proc_general(&vpecmd); + CDBG(" rc = %d\n", rc); + break; + + case CMD_AXI_CFG_VPE: + case CMD_AXI_CFG_SNAP_VPE: + case CMD_AXI_CFG_SNAP_THUMB_VPE: { + struct axidata *axid; + axid = data; + if (!axid) + return -EFAULT; + vpe_config_axi(axid); + break; + } + default: + break; + } + CDBG("%s: rc = %d\n", __func__, rc); + return rc; +} + +void msm_vpe_offset_update(int frame_pack, uint32_t pyaddr, uint32_t pcbcraddr, + struct timespec *ts, int output_id, struct msm_st_half st_half, + int frameid) +{ + struct msm_vpe_buf_info vpe_buf; + uint32_t input_stride; + + vpe_buf.vpe_crop.in2_w = st_half.stCropInfo.in_w; + vpe_buf.vpe_crop.in2_h = st_half.stCropInfo.in_h; + vpe_buf.vpe_crop.out2_w = st_half.stCropInfo.out_w; + vpe_buf.vpe_crop.out2_h = st_half.stCropInfo.out_h; + vpe_ctrl->dis_offset.dis_offset_x = st_half.pix_x_off; + vpe_ctrl->dis_offset.dis_offset_y = st_half.pix_y_off; + vpe_ctrl->dis_offset.frame_id = frameid; + vpe_ctrl->frame_pack = frame_pack; + vpe_ctrl->output_type = output_id; + + input_stride = (st_half.buf_cbcr_stride * (1<<16)) + + st_half.buf_y_stride; + + msm_io_w(input_stride, vpe_device->vpebase + VPE_SRC_YSTRIDE1_OFFSET); + + vpe_update_scaler_with_dis(&(vpe_buf.vpe_crop), + &(vpe_ctrl->dis_offset)); + + msm_send_frame_to_vpe(pyaddr, pcbcraddr, ts, output_id); +} + +static void vpe_send_outmsg(uint8_t msgid, uint32_t pyaddr, + uint32_t pcbcraddr) +{ + struct vpe_message msg; + uint8_t outid; + msg._d = outid = msgid; + msg._u.msgOut.output_id = msgid; + msg._u.msgOut.yBuffer = pyaddr; + msg._u.msgOut.cbcrBuffer = pcbcraddr; + vpe_proc_ops(outid, &msg, sizeof(struct vpe_message)); + return; +} + +int msm_vpe_reg(struct msm_vpe_callback *presp) +{ + if (presp && presp->vpe_resp) + vpe_ctrl->resp = presp; + + return 0; +} + +static void vpe_send_msg_no_payload(enum VPE_MESSAGE_ID id) +{ + struct vpe_message msg; + + CDBG("vfe31_send_msg_no_payload\n"); + msg._d = id; + vpe_proc_ops(id, &msg, 0); +} + +static void vpe_do_tasklet(unsigned long data) +{ + unsigned long flags; + uint32_t pyaddr = 0, pcbcraddr = 0; + uint32_t src_y, src_cbcr, temp; + + struct vpe_isr_queue_cmd_type *qcmd = NULL; + + CDBG("=== vpe_do_tasklet start === \n"); + + spin_lock_irqsave(&vpe_ctrl->tasklet_lock, flags); + qcmd = list_first_entry(&vpe_ctrl->tasklet_q, + struct vpe_isr_queue_cmd_type, list); + + if (!qcmd) { + spin_unlock_irqrestore(&vpe_ctrl->tasklet_lock, flags); + return; + } + + list_del(&qcmd->list); + spin_unlock_irqrestore(&vpe_ctrl->tasklet_lock, flags); + + /* interrupt to be processed, *qcmd has the payload. */ + if (qcmd->irq_status & 0x1) { + if (vpe_ctrl->output_type == OUTPUT_TYPE_ST_L) { + CDBG("vpe left frame done.\n"); + vpe_ctrl->output_type = 0; + CDBG("vpe send out msg.\n"); + orig_src_y = msm_io_r(vpe_device->vpebase + + VPE_SRCP0_ADDR_OFFSET); + orig_src_cbcr = msm_io_r(vpe_device->vpebase + + VPE_SRCP1_ADDR_OFFSET); + + pyaddr = msm_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + pcbcraddr = msm_io_r(vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + CDBG("%s: out_w = %d, out_h = %d\n", __func__, + vpe_ctrl->out_w, vpe_ctrl->out_h); + + if ((vpe_ctrl->frame_pack == TOP_DOWN_FULL) || + (vpe_ctrl->frame_pack == TOP_DOWN_HALF)) { + msm_io_w(pyaddr + (vpe_ctrl->out_w * + vpe_ctrl->out_h), vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + msm_io_w(pcbcraddr + (vpe_ctrl->out_w * + vpe_ctrl->out_h/2), + vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + } else if ((vpe_ctrl->frame_pack == + SIDE_BY_SIDE_HALF) || (vpe_ctrl->frame_pack == + SIDE_BY_SIDE_FULL)) { + msm_io_w(pyaddr + vpe_ctrl->out_w, + vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + msm_io_w(pcbcraddr + vpe_ctrl->out_w, + vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + } else + CDBG("%s: Invalid packing = %d\n", __func__, + vpe_ctrl->frame_pack); + + vpe_send_msg_no_payload(MSG_ID_VPE_OUTPUT_ST_L); + vpe_ctrl->state = VPE_STATE_INIT; + kfree(qcmd); + return; + } else if (vpe_ctrl->output_type == OUTPUT_TYPE_ST_R) { + src_y = orig_src_y; + src_cbcr = orig_src_cbcr; + CDBG("%s: out_w = %d, out_h = %d\n", __func__, + vpe_ctrl->out_w, vpe_ctrl->out_h); + + if ((vpe_ctrl->frame_pack == TOP_DOWN_FULL) || + (vpe_ctrl->frame_pack == TOP_DOWN_HALF)) { + pyaddr = msm_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET) - + (vpe_ctrl->out_w * vpe_ctrl->out_h); + } else if ((vpe_ctrl->frame_pack == + SIDE_BY_SIDE_HALF) || (vpe_ctrl->frame_pack == + SIDE_BY_SIDE_FULL)) { + pyaddr = msm_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET) - vpe_ctrl->out_w; + } else + CDBG("%s: Invalid packing = %d\n", __func__, + vpe_ctrl->frame_pack); + + pcbcraddr = vpe_ctrl->pcbcr_before_dis; + } else { + src_y = msm_io_r(vpe_device->vpebase + + VPE_SRCP0_ADDR_OFFSET); + src_cbcr = msm_io_r(vpe_device->vpebase + + VPE_SRCP1_ADDR_OFFSET); + pyaddr = msm_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + pcbcraddr = msm_io_r(vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + } + + if (vpe_ctrl->dis_en) + pcbcraddr = vpe_ctrl->pcbcr_before_dis; + + msm_io_w(src_y, + vpe_device->vpebase + VPE_OUTP0_ADDR_OFFSET); + msm_io_w(src_cbcr, + vpe_device->vpebase + VPE_OUTP1_ADDR_OFFSET); + + temp = msm_io_r(vpe_device->vpebase + VPE_OP_MODE_OFFSET) & + 0xFFFFFFFC; + msm_io_w(temp, vpe_device->vpebase + VPE_OP_MODE_OFFSET); + + /* now pass this frame to msm_camera.c. */ + if (vpe_ctrl->output_type == OUTPUT_TYPE_ST_R) { + CDBG("vpe send out R msg.\n"); + vpe_send_outmsg(MSG_ID_VPE_OUTPUT_ST_R, pyaddr, + pcbcraddr); + } else if (vpe_ctrl->output_type == OUTPUT_TYPE_V) { + CDBG("vpe send out V msg.\n"); + vpe_send_outmsg(MSG_ID_VPE_OUTPUT_V, pyaddr, pcbcraddr); + } + + vpe_ctrl->output_type = 0; + vpe_ctrl->state = VPE_STATE_INIT; /* put it back to idle. */ + + } + kfree(qcmd); +} +DECLARE_TASKLET(vpe_tasklet, vpe_do_tasklet, 0); + +static irqreturn_t vpe_parse_irq(int irq_num, void *data) +{ + unsigned long flags; + uint32_t irq_status = 0; + struct vpe_isr_queue_cmd_type *qcmd; + + CDBG("vpe_parse_irq.\n"); + /* read and clear back-to-back. */ + irq_status = msm_io_r_mb(vpe_device->vpebase + + VPE_INTR_STATUS_OFFSET); + msm_io_w_mb(irq_status, vpe_device->vpebase + + VPE_INTR_CLEAR_OFFSET); + + msm_io_w(0, vpe_device->vpebase + VPE_INTR_ENABLE_OFFSET); + + if (irq_status == 0) { + pr_err("%s: irq_status = 0,Something is wrong!\n", __func__); + return IRQ_HANDLED; + } + irq_status &= 0x1; + /* apply mask. only interested in bit 0. */ + if (irq_status) { + qcmd = kzalloc(sizeof(struct vpe_isr_queue_cmd_type), + GFP_ATOMIC); + if (!qcmd) { + pr_err("%s: qcmd malloc failed!\n", __func__); + return IRQ_HANDLED; + } + /* must be 0x1 now. so in bottom half we don't really + need to check. */ + qcmd->irq_status = irq_status & 0x1; + spin_lock_irqsave(&vpe_ctrl->tasklet_lock, flags); + list_add_tail(&qcmd->list, &vpe_ctrl->tasklet_q); + spin_unlock_irqrestore(&vpe_ctrl->tasklet_lock, flags); + tasklet_schedule(&vpe_tasklet); + } + return IRQ_HANDLED; +} + +static int vpe_enable_irq(void) +{ + uint32_t rc = 0; + rc = request_irq(vpe_device->vpeirq, + vpe_parse_irq, + IRQF_TRIGGER_HIGH, "vpe", 0); + return rc; +} + +int msm_vpe_open(void) +{ + int rc = 0; + + CDBG("%s: In \n", __func__); + + vpe_ctrl = kzalloc(sizeof(struct vpe_ctrl_type), GFP_KERNEL); + if (!vpe_ctrl) { + pr_err("%s: no memory!\n", __func__); + return -ENOMEM; + } + + spin_lock_init(&vpe_ctrl->ops_lock); + CDBG("%s: Out\n", __func__); + + return rc; +} + +int msm_vpe_release(void) +{ + /* clean up....*/ + int rc = 0; + CDBG("%s: state %d\n", __func__, vpe_ctrl->state); + if (vpe_ctrl->state != VPE_STATE_IDLE) + rc = vpe_disable(); + + kfree(vpe_ctrl); + return rc; +} + + +int vpe_enable(uint32_t clk_rate) +{ + int rc = 0; + unsigned long flags = 0; + /* don't change the order of clock and irq.*/ + CDBG("%s: enable_clock rate %u\n", __func__, clk_rate); + spin_lock_irqsave(&vpe_ctrl->ops_lock, flags); + if (vpe_ctrl->state != VPE_STATE_IDLE) { + CDBG("%s: VPE already enabled", __func__); + spin_unlock_irqrestore(&vpe_ctrl->ops_lock, flags); + return 0; + } + vpe_ctrl->state = VPE_STATE_INIT; + spin_unlock_irqrestore(&vpe_ctrl->ops_lock, flags); + + rc = msm_camio_vpe_clk_enable(clk_rate); + if (rc < 0) { + pr_err("%s: msm_camio_vpe_clk_enable failed", __func__); + vpe_ctrl->state = VPE_STATE_IDLE; + return rc; + } + + CDBG("%s: enable_irq\n", __func__); + vpe_enable_irq(); + + /* initialize the data structure - lock, queue etc. */ + spin_lock_init(&vpe_ctrl->tasklet_lock); + INIT_LIST_HEAD(&vpe_ctrl->tasklet_q); + + return rc; +} + +int vpe_disable(void) +{ + int rc = 0; + unsigned long flags = 0; + CDBG("%s: called", __func__); + spin_lock_irqsave(&vpe_ctrl->ops_lock, flags); + if (vpe_ctrl->state == VPE_STATE_IDLE) { + CDBG("%s: VPE already disabled", __func__); + spin_unlock_irqrestore(&vpe_ctrl->ops_lock, flags); + return 0; + } + vpe_ctrl->state = VPE_STATE_IDLE; + spin_unlock_irqrestore(&vpe_ctrl->ops_lock, flags); + vpe_ctrl->out_y_addr = msm_io_r(vpe_device->vpebase + + VPE_OUTP0_ADDR_OFFSET); + vpe_ctrl->out_cbcr_addr = msm_io_r(vpe_device->vpebase + + VPE_OUTP1_ADDR_OFFSET); + free_irq(vpe_device->vpeirq, 0); + tasklet_kill(&vpe_tasklet); + rc = msm_camio_vpe_clk_disable(); + return rc; +} + +static int __msm_vpe_probe(struct platform_device *pdev) +{ + int rc = 0; + struct resource *vpemem, *vpeirq, *vpeio; + void __iomem *vpebase; + + /* first allocate */ + + vpe_device = &vpe_device_data; + memset(vpe_device, 0, sizeof(struct vpe_device_type)); + + /* does the device exist? */ + vpeirq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!vpeirq) { + pr_err("%s: no vpe irq resource.\n", __func__); + rc = -ENODEV; + goto vpe_free_device; + } + vpemem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!vpemem) { + pr_err("%s: no vpe mem resource!\n", __func__); + rc = -ENODEV; + goto vpe_free_device; + } + vpeio = request_mem_region(vpemem->start, + resource_size(vpemem), pdev->name); + if (!vpeio) { + pr_err("%s: VPE region already claimed.\n", __func__); + rc = -EBUSY; + goto vpe_free_device; + } + + vpebase = + ioremap(vpemem->start, + (vpemem->end - vpemem->start) + 1); + if (!vpebase) { + pr_err("%s: vpe ioremap failed.\n", __func__); + rc = -ENOMEM; + goto vpe_release_mem_region; + } + + /* Fall through, _probe is successful. */ + vpe_device->vpeirq = vpeirq->start; + vpe_device->vpemem = vpemem; + vpe_device->vpeio = vpeio; + vpe_device->vpebase = vpebase; + return rc; /* this rc should be zero.*/ + + iounmap(vpe_device->vpebase); /* this path should never occur */ + +/* from this part it is error handling. */ +vpe_release_mem_region: + release_mem_region(vpemem->start, (vpemem->end - vpemem->start) + 1); +vpe_free_device: + return rc; /* this rc should have error code. */ +} + +static int __msm_vpe_remove(struct platform_device *pdev) +{ + struct resource *vpemem; + vpemem = vpe_device->vpemem; + + iounmap(vpe_device->vpebase); + release_mem_region(vpemem->start, + (vpemem->end - vpemem->start) + 1); + return 0; +} + +static struct platform_driver msm_vpe_driver = { + .probe = __msm_vpe_probe, + .remove = __msm_vpe_remove, + .driver = { + .name = "msm_vpe", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_vpe_init(void) +{ + return platform_driver_register(&msm_vpe_driver); +} +module_init(msm_vpe_init); + +static void __exit msm_vpe_exit(void) +{ + platform_driver_unregister(&msm_vpe_driver); +} +module_exit(msm_vpe_exit); + +MODULE_DESCRIPTION("msm vpe 1.0 driver"); +MODULE_VERSION("msm vpe driver 1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/msm_vpe1.h b/drivers/media/video/msm/msm_vpe1.h new file mode 100644 index 00000000000..ed7112e331f --- /dev/null +++ b/drivers/media/video/msm/msm_vpe1.h @@ -0,0 +1,253 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _msm_vpe1_h_ +#define _msm_vpe1_h_ + +#include + +/*********** start of register offset *********************/ +#define VPE_INTR_ENABLE_OFFSET 0x0020 +#define VPE_INTR_STATUS_OFFSET 0x0024 +#define VPE_INTR_CLEAR_OFFSET 0x0028 +#define VPE_DL0_START_OFFSET 0x0030 +#define VPE_HW_VERSION_OFFSET 0x0070 +#define VPE_SW_RESET_OFFSET 0x0074 +#define VPE_AXI_RD_ARB_CONFIG_OFFSET 0x0078 +#define VPE_SEL_CLK_OR_HCLK_TEST_BUS_OFFSET 0x007C +#define VPE_CGC_EN_OFFSET 0x0100 +#define VPE_CMD_STATUS_OFFSET 0x10008 +#define VPE_PROFILE_EN_OFFSET 0x10010 +#define VPE_PROFILE_COUNT_OFFSET 0x10014 +#define VPE_CMD_MODE_OFFSET 0x10060 +#define VPE_SRC_SIZE_OFFSET 0x10108 +#define VPE_SRCP0_ADDR_OFFSET 0x1010C +#define VPE_SRCP1_ADDR_OFFSET 0x10110 +#define VPE_SRC_YSTRIDE1_OFFSET 0x1011C +#define VPE_SRC_FORMAT_OFFSET 0x10124 +#define VPE_SRC_UNPACK_PATTERN1_OFFSET 0x10128 +#define VPE_OP_MODE_OFFSET 0x10138 +#define VPE_SCALE_PHASEX_INIT_OFFSET 0x1013C +#define VPE_SCALE_PHASEY_INIT_OFFSET 0x10140 +#define VPE_SCALE_PHASEX_STEP_OFFSET 0x10144 +#define VPE_SCALE_PHASEY_STEP_OFFSET 0x10148 +#define VPE_OUT_FORMAT_OFFSET 0x10150 +#define VPE_OUT_PACK_PATTERN1_OFFSET 0x10154 +#define VPE_OUT_SIZE_OFFSET 0x10164 +#define VPE_OUTP0_ADDR_OFFSET 0x10168 +#define VPE_OUTP1_ADDR_OFFSET 0x1016C +#define VPE_OUT_YSTRIDE1_OFFSET 0x10178 +#define VPE_OUT_XY_OFFSET 0x1019C +#define VPE_SRC_XY_OFFSET 0x10200 +#define VPE_SRC_IMAGE_SIZE_OFFSET 0x10208 +#define VPE_SCALE_CONFIG_OFFSET 0x10230 +#define VPE_DEINT_STATUS_OFFSET 0x30000 +#define VPE_DEINT_DECISION_OFFSET 0x30004 +#define VPE_DEINT_COEFF0_OFFSET 0x30010 +#define VPE_SCALE_STATUS_OFFSET 0x50000 +#define VPE_SCALE_SVI_PARAM_OFFSET 0x50010 +#define VPE_SCALE_SHARPEN_CFG_OFFSET 0x50020 +#define VPE_SCALE_COEFF_LSP_0_OFFSET 0x50400 +#define VPE_SCALE_COEFF_MSP_0_OFFSET 0x50404 + +#define VPE_AXI_ARB_2_OFFSET 0x004C + +#define VPE_SCALE_COEFF_LSBn(n) (0x50400 + 8 * (n)) +#define VPE_SCALE_COEFF_MSBn(n) (0x50404 + 8 * (n)) +#define VPE_SCALE_COEFF_NUM 32 + +/*********** end of register offset ********************/ + + +#define VPE_HARDWARE_VERSION 0x00080308 +#define VPE_SW_RESET_VALUE 0x00000010 /* bit 4 for PPP*/ +#define VPE_AXI_RD_ARB_CONFIG_VALUE 0x124924 +#define VPE_CMD_MODE_VALUE 0x1 +#define VPE_DEFAULT_OP_MODE_VALUE 0x40FC0004 +#define VPE_CGC_ENABLE_VALUE 0xffff +#define VPE_DEFAULT_SCALE_CONFIG 0x3c + +#define VPE_NORMAL_MODE_CLOCK_RATE 150000000 +#define VPE_TURBO_MODE_CLOCK_RATE 200000000 +/**************************************************/ +/*********** Start of command id ******************/ +/**************************************************/ +enum VPE_CMD_ID_ENUM { + VPE_DUMMY_0 = 0, + VPE_SET_CLK, + VPE_RESET, + VPE_START, + VPE_ABORT, + VPE_OPERATION_MODE_CFG, /* 5 */ + VPE_INPUT_PLANE_CFG, + VPE_OUTPUT_PLANE_CFG, + VPE_INPUT_PLANE_UPDATE, + VPE_SCALE_CFG_TYPE, + VPE_ROTATION_CFG_TYPE, /* 10 */ + VPE_AXI_OUT_CFG, + VPE_CMD_DIS_OFFSET_CFG, + VPE_ENABLE, + VPE_DISABLE, +}; + +/* Length of each command. In bytes. (payload only) */ +#define VPE_OPERATION_MODE_CFG_LEN 8 +#define VPE_INPUT_PLANE_CFG_LEN 24 +#define VPE_OUTPUT_PLANE_CFG_LEN 20 +#define VPE_INPUT_PLANE_UPDATE_LEN 12 +#define VPE_SCALER_CONFIG_LEN 260 +#define VPE_DIS_OFFSET_CFG_LEN 12 +/**************************************************/ +/*********** End of command id ********************/ +/**************************************************/ + +struct msm_vpe_cmd { + int32_t id; + uint16_t length; + void *value; +}; + +struct vpe_cmd_type { + uint16_t id; + uint32_t length; +}; + +struct vpe_isr_queue_cmd_type { + struct list_head list; + uint32_t irq_status; +}; + +enum VPE_MESSAGE_ID { + MSG_ID_VPE_OUTPUT_V = 7, /* To match with that of VFE */ + MSG_ID_VPE_OUTPUT_ST_L, + MSG_ID_VPE_OUTPUT_ST_R, +}; + +enum vpe_state { + VPE_STATE_IDLE, + VPE_STATE_INIT, + VPE_STATE_ACTIVE, +}; + +struct vpe_device_type { + /* device related. */ + int vpeirq; + void __iomem *vpebase; + struct resource *vpemem; + struct resource *vpeio; + void *device_extdata; +}; + +struct dis_offset_type { + int32_t dis_offset_x; + int32_t dis_offset_y; + uint32_t frame_id; +}; + +struct vpe_ctrl_type { + spinlock_t tasklet_lock; + spinlock_t state_lock; + spinlock_t ops_lock; + + struct list_head tasklet_q; + void *syncdata; + uint16_t op_mode; + void *extdata; + uint32_t extlen; + struct msm_vpe_callback *resp; + uint32_t in_h_w; + uint32_t out_h; /* this is BEFORE rotation. */ + uint32_t out_w; /* this is BEFORE rotation. */ + uint32_t dis_en; + struct timespec ts; + struct dis_offset_type dis_offset; + uint32_t pcbcr_before_dis; + uint32_t pcbcr_dis_offset; + int output_type; + int frame_pack; + uint8_t pad_2k_bool; + enum vpe_state state; + unsigned long out_y_addr; + unsigned long out_cbcr_addr; +}; + +/* +* vpe_input_update +* +* Define the parameters for output plane +*/ +/* this is the dimension of ROI. width / height. */ +struct vpe_src_size_packed { + uint32_t src_w; + uint32_t src_h; +}; + +struct vpe_src_xy_packed { + uint32_t src_x; + uint32_t src_y; +}; + +struct vpe_input_plane_update_type { + struct vpe_src_size_packed src_roi_size; + /* DIS updates this set. */ + struct vpe_src_xy_packed src_roi_offset; + /* input address*/ + uint8_t *src_p0_addr; + uint8_t *src_p1_addr; +}; + +struct vpe_msg_stats{ + uint32_t buffer; + uint32_t frameCounter; +}; + +struct vpe_msg_output { + uint8_t output_id; + uint32_t yBuffer; + uint32_t cbcrBuffer; + uint32_t frameCounter; +}; + +struct vpe_message { + uint8_t _d; + union { + struct vpe_msg_output msgOut; + struct vpe_msg_stats msgStats; + } _u; +}; + +#define SCALER_PHASE_BITS 29 +#define HAL_MDP_PHASE_STEP_2P50 0x50000000 +#define HAL_MDP_PHASE_STEP_1P66 0x35555555 +#define HAL_MDP_PHASE_STEP_1P25 0x28000000 + +struct phase_val_t { + int32_t phase_init_x; + int32_t phase_init_y; + int32_t phase_step_x; + int32_t phase_step_y; +}; + +extern struct vpe_ctrl_type *vpe_ctrl; + +int msm_vpe_open(void); +int msm_vpe_release(void); +int msm_vpe_reg(struct msm_vpe_callback *presp); +void msm_send_frame_to_vpe(uint32_t pyaddr, uint32_t pcbcraddr, + struct timespec *ts, int output_id); +int msm_vpe_config(struct msm_vpe_cfg_cmd *cmd, void *data); +int msm_vpe_cfg_update(void *pinfo); +void msm_vpe_offset_update(int frame_pack, uint32_t pyaddr, uint32_t pcbcraddr, + struct timespec *ts, int output_id, struct msm_st_half st_half, + int frameid); +#endif /*_msm_vpe1_h_*/ + diff --git a/drivers/media/video/msm/mt9d112.c b/drivers/media/video/msm/mt9d112.c new file mode 100644 index 00000000000..a7b5156c1ec --- /dev/null +++ b/drivers/media/video/msm/mt9d112.c @@ -0,0 +1,845 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mt9d112.h" + +/* Micron MT9D112 Registers and their values */ +/* Sensor Core Registers */ +#define REG_MT9D112_MODEL_ID 0x3000 +#define MT9D112_MODEL_ID 0x1580 + +/* SOC Registers Page 1 */ +#define REG_MT9D112_SENSOR_RESET 0x301A +#define REG_MT9D112_STANDBY_CONTROL 0x3202 +#define REG_MT9D112_MCU_BOOT 0x3386 + +#define SENSOR_DEBUG 0 + +struct mt9d112_work { + struct work_struct work; +}; + +static struct mt9d112_work *mt9d112_sensorw; +static struct i2c_client *mt9d112_client; + +struct mt9d112_ctrl { + const struct msm_camera_sensor_info *sensordata; +}; + + +static struct mt9d112_ctrl *mt9d112_ctrl; + +static DECLARE_WAIT_QUEUE_HEAD(mt9d112_wait_queue); +DEFINE_SEMAPHORE(mt9d112_sem); +static int16_t mt9d112_effect = CAMERA_EFFECT_OFF; + +/*============================================================= + EXTERNAL DECLARATIONS +==============================================================*/ +extern struct mt9d112_reg mt9d112_regs; + + +/*=============================================================*/ + +static int mt9d112_reset(const struct msm_camera_sensor_info *dev) +{ + int rc = 0; + + rc = gpio_request(dev->sensor_reset, "mt9d112"); + + if (!rc) { + rc = gpio_direction_output(dev->sensor_reset, 0); + msleep(20); + gpio_set_value_cansleep(dev->sensor_reset, 1); + msleep(20); + } + + return rc; +} + +static int32_t mt9d112_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + +#if SENSOR_DEBUG + if (length == 2) + CDBG("msm_io_i2c_w: 0x%04x 0x%04x\n", + *(u16 *) txdata, *(u16 *) (txdata + 2)); + else if (length == 4) + CDBG("msm_io_i2c_w: 0x%04x\n", *(u16 *) txdata); + else + CDBG("msm_io_i2c_w: length = %d\n", length); +#endif + if (i2c_transfer(mt9d112_client->adapter, msg, 1) < 0) { + CDBG("mt9d112_i2c_txdata failed\n"); + return -EIO; + } + + return 0; +} + +static int32_t mt9d112_i2c_write(unsigned short saddr, + unsigned short waddr, unsigned short wdata, enum mt9d112_width width) +{ + int32_t rc = -EIO; + unsigned char buf[4]; + + memset(buf, 0, sizeof(buf)); + switch (width) { + case WORD_LEN: { + buf[0] = (waddr & 0xFF00)>>8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00)>>8; + buf[3] = (wdata & 0x00FF); + + rc = mt9d112_i2c_txdata(saddr, buf, 4); + } + break; + + case BYTE_LEN: { + buf[0] = waddr; + buf[1] = wdata; + rc = mt9d112_i2c_txdata(saddr, buf, 2); + } + break; + + default: + break; + } + + if (rc < 0) + CDBG( + "i2c_write failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + + return rc; +} + +static int32_t mt9d112_i2c_write_table( + struct mt9d112_i2c_reg_conf const *reg_conf_tbl, + int num_of_items_in_table) +{ + int i; + int32_t rc = -EIO; + + for (i = 0; i < num_of_items_in_table; i++) { + rc = mt9d112_i2c_write(mt9d112_client->addr, + reg_conf_tbl->waddr, reg_conf_tbl->wdata, + reg_conf_tbl->width); + if (rc < 0) + break; + if (reg_conf_tbl->mdelay_time != 0) + mdelay(reg_conf_tbl->mdelay_time); + reg_conf_tbl++; + } + + return rc; +} + +static int mt9d112_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + +#if SENSOR_DEBUG + if (length == 2) + CDBG("msm_io_i2c_r: 0x%04x 0x%04x\n", + *(u16 *) rxdata, *(u16 *) (rxdata + 2)); + else if (length == 4) + CDBG("msm_io_i2c_r: 0x%04x\n", *(u16 *) rxdata); + else + CDBG("msm_io_i2c_r: length = %d\n", length); +#endif + + if (i2c_transfer(mt9d112_client->adapter, msgs, 2) < 0) { + CDBG("mt9d112_i2c_rxdata failed!\n"); + return -EIO; + } + + return 0; +} + +static int32_t mt9d112_i2c_read(unsigned short saddr, + unsigned short raddr, unsigned short *rdata, enum mt9d112_width width) +{ + int32_t rc = 0; + unsigned char buf[4]; + + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + switch (width) { + case WORD_LEN: { + buf[0] = (raddr & 0xFF00)>>8; + buf[1] = (raddr & 0x00FF); + + rc = mt9d112_i2c_rxdata(saddr, buf, 2); + if (rc < 0) + return rc; + + *rdata = buf[0] << 8 | buf[1]; + } + break; + + default: + break; + } + + if (rc < 0) + CDBG("mt9d112_i2c_read failed!\n"); + + return rc; +} + +static int32_t mt9d112_set_lens_roll_off(void) +{ + int32_t rc = 0; + rc = mt9d112_i2c_write_table(&mt9d112_regs.rftbl[0], + mt9d112_regs.rftbl_size); + return rc; +} + +static long mt9d112_reg_init(void) +{ + int32_t array_length; + int32_t i; + long rc; + + /* PLL Setup Start */ + rc = mt9d112_i2c_write_table(&mt9d112_regs.plltbl[0], + mt9d112_regs.plltbl_size); + + if (rc < 0) + return rc; + /* PLL Setup End */ + + array_length = mt9d112_regs.prev_snap_reg_settings_size; + + /* Configure sensor for Preview mode and Snapshot mode */ + for (i = 0; i < array_length; i++) { + rc = mt9d112_i2c_write(mt9d112_client->addr, + mt9d112_regs.prev_snap_reg_settings[i].register_address, + mt9d112_regs.prev_snap_reg_settings[i].register_value, + WORD_LEN); + + if (rc < 0) + return rc; + } + + /* Configure for Noise Reduction, Saturation and Aperture Correction */ + array_length = mt9d112_regs.noise_reduction_reg_settings_size; + + for (i = 0; i < array_length; i++) { + rc = mt9d112_i2c_write(mt9d112_client->addr, + mt9d112_regs.noise_reduction_reg_settings[i].register_address, + mt9d112_regs.noise_reduction_reg_settings[i].register_value, + WORD_LEN); + + if (rc < 0) + return rc; + } + + /* Set Color Kill Saturation point to optimum value */ + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x35A4, + 0x0593, + WORD_LEN); + if (rc < 0) + return rc; + + rc = mt9d112_i2c_write_table(&mt9d112_regs.stbl[0], + mt9d112_regs.stbl_size); + if (rc < 0) + return rc; + + rc = mt9d112_set_lens_roll_off(); + if (rc < 0) + return rc; + + return 0; +} + +static long mt9d112_set_effect(int mode, int effect) +{ + uint16_t reg_addr; + uint16_t reg_val; + long rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + /* Context A Special Effects */ + reg_addr = 0x2799; + break; + + case SENSOR_RAW_SNAPSHOT_MODE: + case SENSOR_SNAPSHOT_MODE: + /* Context B Special Effects */ + reg_addr = 0x279B; + break; + + default: + reg_addr = 0x2799; + break; + } + + switch (effect) { + case CAMERA_EFFECT_OFF: { + reg_val = 0x6440; + + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, reg_addr, WORD_LEN); + if (rc < 0) + return rc; + + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, reg_val, WORD_LEN); + if (rc < 0) + return rc; + } + break; + + case CAMERA_EFFECT_MONO: { + reg_val = 0x6441; + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, reg_addr, WORD_LEN); + if (rc < 0) + return rc; + + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, reg_val, WORD_LEN); + if (rc < 0) + return rc; + } + break; + + case CAMERA_EFFECT_NEGATIVE: { + reg_val = 0x6443; + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, reg_addr, WORD_LEN); + if (rc < 0) + return rc; + + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, reg_val, WORD_LEN); + if (rc < 0) + return rc; + } + break; + + case CAMERA_EFFECT_SOLARIZE: { + reg_val = 0x6445; + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, reg_addr, WORD_LEN); + if (rc < 0) + return rc; + + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, reg_val, WORD_LEN); + if (rc < 0) + return rc; + } + break; + + case CAMERA_EFFECT_SEPIA: { + reg_val = 0x6442; + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, reg_addr, WORD_LEN); + if (rc < 0) + return rc; + + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, reg_val, WORD_LEN); + if (rc < 0) + return rc; + } + break; + + default: { + reg_val = 0x6440; + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, reg_addr, WORD_LEN); + if (rc < 0) + return rc; + + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, reg_val, WORD_LEN); + if (rc < 0) + return rc; + + return -EINVAL; + } + } + mt9d112_effect = effect; + /* Refresh Sequencer */ + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0xA103, WORD_LEN); + if (rc < 0) + return rc; + + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x0005, WORD_LEN); + + return rc; +} + +static long mt9d112_set_sensor_mode(int mode) +{ + uint16_t clock; + long rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0xA20C, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x0004, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0xA215, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x0004, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0xA20B, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x0000, WORD_LEN); + if (rc < 0) + return rc; + + clock = 0x23C; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x341C, clock, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0xA103, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x0001, WORD_LEN); + if (rc < 0) + return rc; + mdelay(5); + + break; + + case SENSOR_SNAPSHOT_MODE: + /* Switch to lower fps for Snapshot */ + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x341C, 0x0120, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0xA120, WORD_LEN); + if (rc < 0) + return rc; + + msleep(40);/*waiting for the delay of one frame*/ + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x0002, WORD_LEN); + if (rc < 0) + return rc; + + msleep(80);/*waiting for the delay of two frames*/ + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0xA103, WORD_LEN); + if (rc < 0) + return rc; + + msleep(40);/*waiting for the delay of one frame*/ + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x0002, WORD_LEN); + if (rc < 0) + return rc; + break; + + case SENSOR_RAW_SNAPSHOT_MODE: + /* Setting the effect to CAMERA_EFFECT_OFF */ + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0x279B, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x6440, WORD_LEN); + if (rc < 0) + return rc; + msleep(40);/*waiting for the delay of one frame*/ + /* Switch to lower fps for Snapshot */ + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x341C, 0x0120, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0xA120, WORD_LEN); + if (rc < 0) + return rc; + + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x0002, WORD_LEN); + if (rc < 0) + return rc; + msleep(80);/*waiting for the delay of two frames frame*/ + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x338C, 0xA103, WORD_LEN); + if (rc < 0) + return rc; + msleep(40);/*waiting for the delay of one frame*/ + rc = + mt9d112_i2c_write(mt9d112_client->addr, + 0x3390, 0x0002, WORD_LEN); + if (rc < 0) + return rc; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int mt9d112_sensor_init_probe(const struct msm_camera_sensor_info *data) +{ + uint16_t model_id = 0; + int rc = 0; + + CDBG("init entry \n"); + rc = mt9d112_reset(data); + if (rc < 0) { + CDBG("reset failed!\n"); + goto init_probe_fail; + } + + msm_camio_clk_rate_set(24000000); + msleep(20); + + /* Micron suggested Power up block Start: + * Put MCU into Reset - Stop MCU */ + rc = mt9d112_i2c_write(mt9d112_client->addr, + REG_MT9D112_MCU_BOOT, 0x0501, WORD_LEN); + if (rc < 0) + goto init_probe_fail; + + /* Pull MCU from Reset - Start MCU */ + rc = mt9d112_i2c_write(mt9d112_client->addr, + REG_MT9D112_MCU_BOOT, 0x0500, WORD_LEN); + if (rc < 0) + goto init_probe_fail; + + mdelay(5); + + /* Micron Suggested - Power up block */ + rc = mt9d112_i2c_write(mt9d112_client->addr, + REG_MT9D112_SENSOR_RESET, 0x0ACC, WORD_LEN); + if (rc < 0) + goto init_probe_fail; + + rc = mt9d112_i2c_write(mt9d112_client->addr, + REG_MT9D112_STANDBY_CONTROL, 0x0008, WORD_LEN); + if (rc < 0) + goto init_probe_fail; + + /* FUSED_DEFECT_CORRECTION */ + rc = mt9d112_i2c_write(mt9d112_client->addr, + 0x33F4, 0x031D, WORD_LEN); + if (rc < 0) + goto init_probe_fail; + + mdelay(5); + + /* Micron suggested Power up block End */ + /* Read the Model ID of the sensor */ + rc = mt9d112_i2c_read(mt9d112_client->addr, + REG_MT9D112_MODEL_ID, &model_id, WORD_LEN); + if (rc < 0) + goto init_probe_fail; + + CDBG("mt9d112 model_id = 0x%x\n", model_id); + + /* Check if it matches it with the value in Datasheet */ + if (model_id != MT9D112_MODEL_ID) { + rc = -EINVAL; + goto init_probe_fail; + } + + rc = mt9d112_reg_init(); + if (rc < 0) + goto init_probe_fail; + + return rc; + +init_probe_fail: + return rc; +} + +int mt9d112_sensor_init(const struct msm_camera_sensor_info *data) +{ + int rc = 0; + + mt9d112_ctrl = kzalloc(sizeof(struct mt9d112_ctrl), GFP_KERNEL); + if (!mt9d112_ctrl) { + CDBG("mt9d112_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + + if (data) + mt9d112_ctrl->sensordata = data; + + /* Input MCLK = 24MHz */ + msm_camio_clk_rate_set(24000000); + mdelay(5); + + msm_camio_camif_pad_reg_reset(); + + rc = mt9d112_sensor_init_probe(data); + if (rc < 0) { + CDBG("mt9d112_sensor_init failed!\n"); + goto init_fail; + } + +init_done: + return rc; + +init_fail: + kfree(mt9d112_ctrl); + return rc; +} + +static int mt9d112_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&mt9d112_wait_queue); + return 0; +} + +int mt9d112_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cfg_data; + long rc = 0; + + if (copy_from_user(&cfg_data, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + + /* down(&mt9d112_sem); */ + + CDBG("mt9d112_ioctl, cfgtype = %d, mode = %d\n", + cfg_data.cfgtype, cfg_data.mode); + + switch (cfg_data.cfgtype) { + case CFG_SET_MODE: + rc = mt9d112_set_sensor_mode( + cfg_data.mode); + break; + + case CFG_SET_EFFECT: + rc = mt9d112_set_effect(cfg_data.mode, + cfg_data.cfg.effect); + break; + + case CFG_GET_AF_MAX_STEPS: + default: + rc = -EINVAL; + break; + } + + /* up(&mt9d112_sem); */ + + return rc; +} + +int mt9d112_sensor_release(void) +{ + int rc = 0; + + /* down(&mt9d112_sem); */ + gpio_set_value_cansleep(mt9d112_ctrl->sensordata->sensor_reset, 0); + msleep(20); + gpio_free(mt9d112_ctrl->sensordata->sensor_reset); + kfree(mt9d112_ctrl); + /* up(&mt9d112_sem); */ + + return rc; +} + +static int mt9d112_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + rc = -ENOTSUPP; + goto probe_failure; + } + + mt9d112_sensorw = + kzalloc(sizeof(struct mt9d112_work), GFP_KERNEL); + + if (!mt9d112_sensorw) { + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, mt9d112_sensorw); + mt9d112_init_client(client); + mt9d112_client = client; + + CDBG("mt9d112_probe succeeded!\n"); + + return 0; + +probe_failure: + kfree(mt9d112_sensorw); + mt9d112_sensorw = NULL; + CDBG("mt9d112_probe failed!\n"); + return rc; +} + +static const struct i2c_device_id mt9d112_i2c_id[] = { + { "mt9d112", 0}, + { }, +}; + +static struct i2c_driver mt9d112_i2c_driver = { + .id_table = mt9d112_i2c_id, + .probe = mt9d112_i2c_probe, + .remove = __exit_p(mt9d112_i2c_remove), + .driver = { + .name = "mt9d112", + }, +}; + +static int mt9d112_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = i2c_add_driver(&mt9d112_i2c_driver); + if (rc < 0 || mt9d112_client == NULL) { + rc = -ENOTSUPP; + goto probe_done; + } + + /* Input MCLK = 24MHz */ + msm_camio_clk_rate_set(24000000); + mdelay(5); + + rc = mt9d112_sensor_init_probe(info); + if (rc < 0) { + gpio_free(info->sensor_reset); + goto probe_done; + } + s->s_init = mt9d112_sensor_init; + s->s_release = mt9d112_sensor_release; + s->s_config = mt9d112_sensor_config; + s->s_camera_type = FRONT_CAMERA_2D; + s->s_mount_angle = 0; + gpio_set_value_cansleep(info->sensor_reset, 0); + msleep(20); + gpio_free(info->sensor_reset); + +probe_done: + CDBG("%s %s:%d\n", __FILE__, __func__, __LINE__); + return rc; +} + +static int __mt9d112_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, mt9d112_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __mt9d112_probe, + .driver = { + .name = "msm_camera_mt9d112", + .owner = THIS_MODULE, + }, +}; + +static int __init mt9d112_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(mt9d112_init); diff --git a/drivers/media/video/msm/mt9d112.h b/drivers/media/video/msm/mt9d112.h new file mode 100644 index 00000000000..309fcec05b4 --- /dev/null +++ b/drivers/media/video/msm/mt9d112.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MT9D112_H +#define MT9D112_H + +#include +#include + +extern struct mt9d112_reg mt9d112_regs; + +enum mt9d112_width { + WORD_LEN, + BYTE_LEN +}; + +struct mt9d112_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; + enum mt9d112_width width; + unsigned short mdelay_time; +}; + +struct mt9d112_reg { + const struct register_address_value_pair *prev_snap_reg_settings; + uint16_t prev_snap_reg_settings_size; + const struct register_address_value_pair *noise_reduction_reg_settings; + uint16_t noise_reduction_reg_settings_size; + const struct mt9d112_i2c_reg_conf *plltbl; + uint16_t plltbl_size; + const struct mt9d112_i2c_reg_conf *stbl; + uint16_t stbl_size; + const struct mt9d112_i2c_reg_conf *rftbl; + uint16_t rftbl_size; +}; + +#endif /* MT9D112_H */ diff --git a/drivers/media/video/msm/mt9d112_reg.c b/drivers/media/video/msm/mt9d112_reg.c new file mode 100644 index 00000000000..24edaf2b72d --- /dev/null +++ b/drivers/media/video/msm/mt9d112_reg.c @@ -0,0 +1,319 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "mt9d112.h" + + +struct register_address_value_pair const +preview_snapshot_mode_reg_settings_array[] = { + {0x338C, 0x2703}, + {0x3390, 800}, /* Output Width (P) = 640 */ + {0x338C, 0x2705}, + {0x3390, 600}, /* Output Height (P) = 480 */ + {0x338C, 0x2707}, + {0x3390, 0x0640}, /* Output Width (S) = 1600 */ + {0x338C, 0x2709}, + {0x3390, 0x04B0}, /* Output Height (S) = 1200 */ + {0x338C, 0x270D}, + {0x3390, 0x0000}, /* Row Start (P) = 0 */ + {0x338C, 0x270F}, + {0x3390, 0x0000}, /* Column Start (P) = 0 */ + {0x338C, 0x2711}, + {0x3390, 0x04BD}, /* Row End (P) = 1213 */ + {0x338C, 0x2713}, + {0x3390, 0x064D}, /* Column End (P) = 1613 */ + {0x338C, 0x2715}, + {0x3390, 0x0000}, /* Extra Delay (P) = 0 */ + {0x338C, 0x2717}, + {0x3390, 0x2111}, /* Row Speed (P) = 8465 */ + {0x338C, 0x2719}, + {0x3390, 0x046C}, /* Read Mode (P) = 1132 */ + {0x338C, 0x271B}, + {0x3390, 0x024F}, /* Sensor_Sample_Time_pck(P) = 591 */ + {0x338C, 0x271D}, + {0x3390, 0x0102}, /* Sensor_Fine_Correction(P) = 258 */ + {0x338C, 0x271F}, + {0x3390, 0x0279}, /* Sensor_Fine_IT_min(P) = 633 */ + {0x338C, 0x2721}, + {0x3390, 0x0155}, /* Sensor_Fine_IT_max_margin(P) = 341 */ + {0x338C, 0x2723}, + {0x3390, 659}, /* Frame Lines (P) = 679 */ + {0x338C, 0x2725}, + {0x3390, 0x061B}, /* Line Length (P) = 1563 */ + {0x338C, 0x2727}, + {0x3390, 0x2020}, + {0x338C, 0x2729}, + {0x3390, 0x2020}, + {0x338C, 0x272B}, + {0x3390, 0x1020}, + {0x338C, 0x272D}, + {0x3390, 0x2007}, + {0x338C, 0x272F}, + {0x3390, 0x0004}, /* Row Start(S) = 4 */ + {0x338C, 0x2731}, + {0x3390, 0x0004}, /* Column Start(S) = 4 */ + {0x338C, 0x2733}, + {0x3390, 0x04BB}, /* Row End(S) = 1211 */ + {0x338C, 0x2735}, + {0x3390, 0x064B}, /* Column End(S) = 1611 */ + {0x338C, 0x2737}, + {0x3390, 0x04CE}, /* Extra Delay(S) = 1230 */ + {0x338C, 0x2739}, + {0x3390, 0x2111}, /* Row Speed(S) = 8465 */ + {0x338C, 0x273B}, + {0x3390, 0x0024}, /* Read Mode(S) = 36 */ + {0x338C, 0x273D}, + {0x3390, 0x0120}, /* Sensor sample time pck(S) = 288 */ + {0x338C, 0x2741}, + {0x3390, 0x0169}, /* Sensor_Fine_IT_min(P) = 361 */ + {0x338C, 0x2745}, + {0x3390, 0x04FF}, /* Frame Lines(S) = 1279 */ + {0x338C, 0x2747}, + {0x3390, 0x0824}, /* Line Length(S) = 2084 */ + {0x338C, 0x2751}, + {0x3390, 0x0000}, /* Crop_X0(P) = 0 */ + {0x338C, 0x2753}, + {0x3390, 0x0320}, /* Crop_X1(P) = 800 */ + {0x338C, 0x2755}, + {0x3390, 0x0000}, /* Crop_Y0(P) = 0 */ + {0x338C, 0x2757}, + {0x3390, 0x0258}, /* Crop_Y1(P) = 600 */ + {0x338C, 0x275F}, + {0x3390, 0x0000}, /* Crop_X0(S) = 0 */ + {0x338C, 0x2761}, + {0x3390, 0x0640}, /* Crop_X1(S) = 1600 */ + {0x338C, 0x2763}, + {0x3390, 0x0000}, /* Crop_Y0(S) = 0 */ + {0x338C, 0x2765}, + {0x3390, 0x04B0}, /* Crop_Y1(S) = 1200 */ + {0x338C, 0x222E}, + {0x3390, 0x00A0}, /* R9 Step = 160 */ + {0x338C, 0xA408}, + {0x3390, 0x001F}, + {0x338C, 0xA409}, + {0x3390, 0x0021}, + {0x338C, 0xA40A}, + {0x3390, 0x0025}, + {0x338C, 0xA40B}, + {0x3390, 0x0027}, + {0x338C, 0x2411}, + {0x3390, 0x00A0}, + {0x338C, 0x2413}, + {0x3390, 0x00C0}, + {0x338C, 0x2415}, + {0x3390, 0x00A0}, + {0x338C, 0x2417}, + {0x3390, 0x00C0}, + {0x338C, 0x2799}, + {0x3390, 0x6408}, /* MODE_SPEC_EFFECTS(P) */ + {0x338C, 0x279B}, + {0x3390, 0x6408}, /* MODE_SPEC_EFFECTS(S) */ +}; + +static struct register_address_value_pair const +noise_reduction_reg_settings_array[] = { + {0x338C, 0xA76D}, + {0x3390, 0x0003}, + {0x338C, 0xA76E}, + {0x3390, 0x0003}, + {0x338C, 0xA76F}, + {0x3390, 0}, + {0x338C, 0xA770}, + {0x3390, 21}, + {0x338C, 0xA771}, + {0x3390, 37}, + {0x338C, 0xA772}, + {0x3390, 63}, + {0x338C, 0xA773}, + {0x3390, 100}, + {0x338C, 0xA774}, + {0x3390, 128}, + {0x338C, 0xA775}, + {0x3390, 151}, + {0x338C, 0xA776}, + {0x3390, 169}, + {0x338C, 0xA777}, + {0x3390, 186}, + {0x338C, 0xA778}, + {0x3390, 199}, + {0x338C, 0xA779}, + {0x3390, 210}, + {0x338C, 0xA77A}, + {0x3390, 220}, + {0x338C, 0xA77B}, + {0x3390, 228}, + {0x338C, 0xA77C}, + {0x3390, 234}, + {0x338C, 0xA77D}, + {0x3390, 240}, + {0x338C, 0xA77E}, + {0x3390, 244}, + {0x338C, 0xA77F}, + {0x3390, 248}, + {0x338C, 0xA780}, + {0x3390, 252}, + {0x338C, 0xA781}, + {0x3390, 255}, + {0x338C, 0xA782}, + {0x3390, 0}, + {0x338C, 0xA783}, + {0x3390, 21}, + {0x338C, 0xA784}, + {0x3390, 37}, + {0x338C, 0xA785}, + {0x3390, 63}, + {0x338C, 0xA786}, + {0x3390, 100}, + {0x338C, 0xA787}, + {0x3390, 128}, + {0x338C, 0xA788}, + {0x3390, 151}, + {0x338C, 0xA789}, + {0x3390, 169}, + {0x338C, 0xA78A}, + {0x3390, 186}, + {0x338C, 0xA78B}, + {0x3390, 199}, + {0x338C, 0xA78C}, + {0x3390, 210}, + {0x338C, 0xA78D}, + {0x3390, 220}, + {0x338C, 0xA78E}, + {0x3390, 228}, + {0x338C, 0xA78F}, + {0x3390, 234}, + {0x338C, 0xA790}, + {0x3390, 240}, + {0x338C, 0xA791}, + {0x3390, 244}, + {0x338C, 0xA793}, + {0x3390, 252}, + {0x338C, 0xA794}, + {0x3390, 255}, + {0x338C, 0xA103}, + {0x3390, 6}, +}; + +static const struct mt9d112_i2c_reg_conf const lens_roll_off_tbl[] = { + { 0x34CE, 0x81A0, WORD_LEN, 0 }, + { 0x34D0, 0x6331, WORD_LEN, 0 }, + { 0x34D2, 0x3394, WORD_LEN, 0 }, + { 0x34D4, 0x9966, WORD_LEN, 0 }, + { 0x34D6, 0x4B25, WORD_LEN, 0 }, + { 0x34D8, 0x2670, WORD_LEN, 0 }, + { 0x34DA, 0x724C, WORD_LEN, 0 }, + { 0x34DC, 0xFFFD, WORD_LEN, 0 }, + { 0x34DE, 0x00CA, WORD_LEN, 0 }, + { 0x34E6, 0x00AC, WORD_LEN, 0 }, + { 0x34EE, 0x0EE1, WORD_LEN, 0 }, + { 0x34F6, 0x0D87, WORD_LEN, 0 }, + { 0x3500, 0xE1F7, WORD_LEN, 0 }, + { 0x3508, 0x1CF4, WORD_LEN, 0 }, + { 0x3510, 0x1D28, WORD_LEN, 0 }, + { 0x3518, 0x1F26, WORD_LEN, 0 }, + { 0x3520, 0x2220, WORD_LEN, 0 }, + { 0x3528, 0x333D, WORD_LEN, 0 }, + { 0x3530, 0x15D9, WORD_LEN, 0 }, + { 0x3538, 0xCFB8, WORD_LEN, 0 }, + { 0x354C, 0x05FE, WORD_LEN, 0 }, + { 0x3544, 0x05F8, WORD_LEN, 0 }, + { 0x355C, 0x0596, WORD_LEN, 0 }, + { 0x3554, 0x0611, WORD_LEN, 0 }, + { 0x34E0, 0x00F2, WORD_LEN, 0 }, + { 0x34E8, 0x00A8, WORD_LEN, 0 }, + { 0x34F0, 0x0F7B, WORD_LEN, 0 }, + { 0x34F8, 0x0CD7, WORD_LEN, 0 }, + { 0x3502, 0xFEDB, WORD_LEN, 0 }, + { 0x350A, 0x13E4, WORD_LEN, 0 }, + { 0x3512, 0x1F2C, WORD_LEN, 0 }, + { 0x351A, 0x1D20, WORD_LEN, 0 }, + { 0x3522, 0x2422, WORD_LEN, 0 }, + { 0x352A, 0x2925, WORD_LEN, 0 }, + { 0x3532, 0x1D04, WORD_LEN, 0 }, + { 0x353A, 0xFBF2, WORD_LEN, 0 }, + { 0x354E, 0x0616, WORD_LEN, 0 }, + { 0x3546, 0x0597, WORD_LEN, 0 }, + { 0x355E, 0x05CD, WORD_LEN, 0 }, + { 0x3556, 0x0529, WORD_LEN, 0 }, + { 0x34E4, 0x00B2, WORD_LEN, 0 }, + { 0x34EC, 0x005E, WORD_LEN, 0 }, + { 0x34F4, 0x0F43, WORD_LEN, 0 }, + { 0x34FC, 0x0E2F, WORD_LEN, 0 }, + { 0x3506, 0xF9FC, WORD_LEN, 0 }, + { 0x350E, 0x0CE4, WORD_LEN, 0 }, + { 0x3516, 0x1E1E, WORD_LEN, 0 }, + { 0x351E, 0x1B19, WORD_LEN, 0 }, + { 0x3526, 0x151B, WORD_LEN, 0 }, + { 0x352E, 0x1416, WORD_LEN, 0 }, + { 0x3536, 0x10FC, WORD_LEN, 0 }, + { 0x353E, 0xC018, WORD_LEN, 0 }, + { 0x3552, 0x06B4, WORD_LEN, 0 }, + { 0x354A, 0x0506, WORD_LEN, 0 }, + { 0x3562, 0x06AB, WORD_LEN, 0 }, + { 0x355A, 0x063A, WORD_LEN, 0 }, + { 0x34E2, 0x00E5, WORD_LEN, 0 }, + { 0x34EA, 0x008B, WORD_LEN, 0 }, + { 0x34F2, 0x0E4C, WORD_LEN, 0 }, + { 0x34FA, 0x0CA3, WORD_LEN, 0 }, + { 0x3504, 0x0907, WORD_LEN, 0 }, + { 0x350C, 0x1DFD, WORD_LEN, 0 }, + { 0x3514, 0x1E24, WORD_LEN, 0 }, + { 0x351C, 0x2529, WORD_LEN, 0 }, + { 0x3524, 0x1D20, WORD_LEN, 0 }, + { 0x352C, 0x2332, WORD_LEN, 0 }, + { 0x3534, 0x10E9, WORD_LEN, 0 }, + { 0x353C, 0x0BCB, WORD_LEN, 0 }, + { 0x3550, 0x04EF, WORD_LEN, 0 }, + { 0x3548, 0x0609, WORD_LEN, 0 }, + { 0x3560, 0x0580, WORD_LEN, 0 }, + { 0x3558, 0x05DD, WORD_LEN, 0 }, + { 0x3540, 0x0000, WORD_LEN, 0 }, + { 0x3542, 0x0000, WORD_LEN, 0 } +}; + +static const struct mt9d112_i2c_reg_conf const pll_setup_tbl[] = { + { 0x341E, 0x8F09, WORD_LEN, 0 }, + { 0x341C, 0x0250, WORD_LEN, 0 }, + { 0x341E, 0x8F09, WORD_LEN, 5 }, + { 0x341E, 0x8F08, WORD_LEN, 0 } +}; + +/* Refresh Sequencer */ +static const struct mt9d112_i2c_reg_conf const sequencer_tbl[] = { + { 0x338C, 0x2799, WORD_LEN, 0}, + { 0x3390, 0x6440, WORD_LEN, 5}, + { 0x338C, 0x279B, WORD_LEN, 0}, + { 0x3390, 0x6440, WORD_LEN, 5}, + { 0x338C, 0xA103, WORD_LEN, 0}, + { 0x3390, 0x0005, WORD_LEN, 5}, + { 0x338C, 0xA103, WORD_LEN, 0}, + { 0x3390, 0x0006, WORD_LEN, 5} +}; + +struct mt9d112_reg mt9d112_regs = { + .prev_snap_reg_settings = &preview_snapshot_mode_reg_settings_array[0], + .prev_snap_reg_settings_size = ARRAY_SIZE( + preview_snapshot_mode_reg_settings_array), + .noise_reduction_reg_settings = &noise_reduction_reg_settings_array[0], + .noise_reduction_reg_settings_size = ARRAY_SIZE( + noise_reduction_reg_settings_array), + .plltbl = pll_setup_tbl, + .plltbl_size = ARRAY_SIZE(pll_setup_tbl), + .stbl = sequencer_tbl, + .stbl_size = ARRAY_SIZE(sequencer_tbl), + .rftbl = lens_roll_off_tbl, + .rftbl_size = ARRAY_SIZE(lens_roll_off_tbl) +}; + + + diff --git a/drivers/media/video/msm/mt9d113.c b/drivers/media/video/msm/mt9d113.c new file mode 100644 index 00000000000..a6b6a285cdf --- /dev/null +++ b/drivers/media/video/msm/mt9d113.c @@ -0,0 +1,664 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mt9d113.h" + +/* Micron MT9D113 Registers and their values */ +#define REG_MT9D113_MODEL_ID 0x0000 +#define MT9D113_MODEL_ID 0x2580 +#define Q8 0x00000100 + +struct mt9d113_work { + struct work_struct work; +}; + +static struct mt9d113_work *mt9d113_sensorw; +static struct i2c_client *mt9d113_client; + +struct mt9d113_ctrl { + const struct msm_camera_sensor_info *sensordata; + uint32_t sensormode; + uint32_t fps_divider;/* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider;/* init to 1 * 0x00000400 */ + uint16_t fps; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + uint16_t config_csi; + enum mt9d113_resolution_t prev_res; + enum mt9d113_resolution_t pict_res; + enum mt9d113_resolution_t curr_res; + enum mt9d113_test_mode_t set_test; +}; + +static struct mt9d113_ctrl *mt9d113_ctrl; + +static DECLARE_WAIT_QUEUE_HEAD(mt9d113_wait_queue); +DEFINE_MUTEX(mt9d113_mut); + +static int mt9d113_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + if (i2c_transfer(mt9d113_client->adapter, msgs, 2) < 0) { + CDBG("mt9d113_i2c_rxdata failed!\n"); + return -EIO; + } + return 0; +} + +static int32_t mt9d113_i2c_read(unsigned short saddr, + unsigned short raddr, + unsigned short *rdata, + enum mt9d113_width width) +{ + int32_t rc = 0; + unsigned char buf[4]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + switch (width) { + case WORD_LEN: { + buf[0] = (raddr & 0xFF00)>>8; + buf[1] = (raddr & 0x00FF); + rc = mt9d113_i2c_rxdata(saddr, buf, 2); + if (rc < 0) + return rc; + *rdata = buf[0] << 8 | buf[1]; + } + break; + default: + break; + } + if (rc < 0) + CDBG("mt9d113_i2c_read failed !\n"); + return rc; +} + +static int32_t mt9d113_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(mt9d113_client->adapter, msg, 1) < 0) { + CDBG("mt9d113_i2c_txdata failed\n"); + return -EIO; + } + return 0; +} + +static int32_t mt9d113_i2c_write(unsigned short saddr, + unsigned short waddr, + unsigned short wdata, + enum mt9d113_width width) +{ + int32_t rc = -EIO; + unsigned char buf[4]; + memset(buf, 0, sizeof(buf)); + switch (width) { + case WORD_LEN: { + buf[0] = (waddr & 0xFF00)>>8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00)>>8; + buf[3] = (wdata & 0x00FF); + rc = mt9d113_i2c_txdata(saddr, buf, 4); + } + break; + case BYTE_LEN: { + buf[0] = waddr; + buf[1] = wdata; + rc = mt9d113_i2c_txdata(saddr, buf, 2); + } + break; + default: + break; + } + if (rc < 0) + printk(KERN_ERR + "i2c_write failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + return rc; +} + +static int32_t mt9d113_i2c_write_table( + struct mt9d113_i2c_reg_conf + const *reg_conf_tbl, + int num_of_items_in_table) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < num_of_items_in_table; i++) { + rc = mt9d113_i2c_write(mt9d113_client->addr, + reg_conf_tbl->waddr, reg_conf_tbl->wdata, + WORD_LEN); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +static long mt9d113_reg_init(void) +{ + uint16_t data = 0; + int32_t rc = 0; + int count = 0; + struct msm_camera_csi_params mt9d113_csi_params; + if (!mt9d113_ctrl->config_csi) { + mt9d113_csi_params.lane_cnt = 1; + mt9d113_csi_params.data_format = CSI_8BIT; + mt9d113_csi_params.lane_assign = 0xe4; + mt9d113_csi_params.dpcm_scheme = 0; + mt9d113_csi_params.settle_cnt = 0x14; + rc = msm_camio_csi_config(&mt9d113_csi_params); + mt9d113_ctrl->config_csi = 1; + msleep(50); + } + /* Disable parallel and enable mipi*/ + rc = mt9d113_i2c_write(mt9d113_client->addr, + 0x001A, + 0x0051, WORD_LEN); + rc = mt9d113_i2c_write(mt9d113_client->addr, + 0x001A, + 0x0050, + WORD_LEN); + msleep(20); + rc = mt9d113_i2c_write(mt9d113_client->addr, + 0x001A, + 0x0058, + WORD_LEN); + + /* Preset pll settings begin*/ + rc = mt9d113_i2c_write_table(&mt9d113_regs.pll_tbl[0], + mt9d113_regs.pll_tbl_size); + if (rc < 0) + return rc; + rc = mt9d113_i2c_read(mt9d113_client->addr, + 0x0014, &data, WORD_LEN); + data = data&0x8000; + /* Poll*/ + while (data == 0x0000) { + data = 0; + rc = mt9d113_i2c_read(mt9d113_client->addr, + 0x0014, &data, WORD_LEN); + data = data & 0x8000; + usleep_range(11000, 12000); + count++; + if (count == 100) { + CDBG(" Timeout:1\n"); + break; + } + } + rc = mt9d113_i2c_write(mt9d113_client->addr, + 0x0014, + 0x20FA, + WORD_LEN); + + /*Preset pll Ends*/ + mt9d113_i2c_write(mt9d113_client->addr, + 0x0018, + 0x402D, + WORD_LEN); + + mt9d113_i2c_write(mt9d113_client->addr, + 0x0018, + 0x402C, + WORD_LEN); + /*POLL_REG=0x0018,0x4000,!=0x0000,DELAY=10,TIMEOUT=100*/ + data = 0; + rc = mt9d113_i2c_read(mt9d113_client->addr, + 0x0018, &data, WORD_LEN); + data = data & 0x4000; + count = 0; + while (data != 0x0000) { + rc = mt9d113_i2c_read(mt9d113_client->addr, + 0x0018, &data, WORD_LEN); + data = data & 0x4000; + CDBG(" data is %d\n" , data); + usleep_range(11000, 12000); + count++; + if (count == 100) { + CDBG(" Loop2 timeout: MT9D113\n"); + break; + } + CDBG(" Not streaming\n"); + } + CDBG("MT9D113: Start stream\n"); + /*Preset Register Wizard Conf*/ + rc = mt9d113_i2c_write_table(&mt9d113_regs.register_tbl[0], + mt9d113_regs.register_tbl_size); + if (rc < 0) + return rc; + rc = mt9d113_i2c_write_table(&mt9d113_regs.err_tbl[0], + mt9d113_regs.err_tbl_size); + if (rc < 0) + return rc; + rc = mt9d113_i2c_write_table(&mt9d113_regs.eeprom_tbl[0], + mt9d113_regs.eeprom_tbl_size); + if (rc < 0) + return rc; + + rc = mt9d113_i2c_write_table(&mt9d113_regs.low_light_tbl[0], + mt9d113_regs.low_light_tbl_size); + if (rc < 0) + return rc; + + rc = mt9d113_i2c_write_table(&mt9d113_regs.awb_tbl[0], + mt9d113_regs.awb_tbl_size); + if (rc < 0) + return rc; + + rc = mt9d113_i2c_write_table(&mt9d113_regs.patch_tbl[0], + mt9d113_regs.patch_tbl_size); + if (rc < 0) + return rc; + + /*check patch load*/ + mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, + 0xA024, + WORD_LEN); + count = 0; + /*To check if patch is loaded properly + poll the register 0x990 till the condition is + met or till the timeout*/ + data = 0; + rc = mt9d113_i2c_read(mt9d113_client->addr, + 0x0990, &data, WORD_LEN); + while (data == 0) { + data = 0; + rc = mt9d113_i2c_read(mt9d113_client->addr, + 0x0990, &data, WORD_LEN); + usleep_range(11000, 12000); + count++; + if (count == 100) { + CDBG("Timeout in patch loading\n"); + break; + } + } + /*BITFIELD=0x0018, 0x0004, 0*/ + /*Preset continue begin */ + rc = mt9d113_i2c_write(mt9d113_client->addr, 0x0018, 0x0028, + WORD_LEN); + CDBG(" mt9d113 wait for seq done\n"); + /* syncronize the FW with the sensor + MCU_ADDRESS [SEQ_CMD]*/ + rc = mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0xA103, WORD_LEN); + rc = mt9d113_i2c_write(mt9d113_client->addr, + 0x0990, 0x0006, WORD_LEN); + /*mt9d113 wait for seq done + syncronize the FW with the sensor */ + msleep(20); + /*Preset continue end */ + CDBG(" MT9D113: Preset continue end\n"); + rc = mt9d113_i2c_write(mt9d113_client->addr, + 0x0012, + 0x00F5, + WORD_LEN); + /*continue begin */ + CDBG(" MT9D113: Preset continue begin\n"); + rc = mt9d113_i2c_write(mt9d113_client->addr, 0x0018, 0x0028 , + WORD_LEN); + /*mt9d113 wait for seq done + syncronize the FW with the sensor + MCU_ADDRESS [SEQ_CMD]*/ + msleep(20); + rc = mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0xA103, WORD_LEN); + /* MCU DATA */ + rc = mt9d113_i2c_write(mt9d113_client->addr, 0x0990, + 0x0006, WORD_LEN); + /*mt9d113 wait for seq done + syncronize the FW with the sensor */ + /* MCU_ADDRESS [SEQ_CMD]*/ + msleep(20); + /*Preset continue end*/ + return rc; + +} + +static long mt9d113_set_sensor_mode(int mode) +{ + long rc = 0; + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = mt9d113_reg_init(); + CDBG("MT9D113: configure to preview begin\n"); + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0xA115, WORD_LEN); + if (rc < 0) + return rc; + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x0990, 0x0000, WORD_LEN); + if (rc < 0) + return rc; + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0xA103, WORD_LEN); + if (rc < 0) + return rc; + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0x0001, WORD_LEN); + if (rc < 0) + return rc; + break; + case SENSOR_SNAPSHOT_MODE: + case SENSOR_RAW_SNAPSHOT_MODE: + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0xA115, WORD_LEN); + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0x0002, WORD_LEN); + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0xA103, WORD_LEN); + rc = + mt9d113_i2c_write(mt9d113_client->addr, + 0x098C, 0x0002, WORD_LEN); + break; + default: + return -EINVAL; + } + return 0; +} + +static int mt9d113_sensor_init_probe(const struct + msm_camera_sensor_info * data) +{ + uint16_t model_id = 0; + int rc = 0; + /* Read the Model ID of the sensor */ + rc = mt9d113_i2c_read(mt9d113_client->addr, + REG_MT9D113_MODEL_ID, + &model_id, WORD_LEN); + if (rc < 0) + goto init_probe_fail; + /* Check if it matches it with the value in Datasheet */ + if (model_id != MT9D113_MODEL_ID) + printk(KERN_INFO "mt9d113 model_id = 0x%x\n", model_id); + if (rc < 0) + goto init_probe_fail; + return rc; +init_probe_fail: + printk(KERN_INFO "probe fail\n"); + return rc; +} + +static int mt9d113_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&mt9d113_wait_queue); + return 0; +} + +int mt9d113_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cfg_data; + long rc = 0; + + if (copy_from_user(&cfg_data, + (void *)argp, + (sizeof(struct sensor_cfg_data)))) + return -EFAULT; + mutex_lock(&mt9d113_mut); + CDBG("mt9d113_ioctl, cfgtype = %d, mode = %d\n", + cfg_data.cfgtype, cfg_data.mode); + switch (cfg_data.cfgtype) { + case CFG_SET_MODE: + rc = mt9d113_set_sensor_mode( + cfg_data.mode); + break; + case CFG_SET_EFFECT: + return rc; + case CFG_GET_AF_MAX_STEPS: + default: + rc = -EINVAL; + break; + } + mutex_unlock(&mt9d113_mut); + return rc; +} + +int mt9d113_sensor_release(void) +{ + int rc = 0; + + mutex_lock(&mt9d113_mut); + gpio_set_value_cansleep(mt9d113_ctrl->sensordata->sensor_reset, 0); + msleep(20); + gpio_free(mt9d113_ctrl->sensordata->sensor_reset); + kfree(mt9d113_ctrl); + mutex_unlock(&mt9d113_mut); + + return rc; +} + +static int mt9d113_probe_init_done(const struct msm_camera_sensor_info + *data) +{ + gpio_free(data->sensor_reset); + return 0; +} + +static int mt9d113_probe_init_sensor(const struct msm_camera_sensor_info + *data) +{ + int32_t rc = 0; + uint16_t chipid = 0; + rc = gpio_request(data->sensor_pwd, "mt9d113"); + if (!rc) { + printk(KERN_INFO "sensor_reset = %d\n", rc); + gpio_direction_output(data->sensor_pwd, 0); + usleep_range(11000, 12000); + } else { + goto init_probe_done; + } + msleep(20); + rc = gpio_request(data->sensor_reset, "mt9d113"); + printk(KERN_INFO " mt9d113_probe_init_sensor\n"); + if (!rc) { + printk(KERN_INFO "sensor_reset = %d\n", rc); + gpio_direction_output(data->sensor_reset, 0); + usleep_range(11000, 12000); + gpio_set_value_cansleep(data->sensor_reset, 1); + usleep_range(11000, 12000); + } else + goto init_probe_done; + printk(KERN_INFO " mt9d113_probe_init_sensor called\n"); + rc = mt9d113_i2c_read(mt9d113_client->addr, REG_MT9D113_MODEL_ID, + &chipid, 2); + if (rc < 0) + goto init_probe_fail; + /*Compare sensor ID to MT9D113 ID: */ + if (chipid != MT9D113_MODEL_ID) { + printk(KERN_INFO "mt9d113_probe_init_sensor chip idis%d\n", + chipid); + } + CDBG("mt9d113_probe_init_sensor Success\n"); + goto init_probe_done; +init_probe_fail: + CDBG(" ov2720_probe_init_sensor fails\n"); + gpio_set_value_cansleep(data->sensor_reset, 0); + mt9d113_probe_init_done(data); +init_probe_done: + printk(KERN_INFO " mt9d113_probe_init_sensor finishes\n"); + return rc; +} + +static int mt9d113_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + rc = -ENOTSUPP; + goto probe_failure; + } + mt9d113_sensorw = + kzalloc(sizeof(struct mt9d113_work), GFP_KERNEL); + if (!mt9d113_sensorw) { + rc = -ENOMEM; + goto probe_failure; + } + i2c_set_clientdata(client, mt9d113_sensorw); + mt9d113_init_client(client); + mt9d113_client = client; + CDBG("mt9d113_probe succeeded!\n"); + return 0; +probe_failure: + kfree(mt9d113_sensorw); + mt9d113_sensorw = NULL; + CDBG("mt9d113_probe failed!\n"); + return rc; +} + +static const struct i2c_device_id mt9d113_i2c_id[] = { + { "mt9d113", 0}, + {}, +}; + +static struct i2c_driver mt9d113_i2c_driver = { + .id_table = mt9d113_i2c_id, + .probe = mt9d113_i2c_probe, + .remove = __exit_p(mt9d113_i2c_remove), + .driver = { + .name = "mt9d113", + }, +}; + +int mt9d113_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + mt9d113_ctrl = kzalloc(sizeof(struct mt9d113_ctrl), GFP_KERNEL); + if (!mt9d113_ctrl) { + printk(KERN_INFO "mt9d113_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + mt9d113_ctrl->fps_divider = 1 * 0x00000400; + mt9d113_ctrl->pict_fps_divider = 1 * 0x00000400; + mt9d113_ctrl->set_test = TEST_OFF; + mt9d113_ctrl->config_csi = 0; + mt9d113_ctrl->prev_res = QTR_SIZE; + mt9d113_ctrl->pict_res = FULL_SIZE; + mt9d113_ctrl->curr_res = INVALID_SIZE; + if (data) + mt9d113_ctrl->sensordata = data; + if (rc < 0) { + printk(KERN_INFO "mt9d113_sensor_open_init fail\n"); + return rc; + } + /* enable mclk first */ + msm_camio_clk_rate_set(24000000); + msleep(20); + rc = mt9d113_probe_init_sensor(data); + if (rc < 0) + goto init_fail; + mt9d113_ctrl->fps = 30*Q8; + rc = mt9d113_sensor_init_probe(data); + if (rc < 0) { + gpio_set_value_cansleep(data->sensor_reset, 0); + goto init_fail; + } else + printk(KERN_ERR "%s: %d\n", __func__, __LINE__); + goto init_done; +init_fail: + printk(KERN_INFO "init_fail\n"); + mt9d113_probe_init_done(data); +init_done: + CDBG("init_done\n"); + return rc; +} + +static int mt9d113_sensor_probe(const struct msm_camera_sensor_info + *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(&mt9d113_i2c_driver); + if (rc < 0 || mt9d113_client == NULL) { + rc = -ENOTSUPP; + goto probe_fail; + } + msm_camio_clk_rate_set(24000000); + usleep_range(5000, 6000); + rc = mt9d113_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + s->s_init = mt9d113_sensor_open_init; + s->s_release = mt9d113_sensor_release; + s->s_config = mt9d113_sensor_config; + s->s_camera_type = FRONT_CAMERA_2D; + s->s_mount_angle = 0; + gpio_set_value_cansleep(info->sensor_reset, 0); + mt9d113_probe_init_done(info); + return rc; +probe_fail: + printk(KERN_INFO "mt9d113_sensor_probe: SENSOR PROBE FAILS!\n"); + return rc; +} + +static int __mt9d113_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, mt9d113_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __mt9d113_probe, + .driver = { + .name = "msm_camera_mt9d113", + .owner = THIS_MODULE, + }, +}; + +static int __init mt9d113_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(mt9d113_init); + +MODULE_DESCRIPTION("Micron 2MP YUV sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/mt9d113.h b/drivers/media/video/msm/mt9d113.h new file mode 100644 index 00000000000..f22f16c542c --- /dev/null +++ b/drivers/media/video/msm/mt9d113.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MT9D113_H +#define MT9D113_H + +#include +#include + +extern struct mt9d113_reg mt9d113_regs; + +enum mt9d113_width { + WORD_LEN, + BYTE_LEN +}; + +struct mt9d113_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +struct mt9d113_reg { + const struct mt9d113_i2c_reg_conf *pll_tbl; + uint16_t pll_tbl_size; + const struct mt9d113_i2c_reg_conf *register_tbl; + uint16_t register_tbl_size; + const struct mt9d113_i2c_reg_conf *err_tbl; + uint16_t err_tbl_size; + const struct mt9d113_i2c_reg_conf *low_light_tbl; + uint16_t low_light_tbl_size; + const struct mt9d113_i2c_reg_conf *awb_tbl; + uint16_t awb_tbl_size; + const struct mt9d113_i2c_reg_conf *patch_tbl; + uint16_t patch_tbl_size; + const struct mt9d113_i2c_reg_conf *eeprom_tbl ; + uint16_t eeprom_tbl_size ; +}; + +enum mt9d113_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum mt9d113_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; + +enum mt9d113_setting { + RES_PREVIEW, + RES_CAPTURE +}; +#endif /* MT9D113_H */ diff --git a/drivers/media/video/msm/mt9d113_reg.c b/drivers/media/video/msm/mt9d113_reg.c new file mode 100644 index 00000000000..cd5be0f441b --- /dev/null +++ b/drivers/media/video/msm/mt9d113_reg.c @@ -0,0 +1,455 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "mt9d113.h" + +struct mt9d113_i2c_reg_conf const + pll_tbl_settings[] = { + {0x0014, 0x21F9 }, /*PLL control: BYPASS PLL = 8697*/ + {0x0010, 0x0115 }, /*PLL Dividers = 277*/ + {0x0012, 0x0F5 }, /*PLL P Dividers = 245*/ + {0x0014, 0x21FB }, /*PLL control: PLL_ENABLE on = 8699*/ + {0x0014, 0x20FB }, /*PLL control: SEL_LOCK_DET on = 8443*/ +}; + +struct mt9d113_i2c_reg_conf const + register_wizard_settings[] = { + {0x098C, 0x2719}, + {0x0990, 0x005A}, + {0x098C, 0x271B}, + {0x0990, 0x01BE}, + {0x098C, 0x271D}, + {0x0990, 0x0131}, + {0x098C, 0x271F}, + {0x0990, 0x02BB}, + {0x098C, 0x2721}, + {0x0990, 0x0888}, + {0x098C, 0x272F}, + {0x0990, 0x003A}, + {0x098C, 0x2731}, + {0x0990, 0x00F6}, + {0x098C, 0x2733}, + {0x0990, 0x008B}, + {0x098C, 0x2735}, + {0x0990, 0x0521}, + {0x098C, 0x2737}, + {0x0990, 0x0888}, + {0x098C, 0x275F}, + {0x0990, 0x0194}, + {0x098C, 0x2761}, + {0x0990, 0x0014}, + {0x098C, 0xA765}, + {0x0990, 0x0044}, + {0x098C, 0xA24F}, + {0x0990, 0x0028}, + {0x098C, 0xA20E}, + {0x0990, 0x00A0}, + {0x098C, 0xA20C}, + {0x0990, 0x000E}, + {0x098C, 0x2222}, + {0x0990, 0x00A0}, + {0x098C, 0x2212}, + {0x0990, 0x01EE}, + {0x098C, 0xA408}, + {0x0990, 0x0026}, + {0x098C, 0xA409}, + {0x0990, 0x0029}, + {0x098C, 0xA40A}, + {0x0990, 0x002E}, + {0x098C, 0xA40B}, + {0x0990, 0x0031}, + {0x098C, 0x2411}, + {0x0990, 0x00A0}, + {0x098C, 0x2413}, + {0x0990, 0x00C0}, + {0x098C, 0x2415}, + {0x0990, 0x00A0}, + {0x098C, 0x2417}, + {0x0990, 0x00C0}, +}; + +struct mt9d113_i2c_reg_conf const + err_settings[] = { + {0x3084, 0x240C}, + {0x3092, 0x0A4C}, + {0x3094, 0x4C4C}, + {0x3096, 0x4C54}, +}; + +struct mt9d113_i2c_reg_conf const + patch_settings[] = { + {0x098C, 0x0415}, /* MCU_ADDRESS*/ + {0x0990, 0xF601}, + {0x0992, 0x42C1}, + {0x0994, 0x0326}, + {0x0996, 0x11F6}, + {0x0998, 0x0143}, + {0x099A, 0xC104}, + {0x099C, 0x260A}, + {0x099E, 0xCC04}, + {0x098C, 0x0425}, + {0x0990, 0x33BD}, + {0x0992, 0xA362}, + {0x0994, 0xBD04}, + {0x0996, 0x3339}, + {0x0998, 0xC6FF}, + {0x099A, 0xF701}, + {0x099C, 0x6439}, + {0x099E, 0xFE01}, + {0x098C, 0x0435}, + {0x0990, 0x6918}, + {0x0992, 0xCE03}, + {0x0994, 0x25CC}, + {0x0996, 0x0013}, + {0x0998, 0xBDC2}, + {0x099A, 0xB8CC}, + {0x099C, 0x0489}, + {0x099E, 0xFD03}, + {0x098C, 0x0445}, + {0x0990, 0x27CC}, + {0x0992, 0x0325}, + {0x0994, 0xFD01}, + {0x0996, 0x69FE}, + {0x0998, 0x02BD}, + {0x099A, 0x18CE}, + {0x099C, 0x0339}, + {0x099E, 0xCC00}, + {0x098C, 0x0455}, + {0x0990, 0x11BD}, + {0x0992, 0xC2B8}, + {0x0994, 0xCC04}, + {0x0996, 0xC8FD}, + {0x0998, 0x0347}, + {0x099A, 0xCC03}, + {0x099C, 0x39FD}, + {0x099E, 0x02BD}, + {0x098C, 0x0465}, + {0x0990, 0xDE00}, + {0x0992, 0x18CE}, + {0x0994, 0x00C2}, + {0x0996, 0xCC00}, + {0x0998, 0x37BD}, + {0x099A, 0xC2B8}, + {0x099C, 0xCC04}, + {0x099E, 0xEFDD}, + {0x098C, 0x0475}, + {0x0990, 0xE6CC}, + {0x0992, 0x00C2}, + {0x0994, 0xDD00}, + {0x0996, 0xC601}, + {0x0998, 0xF701}, + {0x099A, 0x64C6}, + {0x099C, 0x03F7}, + {0x099E, 0x0165}, + {0x098C, 0x0485}, + {0x0990, 0x7F01}, + {0x0992, 0x6639}, + {0x0994, 0x3C3C}, + {0x0996, 0x3C34}, + {0x0998, 0xCC32}, + {0x099A, 0x3EBD}, + {0x099C, 0xA558}, + {0x099E, 0x30ED}, + {0x098C, 0x0495}, + {0x0990, 0x04BD}, + {0x0992, 0xB2D7}, + {0x0994, 0x30E7}, + {0x0996, 0x06CC}, + {0x0998, 0x323E}, + {0x099A, 0xED00}, + {0x099C, 0xEC04}, + {0x099E, 0xBDA5}, + {0x098C, 0x04A5}, + {0x0990, 0x44CC}, + {0x0992, 0x3244}, + {0x0994, 0xBDA5}, + {0x0996, 0x585F}, + {0x0998, 0x30ED}, + {0x099A, 0x02CC}, + {0x099C, 0x3244}, + {0x099E, 0xED00}, + {0x098C, 0x04B5}, + {0x0990, 0xF601}, + {0x0992, 0xD54F}, + {0x0994, 0xEA03}, + {0x0996, 0xAA02}, + {0x0998, 0xBDA5}, + {0x099A, 0x4430}, + {0x099C, 0xE606}, + {0x099E, 0x3838}, + {0x098C, 0x04C5}, + {0x0990, 0x3831}, + {0x0992, 0x39BD}, + {0x0994, 0xD661}, + {0x0996, 0xF602}, + {0x0998, 0xF4C1}, + {0x099A, 0x0126}, + {0x099C, 0x0BFE}, + {0x099E, 0x02BD}, + {0x098C, 0x04D5}, + {0x0990, 0xEE10}, + {0x0992, 0xFC02}, + {0x0994, 0xF5AD}, + {0x0996, 0x0039}, + {0x0998, 0xF602}, + {0x099A, 0xF4C1}, + {0x099C, 0x0226}, + {0x099E, 0x0AFE}, + {0x098C, 0x04E5}, + {0x0990, 0x02BD}, + {0x0992, 0xEE10}, + {0x0994, 0xFC02}, + {0x0996, 0xF7AD}, + {0x0998, 0x0039}, + {0x099A, 0x3CBD}, + {0x099C, 0xB059}, + {0x099E, 0xCC00}, + {0x098C, 0x04F5}, + {0x0990, 0x28BD}, + {0x0992, 0xA558}, + {0x0994, 0x8300}, + {0x0996, 0x0027}, + {0x0998, 0x0BCC}, + {0x099A, 0x0026}, + {0x099C, 0x30ED}, + {0x099E, 0x00C6}, + {0x098C, 0x0505}, + {0x0990, 0x03BD}, + {0x0992, 0xA544}, + {0x0994, 0x3839}, + {0x098C, 0x2006}, + {0x0990, 0x0415}, + {0x098C, 0xA005}, + {0x0990, 0x0001}, +}; + +struct mt9d113_i2c_reg_conf const + eeprom_settings[] = { + {0x3658, 0x0110}, + {0x365A, 0x1B6D}, + {0x365C, 0x01F2}, + {0x365E, 0xFBCD}, + {0x3660, 0x8C91}, + {0x3680, 0xB9ED}, + {0x3682, 0x0EE}, + {0x3684, 0x256F}, + {0x3686, 0x824F}, + {0x3688, 0xD293}, + {0x36A8, 0x5BF2}, + {0x36AA, 0x1711}, + {0x36AC, 0xA095}, + {0x36AE, 0x642C}, + {0x36B0, 0x0E38}, + {0x36D0, 0x88B0}, + {0x36D2, 0x2EB2}, + {0x36D4, 0x4C74}, + {0x36D6, 0x9F96}, + {0x36D8, 0x9557}, + {0x36F8, 0xCE51}, + {0x36FA, 0xB354}, + {0x36FC, 0x2817}, + {0x36FE, 0x14B8}, + {0x3700, 0xB019}, + {0x364E, 0x0710}, + {0x3650, 0x30ED}, + {0x3652, 0x03F2}, + {0x3654, 0xF12E}, + {0x3656, 0x8492}, + {0x3676, 0xD9AD}, + {0x3678, 0x88D0}, + {0x367A, 0x7DED}, + {0x367C, 0x3E31}, + {0x367E, 0x91B3}, + {0x369E, 0x7032}, + {0x36A0, 0x2791}, + {0x36A2, 0xBB55}, + {0x36A4, 0xAB32}, + {0x36A6, 0x1A58}, + {0x36C6, 0xB50F}, + {0x36C8, 0x0011}, + {0x36CA, 0x6DB4}, + {0x36CC, 0x96F5}, + {0x36CE, 0x9BB7}, + {0x36EE, 0x9353}, + {0x36F0, 0xDF74}, + {0x36F2, 0x04F8}, + {0x36F4, 0x0FD8}, + {0x36F6, 0xA87A}, + {0x3662, 0x0170}, + {0x3664, 0x6F0C}, + {0x3666, 0x0112}, + {0x3668, 0xCBAB}, + {0x366A, 0x9111}, + {0x368A, 0xB38D}, + {0x368C, 0xE96F}, + {0x368E, 0xCC0F}, + {0x3690, 0x5851}, + {0x3692, 0xFDD2}, + {0x36B2, 0x5F92}, + {0x36B4, 0x33B2}, + {0x36B6, 0x9815}, + {0x36B8, 0x86F5}, + {0x36BA, 0x0578}, + {0x36DA, 0xCD90}, + {0x36DC, 0x1131}, + {0x36DE, 0x5275}, + {0x36E0, 0xE855}, + {0x36E2, 0xD037}, + {0x3702, 0xAAD1}, + {0x3704, 0xEB75}, + {0x3706, 0x0CD7}, + {0x3708, 0x2C79}, + {0x370A, 0xE0B9}, + {0x366C, 0x0190}, + {0x366E, 0x1C8D}, + {0x3670, 0x0052}, + {0x3672, 0xD66E}, + {0x3674, 0xF511}, + {0x3694, 0xB54D}, + {0x3696, 0x6E4E}, + {0x3698, 0x142E}, + {0x369A, 0xC190}, + {0x369C, 0xA753}, + {0x36BC, 0x70F2}, + {0x36BE, 0x04F1}, + {0x36C0, 0xBD95}, + {0x36C2, 0x0CEE}, + {0x36C4, 0x1BF8}, + {0x36E4, 0x806F}, + {0x36E6, 0x1672}, + {0x36E8, 0x2DF4}, + {0x36EA, 0x8F16}, + {0x36EC, 0xF776}, + {0x370C, 0xAD73}, + {0x370E, 0xB534}, + {0x3710, 0x0D18}, + {0x3712, 0x6057}, + {0x3714, 0xBD1A}, + {0x3644, 0x0354}, + {0x3642, 0x0234}, + {0x3210, 0x01B8}, +}; + +struct mt9d113_i2c_reg_conf const + awb_settings[] = { + {0x098C, 0x2306}, + {0x0990, 0x0180}, + {0x098C, 0x2308}, + {0x0990, 0xFF00}, + {0x098C, 0x230A}, + {0x0990, 0x0080}, + {0x098C, 0x230C}, + {0x0990, 0xFF66}, + {0x098C, 0x230E}, + {0x0990, 0x0180}, + {0x098C, 0x2310}, + {0x0990, 0xFFEE}, + {0x098C, 0x2312}, + {0x0990, 0xFFCD}, + {0x098C, 0x2314}, + {0x0990, 0xFECD}, + {0x098C, 0x2316}, + {0x0990, 0x019A}, + {0x098C, 0x2318}, + {0x0990, 0x0020}, + {0x098C, 0x231A}, + {0x0990, 0x0033}, + {0x098C, 0x231C}, + {0x0990, 0x0100}, + {0x098C, 0x231E}, + {0x0990, 0xFF9A}, + {0x098C, 0x2320}, + {0x0990, 0x0000}, + {0x098C, 0x2322}, + {0x0990, 0x004D}, + {0x098C, 0x2324}, + {0x0990, 0xFFCD}, + {0x098C, 0x2326}, + {0x0990, 0xFFB8}, + {0x098C, 0x2328}, + {0x0990, 0x004D}, + {0x098C, 0x232A}, + {0x0990, 0x0080}, + {0x098C, 0x232C}, + {0x0990, 0xFF66}, + {0x098C, 0x232E}, + {0x0990, 0x0008}, + {0x098C, 0x2330}, + {0x0990, 0xFFF7}, + {0x098C, 0xA363}, + {0x0990, 0x00D2}, + {0x098C, 0xA364}, + {0x0990, 0x00EE}, + {0x3244, 0x0328}, + {0x323E, 0xC22C}, +}; + +struct mt9d113_i2c_reg_conf const + low_light_setting[] = { + {0x098C, 0x2B28}, + {0x0990, 0x35E8}, + {0x098C, 0x2B2A}, + {0x0990, 0xB3B0}, + {0x098C, 0xAB20}, + {0x0990, 0x004B}, + {0x098C, 0xAB24}, + {0x0990, 0x0000}, + {0x098C, 0xAB25}, + {0x0990, 0x00FF}, + {0x098C, 0xAB30}, + {0x0990, 0x00FF}, + {0x098C, 0xAB31}, + {0x0990, 0x00FF}, + {0x098C, 0xAB32}, + {0x0990, 0x00FF}, + {0x098C, 0xAB33}, + {0x0990, 0x0057}, + {0x098C, 0xAB34}, + {0x0990, 0x0080}, + {0x098C, 0xAB35}, + {0x0990, 0x00FF}, + {0x098C, 0xAB36}, + {0x0990, 0x0014}, + {0x098C, 0xAB37}, + {0x0990, 0x0003}, + {0x098C, 0x2B38}, + {0x0990, 0x32C8}, + {0x098C, 0x2B3A}, + {0x0990, 0x7918}, + {0x098C, 0x2B62}, + {0x0990, 0xFFFE}, + {0x098C, 0x2B64}, + {0x0990, 0xFFFF}, +}; + +struct mt9d113_reg mt9d113_regs = { + .pll_tbl = pll_tbl_settings, + .pll_tbl_size = ARRAY_SIZE( + pll_tbl_settings), + .register_tbl = register_wizard_settings, + .register_tbl_size = ARRAY_SIZE( + register_wizard_settings), + .err_tbl = err_settings, + .err_tbl_size = ARRAY_SIZE(err_settings), + .low_light_tbl = low_light_setting, + .low_light_tbl_size = ARRAY_SIZE(low_light_setting), + .awb_tbl = awb_settings, + .awb_tbl_size = ARRAY_SIZE(awb_settings), + .patch_tbl = patch_settings, + .patch_tbl_size = ARRAY_SIZE(patch_settings), + .eeprom_tbl = eeprom_settings, + .eeprom_tbl_size = ARRAY_SIZE(eeprom_settings), +}; + + + diff --git a/drivers/media/video/msm/mt9e013.c b/drivers/media/video/msm/mt9e013.c new file mode 100644 index 00000000000..94546f476a1 --- /dev/null +++ b/drivers/media/video/msm/mt9e013.c @@ -0,0 +1,1140 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mt9e013.h" +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD_OFF 0x00 +#define GROUPED_PARAMETER_HOLD 0x01 +/* Integration Time */ +#define REG_COARSE_INTEGRATION_TIME 0x3012 +/* Gain */ +#define REG_GLOBAL_GAIN 0x305E +/* PLL registers */ +#define REG_FRAME_LENGTH_LINES 0x0340 +/* Test Pattern */ +#define REG_TEST_PATTERN_MODE 0x0601 +#define REG_VCM_NEW_CODE 0x30F2 + +/*============================================================================ + TYPE DECLARATIONS +============================================================================*/ + +/* 16bit address - 8 bit context register structure */ +#define Q8 0x00000100 +#define Q10 0x00000400 +#define MT9E013_MASTER_CLK_RATE 24000000 + +/* AF Total steps parameters */ +#define MT9E013_TOTAL_STEPS_NEAR_TO_FAR 32 + +uint16_t mt9e013_step_position_table[MT9E013_TOTAL_STEPS_NEAR_TO_FAR+1]; +uint16_t mt9e013_nl_region_boundary1; +uint16_t mt9e013_nl_region_code_per_step1; +uint16_t mt9e013_l_region_code_per_step = 4; +uint16_t mt9e013_damping_threshold = 10; +uint16_t mt9e013_sw_damping_time_wait = 1; + +struct mt9e013_work_t { + struct work_struct work; +}; + +static struct mt9e013_work_t *mt9e013_sensorw; +static struct i2c_client *mt9e013_client; + +struct mt9e013_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + + uint32_t sensormode; + uint32_t fps_divider;/* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider;/* init to 1 * 0x00000400 */ + uint16_t fps; + + uint16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + + enum mt9e013_resolution_t prev_res; + enum mt9e013_resolution_t pict_res; + enum mt9e013_resolution_t curr_res; + enum mt9e013_test_mode_t set_test; +}; + + +static bool CSI_CONFIG; +static struct mt9e013_ctrl_t *mt9e013_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(mt9e013_wait_queue); +DEFINE_MUTEX(mt9e013_mut); + +static int cam_debug_init(void); +static struct dentry *debugfs_base; +/*=============================================================*/ + +static int mt9e013_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 2, + .buf = rxdata, + }, + }; + if (i2c_transfer(mt9e013_client->adapter, msgs, 2) < 0) { + CDBG("mt9e013_i2c_rxdata faild 0x%x\n", saddr); + return -EIO; + } + return 0; +} + +static int32_t mt9e013_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(mt9e013_client->adapter, msg, 1) < 0) { + CDBG("mt9e013_i2c_txdata faild 0x%x\n", saddr); + return -EIO; + } + + return 0; +} + +static int32_t mt9e013_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = mt9e013_i2c_rxdata(mt9e013_client->addr<<1, buf, rlen); + if (rc < 0) { + CDBG("mt9e013_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + CDBG("mt9e013_i2c_read 0x%x val = 0x%x!\n", raddr, *rdata); + return rc; +} + +static int32_t mt9e013_i2c_write_w_sensor(unsigned short waddr, uint16_t wdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[4]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, wdata); + rc = mt9e013_i2c_txdata(mt9e013_client->addr<<1, buf, 4); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + } + return rc; +} + +static int32_t mt9e013_i2c_write_b_sensor(unsigned short waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = mt9e013_i2c_txdata(mt9e013_client->addr<<1, buf, 3); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + return rc; +} + +static int32_t mt9e013_i2c_write_w_table(struct mt9e013_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < num; i++) { + rc = mt9e013_i2c_write_w_sensor(reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +static void mt9e013_group_hold_on(void) +{ + mt9e013_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); +} + +static void mt9e013_group_hold_off(void) +{ + mt9e013_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF); +} + +static void mt9e013_start_stream(void) +{ + mt9e013_i2c_write_w_sensor(0x301A, 0x8250); + mt9e013_i2c_write_w_sensor(0x301A, 0x8650); + mt9e013_i2c_write_w_sensor(0x301A, 0x8658); + mt9e013_i2c_write_b_sensor(0x0104, 0x00); + mt9e013_i2c_write_w_sensor(0x301A, 0x065C); +} + +static void mt9e013_stop_stream(void) +{ + mt9e013_i2c_write_w_sensor(0x301A, 0x0058); + mt9e013_i2c_write_w_sensor(0x301A, 0x0050); + mt9e013_i2c_write_b_sensor(0x0104, 0x01); +} + +static void mt9e013_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider, d1, d2; + + d1 = mt9e013_regs.reg_prev[E013_FRAME_LENGTH_LINES].wdata + * 0x00000400/ + mt9e013_regs.reg_snap[E013_FRAME_LENGTH_LINES].wdata; + d2 = mt9e013_regs.reg_prev[E013_LINE_LENGTH_PCK].wdata + * 0x00000400/ + mt9e013_regs.reg_snap[E013_LINE_LENGTH_PCK].wdata; + divider = d1 * d2 / 0x400; + + /*Verify PCLK settings and frame sizes.*/ + *pfps = (uint16_t) (fps * divider / 0x400); + /* 2 is the ratio of no.of snapshot channels + to number of preview channels */ +} + +static uint16_t mt9e013_get_prev_lines_pf(void) +{ + if (mt9e013_ctrl->prev_res == QTR_SIZE) + return mt9e013_regs.reg_prev[E013_FRAME_LENGTH_LINES].wdata; + else if (mt9e013_ctrl->prev_res == FULL_SIZE) + return mt9e013_regs.reg_snap[E013_FRAME_LENGTH_LINES].wdata; + else if (mt9e013_ctrl->prev_res == HFR_60FPS) + return mt9e013_regs.reg_60fps[E013_FRAME_LENGTH_LINES].wdata; + else if (mt9e013_ctrl->prev_res == HFR_90FPS) + return mt9e013_regs.reg_120fps[E013_FRAME_LENGTH_LINES].wdata; + else + return mt9e013_regs.reg_120fps[E013_FRAME_LENGTH_LINES].wdata; +} + +static uint16_t mt9e013_get_prev_pixels_pl(void) +{ + if (mt9e013_ctrl->prev_res == QTR_SIZE) + return mt9e013_regs.reg_prev[E013_LINE_LENGTH_PCK].wdata; + else if (mt9e013_ctrl->prev_res == FULL_SIZE) + return mt9e013_regs.reg_snap[E013_LINE_LENGTH_PCK].wdata; + else if (mt9e013_ctrl->prev_res == HFR_60FPS) + return mt9e013_regs.reg_60fps[E013_LINE_LENGTH_PCK].wdata; + else if (mt9e013_ctrl->prev_res == HFR_90FPS) + return mt9e013_regs.reg_120fps[E013_LINE_LENGTH_PCK].wdata; + else + return mt9e013_regs.reg_120fps[E013_LINE_LENGTH_PCK].wdata; +} + +static uint16_t mt9e013_get_pict_lines_pf(void) +{ + if (mt9e013_ctrl->pict_res == QTR_SIZE) + return mt9e013_regs.reg_prev[E013_FRAME_LENGTH_LINES].wdata; + else if (mt9e013_ctrl->pict_res == FULL_SIZE) + return mt9e013_regs.reg_snap[E013_FRAME_LENGTH_LINES].wdata; + else if (mt9e013_ctrl->pict_res == HFR_60FPS) + return mt9e013_regs.reg_60fps[E013_FRAME_LENGTH_LINES].wdata; + else if (mt9e013_ctrl->pict_res == HFR_90FPS) + return mt9e013_regs.reg_120fps[E013_FRAME_LENGTH_LINES].wdata; + else + return mt9e013_regs.reg_120fps[E013_FRAME_LENGTH_LINES].wdata; +} + +static uint16_t mt9e013_get_pict_pixels_pl(void) +{ + if (mt9e013_ctrl->pict_res == QTR_SIZE) + return mt9e013_regs.reg_prev[E013_LINE_LENGTH_PCK].wdata; + else if (mt9e013_ctrl->pict_res == FULL_SIZE) + return mt9e013_regs.reg_snap[E013_LINE_LENGTH_PCK].wdata; + else if (mt9e013_ctrl->pict_res == HFR_60FPS) + return mt9e013_regs.reg_60fps[E013_LINE_LENGTH_PCK].wdata; + else if (mt9e013_ctrl->pict_res == HFR_90FPS) + return mt9e013_regs.reg_120fps[E013_LINE_LENGTH_PCK].wdata; + else + return mt9e013_regs.reg_120fps[E013_LINE_LENGTH_PCK].wdata; +} + +static uint32_t mt9e013_get_pict_max_exp_lc(void) +{ + if (mt9e013_ctrl->pict_res == QTR_SIZE) + return mt9e013_regs.reg_prev[E013_FRAME_LENGTH_LINES].wdata + * 24; + else if (mt9e013_ctrl->pict_res == FULL_SIZE) + return mt9e013_regs.reg_snap[E013_FRAME_LENGTH_LINES].wdata + * 24; + else if (mt9e013_ctrl->pict_res == HFR_60FPS) + return mt9e013_regs.reg_60fps[E013_FRAME_LENGTH_LINES].wdata + * 24; + else if (mt9e013_ctrl->pict_res == HFR_90FPS) + return mt9e013_regs.reg_120fps[E013_FRAME_LENGTH_LINES].wdata + * 24; + else + return mt9e013_regs.reg_120fps[E013_FRAME_LENGTH_LINES].wdata + * 24; +} + +static int32_t mt9e013_set_fps(struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + if (mt9e013_ctrl->curr_res == QTR_SIZE) + total_lines_per_frame = + mt9e013_regs.reg_prev[E013_FRAME_LENGTH_LINES].wdata; + else if (mt9e013_ctrl->curr_res == FULL_SIZE) + total_lines_per_frame = + mt9e013_regs.reg_snap[E013_FRAME_LENGTH_LINES].wdata; + else if (mt9e013_ctrl->curr_res == HFR_60FPS) + total_lines_per_frame = + mt9e013_regs.reg_60fps[E013_FRAME_LENGTH_LINES].wdata; + else if (mt9e013_ctrl->curr_res == HFR_90FPS) + total_lines_per_frame = + mt9e013_regs.reg_120fps[E013_FRAME_LENGTH_LINES].wdata; + else + total_lines_per_frame = + mt9e013_regs.reg_120fps[E013_FRAME_LENGTH_LINES].wdata; + + mt9e013_ctrl->fps_divider = fps->fps_div; + mt9e013_ctrl->pict_fps_divider = fps->pict_fps_div; + + if (mt9e013_ctrl->curr_res == FULL_SIZE) { + total_lines_per_frame = (uint16_t) + (total_lines_per_frame * mt9e013_ctrl->pict_fps_divider/0x400); + } else { + total_lines_per_frame = (uint16_t) + (total_lines_per_frame * mt9e013_ctrl->fps_divider/0x400); + } + + mt9e013_group_hold_on(); + rc = mt9e013_i2c_write_w_sensor(REG_FRAME_LENGTH_LINES, + total_lines_per_frame); + mt9e013_group_hold_off(); + return rc; +} + +static int32_t mt9e013_write_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t max_legal_gain = 0xE7F; + int32_t rc = 0; + if (gain > max_legal_gain) { + CDBG("Max legal gain Line:%d\n", __LINE__); + gain = max_legal_gain; + } + + if (mt9e013_ctrl->curr_res != FULL_SIZE) { + mt9e013_ctrl->my_reg_gain = gain; + mt9e013_ctrl->my_reg_line_count = (uint16_t) line; + line = (uint32_t) (line * mt9e013_ctrl->fps_divider / + 0x00000400); + } else { + line = (uint32_t) (line * mt9e013_ctrl->pict_fps_divider / + 0x00000400); + } + + gain |= 0x1000; + + mt9e013_group_hold_on(); + rc = mt9e013_i2c_write_w_sensor(REG_GLOBAL_GAIN, gain); + rc = mt9e013_i2c_write_w_sensor(REG_COARSE_INTEGRATION_TIME, line); + mt9e013_group_hold_off(); + return rc; +} + +static int32_t mt9e013_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + rc = mt9e013_write_exp_gain(gain, line); + mt9e013_i2c_write_w_sensor(0x301A, 0x065C|0x2); + return rc; +} + +#define DIV_CEIL(x, y) (x/y + (x%y) ? 1 : 0) + +static int32_t mt9e013_move_focus(int direction, + int32_t num_steps) +{ + int16_t step_direction, dest_lens_position, dest_step_position; + int16_t target_dist, small_step, next_lens_position; + if (direction == MOVE_NEAR) + step_direction = 1; + else + step_direction = -1; + + dest_step_position = mt9e013_ctrl->curr_step_pos + + (step_direction * num_steps); + + if (dest_step_position < 0) + dest_step_position = 0; + else if (dest_step_position > MT9E013_TOTAL_STEPS_NEAR_TO_FAR) + dest_step_position = MT9E013_TOTAL_STEPS_NEAR_TO_FAR; + + if (dest_step_position == mt9e013_ctrl->curr_step_pos) + return 0; + + dest_lens_position = mt9e013_step_position_table[dest_step_position]; + target_dist = step_direction * + (dest_lens_position - mt9e013_ctrl->curr_lens_pos); + + if (step_direction < 0 && (target_dist >= + mt9e013_step_position_table[mt9e013_damping_threshold])) { + small_step = DIV_CEIL(target_dist, 10); + mt9e013_sw_damping_time_wait = 10; + } else { + small_step = DIV_CEIL(target_dist, 4); + mt9e013_sw_damping_time_wait = 4; + } + + for (next_lens_position = mt9e013_ctrl->curr_lens_pos + + (step_direction * small_step); + (step_direction * next_lens_position) <= + (step_direction * dest_lens_position); + next_lens_position += (step_direction * small_step)) { + mt9e013_i2c_write_w_sensor(REG_VCM_NEW_CODE, + next_lens_position); + mt9e013_ctrl->curr_lens_pos = next_lens_position; + usleep(mt9e013_sw_damping_time_wait*50); + } + + if (mt9e013_ctrl->curr_lens_pos != dest_lens_position) { + mt9e013_i2c_write_w_sensor(REG_VCM_NEW_CODE, + dest_lens_position); + usleep(mt9e013_sw_damping_time_wait*50); + } + mt9e013_ctrl->curr_lens_pos = dest_lens_position; + mt9e013_ctrl->curr_step_pos = dest_step_position; + return 0; +} + +static int32_t mt9e013_set_default_focus(uint8_t af_step) +{ + int32_t rc = 0; + if (mt9e013_ctrl->curr_step_pos != 0) { + rc = mt9e013_move_focus(MOVE_FAR, + mt9e013_ctrl->curr_step_pos); + } else { + mt9e013_i2c_write_w_sensor(REG_VCM_NEW_CODE, 0x00); + } + + mt9e013_ctrl->curr_lens_pos = 0; + mt9e013_ctrl->curr_step_pos = 0; + + return rc; +} + +static void mt9e013_init_focus(void) +{ + uint8_t i; + mt9e013_step_position_table[0] = 0; + for (i = 1; i <= MT9E013_TOTAL_STEPS_NEAR_TO_FAR; i++) { + if (i <= mt9e013_nl_region_boundary1) { + mt9e013_step_position_table[i] = + mt9e013_step_position_table[i-1] + + mt9e013_nl_region_code_per_step1; + } else { + mt9e013_step_position_table[i] = + mt9e013_step_position_table[i-1] + + mt9e013_l_region_code_per_step; + } + + if (mt9e013_step_position_table[i] > 255) + mt9e013_step_position_table[i] = 255; + } +} + +static int32_t mt9e013_test(enum mt9e013_test_mode_t mo) +{ + int32_t rc = 0; + if (mo == TEST_OFF) + return rc; + else { + /* REG_0x30D8[4] is TESBYPEN: 0: Normal Operation, + 1: Bypass Signal Processing + REG_0x30D8[5] is EBDMASK: 0: + Output Embedded data, 1: No output embedded data */ + if (mt9e013_i2c_write_b_sensor(REG_TEST_PATTERN_MODE, + (uint8_t) mo) < 0) { + return rc; + } + } + return rc; +} + +static int32_t mt9e013_sensor_setting(int update_type, int rt) +{ + + int32_t rc = 0; + struct msm_camera_csi_params mt9e013_csi_params; + uint8_t stored_af_step = 0; + CDBG("sensor_settings\n"); + stored_af_step = mt9e013_ctrl->curr_step_pos; + mt9e013_set_default_focus(0); + mt9e013_stop_stream(); + msleep(15); + if (update_type == REG_INIT) { + mt9e013_i2c_write_w_table(mt9e013_regs.reg_mipi, + mt9e013_regs.reg_mipi_size); + mt9e013_i2c_write_w_table(mt9e013_regs.rec_settings, + mt9e013_regs.rec_size); + cam_debug_init(); + CSI_CONFIG = 0; + } else if (update_type == UPDATE_PERIODIC) { + if (rt == QTR_SIZE) { + mt9e013_i2c_write_w_table(mt9e013_regs.reg_pll, + mt9e013_regs.reg_pll_size); + mt9e013_i2c_write_w_table(mt9e013_regs.reg_prev, + mt9e013_regs.reg_prev_size); + } else if (rt == FULL_SIZE) { + mt9e013_i2c_write_w_table(mt9e013_regs.reg_pll, + mt9e013_regs.reg_pll_size); + mt9e013_i2c_write_w_table(mt9e013_regs.reg_snap, + mt9e013_regs.reg_snap_size); + } else if (rt == HFR_60FPS) { + mt9e013_i2c_write_w_table(mt9e013_regs.reg_pll_120fps, + mt9e013_regs.reg_pll_120fps_size); + mt9e013_i2c_write_w_sensor(0x0306, 0x0029); + mt9e013_i2c_write_w_table(mt9e013_regs.reg_120fps, + mt9e013_regs.reg_120fps_size); + } else if (rt == HFR_90FPS) { + mt9e013_i2c_write_w_table(mt9e013_regs.reg_pll_120fps, + mt9e013_regs.reg_pll_120fps_size); + mt9e013_i2c_write_w_sensor(0x0306, 0x003D); + mt9e013_i2c_write_w_table(mt9e013_regs.reg_120fps, + mt9e013_regs.reg_120fps_size); + } else if (rt == HFR_120FPS) { + msm_camio_vfe_clk_rate_set(266667000); + mt9e013_i2c_write_w_table(mt9e013_regs.reg_pll_120fps, + mt9e013_regs.reg_pll_120fps_size); + mt9e013_i2c_write_w_table(mt9e013_regs.reg_120fps, + mt9e013_regs.reg_120fps_size); + } + if (!CSI_CONFIG) { + msm_camio_vfe_clk_rate_set(192000000); + mt9e013_csi_params.data_format = CSI_10BIT; + mt9e013_csi_params.lane_cnt = 2; + mt9e013_csi_params.lane_assign = 0xe4; + mt9e013_csi_params.dpcm_scheme = 0; + mt9e013_csi_params.settle_cnt = 0x18; + rc = msm_camio_csi_config(&mt9e013_csi_params); + msleep(10); + CSI_CONFIG = 1; + } + mt9e013_move_focus(MOVE_NEAR, stored_af_step); + mt9e013_start_stream(); + } + return rc; +} + +static int32_t mt9e013_video_config(int mode) +{ + + int32_t rc = 0; + + CDBG("video config\n"); + /* change sensor resolution if needed */ + if (mt9e013_sensor_setting(UPDATE_PERIODIC, + mt9e013_ctrl->prev_res) < 0) + return rc; + if (mt9e013_ctrl->set_test) { + if (mt9e013_test(mt9e013_ctrl->set_test) < 0) + return rc; + } + + mt9e013_ctrl->curr_res = mt9e013_ctrl->prev_res; + mt9e013_ctrl->sensormode = mode; + return rc; +} + +static int32_t mt9e013_snapshot_config(int mode) +{ + int32_t rc = 0; + /*change sensor resolution if needed */ + if (mt9e013_ctrl->curr_res != mt9e013_ctrl->pict_res) { + if (mt9e013_sensor_setting(UPDATE_PERIODIC, + mt9e013_ctrl->pict_res) < 0) + return rc; + } + + mt9e013_ctrl->curr_res = mt9e013_ctrl->pict_res; + mt9e013_ctrl->sensormode = mode; + return rc; +} /*end of mt9e013_snapshot_config*/ + +static int32_t mt9e013_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + /* change sensor resolution if needed */ + if (mt9e013_ctrl->curr_res != mt9e013_ctrl->pict_res) { + if (mt9e013_sensor_setting(UPDATE_PERIODIC, + mt9e013_ctrl->pict_res) < 0) + return rc; + } + + mt9e013_ctrl->curr_res = mt9e013_ctrl->pict_res; + mt9e013_ctrl->sensormode = mode; + return rc; +} /*end of mt9e013_raw_snapshot_config*/ + +static int32_t mt9e013_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + switch (mode) { + case SENSOR_PREVIEW_MODE: + case SENSOR_HFR_60FPS_MODE: + case SENSOR_HFR_90FPS_MODE: + case SENSOR_HFR_120FPS_MODE: + mt9e013_ctrl->prev_res = res; + rc = mt9e013_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + mt9e013_ctrl->pict_res = res; + rc = mt9e013_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + mt9e013_ctrl->pict_res = res; + rc = mt9e013_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +static int32_t mt9e013_power_down(void) +{ + return 0; +} + +static int mt9e013_probe_init_done(const struct msm_camera_sensor_info *data) +{ + CDBG("probe done\n"); + gpio_free(data->sensor_reset); + return 0; +} + +static int mt9e013_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + uint16_t chipid = 0; + CDBG("%s: %d\n", __func__, __LINE__); + rc = gpio_request(data->sensor_reset, "mt9e013"); + CDBG(" mt9e013_probe_init_sensor\n"); + if (!rc) { + CDBG("sensor_reset = %d\n", rc); + gpio_direction_output(data->sensor_reset, 0); + msleep(10); + gpio_set_value_cansleep(data->sensor_reset, 1); + msleep(10); + } else { + goto init_probe_done; + } + + CDBG(" mt9e013_probe_init_sensor is called\n"); + rc = mt9e013_i2c_read(0x0000, &chipid, 2); + CDBG("ID: %d\n", chipid); + /* 4. Compare sensor ID to MT9E013 ID: */ + if (chipid != 0x4B00) { + rc = -ENODEV; + CDBG("mt9e013_probe_init_sensor fail chip id doesnot match\n"); + goto init_probe_fail; + } + + mt9e013_ctrl = kzalloc(sizeof(struct mt9e013_ctrl_t), GFP_KERNEL); + if (!mt9e013_ctrl) { + CDBG("mt9e013_init failed!\n"); + rc = -ENOMEM; + } + mt9e013_ctrl->fps_divider = 1 * 0x00000400; + mt9e013_ctrl->pict_fps_divider = 1 * 0x00000400; + mt9e013_ctrl->set_test = TEST_OFF; + mt9e013_ctrl->prev_res = QTR_SIZE; + mt9e013_ctrl->pict_res = FULL_SIZE; + + if (data) + mt9e013_ctrl->sensordata = data; + + goto init_probe_done; +init_probe_fail: + CDBG(" mt9e013_probe_init_sensor fails\n"); + gpio_set_value_cansleep(data->sensor_reset, 0); + mt9e013_probe_init_done(data); +init_probe_done: + CDBG(" mt9e013_probe_init_sensor finishes\n"); + return rc; +} +/* camsensor_mt9e013_reset */ + +int mt9e013_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + CDBG("Calling mt9e013_sensor_open_init\n"); + + mt9e013_ctrl = kzalloc(sizeof(struct mt9e013_ctrl_t), GFP_KERNEL); + if (!mt9e013_ctrl) { + CDBG("mt9e013_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + mt9e013_ctrl->fps_divider = 1 * 0x00000400; + mt9e013_ctrl->pict_fps_divider = 1 * 0x00000400; + mt9e013_ctrl->set_test = TEST_OFF; + mt9e013_ctrl->prev_res = QTR_SIZE; + mt9e013_ctrl->pict_res = FULL_SIZE; + + if (data) + mt9e013_ctrl->sensordata = data; + if (rc < 0) { + CDBG("Calling mt9e013_sensor_open_init fail1\n"); + return rc; + } + CDBG("%s: %d\n", __func__, __LINE__); + /* enable mclk first */ + msm_camio_clk_rate_set(MT9E013_MASTER_CLK_RATE); + rc = mt9e013_probe_init_sensor(data); + if (rc < 0) + goto init_fail; + + CDBG("init settings\n"); + rc = mt9e013_sensor_setting(REG_INIT, mt9e013_ctrl->prev_res); + mt9e013_ctrl->fps = 30*Q8; + mt9e013_init_focus(); + if (rc < 0) { + gpio_set_value_cansleep(data->sensor_reset, 0); + goto init_fail; + } else + goto init_done; +init_fail: + CDBG("init_fail\n"); + mt9e013_probe_init_done(data); +init_done: + CDBG("init_done\n"); + return rc; +} /*endof mt9e013_sensor_open_init*/ + +static int mt9e013_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&mt9e013_wait_queue); + return 0; +} + +static const struct i2c_device_id mt9e013_i2c_id[] = { + {"mt9e013", 0}, + { } +}; + +static int mt9e013_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("mt9e013_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + mt9e013_sensorw = kzalloc(sizeof(struct mt9e013_work_t), GFP_KERNEL); + if (!mt9e013_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, mt9e013_sensorw); + mt9e013_init_client(client); + mt9e013_client = client; + + + CDBG("mt9e013_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("mt9e013_probe failed! rc = %d\n", rc); + return rc; +} + +static int mt9e013_send_wb_info(struct wb_info_cfg *wb) +{ + return 0; + +} /*end of mt9e013_snapshot_config*/ + +static int __exit mt9e013_remove(struct i2c_client *client) +{ + struct mt9e013_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + mt9e013_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver mt9e013_i2c_driver = { + .id_table = mt9e013_i2c_id, + .probe = mt9e013_i2c_probe, + .remove = __exit_p(mt9e013_i2c_remove), + .driver = { + .name = "mt9e013", + }, +}; + +int mt9e013_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&mt9e013_mut); + CDBG("mt9e013_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + mt9e013_get_pict_fps( + cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + mt9e013_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + mt9e013_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + mt9e013_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + mt9e013_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + mt9e013_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = mt9e013_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = + mt9e013_write_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + rc = + mt9e013_set_pict_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = mt9e013_set_sensor_mode(cdata.mode, + cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = mt9e013_power_down(); + break; + + case CFG_MOVE_FOCUS: + rc = + mt9e013_move_focus( + cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = + mt9e013_set_default_focus( + cdata.cfg.focus.steps); + break; + + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = MT9E013_TOTAL_STEPS_NEAR_TO_FAR; + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_EFFECT: + rc = mt9e013_set_default_focus( + cdata.cfg.effect); + break; + + + case CFG_SEND_WB_INFO: + rc = mt9e013_send_wb_info( + &(cdata.cfg.wb_info)); + break; + + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&mt9e013_mut); + + return rc; +} + +static int mt9e013_sensor_release(void) +{ + int rc = -EBADF; + mutex_lock(&mt9e013_mut); + mt9e013_power_down(); + gpio_set_value_cansleep(mt9e013_ctrl->sensordata->sensor_reset, 0); + msleep(5); + gpio_free(mt9e013_ctrl->sensordata->sensor_reset); + kfree(mt9e013_ctrl); + mt9e013_ctrl = NULL; + CDBG("mt9e013_release completed\n"); + mutex_unlock(&mt9e013_mut); + + return rc; +} + +static int mt9e013_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(&mt9e013_i2c_driver); + if (rc < 0 || mt9e013_client == NULL) { + rc = -ENOTSUPP; + CDBG("I2C add driver failed"); + goto probe_fail; + } + msm_camio_clk_rate_set(MT9E013_MASTER_CLK_RATE); + rc = mt9e013_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + s->s_init = mt9e013_sensor_open_init; + s->s_release = mt9e013_sensor_release; + s->s_config = mt9e013_sensor_config; + s->s_mount_angle = info->sensor_platform_info->mount_angle; + gpio_set_value_cansleep(info->sensor_reset, 0); + mt9e013_probe_init_done(info); + return rc; + +probe_fail: + CDBG("mt9e013_sensor_probe: SENSOR PROBE FAILS!\n"); + return rc; +} + +static int __mt9e013_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, mt9e013_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __mt9e013_probe, + .driver = { + .name = "msm_camera_mt9e013", + .owner = THIS_MODULE, + }, +}; + +static int __init mt9e013_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(mt9e013_init); +void mt9e013_exit(void) +{ + i2c_del_driver(&mt9e013_i2c_driver); +} +MODULE_DESCRIPTION("Aptina 8 MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + +static bool streaming = 1; + +static int mt9e013_focus_test(void *data, u64 *val) +{ + int i = 0; + mt9e013_set_default_focus(0); + + for (i = 90; i < 256; i++) { + mt9e013_i2c_write_w_sensor(REG_VCM_NEW_CODE, i); + msleep(5000); + } + msleep(5000); + for (i = 255; i > 90; i--) { + mt9e013_i2c_write_w_sensor(REG_VCM_NEW_CODE, i); + msleep(5000); + } + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(cam_focus, mt9e013_focus_test, + NULL, "%lld\n"); + +static int mt9e013_step_test(void *data, u64 *val) +{ + int i = 0; + mt9e013_set_default_focus(0); + + for (i = 0; i < MT9E013_TOTAL_STEPS_NEAR_TO_FAR; i++) { + mt9e013_move_focus(MOVE_NEAR, 1); + msleep(5000); + } + + mt9e013_move_focus(MOVE_FAR, MT9E013_TOTAL_STEPS_NEAR_TO_FAR); + msleep(5000); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(cam_step, mt9e013_step_test, + NULL, "%lld\n"); + +static int cam_debug_stream_set(void *data, u64 val) +{ + int rc = 0; + + if (val) { + mt9e013_start_stream(); + streaming = 1; + } else { + mt9e013_stop_stream(); + streaming = 0; + } + + return rc; +} + +static int cam_debug_stream_get(void *data, u64 *val) +{ + *val = streaming; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(cam_stream, cam_debug_stream_get, + cam_debug_stream_set, "%llu\n"); + + +static int cam_debug_init(void) +{ + struct dentry *cam_dir; + debugfs_base = debugfs_create_dir("sensor", NULL); + if (!debugfs_base) + return -ENOMEM; + + cam_dir = debugfs_create_dir("mt9e013", debugfs_base); + if (!cam_dir) + return -ENOMEM; + + if (!debugfs_create_file("focus", S_IRUGO | S_IWUSR, cam_dir, + NULL, &cam_focus)) + return -ENOMEM; + if (!debugfs_create_file("step", S_IRUGO | S_IWUSR, cam_dir, + NULL, &cam_step)) + return -ENOMEM; + if (!debugfs_create_file("stream", S_IRUGO | S_IWUSR, cam_dir, + NULL, &cam_stream)) + return -ENOMEM; + + return 0; +} + + + diff --git a/drivers/media/video/msm/mt9e013.h b/drivers/media/video/msm/mt9e013.h new file mode 100644 index 00000000000..9052a354453 --- /dev/null +++ b/drivers/media/video/msm/mt9e013.h @@ -0,0 +1,174 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MT9E013_H +#define MT9E013_H +#include +#include +extern struct mt9e013_reg mt9e013_regs; +struct reg_struct_init { + uint8_t reg_0x0112; /* 0x0112*/ + uint8_t reg_0x0113; /* 0x0113*/ + uint8_t vt_pix_clk_div; /* 0x0301*/ + uint8_t pre_pll_clk_div; /* 0x0305*/ + uint8_t pll_multiplier; /* 0x0307*/ + uint8_t op_pix_clk_div; /* 0x0309*/ + uint8_t reg_0x3030; /*0x3030*/ + uint8_t reg_0x0111; /*0x0111*/ + uint8_t reg_0x0b00; /*0x0b00*/ + uint8_t reg_0x3001; /*0x3001*/ + uint8_t reg_0x3004; /*0x3004*/ + uint8_t reg_0x3007; /*0x3007*/ + uint8_t reg_0x3016; /*0x3016*/ + uint8_t reg_0x301d; /*0x301d*/ + uint8_t reg_0x317e; /*0x317E*/ + uint8_t reg_0x317f; /*0x317F*/ + uint8_t reg_0x3400; /*0x3400*/ + uint8_t reg_0x0b06; /*0x0b06*/ + uint8_t reg_0x0b07; /*0x0b07*/ + uint8_t reg_0x0b08; /*0x0b08*/ + uint8_t reg_0x0b09; /*0x0b09*/ + uint8_t reg_0x0136; + uint8_t reg_0x0137; + /* Edof */ + uint8_t reg_0x0b83; /*0x0b83*/ + uint8_t reg_0x0b84; /*0x0b84*/ + uint8_t reg_0x0b85; /*0x0b85*/ + uint8_t reg_0x0b88; /*0x0b88*/ + uint8_t reg_0x0b89; /*0x0b89*/ + uint8_t reg_0x0b8a; /*0x0b8a*/ + }; +struct reg_struct { + uint8_t coarse_integration_time_hi; /*REG_COARSE_INTEGRATION_TIME_HI*/ + uint8_t coarse_integration_time_lo; /*REG_COARSE_INTEGRATION_TIME_LO*/ + uint8_t analogue_gain_code_global; + uint8_t frame_length_lines_hi; /* 0x0340*/ + uint8_t frame_length_lines_lo; /* 0x0341*/ + uint8_t line_length_pck_hi; /* 0x0342*/ + uint8_t line_length_pck_lo; /* 0x0343*/ + uint8_t reg_0x3005; /* 0x3005*/ + uint8_t reg_0x3010; /* 0x3010*/ + uint8_t reg_0x3011; /* 0x3011*/ + uint8_t reg_0x301a; /* 0x301a*/ + uint8_t reg_0x3035; /* 0x3035*/ + uint8_t reg_0x3036; /* 0x3036*/ + uint8_t reg_0x3041; /*0x3041*/ + uint8_t reg_0x3042; /*0x3042*/ + uint8_t reg_0x3045; /*0x3045*/ + uint8_t reg_0x0b80; /* 0x0b80*/ + uint8_t reg_0x0900; /*0x0900*/ + uint8_t reg_0x0901; /* 0x0901*/ + uint8_t reg_0x0902; /*0x0902*/ + uint8_t reg_0x0383; /*0x0383*/ + uint8_t reg_0x0387; /* 0x0387*/ + uint8_t reg_0x034c; /* 0x034c*/ + uint8_t reg_0x034d; /*0x034d*/ + uint8_t reg_0x034e; /* 0x034e*/ + uint8_t reg_0x034f; /* 0x034f*/ + uint8_t reg_0x1716; /*0x1716*/ + uint8_t reg_0x1717; /*0x1717*/ + uint8_t reg_0x1718; /*0x1718*/ + uint8_t reg_0x1719; /*0x1719*/ + uint8_t reg_0x3210;/*0x3210*/ + uint8_t reg_0x111; /*0x111*/ + uint8_t reg_0x3410; /*0x3410*/ + uint8_t reg_0x3098; + uint8_t reg_0x309D; + uint8_t reg_0x0200; + uint8_t reg_0x0201; + }; +struct mt9e013_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +enum mt9e013_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum mt9e013_resolution_t { + QTR_SIZE, + FULL_SIZE, + HFR_60FPS, + HFR_90FPS, + HFR_120FPS, + INVALID_SIZE +}; +enum mt9e013_setting { + RES_PREVIEW, + RES_CAPTURE +}; +enum mt9e013_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +enum mt9e013_reg_pll { + E013_VT_PIX_CLK_DIV, + E013_VT_SYS_CLK_DIV, + E013_PRE_PLL_CLK_DIV, + E013_PLL_MULTIPLIER, + E013_OP_PIX_CLK_DIV, + E013_OP_SYS_CLK_DIV +}; + +enum mt9e013_reg_mode { + E013_X_ADDR_START, + E013_X_ADDR_END, + E013_Y_ADDR_START, + E013_Y_ADDR_END, + E013_X_OUTPUT_SIZE, + E013_Y_OUTPUT_SIZE, + E013_DATAPATH_SELECT, + E013_READ_MODE, + E013_ANALOG_CONTROL5, + E013_DAC_LD_4_5, + E013_SCALING_MODE, + E013_SCALE_M, + E013_LINE_LENGTH_PCK, + E013_FRAME_LENGTH_LINES, + E013_COARSE_INTEGRATION_TIME, + E013_FINE_INTEGRATION_TIME, + E013_FINE_CORRECTION +}; + +struct mt9e013_reg { + const struct mt9e013_i2c_reg_conf *reg_mipi; + const unsigned short reg_mipi_size; + const struct mt9e013_i2c_reg_conf *rec_settings; + const unsigned short rec_size; + const struct mt9e013_i2c_reg_conf *reg_pll; + const unsigned short reg_pll_size; + const struct mt9e013_i2c_reg_conf *reg_pll_60fps; + const unsigned short reg_pll_60fps_size; + const struct mt9e013_i2c_reg_conf *reg_pll_120fps; + const unsigned short reg_pll_120fps_size; + const struct mt9e013_i2c_reg_conf *reg_prev; + const unsigned short reg_prev_size; + const struct mt9e013_i2c_reg_conf *reg_snap; + const unsigned short reg_snap_size; + const struct mt9e013_i2c_reg_conf *reg_60fps; + const unsigned short reg_60fps_size; + const struct mt9e013_i2c_reg_conf *reg_120fps; + const unsigned short reg_120fps_size; +}; +#endif /* MT9E013_H */ diff --git a/drivers/media/video/msm/mt9e013_reg.c b/drivers/media/video/msm/mt9e013_reg.c new file mode 100644 index 00000000000..9a4bd7eefa9 --- /dev/null +++ b/drivers/media/video/msm/mt9e013_reg.c @@ -0,0 +1,234 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#include "mt9e013.h" + +static struct mt9e013_i2c_reg_conf mipi_settings[] = { + /*Disable embedded data*/ + {0x3064, 0x7800},/*SMIA_TEST*/ + /*configure 2-lane MIPI*/ + {0x31AE, 0x0202},/*SERIAL_FORMAT*/ + {0x31B8, 0x0E3F},/*MIPI_TIMING_2*/ + /*set data to RAW10 format*/ + {0x0112, 0x0A0A},/*CCP_DATA_FORMAT*/ + {0x30F0, 0x8000},/*VCM CONTROL*/ +}; + +/*PLL Configuration +(Ext=24MHz, vt_pix_clk=174MHz, op_pix_clk=69.6MHz)*/ +static struct mt9e013_i2c_reg_conf pll_settings[] = { + {0x0300, 0x0004},/*VT_PIX_CLK_DIV*/ + {0x0302, 0x0001},/*VT_SYS_CLK_DIV*/ + {0x0304, 0x0002},/*PRE_PLL_CLK_DIV*/ + {0x0306, 0x003A},/*PLL_MULTIPLIER*/ + {0x0308, 0x000A},/*OP_PIX_CLK_DIV*/ + {0x030A, 0x0001},/*OP_SYS_CLK_DIV*/ +}; + +static struct mt9e013_i2c_reg_conf prev_settings[] = { + /*Output Size (1632x1224)*/ + {0x0344, 0x0008},/*X_ADDR_START*/ + {0x0348, 0x0CC9},/*X_ADDR_END*/ + {0x0346, 0x0008},/*Y_ADDR_START*/ + {0x034A, 0x0999},/*Y_ADDR_END*/ + {0x034C, 0x0660},/*X_OUTPUT_SIZE*/ + {0x034E, 0x04C8},/*Y_OUTPUT_SIZE*/ + {0x306E, 0xFCB0},/*DATAPATH_SELECT*/ + {0x3040, 0x04C3},/*READ_MODE*/ + {0x3178, 0x0000},/*ANALOG_CONTROL5*/ + {0x3ED0, 0x1E24},/*DAC_LD_4_5*/ + {0x0400, 0x0002},/*SCALING_MODE*/ + {0x0404, 0x0010},/*SCALE_M*/ + /*Timing configuration*/ + {0x0342, 0x1018},/*LINE_LENGTH_PCK*/ + {0x0340, 0x055B},/*FRAME_LENGTH_LINES*/ + {0x0202, 0x0557},/*COARSE_INTEGRATION_TIME*/ + {0x3014, 0x0846},/*FINE_INTEGRATION_TIME_*/ + {0x3010, 0x0130},/*FINE_CORRECTION*/ +}; + +static struct mt9e013_i2c_reg_conf snap_settings[] = { + /*Output Size (3264x2448)*/ + {0x0344, 0x0008},/*X_ADDR_START */ + {0x0348, 0x0CD7},/*X_ADDR_END*/ + {0x0346, 0x0008},/*Y_ADDR_START */ + {0x034A, 0x09A7},/*Y_ADDR_END*/ + {0x034C, 0x0CD0},/*X_OUTPUT_SIZE*/ + {0x034E, 0x09A0},/*Y_OUTPUT_SIZE*/ + {0x306E, 0xFC80},/*DATAPATH_SELECT*/ + {0x3040, 0x0041},/*READ_MODE*/ + {0x3178, 0x0000},/*ANALOG_CONTROL5*/ + {0x3ED0, 0x1E24},/*DAC_LD_4_5*/ + {0x0400, 0x0000},/*SCALING_MODE*/ + {0x0404, 0x0010},/*SCALE_M*/ + /*Timing configuration*/ + {0x0342, 0x13F8},/*LINE_LENGTH_PCK*/ + {0x0340, 0x0A2F},/*FRAME_LENGTH_LINES*/ + {0x0202, 0x0A1F},/*COARSE_INTEGRATION_TIME*/ + {0x3014, 0x03F6},/*FINE_INTEGRATION_TIME_ */ + {0x3010, 0x0078},/*FINE_CORRECTION*/ +}; + +static struct mt9e013_i2c_reg_conf pll_settings_60fps[] = { + {0x0300, 0x0004},/*VT_PIX_CLK_DIV*/ + {0x0302, 0x0001},/*VT_SYS_CLK_DIV*/ + {0x0304, 0x0002},/*PRE_PLL_CLK_DIV*/ + {0x0306, 0x0042},/*PLL_MULTIPLIER*/ + {0x0308, 0x000A},/*OP_PIX_CLK_DIV*/ + {0x030A, 0x0001},/*OP_SYS_CLK_DIV*/ +}; + +static struct mt9e013_i2c_reg_conf prev_settings_60fps[] = { + /*Output Size (1632x1224)*/ + {0x0344, 0x0008},/*X_ADDR_START*/ + {0x0348, 0x0CC5},/*X_ADDR_END*/ + {0x0346, 0x013a},/*Y_ADDR_START*/ + {0x034A, 0x0863},/*Y_ADDR_END*/ + {0x034C, 0x0660},/*X_OUTPUT_SIZE*/ + {0x034E, 0x0396},/*Y_OUTPUT_SIZE*/ + {0x306E, 0xFC80},/*DATAPATH_SELECT*/ + {0x3040, 0x00C3},/*READ_MODE*/ + {0x3178, 0x0000},/*ANALOG_CONTROL5*/ + {0x3ED0, 0x1E24},/*DAC_LD_4_5*/ + {0x0400, 0x0000},/*SCALING_MODE*/ + {0x0404, 0x0010},/*SCALE_M*/ + /*Timing configuration*/ + {0x0342, 0x0BE8},/*LINE_LENGTH_PCK*/ + {0x0340, 0x0425},/*FRAME_LENGTH_LINES*/ + {0x0202, 0x0425},/*COARSE_INTEGRATION_TIME*/ + {0x3014, 0x03F6},/*FINE_INTEGRATION_TIME_*/ + {0x3010, 0x0078},/*FINE_CORRECTION*/ +}; + +static struct mt9e013_i2c_reg_conf pll_settings_120fps[] = { + {0x0300, 0x0005},/*VT_PIX_CLK_DIV*/ + {0x0302, 0x0001},/*VT_SYS_CLK_DIV*/ + {0x0304, 0x0002},/*PRE_PLL_CLK_DIV*/ + {0x0306, 0x0052},/*PLL_MULTIPLIER*/ + {0x0308, 0x000A},/*OP_PIX_CLK_DIV*/ + {0x030A, 0x0001},/*OP_SYS_CLK_DIV*/ +}; + +static struct mt9e013_i2c_reg_conf prev_settings_120fps[] = { + {0x0344, 0x0008},/*X_ADDR_START*/ + {0x0348, 0x0685},/*X_ADDR_END*/ + {0x0346, 0x013a},/*Y_ADDR_START*/ + {0x034A, 0x055B},/*Y_ADDR_END*/ + {0x034C, 0x0340},/*X_OUTPUT_SIZE*/ + {0x034E, 0x0212},/*Y_OUTPUT_SIZE*/ + {0x306E, 0xFC80},/*DATAPATH_SELECT*/ + {0x3040, 0x00C3},/*READ_MODE*/ + {0x3178, 0x0000},/*ANALOG_CONTROL5*/ + {0x3ED0, 0x1E24},/*DAC_LD_4_5*/ + {0x0400, 0x0000},/*SCALING_MODE*/ + {0x0404, 0x0010},/*SCALE_M*/ + /*Timing configuration*/ + {0x0342, 0x0970},/*LINE_LENGTH_PCK*/ + {0x0340, 0x02A1},/*FRAME_LENGTH_LINES*/ + {0x0202, 0x02A1},/*COARSE_INTEGRATION_TIME*/ + {0x3014, 0x03F6},/*FINE_INTEGRATION_TIME_*/ + {0x3010, 0x0078},/*FINE_CORRECTION*/ +}; + +static struct mt9e013_i2c_reg_conf recommend_settings[] = { + {0x3044, 0x0590}, + {0x306E, 0xFC80}, + {0x30B2, 0xC000}, + {0x30D6, 0x0800}, + {0x316C, 0xB42F}, + {0x316E, 0x869C}, + {0x3170, 0x210E}, + {0x317A, 0x010E}, + {0x31E0, 0x1FB9}, + {0x31E6, 0x07FC}, + {0x37C0, 0x0000}, + {0x37C2, 0x0000}, + {0x37C4, 0x0000}, + {0x37C6, 0x0000}, + {0x3E02, 0x8801}, + {0x3E04, 0x2301}, + {0x3E06, 0x8449}, + {0x3E08, 0x6841}, + {0x3E0A, 0x400C}, + {0x3E0C, 0x1001}, + {0x3E0E, 0x2103}, + {0x3E10, 0x4B41}, + {0x3E12, 0x4B26}, + {0x3E16, 0x8802}, + {0x3E18, 0x84FF}, + {0x3E1A, 0x8601}, + {0x3E1C, 0x8401}, + {0x3E1E, 0x840A}, + {0x3E20, 0xFF00}, + {0x3E22, 0x8401}, + {0x3E24, 0x00FF}, + {0x3E26, 0x0088}, + {0x3E28, 0x2E8A}, + {0x3E32, 0x8801}, + {0x3E34, 0x4024}, + {0x3E38, 0x8469}, + {0x3E3C, 0x2301}, + {0x3E3E, 0x3E25}, + {0x3E40, 0x1C01}, + {0x3E42, 0x8486}, + {0x3E44, 0x8401}, + {0x3E46, 0x00FF}, + {0x3E48, 0x8401}, + {0x3E4A, 0x8601}, + {0x3E4C, 0x8402}, + {0x3E4E, 0x00FF}, + {0x3E50, 0x6623}, + {0x3E52, 0x8340}, + {0x3E54, 0x00FF}, + {0x3E56, 0x4A42}, + {0x3E58, 0x2203}, + {0x3E5A, 0x674D}, + {0x3E5C, 0x3F25}, + {0x3E5E, 0x846A}, + {0x3E60, 0x4C01}, + {0x3E62, 0x8401}, + {0x3E66, 0x3901}, + {0x3ECC, 0x00EB}, + {0x3ED0, 0x1E24}, + {0x3ED4, 0xAFC4}, + {0x3ED6, 0x909B}, + {0x3ED8, 0x0006}, + {0x3EDA, 0xCFC6}, + {0x3EDC, 0x4FE4}, + {0x3EE0, 0x2424}, + {0x3EE2, 0x9797}, + {0x3EE4, 0xC100}, + {0x3EE6, 0x0540} +}; + +struct mt9e013_reg mt9e013_regs = { + .reg_mipi = &mipi_settings[0], + .reg_mipi_size = ARRAY_SIZE(mipi_settings), + .rec_settings = &recommend_settings[0], + .rec_size = ARRAY_SIZE(recommend_settings), + .reg_pll = &pll_settings[0], + .reg_pll_size = ARRAY_SIZE(pll_settings), + .reg_prev = &prev_settings[0], + .reg_pll_60fps = &pll_settings_60fps[0], + .reg_pll_60fps_size = ARRAY_SIZE(pll_settings_60fps), + .reg_pll_120fps = &pll_settings_120fps[0], + .reg_pll_120fps_size = ARRAY_SIZE(pll_settings_120fps), + .reg_prev_size = ARRAY_SIZE(prev_settings), + .reg_snap = &snap_settings[0], + .reg_snap_size = ARRAY_SIZE(snap_settings), + .reg_60fps = &prev_settings_60fps[0], + .reg_60fps_size = ARRAY_SIZE(prev_settings_60fps), + .reg_120fps = &prev_settings_120fps[0], + .reg_120fps_size = ARRAY_SIZE(prev_settings_120fps), +}; diff --git a/drivers/media/video/msm/mt9p012.h b/drivers/media/video/msm/mt9p012.h new file mode 100644 index 00000000000..05798130bbf --- /dev/null +++ b/drivers/media/video/msm/mt9p012.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MT9T012_H +#define MT9T012_H + +#include + +extern struct mt9p012_reg mt9p012_regs; /* from mt9p012_reg.c */ + +struct reg_struct { + uint16_t vt_pix_clk_div; /* 0x0300 */ + uint16_t vt_sys_clk_div; /* 0x0302 */ + uint16_t pre_pll_clk_div; /* 0x0304 */ + uint16_t pll_multiplier; /* 0x0306 */ + uint16_t op_pix_clk_div; /* 0x0308 */ + uint16_t op_sys_clk_div; /* 0x030A */ + uint16_t scale_m; /* 0x0404 */ + uint16_t row_speed; /* 0x3016 */ + uint16_t x_addr_start; /* 0x3004 */ + uint16_t x_addr_end; /* 0x3008 */ + uint16_t y_addr_start; /* 0x3002 */ + uint16_t y_addr_end; /* 0x3006 */ + uint16_t read_mode; /* 0x3040 */ + uint16_t x_output_size ; /* 0x034C */ + uint16_t y_output_size; /* 0x034E */ + uint16_t line_length_pck; /* 0x300C */ + uint16_t frame_length_lines; /* 0x300A */ + uint16_t coarse_int_time; /* 0x3012 */ + uint16_t fine_int_time; /* 0x3014 */ +}; + + +struct mt9p012_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + + +struct mt9p012_reg { + struct reg_struct const *reg_pat; + uint16_t reg_pat_size; + struct mt9p012_i2c_reg_conf const *ttbl; + uint16_t ttbl_size; + struct mt9p012_i2c_reg_conf const *rftbl; + uint16_t rftbl_size; +}; + +#endif /* MT9T012_H */ diff --git a/drivers/media/video/msm/mt9p012_bam.c b/drivers/media/video/msm/mt9p012_bam.c new file mode 100644 index 00000000000..919738099f3 --- /dev/null +++ b/drivers/media/video/msm/mt9p012_bam.c @@ -0,0 +1,1426 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mt9p012.h" + +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ +#define MT9P012_REG_MODEL_ID 0x0000 +#define MT9P012_MODEL_ID 0x2801 +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD 0x0100 +#define GROUPED_PARAMETER_UPDATE 0x0000 +#define REG_COARSE_INT_TIME 0x3012 +#define REG_VT_PIX_CLK_DIV 0x0300 +#define REG_VT_SYS_CLK_DIV 0x0302 +#define REG_PRE_PLL_CLK_DIV 0x0304 +#define REG_PLL_MULTIPLIER 0x0306 +#define REG_OP_PIX_CLK_DIV 0x0308 +#define REG_OP_SYS_CLK_DIV 0x030A +#define REG_SCALE_M 0x0404 +#define REG_FRAME_LENGTH_LINES 0x300A +#define REG_LINE_LENGTH_PCK 0x300C +#define REG_X_ADDR_START 0x3004 +#define REG_Y_ADDR_START 0x3002 +#define REG_X_ADDR_END 0x3008 +#define REG_Y_ADDR_END 0x3006 +#define REG_X_OUTPUT_SIZE 0x034C +#define REG_Y_OUTPUT_SIZE 0x034E +#define REG_FINE_INTEGRATION_TIME 0x3014 +#define REG_ROW_SPEED 0x3016 +#define MT9P012_REG_RESET_REGISTER 0x301A +#define MT9P012_RESET_REGISTER_PWON 0x10CC +#define MT9P012_RESET_REGISTER_PWOFF 0x10C8 +#define REG_READ_MODE 0x3040 +#define REG_GLOBAL_GAIN 0x305E +#define REG_TEST_PATTERN_MODE 0x3070 + +#define MT9P012_REV_7 + +enum mt9p012_test_mode { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum mt9p012_resolution { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; + +enum mt9p012_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +enum mt9p012_setting { + RES_PREVIEW, + RES_CAPTURE +}; + +/* actuator's Slave Address */ +#define MT9P012_AF_I2C_ADDR 0x0A + +/* AF Total steps parameters */ +#define MT9P012_STEPS_NEAR_TO_CLOSEST_INF 20 +#define MT9P012_TOTAL_STEPS_NEAR_TO_FAR 20 + +#define MT9P012_MU5M0_PREVIEW_DUMMY_PIXELS 0 +#define MT9P012_MU5M0_PREVIEW_DUMMY_LINES 0 + +/* Time in milisecs for waiting for the sensor to reset.*/ +#define MT9P012_RESET_DELAY_MSECS 66 + +/* for 20 fps preview */ +#define MT9P012_DEFAULT_CLOCK_RATE 24000000 +#define MT9P012_DEFAULT_MAX_FPS 26 /* ???? */ + +struct mt9p012_work { + struct work_struct work; +}; +static struct mt9p012_work *mt9p012_sensorw; +static struct i2c_client *mt9p012_client; + +struct mt9p012_ctrl { + const struct msm_camera_sensor_info *sensordata; + + int sensormode; + uint32_t fps_divider; /* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ + + uint16_t curr_lens_pos; + uint16_t init_curr_lens_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + + enum mt9p012_resolution prev_res; + enum mt9p012_resolution pict_res; + enum mt9p012_resolution curr_res; + enum mt9p012_test_mode set_test; +}; + +static uint16_t bam_macro, bam_infinite; +static uint16_t bam_step_lookup_table[MT9P012_TOTAL_STEPS_NEAR_TO_FAR + 1]; +static uint16_t update_type = UPDATE_PERIODIC; +static struct mt9p012_ctrl *mt9p012_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(mt9p012_wait_queue); +DEFINE_MUTEX(mt9p012_mut); + +/*=============================================================*/ + +static int mt9p012_i2c_rxdata(unsigned short saddr, int slength, + unsigned char *rxdata, int rxlength) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = slength, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = rxlength, + .buf = rxdata, + }, + }; + + if (i2c_transfer(mt9p012_client->adapter, msgs, 2) < 0) { + CDBG("mt9p012_i2c_rxdata failed!\n"); + return -EIO; + } + + return 0; +} +static int32_t mt9p012_i2c_read_b(unsigned short saddr, unsigned char raddr, + unsigned short *rdata) +{ + int32_t rc = 0; + if (!rdata) + return -EIO; + rc = mt9p012_i2c_rxdata(saddr, 1, &raddr, 1); + if (rc < 0) + return rc; + *rdata = raddr; + if (rc < 0) + CDBG("mt9p012_i2c_read_b failed!\n"); + return rc; +} + +static int32_t mt9p012_i2c_read_w(unsigned short saddr, unsigned short raddr, + unsigned short *rdata) +{ + int32_t rc = 0; + unsigned char buf[4]; + + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + + rc = mt9p012_i2c_rxdata(saddr, 2, buf, 2); + if (rc < 0) + return rc; + + *rdata = buf[0] << 8 | buf[1]; + + if (rc < 0) + CDBG("mt9p012_i2c_read failed!\n"); + + return rc; +} + +static int32_t mt9p012_i2c_txdata(unsigned short saddr, unsigned char *txdata, + int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + + if (i2c_transfer(mt9p012_client->adapter, msg, 1) < 0) { + CDBG("mt9p012_i2c_txdata failed\n"); + return -EIO; + } + + return 0; +} + +static int32_t mt9p012_i2c_write_b(unsigned short saddr, unsigned short baddr, + unsigned short bdata) +{ + int32_t rc = -EIO; + unsigned char buf[2]; + + memset(buf, 0, sizeof(buf)); + buf[0] = baddr; + buf[1] = bdata; + rc = mt9p012_i2c_txdata(saddr, buf, 2); + + if (rc < 0) + CDBG("i2c_write failed, saddr = 0x%x addr = 0x%x, val =0x%x!\n", + saddr, baddr, bdata); + + return rc; +} + +static int32_t mt9p012_i2c_write_w(unsigned short saddr, unsigned short waddr, + unsigned short wdata) +{ + int32_t rc = -EIO; + unsigned char buf[4]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + + rc = mt9p012_i2c_txdata(saddr, buf, 4); + + if (rc < 0) + CDBG("i2c_write_w failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + + return rc; +} + +static int32_t mt9p012_i2c_write_w_table(struct mt9p012_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + + for (i = 0; i < num; i++) { + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + + return rc; +} + +static int32_t mt9p012_test(enum mt9p012_test_mode mo) +{ + int32_t rc = 0; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + if (mo == TEST_OFF) + return 0; + else { + rc = mt9p012_i2c_write_w_table(mt9p012_regs.ttbl, + mt9p012_regs.ttbl_size); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_TEST_PATTERN_MODE, (uint16_t) mo); + if (rc < 0) + return rc; + } + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t mt9p012_lens_shading_enable(uint8_t is_enable) +{ + int32_t rc = 0; + + CDBG("%s: entered. enable = %d\n", __func__, is_enable); + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, 0x3780, + ((uint16_t) is_enable) << 15); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + + CDBG("%s: exiting. rc = %d\n", __func__, rc); + return rc; +} + +static int32_t mt9p012_set_lc(void) +{ + int32_t rc; + + rc = mt9p012_i2c_write_w_table(mt9p012_regs.rftbl, + mt9p012_regs.rftbl_size); + + return rc; +} + +static void mt9p012_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider; /*Q10 */ + uint32_t pclk_mult; /*Q10 */ + + if (mt9p012_ctrl->prev_res == QTR_SIZE) { + divider = (uint32_t) + (((mt9p012_regs.reg_pat[RES_PREVIEW].frame_length_lines * + mt9p012_regs.reg_pat[RES_PREVIEW].line_length_pck) * + 0x00000400) / + (mt9p012_regs.reg_pat[RES_CAPTURE].frame_length_lines * + mt9p012_regs.reg_pat[RES_CAPTURE].line_length_pck)); + + pclk_mult = + (uint32_t) ((mt9p012_regs.reg_pat[RES_CAPTURE]. + pll_multiplier * 0x00000400) / + (mt9p012_regs.reg_pat[RES_PREVIEW]. + pll_multiplier)); + } else { + /* full size resolution used for preview. */ + divider = 0x00000400; /*1.0 */ + pclk_mult = 0x00000400; /*1.0 */ + } + + /* Verify PCLK settings and frame sizes. */ + *pfps = (uint16_t) (fps * divider * pclk_mult / 0x00000400 / + 0x00000400); +} + +static uint16_t mt9p012_get_prev_lines_pf(void) +{ + if (mt9p012_ctrl->prev_res == QTR_SIZE) + return mt9p012_regs.reg_pat[RES_PREVIEW].frame_length_lines; + else + return mt9p012_regs.reg_pat[RES_CAPTURE].frame_length_lines; +} + +static uint16_t mt9p012_get_prev_pixels_pl(void) +{ + if (mt9p012_ctrl->prev_res == QTR_SIZE) + return mt9p012_regs.reg_pat[RES_PREVIEW].line_length_pck; + else + return mt9p012_regs.reg_pat[RES_CAPTURE].line_length_pck; +} + +static uint16_t mt9p012_get_pict_lines_pf(void) +{ + return mt9p012_regs.reg_pat[RES_CAPTURE].frame_length_lines; +} + +static uint16_t mt9p012_get_pict_pixels_pl(void) +{ + return mt9p012_regs.reg_pat[RES_CAPTURE].line_length_pck; +} + +static uint32_t mt9p012_get_pict_max_exp_lc(void) +{ + uint16_t snapshot_lines_per_frame; + + if (mt9p012_ctrl->pict_res == QTR_SIZE) + snapshot_lines_per_frame = + mt9p012_regs.reg_pat[RES_PREVIEW].frame_length_lines - 1; + else + snapshot_lines_per_frame = + mt9p012_regs.reg_pat[RES_CAPTURE].frame_length_lines - 1; + + return snapshot_lines_per_frame * 24; +} + +static int32_t mt9p012_set_fps(struct fps_cfg *fps) +{ + /* input is new fps in Q10 format */ + int32_t rc = 0; + enum mt9p012_setting setting; + + mt9p012_ctrl->fps_divider = fps->fps_div; + mt9p012_ctrl->pict_fps_divider = fps->pict_fps_div; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return -EBUSY; + + if (mt9p012_ctrl->sensormode == SENSOR_PREVIEW_MODE) + setting = RES_PREVIEW; + else + setting = RES_CAPTURE; + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_FRAME_LENGTH_LINES, + (mt9p012_regs.reg_pat[setting].frame_length_lines * + fps->fps_div / 0x00000400)); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + + return rc; +} + +static int32_t mt9p012_write_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t max_legal_gain = 0x01FF; + uint32_t line_length_ratio = 0x00000400; + enum mt9p012_setting setting; + int32_t rc = 0; + + CDBG("Line:%d mt9p012_write_exp_gain \n", __LINE__); + + if (mt9p012_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + mt9p012_ctrl->my_reg_gain = gain; + mt9p012_ctrl->my_reg_line_count = (uint16_t) line; + } + + if (gain > max_legal_gain) { + CDBG("Max legal gain Line:%d \n", __LINE__); + gain = max_legal_gain; + } + + /* Verify no overflow */ + if (mt9p012_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + line = (uint32_t) (line * mt9p012_ctrl->fps_divider / + 0x00000400); + setting = RES_PREVIEW; + } else { + line = (uint32_t) (line * mt9p012_ctrl->pict_fps_divider / + 0x00000400); + setting = RES_CAPTURE; + } + + /* Set digital gain to 1 */ +#ifdef MT9P012_REV_7 + gain |= 0x1000; +#else + gain |= 0x0200; +#endif + + if ((mt9p012_regs.reg_pat[setting].frame_length_lines - 1) < line) { + line_length_ratio = (uint32_t) (line * 0x00000400) / + (mt9p012_regs.reg_pat[setting].frame_length_lines - 1); + } else + line_length_ratio = 0x00000400; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, REG_GLOBAL_GAIN, gain); + if (rc < 0) { + CDBG("mt9p012_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_COARSE_INT_TIME, line); + if (rc < 0) { + CDBG("mt9p012_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + CDBG("mt9p012_write_exp_gain: gain = %d, line = %d\n", gain, line); + + return rc; +} + +static int32_t mt9p012_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + + CDBG("Line:%d mt9p012_set_pict_exp_gain \n", __LINE__); + + rc = mt9p012_write_exp_gain(gain, line); + if (rc < 0) { + CDBG("Line:%d mt9p012_set_pict_exp_gain failed... \n", + __LINE__); + return rc; + } + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, 0x10CC | 0x0002); + if (rc < 0) { + CDBG("mt9p012_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + mdelay(5); + + /* camera_timed_wait(snapshot_wait*exposure_ratio); */ + return rc; +} + +static int32_t mt9p012_setting(enum mt9p012_reg_update rupdate, + enum mt9p012_setting rt) +{ + int32_t rc = 0; + + switch (rupdate) { + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct mt9p012_i2c_reg_conf ppc_tbl[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + {REG_ROW_SPEED, + mt9p012_regs.reg_pat[rt].row_speed}, + {REG_X_ADDR_START, + mt9p012_regs.reg_pat[rt].x_addr_start}, + {REG_X_ADDR_END, + mt9p012_regs.reg_pat[rt].x_addr_end}, + {REG_Y_ADDR_START, + mt9p012_regs.reg_pat[rt].y_addr_start}, + {REG_Y_ADDR_END, + mt9p012_regs.reg_pat[rt].y_addr_end}, + {REG_READ_MODE, + mt9p012_regs.reg_pat[rt].read_mode}, + {REG_SCALE_M, mt9p012_regs.reg_pat[rt].scale_m}, + {REG_X_OUTPUT_SIZE, + mt9p012_regs.reg_pat[rt].x_output_size}, + {REG_Y_OUTPUT_SIZE, + mt9p012_regs.reg_pat[rt].y_output_size}, + + {REG_LINE_LENGTH_PCK, + mt9p012_regs.reg_pat[rt].line_length_pck}, + {REG_FRAME_LENGTH_LINES, + (mt9p012_regs.reg_pat[rt].frame_length_lines * + mt9p012_ctrl->fps_divider / 0x00000400)}, + {REG_COARSE_INT_TIME, + mt9p012_regs.reg_pat[rt].coarse_int_time}, + {REG_FINE_INTEGRATION_TIME, + mt9p012_regs.reg_pat[rt].fine_int_time}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE}, + }; + if (update_type == REG_INIT) { + update_type = rupdate; + return rc; + } + rc = mt9p012_i2c_write_w_table(&ppc_tbl[0], + ARRAY_SIZE(ppc_tbl)); + if (rc < 0) + return rc; + + rc = mt9p012_test(mt9p012_ctrl->set_test); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWON | + 0x0002); + if (rc < 0) + return rc; + + mdelay(5); /* 15? wait for sensor to transition */ + + return rc; + } + break; /* UPDATE_PERIODIC */ + + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct mt9p012_i2c_reg_conf ipc_tbl1[] = { + {MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWOFF}, + {REG_VT_PIX_CLK_DIV, + mt9p012_regs.reg_pat[rt].vt_pix_clk_div}, + {REG_VT_SYS_CLK_DIV, + mt9p012_regs.reg_pat[rt].vt_sys_clk_div}, + {REG_PRE_PLL_CLK_DIV, + mt9p012_regs.reg_pat[rt].pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + mt9p012_regs.reg_pat[rt].pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + mt9p012_regs.reg_pat[rt].op_pix_clk_div}, + {REG_OP_SYS_CLK_DIV, + mt9p012_regs.reg_pat[rt].op_sys_clk_div}, +#ifdef MT9P012_REV_7 + {0x30B0, 0x0001}, + {0x308E, 0xE060}, + {0x3092, 0x0A52}, + {0x3094, 0x4656}, + {0x3096, 0x5652}, + {0x30CA, 0x8006}, + {0x312A, 0xDD02}, + {0x312C, 0x00E4}, + {0x3170, 0x299A}, +#endif + /* optimized settings for noise */ + {0x3088, 0x6FF6}, + {0x3154, 0x0282}, + {0x3156, 0x0381}, + {0x3162, 0x04CE}, + {0x0204, 0x0010}, + {0x0206, 0x0010}, + {0x0208, 0x0010}, + {0x020A, 0x0010}, + {0x020C, 0x0010}, + {MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWON}, + }; + + struct mt9p012_i2c_reg_conf ipc_tbl2[] = { + {MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWOFF}, + {REG_VT_PIX_CLK_DIV, + mt9p012_regs.reg_pat[rt].vt_pix_clk_div}, + {REG_VT_SYS_CLK_DIV, + mt9p012_regs.reg_pat[rt].vt_sys_clk_div}, + {REG_PRE_PLL_CLK_DIV, + mt9p012_regs.reg_pat[rt].pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + mt9p012_regs.reg_pat[rt].pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + mt9p012_regs.reg_pat[rt].op_pix_clk_div}, + {REG_OP_SYS_CLK_DIV, + mt9p012_regs.reg_pat[rt].op_sys_clk_div}, +#ifdef MT9P012_REV_7 + {0x30B0, 0x0001}, + {0x308E, 0xE060}, + {0x3092, 0x0A52}, + {0x3094, 0x4656}, + {0x3096, 0x5652}, + {0x30CA, 0x8006}, + {0x312A, 0xDD02}, + {0x312C, 0x00E4}, + {0x3170, 0x299A}, +#endif + /* optimized settings for noise */ + {0x3088, 0x6FF6}, + {0x3154, 0x0282}, + {0x3156, 0x0381}, + {0x3162, 0x04CE}, + {0x0204, 0x0010}, + {0x0206, 0x0010}, + {0x0208, 0x0010}, + {0x020A, 0x0010}, + {0x020C, 0x0010}, + {MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWON}, + }; + + struct mt9p012_i2c_reg_conf ipc_tbl3[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + /* Set preview or snapshot mode */ + {REG_ROW_SPEED, + mt9p012_regs.reg_pat[rt].row_speed}, + {REG_X_ADDR_START, + mt9p012_regs.reg_pat[rt].x_addr_start}, + {REG_X_ADDR_END, + mt9p012_regs.reg_pat[rt].x_addr_end}, + {REG_Y_ADDR_START, + mt9p012_regs.reg_pat[rt].y_addr_start}, + {REG_Y_ADDR_END, + mt9p012_regs.reg_pat[rt].y_addr_end}, + {REG_READ_MODE, + mt9p012_regs.reg_pat[rt].read_mode}, + {REG_SCALE_M, mt9p012_regs.reg_pat[rt].scale_m}, + {REG_X_OUTPUT_SIZE, + mt9p012_regs.reg_pat[rt].x_output_size}, + {REG_Y_OUTPUT_SIZE, + mt9p012_regs.reg_pat[rt].y_output_size}, + {REG_LINE_LENGTH_PCK, + mt9p012_regs.reg_pat[rt].line_length_pck}, + {REG_FRAME_LENGTH_LINES, + mt9p012_regs.reg_pat[rt].frame_length_lines}, + {REG_COARSE_INT_TIME, + mt9p012_regs.reg_pat[rt].coarse_int_time}, + {REG_FINE_INTEGRATION_TIME, + mt9p012_regs.reg_pat[rt].fine_int_time}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE}, + }; + + /* reset fps_divider */ + mt9p012_ctrl->fps_divider = 1 * 0x0400; + + rc = mt9p012_i2c_write_w_table(&ipc_tbl1[0], + ARRAY_SIZE(ipc_tbl1)); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w_table(&ipc_tbl2[0], + ARRAY_SIZE(ipc_tbl2)); + if (rc < 0) + return rc; + + mdelay(5); + + rc = mt9p012_i2c_write_w_table(&ipc_tbl3[0], + ARRAY_SIZE(ipc_tbl3)); + if (rc < 0) + return rc; + + /* load lens shading */ + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + rc = mt9p012_set_lc(); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + + if (rc < 0) + return rc; + } + update_type = rupdate; + break; /* case REG_INIT: */ + + default: + rc = -EINVAL; + break; + } /* switch (rupdate) */ + + return rc; +} + +static int32_t mt9p012_video_config(int mode, int res) +{ + int32_t rc; + + switch (res) { + case QTR_SIZE: + rc = mt9p012_setting(UPDATE_PERIODIC, RES_PREVIEW); + if (rc < 0) + return rc; + + CDBG("mt9p012 sensor configuration done!\n"); + break; + + case FULL_SIZE: + rc = mt9p012_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + break; + + default: + return 0; + } /* switch */ + + mt9p012_ctrl->prev_res = res; + mt9p012_ctrl->curr_res = res; + mt9p012_ctrl->sensormode = mode; + + rc = mt9p012_write_exp_gain(mt9p012_ctrl->my_reg_gain, + mt9p012_ctrl->my_reg_line_count); + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, 0x10cc | 0x0002); + + return rc; +} + +static int32_t mt9p012_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = mt9p012_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + mt9p012_ctrl->curr_res = mt9p012_ctrl->pict_res; + + mt9p012_ctrl->sensormode = mode; + + return rc; +} + +static int32_t mt9p012_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = mt9p012_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + mt9p012_ctrl->curr_res = mt9p012_ctrl->pict_res; + + mt9p012_ctrl->sensormode = mode; + + return rc; +} + +static int32_t mt9p012_power_down(void) +{ + int32_t rc = 0; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWOFF); + + mdelay(5); + return rc; +} + +static int32_t mt9p012_move_focus(int direction, int32_t num_steps) +{ + int32_t rc; + int16_t step_direction; + int16_t actual_step; + int16_t next_position; + uint8_t code_val; + uint8_t time_out; + uint8_t temp_pos; + + uint16_t actual_position_target; + if (num_steps > MT9P012_TOTAL_STEPS_NEAR_TO_FAR) + num_steps = MT9P012_TOTAL_STEPS_NEAR_TO_FAR; + else if (num_steps == 0) { + CDBG("mt9p012_move_focus failed at line %d ...\n", __LINE__); + return -EINVAL; + } + + if (direction == MOVE_NEAR) + step_direction = -1; + else if (direction == MOVE_FAR) + step_direction = 1; + else { + CDBG("mt9p012_move_focus failed at line %d ...\n", __LINE__); + return -EINVAL; + } + + if (mt9p012_ctrl->curr_lens_pos < mt9p012_ctrl->init_curr_lens_pos) + mt9p012_ctrl->curr_lens_pos = mt9p012_ctrl->init_curr_lens_pos; + + actual_step = (int16_t) (step_direction * (int16_t) num_steps); + next_position = (int16_t) (mt9p012_ctrl->curr_lens_pos + actual_step); + + if (next_position > MT9P012_TOTAL_STEPS_NEAR_TO_FAR) + next_position = MT9P012_TOTAL_STEPS_NEAR_TO_FAR; + else if (next_position < 0) + next_position = 0; + + if (num_steps >= 10) + time_out = 100; + else + time_out = 30; + code_val = next_position; + actual_position_target = bam_step_lookup_table[code_val]; + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x01, 0x29); + if (rc < 0) + return rc; + temp_pos = (uint8_t) (actual_position_target >> 8); + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x05, temp_pos); + if (rc < 0) + return rc; + temp_pos = (uint8_t) (actual_position_target & 0x00FF); + /* code_val_lsb |= mode_mask; */ + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x06, temp_pos); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x0B, time_out); + if (rc < 0) + return rc; + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x07, 0x27); + if (rc < 0) + return rc; + + mdelay(time_out); + + /* Storing the current lens Position */ + mt9p012_ctrl->curr_lens_pos = next_position; + + return rc; +} + +static int32_t mt9p012_set_default_focus(void) +{ + int32_t rc = 0; + + uint8_t temp_pos; + + /* Write the digital code for current to the actuator */ + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x01, 0x29); + if (rc < 0) + return rc; + temp_pos = (uint8_t) (bam_infinite >> 8); + + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x05, temp_pos); + if (rc < 0) + return rc; + temp_pos = (uint8_t) (bam_infinite & 0x00FF); + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x06, temp_pos); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x0B, 0x64); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x07, 0x27); + if (rc < 0) + return rc; + + mdelay(140); + + mt9p012_ctrl->curr_lens_pos = MT9P012_TOTAL_STEPS_NEAR_TO_FAR; + + return rc; +} + +static int mt9p012_probe_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + return 0; +} + +static int mt9p012_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + uint16_t chipid; + + rc = gpio_request(data->sensor_reset, "mt9p012"); + if (!rc) + gpio_direction_output(data->sensor_reset, 1); + else + goto init_probe_done; + + msleep(20); + + /* RESET the sensor image part via I2C command */ + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, 0x10CC | 0x0001); + if (rc < 0) { + CDBG("sensor reset failed. rc = %d\n", rc); + goto init_probe_fail; + } + + msleep(MT9P012_RESET_DELAY_MSECS); + + /* 3. Read sensor Model ID: */ + rc = mt9p012_i2c_read_w(mt9p012_client->addr, + MT9P012_REG_MODEL_ID, &chipid); + if (rc < 0) + goto init_probe_fail; + + /* 4. Compare sensor ID to MT9T012VC ID: */ + if (chipid != MT9P012_MODEL_ID) { + rc = -ENODEV; + goto init_probe_fail; + } + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, 0x306E, 0x9000); + if (rc < 0) { + CDBG("REV_7 write failed. rc = %d\n", rc); + goto init_probe_fail; + } + + /* RESET_REGISTER, enable parallel interface and disable serialiser */ + rc = mt9p012_i2c_write_w(mt9p012_client->addr, 0x301A, 0x10CC); + if (rc < 0) { + CDBG("enable parallel interface failed. rc = %d\n", rc); + goto init_probe_fail; + } + + /* To disable the 2 extra lines */ + rc = mt9p012_i2c_write_w(mt9p012_client->addr, 0x3064, 0x0805); + + if (rc < 0) { + CDBG("disable the 2 extra lines failed. rc = %d\n", rc); + goto init_probe_fail; + } + + goto init_probe_done; + +init_probe_fail: + mt9p012_probe_init_done(data); +init_probe_done: + return rc; +} + +static int mt9p012_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + unsigned short temp_pos; + uint8_t i; + uint16_t temp; + + mt9p012_ctrl = kzalloc(sizeof(struct mt9p012_ctrl), GFP_KERNEL); + if (!mt9p012_ctrl) { + CDBG("mt9p012_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + + mt9p012_ctrl->fps_divider = 1 * 0x00000400; + mt9p012_ctrl->pict_fps_divider = 1 * 0x00000400; + mt9p012_ctrl->set_test = TEST_OFF; + mt9p012_ctrl->prev_res = QTR_SIZE; + mt9p012_ctrl->pict_res = FULL_SIZE; + + if (data) + mt9p012_ctrl->sensordata = data; + + msm_camio_camif_pad_reg_reset(); + mdelay(20); + + rc = mt9p012_probe_init_sensor(data); + if (rc < 0) + goto init_fail1; + + if (mt9p012_ctrl->prev_res == QTR_SIZE) + rc = mt9p012_setting(REG_INIT, RES_PREVIEW); + else + rc = mt9p012_setting(REG_INIT, RES_CAPTURE); + + if (rc < 0) { + CDBG("mt9p012_setting failed. rc = %d\n", rc); + goto init_fail1; + } + + /* sensor : output enable */ + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWON); + if (rc < 0) { + CDBG("sensor output enable failed. rc = %d\n", rc); + goto init_fail1; + } + + /* enable AF actuator */ + rc = gpio_request(mt9p012_ctrl->sensordata->vcm_pwd, "mt9p012"); + if (!rc) + gpio_direction_output(mt9p012_ctrl->sensordata->vcm_pwd, 1); + else { + CDBG("mt9p012_ctrl gpio request failed!\n"); + goto init_fail1; + } + + mdelay(20); + + bam_infinite = 0; + bam_macro = 0; + /*initialize AF actuator */ + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x01, 0x09); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x07, 0x2E); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x0A, 0x01); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x17, 0x06); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x16, 0x0A); + + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x01, 0x29); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x05, 0x00); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x06, 0x00); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x0B, 0x64); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x07, 0x27); + mdelay(140); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x01, 0x29); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x05, 0x03); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x06, 0xFF); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x0B, 0x64); + mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, 0x07, 0x27); + mdelay(140); + + if (mt9p012_i2c_read_b(MT9P012_AF_I2C_ADDR >> 1, 0x12, &temp_pos) + >= 0) { + bam_infinite = (uint16_t) temp_pos; + if (mt9p012_i2c_read_b + (MT9P012_AF_I2C_ADDR >> 1, 0x13, &temp_pos) >= 0) + bam_infinite = + (bam_infinite << 8) | ((uint16_t) temp_pos); + } else { + bam_infinite = 100; + } + + if (mt9p012_i2c_read_b(MT9P012_AF_I2C_ADDR >> 1, 0x14, &temp_pos) + >= 0) { + bam_macro = (uint16_t) temp_pos; + if (mt9p012_i2c_read_b + (MT9P012_AF_I2C_ADDR >> 1, 0x15, &temp_pos) >= 0) + bam_macro = (bam_macro << 8) | ((uint16_t) temp_pos); + } + temp = (bam_infinite - bam_macro) / MT9P012_TOTAL_STEPS_NEAR_TO_FAR; + for (i = 0; i < MT9P012_TOTAL_STEPS_NEAR_TO_FAR; i++) + bam_step_lookup_table[i] = bam_macro + temp * i; + + bam_step_lookup_table[MT9P012_TOTAL_STEPS_NEAR_TO_FAR] = bam_infinite; + + rc = mt9p012_set_default_focus(); + if (rc >= 0) + goto init_done; + +init_fail1: + mt9p012_probe_init_done(data); + kfree(mt9p012_ctrl); +init_done: + return rc; +} + +static int mt9p012_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&mt9p012_wait_queue); + return 0; +} + +static int32_t mt9p012_set_sensor_mode(int mode, int res) +{ + int32_t rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = mt9p012_video_config(mode, res); + break; + + case SENSOR_SNAPSHOT_MODE: + rc = mt9p012_snapshot_config(mode); + break; + + case SENSOR_RAW_SNAPSHOT_MODE: + rc = mt9p012_raw_snapshot_config(mode); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +int mt9p012_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + int rc = 0; + + if (copy_from_user(&cdata, + (void *)argp, sizeof(struct sensor_cfg_data))) + return -EFAULT; + + mutex_lock(&mt9p012_mut); + + CDBG("%s: cfgtype = %d\n", __func__, cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + mt9p012_get_pict_fps(cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = mt9p012_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = mt9p012_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = mt9p012_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = mt9p012_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = mt9p012_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = mt9p012_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = mt9p012_write_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + CDBG("Line:%d CFG_SET_PICT_EXP_GAIN \n", __LINE__); + rc = mt9p012_set_pict_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = mt9p012_set_sensor_mode(cdata.mode, cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = mt9p012_power_down(); + break; + + case CFG_MOVE_FOCUS: + CDBG("mt9p012_ioctl: CFG_MOVE_FOCUS: dir=%d steps=%d\n", + cdata.cfg.focus.dir, cdata.cfg.focus.steps); + rc = mt9p012_move_focus(cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = mt9p012_set_default_focus(); + + break; + + case CFG_SET_EFFECT: + rc = mt9p012_set_default_focus(); + break; + + case CFG_SET_LENS_SHADING: + CDBG("%s: CFG_SET_LENS_SHADING\n", __func__); + rc = mt9p012_lens_shading_enable(cdata.cfg.lens_shading); + break; + + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = MT9P012_STEPS_NEAR_TO_CLOSEST_INF; + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + default: + rc = -EINVAL; + break; + } + + mutex_unlock(&mt9p012_mut); + return rc; +} + +int mt9p012_sensor_release(void) +{ + int rc = -EBADF; + + mutex_lock(&mt9p012_mut); + + mt9p012_power_down(); + + gpio_direction_output(mt9p012_ctrl->sensordata->sensor_reset, 0); + gpio_free(mt9p012_ctrl->sensordata->sensor_reset); + + gpio_direction_output(mt9p012_ctrl->sensordata->vcm_pwd, 0); + gpio_free(mt9p012_ctrl->sensordata->vcm_pwd); + + kfree(mt9p012_ctrl); + mt9p012_ctrl = NULL; + + CDBG("mt9p012_release completed\n"); + + mutex_unlock(&mt9p012_mut); + return rc; +} + +static int mt9p012_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("mt9p012_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + mt9p012_sensorw = kzalloc(sizeof(struct mt9p012_work), GFP_KERNEL); + if (!mt9p012_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, mt9p012_sensorw); + mt9p012_init_client(client); + mt9p012_client = client; + + mdelay(50); + + CDBG("mt9p012_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("mt9p012_probe failed! rc = %d\n", rc); + return rc; +} + +static int __exit mt9p012_remove(struct i2c_client *client) +{ + struct mt9p012_work_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + mt9p012_client = NULL; + kfree(sensorw); + return 0; +} + +static const struct i2c_device_id mt9p012_i2c_id[] = { + {"mt9p012", 0} +}; + +static struct i2c_driver mt9p012_i2c_driver = { + .id_table = mt9p012_i2c_id, + .probe = mt9p012_i2c_probe, + .remove = __exit_p(mt9p012_i2c_remove), + .driver = { + .name = "mt9p012", + }, +}; + +static int mt9p012_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = i2c_add_driver(&mt9p012_i2c_driver); + if (rc < 0 || mt9p012_client == NULL) { + rc = -ENOTSUPP; + goto probe_done; + } + + msm_camio_clk_rate_set(MT9P012_DEFAULT_CLOCK_RATE); + mdelay(20); + + rc = mt9p012_probe_init_sensor(info); + if (rc < 0) + goto probe_done; + + s->s_init = mt9p012_sensor_open_init; + s->s_release = mt9p012_sensor_release; + s->s_config = mt9p012_sensor_config; + s->s_mount_angle = 0; + mt9p012_probe_init_done(info); + +probe_done: + CDBG("%s %s:%d\n", __FILE__, __func__, __LINE__); + return rc; +} + +static int __mt9p012_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, mt9p012_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __mt9p012_probe, + .driver = { + .name = "msm_camera_mt9p012", + .owner = THIS_MODULE, + }, +}; + +static int __init mt9p012_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(mt9p012_init); +void mt9p012_exit(void) +{ + i2c_del_driver(&mt9p012_i2c_driver); +} diff --git a/drivers/media/video/msm/mt9p012_fox.c b/drivers/media/video/msm/mt9p012_fox.c new file mode 100644 index 00000000000..4a732f3098e --- /dev/null +++ b/drivers/media/video/msm/mt9p012_fox.c @@ -0,0 +1,1345 @@ +/* Copyright (c) 2009, 2011 Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mt9p012.h" + +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ +#define MT9P012_REG_MODEL_ID 0x0000 +#define MT9P012_MODEL_ID 0x2801 +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD 0x0100 +#define GROUPED_PARAMETER_UPDATE 0x0000 +#define REG_COARSE_INT_TIME 0x3012 +#define REG_VT_PIX_CLK_DIV 0x0300 +#define REG_VT_SYS_CLK_DIV 0x0302 +#define REG_PRE_PLL_CLK_DIV 0x0304 +#define REG_PLL_MULTIPLIER 0x0306 +#define REG_OP_PIX_CLK_DIV 0x0308 +#define REG_OP_SYS_CLK_DIV 0x030A +#define REG_SCALE_M 0x0404 +#define REG_FRAME_LENGTH_LINES 0x300A +#define REG_LINE_LENGTH_PCK 0x300C +#define REG_X_ADDR_START 0x3004 +#define REG_Y_ADDR_START 0x3002 +#define REG_X_ADDR_END 0x3008 +#define REG_Y_ADDR_END 0x3006 +#define REG_X_OUTPUT_SIZE 0x034C +#define REG_Y_OUTPUT_SIZE 0x034E +#define REG_FINE_INTEGRATION_TIME 0x3014 +#define REG_ROW_SPEED 0x3016 +#define MT9P012_REG_RESET_REGISTER 0x301A +#define MT9P012_RESET_REGISTER_PWON 0x10CC +#define MT9P012_RESET_REGISTER_PWOFF 0x10C8 +#define REG_READ_MODE 0x3040 +#define REG_GLOBAL_GAIN 0x305E +#define REG_TEST_PATTERN_MODE 0x3070 + +#define MT9P012_REV_7 + +enum mt9p012_test_mode { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum mt9p012_resolution { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; + +enum mt9p012_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +enum mt9p012_setting { + RES_PREVIEW, + RES_CAPTURE +}; + +/* actuator's Slave Address */ +#define MT9P012_AF_I2C_ADDR 0x18 + +/* AF Total steps parameters */ +#define MT9P012_STEPS_NEAR_TO_CLOSEST_INF 32 +#define MT9P012_TOTAL_STEPS_NEAR_TO_FAR 32 + +#define MT9P012_MU5M0_PREVIEW_DUMMY_PIXELS 0 +#define MT9P012_MU5M0_PREVIEW_DUMMY_LINES 0 + +/* Time in milisecs for waiting for the sensor to reset.*/ +#define MT9P012_RESET_DELAY_MSECS 66 + +/* for 20 fps preview */ +#define MT9P012_DEFAULT_CLOCK_RATE 24000000 +#define MT9P012_DEFAULT_MAX_FPS 26 /* ???? */ + +struct mt9p012_work { + struct work_struct work; +}; +static struct mt9p012_work *mt9p012_sensorw; +static struct i2c_client *mt9p012_client; + +struct mt9p012_ctrl { + const struct msm_camera_sensor_info *sensordata; + + int sensormode; + uint32_t fps_divider; /* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ + + uint16_t curr_lens_pos; + uint16_t init_curr_lens_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + + enum mt9p012_resolution prev_res; + enum mt9p012_resolution pict_res; + enum mt9p012_resolution curr_res; + enum mt9p012_test_mode set_test; +}; +static uint16_t update_type = UPDATE_PERIODIC; +static struct mt9p012_ctrl *mt9p012_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(mt9p012_wait_queue); +DEFINE_MUTEX(mt9p012_mut); + + +/*=============================================================*/ + +static int mt9p012_i2c_rxdata(unsigned short saddr, unsigned char *rxdata, + int length) +{ + int retry_cnt = 0; + int rc; + + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + + do { + rc = i2c_transfer(mt9p012_client->adapter, msgs, 2); + if (rc > 0) + break; + retry_cnt++; + } while (retry_cnt < 3); + + if (rc < 0) { + pr_err("mt9p012_i2c_rxdata failed!:%d %d\n", rc, retry_cnt); + return -EIO; + } + + return 0; +} + +static int32_t mt9p012_i2c_read_w(unsigned short saddr, unsigned short raddr, + unsigned short *rdata) +{ + int32_t rc = 0; + unsigned char buf[4]; + + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + + rc = mt9p012_i2c_rxdata(saddr, buf, 2); + if (rc < 0) + return rc; + + *rdata = buf[0] << 8 | buf[1]; + + if (rc < 0) + CDBG("mt9p012_i2c_read failed!\n"); + + return rc; +} + +static int32_t mt9p012_i2c_txdata(unsigned short saddr, unsigned char *txdata, + int length) +{ + int retry_cnt = 0; + int rc; + + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + + do { + rc = i2c_transfer(mt9p012_client->adapter, msg, 1); + if (rc > 0) + break; + retry_cnt++; + } while (retry_cnt < 3); + + if (rc < 0) { + pr_err("mt9p012_i2c_txdata failed: %d %d\n", rc, retry_cnt); + return -EIO; + } + + return 0; +} + +static int32_t mt9p012_i2c_write_b(unsigned short saddr, unsigned short baddr, + unsigned short bdata) +{ + int32_t rc = -EIO; + unsigned char buf[2]; + + memset(buf, 0, sizeof(buf)); + buf[0] = baddr; + buf[1] = bdata; + rc = mt9p012_i2c_txdata(saddr, buf, 2); + + if (rc < 0) + CDBG("i2c_write failed, saddr = 0x%x addr = 0x%x, val =0x%x!\n", + saddr, baddr, bdata); + + return rc; +} + +static int32_t mt9p012_i2c_write_w(unsigned short saddr, unsigned short waddr, + unsigned short wdata) +{ + int32_t rc = -EIO; + unsigned char buf[4]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + + rc = mt9p012_i2c_txdata(saddr, buf, 4); + + if (rc < 0) + CDBG("i2c_write_w failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + + return rc; +} + +static int32_t mt9p012_i2c_write_w_table(struct mt9p012_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + + for (i = 0; i < num; i++) { + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + + return rc; +} + +static int32_t mt9p012_test(enum mt9p012_test_mode mo) +{ + int32_t rc = 0; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + if (mo == TEST_OFF) + return 0; + else { + rc = mt9p012_i2c_write_w_table(mt9p012_regs.ttbl, + mt9p012_regs.ttbl_size); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_TEST_PATTERN_MODE, (uint16_t) mo); + if (rc < 0) + return rc; + } + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t mt9p012_lens_shading_enable(uint8_t is_enable) +{ + int32_t rc = 0; + + CDBG("%s: entered. enable = %d\n", __func__, is_enable); + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, 0x3780, + ((uint16_t) is_enable) << 15); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + + CDBG("%s: exiting. rc = %d\n", __func__, rc); + return rc; +} + +static int32_t mt9p012_set_lc(void) +{ + int32_t rc; + + rc = mt9p012_i2c_write_w_table(mt9p012_regs.rftbl, + mt9p012_regs.rftbl_size); + + return rc; +} + +static void mt9p012_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider; /*Q10 */ + uint32_t pclk_mult; /*Q10 */ + uint32_t d1; + uint32_t d2; + + d1 = + (uint32_t)( + (mt9p012_regs.reg_pat[RES_PREVIEW].frame_length_lines * + 0x00000400) / + mt9p012_regs.reg_pat[RES_CAPTURE].frame_length_lines); + + d2 = + (uint32_t)( + (mt9p012_regs.reg_pat[RES_PREVIEW].line_length_pck * + 0x00000400) / + mt9p012_regs.reg_pat[RES_CAPTURE].line_length_pck); + + divider = (uint32_t) (d1 * d2) / 0x00000400; + + pclk_mult = + (uint32_t) ((mt9p012_regs.reg_pat[RES_CAPTURE].pll_multiplier * + 0x00000400) / + (mt9p012_regs.reg_pat[RES_PREVIEW].pll_multiplier)); + + /* Verify PCLK settings and frame sizes. */ + *pfps = (uint16_t) (fps * divider * pclk_mult / 0x00000400 / + 0x00000400); +} + +static uint16_t mt9p012_get_prev_lines_pf(void) +{ + if (mt9p012_ctrl->prev_res == QTR_SIZE) + return mt9p012_regs.reg_pat[RES_PREVIEW].frame_length_lines; + else + return mt9p012_regs.reg_pat[RES_CAPTURE].frame_length_lines; +} + +static uint16_t mt9p012_get_prev_pixels_pl(void) +{ + if (mt9p012_ctrl->prev_res == QTR_SIZE) + return mt9p012_regs.reg_pat[RES_PREVIEW].line_length_pck; + else + return mt9p012_regs.reg_pat[RES_CAPTURE].line_length_pck; +} + +static uint16_t mt9p012_get_pict_lines_pf(void) +{ + return mt9p012_regs.reg_pat[RES_CAPTURE].frame_length_lines; +} + +static uint16_t mt9p012_get_pict_pixels_pl(void) +{ + return mt9p012_regs.reg_pat[RES_CAPTURE].line_length_pck; +} + +static uint32_t mt9p012_get_pict_max_exp_lc(void) +{ + uint16_t snapshot_lines_per_frame; + + if (mt9p012_ctrl->pict_res == QTR_SIZE) + snapshot_lines_per_frame = + mt9p012_regs.reg_pat[RES_PREVIEW].frame_length_lines - 1; + else + snapshot_lines_per_frame = + mt9p012_regs.reg_pat[RES_CAPTURE].frame_length_lines - 1; + + return snapshot_lines_per_frame * 24; +} + +static int32_t mt9p012_set_fps(struct fps_cfg *fps) +{ + /* input is new fps in Q10 format */ + int32_t rc = 0; + enum mt9p012_setting setting; + + mt9p012_ctrl->fps_divider = fps->fps_div; + mt9p012_ctrl->pict_fps_divider = fps->pict_fps_div; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return -EBUSY; + + if (mt9p012_ctrl->sensormode == SENSOR_PREVIEW_MODE) + setting = RES_PREVIEW; + else + setting = RES_CAPTURE; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_FRAME_LENGTH_LINES, + (mt9p012_regs.reg_pat[setting].frame_length_lines * + fps->fps_div / 0x00000400)); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + + return rc; +} + +static int32_t mt9p012_write_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t max_legal_gain = 0x01FF; + uint32_t line_length_ratio = 0x00000400; + enum mt9p012_setting setting; + int32_t rc = 0; + + CDBG("Line:%d mt9p012_write_exp_gain \n", __LINE__); + + if (mt9p012_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + mt9p012_ctrl->my_reg_gain = gain; + mt9p012_ctrl->my_reg_line_count = (uint16_t) line; + } + + if (gain > max_legal_gain) { + CDBG("Max legal gain Line:%d \n", __LINE__); + gain = max_legal_gain; + } + + /* Verify no overflow */ + if (mt9p012_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + line = (uint32_t) (line * mt9p012_ctrl->fps_divider / + 0x00000400); + setting = RES_PREVIEW; + } else { + line = (uint32_t) (line * mt9p012_ctrl->pict_fps_divider / + 0x00000400); + setting = RES_CAPTURE; + } + + /* Set digital gain to 1 */ +#ifdef MT9P012_REV_7 + gain |= 0x1000; +#else + gain |= 0x0200; +#endif + + if ((mt9p012_regs.reg_pat[setting].frame_length_lines - 1) < line) { + line_length_ratio = (uint32_t) (line * 0x00000400) / + (mt9p012_regs.reg_pat[setting].frame_length_lines - 1); + } else + line_length_ratio = 0x00000400; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, REG_GLOBAL_GAIN, gain); + if (rc < 0) { + CDBG("mt9p012_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_COARSE_INT_TIME, line); + if (rc < 0) { + CDBG("mt9p012_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + CDBG("mt9p012_write_exp_gain: gain = %d, line = %d\n", gain, line); + + return rc; +} + +static int32_t mt9p012_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + + CDBG("Line:%d mt9p012_set_pict_exp_gain \n", __LINE__); + + rc = mt9p012_write_exp_gain(gain, line); + if (rc < 0) { + CDBG("Line:%d mt9p012_set_pict_exp_gain failed... \n", + __LINE__); + return rc; + } + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, 0x10CC | 0x0002); + if (rc < 0) { + CDBG("mt9p012_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + mdelay(5); + + /* camera_timed_wait(snapshot_wait*exposure_ratio); */ + return rc; +} + +static int32_t mt9p012_setting(enum mt9p012_reg_update rupdate, + enum mt9p012_setting rt) +{ + int32_t rc = 0; + switch (rupdate) { + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct mt9p012_i2c_reg_conf ppc_tbl[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + {REG_ROW_SPEED, + mt9p012_regs.reg_pat[rt].row_speed}, + {REG_X_ADDR_START, + mt9p012_regs.reg_pat[rt].x_addr_start}, + {REG_X_ADDR_END, + mt9p012_regs.reg_pat[rt].x_addr_end}, + {REG_Y_ADDR_START, + mt9p012_regs.reg_pat[rt].y_addr_start}, + {REG_Y_ADDR_END, + mt9p012_regs.reg_pat[rt].y_addr_end}, + {REG_READ_MODE, + mt9p012_regs.reg_pat[rt].read_mode}, + {REG_SCALE_M, mt9p012_regs.reg_pat[rt].scale_m}, + {REG_X_OUTPUT_SIZE, + mt9p012_regs.reg_pat[rt].x_output_size}, + {REG_Y_OUTPUT_SIZE, + mt9p012_regs.reg_pat[rt].y_output_size}, + + {REG_LINE_LENGTH_PCK, + mt9p012_regs.reg_pat[rt].line_length_pck}, + {REG_FRAME_LENGTH_LINES, + (mt9p012_regs.reg_pat[rt].frame_length_lines * + mt9p012_ctrl->fps_divider / 0x00000400)}, + {REG_COARSE_INT_TIME, + mt9p012_regs.reg_pat[rt].coarse_int_time}, + {REG_FINE_INTEGRATION_TIME, + mt9p012_regs.reg_pat[rt].fine_int_time}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE}, + }; + if (update_type == REG_INIT) { + update_type = rupdate; + return rc; + } + rc = mt9p012_i2c_write_w_table(&ppc_tbl[0], + ARRAY_SIZE(ppc_tbl)); + if (rc < 0) + return rc; + + rc = mt9p012_test(mt9p012_ctrl->set_test); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWON | + 0x0002); + if (rc < 0) + return rc; + + mdelay(5); /* 15? wait for sensor to transition */ + + return rc; + } + break; /* UPDATE_PERIODIC */ + + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct mt9p012_i2c_reg_conf ipc_tbl1[] = { + {MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWOFF}, + {REG_VT_PIX_CLK_DIV, + mt9p012_regs.reg_pat[rt].vt_pix_clk_div}, + {REG_VT_SYS_CLK_DIV, + mt9p012_regs.reg_pat[rt].vt_sys_clk_div}, + {REG_PRE_PLL_CLK_DIV, + mt9p012_regs.reg_pat[rt].pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + mt9p012_regs.reg_pat[rt].pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + mt9p012_regs.reg_pat[rt].op_pix_clk_div}, + {REG_OP_SYS_CLK_DIV, + mt9p012_regs.reg_pat[rt].op_sys_clk_div}, +#ifdef MT9P012_REV_7 + {0x30B0, 0x0001}, + {0x308E, 0xE060}, + {0x3092, 0x0A52}, + {0x3094, 0x4656}, + {0x3096, 0x5652}, + {0x30CA, 0x8006}, + {0x312A, 0xDD02}, + {0x312C, 0x00E4}, + {0x3170, 0x299A}, +#endif + /* optimized settings for noise */ + {0x3088, 0x6FF6}, + {0x3154, 0x0282}, + {0x3156, 0x0381}, + {0x3162, 0x04CE}, + {0x0204, 0x0010}, + {0x0206, 0x0010}, + {0x0208, 0x0010}, + {0x020A, 0x0010}, + {0x020C, 0x0010}, + {MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWON}, + }; + + struct mt9p012_i2c_reg_conf ipc_tbl2[] = { + {MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWOFF}, + {REG_VT_PIX_CLK_DIV, + mt9p012_regs.reg_pat[rt].vt_pix_clk_div}, + {REG_VT_SYS_CLK_DIV, + mt9p012_regs.reg_pat[rt].vt_sys_clk_div}, + {REG_PRE_PLL_CLK_DIV, + mt9p012_regs.reg_pat[rt].pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + mt9p012_regs.reg_pat[rt].pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + mt9p012_regs.reg_pat[rt].op_pix_clk_div}, + {REG_OP_SYS_CLK_DIV, + mt9p012_regs.reg_pat[rt].op_sys_clk_div}, +#ifdef MT9P012_REV_7 + {0x30B0, 0x0001}, + {0x308E, 0xE060}, + {0x3092, 0x0A52}, + {0x3094, 0x4656}, + {0x3096, 0x5652}, + {0x30CA, 0x8006}, + {0x312A, 0xDD02}, + {0x312C, 0x00E4}, + {0x3170, 0x299A}, +#endif + /* optimized settings for noise */ + {0x3088, 0x6FF6}, + {0x3154, 0x0282}, + {0x3156, 0x0381}, + {0x3162, 0x04CE}, + {0x0204, 0x0010}, + {0x0206, 0x0010}, + {0x0208, 0x0010}, + {0x020A, 0x0010}, + {0x020C, 0x0010}, + {MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWON}, + }; + + struct mt9p012_i2c_reg_conf ipc_tbl3[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + /* Set preview or snapshot mode */ + {REG_ROW_SPEED, + mt9p012_regs.reg_pat[rt].row_speed}, + {REG_X_ADDR_START, + mt9p012_regs.reg_pat[rt].x_addr_start}, + {REG_X_ADDR_END, + mt9p012_regs.reg_pat[rt].x_addr_end}, + {REG_Y_ADDR_START, + mt9p012_regs.reg_pat[rt].y_addr_start}, + {REG_Y_ADDR_END, + mt9p012_regs.reg_pat[rt].y_addr_end}, + {REG_READ_MODE, + mt9p012_regs.reg_pat[rt].read_mode}, + {REG_SCALE_M, mt9p012_regs.reg_pat[rt].scale_m}, + {REG_X_OUTPUT_SIZE, + mt9p012_regs.reg_pat[rt].x_output_size}, + {REG_Y_OUTPUT_SIZE, + mt9p012_regs.reg_pat[rt].y_output_size}, + {REG_LINE_LENGTH_PCK, + mt9p012_regs.reg_pat[rt].line_length_pck}, + {REG_FRAME_LENGTH_LINES, + mt9p012_regs.reg_pat[rt].frame_length_lines}, + {REG_COARSE_INT_TIME, + mt9p012_regs.reg_pat[rt].coarse_int_time}, + {REG_FINE_INTEGRATION_TIME, + mt9p012_regs.reg_pat[rt].fine_int_time}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE}, + }; + + /* reset fps_divider */ + mt9p012_ctrl->fps_divider = 1 * 0x0400; + + rc = mt9p012_i2c_write_w_table(&ipc_tbl1[0], + ARRAY_SIZE(ipc_tbl1)); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w_table(&ipc_tbl2[0], + ARRAY_SIZE(ipc_tbl2)); + if (rc < 0) + return rc; + + mdelay(5); + + rc = mt9p012_i2c_write_w_table(&ipc_tbl3[0], + ARRAY_SIZE(ipc_tbl3)); + if (rc < 0) + return rc; + + /* load lens shading */ + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + rc = mt9p012_set_lc(); + if (rc < 0) + return rc; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + + if (rc < 0) + return rc; + } + update_type = rupdate; + break; /* case REG_INIT: */ + + default: + rc = -EINVAL; + break; + } /* switch (rupdate) */ + + return rc; +} + +static int32_t mt9p012_video_config(int mode, int res) +{ + int32_t rc; + + switch (res) { + case QTR_SIZE: + rc = mt9p012_setting(UPDATE_PERIODIC, RES_PREVIEW); + if (rc < 0) + return rc; + + CDBG("mt9p012 sensor configuration done!\n"); + break; + + case FULL_SIZE: + rc = mt9p012_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + break; + + default: + return 0; + } /* switch */ + + mt9p012_ctrl->prev_res = res; + mt9p012_ctrl->curr_res = res; + mt9p012_ctrl->sensormode = mode; + + rc = mt9p012_write_exp_gain(mt9p012_ctrl->my_reg_gain, + mt9p012_ctrl->my_reg_line_count); + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, 0x10cc | 0x0002); + + return rc; +} + +static int32_t mt9p012_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = mt9p012_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + mt9p012_ctrl->curr_res = mt9p012_ctrl->pict_res; + + mt9p012_ctrl->sensormode = mode; + + return rc; +} + +static int32_t mt9p012_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = mt9p012_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + mt9p012_ctrl->curr_res = mt9p012_ctrl->pict_res; + + mt9p012_ctrl->sensormode = mode; + + return rc; +} + +static int32_t mt9p012_power_down(void) +{ + int32_t rc = 0; + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWOFF); + + mdelay(5); + return rc; +} + +static int32_t mt9p012_move_focus(int direction, int32_t num_steps) +{ + int16_t step_direction; + int16_t actual_step; + int16_t next_position; + uint8_t code_val_msb, code_val_lsb; + + if (num_steps > MT9P012_TOTAL_STEPS_NEAR_TO_FAR) + num_steps = MT9P012_TOTAL_STEPS_NEAR_TO_FAR; + else if (num_steps == 0) { + CDBG("mt9p012_move_focus failed at line %d ...\n", __LINE__); + return -EINVAL; + } + + if (direction == MOVE_NEAR) + step_direction = 16; /* 10bit */ + else if (direction == MOVE_FAR) + step_direction = -16; /* 10 bit */ + else { + CDBG("mt9p012_move_focus failed at line %d ...\n", __LINE__); + return -EINVAL; + } + + if (mt9p012_ctrl->curr_lens_pos < mt9p012_ctrl->init_curr_lens_pos) + mt9p012_ctrl->curr_lens_pos = mt9p012_ctrl->init_curr_lens_pos; + + actual_step = (int16_t) (step_direction * (int16_t) num_steps); + next_position = (int16_t) (mt9p012_ctrl->curr_lens_pos + actual_step); + + if (next_position > 1023) + next_position = 1023; + else if (next_position < 0) + next_position = 0; + + code_val_msb = next_position >> 4; + code_val_lsb = (next_position & 0x000F) << 4; + /* code_val_lsb |= mode_mask; */ + + /* Writing the digital code for current to the actuator */ + if (mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, + code_val_msb, code_val_lsb) < 0) { + CDBG("mt9p012_move_focus failed at line %d ...\n", __LINE__); + return -EBUSY; + } + + /* Storing the current lens Position */ + mt9p012_ctrl->curr_lens_pos = next_position; + + return 0; +} + +static int32_t mt9p012_set_default_focus(void) +{ + int32_t rc = 0; + uint8_t code_val_msb, code_val_lsb; + + code_val_msb = 0x00; + code_val_lsb = 0x00; + + /* Write the digital code for current to the actuator */ + rc = mt9p012_i2c_write_b(MT9P012_AF_I2C_ADDR >> 1, + code_val_msb, code_val_lsb); + + mt9p012_ctrl->curr_lens_pos = 0; + mt9p012_ctrl->init_curr_lens_pos = 0; + + return rc; +} + +static int mt9p012_probe_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + return 0; +} + +static int mt9p012_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + uint16_t chipid; + + rc = gpio_request(data->sensor_reset, "mt9p012"); + if (!rc) + gpio_direction_output(data->sensor_reset, 1); + else + goto init_probe_done; + + msleep(20); + + /* RESET the sensor image part via I2C command */ + CDBG("mt9p012_sensor_init(): reseting sensor.\n"); + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, 0x10CC | 0x0001); + if (rc < 0) { + CDBG("sensor reset failed. rc = %d\n", rc); + goto init_probe_fail; + } + + msleep(MT9P012_RESET_DELAY_MSECS); + + /* 3. Read sensor Model ID: */ + rc = mt9p012_i2c_read_w(mt9p012_client->addr, + MT9P012_REG_MODEL_ID, &chipid); + if (rc < 0) + goto init_probe_fail; + + /* 4. Compare sensor ID to MT9T012VC ID: */ + if (chipid != MT9P012_MODEL_ID) { + CDBG("mt9p012 wrong model_id = 0x%x\n", chipid); + rc = -ENODEV; + goto init_probe_fail; + } + + rc = mt9p012_i2c_write_w(mt9p012_client->addr, 0x306E, 0x9000); + if (rc < 0) { + CDBG("REV_7 write failed. rc = %d\n", rc); + goto init_probe_fail; + } + + /* RESET_REGISTER, enable parallel interface and disable serialiser */ + CDBG("mt9p012_sensor_init(): enabling parallel interface.\n"); + rc = mt9p012_i2c_write_w(mt9p012_client->addr, 0x301A, 0x10CC); + if (rc < 0) { + CDBG("enable parallel interface failed. rc = %d\n", rc); + goto init_probe_fail; + } + + /* To disable the 2 extra lines */ + rc = mt9p012_i2c_write_w(mt9p012_client->addr, 0x3064, 0x0805); + + if (rc < 0) { + CDBG("disable the 2 extra lines failed. rc = %d\n", rc); + goto init_probe_fail; + } + goto init_probe_done; + +init_probe_fail: + mt9p012_probe_init_done(data); +init_probe_done: + return rc; +} + +static int mt9p012_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + + mt9p012_ctrl = kzalloc(sizeof(struct mt9p012_ctrl), GFP_KERNEL); + if (!mt9p012_ctrl) { + CDBG("mt9p012_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + + mt9p012_ctrl->fps_divider = 1 * 0x00000400; + mt9p012_ctrl->pict_fps_divider = 1 * 0x00000400; + mt9p012_ctrl->set_test = TEST_OFF; + mt9p012_ctrl->prev_res = QTR_SIZE; + mt9p012_ctrl->pict_res = FULL_SIZE; + + if (data) + mt9p012_ctrl->sensordata = data; + + msm_camio_camif_pad_reg_reset(); + mdelay(20); + + rc = mt9p012_probe_init_sensor(data); + if (rc < 0) + goto init_fail1; + + if (mt9p012_ctrl->prev_res == QTR_SIZE) + rc = mt9p012_setting(REG_INIT, RES_PREVIEW); + else + rc = mt9p012_setting(REG_INIT, RES_CAPTURE); + + if (rc < 0) { + CDBG("mt9p012_setting failed. rc = %d\n", rc); + goto init_fail1; + } + + /* sensor : output enable */ + CDBG("mt9p012_sensor_open_init(): enabling output.\n"); + rc = mt9p012_i2c_write_w(mt9p012_client->addr, + MT9P012_REG_RESET_REGISTER, + MT9P012_RESET_REGISTER_PWON); + if (rc < 0) { + CDBG("sensor output enable failed. rc = %d\n", rc); + goto init_fail1; + } + + /* enable AF actuator */ + if (mt9p012_ctrl->sensordata->vcm_enable) { + CDBG("enable AF actuator, gpio = %d\n", + mt9p012_ctrl->sensordata->vcm_pwd); + rc = gpio_request(mt9p012_ctrl->sensordata->vcm_pwd, + "mt9p012"); + if (!rc) + gpio_direction_output( + mt9p012_ctrl->sensordata->vcm_pwd, + 1); + else { + CDBG("mt9p012_ctrl gpio request failed!\n"); + goto init_fail1; + } + msleep(20); + rc = mt9p012_set_default_focus(); + if (rc < 0) { + gpio_direction_output(mt9p012_ctrl->sensordata->vcm_pwd, + 0); + gpio_free(mt9p012_ctrl->sensordata->vcm_pwd); + } + } + if (rc >= 0) + goto init_done; +init_fail1: + mt9p012_probe_init_done(data); + kfree(mt9p012_ctrl); +init_done: + return rc; +} + +static int mt9p012_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&mt9p012_wait_queue); + return 0; +} + +static int32_t mt9p012_set_sensor_mode(int mode, int res) +{ + int32_t rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = mt9p012_video_config(mode, res); + break; + + case SENSOR_SNAPSHOT_MODE: + rc = mt9p012_snapshot_config(mode); + break; + + case SENSOR_RAW_SNAPSHOT_MODE: + rc = mt9p012_raw_snapshot_config(mode); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +int mt9p012_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + int rc = 0; + + if (copy_from_user(&cdata, + (void *)argp, sizeof(struct sensor_cfg_data))) + return -EFAULT; + + mutex_lock(&mt9p012_mut); + + CDBG("%s: cfgtype = %d\n", __func__, cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + mt9p012_get_pict_fps(cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = mt9p012_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = mt9p012_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = mt9p012_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = mt9p012_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = mt9p012_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = mt9p012_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = mt9p012_write_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + CDBG("Line:%d CFG_SET_PICT_EXP_GAIN \n", __LINE__); + rc = mt9p012_set_pict_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = mt9p012_set_sensor_mode(cdata.mode, cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = mt9p012_power_down(); + break; + + case CFG_MOVE_FOCUS: + CDBG("mt9p012_ioctl: CFG_MOVE_FOCUS: cdata.cfg.focus.dir=%d \ + cdata.cfg.focus.steps=%d\n", + cdata.cfg.focus.dir, cdata.cfg.focus.steps); + rc = mt9p012_move_focus(cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = mt9p012_set_default_focus(); + break; + + case CFG_SET_LENS_SHADING: + CDBG("%s: CFG_SET_LENS_SHADING\n", __func__); + rc = mt9p012_lens_shading_enable(cdata.cfg.lens_shading); + break; + + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = MT9P012_STEPS_NEAR_TO_CLOSEST_INF; + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_EFFECT: + default: + rc = -EINVAL; + break; + } + + mutex_unlock(&mt9p012_mut); + return rc; +} + +int mt9p012_sensor_release(void) +{ + int rc = -EBADF; + + mutex_lock(&mt9p012_mut); + + mt9p012_power_down(); + + gpio_direction_output(mt9p012_ctrl->sensordata->sensor_reset, 0); + gpio_free(mt9p012_ctrl->sensordata->sensor_reset); + + if (mt9p012_ctrl->sensordata->vcm_enable) { + gpio_direction_output(mt9p012_ctrl->sensordata->vcm_pwd, 0); + gpio_free(mt9p012_ctrl->sensordata->vcm_pwd); + } + + kfree(mt9p012_ctrl); + mt9p012_ctrl = NULL; + + CDBG("mt9p012_release completed\n"); + + mutex_unlock(&mt9p012_mut); + return rc; +} + +static int mt9p012_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("mt9p012_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + mt9p012_sensorw = kzalloc(sizeof(struct mt9p012_work), GFP_KERNEL); + if (!mt9p012_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, mt9p012_sensorw); + mt9p012_init_client(client); + mt9p012_client = client; + + mdelay(50); + + CDBG("mt9p012_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("mt9p012_probe failed! rc = %d\n", rc); + return rc; +} + +static const struct i2c_device_id mt9p012_i2c_id[] = { + {"mt9p012", 0}, + {} +}; + +static struct i2c_driver mt9p012_i2c_driver = { + .id_table = mt9p012_i2c_id, + .probe = mt9p012_i2c_probe, + .remove = __exit_p(mt9p012_i2c_remove), + .driver = { + .name = "mt9p012", + }, +}; + +static int mt9p012_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = i2c_add_driver(&mt9p012_i2c_driver); + if (rc < 0 || mt9p012_client == NULL) { + rc = -ENOTSUPP; + goto probe_done; + } + + msm_camio_clk_rate_set(MT9P012_DEFAULT_CLOCK_RATE); + mdelay(20); + + rc = mt9p012_probe_init_sensor(info); + if (rc < 0) + goto probe_done; + + s->s_init = mt9p012_sensor_open_init; + s->s_release = mt9p012_sensor_release; + s->s_config = mt9p012_sensor_config; + s->s_mount_angle = 0; + mt9p012_probe_init_done(info); + +probe_done: + CDBG("%s %s:%d\n", __FILE__, __func__, __LINE__); + return rc; +} + +static int __mt9p012_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, mt9p012_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __mt9p012_probe, + .driver = { + .name = "msm_camera_mt9p012", + .owner = THIS_MODULE, + }, +}; + +static int __init mt9p012_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(mt9p012_init); diff --git a/drivers/media/video/msm/mt9p012_km.c b/drivers/media/video/msm/mt9p012_km.c new file mode 100644 index 00000000000..c20064c0401 --- /dev/null +++ b/drivers/media/video/msm/mt9p012_km.c @@ -0,0 +1,1295 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mt9p012_km.h" + +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ + +#define MT9P012_KM_REG_MODEL_ID 0x0000 +#define MT9P012_KM_MODEL_ID 0x2800 +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD 0x0100 +#define GROUPED_PARAMETER_UPDATE 0x0000 +#define REG_COARSE_INT_TIME 0x3012 +#define REG_VT_PIX_CLK_DIV 0x0300 +#define REG_VT_SYS_CLK_DIV 0x0302 +#define REG_PRE_PLL_CLK_DIV 0x0304 +#define REG_PLL_MULTIPLIER 0x0306 +#define REG_OP_PIX_CLK_DIV 0x0308 +#define REG_OP_SYS_CLK_DIV 0x030A +#define REG_SCALE_M 0x0404 +#define REG_FRAME_LENGTH_LINES 0x300A +#define REG_LINE_LENGTH_PCK 0x300C +#define REG_X_ADDR_START 0x3004 +#define REG_Y_ADDR_START 0x3002 +#define REG_X_ADDR_END 0x3008 +#define REG_Y_ADDR_END 0x3006 +#define REG_X_OUTPUT_SIZE 0x034C +#define REG_Y_OUTPUT_SIZE 0x034E +#define REG_FINE_INTEGRATION_TIME 0x3014 +#define REG_ROW_SPEED 0x3016 +#define MT9P012_KM_REG_RESET_REGISTER 0x301A +#define MT9P012_KM_RESET_REGISTER_PWON 0x10CC +#define MT9P012_KM_RESET_REGISTER_PWOFF 0x10C8 +#define REG_READ_MODE 0x3040 +#define REG_GLOBAL_GAIN 0x305E +#define REG_TEST_PATTERN_MODE 0x3070 + +enum mt9p012_km_test_mode { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum mt9p012_km_resolution { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; + +enum mt9p012_km_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +enum mt9p012_km_setting { + RES_PREVIEW, + RES_CAPTURE +}; + +uint8_t mode_mask = 0x04; + +/* actuator's Slave Address */ +#define MT9P012_KM_AF_I2C_ADDR (0x18 >> 1) + +/* AF Total steps parameters */ +#define MT9P012_KM_STEPS_NEAR_TO_CLOSEST_INF 30 +#define MT9P012_KM_TOTAL_STEPS_NEAR_TO_FAR 30 + +/* Time in milisecs for waiting for the sensor to reset.*/ +#define MT9P012_KM_RESET_DELAY_MSECS 66 + +/* for 20 fps preview */ +#define MT9P012_KM_DEFAULT_CLOCK_RATE 24000000 + +struct mt9p012_km_work { + struct work_struct work; +}; +static struct mt9p012_km_work *mt9p012_km_sensorw; +static struct i2c_client *mt9p012_km_client; + +struct mt9p012_km_ctrl { + const struct msm_camera_sensor_info *sensordata; + + int sensormode; + uint32_t fps_divider; /* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ + + uint16_t curr_lens_pos; + uint16_t init_curr_lens_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + + enum mt9p012_km_resolution prev_res; + enum mt9p012_km_resolution pict_res; + enum mt9p012_km_resolution curr_res; + enum mt9p012_km_test_mode set_test; +}; +static uint16_t update_type = UPDATE_PERIODIC; +static struct mt9p012_km_ctrl *mt9p012_km_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(mt9p012_km_wait_queue); +DEFINE_MUTEX(mt9p012_km_mut); + +/*=============================================================*/ + +static int mt9p012_km_i2c_rxdata(unsigned short saddr, unsigned char *rxdata, + int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr << 1, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr << 1, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + + if (i2c_transfer(mt9p012_km_client->adapter, msgs, 2) < 0) { + CDBG("mt9p012_km_i2c_rxdata failed!\n"); + return -EIO; + } + + return 0; +} + +static int32_t mt9p012_km_i2c_read_w(unsigned short saddr, unsigned short raddr, + unsigned short *rdata) +{ + int32_t rc = 0; + unsigned char buf[4]; + + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + + rc = mt9p012_km_i2c_rxdata(saddr, buf, 2); + if (rc < 0) + return rc; + + *rdata = buf[0] << 8 | buf[1]; + + if (rc < 0) + CDBG("mt9p012_km_i2c_read failed!\n"); + + return rc; +} + +static int32_t mt9p012_km_i2c_txdata(unsigned short saddr, + unsigned char *txdata, + int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr << 1, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + + if (i2c_transfer(mt9p012_km_client->adapter, msg, 1) < 0) { + CDBG("mt9p012_km_i2c_txdata failed\n"); + return -EIO; + } + + return 0; +} + +static int32_t mt9p012_km_i2c_write_b(unsigned short saddr, + unsigned short baddr, + unsigned short bdata) +{ + int32_t rc = -EIO; + unsigned char buf[2]; + + memset(buf, 0, sizeof(buf)); + buf[0] = baddr; + buf[1] = bdata; + rc = mt9p012_km_i2c_txdata(saddr, buf, 2); + + if (rc < 0) + CDBG("i2c_write failed, saddr = 0x%x addr = 0x%x, val =0x%x!\n", + saddr, baddr, bdata); + + return rc; +} + +static int32_t mt9p012_km_i2c_write_w(unsigned short saddr, + unsigned short waddr, + unsigned short wdata) +{ + int32_t rc = -EIO; + unsigned char buf[4]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + + rc = mt9p012_km_i2c_txdata(saddr, buf, 4); + + if (rc < 0) + CDBG("i2c_write_w failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + + return rc; +} + +static int32_t mt9p012_km_i2c_write_w_table(struct mt9p012_km_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + + for (i = 0; i < num; i++) { + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + + return rc; +} + +static int32_t mt9p012_km_test(enum mt9p012_km_test_mode mo) +{ + int32_t rc = 0; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + if (mo == TEST_OFF) + return 0; + else { + rc = mt9p012_km_i2c_write_w_table(mt9p012_km_regs.ttbl, + mt9p012_km_regs.ttbl_size); + if (rc < 0) + return rc; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_TEST_PATTERN_MODE, (uint16_t) mo); + if (rc < 0) + return rc; + } + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t mt9p012_km_lens_shading_enable(uint8_t is_enable) +{ + int32_t rc = 0; + + CDBG("%s: entered. enable = %d\n", __func__, is_enable); + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, 0x3780, + ((uint16_t) is_enable) << 15); + if (rc < 0) + return rc; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + + CDBG("%s: exiting. rc = %d\n", __func__, rc); + return rc; +} + +static int32_t mt9p012_km_set_lc(void) +{ + int32_t rc; + + rc = mt9p012_km_i2c_write_w_table(mt9p012_km_regs.lctbl, + mt9p012_km_regs.lctbl_size); + + return rc; +} + +static void mt9p012_km_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + + /* input fps is preview fps in Q8 format */ + uint32_t divider; /*Q10 */ + uint32_t pclk_mult; /*Q10 */ + uint32_t d1; + uint32_t d2; + + d1 = + (uint32_t)( + (mt9p012_km_regs.reg_pat[RES_PREVIEW].frame_length_lines * + 0x00000400) / + mt9p012_km_regs.reg_pat[RES_CAPTURE].frame_length_lines); + + d2 = + (uint32_t)( + (mt9p012_km_regs.reg_pat[RES_PREVIEW].line_length_pck * + 0x00000400) / + mt9p012_km_regs.reg_pat[RES_CAPTURE].line_length_pck); + + divider = (uint32_t) (d1 * d2) / 0x00000400; + + pclk_mult = + (uint32_t) ((mt9p012_km_regs.reg_pat[RES_CAPTURE]. + pll_multiplier * 0x00000400) / + (mt9p012_km_regs.reg_pat[RES_PREVIEW].pll_multiplier)); + + + /* Verify PCLK settings and frame sizes. */ + *pfps = (uint16_t)((((fps * pclk_mult) / 0x00000400) * divider)/ + 0x00000400); +} + +static uint16_t mt9p012_km_get_prev_lines_pf(void) +{ + if (mt9p012_km_ctrl->prev_res == QTR_SIZE) + return mt9p012_km_regs.reg_pat[RES_PREVIEW].frame_length_lines; + else + return mt9p012_km_regs.reg_pat[RES_CAPTURE].frame_length_lines; +} + +static uint16_t mt9p012_km_get_prev_pixels_pl(void) +{ + if (mt9p012_km_ctrl->prev_res == QTR_SIZE) + return mt9p012_km_regs.reg_pat[RES_PREVIEW].line_length_pck; + else + return mt9p012_km_regs.reg_pat[RES_CAPTURE].line_length_pck; +} + +static uint16_t mt9p012_km_get_pict_lines_pf(void) +{ + return mt9p012_km_regs.reg_pat[RES_CAPTURE].frame_length_lines; +} + +static uint16_t mt9p012_km_get_pict_pixels_pl(void) +{ + return mt9p012_km_regs.reg_pat[RES_CAPTURE].line_length_pck; +} + +static uint32_t mt9p012_km_get_pict_max_exp_lc(void) +{ + uint16_t snapshot_lines_per_frame; + + if (mt9p012_km_ctrl->pict_res == QTR_SIZE) + snapshot_lines_per_frame = + mt9p012_km_regs.reg_pat[RES_PREVIEW].frame_length_lines - 1; + else + snapshot_lines_per_frame = + mt9p012_km_regs.reg_pat[RES_CAPTURE].frame_length_lines - 1; + + return snapshot_lines_per_frame * 24; +} + +static int32_t mt9p012_km_set_fps(struct fps_cfg *fps) +{ + int32_t rc = 0; + + mt9p012_km_ctrl->fps_divider = fps->fps_div; + mt9p012_km_ctrl->pict_fps_divider = fps->pict_fps_div; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return -EBUSY; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_FRAME_LENGTH_LINES, + mt9p012_km_regs.reg_pat[mt9p012_km_ctrl->sensormode]. + frame_length_lines * + mt9p012_km_ctrl->fps_divider / 0x00000400); + if (rc < 0) + return rc; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + + return rc; +} + + +static int32_t mt9p012_km_write_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t max_legal_gain = 0x01FF; + uint32_t line_length_ratio = 0x00000400; + enum mt9p012_km_setting setting; + int32_t rc = 0; + + CDBG("Line:%d mt9p012_km_write_exp_gain \n", __LINE__); + + if (mt9p012_km_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + mt9p012_km_ctrl->my_reg_gain = gain; + mt9p012_km_ctrl->my_reg_line_count = (uint16_t) line; + } + + if (gain > max_legal_gain) { + CDBG("Max legal gain Line:%d \n", __LINE__); + gain = max_legal_gain; + } + + /* Verify no overflow */ + if (mt9p012_km_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + line = (uint32_t) (line * mt9p012_km_ctrl->fps_divider / + 0x00000400); + setting = RES_PREVIEW; + } else { + line = (uint32_t) (line * mt9p012_km_ctrl->pict_fps_divider / + 0x00000400); + setting = RES_CAPTURE; + } + + gain |= 0x0200; + + if ((mt9p012_km_regs.reg_pat[setting].frame_length_lines - 1) < line) { + line_length_ratio = (uint32_t) (line * 0x00000400) / + (mt9p012_km_regs.reg_pat[setting].frame_length_lines - 1); + } else + line_length_ratio = 0x00000400; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) { + CDBG("mt9p012_km_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GLOBAL_GAIN, gain); + if (rc < 0) { + CDBG("mt9p012_km_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_LINE_LENGTH_PCK, + (uint16_t) (mt9p012_km_regs.reg_pat[setting]. + line_length_pck * line_length_ratio / 0x00000400)); + if (rc < 0) + return rc; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_COARSE_INT_TIME, + (uint16_t) ((line * 0x00000400)/ + line_length_ratio)); + if (rc < 0) { + CDBG("mt9p012_km_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + if (rc < 0) { + CDBG("mt9p012_km_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + CDBG("mt9p012_km_write_exp_gain: gain = %d, line = %d\n", gain, line); + + return rc; +} + +static int32_t mt9p012_km_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + + CDBG("Line:%d mt9p012_km_set_pict_exp_gain \n", __LINE__); + + rc = mt9p012_km_write_exp_gain(gain, line); + if (rc < 0) { + CDBG("Line:%d mt9p012_km_set_pict_exp_gain failed... \n", + __LINE__); + return rc; + } + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + MT9P012_KM_REG_RESET_REGISTER, + 0x10CC | 0x0002); + if (rc < 0) { + CDBG("mt9p012_km_i2c_write_w failed... Line:%d \n", __LINE__); + return rc; + } + + mdelay(5); + + /* camera_timed_wait(snapshot_wait*exposure_ratio); */ + return rc; +} + +static int32_t mt9p012_km_setting(enum mt9p012_km_reg_update rupdate, + enum mt9p012_km_setting rt) +{ + int32_t rc = 0; + + switch (rupdate) { + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + + struct mt9p012_km_i2c_reg_conf ppc_tbl[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + {REG_ROW_SPEED, + mt9p012_km_regs.reg_pat[rt].row_speed}, + {REG_X_ADDR_START, + mt9p012_km_regs.reg_pat[rt].x_addr_start}, + {REG_X_ADDR_END, + mt9p012_km_regs.reg_pat[rt].x_addr_end}, + {REG_Y_ADDR_START, + mt9p012_km_regs.reg_pat[rt].y_addr_start}, + {REG_Y_ADDR_END, + mt9p012_km_regs.reg_pat[rt].y_addr_end}, + {REG_READ_MODE, + mt9p012_km_regs.reg_pat[rt].read_mode}, + {REG_SCALE_M, + mt9p012_km_regs.reg_pat[rt].scale_m}, + {REG_X_OUTPUT_SIZE, + mt9p012_km_regs.reg_pat[rt].x_output_size}, + {REG_Y_OUTPUT_SIZE, + mt9p012_km_regs.reg_pat[rt].y_output_size}, + {REG_LINE_LENGTH_PCK, + mt9p012_km_regs.reg_pat[rt].line_length_pck}, + {REG_FRAME_LENGTH_LINES, + (mt9p012_km_regs.reg_pat[rt].frame_length_lines * + mt9p012_km_ctrl->fps_divider / 0x00000400)}, + {REG_COARSE_INT_TIME, + mt9p012_km_regs.reg_pat[rt].coarse_int_time}, + {REG_FINE_INTEGRATION_TIME, + mt9p012_km_regs.reg_pat[rt].fine_int_time}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE}, + }; + + if (update_type == REG_INIT) { + update_type = rupdate; + return rc; + } + + rc = mt9p012_km_i2c_write_w_table(&ppc_tbl[0], + ARRAY_SIZE(ppc_tbl)); + if (rc < 0) + return rc; + + rc = mt9p012_km_test(mt9p012_km_ctrl->set_test); + if (rc < 0) + return rc; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + MT9P012_KM_REG_RESET_REGISTER, + 0x10cc | + 0x0002); + if (rc < 0) + return rc; + + mdelay(15); /* 15? wait for sensor to transition */ + + return rc; + } + break; /* UPDATE_PERIODIC */ + + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct mt9p012_km_i2c_reg_conf ipc_tbl1[] = { + {MT9P012_KM_REG_RESET_REGISTER, + MT9P012_KM_RESET_REGISTER_PWOFF}, + {REG_VT_PIX_CLK_DIV, + mt9p012_km_regs.reg_pat[rt].vt_pix_clk_div}, + {REG_VT_SYS_CLK_DIV, + mt9p012_km_regs.reg_pat[rt].vt_sys_clk_div}, + {REG_PRE_PLL_CLK_DIV, + mt9p012_km_regs.reg_pat[rt].pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + mt9p012_km_regs.reg_pat[rt].pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + mt9p012_km_regs.reg_pat[rt].op_pix_clk_div}, + {REG_OP_SYS_CLK_DIV, + mt9p012_km_regs.reg_pat[rt].op_sys_clk_div}, + {MT9P012_KM_REG_RESET_REGISTER, + MT9P012_KM_RESET_REGISTER_PWON}, + }; + + struct mt9p012_km_i2c_reg_conf ipc_tbl2[] = { + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD}, + /* Optimized register settings for + Rev3 Silicon */ + {0x308A, 0x6424}, + {0x3092, 0x0A52}, + {0x3094, 0x4656}, + {0x3096, 0x5652}, + {0x0204, 0x0010}, + {0x0206, 0x0010}, + {0x0208, 0x0010}, + {0x020A, 0x0010}, + {0x020C, 0x0010}, + {0x3088, 0x6FF6}, + {0x3154, 0x0282}, + {0x3156, 0x0381}, + {0x3162, 0x04CE}, + }; + + struct mt9p012_km_i2c_reg_conf ipc_tbl3[] = { + /* Set preview or snapshot mode */ + {REG_ROW_SPEED, + mt9p012_km_regs.reg_pat[rt].row_speed}, + {REG_X_ADDR_START, + mt9p012_km_regs.reg_pat[rt].x_addr_start}, + {REG_X_ADDR_END, + mt9p012_km_regs.reg_pat[rt].x_addr_end}, + {REG_Y_ADDR_START, + mt9p012_km_regs.reg_pat[rt].y_addr_start}, + {REG_Y_ADDR_END, + mt9p012_km_regs.reg_pat[rt].y_addr_end}, + {REG_READ_MODE, + mt9p012_km_regs.reg_pat[rt].read_mode}, + {REG_SCALE_M, + mt9p012_km_regs.reg_pat[rt].scale_m}, + {REG_X_OUTPUT_SIZE, + mt9p012_km_regs.reg_pat[rt].x_output_size}, + {REG_Y_OUTPUT_SIZE, + mt9p012_km_regs.reg_pat[rt].y_output_size}, + {REG_LINE_LENGTH_PCK, + mt9p012_km_regs.reg_pat[rt].line_length_pck}, + {REG_FRAME_LENGTH_LINES, + mt9p012_km_regs.reg_pat[rt]. + frame_length_lines}, + {REG_COARSE_INT_TIME, + mt9p012_km_regs.reg_pat[rt].coarse_int_time}, + {REG_FINE_INTEGRATION_TIME, + mt9p012_km_regs.reg_pat[rt].fine_int_time}, + {REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE}, + }; + + /* reset fps_divider */ + mt9p012_km_ctrl->fps_divider = 1 * 0x0400; + + rc = mt9p012_km_i2c_write_w_table(&ipc_tbl1[0], + ARRAY_SIZE(ipc_tbl1)); + if (rc < 0) + return rc; + + mdelay(15); + + rc = mt9p012_km_i2c_write_w_table(&ipc_tbl2[0], + ARRAY_SIZE(ipc_tbl2)); + if (rc < 0) + return rc; + + mdelay(5); + + rc = mt9p012_km_i2c_write_w_table(&ipc_tbl3[0], + ARRAY_SIZE(ipc_tbl3)); + if (rc < 0) + return rc; + + /* load lens shading */ + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + rc = mt9p012_km_set_lc(); + if (rc < 0) + return rc; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + + if (rc < 0) + return rc; + } + update_type = rupdate; + break; /* case REG_INIT: */ + + default: + rc = -EINVAL; + break; + } /* switch (rupdate) */ + + return rc; +} + +static int32_t mt9p012_km_video_config(int mode, int res) +{ + int32_t rc; + + switch (res) { + case QTR_SIZE: + rc = mt9p012_km_setting(UPDATE_PERIODIC, RES_PREVIEW); + if (rc < 0) + return rc; + + CDBG("mt9p012_km sensor configuration done!\n"); + break; + + case FULL_SIZE: + rc = mt9p012_km_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + break; + + default: + return 0; + } /* switch */ + + mt9p012_km_ctrl->prev_res = res; + mt9p012_km_ctrl->curr_res = res; + mt9p012_km_ctrl->sensormode = mode; + + rc = mt9p012_km_write_exp_gain(mt9p012_km_ctrl->my_reg_gain, + mt9p012_km_ctrl->my_reg_line_count); + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + MT9P012_KM_REG_RESET_REGISTER, + 0x10cc | 0x0002); + + mdelay(15); + return rc; +} + +static int32_t mt9p012_km_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = mt9p012_km_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + mt9p012_km_ctrl->curr_res = mt9p012_km_ctrl->pict_res; + + mt9p012_km_ctrl->sensormode = mode; + + return rc; +} + +static int32_t mt9p012_km_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = mt9p012_km_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + mt9p012_km_ctrl->curr_res = mt9p012_km_ctrl->pict_res; + + mt9p012_km_ctrl->sensormode = mode; + + return rc; +} + +static int32_t mt9p012_km_power_down(void) +{ + int32_t rc = 0; + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + MT9P012_KM_REG_RESET_REGISTER, + MT9P012_KM_RESET_REGISTER_PWOFF); + + mdelay(5); + return rc; +} + +static int32_t mt9p012_km_move_focus(int direction, int32_t num_steps) +{ + int16_t step_direction; + int16_t actual_step; + int16_t next_position; + uint8_t code_val_msb, code_val_lsb; + + if (num_steps > MT9P012_KM_TOTAL_STEPS_NEAR_TO_FAR) + num_steps = MT9P012_KM_TOTAL_STEPS_NEAR_TO_FAR; + else if (num_steps == 0) { + CDBG("mt9p012_km_move_focus failed at line %d ...\n", __LINE__); + return -EINVAL; + } + + if (direction == MOVE_NEAR) + step_direction = 16; /* 10bit */ + else if (direction == MOVE_FAR) + step_direction = -16; /* 10 bit */ + else { + CDBG("mt9p012_km_move_focus failed at line %d ...\n", __LINE__); + return -EINVAL; + } + + if (mt9p012_km_ctrl->curr_lens_pos < + mt9p012_km_ctrl->init_curr_lens_pos) + mt9p012_km_ctrl->curr_lens_pos = + mt9p012_km_ctrl->init_curr_lens_pos; + + actual_step = (int16_t) (step_direction * (int16_t) num_steps); + next_position = (int16_t) (mt9p012_km_ctrl->curr_lens_pos + + actual_step); + + if (next_position > 1023) + next_position = 1023; + else if (next_position < 0) + next_position = 0; + + code_val_msb = next_position >> 4; + code_val_lsb = (next_position & 0x000F) << 4; + code_val_lsb |= mode_mask; + + /* Writing the digital code for current to the actuator */ + if (mt9p012_km_i2c_write_b(MT9P012_KM_AF_I2C_ADDR >> 1, + code_val_msb, code_val_lsb) < 0) { + CDBG("mt9p012_km_move_focus failed at line %d ...\n", __LINE__); + return -EBUSY; + } + + /* Storing the current lens Position */ + mt9p012_km_ctrl->curr_lens_pos = next_position; + + return 0; +} + +static int32_t mt9p012_km_set_default_focus(void) +{ + int32_t rc = 0; + uint8_t code_val_msb, code_val_lsb; + + code_val_msb = 0x00; + code_val_lsb = 0x04; + + /* Write the digital code for current to the actuator */ + rc = mt9p012_km_i2c_write_b(MT9P012_KM_AF_I2C_ADDR >> 1, + code_val_msb, code_val_lsb); + + mt9p012_km_ctrl->curr_lens_pos = 0; + mt9p012_km_ctrl->init_curr_lens_pos = 0; + + return rc; +} + +static int mt9p012_km_probe_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + return 0; +} + +static int + mt9p012_km_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + uint16_t chipid; + + rc = gpio_request(data->sensor_reset, "mt9p012_km"); + if (!rc) + gpio_direction_output(data->sensor_reset, 1); + else + goto init_probe_done; + + msleep(20); + + /* RESET the sensor image part via I2C command */ + CDBG("mt9p012_km_sensor_init(): reseting sensor.\n"); + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + MT9P012_KM_REG_RESET_REGISTER, + 0x10CC | 0x0001); + if (rc < 0) { + CDBG("sensor reset failed. rc = %d\n", rc); + goto init_probe_fail; + } + + msleep(MT9P012_KM_RESET_DELAY_MSECS); + + /* 3. Read sensor Model ID: */ + rc = mt9p012_km_i2c_read_w(mt9p012_km_client->addr, + MT9P012_KM_REG_MODEL_ID, &chipid); + if (rc < 0) + goto init_probe_fail; + + /* 4. Compare sensor ID to MT9T012VC ID: */ + if (chipid != MT9P012_KM_MODEL_ID) { + CDBG("mt9p012_km wrong model_id = 0x%x\n", chipid); + rc = -ENODEV; + goto init_probe_fail; + } + + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, 0x306E, 0x9080); + if (rc < 0) { + CDBG("REV_7 write failed. rc = %d\n", rc); + goto init_probe_fail; + } + + /* RESET_REGISTER, enable parallel interface and disable serialiser */ + CDBG("mt9p012_km_sensor_init(): enabling parallel interface.\n"); + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, 0x301A, 0x10CC); + if (rc < 0) { + CDBG("enable parallel interface failed. rc = %d\n", rc); + goto init_probe_fail; + } + + /* To disable the 2 extra lines */ + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, 0x3064, 0x0805); + + if (rc < 0) { + CDBG("disable the 2 extra lines failed. rc = %d\n", rc); + goto init_probe_fail; + } + + goto init_probe_done; + +init_probe_fail: + mt9p012_km_probe_init_done(data); +init_probe_done: + return rc; +} + +static int + mt9p012_km_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + + mt9p012_km_ctrl = kzalloc(sizeof(struct mt9p012_km_ctrl), GFP_KERNEL); + if (!mt9p012_km_ctrl) { + CDBG("mt9p012_km_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + + mt9p012_km_ctrl->fps_divider = 1 * 0x00000400; + mt9p012_km_ctrl->pict_fps_divider = 1 * 0x00000400; + mt9p012_km_ctrl->set_test = TEST_OFF; + mt9p012_km_ctrl->prev_res = QTR_SIZE; + mt9p012_km_ctrl->pict_res = FULL_SIZE; + + if (data) + mt9p012_km_ctrl->sensordata = data; + + msm_camio_camif_pad_reg_reset(); + mdelay(20); + + rc = mt9p012_km_probe_init_sensor(data); + if (rc < 0) + goto init_fail1; + + if (mt9p012_km_ctrl->prev_res == QTR_SIZE) + rc = mt9p012_km_setting(REG_INIT, RES_PREVIEW); + else + rc = mt9p012_km_setting(REG_INIT, RES_CAPTURE); + + if (rc < 0) { + CDBG("mt9p012_km_setting failed. rc = %d\n", rc); + goto init_fail1; + } + + /* sensor : output enable */ + CDBG("mt9p012_km_sensor_open_init(): enabling output.\n"); + rc = mt9p012_km_i2c_write_w(mt9p012_km_client->addr, + MT9P012_KM_REG_RESET_REGISTER, + MT9P012_KM_RESET_REGISTER_PWON); + if (rc < 0) { + CDBG("sensor output enable failed. rc = %d\n", rc); + goto init_fail1; + } + + if (rc >= 0) + goto init_done; + +init_fail1: + mt9p012_km_probe_init_done(data); + kfree(mt9p012_km_ctrl); +init_done: + return rc; +} + +static int mt9p012_km_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&mt9p012_km_wait_queue); + return 0; +} + +static int32_t mt9p012_km_set_sensor_mode(int mode, int res) +{ + int32_t rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = mt9p012_km_video_config(mode, res); + break; + + case SENSOR_SNAPSHOT_MODE: + rc = mt9p012_km_snapshot_config(mode); + break; + + case SENSOR_RAW_SNAPSHOT_MODE: + rc = mt9p012_km_raw_snapshot_config(mode); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +int mt9p012_km_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + int rc = 0; + + if (copy_from_user(&cdata, + (void *)argp, sizeof(struct sensor_cfg_data))) + return -EFAULT; + + mutex_lock(&mt9p012_km_mut); + + CDBG("%s: cfgtype = %d\n", __func__, cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + mt9p012_km_get_pict_fps(cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = mt9p012_km_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = mt9p012_km_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = mt9p012_km_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = mt9p012_km_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = mt9p012_km_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = mt9p012_km_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = mt9p012_km_write_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + CDBG("Line:%d CFG_SET_PICT_EXP_GAIN \n", __LINE__); + rc = mt9p012_km_set_pict_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = mt9p012_km_set_sensor_mode(cdata.mode, cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = mt9p012_km_power_down(); + break; + + case CFG_MOVE_FOCUS: + CDBG("mt9p012_km_ioctl: CFG_MOVE_FOCUS: cdata.cfg.focus.dir=%d \ + cdata.cfg.focus.steps=%d\n", + cdata.cfg.focus.dir, cdata.cfg.focus.steps); + rc = mt9p012_km_move_focus(cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = mt9p012_km_set_default_focus(); + break; + + case CFG_SET_LENS_SHADING: + CDBG("%s: CFG_SET_LENS_SHADING\n", __func__); + rc = mt9p012_km_lens_shading_enable(cdata.cfg.lens_shading); + break; + + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = MT9P012_KM_STEPS_NEAR_TO_CLOSEST_INF; + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_EFFECT: + default: + rc = -EINVAL; + break; + } + + mutex_unlock(&mt9p012_km_mut); + return rc; +} + +int mt9p012_km_sensor_release(void) +{ + int rc = -EBADF; + + mutex_lock(&mt9p012_km_mut); + + mt9p012_km_power_down(); + + gpio_direction_output(mt9p012_km_ctrl->sensordata->sensor_reset, 0); + gpio_free(mt9p012_km_ctrl->sensordata->sensor_reset); + + gpio_direction_output(mt9p012_km_ctrl->sensordata->vcm_pwd, 0); + gpio_free(mt9p012_km_ctrl->sensordata->vcm_pwd); + + kfree(mt9p012_km_ctrl); + mt9p012_km_ctrl = NULL; + + CDBG("mt9p012_km_release completed\n"); + + mutex_unlock(&mt9p012_km_mut); + return rc; +} + +static int mt9p012_km_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("mt9p012_km_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + mt9p012_km_sensorw = kzalloc(sizeof(struct mt9p012_km_work), + GFP_KERNEL); + if (!mt9p012_km_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, mt9p012_km_sensorw); + mt9p012_km_init_client(client); + mt9p012_km_client = client; + + mdelay(50); + + CDBG("mt9p012_km_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("mt9p012_km_probe failed! rc = %d\n", rc); + return rc; +} + +static const struct i2c_device_id mt9p012_km_i2c_id[] = { + {"mt9p012_km", 0}, + {} +}; + +static struct i2c_driver mt9p012_km_i2c_driver = { + .id_table = mt9p012_km_i2c_id, + .probe = mt9p012_km_i2c_probe, + .remove = __exit_p(mt9p012_km_i2c_remove), + .driver = { + .name = "mt9p012_km", + }, +}; + +static int mt9p012_km_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = i2c_add_driver(&mt9p012_km_i2c_driver); + if (rc < 0 || mt9p012_km_client == NULL) { + rc = -ENOTSUPP; + goto probe_done; + } + + msm_camio_clk_rate_set(MT9P012_KM_DEFAULT_CLOCK_RATE); + mdelay(20); + + rc = mt9p012_km_probe_init_sensor(info); + if (rc < 0) + goto probe_done; + + s->s_init = mt9p012_km_sensor_open_init; + s->s_release = mt9p012_km_sensor_release; + s->s_config = mt9p012_km_sensor_config; + s->s_mount_angle = 0; + mt9p012_km_probe_init_done(info); + +probe_done: + CDBG("%s %s:%d\n", __FILE__, __func__, __LINE__); + return rc; +} + +static int __mt9p012_km_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, mt9p012_km_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __mt9p012_km_probe, + .driver = { + .name = "msm_camera_mt9p012_km", + .owner = THIS_MODULE, + }, +}; + +static int __init mt9p012_km_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(mt9p012_km_init); diff --git a/drivers/media/video/msm/mt9p012_km.h b/drivers/media/video/msm/mt9p012_km.h new file mode 100644 index 00000000000..aefabd4122c --- /dev/null +++ b/drivers/media/video/msm/mt9p012_km.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MT9P012_KM_H +#define MT9P012_KM_H + +#include + +extern struct mt9p012_km_reg mt9p012_km_regs; /* from mt9p012_km_reg.c */ + +struct reg_struct { + uint16_t vt_pix_clk_div; /* 0x0300 */ + uint16_t vt_sys_clk_div; /* 0x0302 */ + uint16_t pre_pll_clk_div; /* 0x0304 */ + uint16_t pll_multiplier; /* 0x0306 */ + uint16_t op_pix_clk_div; /* 0x0308 */ + uint16_t op_sys_clk_div; /* 0x030A */ + uint16_t scale_m; /* 0x0404 */ + uint16_t row_speed; /* 0x3016 */ + uint16_t x_addr_start; /* 0x3004 */ + uint16_t x_addr_end; /* 0x3008 */ + uint16_t y_addr_start; /* 0x3002 */ + uint16_t y_addr_end; /* 0x3006 */ + uint16_t read_mode; /* 0x3040 */ + uint16_t x_output_size ; /* 0x034C */ + uint16_t y_output_size; /* 0x034E */ + uint16_t line_length_pck; /* 0x300C */ + uint16_t frame_length_lines; /* 0x300A */ + uint16_t coarse_int_time; /* 0x3012 */ + uint16_t fine_int_time; /* 0x3014 */ +}; + + +struct mt9p012_km_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + + +struct mt9p012_km_reg { + struct reg_struct const *reg_pat; + uint16_t reg_pat_size; + struct mt9p012_km_i2c_reg_conf const *ttbl; + uint16_t ttbl_size; + struct mt9p012_km_i2c_reg_conf const *lctbl; + uint16_t lctbl_size; + struct mt9p012_km_i2c_reg_conf const *rftbl; + uint16_t rftbl_size; +}; + +#endif /* MT9P012_KM_H */ diff --git a/drivers/media/video/msm/mt9p012_km_reg.c b/drivers/media/video/msm/mt9p012_km_reg.c new file mode 100644 index 00000000000..109930b6c55 --- /dev/null +++ b/drivers/media/video/msm/mt9p012_km_reg.c @@ -0,0 +1,375 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "mt9p012_km.h" +#include + +/*Micron settings from Applications for lower power consumption.*/ +struct reg_struct const mt9p012_km_reg_pat[2] = { + { /* Preview */ + /* vt_pix_clk_div REG=0x0300 */ + 6, /* 5 */ + + /* vt_sys_clk_div REG=0x0302 */ + 1, + + /* pre_pll_clk_div REG=0x0304 */ + 2, + + /* pll_multiplier REG=0x0306 */ + 60, + + /* op_pix_clk_div REG=0x0308 */ + 8, /* 10 */ + + /* op_sys_clk_div REG=0x030A */ + 1, + + /* scale_m REG=0x0404 */ + 16, + + /* row_speed REG=0x3016 */ + 0x0111, + + /* x_addr_start REG=0x3004 */ + 8, + + /* x_addr_end REG=0x3008 */ + 2597, + + /* y_addr_start REG=0x3002 */ + 8, + + /* y_addr_end REG=0x3006 */ + 1949, + + /* read_mode REG=0x3040 + * Preview 2x2 skipping */ + 0x006C, + + /* x_output_size REG=0x034C */ + 1296, + + /* y_output_size REG=0x034E */ + 972, + + /* line_length_pck REG=0x300C */ + 3783, + + /* frame_length_lines REG=0x300A */ + 1074, + + /* coarse_integration_time REG=0x3012 */ + 16, + + /* fine_integration_time REG=0x3014 */ + 1764 + }, + { /* Snapshot */ + /* vt_pix_clk_div REG=0x0300 */ + 6, + + /* vt_sys_clk_div REG=0x0302 */ + 1, + + /* pre_pll_clk_div REG=0x0304 */ + 2, + + /* pll_multiplier REG=0x0306 + * 39 for 10fps snapshot */ + 39, + + /* op_pix_clk_div REG=0x0308 */ + 8, + + /* op_sys_clk_div REG=0x030A */ + 1, + + /* scale_m REG=0x0404 */ + 16, + + /* row_speed REG=0x3016 */ + 0x0111, + + /* x_addr_start REG=0x3004 */ + 8, + + /* x_addr_end REG=0x3008 */ + 2615, + + /* y_addr_start REG=0x3002 */ + 8, + + /* y_addr_end REG=0x3006 */ + 1967, + + /* read_mode REG=0x3040 */ + 0x0024, + + /* x_output_size REG=0x034C */ + 2608, + + /* y_output_size REG=0x034E */ + 1960, + + /* line_length_pck REG=0x300C */ + 3788, + + /* frame_length_lines REG=0x300A 10 fps snapshot */ + 2045, + + /* coarse_integration_time REG=0x3012 */ + 16, + + /* fine_integration_time REG=0x3014 */ + 882 + } +}; + +struct mt9p012_km_i2c_reg_conf const mt9p012_km_test_tbl[] = { + {0x3044, 0x0544 & 0xFBFF}, + {0x30CA, 0x0004 | 0x0001}, + {0x30D4, 0x9020 & 0x7FFF}, + {0x31E0, 0x0003 & 0xFFFE}, + {0x3180, 0x91FF & 0x7FFF}, + {0x301A, (0x10CC | 0x8000) & 0xFFF7}, + {0x301E, 0x0000}, + {0x3780, 0x0000}, +}; + + +struct mt9p012_km_i2c_reg_conf const mt9p012_km_lc_tbl[] = { + {0x360A, 0x00F0}, + {0x360C, 0x0B29}, + {0x360E, 0x5ED1}, + {0x3610, 0x890D}, + {0x3612, 0x9871}, + {0x364A, 0xAD2C}, + {0x364C, 0x0A8C}, + {0x364E, 0x91EC}, + {0x3650, 0x94EC}, + {0x3652, 0xC76B}, + {0x368A, 0x5931}, + {0x368C, 0x4FED}, + {0x368E, 0x8A50}, + {0x3690, 0x5C0F}, + {0x3692, 0x8393}, + {0x36CA, 0xDB8E}, + {0x36CC, 0xCA4D}, + {0x36CE, 0x146F}, + {0x36D0, 0x618F}, + {0x36D2, 0x014F}, + {0x370A, 0x1FEE}, + {0x370C, 0xDD50}, + {0x370E, 0xDB54}, + {0x3710, 0xCA92}, + {0x3712, 0x1896}, + {0x3600, 0x00F0}, + {0x3602, 0xA04C}, + {0x3604, 0x5711}, + {0x3606, 0x5E6D}, + {0x3608, 0xA971}, + {0x3640, 0xDCCC}, + {0x3642, 0x0529}, + {0x3644, 0x96ED}, + {0x3646, 0xF447}, + {0x3648, 0x4AEE}, + {0x3680, 0x2171}, + {0x3682, 0x634F}, + {0x3684, 0xCC91}, + {0x3686, 0xA9CE}, + {0x3688, 0x8751}, + {0x36C0, 0x8B6D}, + {0x36C2, 0xE20E}, + {0x36C4, 0x750F}, + {0x36C6, 0x0090}, + {0x36C8, 0x9E91}, + {0x3700, 0xEAAF}, + {0x3702, 0xB8AF}, + {0x3704, 0xE293}, + {0x3706, 0xAB33}, + {0x3708, 0x4595}, + {0x3614, 0x00D0}, + {0x3616, 0x8AAB}, + {0x3618, 0x18B1}, + {0x361A, 0x54AD}, + {0x361C, 0x9DB0}, + {0x3654, 0x11EB}, + {0x3656, 0x332C}, + {0x3658, 0x316D}, + {0x365A, 0xF0EB}, + {0x365C, 0xB4ED}, + {0x3694, 0x0F31}, + {0x3696, 0x08D0}, + {0x3698, 0xA52F}, + {0x369A, 0xE64F}, + {0x369C, 0xC9D2}, + {0x36D4, 0x8C2D}, + {0x36D6, 0xAD6E}, + {0x36D8, 0xE1CE}, + {0x36DA, 0x1750}, + {0x36DC, 0x8CAD}, + {0x3714, 0x8CAF}, + {0x3716, 0x8C11}, + {0x3718, 0xE453}, + {0x371A, 0x9693}, + {0x371C, 0x38B5}, + {0x361E, 0x00D0}, + {0x3620, 0xB6CB}, + {0x3622, 0x4811}, + {0x3624, 0xB70C}, + {0x3626, 0xA771}, + {0x365E, 0xB5A9}, + {0x3660, 0x05AA}, + {0x3662, 0x00CF}, + {0x3664, 0xB86B}, + {0x3666, 0xA4AF}, + {0x369E, 0x3E31}, + {0x36A0, 0x902B}, + {0x36A2, 0xD251}, + {0x36A4, 0x5C2F}, + {0x36A6, 0x8471}, + {0x36DE, 0x2C6D}, + {0x36E0, 0xECEE}, + {0x36E2, 0xB650}, + {0x36E4, 0x0210}, + {0x36E6, 0xACAE}, + {0x371E, 0xAC30}, + {0x3720, 0x394E}, + {0x3722, 0xFDD3}, + {0x3724, 0xBCB2}, + {0x3726, 0x5AD5}, + {0x3782, 0x0508}, + {0x3784, 0x03B4}, + {0x3780, 0x8000}, +}; + +struct mt9p012_km_i2c_reg_conf const mt9p012_km_rolloff_tbl[] = { + {0x360A, 0x00F0}, + {0x360C, 0x0B29}, + {0x360E, 0x5ED1}, + {0x3610, 0x890D}, + {0x3612, 0x9871}, + {0x364A, 0xAD2C}, + {0x364C, 0x0A8C}, + {0x364E, 0x91EC}, + {0x3650, 0x94EC}, + {0x3652, 0xC76B}, + {0x368A, 0x5931}, + {0x368C, 0x4FED}, + {0x368E, 0x8A50}, + {0x3690, 0x5C0F}, + {0x3692, 0x8393}, + {0x36CA, 0xDB8E}, + {0x36CC, 0xCA4D}, + {0x36CE, 0x146F}, + {0x36D0, 0x618F}, + {0x36D2, 0x014F}, + {0x370A, 0x1FEE}, + {0x370C, 0xDD50}, + {0x370E, 0xDB54}, + {0x3710, 0xCA92}, + {0x3712, 0x1896}, + {0x3600, 0x00F0}, + {0x3602, 0xA04C}, + {0x3604, 0x5711}, + {0x3606, 0x5E6D}, + {0x3608, 0xA971}, + {0x3640, 0xDCCC}, + {0x3642, 0x0529}, + {0x3644, 0x96ED}, + {0x3646, 0xF447}, + {0x3648, 0x4AEE}, + {0x3680, 0x2171}, + {0x3682, 0x634F}, + {0x3684, 0xCC91}, + {0x3686, 0xA9CE}, + {0x3688, 0x8751}, + {0x36C0, 0x8B6D}, + {0x36C2, 0xE20E}, + {0x36C4, 0x750F}, + {0x36C6, 0x0090}, + {0x36C8, 0x9E91}, + {0x3700, 0xEAAF}, + {0x3702, 0xB8AF}, + {0x3704, 0xE293}, + {0x3706, 0xAB33}, + {0x3708, 0x4595}, + {0x3614, 0x00D0}, + {0x3616, 0x8AAB}, + {0x3618, 0x18B1}, + {0x361A, 0x54AD}, + {0x361C, 0x9DB0}, + {0x3654, 0x11EB}, + {0x3656, 0x332C}, + {0x3658, 0x316D}, + {0x365A, 0xF0EB}, + {0x365C, 0xB4ED}, + {0x3694, 0x0F31}, + {0x3696, 0x08D0}, + {0x3698, 0xA52F}, + {0x369A, 0xE64F}, + {0x369C, 0xC9D2}, + {0x36D4, 0x8C2D}, + {0x36D6, 0xAD6E}, + {0x36D8, 0xE1CE}, + {0x36DA, 0x1750}, + {0x36DC, 0x8CAD}, + {0x3714, 0x8CAF}, + {0x3716, 0x8C11}, + {0x3718, 0xE453}, + {0x371A, 0x9693}, + {0x371C, 0x38B5}, + {0x361E, 0x00D0}, + {0x3620, 0xB6CB}, + {0x3622, 0x4811}, + {0x3624, 0xB70C}, + {0x3626, 0xA771}, + {0x365E, 0xB5A9}, + {0x3660, 0x05AA}, + {0x3662, 0x00CF}, + {0x3664, 0xB86B}, + {0x3666, 0xA4AF}, + {0x369E, 0x3E31}, + {0x36A0, 0x902B}, + {0x36A2, 0xD251}, + {0x36A4, 0x5C2F}, + {0x36A6, 0x8471}, + {0x36DE, 0x2C6D}, + {0x36E0, 0xECEE}, + {0x36E2, 0xB650}, + {0x36E4, 0x0210}, + {0x36E6, 0xACAE}, + {0x371E, 0xAC30}, + {0x3720, 0x394E}, + {0x3722, 0xFDD3}, + {0x3724, 0xBCB2}, + {0x3726, 0x5AD5}, + {0x3782, 0x0508}, + {0x3784, 0x03B4}, + {0x3780, 0x8000}, +}; + + +struct mt9p012_km_reg mt9p012_km_regs = { + .reg_pat = &mt9p012_km_reg_pat[0], + .reg_pat_size = ARRAY_SIZE(mt9p012_km_reg_pat), + .ttbl = &mt9p012_km_test_tbl[0], + .ttbl_size = ARRAY_SIZE(mt9p012_km_test_tbl), + .lctbl = &mt9p012_km_lc_tbl[0], + .lctbl_size = ARRAY_SIZE(mt9p012_km_lc_tbl), + .rftbl = &mt9p012_km_rolloff_tbl[0], + .rftbl_size = ARRAY_SIZE(mt9p012_km_rolloff_tbl) +}; + + diff --git a/drivers/media/video/msm/mt9p012_reg.c b/drivers/media/video/msm/mt9p012_reg.c new file mode 100644 index 00000000000..06fe4196fec --- /dev/null +++ b/drivers/media/video/msm/mt9p012_reg.c @@ -0,0 +1,263 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "mt9p012.h" +#include + +/*Micron settings from Applications for lower power consumption.*/ +struct reg_struct const mt9p012_reg_pat[2] = { + { /* Preview */ + /* vt_pix_clk_div REG=0x0300 */ + 6, /* 5 */ + + /* vt_sys_clk_div REG=0x0302 */ + 1, + /* pre_pll_clk_div REG=0x0304 */ + 2, + /* pll_multiplier REG=0x0306 */ + 60, + + /* op_pix_clk_div REG=0x0308 */ + 8, /* 10 */ + + /* op_sys_clk_div REG=0x030A */ + 1, + + /* scale_m REG=0x0404 */ + 16, + + /* row_speed REG=0x3016 */ + 0x0111, + + /* x_addr_start REG=0x3004 */ + 8, + + /* x_addr_end REG=0x3008 */ + 2597, + + /* y_addr_start REG=0x3002 */ + 8, + + /* y_addr_end REG=0x3006 */ + 1949, + + /* read_mode REG=0x3040 + * Preview 2x2 skipping */ + 0x00C3, + + /* x_output_size REG=0x034C */ + 1296, + + /* y_output_size REG=0x034E */ + 972, + + /* line_length_pck REG=0x300C */ + 3659, + + /* frame_length_lines REG=0x300A */ + 1074, + + /* coarse_integration_time REG=0x3012 */ + 16, + + /* fine_integration_time REG=0x3014 */ + 1764 + }, + { /* Snapshot */ + /* vt_pix_clk_div REG=0x0300 */ + 6, + + /* vt_sys_clk_div REG=0x0302 */ + 1, + + /* pre_pll_clk_div REG=0x0304 */ + 2, + + /* pll_multiplier REG=0x0306 + * 60 for 10fps snapshot */ + 60, + + /* op_pix_clk_div REG=0x0308 */ + 8, + + /* op_sys_clk_div REG=0x030A */ + 1, + + /* scale_m REG=0x0404 */ + 16, + + /* row_speed REG=0x3016 */ + 0x0111, + + /* x_addr_start REG=0x3004 */ + 8, + + /* x_addr_end REG=0x3008 */ + 2615, + + /* y_addr_start REG=0x3002 */ + 8, + + /* y_addr_end REG=0x3006 */ + 1967, + + /* read_mode REG=0x3040 */ + 0x0041, + + /* x_output_size REG=0x034C */ + 2608, + + /* y_output_size REG=0x034E */ + 1960, + + /* line_length_pck REG=0x300C */ + 3911, + + /* frame_length_lines REG=0x300A 10 fps snapshot */ + 2045, + + /* coarse_integration_time REG=0x3012 */ + 16, + + /* fine_integration_time REG=0x3014 */ + 882 + } +}; + + +struct mt9p012_i2c_reg_conf const mt9p012_test_tbl[] = { + {0x3044, 0x0544 & 0xFBFF}, + {0x30CA, 0x0004 | 0x0001}, + {0x30D4, 0x9020 & 0x7FFF}, + {0x31E0, 0x0003 & 0xFFFE}, + {0x3180, 0x91FF & 0x7FFF}, + {0x301A, (0x10CC | 0x8000) & 0xFFF7}, + {0x301E, 0x0000}, + {0x3780, 0x0000}, +}; +struct mt9p012_i2c_reg_conf const mt9p012_rolloff_tbl[] = { + {0x360A, 0x0110}, + {0x360C, 0x270D}, + {0x360E, 0x0071}, + {0x3610, 0xA38D}, + {0x3612, 0xA610}, + {0x364A, 0x8F49}, + {0x364C, 0x696A}, + {0x364E, 0x0FCD}, + {0x3650, 0x20ED}, + {0x3652, 0x81ED}, + {0x368A, 0x1031}, + {0x368C, 0xBCAD}, + {0x368E, 0x77AA}, + {0x3690, 0xD10E}, + {0x3692, 0xC133}, + {0x36CA, 0x4F8D}, + {0x36CC, 0xAC4D}, + {0x36CE, 0xC8CE}, + {0x36D0, 0x73AD}, + {0x36D2, 0xC150}, + {0x370A, 0xB590}, + {0x370C, 0x9010}, + {0x370E, 0xAC52}, + {0x3710, 0x4D51}, + {0x3712, 0x5670}, + {0x3600, 0x00F0}, + {0x3602, 0xCE4B}, + {0x3604, 0x4270}, + {0x3606, 0x8BC9}, + {0x3608, 0xFA2F}, + {0x3640, 0x9A09}, + {0x3642, 0xB40C}, + {0x3644, 0x4ECD}, + {0x3646, 0x1BCC}, + {0x3648, 0xD68E}, + {0x3680, 0x1BF0}, + {0x3682, 0xC94D}, + {0x3684, 0x714F}, + {0x3686, 0x1491}, + {0x3688, 0xB8D3}, + {0x36C0, 0x3E49}, + {0x36C2, 0x7A6C}, + {0x36C4, 0xEF2E}, + {0x36C6, 0xE0EE}, + {0x36C8, 0x570F}, + {0x3700, 0xD6AF}, + {0x3702, 0x2251}, + {0x3704, 0x8A33}, + {0x3706, 0xEFB3}, + {0x3708, 0x1174}, + {0x3614, 0x0150}, + {0x3616, 0xA9AB}, + {0x3618, 0x1770}, + {0x361A, 0x8809}, + {0x361C, 0xE3AE}, + {0x3654, 0x5ACC}, + {0x3656, 0x35EA}, + {0x3658, 0x2DEC}, + {0x365A, 0xB90B}, + {0x365C, 0x250C}, + {0x3694, 0x1630}, + {0x3696, 0xD88C}, + {0x3698, 0xBD0E}, + {0x369A, 0x16D1}, + {0x369C, 0xE492}, + {0x36D4, 0x5D6D}, + {0x36D6, 0x906E}, + {0x36D8, 0x10AE}, + {0x36DA, 0x7A8E}, + {0x36DC, 0x9672}, + {0x3714, 0x8D90}, + {0x3716, 0x04F1}, + {0x3718, 0x23F1}, + {0x371A, 0xF313}, + {0x371C, 0xE833}, + {0x361E, 0x0490}, + {0x3620, 0x14CD}, + {0x3622, 0x38F0}, + {0x3624, 0xBAED}, + {0x3626, 0xFF6F}, + {0x365E, 0x358C}, + {0x3660, 0xA9E9}, + {0x3662, 0x4A4E}, + {0x3664, 0x398D}, + {0x3666, 0x890F}, + {0x369E, 0x2DF0}, + {0x36A0, 0xF7CE}, + {0x36A2, 0xB3CC}, + {0x36A4, 0x118D}, + {0x36A6, 0x9CB3}, + {0x36DE, 0x462D}, + {0x36E0, 0x74AA}, + {0x36E2, 0xC8CF}, + {0x36E4, 0x8DEF}, + {0x36E6, 0xF130}, + {0x371E, 0x9250}, + {0x3720, 0x19CC}, + {0x3722, 0xDFD1}, + {0x3724, 0x5B70}, + {0x3726, 0x34D2}, + {0x3782, 0x0530}, + {0x3784, 0x03C8}, + {0x3780, 0x8000}, +}; + +struct mt9p012_reg mt9p012_regs = { + .reg_pat = &mt9p012_reg_pat[0], + .reg_pat_size = ARRAY_SIZE(mt9p012_reg_pat), + .ttbl = &mt9p012_test_tbl[0], + .ttbl_size = ARRAY_SIZE(mt9p012_test_tbl), + .rftbl = &mt9p012_rolloff_tbl[0], + .rftbl_size = ARRAY_SIZE(mt9p012_rolloff_tbl) +}; + + diff --git a/drivers/media/video/msm/mt9t013.c b/drivers/media/video/msm/mt9t013.c new file mode 100644 index 00000000000..e1f6167416c --- /dev/null +++ b/drivers/media/video/msm/mt9t013.c @@ -0,0 +1,1503 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mt9t013.h" + +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ +#define MT9T013_REG_MODEL_ID 0x0000 +#define MT9T013_MODEL_ID 0x2600 +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD 0x0100 +#define GROUPED_PARAMETER_UPDATE 0x0000 +#define REG_COARSE_INT_TIME 0x3012 +#define REG_VT_PIX_CLK_DIV 0x0300 +#define REG_VT_SYS_CLK_DIV 0x0302 +#define REG_PRE_PLL_CLK_DIV 0x0304 +#define REG_PLL_MULTIPLIER 0x0306 +#define REG_OP_PIX_CLK_DIV 0x0308 +#define REG_OP_SYS_CLK_DIV 0x030A +#define REG_SCALE_M 0x0404 +#define REG_FRAME_LENGTH_LINES 0x300A +#define REG_LINE_LENGTH_PCK 0x300C +#define REG_X_ADDR_START 0x3004 +#define REG_Y_ADDR_START 0x3002 +#define REG_X_ADDR_END 0x3008 +#define REG_Y_ADDR_END 0x3006 +#define REG_X_OUTPUT_SIZE 0x034C +#define REG_Y_OUTPUT_SIZE 0x034E +#define REG_FINE_INT_TIME 0x3014 +#define REG_ROW_SPEED 0x3016 +#define MT9T013_REG_RESET_REGISTER 0x301A +#define MT9T013_RESET_REGISTER_PWON 0x10CC +#define MT9T013_RESET_REGISTER_PWOFF 0x1008 /* 0x10C8 stop streaming*/ +#define MT9T013_RESET_FAST_TRANSITION 0x0002 +#define REG_READ_MODE 0x3040 +#define REG_GLOBAL_GAIN 0x305E +#define REG_TEST_PATTERN_MODE 0x3070 + + +enum mt9t013_test_mode { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum mt9t013_resolution { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; + +enum mt9t013_reg_update { + REG_INIT, /* registers that need to be updated during initialization */ + UPDATE_PERIODIC, /* registers that needs periodic I2C writes */ + UPDATE_ALL, /* all registers will be updated */ + UPDATE_INVALID +}; + +enum mt9t013_setting { + RES_PREVIEW, + RES_CAPTURE +}; + +/* actuator's Slave Address */ +#define MT9T013_AF_I2C_ADDR 0x18 + +/* +* AF Total steps parameters +*/ +#define MT9T013_TOTAL_STEPS_NEAR_TO_FAR 30 + +/* + * Time in milisecs for waiting for the sensor to reset. + */ +#define MT9T013_RESET_DELAY_MSECS 66 + +/* for 30 fps preview */ +#define MT9T013_DEFAULT_CLOCK_RATE 24000000 +#define MT9T013_DEFAULT_MAX_FPS 26 + + +/* FIXME: Changes from here */ +struct mt9t013_work { + struct work_struct work; +}; + +static struct mt9t013_work *mt9t013_sensorw; +static struct i2c_client *mt9t013_client; + +struct mt9t013_ctrl { + const struct msm_camera_sensor_info *sensordata; + + int sensormode; + uint32_t fps_divider; /* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ + + uint16_t curr_lens_pos; + uint16_t init_curr_lens_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + + enum mt9t013_resolution prev_res; + enum mt9t013_resolution pict_res; + enum mt9t013_resolution curr_res; + enum mt9t013_test_mode set_test; + + unsigned short imgaddr; +}; + + +static struct mt9t013_ctrl *mt9t013_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(mt9t013_wait_queue); +DEFINE_SEMAPHORE(mt9t013_sem); + +static int mt9t013_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + + if (i2c_transfer(mt9t013_client->adapter, msgs, 2) < 0) { + pr_err("mt9t013_i2c_rxdata failed!\n"); + return -EIO; + } + + return 0; +} + +static int32_t mt9t013_i2c_read_w(unsigned short saddr, + unsigned short raddr, unsigned short *rdata) +{ + int32_t rc = 0; + unsigned char buf[4]; + + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + buf[0] = (raddr & 0xFF00)>>8; + buf[1] = (raddr & 0x00FF); + + rc = mt9t013_i2c_rxdata(saddr, buf, 2); + if (rc < 0) + return rc; + + *rdata = buf[0] << 8 | buf[1]; + + if (rc < 0) + pr_err("mt9t013_i2c_read failed!\n"); + + return rc; +} + +static int32_t mt9t013_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + + if (i2c_transfer(mt9t013_client->adapter, msg, 1) < 0) { + pr_err("mt9t013_i2c_txdata failed\n"); + return -EIO; + } + + return 0; +} + +static int32_t mt9t013_i2c_write_b(unsigned short saddr, + unsigned short waddr, unsigned short wdata) +{ + int32_t rc = -EIO; + unsigned char buf[2]; + + memset(buf, 0, sizeof(buf)); + buf[0] = waddr; + buf[1] = wdata; + rc = mt9t013_i2c_txdata(saddr, buf, 2); + + if (rc < 0) + pr_err("i2c_write failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + + return rc; +} + +static int32_t mt9t013_i2c_write_w(unsigned short saddr, + unsigned short waddr, unsigned short wdata) +{ + int32_t rc = -EIO; + unsigned char buf[4]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00)>>8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00)>>8; + buf[3] = (wdata & 0x00FF); + + rc = mt9t013_i2c_txdata(saddr, buf, 4); + + if (rc < 0) + pr_err("i2c_write_w failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + + return rc; +} + +static int32_t mt9t013_i2c_write_w_table( + struct mt9t013_i2c_reg_conf const *reg_conf_tbl, + int num_of_items_in_table) +{ + int i; + int32_t rc = -EIO; + + for (i = 0; i < num_of_items_in_table; i++) { + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + reg_conf_tbl->waddr, reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + + return rc; +} + +static int32_t mt9t013_test(enum mt9t013_test_mode mo) +{ + int32_t rc = 0; + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + if (mo == TEST_OFF) + return 0; + else { + rc = mt9t013_i2c_write_w_table(mt9t013_regs.ttbl, + mt9t013_regs.ttbl_size); + if (rc < 0) + return rc; + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_TEST_PATTERN_MODE, (uint16_t)mo); + if (rc < 0) + return rc; + } + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t mt9t013_set_lc(void) +{ + int32_t rc; + + rc = mt9t013_i2c_write_w_table(mt9t013_regs.lctbl, + mt9t013_regs.lctbl_size); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t mt9t013_set_default_focus(uint8_t af_step) +{ + int32_t rc = 0; + uint8_t code_val_msb, code_val_lsb; + code_val_msb = 0x01; + code_val_lsb = af_step; + + /* Write the digital code for current to the actuator */ + rc = mt9t013_i2c_write_b(MT9T013_AF_I2C_ADDR>>1, + code_val_msb, code_val_lsb); + + mt9t013_ctrl->curr_lens_pos = 0; + mt9t013_ctrl->init_curr_lens_pos = 0; + return rc; +} + +static void mt9t013_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider; /*Q10 */ + uint32_t pclk_mult; /*Q10 */ + uint32_t d1; + uint32_t d2; + + d1 = + (uint32_t)( + (mt9t013_regs.reg_pat[RES_PREVIEW].frame_length_lines * + 0x00000400) / + mt9t013_regs.reg_pat[RES_CAPTURE].frame_length_lines); + + d2 = + (uint32_t)( + (mt9t013_regs.reg_pat[RES_PREVIEW].line_length_pck * + 0x00000400) / + mt9t013_regs.reg_pat[RES_CAPTURE].line_length_pck); + + divider = (uint32_t) (d1 * d2) / 0x00000400; + + pclk_mult = + (uint32_t) ((mt9t013_regs.reg_pat[RES_CAPTURE].pll_multiplier * + 0x00000400) / + (mt9t013_regs.reg_pat[RES_PREVIEW].pll_multiplier)); + + + /* Verify PCLK settings and frame sizes. */ + *pfps = + (uint16_t) (fps * divider * pclk_mult / + 0x00000400 / 0x00000400); +} + +static uint16_t mt9t013_get_prev_lines_pf(void) +{ + if (mt9t013_ctrl->prev_res == QTR_SIZE) + return mt9t013_regs.reg_pat[RES_PREVIEW].frame_length_lines; + else + return mt9t013_regs.reg_pat[RES_CAPTURE].frame_length_lines; +} + +static uint16_t mt9t013_get_prev_pixels_pl(void) +{ + if (mt9t013_ctrl->prev_res == QTR_SIZE) + return mt9t013_regs.reg_pat[RES_PREVIEW].line_length_pck; + else + return mt9t013_regs.reg_pat[RES_CAPTURE].line_length_pck; +} + +static uint16_t mt9t013_get_pict_lines_pf(void) +{ + return mt9t013_regs.reg_pat[RES_CAPTURE].frame_length_lines; +} + +static uint16_t mt9t013_get_pict_pixels_pl(void) +{ + return mt9t013_regs.reg_pat[RES_CAPTURE].line_length_pck; +} + +static uint32_t mt9t013_get_pict_max_exp_lc(void) +{ + uint16_t snapshot_lines_per_frame; + + if (mt9t013_ctrl->pict_res == QTR_SIZE) { + snapshot_lines_per_frame = + mt9t013_regs.reg_pat[RES_PREVIEW].frame_length_lines - 1; + } else { + snapshot_lines_per_frame = + mt9t013_regs.reg_pat[RES_CAPTURE].frame_length_lines - 1; + } + + return snapshot_lines_per_frame * 24; +} + +static int32_t mt9t013_set_fps(struct fps_cfg *fps) +{ + /* input is new fps in Q8 format */ + int32_t rc = 0; + enum mt9t013_setting setting; + + mt9t013_ctrl->fps_divider = fps->fps_div; + mt9t013_ctrl->pict_fps_divider = fps->pict_fps_div; + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return -EBUSY; + + CDBG("mt9t013_set_fps: fps_div is %d, f_mult is %d\n", + fps->fps_div, fps->f_mult); + + if (mt9t013_ctrl->sensormode == SENSOR_PREVIEW_MODE) + setting = RES_PREVIEW; + else + setting = RES_CAPTURE; + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_FRAME_LENGTH_LINES, + (uint16_t) ( + mt9t013_regs.reg_pat[setting].frame_length_lines * + fps->fps_div / 0x00000400)); + + if (rc < 0) + return rc; + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t mt9t013_write_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t max_legal_gain = 0x01FF; + int32_t rc = 0; + + if (mt9t013_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + mt9t013_ctrl->my_reg_gain = gain; + mt9t013_ctrl->my_reg_line_count = (uint16_t) line; + } + + if (gain > max_legal_gain) + gain = max_legal_gain; + + if (mt9t013_ctrl->sensormode != SENSOR_SNAPSHOT_MODE) + line = (uint32_t) (line * mt9t013_ctrl->fps_divider / + 0x00000400); + else + line = (uint32_t) (line * mt9t013_ctrl->pict_fps_divider / + 0x00000400); + + /*Set digital gain to 1 */ + gain |= 0x0200; + + /* There used to be PARAMETER_HOLD register write before and + * after REG_GLOBAL_GAIN & REG_COARSE_INIT_TIME. This causes + * aec oscillation. Hence removed. */ + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, REG_GLOBAL_GAIN, gain); + if (rc < 0) + return rc; + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_COARSE_INT_TIME, line); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t mt9t013_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + + rc = mt9t013_write_exp_gain(gain, line); + if (rc < 0) + return rc; + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + MT9T013_REG_RESET_REGISTER, + 0x10CC | 0x0002); + + mdelay(5); + + return rc; +} + +static int32_t mt9t013_setting(enum mt9t013_reg_update rupdate, + enum mt9t013_setting rt) +{ + int32_t rc = 0; + + switch (rupdate) { + case UPDATE_PERIODIC: { + + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { +#if 0 + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + MT9T013_REG_RESET_REGISTER, + MT9T013_RESET_REGISTER_PWOFF); + if (rc < 0) + return rc; +#endif + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_VT_PIX_CLK_DIV, + mt9t013_regs.reg_pat[rt].vt_pix_clk_div); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_VT_SYS_CLK_DIV, + mt9t013_regs.reg_pat[rt].vt_sys_clk_div); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_PRE_PLL_CLK_DIV, + mt9t013_regs.reg_pat[rt].pre_pll_clk_div); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_PLL_MULTIPLIER, + mt9t013_regs.reg_pat[rt].pll_multiplier); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_OP_PIX_CLK_DIV, + mt9t013_regs.reg_pat[rt].op_pix_clk_div); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_OP_SYS_CLK_DIV, + mt9t013_regs.reg_pat[rt].op_sys_clk_div); + if (rc < 0) + return rc; + + mdelay(5); + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_ROW_SPEED, + mt9t013_regs.reg_pat[rt].row_speed); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_X_ADDR_START, + mt9t013_regs.reg_pat[rt].x_addr_start); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_X_ADDR_END, + mt9t013_regs.reg_pat[rt].x_addr_end); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_Y_ADDR_START, + mt9t013_regs.reg_pat[rt].y_addr_start); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_Y_ADDR_END, + mt9t013_regs.reg_pat[rt].y_addr_end); + if (rc < 0) + return rc; + + if (machine_is_sapphire()) { + if (rt == 0) + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_READ_MODE, + 0x046F); + else + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_READ_MODE, + 0x0027); + } else + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_READ_MODE, + mt9t013_regs.reg_pat[rt].read_mode); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_SCALE_M, + mt9t013_regs.reg_pat[rt].scale_m); + if (rc < 0) + return rc; + + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_X_OUTPUT_SIZE, + mt9t013_regs.reg_pat[rt].x_output_size); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_Y_OUTPUT_SIZE, + mt9t013_regs.reg_pat[rt].y_output_size); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_LINE_LENGTH_PCK, + mt9t013_regs.reg_pat[rt].line_length_pck); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_FRAME_LENGTH_LINES, + (mt9t013_regs.reg_pat[rt].frame_length_lines * + mt9t013_ctrl->fps_divider / 0x00000400)); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_COARSE_INT_TIME, + mt9t013_regs.reg_pat[rt].coarse_int_time); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_FINE_INT_TIME, + mt9t013_regs.reg_pat[rt].fine_int_time); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + if (rc < 0) + return rc; + + rc = mt9t013_test(mt9t013_ctrl->set_test); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + MT9T013_REG_RESET_REGISTER, + MT9T013_RESET_REGISTER_PWON| + MT9T013_RESET_FAST_TRANSITION); + if (rc < 0) + return rc; + + mdelay(5); + + return rc; + } + } + break; + + /*CAMSENSOR_REG_UPDATE_PERIODIC */ + case REG_INIT: { + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + MT9T013_REG_RESET_REGISTER, + MT9T013_RESET_REGISTER_PWOFF); + if (rc < 0) + /* MODE_SELECT, stop streaming */ + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_VT_PIX_CLK_DIV, + mt9t013_regs.reg_pat[rt].vt_pix_clk_div); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_VT_SYS_CLK_DIV, + mt9t013_regs.reg_pat[rt].vt_sys_clk_div); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_PRE_PLL_CLK_DIV, + mt9t013_regs.reg_pat[rt].pre_pll_clk_div); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_PLL_MULTIPLIER, + mt9t013_regs.reg_pat[rt].pll_multiplier); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_OP_PIX_CLK_DIV, + mt9t013_regs.reg_pat[rt].op_pix_clk_div); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_OP_SYS_CLK_DIV, + mt9t013_regs.reg_pat[rt].op_sys_clk_div); + if (rc < 0) + return rc; + + mdelay(5); + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + /* additional power saving mode ok around 38.2MHz */ + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + 0x3084, 0x2409); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + 0x3092, 0x0A49); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + 0x3094, 0x4949); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + 0x3096, 0x4949); + if (rc < 0) + return rc; + + /* Set preview or snapshot mode */ + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_ROW_SPEED, + mt9t013_regs.reg_pat[rt].row_speed); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_X_ADDR_START, + mt9t013_regs.reg_pat[rt].x_addr_start); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_X_ADDR_END, + mt9t013_regs.reg_pat[rt].x_addr_end); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_Y_ADDR_START, + mt9t013_regs.reg_pat[rt].y_addr_start); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_Y_ADDR_END, + mt9t013_regs.reg_pat[rt].y_addr_end); + if (rc < 0) + return rc; + + if (machine_is_sapphire()) { + if (rt == 0) + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_READ_MODE, + 0x046F); + else + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_READ_MODE, + 0x0027); + } else + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_READ_MODE, + mt9t013_regs.reg_pat[rt].read_mode); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_SCALE_M, + mt9t013_regs.reg_pat[rt].scale_m); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_X_OUTPUT_SIZE, + mt9t013_regs.reg_pat[rt].x_output_size); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_Y_OUTPUT_SIZE, + mt9t013_regs.reg_pat[rt].y_output_size); + if (rc < 0) + return 0; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_LINE_LENGTH_PCK, + mt9t013_regs.reg_pat[rt].line_length_pck); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_FRAME_LENGTH_LINES, + mt9t013_regs.reg_pat[rt].frame_length_lines); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_COARSE_INT_TIME, + mt9t013_regs.reg_pat[rt].coarse_int_time); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_FINE_INT_TIME, + mt9t013_regs.reg_pat[rt].fine_int_time); + if (rc < 0) + return rc; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + if (rc < 0) + return rc; + + /* load lens shading */ + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + /* most likely needs to be written only once. */ + rc = mt9t013_set_lc(); + if (rc < 0) + return -EBUSY; + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + if (rc < 0) + return rc; + + rc = mt9t013_test(mt9t013_ctrl->set_test); + if (rc < 0) + return rc; + + mdelay(5); + + rc = + mt9t013_i2c_write_w(mt9t013_client->addr, + MT9T013_REG_RESET_REGISTER, + MT9T013_RESET_REGISTER_PWON); + if (rc < 0) + /* MODE_SELECT, stop streaming */ + return rc; + + CDBG("!!! mt9t013 !!! PowerOn is done!\n"); + mdelay(5); + return rc; + } + } /* case CAMSENSOR_REG_INIT: */ + break; + + /*CAMSENSOR_REG_INIT */ + default: + rc = -EINVAL; + break; + } /* switch (rupdate) */ + + return rc; +} + +static int32_t mt9t013_video_config(int mode, int res) +{ + int32_t rc; + + switch (res) { + case QTR_SIZE: + rc = mt9t013_setting(UPDATE_PERIODIC, RES_PREVIEW); + if (rc < 0) + return rc; + CDBG("sensor configuration done!\n"); + break; + + case FULL_SIZE: + rc = mt9t013_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + break; + + default: + return -EINVAL; + } /* switch */ + + mt9t013_ctrl->prev_res = res; + mt9t013_ctrl->curr_res = res; + mt9t013_ctrl->sensormode = mode; + + rc = mt9t013_write_exp_gain(mt9t013_ctrl->my_reg_gain, + mt9t013_ctrl->my_reg_line_count); + if (rc < 0) + return rc; + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + MT9T013_REG_RESET_REGISTER, + MT9T013_RESET_REGISTER_PWON|MT9T013_RESET_FAST_TRANSITION); + if (rc < 0) + return rc; + + msleep(5); + return rc; +} + +static int32_t mt9t013_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = mt9t013_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + mt9t013_ctrl->curr_res = mt9t013_ctrl->pict_res; + mt9t013_ctrl->sensormode = mode; + return rc; +} + +static int32_t mt9t013_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = mt9t013_setting(UPDATE_PERIODIC, RES_CAPTURE); + if (rc < 0) + return rc; + + mt9t013_ctrl->curr_res = mt9t013_ctrl->pict_res; + mt9t013_ctrl->sensormode = mode; + return rc; +} + +static int32_t mt9t013_power_down(void) +{ + int32_t rc = 0; + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + MT9T013_REG_RESET_REGISTER, + MT9T013_RESET_REGISTER_PWOFF); + if (rc >= 0) + mdelay(5); + return rc; +} + +static int32_t mt9t013_move_focus(int direction, int32_t num_steps) +{ + int16_t step_direction; + int16_t actual_step; + int16_t next_position; + int16_t break_steps[4]; + uint8_t code_val_msb, code_val_lsb; + int16_t i; + + if (num_steps > MT9T013_TOTAL_STEPS_NEAR_TO_FAR) + num_steps = MT9T013_TOTAL_STEPS_NEAR_TO_FAR; + else if (num_steps == 0) + return -EINVAL; + + if (direction == MOVE_NEAR) + step_direction = 4; + else if (direction == MOVE_FAR) + step_direction = -4; + else + return -EINVAL; + + if (mt9t013_ctrl->curr_lens_pos < mt9t013_ctrl->init_curr_lens_pos) + mt9t013_ctrl->curr_lens_pos = mt9t013_ctrl->init_curr_lens_pos; + + actual_step = + (int16_t) (step_direction * + (int16_t) num_steps); + + for (i = 0; i < 4; i++) + break_steps[i] = + actual_step / 4 * (i + 1) - actual_step / 4 * i; + + for (i = 0; i < 4; i++) { + next_position = + (int16_t) + (mt9t013_ctrl->curr_lens_pos + break_steps[i]); + + if (next_position > 255) + next_position = 255; + else if (next_position < 0) + next_position = 0; + + code_val_msb = + ((next_position >> 4) << 2) | + ((next_position << 4) >> 6); + + code_val_lsb = + ((next_position & 0x03) << 6); + + /* Writing the digital code for current to the actuator */ + if (mt9t013_i2c_write_b(MT9T013_AF_I2C_ADDR>>1, + code_val_msb, code_val_lsb) < 0) + return -EBUSY; + + /* Storing the current lens Position */ + mt9t013_ctrl->curr_lens_pos = next_position; + + if (i < 3) + mdelay(1); + } /* for */ + + return 0; +} + +static int mt9t013_sensor_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + return 0; +} + +static int mt9t013_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int rc; + uint16_t chipid; + + rc = gpio_request(data->sensor_reset, "mt9t013"); + if (!rc) + gpio_direction_output(data->sensor_reset, 1); + else + goto init_probe_done; + + mdelay(20); + + /* RESET the sensor image part via I2C command */ + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + MT9T013_REG_RESET_REGISTER, 0x1009); + if (rc < 0) + goto init_probe_fail; + + msleep(10); + + /* 3. Read sensor Model ID: */ + rc = mt9t013_i2c_read_w(mt9t013_client->addr, + MT9T013_REG_MODEL_ID, &chipid); + + if (rc < 0) + goto init_probe_fail; + + CDBG("mt9t013 model_id = 0x%x\n", chipid); + + /* 4. Compare sensor ID to MT9T012VC ID: */ + if (chipid != MT9T013_MODEL_ID) { + rc = -ENODEV; + goto init_probe_fail; + } + + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + 0x3064, 0x0805); + if (rc < 0) + goto init_probe_fail; + + mdelay(MT9T013_RESET_DELAY_MSECS); + + goto init_probe_done; + + /* sensor: output enable */ +#if 0 + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + MT9T013_REG_RESET_REGISTER, + MT9T013_RESET_REGISTER_PWON); + + /* if this fails, the sensor is not the MT9T013 */ + rc = mt9t013_set_default_focus(0); +#endif + +init_probe_fail: + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); +init_probe_done: + return rc; +} + +static int32_t mt9t013_poweron_af(void) +{ + int32_t rc = 0; + + /* enable AF actuator */ + CDBG("enable AF actuator, gpio = %d\n", + mt9t013_ctrl->sensordata->vcm_pwd); + rc = gpio_request(mt9t013_ctrl->sensordata->vcm_pwd, "mt9t013"); + if (!rc) { + gpio_direction_output(mt9t013_ctrl->sensordata->vcm_pwd, 0); + mdelay(20); + rc = mt9t013_set_default_focus(0); + } else + pr_err("%s, gpio_request failed (%d)!\n", __func__, rc); + return rc; +} + +static void mt9t013_poweroff_af(void) +{ + gpio_direction_output(mt9t013_ctrl->sensordata->vcm_pwd, 1); + gpio_free(mt9t013_ctrl->sensordata->vcm_pwd); +} + +int mt9t013_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + + mt9t013_ctrl = kzalloc(sizeof(struct mt9t013_ctrl), GFP_KERNEL); + if (!mt9t013_ctrl) { + pr_err("mt9t013_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + + mt9t013_ctrl->fps_divider = 1 * 0x00000400; + mt9t013_ctrl->pict_fps_divider = 1 * 0x00000400; + mt9t013_ctrl->set_test = TEST_OFF; + mt9t013_ctrl->prev_res = QTR_SIZE; + mt9t013_ctrl->pict_res = FULL_SIZE; + + if (data) + mt9t013_ctrl->sensordata = data; + + /* enable mclk first */ + msm_camio_clk_rate_set(MT9T013_DEFAULT_CLOCK_RATE); + mdelay(20); + + msm_camio_camif_pad_reg_reset(); + mdelay(20); + + rc = mt9t013_probe_init_sensor(data); + if (rc < 0) + goto init_fail; + + if (mt9t013_ctrl->prev_res == QTR_SIZE) + rc = mt9t013_setting(REG_INIT, RES_PREVIEW); + else + rc = mt9t013_setting(REG_INIT, RES_CAPTURE); + + if (rc >= 0) + if (machine_is_sapphire()) + rc = mt9t013_poweron_af(); + + if (rc < 0) + goto init_fail; + else + goto init_done; + +init_fail: + kfree(mt9t013_ctrl); +init_done: + return rc; +} + +static int mt9t013_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&mt9t013_wait_queue); + return 0; +} + + +static int32_t mt9t013_set_sensor_mode(int mode, int res) +{ + int32_t rc = 0; + rc = mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (rc < 0) + return rc; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = mt9t013_video_config(mode, res); + break; + + case SENSOR_SNAPSHOT_MODE: + rc = mt9t013_snapshot_config(mode); + break; + + case SENSOR_RAW_SNAPSHOT_MODE: + rc = mt9t013_raw_snapshot_config(mode); + break; + + default: + return -EINVAL; + } + + /* FIXME: what should we do if rc < 0? */ + if (rc >= 0) + return mt9t013_i2c_write_w(mt9t013_client->addr, + REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_UPDATE); + return rc; +} + +int mt9t013_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + + if (copy_from_user(&cdata, (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + + down(&mt9t013_sem); + + CDBG("mt9t013_sensor_config: cfgtype = %d\n", cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + mt9t013_get_pict_fps(cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = mt9t013_get_prev_lines_pf(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = mt9t013_get_prev_pixels_pl(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = mt9t013_get_pict_lines_pf(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + mt9t013_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + mt9t013_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = mt9t013_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = mt9t013_write_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + rc = mt9t013_set_pict_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = mt9t013_set_sensor_mode(cdata.mode, cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = mt9t013_power_down(); + break; + + case CFG_MOVE_FOCUS: + rc = mt9t013_move_focus(cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = mt9t013_set_default_focus(cdata.cfg.focus.steps); + break; + + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = MT9T013_TOTAL_STEPS_NEAR_TO_FAR; + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_EFFECT: + default: + rc = -EINVAL; + break; + } + + up(&mt9t013_sem); + return rc; +} + +int mt9t013_sensor_release(void) +{ + int rc = -EBADF; + + down(&mt9t013_sem); + + if (machine_is_sapphire()) + mt9t013_poweroff_af(); + mt9t013_power_down(); + + gpio_direction_output(mt9t013_ctrl->sensordata->sensor_reset, + 0); + gpio_free(mt9t013_ctrl->sensordata->sensor_reset); + + kfree(mt9t013_ctrl); + + up(&mt9t013_sem); + CDBG("mt9t013_release completed!\n"); + return rc; +} + +static int mt9t013_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + rc = -ENOTSUPP; + goto probe_failure; + } + + mt9t013_sensorw = + kzalloc(sizeof(struct mt9t013_work), GFP_KERNEL); + + if (!mt9t013_sensorw) { + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, mt9t013_sensorw); + mt9t013_init_client(client); + mt9t013_client = client; + mt9t013_client->addr = mt9t013_client->addr >> 1; + mdelay(50); + + CDBG("i2c probe ok\n"); + return 0; + +probe_failure: + kfree(mt9t013_sensorw); + mt9t013_sensorw = NULL; + pr_err("i2c probe failure %d\n", rc); + return rc; +} + +static const struct i2c_device_id mt9t013_i2c_id[] = { + { "mt9t013", 0}, + { } +}; + +static struct i2c_driver mt9t013_i2c_driver = { + .id_table = mt9t013_i2c_id, + .probe = mt9t013_i2c_probe, + .remove = __exit_p(mt9t013_i2c_remove), + .driver = { + .name = "mt9t013", + }, +}; + +static int mt9t013_sensor_probe( + const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + /* We expect this driver to match with the i2c device registered + * in the board file immediately. */ + int rc = i2c_add_driver(&mt9t013_i2c_driver); + if (rc < 0 || mt9t013_client == NULL) { + rc = -ENOTSUPP; + goto probe_done; + } + + /* enable mclk first */ + msm_camio_clk_rate_set(MT9T013_DEFAULT_CLOCK_RATE); + mdelay(20); + + rc = mt9t013_probe_init_sensor(info); + if (rc < 0) { + i2c_del_driver(&mt9t013_i2c_driver); + goto probe_done; + } + + s->s_init = mt9t013_sensor_open_init; + s->s_release = mt9t013_sensor_release; + s->s_config = mt9t013_sensor_config; + s->s_mount_angle = 0; + mt9t013_sensor_init_done(info); + +probe_done: + return rc; +} + +static int __mt9t013_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, mt9t013_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __mt9t013_probe, + .driver = { + .name = "msm_camera_mt9t013", + .owner = THIS_MODULE, + }, +}; + +static int __init mt9t013_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(mt9t013_init); diff --git a/drivers/media/video/msm/mt9t013.h b/drivers/media/video/msm/mt9t013.h new file mode 100644 index 00000000000..f6b7c280f3e --- /dev/null +++ b/drivers/media/video/msm/mt9t013.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MT9T013_H +#define MT9T013_H + +#include + +extern struct mt9t013_reg mt9t013_regs; /* from mt9t013_reg.c */ + +struct reg_struct { + uint16_t vt_pix_clk_div; /* 0x0300 */ + uint16_t vt_sys_clk_div; /* 0x0302 */ + uint16_t pre_pll_clk_div; /* 0x0304 */ + uint16_t pll_multiplier; /* 0x0306 */ + uint16_t op_pix_clk_div; /* 0x0308 */ + uint16_t op_sys_clk_div; /* 0x030A */ + uint16_t scale_m; /* 0x0404 */ + uint16_t row_speed; /* 0x3016 */ + uint16_t x_addr_start; /* 0x3004 */ + uint16_t x_addr_end; /* 0x3008 */ + uint16_t y_addr_start; /* 0x3002 */ + uint16_t y_addr_end; /* 0x3006 */ + uint16_t read_mode; /* 0x3040 */ + uint16_t x_output_size; /* 0x034C */ + uint16_t y_output_size; /* 0x034E */ + uint16_t line_length_pck; /* 0x300C */ + uint16_t frame_length_lines; /* 0x300A */ + uint16_t coarse_int_time; /* 0x3012 */ + uint16_t fine_int_time; /* 0x3014 */ +}; + +struct mt9t013_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +struct mt9t013_reg { + struct reg_struct const *reg_pat; + uint16_t reg_pat_size; + struct mt9t013_i2c_reg_conf const *ttbl; + uint16_t ttbl_size; + struct mt9t013_i2c_reg_conf const *lctbl; + uint16_t lctbl_size; + struct mt9t013_i2c_reg_conf const *rftbl; + uint16_t rftbl_size; +}; + +#endif /* #define MT9T013_H */ diff --git a/drivers/media/video/msm/mt9t013_reg.c b/drivers/media/video/msm/mt9t013_reg.c new file mode 100644 index 00000000000..9a9867e2cc2 --- /dev/null +++ b/drivers/media/video/msm/mt9t013_reg.c @@ -0,0 +1,275 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "mt9t013.h" +#include + +struct reg_struct const mt9t013_reg_pat[2] = { + { /* Preview 2x2 binning 20fps, pclk MHz, MCLK 24MHz */ + /* vt_pix_clk_div:REG=0x0300 update get_snapshot_fps + * if this change */ + 8, + + /* vt_sys_clk_div: REG=0x0302 update get_snapshot_fps + * if this change */ + 1, + + /* pre_pll_clk_div REG=0x0304 update get_snapshot_fps + * if this change */ + 2, + + /* pll_multiplier REG=0x0306 60 for 30fps preview, 40 + * for 20fps preview + * 46 for 30fps preview, try 47/48 to increase further */ + 46, + + /* op_pix_clk_div REG=0x0308 */ + 8, + + /* op_sys_clk_div REG=0x030A */ + 1, + + /* scale_m REG=0x0404 */ + 16, + + /* row_speed REG=0x3016 */ + 0x0111, + + /* x_addr_start REG=0x3004 */ + 8, + + /* x_addr_end REG=0x3008 */ + 2053, + + /* y_addr_start REG=0x3002 */ + 8, + + /* y_addr_end REG=0x3006 */ + 1541, + + /* read_mode REG=0x3040 */ + 0x046C, + + /* x_output_size REG=0x034C */ + 1024, + + /* y_output_size REG=0x034E */ + 768, + + /* line_length_pck REG=0x300C */ + 2616, + + /* frame_length_lines REG=0x300A */ + 916, + + /* coarse_int_time REG=0x3012 */ + 16, + + /* fine_int_time REG=0x3014 */ + 1461 + }, + { /*Snapshot */ + /* vt_pix_clk_div REG=0x0300 update get_snapshot_fps + * if this change */ + 8, + + /* vt_sys_clk_div REG=0x0302 update get_snapshot_fps + * if this change */ + 1, + + /* pre_pll_clk_div REG=0x0304 update get_snapshot_fps + * if this change */ + 2, + + /* pll_multiplier REG=0x0306 50 for 15fps snapshot, + * 40 for 10fps snapshot + * 46 for 30fps snapshot, try 47/48 to increase further */ + 46, + + /* op_pix_clk_div REG=0x0308 */ + 8, + + /* op_sys_clk_div REG=0x030A */ + 1, + + /* scale_m REG=0x0404 */ + 16, + + /* row_speed REG=0x3016 */ + 0x0111, + + /* x_addr_start REG=0x3004 */ + 8, + + /* x_addr_end REG=0x3008 */ + 2071, + + /* y_addr_start REG=0x3002 */ + 8, + + /* y_addr_end REG=0x3006 */ + 1551, + + /* read_mode REG=0x3040 */ + 0x0024, + + /* x_output_size REG=0x034C */ + 2064, + + /* y_output_size REG=0x034E */ + 1544, + + /* line_length_pck REG=0x300C */ + 2952, + + /* frame_length_lines REG=0x300A */ + 1629, + + /* coarse_int_time REG=0x3012 */ + 16, + + /* fine_int_time REG=0x3014 */ + 733 + } +}; + +struct mt9t013_i2c_reg_conf const mt9t013_test_tbl[] = { + { 0x3044, 0x0544 & 0xFBFF }, + { 0x30CA, 0x0004 | 0x0001 }, + { 0x30D4, 0x9020 & 0x7FFF }, + { 0x31E0, 0x0003 & 0xFFFE }, + { 0x3180, 0x91FF & 0x7FFF }, + { 0x301A, (0x10CC | 0x8000) & 0xFFF7 }, + { 0x301E, 0x0000 }, + { 0x3780, 0x0000 }, +}; + +/* [Lens shading 85 Percent TL84] */ +struct mt9t013_i2c_reg_conf const mt9t013_lc_tbl[] = { + { 0x360A, 0x0290 }, /* P_RD_P0Q0 */ + { 0x360C, 0xC92D }, /* P_RD_P0Q1 */ + { 0x360E, 0x0771 }, /* P_RD_P0Q2 */ + { 0x3610, 0xE38C }, /* P_RD_P0Q3 */ + { 0x3612, 0xD74F }, /* P_RD_P0Q4 */ + { 0x364A, 0x168C }, /* P_RD_P1Q0 */ + { 0x364C, 0xCACB }, /* P_RD_P1Q1 */ + { 0x364E, 0x8C4C }, /* P_RD_P1Q2 */ + { 0x3650, 0x0BEA }, /* P_RD_P1Q3 */ + { 0x3652, 0xDC0F }, /* P_RD_P1Q4 */ + { 0x368A, 0x70B0 }, /* P_RD_P2Q0 */ + { 0x368C, 0x200B }, /* P_RD_P2Q1 */ + { 0x368E, 0x30B2 }, /* P_RD_P2Q2 */ + { 0x3690, 0xD04F }, /* P_RD_P2Q3 */ + { 0x3692, 0xACF5 }, /* P_RD_P2Q4 */ + { 0x36CA, 0xF7C9 }, /* P_RD_P3Q0 */ + { 0x36CC, 0x2AED }, /* P_RD_P3Q1 */ + { 0x36CE, 0xA652 }, /* P_RD_P3Q2 */ + { 0x36D0, 0x8192 }, /* P_RD_P3Q3 */ + { 0x36D2, 0x3A15 }, /* P_RD_P3Q4 */ + { 0x370A, 0xDA30 }, /* P_RD_P4Q0 */ + { 0x370C, 0x2E2F }, /* P_RD_P4Q1 */ + { 0x370E, 0xBB56 }, /* P_RD_P4Q2 */ + { 0x3710, 0x8195 }, /* P_RD_P4Q3 */ + { 0x3712, 0x02F9 }, /* P_RD_P4Q4 */ + { 0x3600, 0x0230 }, /* P_GR_P0Q0 */ + { 0x3602, 0x58AD }, /* P_GR_P0Q1 */ + { 0x3604, 0x18D1 }, /* P_GR_P0Q2 */ + { 0x3606, 0x260D }, /* P_GR_P0Q3 */ + { 0x3608, 0xF530 }, /* P_GR_P0Q4 */ + { 0x3640, 0x17EB }, /* P_GR_P1Q0 */ + { 0x3642, 0x3CAB }, /* P_GR_P1Q1 */ + { 0x3644, 0x87CE }, /* P_GR_P1Q2 */ + { 0x3646, 0xC02E }, /* P_GR_P1Q3 */ + { 0x3648, 0xF48F }, /* P_GR_P1Q4 */ + { 0x3680, 0x5350 }, /* P_GR_P2Q0 */ + { 0x3682, 0x7EAF }, /* P_GR_P2Q1 */ + { 0x3684, 0x4312 }, /* P_GR_P2Q2 */ + { 0x3686, 0xC652 }, /* P_GR_P2Q3 */ + { 0x3688, 0xBC15 }, /* P_GR_P2Q4 */ + { 0x36C0, 0xB8AD }, /* P_GR_P3Q0 */ + { 0x36C2, 0xBDCD }, /* P_GR_P3Q1 */ + { 0x36C4, 0xE4B2 }, /* P_GR_P3Q2 */ + { 0x36C6, 0xB50F }, /* P_GR_P3Q3 */ + { 0x36C8, 0x5B95 }, /* P_GR_P3Q4 */ + { 0x3700, 0xFC90 }, /* P_GR_P4Q0 */ + { 0x3702, 0x8C51 }, /* P_GR_P4Q1 */ + { 0x3704, 0xCED6 }, /* P_GR_P4Q2 */ + { 0x3706, 0xB594 }, /* P_GR_P4Q3 */ + { 0x3708, 0x0A39 }, /* P_GR_P4Q4 */ + { 0x3614, 0x0230 }, /* P_BL_P0Q0 */ + { 0x3616, 0x160D }, /* P_BL_P0Q1 */ + { 0x3618, 0x08D1 }, /* P_BL_P0Q2 */ + { 0x361A, 0x98AB }, /* P_BL_P0Q3 */ + { 0x361C, 0xEA50 }, /* P_BL_P0Q4 */ + { 0x3654, 0xB4EA }, /* P_BL_P1Q0 */ + { 0x3656, 0xEA6C }, /* P_BL_P1Q1 */ + { 0x3658, 0xFE08 }, /* P_BL_P1Q2 */ + { 0x365A, 0x2C6E }, /* P_BL_P1Q3 */ + { 0x365C, 0xEB0E }, /* P_BL_P1Q4 */ + { 0x3694, 0x6DF0 }, /* P_BL_P2Q0 */ + { 0x3696, 0x3ACF }, /* P_BL_P2Q1 */ + { 0x3698, 0x3E0F }, /* P_BL_P2Q2 */ + { 0x369A, 0xB2B1 }, /* P_BL_P2Q3 */ + { 0x369C, 0xC374 }, /* P_BL_P2Q4 */ + { 0x36D4, 0xF2AA }, /* P_BL_P3Q0 */ + { 0x36D6, 0x8CCC }, /* P_BL_P3Q1 */ + { 0x36D8, 0xDEF2 }, /* P_BL_P3Q2 */ + { 0x36DA, 0xFA11 }, /* P_BL_P3Q3 */ + { 0x36DC, 0x42F5 }, /* P_BL_P3Q4 */ + { 0x3714, 0xF4F1 }, /* P_BL_P4Q0 */ + { 0x3716, 0xF6F0 }, /* P_BL_P4Q1 */ + { 0x3718, 0x8FD6 }, /* P_BL_P4Q2 */ + { 0x371A, 0xEA14 }, /* P_BL_P4Q3 */ + { 0x371C, 0x6338 }, /* P_BL_P4Q4 */ + { 0x361E, 0x0350 }, /* P_GB_P0Q0 */ + { 0x3620, 0x91AE }, /* P_GB_P0Q1 */ + { 0x3622, 0x0571 }, /* P_GB_P0Q2 */ + { 0x3624, 0x100D }, /* P_GB_P0Q3 */ + { 0x3626, 0xCA70 }, /* P_GB_P0Q4 */ + { 0x365E, 0xE6CB }, /* P_GB_P1Q0 */ + { 0x3660, 0x50ED }, /* P_GB_P1Q1 */ + { 0x3662, 0x3DAE }, /* P_GB_P1Q2 */ + { 0x3664, 0xAA4F }, /* P_GB_P1Q3 */ + { 0x3666, 0xDC50 }, /* P_GB_P1Q4 */ + { 0x369E, 0x5470 }, /* P_GB_P2Q0 */ + { 0x36A0, 0x1F6E }, /* P_GB_P2Q1 */ + { 0x36A2, 0x6671 }, /* P_GB_P2Q2 */ + { 0x36A4, 0xC010 }, /* P_GB_P2Q3 */ + { 0x36A6, 0x8DF5 }, /* P_GB_P2Q4 */ + { 0x36DE, 0x0B0C }, /* P_GB_P3Q0 */ + { 0x36E0, 0x84CE }, /* P_GB_P3Q1 */ + { 0x36E2, 0x8493 }, /* P_GB_P3Q2 */ + { 0x36E4, 0xA610 }, /* P_GB_P3Q3 */ + { 0x36E6, 0x50B5 }, /* P_GB_P3Q4 */ + { 0x371E, 0x9651 }, /* P_GB_P4Q0 */ + { 0x3720, 0x1EAB }, /* P_GB_P4Q1 */ + { 0x3722, 0xAF76 }, /* P_GB_P4Q2 */ + { 0x3724, 0xE4F4 }, /* P_GB_P4Q3 */ + { 0x3726, 0x79F8 }, /* P_GB_P4Q4 */ + { 0x3782, 0x0410 }, /* POLY_ORIGIN_C */ + { 0x3784, 0x0320 }, /* POLY_ORIGIN_R */ + { 0x3780, 0x8000 } /* POLY_SC_ENABLE */ +}; + +struct mt9t013_reg mt9t013_regs = { + .reg_pat = &mt9t013_reg_pat[0], + .reg_pat_size = ARRAY_SIZE(mt9t013_reg_pat), + .ttbl = &mt9t013_test_tbl[0], + .ttbl_size = ARRAY_SIZE(mt9t013_test_tbl), + .lctbl = &mt9t013_lc_tbl[0], + .lctbl_size = ARRAY_SIZE(mt9t013_lc_tbl), + .rftbl = &mt9t013_lc_tbl[0], /* &mt9t013_rolloff_tbl[0], */ + .rftbl_size = ARRAY_SIZE(mt9t013_lc_tbl) +}; + + diff --git a/drivers/media/video/msm/ov2720.c b/drivers/media/video/msm/ov2720.c new file mode 100644 index 00000000000..cfd6efac667 --- /dev/null +++ b/drivers/media/video/msm/ov2720.c @@ -0,0 +1,598 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "msm_sensor.h" +#include "ov2720.h" +#include "msm.h" +#define SENSOR_NAME "ov2720" +#define PLATFORM_DRIVER_NAME "msm_camera_ov2720" +#define ov2720_obj ov2720_##obj + +DEFINE_MUTEX(ov2720_mut); +static struct msm_sensor_ctrl_t ov2720_s_ctrl; + +struct msm_sensor_i2c_reg_conf ov2720_start_settings[] = { + {0x0100, 0x01}, +}; + +struct msm_sensor_i2c_reg_conf ov2720_stop_settings[] = { + {0x0100, 0x00}, +}; + +struct msm_sensor_i2c_reg_conf ov2720_groupon_settings[] = { + {0x3208, 0x00}, +}; + +struct msm_sensor_i2c_reg_conf ov2720_groupoff_settings[] = { + {0x3208, 0x10}, + {0x3208, 0xA0}, +}; + +static struct msm_sensor_i2c_reg_conf ov2720_prev_settings[] = { + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x02}, + {0x3804, 0x07}, + {0x3805, 0x97}, + {0x3806, 0x04}, + {0x3807, 0x45}, + {0x3808, 0x07}, + {0x3809, 0x80}, + {0x380a, 0x04}, + {0x380b, 0x38}, + {0x380c, 0x08},/*Line Length Pclk Hi*/ + {0x380d, 0x5c},/*Line Length Pclk Lo*/ + {0x380e, 0x04},/*Frame Length Line Hi*/ + {0x380f, 0x60},/*Frame Length Line Lo*/ + {0x3810, 0x00}, + {0x3811, 0x05}, + {0x3812, 0x00}, + {0x3813, 0x06}, + {0x3820, 0x80}, + {0x3821, 0x06}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x3a08, 0x01}, + {0x3a09, 0x50}, + {0x3a0a, 0x01}, + {0x3a0b, 0x18}, + {0x3a0d, 0x03}, + {0x3a0e, 0x03}, + {0x4520, 0x00}, + {0x4837, 0x1b}, + {0x3000, 0xff}, + {0x3001, 0xff}, + {0x3002, 0xf0}, + {0x3600, 0x08}, + {0x3621, 0xc0}, + {0x3632, 0xd2}, + {0x3633, 0x23}, + {0x3634, 0x54}, + {0x3f01, 0x0c}, + {0x5001, 0xc1}, + {0x3614, 0xf0}, + {0x3630, 0x2d}, + {0x370b, 0x62}, + {0x3706, 0x61}, + {0x4000, 0x02}, + {0x4002, 0xc5}, + {0x4005, 0x08}, + {0x404f, 0x84}, + {0x4051, 0x00}, + {0x5000, 0xff}, + {0x3a18, 0x00}, + {0x3a19, 0x80}, + {0x3503, 0x13}, + {0x4521, 0x00}, + {0x5183, 0xb0}, + {0x5184, 0xb0}, + {0x5185, 0xb0}, + {0x370c, 0x0c}, + {0x3035, 0x10}, + {0x3036, 0x1e}, + {0x3037, 0x21}, + {0x303e, 0x19}, + {0x3038, 0x06}, + {0x3018, 0x04}, + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x00}, + {0x3a0f, 0x40}, + {0x3a10, 0x38}, + {0x3a1b, 0x48}, + {0x3a1e, 0x30}, + {0x3a11, 0x90}, + {0x3a1f, 0x10}, + {0x4800, 0x24}, +}; + +static struct msm_sensor_i2c_reg_conf ov2720_720_settings[] = { + {0x3800, 0x01}, + {0x3801, 0x4a}, + {0x3802, 0x00}, + {0x3803, 0xba}, + {0x3804, 0x06}, + {0x3805, 0x51+32}, + {0x3806, 0x03}, + {0x3807, 0x8d+24}, + {0x3808, 0x05}, + {0x3809, 0x00+16}, + {0x380a, 0x02}, + {0x380b, 0x78}, + {0x380c, 0x08},/*Line Length Pclk Hi*/ + {0x380d, 0x5e},/*Line Length Pclk Lo*/ + {0x380e, 0x04},/*Frame Length Line Hi*/ + {0x380f, 0x60},/*Frame Length Line Lo*/ + {0x3810, 0x00}, + {0x3811, 0x05}, + {0x3812, 0x00}, + {0x3813, 0x02}, + {0x3820, 0x80}, + {0x3821, 0x06}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x3a08, 0x01}, + {0x3a09, 0x50}, + {0x3a0a, 0x01}, + {0x3a0b, 0x18}, + {0x3a0d, 0x03}, + {0x3a0e, 0x03}, + {0x4520, 0x00}, + {0x4837, 0x1b}, + {0x3000, 0xff}, + {0x3001, 0xff}, + {0x3002, 0xf0}, + {0x3600, 0x08}, + {0x3621, 0xc0}, + {0x3632, 0xd2}, + {0x3633, 0x23}, + {0x3634, 0x54}, + {0x3f01, 0x0c}, + {0x5001, 0xc1}, + {0x3614, 0xf0}, + {0x3630, 0x2d}, + {0x370b, 0x62}, + {0x3706, 0x61}, + {0x4000, 0x02}, + {0x4002, 0xc5}, + {0x4005, 0x08}, + {0x404f, 0x84}, + {0x4051, 0x00}, + {0x5000, 0xff}, + {0x3a18, 0x00}, + {0x3a19, 0x80}, + {0x3503, 0x13}, + {0x4521, 0x00}, + {0x5183, 0xb0}, + {0x5184, 0xb0}, + {0x5185, 0xb0}, + {0x370c, 0x0c}, + {0x3035, 0x10}, + {0x3036, 0x04}, + {0x3037, 0x61}, + {0x303e, 0x19}, + {0x3038, 0x06}, + {0x3018, 0x04}, + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x00}, + {0x3a0f, 0x40}, + {0x3a10, 0x38}, + {0x3a1b, 0x48}, + {0x3a1e, 0x30}, + {0x3a11, 0x90}, + {0x3a1f, 0x10}, + {0x4800, 0x24}, +}; + +static struct msm_sensor_i2c_reg_conf ov2720_vga_settings[] = { + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x02}, + {0x3804, 0x07}, + {0x3805, 0x97+32}, + {0x3806, 0x04}, + {0x3807, 0x45+24}, + {0x3808, 0x02}, + {0x3809, 0x88+16}, + {0x380a, 0x01}, + {0x380b, 0xe6+12}, + {0x380c, 0x08},/*Line Length Pclk Hi*/ + {0x380d, 0x5e},/*Line Length Pclk Lo*/ + {0x380e, 0x04},/*Frame Length Line Hi*/ + {0x380f, 0x68},/*Frame Length Line Lo*/ + {0x3810, 0x00}, + {0x3811, 0x03}, + {0x3812, 0x00}, + {0x3813, 0x03}, + {0x3820, 0x80}, + {0x3821, 0x06}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x3a08, 0x01}, + {0x3a09, 0x50}, + {0x3a0a, 0x01}, + {0x3a0b, 0x18}, + {0x3a0d, 0x03}, + {0x3a0e, 0x03}, + {0x4520, 0x00}, + {0x4837, 0x1b}, + {0x3000, 0xff}, + {0x3001, 0xff}, + {0x3002, 0xf0}, + {0x3600, 0x08}, + {0x3621, 0xc0}, + {0x3632, 0xd2}, + {0x3633, 0x23}, + {0x3634, 0x54}, + {0x3f01, 0x0c}, + {0x5001, 0xc1}, + {0x3614, 0xf0}, + {0x3630, 0x2d}, + {0x370b, 0x62}, + {0x3706, 0x61}, + {0x4000, 0x02}, + {0x4002, 0xc5}, + {0x4005, 0x08}, + {0x404f, 0x84}, + {0x4051, 0x00}, + {0x5000, 0xff}, + {0x3a18, 0x00}, + {0x3a19, 0x80}, + {0x3503, 0x13}, + {0x4521, 0x00}, + {0x5183, 0xb0}, + {0x5184, 0xb0}, + {0x5185, 0xb0}, + {0x370c, 0x0c}, + {0x3035, 0x10}, + {0x3036, 0x04}, + {0x3037, 0x61}, + {0x303e, 0x19}, + {0x3038, 0x06}, + {0x3018, 0x04}, + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x00}, + {0x3a0f, 0x40}, + {0x3a10, 0x38}, + {0x3a1b, 0x48}, + {0x3a1e, 0x30}, + {0x3a11, 0x90}, + {0x3a1f, 0x10}, + {0x4800, 0x24}, + {0x3500, 0x00}, + {0x3501, 0x17}, + {0x3502, 0xf0}, + {0x3508, 0x00}, + {0x3509, 0x20}, +}; + +static struct msm_sensor_i2c_reg_conf ov2720_recommend_settings[] = { + {0x0103, 0x01}, + {0x3718, 0x10}, + {0x3702, 0x24}, + {0x373a, 0x60}, + {0x3715, 0x01}, + {0x3703, 0x2e}, + {0x3705, 0x10}, + {0x3730, 0x30}, + {0x3704, 0x62}, + {0x3f06, 0x3a}, + {0x371c, 0x00}, + {0x371d, 0xc4}, + {0x371e, 0x01}, + {0x371f, 0x0d}, + {0x3708, 0x61}, + {0x3709, 0x12}, +}; + +static struct msm_camera_csi_params ov2720_csi_params = { + .lane_cnt = 2, + .data_format = CSI_10BIT, + .lane_assign = 0xe4, + .dpcm_scheme = 0, + .settle_cnt = 0x18, +}; + +static struct v4l2_subdev_info ov2720_subdev_info[] = { + { + .code = V4L2_MBUS_FMT_SBGGR10_1X10, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 0, + }, + /* more can be supported, to be added later */ +}; + +static struct msm_sensor_i2c_conf_array ov2720_init_conf[] = { + {&ov2720_recommend_settings[0], + ARRAY_SIZE(ov2720_recommend_settings), 0} +}; + +static struct msm_sensor_i2c_conf_array ov2720_confs[] = { + {&ov2720_prev_settings[0], ARRAY_SIZE(ov2720_prev_settings), 0}, + {&ov2720_vga_settings[0], ARRAY_SIZE(ov2720_vga_settings), 0}, + {&ov2720_720_settings[0], ARRAY_SIZE(ov2720_720_settings), 0}, +}; + +static int32_t ov2720_write_exp_gain(struct msm_sensor_ctrl_t *s_ctrl, + uint16_t gain, uint32_t line) +{ + uint32_t fl_lines, offset; + fl_lines = + (s_ctrl->curr_frame_length_lines * s_ctrl->fps_divider) / Q10; + offset = s_ctrl->vert_offset; + if (line > (fl_lines - offset)) + fl_lines = line + offset; + + pr_err("LINE: 0x%x\n", line); + s_ctrl->func_tbl.sensor_group_hold_on(s_ctrl); + msm_sensor_i2c_waddr_write_w(s_ctrl, + s_ctrl->frame_length_lines_addr, fl_lines); + msm_sensor_i2c_waddr_write_b(s_ctrl, + s_ctrl->coarse_int_time_addr-1, line >> 12); + msm_sensor_i2c_waddr_write_w(s_ctrl, + s_ctrl->coarse_int_time_addr, ((line << 4) & 0xFFFF)); + msm_sensor_i2c_waddr_write_w(s_ctrl, + s_ctrl->global_gain_addr, gain); + s_ctrl->func_tbl.sensor_group_hold_off(s_ctrl); + return 0; +} + +static int32_t ov2720_sensor_setting(struct msm_sensor_ctrl_t *s_ctrl, + int update_type, int rt) +{ + struct msm_camera_csid_params ov2720_csid_params; + struct msm_camera_csiphy_params ov2720_csiphy_params; + int32_t rc = 0; + s_ctrl->func_tbl.sensor_stop_stream(s_ctrl); + msleep(30); + if (update_type == MSM_SENSOR_REG_INIT) { + s_ctrl->config_csi_flag = 1; + msm_sensor_enable_debugfs(s_ctrl); + msm_sensor_write_b_init_settings(s_ctrl); + } else if (update_type == MSM_SENSOR_UPDATE_PERIODIC) { + msm_sensor_write_b_res_settings(s_ctrl, rt); + if (s_ctrl->config_csi_flag) { + struct msm_camera_csid_vc_cfg ov2720_vccfg[] = { + {0, CSI_RAW10, CSI_DECODE_10BIT}, + {1, CSI_EMBED_DATA, CSI_DECODE_8BIT}, + }; + ov2720_csid_params.lane_cnt = 2; + ov2720_csid_params.lane_assign = 0xe4; + ov2720_csid_params.lut_params.num_cid = + ARRAY_SIZE(ov2720_vccfg); + ov2720_csid_params.lut_params.vc_cfg = + &ov2720_vccfg[0]; + ov2720_csiphy_params.lane_cnt = 2; + ov2720_csiphy_params.settle_cnt = 0x1B; + rc = msm_camio_csid_config(&ov2720_csid_params); + v4l2_subdev_notify(s_ctrl->sensor_v4l2_subdev, + NOTIFY_CID_CHANGE, NULL); + mb(); + rc = msm_camio_csiphy_config(&ov2720_csiphy_params); + mb(); + msleep(20); + s_ctrl->config_csi_flag = 0; + } + s_ctrl->func_tbl.sensor_start_stream(s_ctrl); + msleep(30); + } + return rc; +} + +static int ov2720_sensor_config(void __user *argp) +{ + return (int) msm_sensor_config(&ov2720_s_ctrl, argp); +} + +static int ov2720_power_down(const struct msm_camera_sensor_info *data) +{ + pr_err("%s\n", __func__); + gpio_set_value_cansleep(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + return 0; +} + +static int ov2720_power_up(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + pr_err("%s: %d\n", __func__, __LINE__); + msm_camio_clk_rate_set(MSM_SENSOR_MCLK_24HZ); + rc = gpio_request(data->sensor_reset, "SENSOR_NAME"); + if (rc < 0) + goto gpio_request_fail; + + pr_err("%s: reset sensor\n", __func__); + gpio_direction_output(data->sensor_reset, 0); + msleep(50); + gpio_set_value_cansleep(data->sensor_reset, 1); + msleep(50); + + rc = msm_sensor_match_id(&ov2720_s_ctrl); + if (rc < 0) + goto init_probe_fail; + + goto init_probe_done; +gpio_request_fail: + pr_err("%s: gpio request fail\n", __func__); + return rc; +init_probe_fail: + pr_err(" %s fails\n", __func__); + ov2720_power_down(data); + return rc; +init_probe_done: + pr_err("%s finishes\n", __func__); + return rc; +} + +static int ov2720_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + pr_err("%s: %d\n", __func__, __LINE__); + ov2720_s_ctrl.fps = 30*Q8; + ov2720_s_ctrl.fps_divider = 1 * 0x00000400; + ov2720_s_ctrl.cam_mode = MSM_SENSOR_MODE_INVALID; + + if (data) + ov2720_s_ctrl.sensordata = data; + + rc = ov2720_power_up(data); + if (rc < 0) + goto init_done; + + goto init_done; +init_done: + pr_err("%s finishes\n", __func__); + return rc; +} + +static int ov2720_sensor_release(void) +{ + mutex_lock(ov2720_s_ctrl.msm_sensor_mutex); + gpio_set_value_cansleep(ov2720_s_ctrl.sensordata->sensor_reset, 0); + msleep(20); + gpio_free(ov2720_s_ctrl.sensordata->sensor_reset); + mutex_unlock(ov2720_s_ctrl.msm_sensor_mutex); + pr_err("%s completed\n", __func__); + return 0; +} + +static const struct i2c_device_id ov2720_i2c_id[] = { + {SENSOR_NAME, (kernel_ulong_t)&ov2720_s_ctrl}, + { } +}; + +static struct i2c_driver ov2720_i2c_driver = { + .id_table = ov2720_i2c_id, + .probe = msm_sensor_i2c_probe, + .driver = { + .name = SENSOR_NAME, + }, +}; + +static int ov2720_sensor_v4l2_probe(const struct msm_camera_sensor_info *info, + struct v4l2_subdev *sdev, struct msm_sensor_ctrl *s) +{ + return msm_sensor_v4l2_probe(&ov2720_s_ctrl, info, sdev, s); +} + +static int ov2720_probe(struct platform_device *pdev) +{ + return msm_sensor_register(pdev, ov2720_sensor_v4l2_probe); +} + +struct platform_driver ov2720_driver = { + .probe = ov2720_probe, + .driver = { + .name = PLATFORM_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_sensor_init_module(void) +{ + return platform_driver_register(&ov2720_driver); +} + +static struct v4l2_subdev_core_ops ov2720_subdev_core_ops; +static struct v4l2_subdev_video_ops ov2720_subdev_video_ops = { + .enum_mbus_fmt = msm_sensor_v4l2_enum_fmt, +}; + +static struct v4l2_subdev_ops ov2720_subdev_ops = { + .core = &ov2720_subdev_core_ops, + .video = &ov2720_subdev_video_ops, +}; + +static struct msm_sensor_ctrl_t ov2720_s_ctrl = { + .msm_sensor_reg = { + .start_stream_conf = ov2720_start_settings, + .start_stream_conf_size = ARRAY_SIZE(ov2720_start_settings), + .stop_stream_conf = ov2720_stop_settings, + .stop_stream_conf_size = ARRAY_SIZE(ov2720_stop_settings), + .group_hold_on_conf = ov2720_groupon_settings, + .group_hold_on_conf_size = ARRAY_SIZE(ov2720_groupon_settings), + .group_hold_off_conf = ov2720_groupoff_settings, + .group_hold_off_conf_size = + ARRAY_SIZE(ov2720_groupoff_settings), + .init_settings = &ov2720_init_conf[0], + .init_size = ARRAY_SIZE(ov2720_init_conf), + .res_settings = &ov2720_confs[0], + .num_conf = ARRAY_SIZE(ov2720_confs), + }, + .sensor_id_addr = 0x300A, + .sensor_id = 0x2720, + .frame_length_lines_addr = 0x380e, + .coarse_int_time_addr = 0x3501, + .global_gain_addr = 0x3508, + .line_length_pck_addr = 0x380c, + .frame_length_lines_array_addr = 14, + .line_length_pck_array_addr = 12, + .vert_offset = 6, + .cam_mode = MSM_SENSOR_MODE_INVALID, + .camera_type = FRONT_CAMERA_2D, + .config_csi_flag = 1, + .csi_params = &ov2720_csi_params, + .msm_sensor_mutex = &ov2720_mut, + .msm_sensor_i2c_driver = &ov2720_i2c_driver, + .sensor_v4l2_subdev_info = ov2720_subdev_info, + .sensor_v4l2_subdev_info_size = ARRAY_SIZE(ov2720_subdev_info), + .sensor_v4l2_subdev_ops = &ov2720_subdev_ops, + + .func_tbl = { + .sensor_start_stream = msm_sensor_start_stream, + .sensor_stop_stream = msm_sensor_stop_stream, + .sensor_group_hold_on = msm_sensor_group_hold_on, + .sensor_group_hold_off = msm_sensor_group_hold_off, + .sensor_get_prev_lines_pf = msm_sensor_get_prev_lines_pf, + .sensor_get_prev_pixels_pl = msm_sensor_get_prev_pixels_pl, + .sensor_get_pict_lines_pf = msm_sensor_get_pict_lines_pf, + .sensor_get_pict_pixels_pl = msm_sensor_get_pict_pixels_pl, + .sensor_get_pict_max_exp_lc = msm_sensor_get_pict_max_exp_lc, + .sensor_get_pict_fps = msm_sensor_get_pict_fps, + .sensor_set_fps = msm_sensor_set_fps, + .sensor_write_exp_gain = ov2720_write_exp_gain, + .sensor_setting = ov2720_sensor_setting, + .sensor_set_sensor_mode = msm_sensor_set_sensor_mode_b, + .sensor_mode_init = msm_sensor_mode_init_bdata, + .sensor_config = ov2720_sensor_config, + .sensor_open_init = ov2720_sensor_open_init, + .sensor_release = ov2720_sensor_release, + .sensor_power_up = ov2720_power_up, + .sensor_power_down = ov2720_power_down, + .sensor_probe = msm_sensor_probe, + }, +}; + +module_init(msm_sensor_init_module); +MODULE_DESCRIPTION("Omnivision 2MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + + diff --git a/drivers/media/video/msm/ov2720.h b/drivers/media/video/msm/ov2720.h new file mode 100644 index 00000000000..7077a7db929 --- /dev/null +++ b/drivers/media/video/msm/ov2720.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +extern struct platform_driver ov2720_driver; + diff --git a/drivers/media/video/msm/ov2720_reg.c b/drivers/media/video/msm/ov2720_reg.c new file mode 100644 index 00000000000..bf094a58de8 --- /dev/null +++ b/drivers/media/video/msm/ov2720_reg.c @@ -0,0 +1,123 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "ov2720.h" + +struct ov2720_i2c_reg_conf ov2720_prev_settings[] = { + {0x3800, 0x00}, + {0x3801, 0x0c}, + {0x3802, 0x00}, + {0x3803, 0x02}, + {0x3804, 0x07}, + {0x3805, 0x97}, + {0x3806, 0x04}, + {0x3807, 0x45}, + {0x3808, 0x07}, + {0x3809, 0x80}, + {0x380a, 0x04}, + {0x380b, 0x38}, + {0x380c, 0x08},/*Line Length Pclk Hi*/ + {0x380d, 0x5c},/*Line Length Pclk Lo*/ + {0x380e, 0x04},/*Frame Length Line Hi*/ + {0x380f, 0x60},/*Frame Length Line Lo*/ + {0x3810, 0x00}, + {0x3811, 0x05}, + {0x3812, 0x00}, + {0x3813, 0x06}, + {0x3820, 0x80}, + {0x3821, 0x06}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3612, 0x0b}, + {0x3618, 0x04}, + {0x3a08, 0x01}, + {0x3a09, 0x50}, + {0x3a0a, 0x01}, + {0x3a0b, 0x18}, + {0x3a0d, 0x03}, + {0x3a0e, 0x03}, + {0x4520, 0x00}, + {0x4837, 0x1b}, + {0x3000, 0xff}, + {0x3001, 0xff}, + {0x3002, 0xf0}, + {0x3600, 0x08}, + {0x3621, 0xc0}, + {0x3632, 0xd2}, + {0x3633, 0x23}, + {0x3634, 0x54}, + {0x3f01, 0x0c}, + {0x5001, 0xc1}, + {0x3614, 0xf0}, + {0x3630, 0x2d}, + {0x370b, 0x62}, + {0x3706, 0x61}, + {0x4000, 0x02}, + {0x4002, 0xc5}, + {0x4005, 0x08}, + {0x404f, 0x84}, + {0x4051, 0x00}, + {0x5000, 0xff}, + {0x3a18, 0x00}, + {0x3a19, 0x80}, + {0x3503, 0x00}, + {0x4521, 0x00}, + {0x5183, 0xb0}, + {0x5184, 0xb0}, + {0x5185, 0xb0}, + {0x370c, 0x0c}, +}; + +struct ov2720_i2c_reg_conf ov2720_recommend_settings[] = { + {0x0103, 0x01}, + {0x3718, 0x10}, + {0x3702, 0x24}, + {0x373a, 0x60}, + {0x3715, 0x01}, + {0x3703, 0x2e}, + {0x3705, 0x10}, + {0x3730, 0x30}, + {0x3704, 0x62}, + {0x3f06, 0x3a}, + {0x371c, 0x00}, + {0x371d, 0xc4}, + {0x371e, 0x01}, + {0x371f, 0x0d}, + {0x3708, 0x61}, + {0x3709, 0x12}, + {0x3035, 0x10}, + {0x3036, 0x1e}, + {0x3037, 0x21}, + {0x303e, 0x19}, + {0x3038, 0x06}, + {0x3018, 0x04}, + {0x3000, 0x00}, + {0x3001, 0x00}, + {0x3002, 0x00}, + {0x3a0f, 0x40}, + {0x3a10, 0x38}, + {0x3a1b, 0x48}, + {0x3a1e, 0x30}, + {0x3a11, 0x90}, + {0x3a1f, 0x10}, +}; + +struct ov2720_i2c_conf_array ov2720_confs[] = { + {&ov2720_prev_settings[0], ARRAY_SIZE(ov2720_prev_settings)}, +}; + +struct ov2720_reg ov2720_regs = { + .rec_settings = &ov2720_recommend_settings[0], + .rec_size = ARRAY_SIZE(ov2720_recommend_settings), + .conf_array = &ov2720_confs[0], +}; diff --git a/drivers/media/video/msm/ov7692.c b/drivers/media/video/msm/ov7692.c new file mode 100644 index 00000000000..7372156fcd3 --- /dev/null +++ b/drivers/media/video/msm/ov7692.c @@ -0,0 +1,595 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ov7692.h" + +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ +#define Q8 0x00000100 + +/* Omnivision8810 product ID register address */ +#define REG_OV7692_MODEL_ID_MSB 0x0A +#define REG_OV7692_MODEL_ID_LSB 0x0B + +#define OV7692_MODEL_ID 0x7692 +/* Omnivision8810 product ID */ + +/* Time in milisecs for waiting for the sensor to reset */ +#define OV7692_RESET_DELAY_MSECS 66 +#define OV7692_DEFAULT_CLOCK_RATE 24000000 +/* Registers*/ + +/* Color bar pattern selection */ +#define OV7692_COLOR_BAR_PATTERN_SEL_REG 0x82 +/* Color bar enabling control */ +#define OV7692_COLOR_BAR_ENABLE_REG 0x601 +/* Time in milisecs for waiting for the sensor to reset*/ +#define OV7692_RESET_DELAY_MSECS 66 + +/*============================================================================ + DATA DECLARATIONS +============================================================================*/ +/* 96MHz PCLK @ 24MHz MCLK */ +struct reg_addr_val_pair_struct ov7692_init_settings_array[] = { + {0x12, 0x80}, + {0x0e, 0x08}, + {0x69, 0x52}, + {0x1e, 0xb3}, + {0x48, 0x42}, + {0xff, 0x01}, + {0xae, 0xa0}, + {0xa8, 0x26}, + {0xb4, 0xc0}, + {0xb5, 0x40}, + {0xff, 0x00}, + {0x0c, 0x00}, + {0x62, 0x10}, + {0x12, 0x00}, + {0x17, 0x65}, + {0x18, 0xa4}, + {0x19, 0x0a}, + {0x1a, 0xf6}, + {0x3e, 0x30}, + {0x64, 0x0a}, + {0xff, 0x01}, + {0xb4, 0xc0}, + {0xff, 0x00}, + {0x67, 0x20}, + {0x81, 0x3f}, + {0xcc, 0x02}, + {0xcd, 0x80}, + {0xce, 0x01}, + {0xcf, 0xe0}, + {0xc8, 0x02}, + {0xc9, 0x80}, + {0xca, 0x01}, + {0xcb, 0xe0}, + {0xd0, 0x48}, + {0x82, 0x03}, + {0x0e, 0x00}, + {0x70, 0x00}, + {0x71, 0x34}, + {0x74, 0x28}, + {0x75, 0x98}, + {0x76, 0x00}, + {0x77, 0x64}, + {0x78, 0x01}, + {0x79, 0xc2}, + {0x7a, 0x4e}, + {0x7b, 0x1f}, + {0x7c, 0x00}, + {0x11, 0x00}, + {0x20, 0x00}, + {0x21, 0x23}, + {0x50, 0x9a}, + {0x51, 0x80}, + {0x4c, 0x7d}, + {0x0e, 0x00}, + {0x80, 0x7f}, + {0x85, 0x10}, + {0x86, 0x00}, + {0x87, 0x00}, + {0x88, 0x00}, + {0x89, 0x2a}, + {0x8a, 0x26}, + {0x8b, 0x22}, + {0xbb, 0x7a}, + {0xbc, 0x69}, + {0xbd, 0x11}, + {0xbe, 0x13}, + {0xbf, 0x81}, + {0xc0, 0x96}, + {0xc1, 0x1e}, + {0xb7, 0x05}, + {0xb8, 0x09}, + {0xb9, 0x00}, + {0xba, 0x18}, + {0x5a, 0x1f}, + {0x5b, 0x9f}, + {0x5c, 0x6a}, + {0x5d, 0x42}, + {0x24, 0x78}, + {0x25, 0x68}, + {0x26, 0xb3}, + {0xa3, 0x0b}, + {0xa4, 0x15}, + {0xa5, 0x2a}, + {0xa6, 0x51}, + {0xa7, 0x63}, + {0xa8, 0x74}, + {0xa9, 0x83}, + {0xaa, 0x91}, + {0xab, 0x9e}, + {0xac, 0xaa}, + {0xad, 0xbe}, + {0xae, 0xce}, + {0xaf, 0xe5}, + {0xb0, 0xf3}, + {0xb1, 0xfb}, + {0xb2, 0x06}, + {0x8c, 0x5c}, + {0x8d, 0x11}, + {0x8e, 0x12}, + {0x8f, 0x19}, + {0x90, 0x50}, + {0x91, 0x20}, + {0x92, 0x96}, + {0x93, 0x80}, + {0x94, 0x13}, + {0x95, 0x1b}, + {0x96, 0xff}, + {0x97, 0x00}, + {0x98, 0x3d}, + {0x99, 0x36}, + {0x9a, 0x51}, + {0x9b, 0x43}, + {0x9c, 0xf0}, + {0x9d, 0xf0}, + {0x9e, 0xf0}, + {0x9f, 0xff}, + {0xa0, 0x68}, + {0xa1, 0x62}, + {0xa2, 0x0e}, +}; + +static bool OV7692_CSI_CONFIG; +/* 816x612, 24MHz MCLK 96MHz PCLK */ +uint32_t OV7692_FULL_SIZE_WIDTH = 640; +uint32_t OV7692_FULL_SIZE_HEIGHT = 480; + +uint32_t OV7692_QTR_SIZE_WIDTH = 640; +uint32_t OV7692_QTR_SIZE_HEIGHT = 480; + +uint32_t OV7692_HRZ_FULL_BLK_PIXELS = 16; +uint32_t OV7692_VER_FULL_BLK_LINES = 12; +uint32_t OV7692_HRZ_QTR_BLK_PIXELS = 16; +uint32_t OV7692_VER_QTR_BLK_LINES = 12; + +struct ov7692_work_t { + struct work_struct work; +}; +static struct ov7692_work_t *ov7692_sensorw; +static struct i2c_client *ov7692_client; +struct ov7692_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + uint32_t sensormode; + uint32_t fps_divider; /* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ + uint32_t fps; + int32_t curr_lens_pos; + uint32_t curr_step_pos; + uint32_t my_reg_gain; + uint32_t my_reg_line_count; + uint32_t total_lines_per_frame; + enum ov7692_resolution_t prev_res; + enum ov7692_resolution_t pict_res; + enum ov7692_resolution_t curr_res; + enum ov7692_test_mode_t set_test; + unsigned short imgaddr; +}; +static struct ov7692_ctrl_t *ov7692_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(ov7692_wait_queue); +DEFINE_MUTEX(ov7692_mut); + +/*=============================================================*/ + +static int ov7692_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 1, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 1, + .buf = rxdata, + }, + }; + if (i2c_transfer(ov7692_client->adapter, msgs, 2) < 0) { + CDBG("ov7692_i2c_rxdata failed!\n"); + return -EIO; + } + return 0; +} +static int32_t ov7692_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = txdata, + }, + }; + if (i2c_transfer(ov7692_client->adapter, msg, 1) < 0) { + CDBG("ov7692_i2c_txdata faild 0x%x\n", ov7692_client->addr); + return -EIO; + } + + return 0; +} + +static int32_t ov7692_i2c_read(uint8_t raddr, + uint8_t *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[1]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = raddr; + rc = ov7692_i2c_rxdata(ov7692_client->addr >> 1, buf, rlen); + if (rc < 0) { + CDBG("ov7692_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = buf[0]; + return rc; +} +static int32_t ov7692_i2c_write_b_sensor(uint8_t waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[2]; + memset(buf, 0, sizeof(buf)); + buf[0] = waddr; + buf[1] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = ov7692_i2c_txdata(ov7692_client->addr >> 1, buf, 2); + if (rc < 0) + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + return rc; +} + +static int32_t ov7692_sensor_setting(int update_type, int rt) +{ + int32_t i, array_length; + int32_t rc = 0; + struct msm_camera_csi_params ov7692_csi_params; + switch (update_type) { + case REG_INIT: + OV7692_CSI_CONFIG = 0; + ov7692_i2c_write_b_sensor(0x0e, 0x08); + return rc; + break; + case UPDATE_PERIODIC: + if (!OV7692_CSI_CONFIG) { + ov7692_csi_params.lane_cnt = 1; + ov7692_csi_params.data_format = CSI_8BIT; + ov7692_csi_params.lane_assign = 0xe4; + ov7692_csi_params.dpcm_scheme = 0; + ov7692_csi_params.settle_cnt = 0x14; + + rc = msm_camio_csi_config(&ov7692_csi_params); + msleep(10); + array_length = sizeof(ov7692_init_settings_array) / + sizeof(ov7692_init_settings_array[0]); + for (i = 0; i < array_length; i++) { + rc = ov7692_i2c_write_b_sensor( + ov7692_init_settings_array[i].reg_addr, + ov7692_init_settings_array[i].reg_val); + if (rc < 0) + return rc; + } + OV7692_CSI_CONFIG = 1; + msleep(20); + return rc; + } + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +static int32_t ov7692_video_config(int mode) +{ + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + rt = RES_PREVIEW; + + if (ov7692_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + ov7692_ctrl->curr_res = ov7692_ctrl->prev_res; + ov7692_ctrl->sensormode = mode; + return rc; +} + +static int32_t ov7692_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = ov7692_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + case SENSOR_RAW_SNAPSHOT_MODE: + break; + default: + rc = -EINVAL; + break; + } + return rc; +} +static int32_t ov7692_power_down(void) +{ + return 0; +} + +static int ov7692_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + uint8_t model_id_msb, model_id_lsb = 0; + uint16_t model_id; + int32_t rc = 0; + /*The reset pin is not physically connected to the sensor. + The standby pin will do the reset hence there is no need + to request the gpio reset*/ + + /* Read sensor Model ID: */ + rc = ov7692_i2c_read(REG_OV7692_MODEL_ID_MSB, &model_id_msb, 1); + if (rc < 0) + goto init_probe_fail; + rc = ov7692_i2c_read(REG_OV7692_MODEL_ID_LSB, &model_id_lsb, 1); + if (rc < 0) + goto init_probe_fail; + model_id = (model_id_msb << 8) | ((model_id_lsb & 0x00FF)) ; + CDBG("ov7692 model_id = 0x%x, 0x%x, 0x%x\n", + model_id, model_id_msb, model_id_lsb); + /* 4. Compare sensor ID to OV7692 ID: */ + if (model_id != OV7692_MODEL_ID) { + rc = -ENODEV; + goto init_probe_fail; + } + goto init_probe_done; +init_probe_fail: + pr_warning(" ov7692_probe_init_sensor fails\n"); +init_probe_done: + CDBG(" ov7692_probe_init_sensor finishes\n"); + return rc; +} + +int ov7692_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + CDBG("Calling ov7692_sensor_open_init\n"); + ov7692_ctrl = kzalloc(sizeof(struct ov7692_ctrl_t), GFP_KERNEL); + if (!ov7692_ctrl) { + CDBG("ov7692_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + ov7692_ctrl->fps_divider = 1 * 0x00000400; + ov7692_ctrl->pict_fps_divider = 1 * 0x00000400; + ov7692_ctrl->fps = 30 * Q8; + ov7692_ctrl->set_test = TEST_OFF; + ov7692_ctrl->prev_res = QTR_SIZE; + ov7692_ctrl->pict_res = FULL_SIZE; + ov7692_ctrl->curr_res = INVALID_SIZE; + + if (data) + ov7692_ctrl->sensordata = data; + + /* enable mclk first */ + + msm_camio_clk_rate_set(24000000); + msleep(20); + + rc = ov7692_probe_init_sensor(data); + if (rc < 0) { + CDBG("Calling ov7692_sensor_open_init fail\n"); + goto init_fail; + } + + rc = ov7692_sensor_setting(REG_INIT, RES_PREVIEW); + if (rc < 0) + goto init_fail; + else + goto init_done; + +init_fail: + CDBG(" ov7692_sensor_open_init fail\n"); + kfree(ov7692_ctrl); +init_done: + CDBG("ov7692_sensor_open_init done\n"); + return rc; +} + +static int ov7692_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&ov7692_wait_queue); + return 0; +} + +static const struct i2c_device_id ov7692_i2c_id[] = { + {"ov7692", 0}, + { } +}; + +static int ov7692_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("ov7692_i2c_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + ov7692_sensorw = kzalloc(sizeof(struct ov7692_work_t), GFP_KERNEL); + if (!ov7692_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, ov7692_sensorw); + ov7692_init_client(client); + ov7692_client = client; + + CDBG("ov7692_i2c_probe success! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("ov7692_i2c_probe failed! rc = %d\n", rc); + return rc; +} + +static int __exit ov7692_remove(struct i2c_client *client) +{ + struct ov7692_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + ov7692_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver ov7692_i2c_driver = { + .id_table = ov7692_i2c_id, + .probe = ov7692_i2c_probe, + .remove = __exit_p(ov7692_i2c_remove), + .driver = { + .name = "ov7692", + }, +}; + +int ov7692_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&ov7692_mut); + CDBG("ov7692_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_SET_MODE: + rc = ov7692_set_sensor_mode(cdata.mode, + cdata.rs); + break; + case CFG_PWR_DOWN: + rc = ov7692_power_down(); + break; + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&ov7692_mut); + + return rc; +} +static int ov7692_sensor_release(void) +{ + int rc = -EBADF; + mutex_lock(&ov7692_mut); + ov7692_power_down(); + kfree(ov7692_ctrl); + ov7692_ctrl = NULL; + CDBG("ov7692_release completed\n"); + mutex_unlock(&ov7692_mut); + + return rc; +} + +static int ov7692_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(&ov7692_i2c_driver); + if (rc < 0 || ov7692_client == NULL) { + rc = -ENOTSUPP; + goto probe_fail; + } + msm_camio_clk_rate_set(24000000); + rc = ov7692_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + s->s_init = ov7692_sensor_open_init; + s->s_release = ov7692_sensor_release; + s->s_config = ov7692_sensor_config; + s->s_camera_type = FRONT_CAMERA_2D; + s->s_mount_angle = 0; + return rc; + +probe_fail: + CDBG("ov7692_sensor_probe: SENSOR PROBE FAILS!\n"); + i2c_del_driver(&ov7692_i2c_driver); + return rc; +} + +static int __ov7692_probe(struct platform_device *pdev) +{ + + return msm_camera_drv_start(pdev, ov7692_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __ov7692_probe, + .driver = { + .name = "msm_camera_ov7692", + .owner = THIS_MODULE, + }, +}; + +static int __init ov7692_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(ov7692_init); + +MODULE_DESCRIPTION("OMNI VGA YUV sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/ov7692.h b/drivers/media/video/msm/ov7692.h new file mode 100644 index 00000000000..e43a17d2200 --- /dev/null +++ b/drivers/media/video/msm/ov7692.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef OV7692_H +#define OV7692_H +#include +#include + +struct reg_addr_val_pair_struct { + uint8_t reg_addr; + uint8_t reg_val; +}; + +enum ov7692_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum ov7692_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; + +enum ov7692_setting { + RES_PREVIEW, + RES_CAPTURE +}; +enum ov7692_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; +#endif + diff --git a/drivers/media/video/msm/ov9726.c b/drivers/media/video/msm/ov9726.c new file mode 100644 index 00000000000..fc045583e51 --- /dev/null +++ b/drivers/media/video/msm/ov9726.c @@ -0,0 +1,792 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ov9726.h" + +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ +#define OV9726_Q8 0x00000100 +#define OV9726_Q8Shift 8 +#define OV9726_Q10 0x00000400 +#define OV9726_Q10Shift 10 + +/* Omnivision8810 product ID register address */ +#define OV9726_PIDH_REG 0x0000 +#define OV9726_PIDL_REG 0x0001 +/* Omnivision8810 product ID */ +#define OV9726_PID 0x97 +/* Omnivision8810 version */ +#define OV9726_VER 0x26 +/* Time in milisecs for waiting for the sensor to reset */ +#define OV9726_RESET_DELAY_MSECS 66 +#define OV9726_DEFAULT_CLOCK_RATE 24000000 +/* Registers*/ +#define OV9726_GAIN 0x3000 +#define OV9726_AEC_MSB 0x3002 +#define OV9726_AEC_LSB 0x3003 + +/* Color bar pattern selection */ +#define OV9726_COLOR_BAR_PATTERN_SEL_REG 0x600 +/* Color bar enabling control */ +#define OV9726_COLOR_BAR_ENABLE_REG 0x601 +/* Time in milisecs for waiting for the sensor to reset*/ +#define OV9726_RESET_DELAY_MSECS 66 +/* I2C Address of the Sensor */ +/*============================================================================ + DATA DECLARATIONS +============================================================================*/ +#define OV9726_FULL_SIZE_DUMMY_PIXELS 0 +#define OV9726_FULL_SIZE_DUMMY_LINES 0 +#define OV9726_QTR_SIZE_DUMMY_PIXELS 0 +#define OV9726_QTR_SIZE_DUMMY_LINES 0 + +#define OV9726_FULL_SIZE_WIDTH 1296 +#define OV9726_FULL_SIZE_HEIGHT 808 + +#define OV9726_QTR_SIZE_WIDTH 1296 +#define OV9726_QTR_SIZE_HEIGHT 808 + +#define OV9726_HRZ_FULL_BLK_PIXELS 368 +#define OV9726_VER_FULL_BLK_LINES 32 +#define OV9726_HRZ_QTR_BLK_PIXELS 368 +#define OV9726_VER_QTR_BLK_LINES 32 + +#define OV9726_MSB_MASK 0xFF00 +#define OV9726_LSB_MASK 0x00FF + +struct ov9726_work_t { + struct work_struct work; +}; +static struct ov9726_work_t *ov9726_sensorw; +static struct i2c_client *ov9726_client; +struct ov9726_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + uint32_t sensormode; + uint32_t fps_divider; /* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ + uint16_t fps; + int16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + enum ov9726_resolution_t prev_res; + enum ov9726_resolution_t pict_res; + enum ov9726_resolution_t curr_res; + enum ov9726_test_mode_t set_test; + unsigned short imgaddr; +}; +static struct ov9726_ctrl_t *ov9726_ctrl; +static int8_t config_not_set = 1; +static DECLARE_WAIT_QUEUE_HEAD(ov9726_wait_queue); +DEFINE_MUTEX(ov9726_mut); + +/*=============================================================*/ +static int ov9726_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + + if (i2c_transfer(ov9726_client->adapter, msgs, 2) < 0) { + CDBG("ov9726_i2c_rxdata failed!\n"); + return -EIO; + } + + return 0; +} + +static int32_t ov9726_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr , + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + + if (i2c_transfer(ov9726_client->adapter, msg, 1) < 0) { + CDBG("ov9726_i2c_txdata faild 0x%x\n", ov9726_client->addr); + return -EIO; + } + + return 0; +} + +static int32_t ov9726_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + + if (!rdata) + return -EIO; + + buf[0] = (raddr & OV9726_MSB_MASK) >> 8; + buf[1] = (raddr & OV9726_LSB_MASK); + + rc = ov9726_i2c_rxdata(ov9726_client->addr, buf, rlen); + + if (rc < 0) { + CDBG("ov9726_i2c_read 0x%x failed!\n", raddr); + return rc; + } + + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + return rc; +} + +static int32_t ov9726_i2c_write_b(unsigned short saddr, + unsigned short waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + + buf[0] = (waddr & OV9726_MSB_MASK) >> 8; + buf[1] = (waddr & OV9726_LSB_MASK); + buf[2] = bdata; + + CDBG("i2c_write_b addr = 0x%x, val = 0x%xd\n", waddr, bdata); + rc = ov9726_i2c_txdata(saddr, buf, 3); + + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + + return rc; +} + +static void ov9726_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + uint32_t divider; /*Q10 */ + uint32_t d1; + uint32_t d2; + uint16_t snapshot_height, preview_height, preview_width, snapshot_width; + if (ov9726_ctrl->prev_res == QTR_SIZE) { + preview_width = OV9726_QTR_SIZE_WIDTH + + OV9726_HRZ_QTR_BLK_PIXELS ; + preview_height = OV9726_QTR_SIZE_HEIGHT + + OV9726_VER_QTR_BLK_LINES ; + } else { + /* full size resolution used for preview. */ + preview_width = OV9726_FULL_SIZE_WIDTH + + OV9726_HRZ_FULL_BLK_PIXELS ; + preview_height = OV9726_FULL_SIZE_HEIGHT + + OV9726_VER_FULL_BLK_LINES ; + } + if (ov9726_ctrl->pict_res == QTR_SIZE) { + snapshot_width = OV9726_QTR_SIZE_WIDTH + + OV9726_HRZ_QTR_BLK_PIXELS ; + snapshot_height = OV9726_QTR_SIZE_HEIGHT + + OV9726_VER_QTR_BLK_LINES ; + } else { + snapshot_width = OV9726_FULL_SIZE_WIDTH + + OV9726_HRZ_FULL_BLK_PIXELS; + snapshot_height = OV9726_FULL_SIZE_HEIGHT + + OV9726_VER_FULL_BLK_LINES; + } + + d1 = (uint32_t)(((uint32_t)preview_height << + OV9726_Q10Shift) / + snapshot_height); + + d2 = (uint32_t)(((uint32_t)preview_width << + OV9726_Q10Shift) / + snapshot_width); + + divider = (uint32_t) (d1 * d2) >> OV9726_Q10Shift; + *pfps = (uint16_t)((uint32_t)(fps * divider) >> OV9726_Q10Shift); +} + +static uint16_t ov9726_get_prev_lines_pf(void) +{ + if (ov9726_ctrl->prev_res == QTR_SIZE) + return OV9726_QTR_SIZE_HEIGHT + OV9726_VER_QTR_BLK_LINES; + else + return OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES; +} + +static uint16_t ov9726_get_prev_pixels_pl(void) +{ + if (ov9726_ctrl->prev_res == QTR_SIZE) + return OV9726_QTR_SIZE_WIDTH + OV9726_HRZ_QTR_BLK_PIXELS; + else + return OV9726_FULL_SIZE_WIDTH + OV9726_HRZ_FULL_BLK_PIXELS; +} + +static uint16_t ov9726_get_pict_lines_pf(void) +{ + if (ov9726_ctrl->pict_res == QTR_SIZE) + return OV9726_QTR_SIZE_HEIGHT + OV9726_VER_QTR_BLK_LINES; + else + return OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES; +} + +static uint16_t ov9726_get_pict_pixels_pl(void) +{ + if (ov9726_ctrl->pict_res == QTR_SIZE) + return OV9726_QTR_SIZE_WIDTH + OV9726_HRZ_QTR_BLK_PIXELS; + else + return OV9726_FULL_SIZE_WIDTH + OV9726_HRZ_FULL_BLK_PIXELS; +} + +static uint32_t ov9726_get_pict_max_exp_lc(void) +{ + if (ov9726_ctrl->pict_res == QTR_SIZE) + return (OV9726_QTR_SIZE_HEIGHT + OV9726_VER_QTR_BLK_LINES)*24; + else + return (OV9726_FULL_SIZE_HEIGHT + OV9726_VER_FULL_BLK_LINES)*24; +} + +static int32_t ov9726_set_fps(struct fps_cfg *fps) +{ + int32_t rc = 0; + CDBG("%s: fps->fps_div = %d\n", __func__, fps->fps_div); + /* TODO: Passing of fps_divider from user space has issues. */ + /* ov9726_ctrl->fps_divider = fps->fps_div; */ + ov9726_ctrl->fps_divider = 1 * 0x400; + CDBG("%s: ov9726_ctrl->fps_divider = %d\n", __func__, + ov9726_ctrl->fps_divider); + ov9726_ctrl->pict_fps_divider = fps->pict_fps_div; + ov9726_ctrl->fps = fps->f_mult; + return rc; +} + +static int32_t ov9726_write_exp_gain(uint16_t gain, uint32_t line) +{ + static uint16_t max_legal_gain = 0x00FF; + uint8_t gain_msb, gain_lsb; + uint8_t intg_time_msb, intg_time_lsb; + uint8_t ov9726_offset = 6; + uint8_t line_length_pck_msb, line_length_pck_lsb; + uint16_t line_length_pck, frame_length_lines; + uint32_t line_length_ratio = 1 << OV9726_Q8Shift; + int32_t rc = -1; + CDBG("%s: gain = %d line = %d", __func__, gain, line); + + if (ov9726_ctrl->sensormode != SENSOR_SNAPSHOT_MODE) { + if (ov9726_ctrl->curr_res == QTR_SIZE) { + frame_length_lines = OV9726_QTR_SIZE_HEIGHT + + OV9726_VER_QTR_BLK_LINES; + line_length_pck = OV9726_QTR_SIZE_WIDTH + + OV9726_HRZ_QTR_BLK_PIXELS; + } else { + frame_length_lines = OV9726_FULL_SIZE_HEIGHT + + OV9726_VER_FULL_BLK_LINES; + line_length_pck = OV9726_FULL_SIZE_WIDTH + + OV9726_HRZ_FULL_BLK_PIXELS; + } + if (line > (frame_length_lines - ov9726_offset)) + ov9726_ctrl->fps = (uint16_t) (((uint32_t)30 << + OV9726_Q8Shift) * + (frame_length_lines - ov9726_offset) / line); + else + ov9726_ctrl->fps = (uint16_t) ((uint32_t)30 << + OV9726_Q8Shift); + } else { + frame_length_lines = OV9726_FULL_SIZE_HEIGHT + + OV9726_VER_FULL_BLK_LINES; + line_length_pck = OV9726_FULL_SIZE_WIDTH + + OV9726_HRZ_FULL_BLK_PIXELS; + } + + if (ov9726_ctrl->sensormode != SENSOR_SNAPSHOT_MODE) { + line = (uint32_t) (line * ov9726_ctrl->fps_divider) >> + OV9726_Q10Shift; + } else { + line = (uint32_t) (line * ov9726_ctrl->pict_fps_divider) >> + OV9726_Q10Shift; + } + + /* calculate line_length_ratio */ + if (line > (frame_length_lines - ov9726_offset)) { + line_length_ratio = (line << OV9726_Q8Shift) / + (frame_length_lines - ov9726_offset); + line = frame_length_lines - ov9726_offset; + } else + line_length_ratio = (uint32_t)1 << OV9726_Q8Shift; + + if (gain > max_legal_gain) { + /* range: 0 to 224 */ + gain = max_legal_gain; + } + /* update gain registers */ + gain_msb = (uint8_t) ((gain & 0xFF00) >> 8); + gain_lsb = (uint8_t) (gain & 0x00FF); + /* linear AFR horizontal stretch */ + line_length_pck = (uint16_t) ((line_length_pck * + line_length_ratio) >> OV9726_Q8Shift); + line_length_pck_msb = (uint8_t) ((line_length_pck & 0xFF00) >> 8); + line_length_pck_lsb = (uint8_t) (line_length_pck & 0x00FF); + /* update line count registers */ + intg_time_msb = (uint8_t) ((line & 0xFF00) >> 8); + intg_time_lsb = (uint8_t) (line & 0x00FF); + + rc = ov9726_i2c_write_b(ov9726_client->addr, 0x104, 0x1); + if (rc < 0) + return rc; + + rc = ov9726_i2c_write_b(ov9726_client->addr, 0x204, gain_msb); + if (rc < 0) + return rc; + + rc = ov9726_i2c_write_b(ov9726_client->addr, 0x205, gain_lsb); + if (rc < 0) + return rc; + + rc = ov9726_i2c_write_b(ov9726_client->addr, 0x342, + line_length_pck_msb); + if (rc < 0) + return rc; + + rc = ov9726_i2c_write_b(ov9726_client->addr, 0x343, + line_length_pck_lsb); + if (rc < 0) + return rc; + + rc = ov9726_i2c_write_b(ov9726_client->addr, 0x0202, intg_time_msb); + if (rc < 0) + return rc; + + rc = ov9726_i2c_write_b(ov9726_client->addr, 0x0203, intg_time_lsb); + if (rc < 0) + return rc; + + rc = ov9726_i2c_write_b(ov9726_client->addr, 0x104, 0x0); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t ov9726_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + rc = ov9726_write_exp_gain(gain, line); + return rc; +} + +static int32_t initialize_ov9726_registers(void) +{ + int32_t i; + int32_t rc = 0; + ov9726_ctrl->sensormode = SENSOR_PREVIEW_MODE ; + /* Configure sensor for Preview mode and Snapshot mode */ + CDBG("Initialize_ov9726_registers\n"); + for (i = 0; i < ov9726_array_length; i++) { + rc = ov9726_i2c_write_b(ov9726_client->addr, + ov9726_init_settings_array[i].reg_addr, + ov9726_init_settings_array[i].reg_val); + if (rc < 0) + return rc; + } + return rc; +} + +static int32_t ov9726_video_config(int mode) +{ + int32_t rc = 0; + + ov9726_ctrl->sensormode = mode; + + if (config_not_set) { + struct msm_camera_csi_params ov9726_csi_params; + + /* sensor in standby */ + ov9726_i2c_write_b(ov9726_client->addr, 0x100, 0); + msleep(5); + /* Initialize Sensor registers */ + ov9726_csi_params.data_format = CSI_10BIT; + ov9726_csi_params.lane_cnt = 1; + ov9726_csi_params.lane_assign = 0xe4; + ov9726_csi_params.dpcm_scheme = 0; + ov9726_csi_params.settle_cnt = 7; + + rc = msm_camio_csi_config(&ov9726_csi_params); + rc = initialize_ov9726_registers(); + config_not_set = 0; + } + return rc; +} + +static int32_t ov9726_snapshot_config(int mode) +{ + int32_t rc = 0; + ov9726_ctrl->sensormode = mode; + return rc; +} + +static int32_t ov9726_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + ov9726_ctrl->sensormode = mode; + return rc; +} + +static int32_t ov9726_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = ov9726_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + rc = ov9726_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + rc = ov9726_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +static int ov9726_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + uint16_t chipidl, chipidh; + + if (data->sensor_reset_enable) { + rc = gpio_request(data->sensor_reset, "ov9726"); + if (!rc) { + gpio_direction_output(data->sensor_reset, 0); + gpio_set_value_cansleep(data->sensor_reset, 1); + msleep(20); + } else + goto init_probe_done; + } + /* 3. Read sensor Model ID: */ + rc = ov9726_i2c_read(OV9726_PIDH_REG, &chipidh, 1); + if (rc < 0) + goto init_probe_fail; + rc = ov9726_i2c_read(OV9726_PIDL_REG, &chipidl, 1); + if (rc < 0) + goto init_probe_fail; + CDBG("kov9726 model_id = 0x%x 0x%x\n", chipidh, chipidl); + /* 4. Compare sensor ID to OV9726 ID: */ + if (chipidh != OV9726_PID) { + rc = -ENODEV; + printk(KERN_INFO "Probeinit fail\n"); + goto init_probe_fail; + } + CDBG("chipidh == OV9726_PID\n"); + msleep(OV9726_RESET_DELAY_MSECS); + CDBG("after delay\n"); + goto init_probe_done; + +init_probe_fail: + if (data->sensor_reset_enable) { + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + } +init_probe_done: + printk(KERN_INFO " ov9726_probe_init_sensor finishes\n"); + return rc; +} + +int ov9726_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + + CDBG("Calling ov9726_sensor_open_init\n"); + ov9726_ctrl = kzalloc(sizeof(struct ov9726_ctrl_t), GFP_KERNEL); + if (!ov9726_ctrl) { + CDBG("ov9726_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + ov9726_ctrl->curr_lens_pos = -1; + ov9726_ctrl->fps_divider = 1 << OV9726_Q10Shift; + ov9726_ctrl->pict_fps_divider = 1 << OV9726_Q10Shift; + ov9726_ctrl->set_test = TEST_OFF; + ov9726_ctrl->prev_res = FULL_SIZE; + ov9726_ctrl->pict_res = FULL_SIZE; + ov9726_ctrl->curr_res = INVALID_SIZE; + config_not_set = 1; + if (data) + ov9726_ctrl->sensordata = data; + /* enable mclk first */ + msm_camio_clk_rate_set(OV9726_DEFAULT_CLOCK_RATE); + msleep(20); + rc = ov9726_probe_init_sensor(data); + if (rc < 0) + goto init_fail; + + ov9726_ctrl->fps = (uint16_t)(30 << OV9726_Q8Shift); + /* generate test pattern */ + if (rc < 0) + goto init_fail; + else + goto init_done; + /* reset the driver state */ +init_fail: + CDBG(" init_fail\n"); + kfree(ov9726_ctrl); +init_done: + CDBG("init_done\n"); + return rc; +} + +static int ov9726_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&ov9726_wait_queue); + return 0; +} + +static const struct i2c_device_id ov9726_i2c_id[] = { + { "ov9726", 0}, + { } +}; + +static int ov9726_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("ov9726_probe called!\n"); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + ov9726_sensorw = kzalloc(sizeof(struct ov9726_work_t), GFP_KERNEL); + if (!ov9726_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + i2c_set_clientdata(client, ov9726_sensorw); + ov9726_init_client(client); + ov9726_client = client; + msleep(50); + CDBG("ov9726_probe successed! rc = %d\n", rc); + return 0; +probe_failure: + CDBG("ov9726_probe failed! rc = %d\n", rc); + return rc; +} + +static int __exit ov9726_remove(struct i2c_client *client) +{ + struct ov9726_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + ov9726_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver ov9726_i2c_driver = { + .id_table = ov9726_i2c_id, + .probe = ov9726_i2c_probe, + .remove = __exit_p(ov9726_i2c_remove), + .driver = { + .name = "ov9726", + }, +}; + +int ov9726_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&ov9726_mut); + CDBG("ov9726_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + ov9726_get_pict_fps(cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = ov9726_get_prev_lines_pf(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = ov9726_get_prev_pixels_pl(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = ov9726_get_pict_lines_pf(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + ov9726_get_pict_pixels_pl(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = ov9726_get_pict_max_exp_lc(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = ov9726_set_fps(&(cdata.cfg.fps)); + break; + case CFG_SET_EXP_GAIN: + rc = ov9726_write_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_PICT_EXP_GAIN: + rc = ov9726_set_pict_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_MODE: + rc = ov9726_set_sensor_mode(cdata.mode, + cdata.rs); + break; + case CFG_PWR_DOWN: + case CFG_MOVE_FOCUS: + case CFG_SET_DEFAULT_FOCUS: + rc = 0; + break; + case CFG_SET_EFFECT: + default: + rc = -EFAULT; + break; + } + mutex_unlock(&ov9726_mut); + return rc; +} + +static int ov9726_probe_init_done(const struct msm_camera_sensor_info *data) +{ + if (data->sensor_reset_enable) { + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + } + return 0; +} + +static int ov9726_sensor_release(void) +{ + int rc = -EBADF; + mutex_lock(&ov9726_mut); + gpio_direction_output(ov9726_ctrl->sensordata->sensor_reset, + 0); + gpio_free(ov9726_ctrl->sensordata->sensor_reset); + kfree(ov9726_ctrl); + ov9726_ctrl = NULL; + CDBG("ov9726_release completed\n"); + mutex_unlock(&ov9726_mut); + return rc; +} + +static int ov9726_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + + rc = i2c_add_driver(&ov9726_i2c_driver); + if (rc < 0 || ov9726_client == NULL) { + rc = -ENOTSUPP; + goto probe_fail; + } + msm_camio_clk_rate_set(24000000); + msleep(20); + rc = ov9726_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + + s->s_init = ov9726_sensor_open_init; + s->s_release = ov9726_sensor_release; + s->s_config = ov9726_sensor_config; + s->s_camera_type = FRONT_CAMERA_2D; + s->s_mount_angle = info->sensor_platform_info->mount_angle; + ov9726_probe_init_done(info); + + return rc; + +probe_fail: + CDBG("SENSOR PROBE FAILS!\n"); + return rc; +} + +static int __ov9726_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, ov9726_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __ov9726_probe, + .driver = { + .name = "msm_camera_ov9726", + .owner = THIS_MODULE, + }, +}; + +static int __init ov9726_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(ov9726_init); +void ov9726_exit(void) +{ + i2c_del_driver(&ov9726_i2c_driver); +} + +MODULE_DESCRIPTION("OMNI VGA Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/media/video/msm/ov9726.h b/drivers/media/video/msm/ov9726.h new file mode 100644 index 00000000000..56d3da6514f --- /dev/null +++ b/drivers/media/video/msm/ov9726.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef OV9726_H +#define OV9726_H +#include +#include + +/* 16bit address - 8 bit context register structure */ +struct reg_struct_type { + uint16_t reg_addr; + unsigned char reg_val; +}; + +enum ov9726_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum ov9726_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; +extern struct reg_struct_type ov9726_init_settings_array[]; +extern int32_t ov9726_array_length; +#endif + diff --git a/drivers/media/video/msm/ov9726_reg.c b/drivers/media/video/msm/ov9726_reg.c new file mode 100644 index 00000000000..54afbe8a00e --- /dev/null +++ b/drivers/media/video/msm/ov9726_reg.c @@ -0,0 +1,101 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "ov9726.h" +struct reg_struct_type ov9726_init_settings_array[] = { + {0x0103, 0x01}, /* SOFTWARE_RESET */ + {0x3026, 0x00}, /* OUTPUT_SELECT01 */ + {0x3027, 0x00}, /* OUTPUT_SELECT02 */ + {0x3002, 0xe8}, /* IO_CTRL00 */ + {0x3004, 0x03}, /* IO_CTRL01 */ + {0x3005, 0xff}, /* IO_CTRL02 */ + {0x3703, 0x42}, + {0x3704, 0x10}, + {0x3705, 0x45}, + {0x3603, 0xaa}, + {0x3632, 0x2f}, + {0x3620, 0x66}, + {0x3621, 0xc0}, + {0x0340, 0x03}, /* FRAME_LENGTH_LINES_HI */ + {0x0341, 0xC1}, /* FRAME_LENGTH_LINES_LO */ + {0x0342, 0x06}, /* LINE_LENGTH_PCK_HI */ + {0x0343, 0x80}, /* LINE_LENGTH_PCK_LO */ + {0x0202, 0x03}, /* COARSE_INTEGRATION_TIME_HI */ + {0x0203, 0x43}, /* COARSE_INTEGRATION_TIME_LO */ + {0x3833, 0x04}, + {0x3835, 0x02}, + {0x4702, 0x04}, + {0x4704, 0x00}, /* DVP_CTRL01 */ + {0x4706, 0x08}, + {0x5052, 0x01}, + {0x3819, 0x6e}, + {0x3817, 0x94}, + {0x3a18, 0x00}, /* AEC_GAIN_CEILING_HI */ + {0x3a19, 0x7f}, /* AEC_GAIN_CEILING_LO */ + {0x404e, 0x7e}, + {0x3631, 0x52}, + {0x3633, 0x50}, + {0x3630, 0xd2}, + {0x3604, 0x08}, + {0x3601, 0x40}, + {0x3602, 0x14}, + {0x3610, 0xa0}, + {0x3612, 0x20}, + {0x034c, 0x05}, /* X_OUTPUT_SIZE_HI */ + {0x034d, 0x10}, /* X_OUTPUT_SIZE_LO */ + {0x034e, 0x03}, /* Y_OUTPUT_SIZE_HI */ + {0x034f, 0x28}, /* Y_OUTPUT_SIZE_LO */ + {0x0340, 0x03}, /* FRAME_LENGTH_LINES_HI */ + {0x0341, 0xC1}, /* FRAME_LENGTH_LINES_LO */ + {0x0342, 0x06}, /* LINE_LENGTH_PCK_HI */ + {0x0343, 0x80}, /* LINE_LENGTH_PCK_LO */ + {0x0202, 0x03}, /* COARSE_INTEGRATION_TIME_HI */ + {0x0203, 0x43}, /* COARSE_INTEGRATION_TIME_LO */ + {0x0303, 0x01}, /* VT_SYS_CLK_DIV_LO */ + {0x3002, 0x00}, /* IO_CTRL00 */ + {0x3004, 0x00}, /* IO_CTRL01 */ + {0x3005, 0x00}, /* IO_CTRL02 */ + {0x4801, 0x0f}, /* MIPI_CTRL01 */ + {0x4803, 0x05}, /* MIPI_CTRL03 */ + {0x4601, 0x16}, /* VFIFO_READ_CONTROL */ + {0x3014, 0x05}, /* SC_CMMN_MIPI / SC_CTRL00 */ + {0x3104, 0x80}, + {0x0305, 0x04}, /* PRE_PLL_CLK_DIV_LO */ + {0x0307, 0x64}, /* PLL_MULTIPLIER_LO */ + {0x300c, 0x02}, + {0x300d, 0x20}, + {0x300e, 0x01}, + {0x3010, 0x01}, + {0x460e, 0x81}, /* VFIFO_CONTROL00 */ + {0x0101, 0x01}, /* IMAGE_ORIENTATION */ + {0x3707, 0x14}, + {0x3622, 0x9f}, + {0x5047, 0x3D}, /* ISP_CTRL47 */ + {0x4002, 0x45}, /* BLC_CTRL02 */ + {0x5000, 0x06}, /* ISP_CTRL0 */ + {0x5001, 0x00}, /* ISP_CTRL1 */ + {0x3406, 0x00}, /* AWB_MANUAL_CTRL */ + {0x3503, 0x13}, /* AEC_ENABLE */ + {0x4005, 0x18}, /* BLC_CTRL05 */ + {0x4837, 0x21}, + {0x0100, 0x01}, /* MODE_SELECT */ + {0x3a0f, 0x64}, /* AEC_CTRL0F */ + {0x3a10, 0x54}, /* AEC_CTRL10 */ + {0x3a11, 0xc2}, /* AEC_CTRL11 */ + {0x3a1b, 0x64}, /* AEC_CTRL1B */ + {0x3a1e, 0x54}, /* AEC_CTRL1E */ + {0x3a1a, 0x05}, /* AEC_DIFF_MAX */ +}; +int32_t ov9726_array_length = sizeof(ov9726_init_settings_array) / + sizeof(ov9726_init_settings_array[0]); + diff --git a/drivers/media/video/msm/qs_s5k4e1.c b/drivers/media/video/msm/qs_s5k4e1.c new file mode 100644 index 00000000000..d6bb51d7081 --- /dev/null +++ b/drivers/media/video/msm/qs_s5k4e1.c @@ -0,0 +1,1663 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qs_s5k4e1.h" +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD_OFF 0x00 +#define GROUPED_PARAMETER_HOLD 0x01 +/* Integration Time */ +#define REG_COARSE_INTEGRATION_TIME 0x0202 +/* Gain */ +#define REG_GLOBAL_GAIN 0x0204 +#define REG_GR_GAIN 0x020E +#define REG_R_GAIN 0x0210 +#define REG_B_GAIN 0x0212 +#define REG_GB_GAIN 0x0214 +/* PLL registers */ +#define REG_FRAME_LENGTH_LINES 0x0340 +#define REG_LINE_LENGTH_PCK 0x0342 +/* Test Pattern */ +#define REG_TEST_PATTERN_MODE 0x0601 +#define REG_VCM_NEW_CODE 0x30F2 +#define AF_ADDR 0x18 +#define BRIDGE_ADDR 0x80 +/*============================================================================ + TYPE DECLARATIONS +============================================================================*/ + +/* 16bit address - 8 bit context register structure */ +#define Q8 0x00000100 +#define Q10 0x00000400 +#define QS_S5K4E1_MASTER_CLK_RATE 24000000 +#define QS_S5K4E1_OFFSET 8 + +/* AF Total steps parameters */ +#define QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR 32 + +uint16_t qs_s5k4e1_step_position_table[QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR+1]; +uint16_t qs_s5k4e1_nl_region_boundary1; +uint16_t qs_s5k4e1_nl_region_code_per_step1 = 190; +uint16_t qs_s5k4e1_l_region_code_per_step = 8; +uint16_t qs_s5k4e1_damping_threshold = 10; +uint16_t qs_s5k4e1_sw_damping_time_wait = 8; +uint16_t qs_s5k4e1_af_mode = 4; +int16_t qs_s5k4e1_af_initial_code = 190; +int16_t qs_s5k4e1_af_right_adjust; + +struct qs_s5k4e1_work_t { + struct work_struct work; +}; + +static struct qs_s5k4e1_work_t *qs_s5k4e1_sensorw; +static struct i2c_client *qs_s5k4e1_client; +static char lens_eeprom_data[864]; +static bool cali_data_status; +struct qs_s5k4e1_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + + uint32_t sensormode; + uint32_t fps_divider;/* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider;/* init to 1 * 0x00000400 */ + uint16_t fps; + + uint16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + + enum qs_s5k4e1_resolution_t prev_res; + enum qs_s5k4e1_resolution_t pict_res; + enum qs_s5k4e1_resolution_t curr_res; + enum qs_s5k4e1_test_mode_t set_test; + enum qs_s5k4e1_cam_mode_t cam_mode; +}; + +static uint16_t prev_line_length_pck; +static uint16_t prev_frame_length_lines; +static uint16_t snap_line_length_pck; +static uint16_t snap_frame_length_lines; + +static bool CSI_CONFIG, LENS_SHADE_CONFIG, default_lens_shade; +static struct qs_s5k4e1_ctrl_t *qs_s5k4e1_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(qs_s5k4e1_wait_queue); +DEFINE_MUTEX(qs_s5k4e1_mut); + +static int cam_debug_init(void); +static struct dentry *debugfs_base; +/*=============================================================*/ + +static int qs_s5k4e1_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + if (i2c_transfer(qs_s5k4e1_client->adapter, msgs, 2) < 0) { + CDBG("qs_s5k4e1_i2c_rxdata faild 0x%x\n", saddr); + return -EIO; + } + return 0; +} + +static int32_t qs_s5k4e1_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(qs_s5k4e1_client->adapter, msg, 1) < 0) { + CDBG("qs_s5k4e1_i2c_txdata faild 0x%x\n", saddr); + return -EIO; + } + + return 0; +} + +static int32_t qs_s5k4e1_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = qs_s5k4e1_i2c_rxdata(qs_s5k4e1_client->addr>>1, buf, rlen); + if (rc < 0) { + CDBG("qs_s5k4e1_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + CDBG("qs_s5k4e1_i2c_read 0x%x val = 0x%x!\n", raddr, *rdata); + return rc; +} + +static int32_t qs_s5k4e1_i2c_write_w_sensor(unsigned short waddr, + uint16_t wdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[4]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, wdata); + rc = qs_s5k4e1_i2c_txdata(qs_s5k4e1_client->addr>>1, buf, 4); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + } + return rc; +} + +static int32_t qs_s5k4e1_i2c_write_b_sensor(unsigned short waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = qs_s5k4e1_i2c_txdata(qs_s5k4e1_client->addr>>1, buf, 3); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + return rc; +} + +static int32_t qs_s5k4e1_i2c_write_b_table(struct qs_s5k4e1_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < num; i++) { + rc = qs_s5k4e1_i2c_write_b_sensor(reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +static int32_t qs_s5k4e1_i2c_write_seq_sensor(unsigned short waddr, + unsigned char *seq_data, int len) +{ + int32_t rc = -EFAULT; + unsigned char buf[len+2]; + int i = 0; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + for (i = 0; i < len; i++) + buf[i+2] = seq_data[i]; + rc = qs_s5k4e1_i2c_txdata(qs_s5k4e1_client->addr>>1, buf, len+2); + return rc; +} + +static int32_t af_i2c_write_b_sensor(unsigned short baddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[2]; + memset(buf, 0, sizeof(buf)); + buf[0] = baddr; + buf[1] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", baddr, bdata); + rc = qs_s5k4e1_i2c_txdata(AF_ADDR>>1, buf, 2); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + baddr, bdata); + } + return rc; +} + +static int32_t bridge_i2c_write_w(unsigned short waddr, uint16_t wdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[4]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + CDBG("bridge_i2c_write_w addr = 0x%x, val = 0x%x\n", waddr, wdata); + rc = qs_s5k4e1_i2c_txdata(BRIDGE_ADDR>>1, buf, 4); + if (rc < 0) { + CDBG("bridge_i2c_write_w failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + } + return rc; +} + +static int32_t bridge_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = qs_s5k4e1_i2c_rxdata(BRIDGE_ADDR>>1, buf, rlen); + if (rc < 0) { + CDBG("bridge_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + CDBG("bridge_i2c_read 0x%x val = 0x%x!\n", raddr, *rdata); + return rc; +} + +static int32_t qs_s5k4e1_eeprom_i2c_read(unsigned short raddr, + unsigned char *rdata, int rlen) +{ + int32_t rc = 0; + unsigned short i2caddr = 0xA0 >> 1; + unsigned char buf[rlen]; + int i = 0; + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = qs_s5k4e1_i2c_rxdata(i2caddr, buf, rlen); + if (rc < 0) { + CDBG("qs_s5k4e1_eeprom_i2c_read 0x%x failed!\n", raddr); + return rc; + } + for (i = 0; i < rlen; i++) { + rdata[i] = buf[i]; + CDBG("qs_s5k4e1_eeprom_i2c_read 0x%x index: %d val = 0x%x!\n", + raddr, i, buf[i]); + } + return rc; +} + +static int32_t qs_s5k4e1_eeprom_i2c_read_b(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + rc = qs_s5k4e1_eeprom_i2c_read(raddr, &buf[0], rlen); + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + CDBG("qs_s5k4e1_eeprom_i2c_read 0x%x val = 0x%x!\n", raddr, *rdata); + return rc; +} + +static int32_t qs_s5k4e1_get_calibration_data( + struct sensor_3d_cali_data_t *cdata) +{ + int32_t rc = 0; + cali_data_status = 1; + rc = qs_s5k4e1_eeprom_i2c_read(0x0, + &(cdata->left_p_matrix[0][0][0]), 96); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read(0x60, + &(cdata->right_p_matrix[0][0][0]), 96); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read(0xC0, &(cdata->square_len[0]), 8); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read(0xC8, &(cdata->focal_len[0]), 8); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read(0xD0, &(cdata->pixel_pitch[0]), 8); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x100, &(cdata->left_r), 1); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x101, &(cdata->right_r), 1); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x102, &(cdata->left_b), 1); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x103, &(cdata->right_b), 1); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x104, &(cdata->left_gb), 1); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x105, &(cdata->right_gb), 1); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x110, &(cdata->left_af_far), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x112, &(cdata->right_af_far), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x114, &(cdata->left_af_mid), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x116, &(cdata->right_af_mid), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x118, &(cdata->left_af_short), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x11A, &(cdata->right_af_short), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x11C, &(cdata->left_af_5um), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x11E, &(cdata->right_af_5um), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x120, &(cdata->left_af_50up), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x122, &(cdata->right_af_50up), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x124, &(cdata->left_af_50down), 2); + if (rc < 0) + goto fail; + rc = qs_s5k4e1_eeprom_i2c_read_b(0x126, &(cdata->right_af_50down), 2); + if (rc < 0) + goto fail; + + return 0; + +fail: + cali_data_status = 0; + return -EIO; + +} +static int32_t qs_s5k4e1_write_left_lsc(char *left_lsc, int rt) +{ + struct qs_s5k4e1_i2c_reg_conf *ptr = (struct qs_s5k4e1_i2c_reg_conf *) + (qs_s5k4e1_regs.reg_lens + rt); + bridge_i2c_write_w(0x06, 0x02); + if (!LENS_SHADE_CONFIG) { + qs_s5k4e1_i2c_write_b_sensor(0x3096, 0x40); + qs_s5k4e1_i2c_write_b_table(ptr, qs_s5k4e1_regs.reg_lens_size); + if (default_lens_shade) + qs_s5k4e1_i2c_write_b_table(qs_s5k4e1_regs. + reg_default_lens, qs_s5k4e1_regs.reg_default_lens_size); + else { + qs_s5k4e1_i2c_write_seq_sensor(0x3200, + &left_lsc[0], 216); + qs_s5k4e1_i2c_write_seq_sensor(0x32D8, + &left_lsc[216], 216); + } + qs_s5k4e1_i2c_write_b_sensor(0x3096, 0x60); + qs_s5k4e1_i2c_write_b_sensor(0x3096, 0x40); + } else + qs_s5k4e1_i2c_write_b_table(ptr, qs_s5k4e1_regs.reg_lens_size); + return 0; +} + +static int32_t qs_s5k4e1_write_right_lsc(char *right_lsc, int rt) +{ + struct qs_s5k4e1_i2c_reg_conf *ptr = (struct qs_s5k4e1_i2c_reg_conf *) + (qs_s5k4e1_regs.reg_lens + rt); + bridge_i2c_write_w(0x06, 0x01); + if (!LENS_SHADE_CONFIG) { + qs_s5k4e1_i2c_write_b_sensor(0x3096, 0x40); + qs_s5k4e1_i2c_write_b_table(ptr, qs_s5k4e1_regs.reg_lens_size); + if (default_lens_shade) + qs_s5k4e1_i2c_write_b_table(qs_s5k4e1_regs. + reg_default_lens, qs_s5k4e1_regs.reg_default_lens_size); + else { + qs_s5k4e1_i2c_write_seq_sensor(0x3200, + &right_lsc[0], 216); + qs_s5k4e1_i2c_write_seq_sensor(0x32D8, + &right_lsc[216], 216); + } + qs_s5k4e1_i2c_write_b_sensor(0x3096, 0x60); + qs_s5k4e1_i2c_write_b_sensor(0x3096, 0x40); + } else + qs_s5k4e1_i2c_write_b_table(ptr, qs_s5k4e1_regs.reg_lens_size); + return 0; +} + +static int32_t qs_s5k4e1_write_lsc(char *lsc, int rt) +{ + if (qs_s5k4e1_ctrl->cam_mode == MODE_3D) { + qs_s5k4e1_write_left_lsc(&lsc[0], rt); + qs_s5k4e1_write_right_lsc(&lsc[432], rt); + bridge_i2c_write_w(0x06, 0x03); + } else if (qs_s5k4e1_ctrl->cam_mode == MODE_2D_LEFT) + qs_s5k4e1_write_left_lsc(&lsc[0], rt); + else if (qs_s5k4e1_ctrl->cam_mode == MODE_2D_RIGHT) + qs_s5k4e1_write_right_lsc(&lsc[432], rt); + return 0; +} + +static int32_t qs_s5k4e1_read_left_lsc(char *left_lsc) +{ + qs_s5k4e1_eeprom_i2c_read(0x200, &left_lsc[0], 216); + qs_s5k4e1_eeprom_i2c_read(0x2D8, &left_lsc[216], 216); + return 0; +} + +static int32_t qs_s5k4e1_read_right_lsc(char *right_lsc) +{ + qs_s5k4e1_eeprom_i2c_read(0x3B0, &right_lsc[0], 216); + qs_s5k4e1_eeprom_i2c_read(0x488, &right_lsc[216], 216); + return 0; +} + +static int32_t qs_s5k4e1_read_lsc(char *lsc) +{ + qs_s5k4e1_read_left_lsc(&lsc[0]); + qs_s5k4e1_read_right_lsc(&lsc[432]); + return 0; +} + +static int32_t qs_s5k4e1_bridge_reset(void){ + unsigned short RegData = 0, GPIOInState = 0; + int32_t rc = 0; + rc = bridge_i2c_write_w(0x50, 0x00); + if (rc < 0) + goto bridge_fail; + rc = bridge_i2c_write_w(0x53, 0x00); + if (rc < 0) + goto bridge_fail; + msleep(30); + rc = bridge_i2c_write_w(0x53, 0x01); + if (rc < 0) + goto bridge_fail; + msleep(30); + rc = bridge_i2c_write_w(0x0E, 0xFFFF); + if (rc < 0) + goto err; + rc = bridge_i2c_read(0x54, &RegData, 2); + if (rc < 0) + goto err; + rc = bridge_i2c_write_w(0x54, (RegData | 0x1)); + if (rc < 0) + goto err; + msleep(30); + rc = bridge_i2c_read(0x54, &RegData, 2); + if (rc < 0) + goto err; + rc = bridge_i2c_write_w(0x54, (RegData | 0x4)); + if (rc < 0) + goto err; + rc = bridge_i2c_read(0x55, &GPIOInState, 2); + if (rc < 0) + goto err; + rc = bridge_i2c_write_w(0x55, (GPIOInState | 0x1)); + if (rc < 0) + goto err; + msleep(30); + rc = bridge_i2c_read(0x55, &GPIOInState, 2); + if (rc < 0) + goto err; + rc = bridge_i2c_write_w(0x55, (GPIOInState | 0x4)); + if (rc < 0) + goto err; + msleep(30); + rc = bridge_i2c_read(0x55, &GPIOInState, 2); + if (rc < 0) + goto err; + GPIOInState = ((GPIOInState >> 4) & 0x1); + + rc = bridge_i2c_read(0x08, &GPIOInState, 2); + if (rc < 0) + goto err; + rc = bridge_i2c_write_w(0x08, GPIOInState | 0x4000); + if (rc < 0) + goto err; + return rc; + +err: + bridge_i2c_write_w(0x53, 0x00); + msleep(30); + +bridge_fail: + return rc; + +} + +static void qs_s5k4e1_bridge_config(int mode, int rt) +{ + unsigned short RegData = 0; + if (mode == MODE_3D) { + bridge_i2c_read(0x54, &RegData, 2); + bridge_i2c_write_w(0x54, (RegData | 0x2)); + bridge_i2c_write_w(0x54, (RegData | 0xa)); + bridge_i2c_read(0x55, &RegData, 2); + bridge_i2c_write_w(0x55, (RegData | 0x2)); + bridge_i2c_write_w(0x55, (RegData | 0xa)); + bridge_i2c_write_w(0x14, 0x0C); + msleep(20); + bridge_i2c_write_w(0x16, 0x00); + bridge_i2c_write_w(0x51, 0x3); + bridge_i2c_write_w(0x52, 0x1); + bridge_i2c_write_w(0x06, 0x03); + bridge_i2c_write_w(0x04, 0x2018); + bridge_i2c_write_w(0x50, 0x00); + } else if (mode == MODE_2D_RIGHT) { + bridge_i2c_read(0x54, &RegData, 2); + RegData |= 0x2; + bridge_i2c_write_w(0x54, RegData); + bridge_i2c_write_w(0x54, (RegData & ~(0x8))); + bridge_i2c_read(0x55, &RegData, 2); + RegData |= 0x2; + bridge_i2c_write_w(0x55, RegData); + bridge_i2c_write_w(0x55, (RegData & ~(0x8))); + bridge_i2c_write_w(0x14, 0x04); + msleep(20); + bridge_i2c_write_w(0x51, 0x3); + bridge_i2c_write_w(0x06, 0x01); + bridge_i2c_write_w(0x04, 0x2018); + bridge_i2c_write_w(0x50, 0x01); + } else if (mode == MODE_2D_LEFT) { + bridge_i2c_read(0x54, &RegData, 2); + RegData |= 0x8; + bridge_i2c_write_w(0x54, RegData); + bridge_i2c_write_w(0x54, (RegData & ~(0x2))); + bridge_i2c_read(0x55, &RegData, 2); + RegData |= 0x8; + bridge_i2c_write_w(0x55, RegData); + bridge_i2c_write_w(0x55, (RegData & ~(0x2))); + bridge_i2c_write_w(0x14, 0x08); + msleep(20); + bridge_i2c_write_w(0x51, 0x3); + bridge_i2c_write_w(0x06, 0x02); + bridge_i2c_write_w(0x04, 0x2018); + bridge_i2c_write_w(0x50, 0x02); + } +} + +static void qs_s5k4e1_group_hold_on(void) +{ + qs_s5k4e1_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); +} + +static void qs_s5k4e1_group_hold_off(void) +{ + qs_s5k4e1_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF); +} + +static void qs_s5k4e1_start_stream(void) +{ + qs_s5k4e1_i2c_write_b_sensor(0x0100, 0x01); +} + +static void qs_s5k4e1_stop_stream(void) +{ + qs_s5k4e1_i2c_write_b_sensor(0x0100, 0x00); +} + +static void qs_s5k4e1_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider, d1, d2; + + d1 = prev_frame_length_lines * 0x00000400 / snap_frame_length_lines; + d2 = prev_line_length_pck * 0x00000400 / snap_line_length_pck; + divider = d1 * d2 / 0x400; + + /*Verify PCLK settings and frame sizes.*/ + *pfps = (uint16_t) (fps * divider / 0x400); + /* 2 is the ratio of no.of snapshot channels + to number of preview channels */ +} + +static uint16_t qs_s5k4e1_get_prev_lines_pf(void) +{ + + return prev_frame_length_lines; + +} + +static uint16_t qs_s5k4e1_get_prev_pixels_pl(void) +{ + return prev_line_length_pck; + +} + +static uint16_t qs_s5k4e1_get_pict_lines_pf(void) +{ + return snap_frame_length_lines; +} + +static uint16_t qs_s5k4e1_get_pict_pixels_pl(void) +{ + return snap_line_length_pck; +} + + +static uint32_t qs_s5k4e1_get_pict_max_exp_lc(void) +{ + return snap_frame_length_lines * 24; +} + +static int32_t qs_s5k4e1_set_fps(struct fps_cfg *fps) +{ + uint16_t total_line_length_pclk; + int32_t rc = 0; + qs_s5k4e1_ctrl->fps_divider = fps->fps_div; + qs_s5k4e1_ctrl->pict_fps_divider = fps->pict_fps_div; + if (qs_s5k4e1_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + total_line_length_pclk = (uint16_t) + ((prev_line_length_pck) * qs_s5k4e1_ctrl->fps_divider/0x400); + } else { + total_line_length_pclk = (uint16_t) + ((snap_line_length_pck) * + qs_s5k4e1_ctrl->pict_fps_divider/0x400); + } + qs_s5k4e1_group_hold_on(); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_LINE_LENGTH_PCK, + total_line_length_pclk); + qs_s5k4e1_group_hold_off(); + return rc; +} + +static int32_t qs_s5k4e1_write_exp_gain(struct sensor_3d_exp_cfg exp_cfg) +{ + uint16_t max_legal_gain = 0x0200; + uint16_t min_ll_pck = 0x0AB2; + uint32_t ll_pck, fl_lines; + uint16_t gain = exp_cfg.gain; + uint32_t line = exp_cfg.line; + uint32_t ll_ratio; + int32_t rc = 0; + if (gain > max_legal_gain) { + CDBG("Max legal gain Line:%d\n", __LINE__); + gain = max_legal_gain; + } + CDBG("qs_s5k4e1_write_exp_gain : gain = %d line = %d\n", gain, line); + + if (qs_s5k4e1_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + qs_s5k4e1_ctrl->my_reg_gain = gain; + qs_s5k4e1_ctrl->my_reg_line_count = (uint16_t) line; + ll_ratio = (uint32_t)(qs_s5k4e1_ctrl->fps_divider); + fl_lines = prev_frame_length_lines; + ll_pck = prev_line_length_pck; + } else { + ll_ratio = (uint32_t)(qs_s5k4e1_ctrl->pict_fps_divider); + fl_lines = snap_frame_length_lines; + ll_pck = snap_line_length_pck; + } + if (((fl_lines * ll_ratio / 0x400) - QS_S5K4E1_OFFSET) < line) { + ll_ratio = ll_ratio * line / (fl_lines - QS_S5K4E1_OFFSET); + line = fl_lines - QS_S5K4E1_OFFSET; + } + ll_pck = ll_pck * ll_ratio / 0x400; + if (ll_pck < min_ll_pck) + ll_pck = min_ll_pck; + qs_s5k4e1_group_hold_on(); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_GLOBAL_GAIN, gain); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_LINE_LENGTH_PCK, ll_pck); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_COARSE_INTEGRATION_TIME, line); + if ((qs_s5k4e1_ctrl->cam_mode == MODE_3D) && (cali_data_status == 1)) { + bridge_i2c_write_w(0x06, 0x01); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_GLOBAL_GAIN, + exp_cfg.gain_adjust); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_GR_GAIN, exp_cfg.gr_gain); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_R_GAIN, + exp_cfg.r_gain); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_B_GAIN, + exp_cfg.b_gain); + rc = qs_s5k4e1_i2c_write_w_sensor(REG_GB_GAIN, + exp_cfg.gb_gain); + bridge_i2c_write_w(0x06, 0x03); + } + qs_s5k4e1_group_hold_off(); + return rc; +} + +static int32_t qs_s5k4e1_set_pict_exp_gain(struct sensor_3d_exp_cfg exp_cfg) +{ + int32_t rc = 0; + rc = qs_s5k4e1_write_exp_gain(exp_cfg); + return rc; +} + +static int32_t qs_s5k4e1_write_focus_value(uint16_t code_value) +{ + uint8_t code_val_msb, code_val_lsb; + if ((qs_s5k4e1_ctrl->cam_mode == MODE_2D_LEFT) || + (qs_s5k4e1_ctrl->cam_mode == MODE_3D)) { + /* Left */ + bridge_i2c_write_w(0x06, 0x02); + CDBG("%s: Left Lens Position: %d\n", __func__, + code_value); + code_val_msb = code_value >> 4; + code_val_lsb = (code_value & 0x000F) << 4; + code_val_lsb |= qs_s5k4e1_af_mode; + if (af_i2c_write_b_sensor(code_val_msb, code_val_lsb) < 0) { + CDBG("move_focus failed at line %d ...\n", __LINE__); + return -EBUSY; + } + } + + if ((qs_s5k4e1_ctrl->cam_mode == MODE_2D_RIGHT) || + (qs_s5k4e1_ctrl->cam_mode == MODE_3D)) { + /* Right */ + bridge_i2c_write_w(0x06, 0x01); + code_value += qs_s5k4e1_af_right_adjust; + CDBG("%s: Right Lens Position: %d\n", __func__, + code_value); + code_val_msb = code_value >> 4; + code_val_lsb = (code_value & 0x000F) << 4; + code_val_lsb |= qs_s5k4e1_af_mode; + if (af_i2c_write_b_sensor(code_val_msb, code_val_lsb) < 0) { + CDBG("move_focus failed at line %d ...\n", __LINE__); + return -EBUSY; + } + } + + if (qs_s5k4e1_ctrl->cam_mode == MODE_3D) { + /* 3D Mode */ + bridge_i2c_write_w(0x06, 0x03); + } + usleep(qs_s5k4e1_sw_damping_time_wait*50); + return 0; +} + +static int32_t qs_s5k4e1_move_focus(int direction, + int32_t num_steps) +{ + int16_t step_direction, actual_step, dest_lens_position, + dest_step_position; + CDBG("Inside %s\n", __func__); + if (direction == MOVE_NEAR) + step_direction = 1; + else + step_direction = -1; + + actual_step = (int16_t) (step_direction * (int16_t) num_steps); + dest_step_position = (int16_t) (qs_s5k4e1_ctrl->curr_step_pos + + actual_step); + + if (dest_step_position > QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR) + dest_step_position = QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR; + else if (dest_step_position < 0) + dest_step_position = 0; + + if (dest_step_position == qs_s5k4e1_ctrl->curr_step_pos) { + CDBG("%s cur and dest pos are same\n", __func__); + CDBG("%s cur_step_pos:%d\n", __func__, + qs_s5k4e1_ctrl->curr_step_pos); + return 0; + } + + dest_lens_position = qs_s5k4e1_step_position_table[dest_step_position]; + CDBG("%s: Step Position: %d\n", __func__, dest_step_position); + + if (step_direction < 0) { + if (num_steps >= 20) { + /* sweeping towards all the way in infinity direction */ + qs_s5k4e1_af_mode = 2; + qs_s5k4e1_sw_damping_time_wait = 8; + } else if (num_steps <= 4) { + /* reverse search during macro mode */ + qs_s5k4e1_af_mode = 4; + qs_s5k4e1_sw_damping_time_wait = 16; + } else { + qs_s5k4e1_af_mode = 3; + qs_s5k4e1_sw_damping_time_wait = 12; + } + } else { + /* coarse search towards macro direction */ + qs_s5k4e1_af_mode = 4; + qs_s5k4e1_sw_damping_time_wait = 16; + } + + if (qs_s5k4e1_ctrl->curr_lens_pos != dest_lens_position) { + if (qs_s5k4e1_write_focus_value(dest_lens_position) < 0) { + CDBG("move_focus failed at line %d ...\n", __LINE__); + return -EBUSY; + } + } + + qs_s5k4e1_ctrl->curr_step_pos = dest_step_position; + qs_s5k4e1_ctrl->curr_lens_pos = dest_lens_position; + return 0; +} + +static int32_t qs_s5k4e1_set_default_focus(uint8_t af_step) +{ + int32_t rc = 0; + if (qs_s5k4e1_ctrl->curr_step_pos) { + rc = qs_s5k4e1_move_focus(MOVE_FAR, + qs_s5k4e1_ctrl->curr_step_pos); + if (rc < 0) + return rc; + } else { + rc = qs_s5k4e1_write_focus_value( + qs_s5k4e1_step_position_table[0]); + if (rc < 0) + return rc; + qs_s5k4e1_ctrl->curr_lens_pos = + qs_s5k4e1_step_position_table[0]; + } + CDBG("%s\n", __func__); + return 0; +} + +static void qs_s5k4e1_init_focus(void) +{ + uint8_t i; + int32_t rc = 0; + int16_t af_far_data = 0; + qs_s5k4e1_af_initial_code = 190; + /* Read the calibration data from left and right sensors if available */ + rc = qs_s5k4e1_eeprom_i2c_read_b(0x110, &af_far_data, 2); + if (rc == 0) { + CDBG("%s: Left Far data - %d\n", __func__, af_far_data); + qs_s5k4e1_af_initial_code = af_far_data; + } + + rc = qs_s5k4e1_eeprom_i2c_read_b(0x112, &af_far_data, 2); + if (rc == 0) { + CDBG("%s: Right Far data - %d\n", __func__, af_far_data); + qs_s5k4e1_af_right_adjust = af_far_data - + qs_s5k4e1_af_initial_code; + } + + qs_s5k4e1_step_position_table[0] = qs_s5k4e1_af_initial_code; + for (i = 1; i <= QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR; i++) { + if (i <= qs_s5k4e1_nl_region_boundary1) { + qs_s5k4e1_step_position_table[i] = + qs_s5k4e1_step_position_table[i-1] + + qs_s5k4e1_nl_region_code_per_step1; + } else { + qs_s5k4e1_step_position_table[i] = + qs_s5k4e1_step_position_table[i-1] + + qs_s5k4e1_l_region_code_per_step; + } + + if (qs_s5k4e1_step_position_table[i] > 1023) + qs_s5k4e1_step_position_table[i] = 1023; + } + qs_s5k4e1_ctrl->curr_step_pos = 0; +} + +static int32_t qs_s5k4e1_test(enum qs_s5k4e1_test_mode_t mo) +{ + int32_t rc = 0; + if (mo == TEST_OFF) + return rc; + else { + /* REG_0x30D8[4] is TESBYPEN: 0: Normal Operation, + 1: Bypass Signal Processing + REG_0x30D8[5] is EBDMASK: 0: + Output Embedded data, 1: No output embedded data */ + if (qs_s5k4e1_i2c_write_b_sensor(REG_TEST_PATTERN_MODE, + (uint8_t) mo) < 0) { + return rc; + } + } + return rc; +} + +static int32_t qs_s5k4e1_sensor_setting(int update_type, int rt) +{ + + int32_t rc = 0; + struct msm_camera_csi_params qs_s5k4e1_csi_params; + + qs_s5k4e1_stop_stream(); + msleep(80); + bridge_i2c_write_w(0x53, 0x00); + msleep(80); + if (update_type == REG_INIT) { + CSI_CONFIG = 0; + LENS_SHADE_CONFIG = 0; + default_lens_shade = 1; + bridge_i2c_write_w(0x53, 0x01); + msleep(30); + qs_s5k4e1_bridge_config(qs_s5k4e1_ctrl->cam_mode, rt); + msleep(30); + qs_s5k4e1_i2c_write_b_table(qs_s5k4e1_regs.rec_settings, + qs_s5k4e1_regs.rec_size); + msleep(30); + } else if (update_type == UPDATE_PERIODIC) { + qs_s5k4e1_write_lsc(lens_eeprom_data, rt); + msleep(100); + if (!CSI_CONFIG) { + if (qs_s5k4e1_ctrl->cam_mode == MODE_3D) { + qs_s5k4e1_csi_params.lane_cnt = 4; + qs_s5k4e1_csi_params.data_format = CSI_8BIT; + } else { + qs_s5k4e1_csi_params.lane_cnt = 2; + qs_s5k4e1_csi_params.data_format = CSI_10BIT; + } + qs_s5k4e1_csi_params.lane_assign = 0xe4; + qs_s5k4e1_csi_params.dpcm_scheme = 0; + qs_s5k4e1_csi_params.settle_cnt = 24; + rc = msm_camio_csi_config(&qs_s5k4e1_csi_params); + msleep(10); + cam_debug_init(); + CSI_CONFIG = 1; + } + bridge_i2c_write_w(0x53, 0x01); + msleep(50); + qs_s5k4e1_i2c_write_b_table(qs_s5k4e1_regs.conf_array[rt].conf, + qs_s5k4e1_regs.conf_array[rt].size); + msleep(50); + qs_s5k4e1_start_stream(); + msleep(80); + } + return rc; +} + +static int32_t qs_s5k4e1_video_config(int mode) +{ + + int32_t rc = 0; + /* change sensor resolution if needed */ + if (qs_s5k4e1_sensor_setting(UPDATE_PERIODIC, + qs_s5k4e1_ctrl->prev_res) < 0) + return rc; + if (qs_s5k4e1_ctrl->set_test) { + if (qs_s5k4e1_test(qs_s5k4e1_ctrl->set_test) < 0) + return rc; + } + + qs_s5k4e1_ctrl->curr_res = qs_s5k4e1_ctrl->prev_res; + qs_s5k4e1_ctrl->sensormode = mode; + return rc; +} + +static int32_t qs_s5k4e1_snapshot_config(int mode) +{ + int32_t rc = 0; + /*change sensor resolution if needed */ + if (qs_s5k4e1_ctrl->curr_res != qs_s5k4e1_ctrl->pict_res) { + if (qs_s5k4e1_sensor_setting(UPDATE_PERIODIC, + qs_s5k4e1_ctrl->pict_res) < 0) + return rc; + } + + qs_s5k4e1_ctrl->curr_res = qs_s5k4e1_ctrl->pict_res; + qs_s5k4e1_ctrl->sensormode = mode; + return rc; +} /*end of qs_s5k4e1_snapshot_config*/ + +static int32_t qs_s5k4e1_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + /* change sensor resolution if needed */ + if (qs_s5k4e1_ctrl->curr_res != qs_s5k4e1_ctrl->pict_res) { + if (qs_s5k4e1_sensor_setting(UPDATE_PERIODIC, + qs_s5k4e1_ctrl->pict_res) < 0) + return rc; + } + + qs_s5k4e1_ctrl->curr_res = qs_s5k4e1_ctrl->pict_res; + qs_s5k4e1_ctrl->sensormode = mode; + return rc; +} /*end of qs_s5k4e1_raw_snapshot_config*/ + +static int32_t qs_s5k4e1_mode_init(int mode, struct sensor_init_cfg init_info) +{ + int32_t rc = 0; + if (mode != qs_s5k4e1_ctrl->cam_mode) { + qs_s5k4e1_ctrl->prev_res = init_info.prev_res; + qs_s5k4e1_ctrl->pict_res = init_info.pict_res; + qs_s5k4e1_ctrl->cam_mode = mode; + + prev_frame_length_lines = + ((qs_s5k4e1_regs.conf_array[qs_s5k4e1_ctrl->prev_res]\ + .conf[QS_S5K4E1_FRAME_LENGTH_LINES_H].wdata << 8) + | qs_s5k4e1_regs.conf_array[qs_s5k4e1_ctrl->prev_res]\ + .conf[QS_S5K4E1_FRAME_LENGTH_LINES_L].wdata); + prev_line_length_pck = + (qs_s5k4e1_regs.conf_array[qs_s5k4e1_ctrl->prev_res]\ + .conf[QS_S5K4E1_LINE_LENGTH_PCK_H].wdata << 8) + | qs_s5k4e1_regs.conf_array[qs_s5k4e1_ctrl->prev_res]\ + .conf[QS_S5K4E1_LINE_LENGTH_PCK_L].wdata; + snap_frame_length_lines = + (qs_s5k4e1_regs.conf_array[qs_s5k4e1_ctrl->pict_res]\ + .conf[QS_S5K4E1_FRAME_LENGTH_LINES_H].wdata << 8) + | qs_s5k4e1_regs.conf_array[qs_s5k4e1_ctrl->pict_res]\ + .conf[QS_S5K4E1_FRAME_LENGTH_LINES_L].wdata; + snap_line_length_pck = + (qs_s5k4e1_regs.conf_array[qs_s5k4e1_ctrl->pict_res]\ + .conf[QS_S5K4E1_LINE_LENGTH_PCK_H].wdata << 8) + | qs_s5k4e1_regs.conf_array[qs_s5k4e1_ctrl->pict_res]\ + .conf[QS_S5K4E1_LINE_LENGTH_PCK_L].wdata; + + rc = qs_s5k4e1_sensor_setting(REG_INIT, + qs_s5k4e1_ctrl->prev_res); + } + return rc; +} +static int32_t qs_s5k4e1_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + switch (mode) { + case SENSOR_PREVIEW_MODE: + qs_s5k4e1_ctrl->prev_res = res; + rc = qs_s5k4e1_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + qs_s5k4e1_ctrl->pict_res = res; + rc = qs_s5k4e1_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + qs_s5k4e1_ctrl->pict_res = res; + rc = qs_s5k4e1_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +static int32_t qs_s5k4e1_power_down(void) +{ + qs_s5k4e1_stop_stream(); + msleep(30); + qs_s5k4e1_af_mode = 2; + qs_s5k4e1_af_right_adjust = 0; + qs_s5k4e1_write_focus_value(0); + msleep(100); + /* Set AF actutator to PowerDown */ + af_i2c_write_b_sensor(0x80, 00); + return 0; +} + +static int qs_s5k4e1_probe_init_done(const struct msm_camera_sensor_info *data) +{ + CDBG("probe done\n"); + gpio_free(data->sensor_reset); + return 0; +} + +static int + qs_s5k4e1_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + uint16_t chipid = 0; + CDBG("%s: %d\n", __func__, __LINE__); + rc = gpio_request(data->sensor_reset, "qs_s5k4e1"); + CDBG(" qs_s5k4e1_probe_init_sensor\n"); + if (!rc) { + CDBG("sensor_reset = %d\n", rc); + gpio_direction_output(data->sensor_reset, 0); + msleep(50); + gpio_set_value_cansleep(data->sensor_reset, 1); + msleep(13); + } else { + goto init_probe_done; + } + msleep(70); + rc = qs_s5k4e1_bridge_reset(); + if (rc < 0) + goto init_probe_fail; + qs_s5k4e1_bridge_config(MODE_3D, RES_PREVIEW); + msleep(30); + + CDBG(" qs_s5k4e1_probe_init_sensor is called\n"); + rc = qs_s5k4e1_i2c_read(0x0000, &chipid, 2); + CDBG("ID: %d\n", chipid); + /* 4. Compare sensor ID to QS_S5K4E1 ID: */ + if (chipid != 0x4e10) { + rc = -ENODEV; + CDBG("qs_s5k4e1_probe_init_sensor fail chip id mismatch\n"); + goto init_probe_fail; + } + goto init_probe_done; +init_probe_fail: + CDBG(" qs_s5k4e1_probe_init_sensor fails\n"); + gpio_set_value_cansleep(data->sensor_reset, 0); + qs_s5k4e1_probe_init_done(data); +init_probe_done: + CDBG(" qs_s5k4e1_probe_init_sensor finishes\n"); + return rc; +} +/* camsensor_qs_s5k4e1_reset */ + +int qs_s5k4e1_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + CDBG("%s: %d\n", __func__, __LINE__); + CDBG("Calling qs_s5k4e1_sensor_open_init\n"); + + qs_s5k4e1_ctrl = kzalloc(sizeof(struct qs_s5k4e1_ctrl_t), GFP_KERNEL); + if (!qs_s5k4e1_ctrl) { + CDBG("qs_s5k4e1_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + qs_s5k4e1_ctrl->fps_divider = 1 * 0x00000400; + qs_s5k4e1_ctrl->pict_fps_divider = 1 * 0x00000400; + qs_s5k4e1_ctrl->set_test = TEST_OFF; + qs_s5k4e1_ctrl->cam_mode = MODE_INVALID; + + if (data) + qs_s5k4e1_ctrl->sensordata = data; + if (rc < 0) { + CDBG("Calling qs_s5k4e1_sensor_open_init fail1\n"); + return rc; + } + CDBG("%s: %d\n", __func__, __LINE__); + /* enable mclk first */ + msm_camio_clk_rate_set(QS_S5K4E1_MASTER_CLK_RATE); + rc = qs_s5k4e1_probe_init_sensor(data); + if (rc < 0) + goto init_fail; +/*Default mode is 3D*/ + memcpy(lens_eeprom_data, data->eeprom_data, 864); + qs_s5k4e1_ctrl->fps = 30*Q8; + qs_s5k4e1_init_focus(); + if (rc < 0) { + gpio_set_value_cansleep(data->sensor_reset, 0); + goto init_fail; + } else + goto init_done; +init_fail: + CDBG("init_fail\n"); + qs_s5k4e1_probe_init_done(data); +init_done: + CDBG("init_done\n"); + return rc; +} /*endof qs_s5k4e1_sensor_open_init*/ + +static int qs_s5k4e1_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&qs_s5k4e1_wait_queue); + return 0; +} + +static const struct i2c_device_id qs_s5k4e1_i2c_id[] = { + {"qs_s5k4e1", 0}, + { } +}; + +static int qs_s5k4e1_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("qs_s5k4e1_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + qs_s5k4e1_sensorw = kzalloc(sizeof(struct qs_s5k4e1_work_t), + GFP_KERNEL); + if (!qs_s5k4e1_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, qs_s5k4e1_sensorw); + qs_s5k4e1_init_client(client); + qs_s5k4e1_client = client; + + msleep(50); + + CDBG("qs_s5k4e1_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("qs_s5k4e1_probe failed! rc = %d\n", rc); + return rc; +} + +static int qs_s5k4e1_send_wb_info(struct wb_info_cfg *wb) +{ + return 0; + +} /*end of qs_s5k4e1_snapshot_config*/ + +static int __exit qs_s5k4e1_remove(struct i2c_client *client) +{ + struct qs_s5k4e1_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + qs_s5k4e1_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver qs_s5k4e1_i2c_driver = { + .id_table = qs_s5k4e1_i2c_id, + .probe = qs_s5k4e1_i2c_probe, + .remove = __exit_p(qs_s5k4e1_i2c_remove), + .driver = { + .name = "qs_s5k4e1", + }, +}; + +int qs_s5k4e1_3D_sensor_config(void __user *argp) +{ + struct sensor_large_data cdata; + long rc; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_large_data))) + return -EFAULT; + mutex_lock(&qs_s5k4e1_mut); + rc = qs_s5k4e1_get_calibration_data + (&cdata.data.sensor_3d_cali_data); + if (rc < 0) + goto fail; + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_large_data))) + rc = -EFAULT; +fail: + mutex_unlock(&qs_s5k4e1_mut); + return rc; +} + +int qs_s5k4e1_2D_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&qs_s5k4e1_mut); + CDBG("qs_s5k4e1_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + qs_s5k4e1_get_pict_fps( + cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + qs_s5k4e1_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + qs_s5k4e1_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + qs_s5k4e1_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + qs_s5k4e1_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + qs_s5k4e1_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = qs_s5k4e1_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = + qs_s5k4e1_write_exp_gain( + cdata.cfg.sensor_3d_exp); + break; + + case CFG_SET_PICT_EXP_GAIN: + rc = + qs_s5k4e1_set_pict_exp_gain( + cdata.cfg.sensor_3d_exp); + break; + + case CFG_SET_MODE: + rc = qs_s5k4e1_set_sensor_mode(cdata.mode, + cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = qs_s5k4e1_power_down(); + break; + + case CFG_MOVE_FOCUS: + rc = + qs_s5k4e1_move_focus( + cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = + qs_s5k4e1_set_default_focus( + cdata.cfg.focus.steps); + break; + + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR; + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_EFFECT: + rc = qs_s5k4e1_set_default_focus( + cdata.cfg.effect); + break; + + + case CFG_SEND_WB_INFO: + rc = qs_s5k4e1_send_wb_info( + &(cdata.cfg.wb_info)); + break; + + case CFG_SENSOR_INIT: + rc = qs_s5k4e1_mode_init(cdata.mode, + cdata.cfg.init_info); + break; + + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&qs_s5k4e1_mut); + + return rc; +} + +int qs_s5k4e1_sensor_config(void __user *argp) +{ + int cfgtype; + long rc; + if (copy_from_user(&cfgtype, + (void *)argp, + sizeof(int))) + return -EFAULT; + if (cfgtype != CFG_GET_3D_CALI_DATA) + rc = qs_s5k4e1_2D_sensor_config(argp); + else + rc = qs_s5k4e1_3D_sensor_config(argp); + return rc; +} + +static int qs_s5k4e1_sensor_release(void) +{ + int rc = -EBADF; + mutex_lock(&qs_s5k4e1_mut); + qs_s5k4e1_power_down(); + bridge_i2c_write_w(0x53, 0x00); + msleep(20); + gpio_set_value_cansleep(qs_s5k4e1_ctrl->sensordata->sensor_reset, 0); + msleep(5); + gpio_free(qs_s5k4e1_ctrl->sensordata->sensor_reset); + kfree(qs_s5k4e1_ctrl); + qs_s5k4e1_ctrl = NULL; + CDBG("qs_s5k4e1_release completed\n"); + mutex_unlock(&qs_s5k4e1_mut); + + return rc; +} + +static int qs_s5k4e1_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(&qs_s5k4e1_i2c_driver); + if (rc < 0 || qs_s5k4e1_client == NULL) { + rc = -ENOTSUPP; + CDBG("I2C add driver failed"); + goto probe_fail; + } + msm_camio_clk_rate_set(QS_S5K4E1_MASTER_CLK_RATE); + rc = qs_s5k4e1_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + qs_s5k4e1_read_lsc(info->eeprom_data); /*Default mode is 3D*/ + s->s_init = qs_s5k4e1_sensor_open_init; + s->s_release = qs_s5k4e1_sensor_release; + s->s_config = qs_s5k4e1_sensor_config; + s->s_mount_angle = 0; + s->s_camera_type = BACK_CAMERA_3D; + s->s_video_packing = SIDE_BY_SIDE_HALF; + s->s_snap_packing = SIDE_BY_SIDE_FULL; + bridge_i2c_write_w(0x53, 0x00); + msleep(20); + gpio_set_value_cansleep(info->sensor_reset, 0); + qs_s5k4e1_probe_init_done(info); + return rc; + +probe_fail: + CDBG("qs_s5k4e1_sensor_probe: SENSOR PROBE FAILS!\n"); + return rc; +} + +static bool streaming = 1; + +static int qs_s5k4e1_focus_test(void *data, u64 *val) +{ + int i = 0; + qs_s5k4e1_set_default_focus(0); + + for (i = 0; i < QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR; i++) { + qs_s5k4e1_move_focus(MOVE_NEAR, 1); + msleep(2000); + } + msleep(5000); + for ( ; i > 0; i--) { + qs_s5k4e1_move_focus(MOVE_FAR, 1); + msleep(2000); + } + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(cam_focus, qs_s5k4e1_focus_test, + NULL, "%lld\n"); + +static int qs_s5k4e1_step_test(void *data, u64 *val) +{ + int rc = 0; + struct sensor_large_data cdata; + rc = qs_s5k4e1_get_calibration_data + (&cdata.data.sensor_3d_cali_data); + if (rc < 0) + CDBG("%s: Calibration data read fail.\n", __func__); + + return 0; +} + +static int qs_s5k4e1_set_step(void *data, u64 val) +{ + qs_s5k4e1_l_region_code_per_step = val & 0xFF; + qs_s5k4e1_af_mode = (val >> 8) & 0xFF; + qs_s5k4e1_nl_region_code_per_step1 = (val >> 16) & 0xFFFF; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(cam_step, qs_s5k4e1_step_test, + qs_s5k4e1_set_step, "%lld\n"); + +static int cam_debug_stream_set(void *data, u64 val) +{ + int rc = 0; + + if (val) { + qs_s5k4e1_start_stream(); + streaming = 1; + } else { + qs_s5k4e1_stop_stream(); + streaming = 0; + } + + return rc; +} + +static int cam_debug_stream_get(void *data, u64 *val) +{ + *val = streaming; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(cam_stream, cam_debug_stream_get, + cam_debug_stream_set, "%llu\n"); + +static uint16_t qs_s5k4e1_step_val = QS_S5K4E1_TOTAL_STEPS_NEAR_TO_FAR; +static uint8_t qs_s5k4e1_step_dir = MOVE_NEAR; +static int qs_s5k4e1_af_step_config(void *data, u64 val) +{ + qs_s5k4e1_step_val = val & 0xFFFF; + qs_s5k4e1_step_dir = (val >> 16) & 0x1; + CDBG("%s\n", __func__); + return 0; +} + +static int qs_s5k4e1_af_step(void *data, u64 *val) +{ + int i = 0; + int dir = MOVE_NEAR; + CDBG("%s\n", __func__); + qs_s5k4e1_set_default_focus(0); + msleep(5000); + if (qs_s5k4e1_step_dir == 1) + dir = MOVE_FAR; + + for (i = 0; i < qs_s5k4e1_step_val; i += 4) { + qs_s5k4e1_move_focus(dir, 4); + msleep(1000); + } + qs_s5k4e1_set_default_focus(0); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(af_step, qs_s5k4e1_af_step, + qs_s5k4e1_af_step_config, "%llu\n"); + +static int cam_debug_init(void) +{ + struct dentry *cam_dir; + debugfs_base = debugfs_create_dir("sensor", NULL); + if (!debugfs_base) + return -ENOMEM; + + cam_dir = debugfs_create_dir("qs_s5k4e1", debugfs_base); + if (!cam_dir) + return -ENOMEM; + + if (!debugfs_create_file("focus", S_IRUGO | S_IWUSR, cam_dir, + NULL, &cam_focus)) + return -ENOMEM; + if (!debugfs_create_file("step", S_IRUGO | S_IWUSR, cam_dir, + NULL, &cam_step)) + return -ENOMEM; + if (!debugfs_create_file("stream", S_IRUGO | S_IWUSR, cam_dir, + NULL, &cam_stream)) + return -ENOMEM; + if (!debugfs_create_file("af_step", S_IRUGO | S_IWUSR, cam_dir, + NULL, &af_step)) + return -ENOMEM; + return 0; +} + +static int __qs_s5k4e1_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, qs_s5k4e1_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __qs_s5k4e1_probe, + .driver = { + .name = "msm_camera_qs_s5k4e1", + .owner = THIS_MODULE, + }, +}; + +static int __init qs_s5k4e1_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(qs_s5k4e1_init); +void qs_s5k4e1_exit(void) +{ + i2c_del_driver(&qs_s5k4e1_i2c_driver); +} +MODULE_DESCRIPTION("Samsung 5MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/qs_s5k4e1.h b/drivers/media/video/msm/qs_s5k4e1.h new file mode 100644 index 00000000000..f9c4c3f492b --- /dev/null +++ b/drivers/media/video/msm/qs_s5k4e1.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef QS_S5K4E1_H +#define QS_S5K4E1_H +#include +#include +extern struct qs_s5k4e1_reg qs_s5k4e1_regs; + +#define LENS_SHADE_TABLE 16 + +struct qs_s5k4e1_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +struct qs_s5k4e1_i2c_conf_array { + struct qs_s5k4e1_i2c_reg_conf *conf; + unsigned short size; +}; + +enum qs_s5k4e1_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum qs_s5k4e1_resolution_t { + QTR_2D_SIZE, + FULL_2D_SIZE, + QTR_3D_SIZE, + FULL_3D_SIZE, + INVALID_SIZE +}; +enum qs_s5k4e1_setting { + RES_PREVIEW, + RES_CAPTURE, + RES_3D_PREVIEW, + RES_3D_CAPTURE +}; +enum qs_s5k4e1_cam_mode_t { + MODE_2D_RIGHT, + MODE_2D_LEFT, + MODE_3D, + MODE_INVALID +}; +enum qs_s5k4e1_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +enum qs_s5k4e1_reg_mode { + QS_S5K4E1_FRAME_LENGTH_LINES_H = 1, + QS_S5K4E1_FRAME_LENGTH_LINES_L, + QS_S5K4E1_LINE_LENGTH_PCK_H, + QS_S5K4E1_LINE_LENGTH_PCK_L, +}; + +struct qs_s5k4e1_reg { + const struct qs_s5k4e1_i2c_reg_conf *rec_settings; + const unsigned short rec_size; + const struct qs_s5k4e1_i2c_reg_conf *reg_prev; + const unsigned short reg_prev_size; + const struct qs_s5k4e1_i2c_reg_conf *reg_snap; + const unsigned short reg_snap_size; + const struct qs_s5k4e1_i2c_reg_conf (*reg_lens)[LENS_SHADE_TABLE]; + const unsigned short reg_lens_size; + const struct qs_s5k4e1_i2c_reg_conf *reg_default_lens; + const unsigned short reg_default_lens_size; + const struct qs_s5k4e1_i2c_conf_array *conf_array; +}; +#endif /* QS_S5K4E1_H */ diff --git a/drivers/media/video/msm/qs_s5k4e1_reg.c b/drivers/media/video/msm/qs_s5k4e1_reg.c new file mode 100644 index 00000000000..22876ded9be --- /dev/null +++ b/drivers/media/video/msm/qs_s5k4e1_reg.c @@ -0,0 +1,799 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#include "qs_s5k4e1.h" + +struct qs_s5k4e1_i2c_reg_conf qs_s5k4e1_prev_settings_3d[] = { + {0x0100, 0x00}, + /*Frame Length*/ + {0x0340, 0x04}, + {0x0341, 0x90}, + /*Line Length*/ + {0x0342, 0x0A}, + {0x0343, 0xB2}, + {0x3030, 0x06}, + {0x3017, 0xA4}, + {0x301B, 0x88}, + {0x30BC, 0x90}, + {0x301C, 0x04}, + {0x0202, 0x04}, + {0x0203, 0x12}, + {0x0204, 0x00}, + {0x0205, 0x80}, + {0x0306, 0x00}, + {0x0307, 0x60}, + {0x30F1, 0x70}, +/*MIPI Size Setting*/ + {0x30A9, 0x02}, + {0x300E, 0xE8}, + {0x0387, 0x01}, + {0x0344, 0x01}, + {0x0345, 0x18}, + {0x0348, 0x09}, + {0x0349, 0x17}, + {0x0346, 0x01}, + {0x0347, 0x94}, + {0x034A, 0x06}, + {0x034B, 0x13}, + {0x0380, 0x00}, + {0x0381, 0x01}, + {0x0382, 0x00}, + {0x0383, 0x01}, + {0x0384, 0x00}, + {0x0385, 0x01}, + {0x0386, 0x00}, + {0x0387, 0x01}, + {0x034C, 0x04}, + {0x034D, 0x00}, + {0x034E, 0x04}, + {0x034F, 0x80}, + {0x30BF, 0xAA}, + {0x30C0, 0x40}, + {0x30C8, 0x04}, + {0x30C9, 0x00}, +}; + +struct qs_s5k4e1_i2c_reg_conf qs_s5k4e1_prev_settings_2d[] = { + {0x0100, 0x00}, + {0x0340, 0x03}, + {0x0341, 0xe0}, + {0x0342, 0x0A}, + {0x0343, 0xB2}, + {0x3030, 0x06}, + {0x301B, 0x83}, + {0x30BC, 0x98}, + {0x301C, 0x04}, + {0x0202, 0x01}, + {0x0203, 0xFD}, + {0x0204, 0x00}, + {0x0205, 0x80}, + {0x0306, 0x00}, + {0x0307, 0x64}, + {0x30F1, 0xa0}, + {0x30A9, 0x02}, + {0x300E, 0xEB}, + {0x0387, 0x03}, + {0x0344, 0x00}, + {0x0345, 0x00}, + {0x0348, 0x0A}, + {0x0349, 0x2F}, + {0x0346, 0x00}, + {0x0347, 0x00}, + {0x034A, 0x07}, + {0x034B, 0xA7}, + {0x0380, 0x00}, + {0x0381, 0x01}, + {0x0382, 0x00}, + {0x0383, 0x01}, + {0x0384, 0x00}, + {0x0385, 0x01}, + {0x0386, 0x00}, + {0x0387, 0x03}, + {0x034C, 0x05}, + {0x034D, 0x10}, + {0x034E, 0x03}, + {0x034F, 0xd4}, + {0x30BF, 0xAB}, + {0x30C0, 0xc0}, + {0x30C8, 0x06}, + {0x30C9, 0x54}, +}; + +struct qs_s5k4e1_i2c_reg_conf qs_s5k4e1_snap_settings_2d[] = { + {0x0100, 0x00}, + {0x0340, 0x07}, + {0x0341, 0xb4}, + {0x0342, 0x0A}, + {0x0343, 0xB2}, + {0x3030, 0x06}, /*shut streaming off*/ + {0x300E, 0xE8}, + {0x301B, 0x75}, + {0x301C, 0x04}, + {0x30BC, 0x98}, + {0x0202, 0x04}, + {0x0203, 0x12}, + {0x0204, 0x00}, + {0x0205, 0x80}, + {0x0306, 0x00}, + {0x0307, 0x64}, + {0x30F1, 0xa0}, + {0x30A9, 0x03},/*Horizontal Binning Off*/ + {0x300E, 0xE8},/*Vertical Binning Off*/ + {0x0387, 0x01},/*y_odd_inc*/ + {0x034C, 0x0A},/*x_output size*/ + {0x034D, 0x30}, + {0x034E, 0x07},/*y_output size*/ + {0x034F, 0xA8}, + {0x30BF, 0xAB},/*outif_enable[7], data_type[5:0](2Bh = bayer 10bit)*/ + {0x30C0, 0x86},/*video_offset[7:4] 3260%12*/ + {0x30C8, 0x0C},/*video_data_length 3260 = 2608 * 1.25*/ + {0x30C9, 0xBC}, + +}; + +struct qs_s5k4e1_i2c_reg_conf qs_s5k4e1_snap_settings_3d[] = { + {0x0100, 0x00}, + +/* Frame Length*/ + {0x0340, 0x09}, + {0x0341, 0x20}, +/* Line Length*/ + {0x0342, 0x0A}, + {0x0343, 0xB2}, + {0x3030, 0x06},/*shut streaming off*/ +/*Analog Setting*/ + {0x3017, 0xA4}, + {0x301B, 0x88}, + {0x30BC, 0x90}, + {0x301C, 0x04}, +/*Integration setting ... */ + {0x0202, 0x04}, + {0x0203, 0x12}, + {0x0204, 0x00}, + {0x0205, 0x80}, +/*PLL setting ...*/ + {0x0306, 0x00}, + {0x0307, 0x60}, + {0x30F1, 0x70}, +/*MIPI Size Setting*/ + {0x30A9, 0x01}, + {0x300E, 0xE8}, + {0x0387, 0x01}, + {0x0344, 0x01},/*x_addr_start*/ + {0x0345, 0x14}, + {0x0348, 0x09},/*x_addr_end*/ + {0x0349, 0x17}, + {0x0346, 0x01},/*y_addr_start*/ + {0x0347, 0x94}, + {0x034A, 0x06},/*y_addr_end*/ + {0x034B, 0x13}, + {0x0380, 0x00},/*x_even_inc 1*/ + {0x0381, 0x01}, + {0x0382, 0x00},/*x_odd_inc 1*/ + {0x0383, 0x01}, + {0x0384, 0x00},/*y_even_inc 1*/ + {0x0385, 0x01}, + {0x0386, 0x00},/*y_odd_inc 1*/ + {0x0387, 0x01}, + {0x034C, 0x08},/*x_output size*/ + {0x034D, 0x00}, + {0x034E, 0x04},/*y_output size*/ + {0x034F, 0x80}, + {0x30BF, 0xAA},/*outif_enable[7], data_type[5:0](2Bh = bayer 8bit)*/ + {0x30C0, 0x80},/*video_offset[7:4]*/ + {0x30C8, 0x08},/*video_data_length*/ + {0x30C9, 0x00}, + +}; + +struct qs_s5k4e1_i2c_reg_conf qs_s5k4e1_recommend_settings[] = { + {0x0100, 0x00}, + + {0x3030, 0x06},/*shut streaming*/ +/*Analog Setting*/ + {0x3000, 0x05}, + {0x3001, 0x03}, + {0x3002, 0x08}, + {0x3003, 0x09}, + {0x3004, 0x2E}, + {0x3005, 0x06}, + {0x3006, 0x34}, + {0x3007, 0x00}, + {0x3008, 0x3C}, + {0x3009, 0x3C}, + {0x300A, 0x28}, + {0x300B, 0x04}, + {0x300C, 0x0A}, + {0x300D, 0x02}, + {0x300F, 0x82}, + {0x3010, 0x00}, + {0x3011, 0x4C}, + {0x3012, 0x30}, + {0x3013, 0xC0}, + {0x3014, 0x00}, + {0x3015, 0x00}, + {0x3016, 0x2C}, + {0x3017, 0x94}, + {0x3018, 0x78}, + {0x301D, 0xD4}, + {0x3021, 0x02}, + {0x3022, 0x24}, + {0x3024, 0x40}, + {0x3027, 0x08}, + {0x3029, 0xC6}, + {0x302B, 0x01}, + {0x30D8, 0x3F}, +/* ADLC setting ...*/ + {0x3070, 0x5F}, + {0x3071, 0x00}, + {0x3080, 0x04}, + {0x3081, 0x38}, + +/*MIPI setting*/ + {0x30BD, 0x00},/*SEL_CCP[0]*/ + {0x3084, 0x15},/*SYNC Mode*/ + {0x30BE, 0x1A},/*M_PCLKDIV_AUTO[4], M_DIV_PCLK[3:0]*/ + {0x30C1, 0x01},/*pack video enable [0]*/ + {0x30EE, 0x02},/*DPHY enable [1]*/ + {0x3111, 0x86},/*Embedded data off [5]*/ +/*For MIPI T8 T9*/ + {0x30E3, 0x38}, + {0x30E4, 0x40}, + {0x3113, 0x70}, + {0x3114, 0x80}, + {0x3115, 0x7B}, + {0x3116, 0xC0}, + {0x30EE, 0x12}, + +/*PLL setting ...*/ + {0x0305, 0x06}, + {0x30B5, 0x01}, + {0x30E2, 0x02},/*num lanes[1:0] = 1*/ + +}; +static struct qs_s5k4e1_i2c_reg_conf qs_s5k4e1_default_lenshading_settings[] = { + + {0x3200, 0x00}, + {0x3201, 0x9a}, + {0x3202, 0x56}, + {0x3203, 0xf }, + {0x3204, 0xd8}, + {0x3205, 0x94}, + {0x3206, 0x0 }, + {0x3207, 0x10}, + {0x3208, 0x71}, + {0x3209, 0x0 }, + {0x320a, 0x9 }, + {0x320b, 0xc1}, + {0x320c, 0xf }, + {0x320d, 0xf1}, + {0x320e, 0x3d}, + {0x320f, 0x0 }, + {0x3210, 0xa }, + {0x3211, 0x93}, + {0x3212, 0xf }, + {0x3213, 0xc9}, + {0x3214, 0xa1}, + {0x3215, 0x0 }, + {0x3216, 0x10}, + {0x3217, 0x89}, + {0x3218, 0xf }, + {0x3219, 0xfb}, + {0x321a, 0xf3}, + {0x321b, 0xf }, + {0x321c, 0xf8}, + {0x321d, 0xfc}, + {0x321e, 0x0 }, + {0x321f, 0x4 }, + {0x3220, 0xe3}, + {0x3221, 0xf }, + {0x3222, 0xfe}, + {0x3223, 0x94}, + {0x3224, 0x0 }, + {0x3225, 0x24}, + {0x3226, 0x59}, + {0x3227, 0xf }, + {0x3228, 0xe9}, + {0x3229, 0x68}, + {0x322a, 0xf }, + {0x322b, 0xfa}, + {0x322c, 0x7f}, + {0x322d, 0x0 }, + {0x322e, 0x13}, + {0x322f, 0xe1}, + {0x3230, 0x0 }, + {0x3231, 0x3 }, + {0x3232, 0xbc}, + {0x3233, 0xf }, + {0x3234, 0xf0}, + {0x3235, 0xa1}, + {0x3236, 0xf }, + {0x3237, 0xf4}, + {0x3238, 0xc9}, + {0x3239, 0x0 }, + {0x323a, 0x11}, + {0x323b, 0x4b}, + {0x323c, 0x0 }, + {0x323d, 0x12}, + {0x323e, 0xc5}, + {0x323f, 0xf }, + {0x3240, 0xe3}, + {0x3241, 0xb }, + {0x3242, 0xf }, + {0x3243, 0xf8}, + {0x3244, 0x4f}, + {0x3245, 0x0 }, + {0x3246, 0x13}, + {0x3247, 0xac}, + {0x3248, 0x0 }, + {0x3249, 0x0 }, + {0x324a, 0x7c}, + {0x324b, 0xf }, + {0x324c, 0xfe}, + {0x324d, 0xdd}, + {0x324e, 0xf }, + {0x324f, 0xf2}, + {0x3250, 0x96}, + {0x3251, 0x0 }, + {0x3252, 0x8 }, + {0x3253, 0xef}, + {0x3254, 0x0 }, + {0x3255, 0x6 }, + {0x3256, 0xa4}, + {0x3257, 0x0 }, + {0x3258, 0x2 }, + {0x3259, 0x4b}, + {0x325a, 0x0 }, + {0x325b, 0x6 }, + {0x325c, 0x85}, + {0x325d, 0xf }, + {0x325e, 0xf8}, + {0x325f, 0x6a}, + {0x3260, 0xf }, + {0x3261, 0xfd}, + {0x3262, 0x70}, + {0x3263, 0x0 }, + {0x3264, 0xd }, + {0x3265, 0xa9}, + {0x3266, 0xf }, + {0x3267, 0xfd}, + {0x3268, 0xf8}, + {0x3269, 0xf }, + {0x326a, 0xec}, + {0x326b, 0xfc}, + {0x326c, 0x0 }, + {0x326d, 0xa7}, + {0x326e, 0x5 }, + {0x326f, 0xf }, + {0x3270, 0xd6}, + {0x3271, 0x19}, + {0x3272, 0x0 }, + {0x3273, 0xa }, + {0x3274, 0xe8}, + {0x3275, 0x0 }, + {0x3276, 0x17}, + {0x3277, 0x1 }, + {0x3278, 0xf }, + {0x3279, 0xe7}, + {0x327a, 0xa0}, + {0x327b, 0x0 }, + {0x327c, 0xb }, + {0x327d, 0xc3}, + {0x327e, 0xf }, + {0x327f, 0xc0}, + {0x3280, 0xe3}, + {0x3281, 0x0 }, + {0x3282, 0x15}, + {0x3283, 0x5a}, + {0x3284, 0xf }, + {0x3285, 0xf9}, + {0x3286, 0xa0}, + {0x3287, 0xf }, + {0x3288, 0xf4}, + {0x3289, 0xce}, + {0x328a, 0x0 }, + {0x328b, 0xb }, + {0x328c, 0x72}, + {0x328d, 0xf }, + {0x328e, 0xfb}, + {0x328f, 0xb5}, + {0x3290, 0x0 }, + {0x3291, 0x2f}, + {0x3292, 0xb }, + {0x3293, 0xf }, + {0x3294, 0xde}, + {0x3295, 0xc0}, + {0x3296, 0x0 }, + {0x3297, 0x0 }, + {0x3298, 0x58}, + {0x3299, 0x0 }, + {0x329a, 0x1b}, + {0x329b, 0x5 }, + {0x329c, 0xf }, + {0x329d, 0xf9}, + {0x329e, 0x23}, + {0x329f, 0xf }, + {0x32a0, 0xf3}, + {0x32a1, 0x94}, + {0x32a2, 0xf }, + {0x32a3, 0xe7}, + {0x32a4, 0xc2}, + {0x32a5, 0x0 }, + {0x32a6, 0x1d}, + {0x32a7, 0xe5}, + {0x32a8, 0x0 }, + {0x32a9, 0x5 }, + {0x32aa, 0xaf}, + {0x32ab, 0xf }, + {0x32ac, 0xe3}, + {0x32ad, 0xb7}, + {0x32ae, 0xf }, + {0x32af, 0xf8}, + {0x32b0, 0x34}, + {0x32b1, 0x0 }, + {0x32b2, 0x1c}, + {0x32b3, 0x3d}, + {0x32b4, 0x0 }, + {0x32b5, 0x10}, + {0x32b6, 0x4a}, + {0x32b7, 0xf }, + {0x32b8, 0xfa}, + {0x32b9, 0x7 }, + {0x32ba, 0xf }, + {0x32bb, 0xff}, + {0x32bc, 0x16}, + {0x32bd, 0x0 }, + {0x32be, 0x5 }, + {0x32bf, 0x4e}, + {0x32c0, 0x0 }, + {0x32c1, 0xc }, + {0x32c2, 0x1b}, + {0x32c3, 0xf }, + {0x32c4, 0xf1}, + {0x32c5, 0xdb}, + {0x32c6, 0xf }, + {0x32c7, 0xfc}, + {0x32c8, 0xf8}, + {0x32c9, 0xf }, + {0x32ca, 0xf4}, + {0x32cb, 0xad}, + {0x32cc, 0xf }, + {0x32cd, 0xfb}, + {0x32ce, 0x59}, + {0x32cf, 0x0 }, + {0x32d0, 0x9 }, + {0x32d1, 0xf7}, + {0x32d2, 0x0 }, + {0x32d3, 0x0 }, + {0x32d4, 0xc1}, + {0x32d5, 0xf }, + {0x32d6, 0xf5}, + {0x32d7, 0x30}, + {0x32d8, 0x0 }, + {0x32d9, 0x83}, + {0x32da, 0x1d}, + {0x32db, 0xf }, + {0x32dc, 0xe3}, + {0x32dd, 0x3c}, + {0x32de, 0x0 }, + {0x32df, 0xa }, + {0x32e0, 0x10}, + {0x32e1, 0x0 }, + {0x32e2, 0x7 }, + {0x32e3, 0x65}, + {0x32e4, 0xf }, + {0x32e5, 0xfe}, + {0x32e6, 0x79}, + {0x32e7, 0xf }, + {0x32e8, 0xfd}, + {0x32e9, 0x57}, + {0x32ea, 0xf }, + {0x32eb, 0xd6}, + {0x32ec, 0x8f}, + {0x32ed, 0x0 }, + {0x32ee, 0x3 }, + {0x32ef, 0x93}, + {0x32f0, 0x0 }, + {0x32f1, 0x6 }, + {0x32f2, 0xa }, + {0x32f3, 0xf }, + {0x32f4, 0xfa}, + {0x32f5, 0x6c}, + {0x32f6, 0xf }, + {0x32f7, 0xf1}, + {0x32f8, 0x1e}, + {0x32f9, 0x0 }, + {0x32fa, 0x14}, + {0x32fb, 0xe7}, + {0x32fc, 0x0 }, + {0x32fd, 0x1f}, + {0x32fe, 0x2d}, + {0x32ff, 0x0 }, + {0x3300, 0x7 }, + {0x3301, 0x5e}, + {0x3302, 0xf }, + {0x3303, 0xe0}, + {0x3304, 0x55}, + {0x3305, 0x0 }, + {0x3306, 0x20}, + {0x3307, 0x93}, + {0x3308, 0x0 }, + {0x3309, 0xf }, + {0x330a, 0x20}, + {0x330b, 0xf }, + {0x330c, 0xd7}, + {0x330d, 0xf5}, + {0x330e, 0xf }, + {0x330f, 0xef}, + {0x3310, 0xb8}, + {0x3311, 0xf }, + {0x3312, 0xf0}, + {0x3313, 0x29}, + {0x3314, 0x0 }, + {0x3315, 0x27}, + {0x3316, 0x5e}, + {0x3317, 0xf }, + {0x3318, 0xda}, + {0x3319, 0x14}, + {0x331a, 0xf }, + {0x331b, 0xef}, + {0x331c, 0x93}, + {0x331d, 0x0 }, + {0x331e, 0x2c}, + {0x331f, 0xdc}, + {0x3320, 0x0 }, + {0x3321, 0xe }, + {0x3322, 0x2d}, + {0x3323, 0x0 }, + {0x3324, 0x6 }, + {0x3325, 0xcf}, + {0x3326, 0xf }, + {0x3327, 0xfb}, + {0x3328, 0x26}, + {0x3329, 0x0 }, + {0x332a, 0x3 }, + {0x332b, 0x5 }, + {0x332c, 0x0 }, + {0x332d, 0x6 }, + {0x332e, 0xa6}, + {0x332f, 0xf }, + {0x3330, 0xf7}, + {0x3331, 0x7b}, + {0x3332, 0xf }, + {0x3333, 0xf9}, + {0x3334, 0xb }, + {0x3335, 0x0 }, + {0x3336, 0x7 }, + {0x3337, 0x5a}, + {0x3338, 0xf }, + {0x3339, 0xe4}, + {0x333a, 0x7a}, + {0x333b, 0x0 }, + {0x333c, 0x1b}, + {0x333d, 0xb0}, + {0x333e, 0x0 }, + {0x333f, 0x2 }, + {0x3340, 0xa7}, + {0x3341, 0xf }, + {0x3342, 0xe9}, + {0x3343, 0x3a}, + {0x3344, 0x0 }, + {0x3345, 0x95}, + {0x3346, 0x42}, + {0x3347, 0xf }, + {0x3348, 0xda}, + {0x3349, 0x45}, + {0x334a, 0x0 }, + {0x334b, 0x16}, + {0x334c, 0x7a}, + {0x334d, 0xf }, + {0x334e, 0xfb}, + {0x334f, 0x32}, + {0x3350, 0x0 }, + {0x3351, 0x6 }, + {0x3352, 0x35}, + {0x3353, 0xf }, + {0x3354, 0xfc}, + {0x3355, 0x8f}, + {0x3356, 0xf }, + {0x3357, 0xca}, + {0x3358, 0xd5}, + {0x3359, 0x0 }, + {0x335a, 0x11}, + {0x335b, 0x59}, + {0x335c, 0xf }, + {0x335d, 0xfa}, + {0x335e, 0xaa}, + {0x335f, 0xf }, + {0x3360, 0xfe}, + {0x3361, 0x84}, + {0x3362, 0xf }, + {0x3363, 0xf6}, + {0x3364, 0x8f}, + {0x3365, 0x0 }, + {0x3366, 0xb }, + {0x3367, 0x70}, + {0x3368, 0x0 }, + {0x3369, 0x25}, + {0x336a, 0x83}, + {0x336b, 0xf }, + {0x336c, 0xe7}, + {0x336d, 0x27}, + {0x336e, 0xf }, + {0x336f, 0xf1}, + {0x3370, 0x72}, + {0x3371, 0x0 }, + {0x3372, 0x21}, + {0x3373, 0x6d}, + {0x3374, 0x0 }, + {0x3375, 0x2 }, + {0x3376, 0xc3}, + {0x3377, 0xf }, + {0x3378, 0xe8}, + {0x3379, 0x5a}, + {0x337a, 0xf }, + {0x337b, 0xf2}, + {0x337c, 0x73}, + {0x337d, 0x0 }, + {0x337e, 0x19}, + {0x337f, 0xa5}, + {0x3380, 0x0 }, + {0x3381, 0x1a}, + {0x3382, 0x81}, + {0x3383, 0xf }, + {0x3384, 0xd0}, + {0x3385, 0x31}, + {0x3386, 0xf }, + {0x3387, 0xfb}, + {0x3388, 0xff}, + {0x3389, 0x0 }, + {0x338a, 0x1e}, + {0x338b, 0xe1}, + {0x338c, 0x0 }, + {0x338d, 0x5 }, + {0x338e, 0xe1}, + {0x338f, 0xf }, + {0x3390, 0xee}, + {0x3391, 0xe2}, + {0x3392, 0xf }, + {0x3393, 0xf6}, + {0x3394, 0xcf}, + {0x3395, 0x0 }, + {0x3396, 0x13}, + {0x3397, 0x8f}, + {0x3398, 0x0 }, + {0x3399, 0x3 }, + {0x339a, 0x61}, + {0x339b, 0xf }, + {0x339c, 0xf8}, + {0x339d, 0xf7}, + {0x339e, 0x0 }, + {0x339f, 0x0 }, + {0x33a0, 0xb5}, + {0x33a1, 0x0 }, + {0x33a2, 0x5 }, + {0x33a3, 0x78}, + {0x33a4, 0xf }, + {0x33a5, 0xf4}, + {0x33a6, 0x5 }, + {0x33a7, 0x0 }, + {0x33a8, 0xc }, + {0x33a9, 0xe }, + {0x33aa, 0x0 }, + {0x33ab, 0x3 }, + {0x33ac, 0x53}, + {0x33ad, 0xf }, + {0x33ae, 0xec}, + {0x33af, 0xbd}, +}; + +const struct +qs_s5k4e1_i2c_reg_conf qs_s5k4e1_lenshading_settings[4][LENS_SHADE_TABLE] = { + {/*2D Preview*/ + {0x3097, 0x52},/*sh4ch_blk_width = 82*/ + {0x3098, 0x3e},/*sh4ch_blk_height = 62*/ + {0x3099, 0x03},/*sh4ch_step_x msb (sh4ch_step_x = 799)*/ + {0x309a, 0x1f},/*sh4ch_step_x lsb*/ + {0x309b, 0x04},/*sh4ch_step_y msb (sh4ch_step_y = 1057)*/ + {0x309c, 0x21},/*sh4ch_step_y lsb*/ + {0x309d, 0x00},/*sh4ch_start_blk_cnt_x = 0*/ + {0x309e, 0x00},/*sh4ch_start_int_cnt_x = 0*/ + {0x309f, 0x00},/*sh4ch_start_frac_cnt_x msb (0)*/ + {0x30a0, 0x00},/*sh4ch_start_frac_cnt_x lsb*/ + {0x30a1, 0x00},/*sh4ch_start_blk_cnt_y = 0*/ + {0x30a2, 0x00},/*sh4ch_start_int_cnt_y = 0*/ + {0x30a3, 0x00},/*sh4ch_start_frac_cnt_y msb (0)*/ + {0x30a4, 0x00},/*sh4ch_start_frac_cnt_y lsb*/ + {0x30a5, 0x01}, + {0x30a6, 0x00},/*gs_pedestal = 64*/ + }, + {/*2D Snapshot*/ + {0x3097, 0x52},/*sh4ch_blk_width = 82*/ + {0x3098, 0x7b},/*sh4ch_blk_height = 123*/ + {0x3099, 0x03},/*sh4ch_step_x msb (sh4ch_step_x = 799)*/ + {0x309a, 0x1f},/*sh4ch_step_x lsb*/ + {0x309b, 0x02},/*sh4ch_step_y msb (sh4ch_step_y = 533)*/ + {0x309c, 0x15},/*sh4ch_step_y lsb*/ + {0x309d, 0x00},/*sh4ch_start_blk_cnt_x = 0*/ + {0x309e, 0x00},/*sh4ch_start_int_cnt_x = 0*/ + {0x309f, 0x00},/*sh4ch_start_frac_cnt_x msb (0)*/ + {0x30a0, 0x00},/*sh4ch_start_frac_cnt_x lsb*/ + {0x30a1, 0x00},/*sh4ch_start_blk_cnt_y = 0*/ + {0x30a2, 0x00},/*sh4ch_start_int_cnt_y = 0*/ + {0x30a3, 0x00},/*sh4ch_start_frac_cnt_y msb (0)*/ + {0x30a4, 0x00},/*sh4ch_start_frac_cnt_y lsb*/ + {0x30a5, 0x01}, + {0x30a6, 0x00},/*gs_pedestal = 64*/ + }, + + {/*3D Preview*/ + {0x3097, 0x52},/*sh4ch_blk_width = 82*/ + {0x3098, 0x7b},/*sh4ch_blk_height = 123*/ + {0x3099, 0x03},/*sh4ch_step_x msb (sh4ch_step_x = 799)*/ + {0x309a, 0x1f},/*sh4ch_step_x lsb*/ + {0x309b, 0x02},/*sh4ch_step_y msb (sh4ch_step_y = 533)*/ + {0x309c, 0x15},/*sh4ch_step_y lsb*/ + {0x309d, 0x3a},/*sh4ch_start_blk_cnt_x = 58*/ + {0x309e, 0x01},/*sh4ch_start_int_cnt_x = 1*/ + {0x309f, 0xb5},/*sh4ch_start_frac_cnt_x msb (46342)*/ + {0x30a0, 0x06},/*sh4ch_start_frac_cnt_x lsb*/ + {0x30a1, 0x23},/*sh4ch_start_blk_cnt_y = 35*/ + {0x30a2, 0x03},/*sh4ch_start_int_cnt_y = 3*/ + {0x30a3, 0x48},/*sh4ch_start_frac_cnt_y msb (46342)*/ + {0x30a4, 0xdf},/*sh4ch_start_frac_cnt_y lsb*/ + {0x30a5, 0x01}, + {0x30a6, 0x00},/*gs_pedestal = 64*/ + }, + + {/*3D Snapshot*/ + {0x3097, 0x52},/*sh4ch_blk_width = 82*/ + {0x3098, 0x7b},/*sh4ch_blk_height = 123*/ + {0x3099, 0x03},/*sh4ch_step_x msb (sh4ch_step_x = 799)*/ + {0x309a, 0x1f},/*sh4ch_step_x lsb*/ + {0x309b, 0x02},/*sh4ch_step_y msb (sh4ch_step_y = 533)*/ + {0x309c, 0x15},/*sh4ch_step_y lsb*/ + {0x309d, 0x38},/*sh4ch_start_blk_cnt_x = 56*/ + {0x309e, 0x01},/*sh4ch_start_int_cnt_x = 1*/ + {0x309f, 0xae},/*sh4ch_start_frac_cnt_x msb (44744)*/ + {0x30a0, 0xc8},/*sh4ch_start_frac_cnt_x lsb*/ + {0x30a1, 0x23},/*sh4ch_start_blk_cnt_y = 35*/ + {0x30a2, 0x03},/*sh4ch_start_int_cnt_y = 3*/ + {0x30a3, 0x48},/*sh4ch_start_frac_cnt_y msb (44744)*/ + {0x30a4, 0xdf},/*sh4ch_start_frac_cnt_y lsb*/ + {0x30a5, 0x01}, + {0x30a6, 0x00},/*gs_pedestal = 64*/ + }, + +}; + +struct qs_s5k4e1_i2c_conf_array qs_s5k4e1_confs[] = { + {&qs_s5k4e1_prev_settings_2d[0], \ + ARRAY_SIZE(qs_s5k4e1_prev_settings_2d)}, + {&qs_s5k4e1_snap_settings_2d[0], \ + ARRAY_SIZE(qs_s5k4e1_snap_settings_2d)}, + {&qs_s5k4e1_prev_settings_3d[0], \ + ARRAY_SIZE(qs_s5k4e1_prev_settings_3d)}, + {&qs_s5k4e1_snap_settings_3d[0], \ + ARRAY_SIZE(qs_s5k4e1_snap_settings_3d)}, +}; +struct qs_s5k4e1_reg qs_s5k4e1_regs = { + .rec_settings = &qs_s5k4e1_recommend_settings[0], + .rec_size = ARRAY_SIZE(qs_s5k4e1_recommend_settings), + .reg_lens = &qs_s5k4e1_lenshading_settings[0], + .reg_lens_size = ARRAY_SIZE(qs_s5k4e1_lenshading_settings[0]), + .reg_default_lens = &qs_s5k4e1_default_lenshading_settings[0], + .reg_default_lens_size = + ARRAY_SIZE(qs_s5k4e1_default_lenshading_settings), + .conf_array = &qs_s5k4e1_confs[0], +}; diff --git a/drivers/media/video/msm/s5k3e2fx.c b/drivers/media/video/msm/s5k3e2fx.c new file mode 100644 index 00000000000..178a080537b --- /dev/null +++ b/drivers/media/video/msm/s5k3e2fx.c @@ -0,0 +1,1386 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "s5k3e2fx.h" + +#define S5K3E2FX_REG_MODEL_ID 0x0000 +#define S5K3E2FX_MODEL_ID 0x3E2F + +/* PLL Registers */ +#define REG_PRE_PLL_CLK_DIV 0x0305 +#define REG_PLL_MULTIPLIER_MSB 0x0306 +#define REG_PLL_MULTIPLIER_LSB 0x0307 +#define REG_VT_PIX_CLK_DIV 0x0301 +#define REG_VT_SYS_CLK_DIV 0x0303 +#define REG_OP_PIX_CLK_DIV 0x0309 +#define REG_OP_SYS_CLK_DIV 0x030B + +/* Data Format Registers */ +#define REG_CCP_DATA_FORMAT_MSB 0x0112 +#define REG_CCP_DATA_FORMAT_LSB 0x0113 + +/* Output Size */ +#define REG_X_OUTPUT_SIZE_MSB 0x034C +#define REG_X_OUTPUT_SIZE_LSB 0x034D +#define REG_Y_OUTPUT_SIZE_MSB 0x034E +#define REG_Y_OUTPUT_SIZE_LSB 0x034F + +/* Binning */ +#define REG_X_EVEN_INC 0x0381 +#define REG_X_ODD_INC 0x0383 +#define REG_Y_EVEN_INC 0x0385 +#define REG_Y_ODD_INC 0x0387 +/*Reserved register */ +#define REG_BINNING_ENABLE 0x3014 + +/* Frame Fotmat */ +#define REG_FRAME_LENGTH_LINES_MSB 0x0340 +#define REG_FRAME_LENGTH_LINES_LSB 0x0341 +#define REG_LINE_LENGTH_PCK_MSB 0x0342 +#define REG_LINE_LENGTH_PCK_LSB 0x0343 + +/* MSR setting */ +/* Reserved registers */ +#define REG_SHADE_CLK_ENABLE 0x30AC +#define REG_SEL_CCP 0x30C4 +#define REG_VPIX 0x3024 +#define REG_CLAMP_ON 0x3015 +#define REG_OFFSET 0x307E + +/* CDS timing settings */ +/* Reserved registers */ +#define REG_LD_START 0x3000 +#define REG_LD_END 0x3001 +#define REG_SL_START 0x3002 +#define REG_SL_END 0x3003 +#define REG_RX_START 0x3004 +#define REG_S1_START 0x3005 +#define REG_S1_END 0x3006 +#define REG_S1S_START 0x3007 +#define REG_S1S_END 0x3008 +#define REG_S3_START 0x3009 +#define REG_S3_END 0x300A +#define REG_CMP_EN_START 0x300B +#define REG_CLP_SL_START 0x300C +#define REG_CLP_SL_END 0x300D +#define REG_OFF_START 0x300E +#define REG_RMP_EN_START 0x300F +#define REG_TX_START 0x3010 +#define REG_TX_END 0x3011 +#define REG_STX_WIDTH 0x3012 +#define REG_TYPE1_AF_ENABLE 0x3130 +#define DRIVER_ENABLED 0x0001 +#define AUTO_START_ENABLED 0x0010 +#define REG_NEW_POSITION 0x3131 +#define REG_3152_RESERVED 0x3152 +#define REG_315A_RESERVED 0x315A +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_MSB 0x0204 +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_LSB 0x0205 +#define REG_FINE_INTEGRATION_TIME 0x0200 +#define REG_COARSE_INTEGRATION_TIME 0x0202 +#define REG_COARSE_INTEGRATION_TIME_LSB 0x0203 + +/* Mode select register */ +#define S5K3E2FX_REG_MODE_SELECT 0x0100 +#define S5K3E2FX_MODE_SELECT_STREAM 0x01 /* start streaming */ +#define S5K3E2FX_MODE_SELECT_SW_STANDBY 0x00 /* software standby */ +#define S5K3E2FX_REG_SOFTWARE_RESET 0x0103 +#define S5K3E2FX_SOFTWARE_RESET 0x01 +#define REG_TEST_PATTERN_MODE 0x0601 + +struct reg_struct { + uint8_t pre_pll_clk_div; /* 0x0305 */ + uint8_t pll_multiplier_msb; /* 0x0306 */ + uint8_t pll_multiplier_lsb; /* 0x0307 */ + uint8_t vt_pix_clk_div; /* 0x0301 */ + uint8_t vt_sys_clk_div; /* 0x0303 */ + uint8_t op_pix_clk_div; /* 0x0309 */ + uint8_t op_sys_clk_div; /* 0x030B */ + uint8_t ccp_data_format_msb; /* 0x0112 */ + uint8_t ccp_data_format_lsb; /* 0x0113 */ + uint8_t x_output_size_msb; /* 0x034C */ + uint8_t x_output_size_lsb; /* 0x034D */ + uint8_t y_output_size_msb; /* 0x034E */ + uint8_t y_output_size_lsb; /* 0x034F */ + uint8_t x_even_inc; /* 0x0381 */ + uint8_t x_odd_inc; /* 0x0383 */ + uint8_t y_even_inc; /* 0x0385 */ + uint8_t y_odd_inc; /* 0x0387 */ + uint8_t binning_enable; /* 0x3014 */ + uint8_t frame_length_lines_msb; /* 0x0340 */ + uint8_t frame_length_lines_lsb; /* 0x0341 */ + uint8_t line_length_pck_msb; /* 0x0342 */ + uint8_t line_length_pck_lsb; /* 0x0343 */ + uint8_t shade_clk_enable ; /* 0x30AC */ + uint8_t sel_ccp; /* 0x30C4 */ + uint8_t vpix; /* 0x3024 */ + uint8_t clamp_on; /* 0x3015 */ + uint8_t offset; /* 0x307E */ + uint8_t ld_start; /* 0x3000 */ + uint8_t ld_end; /* 0x3001 */ + uint8_t sl_start; /* 0x3002 */ + uint8_t sl_end; /* 0x3003 */ + uint8_t rx_start; /* 0x3004 */ + uint8_t s1_start; /* 0x3005 */ + uint8_t s1_end; /* 0x3006 */ + uint8_t s1s_start; /* 0x3007 */ + uint8_t s1s_end; /* 0x3008 */ + uint8_t s3_start; /* 0x3009 */ + uint8_t s3_end; /* 0x300A */ + uint8_t cmp_en_start; /* 0x300B */ + uint8_t clp_sl_start; /* 0x300C */ + uint8_t clp_sl_end; /* 0x300D */ + uint8_t off_start; /* 0x300E */ + uint8_t rmp_en_start; /* 0x300F */ + uint8_t tx_start; /* 0x3010 */ + uint8_t tx_end; /* 0x3011 */ + uint8_t stx_width; /* 0x3012 */ + uint8_t reg_3152_reserved; /* 0x3152 */ + uint8_t reg_315A_reserved; /* 0x315A */ + uint8_t analogue_gain_code_global_msb; /* 0x0204 */ + uint8_t analogue_gain_code_global_lsb; /* 0x0205 */ + uint8_t fine_integration_time; /* 0x0200 */ + uint8_t coarse_integration_time; /* 0x0202 */ + uint32_t size_h; + uint32_t blk_l; + uint32_t size_w; + uint32_t blk_p; +}; + +struct reg_struct s5k3e2fx_reg_pat[2] = { + { /* Preview */ + 0x06, /* pre_pll_clk_div REG=0x0305 */ + 0x00, /* pll_multiplier_msb REG=0x0306 */ + 0x88, /* pll_multiplier_lsb REG=0x0307 */ + 0x0a, /* vt_pix_clk_div REG=0x0301 */ + 0x01, /* vt_sys_clk_div REG=0x0303 */ + 0x0a, /* op_pix_clk_div REG=0x0309 */ + 0x01, /* op_sys_clk_div REG=0x030B */ + 0x0a, /* ccp_data_format_msb REG=0x0112 */ + 0x0a, /* ccp_data_format_lsb REG=0x0113 */ + 0x05, /* x_output_size_msb REG=0x034C */ + 0x10, /* x_output_size_lsb REG=0x034D */ + 0x03, /* y_output_size_msb REG=0x034E */ + 0xcc, /* y_output_size_lsb REG=0x034F */ + + /* enable binning for preview */ + 0x01, /* x_even_inc REG=0x0381 */ + 0x01, /* x_odd_inc REG=0x0383 */ + 0x01, /* y_even_inc REG=0x0385 */ + 0x03, /* y_odd_inc REG=0x0387 */ + 0x06, /* binning_enable REG=0x3014 */ + + 0x03, /* frame_length_lines_msb REG=0x0340 */ + 0xde, /* frame_length_lines_lsb REG=0x0341 */ + 0x0a, /* line_length_pck_msb REG=0x0342 */ + 0xac, /* line_length_pck_lsb REG=0x0343 */ + 0x81, /* shade_clk_enable REG=0x30AC */ + 0x01, /* sel_ccp REG=0x30C4 */ + 0x04, /* vpix REG=0x3024 */ + 0x00, /* clamp_on REG=0x3015 */ + 0x02, /* offset REG=0x307E */ + 0x03, /* ld_start REG=0x3000 */ + 0x9c, /* ld_end REG=0x3001 */ + 0x02, /* sl_start REG=0x3002 */ + 0x9e, /* sl_end REG=0x3003 */ + 0x05, /* rx_start REG=0x3004 */ + 0x0f, /* s1_start REG=0x3005 */ + 0x24, /* s1_end REG=0x3006 */ + 0x7c, /* s1s_start REG=0x3007 */ + 0x9a, /* s1s_end REG=0x3008 */ + 0x10, /* s3_start REG=0x3009 */ + 0x14, /* s3_end REG=0x300A */ + 0x10, /* cmp_en_start REG=0x300B */ + 0x04, /* clp_sl_start REG=0x300C */ + 0x26, /* clp_sl_end REG=0x300D */ + 0x02, /* off_start REG=0x300E */ + 0x0e, /* rmp_en_start REG=0x300F */ + 0x30, /* tx_start REG=0x3010 */ + 0x4e, /* tx_end REG=0x3011 */ + 0x1E, /* stx_width REG=0x3012 */ + 0x08, /* reg_3152_reserved REG=0x3152 */ + 0x10, /* reg_315A_reserved REG=0x315A */ + 0x00, /* analogue_gain_code_global_msb REG=0x0204 */ + 0x80, /* analogue_gain_code_global_lsb REG=0x0205 */ + 0x02, /* fine_integration_time REG=0x0200 */ + 0x03, /* coarse_integration_time REG=0x0202 */ + 972, + 18, + 1296, + 1436 + }, + { /* Snapshot */ + 0x06, /* pre_pll_clk_div REG=0x0305 */ + 0x00, /* pll_multiplier_msb REG=0x0306 */ + 0x88, /* pll_multiplier_lsb REG=0x0307 */ + 0x0a, /* vt_pix_clk_div REG=0x0301 */ + 0x01, /* vt_sys_clk_div REG=0x0303 */ + 0x0a, /* op_pix_clk_div REG=0x0309 */ + 0x01, /* op_sys_clk_div REG=0x030B */ + 0x0a, /* ccp_data_format_msb REG=0x0112 */ + 0x0a, /* ccp_data_format_lsb REG=0x0113 */ + 0x0a, /* x_output_size_msb REG=0x034C */ + 0x30, /* x_output_size_lsb REG=0x034D */ + 0x07, /* y_output_size_msb REG=0x034E */ + 0xa8, /* y_output_size_lsb REG=0x034F */ + + /* disable binning for snapshot */ + 0x01, /* x_even_inc REG=0x0381 */ + 0x01, /* x_odd_inc REG=0x0383 */ + 0x01, /* y_even_inc REG=0x0385 */ + 0x01, /* y_odd_inc REG=0x0387 */ + 0x00, /* binning_enable REG=0x3014 */ + + 0x07, /* frame_length_lines_msb REG=0x0340 */ + 0xb6, /* frame_length_lines_lsb REG=0x0341 */ + 0x0a, /* line_length_pck_msb REG=0x0342 */ + 0xac, /* line_length_pck_lsb REG=0x0343 */ + 0x81, /* shade_clk_enable REG=0x30AC */ + 0x01, /* sel_ccp REG=0x30C4 */ + 0x04, /* vpix REG=0x3024 */ + 0x00, /* clamp_on REG=0x3015 */ + 0x02, /* offset REG=0x307E */ + 0x03, /* ld_start REG=0x3000 */ + 0x9c, /* ld_end REG=0x3001 */ + 0x02, /* sl_start REG=0x3002 */ + 0x9e, /* sl_end REG=0x3003 */ + 0x05, /* rx_start REG=0x3004 */ + 0x0f, /* s1_start REG=0x3005 */ + 0x24, /* s1_end REG=0x3006 */ + 0x7c, /* s1s_start REG=0x3007 */ + 0x9a, /* s1s_end REG=0x3008 */ + 0x10, /* s3_start REG=0x3009 */ + 0x14, /* s3_end REG=0x300A */ + 0x10, /* cmp_en_start REG=0x300B */ + 0x04, /* clp_sl_start REG=0x300C */ + 0x26, /* clp_sl_end REG=0x300D */ + 0x02, /* off_start REG=0x300E */ + 0x0e, /* rmp_en_start REG=0x300F */ + 0x30, /* tx_start REG=0x3010 */ + 0x4e, /* tx_end REG=0x3011 */ + 0x1E, /* stx_width REG=0x3012 */ + 0x08, /* reg_3152_reserved REG=0x3152 */ + 0x10, /* reg_315A_reserved REG=0x315A */ + 0x00, /* analogue_gain_code_global_msb REG=0x0204 */ + 0x80, /* analogue_gain_code_global_lsb REG=0x0205 */ + 0x02, /* fine_integration_time REG=0x0200 */ + 0x03, /* coarse_integration_time REG=0x0202 */ + 1960, + 14, + 2608, + 124 + } +}; + +struct s5k3e2fx_work { + struct work_struct work; +}; +static struct s5k3e2fx_work *s5k3e2fx_sensorw; +static struct i2c_client *s5k3e2fx_client; + +struct s5k3e2fx_ctrl { + const struct msm_camera_sensor_info *sensordata; + + int sensormode; + uint32_t fps_divider; /* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ + + uint16_t curr_lens_pos; + uint16_t init_curr_lens_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + + enum msm_s_resolution prev_res; + enum msm_s_resolution pict_res; + enum msm_s_resolution curr_res; + enum msm_s_test_mode set_test; +}; + +struct s5k3e2fx_i2c_reg_conf { + unsigned short waddr; + unsigned char bdata; +}; + +static struct s5k3e2fx_ctrl *s5k3e2fx_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(s5k3e2fx_wait_queue); +DEFINE_MUTEX(s5k3e2fx_mutex); + +static int s5k3e2fx_i2c_rxdata(unsigned short saddr, unsigned char *rxdata, + int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = length, + .buf = rxdata, + }, + }; + + if (i2c_transfer(s5k3e2fx_client->adapter, msgs, 2) < 0) { + CDBG("s5k3e2fx_i2c_rxdata failed!\n"); + return -EIO; + } + + return 0; +} + +static int32_t s5k3e2fx_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + + if (i2c_transfer(s5k3e2fx_client->adapter, msg, 1) < 0) { + CDBG("s5k3e2fx_i2c_txdata failed\n"); + return -EIO; + } + + return 0; +} + +static int32_t s5k3e2fx_i2c_write_b(unsigned short saddr, unsigned short waddr, + unsigned char bdata) +{ + int32_t rc = -EIO; + unsigned char buf[4]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00)>>8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + + rc = s5k3e2fx_i2c_txdata(saddr, buf, 3); + + if (rc < 0) + CDBG("i2c_write_w failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + + return rc; +} + +static int32_t s5k3e2fx_i2c_write_table( + struct s5k3e2fx_i2c_reg_conf *reg_cfg_tbl, int num) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < num; i++) { + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + reg_cfg_tbl->waddr, reg_cfg_tbl->bdata); + if (rc < 0) + break; + reg_cfg_tbl++; + } + + return rc; +} + +static int32_t s5k3e2fx_i2c_read_w(unsigned short saddr, unsigned short raddr, + unsigned short *rdata) +{ + int32_t rc = 0; + unsigned char buf[4]; + + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + buf[0] = (raddr & 0xFF00)>>8; + buf[1] = (raddr & 0x00FF); + + rc = s5k3e2fx_i2c_rxdata(saddr, buf, 2); + if (rc < 0) + return rc; + + *rdata = buf[0] << 8 | buf[1]; + + if (rc < 0) + CDBG("s5k3e2fx_i2c_read failed!\n"); + + return rc; +} + +static int s5k3e2fx_probe_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + return 0; +} + +static int s5k3e2fx_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + uint16_t chipid = 0; + + rc = gpio_request(data->sensor_reset, "s5k3e2fx"); + if (!rc) + gpio_direction_output(data->sensor_reset, 1); + else + goto init_probe_done; + + mdelay(20); + + CDBG("s5k3e2fx_sensor_init(): reseting sensor.\n"); + + rc = s5k3e2fx_i2c_read_w(s5k3e2fx_client->addr, + S5K3E2FX_REG_MODEL_ID, &chipid); + if (rc < 0) + goto init_probe_fail; + + if (chipid != S5K3E2FX_MODEL_ID) { + CDBG("S5K3E2FX wrong model_id = 0x%x\n", chipid); + rc = -ENODEV; + goto init_probe_fail; + } + + goto init_probe_done; + +init_probe_fail: + s5k3e2fx_probe_init_done(data); +init_probe_done: + return rc; +} + +static int s5k3e2fx_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&s5k3e2fx_wait_queue); + return 0; +} + +static const struct i2c_device_id s5k3e2fx_i2c_id[] = { + { "s5k3e2fx", 0}, + { } +}; + +static int s5k3e2fx_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("s5k3e2fx_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + s5k3e2fx_sensorw = kzalloc(sizeof(struct s5k3e2fx_work), GFP_KERNEL); + if (!s5k3e2fx_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, s5k3e2fx_sensorw); + s5k3e2fx_init_client(client); + s5k3e2fx_client = client; + + mdelay(50); + + CDBG("s5k3e2fx_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("s5k3e2fx_probe failed! rc = %d\n", rc); + return rc; +} + +static struct i2c_driver s5k3e2fx_i2c_driver = { + .id_table = s5k3e2fx_i2c_id, + .probe = s5k3e2fx_i2c_probe, + .remove = __exit_p(s5k3e2fx_i2c_remove), + .driver = { + .name = "s5k3e2fx", + }, +}; + +static int32_t s5k3e2fx_test(enum msm_s_test_mode mo) +{ + int32_t rc = 0; + + if (mo == S_TEST_OFF) + rc = 0; + else + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + REG_TEST_PATTERN_MODE, (uint16_t)mo); + + return rc; +} + +static int32_t s5k3e2fx_setting(enum msm_s_reg_update rupdate, + enum msm_s_setting rt) +{ + int32_t rc = 0; + uint16_t num_lperf; + + switch (rupdate) { + case S_UPDATE_PERIODIC: + if (rt == S_RES_PREVIEW || rt == S_RES_CAPTURE) { + + struct s5k3e2fx_i2c_reg_conf tbl_1[] = { + {REG_CCP_DATA_FORMAT_MSB, + s5k3e2fx_reg_pat[rt].ccp_data_format_msb}, + {REG_CCP_DATA_FORMAT_LSB, + s5k3e2fx_reg_pat[rt].ccp_data_format_lsb}, + {REG_X_OUTPUT_SIZE_MSB, + s5k3e2fx_reg_pat[rt].x_output_size_msb}, + {REG_X_OUTPUT_SIZE_LSB, + s5k3e2fx_reg_pat[rt].x_output_size_lsb}, + {REG_Y_OUTPUT_SIZE_MSB, + s5k3e2fx_reg_pat[rt].y_output_size_msb}, + {REG_Y_OUTPUT_SIZE_LSB, + s5k3e2fx_reg_pat[rt].y_output_size_lsb}, + {REG_X_EVEN_INC, + s5k3e2fx_reg_pat[rt].x_even_inc}, + {REG_X_ODD_INC, + s5k3e2fx_reg_pat[rt].x_odd_inc}, + {REG_Y_EVEN_INC, + s5k3e2fx_reg_pat[rt].y_even_inc}, + {REG_Y_ODD_INC, + s5k3e2fx_reg_pat[rt].y_odd_inc}, + {REG_BINNING_ENABLE, + s5k3e2fx_reg_pat[rt].binning_enable}, + }; + + struct s5k3e2fx_i2c_reg_conf tbl_2[] = { + {REG_FRAME_LENGTH_LINES_MSB, 0}, + {REG_FRAME_LENGTH_LINES_LSB, 0}, + {REG_LINE_LENGTH_PCK_MSB, + s5k3e2fx_reg_pat[rt].line_length_pck_msb}, + {REG_LINE_LENGTH_PCK_LSB, + s5k3e2fx_reg_pat[rt].line_length_pck_lsb}, + {REG_SHADE_CLK_ENABLE, + s5k3e2fx_reg_pat[rt].shade_clk_enable}, + {REG_SEL_CCP, s5k3e2fx_reg_pat[rt].sel_ccp}, + {REG_VPIX, s5k3e2fx_reg_pat[rt].vpix}, + {REG_CLAMP_ON, s5k3e2fx_reg_pat[rt].clamp_on}, + {REG_OFFSET, s5k3e2fx_reg_pat[rt].offset}, + {REG_LD_START, s5k3e2fx_reg_pat[rt].ld_start}, + {REG_LD_END, s5k3e2fx_reg_pat[rt].ld_end}, + {REG_SL_START, s5k3e2fx_reg_pat[rt].sl_start}, + {REG_SL_END, s5k3e2fx_reg_pat[rt].sl_end}, + {REG_RX_START, s5k3e2fx_reg_pat[rt].rx_start}, + {REG_S1_START, s5k3e2fx_reg_pat[rt].s1_start}, + {REG_S1_END, s5k3e2fx_reg_pat[rt].s1_end}, + {REG_S1S_START, s5k3e2fx_reg_pat[rt].s1s_start}, + {REG_S1S_END, s5k3e2fx_reg_pat[rt].s1s_end}, + {REG_S3_START, s5k3e2fx_reg_pat[rt].s3_start}, + {REG_S3_END, s5k3e2fx_reg_pat[rt].s3_end}, + {REG_CMP_EN_START, s5k3e2fx_reg_pat[rt].cmp_en_start}, + {REG_CLP_SL_START, s5k3e2fx_reg_pat[rt].clp_sl_start}, + {REG_CLP_SL_END, s5k3e2fx_reg_pat[rt].clp_sl_end}, + {REG_OFF_START, s5k3e2fx_reg_pat[rt].off_start}, + {REG_RMP_EN_START, s5k3e2fx_reg_pat[rt].rmp_en_start}, + {REG_TX_START, s5k3e2fx_reg_pat[rt].tx_start}, + {REG_TX_END, s5k3e2fx_reg_pat[rt].tx_end}, + {REG_STX_WIDTH, s5k3e2fx_reg_pat[rt].stx_width}, + {REG_3152_RESERVED, + s5k3e2fx_reg_pat[rt].reg_3152_reserved}, + {REG_315A_RESERVED, + s5k3e2fx_reg_pat[rt].reg_315A_reserved}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_MSB, + s5k3e2fx_reg_pat[rt]. + analogue_gain_code_global_msb}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LSB, + s5k3e2fx_reg_pat[rt]. + analogue_gain_code_global_lsb}, + {REG_FINE_INTEGRATION_TIME, + s5k3e2fx_reg_pat[rt].fine_integration_time}, + {REG_COARSE_INTEGRATION_TIME, + s5k3e2fx_reg_pat[rt].coarse_integration_time}, + {S5K3E2FX_REG_MODE_SELECT, S5K3E2FX_MODE_SELECT_STREAM}, + }; + + rc = s5k3e2fx_i2c_write_table(&tbl_1[0], + ARRAY_SIZE(tbl_1)); + if (rc < 0) + return rc; + + num_lperf = (uint16_t) + ((s5k3e2fx_reg_pat[rt].frame_length_lines_msb << 8) + & 0xFF00) + + s5k3e2fx_reg_pat[rt].frame_length_lines_lsb; + + num_lperf = num_lperf * s5k3e2fx_ctrl->fps_divider / 0x0400; + + tbl_2[0] = (struct s5k3e2fx_i2c_reg_conf) + {REG_FRAME_LENGTH_LINES_MSB, (num_lperf & 0xFF00) >> 8}; + tbl_2[1] = (struct s5k3e2fx_i2c_reg_conf) + {REG_FRAME_LENGTH_LINES_LSB, (num_lperf & 0x00FF)}; + + rc = s5k3e2fx_i2c_write_table(&tbl_2[0], + ARRAY_SIZE(tbl_2)); + if (rc < 0) + return rc; + + mdelay(5); + + rc = s5k3e2fx_test(s5k3e2fx_ctrl->set_test); + if (rc < 0) + return rc; + } + break; /* UPDATE_PERIODIC */ + + case S_REG_INIT: + if (rt == S_RES_PREVIEW || rt == S_RES_CAPTURE) { + + struct s5k3e2fx_i2c_reg_conf tbl_3[] = { + {S5K3E2FX_REG_SOFTWARE_RESET, S5K3E2FX_SOFTWARE_RESET}, + {S5K3E2FX_REG_MODE_SELECT, + S5K3E2FX_MODE_SELECT_SW_STANDBY}, + /* PLL setting */ + {REG_PRE_PLL_CLK_DIV, + s5k3e2fx_reg_pat[rt].pre_pll_clk_div}, + {REG_PLL_MULTIPLIER_MSB, + s5k3e2fx_reg_pat[rt].pll_multiplier_msb}, + {REG_PLL_MULTIPLIER_LSB, + s5k3e2fx_reg_pat[rt].pll_multiplier_lsb}, + {REG_VT_PIX_CLK_DIV, + s5k3e2fx_reg_pat[rt].vt_pix_clk_div}, + {REG_VT_SYS_CLK_DIV, + s5k3e2fx_reg_pat[rt].vt_sys_clk_div}, + {REG_OP_PIX_CLK_DIV, + s5k3e2fx_reg_pat[rt].op_pix_clk_div}, + {REG_OP_SYS_CLK_DIV, + s5k3e2fx_reg_pat[rt].op_sys_clk_div}, + /*Data Format */ + {REG_CCP_DATA_FORMAT_MSB, + s5k3e2fx_reg_pat[rt].ccp_data_format_msb}, + {REG_CCP_DATA_FORMAT_LSB, + s5k3e2fx_reg_pat[rt].ccp_data_format_lsb}, + /*Output Size */ + {REG_X_OUTPUT_SIZE_MSB, + s5k3e2fx_reg_pat[rt].x_output_size_msb}, + {REG_X_OUTPUT_SIZE_LSB, + s5k3e2fx_reg_pat[rt].x_output_size_lsb}, + {REG_Y_OUTPUT_SIZE_MSB, + s5k3e2fx_reg_pat[rt].y_output_size_msb}, + {REG_Y_OUTPUT_SIZE_LSB, + s5k3e2fx_reg_pat[rt].y_output_size_lsb}, + /* Binning */ + {REG_X_EVEN_INC, s5k3e2fx_reg_pat[rt].x_even_inc}, + {REG_X_ODD_INC, s5k3e2fx_reg_pat[rt].x_odd_inc }, + {REG_Y_EVEN_INC, s5k3e2fx_reg_pat[rt].y_even_inc}, + {REG_Y_ODD_INC, s5k3e2fx_reg_pat[rt].y_odd_inc}, + {REG_BINNING_ENABLE, + s5k3e2fx_reg_pat[rt].binning_enable}, + /* Frame format */ + {REG_FRAME_LENGTH_LINES_MSB, + s5k3e2fx_reg_pat[rt].frame_length_lines_msb}, + {REG_FRAME_LENGTH_LINES_LSB, + s5k3e2fx_reg_pat[rt].frame_length_lines_lsb}, + {REG_LINE_LENGTH_PCK_MSB, + s5k3e2fx_reg_pat[rt].line_length_pck_msb}, + {REG_LINE_LENGTH_PCK_LSB, + s5k3e2fx_reg_pat[rt].line_length_pck_lsb}, + /* MSR setting */ + {REG_SHADE_CLK_ENABLE, + s5k3e2fx_reg_pat[rt].shade_clk_enable}, + {REG_SEL_CCP, s5k3e2fx_reg_pat[rt].sel_ccp}, + {REG_VPIX, s5k3e2fx_reg_pat[rt].vpix}, + {REG_CLAMP_ON, s5k3e2fx_reg_pat[rt].clamp_on}, + {REG_OFFSET, s5k3e2fx_reg_pat[rt].offset}, + /* CDS timing setting */ + {REG_LD_START, s5k3e2fx_reg_pat[rt].ld_start}, + {REG_LD_END, s5k3e2fx_reg_pat[rt].ld_end}, + {REG_SL_START, s5k3e2fx_reg_pat[rt].sl_start}, + {REG_SL_END, s5k3e2fx_reg_pat[rt].sl_end}, + {REG_RX_START, s5k3e2fx_reg_pat[rt].rx_start}, + {REG_S1_START, s5k3e2fx_reg_pat[rt].s1_start}, + {REG_S1_END, s5k3e2fx_reg_pat[rt].s1_end}, + {REG_S1S_START, s5k3e2fx_reg_pat[rt].s1s_start}, + {REG_S1S_END, s5k3e2fx_reg_pat[rt].s1s_end}, + {REG_S3_START, s5k3e2fx_reg_pat[rt].s3_start}, + {REG_S3_END, s5k3e2fx_reg_pat[rt].s3_end}, + {REG_CMP_EN_START, s5k3e2fx_reg_pat[rt].cmp_en_start}, + {REG_CLP_SL_START, s5k3e2fx_reg_pat[rt].clp_sl_start}, + {REG_CLP_SL_END, s5k3e2fx_reg_pat[rt].clp_sl_end}, + {REG_OFF_START, s5k3e2fx_reg_pat[rt].off_start}, + {REG_RMP_EN_START, s5k3e2fx_reg_pat[rt].rmp_en_start}, + {REG_TX_START, s5k3e2fx_reg_pat[rt].tx_start}, + {REG_TX_END, s5k3e2fx_reg_pat[rt].tx_end}, + {REG_STX_WIDTH, s5k3e2fx_reg_pat[rt].stx_width}, + {REG_3152_RESERVED, + s5k3e2fx_reg_pat[rt].reg_3152_reserved}, + {REG_315A_RESERVED, + s5k3e2fx_reg_pat[rt].reg_315A_reserved}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_MSB, + s5k3e2fx_reg_pat[rt]. + analogue_gain_code_global_msb}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LSB, + s5k3e2fx_reg_pat[rt]. + analogue_gain_code_global_lsb}, + {REG_FINE_INTEGRATION_TIME, + s5k3e2fx_reg_pat[rt].fine_integration_time}, + {REG_COARSE_INTEGRATION_TIME, + s5k3e2fx_reg_pat[rt].coarse_integration_time}, + {S5K3E2FX_REG_MODE_SELECT, S5K3E2FX_MODE_SELECT_STREAM}, + }; + + /* reset fps_divider */ + s5k3e2fx_ctrl->fps_divider = 1 * 0x0400; + rc = s5k3e2fx_i2c_write_table(&tbl_3[0], + ARRAY_SIZE(tbl_3)); + if (rc < 0) + return rc; + } + break; /* case REG_INIT: */ + + default: + rc = -EINVAL; + break; + } /* switch (rupdate) */ + + return rc; +} + +static int s5k3e2fx_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + + s5k3e2fx_ctrl = kzalloc(sizeof(struct s5k3e2fx_ctrl), GFP_KERNEL); + if (!s5k3e2fx_ctrl) { + CDBG("s5k3e2fx_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + + s5k3e2fx_ctrl->fps_divider = 1 * 0x00000400; + s5k3e2fx_ctrl->pict_fps_divider = 1 * 0x00000400; + s5k3e2fx_ctrl->set_test = S_TEST_OFF; + s5k3e2fx_ctrl->prev_res = S_QTR_SIZE; + s5k3e2fx_ctrl->pict_res = S_FULL_SIZE; + + if (data) + s5k3e2fx_ctrl->sensordata = data; + + /* enable mclk first */ + msm_camio_clk_rate_set(24000000); + mdelay(20); + + msm_camio_camif_pad_reg_reset(); + mdelay(20); + + rc = s5k3e2fx_probe_init_sensor(data); + if (rc < 0) + goto init_fail1; + + if (s5k3e2fx_ctrl->prev_res == S_QTR_SIZE) + rc = s5k3e2fx_setting(S_REG_INIT, S_RES_PREVIEW); + else + rc = s5k3e2fx_setting(S_REG_INIT, S_RES_CAPTURE); + + if (rc < 0) { + CDBG("s5k3e2fx_setting failed. rc = %d\n", rc); + goto init_fail1; + } + + /* initialize AF */ + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + 0x3146, 0x3A); + if (rc < 0) + goto init_fail1; + + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + 0x3130, 0x03); + if (rc < 0) + goto init_fail1; + + goto init_done; + +init_fail1: + s5k3e2fx_probe_init_done(data); + kfree(s5k3e2fx_ctrl); +init_done: + return rc; +} + +static int32_t s5k3e2fx_power_down(void) +{ + int32_t rc = 0; + return rc; +} + +static int s5k3e2fx_sensor_release(void) +{ + int rc = -EBADF; + + mutex_lock(&s5k3e2fx_mutex); + + s5k3e2fx_power_down(); + + gpio_direction_output(s5k3e2fx_ctrl->sensordata->sensor_reset, + 0); + gpio_free(s5k3e2fx_ctrl->sensordata->sensor_reset); + + kfree(s5k3e2fx_ctrl); + s5k3e2fx_ctrl = NULL; + + CDBG("s5k3e2fx_release completed\n"); + + mutex_unlock(&s5k3e2fx_mutex); + return rc; +} + +static void s5k3e2fx_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider; /* Q10 */ + + divider = (uint32_t) + ((s5k3e2fx_reg_pat[S_RES_PREVIEW].size_h + + s5k3e2fx_reg_pat[S_RES_PREVIEW].blk_l) * + (s5k3e2fx_reg_pat[S_RES_PREVIEW].size_w + + s5k3e2fx_reg_pat[S_RES_PREVIEW].blk_p)) * 0x00000400 / + ((s5k3e2fx_reg_pat[S_RES_CAPTURE].size_h + + s5k3e2fx_reg_pat[S_RES_CAPTURE].blk_l) * + (s5k3e2fx_reg_pat[S_RES_CAPTURE].size_w + + s5k3e2fx_reg_pat[S_RES_CAPTURE].blk_p)); + + /* Verify PCLK settings and frame sizes. */ + *pfps = (uint16_t)(fps * divider / 0x00000400); +} + +static uint16_t s5k3e2fx_get_prev_lines_pf(void) +{ + return s5k3e2fx_reg_pat[S_RES_PREVIEW].size_h + + s5k3e2fx_reg_pat[S_RES_PREVIEW].blk_l; +} + +static uint16_t s5k3e2fx_get_prev_pixels_pl(void) +{ + return s5k3e2fx_reg_pat[S_RES_PREVIEW].size_w + + s5k3e2fx_reg_pat[S_RES_PREVIEW].blk_p; +} + +static uint16_t s5k3e2fx_get_pict_lines_pf(void) +{ + return s5k3e2fx_reg_pat[S_RES_CAPTURE].size_h + + s5k3e2fx_reg_pat[S_RES_CAPTURE].blk_l; +} + +static uint16_t s5k3e2fx_get_pict_pixels_pl(void) +{ + return s5k3e2fx_reg_pat[S_RES_CAPTURE].size_w + + s5k3e2fx_reg_pat[S_RES_CAPTURE].blk_p; +} + +static uint32_t s5k3e2fx_get_pict_max_exp_lc(void) +{ + uint32_t snapshot_lines_per_frame; + + if (s5k3e2fx_ctrl->pict_res == S_QTR_SIZE) + snapshot_lines_per_frame = + s5k3e2fx_reg_pat[S_RES_PREVIEW].size_h + + s5k3e2fx_reg_pat[S_RES_PREVIEW].blk_l; + else + snapshot_lines_per_frame = 3961 * 3; + + return snapshot_lines_per_frame; +} + +static int32_t s5k3e2fx_set_fps(struct fps_cfg *fps) +{ + /* input is new fps in Q10 format */ + int32_t rc = 0; + enum msm_s_setting setting; + + s5k3e2fx_ctrl->fps_divider = fps->fps_div; + + if (s5k3e2fx_ctrl->sensormode == SENSOR_PREVIEW_MODE) + setting = S_RES_PREVIEW; + else + setting = S_RES_CAPTURE; + + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + REG_FRAME_LENGTH_LINES_MSB, + (((s5k3e2fx_reg_pat[setting].size_h + + s5k3e2fx_reg_pat[setting].blk_l) * + s5k3e2fx_ctrl->fps_divider / 0x400) & 0xFF00) >> 8); + if (rc < 0) + goto set_fps_done; + + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + REG_FRAME_LENGTH_LINES_LSB, + (((s5k3e2fx_reg_pat[setting].size_h + + s5k3e2fx_reg_pat[setting].blk_l) * + s5k3e2fx_ctrl->fps_divider / 0x400) & 0x00FF)); + +set_fps_done: + return rc; +} + +static int32_t s5k3e2fx_write_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + + uint16_t max_legal_gain = 0x0200; + uint32_t ll_ratio; /* Q10 */ + uint32_t ll_pck, fl_lines; + uint16_t offset = 4; + uint32_t gain_msb, gain_lsb; + uint32_t intg_t_msb, intg_t_lsb; + uint32_t ll_pck_msb, ll_pck_lsb; + + struct s5k3e2fx_i2c_reg_conf tbl[2]; + + CDBG("Line:%d s5k3e2fx_write_exp_gain \n", __LINE__); + + if (s5k3e2fx_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + + s5k3e2fx_ctrl->my_reg_gain = gain; + s5k3e2fx_ctrl->my_reg_line_count = (uint16_t)line; + + fl_lines = s5k3e2fx_reg_pat[S_RES_PREVIEW].size_h + + s5k3e2fx_reg_pat[S_RES_PREVIEW].blk_l; + + ll_pck = s5k3e2fx_reg_pat[S_RES_PREVIEW].size_w + + s5k3e2fx_reg_pat[S_RES_PREVIEW].blk_p; + + } else { + + fl_lines = s5k3e2fx_reg_pat[S_RES_CAPTURE].size_h + + s5k3e2fx_reg_pat[S_RES_CAPTURE].blk_l; + + ll_pck = s5k3e2fx_reg_pat[S_RES_CAPTURE].size_w + + s5k3e2fx_reg_pat[S_RES_CAPTURE].blk_p; + } + + if (gain > max_legal_gain) + gain = max_legal_gain; + + /* in Q10 */ + line = (line * s5k3e2fx_ctrl->fps_divider); + + if (fl_lines < (line / 0x400)) + ll_ratio = (line / (fl_lines - offset)); + else + ll_ratio = 0x400; + + /* update gain registers */ + gain_msb = (gain & 0xFF00) >> 8; + gain_lsb = gain & 0x00FF; + tbl[0].waddr = REG_ANALOGUE_GAIN_CODE_GLOBAL_MSB; + tbl[0].bdata = gain_msb; + tbl[1].waddr = REG_ANALOGUE_GAIN_CODE_GLOBAL_LSB; + tbl[1].bdata = gain_lsb; + rc = s5k3e2fx_i2c_write_table(&tbl[0], ARRAY_SIZE(tbl)); + if (rc < 0) + goto write_gain_done; + + ll_pck = ll_pck * ll_ratio; + ll_pck_msb = ((ll_pck / 0x400) & 0xFF00) >> 8; + ll_pck_lsb = (ll_pck / 0x400) & 0x00FF; + tbl[0].waddr = REG_LINE_LENGTH_PCK_MSB; + tbl[0].bdata = ll_pck_msb; + tbl[1].waddr = REG_LINE_LENGTH_PCK_LSB; + tbl[1].bdata = ll_pck_lsb; + rc = s5k3e2fx_i2c_write_table(&tbl[0], ARRAY_SIZE(tbl)); + if (rc < 0) + goto write_gain_done; + + line = line / ll_ratio; + intg_t_msb = (line & 0xFF00) >> 8; + intg_t_lsb = (line & 0x00FF); + tbl[0].waddr = REG_COARSE_INTEGRATION_TIME; + tbl[0].bdata = intg_t_msb; + tbl[1].waddr = REG_COARSE_INTEGRATION_TIME_LSB; + tbl[1].bdata = intg_t_lsb; + rc = s5k3e2fx_i2c_write_table(&tbl[0], ARRAY_SIZE(tbl)); + +write_gain_done: + return rc; +} + +static int32_t s5k3e2fx_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + + CDBG("Line:%d s5k3e2fx_set_pict_exp_gain \n", __LINE__); + + rc = + s5k3e2fx_write_exp_gain(gain, line); + + return rc; +} + +static int32_t s5k3e2fx_video_config(int mode, int res) +{ + int32_t rc; + + switch (res) { + case S_QTR_SIZE: + rc = s5k3e2fx_setting(S_UPDATE_PERIODIC, S_RES_PREVIEW); + if (rc < 0) + return rc; + + CDBG("s5k3e2fx sensor configuration done!\n"); + break; + + case S_FULL_SIZE: + rc = s5k3e2fx_setting(S_UPDATE_PERIODIC, S_RES_CAPTURE); + if (rc < 0) + return rc; + + break; + + default: + return 0; + } /* switch */ + + s5k3e2fx_ctrl->prev_res = res; + s5k3e2fx_ctrl->curr_res = res; + s5k3e2fx_ctrl->sensormode = mode; + + rc = + s5k3e2fx_write_exp_gain(s5k3e2fx_ctrl->my_reg_gain, + s5k3e2fx_ctrl->my_reg_line_count); + + return rc; +} + +static int32_t s5k3e2fx_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = s5k3e2fx_setting(S_UPDATE_PERIODIC, S_RES_CAPTURE); + if (rc < 0) + return rc; + + s5k3e2fx_ctrl->curr_res = s5k3e2fx_ctrl->pict_res; + s5k3e2fx_ctrl->sensormode = mode; + + return rc; +} + +static int32_t s5k3e2fx_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + + rc = s5k3e2fx_setting(S_UPDATE_PERIODIC, S_RES_CAPTURE); + if (rc < 0) + return rc; + + s5k3e2fx_ctrl->curr_res = s5k3e2fx_ctrl->pict_res; + s5k3e2fx_ctrl->sensormode = mode; + + return rc; +} + +static int32_t s5k3e2fx_set_sensor_mode(int mode, int res) +{ + int32_t rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = s5k3e2fx_video_config(mode, res); + break; + + case SENSOR_SNAPSHOT_MODE: + rc = s5k3e2fx_snapshot_config(mode); + break; + + case SENSOR_RAW_SNAPSHOT_MODE: + rc = s5k3e2fx_raw_snapshot_config(mode); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static int32_t s5k3e2fx_set_default_focus(void) +{ + int32_t rc = 0; + + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + 0x3131, 0); + if (rc < 0) + return rc; + + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + 0x3132, 0); + if (rc < 0) + return rc; + + s5k3e2fx_ctrl->curr_lens_pos = 0; + + return rc; +} + +static int32_t s5k3e2fx_move_focus(int direction, int32_t num_steps) +{ + int32_t rc = 0; + int32_t i; + int16_t step_direction; + int16_t actual_step; + int16_t next_pos, pos_offset; + int16_t init_code = 50; + uint8_t next_pos_msb, next_pos_lsb; + int16_t s_move[5]; + uint32_t gain; /* Q10 format */ + + if (direction == MOVE_NEAR) + step_direction = 20; + else if (direction == MOVE_FAR) + step_direction = -20; + else { + CDBG("s5k3e2fx_move_focus failed at line %d ...\n", __LINE__); + return -EINVAL; + } + + actual_step = step_direction * (int16_t)num_steps; + pos_offset = init_code + s5k3e2fx_ctrl->curr_lens_pos; + gain = actual_step * 0x400 / 5; + + for (i = 0; i <= 4; i++) { + if (actual_step >= 0) + s_move[i] = (((i+1)*gain+0x200)-(i*gain+0x200))/0x400; + else + s_move[i] = (((i+1)*gain-0x200)-(i*gain-0x200))/0x400; + } + + /* Ring Damping Code */ + for (i = 0; i <= 4; i++) { + next_pos = (int16_t)(pos_offset + s_move[i]); + + if (next_pos > (738 + init_code)) + next_pos = 738 + init_code; + else if (next_pos < 0) + next_pos = 0; + + CDBG("next_position in damping mode = %d\n", next_pos); + /* Writing the Values to the actuator */ + if (next_pos == init_code) + next_pos = 0x00; + + next_pos_msb = next_pos >> 8; + next_pos_lsb = next_pos & 0x00FF; + + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + 0x3131, next_pos_msb); + if (rc < 0) + break; + + rc = s5k3e2fx_i2c_write_b(s5k3e2fx_client->addr, + 0x3132, next_pos_lsb); + if (rc < 0) + break; + + pos_offset = next_pos; + s5k3e2fx_ctrl->curr_lens_pos = pos_offset - init_code; + if (i < 4) + mdelay(3); + } + + return rc; +} + +static int s5k3e2fx_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + + mutex_lock(&s5k3e2fx_mutex); + + CDBG("%s: cfgtype = %d\n", __func__, cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + s5k3e2fx_get_pict_fps(cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = s5k3e2fx_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = s5k3e2fx_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = s5k3e2fx_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = s5k3e2fx_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + s5k3e2fx_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = s5k3e2fx_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = + s5k3e2fx_write_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + CDBG("Line:%d CFG_SET_PICT_EXP_GAIN \n", __LINE__); + rc = + s5k3e2fx_set_pict_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = + s5k3e2fx_set_sensor_mode( + cdata.mode, cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = s5k3e2fx_power_down(); + break; + + case CFG_MOVE_FOCUS: + rc = + s5k3e2fx_move_focus( + cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = + s5k3e2fx_set_default_focus(); + break; + + case CFG_GET_AF_MAX_STEPS: + case CFG_SET_EFFECT: + case CFG_SET_LENS_SHADING: + default: + rc = -EINVAL; + break; + } + + mutex_unlock(&s5k3e2fx_mutex); + return rc; +} + +static int s5k3e2fx_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + + rc = i2c_add_driver(&s5k3e2fx_i2c_driver); + if (rc < 0 || s5k3e2fx_client == NULL) { + rc = -ENOTSUPP; + goto probe_fail; + } + + msm_camio_clk_rate_set(24000000); + mdelay(20); + + rc = s5k3e2fx_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + + s->s_init = s5k3e2fx_sensor_open_init; + s->s_release = s5k3e2fx_sensor_release; + s->s_config = s5k3e2fx_sensor_config; + s->s_mount_angle = 0; + s5k3e2fx_probe_init_done(info); + + return rc; + +probe_fail: + CDBG("SENSOR PROBE FAILS!\n"); + return rc; +} + +static int __s5k3e2fx_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, s5k3e2fx_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __s5k3e2fx_probe, + .driver = { + .name = "msm_camera_s5k3e2fx", + .owner = THIS_MODULE, + }, +}; + +static int __init s5k3e2fx_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(s5k3e2fx_init); + diff --git a/drivers/media/video/msm/s5k3e2fx.h b/drivers/media/video/msm/s5k3e2fx.h new file mode 100644 index 00000000000..cf3f88140c4 --- /dev/null +++ b/drivers/media/video/msm/s5k3e2fx.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef CAMSENSOR_S5K3E2FX +#define CAMSENSOR_S5K3E2FX + +#include +#endif /* CAMSENSOR_S5K3E2FX */ diff --git a/drivers/media/video/msm/s5k4e1.c b/drivers/media/video/msm/s5k4e1.c new file mode 100644 index 00000000000..9cdd44cc29d --- /dev/null +++ b/drivers/media/video/msm/s5k4e1.c @@ -0,0 +1,1086 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "s5k4e1.h" + +/* 16bit address - 8 bit context register structure */ +#define Q8 0x00000100 +#define Q10 0x00000400 + +/* MCLK */ +#define S5K4E1_MASTER_CLK_RATE 24000000 + +/* AF Total steps parameters */ +#define S5K4E1_TOTAL_STEPS_NEAR_TO_FAR 32 + +#define S5K4E1_REG_PREV_FRAME_LEN_1 31 +#define S5K4E1_REG_PREV_FRAME_LEN_2 32 +#define S5K4E1_REG_PREV_LINE_LEN_1 33 +#define S5K4E1_REG_PREV_LINE_LEN_2 34 + +#define S5K4E1_REG_SNAP_FRAME_LEN_1 15 +#define S5K4E1_REG_SNAP_FRAME_LEN_2 16 +#define S5K4E1_REG_SNAP_LINE_LEN_1 17 +#define S5K4E1_REG_SNAP_LINE_LEN_2 18 +#define MSB 1 +#define LSB 0 + +struct s5k4e1_work_t { + struct work_struct work; +}; + +static struct s5k4e1_work_t *s5k4e1_sensorw; +static struct s5k4e1_work_t *s5k4e1_af_sensorw; +static struct i2c_client *s5k4e1_af_client; +static struct i2c_client *s5k4e1_client; + +struct s5k4e1_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + + uint32_t sensormode; + uint32_t fps_divider;/* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider;/* init to 1 * 0x00000400 */ + uint16_t fps; + + uint16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + + enum s5k4e1_resolution_t prev_res; + enum s5k4e1_resolution_t pict_res; + enum s5k4e1_resolution_t curr_res; + enum s5k4e1_test_mode_t set_test; +}; + +static bool CSI_CONFIG; +static struct s5k4e1_ctrl_t *s5k4e1_ctrl; + +static DECLARE_WAIT_QUEUE_HEAD(s5k4e1_wait_queue); +static DECLARE_WAIT_QUEUE_HEAD(s5k4e1_af_wait_queue); +DEFINE_MUTEX(s5k4e1_mut); + +static uint16_t prev_line_length_pck; +static uint16_t prev_frame_length_lines; +static uint16_t snap_line_length_pck; +static uint16_t snap_frame_length_lines; + +static int s5k4e1_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 1, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 1, + .buf = rxdata, + }, + }; + if (i2c_transfer(s5k4e1_client->adapter, msgs, 2) < 0) { + CDBG("s5k4e1_i2c_rxdata faild 0x%x\n", saddr); + return -EIO; + } + return 0; +} + +static int32_t s5k4e1_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(s5k4e1_client->adapter, msg, 1) < 0) { + CDBG("s5k4e1_i2c_txdata faild 0x%x\n", saddr); + return -EIO; + } + + return 0; +} + +static int32_t s5k4e1_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = s5k4e1_i2c_rxdata(s5k4e1_client->addr, buf, rlen); + if (rc < 0) { + CDBG("s5k4e1_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + CDBG("s5k4e1_i2c_read 0x%x val = 0x%x!\n", raddr, *rdata); + + return rc; +} + +static int32_t s5k4e1_i2c_write_b_sensor(unsigned short waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = s5k4e1_i2c_txdata(s5k4e1_client->addr, buf, 3); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + return rc; +} + +static int32_t s5k4e1_i2c_write_b_table(struct s5k4e1_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + + for (i = 0; i < num; i++) { + rc = s5k4e1_i2c_write_b_sensor(reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +static int32_t s5k4e1_af_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(s5k4e1_af_client->adapter, msg, 1) < 0) { + pr_err("s5k4e1_af_i2c_txdata faild 0x%x\n", saddr); + return -EIO; + } + + return 0; +} + +static int32_t s5k4e1_af_i2c_write_b_sensor(uint8_t waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[2]; + + memset(buf, 0, sizeof(buf)); + buf[0] = waddr; + buf[1] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = s5k4e1_af_i2c_txdata(s5k4e1_af_client->addr << 1, buf, 2); + if (rc < 0) { + pr_err("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + return rc; +} + +static void s5k4e1_start_stream(void) +{ + s5k4e1_i2c_write_b_sensor(0x0100, 0x01);/* streaming on */ +} + +static void s5k4e1_stop_stream(void) +{ + s5k4e1_i2c_write_b_sensor(0x0100, 0x00);/* streaming off */ +} + +static void s5k4e1_group_hold_on(void) +{ + s5k4e1_i2c_write_b_sensor(0x0104, 0x01); +} + +static void s5k4e1_group_hold_off(void) +{ + s5k4e1_i2c_write_b_sensor(0x0104, 0x0); +} + +static void s5k4e1_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider, d1, d2; + + d1 = (prev_frame_length_lines * 0x00000400) / snap_frame_length_lines; + d2 = (prev_line_length_pck * 0x00000400) / snap_line_length_pck; + divider = (d1 * d2) / 0x400; + + /*Verify PCLK settings and frame sizes.*/ + *pfps = (uint16_t) (fps * divider / 0x400); +} + +static uint16_t s5k4e1_get_prev_lines_pf(void) +{ + if (s5k4e1_ctrl->prev_res == QTR_SIZE) + return prev_frame_length_lines; + else + return snap_frame_length_lines; +} + +static uint16_t s5k4e1_get_prev_pixels_pl(void) +{ + if (s5k4e1_ctrl->prev_res == QTR_SIZE) + return prev_line_length_pck; + else + return snap_line_length_pck; +} + +static uint16_t s5k4e1_get_pict_lines_pf(void) +{ + if (s5k4e1_ctrl->pict_res == QTR_SIZE) + return prev_frame_length_lines; + else + return snap_frame_length_lines; +} + +static uint16_t s5k4e1_get_pict_pixels_pl(void) +{ + if (s5k4e1_ctrl->pict_res == QTR_SIZE) + return prev_line_length_pck; + else + return snap_line_length_pck; +} + +static uint32_t s5k4e1_get_pict_max_exp_lc(void) +{ + return snap_frame_length_lines * 24; +} + +static int32_t s5k4e1_set_fps(struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + + s5k4e1_ctrl->fps_divider = fps->fps_div; + s5k4e1_ctrl->pict_fps_divider = fps->pict_fps_div; + + if (s5k4e1_ctrl->sensormode == SENSOR_PREVIEW_MODE) { + total_lines_per_frame = (uint16_t) + ((prev_frame_length_lines * s5k4e1_ctrl->fps_divider) / 0x400); + } else { + total_lines_per_frame = (uint16_t) + ((snap_frame_length_lines * s5k4e1_ctrl->fps_divider) / 0x400); + } + + s5k4e1_group_hold_on(); + rc = s5k4e1_i2c_write_b_sensor(0x0340, + ((total_lines_per_frame & 0xFF00) >> 8)); + rc = s5k4e1_i2c_write_b_sensor(0x0341, + (total_lines_per_frame & 0x00FF)); + s5k4e1_group_hold_off(); + + return rc; +} + +static inline uint8_t s5k4e1_byte(uint16_t word, uint8_t offset) +{ + return word >> (offset * BITS_PER_BYTE); +} + +static int32_t s5k4e1_write_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t max_legal_gain = 0x0200; + int32_t rc = 0; + static uint32_t fl_lines; + + if (gain > max_legal_gain) { + pr_debug("Max legal gain Line:%d\n", __LINE__); + gain = max_legal_gain; + } + /* Analogue Gain */ + s5k4e1_i2c_write_b_sensor(0x0204, s5k4e1_byte(gain, MSB)); + s5k4e1_i2c_write_b_sensor(0x0205, s5k4e1_byte(gain, LSB)); + + if (line > (prev_frame_length_lines - 4)) { + fl_lines = line+4; + s5k4e1_group_hold_on(); + s5k4e1_i2c_write_b_sensor(0x0340, s5k4e1_byte(fl_lines, MSB)); + s5k4e1_i2c_write_b_sensor(0x0341, s5k4e1_byte(fl_lines, LSB)); + /* Coarse Integration Time */ + s5k4e1_i2c_write_b_sensor(0x0202, s5k4e1_byte(line, MSB)); + s5k4e1_i2c_write_b_sensor(0x0203, s5k4e1_byte(line, LSB)); + s5k4e1_group_hold_off(); + } else if (line < (fl_lines - 4)) { + fl_lines = line+4; + if (fl_lines < prev_frame_length_lines) + fl_lines = prev_frame_length_lines; + + s5k4e1_group_hold_on(); + /* Coarse Integration Time */ + s5k4e1_i2c_write_b_sensor(0x0202, s5k4e1_byte(line, MSB)); + s5k4e1_i2c_write_b_sensor(0x0203, s5k4e1_byte(line, LSB)); + s5k4e1_i2c_write_b_sensor(0x0340, s5k4e1_byte(fl_lines, MSB)); + s5k4e1_i2c_write_b_sensor(0x0341, s5k4e1_byte(fl_lines, LSB)); + s5k4e1_group_hold_off(); + } else { + fl_lines = line+4; + s5k4e1_group_hold_on(); + /* Coarse Integration Time */ + s5k4e1_i2c_write_b_sensor(0x0202, s5k4e1_byte(line, MSB)); + s5k4e1_i2c_write_b_sensor(0x0203, s5k4e1_byte(line, LSB)); + s5k4e1_group_hold_off(); + } + return rc; +} + +static int32_t s5k4e1_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t max_legal_gain = 0x0200; + uint16_t min_ll_pck = 0x0AB2; + uint32_t ll_pck, fl_lines; + uint32_t ll_ratio; + int32_t rc = 0; + uint8_t gain_msb, gain_lsb; + uint8_t intg_time_msb, intg_time_lsb; + uint8_t ll_pck_msb, ll_pck_lsb; + + if (gain > max_legal_gain) { + pr_debug("Max legal gain Line:%d\n", __LINE__); + gain = max_legal_gain; + } + + pr_debug("s5k4e1_write_exp_gain : gain = %d line = %d\n", gain, line); + line = (uint32_t) (line * s5k4e1_ctrl->pict_fps_divider); + fl_lines = snap_frame_length_lines; + ll_pck = snap_line_length_pck; + + if (fl_lines < (line / 0x400)) + ll_ratio = (line / (fl_lines - 4)); + else + ll_ratio = 0x400; + + ll_pck = ll_pck * ll_ratio / 0x400; + line = line / ll_ratio; + if (ll_pck < min_ll_pck) + ll_pck = min_ll_pck; + + gain_msb = (uint8_t) ((gain & 0xFF00) >> 8); + gain_lsb = (uint8_t) (gain & 0x00FF); + + intg_time_msb = (uint8_t) ((line & 0xFF00) >> 8); + intg_time_lsb = (uint8_t) (line & 0x00FF); + + ll_pck_msb = (uint8_t) ((ll_pck & 0xFF00) >> 8); + ll_pck_lsb = (uint8_t) (ll_pck & 0x00FF); + + s5k4e1_group_hold_on(); + s5k4e1_i2c_write_b_sensor(0x0204, gain_msb); /* Analogue Gain */ + s5k4e1_i2c_write_b_sensor(0x0205, gain_lsb); + + s5k4e1_i2c_write_b_sensor(0x0342, ll_pck_msb); + s5k4e1_i2c_write_b_sensor(0x0343, ll_pck_lsb); + + /* Coarse Integration Time */ + s5k4e1_i2c_write_b_sensor(0x0202, intg_time_msb); + s5k4e1_i2c_write_b_sensor(0x0203, intg_time_lsb); + s5k4e1_group_hold_off(); + + return rc; +} + +static int32_t s5k4e1_move_focus(int direction, + int32_t num_steps) +{ + int16_t step_direction, actual_step, next_position; + uint8_t code_val_msb, code_val_lsb; + + if (direction == MOVE_NEAR) + step_direction = 16; + else + step_direction = -16; + + actual_step = (int16_t) (step_direction * num_steps); + next_position = (int16_t) (s5k4e1_ctrl->curr_lens_pos + actual_step); + + if (next_position > 1023) + next_position = 1023; + else if (next_position < 0) + next_position = 0; + + code_val_msb = next_position >> 4; + code_val_lsb = (next_position & 0x000F) << 4; + + if (s5k4e1_af_i2c_write_b_sensor(code_val_msb, code_val_lsb) < 0) { + pr_err("move_focus failed at line %d ...\n", __LINE__); + return -EBUSY; + } + + s5k4e1_ctrl->curr_lens_pos = next_position; + return 0; +} + +static int32_t s5k4e1_set_default_focus(uint8_t af_step) +{ + int32_t rc = 0; + + if (s5k4e1_ctrl->curr_step_pos != 0) { + rc = s5k4e1_move_focus(MOVE_FAR, + s5k4e1_ctrl->curr_step_pos); + } else { + s5k4e1_af_i2c_write_b_sensor(0x00, 0x00); + } + + s5k4e1_ctrl->curr_lens_pos = 0; + s5k4e1_ctrl->curr_step_pos = 0; + + return rc; +} + +static int32_t s5k4e1_test(enum s5k4e1_test_mode_t mo) +{ + int32_t rc = 0; + + if (mo != TEST_OFF) + rc = s5k4e1_i2c_write_b_sensor(0x0601, (uint8_t) mo); + + return rc; +} + +static void s5k4e1_reset_sensor(void) +{ + s5k4e1_i2c_write_b_sensor(0x103, 0x1); +} + +static int32_t s5k4e1_sensor_setting(int update_type, int rt) +{ + + int32_t rc = 0; + struct msm_camera_csi_params s5k4e1_csi_params; + + s5k4e1_stop_stream(); + msleep(30); + + if (update_type == REG_INIT) { + s5k4e1_reset_sensor(); + s5k4e1_i2c_write_b_table(s5k4e1_regs.reg_mipi, + s5k4e1_regs.reg_mipi_size); + s5k4e1_i2c_write_b_table(s5k4e1_regs.rec_settings, + s5k4e1_regs.rec_size); + s5k4e1_i2c_write_b_table(s5k4e1_regs.reg_pll_p, + s5k4e1_regs.reg_pll_p_size); + CSI_CONFIG = 0; + } else if (update_type == UPDATE_PERIODIC) { + if (rt == RES_PREVIEW) + s5k4e1_i2c_write_b_table(s5k4e1_regs.reg_prev, + s5k4e1_regs.reg_prev_size); + else + s5k4e1_i2c_write_b_table(s5k4e1_regs.reg_snap, + s5k4e1_regs.reg_snap_size); + msleep(20); + if (!CSI_CONFIG) { + msm_camio_vfe_clk_rate_set(192000000); + s5k4e1_csi_params.data_format = CSI_10BIT; + s5k4e1_csi_params.lane_cnt = 1; + s5k4e1_csi_params.lane_assign = 0xe4; + s5k4e1_csi_params.dpcm_scheme = 0; + s5k4e1_csi_params.settle_cnt = 24; + rc = msm_camio_csi_config(&s5k4e1_csi_params); + msleep(20); + CSI_CONFIG = 1; + } + s5k4e1_start_stream(); + msleep(30); + } + return rc; +} + +static int32_t s5k4e1_video_config(int mode) +{ + + int32_t rc = 0; + int rt; + CDBG("video config\n"); + /* change sensor resolution if needed */ + if (s5k4e1_ctrl->prev_res == QTR_SIZE) + rt = RES_PREVIEW; + else + rt = RES_CAPTURE; + if (s5k4e1_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + if (s5k4e1_ctrl->set_test) { + if (s5k4e1_test(s5k4e1_ctrl->set_test) < 0) + return rc; + } + + s5k4e1_ctrl->curr_res = s5k4e1_ctrl->prev_res; + s5k4e1_ctrl->sensormode = mode; + return rc; +} + +static int32_t s5k4e1_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + + /*change sensor resolution if needed */ + if (s5k4e1_ctrl->curr_res != s5k4e1_ctrl->pict_res) { + if (s5k4e1_ctrl->pict_res == QTR_SIZE) + rt = RES_PREVIEW; + else + rt = RES_CAPTURE; + if (s5k4e1_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + + s5k4e1_ctrl->curr_res = s5k4e1_ctrl->pict_res; + s5k4e1_ctrl->sensormode = mode; + return rc; +} + +static int32_t s5k4e1_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + + /* change sensor resolution if needed */ + if (s5k4e1_ctrl->curr_res != s5k4e1_ctrl->pict_res) { + if (s5k4e1_ctrl->pict_res == QTR_SIZE) + rt = RES_PREVIEW; + else + rt = RES_CAPTURE; + if (s5k4e1_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + + s5k4e1_ctrl->curr_res = s5k4e1_ctrl->pict_res; + s5k4e1_ctrl->sensormode = mode; + return rc; +} + +static int32_t s5k4e1_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = s5k4e1_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + rc = s5k4e1_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + rc = s5k4e1_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} + +static int32_t s5k4e1_power_down(void) +{ + s5k4e1_stop_stream(); + return 0; +} + +static int s5k4e1_probe_init_done(const struct msm_camera_sensor_info *data) +{ + CDBG("probe done\n"); + gpio_free(data->sensor_reset); + return 0; +} + +static int s5k4e1_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + uint16_t regaddress1 = 0x0000; + uint16_t regaddress2 = 0x0001; + uint16_t chipid1 = 0; + uint16_t chipid2 = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + CDBG(" s5k4e1_probe_init_sensor is called\n"); + + rc = gpio_request(data->sensor_reset, "s5k4e1"); + CDBG(" s5k4e1_probe_init_sensor\n"); + if (!rc) { + CDBG("sensor_reset = %d\n", rc); + gpio_direction_output(data->sensor_reset, 0); + msleep(50); + gpio_set_value_cansleep(data->sensor_reset, 1); + msleep(20); + } else { + goto init_probe_done; + } + msleep(20); + + s5k4e1_i2c_read(regaddress1, &chipid1, 1); + if (chipid1 != 0x4E) { + rc = -ENODEV; + CDBG("s5k4e1_probe_init_sensor fail chip id doesnot match\n"); + goto init_probe_fail; + } + + s5k4e1_i2c_read(regaddress2, &chipid2 , 1); + if (chipid2 != 0x10) { + rc = -ENODEV; + CDBG("s5k4e1_probe_init_sensor fail chip id doesnot match\n"); + goto init_probe_fail; + } + + CDBG("ID: %d\n", chipid1); + CDBG("ID: %d\n", chipid1); + + + goto init_probe_done; +init_probe_fail: + CDBG(" s5k4e1_probe_init_sensor fails\n"); + s5k4e1_probe_init_done(data); +init_probe_done: + CDBG(" s5k4e1_probe_init_sensor finishes\n"); + return rc; +} + +int s5k4e1_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + CDBG("Calling s5k4e1_sensor_open_init\n"); + + s5k4e1_ctrl = kzalloc(sizeof(struct s5k4e1_ctrl_t), GFP_KERNEL); + if (!s5k4e1_ctrl) { + CDBG("s5k4e1_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + s5k4e1_ctrl->fps_divider = 1 * 0x00000400; + s5k4e1_ctrl->pict_fps_divider = 1 * 0x00000400; + s5k4e1_ctrl->set_test = TEST_OFF; + s5k4e1_ctrl->prev_res = QTR_SIZE; + s5k4e1_ctrl->pict_res = FULL_SIZE; + + if (data) + s5k4e1_ctrl->sensordata = data; + + prev_frame_length_lines = + ((s5k4e1_regs.reg_prev[S5K4E1_REG_PREV_FRAME_LEN_1].wdata << 8) | + s5k4e1_regs.reg_prev[S5K4E1_REG_PREV_FRAME_LEN_2].wdata); + + prev_line_length_pck = + (s5k4e1_regs.reg_prev[S5K4E1_REG_PREV_LINE_LEN_1].wdata << 8) | + s5k4e1_regs.reg_prev[S5K4E1_REG_PREV_LINE_LEN_2].wdata; + + snap_frame_length_lines = + (s5k4e1_regs.reg_snap[S5K4E1_REG_SNAP_FRAME_LEN_1].wdata << 8) | + s5k4e1_regs.reg_snap[S5K4E1_REG_SNAP_FRAME_LEN_2].wdata; + + snap_line_length_pck = + (s5k4e1_regs.reg_snap[S5K4E1_REG_SNAP_LINE_LEN_1].wdata << 8) | + s5k4e1_regs.reg_snap[S5K4E1_REG_SNAP_LINE_LEN_1].wdata; + + /* enable mclk first */ + msm_camio_clk_rate_set(S5K4E1_MASTER_CLK_RATE); + rc = s5k4e1_probe_init_sensor(data); + if (rc < 0) + goto init_fail; + + CDBG("init settings\n"); + if (s5k4e1_ctrl->prev_res == QTR_SIZE) + rc = s5k4e1_sensor_setting(REG_INIT, RES_PREVIEW); + else + rc = s5k4e1_sensor_setting(REG_INIT, RES_CAPTURE); + s5k4e1_ctrl->fps = 30 * Q8; + + /* enable AF actuator */ + if (s5k4e1_ctrl->sensordata->vcm_enable) { + CDBG("enable AF actuator, gpio = %d\n", + s5k4e1_ctrl->sensordata->vcm_pwd); + rc = gpio_request(s5k4e1_ctrl->sensordata->vcm_pwd, + "s5k4e1_af"); + if (!rc) + gpio_direction_output( + s5k4e1_ctrl->sensordata->vcm_pwd, + 1); + else { + pr_err("s5k4e1_ctrl gpio request failed!\n"); + goto init_fail; + } + msleep(20); + rc = s5k4e1_set_default_focus(0); + if (rc < 0) { + gpio_direction_output(s5k4e1_ctrl->sensordata->vcm_pwd, + 0); + gpio_free(s5k4e1_ctrl->sensordata->vcm_pwd); + } + } + if (rc < 0) + goto init_fail; + else + goto init_done; +init_fail: + CDBG("init_fail\n"); + s5k4e1_probe_init_done(data); +init_done: + CDBG("init_done\n"); + return rc; +} + +static int s5k4e1_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&s5k4e1_wait_queue); + return 0; +} + +static int s5k4e1_af_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&s5k4e1_af_wait_queue); + return 0; +} + +static const struct i2c_device_id s5k4e1_af_i2c_id[] = { + {"s5k4e1_af", 0}, + { } +}; + +static int s5k4e1_af_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("s5k4e1_af_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + s5k4e1_af_sensorw = kzalloc(sizeof(struct s5k4e1_work_t), GFP_KERNEL); + if (!s5k4e1_af_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, s5k4e1_af_sensorw); + s5k4e1_af_init_client(client); + s5k4e1_af_client = client; + + msleep(50); + + CDBG("s5k4e1_af_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("s5k4e1_af_probe failed! rc = %d\n", rc); + return rc; +} + +static const struct i2c_device_id s5k4e1_i2c_id[] = { + {"s5k4e1", 0}, + { } +}; + +static int s5k4e1_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("s5k4e1_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + s5k4e1_sensorw = kzalloc(sizeof(struct s5k4e1_work_t), GFP_KERNEL); + if (!s5k4e1_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, s5k4e1_sensorw); + s5k4e1_init_client(client); + s5k4e1_client = client; + + msleep(50); + + CDBG("s5k4e1_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("s5k4e1_probe failed! rc = %d\n", rc); + return rc; +} + +static int __devexit s5k4e1_remove(struct i2c_client *client) +{ + struct s5k4e1_work_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + s5k4e1_client = NULL; + kfree(sensorw); + return 0; +} + +static int __devexit s5k4e1_af_remove(struct i2c_client *client) +{ + struct s5k4e1_work_t *s5k4e1_af = i2c_get_clientdata(client); + free_irq(client->irq, s5k4e1_af); + s5k4e1_af_client = NULL; + kfree(s5k4e1_af); + return 0; +} + +static struct i2c_driver s5k4e1_i2c_driver = { + .id_table = s5k4e1_i2c_id, + .probe = s5k4e1_i2c_probe, + .remove = __exit_p(s5k4e1_i2c_remove), + .driver = { + .name = "s5k4e1", + }, +}; + +static struct i2c_driver s5k4e1_af_i2c_driver = { + .id_table = s5k4e1_af_i2c_id, + .probe = s5k4e1_af_i2c_probe, + .remove = __exit_p(s5k4e1_af_i2c_remove), + .driver = { + .name = "s5k4e1_af", + }, +}; + +int s5k4e1_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&s5k4e1_mut); + CDBG("s5k4e1_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + s5k4e1_get_pict_fps( + cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + s5k4e1_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + s5k4e1_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + s5k4e1_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + s5k4e1_get_pict_pixels_pl(); + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + s5k4e1_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = s5k4e1_set_fps(&(cdata.cfg.fps)); + break; + case CFG_SET_EXP_GAIN: + rc = s5k4e1_write_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_PICT_EXP_GAIN: + rc = s5k4e1_set_pict_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_MODE: + rc = s5k4e1_set_sensor_mode(cdata.mode, cdata.rs); + break; + case CFG_PWR_DOWN: + rc = s5k4e1_power_down(); + break; + case CFG_MOVE_FOCUS: + rc = s5k4e1_move_focus(cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + case CFG_SET_DEFAULT_FOCUS: + rc = s5k4e1_set_default_focus(cdata.cfg.focus.steps); + break; + case CFG_GET_AF_MAX_STEPS: + cdata.max_steps = S5K4E1_TOTAL_STEPS_NEAR_TO_FAR; + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + case CFG_SET_EFFECT: + rc = s5k4e1_set_default_focus(cdata.cfg.effect); + break; + default: + rc = -EFAULT; + break; + } + mutex_unlock(&s5k4e1_mut); + + return rc; +} + +static int s5k4e1_sensor_release(void) +{ + int rc = -EBADF; + + mutex_lock(&s5k4e1_mut); + s5k4e1_power_down(); + msleep(20); + gpio_set_value_cansleep(s5k4e1_ctrl->sensordata->sensor_reset, 0); + usleep_range(5000, 5100); + gpio_free(s5k4e1_ctrl->sensordata->sensor_reset); + if (s5k4e1_ctrl->sensordata->vcm_enable) { + gpio_direction_output(s5k4e1_ctrl->sensordata->vcm_pwd, 0); + gpio_free(s5k4e1_ctrl->sensordata->vcm_pwd); + } + kfree(s5k4e1_ctrl); + s5k4e1_ctrl = NULL; + CDBG("s5k4e1_release completed\n"); + mutex_unlock(&s5k4e1_mut); + + return rc; +} + +static int s5k4e1_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + + rc = i2c_add_driver(&s5k4e1_i2c_driver); + if (rc < 0 || s5k4e1_client == NULL) { + rc = -ENOTSUPP; + CDBG("I2C add driver failed"); + goto probe_fail_1; + } + + rc = i2c_add_driver(&s5k4e1_af_i2c_driver); + if (rc < 0 || s5k4e1_af_client == NULL) { + rc = -ENOTSUPP; + CDBG("I2C add driver failed"); + goto probe_fail_2; + } + + msm_camio_clk_rate_set(S5K4E1_MASTER_CLK_RATE); + + rc = s5k4e1_probe_init_sensor(info); + if (rc < 0) + goto probe_fail_3; + + s->s_init = s5k4e1_sensor_open_init; + s->s_release = s5k4e1_sensor_release; + s->s_config = s5k4e1_sensor_config; + s->s_mount_angle = info->sensor_platform_info->mount_angle; + gpio_set_value_cansleep(info->sensor_reset, 0); + s5k4e1_probe_init_done(info); + + return rc; + +probe_fail_3: + i2c_del_driver(&s5k4e1_af_i2c_driver); +probe_fail_2: + i2c_del_driver(&s5k4e1_i2c_driver); +probe_fail_1: + CDBG("s5k4e1_sensor_probe: SENSOR PROBE FAILS!\n"); + return rc; +} + +static int __devinit s5k4e1_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, s5k4e1_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = s5k4e1_probe, + .driver = { + .name = "msm_camera_s5k4e1", + .owner = THIS_MODULE, + }, +}; + +static int __init s5k4e1_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(s5k4e1_init); +MODULE_DESCRIPTION("Samsung 5 MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/s5k4e1.h b/drivers/media/video/msm/s5k4e1.h new file mode 100644 index 00000000000..7f603322a3c --- /dev/null +++ b/drivers/media/video/msm/s5k4e1.h @@ -0,0 +1,94 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef S5K4E1_H +#define S5K4E1_H +#include +#include +extern struct s5k4e1_reg s5k4e1_regs; + +struct s5k4e1_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +enum s5k4e1_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum s5k4e1_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; +enum s5k4e1_setting { + RES_PREVIEW, + RES_CAPTURE +}; +enum s5k4e1_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +enum s5k4e1_reg_pll { + E013_VT_PIX_CLK_DIV, + E013_VT_SYS_CLK_DIV, + E013_PRE_PLL_CLK_DIV, + E013_PLL_MULTIPLIER, + E013_OP_PIX_CLK_DIV, + E013_OP_SYS_CLK_DIV +}; + +enum s5k4e1_reg_mode { + E013_X_ADDR_START, + E013_X_ADDR_END, + E013_Y_ADDR_START, + E013_Y_ADDR_END, + E013_X_OUTPUT_SIZE, + E013_Y_OUTPUT_SIZE, + E013_DATAPATH_SELECT, + E013_READ_MODE, + E013_ANALOG_CONTROL5, + E013_DAC_LD_4_5, + E013_SCALING_MODE, + E013_SCALE_M, + E013_LINE_LENGTH_PCK, + E013_FRAME_LENGTH_LINES, + E013_COARSE_INTEGRATION_TIME, + E013_FINE_INTEGRATION_TIME, + E013_FINE_CORRECTION +}; + +struct s5k4e1_reg { + const struct s5k4e1_i2c_reg_conf *reg_mipi; + const unsigned short reg_mipi_size; + const struct s5k4e1_i2c_reg_conf *rec_settings; + const unsigned short rec_size; + const struct s5k4e1_i2c_reg_conf *reg_pll_p; + const unsigned short reg_pll_p_size; + const struct s5k4e1_i2c_reg_conf *reg_pll_s; + const unsigned short reg_pll_s_size; + const struct s5k4e1_i2c_reg_conf *reg_prev; + const unsigned short reg_prev_size; + const struct s5k4e1_i2c_reg_conf *reg_snap; + const unsigned short reg_snap_size; +}; +#endif /* S5K4E1_H */ diff --git a/drivers/media/video/msm/s5k4e1_reg.c b/drivers/media/video/msm/s5k4e1_reg.c new file mode 100644 index 00000000000..59bb1c85295 --- /dev/null +++ b/drivers/media/video/msm/s5k4e1_reg.c @@ -0,0 +1,169 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include "s5k4e1.h" + +struct s5k4e1_i2c_reg_conf s5k4e1_mipi_settings[] = { + {0x30BD, 0x00},/* SEL_CCP[0] */ + {0x3084, 0x15},/* SYNC Mode */ + {0x30BE, 0x1A},/* M_PCLKDIV_AUTO[4], M_DIV_PCLK[3:0] */ + {0x30C1, 0x01},/* pack video enable [0] */ + {0x30EE, 0x02},/* DPHY enable [ 1] */ + {0x3111, 0x86},/* Embedded data off [5] */ +}; + +/* PLL Configuration */ +struct s5k4e1_i2c_reg_conf s5k4e1_pll_preview_settings[] = { + {0x0305, 0x04}, + {0x0306, 0x00}, + {0x0307, 0x44}, + {0x30B5, 0x00}, + {0x30E2, 0x01},/* num lanes[1:0] = 2 */ + {0x30F1, 0xB0}, +}; + +struct s5k4e1_i2c_reg_conf s5k4e1_pll_snap_settings[] = { + {0x0305, 0x04}, + {0x0306, 0x00}, + {0x0307, 0x44}, + {0x30B5, 0x00}, + {0x30E2, 0x01},/* num lanes[1:0] = 2 */ + {0x30F1, 0xB0}, +}; + +struct s5k4e1_i2c_reg_conf s5k4e1_prev_settings[] = { + /* output size (1304 x 980) */ + {0x30A9, 0x02},/* Horizontal Binning On */ + {0x300E, 0xEB},/* Vertical Binning On */ + {0x0387, 0x03},/* y_odd_inc 03(10b AVG) */ + {0x0344, 0x00},/* x_addr_start 0 */ + {0x0345, 0x00}, + {0x0348, 0x0A},/* x_addr_end 2607 */ + {0x0349, 0x2F}, + {0x0346, 0x00},/* y_addr_start 0 */ + {0x0347, 0x00}, + {0x034A, 0x07},/* y_addr_end 1959 */ + {0x034B, 0xA7}, + {0x0380, 0x00},/* x_even_inc 1 */ + {0x0381, 0x01}, + {0x0382, 0x00},/* x_odd_inc 1 */ + {0x0383, 0x01}, + {0x0384, 0x00},/* y_even_inc 1 */ + {0x0385, 0x01}, + {0x0386, 0x00},/* y_odd_inc 3 */ + {0x0387, 0x03}, + {0x034C, 0x05},/* x_output_size 1304 */ + {0x034D, 0x18}, + {0x034E, 0x03},/* y_output_size 980 */ + {0x034F, 0xd4}, + {0x30BF, 0xAB},/* outif_enable[7], data_type[5:0](2Bh = bayer 10bit} */ + {0x30C0, 0xA0},/* video_offset[7:4] 3260%12 */ + {0x30C8, 0x06},/* video_data_length 1600 = 1304 * 1.25 */ + {0x30C9, 0x5E}, + /* Timing Configuration */ + {0x0202, 0x03}, + {0x0203, 0x14}, + {0x0204, 0x00}, + {0x0205, 0x80}, + {0x0340, 0x03},/* Frame Length */ + {0x0341, 0xE0}, + {0x0342, 0x0A},/* 2738 Line Length */ + {0x0343, 0xB2}, +}; + +struct s5k4e1_i2c_reg_conf s5k4e1_snap_settings[] = { + /*Output Size (2608x1960)*/ + {0x30A9, 0x03},/* Horizontal Binning Off */ + {0x300E, 0xE8},/* Vertical Binning Off */ + {0x0387, 0x01},/* y_odd_inc */ + {0x034C, 0x0A},/* x_output size */ + {0x034D, 0x30}, + {0x034E, 0x07},/* y_output size */ + {0x034F, 0xA8}, + {0x30BF, 0xAB},/* outif_enable[7], data_type[5:0](2Bh = bayer 10bit} */ + {0x30C0, 0x80},/* video_offset[7:4] 3260%12 */ + {0x30C8, 0x0C},/* video_data_length 3260 = 2608 * 1.25 */ + {0x30C9, 0xBC}, + /*Timing configuration*/ + {0x0202, 0x06}, + {0x0203, 0x28}, + {0x0204, 0x00}, + {0x0205, 0x80}, + {0x0340, 0x07},/* Frame Length */ + {0x0341, 0xB4}, + {0x0342, 0x0A},/* 2738 Line Length */ + {0x0343, 0xB2}, +}; + +struct s5k4e1_i2c_reg_conf s5k4e1_recommend_settings[] = { + /*CDS timing setting ... */ + {0x3000, 0x05}, + {0x3001, 0x03}, + {0x3002, 0x08}, + {0x3003, 0x0A}, + {0x3004, 0x50}, + {0x3005, 0x0E}, + {0x3006, 0x5E}, + {0x3007, 0x00}, + {0x3008, 0x78}, + {0x3009, 0x78}, + {0x300A, 0x50}, + {0x300B, 0x08}, + {0x300C, 0x14}, + {0x300D, 0x00}, + {0x300E, 0xE8}, + {0x300F, 0x82}, + {0x301B, 0x77}, + + /* CDS option setting ... */ + {0x3010, 0x00}, + {0x3011, 0x3A}, + {0x3029, 0x04}, + {0x3012, 0x30}, + {0x3013, 0xA0}, + {0x3014, 0x00}, + {0x3015, 0x00}, + {0x3016, 0x30}, + {0x3017, 0x94}, + {0x3018, 0x70}, + {0x301D, 0xD4}, + {0x3021, 0x02}, + {0x3022, 0x24}, + {0x3024, 0x40}, + {0x3027, 0x08}, + + /* Pixel option setting ... */ + {0x301C, 0x04}, + {0x30D8, 0x3F}, + {0x302B, 0x01}, + + {0x3070, 0x5F}, + {0x3071, 0x00}, + {0x3080, 0x04}, + {0x3081, 0x38}, +}; + +struct s5k4e1_reg s5k4e1_regs = { + .reg_mipi = &s5k4e1_mipi_settings[0], + .reg_mipi_size = ARRAY_SIZE(s5k4e1_mipi_settings), + .rec_settings = &s5k4e1_recommend_settings[0], + .rec_size = ARRAY_SIZE(s5k4e1_recommend_settings), + .reg_pll_p = &s5k4e1_pll_preview_settings[0], + .reg_pll_p_size = ARRAY_SIZE(s5k4e1_pll_preview_settings), + .reg_pll_s = &s5k4e1_pll_snap_settings[0], + .reg_pll_s_size = ARRAY_SIZE(s5k4e1_pll_snap_settings), + .reg_prev = &s5k4e1_prev_settings[0], + .reg_prev_size = ARRAY_SIZE(s5k4e1_prev_settings), + .reg_snap = &s5k4e1_snap_settings[0], + .reg_snap_size = ARRAY_SIZE(s5k4e1_snap_settings), +}; diff --git a/drivers/media/video/msm/sn12m0pz.c b/drivers/media/video/msm/sn12m0pz.c new file mode 100644 index 00000000000..affa581c559 --- /dev/null +++ b/drivers/media/video/msm/sn12m0pz.c @@ -0,0 +1,1850 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sn12m0pz.h" + + +#define Q8 0x00000100 +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD_OFF 0x00 +#define GROUPED_PARAMETER_HOLD 0x01 +#define REG_MODE_SELECT 0x0100 +#define MODE_SELECT_STANDBY_MODE 0x00 +#define MODE_SELECT_STREAM 0x01 + +/* Integration Time */ +#define REG_COARSE_INTEGRATION_TIME_MSB 0x0202 +#define REG_COARSE_INTEGRATION_TIME_LSB 0x0203 + +/* Gain */ +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_MSB 0x0204 +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_LSB 0x0205 + +/* PLL Register Defines */ +#define REG_PLL_MULTIPLIER 0x0307 +#define REG_0x302B 0x302B + +/* MIPI Enable Settings */ +#define REG_0x30E5 0x30E5 +#define REG_0x3300 0x3300 + +/* Global Setting */ +#define REG_IMAGE_ORIENTATION 0x0101 + +#define REG_0x300A 0x300A +#define REG_0x3014 0x3014 +#define REG_0x3015 0x3015 +#define REG_0x3017 0x3017 +#define REG_0x301C 0x301C +#define REG_0x3031 0x3031 +#define REG_0x3040 0x3040 +#define REG_0x3041 0x3041 +#define REG_0x3051 0x3051 +#define REG_0x3053 0x3053 +#define REG_0x3055 0x3055 +#define REG_0x3057 0x3057 +#define REG_0x3060 0x3060 +#define REG_0x3065 0x3065 +#define REG_0x30AA 0x30AA +#define REG_0x30AB 0x30AB +#define REG_0x30B0 0x30B0 +#define REG_0x30B2 0x30B2 +#define REG_0x30D3 0x30D3 + +#define REG_0x3106 0x3106 +#define REG_0x3108 0x3108 +#define REG_0x310A 0x310A +#define REG_0x310C 0x310C +#define REG_0x310E 0x310E +#define REG_0x3126 0x3126 +#define REG_0x312E 0x312E +#define REG_0x313C 0x313C +#define REG_0x313E 0x313E +#define REG_0x3140 0x3140 +#define REG_0x3142 0x3142 +#define REG_0x3144 0x3144 +#define REG_0x3148 0x3148 +#define REG_0x314A 0x314A +#define REG_0x3166 0x3166 +#define REG_0x3168 0x3168 +#define REG_0x316F 0x316F +#define REG_0x3171 0x3171 +#define REG_0x3173 0x3173 +#define REG_0x3175 0x3175 +#define REG_0x3177 0x3177 +#define REG_0x3179 0x3179 +#define REG_0x317B 0x317B +#define REG_0x317D 0x317D +#define REG_0x317F 0x317F +#define REG_0x3181 0x3181 +#define REG_0x3184 0x3184 +#define REG_0x3185 0x3185 +#define REG_0x3187 0x3187 + +#define REG_0x31A4 0x31A4 +#define REG_0x31A6 0x31A6 +#define REG_0x31AC 0x31AC +#define REG_0x31AE 0x31AE +#define REG_0x31B4 0x31B4 +#define REG_0x31B6 0x31B6 + +#define REG_0x3254 0x3254 +#define REG_0x3256 0x3256 +#define REG_0x3258 0x3258 +#define REG_0x325A 0x325A +#define REG_0x3260 0x3260 +#define REG_0x3262 0x3262 + + +#define REG_0x3304 0x3304 +#define REG_0x3305 0x3305 +#define REG_0x3306 0x3306 +#define REG_0x3307 0x3307 +#define REG_0x3308 0x3308 +#define REG_0x3309 0x3309 +#define REG_0x330A 0x330A +#define REG_0x330B 0x330B +#define REG_0x330C 0x330C +#define REG_0x330D 0x330D + +/* Mode Setting */ +#define REG_FRAME_LENGTH_LINES_MSB 0x0340 +#define REG_FRAME_LENGTH_LINES_LSB 0x0341 +#define REG_LINE_LENGTH_PCK_MSB 0x0342 +#define REG_LINE_LENGTH_PCK_LSB 0x0343 +#define REG_X_OUTPUT_SIZE_MSB 0x034C +#define REG_X_OUTPUT_SIZE_LSB 0x034D +#define REG_Y_OUTPUT_SIZE_MSB 0x034E +#define REG_Y_OUTPUT_SIZE_LSB 0x034F +#define REG_X_EVEN_INC_LSB 0x0381 +#define REG_X_ODD_INC_LSB 0x0383 +#define REG_Y_EVEN_INC_LSB 0x0385 +#define REG_Y_ODD_INC_LSB 0x0387 +#define REG_0x3016 0x3016 +#define REG_0x30E8 0x30E8 +#define REG_0x3301 0x3301 +/* for 120fps support */ +#define REG_0x0344 0x0344 +#define REG_0x0345 0x0345 +#define REG_0x0346 0x0346 +#define REG_0x0347 0x0347 +#define REG_0x0348 0x0348 +#define REG_0x0349 0x0349 +#define REG_0x034A 0x034A +#define REG_0x034B 0x034B + +/* Test Pattern */ +#define REG_0x30D8 0x30D8 +#define REG_TEST_PATTERN_MODE 0x0601 + +/* Solid Color Test Pattern */ +#define REG_TEST_DATA_RED_MSB 0x0603 +#define REG_TEST_DATA_RED_LSB 0x0603 +#define REG_TEST_DATA_GREENR_MSB 0x0604 +#define REG_TEST_DATA_GREENR_LSB 0x0605 +#define REG_TEST_DATA_BLUE_MSB 0x0606 +#define REG_TEST_DATA_BLUE_LSB 0x0607 +#define REG_TEST_DATA_GREENB_MSB 0x0608 +#define REG_TEST_DATA_GREENB_LSB 0x0609 +#define SN12M0PZ_AF_I2C_SLAVE_ID 0xE4 +#define SN12M0PZ_STEPS_NEAR_TO_CLOSEST_INF 42 +#define SN12M0PZ_TOTAL_STEPS_NEAR_TO_FAR 42 + + +/* TYPE DECLARATIONS */ + + +enum mipi_config_type { + IU060F_SN12M0PZ_STMIPID01, + IU060F_SN12M0PZ_STMIPID02 +}; + +enum sn12m0pz_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum sn12m0pz_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE, + QVGA_SIZE, +}; + +enum sn12m0pz_setting { + RES_PREVIEW, + RES_CAPTURE, + RES_VIDEO_120FPS, +}; + +enum mt9p012_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +/* 816x612, 24MHz MCLK 96MHz PCLK */ +#define IU060F_SN12M0PZ_OFFSET 3 +/* Time in milisecs for waiting for the sensor to reset.*/ +#define SN12M0PZ_RESET_DELAY_MSECS 66 +#define SN12M0PZ_WIDTH 4032 +#define SN12M0PZ_HEIGHT 3024 +#define SN12M0PZ_FULL_SIZE_WIDTH 4032 +#define SN12M0PZ_FULL_SIZE_HEIGHT 3024 +#define SN12M0PZ_HRZ_FULL_BLK_PIXELS 176 +#define SN12M0PZ_VER_FULL_BLK_LINES 50 +#define SN12M0PZ_QTR_SIZE_WIDTH 2016 +#define SN12M0PZ_QTR_SIZE_HEIGHT 1512 +#define SN12M0PZ_HRZ_QTR_BLK_PIXELS 2192 +#define SN12M0PZ_VER_QTR_BLK_LINES 26 + +/* 120fps mode */ +#define SN12M0PZ_QVGA_SIZE_WIDTH 4032 +#define SN12M0PZ_QVGA_SIZE_HEIGHT 249 +#define SN12M0PZ_HRZ_QVGA_BLK_PIXELS 176 +#define SN12M0PZ_VER_QVGA_BLK_LINES 9 +#define SN12M0PZ_DEFAULT_CLOCK_RATE 24000000 + +static uint32_t IU060F_SN12M0PZ_DELAY_MSECS = 30; +static enum mipi_config_type mipi_config = IU060F_SN12M0PZ_STMIPID02; +/* AF Tuning Parameters */ +static int16_t enable_single_D02_lane; +static int16_t fullsize_cropped_at_8mp; + +struct sn12m0pz_work_t { + struct work_struct work; +}; + +static struct sn12m0pz_work_t *sn12m0pz_sensorw; +static struct i2c_client *sn12m0pz_client; + +struct sn12m0pz_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + uint32_t sensormode; + uint32_t fps_divider;/* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider;/* init to 1 * 0x00000400 */ + uint16_t fps; + int16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + enum sn12m0pz_resolution_t prev_res; + enum sn12m0pz_resolution_t pict_res; + enum sn12m0pz_resolution_t curr_res; + enum sn12m0pz_test_mode_t set_test; + unsigned short imgaddr; +}; + +static struct sn12m0pz_ctrl_t *sn12m0pz_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(sn12m0pz_wait_queue); +DEFINE_MUTEX(sn12m0pz_mut); + + +static int sn12m0pz_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 2, + .buf = rxdata, + }, + }; + + if (i2c_transfer(sn12m0pz_client->adapter, msgs, 2) < 0) { + CDBG("sn12m0pz_i2c_rxdata failed!"); + return -EIO; + } + + return 0; +} +static int32_t sn12m0pz_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + + if (i2c_transfer(sn12m0pz_client->adapter, msg, 1) < 0) { + CDBG("sn12m0pz_i2c_txdata faild 0x%x", sn12m0pz_client->addr); + return -EIO; + } + + return 0; +} + +static int32_t sn12m0pz_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc; + unsigned char buf[2]; + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + + rc = sn12m0pz_i2c_rxdata(sn12m0pz_client->addr, buf, rlen); + + if (rc < 0) { + CDBG("sn12m0pz_i2c_read 0x%x failed!", raddr); + return rc; + } + + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + + return rc; +} + +static int32_t sn12m0pz_i2c_write_b_sensor(unsigned short waddr, uint8_t bdata) +{ + int32_t rc; + unsigned char buf[3]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + udelay(90); + CDBG("i2c_write_b addr = %x, val = %x\n", waddr, bdata); + rc = sn12m0pz_i2c_txdata(sn12m0pz_client->addr, buf, 3); + + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!", + waddr, bdata); + } + + return rc; +} + +static int16_t sn12m0pz_i2c_write_b_af(unsigned short saddr, + unsigned short baddr, unsigned short bdata) +{ + int16_t rc; + unsigned char buf[2]; + + memset(buf, 0, sizeof(buf)); + buf[0] = baddr; + buf[1] = bdata; + rc = sn12m0pz_i2c_txdata(saddr, buf, 2); + + if (rc < 0) + CDBG("i2c_write failed, saddr = 0x%x addr = 0x%x, val =0x%x!", + saddr, baddr, bdata); + + return rc; +} + +static int32_t sn12m0pz_i2c_write_byte_bridge(unsigned short saddr, + unsigned short waddr, uint8_t bdata) +{ + int32_t rc; + unsigned char buf[3]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + + CDBG("i2c_write_b addr = %x, val = %x", waddr, bdata); + rc = sn12m0pz_i2c_txdata(saddr, buf, 3); + + if (rc < 0) + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!", + waddr, bdata); + + return rc; +} + +static int32_t sn12m0pz_stmipid01_config(void) +{ + int32_t rc = 0; + /* Initiate I2C for D01: */ + /* MIPI Bridge configuration */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0002, 0x19) < 0) + return rc; /* enable clock lane*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0003, 0x00) < 0) + return rc; + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0004, 0x3E) < 0) + return rc; /* mipi mode clock*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0005, 0x01) < 0) + return rc; /* enable data line*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0006, 0x0F) < 0) + return rc; /* mipi mode data 0x01*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0009, 0x00) < 0) + return rc; /* Data_Lane1_Reg1*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x000D, 0x92) < 0) + return rc; /* CCPRxRegisters*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x000E, 0x28) < 0) + return rc; /* 10 bits for pixel width input for CCP rx.*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0014, 0xC0) < 0) + return rc; /* no bypass, no decomp, 1Lane System,CSIstreaming*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0015, 0x48) < 0) + return rc; /* ModeControlRegisters-- Don't reset error flag*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0017, 0x2B) < 0) + return rc; /* Data_ID_Rreg*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0018, 0x2B) < 0) + return rc; /* Data_ID_Rreg_emb*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0019, 0x0C) < 0) + return rc; + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x001E, 0x0A) < 0) + return rc; + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x001F, 0x0A) < 0) + return rc; + + return rc; +} +static int32_t sn12m0pz_stmipid02_config(void) +{ + int32_t rc = 0; + + /* Main Camera Clock Lane 1 (CLHP1, CLKN1)*/ + /* Enable Clock Lane 1 (CLHP1, CLKN1), 0x15 for 400MHz */ + if (enable_single_D02_lane) { + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0002, 0x19) < 0) + return rc; + /* Main Camera Data Lane 1.1 (DATA2P1, DATA2N1) */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0009, 0x00) < 0) + return rc;/* Enable Data Lane 1.2 (DATA2P1, DATA2N1) */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x000A, 0x00) < 0) + return rc; /*CSIMode on Data Lane1.2(DATA2P1,DATA2N1)*/ + /* Mode Control */ + /* Enable single lane for qtr preview */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0014, 0xC0) < 0) + return rc; /*set 0xC0 - left justified on upper bits)*/ + /* bit 1 set to 0 i.e. 1 lane system for qtr size preview */ + } else { + if (sn12m0pz_ctrl->prev_res == QVGA_SIZE) { + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, + 0x0002, 0x19) < 0) + return rc; + } else { + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, + 0x0002, 0x21) < 0) + return rc; + } + /* Main Camera Data Lane 1.1 (DATA2P1, DATA2N1) */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0009, 0x01) < 0) + return rc; /* Enable Data Lane 1.2 (DATA2P1, DATA2N1) */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x000A, 0x01) < 0) + return rc; /* CSI Mode Data Lane1.2(DATA2P1, DATA2N1)*/ + + /* Mode Control */ + /* Enable two lanes for full size preview/ snapshot */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0014, 0xC2) < 0) + return rc; /* No decompression, CSI dual lane */ + } + + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0004, 0x1E) < 0) + return rc; + + /* Main Camera Data Lane 1.1 (DATA1P1, DATA1N1) */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0005, 0x03) < 0) + return rc; /* Enable Data Lane 1.1 (DATA1P1, DATA1N1) */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0006, 0x0f) < 0) + return rc; /* CSI Mode on Data Lane 1.1 (DATA1P1, DATA1N1) */ + + /* Tristated Output, continuous clock, */ + /*polarity of clock is inverted and sync signals not inverted*/ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0015, 0x08) < 0) + return rc; + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0036, 0x20) < 0) + return rc; /* Enable compensation macro, main camera */ + + /* Data type: 0x2B Raw 10 */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0017, 0x2B) < 0) + return rc; + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0018, 0x2B) < 0) + return rc; /* Data type of embedded data: 0x2B Raw 10 */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x0019, 0x0C) < 0) + return rc; /* Data type and pixel width programmed 0x0C*/ + + /* Decompression Mode */ + + /* Pixel Width and Decompression ON/OFF */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x001E, 0x0A) < 0) + return rc; /* Image data not compressed: 0x0A for 10 bits */ + if (sn12m0pz_i2c_write_byte_bridge(0x28>>1, 0x001F, 0x0A) < 0) + return rc; /* Embedded data not compressed: 0x0A for 10 bits */ + return rc; +} + +static int16_t sn12m0pz_af_init(void) +{ + int16_t rc; + /* Initialize waveform */ + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x01, 0xA9); + + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x02, 0xD2); + + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x03, 0x0C); + + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x04, 0x14); + + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x05, 0xB6); + + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x06, 0x4F); + + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x07, 0x00); + + return rc; +} + +static int32_t sn12m0pz_move_focus(int direction, + int32_t num_steps) +{ + int8_t step_direction, dest_step_position, bit_mask; + int32_t rc = 0; + uint16_t sn12m0pz_l_region_code_per_step = 3; + + if (num_steps == 0) + return rc; + + if (direction == MOVE_NEAR) { + step_direction = 1; + bit_mask = 0x80; + } else if (direction == MOVE_FAR) { + step_direction = -1; + bit_mask = 0x00; + } else { + CDBG("sn12m0pz_move_focus: Illegal focus direction"); + return -EINVAL; + } + + dest_step_position = sn12m0pz_ctrl->curr_step_pos + + (step_direction * num_steps); + + if (dest_step_position < 0) + dest_step_position = 0; + else if (dest_step_position > SN12M0PZ_TOTAL_STEPS_NEAR_TO_FAR) + dest_step_position = SN12M0PZ_TOTAL_STEPS_NEAR_TO_FAR; + + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x00, + ((num_steps * sn12m0pz_l_region_code_per_step) | bit_mask)); + + sn12m0pz_ctrl->curr_step_pos = dest_step_position; + + return rc; +} +static int32_t sn12m0pz_set_default_focus(uint8_t af_step) +{ + int32_t rc; + + /* Initialize to infinity */ + + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x00, 0x7F); + + rc = sn12m0pz_i2c_write_b_af(SN12M0PZ_AF_I2C_SLAVE_ID >> 1, 0x00, 0x7F); + + sn12m0pz_ctrl->curr_step_pos = 0; + + return rc; +} +static void sn12m0pz_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint16_t preview_frame_length_lines, snapshot_frame_length_lines; + uint16_t preview_line_length_pck, snapshot_line_length_pck; + uint32_t divider, pclk_mult, d1, d2; + + /* Total frame_length_lines and line_length_pck for preview */ + CDBG("sn12m0pz_get_pict_fps prev_res %d", sn12m0pz_ctrl->prev_res); + if (sn12m0pz_ctrl->prev_res == QVGA_SIZE) { + preview_frame_length_lines = SN12M0PZ_QVGA_SIZE_HEIGHT + + SN12M0PZ_VER_QVGA_BLK_LINES; + preview_line_length_pck = SN12M0PZ_QVGA_SIZE_WIDTH + + SN12M0PZ_HRZ_QVGA_BLK_PIXELS; + } else { + preview_frame_length_lines = SN12M0PZ_QTR_SIZE_HEIGHT + + SN12M0PZ_VER_QTR_BLK_LINES; + preview_line_length_pck = SN12M0PZ_QTR_SIZE_WIDTH + + SN12M0PZ_HRZ_QTR_BLK_PIXELS; + } + /* Total frame_length_lines and line_length_pck for snapshot */ + snapshot_frame_length_lines = SN12M0PZ_FULL_SIZE_HEIGHT + + SN12M0PZ_HRZ_FULL_BLK_PIXELS; + snapshot_line_length_pck = SN12M0PZ_FULL_SIZE_WIDTH + + SN12M0PZ_HRZ_FULL_BLK_PIXELS; + d1 = preview_frame_length_lines * + 0x00000400 / snapshot_frame_length_lines; + d2 = preview_line_length_pck * + 0x00000400/snapshot_line_length_pck; + divider = d1 * d2 / 0x400; + pclk_mult = + (uint32_t) + (sn12m0pz_regs.reg_pat[RES_CAPTURE].pll_multiplier_lsb * + 0x400) / (uint32_t) + sn12m0pz_regs.reg_pat[RES_PREVIEW].pll_multiplier_lsb; + *pfps = (uint16_t) (((fps * divider) / 0x400 * pclk_mult) / 0x400); +} + +static uint16_t sn12m0pz_get_prev_lines_pf(void) +{ + if (sn12m0pz_ctrl->prev_res == QTR_SIZE) + return SN12M0PZ_QTR_SIZE_HEIGHT + + SN12M0PZ_VER_QTR_BLK_LINES; + else if (sn12m0pz_ctrl->prev_res == QVGA_SIZE) + return SN12M0PZ_QVGA_SIZE_HEIGHT + + SN12M0PZ_VER_QVGA_BLK_LINES; + + else + return SN12M0PZ_FULL_SIZE_HEIGHT + + SN12M0PZ_VER_FULL_BLK_LINES; +} + +static uint16_t sn12m0pz_get_prev_pixels_pl(void) +{ + if (sn12m0pz_ctrl->prev_res == QTR_SIZE) + return SN12M0PZ_QTR_SIZE_WIDTH + + SN12M0PZ_HRZ_QTR_BLK_PIXELS; + else + return SN12M0PZ_FULL_SIZE_WIDTH + + SN12M0PZ_HRZ_FULL_BLK_PIXELS; +} + +static uint16_t sn12m0pz_get_pict_lines_pf(void) +{ + if (sn12m0pz_ctrl->pict_res == QTR_SIZE) + return SN12M0PZ_QTR_SIZE_HEIGHT + + SN12M0PZ_VER_QTR_BLK_LINES; + else + return SN12M0PZ_FULL_SIZE_HEIGHT + + SN12M0PZ_VER_FULL_BLK_LINES; +} + +static uint16_t sn12m0pz_get_pict_pixels_pl(void) +{ + if (sn12m0pz_ctrl->pict_res == QTR_SIZE) + return SN12M0PZ_QTR_SIZE_WIDTH + + SN12M0PZ_HRZ_QTR_BLK_PIXELS; + else + return SN12M0PZ_FULL_SIZE_WIDTH + + SN12M0PZ_HRZ_FULL_BLK_PIXELS; +} + +static uint32_t sn12m0pz_get_pict_max_exp_lc(void) +{ + if (sn12m0pz_ctrl->pict_res == QTR_SIZE) + return (SN12M0PZ_QTR_SIZE_HEIGHT + + SN12M0PZ_VER_QTR_BLK_LINES) * 24; + else + return (SN12M0PZ_FULL_SIZE_HEIGHT + + SN12M0PZ_VER_FULL_BLK_LINES) * 24; +} + +static int32_t sn12m0pz_set_fps(struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + + total_lines_per_frame = (uint16_t)((SN12M0PZ_QTR_SIZE_HEIGHT + + SN12M0PZ_VER_QTR_BLK_LINES) * + sn12m0pz_ctrl->fps_divider / 0x400); + + if (sn12m0pz_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_MSB, + ((total_lines_per_frame & 0xFF00) >> 8)) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_LSB, + (total_lines_per_frame & 0x00FF)) < 0) + return rc; + + return rc; +} + +static int32_t sn12m0pz_write_exp_gain(uint16_t gain, uint32_t line) +{ + static uint16_t max_legal_gain = 0x00E0; + uint8_t gain_msb, gain_lsb; + uint8_t intg_time_msb, intg_time_lsb; + uint8_t line_length_pck_msb, line_length_pck_lsb; + uint16_t line_length_pck, frame_length_lines, temp_lines; + uint32_t line_length_ratio = 1 * Q8; + int32_t rc = 0; + CDBG("sn12m0pz_write_exp_gain : gain = %d line = %d", gain, line); + + if (sn12m0pz_ctrl->sensormode != SENSOR_SNAPSHOT_MODE) { + if (sn12m0pz_ctrl->prev_res == QVGA_SIZE) { + frame_length_lines = SN12M0PZ_QVGA_SIZE_HEIGHT + + SN12M0PZ_VER_QVGA_BLK_LINES; + line_length_pck = SN12M0PZ_QVGA_SIZE_WIDTH + + SN12M0PZ_HRZ_QVGA_BLK_PIXELS; + if (line > (frame_length_lines - + IU060F_SN12M0PZ_OFFSET)) + line = frame_length_lines - + IU060F_SN12M0PZ_OFFSET; + sn12m0pz_ctrl->fps = (uint16_t) (120 * Q8); + } else { + if (sn12m0pz_ctrl->curr_res == QTR_SIZE) { + frame_length_lines = SN12M0PZ_QTR_SIZE_HEIGHT + + SN12M0PZ_VER_QTR_BLK_LINES; + line_length_pck = SN12M0PZ_QTR_SIZE_WIDTH + + SN12M0PZ_HRZ_QTR_BLK_PIXELS; + } else { + frame_length_lines = SN12M0PZ_HEIGHT + + SN12M0PZ_VER_FULL_BLK_LINES; + line_length_pck = SN12M0PZ_WIDTH + + SN12M0PZ_HRZ_FULL_BLK_PIXELS; + } + if (line > (frame_length_lines - + IU060F_SN12M0PZ_OFFSET)) + sn12m0pz_ctrl->fps = (uint16_t) (30 * Q8 * + (frame_length_lines - IU060F_SN12M0PZ_OFFSET) / line); + else + sn12m0pz_ctrl->fps = (uint16_t) (30 * Q8); + } + } else { + if (sn12m0pz_ctrl->curr_res == QTR_SIZE) { + frame_length_lines = SN12M0PZ_QTR_SIZE_HEIGHT + + SN12M0PZ_VER_QTR_BLK_LINES; + line_length_pck = SN12M0PZ_QTR_SIZE_WIDTH + + SN12M0PZ_HRZ_QTR_BLK_PIXELS; + } else { + frame_length_lines = SN12M0PZ_HEIGHT + + SN12M0PZ_VER_FULL_BLK_LINES; + line_length_pck = SN12M0PZ_WIDTH + + SN12M0PZ_HRZ_FULL_BLK_PIXELS; + } + } + if (gain > max_legal_gain) + /* range: 0 to 224 */ + gain = max_legal_gain; + temp_lines = line; + /* calculate line_length_ratio */ + if (line > (frame_length_lines - IU060F_SN12M0PZ_OFFSET)) { + line_length_ratio = (line * Q8) / (frame_length_lines - + IU060F_SN12M0PZ_OFFSET); + temp_lines = frame_length_lines - IU060F_SN12M0PZ_OFFSET; + if (line_length_ratio == 0) + line_length_ratio = 1 * Q8; + } else + line_length_ratio = 1 * Q8; + + line = (uint32_t) (line * sn12m0pz_ctrl->fps_divider/0x400); + + /* update gain registers */ + gain_msb = (uint8_t) ((gain & 0xFF00) >> 8); + gain_lsb = (uint8_t) (gain & 0x00FF); + + /* linear AFR horizontal stretch */ + line_length_pck = (uint16_t) (line_length_pck * line_length_ratio / Q8); + line_length_pck_msb = (uint8_t) ((line_length_pck & 0xFF00) >> 8); + line_length_pck_lsb = (uint8_t) (line_length_pck & 0x00FF); + + /* update line count registers */ + intg_time_msb = (uint8_t) ((temp_lines & 0xFF00) >> 8); + intg_time_lsb = (uint8_t) (temp_lines & 0x00FF); + + + if (sn12m0pz_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_ANALOGUE_GAIN_CODE_GLOBAL_MSB, + gain_msb) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_ANALOGUE_GAIN_CODE_GLOBAL_LSB, + gain_lsb) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_LINE_LENGTH_PCK_MSB, + line_length_pck_msb) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_LINE_LENGTH_PCK_LSB, + line_length_pck_lsb) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_MSB, + intg_time_msb) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_LSB, + intg_time_lsb) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF) < 0) + return rc; + + return rc; +} + + +static int32_t sn12m0pz_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc; + rc = sn12m0pz_write_exp_gain(gain, line); + return rc; +} + +static int32_t sn12m0pz_test(enum sn12m0pz_test_mode_t mo) +{ + uint8_t test_data_val_msb = 0x07; + uint8_t test_data_val_lsb = 0xFF; + int32_t rc = 0; + if (mo == TEST_OFF) + return rc; + else { + /* REG_0x30D8[4] is TESBYPEN: 0: Normal Operation, + 1: Bypass Signal Processing. REG_0x30D8[5] is EBDMASK: + 0: Output Embedded data, 1: No output embedded data */ + + if (sn12m0pz_i2c_write_b_sensor(REG_0x30D8, 0x10) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_TEST_PATTERN_MODE, + (uint8_t) mo) < 0) + return rc; + + /* Solid Color Test Pattern */ + + if (mo == TEST_1) { + if (sn12m0pz_i2c_write_b_sensor(REG_TEST_DATA_RED_MSB, + test_data_val_msb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_TEST_DATA_RED_LSB, + test_data_val_lsb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor( + REG_TEST_DATA_GREENR_MSB, + test_data_val_msb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor( + REG_TEST_DATA_GREENR_LSB, + test_data_val_lsb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_TEST_DATA_BLUE_MSB, + test_data_val_msb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_TEST_DATA_BLUE_LSB, + test_data_val_lsb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor( + REG_TEST_DATA_GREENB_MSB, + test_data_val_msb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor( + REG_TEST_DATA_GREENB_LSB, + test_data_val_lsb) < 0) + return rc; + } + + } + + return rc; +} + +static int32_t sn12m0pz_reset(void) +{ + int32_t rc = 0; + /* register 0x0002 is Port 2, CAM_XCLRO */ + gpio_direction_output(sn12m0pz_ctrl-> + sensordata->sensor_reset, + 0); + msleep(50); + gpio_direction_output(sn12m0pz_ctrl-> + sensordata->sensor_reset, + 1); + msleep(13); + return rc; +} + +static int32_t sn12m0pz_sensor_setting(int update_type, int rt) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + + switch (update_type) { + case UPDATE_PERIODIC: + /* Put Sensor into sofware standby mode */ + if (sn12m0pz_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + msleep(5); + /* Hardware reset D02, lane config between full size/qtr size*/ + rc = sn12m0pz_reset(); + if (rc < 0) + return rc; + + if (sn12m0pz_stmipid02_config() < 0) + return rc; + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE + || rt == RES_VIDEO_120FPS) { + /* reset fps_divider */ + sn12m0pz_ctrl->fps_divider = 1 * 0x400; + + /* PLL settings */ + if (sn12m0pz_i2c_write_b_sensor(REG_PLL_MULTIPLIER, + sn12m0pz_regs.reg_pat[rt].pll_multiplier_lsb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x302B, + sn12m0pz_regs.reg_pat_init[0].reg_0x302B) < 0) + return rc; + + /* MIPI Enable Settings */ + if (sn12m0pz_i2c_write_b_sensor(REG_0x30E5, + sn12m0pz_regs.reg_pat_init[0].reg_0x30E5) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_0x3300, + sn12m0pz_regs.reg_pat_init[0].reg_0x3300) < 0) + return rc; + + /* Global Setting */ + if ( + sn12m0pz_i2c_write_b_sensor( + REG_IMAGE_ORIENTATION, + sn12m0pz_regs.reg_pat_init[0].image_orient) < 0) + return rc; + if ( + sn12m0pz_i2c_write_b_sensor( + REG_COARSE_INTEGRATION_TIME_MSB, + sn12m0pz_regs.reg_pat[rt].coarse_integ_time_msb) + < 0) + return rc; + if ( + sn12m0pz_i2c_write_b_sensor( + REG_COARSE_INTEGRATION_TIME_LSB, + sn12m0pz_regs.reg_pat[rt].coarse_integ_time_lsb) + < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x300A, + sn12m0pz_regs.reg_pat_init[0].reg_0x300A) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3014, + sn12m0pz_regs.reg_pat_init[0].reg_0x3014) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3015, + sn12m0pz_regs.reg_pat_init[0].reg_0x3015) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3017, + sn12m0pz_regs.reg_pat_init[0].reg_0x3017) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x301C, + sn12m0pz_regs.reg_pat_init[0].reg_0x301C) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3031, + sn12m0pz_regs.reg_pat_init[0].reg_0x3031) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3040, + sn12m0pz_regs.reg_pat_init[0].reg_0x3040) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3041, + sn12m0pz_regs.reg_pat_init[0].reg_0x3041) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3051, + sn12m0pz_regs.reg_pat_init[0].reg_0x3051) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3053, + sn12m0pz_regs.reg_pat_init[0].reg_0x3053) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3055, + sn12m0pz_regs.reg_pat_init[0].reg_0x3055) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3057, + sn12m0pz_regs.reg_pat_init[0].reg_0x3057) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3060, + sn12m0pz_regs.reg_pat_init[0].reg_0x3060) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3065, + sn12m0pz_regs.reg_pat_init[0].reg_0x3065) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x30AA, + sn12m0pz_regs.reg_pat_init[0].reg_0x30AA) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x30AB, + sn12m0pz_regs.reg_pat_init[0].reg_0x30AB) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x30B0, + sn12m0pz_regs.reg_pat_init[0].reg_0x30B0) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x30B2, + sn12m0pz_regs.reg_pat_init[0].reg_0x30B2) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_0x30D3, + sn12m0pz_regs.reg_pat_init[0].reg_0x30D3) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x30D8, + sn12m0pz_regs.reg_pat_init[0].reg_0x30D8) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_0x3106, + sn12m0pz_regs.reg_pat_init[0].reg_0x3106) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3108, + sn12m0pz_regs.reg_pat_init[0].reg_0x3108) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x310A, + sn12m0pz_regs.reg_pat_init[0].reg_0x310A) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x310C, + sn12m0pz_regs.reg_pat_init[0].reg_0x310C) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x310E, + sn12m0pz_regs.reg_pat_init[0].reg_0x310E) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3126, + sn12m0pz_regs.reg_pat_init[0].reg_0x3126) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x312E, + sn12m0pz_regs.reg_pat_init[0].reg_0x312E) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x313C, + sn12m0pz_regs.reg_pat_init[0].reg_0x313C) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x313E, + sn12m0pz_regs.reg_pat_init[0].reg_0x313E) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3140, + sn12m0pz_regs.reg_pat_init[0].reg_0x3140) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3142, + sn12m0pz_regs.reg_pat_init[0].reg_0x3142) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3144, + sn12m0pz_regs.reg_pat_init[0].reg_0x3144) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3148, + sn12m0pz_regs.reg_pat_init[0].reg_0x3148) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x314A, + sn12m0pz_regs.reg_pat_init[0].reg_0x314A) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3166, + sn12m0pz_regs.reg_pat_init[0].reg_0x3166) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3168, + sn12m0pz_regs.reg_pat_init[0].reg_0x3168) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x316F, + sn12m0pz_regs.reg_pat_init[0].reg_0x316F) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3171, + sn12m0pz_regs.reg_pat_init[0].reg_0x3171) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3173, + sn12m0pz_regs.reg_pat_init[0].reg_0x3173) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3175, + sn12m0pz_regs.reg_pat_init[0].reg_0x3175) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3177, + sn12m0pz_regs.reg_pat_init[0].reg_0x3177) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3179, + sn12m0pz_regs.reg_pat_init[0].reg_0x3179) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x317B, + sn12m0pz_regs.reg_pat_init[0].reg_0x317B) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x317D, + sn12m0pz_regs.reg_pat_init[0].reg_0x317D) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x317F, + sn12m0pz_regs.reg_pat_init[0].reg_0x317F) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3181, + sn12m0pz_regs.reg_pat_init[0].reg_0x3181) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3184, + sn12m0pz_regs.reg_pat_init[0].reg_0x3184) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3185, + sn12m0pz_regs.reg_pat_init[0].reg_0x3185) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3187, + sn12m0pz_regs.reg_pat_init[0].reg_0x3187) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_0x31A4, + sn12m0pz_regs.reg_pat_init[0].reg_0x31A4) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x31A6, + sn12m0pz_regs.reg_pat_init[0].reg_0x31A6) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x31AC, + sn12m0pz_regs.reg_pat_init[0].reg_0x31AC) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x31AE, + sn12m0pz_regs.reg_pat_init[0].reg_0x31AE) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x31B4, + sn12m0pz_regs.reg_pat_init[0].reg_0x31B4) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x31B6, + sn12m0pz_regs.reg_pat_init[0].reg_0x31B6) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_0x3254, + sn12m0pz_regs.reg_pat_init[0].reg_0x3254) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3256, + sn12m0pz_regs.reg_pat_init[0].reg_0x3256) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3258, + sn12m0pz_regs.reg_pat_init[0].reg_0x3258) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x325A, + sn12m0pz_regs.reg_pat_init[0].reg_0x325A) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3260, + sn12m0pz_regs.reg_pat_init[0].reg_0x3260) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3262, + sn12m0pz_regs.reg_pat_init[0].reg_0x3262) < 0) + return rc; + + + if (sn12m0pz_i2c_write_b_sensor(REG_0x3304, + sn12m0pz_regs.reg_pat_init[0].reg_0x3304) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3305, + sn12m0pz_regs.reg_pat_init[0].reg_0x3305) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3306, + sn12m0pz_regs.reg_pat_init[0].reg_0x3306) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3307, + sn12m0pz_regs.reg_pat_init[0].reg_0x3307) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3308, + sn12m0pz_regs.reg_pat_init[0].reg_0x3308) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3309, + sn12m0pz_regs.reg_pat_init[0].reg_0x3309) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x330A, + sn12m0pz_regs.reg_pat_init[0].reg_0x330A) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x330B, + sn12m0pz_regs.reg_pat_init[0].reg_0x330B) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x330C, + sn12m0pz_regs.reg_pat_init[0].reg_0x330C) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x330D, + sn12m0pz_regs.reg_pat_init[0].reg_0x330D) < 0) + return rc; + + /* Mode setting */ + /* Update registers with correct + frame_length_line value for AFR */ + total_lines_per_frame = (uint16_t)( + (sn12m0pz_regs.reg_pat[rt].frame_length_lines_msb << 8) + & 0xFF00) + + sn12m0pz_regs.reg_pat[rt].frame_length_lines_lsb; + total_lines_per_frame = total_lines_per_frame * + sn12m0pz_ctrl->fps_divider / 0x400; + + if (sn12m0pz_i2c_write_b_sensor( + REG_FRAME_LENGTH_LINES_MSB, + (total_lines_per_frame & 0xFF00) >> 8) + < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor( + REG_FRAME_LENGTH_LINES_LSB, + (total_lines_per_frame & 0x00FF)) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_LINE_LENGTH_PCK_MSB, + sn12m0pz_regs.reg_pat[rt].line_length_pck_msb) < + 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_LINE_LENGTH_PCK_LSB, + sn12m0pz_regs.reg_pat[rt].line_length_pck_lsb) < + 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_X_OUTPUT_SIZE_MSB, + sn12m0pz_regs.reg_pat[rt].x_output_size_msb) < + 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_X_OUTPUT_SIZE_LSB, + sn12m0pz_regs.reg_pat[rt].x_output_size_lsb) < + 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_Y_OUTPUT_SIZE_MSB, + sn12m0pz_regs.reg_pat[rt].y_output_size_msb) < + 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_Y_OUTPUT_SIZE_LSB, + sn12m0pz_regs.reg_pat[rt].y_output_size_lsb) < + 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_X_EVEN_INC_LSB, + sn12m0pz_regs.reg_pat[rt].x_even_inc_lsb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_X_ODD_INC_LSB, + sn12m0pz_regs.reg_pat[rt].x_odd_inc_lsb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_Y_EVEN_INC_LSB, + sn12m0pz_regs.reg_pat[rt].y_even_inc_lsb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_Y_ODD_INC_LSB, + sn12m0pz_regs.reg_pat[rt].y_odd_inc_lsb) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3016, + sn12m0pz_regs.reg_pat[rt].reg_0x3016) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x30E8, + sn12m0pz_regs.reg_pat[rt].reg_0x30E8) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x3301, + sn12m0pz_regs.reg_pat[rt].reg_0x3301) < 0) + return rc; + + if (sn12m0pz_i2c_write_b_sensor(REG_0x0344, + sn12m0pz_regs.reg_pat[rt].reg_0x0344) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x0345, + sn12m0pz_regs.reg_pat[rt].reg_0x0345) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x0346, + sn12m0pz_regs.reg_pat[rt].reg_0x0346) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x0347, + sn12m0pz_regs.reg_pat[rt].reg_0x0347) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x0348, + sn12m0pz_regs.reg_pat[rt].reg_0x0348) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x0349, + sn12m0pz_regs.reg_pat[rt].reg_0x0349) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x034A, + sn12m0pz_regs.reg_pat[rt].reg_0x034A) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(REG_0x034B, + sn12m0pz_regs.reg_pat[rt].reg_0x034B) < 0) + return rc; + + if ((rt == RES_CAPTURE) && fullsize_cropped_at_8mp) { + /* x address end */ + if (sn12m0pz_i2c_write_b_sensor(0x0348, + 0x0C) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(0x0349, + 0x0CF) < 0) + return rc; + /* y address end */ + if (sn12m0pz_i2c_write_b_sensor(0x034A, + 0x09) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor(0x034B, + 0x9F) < 0) + return rc; + } + + if (mipi_config == IU060F_SN12M0PZ_STMIPID01) { + if (sn12m0pz_i2c_write_b_sensor( + REG_PLL_MULTIPLIER, 0x43) < 0) + return rc; + if (rt == RES_CAPTURE) { + if (sn12m0pz_i2c_write_b_sensor( + REG_0x3301, 0x01) < 0) + return rc; + if (sn12m0pz_i2c_write_b_sensor( + REG_0x3017, 0xE0) < 0) + return rc; + } + } + + if (sn12m0pz_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STREAM) < 0) + return rc; + + msleep(IU060F_SN12M0PZ_DELAY_MSECS); + + if (sn12m0pz_test(sn12m0pz_ctrl->set_test) < 0) + return rc; + + if (mipi_config == IU060F_SN12M0PZ_STMIPID02) + CDBG("%s,%d", __func__, __LINE__); + return rc; + } + default: + return rc; + } +} + + +static int32_t sn12m0pz_video_config(int mode) +{ + + int32_t rc = 0; + int rt; + + + if (mode == SENSOR_HFR_120FPS_MODE) + sn12m0pz_ctrl->prev_res = QVGA_SIZE; + + /* change sensor resolution if needed */ + if (sn12m0pz_ctrl->curr_res != sn12m0pz_ctrl->prev_res) { + if (sn12m0pz_ctrl->prev_res == QTR_SIZE) { + rt = RES_PREVIEW; + IU060F_SN12M0PZ_DELAY_MSECS = 35; /*measured on scope*/ + enable_single_D02_lane = 1; + } else if (sn12m0pz_ctrl->prev_res == QVGA_SIZE) { + rt = RES_VIDEO_120FPS; + IU060F_SN12M0PZ_DELAY_MSECS = 35; /*measured on scope*/ + enable_single_D02_lane = 0; + } else { + rt = RES_CAPTURE; + enable_single_D02_lane = 0; + } + + if (sn12m0pz_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + + sn12m0pz_ctrl->curr_res = sn12m0pz_ctrl->prev_res; + sn12m0pz_ctrl->sensormode = mode; + + return rc; +} +static int32_t sn12m0pz_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + if (sn12m0pz_ctrl->curr_res != sn12m0pz_ctrl->pict_res) { + if (sn12m0pz_ctrl->pict_res == QTR_SIZE) { + rt = RES_PREVIEW; + enable_single_D02_lane = 1; + } else { + rt = RES_CAPTURE; + IU060F_SN12M0PZ_DELAY_MSECS = 100;/*measured on scope*/ + enable_single_D02_lane = 0; + } + + if (sn12m0pz_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + + sn12m0pz_ctrl->curr_res = sn12m0pz_ctrl->pict_res; + sn12m0pz_ctrl->sensormode = mode; + return rc; +} + +static int32_t sn12m0pz_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + if (sn12m0pz_ctrl->curr_res != sn12m0pz_ctrl->pict_res) { + if (sn12m0pz_ctrl->pict_res == QTR_SIZE) { + rt = RES_PREVIEW; + enable_single_D02_lane = 1; + } else { + rt = RES_CAPTURE; + IU060F_SN12M0PZ_DELAY_MSECS = 100;/*measured on scope*/ + enable_single_D02_lane = 0; + } + if (sn12m0pz_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + sn12m0pz_ctrl->curr_res = sn12m0pz_ctrl->pict_res; + sn12m0pz_ctrl->sensormode = mode; + return rc; +} +static int32_t sn12m0pz_set_sensor_mode(int mode, + int res) +{ + int32_t rc; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + case SENSOR_HFR_120FPS_MODE: + rc = sn12m0pz_video_config(mode); + break; + + case SENSOR_SNAPSHOT_MODE: + rc = sn12m0pz_snapshot_config(mode); + break; + + case SENSOR_RAW_SNAPSHOT_MODE: + rc = sn12m0pz_raw_snapshot_config(mode); + break; + + default: + rc = -EINVAL; + break; + } + return rc; +} + +static int32_t sn12m0pz_power_down(void) +{ + return 0; +} + + +static int sn12m0pz_probe_init_done(const struct msm_camera_sensor_info *data) +{ + + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + gpio_direction_output(data->vcm_pwd, 0); + gpio_free(data->vcm_pwd); + return 0; +} + +static int sn12m0pz_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + unsigned short chipidl, chipidh; + CDBG("Requesting gpio"); + rc = gpio_request(data->sensor_reset, "sn12m0pz"); + CDBG(" sn12m0pz_probe_init_sensor"); + if (!rc) { + gpio_direction_output(data->sensor_reset, 0); + msleep(20); + gpio_direction_output(data->sensor_reset, 1); + msleep(13); + } else { + goto init_probe_done; + } + CDBG("Requestion gpio"); + rc = gpio_request(data->vcm_pwd, "sn12m0pz"); + CDBG(" sn12m0pz_probe_init_sensor"); + + if (!rc) { + gpio_direction_output(data->vcm_pwd, 0); + msleep(20); + gpio_direction_output(data->vcm_pwd, 1); + msleep(13); + } else { + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + goto init_probe_done; + } + + msleep(20); + + /* 3. Read sensor Model ID: */ + rc = sn12m0pz_i2c_read(0x0000, &chipidh, 1); + if (rc < 0) { + CDBG(" sn12m0pz_probe_init_sensor3"); + goto init_probe_fail; + } + rc = sn12m0pz_i2c_read(0x0001, &chipidl, 1); + if (rc < 0) { + CDBG(" sn12m0pz_probe_init_sensor4"); + goto init_probe_fail; + } + + /* 4. Compare sensor ID to SN12M0PZ ID: */ + if (chipidh != 0x00 || chipidl != 0x60) { + rc = -ENODEV; + CDBG("sn12m0pz_probe_init_sensor fail chip id doesnot match"); + goto init_probe_fail; + } + + msleep(SN12M0PZ_RESET_DELAY_MSECS); + + goto init_probe_done; + +init_probe_fail: + CDBG(" sn12m0pz_probe_init_sensor fails"); + sn12m0pz_probe_init_done(data); + +init_probe_done: + CDBG(" sn12m0pz_probe_init_sensor finishes"); + return rc; +} + +int sn12m0pz_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + CDBG("Calling sn12m0pz_sensor_open_init"); + + sn12m0pz_ctrl = kzalloc(sizeof(struct sn12m0pz_ctrl_t), GFP_KERNEL); + if (!sn12m0pz_ctrl) { + CDBG("sn12m0pz_init failed!"); + rc = -ENOMEM; + goto init_done; + } + + sn12m0pz_ctrl->fps_divider = 1 * 0x00000400; + sn12m0pz_ctrl->pict_fps_divider = 1 * 0x00000400; + sn12m0pz_ctrl->set_test = TEST_OFF; + sn12m0pz_ctrl->prev_res = QTR_SIZE; + sn12m0pz_ctrl->pict_res = FULL_SIZE; + sn12m0pz_ctrl->curr_res = INVALID_SIZE; + if (data) + sn12m0pz_ctrl->sensordata = data; + + if (rc < 0) + return rc; + + /* enable mclk first */ + msm_camio_clk_rate_set(SN12M0PZ_DEFAULT_CLOCK_RATE); + msleep(20); + msm_camio_camif_pad_reg_reset(); + msleep(20); + CDBG("Calling sn12m0pz_sensor_open_init"); + rc = sn12m0pz_probe_init_sensor(data); + + if (rc < 0) + goto init_fail; + /* send reset signal */ + if (mipi_config == IU060F_SN12M0PZ_STMIPID01) { + if (sn12m0pz_stmipid01_config() < 0) { + CDBG("Calling sn12m0pz_sensor_open_init fail"); + return rc; + } + } else { + if (sn12m0pz_ctrl->prev_res == QTR_SIZE) + enable_single_D02_lane = 1; + else /* FULL_SIZE */ + enable_single_D02_lane = 0; + + if (sn12m0pz_stmipid02_config() < 0) { + CDBG("Calling sn12m0pz_sensor_open_init fail"); + return rc; + } + } + + + if (sn12m0pz_ctrl->prev_res == QTR_SIZE) { + if (sn12m0pz_sensor_setting(REG_INIT, RES_PREVIEW) < 0) + return rc; + } else if (sn12m0pz_ctrl->prev_res == QVGA_SIZE) { + if (sn12m0pz_sensor_setting(REG_INIT, RES_VIDEO_120FPS) < 0) + return rc; + } else { + if (sn12m0pz_sensor_setting(REG_INIT, RES_CAPTURE) < 0) + return rc; + } + + if (sn12m0pz_af_init() < 0) + return rc; + sn12m0pz_ctrl->fps = 30*Q8; + if (rc < 0) + goto init_fail; + else + goto init_done; +init_fail: + CDBG(" init_fail"); + sn12m0pz_probe_init_done(data); + kfree(sn12m0pz_ctrl); +init_done: + CDBG("init_done"); + return rc; +} +static int __init sn12m0pz_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&sn12m0pz_wait_queue); + return 0; +} + +static const struct i2c_device_id sn12m0pz_i2c_id[] = { + { "sn12m0pz", 0}, + { } +}; + +static int sn12m0pz_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("sn12m0pz_probe called!"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed"); + goto probe_failure; + } + + sn12m0pz_sensorw = kzalloc(sizeof(struct sn12m0pz_work_t), GFP_KERNEL); + if (!sn12m0pz_sensorw) { + CDBG("kzalloc failed"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, sn12m0pz_sensorw); + sn12m0pz_init_client(client); + sn12m0pz_client = client; + + msleep(50); + + CDBG("sn12m0pz_probe successed! rc = %d", rc); + return 0; + +probe_failure: + CDBG("sn12m0pz_probe failed! rc = %d", rc); + return rc; +} + +static int __exit sn12m0pz_remove(struct i2c_client *client) +{ + struct sn12m0pz_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + sn12m0pz_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver sn12m0pz_i2c_driver = { + .id_table = sn12m0pz_i2c_id, + .probe = sn12m0pz_i2c_probe, + .remove = __exit_p(sn12m0pz_i2c_remove), + .driver = { + .name = "sn12m0pz", + }, +}; + +int sn12m0pz_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + int32_t rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + + mutex_lock(&sn12m0pz_mut); + + CDBG("sn12m0pz_sensor_config: cfgtype = %d", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + sn12m0pz_get_pict_fps(cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + sn12m0pz_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + sn12m0pz_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + sn12m0pz_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + sn12m0pz_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + sn12m0pz_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = sn12m0pz_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = + sn12m0pz_write_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + case CFG_SET_PICT_EXP_GAIN: + rc = + sn12m0pz_set_pict_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = sn12m0pz_set_sensor_mode(cdata.mode, + cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = sn12m0pz_power_down(); + break; + + case CFG_MOVE_FOCUS: + rc = sn12m0pz_move_focus(cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = sn12m0pz_set_default_focus(cdata.cfg.focus.steps); + break; + + case CFG_SET_EFFECT: + rc = 0; + break; + case CFG_SET_LENS_SHADING: + rc = 0; + break; + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&sn12m0pz_mut); + + return rc; +} + +static int sn12m0pz_sensor_release(void) +{ + int rc = -EBADF; + + mutex_lock(&sn12m0pz_mut); + + sn12m0pz_power_down(); + + gpio_direction_output(sn12m0pz_ctrl->sensordata->sensor_reset, + 0); + gpio_free(sn12m0pz_ctrl->sensordata->sensor_reset); + + gpio_direction_output(sn12m0pz_ctrl->sensordata->vcm_pwd, + 0); + gpio_free(sn12m0pz_ctrl->sensordata->vcm_pwd); + + kfree(sn12m0pz_ctrl); + sn12m0pz_ctrl = NULL; + + CDBG("sn12m0pz_release completed"); + + + mutex_unlock(&sn12m0pz_mut); + + return rc; +} + +static int sn12m0pz_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc; + + rc = i2c_add_driver(&sn12m0pz_i2c_driver); + if (rc < 0 || sn12m0pz_client == NULL) { + rc = -ENOTSUPP; + goto probe_fail; + } + + msm_camio_clk_rate_set(SN12M0PZ_DEFAULT_CLOCK_RATE); + msleep(20); + + rc = sn12m0pz_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + + s->s_init = sn12m0pz_sensor_open_init; + s->s_release = sn12m0pz_sensor_release; + s->s_config = sn12m0pz_sensor_config; + s->s_mount_angle = 0; + sn12m0pz_probe_init_done(info); + + return rc; + +probe_fail: + CDBG("SENSOR PROBE FAILS!"); + i2c_del_driver(&sn12m0pz_i2c_driver); + return rc; +} + +static int __sn12m0pz_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, sn12m0pz_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __sn12m0pz_probe, + .driver = { + .name = "msm_camera_sn12m0pz", + .owner = THIS_MODULE, + }, +}; + +static int __init sn12m0pz_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(sn12m0pz_init); + +MODULE_DESCRIPTION("Sony 12M MP Bayer sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/video/msm/sn12m0pz.h b/drivers/media/video/msm/sn12m0pz.h new file mode 100644 index 00000000000..f2abc473421 --- /dev/null +++ b/drivers/media/video/msm/sn12m0pz.h @@ -0,0 +1,138 @@ + +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef SN12M0PZ_H +#define SN12M0PZ_H + +#include +extern struct sn12m0pz_reg sn12m0pz_regs; /* from mt9t013_reg.c */ +struct reg_struct{ + uint8_t pll_multiplier_lsb; /* 0x0307*/ + uint8_t coarse_integ_time_msb; /* 0x0202*/ + uint8_t coarse_integ_time_lsb; /* 0x0203*/ + uint8_t frame_length_lines_msb; /* 0x0340*/ + uint8_t frame_length_lines_lsb; /* 0x0341*/ + uint8_t line_length_pck_msb; /* 0x0342*/ + uint8_t line_length_pck_lsb; /* 0x0343*/ + uint8_t x_output_size_msb; /* 0x034C*/ + uint8_t x_output_size_lsb; /* 0x034D*/ + uint8_t y_output_size_msb; /* 0x034E*/ + uint8_t y_output_size_lsb; /* 0x034F*/ + uint8_t x_even_inc_lsb; /* 0x0381*/ + uint8_t x_odd_inc_lsb; /* 0x0383*/ + uint8_t y_even_inc_lsb; /* 0x0385*/ + uint8_t y_odd_inc_lsb; /* 0x0387*/ + uint8_t reg_0x3016; /* 0x3016 VMODEADD*/ + uint8_t reg_0x30E8; /* 0x30E8 HADDAVE*/ + uint8_t reg_0x3301; /* 0x3301 RGLANESEL*/ + /*added for 120fps support */ + uint8_t reg_0x0344; + uint8_t reg_0x0345; + uint8_t reg_0x0346; + uint8_t reg_0x0347; + uint8_t reg_0x0348; + uint8_t reg_0x0349; + uint8_t reg_0x034A; + uint8_t reg_0x034B; +}; +struct reg_struct_init{ + uint8_t reg_0x302B;/* 0x302B*/ + + uint8_t reg_0x30E5;/* 0x30E5*/ + uint8_t reg_0x3300; /* 0x3300*/ + + uint8_t image_orient; /* 0x0101*/ + + uint8_t reg_0x300A; /* 0x300A*/ + uint8_t reg_0x3014; /* 0x3014*/ + uint8_t reg_0x3015; /* 0x3015*/ + uint8_t reg_0x3017; /* 0x3017*/ + uint8_t reg_0x301C; /* 0x301C*/ + uint8_t reg_0x3031; /* 0x3031*/ + uint8_t reg_0x3040; /* 0x3040*/ + uint8_t reg_0x3041; /* 0x3041*/ + uint8_t reg_0x3051; /* 0x3051*/ + uint8_t reg_0x3053; /* 0x3053*/ + uint8_t reg_0x3055; /* 0x3055*/ + uint8_t reg_0x3057; /* 0x3057*/ + uint8_t reg_0x3060; /* 0x3060*/ + uint8_t reg_0x3065; /* 0x3065*/ + uint8_t reg_0x30AA; /* 0x30AA*/ + uint8_t reg_0x30AB; /* 0x30AB*/ + uint8_t reg_0x30B0; /* 0x30B0*/ + uint8_t reg_0x30B2; /* 0x30B2*/ + + uint8_t reg_0x30D3; /* 0X30D3*/ + uint8_t reg_0x30D8; /* 0X30D8*/ + + uint8_t reg_0x3106; /* 0x3106*/ + uint8_t reg_0x3108; /* 0x3108*/ + uint8_t reg_0x310A; /* 0x310A*/ + uint8_t reg_0x310C; /* 0x310C*/ + uint8_t reg_0x310E; /* 0x310E*/ + uint8_t reg_0x3126; /* 0x3126*/ + uint8_t reg_0x312E; /* 0x312E*/ + uint8_t reg_0x313C; /* 0x313C*/ + uint8_t reg_0x313E; /* 0x313E*/ + uint8_t reg_0x3140; /* 0x3140*/ + uint8_t reg_0x3142; /* 0x3142*/ + uint8_t reg_0x3144; /* 0x3144*/ + uint8_t reg_0x3148; /* 0x3148*/ + uint8_t reg_0x314A; /* 0x314A*/ + uint8_t reg_0x3166; /* 0x3166*/ + uint8_t reg_0x3168; /* 0x3168*/ + uint8_t reg_0x316F; /* 0x316F*/ + uint8_t reg_0x3171; /* 0x3171*/ + uint8_t reg_0x3173; /* 0x3173*/ + uint8_t reg_0x3175; /* 0x3175*/ + uint8_t reg_0x3177; /* 0x3177*/ + uint8_t reg_0x3179; /* 0x3179*/ + uint8_t reg_0x317B; /* 0x317B*/ + uint8_t reg_0x317D; /* 0x317D*/ + uint8_t reg_0x317F; /* 0x317F*/ + uint8_t reg_0x3181; /* 0x3181*/ + uint8_t reg_0x3184; /* 0x3184*/ + uint8_t reg_0x3185; /* 0x3185*/ + uint8_t reg_0x3187; /* 0x3187*/ + + uint8_t reg_0x31A4; /* 0x31A4*/ + uint8_t reg_0x31A6; /* 0x31A6*/ + uint8_t reg_0x31AC; /* 0x31AC*/ + uint8_t reg_0x31AE; /* 0x31AE*/ + uint8_t reg_0x31B4; /* 0x31B4*/ + uint8_t reg_0x31B6; /* 0x31B6*/ + + uint8_t reg_0x3254; /* 0x3254*/ + uint8_t reg_0x3256; /* 0x3256*/ + uint8_t reg_0x3258; /* 0x3258*/ + uint8_t reg_0x325A; /* 0x325A*/ + uint8_t reg_0x3260; /* 0x3260*/ + uint8_t reg_0x3262; /* 0x3262*/ + + uint8_t reg_0x3304; /* 0x3304*/ + uint8_t reg_0x3305; /* 0x3305*/ + uint8_t reg_0x3306; /* 0x3306*/ + uint8_t reg_0x3307; /* 0x3307*/ + uint8_t reg_0x3308; /* 0x3308*/ + uint8_t reg_0x3309; /* 0x3309*/ + uint8_t reg_0x330A; /* 0x330A*/ + uint8_t reg_0x330B; /* 0x330B*/ + uint8_t reg_0x330C; /* 0x330C*/ + uint8_t reg_0x330D; /* 0x330D*/ + +}; +struct sn12m0pz_reg{ + const struct reg_struct *reg_pat; + const struct reg_struct_init *reg_pat_init; +}; +#endif diff --git a/drivers/media/video/msm/sn12m0pz_reg.c b/drivers/media/video/msm/sn12m0pz_reg.c new file mode 100644 index 00000000000..d21eac1373c --- /dev/null +++ b/drivers/media/video/msm/sn12m0pz_reg.c @@ -0,0 +1,213 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "sn12m0pz.h" +/* Initialisation settings */ + +const struct reg_struct_init iu060f_reg_pat_init[1] = {{ + /* PLL setting */ + 0x4B, /* reg 0x302B*/ + /* MIPI Enable Setting */ + 0x04, /* reg 0x30E5*/ + 0x00, /* reg 0x3300*/ + /* Global Setting */ + 0x00, /* image_orientation*/ + 0x80, /* reg 0x300A*/ + 0x08, /* reg 0x3014*/ + 0x37, /* reg 0x3015*/ + 0x60, /* reg 0x3017*/ + 0x01, /* reg 0x301C*/ + 0x28, /* reg 0x3031*/ + 0x00, /* reg 0x3040*/ + 0x60, /* reg 0x3041*/ + 0x24, /* reg 0x3051*/ + 0x34, /* reg 0x3053*/ + 0x3B, /* reg 0x3055*/ + 0xC0, /* reg 0x3057*/ + 0x30, /* reg 0x3060*/ + 0x00, /* reg 0x3065*/ + 0x88, /* reg 0x30AA*/ + 0x1C, /* reg 0x30AB*/ + 0x32, /* reg 0x30B0*/ + 0x83, /* reg 0x30B2*/ + 0x04, /* reg 0x30D3*/ + 0xC0, /* reg 0x30D8*/ + 0x50, /* reg 0x3106*/ + 0xA5, /* reg 0x3108*/ + 0xA9, /* reg 0x310A*/ + 0x0C, /* reg 0x310C*/ + 0x55, /* reg 0x310E*/ + 0xCC, /* reg 0x3126*/ + 0x83, /* reg 0x312E*/ + 0xC7, /* reg 0x313C*/ + 0x07, /* reg 0x313E*/ + 0x32, /* reg 0x3140*/ + 0x35, /* reg 0x3142*/ + 0x35, /* reg 0x3144*/ + 0x73, /* reg 0x3148*/ + 0x80, /* reg 0x314A*/ + 0xBE, /* reg 0x3166*/ + 0xBD, /* reg 0x3168*/ + 0x82, /* reg 0x316F*/ + 0xBC, /* reg 0x3171*/ + 0x82, /* reg 0x3173*/ + 0xBC, /* reg 0x3175*/ + 0x0C, /* reg 0x3177*/ + 0x2C, /* reg 0x3179*/ + 0x83, /* reg 0x317B*/ + 0xAF, /* reg 0x317D*/ + 0x83, /* reg 0x317F*/ + 0xAF, /* reg 0x3181*/ + 0x06, /* reg 0x3184*/ + 0xBA, /* reg 0x3185*/ + 0xBE, /* reg 0x3187*/ + 0xD8, /* reg 0x31A4*/ + 0x17, /* reg 0x31A6*/ + 0xCF, /* reg 0x31AC*/ + 0xF1, /* reg 0x31AE*/ + 0xD8, /* reg 0x31B4*/ + 0x17, /* reg 0x31B6*/ + 0x09, /* reg 0x3254 */ + 0xC5, /* reg 0x3256 */ + 0x84, /* reg 0x3258 */ + 0x6C, /* reg 0x325A */ + 0x0B, /* reg 0x3260 */ + 0x09, /* reg 0x3262 */ + 0x05, /* reg 0x3304*/ + 0x04, /* reg 0x3305*/ + 0x15, /* reg 0x3306*/ + 0x03, /* reg 0x3307*/ + 0x13, /* reg 0x3308*/ + 0x05, /* reg 0x3309*/ + 0x0B, /* reg 0x330A*/ + 0x04, /* reg 0x330B*/ + 0x0B, /* reg 0x330C*/ + 0x06 /* reg 0x330D*/ +} +}; + +/* Preview / Snapshot register settings */ +const struct reg_struct iu060f_reg_pat[3] = { + { /* Preview */ + 0x22, /*0x1b*/ /* fps*/ + + /* Global Setting */ + 0x01, /* coarse_integration_time_msb*/ + 0xFF, /* coarse_integration_time_lsb*/ + + /* Mode Setting */ + /* V: 1/2 V-addition (1,3), + H: 1/2 H-averaging (1,3) */ + + 0x06, /* frame_length_lines_msb 0x0340*/ + 0x02, /* frame_length_lines_lsb 0x0341*/ + 0x10, /* line_length_pck_msb 0x0342*/ + 0x70, /* line_length_pck_lsb 0x0343*/ + 0x07, /* x_output_size_msb 0x034C*/ + 0xe0, /* x_output_size_lsb 0x034D*/ + 0x05, /* y_output_size_msb 0x034E*/ + 0xe8, /* y_output_size_lsb 0x034F*/ + 0x01, /* x_even_inc_lsb 0x0381*/ + 0x03, /* x_odd_inc_lsb 0x0383*/ + 0x01, /* y_even_inc_lsb 0x0385*/ + 0x03, /* y_odd_inc_lsb 0x0387*/ + 0x46, /* reg 0x3016 VMODEADD 0x3016*/ + 0x86, /* reg 0x30E8 HADDAVE 0x30E8*/ + 0x01, /* reg 0x3301 RGLANESEL 0x3301*/ + + 0x00, /* 0x0344 */ + 0x00, /* 0x0345 */ + 0x00, /* 0x0346 */ + 0x00, /* 0x0347 */ + 0x0F, /* 0x0348 */ + 0xBF, /* 0x0349 */ + 0x0B, /* 0x034A */ + 0xCF, /* 0x034B */ + }, + { /* Snapshot */ + 0x14, /* pll_multiplier_lsb // 20/10 fps*/ + /* 0x14 for pclk 96MHz at 7.5 fps */ + + /* Global Setting */ + 0x0B, /* coarse_integration_time_msb*/ + 0xFF, /* coarse_integration_time_lsb*/ + + /* Mode Setting */ + /* Full */ + 0x0C,/*frame_length_lines_msb 0x0340*/ + 0x02,/*frame_length_lines_lsb 0x0341*/ + 0x10,/*line_length_pck_msb 0x0342*/ + 0x70,/* line_length_pck_lsb 0x0343*/ + 0x0F,/* x_output_size_msb 0x034C*/ + 0xC0, /* x_output_size_lsb 0x034D*/ + 0x0B, /* y_output_size_msb 0x034E*/ + 0xD0, /* y_output_size_lsb 0x034F*/ + 0x01, /* x_even_inc_lsb 0x0381*/ + 0x01, /* x_odd_inc_lsb 0x0383*/ + 0x01, /* y_even_inc_lsb 0x0385*/ + 0x01, /* y_odd_inc_lsb 0x0387*/ + 0x06, /* reg 0x3016 VMODEADD 0x3016*/ + 0x06, /* reg 0x30E8 HADDAVE 0x30E8*/ + 0x00, /* reg 0x3301 RGLANESEL 0x3301*/ + + 0x00, /* 0x0344 */ + 0x00, /* 0x0345 */ + 0x00, /* 0x0346 */ + 0x00, /* 0x0347 */ + 0x0F, /* 0x0348 */ + 0xBF, /* 0x0349 */ + 0x0B, /* 0x034A */ + 0xCF, /* 0x034B */ + }, + /* 120 fps settings */ + { + 0x1B, /*0x1B fps*/ + /* Global Setting */ + 0x00, /* coarse_integration_time_msb*/ + 0xFE, /* coarse_integration_time_lsb*/ + + /* Mode Setting */ + /* V: 1/8 V-addition (9,7), + H: Full */ + + 0x01, /* frame_length_lines_msb 0x0340*/ + 0x01, /* frame_length_lines_lsb 0x0341*/ + 0x10, /* line_length_pck_msb 0x0342*/ + 0x70, /* line_length_pck_lsb 0x0343*/ + 0x0f, /* x_output_size_msb 0x034C*/ + 0xc0, /* x_output_size_lsb 0x034D*/ + 0x00, /* y_output_size_msb 0x034E*/ + 0xF8, /* y_output_size_lsb 0x034F*/ + 0x01, /* x_even_inc_lsb 0x0381*/ + 0x01, /* x_odd_inc_lsb 0x0383*/ + 0x09, /* y_even_inc_lsb 0x0385*/ + 0x07, /* y_odd_inc_lsb 0x0387*/ + 0x46, /* reg 0x3016 VMODEADD 0x3016*/ + 0x86, /* reg 0x30E8 HADDAVE 0x30E8*/ + 0x00, /* reg 0x3301 RGLANESEL 0x3301*/ + /* add for 120fps support */ + 0x00, /* 0x0344*/ + 0x00, /* 0x0345*/ + 0x02, /* 0x0346*/ + 0x10, /* 0x0347*/ + 0x0F, /* 0x0348*/ + 0xBF, /* 0x0349*/ + 0x09, /* 0x034A*/ + 0xCF, /* 0x034B*/ + } +}; +struct sn12m0pz_reg sn12m0pz_regs = { + .reg_pat = &iu060f_reg_pat[0], + .reg_pat_init = &iu060f_reg_pat_init[0], +}; + diff --git a/drivers/media/video/msm/vb6801.c b/drivers/media/video/msm/vb6801.c new file mode 100644 index 00000000000..fa825700c32 --- /dev/null +++ b/drivers/media/video/msm/vb6801.c @@ -0,0 +1,1616 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "vb6801.h" + +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ +enum { + REG_HOLD = 0x0104, + RELEASE_HOLD = 0x0000, + HOLD = 0x0001, + STANDBY_MODE = 0x0000, + REG_COARSE_INTEGRATION_TIME = 0x0202, + REG_ANALOGUE_GAIN_CODE_GLOBAL = 0x0204, + REG_RAMP_SCALE = 0x3116, + REG_POWER_MAN_ENABLE_3 = 0x3142, + REG_POWER_MAN_ENABLE_4 = 0x3143, + REG_POWER_MAN_ENABLE_5 = 0x3144, + REG_CCP2_DATA_FORMAT = 0x0112, + REG_PRE_PLL_CLK_DIV = 0x0304, + REG_PLL_MULTIPLIER = 0x0306, + REG_VT_SYS_CLK_DIV = 0x0302, + REG_VT_PIX_CLK_DIV = 0x0300, + REG_OP_SYS_CLK_DIV = 0x030A, + REG_OP_PIX_CLK_DIV = 0x0308, + REG_VT_LINE_LENGTH_PCK = 0x0342, + REG_X_OUTPUT_SIZE = 0x034C, + REG_Y_OUTPUT_SIZE = 0x034E, + REG_X_ODD_INC = 0x0382, + REG_Y_ODD_INC = 0x0386, + REG_VT_FRAME_LENGTH_LINES = 0x0340, + REG_ANALOG_TIMING_MODES_2 = 0x3113, + REG_BRUCE_ENABLE = 0x37B0, + REG_OP_CODER_SYNC_CLK_SETUP = 0x3400, + REG_OP_CODER_ENABLE = 0x3401, + REG_OP_CODER_SLOW_PAD_EN = 0x3402, + REG_OP_CODER_AUTO_STARTUP = 0x3414, + REG_SCYTHE_ENABLE = 0x3204, + REG_SCYTHE_WEIGHT = 0x3206, + REG_FRAME_COUNT = 0x0005, + REG_MODE_SELECT = 0x0100, + REG_CCP2_CHANNEL_IDENTIFIER = 0x0110, + REG_CCP2_SIGNALLING_MODE = 0x0111, + REG_BTL_LEVEL_SETUP = 0x311B, + REG_OP_CODER_AUTOMATIC_MODE_ENABLE = 0x3403, + REG_PLL_CTRL = 0x3801, + REG_VCM_DAC_CODE = 0x3860, + REG_VCM_DAC_STROBE = 0x3868, + REG_VCM_DAC_ENABLE = 0x386C, + REG_NVM_T1_ADDR_00 = 0x3600, + REG_NVM_T1_ADDR_01 = 0x3601, + REG_NVM_T1_ADDR_02 = 0x3602, + REG_NVM_T1_ADDR_03 = 0x3603, + REG_NVM_T1_ADDR_04 = 0x3604, + REG_NVM_T1_ADDR_05 = 0x3605, + REG_NVM_T1_ADDR_06 = 0x3606, + REG_NVM_T1_ADDR_07 = 0x3607, + REG_NVM_T1_ADDR_08 = 0x3608, + REG_NVM_T1_ADDR_09 = 0x3609, + REG_NVM_T1_ADDR_0A = 0x360A, + REG_NVM_T1_ADDR_0B = 0x360B, + REG_NVM_T1_ADDR_0C = 0x360C, + REG_NVM_T1_ADDR_0D = 0x360D, + REG_NVM_T1_ADDR_0E = 0x360E, + REG_NVM_T1_ADDR_0F = 0x360F, + REG_NVM_T1_ADDR_10 = 0x3610, + REG_NVM_T1_ADDR_11 = 0x3611, + REG_NVM_T1_ADDR_12 = 0x3612, + REG_NVM_T1_ADDR_13 = 0x3613, + REG_NVM_CTRL = 0x3680, + REG_NVM_PDN = 0x3681, + REG_NVM_PULSE_WIDTH = 0x368B, +}; + +#define VB6801_LINES_PER_FRAME_PREVIEW 800 +#define VB6801_LINES_PER_FRAME_SNAPSHOT 1600 +#define VB6801_PIXELS_PER_LINE_PREVIEW 2500 +#define VB6801_PIXELS_PER_LINE_SNAPSHOT 2500 + +/* AF constant */ +#define VB6801_TOTAL_STEPS_NEAR_TO_FAR 25 +#define VB6801_STEPS_NEAR_TO_CLOSEST_INF 25 + +/* for 30 fps preview */ +#define VB6801_DEFAULT_CLOCK_RATE 12000000 + +enum vb6801_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum vb6801_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; + +enum vb6801_setting_t { + RES_PREVIEW, + RES_CAPTURE +}; + +struct vb6801_work_t { + struct work_struct work; +}; + +struct sensor_dynamic_params_t { + uint16_t preview_pixelsPerLine; + uint16_t preview_linesPerFrame; + uint16_t snapshot_pixelsPerLine; + uint16_t snapshot_linesPerFrame; + uint8_t snapshot_changed_fps; + uint32_t pclk; +}; + +struct vb6801_sensor_info { + /* Sensor Configuration Input Parameters */ + uint32_t ext_clk_freq_mhz; + uint32_t target_frame_rate_fps; + uint32_t target_vt_pix_clk_freq_mhz; + uint32_t sub_sampling_factor; + uint32_t analog_binning_allowed; + uint32_t raw_mode; + uint32_t capture_mode; + + /* Image Readout Registers */ + uint32_t x_odd_inc; /* x pixel array addressing odd increment */ + uint32_t y_odd_inc; /* y pixel array addressing odd increment */ + uint32_t x_output_size; /* width of output image */ + uint32_t y_output_size; /* height of output image */ + + /* Declare data format */ + uint32_t ccp2_data_format; + + /* Clock Tree Registers */ + uint32_t pre_pll_clk_div; + uint32_t pll_multiplier; + uint32_t vt_sys_clk_div; + uint32_t vt_pix_clk_div; + uint32_t op_sys_clk_div; + uint32_t op_pix_clk_div; + + /* Video Timing Registers */ + uint32_t vt_line_length_pck; + uint32_t vt_frame_length_lines; + + /* Analogue Binning Registers */ + uint8_t vtiming_major; + uint8_t analog_timing_modes_4; + + /* Fine (pixel) Integration Time Registers */ + uint32_t fine_integration_time; + + /* Coarse (lines) Integration Time Limit Registers */ + uint32_t coarse_integration_time_max; + + /* Coarse (lines) Integration Timit Register (16-bit) */ + uint32_t coarse_integration_time; + + /* Analogue Gain Code Global Registers */ + uint32_t analogue_gain_code_global; + + /* Digital Gain Code Registers */ + uint32_t digital_gain_code; + + /* Overall gain (analogue & digital) code + * Note that this is not a real register but just + * an abstraction for the combination of analogue + * and digital gain */ + uint32_t gain_code; + + /* FMT Test Information */ + uint32_t pass_fail; + uint32_t day; + uint32_t month; + uint32_t year; + uint32_t tester; + uint32_t part_number; + + /* Autofocus controls */ + uint32_t vcm_dac_code; + int vcm_max_dac_code_step; + int vcm_proportional_factor; + int vcm_dac_code_spacing_ms; + + /* VCM NVM Characterisation Information */ + uint32_t vcm_dac_code_infinity_dn; + uint32_t vcm_dac_code_macro_up; + uint32_t vcm_dac_code_up_dn_delta; + + /* Internal Variables */ + uint32_t min_vt_frame_length_lines; +}; + +struct vb6801_work_t *vb6801_sensorw; +struct i2c_client *vb6801_client; + +struct vb6801_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + + int sensormode; + uint32_t factor_fps; /* init to 1 * 0x00000400 */ + uint16_t curr_fps; + uint16_t max_fps; + int8_t pict_exp_update; + int8_t reducel; + uint16_t curr_lens_pos; + uint16_t init_curr_lens_pos; + enum vb6801_resolution_t prev_res; + enum vb6801_resolution_t pict_res; + enum vb6801_resolution_t curr_res; + enum vb6801_test_mode_t set_test; + + struct vb6801_sensor_info s_info; + struct sensor_dynamic_params_t s_dynamic_params; +}; + +static struct vb6801_ctrl_t *vb6801_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(vb6801_wait_queue); +DEFINE_MUTEX(vb6801_mut); + +static int vb6801_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 2, + .buf = rxdata, + }, + }; + + if (i2c_transfer(vb6801_client->adapter, msgs, 2) < 0) { + CDBG("vb6801_i2c_rxdata failed!\n"); + return -EIO; + } + + return 0; +} + +static int32_t vb6801_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + + if (!rdata) + return -EIO; + + memset(buf, 0, sizeof(buf)); + + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + + rc = vb6801_i2c_rxdata(vb6801_client->addr, buf, rlen); + + if (rc < 0) { + CDBG("vb6801_i2c_read 0x%x failed!\n", raddr); + return rc; + } + + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + + return rc; +} + +static int32_t vb6801_i2c_read_table(struct vb6801_i2c_reg_conf_t *regs, + int items) +{ + int i; + int32_t rc = -EFAULT; + + for (i = 0; i < items; i++) { + unsigned short *buf = + regs->dlen == D_LEN_BYTE ? + (unsigned short *)®s->bdata : + (unsigned short *)®s->wdata; + rc = vb6801_i2c_read(regs->waddr, buf, regs->dlen + 1); + + if (rc < 0) { + CDBG("vb6801_i2c_read_table Failed!!!\n"); + break; + } + + regs++; + } + + return rc; +} + +static int32_t vb6801_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + + if (i2c_transfer(vb6801_client->adapter, msg, 1) < 0) { + CDBG("vb6801_i2c_txdata faild 0x%x\n", vb6801_client->addr); + return -EIO; + } + + return 0; +} + +static int32_t vb6801_i2c_write_b(unsigned short waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + + CDBG("i2c_write_b addr = %d, val = %d\n", waddr, bdata); + rc = vb6801_i2c_txdata(vb6801_client->addr, buf, 3); + + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + + return rc; +} + +static int32_t vb6801_i2c_write_w(unsigned short waddr, unsigned short wdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[4]; + + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + + CDBG("i2c_write_w addr = %d, val = %d, buf[2] = 0x%x, buf[3] = 0x%x\n", + waddr, wdata, buf[2], buf[3]); + + rc = vb6801_i2c_txdata(vb6801_client->addr, buf, 4); + if (rc < 0) { + CDBG("i2c_write_w failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + } + + return rc; +} + +static int32_t vb6801_i2c_write_table(struct vb6801_i2c_reg_conf_t *regs, + int items) +{ + int i; + int32_t rc = -EFAULT; + + for (i = 0; i < items; i++) { + rc = ((regs->dlen == D_LEN_BYTE) ? + vb6801_i2c_write_b(regs->waddr, regs->bdata) : + vb6801_i2c_write_w(regs->waddr, regs->wdata)); + + if (rc < 0) { + CDBG("vb6801_i2c_write_table Failed!!!\n"); + break; + } + + regs++; + } + + return rc; +} + +static int32_t vb6801_reset(const struct msm_camera_sensor_info *data) +{ + int rc; + + rc = gpio_request(data->sensor_reset, "vb6801"); + if (!rc) { + CDBG("sensor_reset SUcceeded\n"); + gpio_direction_output(data->sensor_reset, 0); + mdelay(50); + gpio_direction_output(data->sensor_reset, 1); + mdelay(13); + } else + CDBG("sensor_reset FAiled\n"); + + return rc; +} + +static int32_t vb6801_set_default_focus(void) +{ + int32_t rc = 0; + + /* FIXME: Default focus not supported */ + + return rc; +} + +static void vb6801_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint32_t divider; /*Q10 */ + uint32_t pclk_mult; /*Q10 */ + uint32_t d1; + uint32_t d2; + + d1 = + (uint32_t)( + (vb6801_ctrl->s_dynamic_params.preview_linesPerFrame * + 0x00000400) / + vb6801_ctrl->s_dynamic_params.snapshot_linesPerFrame); + + d2 = + (uint32_t)( + (vb6801_ctrl->s_dynamic_params.preview_pixelsPerLine * + 0x00000400) / + vb6801_ctrl->s_dynamic_params.snapshot_pixelsPerLine); + + + divider = (uint32_t) (d1 * d2) / 0x00000400; + + pclk_mult = (48 * 0x400) / 60; + + /* Verify PCLK settings and frame sizes. */ + *pfps = (uint16_t)((((fps * pclk_mult) / 0x00000400) * divider)/ + 0x00000400); +} + +static uint16_t vb6801_get_prev_lines_pf(void) +{ + if (vb6801_ctrl->prev_res == QTR_SIZE) + return vb6801_ctrl->s_dynamic_params.preview_linesPerFrame; + else + return vb6801_ctrl->s_dynamic_params.snapshot_linesPerFrame; +} + +static uint16_t vb6801_get_prev_pixels_pl(void) +{ + if (vb6801_ctrl->prev_res == QTR_SIZE) + return vb6801_ctrl->s_dynamic_params.preview_pixelsPerLine; + else + return vb6801_ctrl->s_dynamic_params.snapshot_pixelsPerLine; +} + +static uint16_t vb6801_get_pict_lines_pf(void) +{ + return vb6801_ctrl->s_dynamic_params.snapshot_linesPerFrame; +} + +static uint16_t vb6801_get_pict_pixels_pl(void) +{ + return vb6801_ctrl->s_dynamic_params.snapshot_pixelsPerLine; +} + +static uint32_t vb6801_get_pict_max_exp_lc(void) +{ + uint16_t snapshot_lines_per_frame; + + if (vb6801_ctrl->pict_res == QTR_SIZE) { + snapshot_lines_per_frame = + vb6801_ctrl->s_dynamic_params.preview_linesPerFrame - 3; + } else { + snapshot_lines_per_frame = + vb6801_ctrl->s_dynamic_params.snapshot_linesPerFrame - 3; + } + + return snapshot_lines_per_frame; +} + +static int32_t vb6801_set_fps(struct fps_cfg *fps) +{ + int32_t rc = 0; + + /* input is new fps in Q8 format */ + switch (fps->fps_div) { + case 7680: /* 30 * Q8 */ + vb6801_ctrl->factor_fps = 1; + break; + + case 3840: /* 15 * Q8 */ + vb6801_ctrl->factor_fps = 2; + break; + + case 2560: /* 10 * Q8 */ + vb6801_ctrl->factor_fps = 3; + break; + + case 1920: /* 7.5 * Q8 */ + vb6801_ctrl->factor_fps = 4; + break; + + default: + rc = -ENODEV; + break; + } + + return rc; +} + +static int32_t vb6801_write_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + uint16_t lpf; + + if (vb6801_ctrl->curr_res == SENSOR_FULL_SIZE) + lpf = VB6801_LINES_PER_FRAME_SNAPSHOT; + else + lpf = VB6801_LINES_PER_FRAME_PREVIEW; + + /* hold */ + rc = vb6801_i2c_write_w(REG_HOLD, HOLD); + if (rc < 0) + goto exp_gain_done; + + if ((vb6801_ctrl->curr_fps < + vb6801_ctrl->max_fps / vb6801_ctrl->factor_fps) && + (!vb6801_ctrl->pict_exp_update)) { + + if (vb6801_ctrl->reducel) { + + rc = vb6801_i2c_write_w(REG_VT_FRAME_LENGTH_LINES, + lpf * vb6801_ctrl->factor_fps); + + vb6801_ctrl->curr_fps = + vb6801_ctrl->max_fps / vb6801_ctrl->factor_fps; + + } else if (!vb6801_ctrl->reducel) { + + rc = vb6801_i2c_write_w(REG_COARSE_INTEGRATION_TIME, + line * vb6801_ctrl->factor_fps); + + vb6801_ctrl->reducel = 1; + } + } else if ((vb6801_ctrl->curr_fps > + vb6801_ctrl->max_fps / vb6801_ctrl->factor_fps) && + (!vb6801_ctrl->pict_exp_update)) { + + rc = vb6801_i2c_write_w(REG_VT_FRAME_LENGTH_LINES, + lpf * vb6801_ctrl->factor_fps); + + vb6801_ctrl->curr_fps = + vb6801_ctrl->max_fps / vb6801_ctrl->factor_fps; + + } else { + /* analogue_gain_code_global */ + rc = vb6801_i2c_write_w(REG_ANALOGUE_GAIN_CODE_GLOBAL, gain); + if (rc < 0) + goto exp_gain_done; + + /* coarse_integration_time */ + rc = vb6801_i2c_write_w(REG_COARSE_INTEGRATION_TIME, + line * vb6801_ctrl->factor_fps); + if (rc < 0) + goto exp_gain_done; + + vb6801_ctrl->pict_exp_update = 1; + } + + rc = vb6801_i2c_write_w(REG_HOLD, RELEASE_HOLD); + +exp_gain_done: + return rc; +} + +static int32_t vb6801_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + vb6801_ctrl->pict_exp_update = 1; + return vb6801_write_exp_gain(gain, line); +} + +static int32_t vb6801_power_down(void) +{ + int32_t rc = 0; + rc = vb6801_i2c_write_b(REG_NVM_PDN, 0); + + mdelay(5); + return rc; +} + +static int32_t vb6801_go_to_position(uint32_t target_vcm_dac_code, + struct vb6801_sensor_info *ps) +{ + /* Prior to running this function the following values must + * be initialised in the sensor data structure, PS + * ps->vcm_dac_code + * ps->vcm_max_dac_code_step + * ps->vcm_dac_code_spacing_ms */ + + int32_t rc = 0; + + ps->vcm_dac_code = target_vcm_dac_code; + + /* Restore Strobe to zero state */ + rc = vb6801_i2c_write_b(REG_VCM_DAC_STROBE, 0x00); + if (rc < 0) + return rc; + + /* Write 9-bit VCM DAC Code */ + rc = vb6801_i2c_write_w(REG_VCM_DAC_CODE, ps->vcm_dac_code); + if (rc < 0) + return rc; + + /* Generate a rising edge on the dac_strobe to latch + * new DAC value */ + + rc = vb6801_i2c_write_w(REG_VCM_DAC_STROBE, 0x01); + + return rc; +} + +static int32_t vb6801_move_focus(int direction, int32_t num_steps) +{ + int16_t step_direction; + int16_t actual_step; + int16_t next_position; + uint32_t step_size; + int16_t small_move[4]; + uint16_t i; + int32_t rc = 0; + + step_size = (vb6801_ctrl->s_info.vcm_dac_code_macro_up - + vb6801_ctrl->s_info.vcm_dac_code_infinity_dn) / + VB6801_TOTAL_STEPS_NEAR_TO_FAR; + + if (num_steps > VB6801_TOTAL_STEPS_NEAR_TO_FAR) + num_steps = VB6801_TOTAL_STEPS_NEAR_TO_FAR; + else if (num_steps == 0) + return -EINVAL; + + if (direction == MOVE_NEAR) + step_direction = 4; + else if (direction == MOVE_FAR) + step_direction = -4; + else + return -EINVAL; + + /* need to decide about default position and power supplied + * at start up and reset */ + if (vb6801_ctrl->curr_lens_pos < vb6801_ctrl->init_curr_lens_pos) + vb6801_ctrl->curr_lens_pos = vb6801_ctrl->init_curr_lens_pos; + + actual_step = (step_direction * num_steps); + + next_position = vb6801_ctrl->curr_lens_pos; + + for (i = 0; i < 4; i++) { + if (actual_step >= 0) + small_move[i] = + (i + 1) * actual_step / 4 - i * actual_step / 4; + + if (actual_step < 0) + small_move[i] = + (i + 1) * actual_step / 4 - i * actual_step / 4; + } + + if (next_position > 511) + next_position = 511; + else if (next_position < 0) + next_position = 0; + + /* for damping */ + for (i = 0; i < 4; i++) { + next_position = + (int16_t) (vb6801_ctrl->curr_lens_pos + small_move[i]); + + /* Writing the digital code for current to the actuator */ + CDBG("next_position in damping mode = %d\n", next_position); + + rc = vb6801_go_to_position(next_position, &vb6801_ctrl->s_info); + if (rc < 0) { + CDBG("go_to_position Failed!!!\n"); + return rc; + } + + vb6801_ctrl->curr_lens_pos = next_position; + if (i < 3) + mdelay(5); + } + + return rc; +} + +static int vb6801_read_nvm_data(struct vb6801_sensor_info *ps) +{ + /* +--------+------+------+----------------+---------------+ + * | Index | NVM | NVM | Name | Description | + * | | Addr | Byte | | | + * +--------+------+------+----------------+---------------+ + * | 0x3600 | 0 | 3 | nvm_t1_addr_00 | {PF[2:0]:Day[4:0]} | + * | 0x3601 | 0 | 2 | nvm_t1_addr_01 | {Month[3:0]:Year[3:0]} | + * | 0x3602 | 0 | 1 | nvm_t1_addr_02 | Tester[7:0] | + * | 0x3603 | 0 | 0 | nvm_t1_addr_03 | Part[15:8] | + * +--------+------+------+----------------+---------------+ + * | 0x3604 | 1 | 3 | nvm_t1_addr_04 | Part[7:0] | + * | 0x3605 | 1 | 2 | nvm_t1_addr_05 | StartWPM[7:0] | + * | 0x3606 | 1 | 1 | nvm_t1_addr_06 | Infinity[7:0] | + * | 0x3607 | 1 | 0 | nvm_t1_addr_07 | Macro[7:0] | + * +--------+------+------+----------------+---------------+ + * | 0x3608 | 2 | 3 | nvm_t1_addr_08 | Reserved | + * | 0x3609 | 2 | 2 | nvm_t1_addr_09 | Reserved | + * | 0x360A | 2 | 1 | nvm_t1_addr_0A | UpDown[7:0] | + * | 0x360B | 2 | 0 | nvm_t1_addr_0B | Reserved | + * +--------+------+------+----------------+---------------+ + * | 0x360C | 3 | 3 | nvm_t1_addr_0C | Reserved | + * | 0x360D | 3 | 2 | nvm_t1_addr_0D | Reserved | + * | 0x360E | 3 | 1 | nvm_t1_addr_0E | Reserved | + * | 0x360F | 3 | 0 | nvm_t1_addr_0F | Reserved | + * +--------+------+------+----------------+---------------+ + * | 0x3610 | 4 | 3 | nvm_t1_addr_10 | Reserved | + * | 0x3611 | 4 | 2 | nvm_t1_addr_11 | Reserved | + * | 0x3612 | 4 | 1 | nvm_t1_addr_12 | Reserved | + * | 0x3613 | 4 | 0 | nvm_t1_addr_13 | Reserved | + * +--------+------+------+----------------+---------------+*/ + + int32_t rc; + struct vb6801_i2c_reg_conf_t rreg[] = { + {REG_NVM_T1_ADDR_00, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_01, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_02, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_03, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_04, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_05, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_06, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_07, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_08, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_09, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_0A, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_0B, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_0C, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_0D, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_0E, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_0F, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_10, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_11, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_12, 0, 0, D_LEN_BYTE}, + {REG_NVM_T1_ADDR_13, 0, 0, D_LEN_BYTE}, + }; + + struct vb6801_i2c_reg_conf_t wreg[] = { + /* Enable NVM for Direct Reading */ + {REG_NVM_CTRL, 0, 2, D_LEN_BYTE}, + + /* Power up NVM */ + {REG_NVM_PDN, 0, 1, D_LEN_BYTE}, + }; + + rc = vb6801_i2c_write_table(wreg, ARRAY_SIZE(wreg)); + if (rc < 0) { + CDBG("I2C Write Table FAILED!!!\n"); + return rc; + } + + /* NVM Read Pulse Width + * ==================== + * nvm_pulse_width_us = nvm_pulse_width_ext_clk / ext_clk_freq_mhz + * Valid Range for Read Pulse Width = 400ns -> 3.0us + * Min ext_clk_freq_mhz = 6MHz => 3.0 * 6 = 18 + * Max ext_clk_freq_mhz = 27MHz => 0.4 * 27 = 10.8 + * Choose 15 as a common value + * - 15 / 6.0 = 2.5000us + * - 15 / 12.0 = 1.2500us + * - 15 / 27.0 = 0.5555us */ + rc = vb6801_i2c_write_w(REG_NVM_PULSE_WIDTH, 15); + if (rc < 0) { + rc = -EBUSY; + goto nv_shutdown; + } + + rc = vb6801_i2c_read_table(rreg, ARRAY_SIZE(rreg)); + if (rc < 0) { + CDBG("I2C Read Table FAILED!!!\n"); + rc = -EBUSY; + goto nv_shutdown; + } + + /* Decode and Save FMT Info */ + ps->pass_fail = (rreg[0].bdata & 0x00E0) >> 5; + ps->day = (rreg[0].bdata & 0x001F); + ps->month = (rreg[1].bdata & 0x00F0) >> 4; + ps->year = (rreg[1].bdata & 0x000F) + 2000; + ps->tester = rreg[2].bdata; + ps->part_number = (rreg[3].bdata << 8) + rreg[4].bdata; + + /* Decode and Save VCM Dac Values in data structure */ + ps->vcm_dac_code_infinity_dn = rreg[6].bdata; + ps->vcm_dac_code_macro_up = rreg[7].bdata << 1; + ps->vcm_dac_code_up_dn_delta = rreg[10].bdata; + +nv_shutdown: + /* Power Down NVM to extend life time */ + rc = vb6801_i2c_write_b(REG_NVM_PDN, 0); + + return rc; +} + +static int vb6801_config_sensor(int32_t ext_clk_freq_mhz, + int32_t target_frame_rate_fps, + int32_t target_vt_pix_clk_freq_mhz, + uint32_t sub_sampling_factor, + uint32_t analog_binning_allowed, + uint32_t raw_mode, int capture_mode, + enum vb6801_resolution_t res) +{ + uint32_t rc; + /* ext_clk_freq_mhz = 6.0 -> 27.0 MHz + * target_frame_rate_fps = 15 fps + * target_vt_pix_clk_freq_mhz = 24.0 -> 64.0MHz + * sub_sampling factor = 1, 2, 3, or 4 + * raw_mode factor = 10 + * + * capture_mode, 0 = CCP1 + * capture_mode, 1 = CCP2 + * capture_mode, 2 = 10-bit parallel + hsync + vsync */ + + /* Declare data format */ + uint32_t ccp2_data_format = 0x0A0A; + + /* Declare clock tree variables */ + int32_t min_pll_ip_freq_mhz = 6; + int32_t max_pll_op_freq_mhz = 640; + uint32_t pre_pll_clk_div = 1; + int32_t pll_ip_freq_mhz = 6; + uint32_t pll_multiplier = 100; + int32_t pll_op_freq_mhz = 600; + uint32_t vt_sys_clk_div = 1; + int32_t vt_sys_clk_freq_mhz = 600; + uint32_t vt_pix_clk_div = 10; + int32_t vt_pix_clk_freq_mhz = 60; + uint32_t op_sys_clk_div = 1; + int32_t op_sys_clk_freq_mhz = 60; + uint32_t op_pix_clk_div = 10; + int32_t op_pix_clk_freq_mhz = 60; + + /* Declare pixel array and frame timing variables */ + uint32_t x_pixel_array = 2064; + uint32_t y_pixel_array = 1544; + uint32_t x_even_inc = 1; + uint32_t x_odd_inc = 1; + uint32_t y_even_inc = 1; + uint32_t y_odd_inc = 1; + uint32_t x_output_size = 2064; + uint32_t y_output_size = 1544; + uint32_t additional_rows = 2; + uint32_t min_vt_frame_blanking_lines = 16; + uint32_t vt_line_length_pck = 2500; + uint32_t vt_line_length_us = 0; + uint32_t min_vt_frame_length_lines = 1562; + uint32_t vt_frame_length_lines = 1600; + uint32_t target_vt_frame_length_ms; /* 200 * 0x0001000 / 3; */ + uint32_t vt_frame_length_ms; /* 200 * 0x0001000 / 3; */ + uint32_t frame_rate_fps = 15; + + /* Coarse intergration time */ + uint32_t coarse_integration_time = 1597; + uint32_t coarse_integration_time_max_margin = 3; + uint16_t frame_count; + int timeout; + + struct vb6801_sensor_info *pinfo = &vb6801_ctrl->s_info; + + struct vb6801_i2c_reg_conf_t rreg[] = { + {REG_PRE_PLL_CLK_DIV, 0, 0, D_LEN_WORD}, + {REG_PLL_MULTIPLIER, 0, 0, D_LEN_WORD}, + {REG_VT_SYS_CLK_DIV, 0, 0, D_LEN_WORD}, + {REG_VT_PIX_CLK_DIV, 0, 0, D_LEN_WORD}, + {REG_OP_SYS_CLK_DIV, 0, 0, D_LEN_WORD}, + {REG_OP_PIX_CLK_DIV, 0, 0, D_LEN_WORD}, + {REG_FRAME_COUNT, 0, 0, D_LEN_BYTE}, + }; + + struct vb6801_i2c_reg_conf_t wreg2[] = { + {REG_POWER_MAN_ENABLE_3, 0, 95, D_LEN_BYTE}, + {REG_POWER_MAN_ENABLE_4, 0, 142, D_LEN_BYTE}, + {REG_POWER_MAN_ENABLE_5, 0, 7, D_LEN_BYTE}, + }; + + /* VIDEO TIMING CALCULATIONS + * ========================= */ + + /* Pixel Array Size */ + x_pixel_array = 2064; + y_pixel_array = 1544; + + /* set current resolution */ + vb6801_ctrl->curr_res = res; + + /* Analogue binning setup */ + if (pinfo->analog_binning_allowed > 0 && + pinfo->sub_sampling_factor == 4) { + + pinfo->vtiming_major = 1; + pinfo->analog_timing_modes_4 = 32; + } else if (pinfo->analog_binning_allowed > 0 && + pinfo->sub_sampling_factor == 2) { + + pinfo->vtiming_major = 1; + pinfo->analog_timing_modes_4 = 0; + } else { + + pinfo->vtiming_major = 0; + pinfo->analog_timing_modes_4 = 0; + } + + /* Sub-Sampling X & Y Odd Increments: valid values 1, 3, 5, 7 */ + x_even_inc = 1; + y_even_inc = 1; + x_odd_inc = (sub_sampling_factor << 1) - x_even_inc; + y_odd_inc = (sub_sampling_factor << 1) - y_even_inc; + + /* Output image size + * Must always be a multiple of 2 - round down */ + x_output_size = ((x_pixel_array / sub_sampling_factor) >> 1) << 1; + y_output_size = ((y_pixel_array / sub_sampling_factor) >> 1) << 1; + + /* Output data format */ + ccp2_data_format = (raw_mode << 8) + raw_mode; + + /* Pre PLL clock divider : valid values 1, 2 or 4 + * The 1st step is to ensure that PLL input frequency is as close + * as possible to the min allowed PLL input frequency. + * This yields the smallest step size in the PLL output frequency. */ + pre_pll_clk_div = + ((int)(ext_clk_freq_mhz / min_pll_ip_freq_mhz) >> 1) << 1; + if (pre_pll_clk_div < 2) + pre_pll_clk_div = 1; + + pll_ip_freq_mhz = ext_clk_freq_mhz / pre_pll_clk_div; + + /* Video Timing System Clock divider: valid values 1, 2, 4 + * Now need to work backwards through the clock tree to determine the + * 1st pass estimates for vt_sys_clk_freq_mhz and then the PLL output + * frequency.*/ + vt_sys_clk_freq_mhz = vt_pix_clk_div * target_vt_pix_clk_freq_mhz; + vt_sys_clk_div = max_pll_op_freq_mhz / vt_sys_clk_freq_mhz; + if (vt_sys_clk_div < 2) + vt_sys_clk_div = 1; + + /* PLL Mulitplier: min , max 106 */ + pll_op_freq_mhz = vt_sys_clk_div * vt_sys_clk_freq_mhz; + pll_multiplier = (pll_op_freq_mhz * 0x0001000) / pll_ip_freq_mhz; + + /* Calculate the acutal pll output frequency + * - the pll_multiplier calculation introduces a quantisation error + * due the integer nature of the pll multiplier */ + pll_op_freq_mhz = (pll_ip_freq_mhz * pll_multiplier) / 0x0001000; + + /* Re-calculate video timing clock frequencies based + * on actual PLL freq */ + vt_sys_clk_freq_mhz = pll_op_freq_mhz / vt_sys_clk_div; + vt_pix_clk_freq_mhz = ((vt_sys_clk_freq_mhz * 0x0001000) / + vt_pix_clk_div)/0x0001000; + + /* Output System Clock Divider: valid value 1, 2, 4, 6, 8 + * op_sys_clk_div = vt_sys_clk_div;*/ + op_sys_clk_div = (vt_sys_clk_div * sub_sampling_factor); + if (op_sys_clk_div < 2) + op_sys_clk_div = 1; + + /* Calculate output timing clock frequencies */ + op_sys_clk_freq_mhz = pll_op_freq_mhz / op_sys_clk_div; + op_pix_clk_freq_mhz = + (op_sys_clk_freq_mhz * 0x0001000) / (op_pix_clk_div * 0x0001000); + + /* Line length in pixels and us */ + vt_line_length_pck = 2500; + vt_line_length_us = + vt_line_length_pck * 0x0001000 / vt_pix_clk_freq_mhz; + + /* Target vt_frame_length_ms */ + target_vt_frame_length_ms = (1000 * 0x0001000 / target_frame_rate_fps); + + /* Frame length in lines */ + min_vt_frame_length_lines = + additional_rows + y_output_size + min_vt_frame_blanking_lines; + + vt_frame_length_lines = + ((1000 * target_vt_frame_length_ms) / vt_line_length_us); + + if (vt_frame_length_lines <= min_vt_frame_length_lines) + vt_frame_length_lines = min_vt_frame_length_lines; + + /* Calcuate the actual frame length in ms */ + vt_frame_length_ms = (vt_frame_length_lines * vt_line_length_us / 1000); + + /* Frame Rate in fps */ + frame_rate_fps = (1000 * 0x0001000 / vt_frame_length_ms); + + /* Set coarse integration to max */ + coarse_integration_time = + vt_frame_length_lines - coarse_integration_time_max_margin; + + CDBG("SENSOR VIDEO TIMING SUMMARY:\n"); + CDBG(" ============================\n"); + CDBG("ext_clk_freq_mhz = %d\n", ext_clk_freq_mhz); + CDBG("pre_pll_clk_div = %d\n", pre_pll_clk_div); + CDBG("pll_ip_freq_mhz = %d\n", pll_ip_freq_mhz); + CDBG("pll_multiplier = %d\n", pll_multiplier); + CDBG("pll_op_freq_mhz = %d\n", pll_op_freq_mhz); + CDBG("vt_sys_clk_div = %d\n", vt_sys_clk_div); + CDBG("vt_sys_clk_freq_mhz = %d\n", vt_sys_clk_freq_mhz); + CDBG("vt_pix_clk_div = %d\n", vt_pix_clk_div); + CDBG("vt_pix_clk_freq_mhz = %d\n", vt_pix_clk_freq_mhz); + CDBG("op_sys_clk_div = %d\n", op_sys_clk_div); + CDBG("op_sys_clk_freq_mhz = %d\n", op_sys_clk_freq_mhz); + CDBG("op_pix_clk_div = %d\n", op_pix_clk_div); + CDBG("op_pix_clk_freq_mhz = %d\n", op_pix_clk_freq_mhz); + CDBG("vt_line_length_pck = %d\n", vt_line_length_pck); + CDBG("vt_line_length_us = %d\n", vt_line_length_us/0x0001000); + CDBG("vt_frame_length_lines = %d\n", vt_frame_length_lines); + CDBG("vt_frame_length_ms = %d\n", vt_frame_length_ms/0x0001000); + CDBG("frame_rate_fps = %d\n", frame_rate_fps); + CDBG("ccp2_data_format = %d\n", ccp2_data_format); + CDBG("x_output_size = %d\n", x_output_size); + CDBG("y_output_size = %d\n", y_output_size); + CDBG("x_odd_inc = %d\n", x_odd_inc); + CDBG("y_odd_inc = %d\n", y_odd_inc); + CDBG("(vt_frame_length_lines * frame_rate_factor ) = %d\n", + (vt_frame_length_lines * vb6801_ctrl->factor_fps)); + CDBG("coarse_integration_time = %d\n", coarse_integration_time); + CDBG("pinfo->vcm_dac_code = %d\n", pinfo->vcm_dac_code); + CDBG("capture_mode = %d\n", capture_mode); + + /* RE-CONFIGURE SENSOR WITH NEW TIMINGS + * ==================================== + * Enter Software Standby Mode */ + rc = vb6801_i2c_write_b(REG_MODE_SELECT, 0); + if (rc < 0) { + CDBG("I2C vb6801_i2c_write_b FAILED!!!\n"); + return rc; + } + + /* Wait 100ms */ + mdelay(100); + + if (capture_mode == 0) { + + rc = vb6801_i2c_write_b(REG_CCP2_CHANNEL_IDENTIFIER, 0); + rc = vb6801_i2c_write_b(REG_CCP2_SIGNALLING_MODE, 0); + } else if (capture_mode == 1) { + + rc = vb6801_i2c_write_b(REG_CCP2_CHANNEL_IDENTIFIER, 0); + rc = vb6801_i2c_write_b(REG_CCP2_SIGNALLING_MODE, 1); + } + + { + struct vb6801_i2c_reg_conf_t wreg[] = { + /* Re-configure Sensor */ + {REG_CCP2_DATA_FORMAT, ccp2_data_format, 0, + D_LEN_WORD}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL, 128, 0, D_LEN_WORD}, + {REG_PRE_PLL_CLK_DIV, pre_pll_clk_div, 0, D_LEN_WORD}, + {REG_VT_SYS_CLK_DIV, vt_sys_clk_div, 0, D_LEN_WORD}, + {REG_VT_PIX_CLK_DIV, vt_pix_clk_div, 0, D_LEN_WORD}, + {REG_OP_SYS_CLK_DIV, vt_sys_clk_div, 0, D_LEN_WORD}, + {REG_OP_PIX_CLK_DIV, vt_pix_clk_div, 0, D_LEN_WORD}, + {REG_VT_LINE_LENGTH_PCK, vt_line_length_pck, 0, + D_LEN_WORD}, + {REG_X_OUTPUT_SIZE, x_output_size, 0, D_LEN_WORD}, + {REG_Y_OUTPUT_SIZE, y_output_size, 0, D_LEN_WORD}, + {REG_X_ODD_INC, x_odd_inc, 0, D_LEN_WORD}, + {REG_Y_ODD_INC, y_odd_inc, 0, D_LEN_WORD}, + {REG_VT_FRAME_LENGTH_LINES, + vt_frame_length_lines * vb6801_ctrl->factor_fps, 0, + D_LEN_WORD}, + {REG_COARSE_INTEGRATION_TIME, + coarse_integration_time, 0, D_LEN_WORD}, + /* Analogue Settings */ + {REG_ANALOG_TIMING_MODES_2, 0, 132, D_LEN_BYTE}, + {REG_RAMP_SCALE, 0, 5, D_LEN_BYTE}, + {REG_BTL_LEVEL_SETUP, 0, 11, D_LEN_BYTE}, + /* Enable Defect Correction */ + {REG_SCYTHE_ENABLE, 0, 1, D_LEN_BYTE}, + {REG_SCYTHE_WEIGHT, 0, 16, D_LEN_BYTE}, + {REG_BRUCE_ENABLE, 0, 1, D_LEN_BYTE}, + /* Auto Focus Configuration + * Please note that the DAC Code is a written as a + * 16-bit value 0 = infinity (no DAC current) */ + {REG_VCM_DAC_CODE, pinfo->vcm_dac_code, 0, D_LEN_WORD}, + {REG_VCM_DAC_STROBE, 0, 0, D_LEN_BYTE}, + {REG_VCM_DAC_ENABLE, 0, 1, D_LEN_BYTE}, + }; + + rc = vb6801_i2c_write_table(wreg, ARRAY_SIZE(wreg)); + if (rc < 0) { + CDBG("I2C Write Table FAILED!!!\n"); + return rc; + } + } + /* Parallel Interface Configuration */ + if (capture_mode >= 2) { + struct vb6801_i2c_reg_conf_t wreg1[] = { + {REG_OP_CODER_SYNC_CLK_SETUP, 0, 15, D_LEN_BYTE}, + {REG_OP_CODER_ENABLE, 0, 3, D_LEN_BYTE}, + {REG_OP_CODER_SLOW_PAD_EN, 0, 1, D_LEN_BYTE}, + {REG_OP_CODER_AUTOMATIC_MODE_ENABLE, 0, 3, D_LEN_BYTE}, + {REG_OP_CODER_AUTO_STARTUP, 0, 2, D_LEN_BYTE}, + }; + + rc = vb6801_i2c_write_table(wreg1, ARRAY_SIZE(wreg1)); + if (rc < 0) { + CDBG("I2C Write Table FAILED!!!\n"); + return rc; + } + } + + /* Enter Streaming Mode */ + rc = vb6801_i2c_write_b(REG_MODE_SELECT, 1); + if (rc < 0) { + CDBG("I2C Write Table FAILED!!!\n"); + return rc; + } + + /* Wait until the sensor starts streaming + * Poll until the reported frame_count value is != 0xFF */ + frame_count = 0xFF; + timeout = 2000; + while (frame_count == 0xFF && timeout > 0) { + rc = vb6801_i2c_read(REG_FRAME_COUNT, &frame_count, 1); + if (rc < 0) + return rc; + + CDBG("REG_FRAME_COUNT = 0x%x\n", frame_count); + timeout--; + } + + /* Post Streaming Configuration */ + + rc = vb6801_i2c_write_table(wreg2, ARRAY_SIZE(wreg2)); + if (rc < 0) { + CDBG("I2C Write Table FAILED!!!\n"); + return rc; + } + + rc = vb6801_i2c_read_table(rreg, ARRAY_SIZE(rreg)); + if (rc < 0) { + CDBG("I2C Read Table FAILED!!!\n"); + return rc; + } + + CDBG("REG_PRE_PLL_CLK_DIV = 0x%x\n", rreg[0].wdata); + CDBG("REG_PLL_MULTIPLIER = 0x%x\n", rreg[1].wdata); + CDBG("REG_VT_SYS_CLK_DIV = 0x%x\n", rreg[2].wdata); + CDBG("REG_VT_PIX_CLK_DIV = 0x%x\n", rreg[3].wdata); + CDBG("REG_OP_SYS_CLK_DIV = 0x%x\n", rreg[4].wdata); + CDBG("REG_OP_PIX_CLK_DIV = 0x%x\n", rreg[5].wdata); + CDBG("REG_FRAME_COUNT = 0x%x\n", rreg[6].bdata); + + mdelay(50); + frame_count = 0; + rc = vb6801_i2c_read(REG_FRAME_COUNT, &frame_count, 1); + CDBG("REG_FRAME_COUNT1 = 0x%x\n", frame_count); + + mdelay(150); + frame_count = 0; + rc = vb6801_i2c_read(REG_FRAME_COUNT, &frame_count, 1); + CDBG("REG_FRAME_COUNT2 = 0x%x\n", frame_count); + + mdelay(100); + frame_count = 0; + rc = vb6801_i2c_read(REG_FRAME_COUNT, &frame_count, 1); + CDBG("REG_FRAME_COUNT3 = 0x%x\n", frame_count); + + mdelay(250); + frame_count = 0; + rc = vb6801_i2c_read(REG_FRAME_COUNT, &frame_count, 1); + CDBG("REG_FRAME_COUNT4 = 0x%x\n", frame_count); + + return rc; +} + +static int vb6801_sensor_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_direction_output(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + return 0; +} + +static int vb6801_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&vb6801_wait_queue); + return 0; +} + +static int32_t vb6801_video_config(int mode, int res) +{ + int32_t rc = 0; + + vb6801_ctrl->prev_res = res; + vb6801_ctrl->curr_res = res; + vb6801_ctrl->sensormode = mode; + + rc = vb6801_config_sensor(12, 30, 60, 2, 1, 10, 2, RES_PREVIEW); + if (rc < 0) + return rc; + + rc = vb6801_i2c_read(REG_VT_LINE_LENGTH_PCK, + &vb6801_ctrl->s_dynamic_params. + preview_pixelsPerLine, 2); + if (rc < 0) + return rc; + + rc = vb6801_i2c_read(REG_VT_LINE_LENGTH_PCK, + &vb6801_ctrl->s_dynamic_params. + preview_linesPerFrame, 2); + + return rc; +} + +static int32_t vb6801_snapshot_config(int mode, int res) +{ + int32_t rc = 0; + + vb6801_ctrl->curr_res = vb6801_ctrl->pict_res; + vb6801_ctrl->sensormode = mode; + + rc = vb6801_config_sensor(12, 12, 48, 1, 1, 10, 2, RES_CAPTURE); + if (rc < 0) + return rc; + + rc = vb6801_i2c_read(REG_VT_LINE_LENGTH_PCK, + &vb6801_ctrl->s_dynamic_params. + snapshot_pixelsPerLine, 2); + if (rc < 0) + return rc; + + rc = vb6801_i2c_read(REG_VT_LINE_LENGTH_PCK, + &vb6801_ctrl->s_dynamic_params. + snapshot_linesPerFrame, 2); + + return rc; +} + +static int32_t vb6801_set_sensor_mode(int mode, int res) +{ + int32_t rc = 0; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = vb6801_video_config(mode, res); + break; + + case SENSOR_SNAPSHOT_MODE: + case SENSOR_RAW_SNAPSHOT_MODE: + rc = vb6801_snapshot_config(mode, res); + break; + + default: + rc = -EINVAL; + break; + } + + return rc; +} + +int vb6801_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + + if (copy_from_user(&cdata, + (void *)argp, sizeof(struct sensor_cfg_data))) + return -EFAULT; + + mutex_lock(&vb6801_mut); + + CDBG("vb6801_sensor_config, cfgtype = %d\n", cdata.cfgtype); + + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + vb6801_get_pict_fps(cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = vb6801_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = vb6801_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = vb6801_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = vb6801_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = vb6801_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = vb6801_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = vb6801_write_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + rc = vb6801_set_pict_exp_gain(cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = vb6801_set_sensor_mode(cdata.mode, cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = vb6801_power_down(); + break; + + case CFG_MOVE_FOCUS: + rc = vb6801_move_focus(cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = vb6801_set_default_focus(); + break; + + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&vb6801_mut); + + return rc; +} + +static int vb6801_sensor_release(void) +{ + int rc = -EBADF; + + mutex_lock(&vb6801_mut); + + vb6801_power_down(); + vb6801_sensor_init_done(vb6801_ctrl->sensordata); + kfree(vb6801_ctrl); + mutex_unlock(&vb6801_mut); + + return rc; +} + +static int vb6801_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + rc = -ENOTSUPP; + goto probe_failure; + } + + vb6801_sensorw = kzalloc(sizeof(struct vb6801_work_t), GFP_KERNEL); + + if (!vb6801_sensorw) { + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, vb6801_sensorw); + vb6801_init_client(client); + vb6801_client = client; + vb6801_client->addr = vb6801_client->addr >> 1; + + return 0; + +probe_failure: + if (vb6801_sensorw != NULL) { + kfree(vb6801_sensorw); + vb6801_sensorw = NULL; + } + return rc; +} + +static int __exit vb6801_i2c_remove(struct i2c_client *client) +{ + struct vb6801_work_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + vb6801_client = NULL; + kfree(sensorw); + return 0; +} + +static const struct i2c_device_id vb6801_i2c_id[] = { + {"vb6801", 0}, + {} +}; + +static struct i2c_driver vb6801_i2c_driver = { + .id_table = vb6801_i2c_id, + .probe = vb6801_i2c_probe, + .remove = __exit_p(vb6801_i2c_remove), + .driver = { + .name = "vb6801", + }, +}; + +static int vb6801_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int rc; + + struct vb6801_i2c_reg_conf_t rreg[] = { + {0x0000, 0, 0, D_LEN_BYTE}, + {0x0001, 0, 0, D_LEN_BYTE}, + }; + + rc = vb6801_reset(data); + if (rc < 0) + goto init_probe_done; + + mdelay(20); + + rc = vb6801_i2c_read_table(rreg, ARRAY_SIZE(rreg)); + if (rc < 0) { + CDBG("I2C Read Table FAILED!!!\n"); + goto init_probe_fail; + } + + /* 4. Compare sensor ID to VB6801 ID: */ + if (rreg[0].bdata != 0x03 || rreg[1].bdata != 0x53) { + CDBG("vb6801_sensor_init: sensor ID don't match!\n"); + goto init_probe_fail; + } + + goto init_probe_done; + +init_probe_fail: + vb6801_sensor_init_done(data); +init_probe_done: + return rc; +} + +int vb6801_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + int32_t rc; + struct vb6801_i2c_reg_conf_t wreg[] = { + {REG_MODE_SELECT, 0, STANDBY_MODE, D_LEN_BYTE}, + {0x0113, 0, 0x0A, D_LEN_BYTE}, + }; + + vb6801_ctrl = kzalloc(sizeof(struct vb6801_ctrl_t), GFP_KERNEL); + if (!vb6801_ctrl) { + rc = -ENOMEM; + goto open_init_fail1; + } + + vb6801_ctrl->factor_fps = 1 /** 0x00000400*/ ; + vb6801_ctrl->curr_fps = 7680; /* 30 * Q8 */ ; + vb6801_ctrl->max_fps = 7680; /* 30 * Q8 */ ; + vb6801_ctrl->pict_exp_update = 0; /* 30 * Q8 */ ; + vb6801_ctrl->reducel = 0; /* 30 * Q8 */ ; + + vb6801_ctrl->set_test = TEST_OFF; + vb6801_ctrl->prev_res = QTR_SIZE; + vb6801_ctrl->pict_res = FULL_SIZE; + + vb6801_ctrl->s_dynamic_params.preview_linesPerFrame = + VB6801_LINES_PER_FRAME_PREVIEW; + vb6801_ctrl->s_dynamic_params.preview_pixelsPerLine = + VB6801_PIXELS_PER_LINE_PREVIEW; + vb6801_ctrl->s_dynamic_params.snapshot_linesPerFrame = + VB6801_LINES_PER_FRAME_SNAPSHOT; + vb6801_ctrl->s_dynamic_params.snapshot_pixelsPerLine = + VB6801_PIXELS_PER_LINE_SNAPSHOT; + + if (data) + vb6801_ctrl->sensordata = data; + + /* enable mclk first */ + msm_camio_clk_rate_set(VB6801_DEFAULT_CLOCK_RATE); + mdelay(20); + + rc = vb6801_reset(data); + if (rc < 0) + goto open_init_fail1; + + rc = vb6801_i2c_write_table(wreg, ARRAY_SIZE(wreg)); + if (rc < 0) { + CDBG("I2C Write Table FAILED!!!\n"); + goto open_init_fail2; + } + + rc = vb6801_read_nvm_data(&vb6801_ctrl->s_info); + if (rc < 0) { + CDBG("vb6801_read_nvm_data FAILED!!!\n"); + goto open_init_fail2; + } + mdelay(66); + + rc = vb6801_config_sensor(12, 30, 60, 2, 1, 10, 2, RES_PREVIEW); + if (rc < 0) + goto open_init_fail2; + + goto open_init_done; + +open_init_fail2: + vb6801_sensor_init_done(data); +open_init_fail1: + kfree(vb6801_ctrl); +open_init_done: + return rc; +} + +static int vb6801_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = i2c_add_driver(&vb6801_i2c_driver); + if (rc < 0 || vb6801_client == NULL) { + rc = -ENOTSUPP; + goto probe_done; + } + + /* enable mclk first */ + msm_camio_clk_rate_set(VB6801_DEFAULT_CLOCK_RATE); + mdelay(20); + + rc = vb6801_probe_init_sensor(info); + if (rc < 0) + goto probe_done; + + s->s_init = vb6801_sensor_open_init; + s->s_release = vb6801_sensor_release; + s->s_config = vb6801_sensor_config; + s->s_mount_angle = 0; + vb6801_sensor_init_done(info); + +probe_done: + return rc; +} + +static int __vb6801_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, vb6801_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __vb6801_probe, + .driver = { + .name = "msm_camera_vb6801", + .owner = THIS_MODULE, + }, +}; + +static int __init vb6801_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(vb6801_init); +void vb6801_exit(void) +{ + i2c_del_driver(&vb6801_i2c_driver); +} diff --git a/drivers/media/video/msm/vb6801.h b/drivers/media/video/msm/vb6801.h new file mode 100644 index 00000000000..8248f8d97e0 --- /dev/null +++ b/drivers/media/video/msm/vb6801.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VB6801_H +#define VB6801_H + +#include + +extern struct vb6801_reg_t vb6801_regs; /* from vb6801_reg.c */ + +struct reg_struct { + uint16_t vt_pix_clk_div; /* 0x0300 */ + uint16_t vt_sys_clk_div; /* 0x0302 */ + uint16_t pre_pll_clk_div; /* 0x0304 */ + uint16_t pll_multiplier; /* 0x0306 */ + uint16_t op_pix_clk_div; /* 0x0308 */ + uint16_t op_sys_clk_div; /* 0x030A */ + uint16_t scale_m; /* 0x0404 */ + uint16_t row_speed; /* 0x3016 */ + uint16_t x_addr_start; /* 0x3004 */ + uint16_t x_addr_end; /* 0x3008 */ + uint16_t y_addr_start; /* 0x3002 */ + uint16_t y_addr_end; /* 0x3006 */ + uint16_t read_mode; /* 0x3040 */ + uint16_t x_output_size; /* 0x034C */ + uint16_t y_output_size; /* 0x034E */ + uint16_t line_length_pck; /* 0x300C */ + uint16_t frame_length_lines; /* 0x300A */ + uint16_t coarse_int_time; /* 0x3012 */ + uint16_t fine_int_time; /* 0x3014 */ +}; + +enum i2c_data_len { + D_LEN_BYTE, + D_LEN_WORD +}; + +struct vb6801_i2c_reg_conf_t { + unsigned short waddr; + unsigned short wdata; + uint8_t bdata; + enum i2c_data_len dlen; +}; + +struct vb6801_reg_t { + struct reg_struct const *reg_pat; + uint16_t reg_pat_size; + struct vb6801_i2c_reg_conf_t const *ttbl; + uint16_t ttbl_size; + struct vb6801_i2c_reg_conf_t const *lctbl; + uint16_t lctbl_size; + struct vb6801_i2c_reg_conf_t const *rftbl; + uint16_t rftbl_size; +}; + +#endif /* VB6801_H */ diff --git a/drivers/media/video/msm/vx6953.c b/drivers/media/video/msm/vx6953.c new file mode 100644 index 00000000000..17e5e2eb234 --- /dev/null +++ b/drivers/media/video/msm/vx6953.c @@ -0,0 +1,3666 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vx6953.h" + +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ + +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD_OFF 0x00 +#define GROUPED_PARAMETER_HOLD 0x01 +#define REG_MODE_SELECT 0x0100 +#define MODE_SELECT_STANDBY_MODE 0x00 +#define MODE_SELECT_STREAM 0x01 +/* Integration Time */ +#define REG_COARSE_INTEGRATION_TIME_HI 0x0202 +#define REG_COARSE_INTEGRATION_TIME_LO 0x0203 +/* Gain */ +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_HI 0x0204 +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_LO 0x0205 +/* Digital Gain */ +#define REG_DIGITAL_GAIN_GREEN_R_HI 0x020E +#define REG_DIGITAL_GAIN_GREEN_R_LO 0x020F +#define REG_DIGITAL_GAIN_RED_HI 0x0210 +#define REG_DIGITAL_GAIN_RED_LO 0x0211 +#define REG_DIGITAL_GAIN_BLUE_HI 0x0212 +#define REG_DIGITAL_GAIN_BLUE_LO 0x0213 +#define REG_DIGITAL_GAIN_GREEN_B_HI 0x0214 +#define REG_DIGITAL_GAIN_GREEN_B_LO 0x0215 +/* output bits setting */ +#define REG_0x0112 0x0112 +#define REG_0x0113 0x0113 +/* PLL registers */ +#define REG_VT_PIX_CLK_DIV 0x0301 +#define REG_PRE_PLL_CLK_DIV 0x0305 +#define REG_PLL_MULTIPLIER 0x0307 +#define REG_OP_PIX_CLK_DIV 0x0309 +#define REG_0x034c 0x034c +#define REG_0x034d 0x034d +#define REG_0x034e 0x034e +#define REG_0x034f 0x034f +#define REG_0x0387 0x0387 +#define REG_0x0383 0x0383 +#define REG_FRAME_LENGTH_LINES_HI 0x0340 +#define REG_FRAME_LENGTH_LINES_LO 0x0341 +#define REG_LINE_LENGTH_PCK_HI 0x0342 +#define REG_LINE_LENGTH_PCK_LO 0x0343 +#define REG_0x3030 0x3030 +#define REG_0x0111 0x0111 +#define REG_0x0136 0x0136 +#define REG_0x0137 0x0137 +#define REG_0x0b00 0x0b00 +#define REG_0x3001 0x3001 +#define REG_0x3004 0x3004 +#define REG_0x3007 0x3007 +#define REG_0x301a 0x301a +#define REG_0x3101 0x3101 +#define REG_0x3364 0x3364 +#define REG_0x3365 0x3365 +#define REG_0x0b83 0x0b83 +#define REG_0x0b84 0x0b84 +#define REG_0x0b85 0x0b85 +#define REG_0x0b88 0x0b88 +#define REG_0x0b89 0x0b89 +#define REG_0x0b8a 0x0b8a +#define REG_0x3005 0x3005 +#define REG_0x3010 0x3010 +#define REG_0x3036 0x3036 +#define REG_0x3041 0x3041 +#define REG_0x0b80 0x0b80 +#define REG_0x0900 0x0900 +#define REG_0x0901 0x0901 +#define REG_0x0902 0x0902 +#define REG_0x3016 0x3016 +#define REG_0x301d 0x301d +#define REG_0x317e 0x317e +#define REG_0x317f 0x317f +#define REG_0x3400 0x3400 +#define REG_0x303a 0x303a +#define REG_0x1716 0x1716 +#define REG_0x1717 0x1717 +#define REG_0x1718 0x1718 +#define REG_0x1719 0x1719 +#define REG_0x3006 0x3006 +#define REG_0x301b 0x301b +#define REG_0x3098 0x3098 +#define REG_0x309d 0x309d +#define REG_0x3011 0x3011 +#define REG_0x3035 0x3035 +#define REG_0x3045 0x3045 +#define REG_0x3210 0x3210 +#define REG_0x0111 0x0111 +#define REG_0x3410 0x3410 +#define REG_0x0b06 0x0b06 +#define REG_0x0b07 0x0b07 +#define REG_0x0b08 0x0b08 +#define REG_0x0b09 0x0b09 +#define REG_0x3640 0x3640 +/* Test Pattern */ +#define REG_TEST_PATTERN_MODE 0x0601 + +/*============================================================================ + TYPE DECLARATIONS +============================================================================*/ + +/* 16bit address - 8 bit context register structure */ +#define VX6953_STM5M0EDOF_OFFSET 9 +#define Q8 0x00000100 +#define Q10 0x00000400 +#define VX6953_STM5M0EDOF_MAX_SNAPSHOT_EXPOSURE_LINE_COUNT 2922 +#define VX6953_STM5M0EDOF_DEFAULT_MASTER_CLK_RATE 24000000 +#define VX6953_STM5M0EDOF_OP_PIXEL_CLOCK_RATE 79800000 +#define VX6953_STM5M0EDOF_VT_PIXEL_CLOCK_RATE 88670000 +/* Full Size */ +#define VX6953_FULL_SIZE_WIDTH 2608 +#define VX6953_FULL_SIZE_HEIGHT 1960 +#define VX6953_FULL_SIZE_DUMMY_PIXELS 1 +#define VX6953_FULL_SIZE_DUMMY_LINES 0 +/* Quarter Size */ +#define VX6953_QTR_SIZE_WIDTH 1304 +#define VX6953_QTR_SIZE_HEIGHT 980 +#define VX6953_QTR_SIZE_DUMMY_PIXELS 1 +#define VX6953_QTR_SIZE_DUMMY_LINES 0 +/* Blanking as measured on the scope */ +/* Full Size */ +#define VX6953_HRZ_FULL_BLK_PIXELS 348 +#define VX6953_VER_FULL_BLK_LINES 40 +/* Quarter Size */ +#define VX6953_HRZ_QTR_BLK_PIXELS 1628 +#define VX6953_VER_QTR_BLK_LINES 28 +#define MAX_LINE_LENGTH_PCK 8190 +#define MAX_FRAME_LENGTH_LINES 16383 +#define VX6953_REVISION_NUMBER_CUT2 0x10/*revision number for Cut2.0*/ +#define VX6953_REVISION_NUMBER_CUT3 0x20/*revision number for Cut3.0*/ +/* FIXME: Changes from here */ +struct vx6953_work_t { + struct work_struct work; +}; + +static struct vx6953_work_t *vx6953_sensorw; +static struct i2c_client *vx6953_client; + +struct vx6953_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + + uint32_t sensormode; + uint32_t fps_divider; /* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ + uint16_t fps; + + int16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + + enum vx6953_resolution_t prev_res; + enum vx6953_resolution_t pict_res; + enum vx6953_resolution_t curr_res; + enum vx6953_test_mode_t set_test; + enum sensor_revision_t sensor_type; + + enum edof_mode_t edof_mode; + + unsigned short imgaddr; +}; + + +static uint8_t vx6953_stm5m0edof_delay_msecs_stdby; +static uint16_t vx6953_stm5m0edof_delay_msecs_stream = 20; +static uint8_t count; +static struct vx6953_ctrl_t *vx6953_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(vx6953_wait_queue); +DEFINE_MUTEX(vx6953_mut); +static struct vx6953_i2c_reg_conf patch_tbl_cut2[] = { + {0xFB94, 0}, /*intialise Data Xfer Status reg*/ + {0xFB95, 0}, /*gain 1 (0x00)*/ + {0xFB96, 0}, /*gain 1.07 (0x10)*/ + {0xFB97, 0}, /*gain 1.14 (0x20)*/ + {0xFB98, 0}, /*gain 1.23 (0x30)*/ + {0xFB99, 0}, /*gain 1.33 (0x40)*/ + {0xFB9A, 0}, /*gain 1.45 (0x50)*/ + {0xFB9B, 0}, /*gain 1.6 (0x60)*/ + {0xFB9C, 0}, /*gain 1.78 (0x70)*/ + {0xFB9D, 2}, /*gain 2 (0x80)*/ + {0xFB9E, 2}, /*gain 2.29 (0x90)*/ + {0xFB9F, 3}, /*gain 2.67 (0xA0)*/ + {0xFBA0, 3}, /*gain 3.2 (0xB0)*/ + {0xFBA1, 4}, /*gain 4 (0xC0)*/ + {0xFBA2, 7}, /*gain 5.33 (0xD0)*/ + {0xFBA3, 10}, /*gain 8 (0xE0)*/ + {0xFBA4, 11}, /*gain 9.14 (0xE4)*/ + {0xFBA5, 13}, /*gain 10.67 (0xE8)*/ + {0xFBA6, 15}, /*gain 12.8 (0xEC)*/ + {0xFBA7, 19}, /*gain 16 (0xF0)*/ + {0xF800, 0x12}, + {0xF801, 0x06}, + {0xF802, 0xf7}, + {0xF803, 0x90}, + {0xF804, 0x02}, + {0xF805, 0x05}, + {0xF806, 0xe0}, + {0xF807, 0xff}, + {0xF808, 0x65}, + {0xF809, 0x7d}, + {0xF80A, 0x70}, + {0xF80B, 0x03}, + {0xF80C, 0x02}, + {0xF80D, 0xf9}, + {0xF80E, 0x1c}, + {0xF80F, 0x8f}, + {0xF810, 0x7d}, + {0xF811, 0xe4}, + {0xF812, 0xf5}, + {0xF813, 0x7a}, + {0xF814, 0x75}, + {0xF815, 0x78}, + {0xF816, 0x30}, + {0xF817, 0x75}, + {0xF818, 0x79}, + {0xF819, 0x53}, + {0xF81A, 0x85}, + {0xF81B, 0x79}, + {0xF81C, 0x82}, + {0xF81D, 0x85}, + {0xF81E, 0x78}, + {0xF81F, 0x83}, + {0xF820, 0xe0}, + {0xF821, 0xc3}, + {0xF822, 0x95}, + {0xF823, 0x7b}, + {0xF824, 0xf0}, + {0xF825, 0x74}, + {0xF826, 0x02}, + {0xF827, 0x25}, + {0xF828, 0x79}, + {0xF829, 0xf5}, + {0xF82A, 0x79}, + {0xF82B, 0xe4}, + {0xF82C, 0x35}, + {0xF82D, 0x78}, + {0xF82E, 0xf5}, + {0xF82F, 0x78}, + {0xF830, 0x05}, + {0xF831, 0x7a}, + {0xF832, 0xe5}, + {0xF833, 0x7a}, + {0xF834, 0xb4}, + {0xF835, 0x08}, + {0xF836, 0xe3}, + {0xF837, 0xe5}, + {0xF838, 0x7d}, + {0xF839, 0x70}, + {0xF83A, 0x04}, + {0xF83B, 0xff}, + {0xF83C, 0x02}, + {0xF83D, 0xf8}, + {0xF83E, 0xe4}, + {0xF83F, 0xe5}, + {0xF840, 0x7d}, + {0xF841, 0xb4}, + {0xF842, 0x10}, + {0xF843, 0x05}, + {0xF844, 0x7f}, + {0xF845, 0x01}, + {0xF846, 0x02}, + {0xF847, 0xf8}, + {0xF848, 0xe4}, + {0xF849, 0xe5}, + {0xF84A, 0x7d}, + {0xF84B, 0xb4}, + {0xF84C, 0x20}, + {0xF84D, 0x05}, + {0xF84E, 0x7f}, + {0xF84F, 0x02}, + {0xF850, 0x02}, + {0xF851, 0xf8}, + {0xF852, 0xe4}, + {0xF853, 0xe5}, + {0xF854, 0x7d}, + {0xF855, 0xb4}, + {0xF856, 0x30}, + {0xF857, 0x05}, + {0xF858, 0x7f}, + {0xF859, 0x03}, + {0xF85A, 0x02}, + {0xF85B, 0xf8}, + {0xF85C, 0xe4}, + {0xF85D, 0xe5}, + {0xF85E, 0x7d}, + {0xF85F, 0xb4}, + {0xF860, 0x40}, + {0xF861, 0x04}, + {0xF862, 0x7f}, + {0xF863, 0x04}, + {0xF864, 0x80}, + {0xF865, 0x7e}, + {0xF866, 0xe5}, + {0xF867, 0x7d}, + {0xF868, 0xb4}, + {0xF869, 0x50}, + {0xF86A, 0x04}, + {0xF86B, 0x7f}, + {0xF86C, 0x05}, + {0xF86D, 0x80}, + {0xF86E, 0x75}, + {0xF86F, 0xe5}, + {0xF870, 0x7d}, + {0xF871, 0xb4}, + {0xF872, 0x60}, + {0xF873, 0x04}, + {0xF874, 0x7f}, + {0xF875, 0x06}, + {0xF876, 0x80}, + {0xF877, 0x6c}, + {0xF878, 0xe5}, + {0xF879, 0x7d}, + {0xF87A, 0xb4}, + {0xF87B, 0x70}, + {0xF87C, 0x04}, + {0xF87D, 0x7f}, + {0xF87E, 0x07}, + {0xF87F, 0x80}, + {0xF880, 0x63}, + {0xF881, 0xe5}, + {0xF882, 0x7d}, + {0xF883, 0xb4}, + {0xF884, 0x80}, + {0xF885, 0x04}, + {0xF886, 0x7f}, + {0xF887, 0x08}, + {0xF888, 0x80}, + {0xF889, 0x5a}, + {0xF88A, 0xe5}, + {0xF88B, 0x7d}, + {0xF88C, 0xb4}, + {0xF88D, 0x90}, + {0xF88E, 0x04}, + {0xF88F, 0x7f}, + {0xF890, 0x09}, + {0xF891, 0x80}, + {0xF892, 0x51}, + {0xF893, 0xe5}, + {0xF894, 0x7d}, + {0xF895, 0xb4}, + {0xF896, 0xa0}, + {0xF897, 0x04}, + {0xF898, 0x7f}, + {0xF899, 0x0a}, + {0xF89A, 0x80}, + {0xF89B, 0x48}, + {0xF89C, 0xe5}, + {0xF89D, 0x7d}, + {0xF89E, 0xb4}, + {0xF89F, 0xb0}, + {0xF8A0, 0x04}, + {0xF8A1, 0x7f}, + {0xF8A2, 0x0b}, + {0xF8A3, 0x80}, + {0xF8A4, 0x3f}, + {0xF8A5, 0xe5}, + {0xF8A6, 0x7d}, + {0xF8A7, 0xb4}, + {0xF8A8, 0xc0}, + {0xF8A9, 0x04}, + {0xF8AA, 0x7f}, + {0xF8AB, 0x0c}, + {0xF8AC, 0x80}, + {0xF8AD, 0x36}, + {0xF8AE, 0xe5}, + {0xF8AF, 0x7d}, + {0xF8B0, 0xb4}, + {0xF8B1, 0xd0}, + {0xF8B2, 0x04}, + {0xF8B3, 0x7f}, + {0xF8B4, 0x0d}, + {0xF8B5, 0x80}, + {0xF8B6, 0x2d}, + {0xF8B7, 0xe5}, + {0xF8B8, 0x7d}, + {0xF8B9, 0xb4}, + {0xF8BA, 0xe0}, + {0xF8BB, 0x04}, + {0xF8BC, 0x7f}, + {0xF8BD, 0x0e}, + {0xF8BE, 0x80}, + {0xF8BF, 0x24}, + {0xF8C0, 0xe5}, + {0xF8C1, 0x7d}, + {0xF8C2, 0xb4}, + {0xF8C3, 0xe4}, + {0xF8C4, 0x04}, + {0xF8C5, 0x7f}, + {0xF8C6, 0x0f}, + {0xF8C7, 0x80}, + {0xF8C8, 0x1b}, + {0xF8C9, 0xe5}, + {0xF8CA, 0x7d}, + {0xF8CB, 0xb4}, + {0xF8CC, 0xe8}, + {0xF8CD, 0x04}, + {0xF8CE, 0x7f}, + {0xF8CF, 0x10}, + {0xF8D0, 0x80}, + {0xF8D1, 0x12}, + {0xF8D2, 0xe5}, + {0xF8D3, 0x7d}, + {0xF8D4, 0xb4}, + {0xF8D5, 0xec}, + {0xF8D6, 0x04}, + {0xF8D7, 0x7f}, + {0xF8D8, 0x11}, + {0xF8D9, 0x80}, + {0xF8DA, 0x09}, + {0xF8DB, 0xe5}, + {0xF8DC, 0x7d}, + {0xF8DD, 0x7f}, + {0xF8DE, 0x00}, + {0xF8DF, 0xb4}, + {0xF8E0, 0xf0}, + {0xF8E1, 0x02}, + {0xF8E2, 0x7f}, + {0xF8E3, 0x12}, + {0xF8E4, 0x8f}, + {0xF8E5, 0x7c}, + {0xF8E6, 0xef}, + {0xF8E7, 0x24}, + {0xF8E8, 0x95}, + {0xF8E9, 0xff}, + {0xF8EA, 0xe4}, + {0xF8EB, 0x34}, + {0xF8EC, 0xfb}, + {0xF8ED, 0x8f}, + {0xF8EE, 0x82}, + {0xF8EF, 0xf5}, + {0xF8F0, 0x83}, + {0xF8F1, 0xe4}, + {0xF8F2, 0x93}, + {0xF8F3, 0xf5}, + {0xF8F4, 0x7c}, + {0xF8F5, 0xf5}, + {0xF8F6, 0x7b}, + {0xF8F7, 0xe4}, + {0xF8F8, 0xf5}, + {0xF8F9, 0x7a}, + {0xF8FA, 0x75}, + {0xF8FB, 0x78}, + {0xF8FC, 0x30}, + {0xF8FD, 0x75}, + {0xF8FE, 0x79}, + {0xF8FF, 0x53}, + {0xF900, 0x85}, + {0xF901, 0x79}, + {0xF902, 0x82}, + {0xF903, 0x85}, + {0xF904, 0x78}, + {0xF905, 0x83}, + {0xF906, 0xe0}, + {0xF907, 0x25}, + {0xF908, 0x7c}, + {0xF909, 0xf0}, + {0xF90A, 0x74}, + {0xF90B, 0x02}, + {0xF90C, 0x25}, + {0xF90D, 0x79}, + {0xF90E, 0xf5}, + {0xF90F, 0x79}, + {0xF910, 0xe4}, + {0xF911, 0x35}, + {0xF912, 0x78}, + {0xF913, 0xf5}, + {0xF914, 0x78}, + {0xF915, 0x05}, + {0xF916, 0x7a}, + {0xF917, 0xe5}, + {0xF918, 0x7a}, + {0xF919, 0xb4}, + {0xF91A, 0x08}, + {0xF91B, 0xe4}, + {0xF91C, 0x02}, + {0xF91D, 0x18}, + {0xF91E, 0x32}, + {0xF91F, 0x22}, + {0xF920, 0xf0}, + {0xF921, 0x90}, + {0xF922, 0xa0}, + {0xF923, 0xf8}, + {0xF924, 0xe0}, + {0xF925, 0x70}, + {0xF926, 0x02}, + {0xF927, 0xa3}, + {0xF928, 0xe0}, + {0xF929, 0x70}, + {0xF92A, 0x0a}, + {0xF92B, 0x90}, + {0xF92C, 0xa1}, + {0xF92D, 0x10}, + {0xF92E, 0xe0}, + {0xF92F, 0xfe}, + {0xF930, 0xa3}, + {0xF931, 0xe0}, + {0xF932, 0xff}, + {0xF933, 0x80}, + {0xF934, 0x04}, + {0xF935, 0x7e}, + {0xF936, 0x00}, + {0xF937, 0x7f}, + {0xF938, 0x00}, + {0xF939, 0x8e}, + {0xF93A, 0x7e}, + {0xF93B, 0x8f}, + {0xF93C, 0x7f}, + {0xF93D, 0x90}, + {0xF93E, 0x36}, + {0xF93F, 0x0d}, + {0xF940, 0xe0}, + {0xF941, 0x44}, + {0xF942, 0x02}, + {0xF943, 0xf0}, + {0xF944, 0x90}, + {0xF945, 0x36}, + {0xF946, 0x0e}, + {0xF947, 0xe5}, + {0xF948, 0x7e}, + {0xF949, 0xf0}, + {0xF94A, 0xa3}, + {0xF94B, 0xe5}, + {0xF94C, 0x7f}, + {0xF94D, 0xf0}, + {0xF94E, 0xe5}, + {0xF94F, 0x3a}, + {0xF950, 0x60}, + {0xF951, 0x0c}, + {0xF952, 0x90}, + {0xF953, 0x36}, + {0xF954, 0x09}, + {0xF955, 0xe0}, + {0xF956, 0x70}, + {0xF957, 0x06}, + {0xF958, 0x90}, + {0xF959, 0x36}, + {0xF95A, 0x08}, + {0xF95B, 0xf0}, + {0xF95C, 0xf5}, + {0xF95D, 0x3a}, + {0xF95E, 0x02}, + {0xF95F, 0x03}, + {0xF960, 0x94}, + {0xF961, 0x22}, + {0xF962, 0x78}, + {0xF963, 0x07}, + {0xF964, 0xe6}, + {0xF965, 0xd3}, + {0xF966, 0x94}, + {0xF967, 0x00}, + {0xF968, 0x40}, + {0xF969, 0x16}, + {0xF96A, 0x16}, + {0xF96B, 0xe6}, + {0xF96C, 0x90}, + {0xF96D, 0x30}, + {0xF96E, 0xa1}, + {0xF96F, 0xf0}, + {0xF970, 0x90}, + {0xF971, 0x43}, + {0xF972, 0x83}, + {0xF973, 0xe0}, + {0xF974, 0xb4}, + {0xF975, 0x01}, + {0xF976, 0x0f}, + {0xF977, 0x90}, + {0xF978, 0x43}, + {0xF979, 0x87}, + {0xF97A, 0xe0}, + {0xF97B, 0xb4}, + {0xF97C, 0x01}, + {0xF97D, 0x08}, + {0xF97E, 0x80}, + {0xF97F, 0x00}, + {0xF980, 0x90}, + {0xF981, 0x30}, + {0xF982, 0xa0}, + {0xF983, 0x74}, + {0xF984, 0x01}, + {0xF985, 0xf0}, + {0xF986, 0x22}, + {0xF987, 0xf0}, + {0xF988, 0x90}, + {0xF989, 0x35}, + {0xF98A, 0xba}, + {0xF98B, 0xe0}, + {0xF98C, 0xb4}, + {0xF98D, 0x0a}, + {0xF98E, 0x0d}, + {0xF98F, 0xa3}, + {0xF990, 0xe0}, + {0xF991, 0xb4}, + {0xF992, 0x01}, + {0xF993, 0x08}, + {0xF994, 0x90}, + {0xF995, 0xfb}, + {0xF996, 0x94}, + {0xF997, 0xe0}, + {0xF998, 0x90}, + {0xF999, 0x35}, + {0xF99A, 0xb8}, + {0xF99B, 0xf0}, + {0xF99C, 0xd0}, + {0xF99D, 0xd0}, + {0xF99E, 0xd0}, + {0xF99F, 0x82}, + {0xF9A0, 0xd0}, + {0xF9A1, 0x83}, + {0xF9A2, 0xd0}, + {0xF9A3, 0xe0}, + {0xF9A4, 0x32}, + {0xF9A5, 0x22}, + {0xF9A6, 0xe5}, + {0xF9A7, 0x7f}, + {0xF9A8, 0x45}, + {0xF9A9, 0x7e}, + {0xF9AA, 0x60}, + {0xF9AB, 0x15}, + {0xF9AC, 0x90}, + {0xF9AD, 0x01}, + {0xF9AE, 0x00}, + {0xF9AF, 0xe0}, + {0xF9B0, 0x70}, + {0xF9B1, 0x0f}, + {0xF9B2, 0x90}, + {0xF9B3, 0xa0}, + {0xF9B4, 0xf8}, + {0xF9B5, 0xe5}, + {0xF9B6, 0x7e}, + {0xF9B7, 0xf0}, + {0xF9B8, 0xa3}, + {0xF9B9, 0xe5}, + {0xF9BA, 0x7f}, + {0xF9BB, 0xf0}, + {0xF9BC, 0xe4}, + {0xF9BD, 0xf5}, + {0xF9BE, 0x7e}, + {0xF9BF, 0xf5}, + {0xF9C0, 0x7f}, + {0xF9C1, 0x22}, + {0xF9C2, 0x02}, + {0xF9C3, 0x0e}, + {0xF9C4, 0x79}, + {0xF9C5, 0x22}, + /* Offsets:*/ + {0x35C6, 0x00},/* FIDDLEDARKCAL*/ + {0x35C7, 0x00}, + {0x35C8, 0x01},/*STOREDISTANCEATSTOPSTREAMING*/ + {0x35C9, 0x20}, + {0x35CA, 0x01},/*BRUCEFIX*/ + {0x35CB, 0x62}, + {0x35CC, 0x01},/*FIXDATAXFERSTATUSREG*/ + {0x35CD, 0x87}, + {0x35CE, 0x01},/*FOCUSDISTANCEUPDATE*/ + {0x35CF, 0xA6}, + {0x35D0, 0x01},/*SKIPEDOFRESET*/ + {0x35D1, 0xC2}, + {0x35D2, 0x00}, + {0x35D3, 0xFB}, + {0x35D4, 0x00}, + {0x35D5, 0x94}, + {0x35D6, 0x00}, + {0x35D7, 0xFB}, + {0x35D8, 0x00}, + {0x35D9, 0x94}, + {0x35DA, 0x00}, + {0x35DB, 0xFB}, + {0x35DC, 0x00}, + {0x35DD, 0x94}, + {0x35DE, 0x00}, + {0x35DF, 0xFB}, + {0x35E0, 0x00}, + {0x35E1, 0x94}, + {0x35E6, 0x18},/* FIDDLEDARKCAL*/ + {0x35E7, 0x2F}, + {0x35E8, 0x03},/* STOREDISTANCEATSTOPSTREAMING*/ + {0x35E9, 0x93}, + {0x35EA, 0x18},/* BRUCEFIX*/ + {0x35EB, 0x99}, + {0x35EC, 0x00},/* FIXDATAXFERSTATUSREG*/ + {0x35ED, 0xA3}, + {0x35EE, 0x21},/* FOCUSDISTANCEUPDATE*/ + {0x35EF, 0x5B}, + {0x35F0, 0x0E},/* SKIPEDOFRESET*/ + {0x35F1, 0x74}, + {0x35F2, 0x04}, + {0x35F3, 0x64}, + {0x35F4, 0x04}, + {0x35F5, 0x65}, + {0x35F6, 0x04}, + {0x35F7, 0x7B}, + {0x35F8, 0x04}, + {0x35F9, 0x7C}, + {0x35FA, 0x04}, + {0x35FB, 0xDD}, + {0x35FC, 0x04}, + {0x35FD, 0xDE}, + {0x35FE, 0x04}, + {0x35FF, 0xEF}, + {0x3600, 0x04}, + {0x3601, 0xF0}, + /*Jump/Data:*/ + {0x35C2, 0x3F},/* Jump Reg*/ + {0x35C3, 0xFF},/* Jump Reg*/ + {0x35C4, 0x3F},/* Data Reg*/ + {0x35C5, 0xC0},/* Data Reg*/ + {0x35C0, 0x01},/* Enable*/ + +}; + +static struct vx6953_i2c_reg_conf cut3_cali_data[] = { + {0x360A, 0x07 }, + {0x3530, 0x07 }, + {0x35B5, 0x00 }, + {0x35BC, 0x00 }, + {0xAFF8, 0x00 }, + {0xAFF9, 0x01 }, + {0xF800, 0x90 }, + {0xF801, 0x30 }, + {0xF802, 0x31 }, + {0xF803, 0xe0 }, + {0xF804, 0xf5 }, + {0xF805, 0x7d }, + {0xF806, 0xb4 }, + {0xF807, 0x01 }, + {0xF808, 0x06 }, + {0xF809, 0x75 }, + {0xF80A, 0x7d }, + {0xF80B, 0x03 }, + {0xF80C, 0x74 }, + {0xF80D, 0x03 }, + {0xF80E, 0xf0 }, + {0xF80F, 0x90 }, + {0xF810, 0x30 }, + {0xF811, 0x04 }, + {0xF812, 0x74 }, + {0xF813, 0x33 }, + {0xF814, 0xf0 }, + {0xF815, 0x90 }, + {0xF816, 0x30 }, + {0xF817, 0x06 }, + {0xF818, 0xe4 }, + {0xF819, 0xf0 }, + {0xF81A, 0xa3 }, + {0xF81B, 0x74 }, + {0xF81C, 0x08 }, + {0xF81D, 0xf0 }, + {0xF81E, 0x90 }, + {0xF81F, 0x30 }, + {0xF820, 0x10 }, + {0xF821, 0xe4 }, + {0xF822, 0xf0 }, + {0xF823, 0xa3 }, + {0xF824, 0xf0 }, + {0xF825, 0x90 }, + {0xF826, 0x30 }, + {0xF827, 0x16 }, + {0xF828, 0x74 }, + {0xF829, 0x1e }, + {0xF82A, 0xf0 }, + {0xF82B, 0x90 }, + {0xF82C, 0x30 }, + {0xF82D, 0x1a }, + {0xF82E, 0x74 }, + {0xF82F, 0x6a }, + {0xF830, 0xf0 }, + {0xF831, 0x90 }, + {0xF832, 0x30 }, + {0xF833, 0x30 }, + {0xF834, 0x74 }, + {0xF835, 0x08 }, + {0xF836, 0xf0 }, + {0xF837, 0x90 }, + {0xF838, 0x30 }, + {0xF839, 0x36 }, + {0xF83A, 0x74 }, + {0xF83B, 0x2c }, + {0xF83C, 0xf0 }, + {0xF83D, 0x90 }, + {0xF83E, 0x30 }, + {0xF83F, 0x41 }, + {0xF840, 0xe4 }, + {0xF841, 0xf0 }, + {0xF842, 0xa3 }, + {0xF843, 0x74 }, + {0xF844, 0x24 }, + {0xF845, 0xf0 }, + {0xF846, 0x90 }, + {0xF847, 0x30 }, + {0xF848, 0x45 }, + {0xF849, 0x74 }, + {0xF84A, 0x81 }, + {0xF84B, 0xf0 }, + {0xF84C, 0x90 }, + {0xF84D, 0x30 }, + {0xF84E, 0x98 }, + {0xF84F, 0x74 }, + {0xF850, 0x01 }, + {0xF851, 0xf0 }, + {0xF852, 0x90 }, + {0xF853, 0x30 }, + {0xF854, 0x9d }, + {0xF855, 0x74 }, + {0xF856, 0x05 }, + {0xF857, 0xf0 }, + {0xF858, 0xe5 }, + {0xF859, 0x7d }, + {0xF85A, 0x70 }, + {0xF85B, 0x22 }, + {0xF85C, 0x90 }, + {0xF85D, 0x02 }, + {0xF85E, 0x00 }, + {0xF85F, 0x74 }, + {0xF860, 0x02 }, + {0xF861, 0xf0 }, + {0xF862, 0xa3 }, + {0xF863, 0x74 }, + {0xF864, 0x54 }, + {0xF865, 0xf0 }, + {0xF866, 0x90 }, + {0xF867, 0x30 }, + {0xF868, 0x05 }, + {0xF869, 0x74 }, + {0xF86A, 0x01 }, + {0xF86B, 0xf0 }, + {0xF86C, 0x90 }, + {0xF86D, 0x30 }, + {0xF86E, 0x1b }, + {0xF86F, 0x74 }, + {0xF870, 0x29 }, + {0xF871, 0xf0 }, + {0xF872, 0x90 }, + {0xF873, 0x30 }, + {0xF874, 0x30 }, + {0xF875, 0xe4 }, + {0xF876, 0xf0 }, + {0xF877, 0x90 }, + {0xF878, 0x30 }, + {0xF879, 0x35 }, + {0xF87A, 0x04 }, + {0xF87B, 0xf0 }, + {0xF87C, 0x80 }, + {0xF87D, 0x69 }, + {0xF87E, 0xe5 }, + {0xF87F, 0x7d }, + {0xF880, 0x64 }, + {0xF881, 0x02 }, + {0xF882, 0x70 }, + {0xF883, 0x3c }, + {0xF884, 0x90 }, + {0xF885, 0x02 }, + {0xF886, 0x00 }, + {0xF887, 0x74 }, + {0xF888, 0x04 }, + {0xF889, 0xf0 }, + {0xF88A, 0xa3 }, + {0xF88B, 0x74 }, + {0xF88C, 0x10 }, + {0xF88D, 0xf0 }, + {0xF88E, 0x90 }, + {0xF88F, 0x30 }, + {0xF890, 0x04 }, + {0xF891, 0x74 }, + {0xF892, 0x34 }, + {0xF893, 0xf0 }, + {0xF894, 0xa3 }, + {0xF895, 0x74 }, + {0xF896, 0x07 }, + {0xF897, 0xf0 }, + {0xF898, 0x90 }, + {0xF899, 0x30 }, + {0xF89A, 0x10 }, + {0xF89B, 0x74 }, + {0xF89C, 0x10 }, + {0xF89D, 0xf0 }, + {0xF89E, 0x90 }, + {0xF89F, 0x30 }, + {0xF8A0, 0x16 }, + {0xF8A1, 0x74 }, + {0xF8A2, 0x1f }, + {0xF8A3, 0xf0 }, + {0xF8A4, 0x90 }, + {0xF8A5, 0x30 }, + {0xF8A6, 0x1a }, + {0xF8A7, 0x74 }, + {0xF8A8, 0x62 }, + {0xF8A9, 0xf0 }, + {0xF8AA, 0xa3 }, + {0xF8AB, 0x74 }, + {0xF8AC, 0x2a }, + {0xF8AD, 0xf0 }, + {0xF8AE, 0x90 }, + {0xF8AF, 0x30 }, + {0xF8B0, 0x35 }, + {0xF8B1, 0x74 }, + {0xF8B2, 0x04 }, + {0xF8B3, 0xf0 }, + {0xF8B4, 0x90 }, + {0xF8B5, 0x30 }, + {0xF8B6, 0x41 }, + {0xF8B7, 0x74 }, + {0xF8B8, 0x60 }, + {0xF8B9, 0xf0 }, + {0xF8BA, 0xa3 }, + {0xF8BB, 0x74 }, + {0xF8BC, 0x64 }, + {0xF8BD, 0xf0 }, + {0xF8BE, 0x80 }, + {0xF8BF, 0x27 }, + {0xF8C0, 0xe5 }, + {0xF8C1, 0x7d }, + {0xF8C2, 0xb4 }, + {0xF8C3, 0x03 }, + {0xF8C4, 0x22 }, + {0xF8C5, 0x90 }, + {0xF8C6, 0x02 }, + {0xF8C7, 0x00 }, + {0xF8C8, 0x74 }, + {0xF8C9, 0x02 }, + {0xF8CA, 0xf0 }, + {0xF8CB, 0xa3 }, + {0xF8CC, 0x74 }, + {0xF8CD, 0x26 }, + {0xF8CE, 0xf0 }, + {0xF8CF, 0x90 }, + {0xF8D0, 0x30 }, + {0xF8D1, 0x05 }, + {0xF8D2, 0x74 }, + {0xF8D3, 0x03 }, + {0xF8D4, 0xf0 }, + {0xF8D5, 0x90 }, + {0xF8D6, 0x30 }, + {0xF8D7, 0x11 }, + {0xF8D8, 0x74 }, + {0xF8D9, 0x01 }, + {0xF8DA, 0xf0 }, + {0xF8DB, 0x90 }, + {0xF8DC, 0x30 }, + {0xF8DD, 0x1b }, + {0xF8DE, 0x74 }, + {0xF8DF, 0x2a }, + {0xF8E0, 0xf0 }, + {0xF8E1, 0x90 }, + {0xF8E2, 0x30 }, + {0xF8E3, 0x35 }, + {0xF8E4, 0x74 }, + {0xF8E5, 0x03 }, + {0xF8E6, 0xf0 }, + {0xF8E7, 0x90 }, + {0xF8E8, 0x41 }, + {0xF8E9, 0x01 }, + {0xF8EA, 0xe0 }, + {0xF8EB, 0xf5 }, + {0xF8EC, 0x79 }, + {0xF8ED, 0x90 }, + {0xF8EE, 0x43 }, + {0xF8EF, 0x87 }, + {0xF8F0, 0xe0 }, + {0xF8F1, 0xf5 }, + {0xF8F2, 0x7a }, + {0xF8F3, 0x90 }, + {0xF8F4, 0x42 }, + {0xF8F5, 0x05 }, + {0xF8F6, 0xe0 }, + {0xF8F7, 0xf5 }, + {0xF8F8, 0x7b }, + {0xF8F9, 0x22 }, + {0xF8FA, 0x78 }, + {0xF8FB, 0x07 }, + {0xF8FC, 0xe6 }, + {0xF8FD, 0xf5 }, + {0xF8FE, 0x7c }, + {0xF8FF, 0xe5 }, + {0xF900, 0x7c }, + {0xF901, 0x60 }, + {0xF902, 0x1e }, + {0xF903, 0x90 }, + {0xF904, 0x43 }, + {0xF905, 0x83 }, + {0xF906, 0xe0 }, + {0xF907, 0xb4 }, + {0xF908, 0x01 }, + {0xF909, 0x17 }, + {0xF90A, 0x90 }, + {0xF90B, 0x43 }, + {0xF90C, 0x87 }, + {0xF90D, 0xe0 }, + {0xF90E, 0xb4 }, + {0xF90F, 0x01 }, + {0xF910, 0x10 }, + {0xF911, 0x15 }, + {0xF912, 0x7c }, + {0xF913, 0x90 }, + {0xF914, 0x30 }, + {0xF915, 0xa1 }, + {0xF916, 0xe5 }, + {0xF917, 0x7c }, + {0xF918, 0xf0 }, + {0xF919, 0x90 }, + {0xF91A, 0x30 }, + {0xF91B, 0xa0 }, + {0xF91C, 0x74 }, + {0xF91D, 0x01 }, + {0xF91E, 0xf0 }, + {0xF91F, 0x80 }, + {0xF920, 0x05 }, + {0xF921, 0xe4 }, + {0xF922, 0x90 }, + {0xF923, 0x30 }, + {0xF924, 0xa0 }, + {0xF925, 0xf0 }, + {0xF926, 0x90 }, + {0xF927, 0x41 }, + {0xF928, 0x01 }, + {0xF929, 0xe0 }, + {0xF92A, 0xfc }, + {0xF92B, 0x54 }, + {0xF92C, 0x02 }, + {0xF92D, 0xfe }, + {0xF92E, 0xe5 }, + {0xF92F, 0x79 }, + {0xF930, 0x54 }, + {0xF931, 0x02 }, + {0xF932, 0xb5 }, + {0xF933, 0x06 }, + {0xF934, 0x0f }, + {0xF935, 0x90 }, + {0xF936, 0x43 }, + {0xF937, 0x87 }, + {0xF938, 0xe0 }, + {0xF939, 0xb5 }, + {0xF93A, 0x7a }, + {0xF93B, 0x08 }, + {0xF93C, 0x90 }, + {0xF93D, 0x42 }, + {0xF93E, 0x05 }, + {0xF93F, 0xe0 }, + {0xF940, 0x65 }, + {0xF941, 0x7b }, + {0xF942, 0x60 }, + {0xF943, 0x0b }, + {0xF944, 0x90 }, + {0xF945, 0x30 }, + {0xF946, 0x50 }, + {0xF947, 0xe0 }, + {0xF948, 0x54 }, + {0xF949, 0xf9 }, + {0xF94A, 0x44 }, + {0xF94B, 0x02 }, + {0xF94C, 0xf0 }, + {0xF94D, 0x80 }, + {0xF94E, 0x09 }, + {0xF94F, 0x90 }, + {0xF950, 0x30 }, + {0xF951, 0x50 }, + {0xF952, 0xe0 }, + {0xF953, 0x54 }, + {0xF954, 0xf9 }, + {0xF955, 0x44 }, + {0xF956, 0x04 }, + {0xF957, 0xf0 }, + {0xF958, 0x8c }, + {0xF959, 0x79 }, + {0xF95A, 0x90 }, + {0xF95B, 0x43 }, + {0xF95C, 0x87 }, + {0xF95D, 0xe0 }, + {0xF95E, 0xf5 }, + {0xF95F, 0x7a }, + {0xF960, 0x90 }, + {0xF961, 0x42 }, + {0xF962, 0x05 }, + {0xF963, 0xe0 }, + {0xF964, 0xf5 }, + {0xF965, 0x7b }, + {0xF966, 0x22 }, + {0xF967, 0xc3 }, + {0xF968, 0x90 }, + {0xF969, 0x0b }, + {0xF96A, 0x89 }, + {0xF96B, 0xe0 }, + {0xF96C, 0x94 }, + {0xF96D, 0x1e }, + {0xF96E, 0x90 }, + {0xF96F, 0x0b }, + {0xF970, 0x88 }, + {0xF971, 0xe0 }, + {0xF972, 0x94 }, + {0xF973, 0x00 }, + {0xF974, 0x50 }, + {0xF975, 0x06 }, + {0xF976, 0x7e }, + {0xF977, 0x00 }, + {0xF978, 0x7f }, + {0xF979, 0x01 }, + {0xF97A, 0x80 }, + {0xF97B, 0x3d }, + {0xF97C, 0xc3 }, + {0xF97D, 0x90 }, + {0xF97E, 0x0b }, + {0xF97F, 0x89 }, + {0xF980, 0xe0 }, + {0xF981, 0x94 }, + {0xF982, 0x3c }, + {0xF983, 0x90 }, + {0xF984, 0x0b }, + {0xF985, 0x88 }, + {0xF986, 0xe0 }, + {0xF987, 0x94 }, + {0xF988, 0x00 }, + {0xF989, 0x50 }, + {0xF98A, 0x06 }, + {0xF98B, 0x7e }, + {0xF98C, 0x00 }, + {0xF98D, 0x7f }, + {0xF98E, 0x02 }, + {0xF98F, 0x80 }, + {0xF990, 0x28 }, + {0xF991, 0xc3 }, + {0xF992, 0x90 }, + {0xF993, 0x0b }, + {0xF994, 0x89 }, + {0xF995, 0xe0 }, + {0xF996, 0x94 }, + {0xF997, 0xfa }, + {0xF998, 0x90 }, + {0xF999, 0x0b }, + {0xF99A, 0x88 }, + {0xF99B, 0xe0 }, + {0xF99C, 0x94 }, + {0xF99D, 0x00 }, + {0xF99E, 0x50 }, + {0xF99F, 0x06 }, + {0xF9A0, 0x7e }, + {0xF9A1, 0x00 }, + {0xF9A2, 0x7f }, + {0xF9A3, 0x03 }, + {0xF9A4, 0x80 }, + {0xF9A5, 0x13 }, + {0xF9A6, 0xc3 }, + {0xF9A7, 0x90 }, + {0xF9A8, 0x0b }, + {0xF9A9, 0x88 }, + {0xF9AA, 0xe0 }, + {0xF9AB, 0x94 }, + {0xF9AC, 0x80 }, + {0xF9AD, 0x50 }, + {0xF9AE, 0x06 }, + {0xF9AF, 0x7e }, + {0xF9B0, 0x00 }, + {0xF9B1, 0x7f }, + {0xF9B2, 0x04 }, + {0xF9B3, 0x80 }, + {0xF9B4, 0x04 }, + {0xF9B5, 0xae }, + {0xF9B6, 0x7e }, + {0xF9B7, 0xaf }, + {0xF9B8, 0x7f }, + {0xF9B9, 0x90 }, + {0xF9BA, 0xa0 }, + {0xF9BB, 0xf8 }, + {0xF9BC, 0xee }, + {0xF9BD, 0xf0 }, + {0xF9BE, 0xa3 }, + {0xF9BF, 0xef }, + {0xF9C0, 0xf0 }, + {0xF9C1, 0x22 }, + {0xF9C2, 0x90 }, + {0xF9C3, 0x33 }, + {0xF9C4, 0x82 }, + {0xF9C5, 0xe0 }, + {0xF9C6, 0xff }, + {0xF9C7, 0x64 }, + {0xF9C8, 0x01 }, + {0xF9C9, 0x70 }, + {0xF9CA, 0x30 }, + {0xF9CB, 0xe5 }, + {0xF9CC, 0x7f }, + {0xF9CD, 0x64 }, + {0xF9CE, 0x02 }, + {0xF9CF, 0x45 }, + {0xF9D0, 0x7e }, + {0xF9D1, 0x70 }, + {0xF9D2, 0x04 }, + {0xF9D3, 0x7d }, + {0xF9D4, 0x1e }, + {0xF9D5, 0x80 }, + {0xF9D6, 0x1d }, + {0xF9D7, 0xe5 }, + {0xF9D8, 0x7f }, + {0xF9D9, 0x64 }, + {0xF9DA, 0x03 }, + {0xF9DB, 0x45 }, + {0xF9DC, 0x7e }, + {0xF9DD, 0x70 }, + {0xF9DE, 0x04 }, + {0xF9DF, 0x7d }, + {0xF9E0, 0x3c }, + {0xF9E1, 0x80 }, + {0xF9E2, 0x11 }, + {0xF9E3, 0xe5 }, + {0xF9E4, 0x7f }, + {0xF9E5, 0x64 }, + {0xF9E6, 0x04 }, + {0xF9E7, 0x45 }, + {0xF9E8, 0x7e }, + {0xF9E9, 0x70 }, + {0xF9EA, 0x04 }, + {0xF9EB, 0x7d }, + {0xF9EC, 0xfa }, + {0xF9ED, 0x80 }, + {0xF9EE, 0x05 }, + {0xF9EF, 0x90 }, + {0xF9F0, 0x33 }, + {0xF9F1, 0x81 }, + {0xF9F2, 0xe0 }, + {0xF9F3, 0xfd }, + {0xF9F4, 0xae }, + {0xF9F5, 0x05 }, + {0xF9F6, 0x90 }, + {0xF9F7, 0x33 }, + {0xF9F8, 0x81 }, + {0xF9F9, 0xed }, + {0xF9FA, 0xf0 }, + {0xF9FB, 0xef }, + {0xF9FC, 0xb4 }, + {0xF9FD, 0x01 }, + {0xF9FE, 0x10 }, + {0xF9FF, 0x90 }, + {0xFA00, 0x01 }, + {0xFA01, 0x00 }, + {0xFA02, 0xe0 }, + {0xFA03, 0x60 }, + {0xFA04, 0x0a }, + {0xFA05, 0x90 }, + {0xFA06, 0xa1 }, + {0xFA07, 0x10 }, + {0xFA08, 0xe0 }, + {0xFA09, 0xf5 }, + {0xFA0A, 0x7e }, + {0xFA0B, 0xa3 }, + {0xFA0C, 0xe0 }, + {0xFA0D, 0xf5 }, + {0xFA0E, 0x7f }, + {0xFA0F, 0x22 }, + {0xFA10, 0x12 }, + {0xFA11, 0x2f }, + {0xFA12, 0x4d }, + {0xFA13, 0x90 }, + {0xFA14, 0x35 }, + {0xFA15, 0x38 }, + {0xFA16, 0xe0 }, + {0xFA17, 0x70 }, + {0xFA18, 0x05 }, + {0xFA19, 0x12 }, + {0xFA1A, 0x00 }, + {0xFA1B, 0x0e }, + {0xFA1C, 0x80 }, + {0xFA1D, 0x03 }, + {0xFA1E, 0x12 }, + {0xFA1F, 0x07 }, + {0xFA20, 0xc9 }, + {0xFA21, 0x90 }, + {0xFA22, 0x40 }, + {0xFA23, 0x06 }, + {0xFA24, 0xe0 }, + {0xFA25, 0xf4 }, + {0xFA26, 0x54 }, + {0xFA27, 0x02 }, + {0xFA28, 0xff }, + {0xFA29, 0xe0 }, + {0xFA2A, 0x54 }, + {0xFA2B, 0x01 }, + {0xFA2C, 0x4f }, + {0xFA2D, 0x90 }, + {0xFA2E, 0x31 }, + {0xFA2F, 0x32 }, + {0xFA30, 0xf0 }, + {0xFA31, 0x90 }, + {0xFA32, 0xfa }, + {0xFA33, 0x9d }, + {0xFA34, 0xe0 }, + {0xFA35, 0x70 }, + {0xFA36, 0x03 }, + {0xFA37, 0x12 }, + {0xFA38, 0x27 }, + {0xFA39, 0x27 }, + {0xFA3A, 0x02 }, + {0xFA3B, 0x05 }, + {0xFA3C, 0xac }, + {0xFA3D, 0x22 }, + {0xFA3E, 0xf0 }, + {0xFA3F, 0xe5 }, + {0xFA40, 0x3a }, + {0xFA41, 0xb4 }, + {0xFA42, 0x06 }, + {0xFA43, 0x06 }, + {0xFA44, 0x63 }, + {0xFA45, 0x3e }, + {0xFA46, 0x02 }, + {0xFA47, 0x12 }, + {0xFA48, 0x03 }, + {0xFA49, 0xea }, + {0xFA4A, 0x02 }, + {0xFA4B, 0x17 }, + {0xFA4C, 0x4a }, + {0xFA4D, 0x22 }, + {0x35C9, 0xFA }, + {0x35CA, 0x01 }, + {0x35CB, 0x67 }, + {0x35CC, 0x01 }, + {0x35CD, 0xC2 }, + {0x35CE, 0x02 }, + {0x35CF, 0x10 }, + {0x35D0, 0x02 }, + {0x35D1, 0x3E }, + {0x35D3, 0xF6 }, + {0x35D5, 0x07 }, + {0x35D7, 0xA3 }, + {0x35DB, 0x02 }, + {0x35DD, 0x06 }, + {0x35DF, 0x27 }, + {0x35E6, 0x28 }, + {0x35E7, 0x76 }, + {0x35E8, 0x2A }, + {0x35E9, 0x15 }, + {0x35EA, 0x2D }, + {0x35EB, 0x07 }, + {0x35EC, 0x04 }, + {0x35ED, 0x43 }, + {0x35EE, 0x05 }, + {0x35EF, 0xA9 }, + {0x35F0, 0x17 }, + {0x35F1, 0x41 }, + {0x35F2, 0x24 }, + {0x35F3, 0x88 }, + {0x35F4, 0x01 }, + {0x35F5, 0x54 }, + {0x35F6, 0x01 }, + {0x35F7, 0x55 }, + {0x35F8, 0x2E }, + {0x35F9, 0xF2 }, + {0x35FA, 0x06 }, + {0x35FB, 0x02 }, + {0x35FC, 0x06 }, + {0x35FD, 0x03 }, + {0x35FE, 0x06 }, + {0x35FF, 0x04 }, + {0x3600, 0x0F }, + {0x3601, 0x48 }, + {0x3602, 0x0F }, + {0x3603, 0x49 }, + {0x3604, 0x0F }, + {0x3605, 0x4A }, + {0x35C2, 0xFF }, + {0x35C3, 0xFF }, + {0x35C4, 0xFF }, + {0x35C5, 0xC0 }, + {0x35C0, 0x01 }, + + + {0xa098, 0x02 }, + {0xa099, 0x87 }, + {0xa09c, 0x00 }, + {0xa09d, 0xc5 }, + {0xa4ec, 0x05 }, + {0xa4ed, 0x05 }, + {0xa4f0, 0x04 }, + {0xa4f1, 0x04 }, + {0xa4f4, 0x04 }, + {0xa4f5, 0x05 }, + {0xa4f8, 0x05 }, + {0xa4f9, 0x07 }, + {0xa4fc, 0x07 }, + {0xa4fd, 0x07 }, + {0xa500, 0x07 }, + {0xa501, 0x07 }, + {0xa504, 0x08 }, + {0xa505, 0x08 }, + {0xa518, 0x01 }, + {0xa519, 0x02 }, + {0xa51c, 0x01 }, + {0xa51d, 0x00 }, + {0xa534, 0x00 }, + {0xa535, 0x04 }, + {0xa538, 0x04 }, + {0xa539, 0x03 }, + {0xa53c, 0x05 }, + {0xa53d, 0x07 }, + {0xa540, 0x07 }, + {0xa541, 0x06 }, + {0xa544, 0x07 }, + {0xa545, 0x06 }, + {0xa548, 0x05 }, + {0xa549, 0x06 }, + {0xa54c, 0x06 }, + {0xa54d, 0x07 }, + {0xa550, 0x07 }, + {0xa551, 0x04 }, + {0xa554, 0x04 }, + {0xa555, 0x04 }, + {0xa558, 0x05 }, + {0xa559, 0x06 }, + {0xa55c, 0x07 }, + {0xa55d, 0x07 }, + {0xa56c, 0x00 }, + {0xa56d, 0x0a }, + {0xa570, 0x08 }, + {0xa571, 0x05 }, + {0xa574, 0x04 }, + {0xa575, 0x03 }, + {0xa578, 0x04 }, + {0xa579, 0x04 }, + {0xa58c, 0x1f }, + {0xa58d, 0x1b }, + {0xa590, 0x17 }, + {0xa591, 0x13 }, + {0xa594, 0x10 }, + {0xa595, 0x0d }, + {0xa598, 0x0f }, + {0xa599, 0x11 }, + {0xa59c, 0x03 }, + {0xa59d, 0x03 }, + {0xa5a0, 0x03 }, + {0xa5a1, 0x03 }, + {0xa5a4, 0x03 }, + {0xa5a5, 0x04 }, + {0xa5a8, 0x05 }, + {0xa5a9, 0x00 }, + {0xa5ac, 0x00 }, + {0xa5ad, 0x00 }, + {0xa5b0, 0x00 }, + {0xa5b1, 0x00 }, + {0xa5b4, 0x00 }, + {0xa5b5, 0x00 }, + {0xa5c4, 0x1f }, + {0xa5c5, 0x13 }, + {0xa5c8, 0x14 }, + {0xa5c9, 0x14 }, + {0xa5cc, 0x14 }, + {0xa5cd, 0x13 }, + {0xa5d0, 0x17 }, + {0xa5d1, 0x1a }, + {0xa5f4, 0x05 }, + {0xa5f5, 0x05 }, + {0xa5f8, 0x05 }, + {0xa5f9, 0x06 }, + {0xa5fc, 0x06 }, + {0xa5fd, 0x06 }, + {0xa600, 0x06 }, + {0xa601, 0x06 }, + {0xa608, 0x07 }, + {0xa609, 0x08 }, + {0xa60c, 0x08 }, + {0xa60d, 0x07 }, + {0xa63c, 0x00 }, + {0xa63d, 0x02 }, + {0xa640, 0x02 }, + {0xa641, 0x02 }, + {0xa644, 0x02 }, + {0xa645, 0x02 }, + {0xa648, 0x03 }, + {0xa649, 0x04 }, + {0xa64c, 0x0a }, + {0xa64d, 0x09 }, + {0xa650, 0x08 }, + {0xa651, 0x09 }, + {0xa654, 0x09 }, + {0xa655, 0x0a }, + {0xa658, 0x0a }, + {0xa659, 0x0a }, + {0xa65c, 0x0a }, + {0xa65d, 0x09 }, + {0xa660, 0x09 }, + {0xa661, 0x09 }, + {0xa664, 0x09 }, + {0xa665, 0x08 }, + {0xa680, 0x01 }, + {0xa681, 0x02 }, + {0xa694, 0x1f }, + {0xa695, 0x10 }, + {0xa698, 0x0e }, + {0xa699, 0x0c }, + {0xa69c, 0x0d }, + {0xa69d, 0x0d }, + {0xa6a0, 0x0f }, + {0xa6a1, 0x11 }, + {0xa6a4, 0x00 }, + {0xa6a5, 0x00 }, + {0xa6a8, 0x00 }, + {0xa6a9, 0x00 }, + {0xa6ac, 0x00 }, + {0xa6ad, 0x00 }, + {0xa6b0, 0x00 }, + {0xa6b1, 0x04 }, + {0xa6b4, 0x04 }, + {0xa6b5, 0x04 }, + {0xa6b8, 0x04 }, + {0xa6b9, 0x04 }, + {0xa6bc, 0x05 }, + {0xa6bd, 0x05 }, + {0xa6c0, 0x1f }, + {0xa6c1, 0x1f }, + {0xa6c4, 0x1f }, + {0xa6c5, 0x1f }, + {0xa6c8, 0x1f }, + {0xa6c9, 0x1f }, + {0xa6cc, 0x1f }, + {0xa6cd, 0x0b }, + {0xa6d0, 0x0c }, + {0xa6d1, 0x0d }, + {0xa6d4, 0x0d }, + {0xa6d5, 0x0d }, + {0xa6d8, 0x11 }, + {0xa6d9, 0x14 }, + {0xa6fc, 0x02 }, + {0xa6fd, 0x03 }, + {0xa700, 0x03 }, + {0xa701, 0x03 }, + {0xa704, 0x03 }, + {0xa705, 0x04 }, + {0xa708, 0x05 }, + {0xa709, 0x02 }, + {0xa70c, 0x02 }, + {0xa70d, 0x02 }, + {0xa710, 0x03 }, + {0xa711, 0x04 }, + {0xa714, 0x04 }, + {0xa715, 0x04 }, + {0xa744, 0x00 }, + {0xa745, 0x03 }, + {0xa748, 0x04 }, + {0xa749, 0x04 }, + {0xa74c, 0x05 }, + {0xa74d, 0x06 }, + {0xa750, 0x07 }, + {0xa751, 0x07 }, + {0xa754, 0x05 }, + {0xa755, 0x05 }, + {0xa758, 0x05 }, + {0xa759, 0x05 }, + {0xa75c, 0x05 }, + {0xa75d, 0x06 }, + {0xa760, 0x07 }, + {0xa761, 0x07 }, + {0xa764, 0x06 }, + {0xa765, 0x05 }, + {0xa768, 0x05 }, + {0xa769, 0x05 }, + {0xa76c, 0x06 }, + {0xa76d, 0x07 }, + {0xa77c, 0x00 }, + {0xa77d, 0x05 }, + {0xa780, 0x05 }, + {0xa781, 0x05 }, + {0xa784, 0x05 }, + {0xa785, 0x04 }, + {0xa788, 0x05 }, + {0xa789, 0x06 }, + {0xa79c, 0x1f }, + {0xa79d, 0x15 }, + {0xa7a0, 0x13 }, + {0xa7a1, 0x10 }, + {0xa7a4, 0x0f }, + {0xa7a5, 0x0d }, + {0xa7a8, 0x11 }, + {0xa7a9, 0x14 }, + {0xa7ac, 0x02 }, + {0xa7ad, 0x02 }, + {0xa7b0, 0x02 }, + {0xa7b1, 0x02 }, + {0xa7b4, 0x02 }, + {0xa7b5, 0x03 }, + {0xa7b8, 0x03 }, + {0xa7b9, 0x00 }, + {0xa7bc, 0x00 }, + {0xa7bd, 0x00 }, + {0xa7c0, 0x00 }, + {0xa7c1, 0x00 }, + {0xa7c4, 0x00 }, + {0xa7c5, 0x00 }, + {0xa7d4, 0x1f }, + {0xa7d5, 0x0d }, + {0xa7d8, 0x0f }, + {0xa7d9, 0x10 }, + {0xa7dc, 0x10 }, + {0xa7dd, 0x10 }, + {0xa7e0, 0x13 }, + {0xa7e1, 0x16 }, + {0xa7f4, 0x00 }, + {0xa7f5, 0x03 }, + {0xa7f8, 0x04 }, + {0xa7f9, 0x04 }, + {0xa7fc, 0x04 }, + {0xa7fd, 0x03 }, + {0xa800, 0x03 }, + {0xa801, 0x03 }, + {0xa804, 0x03 }, + {0xa805, 0x03 }, + {0xa808, 0x03 }, + {0xa809, 0x03 }, + {0xa80c, 0x03 }, + {0xa80d, 0x04 }, + {0xa810, 0x04 }, + {0xa811, 0x0a }, + {0xa814, 0x0a }, + {0xa815, 0x0a }, + {0xa818, 0x0f }, + {0xa819, 0x14 }, + {0xa81c, 0x14 }, + {0xa81d, 0x14 }, + {0xa82c, 0x00 }, + {0xa82d, 0x04 }, + {0xa830, 0x02 }, + {0xa831, 0x00 }, + {0xa834, 0x00 }, + {0xa835, 0x00 }, + {0xa838, 0x00 }, + {0xa839, 0x00 }, + {0xa840, 0x1f }, + {0xa841, 0x1f }, + {0xa848, 0x1f }, + {0xa849, 0x1f }, + {0xa84c, 0x1f }, + {0xa84d, 0x0c }, + {0xa850, 0x0c }, + {0xa851, 0x0c }, + {0xa854, 0x0c }, + {0xa855, 0x0c }, + {0xa858, 0x0c }, + {0xa859, 0x0c }, + {0xa85c, 0x0c }, + {0xa85d, 0x0c }, + {0xa860, 0x0c }, + {0xa861, 0x0c }, + {0xa864, 0x0c }, + {0xa865, 0x0c }, + {0xa868, 0x0c }, + {0xa869, 0x0c }, + {0xa86c, 0x0c }, + {0xa86d, 0x0c }, + {0xa870, 0x0c }, + {0xa871, 0x0c }, + {0xa874, 0x0c }, + {0xa875, 0x0c }, + {0xa878, 0x1f }, + {0xa879, 0x1f }, + {0xa87c, 0x1f }, + {0xa87d, 0x1f }, + {0xa880, 0x1f }, + {0xa881, 0x1f }, + {0xa884, 0x1f }, + {0xa885, 0x0c }, + {0xa888, 0x0c }, + {0xa889, 0x0c }, + {0xa88c, 0x0c }, + {0xa88d, 0x0c }, + {0xa890, 0x0c }, + {0xa891, 0x0c }, + {0xa898, 0x1f }, + {0xa899, 0x1f }, + {0xa8a0, 0x1f }, + {0xa8a1, 0x1f }, + {0xa8a4, 0x1f }, + {0xa8a5, 0x0c }, + {0xa8a8, 0x0c }, + {0xa8a9, 0x0c }, + {0xa8ac, 0x0c }, + {0xa8ad, 0x0c }, + {0xa8b0, 0x0c }, + {0xa8b1, 0x0c }, + {0xa8b4, 0x0c }, + {0xa8b5, 0x0c }, + {0xa8b8, 0x0c }, + {0xa8b9, 0x0c }, + {0xa8bc, 0x0c }, + {0xa8bd, 0x0c }, + {0xa8c0, 0x0c }, + {0xa8c1, 0x0c }, + {0xa8c4, 0x0c }, + {0xa8c5, 0x0c }, + {0xa8c8, 0x0c }, + {0xa8c9, 0x0c }, + {0xa8cc, 0x0c }, + {0xa8cd, 0x0c }, + {0xa8d0, 0x1f }, + {0xa8d1, 0x1f }, + {0xa8d4, 0x1f }, + {0xa8d5, 0x1f }, + {0xa8d8, 0x1f }, + {0xa8d9, 0x1f }, + {0xa8dc, 0x1f }, + {0xa8dd, 0x0c }, + {0xa8e0, 0x0c }, + {0xa8e1, 0x0c }, + {0xa8e4, 0x0c }, + {0xa8e5, 0x0c }, + {0xa8e8, 0x0c }, + {0xa8e9, 0x0c }, + {0xa8f0, 0x1f }, + {0xa8f1, 0x1f }, + {0xa8f8, 0x1f }, + {0xa8f9, 0x1f }, + {0xa8fc, 0x1f }, + {0xa8fd, 0x0c }, + {0xa900, 0x0c }, + {0xa901, 0x0c }, + {0xa904, 0x0c }, + {0xa905, 0x0c }, + {0xa908, 0x0c }, + {0xa909, 0x0c }, + {0xa90c, 0x0c }, + {0xa90d, 0x0c }, + {0xa910, 0x0c }, + {0xa911, 0x0c }, + {0xa914, 0x0c }, + {0xa915, 0x0c }, + {0xa918, 0x0c }, + {0xa919, 0x0c }, + {0xa91c, 0x0c }, + {0xa91d, 0x0c }, + {0xa920, 0x0c }, + {0xa921, 0x0c }, + {0xa924, 0x0c }, + {0xa925, 0x0c }, + {0xa928, 0x1f }, + {0xa929, 0x1f }, + {0xa92c, 0x1f }, + {0xa92d, 0x1f }, + {0xa930, 0x1f }, + {0xa931, 0x1f }, + {0xa934, 0x1f }, + {0xa935, 0x0c }, + {0xa938, 0x0c }, + {0xa939, 0x0c }, + {0xa93c, 0x0c }, + {0xa93d, 0x0c }, + {0xa940, 0x0c }, + {0xa941, 0x0c }, + {0xa96c, 0x0d }, + {0xa96d, 0x16 }, + {0xa970, 0x19 }, + {0xa971, 0x0e }, + {0xa974, 0x16 }, + {0xa975, 0x1a }, + {0xa978, 0x0d }, + {0xa979, 0x15 }, + {0xa97c, 0x19 }, + {0xa97d, 0x0d }, + {0xa980, 0x15 }, + {0xa981, 0x1a }, + {0xa984, 0x0d }, + {0xa985, 0x15 }, + {0xa988, 0x1a }, + {0xa989, 0x0d }, + {0xa98c, 0x15 }, + {0xa98d, 0x1a }, + {0xa990, 0x0b }, + {0xa991, 0x11 }, + {0xa994, 0x02 }, + {0xa995, 0x0e }, + {0xa998, 0x16 }, + {0xa999, 0x02 }, + {0xa99c, 0x0c }, + {0xa99d, 0x13 }, + {0xa9a0, 0x02 }, + {0xa9a1, 0x0c }, + {0xa9a4, 0x12 }, + {0xa9a5, 0x02 }, + {0xa9a8, 0x0c }, + {0xa9a9, 0x12 }, + {0xa9ac, 0x02 }, + {0xa9ad, 0x0c }, + {0xa9b0, 0x12 }, + {0xa9b1, 0x02 }, + {0xa9b4, 0x10 }, + {0xa9b5, 0x1e }, + {0xa9b8, 0x0f }, + {0xa9b9, 0x13 }, + {0xa9bc, 0x20 }, + {0xa9bd, 0x10 }, + {0xa9c0, 0x11 }, + {0xa9c1, 0x1e }, + {0xa9c4, 0x10 }, + {0xa9c5, 0x11 }, + {0xa9c8, 0x1e }, + {0xa9c9, 0x10 }, + {0xa9cc, 0x11 }, + {0xa9cd, 0x20 }, + {0xa9d0, 0x10 }, + {0xa9d1, 0x13 }, + {0xa9d4, 0x24 }, + {0xa9d5, 0x10 }, + {0xa9f0, 0x02 }, + {0xa9f1, 0x01 }, + {0xa9f8, 0x19 }, + {0xa9f9, 0x0b }, + {0xa9fc, 0x0a }, + {0xa9fd, 0x07 }, + {0xaa00, 0x0c }, + {0xaa01, 0x0e }, + {0xaa08, 0x0c }, + {0xaa09, 0x06 }, + {0xaa0c, 0x0c }, + {0xaa0d, 0x0a }, + {0xaa24, 0x10 }, + {0xaa25, 0x12 }, + {0xaa28, 0x0b }, + {0xaa29, 0x07 }, + {0xaa2c, 0x10 }, + {0xaa2d, 0x14 }, + {0xaa34, 0x0e }, + {0xaa35, 0x0e }, + {0xaa38, 0x07 }, + {0xaa39, 0x07 }, + {0xaa3c, 0x0e }, + {0xaa3d, 0x0c }, + {0xaa48, 0x09 }, + {0xaa49, 0x0c }, + {0xaa4c, 0x0c }, + {0xaa4d, 0x07 }, + {0xaa54, 0x08 }, + {0xaa55, 0x06 }, + {0xaa58, 0x04 }, + {0xaa59, 0x05 }, + {0xaa5c, 0x06 }, + {0xaa5d, 0x06 }, + {0xaa68, 0x05 }, + {0xaa69, 0x05 }, + {0xaa6c, 0x04 }, + {0xaa6d, 0x05 }, + {0xaa74, 0x06 }, + {0xaa75, 0x04 }, + {0xaa78, 0x05 }, + {0xaa79, 0x05 }, + {0xaa7c, 0x04 }, + {0xaa7d, 0x06 }, + {0xac18, 0x14 }, + {0xac19, 0x00 }, + {0xac1c, 0x14 }, + {0xac1d, 0x00 }, + {0xac20, 0x14 }, + {0xac21, 0x00 }, + {0xac24, 0x14 }, + {0xac25, 0x00 }, + {0xac28, 0x14 }, + {0xac29, 0x00 }, + {0xac2c, 0x14 }, + {0xac2d, 0x00 }, + {0xac34, 0x16 }, + {0xac35, 0x00 }, + {0xac38, 0x16 }, + {0xac39, 0x00 }, + {0xac3c, 0x16 }, + {0xac3d, 0x00 }, + {0xac40, 0x16 }, + {0xac41, 0x00 }, + {0xac44, 0x16 }, + {0xac45, 0x00 }, + {0xac48, 0x16 }, + {0xac49, 0x00 }, + {0xac50, 0x1b }, + {0xac51, 0x00 }, + {0xac54, 0x1b }, + {0xac55, 0x00 }, + {0xac58, 0x1b }, + {0xac59, 0x00 }, + {0xac5c, 0x1b }, + {0xac5d, 0x00 }, + {0xac60, 0x1b }, + {0xac61, 0x00 }, + {0xac64, 0x1b }, + {0xac65, 0x00 }, + {0xac74, 0x09 }, + {0xac75, 0x0c }, + {0xac78, 0x0f }, + {0xac79, 0x11 }, + {0xac7c, 0x12 }, + {0xac7d, 0x14 }, + {0xac80, 0x09 }, + {0xac81, 0x0c }, + {0xac84, 0x0f }, + {0xac85, 0x11 }, + {0xac88, 0x12 }, + {0xac89, 0x14 }, + {0xac8c, 0x09 }, + {0xac8d, 0x0c }, + {0xac90, 0x0f }, + {0xac91, 0x11 }, + {0xac94, 0x12 }, + {0xac95, 0x14 }, + {0xac98, 0x09 }, + {0xac99, 0x0c }, + {0xac9c, 0x0f }, + {0xac9d, 0x11 }, + {0xaca0, 0x12 }, + {0xaca1, 0x14 }, + {0xaca4, 0x09 }, + {0xaca5, 0x0c }, + {0xaca8, 0x0f }, + {0xaca9, 0x11 }, + {0xacac, 0x12 }, + {0xacad, 0x14 }, + {0xacb0, 0x07 }, + {0xacb1, 0x09 }, + {0xacb4, 0x0c }, + {0xacb5, 0x0d }, + {0xacb8, 0x0d }, + {0xacb9, 0x0e }, + {0xacbc, 0x05 }, + {0xacbd, 0x07 }, + {0xacc0, 0x0a }, + {0xacc1, 0x0b }, + {0xacc4, 0x0b }, + {0xacc5, 0x0c }, + {0xacc8, 0x03 }, + {0xacc9, 0x04 }, + {0xaccc, 0x07 }, + {0xaccd, 0x08 }, + {0xacd0, 0x09 }, + {0xacd1, 0x09 }, + {0x35B5, 0x01 }, + {0x35BC, 0x01 }, + {0x360A, 0x02 }, + {0xFA9B, 0x01 }, +}; + +#define NUM_LSC_CAST_REGS 33 + +enum LSC_Cast_t{ + cast_H = 0, + cast_U30, + cast_CW, + cast_D, + cast_MAX +}; + +static short int LSC_CorrectionForCast[cast_MAX][NUM_LSC_CAST_REGS] = { + {-30, -20, 8, 11, -16, -26, -35, -53, -9, -10, 44, 57, -39, + -14, 50, -173, -38, -32, -1, 9, 39, 51, -33, -49, -28, + -22, 7, 11, -21, 17, -62, -56, 0}, + {-29, -18, 6, 1, 17, -35, -77, 0, 5, -17, -6, -22, -41, -1, + -37, 83, -38, -32, 1, -2, 15, 25, -67, 19, -28, -22, 5, + 2, -18, 21, -86, 0, 0}, + {-10, -15, -4, -6, -8, -3, -63, 8, 25, -9, -39, -51, -9, + 0, -21, 112, -10, -23, -7, -9, 10, 18, -11, 23, -10, + -15, -4, -6, -10, -3, -52, 7, 0}, + { 5, 3, -4, -5, -1, 3, 4, 8, 12, 3, -22, -21, 7, 17, + 2, 35, 8, 2, -3, -2, -9, -5, 10, 4, 9, 2, -4, -5, + -2, 0, -6, 9, 0} +}; + +static unsigned short LSC_CastRegs[] = { + 0xFB7E, /* H */ + 0xFB3C, /* U30 */ + 0xFAFA, /* CW */ + 0xFAB8 /* D65 */ +}; + +/*=============================================================*/ + +static int vx6953_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 2, + .buf = rxdata, + }, + }; + if (i2c_transfer(vx6953_client->adapter, msgs, 2) < 0) { + CDBG("vx6953_i2c_rxdata failed!\n"); + return -EIO; + } + return 0; +} +static int32_t vx6953_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(vx6953_client->adapter, msg, 1) < 0) { + CDBG("vx6953_i2c_txdata faild 0x%x\n", vx6953_client->addr); + return -EIO; + } + + return 0; +} + + +static int32_t vx6953_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = vx6953_i2c_rxdata(vx6953_client->addr>>1, buf, rlen); + if (rc < 0) { + CDBG("vx6953_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + return rc; +} +static int32_t vx6953_i2c_write_b_sensor(unsigned short waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = vx6953_i2c_txdata(vx6953_client->addr>>1, buf, 3); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + return rc; +} +static int32_t vx6953_i2c_write_w_sensor(unsigned short waddr, uint16_t wdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[4]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = (wdata & 0xFF00) >> 8; + buf[3] = (wdata & 0x00FF); + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, wdata); + rc = vx6953_i2c_txdata(vx6953_client->addr>>1, buf, 4); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, wdata); + } + return rc; +} +static int32_t vx6953_i2c_write_seq_sensor(unsigned short waddr, + uint8_t *bdata, uint16_t len) +{ + int32_t rc = -EFAULT; + unsigned char buf[len+2]; + int i; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + for (i = 2; i < len+2; i++) + buf[i] = *bdata++; + rc = vx6953_i2c_txdata(vx6953_client->addr>>1, buf, len+2); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata[0]); + } + return rc; +} + +static int32_t vx6953_i2c_write_w_table(struct vx6953_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < num; i++) { + rc = vx6953_i2c_write_b_sensor(reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +static void vx6953_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint16_t preview_frame_length_lines, snapshot_frame_length_lines; + uint16_t preview_line_length_pck, snapshot_line_length_pck; + uint32_t divider, d1, d2; + /* Total frame_length_lines and line_length_pck for preview */ + preview_frame_length_lines = VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES; + preview_line_length_pck = VX6953_QTR_SIZE_WIDTH + + VX6953_HRZ_QTR_BLK_PIXELS; + /* Total frame_length_lines and line_length_pck for snapshot */ + snapshot_frame_length_lines = VX6953_FULL_SIZE_HEIGHT + + VX6953_VER_FULL_BLK_LINES; + snapshot_line_length_pck = VX6953_FULL_SIZE_WIDTH + + VX6953_HRZ_FULL_BLK_PIXELS; + d1 = preview_frame_length_lines * 0x00000400/ + snapshot_frame_length_lines; + d2 = preview_line_length_pck * 0x00000400/ + snapshot_line_length_pck; + divider = d1 * d2 / 0x400; + /*Verify PCLK settings and frame sizes.*/ + *pfps = (uint16_t) (fps * divider / 0x400); + /* 2 is the ratio of no.of snapshot channels + to number of preview channels */ + +} + +static uint16_t vx6953_get_prev_lines_pf(void) +{ + if (vx6953_ctrl->prev_res == QTR_SIZE) + return VX6953_QTR_SIZE_HEIGHT + VX6953_VER_QTR_BLK_LINES; + else + return VX6953_FULL_SIZE_HEIGHT + VX6953_VER_FULL_BLK_LINES; + +} + +static uint16_t vx6953_get_prev_pixels_pl(void) +{ + if (vx6953_ctrl->prev_res == QTR_SIZE) + return VX6953_QTR_SIZE_WIDTH + VX6953_HRZ_QTR_BLK_PIXELS; + else + return VX6953_FULL_SIZE_WIDTH + VX6953_HRZ_FULL_BLK_PIXELS; +} + +static uint16_t vx6953_get_pict_lines_pf(void) +{ + if (vx6953_ctrl->pict_res == QTR_SIZE) + return VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES; + else + return VX6953_FULL_SIZE_HEIGHT + + VX6953_VER_FULL_BLK_LINES; +} + +static uint16_t vx6953_get_pict_pixels_pl(void) +{ + if (vx6953_ctrl->pict_res == QTR_SIZE) + return VX6953_QTR_SIZE_WIDTH + + VX6953_HRZ_QTR_BLK_PIXELS; + else + return VX6953_FULL_SIZE_WIDTH + + VX6953_HRZ_FULL_BLK_PIXELS; +} + +static uint32_t vx6953_get_pict_max_exp_lc(void) +{ + if (vx6953_ctrl->pict_res == QTR_SIZE) + return (VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES)*24; + else + return (VX6953_FULL_SIZE_HEIGHT + + VX6953_VER_FULL_BLK_LINES)*24; +} + +static int32_t vx6953_set_fps(struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + total_lines_per_frame = (uint16_t)((VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES) * vx6953_ctrl->fps_divider/0x400); + + vx6953_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if (vx6953_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_HI, + ((total_lines_per_frame & 0xFF00) >> 8)) < 0) + return rc; + if (vx6953_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_LO, + (total_lines_per_frame & 0x00FF)) < 0) + return rc; + vx6953_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF); + return rc; +} + +static int32_t vx6953_write_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t line_length_pck, frame_length_lines; + uint8_t gain_hi, gain_lo; + uint8_t intg_time_hi, intg_time_lo; + uint8_t frame_length_lines_hi = 0, frame_length_lines_lo = 0; + int32_t rc = 0; + if (vx6953_ctrl->sensormode != SENSOR_SNAPSHOT_MODE) { + frame_length_lines = VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES; + line_length_pck = VX6953_QTR_SIZE_WIDTH + + VX6953_HRZ_QTR_BLK_PIXELS; + if (line > (frame_length_lines - + VX6953_STM5M0EDOF_OFFSET)) { + vx6953_ctrl->fps = (uint16_t) (30 * Q8 * + (frame_length_lines - VX6953_STM5M0EDOF_OFFSET)/ + line); + } else { + vx6953_ctrl->fps = (uint16_t) (30 * Q8); + } + } else { + frame_length_lines = VX6953_FULL_SIZE_HEIGHT + + VX6953_VER_FULL_BLK_LINES; + line_length_pck = VX6953_FULL_SIZE_WIDTH + + VX6953_HRZ_FULL_BLK_PIXELS; + } + + vx6953_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + if ((line + VX6953_STM5M0EDOF_OFFSET) > MAX_FRAME_LENGTH_LINES) { + frame_length_lines = MAX_FRAME_LENGTH_LINES; + line = MAX_FRAME_LENGTH_LINES - VX6953_STM5M0EDOF_OFFSET; + } else if ((line + VX6953_STM5M0EDOF_OFFSET) > frame_length_lines) { + frame_length_lines = line + VX6953_STM5M0EDOF_OFFSET; + line = frame_length_lines; + } + + frame_length_lines_hi = (uint8_t) ((frame_length_lines & + 0xFF00) >> 8); + frame_length_lines_lo = (uint8_t) (frame_length_lines & + 0x00FF); + vx6953_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_HI, + frame_length_lines_hi); + vx6953_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_LO, + frame_length_lines_lo); + + /* update analogue gain registers */ + gain_hi = (uint8_t) ((gain & 0xFF00) >> 8); + gain_lo = (uint8_t) (gain & 0x00FF); + vx6953_i2c_write_b_sensor(REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + gain_lo); + vx6953_i2c_write_b_sensor(REG_DIGITAL_GAIN_GREEN_R_LO, gain_hi); + vx6953_i2c_write_b_sensor(REG_DIGITAL_GAIN_RED_LO, gain_hi); + vx6953_i2c_write_b_sensor(REG_DIGITAL_GAIN_BLUE_LO, gain_hi); + vx6953_i2c_write_b_sensor(REG_DIGITAL_GAIN_GREEN_B_LO, gain_hi); + CDBG("%s, gain_hi 0x%x, gain_lo 0x%x\n", __func__, + gain_hi, gain_lo); + /* update line count registers */ + intg_time_hi = (uint8_t) (((uint16_t)line & 0xFF00) >> 8); + intg_time_lo = (uint8_t) ((uint16_t)line & 0x00FF); + vx6953_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_HI, + intg_time_hi); + vx6953_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_LO, + intg_time_lo); + vx6953_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF); + + return rc; +} + +static int32_t vx6953_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + rc = vx6953_write_exp_gain(gain, line); + return rc; +} /* endof vx6953_set_pict_exp_gain*/ + +static int32_t vx6953_move_focus(int direction, + int32_t num_steps) +{ + return 0; +} + + +static int32_t vx6953_set_default_focus(uint8_t af_step) +{ + return 0; +} + +static int32_t vx6953_test(enum vx6953_test_mode_t mo) +{ + int32_t rc = 0; + if (mo == TEST_OFF) + return rc; + else { + /* REG_0x30D8[4] is TESBYPEN: 0: Normal Operation, + 1: Bypass Signal Processing + REG_0x30D8[5] is EBDMASK: 0: + Output Embedded data, 1: No output embedded data */ + if (vx6953_i2c_write_b_sensor(REG_TEST_PATTERN_MODE, + (uint8_t) mo) < 0) { + return rc; + } + } + return rc; +} + +static int vx6953_enable_edof(enum edof_mode_t edof_mode) +{ + int rc = 0; + if (edof_mode == VX6953_EDOF_ESTIMATION) { + /* EDof Estimation mode for preview */ + if (vx6953_i2c_write_b_sensor(REG_0x0b80, 0x02) < 0) + return rc; + CDBG("VX6953_EDOF_ESTIMATION"); + } else if (edof_mode == VX6953_EDOF_APPLICATION) { + /* EDof Application mode for Capture */ + if (vx6953_i2c_write_b_sensor(REG_0x0b80, 0x01) < 0) + return rc; + CDBG("VX6953_EDOF_APPLICATION"); + } else { + /* EDOF disabled */ + if (vx6953_i2c_write_b_sensor(REG_0x0b80, 0x00) < 0) + return rc; + CDBG("VX6953_EDOF_DISABLE"); + } + return rc; +} + +static int32_t vx6953_patch_for_cut2(void) +{ + int32_t rc = 0; + rc = vx6953_i2c_write_w_table(patch_tbl_cut2, + ARRAY_SIZE(patch_tbl_cut2)); + if (rc < 0) + return rc; + + return rc; +} + +static int32_t vx6953_lsc_patch(void) +{ + int32_t rc = 0; + int i, j; + short int v; + unsigned short version = 0; + unsigned short LSC_Raw[NUM_LSC_CAST_REGS]; + unsigned short LSC_Fixed[NUM_LSC_CAST_REGS]; + + vx6953_i2c_read(0x10, &version, 1); + CDBG("Cut 3 Version %d\n", version); + if (version != 1) + return 0; + + vx6953_i2c_write_b_sensor(0x3640, 0x00); + for (j = cast_H; j < cast_MAX; j++) { + for (i = 0; i < NUM_LSC_CAST_REGS; i++) { + rc = vx6953_i2c_read(LSC_CastRegs[cast_D]+(2*i), + &LSC_Raw[i], 2); + if (rc < 0) + return rc; + v = LSC_Raw[i]; + v += LSC_CorrectionForCast[j][i]; + LSC_Fixed[i] = (unsigned short) v; + } + for (i = 0; i < NUM_LSC_CAST_REGS; i++) { + rc = vx6953_i2c_write_w_sensor(LSC_CastRegs[j]+(2*i), + LSC_Fixed[i]); + if (rc < 0) + return rc; + } + } + CDBG("vx6953_lsc_patch done\n"); + return rc; +} + +static int32_t vx6953_sensor_setting(int update_type, int rt) +{ + + int32_t rc = 0; + unsigned short frame_cnt; + struct msm_camera_csi_params vx6953_csi_params; + if (vx6953_ctrl->sensor_type != VX6953_STM5M0EDOF_CUT_2) { + switch (update_type) { + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct vx6953_i2c_reg_conf init_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0]. + reg_0x0112}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0]. + reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {0x303, 0x01}, + {0x30b, 0x01}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + {REG_0x3210, 0x01}, + {REG_0x0111, + vx6953_regs.reg_pat_init[0]. + reg_0x0111}, + {REG_0x0b00, + vx6953_regs.reg_pat_init[0]. + reg_0x0b00}, + {REG_0x0136, + vx6953_regs.reg_pat_init[0]. + reg_0x0136}, + {REG_0x0137, + vx6953_regs.reg_pat_init[0]. + reg_0x0137}, + {REG_0x0b06, + vx6953_regs.reg_pat_init[0]. + reg_0x0b06}, + {REG_0x0b07, + vx6953_regs.reg_pat_init[0]. + reg_0x0b07}, + {REG_0x0b08, + vx6953_regs.reg_pat_init[0]. + reg_0x0b08}, + {REG_0x0b09, + vx6953_regs.reg_pat_init[0]. + reg_0x0b09}, + {REG_0x0b83, + vx6953_regs.reg_pat_init[0]. + reg_0x0b83}, + {REG_0x0b84, + vx6953_regs.reg_pat_init[0]. + reg_0x0b84}, + {REG_0x0b85, + vx6953_regs.reg_pat_init[0]. + reg_0x0b85}, + {REG_0x0b88, + vx6953_regs.reg_pat_init[0]. + reg_0x0b88}, + {REG_0x0b89, + vx6953_regs.reg_pat_init[0]. + reg_0x0b89}, + {REG_0x0b8a, + vx6953_regs.reg_pat_init[0]. + reg_0x0b8a}, + {0x3393, 0x06}, + {0x3394, 0x07}, + {0x338d, 0x08}, + {0x338e, 0x08}, + {0x338f, 0x00}, + }; + /* reset fps_divider */ + vx6953_ctrl->fps = 30 * Q8; + /* stop streaming */ + + count = 0; + CDBG("Init vx6953_sensor_setting standby\n"); + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + vx6953_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + + rc = vx6953_i2c_write_w_table(cut3_cali_data, + ARRAY_SIZE(cut3_cali_data)); + + vx6953_lsc_patch(); + + vx6953_i2c_write_w_sensor(0x100A, 0x07A3); + vx6953_i2c_write_w_sensor(0x114A, 0x002A); + vx6953_i2c_write_w_sensor(0x1716, 0x0204); + vx6953_i2c_write_w_sensor(0x1718, 0x0880); + + rc = vx6953_i2c_write_w_table(&init_tbl[0], + ARRAY_SIZE(init_tbl)); + if (rc < 0) + return rc; + + msleep(10); + + } + return rc; + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct vx6953_i2c_reg_conf preview_mode_tbl[] = { + {0x200, 0x02}, + {0x201, 0x26}, + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_0x0b80, + vx6953_regs.reg_pat[rt]. + reg_0x0b80}, + {REG_0x0900, + vx6953_regs.reg_pat[rt]. + reg_0x0900}, + {REG_0x0901, + vx6953_regs.reg_pat[rt]. + reg_0x0901}, + {REG_0x0902, + vx6953_regs.reg_pat[rt]. + reg_0x0902}, + {REG_0x0383, + vx6953_regs.reg_pat[rt]. + reg_0x0383}, + {REG_0x0387, + vx6953_regs.reg_pat[rt]. + reg_0x0387}, + {REG_0x034c, + vx6953_regs.reg_pat[rt]. + reg_0x034c}, + {REG_0x034d, + vx6953_regs.reg_pat[rt]. + reg_0x034d}, + {REG_0x034e, + vx6953_regs.reg_pat[rt]. + reg_0x034e}, + {REG_0x034f, + vx6953_regs.reg_pat[rt]. + reg_0x034f}, + {REG_0x3640, 0x00}, + }; + + struct vx6953_i2c_reg_conf snapshot_mode_tbl[] = { + {0x0200, 0x02}, + {0x0201, 0x54}, + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_0x0b80, + vx6953_regs.reg_pat[rt]. + reg_0x0b80}, + {REG_0x0900, + vx6953_regs.reg_pat[rt]. + reg_0x0900}, + {REG_0x0901, + vx6953_regs.reg_pat[rt]. + reg_0x0901}, + {REG_0x0902, + vx6953_regs.reg_pat[rt]. + reg_0x0902}, + {REG_0x0383, + vx6953_regs.reg_pat[rt]. + reg_0x0383}, + {REG_0x0387, + vx6953_regs.reg_pat[rt]. + reg_0x0387}, + {REG_0x034c, + vx6953_regs.reg_pat[rt]. + reg_0x034c}, + {REG_0x034d, + vx6953_regs.reg_pat[rt]. + reg_0x034d}, + {REG_0x034e, + vx6953_regs.reg_pat[rt]. + reg_0x034e}, + {REG_0x034f, + vx6953_regs.reg_pat[rt]. + reg_0x034f}, + {0x3140, 0x01}, + {REG_0x3640, 0x00}, + }; + /* stop streaming */ + + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + if (count == 0) { + vx6953_csi_params.data_format = CSI_8BIT; + vx6953_csi_params.lane_cnt = 1; + vx6953_csi_params.lane_assign = 0xe4; + vx6953_csi_params.dpcm_scheme = 0; + vx6953_csi_params.settle_cnt = 7; + rc = msm_camio_csi_config(&vx6953_csi_params); + if (rc < 0) + CDBG("config csi controller failed\n"); + + msleep(20); + count = 1; + } + + vx6953_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + + if (rt == RES_PREVIEW) { + rc = vx6953_i2c_write_w_table( + &preview_mode_tbl[0], + ARRAY_SIZE(preview_mode_tbl)); + if (rc < 0) + return rc; + } + if (rt == RES_CAPTURE) { + rc = vx6953_i2c_write_w_table( + &snapshot_mode_tbl[0], + ARRAY_SIZE(snapshot_mode_tbl)); + if (rc < 0) + return rc; + } + + vx6953_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF); + + /* Start sensor streaming */ + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STREAM) < 0) + return rc; + msleep(10); + + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + + while (frame_cnt == 0xFF) { + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + CDBG("frame_cnt=%d\n", frame_cnt); + msleep(2); + } + } + return rc; + default: + return rc; + } + } else { + switch (update_type) { + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct vx6953_i2c_reg_conf init_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + {REG_0x3030, + vx6953_regs.reg_pat_init[0].reg_0x3030}, + /* 953 specific registers */ + {REG_0x0111, + vx6953_regs.reg_pat_init[0].reg_0x0111}, + {REG_0x0b00, + vx6953_regs.reg_pat_init[0].reg_0x0b00}, + {REG_0x3001, + vx6953_regs.reg_pat_init[0].reg_0x3001}, + {REG_0x3004, + vx6953_regs.reg_pat_init[0].reg_0x3004}, + {REG_0x3007, + vx6953_regs.reg_pat_init[0].reg_0x3007}, + {REG_0x3016, + vx6953_regs.reg_pat_init[0].reg_0x3016}, + {REG_0x301d, + vx6953_regs.reg_pat_init[0].reg_0x301d}, + {REG_0x317e, + vx6953_regs.reg_pat_init[0].reg_0x317e}, + {REG_0x317f, + vx6953_regs.reg_pat_init[0].reg_0x317f}, + {REG_0x3400, + vx6953_regs.reg_pat_init[0].reg_0x3400}, + /* DEFCOR settings */ + /*Single Defect Correction Weight DISABLE*/ + {0x0b06, + vx6953_regs.reg_pat_init[0].reg_0x0b06}, + /*Single_defect_correct_weight = auto*/ + {0x0b07, + vx6953_regs.reg_pat_init[0].reg_0x0b07}, + /*Dynamic couplet correction ENABLED*/ + {0x0b08, + vx6953_regs.reg_pat_init[0].reg_0x0b08}, + /*Dynamic couplet correction weight*/ + {0x0b09, + vx6953_regs.reg_pat_init[0].reg_0x0b09}, + /* Clock Setup */ + /* Tell sensor ext clk is 24MHz*/ + {0x0136, + vx6953_regs.reg_pat_init[0].reg_0x0136}, + {0x0137, + vx6953_regs.reg_pat_init[0].reg_0x0137}, + /* The white balance gains must be written + to the sensor every frame. */ + /* Edof */ + {REG_0x0b83, + vx6953_regs.reg_pat_init[0].reg_0x0b83}, + {REG_0x0b84, + vx6953_regs.reg_pat_init[0].reg_0x0b84}, + {0x0b85, + vx6953_regs.reg_pat_init[0].reg_0x0b85}, + {0x0b88, + vx6953_regs.reg_pat_init[0].reg_0x0b88}, + {0x0b89, + vx6953_regs.reg_pat_init[0].reg_0x0b89}, + {REG_0x0b8a, + vx6953_regs.reg_pat_init[0].reg_0x0b8a}, + /* Mode specific regieters */ + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_0x3005, + vx6953_regs.reg_pat[rt].reg_0x3005}, + {0x3010, + vx6953_regs.reg_pat[rt].reg_0x3010}, + {REG_0x3011, + vx6953_regs.reg_pat[rt].reg_0x3011}, + {REG_0x301a, + vx6953_regs.reg_pat[rt].reg_0x301a}, + {REG_0x3035, + vx6953_regs.reg_pat[rt].reg_0x3035}, + {REG_0x3036, + vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3041, + vx6953_regs.reg_pat[rt].reg_0x3041}, + {0x3042, + vx6953_regs.reg_pat[rt].reg_0x3042}, + {REG_0x3045, + vx6953_regs.reg_pat[rt].reg_0x3045}, + /*EDOF: Estimation settings for Preview mode + Application settings for capture mode + (standard settings - Not tuned) */ + {REG_0x0b80, + vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0900, + vx6953_regs.reg_pat[rt].reg_0x0900}, + {REG_0x0901, + vx6953_regs.reg_pat[rt].reg_0x0901}, + {REG_0x0902, + vx6953_regs.reg_pat[rt].reg_0x0902}, + {REG_0x0383, + vx6953_regs.reg_pat[rt].reg_0x0383}, + {REG_0x0387, + vx6953_regs.reg_pat[rt].reg_0x0387}, + /* Change output size / frame rate */ + {REG_0x034c, + vx6953_regs.reg_pat[rt].reg_0x034c}, + {REG_0x034d, + vx6953_regs.reg_pat[rt].reg_0x034d}, + {REG_0x034e, + vx6953_regs.reg_pat[rt].reg_0x034e}, + {REG_0x034f, + vx6953_regs.reg_pat[rt].reg_0x034f}, + {REG_0x1716, + vx6953_regs.reg_pat[rt].reg_0x1716}, + {REG_0x1717, + vx6953_regs.reg_pat[rt].reg_0x1717}, + {REG_0x1718, + vx6953_regs.reg_pat[rt].reg_0x1718}, + {REG_0x1719, + vx6953_regs.reg_pat[rt].reg_0x1719}, + }; + /* reset fps_divider */ + vx6953_ctrl->fps = 30 * Q8; + /* stop streaming */ + + /* Reset everything first */ + if (vx6953_i2c_write_b_sensor(0x103, 0x01) < 0) { + CDBG("S/W reset failed\n"); + return rc; + } else + CDBG("S/W reset successful\n"); + + msleep(10); + + CDBG("Init vx6953_sensor_setting standby\n"); + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + /*vx6953_stm5m0edof_delay_msecs_stdby*/ + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + vx6953_patch_for_cut2(); + rc = vx6953_i2c_write_w_table(&init_tbl[0], + ARRAY_SIZE(init_tbl)); + if (rc < 0) + return rc; + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + } + return rc; + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct vx6953_i2c_reg_conf init_mode_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + {REG_0x3030, + vx6953_regs.reg_pat_init[0].reg_0x3030}, + /* 953 specific registers */ + {REG_0x0111, + vx6953_regs.reg_pat_init[0].reg_0x0111}, + {REG_0x0b00, + vx6953_regs.reg_pat_init[0].reg_0x0b00}, + {REG_0x3001, + vx6953_regs.reg_pat_init[0].reg_0x3001}, + {REG_0x3004, + vx6953_regs.reg_pat_init[0].reg_0x3004}, + {REG_0x3007, + vx6953_regs.reg_pat_init[0].reg_0x3007}, + {REG_0x3016, + vx6953_regs.reg_pat_init[0].reg_0x3016}, + {REG_0x301d, + vx6953_regs.reg_pat_init[0].reg_0x301d}, + {REG_0x317e, + vx6953_regs.reg_pat_init[0].reg_0x317e}, + {REG_0x317f, + vx6953_regs.reg_pat_init[0].reg_0x317f}, + {REG_0x3400, + vx6953_regs.reg_pat_init[0].reg_0x3400}, + {0x0b06, + vx6953_regs.reg_pat_init[0].reg_0x0b06}, + /*Single_defect_correct_weight = auto*/ + {0x0b07, + vx6953_regs.reg_pat_init[0].reg_0x0b07}, + /*Dynamic couplet correction ENABLED*/ + {0x0b08, + vx6953_regs.reg_pat_init[0].reg_0x0b08}, + /*Dynamic couplet correction weight*/ + {0x0b09, + vx6953_regs.reg_pat_init[0].reg_0x0b09}, + /* Clock Setup */ + /* Tell sensor ext clk is 24MHz*/ + {0x0136, + vx6953_regs.reg_pat_init[0].reg_0x0136}, + {0x0137, + vx6953_regs.reg_pat_init[0].reg_0x0137}, + /* The white balance gains must be written + to the sensor every frame. */ + /* Edof */ + {REG_0x0b83, + vx6953_regs.reg_pat_init[0].reg_0x0b83}, + {REG_0x0b84, + vx6953_regs.reg_pat_init[0].reg_0x0b84}, + {0x0b85, + vx6953_regs.reg_pat_init[0].reg_0x0b85}, + {0x0b88, + vx6953_regs.reg_pat_init[0].reg_0x0b88}, + {0x0b89, + vx6953_regs.reg_pat_init[0].reg_0x0b89}, + {REG_0x0b8a, + vx6953_regs.reg_pat_init[0].reg_0x0b8a}, + /* Mode specific regieters */ + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_0x3005, + vx6953_regs.reg_pat[rt].reg_0x3005}, + {0x3010, + vx6953_regs.reg_pat[rt].reg_0x3010}, + {REG_0x3011, + vx6953_regs.reg_pat[rt].reg_0x3011}, + {REG_0x301a, + vx6953_regs.reg_pat[rt].reg_0x301a}, + {REG_0x3035, + vx6953_regs.reg_pat[rt].reg_0x3035}, + {REG_0x3036, + vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3041, + vx6953_regs.reg_pat[rt].reg_0x3041}, + {0x3042, + vx6953_regs.reg_pat[rt].reg_0x3042}, + {REG_0x3045, + vx6953_regs.reg_pat[rt].reg_0x3045}, + /*EDOF: Estimation settings for Preview mode + Application settings for capture mode + (standard settings - Not tuned) */ + {REG_0x0b80, + vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0900, + vx6953_regs.reg_pat[rt].reg_0x0900}, + {REG_0x0901, + vx6953_regs.reg_pat[rt].reg_0x0901}, + {REG_0x0902, + vx6953_regs.reg_pat[rt].reg_0x0902}, + {REG_0x0383, + vx6953_regs.reg_pat[rt].reg_0x0383}, + {REG_0x0387, + vx6953_regs.reg_pat[rt].reg_0x0387}, + /* Change output size / frame rate */ + {REG_0x034c, + vx6953_regs.reg_pat[rt].reg_0x034c}, + {REG_0x034d, + vx6953_regs.reg_pat[rt].reg_0x034d}, + {REG_0x034e, + vx6953_regs.reg_pat[rt].reg_0x034e}, + {REG_0x034f, + vx6953_regs.reg_pat[rt].reg_0x034f}, + {REG_0x1716, + vx6953_regs.reg_pat[rt].reg_0x1716}, + {REG_0x1717, + vx6953_regs.reg_pat[rt].reg_0x1717}, + {REG_0x1718, + vx6953_regs.reg_pat[rt].reg_0x1718}, + {REG_0x1719, + vx6953_regs.reg_pat[rt].reg_0x1719}, + }; + struct vx6953_i2c_reg_conf mode_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + /* Mode specific regieters */ + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt].frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt].frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt].line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt].line_length_pck_lo}, + {REG_0x3005, vx6953_regs.reg_pat[rt].reg_0x3005}, + {0x3010, vx6953_regs.reg_pat[rt].reg_0x3010}, + {REG_0x3011, vx6953_regs.reg_pat[rt].reg_0x3011}, + {REG_0x301a, vx6953_regs.reg_pat[rt].reg_0x301a}, + {REG_0x3035, vx6953_regs.reg_pat[rt].reg_0x3035}, + {REG_0x3036, vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3041, vx6953_regs.reg_pat[rt].reg_0x3041}, + {0x3042, vx6953_regs.reg_pat[rt].reg_0x3042}, + {REG_0x3045, vx6953_regs.reg_pat[rt].reg_0x3045}, + /*EDOF: Estimation settings for Preview mode + Application settings for capture + mode(standard settings - Not tuned) */ + {REG_0x0b80, vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0900, vx6953_regs.reg_pat[rt].reg_0x0900}, + {REG_0x0901, vx6953_regs.reg_pat[rt].reg_0x0901}, + {REG_0x0902, vx6953_regs.reg_pat[rt].reg_0x0902}, + {REG_0x0383, vx6953_regs.reg_pat[rt].reg_0x0383}, + {REG_0x0387, vx6953_regs.reg_pat[rt].reg_0x0387}, + /* Change output size / frame rate */ + {REG_0x034c, vx6953_regs.reg_pat[rt].reg_0x034c}, + {REG_0x034d, vx6953_regs.reg_pat[rt].reg_0x034d}, + {REG_0x034e, vx6953_regs.reg_pat[rt].reg_0x034e}, + {REG_0x034f, vx6953_regs.reg_pat[rt].reg_0x034f}, + /*{0x200, vx6953_regs.reg_pat[rt].reg_0x0200}, + {0x201, vx6953_regs.reg_pat[rt].reg_0x0201},*/ + {REG_0x1716, vx6953_regs.reg_pat[rt].reg_0x1716}, + {REG_0x1717, vx6953_regs.reg_pat[rt].reg_0x1717}, + {REG_0x1718, vx6953_regs.reg_pat[rt].reg_0x1718}, + {REG_0x1719, vx6953_regs.reg_pat[rt].reg_0x1719}, + }; + /* stop streaming */ + msleep(5); + + /* Reset everything first */ + if (vx6953_i2c_write_b_sensor(0x103, 0x01) < 0) { + CDBG("S/W reset failed\n"); + return rc; + } else + CDBG("S/W reset successful\n"); + + msleep(10); + + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + /*vx6953_stm5m0edof_delay_msecs_stdby*/ + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + + vx6953_csi_params.data_format = CSI_8BIT; + vx6953_csi_params.lane_cnt = 1; + vx6953_csi_params.lane_assign = 0xe4; + vx6953_csi_params.dpcm_scheme = 0; + vx6953_csi_params.settle_cnt = 7; + rc = msm_camio_csi_config(&vx6953_csi_params); + if (rc < 0) + CDBG(" config csi controller failed \n"); + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + vx6953_patch_for_cut2(); + rc = vx6953_i2c_write_w_table(&init_mode_tbl[0], + ARRAY_SIZE(init_mode_tbl)); + if (rc < 0) + return rc; + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + rc = vx6953_i2c_write_w_table(&mode_tbl[0], + ARRAY_SIZE(mode_tbl)); + if (rc < 0) + return rc; + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + /* Start sensor streaming */ + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STREAM) < 0) + return rc; + msleep(vx6953_stm5m0edof_delay_msecs_stream); + + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + + while (frame_cnt == 0xFF) { + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + CDBG("frame_cnt=%d", frame_cnt); + msleep(10); + } + } + return rc; + default: + return rc; + } + } + return rc; +} + + +static int32_t vx6953_video_config(int mode) +{ + + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + if (vx6953_ctrl->curr_res != vx6953_ctrl->prev_res) { + if (vx6953_ctrl->prev_res == QTR_SIZE) { + rt = RES_PREVIEW; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((2 * 1000 * vx6953_ctrl->fps_divider) / + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } else { + rt = RES_CAPTURE; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((1000 * vx6953_ctrl->fps_divider) / + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } + if (vx6953_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + if (vx6953_ctrl->set_test) { + if (vx6953_test(vx6953_ctrl->set_test) < 0) + return rc; + } + vx6953_ctrl->edof_mode = VX6953_EDOF_ESTIMATION; + rc = vx6953_enable_edof(vx6953_ctrl->edof_mode); + if (rc < 0) + return rc; + vx6953_ctrl->curr_res = vx6953_ctrl->prev_res; + vx6953_ctrl->sensormode = mode; + return rc; +} + +static int32_t vx6953_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + /*change sensor resolution if needed */ + if (vx6953_ctrl->curr_res != vx6953_ctrl->pict_res) { + if (vx6953_ctrl->pict_res == QTR_SIZE) { + rt = RES_PREVIEW; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((2 * 1000 * vx6953_ctrl->fps_divider) / + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } else { + rt = RES_CAPTURE; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((1000 * vx6953_ctrl->fps_divider) / + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } + if (vx6953_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + + vx6953_ctrl->edof_mode = VX6953_EDOF_APPLICATION; + if (vx6953_enable_edof(vx6953_ctrl->edof_mode) < 0) + return rc; + vx6953_ctrl->curr_res = vx6953_ctrl->pict_res; + vx6953_ctrl->sensormode = mode; + return rc; +} /*end of vx6953_snapshot_config*/ + +static int32_t vx6953_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + if (vx6953_ctrl->curr_res != vx6953_ctrl->pict_res) { + if (vx6953_ctrl->pict_res == QTR_SIZE) { + rt = RES_PREVIEW; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((2 * 1000 * vx6953_ctrl->fps_divider)/ + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } else { + rt = RES_CAPTURE; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((1000 * vx6953_ctrl->fps_divider)/ + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } + if (vx6953_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + vx6953_ctrl->edof_mode = VX6953_EDOF_APPLICATION; + if (vx6953_enable_edof(vx6953_ctrl->edof_mode) < 0) + return rc; + vx6953_ctrl->curr_res = vx6953_ctrl->pict_res; + vx6953_ctrl->sensormode = mode; + return rc; +} /*end of vx6953_raw_snapshot_config*/ +static int32_t vx6953_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = vx6953_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + rc = vx6953_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + rc = vx6953_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} +static int32_t vx6953_power_down(void) +{ + return 0; +} + + +static int vx6953_probe_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_set_value_cansleep(data->sensor_reset, 0); + gpio_free(data->sensor_reset); + return 0; +} +static int vx6953_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + int32_t rc = 0; + unsigned short chipidl, chipidh; + CDBG("%s: %d\n", __func__, __LINE__); + rc = gpio_request(data->sensor_reset, "vx6953"); + CDBG(" vx6953_probe_init_sensor \n"); + if (!rc) { + CDBG("sensor_reset = %d\n", rc); + CDBG(" vx6953_probe_init_sensor 1\n"); + gpio_direction_output(data->sensor_reset, 0); + msleep(10); + CDBG(" vx6953_probe_init_sensor 1\n"); + gpio_set_value_cansleep(data->sensor_reset, 1); + } else { + CDBG(" vx6953_probe_init_sensor 2\n"); + goto init_probe_done; + } + msleep(20); + CDBG(" vx6953_probe_init_sensor is called\n"); + /* 3. Read sensor Model ID: */ + rc = vx6953_i2c_read(0x0000, &chipidh, 1); + if (rc < 0) { + CDBG(" vx6953_probe_init_sensor 3\n"); + goto init_probe_fail; + } + rc = vx6953_i2c_read(0x0001, &chipidl, 1); + if (rc < 0) { + CDBG(" vx6953_probe_init_sensor4\n"); + goto init_probe_fail; + } + CDBG("vx6953 model_id = 0x%x 0x%x\n", chipidh, chipidl); + /* 4. Compare sensor ID to VX6953 ID: */ + if (chipidh != 0x03 || chipidl != 0xB9) { + rc = -ENODEV; + CDBG("vx6953_probe_init_sensor fail chip id doesnot match\n"); + goto init_probe_fail; + } + goto init_probe_done; +init_probe_fail: + CDBG(" vx6953_probe_init_sensor fails\n"); + vx6953_probe_init_done(data); +init_probe_done: + CDBG(" vx6953_probe_init_sensor finishes\n"); + return rc; + } +/* camsensor_iu060f_vx6953_reset */ +int vx6953_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + unsigned short revision_number; + int32_t rc = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + CDBG("Calling vx6953_sensor_open_init\n"); + vx6953_ctrl = kzalloc(sizeof(struct vx6953_ctrl_t), GFP_KERNEL); + if (!vx6953_ctrl) { + CDBG("vx6953_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + vx6953_ctrl->fps_divider = 1 * 0x00000400; + vx6953_ctrl->pict_fps_divider = 1 * 0x00000400; + vx6953_ctrl->set_test = TEST_OFF; + vx6953_ctrl->prev_res = QTR_SIZE; + vx6953_ctrl->pict_res = FULL_SIZE; + vx6953_ctrl->curr_res = INVALID_SIZE; + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_2; + vx6953_ctrl->edof_mode = VX6953_EDOF_ESTIMATION; + if (data) + vx6953_ctrl->sensordata = data; + if (rc < 0) { + CDBG("Calling vx6953_sensor_open_init fail1\n"); + return rc; + } + CDBG("%s: %d\n", __func__, __LINE__); + /* enable mclk first */ + msm_camio_clk_rate_set(VX6953_STM5M0EDOF_DEFAULT_MASTER_CLK_RATE); + CDBG("%s: %d\n", __func__, __LINE__); + rc = vx6953_probe_init_sensor(data); + if (rc < 0) { + CDBG("Calling vx6953_sensor_open_init fail3\n"); + goto init_fail; + } + if (vx6953_i2c_read(0x0002, &revision_number, 1) < 0) + return rc; + CDBG("sensor revision number major = 0x%x\n", revision_number); + if (vx6953_i2c_read(0x0018, &revision_number, 1) < 0) + return rc; + CDBG("sensor revision number = 0x%x\n", revision_number); + if (revision_number == VX6953_REVISION_NUMBER_CUT3) { + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_3; + CDBG("VX6953 EDof Cut 3.0 sensor\n "); + } else if (revision_number == VX6953_REVISION_NUMBER_CUT2) { + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_2; + CDBG("VX6953 EDof Cut 2.0 sensor\n "); + } else {/* Cut1.0 reads 0x00 for register 0x0018*/ + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_1; + CDBG("VX6953 EDof Cut 1.0 sensor\n "); + } + if (vx6953_ctrl->prev_res == QTR_SIZE) { + if (vx6953_sensor_setting(REG_INIT, RES_PREVIEW) < 0) + return rc; + } else { + if (vx6953_sensor_setting(REG_INIT, RES_CAPTURE) < 0) + return rc; + } + vx6953_ctrl->fps = 30*Q8; + if (rc < 0) + goto init_fail; + else + goto init_done; +init_fail: + CDBG("init_fail\n"); + vx6953_probe_init_done(data); + kfree(vx6953_ctrl); +init_done: + CDBG("init_done\n"); + return rc; +} /*endof vx6953_sensor_open_init*/ + +static int vx6953_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&vx6953_wait_queue); + return 0; +} + +static const struct i2c_device_id vx6953_i2c_id[] = { + {"vx6953", 0}, + { } +}; + +static int vx6953_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("vx6953_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + vx6953_sensorw = kzalloc(sizeof(struct vx6953_work_t), GFP_KERNEL); + if (!vx6953_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, vx6953_sensorw); + vx6953_init_client(client); + vx6953_client = client; + + msleep(50); + + CDBG("vx6953_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("vx6953_probe failed! rc = %d\n", rc); + return rc; +} + +static int vx6953_send_wb_info(struct wb_info_cfg *wb) +{ + unsigned short read_data; + uint8_t temp[8]; + int rc = 0; + int i = 0; + + /* red_gain */ + temp[2] = wb->red_gain >> 8; + temp[3] = wb->red_gain & 0xFF; + + /* green_gain */ + temp[0] = wb->green_gain >> 8; + temp[1] = wb->green_gain & 0xFF; + temp[6] = temp[0]; + temp[7] = temp[1]; + + /* blue_gain */ + temp[4] = wb->blue_gain >> 8; + temp[5] = wb->blue_gain & 0xFF; + rc = vx6953_i2c_write_seq_sensor(0x0B8E, &temp[0], 8); + + for (i = 0; i < 6; i++) { + rc = vx6953_i2c_read(0x0B8E + i, &read_data, 1); + CDBG("%s addr 0x%x val %d \n", __func__, 0x0B8E + i, read_data); + } + rc = vx6953_i2c_read(0x0B82, &read_data, 1); + CDBG("%s addr 0x%x val %d \n", __func__, 0x0B82, read_data); + if (rc < 0) + return rc; + return rc; +} /*end of vx6953_snapshot_config*/ + +static int __exit vx6953_remove(struct i2c_client *client) +{ + struct vx6953_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + vx6953_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver vx6953_i2c_driver = { + .id_table = vx6953_i2c_id, + .probe = vx6953_i2c_probe, + .remove = __exit_p(vx6953_i2c_remove), + .driver = { + .name = "vx6953", + }, +}; + +int vx6953_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&vx6953_mut); + CDBG("vx6953_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + vx6953_get_pict_fps( + cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + vx6953_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + vx6953_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + vx6953_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + vx6953_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + vx6953_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = vx6953_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = + vx6953_write_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + rc = + vx6953_set_pict_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = vx6953_set_sensor_mode(cdata.mode, + cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = vx6953_power_down(); + break; + + case CFG_MOVE_FOCUS: + rc = + vx6953_move_focus( + cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = + vx6953_set_default_focus( + cdata.cfg.focus.steps); + break; + + case CFG_SET_EFFECT: + rc = vx6953_set_default_focus( + cdata.cfg.effect); + break; + + + case CFG_SEND_WB_INFO: + rc = vx6953_send_wb_info( + &(cdata.cfg.wb_info)); + break; + + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&vx6953_mut); + + return rc; +} + + + + +static int vx6953_sensor_release(void) +{ + int rc = -EBADF; + mutex_lock(&vx6953_mut); + vx6953_power_down(); + gpio_direction_output(vx6953_ctrl->sensordata->sensor_reset, 0); + gpio_free(vx6953_ctrl->sensordata->sensor_reset); + kfree(vx6953_ctrl); + vx6953_ctrl = NULL; + CDBG("vx6953_release completed\n"); + mutex_unlock(&vx6953_mut); + + return rc; +} + +static int vx6953_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(&vx6953_i2c_driver); + if (rc < 0 || vx6953_client == NULL) { + rc = -ENOTSUPP; + goto probe_fail; + } + msm_camio_clk_rate_set(24000000); + rc = vx6953_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + s->s_init = vx6953_sensor_open_init; + s->s_release = vx6953_sensor_release; + s->s_config = vx6953_sensor_config; + s->s_mount_angle = 0; + vx6953_probe_init_done(info); + return rc; + +probe_fail: + CDBG("vx6953_sensor_probe: SENSOR PROBE FAILS!\n"); + return rc; +} + +static int __vx6953_probe(struct platform_device *pdev) +{ + return msm_camera_drv_start(pdev, vx6953_sensor_probe); +} + +static struct platform_driver msm_camera_driver = { + .probe = __vx6953_probe, + .driver = { + .name = "msm_camera_vx6953", + .owner = THIS_MODULE, + }, +}; + +static int __init vx6953_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(vx6953_init); +void vx6953_exit(void) +{ + i2c_del_driver(&vx6953_i2c_driver); +} + + diff --git a/drivers/media/video/msm/vx6953.h b/drivers/media/video/msm/vx6953.h new file mode 100644 index 00000000000..0e120635a5d --- /dev/null +++ b/drivers/media/video/msm/vx6953.h @@ -0,0 +1,136 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VX6953_H +#define VX6953_H +#include +#include +extern struct vx6953_reg vx6953_regs; +struct reg_struct_init { + uint8_t reg_0x0112; /* 0x0112*/ + uint8_t reg_0x0113; /* 0x0113*/ + uint8_t vt_pix_clk_div; /* 0x0301*/ + uint8_t pre_pll_clk_div; /* 0x0305*/ + uint8_t pll_multiplier; /* 0x0307*/ + uint8_t op_pix_clk_div; /* 0x0309*/ + uint8_t reg_0x3030; /*0x3030*/ + uint8_t reg_0x0111; /*0x0111*/ + uint8_t reg_0x0b00; /*0x0b00*/ + uint8_t reg_0x3001; /*0x3001*/ + uint8_t reg_0x3004; /*0x3004*/ + uint8_t reg_0x3007; /*0x3007*/ + uint8_t reg_0x3016; /*0x3016*/ + uint8_t reg_0x301d; /*0x301d*/ + uint8_t reg_0x317e; /*0x317E*/ + uint8_t reg_0x317f; /*0x317F*/ + uint8_t reg_0x3400; /*0x3400*/ + uint8_t reg_0x0b06; /*0x0b06*/ + uint8_t reg_0x0b07; /*0x0b07*/ + uint8_t reg_0x0b08; /*0x0b08*/ + uint8_t reg_0x0b09; /*0x0b09*/ + uint8_t reg_0x0136; + uint8_t reg_0x0137; + /* Edof */ + uint8_t reg_0x0b83; /*0x0b83*/ + uint8_t reg_0x0b84; /*0x0b84*/ + uint8_t reg_0x0b85; /*0x0b85*/ + uint8_t reg_0x0b88; /*0x0b88*/ + uint8_t reg_0x0b89; /*0x0b89*/ + uint8_t reg_0x0b8a; /*0x0b8a*/ + }; +struct reg_struct { + uint8_t coarse_integration_time_hi; /*REG_COARSE_INTEGRATION_TIME_HI*/ + uint8_t coarse_integration_time_lo; /*REG_COARSE_INTEGRATION_TIME_LO*/ + uint8_t analogue_gain_code_global; + uint8_t frame_length_lines_hi; /* 0x0340*/ + uint8_t frame_length_lines_lo; /* 0x0341*/ + uint8_t line_length_pck_hi; /* 0x0342*/ + uint8_t line_length_pck_lo; /* 0x0343*/ + uint8_t reg_0x3005; /* 0x3005*/ + uint8_t reg_0x3010; /* 0x3010*/ + uint8_t reg_0x3011; /* 0x3011*/ + uint8_t reg_0x301a; /* 0x301a*/ + uint8_t reg_0x3035; /* 0x3035*/ + uint8_t reg_0x3036; /* 0x3036*/ + uint8_t reg_0x3041; /*0x3041*/ + uint8_t reg_0x3042; /*0x3042*/ + uint8_t reg_0x3045; /*0x3045*/ + uint8_t reg_0x0b80; /* 0x0b80*/ + uint8_t reg_0x0900; /*0x0900*/ + uint8_t reg_0x0901; /* 0x0901*/ + uint8_t reg_0x0902; /*0x0902*/ + uint8_t reg_0x0383; /*0x0383*/ + uint8_t reg_0x0387; /* 0x0387*/ + uint8_t reg_0x034c; /* 0x034c*/ + uint8_t reg_0x034d; /*0x034d*/ + uint8_t reg_0x034e; /* 0x034e*/ + uint8_t reg_0x034f; /* 0x034f*/ + uint8_t reg_0x1716; /*0x1716*/ + uint8_t reg_0x1717; /*0x1717*/ + uint8_t reg_0x1718; /*0x1718*/ + uint8_t reg_0x1719; /*0x1719*/ + uint8_t reg_0x3210;/*0x3210*/ + uint8_t reg_0x111; /*0x111*/ + uint8_t reg_0x3410; /*0x3410*/ + uint8_t reg_0x3098; + uint8_t reg_0x309D; + uint8_t reg_0x0200; + uint8_t reg_0x0201; + }; +struct vx6953_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +enum vx6953_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum vx6953_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; +enum vx6953_setting { + RES_PREVIEW, + RES_CAPTURE +}; +enum mt9p012_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +enum sensor_revision_t { + VX6953_STM5M0EDOF_CUT_1, + VX6953_STM5M0EDOF_CUT_2, + VX6953_STM5M0EDOF_CUT_3 +}; +enum edof_mode_t { + VX6953_EDOF_DISABLE, /* 0x00 */ + VX6953_EDOF_APPLICATION, /* 0x01 */ + VX6953_EDOF_ESTIMATION /* 0x02 */ +}; +struct vx6953_reg { + const struct reg_struct_init *reg_pat_init; + const struct reg_struct *reg_pat; +}; +#endif /* VX6953_H */ diff --git a/drivers/media/video/msm/vx6953_reg.c b/drivers/media/video/msm/vx6953_reg.c new file mode 100644 index 00000000000..48fc71f5bf4 --- /dev/null +++ b/drivers/media/video/msm/vx6953_reg.c @@ -0,0 +1,135 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#include "vx6953.h" +const struct reg_struct_init vx6953_reg_init[1] = { + { + 10, /*REG = 0x0112 , 10 bit */ + 10, /*REG = 0x0113*/ + 9, /*REG = 0x0301 vt_pix_clk_div*/ + 4, /*REG = 0x0305 pre_pll_clk_div*/ + 133, /*REG = 0x0307 pll_multiplier*/ + 10, /*REG = 0x0309 op_pix_clk_div*/ + 0x08, /*REG = 0x3030*/ + 0x02, /*REG = 0x0111*/ + 0x01, /*REG = 0x0b00 ,lens shading off */ + 0x30, /*REG = 0x3001*/ + 0x33, /*REG = 0x3004*/ + 0x09, /*REG = 0x3007*/ + 0x1F, /*REG = 0x3016*/ + 0x03, /*REG = 0x301d*/ + 0x11, /*REG = 0x317E*/ + 0x09, /*REG = 0x317F*/ + 0x38, /*REG = 0x3400*/ + 0x00, /*REG_0x0b06*/ + 0x80, /*REG_0x0b07*/ + 0x01, /*REG_0x0b08*/ + 0x4F, /*REG_0x0b09*/ + 0x18, /*REG_0x0136*/ + 0x00, /*/REG_0x0137*/ + 0x20, /*REG = 0x0b83*/ + 0x90, /*REG = 0x0b84*/ + 0x20, /*REG = 0x0b85*/ + 0x80, /*REG = 0x0b88*/ + 0x00, /*REG = 0x0b89*/ + 0x00, /*REG = 0x0b8a*/ + } +}; +const struct reg_struct vx6953_reg_pat[2] = { + {/* Preview */ + 0x03, /*REG = 0x0202 coarse integration_time_hi*/ + 0xd0, /*REG = 0x0203 coarse_integration_time_lo*/ + 0xc0, /*REG = 0x0205 analogue_gain_code_global*/ + 0x03, /*REG = 0x0340 frame_length_lines_hi*/ + 0xf0, /*REG = 0x0341 frame_length_lines_lo*/ + 0x0b, /*REG = 0x0342 line_length_pck_hi*/ + 0x74, /*REG = 0x0343 line_length_pck_lo*/ + 0x03, /*REG = 0x3005*/ + 0x00, /*REG = 0x3010*/ + 0x01, /*REG = 0x3011*/ + 0x6a, /*REG = 0x301a*/ + 0x03, /*REG = 0x3035*/ + 0x2c, /*REG = 0x3036*/ + 0x00, /*REG = 0x3041*/ + 0x24, /*REG = 0x3042*/ + 0x81, /*REG = 0x3045*/ + 0x02, /*REG = 0x0b80 edof estimate*/ + 0x01, /*REG = 0x0900*/ + 0x22, /*REG = 0x0901*/ + 0x04, /*REG = 0x0902*/ + 0x03, /*REG = 0x0383*/ + 0x03, /*REG = 0x0387*/ + 0x05, /*REG = 0x034c*/ + 0x18, /*REG = 0x034d*/ + 0x03, /*REG = 0x034e*/ + 0xd4, /*REG = 0x034f*/ + 0x02, /*0x1716*/ + 0x04, /*0x1717*/ + 0x08, /*0x1718*/ + 0x2c, /*0x1719*/ + 0x01, /*0x3210*/ + 0x02, /*0x111*/ + 0x01, /*0x3410*/ + 0x01, /*0x3098*/ + 0x05, /*0x309D*/ + 0x02, + 0x04, + }, + { /* Snapshot */ + 0x07,/*REG = 0x0202 coarse_integration_time_hi*/ + 0x00,/*REG = 0x0203 coarse_integration_time_lo*/ + 0xc0,/*REG = 0x0205 analogue_gain_code_global*/ + 0x07,/*REG = 0x0340 frame_length_lines_hi*/ + 0xd0,/*REG = 0x0341 frame_length_lines_lo*/ + 0x0b,/*REG = 0x0342 line_length_pck_hi*/ + 0x8c,/*REG = 0x0343 line_length_pck_lo*/ + 0x01,/*REG = 0x3005*/ + 0x00,/*REG = 0x3010*/ + 0x00,/*REG = 0x3011*/ + 0x55,/*REG = 0x301a*/ + 0x01,/*REG = 0x3035*/ + 0x23,/*REG = 0x3036*/ + 0x00,/*REG = 0x3041*/ + 0x24,/*REG = 0x3042*/ + 0xb7,/*REG = 0x3045*/ + 0x01,/*REG = 0x0b80 edof application*/ + 0x00,/*REG = 0x0900*/ + 0x00,/*REG = 0x0901*/ + 0x00,/*REG = 0x0902*/ + 0x01,/*REG = 0x0383*/ + 0x01,/*REG = 0x0387*/ + 0x0A,/*REG = 0x034c*/ + 0x30,/*REG = 0x034d*/ + 0x07,/*REG = 0x034e*/ + 0xA8,/*REG = 0x034f*/ + 0x02,/*0x1716*/ + 0x0d,/*0x1717*/ + 0x07,/*0x1718*/ + 0x7d,/*0x1719*/ + 0x01,/*0x3210*/ + 0x02,/*0x111*/ + 0x01,/*0x3410*/ + 0x01,/*0x3098*/ + 0x05, /*0x309D*/ + 0x02, + 0x00, + } +}; + + + +struct vx6953_reg vx6953_regs = { + .reg_pat_init = &vx6953_reg_init[0], + .reg_pat = &vx6953_reg_pat[0], +}; diff --git a/drivers/media/video/msm/vx6953_reg_v4l2.c b/drivers/media/video/msm/vx6953_reg_v4l2.c new file mode 100644 index 00000000000..f16054b6865 --- /dev/null +++ b/drivers/media/video/msm/vx6953_reg_v4l2.c @@ -0,0 +1,135 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#include "vx6953_v4l2.h" +const struct reg_struct_init vx6953_reg_init[1] = { + { + 10, /*REG = 0x0112 , 10 bit */ + 10, /*REG = 0x0113*/ + 9, /*REG = 0x0301 vt_pix_clk_div*/ + 4, /*REG = 0x0305 pre_pll_clk_div*/ + 133, /*REG = 0x0307 pll_multiplier*/ + 10, /*REG = 0x0309 op_pix_clk_div*/ + 0x08, /*REG = 0x3030*/ + 0x02, /*REG = 0x0111*/ + 0x01, /*REG = 0x0b00 ,lens shading off */ + 0x30, /*REG = 0x3001*/ + 0x33, /*REG = 0x3004*/ + 0x09, /*REG = 0x3007*/ + 0x1F, /*REG = 0x3016*/ + 0x03, /*REG = 0x301d*/ + 0x11, /*REG = 0x317E*/ + 0x09, /*REG = 0x317F*/ + 0x38, /*REG = 0x3400*/ + 0x00, /*REG_0x0b06*/ + 0x80, /*REG_0x0b07*/ + 0x01, /*REG_0x0b08*/ + 0x4F, /*REG_0x0b09*/ + 0x18, /*REG_0x0136*/ + 0x00, /*/REG_0x0137*/ + 0x20, /*REG = 0x0b83*/ + 0x90, /*REG = 0x0b84*/ + 0x20, /*REG = 0x0b85*/ + 0x80, /*REG = 0x0b88*/ + 0x00, /*REG = 0x0b89*/ + 0x00, /*REG = 0x0b8a*/ + } +}; +const struct reg_struct vx6953_reg_pat[2] = { + {/* Preview */ + 0x03, /*REG = 0x0202 coarse integration_time_hi*/ + 0xd0, /*REG = 0x0203 coarse_integration_time_lo*/ + 0xc0, /*REG = 0x0205 analogue_gain_code_global*/ + 0x03, /*REG = 0x0340 frame_length_lines_hi*/ + 0xf0, /*REG = 0x0341 frame_length_lines_lo*/ + 0x0b, /*REG = 0x0342 line_length_pck_hi*/ + 0xa5, /*REG = 0x0343 line_length_pck_lo*/ + 0x03, /*REG = 0x3005*/ + 0x00, /*REG = 0x3010*/ + 0x01, /*REG = 0x3011*/ + 0x6a, /*REG = 0x301a*/ + 0x03, /*REG = 0x3035*/ + 0x2c, /*REG = 0x3036*/ + 0x00, /*REG = 0x3041*/ + 0x24, /*REG = 0x3042*/ + 0x81, /*REG = 0x3045*/ + 0x02, /*REG = 0x0b80 edof estimate*/ + 0x01, /*REG = 0x0900*/ + 0x22, /*REG = 0x0901*/ + 0x04, /*REG = 0x0902*/ + 0x03, /*REG = 0x0383*/ + 0x03, /*REG = 0x0387*/ + 0x05, /*REG = 0x034c*/ + 0x18, /*REG = 0x034d*/ + 0x03, /*REG = 0x034e*/ + 0xd4, /*REG = 0x034f*/ + 0x02, /*0x1716*/ + 0x04, /*0x1717*/ + 0x08, /*0x1718*/ + 0x80, /*0x1719*/ + 0x01, /*0x3210*/ + 0x02, /*0x111*/ + 0x01, /*0x3410*/ + 0x01, /*0x3098*/ + 0x05, /*0x309D*/ + 0x02, + 0x04, + }, + { /* Snapshot */ + 0x07,/*REG = 0x0202 coarse_integration_time_hi*/ + 0x00,/*REG = 0x0203 coarse_integration_time_lo*/ + 0xc0,/*REG = 0x0205 analogue_gain_code_global*/ + 0x07,/*REG = 0x0340 frame_length_lines_hi*/ + 0xd0,/*REG = 0x0341 frame_length_lines_lo*/ + 0x0b,/*REG = 0x0342 line_length_pck_hi*/ + 0x8c,/*REG = 0x0343 line_length_pck_lo*/ + 0x01,/*REG = 0x3005*/ + 0x00,/*REG = 0x3010*/ + 0x00,/*REG = 0x3011*/ + 0x55,/*REG = 0x301a*/ + 0x01,/*REG = 0x3035*/ + 0x23,/*REG = 0x3036*/ + 0x00,/*REG = 0x3041*/ + 0x24,/*REG = 0x3042*/ + 0xb7,/*REG = 0x3045*/ + 0x01,/*REG = 0x0b80 edof application*/ + 0x00,/*REG = 0x0900*/ + 0x00,/*REG = 0x0901*/ + 0x00,/*REG = 0x0902*/ + 0x01,/*REG = 0x0383*/ + 0x01,/*REG = 0x0387*/ + 0x0A,/*REG = 0x034c*/ + 0x30,/*REG = 0x034d*/ + 0x07,/*REG = 0x034e*/ + 0xA8,/*REG = 0x034f*/ + 0x02,/*0x1716*/ + 0x0d,/*0x1717*/ + 0x07,/*0x1718*/ + 0x7d,/*0x1719*/ + 0x01,/*0x3210*/ + 0x02,/*0x111*/ + 0x01,/*0x3410*/ + 0x01,/*0x3098*/ + 0x05, /*0x309D*/ + 0x02, + 0x00, + } +}; + + + +struct vx6953_reg vx6953_regs = { + .reg_pat_init = &vx6953_reg_init[0], + .reg_pat = &vx6953_reg_pat[0], +}; diff --git a/drivers/media/video/msm/vx6953_v4l2.c b/drivers/media/video/msm/vx6953_v4l2.c new file mode 100644 index 00000000000..2e5e39be672 --- /dev/null +++ b/drivers/media/video/msm/vx6953_v4l2.c @@ -0,0 +1,4149 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vx6953_v4l2.h" +#include "msm.h" + +#define V4L2_IDENT_VX6953 50000 + +/*============================================================= + SENSOR REGISTER DEFINES +==============================================================*/ + +#define REG_GROUPED_PARAMETER_HOLD 0x0104 +#define GROUPED_PARAMETER_HOLD_OFF 0x00 +#define GROUPED_PARAMETER_HOLD 0x01 +#define REG_MODE_SELECT 0x0100 +#define MODE_SELECT_STANDBY_MODE 0x00 +#define MODE_SELECT_STREAM 0x01 +/* Integration Time */ +#define REG_COARSE_INTEGRATION_TIME_HI 0x0202 +#define REG_COARSE_INTEGRATION_TIME_LO 0x0203 +/* Gain */ +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_HI 0x0204 +#define REG_ANALOGUE_GAIN_CODE_GLOBAL_LO 0x0205 +/* Digital Gain */ +#define REG_DIGITAL_GAIN_GREEN_R_HI 0x020E +#define REG_DIGITAL_GAIN_GREEN_R_LO 0x020F +#define REG_DIGITAL_GAIN_RED_HI 0x0210 +#define REG_DIGITAL_GAIN_RED_LO 0x0211 +#define REG_DIGITAL_GAIN_BLUE_HI 0x0212 +#define REG_DIGITAL_GAIN_BLUE_LO 0x0213 +#define REG_DIGITAL_GAIN_GREEN_B_HI 0x0214 +#define REG_DIGITAL_GAIN_GREEN_B_LO 0x0215 +/* output bits setting */ +#define REG_0x0112 0x0112 +#define REG_0x0113 0x0113 +/* PLL registers */ +#define REG_VT_PIX_CLK_DIV 0x0301 +#define REG_PRE_PLL_CLK_DIV 0x0305 +#define REG_PLL_MULTIPLIER 0x0307 +#define REG_OP_PIX_CLK_DIV 0x0309 +#define REG_0x034c 0x034c +#define REG_0x034d 0x034d +#define REG_0x034e 0x034e +#define REG_0x034f 0x034f +#define REG_0x0387 0x0387 +#define REG_0x0383 0x0383 +#define REG_FRAME_LENGTH_LINES_HI 0x0340 +#define REG_FRAME_LENGTH_LINES_LO 0x0341 +#define REG_LINE_LENGTH_PCK_HI 0x0342 +#define REG_LINE_LENGTH_PCK_LO 0x0343 +#define REG_0x3030 0x3030 +#define REG_0x0111 0x0111 +#define REG_0x0136 0x0136 +#define REG_0x0137 0x0137 +#define REG_0x0b00 0x0b00 +#define REG_0x3001 0x3001 +#define REG_0x3004 0x3004 +#define REG_0x3007 0x3007 +#define REG_0x301a 0x301a +#define REG_0x3101 0x3101 +#define REG_0x3364 0x3364 +#define REG_0x3365 0x3365 +#define REG_0x0b83 0x0b83 +#define REG_0x0b84 0x0b84 +#define REG_0x0b85 0x0b85 +#define REG_0x0b88 0x0b88 +#define REG_0x0b89 0x0b89 +#define REG_0x0b8a 0x0b8a +#define REG_0x3005 0x3005 +#define REG_0x3010 0x3010 +#define REG_0x3036 0x3036 +#define REG_0x3041 0x3041 +#define REG_0x0b80 0x0b80 +#define REG_0x0900 0x0900 +#define REG_0x0901 0x0901 +#define REG_0x0902 0x0902 +#define REG_0x3016 0x3016 +#define REG_0x301d 0x301d +#define REG_0x317e 0x317e +#define REG_0x317f 0x317f +#define REG_0x3400 0x3400 +#define REG_0x303a 0x303a +#define REG_0x1716 0x1716 +#define REG_0x1717 0x1717 +#define REG_0x1718 0x1718 +#define REG_0x1719 0x1719 +#define REG_0x3006 0x3006 +#define REG_0x301b 0x301b +#define REG_0x3098 0x3098 +#define REG_0x309d 0x309d +#define REG_0x3011 0x3011 +#define REG_0x3035 0x3035 +#define REG_0x3045 0x3045 +#define REG_0x3210 0x3210 +#define REG_0x0111 0x0111 +#define REG_0x3410 0x3410 +/* Test Pattern */ +#define REG_TEST_PATTERN_MODE 0x0601 + +/*============================================================================ + TYPE DECLARATIONS +============================================================================*/ + +/* 16bit address - 8 bit context register structure */ +#define VX6953_STM5M0EDOF_OFFSET 9 +#define Q8 0x00000100 +#define Q10 0x00000400 +#define VX6953_STM5M0EDOF_MAX_SNAPSHOT_EXPOSURE_LINE_COUNT 2922 +#define VX6953_STM5M0EDOF_DEFAULT_MASTER_CLK_RATE 24000000 +#define VX6953_STM5M0EDOF_OP_PIXEL_CLOCK_RATE 79800000 +#define VX6953_STM5M0EDOF_VT_PIXEL_CLOCK_RATE 88670000 +/* Full Size */ +#define VX6953_FULL_SIZE_WIDTH 2608 +#define VX6953_FULL_SIZE_HEIGHT 1960 +#define VX6953_FULL_SIZE_DUMMY_PIXELS 1 +#define VX6953_FULL_SIZE_DUMMY_LINES 0 +/* Quarter Size */ +#define VX6953_QTR_SIZE_WIDTH 1304 +#define VX6953_QTR_SIZE_HEIGHT 980 +#define VX6953_QTR_SIZE_DUMMY_PIXELS 1 +#define VX6953_QTR_SIZE_DUMMY_LINES 0 +/* Blanking as measured on the scope */ +/* Full Size */ +#define VX6953_HRZ_FULL_BLK_PIXELS 348 +#define VX6953_VER_FULL_BLK_LINES 40 +/* Quarter Size */ +#define VX6953_HRZ_QTR_BLK_PIXELS 1628 +#define VX6953_VER_QTR_BLK_LINES 28 +#define MAX_LINE_LENGTH_PCK 8190 +#define VX6953_REVISION_NUMBER_CUT2 0x10/*revision number for Cut2.0*/ +#define VX6953_REVISION_NUMBER_CUT3 0x20/*revision number for Cut3.0*/ +/* FIXME: Changes from here */ +struct vx6953_work_t { + struct work_struct work; +}; + +static struct vx6953_work_t *vx6953_sensorw; +static struct i2c_client *vx6953_client; + +struct vx6953_ctrl_t { + const struct msm_camera_sensor_info *sensordata; + + uint32_t sensormode; + uint32_t fps_divider; /* init to 1 * 0x00000400 */ + uint32_t pict_fps_divider; /* init to 1 * 0x00000400 */ + uint16_t fps; + + int16_t curr_lens_pos; + uint16_t curr_step_pos; + uint16_t my_reg_gain; + uint32_t my_reg_line_count; + uint16_t total_lines_per_frame; + + enum vx6953_resolution_t prev_res; + enum vx6953_resolution_t pict_res; + enum vx6953_resolution_t curr_res; + enum vx6953_test_mode_t set_test; + enum sensor_revision_t sensor_type; + + enum edof_mode_t edof_mode; + + unsigned short imgaddr; + + struct v4l2_subdev *sensor_dev; + struct vx6953_format *fmt; +}; + + +static uint8_t vx6953_stm5m0edof_delay_msecs_stdby; +static uint16_t vx6953_stm5m0edof_delay_msecs_stream = 20; + +static struct vx6953_ctrl_t *vx6953_ctrl; +static DECLARE_WAIT_QUEUE_HEAD(vx6953_wait_queue); +DEFINE_MUTEX(vx6953_mut); +static struct vx6953_i2c_reg_conf patch_tbl_cut2[] = { + {0xFB94, 0}, /*intialise Data Xfer Status reg*/ + {0xFB95, 0}, /*gain 1 (0x00)*/ + {0xFB96, 0}, /*gain 1.07 (0x10)*/ + {0xFB97, 0}, /*gain 1.14 (0x20)*/ + {0xFB98, 0}, /*gain 1.23 (0x30)*/ + {0xFB99, 0}, /*gain 1.33 (0x40)*/ + {0xFB9A, 0}, /*gain 1.45 (0x50)*/ + {0xFB9B, 0}, /*gain 1.6 (0x60)*/ + {0xFB9C, 0}, /*gain 1.78 (0x70)*/ + {0xFB9D, 2}, /*gain 2 (0x80)*/ + {0xFB9E, 2}, /*gain 2.29 (0x90)*/ + {0xFB9F, 3}, /*gain 2.67 (0xA0)*/ + {0xFBA0, 3}, /*gain 3.2 (0xB0)*/ + {0xFBA1, 4}, /*gain 4 (0xC0)*/ + {0xFBA2, 7}, /*gain 5.33 (0xD0)*/ + {0xFBA3, 10}, /*gain 8 (0xE0)*/ + {0xFBA4, 11}, /*gain 9.14 (0xE4)*/ + {0xFBA5, 13}, /*gain 10.67 (0xE8)*/ + {0xFBA6, 15}, /*gain 12.8 (0xEC)*/ + {0xFBA7, 19}, /*gain 16 (0xF0)*/ + {0xF800, 0x12}, + {0xF801, 0x06}, + {0xF802, 0xf7}, + {0xF803, 0x90}, + {0xF804, 0x02}, + {0xF805, 0x05}, + {0xF806, 0xe0}, + {0xF807, 0xff}, + {0xF808, 0x65}, + {0xF809, 0x7d}, + {0xF80A, 0x70}, + {0xF80B, 0x03}, + {0xF80C, 0x02}, + {0xF80D, 0xf9}, + {0xF80E, 0x1c}, + {0xF80F, 0x8f}, + {0xF810, 0x7d}, + {0xF811, 0xe4}, + {0xF812, 0xf5}, + {0xF813, 0x7a}, + {0xF814, 0x75}, + {0xF815, 0x78}, + {0xF816, 0x30}, + {0xF817, 0x75}, + {0xF818, 0x79}, + {0xF819, 0x53}, + {0xF81A, 0x85}, + {0xF81B, 0x79}, + {0xF81C, 0x82}, + {0xF81D, 0x85}, + {0xF81E, 0x78}, + {0xF81F, 0x83}, + {0xF820, 0xe0}, + {0xF821, 0xc3}, + {0xF822, 0x95}, + {0xF823, 0x7b}, + {0xF824, 0xf0}, + {0xF825, 0x74}, + {0xF826, 0x02}, + {0xF827, 0x25}, + {0xF828, 0x79}, + {0xF829, 0xf5}, + {0xF82A, 0x79}, + {0xF82B, 0xe4}, + {0xF82C, 0x35}, + {0xF82D, 0x78}, + {0xF82E, 0xf5}, + {0xF82F, 0x78}, + {0xF830, 0x05}, + {0xF831, 0x7a}, + {0xF832, 0xe5}, + {0xF833, 0x7a}, + {0xF834, 0xb4}, + {0xF835, 0x08}, + {0xF836, 0xe3}, + {0xF837, 0xe5}, + {0xF838, 0x7d}, + {0xF839, 0x70}, + {0xF83A, 0x04}, + {0xF83B, 0xff}, + {0xF83C, 0x02}, + {0xF83D, 0xf8}, + {0xF83E, 0xe4}, + {0xF83F, 0xe5}, + {0xF840, 0x7d}, + {0xF841, 0xb4}, + {0xF842, 0x10}, + {0xF843, 0x05}, + {0xF844, 0x7f}, + {0xF845, 0x01}, + {0xF846, 0x02}, + {0xF847, 0xf8}, + {0xF848, 0xe4}, + {0xF849, 0xe5}, + {0xF84A, 0x7d}, + {0xF84B, 0xb4}, + {0xF84C, 0x20}, + {0xF84D, 0x05}, + {0xF84E, 0x7f}, + {0xF84F, 0x02}, + {0xF850, 0x02}, + {0xF851, 0xf8}, + {0xF852, 0xe4}, + {0xF853, 0xe5}, + {0xF854, 0x7d}, + {0xF855, 0xb4}, + {0xF856, 0x30}, + {0xF857, 0x05}, + {0xF858, 0x7f}, + {0xF859, 0x03}, + {0xF85A, 0x02}, + {0xF85B, 0xf8}, + {0xF85C, 0xe4}, + {0xF85D, 0xe5}, + {0xF85E, 0x7d}, + {0xF85F, 0xb4}, + {0xF860, 0x40}, + {0xF861, 0x04}, + {0xF862, 0x7f}, + {0xF863, 0x04}, + {0xF864, 0x80}, + {0xF865, 0x7e}, + {0xF866, 0xe5}, + {0xF867, 0x7d}, + {0xF868, 0xb4}, + {0xF869, 0x50}, + {0xF86A, 0x04}, + {0xF86B, 0x7f}, + {0xF86C, 0x05}, + {0xF86D, 0x80}, + {0xF86E, 0x75}, + {0xF86F, 0xe5}, + {0xF870, 0x7d}, + {0xF871, 0xb4}, + {0xF872, 0x60}, + {0xF873, 0x04}, + {0xF874, 0x7f}, + {0xF875, 0x06}, + {0xF876, 0x80}, + {0xF877, 0x6c}, + {0xF878, 0xe5}, + {0xF879, 0x7d}, + {0xF87A, 0xb4}, + {0xF87B, 0x70}, + {0xF87C, 0x04}, + {0xF87D, 0x7f}, + {0xF87E, 0x07}, + {0xF87F, 0x80}, + {0xF880, 0x63}, + {0xF881, 0xe5}, + {0xF882, 0x7d}, + {0xF883, 0xb4}, + {0xF884, 0x80}, + {0xF885, 0x04}, + {0xF886, 0x7f}, + {0xF887, 0x08}, + {0xF888, 0x80}, + {0xF889, 0x5a}, + {0xF88A, 0xe5}, + {0xF88B, 0x7d}, + {0xF88C, 0xb4}, + {0xF88D, 0x90}, + {0xF88E, 0x04}, + {0xF88F, 0x7f}, + {0xF890, 0x09}, + {0xF891, 0x80}, + {0xF892, 0x51}, + {0xF893, 0xe5}, + {0xF894, 0x7d}, + {0xF895, 0xb4}, + {0xF896, 0xa0}, + {0xF897, 0x04}, + {0xF898, 0x7f}, + {0xF899, 0x0a}, + {0xF89A, 0x80}, + {0xF89B, 0x48}, + {0xF89C, 0xe5}, + {0xF89D, 0x7d}, + {0xF89E, 0xb4}, + {0xF89F, 0xb0}, + {0xF8A0, 0x04}, + {0xF8A1, 0x7f}, + {0xF8A2, 0x0b}, + {0xF8A3, 0x80}, + {0xF8A4, 0x3f}, + {0xF8A5, 0xe5}, + {0xF8A6, 0x7d}, + {0xF8A7, 0xb4}, + {0xF8A8, 0xc0}, + {0xF8A9, 0x04}, + {0xF8AA, 0x7f}, + {0xF8AB, 0x0c}, + {0xF8AC, 0x80}, + {0xF8AD, 0x36}, + {0xF8AE, 0xe5}, + {0xF8AF, 0x7d}, + {0xF8B0, 0xb4}, + {0xF8B1, 0xd0}, + {0xF8B2, 0x04}, + {0xF8B3, 0x7f}, + {0xF8B4, 0x0d}, + {0xF8B5, 0x80}, + {0xF8B6, 0x2d}, + {0xF8B7, 0xe5}, + {0xF8B8, 0x7d}, + {0xF8B9, 0xb4}, + {0xF8BA, 0xe0}, + {0xF8BB, 0x04}, + {0xF8BC, 0x7f}, + {0xF8BD, 0x0e}, + {0xF8BE, 0x80}, + {0xF8BF, 0x24}, + {0xF8C0, 0xe5}, + {0xF8C1, 0x7d}, + {0xF8C2, 0xb4}, + {0xF8C3, 0xe4}, + {0xF8C4, 0x04}, + {0xF8C5, 0x7f}, + {0xF8C6, 0x0f}, + {0xF8C7, 0x80}, + {0xF8C8, 0x1b}, + {0xF8C9, 0xe5}, + {0xF8CA, 0x7d}, + {0xF8CB, 0xb4}, + {0xF8CC, 0xe8}, + {0xF8CD, 0x04}, + {0xF8CE, 0x7f}, + {0xF8CF, 0x10}, + {0xF8D0, 0x80}, + {0xF8D1, 0x12}, + {0xF8D2, 0xe5}, + {0xF8D3, 0x7d}, + {0xF8D4, 0xb4}, + {0xF8D5, 0xec}, + {0xF8D6, 0x04}, + {0xF8D7, 0x7f}, + {0xF8D8, 0x11}, + {0xF8D9, 0x80}, + {0xF8DA, 0x09}, + {0xF8DB, 0xe5}, + {0xF8DC, 0x7d}, + {0xF8DD, 0x7f}, + {0xF8DE, 0x00}, + {0xF8DF, 0xb4}, + {0xF8E0, 0xf0}, + {0xF8E1, 0x02}, + {0xF8E2, 0x7f}, + {0xF8E3, 0x12}, + {0xF8E4, 0x8f}, + {0xF8E5, 0x7c}, + {0xF8E6, 0xef}, + {0xF8E7, 0x24}, + {0xF8E8, 0x95}, + {0xF8E9, 0xff}, + {0xF8EA, 0xe4}, + {0xF8EB, 0x34}, + {0xF8EC, 0xfb}, + {0xF8ED, 0x8f}, + {0xF8EE, 0x82}, + {0xF8EF, 0xf5}, + {0xF8F0, 0x83}, + {0xF8F1, 0xe4}, + {0xF8F2, 0x93}, + {0xF8F3, 0xf5}, + {0xF8F4, 0x7c}, + {0xF8F5, 0xf5}, + {0xF8F6, 0x7b}, + {0xF8F7, 0xe4}, + {0xF8F8, 0xf5}, + {0xF8F9, 0x7a}, + {0xF8FA, 0x75}, + {0xF8FB, 0x78}, + {0xF8FC, 0x30}, + {0xF8FD, 0x75}, + {0xF8FE, 0x79}, + {0xF8FF, 0x53}, + {0xF900, 0x85}, + {0xF901, 0x79}, + {0xF902, 0x82}, + {0xF903, 0x85}, + {0xF904, 0x78}, + {0xF905, 0x83}, + {0xF906, 0xe0}, + {0xF907, 0x25}, + {0xF908, 0x7c}, + {0xF909, 0xf0}, + {0xF90A, 0x74}, + {0xF90B, 0x02}, + {0xF90C, 0x25}, + {0xF90D, 0x79}, + {0xF90E, 0xf5}, + {0xF90F, 0x79}, + {0xF910, 0xe4}, + {0xF911, 0x35}, + {0xF912, 0x78}, + {0xF913, 0xf5}, + {0xF914, 0x78}, + {0xF915, 0x05}, + {0xF916, 0x7a}, + {0xF917, 0xe5}, + {0xF918, 0x7a}, + {0xF919, 0xb4}, + {0xF91A, 0x08}, + {0xF91B, 0xe4}, + {0xF91C, 0x02}, + {0xF91D, 0x18}, + {0xF91E, 0x32}, + {0xF91F, 0x22}, + {0xF920, 0xf0}, + {0xF921, 0x90}, + {0xF922, 0xa0}, + {0xF923, 0xf8}, + {0xF924, 0xe0}, + {0xF925, 0x70}, + {0xF926, 0x02}, + {0xF927, 0xa3}, + {0xF928, 0xe0}, + {0xF929, 0x70}, + {0xF92A, 0x0a}, + {0xF92B, 0x90}, + {0xF92C, 0xa1}, + {0xF92D, 0x10}, + {0xF92E, 0xe0}, + {0xF92F, 0xfe}, + {0xF930, 0xa3}, + {0xF931, 0xe0}, + {0xF932, 0xff}, + {0xF933, 0x80}, + {0xF934, 0x04}, + {0xF935, 0x7e}, + {0xF936, 0x00}, + {0xF937, 0x7f}, + {0xF938, 0x00}, + {0xF939, 0x8e}, + {0xF93A, 0x7e}, + {0xF93B, 0x8f}, + {0xF93C, 0x7f}, + {0xF93D, 0x90}, + {0xF93E, 0x36}, + {0xF93F, 0x0d}, + {0xF940, 0xe0}, + {0xF941, 0x44}, + {0xF942, 0x02}, + {0xF943, 0xf0}, + {0xF944, 0x90}, + {0xF945, 0x36}, + {0xF946, 0x0e}, + {0xF947, 0xe5}, + {0xF948, 0x7e}, + {0xF949, 0xf0}, + {0xF94A, 0xa3}, + {0xF94B, 0xe5}, + {0xF94C, 0x7f}, + {0xF94D, 0xf0}, + {0xF94E, 0xe5}, + {0xF94F, 0x3a}, + {0xF950, 0x60}, + {0xF951, 0x0c}, + {0xF952, 0x90}, + {0xF953, 0x36}, + {0xF954, 0x09}, + {0xF955, 0xe0}, + {0xF956, 0x70}, + {0xF957, 0x06}, + {0xF958, 0x90}, + {0xF959, 0x36}, + {0xF95A, 0x08}, + {0xF95B, 0xf0}, + {0xF95C, 0xf5}, + {0xF95D, 0x3a}, + {0xF95E, 0x02}, + {0xF95F, 0x03}, + {0xF960, 0x94}, + {0xF961, 0x22}, + {0xF962, 0x78}, + {0xF963, 0x07}, + {0xF964, 0xe6}, + {0xF965, 0xd3}, + {0xF966, 0x94}, + {0xF967, 0x00}, + {0xF968, 0x40}, + {0xF969, 0x16}, + {0xF96A, 0x16}, + {0xF96B, 0xe6}, + {0xF96C, 0x90}, + {0xF96D, 0x30}, + {0xF96E, 0xa1}, + {0xF96F, 0xf0}, + {0xF970, 0x90}, + {0xF971, 0x43}, + {0xF972, 0x83}, + {0xF973, 0xe0}, + {0xF974, 0xb4}, + {0xF975, 0x01}, + {0xF976, 0x0f}, + {0xF977, 0x90}, + {0xF978, 0x43}, + {0xF979, 0x87}, + {0xF97A, 0xe0}, + {0xF97B, 0xb4}, + {0xF97C, 0x01}, + {0xF97D, 0x08}, + {0xF97E, 0x80}, + {0xF97F, 0x00}, + {0xF980, 0x90}, + {0xF981, 0x30}, + {0xF982, 0xa0}, + {0xF983, 0x74}, + {0xF984, 0x01}, + {0xF985, 0xf0}, + {0xF986, 0x22}, + {0xF987, 0xf0}, + {0xF988, 0x90}, + {0xF989, 0x35}, + {0xF98A, 0xba}, + {0xF98B, 0xe0}, + {0xF98C, 0xb4}, + {0xF98D, 0x0a}, + {0xF98E, 0x0d}, + {0xF98F, 0xa3}, + {0xF990, 0xe0}, + {0xF991, 0xb4}, + {0xF992, 0x01}, + {0xF993, 0x08}, + {0xF994, 0x90}, + {0xF995, 0xfb}, + {0xF996, 0x94}, + {0xF997, 0xe0}, + {0xF998, 0x90}, + {0xF999, 0x35}, + {0xF99A, 0xb8}, + {0xF99B, 0xf0}, + {0xF99C, 0xd0}, + {0xF99D, 0xd0}, + {0xF99E, 0xd0}, + {0xF99F, 0x82}, + {0xF9A0, 0xd0}, + {0xF9A1, 0x83}, + {0xF9A2, 0xd0}, + {0xF9A3, 0xe0}, + {0xF9A4, 0x32}, + {0xF9A5, 0x22}, + {0xF9A6, 0xe5}, + {0xF9A7, 0x7f}, + {0xF9A8, 0x45}, + {0xF9A9, 0x7e}, + {0xF9AA, 0x60}, + {0xF9AB, 0x15}, + {0xF9AC, 0x90}, + {0xF9AD, 0x01}, + {0xF9AE, 0x00}, + {0xF9AF, 0xe0}, + {0xF9B0, 0x70}, + {0xF9B1, 0x0f}, + {0xF9B2, 0x90}, + {0xF9B3, 0xa0}, + {0xF9B4, 0xf8}, + {0xF9B5, 0xe5}, + {0xF9B6, 0x7e}, + {0xF9B7, 0xf0}, + {0xF9B8, 0xa3}, + {0xF9B9, 0xe5}, + {0xF9BA, 0x7f}, + {0xF9BB, 0xf0}, + {0xF9BC, 0xe4}, + {0xF9BD, 0xf5}, + {0xF9BE, 0x7e}, + {0xF9BF, 0xf5}, + {0xF9C0, 0x7f}, + {0xF9C1, 0x22}, + {0xF9C2, 0x02}, + {0xF9C3, 0x0e}, + {0xF9C4, 0x79}, + {0xF9C5, 0x22}, + /* Offsets:*/ + {0x35C6, 0x00},/* FIDDLEDARKCAL*/ + {0x35C7, 0x00}, + {0x35C8, 0x01},/*STOREDISTANCEATSTOPSTREAMING*/ + {0x35C9, 0x20}, + {0x35CA, 0x01},/*BRUCEFIX*/ + {0x35CB, 0x62}, + {0x35CC, 0x01},/*FIXDATAXFERSTATUSREG*/ + {0x35CD, 0x87}, + {0x35CE, 0x01},/*FOCUSDISTANCEUPDATE*/ + {0x35CF, 0xA6}, + {0x35D0, 0x01},/*SKIPEDOFRESET*/ + {0x35D1, 0xC2}, + {0x35D2, 0x00}, + {0x35D3, 0xFB}, + {0x35D4, 0x00}, + {0x35D5, 0x94}, + {0x35D6, 0x00}, + {0x35D7, 0xFB}, + {0x35D8, 0x00}, + {0x35D9, 0x94}, + {0x35DA, 0x00}, + {0x35DB, 0xFB}, + {0x35DC, 0x00}, + {0x35DD, 0x94}, + {0x35DE, 0x00}, + {0x35DF, 0xFB}, + {0x35E0, 0x00}, + {0x35E1, 0x94}, + {0x35E6, 0x18},/* FIDDLEDARKCAL*/ + {0x35E7, 0x2F}, + {0x35E8, 0x03},/* STOREDISTANCEATSTOPSTREAMING*/ + {0x35E9, 0x93}, + {0x35EA, 0x18},/* BRUCEFIX*/ + {0x35EB, 0x99}, + {0x35EC, 0x00},/* FIXDATAXFERSTATUSREG*/ + {0x35ED, 0xA3}, + {0x35EE, 0x21},/* FOCUSDISTANCEUPDATE*/ + {0x35EF, 0x5B}, + {0x35F0, 0x0E},/* SKIPEDOFRESET*/ + {0x35F1, 0x74}, + {0x35F2, 0x04}, + {0x35F3, 0x64}, + {0x35F4, 0x04}, + {0x35F5, 0x65}, + {0x35F6, 0x04}, + {0x35F7, 0x7B}, + {0x35F8, 0x04}, + {0x35F9, 0x7C}, + {0x35FA, 0x04}, + {0x35FB, 0xDD}, + {0x35FC, 0x04}, + {0x35FD, 0xDE}, + {0x35FE, 0x04}, + {0x35FF, 0xEF}, + {0x3600, 0x04}, + {0x3601, 0xF0}, + /*Jump/Data:*/ + {0x35C2, 0x3F},/* Jump Reg*/ + {0x35C3, 0xFF},/* Jump Reg*/ + {0x35C4, 0x3F},/* Data Reg*/ + {0x35C5, 0xC0},/* Data Reg*/ + {0x35C0, 0x01},/* Enable*/ + +}; + +static struct vx6953_i2c_reg_conf edof_tbl[] = { + {0xa098, 0x02}, + {0xa099, 0x87}, + {0xa09c, 0x00}, + {0xa09d, 0xc5}, + {0xa4ec, 0x05}, + {0xa4ed, 0x05}, + {0xa4f0, 0x04}, + {0xa4f1, 0x04}, + {0xa4f4, 0x04}, + {0xa4f5, 0x05}, + {0xa4f8, 0x05}, + {0xa4f9, 0x07}, + {0xa4fc, 0x07}, + {0xa4fd, 0x07}, + {0xa500, 0x07}, + {0xa501, 0x07}, + {0xa504, 0x08}, + {0xa505, 0x08}, + {0xa518, 0x01}, + {0xa519, 0x02}, + {0xa51c, 0x01}, + {0xa51d, 0x00}, + {0xa534, 0x00}, + {0xa535, 0x04}, + {0xa538, 0x04}, + {0xa539, 0x03}, + {0xa53c, 0x05}, + {0xa53d, 0x07}, + {0xa540, 0x07}, + {0xa541, 0x06}, + {0xa544, 0x07}, + {0xa545, 0x06}, + {0xa548, 0x05}, + {0xa549, 0x06}, + {0xa54c, 0x06}, + {0xa54d, 0x07}, + {0xa550, 0x07}, + {0xa551, 0x04}, + {0xa554, 0x04}, + {0xa555, 0x04}, + {0xa558, 0x05}, + {0xa559, 0x06}, + {0xa55c, 0x07}, + {0xa55d, 0x07}, + {0xa56c, 0x00}, + {0xa56d, 0x0a}, + {0xa570, 0x08}, + {0xa571, 0x05}, + {0xa574, 0x04}, + {0xa575, 0x03}, + {0xa578, 0x04}, + {0xa579, 0x04}, + {0xa58c, 0x1f}, + {0xa58d, 0x1b}, + {0xa590, 0x17}, + {0xa591, 0x13}, + {0xa594, 0x10}, + {0xa595, 0x0d}, + {0xa598, 0x0f}, + {0xa599, 0x11}, + {0xa59c, 0x03}, + {0xa59d, 0x03}, + {0xa5a0, 0x03}, + {0xa5a1, 0x03}, + {0xa5a4, 0x03}, + {0xa5a5, 0x04}, + {0xa5a8, 0x05}, + {0xa5a9, 0x00}, + {0xa5ac, 0x00}, + {0xa5ad, 0x00}, + {0xa5b0, 0x00}, + {0xa5b1, 0x00}, + {0xa5b4, 0x00}, + {0xa5b5, 0x00}, + {0xa5c4, 0x1f}, + {0xa5c5, 0x13}, + {0xa5c8, 0x14}, + {0xa5c9, 0x14}, + {0xa5cc, 0x14}, + {0xa5cd, 0x13}, + {0xa5d0, 0x17}, + {0xa5d1, 0x1a}, + {0xa5f4, 0x05}, + {0xa5f5, 0x05}, + {0xa5f8, 0x05}, + {0xa5f9, 0x06}, + {0xa5fc, 0x06}, + {0xa5fd, 0x06}, + {0xa600, 0x06}, + {0xa601, 0x06}, + {0xa608, 0x07}, + {0xa609, 0x08}, + {0xa60c, 0x08}, + {0xa60d, 0x07}, + {0xa63c, 0x00}, + {0xa63d, 0x02}, + {0xa640, 0x02}, + {0xa641, 0x02}, + {0xa644, 0x02}, + {0xa645, 0x02}, + {0xa648, 0x03}, + {0xa649, 0x04}, + {0xa64c, 0x0a}, + {0xa64d, 0x09}, + {0xa650, 0x08}, + {0xa651, 0x09}, + {0xa654, 0x09}, + {0xa655, 0x0a}, + {0xa658, 0x0a}, + {0xa659, 0x0a}, + {0xa65c, 0x0a}, + {0xa65d, 0x09}, + {0xa660, 0x09}, + {0xa661, 0x09}, + {0xa664, 0x09}, + {0xa665, 0x08}, + {0xa680, 0x01}, + {0xa681, 0x02}, + {0xa694, 0x1f}, + {0xa695, 0x10}, + {0xa698, 0x0e}, + {0xa699, 0x0c}, + {0xa69c, 0x0d}, + {0xa69d, 0x0d}, + {0xa6a0, 0x0f}, + {0xa6a1, 0x11}, + {0xa6a4, 0x00}, + {0xa6a5, 0x00}, + {0xa6a8, 0x00}, + {0xa6a9, 0x00}, + {0xa6ac, 0x00}, + {0xa6ad, 0x00}, + {0xa6b0, 0x00}, + {0xa6b1, 0x04}, + {0xa6b4, 0x04}, + {0xa6b5, 0x04}, + {0xa6b8, 0x04}, + {0xa6b9, 0x04}, + {0xa6bc, 0x05}, + {0xa6bd, 0x05}, + {0xa6c0, 0x1f}, + {0xa6c1, 0x1f}, + {0xa6c4, 0x1f}, + {0xa6c5, 0x1f}, + {0xa6c8, 0x1f}, + {0xa6c9, 0x1f}, + {0xa6cc, 0x1f}, + {0xa6cd, 0x0b}, + {0xa6d0, 0x0c}, + {0xa6d1, 0x0d}, + {0xa6d4, 0x0d}, + {0xa6d5, 0x0d}, + {0xa6d8, 0x11}, + {0xa6d9, 0x14}, + {0xa6fc, 0x02}, + {0xa6fd, 0x03}, + {0xa700, 0x03}, + {0xa701, 0x03}, + {0xa704, 0x03}, + {0xa705, 0x04}, + {0xa708, 0x05}, + {0xa709, 0x02}, + {0xa70c, 0x02}, + {0xa70d, 0x02}, + {0xa710, 0x03}, + {0xa711, 0x04}, + {0xa714, 0x04}, + {0xa715, 0x04}, + {0xa744, 0x00}, + {0xa745, 0x03}, + {0xa748, 0x04}, + {0xa749, 0x04}, + {0xa74c, 0x05}, + {0xa74d, 0x06}, + {0xa750, 0x07}, + {0xa751, 0x07}, + {0xa754, 0x05}, + {0xa755, 0x05}, + {0xa758, 0x05}, + {0xa759, 0x05}, + {0xa75c, 0x05}, + {0xa75d, 0x06}, + {0xa760, 0x07}, + {0xa761, 0x07}, + {0xa764, 0x06}, + {0xa765, 0x05}, + {0xa768, 0x05}, + {0xa769, 0x05}, + {0xa76c, 0x06}, + {0xa76d, 0x07}, + {0xa77c, 0x00}, + {0xa77d, 0x05}, + {0xa780, 0x05}, + {0xa781, 0x05}, + {0xa784, 0x05}, + {0xa785, 0x04}, + {0xa788, 0x05}, + {0xa789, 0x06}, + {0xa79c, 0x1f}, + {0xa79d, 0x15}, + {0xa7a0, 0x13}, + {0xa7a1, 0x10}, + {0xa7a4, 0x0f}, + {0xa7a5, 0x0d}, + {0xa7a8, 0x11}, + {0xa7a9, 0x14}, + {0xa7ac, 0x02}, + {0xa7ad, 0x02}, + {0xa7b0, 0x02}, + {0xa7b1, 0x02}, + {0xa7b4, 0x02}, + {0xa7b5, 0x03}, + {0xa7b8, 0x03}, + {0xa7b9, 0x00}, + {0xa7bc, 0x00}, + {0xa7bd, 0x00}, + {0xa7c0, 0x00}, + {0xa7c1, 0x00}, + {0xa7c4, 0x00}, + {0xa7c5, 0x00}, + {0xa7d4, 0x1f}, + {0xa7d5, 0x0d}, + {0xa7d8, 0x0f}, + {0xa7d9, 0x10}, + {0xa7dc, 0x10}, + {0xa7dd, 0x10}, + {0xa7e0, 0x13}, + {0xa7e1, 0x16}, + {0xa7f4, 0x00}, + {0xa7f5, 0x03}, + {0xa7f8, 0x04}, + {0xa7f9, 0x04}, + {0xa7fc, 0x04}, + {0xa7fd, 0x03}, + {0xa800, 0x03}, + {0xa801, 0x03}, + {0xa804, 0x03}, + {0xa805, 0x03}, + {0xa808, 0x03}, + {0xa809, 0x03}, + {0xa80c, 0x03}, + {0xa80d, 0x04}, + {0xa810, 0x04}, + {0xa811, 0x0a}, + {0xa814, 0x0a}, + {0xa815, 0x0a}, + {0xa818, 0x0f}, + {0xa819, 0x14}, + {0xa81c, 0x14}, + {0xa81d, 0x14}, + {0xa82c, 0x00}, + {0xa82d, 0x04}, + {0xa830, 0x02}, + {0xa831, 0x00}, + {0xa834, 0x00}, + {0xa835, 0x00}, + {0xa838, 0x00}, + {0xa839, 0x00}, + {0xa840, 0x1f}, + {0xa841, 0x1f}, + {0xa848, 0x1f}, + {0xa849, 0x1f}, + {0xa84c, 0x1f}, + {0xa84d, 0x0c}, + {0xa850, 0x0c}, + {0xa851, 0x0c}, + {0xa854, 0x0c}, + {0xa855, 0x0c}, + {0xa858, 0x0c}, + {0xa859, 0x0c}, + {0xa85c, 0x0c}, + {0xa85d, 0x0c}, + {0xa860, 0x0c}, + {0xa861, 0x0c}, + {0xa864, 0x0c}, + {0xa865, 0x0c}, + {0xa868, 0x0c}, + {0xa869, 0x0c}, + {0xa86c, 0x0c}, + {0xa86d, 0x0c}, + {0xa870, 0x0c}, + {0xa871, 0x0c}, + {0xa874, 0x0c}, + {0xa875, 0x0c}, + {0xa878, 0x1f}, + {0xa879, 0x1f}, + {0xa87c, 0x1f}, + {0xa87d, 0x1f}, + {0xa880, 0x1f}, + {0xa881, 0x1f}, + {0xa884, 0x1f}, + {0xa885, 0x0c}, + {0xa888, 0x0c}, + {0xa889, 0x0c}, + {0xa88c, 0x0c}, + {0xa88d, 0x0c}, + {0xa890, 0x0c}, + {0xa891, 0x0c}, + {0xa898, 0x1f}, + {0xa899, 0x1f}, + {0xa8a0, 0x1f}, + {0xa8a1, 0x1f}, + {0xa8a4, 0x1f}, + {0xa8a5, 0x0c}, + {0xa8a8, 0x0c}, + {0xa8a9, 0x0c}, + {0xa8ac, 0x0c}, + {0xa8ad, 0x0c}, + {0xa8b0, 0x0c}, + {0xa8b1, 0x0c}, + {0xa8b4, 0x0c}, + {0xa8b5, 0x0c}, + {0xa8b8, 0x0c}, + {0xa8b9, 0x0c}, + {0xa8bc, 0x0c}, + {0xa8bd, 0x0c}, + {0xa8c0, 0x0c}, + {0xa8c1, 0x0c}, + {0xa8c4, 0x0c}, + {0xa8c5, 0x0c}, + {0xa8c8, 0x0c}, + {0xa8c9, 0x0c}, + {0xa8cc, 0x0c}, + {0xa8cd, 0x0c}, + {0xa8d0, 0x1f}, + {0xa8d1, 0x1f}, + {0xa8d4, 0x1f}, + {0xa8d5, 0x1f}, + {0xa8d8, 0x1f}, + {0xa8d9, 0x1f}, + {0xa8dc, 0x1f}, + {0xa8dd, 0x0c}, + {0xa8e0, 0x0c}, + {0xa8e1, 0x0c}, + {0xa8e4, 0x0c}, + {0xa8e5, 0x0c}, + {0xa8e8, 0x0c}, + {0xa8e9, 0x0c}, + {0xa8f0, 0x1f}, + {0xa8f1, 0x1f}, + {0xa8f8, 0x1f}, + {0xa8f9, 0x1f}, + {0xa8fc, 0x1f}, + {0xa8fd, 0x0c}, + {0xa900, 0x0c}, + {0xa901, 0x0c}, + {0xa904, 0x0c}, + {0xa905, 0x0c}, + {0xa908, 0x0c}, + {0xa909, 0x0c}, + {0xa90c, 0x0c}, + {0xa90d, 0x0c}, + {0xa910, 0x0c}, + {0xa911, 0x0c}, + {0xa914, 0x0c}, + {0xa915, 0x0c}, + {0xa918, 0x0c}, + {0xa919, 0x0c}, + {0xa91c, 0x0c}, + {0xa91d, 0x0c}, + {0xa920, 0x0c}, + {0xa921, 0x0c}, + {0xa924, 0x0c}, + {0xa925, 0x0c}, + {0xa928, 0x1f}, + {0xa929, 0x1f}, + {0xa92c, 0x1f}, + {0xa92d, 0x1f}, + {0xa930, 0x1f}, + {0xa931, 0x1f}, + {0xa934, 0x1f}, + {0xa935, 0x0c}, + {0xa938, 0x0c}, + {0xa939, 0x0c}, + {0xa93c, 0x0c}, + {0xa93d, 0x0c}, + {0xa940, 0x0c}, + {0xa941, 0x0c}, + {0xa96c, 0x0d}, + {0xa96d, 0x16}, + {0xa970, 0x19}, + {0xa971, 0x0e}, + {0xa974, 0x16}, + {0xa975, 0x1a}, + {0xa978, 0x0d}, + {0xa979, 0x15}, + {0xa97c, 0x19}, + {0xa97d, 0x0d}, + {0xa980, 0x15}, + {0xa981, 0x1a}, + {0xa984, 0x0d}, + {0xa985, 0x15}, + {0xa988, 0x1a}, + {0xa989, 0x0d}, + {0xa98c, 0x15}, + {0xa98d, 0x1a}, + {0xa990, 0x0b}, + {0xa991, 0x11}, + {0xa994, 0x02}, + {0xa995, 0x0e}, + {0xa998, 0x16}, + {0xa999, 0x02}, + {0xa99c, 0x0c}, + {0xa99d, 0x13}, + {0xa9a0, 0x02}, + {0xa9a1, 0x0c}, + {0xa9a4, 0x12}, + {0xa9a5, 0x02}, + {0xa9a8, 0x0c}, + {0xa9a9, 0x12}, + {0xa9ac, 0x02}, + {0xa9ad, 0x0c}, + {0xa9b0, 0x12}, + {0xa9b1, 0x02}, + {0xa9b4, 0x10}, + {0xa9b5, 0x1e}, + {0xa9b8, 0x0f}, + {0xa9b9, 0x13}, + {0xa9bc, 0x20}, + {0xa9bd, 0x10}, + {0xa9c0, 0x11}, + {0xa9c1, 0x1e}, + {0xa9c4, 0x10}, + {0xa9c5, 0x11}, + {0xa9c8, 0x1e}, + {0xa9c9, 0x10}, + {0xa9cc, 0x11}, + {0xa9cd, 0x20}, + {0xa9d0, 0x10}, + {0xa9d1, 0x13}, + {0xa9d4, 0x24}, + {0xa9d5, 0x10}, + {0xa9f0, 0x02}, + {0xa9f1, 0x01}, + {0xa9f8, 0x19}, + {0xa9f9, 0x0b}, + {0xa9fc, 0x0a}, + {0xa9fd, 0x07}, + {0xaa00, 0x0c}, + {0xaa01, 0x0e}, + {0xaa08, 0x0c}, + {0xaa09, 0x06}, + {0xaa0c, 0x0c}, + {0xaa0d, 0x0a}, + {0xaa24, 0x10}, + {0xaa25, 0x12}, + {0xaa28, 0x0b}, + {0xaa29, 0x07}, + {0xaa2c, 0x10}, + {0xaa2d, 0x14}, + {0xaa34, 0x0e}, + {0xaa35, 0x0e}, + {0xaa38, 0x07}, + {0xaa39, 0x07}, + {0xaa3c, 0x0e}, + {0xaa3d, 0x0c}, + {0xaa48, 0x09}, + {0xaa49, 0x0c}, + {0xaa4c, 0x0c}, + {0xaa4d, 0x07}, + {0xaa54, 0x08}, + {0xaa55, 0x06}, + {0xaa58, 0x04}, + {0xaa59, 0x05}, + {0xaa5c, 0x06}, + {0xaa5d, 0x06}, + {0xaa68, 0x05}, + {0xaa69, 0x05}, + {0xaa6c, 0x04}, + {0xaa6d, 0x05}, + {0xaa74, 0x06}, + {0xaa75, 0x04}, + {0xaa78, 0x05}, + {0xaa79, 0x05}, + {0xaa7c, 0x04}, + {0xaa7d, 0x06}, + {0xac18, 0x14}, + {0xac19, 0x00}, + {0xac1c, 0x14}, + {0xac1d, 0x00}, + {0xac20, 0x14}, + {0xac21, 0x00}, + {0xac24, 0x14}, + {0xac25, 0x00}, + {0xac28, 0x14}, + {0xac29, 0x00}, + {0xac2c, 0x14}, + {0xac2d, 0x00}, + {0xac34, 0x16}, + {0xac35, 0x00}, + {0xac38, 0x16}, + {0xac39, 0x00}, + {0xac3c, 0x16}, + {0xac3d, 0x00}, + {0xac40, 0x16}, + {0xac41, 0x00}, + {0xac44, 0x16}, + {0xac45, 0x00}, + {0xac48, 0x16}, + {0xac49, 0x00}, + {0xac50, 0x1b}, + {0xac51, 0x00}, + {0xac54, 0x1b}, + {0xac55, 0x00}, + {0xac58, 0x1b}, + {0xac59, 0x00}, + {0xac5c, 0x1b}, + {0xac5d, 0x00}, + {0xac60, 0x1b}, + {0xac61, 0x00}, + {0xac64, 0x1b}, + {0xac65, 0x00}, + {0xac74, 0x09}, + {0xac75, 0x0c}, + {0xac78, 0x0f}, + {0xac79, 0x11}, + {0xac7c, 0x12}, + {0xac7d, 0x14}, + {0xac80, 0x09}, + {0xac81, 0x0c}, + {0xac84, 0x0f}, + {0xac85, 0x11}, + {0xac88, 0x12}, + {0xac89, 0x14}, + {0xac8c, 0x09}, + {0xac8d, 0x0c}, + {0xac90, 0x0f}, + {0xac91, 0x11}, + {0xac94, 0x12}, + {0xac95, 0x14}, + {0xac98, 0x09}, + {0xac99, 0x0c}, + {0xac9c, 0x0f}, + {0xac9d, 0x11}, + {0xaca0, 0x12}, + {0xaca1, 0x14}, + {0xaca4, 0x09}, + {0xaca5, 0x0c}, + {0xaca8, 0x0f}, + {0xaca9, 0x11}, + {0xacac, 0x12}, + {0xacad, 0x14}, + {0xacb0, 0x07}, + {0xacb1, 0x09}, + {0xacb4, 0x0c}, + {0xacb5, 0x0d}, + {0xacb8, 0x0d}, + {0xacb9, 0x0e}, + {0xacbc, 0x05}, + {0xacbd, 0x07}, + {0xacc0, 0x0a}, + {0xacc1, 0x0b}, + {0xacc4, 0x0b}, + {0xacc5, 0x0c}, + {0xacc8, 0x03}, + {0xacc9, 0x04}, + {0xaccc, 0x07}, + {0xaccd, 0x08}, + {0xacd0, 0x09}, + {0xacd1, 0x09} +}; + +static struct vx6953_i2c_reg_conf patch_tbl_cut3[] = { + {0xF800, 0x90}, + {0xF801, 0x30}, + {0xF802, 0x31}, + {0xF803, 0xe0}, + {0xF804, 0xf5}, + {0xF805, 0x7d}, + {0xF806, 0xb4}, + {0xF807, 0x01}, + {0xF808, 0x06}, + {0xF809, 0x75}, + {0xF80A, 0x7d}, + {0xF80B, 0x03}, + {0xF80C, 0x74}, + {0xF80D, 0x03}, + {0xF80E, 0xf0}, + {0xF80F, 0x90}, + {0xF810, 0x30}, + {0xF811, 0x04}, + {0xF812, 0x74}, + {0xF813, 0x33}, + {0xF814, 0xf0}, + {0xF815, 0x90}, + {0xF816, 0x30}, + {0xF817, 0x06}, + {0xF818, 0xe4}, + {0xF819, 0xf0}, + {0xF81A, 0xa3}, + {0xF81B, 0x74}, + {0xF81C, 0x09}, + {0xF81D, 0xf0}, + {0xF81E, 0x90}, + {0xF81F, 0x30}, + {0xF820, 0x10}, + {0xF821, 0xe4}, + {0xF822, 0xf0}, + {0xF823, 0xa3}, + {0xF824, 0xf0}, + {0xF825, 0x90}, + {0xF826, 0x30}, + {0xF827, 0x16}, + {0xF828, 0x74}, + {0xF829, 0x1e}, + {0xF82A, 0xf0}, + {0xF82B, 0x90}, + {0xF82C, 0x30}, + {0xF82D, 0x1a}, + {0xF82E, 0x74}, + {0xF82F, 0x6a}, + {0xF830, 0xf0}, + {0xF831, 0xa3}, + {0xF832, 0x74}, + {0xF833, 0x29}, + {0xF834, 0xf0}, + {0xF835, 0x90}, + {0xF836, 0x30}, + {0xF837, 0x30}, + {0xF838, 0x74}, + {0xF839, 0x08}, + {0xF83A, 0xf0}, + {0xF83B, 0x90}, + {0xF83C, 0x30}, + {0xF83D, 0x36}, + {0xF83E, 0x74}, + {0xF83F, 0x2c}, + {0xF840, 0xf0}, + {0xF841, 0x90}, + {0xF842, 0x30}, + {0xF843, 0x41}, + {0xF844, 0xe4}, + {0xF845, 0xf0}, + {0xF846, 0xa3}, + {0xF847, 0x74}, + {0xF848, 0x24}, + {0xF849, 0xf0}, + {0xF84A, 0x90}, + {0xF84B, 0x30}, + {0xF84C, 0x45}, + {0xF84D, 0x74}, + {0xF84E, 0x81}, + {0xF84F, 0xf0}, + {0xF850, 0x90}, + {0xF851, 0x30}, + {0xF852, 0x98}, + {0xF853, 0x74}, + {0xF854, 0x01}, + {0xF855, 0xf0}, + {0xF856, 0x90}, + {0xF857, 0x30}, + {0xF858, 0x9d}, + {0xF859, 0x74}, + {0xF85A, 0x05}, + {0xF85B, 0xf0}, + {0xF85C, 0xe5}, + {0xF85D, 0x7d}, + {0xF85E, 0x70}, + {0xF85F, 0x10}, + {0xF860, 0x90}, + {0xF861, 0x30}, + {0xF862, 0x05}, + {0xF863, 0x04}, + {0xF864, 0xf0}, + {0xF865, 0x90}, + {0xF866, 0x30}, + {0xF867, 0x30}, + {0xF868, 0xe4}, + {0xF869, 0xf0}, + {0xF86A, 0x90}, + {0xF86B, 0x30}, + {0xF86C, 0x35}, + {0xF86D, 0x04}, + {0xF86E, 0xf0}, + {0xF86F, 0x22}, + {0xF870, 0xe5}, + {0xF871, 0x7d}, + {0xF872, 0x64}, + {0xF873, 0x02}, + {0xF874, 0x70}, + {0xF875, 0x2d}, + {0xF876, 0x90}, + {0xF877, 0x30}, + {0xF878, 0x04}, + {0xF879, 0x74}, + {0xF87A, 0x34}, + {0xF87B, 0xf0}, + {0xF87C, 0xa3}, + {0xF87D, 0x74}, + {0xF87E, 0x07}, + {0xF87F, 0xf0}, + {0xF880, 0x90}, + {0xF881, 0x30}, + {0xF882, 0x10}, + {0xF883, 0x74}, + {0xF884, 0x10}, + {0xF885, 0xf0}, + {0xF886, 0x90}, + {0xF887, 0x30}, + {0xF888, 0x16}, + {0xF889, 0x74}, + {0xF88A, 0x1f}, + {0xF88B, 0xf0}, + {0xF88C, 0x90}, + {0xF88D, 0x30}, + {0xF88E, 0x1a}, + {0xF88F, 0x74}, + {0xF890, 0x62}, + {0xF891, 0xf0}, + {0xF892, 0x90}, + {0xF893, 0x30}, + {0xF894, 0x35}, + {0xF895, 0x74}, + {0xF896, 0x04}, + {0xF897, 0xf0}, + {0xF898, 0x90}, + {0xF899, 0x30}, + {0xF89A, 0x41}, + {0xF89B, 0x74}, + {0xF89C, 0x60}, + {0xF89D, 0xf0}, + {0xF89E, 0xa3}, + {0xF89F, 0x74}, + {0xF8A0, 0x64}, + {0xF8A1, 0xf0}, + {0xF8A2, 0x22}, + {0xF8A3, 0xe5}, + {0xF8A4, 0x7d}, + {0xF8A5, 0xb4}, + {0xF8A6, 0x03}, + {0xF8A7, 0x12}, + {0xF8A8, 0x90}, + {0xF8A9, 0x30}, + {0xF8AA, 0x05}, + {0xF8AB, 0x74}, + {0xF8AC, 0x03}, + {0xF8AD, 0xf0}, + {0xF8AE, 0x90}, + {0xF8AF, 0x30}, + {0xF8B0, 0x11}, + {0xF8B1, 0x74}, + {0xF8B2, 0x01}, + {0xF8B3, 0xf0}, + {0xF8B4, 0x90}, + {0xF8B5, 0x30}, + {0xF8B6, 0x35}, + {0xF8B7, 0x74}, + {0xF8B8, 0x03}, + {0xF8B9, 0xf0}, + {0xF8BA, 0x22}, + {0xF8BB, 0xc3}, + {0xF8BC, 0x90}, + {0xF8BD, 0x0b}, + {0xF8BE, 0x89}, + {0xF8BF, 0xe0}, + {0xF8C0, 0x94}, + {0xF8C1, 0x1e}, + {0xF8C2, 0x90}, + {0xF8C3, 0x0b}, + {0xF8C4, 0x88}, + {0xF8C5, 0xe0}, + {0xF8C6, 0x94}, + {0xF8C7, 0x00}, + {0xF8C8, 0x50}, + {0xF8C9, 0x06}, + {0xF8CA, 0x7e}, + {0xF8CB, 0x00}, + {0xF8CC, 0x7f}, + {0xF8CD, 0x01}, + {0xF8CE, 0x80}, + {0xF8CF, 0x3d}, + {0xF8D0, 0xc3}, + {0xF8D1, 0x90}, + {0xF8D2, 0x0b}, + {0xF8D3, 0x89}, + {0xF8D4, 0xe0}, + {0xF8D5, 0x94}, + {0xF8D6, 0x3c}, + {0xF8D7, 0x90}, + {0xF8D8, 0x0b}, + {0xF8D9, 0x88}, + {0xF8DA, 0xe0}, + {0xF8DB, 0x94}, + {0xF8DC, 0x00}, + {0xF8DD, 0x50}, + {0xF8DE, 0x06}, + {0xF8DF, 0x7e}, + {0xF8E0, 0x00}, + {0xF8E1, 0x7f}, + {0xF8E2, 0x02}, + {0xF8E3, 0x80}, + {0xF8E4, 0x28}, + {0xF8E5, 0xc3}, + {0xF8E6, 0x90}, + {0xF8E7, 0x0b}, + {0xF8E8, 0x89}, + {0xF8E9, 0xe0}, + {0xF8EA, 0x94}, + {0xF8EB, 0xfa}, + {0xF8EC, 0x90}, + {0xF8ED, 0x0b}, + {0xF8EE, 0x88}, + {0xF8EF, 0xe0}, + {0xF8F0, 0x94}, + {0xF8F1, 0x00}, + {0xF8F2, 0x50}, + {0xF8F3, 0x06}, + {0xF8F4, 0x7e}, + {0xF8F5, 0x00}, + {0xF8F6, 0x7f}, + {0xF8F7, 0x03}, + {0xF8F8, 0x80}, + {0xF8F9, 0x13}, + {0xF8FA, 0xc3}, + {0xF8FB, 0x90}, + {0xF8FC, 0x0b}, + {0xF8FD, 0x88}, + {0xF8FE, 0xe0}, + {0xF8FF, 0x94}, + {0xF900, 0x80}, + {0xF901, 0x50}, + {0xF902, 0x06}, + {0xF903, 0x7e}, + {0xF904, 0x00}, + {0xF905, 0x7f}, + {0xF906, 0x04}, + {0xF907, 0x80}, + {0xF908, 0x04}, + {0xF909, 0xae}, + {0xF90A, 0x7e}, + {0xF90B, 0xaf}, + {0xF90C, 0x7f}, + {0xF90D, 0x90}, + {0xF90E, 0xa0}, + {0xF90F, 0xf8}, + {0xF910, 0xee}, + {0xF911, 0xf0}, + {0xF912, 0xa3}, + {0xF913, 0xef}, + {0xF914, 0xf0}, + {0xF915, 0x22}, + {0xF916, 0x90}, + {0xF917, 0x33}, + {0xF918, 0x82}, + {0xF919, 0xe0}, + {0xF91A, 0xff}, + {0xF91B, 0x64}, + {0xF91C, 0x01}, + {0xF91D, 0x70}, + {0xF91E, 0x30}, + {0xF91F, 0xe5}, + {0xF920, 0x7f}, + {0xF921, 0x64}, + {0xF922, 0x02}, + {0xF923, 0x45}, + {0xF924, 0x7e}, + {0xF925, 0x70}, + {0xF926, 0x04}, + {0xF927, 0x7d}, + {0xF928, 0x1e}, + {0xF929, 0x80}, + {0xF92A, 0x1d}, + {0xF92B, 0xe5}, + {0xF92C, 0x7f}, + {0xF92D, 0x64}, + {0xF92E, 0x03}, + {0xF92F, 0x45}, + {0xF930, 0x7e}, + {0xF931, 0x70}, + {0xF932, 0x04}, + {0xF933, 0x7d}, + {0xF934, 0x3c}, + {0xF935, 0x80}, + {0xF936, 0x11}, + {0xF937, 0xe5}, + {0xF938, 0x7f}, + {0xF939, 0x64}, + {0xF93A, 0x04}, + {0xF93B, 0x45}, + {0xF93C, 0x7e}, + {0xF93D, 0x70}, + {0xF93E, 0x04}, + {0xF93F, 0x7d}, + {0xF940, 0xfa}, + {0xF941, 0x80}, + {0xF942, 0x05}, + {0xF943, 0x90}, + {0xF944, 0x33}, + {0xF945, 0x81}, + {0xF946, 0xe0}, + {0xF947, 0xfd}, + {0xF948, 0xae}, + {0xF949, 0x05}, + {0xF94A, 0x90}, + {0xF94B, 0x33}, + {0xF94C, 0x81}, + {0xF94D, 0xed}, + {0xF94E, 0xf0}, + {0xF94F, 0xef}, + {0xF950, 0xb4}, + {0xF951, 0x01}, + {0xF952, 0x10}, + {0xF953, 0x90}, + {0xF954, 0x01}, + {0xF955, 0x00}, + {0xF956, 0xe0}, + {0xF957, 0x60}, + {0xF958, 0x0a}, + {0xF959, 0x90}, + {0xF95A, 0xa1}, + {0xF95B, 0x10}, + {0xF95C, 0xe0}, + {0xF95D, 0xf5}, + {0xF95E, 0x7e}, + {0xF95F, 0xa3}, + {0xF960, 0xe0}, + {0xF961, 0xf5}, + {0xF962, 0x7f}, + {0xF963, 0x22}, + {0xF964, 0x12}, + {0xF965, 0x2f}, + {0xF966, 0x4d}, + {0xF967, 0x90}, + {0xF968, 0x35}, + {0xF969, 0x38}, + {0xF96A, 0xe0}, + {0xF96B, 0x70}, + {0xF96C, 0x05}, + {0xF96D, 0x12}, + {0xF96E, 0x00}, + {0xF96F, 0x0e}, + {0xF970, 0x80}, + {0xF971, 0x03}, + {0xF972, 0x12}, + {0xF973, 0x07}, + {0xF974, 0xc9}, + {0xF975, 0x90}, + {0xF976, 0x40}, + {0xF977, 0x06}, + {0xF978, 0xe0}, + {0xF979, 0xf4}, + {0xF97A, 0x54}, + {0xF97B, 0x02}, + {0xF97C, 0xff}, + {0xF97D, 0xe0}, + {0xF97E, 0x54}, + {0xF97F, 0x01}, + {0xF980, 0x4f}, + {0xF981, 0x90}, + {0xF982, 0x31}, + {0xF983, 0x32}, + {0xF984, 0xf0}, + {0xF985, 0x90}, + {0xF986, 0xfa}, + {0xF987, 0x9d}, + {0xF988, 0xe0}, + {0xF989, 0x70}, + {0xF98A, 0x03}, + {0xF98B, 0x12}, + {0xF98C, 0x27}, + {0xF98D, 0x27}, + {0xF98E, 0x02}, + {0xF98F, 0x05}, + {0xF990, 0xac}, + {0xF991, 0x22}, + {0xF992, 0x78}, + {0xF993, 0x07}, + {0xF994, 0xe6}, + {0xF995, 0xf5}, + {0xF996, 0x7c}, + {0xF997, 0xe5}, + {0xF998, 0x7c}, + {0xF999, 0x60}, + {0xF99A, 0x1d}, + {0xF99B, 0x90}, + {0xF99C, 0x43}, + {0xF99D, 0x83}, + {0xF99E, 0xe0}, + {0xF99F, 0xb4}, + {0xF9A0, 0x01}, + {0xF9A1, 0x16}, + {0xF9A2, 0x90}, + {0xF9A3, 0x43}, + {0xF9A4, 0x87}, + {0xF9A5, 0xe0}, + {0xF9A6, 0xb4}, + {0xF9A7, 0x01}, + {0xF9A8, 0x0f}, + {0xF9A9, 0x15}, + {0xF9AA, 0x7c}, + {0xF9AB, 0x90}, + {0xF9AC, 0x30}, + {0xF9AD, 0xa1}, + {0xF9AE, 0xe5}, + {0xF9AF, 0x7c}, + {0xF9B0, 0xf0}, + {0xF9B1, 0x90}, + {0xF9B2, 0x30}, + {0xF9B3, 0xa0}, + {0xF9B4, 0x74}, + {0xF9B5, 0x01}, + {0xF9B6, 0xf0}, + {0xF9B7, 0x22}, + {0xF9B8, 0xe4}, + {0xF9B9, 0x90}, + {0xF9BA, 0x30}, + {0xF9BB, 0xa0}, + {0xF9BC, 0xf0}, + {0xF9BD, 0x22}, + {0xF9BE, 0xf0}, + {0xF9BF, 0xe5}, + {0xF9C0, 0x3a}, + {0xF9C1, 0xb4}, + {0xF9C2, 0x06}, + {0xF9C3, 0x06}, + {0xF9C4, 0x63}, + {0xF9C5, 0x3e}, + {0xF9C6, 0x02}, + {0xF9C7, 0x12}, + {0xF9C8, 0x03}, + {0xF9C9, 0xea}, + {0xF9CA, 0x02}, + {0xF9CB, 0x17}, + {0xF9CC, 0x4a}, + {0xF9CD, 0x22}, + {0x35C9, 0xBB}, + {0x35CA, 0x01}, + {0x35CB, 0x16}, + {0x35CC, 0x01}, + {0x35CD, 0x64}, + {0x35CE, 0x01}, + {0x35CF, 0x92}, + {0x35D0, 0x01}, + {0x35D1, 0xBE}, + {0x35D3, 0xF6}, + {0x35D5, 0x07}, + {0x35D7, 0xA3}, + {0x35DB, 0x02}, + {0x35DD, 0x06}, + {0x35DF, 0x1B}, + {0x35E6, 0x28}, + {0x35E7, 0x76}, + {0x35E8, 0x2D}, + {0x35E9, 0x07}, + {0x35EA, 0x04}, + {0x35EB, 0x43}, + {0x35EC, 0x05}, + {0x35ED, 0xA9}, + {0x35EE, 0x2A}, + {0x35EF, 0x15}, + {0x35F0, 0x17}, + {0x35F1, 0x41}, + {0x35F2, 0x24}, + {0x35F3, 0x88}, + {0x35F4, 0x01}, + {0x35F5, 0x54}, + {0x35F6, 0x01}, + {0x35F7, 0x55}, + {0x35F8, 0x2E}, + {0x35F9, 0xF2}, + {0x35FA, 0x06}, + {0x35FB, 0x02}, + {0x35FC, 0x06}, + {0x35FD, 0x03}, + {0x35FE, 0x06}, + {0x35FF, 0x04}, + {0x35C2, 0x1F}, + {0x35C3, 0xFF}, + {0x35C4, 0x1F}, + {0x35C5, 0xC0}, + {0x35C0, 0x01}, +}; + +struct vx6953_format { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; + u16 fmt; + u16 order; +}; + +static const struct vx6953_format vx6953_cfmts[] = { + { + .code = V4L2_MBUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .fmt = 1, + .order = 0, + } + /* more can be supported, to be added later */ +}; + + +/*=============================================================*/ + +static int vx6953_i2c_rxdata(unsigned short saddr, + unsigned char *rxdata, int length) +{ + struct i2c_msg msgs[] = { + { + .addr = saddr, + .flags = 0, + .len = 2, + .buf = rxdata, + }, + { + .addr = saddr, + .flags = I2C_M_RD, + .len = 2, + .buf = rxdata, + }, + }; + if (i2c_transfer(vx6953_client->adapter, msgs, 2) < 0) { + CDBG("vx6953_i2c_rxdata failed!\n"); + return -EIO; + } + return 0; +} +static int32_t vx6953_i2c_txdata(unsigned short saddr, + unsigned char *txdata, int length) +{ + struct i2c_msg msg[] = { + { + .addr = saddr, + .flags = 0, + .len = length, + .buf = txdata, + }, + }; + if (i2c_transfer(vx6953_client->adapter, msg, 1) < 0) { + CDBG("vx6953_i2c_txdata faild 0x%x\n", vx6953_client->addr); + return -EIO; + } + + return 0; +} + + +static int32_t vx6953_i2c_read(unsigned short raddr, + unsigned short *rdata, int rlen) +{ + int32_t rc = 0; + unsigned char buf[2]; + if (!rdata) + return -EIO; + memset(buf, 0, sizeof(buf)); + buf[0] = (raddr & 0xFF00) >> 8; + buf[1] = (raddr & 0x00FF); + rc = vx6953_i2c_rxdata(vx6953_client->addr>>1, buf, rlen); + if (rc < 0) { + CDBG("vx6953_i2c_read 0x%x failed!\n", raddr); + return rc; + } + *rdata = (rlen == 2 ? buf[0] << 8 | buf[1] : buf[0]); + return rc; +} +static int32_t vx6953_i2c_write_b_sensor(unsigned short waddr, uint8_t bdata) +{ + int32_t rc = -EFAULT; + unsigned char buf[3]; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + buf[2] = bdata; + CDBG("i2c_write_b addr = 0x%x, val = 0x%x\n", waddr, bdata); + rc = vx6953_i2c_txdata(vx6953_client->addr>>1, buf, 3); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata); + } + return rc; +} +static int32_t vx6953_i2c_write_seq_sensor(unsigned short waddr, + uint8_t *bdata, uint16_t len) +{ + int32_t rc = -EFAULT; + unsigned char buf[len+2]; + int i; + memset(buf, 0, sizeof(buf)); + buf[0] = (waddr & 0xFF00) >> 8; + buf[1] = (waddr & 0x00FF); + for (i = 2; i < len+2; i++) + buf[i] = *bdata++; + rc = vx6953_i2c_txdata(vx6953_client->addr>>1, buf, len+2); + if (rc < 0) { + CDBG("i2c_write_b failed, addr = 0x%x, val = 0x%x!\n", + waddr, bdata[0]); + } + return rc; +} + +static int32_t vx6953_i2c_write_w_table(struct vx6953_i2c_reg_conf const + *reg_conf_tbl, int num) +{ + int i; + int32_t rc = -EIO; + for (i = 0; i < num; i++) { + rc = vx6953_i2c_write_b_sensor(reg_conf_tbl->waddr, + reg_conf_tbl->wdata); + if (rc < 0) + break; + reg_conf_tbl++; + } + return rc; +} + +static void vx6953_get_pict_fps(uint16_t fps, uint16_t *pfps) +{ + /* input fps is preview fps in Q8 format */ + uint16_t preview_frame_length_lines, snapshot_frame_length_lines; + uint16_t preview_line_length_pck, snapshot_line_length_pck; + uint32_t divider, d1, d2; + /* Total frame_length_lines and line_length_pck for preview */ + preview_frame_length_lines = VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES; + preview_line_length_pck = VX6953_QTR_SIZE_WIDTH + + VX6953_HRZ_QTR_BLK_PIXELS; + /* Total frame_length_lines and line_length_pck for snapshot */ + snapshot_frame_length_lines = VX6953_FULL_SIZE_HEIGHT + + VX6953_VER_FULL_BLK_LINES; + snapshot_line_length_pck = VX6953_FULL_SIZE_WIDTH + + VX6953_HRZ_FULL_BLK_PIXELS; + d1 = preview_frame_length_lines * 0x00000400/ + snapshot_frame_length_lines; + d2 = preview_line_length_pck * 0x00000400/ + snapshot_line_length_pck; + divider = d1 * d2 / 0x400; + /*Verify PCLK settings and frame sizes.*/ + *pfps = (uint16_t) (fps * divider / 0x400); + /* 2 is the ratio of no.of snapshot channels + to number of preview channels */ + +} + +static uint16_t vx6953_get_prev_lines_pf(void) +{ + if (vx6953_ctrl->prev_res == QTR_SIZE) + return VX6953_QTR_SIZE_HEIGHT + VX6953_VER_QTR_BLK_LINES; + else + return VX6953_FULL_SIZE_HEIGHT + VX6953_VER_FULL_BLK_LINES; + +} + +static uint16_t vx6953_get_prev_pixels_pl(void) +{ + if (vx6953_ctrl->prev_res == QTR_SIZE) + return VX6953_QTR_SIZE_WIDTH + VX6953_HRZ_QTR_BLK_PIXELS; + else + return VX6953_FULL_SIZE_WIDTH + VX6953_HRZ_FULL_BLK_PIXELS; +} + +static uint16_t vx6953_get_pict_lines_pf(void) +{ + if (vx6953_ctrl->pict_res == QTR_SIZE) + return VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES; + else + return VX6953_FULL_SIZE_HEIGHT + + VX6953_VER_FULL_BLK_LINES; +} + +static uint16_t vx6953_get_pict_pixels_pl(void) +{ + if (vx6953_ctrl->pict_res == QTR_SIZE) + return VX6953_QTR_SIZE_WIDTH + + VX6953_HRZ_QTR_BLK_PIXELS; + else + return VX6953_FULL_SIZE_WIDTH + + VX6953_HRZ_FULL_BLK_PIXELS; +} + +static uint32_t vx6953_get_pict_max_exp_lc(void) +{ + if (vx6953_ctrl->pict_res == QTR_SIZE) + return (VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES)*24; + else + return (VX6953_FULL_SIZE_HEIGHT + + VX6953_VER_FULL_BLK_LINES)*24; +} + +static int32_t vx6953_set_fps(struct fps_cfg *fps) +{ + uint16_t total_lines_per_frame; + int32_t rc = 0; + total_lines_per_frame = (uint16_t)((VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES) * vx6953_ctrl->fps_divider/0x400); + if (vx6953_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_HI, + ((total_lines_per_frame & 0xFF00) >> 8)) < 0) + return rc; + if (vx6953_i2c_write_b_sensor(REG_FRAME_LENGTH_LINES_LO, + (total_lines_per_frame & 0x00FF)) < 0) + return rc; + return rc; +} + +static int32_t vx6953_write_exp_gain(uint16_t gain, uint32_t line) +{ + uint16_t line_length_pck, frame_length_lines; + uint8_t gain_hi, gain_lo; + uint8_t intg_time_hi, intg_time_lo; + uint8_t line_length_pck_hi = 0, line_length_pck_lo = 0; + uint16_t line_length_ratio = 1 * Q8; + int32_t rc = 0; + if (vx6953_ctrl->sensormode != SENSOR_SNAPSHOT_MODE) { + frame_length_lines = VX6953_QTR_SIZE_HEIGHT + + VX6953_VER_QTR_BLK_LINES; + line_length_pck = VX6953_QTR_SIZE_WIDTH + + VX6953_HRZ_QTR_BLK_PIXELS; + if (line > (frame_length_lines - + VX6953_STM5M0EDOF_OFFSET)) { + vx6953_ctrl->fps = (uint16_t) (30 * Q8 * + (frame_length_lines - VX6953_STM5M0EDOF_OFFSET)/ + line); + } else { + vx6953_ctrl->fps = (uint16_t) (30 * Q8); + } + } else { + frame_length_lines = VX6953_FULL_SIZE_HEIGHT + + VX6953_VER_FULL_BLK_LINES; + line_length_pck = VX6953_FULL_SIZE_WIDTH + + VX6953_HRZ_FULL_BLK_PIXELS; + } + /* calculate line_length_ratio */ + if ((frame_length_lines - VX6953_STM5M0EDOF_OFFSET) < line) { + line_length_ratio = (line*Q8) / + (frame_length_lines - VX6953_STM5M0EDOF_OFFSET); + line = frame_length_lines - VX6953_STM5M0EDOF_OFFSET; + } else { + line_length_ratio = 1*Q8; + } + vx6953_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD); + line_length_pck = (line_length_pck > + MAX_LINE_LENGTH_PCK) ? + MAX_LINE_LENGTH_PCK : line_length_pck; + line_length_pck = (uint16_t) (line_length_pck * + line_length_ratio/Q8); + line_length_pck_hi = (uint8_t) ((line_length_pck & + 0xFF00) >> 8); + line_length_pck_lo = (uint8_t) (line_length_pck & + 0x00FF); + vx6953_i2c_write_b_sensor(REG_LINE_LENGTH_PCK_HI, + line_length_pck_hi); + vx6953_i2c_write_b_sensor(REG_LINE_LENGTH_PCK_LO, + line_length_pck_lo); + /* update analogue gain registers */ + gain_hi = (uint8_t) ((gain & 0xFF00) >> 8); + gain_lo = (uint8_t) (gain & 0x00FF); + vx6953_i2c_write_b_sensor(REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + gain_lo); + vx6953_i2c_write_b_sensor(REG_DIGITAL_GAIN_GREEN_R_LO, gain_hi); + vx6953_i2c_write_b_sensor(REG_DIGITAL_GAIN_RED_LO, gain_hi); + vx6953_i2c_write_b_sensor(REG_DIGITAL_GAIN_BLUE_LO, gain_hi); + vx6953_i2c_write_b_sensor(REG_DIGITAL_GAIN_GREEN_B_LO, gain_hi); + CDBG("%s, gain_hi 0x%x, gain_lo 0x%x\n", __func__, + gain_hi, gain_lo); + /* update line count registers */ + intg_time_hi = (uint8_t) (((uint16_t)line & 0xFF00) >> 8); + intg_time_lo = (uint8_t) ((uint16_t)line & 0x00FF); + vx6953_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_HI, + intg_time_hi); + vx6953_i2c_write_b_sensor(REG_COARSE_INTEGRATION_TIME_LO, + intg_time_lo); + vx6953_i2c_write_b_sensor(REG_GROUPED_PARAMETER_HOLD, + GROUPED_PARAMETER_HOLD_OFF); + + return rc; +} + +static int32_t vx6953_set_pict_exp_gain(uint16_t gain, uint32_t line) +{ + int32_t rc = 0; + rc = vx6953_write_exp_gain(gain, line); + return rc; +} /* endof vx6953_set_pict_exp_gain*/ + +static int32_t vx6953_move_focus(int direction, + int32_t num_steps) +{ + return 0; +} + + +static int32_t vx6953_set_default_focus(uint8_t af_step) +{ + return 0; +} + +static int32_t vx6953_test(enum vx6953_test_mode_t mo) +{ + int32_t rc = 0; + if (mo == TEST_OFF) + return rc; + else { + /* REG_0x30D8[4] is TESBYPEN: 0: Normal Operation, + 1: Bypass Signal Processing + REG_0x30D8[5] is EBDMASK: 0: + Output Embedded data, 1: No output embedded data */ + if (vx6953_i2c_write_b_sensor(REG_TEST_PATTERN_MODE, + (uint8_t) mo) < 0) { + return rc; + } + } + return rc; +} + +static int vx6953_enable_edof(enum edof_mode_t edof_mode) +{ + int rc = 0; + if (edof_mode == VX6953_EDOF_ESTIMATION) { + /* EDof Estimation mode for preview */ + if (vx6953_i2c_write_b_sensor(REG_0x0b80, 0x02) < 0) + return rc; + CDBG("VX6953_EDOF_ESTIMATION"); + } else if (edof_mode == VX6953_EDOF_APPLICATION) { + /* EDof Application mode for Capture */ + if (vx6953_i2c_write_b_sensor(REG_0x0b80, 0x01) < 0) + return rc; + CDBG("VX6953_EDOF_APPLICATION"); + } else { + /* EDOF disabled */ + if (vx6953_i2c_write_b_sensor(REG_0x0b80, 0x00) < 0) + return rc; + CDBG("VX6953_EDOF_DISABLE"); + } + return rc; +} + +static int32_t vx6953_patch_for_cut2(void) +{ + int32_t rc = 0; + rc = vx6953_i2c_write_w_table(patch_tbl_cut2, + ARRAY_SIZE(patch_tbl_cut2)); + if (rc < 0) + return rc; + + return rc; +} +static int32_t vx6953_patch_for_cut3(void) +{ + int32_t rc = 0; + rc = vx6953_i2c_write_w_table(patch_tbl_cut3, + ARRAY_SIZE(patch_tbl_cut3)); + if (rc < 0) + return rc; + + return rc; +} +static int32_t vx6953_sensor_setting(int update_type, int rt) +{ + + int32_t rc = 0; + unsigned short frame_cnt; + struct msm_camera_csi_params vx6953_csi_params; + if (vx6953_ctrl->sensor_type != VX6953_STM5M0EDOF_CUT_2) { + switch (update_type) { + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct vx6953_i2c_reg_conf init_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {0x6003, 0x01}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + {REG_0x3030, + vx6953_regs.reg_pat_init[0].reg_0x3030}, + /* 953 specific registers */ + {REG_0x0111, + vx6953_regs.reg_pat_init[0].reg_0x0111}, + {REG_0x0b00, + vx6953_regs.reg_pat_init[0].reg_0x0b00}, + {REG_0x3001, + vx6953_regs.reg_pat_init[0].reg_0x3001}, + {REG_0x3004, + vx6953_regs.reg_pat_init[0].reg_0x3004}, + {0x3006, 0x00}, + {REG_0x3007, + vx6953_regs.reg_pat_init[0].reg_0x3007}, + {0x301b, 0x29}, + /* DEFCOR settings */ + /*Single Defect Correction Weight DISABLE*/ + {0x0b06, + vx6953_regs.reg_pat_init[0].reg_0x0b06}, + /*Single_defect_correct_weight = auto*/ + {0x0b07, + vx6953_regs.reg_pat_init[0].reg_0x0b07}, + /*Dynamic couplet correction ENABLED*/ + {0x0b08, + vx6953_regs.reg_pat_init[0].reg_0x0b08}, + /*Dynamic couplet correction weight*/ + {0x0b09, + vx6953_regs.reg_pat_init[0].reg_0x0b09}, + /* Clock Setup */ + /* Tell sensor ext clk is 24MHz*/ + {REG_0x0136, + vx6953_regs.reg_pat_init[0].reg_0x0136}, + {REG_0x0137, + vx6953_regs.reg_pat_init[0].reg_0x0137}, + /* The white balance gains must be written + to the sensor every frame. */ + /* Edof */ + {REG_0x0b83, + vx6953_regs.reg_pat_init[0].reg_0x0b83}, + {REG_0x0b84, + vx6953_regs.reg_pat_init[0].reg_0x0b84}, + {REG_0x0b85, + vx6953_regs.reg_pat_init[0].reg_0x0b85}, + {REG_0x0b88, + vx6953_regs.reg_pat_init[0].reg_0x0b88}, + {REG_0x0b89, + vx6953_regs.reg_pat_init[0].reg_0x0b89}, + {REG_0x0b8a, + vx6953_regs.reg_pat_init[0].reg_0x0b8a}, + /* Mode specific regieters */ + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_0x3005, + vx6953_regs.reg_pat[rt].reg_0x3005}, + {0x3010, + vx6953_regs.reg_pat[rt].reg_0x3010}, + {REG_0x3011, + vx6953_regs.reg_pat[rt].reg_0x3011}, + {REG_0x301a, + vx6953_regs.reg_pat[rt].reg_0x301a}, + {REG_0x3035, + vx6953_regs.reg_pat[rt].reg_0x3035}, + {REG_0x3036, + vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3041, + vx6953_regs.reg_pat[rt].reg_0x3041}, + {0x3042, + vx6953_regs.reg_pat[rt].reg_0x3042}, + {REG_0x3045, + vx6953_regs.reg_pat[rt].reg_0x3045}, + /*EDOF: Estimation settings for Preview mode + Application settings for capture mode + (standard settings - Not tuned) */ + {REG_0x0b80, + vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0900, + vx6953_regs.reg_pat[rt].reg_0x0900}, + {REG_0x0901, + vx6953_regs.reg_pat[rt].reg_0x0901}, + {REG_0x0902, + vx6953_regs.reg_pat[rt].reg_0x0902}, + {REG_0x0383, + vx6953_regs.reg_pat[rt].reg_0x0383}, + {REG_0x0387, + vx6953_regs.reg_pat[rt].reg_0x0387}, + /* Change output size / frame rate */ + {REG_0x034c, + vx6953_regs.reg_pat[rt].reg_0x034c}, + {REG_0x034d, + vx6953_regs.reg_pat[rt].reg_0x034d}, + {REG_0x034e, + vx6953_regs.reg_pat[rt].reg_0x034e}, + {REG_0x034f, + vx6953_regs.reg_pat[rt].reg_0x034f}, + }; + /* reset fps_divider */ + vx6953_ctrl->fps = 30 * Q8; + /* stop streaming */ + + /* Reset everything first */ + if (vx6953_i2c_write_b_sensor(0x103, 0x01) < 0) { + CDBG("S/W reset failed\n"); + return rc; + } else + CDBG("S/W reset successful\n"); + + msleep(10); + + CDBG("Init vx6953_sensor_setting standby\n"); + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + /*vx6953_stm5m0edof_delay_msecs_stdby*/ + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + + + vx6953_patch_for_cut3(); + rc = vx6953_i2c_write_w_table(&init_tbl[0], + ARRAY_SIZE(init_tbl)); + if (rc < 0) + return rc; + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + vx6953_i2c_write_b_sensor(0x0b80, 0x00); + vx6953_i2c_write_b_sensor(0x3388, 0x03); + vx6953_i2c_write_b_sensor(0x3640, 0x00); + + rc = vx6953_i2c_write_w_table(&edof_tbl[0], + ARRAY_SIZE(edof_tbl)); + vx6953_i2c_write_b_sensor(0x3388, 0x00); + + } + return rc; + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct vx6953_i2c_reg_conf preview_mode_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {0x6003, 0x01}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + + {REG_0x3210, vx6953_regs.reg_pat[rt].reg_0x3210}, + {REG_0x0111, vx6953_regs.reg_pat[rt].reg_0x111}, + {REG_0x3410, vx6953_regs.reg_pat[rt].reg_0x3410}, + + {REG_0x3004, + vx6953_regs.reg_pat_init[0].reg_0x3004}, + {REG_0x3006, 0x00}, + {REG_0x3007, + vx6953_regs.reg_pat_init[0].reg_0x3007}, + {REG_0x301b, 0x29}, + {REG_0x3036, + vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3045, vx6953_regs.reg_pat[rt].reg_0x3045}, + {REG_0x3098, vx6953_regs.reg_pat[rt].reg_0x3098}, + {REG_0x309d, vx6953_regs.reg_pat[rt].reg_0x309D}, + + {REG_0x0900, vx6953_regs.reg_pat[rt].reg_0x0900}, + {REG_0x0901, vx6953_regs.reg_pat[rt].reg_0x0901}, + {REG_0x0902, vx6953_regs.reg_pat[rt].reg_0x0902}, + {REG_0x0383, vx6953_regs.reg_pat[rt].reg_0x0383}, + {REG_0x0387, vx6953_regs.reg_pat[rt].reg_0x0387}, + + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_0x034c, + vx6953_regs.reg_pat[rt].reg_0x034c}, + {REG_0x034d, + vx6953_regs.reg_pat[rt].reg_0x034d}, + {REG_0x034e, + vx6953_regs.reg_pat[rt].reg_0x034e}, + {REG_0x034f, + vx6953_regs.reg_pat[rt].reg_0x034f}, + + {REG_0x3005, vx6953_regs.reg_pat[rt].reg_0x3005}, + {REG_0x3010, vx6953_regs.reg_pat[rt].reg_0x3010}, + {REG_0x3011, vx6953_regs.reg_pat[rt].reg_0x3011}, + {REG_0x301a, vx6953_regs.reg_pat[rt].reg_0x301a}, + {REG_0x3030, 0x08}, + {REG_0x3035, vx6953_regs.reg_pat[rt].reg_0x3035}, + {REG_0x3041, vx6953_regs.reg_pat[rt].reg_0x3041}, + {0x3042, vx6953_regs.reg_pat[rt].reg_0x3042}, + + {0x200, vx6953_regs.reg_pat[rt].reg_0x0200}, + {0x201, vx6953_regs.reg_pat[rt].reg_0x0201}, + + {0x0b06, + vx6953_regs.reg_pat_init[0].reg_0x0b06}, + /*Single_defect_correct_weight = auto*/ + {0x0b07, + vx6953_regs.reg_pat_init[0].reg_0x0b07}, + /*Dynamic couplet correction ENABLED*/ + {0x0b08, + vx6953_regs.reg_pat_init[0].reg_0x0b08}, + /*Dynamic couplet correction weight*/ + {0x0b09, + vx6953_regs.reg_pat_init[0].reg_0x0b09}, + + {REG_0x0136, + vx6953_regs.reg_pat_init[0].reg_0x0136}, + {REG_0x0137, + vx6953_regs.reg_pat_init[0].reg_0x0137}, + + /*EDOF: Estimation settings for Preview mode + Application settings for capture + mode(standard settings - Not tuned) */ + {REG_0x0b80, vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0b83, + vx6953_regs.reg_pat_init[0].reg_0x0b83}, + {REG_0x0b84, + vx6953_regs.reg_pat_init[0].reg_0x0b84}, + {REG_0x0b85, + vx6953_regs.reg_pat_init[0].reg_0x0b85}, + {REG_0x0b88, + vx6953_regs.reg_pat_init[0].reg_0x0b88}, + {REG_0x0b89, + vx6953_regs.reg_pat_init[0].reg_0x0b89}, + {REG_0x0b8a, + vx6953_regs.reg_pat_init[0].reg_0x0b8a}, + {0x3393, 0x06}, /* man_spec_edof_ctrl_edof*/ + {0x3394, 0x07}, /* man_spec_edof_ctrl_edof*/ + }; + + struct vx6953_i2c_reg_conf snapshot_mode_tbl[] = { + {REG_MODE_SELECT, MODE_SELECT_STANDBY_MODE}, + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {0x6003, 0x01}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {0x303, 1}, /* VT_SYS_CLK_DIV */ + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + {0x30b, 1}, + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_0x3210, vx6953_regs.reg_pat[rt].reg_0x3210}, + {REG_0x0111, vx6953_regs.reg_pat[rt].reg_0x111}, + + {REG_0x0b00, + vx6953_regs.reg_pat_init[0].reg_0x0b00}, + {0x3140, 0x01}, /* AV2X2 block enabled */ + {REG_0x3410, vx6953_regs.reg_pat[rt].reg_0x3410}, + {0x0b06, + vx6953_regs.reg_pat_init[0].reg_0x0b06}, + /*Single_defect_correct_weight = auto*/ + {0x0b07, + vx6953_regs.reg_pat_init[0].reg_0x0b07}, + /*Dynamic couplet correction ENABLED*/ + {0x0b08, + vx6953_regs.reg_pat_init[0].reg_0x0b08}, + /*Dynamic couplet correction weight*/ + {0x0b09, + vx6953_regs.reg_pat_init[0].reg_0x0b09}, + + + {REG_0x3004, + vx6953_regs.reg_pat_init[0].reg_0x3004}, + {REG_0x3006, 0x00}, + {REG_0x3007, + vx6953_regs.reg_pat_init[0].reg_0x3007}, + {0x301A, 0x6A}, + {REG_0x301b, 0x29}, + {REG_0x3036, + vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3045, vx6953_regs.reg_pat[rt].reg_0x3045}, + {REG_0x3098, vx6953_regs.reg_pat[rt].reg_0x3098}, + {REG_0x309d, vx6953_regs.reg_pat[rt].reg_0x309D}, + + {REG_0x0136, + vx6953_regs.reg_pat_init[0].reg_0x0136}, + {REG_0x0137, + vx6953_regs.reg_pat_init[0].reg_0x0137}, + + {REG_0x0b80, vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0b83, + vx6953_regs.reg_pat_init[0].reg_0x0b83}, + {REG_0x0b84, + vx6953_regs.reg_pat_init[0].reg_0x0b84}, + {REG_0x0b85, + vx6953_regs.reg_pat_init[0].reg_0x0b85}, + {REG_0x0b88, + vx6953_regs.reg_pat_init[0].reg_0x0b88}, + {REG_0x0b89, + vx6953_regs.reg_pat_init[0].reg_0x0b89}, + {REG_0x0b8a, + vx6953_regs.reg_pat_init[0].reg_0x0b8a}, + {0x3393, 0x06}, /* man_spec_edof_ctrl*/ + {0x3394, 0x07}, /* man_spec_edof_ctrl*/ + }; + /* stop streaming */ + msleep(5); + + /* Reset everything first */ + + if (vx6953_i2c_write_b_sensor(0x103, 0x01) < 0) { + CDBG("S/W reset failed\n"); + return rc; + } else + CDBG("S/W reset successful\n"); + + msleep(10); + + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + /*vx6953_stm5m0edof_delay_msecs_stdby*/ + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + vx6953_csi_params.data_format = CSI_8BIT; + vx6953_csi_params.lane_cnt = 1; + vx6953_csi_params.lane_assign = 0xe4; + vx6953_csi_params.dpcm_scheme = 0; + vx6953_csi_params.settle_cnt = 7; + rc = msm_camio_csi_config(&vx6953_csi_params); + if (rc < 0) + CDBG(" config csi controller failed\n"); + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + vx6953_patch_for_cut3(); + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + if (rt == RES_PREVIEW) { + rc = vx6953_i2c_write_w_table( + &preview_mode_tbl[0], + ARRAY_SIZE(preview_mode_tbl)); + if (rc < 0) + return rc; + } + if (rt == RES_CAPTURE) { + rc = vx6953_i2c_write_w_table( + &snapshot_mode_tbl[0], + ARRAY_SIZE(snapshot_mode_tbl)); + if (rc < 0) + return rc; + } + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + /* Start sensor streaming */ + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STREAM) < 0) + return rc; + msleep(vx6953_stm5m0edof_delay_msecs_stream); + /* man_spec_edof_ctrl_tune_smooth_lowlight*/ + vx6953_i2c_write_b_sensor(0x338d, 0x08); + /* man_spec_edof_ctrl_tune_smooth_indoor*/ + vx6953_i2c_write_b_sensor(0x338e, 0x08); + /* man_spec_edof_ctrl_tune_smooth_outdoor*/ + vx6953_i2c_write_b_sensor(0x338f, 0x00); + /*Apply Capture FPGA state machine reset*/ + vx6953_i2c_write_b_sensor(0x16, 0x00); + msleep(100); + vx6953_i2c_write_b_sensor(0x16, 0x01); + + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + + while (frame_cnt == 0xFF) { + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + CDBG("frame_cnt=%d", frame_cnt); + msleep(10); + } + } + return rc; + default: + return rc; + } + } else { + switch (update_type) { + case REG_INIT: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct vx6953_i2c_reg_conf init_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + {REG_0x3030, + vx6953_regs.reg_pat_init[0].reg_0x3030}, + /* 953 specific registers */ + {REG_0x0111, + vx6953_regs.reg_pat_init[0].reg_0x0111}, + {REG_0x0b00, + vx6953_regs.reg_pat_init[0].reg_0x0b00}, + {REG_0x3001, + vx6953_regs.reg_pat_init[0].reg_0x3001}, + {REG_0x3004, + vx6953_regs.reg_pat_init[0].reg_0x3004}, + {REG_0x3007, + vx6953_regs.reg_pat_init[0].reg_0x3007}, + {REG_0x3016, + vx6953_regs.reg_pat_init[0].reg_0x3016}, + {REG_0x301d, + vx6953_regs.reg_pat_init[0].reg_0x301d}, + {REG_0x317e, + vx6953_regs.reg_pat_init[0].reg_0x317e}, + {REG_0x317f, + vx6953_regs.reg_pat_init[0].reg_0x317f}, + {REG_0x3400, + vx6953_regs.reg_pat_init[0].reg_0x3400}, + /* DEFCOR settings */ + /*Single Defect Correction Weight DISABLE*/ + {0x0b06, + vx6953_regs.reg_pat_init[0].reg_0x0b06}, + /*Single_defect_correct_weight = auto*/ + {0x0b07, + vx6953_regs.reg_pat_init[0].reg_0x0b07}, + /*Dynamic couplet correction ENABLED*/ + {0x0b08, + vx6953_regs.reg_pat_init[0].reg_0x0b08}, + /*Dynamic couplet correction weight*/ + {0x0b09, + vx6953_regs.reg_pat_init[0].reg_0x0b09}, + /* Clock Setup */ + /* Tell sensor ext clk is 24MHz*/ + {0x0136, + vx6953_regs.reg_pat_init[0].reg_0x0136}, + {0x0137, + vx6953_regs.reg_pat_init[0].reg_0x0137}, + /* The white balance gains must be written + to the sensor every frame. */ + /* Edof */ + {REG_0x0b83, + vx6953_regs.reg_pat_init[0].reg_0x0b83}, + {REG_0x0b84, + vx6953_regs.reg_pat_init[0].reg_0x0b84}, + {0x0b85, + vx6953_regs.reg_pat_init[0].reg_0x0b85}, + {0x0b88, + vx6953_regs.reg_pat_init[0].reg_0x0b88}, + {0x0b89, + vx6953_regs.reg_pat_init[0].reg_0x0b89}, + {REG_0x0b8a, + vx6953_regs.reg_pat_init[0].reg_0x0b8a}, + /* Mode specific regieters */ + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_0x3005, + vx6953_regs.reg_pat[rt].reg_0x3005}, + {0x3010, + vx6953_regs.reg_pat[rt].reg_0x3010}, + {REG_0x3011, + vx6953_regs.reg_pat[rt].reg_0x3011}, + {REG_0x301a, + vx6953_regs.reg_pat[rt].reg_0x301a}, + {REG_0x3035, + vx6953_regs.reg_pat[rt].reg_0x3035}, + {REG_0x3036, + vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3041, + vx6953_regs.reg_pat[rt].reg_0x3041}, + {0x3042, + vx6953_regs.reg_pat[rt].reg_0x3042}, + {REG_0x3045, + vx6953_regs.reg_pat[rt].reg_0x3045}, + /*EDOF: Estimation settings for Preview mode + Application settings for capture mode + (standard settings - Not tuned) */ + {REG_0x0b80, + vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0900, + vx6953_regs.reg_pat[rt].reg_0x0900}, + {REG_0x0901, + vx6953_regs.reg_pat[rt].reg_0x0901}, + {REG_0x0902, + vx6953_regs.reg_pat[rt].reg_0x0902}, + {REG_0x0383, + vx6953_regs.reg_pat[rt].reg_0x0383}, + {REG_0x0387, + vx6953_regs.reg_pat[rt].reg_0x0387}, + /* Change output size / frame rate */ + {REG_0x034c, + vx6953_regs.reg_pat[rt].reg_0x034c}, + {REG_0x034d, + vx6953_regs.reg_pat[rt].reg_0x034d}, + {REG_0x034e, + vx6953_regs.reg_pat[rt].reg_0x034e}, + {REG_0x034f, + vx6953_regs.reg_pat[rt].reg_0x034f}, + {REG_0x1716, + vx6953_regs.reg_pat[rt].reg_0x1716}, + {REG_0x1717, + vx6953_regs.reg_pat[rt].reg_0x1717}, + {REG_0x1718, + vx6953_regs.reg_pat[rt].reg_0x1718}, + {REG_0x1719, + vx6953_regs.reg_pat[rt].reg_0x1719}, + }; + /* reset fps_divider */ + vx6953_ctrl->fps = 30 * Q8; + /* stop streaming */ + + /* Reset everything first */ + if (vx6953_i2c_write_b_sensor(0x103, 0x01) < 0) { + CDBG("S/W reset failed\n"); + return rc; + } else + CDBG("S/W reset successful\n"); + + msleep(10); + + CDBG("Init vx6953_sensor_setting standby\n"); + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + /*vx6953_stm5m0edof_delay_msecs_stdby*/ + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + vx6953_patch_for_cut2(); + rc = vx6953_i2c_write_w_table(&init_tbl[0], + ARRAY_SIZE(init_tbl)); + if (rc < 0) + return rc; + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + } + return rc; + case UPDATE_PERIODIC: + if (rt == RES_PREVIEW || rt == RES_CAPTURE) { + struct vx6953_i2c_reg_conf init_mode_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + {REG_0x3030, + vx6953_regs.reg_pat_init[0].reg_0x3030}, + /* 953 specific registers */ + {REG_0x0111, + vx6953_regs.reg_pat_init[0].reg_0x0111}, + {REG_0x0b00, + vx6953_regs.reg_pat_init[0].reg_0x0b00}, + {REG_0x3001, + vx6953_regs.reg_pat_init[0].reg_0x3001}, + {REG_0x3004, + vx6953_regs.reg_pat_init[0].reg_0x3004}, + {REG_0x3007, + vx6953_regs.reg_pat_init[0].reg_0x3007}, + {REG_0x3016, + vx6953_regs.reg_pat_init[0].reg_0x3016}, + {REG_0x301d, + vx6953_regs.reg_pat_init[0].reg_0x301d}, + {REG_0x317e, + vx6953_regs.reg_pat_init[0].reg_0x317e}, + {REG_0x317f, + vx6953_regs.reg_pat_init[0].reg_0x317f}, + {REG_0x3400, + vx6953_regs.reg_pat_init[0].reg_0x3400}, + {0x0b06, + vx6953_regs.reg_pat_init[0].reg_0x0b06}, + /*Single_defect_correct_weight = auto*/ + {0x0b07, + vx6953_regs.reg_pat_init[0].reg_0x0b07}, + /*Dynamic couplet correction ENABLED*/ + {0x0b08, + vx6953_regs.reg_pat_init[0].reg_0x0b08}, + /*Dynamic couplet correction weight*/ + {0x0b09, + vx6953_regs.reg_pat_init[0].reg_0x0b09}, + /* Clock Setup */ + /* Tell sensor ext clk is 24MHz*/ + {0x0136, + vx6953_regs.reg_pat_init[0].reg_0x0136}, + {0x0137, + vx6953_regs.reg_pat_init[0].reg_0x0137}, + /* The white balance gains must be written + to the sensor every frame. */ + /* Edof */ + {REG_0x0b83, + vx6953_regs.reg_pat_init[0].reg_0x0b83}, + {REG_0x0b84, + vx6953_regs.reg_pat_init[0].reg_0x0b84}, + {0x0b85, + vx6953_regs.reg_pat_init[0].reg_0x0b85}, + {0x0b88, + vx6953_regs.reg_pat_init[0].reg_0x0b88}, + {0x0b89, + vx6953_regs.reg_pat_init[0].reg_0x0b89}, + {REG_0x0b8a, + vx6953_regs.reg_pat_init[0].reg_0x0b8a}, + /* Mode specific regieters */ + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_0x3005, + vx6953_regs.reg_pat[rt].reg_0x3005}, + {0x3010, + vx6953_regs.reg_pat[rt].reg_0x3010}, + {REG_0x3011, + vx6953_regs.reg_pat[rt].reg_0x3011}, + {REG_0x301a, + vx6953_regs.reg_pat[rt].reg_0x301a}, + {REG_0x3035, + vx6953_regs.reg_pat[rt].reg_0x3035}, + {REG_0x3036, + vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3041, + vx6953_regs.reg_pat[rt].reg_0x3041}, + {0x3042, + vx6953_regs.reg_pat[rt].reg_0x3042}, + {REG_0x3045, + vx6953_regs.reg_pat[rt].reg_0x3045}, + /*EDOF: Estimation settings for Preview mode + Application settings for capture mode + (standard settings - Not tuned) */ + {REG_0x0b80, + vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0900, + vx6953_regs.reg_pat[rt].reg_0x0900}, + {REG_0x0901, + vx6953_regs.reg_pat[rt].reg_0x0901}, + {REG_0x0902, + vx6953_regs.reg_pat[rt].reg_0x0902}, + {REG_0x0383, + vx6953_regs.reg_pat[rt].reg_0x0383}, + {REG_0x0387, + vx6953_regs.reg_pat[rt].reg_0x0387}, + /* Change output size / frame rate */ + {REG_0x034c, + vx6953_regs.reg_pat[rt].reg_0x034c}, + {REG_0x034d, + vx6953_regs.reg_pat[rt].reg_0x034d}, + {REG_0x034e, + vx6953_regs.reg_pat[rt].reg_0x034e}, + {REG_0x034f, + vx6953_regs.reg_pat[rt].reg_0x034f}, + {REG_0x1716, + vx6953_regs.reg_pat[rt].reg_0x1716}, + {REG_0x1717, + vx6953_regs.reg_pat[rt].reg_0x1717}, + {REG_0x1718, + vx6953_regs.reg_pat[rt].reg_0x1718}, + {REG_0x1719, + vx6953_regs.reg_pat[rt].reg_0x1719}, + }; + struct vx6953_i2c_reg_conf mode_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + /* Mode specific regieters */ + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt].frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt].frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt].line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt].line_length_pck_lo}, + {REG_0x3005, vx6953_regs.reg_pat[rt].reg_0x3005}, + {0x3010, vx6953_regs.reg_pat[rt].reg_0x3010}, + {REG_0x3011, vx6953_regs.reg_pat[rt].reg_0x3011}, + {REG_0x301a, vx6953_regs.reg_pat[rt].reg_0x301a}, + {REG_0x3035, vx6953_regs.reg_pat[rt].reg_0x3035}, + {REG_0x3036, vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3041, vx6953_regs.reg_pat[rt].reg_0x3041}, + {0x3042, vx6953_regs.reg_pat[rt].reg_0x3042}, + {REG_0x3045, vx6953_regs.reg_pat[rt].reg_0x3045}, + /*EDOF: Estimation settings for Preview mode + Application settings for capture + mode(standard settings - Not tuned) */ + {REG_0x0b80, vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0900, vx6953_regs.reg_pat[rt].reg_0x0900}, + {REG_0x0901, vx6953_regs.reg_pat[rt].reg_0x0901}, + {REG_0x0902, vx6953_regs.reg_pat[rt].reg_0x0902}, + {REG_0x0383, vx6953_regs.reg_pat[rt].reg_0x0383}, + {REG_0x0387, vx6953_regs.reg_pat[rt].reg_0x0387}, + /* Change output size / frame rate */ + {REG_0x034c, vx6953_regs.reg_pat[rt].reg_0x034c}, + {REG_0x034d, vx6953_regs.reg_pat[rt].reg_0x034d}, + {REG_0x034e, vx6953_regs.reg_pat[rt].reg_0x034e}, + {REG_0x034f, vx6953_regs.reg_pat[rt].reg_0x034f}, + /*{0x200, vx6953_regs.reg_pat[rt].reg_0x0200}, + {0x201, vx6953_regs.reg_pat[rt].reg_0x0201},*/ + {REG_0x1716, vx6953_regs.reg_pat[rt].reg_0x1716}, + {REG_0x1717, vx6953_regs.reg_pat[rt].reg_0x1717}, + {REG_0x1718, vx6953_regs.reg_pat[rt].reg_0x1718}, + {REG_0x1719, vx6953_regs.reg_pat[rt].reg_0x1719}, + }; + /* stop streaming */ + msleep(5); + + /* Reset everything first */ + if (vx6953_i2c_write_b_sensor(0x103, 0x01) < 0) { + CDBG("S/W reset failed\n"); + return rc; + } else + CDBG("S/W reset successful\n"); + + msleep(10); + + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + /*vx6953_stm5m0edof_delay_msecs_stdby*/ + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + vx6953_csi_params.data_format = CSI_8BIT; + vx6953_csi_params.lane_cnt = 1; + vx6953_csi_params.lane_assign = 0xe4; + vx6953_csi_params.dpcm_scheme = 0; + vx6953_csi_params.settle_cnt = 7; + rc = msm_camio_csi_config(&vx6953_csi_params); + if (rc < 0) + CDBG(" config csi controller failed\n"); + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + vx6953_patch_for_cut2(); + rc = vx6953_i2c_write_w_table(&init_mode_tbl[0], + ARRAY_SIZE(init_mode_tbl)); + if (rc < 0) + return rc; + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + rc = vx6953_i2c_write_w_table(&mode_tbl[0], + ARRAY_SIZE(mode_tbl)); + if (rc < 0) + return rc; + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + /* Start sensor streaming */ + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STREAM) < 0) + return rc; + msleep(vx6953_stm5m0edof_delay_msecs_stream); + + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + + while (frame_cnt == 0xFF) { + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + CDBG("frame_cnt=%d", frame_cnt); + msleep(10); + } + } + return rc; + default: + return rc; + } + } + return rc; +} + + +static int32_t vx6953_video_config(int mode) +{ + + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + if (vx6953_ctrl->prev_res == QTR_SIZE) { + rt = RES_PREVIEW; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((2 * 1000 * vx6953_ctrl->fps_divider) / + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } else { + rt = RES_CAPTURE; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((1000 * vx6953_ctrl->fps_divider) / + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } + if (vx6953_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + if (vx6953_ctrl->set_test) { + if (vx6953_test(vx6953_ctrl->set_test) < 0) + return rc; + } + vx6953_ctrl->edof_mode = VX6953_EDOF_ESTIMATION; + rc = vx6953_enable_edof(vx6953_ctrl->edof_mode); + if (rc < 0) + return rc; + vx6953_ctrl->curr_res = vx6953_ctrl->prev_res; + vx6953_ctrl->sensormode = mode; + return rc; +} + +static int32_t vx6953_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + /*change sensor resolution if needed */ + if (vx6953_ctrl->curr_res != vx6953_ctrl->pict_res) { + if (vx6953_ctrl->pict_res == QTR_SIZE) { + rt = RES_PREVIEW; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((2 * 1000 * vx6953_ctrl->fps_divider) / + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } else { + rt = RES_CAPTURE; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((1000 * vx6953_ctrl->fps_divider) / + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } + if (vx6953_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + + vx6953_ctrl->edof_mode = VX6953_EDOF_APPLICATION; + if (vx6953_enable_edof(vx6953_ctrl->edof_mode) < 0) + return rc; + vx6953_ctrl->curr_res = vx6953_ctrl->pict_res; + vx6953_ctrl->sensormode = mode; + return rc; +} /*end of vx6953_snapshot_config*/ + +static int32_t vx6953_raw_snapshot_config(int mode) +{ + int32_t rc = 0; + int rt; + /* change sensor resolution if needed */ + if (vx6953_ctrl->curr_res != vx6953_ctrl->pict_res) { + if (vx6953_ctrl->pict_res == QTR_SIZE) { + rt = RES_PREVIEW; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((2 * 1000 * vx6953_ctrl->fps_divider)/ + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } else { + rt = RES_CAPTURE; + vx6953_stm5m0edof_delay_msecs_stdby = + ((((1000 * vx6953_ctrl->fps_divider)/ + vx6953_ctrl->fps) * Q8) / Q10) + 1; + } + if (vx6953_sensor_setting(UPDATE_PERIODIC, rt) < 0) + return rc; + } + vx6953_ctrl->edof_mode = VX6953_EDOF_APPLICATION; + if (vx6953_enable_edof(vx6953_ctrl->edof_mode) < 0) + return rc; + vx6953_ctrl->curr_res = vx6953_ctrl->pict_res; + vx6953_ctrl->sensormode = mode; + return rc; +} /*end of vx6953_raw_snapshot_config*/ +static int32_t vx6953_set_sensor_mode(int mode, + int res) +{ + int32_t rc = 0; + switch (mode) { + case SENSOR_PREVIEW_MODE: + rc = vx6953_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + rc = vx6953_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + rc = vx6953_raw_snapshot_config(mode); + break; + default: + rc = -EINVAL; + break; + } + return rc; +} +static int32_t vx6953_power_down(void) +{ + vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE); + return 0; +} + + +static int vx6953_probe_init_done(const struct msm_camera_sensor_info *data) +{ + gpio_free(data->sensor_reset); + kfree(vx6953_ctrl); + vx6953_ctrl = NULL; + return 0; +} +static int vx6953_probe_init_sensor(const struct msm_camera_sensor_info *data) +{ + unsigned short revision_number; + int32_t rc = 0; + unsigned short chipidl, chipidh; + CDBG("%s: %d\n", __func__, __LINE__); + rc = gpio_request(data->sensor_reset, "vx6953"); + CDBG(" vx6953_probe_init_sensor\n"); + if (!rc) { + CDBG("sensor_reset = %d\n", rc); + CDBG(" vx6953_probe_init_sensor 1\n"); + gpio_direction_output(data->sensor_reset, 0); + msleep(50); + CDBG(" vx6953_probe_init_sensor 1\n"); + gpio_direction_output(data->sensor_reset, 1); + msleep(13); + } else { + CDBG(" vx6953_probe_init_sensor 2\n"); + goto init_probe_done; + } + msleep(20); + CDBG(" vx6953_probe_init_sensor is called\n"); + /* 3. Read sensor Model ID: */ + rc = vx6953_i2c_read(0x0000, &chipidh, 1); + if (rc < 0) { + CDBG(" vx6953_probe_init_sensor 3\n"); + goto init_probe_fail; + } + rc = vx6953_i2c_read(0x0001, &chipidl, 1); + if (rc < 0) { + CDBG(" vx6953_probe_init_sensor4\n"); + goto init_probe_fail; + } + CDBG("vx6953 model_id = 0x%x 0x%x\n", chipidh, chipidl); + /* 4. Compare sensor ID to VX6953 ID: */ + if (chipidh != 0x03 || chipidl != 0xB9) { + rc = -ENODEV; + CDBG("vx6953_probe_init_sensor fail chip id doesnot match\n"); + goto init_probe_fail; + } + + vx6953_ctrl = kzalloc(sizeof(struct vx6953_ctrl_t), GFP_KERNEL); + if (!vx6953_ctrl) { + CDBG("vx6953_init failed!\n"); + rc = -ENOMEM; + } + vx6953_ctrl->fps_divider = 1 * 0x00000400; + vx6953_ctrl->pict_fps_divider = 1 * 0x00000400; + vx6953_ctrl->set_test = TEST_OFF; + vx6953_ctrl->prev_res = QTR_SIZE; + vx6953_ctrl->pict_res = FULL_SIZE; + vx6953_ctrl->curr_res = INVALID_SIZE; + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_2; + vx6953_ctrl->edof_mode = VX6953_EDOF_ESTIMATION; + + if (data) + vx6953_ctrl->sensordata = data; + + if (vx6953_i2c_read(0x0002, &revision_number, 1) < 0) + return rc; + CDBG("sensor revision number major = 0x%x\n", revision_number); + if (vx6953_i2c_read(0x0018, &revision_number, 1) < 0) + return rc; + CDBG("sensor revision number = 0x%x\n", revision_number); + if (revision_number == VX6953_REVISION_NUMBER_CUT3) { + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_3; + CDBG("VX6953 EDof Cut 3.0 sensor\n "); + } else if (revision_number == VX6953_REVISION_NUMBER_CUT2) { + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_2; + CDBG("VX6953 EDof Cut 2.0 sensor\n "); + } else {/* Cut1.0 reads 0x00 for register 0x0018*/ + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_1; + CDBG("VX6953 EDof Cut 1.0 sensor\n "); + } + + if (vx6953_ctrl->prev_res == QTR_SIZE) { + if (vx6953_sensor_setting(REG_INIT, RES_PREVIEW) < 0) + goto init_probe_fail; + } else { + if (vx6953_sensor_setting(REG_INIT, RES_CAPTURE) < 0) + goto init_probe_fail; + } + + goto init_probe_done; +init_probe_fail: + CDBG(" vx6953_probe_init_sensor fails\n"); + gpio_direction_output(data->sensor_reset, 0); + vx6953_probe_init_done(data); +init_probe_done: + CDBG(" vx6953_probe_init_sensor finishes\n"); + return rc; + } +/* camsensor_iu060f_vx6953_reset */ +int vx6953_sensor_open_init(const struct msm_camera_sensor_info *data) +{ + unsigned short revision_number; + int32_t rc = 0; + + CDBG("%s: %d\n", __func__, __LINE__); + CDBG("Calling vx6953_sensor_open_init\n"); + rc = gpio_request(data->sensor_reset, "vx6953"); + if (!rc) + CDBG("vx6953 gpio_request fail\n"); + + vx6953_ctrl = kzalloc(sizeof(struct vx6953_ctrl_t), GFP_KERNEL); + if (!vx6953_ctrl) { + CDBG("vx6953_init failed!\n"); + rc = -ENOMEM; + goto init_done; + } + vx6953_ctrl->fps_divider = 1 * 0x00000400; + vx6953_ctrl->pict_fps_divider = 1 * 0x00000400; + vx6953_ctrl->set_test = TEST_OFF; + vx6953_ctrl->prev_res = QTR_SIZE; + vx6953_ctrl->pict_res = FULL_SIZE; + vx6953_ctrl->curr_res = INVALID_SIZE; + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_2; + vx6953_ctrl->edof_mode = VX6953_EDOF_ESTIMATION; + if (data) + vx6953_ctrl->sensordata = data; + if (rc < 0) { + CDBG("Calling vx6953_sensor_open_init fail1\n"); + return rc; + } + CDBG("%s: %d\n", __func__, __LINE__); + /* enable mclk first */ + msm_camio_clk_rate_set(VX6953_STM5M0EDOF_DEFAULT_MASTER_CLK_RATE); + CDBG("%s: %d\n", __func__, __LINE__); + if (vx6953_i2c_read(0x0002, &revision_number, 1) < 0) + return rc; + CDBG("sensor revision number major = 0x%x\n", revision_number); + if (vx6953_i2c_read(0x0018, &revision_number, 1) < 0) + return rc; + CDBG("sensor revision number = 0x%x\n", revision_number); + if (revision_number == VX6953_REVISION_NUMBER_CUT3) { + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_3; + CDBG("VX6953 EDof Cut 3.0 sensor\n "); + } else if (revision_number == VX6953_REVISION_NUMBER_CUT2) { + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_2; + CDBG("VX6953 EDof Cut 2.0 sensor\n "); + } else {/* Cut1.0 reads 0x00 for register 0x0018*/ + vx6953_ctrl->sensor_type = VX6953_STM5M0EDOF_CUT_1; + CDBG("VX6953 EDof Cut 1.0 sensor\n "); + } + + vx6953_ctrl->fps = 30*Q8; + if (rc < 0) + goto init_fail; + else + goto init_done; +init_fail: + CDBG("init_fail\n"); + gpio_direction_output(data->sensor_reset, 0); + vx6953_probe_init_done(data); +init_done: + CDBG("init_done\n"); + return rc; +} /*endof vx6953_sensor_open_init*/ + +static int vx6953_init_client(struct i2c_client *client) +{ + /* Initialize the MSM_CAMI2C Chip */ + init_waitqueue_head(&vx6953_wait_queue); + return 0; +} + +static const struct i2c_device_id vx6953_i2c_id[] = { + {"vx6953", 0}, + { } +}; + +static int vx6953_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc = 0; + CDBG("vx6953_probe called!\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + CDBG("i2c_check_functionality failed\n"); + goto probe_failure; + } + + vx6953_sensorw = kzalloc(sizeof(struct vx6953_work_t), GFP_KERNEL); + if (!vx6953_sensorw) { + CDBG("kzalloc failed.\n"); + rc = -ENOMEM; + goto probe_failure; + } + + i2c_set_clientdata(client, vx6953_sensorw); + vx6953_init_client(client); + vx6953_client = client; + + msleep(50); + + CDBG("vx6953_probe successed! rc = %d\n", rc); + return 0; + +probe_failure: + CDBG("vx6953_probe failed! rc = %d\n", rc); + return rc; +} + +static int vx6953_send_wb_info(struct wb_info_cfg *wb) +{ + unsigned short read_data; + uint8_t temp[8]; + int rc = 0; + int i = 0; + + /* red_gain */ + temp[2] = wb->red_gain >> 8; + temp[3] = wb->red_gain & 0xFF; + + /* green_gain */ + temp[0] = wb->green_gain >> 8; + temp[1] = wb->green_gain & 0xFF; + temp[6] = temp[0]; + temp[7] = temp[1]; + + /* blue_gain */ + temp[4] = wb->blue_gain >> 8; + temp[5] = wb->blue_gain & 0xFF; + rc = vx6953_i2c_write_seq_sensor(0x0B8E, &temp[0], 8); + + for (i = 0; i < 6; i++) { + rc = vx6953_i2c_read(0x0B8E + i, &read_data, 1); + CDBG("%s addr 0x%x val %d\n", __func__, 0x0B8E + i, read_data); + } + rc = vx6953_i2c_read(0x0B82, &read_data, 1); + CDBG("%s addr 0x%x val %d\n", __func__, 0x0B82, read_data); + if (rc < 0) + return rc; + return rc; +} /*end of vx6953_snapshot_config*/ + +static int __exit vx6953_remove(struct i2c_client *client) +{ + struct vx6953_work_t_t *sensorw = i2c_get_clientdata(client); + free_irq(client->irq, sensorw); + vx6953_client = NULL; + kfree(sensorw); + return 0; +} + +static struct i2c_driver vx6953_i2c_driver = { + .id_table = vx6953_i2c_id, + .probe = vx6953_i2c_probe, + .remove = __exit_p(vx6953_i2c_remove), + .driver = { + .name = "vx6953", + }, +}; + +static int vx6953_sensor_config(void __user *argp) +{ + struct sensor_cfg_data cdata; + long rc = 0; + if (copy_from_user(&cdata, + (void *)argp, + sizeof(struct sensor_cfg_data))) + return -EFAULT; + mutex_lock(&vx6953_mut); + CDBG("vx6953_sensor_config: cfgtype = %d\n", + cdata.cfgtype); + switch (cdata.cfgtype) { + case CFG_GET_PICT_FPS: + vx6953_get_pict_fps( + cdata.cfg.gfps.prevfps, + &(cdata.cfg.gfps.pictfps)); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_L_PF: + cdata.cfg.prevl_pf = + vx6953_get_prev_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PREV_P_PL: + cdata.cfg.prevp_pl = + vx6953_get_prev_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_L_PF: + cdata.cfg.pictl_pf = + vx6953_get_pict_lines_pf(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_P_PL: + cdata.cfg.pictp_pl = + vx6953_get_pict_pixels_pl(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_GET_PICT_MAX_EXP_LC: + cdata.cfg.pict_max_exp_lc = + vx6953_get_pict_max_exp_lc(); + + if (copy_to_user((void *)argp, + &cdata, + sizeof(struct sensor_cfg_data))) + rc = -EFAULT; + break; + + case CFG_SET_FPS: + case CFG_SET_PICT_FPS: + rc = vx6953_set_fps(&(cdata.cfg.fps)); + break; + + case CFG_SET_EXP_GAIN: + rc = + vx6953_write_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_PICT_EXP_GAIN: + rc = + vx6953_set_pict_exp_gain( + cdata.cfg.exp_gain.gain, + cdata.cfg.exp_gain.line); + break; + + case CFG_SET_MODE: + rc = vx6953_set_sensor_mode(cdata.mode, + cdata.rs); + break; + + case CFG_PWR_DOWN: + rc = vx6953_power_down(); + break; + + case CFG_MOVE_FOCUS: + rc = + vx6953_move_focus( + cdata.cfg.focus.dir, + cdata.cfg.focus.steps); + break; + + case CFG_SET_DEFAULT_FOCUS: + rc = + vx6953_set_default_focus( + cdata.cfg.focus.steps); + break; + + case CFG_SET_EFFECT: + rc = vx6953_set_default_focus( + cdata.cfg.effect); + break; + + + case CFG_SEND_WB_INFO: + rc = vx6953_send_wb_info( + &(cdata.cfg.wb_info)); + break; + + default: + rc = -EFAULT; + break; + } + + mutex_unlock(&vx6953_mut); + + return rc; +} + + + + +static int vx6953_sensor_release(void) +{ + int rc = -EBADF; + mutex_lock(&vx6953_mut); + vx6953_power_down(); + gpio_free(vx6953_ctrl->sensordata->sensor_reset); + kfree(vx6953_ctrl); + vx6953_ctrl = NULL; + CDBG("vx6953_release completed\n"); + mutex_unlock(&vx6953_mut); + + return rc; +} + +static int vx6953_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + /* TODO: Need to add this ID in v4l2-chip-ident.h */ + id->ident = V4L2_IDENT_VX6953; + id->revision = 0; + + return 0; +} + +static int vx6953_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *param) +{ + int ret = 0; + /* return current mode value */ + param->parm.capture.capturemode = vx6953_ctrl->sensormode; + return ret; +} + +static int vx6953_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *param) +{ + /* set the desired mode */ + /* right now, the only purpose is to set the desired mode - + preview or snapshot */ + vx6953_ctrl->sensormode = param->parm.capture.capturemode; + return 0; +} + +static int vx6953_s_stream(struct v4l2_subdev *sd, int enable) +{ + long rc = 0; + int mode = vx6953_ctrl->sensormode; + int rt = RES_PREVIEW; + unsigned short frame_cnt; + struct msm_camera_csi_params vx6953_csi_params; + + CDBG("mode = %d, enable = %d\n", mode, enable); + + if (!enable) { + /* turn off streaming */ + /* TODO: Make call to I2C write to turn streaming off */ + /* rc = vx6953_i2c_write_b_sensor(); */ + + struct vx6953_i2c_reg_conf init_tbl[] = { + {REG_0x0112, + vx6953_regs.reg_pat_init[0].reg_0x0112}, + {0x6003, 0x01}, + {REG_0x0113, + vx6953_regs.reg_pat_init[0].reg_0x0113}, + {REG_VT_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + vt_pix_clk_div}, + {REG_PRE_PLL_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + pre_pll_clk_div}, + {REG_PLL_MULTIPLIER, + vx6953_regs.reg_pat_init[0]. + pll_multiplier}, + {REG_OP_PIX_CLK_DIV, + vx6953_regs.reg_pat_init[0]. + op_pix_clk_div}, + {REG_COARSE_INTEGRATION_TIME_HI, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_hi}, + {REG_COARSE_INTEGRATION_TIME_LO, + vx6953_regs.reg_pat[rt]. + coarse_integration_time_lo}, + {REG_ANALOGUE_GAIN_CODE_GLOBAL_LO, + vx6953_regs.reg_pat[rt]. + analogue_gain_code_global}, + {REG_0x3030, + vx6953_regs.reg_pat_init[0].reg_0x3030}, + /* 953 specific registers */ + {REG_0x0111, + vx6953_regs.reg_pat_init[0].reg_0x0111}, + {REG_0x0b00, + vx6953_regs.reg_pat_init[0].reg_0x0b00}, + {REG_0x3001, + vx6953_regs.reg_pat_init[0].reg_0x3001}, + {REG_0x3004, + vx6953_regs.reg_pat_init[0].reg_0x3004}, + {0x3006, 0x00}, + {REG_0x3007, + vx6953_regs.reg_pat_init[0].reg_0x3007}, + {0x301b, 0x29}, + /* DEFCOR settings */ + /*Single Defect Correction Weight DISABLE*/ + {0x0b06, + vx6953_regs.reg_pat_init[0].reg_0x0b06}, + /*Single_defect_correct_weight = auto*/ + {0x0b07, + vx6953_regs.reg_pat_init[0].reg_0x0b07}, + /*Dynamic couplet correction ENABLED*/ + {0x0b08, + vx6953_regs.reg_pat_init[0].reg_0x0b08}, + /*Dynamic couplet correction weight*/ + {0x0b09, + vx6953_regs.reg_pat_init[0].reg_0x0b09}, + /* Clock Setup */ + /* Tell sensor ext clk is 24MHz*/ + {REG_0x0136, + vx6953_regs.reg_pat_init[0].reg_0x0136}, + {REG_0x0137, + vx6953_regs.reg_pat_init[0].reg_0x0137}, + /* The white balance gains must be written + to the sensor every frame. */ + /* Edof */ + {REG_0x0b83, + vx6953_regs.reg_pat_init[0].reg_0x0b83}, + {REG_0x0b84, + vx6953_regs.reg_pat_init[0].reg_0x0b84}, + {REG_0x0b85, + vx6953_regs.reg_pat_init[0].reg_0x0b85}, + {REG_0x0b88, + vx6953_regs.reg_pat_init[0].reg_0x0b88}, + {REG_0x0b89, + vx6953_regs.reg_pat_init[0].reg_0x0b89}, + {REG_0x0b8a, + vx6953_regs.reg_pat_init[0].reg_0x0b8a}, + /* Mode specific regieters */ + {REG_FRAME_LENGTH_LINES_HI, + vx6953_regs.reg_pat[rt]. + frame_length_lines_hi}, + {REG_FRAME_LENGTH_LINES_LO, + vx6953_regs.reg_pat[rt]. + frame_length_lines_lo}, + {REG_LINE_LENGTH_PCK_HI, + vx6953_regs.reg_pat[rt]. + line_length_pck_hi}, + {REG_LINE_LENGTH_PCK_LO, + vx6953_regs.reg_pat[rt]. + line_length_pck_lo}, + {REG_0x3005, + vx6953_regs.reg_pat[rt].reg_0x3005}, + {0x3010, + vx6953_regs.reg_pat[rt].reg_0x3010}, + {REG_0x3011, + vx6953_regs.reg_pat[rt].reg_0x3011}, + {REG_0x301a, + vx6953_regs.reg_pat[rt].reg_0x301a}, + {REG_0x3035, + vx6953_regs.reg_pat[rt].reg_0x3035}, + {REG_0x3036, + vx6953_regs.reg_pat[rt].reg_0x3036}, + {REG_0x3041, + vx6953_regs.reg_pat[rt].reg_0x3041}, + {0x3042, + vx6953_regs.reg_pat[rt].reg_0x3042}, + {REG_0x3045, + vx6953_regs.reg_pat[rt].reg_0x3045}, + /*EDOF: Estimation settings for Preview mode + Application settings for capture mode + (standard settings - Not tuned) */ + {REG_0x0b80, + vx6953_regs.reg_pat[rt].reg_0x0b80}, + {REG_0x0900, + vx6953_regs.reg_pat[rt].reg_0x0900}, + {REG_0x0901, + vx6953_regs.reg_pat[rt].reg_0x0901}, + {REG_0x0902, + vx6953_regs.reg_pat[rt].reg_0x0902}, + {REG_0x0383, + vx6953_regs.reg_pat[rt].reg_0x0383}, + {REG_0x0387, + vx6953_regs.reg_pat[rt].reg_0x0387}, + /* Change output size / frame rate */ + {REG_0x034c, + vx6953_regs.reg_pat[rt].reg_0x034c}, + {REG_0x034d, + vx6953_regs.reg_pat[rt].reg_0x034d}, + {REG_0x034e, + vx6953_regs.reg_pat[rt].reg_0x034e}, + {REG_0x034f, + vx6953_regs.reg_pat[rt].reg_0x034f}, + }; + /* reset fps_divider */ + vx6953_ctrl->fps = 30 * Q8; + /* stop streaming */ + + /* Reset everything first */ + if (vx6953_i2c_write_b_sensor(0x103, 0x01) < 0) { + CDBG("S/W reset failed\n"); + return rc; + } else + CDBG("S/W reset successful\n"); + + msleep(10); + + CDBG("Init vx6953_sensor_setting standby\n"); + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STANDBY_MODE) < 0) + return rc; + + /*vx6953_stm5m0edof_delay_msecs_stdby*/ + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + vx6953_csi_params.data_format = CSI_8BIT; + vx6953_csi_params.lane_cnt = 1; + vx6953_csi_params.lane_assign = 0xe4; + vx6953_csi_params.dpcm_scheme = 0; + vx6953_csi_params.settle_cnt = 7; + rc = msm_camio_csi_config(&vx6953_csi_params); + if (rc < 0) + CDBG(" config csi controller failed\n"); + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + vx6953_patch_for_cut3(); + rc = vx6953_i2c_write_w_table(&init_tbl[0], + ARRAY_SIZE(init_tbl)); + if (rc < 0) + return rc; + + msleep(vx6953_stm5m0edof_delay_msecs_stdby); + + vx6953_i2c_write_b_sensor(0x0b80, 0x00); + vx6953_i2c_write_b_sensor(0x3388, 0x03); + vx6953_i2c_write_b_sensor(0x3640, 0x00); + return rc; + } else { + /* Start sensor streaming */ + if (vx6953_i2c_write_b_sensor(REG_MODE_SELECT, + MODE_SELECT_STREAM) < 0) + return rc; + CDBG("Init vx6953_sensor_setting stream\n"); + msleep(vx6953_stm5m0edof_delay_msecs_stream); + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + + rc = vx6953_i2c_write_w_table(&edof_tbl[0], + ARRAY_SIZE(edof_tbl)); + vx6953_i2c_write_b_sensor(0x3388, 0x00); + + while (frame_cnt == 0xFF) { + if (vx6953_i2c_read(0x0005, &frame_cnt, 1) < 0) + return rc; + CDBG("frame_cnt=%d", frame_cnt); + msleep(10); + } + + /* set desired mode */ + switch (mode) { + case SENSOR_PREVIEW_MODE: + CDBG("SENSOR_PREVIEW_MODE\n"); + rc = vx6953_video_config(mode); + break; + case SENSOR_SNAPSHOT_MODE: + CDBG("SENSOR_SNAPSHOT_MODE\n"); + rc = vx6953_snapshot_config(mode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + CDBG("SENSOR_RAW_SNAPSHOT_MODE\n"); + rc = vx6953_raw_snapshot_config(mode); + break; + default: + CDBG("default\n"); + return -EINVAL; + } + } + + return 0; +} + +static void vx6953_frame_check(u32 *width, u32 *height) +{ + /* get mode first */ + int mode = vx6953_ctrl->sensormode; + + switch (mode) { + case SENSOR_PREVIEW_MODE: + if (*width > VX6953_QTR_SIZE_WIDTH) + *width = VX6953_QTR_SIZE_WIDTH; + + if (*height > VX6953_QTR_SIZE_HEIGHT) + *height = VX6953_QTR_SIZE_HEIGHT; + break; + case SENSOR_SNAPSHOT_MODE: + case SENSOR_RAW_SNAPSHOT_MODE: + if (*width > VX6953_HRZ_FULL_BLK_PIXELS) + *width = VX6953_HRZ_FULL_BLK_PIXELS; + + if (*height > VX6953_VER_FULL_BLK_LINES) + *height = VX6953_VER_FULL_BLK_LINES; + break; + default: + break; + } +} + + +static int vx6953_set_params(struct i2c_client *client, u32 width, u32 height, + enum v4l2_mbus_pixelcode code) +{ + int i; + vx6953_ctrl->fmt = NULL; + + /* + * frame size check + */ + vx6953_frame_check(&width, &height); + + /* + * get color format + */ + for (i = 0; i < ARRAY_SIZE(vx6953_cfmts); i++) + if (vx6953_cfmts[i].code == code) + break; + if (i == ARRAY_SIZE(vx6953_cfmts)) + return -EINVAL; + + /* sensor supports one fixed size depending upon the mode */ + switch (vx6953_ctrl->sensormode) { + case SENSOR_PREVIEW_MODE: + vx6953_video_config(vx6953_ctrl->sensormode); + break; + case SENSOR_SNAPSHOT_MODE: + vx6953_snapshot_config(vx6953_ctrl->sensormode); + break; + case SENSOR_RAW_SNAPSHOT_MODE: + vx6953_raw_snapshot_config(vx6953_ctrl->sensormode); + break; + default: + return -EINVAL; + } + + /* why need this ? vx6953_ctrl->fmt = &(vx6953_cfmts[i]); */ + + return 0; +} + +static int vx6953_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + /* right now we are not supporting, probably vfe can take care */ + return -EINVAL; +} + +static int vx6953_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + return -EINVAL; +} + +static int vx6953_s_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + return -EINVAL; +} + +static int vx6953_g_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + /* by this time vx6953_client should already be set */ + struct i2c_client *client = vx6953_client; + + /* currently sensor supports fixed dimensions only + * depending upon the mode*/ + if (!vx6953_ctrl->fmt) { + int ret = vx6953_set_params(client, VX6953_QTR_SIZE_WIDTH, + VX6953_QTR_SIZE_HEIGHT, + V4L2_MBUS_FMT_YUYV8_2X8); + if (ret < 0) + return ret; + } + + mf->width = vx6953_get_pict_pixels_pl(); + mf->height = vx6953_get_pict_lines_pf(); + /* TODO: set colorspace */ + mf->code = vx6953_ctrl->fmt->code; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int vx6953_s_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + /* by this time vx6953_client should already be set */ + struct i2c_client *client = vx6953_client; + + /* TODO: We need to define this function */ + /* TODO: set colorspace */ + return vx6953_set_params(client, mf->width, mf->height, mf->code); +} + +static int vx6953_try_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vx6953_cfmts); i++) + if (mf->code == vx6953_cfmts[i].code) + break; + + if (i == ARRAY_SIZE(vx6953_cfmts)) + return -EINVAL; + + /* check that frame is within max sensor supported frame size */ + vx6953_frame_check(&mf->width, &mf->height); + + /* TODO: set colorspace */ + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int vx6953_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + printk(KERN_DEBUG "Index is %d\n", index); + if ((unsigned int)index >= ARRAY_SIZE(vx6953_cfmts)) + return -EINVAL; + + *code = vx6953_cfmts[index].code; + return 0; +} + +static struct v4l2_subdev_core_ops vx6953_subdev_core_ops = { + .g_chip_ident = vx6953_g_chip_ident, +}; + +static struct v4l2_subdev_video_ops vx6953_subdev_video_ops = { + .g_parm = vx6953_g_parm, + .s_parm = vx6953_s_parm, + .s_stream = vx6953_s_stream, + .g_mbus_fmt = vx6953_g_fmt, + .s_mbus_fmt = vx6953_s_fmt, + .try_mbus_fmt = vx6953_try_fmt, + .cropcap = vx6953_cropcap, + .g_crop = vx6953_g_crop, + .s_crop = vx6953_s_crop, + .enum_mbus_fmt = vx6953_enum_fmt, +}; + +static struct v4l2_subdev_ops vx6953_subdev_ops = { + .core = &vx6953_subdev_core_ops, + .video = &vx6953_subdev_video_ops, +}; + +static int vx6953_sensor_probe(const struct msm_camera_sensor_info *info, + struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = i2c_add_driver(&vx6953_i2c_driver); + if (rc < 0 || vx6953_client == NULL) { + rc = -ENOTSUPP; + goto probe_fail; + } + msm_camio_clk_rate_set(24000000); + rc = vx6953_probe_init_sensor(info); + if (rc < 0) + goto probe_fail; + s->s_init = vx6953_sensor_open_init; + s->s_release = vx6953_sensor_release; + s->s_config = vx6953_sensor_config; + vx6953_probe_init_done(info); + return rc; + +probe_fail: + CDBG("vx6953_sensor_probe: SENSOR PROBE FAILS!\n"); + return rc; +} + + +static int vx6953_sensor_probe_cb(const struct msm_camera_sensor_info *info, + struct v4l2_subdev *sdev, struct msm_sensor_ctrl *s) +{ + int rc = 0; + rc = vx6953_sensor_probe(info, s); + if (rc < 0) + return rc; + + vx6953_ctrl = kzalloc(sizeof(struct vx6953_ctrl_t), GFP_KERNEL); + if (!vx6953_ctrl) { + CDBG("vx6953_sensor_probe failed!\n"); + return -ENOMEM; + } + + /* probe is successful, init a v4l2 subdevice */ + printk(KERN_DEBUG "going into v4l2_i2c_subdev_init\n"); + if (sdev) { + v4l2_i2c_subdev_init(sdev, vx6953_client, + &vx6953_subdev_ops); + vx6953_ctrl->sensor_dev = sdev; + } + return rc; +} + +static int __vx6953_probe(struct platform_device *pdev) +{ + return msm_sensor_register(pdev, vx6953_sensor_probe_cb); +} + +static struct platform_driver msm_camera_driver = { + .probe = __vx6953_probe, + .driver = { + .name = "msm_camera_vx6953", + .owner = THIS_MODULE, + }, +}; + +static int __init vx6953_init(void) +{ + return platform_driver_register(&msm_camera_driver); +} + +module_init(vx6953_init); +void vx6953_exit(void) +{ + i2c_del_driver(&vx6953_i2c_driver); +} + + diff --git a/drivers/media/video/msm/vx6953_v4l2.h b/drivers/media/video/msm/vx6953_v4l2.h new file mode 100644 index 00000000000..e5428e99192 --- /dev/null +++ b/drivers/media/video/msm/vx6953_v4l2.h @@ -0,0 +1,136 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VX6953_V4L2_H +#define VX6953_V4L2_H +#include +#include +extern struct vx6953_reg vx6953_regs; +struct reg_struct_init { + uint8_t reg_0x0112; /* 0x0112*/ + uint8_t reg_0x0113; /* 0x0113*/ + uint8_t vt_pix_clk_div; /* 0x0301*/ + uint8_t pre_pll_clk_div; /* 0x0305*/ + uint8_t pll_multiplier; /* 0x0307*/ + uint8_t op_pix_clk_div; /* 0x0309*/ + uint8_t reg_0x3030; /*0x3030*/ + uint8_t reg_0x0111; /*0x0111*/ + uint8_t reg_0x0b00; /*0x0b00*/ + uint8_t reg_0x3001; /*0x3001*/ + uint8_t reg_0x3004; /*0x3004*/ + uint8_t reg_0x3007; /*0x3007*/ + uint8_t reg_0x3016; /*0x3016*/ + uint8_t reg_0x301d; /*0x301d*/ + uint8_t reg_0x317e; /*0x317E*/ + uint8_t reg_0x317f; /*0x317F*/ + uint8_t reg_0x3400; /*0x3400*/ + uint8_t reg_0x0b06; /*0x0b06*/ + uint8_t reg_0x0b07; /*0x0b07*/ + uint8_t reg_0x0b08; /*0x0b08*/ + uint8_t reg_0x0b09; /*0x0b09*/ + uint8_t reg_0x0136; + uint8_t reg_0x0137; + /* Edof */ + uint8_t reg_0x0b83; /*0x0b83*/ + uint8_t reg_0x0b84; /*0x0b84*/ + uint8_t reg_0x0b85; /*0x0b85*/ + uint8_t reg_0x0b88; /*0x0b88*/ + uint8_t reg_0x0b89; /*0x0b89*/ + uint8_t reg_0x0b8a; /*0x0b8a*/ + }; +struct reg_struct { + uint8_t coarse_integration_time_hi; /*REG_COARSE_INTEGRATION_TIME_HI*/ + uint8_t coarse_integration_time_lo; /*REG_COARSE_INTEGRATION_TIME_LO*/ + uint8_t analogue_gain_code_global; + uint8_t frame_length_lines_hi; /* 0x0340*/ + uint8_t frame_length_lines_lo; /* 0x0341*/ + uint8_t line_length_pck_hi; /* 0x0342*/ + uint8_t line_length_pck_lo; /* 0x0343*/ + uint8_t reg_0x3005; /* 0x3005*/ + uint8_t reg_0x3010; /* 0x3010*/ + uint8_t reg_0x3011; /* 0x3011*/ + uint8_t reg_0x301a; /* 0x301a*/ + uint8_t reg_0x3035; /* 0x3035*/ + uint8_t reg_0x3036; /* 0x3036*/ + uint8_t reg_0x3041; /*0x3041*/ + uint8_t reg_0x3042; /*0x3042*/ + uint8_t reg_0x3045; /*0x3045*/ + uint8_t reg_0x0b80; /* 0x0b80*/ + uint8_t reg_0x0900; /*0x0900*/ + uint8_t reg_0x0901; /* 0x0901*/ + uint8_t reg_0x0902; /*0x0902*/ + uint8_t reg_0x0383; /*0x0383*/ + uint8_t reg_0x0387; /* 0x0387*/ + uint8_t reg_0x034c; /* 0x034c*/ + uint8_t reg_0x034d; /*0x034d*/ + uint8_t reg_0x034e; /* 0x034e*/ + uint8_t reg_0x034f; /* 0x034f*/ + uint8_t reg_0x1716; /*0x1716*/ + uint8_t reg_0x1717; /*0x1717*/ + uint8_t reg_0x1718; /*0x1718*/ + uint8_t reg_0x1719; /*0x1719*/ + uint8_t reg_0x3210;/*0x3210*/ + uint8_t reg_0x111; /*0x111*/ + uint8_t reg_0x3410; /*0x3410*/ + uint8_t reg_0x3098; + uint8_t reg_0x309D; + uint8_t reg_0x0200; + uint8_t reg_0x0201; + }; +struct vx6953_i2c_reg_conf { + unsigned short waddr; + unsigned short wdata; +}; + +enum vx6953_test_mode_t { + TEST_OFF, + TEST_1, + TEST_2, + TEST_3 +}; + +enum vx6953_resolution_t { + QTR_SIZE, + FULL_SIZE, + INVALID_SIZE +}; +enum vx6953_setting { + RES_PREVIEW, + RES_CAPTURE +}; +enum mt9p012_reg_update { + /* Sensor egisters that need to be updated during initialization */ + REG_INIT, + /* Sensor egisters that needs periodic I2C writes */ + UPDATE_PERIODIC, + /* All the sensor Registers will be updated */ + UPDATE_ALL, + /* Not valid update */ + UPDATE_INVALID +}; + +enum sensor_revision_t { + VX6953_STM5M0EDOF_CUT_1, + VX6953_STM5M0EDOF_CUT_2, + VX6953_STM5M0EDOF_CUT_3 +}; +enum edof_mode_t { + VX6953_EDOF_DISABLE, /* 0x00 */ + VX6953_EDOF_APPLICATION, /* 0x01 */ + VX6953_EDOF_ESTIMATION /* 0x02 */ +}; +struct vx6953_reg { + const struct reg_struct_init *reg_pat_init; + const struct reg_struct *reg_pat; +}; +#endif /* VX6953_H */ diff --git a/drivers/media/video/videobuf-core.c b/drivers/media/video/videobuf-core.c index de4fa4eb884..ac48afd82c4 100644 --- a/drivers/media/video/videobuf-core.c +++ b/drivers/media/video/videobuf-core.c @@ -330,6 +330,7 @@ static void videobuf_status(struct videobuf_queue *q, struct v4l2_buffer *b, break; case V4L2_MEMORY_USERPTR: b->m.userptr = vb->baddr; + b->reserved = vb->boff; b->length = vb->bsize; break; case V4L2_MEMORY_OVERLAY: @@ -600,6 +601,7 @@ int videobuf_qbuf(struct videobuf_queue *q, struct v4l2_buffer *b) buf->baddr != b->m.userptr) q->ops->buf_release(q, buf); buf->baddr = b->m.userptr; + buf->boff = b->reserved; break; case V4L2_MEMORY_OVERLAY: buf->boff = b->m.offset; @@ -1138,8 +1140,6 @@ unsigned int videobuf_poll_stream(struct file *file, buf = list_entry(q->stream.next, struct videobuf_buffer, stream); } else { - if (!q->reading) - __videobuf_read_start(q); if (!q->reading) { rc = POLLERR; } else if (NULL == q->read_buf) { diff --git a/drivers/media/video/videobuf-msm-mem.c b/drivers/media/video/videobuf-msm-mem.c new file mode 100644 index 00000000000..6f2cf9fdd5b --- /dev/null +++ b/drivers/media/video/videobuf-msm-mem.c @@ -0,0 +1,394 @@ +/* Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * Based on videobuf-dma-contig.c, + * (c) 2008 Magnus Damm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * helper functions for physically contiguous pmem capture buffers + * The functions support contiguous memory allocations using pmem + * kernel API. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAGIC_PMEM 0x0733ac64 +#define MAGIC_CHECK(is, should) \ + if (unlikely((is) != (should))) { \ + pr_err("magic mismatch: %x expected %x\n", (is), (should)); \ + BUG(); \ + } + +#ifdef CONFIG_MSM_CAMERA_DEBUG +#define D(fmt, args...) printk(KERN_DEBUG "videobuf-msm-mem: " fmt, ##args) +#else +#define D(fmt, args...) do {} while (0) +#endif + +static int32_t msm_mem_allocate(const size_t size) +{ + int32_t phyaddr; + phyaddr = allocate_contiguous_ebi_nomap(size, SZ_4K); + return phyaddr; +} + +static int32_t msm_mem_free(const int32_t phyaddr) +{ + int32_t rc = 0; + free_contiguous_memory_by_paddr(phyaddr); + return rc; +} + +static void +videobuf_vm_open(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + + D("vm_open %p [count=%u,vma=%08lx-%08lx]\n", + map, map->count, vma->vm_start, vma->vm_end); + + map->count++; +} + +static void videobuf_vm_close(struct vm_area_struct *vma) +{ + struct videobuf_mapping *map = vma->vm_private_data; + struct videobuf_queue *q = map->q; + int i, rc; + + D("vm_close %p [count=%u,vma=%08lx-%08lx]\n", + map, map->count, vma->vm_start, vma->vm_end); + + map->count--; + if (0 == map->count) { + struct videobuf_contig_pmem *mem; + + D("munmap %p q=%p\n", map, q); + mutex_lock(&q->vb_lock); + + /* We need first to cancel streams, before unmapping */ + if (q->streaming) + videobuf_queue_cancel(q); + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + + if (q->bufs[i]->map != map) + continue; + + mem = q->bufs[i]->priv; + if (mem) { + /* This callback is called only if kernel has + * allocated memory and this memory is mmapped. + * In this case, memory should be freed, + * in order to do memory unmap. + */ + + MAGIC_CHECK(mem->magic, MAGIC_PMEM); + + /* vfree is not atomic - can't be + called with IRQ's disabled + */ + D("buf[%d] freeing physical %d\n", + i, mem->phyaddr); + + rc = msm_mem_free(mem->phyaddr); + if (rc < 0) + D("%s: Invalid memory location\n", + __func__); + else { + mem->phyaddr = 0; + } + } + + q->bufs[i]->map = NULL; + q->bufs[i]->baddr = 0; + } + + kfree(map); + + mutex_unlock(&q->vb_lock); + + /* deallocate the q->bufs[i] structure not a good solution + as it will result in unnecessary iterations but right now + this looks like the only cleaner way */ + videobuf_mmap_free(q); + } +} + +static const struct vm_operations_struct videobuf_vm_ops = { + .open = videobuf_vm_open, + .close = videobuf_vm_close, +}; + +/** + * videobuf_pmem_contig_user_put() - reset pointer to user space buffer + * @mem: per-buffer private videobuf-contig-pmem data + * + * This function resets the user space pointer + */ +static void videobuf_pmem_contig_user_put(struct videobuf_contig_pmem *mem) +{ + put_pmem_file(mem->file); + mem->is_userptr = 0; + mem->phyaddr = 0; + mem->size = 0; +} + +/** + * videobuf_pmem_contig_user_get() - setup user space memory pointer + * @mem: per-buffer private videobuf-contig-pmem data + * @vb: video buffer to map + * + * This function validates and sets up a pointer to user space memory. + * Only physically contiguous pfn-mapped memory is accepted. + * + * Returns 0 if successful. + */ +static int videobuf_pmem_contig_user_get(struct videobuf_contig_pmem *mem, + struct videobuf_buffer *vb) +{ + unsigned long kvstart; + unsigned long len; + int rc; + + mem->size = PAGE_ALIGN(vb->size); + rc = get_pmem_file(vb->baddr, (unsigned long *)&mem->phyaddr, + &kvstart, &len, &mem->file); + if (rc < 0) { + pr_err("%s: get_pmem_file fd %lu error %d\n", + __func__, vb->baddr, + rc); + return rc; + } + mem->phyaddr += vb->boff; + mem->y_off = 0; + mem->cbcr_off = (vb->size)*2/3; + mem->is_userptr = 1; + return rc; +} + +static struct videobuf_buffer *__videobuf_alloc(size_t size) +{ + struct videobuf_contig_pmem *mem; + struct videobuf_buffer *vb; + + vb = kzalloc(size + sizeof(*mem), GFP_KERNEL); + if (vb) { + mem = vb->priv = ((char *)vb) + size; + mem->magic = MAGIC_PMEM; + } + + return vb; +} + +static void *__videobuf_to_vaddr(struct videobuf_buffer *buf) +{ + struct videobuf_contig_pmem *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_PMEM); + + return mem->vaddr; +} + +static int __videobuf_iolock(struct videobuf_queue *q, + struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf) +{ + int rc = 0; + struct videobuf_contig_pmem *mem = vb->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_PMEM); + + switch (vb->memory) { + case V4L2_MEMORY_MMAP: + D("%s memory method MMAP\n", __func__); + + /* All handling should be done by __videobuf_mmap_mapper() */ + break; + case V4L2_MEMORY_USERPTR: + D("%s memory method USERPTR\n", __func__); + + /* handle pointer from user space */ + rc = videobuf_pmem_contig_user_get(mem, vb); + break; + case V4L2_MEMORY_OVERLAY: + default: + pr_err("%s memory method OVERLAY/unknown\n", __func__); + rc = -EINVAL; + } + + return rc; +} + +static int __videobuf_mmap_mapper(struct videobuf_queue *q, + struct videobuf_buffer *buf, + struct vm_area_struct *vma) +{ + struct videobuf_contig_pmem *mem; + struct videobuf_mapping *map; + int retval; + unsigned long size; + + D("%s\n", __func__); + + /* create mapping + update buffer list */ + map = kzalloc(sizeof(struct videobuf_mapping), GFP_KERNEL); + if (!map) { + pr_err("%s: kzalloc failed.\n", __func__); + return -ENOMEM; + } + + buf->map = map; + map->q = q; + + buf->baddr = vma->vm_start; + + mem = buf->priv; + D("mem = 0x%x\n", (u32)mem); + D("buf = 0x%x\n", (u32)buf); + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_PMEM); + + mem->size = PAGE_ALIGN(buf->bsize); + mem->y_off = 0; + mem->cbcr_off = (buf->bsize)*2/3; + if (buf->i >= 0 && buf->i <= 3) + mem->buffer_type = OUTPUT_TYPE_P; + else + mem->buffer_type = OUTPUT_TYPE_V; + + buf->bsize = mem->size; + mem->phyaddr = msm_mem_allocate(mem->size); + + if (IS_ERR((void *)mem->phyaddr)) { + pr_err("%s : pmem memory allocation failed\n", __func__); + goto error; + } + + /* Try to remap memory */ + size = vma->vm_end - vma->vm_start; + size = (size < mem->size) ? size : mem->size; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + retval = remap_pfn_range(vma, vma->vm_start, + mem->phyaddr >> PAGE_SHIFT, + size, vma->vm_page_prot); + if (retval) { + pr_err("mmap: remap failed with error %d. ", retval); + retval = msm_mem_free(mem->phyaddr); + if (retval < 0) + printk(KERN_ERR "%s: Invalid memory location\n", + __func__); + else { + mem->phyaddr = 0; + } + goto error; + } + + vma->vm_ops = &videobuf_vm_ops; + vma->vm_flags |= VM_DONTEXPAND; + vma->vm_private_data = map; + + D("mmap %p: q=%p %08lx-%08lx (%lx) pgoff %08lx buf %d\n", + map, q, vma->vm_start, vma->vm_end, + (long int)buf->bsize, + vma->vm_pgoff, buf->i); + + videobuf_vm_open(vma); + + return 0; + +error: + kfree(map); + return -ENOMEM; +} + +static struct videobuf_qtype_ops qops = { + .magic = MAGIC_QTYPE_OPS, + + .alloc_vb = __videobuf_alloc, + .iolock = __videobuf_iolock, + .mmap_mapper = __videobuf_mmap_mapper, + .vaddr = __videobuf_to_vaddr, +}; + +void videobuf_queue_pmem_contig_init(struct videobuf_queue *q, + const struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv, + struct mutex *ext_lock) +{ + videobuf_queue_core_init(q, ops, dev, irqlock, type, field, msize, + priv, &qops, ext_lock); +} +EXPORT_SYMBOL_GPL(videobuf_queue_pmem_contig_init); + +int videobuf_to_pmem_contig(struct videobuf_buffer *buf) +{ + struct videobuf_contig_pmem *mem = buf->priv; + + BUG_ON(!mem); + MAGIC_CHECK(mem->magic, MAGIC_PMEM); + + return mem->phyaddr; +} +EXPORT_SYMBOL_GPL(videobuf_to_pmem_contig); + +int videobuf_pmem_contig_free(struct videobuf_queue *q, + struct videobuf_buffer *buf) +{ + struct videobuf_contig_pmem *mem = buf->priv; + + /* mmapped memory can't be freed here, otherwise mmapped region + would be released, while still needed. In this case, the memory + release should happen inside videobuf_vm_close(). + So, it should free memory only if the memory were allocated for + read() operation. + */ + if (buf->memory != V4L2_MEMORY_USERPTR) + return -EINVAL; + + if (!mem) + return -ENOMEM; + + MAGIC_CHECK(mem->magic, MAGIC_PMEM); + + /* handle user space pointer case */ + if (buf->baddr) { + videobuf_pmem_contig_user_put(mem); + return 0; + } else { + /* don't support read() method */ + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(videobuf_pmem_contig_free); + +MODULE_DESCRIPTION("helper module to manage video4linux PMEM contig buffers"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 37b83eb6d70..8f2c9ac8c60 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -145,6 +145,39 @@ config TPS65010 This driver can also be built as a module. If so, the module will be called tps65010. +config TPS65023 + tristate "TPS65023 Power Management chip" + depends on I2C && ARCH_MSM_SCORPION && !MSM_SMP + default y if I2C && ARCH_MSM_SCORPION && !MSM_SMP + help + Say yes here for Qualcomm QSD chips. The TI PMIC is used by the + QSD8x50 series of chips for power management. + +config PMIC8058 + tristate "PMIC8058 Power Management chip" + depends on I2C_SSBI && (ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_FSM9XXX) + default y if I2C_SSBI && (ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_FSM9XXX) + select MFD_CORE + select MSM_SHOW_RESUME_IRQ + help + Say yes here for Qualcomm PM8058 chip. + +config PMIC8901 + tristate "PMIC8901 Power Management chip" + depends on I2C_SSBI && ARCH_MSM8X60 + default y if I2C_SSBI && ARCH_MSM8X60 + select MFD_CORE + help + Say yes here for Qualcomm PM8901 chip. + +config MARIMBA_TSADC + tristate "Support for Marimba Touchscreen ADC" + depends on MARIMBA_CORE && ARCH_MSM7X30 + default y if MARIMBA_CORE + help + Say yes here if you want to include support for TSADC in the + Qualcomm Marimba chip. + config TPS6507X tristate "TPS6507x Power Management / Touch Screen chips" select MFD_CORE @@ -181,6 +214,33 @@ config MENELAUS and other features that are often used in portable devices like cell phones and PDAs. +config MARIMBA_CORE + tristate "Marimba Core" + depends on I2C && (ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_MSM7X27A) + default n + help + Enables the Marimba Core driver. The core driver provides + read/write capability to registers which are part of the + marimba core. + This driver dynamically detects the SoC and works for both + Marimba and Bahama Chip. + +config MARIMBA_CODEC + tristate "Marimba Codec" + depends on MARIMBA_CORE + default n + help + This driver programs Marimba Wideband Codec for input/output of + audio signal. + +config TIMPANI_CODEC + tristate "Timpani Codec" + depends on MARIMBA_CORE + default n + help + This driver programs Timpani Wideband Codec for input/output of + audio signal. + config TWL4030_CORE bool "Texas Instruments TWL4030/TWL5030/TWL6030/TPS659x0 Support" depends on I2C=y && GENERIC_HARDIRQS @@ -737,6 +797,63 @@ config MFD_TPS65910 config TPS65911_COMPARATOR tristate +config MFD_PM8921_ADC + tristate "Support for Qualcomm PM8921 ADC" + depends on MFD_PM8921_CORE + help + This is the ADC arbiter driver for Qualcomm PM8921 Chip. + + The driver supports reading the HKADC, XOADC and support to set and receive + temperature threshold notifications using the Battery temperature module. + +config MFD_PM8XXX_DEBUG + tristate "Qualcomm PM8xxx debugfs support" + depends on MFD_PM8XXX && DEBUG_FS + default y if MFD_PM8XXX + help + This driver provides a debugfs interface to the SSBI registers on + Qualcomm PM 8xxx PMIC chips. It allows for reads and writes to + arbitrary addresses. Writes are blocking so values are guaranteed to + be set into hardware registers upon return. + +config MFD_PM8XXX_PWM + tristate "Support for Qualcomm PM8xxx PWM feature" + depends on MFD_PM8XXX + default y if MFD_PM8XXX + help + This is the Pulse Width Modulation (PWM) driver for Qualcomm + PM 8xxx PMIC chips. It can drive 8 channels of PWM output, and + has a lookup table with size of 64 to be shared by any of the + 8 channels. + +config MFD_PM8XXX_MISC + tristate "Support for Qualcomm PM8xxx miscellaneous APIs" + depends on MFD_PM8XXX + default y if MFD_PM8XXX + help + This driver implements several miscellaneous APIs that may be needed + in order to control the PM8XXX PMIC chip. + +config MFD_PM8XXX_BATT_ALARM + tristate "Support for Qualcomm PM8xxx battery voltage alarm" + depends on MFD_PM8XXX + help + This driver provides a means monitor battery under and over-voltage + conditions. An upper and/or lower threshold can be specified for + normal operation. A wakeable interrupt is triggered when the battery + voltage leaves the accepatable range which then calls a notifier call + chain. + +config WCD9310_CODEC + tristate "WCD9310 Codec" + depends on SLIMBUS + select MFD_CORE + default n + help + Enables the WCD9310 core driver. The core driver provides + read/write capability to registers which are part of the + WCD9310 core and gives the ability to use the WCD9310 codec. + endif # MFD_SUPPORT menu "Multimedia Capabilities Port drivers" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 22a280fcb70..f66fc763c1f 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -35,12 +35,26 @@ obj-$(CONFIG_MFD_WM8994) += wm8994-core.o wm8994-irq.o obj-$(CONFIG_TPS6105X) += tps6105x.o obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_TPS6507X) += tps6507x.o +obj-$(CONFIG_MARIMBA_CODEC) += marimba-codec.o obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl-core.o twl4030-irq.o twl6030-irq.o obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o obj-$(CONFIG_MFD_TWL4030_AUDIO) += twl4030-audio.o + +obj-$(CONFIG_MARIMBA_CORE) += marimba-core.o +obj-$(CONFIG_MARIMBA_TSADC) += marimba-tsadc.o +obj-$(CONFIG_TPS65023) += tps65023.o + +obj-$(CONFIG_TIMPANI_CODEC) += timpani-codec.o + +ifdef CONFIG_TIMPANI_CODEC +obj-$(CONFIG_TIMPANI_CODEC) += msm-adie-codec.o +else ifdef CONFIG_MARIMBA_CODEC +obj-$(CONFIG_MARIMBA_CODEC) += msm-adie-codec.o +endif + obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o obj-$(CONFIG_TWL6040_CORE) += twl6040-core.o twl6040-irq.o @@ -55,6 +69,8 @@ obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.o obj-$(CONFIG_MCP_UCB1200_TS) += ucb1x00-ts.o +obj-$(CONFIG_WCD9310_CODEC) += wcd9310-core.o wcd9310-irq.o + ifeq ($(CONFIG_SA1100_ASSABET),y) obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o endif @@ -96,3 +112,12 @@ obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o obj-$(CONFIG_MFD_TPS65910) += tps65910.o tps65910-irq.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o +obj-$(CONFIG_PMIC8058) += pmic8058.o +obj-$(CONFIG_PMIC8901) += pmic8901.o +obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o +obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o +obj-$(CONFIG_MFD_PM8XXX_DEBUG) += pm8xxx-debug.o +obj-$(CONFIG_MFD_PM8XXX_PWM) += pm8xxx-pwm.o +obj-$(CONFIG_MFD_PM8XXX_MISC) += pm8xxx-misc.o +obj-$(CONFIG_MFD_PM8XXX_BATT_ALARM) += pm8xxx-batt-alarm.o +obj-$(CONFIG_MFD_PM8921_ADC) += pm8921-adc.o msmproc_adc.o diff --git a/drivers/mfd/marimba-codec.c b/drivers/mfd/marimba-codec.c new file mode 100644 index 00000000000..6416e0a1ed6 --- /dev/null +++ b/drivers/mfd/marimba-codec.c @@ -0,0 +1,963 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MARIMBA_CDC_RX_CTL 0x81 +#define MARIMBA_CDC_RX_CTL_ST_EN_MASK 0x20 +#define MARIMBA_CDC_RX_CTL_ST_EN_SHFT 0x5 +#define MARIMBA_CODEC_CDC_LRXG 0x84 +#define MARIMBA_CODEC_CDC_RRXG 0x85 +#define MARIMBA_CODEC_CDC_LTXG 0x86 +#define MARIMBA_CODEC_CDC_RTXG 0x87 + +#define MAX_MDELAY_US 2000 +#define MIN_MDELAY_US 1000 + +struct adie_codec_path { + struct adie_codec_dev_profile *profile; + struct adie_codec_register_image img; + u32 hwsetting_idx; + u32 stage_idx; + u32 curr_stage; +}; + +static struct adie_codec_register adie_codec_tx_regs[] = { + { 0x04, 0xc0, 0x8C }, + { 0x0D, 0xFF, 0x00 }, + { 0x0E, 0xFF, 0x00 }, + { 0x0F, 0xFF, 0x00 }, + { 0x10, 0xF8, 0x68 }, + { 0x11, 0xFE, 0x00 }, + { 0x12, 0xFE, 0x00 }, + { 0x13, 0xFF, 0x58 }, + { 0x14, 0xFF, 0x00 }, + { 0x15, 0xFE, 0x00 }, + { 0x16, 0xFF, 0x00 }, + { 0x1A, 0xFF, 0x00 }, + { 0x80, 0x01, 0x00 }, + { 0x82, 0x7F, 0x18 }, + { 0x83, 0x1C, 0x00 }, + { 0x86, 0xFF, 0xAC }, + { 0x87, 0xFF, 0xAC }, + { 0x89, 0xFF, 0xFF }, + { 0x8A, 0xF0, 0x30 } +}; + +static struct adie_codec_register adie_codec_rx_regs[] = { + { 0x23, 0xF8, 0x00 }, + { 0x24, 0x6F, 0x00 }, + { 0x25, 0x7F, 0x00 }, + { 0x26, 0xFC, 0x00 }, + { 0x28, 0xFE, 0x00 }, + { 0x29, 0xFE, 0x00 }, + { 0x33, 0xFF, 0x00 }, + { 0x34, 0xFF, 0x00 }, + { 0x35, 0xFC, 0x00 }, + { 0x36, 0xFE, 0x00 }, + { 0x37, 0xFE, 0x00 }, + { 0x38, 0xFE, 0x00 }, + { 0x39, 0xF0, 0x00 }, + { 0x3A, 0xFF, 0x0A }, + { 0x3B, 0xFC, 0xAC }, + { 0x3C, 0xFC, 0xAC }, + { 0x3D, 0xFF, 0x55 }, + { 0x3E, 0xFF, 0x55 }, + { 0x3F, 0xCF, 0x00 }, + { 0x40, 0x3F, 0x00 }, + { 0x41, 0x3F, 0x00 }, + { 0x42, 0xFF, 0x00 }, + { 0x43, 0xF7, 0x00 }, + { 0x43, 0xF7, 0x00 }, + { 0x43, 0xF7, 0x00 }, + { 0x43, 0xF7, 0x00 }, + { 0x44, 0xF7, 0x00 }, + { 0x45, 0xFF, 0x00 }, + { 0x46, 0xFF, 0x00 }, + { 0x47, 0xF7, 0x00 }, + { 0x48, 0xF7, 0x00 }, + { 0x49, 0xFF, 0x00 }, + { 0x4A, 0xFF, 0x00 }, + { 0x80, 0x02, 0x00 }, + { 0x81, 0xFF, 0x4C }, + { 0x83, 0x23, 0x00 }, + { 0x84, 0xFF, 0xAC }, + { 0x85, 0xFF, 0xAC }, + { 0x88, 0xFF, 0xFF }, + { 0x8A, 0x0F, 0x03 }, + { 0x8B, 0xFF, 0xAC }, + { 0x8C, 0x03, 0x01 }, + { 0x8D, 0xFF, 0x00 }, + { 0x8E, 0xFF, 0x00 } +}; + +static struct adie_codec_register adie_codec_lb_regs[] = { + { 0x2B, 0x8F, 0x02 }, + { 0x2C, 0x8F, 0x02 } +}; + +struct adie_codec_state { + struct adie_codec_path path[ADIE_CODEC_MAX]; + u32 ref_cnt; + struct marimba *pdrv_ptr; + struct marimba_codec_platform_data *codec_pdata; + struct mutex lock; +}; + +static struct adie_codec_state adie_codec; + +/* Array containing write details of Tx and RX Digital Volume + Tx and Rx and both the left and right channel use the same data +*/ +u8 adie_codec_rx_tx_dig_vol_data[] = { + 0x81, 0x82, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x8b, 0x8c, + 0x8d, 0x8e, 0x8f, 0x90, + 0x91, 0x92, 0x93, 0x94, + 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, + 0x9d, 0x9e, 0x9f, 0xa0, + 0xa1, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xaa, 0xab, 0xac, + 0xad, 0xae, 0xaf, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, + 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xc0, + 0xc1, 0xc2, 0xc3, 0xc4, + 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, + 0xcd, 0xce, 0xcf, 0xd0, + 0xd1, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xdb, 0xdc, + 0xdd, 0xde, 0xdf, 0xe0, + 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe7, 0xe8, + 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, + 0xfe, 0xff, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, + 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, 0x30, 0x31, + 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x41, + 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, + 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x60, 0x61, + 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0x7f +}; + +enum adie_vol_type { + ADIE_CODEC_RX_DIG_VOL, + ADIE_CODEC_TX_DIG_VOL, + ADIE_CODEC_VOL_TYPE_MAX +}; + +struct adie_codec_ch_vol_cntrl { + u8 codec_reg; + u8 codec_mask; + u8 *vol_cntrl_data; +}; + +struct adie_codec_vol_cntrl_data { + + enum adie_vol_type vol_type; + + /* Jump length used while doing writes in incremental fashion */ + u32 jump_length; + s32 min_mb; /* Min Db applicable to the vol control */ + s32 max_mb; /* Max Db applicable to the vol control */ + u32 step_in_mb; + u32 steps; /* No of steps allowed for this vol type */ + + struct adie_codec_ch_vol_cntrl *ch_vol_cntrl_info; +}; + +static struct adie_codec_ch_vol_cntrl adie_codec_rx_vol_cntrl[] = { + {MARIMBA_CODEC_CDC_LRXG, 0xff, adie_codec_rx_tx_dig_vol_data}, + {MARIMBA_CODEC_CDC_RRXG, 0xff, adie_codec_rx_tx_dig_vol_data} +}; + +static struct adie_codec_ch_vol_cntrl adie_codec_tx_vol_cntrl[] = { + {MARIMBA_CODEC_CDC_LTXG, 0xff, adie_codec_rx_tx_dig_vol_data}, + {MARIMBA_CODEC_CDC_RTXG, 0xff, adie_codec_rx_tx_dig_vol_data} +}; + +static struct adie_codec_vol_cntrl_data adie_codec_vol_cntrl[] = { + {ADIE_CODEC_RX_DIG_VOL, 5100, -12700, 12700, 100, 255, + adie_codec_rx_vol_cntrl}, + + {ADIE_CODEC_TX_DIG_VOL, 5100, -12700, 12700, 100, 255, + adie_codec_tx_vol_cntrl} +}; + +static int adie_codec_write(u8 reg, u8 mask, u8 val) +{ + int rc; + + rc = marimba_write_bit_mask(adie_codec.pdrv_ptr, reg, &val, 1, mask); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: fail to write reg %x\n", __func__, reg); + return -EIO; + } + + pr_debug("%s: write reg %x val %x\n", __func__, reg, val); + + return 0; +} + +static int adie_codec_read(u8 reg, u8 *val) +{ + return marimba_read(adie_codec.pdrv_ptr, reg, val, 1); +} + +static int adie_codec_read_dig_vol(enum adie_vol_type vol_type, u32 chan_index, + u32 *cur_index) +{ + u32 counter; + u32 size; + u8 reg, mask, cur_val; + int rc; + + reg = + adie_codec_vol_cntrl[vol_type]. + ch_vol_cntrl_info[chan_index].codec_reg; + + mask = + adie_codec_vol_cntrl[vol_type]. + ch_vol_cntrl_info[chan_index].codec_mask; + + rc = marimba_read(adie_codec.pdrv_ptr, reg, &cur_val, 1); + + if (IS_ERR_VALUE(rc)) { + pr_err("%s: fail to read reg %x\n", __func__, reg); + return -EIO; + } + + cur_val = cur_val & mask; + + pr_debug("%s: reg 0x%x mask 0x%x reg_value = 0x%x" + "vol_type = %d\n", __func__, reg, mask, cur_val, vol_type); + + size = adie_codec_vol_cntrl[vol_type].steps; + + for (counter = 0; counter <= size; counter++) { + + if (adie_codec_vol_cntrl[vol_type].ch_vol_cntrl_info + [chan_index].vol_cntrl_data[counter] == cur_val) { + *cur_index = counter; + return 0; + } + } + + pr_err("%s: could not find 0x%x in reg 0x%x values array\n", + __func__, cur_val, reg); + + return -EINVAL;; +} + +static int adie_codec_set_dig_vol(enum adie_vol_type vol_type, u32 chan_index, + u32 cur_index, u32 target_index) +{ + u32 count; + u8 reg, mask, val; + u32 i; + u32 index; + u32 index_jump; + + int rc; + + index_jump = adie_codec_vol_cntrl[vol_type].jump_length; + + reg = + adie_codec_vol_cntrl[vol_type]. + ch_vol_cntrl_info[chan_index].codec_reg; + + mask = + adie_codec_vol_cntrl[vol_type]. + ch_vol_cntrl_info[chan_index].codec_mask; + + /* compare the target index with current index */ + if (cur_index < target_index) { + + /* Volume is being increased loop and increase it in 4-5 steps + */ + count = ((target_index - cur_index) * 100 / index_jump); + index = cur_index; + + for (i = 1; i <= count; i++) { + index = index + (int)(index_jump / 100); + + val = + adie_codec_vol_cntrl[vol_type].ch_vol_cntrl_info + [chan_index].vol_cntrl_data[index]; + + pr_debug("%s: write reg %x val 0x%x\n", + __func__, reg, val); + + rc = adie_codec_write(reg, mask, val); + if (rc < 0) { + pr_err("%s: write reg %x val 0x%x failed\n", + __func__, reg, val); + return rc; + } + } + + /*do one final write to take it to the target index level */ + val = + adie_codec_vol_cntrl[vol_type].ch_vol_cntrl_info + [chan_index].vol_cntrl_data[target_index]; + + pr_debug("%s: write reg %x val 0x%x\n", __func__, reg, val); + + rc = adie_codec_write(reg, mask, val); + + if (rc < 0) { + pr_err("%s: write reg %x val 0x%x failed\n", + __func__, reg, val); + return rc; + } + + } else { + + /* Volume is being decreased from the current setting */ + index = cur_index; + /* loop and decrease it in 4-5 steps */ + count = ((cur_index - target_index) * 100 / index_jump); + + for (i = 1; i <= count; i++) { + index = index - (int)(index_jump / 100); + + val = + adie_codec_vol_cntrl[vol_type].ch_vol_cntrl_info + [chan_index].vol_cntrl_data[index]; + + pr_debug("%s: write reg %x val 0x%x\n", + __func__, reg, val); + + rc = adie_codec_write(reg, mask, val); + if (rc < 0) { + pr_err("%s: write reg %x val 0x%x failed\n", + __func__, reg, val); + return rc; + } + } + + /* do one final write to take it to the target index level */ + val = + adie_codec_vol_cntrl[vol_type].ch_vol_cntrl_info + [chan_index].vol_cntrl_data[target_index]; + + pr_debug("%s: write reg %x val 0x%x\n", __func__, reg, val); + + rc = adie_codec_write(reg, mask, val); + + if (rc < 0) { + pr_err("%s: write reg %x val 0x%x failed\n", + __func__, reg, val); + return rc; + } + } + return 0; +} + +static int marimba_adie_codec_set_device_digital_volume( + struct adie_codec_path *path_ptr, + u32 num_channels, u32 vol_percentage /* in percentage */) +{ + enum adie_vol_type vol_type; + s32 milli_bel; + u32 chan_index; + u32 step_index; + u32 cur_step_index = 0; + + if (!path_ptr || (path_ptr->curr_stage != + ADIE_CODEC_DIGITAL_ANALOG_READY)) { + pr_info("%s: Marimba codec not ready for volume control\n", + __func__); + return -EPERM; + } + + if (num_channels > 2) { + pr_err("%s: Marimba codec only supports max two channels\n", + __func__); + return -EINVAL; + } + + if (path_ptr->profile->path_type == ADIE_CODEC_RX) + vol_type = ADIE_CODEC_RX_DIG_VOL; + else if (path_ptr->profile->path_type == ADIE_CODEC_TX) + vol_type = ADIE_CODEC_TX_DIG_VOL; + else { + pr_err("%s: invalid device data neither RX nor TX\n", + __func__); + return -EINVAL; + } + + milli_bel = ((adie_codec_vol_cntrl[vol_type].max_mb - + adie_codec_vol_cntrl[vol_type].min_mb) * + vol_percentage) / 100; + + milli_bel = adie_codec_vol_cntrl[vol_type].min_mb + milli_bel; + + pr_debug("%s: milli bell = %d vol_type = %d vol_percentage = %d" + " num_cha = %d \n", + __func__, milli_bel, vol_type, vol_percentage, num_channels); + + + step_index = ((milli_bel + - adie_codec_vol_cntrl[vol_type].min_mb + + (adie_codec_vol_cntrl[vol_type].step_in_mb / 2)) + / adie_codec_vol_cntrl[vol_type].step_in_mb); + + + for (chan_index = 0; chan_index < num_channels; chan_index++) { + adie_codec_read_dig_vol(vol_type, chan_index, &cur_step_index); + + pr_debug("%s: cur_step_index = %u current vol = 0x%x\n", + __func__, cur_step_index, + adie_codec_vol_cntrl[vol_type].ch_vol_cntrl_info + [chan_index].vol_cntrl_data[cur_step_index]); + + pr_debug("%s: step index = %u new volume = 0x%x\n", + __func__, step_index, + adie_codec_vol_cntrl[vol_type].ch_vol_cntrl_info + [chan_index].vol_cntrl_data[step_index]); + + adie_codec_set_dig_vol(vol_type, chan_index, cur_step_index, + step_index); + + } + return 0; +} + +static int marimba_adie_codec_setpath(struct adie_codec_path *path_ptr, + u32 freq_plan, u32 osr) +{ + int rc = 0; + u32 i, freq_idx = 0, freq = 0; + + if ((path_ptr->curr_stage != ADIE_CODEC_DIGITAL_OFF) && + (path_ptr->curr_stage != ADIE_CODEC_FLASH_IMAGE)) { + rc = -EBUSY; + goto error; + } + + for (i = 0; i < path_ptr->profile->setting_sz; i++) { + if (path_ptr->profile->settings[i].osr == osr) { + if (path_ptr->profile->settings[i].freq_plan >= + freq_plan) { + if (freq == 0) { + freq = path_ptr->profile->settings[i]. + freq_plan; + freq_idx = i; + } else if (path_ptr->profile->settings[i]. + freq_plan < freq) { + freq = path_ptr->profile->settings[i]. + freq_plan; + freq_idx = i; + } + } + } + } + + if (freq_idx >= path_ptr->profile->setting_sz) + rc = -ENODEV; + else { + path_ptr->hwsetting_idx = freq_idx; + path_ptr->stage_idx = 0; + } + +error: + return rc; +} + +static u32 marimba_adie_codec_freq_supported( + struct adie_codec_dev_profile *profile, + u32 requested_freq) +{ + u32 i, rc = -EINVAL; + + for (i = 0; i < profile->setting_sz; i++) { + if (profile->settings[i].freq_plan >= requested_freq) { + rc = 0; + break; + } + } + return rc; +} + +static int marimba_adie_codec_enable_sidetone( + struct adie_codec_path *rx_path_ptr, + u32 enable) +{ + int rc = 0; + + pr_debug("%s()\n", __func__); + + mutex_lock(&adie_codec.lock); + + if (!rx_path_ptr || &adie_codec.path[ADIE_CODEC_RX] != rx_path_ptr) { + pr_err("%s: invalid path pointer\n", __func__); + rc = -EINVAL; + goto error; + } else if (rx_path_ptr->curr_stage != + ADIE_CODEC_DIGITAL_ANALOG_READY) { + pr_err("%s: bad state\n", __func__); + rc = -EPERM; + goto error; + } + + if (enable) + rc = adie_codec_write(MARIMBA_CDC_RX_CTL, + MARIMBA_CDC_RX_CTL_ST_EN_MASK, + (0x1 << MARIMBA_CDC_RX_CTL_ST_EN_SHFT)); + else + rc = adie_codec_write(MARIMBA_CDC_RX_CTL, + MARIMBA_CDC_RX_CTL_ST_EN_MASK, 0); + +error: + mutex_unlock(&adie_codec.lock); + return rc; +} + +static void adie_codec_reach_stage_action(struct adie_codec_path *path_ptr, + u32 stage) +{ + u32 iter; + struct adie_codec_register *reg_info; + + if (stage == ADIE_CODEC_FLASH_IMAGE) { + /* perform reimage */ + for (iter = 0; iter < path_ptr->img.img_sz; iter++) { + reg_info = &path_ptr->img.regs[iter]; + adie_codec_write(reg_info->reg, + reg_info->mask, reg_info->val); + } + } +} + +static int marimba_adie_codec_proceed_stage(struct adie_codec_path *path_ptr, + u32 state) +{ + int rc = 0, loop_exit = 0; + struct adie_codec_action_unit *curr_action; + struct adie_codec_hwsetting_entry *setting; + u8 reg, mask, val; + + mutex_lock(&adie_codec.lock); + setting = &path_ptr->profile->settings[path_ptr->hwsetting_idx]; + while (!loop_exit) { + curr_action = &setting->actions[path_ptr->stage_idx]; + switch (curr_action->type) { + case ADIE_CODEC_ACTION_ENTRY: + ADIE_CODEC_UNPACK_ENTRY(curr_action->action, + reg, mask, val); + adie_codec_write(reg, mask, val); + break; + case ADIE_CODEC_ACTION_DELAY_WAIT: + if (curr_action->action > MAX_MDELAY_US) + msleep(curr_action->action/1000); + else if (curr_action->action < MIN_MDELAY_US) + udelay(curr_action->action); + else + mdelay(curr_action->action/1000); + break; + case ADIE_CODEC_ACTION_STAGE_REACHED: + adie_codec_reach_stage_action(path_ptr, + curr_action->action); + if (curr_action->action == state) { + path_ptr->curr_stage = state; + loop_exit = 1; + } + break; + default: + BUG(); + } + + path_ptr->stage_idx++; + if (path_ptr->stage_idx == setting->action_sz) + path_ptr->stage_idx = 0; + } + mutex_unlock(&adie_codec.lock); + return rc; +} + +static void marimba_codec_bring_up(void) +{ + /* bring up sequence for Marimba codec core + * ensure RESET_N = 0 and GDFS_CLAMP_EN=1 - + * set GDFS_EN_FEW=1 then GDFS_EN_REST=1 then + * GDFS_CLAMP_EN = 0 and finally RESET_N = 1 + * Marimba codec bring up should use the Marimba + * slave address after which the codec slave + * address can be used + */ + + /* Bring up codec */ + adie_codec_write(0xFF, 0xFF, 0x08); + + /* set GDFS_EN_FEW=1 */ + adie_codec_write(0xFF, 0xFF, 0x0a); + + /* set GDFS_EN_REST=1 */ + adie_codec_write(0xFF, 0xFF, 0x0e); + + /* set RESET_N=1 */ + adie_codec_write(0xFF, 0xFF, 0x07); + + adie_codec_write(0xFF, 0xFF, 0x17); + + /* enable band gap */ + adie_codec_write(0x03, 0xFF, 0x04); + + /* dither delay selected and dmic gain stage bypassed */ + adie_codec_write(0x8F, 0xFF, 0x44); +} + +static void marimba_codec_bring_down(void) +{ + adie_codec_write(0xFF, 0xFF, 0x07); + adie_codec_write(0xFF, 0xFF, 0x06); + adie_codec_write(0xFF, 0xFF, 0x0e); + adie_codec_write(0xFF, 0xFF, 0x08); + adie_codec_write(0x03, 0xFF, 0x00); +} + +static int marimba_adie_codec_open(struct adie_codec_dev_profile *profile, + struct adie_codec_path **path_pptr) +{ + int rc = 0; + + mutex_lock(&adie_codec.lock); + + if (!profile || !path_pptr) { + rc = -EINVAL; + goto error; + } + + if (adie_codec.path[profile->path_type].profile) { + rc = -EBUSY; + goto error; + } + + if (!adie_codec.ref_cnt) { + + if (adie_codec.codec_pdata && + adie_codec.codec_pdata->marimba_codec_power) { + + rc = adie_codec.codec_pdata->marimba_codec_power(1); + if (rc) { + pr_err("%s: could not power up marimba " + "codec\n", __func__); + goto error; + } + } + marimba_codec_bring_up(); + } + + adie_codec.path[profile->path_type].profile = profile; + *path_pptr = (void *) &adie_codec.path[profile->path_type]; + adie_codec.ref_cnt++; + adie_codec.path[profile->path_type].hwsetting_idx = 0; + adie_codec.path[profile->path_type].curr_stage = ADIE_CODEC_FLASH_IMAGE; + adie_codec.path[profile->path_type].stage_idx = 0; + + +error: + + mutex_unlock(&adie_codec.lock); + return rc; +} + +static int marimba_adie_codec_close(struct adie_codec_path *path_ptr) +{ + int rc = 0; + + mutex_lock(&adie_codec.lock); + + if (!path_ptr) { + rc = -EINVAL; + goto error; + } + if (path_ptr->curr_stage != ADIE_CODEC_DIGITAL_OFF) + adie_codec_proceed_stage(path_ptr, ADIE_CODEC_DIGITAL_OFF); + + BUG_ON(!adie_codec.ref_cnt); + + path_ptr->profile = NULL; + adie_codec.ref_cnt--; + + if (!adie_codec.ref_cnt) { + + marimba_codec_bring_down(); + + if (adie_codec.codec_pdata && + adie_codec.codec_pdata->marimba_codec_power) { + + rc = adie_codec.codec_pdata->marimba_codec_power(0); + if (rc) { + pr_err("%s: could not power down marimba " + "codec\n", __func__); + goto error; + } + } + } + +error: + mutex_unlock(&adie_codec.lock); + return rc; +} + +static const struct adie_codec_operations marimba_adie_ops = { + .codec_id = MARIMBA_ID, + .codec_open = marimba_adie_codec_open, + .codec_close = marimba_adie_codec_close, + .codec_setpath = marimba_adie_codec_setpath, + .codec_proceed_stage = marimba_adie_codec_proceed_stage, + .codec_freq_supported = marimba_adie_codec_freq_supported, + .codec_enable_sidetone = marimba_adie_codec_enable_sidetone, + .codec_set_device_digital_volume = + marimba_adie_codec_set_device_digital_volume, +}; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_marimba_dent; +static struct dentry *debugfs_peek; +static struct dentry *debugfs_poke; +static struct dentry *debugfs_power; + +static unsigned char read_data; + +static int codec_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static int get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (strict_strtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } + else + return -EINVAL; + } + return 0; +} + +static ssize_t codec_debug_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char lbuf[8]; + + snprintf(lbuf, sizeof(lbuf), "0x%x\n", read_data); + return simple_read_from_buffer(ubuf, count, ppos, lbuf, strlen(lbuf)); +} + +static ssize_t codec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *access_str = filp->private_data; + char lbuf[32]; + int rc; + long int param[5]; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + + if (!strcmp(access_str, "power")) { + if (get_parameters(lbuf, param, 1) == 0) { + switch (param[0]) { + case 1: + adie_codec.codec_pdata->marimba_codec_power(1); + marimba_codec_bring_up(); + break; + case 0: + marimba_codec_bring_down(); + adie_codec.codec_pdata->marimba_codec_power(0); + break; + default: + rc = -EINVAL; + break; + } + } else + rc = -EINVAL; + } else if (!strcmp(access_str, "poke")) { + /* write */ + rc = get_parameters(lbuf, param, 2); + if ((param[0] <= 0xFF) && (param[1] <= 0xFF) && + (rc == 0)) + adie_codec_write(param[0], 0xFF, param[1]); + else + rc = -EINVAL; + } else if (!strcmp(access_str, "peek")) { + /* read */ + rc = get_parameters(lbuf, param, 1); + if ((param[0] <= 0xFF) && (rc == 0)) + adie_codec_read(param[0], &read_data); + else + rc = -EINVAL; + } + + if (rc == 0) + rc = cnt; + else + pr_err("%s: rc = %d\n", __func__, rc); + + return rc; +} + +static const struct file_operations codec_debug_ops = { + .open = codec_debug_open, + .write = codec_debug_write, + .read = codec_debug_read +}; +#endif + +static int marimba_codec_probe(struct platform_device *pdev) +{ + int rc; + + adie_codec.pdrv_ptr = platform_get_drvdata(pdev); + adie_codec.codec_pdata = pdev->dev.platform_data; + + if (adie_codec.codec_pdata->snddev_profile_init) + adie_codec.codec_pdata->snddev_profile_init(); + + /* Register the marimba ADIE operations */ + rc = adie_codec_register_codec_operations(&marimba_adie_ops); + +#ifdef CONFIG_DEBUG_FS + debugfs_marimba_dent = debugfs_create_dir("msm_adie_codec", 0); + if (!IS_ERR(debugfs_marimba_dent)) { + debugfs_peek = debugfs_create_file("peek", + S_IFREG | S_IRUGO, debugfs_marimba_dent, + (void *) "peek", &codec_debug_ops); + + debugfs_poke = debugfs_create_file("poke", + S_IFREG | S_IRUGO, debugfs_marimba_dent, + (void *) "poke", &codec_debug_ops); + + debugfs_power = debugfs_create_file("power", + S_IFREG | S_IRUGO, debugfs_marimba_dent, + (void *) "power", &codec_debug_ops); + } +#endif + return rc; +} + +static struct platform_driver marimba_codec_driver = { + .probe = marimba_codec_probe, + .driver = { + .name = "marimba_codec", + .owner = THIS_MODULE, + }, +}; + +static int __init marimba_codec_init(void) +{ + s32 rc; + + rc = platform_driver_register(&marimba_codec_driver); + if (IS_ERR_VALUE(rc)) + goto error; + + adie_codec.path[ADIE_CODEC_TX].img.regs = adie_codec_tx_regs; + adie_codec.path[ADIE_CODEC_TX].img.img_sz = + ARRAY_SIZE(adie_codec_tx_regs); + adie_codec.path[ADIE_CODEC_RX].img.regs = adie_codec_rx_regs; + adie_codec.path[ADIE_CODEC_RX].img.img_sz = + ARRAY_SIZE(adie_codec_rx_regs); + adie_codec.path[ADIE_CODEC_LB].img.regs = adie_codec_lb_regs; + adie_codec.path[ADIE_CODEC_LB].img.img_sz = + ARRAY_SIZE(adie_codec_lb_regs); + mutex_init(&adie_codec.lock); + +error: + return rc; +} + +static void __exit marimba_codec_exit(void) +{ +#ifdef CONFIG_DEBUG_FS + debugfs_remove(debugfs_peek); + debugfs_remove(debugfs_poke); + debugfs_remove(debugfs_power); + debugfs_remove(debugfs_marimba_dent); +#endif + platform_driver_unregister(&marimba_codec_driver); +} + +module_init(marimba_codec_init); +module_exit(marimba_codec_exit); + +MODULE_DESCRIPTION("Marimba codec driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/marimba-core.c b/drivers/mfd/marimba-core.c new file mode 100644 index 00000000000..fec9ef4acad --- /dev/null +++ b/drivers/mfd/marimba-core.c @@ -0,0 +1,699 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm Marimba Core Driver + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define MARIMBA_MODE 0x00 + +#define ADIE_ARRY_SIZE (CHIP_ID_MAX * MARIMBA_NUM_CHILD) + +static int marimba_shadow[ADIE_ARRY_SIZE][0xff]; + +struct marimba marimba_modules[ADIE_ARRY_SIZE]; + +#define MARIMBA_VERSION_REG 0x11 +#define MARIMBA_MODE_REG 0x00 + +struct marimba_platform_data *marimba_pdata; + +static uint32_t marimba_gpio_count; +static bool fm_status; +static bool bt_status; + +#ifdef CONFIG_I2C_SSBI +#define NUM_ADD MARIMBA_NUM_CHILD +#else +#define NUM_ADD (MARIMBA_NUM_CHILD - 1) +#endif + + +/** + * marimba_read_bahama_ver - Reads Bahama version. + * @param marimba: marimba structure pointer passed by client + * @returns result of the operation. + */ +int marimba_read_bahama_ver(struct marimba *marimba) +{ + int rc; + u8 bahama_version; + + rc = marimba_read_bit_mask(marimba, 0x00, &bahama_version, 1, 0x1F); + if (rc < 0) + return rc; + switch (bahama_version) { + case 0x08: /* varient of bahama v1 */ + case 0x10: + case 0x00: + return BAHAMA_VER_1_0; + case 0x09: /* variant of bahama v2 */ + return BAHAMA_VER_2_0; + default: + return BAHAMA_VER_UNSUPPORTED; + } +} +EXPORT_SYMBOL(marimba_read_bahama_ver); +/** + * marimba_ssbi_write - Writes a n bit TSADC register in Marimba + * @param marimba: marimba structure pointer passed by client + * @param reg: register address + * @param value: buffer to be written + * @param len: num of bytes + * @returns result of the operation. + */ +int marimba_ssbi_write(struct marimba *marimba, u16 reg , u8 *value, int len) +{ + struct i2c_msg *msg; + int ret; + + marimba = &marimba_modules[marimba->mod_id]; + + mutex_lock(&marimba->xfer_lock); + + msg = &marimba->xfer_msg[0]; + msg->addr = reg; + msg->flags = 0x0; + msg->buf = value; + msg->len = len; + + ret = i2c_transfer(marimba->client->adapter, marimba->xfer_msg, 1); + + mutex_unlock(&marimba->xfer_lock); + + return ret; +} +EXPORT_SYMBOL(marimba_ssbi_write); + +/** + * marimba_ssbi_read - Reads a n bit TSADC register in Marimba + * @param marimba: marimba structure pointer passed by client + * @param reg: register address + * @param value: ssbi read of the register to be stored + * @param len: num of bytes + * + * @returns result of the operation. +*/ +int marimba_ssbi_read(struct marimba *marimba, u16 reg, u8 *value, int len) +{ + struct i2c_msg *msg; + int ret; + + marimba = &marimba_modules[marimba->mod_id]; + + mutex_lock(&marimba->xfer_lock); + + msg = &marimba->xfer_msg[0]; + msg->addr = reg; + msg->flags = I2C_M_RD; + msg->buf = value; + msg->len = len; + + ret = i2c_transfer(marimba->client->adapter, marimba->xfer_msg, 1); + + mutex_unlock(&marimba->xfer_lock); + + return ret; +} +EXPORT_SYMBOL(marimba_ssbi_read); + +/** + * marimba_write_bit_mask - Sets n bit register using bit mask + * @param marimba: marimba structure pointer passed by client + * @param reg: register address + * @param value: buffer to be written to the registers + * @param num_bytes: n bytes to write + * @param mask: bit mask corresponding to the registers + * + * @returns result of the operation. + */ +int marimba_write_bit_mask(struct marimba *marimba, u8 reg, u8 *value, + unsigned num_bytes, u8 mask) +{ + int ret, i; + struct i2c_msg *msg; + u8 data[num_bytes + 1]; + u8 mask_value[num_bytes]; + + marimba = &marimba_modules[marimba->mod_id]; + + mutex_lock(&marimba->xfer_lock); + + for (i = 0; i < num_bytes; i++) + mask_value[i] = (marimba_shadow[marimba->mod_id][reg + i] + & ~mask) | (value[i] & mask); + + msg = &marimba->xfer_msg[0]; + msg->addr = marimba->client->addr; + msg->flags = 0; + msg->len = num_bytes + 1; + msg->buf = data; + data[0] = reg; + memcpy(data+1, mask_value, num_bytes); + + ret = i2c_transfer(marimba->client->adapter, marimba->xfer_msg, 1); + + /* Try again if the write fails */ + if (ret != 1) + ret = i2c_transfer(marimba->client->adapter, + marimba->xfer_msg, 1); + + if (ret == 1) { + for (i = 0; i < num_bytes; i++) + marimba_shadow[marimba->mod_id][reg + i] + = mask_value[i]; + } else + dev_err(&marimba->client->dev, "i2c write failed\n"); + + mutex_unlock(&marimba->xfer_lock); + + return ret; +} +EXPORT_SYMBOL(marimba_write_bit_mask); + +/** + * marimba_write - Sets n bit register in Marimba + * @param marimba: marimba structure pointer passed by client + * @param reg: register address + * @param value: buffer values to be written + * @param num_bytes: n bytes to write + * + * @returns result of the operation. + */ +int marimba_write(struct marimba *marimba, u8 reg, u8 *value, + unsigned num_bytes) +{ + return marimba_write_bit_mask(marimba, reg, value, num_bytes, 0xff); +} +EXPORT_SYMBOL(marimba_write); + +/** + * marimba_read_bit_mask - Reads a n bit register based on bit mask + * @param marimba: marimba structure pointer passed by client + * @param reg: register address + * @param value: i2c read of the register to be stored + * @param num_bytes: n bytes to be read. + * @param mask: bit mask concerning its register + * + * @returns result of the operation. +*/ +int marimba_read_bit_mask(struct marimba *marimba, u8 reg, u8 *value, + unsigned num_bytes, u8 mask) +{ + int ret, i; + + struct i2c_msg *msg; + + marimba = &marimba_modules[marimba->mod_id]; + + mutex_lock(&marimba->xfer_lock); + + msg = &marimba->xfer_msg[0]; + msg->addr = marimba->client->addr; + msg->len = 1; + msg->flags = 0; + msg->buf = ® + + msg = &marimba->xfer_msg[1]; + msg->addr = marimba->client->addr; + msg->len = num_bytes; + msg->flags = I2C_M_RD; + msg->buf = value; + + ret = i2c_transfer(marimba->client->adapter, marimba->xfer_msg, 2); + + /* Try again if read fails first time */ + if (ret != 2) + ret = i2c_transfer(marimba->client->adapter, + marimba->xfer_msg, 2); + + if (ret == 2) { + for (i = 0; i < num_bytes; i++) { + marimba_shadow[marimba->mod_id][reg + i] = value[i]; + value[i] &= mask; + } + } else + dev_err(&marimba->client->dev, "i2c read failed\n"); + + mutex_unlock(&marimba->xfer_lock); + + return ret; +} +EXPORT_SYMBOL(marimba_read_bit_mask); + +/** + * marimba_read - Reads n bit registers in Marimba + * @param marimba: marimba structure pointer passed by client + * @param reg: register address + * @param value: i2c read of the register to be stored + * @param num_bytes: n bytes to read. + * @param mask: bit mask concerning its register + * + * @returns result of the operation. +*/ +int marimba_read(struct marimba *marimba, u8 reg, u8 *value, unsigned num_bytes) +{ + return marimba_read_bit_mask(marimba, reg, value, num_bytes, 0xff); +} +EXPORT_SYMBOL(marimba_read); + +int timpani_read(struct marimba *marimba, u8 reg, u8 *value, unsigned num_bytes) +{ + return marimba_read_bit_mask(marimba, reg, value, num_bytes, 0xff); +} +EXPORT_SYMBOL(timpani_read); + +int timpani_write(struct marimba *marimba, u8 reg, + u8 *value, unsigned num_bytes) +{ + return marimba_write_bit_mask(marimba, reg, value, num_bytes, 0xff); +} +EXPORT_SYMBOL(timpani_write); + +static int cur_codec_type = -1, cur_adie_type = -1, cur_connv_type = -1; +static int adie_arry_idx; + +int adie_get_detected_codec_type(void) +{ + return cur_codec_type; +} +EXPORT_SYMBOL(adie_get_detected_codec_type); + +int adie_get_detected_connectivity_type(void) +{ + return cur_connv_type; +} +EXPORT_SYMBOL(adie_get_detected_connectivity_type); + +static struct device * +add_numbered_child(unsigned chip, const char *name, int num, u8 driver_data, + void *pdata, unsigned pdata_len) +{ + struct platform_device *pdev; + struct marimba *marimba = &marimba_modules[chip + adie_arry_idx]; + int status = 0; + + pdev = platform_device_alloc(name, num); + if (!pdev) { + status = -ENOMEM; + return ERR_PTR(status); + } + + pdev->dev.parent = &marimba->client->dev; + + marimba->mod_id = chip + adie_arry_idx; + + platform_set_drvdata(pdev, marimba); + + if (pdata) { + status = platform_device_add_data(pdev, pdata, pdata_len); + if (status < 0) + goto err; + } + + status = platform_device_add(pdev); + if (status < 0) + goto err; + +err: + if (status < 0) { + platform_set_drvdata(pdev, NULL); + platform_device_put(pdev); + dev_err(&marimba->client->dev, "can't add %s dev\n", name); + return ERR_PTR(status); + } + return &pdev->dev; +} + +static inline struct device *add_child(unsigned chip, const char *name, + u8 driver_data, void *pdata, unsigned pdata_len) +{ + return add_numbered_child(chip, name, -1, driver_data, pdata, + pdata_len); +} + +static int marimba_add_child(struct marimba_platform_data *pdata, + u8 driver_data) +{ + struct device *child; + + if (cur_adie_type == MARIMBA_ID) { + child = add_child(MARIMBA_SLAVE_ID_FM, "marimba_fm", + driver_data, pdata->fm, sizeof(*pdata->fm)); + if (IS_ERR(child)) + return PTR_ERR(child); + } else if ((cur_adie_type == BAHAMA_ID) && + (cur_connv_type == BAHAMA_ID)) { + child = add_child(BAHAMA_SLAVE_ID_FM_ID, "marimba_fm", + driver_data, pdata->fm, sizeof(*pdata->fm)); + if (IS_ERR(child)) + return PTR_ERR(child); + } + + /* Add Codec for Marimba and Timpani */ + if (cur_adie_type == MARIMBA_ID) { + child = add_child(MARIMBA_SLAVE_ID_CDC, "marimba_codec", + driver_data, pdata->codec, sizeof(*pdata->codec)); + if (IS_ERR(child)) + return PTR_ERR(child); + } else if (cur_adie_type == TIMPANI_ID) { + child = add_child(MARIMBA_SLAVE_ID_CDC, "timpani_codec", + driver_data, pdata->codec, sizeof(*pdata->codec)); + if (IS_ERR(child)) + return PTR_ERR(child); + } + +#if defined(CONFIG_I2C_SSBI) + if ((pdata->tsadc != NULL) && (cur_adie_type != BAHAMA_ID)) { + child = add_child(MARIMBA_ID_TSADC, "marimba_tsadc", + driver_data, pdata->tsadc, sizeof(*pdata->tsadc)); + if (IS_ERR(child)) + return PTR_ERR(child); + } +#endif + return 0; +} + +int marimba_gpio_config(int gpio_value) +{ + struct marimba *marimba = &marimba_modules[MARIMBA_SLAVE_ID_MARIMBA]; + struct marimba_platform_data *pdata = marimba_pdata; + int rc = 0; + + /* Clients BT/FM need to manage GPIO 34 on Fusion for its clocks */ + + mutex_lock(&marimba->xfer_lock); + + if (gpio_value) { + marimba_gpio_count++; + if (marimba_gpio_count == 1) + rc = pdata->marimba_gpio_config(1); + } else { + marimba_gpio_count--; + if (marimba_gpio_count == 0) + rc = pdata->marimba_gpio_config(0); + } + + mutex_unlock(&marimba->xfer_lock); + + return rc; + +} +EXPORT_SYMBOL(marimba_gpio_config); + +bool marimba_get_fm_status(struct marimba *marimba) +{ + bool ret; + + marimba = &marimba_modules[marimba->mod_id]; + + mutex_lock(&marimba->xfer_lock); + + ret = fm_status; + + mutex_unlock(&marimba->xfer_lock); + + return ret; +} +EXPORT_SYMBOL(marimba_get_fm_status); + +void marimba_set_fm_status(struct marimba *marimba, bool value) +{ + marimba = &marimba_modules[marimba->mod_id]; + + mutex_lock(&marimba->xfer_lock); + + fm_status = value; + + mutex_unlock(&marimba->xfer_lock); +} +EXPORT_SYMBOL(marimba_set_fm_status); + +bool marimba_get_bt_status(struct marimba *marimba) +{ + bool ret; + + marimba = &marimba_modules[marimba->mod_id]; + + mutex_lock(&marimba->xfer_lock); + + ret = bt_status; + + mutex_unlock(&marimba->xfer_lock); + + return ret; +} +EXPORT_SYMBOL(marimba_get_bt_status); + +void marimba_set_bt_status(struct marimba *marimba, bool value) +{ + marimba = &marimba_modules[marimba->mod_id]; + + mutex_lock(&marimba->xfer_lock); + + bt_status = value; + + mutex_unlock(&marimba->xfer_lock); +} +EXPORT_SYMBOL(marimba_set_bt_status); + +static int get_adie_type(void) +{ + u8 rd_val; + int ret; + + struct marimba *marimba = &marimba_modules[ADIE_ARRY_SIZE - 1]; + + marimba->mod_id = ADIE_ARRY_SIZE - 1; + /* Enable the Mode for Marimba/Timpani */ + ret = marimba_read(marimba, MARIMBA_MODE_REG, &rd_val, 1); + + if (ret >= 0) { + if (rd_val & 0x80) { + cur_adie_type = BAHAMA_ID; + return cur_adie_type; + } else { + ret = marimba_read(marimba, + MARIMBA_VERSION_REG, &rd_val, 1); + if ((ret >= 0) && (rd_val & 0x20)) { + cur_adie_type = TIMPANI_ID; + return cur_adie_type; + } else if (ret >= 0) { + cur_adie_type = MARIMBA_ID; + return cur_adie_type; + } + } + } + + return ret; +} + +static void marimba_init_reg(struct i2c_client *client, u8 driver_data) +{ + struct marimba_platform_data *pdata = client->dev.platform_data; + struct marimba *marimba = + &marimba_modules[MARIMBA_SLAVE_ID_MARIMBA + adie_arry_idx]; + + u8 buf[1]; + + buf[0] = 0x10; + + if (cur_adie_type != BAHAMA_ID) { + marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA + adie_arry_idx; + /* Enable the Mode for Marimba/Timpani */ + marimba_write(marimba, MARIMBA_MODE, buf, 1); + } else if ((cur_adie_type == BAHAMA_ID) && + (cur_connv_type == BAHAMA_ID)) { + marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA + adie_arry_idx; + marimba_write(marimba, BAHAMA_SLAVE_ID_FM_ID, + &pdata->slave_id[SLAVE_ID_BAHAMA_FM], 1); + /* Configure Bahama core registers (AREG & DREG) */ + /* with optimal values to eliminate power leakage */ + if (pdata->bahama_core_config != NULL) + pdata->bahama_core_config(cur_adie_type); + } +} + +static int marimba_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct marimba_platform_data *pdata = client->dev.platform_data; + struct i2c_adapter *ssbi_adap; + struct marimba *marimba; + int i, status, rc, client_loop, adie_slave_idx_offset; + int rc_bahama = 0, rc_marimba = 0; + + if (!pdata) { + dev_dbg(&client->dev, "no platform data?\n"); + return -EINVAL; + } + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C) == 0) { + dev_dbg(&client->dev, "can't talk I2C?\n"); + return -EIO; + } + + /* First, identify the codec type */ + if (pdata->marimba_setup != NULL) { + rc_marimba = pdata->marimba_setup(); + if (rc_marimba) + pdata->marimba_shutdown(); + } + + if (pdata->bahama_setup != NULL && + cur_connv_type != BAHAMA_ID) { + rc_bahama = pdata->bahama_setup(); + if (rc_bahama) + pdata->bahama_shutdown(cur_connv_type); + } + + if (rc_marimba & rc_bahama) + return -EAGAIN; + + marimba = &marimba_modules[ADIE_ARRY_SIZE - 1]; + marimba->client = client; + mutex_init(&marimba->xfer_lock); + + rc = get_adie_type(); + + mutex_destroy(&marimba->xfer_lock); + + if (rc < 0) { + if (pdata->bahama_setup != NULL) + pdata->bahama_shutdown(cur_adie_type); + if (pdata->marimba_shutdown != NULL) + pdata->marimba_shutdown(); + return -ENODEV; + } + + if (rc < 2) { + adie_arry_idx = 0; + adie_slave_idx_offset = 0; + client_loop = 0; + cur_codec_type = rc; + if (cur_connv_type < 0) + cur_connv_type = rc; + if (pdata->bahama_shutdown != NULL) + pdata->bahama_shutdown(cur_connv_type); + } else { + adie_arry_idx = 5; + adie_slave_idx_offset = 5; + client_loop = 1; + cur_connv_type = rc; + } + + marimba = &marimba_modules[adie_arry_idx]; + marimba->client = client; + mutex_init(&marimba->xfer_lock); + + for (i = 1; i <= (NUM_ADD - client_loop); i++) { + /* Skip adding BT/FM for Timpani */ + if (i == 1 && rc >= 1) + i++; + marimba = &marimba_modules[i + adie_arry_idx]; + if (i != MARIMBA_ID_TSADC) + marimba->client = i2c_new_dummy(client->adapter, + pdata->slave_id[i + adie_slave_idx_offset]); + else if (pdata->tsadc_ssbi_adap) { + ssbi_adap = i2c_get_adapter(pdata->tsadc_ssbi_adap); + marimba->client = i2c_new_dummy(ssbi_adap, + 0x55); + } else + ssbi_adap = NULL; + + if (!marimba->client) { + dev_err(&marimba->client->dev, + "can't attach client %d\n", i); + status = -ENOMEM; + goto fail; + } + strlcpy(marimba->client->name, id->name, + sizeof(marimba->client->name)); + + mutex_init(&marimba->xfer_lock); + } + + marimba_init_reg(client, id->driver_data); + + status = marimba_add_child(pdata, id->driver_data); + + marimba_pdata = pdata; + + return 0; + +fail: + return status; +} + +static int __devexit marimba_remove(struct i2c_client *client) +{ + int i; + struct marimba_platform_data *pdata; + + pdata = client->dev.platform_data; + for (i = 0; i <= ADIE_ARRY_SIZE; i++) { + struct marimba *marimba = &marimba_modules[i]; + + if (marimba->client && marimba->client != client) + i2c_unregister_device(marimba->client); + + marimba_modules[i].client = NULL; + } + + if (pdata->marimba_shutdown != NULL) + pdata->marimba_shutdown(); + + return 0; +} + +static struct i2c_device_id marimba_id_table[] = { + {"marimba", MARIMBA_ID}, + {"timpani", TIMPANI_ID}, + {} +}; +MODULE_DEVICE_TABLE(i2c, marimba_id_table); + +static struct i2c_driver marimba_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "marimba-core", + }, + .id_table = marimba_id_table, + .probe = marimba_probe, + .remove = __devexit_p(marimba_remove), +}; + +static int __init marimba_init(void) +{ + return i2c_add_driver(&marimba_driver); +} +module_init(marimba_init); + +static void __exit marimba_exit(void) +{ + i2c_del_driver(&marimba_driver); +} +module_exit(marimba_exit); + +MODULE_DESCRIPTION("Marimba Top level Driver"); +MODULE_ALIAS("platform:marimba-core"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); diff --git a/drivers/mfd/marimba-tsadc.c b/drivers/mfd/marimba-tsadc.c new file mode 100644 index 00000000000..8a7b781373f --- /dev/null +++ b/drivers/mfd/marimba-tsadc.c @@ -0,0 +1,696 @@ +/* + * Marimba TSADC driver. + * + * Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include +#endif + +/* marimba configuration block: TS_CTL0 */ +#define TS_CTL0 0xFF +#define TS_CTL0_RESET BIT(0) +#define TS_CTL0_CLK_EN BIT(1) +#define TS_CTL0_XO_EN BIT(2) +#define TS_CTL0_EOC_EN BIT(3) +#define TS_CTL0_PENIRQ_EN BIT(4) + +/* TSADC registers */ +#define SSBI_PRESET 0x00 +#define TSHK_DIG_CONFIG 0x4F +#define TSHK_INTF_CONFIG 0x50 +#define TSHK_SETUP 0x51 + #define TSHK_SETUP_EN_ADC BIT(0) + #define TSHK_SETUP_EN_PIRQ BIT(7) +#define TSHK_PARAM 0x52 +#define TSHK_DATA_RD 0x53 +#define TSHK_STATUS 0x54 +#define TSHK_SETUP2 0x55 +#define TSHK_RSV1 0x56 + #define TSHK_RSV1_PRECHARGE_EN BIT(0) +#define TSHK_COMMAND 0x57 +#define TSHK_PARAM2 0x58 + #define TSHK_INPUT_CLK_MASK 0x3F + #define TSHK_SAMPLE_PRD_MASK 0xC7 + #define TSHK_INPUT_CLK_SHIFT 0x6 + #define TSHK_SAMPLE_PRD_SHIFT 0x3 +#define TSHK_PARAM3 0x59 + #define TSHK_PARAM3_MODE_MASK 0xFC + #define TSHK_PARAM3_PRE_CHG_SHIFT (5) + #define TSHK_PARAM3_STABIZ_SHIFT (2) + #define TSHK_STABLE_TIME_MASK 0xE3 + #define TSHK_PRECHG_TIME_MASK 0x1F +#define TSHK_PARAM4 0x5A +#define TSHK_RSV2 0x5B +#define TSHK_RSV3 0x5C +#define TSHK_RSV4 0x5D +#define TSHK_RSV5 0x5E + +struct marimba_tsadc_client { + unsigned int is_ts; + struct platform_device *pdev; +}; + +struct marimba_tsadc { + struct marimba *marimba; + struct device *dev; + struct marimba_tsadc_platform_data *pdata; + struct clk *codec_ssbi; + struct device *child_tssc; + bool clk_enabled; +#if defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif +}; + +static struct marimba_tsadc *tsadc_dev; + +static int marimba_write_u8(struct marimba_tsadc *tsadc, u8 reg, u8 data) +{ + int rc; + + tsadc->marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA; + rc = marimba_write(tsadc->marimba, reg, &data, 1); + + if (!rc) + dev_warn(tsadc->dev, "Error writing marimba reg %X - ret %X\n", + reg, data); + return 0; +} + +static int marimba_tsadc_write(struct marimba_tsadc *tsadc, u8 reg, u8 data) +{ + int rc; + + tsadc->marimba->mod_id = MARIMBA_ID_TSADC; + + rc = marimba_ssbi_write(tsadc->marimba, reg, &data, 1); + if (!rc) + dev_warn(tsadc->dev, "Error writing marimba reg %X - ret %X\n", + reg, data); + return rc; +} + +static int marimba_tsadc_shutdown(struct marimba_tsadc *tsadc) +{ + u8 val; + int rc; + + /* force reset */ + val = TS_CTL0_XO_EN | TS_CTL0_EOC_EN | TS_CTL0_PENIRQ_EN | + TS_CTL0_CLK_EN; + rc = marimba_write_u8(tsadc, TS_CTL0, val); + if (rc < 0) + return rc; + + /* disable xo, clock */ + val = TS_CTL0_PENIRQ_EN | TS_CTL0_EOC_EN; + rc = marimba_write_u8(tsadc, TS_CTL0, val); + if (rc < 0) + return rc; + + /* de-vote S2 1.3v */ + if (tsadc->pdata->level_vote) + /* REVISIT: Ignore error for level_vote(0) for now*/ + tsadc->pdata->level_vote(0); + + return 0; +} + +static int marimba_tsadc_startup(struct marimba_tsadc *tsadc) +{ + u8 val; + int rc = 0; + + /* vote for S2 1.3v */ + if (tsadc->pdata->level_vote) { + rc = tsadc->pdata->level_vote(1); + if (rc < 0) + return rc; + } + + /* disable XO, clock and output enables */ + rc = marimba_write_u8(tsadc, TS_CTL0, 0x00); + if (rc < 0) + goto fail_marimba_write; + + /* Enable output enables */ + val = TS_CTL0_XO_EN | TS_CTL0_EOC_EN | TS_CTL0_PENIRQ_EN; + rc = marimba_write_u8(tsadc, TS_CTL0, val); + if (rc < 0) + goto fail_marimba_write; + + /* Enable clock */ + val = val | TS_CTL0_CLK_EN; + rc = marimba_write_u8(tsadc, TS_CTL0, val); + if (rc < 0) + goto fail_marimba_write; + + /* remove reset */ + val = val | TS_CTL0_RESET; + rc = marimba_write_u8(tsadc, TS_CTL0, val); + if (rc < 0) + goto fail_marimba_write; + + return 0; + +fail_marimba_write: + if (tsadc->pdata->level_vote) + /* REVISIT: Ignore error for level_vote(0) for now*/ + tsadc->pdata->level_vote(0); + return rc; +} + + +static int marimba_tsadc_configure(struct marimba_tsadc *tsadc) +{ + u8 rsv1 = 0, setup = 0, i, count = 0; + u8 param2 = 0, param3 = 0; + unsigned long val; + int rc; + + rc = marimba_tsadc_write(tsadc, SSBI_PRESET, 0x00); + if (rc < 0) + return rc; + + if (!tsadc->pdata) + return -EINVAL; + + /* Configure RSV1 register*/ + if (tsadc->pdata->tsadc_prechg_en == true) + rsv1 |= TSHK_RSV1_PRECHARGE_EN; + else + rsv1 &= ~TSHK_RSV1_PRECHARGE_EN; + + /* Set RSV1 register*/ + rc = marimba_tsadc_write(tsadc, TSHK_RSV1, rsv1); + if (rc < 0) + return rc; + + /* Configure PARAM2 register */ + /* Input clk */ + val = tsadc->pdata->params2.input_clk_khz; + param2 &= TSHK_INPUT_CLK_MASK; + val /= 600; + if (val >= 1 && val <= 8 && !(val & (val - 1))) { + /* Input clk can be .6, 1.2, 2.4, 4.8Mhz */ + if (val % 4 != 0) + param2 = (4 - (val % 4)) << TSHK_INPUT_CLK_SHIFT; + else + param2 = ((val / 4) - 1) << TSHK_INPUT_CLK_SHIFT; + } else /* Configure the default clk 2.4Mhz */ + param2 = 0x00 << TSHK_INPUT_CLK_SHIFT; + + /* Sample period */ + param2 &= TSHK_SAMPLE_PRD_MASK; + param2 |= tsadc->pdata->params2.sample_prd << TSHK_SAMPLE_PRD_SHIFT; + + /* Write PARAM2 register */ + rc = marimba_tsadc_write(tsadc, TSHK_PARAM2, param2); + if (rc < 0) + return rc; + + /* REVISIT: If Precharge time, stabilization time > 409.6us */ + /* Configure PARAM3 register */ + val = tsadc->pdata->params3.prechg_time_nsecs; + param3 &= TSHK_PRECHG_TIME_MASK; + val /= 6400; + if (val >= 1 && val <= 64 && !(val & (val - 1))) { + count = 0; + while ((val = val >> 1) != 0) + count++; + param3 |= count << TSHK_PARAM3_PRE_CHG_SHIFT; + } else /* Set default value if the input is wrong */ + param3 |= 0x00 << TSHK_PARAM3_PRE_CHG_SHIFT; + + val = tsadc->pdata->params3.stable_time_nsecs; + param3 &= TSHK_STABLE_TIME_MASK; + val /= 6400; + if (val >= 1 && val <= 64 && !(val & (val - 1))) { + count = 0; + while ((val = val >> 1) != 0) + count++; + param3 |= count << TSHK_PARAM3_STABIZ_SHIFT; + } else /* Set default value if the input is wrong */ + param3 |= 0x00 << TSHK_PARAM3_STABIZ_SHIFT; + + /* Get TSADC mode */ + val = tsadc->pdata->params3.tsadc_test_mode; + param3 &= TSHK_PARAM3_MODE_MASK; + if (val == 0) + param3 |= 0x00; + else + for (i = 0; i < 3 ; i++) { + if (((val + i) % 39322) == 0) { + param3 |= (i + 1); + break; + } + } + if (i == 3) /* Set to normal mode if input is wrong */ + param3 |= 0x00; + + rc = marimba_tsadc_write(tsadc, TSHK_PARAM3, param3); + if (rc < 0) + return rc; + + /* Configure TSHK SETUP Register */ + if (tsadc->pdata->setup.pen_irq_en == true) + setup |= TSHK_SETUP_EN_PIRQ; + else + setup &= ~TSHK_SETUP_EN_PIRQ; + + if (tsadc->pdata->setup.tsadc_en == true) + setup |= TSHK_SETUP_EN_ADC; + else + setup &= ~TSHK_SETUP_EN_ADC; + + /* Enable signals to ADC, pen irq assertion */ + rc = marimba_tsadc_write(tsadc, TSHK_SETUP, setup); + if (rc < 0) + return rc; + + return 0; +} + +int marimba_tsadc_start(struct marimba_tsadc_client *client) +{ + int rc = 0; + + if (!client) { + pr_err("%s: Not a valid client\n", __func__); + return -ENODEV; + } + + if (!tsadc_dev) { + dev_err(&client->pdev->dev, + "%s: No tsadc device available\n", __func__); + return -ENODEV; + } + + /* REVISIT - add locks */ + if (client->is_ts) { + rc = marimba_tsadc_startup(tsadc_dev); + if (rc < 0) + goto fail_tsadc_startup; + rc = marimba_tsadc_configure(tsadc_dev); + if (rc < 0) + goto fail_tsadc_conf; + } + + return 0; +fail_tsadc_conf: + marimba_tsadc_shutdown(tsadc_dev); +fail_tsadc_startup: + return rc; +} +EXPORT_SYMBOL(marimba_tsadc_start); + +struct marimba_tsadc_client * +marimba_tsadc_register(struct platform_device *pdev, unsigned int is_ts) +{ + struct marimba_tsadc_client *client; + + if (!pdev) { + pr_err("%s: valid platform device pointer please\n", __func__); + return ERR_PTR(-EINVAL); + } + + if (!is_ts) { + dev_err(&pdev->dev, "%s: only TS right now\n", __func__); + return ERR_PTR(-EINVAL); + } + + if (!tsadc_dev) { + dev_err(&pdev->dev, + "%s: No tsadc device available\n", __func__); + return ERR_PTR(-ENODEV); + } + + client = kzalloc(sizeof *client, GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + client->pdev = pdev; + client->is_ts = is_ts; + + return client; +} +EXPORT_SYMBOL(marimba_tsadc_register); + +void marimba_tsadc_unregister(struct marimba_tsadc_client *client) +{ + if (client->is_ts) + marimba_tsadc_shutdown(tsadc_dev); + kfree(client); +} +EXPORT_SYMBOL(marimba_tsadc_unregister); + +static struct resource resources_tssc[] = { + { + .start = 0xAD300000, + .end = 0xAD300000 + SZ_4K - 1, + .name = "tssc", + .flags = IORESOURCE_MEM, + }, + { + .start = 55, + .end = 55, + .name = "tssc1", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, + { + .start = 56, + .end = 56, + .name = "tssc2", + .flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING, + }, +}; + +static struct device * +marimba_add_tssc_subdev(struct device *parent, const char *name, int num, + struct resource *resources, int num_resources, + void *pdata, int pdata_len) +{ + struct platform_device *pdev; + int status; + + pdev = platform_device_alloc(name, num); + if (!pdev) { + dev_dbg(parent, "can't alloc dev\n"); + status = -ENOMEM; + goto err; + } + + pdev->dev.parent = parent; + + if (pdata) { + status = platform_device_add_data(pdev, pdata, pdata_len); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add platform_data\n"); + goto err; + } + } + + status = platform_device_add_resources(pdev, resources, num_resources); + if (status < 0) { + dev_dbg(&pdev->dev, "can't add resources\n"); + goto err; + } + + status = platform_device_add(pdev); + +err: + if (status < 0) { + platform_device_put(pdev); + dev_err(parent, "can't add %s dev\n", name); + return ERR_PTR(status); + } + return &pdev->dev; +} + +#ifdef CONFIG_PM +static int +marimba_tsadc_suspend(struct device *dev) +{ + int rc = 0, ret = 0; + struct marimba_tsadc *tsadc = dev_get_drvdata(dev); + + if (tsadc->clk_enabled == true) { + clk_disable(tsadc->codec_ssbi); + tsadc->clk_enabled = false; + } + + if (!(device_may_wakeup(dev) && + device_may_wakeup(tsadc->child_tssc))) { + rc = marimba_tsadc_shutdown(tsadc); + if (rc < 0) { + pr_err("%s: Unable to shutdown TSADC\n", __func__); + goto fail_shutdown; + } + + if (tsadc->pdata->marimba_tsadc_power) { + rc = tsadc->pdata->marimba_tsadc_power(0); + if (rc < 0) + goto fail_tsadc_power; + } + } + return rc; + +fail_tsadc_power: + marimba_tsadc_startup(tsadc_dev); + marimba_tsadc_configure(tsadc_dev); +fail_shutdown: + if (tsadc->clk_enabled == false) { + ret = clk_enable(tsadc->codec_ssbi); + if (ret == 0) + tsadc->clk_enabled = true; + } + return rc; +} + +static int marimba_tsadc_resume(struct device *dev) +{ + int rc = 0; + struct marimba_tsadc *tsadc = dev_get_drvdata(dev); + + if (tsadc->clk_enabled == false) { + rc = clk_enable(tsadc->codec_ssbi); + if (rc != 0) { + pr_err("%s: Clk enable failed\n", __func__); + return rc; + } + tsadc->clk_enabled = true; + } + + if (!(device_may_wakeup(dev) && + device_may_wakeup(tsadc->child_tssc))) { + if (tsadc->pdata->marimba_tsadc_power) { + rc = tsadc->pdata->marimba_tsadc_power(1); + if (rc) { + pr_err("%s: Unable to power on TSADC \n", + __func__); + goto fail_tsadc_power; + } + } + + rc = marimba_tsadc_startup(tsadc_dev); + if (rc < 0) { + pr_err("%s: Unable to startup TSADC\n", __func__); + goto fail_tsadc_startup; + } + + rc = marimba_tsadc_configure(tsadc_dev); + if (rc < 0) { + pr_err("%s: Unable to configure TSADC\n", __func__); + goto fail_tsadc_configure; + } + } + return rc; + +fail_tsadc_configure: + marimba_tsadc_shutdown(tsadc_dev); +fail_tsadc_startup: + if (tsadc->pdata->marimba_tsadc_power) + tsadc->pdata->marimba_tsadc_power(0); +fail_tsadc_power: + if (tsadc->clk_enabled == true) { + clk_disable(tsadc->codec_ssbi); + tsadc->clk_enabled = false; + } + return rc; +} + +static struct dev_pm_ops tsadc_pm_ops = { +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = marimba_tsadc_suspend, + .resume = marimba_tsadc_resume, +#endif +}; +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void marimba_tsadc_early_suspend(struct early_suspend *h) +{ + struct marimba_tsadc *tsadc = container_of(h, struct marimba_tsadc, + early_suspend); + + marimba_tsadc_suspend(tsadc->dev); +} + +static void marimba_tsadc_late_resume(struct early_suspend *h) +{ + struct marimba_tsadc *tsadc = container_of(h, struct marimba_tsadc, + early_suspend); + + marimba_tsadc_resume(tsadc->dev); +} +#endif + +static int __devinit marimba_tsadc_probe(struct platform_device *pdev) +{ + struct marimba *marimba = platform_get_drvdata(pdev); + struct marimba_tsadc *tsadc; + struct marimba_tsadc_platform_data *pdata = pdev->dev.platform_data; + int rc = 0; + struct device *child; + + printk("%s\n", __func__); + + if (!pdata) { + dev_dbg(&pdev->dev, "no tsadc platform data?\n"); + return -EINVAL; + } + + tsadc = kzalloc(sizeof *tsadc, GFP_KERNEL); + if (!tsadc) + return -ENOMEM; + + tsadc->marimba = marimba; + tsadc->dev = &pdev->dev; + tsadc->pdata = pdata; + + platform_set_drvdata(pdev, tsadc); + + if (tsadc->pdata->init) { + rc = tsadc->pdata->init(); + if (rc < 0) + goto fail_tsadc_init; + } + + if (tsadc->pdata->marimba_tsadc_power) { + rc = tsadc->pdata->marimba_tsadc_power(1); + if (rc) { + pr_err("%s: Unable to power up TSADC \n", __func__); + goto fail_tsadc_power; + } + } + + tsadc->codec_ssbi = clk_get(NULL, "codec_ssbi_clk"); + if (IS_ERR(tsadc->codec_ssbi)) { + rc = PTR_ERR(tsadc->codec_ssbi); + goto fail_clk_get; + } + rc = clk_enable(tsadc->codec_ssbi); + if (rc != 0) + goto fail_clk_enable; + + tsadc->clk_enabled = true; + + child = marimba_add_tssc_subdev(&pdev->dev, "msm_touchscreen", -1, + resources_tssc, ARRAY_SIZE(resources_tssc), + pdata->tssc_data, sizeof(*pdata->tssc_data)); + + if (IS_ERR(child)) { + rc = PTR_ERR(child); + goto fail_add_subdev; + } + + tsadc->child_tssc = child; + platform_set_drvdata(pdev, tsadc); + +#ifdef CONFIG_HAS_EARLYSUSPEND + tsadc->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + + TSADC_SUSPEND_LEVEL; + tsadc->early_suspend.suspend = marimba_tsadc_early_suspend; + tsadc->early_suspend.resume = marimba_tsadc_late_resume; + register_early_suspend(&tsadc->early_suspend); +#endif + + tsadc_dev = tsadc; + device_init_wakeup(&pdev->dev, pdata->can_wakeup); + + return rc; + +fail_add_subdev: + clk_disable(tsadc->codec_ssbi); + +fail_clk_enable: + clk_put(tsadc->codec_ssbi); + +fail_clk_get: + if (tsadc->pdata->marimba_tsadc_power) + rc = tsadc->pdata->marimba_tsadc_power(0); +fail_tsadc_power: + if (tsadc->pdata->exit) + rc = tsadc->pdata->exit(); +fail_tsadc_init: + kfree(tsadc); + return rc; +} + +static int __devexit marimba_tsadc_remove(struct platform_device *pdev) +{ + int rc = 0; + struct marimba_tsadc *tsadc = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + + if (tsadc->clk_enabled == true) + clk_disable(tsadc->codec_ssbi); + + clk_put(tsadc->codec_ssbi); + + if (tsadc->pdata->exit) + rc = tsadc->pdata->exit(); + + if (tsadc->pdata->marimba_tsadc_power) + rc = tsadc->pdata->marimba_tsadc_power(0); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&tsadc->early_suspend); +#endif + + platform_set_drvdata(pdev, NULL); + kfree(tsadc); + return rc; +} + +static struct platform_driver tsadc_driver = { + .probe = marimba_tsadc_probe, + .remove = __devexit_p(marimba_tsadc_remove), + .driver = { + .name = "marimba_tsadc", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &tsadc_pm_ops, +#endif + }, +}; + +static int __init marimba_tsadc_init(void) +{ + return platform_driver_register(&tsadc_driver); +} +device_initcall(marimba_tsadc_init); + +static void __exit marimba_tsadc_exit(void) +{ + return platform_driver_unregister(&tsadc_driver); +} +module_exit(marimba_tsadc_exit); + +MODULE_DESCRIPTION("Marimba TSADC driver"); +MODULE_VERSION("0.1"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:marimba_tsadc"); diff --git a/drivers/mfd/msm-adie-codec.c b/drivers/mfd/msm-adie-codec.c new file mode 100644 index 00000000000..d9414edfe21 --- /dev/null +++ b/drivers/mfd/msm-adie-codec.c @@ -0,0 +1,196 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include + +static const struct adie_codec_operations *cur_adie_ops; + +int adie_codec_register_codec_operations( + const struct adie_codec_operations *adie_ops) +{ + if (adie_ops == NULL) + return -EINVAL; + + if (adie_ops->codec_id != adie_get_detected_codec_type()) + return -EINVAL; + + cur_adie_ops = adie_ops; + pr_info("%s: codec type %d\n", __func__, adie_ops->codec_id); + return 0; +} + +int adie_codec_open(struct adie_codec_dev_profile *profile, + struct adie_codec_path **path_pptr) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_open != NULL) + rc = cur_adie_ops->codec_open(profile, path_pptr); + } else + rc = -ENODEV; + + return rc; +} +EXPORT_SYMBOL(adie_codec_open); + +int adie_codec_close(struct adie_codec_path *path_ptr) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_close != NULL) + rc = cur_adie_ops->codec_close(path_ptr); + } else + rc = -ENODEV; + + return rc; +} +EXPORT_SYMBOL(adie_codec_close); + +int adie_codec_set_device_digital_volume(struct adie_codec_path *path_ptr, + u32 num_channels, u32 vol_percentage /* in percentage */) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_set_device_digital_volume != NULL) { + rc = cur_adie_ops->codec_set_device_digital_volume( + path_ptr, + num_channels, + vol_percentage); + } + } else + rc = -ENODEV; + + return rc; +} +EXPORT_SYMBOL(adie_codec_set_device_digital_volume); + +int adie_codec_set_device_analog_volume(struct adie_codec_path *path_ptr, + u32 num_channels, u32 volume /* in percentage */) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_set_device_analog_volume != NULL) { + rc = cur_adie_ops->codec_set_device_analog_volume( + path_ptr, + num_channels, + volume); + } + } else + rc = -ENODEV; + + return rc; +} +EXPORT_SYMBOL(adie_codec_set_device_analog_volume); + +int adie_codec_setpath(struct adie_codec_path *path_ptr, u32 freq_plan, u32 osr) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_setpath != NULL) { + rc = cur_adie_ops->codec_setpath(path_ptr, + freq_plan, + osr); + } + } else + rc = -ENODEV; + + return rc; +} +EXPORT_SYMBOL(adie_codec_setpath); + +u32 adie_codec_freq_supported(struct adie_codec_dev_profile *profile, + u32 requested_freq) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_freq_supported != NULL) + rc = cur_adie_ops->codec_freq_supported(profile, + requested_freq); + } else + rc = -ENODEV; + + return rc; +} +EXPORT_SYMBOL(adie_codec_freq_supported); + +int adie_codec_enable_sidetone(struct adie_codec_path *rx_path_ptr, + u32 enable) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_enable_sidetone != NULL) + rc = cur_adie_ops->codec_enable_sidetone(rx_path_ptr, + enable); + } else + rc = -ENODEV; + + return rc; +} +EXPORT_SYMBOL(adie_codec_enable_sidetone); + +int adie_codec_enable_anc(struct adie_codec_path *rx_path_ptr, + u32 enable, struct adie_codec_anc_data *calibration_writes) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_enable_anc != NULL) + rc = cur_adie_ops->codec_enable_anc(rx_path_ptr, + enable, calibration_writes); + } + + return rc; +} +EXPORT_SYMBOL(adie_codec_enable_anc); + +int adie_codec_proceed_stage(struct adie_codec_path *path_ptr, u32 state) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_proceed_stage != NULL) + rc = cur_adie_ops->codec_proceed_stage(path_ptr, + state); + } else + rc = -ENODEV; + + return rc; +} +EXPORT_SYMBOL(adie_codec_proceed_stage); + +int adie_codec_set_master_mode(struct adie_codec_path *path_ptr, u8 master) +{ + int rc = -EPERM; + + if (cur_adie_ops != NULL) { + if (cur_adie_ops->codec_set_master_mode != NULL) + rc = cur_adie_ops->codec_set_master_mode(path_ptr, + master); + } else + rc = -ENODEV; + + return rc; +} +EXPORT_SYMBOL(adie_codec_set_master_mode); + + diff --git a/drivers/mfd/msmproc_adc.c b/drivers/mfd/msmproc_adc.c new file mode 100644 index 00000000000..a47486a1055 --- /dev/null +++ b/drivers/mfd/msmproc_adc.c @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#define KELVINMIL_DEGMIL 273160 +#define PM8921_ADC_SLOPE 10 +#define PM8921_ADC_CODE_SCALE 24576 + +static const struct pm8921_adc_map_pt adcmap_batttherm[] = { + {2020, -30}, + {1923, -20}, + {1796, -10}, + {1640, 0}, + {1459, 10}, + {1260, 20}, + {1159, 25}, + {1059, 30}, + {871, 40}, + {706, 50}, + {567, 60}, + {453, 70}, + {364, 80} +}; + +static const struct pm8921_adc_map_pt adcmap_ntcg_104ef_104fb[] = { + {696483, -40960}, + {649148, -39936}, + {605368, -38912}, + {564809, -37888}, + {527215, -36864}, + {492322, -35840}, + {460007, -34816}, + {429982, -33792}, + {402099, -32768}, + {376192, -31744}, + {352075, -30720}, + {329714, -29696}, + {308876, -28672}, + {289480, -27648}, + {271417, -26624}, + {254574, -25600}, + {238903, -24576}, + {224276, -23552}, + {210631, -22528}, + {197896, -21504}, + {186007, -20480}, + {174899, -19456}, + {164521, -18432}, + {154818, -17408}, + {145744, -16384}, + {137265, -15360}, + {129307, -14336}, + {121866, -13312}, + {114896, -12288}, + {108365, -11264}, + {102252, -10240}, + {96499, -9216}, + {91111, -8192}, + {86055, -7168}, + {81308, -6144}, + {76857, -5120}, + {72660, -4096}, + {68722, -3072}, + {65020, -2048}, + {61538, -1024}, + {58261, 0}, + {55177, 1024}, + {52274, 2048}, + {49538, 3072}, + {46962, 4096}, + {44531, 5120}, + {42243, 6144}, + {40083, 7168}, + {38045, 8192}, + {36122, 9216}, + {34308, 10240}, + {32592, 11264}, + {30972, 12288}, + {29442, 13312}, + {27995, 14336}, + {26624, 15360}, + {25333, 16384}, + {24109, 17408}, + {22951, 18432}, + {21854, 19456}, + {20807, 20480}, + {19831, 21504}, + {18899, 22528}, + {18016, 23552}, + {17178, 24576}, + {16384, 25600}, + {15631, 26624}, + {14916, 27648}, + {14237, 28672}, + {13593, 29696}, + {12976, 30720}, + {12400, 31744}, + {11848, 32768}, + {11324, 33792}, + {10825, 34816}, + {10354, 35840}, + {9900, 36864}, + {9471, 37888}, + {9062, 38912}, + {8674, 39936}, + {8306, 40960}, + {7951, 41984}, + {7616, 43008}, + {7296, 44032}, + {6991, 45056}, + {6701, 46080}, + {6424, 47104}, + {6160, 48128}, + {5908, 49152}, + {5667, 50176}, + {5439, 51200}, + {5219, 52224}, + {5010, 53248}, + {4810, 54272}, + {4619, 55296}, + {4440, 56320}, + {4263, 57344}, + {4097, 58368}, + {3938, 59392}, + {3785, 60416}, + {3637, 61440}, + {3501, 62464}, + {3368, 63488}, + {3240, 64512}, + {3118, 65536}, + {2998, 66560}, + {2889, 67584}, + {2782, 68608}, + {2680, 69632}, + {2581, 70656}, + {2490, 71680}, + {2397, 72704}, + {2310, 73728}, + {2227, 74752}, + {2147, 75776}, + {2064, 76800}, + {1998, 77824}, + {1927, 78848}, + {1860, 79872}, + {1795, 80896}, + {1736, 81920}, + {1673, 82944}, + {1615, 83968}, + {1560, 84992}, + {1507, 86016}, + {1456, 87040}, + {1407, 88064}, + {1360, 89088}, + {1314, 90112}, + {1271, 91136}, + {1228, 92160}, + {1189, 93184}, + {1150, 94208}, + {1112, 95232}, + {1076, 96256}, + {1042, 97280}, + {1008, 98304}, + {976, 99328}, + {945, 100352}, + {915, 101376}, + {886, 102400}, + {859, 103424}, + {832, 104448}, + {807, 105472}, + {782, 106496}, + {756, 107520}, + {735, 108544}, + {712, 109568}, + {691, 110592}, + {670, 111616}, + {650, 112640}, + {631, 113664}, + {612, 114688}, + {594, 115712}, + {577, 116736}, + {560, 117760}, + {544, 118784}, + {528, 119808}, + {513, 120832}, + {498, 121856}, + {483, 122880}, + {470, 123904}, + {457, 124928}, + {444, 125952}, + {431, 126976}, + {419, 128000} +}; + +static int32_t pm8921_adc_map_linear(const struct pm8921_adc_map_pt *pts, + uint32_t tablesize, int32_t input, int64_t *output) +{ + bool descending = 1; + uint32_t i = 0; + + if ((pts == NULL) || (output == NULL)) + return -EINVAL; + + /* Check if table is descending or ascending */ + if (tablesize > 1) { + if (pts[0].x < pts[1].x) + descending = 0; + } + + while (i < tablesize) { + if ((descending == 1) && (pts[i].x < input)) { + /* table entry is less than measured + value and table is descending, stop */ + break; + } else if ((descending == 0) && + (pts[i].x > input)) { + /* table entry is greater than measured + value and table is ascending, stop */ + break; + } else { + i++; + } + } + + if (i == 0) + *output = pts[0].y; + else if (i == tablesize) + *output = pts[tablesize-1].y; + else { + /* result is between search_index and search_index-1 */ + /* interpolate linearly */ + *output = (((int32_t) ((pts[i].y - pts[i-1].y)* + (input - pts[i-1].x))/ + (pts[i].x - pts[i-1].x))+ + pts[i-1].y); + } + + return 0; +} + +int32_t pm8921_adc_scale_default(int32_t adc_code, + const struct pm8921_adc_properties *adc_properties, + const struct pm8921_adc_chan_properties *chan_properties, + struct pm8921_adc_chan_result *adc_chan_result) +{ + bool negative_rawfromoffset = 0; + int32_t rawfromoffset = 0; + + if (!chan_properties || !chan_properties->offset_gain_numerator || + !chan_properties->offset_gain_denominator || !adc_properties + || !adc_chan_result) + return -EINVAL; + + rawfromoffset = adc_code - + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].offset; + + adc_chan_result->adc_code = adc_code; + if (rawfromoffset < 0) { + if (adc_properties->bipolar) { + rawfromoffset = -rawfromoffset; + negative_rawfromoffset = 1; + } else { + rawfromoffset = 0; + } + } + + if (rawfromoffset >= 1 << adc_properties->bitresolution) + rawfromoffset = (1 << adc_properties->bitresolution) - 1; + + adc_chan_result->measurement = (int64_t)rawfromoffset * + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dx * + chan_properties->offset_gain_denominator; + + /* do_div only perform positive integer division! */ + do_div(adc_chan_result->measurement, + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dy * + chan_properties->offset_gain_numerator); + + if (negative_rawfromoffset) + adc_chan_result->measurement = -adc_chan_result->measurement; + + /* Note: adc_chan_result->measurement is in the unit of + * adc_properties.adc_reference. For generic channel processing, + * channel measurement is a scale/ratio relative to the adc + * reference input */ + adc_chan_result->physical = (int32_t) adc_chan_result->measurement; + + return 0; +} +EXPORT_SYMBOL_GPL(pm8921_adc_scale_default); + +int32_t pm8921_adc_scale_batt_therm(int32_t adc_code, + const struct pm8921_adc_properties *adc_properties, + const struct pm8921_adc_chan_properties *chan_properties, + struct pm8921_adc_chan_result *adc_chan_result) +{ + int rc; + + rc = pm8921_adc_scale_default(adc_code, adc_properties, chan_properties, + adc_chan_result); + if (rc < 0) { + pr_debug("PM8921 ADC scale default error with %d\n", rc); + return rc; + } + /* convert mV ---> degC using the table */ + return pm8921_adc_map_linear( + adcmap_batttherm, + sizeof(adcmap_batttherm)/sizeof(adcmap_batttherm[0]), + adc_chan_result->physical, + &adc_chan_result->physical); +} +EXPORT_SYMBOL_GPL(pm8921_adc_scale_batt_therm); + +int32_t pm8921_adc_scale_pmic_therm(int32_t adc_code, + const struct pm8921_adc_properties *adc_properties, + const struct pm8921_adc_chan_properties *chan_properties, + struct pm8921_adc_chan_result *adc_chan_result) +{ + int32_t rawfromoffset; + + if (!chan_properties || !chan_properties->offset_gain_numerator || + !chan_properties->offset_gain_denominator || !adc_properties + || !adc_chan_result) + return -EINVAL; + + adc_chan_result->adc_code = adc_code; + rawfromoffset = adc_code - + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].offset; + if (rawfromoffset > 0) { + if (rawfromoffset >= 1 << adc_properties->bitresolution) + rawfromoffset = (1 << adc_properties->bitresolution) + - 1; + /* 2mV/K */ + adc_chan_result->measurement = (int64_t)rawfromoffset* + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dx * + chan_properties->offset_gain_denominator * 1000; + + do_div(adc_chan_result->measurement, + chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dy * + chan_properties->offset_gain_numerator*2); + } else { + adc_chan_result->measurement = 0; + } + /* Note: adc_chan_result->measurement is in the unit of + adc_properties.adc_reference */ + adc_chan_result->physical = (int32_t)adc_chan_result->measurement; + /* Change to .001 deg C */ + adc_chan_result->physical -= KELVINMIL_DEGMIL; + adc_chan_result->measurement <<= 1; + + return 0; +} +EXPORT_SYMBOL_GPL(pm8921_adc_scale_pmic_therm); + +/* Scales the ADC code to 0.001 degrees C using the map + * table for the XO thermistor. + */ +int32_t pm8921_adc_tdkntcg_therm(int32_t adc_code, + const struct pm8921_adc_properties *adc_properties, + const struct pm8921_adc_chan_properties *chan_properties, + struct pm8921_adc_chan_result *adc_chan_result) +{ + uint32_t num1, num2, denom, rt_r25; + int32_t offset = chan_properties->adc_graph->offset, + dy = chan_properties->adc_graph->dy, + dx = chan_properties->adc_graph->dx, + fullscale_calibrated_adc_code; + + adc_chan_result->adc_code = adc_code; + fullscale_calibrated_adc_code = dy + offset; + /* The above is a short cut in math that would reduce a lot of + computation whereas the below expression + (adc_properties->adc_reference*dy+dx*offset+(dx>>1))/dx + is a more generic formula when the 2 reference voltages are + different than 0 and full scale voltage. */ + + if ((dy == 0) || (dx == 0) || + (offset >= fullscale_calibrated_adc_code)) { + return -EINVAL; + } else { + if (adc_code >= fullscale_calibrated_adc_code) { + rt_r25 = (uint32_t)-1; + } else if (adc_code <= offset) { + rt_r25 = 0; + } else { + /* The formula used is (adc_code of current reading - offset)/ + * (the calibrated fullscale adc code - adc_code of current + * reading). For this channel, at this time, chan_properties-> + * offset_gain_numerator = chan_properties-> + * offset_gain_denominator = 1, so no need to incorporate into + * the formula even though it could be multiplied/divided by 1 + * which yields the same result but + * expensive on computation. */ + num1 = (adc_code - offset) << 14; + num2 = (fullscale_calibrated_adc_code - adc_code) >> 1; + denom = fullscale_calibrated_adc_code - adc_code; + + if ((int)denom <= 0) + rt_r25 = 0x7FFFFFFF; + else + rt_r25 = (num1 + num2) / denom; + } + + if (rt_r25 > 0x7FFFFFFF) + rt_r25 = 0x7FFFFFFF; + + pm8921_adc_map_linear(adcmap_ntcg_104ef_104fb, + sizeof(adcmap_ntcg_104ef_104fb)/ + sizeof(adcmap_ntcg_104ef_104fb[0]), + (int32_t)rt_r25, &adc_chan_result->physical); + } + + return 0; +} +EXPORT_SYMBOL_GPL(pm8921_adc_tdkntcg_therm); + +int32_t pm8921_adc_scale_xtern_chgr_cur(int32_t adc_code, + const struct pm8921_adc_properties *adc_properties, + const struct pm8921_adc_chan_properties *chan_properties, + struct pm8921_adc_chan_result *adc_chan_result) +{ + int32_t rawfromoffset = (adc_code - PM8921_ADC_CODE_SCALE) + /PM8921_ADC_SLOPE; + + if (!chan_properties || !chan_properties->offset_gain_numerator || + !chan_properties->offset_gain_denominator || !adc_properties + || !adc_chan_result) + return -EINVAL; + + adc_chan_result->adc_code = adc_code; + if (rawfromoffset > 0) { + if (rawfromoffset >= 1 << adc_properties->bitresolution) + rawfromoffset = (1 << adc_properties->bitresolution) + - 1; + adc_chan_result->measurement = ((int64_t)rawfromoffset * 5)* + chan_properties->offset_gain_denominator; + do_div(adc_chan_result->measurement, + chan_properties->offset_gain_numerator); + } else { + adc_chan_result->measurement = 0; + } + adc_chan_result->physical = (int32_t) adc_chan_result->measurement; + + return 0; +} +EXPORT_SYMBOL_GPL(pm8921_adc_scale_xtern_chgr_cur); + +int32_t pm8921_adc_batt_scaler(struct pm8921_adc_arb_btm_param *btm_param) +{ + /* TODO based on the schematics for the batt thermistor + parameters and the HW/SW doc for the device. This is the + external batt therm */ + + return 0; +} +EXPORT_SYMBOL_GPL(pm8921_adc_batt_scaler); diff --git a/drivers/mfd/pm8921-adc.c b/drivers/mfd/pm8921-adc.c new file mode 100644 index 00000000000..b48b033e17d --- /dev/null +++ b/drivers/mfd/pm8921-adc.c @@ -0,0 +1,1022 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Qualcomm's PM8921 ADC Arbiter driver + */ +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* User Bank register set */ +#define PM8921_ADC_ARB_USRP_CNTRL1 0x197 +#define PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB BIT(0) +#define PM8921_ADC_ARB_USRP_CNTRL1_RSV1 BIT(1) +#define PM8921_ADC_ARB_USRP_CNTRL1_RSV2 BIT(2) +#define PM8921_ADC_ARB_USRP_CNTRL1_RSV3 BIT(3) +#define PM8921_ADC_ARB_USRP_CNTRL1_RSV4 BIT(4) +#define PM8921_ADC_ARB_USRP_CNTRL1_RSV5 BIT(5) +#define PM8921_ADC_ARB_USRP_CNTRL1_EOC BIT(6) +#define PM8921_ADC_ARB_USRP_CNTRL1_REQ BIT(7) + +#define PM8921_ADC_ARB_USRP_AMUX_CNTRL 0x198 +#define PM8921_ADC_ARB_USRP_AMUX_CNTRL_RSV0 BIT(0) +#define PM8921_ADC_ARB_USRP_AMUX_CNTRL_RSV1 BIT(1) +#define PM8921_ADC_ARB_USRP_AMUX_CNTRL_PREMUX0 BIT(2) +#define PM8921_ADC_ARB_USRP_AMUX_CNTRL_PREMUX1 BIT(3) +#define PM8921_ADC_ARB_USRP_AMUX_CNTRL_SEL0 BIT(4) +#define PM8921_ADC_ARB_USRP_AMUX_CNTRL_SEL1 BIT(5) +#define PM8921_ADC_ARB_USRP_AMUX_CNTRL_SEL2 BIT(6) +#define PM8921_ADC_ARB_USRP_AMUX_CNTRL_SEL3 BIT(7) + +#define PM8921_ADC_ARB_USRP_ANA_PARAM 0x199 +#define PM8921_ADC_ARB_USRP_DIG_PARAM 0x19A +#define PM8921_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0 BIT(0) +#define PM8921_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1 BIT(1) +#define PM8921_ADC_ARB_USRP_DIG_PARAM_CLK_RATE0 BIT(2) +#define PM8921_ADC_ARB_USRP_DIG_PARAM_CLK_RATE1 BIT(3) +#define PM8921_ADC_ARB_USRP_DIG_PARAM_EOC BIT(4) +#define PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 BIT(5) +#define PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1 BIT(6) +#define PM8921_ADC_ARB_USRP_DIG_PARAM_EN BIT(7) + +#define PM8921_ADC_ARB_USRP_RSV 0x19B +#define PM8921_ADC_ARB_USRP_RSV_RST BIT(0) +#define PM8921_ADC_ARB_USRP_RSV_DTEST0 BIT(1) +#define PM8921_ADC_ARB_USRP_RSV_DTEST1 BIT(2) +#define PM8921_ADC_ARB_USRP_RSV_OP BIT(3) +#define PM8921_ADC_ARB_USRP_RSV_IP_SEL0 BIT(4) +#define PM8921_ADC_ARB_USRP_RSV_IP_SEL1 BIT(5) +#define PM8921_ADC_ARB_USRP_RSV_IP_SEL2 BIT(6) +#define PM8921_ADC_ARB_USRP_RSV_TRM BIT(7) + +#define PM8921_ADC_ARB_USRP_DATA0 0x19D +#define PM8921_ADC_ARB_USRP_DATA1 0x19C + +#define PM8921_ADC_ARB_BTM_CNTRL1 0x17e +#define PM8921_ADC_ARB_BTM_CNTRL1_EN_BTM BIT(0) +#define PM8921_ADC_ARB_BTM_CNTRL1_SEL_OP_MODE BIT(1) +#define PM8921_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL1 BIT(2) +#define PM8921_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL2 BIT(3) +#define PM8921_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL3 BIT(4) +#define PM8921_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL4 BIT(5) +#define PM8921_ADC_ARB_BTM_CNTRL1_EOC BIT(6) +#define PM8921_ADC_ARB_BTM_CNTRL1_REQ BIT(7) + +#define PM8921_ADC_ARB_BTM_CNTRL2 0x18c +#define PM8921_ADC_ARB_BTM_AMUX_CNTRL 0x17f +#define PM8921_ADC_ARB_BTM_ANA_PARAM 0x180 +#define PM8921_ADC_ARB_BTM_DIG_PARAM 0x181 +#define PM8921_ADC_ARB_BTM_RSV 0x182 +#define PM8921_ADC_ARB_BTM_DATA1 0x183 +#define PM8921_ADC_ARB_BTM_DATA0 0x184 +#define PM8921_ADC_ARB_BTM_BAT_COOL_THR1 0x185 +#define PM8921_ADC_ARB_BTM_BAT_COOL_THR0 0x186 +#define PM8921_ADC_ARB_BTM_BAT_WARM_THR1 0x187 +#define PM8921_ADC_ARB_BTM_BAT_WARM_THR0 0x188 + +#define PM8921_ADC_ARB_SECP_CNTRL 0x190 +#define PM8921_ADC_ARB_IRQ_BLOCK_SEL_SEC 0x1ac +#define PM8921_ADC_ARB_IRQ_CONFIG_SEC 0x1ae +#define PM8921_ADC_ARB_IRQ_BIT_PERM_USR 0x1a6 +#define PM8921_ADC_ARB_IRQ_BLOCK_SEL_USR 0x1c0 +#define PM8921_ADC_ARB_IRQ_CONFIG_USR 0x1c2 + +#define PM8921_ADC_ARB_IRQ_BLOCK_SEL_DATA 0x09 +#define PM8921_ADC_ARB_IRQ_CONFIG_SEC_DATA 0xe0 +#define PM8921_ADC_ARB_IRQ_BIT_PERM_USR_DATA 0x40 +#define PM8921_ADC_ARB_IRQ_BLOCK_SEL_USR_DATA 0x09 +#define PM8921_ADC_ARB_IRQ_CONFIG_USR_DATA 0xe0 + +#define PM8921_ADC_ARB_ANA_DIG 0xa0 +#define PM8921_ADC_ARB_SECP_CNTRL_WR 0x31 + +#define PM8921_ADC_AMUX_MPP_SEL 2 +#define PM8921_ADC_AMUX_SEL 4 +#define PM8921_ADC_RSV_IP_SEL 4 +#define PM8921_ADC_BTM_CHANNEL_SEL 4 +#define PM8921_MAX_CHANNEL_PROPERTIES 2 +#define PM8921_ADC_IRQ_0 0 +#define PM8921_ADC_IRQ_1 1 +#define PM8921_ADC_IRQ_2 2 +#define PM8921_ADC_BTM_INTERVAL_SEL 5 +#define PM8921_ADC_BTM_DECIMATION_SEL 5 +#define PM8921_ADC_MUL 10 +#define PM8921_ADC_CONV_TIME_MIN 2000 +#define PM8921_ADC_CONV_TIME_MAX 2100 + +struct pm8921_adc { + struct device *dev; + struct pm8921_adc_properties *adc_prop; + int adc_irq; + struct mutex adc_lock; + struct mutex btm_lock; + uint32_t adc_num_channel; + struct completion adc_rslt_completion; + struct pm8921_adc_amux *adc_channel; + struct pm8921_adc_amux_properties *conv; + struct pm8921_adc_arb_btm *batt; + int btm_warm_irq; + int btm_cold_irq; + struct dentry *dent; +}; + +struct pm8921_adc_amux_properties { + uint32_t amux_channel; + uint32_t decimation; + uint32_t amux_ip_rsv; + uint32_t amux_mpp_channel; + struct pm8921_adc_chan_properties *chan_prop; +}; + +static const struct pm8921_adc_scaling_ratio pm8921_amux_scaling_ratio[] = { + {1, 1}, + {1, 3}, + {1, 4}, + {1, 6} +}; + +static struct pm8921_adc *pmic_adc; + +static struct pm8921_adc_scale_fn adc_scale_fn[] = { + [ADC_SCALE_DEFAULT] = {pm8921_adc_scale_default}, + [ADC_SCALE_BATT_THERM] = {pm8921_adc_scale_batt_therm}, + [ADC_SCALE_PMIC_THERM] = {pm8921_adc_scale_pmic_therm}, + [ADC_SCALE_XTERN_CHGR_CUR] = {pm8921_adc_scale_xtern_chgr_cur}, +}; + +static bool pm8921_adc_calib_first_adc, pm8921_btm_calib_first_adc; +static bool pm8921_adc_initialized, pm8921_adc_calib_device_init; + +static int32_t pm8921_adc_arb_cntrl(uint32_t arb_cntrl) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + int i, rc; + u8 data_arb_cntrl = 0; + + if (arb_cntrl) + data_arb_cntrl |= (PM8921_ADC_ARB_USRP_CNTRL1_REQ | + PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB); + + /* Write twice to the CNTRL register for the arbiter settings + to take into effect */ + for (i = 0; i < 2; i++) { + rc = pm8xxx_writeb(adc_pmic->dev->parent, + PM8921_ADC_ARB_USRP_CNTRL1, data_arb_cntrl); + if (rc < 0) { + pr_err("PM8921 arb cntrl write failed with %d\n", rc); + return rc; + } + } + + return 0; +} + +static uint32_t pm8921_adc_read_reg(uint32_t reg, u8 *data) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + int rc; + + rc = pm8xxx_readb(adc_pmic->dev->parent, reg, data); + if (rc < 0) { + pr_err("PM8921 adc read reg %d failed with %d\n", reg, rc); + return rc; + } + + return 0; +} + +static uint32_t pm8921_adc_write_reg(uint32_t reg, u8 data) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + int rc; + + rc = pm8xxx_writeb(adc_pmic->dev->parent, reg, data); + if (rc < 0) { + pr_err("PM8921 adc write reg %d failed with %d\n", reg, rc); + return rc; + } + + return 0; +} + +static int32_t pm8921_adc_configure( + struct pm8921_adc_amux_properties *chan_prop) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + u8 data_amux_chan = 0, data_arb_rsv = 0, data_dig_param = 0; + int rc, i; + + for (i = 0; i < 2; i++) { + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_SECP_CNTRL, + PM8921_ADC_ARB_SECP_CNTRL_WR); + if (rc < 0) + return rc; + } + + data_amux_chan |= chan_prop->amux_channel << PM8921_ADC_AMUX_SEL; + + if (chan_prop->amux_mpp_channel) + data_amux_chan |= chan_prop->amux_mpp_channel << + PM8921_ADC_AMUX_MPP_SEL; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_USRP_AMUX_CNTRL, + data_amux_chan); + if (rc < 0) + return rc; + + data_arb_rsv &= (PM8921_ADC_ARB_USRP_RSV_RST | + PM8921_ADC_ARB_USRP_RSV_DTEST0 | + PM8921_ADC_ARB_USRP_RSV_DTEST1 | + PM8921_ADC_ARB_USRP_RSV_OP | + PM8921_ADC_ARB_USRP_RSV_TRM); + data_arb_rsv |= chan_prop->amux_ip_rsv << PM8921_ADC_RSV_IP_SEL; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_USRP_RSV, data_arb_rsv); + if (rc < 0) + return rc; + + rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_DIG_PARAM, + &data_dig_param); + if (rc < 0) + return rc; + + /* Default 2.4Mhz clock rate */ + /* Client chooses the decimation */ + switch (chan_prop->decimation) { + case ADC_DECIMATION_TYPE1: + data_dig_param |= PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0; + break; + case ADC_DECIMATION_TYPE2: + data_dig_param |= (PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0 + | PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1); + break; + default: + data_dig_param |= PM8921_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0; + break; + } + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_USRP_DIG_PARAM, + PM8921_ADC_ARB_ANA_DIG); + if (rc < 0) + return rc; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_USRP_ANA_PARAM, + PM8921_ADC_ARB_ANA_DIG); + if (rc < 0) + return rc; + + if (!pm8921_adc_calib_first_adc) + enable_irq(adc_pmic->adc_irq); + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_IRQ_BLOCK_SEL_SEC, + PM8921_ADC_ARB_IRQ_BLOCK_SEL_DATA); + if (rc < 0) + return rc; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_IRQ_CONFIG_SEC, + PM8921_ADC_ARB_IRQ_CONFIG_SEC_DATA); + if (rc < 0) + return rc; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_IRQ_BIT_PERM_USR, + PM8921_ADC_ARB_IRQ_BIT_PERM_USR_DATA); + if (rc < 0) + return rc; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_IRQ_BLOCK_SEL_USR, + PM8921_ADC_ARB_IRQ_BLOCK_SEL_USR_DATA); + if (rc < 0) + return rc; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_IRQ_CONFIG_USR, + PM8921_ADC_ARB_IRQ_CONFIG_USR_DATA); + if (rc < 0) + return rc; + + rc = pm8921_adc_arb_cntrl(1); + if (rc < 0) { + pr_err("Configuring ADC Arbiter" + "enable failed with %d\n", rc); + return rc; + } + + return 0; +} + +static uint32_t pm8921_adc_read_adc_code(int32_t *data) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + uint8_t rslt_lsb, rslt_msb; + int32_t rc, max_ideal_adc_code = 1 << adc_pmic->adc_prop->bitresolution; + + rc = pm8xxx_readb(adc_pmic->dev->parent, + PM8921_ADC_ARB_USRP_DATA0, &rslt_lsb); + if (rc < 0) { + pr_err("PM8921 adc result read failed with %d\n", rc); + return rc; + } + + rc = pm8xxx_readb(adc_pmic->dev->parent, + PM8921_ADC_ARB_USRP_DATA1, &rslt_msb); + if (rc < 0) { + pr_err("PM8921 adc result read failed with %d\n", rc); + return rc; + } + + *data = (rslt_msb << 8) | rslt_lsb; + + /* Use the midpoint to determine underflow or overflow */ + if (*data > max_ideal_adc_code + (max_ideal_adc_code >> 1)) + *data |= ((1 << (8 * sizeof(*data) - + adc_pmic->adc_prop->bitresolution)) - 1) << + adc_pmic->adc_prop->bitresolution; + + /* Default value for switching off the arbiter after reading + the ADC value. Bit 0 set to 0. */ + rc = pm8921_adc_arb_cntrl(0); + if (rc < 0) { + pr_err("%s: Configuring ADC Arbiter disable" + "failed\n", __func__); + return rc; + } + + return 0; +} + +static irqreturn_t pm8921_adc_isr(int irq, void *dev_id) +{ + struct pm8921_adc *adc_8921 = dev_id; + + disable_irq_nosync(adc_8921->adc_irq); + + if (pm8921_adc_calib_first_adc) + return IRQ_HANDLED; + /* TODO Handle spurius interrupt condition */ + complete(&adc_8921->adc_rslt_completion); + + return IRQ_HANDLED; +} + +static irqreturn_t pm8921_btm_warm_isr(int irq, void *dev_id) +{ + struct pm8921_adc *btm_8921 = dev_id; + + disable_irq_nosync(btm_8921->btm_warm_irq); + + if (pm8921_btm_calib_first_adc) + return IRQ_HANDLED; + + if (btm_8921->batt->btm_param->btm_warm_fn != NULL) + btm_8921->batt->btm_param->btm_warm_fn(); + + return IRQ_HANDLED; +} + +static irqreturn_t pm8921_btm_cold_isr(int irq, void *dev_id) +{ + struct pm8921_adc *btm_8921 = dev_id; + + disable_irq_nosync(btm_8921->btm_cold_irq); + + if (pm8921_btm_calib_first_adc) + return IRQ_HANDLED; + + if (btm_8921->batt->btm_param->btm_cold_fn != NULL) + btm_8921->batt->btm_param->btm_cold_fn(); + + return IRQ_HANDLED; +} + +static uint32_t pm8921_adc_calib_device(void) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + struct pm8921_adc_amux_properties conv; + int rc, offset_adc, slope_adc, calib_read_1, calib_read_2; + u8 data_arb_usrp_cntrl1 = 0; + + conv.amux_channel = CHANNEL_125V; + conv.decimation = ADC_DECIMATION_TYPE2; + conv.amux_ip_rsv = AMUX_RSV1; + conv.amux_mpp_channel = PREMUX_MPP_SCALE_0; + pm8921_adc_calib_first_adc = true; + rc = pm8921_adc_configure(&conv); + if (rc) { + pr_err("pm8921_adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (data_arb_usrp_cntrl1 != (PM8921_ADC_ARB_USRP_CNTRL1_EOC | + PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB)) { + rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_CNTRL1, + &data_arb_usrp_cntrl1); + if (rc < 0) + return rc; + usleep_range(PM8921_ADC_CONV_TIME_MIN, + PM8921_ADC_CONV_TIME_MAX); + } + data_arb_usrp_cntrl1 = 0; + + rc = pm8921_adc_read_adc_code(&calib_read_1); + if (rc) { + pr_err("pm8921_adc read adc failed with %d\n", rc); + pm8921_adc_calib_first_adc = false; + goto calib_fail; + } + pm8921_adc_calib_first_adc = false; + + conv.amux_channel = CHANNEL_625MV; + conv.decimation = ADC_DECIMATION_TYPE2; + conv.amux_ip_rsv = AMUX_RSV1; + conv.amux_mpp_channel = PREMUX_MPP_SCALE_0; + pm8921_adc_calib_first_adc = true; + rc = pm8921_adc_configure(&conv); + if (rc) { + pr_err("pm8921_adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (data_arb_usrp_cntrl1 != (PM8921_ADC_ARB_USRP_CNTRL1_EOC | + PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB)) { + rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_CNTRL1, + &data_arb_usrp_cntrl1); + if (rc < 0) + return rc; + usleep_range(PM8921_ADC_CONV_TIME_MIN, + PM8921_ADC_CONV_TIME_MAX); + } + data_arb_usrp_cntrl1 = 0; + + rc = pm8921_adc_read_adc_code(&calib_read_2); + if (rc) { + pr_err("pm8921_adc read adc failed with %d\n", rc); + pm8921_adc_calib_first_adc = false; + goto calib_fail; + } + pm8921_adc_calib_first_adc = false; + + slope_adc = (((calib_read_1 - calib_read_2) << PM8921_ADC_MUL)/ + PM8921_CHANNEL_ADC_625_MV); + offset_adc = calib_read_2 - + ((slope_adc * PM8921_CHANNEL_ADC_625_MV) >> + PM8921_ADC_MUL); + + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].offset + = offset_adc; + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].dy = + (calib_read_1 - calib_read_2); + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].dx + = PM8921_CHANNEL_ADC_625_MV; + rc = pm8921_adc_arb_cntrl(0); + if (rc < 0) { + pr_err("%s: Configuring ADC Arbiter disable" + "failed\n", __func__); + return rc; + } + /* Ratiometric Calibration */ + conv.amux_channel = CHANNEL_MUXOFF; + conv.decimation = ADC_DECIMATION_TYPE2; + conv.amux_ip_rsv = AMUX_RSV5; + conv.amux_mpp_channel = PREMUX_MPP_SCALE_0; + pm8921_adc_calib_first_adc = true; + rc = pm8921_adc_configure(&conv); + if (rc) { + pr_err("pm8921_adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (data_arb_usrp_cntrl1 != (PM8921_ADC_ARB_USRP_CNTRL1_EOC | + PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB)) { + rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_CNTRL1, + &data_arb_usrp_cntrl1); + if (rc < 0) + return rc; + usleep_range(PM8921_ADC_CONV_TIME_MIN, + PM8921_ADC_CONV_TIME_MAX); + } + data_arb_usrp_cntrl1 = 0; + + rc = pm8921_adc_read_adc_code(&calib_read_1); + if (rc) { + pr_err("pm8921_adc read adc failed with %d\n", rc); + pm8921_adc_calib_first_adc = false; + goto calib_fail; + } + pm8921_adc_calib_first_adc = false; + + conv.amux_channel = CHANNEL_MUXOFF; + conv.decimation = ADC_DECIMATION_TYPE2; + conv.amux_ip_rsv = AMUX_RSV4; + conv.amux_mpp_channel = PREMUX_MPP_SCALE_0; + pm8921_adc_calib_first_adc = true; + rc = pm8921_adc_configure(&conv); + if (rc) { + pr_err("pm8921_adc configure failed with %d\n", rc); + goto calib_fail; + } + + while (data_arb_usrp_cntrl1 != (PM8921_ADC_ARB_USRP_CNTRL1_EOC | + PM8921_ADC_ARB_USRP_CNTRL1_EN_ARB)) { + rc = pm8921_adc_read_reg(PM8921_ADC_ARB_USRP_CNTRL1, + &data_arb_usrp_cntrl1); + if (rc < 0) + return rc; + usleep_range(PM8921_ADC_CONV_TIME_MIN, + PM8921_ADC_CONV_TIME_MAX); + } + data_arb_usrp_cntrl1 = 0; + + rc = pm8921_adc_read_adc_code(&calib_read_2); + if (rc) { + pr_err("pm8921_adc read adc failed with %d\n", rc); + pm8921_adc_calib_first_adc = false; + goto calib_fail; + } + pm8921_adc_calib_first_adc = false; + + slope_adc = (((calib_read_1 - calib_read_2) << PM8921_ADC_MUL)/ + adc_pmic->adc_prop->adc_vdd_reference); + offset_adc = calib_read_2 - + ((slope_adc * adc_pmic->adc_prop->adc_vdd_reference) + >> PM8921_ADC_MUL); + + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].offset + = offset_adc; + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].dy = + (calib_read_1 - calib_read_2); + adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].dx = + adc_pmic->adc_prop->adc_vdd_reference; +calib_fail: + rc = pm8921_adc_arb_cntrl(0); + if (rc < 0) { + pr_err("%s: Configuring ADC Arbiter disable" + "failed\n", __func__); + } + + return rc; +} + +uint32_t pm8921_adc_read(enum pm8921_adc_channels channel, + struct pm8921_adc_chan_result *result) +{ + return pm8921_adc_mpp_read(channel, result, PREMUX_MPP_SCALE_0); +} +EXPORT_SYMBOL_GPL(pm8921_adc_read); + +uint32_t pm8921_adc_mpp_read(enum pm8921_adc_mpp_channels channel, + struct pm8921_adc_chan_result *result, + enum pm8921_adc_premux_mpp_scale_type mpp_scale) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + int i = 0, rc, amux_prescaling, scale_type; + + if (!pm8921_adc_initialized) + return -ENODEV; + + if (!pm8921_adc_calib_device_init) { + if (pm8921_adc_calib_device() == 0) + pm8921_adc_calib_device_init = true; + } + + mutex_lock(&adc_pmic->adc_lock); + + for (i = 0; i < adc_pmic->adc_num_channel; i++) { + if (channel == adc_pmic->adc_channel[i].channel_name) + break; + } + + if (i == adc_pmic->adc_num_channel) { + mutex_unlock(&adc_pmic->adc_lock); + return -EBADF; /* unknown channel */ + } + + adc_pmic->conv->amux_channel = i; + adc_pmic->conv->amux_mpp_channel = mpp_scale; + + adc_pmic->conv->amux_ip_rsv = adc_pmic->adc_channel[i].adc_rsv; + adc_pmic->conv->decimation = adc_pmic->adc_channel[i].adc_decimation; + amux_prescaling = adc_pmic->adc_channel[i].chan_path_prescaling; + + adc_pmic->conv->chan_prop->offset_gain_numerator = + pm8921_amux_scaling_ratio[amux_prescaling].num; + adc_pmic->conv->chan_prop->offset_gain_denominator = + pm8921_amux_scaling_ratio[amux_prescaling].den; + + rc = pm8921_adc_configure(adc_pmic->conv); + if (rc) { + mutex_unlock(&adc_pmic->adc_lock); + return -EINVAL; + } + + wait_for_completion(&adc_pmic->adc_rslt_completion); + + rc = pm8921_adc_read_adc_code(&result->adc_code); + if (rc) { + mutex_unlock(&adc_pmic->adc_lock); + return -EINVAL; + } + + scale_type = adc_pmic->adc_channel[i].adc_scale_fn; + if (scale_type >= ADC_SCALE_NONE) { + mutex_unlock(&adc_pmic->adc_lock); + return -EBADF; + } + + adc_scale_fn[scale_type].chan(result->adc_code, + adc_pmic->adc_prop, adc_pmic->conv->chan_prop, result); + + mutex_unlock(&adc_pmic->adc_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(pm8921_adc_mpp_read); + +uint32_t pm8921_adc_btm_configure(struct pm8921_adc_arb_btm_param *btm_param) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + u8 data_btm_cool_thr0, data_btm_cool_thr1; + u8 data_btm_warm_thr0, data_btm_warm_thr1; + u8 arb_btm_cntrl1; + int rc; + + mutex_lock(&adc_pmic->btm_lock); + + data_btm_cool_thr0 = ((btm_param->low_thr_voltage << 24) >> 24); + data_btm_cool_thr1 = ((btm_param->low_thr_voltage << 16) >> 24); + data_btm_warm_thr0 = ((btm_param->high_thr_voltage << 24) >> 24); + data_btm_warm_thr1 = ((btm_param->high_thr_voltage << 16) >> 24); + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_BAT_COOL_THR0, + data_btm_cool_thr0); + if (rc < 0) + goto write_err; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_BAT_COOL_THR1, + data_btm_cool_thr0); + if (rc < 0) + goto write_err; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_BAT_WARM_THR0, + data_btm_warm_thr0); + if (rc < 0) + goto write_err; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_BAT_WARM_THR1, + data_btm_warm_thr1); + if (rc < 0) + goto write_err; + + arb_btm_cntrl1 = btm_param->interval << PM8921_ADC_BTM_INTERVAL_SEL; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_CNTRL1, arb_btm_cntrl1); + if (rc < 0) + goto write_err; + + adc_pmic->batt->btm_param->btm_warm_fn = btm_param->btm_warm_fn; + adc_pmic->batt->btm_param->btm_cold_fn = btm_param->btm_cold_fn; + + mutex_unlock(&adc_pmic->btm_lock); + + return rc; + +write_err: + mutex_unlock(&adc_pmic->btm_lock); + return rc; +} +EXPORT_SYMBOL_GPL(pm8921_adc_btm_configure); + +static uint32_t pm8921_adc_btm_read(uint32_t channel) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + int rc, i; + u8 arb_btm_dig_param, arb_btm_ana_param, arb_btm_rsv; + u8 arb_btm_amux_cntrl, arb_btm_decimation, data_arb_btm_cntrl; + + arb_btm_amux_cntrl = channel << PM8921_ADC_BTM_CHANNEL_SEL; + arb_btm_rsv = adc_pmic->adc_channel[channel].adc_rsv; + arb_btm_decimation = + adc_pmic->adc_channel[channel].adc_decimation; + arb_btm_ana_param = PM8921_ADC_ARB_ANA_DIG; + + mutex_lock(&adc_pmic->btm_lock); + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_AMUX_CNTRL, + arb_btm_amux_cntrl); + if (rc < 0) + goto write_err; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_RSV, arb_btm_rsv); + if (rc < 0) + goto write_err; + + arb_btm_dig_param = arb_btm_decimation << + PM8921_ADC_BTM_DECIMATION_SEL; + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_DIG_PARAM, + arb_btm_dig_param); + if (rc < 0) + goto write_err; + + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_ANA_PARAM, + arb_btm_ana_param); + if (rc < 0) + goto write_err; + + data_arb_btm_cntrl = PM8921_ADC_ARB_BTM_CNTRL1_EOC | + PM8921_ADC_ARB_BTM_CNTRL1_EN_BTM; + + /* Write twice to the CNTRL register for the arbiter settings + to take into effect */ + for (i = 0; i < 2; i++) { + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_CNTRL1, + data_arb_btm_cntrl); + if (rc < 0) + goto write_err; + } + + mutex_unlock(&adc_pmic->btm_lock); + + return 0; + +write_err: + mutex_unlock(&adc_pmic->btm_lock); + return rc; +} + +uint32_t pm8921_adc_btm_start(void) +{ + int rc; + + rc = pm8921_adc_btm_read(CHANNEL_BATT_THERM); + return rc; +} +EXPORT_SYMBOL_GPL(pm8921_adc_btm_start); + +uint32_t pm8921_adc_btm_end(void) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + int i, rc; + u8 data_arb_btm_cntrl; + + /* Set BTM registers to Disable mode */ + data_arb_btm_cntrl = PM8921_ADC_ARB_BTM_CNTRL1_EOC; + + mutex_lock(&adc_pmic->btm_lock); + /* Write twice to the CNTRL register for the arbiter settings + to take into effect */ + for (i = 0; i < 2; i++) { + rc = pm8921_adc_write_reg(PM8921_ADC_ARB_BTM_CNTRL1, + data_arb_btm_cntrl); + if (rc < 0) { + mutex_unlock(&adc_pmic->btm_lock); + return rc; + } + } + mutex_unlock(&adc_pmic->btm_lock); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8921_adc_btm_end); + +static int get_adc(void *data, u64 *val) +{ + struct pm8921_adc_chan_result result; + int i = (int)data; + int rc; + + rc = pm8921_adc_read(i, &result); + + pr_info("ADC value raw:%x physical:%lld\n", + result.adc_code, result.physical); + *val = result.physical; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_adc, NULL, "%llu\n"); + +#ifdef CONFIG_DEBUG_FS +static void create_debugfs_entries(void) +{ + pmic_adc->dent = debugfs_create_dir("pm8921_adc", NULL); + + if (IS_ERR(pmic_adc->dent)) { + pr_err("pmic adc debugfs dir not created\n"); + return; + } + + debugfs_create_file("vbat", 0644, pmic_adc->dent, + (void *)CHANNEL_VBAT, ®_fops); + debugfs_create_file("625mv", 0644, pmic_adc->dent, + (void *)CHANNEL_625MV, ®_fops); + debugfs_create_file("125v", 0644, pmic_adc->dent, + (void *)CHANNEL_125V, ®_fops); + debugfs_create_file("die_temp", 0644, pmic_adc->dent, + (void *)CHANNEL_DIE_TEMP, ®_fops); + debugfs_create_file("vcoin", 0644, pmic_adc->dent, + (void *)CHANNEL_VCOIN, ®_fops); + debugfs_create_file("dc_in", 0644, pmic_adc->dent, + (void *)CHANNEL_DCIN, ®_fops); + debugfs_create_file("vph_pwr", 0644, pmic_adc->dent, + (void *)CHANNEL_VPH_PWR, ®_fops); + debugfs_create_file("usb_in", 0644, pmic_adc->dent, + (void *)CHANNEL_USBIN, ®_fops); + debugfs_create_file("batt_therm", 0644, pmic_adc->dent, + (void *)CHANNEL_BATT_THERM, ®_fops); + debugfs_create_file("batt_id", 0644, pmic_adc->dent, + (void *)CHANNEL_BATT_ID, ®_fops); + debugfs_create_file("chg_temp", 0644, pmic_adc->dent, + (void *)CHANNEL_CHG_TEMP, ®_fops); + debugfs_create_file("charger_current", 0644, pmic_adc->dent, + (void *)CHANNEL_ICHG, ®_fops); + debugfs_create_file("ibat", 0644, pmic_adc->dent, + (void *)CHANNEL_IBAT, ®_fops); +} +#else +static inline void create_debugfs_entries(void) +{ +} +#endif + +static int __devexit pm8921_adc_teardown(struct platform_device *pdev) +{ + struct pm8921_adc *adc_pmic = pmic_adc; + + device_init_wakeup(&pdev->dev, 0); + free_irq(adc_pmic->adc_irq, adc_pmic); + free_irq(adc_pmic->btm_warm_irq, adc_pmic); + free_irq(adc_pmic->btm_cold_irq, adc_pmic); + platform_set_drvdata(pdev, NULL); + pmic_adc = NULL; + kfree(adc_pmic->conv->chan_prop); + kfree(adc_pmic->adc_channel); + kfree(adc_pmic); + pm8921_adc_initialized = false; + + return 0; +} + +static int __devinit pm8921_adc_probe(struct platform_device *pdev) +{ + const struct pm8921_adc_platform_data *pdata = pdev->dev.platform_data; + struct pm8921_adc *adc_pmic; + struct pm8921_adc_amux_properties *adc_amux_prop; + struct pm8921_adc_chan_properties *adc_pmic_chanprop; + struct pm8921_adc_amux *adc_amux; + int rc = 0; + + if (!pdata) { + dev_err(&pdev->dev, "no platform data?\n"); + return -EINVAL; + } + + adc_pmic = kzalloc(sizeof(struct pm8921_adc), + GFP_KERNEL); + if (!adc_pmic) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + adc_amux_prop = kzalloc(sizeof(struct pm8921_adc_amux_properties), + GFP_KERNEL); + if (!adc_amux_prop) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + adc_amux = kzalloc(sizeof(struct pm8921_adc_amux), + GFP_KERNEL); + if (!adc_amux) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + adc_pmic_chanprop = kzalloc(sizeof(struct pm8921_adc_chan_properties), + GFP_KERNEL); + if (!adc_pmic_chanprop) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + adc_pmic->dev = &pdev->dev; + adc_pmic->adc_prop = pdata->adc_prop; + adc_pmic->conv = adc_amux_prop; + adc_pmic->conv->chan_prop = adc_pmic_chanprop; + + init_completion(&adc_pmic->adc_rslt_completion); + adc_amux = pdata->adc_channel; + adc_pmic->adc_channel = adc_amux; + adc_pmic->adc_num_channel = pdata->adc_num_channel; + + mutex_init(&adc_pmic->adc_lock); + mutex_init(&adc_pmic->btm_lock); + + adc_pmic->adc_irq = platform_get_irq(pdev, PM8921_ADC_IRQ_0); + if (adc_pmic->adc_irq < 0) { + rc = -ENXIO; + goto err_cleanup; + } + + rc = request_irq(adc_pmic->adc_irq, + pm8921_adc_isr, + IRQF_TRIGGER_RISING, "pm8921_adc_interrupt", adc_pmic); + if (rc) { + dev_err(&pdev->dev, "failed to request adc irq " + "with error %d\n", rc); + goto err_cleanup; + } + + disable_irq_nosync(adc_pmic->adc_irq); + + adc_pmic->btm_warm_irq = platform_get_irq(pdev, PM8921_ADC_IRQ_1); + if (adc_pmic->btm_warm_irq < 0) { + rc = -ENXIO; + goto err_cleanup; + } + + rc = request_irq(adc_pmic->btm_warm_irq, + pm8921_btm_warm_isr, + IRQF_TRIGGER_RISING, "pm8921_btm_warm_interrupt", adc_pmic); + if (rc) { + pr_err("btm warm irq failed %d with interrupt number %d\n", + rc, adc_pmic->btm_warm_irq); + dev_err(&pdev->dev, "failed to request btm irq\n"); + goto err_cleanup; + } + + disable_irq_nosync(adc_pmic->btm_warm_irq); + + adc_pmic->btm_cold_irq = platform_get_irq(pdev, PM8921_ADC_IRQ_2); + if (adc_pmic->btm_cold_irq < 0) { + rc = -ENXIO; + goto err_cleanup; + } + + rc = request_irq(adc_pmic->btm_cold_irq, + pm8921_btm_cold_isr, + IRQF_TRIGGER_RISING, "pm8921_btm_cold_interrupt", adc_pmic); + if (rc) { + pr_err("btm cold irq failed with return %d and number %d\n", + rc, adc_pmic->btm_cold_irq); + dev_err(&pdev->dev, "failed to request btm irq\n"); + goto err_cleanup; + } + + disable_irq_nosync(adc_pmic->btm_cold_irq); + device_init_wakeup(&pdev->dev, pdata->adc_wakeup); + platform_set_drvdata(pdev, adc_pmic); + pmic_adc = adc_pmic; + + create_debugfs_entries(); + pm8921_adc_calib_first_adc = false; + pm8921_btm_calib_first_adc = false; + pm8921_adc_calib_device_init = false; + pm8921_adc_initialized = true; + return 0; + +err_cleanup: + pm8921_adc_teardown(pdev); + return rc; +} + +static struct platform_driver pm8921_adc_driver = { + .probe = pm8921_adc_probe, + .remove = __devexit_p(pm8921_adc_teardown), + .driver = { + .name = PM8921_ADC_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8921_adc_init(void) +{ + return platform_driver_register(&pm8921_adc_driver); +} +module_init(pm8921_adc_init); + +static void __exit pm8921_adc_exit(void) +{ + platform_driver_unregister(&pm8921_adc_driver); +} +module_exit(pm8921_adc_exit); + +MODULE_ALIAS("platform:" PM8921_ADC_DEV_NAME); +MODULE_DESCRIPTION("PMIC8921 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c index e873b15753d..f088dc17018 100644 --- a/drivers/mfd/pm8921-core.c +++ b/drivers/mfd/pm8921-core.c @@ -21,13 +21,38 @@ #include #include #include +#include #define REG_HWREV 0x002 /* PMIC4 revision */ #define REG_HWREV_2 0x0E8 /* PMIC4 revision 2 */ +#define REG_MPP_BASE 0x050 + +#define REG_TEMP_ALARM_CTRL 0x1B +#define REG_TEMP_ALARM_PWM 0x9B + +#define REG_BATT_ALARM_THRESH 0x023 +#define REG_BATT_ALARM_CTRL1 0x024 +#define REG_BATT_ALARM_CTRL2 0x0AA +#define REG_BATT_ALARM_PWM_CTRL 0x0A3 + +#define PM8921_VERSION_MASK 0xFFF0 +#define PM8921_VERSION_VALUE 0x06F0 +#define PM8921_REVISION_MASK 0x000F + +#define SINGLE_IRQ_RESOURCE(_name, _irq) \ +{ \ + .name = _name, \ + .start = _irq, \ + .end = _irq, \ + .flags = IORESOURCE_IRQ, \ +} + struct pm8921 { struct device *dev; struct pm_irq_chip *irq_chip; + struct mfd_cell *mfd_regulators; + u32 rev_registers; }; static int pm8921_readb(const struct device *dev, u16 addr, u8 *val) @@ -72,25 +97,258 @@ static int pm8921_read_irq_stat(const struct device *dev, int irq) return pm8xxx_get_irq_stat(pmic->irq_chip, irq); } +static enum pm8xxx_version pm8921_get_version(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); + const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; + enum pm8xxx_version version = -ENODEV; + + if ((pmic->rev_registers & PM8921_VERSION_MASK) == PM8921_VERSION_VALUE) + version = PM8XXX_VERSION_8921; + + return version; +} + +static int pm8921_get_revision(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); + const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; + + return pmic->rev_registers & PM8921_REVISION_MASK; +} + static struct pm8xxx_drvdata pm8921_drvdata = { .pmic_readb = pm8921_readb, .pmic_writeb = pm8921_writeb, .pmic_read_buf = pm8921_read_buf, .pmic_write_buf = pm8921_write_buf, .pmic_read_irq_stat = pm8921_read_irq_stat, + .pmic_get_version = pm8921_get_version, + .pmic_get_revision = pm8921_get_revision, +}; + +static const struct resource gpio_cell_resources[] __devinitconst = { + [0] = { + .start = PM8921_IRQ_BLOCK_BIT(PM8921_GPIO_BLOCK_START, 0), + .end = PM8921_IRQ_BLOCK_BIT(PM8921_GPIO_BLOCK_START, 0) + + PM8921_NR_GPIOS - 1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell gpio_cell __devinitdata = { + .name = PM8XXX_GPIO_DEV_NAME, + .id = -1, + .resources = gpio_cell_resources, + .num_resources = ARRAY_SIZE(gpio_cell_resources), +}; + +static const struct resource adc_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE(NULL, PM8921_ADC_EOC_USR_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8921_ADC_BATT_TEMP_WARM_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8921_ADC_BATT_TEMP_COLD_IRQ), +}; + +static struct mfd_cell adc_cell __devinitdata = { + .name = PM8921_ADC_DEV_NAME, + .id = -1, + .resources = adc_cell_resources, + .num_resources = ARRAY_SIZE(adc_cell_resources), +}; + +static const struct resource mpp_cell_resources[] __devinitconst = { + { + .start = PM8921_IRQ_BLOCK_BIT(PM8921_MPP_BLOCK_START, 0), + .end = PM8921_IRQ_BLOCK_BIT(PM8921_MPP_BLOCK_START, 0) + + PM8921_NR_MPPS - 1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell mpp_cell __devinitdata = { + .name = PM8XXX_MPP_DEV_NAME, + .id = -1, + .resources = mpp_cell_resources, + .num_resources = ARRAY_SIZE(mpp_cell_resources), +}; + +static const struct resource rtc_cell_resources[] __devinitconst = { + [0] = SINGLE_IRQ_RESOURCE(NULL, PM8921_RTC_ALARM_IRQ), + [1] = { + .name = "pmic_rtc_base", + .start = PM8921_RTC_BASE, + .end = PM8921_RTC_BASE, + .flags = IORESOURCE_IO, + }, +}; + +static struct mfd_cell rtc_cell __devinitdata = { + .name = PM8XXX_RTC_DEV_NAME, + .id = -1, + .resources = rtc_cell_resources, + .num_resources = ARRAY_SIZE(rtc_cell_resources), +}; + +static const struct resource resources_pwrkey[] __devinitconst = { + SINGLE_IRQ_RESOURCE(NULL, PM8921_PWRKEY_REL_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8921_PWRKEY_PRESS_IRQ), +}; + +static struct mfd_cell pwrkey_cell __devinitdata = { + .name = PM8XXX_PWRKEY_DEV_NAME, + .id = -1, + .num_resources = ARRAY_SIZE(resources_pwrkey), + .resources = resources_pwrkey, +}; + +static const struct resource resources_keypad[] = { + SINGLE_IRQ_RESOURCE(NULL, PM8921_KEYPAD_IRQ), + SINGLE_IRQ_RESOURCE(NULL, PM8921_KEYSTUCK_IRQ), +}; + +static struct mfd_cell keypad_cell __devinitdata = { + .name = PM8XXX_KEYPAD_DEV_NAME, + .id = -1, + .num_resources = ARRAY_SIZE(resources_keypad), + .resources = resources_keypad, +}; + +static struct mfd_cell debugfs_cell __devinitdata = { + .name = "pm8xxx-debug", + .id = -1, + .platform_data = "pm8921-dbg", + .pdata_size = sizeof("pm8921-dbg"), +}; + +static struct mfd_cell pwm_cell __devinitdata = { + .name = PM8XXX_PWM_DEV_NAME, + .id = -1, +}; + +static const struct resource charger_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE("USBIN_VALID_IRQ", PM8921_USBIN_VALID_IRQ), + SINGLE_IRQ_RESOURCE("USBIN_OV_IRQ", PM8921_USBIN_OV_IRQ), + SINGLE_IRQ_RESOURCE("BATT_INSERTED_IRQ", PM8921_BATT_INSERTED_IRQ), + SINGLE_IRQ_RESOURCE("VBATDET_LOW_IRQ", PM8921_VBATDET_LOW_IRQ), + SINGLE_IRQ_RESOURCE("USBIN_UV_IRQ", PM8921_USBIN_UV_IRQ), + SINGLE_IRQ_RESOURCE("VBAT_OV_IRQ", PM8921_VBAT_OV_IRQ), + SINGLE_IRQ_RESOURCE("CHGWDOG_IRQ", PM8921_CHGWDOG_IRQ), + SINGLE_IRQ_RESOURCE("VCP_IRQ", PM8921_VCP_IRQ), + SINGLE_IRQ_RESOURCE("ATCDONE_IRQ", PM8921_ATCDONE_IRQ), + SINGLE_IRQ_RESOURCE("ATCFAIL_IRQ", PM8921_ATCFAIL_IRQ), + SINGLE_IRQ_RESOURCE("CHGDONE_IRQ", PM8921_CHGDONE_IRQ), + SINGLE_IRQ_RESOURCE("CHGFAIL_IRQ", PM8921_CHGFAIL_IRQ), + SINGLE_IRQ_RESOURCE("CHGSTATE_IRQ", PM8921_CHGSTATE_IRQ), + SINGLE_IRQ_RESOURCE("LOOP_CHANGE_IRQ", PM8921_LOOP_CHANGE_IRQ), + SINGLE_IRQ_RESOURCE("FASTCHG_IRQ", PM8921_FASTCHG_IRQ), + SINGLE_IRQ_RESOURCE("TRKLCHG_IRQ", PM8921_TRKLCHG_IRQ), + SINGLE_IRQ_RESOURCE("BATT_REMOVED_IRQ", PM8921_BATT_REMOVED_IRQ), + SINGLE_IRQ_RESOURCE("BATTTEMP_HOT_IRQ", PM8921_BATTTEMP_HOT_IRQ), + SINGLE_IRQ_RESOURCE("CHGHOT_IRQ", PM8921_CHGHOT_IRQ), + SINGLE_IRQ_RESOURCE("BATTTEMP_COLD_IRQ", PM8921_BATTTEMP_COLD_IRQ), + SINGLE_IRQ_RESOURCE("CHG_GONE_IRQ", PM8921_CHG_GONE_IRQ), + SINGLE_IRQ_RESOURCE("BAT_TEMP_OK_IRQ", PM8921_BAT_TEMP_OK_IRQ), + SINGLE_IRQ_RESOURCE("COARSE_DET_LOW_IRQ", PM8921_COARSE_DET_LOW_IRQ), + SINGLE_IRQ_RESOURCE("VDD_LOOP_IRQ", PM8921_VDD_LOOP_IRQ), + SINGLE_IRQ_RESOURCE("VREG_OV_IRQ", PM8921_VREG_OV_IRQ), + SINGLE_IRQ_RESOURCE("VBATDET_IRQ", PM8921_VBATDET_IRQ), + SINGLE_IRQ_RESOURCE("BATFET_IRQ", PM8921_BATFET_IRQ), + SINGLE_IRQ_RESOURCE("PSI_IRQ", PM8921_PSI_IRQ), + SINGLE_IRQ_RESOURCE("DCIN_VALID_IRQ", PM8921_DCIN_VALID_IRQ), + SINGLE_IRQ_RESOURCE("DCIN_OV_IRQ", PM8921_DCIN_OV_IRQ), + SINGLE_IRQ_RESOURCE("DCIN_UV_IRQ", PM8921_DCIN_UV_IRQ), +}; + +static const struct resource bms_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE("PM8921_BMS_SBI_WRITE_OK", PM8921_BMS_SBI_WRITE_OK), + SINGLE_IRQ_RESOURCE("PM8921_BMS_CC_THR", PM8921_BMS_CC_THR), + SINGLE_IRQ_RESOURCE("PM8921_BMS_VSENSE_THR", PM8921_BMS_VSENSE_THR), + SINGLE_IRQ_RESOURCE("PM8921_BMS_VSENSE_FOR_R", PM8921_BMS_VSENSE_FOR_R), + SINGLE_IRQ_RESOURCE("PM8921_BMS_OCV_FOR_R", PM8921_BMS_OCV_FOR_R), + SINGLE_IRQ_RESOURCE("PM8921_BMS_GOOD_OCV", PM8921_BMS_GOOD_OCV), + SINGLE_IRQ_RESOURCE("PM8921_BMS_VSENSE_AVG", PM8921_BMS_VSENSE_AVG), +}; + +static struct mfd_cell charger_cell __devinitdata = { + .name = PM8921_CHARGER_DEV_NAME, + .id = -1, + .resources = charger_cell_resources, + .num_resources = ARRAY_SIZE(charger_cell_resources), +}; + +static struct mfd_cell bms_cell __devinitdata = { + .name = PM8921_BMS_DEV_NAME, + .id = -1, + .resources = bms_cell_resources, + .num_resources = ARRAY_SIZE(bms_cell_resources), +}; + +static struct mfd_cell misc_cell __devinitdata = { + .name = PM8XXX_MISC_DEV_NAME, + .id = -1, +}; + +static struct mfd_cell leds_cell __devinitdata = { + .name = PM8XXX_LEDS_DEV_NAME, + .id = -1, +}; + +static const struct resource thermal_alarm_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE("pm8921_tempstat_irq", PM8921_TEMPSTAT_IRQ), + SINGLE_IRQ_RESOURCE("pm8921_overtemp_irq", PM8921_OVERTEMP_IRQ), }; -static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data - *pdata, - struct pm8921 *pmic, - u32 rev) +static struct pm8xxx_tm_core_data thermal_alarm_cdata = { + .adc_channel = CHANNEL_DIE_TEMP, + .adc_type = PM8XXX_TM_ADC_PM8921_ADC, + .reg_addr_temp_alarm_ctrl = REG_TEMP_ALARM_CTRL, + .reg_addr_temp_alarm_pwm = REG_TEMP_ALARM_PWM, + .tm_name = "pm8921_tz", + .irq_name_temp_stat = "pm8921_tempstat_irq", + .irq_name_over_temp = "pm8921_overtemp_irq", +}; + +static struct mfd_cell thermal_alarm_cell __devinitdata = { + .name = PM8XXX_TM_DEV_NAME, + .id = -1, + .resources = thermal_alarm_cell_resources, + .num_resources = ARRAY_SIZE(thermal_alarm_cell_resources), + .platform_data = &thermal_alarm_cdata, + .pdata_size = sizeof(struct pm8xxx_tm_core_data), +}; + +static const struct resource batt_alarm_cell_resources[] __devinitconst = { + SINGLE_IRQ_RESOURCE("pm8921_batt_alarm_irq", PM8921_BATT_ALARM_IRQ), +}; + +static struct pm8xxx_batt_alarm_core_data batt_alarm_cdata = { + .irq_name = "pm8921_batt_alarm_irq", + .reg_addr_threshold = REG_BATT_ALARM_THRESH, + .reg_addr_ctrl1 = REG_BATT_ALARM_CTRL1, + .reg_addr_ctrl2 = REG_BATT_ALARM_CTRL2, + .reg_addr_pwm_ctrl = REG_BATT_ALARM_PWM_CTRL, +}; + +static struct mfd_cell batt_alarm_cell __devinitdata = { + .name = PM8XXX_BATT_ALARM_DEV_NAME, + .id = -1, + .resources = batt_alarm_cell_resources, + .num_resources = ARRAY_SIZE(batt_alarm_cell_resources), + .platform_data = &batt_alarm_cdata, + .pdata_size = sizeof(struct pm8xxx_batt_alarm_core_data), +}; + +static int __devinit +pm8921_add_subdevices(const struct pm8921_platform_data *pdata, + struct pm8921 *pmic) { int ret = 0, irq_base = 0; struct pm_irq_chip *irq_chip; + static struct mfd_cell *mfd_regulators; + int i; if (pdata->irq_pdata) { pdata->irq_pdata->irq_cdata.nirqs = PM8921_NR_IRQS; - pdata->irq_pdata->irq_cdata.rev = rev; irq_base = pdata->irq_pdata->irq_base; irq_chip = pm8xxx_irq_init(pmic->dev, pdata->irq_pdata); @@ -101,16 +359,217 @@ static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data } pmic->irq_chip = irq_chip; } + + if (pdata->gpio_pdata) { + pdata->gpio_pdata->gpio_cdata.ngpios = PM8921_NR_GPIOS; + gpio_cell.platform_data = pdata->gpio_pdata; + gpio_cell.pdata_size = sizeof(struct pm8xxx_gpio_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &gpio_cell, 1, + NULL, irq_base); + if (ret) { + pr_err("Failed to add gpio subdevice ret=%d\n", ret); + goto bail; + } + } + + if (pdata->mpp_pdata) { + pdata->mpp_pdata->core_data.nmpps = PM8921_NR_MPPS; + pdata->mpp_pdata->core_data.base_addr = REG_MPP_BASE; + mpp_cell.platform_data = pdata->mpp_pdata; + mpp_cell.pdata_size = sizeof(struct pm8xxx_mpp_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &mpp_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add mpp subdevice ret=%d\n", ret); + goto bail; + } + } + + if (pdata->rtc_pdata) { + rtc_cell.platform_data = pdata->rtc_pdata; + rtc_cell.pdata_size = sizeof(struct pm8xxx_rtc_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &rtc_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add rtc subdevice ret=%d\n", ret); + goto bail; + } + } + + if (pdata->pwrkey_pdata) { + pwrkey_cell.platform_data = pdata->pwrkey_pdata; + pwrkey_cell.pdata_size = + sizeof(struct pm8xxx_pwrkey_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &pwrkey_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add pwrkey subdevice ret=%d\n", ret); + goto bail; + } + } + + if (pdata->keypad_pdata) { + keypad_cell.platform_data = pdata->keypad_pdata; + keypad_cell.pdata_size = + sizeof(struct pm8xxx_keypad_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &keypad_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add keypad subdevice ret=%d\n", ret); + goto bail; + } + } + + if (pdata->charger_pdata) { + pdata->charger_pdata->charger_cdata.vbat_channel = CHANNEL_VBAT; + charger_cell.platform_data = pdata->charger_pdata; + charger_cell.pdata_size = + sizeof(struct pm8921_charger_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &charger_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add charger subdevice ret=%d\n", ret); + goto bail; + } + } + + if (pdata->adc_pdata) { + adc_cell.platform_data = pdata->adc_pdata; + adc_cell.pdata_size = + sizeof(struct pm8921_adc_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &adc_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add regulator subdevices ret=%d\n", + ret); + } + } + + if (pdata->bms_pdata) { + bms_cell.platform_data = pdata->bms_pdata; + bms_cell.pdata_size = + sizeof(struct pm8921_bms_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &bms_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add bms subdevice ret=%d\n", ret); + goto bail; + } + } + + /* Add one device for each regulator used by the board. */ + if (pdata->num_regulators > 0 && pdata->regulator_pdatas) { + mfd_regulators = kzalloc(sizeof(struct mfd_cell) + * (pdata->num_regulators), GFP_KERNEL); + if (!mfd_regulators) { + pr_err("Cannot allocate %d bytes for pm8921 regulator " + "mfd cells\n", sizeof(struct mfd_cell) + * (pdata->num_regulators)); + ret = -ENOMEM; + goto bail; + } + for (i = 0; i < pdata->num_regulators; i++) { + mfd_regulators[i].name = PM8921_REGULATOR_DEV_NAME; + mfd_regulators[i].id = pdata->regulator_pdatas[i].id; + mfd_regulators[i].platform_data = + &(pdata->regulator_pdatas[i]); + mfd_regulators[i].pdata_size = + sizeof(struct pm8921_regulator_platform_data); + } + ret = mfd_add_devices(pmic->dev, 0, mfd_regulators, + pdata->num_regulators, NULL, irq_base); + if (ret) { + pr_err("Failed to add regulator subdevices ret=%d\n", + ret); + kfree(mfd_regulators); + goto bail; + } + pmic->mfd_regulators = mfd_regulators; + } + + ret = mfd_add_devices(pmic->dev, 0, &debugfs_cell, 1, NULL, irq_base); + if (ret) { + pr_err("Failed to add debugfs subdevice ret=%d\n", ret); + goto bail; + } + + ret = mfd_add_devices(pmic->dev, 0, &pwm_cell, 1, NULL, 0); + if (ret) { + pr_err("Failed to add pwm subdevice ret=%d\n", ret); + goto bail; + } + + if (pdata->misc_pdata) { + misc_cell.platform_data = pdata->misc_pdata; + misc_cell.pdata_size = sizeof(struct pm8xxx_misc_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &misc_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add misc subdevice ret=%d\n", ret); + goto bail; + } + } + + if (pdata->leds_pdata) { + /* PM8921 supports only 4 LED DRVs */ + for (i = 0; i < pdata->leds_pdata->num_leds; i++) { + if (pdata->leds_pdata->leds[i].flags > + PM8XXX_ID_LED_2) { + pr_err("%s: LED %d not supported\n", __func__, + pdata->leds_pdata->leds[i].flags); + goto bail; + } + } + leds_cell.platform_data = pdata->leds_pdata; + leds_cell.pdata_size = sizeof(struct led_platform_data); + ret = mfd_add_devices(pmic->dev, 0, &leds_cell, 1, NULL, 0); + if (ret) { + pr_err("Failed to add leds subdevice ret=%d\n", ret); + goto bail; + } + } + + ret = mfd_add_devices(pmic->dev, 0, &thermal_alarm_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add thermal alarm subdevice ret=%d\n", + ret); + goto bail; + } + + ret = mfd_add_devices(pmic->dev, 0, &batt_alarm_cell, 1, NULL, + irq_base); + if (ret) { + pr_err("Failed to add battery alarm subdevice ret=%d\n", + ret); + goto bail; + } + + return 0; +bail: + if (pmic->irq_chip) { + pm8xxx_irq_exit(pmic->irq_chip); + pmic->irq_chip = NULL; + } return ret; } +static const char * const pm8921_rev_names[] = { + [PM8XXX_REVISION_8921_TEST] = "test", + [PM8XXX_REVISION_8921_1p0] = "1.0", + [PM8XXX_REVISION_8921_1p1] = "1.1", + [PM8XXX_REVISION_8921_2p0] = "2.0", +}; + static int __devinit pm8921_probe(struct platform_device *pdev) { const struct pm8921_platform_data *pdata = pdev->dev.platform_data; + const char *revision_name = "unknown"; struct pm8921 *pmic; + enum pm8xxx_version version; + int revision; int rc; u8 val; - u32 rev; if (!pdata) { pr_err("missing platform data\n"); @@ -130,7 +589,7 @@ static int __devinit pm8921_probe(struct platform_device *pdev) goto err_read_rev; } pr_info("PMIC revision 1: %02X\n", val); - rev = val; + pmic->rev_registers = val; /* Read PMIC chip revision 2 */ rc = msm_ssbi_read(pdev->dev.parent, REG_HWREV_2, &val, sizeof(val)); @@ -140,13 +599,24 @@ static int __devinit pm8921_probe(struct platform_device *pdev) goto err_read_rev; } pr_info("PMIC revision 2: %02X\n", val); - rev |= val << BITS_PER_BYTE; + pmic->rev_registers |= val << BITS_PER_BYTE; pmic->dev = &pdev->dev; pm8921_drvdata.pm_chip_data = pmic; platform_set_drvdata(pdev, &pm8921_drvdata); - rc = pm8921_add_subdevices(pdata, pmic, rev); + /* Print out human readable version and revision names. */ + version = pm8xxx_get_version(pmic->dev); + if (version == PM8XXX_VERSION_8921) { + revision = pm8xxx_get_revision(pmic->dev); + if (revision >= 0 && revision < ARRAY_SIZE(pm8921_rev_names)) + revision_name = pm8921_rev_names[revision]; + pr_info("PMIC version: PM8921 rev %s\n", revision_name); + } else { + WARN_ON(version != PM8XXX_VERSION_8921); + } + + rc = pm8921_add_subdevices(pdata, pmic); if (rc) { pr_err("Cannot add subdevices rc=%d\n", rc); goto err; @@ -180,6 +650,7 @@ static int __devexit pm8921_remove(struct platform_device *pdev) pmic->irq_chip = NULL; } platform_set_drvdata(pdev, NULL); + kfree(pmic->mfd_regulators); kfree(pmic); return 0; @@ -198,7 +669,7 @@ static int __init pm8921_init(void) { return platform_driver_register(&pm8921_driver); } -subsys_initcall(pm8921_init); +postcore_initcall(pm8921_init); static void __exit pm8921_exit(void) { diff --git a/drivers/mfd/pm8xxx-batt-alarm.c b/drivers/mfd/pm8xxx-batt-alarm.c new file mode 100644 index 00000000000..92ade1c2ec2 --- /dev/null +++ b/drivers/mfd/pm8xxx-batt-alarm.c @@ -0,0 +1,805 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * Qualcomm PMIC PM8xxx Battery Alarm driver + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Available voltage threshold values */ +#define THRESHOLD_MIN_MV 2500 +#define THRESHOLD_MAX_MV 5675 +#define THRESHOLD_STEP_MV 25 + +/* Register bit definitions */ + +/* Threshold register */ +#define THRESHOLD_UPPER_MASK 0xF0 +#define THRESHOLD_LOWER_MASK 0x0F +#define THRESHOLD_UPPER_SHIFT 4 +#define THRESHOLD_LOWER_SHIFT 0 + +/* CTRL 1 register */ +#define CTRL1_BATT_ALARM_ENABLE_MASK 0x80 +#define CTRL1_BATT_ALARM_ENABLE 0x80 +#define CTRL1_BATT_ALARM_DISABLE 0x00 +#define CTRL1_HOLD_TIME_MASK 0x70 +#define CTRL1_STATUS_UPPER_MASK 0x02 +#define CTRL1_STATUS_LOWER_MASK 0x01 +#define CTRL1_HOLD_TIME_SHIFT 4 +#define CTRL1_HOLD_TIME_MIN 0 +#define CTRL1_HOLD_TIME_MAX 7 + +/* CTRL 2 register */ +#define CTRL2_COMP_UPPER_DISABLE_MASK 0x80 +#define CTRL2_COMP_UPPER_ENABLE 0x00 +#define CTRL2_COMP_UPPER_DISABLE 0x80 +#define CTRL2_COMP_LOWER_DISABLE_MASK 0x40 +#define CTRL2_COMP_LOWER_ENABLE 0x00 +#define CTRL2_COMP_LOWER_DISABLE 0x40 +#define CTRL2_FINE_STEP_UPPER_MASK 0x30 +#define CTRL2_RANGE_EXT_UPPER_MASK 0x08 +#define CTRL2_FINE_STEP_LOWER_MASK 0x06 +#define CTRL2_RANGE_EXT_LOWER_MASK 0x01 +#define CTRL2_FINE_STEP_UPPER_SHIFT 4 +#define CTRL2_FINE_STEP_LOWER_SHIFT 1 + +/* PWM control register */ +#define PWM_CTRL_ALARM_EN_MASK 0xC0 +#define PWM_CTRL_ALARM_EN_NEVER 0x00 +#define PWM_CTRL_ALARM_EN_TCXO 0x40 +#define PWM_CTRL_ALARM_EN_PWM 0x80 +#define PWM_CTRL_ALARM_EN_ALWAYS 0xC0 +#define PWM_CTRL_PRE_MASK 0x38 +#define PWM_CTRL_DIV_MASK 0x07 +#define PWM_CTRL_PRE_SHIFT 3 +#define PWM_CTRL_DIV_SHIFT 0 +#define PWM_CTRL_PRE_MIN 0 +#define PWM_CTRL_PRE_MAX 7 +#define PWM_CTRL_DIV_MIN 1 +#define PWM_CTRL_DIV_MAX 7 + +/* PWM control input range */ +#define PWM_CTRL_PRE_INPUT_MIN 2 +#define PWM_CTRL_PRE_INPUT_MAX 9 +#define PWM_CTRL_DIV_INPUT_MIN 2 +#define PWM_CTRL_DIV_INPUT_MAX 8 + +/* Available voltage threshold values */ +#define THRESHOLD_BASIC_MIN_MV 2800 +#define THRESHOLD_EXT_MIN_MV 4400 + +/* + * Default values used during initialization: + * Slowest PWM rate to ensure minimal status jittering when crossing thresholds. + * Largest hold time also helps reduce status value jittering. Comparators + * are disabled by default and must be turned on by calling + * pm8xxx_batt_alarm_state_set. + */ +#define DEFAULT_THRESHOLD_LOWER 3200 +#define DEFAULT_THRESHOLD_UPPER 4300 +#define DEFAULT_HOLD_TIME PM8XXX_BATT_ALARM_HOLD_TIME_16_MS +#define DEFAULT_USE_PWM 1 +#define DEFAULT_PWM_SCALER 9 +#define DEFAULT_PWM_DIVIDER 8 +#define DEFAULT_LOWER_ENABLE 0 +#define DEFAULT_UPPER_ENABLE 0 + +struct pm8xxx_batt_alarm_chip { + struct pm8xxx_batt_alarm_core_data cdata; + struct srcu_notifier_head irq_notifier_list; + struct work_struct irq_work; + struct device *dev; + struct mutex lock; + unsigned int irq; + int notifier_count; + u8 reg_threshold; + u8 reg_ctrl1; + u8 reg_ctrl2; + u8 reg_pwm_ctrl; +}; +static struct pm8xxx_batt_alarm_chip *the_battalarm; + +static int pm8xxx_reg_write(struct pm8xxx_batt_alarm_chip *chip, u16 addr, + u8 val, u8 mask, u8 *reg_save) +{ + int rc = 0; + u8 reg; + + reg = (*reg_save & ~mask) | (val & mask); + if (reg != *reg_save) + rc = pm8xxx_writeb(chip->dev->parent, addr, reg); + if (rc) + pr_err("pm8xxx_writeb failed; addr=%03X, rc=%d\n", addr, rc); + else + *reg_save = reg; + return rc; +} + +/** + * pm8xxx_batt_alarm_enable - enable one of the battery voltage threshold + * comparators + * @comparator: selects which comparator to enable + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_enable(enum pm8xxx_batt_alarm_comparator comparator) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + u8 val_ctrl2 = 0, mask_ctrl2 = 0; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENODEV; + } + + if (comparator < 0 || comparator > PM8XXX_BATT_ALARM_UPPER_COMPARATOR) { + pr_err("invalid comparator ID number: %d\n", comparator); + return -EINVAL; + } + + if (comparator == PM8XXX_BATT_ALARM_LOWER_COMPARATOR) { + val_ctrl2 = CTRL2_COMP_LOWER_ENABLE; + mask_ctrl2 = CTRL2_COMP_LOWER_DISABLE_MASK; + } else { + val_ctrl2 = CTRL2_COMP_UPPER_ENABLE; + mask_ctrl2 = CTRL2_COMP_UPPER_DISABLE_MASK; + } + + mutex_lock(&chip->lock); + + /* Enable the battery alarm block. */ + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl1, + CTRL1_BATT_ALARM_ENABLE, + CTRL1_BATT_ALARM_ENABLE_MASK, &chip->reg_ctrl1); + if (rc) + goto bail; + + /* Enable the individual comparators. */ + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl2, val_ctrl2, + mask_ctrl2, &chip->reg_ctrl2); + +bail: + mutex_unlock(&chip->lock); + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_enable); + +/** + * pm8xxx_batt_alarm_disable - disable one of the battery voltage threshold + * comparators + * @comparator: selects which comparator to disable + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_disable(enum pm8xxx_batt_alarm_comparator comparator) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + u8 val_ctrl1 = 0, val_ctrl2 = 0, mask_ctrl2 = 0; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENODEV; + } + + if (comparator < 0 || comparator > PM8XXX_BATT_ALARM_UPPER_COMPARATOR) { + pr_err("invalid comparator ID number: %d\n", comparator); + return -EINVAL; + } + + if (comparator == PM8XXX_BATT_ALARM_LOWER_COMPARATOR) { + val_ctrl2 = CTRL2_COMP_LOWER_DISABLE; + mask_ctrl2 = CTRL2_COMP_LOWER_DISABLE_MASK; + } else { + val_ctrl2 = CTRL2_COMP_UPPER_DISABLE; + mask_ctrl2 = CTRL2_COMP_UPPER_DISABLE_MASK; + } + + mutex_lock(&chip->lock); + + /* Disable the specified comparator. */ + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl2, val_ctrl2, + mask_ctrl2, &chip->reg_ctrl2); + if (rc) + goto bail; + + /* Disable the battery alarm block if both comparators are disabled. */ + val_ctrl2 = chip->reg_ctrl2 + & (CTRL2_COMP_LOWER_DISABLE_MASK | CTRL2_COMP_UPPER_DISABLE_MASK); + if (val_ctrl2 == (CTRL2_COMP_LOWER_DISABLE | CTRL2_COMP_UPPER_DISABLE)) + val_ctrl1 = CTRL1_BATT_ALARM_DISABLE; + else + val_ctrl1 = CTRL1_BATT_ALARM_ENABLE; + + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl1, val_ctrl1, + CTRL1_BATT_ALARM_ENABLE_MASK, &chip->reg_ctrl1); + +bail: + mutex_unlock(&chip->lock); + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_disable); + +/** + * pm8xxx_batt_alarm_threshold_set - set the lower and upper alarm thresholds + * @comparator: selects which comparator to set the threshold of + * @threshold_mV: battery voltage threshold in millivolts + * set points = 2500-5675 mV in 25 mV steps + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_threshold_set( + enum pm8xxx_batt_alarm_comparator comparator, int threshold_mV) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int step, fine_step, rc; + u8 val_threshold = 0, val_ctrl2 = 0; + int threshold_mask, threshold_shift, range_ext_mask, fine_step_mask; + int fine_step_shift; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (comparator < 0 || comparator > PM8XXX_BATT_ALARM_UPPER_COMPARATOR) { + pr_err("invalid comparator ID number: %d\n", comparator); + return -EINVAL; + } + + if (threshold_mV < THRESHOLD_MIN_MV + || threshold_mV > THRESHOLD_MAX_MV) { + pr_err("threshold value, %d mV, is outside of allowable " + "range: [%d, %d] mV\n", threshold_mV, + THRESHOLD_MIN_MV, THRESHOLD_MAX_MV); + return -EINVAL; + } + + if (comparator == PM8XXX_BATT_ALARM_LOWER_COMPARATOR) { + threshold_mask = THRESHOLD_LOWER_MASK; + threshold_shift = THRESHOLD_LOWER_SHIFT; + range_ext_mask = CTRL2_RANGE_EXT_LOWER_MASK; + fine_step_mask = CTRL2_FINE_STEP_LOWER_MASK; + fine_step_shift = CTRL2_FINE_STEP_LOWER_SHIFT; + } else { + threshold_mask = THRESHOLD_UPPER_MASK; + threshold_shift = THRESHOLD_UPPER_SHIFT; + range_ext_mask = CTRL2_RANGE_EXT_UPPER_MASK; + fine_step_mask = CTRL2_FINE_STEP_UPPER_MASK; + fine_step_shift = CTRL2_FINE_STEP_UPPER_SHIFT; + } + + /* Determine register settings to achieve the threshold. */ + if (threshold_mV < THRESHOLD_BASIC_MIN_MV) { + /* Extended low range */ + val_ctrl2 |= range_ext_mask; + + step = (threshold_mV - THRESHOLD_MIN_MV) / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + /* Extended low range is for steps 0 to 2 */ + step >>= 2; + } else if (threshold_mV >= THRESHOLD_EXT_MIN_MV) { + /* Extended high range */ + val_ctrl2 |= range_ext_mask; + + step = (threshold_mV - THRESHOLD_EXT_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + /* Extended high range is for steps 3 to 15 */ + step = (step >> 2) + 3; + } else { + /* Basic range */ + step = (threshold_mV - THRESHOLD_BASIC_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + step >>= 2; + } + val_threshold |= step << threshold_shift; + val_ctrl2 |= (fine_step << fine_step_shift) & fine_step_mask; + + mutex_lock(&chip->lock); + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_threshold, + val_threshold, threshold_mask, &chip->reg_threshold); + if (rc) + goto bail; + + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl2, val_ctrl2, + range_ext_mask | fine_step_mask, &chip->reg_ctrl2); + +bail: + mutex_unlock(&chip->lock); + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_threshold_set); + +/** + * pm8xxx_batt_alarm_status_read - get status of both threshold comparators + * + * RETURNS: < 0 = error + * 0 = battery voltage ok + * BIT(0) set = battery voltage below lower threshold + * BIT(1) set = battery voltage above upper threshold + */ +int pm8xxx_batt_alarm_status_read(void) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int status, rc; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + mutex_lock(&chip->lock); + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_ctrl1, + &chip->reg_ctrl1); + + status = ((chip->reg_ctrl1 & CTRL1_STATUS_LOWER_MASK) + ? PM8XXX_BATT_ALARM_STATUS_BELOW_LOWER : 0) + | ((chip->reg_ctrl1 & CTRL1_STATUS_UPPER_MASK) + ? PM8XXX_BATT_ALARM_STATUS_ABOVE_UPPER : 0); + mutex_unlock(&chip->lock); + + if (rc) { + pr_err("pm8xxx_readb failed, rc=%d\n", rc); + return rc; + } + + return status; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_status_read); + +/** + * pm8xxx_batt_alarm_hold_time_set - set hold time of interrupt output * + * @hold_time: amount of time that battery voltage must remain outside of the + * threshold range before the battery alarm interrupt triggers + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_hold_time_set(enum pm8xxx_batt_alarm_hold_time hold_time) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + u8 reg_ctrl1 = 0; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (hold_time < CTRL1_HOLD_TIME_MIN + || hold_time > CTRL1_HOLD_TIME_MAX) { + + pr_err("hold time, %d, is outside of allowable range: " + "[%d, %d]\n", hold_time, CTRL1_HOLD_TIME_MIN, + CTRL1_HOLD_TIME_MAX); + return -EINVAL; + } + + reg_ctrl1 = hold_time << CTRL1_HOLD_TIME_SHIFT; + + mutex_lock(&chip->lock); + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_ctrl1, reg_ctrl1, + CTRL1_HOLD_TIME_MASK, &chip->reg_ctrl1); + mutex_unlock(&chip->lock); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_hold_time_set); + +/** + * pm8xxx_batt_alarm_pwm_rate_set - set battery alarm update rate * + * @use_pwm: 1 = use PWM update rate, 0 = comparators always active + * @clock_scaler: PWM clock scaler = 2 to 9 + * @clock_divider: PWM clock divider = 2 to 8 + * + * This function sets the rate at which the battery alarm module enables + * the threshold comparators. The rate is determined by the following equation: + * + * f_update = (1024 Hz) / (clock_divider * (2 ^ clock_scaler)) + * + * Thus, the update rate can range from 0.25 Hz to 128 Hz. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_pwm_rate_set(int use_pwm, int clock_scaler, + int clock_divider) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + u8 reg_pwm_ctrl = 0, mask = 0; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (use_pwm && (clock_scaler < PWM_CTRL_PRE_INPUT_MIN + || clock_scaler > PWM_CTRL_PRE_INPUT_MAX)) { + pr_err("PWM clock scaler, %d, is outside of allowable range: " + "[%d, %d]\n", clock_scaler, PWM_CTRL_PRE_INPUT_MIN, + PWM_CTRL_PRE_INPUT_MAX); + return -EINVAL; + } + + if (use_pwm && (clock_divider < PWM_CTRL_DIV_INPUT_MIN + || clock_divider > PWM_CTRL_DIV_INPUT_MAX)) { + pr_err("PWM clock divider, %d, is outside of allowable range: " + "[%d, %d]\n", clock_divider, PWM_CTRL_DIV_INPUT_MIN, + PWM_CTRL_DIV_INPUT_MAX); + return -EINVAL; + } + + if (!use_pwm) { + /* Turn off PWM control and always enable. */ + reg_pwm_ctrl = PWM_CTRL_ALARM_EN_ALWAYS; + mask = PWM_CTRL_ALARM_EN_MASK; + } else { + /* Use PWM control. */ + reg_pwm_ctrl = PWM_CTRL_ALARM_EN_PWM; + mask = PWM_CTRL_ALARM_EN_MASK | PWM_CTRL_PRE_MASK + | PWM_CTRL_DIV_MASK; + + clock_scaler -= PWM_CTRL_PRE_INPUT_MIN - PWM_CTRL_PRE_MIN; + clock_divider -= PWM_CTRL_DIV_INPUT_MIN - PWM_CTRL_DIV_MIN; + + reg_pwm_ctrl |= (clock_scaler << PWM_CTRL_PRE_SHIFT) + & PWM_CTRL_PRE_MASK; + reg_pwm_ctrl |= (clock_divider << PWM_CTRL_DIV_SHIFT) + & PWM_CTRL_DIV_MASK; + } + + mutex_lock(&chip->lock); + rc = pm8xxx_reg_write(chip, chip->cdata.reg_addr_pwm_ctrl, reg_pwm_ctrl, + mask, &chip->reg_pwm_ctrl); + mutex_unlock(&chip->lock); + + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_pwm_rate_set); + +/* + * Handle the BATT_ALARM interrupt: + * Battery voltage is above or below threshold range. + */ +static irqreturn_t pm8xxx_batt_alarm_isr(int irq, void *data) +{ + struct pm8xxx_batt_alarm_chip *chip = data; + + disable_irq_nosync(chip->irq); + schedule_work(&chip->irq_work); + + return IRQ_HANDLED; +} + +static void pm8xxx_batt_alarm_isr_work(struct work_struct *work) +{ + struct pm8xxx_batt_alarm_chip *chip + = container_of(work, struct pm8xxx_batt_alarm_chip, irq_work); + int status; + + if (chip) { + status = pm8xxx_batt_alarm_status_read(); + + if (status < 0) + pr_err("failed to read status, rc=%d\n", status); + else + srcu_notifier_call_chain(&chip->irq_notifier_list, + status, NULL); + } + + enable_irq(chip->irq); +} + +/** + * pm8xxx_batt_alarm_register_notifier - register a notifier to run when a + * battery voltage change interrupt fires + * @nb: notifier block containing callback function to register + * + * nb->notifier_call must point to a function of this form - + * int (*notifier_call)(struct notifier_block *nb, unsigned long status, + * void *unused); + * "status" will receive the battery alarm status; "unused" will be NULL. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_register_notifier(struct notifier_block *nb) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + rc = srcu_notifier_chain_register(&chip->irq_notifier_list, nb); + mutex_lock(&chip->lock); + if (rc == 0) { + if (chip->notifier_count == 0) { + enable_irq(chip->irq); + rc = irq_set_irq_wake(chip->irq, 1); + } + + chip->notifier_count++; + } + + mutex_unlock(&chip->lock); + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_register_notifier); + +/** + * pm8xxx_batt_alarm_unregister_notifier - unregister a notifier that is run + * when a battery voltage change interrupt fires + * @nb: notifier block containing callback function to unregister + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_unregister_notifier(struct notifier_block *nb) +{ + struct pm8xxx_batt_alarm_chip *chip = the_battalarm; + int rc; + + if (!chip) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + rc = srcu_notifier_chain_unregister(&chip->irq_notifier_list, nb); + if (rc == 0) { + mutex_lock(&chip->lock); + + chip->notifier_count--; + + if (chip->notifier_count == 0) { + rc = irq_set_irq_wake(chip->irq, 0); + disable_irq(chip->irq); + } + + WARN_ON(chip->notifier_count < 0); + + mutex_unlock(&chip->lock); + } + + return rc; +} +EXPORT_SYMBOL(pm8xxx_batt_alarm_unregister_notifier); + +static int pm8xxx_batt_alarm_reg_init(struct pm8xxx_batt_alarm_chip *chip) +{ + int rc = 0; + + /* save the current register states */ + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_threshold, + &chip->reg_threshold); + if (rc) + goto bail; + + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_ctrl1, + &chip->reg_ctrl1); + if (rc) + goto bail; + + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_ctrl2, + &chip->reg_ctrl2); + if (rc) + goto bail; + + rc = pm8xxx_readb(chip->dev->parent, chip->cdata.reg_addr_pwm_ctrl, + &chip->reg_pwm_ctrl); + if (rc) + goto bail; + +bail: + if (rc) + pr_err("pm8xxx_readb failed; initial register states " + "unknown, rc=%d\n", rc); + return rc; +} + +/* TODO: should this default setting function be removed? */ +static int pm8xxx_batt_alarm_config_defaults(void) +{ + int rc = 0; + + /* Use default values when no platform data is provided. */ + rc = pm8xxx_batt_alarm_threshold_set(PM8XXX_BATT_ALARM_LOWER_COMPARATOR, + DEFAULT_THRESHOLD_LOWER); + if (rc) { + pr_err("threshold_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_threshold_set(PM8XXX_BATT_ALARM_UPPER_COMPARATOR, + DEFAULT_THRESHOLD_UPPER); + if (rc) { + pr_err("threshold_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_hold_time_set(DEFAULT_HOLD_TIME); + if (rc) { + pr_err("hold_time_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_pwm_rate_set(DEFAULT_USE_PWM, + DEFAULT_PWM_SCALER, DEFAULT_PWM_DIVIDER); + if (rc) { + pr_err("pwm_rate_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_LOWER_COMPARATOR); + if (rc) { + pr_err("disable lower failed, rc=%d\n", rc); + goto done; + } + + rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR); + if (rc) { + pr_err("disable upper failed, rc=%d\n", rc); + goto done; + } + +done: + return rc; +} + +static int __devinit pm8xxx_batt_alarm_probe(struct platform_device *pdev) +{ + const struct pm8xxx_batt_alarm_core_data *cdata + = pdev->dev.platform_data; + struct pm8xxx_batt_alarm_chip *chip; + struct resource *res; + int rc; + + if (the_battalarm) { + pr_err("A PMIC battery alarm device has already probed.\n"); + return -ENODEV; + } + + if (!cdata) { + pr_err("missing core data\n"); + return -EINVAL; + } + + if (!cdata->irq_name) { + pr_err("missing IRQ name\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct pm8xxx_batt_alarm_chip), GFP_KERNEL); + if (chip == NULL) { + pr_err("kzalloc() failed.\n"); + return -ENOMEM; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + cdata->irq_name); + if (res) { + chip->irq = res->start; + } else { + pr_err("Battery alarm IRQ not specified\n"); + rc = -EINVAL; + goto err_free_chip; + } + + chip->dev = &pdev->dev; + memcpy(&(chip->cdata), cdata, + sizeof(struct pm8xxx_batt_alarm_core_data)); + + srcu_init_notifier_head(&chip->irq_notifier_list); + + chip->notifier_count = 0; + mutex_init(&chip->lock); + + the_battalarm = chip; + + rc = pm8xxx_batt_alarm_reg_init(chip); + if (rc) + goto err_free_mutex; + + rc = pm8xxx_batt_alarm_config_defaults(); + if (rc) + goto err_free_mutex; + + INIT_WORK(&chip->irq_work, pm8xxx_batt_alarm_isr_work); + +/* TODO: Is it best to trigger on both edges? Should this be configurable? */ + rc = request_irq(chip->irq, pm8xxx_batt_alarm_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, cdata->irq_name, + chip); + if (rc < 0) { + pr_err("request_irq(%d) failed, rc=%d\n", chip->irq, rc); + goto err_cancel_work; + } + + /* Disable the IRQ until a notifier is registered. */ + disable_irq(chip->irq); + + platform_set_drvdata(pdev, chip); + + return 0; + +err_cancel_work: + cancel_work_sync(&chip->irq_work); +err_free_mutex: + mutex_destroy(&chip->lock); + srcu_cleanup_notifier_head(&chip->irq_notifier_list); +err_free_chip: + kfree(chip); + the_battalarm = NULL; + + return rc; +} + +static int __devexit pm8xxx_batt_alarm_remove(struct platform_device *pdev) +{ + struct pm8xxx_batt_alarm_chip *chip = platform_get_drvdata(pdev); + + if (chip) { + platform_set_drvdata(pdev, NULL); + irq_set_irq_wake(chip->irq, 0); + free_irq(chip->irq, chip); + cancel_work_sync(&chip->irq_work); + srcu_cleanup_notifier_head(&chip->irq_notifier_list); + mutex_destroy(&chip->lock); + kfree(chip); + the_battalarm = NULL; + } + + return 0; +} + +static struct platform_driver pm8xxx_batt_alarm_driver = { + .probe = pm8xxx_batt_alarm_probe, + .remove = __devexit_p(pm8xxx_batt_alarm_remove), + .driver = { + .name = PM8XXX_BATT_ALARM_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_batt_alarm_init(void) +{ + return platform_driver_register(&pm8xxx_batt_alarm_driver); +} + +static void __exit pm8xxx_batt_alarm_exit(void) +{ + platform_driver_unregister(&pm8xxx_batt_alarm_driver); +} + +module_init(pm8xxx_batt_alarm_init); +module_exit(pm8xxx_batt_alarm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC PM8xxx Battery Alarm"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_BATT_ALARM_DEV_NAME); diff --git a/drivers/mfd/pm8xxx-debug.c b/drivers/mfd/pm8xxx-debug.c new file mode 100644 index 00000000000..3b69121fd61 --- /dev/null +++ b/drivers/mfd/pm8xxx-debug.c @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +#define PM8XXX_DEBUG_DEV_NAME "pm8xxx-debug" + +struct pm8xxx_debug_device { + struct mutex debug_mutex; + struct device *parent; + struct dentry *dir; + int addr; +}; + +static bool pm8xxx_debug_addr_is_valid(int addr) +{ + if (addr < 0 || addr > 0x3FF) { + pr_err("PMIC register address is invalid: %d\n", addr); + return false; + } + return true; +} + +static int pm8xxx_debug_data_set(void *data, u64 val) +{ + struct pm8xxx_debug_device *debugdev = data; + u8 reg = val; + int rc; + + mutex_lock(&debugdev->debug_mutex); + + if (pm8xxx_debug_addr_is_valid(debugdev->addr)) { + rc = pm8xxx_writeb(debugdev->parent, debugdev->addr, reg); + + if (rc) + pr_err("pm8xxx_writeb(0x%03X)=0x%02X failed: rc=%d\n", + debugdev->addr, reg, rc); + } + + mutex_unlock(&debugdev->debug_mutex); + return 0; +} + +static int pm8xxx_debug_data_get(void *data, u64 *val) +{ + struct pm8xxx_debug_device *debugdev = data; + int rc; + u8 reg; + + mutex_lock(&debugdev->debug_mutex); + + if (pm8xxx_debug_addr_is_valid(debugdev->addr)) { + rc = pm8xxx_readb(debugdev->parent, debugdev->addr, ®); + + if (rc) + pr_err("pm8xxx_readb(0x%03X) failed: rc=%d\n", + debugdev->addr, rc); + else + *val = reg; + } + + mutex_unlock(&debugdev->debug_mutex); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(debug_data_fops, pm8xxx_debug_data_get, + pm8xxx_debug_data_set, "0x%02llX\n"); + +static int pm8xxx_debug_addr_set(void *data, u64 val) +{ + struct pm8xxx_debug_device *debugdev = data; + + if (pm8xxx_debug_addr_is_valid(val)) { + mutex_lock(&debugdev->debug_mutex); + debugdev->addr = val; + mutex_unlock(&debugdev->debug_mutex); + } + + return 0; +} + +static int pm8xxx_debug_addr_get(void *data, u64 *val) +{ + struct pm8xxx_debug_device *debugdev = data; + + mutex_lock(&debugdev->debug_mutex); + + if (pm8xxx_debug_addr_is_valid(debugdev->addr)) + *val = debugdev->addr; + + mutex_unlock(&debugdev->debug_mutex); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(debug_addr_fops, pm8xxx_debug_addr_get, + pm8xxx_debug_addr_set, "0x%03llX\n"); + +static int __devinit pm8xxx_debug_probe(struct platform_device *pdev) +{ + char *name = pdev->dev.platform_data; + struct pm8xxx_debug_device *debugdev; + struct dentry *dir; + struct dentry *temp; + int rc; + + if (name == NULL) { + pr_err("debugfs directory name must be specified in " + "platform_data pointer\n"); + return -EINVAL; + } + + debugdev = kzalloc(sizeof(struct pm8xxx_debug_device), GFP_KERNEL); + if (debugdev == NULL) { + pr_err("kzalloc failed\n"); + return -ENOMEM; + } + + mutex_init(&debugdev->debug_mutex); + + debugdev->parent = pdev->dev.parent; + debugdev->addr = -1; + + dir = debugfs_create_dir(name, NULL); + if (dir == NULL || IS_ERR(dir)) { + pr_err("debugfs_create_dir failed: rc=%ld\n", PTR_ERR(dir)); + rc = PTR_ERR(dir); + goto dir_error; + } + + temp = debugfs_create_file("addr", S_IRUSR | S_IWUSR, dir, debugdev, + &debug_addr_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("debugfs_create_file failed: rc=%ld\n", PTR_ERR(temp)); + rc = PTR_ERR(temp); + goto file_error; + } + + temp = debugfs_create_file("data", S_IRUSR | S_IWUSR, dir, debugdev, + &debug_data_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("debugfs_create_file failed: rc=%ld\n", PTR_ERR(temp)); + rc = PTR_ERR(temp); + goto file_error; + } + + debugdev->dir = dir; + platform_set_drvdata(pdev, debugdev); + + return 0; + +file_error: + debugfs_remove_recursive(dir); +dir_error: + kfree(debugdev); + + return rc; +} + +static int __devexit pm8xxx_debug_remove(struct platform_device *pdev) +{ + struct pm8xxx_debug_device *debugdev = platform_get_drvdata(pdev); + + if (debugdev) { + debugfs_remove_recursive(debugdev->dir); + kfree(debugdev); + } + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver pm8xxx_debug_driver = { + .probe = pm8xxx_debug_probe, + .remove = __devexit_p(pm8xxx_debug_remove), + .driver = { + .name = PM8XXX_DEBUG_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_debug_init(void) +{ + return platform_driver_register(&pm8xxx_debug_driver); +} +subsys_initcall(pm8xxx_debug_init); + +static void __exit pm8xxx_debug_exit(void) +{ + platform_driver_unregister(&pm8xxx_debug_driver); +} +module_exit(pm8xxx_debug_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PM8XXX Debug driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_DEBUG_DEV_NAME); diff --git a/drivers/mfd/pm8xxx-irq.c b/drivers/mfd/pm8xxx-irq.c index d452dd01308..c04d0d1bfe2 100644 --- a/drivers/mfd/pm8xxx-irq.c +++ b/drivers/mfd/pm8xxx-irq.c @@ -247,12 +247,20 @@ static int pm8xxx_irq_set_wake(struct irq_data *d, unsigned int on) return 0; } +static int pm8xxx_irq_read_line(struct irq_data *d) +{ + struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d); + + return pm8xxx_get_irq_stat(chip, d->irq); +} + static struct irq_chip pm8xxx_irq_chip = { .name = "pm8xxx", .irq_mask_ack = pm8xxx_irq_mask_ack, .irq_unmask = pm8xxx_irq_unmask, .irq_set_type = pm8xxx_irq_set_type, .irq_set_wake = pm8xxx_irq_set_wake, + .irq_read_line = pm8xxx_irq_read_line, .flags = IRQCHIP_MASK_ON_SUSPEND, }; @@ -358,7 +366,7 @@ struct pm_irq_chip * __devinit pm8xxx_irq_init(struct device *dev, irq_set_irq_type(devirq, pdata->irq_trigger_flag); irq_set_handler_data(devirq, chip); irq_set_chained_handler(devirq, pm8xxx_irq_handler); - set_irq_wake(devirq, 1); + irq_set_irq_wake(devirq, 1); return chip; } diff --git a/drivers/mfd/pm8xxx-misc.c b/drivers/mfd/pm8xxx-misc.c new file mode 100644 index 00000000000..cd5624f5292 --- /dev/null +++ b/drivers/mfd/pm8xxx-misc.c @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include + +/* PON CTRL 1 register */ +#define REG_PM8058_PON_CTRL_1 0x01C +#define REG_PM8921_PON_CTRL_1 0x01C + +#define PON_CTRL_1_PULL_UP_MASK 0xE0 +#define PON_CTRL_1_USB_PWR_EN 0x10 + +#define PON_CTRL_1_WD_EN_MASK 0x08 +#define PON_CTRL_1_WD_EN_RESET 0x08 +#define PON_CTRL_1_WD_EN_PWR_OFF 0x00 + +/* Regulator L22 control register */ +#define REG_PM8058_L22_CTRL 0x121 + +/* SLEEP CTRL register */ +#define REG_PM8058_SLEEP_CTRL 0x02B +#define REG_PM8921_SLEEP_CTRL 0x10A + +#define SLEEP_CTRL_SMPL_EN_MASK 0x04 +#define SLEEP_CTRL_SMPL_EN_RESET 0x04 +#define SLEEP_CTRL_SMPL_EN_PWR_OFF 0x00 + +/* FTS regulator PMR registers */ +#define REG_PM8901_REGULATOR_S1_PMR 0xA7 +#define REG_PM8901_REGULATOR_S2_PMR 0xA8 +#define REG_PM8901_REGULATOR_S3_PMR 0xA9 +#define REG_PM8901_REGULATOR_S4_PMR 0xAA + +#define PM8901_REGULATOR_PMR_STATE_MASK 0x60 +#define PM8901_REGULATOR_PMR_STATE_OFF 0x20 + +struct pm8xxx_misc_chip { + struct list_head link; + struct pm8xxx_misc_platform_data pdata; + struct device *dev; + enum pm8xxx_version version; +}; + +static LIST_HEAD(pm8xxx_misc_chips); +static DEFINE_SPINLOCK(pm8xxx_misc_chips_lock); + +static int pm8xxx_misc_masked_write(struct pm8xxx_misc_chip *chip, u16 addr, + u8 mask, u8 val) +{ + int rc; + u8 reg; + + rc = pm8xxx_readb(chip->dev->parent, addr, ®); + if (rc) { + pr_err("pm8xxx_readb(0x%03X) failed, rc=%d\n", addr, rc); + return rc; + } + reg &= ~mask; + reg |= val & mask; + rc = pm8xxx_writeb(chip->dev->parent, addr, reg); + if (rc) + pr_err("pm8xxx_writeb(0x%03X)=0x%02X failed, rc=%d\n", addr, + reg, rc); + return rc; +} + +static int __pm8058_reset_pwr_off(struct pm8xxx_misc_chip *chip, int reset) +{ + int rc; + + /* + * Fix-up: Set regulator LDO22 to 1.225 V in high power mode. Leave its + * pull-down state intact. This ensures a safe shutdown. + */ + rc = pm8xxx_misc_masked_write(chip, REG_PM8058_L22_CTRL, 0xBF, 0x93); + if (rc) { + pr_err("pm8xxx_misc_masked_write failed, rc=%d\n", rc); + goto read_write_err; + } + + /* Enable SMPL if resetting is desired. */ + rc = pm8xxx_misc_masked_write(chip, REG_PM8058_SLEEP_CTRL, + SLEEP_CTRL_SMPL_EN_MASK, + (reset ? SLEEP_CTRL_SMPL_EN_RESET : SLEEP_CTRL_SMPL_EN_PWR_OFF)); + if (rc) { + pr_err("pm8xxx_misc_masked_write failed, rc=%d\n", rc); + goto read_write_err; + } + + /* + * Select action to perform (reset or shutdown) when PS_HOLD goes low. + * Also ensure that KPD, CBL0, and CBL1 pull ups are enabled and that + * USB charging is enabled. + */ + rc = pm8xxx_misc_masked_write(chip, REG_PM8058_PON_CTRL_1, + PON_CTRL_1_PULL_UP_MASK | PON_CTRL_1_USB_PWR_EN + | PON_CTRL_1_WD_EN_MASK, + PON_CTRL_1_PULL_UP_MASK | PON_CTRL_1_USB_PWR_EN + | (reset ? PON_CTRL_1_WD_EN_RESET : PON_CTRL_1_WD_EN_PWR_OFF)); + if (rc) { + pr_err("pm8xxx_misc_masked_write failed, rc=%d\n", rc); + goto read_write_err; + } + +read_write_err: + return rc; +} + +static int __pm8901_reset_pwr_off(struct pm8xxx_misc_chip *chip, int reset) +{ + int rc = 0, i; + u8 pmr_addr[4] = { + REG_PM8901_REGULATOR_S2_PMR, + REG_PM8901_REGULATOR_S3_PMR, + REG_PM8901_REGULATOR_S4_PMR, + REG_PM8901_REGULATOR_S1_PMR, + }; + + /* Fix-up: Turn off regulators S1, S2, S3, S4 when shutting down. */ + if (!reset) { + for (i = 0; i < 4; i++) { + rc = pm8xxx_misc_masked_write(chip, pmr_addr[i], + PM8901_REGULATOR_PMR_STATE_MASK, + PM8901_REGULATOR_PMR_STATE_OFF); + if (rc) { + pr_err("pm8xxx_misc_masked_write failed, " + "rc=%d\n", rc); + goto read_write_err; + } + } + } + +read_write_err: + return rc; +} + +static int __pm8921_reset_pwr_off(struct pm8xxx_misc_chip *chip, int reset) +{ + int rc; + + /* Enable SMPL if resetting is desired. */ + rc = pm8xxx_misc_masked_write(chip, REG_PM8921_SLEEP_CTRL, + SLEEP_CTRL_SMPL_EN_MASK, + (reset ? SLEEP_CTRL_SMPL_EN_RESET : SLEEP_CTRL_SMPL_EN_PWR_OFF)); + if (rc) { + pr_err("pm8xxx_misc_masked_write failed, rc=%d\n", rc); + goto read_write_err; + } + + /* + * Select action to perform (reset or shutdown) when PS_HOLD goes low. + * Also ensure that KPD, CBL0, and CBL1 pull ups are enabled and that + * USB charging is enabled. + */ + rc = pm8xxx_misc_masked_write(chip, REG_PM8921_PON_CTRL_1, + PON_CTRL_1_PULL_UP_MASK | PON_CTRL_1_USB_PWR_EN + | PON_CTRL_1_WD_EN_MASK, + PON_CTRL_1_PULL_UP_MASK | PON_CTRL_1_USB_PWR_EN + | (reset ? PON_CTRL_1_WD_EN_RESET : PON_CTRL_1_WD_EN_PWR_OFF)); + if (rc) { + pr_err("pm8xxx_misc_masked_write failed, rc=%d\n", rc); + goto read_write_err; + } + +read_write_err: + return rc; +} + +/** + * pm8xxx_reset_pwr_off - switch all PM8XXX PMIC chips attached to the system to + * either reset or shutdown when they are turned off + * @reset: 0 = shudown the PMICs, 1 = shutdown and then restart the PMICs + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_reset_pwr_off(int reset) +{ + struct pm8xxx_misc_chip *chip; + unsigned long flags; + int rc = 0; + + spin_lock_irqsave(&pm8xxx_misc_chips_lock, flags); + + /* Loop over all attached PMICs and call specific functions for them. */ + list_for_each_entry(chip, &pm8xxx_misc_chips, link) { + switch (chip->version) { + case PM8XXX_VERSION_8058: + rc = __pm8058_reset_pwr_off(chip, reset); + break; + case PM8XXX_VERSION_8901: + rc = __pm8901_reset_pwr_off(chip, reset); + break; + case PM8XXX_VERSION_8921: + rc = __pm8921_reset_pwr_off(chip, reset); + break; + default: + /* PMIC doesn't have reset_pwr_off; do nothing. */ + break; + } + if (rc) { + pr_err("reset_pwr_off failed, rc=%d\n", rc); + break; + } + } + + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_reset_pwr_off); + +static int __devinit pm8xxx_misc_probe(struct platform_device *pdev) +{ + const struct pm8xxx_misc_platform_data *pdata = pdev->dev.platform_data; + struct pm8xxx_misc_chip *chip; + struct pm8xxx_misc_chip *sibling; + struct list_head *prev; + unsigned long flags; + int rc = 0; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct pm8xxx_misc_chip), GFP_KERNEL); + if (!chip) { + pr_err("Cannot allocate %d bytes\n", + sizeof(struct pm8xxx_misc_chip)); + return -ENOMEM; + } + + chip->dev = &pdev->dev; + chip->version = pm8xxx_get_version(chip->dev->parent); + memcpy(&(chip->pdata), pdata, sizeof(struct pm8xxx_misc_platform_data)); + + /* Insert PMICs in priority order (lowest value first). */ + spin_lock_irqsave(&pm8xxx_misc_chips_lock, flags); + prev = &pm8xxx_misc_chips; + list_for_each_entry(sibling, &pm8xxx_misc_chips, link) { + if (chip->pdata.priority < sibling->pdata.priority) + break; + else + prev = &sibling->link; + } + list_add(&chip->link, prev); + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + platform_set_drvdata(pdev, chip); + + return rc; +} + +static int __devexit pm8xxx_misc_remove(struct platform_device *pdev) +{ + struct pm8xxx_misc_chip *chip = platform_get_drvdata(pdev); + unsigned long flags; + + spin_lock_irqsave(&pm8xxx_misc_chips_lock, flags); + list_del(&chip->link); + spin_unlock_irqrestore(&pm8xxx_misc_chips_lock, flags); + + platform_set_drvdata(pdev, NULL); + kfree(chip); + + return 0; +} + +static struct platform_driver pm8xxx_misc_driver = { + .probe = pm8xxx_misc_probe, + .remove = __devexit_p(pm8xxx_misc_remove), + .driver = { + .name = PM8XXX_MISC_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_misc_init(void) +{ + return platform_driver_register(&pm8xxx_misc_driver); +} +postcore_initcall(pm8xxx_misc_init); + +static void __exit pm8xxx_misc_exit(void) +{ + platform_driver_unregister(&pm8xxx_misc_driver); +} +module_exit(pm8xxx_misc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC 8XXX misc driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_MISC_DEV_NAME); diff --git a/drivers/mfd/pm8xxx-pwm.c b/drivers/mfd/pm8xxx-pwm.c new file mode 100644 index 00000000000..cdddd98cf56 --- /dev/null +++ b/drivers/mfd/pm8xxx-pwm.c @@ -0,0 +1,1082 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * Qualcomm PM8XXX Pulse Width Modulation (PWM) driver + * + * The HW module is also called LPG (Light Pulse Generator). + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#define PM8XXX_LPG_BANKS 8 +#define PM8XXX_PWM_CHANNELS PM8XXX_LPG_BANKS + +#define PM8XXX_LPG_CTL_REGS 7 + +/* PM8XXX PWM */ +#define SSBI_REG_ADDR_LPG_CTL_BASE 0x13C +#define SSBI_REG_ADDR_LPG_CTL(n) (SSBI_REG_ADDR_LPG_CTL_BASE + (n)) +#define SSBI_REG_ADDR_LPG_BANK_SEL 0x143 +#define SSBI_REG_ADDR_LPG_BANK_EN 0x144 +#define SSBI_REG_ADDR_LPG_LUT_CFG0 0x145 +#define SSBI_REG_ADDR_LPG_LUT_CFG1 0x146 + +/* Control 0 */ +#define PM8XXX_PWM_1KHZ_COUNT_MASK 0xF0 +#define PM8XXX_PWM_1KHZ_COUNT_SHIFT 4 + +#define PM8XXX_PWM_1KHZ_COUNT_MAX 15 + +#define PM8XXX_PWM_OUTPUT_EN 0x08 +#define PM8XXX_PWM_PWM_EN 0x04 +#define PM8XXX_PWM_RAMP_GEN_EN 0x02 +#define PM8XXX_PWM_RAMP_START 0x01 + +#define PM8XXX_PWM_PWM_START (PM8XXX_PWM_OUTPUT_EN \ + | PM8XXX_PWM_PWM_EN) +#define PM8XXX_PWM_RAMP_GEN_START (PM8XXX_PWM_RAMP_GEN_EN \ + | PM8XXX_PWM_RAMP_START) + +/* Control 1 */ +#define PM8XXX_PWM_REVERSE_EN 0x80 +#define PM8XXX_PWM_BYPASS_LUT 0x40 +#define PM8XXX_PWM_HIGH_INDEX_MASK 0x3F + +/* Control 2 */ +#define PM8XXX_PWM_LOOP_EN 0x80 +#define PM8XXX_PWM_RAMP_UP 0x40 +#define PM8XXX_PWM_LOW_INDEX_MASK 0x3F + +/* Control 3 */ +#define PM8XXX_PWM_VALUE_BIT7_0 0xFF +#define PM8XXX_PWM_VALUE_BIT5_0 0x3F + +/* Control 4 */ +#define PM8XXX_PWM_VALUE_BIT8 0x80 + +#define PM8XXX_PWM_CLK_SEL_MASK 0x60 +#define PM8XXX_PWM_CLK_SEL_SHIFT 5 + +#define PM8XXX_PWM_CLK_SEL_NO 0 +#define PM8XXX_PWM_CLK_SEL_1KHZ 1 +#define PM8XXX_PWM_CLK_SEL_32KHZ 2 +#define PM8XXX_PWM_CLK_SEL_19P2MHZ 3 + +#define PM8XXX_PWM_PREDIVIDE_MASK 0x18 +#define PM8XXX_PWM_PREDIVIDE_SHIFT 3 + +#define PM8XXX_PWM_PREDIVIDE_2 0 +#define PM8XXX_PWM_PREDIVIDE_3 1 +#define PM8XXX_PWM_PREDIVIDE_5 2 +#define PM8XXX_PWM_PREDIVIDE_6 3 + +#define PM8XXX_PWM_M_MASK 0x07 +#define PM8XXX_PWM_M_MIN 0 +#define PM8XXX_PWM_M_MAX 7 + +/* Control 5 */ +#define PM8XXX_PWM_PAUSE_COUNT_HI_MASK 0xFC +#define PM8XXX_PWM_PAUSE_COUNT_HI_SHIFT 2 + +#define PM8XXX_PWM_PAUSE_ENABLE_HIGH 0x02 +#define PM8XXX_PWM_SIZE_9_BIT 0x01 + +/* Control 6 */ +#define PM8XXX_PWM_PAUSE_COUNT_LO_MASK 0xFC +#define PM8XXX_PWM_PAUSE_COUNT_LO_SHIFT 2 + +#define PM8XXX_PWM_PAUSE_ENABLE_LOW 0x02 +#define PM8XXX_PWM_RESERVED 0x01 + +#define PM8XXX_PWM_PAUSE_COUNT_MAX 56 /* < 2^6 = 64 */ + +/* LUT_CFG1 */ +#define PM8XXX_PWM_LUT_READ 0x40 + +/* + * PWM Frequency = Clock Frequency / (N * T) + * or + * PWM Period = Clock Period * (N * T) + * where + * N = 2^9 or 2^6 for 9-bit or 6-bit PWM size + * T = Pre-divide * 2^m, where m = 0..7 (exponent) + * + * This is the formula to figure out m for the best pre-divide and clock: + * (PWM Period / N) / 2^m = (Pre-divide * Clock Period) + */ +#define NUM_CLOCKS 3 + +#define NSEC_1000HZ (NSEC_PER_SEC / 1000) +#define NSEC_32768HZ (NSEC_PER_SEC / 32768) +#define NSEC_19P2MHZ (NSEC_PER_SEC / 19200000) + +#define CLK_PERIOD_MIN NSEC_19P2MHZ +#define CLK_PERIOD_MAX NSEC_1000HZ + +#define NUM_PRE_DIVIDE 3 /* No default support for pre-divide = 6 */ + +#define PRE_DIVIDE_0 2 +#define PRE_DIVIDE_1 3 +#define PRE_DIVIDE_2 5 + +#define PRE_DIVIDE_MIN PRE_DIVIDE_0 +#define PRE_DIVIDE_MAX PRE_DIVIDE_2 + +static unsigned int pt_t[NUM_PRE_DIVIDE][NUM_CLOCKS] = { + { PRE_DIVIDE_0 * NSEC_1000HZ, + PRE_DIVIDE_0 * NSEC_32768HZ, + PRE_DIVIDE_0 * NSEC_19P2MHZ, + }, + { PRE_DIVIDE_1 * NSEC_1000HZ, + PRE_DIVIDE_1 * NSEC_32768HZ, + PRE_DIVIDE_1 * NSEC_19P2MHZ, + }, + { PRE_DIVIDE_2 * NSEC_1000HZ, + PRE_DIVIDE_2 * NSEC_32768HZ, + PRE_DIVIDE_2 * NSEC_19P2MHZ, + }, +}; + +#define MIN_MPT ((PRE_DIVIDE_MIN * CLK_PERIOD_MIN) << PM8XXX_PWM_M_MIN) +#define MAX_MPT ((PRE_DIVIDE_MAX * CLK_PERIOD_MAX) << PM8XXX_PWM_M_MAX) + +/* Private data */ +struct pm8xxx_pwm_chip; + +struct pwm_device { + int pwm_id; /* = bank/channel id */ + int in_use; + const char *label; + int pwm_period; + int pwm_duty; + u8 pwm_ctl[PM8XXX_LPG_CTL_REGS]; + int irq; + struct pm8xxx_pwm_chip *chip; +}; + +struct pm8xxx_pwm_chip { + struct pwm_device pwm_dev[PM8XXX_PWM_CHANNELS]; + u8 bank_mask; + struct mutex pwm_mutex; + struct device *dev; +}; + +static struct pm8xxx_pwm_chip *pwm_chip; + +struct pm8xxx_pwm_config { + int pwm_size; /* round up to 6 or 9 for 6/9-bit PWM SIZE */ + int clk; + int pre_div; + int pre_div_exp; + int pwm_value; + int bypass_lut; + + /* LUT parameters when bypass_lut is 0 */ + int lut_duty_ms; + int lut_lo_index; + int lut_hi_index; + int lut_pause_hi; + int lut_pause_lo; + int flags; +}; + +static const u16 duty_msec[PM8XXX_PWM_1KHZ_COUNT_MAX + 1] = { + 0, 1, 2, 3, 4, 6, 8, 16, 18, 24, 32, 36, 64, 128, 256, 512 +}; + +static const u16 pause_count[PM8XXX_PWM_PAUSE_COUNT_MAX + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 23, 28, 31, 42, 47, 56, 63, 83, 94, 111, 125, 167, 188, 222, 250, 333, + 375, 500, 667, 750, 800, 900, 1000, 1100, + 1200, 1300, 1400, 1500, 1600, 1800, 2000, 2500, + 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, + 7000 +}; + +/* Internal functions */ +static int pm8xxx_pwm_bank_enable(struct pwm_device *pwm, int enable) +{ + int rc; + u8 reg; + struct pm8xxx_pwm_chip *chip; + + chip = pwm->chip; + + if (enable) + reg = chip->bank_mask | (1 << pwm->pwm_id); + else + reg = chip->bank_mask & ~(1 << pwm->pwm_id); + + rc = pm8xxx_writeb(chip->dev->parent, SSBI_REG_ADDR_LPG_BANK_EN, reg); + if (rc) { + pr_err("pm8xxx_write(): rc=%d (Enable LPG Bank)\n", rc); + return rc; + } + chip->bank_mask = reg; + + return 0; +} + +static int pm8xxx_pwm_bank_sel(struct pwm_device *pwm) +{ + int rc; + + rc = pm8xxx_writeb(pwm->chip->dev->parent, SSBI_REG_ADDR_LPG_BANK_SEL, + pwm->pwm_id); + if (rc) + pr_err("pm8xxx_write(): rc=%d (Select PWM Bank)\n", rc); + return rc; +} + +static int pm8xxx_pwm_start(struct pwm_device *pwm, int start, int ramp_start) +{ + int rc; + u8 reg; + + if (start) { + reg = pwm->pwm_ctl[0] | PM8XXX_PWM_PWM_START; + if (ramp_start) + reg |= PM8XXX_PWM_RAMP_GEN_START; + else + reg &= ~PM8XXX_PWM_RAMP_GEN_START; + } else { + reg = pwm->pwm_ctl[0] & ~PM8XXX_PWM_PWM_START; + reg &= ~PM8XXX_PWM_RAMP_GEN_START; + } + + rc = pm8xxx_writeb(pwm->chip->dev->parent, SSBI_REG_ADDR_LPG_CTL(0), + reg); + if (rc) + pr_err("pm8xxx_write(): rc=%d (Enable PWM Ctl 0)\n", rc); + else + pwm->pwm_ctl[0] = reg; + return rc; +} + +static void pm8xxx_pwm_calc_period(unsigned int period_us, + struct pm8xxx_pwm_config *pwm_conf) +{ + int n, m, clk, div; + int best_m, best_div, best_clk; + int last_err, cur_err, better_err, better_m; + unsigned int tmp_p, last_p, min_err, period_n; + + /* PWM Period / N */ + if (period_us < (40 * USEC_PER_SEC)) { /* ~6-bit max */ + period_n = (period_us * NSEC_PER_USEC) >> 6; + n = 6; + } else if (period_us < (274 * USEC_PER_SEC)) { /* overflow threshold */ + period_n = (period_us >> 6) * NSEC_PER_USEC; + if (period_n >= MAX_MPT) { + n = 9; + period_n >>= 3; + } else { + n = 6; + } + } else { + period_n = (period_us >> 9) * NSEC_PER_USEC; + n = 9; + } + + min_err = MAX_MPT; + best_m = 0; + best_clk = 0; + best_div = 0; + for (clk = 0; clk < NUM_CLOCKS; clk++) { + for (div = 0; div < NUM_PRE_DIVIDE; div++) { + tmp_p = period_n; + last_p = tmp_p; + for (m = 0; m <= PM8XXX_PWM_M_MAX; m++) { + if (tmp_p <= pt_t[div][clk]) { + /* Found local best */ + if (!m) { + better_err = pt_t[div][clk] - + tmp_p; + better_m = m; + } else { + last_err = last_p - + pt_t[div][clk]; + cur_err = pt_t[div][clk] - + tmp_p; + + if (cur_err < last_err) { + better_err = cur_err; + better_m = m; + } else { + better_err = last_err; + better_m = m - 1; + } + } + + if (better_err < min_err) { + min_err = better_err; + best_m = better_m; + best_clk = clk; + best_div = div; + } + break; + } else { + last_p = tmp_p; + tmp_p >>= 1; + } + } + } + } + + pwm_conf->pwm_size = n; + pwm_conf->clk = best_clk; + pwm_conf->pre_div = best_div; + pwm_conf->pre_div_exp = best_m; +} + +static int pm8xxx_pwm_configure(struct pwm_device *pwm, + struct pm8xxx_pwm_config *pwm_conf) +{ + int i, rc, len; + u8 reg, ramp_enabled = 0; + + reg = (pwm_conf->pwm_size > 6) ? PM8XXX_PWM_SIZE_9_BIT : 0; + pwm->pwm_ctl[5] = reg; + + reg = ((pwm_conf->clk + 1) << PM8XXX_PWM_CLK_SEL_SHIFT) + & PM8XXX_PWM_CLK_SEL_MASK; + reg |= (pwm_conf->pre_div << PM8XXX_PWM_PREDIVIDE_SHIFT) + & PM8XXX_PWM_PREDIVIDE_MASK; + reg |= pwm_conf->pre_div_exp & PM8XXX_PWM_M_MASK; + pwm->pwm_ctl[4] = reg; + + if (pwm_conf->bypass_lut) { + pwm->pwm_ctl[0] &= PM8XXX_PWM_PWM_START; /* keep enabled */ + pwm->pwm_ctl[1] = PM8XXX_PWM_BYPASS_LUT; + pwm->pwm_ctl[2] = 0; + + if (pwm_conf->pwm_size > 6) { + pwm->pwm_ctl[3] = pwm_conf->pwm_value + & PM8XXX_PWM_VALUE_BIT7_0; + pwm->pwm_ctl[4] |= (pwm_conf->pwm_value >> 1) + & PM8XXX_PWM_VALUE_BIT8; + } else { + pwm->pwm_ctl[3] = pwm_conf->pwm_value + & PM8XXX_PWM_VALUE_BIT5_0; + } + + len = 6; + } else { + int pause_cnt, j; + + /* Linear search for duty time */ + for (i = 0; i < PM8XXX_PWM_1KHZ_COUNT_MAX; i++) { + if (duty_msec[i] >= pwm_conf->lut_duty_ms) + break; + } + + ramp_enabled = pwm->pwm_ctl[0] & PM8XXX_PWM_RAMP_GEN_START; + pwm->pwm_ctl[0] &= PM8XXX_PWM_PWM_START; /* keep enabled */ + pwm->pwm_ctl[0] |= (i << PM8XXX_PWM_1KHZ_COUNT_SHIFT) & + PM8XXX_PWM_1KHZ_COUNT_MASK; + pwm->pwm_ctl[1] = pwm_conf->lut_hi_index & + PM8XXX_PWM_HIGH_INDEX_MASK; + pwm->pwm_ctl[2] = pwm_conf->lut_lo_index & + PM8XXX_PWM_LOW_INDEX_MASK; + + if (pwm_conf->flags & PM_PWM_LUT_REVERSE) + pwm->pwm_ctl[1] |= PM8XXX_PWM_REVERSE_EN; + if (pwm_conf->flags & PM_PWM_LUT_RAMP_UP) + pwm->pwm_ctl[2] |= PM8XXX_PWM_RAMP_UP; + if (pwm_conf->flags & PM_PWM_LUT_LOOP) + pwm->pwm_ctl[2] |= PM8XXX_PWM_LOOP_EN; + + /* Pause time */ + if (pwm_conf->flags & PM_PWM_LUT_PAUSE_HI_EN) { + /* Linear search for pause time */ + pause_cnt = (pwm_conf->lut_pause_hi + duty_msec[i] / 2) + / duty_msec[i]; + for (j = 0; j < PM8XXX_PWM_PAUSE_COUNT_MAX; j++) { + if (pause_count[j] >= pause_cnt) + break; + } + pwm->pwm_ctl[5] |= (j << + PM8XXX_PWM_PAUSE_COUNT_HI_SHIFT) & + PM8XXX_PWM_PAUSE_COUNT_HI_MASK; + pwm->pwm_ctl[5] |= PM8XXX_PWM_PAUSE_ENABLE_HIGH; + } + + if (pwm_conf->flags & PM_PWM_LUT_PAUSE_LO_EN) { + /* Linear search for pause time */ + pause_cnt = (pwm_conf->lut_pause_lo + duty_msec[i] / 2) + / duty_msec[i]; + for (j = 0; j < PM8XXX_PWM_PAUSE_COUNT_MAX; j++) { + if (pause_count[j] >= pause_cnt) + break; + } + pwm->pwm_ctl[6] = (j << + PM8XXX_PWM_PAUSE_COUNT_LO_SHIFT) & + PM8XXX_PWM_PAUSE_COUNT_LO_MASK; + pwm->pwm_ctl[6] |= PM8XXX_PWM_PAUSE_ENABLE_LOW; + } else { + pwm->pwm_ctl[6] = 0; + } + + len = 7; + } + + pm8xxx_pwm_bank_sel(pwm); + + for (i = 0; i < len; i++) { + rc = pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_LPG_CTL(i), + pwm->pwm_ctl[i]); + if (rc) { + pr_err("pm8xxx_write(): rc=%d (PWM Ctl[%d])\n", rc, i); + break; + } + } + + if (ramp_enabled) { + pwm->pwm_ctl[0] |= ramp_enabled; + pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_LPG_CTL(0), + pwm->pwm_ctl[0]); + } + + return rc; +} + +/* APIs */ +/** + * pwm_request - request a PWM device + * @pwm_id: PWM id or channel + * @label: the label to identify the user + */ +struct pwm_device *pwm_request(int pwm_id, const char *label) +{ + struct pwm_device *pwm; + + if (pwm_id > PM8XXX_PWM_CHANNELS || pwm_id < 0) { + pr_err("Invalid pwm_id: %d with %s\n", + pwm_id, label ? label : "."); + return ERR_PTR(-EINVAL); + } + if (pwm_chip == NULL) { + pr_err("No pwm_chip\n"); + return ERR_PTR(-ENODEV); + } + + mutex_lock(&pwm_chip->pwm_mutex); + pwm = &pwm_chip->pwm_dev[pwm_id]; + if (!pwm->in_use) { + pwm->in_use = 1; + pwm->label = label; + } else { + pwm = ERR_PTR(-EBUSY); + } + mutex_unlock(&pwm_chip->pwm_mutex); + + return pwm; +} +EXPORT_SYMBOL_GPL(pwm_request); + +/** + * pwm_free - free a PWM device + * @pwm: the PWM device + */ +void pwm_free(struct pwm_device *pwm) +{ + if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) { + pr_err("Invalid pwm handle\n"); + return; + } + + mutex_lock(&pwm->chip->pwm_mutex); + if (pwm->in_use) { + pm8xxx_pwm_bank_sel(pwm); + pm8xxx_pwm_start(pwm, 0, 0); + + pwm->in_use = 0; + pwm->label = NULL; + } + pm8xxx_pwm_bank_enable(pwm, 0); + mutex_unlock(&pwm->chip->pwm_mutex); +} +EXPORT_SYMBOL_GPL(pwm_free); + +/** + * pwm_config - change a PWM device configuration + * @pwm: the PWM device + * @period_us: period in microseconds + * @duty_us: duty cycle in microseconds + */ +int pwm_config(struct pwm_device *pwm, int duty_us, int period_us) +{ + struct pm8xxx_pwm_config pwm_conf; + unsigned int max_pwm_value, tmp; + int rc; + + if (pwm == NULL || IS_ERR(pwm) || + duty_us > period_us || + (unsigned)period_us > PM8XXX_PWM_PERIOD_MAX || + (unsigned)period_us < PM8XXX_PWM_PERIOD_MIN) { + pr_err("Invalid pwm handle or parameters\n"); + return -EINVAL; + } + if (pwm->chip == NULL) { + pr_err("No pwm_chip\n"); + return -ENODEV; + } + + mutex_lock(&pwm->chip->pwm_mutex); + + if (!pwm->in_use) { + pr_err("pwm_id: %d: stale handle?\n", pwm->pwm_id); + rc = -EINVAL; + goto out_unlock; + } + + pm8xxx_pwm_calc_period(period_us, &pwm_conf); + + /* Figure out pwm_value with overflow handling */ + if ((unsigned)period_us > (1 << pwm_conf.pwm_size)) { + tmp = period_us; + tmp >>= pwm_conf.pwm_size; + pwm_conf.pwm_value = (unsigned)duty_us / tmp; + } else { + tmp = duty_us; + tmp <<= pwm_conf.pwm_size; + pwm_conf.pwm_value = tmp / (unsigned)period_us; + } + max_pwm_value = (1 << pwm_conf.pwm_size) - 1; + if (pwm_conf.pwm_value > max_pwm_value) + pwm_conf.pwm_value = max_pwm_value; + + pwm_conf.bypass_lut = 1; + + rc = pm8xxx_pwm_configure(pwm, &pwm_conf); + +out_unlock: + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(pwm_config); + +/** + * pwm_enable - start a PWM output toggling + * @pwm: the PWM device + */ +int pwm_enable(struct pwm_device *pwm) +{ + int rc; + + if (pwm == NULL || IS_ERR(pwm)) { + pr_err("Invalid pwm handle\n"); + return -EINVAL; + } + if (pwm->chip == NULL) { + pr_err("No pwm_chip\n"); + return -ENODEV; + } + + mutex_lock(&pwm->chip->pwm_mutex); + if (!pwm->in_use) { + pr_err("pwm_id: %d: stale handle?\n", pwm->pwm_id); + rc = -EINVAL; + } else { + rc = pm8xxx_pwm_bank_enable(pwm, 1); + + pm8xxx_pwm_bank_sel(pwm); + pm8xxx_pwm_start(pwm, 1, 0); + } + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(pwm_enable); + +/** + * pwm_disable - stop a PWM output toggling + * @pwm: the PWM device + */ +void pwm_disable(struct pwm_device *pwm) +{ + if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) { + pr_err("Invalid pwm handle or no pwm_chip\n"); + return; + } + + mutex_lock(&pwm->chip->pwm_mutex); + if (pwm->in_use) { + pm8xxx_pwm_bank_sel(pwm); + pm8xxx_pwm_start(pwm, 0, 0); + + pm8xxx_pwm_bank_enable(pwm, 0); + } + mutex_unlock(&pwm->chip->pwm_mutex); +} +EXPORT_SYMBOL_GPL(pwm_disable); + +/** + * pm8xxx_pwm_lut_config - change a PWM device configuration to use LUT + * @pwm: the PWM device + * @period_us: period in microseconds + * @duty_pct: arrary of duty cycles in percent, like 20, 50. + * @duty_time_ms: time for each duty cycle in milliseconds + * @start_idx: start index in lookup table from 0 to MAX-1 + * @idx_len: number of index + * @pause_lo: pause time in milliseconds at low index + * @pause_hi: pause time in milliseconds at high index + * @flags: control flags + */ +int pm8xxx_pwm_lut_config(struct pwm_device *pwm, int period_us, + int duty_pct[], int duty_time_ms, int start_idx, + int idx_len, int pause_lo, int pause_hi, int flags) +{ + struct pm8xxx_pwm_config pwm_conf; + unsigned int pwm_value, max_pwm_value; + u8 cfg0, cfg1; + int i, len; + int rc; + + if (pwm == NULL || IS_ERR(pwm) || !idx_len) { + pr_err("Invalid pwm handle or idx_len=0\n"); + return -EINVAL; + } + if (duty_pct == NULL && !(flags & PM_PWM_LUT_NO_TABLE)) { + pr_err("Invalid duty_pct with flag\n"); + return -EINVAL; + } + if (pwm->chip == NULL) { + pr_err("No pwm_chip\n"); + return -ENODEV; + } + if (idx_len >= PM_PWM_LUT_SIZE && start_idx) { + pr_err("Wrong LUT size or index\n"); + return -EINVAL; + } + if ((start_idx + idx_len) > PM_PWM_LUT_SIZE) { + pr_err("Exceed LUT limit\n"); + return -EINVAL; + } + if ((unsigned)period_us > PM8XXX_PWM_PERIOD_MAX || + (unsigned)period_us < PM8XXX_PWM_PERIOD_MIN) { + pr_err("Period out of range\n"); + return -EINVAL; + } + + mutex_lock(&pwm->chip->pwm_mutex); + + if (!pwm->in_use) { + pr_err("pwm_id: %d: stale handle?\n", pwm->pwm_id); + rc = -EINVAL; + goto out_unlock; + } + + pm8xxx_pwm_calc_period(period_us, &pwm_conf); + + len = (idx_len > PM_PWM_LUT_SIZE) ? PM_PWM_LUT_SIZE : idx_len; + + if (flags & PM_PWM_LUT_NO_TABLE) + goto after_table_write; + + max_pwm_value = (1 << pwm_conf.pwm_size) - 1; + for (i = 0; i < len; i++) { + pwm_value = (duty_pct[i] << pwm_conf.pwm_size) / 100; + /* Avoid overflow */ + if (pwm_value > max_pwm_value) + pwm_value = max_pwm_value; + cfg0 = pwm_value & 0xff; + cfg1 = (pwm_value >> 1) & 0x80; + cfg1 |= start_idx + i; + + pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_LPG_LUT_CFG0, cfg0); + pm8xxx_writeb(pwm->chip->dev->parent, + SSBI_REG_ADDR_LPG_LUT_CFG1, cfg1); + } + +after_table_write: + pwm_conf.lut_duty_ms = duty_time_ms; + pwm_conf.lut_lo_index = start_idx; + pwm_conf.lut_hi_index = start_idx + len - 1; + pwm_conf.lut_pause_lo = pause_lo; + pwm_conf.lut_pause_hi = pause_hi; + pwm_conf.flags = flags; + pwm_conf.bypass_lut = 0; + + rc = pm8xxx_pwm_configure(pwm, &pwm_conf); + +out_unlock: + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(pm8xxx_pwm_lut_config); + +/** + * pm8xxx_pwm_lut_enable - control a PWM device to start/stop LUT ramp + * @pwm: the PWM device + * @start: to start (1), or stop (0) + */ +int pm8xxx_pwm_lut_enable(struct pwm_device *pwm, int start) +{ + if (pwm == NULL || IS_ERR(pwm)) { + pr_err("Invalid pwm handle\n"); + return -EINVAL; + } + if (pwm->chip == NULL) { + pr_err("No pwm_chip\n"); + return -ENODEV; + } + + mutex_lock(&pwm->chip->pwm_mutex); + if (start) { + pm8xxx_pwm_bank_enable(pwm, 1); + + pm8xxx_pwm_bank_sel(pwm); + pm8xxx_pwm_start(pwm, 1, 1); + } else { + pm8xxx_pwm_bank_sel(pwm); + pm8xxx_pwm_start(pwm, 0, 0); + + pm8xxx_pwm_bank_enable(pwm, 0); + } + mutex_unlock(&pwm->chip->pwm_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(pm8xxx_pwm_lut_enable); + +#if defined(CONFIG_DEBUG_FS) + +struct pm8xxx_pwm_dbg_device; + +struct pm8xxx_pwm_user { + int pwm_id; + struct pwm_device *pwm; + int period; + int duty_cycle; + int enable; + struct pm8xxx_pwm_dbg_device *dbgdev; +}; + +struct pm8xxx_pwm_dbg_device { + struct mutex dbg_mutex; + struct device *dev; + struct dentry *dent; + + struct pm8xxx_pwm_user user[PM8XXX_PWM_CHANNELS]; +}; + +static struct pm8xxx_pwm_dbg_device *pmic_dbg_device; + +static int dbg_pwm_check_period(int period) +{ + if (period < PM8XXX_PWM_PERIOD_MIN || period > PM8XXX_PWM_PERIOD_MAX) { + pr_err("period is invalid: %d\n", period); + return -EINVAL; + } + return 0; +} + +static int dbg_pwm_check_duty_cycle(int duty_cycle, const char *func_name) +{ + if (duty_cycle <= 0 || duty_cycle > 100) { + pr_err("%s: duty_cycle is invalid: %d\n", + func_name, duty_cycle); + return -EINVAL; + } + return 0; +} + +static void dbg_pwm_check_handle(struct pm8xxx_pwm_user *puser) +{ + struct pwm_device *tmp; + + if (puser->pwm == NULL) { + tmp = pwm_request(puser->pwm_id, "pwm-dbg"); + if (PTR_ERR(puser->pwm)) { + pr_err("pwm_request: err=%ld\n", PTR_ERR(puser->pwm)); + puser->pwm = NULL; + } + } +} + +static int dbg_pwm_enable_set(void *data, u64 val) +{ + struct pm8xxx_pwm_user *puser = data; + struct pm8xxx_pwm_dbg_device *dbgdev = puser->dbgdev; + int rc; + + mutex_lock(&dbgdev->dbg_mutex); + rc = dbg_pwm_check_duty_cycle(puser->duty_cycle, __func__); + if (!rc) { + puser->enable = val; + dbg_pwm_check_handle(puser); + if (puser->pwm) { + if (puser->enable) + pwm_enable(puser->pwm); + else + pwm_disable(puser->pwm); + } + } + mutex_unlock(&dbgdev->dbg_mutex); + return 0; +} + +static int dbg_pwm_enable_get(void *data, u64 *val) +{ + struct pm8xxx_pwm_user *puser = data; + struct pm8xxx_pwm_dbg_device *dbgdev = puser->dbgdev; + + mutex_lock(&dbgdev->dbg_mutex); + *val = puser->enable; + mutex_unlock(&dbgdev->dbg_mutex); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(dbg_pwm_enable_fops, + dbg_pwm_enable_get, dbg_pwm_enable_set, + "%lld\n"); + +static int dbg_pwm_duty_cycle_set(void *data, u64 val) +{ + struct pm8xxx_pwm_user *puser = data; + struct pm8xxx_pwm_dbg_device *dbgdev = puser->dbgdev; + int rc; + + mutex_lock(&dbgdev->dbg_mutex); + rc = dbg_pwm_check_duty_cycle(val, __func__); + if (!rc) { + puser->duty_cycle = val; + dbg_pwm_check_handle(puser); + if (puser->pwm) { + int duty_us; + + duty_us = puser->duty_cycle * puser->period; + pwm_config(puser->pwm, + puser->duty_cycle, puser->period); + } + } + mutex_unlock(&dbgdev->dbg_mutex); + return 0; +} + +static int dbg_pwm_duty_cycle_get(void *data, u64 *val) +{ + struct pm8xxx_pwm_user *puser = data; + struct pm8xxx_pwm_dbg_device *dbgdev = puser->dbgdev; + + mutex_lock(&dbgdev->dbg_mutex); + *val = puser->duty_cycle; + mutex_unlock(&dbgdev->dbg_mutex); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(dbg_pwm_duty_cycle_fops, + dbg_pwm_duty_cycle_get, dbg_pwm_duty_cycle_set, + "%lld\n"); + +static int dbg_pwm_period_set(void *data, u64 val) +{ + struct pm8xxx_pwm_user *puser = data; + struct pm8xxx_pwm_dbg_device *dbgdev = puser->dbgdev; + int rc; + + mutex_lock(&dbgdev->dbg_mutex); + rc = dbg_pwm_check_period(val); + if (!rc) + puser->period = val; + mutex_unlock(&dbgdev->dbg_mutex); + return 0; +} + +static int dbg_pwm_period_get(void *data, u64 *val) +{ + struct pm8xxx_pwm_user *puser = data; + struct pm8xxx_pwm_dbg_device *dbgdev = puser->dbgdev; + + mutex_lock(&dbgdev->dbg_mutex); + *val = puser->period; + mutex_unlock(&dbgdev->dbg_mutex); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(dbg_pwm_period_fops, + dbg_pwm_period_get, dbg_pwm_period_set, "%lld\n"); + +static int __devinit pm8xxx_pwm_dbg_probe(struct device *dev) +{ + struct pm8xxx_pwm_dbg_device *dbgdev; + struct dentry *dent; + struct dentry *temp; + struct pm8xxx_pwm_user *puser; + int i; + + if (dev == NULL) { + pr_err("no parent data passed in.\n"); + return -EINVAL; + } + + dbgdev = kzalloc(sizeof *dbgdev, GFP_KERNEL); + if (dbgdev == NULL) { + pr_err("kzalloc() failed.\n"); + return -ENOMEM; + } + + mutex_init(&dbgdev->dbg_mutex); + + dbgdev->dev = dev; + + dent = debugfs_create_dir("pm8xxx-pwm-dbg", NULL); + if (dent == NULL || IS_ERR(dent)) { + pr_err("ERR debugfs_create_dir: dent=%p\n", dent); + return -ENOMEM; + } + + dbgdev->dent = dent; + + for (i = 0; i < PM8XXX_PWM_CHANNELS; i++) { + char pwm_ch[] = "0"; + + pwm_ch[0] = '0' + i; + dent = debugfs_create_dir(pwm_ch, dbgdev->dent); + if (dent == NULL || IS_ERR(dent)) { + pr_err("ERR: pwm=%d: dir: dent=%p\n", i, dent); + goto debug_error; + } + + puser = &dbgdev->user[i]; + puser->dbgdev = dbgdev; + puser->pwm_id = i; + temp = debugfs_create_file("period", S_IRUGO | S_IWUSR, + dent, puser, &dbg_pwm_period_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("ERR: pwm=%d: period: dent=%p\n", i, dent); + goto debug_error; + } + + temp = debugfs_create_file("duty-cycle", S_IRUGO | S_IWUSR, + dent, puser, &dbg_pwm_duty_cycle_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("ERR: pwm=%d: duty-cycle: dent=%p\n", i, dent); + goto debug_error; + } + + temp = debugfs_create_file("enable", S_IRUGO | S_IWUSR, + dent, puser, &dbg_pwm_enable_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("ERR: pwm=%d: enable: dent=%p\n", i, dent); + goto debug_error; + } + } + + pmic_dbg_device = dbgdev; + + return 0; + +debug_error: + debugfs_remove_recursive(dbgdev->dent); + return -ENOMEM; +} + +static int __devexit pm8xxx_pwm_dbg_remove(void) +{ + if (pmic_dbg_device) { + debugfs_remove_recursive(pmic_dbg_device->dent); + kfree(pmic_dbg_device); + } + return 0; +} + +#else + +static int __devinit pm8xxx_pwm_dbg_probe(struct device *dev) +{ + return 0; +} + +static int __devexit pm8xxx_pwm_dbg_remove(void) +{ + return 0; +} + +#endif + +static int __devinit pm8xxx_pwm_probe(struct platform_device *pdev) +{ + struct pm8xxx_pwm_chip *chip; + int i; + + chip = kzalloc(sizeof *chip, GFP_KERNEL); + if (chip == NULL) { + pr_err("kzalloc() failed.\n"); + return -ENOMEM; + } + + for (i = 0; i < PM8XXX_PWM_CHANNELS; i++) { + chip->pwm_dev[i].pwm_id = i; + chip->pwm_dev[i].chip = chip; + } + + mutex_init(&chip->pwm_mutex); + + chip->dev = &pdev->dev; + pwm_chip = chip; + platform_set_drvdata(pdev, chip); + + if (pm8xxx_pwm_dbg_probe(&pdev->dev) < 0) + pr_err("could not set up debugfs\n"); + + pr_notice("OK\n"); + return 0; +} + +static int __devexit pm8xxx_pwm_remove(struct platform_device *pdev) +{ + struct pm8xxx_pwm_chip *chip = dev_get_drvdata(pdev->dev.parent); + + pm8xxx_pwm_dbg_remove(); + mutex_destroy(&chip->pwm_mutex); + platform_set_drvdata(pdev, NULL); + kfree(chip); + return 0; +} + +static struct platform_driver pm8xxx_pwm_driver = { + .probe = pm8xxx_pwm_probe, + .remove = __devexit_p(pm8xxx_pwm_remove), + .driver = { + .name = PM8XXX_PWM_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8xxx_pwm_init(void) +{ + return platform_driver_register(&pm8xxx_pwm_driver); +} + +static void __exit pm8xxx_pwm_exit(void) +{ + platform_driver_unregister(&pm8xxx_pwm_driver); +} + +subsys_initcall(pm8xxx_pwm_init); +module_exit(pm8xxx_pwm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PM8XXX PWM driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_PWM_DEV_NAME); diff --git a/drivers/mfd/pmic8058.c b/drivers/mfd/pmic8058.c new file mode 100644 index 00000000000..87d44759ced --- /dev/null +++ b/drivers/mfd/pmic8058.c @@ -0,0 +1,1308 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8058 driver + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PMIC8058 Revision */ +#define SSBI_REG_REV 0x002 /* PMIC4 revision */ + +/* PMIC8058 IRQ */ +#define SSBI_REG_ADDR_IRQ_BASE 0x1BB + +#define SSBI_REG_ADDR_IRQ_ROOT (SSBI_REG_ADDR_IRQ_BASE + 0) +#define SSBI_REG_ADDR_IRQ_M_STATUS1 (SSBI_REG_ADDR_IRQ_BASE + 1) +#define SSBI_REG_ADDR_IRQ_M_STATUS2 (SSBI_REG_ADDR_IRQ_BASE + 2) +#define SSBI_REG_ADDR_IRQ_M_STATUS3 (SSBI_REG_ADDR_IRQ_BASE + 3) +#define SSBI_REG_ADDR_IRQ_M_STATUS4 (SSBI_REG_ADDR_IRQ_BASE + 4) +#define SSBI_REG_ADDR_IRQ_BLK_SEL (SSBI_REG_ADDR_IRQ_BASE + 5) +#define SSBI_REG_ADDR_IRQ_IT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 6) +#define SSBI_REG_ADDR_IRQ_CONFIG (SSBI_REG_ADDR_IRQ_BASE + 7) +#define SSBI_REG_ADDR_IRQ_RT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 8) + +#define PM8058_IRQF_LVL_SEL 0x01 /* level select */ +#define PM8058_IRQF_MASK_FE 0x02 /* mask falling edge */ +#define PM8058_IRQF_MASK_RE 0x04 /* mask rising edge */ +#define PM8058_IRQF_CLR 0x08 /* clear interrupt */ +#define PM8058_IRQF_BITS_MASK 0x70 +#define PM8058_IRQF_BITS_SHIFT 4 +#define PM8058_IRQF_WRITE 0x80 + +#define PM8058_IRQF_MASK_ALL (PM8058_IRQF_MASK_FE | \ + PM8058_IRQF_MASK_RE) +#define PM8058_IRQF_W_C_M (PM8058_IRQF_WRITE | \ + PM8058_IRQF_CLR | \ + PM8058_IRQF_MASK_ALL) + +/* MISC register */ +#define SSBI_REG_ADDR_MISC 0x1CC + +/* PON CNTL 1 register */ +#define SSBI_REG_ADDR_PON_CNTL_1 0x01C + +#define PM8058_PON_PUP_MASK 0xF0 + +#define PM8058_PON_WD_EN_MASK 0x08 +#define PM8058_PON_WD_EN_RESET 0x08 +#define PM8058_PON_WD_EN_PWR_OFF 0x00 + +/* PON CNTL 4 register */ +#define SSBI_REG_ADDR_PON_CNTL_4 0x98 +#define PM8058_PON_RESET_EN_MASK 0x01 + +/* PON CNTL 5 register */ +#define SSBI_REG_ADDR_PON_CNTL_5 0x7B +#define PM8058_HARD_RESET_EN_MASK 0x08 + +/* Regulator L22 control register */ +#define SSBI_REG_ADDR_L22_CTRL 0x121 + +/* SLEEP CNTL register */ +#define SSBI_REG_ADDR_SLEEP_CNTL 0x02B + +#define PM8058_SLEEP_SMPL_EN_MASK 0x04 +#define PM8058_SLEEP_SMPL_EN_RESET 0x04 +#define PM8058_SLEEP_SMPL_EN_PWR_OFF 0x00 + +#define PM8058_SLEEP_SMPL_SEL_MASK 0x03 +#define PM8058_SLEEP_SMPL_SEL_MIN 0 +#define PM8058_SLEEP_SMPL_SEL_MAX 3 + +#define MAX_PM_IRQ 256 +#define MAX_PM_BLOCKS (MAX_PM_IRQ / 8 + 1) +#define MAX_PM_MASTERS (MAX_PM_BLOCKS / 8 + 1) + +struct pm8058_chip { + struct pm8058_platform_data pdata; + + struct i2c_client *dev; + + u8 irqs_allowed[MAX_PM_BLOCKS]; + u8 blocks_allowed[MAX_PM_MASTERS]; + u8 masters_allowed; + int pm_max_irq; + int pm_max_blocks; + int pm_max_masters; + + u8 config[MAX_PM_IRQ]; + u8 bus_unlock_config[MAX_PM_IRQ]; + u8 wake_enable[MAX_PM_IRQ]; + u16 count_wakeable; + + u8 revision; + + struct mutex pm_lock; +}; + +#if defined(CONFIG_DEBUG_FS) +struct pm8058_dbg_device { + struct mutex dbg_mutex; + struct pm8058_chip *pm_chip; + struct dentry *dent; + int addr; +}; + +static struct pm8058_dbg_device *pmic_dbg_device; +#endif + +static struct pm8058_chip *pmic_chip; + +/* Helper Functions */ +DEFINE_RATELIMIT_STATE(pm8058_msg_ratelimit, 60 * HZ, 10); + +static inline int pm8058_can_print(void) +{ + return __ratelimit(&pm8058_msg_ratelimit); +} + +static inline int +ssbi_write(struct i2c_client *client, u16 addr, const u8 *buf, size_t len) +{ + int rc; + struct i2c_msg msg = { + .addr = addr, + .flags = 0x0, + .buf = (u8 *)buf, + .len = len, + }; + + rc = i2c_transfer(client->adapter, &msg, 1); + return (rc == 1) ? 0 : rc; +} + +static inline int +ssbi_read(struct i2c_client *client, u16 addr, u8 *buf, size_t len) +{ + int rc; + struct i2c_msg msg = { + .addr = addr, + .flags = I2C_M_RD, + .buf = buf, + .len = len, + }; + + rc = i2c_transfer(client->adapter, &msg, 1); + return (rc == 1) ? 0 : rc; +} + +static int pm8058_masked_write(u16 addr, u8 val, u8 mask) +{ + int rc; + u8 reg; + + if (pmic_chip == NULL) + return -ENODEV; + + mutex_lock(&pmic_chip->pm_lock); + + rc = ssbi_read(pmic_chip->dev, addr, ®, 1); + if (rc) { + pr_err("%s: ssbi_read(0x%03X) failed: rc=%d\n", __func__, addr, + rc); + goto done; + } + + reg &= ~mask; + reg |= val & mask; + + rc = ssbi_write(pmic_chip->dev, addr, ®, 1); + if (rc) + pr_err("%s: ssbi_write(0x%03X)=0x%02X failed: rc=%d\n", + __func__, addr, reg, rc); +done: + mutex_unlock(&pmic_chip->pm_lock); + + return rc; +} + +/* External APIs */ +int pm8058_rev(struct pm8058_chip *chip) +{ + if (chip == NULL) + return -EINVAL; + + return chip->revision; +} +EXPORT_SYMBOL(pm8058_rev); + +int pm8058_irq_get_rt_status(struct pm8058_chip *chip, int irq) +{ + int rc; + u8 block, bits, bit; + + if (chip == NULL || irq < chip->pdata.irq_base || + irq >= chip->pdata.irq_base + MAX_PM_IRQ) + return -EINVAL; + + irq -= chip->pdata.irq_base; + + block = irq / 8; + bit = irq % 8; + + mutex_lock(&chip->pm_lock); + + rc = ssbi_write(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, &block, 1); + if (rc) { + pr_err("%s: FAIL ssbi_write(): rc=%d (Select Block)\n", + __func__, rc); + goto bail_out; + } + + rc = ssbi_read(chip->dev, SSBI_REG_ADDR_IRQ_RT_STATUS, &bits, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(): rc=%d (Read RT Status)\n", + __func__, rc); + goto bail_out; + } + + rc = (bits & (1 << bit)) ? 1 : 0; + +bail_out: + mutex_unlock(&chip->pm_lock); + + return rc; +} +EXPORT_SYMBOL(pm8058_irq_get_rt_status); + +int pm8058_read(struct pm8058_chip *chip, u16 addr, u8 *values, + unsigned int len) +{ + if (chip == NULL) + return -EINVAL; + + return ssbi_read(chip->dev, addr, values, len); +} +EXPORT_SYMBOL(pm8058_read); + +int pm8058_write(struct pm8058_chip *chip, u16 addr, u8 *values, + unsigned int len) +{ + if (chip == NULL) + return -EINVAL; + + return ssbi_write(chip->dev, addr, values, len); +} +EXPORT_SYMBOL(pm8058_write); + +int pm8058_misc_control(struct pm8058_chip *chip, int mask, int flag) +{ + int rc; + u8 misc; + + if (chip == NULL) + chip = pmic_chip; /* for calls from non child */ + if (chip == NULL) + return -ENODEV; + + mutex_lock(&chip->pm_lock); + + rc = ssbi_read(chip->dev, SSBI_REG_ADDR_MISC, &misc, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(0x%x): rc=%d\n", + __func__, SSBI_REG_ADDR_MISC, rc); + goto get_out; + } + + misc &= ~mask; + misc |= flag; + + rc = ssbi_write(chip->dev, SSBI_REG_ADDR_MISC, &misc, 1); + if (rc) { + pr_err("%s: FAIL ssbi_write(0x%x)=0x%x: rc=%d\n", + __func__, SSBI_REG_ADDR_MISC, misc, rc); + goto get_out; + } + +get_out: + mutex_unlock(&chip->pm_lock); + + return rc; +} +EXPORT_SYMBOL(pm8058_misc_control); + +/** + * pm8058_smpl_control - enables/disables SMPL detection + * @enable: 0 = shutdown PMIC on power loss, 1 = reset PMIC on power loss + * + * This function enables or disables the Sudden Momentary Power Loss detection + * module. If SMPL detection is enabled, then when a sufficiently long power + * loss event occurs, the PMIC will automatically reset itself. If SMPL + * detection is disabled, then the PMIC will shutdown when power loss occurs. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_smpl_control(int enable) +{ + return pm8058_masked_write(SSBI_REG_ADDR_SLEEP_CNTL, + (enable ? PM8058_SLEEP_SMPL_EN_RESET + : PM8058_SLEEP_SMPL_EN_PWR_OFF), + PM8058_SLEEP_SMPL_EN_MASK); +} +EXPORT_SYMBOL(pm8058_smpl_control); + +/** + * pm8058_smpl_set_delay - sets the SMPL detection time delay + * @delay: enum value corresponding to delay time + * + * This function sets the time delay of the SMPL detection module. If power + * is reapplied within this interval, then the PMIC reset automatically. The + * SMPL detection module must be enabled for this delay time to take effect. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_smpl_set_delay(enum pm8058_smpl_delay delay) +{ + if (delay < PM8058_SLEEP_SMPL_SEL_MIN + || delay > PM8058_SLEEP_SMPL_SEL_MAX) { + pr_err("%s: invalid delay specified: %d\n", __func__, delay); + return -EINVAL; + } + + return pm8058_masked_write(SSBI_REG_ADDR_SLEEP_CNTL, delay, + PM8058_SLEEP_SMPL_SEL_MASK); +} +EXPORT_SYMBOL(pm8058_smpl_set_delay); + +/** + * pm8058_watchdog_reset_control - enables/disables watchdog reset detection + * @enable: 0 = shutdown when PS_HOLD goes low, 1 = reset when PS_HOLD goes low + * + * This function enables or disables the PMIC watchdog reset detection feature. + * If watchdog reset detection is enabled, then the PMIC will reset itself + * when PS_HOLD goes low. If it is not enabled, then the PMIC will shutdown + * when PS_HOLD goes low. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_watchdog_reset_control(int enable) +{ + return pm8058_masked_write(SSBI_REG_ADDR_PON_CNTL_1, + (enable ? PM8058_PON_WD_EN_RESET + : PM8058_PON_WD_EN_PWR_OFF), + PM8058_PON_WD_EN_MASK); +} +EXPORT_SYMBOL(pm8058_watchdog_reset_control); + +int pm8058_reset_pwr_off(int reset) +{ + int rc; + u8 pon; + u8 ctrl; + u8 smpl; + + if (pmic_chip == NULL) + return -ENODEV; + + /* Set regulator L22 to 1.225V in high power mode. */ + rc = ssbi_read(pmic_chip->dev, SSBI_REG_ADDR_L22_CTRL, &ctrl, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(0x%x): rc=%d\n", __func__, + SSBI_REG_ADDR_L22_CTRL, rc); + goto get_out3; + } + /* Leave pull-down state intact. */ + ctrl &= 0x40; + ctrl |= 0x93; + rc = ssbi_write(pmic_chip->dev, SSBI_REG_ADDR_L22_CTRL, &ctrl, 1); + if (rc) + pr_err("%s: FAIL ssbi_write(0x%x)=0x%x: rc=%d\n", __func__, + SSBI_REG_ADDR_L22_CTRL, ctrl, rc); + +get_out3: + if (!reset) { + /* Only modify the SLEEP_CNTL reg if shutdown is desired. */ + rc = ssbi_read(pmic_chip->dev, SSBI_REG_ADDR_SLEEP_CNTL, + &smpl, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(0x%x): rc=%d\n", + __func__, SSBI_REG_ADDR_SLEEP_CNTL, rc); + goto get_out2; + } + + smpl &= ~PM8058_SLEEP_SMPL_EN_MASK; + smpl |= PM8058_SLEEP_SMPL_EN_PWR_OFF; + + rc = ssbi_write(pmic_chip->dev, SSBI_REG_ADDR_SLEEP_CNTL, + &smpl, 1); + if (rc) + pr_err("%s: FAIL ssbi_write(0x%x)=0x%x: rc=%d\n", + __func__, SSBI_REG_ADDR_SLEEP_CNTL, smpl, rc); + } + +get_out2: + rc = ssbi_read(pmic_chip->dev, SSBI_REG_ADDR_PON_CNTL_1, &pon, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(0x%x): rc=%d\n", + __func__, SSBI_REG_ADDR_PON_CNTL_1, rc); + goto get_out; + } + + pon &= ~PM8058_PON_WD_EN_MASK; + pon |= reset ? PM8058_PON_WD_EN_RESET : PM8058_PON_WD_EN_PWR_OFF; + + /* Enable all pullups */ + pon |= PM8058_PON_PUP_MASK; + + rc = ssbi_write(pmic_chip->dev, SSBI_REG_ADDR_PON_CNTL_1, &pon, 1); + if (rc) { + pr_err("%s: FAIL ssbi_write(0x%x)=0x%x: rc=%d\n", + __func__, SSBI_REG_ADDR_PON_CNTL_1, pon, rc); + goto get_out; + } + +get_out: + return rc; +} +EXPORT_SYMBOL(pm8058_reset_pwr_off); + +/* + power on hard reset configuration + config = DISABLE_HARD_RESET to disable hard reset + = SHUTDOWN_ON_HARD_RESET to turn off the system on hard reset + = RESTART_ON_HARD_RESET to restart the system on hard reset + */ +int pm8058_hard_reset_config(enum pon_config config) +{ + int rc, ret; + u8 pon, pon_5; + + if (config >= MAX_PON_CONFIG) + return -EINVAL; + + if (pmic_chip == NULL) + return -ENODEV; + + mutex_lock(&pmic_chip->pm_lock); + + rc = ssbi_read(pmic_chip->dev, SSBI_REG_ADDR_PON_CNTL_5, &pon, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(0x%x): rc=%d\n", + __func__, SSBI_REG_ADDR_PON_CNTL_5, rc); + mutex_unlock(&pmic_chip->pm_lock); + return rc; + } + + pon_5 = pon; + (config != DISABLE_HARD_RESET) ? (pon |= PM8058_HARD_RESET_EN_MASK) : + (pon &= ~PM8058_HARD_RESET_EN_MASK); + + rc = ssbi_write(pmic_chip->dev, SSBI_REG_ADDR_PON_CNTL_5, &pon, 1); + if (rc) { + pr_err("%s: FAIL ssbi_write(0x%x)=0x%x: rc=%d\n", + __func__, SSBI_REG_ADDR_PON_CNTL_5, pon, rc); + mutex_unlock(&pmic_chip->pm_lock); + return rc; + } + + if (config == DISABLE_HARD_RESET) { + mutex_unlock(&pmic_chip->pm_lock); + return 0; + } + + rc = ssbi_read(pmic_chip->dev, SSBI_REG_ADDR_PON_CNTL_4, &pon, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(0x%x): rc=%d\n", + __func__, SSBI_REG_ADDR_PON_CNTL_4, rc); + goto err_restore_pon_5; + } + + (config == RESTART_ON_HARD_RESET) ? (pon |= PM8058_PON_RESET_EN_MASK) : + (pon &= ~PM8058_PON_RESET_EN_MASK); + + rc = ssbi_write(pmic_chip->dev, SSBI_REG_ADDR_PON_CNTL_4, &pon, 1); + if (rc) { + pr_err("%s: FAIL ssbi_write(0x%x)=0x%x: rc=%d\n", + __func__, SSBI_REG_ADDR_PON_CNTL_4, pon, rc); + goto err_restore_pon_5; + } + mutex_unlock(&pmic_chip->pm_lock); + return 0; + +err_restore_pon_5: + ret = ssbi_write(pmic_chip->dev, SSBI_REG_ADDR_PON_CNTL_5, &pon_5, 1); + if (ret) + pr_err("%s: FAIL ssbi_write(0x%x)=0x%x: rc=%d\n", + __func__, SSBI_REG_ADDR_PON_CNTL_5, pon, ret); + mutex_unlock(&pmic_chip->pm_lock); + return rc; +} +EXPORT_SYMBOL(pm8058_hard_reset_config); + +/* Internal functions */ +static inline int +pm8058_config_irq(struct pm8058_chip *chip, u8 *bp, u8 *cp) +{ + int rc; + + rc = ssbi_write(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, bp, 1); + if (rc) { + pr_err("%s: ssbi_write: rc=%d (Select block)\n", + __func__, rc); + goto bail_out; + } + + rc = ssbi_write(chip->dev, SSBI_REG_ADDR_IRQ_CONFIG, cp, 1); + if (rc) + pr_err("%s: ssbi_write: rc=%d (Configure IRQ)\n", + __func__, rc); + +bail_out: + return rc; +} + +static void pm8058_irq_mask(struct irq_data *data) +{ + int master, irq_bit; + struct pm8058_chip *chip = irq_data_get_irq_chip_data(data); + u8 block, config; + unsigned int irq = data->irq; + + irq -= chip->pdata.irq_base; + block = irq / 8; + master = block / 8; + irq_bit = irq % 8; + + chip->irqs_allowed[block] &= ~(1 << irq_bit); + if (!chip->irqs_allowed[block]) { + chip->blocks_allowed[master] &= ~(1 << (block % 8)); + + if (!chip->blocks_allowed[master]) + chip->masters_allowed &= ~(1 << master); + } + + config = PM8058_IRQF_WRITE | chip->config[irq] | + PM8058_IRQF_MASK_FE | PM8058_IRQF_MASK_RE; + chip->bus_unlock_config[irq] = config; +} + +static void pm8058_irq_unmask(struct irq_data *data) +{ + int master, irq_bit; + struct pm8058_chip *chip = irq_data_get_irq_chip_data(data); + u8 block, config, old_irqs_allowed, old_blocks_allowed; + unsigned int irq = data->irq; + + irq -= chip->pdata.irq_base; + block = irq / 8; + master = block / 8; + irq_bit = irq % 8; + + old_irqs_allowed = chip->irqs_allowed[block]; + chip->irqs_allowed[block] |= 1 << irq_bit; + if (!old_irqs_allowed) { + master = block / 8; + + old_blocks_allowed = chip->blocks_allowed[master]; + chip->blocks_allowed[master] |= 1 << (block % 8); + + if (!old_blocks_allowed) + chip->masters_allowed |= 1 << master; + } + + config = PM8058_IRQF_WRITE | chip->config[irq]; + chip->bus_unlock_config[irq] = config; +} + +static void pm8058_irq_ack(struct irq_data *data) +{ + struct pm8058_chip *chip = irq_data_get_irq_chip_data(data); + u8 block, config; + unsigned int irq = data->irq; + + irq -= chip->pdata.irq_base; + block = irq / 8; + + config = PM8058_IRQF_WRITE | chip->config[irq] | PM8058_IRQF_CLR; + /* Keep the mask */ + if (!(chip->irqs_allowed[block] & (1 << (irq % 8)))) + config |= PM8058_IRQF_MASK_FE | PM8058_IRQF_MASK_RE; + chip->bus_unlock_config[irq] = config; +} + +static int pm8058_irq_set_type(struct irq_data *data, unsigned int flow_type) +{ + int master, irq_bit; + struct pm8058_chip *chip = irq_data_get_irq_chip_data(data); + u8 block, config; + unsigned int irq = data->irq; + + irq -= chip->pdata.irq_base; + if (irq > chip->pm_max_irq) { + chip->pm_max_irq = irq; + chip->pm_max_blocks = + chip->pm_max_irq / 8 + 1; + chip->pm_max_masters = + chip->pm_max_blocks / 8 + 1; + } + block = irq / 8; + master = block / 8; + irq_bit = irq % 8; + + chip->config[irq] = (irq_bit << PM8058_IRQF_BITS_SHIFT) | + PM8058_IRQF_MASK_RE | PM8058_IRQF_MASK_FE; + if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { + if (flow_type & IRQF_TRIGGER_RISING) + chip->config[irq] &= ~PM8058_IRQF_MASK_RE; + if (flow_type & IRQF_TRIGGER_FALLING) + chip->config[irq] &= ~PM8058_IRQF_MASK_FE; + } else { + chip->config[irq] |= PM8058_IRQF_LVL_SEL; + + if (flow_type & IRQF_TRIGGER_HIGH) + chip->config[irq] &= ~PM8058_IRQF_MASK_RE; + else + chip->config[irq] &= ~PM8058_IRQF_MASK_FE; + } + + config = PM8058_IRQF_WRITE | chip->config[irq] | PM8058_IRQF_CLR; + chip->bus_unlock_config[irq] = config; + return 0; +} + +static int pm8058_irq_set_wake(struct irq_data *data, unsigned int on) +{ + struct pm8058_chip *chip = irq_data_get_irq_chip_data(data); + unsigned int irq = data->irq; + + irq -= chip->pdata.irq_base; + if (on) { + if (!chip->wake_enable[irq]) { + chip->wake_enable[irq] = 1; + chip->count_wakeable++; + } + } else { + if (chip->wake_enable[irq]) { + chip->wake_enable[irq] = 0; + chip->count_wakeable--; + } + } + + return 0; +} + +static void pm8058_irq_bus_lock(struct irq_data *data) +{ + u8 block; + struct pm8058_chip *chip = irq_data_get_irq_chip_data(data); + unsigned int irq = data->irq; + + irq -= chip->pdata.irq_base; + block = irq / 8; + chip->bus_unlock_config[irq] = 0; + + mutex_lock(&chip->pm_lock); +} + +static void pm8058_irq_bus_sync_unlock(struct irq_data *data) +{ + u8 block, config; + struct pm8058_chip *chip = irq_data_get_irq_chip_data(data); + unsigned int irq = data->irq; + + irq -= chip->pdata.irq_base; + block = irq / 8; + config = chip->bus_unlock_config[irq]; + /* dont waste cpu cycles if we dont have data to write */ + if (config) + pm8058_config_irq(chip, &block, &config); + mutex_unlock(&chip->pm_lock); +} + +static inline int +pm8058_read_root(struct pm8058_chip *chip, u8 *rp) +{ + int rc; + + rc = ssbi_read(chip->dev, SSBI_REG_ADDR_IRQ_ROOT, rp, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(): rc=%d (Read Root)\n", + __func__, rc); + *rp = 0; + } + + return rc; +} + +static inline int +pm8058_read_master(struct pm8058_chip *chip, u8 m, u8 *bp) +{ + int rc; + + rc = ssbi_read(chip->dev, SSBI_REG_ADDR_IRQ_M_STATUS1 + m, bp, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(): rc=%d (Read Master)\n", + __func__, rc); + *bp = 0; + } + + return rc; +} + +static inline int +pm8058_read_block(struct pm8058_chip *chip, u8 *bp, u8 *ip) +{ + int rc; + + rc = ssbi_write(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, bp, 1); + if (rc) { + pr_err("%s: FAIL ssbi_write(): rc=%d (Select Block)\n", + __func__, rc); + *bp = 0; + goto bail_out; + } + + rc = ssbi_read(chip->dev, SSBI_REG_ADDR_IRQ_IT_STATUS, ip, 1); + if (rc) + pr_err("%s: FAIL ssbi_read(): rc=%d (Read Status)\n", + __func__, rc); + +bail_out: + return rc; +} + +static irqreturn_t pm8058_isr_thread(int irq_requested, void *data) +{ + struct pm8058_chip *chip = data; + int i, j, k; + u8 root, block, config, bits; + u8 blocks[MAX_PM_MASTERS]; + int masters = 0, irq, handled = 0, spurious = 0; + u16 irqs_to_handle[MAX_PM_IRQ]; + + mutex_lock(&chip->pm_lock); + + /* Read root for masters */ + if (pm8058_read_root(chip, &root)) + goto bail_out; + + masters = root >> 1; + + if (!(masters & chip->masters_allowed) || + (masters & ~chip->masters_allowed)) { + spurious = 1000000; + } + + /* Read allowed masters for blocks. */ + for (i = 0; i < chip->pm_max_masters; i++) { + if (masters & (1 << i)) { + if (pm8058_read_master(chip, i, &blocks[i])) + goto bail_out; + + if (!blocks[i]) { + if (pm8058_can_print()) + pr_err("%s: Spurious master: %d " + "(blocks=0)", __func__, i); + spurious += 10000; + } + } else + blocks[i] = 0; + } + + /* Select block, read status and call isr */ + for (i = 0; i < chip->pm_max_masters; i++) { + if (!blocks[i]) + continue; + + for (j = 0; j < 8; j++) { + if (!(blocks[i] & (1 << j))) + continue; + + block = i * 8 + j; /* block # */ + if (pm8058_read_block(chip, &block, &bits)) + goto bail_out; + + if (!bits) { + if (pm8058_can_print()) + pr_err("%s: Spurious block: " + "[master, block]=[%d, %d] " + "(bits=0)\n", __func__, i, j); + spurious += 100; + continue; + } + + /* Check IRQ bits */ + for (k = 0; k < 8; k++) { + if (!(bits & (1 << k))) + continue; + + /* Check spurious interrupts */ + if (((1 << i) & chip->masters_allowed) && + (blocks[i] & chip->blocks_allowed[i]) && + (bits & chip->irqs_allowed[block])) { + + /* Found one */ + irq = block * 8 + k; + irqs_to_handle[handled] = irq + + chip->pdata.irq_base; + handled++; + } else { + /* Clear and mask wrong one */ + config = PM8058_IRQF_W_C_M | + (k << PM8058_IRQF_BITS_SHIFT); + + pm8058_config_irq(chip, + &block, &config); + + if (pm8058_can_print()) + pr_err("%s: Spurious IRQ: " + "[master, block, bit]=" + "[%d, %d (%d), %d]\n", + __func__, + i, j, block, k); + spurious++; + } + } + } + + } + +bail_out: + + mutex_unlock(&chip->pm_lock); + + for (i = 0; i < handled; i++) + handle_nested_irq(irqs_to_handle[i]); + + for (i = 0; i < handled; i++) { + irqs_to_handle[i] -= chip->pdata.irq_base; + block = irqs_to_handle[i] / 8 ; + config = PM8058_IRQF_WRITE | chip->config[irqs_to_handle[i]] + | PM8058_IRQF_CLR; + pm8058_config_irq(chip, &block, &config); + } + + if (spurious) { + if (!pm8058_can_print()) + return IRQ_HANDLED; + + pr_err("%s: spurious = %d (handled = %d)\n", + __func__, spurious, handled); + pr_err(" root = 0x%x (masters_allowed<<1 = 0x%x)\n", + root, chip->masters_allowed << 1); + for (i = 0; i < chip->pm_max_masters; i++) { + if (masters & (1 << i)) + pr_err(" blocks[%d]=0x%x, " + "allowed[%d]=0x%x\n", + i, blocks[i], + i, chip->blocks_allowed[i]); + } + } + + return IRQ_HANDLED; +} + +#if defined(CONFIG_DEBUG_FS) + +static int check_addr(int addr, const char *func_name) +{ + if (addr < 0 || addr > 0x3FF) { + pr_err("%s: PMIC 8058 register address is invalid: %d\n", + func_name, addr); + return -EINVAL; + } + return 0; +} + +static int data_set(void *data, u64 val) +{ + struct pm8058_dbg_device *dbgdev = data; + u8 reg = val; + int rc; + + mutex_lock(&dbgdev->dbg_mutex); + + rc = check_addr(dbgdev->addr, __func__); + if (rc) + goto done; + + rc = pm8058_write(dbgdev->pm_chip, dbgdev->addr, ®, 1); + + if (rc) + pr_err("%s: FAIL pm8058_write(0x%03X)=0x%02X: rc=%d\n", + __func__, dbgdev->addr, reg, rc); +done: + mutex_unlock(&dbgdev->dbg_mutex); + return rc; +} + +static int data_get(void *data, u64 *val) +{ + struct pm8058_dbg_device *dbgdev = data; + int rc; + u8 reg; + + mutex_lock(&dbgdev->dbg_mutex); + + rc = check_addr(dbgdev->addr, __func__); + if (rc) + goto done; + + rc = pm8058_read(dbgdev->pm_chip, dbgdev->addr, ®, 1); + + if (rc) { + pr_err("%s: FAIL pm8058_read(0x%03X)=0x%02X: rc=%d\n", + __func__, dbgdev->addr, reg, rc); + goto done; + } + + *val = reg; +done: + mutex_unlock(&dbgdev->dbg_mutex); + return rc; +} + +DEFINE_SIMPLE_ATTRIBUTE(dbg_data_fops, data_get, data_set, "0x%02llX\n"); + +static int addr_set(void *data, u64 val) +{ + struct pm8058_dbg_device *dbgdev = data; + int rc; + + rc = check_addr(val, __func__); + if (rc) + return rc; + + mutex_lock(&dbgdev->dbg_mutex); + dbgdev->addr = val; + mutex_unlock(&dbgdev->dbg_mutex); + + return 0; +} + +static int addr_get(void *data, u64 *val) +{ + struct pm8058_dbg_device *dbgdev = data; + int rc; + + mutex_lock(&dbgdev->dbg_mutex); + + rc = check_addr(dbgdev->addr, __func__); + if (rc) { + mutex_unlock(&dbgdev->dbg_mutex); + return rc; + } + *val = dbgdev->addr; + + mutex_unlock(&dbgdev->dbg_mutex); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(dbg_addr_fops, addr_get, addr_set, "0x%03llX\n"); + +static int __devinit pmic8058_dbg_probe(struct pm8058_chip *chip) +{ + struct pm8058_dbg_device *dbgdev; + struct dentry *dent; + struct dentry *temp; + + if (chip == NULL) { + pr_err("%s: no parent data passed in.\n", __func__); + return -EINVAL; + } + + dbgdev = kzalloc(sizeof *dbgdev, GFP_KERNEL); + if (dbgdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + mutex_init(&dbgdev->dbg_mutex); + + dbgdev->pm_chip = chip; + dbgdev->addr = -1; + + dent = debugfs_create_dir("pm8058-dbg", NULL); + if (dent == NULL || IS_ERR(dent)) { + pr_err("%s: ERR debugfs_create_dir: dent=0x%X\n", + __func__, (unsigned)dent); + return -ENOMEM; + } + + temp = debugfs_create_file("addr", S_IRUSR | S_IWUSR, dent, + dbgdev, &dbg_addr_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("%s: ERR debugfs_create_file: dent=0x%X\n", + __func__, (unsigned)temp); + goto debug_error; + } + + temp = debugfs_create_file("data", S_IRUSR | S_IWUSR, dent, + dbgdev, &dbg_data_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("%s: ERR debugfs_create_file: dent=0x%X\n", + __func__, (unsigned)temp); + goto debug_error; + } + + dbgdev->dent = dent; + + pmic_dbg_device = dbgdev; + + return 0; + +debug_error: + debugfs_remove_recursive(dent); + return -ENOMEM; +} + +static int __devexit pmic8058_dbg_remove(void) +{ + if (pmic_dbg_device) { + debugfs_remove_recursive(pmic_dbg_device->dent); + kfree(pmic_dbg_device); + } + return 0; +} + +#else + +static int __devinit pmic8058_dbg_probe(struct pm8058_chip *chip) +{ + return 0; +} + +static int __devexit pmic8058_dbg_remove(void) +{ + return 0; +} + +#endif + +static struct irq_chip pm8058_irq_chip = { + .name = "pm8058", + .irq_ack = pm8058_irq_ack, + .irq_mask = pm8058_irq_mask, + .irq_unmask = pm8058_irq_unmask, + .irq_set_type = pm8058_irq_set_type, + .irq_set_wake = pm8058_irq_set_wake, + .irq_bus_lock = pm8058_irq_bus_lock, + .irq_bus_sync_unlock = pm8058_irq_bus_sync_unlock, +}; + +static int pm8058_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i, rc; + struct pm8058_platform_data *pdata = client->dev.platform_data; + struct pm8058_chip *chip; + + if (pdata == NULL || !client->irq) { + pr_err("%s: No platform_data or IRQ.\n", __func__); + return -ENODEV; + } + + if (pdata->num_subdevs == 0) { + pr_err("%s: No sub devices to support.\n", __func__); + return -ENODEV; + } + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C) == 0) { + pr_err("%s: i2c_check_functionality failed.\n", __func__); + return -ENODEV; + } + + chip = kzalloc(sizeof *chip, GFP_KERNEL); + if (chip == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + chip->dev = client; + + /* Read PMIC chip revision */ + rc = ssbi_read(chip->dev, SSBI_REG_REV, &chip->revision, 1); + if (rc) + pr_err("%s: Failed on ssbi_read for revision: rc=%d.\n", + __func__, rc); + pr_info("%s: PMIC revision: %X\n", __func__, chip->revision); + + (void) memcpy((void *)&chip->pdata, (const void *)pdata, + sizeof(chip->pdata)); + + mutex_init(&chip->pm_lock); + irq_set_handler_data(chip->dev->irq, (void *)chip); + irq_set_irq_wake(chip->dev->irq, 1); + + chip->pm_max_irq = 0; + chip->pm_max_blocks = 0; + chip->pm_max_masters = 0; + + i2c_set_clientdata(client, chip); + + pmic_chip = chip; + + /* Register for all reserved IRQs */ + for (i = pdata->irq_base; i < (pdata->irq_base + MAX_PM_IRQ); i++) { + irq_set_chip(i, &pm8058_irq_chip); + irq_set_chip_data(i, (void *)chip); + irq_set_handler(i, handle_edge_irq); + set_irq_flags(i, IRQF_VALID); + irq_set_nested_thread(i, 1); + } + + rc = mfd_add_devices(&chip->dev->dev, 0, pdata->sub_devices, + pdata->num_subdevs, NULL, 0); + + /* Add charger sub device with the chip parameter as driver data */ + if (pdata->charger_sub_device) { + rc = mfd_add_devices(&chip->dev->dev, 0, + pdata->charger_sub_device, + 1, NULL, 0); + } + + if (pdata->init) { + rc = pdata->init(chip); + if (rc != 0) { + pr_err("%s: board init failed\n", __func__); + chip->dev = NULL; + kfree(chip); + return -ENODEV; + } + } + + rc = request_threaded_irq(chip->dev->irq, NULL, pm8058_isr_thread, + IRQF_ONESHOT | IRQF_DISABLED | pdata->irq_trigger_flags, + "pm8058-irq", chip); + if (rc < 0) + pr_err("%s: could not request irq %d: %d\n", __func__, + chip->dev->irq, rc); + + rc = pmic8058_dbg_probe(chip); + if (rc < 0) + pr_err("%s: could not set up debugfs: %d\n", __func__, rc); + + return 0; +} + +static int __devexit pm8058_remove(struct i2c_client *client) +{ + struct pm8058_chip *chip; + + chip = i2c_get_clientdata(client); + if (chip) { + if (chip->pm_max_irq) { + irq_set_irq_wake(chip->dev->irq, 0); + free_irq(chip->dev->irq, chip); + } + mutex_destroy(&chip->pm_lock); + chip->dev = NULL; + + kfree(chip); + } + + pmic8058_dbg_remove(); + + return 0; +} + +#ifdef CONFIG_PM +static int pm8058_suspend(struct device *dev) +{ + struct i2c_client *client; + struct pm8058_chip *chip; + struct irq_data *data; + int i; + + client = to_i2c_client(dev); + chip = i2c_get_clientdata(client); + + for (i = 0; i < MAX_PM_IRQ; i++) { + if (chip->config[i] && !chip->wake_enable[i]) { + if (!((chip->config[i] & PM8058_IRQF_MASK_ALL) + == PM8058_IRQF_MASK_ALL)) { + data = irq_get_irq_data(i + + chip->pdata.irq_base); + pm8058_irq_bus_lock(data); + pm8058_irq_mask(data); + pm8058_irq_bus_sync_unlock(data); + } + } + } + + if (!chip->count_wakeable) + disable_irq(chip->dev->irq); + + return 0; +} + +extern int msm_show_resume_irq_mask; + +static void pm8058_show_resume_irq(void) +{ + u8 block, bits; + int i; + struct pm8058_chip *chip = pmic_chip; + + if (!msm_show_resume_irq_mask) + return; + + for (i = 0; i < MAX_PM_IRQ; i++) { + if (chip->wake_enable[i]) { + block = i / 8; + if (!pm8058_read_block(chip, &block, &bits)) { + if (bits & (1 << (i & 0x7))) + pr_warning("%s:%d triggered\n", + __func__, i + chip->pdata.irq_base); + } + } + } +} + +static int pm8058_resume(struct device *dev) +{ + struct i2c_client *client; + struct pm8058_chip *chip; + struct irq_data *data; + int i; + + pm8058_show_resume_irq(); + + client = to_i2c_client(dev); + chip = i2c_get_clientdata(client); + + for (i = 0; i < MAX_PM_IRQ; i++) { + if (chip->config[i] && !chip->wake_enable[i]) { + if (!((chip->config[i] & PM8058_IRQF_MASK_ALL) + == PM8058_IRQF_MASK_ALL)) { + data = irq_get_irq_data(i + + chip->pdata.irq_base); + pm8058_irq_bus_lock(data); + pm8058_irq_unmask(data); + pm8058_irq_bus_sync_unlock(data); + } + } + } + + if (!chip->count_wakeable) + enable_irq(chip->dev->irq); + + return 0; +} +#else +#define pm8058_suspend NULL +#define pm8058_resume NULL +#endif + +static const struct i2c_device_id pm8058_ids[] = { + { "pm8058-core", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, pm8058_ids); + +static struct dev_pm_ops pm8058_pm = { + .suspend = pm8058_suspend, + .resume = pm8058_resume, +}; + +static struct i2c_driver pm8058_driver = { + .driver.name = "pm8058-core", + .driver.pm = &pm8058_pm, + .id_table = pm8058_ids, + .probe = pm8058_probe, + .remove = __devexit_p(pm8058_remove), +}; + +static int __init pm8058_init(void) +{ + int rc = i2c_add_driver(&pm8058_driver); + pr_notice("%s: i2c_add_driver: rc = %d\n", __func__, rc); + + return rc; +} + +static void __exit pm8058_exit(void) +{ + i2c_del_driver(&pm8058_driver); +} + +arch_initcall(pm8058_init); +module_exit(pm8058_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 core driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8058-core"); diff --git a/drivers/mfd/pmic8901.c b/drivers/mfd/pmic8901.c new file mode 100644 index 00000000000..da466563fb1 --- /dev/null +++ b/drivers/mfd/pmic8901.c @@ -0,0 +1,953 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* PMIC8901 Revision */ +#define SSBI_REG_REV 0x002 /* PMIC4 revision */ + +/* PMIC8901 IRQ */ +#define SSBI_REG_ADDR_IRQ_BASE 0xD5 + +#define SSBI_REG_ADDR_IRQ_ROOT (SSBI_REG_ADDR_IRQ_BASE + 0) +#define SSBI_REG_ADDR_IRQ_M_STATUS1 (SSBI_REG_ADDR_IRQ_BASE + 1) +#define SSBI_REG_ADDR_IRQ_M_STATUS2 (SSBI_REG_ADDR_IRQ_BASE + 2) +#define SSBI_REG_ADDR_IRQ_M_STATUS3 (SSBI_REG_ADDR_IRQ_BASE + 3) +#define SSBI_REG_ADDR_IRQ_M_STATUS4 (SSBI_REG_ADDR_IRQ_BASE + 4) +#define SSBI_REG_ADDR_IRQ_BLK_SEL (SSBI_REG_ADDR_IRQ_BASE + 5) +#define SSBI_REG_ADDR_IRQ_IT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 6) +#define SSBI_REG_ADDR_IRQ_CONFIG (SSBI_REG_ADDR_IRQ_BASE + 7) +#define SSBI_REG_ADDR_IRQ_RT_STATUS (SSBI_REG_ADDR_IRQ_BASE + 8) + +#define PM8901_IRQF_LVL_SEL 0x01 /* level select */ +#define PM8901_IRQF_MASK_FE 0x02 /* mask falling edge */ +#define PM8901_IRQF_MASK_RE 0x04 /* mask rising edge */ +#define PM8901_IRQF_CLR 0x08 /* clear interrupt */ +#define PM8901_IRQF_BITS_MASK 0x70 +#define PM8901_IRQF_BITS_SHIFT 4 +#define PM8901_IRQF_WRITE 0x80 + +#define PM8901_IRQF_MASK_ALL (PM8901_IRQF_MASK_FE | \ + PM8901_IRQF_MASK_RE) +#define PM8901_IRQF_W_C_M (PM8901_IRQF_WRITE | \ + PM8901_IRQF_CLR | \ + PM8901_IRQF_MASK_ALL) + +#define MAX_PM_IRQ 72 +#define MAX_PM_BLOCKS (MAX_PM_IRQ / 8 + 1) +#define MAX_PM_MASTERS (MAX_PM_BLOCKS / 8 + 1) + +#define MPP_IRQ_BLOCK 1 + +/* FTS regulator PMR registers */ +#define SSBI_REG_ADDR_S1_PMR (0xA7) +#define SSBI_REG_ADDR_S2_PMR (0xA8) +#define SSBI_REG_ADDR_S3_PMR (0xA9) +#define SSBI_REG_ADDR_S4_PMR (0xAA) + +#define REGULATOR_PMR_STATE_MASK 0x60 +#define REGULATOR_PMR_STATE_OFF 0x20 + +struct pm8901_chip { + struct pm8901_platform_data pdata; + + struct i2c_client *dev; + + u8 irqs_allowed[MAX_PM_BLOCKS]; + u8 blocks_allowed[MAX_PM_MASTERS]; + u8 masters_allowed; + int pm_max_irq; + int pm_max_blocks; + int pm_max_masters; + + u8 config[MAX_PM_IRQ]; + u8 wake_enable[MAX_PM_IRQ]; + u16 count_wakeable; + + u8 revision; + + spinlock_t pm_lock; +}; + +#if defined(CONFIG_DEBUG_FS) +struct pm8901_dbg_device { + struct mutex dbg_mutex; + struct pm8901_chip *pm_chip; + struct dentry *dent; + int addr; +}; + +static struct pm8901_dbg_device *pmic_dbg_device; +#endif + +static struct pm8901_chip *pmic_chip; + +/* Helper Functions */ +DEFINE_RATELIMIT_STATE(pm8901_msg_ratelimit, 60 * HZ, 10); + +static inline int pm8901_can_print(void) +{ + return __ratelimit(&pm8901_msg_ratelimit); +} + +static inline int +ssbi_write(struct i2c_client *client, u16 addr, const u8 *buf, size_t len) +{ + int rc; + struct i2c_msg msg = { + .addr = addr, + .flags = 0x0, + .buf = (u8 *)buf, + .len = len, + }; + + rc = i2c_transfer(client->adapter, &msg, 1); + return (rc == 1) ? 0 : rc; +} + +static inline int +ssbi_read(struct i2c_client *client, u16 addr, u8 *buf, size_t len) +{ + int rc; + struct i2c_msg msg = { + .addr = addr, + .flags = I2C_M_RD, + .buf = buf, + .len = len, + }; + + rc = i2c_transfer(client->adapter, &msg, 1); + return (rc == 1) ? 0 : rc; +} + +/* External APIs */ +int pm8901_rev(struct pm8901_chip *chip) +{ + if (chip == NULL) { + if (pmic_chip != NULL) + return pmic_chip->revision; + else + return -EINVAL; + } + + return chip->revision; +} +EXPORT_SYMBOL(pm8901_rev); + +int pm8901_read(struct pm8901_chip *chip, u16 addr, u8 *values, + unsigned int len) +{ + if (chip == NULL) + return -EINVAL; + + return ssbi_read(chip->dev, addr, values, len); +} +EXPORT_SYMBOL(pm8901_read); + +int pm8901_write(struct pm8901_chip *chip, u16 addr, u8 *values, + unsigned int len) +{ + if (chip == NULL) + return -EINVAL; + + return ssbi_write(chip->dev, addr, values, len); +} +EXPORT_SYMBOL(pm8901_write); + +int pm8901_irq_get_rt_status(struct pm8901_chip *chip, int irq) +{ + int rc; + u8 block, bits, bit; + unsigned long irqsave; + + if (chip == NULL || irq < chip->pdata.irq_base || + irq >= chip->pdata.irq_base + MAX_PM_IRQ) + return -EINVAL; + + irq -= chip->pdata.irq_base; + + block = irq / 8; + bit = irq % 8; + + spin_lock_irqsave(&chip->pm_lock, irqsave); + + rc = ssbi_write(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, &block, 1); + if (rc) { + pr_err("%s: FAIL ssbi_write(): rc=%d (Select Block)\n", + __func__, rc); + goto bail_out; + } + + rc = ssbi_read(chip->dev, SSBI_REG_ADDR_IRQ_RT_STATUS, &bits, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(): rc=%d (Read RT Status)\n", + __func__, rc); + goto bail_out; + } + + rc = (bits & (1 << bit)) ? 1 : 0; + +bail_out: + spin_unlock_irqrestore(&chip->pm_lock, irqsave); + + return rc; +} +EXPORT_SYMBOL(pm8901_irq_get_rt_status); + +int pm8901_reset_pwr_off(int reset) +{ + int rc = 0, i; + u8 pmr; + u8 pmr_addr[4] = { + SSBI_REG_ADDR_S2_PMR, + SSBI_REG_ADDR_S3_PMR, + SSBI_REG_ADDR_S4_PMR, + SSBI_REG_ADDR_S1_PMR, + }; + + if (pmic_chip == NULL) + return -ENODEV; + + /* Turn off regulators S1, S2, S3, S4 when shutting down. */ + if (!reset) { + for (i = 0; i < 4; i++) { + rc = ssbi_read(pmic_chip->dev, pmr_addr[i], &pmr, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(0x%x): rc=%d\n", + __func__, pmr_addr[i], rc); + goto get_out; + } + + pmr &= ~REGULATOR_PMR_STATE_MASK; + pmr |= REGULATOR_PMR_STATE_OFF; + + rc = ssbi_write(pmic_chip->dev, pmr_addr[i], &pmr, 1); + if (rc) { + pr_err("%s: FAIL ssbi_write(0x%x)=0x%x: rc=%d" + "\n", __func__, pmr_addr[i], pmr, rc); + goto get_out; + } + } + } + +get_out: + return rc; +} +EXPORT_SYMBOL(pm8901_reset_pwr_off); + +/* Internal functions */ +static inline int +pm8901_config_irq(struct pm8901_chip *chip, u8 *bp, u8 *cp) +{ + int rc; + + rc = ssbi_write(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, bp, 1); + if (rc) { + pr_err("%s: ssbi_write: rc=%d (Select block)\n", + __func__, rc); + goto bail_out; + } + + rc = ssbi_write(chip->dev, SSBI_REG_ADDR_IRQ_CONFIG, cp, 1); + if (rc) + pr_err("%s: ssbi_write: rc=%d (Configure IRQ)\n", + __func__, rc); + +bail_out: + return rc; +} + +static void pm8901_irq_mask(struct irq_data *d) +{ + int master, irq_bit; + struct pm8901_chip *chip = irq_data_get_irq_handler_data(d); + u8 block, config; + unsigned int irq = d->irq; + + irq -= chip->pdata.irq_base; + block = irq / 8; + master = block / 8; + irq_bit = irq % 8; + + chip->irqs_allowed[block] &= ~(1 << irq_bit); + if (!chip->irqs_allowed[block]) { + chip->blocks_allowed[master] &= ~(1 << (block % 8)); + + if (!chip->blocks_allowed[master]) + chip->masters_allowed &= ~(1 << master); + } + + config = PM8901_IRQF_WRITE | chip->config[irq] | + PM8901_IRQF_MASK_FE | PM8901_IRQF_MASK_RE; + pm8901_config_irq(chip, &block, &config); +} + +static void pm8901_irq_unmask(struct irq_data *d) +{ + int master, irq_bit; + struct pm8901_chip *chip = irq_data_get_irq_handler_data(d); + u8 block, config, old_irqs_allowed, old_blocks_allowed; + unsigned int irq = d->irq; + + irq -= chip->pdata.irq_base; + block = irq / 8; + master = block / 8; + irq_bit = irq % 8; + + old_irqs_allowed = chip->irqs_allowed[block]; + chip->irqs_allowed[block] |= 1 << irq_bit; + if (!old_irqs_allowed) { + master = block / 8; + + old_blocks_allowed = chip->blocks_allowed[master]; + chip->blocks_allowed[master] |= 1 << (block % 8); + + if (!old_blocks_allowed) + chip->masters_allowed |= 1 << master; + } + + config = PM8901_IRQF_WRITE | chip->config[irq]; + pm8901_config_irq(chip, &block, &config); +} + +static void pm8901_irq_ack(struct irq_data *d) +{ + struct pm8901_chip *chip = irq_data_get_irq_handler_data(d); + u8 block, config; + unsigned int irq = d->irq; + + irq -= chip->pdata.irq_base; + block = irq / 8; + + config = PM8901_IRQF_WRITE | chip->config[irq] | PM8901_IRQF_CLR; + pm8901_config_irq(chip, &block, &config); +} + +static int pm8901_irq_set_type(struct irq_data *d, unsigned int flow_type) +{ + int master, irq_bit; + struct pm8901_chip *chip = irq_data_get_irq_handler_data(d); + u8 block, config; + unsigned int irq = d->irq; + + irq -= chip->pdata.irq_base; + if (irq > chip->pm_max_irq) { + chip->pm_max_irq = irq; + chip->pm_max_blocks = + chip->pm_max_irq / 8 + 1; + chip->pm_max_masters = + chip->pm_max_blocks / 8 + 1; + } + block = irq / 8; + master = block / 8; + irq_bit = irq % 8; + + chip->config[irq] = (irq_bit << PM8901_IRQF_BITS_SHIFT) | + PM8901_IRQF_MASK_RE | PM8901_IRQF_MASK_FE; + if (flow_type & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) { + if (flow_type & IRQF_TRIGGER_RISING) + chip->config[irq] &= ~PM8901_IRQF_MASK_RE; + if (flow_type & IRQF_TRIGGER_FALLING) + chip->config[irq] &= ~PM8901_IRQF_MASK_FE; + } else { + chip->config[irq] |= PM8901_IRQF_LVL_SEL; + + if (flow_type & IRQF_TRIGGER_HIGH) + chip->config[irq] &= ~PM8901_IRQF_MASK_RE; + else + chip->config[irq] &= ~PM8901_IRQF_MASK_FE; + } + + config = PM8901_IRQF_WRITE | chip->config[irq] | PM8901_IRQF_CLR; + return pm8901_config_irq(chip, &block, &config); +} + +static int pm8901_irq_set_wake(struct irq_data *d, unsigned int on) +{ + struct pm8901_chip *chip = irq_data_get_irq_handler_data(d); + unsigned int irq = d->irq; + + irq -= chip->pdata.irq_base; + if (on) { + if (!chip->wake_enable[irq]) { + chip->wake_enable[irq] = 1; + chip->count_wakeable++; + } + } else { + if (chip->wake_enable[irq]) { + chip->wake_enable[irq] = 0; + chip->count_wakeable--; + } + } + + return 0; +} + +static inline int +pm8901_read_root(struct pm8901_chip *chip, u8 *rp) +{ + int rc; + + rc = ssbi_read(chip->dev, SSBI_REG_ADDR_IRQ_ROOT, rp, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(): rc=%d (Read Root)\n", + __func__, rc); + *rp = 0; + } + + return rc; +} + +static inline int +pm8901_read_master(struct pm8901_chip *chip, u8 m, u8 *bp) +{ + int rc; + + rc = ssbi_read(chip->dev, SSBI_REG_ADDR_IRQ_M_STATUS1 + m, bp, 1); + if (rc) { + pr_err("%s: FAIL ssbi_read(): rc=%d (Read Master)\n", + __func__, rc); + *bp = 0; + } + + return rc; +} + +static inline int +pm8901_read_block(struct pm8901_chip *chip, u8 *bp, u8 *ip) +{ + int rc; + + rc = ssbi_write(chip->dev, SSBI_REG_ADDR_IRQ_BLK_SEL, bp, 1); + if (rc) { + pr_err("%s: FAIL ssbi_write(): rc=%d (Select Block)\n", + __func__, rc); + *bp = 0; + goto bail_out; + } + + rc = ssbi_read(chip->dev, SSBI_REG_ADDR_IRQ_IT_STATUS, ip, 1); + if (rc) + pr_err("%s: FAIL ssbi_read(): rc=%d (Read Status)\n", + __func__, rc); + +bail_out: + return rc; +} + +static irqreturn_t pm8901_isr_thread(int irq_requested, void *data) +{ + struct pm8901_chip *chip = data; + int i, j, k; + u8 root, block, config, bits; + u8 blocks[MAX_PM_MASTERS]; + int masters = 0, irq, handled = 0, spurious = 0; + u16 irqs_to_handle[MAX_PM_IRQ]; + unsigned long irqsave; + + spin_lock_irqsave(&chip->pm_lock, irqsave); + + /* Read root for masters */ + if (pm8901_read_root(chip, &root)) + goto bail_out; + + masters = root >> 1; + + if (!(masters & chip->masters_allowed) || + (masters & ~chip->masters_allowed)) { + spurious = 1000000; + } + + /* Read allowed masters for blocks. */ + for (i = 0; i < chip->pm_max_masters; i++) { + if (masters & (1 << i)) { + if (pm8901_read_master(chip, i, &blocks[i])) + goto bail_out; + + if (!blocks[i]) { + if (pm8901_can_print()) + pr_err("%s: Spurious master: %d " + "(blocks=0)", __func__, i); + spurious += 10000; + } + } else + blocks[i] = 0; + } + + /* Select block, read status and call isr */ + for (i = 0; i < chip->pm_max_masters; i++) { + if (!blocks[i]) + continue; + + for (j = 0; j < 8; j++) { + if (!(blocks[i] & (1 << j))) + continue; + + block = i * 8 + j; /* block # */ + if (pm8901_read_block(chip, &block, &bits)) + goto bail_out; + + if (!bits) { + if (pm8901_can_print()) + pr_err("%s: Spurious block: " + "[master, block]=[%d, %d] " + "(bits=0)\n", __func__, i, j); + spurious += 100; + continue; + } + + /* Check IRQ bits */ + for (k = 0; k < 8; k++) { + if (!(bits & (1 << k))) + continue; + + /* Check spurious interrupts */ + if (((1 << i) & chip->masters_allowed) && + (blocks[i] & chip->blocks_allowed[i]) && + (bits & chip->irqs_allowed[block])) { + + /* Found one */ + irq = block * 8 + k; + irqs_to_handle[handled] = irq + + chip->pdata.irq_base; + handled++; + } else { + /* Clear and mask wrong one */ + config = PM8901_IRQF_W_C_M | + (k < PM8901_IRQF_BITS_SHIFT); + + pm8901_config_irq(chip, + &block, &config); + + if (pm8901_can_print()) + pr_err("%s: Spurious IRQ: " + "[master, block, bit]=" + "[%d, %d (%d), %d]\n", + __func__, + i, j, block, k); + spurious++; + } + } + } + + } + +bail_out: + + spin_unlock_irqrestore(&chip->pm_lock, irqsave); + + for (i = 0; i < handled; i++) + generic_handle_irq(irqs_to_handle[i]); + + if (spurious) { + if (!pm8901_can_print()) + return IRQ_HANDLED; + + pr_err("%s: spurious = %d (handled = %d)\n", + __func__, spurious, handled); + pr_err(" root = 0x%x (masters_allowed<<1 = 0x%x)\n", + root, chip->masters_allowed << 1); + for (i = 0; i < chip->pm_max_masters; i++) { + if (masters & (1 << i)) + pr_err(" blocks[%d]=0x%x, " + "allowed[%d]=0x%x\n", + i, blocks[i], + i, chip->blocks_allowed[i]); + } + } + + return IRQ_HANDLED; +} + +#if defined(CONFIG_DEBUG_FS) + +static int check_addr(int addr, const char *func_name) +{ + if (addr < 0 || addr > 0x3FF) { + pr_err("%s: PMIC 8901 register address is invalid: %d\n", + func_name, addr); + return -EINVAL; + } + return 0; +} + +static int data_set(void *data, u64 val) +{ + struct pm8901_dbg_device *dbgdev = data; + u8 reg = val; + int rc; + + mutex_lock(&dbgdev->dbg_mutex); + + rc = check_addr(dbgdev->addr, __func__); + if (rc) + goto done; + + rc = pm8901_write(dbgdev->pm_chip, dbgdev->addr, ®, 1); + + if (rc) + pr_err("%s: FAIL pm8901_write(0x%03X)=0x%02X: rc=%d\n", + __func__, dbgdev->addr, reg, rc); +done: + mutex_unlock(&dbgdev->dbg_mutex); + return rc; +} + +static int data_get(void *data, u64 *val) +{ + struct pm8901_dbg_device *dbgdev = data; + int rc; + u8 reg; + + mutex_lock(&dbgdev->dbg_mutex); + + rc = check_addr(dbgdev->addr, __func__); + if (rc) + goto done; + + rc = pm8901_read(dbgdev->pm_chip, dbgdev->addr, ®, 1); + + if (rc) { + pr_err("%s: FAIL pm8901_read(0x%03X)=0x%02X: rc=%d\n", + __func__, dbgdev->addr, reg, rc); + goto done; + } + + *val = reg; +done: + mutex_unlock(&dbgdev->dbg_mutex); + return rc; +} + +DEFINE_SIMPLE_ATTRIBUTE(dbg_data_fops, data_get, data_set, "0x%02llX\n"); + +static int addr_set(void *data, u64 val) +{ + struct pm8901_dbg_device *dbgdev = data; + int rc; + + rc = check_addr(val, __func__); + if (rc) + return rc; + + mutex_lock(&dbgdev->dbg_mutex); + dbgdev->addr = val; + mutex_unlock(&dbgdev->dbg_mutex); + + return 0; +} + +static int addr_get(void *data, u64 *val) +{ + struct pm8901_dbg_device *dbgdev = data; + int rc; + + mutex_lock(&dbgdev->dbg_mutex); + + rc = check_addr(dbgdev->addr, __func__); + if (rc) { + mutex_unlock(&dbgdev->dbg_mutex); + return rc; + } + *val = dbgdev->addr; + + mutex_unlock(&dbgdev->dbg_mutex); + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(dbg_addr_fops, addr_get, addr_set, "0x%03llX\n"); + +static int __devinit pmic8901_dbg_probe(struct pm8901_chip *chip) +{ + struct pm8901_dbg_device *dbgdev; + struct dentry *dent; + struct dentry *temp; + + if (chip == NULL) { + pr_err("%s: no parent data passed in.\n", __func__); + return -EINVAL; + } + + dbgdev = kzalloc(sizeof *dbgdev, GFP_KERNEL); + if (dbgdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + mutex_init(&dbgdev->dbg_mutex); + + dbgdev->pm_chip = chip; + dbgdev->addr = -1; + + dent = debugfs_create_dir("pm8901-dbg", NULL); + if (dent == NULL || IS_ERR(dent)) { + pr_err("%s: ERR debugfs_create_dir: dent=0x%X\n", + __func__, (unsigned)dent); + return -ENOMEM; + } + + temp = debugfs_create_file("addr", S_IRUSR | S_IWUSR, dent, + dbgdev, &dbg_addr_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("%s: ERR debugfs_create_file: dent=0x%X\n", + __func__, (unsigned)temp); + goto debug_error; + } + + temp = debugfs_create_file("data", S_IRUSR | S_IWUSR, dent, + dbgdev, &dbg_data_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("%s: ERR debugfs_create_file: dent=0x%X\n", + __func__, (unsigned)temp); + goto debug_error; + } + + dbgdev->dent = dent; + + pmic_dbg_device = dbgdev; + + return 0; + +debug_error: + debugfs_remove_recursive(dent); + return -ENOMEM; +} + +static int __devexit pmic8901_dbg_remove(void) +{ + if (pmic_dbg_device) { + debugfs_remove_recursive(pmic_dbg_device->dent); + kfree(pmic_dbg_device); + } + return 0; +} + +#else + +static int __devinit pmic8901_dbg_probe(struct pm8901_chip *chip) +{ + return 0; +} + +static int __devexit pmic8901_dbg_remove(void) +{ + return 0; +} + +#endif + +static struct irq_chip pm8901_irq_chip = { + .name = "pm8901", + .irq_ack = pm8901_irq_ack, + .irq_mask = pm8901_irq_mask, + .irq_unmask = pm8901_irq_unmask, + .irq_set_type = pm8901_irq_set_type, + .irq_set_wake = pm8901_irq_set_wake, +}; + +static int pm8901_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int i, rc; + struct pm8901_platform_data *pdata = client->dev.platform_data; + struct pm8901_chip *chip; + + if (pdata == NULL || !client->irq) { + pr_err("%s: No platform_data or IRQ.\n", __func__); + return -ENODEV; + } + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C) == 0) { + pr_err("%s: i2c_check_functionality failed.\n", __func__); + return -ENODEV; + } + + chip = kzalloc(sizeof *chip, GFP_KERNEL); + if (chip == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + chip->dev = client; + + /* Read PMIC chip revision */ + rc = ssbi_read(chip->dev, SSBI_REG_REV, &chip->revision, 1); + if (rc) + pr_err("%s: Failed on ssbi_read for revision: rc=%d.\n", + __func__, rc); + pr_info("%s: PMIC revision: %X\n", __func__, chip->revision); + + (void) memcpy((void *)&chip->pdata, (const void *)pdata, + sizeof(chip->pdata)); + + irq_set_handler_data(chip->dev->irq, (void *)chip); + irq_set_irq_wake(chip->dev->irq, 1); + + chip->pm_max_irq = 0; + chip->pm_max_blocks = 0; + chip->pm_max_masters = 0; + + i2c_set_clientdata(client, chip); + + pmic_chip = chip; + spin_lock_init(&chip->pm_lock); + + /* Register for all reserved IRQs */ + for (i = pdata->irq_base; i < (pdata->irq_base + MAX_PM_IRQ); i++) { + irq_set_chip(i, &pm8901_irq_chip); + irq_set_handler(i, handle_edge_irq); + set_irq_flags(i, IRQF_VALID); + irq_set_handler_data(i, (void *)chip); + } + + rc = mfd_add_devices(&chip->dev->dev, 0, pdata->sub_devices, + pdata->num_subdevs, NULL, 0); + if (rc) { + pr_err("%s: could not add devices %d\n", __func__, rc); + return rc; + } + + rc = request_threaded_irq(chip->dev->irq, NULL, pm8901_isr_thread, + IRQF_ONESHOT | IRQF_DISABLED | pdata->irq_trigger_flags, + "pm8901-irq", chip); + if (rc) + pr_err("%s: could not request irq %d: %d\n", __func__, + chip->dev->irq, rc); + + rc = pmic8901_dbg_probe(chip); + if (rc < 0) + pr_err("%s: could not set up debugfs: %d\n", __func__, rc); + + return rc; +} + +static int __devexit pm8901_remove(struct i2c_client *client) +{ + struct pm8901_chip *chip; + + chip = i2c_get_clientdata(client); + if (chip) { + if (chip->pm_max_irq) { + irq_set_irq_wake(chip->dev->irq, 0); + free_irq(chip->dev->irq, chip); + } + + mfd_remove_devices(&chip->dev->dev); + + chip->dev = NULL; + + kfree(chip); + } + + pmic8901_dbg_remove(); + + return 0; +} + +#ifdef CONFIG_PM +static int pm8901_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct pm8901_chip *chip; + int i; + unsigned long irqsave; + + chip = i2c_get_clientdata(client); + + for (i = 0; i < MAX_PM_IRQ; i++) { + spin_lock_irqsave(&chip->pm_lock, irqsave); + if (chip->config[i] && !chip->wake_enable[i]) { + if (!((chip->config[i] & PM8901_IRQF_MASK_ALL) + == PM8901_IRQF_MASK_ALL)) + pm8901_irq_mask(irq_get_irq_data(i + + chip->pdata.irq_base)); + } + spin_unlock_irqrestore(&chip->pm_lock, irqsave); + } + + if (!chip->count_wakeable) + disable_irq(chip->dev->irq); + + return 0; +} + +static int pm8901_resume(struct i2c_client *client) +{ + struct pm8901_chip *chip; + int i; + unsigned long irqsave; + + chip = i2c_get_clientdata(client); + + for (i = 0; i < MAX_PM_IRQ; i++) { + spin_lock_irqsave(&chip->pm_lock, irqsave); + if (chip->config[i] && !chip->wake_enable[i]) { + if (!((chip->config[i] & PM8901_IRQF_MASK_ALL) + == PM8901_IRQF_MASK_ALL)) + pm8901_irq_unmask(irq_get_irq_data(i + + chip->pdata.irq_base)); + } + spin_unlock_irqrestore(&chip->pm_lock, irqsave); + } + + if (!chip->count_wakeable) + enable_irq(chip->dev->irq); + + return 0; +} +#else +#define pm8901_suspend NULL +#define pm8901_resume NULL +#endif + +static const struct i2c_device_id pm8901_ids[] = { + { "pm8901-core", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, pm8901_ids); + +static struct i2c_driver pm8901_driver = { + .driver.name = "pm8901-core", + .id_table = pm8901_ids, + .probe = pm8901_probe, + .remove = __devexit_p(pm8901_remove), + .suspend = pm8901_suspend, + .resume = pm8901_resume, +}; + +static int __init pm8901_init(void) +{ + int rc = i2c_add_driver(&pm8901_driver); + pr_notice("%s: i2c_add_driver: rc = %d\n", __func__, rc); + + return rc; +} + +static void __exit pm8901_exit(void) +{ + i2c_del_driver(&pm8901_driver); +} + +arch_initcall(pm8901_init); +module_exit(pm8901_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8901 core driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8901-core"); diff --git a/drivers/mfd/timpani-codec.c b/drivers/mfd/timpani-codec.c new file mode 100644 index 00000000000..364670e1299 --- /dev/null +++ b/drivers/mfd/timpani-codec.c @@ -0,0 +1,3645 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Timpani codec driver is activated through Marimba core driver */ + +#define MAX_MDELAY_US 20000 + +#define TIMPANI_PATH_MASK(x) (1 << (x)) + +#define TIMPANI_CODEC_AUXPGA_GAIN_RANGE (0x0F) + +#define TIMPANI_RX1_ST_MASK (TIMPANI_CDC_RX1_CTL_SIDETONE_EN1_L_M |\ + TIMPANI_CDC_RX1_CTL_SIDETONE_EN1_R_M) +#define TIMPANI_RX1_ST_ENABLE ((1 << TIMPANI_CDC_RX1_CTL_SIDETONE_EN1_L_S) |\ + (1 << TIMPANI_CDC_RX1_CTL_SIDETONE_EN1_R_S)) +#define TIMPANI_CDC_ST_MIXING_TX1_MASK (TIMPANI_CDC_ST_MIXING_TX1_L_M |\ + TIMPANI_CDC_ST_MIXING_TX1_R_M) +#define TIMPANI_CDC_ST_MIXING_TX1_ENABLE ((1 << TIMPANI_CDC_ST_MIXING_TX1_L_S)\ + | (1 << TIMPANI_CDC_ST_MIXING_TX1_R_S)) +#define TIMPANI_CDC_ST_MIXING_TX2_MASK (TIMPANI_CDC_ST_MIXING_TX2_L_M |\ + TIMPANI_CDC_ST_MIXING_TX2_R_M) +#define TIMPANI_CDC_ST_MIXING_TX2_ENABLE ((1 << TIMPANI_CDC_ST_MIXING_TX2_L_S)\ + | (1 << TIMPANI_CDC_ST_MIXING_TX2_R_S)) + +enum refcnt { + DEC = 0, + INC = 1, + IGNORE = 2, +}; +#define TIMPANI_ARRAY_SIZE (TIMPANI_A_CDC_COMP_HALT + 1) + +static u8 timpani_shadow[TIMPANI_ARRAY_SIZE]; + +struct adie_codec_path { + struct adie_codec_dev_profile *profile; + struct adie_codec_register_image img; + u32 hwsetting_idx; + u32 stage_idx; + u32 curr_stage; + u32 reg_owner; +}; + +enum /* regaccess blk id */ +{ + RA_BLOCK_RX1 = 0, + RA_BLOCK_RX2, + RA_BLOCK_TX1, + RA_BLOCK_TX2, + RA_BLOCK_LB, + RA_BLOCK_SHARED_RX_LB, + RA_BLOCK_SHARED_TX, + RA_BLOCK_TXFE1, + RA_BLOCK_TXFE2, + RA_BLOCK_PA_COMMON, + RA_BLOCK_PA_EAR, + RA_BLOCK_PA_HPH, + RA_BLOCK_PA_LINE, + RA_BLOCK_PA_AUX, + RA_BLOCK_ADC, + RA_BLOCK_DMIC, + RA_BLOCK_TX_I2S, + RA_BLOCK_DRV, + RA_BLOCK_TEST, + RA_BLOCK_RESERVED, + RA_BLOCK_NUM, +}; + +enum /* regaccess onwer ID */ +{ + RA_OWNER_NONE = 0, + RA_OWNER_PATH_RX1, + RA_OWNER_PATH_RX2, + RA_OWNER_PATH_TX1, + RA_OWNER_PATH_TX2, + RA_OWNER_PATH_LB, + RA_OWNER_DRV, + RA_OWNER_NUM, +}; + +struct reg_acc_blk_cfg { + u8 valid_owners[RA_OWNER_NUM]; +}; + +struct reg_ref_cnt { + u8 mask; + u8 path_mask; +}; + +#define TIMPANI_MAX_FIELDS 5 + +struct timpani_regaccess { + u8 reg_addr; + u8 blk_mask[RA_BLOCK_NUM]; + u8 reg_mask; + u8 reg_default; + struct reg_ref_cnt fld_ref_cnt[TIMPANI_MAX_FIELDS]; +}; + +struct timpani_regaccess timpani_regset[] = { + { + TIMPANI_A_MREF, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFC, 0x0, 0x3}, + TIMPANI_MREF_M, + TIMPANI_MREF_POR, + { + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDAC_IDAC_REF_CUR, + {0xFC, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDAC_IDAC_REF_CUR_M, + TIMPANI_CDAC_IDAC_REF_CUR_POR, + { + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXADC12_REF_CURR, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF}, + TIMPANI_TXADC12_REF_CURR_M, + TIMPANI_TXADC12_REF_CURR_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXADC3_EN, + { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFE, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + TIMPANI_TXADC3_EN_M, + TIMPANI_TXADC3_EN_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXADC4_EN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFE, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + TIMPANI_TXADC4_EN_M, + TIMPANI_TXADC4_EN_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CODEC_TXADC_STATUS_REGISTER_1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x30, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF}, + TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_M, + TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_POR, + { + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x30, .path_mask = 0}, + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXFE1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_TXFE1_M, + TIMPANI_TXFE1_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXFE2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_TXFE2_M, + TIMPANI_TXFE2_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXFE12_ATEST, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_TXFE12_ATEST_M, + TIMPANI_TXFE12_ATEST_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXFE_CLT, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF8, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7}, + TIMPANI_TXFE_CLT_M, + TIMPANI_TXFE_CLT_POR, + { + { .mask = 0xF8, .path_mask = 0}, + { .mask = 0x07, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXADC1_EN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFE, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + TIMPANI_TXADC1_EN_M, + TIMPANI_TXADC1_EN_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXADC2_EN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFE, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + TIMPANI_TXADC2_EN_M, + TIMPANI_TXADC2_EN_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXADC_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_TXADC_CTL_M, + TIMPANI_TXADC_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXADC_CTL2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_TXADC_CTL2_M, + TIMPANI_TXADC_CTL2_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXADC_CTL3, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xFE, 0x0, 0x0, 0x0, 0x0, 0x1}, + TIMPANI_TXADC_CTL3_M, + TIMPANI_TXADC_CTL3_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXADC_CHOP_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xFC, 0x0, 0x0, 0x0, 0x0, 0x3}, + TIMPANI_TXADC_CHOP_CTL_M, + TIMPANI_TXADC_CHOP_CTL_POR, + { + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXFE3, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE2, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1D}, + TIMPANI_TXFE3_M, + TIMPANI_TXFE3_POR, + { + { .mask = 0xE2, .path_mask = 0}, + { .mask = 0x1D, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXFE4, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE2, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1D}, + TIMPANI_TXFE4_M, + TIMPANI_TXFE4_POR, + { + { .mask = 0xE2, .path_mask = 0}, + { .mask = 0x1D, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXFE3_ATEST, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_TXFE3_ATEST_M, + TIMPANI_TXFE3_ATEST_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_TXFE_DIFF_SE, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xC, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF0}, + TIMPANI_TXFE_DIFF_SE_M, + TIMPANI_TXFE_DIFF_SE_POR, + { + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x0C, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDAC_RX_CLK_CTL, + {0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDAC_RX_CLK_CTL_M, + TIMPANI_CDAC_RX_CLK_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDAC_BUFF_CTL, + {0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDAC_BUFF_CTL_M, + TIMPANI_CDAC_BUFF_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDAC_REF_CTL1, + {0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDAC_REF_CTL1_M, + TIMPANI_CDAC_REF_CTL1_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_IDAC_DWA_FIR_CTL, + {0xF8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7}, + TIMPANI_IDAC_DWA_FIR_CTL_M, + TIMPANI_IDAC_DWA_FIR_CTL_POR, + { + { .mask = 0xF8, .path_mask = 0}, + { .mask = 0x07, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDAC_REF_CTL2, + {0x6F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90}, + TIMPANI_CDAC_REF_CTL2_M, + TIMPANI_CDAC_REF_CTL2_POR, + { + { .mask = 0x6F, .path_mask = 0}, + { .mask = 0x90, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDAC_CTL1, + {0x7F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80}, + TIMPANI_CDAC_CTL1_M, + TIMPANI_CDAC_CTL1_POR, + { + { .mask = 0x7F, .path_mask = 0}, + { .mask = 0x80, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDAC_CTL2, + {0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDAC_CTL2_M, + TIMPANI_CDAC_CTL2_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_IDAC_L_CTL, + {0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_IDAC_L_CTL_M, + TIMPANI_IDAC_L_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_IDAC_R_CTL, + {0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_IDAC_R_CTL_M, + TIMPANI_IDAC_R_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_MASTER_BIAS, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1F, + 0xE0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_MASTER_BIAS_M, + TIMPANI_PA_MASTER_BIAS_POR, + { + { .mask = 0x1F, .path_mask = 0}, + { .mask = 0xE0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_BIAS, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_CLASSD_BIAS_M, + TIMPANI_PA_CLASSD_BIAS_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_AUXPGA_CUR, + {0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_AUXPGA_CUR_M, + TIMPANI_AUXPGA_CUR_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_AUXPGA_CM, + {0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_AUXPGA_CM_M, + TIMPANI_AUXPGA_CM_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_EARPA_MSTB_EN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0xFC, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_HPH_EARPA_MSTB_EN_M, + TIMPANI_PA_HPH_EARPA_MSTB_EN_POR, + { + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x02, .path_mask = 0}, + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_LINE_AUXO_EN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF8, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_LINE_AUXO_EN_M, + TIMPANI_PA_LINE_AUXO_EN_POR, + { + { .mask = 0xF8, .path_mask = 0}, + { .mask = 0x07, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_AUXPGA_EN, + {0x0, 0x0, 0x0, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_PA_CLASSD_AUXPGA_EN_M, + TIMPANI_PA_CLASSD_AUXPGA_EN_POR, + { + { .mask = 0x30, .path_mask = 0}, + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_LINE_L_GAIN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFC, 0x0, 0x3}, + TIMPANI_PA_LINE_L_GAIN_M, + TIMPANI_PA_LINE_L_GAIN_POR, + { + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_LINE_R_GAIN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFC, 0x0, 0x3}, + TIMPANI_PA_LINE_R_GAIN_M, + TIMPANI_PA_LINE_R_GAIN_POR, + { + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_L_GAIN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFE, 0x0, 0x1}, + TIMPANI_PA_HPH_L_GAIN_M, + TIMPANI_PA_HPH_L_GAIN_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_R_GAIN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFE, 0x0, 0x1}, + TIMPANI_PA_HPH_R_GAIN_M, + TIMPANI_PA_HPH_R_GAIN_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_AUXPGA_LR_GAIN, + {0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_AUXPGA_LR_GAIN_M, + TIMPANI_AUXPGA_LR_GAIN_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_AUXO_EARPA_CONN, + {0x21, 0x42, 0x0, 0x0, 0x84, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18}, + TIMPANI_PA_AUXO_EARPA_CONN_M, + TIMPANI_PA_AUXO_EARPA_CONN_POR, + { + { .mask = 0x21, .path_mask = 0}, + { .mask = 0x42, .path_mask = 0}, + { .mask = 0x84, .path_mask = 0}, + { .mask = 0x18, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_LINE_ST_CONN, + {0x24, 0x48, 0x0, 0x0, 0x93, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_LINE_ST_CONN_M, + TIMPANI_PA_LINE_ST_CONN_POR, + { + { .mask = 0x24, .path_mask = 0}, + { .mask = 0x48, .path_mask = 0}, + { .mask = 0x93, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_LINE_MONO_CONN, + {0x24, 0x48, 0x0, 0x0, 0x93, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_LINE_MONO_CONN_M, + TIMPANI_PA_LINE_MONO_CONN_POR, + { + { .mask = 0x24, .path_mask = 0}, + { .mask = 0x48, .path_mask = 0}, + { .mask = 0x93, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_ST_CONN, + {0x24, 0x48, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_HPH_ST_CONN_M, + TIMPANI_PA_HPH_ST_CONN_POR, + { + { .mask = 0x24, .path_mask = 0}, + { .mask = 0x48, .path_mask = 0}, + { .mask = 0x90, .path_mask = 0}, + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_MONO_CONN, + {0x24, 0x48, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3}, + TIMPANI_PA_HPH_MONO_CONN_M, + TIMPANI_PA_HPH_MONO_CONN_POR, + { + { .mask = 0x24, .path_mask = 0}, + { .mask = 0x48, .path_mask = 0}, + { .mask = 0x90, .path_mask = 0}, + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_CONN, + {0x80, 0x40, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF}, + TIMPANI_PA_CLASSD_CONN_M, + TIMPANI_PA_CLASSD_CONN_POR, + { + { .mask = 0x80, .path_mask = 0}, + { .mask = 0x40, .path_mask = 0}, + { .mask = 0x20, .path_mask = 0}, + { .mask = 0x10, .path_mask = 0}, + { .mask = 0x0F, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CNP_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xCF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30}, + TIMPANI_PA_CNP_CTL_M, + TIMPANI_PA_CNP_CTL_POR, + { + { .mask = 0xCF, .path_mask = 0}, + { .mask = 0x30, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_L_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3F, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_PA_CLASSD_L_CTL_M, + TIMPANI_PA_CLASSD_L_CTL_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_R_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3F, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_PA_CLASSD_R_CTL_M, + TIMPANI_PA_CLASSD_R_CTL_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_INT2_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_CLASSD_INT2_CTL_M, + TIMPANI_PA_CLASSD_INT2_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_L_OCP_CLK_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_HPH_L_OCP_CLK_CTL_M, + TIMPANI_PA_HPH_L_OCP_CLK_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_L_SW_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8}, + TIMPANI_PA_CLASSD_L_SW_CTL_M, + TIMPANI_PA_CLASSD_L_SW_CTL_POR, + { + { .mask = 0xF7, .path_mask = 0}, + { .mask = 0x08, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_L_OCP1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_CLASSD_L_OCP1_M, + TIMPANI_PA_CLASSD_L_OCP1_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_L_OCP2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_CLASSD_L_OCP2_M, + TIMPANI_PA_CLASSD_L_OCP2_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_R_OCP_CLK_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_HPH_R_OCP_CLK_CTL_M, + TIMPANI_PA_HPH_R_OCP_CLK_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_R_SW_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8}, + TIMPANI_PA_CLASSD_R_SW_CTL_M, + TIMPANI_PA_CLASSD_R_SW_CTL_POR, + { + { .mask = 0xF7, .path_mask = 0}, + { .mask = 0x08, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_R_OCP1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_CLASSD_R_OCP1_M, + TIMPANI_PA_CLASSD_R_OCP1_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_R_OCP2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_CLASSD_R_OCP2_M, + TIMPANI_PA_CLASSD_R_OCP2_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_CTL1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF}, + TIMPANI_PA_HPH_CTL1_M, + TIMPANI_PA_HPH_CTL1_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_CTL2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFE, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + TIMPANI_PA_HPH_CTL2_M, + TIMPANI_PA_HPH_CTL2_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_LINE_AUXO_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x3C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_LINE_AUXO_CTL_M, + TIMPANI_PA_LINE_AUXO_CTL_POR, + { + { .mask = 0xC3, .path_mask = 0}, + { .mask = 0x3C, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_AUXO_EARPA_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, + 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_PA_AUXO_EARPA_CTL_M, + TIMPANI_PA_AUXO_EARPA_CTL_POR, + { + { .mask = 0x07, .path_mask = 0}, + { .mask = 0x38, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_EARO_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_PA_EARO_CTL_M, + TIMPANI_PA_EARO_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_MASTER_BIAS_CUR, + {0x0, 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x18, + 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + TIMPANI_PA_MASTER_BIAS_CUR_M, + TIMPANI_PA_MASTER_BIAS_CUR_POR, + { + { .mask = 0x60, .path_mask = 0}, + { .mask = 0x80, .path_mask = 0}, + { .mask = 0x18, .path_mask = 0}, + { .mask = 0x06, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_CLASSD_SC_STATUS, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xCC, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x33}, + TIMPANI_PA_CLASSD_SC_STATUS_M, + TIMPANI_PA_CLASSD_SC_STATUS_POR, + { + { .mask = 0xCC, .path_mask = 0}, + { .mask = 0x33, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_PA_HPH_SC_STATUS, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x77}, + TIMPANI_PA_HPH_SC_STATUS_M, + TIMPANI_PA_HPH_SC_STATUS_POR, + { + { .mask = 0x88, .path_mask = 0}, + { .mask = 0x77, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_EN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x7F}, + TIMPANI_ATEST_EN_M, + TIMPANI_ATEST_EN_POR, + { + { .mask = 0x80, .path_mask = 0}, + { .mask = 0x7F, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_TSHKADC, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF0}, + TIMPANI_ATEST_TSHKADC_M, + TIMPANI_ATEST_TSHKADC_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_TXADC13, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7F, 0x80}, + TIMPANI_ATEST_TXADC13_M, + TIMPANI_ATEST_TXADC13_POR, + { + { .mask = 0x7F, .path_mask = 0}, + { .mask = 0x80, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_TXADC24, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7F, 0x80}, + TIMPANI_ATEST_TXADC24_M, + TIMPANI_ATEST_TXADC24_POR, + { + { .mask = 0x7F, .path_mask = 0}, + { .mask = 0x80, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_AUXPGA, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF8, 0x7}, + TIMPANI_ATEST_AUXPGA_M, + TIMPANI_ATEST_AUXPGA_POR, + { + { .mask = 0xF8, .path_mask = 0}, + { .mask = 0x07, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_CDAC, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0}, + TIMPANI_ATEST_CDAC_M, + TIMPANI_ATEST_CDAC_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_IDAC, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0}, + TIMPANI_ATEST_IDAC_M, + TIMPANI_ATEST_IDAC_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_PA1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0}, + TIMPANI_ATEST_PA1_M, + TIMPANI_ATEST_PA1_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_CLASSD, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0}, + TIMPANI_ATEST_CLASSD_M, + TIMPANI_ATEST_CLASSD_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_ATEST_LINEO_AUXO, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0}, + TIMPANI_ATEST_LINEO_AUXO_M, + TIMPANI_ATEST_LINEO_AUXO_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RESET_CTL, + {0x2, 0x8, 0x5, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_CDC_RESET_CTL_M, + TIMPANI_CDC_RESET_CTL_POR, + { + { .mask = 0x02, .path_mask = 0}, + { .mask = 0x08, .path_mask = 0}, + { .mask = 0x05, .path_mask = 0}, + { .mask = 0x30, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX1_CTL, + {0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX1_CTL_M, + TIMPANI_CDC_RX1_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX_I2S_CTL, + {0x0, 0x0, 0x10, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0, 0xC0}, + TIMPANI_CDC_TX_I2S_CTL_M, + TIMPANI_CDC_TX_I2S_CTL_POR, + { + { .mask = 0x10, .path_mask = 0}, + { .mask = 0x20, .path_mask = 0}, + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_CH_CTL, + {0x3, 0x30, 0xC, 0xC0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_CH_CTL_M, + TIMPANI_CDC_CH_CTL_POR, + { + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x30, .path_mask = 0}, + { .mask = 0x0C, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX1LG, + {0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX1LG_M, + TIMPANI_CDC_RX1LG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX1RG, + {0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX1RG_M, + TIMPANI_CDC_RX1RG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX1LG, + {0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_TX1LG_M, + TIMPANI_CDC_TX1LG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX1RG, + {0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_TX1RG_M, + TIMPANI_CDC_TX1RG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX_PGA_TIMER, + {0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX_PGA_TIMER_M, + TIMPANI_CDC_RX_PGA_TIMER_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX_PGA_TIMER, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_TX_PGA_TIMER_M, + TIMPANI_CDC_TX_PGA_TIMER_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_GCTL1, + {0xF, 0x0, 0xF0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_GCTL1_M, + TIMPANI_CDC_GCTL1_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX1L_STG, + {0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_TX1L_STG_M, + TIMPANI_CDC_TX1L_STG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ST_CTL, + {0x0, 0xF, 0x0, 0xF0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_ST_CTL_M, + TIMPANI_CDC_ST_CTL_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX1L_DCOFFSET, + {0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX1L_DCOFFSET_M, + TIMPANI_CDC_RX1L_DCOFFSET_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX1R_DCOFFSET, + {0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX1R_DCOFFSET_M, + TIMPANI_CDC_RX1R_DCOFFSET_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_BYPASS_CTL1, + {0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF0}, + TIMPANI_CDC_BYPASS_CTL1_M, + TIMPANI_CDC_BYPASS_CTL1_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_PDM_CONFIG, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF0}, + TIMPANI_CDC_PDM_CONFIG_M, + TIMPANI_CDC_PDM_CONFIG_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TESTMODE1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3F, 0xC0}, + TIMPANI_CDC_TESTMODE1_M, + TIMPANI_CDC_TESTMODE1_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_DMIC_CLK_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_CDC_DMIC_CLK_CTL_M, + TIMPANI_CDC_DMIC_CLK_CTL_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ADC12_CLK_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_ADC12_CLK_CTL_M, + TIMPANI_CDC_ADC12_CLK_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX1_CTL, + {0x0, 0x0, 0x3F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_CDC_TX1_CTL_M, + TIMPANI_CDC_TX1_CTL_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ADC34_CLK_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_ADC34_CLK_CTL_M, + TIMPANI_CDC_ADC34_CLK_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX2_CTL, + {0x0, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_CDC_TX2_CTL_M, + TIMPANI_CDC_TX2_CTL_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX1_CLK_CTL, + {0x1F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE0}, + TIMPANI_CDC_RX1_CLK_CTL_M, + TIMPANI_CDC_RX1_CLK_CTL_POR, + { + { .mask = 0x1F, .path_mask = 0}, + { .mask = 0xE0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX2_CLK_CTL, + {0x1F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE0}, + TIMPANI_CDC_RX2_CLK_CTL_M, + TIMPANI_CDC_RX2_CLK_CTL_POR, + { + { .mask = 0x1F, .path_mask = 0}, + { .mask = 0xE0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_DEC_ADC_SEL, + {0x0, 0x0, 0xF, 0xF0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_DEC_ADC_SEL_M, + TIMPANI_CDC_DEC_ADC_SEL_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC_INPUT_MUX, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3F, 0x0, 0xC0}, + TIMPANI_CDC_ANC_INPUT_MUX_M, + TIMPANI_CDC_ANC_INPUT_MUX_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC_RX_CLK_NS_SEL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0xFE}, + TIMPANI_CDC_ANC_RX_CLK_NS_SEL_M, + TIMPANI_CDC_ANC_RX_CLK_NS_SEL_POR, + { + { .mask = 0x01, .path_mask = 0}, + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC_FB_TUNE_SEL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF}, + TIMPANI_CDC_ANC_FB_TUNE_SEL_M, + TIMPANI_CDC_ANC_FB_TUNE_SEL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CLK_DIV_SYNC_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0xFC}, + TIMPANI_CLK_DIV_SYNC_CTL_M, + TIMPANI_CLK_DIV_SYNC_CTL_POR, + { + { .mask = 0x03, .path_mask = 0}, + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ADC_CLK_EN, + {0x0, 0x0, 0x3, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF0}, + TIMPANI_CDC_ADC_CLK_EN_M, + TIMPANI_CDC_ADC_CLK_EN_POR, + { + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x0C, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ST_MIXING, + {0x0, 0x0, 0x3, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF0}, + TIMPANI_CDC_ST_MIXING_M, + TIMPANI_CDC_ST_MIXING_POR, + { + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x0C, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX2_CTL, + {0x0, 0x7F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80}, + TIMPANI_CDC_RX2_CTL_M, + TIMPANI_CDC_RX2_CTL_POR, + { + { .mask = 0x7F, .path_mask = 0}, + { .mask = 0x80, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ARB_CLK_EN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF}, + TIMPANI_CDC_ARB_CLK_EN_M, + TIMPANI_CDC_ARB_CLK_EN_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_I2S_CTL2, + {0x2, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x39, 0x0, 0x0, 0xC0}, + TIMPANI_CDC_I2S_CTL2_M, + TIMPANI_CDC_I2S_CTL2_POR, + { + { .mask = 0x02, .path_mask = 0}, + { .mask = 0x04, .path_mask = 0}, + { .mask = 0x39, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX2LG, + {0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX2LG_M, + TIMPANI_CDC_RX2LG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX2RG, + {0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX2RG_M, + TIMPANI_CDC_RX2RG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX2LG, + {0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_TX2LG_M, + TIMPANI_CDC_TX2LG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX2RG, + {0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_TX2RG_M, + TIMPANI_CDC_TX2RG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_DMIC_MUX, + {0x0, 0x0, 0xF, 0xF0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_DMIC_MUX_M, + TIMPANI_CDC_DMIC_MUX_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ARB_CLK_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0xFC}, + TIMPANI_CDC_ARB_CLK_CTL_M, + TIMPANI_CDC_ARB_CLK_CTL_POR, + { + { .mask = 0x03, .path_mask = 0}, + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_GCTL2, + {0x0, 0xF, 0x0, 0xF0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_GCTL2_M, + TIMPANI_CDC_GCTL2_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_BYPASS_CTL2, + {0x0, 0x0, 0x3F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_CDC_BYPASS_CTL2_M, + TIMPANI_CDC_BYPASS_CTL2_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_BYPASS_CTL3, + {0x0, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0}, + TIMPANI_CDC_BYPASS_CTL3_M, + TIMPANI_CDC_BYPASS_CTL3_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_BYPASS_CTL4, + {0x0, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF0}, + TIMPANI_CDC_BYPASS_CTL4_M, + TIMPANI_CDC_BYPASS_CTL4_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX2L_DCOFFSET, + {0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX2L_DCOFFSET_M, + TIMPANI_CDC_RX2L_DCOFFSET_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX2R_DCOFFSET, + {0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_RX2R_DCOFFSET_M, + TIMPANI_CDC_RX2R_DCOFFSET_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_RX_MIX_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0xFC}, + TIMPANI_CDC_RX_MIX_CTL_M, + TIMPANI_CDC_RX_MIX_CTL_POR, + { + { .mask = 0x03, .path_mask = 0}, + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_SPARE_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xFE}, + TIMPANI_CDC_SPARE_CTL_M, + TIMPANI_CDC_SPARE_CTL_POR, + { + { .mask = 0x01, .path_mask = 0}, + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TESTMODE2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1F, 0xE0}, + TIMPANI_CDC_TESTMODE2_M, + TIMPANI_CDC_TESTMODE2_POR, + { + { .mask = 0x1F, .path_mask = 0}, + { .mask = 0xE0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_PDM_OE, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0}, + TIMPANI_CDC_PDM_OE_M, + TIMPANI_CDC_PDM_OE_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX1R_STG, + {0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_TX1R_STG_M, + TIMPANI_CDC_TX1R_STG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX2L_STG, + {0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_TX2L_STG_M, + TIMPANI_CDC_TX2L_STG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_TX2R_STG, + {0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + TIMPANI_CDC_TX2R_STG_M, + TIMPANI_CDC_TX2R_STG_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ARB_BYPASS_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF}, + TIMPANI_CDC_ARB_BYPASS_CTL_M, + TIMPANI_CDC_ARB_BYPASS_CTL_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_CTL1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1F, 0x0, 0xE0}, + TIMPANI_CDC_ANC1_CTL1_M, + TIMPANI_CDC_ANC1_CTL1_POR, + { + { .mask = 0x1F, .path_mask = 0}, + { .mask = 0xE0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_CTL2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3F, 0x0, 0xC0}, + TIMPANI_CDC_ANC1_CTL2_M, + TIMPANI_CDC_ANC1_CTL2_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_FF_FB_SHIFT, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC1_FF_FB_SHIFT_M, + TIMPANI_CDC_ANC1_FF_FB_SHIFT_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_RX_NS, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0xF8}, + TIMPANI_CDC_ANC1_RX_NS_M, + TIMPANI_CDC_ANC1_RX_NS_POR, + { + { .mask = 0x07, .path_mask = 0}, + { .mask = 0xF8, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_SPARE, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC1_SPARE_M, + TIMPANI_CDC_ANC1_SPARE_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_IIR_COEFF_PTR, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1F, 0x0, 0xE0}, + TIMPANI_CDC_ANC1_IIR_COEFF_PTR_M, + TIMPANI_CDC_ANC1_IIR_COEFF_PTR_POR, + { + { .mask = 0x1F, .path_mask = 0}, + { .mask = 0xE0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_IIR_COEFF_MSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0xFE}, + TIMPANI_CDC_ANC1_IIR_COEFF_MSB_M, + TIMPANI_CDC_ANC1_IIR_COEFF_MSB_POR, + { + { .mask = 0x01, .path_mask = 0}, + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_IIR_COEFF_LSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC1_IIR_COEFF_LSB_M, + TIMPANI_CDC_ANC1_IIR_COEFF_LSB_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_IIR_COEFF_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0xFC}, + TIMPANI_CDC_ANC1_IIR_COEFF_CTL_M, + TIMPANI_CDC_ANC1_IIR_COEFF_CTL_POR, + { + { .mask = 0x03, .path_mask = 0}, + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_LPF_COEFF_PTR, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_ANC1_LPF_COEFF_PTR_M, + TIMPANI_CDC_ANC1_LPF_COEFF_PTR_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_LPF_COEFF_MSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_ANC1_LPF_COEFF_MSB_M, + TIMPANI_CDC_ANC1_LPF_COEFF_MSB_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_LPF_COEFF_LSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC1_LPF_COEFF_LSB_M, + TIMPANI_CDC_ANC1_LPF_COEFF_LSB_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_SCALE_PTR, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC1_SCALE_PTR_M, + TIMPANI_CDC_ANC1_SCALE_PTR_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_SCALE, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC1_SCALE_M, + TIMPANI_CDC_ANC1_SCALE_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC1_DEBUG, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_ANC1_DEBUG_M, + TIMPANI_CDC_ANC1_DEBUG_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_CTL1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1F, 0x0, 0xE0}, + TIMPANI_CDC_ANC2_CTL1_M, + TIMPANI_CDC_ANC2_CTL1_POR, + { + { .mask = 0x1F, .path_mask = 0}, + { .mask = 0xE0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_CTL2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3F, 0x0, 0xC0}, + TIMPANI_CDC_ANC2_CTL2_M, + TIMPANI_CDC_ANC2_CTL2_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_FF_FB_SHIFT, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC2_FF_FB_SHIFT_M, + TIMPANI_CDC_ANC2_FF_FB_SHIFT_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_RX_NS, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0xF8}, + TIMPANI_CDC_ANC2_RX_NS_M, + TIMPANI_CDC_ANC2_RX_NS_POR, + { + { .mask = 0x07, .path_mask = 0}, + { .mask = 0xF8, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_SPARE, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC2_SPARE_M, + TIMPANI_CDC_ANC2_SPARE_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_IIR_COEFF_PTR, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_ANC2_IIR_COEFF_PTR_M, + TIMPANI_CDC_ANC2_IIR_COEFF_PTR_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_IIR_COEFF_MSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0xFE}, + TIMPANI_CDC_ANC2_IIR_COEFF_MSB_M, + TIMPANI_CDC_ANC2_IIR_COEFF_MSB_POR, + { + { .mask = 0x01, .path_mask = 0}, + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_IIR_COEFF_LSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC2_IIR_COEFF_LSB_M, + TIMPANI_CDC_ANC2_IIR_COEFF_LSB_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_IIR_COEFF_CTL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0xFC}, + TIMPANI_CDC_ANC2_IIR_COEFF_CTL_M, + TIMPANI_CDC_ANC2_IIR_COEFF_CTL_POR, + { + { .mask = 0x03, .path_mask = 0}, + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_LPF_COEFF_PTR, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_ANC2_LPF_COEFF_PTR_M, + TIMPANI_CDC_ANC2_LPF_COEFF_PTR_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_LPF_COEFF_MSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_ANC2_LPF_COEFF_MSB_M, + TIMPANI_CDC_ANC2_LPF_COEFF_MSB_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_LPF_COEFF_LSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC2_LPF_COEFF_LSB_M, + TIMPANI_CDC_ANC2_LPF_COEFF_LSB_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_SCALE_PTR, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC2_SCALE_PTR_M, + TIMPANI_CDC_ANC2_SCALE_PTR_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_SCALE, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_ANC2_SCALE_M, + TIMPANI_CDC_ANC2_SCALE_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_ANC2_DEBUG, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_ANC2_DEBUG_M, + TIMPANI_CDC_ANC2_DEBUG_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_LINE_L_AVOL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xFC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3}, + TIMPANI_CDC_LINE_L_AVOL_M, + TIMPANI_CDC_LINE_L_AVOL_POR, + { + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_LINE_R_AVOL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xFC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3}, + TIMPANI_CDC_LINE_R_AVOL_M, + TIMPANI_CDC_LINE_R_AVOL_POR, + { + { .mask = 0xFC, .path_mask = 0}, + { .mask = 0x03, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_HPH_L_AVOL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFE, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + TIMPANI_CDC_HPH_L_AVOL_M, + TIMPANI_CDC_HPH_L_AVOL_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_HPH_R_AVOL, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFE, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + TIMPANI_CDC_HPH_R_AVOL_M, + TIMPANI_CDC_HPH_R_AVOL_POR, + { + { .mask = 0xFE, .path_mask = 0}, + { .mask = 0x01, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_CTL1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x3F, 0x0, 0xC0}, + TIMPANI_CDC_COMP_CTL1_M, + TIMPANI_CDC_COMP_CTL1_POR, + { + { .mask = 0x3F, .path_mask = 0}, + { .mask = 0xC0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_CTL2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_COMP_CTL2_M, + TIMPANI_CDC_COMP_CTL2_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_PEAK_METER, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_COMP_PEAK_METER_M, + TIMPANI_CDC_COMP_PEAK_METER_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_LEVEL_METER_CTL1, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0xF0}, + TIMPANI_CDC_COMP_LEVEL_METER_CTL1_M, + TIMPANI_CDC_COMP_LEVEL_METER_CTL1_POR, + { + { .mask = 0x0F, .path_mask = 0}, + { .mask = 0xF0, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_LEVEL_METER_CTL2, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_COMP_LEVEL_METER_CTL2_M, + TIMPANI_CDC_COMP_LEVEL_METER_CTL2_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_ZONE_SELECT, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x7F, 0x0, 0x80}, + TIMPANI_CDC_COMP_ZONE_SELECT_M, + TIMPANI_CDC_COMP_ZONE_SELECT_POR, + { + { .mask = 0x7F, .path_mask = 0}, + { .mask = 0x80, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_ZC_MSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_COMP_ZC_MSB_M, + TIMPANI_CDC_COMP_ZC_MSB_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_ZC_LSB, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0}, + TIMPANI_CDC_COMP_ZC_LSB_M, + TIMPANI_CDC_COMP_ZC_LSB_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_SHUT_DOWN, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF}, + TIMPANI_CDC_COMP_SHUT_DOWN_M, + TIMPANI_CDC_COMP_SHUT_DOWN_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_SHUT_DOWN_STATUS, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF}, + TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_M, + TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + }, + { + TIMPANI_A_CDC_COMP_HALT, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF}, + TIMPANI_CDC_COMP_HALT_M, + TIMPANI_CDC_COMP_HALT_POR, + { + { .mask = 0xFF, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + { .mask = 0x00, .path_mask = 0}, + } + } +}; + +struct reg_acc_blk_cfg timpani_blkcfg[RA_BLOCK_NUM] = { + { + .valid_owners = {RA_OWNER_NONE, RA_OWNER_PATH_RX1, + 0, 0, 0, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_RX1 */ + { + .valid_owners = {RA_OWNER_NONE, 0, RA_OWNER_PATH_RX2, + 0, 0, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_RX2 */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, RA_OWNER_PATH_TX1, + 0, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_TX1 */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, 0, RA_OWNER_PATH_TX2, + 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_TX2 */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, 0, 0, + RA_OWNER_PATH_LB, RA_OWNER_DRV} + }, + /* RA_BLOCK_LB */ + { + .valid_owners = {RA_OWNER_NONE, RA_OWNER_PATH_RX1, + RA_OWNER_PATH_RX2, 0, 0, RA_OWNER_PATH_LB, RA_OWNER_DRV} + }, + /* RA_BLOCK_SHARED_RX_LB */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, RA_OWNER_PATH_TX1, + RA_OWNER_PATH_TX2, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_SHARED_TX */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, RA_OWNER_PATH_TX1, + RA_OWNER_PATH_TX2, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_TXFE1 */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, RA_OWNER_PATH_TX1, + RA_OWNER_PATH_TX2, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_TXFE2 */ + { + .valid_owners = {RA_OWNER_NONE, RA_OWNER_PATH_RX1, + RA_OWNER_PATH_RX2, 0, 0, RA_OWNER_PATH_LB, RA_OWNER_DRV} + }, + /* RA_BLOCK_PA_COMMON */ + { + .valid_owners = {RA_OWNER_NONE, RA_OWNER_PATH_RX1, + RA_OWNER_PATH_RX2, 0, 0, RA_OWNER_PATH_LB, RA_OWNER_DRV} + }, + /* RA_BLOCK_PA_EAR */ + { + .valid_owners = {RA_OWNER_NONE, RA_OWNER_PATH_RX1, + RA_OWNER_PATH_RX2, 0, 0, RA_OWNER_PATH_LB, RA_OWNER_DRV} + }, + /* RA_BLOCK_PA_HPH */ + { + .valid_owners = {RA_OWNER_NONE, RA_OWNER_PATH_RX1, + RA_OWNER_PATH_RX2, 0, 0, RA_OWNER_PATH_LB, RA_OWNER_DRV} + }, + /* RA_BLOCK_PA_LINE */ + { + .valid_owners = {RA_OWNER_NONE, RA_OWNER_PATH_RX1, + RA_OWNER_PATH_RX2, 0, 0, RA_OWNER_PATH_LB, RA_OWNER_DRV} + }, + /* RA_BLOCK_PA_AUX */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, RA_OWNER_PATH_TX1, + RA_OWNER_PATH_TX2, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_ADC */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, RA_OWNER_PATH_TX1, + RA_OWNER_PATH_TX2, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_DMIC */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, RA_OWNER_PATH_TX1, + RA_OWNER_PATH_TX2, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_TX_I2S */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, 0, 0, 0, RA_OWNER_DRV} + }, + /*RA_BLOCK_DRV */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, 0, 0, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_TEST */ + { + .valid_owners = {RA_OWNER_NONE, 0, 0, 0, 0, 0, RA_OWNER_DRV} + }, + /* RA_BLOCK_RESERVED */ +}; + +struct adie_codec_state { + struct adie_codec_path path[ADIE_CODEC_MAX]; + u32 ref_cnt; + struct marimba *pdrv_ptr; + struct marimba_codec_platform_data *codec_pdata; + struct mutex lock; +}; + +static struct adie_codec_state adie_codec; + +/* A cacheable register is one that if the register's current value is being + * written to it again, then it is permissable to skip that register write + * because it does not actually change the value of the hardware register. + * + * Some registers are uncacheable, meaning that even they are being written + * again with their current value, the write has another purpose and must go + * through. + * + * Knowing the codec's uncacheable registers allows the driver to avoid + * unnecessary codec register writes while making sure important register writes + * are not skipped. + */ + +static bool timpani_register_is_cacheable(u8 reg) +{ + switch (reg) { + case TIMPANI_A_PA_LINE_L_GAIN: + case TIMPANI_A_PA_LINE_R_GAIN: + case TIMPANI_A_PA_HPH_L_GAIN: + case TIMPANI_A_PA_HPH_R_GAIN: + case TIMPANI_A_CDC_GCTL1: + case TIMPANI_A_CDC_ST_CTL: + case TIMPANI_A_CDC_GCTL2: + case TIMPANI_A_CDC_ARB_BYPASS_CTL: + case TIMPANI_A_CDC_CH_CTL: + case TIMPANI_A_CDC_ANC1_IIR_COEFF_PTR: + case TIMPANI_A_CDC_ANC1_IIR_COEFF_MSB: + case TIMPANI_A_CDC_ANC1_IIR_COEFF_LSB: + case TIMPANI_A_CDC_ANC1_LPF_COEFF_PTR: + case TIMPANI_A_CDC_ANC1_LPF_COEFF_MSB: + case TIMPANI_A_CDC_ANC1_LPF_COEFF_LSB: + case TIMPANI_A_CDC_ANC1_SCALE_PTR: + case TIMPANI_A_CDC_ANC1_SCALE: + case TIMPANI_A_CDC_ANC2_IIR_COEFF_PTR: + case TIMPANI_A_CDC_ANC2_IIR_COEFF_MSB: + case TIMPANI_A_CDC_ANC2_IIR_COEFF_LSB: + case TIMPANI_A_CDC_ANC2_LPF_COEFF_PTR: + case TIMPANI_A_CDC_ANC2_LPF_COEFF_MSB: + case TIMPANI_A_CDC_ANC2_LPF_COEFF_LSB: + case TIMPANI_A_CDC_ANC2_SCALE_PTR: + case TIMPANI_A_CDC_ANC2_SCALE: + case TIMPANI_A_CDC_ANC1_CTL1: + case TIMPANI_A_CDC_ANC1_CTL2: + case TIMPANI_A_CDC_ANC1_FF_FB_SHIFT: + case TIMPANI_A_CDC_ANC2_CTL1: + case TIMPANI_A_CDC_ANC2_CTL2: + case TIMPANI_A_CDC_ANC2_FF_FB_SHIFT: + return false; + default: + return true; + } +} + +static int adie_codec_write(u8 reg, u8 mask, u8 val) +{ + int rc = 0; + u8 new_val; + + new_val = (val & mask) | (timpani_shadow[reg] & ~mask); + if (!(timpani_register_is_cacheable(reg) && + (new_val == timpani_shadow[reg]))) { + + rc = marimba_write_bit_mask(adie_codec.pdrv_ptr, reg, &new_val, + 1, 0xFF); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: fail to write reg %x\n", __func__, reg); + rc = -EIO; + goto error; + } + timpani_shadow[reg] = new_val; + pr_debug("%s: write reg %x val %x new value %x\n", __func__, + reg, val, new_val); + } + +error: + return rc; +} + + +static int reg_in_use(u8 reg_ref, u8 path_type) +{ + if ((reg_ref & ~path_type) == 0) + return 0; + else + return 1; +} + +static int adie_codec_refcnt_write(u8 reg, u8 mask, u8 val, enum refcnt cnt, + u8 path_type) +{ + u8 i; + int j; + u8 fld_mask; + u8 path_mask; + u8 reg_mask = 0; + int rc = 0; + + for (i = 0; i < 0xEF; i++) { + if (timpani_regset[i].reg_addr == reg) { + for (j = 0; j < TIMPANI_MAX_FIELDS; j++) { + fld_mask = timpani_regset[i].fld_ref_cnt[j].mask + & mask; + path_mask = timpani_regset[i].fld_ref_cnt[j] + .path_mask; + if (fld_mask) { + if (!reg_in_use(path_mask, path_type)) + reg_mask |= fld_mask; + if (cnt == INC) + timpani_regset[i].fld_ref_cnt[j] + .path_mask |= path_type; + else if (cnt == DEC) + timpani_regset[i].fld_ref_cnt[j] + .path_mask &= + ~path_type; + } + } + + if (reg_mask) + rc = adie_codec_write(reg, reg_mask, val); + reg_mask = 0; + break; + } + } + + return rc; +} + +static int adie_codec_read(u8 reg, u8 *val) +{ + return marimba_read(adie_codec.pdrv_ptr, reg, val, 1); +} + +static int timpani_adie_codec_setpath(struct adie_codec_path *path_ptr, + u32 freq_plan, u32 osr) +{ + int rc = 0; + u32 i, freq_idx = 0, freq = 0; + + if (path_ptr == NULL) + return -EINVAL; + + if (path_ptr->curr_stage != ADIE_CODEC_DIGITAL_OFF) { + rc = -EBUSY; + goto error; + } + + for (i = 0; i < path_ptr->profile->setting_sz; i++) { + if (path_ptr->profile->settings[i].osr == osr) { + if (path_ptr->profile->settings[i].freq_plan >= + freq_plan) { + if (freq == 0) { + freq = path_ptr->profile->settings[i]. + freq_plan; + freq_idx = i; + } else if (path_ptr->profile->settings[i]. + freq_plan < freq) { + freq = path_ptr->profile->settings[i]. + freq_plan; + freq_idx = i; + } + } + } + } + + if (freq_idx >= path_ptr->profile->setting_sz) + rc = -ENODEV; + else { + path_ptr->hwsetting_idx = freq_idx; + path_ptr->stage_idx = 0; + } + +error: + return rc; +} + +static u32 timpani_adie_codec_freq_supported( + struct adie_codec_dev_profile *profile, + u32 requested_freq) +{ + u32 i, rc = -EINVAL; + + for (i = 0; i < profile->setting_sz; i++) { + if (profile->settings[i].freq_plan >= requested_freq) { + rc = 0; + break; + } + } + return rc; +} +int timpani_adie_codec_enable_sidetone(struct adie_codec_path *rx_path_ptr, + u32 enable) +{ + int rc = 0; + + pr_debug("%s()\n", __func__); + + mutex_lock(&adie_codec.lock); + + if (!rx_path_ptr || &adie_codec.path[ADIE_CODEC_RX] != rx_path_ptr) { + pr_err("%s: invalid path pointer\n", __func__); + rc = -EINVAL; + goto error; + } else if (rx_path_ptr->curr_stage != + ADIE_CODEC_DIGITAL_ANALOG_READY) { + pr_err("%s: bad state\n", __func__); + rc = -EPERM; + goto error; + } + + if (enable) { + rc = adie_codec_write(TIMPANI_A_CDC_RX1_CTL, + TIMPANI_RX1_ST_MASK, TIMPANI_RX1_ST_ENABLE); + + if (rx_path_ptr->reg_owner == RA_OWNER_PATH_RX1) + adie_codec_write(TIMPANI_A_CDC_ST_MIXING, + TIMPANI_CDC_ST_MIXING_TX1_MASK, + TIMPANI_CDC_ST_MIXING_TX1_ENABLE); + else if (rx_path_ptr->reg_owner == RA_OWNER_PATH_RX2) + adie_codec_write(TIMPANI_A_CDC_ST_MIXING, + TIMPANI_CDC_ST_MIXING_TX2_MASK, + TIMPANI_CDC_ST_MIXING_TX2_ENABLE); + } else { + rc = adie_codec_write(TIMPANI_A_CDC_RX1_CTL, + TIMPANI_RX1_ST_MASK, 0); + + if (rx_path_ptr->reg_owner == RA_OWNER_PATH_RX1) + adie_codec_write(TIMPANI_A_CDC_ST_MIXING, + TIMPANI_CDC_ST_MIXING_TX1_MASK, 0); + else if (rx_path_ptr->reg_owner == RA_OWNER_PATH_RX2) + adie_codec_write(TIMPANI_A_CDC_ST_MIXING, + TIMPANI_CDC_ST_MIXING_TX2_MASK, 0); + } + +error: + mutex_unlock(&adie_codec.lock); + return rc; +} +static int timpani_adie_codec_enable_anc(struct adie_codec_path *rx_path_ptr, + u32 enable, struct adie_codec_anc_data *calibration_writes) +{ + int index = 0; + int rc = 0; + u8 reg, mask, val; + pr_debug("%s: enable = %d\n", __func__, enable); + + mutex_lock(&adie_codec.lock); + + if (!rx_path_ptr || &adie_codec.path[ADIE_CODEC_RX] != rx_path_ptr) { + pr_err("%s: invalid path pointer\n", __func__); + rc = -EINVAL; + goto error; + } else if (rx_path_ptr->curr_stage != + ADIE_CODEC_DIGITAL_ANALOG_READY) { + pr_err("%s: bad state\n", __func__); + rc = -EPERM; + goto error; + } + if (enable) { + if (!calibration_writes || !calibration_writes->writes) { + pr_err("%s: No ANC calibration data\n", __func__); + rc = -EPERM; + goto error; + } + while (index < calibration_writes->size) { + ADIE_CODEC_UNPACK_ENTRY(calibration_writes-> + writes[index], reg, mask, val); + adie_codec_write(reg, mask, val); + index++; + } + } else { + adie_codec_write(TIMPANI_A_CDC_ANC1_CTL1, + TIMPANI_CDC_ANC1_CTL1_ANC1_EN_M, + TIMPANI_CDC_ANC1_CTL1_ANC1_EN_ANC_DIS << + TIMPANI_CDC_ANC1_CTL1_ANC1_EN_S); + + adie_codec_write(TIMPANI_A_CDC_ANC2_CTL1, + TIMPANI_CDC_ANC2_CTL1_ANC2_EN_M, + TIMPANI_CDC_ANC2_CTL1_ANC2_EN_ANC_DIS << + TIMPANI_CDC_ANC2_CTL1_ANC2_EN_S); + } + +error: + mutex_unlock(&adie_codec.lock); + return rc; +} + +static void adie_codec_restore_regdefault(u8 path_mask, u32 blk) +{ + u32 ireg; + u32 regset_sz = + (sizeof(timpani_regset)/sizeof(struct timpani_regaccess)); + + for (ireg = 0; ireg < regset_sz; ireg++) { + if (timpani_regset[ireg].blk_mask[blk]) { + /* only process register belong to the block */ + u8 reg = timpani_regset[ireg].reg_addr; + u8 mask = timpani_regset[ireg].blk_mask[blk]; + u8 val = timpani_regset[ireg].reg_default; + adie_codec_refcnt_write(reg, mask, val, IGNORE, + path_mask); + } + } +} + +static void adie_codec_reach_stage_action(struct adie_codec_path *path_ptr, + u32 stage) +{ + u32 iblk, iowner; /* iterators */ + u8 path_mask; + + if (path_ptr == NULL) + return; + + path_mask = TIMPANI_PATH_MASK(path_ptr->reg_owner); + + if (stage != ADIE_CODEC_DIGITAL_OFF) + return; + + for (iblk = 0 ; iblk <= RA_BLOCK_RESERVED ; iblk++) { + for (iowner = 0; iowner < RA_OWNER_NUM; iowner++) { + if (timpani_blkcfg[iblk].valid_owners[iowner] == + path_ptr->reg_owner) { + adie_codec_restore_regdefault(path_mask, iblk); + break; /* This path owns this block */ + } + } + } +} + +static int timpani_adie_codec_proceed_stage(struct adie_codec_path *path_ptr, + u32 state) +{ + int rc = 0, loop_exit = 0; + struct adie_codec_action_unit *curr_action; + struct adie_codec_hwsetting_entry *setting; + u8 reg, mask, val; + u8 path_mask; + + if (path_ptr == NULL) + return -EINVAL; + + path_mask = TIMPANI_PATH_MASK(path_ptr->reg_owner); + + mutex_lock(&adie_codec.lock); + setting = &path_ptr->profile->settings[path_ptr->hwsetting_idx]; + while (!loop_exit) { + + curr_action = &setting->actions[path_ptr->stage_idx]; + + switch (curr_action->type) { + case ADIE_CODEC_ACTION_ENTRY: + ADIE_CODEC_UNPACK_ENTRY(curr_action->action, + reg, mask, val); + if (state == ADIE_CODEC_DIGITAL_OFF) + adie_codec_refcnt_write(reg, mask, val, DEC, + path_mask); + else + adie_codec_refcnt_write(reg, mask, val, INC, + path_mask); + break; + case ADIE_CODEC_ACTION_DELAY_WAIT: + if (curr_action->action > MAX_MDELAY_US) + msleep(curr_action->action/1000); + else + usleep_range(curr_action->action, + curr_action->action); + break; + case ADIE_CODEC_ACTION_STAGE_REACHED: + adie_codec_reach_stage_action(path_ptr, + curr_action->action); + if (curr_action->action == state) { + path_ptr->curr_stage = state; + loop_exit = 1; + } + break; + default: + BUG(); + } + + path_ptr->stage_idx++; + if (path_ptr->stage_idx == setting->action_sz) + path_ptr->stage_idx = 0; + } + mutex_unlock(&adie_codec.lock); + + return rc; +} + +static void timpani_codec_bring_up(void) +{ + /* Codec power up sequence */ + adie_codec_write(0xFF, 0xFF, 0x08); + adie_codec_write(0xFF, 0xFF, 0x0A); + adie_codec_write(0xFF, 0xFF, 0x0E); + adie_codec_write(0xFF, 0xFF, 0x07); + adie_codec_write(0xFF, 0xFF, 0x17); + adie_codec_write(TIMPANI_A_MREF, 0xFF, 0xF2); + msleep(15); + adie_codec_write(TIMPANI_A_MREF, 0xFF, 0x22); + + /* Bypass TX HPFs to prevent pops */ + adie_codec_write(TIMPANI_A_CDC_BYPASS_CTL2, TIMPANI_CDC_BYPASS_CTL2_M, + TIMPANI_CDC_BYPASS_CTL2_POR); + adie_codec_write(TIMPANI_A_CDC_BYPASS_CTL3, TIMPANI_CDC_BYPASS_CTL3_M, + TIMPANI_CDC_BYPASS_CTL3_POR); +} + +static void timpani_codec_bring_down(void) +{ + adie_codec_write(TIMPANI_A_MREF, 0xFF, TIMPANI_MREF_POR); + adie_codec_write(0xFF, 0xFF, 0x07); + adie_codec_write(0xFF, 0xFF, 0x06); + adie_codec_write(0xFF, 0xFF, 0x0E); + adie_codec_write(0xFF, 0xFF, 0x08); +} + +static int timpani_adie_codec_open(struct adie_codec_dev_profile *profile, + struct adie_codec_path **path_pptr) +{ + int rc = 0; + + mutex_lock(&adie_codec.lock); + + if (!profile || !path_pptr) { + rc = -EINVAL; + goto error; + } + + if (adie_codec.path[profile->path_type].profile) { + rc = -EBUSY; + goto error; + } + + if (!adie_codec.ref_cnt) { + + if (adie_codec.codec_pdata && + adie_codec.codec_pdata->marimba_codec_power) { + + rc = adie_codec.codec_pdata->marimba_codec_power(1); + if (rc) { + pr_err("%s: could not power up timpani " + "codec\n", __func__); + goto error; + } + timpani_codec_bring_up(); + } else { + pr_err("%s: couldn't detect timpani codec\n", __func__); + rc = -ENODEV; + goto error; + } + + } + + adie_codec.path[profile->path_type].profile = profile; + *path_pptr = (void *) &adie_codec.path[profile->path_type]; + adie_codec.ref_cnt++; + adie_codec.path[profile->path_type].hwsetting_idx = 0; + adie_codec.path[profile->path_type].curr_stage = ADIE_CODEC_DIGITAL_OFF; + adie_codec.path[profile->path_type].stage_idx = 0; + + +error: + mutex_unlock(&adie_codec.lock); + return rc; +} + +static int timpani_adie_codec_close(struct adie_codec_path *path_ptr) +{ + int rc = 0; + + mutex_lock(&adie_codec.lock); + + if (!path_ptr) { + rc = -EINVAL; + goto error; + } + if (path_ptr->curr_stage != ADIE_CODEC_DIGITAL_OFF) + adie_codec_proceed_stage(path_ptr, ADIE_CODEC_DIGITAL_OFF); + + BUG_ON(!adie_codec.ref_cnt); + + path_ptr->profile = NULL; + adie_codec.ref_cnt--; + + if (!adie_codec.ref_cnt) { + /* Timpani CDC power down sequence */ + timpani_codec_bring_down(); + + if (adie_codec.codec_pdata && + adie_codec.codec_pdata->marimba_codec_power) { + + rc = adie_codec.codec_pdata->marimba_codec_power(0); + if (rc) { + pr_err("%s: could not power down timpani " + "codec\n", __func__); + goto error; + } + } + } + +error: + mutex_unlock(&adie_codec.lock); + return rc; +} + +static int timpani_adie_codec_set_master_mode(struct adie_codec_path *path_ptr, + u8 master) +{ + u8 val = master ? 1 : 0; + + if (!path_ptr) + return -EINVAL; + + if (path_ptr->reg_owner == RA_OWNER_PATH_RX1) + adie_codec_write(TIMPANI_A_CDC_RX1_CTL, 0x01, val); + else if (path_ptr->reg_owner == RA_OWNER_PATH_TX1) + adie_codec_write(TIMPANI_A_CDC_TX_I2S_CTL, 0x01, val); + else + return -EINVAL; + + return 0; +} + +int timpani_adie_codec_set_device_analog_volume( + struct adie_codec_path *path_ptr, + u32 num_channels, u32 volume) +{ + u8 val; + u8 curr_val; + u8 i; + + adie_codec_read(TIMPANI_A_AUXPGA_LR_GAIN, &curr_val); + + /* Volume is expressed as a percentage. */ + /* The upper nibble is the left channel, lower right channel. */ + val = (u8)((volume * TIMPANI_CODEC_AUXPGA_GAIN_RANGE) / 100); + val |= val << 4; + + if ((curr_val & 0x0F) < (val & 0x0F)) { + for (i = curr_val; i < val; i += 0x11) + adie_codec_write(TIMPANI_A_AUXPGA_LR_GAIN, 0xFF, i); + } else if ((curr_val & 0x0F) > (val & 0x0F)) { + for (i = curr_val; i > val; i -= 0x11) + adie_codec_write(TIMPANI_A_AUXPGA_LR_GAIN, 0xFF, i); + } + + return 0; +} + +enum adie_vol_type { + ADIE_CODEC_RX_DIG_VOL, + ADIE_CODEC_TX_DIG_VOL, + ADIE_CODEC_VOL_TYPE_MAX +}; + +#define CDC_RX1LG 0x84 +#define CDC_RX1RG 0x85 +#define CDC_TX1LG 0x86 +#define CDC_TX1RG 0x87 +#define DIG_VOL_MASK 0xFF + +#define CDC_GCTL1 0x8A +#define RX1_PGA_UPDATE_L 0x04 +#define RX1_PGA_UPDATE_R 0x08 +#define TX1_PGA_UPDATE_L 0x40 +#define TX1_PGA_UPDATE_R 0x80 +#define CDC_GCTL1_RX_MASK 0x0F +#define CDC_GCTL1_TX_MASK 0xF0 + +enum { + TIMPANI_MIN_DIG_VOL = -84, /* in DB*/ + TIMPANI_MAX_DIG_VOL = 16, /* in DB*/ + TIMPANI_DIG_VOL_STEP = 3 /* in DB*/ +}; + +static int timpani_adie_codec_set_dig_vol(enum adie_vol_type vol_type, + u32 num_chan, u32 vol_per) +{ + u8 reg_left, reg_right; + u8 gain_reg_val, gain_reg_mask; + s8 new_reg_val, cur_reg_val; + s8 step_size; + + adie_codec_read(CDC_GCTL1, &gain_reg_val); + + if (vol_type == ADIE_CODEC_RX_DIG_VOL) { + + pr_debug("%s : RX DIG VOL. num_chan = %u\n", __func__, + num_chan); + reg_left = CDC_RX1LG; + reg_right = CDC_RX1RG; + + if (num_chan == 1) + gain_reg_val |= RX1_PGA_UPDATE_L; + else + gain_reg_val |= (RX1_PGA_UPDATE_L | RX1_PGA_UPDATE_R); + + gain_reg_mask = CDC_GCTL1_RX_MASK; + } else { + + pr_debug("%s : TX DIG VOL. num_chan = %u\n", __func__, + num_chan); + reg_left = CDC_TX1LG; + reg_right = CDC_TX1RG; + + if (num_chan == 1) + gain_reg_val |= TX1_PGA_UPDATE_L; + else + gain_reg_val |= (TX1_PGA_UPDATE_L | TX1_PGA_UPDATE_R); + + gain_reg_mask = CDC_GCTL1_TX_MASK; + } + + adie_codec_read(reg_left, &cur_reg_val); + + pr_debug("%s: vol_per = %d cur_reg_val = %d 0x%x\n", __func__, vol_per, + cur_reg_val, cur_reg_val); + + new_reg_val = TIMPANI_MIN_DIG_VOL + + (((TIMPANI_MAX_DIG_VOL - TIMPANI_MIN_DIG_VOL) * vol_per) / 100); + + pr_debug("new_reg_val = %d 0x%x\n", new_reg_val, new_reg_val); + + if (new_reg_val > cur_reg_val) { + step_size = TIMPANI_DIG_VOL_STEP; + } else if (new_reg_val < cur_reg_val) { + step_size = -TIMPANI_DIG_VOL_STEP; + } else { + pr_debug("new_reg_val and cur_reg_val are same 0x%x\n", + new_reg_val); + return 0; + } + + while (cur_reg_val != new_reg_val) { + + if (((new_reg_val > cur_reg_val) && + ((new_reg_val - cur_reg_val) < TIMPANI_DIG_VOL_STEP)) || + ((cur_reg_val > new_reg_val) && + ((cur_reg_val - new_reg_val) + < TIMPANI_DIG_VOL_STEP))) { + + cur_reg_val = new_reg_val; + + pr_debug("diff less than step. write new_reg_val = %d" + " 0x%x\n", new_reg_val, new_reg_val); + + } else { + cur_reg_val = cur_reg_val + step_size; + + pr_debug("cur_reg_val = %d 0x%x\n", + cur_reg_val, cur_reg_val); + } + + adie_codec_write(reg_left, DIG_VOL_MASK, cur_reg_val); + + if (num_chan == 2) + adie_codec_write(reg_right, DIG_VOL_MASK, cur_reg_val); + + adie_codec_write(CDC_GCTL1, gain_reg_mask, gain_reg_val); + } + return 0; +} + +static int timpani_adie_codec_set_device_digital_volume( + struct adie_codec_path *path_ptr, + u32 num_channels, u32 vol_percentage /* in percentage */) +{ + enum adie_vol_type vol_type; + + if (!path_ptr || (path_ptr->curr_stage != + ADIE_CODEC_DIGITAL_ANALOG_READY)) { + pr_info("%s: timpani codec not ready for volume control\n", + __func__); + return -EPERM; + } + + if (num_channels > 2) { + pr_err("%s: timpani odec only supports max two channels\n", + __func__); + return -EINVAL; + } + + if (path_ptr->profile->path_type == ADIE_CODEC_RX) { + vol_type = ADIE_CODEC_RX_DIG_VOL; + } else if (path_ptr->profile->path_type == ADIE_CODEC_TX) { + vol_type = ADIE_CODEC_TX_DIG_VOL; + } else { + pr_err("%s: invalid device data neither RX nor TX\n", + __func__); + return -EINVAL; + } + + timpani_adie_codec_set_dig_vol(vol_type, num_channels, vol_percentage); + + return 0; +} + +static const struct adie_codec_operations timpani_adie_ops = { + .codec_id = TIMPANI_ID, + .codec_open = timpani_adie_codec_open, + .codec_close = timpani_adie_codec_close, + .codec_setpath = timpani_adie_codec_setpath, + .codec_proceed_stage = timpani_adie_codec_proceed_stage, + .codec_freq_supported = timpani_adie_codec_freq_supported, + .codec_enable_sidetone = timpani_adie_codec_enable_sidetone, + .codec_set_master_mode = timpani_adie_codec_set_master_mode, + .codec_enable_anc = timpani_adie_codec_enable_anc, + .codec_set_device_analog_volume = + timpani_adie_codec_set_device_analog_volume, + .codec_set_device_digital_volume = + timpani_adie_codec_set_device_digital_volume, +}; + +static void timpani_codec_populate_shadow_registers(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(timpani_regset); i++) { + if (timpani_regset[i].reg_addr < TIMPANI_ARRAY_SIZE) { + timpani_shadow[timpani_regset[i].reg_addr] = + timpani_regset[i].reg_default; + } + } +} + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_timpani_dent; +static struct dentry *debugfs_peek; +static struct dentry *debugfs_poke; +static struct dentry *debugfs_power; +static struct dentry *debugfs_dump; + +static unsigned char read_data; + +static int codec_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static int get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (strict_strtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } + else + return -EINVAL; + } + return 0; +} + +static ssize_t codec_debug_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char lbuf[8]; + + snprintf(lbuf, sizeof(lbuf), "0x%x\n", read_data); + return simple_read_from_buffer(ubuf, count, ppos, lbuf, strlen(lbuf)); +} + +static ssize_t codec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *access_str = filp->private_data; + char lbuf[32]; + int rc; + int i; + int read_result; + u8 reg_val; + long int param[5]; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + + if (!strcmp(access_str, "power")) { + if (get_parameters(lbuf, param, 1) == 0) { + switch (param[0]) { + case 1: + adie_codec.codec_pdata->marimba_codec_power(1); + timpani_codec_bring_up(); + break; + case 0: + timpani_codec_bring_down(); + adie_codec.codec_pdata->marimba_codec_power(0); + break; + default: + rc = -EINVAL; + break; + } + } else + rc = -EINVAL; + } else if (!strcmp(access_str, "poke")) { + /* write */ + rc = get_parameters(lbuf, param, 2); + if ((param[0] <= 0xFF) && (param[1] <= 0xFF) && + (rc == 0)) + adie_codec_write(param[0], 0xFF, param[1]); + else + rc = -EINVAL; + } else if (!strcmp(access_str, "peek")) { + /* read */ + rc = get_parameters(lbuf, param, 1); + if ((param[0] <= 0xFF) && (rc == 0)) + adie_codec_read(param[0], &read_data); + else + rc = -EINVAL; + } else if (!strcmp(access_str, "dump")) { + pr_info("************** timpani regs *************\n"); + for (i = 0; i < 0xFF; i++) { + read_result = adie_codec_read(i, ®_val); + if (read_result < 0) { + pr_info("failed to read codec register\n"); + break; + } else + pr_info("reg 0x%02X val 0x%02X\n", i, reg_val); + } + pr_info("*****************************************\n"); + } + + if (rc == 0) + rc = cnt; + else + pr_err("%s: rc = %d\n", __func__, rc); + + return rc; +} + +static const struct file_operations codec_debug_ops = { + .open = codec_debug_open, + .write = codec_debug_write, + .read = codec_debug_read +}; +#endif + +static int timpani_codec_probe(struct platform_device *pdev) +{ + int rc; + + adie_codec.pdrv_ptr = platform_get_drvdata(pdev); + adie_codec.codec_pdata = pdev->dev.platform_data; + + if (adie_codec.codec_pdata->snddev_profile_init) + adie_codec.codec_pdata->snddev_profile_init(); + + timpani_codec_populate_shadow_registers(); + + /* Register the timpani ADIE operations */ + rc = adie_codec_register_codec_operations(&timpani_adie_ops); + +#ifdef CONFIG_DEBUG_FS + debugfs_timpani_dent = debugfs_create_dir("msm_adie_codec", 0); + if (!IS_ERR(debugfs_timpani_dent)) { + debugfs_peek = debugfs_create_file("peek", + S_IFREG | S_IRUGO, debugfs_timpani_dent, + (void *) "peek", &codec_debug_ops); + + debugfs_poke = debugfs_create_file("poke", + S_IFREG | S_IRUGO, debugfs_timpani_dent, + (void *) "poke", &codec_debug_ops); + + debugfs_power = debugfs_create_file("power", + S_IFREG | S_IRUGO, debugfs_timpani_dent, + (void *) "power", &codec_debug_ops); + + debugfs_dump = debugfs_create_file("dump", + S_IFREG | S_IRUGO, debugfs_timpani_dent, + (void *) "dump", &codec_debug_ops); + + } +#endif + + return rc; +} + +static struct platform_driver timpani_codec_driver = { + .probe = timpani_codec_probe, + .driver = { + .name = "timpani_codec", + .owner = THIS_MODULE, + }, +}; + +static int __init timpani_codec_init(void) +{ + s32 rc; + + rc = platform_driver_register(&timpani_codec_driver); + if (IS_ERR_VALUE(rc)) + goto error; + + adie_codec.path[ADIE_CODEC_TX].reg_owner = RA_OWNER_PATH_TX1; + adie_codec.path[ADIE_CODEC_RX].reg_owner = RA_OWNER_PATH_RX1; + adie_codec.path[ADIE_CODEC_LB].reg_owner = RA_OWNER_PATH_LB; + mutex_init(&adie_codec.lock); +error: + return rc; +} + +static void __exit timpani_codec_exit(void) +{ +#ifdef CONFIG_DEBUG_FS + debugfs_remove(debugfs_peek); + debugfs_remove(debugfs_poke); + debugfs_remove(debugfs_power); + debugfs_remove(debugfs_dump); + debugfs_remove(debugfs_timpani_dent); +#endif + platform_driver_unregister(&timpani_codec_driver); +} + +module_init(timpani_codec_init); +module_exit(timpani_codec_exit); + +MODULE_DESCRIPTION("Timpani codec driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/tps65023.c b/drivers/mfd/tps65023.c new file mode 100644 index 00000000000..e67997cc361 --- /dev/null +++ b/drivers/mfd/tps65023.c @@ -0,0 +1,122 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + +/* TPS65023_registers */ +#define TPS65023_VERSION 0 +#define TPS65023_PGOODZ 1 +#define TPS65023_MASK 2 +#define TPS65023_REG_CTRL 3 +#define TPS65023_CON_CTRL 4 +#define TPS65023_CON_CTRL2 5 +#define TPS65023_DEFCORE 6 +#define TPS65023_DEFSLEW 7 +#define TPS65023_LDO_CTRL 8 +#define TPS65023_MAX 9 + +static struct i2c_client *tpsclient; + +int tps65023_set_dcdc1_level(int mvolts) +{ + int val; + int ret; + + if (!tpsclient) + return -ENODEV; + + if (mvolts < 800 || mvolts > 1600) + return -EINVAL; + + if (mvolts == 1600) + val = 0x1F; + else + val = ((mvolts - 800)/25) & 0x1F; + + ret = i2c_smbus_write_byte_data(tpsclient, TPS65023_DEFCORE, val); + + if (!ret) + ret = i2c_smbus_write_byte_data(tpsclient, + TPS65023_CON_CTRL2, 0x80); + + return ret; +} +EXPORT_SYMBOL(tps65023_set_dcdc1_level); + +int tps65023_get_dcdc1_level(int *mvolts) +{ + int val; + + if (!tpsclient) + return -ENODEV; + + val = i2c_smbus_read_byte_data(tpsclient, TPS65023_DEFCORE) & 0x1F; + + if (val == 0x1F) + *mvolts = 1600; + else + *mvolts = (val * 25) + 800; + return 0; +} +EXPORT_SYMBOL(tps65023_get_dcdc1_level); + +static int tps65023_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + printk(KERN_ERR "TPS65023 does not support SMBUS_BYTE_DATA.\n"); + return -EINVAL; + } + + tpsclient = client; + printk(KERN_INFO "TPS65023: PMIC probed.\n"); + return 0; +} + +static int __devexit tps65023_remove(struct i2c_client *client) +{ + tpsclient = NULL; + return 0; +} + +static const struct i2c_device_id tps65023_id[] = { + { "tps65023", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps65023_id); + +static struct i2c_driver tps65023_driver = { + .driver = { + .name = "tps65023", + .owner = THIS_MODULE, + }, + .probe = tps65023_probe, + .remove = __devexit_p(tps65023_remove), + .id_table = tps65023_id, +}; + +static int __init tps65023_init(void) +{ + return i2c_add_driver(&tps65023_driver); +} + + +static void __exit tps65023_exit(void) +{ + i2c_del_driver(&tps65023_driver); +} + +module_init(tps65023_init); +module_exit(tps65023_exit); diff --git a/drivers/mfd/wcd9310-core.c b/drivers/mfd/wcd9310-core.c new file mode 100644 index 00000000000..5a777850b94 --- /dev/null +++ b/drivers/mfd/wcd9310-core.c @@ -0,0 +1,734 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TABLA_REGISTER_START_OFFSET 0x800 +static int tabla_read(struct tabla *tabla, unsigned short reg, + int bytes, void *dest, bool interface_reg) +{ + int ret; + u8 *buf = dest; + + if (bytes <= 0) { + dev_err(tabla->dev, "Invalid byte read length %d\n", bytes); + return -EINVAL; + } + + ret = tabla->read_dev(tabla, reg, bytes, dest, interface_reg); + if (ret < 0) { + dev_err(tabla->dev, "Tabla read failed\n"); + return ret; + } else + dev_dbg(tabla->dev, "Read 0x%02x from R%d(0x%x)\n", + *buf, reg, reg); + + return 0; +} +int tabla_reg_read(struct tabla *tabla, unsigned short reg) +{ + u8 val; + int ret; + + mutex_lock(&tabla->io_lock); + ret = tabla_read(tabla, reg, 1, &val, false); + mutex_unlock(&tabla->io_lock); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(tabla_reg_read); + +static int tabla_write(struct tabla *tabla, unsigned short reg, + int bytes, void *src, bool interface_reg) +{ + u8 *buf = src; + + if (bytes <= 0) { + pr_err("%s: Error, invalid write length\n", __func__); + return -EINVAL; + } + + dev_dbg(tabla->dev, "Write %02x to R%d(0x%x)\n", + *buf, reg, reg); + + return tabla->write_dev(tabla, reg, bytes, src, interface_reg); +} + +int tabla_reg_write(struct tabla *tabla, unsigned short reg, + u8 val) +{ + int ret; + + mutex_lock(&tabla->io_lock); + ret = tabla_write(tabla, reg, 1, &val, false); + mutex_unlock(&tabla->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(tabla_reg_write); + +static u8 tabla_pgd_la; +static u8 tabla_inf_la; + +int tabla_get_logical_addresses(u8 *pgd_la, u8 *inf_la) +{ + *pgd_la = tabla_pgd_la; + *inf_la = tabla_inf_la; + return 0; + +} +EXPORT_SYMBOL_GPL(tabla_get_logical_addresses); + +int tabla_interface_reg_read(struct tabla *tabla, unsigned short reg) +{ + u8 val; + int ret; + + mutex_lock(&tabla->io_lock); + ret = tabla_read(tabla, reg, 1, &val, true); + mutex_unlock(&tabla->io_lock); + + if (ret < 0) + return ret; + else + return val; +} +EXPORT_SYMBOL_GPL(tabla_interface_reg_read); + +int tabla_interface_reg_write(struct tabla *tabla, unsigned short reg, + u8 val) +{ + int ret; + + mutex_lock(&tabla->io_lock); + ret = tabla_write(tabla, reg, 1, &val, true); + mutex_unlock(&tabla->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(tabla_interface_reg_write); + +int tabla_bulk_read(struct tabla *tabla, unsigned short reg, + int count, u8 *buf) +{ + int ret; + + mutex_lock(&tabla->io_lock); + + ret = tabla_read(tabla, reg, count, buf, false); + + mutex_unlock(&tabla->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(tabla_bulk_read); + +int tabla_bulk_write(struct tabla *tabla, unsigned short reg, + int count, u8 *buf) +{ + int ret; + + mutex_lock(&tabla->io_lock); + + ret = tabla_write(tabla, reg, count, buf, false); + + mutex_unlock(&tabla->io_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(tabla_bulk_write); + +static int tabla_slim_read_device(struct tabla *tabla, unsigned short reg, + int bytes, void *dest, bool interface) +{ + int ret; + struct slim_ele_access msg; + msg.start_offset = TABLA_REGISTER_START_OFFSET + reg; + msg.num_bytes = bytes; + msg.comp = NULL; + + mutex_lock(&tabla->xfer_lock); + if (interface) + ret = slim_request_val_element(tabla->slim_slave, &msg, dest, + bytes); + else + ret = slim_request_val_element(tabla->slim, &msg, dest, bytes); + + if (ret) + pr_err("%s: Error, Tabla read failed\n", __func__); + + mutex_unlock(&tabla->xfer_lock); + return ret; +} +/* Interface specifies whether the write is to the interface or general + * registers. + */ +static int tabla_slim_write_device(struct tabla *tabla, unsigned short reg, + int bytes, void *src, bool interface) +{ + int ret; + struct slim_ele_access msg; + msg.start_offset = TABLA_REGISTER_START_OFFSET + reg; + msg.num_bytes = bytes; + msg.comp = NULL; + + mutex_lock(&tabla->xfer_lock); + if (interface) + ret = slim_change_val_element(tabla->slim_slave, &msg, src, + bytes); + else + ret = slim_change_val_element(tabla->slim, &msg, src, bytes); + if (ret) + pr_err("%s: Error, Tabla write failed\n", __func__); + + mutex_unlock(&tabla->xfer_lock); + return ret; +} + +static struct mfd_cell tabla_devs[] = { + { + .name = "tabla_codec", + }, +}; + +static void tabla_bring_up(struct tabla *tabla) +{ + tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0x4); + tabla_reg_write(tabla, TABLA_A_CDC_CTL, 0); + usleep_range(5000, 5000); + tabla_reg_write(tabla, TABLA_A_CDC_CTL, 3); + tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 3); +} + +static void tabla_bring_down(struct tabla *tabla) +{ + tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0x7); + tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0x6); + tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0xe); + tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0x8); +} + +static int tabla_reset(struct tabla *tabla) +{ + int ret; + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + }; + + if (tabla->reset_gpio) { + ret = gpio_request(tabla->reset_gpio, "CDC_RESET"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + tabla->reset_gpio); + tabla->reset_gpio = 0; + return ret; + } + + ret = pm8xxx_gpio_config(tabla->reset_gpio, ¶m); + if (ret) + pr_err("%s: Failed to configure gpio\n", __func__); + + gpio_direction_output(tabla->reset_gpio, 1); + msleep(20); + gpio_direction_output(tabla->reset_gpio, 0); + msleep(20); + gpio_direction_output(tabla->reset_gpio, 1); + msleep(20); + } + return 0; +} + +static void tabla_free_reset(struct tabla *tabla) +{ + if (tabla->reset_gpio) { + gpio_free(tabla->reset_gpio); + tabla->reset_gpio = 0; + } +} + +struct tabla_regulator { + const char *name; + int min_uV; + int max_uV; + int optimum_uA; + struct regulator *regulator; +}; + + +/* + * format : TABLA__CUR_MAX + * + * from Tabla objective spec +*/ + +#define TABLA_CDC_VDDA_CP_CUR_MAX 500000 +#define TABLA_CDC_VDDA_RX_CUR_MAX 20000 +#define TABLA_CDC_VDDA_TX_CUR_MAX 20000 +#define TABLA_VDDIO_CDC_CUR_MAX 5000 + +#define TABLA_VDDD_CDC_D_CUR_MAX 5000 +#define TABLA_VDDD_CDC_A_CUR_MAX 5000 + +static struct tabla_regulator tabla_regulators[] = { + { + .name = "CDC_VDD_CP", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = TABLA_CDC_VDDA_CP_CUR_MAX, + }, + { + .name = "CDC_VDDA_RX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = TABLA_CDC_VDDA_RX_CUR_MAX, + }, + { + .name = "CDC_VDDA_TX", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = TABLA_CDC_VDDA_TX_CUR_MAX, + }, + { + .name = "VDDIO_CDC", + .min_uV = 1800000, + .max_uV = 1800000, + .optimum_uA = TABLA_VDDIO_CDC_CUR_MAX, + }, + { + .name = "VDDD_CDC_D", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = TABLA_VDDD_CDC_D_CUR_MAX, + }, + { + .name = "CDC_VDDA_A_1P2V", + .min_uV = 1225000, + .max_uV = 1225000, + .optimum_uA = TABLA_VDDD_CDC_A_CUR_MAX, + }, +}; + +static int tabla_device_init(struct tabla *tabla, int irq) +{ + int ret; + + mutex_init(&tabla->io_lock); + mutex_init(&tabla->xfer_lock); + dev_set_drvdata(tabla->dev, tabla); + + tabla_bring_up(tabla); + + ret = tabla_irq_init(tabla); + if (ret) { + pr_err("IRQ initialization failed\n"); + goto err; + } + + ret = mfd_add_devices(tabla->dev, -1, + tabla_devs, ARRAY_SIZE(tabla_devs), + NULL, 0); + if (ret != 0) { + dev_err(tabla->dev, "Failed to add children: %d\n", ret); + goto err_irq; + } + + return ret; +err_irq: + tabla_irq_exit(tabla); +err: + tabla_bring_down(tabla); + mutex_destroy(&tabla->io_lock); + mutex_destroy(&tabla->xfer_lock); + return ret; +} +static void tabla_device_exit(struct tabla *tabla) +{ + tabla_irq_exit(tabla); + tabla_bring_down(tabla); + tabla_free_reset(tabla); + mutex_destroy(&tabla->io_lock); + mutex_destroy(&tabla->xfer_lock); + slim_remove_device(tabla->slim_slave); + kfree(tabla); +} + + +#ifdef CONFIG_DEBUG_FS +struct tabla *debugTabla; + +static struct dentry *debugfs_tabla_dent; +static struct dentry *debugfs_peek; +static struct dentry *debugfs_poke; + +static unsigned char read_data; + +static int codec_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static int get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (strict_strtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } else + return -EINVAL; + } + return 0; +} + +static ssize_t codec_debug_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char lbuf[8]; + + snprintf(lbuf, sizeof(lbuf), "0x%x\n", read_data); + return simple_read_from_buffer(ubuf, count, ppos, lbuf, + strnlen(lbuf, 7)); +} + + +static ssize_t codec_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *access_str = filp->private_data; + char lbuf[32]; + int rc; + long int param[5]; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + + if (!strncmp(access_str, "poke", 6)) { + /* write */ + rc = get_parameters(lbuf, param, 2); + if ((param[0] <= 0x3FF) && (param[1] <= 0xFF) && + (rc == 0)) + tabla_interface_reg_write(debugTabla, param[0], + param[1]); + else + rc = -EINVAL; + } else if (!strncmp(access_str, "peek", 6)) { + /* read */ + rc = get_parameters(lbuf, param, 1); + if ((param[0] <= 0x3FF) && (rc == 0)) + read_data = tabla_interface_reg_read(debugTabla, + param[0]); + else + rc = -EINVAL; + } + + if (rc == 0) + rc = cnt; + else + pr_err("%s: rc = %d\n", __func__, rc); + + return rc; +} + +static const struct file_operations codec_debug_ops = { + .open = codec_debug_open, + .write = codec_debug_write, + .read = codec_debug_read +}; +#endif + +static int tabla_enable_supplies(struct tabla *tabla) +{ + int ret; + int i; + + tabla->supplies = kzalloc(sizeof(struct regulator_bulk_data) * + ARRAY_SIZE(tabla_regulators), + GFP_KERNEL); + if (!tabla->supplies) { + ret = -ENOMEM; + goto err; + } + + for (i = 0; i < ARRAY_SIZE(tabla_regulators); i++) + tabla->supplies[i].supply = tabla_regulators[i].name; + + ret = regulator_bulk_get(tabla->dev, ARRAY_SIZE(tabla_regulators), + tabla->supplies); + if (ret != 0) { + dev_err(tabla->dev, "Failed to get supplies: err = %d\n", ret); + goto err_supplies; + } + + for (i = 0; i < ARRAY_SIZE(tabla_regulators); i++) { + ret = regulator_set_voltage(tabla->supplies[i].consumer, + tabla_regulators[i].min_uV, tabla_regulators[i].max_uV); + if (ret) { + pr_err("%s: Setting regulator voltage failed for " + "regulator %s err = %d\n", __func__, + tabla->supplies[i].supply, ret); + goto err_get; + } + + ret = regulator_set_optimum_mode(tabla->supplies[i].consumer, + tabla_regulators[i].optimum_uA); + if (ret < 0) { + pr_err("%s: Setting regulator optimum mode failed for " + "regulator %s err = %d\n", __func__, + tabla->supplies[i].supply, ret); + goto err_get; + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(tabla_regulators), + tabla->supplies); + if (ret != 0) { + dev_err(tabla->dev, "Failed to enable supplies: err = %d\n", + ret); + goto err_configure; + } + return ret; + +err_configure: + for (i = 0; i < ARRAY_SIZE(tabla_regulators); i++) { + regulator_set_voltage(tabla->supplies[i].consumer, 0, + tabla_regulators[i].max_uV); + regulator_set_optimum_mode(tabla->supplies[i].consumer, 0); + } +err_get: + regulator_bulk_free(ARRAY_SIZE(tabla_regulators), tabla->supplies); +err_supplies: + kfree(tabla->supplies); +err: + return ret; +} + +static void tabla_disable_supplies(struct tabla *tabla) +{ + int i; + + regulator_bulk_disable(ARRAY_SIZE(tabla_regulators), + tabla->supplies); + for (i = 0; i < ARRAY_SIZE(tabla_regulators); i++) { + regulator_set_voltage(tabla->supplies[i].consumer, 0, + tabla_regulators[i].max_uV); + regulator_set_optimum_mode(tabla->supplies[i].consumer, 0); + } + regulator_bulk_free(ARRAY_SIZE(tabla_regulators), tabla->supplies); + kfree(tabla->supplies); +} + +static int tabla_slim_probe(struct slim_device *slim) +{ + struct tabla *tabla; + struct tabla_pdata *pdata; + int ret = 0; + + pdata = slim->dev.platform_data; + + if (!pdata) { + dev_err(&slim->dev, "Error, no platform data\n"); + ret = -EINVAL; + goto err; + } + + tabla = kzalloc(sizeof(struct tabla), GFP_KERNEL); + if (tabla == NULL) { + pr_err("%s: error, allocation failed\n", __func__); + ret = -ENOMEM; + goto err; + } + if (!slim->ctrl) { + pr_err("Error, no SLIMBUS control data\n"); + ret = -EINVAL; + goto err_tabla; + } + tabla->slim = slim; + slim_set_clientdata(slim, tabla); + tabla->reset_gpio = pdata->reset_gpio; + tabla->dev = &slim->dev; + + ret = tabla_enable_supplies(tabla); + if (ret) { + pr_info("%s: Fail to enable Tabla supplies\n", __func__); + goto err_tabla; + } + usleep_range(5, 5); + + ret = tabla_reset(tabla); + if (ret) { + pr_err("%s: Resetting Tabla failed\n", __func__); + goto err_supplies; + } + + ret = slim_get_logical_addr(tabla->slim, tabla->slim->e_addr, + ARRAY_SIZE(tabla->slim->e_addr), &tabla->slim->laddr); + if (ret) { + pr_err("fail to get slimbus logical address %d\n", ret); + goto err_reset; + } + tabla->read_dev = tabla_slim_read_device; + tabla->write_dev = tabla_slim_write_device; + tabla->irq = pdata->irq; + tabla->irq_base = pdata->irq_base; + tabla_pgd_la = tabla->slim->laddr; + + if (pdata->num_irqs < TABLA_NUM_IRQS) { + pr_err("%s: Error, not enough interrupt lines allocated\n", + __func__); + goto err_reset; + } + + tabla->slim_slave = &pdata->slimbus_slave_device; + + ret = slim_add_device(slim->ctrl, tabla->slim_slave); + if (ret) { + pr_err("%s: error, adding SLIMBUS device failed\n", __func__); + goto err_reset; + } + + ret = slim_get_logical_addr(tabla->slim_slave, + tabla->slim_slave->e_addr, + ARRAY_SIZE(tabla->slim_slave->e_addr), + &tabla->slim_slave->laddr); + if (ret) { + pr_err("fail to get slimbus slave logical address %d\n", ret); + goto err_slim_add; + } + tabla_inf_la = tabla->slim_slave->laddr; + + ret = tabla_device_init(tabla, tabla->irq); + if (ret) { + pr_err("%s: error, initializing device failed\n", __func__); + goto err_slim_add; + } + +#ifdef CONFIG_DEBUG_FS + debugTabla = tabla; + + debugfs_tabla_dent = debugfs_create_dir + ("wcd9310_slimbus_interface_device", 0); + if (!IS_ERR(debugfs_tabla_dent)) { + debugfs_peek = debugfs_create_file("peek", + S_IFREG | S_IRUGO, debugfs_tabla_dent, + (void *) "peek", &codec_debug_ops); + + debugfs_poke = debugfs_create_file("poke", + S_IFREG | S_IRUGO, debugfs_tabla_dent, + (void *) "poke", &codec_debug_ops); + } +#endif + + + return ret; + +err_slim_add: + slim_remove_device(tabla->slim_slave); +err_reset: + tabla_free_reset(tabla); +err_supplies: + tabla_disable_supplies(tabla); +err_tabla: + kfree(tabla); +err: + return ret; +} +static int tabla_slim_remove(struct slim_device *pdev) +{ + struct tabla *tabla; + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(debugfs_peek); + debugfs_remove(debugfs_poke); + debugfs_remove(debugfs_tabla_dent); +#endif + + tabla = slim_get_devicedata(pdev); + tabla_device_exit(tabla); + tabla_disable_supplies(tabla); + kfree(tabla); + + return 0; +} +static const struct slim_device_id slimtest_id[] = { + {"tabla-slim", 0}, + {} +}; +static struct slim_driver tabla_slim_driver = { + .driver = { + .name = "tabla-slim", + .owner = THIS_MODULE, + }, + .probe = tabla_slim_probe, + .remove = tabla_slim_remove, + .id_table = slimtest_id, +}; +static int __init tabla_init(void) +{ + int ret; + + ret = slim_driver_register(&tabla_slim_driver); + if (ret != 0) { + pr_err("Failed to register tabla SB driver: %d\n", ret); + goto err; + } +err: + return ret; +} +module_init(tabla_init); + +static void __exit tabla_exit(void) +{ +} +module_exit(tabla_exit); + +MODULE_DESCRIPTION("Tabla core driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/wcd9310-irq.c b/drivers/mfd/wcd9310-irq.c new file mode 100644 index 00000000000..bc7841e249f --- /dev/null +++ b/drivers/mfd/wcd9310-irq.c @@ -0,0 +1,197 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define BYTE_BIT_MASK(nr) (1UL << ((nr) % BITS_PER_BYTE)) +#define BIT_BYTE(nr) ((nr) / BITS_PER_BYTE) + +struct tabla_irq { + bool level; +}; + +static struct tabla_irq tabla_irqs[TABLA_NUM_IRQS] = { + [0] = { .level = 1}, +/* All other tabla interrupts are edge triggered */ +}; + +static inline int irq_to_tabla_irq(struct tabla *tabla, int irq) +{ + return irq - tabla->irq_base; +} + +static void tabla_irq_lock(struct irq_data *data) +{ + struct tabla *tabla = irq_data_get_irq_chip_data(data); + mutex_lock(&tabla->irq_lock); +} + +static void tabla_irq_sync_unlock(struct irq_data *data) +{ + struct tabla *tabla = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ARRAY_SIZE(tabla->irq_masks_cur); i++) { + /* If there's been a change in the mask write it back + * to the hardware. + */ + if (tabla->irq_masks_cur[i] != tabla->irq_masks_cache[i]) { + tabla->irq_masks_cache[i] = tabla->irq_masks_cur[i]; + tabla_reg_write(tabla, TABLA_A_INTR_MASK0+i, + tabla->irq_masks_cur[i]); + } + } + + mutex_unlock(&tabla->irq_lock); +} + +static void tabla_irq_enable(struct irq_data *data) +{ + struct tabla *tabla = irq_data_get_irq_chip_data(data); + int tabla_irq = irq_to_tabla_irq(tabla, data->irq); + tabla->irq_masks_cur[BIT_BYTE(tabla_irq)] &= + ~(BYTE_BIT_MASK(tabla_irq)); +} + +static void tabla_irq_disable(struct irq_data *data) +{ + struct tabla *tabla = irq_data_get_irq_chip_data(data); + int tabla_irq = irq_to_tabla_irq(tabla, data->irq); + tabla->irq_masks_cur[BIT_BYTE(tabla_irq)] |= BYTE_BIT_MASK(tabla_irq); +} + +static struct irq_chip tabla_irq_chip = { + .name = "tabla", + .irq_bus_lock = tabla_irq_lock, + .irq_bus_sync_unlock = tabla_irq_sync_unlock, + .irq_disable = tabla_irq_disable, + .irq_enable = tabla_irq_enable, +}; + +static irqreturn_t tabla_irq_thread(int irq, void *data) +{ + int ret; + struct tabla *tabla = data; + u8 status[TABLA_NUM_IRQ_REGS]; + unsigned int i; + + ret = tabla_bulk_read(tabla, TABLA_A_INTR_STATUS0, + TABLA_NUM_IRQ_REGS, status); + if (ret < 0) { + dev_err(tabla->dev, "Failed to read interrupt status: %d\n", + ret); + return IRQ_NONE; + } + /* Apply masking */ + for (i = 0; i < TABLA_NUM_IRQ_REGS; i++) + status[i] &= ~tabla->irq_masks_cur[i]; + + /* Find out which interrupt was triggered and call that interrupt's + * handler function + */ + for (i = 0; i < TABLA_NUM_IRQS; i++) { + if (status[BIT_BYTE(i)] & BYTE_BIT_MASK(i)) { + if ((i <= TABLA_IRQ_MBHC_INSERTION) && + (i >= TABLA_IRQ_MBHC_REMOVAL)) { + tabla_reg_write(tabla, TABLA_A_INTR_CLEAR0 + + BIT_BYTE(i), BYTE_BIT_MASK(i)); + handle_nested_irq(tabla->irq_base + i); + } else { + handle_nested_irq(tabla->irq_base + i); + tabla_reg_write(tabla, TABLA_A_INTR_CLEAR0 + + BIT_BYTE(i), BYTE_BIT_MASK(i)); + } + break; + } + } + + return IRQ_HANDLED; +} + +int tabla_irq_init(struct tabla *tabla) +{ + int ret; + unsigned int i, cur_irq; + + mutex_init(&tabla->irq_lock); + + if (!tabla->irq) { + dev_warn(tabla->dev, + "No interrupt specified, no interrupts\n"); + tabla->irq_base = 0; + return 0; + } + + if (!tabla->irq_base) { + dev_err(tabla->dev, + "No interrupt base specified, no interrupts\n"); + return 0; + } + /* Mask the individual interrupt sources */ + for (i = 0, cur_irq = tabla->irq_base; i < TABLA_NUM_IRQS; i++, + cur_irq++) { + + irq_set_chip_data(cur_irq, tabla); + + if (tabla_irqs[i].level) + irq_set_chip_and_handler(cur_irq, &tabla_irq_chip, + handle_level_irq); + else + irq_set_chip_and_handler(cur_irq, &tabla_irq_chip, + handle_edge_irq); + + irq_set_nested_thread(cur_irq, 1); + + /* ARM needs us to explicitly flag the IRQ as valid + * and will set them noprobe when we do so. */ +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + set_irq_noprobe(cur_irq); +#endif + + tabla->irq_masks_cur[BIT_BYTE(i)] |= BYTE_BIT_MASK(i); + tabla->irq_masks_cache[BIT_BYTE(i)] |= BYTE_BIT_MASK(i); + tabla->irq_level[BIT_BYTE(i)] |= tabla_irqs[i].level << + (i % BITS_PER_BYTE); + } + for (i = 0; i < TABLA_NUM_IRQ_REGS; i++) { + /* Initialize interrupt mask and level registers */ + tabla_reg_write(tabla, TABLA_A_INTR_LEVEL0 + i, + tabla->irq_level[i]); + tabla_reg_write(tabla, TABLA_A_INTR_MASK0 + i, + tabla->irq_masks_cur[i]); + } + + ret = request_threaded_irq(tabla->irq, NULL, tabla_irq_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "tabla", tabla); + + if (ret != 0) { + dev_err(tabla->dev, "Failed to request IRQ %d: %d\n", + tabla->irq, ret); + return ret; + } + return 0; +} +void tabla_irq_exit(struct tabla *tabla) +{ + if (tabla->irq) + free_irq(tabla->irq, tabla); + mutex_destroy(&tabla->irq_lock); +} diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 68f367184ab..1b1c6e681ae 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -535,6 +535,124 @@ config APANIC_PLABEL If your platform uses a different flash partition label for storing crashdumps, enter it here. +config TSIF + depends on ARCH_MSM + tristate "TSIF (Transport Stream InterFace) support" + default n + ---help--- + This driver supports low level TSIF interface. It provides API + for upper layer drivers. If you have a TSIF hardware, say + Y here and read . + + To compile this driver as module, choose M here: the + module will be called msm_tsif. + +config TSIF_CHRDEV + tristate "TSIF character device" + depends on TSIF + default n + ---help--- + This driver uses low level TSIF interface. It provides character + device useable from user space programs: one can read TSIF stream + from this device. + + This driver may be used as example for TSIF API usage. + + To compile this driver as module, choose M here: the + module will be called tsif_chrdev. + +config TSIF_DEBUG + bool "Turn on debugging information for tsif driver" + depends on TSIF + default n + ---help--- + This turns on debugging information for the tsif driver + +config HAPTIC_ISA1200 + tristate "ISA1200 haptic support" + depends on I2C + default n + help + The ISA1200 is a high performance enhanced haptic driver. + +config PMIC8058_PWM + tristate "Qualcomm PM8058 PWM support" + depends on PMIC8058 + default y + help + This option enables device driver support for the PWM channels + on Qualcomm PM8058 chip. Pulse Width Modulation is used for + purposes including software controlled brightness of backlight, + motor control, and waveform generation. + +config PMIC8058_VIBRATOR + tristate "Qualcomm PM8058 vibrator support" + depends on PMIC8058 && ANDROID_TIMED_OUTPUT + default n + help + This option enables device driver support for the vibrator + on Qualcomm PM8058 chip. + +config PMIC8058_NFC + tristate "Qualcomm PM8058 support for Near Field Communication" + depends on PMIC8058 + default y + help + Qualcomm PM8058 chip has a module to support NFC (Near Field + Communication). This option enables the driver to support it. + +config PMIC8058_UPL + tristate "Qualcomm PM8058 support for User Programmable Logic" + depends on PMIC8058 + default n + help + This option enables device driver support for User Programmable Logic + on Qualcomm PM8058 chip. The UPL module provides a means to implement + simple truth table based logic via a set of control registers. I/O may + be routed in and out of the UPL module via GPIO or DTEST pins. + +config PMIC8058_XOADC + tristate "Qualcomm PM8058 XOADC driver" + depends on PMIC8058 + default n + help + Enables User processor ADC reads over the XOADC module of Qualcomm's + PMIC8058. Driver interface to program registers of the ADC over + AMUX channels, devices on programmable MPP's and xotherm. + +config PMIC8058_MISC + tristate "Qualcomm PM8058 Misc Device driver" + depends on PMIC8058 + default n + help + Provides functionality for various small drivers utilizing the + Qualcomm PM8058 chip. Examples include: signalling when the 32kHz + oscillator malfunctions. + +config PMIC8058_BATTALARM + tristate "Qualcomm PM8058 Battery Alarm Device driver" + depends on PMIC8058 + help + This option enables support for the battery alarm module on the + Qualcomm PM8058 PMIC chip. This support allows for configuration of + the alarm module as well as interrupt handling. + +config TZCOM + tristate "Trustzone Communicator driver" + default n + help + Provides a communication interface between userspace and + TrustZone Operating Environment (TZBSP) using Secure Channel + Manager (SCM) interface. + +config QFP_FUSE + tristate "QFPROM Fuse Read/Write support" + help + This option enables device driver to read/write QFPROM + fuses. The ioctls provides the necessary interface + to the fuse block. Currently this is supported only + on FSM targets. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 2d430484519..1795ecf4381 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -52,3 +52,17 @@ obj-y += carma/ obj-$(CONFIG_WL127X_RFKILL) += wl127x-rfkill.o obj-$(CONFIG_APANIC) += apanic.o obj-$(CONFIG_SENSORS_AK8975) += akm8975.o +obj-$(CONFIG_TSIF) += msm_tsif.o +msm_tsif-objs := tsif.o +obj-$(CONFIG_TSIF_CHRDEV) += tsif_chrdev.o +obj-$(CONFIG_HAPTIC_ISA1200) += isa1200.o +obj-$(CONFIG_PMIC8058_PWM) += pmic8058-pwm.o +obj-$(CONFIG_PMIC8058_VIBRATOR) += pmic8058-vibrator.o +obj-$(CONFIG_PMIC8058_NFC) += pmic8058-nfc.o +obj-$(CONFIG_PMIC8058_UPL) += pmic8058-upl.o +obj-$(CONFIG_MSM_MEMORY_LOW_POWER_MODE_SUSPEND_DEEP_POWER_DOWN) \ + += msm_migrate_pages.o +obj-$(CONFIG_PMIC8058_XOADC) += pmic8058-xoadc.o +obj-$(CONFIG_PMIC8058_MISC) += pmic8058-misc.o +obj-$(CONFIG_PMIC8058_BATTALARM) += pmic8058-batt-alarm.o +obj-$(CONFIG_TZCOM) += tzcom.o diff --git a/drivers/misc/isa1200.c b/drivers/misc/isa1200.c new file mode 100644 index 00000000000..bb3f9a83555 --- /dev/null +++ b/drivers/misc/isa1200.c @@ -0,0 +1,440 @@ +/* + * isa1200.c - Haptic Motor + * + * Copyright (C) 2009 Samsung Electronics + * Kyungmin Park + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../staging/android/timed_output.h" + +#define ISA1200_HCTRL0 0x30 +#define ISA1200_HCTRL1 0x31 +#define ISA1200_HCTRL5 0x35 + +#define ISA1200_HCTRL0_RESET 0x01 +#define ISA1200_HCTRL1_RESET 0x4B + +#define ISA1200_HCTRL5_VIB_STRT 0xD5 +#define ISA1200_HCTRL5_VIB_STOP 0x6B + +struct isa1200_chip { + struct i2c_client *client; + struct isa1200_platform_data *pdata; + struct pwm_device *pwm; + struct hrtimer timer; + struct timed_output_dev dev; + struct work_struct work; + spinlock_t lock; + unsigned int enable; + unsigned int period_ns; +}; + +static int isa1200_read_reg(struct i2c_client *client, int reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static int isa1200_write_reg(struct i2c_client *client, int reg, u8 value) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, value); + if (ret < 0) + dev_err(&client->dev, "%s: err %d\n", __func__, ret); + + return ret; +} + +static void isa1200_vib_set(struct isa1200_chip *haptic, int enable) +{ + int rc = 0; + + if (enable) { + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { + int period_us = haptic->period_ns / 1000; + rc = pwm_config(haptic->pwm, + (period_us * haptic->pdata->duty) / 100, + period_us); + if (rc < 0) + pr_err("%s: pwm_config fail\n", __func__); + rc = pwm_enable(haptic->pwm); + if (rc < 0) + pr_err("%s: pwm_enable fail\n", __func__); + } else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + rc = isa1200_write_reg(haptic->client, + ISA1200_HCTRL5, + ISA1200_HCTRL5_VIB_STRT); + if (rc < 0) + pr_err("%s: start vibartion fail\n", __func__); + } + } else { + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) + pwm_disable(haptic->pwm); + else if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + rc = isa1200_write_reg(haptic->client, + ISA1200_HCTRL5, + ISA1200_HCTRL5_VIB_STOP); + if (rc < 0) + pr_err("%s: stop vibartion fail\n", __func__); + } + } +} + +static void isa1200_chip_work(struct work_struct *work) +{ + struct isa1200_chip *haptic; + + haptic = container_of(work, struct isa1200_chip, work); + isa1200_vib_set(haptic, haptic->enable); +} + +static void isa1200_chip_enable(struct timed_output_dev *dev, int value) +{ + struct isa1200_chip *haptic = container_of(dev, struct isa1200_chip, + dev); + unsigned long flags; + + spin_lock_irqsave(&haptic->lock, flags); + hrtimer_cancel(&haptic->timer); + if (value == 0) + haptic->enable = 0; + else { + value = (value > haptic->pdata->max_timeout ? + haptic->pdata->max_timeout : value); + haptic->enable = 1; + hrtimer_start(&haptic->timer, + ktime_set(value / 1000, (value % 1000) * 1000000), + HRTIMER_MODE_REL); + } + spin_unlock_irqrestore(&haptic->lock, flags); + schedule_work(&haptic->work); +} + +static int isa1200_chip_get_time(struct timed_output_dev *dev) +{ + struct isa1200_chip *haptic = container_of(dev, struct isa1200_chip, + dev); + + if (hrtimer_active(&haptic->timer)) { + ktime_t r = hrtimer_get_remaining(&haptic->timer); + struct timeval t = ktime_to_timeval(r); + return t.tv_sec * 1000 + t.tv_usec / 1000; + } else + return 0; +} + +static enum hrtimer_restart isa1200_vib_timer_func(struct hrtimer *timer) +{ + struct isa1200_chip *haptic = container_of(timer, struct isa1200_chip, + timer); + haptic->enable = 0; + schedule_work(&haptic->work); + + return HRTIMER_NORESTART; +} + +static void dump_isa1200_reg(char *str, struct i2c_client *client) +{ + pr_debug("%s reg0x%x=0x%x, reg0x%x=0x%x, reg0x%x=0x%x\n", str, + ISA1200_HCTRL0, isa1200_read_reg(client, ISA1200_HCTRL0), + ISA1200_HCTRL1, isa1200_read_reg(client, ISA1200_HCTRL1), + ISA1200_HCTRL5, isa1200_read_reg(client, ISA1200_HCTRL5)); +} + +static int isa1200_setup(struct i2c_client *client) +{ + struct isa1200_chip *haptic = i2c_get_clientdata(client); + int value, temp, rc; + + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 0); + udelay(250); + gpio_set_value_cansleep(haptic->pdata->hap_en_gpio, 1); + + value = (haptic->pdata->smart_en << 3) | + (haptic->pdata->is_erm << 5) | + (haptic->pdata->ext_clk_en << 7); + + rc = isa1200_write_reg(client, ISA1200_HCTRL1, value); + if (rc < 0) { + pr_err("%s: i2c write failure\n", __func__); + return rc; + } + + if (haptic->pdata->mode_ctrl == PWM_GEN_MODE) { + temp = haptic->pdata->pwm_fd.pwm_div; + if (temp < 128 || temp > 1024 || temp % 128) { + pr_err("%s: Invalid divider\n", __func__); + goto reset_hctrl1; + } + value = ((temp >> 7) - 1); + } else if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { + temp = haptic->pdata->pwm_fd.pwm_freq; + if (temp < 22400 || temp > 172600 || temp % 22400) { + pr_err("%s: Invalid frequency\n", __func__); + goto reset_hctrl1; + } + value = ((temp / 22400) - 1); + haptic->period_ns = NSEC_PER_SEC / temp; + } + + value |= (haptic->pdata->mode_ctrl << 3) | + (haptic->pdata->overdrive_high << 5) | + (haptic->pdata->overdrive_en << 5) | + (haptic->pdata->chip_en << 7); + + rc = isa1200_write_reg(client, ISA1200_HCTRL0, value); + if (rc < 0) { + pr_err("%s: i2c write failure\n", __func__); + goto reset_hctrl1; + } + + dump_isa1200_reg("new:", client); + return 0; + +reset_hctrl1: + i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, + ISA1200_HCTRL1_RESET); + return rc; +} + +static int __devinit isa1200_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isa1200_chip *haptic; + struct isa1200_platform_data *pdata; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, "%s: no support for i2c read/write" + "byte data\n", __func__); + return -EIO; + } + + pdata = client->dev.platform_data; + if (!pdata) { + dev_err(&client->dev, "%s: no platform data\n", __func__); + return -EINVAL; + } + + if (pdata->dev_setup) { + ret = pdata->dev_setup(true); + if (ret < 0) { + dev_err(&client->dev, "dev setup failed\n"); + return -EINVAL; + } + } + + haptic = kzalloc(sizeof(struct isa1200_chip), GFP_KERNEL); + if (!haptic) { + ret = -ENOMEM; + goto mem_alloc_fail; + } + haptic->client = client; + haptic->enable = 0; + haptic->pdata = pdata; + + if (pdata->power_on) { + ret = pdata->power_on(1); + if (ret) { + dev_err(&client->dev, "%s: power-up failed\n", + __func__); + goto pwr_up_fail; + } + } + + spin_lock_init(&haptic->lock); + INIT_WORK(&haptic->work, isa1200_chip_work); + + hrtimer_init(&haptic->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + haptic->timer.function = isa1200_vib_timer_func; + + /*register with timed output class*/ + haptic->dev.name = pdata->name; + haptic->dev.get_time = isa1200_chip_get_time; + haptic->dev.enable = isa1200_chip_enable; + ret = timed_output_dev_register(&haptic->dev); + if (ret < 0) + goto timed_reg_fail; + + i2c_set_clientdata(client, haptic); + + ret = gpio_is_valid(pdata->hap_en_gpio); + if (ret) { + ret = gpio_request(pdata->hap_en_gpio, "haptic_gpio"); + if (ret) { + dev_err(&client->dev, "%s: gpio %d request failed\n", + __func__, pdata->hap_en_gpio); + goto gpio_fail; + } + } else { + dev_err(&client->dev, "%s: Invalid gpio %d\n", __func__, + pdata->hap_en_gpio); + goto gpio_fail; + } + + ret = isa1200_setup(client); + if (ret) { + dev_err(&client->dev, "%s: setup fail %d\n", __func__, ret); + goto gpio_fail; + } + + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) { + haptic->pwm = pwm_request(pdata->pwm_ch_id, id->name); + if (IS_ERR(haptic->pwm)) { + dev_err(&client->dev, "%s: pwm request failed\n", + __func__); + ret = PTR_ERR(haptic->pwm); + goto reset_hctrl0; + } + } + + printk(KERN_INFO "%s: %s registered\n", __func__, id->name); + return 0; + +reset_hctrl0: + i2c_smbus_write_byte_data(client, ISA1200_HCTRL0, + ISA1200_HCTRL0_RESET); +gpio_fail: + timed_output_dev_unregister(&haptic->dev); +timed_reg_fail: + if (pdata->power_on) + pdata->power_on(0); +pwr_up_fail: + kfree(haptic); +mem_alloc_fail: + if (pdata->dev_setup) + pdata->dev_setup(false); + return ret; +} + +static int __devexit isa1200_remove(struct i2c_client *client) +{ + struct isa1200_chip *haptic = i2c_get_clientdata(client); + + hrtimer_cancel(&haptic->timer); + cancel_work_sync(&haptic->work); + + /* turn-off current vibration */ + isa1200_vib_set(haptic, 0); + + if (haptic->pdata->mode_ctrl == PWM_INPUT_MODE) + pwm_free(haptic->pwm); + + timed_output_dev_unregister(&haptic->dev); + gpio_free(haptic->pdata->hap_en_gpio); + + /* reset hardware registers */ + i2c_smbus_write_byte_data(client, ISA1200_HCTRL0, + ISA1200_HCTRL0_RESET); + i2c_smbus_write_byte_data(client, ISA1200_HCTRL1, + ISA1200_HCTRL1_RESET); + + if (haptic->pdata->dev_setup) + haptic->pdata->dev_setup(false); + + /* power-off the chip */ + if (haptic->pdata->power_on) + haptic->pdata->power_on(0); + + kfree(haptic); + return 0; +} + +#ifdef CONFIG_PM +static int isa1200_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct isa1200_chip *haptic = i2c_get_clientdata(client); + int ret; + + hrtimer_cancel(&haptic->timer); + cancel_work_sync(&haptic->work); + /* turn-off current vibration */ + isa1200_vib_set(haptic, 0); + + if (haptic->pdata->power_on) { + ret = haptic->pdata->power_on(0); + if (ret) { + dev_err(&client->dev, "power-down failed\n"); + return ret; + } + } + + return 0; +} + +static int isa1200_resume(struct i2c_client *client) +{ + struct isa1200_chip *haptic = i2c_get_clientdata(client); + int ret; + + if (haptic->pdata->power_on) { + ret = haptic->pdata->power_on(1); + if (ret) { + dev_err(&client->dev, "power-up failed\n"); + return ret; + } + } + + isa1200_setup(client); + return 0; +} +#else +#define isa1200_suspend NULL +#define isa1200_resume NULL +#endif + +static const struct i2c_device_id isa1200_id[] = { + { "isa1200_1", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, isa1200_id); + +static struct i2c_driver isa1200_driver = { + .driver = { + .name = "isa1200", + }, + .probe = isa1200_probe, + .remove = __devexit_p(isa1200_remove), + .suspend = isa1200_suspend, + .resume = isa1200_resume, + .id_table = isa1200_id, +}; + +static int __init isa1200_init(void) +{ + return i2c_add_driver(&isa1200_driver); +} + +static void __exit isa1200_exit(void) +{ + i2c_del_driver(&isa1200_driver); +} + +module_init(isa1200_init); +module_exit(isa1200_exit); + +MODULE_AUTHOR("Kyungmin Park "); +MODULE_DESCRIPTION("ISA1200 Haptic Motor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/msm_migrate_pages.c b/drivers/misc/msm_migrate_pages.c new file mode 100644 index 00000000000..df7af5f7d9c --- /dev/null +++ b/drivers/misc/msm_migrate_pages.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +static unsigned long unstable_memory_state; + +unsigned long get_msm_migrate_pages_status(void) +{ + return unstable_memory_state; +} +EXPORT_SYMBOL(get_msm_migrate_pages_status); + +#ifdef CONFIG_MEMORY_HOTPLUG +static int migrate_pages_callback(struct notifier_block *self, + unsigned long action, void *arg) +{ + int ret = 0; + + switch (action) { + case MEM_ONLINE: + unstable_memory_state = action; + break; + case MEM_OFFLINE: + unstable_memory_state = action; + break; + case MEM_GOING_OFFLINE: + case MEM_GOING_ONLINE: + case MEM_CANCEL_ONLINE: + case MEM_CANCEL_OFFLINE: + break; + } + return ret; +} +#endif + +static int __devinit msm_migrate_pages_probe(struct platform_device *pdev) +{ +#ifdef CONFIG_MEMORY_HOTPLUG + hotplug_memory_notifier(migrate_pages_callback, 0); +#endif + unstable_memory_state = 0; + return 0; +} + +static struct platform_driver msm_migrate_pages_driver = { + .probe = msm_migrate_pages_probe, + .driver = { + .name = "msm_migrate_pages", + }, +}; + +static int __init msm_migrate_pages_init(void) +{ + return platform_driver_register(&msm_migrate_pages_driver); +} + +static void __exit msm_migrate_pages_exit(void) +{ + platform_driver_unregister(&msm_migrate_pages_driver); +} + +module_init(msm_migrate_pages_init); +module_exit(msm_migrate_pages_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Get Status of Unstable Memory Region"); diff --git a/drivers/misc/pmem.c b/drivers/misc/pmem.c index abb73c14316..50635512f80 100644 --- a/drivers/misc/pmem.c +++ b/drivers/misc/pmem.c @@ -1,6 +1,7 @@ /* drivers/android/pmem.c * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 @@ -19,20 +20,34 @@ #include #include #include -#include #include #include #include -#include +#include #include #include #include +#include +#include +#include -#define PMEM_MAX_DEVICES 10 -#define PMEM_MAX_ORDER 128 +#define PMEM_MAX_DEVICES (10) + +#define PMEM_MAX_ORDER (128) #define PMEM_MIN_ALLOC PAGE_SIZE +#define PMEM_INITIAL_NUM_BITMAP_ALLOCATIONS (64) + +#define PMEM_32BIT_WORD_ORDER (5) +#define PMEM_BITS_PER_WORD_MASK (BITS_PER_LONG - 1) + +#ifdef CONFIG_ANDROID_PMEM_DEBUG #define PMEM_DEBUG 1 +#else +#define PMEM_DEBUG 0 +#endif + +#define SYSTEM_ALLOC_RETRY 10 /* indicates that a refernce to this file has been taken via get_pmem_file, * the file should not be released until put_pmem_file is called */ @@ -50,7 +65,6 @@ #define PMEM_FLAGS_SUBMAP 0x1 << 3 #define PMEM_FLAGS_UNSUBMAP 0x1 << 4 - struct pmem_data { /* in alloc mode: an index into the bitmap * in no_alloc mode: the size of the allocation */ @@ -93,13 +107,28 @@ struct pmem_region_node { #define PMEM_DEBUG_MSGS 0 #if PMEM_DEBUG_MSGS #define DLOG(fmt,args...) \ - do { printk(KERN_INFO "[%s:%s:%d] "fmt, __FILE__, __func__, __LINE__, \ + do { pr_debug("[%s:%s:%d] "fmt, __FILE__, __func__, __LINE__, \ ##args); } \ while (0) #else #define DLOG(x...) do {} while (0) #endif +enum pmem_align { + PMEM_ALIGN_4K, + PMEM_ALIGN_1M, +}; + +#define PMEM_NAME_SIZE 16 + +struct alloc_list { + void *addr; /* physical addr of allocation */ + void *aaddr; /* aligned physical addr */ + unsigned int size; /* total size of allocation */ + unsigned char __iomem *vaddr; /* Virtual addr */ + struct list_head allocs; +}; + struct pmem_info { struct miscdevice dev; /* physical start address of the remaped pmem space */ @@ -112,62 +141,113 @@ struct pmem_info { unsigned long num_entries; /* pfn of the garbage page in memory */ unsigned long garbage_pfn; + /* which memory type (i.e. SMI, EBI1) this PMEM device is backed by */ + unsigned memory_type; + + char name[PMEM_NAME_SIZE]; + /* index of the garbage page in the pmem space */ int garbage_index; - /* the bitmap for the region indicating which entries are allocated - * and which are free */ - struct pmem_bits *bitmap; - /* indicates the region should not be managed with an allocator */ - unsigned no_allocator; + + enum pmem_allocator_type allocator_type; + + int (*allocate)(const int, + const unsigned long, + const unsigned int); + int (*free)(int, int); + int (*free_space)(int, struct pmem_freespace *); + unsigned long (*len)(int, struct pmem_data *); + unsigned long (*start_addr)(int, struct pmem_data *); + + /* actual size of memory element, e.g.: (4 << 10) is 4K */ + unsigned int quantum; + /* indicates maps of this region should be cached, if a mix of * cached and uncached is desired, set this and open the device with * O_SYNC to get an uncached region */ unsigned cached; unsigned buffered; - /* in no_allocator mode the first mapper gets the whole space and sets - * this flag */ - unsigned allocated; + union { + struct { + /* in all_or_nothing allocator mode the first mapper + * gets the whole space and sets this flag */ + unsigned allocated; + } all_or_nothing; + + struct { + /* the buddy allocator bitmap for the region + * indicating which entries are allocated and which + * are free. + */ + + struct pmem_bits *buddy_bitmap; + } buddy_bestfit; + + struct { + unsigned int bitmap_free; /* # of zero bits/quanta */ + uint32_t *bitmap; + int32_t bitmap_allocs; + struct { + short bit; + unsigned short quanta; + } *bitm_alloc; + } bitmap; + + struct { + unsigned long used; /* Bytes currently allocated */ + struct list_head alist; /* List of allocations */ + } system_mem; + } allocator; + + int id; + struct kobject kobj; + /* for debugging, creates a list of pmem file structs, the - * data_list_lock should be taken before pmem_data->sem if both are + * data_list_mutex should be taken before pmem_data->sem if both are * needed */ - struct mutex data_list_lock; + struct mutex data_list_mutex; struct list_head data_list; - /* pmem_sem protects the bitmap array - * a write lock should be held when modifying entries in bitmap - * a read lock should be held when reading data from bits or - * dereferencing a pointer into bitmap - * - * pmem_data->sem protects the pmem data of a particular file - * Many of the function that require the pmem_data->sem have a non- - * locking version for when the caller is already holding that sem. + /* arena_mutex protects the global allocation arena * * IF YOU TAKE BOTH LOCKS TAKE THEM IN THIS ORDER: - * down(pmem_data->sem) => down(bitmap_sem) + * down(pmem_data->sem) => mutex_lock(arena_mutex) */ - struct rw_semaphore bitmap_sem; + struct mutex arena_mutex; long (*ioctl)(struct file *, unsigned int, unsigned long); int (*release)(struct inode *, struct file *); }; +#define to_pmem_info_id(a) (container_of(a, struct pmem_info, kobj)->id) static struct pmem_info pmem[PMEM_MAX_DEVICES]; static int id_count; -#define PMEM_IS_FREE(id, index) !(pmem[id].bitmap[index].allocated) -#define PMEM_ORDER(id, index) pmem[id].bitmap[index].order -#define PMEM_BUDDY_INDEX(id, index) (index ^ (1 << PMEM_ORDER(id, index))) -#define PMEM_NEXT_INDEX(id, index) (index + (1 << PMEM_ORDER(id, index))) -#define PMEM_OFFSET(index) (index * PMEM_MIN_ALLOC) -#define PMEM_START_ADDR(id, index) (PMEM_OFFSET(index) + pmem[id].base) -#define PMEM_LEN(id, index) ((1 << PMEM_ORDER(id, index)) * PMEM_MIN_ALLOC) -#define PMEM_END_ADDR(id, index) (PMEM_START_ADDR(id, index) + \ - PMEM_LEN(id, index)) -#define PMEM_START_VADDR(id, index) (PMEM_OFFSET(id, index) + pmem[id].vbase) -#define PMEM_END_VADDR(id, index) (PMEM_START_VADDR(id, index) + \ - PMEM_LEN(id, index)) +#define PMEM_SYSFS_DIR_NAME "pmem_regions" /* under /sys/kernel/ */ +static struct kset *pmem_kset; + +#define PMEM_IS_FREE_BUDDY(id, index) \ + (!(pmem[id].allocator.buddy_bestfit.buddy_bitmap[index].allocated)) +#define PMEM_BUDDY_ORDER(id, index) \ + (pmem[id].allocator.buddy_bestfit.buddy_bitmap[index].order) +#define PMEM_BUDDY_INDEX(id, index) \ + (index ^ (1 << PMEM_BUDDY_ORDER(id, index))) +#define PMEM_BUDDY_NEXT_INDEX(id, index) \ + (index + (1 << PMEM_BUDDY_ORDER(id, index))) +#define PMEM_OFFSET(index) (index * pmem[id].quantum) +#define PMEM_START_ADDR(id, index) \ + (PMEM_OFFSET(index) + pmem[id].base) +#define PMEM_BUDDY_LEN(id, index) \ + ((1 << PMEM_BUDDY_ORDER(id, index)) * pmem[id].quantum) +#define PMEM_END_ADDR(id, index) \ + (PMEM_START_ADDR(id, index) + PMEM_LEN(id, index)) +#define PMEM_START_VADDR(id, index) \ + (PMEM_OFFSET(id, index) + pmem[id].vbase) +#define PMEM_END_VADDR(id, index) \ + (PMEM_START_VADDR(id, index) + PMEM_LEN(id, index)) #define PMEM_REVOKED(data) (data->flags & PMEM_FLAGS_REVOKED) #define PMEM_IS_PAGE_ALIGNED(addr) (!((addr) & (~PAGE_MASK))) -#define PMEM_IS_SUBMAP(data) ((data->flags & PMEM_FLAGS_SUBMAP) && \ +#define PMEM_IS_SUBMAP(data) \ + ((data->flags & PMEM_FLAGS_SUBMAP) && \ (!(data->flags & PMEM_FLAGS_UNSUBMAP))) static int pmem_release(struct inode *, struct file *); @@ -182,79 +262,361 @@ struct file_operations pmem_fops = { .unlocked_ioctl = pmem_ioctl, }; +#define PMEM_ATTR(_name, _mode, _show, _store) { \ + .attr = {.name = __stringify(_name), .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +} + +struct pmem_attr { + struct attribute attr; + ssize_t(*show) (const int id, char * const); + ssize_t(*store) (const int id, const char * const, const size_t count); +}; +#define to_pmem_attr(a) container_of(a, struct pmem_attr, attr) + +#define RW_PMEM_ATTR(name) \ +static struct pmem_attr pmem_attr_## name = \ + PMEM_ATTR(name, S_IRUGO | S_IWUSR, show_pmem_## name, store_pmem_## name) + +#define RO_PMEM_ATTR(name) \ +static struct pmem_attr pmem_attr_## name = \ + PMEM_ATTR(name, S_IRUGO, show_pmem_## name, NULL) + +#define WO_PMEM_ATTR(name) \ +static struct pmem_attr pmem_attr_## name = \ + PMEM_ATTR(name, S_IWUSR, NULL, store_pmem_## name) + +static ssize_t show_pmem(struct kobject *kobj, + struct attribute *attr, + char *buf) +{ + struct pmem_attr *a = to_pmem_attr(attr); + return a->show ? a->show(to_pmem_info_id(kobj), buf) : -EIO; +} + +static ssize_t store_pmem(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct pmem_attr *a = to_pmem_attr(attr); + return a->store ? a->store(to_pmem_info_id(kobj), buf, count) : -EIO; +} + +static struct sysfs_ops pmem_ops = { + .show = show_pmem, + .store = store_pmem, +}; + +static ssize_t show_pmem_base(int id, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%lu(%#lx)\n", + pmem[id].base, pmem[id].base); +} +RO_PMEM_ATTR(base); + +static ssize_t show_pmem_size(int id, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%lu(%#lx)\n", + pmem[id].size, pmem[id].size); +} +RO_PMEM_ATTR(size); + +static ssize_t show_pmem_allocator_type(int id, char *buf) +{ + switch (pmem[id].allocator_type) { + case PMEM_ALLOCATORTYPE_ALLORNOTHING: + return scnprintf(buf, PAGE_SIZE, "%s\n", "All or Nothing"); + case PMEM_ALLOCATORTYPE_BUDDYBESTFIT: + return scnprintf(buf, PAGE_SIZE, "%s\n", "Buddy Bestfit"); + case PMEM_ALLOCATORTYPE_BITMAP: + return scnprintf(buf, PAGE_SIZE, "%s\n", "Bitmap"); + case PMEM_ALLOCATORTYPE_SYSTEM: + return scnprintf(buf, PAGE_SIZE, "%s\n", "System heap"); + default: + return scnprintf(buf, PAGE_SIZE, + "??? Invalid allocator type (%d) for this region! " + "Something isn't right.\n", + pmem[id].allocator_type); + } +} +RO_PMEM_ATTR(allocator_type); + +static ssize_t show_pmem_mapped_regions(int id, char *buf) +{ + struct list_head *elt; + int ret; + + ret = scnprintf(buf, PAGE_SIZE, + "pid #: mapped regions (offset, len) (offset,len)...\n"); + + mutex_lock(&pmem[id].data_list_mutex); + list_for_each(elt, &pmem[id].data_list) { + struct pmem_data *data = + list_entry(elt, struct pmem_data, list); + struct list_head *elt2; + + down_read(&data->sem); + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "pid %u:", + data->pid); + list_for_each(elt2, &data->region_list) { + struct pmem_region_node *region_node = list_entry(elt2, + struct pmem_region_node, + list); + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "(%lx,%lx) ", + region_node->region.offset, + region_node->region.len); + } + up_read(&data->sem); + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n"); + } + mutex_unlock(&pmem[id].data_list_mutex); + return ret; +} +RO_PMEM_ATTR(mapped_regions); + +#define PMEM_COMMON_SYSFS_ATTRS \ + &pmem_attr_base.attr, \ + &pmem_attr_size.attr, \ + &pmem_attr_allocator_type.attr, \ + &pmem_attr_mapped_regions.attr + + +static ssize_t show_pmem_allocated(int id, char *buf) +{ + ssize_t ret; + + mutex_lock(&pmem[id].arena_mutex); + ret = scnprintf(buf, PAGE_SIZE, "%s\n", + pmem[id].allocator.all_or_nothing.allocated ? + "is allocated" : "is NOT allocated"); + mutex_unlock(&pmem[id].arena_mutex); + return ret; +} +RO_PMEM_ATTR(allocated); + +static struct attribute *pmem_allornothing_attrs[] = { + PMEM_COMMON_SYSFS_ATTRS, + + &pmem_attr_allocated.attr, + + NULL +}; + +static struct kobj_type pmem_allornothing_ktype = { + .sysfs_ops = &pmem_ops, + .default_attrs = pmem_allornothing_attrs, +}; + +static ssize_t show_pmem_total_entries(int id, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%lu\n", pmem[id].num_entries); +} +RO_PMEM_ATTR(total_entries); + +static ssize_t show_pmem_quantum_size(int id, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%u (%#x)\n", + pmem[id].quantum, pmem[id].quantum); +} +RO_PMEM_ATTR(quantum_size); + +static ssize_t show_pmem_buddy_bitmap_dump(int id, char *buf) +{ + int ret, i; + + mutex_lock(&pmem[id].data_list_mutex); + ret = scnprintf(buf, PAGE_SIZE, "index\torder\tlength\tallocated\n"); + + for (i = 0; i < pmem[id].num_entries && (PAGE_SIZE - ret); + i = PMEM_BUDDY_NEXT_INDEX(id, i)) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, "%d\t%d\t%d\t%d\n", + i, PMEM_BUDDY_ORDER(id, i), + PMEM_BUDDY_LEN(id, i), + !PMEM_IS_FREE_BUDDY(id, i)); + + mutex_unlock(&pmem[id].data_list_mutex); + return ret; +} +RO_PMEM_ATTR(buddy_bitmap_dump); + +#define PMEM_BITMAP_BUDDY_BESTFIT_COMMON_SYSFS_ATTRS \ + &pmem_attr_quantum_size.attr, \ + &pmem_attr_total_entries.attr + +static struct attribute *pmem_buddy_bestfit_attrs[] = { + PMEM_COMMON_SYSFS_ATTRS, + + PMEM_BITMAP_BUDDY_BESTFIT_COMMON_SYSFS_ATTRS, + + &pmem_attr_buddy_bitmap_dump.attr, + + NULL +}; + +static struct kobj_type pmem_buddy_bestfit_ktype = { + .sysfs_ops = &pmem_ops, + .default_attrs = pmem_buddy_bestfit_attrs, +}; + +static ssize_t show_pmem_free_quanta(int id, char *buf) +{ + ssize_t ret; + + mutex_lock(&pmem[id].arena_mutex); + ret = scnprintf(buf, PAGE_SIZE, "%u\n", + pmem[id].allocator.bitmap.bitmap_free); + mutex_unlock(&pmem[id].arena_mutex); + return ret; +} +RO_PMEM_ATTR(free_quanta); + +static ssize_t show_pmem_bits_allocated(int id, char *buf) +{ + ssize_t ret; + unsigned int i; + + mutex_lock(&pmem[id].arena_mutex); + + ret = scnprintf(buf, PAGE_SIZE, + "id: %d\nbitnum\tindex\tquanta allocated\n", id); + + for (i = 0; i < pmem[id].allocator.bitmap.bitmap_allocs; i++) + if (pmem[id].allocator.bitmap.bitm_alloc[i].bit != -1) + ret += scnprintf(buf + ret, PAGE_SIZE - ret, + "%u\t%u\t%u\n", + i, + pmem[id].allocator.bitmap.bitm_alloc[i].bit, + pmem[id].allocator.bitmap.bitm_alloc[i].quanta + ); + + mutex_unlock(&pmem[id].arena_mutex); + return ret; +} +RO_PMEM_ATTR(bits_allocated); + +static struct attribute *pmem_bitmap_attrs[] = { + PMEM_COMMON_SYSFS_ATTRS, + + PMEM_BITMAP_BUDDY_BESTFIT_COMMON_SYSFS_ATTRS, + + &pmem_attr_free_quanta.attr, + &pmem_attr_bits_allocated.attr, + + NULL +}; + +static struct attribute *pmem_system_attrs[] = { + PMEM_COMMON_SYSFS_ATTRS, + + NULL +}; + +static struct kobj_type pmem_bitmap_ktype = { + .sysfs_ops = &pmem_ops, + .default_attrs = pmem_bitmap_attrs, +}; + +static struct kobj_type pmem_system_ktype = { + .sysfs_ops = &pmem_ops, + .default_attrs = pmem_system_attrs, +}; + static int get_id(struct file *file) { return MINOR(file->f_dentry->d_inode->i_rdev); } -int is_pmem_file(struct file *file) +static char *get_name(struct file *file) +{ + int id = get_id(file); + return pmem[id].name; +} + +static int is_pmem_file(struct file *file) { int id; if (unlikely(!file || !file->f_dentry || !file->f_dentry->d_inode)) return 0; + id = get_id(file); - if (unlikely(id >= PMEM_MAX_DEVICES)) - return 0; - if (unlikely(file->f_dentry->d_inode->i_rdev != - MKDEV(MISC_MAJOR, pmem[id].dev.minor))) - return 0; - return 1; + return (unlikely(id >= PMEM_MAX_DEVICES || + file->f_dentry->d_inode->i_rdev != + MKDEV(MISC_MAJOR, pmem[id].dev.minor))) ? 0 : 1; } static int has_allocation(struct file *file) { - struct pmem_data *data; - /* check is_pmem_file first if not accessed via pmem_file_ops */ - - if (unlikely(!file->private_data)) - return 0; - data = (struct pmem_data *)file->private_data; - if (unlikely(data->index < 0)) - return 0; - return 1; + /* must be called with at least read lock held on + * ((struct pmem_data *)(file->private_data))->sem which + * means that file is guaranteed not to be NULL upon entry!! + * check is_pmem_file first if not accessed via pmem_file_ops */ + struct pmem_data *pdata = file->private_data; + return pdata && pdata->index != -1; } static int is_master_owner(struct file *file) { struct file *master_file; - struct pmem_data *data; + struct pmem_data *data = file->private_data; int put_needed, ret = 0; - if (!is_pmem_file(file) || !has_allocation(file)) + if (!has_allocation(file)) return 0; - data = (struct pmem_data *)file->private_data; if (PMEM_FLAGS_MASTERMAP & data->flags) return 1; master_file = fget_light(data->master_fd, &put_needed); if (master_file && data->master_file == master_file) ret = 1; - fput_light(master_file, put_needed); + if (master_file) + fput_light(master_file, put_needed); return ret; } -static int pmem_free(int id, int index) +static int pmem_free_all_or_nothing(int id, int index) { - /* caller should hold the write lock on pmem_sem! */ - int buddy, curr = index; + /* caller should hold the lock on arena_mutex! */ DLOG("index %d\n", index); - if (pmem[id].no_allocator) { - pmem[id].allocated = 0; - return 0; - } + pmem[id].allocator.all_or_nothing.allocated = 0; + return 0; +} + +static int pmem_free_space_all_or_nothing(int id, + struct pmem_freespace *fs) +{ + /* caller should hold the lock on arena_mutex! */ + fs->total = (unsigned long) + pmem[id].allocator.all_or_nothing.allocated == 0 ? + pmem[id].size : 0; + + fs->largest = fs->total; + return 0; +} + + +static int pmem_free_buddy_bestfit(int id, int index) +{ + /* caller should hold the lock on arena_mutex! */ + int curr = index; + DLOG("index %d\n", index); + + /* clean up the bitmap, merging any buddies */ - pmem[id].bitmap[curr].allocated = 0; + pmem[id].allocator.buddy_bestfit.buddy_bitmap[curr].allocated = 0; /* find a slots buddy Buddy# = Slot# ^ (1 << order) * if the buddy is also free merge them * repeat until the buddy is not free or end of the bitmap is reached */ do { - buddy = PMEM_BUDDY_INDEX(id, curr); - if (PMEM_IS_FREE(id, buddy) && - PMEM_ORDER(id, buddy) == PMEM_ORDER(id, curr)) { - PMEM_ORDER(id, buddy)++; - PMEM_ORDER(id, curr)++; + int buddy = PMEM_BUDDY_INDEX(id, curr); + if (buddy < pmem[id].num_entries && + PMEM_IS_FREE_BUDDY(id, buddy) && + PMEM_BUDDY_ORDER(id, buddy) == + PMEM_BUDDY_ORDER(id, curr)) { + PMEM_BUDDY_ORDER(id, buddy)++; + PMEM_BUDDY_ORDER(id, curr)++; curr = min(buddy, curr); } else { break; @@ -264,43 +626,222 @@ static int pmem_free(int id, int index) return 0; } + +static int pmem_free_space_buddy_bestfit(int id, + struct pmem_freespace *fs) +{ + /* caller should hold the lock on arena_mutex! */ + int curr; + unsigned long size; + fs->total = 0; + fs->largest = 0; + + for (curr = 0; curr < pmem[id].num_entries; + curr = PMEM_BUDDY_NEXT_INDEX(id, curr)) { + if (PMEM_IS_FREE_BUDDY(id, curr)) { + size = PMEM_BUDDY_LEN(id, curr); + if (size > fs->largest) + fs->largest = size; + fs->total += size; + } + } + return 0; +} + + +static inline uint32_t start_mask(int bit_start) +{ + return (uint32_t)(~0) << (bit_start & PMEM_BITS_PER_WORD_MASK); +} + +static inline uint32_t end_mask(int bit_end) +{ + return (uint32_t)(~0) >> + ((BITS_PER_LONG - bit_end) & PMEM_BITS_PER_WORD_MASK); +} + +static inline int compute_total_words(int bit_end, int word_index) +{ + return ((bit_end + BITS_PER_LONG - 1) >> + PMEM_32BIT_WORD_ORDER) - word_index; +} + +static void bitmap_bits_clear_all(uint32_t *bitp, int bit_start, int bit_end) +{ + int word_index = bit_start >> PMEM_32BIT_WORD_ORDER, total_words; + + total_words = compute_total_words(bit_end, word_index); + if (total_words > 0) { + if (total_words == 1) { + bitp[word_index] &= + ~(start_mask(bit_start) & end_mask(bit_end)); + } else { + bitp[word_index++] &= ~start_mask(bit_start); + if (total_words > 2) { + int total_bytes; + + total_words -= 2; + total_bytes = total_words << 2; + + memset(&bitp[word_index], 0, total_bytes); + word_index += total_words; + } + bitp[word_index] &= ~end_mask(bit_end); + } + } +} + +static int pmem_free_bitmap(int id, int bitnum) +{ + /* caller should hold the lock on arena_mutex! */ + int i; + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; + + DLOG("bitnum %d\n", bitnum); + + for (i = 0; i < pmem[id].allocator.bitmap.bitmap_allocs; i++) { + const int curr_bit = + pmem[id].allocator.bitmap.bitm_alloc[i].bit; + + if (curr_bit == bitnum) { + const int curr_quanta = + pmem[id].allocator.bitmap.bitm_alloc[i].quanta; + + bitmap_bits_clear_all(pmem[id].allocator.bitmap.bitmap, + curr_bit, curr_bit + curr_quanta); + pmem[id].allocator.bitmap.bitmap_free += curr_quanta; + pmem[id].allocator.bitmap.bitm_alloc[i].bit = -1; + pmem[id].allocator.bitmap.bitm_alloc[i].quanta = 0; + return 0; + } + } + printk(KERN_ALERT "pmem: %s: Attempt to free unallocated index %d, id" + " %d, pid %d(%s)\n", __func__, bitnum, id, current->pid, + get_task_comm(currtask_name, current)); + + return -1; +} + +static int pmem_free_system(int id, int index) +{ + /* caller should hold the lock on arena_mutex! */ + struct alloc_list *item; + + DLOG("index %d\n", index); + if (index != 0) + item = (struct alloc_list *)index; + else + return 0; + + if (item->vaddr != NULL) { + iounmap(item->vaddr); + kfree(__va(item->addr)); + list_del(&item->allocs); + kfree(item); + } + + return 0; +} + +static int pmem_free_space_bitmap(int id, struct pmem_freespace *fs) +{ + int i, j; + int max_allocs = pmem[id].allocator.bitmap.bitmap_allocs; + int alloc_start = 0; + int next_alloc; + unsigned long size = 0; + + fs->total = 0; + fs->largest = 0; + + for (i = 0; i < max_allocs; i++) { + + int alloc_quanta = 0; + int alloc_idx = 0; + next_alloc = pmem[id].num_entries; + + /* Look for the lowest bit where next allocation starts */ + for (j = 0; j < max_allocs; j++) { + const int curr_alloc = pmem[id].allocator. + bitmap.bitm_alloc[j].bit; + if (curr_alloc != -1) { + if (alloc_start == curr_alloc) + alloc_idx = j; + if (alloc_start >= curr_alloc) + continue; + if (curr_alloc < next_alloc) + next_alloc = curr_alloc; + } + } + alloc_quanta = pmem[id].allocator.bitmap. + bitm_alloc[alloc_idx].quanta; + size = (next_alloc - (alloc_start + alloc_quanta)) * + pmem[id].quantum; + + if (size > fs->largest) + fs->largest = size; + fs->total += size; + + if (next_alloc == pmem[id].num_entries) + break; + else + alloc_start = next_alloc; + } + + return 0; +} + +static int pmem_free_space_system(int id, struct pmem_freespace *fs) +{ + fs->total = pmem[id].size; + fs->largest = pmem[id].size; + + return 0; +} + static void pmem_revoke(struct file *file, struct pmem_data *data); static int pmem_release(struct inode *inode, struct file *file) { - struct pmem_data *data = (struct pmem_data *)file->private_data; + struct pmem_data *data = file->private_data; struct pmem_region_node *region_node; struct list_head *elt, *elt2; int id = get_id(file), ret = 0; - - mutex_lock(&pmem[id].data_list_lock); +#if PMEM_DEBUG_MSGS + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; +#endif + DLOG("releasing memory pid %u(%s) file %p(%ld) dev %s(id: %d)\n", + current->pid, get_task_comm(currtask_name, current), + file, file_count(file), get_name(file), id); + mutex_lock(&pmem[id].data_list_mutex); /* if this file is a master, revoke all the memory in the connected * files */ if (PMEM_FLAGS_MASTERMAP & data->flags) { - struct pmem_data *sub_data; list_for_each(elt, &pmem[id].data_list) { - sub_data = list_entry(elt, struct pmem_data, list); + struct pmem_data *sub_data = + list_entry(elt, struct pmem_data, list); + int is_master; + down_read(&sub_data->sem); - if (PMEM_IS_SUBMAP(sub_data) && - file == sub_data->master_file) { - up_read(&sub_data->sem); + is_master = (PMEM_IS_SUBMAP(sub_data) && + file == sub_data->master_file); + up_read(&sub_data->sem); + + if (is_master) pmem_revoke(file, sub_data); - } else - up_read(&sub_data->sem); } } list_del(&data->list); - mutex_unlock(&pmem[id].data_list_lock); - + mutex_unlock(&pmem[id].data_list_mutex); down_write(&data->sem); - /* if its not a conencted file and it has an allocation, free it */ + /* if it is not a connected file and it has an allocation, free it */ if (!(PMEM_FLAGS_CONNECTED & data->flags) && has_allocation(file)) { - down_write(&pmem[id].bitmap_sem); - ret = pmem_free(id, data->index); - up_write(&pmem[id].bitmap_sem); + mutex_lock(&pmem[id].arena_mutex); + ret = pmem[id].free(id, data->index); + mutex_unlock(&pmem[id].arena_mutex); } /* if this file is a submap (mapped, connected file), downref the @@ -333,15 +874,17 @@ static int pmem_open(struct inode *inode, struct file *file) struct pmem_data *data; int id = get_id(file); int ret = 0; +#if PMEM_DEBUG_MSGS + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; +#endif - DLOG("current %u file %p(%d)\n", current->pid, file, file_count(file)); - /* setup file->private_data to indicate its unmapped */ - /* you can only open a pmem device one time */ - if (file->private_data != NULL) - return -1; + DLOG("pid %u(%s) file %p(%ld) dev %s(id: %d)\n", + current->pid, get_task_comm(currtask_name, current), + file, file_count(file), get_name(file), id); data = kmalloc(sizeof(struct pmem_data), GFP_KERNEL); if (!data) { - printk("pmem: unable to allocate memory for pmem metadata."); + printk(KERN_ALERT "pmem: %s: unable to allocate memory for " + "pmem metadata.", __func__); return -1; } data->flags = 0; @@ -359,17 +902,17 @@ static int pmem_open(struct inode *inode, struct file *file) file->private_data = data; INIT_LIST_HEAD(&data->list); - mutex_lock(&pmem[id].data_list_lock); + mutex_lock(&pmem[id].data_list_mutex); list_add(&data->list, &pmem[id].data_list); - mutex_unlock(&pmem[id].data_list_lock); + mutex_unlock(&pmem[id].data_list_mutex); return ret; } -static unsigned long pmem_order(unsigned long len) +static unsigned long pmem_order(unsigned long len, int id) { int i; - len = (len + PMEM_MIN_ALLOC - 1)/PMEM_MIN_ALLOC; + len = (len + pmem[id].quantum - 1)/pmem[id].quantum; len--; for (i = 0; i < sizeof(len)*8; i++) if (len >> i == 0) @@ -377,74 +920,385 @@ static unsigned long pmem_order(unsigned long len) return i; } -static int pmem_allocate(int id, unsigned long len) +static int pmem_allocator_all_or_nothing(const int id, + const unsigned long len, + const unsigned int align) { - /* caller should hold the write lock on pmem_sem! */ - /* return the corresponding pdata[] entry */ - int curr = 0; - int end = pmem[id].num_entries; - int best_fit = -1; - unsigned long order = pmem_order(len); + /* caller should hold the lock on arena_mutex! */ + DLOG("all or nothing\n"); + if ((len > pmem[id].size) || + pmem[id].allocator.all_or_nothing.allocated) + return -1; + pmem[id].allocator.all_or_nothing.allocated = 1; + return len; +} - if (pmem[id].no_allocator) { - DLOG("no allocator"); - if ((len > pmem[id].size) || pmem[id].allocated) - return -1; - pmem[id].allocated = 1; - return len; - } +static int pmem_allocator_buddy_bestfit(const int id, + const unsigned long len, + unsigned int align) +{ + /* caller should hold the lock on arena_mutex! */ + int curr; + int best_fit = -1; + unsigned long order; + DLOG("buddy bestfit\n"); + order = pmem_order(len, id); if (order > PMEM_MAX_ORDER) - return -1; + goto out; + DLOG("order %lx\n", order); - /* look through the bitmap: - * if you find a free slot of the correct order use it - * otherwise, use the best fit (smallest with size > order) slot + /* Look through the bitmap. + * If a free slot of the correct order is found, use it. + * Otherwise, use the best fit (smallest with size > order) slot. */ - while (curr < end) { - if (PMEM_IS_FREE(id, curr)) { - if (PMEM_ORDER(id, curr) == (unsigned char)order) { + for (curr = 0; + curr < pmem[id].num_entries; + curr = PMEM_BUDDY_NEXT_INDEX(id, curr)) + if (PMEM_IS_FREE_BUDDY(id, curr)) { + if (PMEM_BUDDY_ORDER(id, curr) == + (unsigned char)order) { /* set the not free bit and clear others */ best_fit = curr; break; } - if (PMEM_ORDER(id, curr) > (unsigned char)order && + if (PMEM_BUDDY_ORDER(id, curr) > + (unsigned char)order && (best_fit < 0 || - PMEM_ORDER(id, curr) < PMEM_ORDER(id, best_fit))) + PMEM_BUDDY_ORDER(id, curr) < + PMEM_BUDDY_ORDER(id, best_fit))) best_fit = curr; } - curr = PMEM_NEXT_INDEX(id, curr); - } - /* if best_fit < 0, there are no suitable slots, - * return an error - */ + /* if best_fit < 0, there are no suitable slots; return an error */ if (best_fit < 0) { - printk("pmem: no space left to allocate!\n"); - return -1; +#if PMEM_DEBUG + printk(KERN_ALERT "pmem: %s: no space left to allocate!\n", + __func__); +#endif + goto out; } /* now partition the best fit: * split the slot into 2 buddies of order - 1 * repeat until the slot is of the correct order */ - while (PMEM_ORDER(id, best_fit) > (unsigned char)order) { + while (PMEM_BUDDY_ORDER(id, best_fit) > (unsigned char)order) { int buddy; - PMEM_ORDER(id, best_fit) -= 1; + PMEM_BUDDY_ORDER(id, best_fit) -= 1; buddy = PMEM_BUDDY_INDEX(id, best_fit); - PMEM_ORDER(id, buddy) = PMEM_ORDER(id, best_fit); + PMEM_BUDDY_ORDER(id, buddy) = PMEM_BUDDY_ORDER(id, best_fit); } - pmem[id].bitmap[best_fit].allocated = 1; + pmem[id].allocator.buddy_bestfit.buddy_bitmap[best_fit].allocated = 1; +out: return best_fit; } -static pgprot_t pmem_access_prot(struct file *file, pgprot_t vma_prot) + +static inline unsigned long paddr_from_bit(const int id, const int bitnum) +{ + return pmem[id].base + pmem[id].quantum * bitnum; +} + +static inline unsigned long bit_from_paddr(const int id, + const unsigned long paddr) +{ + return (paddr - pmem[id].base) / pmem[id].quantum; +} + +static void bitmap_bits_set_all(uint32_t *bitp, int bit_start, int bit_end) +{ + int word_index = bit_start >> PMEM_32BIT_WORD_ORDER, total_words; + + total_words = compute_total_words(bit_end, word_index); + if (total_words > 0) { + if (total_words == 1) { + bitp[word_index] |= + (start_mask(bit_start) & end_mask(bit_end)); + } else { + bitp[word_index++] |= start_mask(bit_start); + if (total_words > 2) { + int total_bytes; + + total_words -= 2; + total_bytes = total_words << 2; + + memset(&bitp[word_index], ~0, total_bytes); + word_index += total_words; + } + bitp[word_index] |= end_mask(bit_end); + } + } +} + +static int +bitmap_allocate_contiguous(uint32_t *bitp, int num_bits_to_alloc, + int total_bits, int spacing) +{ + int bit_start, last_bit, word_index; + + if (num_bits_to_alloc <= 0) + return -1; + + for (bit_start = 0; ; + bit_start = (last_bit + + (word_index << PMEM_32BIT_WORD_ORDER) + spacing - 1) + & ~(spacing - 1)) { + int bit_end = bit_start + num_bits_to_alloc, total_words; + + if (bit_end > total_bits) + return -1; /* out of contiguous memory */ + + word_index = bit_start >> PMEM_32BIT_WORD_ORDER; + total_words = compute_total_words(bit_end, word_index); + + if (total_words <= 0) + return -1; + + if (total_words == 1) { + last_bit = fls(bitp[word_index] & + (start_mask(bit_start) & + end_mask(bit_end))); + if (last_bit) + continue; + } else { + int end_word = word_index + (total_words - 1); + last_bit = + fls(bitp[word_index] & start_mask(bit_start)); + if (last_bit) + continue; + + for (word_index++; + word_index < end_word; + word_index++) { + last_bit = fls(bitp[word_index]); + if (last_bit) + break; + } + if (last_bit) + continue; + + last_bit = fls(bitp[word_index] & end_mask(bit_end)); + if (last_bit) + continue; + } + bitmap_bits_set_all(bitp, bit_start, bit_end); + return bit_start; + } + return -1; +} + +static int reserve_quanta(const unsigned int quanta_needed, + const int id, + unsigned int align) +{ + /* alignment should be a valid power of 2 */ + int ret = -1, start_bit = 0, spacing = 1; + + /* Sanity check */ + if (quanta_needed > pmem[id].allocator.bitmap.bitmap_free) { +#if PMEM_DEBUG + printk(KERN_ALERT "pmem: %s: request (%d) too big for" + " available free (%d)\n", __func__, quanta_needed, + pmem[id].allocator.bitmap.bitmap_free); +#endif + return -1; + } + + start_bit = bit_from_paddr(id, + (pmem[id].base + align - 1) & ~(align - 1)); + if (start_bit <= -1) { +#if PMEM_DEBUG + printk(KERN_ALERT + "pmem: %s: bit_from_paddr fails for" + " %u alignment.\n", __func__, align); +#endif + return -1; + } + spacing = align / pmem[id].quantum; + spacing = spacing > 1 ? spacing : 1; + + ret = bitmap_allocate_contiguous(pmem[id].allocator.bitmap.bitmap, + quanta_needed, + (pmem[id].size + pmem[id].quantum - 1) / pmem[id].quantum, + spacing); + +#if PMEM_DEBUG + if (ret < 0) + printk(KERN_ALERT "pmem: %s: not enough contiguous bits free " + "in bitmap! Region memory is either too fragmented or" + " request is too large for available memory.\n", + __func__); +#endif + + return ret; +} + +static int pmem_allocator_bitmap(const int id, + const unsigned long len, + const unsigned int align) +{ + /* caller should hold the lock on arena_mutex! */ + int bitnum, i; + unsigned int quanta_needed; + + DLOG("bitmap id %d, len %ld, align %u\n", id, len, align); + if (!pmem[id].allocator.bitmap.bitm_alloc) { +#if PMEM_DEBUG + printk(KERN_ALERT "pmem: bitm_alloc not present! id: %d\n", + id); +#endif + return -1; + } + + quanta_needed = (len + pmem[id].quantum - 1) / pmem[id].quantum; + DLOG("quantum size %u quanta needed %u free %u id %d\n", + pmem[id].quantum, quanta_needed, + pmem[id].allocator.bitmap.bitmap_free, id); + + if (pmem[id].allocator.bitmap.bitmap_free < quanta_needed) { +#if PMEM_DEBUG + printk(KERN_ALERT "pmem: memory allocation failure. " + "PMEM memory region exhausted, id %d." + " Unable to comply with allocation request.\n", id); +#endif + return -1; + } + + bitnum = reserve_quanta(quanta_needed, id, align); + if (bitnum == -1) + goto leave; + + for (i = 0; + i < pmem[id].allocator.bitmap.bitmap_allocs && + pmem[id].allocator.bitmap.bitm_alloc[i].bit != -1; + i++) + ; + + if (i >= pmem[id].allocator.bitmap.bitmap_allocs) { + void *temp; + int32_t new_bitmap_allocs = + pmem[id].allocator.bitmap.bitmap_allocs << 1; + int j; + + if (!new_bitmap_allocs) { /* failed sanity check!! */ +#if PMEM_DEBUG + pr_alert("pmem: bitmap_allocs number" + " wrapped around to zero! Something " + "is VERY wrong.\n"); +#endif + return -1; + } + + if (new_bitmap_allocs > pmem[id].num_entries) { + /* failed sanity check!! */ +#if PMEM_DEBUG + pr_alert("pmem: required bitmap_allocs" + " number exceeds maximum entries possible" + " for current quanta\n"); +#endif + return -1; + } + + temp = krealloc(pmem[id].allocator.bitmap.bitm_alloc, + new_bitmap_allocs * + sizeof(*pmem[id].allocator.bitmap.bitm_alloc), + GFP_KERNEL); + if (!temp) { +#if PMEM_DEBUG + pr_alert("pmem: can't realloc bitmap_allocs," + "id %d, current num bitmap allocs %d\n", + id, pmem[id].allocator.bitmap.bitmap_allocs); +#endif + return -1; + } + pmem[id].allocator.bitmap.bitmap_allocs = new_bitmap_allocs; + pmem[id].allocator.bitmap.bitm_alloc = temp; + + for (j = i; j < new_bitmap_allocs; j++) { + pmem[id].allocator.bitmap.bitm_alloc[j].bit = -1; + pmem[id].allocator.bitmap.bitm_alloc[i].quanta = 0; + } + + DLOG("increased # of allocated regions to %d for id %d\n", + pmem[id].allocator.bitmap.bitmap_allocs, id); + } + + DLOG("bitnum %d, bitm_alloc index %d\n", bitnum, i); + + pmem[id].allocator.bitmap.bitmap_free -= quanta_needed; + pmem[id].allocator.bitmap.bitm_alloc[i].bit = bitnum; + pmem[id].allocator.bitmap.bitm_alloc[i].quanta = quanta_needed; +leave: + return bitnum; +} + +static int pmem_allocator_system(const int id, + const unsigned long len, + const unsigned int align) +{ + /* caller should hold the lock on arena_mutex! */ + struct alloc_list *list; + unsigned long aligned_len; + int count = SYSTEM_ALLOC_RETRY; + void *buf; + + DLOG("system id %d, len %ld, align %u\n", id, len, align); + + if ((pmem[id].allocator.system_mem.used + len) > pmem[id].size) { + DLOG("requested size would be larger than quota\n"); + return -1; + } + + /* Handle alignment */ + aligned_len = len + align; + + /* Attempt allocation */ + list = kmalloc(sizeof(struct alloc_list), GFP_KERNEL); + if (list == NULL) { + printk(KERN_ERR "pmem: failed to allocate system metadata\n"); + return -1; + } + list->vaddr = NULL; + + buf = NULL; + while ((buf == NULL) && count--) { + buf = kmalloc((aligned_len), GFP_KERNEL); + if (buf == NULL) { + DLOG("pmem: kmalloc %d temporarily failed len= %ld\n", + count, aligned_len); + } + } + if (!buf) { + printk(KERN_CRIT "pmem: kmalloc failed for id= %d len= %ld\n", + id, aligned_len); + kfree(list); + return -1; + } + list->size = aligned_len; + list->addr = (void *)__pa(buf); + list->aaddr = (void *)(((unsigned int)(list->addr) + (align - 1)) & + ~(align - 1)); + + if (!pmem[id].cached) + list->vaddr = ioremap(__pa(buf), aligned_len); + else + list->vaddr = ioremap_cached(__pa(buf), aligned_len); + + INIT_LIST_HEAD(&list->allocs); + list_add(&list->allocs, &pmem[id].allocator.system_mem.alist); + + return (int)list; +} + +static pgprot_t pmem_phys_mem_access_prot(struct file *file, pgprot_t vma_prot) { int id = get_id(file); -#ifdef pgprot_noncached +#ifdef pgprot_writecombine if (pmem[id].cached == 0 || file->f_flags & O_SYNC) - return pgprot_noncached(vma_prot); + /* on ARMv6 and ARMv7 this expands to Normal Noncached */ + return pgprot_writecombine(vma_prot); #endif #ifdef pgprot_ext_buffered else if (pmem[id].buffered) @@ -453,26 +1307,80 @@ static pgprot_t pmem_access_prot(struct file *file, pgprot_t vma_prot) return vma_prot; } -static unsigned long pmem_start_addr(int id, struct pmem_data *data) +static unsigned long pmem_start_addr_all_or_nothing(int id, + struct pmem_data *data) { - if (pmem[id].no_allocator) - return PMEM_START_ADDR(id, 0); - else - return PMEM_START_ADDR(id, data->index); + return PMEM_START_ADDR(id, 0); +} +static unsigned long pmem_start_addr_buddy_bestfit(int id, + struct pmem_data *data) +{ + return PMEM_START_ADDR(id, data->index); } -static void *pmem_start_vaddr(int id, struct pmem_data *data) +static unsigned long pmem_start_addr_bitmap(int id, struct pmem_data *data) { - return pmem_start_addr(id, data) - pmem[id].base + pmem[id].vbase; + return data->index * pmem[id].quantum + pmem[id].base; } -static unsigned long pmem_len(int id, struct pmem_data *data) +static unsigned long pmem_start_addr_system(int id, struct pmem_data *data) { - if (pmem[id].no_allocator) - return data->index; + return (unsigned long)(((struct alloc_list *)(data->index))->aaddr); +} + +static void *pmem_start_vaddr(int id, struct pmem_data *data) +{ + if (pmem[id].allocator_type == PMEM_ALLOCATORTYPE_SYSTEM) + return ((struct alloc_list *)(data->index))->vaddr; else - return PMEM_LEN(id, data->index); + return pmem[id].start_addr(id, data) - pmem[id].base + pmem[id].vbase; +} + +static unsigned long pmem_len_all_or_nothing(int id, struct pmem_data *data) +{ + return data->index; +} + +static unsigned long pmem_len_buddy_bestfit(int id, struct pmem_data *data) +{ + return PMEM_BUDDY_LEN(id, data->index); +} + +static unsigned long pmem_len_bitmap(int id, struct pmem_data *data) +{ + int i; + unsigned long ret = 0; + + mutex_lock(&pmem[id].arena_mutex); + + for (i = 0; i < pmem[id].allocator.bitmap.bitmap_allocs; i++) + if (pmem[id].allocator.bitmap.bitm_alloc[i].bit == + data->index) { + ret = pmem[id].allocator.bitmap.bitm_alloc[i].quanta * + pmem[id].quantum; + break; + } + + mutex_unlock(&pmem[id].arena_mutex); +#if PMEM_DEBUG + if (i >= pmem[id].allocator.bitmap.bitmap_allocs) + pr_alert("pmem: %s: can't find bitnum %d in " + "alloc'd array!\n", __func__, data->index); +#endif + return ret; +} + +static unsigned long pmem_len_system(int id, struct pmem_data *data) +{ + unsigned long ret = 0; + + mutex_lock(&pmem[id].arena_mutex); + + ret = ((struct alloc_list *)data->index)->size; + mutex_unlock(&pmem[id].arena_mutex); + + return ret; } static int pmem_map_garbage(int id, struct vm_area_struct *vma, @@ -509,18 +1417,25 @@ static int pmem_map_pfn_range(int id, struct vm_area_struct *vma, struct pmem_data *data, unsigned long offset, unsigned long len) { + int ret; DLOG("map offset %lx len %lx\n", offset, len); BUG_ON(!PMEM_IS_PAGE_ALIGNED(vma->vm_start)); BUG_ON(!PMEM_IS_PAGE_ALIGNED(vma->vm_end)); BUG_ON(!PMEM_IS_PAGE_ALIGNED(len)); BUG_ON(!PMEM_IS_PAGE_ALIGNED(offset)); - if (io_remap_pfn_range(vma, vma->vm_start + offset, - (pmem_start_addr(id, data) + offset) >> PAGE_SHIFT, - len, vma->vm_page_prot)) { - return -EAGAIN; + ret = io_remap_pfn_range(vma, vma->vm_start + offset, + (pmem[id].start_addr(id, data) + offset) >> PAGE_SHIFT, + len, vma->vm_page_prot); + if (ret) { +#if PMEM_DEBUG + pr_alert("pmem: %s: io_remap_pfn_range fails with " + "return value: %d!\n", __func__, ret); +#endif + + ret = -EAGAIN; } - return 0; + return ret; } static int pmem_remap_pfn_range(int id, struct vm_area_struct *vma, @@ -538,13 +1453,21 @@ static void pmem_vma_open(struct vm_area_struct *vma) struct file *file = vma->vm_file; struct pmem_data *data = file->private_data; int id = get_id(file); + +#if PMEM_DEBUG_MSGS + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; +#endif + DLOG("Dev %s(id: %d) pid %u(%s) ppid %u file %p count %ld\n", + get_name(file), id, current->pid, + get_task_comm(currtask_name, current), + current->parent->pid, file, file_count(file)); /* this should never be called as we don't support copying pmem * ranges via fork */ + down_read(&data->sem); BUG_ON(!has_allocation(file)); - down_write(&data->sem); /* remap the garbage pages, forkers don't get access to the data */ pmem_unmap_pfn_range(id, vma, data, 0, vma->vm_start - vma->vm_end); - up_write(&data->sem); + up_read(&data->sem); } static void pmem_vma_close(struct vm_area_struct *vma) @@ -552,15 +1475,29 @@ static void pmem_vma_close(struct vm_area_struct *vma) struct file *file = vma->vm_file; struct pmem_data *data = file->private_data; - DLOG("current %u ppid %u file %p count %d\n", current->pid, - current->parent->pid, file, file_count(file)); - if (unlikely(!is_pmem_file(file) || !has_allocation(file))) { - printk(KERN_WARNING "pmem: something is very wrong, you are " +#if PMEM_DEBUG_MSGS + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; +#endif + DLOG("Dev %s(id: %d) pid %u(%s) ppid %u file %p count %ld\n", + get_name(file), get_id(file), current->pid, + get_task_comm(currtask_name, current), + current->parent->pid, file, file_count(file)); + + if (unlikely(!is_pmem_file(file))) { + pr_warning("pmem: something is very wrong, you are " "closing a vm backing an allocation that doesn't " "exist!\n"); return; } + down_write(&data->sem); + if (unlikely(!has_allocation(file))) { + up_write(&data->sem); + pr_warning("pmem: something is very wrong, you are " + "closing a vm backing an allocation that doesn't " + "exist!\n"); + return; + } if (data->vma == vma) { data->vma = NULL; if ((data->flags & PMEM_FLAGS_CONNECTED) && @@ -578,64 +1515,78 @@ static struct vm_operations_struct vm_ops = { static int pmem_mmap(struct file *file, struct vm_area_struct *vma) { - struct pmem_data *data; + struct pmem_data *data = file->private_data; int index; unsigned long vma_size = vma->vm_end - vma->vm_start; int ret = 0, id = get_id(file); +#if PMEM_DEBUG_MSGS + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; +#endif + if (!data) { + pr_err("pmem: Invalid file descriptor, no private data\n"); + return -EINVAL; + } + DLOG("pid %u(%s) mmap vma_size %lu on dev %s(id: %d)\n", current->pid, + get_task_comm(currtask_name, current), vma_size, + get_name(file), id); if (vma->vm_pgoff || !PMEM_IS_PAGE_ALIGNED(vma_size)) { #if PMEM_DEBUG - printk(KERN_ERR "pmem: mmaps must be at offset zero, aligned" + pr_err("pmem: mmaps must be at offset zero, aligned" " and a multiple of pages_size.\n"); #endif return -EINVAL; } - data = (struct pmem_data *)file->private_data; down_write(&data->sem); /* check this file isn't already mmaped, for submaps check this file * has never been mmaped */ if ((data->flags & PMEM_FLAGS_SUBMAP) || (data->flags & PMEM_FLAGS_UNSUBMAP)) { #if PMEM_DEBUG - printk(KERN_ERR "pmem: you can only mmap a pmem file once, " + pr_err("pmem: you can only mmap a pmem file once, " "this file is already mmaped. %x\n", data->flags); #endif ret = -EINVAL; goto error; } /* if file->private_data == unalloced, alloc*/ - if (data && data->index == -1) { - down_write(&pmem[id].bitmap_sem); - index = pmem_allocate(id, vma->vm_end - vma->vm_start); - up_write(&pmem[id].bitmap_sem); + if (data->index == -1) { + mutex_lock(&pmem[id].arena_mutex); + index = pmem[id].allocate(id, + vma->vm_end - vma->vm_start, + SZ_4K); + mutex_unlock(&pmem[id].arena_mutex); + /* either no space was available or an error occured */ + if (index == -1) { + pr_err("pmem: mmap unable to allocate memory" + "on %s\n", get_name(file)); + ret = -ENOMEM; + goto error; + } + /* store the index of a successful allocation */ data->index = index; } - /* either no space was available or an error occured */ - if (!has_allocation(file)) { - ret = -EINVAL; - printk("pmem: could not find allocation for map.\n"); - goto error; - } - if (pmem_len(id, data) < vma_size) { + if (pmem[id].len(id, data) < vma_size) { #if PMEM_DEBUG - printk(KERN_WARNING "pmem: mmap size [%lu] does not match" - "size of backing region [%lu].\n", vma_size, - pmem_len(id, data)); + pr_err("pmem: mmap size [%lu] does not match" + " size of backing region [%lu].\n", vma_size, + pmem[id].len(id, data)); #endif ret = -EINVAL; goto error; } - vma->vm_pgoff = pmem_start_addr(id, data) >> PAGE_SHIFT; - vma->vm_page_prot = pmem_access_prot(file, vma->vm_page_prot); + vma->vm_pgoff = pmem[id].start_addr(id, data) >> PAGE_SHIFT; + + vma->vm_page_prot = pmem_phys_mem_access_prot(file, vma->vm_page_prot); if (data->flags & PMEM_FLAGS_CONNECTED) { struct pmem_region_node *region_node; struct list_head *elt; if (pmem_map_garbage(id, vma, data, 0, vma_size)) { - printk("pmem: mmap failed in kernel!\n"); + pr_alert("pmem: mmap failed in kernel!\n"); ret = -EAGAIN; goto error; } @@ -663,7 +1614,7 @@ static int pmem_mmap(struct file *file, struct vm_area_struct *vma) current->pid); } else { if (pmem_map_pfn_range(id, vma, data, 0, vma_size)) { - printk(KERN_INFO "pmem: mmap failed in kernel!\n"); + pr_err("pmem: mmap failed in kernel!\n"); ret = -EAGAIN; goto error; } @@ -681,103 +1632,155 @@ static int pmem_mmap(struct file *file, struct vm_area_struct *vma) int get_pmem_user_addr(struct file *file, unsigned long *start, unsigned long *len) { - struct pmem_data *data; - if (!is_pmem_file(file) || !has_allocation(file)) { + int ret = -1; + + if (is_pmem_file(file)) { + struct pmem_data *data = file->private_data; + + down_read(&data->sem); + if (has_allocation(file)) { + if (data->vma) { + *start = data->vma->vm_start; + *len = data->vma->vm_end - data->vma->vm_start; + } else { + *start = *len = 0; #if PMEM_DEBUG - printk(KERN_INFO "pmem: requested pmem data from invalid" - "file.\n"); + pr_err("pmem: %s: no vma present.\n", + __func__); #endif - return -1; - } - data = (struct pmem_data *)file->private_data; - down_read(&data->sem); - if (data->vma) { - *start = data->vma->vm_start; - *len = data->vma->vm_end - data->vma->vm_start; - } else { - *start = 0; - *len = 0; + } + ret = 0; + } + up_read(&data->sem); } - up_read(&data->sem); - return 0; + +#if PMEM_DEBUG + if (ret) + pr_err("pmem: %s: requested pmem data from invalid" + "file.\n", __func__); +#endif + return ret; } int get_pmem_addr(struct file *file, unsigned long *start, unsigned long *vstart, unsigned long *len) { - struct pmem_data *data; - int id; + int ret = -1; - if (!is_pmem_file(file) || !has_allocation(file)) { - return -1; - } + if (is_pmem_file(file)) { + struct pmem_data *data = file->private_data; - data = (struct pmem_data *)file->private_data; - if (data->index == -1) { + down_read(&data->sem); + if (has_allocation(file)) { + int id = get_id(file); + + *start = pmem[id].start_addr(id, data); + *len = pmem[id].len(id, data); + *vstart = (unsigned long) + pmem_start_vaddr(id, data); + up_read(&data->sem); #if PMEM_DEBUG - printk(KERN_INFO "pmem: requested pmem data from file with no " - "allocation.\n"); - return -1; + down_write(&data->sem); + data->ref++; + up_write(&data->sem); +#endif + DLOG("returning start %#lx len %lu " + "vstart %#lx\n", + *start, *len, *vstart); + ret = 0; + } else { + up_read(&data->sem); + } + } + return ret; +} + +int get_pmem_file(unsigned int fd, unsigned long *start, unsigned long *vstart, + unsigned long *len, struct file **filp) +{ + int ret = -1; + struct file *file = fget(fd); + + if (unlikely(file == NULL)) { + pr_err("pmem: %s: requested data from file " + "descriptor that doesn't exist.\n", __func__); + } else { +#if PMEM_DEBUG_MSGS + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; #endif + DLOG("filp %p rdev %d pid %u(%s) file %p(%ld)" + " dev %s(id: %d)\n", filp, + file->f_dentry->d_inode->i_rdev, + current->pid, get_task_comm(currtask_name, current), + file, file_count(file), get_name(file), get_id(file)); + + if (!get_pmem_addr(file, start, vstart, len)) { + if (filp) + *filp = file; + ret = 0; + } else { + fput(file); + } } - id = get_id(file); + return ret; +} +EXPORT_SYMBOL(get_pmem_file); - down_read(&data->sem); - *start = pmem_start_addr(id, data); - *len = pmem_len(id, data); - *vstart = (unsigned long)pmem_start_vaddr(id, data); - up_read(&data->sem); -#if PMEM_DEBUG - down_write(&data->sem); - data->ref++; - up_write(&data->sem); +int get_pmem_fd(int fd, unsigned long *start, unsigned long *len) +{ + unsigned long vstart; + return get_pmem_file(fd, start, &vstart, len, NULL); +} +EXPORT_SYMBOL(get_pmem_fd); + +void put_pmem_file(struct file *file) +{ +#if PMEM_DEBUG_MSGS + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; +#endif + DLOG("rdev %d pid %u(%s) file %p(%ld)" " dev %s(id: %d)\n", + file->f_dentry->d_inode->i_rdev, current->pid, + get_task_comm(currtask_name, current), file, + file_count(file), get_name(file), get_id(file)); + if (is_pmem_file(file)) { +#if PMEM_DEBUG + struct pmem_data *data = file->private_data; + + down_write(&data->sem); + if (!data->ref--) { + data->ref++; + pr_alert("pmem: pmem_put > pmem_get %s " + "(pid %d)\n", + pmem[get_id(file)].dev.name, data->pid); + BUG(); + } + up_write(&data->sem); #endif - return 0; + fput(file); + } } +EXPORT_SYMBOL(put_pmem_file); -int get_pmem_file(int fd, unsigned long *start, unsigned long *vstart, - unsigned long *len, struct file **filp) +void put_pmem_fd(int fd) { - struct file *file; + int put_needed; + struct file *file = fget_light(fd, &put_needed); - file = fget(fd); - if (unlikely(file == NULL)) { - printk(KERN_INFO "pmem: requested data from file descriptor " - "that doesn't exist."); - return -1; + if (file) { + put_pmem_file(file); + fput_light(file, put_needed); } - - if (get_pmem_addr(file, start, vstart, len)) - goto end; - - if (filp) - *filp = file; - return 0; -end: - fput(file); - return -1; } -void put_pmem_file(struct file *file) +void flush_pmem_fd(int fd, unsigned long offset, unsigned long len) { - struct pmem_data *data; - int id; + int fput_needed; + struct file *file = fget_light(fd, &fput_needed); - if (!is_pmem_file(file)) - return; - id = get_id(file); - data = (struct pmem_data *)file->private_data; -#if PMEM_DEBUG - down_write(&data->sem); - if (data->ref == 0) { - printk("pmem: pmem_put > pmem_get %s (pid %d)\n", - pmem[id].dev.name, data->pid); - BUG(); + if (file) { + flush_pmem_file(file, offset, len); + fput_light(file, fput_needed); } - data->ref--; - up_write(&data->sem); -#endif - fput(file); } void flush_pmem_file(struct file *file, unsigned long offset, unsigned long len) @@ -788,21 +1791,50 @@ void flush_pmem_file(struct file *file, unsigned long offset, unsigned long len) struct pmem_region_node *region_node; struct list_head *elt; void *flush_start, *flush_end; - - if (!is_pmem_file(file) || !has_allocation(file)) { +#ifdef CONFIG_OUTER_CACHE + unsigned long phy_start, phy_end; +#endif + if (!is_pmem_file(file)) return; - } id = get_id(file); - data = (struct pmem_data *)file->private_data; - if (!pmem[id].cached || file->f_flags & O_SYNC) + if (!pmem[id].cached) return; + /* is_pmem_file fails if !file */ + data = file->private_data; + down_read(&data->sem); + if (!has_allocation(file)) + goto end; + vaddr = pmem_start_vaddr(id, data); + + if (pmem[id].allocator_type == PMEM_ALLOCATORTYPE_SYSTEM) { + dmac_flush_range(vaddr, + (void *)((unsigned long)vaddr + + ((struct alloc_list *)(data->index))->size)); +#ifdef CONFIG_OUTER_CACHE + phy_start = pmem_start_addr_system(id, data); + + phy_end = phy_start + + ((struct alloc_list *)(data->index))->size; + + outer_flush_range(phy_start, phy_end); +#endif + goto end; + } /* if this isn't a submmapped file, flush the whole thing */ if (unlikely(!(data->flags & PMEM_FLAGS_CONNECTED))) { - dmac_flush_range(vaddr, vaddr + pmem_len(id, data)); + dmac_flush_range(vaddr, vaddr + pmem[id].len(id, data)); +#ifdef CONFIG_OUTER_CACHE + phy_start = (unsigned long)vaddr - + (unsigned long)pmem[id].vbase + pmem[id].base; + + phy_end = phy_start + pmem[id].len(id, data); + + outer_flush_range(phy_start, phy_end); +#endif goto end; } /* otherwise, flush the region of the file we are drawing */ @@ -814,6 +1846,15 @@ void flush_pmem_file(struct file *file, unsigned long offset, unsigned long len) flush_start = vaddr + region_node->region.offset; flush_end = flush_start + region_node->region.len; dmac_flush_range(flush_start, flush_end); +#ifdef CONFIG_OUTER_CACHE + + phy_start = (unsigned long)flush_start - + (unsigned long)pmem[id].vbase + pmem[id].base; + + phy_end = phy_start + region_node->region.len; + + outer_flush_range(phy_start, phy_end); +#endif break; } } @@ -821,45 +1862,145 @@ void flush_pmem_file(struct file *file, unsigned long offset, unsigned long len) up_read(&data->sem); } +int pmem_cache_maint(struct file *file, unsigned int cmd, + struct pmem_addr *pmem_addr) +{ + struct pmem_data *data; + int id; + unsigned long vaddr, paddr, length, offset, + pmem_len, pmem_start_addr; + + /* Called from kernel-space so file may be NULL */ + if (!file) + return -EBADF; + + data = file->private_data; + id = get_id(file); + + if (!pmem[id].cached) + return 0; + + offset = pmem_addr->offset; + length = pmem_addr->length; + + down_read(&data->sem); + if (!has_allocation(file)) { + up_read(&data->sem); + return -EINVAL; + } + pmem_len = pmem[id].len(id, data); + pmem_start_addr = pmem[id].start_addr(id, data); + up_read(&data->sem); + + if (offset + length > pmem_len) + return -EINVAL; + + vaddr = pmem_addr->vaddr; + paddr = pmem_start_addr + offset; + + DLOG("pmem cache maint on dev %s(id: %d)" + "(vaddr %lx paddr %lx len %lu bytes)\n", + get_name(file), id, vaddr, paddr, length); + if (cmd == PMEM_CLEAN_INV_CACHES) + clean_and_invalidate_caches(vaddr, + length, paddr); + else if (cmd == PMEM_CLEAN_CACHES) + clean_caches(vaddr, length, paddr); + else if (cmd == PMEM_INV_CACHES) + invalidate_caches(vaddr, length, paddr); + + return 0; +} +EXPORT_SYMBOL(pmem_cache_maint); + static int pmem_connect(unsigned long connect, struct file *file) { - struct pmem_data *data = (struct pmem_data *)file->private_data; - struct pmem_data *src_data; - struct file *src_file; int ret = 0, put_needed; + struct file *src_file; + + if (!file) { + pr_err("pmem: %s: NULL file pointer passed in, " + "bailing out!\n", __func__); + ret = -EINVAL; + goto leave; + } - down_write(&data->sem); - /* retrieve the src file and check it is a pmem file with an alloc */ src_file = fget_light(connect, &put_needed); - DLOG("connect %p to %p\n", file, src_file); + if (!src_file) { - printk("pmem: src file not found!\n"); - ret = -EINVAL; - goto err_no_file; + pr_err("pmem: %s: src file not found!\n", __func__); + ret = -EBADF; + goto leave; } - if (unlikely(!is_pmem_file(src_file) || !has_allocation(src_file))) { - printk(KERN_INFO "pmem: src file is not a pmem file or has no " - "alloc!\n"); + + if (src_file == file) { /* degenerative case, operator error */ + pr_err("pmem: %s: src_file and passed in file are " + "the same; refusing to connect to self!\n", __func__); ret = -EINVAL; - goto err_bad_file; + goto put_src_file; } - src_data = (struct pmem_data *)src_file->private_data; - if (has_allocation(file) && (data->index != src_data->index)) { - printk("pmem: file is already mapped but doesn't match this" - " src_file!\n"); + if (unlikely(!is_pmem_file(src_file))) { + pr_err("pmem: %s: src file is not a pmem file!\n", + __func__); ret = -EINVAL; - goto err_bad_file; - } - data->index = src_data->index; - data->flags |= PMEM_FLAGS_CONNECTED; - data->master_fd = connect; - data->master_file = src_file; + goto put_src_file; + } else { + struct pmem_data *src_data = src_file->private_data; + + if (!src_data) { + pr_err("pmem: %s: src file pointer has no" + "private data, bailing out!\n", __func__); + ret = -EINVAL; + goto put_src_file; + } + + down_read(&src_data->sem); + + if (unlikely(!has_allocation(src_file))) { + up_read(&src_data->sem); + pr_err("pmem: %s: src file has no allocation!\n", + __func__); + ret = -EINVAL; + } else { + struct pmem_data *data; + int src_index = src_data->index; + + up_read(&src_data->sem); + + data = file->private_data; + if (!data) { + pr_err("pmem: %s: passed in file " + "pointer has no private data, bailing" + " out!\n", __func__); + ret = -EINVAL; + goto put_src_file; + } + + down_write(&data->sem); + if (has_allocation(file) && + (data->index != src_index)) { + up_write(&data->sem); -err_bad_file: + pr_err("pmem: %s: file is already " + "mapped but doesn't match this " + "src_file!\n", __func__); + ret = -EINVAL; + } else { + data->index = src_index; + data->flags |= PMEM_FLAGS_CONNECTED; + data->master_fd = connect; + data->master_file = src_file; + + up_write(&data->sem); + + DLOG("connect %p to %p\n", file, src_file); + } + } + } +put_src_file: fput_light(src_file, put_needed); -err_no_file: - up_write(&data->sem); +leave: return ret; } @@ -878,16 +2019,23 @@ static int pmem_lock_data_and_mm(struct file *file, struct pmem_data *data, { int ret = 0; struct mm_struct *mm = NULL; +#if PMEM_DEBUG_MSGS + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; +#endif + DLOG("pid %u(%s) file %p(%ld)\n", + current->pid, get_task_comm(currtask_name, current), + file, file_count(file)); + *locked_mm = NULL; lock_mm: down_read(&data->sem); if (PMEM_IS_SUBMAP(data)) { mm = get_task_mm(data->task); if (!mm) { + up_read(&data->sem); #if PMEM_DEBUG - printk("pmem: can't remap task is gone!\n"); + pr_alert("pmem: can't remap - task is gone!\n"); #endif - up_read(&data->sem); return -1; } } @@ -902,7 +2050,7 @@ static int pmem_lock_data_and_mm(struct file *file, struct pmem_data *data, * once */ if (PMEM_IS_SUBMAP(data) && !mm) { pmem_unlock_data_and_mm(data, mm); - up_write(&data->sem); + DLOG("mapping contention, repeating mmap op\n"); goto lock_mm; } /* now check that vma.mm is still there, it could have been @@ -916,6 +2064,9 @@ static int pmem_lock_data_and_mm(struct file *file, struct pmem_data *data, data->flags &= ~(PMEM_FLAGS_SUBMAP); } pmem_unlock_data_and_mm(data, mm); +#if PMEM_DEBUG + pr_alert("pmem: vma.mm went away!\n"); +#endif return -1; } *locked_mm = mm; @@ -930,14 +2081,28 @@ int pmem_remap(struct pmem_region *region, struct file *file, struct mm_struct *mm = NULL; struct list_head *elt, *elt2; int id = get_id(file); - struct pmem_data *data = (struct pmem_data *)file->private_data; + struct pmem_data *data; + + DLOG("operation %#x, region offset %ld, region len %ld\n", + operation, region->offset, region->len); + + if (!is_pmem_file(file)) { +#if PMEM_DEBUG + pr_err("pmem: remap request for non-pmem file descriptor\n"); +#endif + return -EINVAL; + } + + /* is_pmem_file fails if !file */ + data = file->private_data; /* pmem region must be aligned on a page boundry */ if (unlikely(!PMEM_IS_PAGE_ALIGNED(region->offset) || !PMEM_IS_PAGE_ALIGNED(region->len))) { #if PMEM_DEBUG - printk("pmem: request for unaligned pmem suballocation " - "%lx %lx\n", region->offset, region->len); + pr_err("pmem: request for unaligned pmem" + "suballocation %lx %lx\n", + region->offset, region->len); #endif return -EINVAL; } @@ -955,18 +2120,18 @@ int pmem_remap(struct pmem_region *region, struct file *file, * that back in it */ if (!is_master_owner(file)) { #if PMEM_DEBUG - printk("pmem: remap requested from non-master process\n"); + pr_err("pmem: remap requested from non-master process\n"); #endif ret = -EINVAL; goto err; } /* check that the requested range is within the src allocation */ - if (unlikely((region->offset > pmem_len(id, data)) || - (region->len > pmem_len(id, data)) || - (region->offset + region->len > pmem_len(id, data)))) { + if (unlikely((region->offset > pmem[id].len(id, data)) || + (region->len > pmem[id].len(id, data)) || + (region->offset + region->len > pmem[id].len(id, data)))) { #if PMEM_DEBUG - printk(KERN_INFO "pmem: suballoc doesn't fit in src_file!\n"); + pr_err("pmem: suballoc doesn't fit in src_file!\n"); #endif ret = -EINVAL; goto err; @@ -978,7 +2143,7 @@ int pmem_remap(struct pmem_region *region, struct file *file, if (!region_node) { ret = -ENOMEM; #if PMEM_DEBUG - printk(KERN_INFO "No space to allocate metadata!"); + pr_alert("pmem: No space to allocate remap metadata!"); #endif goto err; } @@ -999,8 +2164,8 @@ int pmem_remap(struct pmem_region *region, struct file *file, } if (!found) { #if PMEM_DEBUG - printk("pmem: Unmap region does not map any mapped " - "region!"); + pr_err("pmem: Unmap region does not map any" + " mapped region!"); #endif ret = -EINVAL; goto err; @@ -1010,10 +2175,10 @@ int pmem_remap(struct pmem_region *region, struct file *file, if (data->vma && PMEM_IS_SUBMAP(data)) { if (operation == PMEM_MAP) ret = pmem_remap_pfn_range(id, data->vma, data, - region->offset, region->len); + region->offset, region->len); else if (operation == PMEM_UNMAP) ret = pmem_unmap_pfn_range(id, data->vma, data, - region->offset, region->len); + region->offset, region->len); } err: @@ -1054,63 +2219,83 @@ static void pmem_revoke(struct file *file, struct pmem_data *data) static void pmem_get_size(struct pmem_region *region, struct file *file) { - struct pmem_data *data = (struct pmem_data *)file->private_data; + /* called via ioctl file op, so file guaranteed to be not NULL */ + struct pmem_data *data = file->private_data; int id = get_id(file); + down_read(&data->sem); if (!has_allocation(file)) { region->offset = 0; region->len = 0; - return; } else { - region->offset = pmem_start_addr(id, data); - region->len = pmem_len(id, data); + region->offset = pmem[id].start_addr(id, data); + region->len = pmem[id].len(id, data); } - DLOG("offset %lx len %lx\n", region->offset, region->len); + up_read(&data->sem); + DLOG("offset 0x%lx len 0x%lx\n", region->offset, region->len); } static long pmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { - struct pmem_data *data; + /* called from user space as file op, so file guaranteed to be not + * NULL + */ + struct pmem_data *data = file->private_data; int id = get_id(file); +#if PMEM_DEBUG_MSGS + char currtask_name[ + FIELD_SIZEOF(struct task_struct, comm) + 1]; +#endif + + DLOG("pid %u(%s) file %p(%ld) cmd %#x, dev %s(id: %d)\n", + current->pid, get_task_comm(currtask_name, current), + file, file_count(file), cmd, get_name(file), id); switch (cmd) { case PMEM_GET_PHYS: { struct pmem_region region; + DLOG("get_phys\n"); + down_read(&data->sem); if (!has_allocation(file)) { region.offset = 0; region.len = 0; } else { - data = (struct pmem_data *)file->private_data; - region.offset = pmem_start_addr(id, data); - region.len = pmem_len(id, data); + region.offset = pmem[id].start_addr(id, data); + region.len = pmem[id].len(id, data); } - printk(KERN_INFO "pmem: request for physical address of pmem region " - "from process %d.\n", current->pid); + up_read(&data->sem); + if (copy_to_user((void __user *)arg, ®ion, sizeof(struct pmem_region))) return -EFAULT; + + DLOG("pmem: successful request for " + "physical address of pmem region id %d, " + "offset 0x%lx, len 0x%lx\n", + id, region.offset, region.len); + break; } case PMEM_MAP: { struct pmem_region region; + DLOG("map\n"); if (copy_from_user(®ion, (void __user *)arg, sizeof(struct pmem_region))) return -EFAULT; - data = (struct pmem_data *)file->private_data; return pmem_remap(®ion, file, PMEM_MAP); } break; case PMEM_UNMAP: { struct pmem_region region; + DLOG("unmap\n"); if (copy_from_user(®ion, (void __user *)arg, sizeof(struct pmem_region))) return -EFAULT; - data = (struct pmem_data *)file->private_data; return pmem_remap(®ion, file, PMEM_UNMAP); break; } @@ -1136,169 +2321,369 @@ static long pmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return -EFAULT; break; } + case PMEM_GET_FREE_SPACE: + { + struct pmem_freespace fs; + DLOG("get freespace on %s(id: %d)\n", + get_name(file), id); + + mutex_lock(&pmem[id].arena_mutex); + pmem[id].free_space(id, &fs); + mutex_unlock(&pmem[id].arena_mutex); + + DLOG("%s(id: %d) total free %lu, largest %lu\n", + get_name(file), id, fs.total, fs.largest); + + if (copy_to_user((void __user *)arg, &fs, + sizeof(struct pmem_freespace))) + return -EFAULT; + break; + } + case PMEM_ALLOCATE: { - if (has_allocation(file)) + int ret = 0; + DLOG("allocate, id %d\n", id); + down_write(&data->sem); + if (has_allocation(file)) { + pr_err("pmem: Existing allocation found on " + "this file descrpitor\n"); + up_write(&data->sem); return -EINVAL; - data = (struct pmem_data *)file->private_data; - data->index = pmem_allocate(id, arg); - break; + } + + mutex_lock(&pmem[id].arena_mutex); + data->index = pmem[id].allocate(id, + arg, + SZ_4K); + mutex_unlock(&pmem[id].arena_mutex); + ret = data->index == -1 ? -ENOMEM : + data->index; + up_write(&data->sem); + return ret; + } + case PMEM_ALLOCATE_ALIGNED: + { + struct pmem_allocation alloc; + int ret = 0; + + if (copy_from_user(&alloc, (void __user *)arg, + sizeof(struct pmem_allocation))) + return -EFAULT; + DLOG("allocate id align %d %u\n", id, alloc.align); + down_write(&data->sem); + if (has_allocation(file)) { + pr_err("pmem: Existing allocation found on " + "this file descrpitor\n"); + up_write(&data->sem); + return -EINVAL; + } + + if (alloc.align & (alloc.align - 1)) { + pr_err("pmem: Alignment is not a power of 2\n"); + return -EINVAL; + } + + if (alloc.align != SZ_4K && + (pmem[id].allocator_type != + PMEM_ALLOCATORTYPE_BITMAP)) { + pr_err("pmem: Non 4k alignment requires bitmap" + " allocator on %s\n", pmem[id].name); + return -EINVAL; + } + + if (alloc.align > SZ_1M || + alloc.align < SZ_4K) { + pr_err("pmem: Invalid Alignment (%u) " + "specified\n", alloc.align); + return -EINVAL; + } + + mutex_lock(&pmem[id].arena_mutex); + data->index = pmem[id].allocate(id, + alloc.size, + alloc.align); + mutex_unlock(&pmem[id].arena_mutex); + ret = data->index == -1 ? -ENOMEM : + data->index; + up_write(&data->sem); + return ret; } case PMEM_CONNECT: DLOG("connect\n"); return pmem_connect(arg, file); - break; - case PMEM_CACHE_FLUSH: + case PMEM_CLEAN_INV_CACHES: + case PMEM_CLEAN_CACHES: + case PMEM_INV_CACHES: { - struct pmem_region region; - DLOG("flush\n"); - if (copy_from_user(®ion, (void __user *)arg, - sizeof(struct pmem_region))) + struct pmem_addr pmem_addr; + + if (copy_from_user(&pmem_addr, (void __user *)arg, + sizeof(struct pmem_addr))) return -EFAULT; - flush_pmem_file(file, region.offset, region.len); - break; + + return pmem_cache_maint(file, cmd, &pmem_addr); } default: if (pmem[id].ioctl) return pmem[id].ioctl(file, cmd, arg); + + DLOG("ioctl invalid (%#x)\n", cmd); return -EINVAL; } return 0; } -#if PMEM_DEBUG -static ssize_t debug_open(struct inode *inode, struct file *file) +static void ioremap_pmem(int id) { - file->private_data = inode->i_private; - return 0; + if (pmem[id].cached) + pmem[id].vbase = ioremap_cached(pmem[id].base, pmem[id].size); +#ifdef ioremap_ext_buffered + else if (pmem[id].buffered) + pmem[id].vbase = ioremap_ext_buffered(pmem[id].base, + pmem[id].size); +#endif + else + pmem[id].vbase = ioremap(pmem[id].base, pmem[id].size); } -static ssize_t debug_read(struct file *file, char __user *buf, size_t count, - loff_t *ppos) +int pmem_setup(struct android_pmem_platform_data *pdata, + long (*ioctl)(struct file *, unsigned int, unsigned long), + int (*release)(struct inode *, struct file *)) { - struct list_head *elt, *elt2; - struct pmem_data *data; - struct pmem_region_node *region_node; - int id = (int)file->private_data; - const int debug_bufmax = 4096; - static char buffer[4096]; - int n = 0; + int i, index = 0, id; - DLOG("debug open\n"); - n = scnprintf(buffer, debug_bufmax, - "pid #: mapped regions (offset, len) (offset,len)...\n"); + if (id_count >= PMEM_MAX_DEVICES) { + pr_alert("pmem: %s: unable to register driver(%s) - no more " + "devices available!\n", __func__, pdata->name); + goto err_no_mem; + } - mutex_lock(&pmem[id].data_list_lock); - list_for_each(elt, &pmem[id].data_list) { - data = list_entry(elt, struct pmem_data, list); - down_read(&data->sem); - n += scnprintf(buffer + n, debug_bufmax - n, "pid %u:", - data->pid); - list_for_each(elt2, &data->region_list) { - region_node = list_entry(elt2, struct pmem_region_node, - list); - n += scnprintf(buffer + n, debug_bufmax - n, - "(%lx,%lx) ", - region_node->region.offset, - region_node->region.len); - } - n += scnprintf(buffer + n, debug_bufmax - n, "\n"); - up_read(&data->sem); + if (!pdata->size) { + pr_alert("pmem: %s: unable to register pmem driver(%s) - zero " + "size passed in!\n", __func__, pdata->name); + goto err_no_mem; } - mutex_unlock(&pmem[id].data_list_lock); - n++; - buffer[n] = 0; - return simple_read_from_buffer(buf, count, ppos, buffer, n); -} + id = id_count++; -static struct file_operations debug_fops = { - .read = debug_read, - .open = debug_open, -}; -#endif + pmem[id].id = id; -#if 0 -static struct miscdevice pmem_dev = { - .name = "pmem", - .fops = &pmem_fops, -}; -#endif + if (pmem[id].allocate) { + pr_alert("pmem: %s: unable to register pmem driver - " + "duplicate registration of %s!\n", + __func__, pdata->name); + goto err_no_mem; + } -int pmem_setup(struct android_pmem_platform_data *pdata, - long (*ioctl)(struct file *, unsigned int, unsigned long), - int (*release)(struct inode *, struct file *)) -{ - int err = 0; - int i, index = 0; - int id = id_count; - id_count++; + pmem[id].allocator_type = pdata->allocator_type; + + /* 'quantum' is a "hidden" variable that defaults to 0 in the board + * files */ + pmem[id].quantum = pdata->quantum ?: PMEM_MIN_ALLOC; + if (pmem[id].quantum < PMEM_MIN_ALLOC || + !is_power_of_2(pmem[id].quantum)) { + pr_alert("pmem: %s: unable to register pmem driver %s - " + "invalid quantum value (%#x)!\n", + __func__, pdata->name, pmem[id].quantum); + goto err_reset_pmem_info; + } + + if (pdata->size % pmem[id].quantum) { + /* bad alignment for size! */ + pr_alert("pmem: %s: Unable to register driver %s - " + "memory region size (%#lx) is not a multiple of " + "quantum size(%#x)!\n", __func__, pdata->name, + pdata->size, pmem[id].quantum); + goto err_reset_pmem_info; + } - pmem[id].no_allocator = pdata->no_allocator; pmem[id].cached = pdata->cached; pmem[id].buffered = pdata->buffered; - pmem[id].base = pdata->start; pmem[id].size = pdata->size; + pmem[id].memory_type = pdata->memory_type; + strlcpy(pmem[id].name, pdata->name, PMEM_NAME_SIZE); + + pmem[id].num_entries = pmem[id].size / pmem[id].quantum; + + memset(&pmem[id].kobj, 0, sizeof(pmem[0].kobj)); + pmem[id].kobj.kset = pmem_kset; + + switch (pmem[id].allocator_type) { + case PMEM_ALLOCATORTYPE_ALLORNOTHING: + pmem[id].allocate = pmem_allocator_all_or_nothing; + pmem[id].free = pmem_free_all_or_nothing; + pmem[id].free_space = pmem_free_space_all_or_nothing; + pmem[id].len = pmem_len_all_or_nothing; + pmem[id].start_addr = pmem_start_addr_all_or_nothing; + pmem[id].num_entries = 1; + pmem[id].quantum = pmem[id].size; + pmem[id].allocator.all_or_nothing.allocated = 0; + + if (kobject_init_and_add(&pmem[id].kobj, + &pmem_allornothing_ktype, NULL, + "%s", pdata->name)) + goto out_put_kobj; + + break; + + case PMEM_ALLOCATORTYPE_BUDDYBESTFIT: + pmem[id].allocator.buddy_bestfit.buddy_bitmap = kmalloc( + pmem[id].num_entries * sizeof(struct pmem_bits), + GFP_KERNEL); + if (!pmem[id].allocator.buddy_bestfit.buddy_bitmap) + goto err_reset_pmem_info; + + memset(pmem[id].allocator.buddy_bestfit.buddy_bitmap, 0, + sizeof(struct pmem_bits) * pmem[id].num_entries); + + for (i = sizeof(pmem[id].num_entries) * 8 - 1; i >= 0; i--) + if ((pmem[id].num_entries) & 1<name)) + goto out_put_kobj; + + break; + + case PMEM_ALLOCATORTYPE_BITMAP: /* 0, default if not explicit */ + pmem[id].allocator.bitmap.bitm_alloc = kmalloc( + PMEM_INITIAL_NUM_BITMAP_ALLOCATIONS * + sizeof(*pmem[id].allocator.bitmap.bitm_alloc), + GFP_KERNEL); + if (!pmem[id].allocator.bitmap.bitm_alloc) { + pr_alert("pmem: %s: Unable to register pmem " + "driver %s - can't allocate " + "bitm_alloc!\n", + __func__, pdata->name); + goto err_reset_pmem_info; + } + + if (kobject_init_and_add(&pmem[id].kobj, + &pmem_bitmap_ktype, NULL, + "%s", pdata->name)) + goto out_put_kobj; + + for (i = 0; i < PMEM_INITIAL_NUM_BITMAP_ALLOCATIONS; i++) { + pmem[id].allocator.bitmap.bitm_alloc[i].bit = -1; + pmem[id].allocator.bitmap.bitm_alloc[i].quanta = 0; + } + + pmem[id].allocator.bitmap.bitmap_allocs = + PMEM_INITIAL_NUM_BITMAP_ALLOCATIONS; + + pmem[id].allocator.bitmap.bitmap = + kcalloc((pmem[id].num_entries + 31) / 32, + sizeof(unsigned int), GFP_KERNEL); + if (!pmem[id].allocator.bitmap.bitmap) { + pr_alert("pmem: %s: Unable to register pmem " + "driver - can't allocate bitmap!\n", + __func__); + goto err_cant_register_device; + } + pmem[id].allocator.bitmap.bitmap_free = pmem[id].num_entries; + + pmem[id].allocate = pmem_allocator_bitmap; + pmem[id].free = pmem_free_bitmap; + pmem[id].free_space = pmem_free_space_bitmap; + pmem[id].len = pmem_len_bitmap; + pmem[id].start_addr = pmem_start_addr_bitmap; + + DLOG("bitmap allocator id %d (%s), num_entries %u, raw size " + "%lu, quanta size %u\n", + id, pdata->name, pmem[id].allocator.bitmap.bitmap_free, + pmem[id].size, pmem[id].quantum); + break; + + case PMEM_ALLOCATORTYPE_SYSTEM: + + INIT_LIST_HEAD(&pmem[id].allocator.system_mem.alist); + + pmem[id].allocator.system_mem.used = 0; + pmem[id].vbase = NULL; + + if (kobject_init_and_add(&pmem[id].kobj, + &pmem_system_ktype, NULL, + "%s", pdata->name)) + goto out_put_kobj; + + pmem[id].allocate = pmem_allocator_system; + pmem[id].free = pmem_free_system; + pmem[id].free_space = pmem_free_space_system; + pmem[id].len = pmem_len_system; + pmem[id].start_addr = pmem_start_addr_system; + pmem[id].num_entries = 0; + pmem[id].quantum = PAGE_SIZE; + + DLOG("system allocator id %d (%s), raw size %lu\n", + id, pdata->name, pmem[id].size); + break; + + default: + pr_alert("Invalid allocator type (%d) for pmem driver\n", + pdata->allocator_type); + goto err_reset_pmem_info; + } + pmem[id].ioctl = ioctl; pmem[id].release = release; - init_rwsem(&pmem[id].bitmap_sem); - mutex_init(&pmem[id].data_list_lock); + mutex_init(&pmem[id].arena_mutex); + mutex_init(&pmem[id].data_list_mutex); INIT_LIST_HEAD(&pmem[id].data_list); + pmem[id].dev.name = pdata->name; pmem[id].dev.minor = id; pmem[id].dev.fops = &pmem_fops; - printk(KERN_INFO "%s: %d init\n", pdata->name, pdata->cached); + pr_info("pmem: Initializing %s (user-space) as %s\n", + pdata->name, pdata->cached ? "cached" : "non-cached"); - err = misc_register(&pmem[id].dev); - if (err) { - printk(KERN_ALERT "Unable to register pmem driver!\n"); + if (misc_register(&pmem[id].dev)) { + pr_alert("Unable to register pmem driver!\n"); goto err_cant_register_device; } - pmem[id].num_entries = pmem[id].size / PMEM_MIN_ALLOC; - pmem[id].bitmap = kmalloc(pmem[id].num_entries * - sizeof(struct pmem_bits), GFP_KERNEL); - if (!pmem[id].bitmap) - goto err_no_mem_for_metadata; + pmem[id].base = allocate_contiguous_memory_nomap(pmem[id].size, + pmem[id].memory_type, PAGE_SIZE); - memset(pmem[id].bitmap, 0, sizeof(struct pmem_bits) * - pmem[id].num_entries); - - for (i = sizeof(pmem[id].num_entries) * 8 - 1; i >= 0; i--) { - if ((pmem[id].num_entries) & 1<name, S_IFREG | S_IRUGO, NULL, (void *)id, - &debug_fops); -#endif return 0; + error_cant_remap: - kfree(pmem[id].bitmap); -err_no_mem_for_metadata: misc_deregister(&pmem[id].dev); err_cant_register_device: +out_put_kobj: + kobject_put(&pmem[id].kobj); + if (pmem[id].allocator_type == PMEM_ALLOCATORTYPE_BUDDYBESTFIT) + kfree(pmem[id].allocator.buddy_bestfit.buddy_bitmap); + else if (pmem[id].allocator_type == PMEM_ALLOCATORTYPE_BITMAP) { + kfree(pmem[id].allocator.bitmap.bitmap); + kfree(pmem[id].allocator.bitmap.bitm_alloc); + } +err_reset_pmem_info: + pmem[id].allocate = 0; + pmem[id].dev.minor = -1; +err_no_mem: return -1; } @@ -1307,31 +2692,62 @@ static int pmem_probe(struct platform_device *pdev) struct android_pmem_platform_data *pdata; if (!pdev || !pdev->dev.platform_data) { - printk(KERN_ALERT "Unable to probe pmem!\n"); + pr_alert("Unable to probe pmem!\n"); return -1; } pdata = pdev->dev.platform_data; + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + return pmem_setup(pdata, NULL, NULL); } - static int pmem_remove(struct platform_device *pdev) { int id = pdev->id; __free_page(pfn_to_page(pmem[id].garbage_pfn)); + pm_runtime_disable(&pdev->dev); misc_deregister(&pmem[id].dev); return 0; } +static int pmem_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int pmem_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops pmem_dev_pm_ops = { + .runtime_suspend = pmem_runtime_suspend, + .runtime_resume = pmem_runtime_resume, +}; + static struct platform_driver pmem_driver = { .probe = pmem_probe, .remove = pmem_remove, - .driver = { .name = "android_pmem" } + .driver = { .name = "android_pmem", + .pm = &pmem_dev_pm_ops, + } }; static int __init pmem_init(void) { + /* create /sys/kernel/ directory */ + pmem_kset = kset_create_and_add(PMEM_SYSFS_DIR_NAME, + NULL, kernel_kobj); + if (!pmem_kset) { + pr_err("pmem(%s):kset_create_and_add fail\n", __func__); + return -ENOMEM; + } + return platform_driver_register(&pmem_driver); } diff --git a/drivers/misc/pmic8058-batt-alarm.c b/drivers/misc/pmic8058-batt-alarm.c new file mode 100644 index 00000000000..bff0720d40d --- /dev/null +++ b/drivers/misc/pmic8058-batt-alarm.c @@ -0,0 +1,753 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * Qualcomm PMIC 8058 Battery Alarm Device driver + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* PMIC 8058 Battery Alarm SSBI registers */ +#define REG_THRESHOLD 0x023 +#define REG_CTRL1 0x024 +#define REG_CTRL2 0x0AA +#define REG_PWM_CTRL 0x0A3 + +/* Available voltage threshold values */ +#define THRESHOLD_MIN_MV 2500 +#define THRESHOLD_MAX_MV 5675 +#define THRESHOLD_STEP_MV 25 + +/* Register bit definitions */ + +/* Threshold register */ +#define THRESHOLD_UPPER_MASK 0xF0 +#define THRESHOLD_LOWER_MASK 0x0F +#define THRESHOLD_UPPER_SHIFT 4 +#define THRESHOLD_LOWER_SHIFT 0 + +/* CTRL 1 register */ +#define CTRL1_BATT_ALARM_EN_MASK 0x80 +#define CTRL1_HOLD_TIME_MASK 0x70 +#define CTRL1_STATUS_UPPER_MASK 0x02 +#define CTRL1_STATUS_LOWER_MASK 0x01 +#define CTRL1_HOLD_TIME_SHIFT 4 +#define CTRL1_HOLD_TIME_MIN 0 +#define CTRL1_HOLD_TIME_MAX 7 + +/* CTRL 2 register */ +#define CTRL2_COMP_UPPER_DISABLE_MASK 0x80 +#define CTRL2_COMP_LOWER_DISABLE_MASK 0x40 +#define CTRL2_FINE_STEP_UPPER_MASK 0x30 +#define CTRL2_RANGE_EXT_UPPER_MASK 0x08 +#define CTRL2_FINE_STEP_LOWER_MASK 0x06 +#define CTRL2_RANGE_EXT_LOWER_MASK 0x01 +#define CTRL2_FINE_STEP_UPPER_SHIFT 4 +#define CTRL2_FINE_STEP_LOWER_SHIFT 1 + +/* PWM control register */ +#define PWM_CTRL_ALARM_EN_MASK 0xC0 +#define PWM_CTRL_ALARM_EN_NEVER 0x00 +#define PWM_CTRL_ALARM_EN_TCXO 0x40 +#define PWM_CTRL_ALARM_EN_PWM 0x80 +#define PWM_CTRL_ALARM_EN_ALWAYS 0xC0 +#define PWM_CTRL_PRE_MASK 0x38 +#define PWM_CTRL_DIV_MASK 0x07 +#define PWM_CTRL_PRE_SHIFT 3 +#define PWM_CTRL_DIV_SHIFT 0 +#define PWM_CTRL_PRE_MIN 0 +#define PWM_CTRL_PRE_MAX 7 +#define PWM_CTRL_DIV_MIN 1 +#define PWM_CTRL_DIV_MAX 7 + +/* PWM control input range */ +#define PWM_CTRL_PRE_INPUT_MIN 2 +#define PWM_CTRL_PRE_INPUT_MAX 9 +#define PWM_CTRL_DIV_INPUT_MIN 2 +#define PWM_CTRL_DIV_INPUT_MAX 8 + +/* Available voltage threshold values */ +#define THRESHOLD_BASIC_MIN_MV 2800 +#define THRESHOLD_EXT_MIN_MV 4400 + +/* + * Default values used during initialization: + * Slowest PWM rate to ensure minimal status jittering when crossing thresholds. + * Largest hold time also helps reduce status value jittering. Comparators + * are disabled by default and must be turned on by calling + * pm8058_batt_alarm_state_set. + */ +#define DEFAULT_THRESHOLD_LOWER 3200 +#define DEFAULT_THRESHOLD_UPPER 4300 +#define DEFAULT_HOLD_TIME PM8058_BATT_ALARM_HOLD_TIME_16_MS +#define DEFAULT_USE_PWM 1 +#define DEFAULT_PWM_SCALER 9 +#define DEFAULT_PWM_DIVIDER 8 +#define DEFAULT_LOWER_ENABLE 0 +#define DEFAULT_UPPER_ENABLE 0 + +struct pm8058_batt_alarm_device { + struct srcu_notifier_head irq_notifier_list; + struct pm8058_chip *pm_chip; + struct mutex batt_mutex; + unsigned int irq; + int notifier_count; + u8 reg_threshold; + u8 reg_ctrl1; + u8 reg_ctrl2; + u8 reg_pwm_ctrl; +}; +static struct pm8058_batt_alarm_device *the_battalarm; + +static int pm8058_reg_write(struct pm8058_chip *chip, u16 addr, u8 val, u8 mask, + u8 *reg_save) +{ + int rc = 0; + u8 reg; + + reg = (*reg_save & ~mask) | (val & mask); + if (reg != *reg_save) + rc = pm8058_write(chip, addr, ®, 1); + if (rc) + pr_err("pm8058_write failed; addr=%03X, rc=%d\n", addr, rc); + else + *reg_save = reg; + return rc; +} + +/** + * pm8058_batt_alarm_state_set - enable or disable the threshold comparators + * @enable_lower_comparator: 1 = enable comparator, 0 = disable comparator + * @enable_upper_comparator: 1 = enable comparator, 0 = disable comparator + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_state_set(int enable_lower_comparator, + int enable_upper_comparator) +{ + struct pm8058_batt_alarm_device *battdev = the_battalarm; + int rc; + u8 reg_ctrl1 = 0, reg_ctrl2 = 0; + + if (!battdev) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (!enable_lower_comparator) + reg_ctrl2 |= CTRL2_COMP_LOWER_DISABLE_MASK; + if (!enable_upper_comparator) + reg_ctrl2 |= CTRL2_COMP_UPPER_DISABLE_MASK; + + if (enable_lower_comparator || enable_upper_comparator) + reg_ctrl1 = CTRL1_BATT_ALARM_EN_MASK; + + mutex_lock(&battdev->batt_mutex); + rc = pm8058_reg_write(battdev->pm_chip, REG_CTRL1, reg_ctrl1, + CTRL1_BATT_ALARM_EN_MASK, &battdev->reg_ctrl1); + if (rc) + goto bail; + + rc = pm8058_reg_write(battdev->pm_chip, REG_CTRL2, reg_ctrl2, + CTRL2_COMP_LOWER_DISABLE_MASK | CTRL2_COMP_UPPER_DISABLE_MASK, + &battdev->reg_ctrl2); + +bail: + mutex_unlock(&battdev->batt_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(pm8058_batt_alarm_state_set); + +/** + * pm8058_batt_alarm_threshold_set - set the lower and upper alarm thresholds + * @lower_threshold_mV: battery undervoltage threshold in millivolts + * @upper_threshold_mV: battery overvoltage threshold in millivolts + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_threshold_set(int lower_threshold_mV, + int upper_threshold_mV) +{ + struct pm8058_batt_alarm_device *battdev = the_battalarm; + int step, fine_step, rc; + u8 reg_threshold = 0, reg_ctrl2 = 0; + + if (!battdev) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (lower_threshold_mV < THRESHOLD_MIN_MV + || lower_threshold_mV > THRESHOLD_MAX_MV) { + pr_err("lower threshold value, %d mV, is outside of allowable " + "range: [%d, %d] mV\n", lower_threshold_mV, + THRESHOLD_MIN_MV, THRESHOLD_MAX_MV); + return -EINVAL; + } + + if (upper_threshold_mV < THRESHOLD_MIN_MV + || upper_threshold_mV > THRESHOLD_MAX_MV) { + pr_err("upper threshold value, %d mV, is outside of allowable " + "range: [%d, %d] mV\n", upper_threshold_mV, + THRESHOLD_MIN_MV, THRESHOLD_MAX_MV); + return -EINVAL; + } + + if (upper_threshold_mV < lower_threshold_mV) { + pr_err("lower threshold value, %d mV, must be <= upper " + "threshold value, %d mV\n", lower_threshold_mV, + upper_threshold_mV); + return -EINVAL; + } + + /* Determine register settings for lower threshold. */ + if (lower_threshold_mV < THRESHOLD_BASIC_MIN_MV) { + /* Extended low range */ + reg_ctrl2 |= CTRL2_RANGE_EXT_LOWER_MASK; + + step = (lower_threshold_mV - THRESHOLD_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + /* Extended low range is for steps 0 to 2 */ + step >>= 2; + + reg_threshold |= (step << THRESHOLD_LOWER_SHIFT) + & THRESHOLD_LOWER_MASK; + reg_ctrl2 |= (fine_step << CTRL2_FINE_STEP_LOWER_SHIFT) + & CTRL2_FINE_STEP_LOWER_MASK; + } else if (lower_threshold_mV >= THRESHOLD_EXT_MIN_MV) { + /* Extended high range */ + reg_ctrl2 |= CTRL2_RANGE_EXT_LOWER_MASK; + + step = (lower_threshold_mV - THRESHOLD_EXT_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + /* Extended high range is for steps 3 to 15 */ + step = (step >> 2) + 3; + + reg_threshold |= (step << THRESHOLD_LOWER_SHIFT) + & THRESHOLD_LOWER_MASK; + reg_ctrl2 |= (fine_step << CTRL2_FINE_STEP_LOWER_SHIFT) + & CTRL2_FINE_STEP_LOWER_MASK; + } else { + /* Basic range */ + step = (lower_threshold_mV - THRESHOLD_BASIC_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + step >>= 2; + + reg_threshold |= (step << THRESHOLD_LOWER_SHIFT) + & THRESHOLD_LOWER_MASK; + reg_ctrl2 |= (fine_step << CTRL2_FINE_STEP_LOWER_SHIFT) + & CTRL2_FINE_STEP_LOWER_MASK; + } + + /* Determine register settings for upper threshold. */ + if (upper_threshold_mV < THRESHOLD_BASIC_MIN_MV) { + /* Extended low range */ + reg_ctrl2 |= CTRL2_RANGE_EXT_UPPER_MASK; + + step = (upper_threshold_mV - THRESHOLD_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + /* Extended low range is for steps 0 to 2 */ + step >>= 2; + + reg_threshold |= (step << THRESHOLD_UPPER_SHIFT) + & THRESHOLD_UPPER_MASK; + reg_ctrl2 |= (fine_step << CTRL2_FINE_STEP_UPPER_SHIFT) + & CTRL2_FINE_STEP_UPPER_MASK; + } else if (upper_threshold_mV >= THRESHOLD_EXT_MIN_MV) { + /* Extended high range */ + reg_ctrl2 |= CTRL2_RANGE_EXT_UPPER_MASK; + + step = (upper_threshold_mV - THRESHOLD_EXT_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + /* Extended high range is for steps 3 to 15 */ + step = (step >> 2) + 3; + + reg_threshold |= (step << THRESHOLD_UPPER_SHIFT) + & THRESHOLD_UPPER_MASK; + reg_ctrl2 |= (fine_step << CTRL2_FINE_STEP_UPPER_SHIFT) + & CTRL2_FINE_STEP_UPPER_MASK; + } else { + /* Basic range */ + step = (upper_threshold_mV - THRESHOLD_BASIC_MIN_MV) + / THRESHOLD_STEP_MV; + + fine_step = step & 0x3; + step >>= 2; + + reg_threshold |= (step << THRESHOLD_UPPER_SHIFT) + & THRESHOLD_UPPER_MASK; + reg_ctrl2 |= (fine_step << CTRL2_FINE_STEP_UPPER_SHIFT) + & CTRL2_FINE_STEP_UPPER_MASK; + } + + mutex_lock(&battdev->batt_mutex); + rc = pm8058_reg_write(battdev->pm_chip, REG_THRESHOLD, reg_threshold, + THRESHOLD_LOWER_MASK | THRESHOLD_UPPER_MASK, + &battdev->reg_threshold); + if (rc) + goto bail; + + rc = pm8058_reg_write(battdev->pm_chip, REG_CTRL2, reg_ctrl2, + CTRL2_FINE_STEP_LOWER_MASK | CTRL2_FINE_STEP_UPPER_MASK + | CTRL2_RANGE_EXT_LOWER_MASK | CTRL2_RANGE_EXT_UPPER_MASK, + &battdev->reg_ctrl2); + +bail: + mutex_unlock(&battdev->batt_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(pm8058_batt_alarm_threshold_set); + +/** + * pm8058_batt_alarm_status_read - get status of both threshold comparators + * + * RETURNS: < 0 = error + * 0 = battery voltage ok + * BIT(0) set = battery voltage below lower threshold + * BIT(1) set = battery voltage above upper threshold + */ +int pm8058_batt_alarm_status_read(void) +{ + struct pm8058_batt_alarm_device *battdev = the_battalarm; + int status, rc; + + if (!battdev) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + mutex_lock(&battdev->batt_mutex); + rc = pm8058_read(battdev->pm_chip, REG_CTRL1, &battdev->reg_ctrl1, 1); + + status = ((battdev->reg_ctrl1 & CTRL1_STATUS_LOWER_MASK) + ? PM8058_BATT_ALARM_STATUS_BELOW_LOWER : 0) + | ((battdev->reg_ctrl1 & CTRL1_STATUS_UPPER_MASK) + ? PM8058_BATT_ALARM_STATUS_ABOVE_UPPER : 0); + mutex_unlock(&battdev->batt_mutex); + + if (rc) { + pr_err("pm8058_read failed, rc=%d\n", rc); + return rc; + } + + return status; +} +EXPORT_SYMBOL_GPL(pm8058_batt_alarm_status_read); + +/** + * pm8058_batt_alarm_hold_time_set - set hold time of interrupt output * + * @hold_time: amount of time that battery voltage must remain outside of the + * threshold range before the battery alarm interrupt triggers + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_hold_time_set(enum pm8058_batt_alarm_hold_time hold_time) +{ + struct pm8058_batt_alarm_device *battdev = the_battalarm; + int rc; + u8 reg_ctrl1 = 0; + + if (!battdev) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (hold_time < CTRL1_HOLD_TIME_MIN + || hold_time > CTRL1_HOLD_TIME_MAX) { + + pr_err("hold time, %d, is outside of allowable range: " + "[%d, %d]\n", hold_time, CTRL1_HOLD_TIME_MIN, + CTRL1_HOLD_TIME_MAX); + return -EINVAL; + } + + reg_ctrl1 = hold_time << CTRL1_HOLD_TIME_SHIFT; + + mutex_lock(&battdev->batt_mutex); + rc = pm8058_reg_write(battdev->pm_chip, REG_CTRL1, reg_ctrl1, + CTRL1_HOLD_TIME_MASK, &battdev->reg_ctrl1); + mutex_unlock(&battdev->batt_mutex); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8058_batt_alarm_hold_time_set); + +/** + * pm8058_batt_alarm_pwm_rate_set - set battery alarm update rate * + * @use_pwm: 1 = use PWM update rate, 0 = comparators always active + * @clock_scaler: PWM clock scaler = 2 to 9 + * @clock_divider: PWM clock divider = 2 to 8 + * + * This function sets the rate at which the battery alarm module enables + * the threshold comparators. The rate is determined by the following equation: + * + * f_update = (1024 Hz) / (clock_divider * (2 ^ clock_scaler)) + * + * Thus, the update rate can range from 0.25 Hz to 128 Hz. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_pwm_rate_set(int use_pwm, int clock_scaler, + int clock_divider) +{ + struct pm8058_batt_alarm_device *battdev = the_battalarm; + int rc; + u8 reg_pwm_ctrl = 0, mask = 0; + + if (!battdev) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + if (use_pwm && (clock_scaler < PWM_CTRL_PRE_INPUT_MIN + || clock_scaler > PWM_CTRL_PRE_INPUT_MAX)) { + pr_err("PWM clock scaler, %d, is outside of allowable range: " + "[%d, %d]\n", clock_scaler, PWM_CTRL_PRE_INPUT_MIN, + PWM_CTRL_PRE_INPUT_MAX); + return -EINVAL; + } + + if (use_pwm && (clock_divider < PWM_CTRL_DIV_INPUT_MIN + || clock_divider > PWM_CTRL_DIV_INPUT_MAX)) { + pr_err("PWM clock divider, %d, is outside of allowable range: " + "[%d, %d]\n", clock_divider, PWM_CTRL_DIV_INPUT_MIN, + PWM_CTRL_DIV_INPUT_MAX); + return -EINVAL; + } + + if (!use_pwm) { + /* Turn off PWM control and always enable. */ + reg_pwm_ctrl = PWM_CTRL_ALARM_EN_ALWAYS; + mask = PWM_CTRL_ALARM_EN_MASK; + } else { + /* Use PWM control. */ + reg_pwm_ctrl = PWM_CTRL_ALARM_EN_PWM; + mask = PWM_CTRL_ALARM_EN_MASK | PWM_CTRL_PRE_MASK + | PWM_CTRL_DIV_MASK; + + clock_scaler -= PWM_CTRL_PRE_INPUT_MIN - PWM_CTRL_PRE_MIN; + clock_divider -= PWM_CTRL_DIV_INPUT_MIN - PWM_CTRL_DIV_MIN; + + reg_pwm_ctrl |= (clock_scaler << PWM_CTRL_PRE_SHIFT) + & PWM_CTRL_PRE_MASK; + reg_pwm_ctrl |= (clock_divider << PWM_CTRL_DIV_SHIFT) + & PWM_CTRL_DIV_MASK; + } + + mutex_lock(&battdev->batt_mutex); + rc = pm8058_reg_write(battdev->pm_chip, REG_PWM_CTRL, reg_pwm_ctrl, + mask, &battdev->reg_pwm_ctrl); + mutex_unlock(&battdev->batt_mutex); + + return rc; +} +EXPORT_SYMBOL_GPL(pm8058_batt_alarm_pwm_rate_set); + +/* + * Handle the BATT_ALARM interrupt: + * Battery voltage is above or below threshold range. + */ +static irqreturn_t pm8058_batt_alarm_isr(int irq, void *data) +{ + struct pm8058_batt_alarm_device *battdev = data; + int status; + + if (battdev) { + status = pm8058_batt_alarm_status_read(); + + if (status < 0) + pr_err("failed to read status, rc=%d\n", status); + else + srcu_notifier_call_chain(&battdev->irq_notifier_list, + status, NULL); + } + + return IRQ_HANDLED; +} + +/** + * pm8058_batt_alarm_register_notifier - register a notifier to run when a + * battery voltage change interrupt fires + * @nb: notifier block containing callback function to register + * + * nb->notifier_call must point to a function of this form - + * int (*notifier_call)(struct notifier_block *nb, unsigned long status, + * void *unused); + * "status" will receive the battery alarm status; "unused" will be NULL. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_register_notifier(struct notifier_block *nb) +{ + int rc; + + if (!the_battalarm) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + rc = srcu_notifier_chain_register(&the_battalarm->irq_notifier_list, + nb); + mutex_lock(&the_battalarm->batt_mutex); + if (rc == 0) { + if (the_battalarm->notifier_count == 0) { + /* request the irq */ + rc = request_threaded_irq(the_battalarm->irq, NULL, + pm8058_batt_alarm_isr, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "pm8058-batt_alarm-irq", the_battalarm); + if (rc < 0) { + pr_err("request_irq(%d) failed, rc=%d\n", + the_battalarm->irq, rc); + goto done; + } + + rc = irq_set_irq_wake(the_battalarm->irq, 1); + if (rc < 0) { + pr_err("irq_set_irq_wake(%d,1) failed, rc=%d\n", + the_battalarm->irq, rc); + goto done; + } + } + + the_battalarm->notifier_count++; + } +done: + mutex_unlock(&the_battalarm->batt_mutex); + return rc; +} +EXPORT_SYMBOL_GPL(pm8058_batt_alarm_register_notifier); + +/** + * pm8058_batt_alarm_unregister_notifier - unregister a notifier that is run + * when a battery voltage change interrupt fires + * @nb: notifier block containing callback function to unregister + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_unregister_notifier(struct notifier_block *nb) +{ + int rc; + + if (!the_battalarm) { + pr_err("no battery alarm device found.\n"); + return -ENXIO; + } + + rc = srcu_notifier_chain_unregister(&the_battalarm->irq_notifier_list, + nb); + if (rc == 0) { + mutex_lock(&the_battalarm->batt_mutex); + + the_battalarm->notifier_count--; + + if (the_battalarm->notifier_count == 0) + free_irq(the_battalarm->irq, the_battalarm); + + WARN_ON(the_battalarm->notifier_count < 0); + + mutex_unlock(&the_battalarm->batt_mutex); + } + + + + return rc; +} +EXPORT_SYMBOL_GPL(pm8058_batt_alarm_unregister_notifier); + +static int pm8058_batt_alarm_reg_init(struct pm8058_batt_alarm_device *battdev) +{ + int rc = 0; + + /* save the current register states */ + rc = pm8058_read(battdev->pm_chip, REG_THRESHOLD, + &battdev->reg_threshold, 1); + if (rc) + goto bail; + + rc = pm8058_read(battdev->pm_chip, REG_CTRL1, + &battdev->reg_ctrl1, 1); + if (rc) + goto bail; + + rc = pm8058_read(battdev->pm_chip, REG_CTRL2, + &battdev->reg_ctrl2, 1); + if (rc) + goto bail; + + rc = pm8058_read(battdev->pm_chip, REG_PWM_CTRL, + &battdev->reg_pwm_ctrl, 1); + if (rc) + goto bail; + +bail: + if (rc) + pr_err("pm8058_read failed; initial register states " + "unknown, rc=%d\n", rc); + return rc; +} + +static int pm8058_batt_alarm_config(void) +{ + int rc = 0; + + /* Use default values when no platform data is provided. */ + rc = pm8058_batt_alarm_threshold_set(DEFAULT_THRESHOLD_LOWER, + DEFAULT_THRESHOLD_UPPER); + if (rc) { + pr_err("threshold_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8058_batt_alarm_hold_time_set(DEFAULT_HOLD_TIME); + if (rc) { + pr_err("hold_time_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8058_batt_alarm_pwm_rate_set(DEFAULT_USE_PWM, + DEFAULT_PWM_SCALER, DEFAULT_PWM_DIVIDER); + if (rc) { + pr_err("pwm_rate_set failed, rc=%d\n", rc); + goto done; + } + + rc = pm8058_batt_alarm_state_set(DEFAULT_LOWER_ENABLE, + DEFAULT_UPPER_ENABLE); + if (rc) { + pr_err("state_set failed, rc=%d\n", rc); + goto done; + } + +done: + return rc; +} + +static int __devinit pm8058_batt_alarm_probe(struct platform_device *pdev) +{ + struct pm8058_batt_alarm_device *battdev; + struct pm8058_chip *pm_chip; + unsigned int irq; + int rc; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + pr_err("no driver data passed in.\n"); + rc = -EFAULT; + goto exit_input; + } + + irq = platform_get_irq(pdev, 0); + if (!irq) { + pr_err("no IRQ passed in.\n"); + rc = -EFAULT; + goto exit_input; + } + + battdev = kzalloc(sizeof *battdev, GFP_KERNEL); + if (battdev == NULL) { + pr_err("kzalloc() failed.\n"); + rc = -ENOMEM; + goto exit_input; + } + + battdev->pm_chip = pm_chip; + platform_set_drvdata(pdev, battdev); + + srcu_init_notifier_head(&battdev->irq_notifier_list); + + battdev->irq = irq; + battdev->notifier_count = 0; + mutex_init(&battdev->batt_mutex); + + rc = pm8058_batt_alarm_reg_init(battdev); + if (rc) + goto exit_free_dev; + + the_battalarm = battdev; + + rc = pm8058_batt_alarm_config(); + if (rc) + goto exit_free_dev; + + pr_notice("OK\n"); + return 0; + +exit_free_dev: + mutex_destroy(&battdev->batt_mutex); + srcu_cleanup_notifier_head(&battdev->irq_notifier_list); + platform_set_drvdata(pdev, battdev->pm_chip); + kfree(battdev); +exit_input: + return rc; +} + +static int __devexit pm8058_batt_alarm_remove(struct platform_device *pdev) +{ + struct pm8058_batt_alarm_device *battdev = platform_get_drvdata(pdev); + + mutex_destroy(&battdev->batt_mutex); + srcu_cleanup_notifier_head(&battdev->irq_notifier_list); + platform_set_drvdata(pdev, battdev->pm_chip); + free_irq(battdev->irq, battdev); + kfree(battdev); + + the_battalarm = NULL; + + return 0; +} + +static struct platform_driver pm8058_batt_alarm_driver = { + .probe = pm8058_batt_alarm_probe, + .remove = __devexit_p(pm8058_batt_alarm_remove), + .driver = { + .name = "pm8058-batt-alarm", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_batt_alarm_init(void) +{ + return platform_driver_register(&pm8058_batt_alarm_driver); +} + +static void __exit pm8058_batt_alarm_exit(void) +{ + platform_driver_unregister(&pm8058_batt_alarm_driver); +} + +module_init(pm8058_batt_alarm_init); +module_exit(pm8058_batt_alarm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 Battery Alarm Device driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8058-batt-alarm"); diff --git a/drivers/misc/pmic8058-misc.c b/drivers/misc/pmic8058-misc.c new file mode 100644 index 00000000000..77a2f478b94 --- /dev/null +++ b/drivers/misc/pmic8058-misc.c @@ -0,0 +1,335 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8058 Misc Device driver + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* VIB_DRV register */ +#define SSBI_REG_ADDR_DRV_VIB 0x4A + +#define PM8058_VIB_DRIVE_SHIFT 3 +#define PM8058_VIB_LOGIC_SHIFT 2 +#define PM8058_VIB_MIN_LEVEL_mV 1200 +#define PM8058_VIB_MAX_LEVEL_mV 3100 + +/* COINCELL_CHG register */ +#define SSBI_REG_ADDR_COINCELL_CHG (0x2F) +#define PM8058_COINCELL_RESISTOR_SHIFT (2) + +/* Resource offsets. */ +enum PM8058_MISC_IRQ { + PM8058_MISC_IRQ_OSC_HALT = 0 +}; + +struct pm8058_misc_device { + struct pm8058_chip *pm_chip; + struct dentry *dgb_dir; + unsigned int osc_halt_irq; + u64 osc_halt_count; +}; + +static struct pm8058_misc_device *misc_dev; + +int pm8058_vibrator_config(struct pm8058_vib_config *vib_config) +{ + u8 reg = 0; + int rc; + + if (misc_dev == NULL) { + pr_info("misc_device is NULL\n"); + return -EINVAL; + } + + if (vib_config->drive_mV) { + if (vib_config->drive_mV < PM8058_VIB_MIN_LEVEL_mV || + vib_config->drive_mV > PM8058_VIB_MAX_LEVEL_mV) { + pr_err("Invalid vibrator drive strength\n"); + return -EINVAL; + } + } + + reg = (vib_config->drive_mV / 100) << PM8058_VIB_DRIVE_SHIFT; + + reg |= (!!vib_config->active_low) << PM8058_VIB_LOGIC_SHIFT; + + reg |= vib_config->enable_mode; + + rc = pm8058_write(misc_dev->pm_chip, SSBI_REG_ADDR_DRV_VIB, ®, 1); + if (rc) + pr_err("%s: pm8058 write failed: rc=%d\n", __func__, rc); + + return rc; +} +EXPORT_SYMBOL(pm8058_vibrator_config); + +/** + * pm8058_coincell_chg_config - Disables or enables the coincell charger, and + * configures its voltage and resistor settings. + * @chg_config: Holds both voltage and resistor values, and a + * switch to change the state of charger. + * If state is to disable the charger then + * both voltage and resistor are disregarded. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_coincell_chg_config(struct pm8058_coincell_chg_config *chg_config) +{ + u8 reg, voltage, resistor; + int rc; + + reg = 0; + voltage = 0; + resistor = 0; + rc = 0; + + if (misc_dev == NULL) { + pr_err("misc_device is NULL\n"); + return -EINVAL; + } + + if (chg_config == NULL) { + pr_err("chg_config is NULL\n"); + return -EINVAL; + } + + if (chg_config->state == PM8058_COINCELL_CHG_DISABLE) { + rc = pm8058_write(misc_dev->pm_chip, + SSBI_REG_ADDR_COINCELL_CHG, ®, 1); + if (rc) + pr_err("%s: pm8058 write failed: rc=%d\n", + __func__, rc); + return rc; + } + + voltage = chg_config->voltage; + resistor = chg_config->resistor; + + if (voltage < PM8058_COINCELL_VOLTAGE_3p2V || + (voltage > PM8058_COINCELL_VOLTAGE_3p0V && + voltage != PM8058_COINCELL_VOLTAGE_2p5V)) { + pr_err("Invalid voltage value provided\n"); + return -EINVAL; + } + + if (resistor < PM8058_COINCELL_RESISTOR_2100_OHMS || + resistor > PM8058_COINCELL_RESISTOR_800_OHMS) { + pr_err("Invalid resistor value provided\n"); + return -EINVAL; + } + + reg |= voltage; + + reg |= (resistor << PM8058_COINCELL_RESISTOR_SHIFT); + + rc = pm8058_write(misc_dev->pm_chip, + SSBI_REG_ADDR_COINCELL_CHG, ®, 1); + + if (rc) + pr_err("%s: pm8058 write failed: rc=%d\n", __func__, rc); + + return rc; +} +EXPORT_SYMBOL(pm8058_coincell_chg_config); + +/* Handle the OSC_HALT interrupt: 32 kHz XTAL oscillator has stopped. */ +static irqreturn_t pm8058_osc_halt_isr(int irq, void *data) +{ + struct pm8058_misc_device *miscdev = data; + u64 count = 0; + + if (miscdev) { + miscdev->osc_halt_count++; + count = miscdev->osc_halt_count; + } + + pr_crit("%s: OSC_HALT interrupt has triggered, 32 kHz XTAL oscillator" + " has halted (%llu)!\n", __func__, count); + + return IRQ_HANDLED; +} + +#if defined(CONFIG_DEBUG_FS) + +static int osc_halt_count_get(void *data, u64 *val) +{ + struct pm8058_misc_device *miscdev = data; + + if (miscdev == NULL) { + pr_err("%s: null pointer input.\n", __func__); + return -EINVAL; + } + + *val = miscdev->osc_halt_count; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(dbg_osc_halt_fops, osc_halt_count_get, NULL, "%llu\n"); + +static int __devinit pmic8058_misc_dbg_probe(struct pm8058_misc_device *miscdev) +{ + struct dentry *dent; + struct dentry *temp; + + if (miscdev == NULL) { + pr_err("%s: no parent data passed in.\n", __func__); + return -EINVAL; + } + + dent = debugfs_create_dir("pm8058-misc", NULL); + if (dent == NULL || IS_ERR(dent)) { + pr_err("%s: ERR debugfs_create_dir: dent=0x%X\n", + __func__, (unsigned)dent); + return -ENOMEM; + } + + temp = debugfs_create_file("osc_halt_count", S_IRUSR, dent, + miscdev, &dbg_osc_halt_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("%s: ERR debugfs_create_file: dent=0x%X\n", + __func__, (unsigned)temp); + goto debug_error; + } + + miscdev->dgb_dir = dent; + return 0; + +debug_error: + debugfs_remove_recursive(dent); + return -ENOMEM; +} + +static int __devexit pmic8058_misc_dbg_remove( + struct pm8058_misc_device *miscdev) +{ + if (miscdev->dgb_dir) + debugfs_remove_recursive(miscdev->dgb_dir); + + return 0; +} + +#else + +static int __devinit pmic8058_misc_dbg_probe(struct pm8058_misc_device *miscdev) +{ + return 0; +} + +static int __devexit pmic8058_misc_dbg_remove( + struct pm8058_misc_device *miscdev) +{ + return 0; +} + +#endif + + +static int __devinit pmic8058_misc_probe(struct platform_device *pdev) +{ + struct pm8058_misc_device *miscdev; + struct pm8058_chip *pm_chip; + unsigned int irq; + int rc; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + pr_err("%s: no driver data passed in.\n", __func__); + return -EFAULT; + } + + irq = platform_get_irq(pdev, PM8058_MISC_IRQ_OSC_HALT); + if (!irq) { + pr_err("%s: no IRQ passed in.\n", __func__); + return -EFAULT; + } + + miscdev = kzalloc(sizeof *miscdev, GFP_KERNEL); + if (miscdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + miscdev->pm_chip = pm_chip; + platform_set_drvdata(pdev, miscdev); + + rc = request_threaded_irq(irq, NULL, pm8058_osc_halt_isr, + IRQF_TRIGGER_RISING | IRQF_DISABLED, + "pm8058-osc_halt-irq", miscdev); + if (rc < 0) { + pr_err("%s: request_irq(%d) FAIL: %d\n", __func__, irq, rc); + platform_set_drvdata(pdev, miscdev->pm_chip); + kfree(miscdev); + return rc; + } + miscdev->osc_halt_irq = irq; + miscdev->osc_halt_count = 0; + + rc = pmic8058_misc_dbg_probe(miscdev); + if (rc) + return rc; + + misc_dev = miscdev; + + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit pmic8058_misc_remove(struct platform_device *pdev) +{ + struct pm8058_misc_device *miscdev = platform_get_drvdata(pdev); + + pmic8058_misc_dbg_remove(miscdev); + + platform_set_drvdata(pdev, miscdev->pm_chip); + free_irq(miscdev->osc_halt_irq, miscdev); + kfree(miscdev); + + return 0; +} + +static struct platform_driver pmic8058_misc_driver = { + .probe = pmic8058_misc_probe, + .remove = __devexit_p(pmic8058_misc_remove), + .driver = { + .name = "pm8058-misc", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_misc_init(void) +{ + return platform_driver_register(&pmic8058_misc_driver); +} + +static void __exit pm8058_misc_exit(void) +{ + platform_driver_unregister(&pmic8058_misc_driver); +} + +module_init(pm8058_misc_init); +module_exit(pm8058_misc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 Misc Device driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8058-misc"); diff --git a/drivers/misc/pmic8058-nfc.c b/drivers/misc/pmic8058-nfc.c new file mode 100644 index 00000000000..76a19f494f2 --- /dev/null +++ b/drivers/misc/pmic8058-nfc.c @@ -0,0 +1,322 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8058 NFC driver + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* PMIC8058 NFC */ +#define SSBI_REG_NFC_CTRL 0x14D +#define SSBI_REG_NFC_TEST 0x14E + +/* NFC_CTRL */ +#define PM8058_NFC_SUPPORT_EN 0x80 +#define PM8058_NFC_LDO_EN 0x40 +#define PM8058_NFC_EN 0x20 +#define PM8058_NFC_EXT_VDDLDO_EN 0x10 +#define PM8058_NFC_VPH_PWR_EN 0x08 +#define PM8058_NFC_RESERVED 0x04 +#define PM8058_NFC_VDDLDO_LEVEL 0x03 + +/* NFC_TEST */ +#define PM8058_NFC_VDDLDO_MON_EN 0x80 +#define PM8058_NFC_ATEST_EN 0x40 +#define PM8058_NFC_DTEST1_EN 0x20 +#define PM8058_NFC_RESERVED2 0x18 +#define PM8058_NFC_VDDLDO_OK_S 0x04 +#define PM8058_NFC_MBG_EN_S 0x02 +#define PM8058_NFC_EXT_EN_S 0x01 + +struct pm8058_nfc_device { + struct mutex nfc_mutex; + struct pm8058_chip *pm_chip; +#if defined(CONFIG_DEBUG_FS) + struct dentry *dent; +#endif +}; +static struct pm8058_nfc_device *nfc_dev; + +/* APIs */ +/* + * pm8058_nfc_request - request a handle to access NFC device + */ +struct pm8058_nfc_device *pm8058_nfc_request(void) +{ + return nfc_dev; +} +EXPORT_SYMBOL(pm8058_nfc_request); + +/* + * pm8058_nfc_config - configure NFC signals + * + * @nfcdev: the NFC device + * @mask: signal mask to configure + * @flags: control flags + */ +int pm8058_nfc_config(struct pm8058_nfc_device *nfcdev, u32 mask, u32 flags) +{ + u8 nfc_ctrl, nfc_test, m, f; + int rc; + + if (nfcdev == NULL || IS_ERR(nfcdev) || !mask) + return -EINVAL; + if (nfcdev->pm_chip == NULL) + return -ENODEV; + + mutex_lock(&nfcdev->nfc_mutex); + + if (!(mask & PM_NFC_CTRL_REQ)) + goto config_test; + + rc = pm8058_read(nfcdev->pm_chip, SSBI_REG_NFC_CTRL, &nfc_ctrl, 1); + if (rc) { + pr_err("%s: FAIL pm8058_read(): rc=%d (nfc_ctrl=0x%x)\n", + __func__, rc, nfc_ctrl); + goto config_done; + } + + m = mask & 0x00ff; + f = flags & 0x00ff; + nfc_ctrl &= ~m; + nfc_ctrl |= m & f; + + rc = pm8058_write(nfcdev->pm_chip, SSBI_REG_NFC_CTRL, &nfc_ctrl, 1); + if (rc) { + pr_err("%s: FAIL pm8058_write(): rc=%d (nfc_ctrl=0x%x)\n", + __func__, rc, nfc_ctrl); + goto config_done; + } + +config_test: + if (!(mask & PM_NFC_TEST_REQ)) + goto config_done; + + rc = pm8058_read(nfcdev->pm_chip, SSBI_REG_NFC_TEST, &nfc_test, 1); + if (rc) { + pr_err("%s: FAIL pm8058_read(): rc=%d (nfc_test=0x%x)\n", + __func__, rc, nfc_test); + goto config_done; + } + + m = (mask >> 8) & 0x00ff; + f = (flags >> 8) & 0x00ff; + nfc_test &= ~m; + nfc_test |= m & f; + + rc = pm8058_write(nfcdev->pm_chip, SSBI_REG_NFC_TEST, &nfc_test, 1); + if (rc) { + pr_err("%s: FAIL pm8058_write(): rc=%d (nfc_test=0x%x)\n", + __func__, rc, nfc_test); + goto config_done; + } + +config_done: + mutex_unlock(&nfcdev->nfc_mutex); + return 0; +} +EXPORT_SYMBOL(pm8058_nfc_config); + +/* + * pm8058_nfc_get_status - get NFC status + * + * @nfcdev: the NFC device + * @mask: of status mask to read + * @status: pointer to the status variable + */ +int pm8058_nfc_get_status(struct pm8058_nfc_device *nfcdev, + u32 mask, u32 *status) +{ + u8 nfc_ctrl, nfc_test; + u32 st; + int rc; + + if (nfcdev == NULL || IS_ERR(nfcdev) || status == NULL) + return -EINVAL; + if (nfcdev->pm_chip == NULL) + return -ENODEV; + + st = 0; + mutex_lock(&nfcdev->nfc_mutex); + + if (!(mask & PM_NFC_CTRL_REQ)) + goto read_test; + + rc = pm8058_read(nfcdev->pm_chip, SSBI_REG_NFC_CTRL, &nfc_ctrl, 1); + if (rc) { + pr_err("%s: FAIL pm8058_read(): rc=%d (nfc_ctrl=0x%x)\n", + __func__, rc, nfc_ctrl); + goto get_status_done; + } + +read_test: + if (!(mask & (PM_NFC_TEST_REQ | PM_NFC_TEST_STATUS))) + goto get_status_done; + + rc = pm8058_read(nfcdev->pm_chip, SSBI_REG_NFC_TEST, &nfc_test, 1); + if (rc) + pr_err("%s: FAIL pm8058_read(): rc=%d (nfc_test=0x%x)\n", + __func__, rc, nfc_test); + +get_status_done: + st = nfc_ctrl; + st |= nfc_test << 8; + *status = st; + + mutex_unlock(&nfcdev->nfc_mutex); + return 0; +} +EXPORT_SYMBOL(pm8058_nfc_get_status); + +/* + * pm8058_nfc_free - free the NFC device + */ +void pm8058_nfc_free(struct pm8058_nfc_device *nfcdev) +{ + /* Disable all signals */ + pm8058_nfc_config(nfcdev, PM_NFC_CTRL_REQ, 0); +} +EXPORT_SYMBOL(pm8058_nfc_free); + +#if defined(CONFIG_DEBUG_FS) +static int pm8058_nfc_debug_set(void *data, u64 val) +{ + struct pm8058_nfc_device *nfcdev; + u32 mask, control; + int rc; + + nfcdev = (struct pm8058_nfc_device *)data; + control = (u32)val & 0xffff; + mask = ((u32)val >> 16) & 0xffff; + rc = pm8058_nfc_config(nfcdev, mask, control); + if (rc) + pr_err("%s: ERR pm8058_nfc_config: rc=%d, " + "[mask, control]=[0x%x, 0x%x]\n", + __func__, rc, mask, control); + + return 0; +} + +static int pm8058_nfc_debug_get(void *data, u64 *val) +{ + struct pm8058_nfc_device *nfcdev; + u32 status; + int rc; + + nfcdev = (struct pm8058_nfc_device *)data; + rc = pm8058_nfc_get_status(nfcdev, (u32)-1, &status); + if (rc) + pr_err("%s: ERR pm8058_nfc_get_status: rc=%d, status=0x%x\n", + __func__, rc, status); + + if (val) + *val = (u64)status; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(pm8058_nfc_fops, pm8058_nfc_debug_get, + pm8058_nfc_debug_set, "%llu\n"); + +static int pm8058_nfc_debug_init(struct pm8058_nfc_device *nfcdev) +{ + struct dentry *dent; + + dent = debugfs_create_file("pm8058-nfc", 0644, NULL, + (void *)nfcdev, &pm8058_nfc_fops); + + if (dent == NULL || IS_ERR(dent)) + pr_err("%s: ERR debugfs_create_file: dent=0x%x\n", + __func__, (unsigned)dent); + + nfcdev->dent = dent; + return 0; +} +#endif + +static int __devinit pmic8058_nfc_probe(struct platform_device *pdev) +{ + struct pm8058_chip *pm_chip; + struct pm8058_nfc_device *nfcdev; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + pr_err("%s: no parent data passed in.\n", __func__); + return -EFAULT; + } + + nfcdev = kzalloc(sizeof *nfcdev, GFP_KERNEL); + if (nfcdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + mutex_init(&nfcdev->nfc_mutex); + + nfcdev->pm_chip = pm_chip; + nfc_dev = nfcdev; + platform_set_drvdata(pdev, nfcdev); + +#if defined(CONFIG_DEBUG_FS) + pm8058_nfc_debug_init(nfc_dev); +#endif + + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit pmic8058_nfc_remove(struct platform_device *pdev) +{ + struct pm8058_nfc_device *nfcdev = platform_get_drvdata(pdev); + +#if defined(CONFIG_DEBUG_FS) + debugfs_remove(nfcdev->dent); +#endif + + platform_set_drvdata(pdev, nfcdev->pm_chip); + kfree(nfcdev); + return 0; +} + +static struct platform_driver pmic8058_nfc_driver = { + .probe = pmic8058_nfc_probe, + .remove = __devexit_p(pmic8058_nfc_remove), + .driver = { + .name = "pm8058-nfc", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_nfc_init(void) +{ + return platform_driver_register(&pmic8058_nfc_driver); +} + +static void __exit pm8058_nfc_exit(void) +{ + platform_driver_unregister(&pmic8058_nfc_driver); +} + +module_init(pm8058_nfc_init); +module_exit(pm8058_nfc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 NFC driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8058-nfc"); diff --git a/drivers/misc/pmic8058-pwm.c b/drivers/misc/pmic8058-pwm.c new file mode 100644 index 00000000000..2c04bdc7674 --- /dev/null +++ b/drivers/misc/pmic8058-pwm.c @@ -0,0 +1,926 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8058 PWM driver + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define PM8058_LPG_BANKS 8 +#define PM8058_PWM_CHANNELS PM8058_LPG_BANKS /* MAX=8 */ + +#define PM8058_LPG_CTL_REGS 7 + +/* PMIC8058 LPG/PWM */ +#define SSBI_REG_ADDR_LPG_CTL_BASE 0x13C +#define SSBI_REG_ADDR_LPG_CTL(n) (SSBI_REG_ADDR_LPG_CTL_BASE + (n)) +#define SSBI_REG_ADDR_LPG_BANK_SEL 0x143 +#define SSBI_REG_ADDR_LPG_BANK_EN 0x144 +#define SSBI_REG_ADDR_LPG_LUT_CFG0 0x145 +#define SSBI_REG_ADDR_LPG_LUT_CFG1 0x146 +#define SSBI_REG_ADDR_LPG_TEST 0x147 + +/* Control 0 */ +#define PM8058_PWM_1KHZ_COUNT_MASK 0xF0 +#define PM8058_PWM_1KHZ_COUNT_SHIFT 4 + +#define PM8058_PWM_1KHZ_COUNT_MAX 15 + +#define PM8058_PWM_OUTPUT_EN 0x08 +#define PM8058_PWM_PWM_EN 0x04 +#define PM8058_PWM_RAMP_GEN_EN 0x02 +#define PM8058_PWM_RAMP_START 0x01 + +#define PM8058_PWM_PWM_START (PM8058_PWM_OUTPUT_EN \ + | PM8058_PWM_PWM_EN) +#define PM8058_PWM_RAMP_GEN_START (PM8058_PWM_RAMP_GEN_EN \ + | PM8058_PWM_RAMP_START) + +/* Control 1 */ +#define PM8058_PWM_REVERSE_EN 0x80 +#define PM8058_PWM_BYPASS_LUT 0x40 +#define PM8058_PWM_HIGH_INDEX_MASK 0x3F + +/* Control 2 */ +#define PM8058_PWM_LOOP_EN 0x80 +#define PM8058_PWM_RAMP_UP 0x40 +#define PM8058_PWM_LOW_INDEX_MASK 0x3F + +/* Control 3 */ +#define PM8058_PWM_VALUE_BIT7_0 0xFF +#define PM8058_PWM_VALUE_BIT5_0 0x3F + +/* Control 4 */ +#define PM8058_PWM_VALUE_BIT8 0x80 + +#define PM8058_PWM_CLK_SEL_MASK 0x60 +#define PM8058_PWM_CLK_SEL_SHIFT 5 + +#define PM8058_PWM_CLK_SEL_NO 0 +#define PM8058_PWM_CLK_SEL_1KHZ 1 +#define PM8058_PWM_CLK_SEL_32KHZ 2 +#define PM8058_PWM_CLK_SEL_19P2MHZ 3 + +#define PM8058_PWM_PREDIVIDE_MASK 0x18 +#define PM8058_PWM_PREDIVIDE_SHIFT 3 + +#define PM8058_PWM_PREDIVIDE_2 0 +#define PM8058_PWM_PREDIVIDE_3 1 +#define PM8058_PWM_PREDIVIDE_5 2 +#define PM8058_PWM_PREDIVIDE_6 3 + +#define PM8058_PWM_M_MASK 0x07 +#define PM8058_PWM_M_MIN 0 +#define PM8058_PWM_M_MAX 7 + +/* Control 5 */ +#define PM8058_PWM_PAUSE_COUNT_HI_MASK 0xFC +#define PM8058_PWM_PAUSE_COUNT_HI_SHIFT 2 + +#define PM8058_PWM_PAUSE_ENABLE_HIGH 0x02 +#define PM8058_PWM_SIZE_9_BIT 0x01 + +/* Control 6 */ +#define PM8058_PWM_PAUSE_COUNT_LO_MASK 0xFC +#define PM8058_PWM_PAUSE_COUNT_LO_SHIFT 2 + +#define PM8058_PWM_PAUSE_ENABLE_LOW 0x02 +#define PM8058_PWM_RESERVED 0x01 + +#define PM8058_PWM_PAUSE_COUNT_MAX 56 /* < 2^6 = 64*/ + +/* LUT_CFG1 */ +#define PM8058_PWM_LUT_READ 0x40 + +/* TEST */ +#define PM8058_PWM_DTEST_MASK 0x38 +#define PM8058_PWM_DTEST_SHIFT 3 + +#define PM8058_PWM_DTEST_BANK_MASK 0x07 + +/* PWM frequency support + * + * PWM Frequency = Clock Frequency / (N * T) + * or + * PWM Period = Clock Period * (N * T) + * where + * N = 2^9 or 2^6 for 9-bit or 6-bit PWM size + * T = Pre-divide * 2^m, m = 0..7 (exponent) + * + * We use this formula to figure out m for the best pre-divide and clock: + * (PWM Period / N) / 2^m = (Pre-divide * Clock Period) +*/ +#define NUM_CLOCKS 3 + +#define NSEC_1000HZ (NSEC_PER_SEC / 1000) +#define NSEC_32768HZ (NSEC_PER_SEC / 32768) +#define NSEC_19P2MHZ (NSEC_PER_SEC / 19200000) + +#define CLK_PERIOD_MIN NSEC_19P2MHZ +#define CLK_PERIOD_MAX NSEC_1000HZ + +#define NUM_PRE_DIVIDE 3 /* No default support for pre-divide = 6 */ + +#define PRE_DIVIDE_0 2 +#define PRE_DIVIDE_1 3 +#define PRE_DIVIDE_2 5 + +#define PRE_DIVIDE_MIN PRE_DIVIDE_0 +#define PRE_DIVIDE_MAX PRE_DIVIDE_2 + +static char *clks[NUM_CLOCKS] = { + "1K", "32768", "19.2M" +}; + +static unsigned pre_div[NUM_PRE_DIVIDE] = { + PRE_DIVIDE_0, PRE_DIVIDE_1, PRE_DIVIDE_2 +}; + +static unsigned int pt_t[NUM_PRE_DIVIDE][NUM_CLOCKS] = { + { PRE_DIVIDE_0 * NSEC_1000HZ, + PRE_DIVIDE_0 * NSEC_32768HZ, + PRE_DIVIDE_0 * NSEC_19P2MHZ, + }, + { PRE_DIVIDE_1 * NSEC_1000HZ, + PRE_DIVIDE_1 * NSEC_32768HZ, + PRE_DIVIDE_1 * NSEC_19P2MHZ, + }, + { PRE_DIVIDE_2 * NSEC_1000HZ, + PRE_DIVIDE_2 * NSEC_32768HZ, + PRE_DIVIDE_2 * NSEC_19P2MHZ, + }, +}; + +#define MIN_MPT ((PRE_DIVIDE_MIN * CLK_PERIOD_MIN) << PM8058_PWM_M_MIN) +#define MAX_MPT ((PRE_DIVIDE_MAX * CLK_PERIOD_MAX) << PM8058_PWM_M_MAX) + +/* Private data */ +struct pm8058_pwm_chip; + +struct pwm_device { + int pwm_id; /* = bank/channel id */ + int in_use; + const char *label; + int pwm_period; + int pwm_duty; + u8 pwm_ctl[PM8058_LPG_CTL_REGS]; + int irq; + struct pm8058_pwm_chip *chip; +}; + +struct pm8058_pwm_chip { + struct pwm_device pwm_dev[PM8058_PWM_CHANNELS]; + u8 bank_mask; + struct mutex pwm_mutex; + struct pm8058_chip *pm_chip; + struct pm8058_pwm_pdata *pdata; +}; + +static struct pm8058_pwm_chip *pwm_chip; + +struct pw8058_pwm_config { + int pwm_size; /* round up to 6 or 9 for 6/9-bit PWM SIZE */ + int clk; + int pre_div; + int pre_div_exp; + int pwm_value; + int bypass_lut; + + /* LUT parameters when bypass_lut is 0 */ + int lut_duty_ms; + int lut_lo_index; + int lut_hi_index; + int lut_pause_hi; + int lut_pause_lo; + int flags; +}; + +static u16 duty_msec[PM8058_PWM_1KHZ_COUNT_MAX + 1] = { + 0, 1, 2, 3, 4, 6, 8, 16, 18, 24, 32, 36, 64, 128, 256, 512 +}; + +static u16 pause_count[PM8058_PWM_PAUSE_COUNT_MAX + 1] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 23, 28, 31, 42, 47, 56, 63, 83, 94, 111, 125, 167, 188, 222, 250, 333, + 375, 500, 667, 750, 800, 900, 1000, 1100, + 1200, 1300, 1400, 1500, 1600, 1800, 2000, 2500, + 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, + 7000 +}; + +/* Internal functions */ +static int pm8058_pwm_bank_enable(struct pwm_device *pwm, int enable) +{ + int rc; + u8 reg; + struct pm8058_pwm_chip *chip; + + chip = pwm->chip; + + if (enable) + reg = chip->bank_mask | (1 << pwm->pwm_id); + else + reg = chip->bank_mask & ~(1 << pwm->pwm_id); + + rc = pm8058_write(chip->pm_chip, SSBI_REG_ADDR_LPG_BANK_EN, ®, 1); + if (rc) { + pr_err("%s: pm8058_write(): rc=%d (Enable LPG Bank)\n", + __func__, rc); + goto bail_out; + } + chip->bank_mask = reg; + +bail_out: + return rc; +} + +static int pm8058_pwm_bank_sel(struct pwm_device *pwm) +{ + int rc; + u8 reg; + + reg = pwm->pwm_id; + rc = pm8058_write(pwm->chip->pm_chip, SSBI_REG_ADDR_LPG_BANK_SEL, + ®, 1); + if (rc) + pr_err("%s: pm8058_write(): rc=%d (Select PWM Bank)\n", + __func__, rc); + return rc; +} + +static int pm8058_pwm_start(struct pwm_device *pwm, int start, int ramp_start) +{ + int rc; + u8 reg; + + if (start) { + reg = pwm->pwm_ctl[0] | PM8058_PWM_PWM_START; + if (ramp_start) + reg |= PM8058_PWM_RAMP_GEN_START; + else + reg &= ~PM8058_PWM_RAMP_GEN_START; + } else { + reg = pwm->pwm_ctl[0] & ~PM8058_PWM_PWM_START; + reg &= ~PM8058_PWM_RAMP_GEN_START; + } + + rc = pm8058_write(pwm->chip->pm_chip, SSBI_REG_ADDR_LPG_CTL(0), + ®, 1); + if (rc) + pr_err("%s: pm8058_write(): rc=%d (Enable PWM Ctl 0)\n", + __func__, rc); + else + pwm->pwm_ctl[0] = reg; + return rc; +} + +static void pm8058_pwm_calc_period(unsigned int period_us, + struct pw8058_pwm_config *pwm_conf) +{ + int n, m, clk, div; + int best_m, best_div, best_clk; + int last_err, cur_err, better_err, better_m; + unsigned int tmp_p, last_p, min_err, period_n; + + /* PWM Period / N : handle underflow or overflow */ + if (period_us < (PM_PWM_PERIOD_MAX / NSEC_PER_USEC)) + period_n = (period_us * NSEC_PER_USEC) >> 6; + else + period_n = (period_us >> 6) * NSEC_PER_USEC; + if (period_n >= MAX_MPT) { + n = 9; + period_n >>= 3; + } else + n = 6; + + min_err = MAX_MPT; + best_m = 0; + best_clk = 0; + best_div = 0; + for (clk = 0; clk < NUM_CLOCKS; clk++) { + for (div = 0; div < NUM_PRE_DIVIDE; div++) { + tmp_p = period_n; + last_p = tmp_p; + for (m = 0; m <= PM8058_PWM_M_MAX; m++) { + if (tmp_p <= pt_t[div][clk]) { + /* Found local best */ + if (!m) { + better_err = pt_t[div][clk] - + tmp_p; + better_m = m; + } else { + last_err = last_p - + pt_t[div][clk]; + cur_err = pt_t[div][clk] - + tmp_p; + + if (cur_err < last_err) { + better_err = cur_err; + better_m = m; + } else { + better_err = last_err; + better_m = m - 1; + } + } + + if (better_err < min_err) { + min_err = better_err; + best_m = better_m; + best_clk = clk; + best_div = div; + } + break; + } else { + last_p = tmp_p; + tmp_p >>= 1; + } + } + } + } + + pwm_conf->pwm_size = n; + pwm_conf->clk = best_clk; + pwm_conf->pre_div = best_div; + pwm_conf->pre_div_exp = best_m; + + pr_debug("%s: period=%u: n=%d, m=%d, clk[%d]=%s, div[%d]=%d\n", + __func__, (unsigned)period_us, n, best_m, + best_clk, clks[best_clk], best_div, pre_div[best_div]); +} + +static int pm8058_pwm_configure(struct pwm_device *pwm, + struct pw8058_pwm_config *pwm_conf) +{ + int i, rc, len; + u8 reg, ramp_enabled = 0; + + reg = (pwm_conf->pwm_size > 6) ? PM8058_PWM_SIZE_9_BIT : 0; + pwm->pwm_ctl[5] = reg; + + reg = ((pwm_conf->clk + 1) << PM8058_PWM_CLK_SEL_SHIFT) + & PM8058_PWM_CLK_SEL_MASK; + reg |= (pwm_conf->pre_div << PM8058_PWM_PREDIVIDE_SHIFT) + & PM8058_PWM_PREDIVIDE_MASK; + reg |= pwm_conf->pre_div_exp & PM8058_PWM_M_MASK; + pwm->pwm_ctl[4] = reg; + + if (pwm_conf->bypass_lut) { + pwm->pwm_ctl[0] &= PM8058_PWM_PWM_START; /* keep enabled */ + pwm->pwm_ctl[1] = PM8058_PWM_BYPASS_LUT; + pwm->pwm_ctl[2] = 0; + + if (pwm_conf->pwm_size > 6) { + pwm->pwm_ctl[3] = pwm_conf->pwm_value + & PM8058_PWM_VALUE_BIT7_0; + pwm->pwm_ctl[4] |= (pwm_conf->pwm_value >> 1) + & PM8058_PWM_VALUE_BIT8; + } else { + pwm->pwm_ctl[3] = pwm_conf->pwm_value + & PM8058_PWM_VALUE_BIT5_0; + } + + len = 6; + } else { + int pause_cnt, j; + + /* Linear search for duty time */ + for (i = 0; i < PM8058_PWM_1KHZ_COUNT_MAX; i++) { + if (duty_msec[i] >= pwm_conf->lut_duty_ms) + break; + } + + ramp_enabled = pwm->pwm_ctl[0] & PM8058_PWM_RAMP_GEN_START; + pwm->pwm_ctl[0] &= PM8058_PWM_PWM_START; /* keep enabled */ + pwm->pwm_ctl[0] |= (i << PM8058_PWM_1KHZ_COUNT_SHIFT) & + PM8058_PWM_1KHZ_COUNT_MASK; + pwm->pwm_ctl[1] = pwm_conf->lut_hi_index & + PM8058_PWM_HIGH_INDEX_MASK; + pwm->pwm_ctl[2] = pwm_conf->lut_lo_index & + PM8058_PWM_LOW_INDEX_MASK; + + if (pwm_conf->flags & PM_PWM_LUT_REVERSE) + pwm->pwm_ctl[1] |= PM8058_PWM_REVERSE_EN; + if (pwm_conf->flags & PM_PWM_LUT_RAMP_UP) + pwm->pwm_ctl[2] |= PM8058_PWM_RAMP_UP; + if (pwm_conf->flags & PM_PWM_LUT_LOOP) + pwm->pwm_ctl[2] |= PM8058_PWM_LOOP_EN; + + /* Pause time */ + if (pwm_conf->flags & PM_PWM_LUT_PAUSE_HI_EN) { + /* Linear search for pause time */ + pause_cnt = (pwm_conf->lut_pause_hi + duty_msec[i] / 2) + / duty_msec[i]; + for (j = 0; j < PM8058_PWM_PAUSE_COUNT_MAX; j++) { + if (pause_count[j] >= pause_cnt) + break; + } + pwm->pwm_ctl[5] = (j << + PM8058_PWM_PAUSE_COUNT_HI_SHIFT) & + PM8058_PWM_PAUSE_COUNT_HI_MASK; + pwm->pwm_ctl[5] |= PM8058_PWM_PAUSE_ENABLE_HIGH; + } else + pwm->pwm_ctl[5] = 0; + + if (pwm_conf->flags & PM_PWM_LUT_PAUSE_LO_EN) { + /* Linear search for pause time */ + pause_cnt = (pwm_conf->lut_pause_lo + duty_msec[i] / 2) + / duty_msec[i]; + for (j = 0; j < PM8058_PWM_PAUSE_COUNT_MAX; j++) { + if (pause_count[j] >= pause_cnt) + break; + } + pwm->pwm_ctl[6] = (j << + PM8058_PWM_PAUSE_COUNT_LO_SHIFT) & + PM8058_PWM_PAUSE_COUNT_LO_MASK; + pwm->pwm_ctl[6] |= PM8058_PWM_PAUSE_ENABLE_LOW; + } else + pwm->pwm_ctl[6] = 0; + + len = 7; + } + + pm8058_pwm_bank_sel(pwm); + + for (i = 0; i < len; i++) { + rc = pm8058_write(pwm->chip->pm_chip, + SSBI_REG_ADDR_LPG_CTL(i), + &pwm->pwm_ctl[i], 1); + if (rc) { + pr_err("%s: pm8058_write(): rc=%d (PWM Ctl[%d])\n", + __func__, rc, i); + break; + } + } + + if (ramp_enabled) { + pwm->pwm_ctl[0] |= ramp_enabled; + pm8058_write(pwm->chip->pm_chip, SSBI_REG_ADDR_LPG_CTL(0), + &pwm->pwm_ctl[0], 1); + } + + return rc; +} + +/* APIs */ +/* + * pwm_request - request a PWM device + */ +struct pwm_device *pwm_request(int pwm_id, const char *label) +{ + struct pwm_device *pwm; + + if (pwm_id > PM8058_PWM_CHANNELS || pwm_id < 0) + return ERR_PTR(-EINVAL); + if (pwm_chip == NULL) + return ERR_PTR(-ENODEV); + + mutex_lock(&pwm_chip->pwm_mutex); + pwm = &pwm_chip->pwm_dev[pwm_id]; + if (!pwm->in_use) { + pwm->in_use = 1; + pwm->label = label; + + if (pwm_chip->pdata && pwm_chip->pdata->config) + pwm_chip->pdata->config(pwm, pwm_id, 1); + } else + pwm = ERR_PTR(-EBUSY); + mutex_unlock(&pwm_chip->pwm_mutex); + + return pwm; +} +EXPORT_SYMBOL(pwm_request); + +/* + * pwm_free - free a PWM device + */ +void pwm_free(struct pwm_device *pwm) +{ + if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) + return; + + mutex_lock(&pwm->chip->pwm_mutex); + if (pwm->in_use) { + pm8058_pwm_bank_sel(pwm); + pm8058_pwm_start(pwm, 0, 0); + + if (pwm->chip->pdata && pwm->chip->pdata->config) + pwm->chip->pdata->config(pwm, pwm->pwm_id, 0); + + pwm->in_use = 0; + pwm->label = NULL; + } + pm8058_pwm_bank_enable(pwm, 0); + mutex_unlock(&pwm->chip->pwm_mutex); +} +EXPORT_SYMBOL(pwm_free); + +/* + * pwm_config - change a PWM device configuration + * + * @pwm: the PWM device + * @period_us: period in micro second + * @duty_us: duty cycle in micro second + */ +int pwm_config(struct pwm_device *pwm, int duty_us, int period_us) +{ + struct pw8058_pwm_config pwm_conf; + unsigned int max_pwm_value, tmp; + int rc; + + if (pwm == NULL || IS_ERR(pwm) || + (unsigned)duty_us > (unsigned)period_us || + (unsigned)period_us > PM_PWM_PERIOD_MAX || + (unsigned)period_us < PM_PWM_PERIOD_MIN) + return -EINVAL; + if (pwm->chip == NULL) + return -ENODEV; + + mutex_lock(&pwm->chip->pwm_mutex); + + if (!pwm->in_use) { + rc = -EINVAL; + goto out_unlock; + } + + pm8058_pwm_calc_period(period_us, &pwm_conf); + + /* Figure out pwm_value with overflow handling */ + if ((unsigned)period_us > (1 << pwm_conf.pwm_size)) { + tmp = period_us; + tmp >>= pwm_conf.pwm_size; + pwm_conf.pwm_value = (unsigned)duty_us / tmp; + } else { + tmp = duty_us; + tmp <<= pwm_conf.pwm_size; + pwm_conf.pwm_value = tmp / (unsigned)period_us; + } + max_pwm_value = (1 << pwm_conf.pwm_size) - 1; + if (pwm_conf.pwm_value > max_pwm_value) + pwm_conf.pwm_value = max_pwm_value; + + pwm_conf.bypass_lut = 1; + + pr_debug("%s: duty/period=%u/%u usec: pwm_value=%d (of %d)\n", + __func__, (unsigned)duty_us, (unsigned)period_us, + pwm_conf.pwm_value, 1 << pwm_conf.pwm_size); + + rc = pm8058_pwm_configure(pwm, &pwm_conf); + +out_unlock: + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL(pwm_config); + +/* + * pwm_enable - start a PWM output toggling + */ +int pwm_enable(struct pwm_device *pwm) +{ + int rc; + + if (pwm == NULL || IS_ERR(pwm)) + return -EINVAL; + if (pwm->chip == NULL) + return -ENODEV; + + mutex_lock(&pwm->chip->pwm_mutex); + if (!pwm->in_use) + rc = -EINVAL; + else { + if (pwm->chip->pdata && pwm->chip->pdata->enable) + pwm->chip->pdata->enable(pwm, pwm->pwm_id, 1); + + rc = pm8058_pwm_bank_enable(pwm, 1); + + pm8058_pwm_bank_sel(pwm); + pm8058_pwm_start(pwm, 1, 0); + } + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL(pwm_enable); + +/* + * pwm_disable - stop a PWM output toggling + */ +void pwm_disable(struct pwm_device *pwm) +{ + if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) + return; + + mutex_lock(&pwm->chip->pwm_mutex); + if (pwm->in_use) { + pm8058_pwm_bank_sel(pwm); + pm8058_pwm_start(pwm, 0, 0); + + pm8058_pwm_bank_enable(pwm, 0); + + if (pwm->chip->pdata && pwm->chip->pdata->enable) + pwm->chip->pdata->enable(pwm, pwm->pwm_id, 0); + } + mutex_unlock(&pwm->chip->pwm_mutex); +} +EXPORT_SYMBOL(pwm_disable); + +/* + * pm8058_pwm_lut_config - change a PWM device configuration to use LUT + * + * @pwm: the PWM device + * @period_us: period in micro second + * @duty_pct: arrary of duty cycles in percent, like 20, 50. + * @duty_time_ms: time for each duty cycle in millisecond + * @start_idx: start index in lookup table from 0 to MAX-1 + * @idx_len: number of index + * @pause_lo: pause time in millisecond at low index + * @pause_hi: pause time in millisecond at high index + * @flags: control flags + * + */ +int pm8058_pwm_lut_config(struct pwm_device *pwm, int period_us, + int duty_pct[], int duty_time_ms, int start_idx, + int idx_len, int pause_lo, int pause_hi, int flags) +{ + struct pw8058_pwm_config pwm_conf; + unsigned int pwm_value, max_pwm_value; + u8 cfg0, cfg1; + int i, len; + int rc; + + if (pwm == NULL || IS_ERR(pwm) || !idx_len) + return -EINVAL; + if (duty_pct == NULL && !(flags & PM_PWM_LUT_NO_TABLE)) + return -EINVAL; + if (pwm->chip == NULL) + return -ENODEV; + if (idx_len >= PM_PWM_LUT_SIZE && start_idx) + return -EINVAL; + if ((start_idx + idx_len) > PM_PWM_LUT_SIZE) + return -EINVAL; + if ((unsigned)period_us > PM_PWM_PERIOD_MAX || + (unsigned)period_us < PM_PWM_PERIOD_MIN) + return -EINVAL; + + mutex_lock(&pwm->chip->pwm_mutex); + + if (!pwm->in_use) { + rc = -EINVAL; + goto out_unlock; + } + + pm8058_pwm_calc_period(period_us, &pwm_conf); + + len = (idx_len > PM_PWM_LUT_SIZE) ? PM_PWM_LUT_SIZE : idx_len; + + if (flags & PM_PWM_LUT_NO_TABLE) + goto after_table_write; + + max_pwm_value = (1 << pwm_conf.pwm_size) - 1; + for (i = 0; i < len; i++) { + pwm_value = (duty_pct[i] << pwm_conf.pwm_size) / 100; + /* Avoid overflow */ + if (pwm_value > max_pwm_value) + pwm_value = max_pwm_value; + cfg0 = pwm_value & 0xff; + cfg1 = (pwm_value >> 1) & 0x80; + cfg1 |= start_idx + i; + + pr_debug("%s: %d: pwm=%d\n", __func__, i, pwm_value); + + pm8058_write(pwm->chip->pm_chip, + SSBI_REG_ADDR_LPG_LUT_CFG0, + &cfg0, 1); + pm8058_write(pwm->chip->pm_chip, + SSBI_REG_ADDR_LPG_LUT_CFG1, + &cfg1, 1); + } + +after_table_write: + pwm_conf.lut_duty_ms = duty_time_ms; + pwm_conf.lut_lo_index = start_idx; + pwm_conf.lut_hi_index = start_idx + len - 1; + pwm_conf.lut_pause_lo = pause_lo; + pwm_conf.lut_pause_hi = pause_hi; + pwm_conf.flags = flags; + pwm_conf.bypass_lut = 0; + + rc = pm8058_pwm_configure(pwm, &pwm_conf); + +out_unlock: + mutex_unlock(&pwm->chip->pwm_mutex); + return rc; +} +EXPORT_SYMBOL(pm8058_pwm_lut_config); + +/* + * pm8058_pwm_lut_enable - control a PWM device to start/stop LUT ramp + * + * @pwm: the PWM device + * @start: to start (1), or stop (0) + */ +int pm8058_pwm_lut_enable(struct pwm_device *pwm, int start) +{ + if (pwm == NULL || IS_ERR(pwm)) + return -EINVAL; + if (pwm->chip == NULL) + return -ENODEV; + + mutex_lock(&pwm->chip->pwm_mutex); + if (start) { + pm8058_pwm_bank_enable(pwm, 1); + + pm8058_pwm_bank_sel(pwm); + pm8058_pwm_start(pwm, 1, 1); + } else { + pm8058_pwm_bank_sel(pwm); + pm8058_pwm_start(pwm, 0, 0); + + pm8058_pwm_bank_enable(pwm, 0); + } + mutex_unlock(&pwm->chip->pwm_mutex); + return 0; +} +EXPORT_SYMBOL(pm8058_pwm_lut_enable); + +#define SSBI_REG_ADDR_LED_BASE 0x131 +#define SSBI_REG_ADDR_LED(n) (SSBI_REG_ADDR_LED_BASE + (n)) +#define SSBI_REG_ADDR_FLASH_BASE 0x48 +#define SSBI_REG_ADDR_FLASH_DRV_1 0xFB +#define SSBI_REG_ADDR_FLASH(n) (((n) < 2 ? \ + SSBI_REG_ADDR_FLASH_BASE + (n) : \ + SSBI_REG_ADDR_FLASH_DRV_1)) + +#define PM8058_LED_CURRENT_SHIFT 3 +#define PM8058_LED_MODE_MASK 0x07 + +#define PM8058_FLASH_CURRENT_SHIFT 4 +#define PM8058_FLASH_MODE_MASK 0x03 +#define PM8058_FLASH_MODE_NONE 0 +#define PM8058_FLASH_MODE_DTEST1 1 +#define PM8058_FLASH_MODE_DTEST2 2 +#define PM8058_FLASH_MODE_PWM 3 + +int pm8058_pwm_config_led(struct pwm_device *pwm, int id, + int mode, int max_current) +{ + int rc; + u8 conf; + + switch (id) { + case PM_PWM_LED_0: + case PM_PWM_LED_1: + case PM_PWM_LED_2: + conf = mode & PM8058_LED_MODE_MASK; + conf |= (max_current / 2) << PM8058_LED_CURRENT_SHIFT; + rc = pm8058_write(pwm->chip->pm_chip, + SSBI_REG_ADDR_LED(id), &conf, 1); + break; + + case PM_PWM_LED_KPD: + case PM_PWM_LED_FLASH: + case PM_PWM_LED_FLASH1: + switch (mode) { + case PM_PWM_CONF_PWM1: + case PM_PWM_CONF_PWM2: + case PM_PWM_CONF_PWM3: + conf = PM8058_FLASH_MODE_PWM; + break; + case PM_PWM_CONF_DTEST1: + conf = PM8058_FLASH_MODE_DTEST1; + break; + case PM_PWM_CONF_DTEST2: + conf = PM8058_FLASH_MODE_DTEST2; + break; + default: + conf = PM8058_FLASH_MODE_NONE; + break; + } + conf |= (max_current / 20) << PM8058_FLASH_CURRENT_SHIFT; + id -= PM_PWM_LED_KPD; + rc = pm8058_write(pwm->chip->pm_chip, + SSBI_REG_ADDR_FLASH(id), &conf, 1); + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} +EXPORT_SYMBOL(pm8058_pwm_config_led); + +int pm8058_pwm_set_dtest(struct pwm_device *pwm, int enable) +{ + int rc; + u8 reg; + + if (pwm == NULL || IS_ERR(pwm)) + return -EINVAL; + if (pwm->chip == NULL) + return -ENODEV; + + if (!pwm->in_use) + rc = -EINVAL; + else { + reg = pwm->pwm_id & PM8058_PWM_DTEST_BANK_MASK; + if (enable) + /* Only Test 1 available */ + reg |= (1 << PM8058_PWM_DTEST_SHIFT) & + PM8058_PWM_DTEST_MASK; + rc = pm8058_write(pwm->chip->pm_chip, SSBI_REG_ADDR_LPG_TEST, + ®, 1); + if (rc) + pr_err("%s: pm8058_write(DTEST=0x%x): rc=%d\n", + __func__, reg, rc); + + } + return rc; +} +EXPORT_SYMBOL(pm8058_pwm_set_dtest); + +static int __devinit pmic8058_pwm_probe(struct platform_device *pdev) +{ + struct pm8058_chip *pm_chip; + struct pm8058_pwm_chip *chip; + int i; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + pr_err("%s: no parent data passed in.\n", __func__); + return -EFAULT; + } + + chip = kzalloc(sizeof *chip, GFP_KERNEL); + if (chip == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < PM8058_PWM_CHANNELS; i++) { + chip->pwm_dev[i].pwm_id = i; + chip->pwm_dev[i].chip = chip; + } + + mutex_init(&chip->pwm_mutex); + + chip->pdata = pdev->dev.platform_data; + chip->pm_chip = pm_chip; + pwm_chip = chip; + platform_set_drvdata(pdev, chip); + + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit pmic8058_pwm_remove(struct platform_device *pdev) +{ + struct pm8058_pwm_chip *chip = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + kfree(chip); + return 0; +} + +static struct platform_driver pmic8058_pwm_driver = { + .probe = pmic8058_pwm_probe, + .remove = __devexit_p(pmic8058_pwm_remove), + .driver = { + .name = "pm8058-pwm", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_pwm_init(void) +{ + return platform_driver_register(&pmic8058_pwm_driver); +} + +static void __exit pm8058_pwm_exit(void) +{ + platform_driver_unregister(&pmic8058_pwm_driver); +} + +subsys_initcall(pm8058_pwm_init); +module_exit(pm8058_pwm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 PWM driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8058_pwm"); diff --git a/drivers/misc/pmic8058-upl.c b/drivers/misc/pmic8058-upl.c new file mode 100644 index 00000000000..ae0abd86423 --- /dev/null +++ b/drivers/misc/pmic8058-upl.c @@ -0,0 +1,363 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8058 UPL driver + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* PMIC8058 UPL registers */ +#define SSBI_REG_UPL_CTRL 0x17B +#define SSBI_REG_UPL_TRUTHTABLE1 0x17C +#define SSBI_REG_UPL_TRUTHTABLE2 0x17D + +struct pm8058_upl_device { + struct mutex upl_mutex; + struct pm8058_chip *pm_chip; +#if defined(CONFIG_DEBUG_FS) + struct dentry *dent; +#endif +}; +static struct pm8058_upl_device *upl_dev; + +/* APIs */ + +/* + * pm8058_upl_request - request a handle to access UPL device + */ +struct pm8058_upl_device *pm8058_upl_request(void) +{ + return upl_dev; +} +EXPORT_SYMBOL(pm8058_upl_request); + +/* + * pm8058_upl_read_truthtable - read value currently stored in UPL truth table + * + * @upldev: the UPL device + * @truthtable: value read from UPL truth table + */ +int pm8058_upl_read_truthtable(struct pm8058_upl_device *upldev, + u16 *truthtable) +{ + int rc = 0; + u8 table[2]; + + if (upldev == NULL || IS_ERR(upldev)) + return -EINVAL; + if (upldev->pm_chip == NULL) + return -ENODEV; + + mutex_lock(&upldev->upl_mutex); + + rc = pm8058_read(upldev->pm_chip, SSBI_REG_UPL_TRUTHTABLE1, + &(table[0]), 1); + if (rc) { + pr_err("%s: FAIL pm8058_read(0x%X)=0x%02X: rc=%d\n", + __func__, SSBI_REG_UPL_TRUTHTABLE1, table[0], rc); + goto upl_read_done; + } + + rc = pm8058_read(upldev->pm_chip, SSBI_REG_UPL_TRUTHTABLE2, + &(table[1]), 1); + if (rc) + pr_err("%s: FAIL pm8058_read(0x%X)=0x%02X: rc=%d\n", + __func__, SSBI_REG_UPL_TRUTHTABLE2, table[1], rc); +upl_read_done: + mutex_unlock(&upldev->upl_mutex); + *truthtable = (((u16)table[1]) << 8) | table[0]; + return rc; +} +EXPORT_SYMBOL(pm8058_upl_read_truthtable); + +/* + * pm8058_upl_writes_truthtable - write value into UPL truth table + * + * @upldev: the UPL device + * @truthtable: value written to UPL truth table + * + * Each bit in parameter "truthtable" corresponds to the UPL output for a given + * set of input pin values. For example, if the input pins have the following + * values: A=1, B=1, C=1, D=0, then the UPL would output the value of bit 14 + * (0b1110) in parameter "truthtable". + */ +int pm8058_upl_write_truthtable(struct pm8058_upl_device *upldev, + u16 truthtable) +{ + int rc = 0; + u8 table[2]; + + if (upldev == NULL || IS_ERR(upldev)) + return -EINVAL; + if (upldev->pm_chip == NULL) + return -ENODEV; + + table[0] = truthtable & 0xFF; + table[1] = (truthtable >> 8) & 0xFF; + + mutex_lock(&upldev->upl_mutex); + + rc = pm8058_write(upldev->pm_chip, SSBI_REG_UPL_TRUTHTABLE1, + &(table[0]), 1); + if (rc) { + pr_err("%s: FAIL pm8058_write(0x%X)=0x%04X: rc=%d\n", + __func__, SSBI_REG_UPL_TRUTHTABLE1, table[0], rc); + goto upl_write_done; + } + + rc = pm8058_write(upldev->pm_chip, SSBI_REG_UPL_TRUTHTABLE2, + &(table[1]), 1); + if (rc) + pr_err("%s: FAIL pm8058_write(0x%X)=0x%04X: rc=%d\n", + __func__, SSBI_REG_UPL_TRUTHTABLE2, table[1], rc); +upl_write_done: + mutex_unlock(&upldev->upl_mutex); + return rc; +} +EXPORT_SYMBOL(pm8058_upl_write_truthtable); + +/* + * pm8058_upl_config - configure UPL I/O settings and UPL enable/disable + * + * @upldev: the UPL device + * @mask: setting mask to configure + * @flags: setting flags + */ +int pm8058_upl_config(struct pm8058_upl_device *upldev, u32 mask, u32 flags) +{ + int rc; + u8 upl_ctrl, m, f; + + if (upldev == NULL || IS_ERR(upldev)) + return -EINVAL; + if (upldev->pm_chip == NULL) + return -ENODEV; + + mutex_lock(&upldev->upl_mutex); + + rc = pm8058_read(upldev->pm_chip, SSBI_REG_UPL_CTRL, &upl_ctrl, 1); + if (rc) { + pr_err("%s: FAIL pm8058_read(0x%X)=0x%02X: rc=%d\n", + __func__, SSBI_REG_UPL_CTRL, upl_ctrl, rc); + goto upl_config_done; + } + + m = mask & 0x00ff; + f = flags & 0x00ff; + upl_ctrl &= ~m; + upl_ctrl |= m & f; + + rc = pm8058_write(upldev->pm_chip, SSBI_REG_UPL_CTRL, &upl_ctrl, 1); + if (rc) + pr_err("%s: FAIL pm8058_write(0x%X)=0x%02X: rc=%d\n", + __func__, SSBI_REG_UPL_CTRL, upl_ctrl, rc); +upl_config_done: + mutex_unlock(&upldev->upl_mutex); + return rc; +} +EXPORT_SYMBOL(pm8058_upl_config); + +#if defined(CONFIG_DEBUG_FS) + +static int truthtable_set(void *data, u64 val) +{ + int rc; + + rc = pm8058_upl_write_truthtable(data, val); + if (rc) + pr_err("%s: pm8058_upl_write_truthtable: rc=%d, " + "truthtable=0x%llX\n", __func__, rc, val); + return rc; +} + +static int truthtable_get(void *data, u64 *val) +{ + int rc; + u16 truthtable; + + rc = pm8058_upl_read_truthtable(data, &truthtable); + if (rc) + pr_err("%s: pm8058_upl_read_truthtable: rc=%d, " + "truthtable=0x%X\n", __func__, rc, truthtable); + if (val) + *val = truthtable; + + return rc; +} + +DEFINE_SIMPLE_ATTRIBUTE(upl_truthtable_fops, truthtable_get, + truthtable_set, "0x%04llX\n"); + +/* enter values as 0xMMMMFFFF where MMMM is the mask and FFFF is the flags */ +static int control_set(void *data, u64 val) +{ + u8 mask, flags; + int rc; + + flags = val & 0xFFFF; + mask = (val >> 16) & 0xFFFF; + + rc = pm8058_upl_config(data, mask, flags); + if (rc) + pr_err("%s: pm8058_upl_config: rc=%d, mask = 0x%X, " + "flags = 0x%X\n", __func__, rc, mask, flags); + return rc; +} + +static int control_get(void *data, u64 *val) +{ + struct pm8058_upl_device *upldev; + int rc = 0; + u8 ctrl; + + upldev = data; + + mutex_lock(&upldev->upl_mutex); + + rc = pm8058_read(upldev->pm_chip, SSBI_REG_UPL_CTRL, &ctrl, 1); + if (rc) + pr_err("%s: FAIL pm8058_read(): rc=%d (ctrl=0x%02X)\n", + __func__, rc, ctrl); + + mutex_unlock(&upldev->upl_mutex); + + *val = ctrl; + + return rc; +} + +DEFINE_SIMPLE_ATTRIBUTE(upl_control_fops, control_get, + control_set, "0x%02llX\n"); + +static int pm8058_upl_debug_init(struct pm8058_upl_device *upldev) +{ + struct dentry *dent; + struct dentry *temp; + + dent = debugfs_create_dir("pm8058-upl", NULL); + if (dent == NULL || IS_ERR(dent)) { + pr_err("%s: ERR debugfs_create_dir: dent=0x%X\n", + __func__, (unsigned)dent); + return -ENOMEM; + } + + temp = debugfs_create_file("truthtable", S_IRUSR | S_IWUSR, dent, + upldev, &upl_truthtable_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("%s: ERR debugfs_create_file: dent=0x%X\n", + __func__, (unsigned)dent); + goto debug_error; + } + + temp = debugfs_create_file("control", S_IRUSR | S_IWUSR, dent, + upldev, &upl_control_fops); + if (temp == NULL || IS_ERR(temp)) { + pr_err("%s: ERR debugfs_create_file: dent=0x%X\n", + __func__, (unsigned)dent); + goto debug_error; + } + + upldev->dent = dent; + return 0; + +debug_error: + debugfs_remove_recursive(dent); + return -ENOMEM; +} + +static int __devexit pm8058_upl_debug_remove(struct pm8058_upl_device *upldev) +{ + debugfs_remove_recursive(upldev->dent); + return 0; +} + +#endif /* CONFIG_DEBUG_FS */ + +static int __devinit pmic8058_upl_probe(struct platform_device *pdev) +{ + struct pm8058_chip *pm_chip; + struct pm8058_upl_device *upldev; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + pr_err("%s: no parent data passed in.\n", __func__); + return -EFAULT; + } + + upldev = kzalloc(sizeof *upldev, GFP_KERNEL); + if (upldev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + mutex_init(&upldev->upl_mutex); + + upldev->pm_chip = pm_chip; + upl_dev = upldev; + platform_set_drvdata(pdev, upldev); + +#if defined(CONFIG_DEBUG_FS) + pm8058_upl_debug_init(upl_dev); +#endif + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit pmic8058_upl_remove(struct platform_device *pdev) +{ + struct pm8058_upl_device *upldev = platform_get_drvdata(pdev); + +#if defined(CONFIG_DEBUG_FS) + pm8058_upl_debug_remove(upldev); +#endif + + platform_set_drvdata(pdev, upldev->pm_chip); + kfree(upldev); + pr_notice("%s: OK\n", __func__); + + return 0; +} + +static struct platform_driver pmic8058_upl_driver = { + .probe = pmic8058_upl_probe, + .remove = __devexit_p(pmic8058_upl_remove), + .driver = { + .name = "pm8058-upl", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_upl_init(void) +{ + return platform_driver_register(&pmic8058_upl_driver); +} + +static void __exit pm8058_upl_exit(void) +{ + platform_driver_unregister(&pmic8058_upl_driver); +} + +module_init(pm8058_upl_init); +module_exit(pm8058_upl_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 UPL driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8058-upl"); diff --git a/drivers/misc/pmic8058-vibrator.c b/drivers/misc/pmic8058-vibrator.c new file mode 100644 index 00000000000..1b15b18f015 --- /dev/null +++ b/drivers/misc/pmic8058-vibrator.c @@ -0,0 +1,307 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../staging/android/timed_output.h" + +#define VIB_DRV 0x4A + +#define VIB_DRV_SEL_MASK 0xf8 +#define VIB_DRV_SEL_SHIFT 0x03 +#define VIB_DRV_EN_MANUAL_MASK 0xfc + +#define VIB_MAX_LEVEL_mV 3100 +#define VIB_MIN_LEVEL_mV 1200 + +struct pmic8058_vib { + struct hrtimer vib_timer; + struct timed_output_dev timed_dev; + spinlock_t lock; + struct work_struct work; + + struct device *dev; + struct pmic8058_vibrator_pdata *pdata; + int state; + int level; + u8 reg_vib_drv; + + struct pm8058_chip *pm_chip; +}; + +/* REVISIT: just for debugging, will be removed in final working version */ +static void __dump_vib_regs(struct pmic8058_vib *vib, char *msg) +{ + u8 temp; + + dev_dbg(vib->dev, "%s\n", msg); + + pm8058_read(vib->pm_chip, VIB_DRV, &temp, 1); + dev_dbg(vib->dev, "VIB_DRV - %X\n", temp); +} + +static int pmic8058_vib_read_u8(struct pmic8058_vib *vib, + u8 *data, u16 reg) +{ + int rc; + + rc = pm8058_read(vib->pm_chip, reg, data, 1); + if (rc < 0) + dev_warn(vib->dev, "Error reading pmic8058: %X - ret %X\n", + reg, rc); + + return rc; +} + +static int pmic8058_vib_write_u8(struct pmic8058_vib *vib, + u8 data, u16 reg) +{ + int rc; + + rc = pm8058_write(vib->pm_chip, reg, &data, 1); + if (rc < 0) + dev_warn(vib->dev, "Error writing pmic8058: %X - ret %X\n", + reg, rc); + return rc; +} + +static int pmic8058_vib_set(struct pmic8058_vib *vib, int on) +{ + int rc; + u8 val; + + if (on) { + rc = pm_runtime_resume(vib->dev); + if (rc < 0) + dev_dbg(vib->dev, "pm_runtime_resume failed\n"); + + val = vib->reg_vib_drv; + val |= ((vib->level << VIB_DRV_SEL_SHIFT) & VIB_DRV_SEL_MASK); + rc = pmic8058_vib_write_u8(vib, val, VIB_DRV); + if (rc < 0) + return rc; + vib->reg_vib_drv = val; + } else { + val = vib->reg_vib_drv; + val &= ~VIB_DRV_SEL_MASK; + rc = pmic8058_vib_write_u8(vib, val, VIB_DRV); + if (rc < 0) + return rc; + vib->reg_vib_drv = val; + + rc = pm_runtime_suspend(vib->dev); + if (rc < 0) + dev_dbg(vib->dev, "pm_runtime_suspend failed\n"); + } + __dump_vib_regs(vib, "vib_set_end"); + + return rc; +} + +static void pmic8058_vib_enable(struct timed_output_dev *dev, int value) +{ + struct pmic8058_vib *vib = container_of(dev, struct pmic8058_vib, + timed_dev); + unsigned long flags; + + spin_lock_irqsave(&vib->lock, flags); + hrtimer_cancel(&vib->vib_timer); + + if (value == 0) + vib->state = 0; + else { + value = (value > vib->pdata->max_timeout_ms ? + vib->pdata->max_timeout_ms : value); + vib->state = 1; + hrtimer_start(&vib->vib_timer, + ktime_set(value / 1000, (value % 1000) * 1000000), + HRTIMER_MODE_REL); + } + spin_unlock_irqrestore(&vib->lock, flags); + schedule_work(&vib->work); +} + +static void pmic8058_vib_update(struct work_struct *work) +{ + struct pmic8058_vib *vib = container_of(work, struct pmic8058_vib, + work); + + pmic8058_vib_set(vib, vib->state); +} + +static int pmic8058_vib_get_time(struct timed_output_dev *dev) +{ + struct pmic8058_vib *vib = container_of(dev, struct pmic8058_vib, + timed_dev); + + if (hrtimer_active(&vib->vib_timer)) { + ktime_t r = hrtimer_get_remaining(&vib->vib_timer); + return (int) ktime_to_us(r); + } else + return 0; +} + +static enum hrtimer_restart pmic8058_vib_timer_func(struct hrtimer *timer) +{ + struct pmic8058_vib *vib = container_of(timer, struct pmic8058_vib, + vib_timer); + vib->state = 0; + schedule_work(&vib->work); + return HRTIMER_NORESTART; +} + +#ifdef CONFIG_PM +static int pmic8058_vib_suspend(struct device *dev) +{ + struct pmic8058_vib *vib = dev_get_drvdata(dev); + + hrtimer_cancel(&vib->vib_timer); + cancel_work_sync(&vib->work); + /* turn-off vibrator */ + pmic8058_vib_set(vib, 0); + return 0; +} + +static struct dev_pm_ops pmic8058_vib_pm_ops = { + .suspend = pmic8058_vib_suspend, +}; +#endif + +static int __devinit pmic8058_vib_probe(struct platform_device *pdev) + +{ + struct pmic8058_vibrator_pdata *pdata = pdev->dev.platform_data; + struct pmic8058_vib *vib; + u8 val; + int rc; + + struct pm8058_chip *pm_chip; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + dev_err(&pdev->dev, "no parent data passed in\n"); + return -EFAULT; + } + + if (!pdata) + return -EINVAL; + + if (pdata->level_mV < VIB_MIN_LEVEL_mV || + pdata->level_mV > VIB_MAX_LEVEL_mV) + return -EINVAL; + + vib = kzalloc(sizeof(*vib), GFP_KERNEL); + if (!vib) + return -ENOMEM; + + /* Enable runtime PM ops, start in ACTIVE mode */ + rc = pm_runtime_set_active(&pdev->dev); + if (rc < 0) + dev_dbg(&pdev->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&pdev->dev); + + vib->pm_chip = pm_chip; + vib->pdata = pdata; + vib->level = pdata->level_mV / 100; + vib->dev = &pdev->dev; + + spin_lock_init(&vib->lock); + INIT_WORK(&vib->work, pmic8058_vib_update); + + hrtimer_init(&vib->vib_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + vib->vib_timer.function = pmic8058_vib_timer_func; + + vib->timed_dev.name = "vibrator"; + vib->timed_dev.get_time = pmic8058_vib_get_time; + vib->timed_dev.enable = pmic8058_vib_enable; + + __dump_vib_regs(vib, "boot_vib_default"); + + /* operate in manual mode */ + rc = pmic8058_vib_read_u8(vib, &val, VIB_DRV); + if (rc < 0) + goto err_read_vib; + val &= ~VIB_DRV_EN_MANUAL_MASK; + rc = pmic8058_vib_write_u8(vib, val, VIB_DRV); + if (rc < 0) + goto err_read_vib; + + vib->reg_vib_drv = val; + + rc = timed_output_dev_register(&vib->timed_dev); + if (rc < 0) + goto err_read_vib; + + pmic8058_vib_enable(&vib->timed_dev, pdata->initial_vibrate_ms); + + platform_set_drvdata(pdev, vib); + + pm_runtime_set_suspended(&pdev->dev); + return 0; + +err_read_vib: + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + kfree(vib); + return rc; +} + +static int __devexit pmic8058_vib_remove(struct platform_device *pdev) +{ + struct pmic8058_vib *vib = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + cancel_work_sync(&vib->work); + hrtimer_cancel(&vib->vib_timer); + timed_output_dev_unregister(&vib->timed_dev); + kfree(vib); + + return 0; +} + +static struct platform_driver pmic8058_vib_driver = { + .probe = pmic8058_vib_probe, + .remove = __devexit_p(pmic8058_vib_remove), + .driver = { + .name = "pm8058-vib", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &pmic8058_vib_pm_ops, +#endif + }, +}; + +static int __init pmic8058_vib_init(void) +{ + return platform_driver_register(&pmic8058_vib_driver); +} +module_init(pmic8058_vib_init); + +static void __exit pmic8058_vib_exit(void) +{ + platform_driver_unregister(&pmic8058_vib_driver); +} +module_exit(pmic8058_vib_exit); + +MODULE_ALIAS("platform:pmic8058_vib"); +MODULE_DESCRIPTION("PMIC8058 vibrator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/pmic8058-xoadc.c b/drivers/misc/pmic8058-xoadc.c new file mode 100644 index 00000000000..d2d8cbaf6ab --- /dev/null +++ b/drivers/misc/pmic8058-xoadc.c @@ -0,0 +1,770 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ADC_DRIVER_NAME "pm8058-xoadc" + +#define MAX_QUEUE_LENGTH 0X15 +#define MAX_CHANNEL_PROPERTIES_QUEUE 0X7 +#define MAX_QUEUE_SLOT 0x1 + +/* User Processor */ +#define ADC_ARB_USRP_CNTRL 0x197 + #define ADC_ARB_USRP_CNTRL_EN_ARB BIT(0) + #define ADC_ARB_USRP_CNTRL_RSV1 BIT(1) + #define ADC_ARB_USRP_CNTRL_RSV2 BIT(2) + #define ADC_ARB_USRP_CNTRL_RSV3 BIT(3) + #define ADC_ARB_USRP_CNTRL_RSV4 BIT(4) + #define ADC_ARB_USRP_CNTRL_RSV5 BIT(5) + #define ADC_ARB_USRP_CNTRL_EOC BIT(6) + #define ADC_ARB_USRP_CNTRL_REQ BIT(7) + +#define ADC_ARB_USRP_AMUX_CNTRL 0x198 +#define ADC_ARB_USRP_ANA_PARAM 0x199 +#define ADC_ARB_USRP_DIG_PARAM 0x19A +#define ADC_ARB_USRP_RSV 0x19B + +#define ADC_ARB_USRP_DATA0 0x19D +#define ADC_ARB_USRP_DATA1 0x19C + +struct pmic8058_adc { + struct xoadc_platform_data *pdata; + struct pm8058_chip *pm_chip; + struct adc_properties *adc_prop; + struct xoadc_conv_state conv[2]; + int xoadc_queue_count; + int adc_irq; + struct linear_graph *adc_graph; + struct xoadc_conv_state *conv_slot_request; + struct xoadc_conv_state *conv_queue_list; + struct adc_conv_slot conv_queue_elements[MAX_QUEUE_LENGTH]; + int xoadc_num; + struct msm_xo_voter *adc_voter; +}; + +static struct pmic8058_adc *pmic_adc[XOADC_PMIC_0 + 1]; + +static bool xoadc_initialized, xoadc_calib_first_adc; + +DEFINE_RATELIMIT_STATE(pm8058_xoadc_msg_ratelimit, + DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); + +static inline int pm8058_xoadc_can_print(void) +{ + return __ratelimit(&pm8058_xoadc_msg_ratelimit); +} + +int32_t pm8058_xoadc_registered(void) +{ + return xoadc_initialized; +} +EXPORT_SYMBOL(pm8058_xoadc_registered); + +void pm8058_xoadc_restore_slot(uint32_t adc_instance, + struct adc_conv_slot *slot) +{ + struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance]; + struct xoadc_conv_state *slot_state = adc_pmic->conv_slot_request; + + mutex_lock(&slot_state->list_lock); + list_add(&slot->list, &slot_state->slots); + mutex_unlock(&slot_state->list_lock); +} +EXPORT_SYMBOL(pm8058_xoadc_restore_slot); + +void pm8058_xoadc_slot_request(uint32_t adc_instance, + struct adc_conv_slot **slot) +{ + struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance]; + struct xoadc_conv_state *slot_state = adc_pmic->conv_slot_request; + + mutex_lock(&slot_state->list_lock); + + if (!list_empty(&slot_state->slots)) { + *slot = list_first_entry(&slot_state->slots, + struct adc_conv_slot, list); + list_del(&(*slot)->list); + } else + *slot = NULL; + + mutex_unlock(&slot_state->list_lock); +} +EXPORT_SYMBOL(pm8058_xoadc_slot_request); + +static int32_t pm8058_xoadc_arb_cntrl(uint32_t arb_cntrl, + uint32_t adc_instance) +{ + struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance]; + int i, rc; + u8 data_arb_cntrl; + + data_arb_cntrl = ADC_ARB_USRP_CNTRL_EOC | + ADC_ARB_USRP_CNTRL_RSV5 | + ADC_ARB_USRP_CNTRL_RSV4; + + if (arb_cntrl) { + data_arb_cntrl |= ADC_ARB_USRP_CNTRL_EN_ARB; + msm_xo_mode_vote(adc_pmic->adc_voter, MSM_XO_MODE_ON); + adc_pmic->pdata->xoadc_mpp_config(); + } + + /* Write twice to the CNTRL register for the arbiter settings + to take into effect */ + for (i = 0; i < 2; i++) { + rc = pm8058_write(adc_pmic->pm_chip, ADC_ARB_USRP_CNTRL, + &data_arb_cntrl, 1); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + } + + if (!arb_cntrl) + msm_xo_mode_vote(adc_pmic->adc_voter, MSM_XO_MODE_OFF); + + return 0; +} + +static int32_t pm8058_xoadc_configure(uint32_t adc_instance, + struct adc_conv_slot *slot) +{ + + struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance]; + u8 data_arb_cntrl, data_amux_chan, data_arb_rsv, data_ana_param; + u8 data_dig_param, data_ana_param2; + int rc; + + rc = pm8058_xoadc_arb_cntrl(1, adc_instance); + if (rc < 0) { + pr_debug("%s: Configuring ADC Arbiter" + "enable failed\n", __func__); + return rc; + } + + switch (slot->chan_path) { + + case CHAN_PATH_TYPE1: + data_amux_chan = CHANNEL_VCOIN << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 2; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE2: + data_amux_chan = CHANNEL_VBAT << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 3; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE3: + data_amux_chan = CHANNEL_VCHG << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 10; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE4: + data_amux_chan = CHANNEL_CHG_MONITOR << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 1; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE5: + data_amux_chan = CHANNEL_VPH_PWR << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 3; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE6: + data_amux_chan = CHANNEL_MPP5 << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 1; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE7: + data_amux_chan = CHANNEL_MPP6 << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 1; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE8: + data_amux_chan = CHANNEL_MPP7 << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 2; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE9: + data_amux_chan = CHANNEL_MPP8 << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 2; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE10: + data_amux_chan = CHANNEL_MPP9 << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 3; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE11: + data_amux_chan = CHANNEL_USB_VBUS << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 3; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE12: + data_amux_chan = CHANNEL_DIE_TEMP << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 1; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE13: + data_amux_chan = CHANNEL_125V << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 1; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE14: + data_amux_chan = CHANNEL_INTERNAL_2 << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 1; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + + case CHAN_PATH_TYPE_NONE: + data_amux_chan = CHANNEL_MUXOFF << 4; + data_arb_rsv = 0x10; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 1; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[1]; + break; + + case CHAN_PATH_TYPE15: + data_amux_chan = CHANNEL_INTERNAL << 4; + data_arb_rsv = 0x20; + slot->chan_properties.gain_numerator = 1; + slot->chan_properties.gain_denominator = 1; + slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0]; + break; + } + + rc = pm8058_write(adc_pmic->pm_chip, + ADC_ARB_USRP_AMUX_CNTRL, &data_amux_chan, 1); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + rc = pm8058_write(adc_pmic->pm_chip, + ADC_ARB_USRP_RSV, &data_arb_rsv, 1); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + /* Set default clock rate to 2.4 MHz XO ADC clock digital */ + switch (slot->chan_adc_config) { + + case ADC_CONFIG_TYPE1: + data_ana_param = 0xFE; + data_dig_param = 0x23; + data_ana_param2 = 0xFF; + /* AMUX register data to start the ADC conversion */ + data_arb_cntrl = 0xF1; + break; + + case ADC_CONFIG_TYPE2: + data_ana_param = 0xFE; + data_dig_param = 0x03; + data_ana_param2 = 0xFF; + /* AMUX register data to start the ADC conversion */ + data_arb_cntrl = 0xF1; + break; + } + + rc = pm8058_write(adc_pmic->pm_chip, + ADC_ARB_USRP_ANA_PARAM, &data_ana_param, 1); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + rc = pm8058_write(adc_pmic->pm_chip, + ADC_ARB_USRP_DIG_PARAM, &data_dig_param, 1); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + rc = pm8058_write(adc_pmic->pm_chip, + ADC_ARB_USRP_ANA_PARAM, &data_ana_param2, 1); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + enable_irq(adc_pmic->adc_irq); + + rc = pm8058_write(adc_pmic->pm_chip, + ADC_ARB_USRP_CNTRL, &data_arb_cntrl, 1); + if (rc < 0) { + pr_debug("%s: PM8058 write failed\n", __func__); + return rc; + } + + return 0; +} + +int32_t pm8058_xoadc_select_chan_and_start_conv(uint32_t adc_instance, + struct adc_conv_slot *slot) +{ + struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance]; + struct xoadc_conv_state *slot_state = adc_pmic->conv_queue_list; + + if (!xoadc_initialized) + return -ENODEV; + + mutex_lock(&slot_state->list_lock); + list_add_tail(&slot->list, &slot_state->slots); + if (adc_pmic->xoadc_queue_count == 0) { + if (adc_pmic->pdata->xoadc_vreg_set != NULL) + adc_pmic->pdata->xoadc_vreg_set(1); + pm8058_xoadc_configure(adc_instance, slot); + } + adc_pmic->xoadc_queue_count++; + mutex_unlock(&slot_state->list_lock); + + return 0; +} +EXPORT_SYMBOL(pm8058_xoadc_select_chan_and_start_conv); + +static int32_t pm8058_xoadc_dequeue_slot_request(uint32_t adc_instance, + struct adc_conv_slot **slot) +{ + struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance]; + struct xoadc_conv_state *slot_state = adc_pmic->conv_queue_list; + int rc = 0; + + mutex_lock(&slot_state->list_lock); + if (adc_pmic->xoadc_queue_count > 0 && + !list_empty(&slot_state->slots)) { + *slot = list_first_entry(&slot_state->slots, + struct adc_conv_slot, list); + list_del(&(*slot)->list); + } else + rc = -EINVAL; + mutex_unlock(&slot_state->list_lock); + + if (rc < 0) { + if (pm8058_xoadc_can_print()) + pr_err("Pmic 8058 xoadc spurious interrupt detected\n"); + return rc; + } + + return 0; +} + +int32_t pm8058_xoadc_read_adc_code(uint32_t adc_instance, int32_t *data) +{ + struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance]; + struct xoadc_conv_state *slot_state = adc_pmic->conv_queue_list; + uint8_t rslt_lsb, rslt_msb; + struct adc_conv_slot *slot; + int32_t rc, max_ideal_adc_code = 1 << adc_pmic->adc_prop->bitresolution; + + if (!xoadc_initialized) + return -ENODEV; + + rc = pm8058_read(adc_pmic->pm_chip, ADC_ARB_USRP_DATA0, &rslt_lsb, 1); + if (rc < 0) { + pr_debug("%s: PM8058 read failed\n", __func__); + return rc; + } + + rc = pm8058_read(adc_pmic->pm_chip, ADC_ARB_USRP_DATA1, &rslt_msb, 1); + if (rc < 0) { + pr_debug("%s: PM8058 read failed\n", __func__); + return rc; + } + + *data = (rslt_msb << 8) | rslt_lsb; + + /* Use the midpoint to determine underflow or overflow */ + if (*data > max_ideal_adc_code + (max_ideal_adc_code >> 1)) + *data |= ((1 << (8 * sizeof(*data) - + adc_pmic->adc_prop->bitresolution)) - 1) << + adc_pmic->adc_prop->bitresolution; + /* Return if this is a calibration run since there + * is no need to check requests in the waiting queue */ + if (xoadc_calib_first_adc) + return 0; + + mutex_lock(&slot_state->list_lock); + adc_pmic->xoadc_queue_count--; + if (adc_pmic->xoadc_queue_count > 0) { + slot = list_first_entry(&slot_state->slots, + struct adc_conv_slot, list); + pm8058_xoadc_configure(adc_instance, slot); + } + mutex_unlock(&slot_state->list_lock); + + mutex_lock(&slot_state->list_lock); + /* Default value for switching off the arbiter after reading + the ADC value. Bit 0 set to 0. */ + if (adc_pmic->xoadc_queue_count == 0) { + rc = pm8058_xoadc_arb_cntrl(0, adc_instance); + if (rc < 0) { + pr_debug("%s: Configuring ADC Arbiter disable" + "failed\n", __func__); + return rc; + } + if (adc_pmic->pdata->xoadc_vreg_set != NULL) + adc_pmic->pdata->xoadc_vreg_set(0); + } + mutex_unlock(&slot_state->list_lock); + + return 0; +} +EXPORT_SYMBOL(pm8058_xoadc_read_adc_code); + +static irqreturn_t pm8058_xoadc(int irq, void *dev_id) +{ + struct pmic8058_adc *xoadc_8058 = dev_id; + struct adc_conv_slot *slot = NULL; + int rc; + + disable_irq_nosync(xoadc_8058->adc_irq); + + if (xoadc_calib_first_adc) + return IRQ_HANDLED; + + rc = pm8058_xoadc_dequeue_slot_request(xoadc_8058->xoadc_num, &slot); + + if (rc < 0) + return IRQ_NONE; + + if (rc == 0) + msm_adc_conv_cb(slot, 0, NULL, 0); + + return IRQ_HANDLED; +} + +struct adc_properties *pm8058_xoadc_get_properties(uint32_t dev_instance) +{ + struct pmic8058_adc *xoadc_8058 = pmic_adc[dev_instance]; + + return xoadc_8058->adc_prop; +} +EXPORT_SYMBOL(pm8058_xoadc_get_properties); + +int32_t pm8058_xoadc_calib_device(uint32_t adc_instance) +{ + struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance]; + struct adc_conv_slot *slot; + int rc, offset_xoadc, slope_xoadc, calib_read_1, calib_read_2; + + if (adc_pmic->pdata->xoadc_vreg_set != NULL) + adc_pmic->pdata->xoadc_vreg_set(1); + + pm8058_xoadc_slot_request(adc_instance, &slot); + if (slot) { + slot->chan_path = CHAN_PATH_TYPE13; + slot->chan_adc_config = ADC_CONFIG_TYPE2; + slot->chan_adc_calib = ADC_CONFIG_TYPE2; + xoadc_calib_first_adc = true; + rc = pm8058_xoadc_configure(adc_instance, slot); + if (rc) { + pr_err("pm8058_xoadc configure failed\n"); + goto fail; + } + } else { + rc = -EINVAL; + goto fail; + } + + msleep(3); + + rc = pm8058_xoadc_read_adc_code(adc_instance, &calib_read_1); + if (rc) { + pr_err("pm8058_xoadc read adc failed\n"); + xoadc_calib_first_adc = false; + goto fail; + } + xoadc_calib_first_adc = false; + + pm8058_xoadc_slot_request(adc_instance, &slot); + if (slot) { + slot->chan_path = CHAN_PATH_TYPE15; + slot->chan_adc_config = ADC_CONFIG_TYPE2; + slot->chan_adc_calib = ADC_CONFIG_TYPE2; + xoadc_calib_first_adc = true; + rc = pm8058_xoadc_configure(adc_instance, slot); + if (rc) { + pr_err("pm8058_xoadc configure failed\n"); + goto fail; + } + } else { + rc = -EINVAL; + goto fail; + } + + msleep(3); + + rc = pm8058_xoadc_read_adc_code(adc_instance, &calib_read_2); + if (rc) { + pr_err("pm8058_xoadc read adc failed\n"); + xoadc_calib_first_adc = false; + goto fail; + } + xoadc_calib_first_adc = false; + + pm8058_xoadc_restore_slot(adc_instance, slot); + + slope_xoadc = (((calib_read_1 - calib_read_2) << 10)/ + CHANNEL_ADC_625_MV); + offset_xoadc = calib_read_2 - + ((slope_xoadc * CHANNEL_ADC_625_MV) >> 10); + + printk(KERN_INFO"pmic8058_xoadc:The offset for AMUX calibration" + "was %d\n", offset_xoadc); + + adc_pmic->adc_graph[0].offset = offset_xoadc; + adc_pmic->adc_graph[0].dy = (calib_read_1 - calib_read_2); + adc_pmic->adc_graph[0].dx = CHANNEL_ADC_625_MV; + + /* Retain ideal calibration settings for therm readings */ + adc_pmic->adc_graph[1].offset = 0 ; + adc_pmic->adc_graph[1].dy = (1 << 15) - 1; + adc_pmic->adc_graph[1].dx = 2200; + + if (adc_pmic->pdata->xoadc_vreg_set != NULL) + adc_pmic->pdata->xoadc_vreg_set(0); + + return 0; +fail: + if (adc_pmic->pdata->xoadc_vreg_set != NULL) + adc_pmic->pdata->xoadc_vreg_set(0); + + return rc; +} +EXPORT_SYMBOL(pm8058_xoadc_calib_device); + +int32_t pm8058_xoadc_calibrate(uint32_t dev_instance, + struct adc_conv_slot *slot, int *calib_status) +{ + *calib_status = CALIB_NOT_REQUIRED; + + return 0; +} +EXPORT_SYMBOL(pm8058_xoadc_calibrate); + +static int __devexit pm8058_xoadc_teardown(struct platform_device *pdev) +{ + struct pmic8058_adc *adc_pmic = platform_get_drvdata(pdev); + + if (adc_pmic->pdata->xoadc_vreg_shutdown != NULL) + adc_pmic->pdata->xoadc_vreg_shutdown(); + + msm_xo_put(adc_pmic->adc_voter); + platform_set_drvdata(pdev, adc_pmic->pm_chip); + device_init_wakeup(&pdev->dev, 0); + kfree(adc_pmic); + xoadc_initialized = false; + + return 0; +} + +static int __devinit pm8058_xoadc_probe(struct platform_device *pdev) +{ + struct xoadc_platform_data *pdata = pdev->dev.platform_data; + struct pm8058_chip *pm_chip; + struct pmic8058_adc *adc_pmic; + int i, rc = 0; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + dev_err(&pdev->dev, "no parent data passed in\n"); + return -EFAULT; + } + + if (!pdata) { + dev_err(&pdev->dev, "no platform data?\n"); + return -EINVAL; + } + + adc_pmic = kzalloc(sizeof(struct pmic8058_adc), GFP_KERNEL); + if (!adc_pmic) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + adc_pmic->pm_chip = pm_chip; + adc_pmic->adc_prop = pdata->xoadc_prop; + adc_pmic->xoadc_num = pdata->xoadc_num; + adc_pmic->xoadc_queue_count = 0; + + platform_set_drvdata(pdev, adc_pmic); + + if (adc_pmic->xoadc_num > XOADC_PMIC_0) { + dev_err(&pdev->dev, "ADC device not supported\n"); + rc = -EINVAL; + goto err_cleanup; + } + + adc_pmic->pdata = pdata; + adc_pmic->adc_graph = kzalloc(sizeof(struct linear_graph) + * MAX_CHANNEL_PROPERTIES_QUEUE, GFP_KERNEL); + if (!adc_pmic->adc_graph) { + dev_err(&pdev->dev, "Unable to allocate memory\n"); + rc = -ENOMEM; + goto err_cleanup; + } + + /* Will be replaced by individual channel calibration */ + for (i = 0; i < MAX_CHANNEL_PROPERTIES_QUEUE; i++) { + adc_pmic->adc_graph[i].offset = 0 ; + adc_pmic->adc_graph[i].dy = (1 << 15) - 1; + adc_pmic->adc_graph[i].dx = 2200; + } + + if (pdata->xoadc_mpp_config != NULL) + pdata->xoadc_mpp_config(); + + adc_pmic->conv_slot_request = &adc_pmic->conv[0]; + adc_pmic->conv_slot_request->context = + &adc_pmic->conv_queue_elements[0]; + + mutex_init(&adc_pmic->conv_slot_request->list_lock); + INIT_LIST_HEAD(&adc_pmic->conv_slot_request->slots); + + /* tie each slot and initwork them */ + for (i = 0; i < MAX_QUEUE_LENGTH; i++) { + list_add(&adc_pmic->conv_slot_request->context[i].list, + &adc_pmic->conv_slot_request->slots); + INIT_WORK(&adc_pmic->conv_slot_request->context[i].work, + msm_adc_wq_work); + init_completion(&adc_pmic->conv_slot_request->context[i].comp); + adc_pmic->conv_slot_request->context[i].idx = i; + } + + adc_pmic->conv_queue_list = &adc_pmic->conv[1]; + + mutex_init(&adc_pmic->conv_queue_list->list_lock); + INIT_LIST_HEAD(&adc_pmic->conv_queue_list->slots); + + adc_pmic->adc_irq = platform_get_irq(pdev, 0); + if (adc_pmic->adc_irq < 0) { + rc = -ENXIO; + goto err_cleanup; + } + + rc = request_threaded_irq(adc_pmic->adc_irq, + NULL, pm8058_xoadc, + IRQF_TRIGGER_RISING, "pm8058_adc_interrupt", adc_pmic); + if (rc) { + dev_err(&pdev->dev, "failed to request adc irq\n"); + goto err_cleanup; + } + + disable_irq(adc_pmic->adc_irq); + + device_init_wakeup(&pdev->dev, pdata->xoadc_wakeup); + + if (adc_pmic->adc_voter == NULL) { + adc_pmic->adc_voter = msm_xo_get(MSM_XO_TCXO_D1, + "pmic8058_xoadc"); + if (IS_ERR(adc_pmic->adc_voter)) { + dev_err(&pdev->dev, "Failed to get XO vote\n"); + goto err_cleanup; + } + } + + pmic_adc[adc_pmic->xoadc_num] = adc_pmic; + + if (pdata->xoadc_vreg_setup != NULL) + pdata->xoadc_vreg_setup(); + + xoadc_initialized = true; + xoadc_calib_first_adc = false; + + return 0; + +err_cleanup: + pm8058_xoadc_teardown(pdev); + + return rc; +} + +static struct platform_driver pm8058_xoadc_driver = { + .probe = pm8058_xoadc_probe, + .remove = __devexit_p(pm8058_xoadc_teardown), + .driver = { + .name = "pm8058-xoadc", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_xoadc_init(void) +{ + return platform_driver_register(&pm8058_xoadc_driver); +} +module_init(pm8058_xoadc_init); + +static void __exit pm8058_xoadc_exit(void) +{ + platform_driver_unregister(&pm8058_xoadc_driver); +} +module_exit(pm8058_xoadc_exit); + +MODULE_ALIAS("platform:pmic8058_xoadc"); +MODULE_DESCRIPTION("PMIC8058 XOADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/qfp_fuse.c b/drivers/misc/qfp_fuse.c new file mode 100644 index 00000000000..341e5b27d2c --- /dev/null +++ b/drivers/misc/qfp_fuse.c @@ -0,0 +1,410 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Time QFPROM requires to reliably burn a fuse. + */ +#define QFPROM_BLOW_TIMEOUT_US 10 +#define QFPROM_BLOW_TIMER_OFFSET 0x2038 +/* + * Denotes number of cycles required to blow the fuse. + */ +#define QFPROM_BLOW_TIMER_VALUE (QFPROM_BLOW_TIMEOUT_US * 83) + +#define QFPROM_BLOW_STATUS_OFFSET 0x204C +#define QFPROM_BLOW_STATUS_BUSY 0x01 +#define QFPROM_BLOW_STATUS_ERROR 0x02 + +#define QFP_FUSE_READY 0x01 +#define QFP_FUSE_OFF 0x00 + +struct qfp_priv_t { + uint32_t base; + uint32_t end; + struct mutex lock; + struct regulator *fuse_vdd; + u8 state; +}; + +/* We need only one instance of this for the driver */ +static struct qfp_priv_t *qfp_priv; + + +static int qfp_fuse_open(struct inode *inode, struct file *filp) +{ + if (qfp_priv == NULL) + return -ENODEV; + + filp->private_data = qfp_priv; + + return 0; +} + +static int qfp_fuse_release(struct inode *inode, struct file *filp) +{ + + filp->private_data = NULL; + + return 0; +} + +static inline int qfp_fuse_wait_for_fuse_blow(u32 *status) +{ + u32 timeout = QFPROM_BLOW_TIMEOUT_US; + /* wait for 400us before checking for the first time */ + udelay(400); + do { + *status = readl_relaxed( + qfp_priv->base + QFPROM_BLOW_STATUS_OFFSET); + + if (!(*status & QFPROM_BLOW_STATUS_BUSY)) + return 0; + + timeout--; + udelay(1); + } while (timeout); + pr_err("Timeout waiting for FUSE blow, status = %x\n", *status); + return -ETIMEDOUT; +} + +static inline int qfp_fuse_enable_regulator(void) +{ + int err; + err = regulator_enable(qfp_priv->fuse_vdd); + if (err != 0) + pr_err("Error (%d) enabling regulator\n", err); + return err; +} + +static inline int qfp_fuse_disable_regulator(void) +{ + int err; + err = regulator_disable(qfp_priv->fuse_vdd); + if (err != 0) + pr_err("Error (%d) disabling regulator\n", err); + return err; +} + +static int qfp_fuse_write_word(u32 *addr, u32 data) +{ + u32 blow_status = 0; + u32 read_data; + int err; + + /* Set QFPROM blow timer register */ + writel_relaxed(QFPROM_BLOW_TIMER_VALUE, + qfp_priv->base + QFPROM_BLOW_TIMER_OFFSET); + mb(); + + /* Enable LVS0 regulator */ + err = qfp_fuse_enable_regulator(); + if (err != 0) + return err; + + /* + * Wait for about 1ms. However msleep(1) can sleep for + * up to 20ms as per Documentation/timers/timers-howto.txt. + * Time is not a constraint here. + */ + + msleep(20); + + /* Write data */ + __raw_writel(data, addr); + mb(); + + /* blow_status = QFPROM_BLOW_STATUS_BUSY; */ + err = qfp_fuse_wait_for_fuse_blow(&blow_status); + if (err) { + qfp_fuse_disable_regulator(); + return err; + } + + /* Check error status */ + if (blow_status & QFPROM_BLOW_STATUS_ERROR) { + pr_err("Fuse blow status error: %d\n", blow_status); + qfp_fuse_disable_regulator(); + return -EFAULT; + } + + /* Disable regulator */ + qfp_fuse_disable_regulator(); + /* + * Wait for about 1ms. However msleep(1) can sleep for + * up to 20ms as per Documentation/timers/timers-howto.txt. + * Time is not a constraint here. + */ + msleep(20); + + /* Verify written data */ + read_data = readl_relaxed(addr); + if (read_data != data) { + pr_err("Error: read/write data mismatch\n"); + pr_err("Address = %p written data = %x read data = %x\n", + addr, data, read_data); + return -EFAULT; + } + + return 0; +} + +static long +qfp_fuse_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct qfp_fuse_req req; + u32 *buf = NULL; + int i; + + /* Verify user arguments. */ + if (_IOC_TYPE(cmd) != QFP_FUSE_IOC_MAGIC) + return -ENOTTY; + + switch (cmd) { + case QFP_FUSE_IOC_READ: + if (arg == 0) { + pr_err("user space arg not supplied\n"); + err = -EFAULT; + break; + } + + if (copy_from_user(&req, (void __user *)arg, sizeof(req))) { + pr_err("Error copying req from user space\n"); + err = -EFAULT; + break; + } + + /* Check for limits */ + if (!req.size) { + pr_err("Request size zero.\n"); + err = -EFAULT; + break; + } + + if (qfp_priv->base + req.offset + (req.size - 1) * 4 > + qfp_priv->end) { + pr_err("Req size exceeds QFPROM addr space\n"); + err = -EFAULT; + break; + } + + /* Allocate memory for buffer */ + buf = kzalloc(req.size * 4, GFP_KERNEL); + if (buf == NULL) { + pr_alert("No memory for data\n"); + err = -ENOMEM; + break; + } + + if (mutex_lock_interruptible(&qfp_priv->lock)) { + err = -ERESTARTSYS; + break; + } + + /* Read data */ + for (i = 0; i < req.size; i++) + buf[i] = readl_relaxed( + ((u32 *) (qfp_priv->base + req.offset)) + i); + + if (copy_to_user((void __user *)req.data, buf, 4*(req.size))) { + pr_err("Error copying to user space\n"); + err = -EFAULT; + } + + mutex_unlock(&qfp_priv->lock); + break; + + case QFP_FUSE_IOC_WRITE: + if (arg == 0) { + pr_err("user space arg not supplied\n"); + err = -EFAULT; + break; + } + + if (copy_from_user(&req, (void __user *)arg, sizeof(req))) { + pr_err("Error copying req from user space\n"); + err = -EFAULT; + break; + } + /* Check for limits */ + if (!req.size) { + pr_err("Request size zero.\n"); + err = -EFAULT; + break; + } + if (qfp_priv->base + req.offset + (req.size - 1) * 4 > + qfp_priv->end) { + pr_err("Req size exceeds QFPROM space\n"); + err = -EFAULT; + break; + } + + /* Allocate memory for buffer */ + buf = kzalloc(4 * (req.size), GFP_KERNEL); + if (buf == NULL) { + pr_alert("No memory for data\n"); + err = -ENOMEM; + break; + } + + /* Copy user data to local buffer */ + if (copy_from_user(buf, (void __user *)req.data, + 4 * (req.size))) { + pr_err("Error copying data from user space\n"); + err = -EFAULT; + break; + } + + if (mutex_lock_interruptible(&qfp_priv->lock)) { + err = -ERESTARTSYS; + break; + } + + /* Write data word at a time */ + for (i = 0; i < req.size && !err; i++) { + err = qfp_fuse_write_word(((u32 *) ( + qfp_priv->base + req.offset) + i), buf[i]); + } + + mutex_unlock(&qfp_priv->lock); + break; + default: + pr_err("Invalid ioctl command.\n"); + return -ENOTTY; + } + kfree(buf); + return err; +} + +static const struct file_operations qfp_fuse_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = qfp_fuse_ioctl, + .open = qfp_fuse_open, + .release = qfp_fuse_release +}; + +static struct miscdevice qfp_fuse_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "qfpfuse", + .fops = &qfp_fuse_fops +}; + + +static int qfp_fuse_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res; + const char *regulator_name = pdev->dev.platform_data; + + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + if (!regulator_name) + return -EINVAL; + + /* Initialize */ + qfp_priv = kzalloc(sizeof(struct qfp_priv_t), GFP_KERNEL); + + if (qfp_priv == NULL) { + pr_alert("Not enough memory to initialize device\n"); + return -ENOMEM; + } + + /* The driver is passed ioremapped address */ + qfp_priv->base = res->start; + qfp_priv->end = res->end; + + /* Get regulator for QFPROM writes */ + qfp_priv->fuse_vdd = regulator_get(NULL, regulator_name); + if (IS_ERR(qfp_priv->fuse_vdd)) { + ret = PTR_ERR(qfp_priv->fuse_vdd); + pr_err("Err (%d) getting %s\n", ret, regulator_name); + qfp_priv->fuse_vdd = NULL; + goto err; + } + + mutex_init(&qfp_priv->lock); + + ret = misc_register(&qfp_fuse_dev); + if (ret < 0) + goto err; + + pr_info("Fuse driver base:%x end:%x\n", qfp_priv->base, qfp_priv->end); + return 0; + +err: + if (qfp_priv->fuse_vdd) + regulator_put(qfp_priv->fuse_vdd); + + kfree(qfp_priv); + qfp_priv = NULL; + + return ret; + +} + +static int __devexit qfp_fuse_remove(struct platform_device *plat) +{ + if (qfp_priv && qfp_priv->fuse_vdd) + regulator_put(qfp_priv->fuse_vdd); + + kfree(qfp_priv); + qfp_priv = NULL; + + misc_deregister(&qfp_fuse_dev); + pr_info("Removing Fuse driver\n"); + return 0; +} + +static struct platform_driver qfp_fuse_driver = { + .probe = qfp_fuse_probe, + .remove = qfp_fuse_remove, + .driver = { + .name = "qfp_fuse_driver", + .owner = THIS_MODULE, + }, +}; + +static int __init qfp_fuse_init(void) +{ + return platform_driver_register(&qfp_fuse_driver); +} + +static void __exit qfp_fuse_exit(void) +{ + platform_driver_unregister(&qfp_fuse_driver); +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Rohit Vaswani "); +MODULE_DESCRIPTION("Driver to read/write to QFPROM fuses."); +MODULE_VERSION("1.01"); + +module_init(qfp_fuse_init); +module_exit(qfp_fuse_exit); diff --git a/drivers/misc/tsif.c b/drivers/misc/tsif.c new file mode 100644 index 00000000000..53d4ef2e0d8 --- /dev/null +++ b/drivers/misc/tsif.c @@ -0,0 +1,1564 @@ +/* + * TSIF Driver + * + * Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include /* Needed by all modules */ +#include /* Needed for KERN_INFO */ +#include /* Needed for the macros */ +#include /* IS_ERR etc. */ +#include + +#include /* XXX_mem_region */ +#include +#include /* dma_XXX */ +#include /* msleep */ + +#include /* ioXXX */ +#include /* copy_from_user */ +#include +#include +#include +#include +#include /* kfree, kzalloc */ + +#include +#include +#include + +/* + * TSIF register offsets + */ +#define TSIF_STS_CTL_OFF (0x0) +#define TSIF_TIME_LIMIT_OFF (0x4) +#define TSIF_CLK_REF_OFF (0x8) +#define TSIF_LPBK_FLAGS_OFF (0xc) +#define TSIF_LPBK_DATA_OFF (0x10) +#define TSIF_TEST_CTL_OFF (0x14) +#define TSIF_TEST_MODE_OFF (0x18) +#define TSIF_TEST_RESET_OFF (0x1c) +#define TSIF_TEST_EXPORT_OFF (0x20) +#define TSIF_TEST_CURRENT_OFF (0x24) + +#define TSIF_DATA_PORT_OFF (0x100) + +/* bits for TSIF_STS_CTL register */ +#define TSIF_STS_CTL_EN_IRQ (1 << 28) +#define TSIF_STS_CTL_PACK_AVAIL (1 << 27) +#define TSIF_STS_CTL_1ST_PACKET (1 << 26) +#define TSIF_STS_CTL_OVERFLOW (1 << 25) +#define TSIF_STS_CTL_LOST_SYNC (1 << 24) +#define TSIF_STS_CTL_TIMEOUT (1 << 23) +#define TSIF_STS_CTL_INV_SYNC (1 << 21) +#define TSIF_STS_CTL_INV_NULL (1 << 20) +#define TSIF_STS_CTL_INV_ERROR (1 << 19) +#define TSIF_STS_CTL_INV_ENABLE (1 << 18) +#define TSIF_STS_CTL_INV_DATA (1 << 17) +#define TSIF_STS_CTL_INV_CLOCK (1 << 16) +#define TSIF_STS_CTL_SPARE (1 << 15) +#define TSIF_STS_CTL_EN_NULL (1 << 11) +#define TSIF_STS_CTL_EN_ERROR (1 << 10) +#define TSIF_STS_CTL_LAST_BIT (1 << 9) +#define TSIF_STS_CTL_EN_TIME_LIM (1 << 8) +#define TSIF_STS_CTL_EN_TCR (1 << 7) +#define TSIF_STS_CTL_TEST_MODE (3 << 5) +#define TSIF_STS_CTL_EN_DM (1 << 4) +#define TSIF_STS_CTL_STOP (1 << 3) +#define TSIF_STS_CTL_START (1 << 0) + +/* + * Data buffering parameters + * + * Data stored in cyclic buffer; + * + * Data organized in chunks of packets. + * One chunk processed at a time by the data mover + * + */ +#define TSIF_PKTS_IN_CHUNK_DEFAULT (16) /**< packets in one DM chunk */ +#define TSIF_CHUNKS_IN_BUF_DEFAULT (8) +#define TSIF_PKTS_IN_CHUNK (tsif_device->pkts_per_chunk) +#define TSIF_CHUNKS_IN_BUF (tsif_device->chunks_per_buf) +#define TSIF_PKTS_IN_BUF (TSIF_PKTS_IN_CHUNK * TSIF_CHUNKS_IN_BUF) +#define TSIF_BUF_SIZE (TSIF_PKTS_IN_BUF * TSIF_PKT_SIZE) + +#define ROW_RESET (MSM_CLK_CTL_BASE + 0x214) +#define GLBL_CLK_ENA (MSM_CLK_CTL_BASE + 0x000) +#define CLK_HALT_STATEB (MSM_CLK_CTL_BASE + 0x104) +#define TSIF_NS_REG (MSM_CLK_CTL_BASE + 0x0b4) +#define TV_NS_REG (MSM_CLK_CTL_BASE + 0x0bc) + +/* used to create debugfs entries */ +static const struct { + const char *name; + mode_t mode; + int offset; +} debugfs_tsif_regs[] = { + {"sts_ctl", S_IRUGO | S_IWUSR, TSIF_STS_CTL_OFF}, + {"time_limit", S_IRUGO | S_IWUSR, TSIF_TIME_LIMIT_OFF}, + {"clk_ref", S_IRUGO | S_IWUSR, TSIF_CLK_REF_OFF}, + {"lpbk_flags", S_IRUGO | S_IWUSR, TSIF_LPBK_FLAGS_OFF}, + {"lpbk_data", S_IRUGO | S_IWUSR, TSIF_LPBK_DATA_OFF}, + {"test_ctl", S_IRUGO | S_IWUSR, TSIF_TEST_CTL_OFF}, + {"test_mode", S_IRUGO | S_IWUSR, TSIF_TEST_MODE_OFF}, + {"test_reset", S_IWUSR, TSIF_TEST_RESET_OFF}, + {"test_export", S_IRUGO | S_IWUSR, TSIF_TEST_EXPORT_OFF}, + {"test_current", S_IRUGO, TSIF_TEST_CURRENT_OFF}, + {"data_port", S_IRUSR, TSIF_DATA_PORT_OFF}, +}; + +/* structures for Data Mover */ +struct tsif_dmov_cmd { + dmov_box box; + dma_addr_t box_ptr; +}; + +struct msm_tsif_device; + +struct tsif_xfer { + struct msm_dmov_cmd hdr; + struct msm_tsif_device *tsif_device; + int busy; + int wi; /**< set devices's write index after xfer */ +}; + +struct msm_tsif_device { + struct list_head devlist; + struct platform_device *pdev; + struct resource *memres; + void __iomem *base; + unsigned int irq; + int mode; + u32 time_limit; + enum tsif_state state; + struct wake_lock wake_lock; + /* clocks */ + struct clk *tsif_clk; + struct clk *tsif_pclk; + struct clk *tsif_ref_clk; + /* debugfs */ + struct dentry *dent_tsif; + struct dentry *debugfs_tsif_regs[ARRAY_SIZE(debugfs_tsif_regs)]; + struct dentry *debugfs_gpio; + struct dentry *debugfs_action; + struct dentry *debugfs_dma; + struct dentry *debugfs_databuf; + struct debugfs_blob_wrapper blob_wrapper_databuf; + /* DMA related */ + int dma; + int crci; + void *data_buffer; + dma_addr_t data_buffer_dma; + u32 pkts_per_chunk; + u32 chunks_per_buf; + int ri; + int wi; + int dmwi; /**< DataMover write index */ + struct tsif_dmov_cmd *dmov_cmd[2]; + dma_addr_t dmov_cmd_dma[2]; + struct tsif_xfer xfer[2]; + struct tasklet_struct dma_refill; + /* statistics */ + u32 stat_rx; + u32 stat_overflow; + u32 stat_lost_sync; + u32 stat_timeout; + u32 stat_dmov_err; + u32 stat_soft_drop; + int stat_ifi; /* inter frame interval */ + u32 stat0, stat1; + /* client */ + void *client_data; + void (*client_notify)(void *client_data); +}; + +/* ===clocks begin=== */ + +static void tsif_put_clocks(struct msm_tsif_device *tsif_device) +{ + if (tsif_device->tsif_clk) { + clk_put(tsif_device->tsif_clk); + tsif_device->tsif_clk = NULL; + } + if (tsif_device->tsif_pclk) { + clk_put(tsif_device->tsif_pclk); + tsif_device->tsif_pclk = NULL; + } + + if (tsif_device->tsif_ref_clk) { + clk_put(tsif_device->tsif_ref_clk); + tsif_device->tsif_ref_clk = NULL; + } +} + +static int tsif_get_clocks(struct msm_tsif_device *tsif_device) +{ + struct msm_tsif_platform_data *pdata = + tsif_device->pdev->dev.platform_data; + int rc = 0; + + if (pdata->tsif_clk) { + tsif_device->tsif_clk = clk_get(NULL, pdata->tsif_clk); + if (IS_ERR(tsif_device->tsif_clk)) { + dev_err(&tsif_device->pdev->dev, "failed to get %s\n", + pdata->tsif_clk); + rc = PTR_ERR(tsif_device->tsif_clk); + tsif_device->tsif_clk = NULL; + goto ret; + } + } + if (pdata->tsif_pclk) { + tsif_device->tsif_pclk = clk_get(NULL, pdata->tsif_pclk); + if (IS_ERR(tsif_device->tsif_pclk)) { + dev_err(&tsif_device->pdev->dev, "failed to get %s\n", + pdata->tsif_pclk); + rc = PTR_ERR(tsif_device->tsif_pclk); + tsif_device->tsif_pclk = NULL; + goto ret; + } + } + if (pdata->tsif_ref_clk) { + tsif_device->tsif_ref_clk = clk_get(NULL, pdata->tsif_ref_clk); + if (IS_ERR(tsif_device->tsif_ref_clk)) { + dev_err(&tsif_device->pdev->dev, "failed to get %s\n", + pdata->tsif_ref_clk); + rc = PTR_ERR(tsif_device->tsif_ref_clk); + tsif_device->tsif_ref_clk = NULL; + goto ret; + } + } + return 0; +ret: + tsif_put_clocks(tsif_device); + return rc; +} + +static void tsif_clock(struct msm_tsif_device *tsif_device, int on) +{ + if (on) { + if (tsif_device->tsif_clk) + clk_enable(tsif_device->tsif_clk); + if (tsif_device->tsif_pclk) + clk_enable(tsif_device->tsif_pclk); + clk_enable(tsif_device->tsif_ref_clk); + } else { + if (tsif_device->tsif_clk) + clk_disable(tsif_device->tsif_clk); + if (tsif_device->tsif_pclk) + clk_disable(tsif_device->tsif_pclk); + clk_disable(tsif_device->tsif_ref_clk); + } +} +/* ===clocks end=== */ +/* ===gpio begin=== */ + +static void tsif_gpios_free(const struct msm_gpio *table, int size) +{ + int i; + const struct msm_gpio *g; + for (i = size-1; i >= 0; i--) { + g = table + i; + gpio_free(GPIO_PIN(g->gpio_cfg)); + } +} + +static int tsif_gpios_request(const struct msm_gpio *table, int size) +{ + int rc; + int i; + const struct msm_gpio *g; + for (i = 0; i < size; i++) { + g = table + i; + rc = gpio_request(GPIO_PIN(g->gpio_cfg), g->label); + if (rc) { + pr_err("gpio_request(%d) <%s> failed: %d\n", + GPIO_PIN(g->gpio_cfg), g->label ?: "?", rc); + goto err; + } + } + return 0; +err: + tsif_gpios_free(table, i); + return rc; +} + +static int tsif_gpios_disable(const struct msm_gpio *table, int size) +{ + int rc = 0; + int i; + const struct msm_gpio *g; + for (i = size-1; i >= 0; i--) { + int tmp; + g = table + i; + tmp = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_DISABLE); + if (tmp) { + pr_err("gpio_tlmm_config(0x%08x, GPIO_CFG_DISABLE)" + " <%s> failed: %d\n", + g->gpio_cfg, g->label ?: "?", rc); + pr_err("pin %d func %d dir %d pull %d drvstr %d\n", + GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg), + GPIO_DIR(g->gpio_cfg), GPIO_PULL(g->gpio_cfg), + GPIO_DRVSTR(g->gpio_cfg)); + if (!rc) + rc = tmp; + } + } + + return rc; +} + +static int tsif_gpios_enable(const struct msm_gpio *table, int size) +{ + int rc; + int i; + const struct msm_gpio *g; + for (i = 0; i < size; i++) { + g = table + i; + rc = gpio_tlmm_config(g->gpio_cfg, GPIO_CFG_ENABLE); + if (rc) { + pr_err("gpio_tlmm_config(0x%08x, GPIO_CFG_ENABLE)" + " <%s> failed: %d\n", + g->gpio_cfg, g->label ?: "?", rc); + pr_err("pin %d func %d dir %d pull %d drvstr %d\n", + GPIO_PIN(g->gpio_cfg), GPIO_FUNC(g->gpio_cfg), + GPIO_DIR(g->gpio_cfg), GPIO_PULL(g->gpio_cfg), + GPIO_DRVSTR(g->gpio_cfg)); + goto err; + } + } + return 0; +err: + tsif_gpios_disable(table, i); + return rc; +} + +static int tsif_gpios_request_enable(const struct msm_gpio *table, int size) +{ + int rc = tsif_gpios_request(table, size); + if (rc) + return rc; + rc = tsif_gpios_enable(table, size); + if (rc) + tsif_gpios_free(table, size); + return rc; +} + +static void tsif_gpios_disable_free(const struct msm_gpio *table, int size) +{ + tsif_gpios_disable(table, size); + tsif_gpios_free(table, size); +} + +static int tsif_start_gpios(struct msm_tsif_device *tsif_device) +{ + struct msm_tsif_platform_data *pdata = + tsif_device->pdev->dev.platform_data; + return tsif_gpios_request_enable(pdata->gpios, pdata->num_gpios); +} + +static void tsif_stop_gpios(struct msm_tsif_device *tsif_device) +{ + struct msm_tsif_platform_data *pdata = + tsif_device->pdev->dev.platform_data; + tsif_gpios_disable_free(pdata->gpios, pdata->num_gpios); +} + +/* ===gpio end=== */ + +static int tsif_start_hw(struct msm_tsif_device *tsif_device) +{ + u32 ctl = TSIF_STS_CTL_EN_IRQ | + TSIF_STS_CTL_EN_TIME_LIM | + TSIF_STS_CTL_EN_TCR | + TSIF_STS_CTL_EN_DM; + dev_info(&tsif_device->pdev->dev, "%s\n", __func__); + switch (tsif_device->mode) { + case 1: /* mode 1 */ + ctl |= (0 << 5); + break; + case 2: /* mode 2 */ + ctl |= (1 << 5); + break; + case 3: /* manual - control from debugfs */ + return 0; + break; + default: + return -EINVAL; + } + iowrite32(ctl, tsif_device->base + TSIF_STS_CTL_OFF); + iowrite32(tsif_device->time_limit, + tsif_device->base + TSIF_TIME_LIMIT_OFF); + wmb(); + iowrite32(ctl | TSIF_STS_CTL_START, + tsif_device->base + TSIF_STS_CTL_OFF); + wmb(); + ctl = ioread32(tsif_device->base + TSIF_STS_CTL_OFF); + return (ctl & TSIF_STS_CTL_START) ? 0 : -EFAULT; +} + +static void tsif_stop_hw(struct msm_tsif_device *tsif_device) +{ + iowrite32(TSIF_STS_CTL_STOP, tsif_device->base + TSIF_STS_CTL_OFF); + wmb(); +} + +/* ===DMA begin=== */ +/** + * TSIF DMA theory of operation + * + * Circular memory buffer \a tsif_mem_buffer allocated; + * 4 pointers points to and moved forward on: + * - \a ri index of first ready to read packet. + * Updated by client's call to tsif_reclaim_packets() + * - \a wi points to the next packet to be written by DM. + * Data below is valid and will not be overriden by DMA. + * Moved on DM callback + * - \a dmwi points to the next packet not scheduled yet for DM + * moved when packet scheduled for DM + * + * In addition, DM xfer keep internal \a wi - copy of \a tsif_device->dmwi + * at time immediately after scheduling. + * + * Initially, 2 packets get scheduled for the DM. + * + * Upon packet receive, DM writes packet to the pre-programmed + * location and invoke its callback. + * + * DM callback moves sets wi pointer to \a xfer->wi; + * then it schedules next packet for DM and moves \a dmwi pointer. + * + * Buffer overflow handling + * + * If \a dmwi == \a ri-1, buffer is full and \a dmwi can't be advanced. + * DMA re-scheduled to the same index. + * Callback check and not move \a wi to become equal to \a ri + * + * On \a read request, data between \a ri and \a wi pointers may be read; + * \ri pointer moved accordingly. + * + * It is always granted, on modulo sizeof(tsif_mem_buffer), that + * \a wi is between [\a ri, \a dmwi] + * + * Amount of data available is (wi-ri)*TSIF_PKT_SIZE + * + * Number of scheduled packets for DM: (dmwi-wi) + */ + +/** + * tsif_dma_schedule - schedule DMA transfers + * + * @tsif_device: device + * + * Executed from process context on init, or from tasklet when + * re-scheduling upon DMA completion. + * This prevent concurrent execution from several CPU's + */ +static void tsif_dma_schedule(struct msm_tsif_device *tsif_device) +{ + int i, dmwi0, dmwi1, found = 0; + /* find free entry */ + for (i = 0; i < 2; i++) { + struct tsif_xfer *xfer = &tsif_device->xfer[i]; + if (xfer->busy) + continue; + found++; + xfer->busy = 1; + dmwi0 = tsif_device->dmwi; + tsif_device->dmov_cmd[i]->box.dst_row_addr = + tsif_device->data_buffer_dma + TSIF_PKT_SIZE * dmwi0; + /* proposed value for dmwi */ + dmwi1 = (dmwi0 + TSIF_PKTS_IN_CHUNK) % TSIF_PKTS_IN_BUF; + /** + * If dmwi going to overlap with ri, + * overflow occurs because data was not read. + * Still get this packet, to not interrupt TSIF + * hardware, but do not advance dmwi. + * + * Upon receive, packet will be dropped. + */ + if (dmwi1 != tsif_device->ri) { + tsif_device->dmwi = dmwi1; + } else { + dev_info(&tsif_device->pdev->dev, + "Overflow detected\n"); + } + xfer->wi = tsif_device->dmwi; +#ifdef CONFIG_TSIF_DEBUG + dev_info(&tsif_device->pdev->dev, + "schedule xfer[%d] -> [%2d]{%2d}\n", + i, dmwi0, xfer->wi); +#endif + /* complete all the writes to box */ + dma_coherent_pre_ops(); + msm_dmov_enqueue_cmd(tsif_device->dma, &xfer->hdr); + } + if (!found) + dev_info(&tsif_device->pdev->dev, + "All xfer entries are busy\n"); +} + +/** + * tsif_dmov_complete_func - DataMover completion callback + * + * @cmd: original DM command + * @result: DM result + * @err: optional error buffer + * + * Executed in IRQ context (Data Mover's IRQ) + * DataMover's spinlock @msm_dmov_lock held. + */ +static void tsif_dmov_complete_func(struct msm_dmov_cmd *cmd, + unsigned int result, + struct msm_dmov_errdata *err) +{ + int i; + u32 data_offset; + struct tsif_xfer *xfer; + struct msm_tsif_device *tsif_device; + int reschedule = 0; + if (!(result & DMOV_RSLT_VALID)) { /* can I trust to @cmd? */ + pr_err("Invalid DMOV result: rc=0x%08x, cmd = %p", result, cmd); + return; + } + /* restore original context */ + xfer = container_of(cmd, struct tsif_xfer, hdr); + tsif_device = xfer->tsif_device; + i = xfer - tsif_device->xfer; + data_offset = tsif_device->dmov_cmd[i]->box.dst_row_addr - + tsif_device->data_buffer_dma; + + /* order reads from the xferred buffer */ + dma_coherent_post_ops(); + if (result & DMOV_RSLT_DONE) { + int w = data_offset / TSIF_PKT_SIZE; + tsif_device->stat_rx++; + /* + * sowtware overflow when I was scheduled? + * + * @w is where this xfer was actually written to; + * @xfer->wi is where device's @wi will be set; + * + * if these 2 are equal, we are short in space and + * going to overwrite this xfer - this is "soft drop" + */ + if (w == xfer->wi) + tsif_device->stat_soft_drop++; + reschedule = (tsif_device->state == tsif_state_running); +#ifdef CONFIG_TSIF_DEBUG + /* IFI calculation */ + /* + * update stat_ifi (inter frame interval) + * + * Calculate time difference between last and 1-st + * packets in chunk + * + * To be removed after tuning + */ + if (TSIF_PKTS_IN_CHUNK > 1) { + void *ptr = tsif_device->data_buffer + data_offset; + u32 *p0 = ptr; + u32 *p1 = ptr + (TSIF_PKTS_IN_CHUNK - 1) * + TSIF_PKT_SIZE; + u32 tts0 = TSIF_STATUS_TTS(tsif_device->stat0 = + tsif_pkt_status(p0)); + u32 tts1 = TSIF_STATUS_TTS(tsif_device->stat1 = + tsif_pkt_status(p1)); + tsif_device->stat_ifi = (tts1 - tts0) / + (TSIF_PKTS_IN_CHUNK - 1); + } +#endif + } else { + /** + * Error or flush + * + * To recover - re-open TSIF device. + */ + /* mark status "not valid" in data buffer */ + int n; + void *ptr = tsif_device->data_buffer + data_offset; + for (n = 0; n < TSIF_PKTS_IN_CHUNK; n++) { + u32 *p = ptr + (n * TSIF_PKT_SIZE); + /* last dword is status + TTS */ + p[TSIF_PKT_SIZE / sizeof(*p) - 1] = 0; + } + if (result & DMOV_RSLT_ERROR) { + dev_err(&tsif_device->pdev->dev, + "DMA error (0x%08x)\n", result); + tsif_device->stat_dmov_err++; + /* force device close */ + if (tsif_device->state == tsif_state_running) { + tsif_stop_hw(tsif_device); + /* + * Clocks _may_ be stopped right from IRQ + * context. This is far from optimal w.r.t + * latency. + * + * But, this branch taken only in case of + * severe hardware problem (I don't even know + * what should happens for DMOV_RSLT_ERROR); + * thus I prefer code simplicity over + * performance. + */ + tsif_clock(tsif_device, 0); + tsif_device->state = tsif_state_flushing; + } + } + if (result & DMOV_RSLT_FLUSH) { + /* + * Flushing normally happens in process of + * @tsif_stop(), when we are waiting for outstanding + * DMA commands to be flushed. + */ + dev_info(&tsif_device->pdev->dev, + "DMA channel flushed (0x%08x)\n", result); + if (tsif_device->state == tsif_state_flushing) { + if ((!tsif_device->xfer[0].busy) && + (!tsif_device->xfer[1].busy)) { + tsif_device->state = tsif_state_stopped; + } + } + } + if (err) + dev_err(&tsif_device->pdev->dev, + "Flush data: %08x %08x %08x %08x %08x %08x\n", + err->flush[0], err->flush[1], err->flush[2], + err->flush[3], err->flush[4], err->flush[5]); + } + tsif_device->wi = xfer->wi; + xfer->busy = 0; + if (tsif_device->client_notify) + tsif_device->client_notify(tsif_device->client_data); + /* + * Can't schedule next DMA - + * DataMover driver still hold its semaphore, + * deadlock will occur. + */ + if (reschedule) + tasklet_schedule(&tsif_device->dma_refill); +} + +/** + * tsif_dma_refill - tasklet function for tsif_device->dma_refill + * + * @data: tsif_device + * + * Reschedule DMA requests + * + * Executed in tasklet + */ +static void tsif_dma_refill(unsigned long data) +{ + struct msm_tsif_device *tsif_device = (struct msm_tsif_device *) data; + if (tsif_device->state == tsif_state_running) + tsif_dma_schedule(tsif_device); +} + +/** + * tsif_dma_flush - flush DMA channel + * + * @tsif_device: + * + * busy wait till DMA flushed + */ +static void tsif_dma_flush(struct msm_tsif_device *tsif_device) +{ + if (tsif_device->xfer[0].busy || tsif_device->xfer[1].busy) { + tsif_device->state = tsif_state_flushing; + while (tsif_device->xfer[0].busy || + tsif_device->xfer[1].busy) { + msm_dmov_flush(tsif_device->dma); + msleep(10); + } + } + tsif_device->state = tsif_state_stopped; + if (tsif_device->client_notify) + tsif_device->client_notify(tsif_device->client_data); +} + +static void tsif_dma_exit(struct msm_tsif_device *tsif_device) +{ + int i; + tsif_device->state = tsif_state_flushing; + tasklet_kill(&tsif_device->dma_refill); + tsif_dma_flush(tsif_device); + for (i = 0; i < 2; i++) { + if (tsif_device->dmov_cmd[i]) { + dma_free_coherent(NULL, sizeof(struct tsif_dmov_cmd), + tsif_device->dmov_cmd[i], + tsif_device->dmov_cmd_dma[i]); + tsif_device->dmov_cmd[i] = NULL; + } + } + if (tsif_device->data_buffer) { + tsif_device->blob_wrapper_databuf.data = NULL; + tsif_device->blob_wrapper_databuf.size = 0; + dma_free_coherent(NULL, TSIF_BUF_SIZE, + tsif_device->data_buffer, + tsif_device->data_buffer_dma); + tsif_device->data_buffer = NULL; + } +} + +static int tsif_dma_init(struct msm_tsif_device *tsif_device) +{ + int i; + /* TODO: allocate all DMA memory in one buffer */ + /* Note: don't pass device, + it require coherent_dma_mask id device definition */ + tsif_device->data_buffer = dma_alloc_coherent(NULL, TSIF_BUF_SIZE, + &tsif_device->data_buffer_dma, GFP_KERNEL); + if (!tsif_device->data_buffer) + goto err; + dev_info(&tsif_device->pdev->dev, "data_buffer: %p phys 0x%08x\n", + tsif_device->data_buffer, tsif_device->data_buffer_dma); + tsif_device->blob_wrapper_databuf.data = tsif_device->data_buffer; + tsif_device->blob_wrapper_databuf.size = TSIF_BUF_SIZE; + tsif_device->ri = 0; + tsif_device->wi = 0; + tsif_device->dmwi = 0; + for (i = 0; i < 2; i++) { + dmov_box *box; + struct msm_dmov_cmd *hdr; + tsif_device->dmov_cmd[i] = dma_alloc_coherent(NULL, + sizeof(struct tsif_dmov_cmd), + &tsif_device->dmov_cmd_dma[i], GFP_KERNEL); + if (!tsif_device->dmov_cmd[i]) + goto err; + dev_info(&tsif_device->pdev->dev, "dma[%i]: %p phys 0x%08x\n", + i, tsif_device->dmov_cmd[i], + tsif_device->dmov_cmd_dma[i]); + /* dst in 16 LSB, src in 16 MSB */ + box = &(tsif_device->dmov_cmd[i]->box); + box->cmd = CMD_MODE_BOX | CMD_LC | + CMD_SRC_CRCI(tsif_device->crci); + box->src_row_addr = + tsif_device->memres->start + TSIF_DATA_PORT_OFF; + box->src_dst_len = (TSIF_PKT_SIZE << 16) | TSIF_PKT_SIZE; + box->num_rows = (TSIF_PKTS_IN_CHUNK << 16) | TSIF_PKTS_IN_CHUNK; + box->row_offset = (0 << 16) | TSIF_PKT_SIZE; + + tsif_device->dmov_cmd[i]->box_ptr = CMD_PTR_LP | + DMOV_CMD_ADDR(tsif_device->dmov_cmd_dma[i] + + offsetof(struct tsif_dmov_cmd, box)); + tsif_device->xfer[i].tsif_device = tsif_device; + hdr = &tsif_device->xfer[i].hdr; + hdr->cmdptr = DMOV_CMD_ADDR(tsif_device->dmov_cmd_dma[i] + + offsetof(struct tsif_dmov_cmd, box_ptr)); + hdr->complete_func = tsif_dmov_complete_func; + } + msm_dmov_flush(tsif_device->dma); + return 0; +err: + dev_err(&tsif_device->pdev->dev, "Failed to allocate DMA buffers\n"); + tsif_dma_exit(tsif_device); + return -ENOMEM; +} + +/* ===DMA end=== */ + +/* ===IRQ begin=== */ + +static irqreturn_t tsif_irq(int irq, void *dev_id) +{ + struct msm_tsif_device *tsif_device = dev_id; + u32 sts_ctl = ioread32(tsif_device->base + TSIF_STS_CTL_OFF); + if (!(sts_ctl & (TSIF_STS_CTL_PACK_AVAIL | + TSIF_STS_CTL_OVERFLOW | + TSIF_STS_CTL_LOST_SYNC | + TSIF_STS_CTL_TIMEOUT))) { + dev_warn(&tsif_device->pdev->dev, "Spurious interrupt\n"); + return IRQ_NONE; + } + if (sts_ctl & TSIF_STS_CTL_PACK_AVAIL) { + dev_info(&tsif_device->pdev->dev, "TSIF IRQ: PACK_AVAIL\n"); + tsif_device->stat_rx++; + } + if (sts_ctl & TSIF_STS_CTL_OVERFLOW) { + dev_info(&tsif_device->pdev->dev, "TSIF IRQ: OVERFLOW\n"); + tsif_device->stat_overflow++; + } + if (sts_ctl & TSIF_STS_CTL_LOST_SYNC) { + dev_info(&tsif_device->pdev->dev, "TSIF IRQ: LOST SYNC\n"); + tsif_device->stat_lost_sync++; + } + if (sts_ctl & TSIF_STS_CTL_TIMEOUT) { + dev_info(&tsif_device->pdev->dev, "TSIF IRQ: TIMEOUT\n"); + tsif_device->stat_timeout++; + } + iowrite32(sts_ctl, tsif_device->base + TSIF_STS_CTL_OFF); + wmb(); + return IRQ_HANDLED; +} + +/* ===IRQ end=== */ + +/* ===Device attributes begin=== */ + +static ssize_t show_stats(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct msm_tsif_device *tsif_device = dev_get_drvdata(dev); + char *state_string; + switch (tsif_device->state) { + case tsif_state_stopped: + state_string = "stopped"; + break; + case tsif_state_running: + state_string = "running"; + break; + case tsif_state_flushing: + state_string = "flushing"; + break; + default: + state_string = "???"; + } + return snprintf(buf, PAGE_SIZE, + "Device %s\n" + "Mode = %d\n" + "Time limit = %d\n" + "State %s\n" + "Client = %p\n" + "Pkt/Buf = %d\n" + "Pkt/chunk = %d\n" + "--statistics--\n" + "Rx chunks = %d\n" + "Overflow = %d\n" + "Lost sync = %d\n" + "Timeout = %d\n" + "DMA error = %d\n" + "Soft drop = %d\n" + "IFI = %d\n" + "(0x%08x - 0x%08x) / %d\n" + "--debug--\n" + "GLBL_CLK_ENA = 0x%08x\n" + "ROW_RESET = 0x%08x\n" + "CLK_HALT_STATEB = 0x%08x\n" + "TV_NS_REG = 0x%08x\n" + "TSIF_NS_REG = 0x%08x\n", + dev_name(dev), + tsif_device->mode, + tsif_device->time_limit, + state_string, + tsif_device->client_data, + TSIF_PKTS_IN_BUF, + TSIF_PKTS_IN_CHUNK, + tsif_device->stat_rx, + tsif_device->stat_overflow, + tsif_device->stat_lost_sync, + tsif_device->stat_timeout, + tsif_device->stat_dmov_err, + tsif_device->stat_soft_drop, + tsif_device->stat_ifi, + tsif_device->stat1, + tsif_device->stat0, + TSIF_PKTS_IN_CHUNK - 1, + ioread32(GLBL_CLK_ENA), + ioread32(ROW_RESET), + ioread32(CLK_HALT_STATEB), + ioread32(TV_NS_REG), + ioread32(TSIF_NS_REG) + ); +} +/** + * set_stats - reset statistics on write + * + * @dev: + * @attr: + * @buf: + * @count: + */ +static ssize_t set_stats(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_tsif_device *tsif_device = dev_get_drvdata(dev); + tsif_device->stat_rx = 0; + tsif_device->stat_overflow = 0; + tsif_device->stat_lost_sync = 0; + tsif_device->stat_timeout = 0; + tsif_device->stat_dmov_err = 0; + tsif_device->stat_soft_drop = 0; + tsif_device->stat_ifi = 0; + return count; +} +static DEVICE_ATTR(stats, S_IRUGO | S_IWUSR, show_stats, set_stats); + +static ssize_t show_mode(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct msm_tsif_device *tsif_device = dev_get_drvdata(dev); + return snprintf(buf, PAGE_SIZE, "%d\n", tsif_device->mode); +} + +static ssize_t set_mode(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_tsif_device *tsif_device = dev_get_drvdata(dev); + int value; + int rc; + if (1 != sscanf(buf, "%d", &value)) { + dev_err(&tsif_device->pdev->dev, + "Failed to parse integer: <%s>\n", buf); + return -EINVAL; + } + rc = tsif_set_mode(tsif_device, value); + if (!rc) + rc = count; + return rc; +} +static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR, show_mode, set_mode); + +static ssize_t show_time_limit(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct msm_tsif_device *tsif_device = dev_get_drvdata(dev); + return snprintf(buf, PAGE_SIZE, "%d\n", tsif_device->time_limit); +} + +static ssize_t set_time_limit(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_tsif_device *tsif_device = dev_get_drvdata(dev); + int value; + int rc; + if (1 != sscanf(buf, "%d", &value)) { + dev_err(&tsif_device->pdev->dev, + "Failed to parse integer: <%s>\n", buf); + return -EINVAL; + } + rc = tsif_set_time_limit(tsif_device, value); + if (!rc) + rc = count; + return rc; +} +static DEVICE_ATTR(time_limit, S_IRUGO | S_IWUSR, + show_time_limit, set_time_limit); + +static ssize_t show_buf_config(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct msm_tsif_device *tsif_device = dev_get_drvdata(dev); + return snprintf(buf, PAGE_SIZE, "%d * %d\n", + tsif_device->pkts_per_chunk, + tsif_device->chunks_per_buf); +} + +static ssize_t set_buf_config(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_tsif_device *tsif_device = dev_get_drvdata(dev); + u32 p, c; + int rc; + if (2 != sscanf(buf, "%d * %d", &p, &c)) { + dev_err(&tsif_device->pdev->dev, + "Failed to parse integer: <%s>\n", buf); + return -EINVAL; + } + rc = tsif_set_buf_config(tsif_device, p, c); + if (!rc) + rc = count; + return rc; +} +static DEVICE_ATTR(buf_config, S_IRUGO | S_IWUSR, + show_buf_config, set_buf_config); + +static struct attribute *dev_attrs[] = { + &dev_attr_stats.attr, + &dev_attr_mode.attr, + &dev_attr_time_limit.attr, + &dev_attr_buf_config.attr, + NULL, +}; +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, +}; +/* ===Device attributes end=== */ + +/* ===debugfs begin=== */ + +static int debugfs_iomem_x32_set(void *data, u64 val) +{ + iowrite32(val, data); + wmb(); + return 0; +} + +static int debugfs_iomem_x32_get(void *data, u64 *val) +{ + *val = ioread32(data); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_x32_get, + debugfs_iomem_x32_set, "0x%08llx\n"); + +struct dentry *debugfs_create_iomem_x32(const char *name, mode_t mode, + struct dentry *parent, u32 *value) +{ + return debugfs_create_file(name, mode, parent, value, &fops_iomem_x32); +} + +static int action_open(struct msm_tsif_device *tsif_device) +{ + int rc = -EINVAL; + int result; + + struct msm_tsif_platform_data *pdata = + tsif_device->pdev->dev.platform_data; + dev_info(&tsif_device->pdev->dev, "%s\n", __func__); + if (tsif_device->state != tsif_state_stopped) + return -EAGAIN; + rc = tsif_dma_init(tsif_device); + if (rc) { + dev_err(&tsif_device->pdev->dev, "failed to init DMA\n"); + return rc; + } + tsif_device->state = tsif_state_running; + /* + * DMA should be scheduled prior to TSIF hardware initialization, + * otherwise "bus error" will be reported by Data Mover + */ + enable_irq(tsif_device->irq); + tsif_clock(tsif_device, 1); + tsif_dma_schedule(tsif_device); + /* + * init the device if required + */ + if (pdata->init) + pdata->init(pdata); + rc = tsif_start_hw(tsif_device); + if (rc) { + dev_err(&tsif_device->pdev->dev, "Unable to start HW\n"); + tsif_dma_exit(tsif_device); + tsif_clock(tsif_device, 0); + return rc; + } + + result = pm_runtime_get(&tsif_device->pdev->dev); + if (result < 0) { + dev_err(&tsif_device->pdev->dev, + "Runtime PM: Unable to wake up the device, rc = %d\n", + result); + return result; + } + + wake_lock(&tsif_device->wake_lock); + return rc; +} + +static int action_close(struct msm_tsif_device *tsif_device) +{ + dev_info(&tsif_device->pdev->dev, "%s, state %d\n", __func__, + (int)tsif_device->state); + /* + * DMA should be flushed/stopped prior to TSIF hardware stop, + * otherwise "bus error" will be reported by Data Mover + */ + tsif_stop_hw(tsif_device); + tsif_dma_exit(tsif_device); + tsif_clock(tsif_device, 0); + disable_irq(tsif_device->irq); + + pm_runtime_put(&tsif_device->pdev->dev); + wake_unlock(&tsif_device->wake_lock); + return 0; +} + + +static struct { + int (*func)(struct msm_tsif_device *); + const char *name; +} actions[] = { + { action_open, "open"}, + { action_close, "close"}, +}; + +static ssize_t tsif_debugfs_action_write(struct file *filp, + const char __user *userbuf, + size_t count, loff_t *f_pos) +{ + int i; + struct msm_tsif_device *tsif_device = filp->private_data; + char s[40]; + int len = min(sizeof(s) - 1, count); + if (copy_from_user(s, userbuf, len)) + return -EFAULT; + s[len] = '\0'; + dev_info(&tsif_device->pdev->dev, "%s:%s\n", __func__, s); + for (i = 0; i < ARRAY_SIZE(actions); i++) { + if (!strncmp(s, actions[i].name, + min(count, strlen(actions[i].name)))) { + int rc = actions[i].func(tsif_device); + if (!rc) + rc = count; + return rc; + } + } + return -EINVAL; +} + +static int tsif_debugfs_generic_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static const struct file_operations fops_debugfs_action = { + .open = tsif_debugfs_generic_open, + .write = tsif_debugfs_action_write, +}; + +static ssize_t tsif_debugfs_dma_read(struct file *filp, char __user *userbuf, + size_t count, loff_t *f_pos) +{ + static char bufa[200]; + static char *buf = bufa; + int sz = sizeof(bufa); + struct msm_tsif_device *tsif_device = filp->private_data; + int len = 0; + if (tsif_device) { + int i; + len += snprintf(buf + len, sz - len, + "ri %3d | wi %3d | dmwi %3d |", + tsif_device->ri, tsif_device->wi, + tsif_device->dmwi); + for (i = 0; i < 2; i++) { + struct tsif_xfer *xfer = &tsif_device->xfer[i]; + if (xfer->busy) { + u32 dst = + tsif_device->dmov_cmd[i]->box.dst_row_addr; + u32 base = tsif_device->data_buffer_dma; + int w = (dst - base) / TSIF_PKT_SIZE; + len += snprintf(buf + len, sz - len, + " [%3d]{%3d}", + w, xfer->wi); + } else { + len += snprintf(buf + len, sz - len, + " ---idle---"); + } + } + len += snprintf(buf + len, sz - len, "\n"); + } else { + len += snprintf(buf + len, sz - len, "No TSIF device???\n"); + } + return simple_read_from_buffer(userbuf, count, f_pos, buf, len); +} + +static const struct file_operations fops_debugfs_dma = { + .open = tsif_debugfs_generic_open, + .read = tsif_debugfs_dma_read, +}; + +static ssize_t tsif_debugfs_gpios_read(struct file *filp, char __user *userbuf, + size_t count, loff_t *f_pos) +{ + static char bufa[300]; + static char *buf = bufa; + int sz = sizeof(bufa); + struct msm_tsif_device *tsif_device = filp->private_data; + int len = 0; + if (tsif_device) { + struct msm_tsif_platform_data *pdata = + tsif_device->pdev->dev.platform_data; + int i; + for (i = 0; i < pdata->num_gpios; i++) { + if (pdata->gpios[i].gpio_cfg) { + int x = !!gpio_get_value(GPIO_PIN( + pdata->gpios[i].gpio_cfg)); + len += snprintf(buf + len, sz - len, + "%15s: %d\n", + pdata->gpios[i].label, x); + } + } + } else { + len += snprintf(buf + len, sz - len, "No TSIF device???\n"); + } + return simple_read_from_buffer(userbuf, count, f_pos, buf, len); +} + +static const struct file_operations fops_debugfs_gpios = { + .open = tsif_debugfs_generic_open, + .read = tsif_debugfs_gpios_read, +}; + + +static void tsif_debugfs_init(struct msm_tsif_device *tsif_device) +{ + tsif_device->dent_tsif = debugfs_create_dir( + dev_name(&tsif_device->pdev->dev), NULL); + if (tsif_device->dent_tsif) { + int i; + void __iomem *base = tsif_device->base; + for (i = 0; i < ARRAY_SIZE(debugfs_tsif_regs); i++) { + tsif_device->debugfs_tsif_regs[i] = + debugfs_create_iomem_x32( + debugfs_tsif_regs[i].name, + debugfs_tsif_regs[i].mode, + tsif_device->dent_tsif, + base + debugfs_tsif_regs[i].offset); + } + tsif_device->debugfs_gpio = debugfs_create_file("gpios", + S_IRUGO, + tsif_device->dent_tsif, tsif_device, &fops_debugfs_gpios); + tsif_device->debugfs_action = debugfs_create_file("action", + S_IWUSR, + tsif_device->dent_tsif, tsif_device, &fops_debugfs_action); + tsif_device->debugfs_dma = debugfs_create_file("dma", + S_IRUGO, + tsif_device->dent_tsif, tsif_device, &fops_debugfs_dma); + tsif_device->debugfs_databuf = debugfs_create_blob("data_buf", + S_IRUGO, + tsif_device->dent_tsif, &tsif_device->blob_wrapper_databuf); + } +} + +static void tsif_debugfs_exit(struct msm_tsif_device *tsif_device) +{ + if (tsif_device->dent_tsif) { + int i; + debugfs_remove_recursive(tsif_device->dent_tsif); + tsif_device->dent_tsif = NULL; + for (i = 0; i < ARRAY_SIZE(debugfs_tsif_regs); i++) + tsif_device->debugfs_tsif_regs[i] = NULL; + tsif_device->debugfs_gpio = NULL; + tsif_device->debugfs_action = NULL; + tsif_device->debugfs_dma = NULL; + tsif_device->debugfs_databuf = NULL; + } +} +/* ===debugfs end=== */ + +/* ===module begin=== */ +static LIST_HEAD(tsif_devices); + +static struct msm_tsif_device *tsif_find_by_id(int id) +{ + struct msm_tsif_device *tsif_device; + list_for_each_entry(tsif_device, &tsif_devices, devlist) { + if (tsif_device->pdev->id == id) + return tsif_device; + } + return NULL; +} + +static int __devinit msm_tsif_probe(struct platform_device *pdev) +{ + int rc = -ENODEV; + struct msm_tsif_platform_data *plat = pdev->dev.platform_data; + struct msm_tsif_device *tsif_device; + struct resource *res; + /* check device validity */ + /* must have platform data */ + if (!plat) { + dev_err(&pdev->dev, "Platform data not available\n"); + rc = -EINVAL; + goto out; + } +/*TODO macro for max. id*/ + if ((pdev->id < 0) || (pdev->id > 0)) { + dev_err(&pdev->dev, "Invalid device ID %d\n", pdev->id); + rc = -EINVAL; + goto out; + } + /* OK, we will use this device */ + tsif_device = kzalloc(sizeof(struct msm_tsif_device), GFP_KERNEL); + if (!tsif_device) { + dev_err(&pdev->dev, "Failed to allocate memory for device\n"); + rc = -ENOMEM; + goto out; + } + /* cross links */ + tsif_device->pdev = pdev; + platform_set_drvdata(pdev, tsif_device); + tsif_device->mode = 1; + tsif_device->pkts_per_chunk = TSIF_PKTS_IN_CHUNK_DEFAULT; + tsif_device->chunks_per_buf = TSIF_CHUNKS_IN_BUF_DEFAULT; + tasklet_init(&tsif_device->dma_refill, tsif_dma_refill, + (unsigned long)tsif_device); + if (tsif_get_clocks(tsif_device)) + goto err_clocks; +/* map I/O memory */ + tsif_device->memres = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!tsif_device->memres) { + dev_err(&pdev->dev, "Missing MEM resource\n"); + rc = -ENXIO; + goto err_rgn; + } + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, "Missing DMA resource\n"); + rc = -ENXIO; + goto err_rgn; + } + tsif_device->dma = res->start; + tsif_device->crci = res->end; + tsif_device->base = ioremap(tsif_device->memres->start, + resource_size(tsif_device->memres)); + if (!tsif_device->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + goto err_ioremap; + } + dev_info(&pdev->dev, "remapped phys 0x%08x => virt %p\n", + tsif_device->memres->start, tsif_device->base); + rc = tsif_start_gpios(tsif_device); + if (rc) + goto err_gpio; + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + tsif_debugfs_init(tsif_device); + rc = platform_get_irq(pdev, 0); + if (rc > 0) { + tsif_device->irq = rc; + rc = request_irq(tsif_device->irq, tsif_irq, IRQF_SHARED, + dev_name(&pdev->dev), tsif_device); + disable_irq(tsif_device->irq); + } + if (rc) { + dev_err(&pdev->dev, "failed to request IRQ %d : %d\n", + tsif_device->irq, rc); + goto err_irq; + } + rc = sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp); + if (rc) { + dev_err(&pdev->dev, "failed to create dev. attrs : %d\n", rc); + goto err_attrs; + } + wake_lock_init(&tsif_device->wake_lock, WAKE_LOCK_SUSPEND, + dev_name(&pdev->dev)); + dev_info(&pdev->dev, "Configured irq %d memory 0x%08x DMA %d CRCI %d\n", + tsif_device->irq, tsif_device->memres->start, + tsif_device->dma, tsif_device->crci); + list_add(&tsif_device->devlist, &tsif_devices); + return 0; +/* error path */ + sysfs_remove_group(&pdev->dev.kobj, &dev_attr_grp); +err_attrs: + free_irq(tsif_device->irq, tsif_device); +err_irq: + tsif_debugfs_exit(tsif_device); + tsif_stop_gpios(tsif_device); +err_gpio: + iounmap(tsif_device->base); +err_ioremap: +err_rgn: + tsif_put_clocks(tsif_device); +err_clocks: + kfree(tsif_device); +out: + return rc; +} + +static int __devexit msm_tsif_remove(struct platform_device *pdev) +{ + struct msm_tsif_device *tsif_device = platform_get_drvdata(pdev); + dev_info(&pdev->dev, "Unload\n"); + list_del(&tsif_device->devlist); + wake_lock_destroy(&tsif_device->wake_lock); + sysfs_remove_group(&pdev->dev.kobj, &dev_attr_grp); + free_irq(tsif_device->irq, tsif_device); + tsif_debugfs_exit(tsif_device); + tsif_dma_exit(tsif_device); + tsif_stop_gpios(tsif_device); + iounmap(tsif_device->base); + tsif_put_clocks(tsif_device); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + kfree(tsif_device); + return 0; +} + +static int tsif_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int tsif_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops tsif_dev_pm_ops = { + .runtime_suspend = tsif_runtime_suspend, + .runtime_resume = tsif_runtime_resume, +}; + + +static struct platform_driver msm_tsif_driver = { + .probe = msm_tsif_probe, + .remove = __exit_p(msm_tsif_remove), + .driver = { + .name = "msm_tsif", + .pm = &tsif_dev_pm_ops, + }, +}; + +static int __init mod_init(void) +{ + int rc = platform_driver_register(&msm_tsif_driver); + if (rc) + pr_err("TSIF: platform_driver_register failed: %d\n", rc); + return rc; +} + +static void __exit mod_exit(void) +{ + platform_driver_unregister(&msm_tsif_driver); +} +/* ===module end=== */ + +/* public API */ + +void *tsif_attach(int id, void (*notify)(void *client_data), void *data) +{ + struct msm_tsif_device *tsif_device = tsif_find_by_id(id); + if (tsif_device->client_notify || tsif_device->client_data) + return ERR_PTR(-EBUSY); + tsif_device->client_notify = notify; + tsif_device->client_data = data; + /* prevent from unloading */ + get_device(&tsif_device->pdev->dev); + return tsif_device; +} +EXPORT_SYMBOL(tsif_attach); + +void tsif_detach(void *cookie) +{ + struct msm_tsif_device *tsif_device = cookie; + tsif_device->client_notify = NULL; + tsif_device->client_data = NULL; + put_device(&tsif_device->pdev->dev); +} +EXPORT_SYMBOL(tsif_detach); + +void tsif_get_info(void *cookie, void **pdata, int *psize) +{ + struct msm_tsif_device *tsif_device = cookie; + if (pdata) + *pdata = tsif_device->data_buffer; + if (psize) + *psize = TSIF_PKTS_IN_BUF; +} +EXPORT_SYMBOL(tsif_get_info); + +int tsif_set_mode(void *cookie, int mode) +{ + struct msm_tsif_device *tsif_device = cookie; + if (tsif_device->state != tsif_state_stopped) { + dev_err(&tsif_device->pdev->dev, + "Can't change mode while device is active\n"); + return -EBUSY; + } + switch (mode) { + case 1: + case 2: + case 3: + tsif_device->mode = mode; + break; + default: + dev_err(&tsif_device->pdev->dev, "Invalid mode: %d\n", mode); + return -EINVAL; + } + return 0; +} +EXPORT_SYMBOL(tsif_set_mode); + +int tsif_set_time_limit(void *cookie, u32 value) +{ + struct msm_tsif_device *tsif_device = cookie; + if (tsif_device->state != tsif_state_stopped) { + dev_err(&tsif_device->pdev->dev, + "Can't change time limit while device is active\n"); + return -EBUSY; + } + if (value != (value & 0xFFFFFF)) { + dev_err(&tsif_device->pdev->dev, + "Invalid time limit (should be 24 bit): %#x\n", value); + return -EINVAL; + } + tsif_device->time_limit = value; + return 0; +} +EXPORT_SYMBOL(tsif_set_time_limit); + +int tsif_set_buf_config(void *cookie, u32 pkts_in_chunk, u32 chunks_in_buf) +{ + struct msm_tsif_device *tsif_device = cookie; + if (tsif_device->data_buffer) { + dev_err(&tsif_device->pdev->dev, + "Data buffer already allocated: %p\n", + tsif_device->data_buffer); + return -EBUSY; + } + /* check for crazy user */ + if (pkts_in_chunk * chunks_in_buf > 10240) { + dev_err(&tsif_device->pdev->dev, + "Buffer requested is too large: %d * %d\n", + pkts_in_chunk, + chunks_in_buf); + return -EINVAL; + } + /* parameters are OK, execute */ + tsif_device->pkts_per_chunk = pkts_in_chunk; + tsif_device->chunks_per_buf = chunks_in_buf; + return 0; +} +EXPORT_SYMBOL(tsif_set_buf_config); + +void tsif_get_state(void *cookie, int *ri, int *wi, enum tsif_state *state) +{ + struct msm_tsif_device *tsif_device = cookie; + if (ri) + *ri = tsif_device->ri; + if (wi) + *wi = tsif_device->wi; + if (state) + *state = tsif_device->state; +} +EXPORT_SYMBOL(tsif_get_state); + +int tsif_start(void *cookie) +{ + struct msm_tsif_device *tsif_device = cookie; + return action_open(tsif_device); +} +EXPORT_SYMBOL(tsif_start); + +void tsif_stop(void *cookie) +{ + struct msm_tsif_device *tsif_device = cookie; + action_close(tsif_device); +} +EXPORT_SYMBOL(tsif_stop); + +void tsif_reclaim_packets(void *cookie, int read_index) +{ + struct msm_tsif_device *tsif_device = cookie; + tsif_device->ri = read_index; +} +EXPORT_SYMBOL(tsif_reclaim_packets); + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_DESCRIPTION("TSIF (Transport Stream Interface)" + " Driver for the MSM chipset"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/misc/tsif_chrdev.c b/drivers/misc/tsif_chrdev.c new file mode 100644 index 00000000000..4068ac304c7 --- /dev/null +++ b/drivers/misc/tsif_chrdev.c @@ -0,0 +1,225 @@ +/** + * TSIF driver client + * + * Character device that, being read + * returns stream of TSIF packets. + * + * Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include /* Needed by all modules */ +#include /* Needed for KERN_INFO */ +#include +#include /* IS_ERR etc. */ +#include +#include +#include /* TASK_INTERRUPTIBLE */ + +#include /* copy_to_user */ + +#include + +struct tsif_chrdev { + struct cdev cdev; + struct device *dev; + wait_queue_head_t wq_read; + void *cookie; + /* mirror for tsif data */ + void *data_buffer; + unsigned buf_size_packets; /**< buffer size in packets */ + unsigned ri, wi; + enum tsif_state state; + unsigned rptr; +}; + +static ssize_t tsif_open(struct inode *inode, struct file *file) +{ + int rc; + struct tsif_chrdev *the_dev = + container_of(inode->i_cdev, struct tsif_chrdev, cdev); + if (!the_dev->cookie) /* not bound yet */ + return -ENODEV; + file->private_data = the_dev; + rc = tsif_start(the_dev->cookie); + if (rc) + return rc; + tsif_get_info(the_dev->cookie, &the_dev->data_buffer, + &the_dev->buf_size_packets); + the_dev->rptr = 0; + return nonseekable_open(inode, file); +} + +static ssize_t tsif_release(struct inode *inode, struct file *filp) +{ + struct tsif_chrdev *the_dev = filp->private_data; + tsif_stop(the_dev->cookie); + return 0; +} + +static ssize_t tsif_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + int avail = 0; + int wi; + struct tsif_chrdev *the_dev = filp->private_data; + tsif_get_state(the_dev->cookie, &the_dev->ri, &the_dev->wi, + &the_dev->state); + /* consistency check */ + if (the_dev->ri != (the_dev->rptr / TSIF_PKT_SIZE)) { + dev_err(the_dev->dev, + "%s: inconsistent read pointers: ri %d rptr %d\n", + __func__, the_dev->ri, the_dev->rptr); + the_dev->rptr = the_dev->ri * TSIF_PKT_SIZE; + } + /* ri == wi if no data */ + if (the_dev->ri == the_dev->wi) { + /* shall I block waiting for data? */ + if (filp->f_flags & O_NONBLOCK) { + if (the_dev->state == tsif_state_running) { + return -EAGAIN; + } else { + /* not running -> EOF */ + return 0; + } + } + if (wait_event_interruptible(the_dev->wq_read, + (the_dev->ri != the_dev->wi) || + (the_dev->state != tsif_state_running))) { + /* got signal -> tell FS to handle it */ + return -ERESTARTSYS; + } + if (the_dev->ri == the_dev->wi) { + /* still no data -> EOF */ + return 0; + } + } + /* contiguous chunk last up to wi or end of buffer */ + wi = (the_dev->wi > the_dev->ri) ? + the_dev->wi : the_dev->buf_size_packets; + avail = min(wi * TSIF_PKT_SIZE - the_dev->rptr, count); + if (copy_to_user(buf, the_dev->data_buffer + the_dev->rptr, avail)) + return -EFAULT; + the_dev->rptr = (the_dev->rptr + avail) % + (TSIF_PKT_SIZE * the_dev->buf_size_packets); + the_dev->ri = the_dev->rptr / TSIF_PKT_SIZE; + *f_pos += avail; + tsif_reclaim_packets(the_dev->cookie, the_dev->ri); + return avail; +} + +static void tsif_notify(void *data) +{ + struct tsif_chrdev *the_dev = data; + tsif_get_state(the_dev->cookie, &the_dev->ri, &the_dev->wi, + &the_dev->state); + wake_up_interruptible(&the_dev->wq_read); +} + +static const struct file_operations tsif_fops = { + .owner = THIS_MODULE, + .read = tsif_read, + .open = tsif_open, + .release = tsif_release, +}; + +static struct class *tsif_class; +static dev_t tsif_dev; /**< 1-st dev_t from allocated range */ +static dev_t tsif_dev0; /**< next not yet assigned dev_t */ + +static int tsif_init_one(struct tsif_chrdev *the_dev, int index) +{ + int rc; + pr_info("%s[%d]\n", __func__, index); + cdev_init(&the_dev->cdev, &tsif_fops); + the_dev->cdev.owner = THIS_MODULE; + init_waitqueue_head(&the_dev->wq_read); + rc = cdev_add(&the_dev->cdev, tsif_dev0++, 1); + the_dev->dev = device_create(tsif_class, NULL, the_dev->cdev.dev, + the_dev, "tsif%d", index); + if (IS_ERR(the_dev->dev)) { + rc = PTR_ERR(the_dev->dev); + pr_err("device_create failed: %d\n", rc); + goto err_create; + } + the_dev->cookie = tsif_attach(index, tsif_notify, the_dev); + if (IS_ERR(the_dev->cookie)) { + rc = PTR_ERR(the_dev->cookie); + pr_err("tsif_attach failed: %d\n", rc); + goto err_attach; + } + /* now data buffer is not allocated yet */ + tsif_get_info(the_dev->cookie, &the_dev->data_buffer, NULL); + dev_info(the_dev->dev, + "Device %d.%d attached to TSIF, buffer size %d\n", + MAJOR(the_dev->cdev.dev), MINOR(the_dev->cdev.dev), + the_dev->buf_size_packets); + return 0; +err_attach: + device_destroy(tsif_class, the_dev->cdev.dev); +err_create: + cdev_del(&the_dev->cdev); + return rc; +} + +static void tsif_exit_one(struct tsif_chrdev *the_dev) +{ + dev_info(the_dev->dev, "%s\n", __func__); + tsif_detach(the_dev->cookie); + device_destroy(tsif_class, the_dev->cdev.dev); + cdev_del(&the_dev->cdev); +} + +#define TSIF_NUM_DEVS 1 /**< support this many devices */ + +struct tsif_chrdev the_devices[TSIF_NUM_DEVS]; + +static int __init mod_init(void) +{ + int rc; + rc = alloc_chrdev_region(&tsif_dev, 0, TSIF_NUM_DEVS, "tsif"); + if (rc) { + pr_err("alloc_chrdev_region failed: %d\n", rc); + goto err_devrgn; + } + tsif_dev0 = tsif_dev; + tsif_class = class_create(THIS_MODULE, "tsif"); + if (IS_ERR(tsif_class)) { + rc = PTR_ERR(tsif_class); + pr_err("Error creating tsif class: %d\n", rc); + goto err_class; + } + rc = tsif_init_one(&the_devices[0], 0); + if (rc) + goto err_init1; + return 0; +err_init1: + class_destroy(tsif_class); +err_class: + unregister_chrdev_region(tsif_dev, TSIF_NUM_DEVS); +err_devrgn: + return rc; +} + +static void __exit mod_exit(void) +{ + tsif_exit_one(&the_devices[0]); + class_destroy(tsif_class); + unregister_chrdev_region(tsif_dev, TSIF_NUM_DEVS); +} + +module_init(mod_init); +module_exit(mod_exit); + +MODULE_DESCRIPTION("TSIF character device interface"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/misc/tzcom.c b/drivers/misc/tzcom.c new file mode 100644 index 00000000000..e947dee1ec4 --- /dev/null +++ b/drivers/misc/tzcom.c @@ -0,0 +1,910 @@ +/* Qualcomm TrustZone communicator driver + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define KMSG_COMPONENT "TZCOM" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tzcomi.h" + +#define TZCOM_DEV "tzcom" + +#define TZSCHEDULER_CMD_ID 1 /* CMD id of the trustzone scheduler */ + +#undef PDEBUG +#define PDEBUG(fmt, args...) pr_debug("%s(%i, %s): " fmt "\n", \ + __func__, current->pid, current->comm, ## args) + +#undef PERR +#define PERR(fmt, args...) pr_err("%s(%i, %s): " fmt "\n", \ + __func__, current->pid, current->comm, ## args) + + +static struct class *driver_class; +static dev_t tzcom_device_no; +static struct cdev tzcom_cdev; + +static u8 *sb_in_virt; +static s32 sb_in_phys; +static size_t sb_in_length = 20 * SZ_1K; +static u8 *sb_out_virt; +static s32 sb_out_phys; +static size_t sb_out_length = 20 * SZ_1K; + +static void *pil; + +static atomic_t svc_instance_ctr = ATOMIC_INIT(0); +static DEFINE_MUTEX(sb_in_lock); +static DEFINE_MUTEX(sb_out_lock); +static DEFINE_MUTEX(send_cmd_lock); + +struct tzcom_callback_list { + struct list_head list; + struct tzcom_callback callback; +}; + +struct tzcom_registered_svc_list { + struct list_head list; + struct tzcom_register_svc_op_req svc; + wait_queue_head_t next_cmd_wq; + int next_cmd_flag; +}; + +struct tzcom_data_t { + struct list_head callback_list_head; + struct mutex callback_list_lock; + struct list_head registered_svc_list_head; + spinlock_t registered_svc_list_lock; + wait_queue_head_t cont_cmd_wq; + int cont_cmd_flag; + u32 handled_cmd_svc_instance_id; +}; + +static int tzcom_scm_call(const void *cmd_buf, size_t cmd_len, + void *resp_buf, size_t resp_len) +{ + return scm_call(SCM_SVC_TZSCHEDULER, TZSCHEDULER_CMD_ID, + cmd_buf, cmd_len, resp_buf, resp_len); +} + +static s32 tzcom_virt_to_phys(u8 *virt) +{ + if (virt >= sb_in_virt && + virt < (sb_in_virt + sb_in_length)) { + return sb_in_phys + (virt - sb_in_virt); + } else if (virt >= sb_out_virt && + virt < (sb_out_virt + sb_out_length)) { + return sb_out_phys + (virt - sb_out_virt); + } else { + return virt_to_phys(virt); + } +} + +static u8 *tzcom_phys_to_virt(s32 phys) +{ + if (phys >= sb_in_phys && + phys < (sb_in_phys + sb_in_length)) { + return sb_in_virt + (phys - sb_in_phys); + } else if (phys >= sb_out_phys && + phys < (sb_out_phys + sb_out_length)) { + return sb_out_virt + (phys - sb_out_phys); + } else { + return phys_to_virt(phys); + } +} + +static int __tzcom_is_svc_unique(struct tzcom_data_t *data, + struct tzcom_register_svc_op_req svc) +{ + struct tzcom_registered_svc_list *ptr; + int unique = 1; + unsigned long flags; + + spin_lock_irqsave(&data->registered_svc_list_lock, flags); + list_for_each_entry(ptr, &data->registered_svc_list_head, list) { + if (ptr->svc.svc_id == svc.svc_id) { + PERR("Service id: %u is already registered", + ptr->svc.svc_id); + unique = 0; + break; + } else if (svc.cmd_id_low >= ptr->svc.cmd_id_low && + svc.cmd_id_low <= ptr->svc.cmd_id_high) { + PERR("Cmd id low falls in the range of another" + "registered service"); + unique = 0; + break; + } else if (svc.cmd_id_high >= ptr->svc.cmd_id_low && + svc.cmd_id_high <= ptr->svc.cmd_id_high) { + PERR("Cmd id high falls in the range of another" + "registered service"); + unique = 0; + break; + } + } + spin_unlock_irqrestore(&data->registered_svc_list_lock, flags); + return unique; +} + +static int tzcom_register_service(struct tzcom_data_t *data, void __user *argp) +{ + int ret; + unsigned long flags; + struct tzcom_register_svc_op_req rcvd_svc; + struct tzcom_registered_svc_list *new_entry; + + ret = copy_from_user(&rcvd_svc, argp, sizeof(rcvd_svc)); + + if (ret) { + PDEBUG("copy_from_user failed"); + return ret; + } + + PDEBUG("svc_id: %u, cmd_id_low: %u, cmd_id_high: %u", + rcvd_svc.svc_id, rcvd_svc.cmd_id_low, + rcvd_svc.cmd_id_high); + if (!__tzcom_is_svc_unique(data, rcvd_svc)) { + PDEBUG("Provided service is not unique"); + return -EINVAL; + } + + rcvd_svc.instance_id = atomic_inc_return(&svc_instance_ctr); + + ret = copy_to_user(argp, &rcvd_svc, sizeof(rcvd_svc)); + if (ret) { + PDEBUG("copy_to_user failed"); + return ret; + } + + new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL); + if (!new_entry) { + pr_err("%s: kmalloc failed\n", __func__); + return -ENOMEM; + } + memcpy(&new_entry->svc, &rcvd_svc, sizeof(rcvd_svc)); + new_entry->next_cmd_flag = 0; + init_waitqueue_head(&new_entry->next_cmd_wq); + + spin_lock_irqsave(&data->registered_svc_list_lock, flags); + list_add_tail(&new_entry->list, &data->registered_svc_list_head); + spin_unlock_irqrestore(&data->registered_svc_list_lock, flags); + + + return ret; +} + +static int tzcom_unregister_service(struct tzcom_data_t *data, + void __user *argp) +{ + int ret = 0; + unsigned long flags; + struct tzcom_unregister_svc_op_req req; + struct tzcom_registered_svc_list *ptr; + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + PDEBUG("copy_from_user failed"); + return ret; + } + + spin_lock_irqsave(&data->registered_svc_list_lock, flags); + list_for_each_entry(ptr, &data->registered_svc_list_head, list) { + if (req.svc_id == ptr->svc.svc_id && + req.instance_id == ptr->svc.instance_id) { + wake_up_all(&ptr->next_cmd_wq); + list_del(&ptr->list); + kfree(ptr); + spin_unlock_irqrestore(&data->registered_svc_list_lock, + flags); + return 0; + } + } + spin_unlock_irqrestore(&data->registered_svc_list_lock, flags); + + return -EINVAL; +} + +/** + * +---------+ +-----+ +-----------------+ + * | TZCOM | | SCM | | TZCOM_SCHEDULER | + * +----+----+ +--+--+ +--------+--------+ + * | | | + * | scm_call | | + * |------------------------------------->| | + * | cmd_buf = struct tzcom_command { | | + * | cmd_type, |------------------>| + * +------+------------- sb_in_cmd_addr, | | + * | | sb_in_cmd_len | | + * | | } | | + * | | resp_buf = struct tzcom_response { | | + * | cmd_status, | | + * | +---------- sb_in_rsp_addr, | | + * | | sb_in_rsp_len |<------------------| + * | | } + * | | struct tzcom_callback {---------+ + * | | uint32_t cmd_id; | + * | | uint32_t sb_out_cb_data_len;| + * | +---------------+ uint32_t sb_out_cb_data_off;| + * | | } | + * | _________________________|_______________________________ | + * | +-----------------------+| +----------------------+ | + * +--->+ copy from req.cmd_buf |+>| copy to req.resp_buf | | + * +-----------------------+ +----------------------+ | + * _________________________________________________________ | + * INPUT SHARED BUFFER | + * +------------------------------------------------------------------------+ + * | _________________________________________________________ + * | +---------------------------------------------+ + * +->| cmd_id | data_len | data_off | data... | + * +---------------------------------------------+ + * |<------------>|copy to next_cmd.req_buf + * _________________________________________________________ + * OUTPUT SHARED BUFFER + */ +static int tzcom_send_cmd(struct tzcom_data_t *data, void __user *argp) +{ + int ret = 0; + unsigned long flags; + u32 reqd_len_sb_in = 0; + u32 reqd_len_sb_out = 0; + struct tzcom_send_cmd_op_req req; + struct tzcom_command cmd; + struct tzcom_response resp; + struct tzcom_callback *next_callback; + void *cb_data = NULL; + struct tzcom_callback_list *new_entry; + struct tzcom_callback *cb; + size_t new_entry_len = 0; + struct tzcom_registered_svc_list *ptr_svc; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + PDEBUG("copy_from_user failed"); + return ret; + } + + if (req.cmd_buf == NULL || req.resp_buf == NULL) { + PDEBUG("cmd buffer or response buffer is null"); + return -EINVAL; + } + + if (req.cmd_len <= 0 || req.resp_len <= 0) { + PDEBUG("cmd buffer length or " + "response buffer length not valid"); + return -EINVAL; + } + PDEBUG("received cmd_req.req: 0x%p", + req.cmd_buf); + PDEBUG("received cmd_req.rsp size: %u, ptr: 0x%p", + req.resp_len, + req.resp_buf); + + reqd_len_sb_in = req.cmd_len + req.resp_len; + if (reqd_len_sb_in > sb_in_length) { + PDEBUG("Not enough memory to fit cmd_buf and " + "resp_buf. Required: %u, Available: %u", + reqd_len_sb_in, sb_in_length); + return -ENOMEM; + } + + /* Copy req.cmd_buf to SB in and set req.resp_buf to SB in + cmd_len */ + mutex_lock(&sb_in_lock); + PDEBUG("Before memcpy on sb_in"); + memcpy(sb_in_virt, req.cmd_buf, req.cmd_len); + PDEBUG("After memcpy on sb_in"); + + /* cmd_type will always be a new here */ + cmd.cmd_type = TZ_SCHED_CMD_NEW; + cmd.sb_in_cmd_addr = (u8 *) tzcom_virt_to_phys(sb_in_virt); + cmd.sb_in_cmd_len = req.cmd_len; + + resp.cmd_status = TZ_SCHED_STATUS_INCOMPLETE; + resp.sb_in_rsp_addr = (u8 *) tzcom_virt_to_phys(sb_in_virt + + req.cmd_len); + resp.sb_in_rsp_len = req.resp_len; + + PDEBUG("before call tzcom_scm_call, cmd_id = : %u", req.cmd_id); + PDEBUG("before call tzcom_scm_call, sizeof(cmd) = : %u", sizeof(cmd)); + + tzcom_scm_call((const void *) &cmd, sizeof(cmd), &resp, sizeof(resp)); + mutex_unlock(&sb_in_lock); + + while (resp.cmd_status != TZ_SCHED_STATUS_COMPLETE) { + /* + * If cmd is incomplete, get the callback cmd out from SB out + * and put it on the list + */ + PDEBUG("cmd_status is incomplete."); + next_callback = (struct tzcom_callback *)sb_out_virt; + + mutex_lock(&sb_out_lock); + reqd_len_sb_out = sizeof(*next_callback) + + next_callback->sb_out_cb_data_len; + if (reqd_len_sb_out > sb_out_length) { + PDEBUG("Not enough memory to" + " fit tzcom_callback buffer." + " Required: %u, Available: %u", + reqd_len_sb_out, sb_out_length); + mutex_unlock(&sb_out_lock); + return -ENOMEM; + } + + /* Assumption is cb_data_off is sizeof(tzcom_callback) */ + new_entry_len = sizeof(*new_entry) + + next_callback->sb_out_cb_data_len; + new_entry = kmalloc(new_entry_len, GFP_KERNEL); + if (!new_entry) { + PERR("kmalloc failed"); + mutex_unlock(&sb_out_lock); + return -ENOMEM; + } + + cb = &new_entry->callback; + cb->cmd_id = next_callback->cmd_id; + cb->sb_out_cb_data_len = next_callback->sb_out_cb_data_len; + cb->sb_out_cb_data_off = next_callback->sb_out_cb_data_off; + + cb_data = (u8 *)next_callback + + next_callback->sb_out_cb_data_off; + memcpy((u8 *)cb + cb->sb_out_cb_data_off, cb_data, + next_callback->sb_out_cb_data_len); + mutex_unlock(&sb_out_lock); + + mutex_lock(&data->callback_list_lock); + list_add_tail(&new_entry->list, &data->callback_list_head); + mutex_unlock(&data->callback_list_lock); + + /* + * We don't know which service can handle the command. so we + * wake up all blocking services and let them figure out if + * they can handle the given command. + */ + spin_lock_irqsave(&data->registered_svc_list_lock, flags); + list_for_each_entry(ptr_svc, + &data->registered_svc_list_head, list) { + ptr_svc->next_cmd_flag = 1; + wake_up_interruptible(&ptr_svc->next_cmd_wq); + } + spin_unlock_irqrestore(&data->registered_svc_list_lock, + flags); + + PDEBUG("waking up next_cmd_wq and " + "waiting for cont_cmd_wq"); + if (wait_event_interruptible(data->cont_cmd_wq, + data->cont_cmd_flag != 0)) { + PDEBUG("Interrupted: exiting send_cmd loop"); + return -ERESTARTSYS; + } + data->cont_cmd_flag = 0; + cmd.cmd_type = TZ_SCHED_CMD_PENDING; + mutex_lock(&sb_in_lock); + tzcom_scm_call((const void *) &cmd, sizeof(cmd), &resp, + sizeof(resp)); + mutex_unlock(&sb_in_lock); + } + + mutex_lock(&sb_in_lock); + resp.sb_in_rsp_addr = sb_in_virt + cmd.sb_in_cmd_len; + resp.sb_in_rsp_len = req.resp_len; + mutex_unlock(&sb_in_lock); + + /* Cmd is done now. Copy the response from SB in to user */ + if (req.resp_len >= resp.sb_in_rsp_len) { + PDEBUG("Before memcpy resp_buf"); + mutex_lock(&sb_in_lock); + memcpy(req.resp_buf, resp.sb_in_rsp_addr, resp.sb_in_rsp_len); + mutex_unlock(&sb_in_lock); + } else { + PDEBUG("Provided response buffer is smaller" + " than required. Required: %u," + " Provided: %u", + resp.sb_in_rsp_len, req.resp_len); + ret = -ENOMEM; + } + + PDEBUG("sending cmd_req.rsp " + "size: %u, ptr: 0x%p", req.resp_len, + req.resp_buf); + ret = copy_to_user(argp, &req, sizeof(req)); + if (ret) { + PDEBUG("copy_to_user failed"); + return ret; + } + + return ret; +} + +static struct tzcom_registered_svc_list *__tzcom_find_svc( + struct tzcom_data_t *data, + uint32_t instance_id) +{ + struct tzcom_registered_svc_list *entry; + unsigned long flags; + + spin_lock_irqsave(&data->registered_svc_list_lock, flags); + list_for_each_entry(entry, + &data->registered_svc_list_head, list) { + if (entry->svc.instance_id == instance_id) + break; + } + spin_unlock_irqrestore(&data->registered_svc_list_lock, flags); + + return entry; +} + +static int __tzcom_copy_cmd(struct tzcom_data_t *data, + struct tzcom_next_cmd_op_req *req, + struct tzcom_registered_svc_list *ptr_svc) +{ + int found = 0; + int ret = -EAGAIN; + struct tzcom_callback_list *entry; + struct tzcom_callback *cb; + + PDEBUG("In here"); + mutex_lock(&data->callback_list_lock); + PDEBUG("Before looping through cmd and svc lists."); + list_for_each_entry(entry, &data->callback_list_head, list) { + cb = &entry->callback; + if (req->svc_id == ptr_svc->svc.svc_id && + req->instance_id == ptr_svc->svc.instance_id && + cb->cmd_id >= ptr_svc->svc.cmd_id_low && + cb->cmd_id <= ptr_svc->svc.cmd_id_high) { + PDEBUG("Found matching entry"); + found = 1; + if (cb->sb_out_cb_data_len <= req->req_len) { + PDEBUG("copying cmd buffer %p to req " + "buffer %p, length: %u", + (u8 *)cb + cb->sb_out_cb_data_off, + req->req_buf, cb->sb_out_cb_data_len); + req->cmd_id = cb->cmd_id; + ret = copy_to_user(req->req_buf, + (u8 *)cb + cb->sb_out_cb_data_off, + cb->sb_out_cb_data_len); + if (ret) { + PDEBUG("copy_to_user failed"); + break; + } + list_del(&entry->list); + kfree(entry); + ret = 0; + } else { + PDEBUG("callback data buffer is " + "larger than provided buffer." + "Required: %u, Provided: %u", + cb->sb_out_cb_data_len, + req->req_len); + ret = -ENOMEM; + } + break; + } + } + PDEBUG("After looping through cmd and svc lists."); + mutex_unlock(&data->callback_list_lock); + return ret; +} + +static int tzcom_read_next_cmd(struct tzcom_data_t *data, void __user *argp) +{ + int ret = 0; + struct tzcom_next_cmd_op_req req; + struct tzcom_registered_svc_list *this_svc; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + PDEBUG("copy_from_user failed"); + return ret; + } + + if (req.instance_id > atomic_read(&svc_instance_ctr)) { + PDEBUG("Invalid instance_id for the request"); + return -EINVAL; + } + + if (!req.req_buf || req.req_len == 0) { + PDEBUG("Invalid request buffer or buffer length"); + return -EINVAL; + } + + PDEBUG("Before next_cmd loop"); + this_svc = __tzcom_find_svc(data, req.instance_id); + + while (1) { + PDEBUG("Before wait_event next_cmd."); + if (wait_event_interruptible(this_svc->next_cmd_wq, + this_svc->next_cmd_flag != 0)) { + PDEBUG("Interrupted: exiting wait_next_cmd loop"); + /* woken up for different reason */ + return -ERESTARTSYS; + } + PDEBUG("After wait_event next_cmd."); + this_svc->next_cmd_flag = 0; + + ret = __tzcom_copy_cmd(data, &req, this_svc); + if (ret == 0) { + PDEBUG("Successfully found svc for cmd"); + data->handled_cmd_svc_instance_id = req.instance_id; + break; + } else if (ret == -ENOMEM) { + PDEBUG("Not enough memory"); + return ret; + } + } + ret = copy_to_user(argp, &req, sizeof(req)); + if (ret) { + PDEBUG("copy_to_user failed"); + return ret; + } + PDEBUG("copy_to_user is done."); + return ret; +} + +static int tzcom_cont_cmd(struct tzcom_data_t *data, void __user *argp) +{ + int ret = 0; + struct tzcom_cont_cmd_op_req req; + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + PDEBUG("copy_from_user failed"); + return ret; + } + + /* + * Only the svc instance that handled the cmd (in read_next_cmd method) + * can call continue cmd + */ + if (data->handled_cmd_svc_instance_id != req.instance_id) { + PDEBUG("Only the service instance that handled the last " + "callback can continue cmd. " + "Expected: %u, Received: %u", + data->handled_cmd_svc_instance_id, + req.instance_id); + return -EINVAL; + } + + if (req.resp_buf) { + mutex_lock(&sb_out_lock); + memcpy(sb_out_virt, req.resp_buf, req.resp_len); + mutex_unlock(&sb_out_lock); + } + + data->cont_cmd_flag = 1; + wake_up_interruptible(&data->cont_cmd_wq); + return ret; +} + +static long tzcom_ioctl(struct file *file, unsigned cmd, + unsigned long arg) +{ + int ret = 0; + struct tzcom_data_t *tzcom_data = file->private_data; + void __user *argp = (void __user *) arg; + PDEBUG("enter tzcom_ioctl()"); + switch (cmd) { + case TZCOM_IOCTL_REGISTER_SERVICE_REQ: { + PDEBUG("ioctl register_service_req()"); + ret = tzcom_register_service(tzcom_data, argp); + if (ret) + PDEBUG("failed tzcom_register_service: %d", ret); + break; + } + case TZCOM_IOCTL_UNREGISTER_SERVICE_REQ: { + PDEBUG("ioctl unregister_service_req()"); + ret = tzcom_unregister_service(tzcom_data, argp); + if (ret) + PDEBUG("failed tzcom_unregister_service: %d", ret); + break; + } + case TZCOM_IOCTL_SEND_CMD_REQ: { + PDEBUG("ioctl send_cmd_req()"); + /* Only one client allowed here at a time */ + mutex_lock(&send_cmd_lock); + ret = tzcom_send_cmd(tzcom_data, argp); + mutex_unlock(&send_cmd_lock); + if (ret) + PDEBUG("failed tzcom_send_cmd: %d", ret); + break; + } + case TZCOM_IOCTL_READ_NEXT_CMD_REQ: { + PDEBUG("ioctl read_next_cmd_req()"); + ret = tzcom_read_next_cmd(tzcom_data, argp); + if (ret) + PDEBUG("failed tzcom_read_next: %d", ret); + break; + } + case TZCOM_IOCTL_CONTINUE_CMD_REQ: { + PDEBUG("ioctl continue_cmd_req()"); + ret = tzcom_cont_cmd(tzcom_data, argp); + if (ret) + PDEBUG("failed tzcom_cont_cmd: %d", ret); + break; + } + default: + return -EINVAL; + } + return ret; +} + +static int tzcom_open(struct inode *inode, struct file *file) +{ + long pil_error; + struct tz_pr_init_sb_req_s sb_out_init_req; + struct tz_pr_init_sb_rsp_s sb_out_init_rsp; + void *rsp_addr_virt; + struct tzcom_command cmd; + struct tzcom_response resp; + struct tzcom_data_t *tzcom_data; + + PDEBUG("In here"); + if (pil == NULL) { + pil = pil_get("playrdy"); + if (IS_ERR(pil)) { + PERR("Playready PIL image load failed"); + pil_error = PTR_ERR(pil); + pil = NULL; + return pil_error; + } + PDEBUG("playrdy image loaded successfully"); + } + + sb_out_init_req.pr_cmd = TZ_SCHED_CMD_ID_INIT_SB_OUT; + sb_out_init_req.sb_len = sb_out_length; + sb_out_init_req.sb_ptr = tzcom_virt_to_phys(sb_out_virt); + PDEBUG("sb_out_init_req { pr_cmd: %d, sb_len: %u, " + "sb_ptr (phys): 0x%x }", + sb_out_init_req.pr_cmd, + sb_out_init_req.sb_len, + sb_out_init_req.sb_ptr); + + mutex_lock(&sb_in_lock); + PDEBUG("Before memcpy on sb_in"); + memcpy(sb_in_virt, &sb_out_init_req, sizeof(sb_out_init_req)); + PDEBUG("After memcpy on sb_in"); + + /* It will always be a new cmd from this method */ + cmd.cmd_type = TZ_SCHED_CMD_NEW; + cmd.sb_in_cmd_addr = (u8 *) tzcom_virt_to_phys(sb_in_virt); + cmd.sb_in_cmd_len = sizeof(sb_out_init_req); + PDEBUG("tzcom_command { cmd_type: %u, sb_in_cmd_addr: %p, " + "sb_in_cmd_len: %u }", + cmd.cmd_type, cmd.sb_in_cmd_addr, cmd.sb_in_cmd_len); + + resp.cmd_status = 0; + resp.sb_in_rsp_addr = (u8 *)cmd.sb_in_cmd_addr + cmd.sb_in_cmd_len; + resp.sb_in_rsp_len = sizeof(sb_out_init_rsp); + PDEBUG("tzcom_response before scm { cmd_status: %u, " + "sb_in_rsp_addr: %p, sb_in_rsp_len: %u }", + resp.cmd_status, resp.sb_in_rsp_addr, + resp.sb_in_rsp_len); + + PDEBUG("Before scm_call for sb_init"); + tzcom_scm_call(&cmd, sizeof(cmd), &resp, sizeof(resp)); + PDEBUG("After scm_call for sb_init"); + PDEBUG("tzcom_response after scm { cmd_status: %u, " + "sb_in_rsp_addr: %p, sb_in_rsp_len: %u }", + resp.cmd_status, resp.sb_in_rsp_addr, + resp.sb_in_rsp_len); + + if (resp.sb_in_rsp_addr) { + rsp_addr_virt = tzcom_phys_to_virt((unsigned long) + resp.sb_in_rsp_addr); + PDEBUG("Received response phys: %p, virt: %p", + resp.sb_in_rsp_addr, + rsp_addr_virt); + memcpy(&sb_out_init_rsp, rsp_addr_virt, resp.sb_in_rsp_len); + } else { + PERR("Error with SB initialization"); + mutex_unlock(&sb_in_lock); + return -EPERM; + } + mutex_unlock(&sb_in_lock); + + PDEBUG("sb_out_init_rsp { pr_cmd: %d, ret: %d }", + sb_out_init_rsp.pr_cmd, sb_out_init_rsp.ret); + + if (sb_out_init_rsp.ret) { + PERR("sb_out_init_req failed: %d", sb_out_init_rsp.ret); + return -EPERM; + } + + tzcom_data = kmalloc(sizeof(*tzcom_data), GFP_KERNEL); + if (!tzcom_data) { + PERR("kmalloc failed"); + return -ENOMEM; + } + file->private_data = tzcom_data; + + INIT_LIST_HEAD(&tzcom_data->callback_list_head); + mutex_init(&tzcom_data->callback_list_lock); + + INIT_LIST_HEAD(&tzcom_data->registered_svc_list_head); + spin_lock_init(&tzcom_data->registered_svc_list_lock); + + init_waitqueue_head(&tzcom_data->cont_cmd_wq); + tzcom_data->cont_cmd_flag = 0; + tzcom_data->handled_cmd_svc_instance_id = 0; + return 0; +} + +static int tzcom_release(struct inode *inode, struct file *file) +{ + struct tzcom_data_t *tzcom_data = file->private_data; + struct tzcom_callback_list *lcb, *ncb; + struct tzcom_registered_svc_list *lsvc, *nsvc; + PDEBUG("In here"); + + wake_up_all(&tzcom_data->cont_cmd_wq); + + list_for_each_entry_safe(lcb, ncb, + &tzcom_data->callback_list_head, list) { + list_del(&lcb->list); + kfree(lcb); + } + + list_for_each_entry_safe(lsvc, nsvc, + &tzcom_data->registered_svc_list_head, list) { + wake_up_all(&lsvc->next_cmd_wq); + list_del(&lsvc->list); + kfree(lsvc); + } + + kfree(tzcom_data); + return 0; +} + +static const struct file_operations tzcom_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = tzcom_ioctl, + .open = tzcom_open, + .release = tzcom_release +}; + +static int __init tzcom_init(void) +{ + int rc; + struct device *class_dev; + + PDEBUG("Hello tzcom"); + + rc = alloc_chrdev_region(&tzcom_device_no, 0, 1, TZCOM_DEV); + if (rc < 0) { + PERR("alloc_chrdev_region failed %d", rc); + return rc; + } + + driver_class = class_create(THIS_MODULE, TZCOM_DEV); + if (IS_ERR(driver_class)) { + rc = -ENOMEM; + PERR("class_create failed %d", rc); + goto unregister_chrdev_region; + } + + class_dev = device_create(driver_class, NULL, tzcom_device_no, NULL, + TZCOM_DEV); + if (!class_dev) { + PERR("class_device_create failed %d", rc); + rc = -ENOMEM; + goto class_destroy; + } + + cdev_init(&tzcom_cdev, &tzcom_fops); + tzcom_cdev.owner = THIS_MODULE; + + rc = cdev_add(&tzcom_cdev, MKDEV(MAJOR(tzcom_device_no), 0), 1); + if (rc < 0) { + PERR("cdev_add failed %d", rc); + goto class_device_destroy; + } + + sb_in_phys = pmem_kalloc(sb_in_length, PMEM_MEMTYPE_EBI1 | + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)sb_in_phys)) { + PERR("could not allocte in kernel pmem buffers for sb_in"); + rc = -ENOMEM; + goto class_device_destroy; + } + PDEBUG("physical_addr for sb_in: 0x%x", sb_in_phys); + + sb_in_virt = (u8 *) ioremap((unsigned long)sb_in_phys, + sb_in_length); + if (!sb_in_virt) { + PERR("Shared buffer IN allocation failed."); + rc = -ENOMEM; + goto class_device_destroy; + } + PDEBUG("sb_in virt address: %p, phys address: 0x%x", + sb_in_virt, tzcom_virt_to_phys(sb_in_virt)); + + sb_out_phys = pmem_kalloc(sb_out_length, PMEM_MEMTYPE_EBI1 | + PMEM_ALIGNMENT_4K); + if (IS_ERR((void *)sb_out_phys)) { + PERR("could not allocte in kernel pmem buffers for sb_out"); + rc = -ENOMEM; + goto class_device_destroy; + } + PDEBUG("physical_addr for sb_out: 0x%x", sb_out_phys); + + sb_out_virt = (u8 *) ioremap((unsigned long)sb_out_phys, + sb_out_length); + if (!sb_out_virt) { + PERR("Shared buffer OUT allocation failed."); + rc = -ENOMEM; + goto class_device_destroy; + } + PDEBUG("sb_out virt address: %p, phys address: 0x%x", + sb_out_virt, tzcom_virt_to_phys(sb_out_virt)); + + /* Initialized in tzcom_open */ + pil = NULL; + + return 0; + +class_device_destroy: + if (sb_in_virt) + iounmap(sb_in_virt); + if (sb_in_phys) + pmem_kfree(sb_in_phys); + if (sb_out_virt) + iounmap(sb_out_virt); + if (sb_out_phys) + pmem_kfree(sb_out_phys); + device_destroy(driver_class, tzcom_device_no); +class_destroy: + class_destroy(driver_class); +unregister_chrdev_region: + unregister_chrdev_region(tzcom_device_no, 1); + return rc; +} + +static void __exit tzcom_exit(void) +{ + PDEBUG("Goodbye tzcom"); + if (sb_in_virt) + iounmap(sb_in_virt); + if (sb_in_phys) + pmem_kfree(sb_in_phys); + if (sb_out_virt) + iounmap(sb_out_virt); + if (sb_out_phys) + pmem_kfree(sb_out_phys); + if (pil != NULL) { + pil_put("playrdy"); + pil = NULL; + } + device_destroy(driver_class, tzcom_device_no); + class_destroy(driver_class); + unregister_chrdev_region(tzcom_device_no, 1); +} + + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sachin Shah "); +MODULE_DESCRIPTION("Qualcomm TrustZone Communicator"); +MODULE_VERSION("1.00"); + +module_init(tzcom_init); +module_exit(tzcom_exit); diff --git a/drivers/misc/tzcomi.h b/drivers/misc/tzcomi.h new file mode 100644 index 00000000000..33634cf83d4 --- /dev/null +++ b/drivers/misc/tzcomi.h @@ -0,0 +1,112 @@ +/* Qualcomm TrustZone communicator driver + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __TZCOMI_H_ +#define __TZCOMI_H_ + +#include + +enum tz_sched_cmd_id { + TZ_SCHED_CMD_ID_INVALID = 0, + TZ_SCHED_CMD_ID_INIT_SB_OUT, /**< Initialize the shared buffer */ + TZ_SCHED_CMD_ID_INIT_SB_LOG, /**< Initialize the logging shared buf */ + TZ_SCHED_CMD_ID_UNKNOWN = 0x7FFFFFFE, + TZ_SCHED_CMD_ID_MAX = 0x7FFFFFFF +}; + +enum tz_sched_cmd_type { + TZ_SCHED_CMD_INVALID = 0, + TZ_SCHED_CMD_NEW, /** New TZ Scheduler Command */ + TZ_SCHED_CMD_PENDING, /** Pending cmd...sched will restore stack */ + TZ_SCHED_CMD_COMPLETE, /** TZ sched command is complete */ + TZ_SCHED_CMD_MAX = 0x7FFFFFFF +}; + +enum tz_sched_cmd_status { + TZ_SCHED_STATUS_INCOMPLETE = 0, + TZ_SCHED_STATUS_COMPLETE, + TZ_SCHED_STATUS_MAX = 0x7FFFFFFF +}; + +/** Command structure for initializing shared buffers (SB_OUT + and SB_LOG) +*/ +__packed struct tz_pr_init_sb_req_s { + /** First 4 bytes should always be command id + * from enum tz_sched_cmd_id */ + uint32_t pr_cmd; + /** Pointer to the physical location of sb_out buffer */ + uint32_t sb_ptr; + /** length of shared buffer */ + uint32_t sb_len; +}; + + +__packed struct tz_pr_init_sb_rsp_s { + /** First 4 bytes should always be command id + * from enum tz_sched_cmd_id */ + uint32_t pr_cmd; + /** Return code, 0 for success, Approp error code otherwise */ + int32_t ret; +}; + + +/** + * struct tzcom_command - tzcom command buffer + * @cmd_type: value from enum tz_sched_cmd_type + * @sb_in_cmd_addr: points to physical location of command + * buffer + * @sb_in_cmd_len: length of command buffer + */ +__packed struct tzcom_command { + uint32_t cmd_type; + uint8_t *sb_in_cmd_addr; + uint32_t sb_in_cmd_len; +}; + +/** + * struct tzcom_response - tzcom response buffer + * @cmd_status: value from enum tz_sched_cmd_status + * @sb_in_rsp_addr: points to physical location of response + * buffer + * @sb_in_rsp_len: length of command response + */ +__packed struct tzcom_response { + uint32_t cmd_status; + uint8_t *sb_in_rsp_addr; + uint32_t sb_in_rsp_len; +}; + +/** + * struct tzcom_callback - tzcom callback buffer + * @cmd_id: command to run in registered service + * @sb_out_rsp_addr: points to physical location of response + * buffer + * @sb_in_cmd_len: length of command response + * + * A callback buffer would be laid out in sb_out as follows: + * + * --------------------- <--- struct tzcom_callback + * | callback header | + * --------------------- <--- tzcom_callback.sb_out_cb_data_off + * | callback data | + * --------------------- + */ +__packed struct tzcom_callback { + uint32_t cmd_id; + uint32_t sb_out_cb_data_len; + uint32_t sb_out_cb_data_off; +}; + +#endif /* __TZCOMI_H_ */ diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index f2eeb38efa6..91165514156 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -19,6 +19,14 @@ config MMC_DEBUG This is an option for use by developers; most people should say N here. This enables MMC core and driver debugging. +config MMC_PERF_PROFILING + bool "MMC performance profiling" + depends on MMC != n + default n + help + If you say Y here, support will be added for collecting + performance numbers at the MMC Queue and Host layers. + if MMC source "drivers/mmc/core/Kconfig" diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index c779503e757..16feada64a8 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1044,7 +1044,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, md->disk->fops = &mmc_bdops; md->disk->private_data = md; md->disk->queue = md->queue.queue; - md->disk->driverfs_dev = parent; + md->disk->driverfs_dev = &card->dev; set_disk_ro(md->disk, md->read_only || default_ro); md->disk->flags = GENHD_FL_EXT_DEVT; diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 6413afa318d..9b64847421d 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -46,12 +46,20 @@ static int mmc_queue_thread(void *d) { struct mmc_queue *mq = d; struct request_queue *q = mq->queue; + struct request *req; + +#ifdef CONFIG_MMC_PERF_PROFILING + ktime_t start, diff; + struct mmc_host *host = mq->card->host; + unsigned long bytes_xfer; +#endif + current->flags |= PF_MEMALLOC; down(&mq->thread_sem); do { - struct request *req = NULL; + req = NULL; /* Must be set to NULL at each iteration */ spin_lock_irq(q->queue_lock); set_current_state(TASK_INTERRUPTIBLE); @@ -71,7 +79,26 @@ static int mmc_queue_thread(void *d) } set_current_state(TASK_RUNNING); - mq->issue_fn(mq, req); +#ifdef CONFIG_MMC_PERF_PROFILING + bytes_xfer = blk_rq_bytes(req); + if (rq_data_dir(req) == READ) { + start = ktime_get(); + mq->issue_fn(mq, req); + diff = ktime_sub(ktime_get(), start); + host->perf.rbytes_mmcq += bytes_xfer; + host->perf.rtime_mmcq = + ktime_add(host->perf.rtime_mmcq, diff); + } else { + start = ktime_get(); + mq->issue_fn(mq, req); + diff = ktime_sub(ktime_get(), start); + host->perf.wbytes_mmcq += bytes_xfer; + host->perf.wtime_mmcq = + ktime_add(host->perf.wtime_mmcq, diff); + } +#else + mq->issue_fn(mq, req); +#endif } while (1); up(&mq->thread_sem); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 7c3444a3707..3223110bcdb 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -97,6 +98,9 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) { struct mmc_command *cmd = mrq->cmd; int err = cmd->error; +#ifdef CONFIG_MMC_PERF_PROFILING + ktime_t diff; +#endif if (err && cmd->retries && mmc_host_is_spi(host)) { if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND) @@ -119,6 +123,20 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) cmd->resp[2], cmd->resp[3]); if (mrq->data) { +#ifdef CONFIG_MMC_PERF_PROFILING + diff = ktime_sub(ktime_get(), host->perf.start); + if (mrq->data->flags == MMC_DATA_READ) { + host->perf.rbytes_drv += + mrq->data->bytes_xfered; + host->perf.rtime_drv = + ktime_add(host->perf.rtime_drv, diff); + } else { + host->perf.wbytes_drv += + mrq->data->bytes_xfered; + host->perf.wtime_drv = + ktime_add(host->perf.wtime_drv, diff); + } +#endif pr_debug("%s: %d bytes transferred: %d\n", mmc_hostname(host), mrq->data->bytes_xfered, mrq->data->error); @@ -193,6 +211,9 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) mrq->stop->error = 0; mrq->stop->mrq = mrq; } +#ifdef CONFIG_MMC_PERF_PROFILING + host->perf.start = ktime_get(); +#endif } mmc_host_clk_ungate(host); led_trigger_event(host->led, LED_FULL); @@ -222,7 +243,7 @@ void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq) mmc_start_request(host, mrq); - wait_for_completion(&complete); + wait_for_completion_io(&complete); } EXPORT_SYMBOL(mmc_wait_for_req); @@ -479,6 +500,14 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) might_sleep(); add_wait_queue(&host->wq, &wait); +#ifdef CONFIG_PM_RUNTIME + while (mmc_dev(host)->power.runtime_status == RPM_SUSPENDING) { + if (host->suspend_task == current) + break; + msleep(15); + } +#endif + spin_lock_irqsave(&host->lock, flags); while (1) { set_current_state(TASK_UNINTERRUPTIBLE); @@ -1589,8 +1618,16 @@ static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) /* Order's important: probe SDIO, then SD, then MMC */ if (!mmc_attach_sdio(host)) return 0; + + if (!host->ios.vdd) + mmc_power_up(host); + if (!mmc_attach_sd(host)) return 0; + + if (!host->ios.vdd) + mmc_power_up(host); + if (!mmc_attach_mmc(host)) return 0; diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 84694a9410d..3dead908e67 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -94,7 +94,7 @@ static void mmc_host_clk_gate_delayed(struct mmc_host *host) spin_unlock_irqrestore(&host->clk_lock, flags); return; } - mutex_lock(&host->clk_gate_mutex); + mmc_claim_host(host); spin_lock_irqsave(&host->clk_lock, flags); if (!host->clk_requests) { spin_unlock_irqrestore(&host->clk_lock, flags); @@ -104,7 +104,7 @@ static void mmc_host_clk_gate_delayed(struct mmc_host *host) pr_debug("%s: gated MCI clock\n", mmc_hostname(host)); } spin_unlock_irqrestore(&host->clk_lock, flags); - mutex_unlock(&host->clk_gate_mutex); + mmc_release_host(host); } /* @@ -130,7 +130,7 @@ void mmc_host_clk_ungate(struct mmc_host *host) { unsigned long flags; - mutex_lock(&host->clk_gate_mutex); + mmc_claim_host(host); spin_lock_irqsave(&host->clk_lock, flags); if (host->clk_gated) { spin_unlock_irqrestore(&host->clk_lock, flags); @@ -140,7 +140,7 @@ void mmc_host_clk_ungate(struct mmc_host *host) } host->clk_requests++; spin_unlock_irqrestore(&host->clk_lock, flags); - mutex_unlock(&host->clk_gate_mutex); + mmc_release_host(host); } /** @@ -215,7 +215,6 @@ static inline void mmc_host_clk_init(struct mmc_host *host) host->clk_gated = false; INIT_WORK(&host->clk_gate_work, mmc_host_clk_gate_work); spin_lock_init(&host->clk_lock); - mutex_init(&host->clk_gate_mutex); } /** @@ -309,6 +308,70 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) } EXPORT_SYMBOL(mmc_alloc_host); +#ifdef CONFIG_MMC_PERF_PROFILING +static ssize_t +show_perf(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + int64_t rtime_mmcq, wtime_mmcq, rtime_drv, wtime_drv; + unsigned long rbytes_mmcq, wbytes_mmcq, rbytes_drv, wbytes_drv; + + spin_lock(&host->lock); + + rbytes_mmcq = host->perf.rbytes_mmcq; + wbytes_mmcq = host->perf.wbytes_mmcq; + rbytes_drv = host->perf.rbytes_drv; + wbytes_drv = host->perf.wbytes_drv; + + rtime_mmcq = ktime_to_us(host->perf.rtime_mmcq); + wtime_mmcq = ktime_to_us(host->perf.wtime_mmcq); + rtime_drv = ktime_to_us(host->perf.rtime_drv); + wtime_drv = ktime_to_us(host->perf.wtime_drv); + + spin_unlock(&host->lock); + + return snprintf(buf, PAGE_SIZE, "Write performance at MMCQ Level:" + "%lu bytes in %lld microseconds\n" + "Read performance at MMCQ Level:" + "%lu bytes in %lld microseconds\n" + "Write performance at driver Level:" + "%lu bytes in %lld microseconds\n" + "Read performance at driver Level:" + "%lu bytes in %lld microseconds\n", + wbytes_mmcq, wtime_mmcq, rbytes_mmcq, + rtime_mmcq, wbytes_drv, wtime_drv, + rbytes_drv, rtime_drv); +} + +static ssize_t +set_perf(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int64_t value; + struct mmc_host *host = dev_get_drvdata(dev); + sscanf(buf, "%lld", &value); + if (!value) { + spin_lock(&host->lock); + memset(&host->perf, 0, sizeof(host->perf)); + spin_unlock(&host->lock); + } + + return count; +} + +static DEVICE_ATTR(perf, S_IRUGO | S_IWUSR, + show_perf, set_perf); +#endif + +static struct attribute *dev_attrs[] = { +#ifdef CONFIG_MMC_PERF_PROFILING + &dev_attr_perf.attr, +#endif + NULL, +}; +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, +}; /** * mmc_add_host - initialise host hardware @@ -334,6 +397,10 @@ int mmc_add_host(struct mmc_host *host) #ifdef CONFIG_DEBUG_FS mmc_add_host_debugfs(host); #endif + err = sysfs_create_group(&host->parent->kobj, &dev_attr_grp); + if (err) + pr_err("%s: failed to create sysfs group with err %d\n", + __func__, err); mmc_start_host(host); if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY)) @@ -362,6 +429,8 @@ void mmc_remove_host(struct mmc_host *host) #ifdef CONFIG_DEBUG_FS mmc_remove_host_debugfs(host); #endif + sysfs_remove_group(&host->parent->kobj, &dev_attr_grp); + device_del(&host->class_dev); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index aa7d1d79b8c..04816e9a614 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -660,6 +660,9 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, /* Erase size depends on CSD and Extended CSD */ mmc_set_erase_size(card); + + if (card->ext_csd.sectors && (rocr & MMC_CARD_SECTOR_ADDR)) + mmc_card_set_blockaddr(card); } /* @@ -864,7 +867,10 @@ static void mmc_remove(struct mmc_host *host) BUG_ON(!host->card); mmc_remove_card(host->card); + + mmc_claim_host(host); host->card = NULL; + mmc_release_host(host); } /* diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 845ce7c533b..2e39d2c334b 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -398,6 +398,7 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, if (err) return err; + mmc_delay(1); /* Must check status to be sure of no errors */ do { err = mmc_send_status(card, &status); @@ -506,6 +507,9 @@ mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode, data.sg = &sg; data.sg_len = 1; + data.timeout_ns = 1000000; + data.timeout_clks = 0; + sg_init_one(&sg, data_buf, len); mmc_wait_for_req(host, &mrq); err = 0; diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 5decf4972bc..c5492166b13 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1003,7 +1003,10 @@ static void mmc_sd_remove(struct mmc_host *host) BUG_ON(!host->card); mmc_remove_card(host->card); + + mmc_claim_host(host); host->card = NULL; + mmc_release_host(host); } /* diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 7da522e958a..202482478c9 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -161,7 +161,7 @@ static int sdio_enable_wide(struct mmc_card *card) int ret; u8 ctrl; - if (!(card->host->caps & MMC_CAP_4_BIT_DATA)) + if (!(card->host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) return 0; if (card->cccr.low_speed && !card->cccr.wide_bus) @@ -171,7 +171,10 @@ static int sdio_enable_wide(struct mmc_card *card) if (ret) return ret; - ctrl |= SDIO_BUS_WIDTH_4BIT; + if (card->host->caps & MMC_CAP_8_BIT_DATA) + ctrl |= SDIO_BUS_WIDTH_8BIT; + else if (card->host->caps & MMC_CAP_4_BIT_DATA) + ctrl |= SDIO_BUS_WIDTH_4BIT; ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); if (ret) @@ -212,7 +215,7 @@ static int sdio_disable_wide(struct mmc_card *card) int ret; u8 ctrl; - if (!(card->host->caps & MMC_CAP_4_BIT_DATA)) + if (!(card->host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) return 0; if (card->cccr.low_speed && !card->cccr.wide_bus) @@ -222,10 +225,10 @@ static int sdio_disable_wide(struct mmc_card *card) if (ret) return ret; - if (!(ctrl & SDIO_BUS_WIDTH_4BIT)) + if (!(ctrl & (SDIO_BUS_WIDTH_4BIT | SDIO_BUS_WIDTH_8BIT))) return 0; - ctrl &= ~SDIO_BUS_WIDTH_4BIT; + ctrl &= ~(SDIO_BUS_WIDTH_4BIT | SDIO_BUS_WIDTH_8BIT); ctrl |= SDIO_BUS_ASYNC_INT; ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); @@ -532,8 +535,12 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, * Switch to wider bus (if supported). */ err = sdio_enable_4bit_bus(card); - if (err > 0) - mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + if (err > 0) { + if (card->host->caps & MMC_CAP_8_BIT_DATA) + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_8); + else if (card->host->caps & MMC_CAP_4_BIT_DATA) + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + } else if (err) goto remove; @@ -678,7 +685,10 @@ static int mmc_sdio_resume(struct mmc_host *host) /* We may have switched to 1-bit mode during suspend */ err = sdio_enable_4bit_bus(host->card); if (err > 0) { - mmc_set_bus_width(host, MMC_BUS_WIDTH_4); + if (host->caps & MMC_CAP_8_BIT_DATA) + mmc_set_bus_width(host, MMC_BUS_WIDTH_8); + else if (host->caps & MMC_CAP_4_BIT_DATA) + mmc_set_bus_width(host, MMC_BUS_WIDTH_4); err = 0; } } @@ -983,8 +993,12 @@ int sdio_reset_comm(struct mmc_card *card) mmc_set_clock(host, mmc_sdio_get_max_clock(card)); err = sdio_enable_4bit_bus(card); - if (err > 0) - mmc_set_bus_width(host, MMC_BUS_WIDTH_4); + if (err > 0) { + if (host->caps & MMC_CAP_8_BIT_DATA) + mmc_set_bus_width(host, MMC_BUS_WIDTH_8); + else if (host->caps & MMC_CAP_4_BIT_DATA) + mmc_set_bus_width(host, MMC_BUS_WIDTH_4); + } else if (err) goto err; diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index 541bdb89e0c..dc94222fab7 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -270,8 +270,16 @@ static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func) break; /* null entries have no link field or data */ - if (tpl_code == 0x00) - continue; + if (tpl_code == 0x00) { + if (card->cis.vendor == 0x70 && + (card->cis.device == 0x2460 || + card->cis.device == 0x0460 || + card->cis.device == 0x23F1 || + card->cis.device == 0x23F0)) + break; + else + continue; + } ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link); if (ret) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 56dbf3f6ad0..7378c623063 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -442,6 +442,148 @@ config MMC_SDRICOH_CS config MMC_TMIO_CORE tristate +config MMC_MSM + tristate "Qualcomm SDCC Controller Support" + depends on MMC && ARCH_MSM + help + This provides support for the SD/MMC cell found in the + MSM and QSD SOCs from Qualcomm. + +config MMC_MSM_SDIO_SUPPORT + boolean "Qualcomm MSM SDIO support" + depends on MMC_MSM + help + This enables SDIO support in the msm_sdcc driver. + +config MMC_MSM_CARD_HW_DETECTION + boolean "Qualcomm MMC Hardware detection support" + depends on MMC_MSM + default n + help + Select Y if the hardware has support to detect card insertion/removal. + +config MMC_MSM_SDC1_SUPPORT + boolean "Qualcomm SDC1 support" + depends on MMC_MSM + default y + help + Select Y to enable Slot 1. + +config MMC_MSM_SDC1_8_BIT_SUPPORT + boolean "Qualcomm SDC1 8bit support" + depends on MMC_MSM_SDC1_SUPPORT + default n + help + Select Y to enable 8bit support for Slot 1. + +config MMC_MSM_SDC1_DUMMY52_REQUIRED + boolean "Send dummy 52 read for SDC1" + depends on MMC_MSM_SDC1_SUPPORT + default n + help + Select Y to enable sending dummy 52 reads to complete + all data commands. Required for some SDIO cards. + If unsure, say N. + +config MMC_MSM_SDC2_SUPPORT + boolean "Qualcomm SDC2 support" + depends on MMC_MSM + default y + help + Select Y to enable Slot 2. + +config MMC_MSM_SDC2_8_BIT_SUPPORT + boolean "Qualcomm SDC2 8bit support" + depends on MMC_MSM_SDC2_SUPPORT + default n + help + Select Y to enable 8bit support for Slot 2. + +config MMC_MSM_SDC2_DUMMY52_REQUIRED + boolean "Send dummy 52 read for SDC2" + depends on MMC_MSM_SDC2_SUPPORT + default n + help + Select Y to enable sending dummy 52 reads to complete + all data commands. Required for some SDIO cards. + If unsure, say N. + +config MMC_MSM_SDC3_SUPPORT + boolean "Qualcomm SDC3 support" + depends on MMC_MSM + default n + help + Select Y to enable Slot 3. + +config MMC_MSM_SDC3_8_BIT_SUPPORT + boolean "Qualcomm SDC3 8bit support" + depends on MMC_MSM_SDC3_SUPPORT + default n + help + Select Y to enable 8bit support for Slot 3. + +config MMC_MSM_SDC3_DUMMY52_REQUIRED + boolean "Send dummy 52 read for SDC3" + depends on MMC_MSM_SDC3_SUPPORT + default n + help + Select Y to enable sending dummy 52 reads to complete + all data commands. Required for some SDIO cards. + If unsure, say N. + +config MMC_MSM_SDC4_SUPPORT + boolean "Qualcomm SDC4 support" + depends on MMC_MSM + default n + help + Select Y to enable Slot 4. + +config MMC_MSM_SDC4_8_BIT_SUPPORT + boolean "Qualcomm SDC4 8bit support" + depends on MMC_MSM_SDC4_SUPPORT + default n + help + Select Y to enable 8bit support for Slot 4. + +config MMC_MSM_SDC4_DUMMY52_REQUIRED + boolean "Send dummy 52 read for SDC4" + depends on MMC_MSM_SDC4_SUPPORT + default n + help + Select Y to enable sending dummy 52 reads to complete + all data commands. Required for some SDIO cards. + If unsure, say N. + +config MMC_MSM_SDC5_SUPPORT + boolean "Qualcomm SDC5 support" + depends on MMC_MSM + default n + help + Select Y to enable Slot 5. + +config MMC_MSM_SDC5_8_BIT_SUPPORT + boolean "Qualcomm SDC5 8bit support" + depends on MMC_MSM_SDC5_SUPPORT + default n + help + Select Y to enable 8bit support for Slot 5. + +config MMC_MSM_SDC5_DUMMY52_REQUIRED + boolean "Send dummy 52 read for SDC5" + depends on MMC_MSM_SDC5_SUPPORT + default n + help + Select Y to enable sending dummy 52 reads to complete + all data commands. Required for some SDIO cards. + If unsure, say N. + +config MMC_MSM_SPS_SUPPORT + bool "Use SPS BAM as data mover" + depends on MMC_MSM && SPS + default n + help + Select Y to use SPS BAM as data mover + config MMC_TMIO tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support" depends on MFD_TMIO || MFD_ASIC3 diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 58a5cf73d6e..e780400707d 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -36,6 +36,9 @@ tmio_mmc_core-y += tmio_mmc_dma.o endif obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o obj-$(CONFIG_MMC_CB710) += cb710-mmc.o +obj-$(CONFIG_MMC_MSM) += msm_sdcc.o +obj-$(CONFIG_MMC_MSM_SPS_SUPPORT) += msm_sdcc_dml.o +obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_DW) += dw_mmc.o diff --git a/drivers/mmc/host/msm_sdcc.c b/drivers/mmc/host/msm_sdcc.c index a4c865a5286..23e8d698815 100644 --- a/drivers/mmc/host/msm_sdcc.c +++ b/drivers/mmc/host/msm_sdcc.c @@ -3,7 +3,7 @@ * * Copyright (C) 2007 Google Inc, * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved. - * Copyright (C) 2009, Code Aurora Forum. All Rights Reserved. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 @@ -21,12 +21,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -35,149 +37,251 @@ #include #include #include -#include +#include +#include #include +#include +#include +#include #include #include #include -#include +#include #include -#include #include +#include +#include +#include #include "msm_sdcc.h" +#include "msm_sdcc_dml.h" #define DRIVER_NAME "msm-sdcc" -#define BUSCLK_PWRSAVE 1 -#define BUSCLK_TIMEOUT (HZ) -static unsigned int msmsdcc_fmin = 144000; -static unsigned int msmsdcc_fmax = 50000000; -static unsigned int msmsdcc_4bit = 1; -static unsigned int msmsdcc_pwrsave = 1; -static unsigned int msmsdcc_piopoll = 1; -static unsigned int msmsdcc_sdioirq; +#define DBG(host, fmt, args...) \ + pr_debug("%s: %s: " fmt "\n", mmc_hostname(host->mmc), __func__ , args) + +#define IRQ_DEBUG 0 +#define SPS_SDCC_PRODUCER_PIPE_INDEX 1 +#define SPS_SDCC_CONSUMER_PIPE_INDEX 2 +#define SPS_CONS_PERIPHERAL 0 +#define SPS_PROD_PERIPHERAL 1 +/* 16 KB */ +#define SPS_MAX_DESC_SIZE (16 * 1024) + +#if defined(CONFIG_DEBUG_FS) +static void msmsdcc_dbg_createhost(struct msmsdcc_host *); +static struct dentry *debugfs_dir; +static struct dentry *debugfs_file; +static int msmsdcc_dbg_init(void); +#endif -#define PIO_SPINMAX 30 -#define CMD_SPINMAX 20 +static unsigned int msmsdcc_pwrsave = 1; +#define DUMMY_52_STATE_NONE 0 +#define DUMMY_52_STATE_SENT 1 -static inline void -msmsdcc_disable_clocks(struct msmsdcc_host *host, int deferr) -{ - WARN_ON(!host->clks_on); +static struct mmc_command dummy52cmd; +static struct mmc_request dummy52mrq = { + .cmd = &dummy52cmd, + .data = NULL, + .stop = NULL, +}; +static struct mmc_command dummy52cmd = { + .opcode = SD_IO_RW_DIRECT, + .flags = MMC_RSP_PRESENT, + .data = NULL, + .mrq = &dummy52mrq, +}; +/* + * An array holding the Tuning pattern to compare with when + * executing a tuning cycle. + */ +static const u32 cmd19_tuning_block[16] = { + 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, + 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777, + 0xF0FFF0FF, 0x3CCCFC0F, 0xCFCC33CC, 0xEEFFEFFF, + 0xFDFFFDFF, 0xFFBFFFDF, 0xFFF7FFBB, 0xDE7B7FF7 +}; - BUG_ON(host->curr.mrq); +#define VERBOSE_COMMAND_TIMEOUTS 0 - if (deferr) { - mod_timer(&host->busclk_timer, jiffies + BUSCLK_TIMEOUT); - } else { - del_timer_sync(&host->busclk_timer); - /* Need to check clks_on again in case the busclk - * timer fired - */ - if (host->clks_on) { - clk_disable(host->clk); - clk_disable(host->pclk); - host->clks_on = 0; - } - } -} +#if IRQ_DEBUG == 1 +static char *irq_status_bits[] = { "cmdcrcfail", "datcrcfail", "cmdtimeout", + "dattimeout", "txunderrun", "rxoverrun", + "cmdrespend", "cmdsent", "dataend", NULL, + "datablkend", "cmdactive", "txactive", + "rxactive", "txhalfempty", "rxhalffull", + "txfifofull", "rxfifofull", "txfifoempty", + "rxfifoempty", "txdataavlbl", "rxdataavlbl", + "sdiointr", "progdone", "atacmdcompl", + "sdiointrope", "ccstimeout", NULL, NULL, + NULL, NULL, NULL }; -static inline int -msmsdcc_enable_clocks(struct msmsdcc_host *host) +static void +msmsdcc_print_status(struct msmsdcc_host *host, char *hdr, uint32_t status) { - int rc; + int i; - del_timer_sync(&host->busclk_timer); - - if (!host->clks_on) { - rc = clk_enable(host->pclk); - if (rc) - return rc; - rc = clk_enable(host->clk); - if (rc) { - clk_disable(host->pclk); - return rc; - } - udelay(1 + ((3 * USEC_PER_SEC) / - (host->clk_rate ? host->clk_rate : msmsdcc_fmin))); - host->clks_on = 1; + pr_debug("%s-%s ", mmc_hostname(host->mmc), hdr); + for (i = 0; i < 32; i++) { + if (status & (1 << i)) + pr_debug("%s ", irq_status_bits[i]); } - return 0; -} - -static inline unsigned int -msmsdcc_readl(struct msmsdcc_host *host, unsigned int reg) -{ - return readl(host->base + reg); -} - -static inline void -msmsdcc_writel(struct msmsdcc_host *host, u32 data, unsigned int reg) -{ - writel(data, host->base + reg); - /* 3 clk delay required! */ - udelay(1 + ((3 * USEC_PER_SEC) / - (host->clk_rate ? host->clk_rate : msmsdcc_fmin))); + pr_debug("\n"); } +#endif static void msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd, u32 c); -static void msmsdcc_reset_and_restore(struct msmsdcc_host *host) +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT +static int msmsdcc_sps_reset_ep(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep); +static int msmsdcc_sps_restore_ep(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep); +#else +static inline int msmsdcc_sps_init_ep_conn(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep, + bool is_producer) { return 0; } +static inline void msmsdcc_sps_exit_ep_conn(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep) { } +static inline int msmsdcc_sps_reset_ep(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep) { - u32 mci_clk = 0; - u32 mci_mask0 = 0; - int ret = 0; - - /* Save the controller state */ - mci_clk = readl(host->base + MMCICLOCK); - mci_mask0 = readl(host->base + MMCIMASK0); + return 0; +} +static inline int msmsdcc_sps_restore_ep(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep) +{ + return 0; +} +static inline int msmsdcc_sps_init(struct msmsdcc_host *host) { return 0; } +static inline void msmsdcc_sps_exit(struct msmsdcc_host *host) {} +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ - /* Reset the controller */ - ret = clk_reset(host->clk, CLK_RESET_ASSERT); - if (ret) - pr_err("%s: Clock assert failed at %u Hz with err %d\n", - mmc_hostname(host->mmc), host->clk_rate, ret); +/** + * Apply soft reset + * + * This function applies soft reset to SDCC core and + * BAM, DML core. + * + * This function should be called to recover from error + * conditions encountered with CMD/DATA tranfsers with card. + * + * Soft reset should only be used with SDCC controller v4. + * + * @host - Pointer to driver's host structure + * + */ +static void msmsdcc_soft_reset_and_restore(struct msmsdcc_host *host) +{ + int rc; - ret = clk_reset(host->clk, CLK_RESET_DEASSERT); - if (ret) - pr_err("%s: Clock deassert failed at %u Hz with err %d\n", - mmc_hostname(host->mmc), host->clk_rate, ret); + if (host->is_sps_mode) { + /* Reset DML first */ + msmsdcc_dml_reset(host); + /* Now reset all BAM pipes connections */ + rc = msmsdcc_sps_reset_ep(host, &host->sps.prod); + if (rc) + pr_err("%s:msmsdcc_sps_reset_ep() error=%d\n", + mmc_hostname(host->mmc), rc); + rc = msmsdcc_sps_reset_ep(host, &host->sps.cons); + if (rc) + pr_err("%s:msmsdcc_sps_reset_ep() error=%d\n", + mmc_hostname(host->mmc), rc); + } + /* + * Reset SDCC controller's DPSM (data path state machine + * and CPSM (command path state machine). + */ + mb(); + writel_relaxed(0, host->base + MMCICOMMAND); + writel_relaxed(0, host->base + MMCIDATACTRL); + mb(); - pr_info("%s: Controller has been re-initialiazed\n", + pr_debug("%s: Applied soft reset to Controller\n", mmc_hostname(host->mmc)); - /* Restore the contoller state */ - writel(host->pwr, host->base + MMCIPOWER); - writel(mci_clk, host->base + MMCICLOCK); - writel(mci_mask0, host->base + MMCIMASK0); - ret = clk_set_rate(host->clk, host->clk_rate); - if (ret) - pr_err("%s: Failed to set clk rate %u Hz (%d)\n", - mmc_hostname(host->mmc), host->clk_rate, ret); + if (host->is_sps_mode) { + /* Restore all BAM pipes connections */ + rc = msmsdcc_sps_restore_ep(host, &host->sps.prod); + if (rc) + pr_err("%s:msmsdcc_sps_restore_ep() error=%d\n", + mmc_hostname(host->mmc), rc); + rc = msmsdcc_sps_restore_ep(host, &host->sps.cons); + if (rc) + pr_err("%s:msmsdcc_sps_restore_ep() error=%d\n", + mmc_hostname(host->mmc), rc); + msmsdcc_dml_init(host); + } } -static void +static void msmsdcc_reset_and_restore(struct msmsdcc_host *host) +{ + if (host->plat->sdcc_v4_sup) { + msmsdcc_soft_reset_and_restore(host); + } else { + /* Give Clock reset (hard reset) to controller */ + u32 mci_clk = 0; + u32 mci_mask0 = 0; + int ret; + + /* Save the controller state */ + mci_clk = readl_relaxed(host->base + MMCICLOCK); + mci_mask0 = readl_relaxed(host->base + MMCIMASK0); + + mb(); + /* Reset the controller */ + ret = clk_reset(host->clk, CLK_RESET_ASSERT); + if (ret) + pr_err("%s: Clock assert failed at %u Hz" + " with err %d\n", mmc_hostname(host->mmc), + host->clk_rate, ret); + + ret = clk_reset(host->clk, CLK_RESET_DEASSERT); + if (ret) + pr_err("%s: Clock deassert failed at %u Hz" + " with err %d\n", mmc_hostname(host->mmc), + host->clk_rate, ret); + + pr_debug("%s: Controller has been reinitialized\n", + mmc_hostname(host->mmc)); + + mb(); + /* Restore the contoller state */ + writel_relaxed(host->pwr, host->base + MMCIPOWER); + writel_relaxed(mci_clk, host->base + MMCICLOCK); + writel_relaxed(mci_mask0, host->base + MMCIMASK0); + ret = clk_set_rate(host->clk, host->clk_rate); + if (ret) + pr_err("%s: Failed to set clk rate %u Hz. err %d\n", + mmc_hostname(host->mmc), + host->clk_rate, ret); + mb(); + } +} + +static int msmsdcc_request_end(struct msmsdcc_host *host, struct mmc_request *mrq) { + int retval = 0; + BUG_ON(host->curr.data); host->curr.mrq = NULL; host->curr.cmd = NULL; + del_timer(&host->req_tout_timer); + if (mrq->data) mrq->data->bytes_xfered = host->curr.data_xfered; if (mrq->cmd->error == -ETIMEDOUT) mdelay(5); -#if BUSCLK_PWRSAVE - msmsdcc_disable_clocks(host, 1); -#endif /* * Need to drop the host lock here; mmc_request_done may call * back into the driver... @@ -185,6 +289,8 @@ msmsdcc_request_end(struct msmsdcc_host *host, struct mmc_request *mrq) spin_unlock(&host->lock); mmc_request_done(host->mmc, mrq); spin_lock(&host->lock); + + return retval; } static void @@ -194,34 +300,46 @@ msmsdcc_stop_data(struct msmsdcc_host *host) host->curr.got_dataend = 0; } -uint32_t msmsdcc_fifo_addr(struct msmsdcc_host *host) +static inline uint32_t msmsdcc_fifo_addr(struct msmsdcc_host *host) +{ + return host->core_memres->start + MMCIFIFO; +} + +static inline unsigned int msmsdcc_get_min_sup_clk_rate( + struct msmsdcc_host *host); +static inline void msmsdcc_delay(struct msmsdcc_host *host) { - return host->memres->start + MMCIFIFO; + mb(); + udelay(1 + ((3 * USEC_PER_SEC) / + (host->clk_rate ? host->clk_rate : + msmsdcc_get_min_sup_clk_rate(host)))); } static inline void -msmsdcc_start_command_exec(struct msmsdcc_host *host, u32 arg, u32 c) { - msmsdcc_writel(host, arg, MMCIARGUMENT); - msmsdcc_writel(host, c, MMCICOMMAND); +msmsdcc_start_command_exec(struct msmsdcc_host *host, u32 arg, u32 c) +{ + writel_relaxed(arg, host->base + MMCIARGUMENT); + msmsdcc_delay(host); + writel_relaxed(c, host->base + MMCICOMMAND); + mb(); } static void msmsdcc_dma_exec_func(struct msm_dmov_cmd *cmd) { - struct msmsdcc_host *host = (struct msmsdcc_host *)cmd->data; + struct msmsdcc_host *host = (struct msmsdcc_host *)cmd->user; - msmsdcc_writel(host, host->cmd_timeout, MMCIDATATIMER); - msmsdcc_writel(host, (unsigned int)host->curr.xfer_size, - MMCIDATALENGTH); - msmsdcc_writel(host, host->cmd_pio_irqmask, MMCIMASK1); - msmsdcc_writel(host, host->cmd_datactrl, MMCIDATACTRL); + writel_relaxed(host->cmd_timeout, host->base + MMCIDATATIMER); + writel_relaxed((unsigned int)host->curr.xfer_size, + host->base + MMCIDATALENGTH); + msmsdcc_delay(host); /* Allow data parms to be applied */ + writel_relaxed(host->cmd_datactrl, host->base + MMCIDATACTRL); + msmsdcc_delay(host); /* Force delay prior to ADM or command */ if (host->cmd_cmd) { msmsdcc_start_command_exec(host, - (u32) host->cmd_cmd->arg, - (u32) host->cmd_c); + (u32)host->cmd_cmd->arg, (u32)host->cmd_c); } - host->dma.active = 1; } static void @@ -230,15 +348,10 @@ msmsdcc_dma_complete_tlet(unsigned long data) struct msmsdcc_host *host = (struct msmsdcc_host *)data; unsigned long flags; struct mmc_request *mrq; - struct msm_dmov_errdata err; spin_lock_irqsave(&host->lock, flags); - host->dma.active = 0; - - err = host->dma.err; mrq = host->curr.mrq; BUG_ON(!mrq); - WARN_ON(!mrq->data); if (!(host->dma.result & DMOV_RSLT_VALID)) { pr_err("msmsdcc: Invalid DataMover result\n"); @@ -247,6 +360,7 @@ msmsdcc_dma_complete_tlet(unsigned long data) if (host->dma.result & DMOV_RSLT_DONE) { host->curr.data_xfered = host->curr.xfer_size; + host->curr.xfer_remain -= host->curr.xfer_size; } else { /* Error or flush */ if (host->dma.result & DMOV_RSLT_ERROR) @@ -255,11 +369,11 @@ msmsdcc_dma_complete_tlet(unsigned long data) if (host->dma.result & DMOV_RSLT_FLUSH) pr_err("%s: DMA channel flushed (0x%.8x)\n", mmc_hostname(host->mmc), host->dma.result); - pr_err("Flush data: %.8x %.8x %.8x %.8x %.8x %.8x\n", - err.flush[0], err.flush[1], err.flush[2], - err.flush[3], err.flush[4], err.flush[5]); - + host->dma.err.flush[0], host->dma.err.flush[1], + host->dma.err.flush[2], host->dma.err.flush[3], + host->dma.err.flush[4], + host->dma.err.flush[5]); msmsdcc_reset_and_restore(host); if (!mrq->data->error) mrq->data->error = -EIO; @@ -267,6 +381,14 @@ msmsdcc_dma_complete_tlet(unsigned long data) dma_unmap_sg(mmc_dev(host->mmc), host->dma.sg, host->dma.num_ents, host->dma.dir); + if (host->curr.user_pages) { + struct scatterlist *sg = host->dma.sg; + int i; + + for (i = 0; i < host->dma.num_ents; i++, sg++) + flush_dcache_page(sg_page(sg)); + } + host->dma.sg = NULL; host->dma.busy = 0; @@ -278,17 +400,17 @@ msmsdcc_dma_complete_tlet(unsigned long data) */ msmsdcc_stop_data(host); - if (!mrq->data->error) + if (!mrq->data->error) { host->curr.data_xfered = host->curr.xfer_size; + host->curr.xfer_remain -= host->curr.xfer_size; + } if (!mrq->data->stop || mrq->cmd->error) { host->curr.mrq = NULL; host->curr.cmd = NULL; mrq->data->bytes_xfered = host->curr.data_xfered; - + del_timer(&host->req_tout_timer); spin_unlock_irqrestore(&host->lock, flags); -#if BUSCLK_PWRSAVE - msmsdcc_disable_clocks(host, 1); -#endif + mmc_request_done(host->mmc, mrq); return; } else @@ -300,6 +422,193 @@ msmsdcc_dma_complete_tlet(unsigned long data) return; } +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT +/** + * Callback notification from SPS driver + * + * This callback function gets triggered called from + * SPS driver when requested SPS data transfer is + * completed. + * + * SPS driver invokes this callback in BAM irq context so + * SDCC driver schedule a tasklet for further processing + * this callback notification at later point of time in + * tasklet context and immediately returns control back + * to SPS driver. + * + * @nofity - Pointer to sps event notify sturcture + * + */ +static void +msmsdcc_sps_complete_cb(struct sps_event_notify *notify) +{ + struct msmsdcc_host *host = + (struct msmsdcc_host *) + ((struct sps_event_notify *)notify)->user; + + host->sps.notify = *notify; + pr_debug("%s: %s: sps ev_id=%d, addr=0x%x, size=0x%x, flags=0x%x\n", + mmc_hostname(host->mmc), __func__, notify->event_id, + notify->data.transfer.iovec.addr, + notify->data.transfer.iovec.size, + notify->data.transfer.iovec.flags); + /* Schedule a tasklet for completing data transfer */ + tasklet_schedule(&host->sps.tlet); +} + +/** + * Tasklet handler for processing SPS callback event + * + * This function processing SPS event notification and + * checks if the SPS transfer is completed or not and + * then accordingly notifies status to MMC core layer. + * + * This function is called in tasklet context. + * + * @data - Pointer to sdcc driver data + * + */ +static void msmsdcc_sps_complete_tlet(unsigned long data) +{ + unsigned long flags; + int i, rc; + u32 data_xfered = 0; + struct mmc_request *mrq; + struct sps_iovec iovec; + struct sps_pipe *sps_pipe_handle; + struct msmsdcc_host *host = (struct msmsdcc_host *)data; + struct sps_event_notify *notify = &host->sps.notify; + + spin_lock_irqsave(&host->lock, flags); + if (host->sps.dir == DMA_FROM_DEVICE) + sps_pipe_handle = host->sps.prod.pipe_handle; + else + sps_pipe_handle = host->sps.cons.pipe_handle; + mrq = host->curr.mrq; + + if (!mrq) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + pr_debug("%s: %s: sps event_id=%d\n", + mmc_hostname(host->mmc), __func__, + notify->event_id); + + if (msmsdcc_is_dml_busy(host)) { + /* oops !!! this should never happen. */ + pr_err("%s: %s: Received SPS EOT event" + " but DML HW is still busy !!!\n", + mmc_hostname(host->mmc), __func__); + } + /* + * Got End of transfer event!!! Check if all of the data + * has been transferred? + */ + for (i = 0; i < host->sps.xfer_req_cnt; i++) { + rc = sps_get_iovec(sps_pipe_handle, &iovec); + if (rc) { + pr_err("%s: %s: sps_get_iovec() failed rc=%d, i=%d", + mmc_hostname(host->mmc), __func__, rc, i); + break; + } + data_xfered += iovec.size; + } + + if (data_xfered == host->curr.xfer_size) { + host->curr.data_xfered = host->curr.xfer_size; + host->curr.xfer_remain -= host->curr.xfer_size; + pr_debug("%s: Data xfer success. data_xfered=0x%x", + mmc_hostname(host->mmc), + host->curr.xfer_size); + } else { + pr_err("%s: Data xfer failed. data_xfered=0x%x," + " xfer_size=%d", mmc_hostname(host->mmc), + data_xfered, host->curr.xfer_size); + msmsdcc_reset_and_restore(host); + if (!mrq->data->error) + mrq->data->error = -EIO; + } + + /* Unmap sg buffers */ + dma_unmap_sg(mmc_dev(host->mmc), host->sps.sg, host->sps.num_ents, + host->sps.dir); + + host->sps.sg = NULL; + host->sps.busy = 0; + + if (host->curr.got_dataend || mrq->data->error) { + /* + * If we've already gotten our DATAEND / DATABLKEND + * for this request, then complete it through here. + */ + msmsdcc_stop_data(host); + + if (!mrq->data->error) { + host->curr.data_xfered = host->curr.xfer_size; + host->curr.xfer_remain -= host->curr.xfer_size; + } + if (!mrq->data->stop || mrq->cmd->error) { + host->curr.mrq = NULL; + host->curr.cmd = NULL; + mrq->data->bytes_xfered = host->curr.data_xfered; + del_timer(&host->req_tout_timer); + spin_unlock_irqrestore(&host->lock, flags); + + mmc_request_done(host->mmc, mrq); + return; + } else { + msmsdcc_start_command(host, mrq->data->stop, 0); + } + } + spin_unlock_irqrestore(&host->lock, flags); +} + +/** + * Exit from current SPS data transfer + * + * This function exits from current SPS data transfer. + * + * This function should be called when error condition + * is encountered during data transfer. + * + * @host - Pointer to sdcc host structure + * + */ +static void msmsdcc_sps_exit_curr_xfer(struct msmsdcc_host *host) +{ + struct mmc_request *mrq; + + mrq = host->curr.mrq; + BUG_ON(!mrq); + + msmsdcc_reset_and_restore(host); + if (!mrq->data->error) + mrq->data->error = -EIO; + + /* Unmap sg buffers */ + dma_unmap_sg(mmc_dev(host->mmc), host->sps.sg, host->sps.num_ents, + host->sps.dir); + + host->sps.sg = NULL; + host->sps.busy = 0; + if (host->curr.data) + msmsdcc_stop_data(host); + + if (!mrq->data->stop || mrq->cmd->error) + msmsdcc_request_end(host, mrq); + else + msmsdcc_start_command(host, mrq->data->stop, 0); + +} +#else +static inline void msmsdcc_sps_complete_cb(struct sps_event_notify *notify) { } +static inline void msmsdcc_sps_complete_tlet(unsigned long data) { } +static inline void msmsdcc_sps_exit_curr_xfer(struct msmsdcc_host *host) { } +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ + +static void msmsdcc_enable_cdr_cm_sdc4_dll(struct msmsdcc_host *host); + static void msmsdcc_dma_complete_func(struct msm_dmov_cmd *cmd, unsigned int result, @@ -316,16 +625,13 @@ msmsdcc_dma_complete_func(struct msm_dmov_cmd *cmd, tasklet_schedule(&host->dma_tlet); } -static int validate_dma(struct msmsdcc_host *host, struct mmc_data *data) +static int msmsdcc_check_dma_op_req(struct mmc_data *data) { - if (host->dma.channel == -1) - return -ENOENT; - - if ((data->blksz * data->blocks) < MCI_FIFOSIZE) - return -EINVAL; - if ((data->blksz * data->blocks) % MCI_FIFOSIZE) + if (((data->blksz * data->blocks) < MCI_FIFOSIZE) || + ((data->blksz * data->blocks) % MCI_FIFOSIZE)) return -EINVAL; - return 0; + else + return 0; } static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data) @@ -335,34 +641,32 @@ static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data) uint32_t rows; uint32_t crci; unsigned int n; - int i, rc; + int i; struct scatterlist *sg = data->sg; - rc = validate_dma(host, data); - if (rc) - return rc; + if (host->dma.channel == -1) + return -ENOENT; host->dma.sg = data->sg; host->dma.num_ents = data->sg_len; - BUG_ON(host->dma.num_ents > NR_SG); /* Prevent memory corruption */ + BUG_ON(host->dma.num_ents > NR_SG); /* Prevent memory corruption */ nc = host->dma.nc; - switch (host->pdev_id) { - case 1: - crci = MSMSDCC_CRCI_SDC1; - break; - case 2: - crci = MSMSDCC_CRCI_SDC2; - break; - case 3: - crci = MSMSDCC_CRCI_SDC3; - break; - case 4: - crci = MSMSDCC_CRCI_SDC4; - break; - default: + if (host->pdev_id == 1) + crci = DMOV_SDC1_CRCI; + else if (host->pdev_id == 2) + crci = DMOV_SDC2_CRCI; + else if (host->pdev_id == 3) + crci = DMOV_SDC3_CRCI; + else if (host->pdev_id == 4) + crci = DMOV_SDC4_CRCI; +#ifdef DMOV_SDC5_CRCI + else if (host->pdev_id == 5) + crci = DMOV_SDC5_CRCI; +#endif + else { host->dma.sg = NULL; host->dma.num_ents = 0; return -ENOENT; @@ -373,33 +677,18 @@ static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data) else host->dma.dir = DMA_TO_DEVICE; + /* host->curr.user_pages = (data->flags & MMC_DATA_USERPAGE); */ host->curr.user_pages = 0; - box = &nc->cmd[0]; - - /* location of command block must be 64 bit aligned */ - BUG_ON(host->dma.cmd_busaddr & 0x07); - - nc->cmdptr = (host->dma.cmd_busaddr >> 3) | CMD_PTR_LP; - host->dma.hdr.cmdptr = DMOV_CMD_PTR_LIST | - DMOV_CMD_ADDR(host->dma.cmdptr_busaddr); - host->dma.hdr.complete_func = msmsdcc_dma_complete_func; - - n = dma_map_sg(mmc_dev(host->mmc), host->dma.sg, - host->dma.num_ents, host->dma.dir); - if (n == 0) { - printk(KERN_ERR "%s: Unable to map in all sg elements\n", - mmc_hostname(host->mmc)); - host->dma.sg = NULL; - host->dma.num_ents = 0; - return -ENOMEM; - } - - for_each_sg(host->dma.sg, sg, n, i) { - + for (i = 0; i < host->dma.num_ents; i++) { box->cmd = CMD_MODE_BOX; - if (i == n - 1) + /* Initialize sg dma address */ + sg->dma_address = pfn_to_dma(mmc_dev(host->mmc), + page_to_pfn(sg_page(sg))) + + sg->offset; + + if (i == (host->dma.num_ents - 1)) box->cmd |= CMD_LC; rows = (sg_dma_len(sg) % MCI_FIFOSIZE) ? (sg_dma_len(sg) / MCI_FIFOSIZE) + 1 : @@ -427,32 +716,147 @@ static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data) box->cmd |= CMD_DST_CRCI(crci); } box++; + sg++; } - return 0; -} + /* location of command block must be 64 bit aligned */ + BUG_ON(host->dma.cmd_busaddr & 0x07); + + nc->cmdptr = (host->dma.cmd_busaddr >> 3) | CMD_PTR_LP; + host->dma.hdr.cmdptr = DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(host->dma.cmdptr_busaddr); + host->dma.hdr.complete_func = msmsdcc_dma_complete_func; + host->dma.hdr.crci_mask = msm_dmov_build_crci_mask(1, crci); + + n = dma_map_sg(mmc_dev(host->mmc), host->dma.sg, + host->dma.num_ents, host->dma.dir); + /* dsb inside dma_map_sg will write nc out to mem as well */ + + if (n != host->dma.num_ents) { + pr_err("%s: Unable to map in all sg elements\n", + mmc_hostname(host->mmc)); + host->dma.sg = NULL; + host->dma.num_ents = 0; + return -ENOMEM; + } -static int -snoop_cccr_abort(struct mmc_command *cmd) -{ - if ((cmd->opcode == 52) && - (cmd->arg & 0x80000000) && - (((cmd->arg >> 9) & 0x1ffff) == SDIO_CCCR_ABORT)) - return 1; return 0; } -static void -msmsdcc_start_command_deferred(struct msmsdcc_host *host, - struct mmc_command *cmd, u32 *c) +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT +/** + * Submits data transfer request to SPS driver + * + * This function make sg (scatter gather) data buffers + * DMA ready and then submits them to SPS driver for + * transfer. + * + * @host - Pointer to sdcc host structure + * @data - Pointer to mmc_data structure + * + * @return 0 if success else negative value + */ +static int msmsdcc_sps_start_xfer(struct msmsdcc_host *host, + struct mmc_data *data) { - *c |= (cmd->opcode | MCI_CPSM_ENABLE); + int rc = 0; + u32 flags; + int i; + u32 addr, len, data_cnt; + struct scatterlist *sg = data->sg; + struct sps_pipe *sps_pipe_handle; - if (cmd->flags & MMC_RSP_PRESENT) { - if (cmd->flags & MMC_RSP_136) - *c |= MCI_CPSM_LONGRSP; - *c |= MCI_CPSM_RESPONSE; - } + BUG_ON(data->sg_len > NR_SG); /* Prevent memory corruption */ + + host->sps.sg = data->sg; + host->sps.num_ents = data->sg_len; + host->sps.xfer_req_cnt = 0; + if (data->flags & MMC_DATA_READ) { + host->sps.dir = DMA_FROM_DEVICE; + sps_pipe_handle = host->sps.prod.pipe_handle; + } else { + host->sps.dir = DMA_TO_DEVICE; + sps_pipe_handle = host->sps.cons.pipe_handle; + } + + /* Make sg buffers DMA ready */ + rc = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + host->sps.dir); + + if (rc != data->sg_len) { + pr_err("%s: Unable to map in all sg elements, rc=%d\n", + mmc_hostname(host->mmc), rc); + host->sps.sg = NULL; + host->sps.num_ents = 0; + rc = -ENOMEM; + goto dma_map_err; + } + + pr_debug("%s: %s: %s: pipe=0x%x, total_xfer=0x%x, sg_len=%d\n", + mmc_hostname(host->mmc), __func__, + host->sps.dir == DMA_FROM_DEVICE ? "READ" : "WRITE", + (u32)sps_pipe_handle, host->curr.xfer_size, data->sg_len); + + for (i = 0; i < data->sg_len; i++) { + /* + * Check if this is the last buffer to transfer? + * If yes then set the INT and EOT flags. + */ + len = sg_dma_len(sg); + addr = sg_dma_address(sg); + flags = 0; + while (len > 0) { + if (len > SPS_MAX_DESC_SIZE) { + data_cnt = SPS_MAX_DESC_SIZE; + } else { + data_cnt = len; + if (i == data->sg_len - 1) + flags = SPS_IOVEC_FLAG_INT | + SPS_IOVEC_FLAG_EOT; + } + rc = sps_transfer_one(sps_pipe_handle, addr, + data_cnt, host, flags); + if (rc) { + pr_err("%s: sps_transfer_one() error! rc=%d," + " pipe=0x%x, sg=0x%x, sg_buf_no=%d\n", + mmc_hostname(host->mmc), rc, + (u32)sps_pipe_handle, (u32)sg, i); + goto dma_map_err; + } + addr += data_cnt; + len -= data_cnt; + host->sps.xfer_req_cnt++; + } + sg++; + } + goto out; + +dma_map_err: + /* unmap sg buffers */ + dma_unmap_sg(mmc_dev(host->mmc), host->sps.sg, host->sps.num_ents, + host->sps.dir); +out: + return rc; +} +#else +static int msmsdcc_sps_start_xfer(struct msmsdcc_host *host, + struct mmc_data *data) { return 0; } +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ + +static void +msmsdcc_start_command_deferred(struct msmsdcc_host *host, + struct mmc_command *cmd, u32 *c) +{ + DBG(host, "op %02x arg %08x flags %08x\n", + cmd->opcode, cmd->arg, cmd->flags); + + *c |= (cmd->opcode | MCI_CPSM_ENABLE); + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) + *c |= MCI_CPSM_LONGRSP; + *c |= MCI_CPSM_RESPONSE; + } if (/*interrupt*/0) *c |= MCI_CPSM_INTERRUPT; @@ -462,22 +866,33 @@ msmsdcc_start_command_deferred(struct msmsdcc_host *host, (cmd->opcode == 53)) *c |= MCI_CSPM_DATCMD; + /* Check if AUTO CMD19 is required or not? */ + if (((cmd->opcode == 17) || (cmd->opcode == 18)) && + host->tuning_needed) { + msmsdcc_enable_cdr_cm_sdc4_dll(host); + *c |= MCI_CSPM_AUTO_CMD19; + } + if (host->prog_scan && (cmd->opcode == 12)) { *c |= MCI_CPSM_PROGENA; - host->prog_enable = true; + host->prog_enable = 1; } if (cmd == cmd->mrq->stop) *c |= MCI_CSPM_MCIABORT; - if (snoop_cccr_abort(cmd)) - *c |= MCI_CSPM_MCIABORT; - if (host->curr.cmd != NULL) { - printk(KERN_ERR "%s: Overlapping command requests\n", - mmc_hostname(host->mmc)); + pr_err("%s: Overlapping command requests\n", + mmc_hostname(host->mmc)); } host->curr.cmd = cmd; + + /* + * Kick the software command timeout timer here. + * Timer expires in 10 secs. + */ + mod_timer(&host->req_tout_timer, + (jiffies + msecs_to_jiffies(MSM_MMC_REQ_TIMEOUT))); } static void @@ -486,6 +901,7 @@ msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data, { unsigned int datactrl, timeout; unsigned long long clks; + void __iomem *base = host->base; unsigned int pio_irqmask = 0; host->curr.data = data; @@ -498,9 +914,35 @@ msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data, datactrl = MCI_DPSM_ENABLE | (data->blksz << 4); - if (!msmsdcc_config_dma(host, data)) - datactrl |= MCI_DPSM_DMAENABLE; - else { + if (!msmsdcc_check_dma_op_req(data)) { + if (host->is_dma_mode && !msmsdcc_config_dma(host, data)) { + datactrl |= MCI_DPSM_DMAENABLE; + } else if (host->is_sps_mode) { + if (!msmsdcc_is_dml_busy(host)) { + if (!msmsdcc_sps_start_xfer(host, data)) { + /* Now kick start DML transfer */ + mb(); + msmsdcc_dml_start_xfer(host, data); + datactrl |= MCI_DPSM_DMAENABLE; + host->sps.busy = 1; + } + } else { + /* + * Can't proceed with new transfer as + * previous trasnfer is already in progress. + * There is no point of going into PIO mode + * as well. Is this a time to do kernel panic? + */ + pr_err("%s: %s: DML HW is busy!!!" + " Can't perform new SPS transfers" + " now\n", mmc_hostname(host->mmc), + __func__); + } + } + } + + /* Is data transfer in PIO mode required? */ + if (!(datactrl & MCI_DPSM_DMAENABLE)) { host->pio.sg = data->sg; host->pio.sg_len = data->sg_len; host->pio.sg_off = 0; @@ -510,43 +952,56 @@ msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data, if (host->curr.xfer_remain < MCI_FIFOSIZE) pio_irqmask |= MCI_RXDATAAVLBLMASK; } else - pio_irqmask = MCI_TXFIFOHALFEMPTYMASK; + pio_irqmask = MCI_TXFIFOHALFEMPTYMASK | + MCI_TXFIFOEMPTYMASK; } if (data->flags & MMC_DATA_READ) datactrl |= MCI_DPSM_DIRECTION; clks = (unsigned long long)data->timeout_ns * host->clk_rate; - do_div(clks, NSEC_PER_SEC); + do_div(clks, 1000000000UL); timeout = data->timeout_clks + (unsigned int)clks*2 ; - if (datactrl & MCI_DPSM_DMAENABLE) { - /* Save parameters for the exec function */ + if (host->is_dma_mode && (datactrl & MCI_DPSM_DMAENABLE)) { + /* Use ADM (Application Data Mover) HW for Data transfer */ + /* Save parameters for the dma exec function */ host->cmd_timeout = timeout; host->cmd_pio_irqmask = pio_irqmask; host->cmd_datactrl = datactrl; host->cmd_cmd = cmd; - host->dma.hdr.execute_func = msmsdcc_dma_exec_func; - host->dma.hdr.data = (void *)host; + host->dma.hdr.exec_func = msmsdcc_dma_exec_func; + host->dma.hdr.user = (void *)host; host->dma.busy = 1; + if (data->flags & MMC_DATA_WRITE) + host->prog_scan = 1; if (cmd) { msmsdcc_start_command_deferred(host, cmd, &c); host->cmd_c = c; } - msm_dmov_enqueue_cmd(host->dma.channel, &host->dma.hdr); - if (data->flags & MMC_DATA_WRITE) - host->prog_scan = true; + writel_relaxed((readl_relaxed(host->base + MMCIMASK0) & + (~(MCI_IRQ_PIO))) | host->cmd_pio_irqmask, + host->base + MMCIMASK0); + mb(); + msm_dmov_enqueue_cmd_ext(host->dma.channel, &host->dma.hdr); } else { - msmsdcc_writel(host, timeout, MMCIDATATIMER); + /* SPS-BAM mode or PIO mode */ + if (data->flags & MMC_DATA_WRITE) + host->prog_scan = 1; + writel_relaxed(timeout, base + MMCIDATATIMER); - msmsdcc_writel(host, host->curr.xfer_size, MMCIDATALENGTH); + writel_relaxed(host->curr.xfer_size, base + MMCIDATALENGTH); - msmsdcc_writel(host, pio_irqmask, MMCIMASK1); - msmsdcc_writel(host, datactrl, MMCIDATACTRL); + writel_relaxed((readl_relaxed(host->base + MMCIMASK0) & + (~(MCI_IRQ_PIO))) | pio_irqmask, + host->base + MMCIMASK0); + msmsdcc_delay(host); /* Allow parms to be applied */ + writel_relaxed(datactrl, base + MMCIDATACTRL); if (cmd) { + msmsdcc_delay(host); /* Delay between data/command */ /* Daisy-chain the command if requested */ msmsdcc_start_command(host, cmd, c); } @@ -556,11 +1011,6 @@ msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data, static void msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd, u32 c) { - if (cmd == cmd->mrq->stop) - c |= MCI_CSPM_MCIABORT; - - host->stats.cmds++; - msmsdcc_start_command_deferred(host, cmd, &c); msmsdcc_start_command_exec(host, cmd->arg, c); } @@ -570,15 +1020,28 @@ msmsdcc_data_err(struct msmsdcc_host *host, struct mmc_data *data, unsigned int status) { if (status & MCI_DATACRCFAIL) { - pr_err("%s: Data CRC error\n", mmc_hostname(host->mmc)); - pr_err("%s: opcode 0x%.8x\n", __func__, - data->mrq->cmd->opcode); - pr_err("%s: blksz %d, blocks %d\n", __func__, - data->blksz, data->blocks); - data->error = -EILSEQ; + if (!(data->mrq->cmd->opcode == MMC_BUS_TEST_W + || data->mrq->cmd->opcode == MMC_BUS_TEST_R)) { + pr_err("%s: Data CRC error\n", + mmc_hostname(host->mmc)); + pr_err("%s: opcode 0x%.8x\n", __func__, + data->mrq->cmd->opcode); + pr_err("%s: blksz %d, blocks %d\n", __func__, + data->blksz, data->blocks); + data->error = -EILSEQ; + } } else if (status & MCI_DATATIMEOUT) { - pr_err("%s: Data timeout\n", mmc_hostname(host->mmc)); - data->error = -ETIMEDOUT; + /* CRC is optional for the bus test commands, not all + * cards respond back with CRC. However controller + * waits for the CRC and times out. Hence ignore the + * data timeouts during the Bustest. + */ + if (!(data->mrq->cmd->opcode == MMC_BUS_TEST_W + || data->mrq->cmd->opcode == MMC_BUS_TEST_R)) { + pr_err("%s: Data timeout\n", + mmc_hostname(host->mmc)); + data->error = -ETIMEDOUT; + } } else if (status & MCI_RXOVERRUN) { pr_err("%s: RX overrun\n", mmc_hostname(host->mmc)); data->error = -EIO; @@ -587,23 +1050,28 @@ msmsdcc_data_err(struct msmsdcc_host *host, struct mmc_data *data, data->error = -EIO; } else { pr_err("%s: Unknown error (0x%.8x)\n", - mmc_hostname(host->mmc), status); + mmc_hostname(host->mmc), status); data->error = -EIO; } -} + /* Dummy CMD52 is not needed when CMD53 has errors */ + if (host->plat->dummy52_required && host->dummy_52_needed) + host->dummy_52_needed = 0; +} static int msmsdcc_pio_read(struct msmsdcc_host *host, char *buffer, unsigned int remain) { + void __iomem *base = host->base; uint32_t *ptr = (uint32_t *) buffer; int count = 0; if (remain % 4) remain = ((remain >> 2) + 1) << 2; - while (msmsdcc_readl(host, MMCISTATUS) & MCI_RXDATAAVLBL) { - *ptr = msmsdcc_readl(host, MMCIFIFO + (count % MCI_FIFOSIZE)); + while (readl_relaxed(base + MMCISTATUS) & MCI_RXDATAAVLBL) { + + *ptr = readl_relaxed(base + MMCIFIFO + (count % MCI_FIFOSIZE)); ptr++; count += sizeof(uint32_t); @@ -616,16 +1084,16 @@ msmsdcc_pio_read(struct msmsdcc_host *host, char *buffer, unsigned int remain) static int msmsdcc_pio_write(struct msmsdcc_host *host, char *buffer, - unsigned int remain, u32 status) + unsigned int remain) { void __iomem *base = host->base; char *ptr = buffer; + unsigned int maxcnt = MCI_FIFOHALFSIZE; - do { - unsigned int count, maxcnt, sz; + while (readl_relaxed(base + MMCISTATUS) & + (MCI_TXFIFOEMPTY | MCI_TXFIFOHALFEMPTY)) { + unsigned int count, sz; - maxcnt = status & MCI_TXFIFOEMPTY ? MCI_FIFOSIZE : - MCI_FIFOHALFSIZE; count = min(remain, maxcnt); sz = count % 4 ? (count >> 2) + 1 : (count >> 2); @@ -635,49 +1103,38 @@ msmsdcc_pio_write(struct msmsdcc_host *host, char *buffer, if (remain == 0) break; - - status = msmsdcc_readl(host, MMCISTATUS); - } while (status & MCI_TXFIFOHALFEMPTY); + } + mb(); return ptr - buffer; } -static int -msmsdcc_spin_on_status(struct msmsdcc_host *host, uint32_t mask, int maxspin) -{ - while (maxspin) { - if ((msmsdcc_readl(host, MMCISTATUS) & mask)) - return 0; - udelay(1); - --maxspin; - } - return -ETIMEDOUT; -} - static irqreturn_t msmsdcc_pio_irq(int irq, void *dev_id) { struct msmsdcc_host *host = dev_id; + void __iomem *base = host->base; uint32_t status; - status = msmsdcc_readl(host, MMCISTATUS); + status = readl_relaxed(base + MMCISTATUS); + if (((readl_relaxed(host->base + MMCIMASK0) & status) & + (MCI_IRQ_PIO)) == 0) + return IRQ_NONE; + +#if IRQ_DEBUG + msmsdcc_print_status(host, "irq1-r", status); +#endif + + spin_lock(&host->lock); do { unsigned long flags; unsigned int remain, len; char *buffer; - if (!(status & (MCI_TXFIFOHALFEMPTY | MCI_RXDATAAVLBL))) { - if (host->curr.xfer_remain == 0 || !msmsdcc_piopoll) - break; - - if (msmsdcc_spin_on_status(host, - (MCI_TXFIFOHALFEMPTY | - MCI_RXDATAAVLBL), - PIO_SPINMAX)) { - break; - } - } + if (!(status & (MCI_TXFIFOHALFEMPTY | MCI_TXFIFOEMPTY + | MCI_RXDATAAVLBL))) + break; /* Map the current scatter buffer */ local_irq_save(flags); @@ -685,11 +1142,12 @@ msmsdcc_pio_irq(int irq, void *dev_id) KM_BIO_SRC_IRQ) + host->pio.sg->offset; buffer += host->pio.sg_off; remain = host->pio.sg->length - host->pio.sg_off; + len = 0; if (status & MCI_RXACTIVE) len = msmsdcc_pio_read(host, buffer, remain); if (status & MCI_TXACTIVE) - len = msmsdcc_pio_write(host, buffer, remain, status); + len = msmsdcc_pio_write(host, buffer, remain); /* Unmap the buffer */ kunmap_atomic(buffer, KM_BIO_SRC_IRQ); @@ -700,55 +1158,114 @@ msmsdcc_pio_irq(int irq, void *dev_id) host->curr.data_xfered += len; remain -= len; - if (remain == 0) { - /* This sg page is full - do some housekeeping */ - if (status & MCI_RXACTIVE && host->curr.user_pages) - flush_dcache_page(sg_page(host->pio.sg)); + if (remain) /* Done with this page? */ + break; /* Nope */ - if (!--host->pio.sg_len) { - memset(&host->pio, 0, sizeof(host->pio)); - break; - } + if (status & MCI_RXACTIVE && host->curr.user_pages) + flush_dcache_page(sg_page(host->pio.sg)); - /* Advance to next sg */ - host->pio.sg++; - host->pio.sg_off = 0; + if (!--host->pio.sg_len) { + memset(&host->pio, 0, sizeof(host->pio)); + break; } - status = msmsdcc_readl(host, MMCISTATUS); + /* Advance to next sg */ + host->pio.sg++; + host->pio.sg_off = 0; + + status = readl_relaxed(base + MMCISTATUS); } while (1); - if (status & MCI_RXACTIVE && host->curr.xfer_remain < MCI_FIFOSIZE) - msmsdcc_writel(host, MCI_RXDATAAVLBLMASK, MMCIMASK1); + if (status & MCI_RXACTIVE && host->curr.xfer_remain < MCI_FIFOSIZE) { + writel_relaxed((readl_relaxed(host->base + MMCIMASK0) & + (~(MCI_IRQ_PIO))) | MCI_RXDATAAVLBLMASK, + host->base + MMCIMASK0); + if (!host->curr.xfer_remain) { + /* Delay needed (same port was just written) */ + msmsdcc_delay(host); + writel_relaxed((readl_relaxed(host->base + MMCIMASK0) & + (~(MCI_IRQ_PIO))) | 0, host->base + MMCIMASK0); + } + mb(); + } else if (!host->curr.xfer_remain) { + writel_relaxed((readl_relaxed(host->base + MMCIMASK0) & + (~(MCI_IRQ_PIO))) | 0, host->base + MMCIMASK0); + mb(); + } - if (!host->curr.xfer_remain) - msmsdcc_writel(host, 0, MMCIMASK1); + spin_unlock(&host->lock); return IRQ_HANDLED; } +static void +msmsdcc_request_start(struct msmsdcc_host *host, struct mmc_request *mrq); + +static void msmsdcc_wait_for_rxdata(struct msmsdcc_host *host, + struct mmc_data *data) +{ + u32 loop_cnt = 0; + + /* + * For read commands with data less than fifo size, it is possible to + * get DATAEND first and RXDATA_AVAIL might be set later because of + * synchronization delay through the asynchronous RX FIFO. Thus, for + * such cases, even after DATAEND interrupt is received software + * should poll for RXDATA_AVAIL until the requested data is read out + * of FIFO. This change is needed to get around this abnormal but + * sometimes expected behavior of SDCC3 controller. + * + * We can expect RXDATAAVAIL bit to be set after 6HCLK clock cycles + * after the data is loaded into RX FIFO. This would amount to less + * than a microsecond and thus looping for 1000 times is good enough + * for that delay. + */ + while (((int)host->curr.xfer_remain > 0) && (++loop_cnt < 1000)) { + if (readl_relaxed(host->base + MMCISTATUS) & MCI_RXDATAAVLBL) { + spin_unlock(&host->lock); + msmsdcc_pio_irq(1, host); + spin_lock(&host->lock); + } + } + if (loop_cnt == 1000) { + pr_info("%s: Timed out while polling for Rx Data\n", + mmc_hostname(host->mmc)); + data->error = -ETIMEDOUT; + msmsdcc_reset_and_restore(host); + } +} + static void msmsdcc_do_cmdirq(struct msmsdcc_host *host, uint32_t status) { struct mmc_command *cmd = host->curr.cmd; host->curr.cmd = NULL; - cmd->resp[0] = msmsdcc_readl(host, MMCIRESPONSE0); - cmd->resp[1] = msmsdcc_readl(host, MMCIRESPONSE1); - cmd->resp[2] = msmsdcc_readl(host, MMCIRESPONSE2); - cmd->resp[3] = msmsdcc_readl(host, MMCIRESPONSE3); - - if (status & MCI_CMDTIMEOUT) { + cmd->resp[0] = readl_relaxed(host->base + MMCIRESPONSE0); + cmd->resp[1] = readl_relaxed(host->base + MMCIRESPONSE1); + cmd->resp[2] = readl_relaxed(host->base + MMCIRESPONSE2); + cmd->resp[3] = readl_relaxed(host->base + MMCIRESPONSE3); + + if (status & (MCI_CMDTIMEOUT | MCI_AUTOCMD19TIMEOUT)) { +#if VERBOSE_COMMAND_TIMEOUTS + pr_err("%s: Command timeout\n", mmc_hostname(host->mmc)); +#endif cmd->error = -ETIMEDOUT; - } else if (status & MCI_CMDCRCFAIL && - cmd->flags & MMC_RSP_CRC) { + } else if ((status & MCI_CMDCRCFAIL && cmd->flags & MMC_RSP_CRC) && + !host->cmd19_tuning_in_progress) { pr_err("%s: Command CRC error\n", mmc_hostname(host->mmc)); cmd->error = -EILSEQ; } if (!cmd->data || cmd->error) { - if (host->curr.data && host->dma.sg) + if (host->curr.data && host->dma.sg && + host->is_dma_mode) msm_dmov_stop_cmd(host->dma.channel, &host->dma.hdr, 0); + else if (host->curr.data && host->sps.sg && + host->is_sps_mode){ + /* Stop current SPS transfer */ + msmsdcc_sps_exit_curr_xfer(host); + } else if (host->curr.data) { /* Non DMA */ msmsdcc_reset_and_restore(host); msmsdcc_stop_data(host); @@ -756,87 +1273,28 @@ static void msmsdcc_do_cmdirq(struct msmsdcc_host *host, uint32_t status) } else { /* host->data == NULL */ if (!cmd->error && host->prog_enable) { if (status & MCI_PROGDONE) { - host->prog_scan = false; - host->prog_enable = false; - msmsdcc_request_end(host, cmd->mrq); - } else { + host->prog_scan = 0; + host->prog_enable = 0; + msmsdcc_request_end(host, cmd->mrq); + } else host->curr.cmd = cmd; - } } else { if (host->prog_enable) { - host->prog_scan = false; - host->prog_enable = false; + host->prog_scan = 0; + host->prog_enable = 0; + } + if (cmd->data && cmd->error) { + msmsdcc_reset_and_restore(host); + if (host->plat->dummy52_required && + host->dummy_52_needed) + host->dummy_52_needed = 0; } msmsdcc_request_end(host, cmd->mrq); } } - } else if (cmd->data) + } else if (cmd->data) { if (!(cmd->data->flags & MMC_DATA_READ)) - msmsdcc_start_data(host, cmd->data, - NULL, 0); -} - -static void -msmsdcc_handle_irq_data(struct msmsdcc_host *host, u32 status, - void __iomem *base) -{ - struct mmc_data *data = host->curr.data; - - if (status & (MCI_CMDSENT | MCI_CMDRESPEND | MCI_CMDCRCFAIL | - MCI_CMDTIMEOUT | MCI_PROGDONE) && host->curr.cmd) { - msmsdcc_do_cmdirq(host, status); - } - - if (!data) - return; - - /* Check for data errors */ - if (status & (MCI_DATACRCFAIL | MCI_DATATIMEOUT | - MCI_TXUNDERRUN | MCI_RXOVERRUN)) { - msmsdcc_data_err(host, data, status); - host->curr.data_xfered = 0; - if (host->dma.sg) - msm_dmov_stop_cmd(host->dma.channel, - &host->dma.hdr, 0); - else { - msmsdcc_reset_and_restore(host); - if (host->curr.data) - msmsdcc_stop_data(host); - if (!data->stop) - msmsdcc_request_end(host, data->mrq); - else - msmsdcc_start_command(host, data->stop, 0); - } - } - - /* Check for data done */ - if (!host->curr.got_dataend && (status & MCI_DATAEND)) - host->curr.got_dataend = 1; - - /* - * If DMA is still in progress, we complete via the completion handler - */ - if (host->curr.got_dataend && !host->dma.busy) { - /* - * There appears to be an issue in the controller where - * if you request a small block transfer (< fifo size), - * you may get your DATAEND/DATABLKEND irq without the - * PIO data irq. - * - * Check to see if there is still data to be read, - * and simulate a PIO irq. - */ - if (readl(base + MMCISTATUS) & MCI_RXDATAAVLBL) - msmsdcc_pio_irq(1, host); - - msmsdcc_stop_data(host); - if (!data->error) - host->curr.data_xfered = host->curr.xfer_size; - - if (!data->stop) - msmsdcc_request_end(host, data->mrq); - else - msmsdcc_start_command(host, data->stop, 0); + msmsdcc_start_data(host, cmd->data, NULL, 0); } } @@ -844,57 +1302,197 @@ static irqreturn_t msmsdcc_irq(int irq, void *dev_id) { struct msmsdcc_host *host = dev_id; - void __iomem *base = host->base; u32 status; int ret = 0; - int cardint = 0; + int timer = 0; spin_lock(&host->lock); do { - status = msmsdcc_readl(host, MMCISTATUS); - status &= msmsdcc_readl(host, MMCIMASK0); - msmsdcc_writel(host, status, MMCICLEAR); + struct mmc_command *cmd; + struct mmc_data *data; - if (status & MCI_SDIOINTR) - status &= ~MCI_SDIOINTR; + if (timer) { + timer = 0; + msmsdcc_delay(host); + } - if (!status) + if (!host->clks_on) { + pr_debug("%s: %s: SDIO async irq received\n", + mmc_hostname(host->mmc), __func__); + host->mmc->ios.clock = host->clk_rate; + spin_unlock(&host->lock); + host->mmc->ops->set_ios(host->mmc, &host->mmc->ios); + spin_lock(&host->lock); + if (host->plat->cfg_mpm_sdiowakeup && + (host->mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ)) + wake_lock(&host->sdio_wlock); + /* only ansyc interrupt can come when clocks are off */ + writel_relaxed(MCI_SDIOINTMASK, host->base + MMCICLEAR); + } + + status = readl_relaxed(host->base + MMCISTATUS); + + if (((readl_relaxed(host->base + MMCIMASK0) & status) & + (~(MCI_IRQ_PIO))) == 0) + break; + +#if IRQ_DEBUG + msmsdcc_print_status(host, "irq0-r", status); +#endif + status &= readl_relaxed(host->base + MMCIMASK0); + writel_relaxed(status, host->base + MMCICLEAR); + mb(); +#if IRQ_DEBUG + msmsdcc_print_status(host, "irq0-p", status); +#endif +#ifdef CONFIG_MMC_MSM_SDIO_SUPPORT + if (status & MCI_SDIOINTROPE) { + if (host->sdcc_suspending) + wake_lock(&host->sdio_suspend_wlock); + mmc_signal_sdio_irq(host->mmc); + } +#endif + if ((host->plat->dummy52_required) && + (host->dummy_52_state == DUMMY_52_STATE_SENT)) { + if (status & (MCI_PROGDONE | MCI_CMDCRCFAIL | + MCI_CMDTIMEOUT)) { + if (status & MCI_CMDTIMEOUT) + pr_debug("%s: dummy CMD52 timeout\n", + mmc_hostname(host->mmc)); + if (status & MCI_CMDCRCFAIL) + pr_debug("%s: dummy CMD52 CRC failed\n", + mmc_hostname(host->mmc)); + host->dummy_52_state = DUMMY_52_STATE_NONE; + host->curr.cmd = NULL; + msmsdcc_request_start(host, host->curr.mrq); + spin_unlock(&host->lock); + return IRQ_HANDLED; + } break; + } + + data = host->curr.data; - msmsdcc_handle_irq_data(host, status, base); + /* + * Check for proper command response + */ + cmd = host->curr.cmd; + if ((status & (MCI_CMDSENT | MCI_CMDRESPEND | MCI_CMDCRCFAIL | + MCI_CMDTIMEOUT | MCI_PROGDONE | + MCI_AUTOCMD19TIMEOUT)) && host->curr.cmd) { + msmsdcc_do_cmdirq(host, status); + } + + if (data) { + /* Check for data errors */ + if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT| + MCI_TXUNDERRUN|MCI_RXOVERRUN)) { + msmsdcc_data_err(host, data, status); + host->curr.data_xfered = 0; + if (host->dma.sg && host->is_dma_mode) + msm_dmov_stop_cmd(host->dma.channel, + &host->dma.hdr, 0); + else if (host->sps.sg && host->is_sps_mode) { + /* Stop current SPS transfer */ + msmsdcc_sps_exit_curr_xfer(host); + } + else { + msmsdcc_reset_and_restore(host); + if (host->curr.data) + msmsdcc_stop_data(host); + if (!data->stop) + timer |= + msmsdcc_request_end(host, + data->mrq); + else { + msmsdcc_start_command(host, + data->stop, + 0); + timer = 1; + } + } + } - if (status & MCI_SDIOINTOPER) { - cardint = 1; - status &= ~MCI_SDIOINTOPER; + /* Check for data done */ + if (!host->curr.got_dataend && (status & MCI_DATAEND)) + host->curr.got_dataend = 1; + + if (host->curr.got_dataend) { + /* + * If DMA is still in progress, we complete + * via the completion handler + */ + if (!host->dma.busy && !host->sps.busy) { + /* + * There appears to be an issue in the + * controller where if you request a + * small block transfer (< fifo size), + * you may get your DATAEND/DATABLKEND + * irq without the PIO data irq. + * + * Check to see if theres still data + * to be read, and simulate a PIO irq. + */ + if (data->flags & MMC_DATA_READ) + msmsdcc_wait_for_rxdata(host, + data); + msmsdcc_stop_data(host); + if (!data->error) { + host->curr.data_xfered = + host->curr.xfer_size; + host->curr.xfer_remain -= + host->curr.xfer_size; + } + + if (!data->stop) + timer |= msmsdcc_request_end( + host, data->mrq); + else { + msmsdcc_start_command(host, + data->stop, 0); + timer = 1; + } + } + } } + ret = 1; } while (status); spin_unlock(&host->lock); - /* - * We have to delay handling the card interrupt as it calls - * back into the driver. - */ - if (cardint) - mmc_signal_sdio_irq(host->mmc); - return IRQ_RETVAL(ret); } +static void +msmsdcc_request_start(struct msmsdcc_host *host, struct mmc_request *mrq) +{ + if (mrq->data && mrq->data->flags & MMC_DATA_READ) { + /* Queue/read data, daisy-chain command when data starts */ + msmsdcc_start_data(host, mrq->data, mrq->cmd, 0); + } else { + msmsdcc_start_command(host, mrq->cmd, 0); + } +} + static void msmsdcc_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct msmsdcc_host *host = mmc_priv(mmc); - unsigned long flags; + unsigned long flags; - WARN_ON(host->curr.mrq != NULL); - WARN_ON(host->pwr == 0); + /* + * Get the SDIO AL client out of LPM. + */ + if (host->plat->is_sdio_al_client) + msmsdcc_sdio_al_lpm(mmc, false); spin_lock_irqsave(&host->lock, flags); - - host->stats.reqs++; + WARN(host->curr.mrq, "Request in progress\n"); + WARN(!host->pwr, "SDCC power is turned off\n"); + WARN(!host->clks_on, "SDCC clocks are turned off\n"); + WARN(host->sdcc_irq_disabled, "SDCC IRQ is disabled\n"); if (host->eject) { if (mrq->data && !(mrq->data->flags & MMC_DATA_READ)) { @@ -909,182 +1507,1160 @@ msmsdcc_request(struct mmc_host *mmc, struct mmc_request *mrq) return; } - msmsdcc_enable_clocks(host); - host->curr.mrq = mrq; - if (mrq->data && mrq->data->flags & MMC_DATA_READ) - /* Queue/read data, daisy-chain command when data starts */ - msmsdcc_start_data(host, mrq->data, mrq->cmd, 0); - else - msmsdcc_start_command(host, mrq->cmd, 0); - - if (host->cmdpoll && !msmsdcc_spin_on_status(host, - MCI_CMDRESPEND|MCI_CMDCRCFAIL|MCI_CMDTIMEOUT, - CMD_SPINMAX)) { - uint32_t status = msmsdcc_readl(host, MMCISTATUS); - msmsdcc_do_cmdirq(host, status); - msmsdcc_writel(host, - MCI_CMDRESPEND | MCI_CMDCRCFAIL | MCI_CMDTIMEOUT, - MMCICLEAR); - host->stats.cmdpoll_hits++; - } else { - host->stats.cmdpoll_misses++; + if (host->plat->dummy52_required) { + if (host->dummy_52_needed) { + host->dummy_52_state = DUMMY_52_STATE_SENT; + msmsdcc_start_command(host, &dummy52cmd, + MCI_CPSM_PROGENA); + spin_unlock_irqrestore(&host->lock, flags); + if (mrq->data && mrq->data->flags == MMC_DATA_WRITE) { + if (mrq->cmd->opcode == SD_IO_RW_EXTENDED || + mrq->cmd->opcode == 54) + host->dummy_52_needed = 1; + } else { + host->dummy_52_needed = 0; + } + return; + } + if (mrq->data && mrq->data->flags == MMC_DATA_WRITE) { + if (mrq->cmd->opcode == SD_IO_RW_EXTENDED || + mrq->cmd->opcode == 54) + host->dummy_52_needed = 1; + } } + msmsdcc_request_start(host, mrq); spin_unlock_irqrestore(&host->lock, flags); } -static void msmsdcc_setup_gpio(struct msmsdcc_host *host, bool enable) +static inline int msmsdcc_vreg_set_voltage(struct msm_mmc_reg_data *vreg, + int min_uV, int max_uV) { - struct msm_mmc_gpio_data *curr; - int i, rc = 0; - - if (!host->plat->gpio_data && host->gpio_config_status == enable) - return; + int rc = 0; - curr = host->plat->gpio_data; - for (i = 0; i < curr->size; i++) { - if (enable) { - rc = gpio_request(curr->gpio[i].no, - curr->gpio[i].name); - if (rc) { - pr_err("%s: gpio_request(%d, %s) failed %d\n", - mmc_hostname(host->mmc), - curr->gpio[i].no, - curr->gpio[i].name, rc); - goto free_gpios; - } - } else { - gpio_free(curr->gpio[i].no); + if (vreg->set_voltage_sup) { + rc = regulator_set_voltage(vreg->reg, min_uV, max_uV); + if (rc) { + pr_err("%s: regulator_set_voltage(%s) failed." + " min_uV=%d, max_uV=%d, rc=%d\n", + __func__, vreg->name, min_uV, max_uV, rc); } } - host->gpio_config_status = enable; - return; -free_gpios: - for (; i >= 0; i--) - gpio_free(curr->gpio[i].no); + return rc; } -static void -msmsdcc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +static inline int msmsdcc_vreg_set_optimum_mode(struct msm_mmc_reg_data *vreg, + int uA_load) { - struct msmsdcc_host *host = mmc_priv(mmc); - u32 clk = 0, pwr = 0; - int rc; - unsigned long flags; + int rc = 0; - spin_lock_irqsave(&host->lock, flags); + rc = regulator_set_optimum_mode(vreg->reg, uA_load); + if (rc < 0) + pr_err("%s: regulator_set_optimum_mode(reg=%s, uA_load=%d)" + " failed. rc=%d\n", __func__, vreg->name, + uA_load, rc); + else + /* regulator_set_optimum_mode() can return non zero value + * even for success case. + */ + rc = 0; - msmsdcc_enable_clocks(host); + return rc; +} - spin_unlock_irqrestore(&host->lock, flags); +static inline int msmsdcc_vreg_init_reg(struct msm_mmc_reg_data *vreg, + struct device *dev) +{ + int rc = 0; - if (ios->clock) { - if (ios->clock != host->clk_rate) { - rc = clk_set_rate(host->clk, ios->clock); - if (rc < 0) - pr_err("%s: Error setting clock rate (%d)\n", - mmc_hostname(host->mmc), rc); - else - host->clk_rate = ios->clock; - } - clk |= MCI_CLK_ENABLE; + /* check if regulator is already initialized? */ + if (vreg->reg) + goto out; + + /* Get the regulator handle */ + vreg->reg = regulator_get(dev, vreg->name); + if (IS_ERR(vreg->reg)) { + rc = PTR_ERR(vreg->reg); + pr_err("%s: regulator_get(%s) failed. rc=%d\n", + __func__, vreg->name, rc); } +out: + return rc; +} - if (ios->bus_width == MMC_BUS_WIDTH_4) - clk |= (2 << 10); /* Set WIDEBUS */ +static inline void msmsdcc_vreg_deinit_reg(struct msm_mmc_reg_data *vreg) +{ + if (vreg->reg) + regulator_put(vreg->reg); +} - if (ios->clock > 400000 && msmsdcc_pwrsave) - clk |= (1 << 9); /* PWRSAVE */ +/* This init function should be called only once for each SDCC slot */ +static int msmsdcc_vreg_init(struct msmsdcc_host *host, bool is_init) +{ + int rc = 0; + struct msm_mmc_slot_reg_data *curr_slot; + struct msm_mmc_reg_data *curr_vdd_reg, *curr_vccq_reg, *curr_vddp_reg; + struct device *dev = mmc_dev(host->mmc); - clk |= (1 << 12); /* FLOW_ENA */ - clk |= (1 << 15); /* feedback clock */ + curr_slot = host->plat->vreg_data; + if (!curr_slot) + goto out; - if (host->plat->translate_vdd) - pwr |= host->plat->translate_vdd(mmc_dev(mmc), ios->vdd); + curr_vdd_reg = curr_slot->vdd_data; + curr_vccq_reg = curr_slot->vccq_data; + curr_vddp_reg = curr_slot->vddp_data; - switch (ios->power_mode) { - case MMC_POWER_OFF: - msmsdcc_setup_gpio(host, false); - break; - case MMC_POWER_UP: - pwr |= MCI_PWR_UP; - msmsdcc_setup_gpio(host, true); - break; - case MMC_POWER_ON: - pwr |= MCI_PWR_ON; - break; - } + if (is_init) { + /* + * Get the regulator handle from voltage regulator framework + * and then try to set the voltage level for the regulator + */ + if (curr_vdd_reg) { + rc = msmsdcc_vreg_init_reg(curr_vdd_reg, dev); + if (rc) + goto out; + } + if (curr_vccq_reg) { + rc = msmsdcc_vreg_init_reg(curr_vccq_reg, dev); + if (rc) + goto vdd_reg_deinit; + } + if (curr_vddp_reg) { + rc = msmsdcc_vreg_init_reg(curr_vddp_reg, dev); + if (rc) + goto vccq_reg_deinit; + } + goto out; + } else { + /* Deregister all regulators from regulator framework */ + goto vddp_reg_deinit; + } +vddp_reg_deinit: + if (curr_vddp_reg) + msmsdcc_vreg_deinit_reg(curr_vddp_reg); +vccq_reg_deinit: + if (curr_vccq_reg) + msmsdcc_vreg_deinit_reg(curr_vccq_reg); +vdd_reg_deinit: + if (curr_vdd_reg) + msmsdcc_vreg_deinit_reg(curr_vdd_reg); +out: + return rc; +} + +static int msmsdcc_vreg_enable(struct msm_mmc_reg_data *vreg) +{ + int rc = 0; + + if (!vreg->is_enabled) { + /* Set voltage level */ + rc = msmsdcc_vreg_set_voltage(vreg, vreg->level, + vreg->level); + if (rc) + goto out; + + rc = regulator_enable(vreg->reg); + if (rc) { + pr_err("%s: regulator_enable(%s) failed. rc=%d\n", + __func__, vreg->name, rc); + goto out; + } + vreg->is_enabled = true; + } + + /* Put regulator in HPM (high power mode) */ + rc = msmsdcc_vreg_set_optimum_mode(vreg, vreg->hpm_uA); + if (rc < 0) + goto vreg_disable; + + goto out; + +vreg_disable: + regulator_disable(vreg->reg); + vreg->is_enabled = false; +out: + return rc; +} + +static int msmsdcc_vreg_disable(struct msm_mmc_reg_data *vreg) +{ + int rc = 0; + + /* Never disable regulator marked as always_on */ + if (vreg->is_enabled && !vreg->always_on) { + rc = regulator_disable(vreg->reg); + if (rc) { + pr_err("%s: regulator_disable(%s) failed. rc=%d\n", + __func__, vreg->name, rc); + goto out; + } + vreg->is_enabled = false; + + rc = msmsdcc_vreg_set_optimum_mode(vreg, 0); + if (rc < 0) + goto out; + + /* Set min. voltage level to 0 */ + rc = msmsdcc_vreg_set_voltage(vreg, 0, vreg->level); + if (rc) + goto out; + } else if (vreg->is_enabled && vreg->always_on && vreg->lpm_sup) { + /* Put always_on regulator in LPM (low power mode) */ + rc = msmsdcc_vreg_set_optimum_mode(vreg, vreg->lpm_uA); + if (rc < 0) + goto out; + } +out: + return rc; +} + +static int msmsdcc_setup_vreg(struct msmsdcc_host *host, bool enable) +{ + int rc = 0, i; + struct msm_mmc_slot_reg_data *curr_slot; + struct msm_mmc_reg_data *curr_vdd_reg, *curr_vccq_reg, *curr_vddp_reg; + struct msm_mmc_reg_data *vreg_table[3]; + + curr_slot = host->plat->vreg_data; + if (!curr_slot) + goto out; + + curr_vdd_reg = vreg_table[0] = curr_slot->vdd_data; + curr_vccq_reg = vreg_table[1] = curr_slot->vccq_data; + curr_vddp_reg = vreg_table[2] = curr_slot->vddp_data; + + for (i = 0; i < ARRAY_SIZE(vreg_table); i++) { + if (vreg_table[i]) { + if (enable) + rc = msmsdcc_vreg_enable(vreg_table[i]); + else + rc = msmsdcc_vreg_disable(vreg_table[i]); + if (rc) + goto out; + } + } +out: + return rc; +} + +static int msmsdcc_tune_vdd_pad_level(struct msmsdcc_host *host, int level) +{ + int rc = 0; + + if (host->plat->vreg_data) { + struct msm_mmc_reg_data *vddp_reg = + host->plat->vreg_data->vddp_data; + + if (vddp_reg && vddp_reg->is_enabled) + rc = msmsdcc_vreg_set_voltage(vddp_reg, level, level); + } + + return rc; +} + +static inline int msmsdcc_is_pwrsave(struct msmsdcc_host *host) +{ + if (host->clk_rate > 400000 && msmsdcc_pwrsave) + return 1; + return 0; +} + +static inline void msmsdcc_setup_clocks(struct msmsdcc_host *host, bool enable) +{ + if (enable) { + if (!IS_ERR_OR_NULL(host->dfab_pclk)) + clk_enable(host->dfab_pclk); + if (!IS_ERR(host->pclk)) + clk_enable(host->pclk); + clk_enable(host->clk); + } else { + clk_disable(host->clk); + if (!IS_ERR(host->pclk)) + clk_disable(host->pclk); + if (!IS_ERR_OR_NULL(host->dfab_pclk)) + clk_disable(host->dfab_pclk); + } +} + +static inline unsigned int msmsdcc_get_sup_clk_rate(struct msmsdcc_host *host, + unsigned int req_clk) +{ + unsigned int sel_clk = -1; + + if (host->plat->sup_clk_table && host->plat->sup_clk_cnt) { + unsigned char cnt; + + for (cnt = 0; cnt < host->plat->sup_clk_cnt; cnt++) { + if (host->plat->sup_clk_table[cnt] > req_clk) + break; + else if (host->plat->sup_clk_table[cnt] == req_clk) { + sel_clk = host->plat->sup_clk_table[cnt]; + break; + } else + sel_clk = host->plat->sup_clk_table[cnt]; + } + } else { + if ((req_clk < host->plat->msmsdcc_fmax) && + (req_clk > host->plat->msmsdcc_fmid)) + sel_clk = host->plat->msmsdcc_fmid; + else + sel_clk = req_clk; + } + + return sel_clk; +} + +static inline unsigned int msmsdcc_get_min_sup_clk_rate( + struct msmsdcc_host *host) +{ + if (host->plat->sup_clk_table && host->plat->sup_clk_cnt) + return host->plat->sup_clk_table[0]; + else + return host->plat->msmsdcc_fmin; +} + +static inline unsigned int msmsdcc_get_max_sup_clk_rate( + struct msmsdcc_host *host) +{ + if (host->plat->sup_clk_table && host->plat->sup_clk_cnt) + return host->plat->sup_clk_table[host->plat->sup_clk_cnt - 1]; + else + return host->plat->msmsdcc_fmax; +} + +static int msmsdcc_setup_gpio(struct msmsdcc_host *host, bool enable) +{ + struct msm_mmc_gpio_data *curr; + int i, rc = 0; + + curr = host->plat->pin_data->gpio_data; + for (i = 0; i < curr->size; i++) { + if (enable) { + if (curr->gpio[i].is_always_on && + curr->gpio[i].is_enabled) + continue; + rc = gpio_request(curr->gpio[i].no, + curr->gpio[i].name); + if (rc) { + pr_err("%s: gpio_request(%d, %s) failed %d\n", + mmc_hostname(host->mmc), + curr->gpio[i].no, + curr->gpio[i].name, rc); + goto free_gpios; + } + curr->gpio[i].is_enabled = true; + } else { + if (curr->gpio[i].is_always_on) + continue; + gpio_free(curr->gpio[i].no); + curr->gpio[i].is_enabled = false; + } + } + goto out; + +free_gpios: + for (; i >= 0; i--) { + gpio_free(curr->gpio[i].no); + curr->gpio[i].is_enabled = false; + } +out: + return rc; +} + +static int msmsdcc_setup_pad(struct msmsdcc_host *host, bool enable) +{ + struct msm_mmc_pad_data *curr; + int i; + + curr = host->plat->pin_data->pad_data; + for (i = 0; i < curr->drv->size; i++) { + if (enable) + msm_tlmm_set_hdrive(curr->drv->on[i].no, + curr->drv->on[i].val); + else + msm_tlmm_set_hdrive(curr->drv->off[i].no, + curr->drv->off[i].val); + } + + for (i = 0; i < curr->pull->size; i++) { + if (enable) + msm_tlmm_set_hdrive(curr->pull->on[i].no, + curr->pull->on[i].val); + else + msm_tlmm_set_hdrive(curr->pull->off[i].no, + curr->pull->off[i].val); + } + + return 0; +} + +static u32 msmsdcc_setup_pins(struct msmsdcc_host *host, bool enable) +{ + int rc = 0; + + if (!host->plat->pin_data || host->plat->pin_data->cfg_sts == enable) + return 0; + + if (host->plat->pin_data->is_gpio) + rc = msmsdcc_setup_gpio(host, enable); + else + rc = msmsdcc_setup_pad(host, enable); + + if (!rc) + host->plat->pin_data->cfg_sts = enable; + + return rc; +} + +static void msmsdcc_enable_irq_wake(struct msmsdcc_host *host) +{ + unsigned int wakeup_irq; + + wakeup_irq = (host->plat->sdiowakeup_irq) ? + host->plat->sdiowakeup_irq : + host->core_irqres->start; + + if (!host->irq_wake_enabled) { + enable_irq_wake(wakeup_irq); + host->irq_wake_enabled = true; + } +} + +static void msmsdcc_disable_irq_wake(struct msmsdcc_host *host) +{ + unsigned int wakeup_irq; + + wakeup_irq = (host->plat->sdiowakeup_irq) ? + host->plat->sdiowakeup_irq : + host->core_irqres->start; + + if (host->irq_wake_enabled) { + disable_irq_wake(wakeup_irq); + host->irq_wake_enabled = false; + } +} + +static void +msmsdcc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + u32 clk = 0, pwr = 0; + int rc; + unsigned long flags; + unsigned int clock; + + DBG(host, "ios->clock = %u\n", ios->clock); + + if (ios->clock) { + spin_lock_irqsave(&host->lock, flags); + if (!host->clks_on) { + msmsdcc_setup_clocks(host, true); + host->clks_on = 1; + if (mmc->card && mmc->card->type == MMC_TYPE_SDIO) { + if (!host->plat->sdiowakeup_irq) { + writel_relaxed(host->mci_irqenable, + host->base + MMCIMASK0); + mb(); + if (host->plat->cfg_mpm_sdiowakeup && + (mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ)) + host->plat->cfg_mpm_sdiowakeup( + mmc_dev(mmc), SDC_DAT1_DISWAKE); + msmsdcc_disable_irq_wake(host); + } else if (!(mmc->pm_flags & + MMC_PM_WAKE_SDIO_IRQ)) { + writel_relaxed(host->mci_irqenable, + host->base + MMCIMASK0); + } + } + } + spin_unlock_irqrestore(&host->lock, flags); + + clock = msmsdcc_get_sup_clk_rate(host, ios->clock); + /* + * For DDR50 mode, controller needs clock rate to be + * double than what is required on the SD card CLK pin. + */ + if (ios->timing == MMC_TIMING_UHS_DDR50) { + /* + * Make sure that we don't double the clock if + * doubled clock rate is already set + */ + if (!host->ddr_doubled_clk_rate || + (host->ddr_doubled_clk_rate && + (host->ddr_doubled_clk_rate != ios->clock))) { + host->ddr_doubled_clk_rate = + msmsdcc_get_sup_clk_rate( + host, (ios->clock * 2)); + clock = host->ddr_doubled_clk_rate; + } + } else { + host->ddr_doubled_clk_rate = 0; + } + + if (clock != host->clk_rate) { + rc = clk_set_rate(host->clk, clock); + if (rc < 0) + pr_debug("%s: failed to set clk rate %u\n", + mmc_hostname(mmc), clock); + host->clk_rate = clock; + } + /* + * give atleast 2 MCLK cycles delay for clocks + * and SDCC core to stabilize + */ + msmsdcc_delay(host); + clk |= MCI_CLK_ENABLE; + } + + if (ios->bus_width == MMC_BUS_WIDTH_8) + clk |= MCI_CLK_WIDEBUS_8; + else if (ios->bus_width == MMC_BUS_WIDTH_4) + clk |= MCI_CLK_WIDEBUS_4; + else + clk |= MCI_CLK_WIDEBUS_1; + + if (msmsdcc_is_pwrsave(host)) + clk |= MCI_CLK_PWRSAVE; + + clk |= MCI_CLK_FLOWENA; + + host->tuning_needed = 0; + /* + * Select the controller timing mode according + * to current bus speed mode + */ + if ((ios->timing == MMC_TIMING_UHS_SDR104) || + (ios->timing == MMC_TIMING_UHS_SDR50)) { + clk |= (4 << 14); + host->tuning_needed = 1; + } else if (ios->timing == MMC_TIMING_UHS_DDR50) { + clk |= (3 << 14); + } else { + clk |= (2 << 14); /* feedback clock */ + } + + /* Select free running MCLK as input clock of cm_dll_sdc4 */ + clk |= (2 << 23); + + if (host->io_pad_pwr_switch) + clk |= IO_PAD_PWR_SWITCH; + + if (host->plat->translate_vdd && !host->sdio_gpio_lpm) + pwr |= host->plat->translate_vdd(mmc_dev(mmc), ios->vdd); + else if (!host->plat->translate_vdd && !host->sdio_gpio_lpm) + pwr |= msmsdcc_setup_vreg(host, !!ios->vdd); + + switch (ios->power_mode) { + case MMC_POWER_OFF: + htc_pwrsink_set(PWRSINK_SDCARD, 0); + if (!host->sdcc_irq_disabled) { + if (host->plat->cfg_mpm_sdiowakeup) + host->plat->cfg_mpm_sdiowakeup( + mmc_dev(mmc), SDC_DAT1_DISABLE); + disable_irq(host->core_irqres->start); + host->sdcc_irq_disabled = 1; + } + msmsdcc_setup_pins(host, false); + break; + case MMC_POWER_UP: + pwr |= MCI_PWR_UP; + if (host->sdcc_irq_disabled) { + if (host->plat->cfg_mpm_sdiowakeup) + host->plat->cfg_mpm_sdiowakeup( + mmc_dev(mmc), SDC_DAT1_ENABLE); + enable_irq(host->core_irqres->start); + host->sdcc_irq_disabled = 0; + } + msmsdcc_setup_pins(host, true); + break; + case MMC_POWER_ON: + htc_pwrsink_set(PWRSINK_SDCARD, 100); + pwr |= MCI_PWR_ON; + break; + } + + spin_lock_irqsave(&host->lock, flags); + if (!host->clks_on) { + /* force the clocks to be on */ + msmsdcc_setup_clocks(host, true); + /* + * give atleast 2 MCLK cycles delay for clocks + * and SDCC core to stabilize + */ + msmsdcc_delay(host); + } + writel_relaxed(clk, host->base + MMCICLOCK); + msmsdcc_delay(host); + + if (host->pwr != pwr) { + host->pwr = pwr; + writel_relaxed(pwr, host->base + MMCIPOWER); + mb(); + } + if (!host->clks_on) { + /* force the clocks to be off */ + msmsdcc_setup_clocks(host, false); + /* + * give atleast 2 MCLK cycles delay for clocks + * and SDCC core to stabilize + */ + msmsdcc_delay(host); + } + + if (!(clk & MCI_CLK_ENABLE) && host->clks_on) { + if (mmc->card && mmc->card->type == MMC_TYPE_SDIO) { + if (!host->plat->sdiowakeup_irq) { + writel_relaxed(MCI_SDIOINTMASK, + host->base + MMCIMASK0); + mb(); + if (host->plat->cfg_mpm_sdiowakeup && + (mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ)) + host->plat->cfg_mpm_sdiowakeup( + mmc_dev(mmc), SDC_DAT1_ENWAKE); + msmsdcc_enable_irq_wake(host); + } else if (mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ) { + writel_relaxed(0, host->base + MMCIMASK0); + } else { + writel_relaxed(MCI_SDIOINTMASK, + host->base + MMCIMASK0); + } + msmsdcc_delay(host); + } + msmsdcc_setup_clocks(host, false); + host->clks_on = 0; + } + spin_unlock_irqrestore(&host->lock, flags); +} + +int msmsdcc_set_pwrsave(struct mmc_host *mmc, int pwrsave) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + u32 clk; + + clk = readl_relaxed(host->base + MMCICLOCK); + pr_debug("Changing to pwr_save=%d", pwrsave); + if (pwrsave && msmsdcc_is_pwrsave(host)) + clk |= MCI_CLK_PWRSAVE; + else + clk &= ~MCI_CLK_PWRSAVE; + writel_relaxed(clk, host->base + MMCICLOCK); + mb(); + + return 0; +} + +static int msmsdcc_get_ro(struct mmc_host *mmc) +{ + int status = -ENOSYS; + struct msmsdcc_host *host = mmc_priv(mmc); + + if (host->plat->wpswitch) { + status = host->plat->wpswitch(mmc_dev(mmc)); + } else if (host->plat->wpswitch_gpio) { + status = gpio_request(host->plat->wpswitch_gpio, + "SD_WP_Switch"); + if (status) { + pr_err("%s: %s: Failed to request GPIO %d\n", + mmc_hostname(mmc), __func__, + host->plat->wpswitch_gpio); + } else { + status = gpio_direction_input( + host->plat->wpswitch_gpio); + if (!status) { + /* + * Wait for atleast 300ms as debounce + * time for GPIO input to stabilize. + */ + msleep(300); + status = gpio_get_value_cansleep( + host->plat->wpswitch_gpio); + status ^= !host->plat->wpswitch_polarity; + } + gpio_free(host->plat->wpswitch_gpio); + } + } + + if (status < 0) + status = -ENOSYS; + pr_debug("%s: Card read-only status %d\n", __func__, status); + + return status; +} + +#ifdef CONFIG_MMC_MSM_SDIO_SUPPORT +static void msmsdcc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + unsigned long flags; + + if (enable) { + spin_lock_irqsave(&host->lock, flags); + host->mci_irqenable |= MCI_SDIOINTOPERMASK; + writel_relaxed(readl_relaxed(host->base + MMCIMASK0) | + MCI_SDIOINTOPERMASK, host->base + MMCIMASK0); + spin_unlock_irqrestore(&host->lock, flags); + } else { + host->mci_irqenable &= ~MCI_SDIOINTOPERMASK; + writel_relaxed(readl_relaxed(host->base + MMCIMASK0) & + ~MCI_SDIOINTOPERMASK, host->base + MMCIMASK0); + } + mb(); +} +#endif /* CONFIG_MMC_MSM_SDIO_SUPPORT */ + +#ifdef CONFIG_PM_RUNTIME +static int msmsdcc_enable(struct mmc_host *mmc) +{ + int rc; + struct device *dev = mmc->parent; + + if (atomic_read(&dev->power.usage_count) > 0) { + pm_runtime_get_noresume(dev); + goto out; + } + + rc = pm_runtime_get_sync(dev); + + if (rc < 0) { + pr_info("%s: %s: failed with error %d", mmc_hostname(mmc), + __func__, rc); + return rc; + } +out: + return 0; +} + +static int msmsdcc_disable(struct mmc_host *mmc, int lazy) +{ + int rc; + + if (mmc->card && mmc->card->type == MMC_TYPE_SDIO) + return -ENOTSUPP; + + rc = pm_runtime_put_sync(mmc->parent); + + if (rc < 0) + pr_info("%s: %s: failed with error %d", mmc_hostname(mmc), + __func__, rc); + return rc; +} +#else +#define msmsdcc_enable NULL +#define msmsdcc_disable NULL +#endif + +static int msmsdcc_start_signal_voltage_switch(struct mmc_host *mmc, + struct mmc_ios *ios) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + unsigned long flags; + int err = 0; + + if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) { + /* Change voltage level of VDDPX to high voltage */ + if (msmsdcc_tune_vdd_pad_level(host, 2950000)) { + pr_err("%s: %s: failed to change vddp level to %d", + mmc_hostname(mmc), __func__, 2950000); + } + goto out; + } else if (ios->signal_voltage != MMC_SIGNAL_VOLTAGE_180) { + /* invalid selection. don't do anything */ + goto out; + } + + spin_lock_irqsave(&host->lock, flags); + /* + * If we are here means voltage switch from high voltage to + * low voltage is required + */ + + /* + * Poll on MCIDATIN_3_0 and MCICMDIN bits of MCI_TEST_INPUT + * register until they become all zeros. + */ + if (readl_relaxed(host->base + MCI_TEST_INPUT) & (0xF << 1)) { + err = -EAGAIN; + pr_err("%s: %s: MCIDATIN_3_0 is still not all zeros", + mmc_hostname(mmc), __func__); + goto out_unlock; + } + + /* Stop SD CLK output. */ + writel_relaxed((readl_relaxed(host->base + MMCICLOCK) | + MCI_CLK_PWRSAVE), host->base + MMCICLOCK); + + spin_unlock_irqrestore(&host->lock, flags); + + /* + * Switch VDDPX from high voltage to low voltage + * to change the VDD of the SD IO pads. + */ + if (msmsdcc_tune_vdd_pad_level(host, 1850000)) { + pr_err("%s: %s: failed to change vddp level to %d", + mmc_hostname(mmc), __func__, 1850000); + goto out; + } + + spin_lock_irqsave(&host->lock, flags); + writel_relaxed((readl_relaxed(host->base + MMCICLOCK) | + IO_PAD_PWR_SWITCH), host->base + MMCICLOCK); + host->io_pad_pwr_switch = 1; + spin_unlock_irqrestore(&host->lock, flags); + + /* Wait 5 ms for the voltage regulater in the card to become stable. */ + usleep_range(5000, 5500); + + spin_lock_irqsave(&host->lock, flags); + /* Start SD CLK output. */ + writel_relaxed((readl_relaxed(host->base + MMCICLOCK) + & ~MCI_CLK_PWRSAVE), host->base + MMCICLOCK); + spin_unlock_irqrestore(&host->lock, flags); + + /* + * If MCIDATIN_3_0 and MCICMDIN bits of MCI_TEST_INPUT register + * don't become all ones within 1 ms then a Voltage Switch + * sequence has failed and a power cycle to the card is required. + * Otherwise Voltage Switch sequence is completed successfully. + */ + usleep_range(1000, 1500); + + spin_lock_irqsave(&host->lock, flags); + if ((readl_relaxed(host->base + MCI_TEST_INPUT) & (0xF << 1)) + != (0xF << 1)) { + pr_err("%s: %s: MCIDATIN_3_0 are still not all ones", + mmc_hostname(mmc), __func__); + err = -EAGAIN; + goto out_unlock; + } + +out_unlock: + spin_unlock_irqrestore(&host->lock, flags); +out: + return err; +} + +static int msmsdcc_config_cm_sdc4_dll_phase(struct msmsdcc_host *host, + u8 phase); +/* Initialize the DLL (Programmable Delay Line ) */ +static int msmsdcc_init_cm_sdc4_dll(struct msmsdcc_host *host) +{ + int rc = 0; + u32 wait_timeout; + + /* Write 0 to DLL_PDN bit of MCI_DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + & ~MCI_DLL_PDN), host->base + MCI_DLL_CONFIG); + + /* Write 1 to DLL_RST bit of MCI_DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + | MCI_DLL_RST), host->base + MCI_DLL_CONFIG); + + msmsdcc_delay(host); + + /* Write 0 to DLL_RST bit of MCI_DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + & ~MCI_DLL_RST), host->base + MCI_DLL_CONFIG); + + /* Initialize the phase to 0 */ + rc = msmsdcc_config_cm_sdc4_dll_phase(host, 0); + if (rc) + goto out; + + wait_timeout = 1000; + /* Wait until DLL_LOCK bit of MCI_DLL_STATUS register becomes '1' */ + while (!(readl_relaxed(host->base + MCI_DLL_STATUS) & MCI_DLL_LOCK)) { + /* max. wait for 1 sec for LOCK bit to be set */ + if (--wait_timeout == 0) { + pr_err("%s: %s: DLL failed to lock at phase: %d", + mmc_hostname(host->mmc), __func__, 0); + rc = -1; + goto out; + } + /* wait for 1ms */ + usleep_range(1000, 1500); + } +out: + return rc; +} + +/* + * Enable a CDR circuit in CM_SDC4_DLL block to enable automatic + * calibration sequence. This function should be called before + * enabling AUTO_CMD19 bit in MCI_CMD register for block read + * commands (CMD17/CMD18). + */ +static void msmsdcc_enable_cdr_cm_sdc4_dll(struct msmsdcc_host *host) +{ + /* Set CDR_EN bit to 1. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) | + MCI_CDR_EN), host->base + MCI_DLL_CONFIG); - if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) - pwr |= MCI_OD; + /* Set CDR_EXT_EN bit to 0. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + & ~MCI_CDR_EXT_EN), host->base + MCI_DLL_CONFIG); - msmsdcc_writel(host, clk, MMCICLOCK); + /* Set CK_OUT_EN bit to 0. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + & ~MCI_CK_OUT_EN), host->base + MCI_DLL_CONFIG); - if (host->pwr != pwr) { - host->pwr = pwr; - msmsdcc_writel(host, pwr, MMCIPOWER); + /* Wait until CK_OUT_EN bit of MCI_DLL_CONFIG register becomes '0' */ + while (readl_relaxed(host->base + MCI_DLL_CONFIG) & MCI_CK_OUT_EN) + ; + + /* Set CK_OUT_EN bit of MCI_DLL_CONFIG register to 1. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + | MCI_CK_OUT_EN), host->base + MCI_DLL_CONFIG); + + /* Wait until CK_OUT_EN bit of MCI_DLL_CONFIG register is 1. */ + while (!(readl_relaxed(host->base + MCI_DLL_CONFIG) & MCI_CK_OUT_EN)) + ; +} + +static int msmsdcc_config_cm_sdc4_dll_phase(struct msmsdcc_host *host, + u8 phase) +{ + int rc = 0; + u32 mclk_freq = 0; + u32 wait_timeout; + + /* Set CDR_EN bit to 0. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + & ~MCI_CDR_EN), host->base + MCI_DLL_CONFIG); + + /* Set CDR_EXT_EN bit to 1. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + | MCI_CDR_EXT_EN), host->base + MCI_DLL_CONFIG); + + /* Program the MCLK value to MCLK_FREQ bit field */ + if (host->clk_rate <= 112000000) + mclk_freq = 0; + else if (host->clk_rate <= 125000000) + mclk_freq = 1; + else if (host->clk_rate <= 137000000) + mclk_freq = 2; + else if (host->clk_rate <= 150000000) + mclk_freq = 3; + else if (host->clk_rate <= 162000000) + mclk_freq = 4; + else if (host->clk_rate <= 175000000) + mclk_freq = 5; + else if (host->clk_rate <= 187000000) + mclk_freq = 6; + else if (host->clk_rate <= 200000000) + mclk_freq = 7; + + writel_relaxed(((readl_relaxed(host->base + MCI_DLL_CONFIG) + & ~(7 << 24)) | (mclk_freq << 24)), + host->base + MCI_DLL_CONFIG); + + /* Set CK_OUT_EN bit to 0. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + & ~MCI_CK_OUT_EN), host->base + MCI_DLL_CONFIG); + + /* Set DLL_EN bit to 1. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + | MCI_DLL_EN), host->base + MCI_DLL_CONFIG); + + wait_timeout = 1000; + /* Wait until CK_OUT_EN bit of MCI_DLL_CONFIG register becomes '0' */ + while (readl_relaxed(host->base + MCI_DLL_CONFIG) & MCI_CK_OUT_EN) { + /* max. wait for 1 sec for LOCK bit for be set */ + if (--wait_timeout == 0) { + pr_err("%s: %s: Failed to set DLL phase: %d, CK_OUT_EN bit is not 0", + mmc_hostname(host->mmc), __func__, phase); + rc = -1; + goto out; + } + /* wait for 1ms */ + usleep_range(1000, 1500); } -#if BUSCLK_PWRSAVE - spin_lock_irqsave(&host->lock, flags); - msmsdcc_disable_clocks(host, 1); - spin_unlock_irqrestore(&host->lock, flags); -#endif + + /* + * Write the selected DLL clock output phase (0 ... 15) + * to CDR_SELEXT bit field of MCI_DLL_CONFIG register. + */ + writel_relaxed(((readl_relaxed(host->base + MCI_DLL_CONFIG) + & ~(0xF << 20)) | (phase << 20)), + host->base + MCI_DLL_CONFIG); + + /* Set CK_OUT_EN bit of MCI_DLL_CONFIG register to 1. */ + writel_relaxed((readl_relaxed(host->base + MCI_DLL_CONFIG) + | MCI_CK_OUT_EN), host->base + MCI_DLL_CONFIG); + + wait_timeout = 1000; + /* Wait until CK_OUT_EN bit of MCI_DLL_CONFIG register becomes '1' */ + while (!(readl_relaxed(host->base + MCI_DLL_CONFIG) & MCI_CK_OUT_EN)) { + /* max. wait for 1 sec for LOCK bit for be set */ + if (--wait_timeout == 0) { + pr_err("%s: %s: Failed to set DLL phase: %d, CK_OUT_EN bit is not 1", + mmc_hostname(host->mmc), __func__, phase); + rc = -1; + goto out; + } + /* wait for 1ms */ + usleep_range(1000, 1500); + } +out: + return rc; } -static void msmsdcc_enable_sdio_irq(struct mmc_host *mmc, int enable) +static int msmsdcc_execute_tuning(struct mmc_host *mmc) { struct msmsdcc_host *host = mmc_priv(mmc); - unsigned long flags; - u32 status; + u8 phase; + u8 *data_buf; + u8 tuned_phases[16], tuned_phase_cnt = 0; + int rc = 0; - spin_lock_irqsave(&host->lock, flags); - if (msmsdcc_sdioirq == 1) { - status = msmsdcc_readl(host, MMCIMASK0); - if (enable) - status |= MCI_SDIOINTOPERMASK; - else - status &= ~MCI_SDIOINTOPERMASK; - host->saved_irq0mask = status; - msmsdcc_writel(host, status, MMCIMASK0); + /* Tuning is only required for SDR50 & SDR104 modes */ + if (!host->tuning_needed) { + rc = 0; + goto out; } - spin_unlock_irqrestore(&host->lock, flags); + + host->cmd19_tuning_in_progress = 1; + /* + * Make sure that clock is always enabled when DLL + * tuning is in progress. Keeping PWRSAVE ON may + * turn off the clock. So let's disable the PWRSAVE + * here and re-enable it once tuning is completed. + */ + writel_relaxed((readl_relaxed(host->base + MMCICLOCK) + & ~MCI_CLK_PWRSAVE), host->base + MMCICLOCK); + /* first of all reset the tuning block */ + rc = msmsdcc_init_cm_sdc4_dll(host); + if (rc) + goto out; + + data_buf = kmalloc(64, GFP_KERNEL); + if (!data_buf) { + rc = -ENOMEM; + goto out; + } + + phase = 0; + do { + struct mmc_command cmd = {0}; + struct mmc_data data = {0}; + struct mmc_request mrq = { + .cmd = &cmd, + .data = &data + }; + struct scatterlist sg; + + /* set the phase in delay line hw block */ + rc = msmsdcc_config_cm_sdc4_dll_phase(host, phase); + if (rc) + goto kfree; + + cmd.opcode = MMC_SEND_TUNING_BLOCK; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + + data.blksz = 64; + data.blocks = 1; + data.flags = MMC_DATA_READ; + data.timeout_ns = 1000 * 1000 * 1000; /* 1 sec */ + + data.sg = &sg; + data.sg_len = 1; + sg_init_one(&sg, data_buf, 64); + memset(data_buf, 0, 64); + mmc_wait_for_req(mmc, &mrq); + + if (!cmd.error && !data.error && + !memcmp(data_buf, cmd19_tuning_block, 64)) { + /* tuning is successful with this tuning point */ + tuned_phases[tuned_phase_cnt++] = phase; + } + } while (++phase < 16); + + kfree(data_buf); + + if (tuned_phase_cnt) { + tuned_phase_cnt--; + tuned_phase_cnt = (tuned_phase_cnt * 3) / 4; + phase = tuned_phases[tuned_phase_cnt]; + /* + * Finally set the selected phase in delay + * line hw block. + */ + rc = msmsdcc_config_cm_sdc4_dll_phase(host, phase); + if (rc) + goto out; + } else { + /* tuning failed */ + rc = -EAGAIN; + pr_err("%s: %s: no tuning point found", + mmc_hostname(mmc), __func__); + } + goto out; + +kfree: + kfree(data_buf); +out: + /* re-enable PWESAVE */ + writel_relaxed((readl_relaxed(host->base + MMCICLOCK) | + MCI_CLK_PWRSAVE), host->base + MMCICLOCK); + host->cmd19_tuning_in_progress = 0; + return rc; } static const struct mmc_host_ops msmsdcc_ops = { + .enable = msmsdcc_enable, + .disable = msmsdcc_disable, .request = msmsdcc_request, .set_ios = msmsdcc_set_ios, + .get_ro = msmsdcc_get_ro, +#ifdef CONFIG_MMC_MSM_SDIO_SUPPORT .enable_sdio_irq = msmsdcc_enable_sdio_irq, +#endif + .start_signal_voltage_switch = msmsdcc_start_signal_voltage_switch, + .execute_tuning = msmsdcc_execute_tuning }; +static unsigned int +msmsdcc_slot_status(struct msmsdcc_host *host) +{ + int status; + unsigned int gpio_no = host->plat->status_gpio; + + status = gpio_request(gpio_no, "SD_HW_Detect"); + if (status) { + pr_err("%s: %s: Failed to request GPIO %d\n", + mmc_hostname(host->mmc), __func__, gpio_no); + } else { + status = gpio_direction_input(gpio_no); + if (!status) + status = !gpio_get_value_cansleep(gpio_no); + gpio_free(gpio_no); + } + return status; +} + static void msmsdcc_check_status(unsigned long data) { struct msmsdcc_host *host = (struct msmsdcc_host *)data; unsigned int status; - if (!host->plat->status) { - mmc_detect_change(host->mmc, 0); - goto out; - } - - status = host->plat->status(mmc_dev(host->mmc)); - host->eject = !status; - if (status ^ host->oldstat) { - pr_info("%s: Slot status change detected (%d -> %d)\n", - mmc_hostname(host->mmc), host->oldstat, status); - if (status) - mmc_detect_change(host->mmc, (5 * HZ) / 2); + if (host->plat->status || host->plat->status_gpio) { + if (host->plat->status) + status = host->plat->status(mmc_dev(host->mmc)); else + status = msmsdcc_slot_status(host); + + host->eject = !status; + if (status ^ host->oldstat) { + pr_info("%s: Slot status change detected (%d -> %d)\n", + mmc_hostname(host->mmc), host->oldstat, status); mmc_detect_change(host->mmc, 0); + } + host->oldstat = status; + } else { + mmc_detect_change(host->mmc, 0); } - - host->oldstat = status; - -out: - if (host->timer.function) - mod_timer(&host->timer, jiffies + HZ); } static irqreturn_t @@ -1092,28 +2668,53 @@ msmsdcc_platform_status_irq(int irq, void *dev_id) { struct msmsdcc_host *host = dev_id; - printk(KERN_DEBUG "%s: %d\n", __func__, irq); + pr_debug("%s: %d\n", __func__, irq); msmsdcc_check_status((unsigned long) host); return IRQ_HANDLED; } -static void -msmsdcc_status_notify_cb(int card_present, void *dev_id) +static irqreturn_t +msmsdcc_platform_sdiowakeup_irq(int irq, void *dev_id) { - struct msmsdcc_host *host = dev_id; + struct msmsdcc_host *host = dev_id; - printk(KERN_DEBUG "%s: card_present %d\n", mmc_hostname(host->mmc), - card_present); - msmsdcc_check_status((unsigned long) host); + pr_debug("%s: SDIO Wake up IRQ : %d\n", mmc_hostname(host->mmc), irq); + spin_lock(&host->lock); + if (!host->sdio_irq_disabled) { + disable_irq_nosync(irq); + if (host->mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ) { + wake_lock(&host->sdio_wlock); + msmsdcc_disable_irq_wake(host); + } + host->sdio_irq_disabled = 1; + } + if (host->plat->is_sdio_al_client) { + if (!host->clks_on) { + msmsdcc_setup_clocks(host, true); + host->clks_on = 1; + } + if (host->sdcc_irq_disabled) { + writel_relaxed(host->mci_irqenable, + host->base + MMCIMASK0); + mb(); + enable_irq(host->core_irqres->start); + host->sdcc_irq_disabled = 0; + } + wake_lock(&host->sdio_wlock); + } + spin_unlock(&host->lock); + + return IRQ_HANDLED; } static void -msmsdcc_busclk_expired(unsigned long _data) +msmsdcc_status_notify_cb(int card_present, void *dev_id) { - struct msmsdcc_host *host = (struct msmsdcc_host *) _data; + struct msmsdcc_host *host = dev_id; - if (host->clks_on) - msmsdcc_disable_clocks(host, 0); + pr_debug("%s: card_present %d\n", mmc_hostname(host->mmc), + card_present); + msmsdcc_check_status((unsigned long) host); } static int @@ -1143,18 +2744,534 @@ msmsdcc_init_dma(struct msmsdcc_host *host) return 0; } +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT +/** + * Allocate and Connect a SDCC peripheral's SPS endpoint + * + * This function allocates endpoint context and + * connect it with memory endpoint by calling + * appropriate SPS driver APIs. + * + * Also registers a SPS callback function with + * SPS driver + * + * This function should only be called once typically + * during driver probe. + * + * @host - Pointer to sdcc host structure + * @ep - Pointer to sps endpoint data structure + * @is_produce - 1 means Producer endpoint + * 0 means Consumer endpoint + * + * @return - 0 if successful else negative value. + * + */ +static int msmsdcc_sps_init_ep_conn(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep, + bool is_producer) +{ + int rc = 0; + struct sps_pipe *sps_pipe_handle; + struct sps_connect *sps_config = &ep->config; + struct sps_register_event *sps_event = &ep->event; + + /* Allocate endpoint context */ + sps_pipe_handle = sps_alloc_endpoint(); + if (!sps_pipe_handle) { + pr_err("%s: sps_alloc_endpoint() failed!!! is_producer=%d", + mmc_hostname(host->mmc), is_producer); + rc = -ENOMEM; + goto out; + } + + /* Get default connection configuration for an endpoint */ + rc = sps_get_config(sps_pipe_handle, sps_config); + if (rc) { + pr_err("%s: sps_get_config() failed!!! pipe_handle=0x%x," + " rc=%d", mmc_hostname(host->mmc), + (u32)sps_pipe_handle, rc); + goto get_config_err; + } + + /* Modify the default connection configuration */ + if (is_producer) { + /* + * For SDCC producer transfer, source should be + * SDCC peripheral where as destination should + * be system memory. + */ + sps_config->source = host->sps.bam_handle; + sps_config->destination = SPS_DEV_HANDLE_MEM; + /* Producer pipe will handle this connection */ + sps_config->mode = SPS_MODE_SRC; + sps_config->options = + SPS_O_AUTO_ENABLE | SPS_O_EOT | SPS_O_ACK_TRANSFERS; + } else { + /* + * For SDCC consumer transfer, source should be + * system memory where as destination should + * SDCC peripheral + */ + sps_config->source = SPS_DEV_HANDLE_MEM; + sps_config->destination = host->sps.bam_handle; + sps_config->mode = SPS_MODE_DEST; + sps_config->options = + SPS_O_AUTO_ENABLE | SPS_O_EOT | SPS_O_ACK_TRANSFERS; + } + + /* Producer pipe index */ + sps_config->src_pipe_index = host->sps.src_pipe_index; + /* Consumer pipe index */ + sps_config->dest_pipe_index = host->sps.dest_pipe_index; + /* + * This event thresold value is only significant for BAM-to-BAM + * transfer. It's ignored for BAM-to-System mode transfer. + */ + sps_config->event_thresh = 0x10; + /* + * Max. no of scatter/gather buffers that can + * be passed by block layer = 32 (NR_SG). + * Each BAM descritor needs 64 bits (8 bytes). + * One BAM descriptor is required per buffer transfer. + * So we would require total 256 (32 * 8) bytes of descriptor FIFO. + * But due to HW limitation we need to allocate atleast one extra + * descriptor memory (256 bytes + 8 bytes). But in order to be + * in power of 2, we are allocating 512 bytes of memory. + */ + sps_config->desc.size = 512; + sps_config->desc.base = dma_alloc_coherent(mmc_dev(host->mmc), + sps_config->desc.size, + &sps_config->desc.phys_base, + GFP_KERNEL); + + memset(sps_config->desc.base, 0x00, sps_config->desc.size); + + /* Establish connection between peripheral and memory endpoint */ + rc = sps_connect(sps_pipe_handle, sps_config); + if (rc) { + pr_err("%s: sps_connect() failed!!! pipe_handle=0x%x," + " rc=%d", mmc_hostname(host->mmc), + (u32)sps_pipe_handle, rc); + goto sps_connect_err; + } + + sps_event->mode = SPS_TRIGGER_CALLBACK; + sps_event->options = SPS_O_EOT; + sps_event->callback = msmsdcc_sps_complete_cb; + sps_event->xfer_done = NULL; + sps_event->user = (void *)host; + + /* Register callback event for EOT (End of transfer) event. */ + rc = sps_register_event(sps_pipe_handle, sps_event); + if (rc) { + pr_err("%s: sps_connect() failed!!! pipe_handle=0x%x," + " rc=%d", mmc_hostname(host->mmc), + (u32)sps_pipe_handle, rc); + goto reg_event_err; + } + /* Now save the sps pipe handle */ + ep->pipe_handle = sps_pipe_handle; + pr_debug("%s: %s, success !!! %s: pipe_handle=0x%x," + " desc_fifo.phys_base=0x%x\n", mmc_hostname(host->mmc), + __func__, is_producer ? "READ" : "WRITE", + (u32)sps_pipe_handle, sps_config->desc.phys_base); + goto out; + +reg_event_err: + sps_disconnect(sps_pipe_handle); +sps_connect_err: + dma_free_coherent(mmc_dev(host->mmc), + sps_config->desc.size, + sps_config->desc.base, + sps_config->desc.phys_base); +get_config_err: + sps_free_endpoint(sps_pipe_handle); +out: + return rc; +} + +/** + * Disconnect and Deallocate a SDCC peripheral's SPS endpoint + * + * This function disconnect endpoint and deallocates + * endpoint context. + * + * This function should only be called once typically + * during driver remove. + * + * @host - Pointer to sdcc host structure + * @ep - Pointer to sps endpoint data structure + * + */ +static void msmsdcc_sps_exit_ep_conn(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep) +{ + struct sps_pipe *sps_pipe_handle = ep->pipe_handle; + struct sps_connect *sps_config = &ep->config; + struct sps_register_event *sps_event = &ep->event; + + sps_event->xfer_done = NULL; + sps_event->callback = NULL; + sps_register_event(sps_pipe_handle, sps_event); + sps_disconnect(sps_pipe_handle); + dma_free_coherent(mmc_dev(host->mmc), + sps_config->desc.size, + sps_config->desc.base, + sps_config->desc.phys_base); + sps_free_endpoint(sps_pipe_handle); +} + +/** + * Reset SDCC peripheral's SPS endpoint + * + * This function disconnects an endpoint. + * + * This function should be called for reseting + * SPS endpoint when data transfer error is + * encountered during data transfer. This + * can be considered as soft reset to endpoint. + * + * This function should only be called if + * msmsdcc_sps_init() is already called. + * + * @host - Pointer to sdcc host structure + * @ep - Pointer to sps endpoint data structure + * + * @return - 0 if successful else negative value. + */ +static int msmsdcc_sps_reset_ep(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep) +{ + int rc = 0; + struct sps_pipe *sps_pipe_handle = ep->pipe_handle; + + rc = sps_disconnect(sps_pipe_handle); + if (rc) { + pr_err("%s: %s: sps_disconnect() failed!!! pipe_handle=0x%x," + " rc=%d", mmc_hostname(host->mmc), __func__, + (u32)sps_pipe_handle, rc); + goto out; + } + out: + return rc; +} + +/** + * Restore SDCC peripheral's SPS endpoint + * + * This function connects an endpoint. + * + * This function should be called for restoring + * SPS endpoint after data transfer error is + * encountered during data transfer. This + * can be considered as soft reset to endpoint. + * + * This function should only be called if + * msmsdcc_sps_reset_ep() is called before. + * + * @host - Pointer to sdcc host structure + * @ep - Pointer to sps endpoint data structure + * + * @return - 0 if successful else negative value. + */ +static int msmsdcc_sps_restore_ep(struct msmsdcc_host *host, + struct msmsdcc_sps_ep_conn_data *ep) +{ + int rc = 0; + struct sps_pipe *sps_pipe_handle = ep->pipe_handle; + struct sps_connect *sps_config = &ep->config; + struct sps_register_event *sps_event = &ep->event; + + /* Establish connection between peripheral and memory endpoint */ + rc = sps_connect(sps_pipe_handle, sps_config); + if (rc) { + pr_err("%s: %s: sps_connect() failed!!! pipe_handle=0x%x," + " rc=%d", mmc_hostname(host->mmc), __func__, + (u32)sps_pipe_handle, rc); + goto out; + } + + /* Register callback event for EOT (End of transfer) event. */ + rc = sps_register_event(sps_pipe_handle, sps_event); + if (rc) { + pr_err("%s: %s: sps_register_event() failed!!!" + " pipe_handle=0x%x, rc=%d", + mmc_hostname(host->mmc), __func__, + (u32)sps_pipe_handle, rc); + goto reg_event_err; + } + goto out; + +reg_event_err: + sps_disconnect(sps_pipe_handle); +out: + return rc; +} + +/** + * Initialize SPS HW connected with SDCC core + * + * This function register BAM HW resources with + * SPS driver and then initialize 2 SPS endpoints + * + * This function should only be called once typically + * during driver probe. + * + * @host - Pointer to sdcc host structure + * + * @return - 0 if successful else negative value. + * + */ +static int msmsdcc_sps_init(struct msmsdcc_host *host) +{ + int rc = 0; + struct sps_bam_props bam = {0}; + + host->bam_base = ioremap(host->bam_memres->start, + resource_size(host->bam_memres)); + if (!host->bam_base) { + pr_err("%s: BAM ioremap() failed!!! phys_addr=0x%x," + " size=0x%x", mmc_hostname(host->mmc), + host->bam_memres->start, + (host->bam_memres->end - + host->bam_memres->start)); + rc = -ENOMEM; + goto out; + } + + bam.phys_addr = host->bam_memres->start; + bam.virt_addr = host->bam_base; + /* + * This event thresold value is only significant for BAM-to-BAM + * transfer. It's ignored for BAM-to-System mode transfer. + */ + bam.event_threshold = 0x10; /* Pipe event threshold */ + /* + * This threshold controls when the BAM publish + * the descriptor size on the sideband interface. + * SPS HW will only be used when + * data transfer size > MCI_FIFOSIZE (64 bytes). + * PIO mode will be used when + * data transfer size < MCI_FIFOSIZE (64 bytes). + * So set this thresold value to 64 bytes. + */ + bam.summing_threshold = 64; + /* SPS driver wll handle the SDCC BAM IRQ */ + bam.irq = (u32)host->bam_irqres->start; + bam.manage = SPS_BAM_MGR_LOCAL; + + pr_info("%s: bam physical base=0x%x\n", mmc_hostname(host->mmc), + (u32)bam.phys_addr); + pr_info("%s: bam virtual base=0x%x\n", mmc_hostname(host->mmc), + (u32)bam.virt_addr); + + /* Register SDCC Peripheral BAM device to SPS driver */ + rc = sps_register_bam_device(&bam, &host->sps.bam_handle); + if (rc) { + pr_err("%s: sps_register_bam_device() failed!!! err=%d", + mmc_hostname(host->mmc), rc); + goto reg_bam_err; + } + pr_info("%s: BAM device registered. bam_handle=0x%x", + mmc_hostname(host->mmc), host->sps.bam_handle); + + host->sps.src_pipe_index = SPS_SDCC_PRODUCER_PIPE_INDEX; + host->sps.dest_pipe_index = SPS_SDCC_CONSUMER_PIPE_INDEX; + + rc = msmsdcc_sps_init_ep_conn(host, &host->sps.prod, + SPS_PROD_PERIPHERAL); + if (rc) + goto sps_reset_err; + rc = msmsdcc_sps_init_ep_conn(host, &host->sps.cons, + SPS_CONS_PERIPHERAL); + if (rc) + goto cons_conn_err; + + pr_info("%s: Qualcomm MSM SDCC-BAM at 0x%016llx irq %d\n", + mmc_hostname(host->mmc), + (unsigned long long)host->bam_memres->start, + (unsigned int)host->bam_irqres->start); + goto out; + +cons_conn_err: + msmsdcc_sps_exit_ep_conn(host, &host->sps.prod); +sps_reset_err: + sps_deregister_bam_device(host->sps.bam_handle); +reg_bam_err: + iounmap(host->bam_base); +out: + return rc; +} + +/** + * De-initialize SPS HW connected with SDCC core + * + * This function deinitialize SPS endpoints and then + * deregisters BAM resources from SPS driver. + * + * This function should only be called once typically + * during driver remove. + * + * @host - Pointer to sdcc host structure + * + */ +static void msmsdcc_sps_exit(struct msmsdcc_host *host) +{ + msmsdcc_sps_exit_ep_conn(host, &host->sps.cons); + msmsdcc_sps_exit_ep_conn(host, &host->sps.prod); + sps_deregister_bam_device(host->sps.bam_handle); + iounmap(host->bam_base); +} +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ + +static ssize_t +show_polling(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct msmsdcc_host *host = mmc_priv(mmc); + int poll; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + poll = !!(mmc->caps & MMC_CAP_NEEDS_POLL); + spin_unlock_irqrestore(&host->lock, flags); + + return snprintf(buf, PAGE_SIZE, "%d\n", poll); +} + +static ssize_t +set_polling(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct msmsdcc_host *host = mmc_priv(mmc); + int value; + unsigned long flags; + + sscanf(buf, "%d", &value); + + spin_lock_irqsave(&host->lock, flags); + if (value) { + mmc->caps |= MMC_CAP_NEEDS_POLL; + mmc_detect_change(host->mmc, 0); + } else { + mmc->caps &= ~MMC_CAP_NEEDS_POLL; + } +#ifdef CONFIG_HAS_EARLYSUSPEND + host->polling_enabled = mmc->caps & MMC_CAP_NEEDS_POLL; +#endif + spin_unlock_irqrestore(&host->lock, flags); + return count; +} + +static DEVICE_ATTR(polling, S_IRUGO | S_IWUSR, + show_polling, set_polling); +static struct attribute *dev_attrs[] = { + &dev_attr_polling.attr, + NULL, +}; +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void msmsdcc_early_suspend(struct early_suspend *h) +{ + struct msmsdcc_host *host = + container_of(h, struct msmsdcc_host, early_suspend); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + host->polling_enabled = host->mmc->caps & MMC_CAP_NEEDS_POLL; + host->mmc->caps &= ~MMC_CAP_NEEDS_POLL; + spin_unlock_irqrestore(&host->lock, flags); +}; +static void msmsdcc_late_resume(struct early_suspend *h) +{ + struct msmsdcc_host *host = + container_of(h, struct msmsdcc_host, early_suspend); + unsigned long flags; + + if (host->polling_enabled) { + spin_lock_irqsave(&host->lock, flags); + host->mmc->caps |= MMC_CAP_NEEDS_POLL; + mmc_detect_change(host->mmc, 0); + spin_unlock_irqrestore(&host->lock, flags); + } +}; +#endif + +static void msmsdcc_req_tout_timer_hdlr(unsigned long data) +{ + struct msmsdcc_host *host = (struct msmsdcc_host *)data; + struct mmc_request *mrq; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if ((host->plat->dummy52_required) && + (host->dummy_52_state == DUMMY_52_STATE_SENT)) { + pr_info("%s: %s: dummy CMD52 timeout\n", + mmc_hostname(host->mmc), __func__); + host->dummy_52_state = DUMMY_52_STATE_NONE; + } + + mrq = host->curr.mrq; + + if (mrq && mrq->cmd) { + pr_info("%s: %s CMD%d\n", mmc_hostname(host->mmc), + __func__, mrq->cmd->opcode); + if (!mrq->cmd->error) + mrq->cmd->error = -ETIMEDOUT; + if (host->plat->dummy52_required && host->dummy_52_needed) + host->dummy_52_needed = 0; + if (host->curr.data) { + pr_info("%s: %s Request timeout\n", + mmc_hostname(host->mmc), __func__); + if (mrq->data && !mrq->data->error) + mrq->data->error = -ETIMEDOUT; + host->curr.data_xfered = 0; + if (host->dma.sg && host->is_dma_mode) { + msm_dmov_stop_cmd(host->dma.channel, + &host->dma.hdr, 0); + } else if (host->sps.sg && host->is_sps_mode) { + /* Stop current SPS transfer */ + msmsdcc_sps_exit_curr_xfer(host); + } else { + msmsdcc_reset_and_restore(host); + msmsdcc_stop_data(host); + if (mrq->data && mrq->data->stop) + msmsdcc_start_command(host, + mrq->data->stop, 0); + else + msmsdcc_request_end(host, mrq); + } + } else { + if (host->prog_enable) { + host->prog_scan = 0; + host->prog_enable = 0; + } + msmsdcc_reset_and_restore(host); + msmsdcc_request_end(host, mrq); + } + } + spin_unlock_irqrestore(&host->lock, flags); +} + static int msmsdcc_probe(struct platform_device *pdev) { - struct msm_mmc_platform_data *plat = pdev->dev.platform_data; + struct mmc_platform_data *plat = pdev->dev.platform_data; struct msmsdcc_host *host; struct mmc_host *mmc; - struct resource *cmd_irqres = NULL; - struct resource *pio_irqres = NULL; - struct resource *stat_irqres = NULL; - struct resource *memres = NULL; + unsigned long flags; + struct resource *core_irqres = NULL; + struct resource *bam_irqres = NULL; + struct resource *core_memres = NULL; + struct resource *dml_memres = NULL; + struct resource *bam_memres = NULL; struct resource *dmares = NULL; int ret; + int i; /* must have platform data */ if (!plat) { @@ -1163,32 +3280,60 @@ msmsdcc_probe(struct platform_device *pdev) goto out; } - if (pdev->id < 1 || pdev->id > 4) + if (pdev->id < 1 || pdev->id > 5) return -EINVAL; + if (plat->is_sdio_al_client) + if (!plat->sdio_lpm_gpio_setup || !plat->sdiowakeup_irq) + return -EINVAL; + if (pdev->resource == NULL || pdev->num_resources < 2) { pr_err("%s: Invalid resource\n", __func__); return -ENXIO; } - memres = platform_get_resource(pdev, IORESOURCE_MEM, 0); - dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); - cmd_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, - "cmd_irq"); - pio_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, - "pio_irq"); - stat_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, - "status_irq"); + for (i = 0; i < pdev->num_resources; i++) { + if (pdev->resource[i].flags & IORESOURCE_MEM) { + if (!strcmp(pdev->resource[i].name, + "sdcc_dml_addr")) + dml_memres = &pdev->resource[i]; + else if (!strcmp(pdev->resource[i].name, + "sdcc_bam_addr")) + bam_memres = &pdev->resource[i]; + else + core_memres = &pdev->resource[i]; - if (!cmd_irqres || !pio_irqres || !memres) { - pr_err("%s: Invalid resource\n", __func__); + } + if (pdev->resource[i].flags & IORESOURCE_IRQ) { + if (!strcmp(pdev->resource[i].name, + "sdcc_bam_irq")) + bam_irqres = &pdev->resource[i]; + else + core_irqres = &pdev->resource[i]; + } + if (pdev->resource[i].flags & IORESOURCE_DMA) + dmares = &pdev->resource[i]; + } + + if (!core_irqres || !core_memres) { + pr_err("%s: Invalid sdcc core resource\n", __func__); return -ENXIO; } /* - * Setup our host structure + * Both BAM and DML memory resource should be preset. + * BAM IRQ resource should also be present. */ + if ((bam_memres && !dml_memres) || + (!bam_memres && dml_memres) || + ((bam_memres && dml_memres) && !bam_irqres)) { + pr_err("%s: Invalid sdcc BAM/DML resource\n", __func__); + return -ENXIO; + } + /* + * Setup our host structure + */ mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev); if (!mmc) { ret = -ENOMEM; @@ -1200,268 +3345,851 @@ msmsdcc_probe(struct platform_device *pdev) host->plat = plat; host->mmc = mmc; host->curr.cmd = NULL; + if (bam_memres && dml_memres && bam_irqres) + host->is_sps_mode = 1; + else if (dmares) + host->is_dma_mode = 1; - host->cmdpoll = 1; - - host->base = ioremap(memres->start, PAGE_SIZE); + host->base = ioremap(core_memres->start, + resource_size(core_memres)); if (!host->base) { ret = -ENOMEM; - goto out; + goto host_free; } - host->cmd_irqres = cmd_irqres; - host->pio_irqres = pio_irqres; - host->memres = memres; + host->core_irqres = core_irqres; + host->bam_irqres = bam_irqres; + host->core_memres = core_memres; + host->dml_memres = dml_memres; + host->bam_memres = bam_memres; host->dmares = dmares; spin_lock_init(&host->lock); +#ifdef CONFIG_MMC_EMBEDDED_SDIO + if (plat->embedded_sdio) + mmc_set_embedded_sdio_data(mmc, + &plat->embedded_sdio->cis, + &plat->embedded_sdio->cccr, + plat->embedded_sdio->funcs, + plat->embedded_sdio->num_funcs); +#endif + tasklet_init(&host->dma_tlet, msmsdcc_dma_complete_tlet, (unsigned long)host); + tasklet_init(&host->sps.tlet, msmsdcc_sps_complete_tlet, + (unsigned long)host); + if (host->is_dma_mode) { + /* Setup DMA */ + ret = msmsdcc_init_dma(host); + if (ret) + goto ioremap_free; + } else { + host->dma.channel = -1; + } + /* - * Setup DMA + * Setup SDCC clock if derived from Dayatona + * fabric core clock. */ - msmsdcc_init_dma(host); + if (plat->pclk_src_dfab) { + host->dfab_pclk = clk_get(&pdev->dev, "dfab_sdc_clk"); + if (!IS_ERR(host->dfab_pclk)) { + /* Set the clock rate to 64MHz for max. performance */ + ret = clk_set_rate(host->dfab_pclk, 64000000); + if (ret) + goto dfab_pclk_put; + ret = clk_enable(host->dfab_pclk); + if (ret) + goto dfab_pclk_put; + } else + goto dma_free; + } - /* Get our clocks */ + /* + * Setup main peripheral bus clock + */ host->pclk = clk_get(&pdev->dev, "sdc_pclk"); - if (IS_ERR(host->pclk)) { - ret = PTR_ERR(host->pclk); - goto host_free; + if (!IS_ERR(host->pclk)) { + ret = clk_enable(host->pclk); + if (ret) + goto pclk_put; + + host->pclk_rate = clk_get_rate(host->pclk); } + /* + * Setup SDC MMC clock + */ host->clk = clk_get(&pdev->dev, "sdc_clk"); if (IS_ERR(host->clk)) { ret = PTR_ERR(host->clk); - goto pclk_put; + goto pclk_disable; + } + + ret = clk_set_rate(host->clk, msmsdcc_get_min_sup_clk_rate(host)); + if (ret) { + pr_err("%s: Clock rate set failed (%d)\n", __func__, ret); + goto clk_put; } - /* Enable clocks */ - ret = msmsdcc_enable_clocks(host); + ret = clk_enable(host->clk); if (ret) goto clk_put; - ret = clk_set_rate(host->clk, msmsdcc_fmin); + host->clk_rate = clk_get_rate(host->clk); + + host->clks_on = 1; + + ret = msmsdcc_vreg_init(host, true); if (ret) { - pr_err("%s: Clock rate set failed (%d)\n", __func__, ret); + pr_err("%s: msmsdcc_vreg_init() failed (%d)\n", __func__, ret); goto clk_disable; } - host->pclk_rate = clk_get_rate(host->pclk); - host->clk_rate = clk_get_rate(host->clk); + + /* Clocks has to be running before accessing SPS/DML HW blocks */ + if (host->is_sps_mode) { + /* Initialize SPS */ + ret = msmsdcc_sps_init(host); + if (ret) + goto vreg_deinit; + /* Initialize DML */ + ret = msmsdcc_dml_init(host); + if (ret) + goto sps_exit; + } /* * Setup MMC host structure */ mmc->ops = &msmsdcc_ops; - mmc->f_min = msmsdcc_fmin; - mmc->f_max = msmsdcc_fmax; + mmc->f_min = msmsdcc_get_min_sup_clk_rate(host); + mmc->f_max = msmsdcc_get_max_sup_clk_rate(host); mmc->ocr_avail = plat->ocr_mask; + mmc->pm_caps |= MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ; + mmc->caps |= plat->mmc_bus_width; - if (msmsdcc_4bit) - mmc->caps |= MMC_CAP_4_BIT_DATA; - if (msmsdcc_sdioirq) - mmc->caps |= MMC_CAP_SDIO_IRQ; mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; + mmc->caps |= plat->uhs_caps; + /* + * XPC controls the maximum current in the default speed mode of SDXC + * card. XPC=0 means 100mA (max.) but speed class is not supported. + * XPC=1 means 150mA (max.) and speed class is supported. + */ + if (plat->xpc_cap) + mmc->caps |= (MMC_CAP_SET_XPC_330 | MMC_CAP_SET_XPC_300 | + MMC_CAP_SET_XPC_180); + + if (plat->nonremovable) + mmc->caps |= MMC_CAP_NONREMOVABLE; +#ifdef CONFIG_MMC_MSM_SDIO_SUPPORT + mmc->caps |= MMC_CAP_SDIO_IRQ; +#endif + + if (plat->is_sdio_al_client) + mmc->pm_flags |= MMC_PM_IGNORE_PM_NOTIFY; mmc->max_segs = NR_SG; mmc->max_blk_size = 4096; /* MCI_DATA_CTL BLOCKSIZE up to 4096 */ - mmc->max_blk_count = 65536; + mmc->max_blk_count = 65535; mmc->max_req_size = 33554432; /* MCI_DATA_LENGTH is 25 bits */ mmc->max_seg_size = mmc->max_req_size; - msmsdcc_writel(host, 0, MMCIMASK0); - msmsdcc_writel(host, 0x5e007ff, MMCICLEAR); + writel_relaxed(0, host->base + MMCIMASK0); + writel_relaxed(MCI_CLEAR_STATIC_MASK, host->base + MMCICLEAR); + + /* Delay needed (MMCIMASK0 was just written above) */ + msmsdcc_delay(host); + writel_relaxed(MCI_IRQENABLE, host->base + MMCIMASK0); + mb(); + host->mci_irqenable = MCI_IRQENABLE; + + ret = request_irq(core_irqres->start, msmsdcc_irq, IRQF_SHARED, + DRIVER_NAME " (cmd)", host); + if (ret) + goto dml_exit; - msmsdcc_writel(host, MCI_IRQENABLE, MMCIMASK0); - host->saved_irq0mask = MCI_IRQENABLE; + ret = request_irq(core_irqres->start, msmsdcc_pio_irq, IRQF_SHARED, + DRIVER_NAME " (pio)", host); + if (ret) + goto irq_free; /* - * Setup card detect change + * Enable SDCC IRQ only when host is powered on. Otherwise, this + * IRQ is un-necessarily being monitored by MPM (Modem power + * management block) during idle-power collapse. The MPM will be + * configured to monitor the DATA1 GPIO line with level-low trigger + * and thus depending on the GPIO status, it prevents TCXO shutdown + * during idle-power collapse. */ + disable_irq(core_irqres->start); + host->sdcc_irq_disabled = 1; + + if (plat->sdiowakeup_irq) { + wake_lock_init(&host->sdio_wlock, WAKE_LOCK_SUSPEND, + mmc_hostname(mmc)); + ret = request_irq(plat->sdiowakeup_irq, + msmsdcc_platform_sdiowakeup_irq, + IRQF_SHARED | IRQF_TRIGGER_LOW, + DRIVER_NAME "sdiowakeup", host); + if (ret) { + pr_err("Unable to get sdio wakeup IRQ %d (%d)\n", + plat->sdiowakeup_irq, ret); + goto pio_irq_free; + } else { + spin_lock_irqsave(&host->lock, flags); + if (!host->sdio_irq_disabled) { + disable_irq_nosync(plat->sdiowakeup_irq); + host->sdio_irq_disabled = 1; + } + spin_unlock_irqrestore(&host->lock, flags); + } + } + + if (plat->cfg_mpm_sdiowakeup) { + wake_lock_init(&host->sdio_wlock, WAKE_LOCK_SUSPEND, + mmc_hostname(mmc)); + } - memset(&host->timer, 0, sizeof(host->timer)); + wake_lock_init(&host->sdio_suspend_wlock, WAKE_LOCK_SUSPEND, + mmc_hostname(mmc)); + /* + * Setup card detect change + */ - if (stat_irqres && !(stat_irqres->flags & IORESOURCE_DISABLED)) { - unsigned long irqflags = IRQF_SHARED | - (stat_irqres->flags & IRQF_TRIGGER_MASK); + if (plat->status || plat->status_gpio) { + if (plat->status) + host->oldstat = plat->status(mmc_dev(host->mmc)); + else + host->oldstat = msmsdcc_slot_status(host); + host->eject = !host->oldstat; + } - host->stat_irq = stat_irqres->start; - ret = request_irq(host->stat_irq, + if (plat->status_irq) { + ret = request_threaded_irq(plat->status_irq, NULL, msmsdcc_platform_status_irq, - irqflags, + plat->irq_flags, DRIVER_NAME " (slot)", host); if (ret) { - pr_err("%s: Unable to get slot IRQ %d (%d)\n", - mmc_hostname(mmc), host->stat_irq, ret); - goto clk_disable; + pr_err("Unable to get slot IRQ %d (%d)\n", + plat->status_irq, ret); + goto sdiowakeup_irq_free; } } else if (plat->register_status_notify) { plat->register_status_notify(msmsdcc_status_notify_cb, host); } else if (!plat->status) pr_err("%s: No card detect facilities available\n", mmc_hostname(mmc)); - else { - init_timer(&host->timer); - host->timer.data = (unsigned long)host; - host->timer.function = msmsdcc_check_status; - host->timer.expires = jiffies + HZ; - add_timer(&host->timer); - } - - if (plat->status) { - host->oldstat = host->plat->status(mmc_dev(host->mmc)); - host->eject = !host->oldstat; - } - init_timer(&host->busclk_timer); - host->busclk_timer.data = (unsigned long) host; - host->busclk_timer.function = msmsdcc_busclk_expired; + mmc_set_drvdata(pdev, mmc); - ret = request_irq(cmd_irqres->start, msmsdcc_irq, IRQF_SHARED, - DRIVER_NAME " (cmd)", host); - if (ret) - goto stat_irq_free; + ret = pm_runtime_set_active(&(pdev)->dev); + if (ret < 0) + pr_info("%s: %s: failed with error %d", mmc_hostname(mmc), + __func__, ret); + /* + * There is no notion of suspend/resume for SD/MMC/SDIO + * cards. So host can be suspended/resumed with out + * worrying about its children. + */ + pm_suspend_ignore_children(&(pdev)->dev, true); - ret = request_irq(pio_irqres->start, msmsdcc_pio_irq, IRQF_SHARED, - DRIVER_NAME " (pio)", host); - if (ret) - goto cmd_irq_free; + /* + * MMC/SD/SDIO bus suspend/resume operations are defined + * only for the slots that will be used for non-removable + * media or for all slots when CONFIG_MMC_UNSAFE_RESUME is + * defined. Otherwise, they simply become card removal and + * insertion events during suspend and resume respectively. + * Hence, enable run-time PM only for slots for which bus + * suspend/resume operations are defined. + */ +#ifdef CONFIG_MMC_UNSAFE_RESUME + /* + * If this capability is set, MMC core will enable/disable host + * for every claim/release operation on a host. We use this + * notification to increment/decrement runtime pm usage count. + */ + mmc->caps |= MMC_CAP_DISABLE; + pm_runtime_enable(&(pdev)->dev); +#else + if (mmc->caps & MMC_CAP_NONREMOVABLE) { + mmc->caps |= MMC_CAP_DISABLE; + pm_runtime_enable(&(pdev)->dev); + } +#endif + setup_timer(&host->req_tout_timer, msmsdcc_req_tout_timer_hdlr, + (unsigned long)host); - mmc_set_drvdata(pdev, mmc); mmc_add_host(mmc); - pr_info("%s: Qualcomm MSM SDCC at 0x%016llx irq %d,%d dma %d\n", - mmc_hostname(mmc), (unsigned long long)memres->start, - (unsigned int) cmd_irqres->start, - (unsigned int) host->stat_irq, host->dma.channel); +#ifdef CONFIG_HAS_EARLYSUSPEND + host->early_suspend.suspend = msmsdcc_early_suspend; + host->early_suspend.resume = msmsdcc_late_resume; + host->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + register_early_suspend(&host->early_suspend); +#endif + + pr_info("%s: Qualcomm MSM SDCC-core at 0x%016llx irq %d,%d dma %d\n", + mmc_hostname(mmc), (unsigned long long)core_memres->start, + (unsigned int) core_irqres->start, + (unsigned int) plat->status_irq, host->dma.channel); + + pr_info("%s: 8 bit data mode %s\n", mmc_hostname(mmc), + (mmc->caps & MMC_CAP_8_BIT_DATA ? "enabled" : "disabled")); pr_info("%s: 4 bit data mode %s\n", mmc_hostname(mmc), - (mmc->caps & MMC_CAP_4_BIT_DATA ? "enabled" : "disabled")); + (mmc->caps & MMC_CAP_4_BIT_DATA ? "enabled" : "disabled")); + pr_info("%s: polling status mode %s\n", mmc_hostname(mmc), + (mmc->caps & MMC_CAP_NEEDS_POLL ? "enabled" : "disabled")); pr_info("%s: MMC clock %u -> %u Hz, PCLK %u Hz\n", - mmc_hostname(mmc), msmsdcc_fmin, msmsdcc_fmax, host->pclk_rate); - pr_info("%s: Slot eject status = %d\n", mmc_hostname(mmc), host->eject); + mmc_hostname(mmc), msmsdcc_get_min_sup_clk_rate(host), + msmsdcc_get_max_sup_clk_rate(host), host->pclk_rate); + pr_info("%s: Slot eject status = %d\n", mmc_hostname(mmc), + host->eject); pr_info("%s: Power save feature enable = %d\n", - mmc_hostname(mmc), msmsdcc_pwrsave); + mmc_hostname(mmc), msmsdcc_pwrsave); - if (host->dma.channel != -1) { + if (host->is_dma_mode && host->dma.channel != -1) { pr_info("%s: DM non-cached buffer at %p, dma_addr 0x%.8x\n", - mmc_hostname(mmc), host->dma.nc, host->dma.nc_busaddr); + mmc_hostname(mmc), host->dma.nc, host->dma.nc_busaddr); pr_info("%s: DM cmd busaddr 0x%.8x, cmdptr busaddr 0x%.8x\n", - mmc_hostname(mmc), host->dma.cmd_busaddr, - host->dma.cmdptr_busaddr); + mmc_hostname(mmc), host->dma.cmd_busaddr, + host->dma.cmdptr_busaddr); + } else if (host->is_sps_mode) { + pr_info("%s: SPS-BAM data transfer mode available\n", + mmc_hostname(mmc)); } else pr_info("%s: PIO transfer enabled\n", mmc_hostname(mmc)); - if (host->timer.function) - pr_info("%s: Polling status mode enabled\n", mmc_hostname(mmc)); +#if defined(CONFIG_DEBUG_FS) + msmsdcc_dbg_createhost(host); +#endif + if (!plat->status_irq) { + ret = sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp); + if (ret) + goto platform_irq_free; + } return 0; - cmd_irq_free: - free_irq(cmd_irqres->start, host); - stat_irq_free: - if (host->stat_irq) - free_irq(host->stat_irq, host); + + platform_irq_free: + del_timer_sync(&host->req_tout_timer); + pm_runtime_disable(&(pdev)->dev); + pm_runtime_set_suspended(&(pdev)->dev); + + if (plat->status_irq) + free_irq(plat->status_irq, host); + sdiowakeup_irq_free: + wake_lock_destroy(&host->sdio_suspend_wlock); + if (plat->sdiowakeup_irq) + free_irq(plat->sdiowakeup_irq, host); + pio_irq_free: + if (plat->sdiowakeup_irq) + wake_lock_destroy(&host->sdio_wlock); + free_irq(core_irqres->start, host); + irq_free: + free_irq(core_irqres->start, host); + dml_exit: + if (host->is_sps_mode) + msmsdcc_dml_exit(host); + sps_exit: + if (host->is_sps_mode) + msmsdcc_sps_exit(host); + vreg_deinit: + msmsdcc_vreg_init(host, false); clk_disable: - msmsdcc_disable_clocks(host, 0); + clk_disable(host->clk); clk_put: clk_put(host->clk); + pclk_disable: + if (!IS_ERR(host->pclk)) + clk_disable(host->pclk); pclk_put: - clk_put(host->pclk); + if (!IS_ERR(host->pclk)) + clk_put(host->pclk); + if (!IS_ERR_OR_NULL(host->dfab_pclk)) + clk_disable(host->dfab_pclk); + dfab_pclk_put: + if (!IS_ERR_OR_NULL(host->dfab_pclk)) + clk_put(host->dfab_pclk); + dma_free: + if (host->is_dma_mode) { + if (host->dmares) + dma_free_coherent(NULL, + sizeof(struct msmsdcc_nc_dmadata), + host->dma.nc, host->dma.nc_busaddr); + } + ioremap_free: + iounmap(host->base); host_free: mmc_free_host(mmc); out: return ret; } -#ifdef CONFIG_PM -#ifdef CONFIG_MMC_MSM7X00A_RESUME_IN_WQ -static void -do_resume_work(struct work_struct *work) +static int msmsdcc_remove(struct platform_device *pdev) { - struct msmsdcc_host *host = - container_of(work, struct msmsdcc_host, resume_task); - struct mmc_host *mmc = host->mmc; + struct mmc_host *mmc = mmc_get_drvdata(pdev); + struct mmc_platform_data *plat; + struct msmsdcc_host *host; - if (mmc) { - mmc_resume_host(mmc); - if (host->stat_irq) - enable_irq(host->stat_irq); + if (!mmc) + return -ENXIO; + + if (pm_runtime_suspended(&(pdev)->dev)) + pm_runtime_resume(&(pdev)->dev); + + host = mmc_priv(mmc); + + DBG(host, "Removing SDCC device = %d\n", pdev->id); + plat = host->plat; + + if (!plat->status_irq) + sysfs_remove_group(&pdev->dev.kobj, &dev_attr_grp); + + del_timer_sync(&host->req_tout_timer); + tasklet_kill(&host->dma_tlet); + tasklet_kill(&host->sps.tlet); + mmc_remove_host(mmc); + + if (plat->status_irq) + free_irq(plat->status_irq, host); + + wake_lock_destroy(&host->sdio_suspend_wlock); + if (plat->sdiowakeup_irq) { + wake_lock_destroy(&host->sdio_wlock); + irq_set_irq_wake(plat->sdiowakeup_irq, 0); + free_irq(plat->sdiowakeup_irq, host); } -} + + free_irq(host->core_irqres->start, host); + free_irq(host->core_irqres->start, host); + + clk_put(host->clk); + if (!IS_ERR(host->pclk)) + clk_put(host->pclk); + if (!IS_ERR_OR_NULL(host->dfab_pclk)) + clk_put(host->dfab_pclk); + + msmsdcc_vreg_init(host, false); + + if (host->is_dma_mode) { + if (host->dmares) + dma_free_coherent(NULL, + sizeof(struct msmsdcc_nc_dmadata), + host->dma.nc, host->dma.nc_busaddr); + } + + if (host->is_sps_mode) { + msmsdcc_dml_exit(host); + msmsdcc_sps_exit(host); + } + + iounmap(host->base); + mmc_free_host(mmc); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&host->early_suspend); #endif + pm_runtime_disable(&(pdev)->dev); + pm_runtime_set_suspended(&(pdev)->dev); + + return 0; +} + +#ifdef CONFIG_MSM_SDIO_AL +int msmsdcc_sdio_al_lpm(struct mmc_host *mmc, bool enable) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + pr_debug("%s: %sabling LPM\n", mmc_hostname(mmc), + enable ? "En" : "Dis"); + + if (enable) { + if (!host->sdcc_irq_disabled) { + writel_relaxed(0, host->base + MMCIMASK0); + disable_irq(host->core_irqres->start); + host->sdcc_irq_disabled = 1; + } + + if (host->clks_on) { + msmsdcc_setup_clocks(host, false); + host->clks_on = 0; + } + + if (!host->sdio_gpio_lpm) { + spin_unlock_irqrestore(&host->lock, flags); + host->plat->sdio_lpm_gpio_setup(mmc_dev(mmc), 0); + spin_lock_irqsave(&host->lock, flags); + host->sdio_gpio_lpm = 1; + } + + if (host->sdio_irq_disabled) { + msmsdcc_enable_irq_wake(host); + enable_irq(host->plat->sdiowakeup_irq); + host->sdio_irq_disabled = 0; + } + } else { + if (!host->sdio_irq_disabled) { + disable_irq_nosync(host->plat->sdiowakeup_irq); + host->sdio_irq_disabled = 1; + msmsdcc_disable_irq_wake(host); + } + + if (host->sdio_gpio_lpm) { + spin_unlock_irqrestore(&host->lock, flags); + host->plat->sdio_lpm_gpio_setup(mmc_dev(mmc), 1); + spin_lock_irqsave(&host->lock, flags); + host->sdio_gpio_lpm = 0; + } + + if (!host->clks_on) { + msmsdcc_setup_clocks(host, true); + host->clks_on = 1; + } + if (host->sdcc_irq_disabled) { + writel_relaxed(host->mci_irqenable, + host->base + MMCIMASK0); + mb(); + enable_irq(host->core_irqres->start); + host->sdcc_irq_disabled = 0; + } + wake_lock_timeout(&host->sdio_wlock, 1); + } + spin_unlock_irqrestore(&host->lock, flags); + return 0; +} +#else +int msmsdcc_sdio_al_lpm(struct mmc_host *mmc, bool enable) +{ + return 0; +} +#endif +#ifdef CONFIG_PM static int -msmsdcc_suspend(struct platform_device *dev, pm_message_t state) +msmsdcc_runtime_suspend(struct device *dev) { - struct mmc_host *mmc = mmc_get_drvdata(dev); + struct mmc_host *mmc = dev_get_drvdata(dev); + struct msmsdcc_host *host = mmc_priv(mmc); int rc = 0; + if (host->plat->is_sdio_al_client) + return 0; + if (mmc) { - struct msmsdcc_host *host = mmc_priv(mmc); + host->sdcc_suspending = 1; + mmc->suspend_task = current; - if (host->stat_irq) - disable_irq(host->stat_irq); + /* + * If the clocks are already turned off by SDIO clients (as + * part of LPM), then clocks should be turned on before + * calling mmc_suspend_host() because mmc_suspend_host might + * send some commands to the card. The clocks will be turned + * off again after mmc_suspend_host. Thus for SD/MMC/SDIO + * cards, clocks will be turned on before mmc_suspend_host + * and turned off after mmc_suspend_host. + */ + mmc->ios.clock = host->clk_rate; + mmc->ops->set_ios(host->mmc, &host->mmc->ios); - if (mmc->card && mmc->card->type != MMC_TYPE_SDIO) - rc = mmc_suspend_host(mmc); - if (!rc) - msmsdcc_writel(host, 0, MMCIMASK0); - if (host->clks_on) - msmsdcc_disable_clocks(host, 0); + /* + * MMC core thinks that host is disabled by now since + * runtime suspend is scheduled after msmsdcc_disable() + * is called. Thus, MMC core will try to enable the host + * while suspending it. This results in a synchronous + * runtime resume request while in runtime suspending + * context and hence inorder to complete this resume + * requet, it will wait for suspend to be complete, + * but runtime suspend also can not proceed further + * until the host is resumed. Thus, it leads to a hang. + * Hence, increase the pm usage count before suspending + * the host so that any resume requests after this will + * simple become pm usage counter increment operations. + */ + pm_runtime_get_noresume(dev); + rc = mmc_suspend_host(mmc); + pm_runtime_put_noidle(dev); + + if (!rc) { + if (mmc->card && (mmc->card->type == MMC_TYPE_SDIO) && + (mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ)) { + disable_irq(host->core_irqres->start); + host->sdcc_irq_disabled = 1; + + /* + * If MMC core level suspend is not supported, + * turn off clocks to allow deep sleep (TCXO + * shutdown). + */ + mmc->ios.clock = 0; + mmc->ops->set_ios(host->mmc, &host->mmc->ios); + enable_irq(host->core_irqres->start); + host->sdcc_irq_disabled = 0; + + if (host->plat->sdiowakeup_irq) { + host->sdio_irq_disabled = 0; + msmsdcc_enable_irq_wake(host); + enable_irq(host->plat->sdiowakeup_irq); + } + } + } + host->sdcc_suspending = 0; + mmc->suspend_task = NULL; + if (rc && wake_lock_active(&host->sdio_suspend_wlock)) + wake_unlock(&host->sdio_suspend_wlock); } return rc; } static int -msmsdcc_resume(struct platform_device *dev) +msmsdcc_runtime_resume(struct device *dev) { - struct mmc_host *mmc = mmc_get_drvdata(dev); + struct mmc_host *mmc = dev_get_drvdata(dev); + struct msmsdcc_host *host = mmc_priv(mmc); + unsigned long flags; + + if (host->plat->is_sdio_al_client) + return 0; if (mmc) { - struct msmsdcc_host *host = mmc_priv(mmc); + if (mmc->card && mmc->card->type == MMC_TYPE_SDIO) { + if (host->sdcc_irq_disabled) { + enable_irq(host->core_irqres->start); + host->sdcc_irq_disabled = 0; + } + } + mmc->ios.clock = host->clk_rate; + mmc->ops->set_ios(host->mmc, &host->mmc->ios); + + spin_lock_irqsave(&host->lock, flags); + writel_relaxed(host->mci_irqenable, host->base + MMCIMASK0); + mb(); + + if (mmc->card && (mmc->card->type == MMC_TYPE_SDIO) && + (mmc->pm_flags & MMC_PM_WAKE_SDIO_IRQ) && + !host->sdio_irq_disabled) { + if (host->plat->sdiowakeup_irq) { + disable_irq_nosync( + host->plat->sdiowakeup_irq); + msmsdcc_disable_irq_wake(host); + host->sdio_irq_disabled = 1; + } + } - msmsdcc_enable_clocks(host); + spin_unlock_irqrestore(&host->lock, flags); - msmsdcc_writel(host, host->saved_irq0mask, MMCIMASK0); + mmc_resume_host(mmc); - if (mmc->card && mmc->card->type != MMC_TYPE_SDIO) - mmc_resume_host(mmc); - if (host->stat_irq) - enable_irq(host->stat_irq); -#if BUSCLK_PWRSAVE - msmsdcc_disable_clocks(host, 1); -#endif + /* + * FIXME: Clearing of flags must be handled in clients + * resume handler. + */ + spin_lock_irqsave(&host->lock, flags); + mmc->pm_flags = 0; + spin_unlock_irqrestore(&host->lock, flags); + + /* + * After resuming the host wait for sometime so that + * the SDIO work will be processed. + */ + if (mmc->card && (mmc->card->type == MMC_TYPE_SDIO)) { + if ((host->plat->cfg_mpm_sdiowakeup || + host->plat->sdiowakeup_irq) && + wake_lock_active(&host->sdio_wlock)) + wake_lock_timeout(&host->sdio_wlock, 1); + } + + wake_unlock(&host->sdio_suspend_wlock); } return 0; } + +static int msmsdcc_runtime_idle(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct msmsdcc_host *host = mmc_priv(mmc); + + if (host->plat->is_sdio_al_client) + return 0; + + /* Idle timeout is not configurable for now */ + pm_schedule_suspend(dev, MSM_MMC_IDLE_TIMEOUT); + + return -EAGAIN; +} + +static int msmsdcc_pm_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct msmsdcc_host *host = mmc_priv(mmc); + int rc = 0; + + if (host->plat->is_sdio_al_client) + return 0; + + + if (host->plat->status_irq) + disable_irq(host->plat->status_irq); + + if (!pm_runtime_suspended(dev)) + rc = msmsdcc_runtime_suspend(dev); + + return rc; +} + +static int msmsdcc_pm_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct msmsdcc_host *host = mmc_priv(mmc); + int rc = 0; + + if (host->plat->is_sdio_al_client) + return 0; + + rc = msmsdcc_runtime_resume(dev); + if (host->plat->status_irq) { + msmsdcc_check_status((unsigned long)host); + enable_irq(host->plat->status_irq); + } + + /* Update the run-time PM status */ + pm_runtime_disable(dev); + rc = pm_runtime_set_active(dev); + if (rc < 0) + pr_info("%s: %s: failed with error %d", mmc_hostname(mmc), + __func__, rc); + pm_runtime_enable(dev); + + return rc; +} + #else -#define msmsdcc_suspend 0 -#define msmsdcc_resume 0 +#define msmsdcc_runtime_suspend NULL +#define msmsdcc_runtime_resume NULL +#define msmsdcc_runtime_idle NULL +#define msmsdcc_pm_suspend NULL +#define msmsdcc_pm_resume NULL #endif +static const struct dev_pm_ops msmsdcc_dev_pm_ops = { + .runtime_suspend = msmsdcc_runtime_suspend, + .runtime_resume = msmsdcc_runtime_resume, + .runtime_idle = msmsdcc_runtime_idle, + .suspend = msmsdcc_pm_suspend, + .resume = msmsdcc_pm_resume, +}; + static struct platform_driver msmsdcc_driver = { .probe = msmsdcc_probe, - .suspend = msmsdcc_suspend, - .resume = msmsdcc_resume, + .remove = msmsdcc_remove, .driver = { .name = "msm_sdcc", + .pm = &msmsdcc_dev_pm_ops, }, }; static int __init msmsdcc_init(void) { +#if defined(CONFIG_DEBUG_FS) + int ret = 0; + ret = msmsdcc_dbg_init(); + if (ret) { + pr_err("Failed to create debug fs dir \n"); + return ret; + } +#endif return platform_driver_register(&msmsdcc_driver); } static void __exit msmsdcc_exit(void) { platform_driver_unregister(&msmsdcc_driver); + +#if defined(CONFIG_DEBUG_FS) + debugfs_remove(debugfs_file); + debugfs_remove(debugfs_dir); +#endif } module_init(msmsdcc_init); module_exit(msmsdcc_exit); -MODULE_DESCRIPTION("Qualcomm MSM 7X00A Multimedia Card Interface driver"); +MODULE_DESCRIPTION("Qualcomm Multimedia Card Interface driver"); MODULE_LICENSE("GPL"); + +#if defined(CONFIG_DEBUG_FS) + +static int +msmsdcc_dbg_state_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t +msmsdcc_dbg_state_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct msmsdcc_host *host = (struct msmsdcc_host *) file->private_data; + char buf[1024]; + int max, i; + + i = 0; + max = sizeof(buf) - 1; + + i += scnprintf(buf + i, max - i, "STAT: %p %p %p\n", host->curr.mrq, + host->curr.cmd, host->curr.data); + if (host->curr.cmd) { + struct mmc_command *cmd = host->curr.cmd; + + i += scnprintf(buf + i, max - i, "CMD : %.8x %.8x %.8x\n", + cmd->opcode, cmd->arg, cmd->flags); + } + if (host->curr.data) { + struct mmc_data *data = host->curr.data; + i += scnprintf(buf + i, max - i, + "DAT0: %.8x %.8x %.8x %.8x %.8x %.8x\n", + data->timeout_ns, data->timeout_clks, + data->blksz, data->blocks, data->error, + data->flags); + i += scnprintf(buf + i, max - i, "DAT1: %.8x %.8x %.8x %p\n", + host->curr.xfer_size, host->curr.xfer_remain, + host->curr.data_xfered, host->dma.sg); + } + + return simple_read_from_buffer(ubuf, count, ppos, buf, i); +} + +static const struct file_operations msmsdcc_dbg_state_ops = { + .read = msmsdcc_dbg_state_read, + .open = msmsdcc_dbg_state_open, +}; + +static void msmsdcc_dbg_createhost(struct msmsdcc_host *host) +{ + if (debugfs_dir) { + debugfs_file = debugfs_create_file(mmc_hostname(host->mmc), + 0644, debugfs_dir, host, + &msmsdcc_dbg_state_ops); + } +} + +static int __init msmsdcc_dbg_init(void) +{ + int err; + + debugfs_dir = debugfs_create_dir("msmsdcc", 0); + if (IS_ERR(debugfs_dir)) { + err = PTR_ERR(debugfs_dir); + debugfs_dir = NULL; + return err; + } + + return 0; +} +#endif diff --git a/drivers/mmc/host/msm_sdcc.h b/drivers/mmc/host/msm_sdcc.h index 42d7bbc977c..c6acea9fcc7 100644 --- a/drivers/mmc/host/msm_sdcc.h +++ b/drivers/mmc/host/msm_sdcc.h @@ -2,6 +2,7 @@ * linux/drivers/mmc/host/msmsdcc.h - QCT MSM7K SDC Controller * * Copyright (C) 2008 Google, All Rights Reserved. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 @@ -13,10 +14,23 @@ #ifndef _MSM_SDCC_H #define _MSM_SDCC_H -#define MSMSDCC_CRCI_SDC1 6 -#define MSMSDCC_CRCI_SDC2 7 -#define MSMSDCC_CRCI_SDC3 12 -#define MSMSDCC_CRCI_SDC4 13 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #define MMCIPOWER 0x000 #define MCI_PWR_OFF 0x00 @@ -27,10 +41,13 @@ #define MMCICLOCK 0x004 #define MCI_CLK_ENABLE (1 << 8) #define MCI_CLK_PWRSAVE (1 << 9) -#define MCI_CLK_WIDEBUS (1 << 10) +#define MCI_CLK_WIDEBUS_1 (0 << 10) +#define MCI_CLK_WIDEBUS_4 (2 << 10) +#define MCI_CLK_WIDEBUS_8 (3 << 10) #define MCI_CLK_FLOWENA (1 << 12) #define MCI_CLK_INVERTOUT (1 << 13) -#define MCI_CLK_SELECTIN (1 << 14) +#define MCI_CLK_SELECTIN (1 << 15) +#define IO_PAD_PWR_SWITCH (1 << 21) #define MMCIARGUMENT 0x008 #define MMCICOMMAND 0x00c @@ -44,6 +61,7 @@ #define MCI_CSPM_MCIABORT (1 << 13) #define MCI_CSPM_CCSENABLE (1 << 14) #define MCI_CSPM_CCSDISABLE (1 << 15) +#define MCI_CSPM_AUTO_CMD19 (1 << 16) #define MMCIRESPCMD 0x010 @@ -86,8 +104,9 @@ #define MCI_SDIOINTR (1 << 22) #define MCI_PROGDONE (1 << 23) #define MCI_ATACMDCOMPL (1 << 24) -#define MCI_SDIOINTOPER (1 << 25) +#define MCI_SDIOINTROPE (1 << 25) #define MCI_CCSTIMEOUT (1 << 26) +#define MCI_AUTOCMD19TIMEOUT (1 << 30) #define MMCICLEAR 0x038 #define MCI_CMDCRCFAILCLR (1 << 0) @@ -99,8 +118,23 @@ #define MCI_CMDRESPENDCLR (1 << 6) #define MCI_CMDSENTCLR (1 << 7) #define MCI_DATAENDCLR (1 << 8) +#define MCI_STARTBITERRCLR (1 << 9) #define MCI_DATABLOCKENDCLR (1 << 10) +#define MCI_SDIOINTRCLR (1 << 22) +#define MCI_PROGDONECLR (1 << 23) +#define MCI_ATACMDCOMPLCLR (1 << 24) +#define MCI_SDIOINTROPECLR (1 << 25) +#define MCI_CCSTIMEOUTCLR (1 << 26) + +#define MCI_CLEAR_STATIC_MASK \ + (MCI_CMDCRCFAILCLR|MCI_DATACRCFAILCLR|MCI_CMDTIMEOUTCLR|\ + MCI_DATATIMEOUTCLR|MCI_TXUNDERRUNCLR|MCI_RXOVERRUNCLR| \ + MCI_CMDRESPENDCLR|MCI_CMDSENTCLR|MCI_DATAENDCLR| \ + MCI_STARTBITERRCLR|MCI_DATABLOCKENDCLR|MCI_SDIOINTRCLR| \ + MCI_SDIOINTROPECLR|MCI_PROGDONECLR|MCI_ATACMDCOMPLCLR| \ + MCI_CCSTIMEOUTCLR) + #define MMCIMASK0 0x03c #define MCI_CMDCRCFAILMASK (1 << 0) #define MCI_DATACRCFAILMASK (1 << 1) @@ -128,17 +162,37 @@ #define MCI_ATACMDCOMPLMASK (1 << 24) #define MCI_SDIOINTOPERMASK (1 << 25) #define MCI_CCSTIMEOUTMASK (1 << 26) +#define MCI_AUTOCMD19TIMEOUTMASK (1 << 30) #define MMCIMASK1 0x040 #define MMCIFIFOCNT 0x044 #define MCICCSTIMER 0x058 +#define MCI_DLL_CONFIG 0x060 +#define MCI_DLL_EN (1 << 16) +#define MCI_CDR_EN (1 << 17) +#define MCI_CK_OUT_EN (1 << 18) +#define MCI_CDR_EXT_EN (1 << 19) +#define MCI_DLL_PDN (1 << 29) +#define MCI_DLL_RST (1 << 30) + +#define MCI_DLL_STATUS 0x068 +#define MCI_DLL_LOCK (1 << 7) #define MMCIFIFO 0x080 /* to 0x0bc */ +#define MCI_TEST_INPUT 0x0D4 + #define MCI_IRQENABLE \ (MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \ MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \ - MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_DATAENDMASK|MCI_PROGDONEMASK) + MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_DATAENDMASK| \ + MCI_PROGDONEMASK|MCI_AUTOCMD19TIMEOUTMASK) + +#define MCI_IRQ_PIO \ + (MCI_RXDATAAVLBLMASK | MCI_TXDATAAVLBLMASK | \ + MCI_RXFIFOEMPTYMASK | MCI_TXFIFOEMPTYMASK | MCI_RXFIFOFULLMASK |\ + MCI_TXFIFOFULLMASK | MCI_RXFIFOHALFFULLMASK | \ + MCI_TXFIFOHALFEMPTYMASK | MCI_RXACTIVEMASK | MCI_TXACTIVEMASK) /* * The size of the FIFO in bytes. @@ -149,6 +203,14 @@ #define NR_SG 32 +#define MSM_MMC_IDLE_TIMEOUT 10000 /* msecs */ + +/* + * Set the request timeout to 10secs to allow + * bad cards/controller to respond. + */ +#define MSM_MMC_REQ_TIMEOUT 10000 /* msecs */ + struct clk; struct msmsdcc_nc_dmadata { @@ -171,8 +233,7 @@ struct msmsdcc_dma_data { int channel; struct msmsdcc_host *host; int busy; /* Set if DM is busy */ - int active; - unsigned int result; + unsigned int result; struct msm_dmov_errdata err; }; @@ -193,29 +254,48 @@ struct msmsdcc_curr_req { int user_pages; }; -struct msmsdcc_stats { - unsigned int reqs; - unsigned int cmds; - unsigned int cmdpoll_hits; - unsigned int cmdpoll_misses; +struct msmsdcc_sps_ep_conn_data { + struct sps_pipe *pipe_handle; + struct sps_connect config; + struct sps_register_event event; +}; + +struct msmsdcc_sps_data { + struct msmsdcc_sps_ep_conn_data prod; + struct msmsdcc_sps_ep_conn_data cons; + struct sps_event_notify notify; + enum dma_data_direction dir; + struct scatterlist *sg; + int num_ents; + u32 bam_handle; + unsigned int src_pipe_index; + unsigned int dest_pipe_index; + unsigned int busy; + unsigned int xfer_req_cnt; + struct tasklet_struct tlet; + }; struct msmsdcc_host { - struct resource *cmd_irqres; - struct resource *pio_irqres; - struct resource *memres; + struct resource *core_irqres; + struct resource *bam_irqres; + struct resource *core_memres; + struct resource *bam_memres; + struct resource *dml_memres; struct resource *dmares; void __iomem *base; + void __iomem *dml_base; + void __iomem *bam_base; + int pdev_id; - unsigned int stat_irq; struct msmsdcc_curr_req curr; struct mmc_host *mmc; struct clk *clk; /* main MMC bus clock */ struct clk *pclk; /* SDCC peripheral bus clock */ + struct clk *dfab_pclk; /* Daytona Fabric SDCC clock */ unsigned int clks_on; /* set if clocks are enabled */ - struct timer_list busclk_timer; unsigned int eject; /* eject state */ @@ -223,30 +303,67 @@ struct msmsdcc_host { unsigned int clk_rate; /* Current clock rate */ unsigned int pclk_rate; + unsigned int ddr_doubled_clk_rate; u32 pwr; - u32 saved_irq0mask; /* MMCIMASK0 reg value */ - struct msm_mmc_platform_data *plat; + struct mmc_platform_data *plat; - struct timer_list timer; unsigned int oldstat; struct msmsdcc_dma_data dma; + struct msmsdcc_sps_data sps; + bool is_dma_mode; + bool is_sps_mode; struct msmsdcc_pio_data pio; - int cmdpoll; - struct msmsdcc_stats stats; - struct tasklet_struct dma_tlet; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; + int polling_enabled; +#endif + + struct tasklet_struct dma_tlet; + + unsigned int prog_scan; + unsigned int prog_enable; + /* Command parameters */ unsigned int cmd_timeout; unsigned int cmd_pio_irqmask; unsigned int cmd_datactrl; struct mmc_command *cmd_cmd; - u32 cmd_c; - bool gpio_config_status; - - bool prog_scan; - bool prog_enable; + u32 cmd_c; + + unsigned int mci_irqenable; + unsigned int dummy_52_needed; + unsigned int dummy_52_state; + unsigned int sdio_irq_disabled; + struct wake_lock sdio_wlock; + struct wake_lock sdio_suspend_wlock; + unsigned int sdcc_suspending; + + unsigned int sdcc_irq_disabled; + struct timer_list req_tout_timer; + bool io_pad_pwr_switch; + bool cmd19_tuning_in_progress; + bool tuning_needed; + bool sdio_gpio_lpm; + bool irq_wake_enabled; }; +int msmsdcc_set_pwrsave(struct mmc_host *mmc, int pwrsave); +int msmsdcc_sdio_al_lpm(struct mmc_host *mmc, bool enable); + +#ifdef CONFIG_MSM_SDIO_AL + +static inline int msmsdcc_lpm_enable(struct mmc_host *mmc) +{ + return msmsdcc_sdio_al_lpm(mmc, true); +} + +static inline int msmsdcc_lpm_disable(struct mmc_host *mmc) +{ + return msmsdcc_sdio_al_lpm(mmc, false); +} +#endif + #endif diff --git a/drivers/mmc/host/msm_sdcc_dml.c b/drivers/mmc/host/msm_sdcc_dml.c new file mode 100644 index 00000000000..320f52e4e60 --- /dev/null +++ b/drivers/mmc/host/msm_sdcc_dml.c @@ -0,0 +1,303 @@ +/* + * linux/drivers/mmc/host/msm_sdcc_dml.c - Qualcomm MSM SDCC DML Driver + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include "msm_sdcc_dml.h" + +/* + * DML registers definations + */ + +/* DML config register defination */ +#define DML_CONFIG 0x0000 +#define PRODUCER_CRCI_DIS 0x00 +#define PRODUCER_CRCI_X_SEL 0x01 +#define PRODUCER_CRCI_Y_SEL 0x02 +#define PRODUCER_CRCI_MSK 0x3 +#define CONSUMER_CRCI_DIS (0x00 << 2) +#define CONSUMER_CRCI_X_SEL (0x01 << 2) +#define CONSUMER_CRCI_Y_SEL (0x02 << 2) +#define CONSUMER_CRCI_MSK (0x3 << 2) +#define PRODUCER_TRANS_END_EN (1 << 4) +#define BYPASS (1 << 16) +#define DIRECT_MODE (1 << 17) +#define INFINITE_CONS_TRANS (1 << 18) + +/* DML status register defination */ +#define DML_STATUS 0x0004 +#define PRODUCER_IDLE (1 << 0) +#define CONSUMER_IDLE (1 << 16) + +/* + * DML SW RESET register defination + * NOTE: write to this register resets the DML core. + * All internal state information will be lost and all + * register values will be reset as well + */ +#define DML_SW_RESET 0x0008 + +/* + * DML PRODUCER START register defination + * NOTE: A write to this register triggers the DML + * Producer state machine. No SW register values will be + * altered. + */ +#define DML_PRODUCER_START 0x000C + +/* + * DML CONSUMER START register defination + * NOTE: A write to this register triggers the DML + * Consumer state machine. No SW register values will be + * altered. + */ +#define DML_CONSUMER_START 0x0010 + +/* + * DML producer pipe logical size register defination + * NOTE: This register holds the size of the producer pipe + * (in units of bytes) _to_ which the peripheral can + * keep writing data to when its the PRODUCER. + */ +#define DML_PRODUCER_PIPE_LOGICAL_SIZE 0x0014 + +/* + * DML producer pipe logical size register defination + * NOTE: This register holds the size of the consumer pipe + * (in units of bytes) _from_ which the peripheral + * can keep _reading_ data from when its the CONSUMER. + */ +#define DML_CONSUMER_PIPE_LOGICAL_SIZE 0x00018 + +/* + * DML PIPE ID register + * This register holds pipe IDs that services + * the producer and consumer side of the peripheral + */ +#define DML_PIPE_ID 0x0001C +#define PRODUCER_PIPE_ID_SHFT 0 +#define PRODUCER_PIPE_ID_MSK 0x1f +#define CONSUMER_PIPE_ID_SHFT 16 +#define CONSUMER_PIPE_ID_MSK (0x1f << 16) + +/* + * DML Producer trackers register defination. + * This register is for debug purposes only. They reflect + * the value of the producer block and transaction counters + * when read. The values may be dynamically changing when + * a transaction is in progress. + */ +#define DML_PRODUCER_TRACKERS 0x00020 +#define PROD_BLOCK_CNT_SHFT 0 +#define PROD_BLOCK_CNT_MSK 0xffff +#define PROD_TRANS_CNT_SHFT 16 +#define PROD_TRANS_CNT_MSK (0xffff << 16) + +/* + * DML Producer BAM block size register defination. + * This regsiter holds the block size, in units of bytes, + * associated with the Producer BAM. The DML asserts the + * block_end side band signal to the BAM whenever the producer + * side of the peripheral has generated the said amount of data. + * This register value should be an integral multiple of the + * Producer CRCI Block Size. + */ +#define DML_PRODUCER_BAM_BLOCK_SIZE 0x00024 + +/* + * DML Producer BAM Transaction size defination. + * This regsiter holds the transaction size, in units of bytes, + * associated with the Producer BAM. The DML asserts the transaction_end + * side band signal to the BAM whenever the producer side of the peripheral + * has generated the said amount of data. + */ +#define DML_PRODUCER_BAM_TRANS_SIZE 0x00028 + +/* + * DML Direct mode base address defination + * This register is used whenever the DIRECT_MODE bit + * in config register is set. + */ +#define DML_DIRECT_MODE_BASE_ADDR 0x002C +#define PRODUCER_BASE_ADDR_BSHFT 0 +#define PRODUCER_BASE_ADDR_BMSK 0xffff +#define CONSUMER_BASE_ADDR_BSHFT 16 +#define CONSUMER_BASE_ADDR_BMSK (0xffff << 16) + +/* + * DMA Debug and status register defination. + * These are the read-only registers useful debugging. + */ +#define DML_DEBUG 0x0030 +#define DML_BAM_SIDE_STATUS_1 0x0034 +#define DML_BAM_SIDE_STATUS_2 0x0038 + +/* other definations */ +#define PRODUCER_PIPE_LOGICAL_SIZE 4096 +#define CONSUMER_PIPE_LOGICAL_SIZE 4096 + +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT +/** + * Initialize DML HW connected with SDCC core + * + */ +int msmsdcc_dml_init(struct msmsdcc_host *host) +{ + int rc = 0; + u32 config = 0; + void __iomem *dml_base; + + if (!host->dml_base) { + host->dml_base = ioremap(host->dml_memres->start, + resource_size(host->dml_memres)); + if (!host->dml_base) { + pr_err("%s: DML ioremap() failed!!! phys_addr=0x%x," + " size=0x%x", mmc_hostname(host->mmc), + host->dml_memres->start, + (host->dml_memres->end - + host->dml_memres->start)); + rc = -ENOMEM; + goto out; + } + pr_info("%s: Qualcomm MSM SDCC-DML at 0x%016llx\n", + mmc_hostname(host->mmc), + (unsigned long long)host->dml_memres->start); + } + + dml_base = host->dml_base; + /* Reset the DML block */ + writel_relaxed(1, (dml_base + DML_SW_RESET)); + + /* Disable the producer and consumer CRCI */ + config = (PRODUCER_CRCI_DIS | CONSUMER_CRCI_DIS); + /* + * Disable the bypass mode. Bypass mode will only be used + * if data transfer is to happen in PIO mode and don't + * want the BAM interface to connect with SDCC-DML. + */ + config &= ~BYPASS; + /* + * Disable direct mode as we don't DML to MASTER the AHB bus. + * BAM connected with DML should MASTER the AHB bus. + */ + config &= ~DIRECT_MODE; + /* + * Disable infinite mode transfer as we won't be doing any + * infinite size data transfers. All data transfer will be + * of finite data size. + */ + config &= ~INFINITE_CONS_TRANS; + writel_relaxed(config, (dml_base + DML_CONFIG)); + + /* + * Initialize the logical BAM pipe size for producer + * and consumer. + */ + writel_relaxed(PRODUCER_PIPE_LOGICAL_SIZE, + (dml_base + DML_PRODUCER_PIPE_LOGICAL_SIZE)); + writel_relaxed(CONSUMER_PIPE_LOGICAL_SIZE, + (dml_base + DML_CONSUMER_PIPE_LOGICAL_SIZE)); + + /* Initialize Producer/consumer pipe id */ + writel_relaxed(host->sps.src_pipe_index | + (host->sps.dest_pipe_index << CONSUMER_PIPE_ID_SHFT), + (dml_base + DML_PIPE_ID)); + mb(); +out: + return rc; +} + +/** + * Soft reset DML HW + * + */ +void msmsdcc_dml_reset(struct msmsdcc_host *host) +{ + /* Reset the DML block */ + writel_relaxed(1, (host->dml_base + DML_SW_RESET)); + mb(); +} + +/** + * Checks if DML HW is busy or not? + * + */ +bool msmsdcc_is_dml_busy(struct msmsdcc_host *host) +{ + return !(readl_relaxed(host->dml_base + DML_STATUS) & PRODUCER_IDLE) || + !(readl_relaxed(host->dml_base + DML_STATUS) & CONSUMER_IDLE); +} + +/** + * Start data transfer. + * + */ +void msmsdcc_dml_start_xfer(struct msmsdcc_host *host, struct mmc_data *data) +{ + u32 config; + void __iomem *dml_base = host->dml_base; + + if (data->flags & MMC_DATA_READ) { + /* Read operation: configure DML for producer operation */ + /* Set producer CRCI-x and disable consumer CRCI */ + config = readl_relaxed(dml_base + DML_CONFIG); + config = (config & ~PRODUCER_CRCI_MSK) | PRODUCER_CRCI_X_SEL; + config = (config & ~CONSUMER_CRCI_MSK) | CONSUMER_CRCI_DIS; + writel_relaxed(config, (dml_base + DML_CONFIG)); + + /* Set the Producer BAM block size */ + writel_relaxed(data->blksz, (dml_base + + DML_PRODUCER_BAM_BLOCK_SIZE)); + + /* Set Producer BAM Transaction size */ + writel_relaxed(host->curr.xfer_size, + (dml_base + DML_PRODUCER_BAM_TRANS_SIZE)); + /* Set Producer Transaction End bit */ + writel_relaxed((readl_relaxed(dml_base + DML_CONFIG) + | PRODUCER_TRANS_END_EN), + (dml_base + DML_CONFIG)); + /* Trigger producer */ + writel_relaxed(1, (dml_base + DML_PRODUCER_START)); + } else { + /* Write operation: configure DML for consumer operation */ + /* Set consumer CRCI-x and disable producer CRCI*/ + config = readl_relaxed(dml_base + DML_CONFIG); + config = (config & ~CONSUMER_CRCI_MSK) | CONSUMER_CRCI_X_SEL; + config = (config & ~PRODUCER_CRCI_MSK) | PRODUCER_CRCI_DIS; + writel_relaxed(config, (dml_base + DML_CONFIG)); + /* Clear Producer Transaction End bit */ + writel_relaxed((readl_relaxed(dml_base + DML_CONFIG) + & ~PRODUCER_TRANS_END_EN), + (dml_base + DML_CONFIG)); + /* Trigger consumer */ + writel_relaxed(1, (dml_base + DML_CONSUMER_START)); + } + mb(); +} + +/** + * Deinitialize DML HW connected with SDCC core + * + */ +void msmsdcc_dml_exit(struct msmsdcc_host *host) +{ + /* Put DML block in reset state before exiting */ + msmsdcc_dml_reset(host); + iounmap(host->dml_base); +} +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ diff --git a/drivers/mmc/host/msm_sdcc_dml.h b/drivers/mmc/host/msm_sdcc_dml.h new file mode 100644 index 00000000000..f0e1b78e7a8 --- /dev/null +++ b/drivers/mmc/host/msm_sdcc_dml.h @@ -0,0 +1,105 @@ +/* + * linux/drivers/mmc/host/msm_sdcc_dml.h - Qualcomm SDCC DML driver + * header file + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _MSM_SDCC_DML_H +#define _MSM_SDCC_DML_H + +#include +#include + +#include "msm_sdcc.h" + +#ifdef CONFIG_MMC_MSM_SPS_SUPPORT +/** + * Initialize DML HW connected with SDCC core + * + * This function initialize DML HW. + * + * This function should only be called once + * typically during driver probe. + * + * @host - Pointer to sdcc host structure + * + * @return - 0 if successful else negative value. + * + */ +int msmsdcc_dml_init(struct msmsdcc_host *host); + +/** + * Start data transfer. + * + * This function configure DML HW registers with + * data transfer direction and data transfer size. + * + * This function should be called after submitting + * data transfer request to SPS HW and before kick + * starting data transfer in SDCC core. + * + * @host - Pointer to sdcc host structure + * @data - Pointer to mmc_data structure + * + */ +void msmsdcc_dml_start_xfer(struct msmsdcc_host *host, struct mmc_data *data); + +/** + * Checks if DML HW is busy or not? + * + * @host - Pointer to sdcc host structure + * + * @return - 1 if DML HW is busy with data transfer + * 0 if DML HW is IDLE. + * + */ +bool msmsdcc_is_dml_busy(struct msmsdcc_host *host); + +/** + * Soft reset DML HW + * + * This function give soft reset to DML HW. + * + * This function should be called to reset DML HW + * if data transfer error is detected. + * + * @host - Pointer to sdcc host structure + * + */ +void msmsdcc_dml_reset(struct msmsdcc_host *host); + +/** + * Deinitialize DML HW connected with SDCC core + * + * This function resets DML HW and unmap DML + * register region. + * + * This function should only be called once + * typically during driver remove. + * + * @host - Pointer to sdcc host structure + * + */ +void msmsdcc_dml_exit(struct msmsdcc_host *host); +#else +static inline int msmsdcc_dml_init(struct msmsdcc_host *host) { return 0; } +static inline int msmsdcc_dml_start_xfer(struct msmsdcc_host *host, + struct mmc_data *data) { return 0; } +static inline bool msmsdcc_is_dml_busy( + struct msmsdcc_host *host) { return 0; } +static inline void msmsdcc_dml_reset(struct msmsdcc_host *host) { } +static inline void msmsdcc_dml_exit(struct msmsdcc_host *host) { } +#endif /* CONFIG_MMC_MSM_SPS_SUPPORT */ + +#endif /* _MSM_SDCC_DML_H */ diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 7fc5848a45c..c35a7c7c2b4 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2456,6 +2456,22 @@ int sdhci_add_host(struct sdhci_host *host) if (caps[0] & SDHCI_TIMEOUT_CLK_UNIT) host->timeout_clk *= 1000; + /* + * In case of Host Controller v3.00, find out whether clock + * multiplier is supported. + */ + host->clk_mul = (caps[1] & SDHCI_CLOCK_MUL_MASK) >> + SDHCI_CLOCK_MUL_SHIFT; + + /* + * In case the value in Clock Multiplier is 0, then programmable + * clock mode is not supported, otherwise the actual clock + * multiplier is one more than the value of Clock Multiplier + * in the Capabilities Register. + */ + if (host->clk_mul) + host->clk_mul += 1; + /* * In case of Host Controller v3.00, find out whether clock * multiplier is supported. diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index 35081ce77fb..1b48daee9a1 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig @@ -49,6 +49,16 @@ config MTD_MS02NV say M here and read . The module will be called ms02-nv. +config MTD_MSM_NAND + tristate "MSM NAND Device Support" + depends on MTD && ARCH_MSM + select CRC16 + select BITREVERSE + select MTD_NAND_IDS + default y + help + Support for some NAND chips connected to the MSM NAND controller. + config MTD_DATAFLASH tristate "Support for AT45xxx DataFlash" depends on SPI_MASTER && EXPERIMENTAL diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index f3226b1d38f..fe959e82250 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_MTD_SLRAM) += slram.o obj-$(CONFIG_MTD_PHRAM) += phram.o obj-$(CONFIG_MTD_PMC551) += pmc551.o obj-$(CONFIG_MTD_MS02NV) += ms02-nv.o +obj-$(CONFIG_MTD_MSM_NAND) += msm_nand.o obj-$(CONFIG_MTD_MTDRAM) += mtdram.o obj-$(CONFIG_MTD_LART) += lart.o obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o diff --git a/drivers/mtd/devices/msm_nand.c b/drivers/mtd/devices/msm_nand.c new file mode 100644 index 00000000000..d1a13539727 --- /dev/null +++ b/drivers/mtd/devices/msm_nand.c @@ -0,0 +1,7143 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "msm_nand.h" + +unsigned long msm_nand_phys; +unsigned long msm_nandc01_phys; +unsigned long msm_nandc10_phys; +unsigned long msm_nandc11_phys; +unsigned long ebi2_register_base; +uint32_t dual_nand_ctlr_present; +uint32_t interleave_enable; +uint32_t enable_bch_ecc; +unsigned crci_mask; + +#define MSM_NAND_DMA_BUFFER_SIZE SZ_8K +#define MSM_NAND_DMA_BUFFER_SLOTS \ + (MSM_NAND_DMA_BUFFER_SIZE / (sizeof(((atomic_t *)0)->counter) * 8)) + +#define MSM_NAND_CFG0_RAW_ONFI_IDENTIFIER 0x88000800 +#define MSM_NAND_CFG0_RAW_ONFI_PARAM_INFO 0x88040000 +#define MSM_NAND_CFG1_RAW_ONFI_IDENTIFIER 0x0005045d +#define MSM_NAND_CFG1_RAW_ONFI_PARAM_INFO 0x0005045d + +#define ONFI_IDENTIFIER_LENGTH 0x0004 +#define ONFI_PARAM_INFO_LENGTH 0x0200 +#define ONFI_PARAM_PAGE_LENGTH 0x0100 + +#define ONFI_PARAMETER_PAGE_SIGNATURE 0x49464E4F + +#define FLASH_READ_ONFI_IDENTIFIER_COMMAND 0x90 +#define FLASH_READ_ONFI_IDENTIFIER_ADDRESS 0x20 +#define FLASH_READ_ONFI_PARAMETERS_COMMAND 0xEC +#define FLASH_READ_ONFI_PARAMETERS_ADDRESS 0x00 + +#define VERBOSE 0 + +struct msm_nand_chip { + struct device *dev; + wait_queue_head_t wait_queue; + atomic_t dma_buffer_busy; + unsigned dma_channel; + uint8_t *dma_buffer; + dma_addr_t dma_addr; + unsigned CFG0, CFG1, CFG0_RAW, CFG1_RAW; + uint32_t ecc_buf_cfg; + uint32_t ecc_bch_cfg; + uint32_t ecc_parity_bytes; + unsigned cw_size; +}; + +#define CFG1_WIDE_FLASH (1U << 1) + +/* TODO: move datamover code out */ + +#define SRC_CRCI_NAND_CMD CMD_SRC_CRCI(DMOV_NAND_CRCI_CMD) +#define DST_CRCI_NAND_CMD CMD_DST_CRCI(DMOV_NAND_CRCI_CMD) +#define SRC_CRCI_NAND_DATA CMD_SRC_CRCI(DMOV_NAND_CRCI_DATA) +#define DST_CRCI_NAND_DATA CMD_DST_CRCI(DMOV_NAND_CRCI_DATA) + +#define msm_virt_to_dma(chip, vaddr) \ + ((chip)->dma_addr + \ + ((uint8_t *)(vaddr) - (chip)->dma_buffer)) + +/** + * msm_nand_oob_64 - oob info for 2KB page + */ +static struct nand_ecclayout msm_nand_oob_64 = { + .eccbytes = 40, + .eccpos = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + }, + .oobavail = 16, + .oobfree = { + {30, 16}, + } +}; + +/** + * msm_nand_oob_128 - oob info for 4KB page + */ +static struct nand_ecclayout msm_nand_oob_128 = { + .eccbytes = 80, + .eccpos = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + }, + .oobavail = 32, + .oobfree = { + {70, 32}, + } +}; + +/** + * msm_nand_oob_224 - oob info for 4KB page 8Bit interface + */ +static struct nand_ecclayout msm_nand_oob_224_x8 = { + .eccbytes = 104, + .eccpos = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, + }, + .oobavail = 32, + .oobfree = { + {91, 32}, + } +}; + +/** + * msm_nand_oob_224 - oob info for 4KB page 16Bit interface + */ +static struct nand_ecclayout msm_nand_oob_224_x16 = { + .eccbytes = 112, + .eccpos = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + }, + .oobavail = 32, + .oobfree = { + {98, 32}, + } +}; + +/** + * msm_nand_oob_256 - oob info for 8KB page + */ +static struct nand_ecclayout msm_nand_oob_256 = { + .eccbytes = 160, + .eccpos = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 96, 97, 98 , 99, 100, + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, + 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, + 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, + 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, + }, + .oobavail = 64, + .oobfree = { + {151, 64}, + } +}; + +/** + * msm_onenand_oob_64 - oob info for large (2KB) page + */ +static struct nand_ecclayout msm_onenand_oob_64 = { + .eccbytes = 20, + .eccpos = { + 8, 9, 10, 11, 12, + 24, 25, 26, 27, 28, + 40, 41, 42, 43, 44, + 56, 57, 58, 59, 60, + }, + .oobavail = 20, + .oobfree = { + {2, 3}, {14, 2}, {18, 3}, {30, 2}, + {34, 3}, {46, 2}, {50, 3}, {62, 2} + } +}; + +static void *msm_nand_get_dma_buffer(struct msm_nand_chip *chip, size_t size) +{ + unsigned int bitmask, free_bitmask, old_bitmask; + unsigned int need_mask, current_need_mask; + int free_index; + + need_mask = (1UL << DIV_ROUND_UP(size, MSM_NAND_DMA_BUFFER_SLOTS)) - 1; + bitmask = atomic_read(&chip->dma_buffer_busy); + free_bitmask = ~bitmask; + do { + free_index = __ffs(free_bitmask); + current_need_mask = need_mask << free_index; + + if (size + free_index * MSM_NAND_DMA_BUFFER_SLOTS >= + MSM_NAND_DMA_BUFFER_SIZE) + return NULL; + + if ((bitmask & current_need_mask) == 0) { + old_bitmask = + atomic_cmpxchg(&chip->dma_buffer_busy, + bitmask, + bitmask | current_need_mask); + if (old_bitmask == bitmask) + return chip->dma_buffer + + free_index * MSM_NAND_DMA_BUFFER_SLOTS; + free_bitmask = 0; /* force return */ + } + /* current free range was too small, clear all free bits */ + /* below the top busy bit within current_need_mask */ + free_bitmask &= + ~(~0U >> (32 - fls(bitmask & current_need_mask))); + } while (free_bitmask); + + return NULL; +} + +static void msm_nand_release_dma_buffer(struct msm_nand_chip *chip, + void *buffer, size_t size) +{ + int index; + unsigned int used_mask; + + used_mask = (1UL << DIV_ROUND_UP(size, MSM_NAND_DMA_BUFFER_SLOTS)) - 1; + index = ((uint8_t *)buffer - chip->dma_buffer) / + MSM_NAND_DMA_BUFFER_SLOTS; + atomic_sub(used_mask << index, &chip->dma_buffer_busy); + + wake_up(&chip->wait_queue); +} + + +unsigned flash_rd_reg(struct msm_nand_chip *chip, unsigned addr) +{ + struct { + dmov_s cmd; + unsigned cmdptr; + unsigned data; + } *dma_buffer; + unsigned rv; + + wait_event(chip->wait_queue, + (dma_buffer = msm_nand_get_dma_buffer( + chip, sizeof(*dma_buffer)))); + + dma_buffer->cmd.cmd = CMD_LC | CMD_OCB | CMD_OCU; + dma_buffer->cmd.src = addr; + dma_buffer->cmd.dst = msm_virt_to_dma(chip, &dma_buffer->data); + dma_buffer->cmd.len = 4; + + dma_buffer->cmdptr = + (msm_virt_to_dma(chip, &dma_buffer->cmd) >> 3) | CMD_PTR_LP; + dma_buffer->data = 0xeeeeeeee; + + mb(); + msm_dmov_exec_cmd( + chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(msm_virt_to_dma(chip, &dma_buffer->cmdptr))); + mb(); + + rv = dma_buffer->data; + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + + return rv; +} + +void flash_wr_reg(struct msm_nand_chip *chip, unsigned addr, unsigned val) +{ + struct { + dmov_s cmd; + unsigned cmdptr; + unsigned data; + } *dma_buffer; + + wait_event(chip->wait_queue, + (dma_buffer = msm_nand_get_dma_buffer( + chip, sizeof(*dma_buffer)))); + + dma_buffer->cmd.cmd = CMD_LC | CMD_OCB | CMD_OCU; + dma_buffer->cmd.src = msm_virt_to_dma(chip, &dma_buffer->data); + dma_buffer->cmd.dst = addr; + dma_buffer->cmd.len = 4; + + dma_buffer->cmdptr = + (msm_virt_to_dma(chip, &dma_buffer->cmd) >> 3) | CMD_PTR_LP; + dma_buffer->data = val; + + mb(); + msm_dmov_exec_cmd( + chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(msm_virt_to_dma(chip, &dma_buffer->cmdptr))); + mb(); + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); +} + +static dma_addr_t +msm_nand_dma_map(struct device *dev, void *addr, size_t size, + enum dma_data_direction dir) +{ + struct page *page; + unsigned long offset = (unsigned long)addr & ~PAGE_MASK; + if (virt_addr_valid(addr)) + page = virt_to_page(addr); + else { + if (WARN_ON(size + offset > PAGE_SIZE)) + return ~0; + page = vmalloc_to_page(addr); + } + return dma_map_page(dev, page, offset, size, dir); +} + +uint32_t flash_read_id(struct msm_nand_chip *chip) +{ + struct { + dmov_s cmd[7]; + unsigned cmdptr; + unsigned data[7]; + } *dma_buffer; + uint32_t rv; + + wait_event(chip->wait_queue, (dma_buffer = msm_nand_get_dma_buffer + (chip, sizeof(*dma_buffer)))); + + dma_buffer->data[0] = 0 | 4; + dma_buffer->data[1] = MSM_NAND_CMD_FETCH_ID; + dma_buffer->data[2] = 1; + dma_buffer->data[3] = 0xeeeeeeee; + dma_buffer->data[4] = 0xeeeeeeee; + dma_buffer->data[5] = flash_rd_reg(chip, MSM_NAND_SFLASHC_BURST_CFG); + dma_buffer->data[6] = 0x00000000; + BUILD_BUG_ON(6 != ARRAY_SIZE(dma_buffer->data) - 1); + + dma_buffer->cmd[0].cmd = 0 | CMD_OCB; + dma_buffer->cmd[0].src = msm_virt_to_dma(chip, &dma_buffer->data[6]); + dma_buffer->cmd[0].dst = MSM_NAND_SFLASHC_BURST_CFG; + dma_buffer->cmd[0].len = 4; + + dma_buffer->cmd[1].cmd = 0; + dma_buffer->cmd[1].src = msm_virt_to_dma(chip, &dma_buffer->data[0]); + dma_buffer->cmd[1].dst = MSM_NAND_FLASH_CHIP_SELECT; + dma_buffer->cmd[1].len = 4; + + dma_buffer->cmd[2].cmd = DST_CRCI_NAND_CMD; + dma_buffer->cmd[2].src = msm_virt_to_dma(chip, &dma_buffer->data[1]); + dma_buffer->cmd[2].dst = MSM_NAND_FLASH_CMD; + dma_buffer->cmd[2].len = 4; + + dma_buffer->cmd[3].cmd = 0; + dma_buffer->cmd[3].src = msm_virt_to_dma(chip, &dma_buffer->data[2]); + dma_buffer->cmd[3].dst = MSM_NAND_EXEC_CMD; + dma_buffer->cmd[3].len = 4; + + dma_buffer->cmd[4].cmd = SRC_CRCI_NAND_DATA; + dma_buffer->cmd[4].src = MSM_NAND_FLASH_STATUS; + dma_buffer->cmd[4].dst = msm_virt_to_dma(chip, &dma_buffer->data[3]); + dma_buffer->cmd[4].len = 4; + + dma_buffer->cmd[5].cmd = 0; + dma_buffer->cmd[5].src = MSM_NAND_READ_ID; + dma_buffer->cmd[5].dst = msm_virt_to_dma(chip, &dma_buffer->data[4]); + dma_buffer->cmd[5].len = 4; + + dma_buffer->cmd[6].cmd = CMD_OCU | CMD_LC; + dma_buffer->cmd[6].src = msm_virt_to_dma(chip, &dma_buffer->data[5]); + dma_buffer->cmd[6].dst = MSM_NAND_SFLASHC_BURST_CFG; + dma_buffer->cmd[6].len = 4; + + BUILD_BUG_ON(6 != ARRAY_SIZE(dma_buffer->cmd) - 1); + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, dma_buffer->cmd) >> 3 + ) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(msm_virt_to_dma(chip, &dma_buffer->cmdptr))); + mb(); + + pr_info("status: %x\n", dma_buffer->data[3]); + pr_info("nandid: %x maker %02x device %02x\n", + dma_buffer->data[4], dma_buffer->data[4] & 0xff, + (dma_buffer->data[4] >> 8) & 0xff); + rv = dma_buffer->data[4]; + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + return rv; +} + +struct flash_identification { + uint32_t flash_id; + uint32_t density; + uint32_t widebus; + uint32_t pagesize; + uint32_t blksize; + uint32_t oobsize; + uint32_t ecc_correctability; +} supported_flash; + +uint16_t flash_onfi_crc_check(uint8_t *buffer, uint16_t count) +{ + int i; + uint16_t result; + + for (i = 0; i < count; i++) + buffer[i] = bitrev8(buffer[i]); + + result = bitrev16(crc16(bitrev16(0x4f4e), buffer, count)); + + for (i = 0; i < count; i++) + buffer[i] = bitrev8(buffer[i]); + + return result; +} + + +uint32_t flash_onfi_probe(struct msm_nand_chip *chip) +{ + struct onfi_param_page { + uint32_t parameter_page_signature; + uint16_t revision_number; + uint16_t features_supported; + uint16_t optional_commands_supported; + uint8_t reserved0[22]; + uint8_t device_manufacturer[12]; + uint8_t device_model[20]; + uint8_t jedec_manufacturer_id; + uint16_t date_code; + uint8_t reserved1[13]; + uint32_t number_of_data_bytes_per_page; + uint16_t number_of_spare_bytes_per_page; + uint32_t number_of_data_bytes_per_partial_page; + uint16_t number_of_spare_bytes_per_partial_page; + uint32_t number_of_pages_per_block; + uint32_t number_of_blocks_per_logical_unit; + uint8_t number_of_logical_units; + uint8_t number_of_address_cycles; + uint8_t number_of_bits_per_cell; + uint16_t maximum_bad_blocks_per_logical_unit; + uint16_t block_endurance; + uint8_t guaranteed_valid_begin_blocks; + uint16_t guaranteed_valid_begin_blocks_endurance; + uint8_t number_of_programs_per_page; + uint8_t partial_program_attributes; + uint8_t number_of_bits_ecc_correctability; + uint8_t number_of_interleaved_address_bits; + uint8_t interleaved_operation_attributes; + uint8_t reserved2[13]; + uint8_t io_pin_capacitance; + uint16_t timing_mode_support; + uint16_t program_cache_timing_mode_support; + uint16_t maximum_page_programming_time; + uint16_t maximum_block_erase_time; + uint16_t maximum_page_read_time; + uint16_t maximum_change_column_setup_time; + uint8_t reserved3[23]; + uint16_t vendor_specific_revision_number; + uint8_t vendor_specific[88]; + uint16_t integrity_crc; + + } __attribute__((__packed__)); + + struct onfi_param_page *onfi_param_page_ptr; + uint8_t *onfi_identifier_buf = NULL; + uint8_t *onfi_param_info_buf = NULL; + + struct { + dmov_s cmd[11]; + unsigned cmdptr; + struct { + uint32_t cmd; + uint32_t addr0; + uint32_t addr1; + uint32_t cfg0; + uint32_t cfg1; + uint32_t exec; + uint32_t flash_status; + uint32_t devcmd1_orig; + uint32_t devcmdvld_orig; + uint32_t devcmd1_mod; + uint32_t devcmdvld_mod; + uint32_t sflash_bcfg_orig; + uint32_t sflash_bcfg_mod; + } data; + } *dma_buffer; + dmov_s *cmd; + + unsigned page_address = 0; + int err = 0; + dma_addr_t dma_addr_param_info = 0; + dma_addr_t dma_addr_identifier = 0; + unsigned cmd_set_count = 2; + unsigned crc_chk_count = 0; + + if (msm_nand_data.nr_parts) { + page_address = ((msm_nand_data.parts[0]).offset << 6); + } else { + pr_err("flash_onfi_probe: " + "No partition info available\n"); + err = -EIO; + return err; + } + + wait_event(chip->wait_queue, (onfi_identifier_buf = + msm_nand_get_dma_buffer(chip, ONFI_IDENTIFIER_LENGTH))); + dma_addr_identifier = msm_virt_to_dma(chip, onfi_identifier_buf); + + wait_event(chip->wait_queue, (onfi_param_info_buf = + msm_nand_get_dma_buffer(chip, ONFI_PARAM_INFO_LENGTH))); + dma_addr_param_info = msm_virt_to_dma(chip, onfi_param_info_buf); + + wait_event(chip->wait_queue, (dma_buffer = msm_nand_get_dma_buffer + (chip, sizeof(*dma_buffer)))); + + dma_buffer->data.sflash_bcfg_orig = flash_rd_reg + (chip, MSM_NAND_SFLASHC_BURST_CFG); + dma_buffer->data.devcmd1_orig = flash_rd_reg(chip, MSM_NAND_DEV_CMD1); + dma_buffer->data.devcmdvld_orig = flash_rd_reg(chip, + MSM_NAND_DEV_CMD_VLD); + + while (cmd_set_count-- > 0) { + cmd = dma_buffer->cmd; + + dma_buffer->data.devcmd1_mod = (dma_buffer->data.devcmd1_orig & + 0xFFFFFF00) | (cmd_set_count + ? FLASH_READ_ONFI_IDENTIFIER_COMMAND + : FLASH_READ_ONFI_PARAMETERS_COMMAND); + dma_buffer->data.cmd = MSM_NAND_CMD_PAGE_READ; + dma_buffer->data.addr0 = (page_address << 16) | (cmd_set_count + ? FLASH_READ_ONFI_IDENTIFIER_ADDRESS + : FLASH_READ_ONFI_PARAMETERS_ADDRESS); + dma_buffer->data.addr1 = (page_address >> 16) & 0xFF; + dma_buffer->data.cfg0 = (cmd_set_count + ? MSM_NAND_CFG0_RAW_ONFI_IDENTIFIER + : MSM_NAND_CFG0_RAW_ONFI_PARAM_INFO); + dma_buffer->data.cfg1 = (cmd_set_count + ? MSM_NAND_CFG1_RAW_ONFI_IDENTIFIER + : MSM_NAND_CFG1_RAW_ONFI_PARAM_INFO); + dma_buffer->data.sflash_bcfg_mod = 0x00000000; + dma_buffer->data.devcmdvld_mod = (dma_buffer-> + data.devcmdvld_orig & 0xFFFFFFFE); + dma_buffer->data.exec = 1; + dma_buffer->data.flash_status = 0xeeeeeeee; + + /* Put the Nand ctlr in Async mode and disable SFlash ctlr */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.sflash_bcfg_mod); + cmd->dst = MSM_NAND_SFLASHC_BURST_CFG; + cmd->len = 4; + cmd++; + + /* Block on cmd ready, & write CMD,ADDR0,ADDR1,CHIPSEL regs */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = MSM_NAND_FLASH_CMD; + cmd->len = 12; + cmd++; + + /* Configure the CFG0 and CFG1 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0); + cmd->dst = MSM_NAND_DEV0_CFG0; + cmd->len = 8; + cmd++; + + /* Configure the DEV_CMD_VLD register */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.devcmdvld_mod); + cmd->dst = MSM_NAND_DEV_CMD_VLD; + cmd->len = 4; + cmd++; + + /* Configure the DEV_CMD1 register */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.devcmd1_mod); + cmd->dst = MSM_NAND_DEV_CMD1; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.exec); + cmd->dst = MSM_NAND_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the two status registers */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_FLASH_STATUS; + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.flash_status); + cmd->len = 4; + cmd++; + + /* Read data block - valid only if status says success */ + cmd->cmd = 0; + cmd->src = MSM_NAND_FLASH_BUFFER; + cmd->dst = (cmd_set_count ? dma_addr_identifier : + dma_addr_param_info); + cmd->len = (cmd_set_count ? ONFI_IDENTIFIER_LENGTH : + ONFI_PARAM_INFO_LENGTH); + cmd++; + + /* Restore the DEV_CMD1 register */ + cmd->cmd = 0 ; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.devcmd1_orig); + cmd->dst = MSM_NAND_DEV_CMD1; + cmd->len = 4; + cmd++; + + /* Restore the DEV_CMD_VLD register */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.devcmdvld_orig); + cmd->dst = MSM_NAND_DEV_CMD_VLD; + cmd->len = 4; + cmd++; + + /* Restore the SFLASH_BURST_CONFIG register */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.sflash_bcfg_orig); + cmd->dst = MSM_NAND_SFLASHC_BURST_CFG; + cmd->len = 4; + cmd++; + + BUILD_BUG_ON(11 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, dma_buffer->cmd) + >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + + /* Check for errors, protection violations etc */ + if (dma_buffer->data.flash_status & 0x110) { + pr_info("MPU/OP error (0x%x) during " + "ONFI probe\n", + dma_buffer->data.flash_status); + err = -EIO; + break; + } + + if (cmd_set_count) { + onfi_param_page_ptr = (struct onfi_param_page *) + (&(onfi_identifier_buf[0])); + if (onfi_param_page_ptr->parameter_page_signature != + ONFI_PARAMETER_PAGE_SIGNATURE) { + pr_info("ONFI probe : Found a non" + "ONFI Compliant device \n"); + err = -EIO; + break; + } + } else { + for (crc_chk_count = 0; crc_chk_count < + ONFI_PARAM_INFO_LENGTH + / ONFI_PARAM_PAGE_LENGTH; + crc_chk_count++) { + onfi_param_page_ptr = + (struct onfi_param_page *) + (&(onfi_param_info_buf + [ONFI_PARAM_PAGE_LENGTH * + crc_chk_count])); + if (flash_onfi_crc_check( + (uint8_t *)onfi_param_page_ptr, + ONFI_PARAM_PAGE_LENGTH - 2) == + onfi_param_page_ptr->integrity_crc) { + break; + } + } + if (crc_chk_count >= ONFI_PARAM_INFO_LENGTH + / ONFI_PARAM_PAGE_LENGTH) { + pr_info("ONFI probe : CRC Check " + "failed on ONFI Parameter " + "data \n"); + err = -EIO; + break; + } else { + supported_flash.flash_id = + flash_read_id(chip); + supported_flash.widebus = + onfi_param_page_ptr-> + features_supported & 0x01; + supported_flash.pagesize = + onfi_param_page_ptr-> + number_of_data_bytes_per_page; + supported_flash.blksize = + onfi_param_page_ptr-> + number_of_pages_per_block * + supported_flash.pagesize; + supported_flash.oobsize = + onfi_param_page_ptr-> + number_of_spare_bytes_per_page; + supported_flash.density = + onfi_param_page_ptr-> + number_of_blocks_per_logical_unit + * supported_flash.blksize; + supported_flash.ecc_correctability = + onfi_param_page_ptr-> + number_of_bits_ecc_correctability; + + pr_info("ONFI probe : Found an ONFI " + "compliant device %s\n", + onfi_param_page_ptr->device_model); + + /* Temporary hack for MT29F4G08ABC device. + * Since the device is not properly adhering + * to ONFi specification it is reporting + * as 16 bit device though it is 8 bit device!!! + */ + if (!strncmp(onfi_param_page_ptr->device_model, + "MT29F4G08ABC", 12)) + supported_flash.widebus = 0; + } + } + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + msm_nand_release_dma_buffer(chip, onfi_param_info_buf, + ONFI_PARAM_INFO_LENGTH); + msm_nand_release_dma_buffer(chip, onfi_identifier_buf, + ONFI_IDENTIFIER_LENGTH); + + return err; +} + +static int msm_nand_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct msm_nand_chip *chip = mtd->priv; + + struct { + dmov_s cmd[8 * 5 + 2]; + unsigned cmdptr; + struct { + uint32_t cmd; + uint32_t addr0; + uint32_t addr1; + uint32_t chipsel; + uint32_t cfg0; + uint32_t cfg1; + uint32_t eccbchcfg; + uint32_t exec; + uint32_t ecccfg; + struct { + uint32_t flash_status; + uint32_t buffer_status; + } result[8]; + } data; + } *dma_buffer; + dmov_s *cmd; + unsigned n; + unsigned page = 0; + uint32_t oob_len; + uint32_t sectordatasize; + uint32_t sectoroobsize; + int err, pageerr, rawerr; + dma_addr_t data_dma_addr = 0; + dma_addr_t oob_dma_addr = 0; + dma_addr_t data_dma_addr_curr = 0; + dma_addr_t oob_dma_addr_curr = 0; + uint32_t oob_col = 0; + unsigned page_count; + unsigned pages_read = 0; + unsigned start_sector = 0; + uint32_t ecc_errors; + uint32_t total_ecc_errors = 0; + unsigned cwperpage; +#if VERBOSE + pr_info("=================================================" + "================\n"); + pr_info("%s:\nfrom 0x%llx mode %d\ndatbuf 0x%p datlen 0x%x" + "\noobbuf 0x%p ooblen 0x%x\n", + __func__, from, ops->mode, ops->datbuf, ops->len, + ops->oobbuf, ops->ooblen); +#endif + + if (mtd->writesize == 2048) + page = from >> 11; + + if (mtd->writesize == 4096) + page = from >> 12; + + oob_len = ops->ooblen; + cwperpage = (mtd->writesize >> 9); + + if (from & (mtd->writesize - 1)) { + pr_err("%s: unsupported from, 0x%llx\n", + __func__, from); + return -EINVAL; + } + if (ops->mode != MTD_OOB_RAW) { + if (ops->datbuf != NULL && (ops->len % mtd->writesize) != 0) { + /* when ops->datbuf is NULL, ops->len can be ooblen */ + pr_err("%s: unsupported ops->len, %d\n", + __func__, ops->len); + return -EINVAL; + } + } else { + if (ops->datbuf != NULL && + (ops->len % (mtd->writesize + mtd->oobsize)) != 0) { + pr_err("%s: unsupported ops->len," + " %d for MTD_OOB_RAW\n", __func__, ops->len); + return -EINVAL; + } + } + + if (ops->mode != MTD_OOB_RAW && ops->ooblen != 0 && ops->ooboffs != 0) { + pr_err("%s: unsupported ops->ooboffs, %d\n", + __func__, ops->ooboffs); + return -EINVAL; + } + + if (ops->oobbuf && !ops->datbuf && ops->mode == MTD_OOB_AUTO) + start_sector = cwperpage - 1; + + if (ops->oobbuf && !ops->datbuf) { + page_count = ops->ooblen / ((ops->mode == MTD_OOB_AUTO) ? + mtd->oobavail : mtd->oobsize); + if ((page_count == 0) && (ops->ooblen)) + page_count = 1; + } else if (ops->mode != MTD_OOB_RAW) + page_count = ops->len / mtd->writesize; + else + page_count = ops->len / (mtd->writesize + mtd->oobsize); + + if (ops->datbuf) { + data_dma_addr_curr = data_dma_addr = + msm_nand_dma_map(chip->dev, ops->datbuf, ops->len, + DMA_FROM_DEVICE); + if (dma_mapping_error(chip->dev, data_dma_addr)) { + pr_err("msm_nand_read_oob: failed to get dma addr " + "for %p\n", ops->datbuf); + return -EIO; + } + } + if (ops->oobbuf) { + memset(ops->oobbuf, 0xff, ops->ooblen); + oob_dma_addr_curr = oob_dma_addr = + msm_nand_dma_map(chip->dev, ops->oobbuf, + ops->ooblen, DMA_BIDIRECTIONAL); + if (dma_mapping_error(chip->dev, oob_dma_addr)) { + pr_err("msm_nand_read_oob: failed to get dma addr " + "for %p\n", ops->oobbuf); + err = -EIO; + goto err_dma_map_oobbuf_failed; + } + } + + wait_event(chip->wait_queue, + (dma_buffer = msm_nand_get_dma_buffer( + chip, sizeof(*dma_buffer)))); + + oob_col = start_sector * chip->cw_size; + if (chip->CFG1 & CFG1_WIDE_FLASH) + oob_col >>= 1; + + err = 0; + while (page_count-- > 0) { + cmd = dma_buffer->cmd; + + /* CMD / ADDR0 / ADDR1 / CHIPSEL program values */ + if (ops->mode != MTD_OOB_RAW) { + dma_buffer->data.cmd = MSM_NAND_CMD_PAGE_READ_ECC; + dma_buffer->data.cfg0 = + (chip->CFG0 & ~(7U << 6)) + | (((cwperpage-1) - start_sector) << 6); + dma_buffer->data.cfg1 = chip->CFG1; + if (enable_bch_ecc) + dma_buffer->data.eccbchcfg = chip->ecc_bch_cfg; + } else { + dma_buffer->data.cmd = MSM_NAND_CMD_PAGE_READ; + dma_buffer->data.cfg0 = (chip->CFG0_RAW + & ~(7U << 6)) | ((cwperpage-1) << 6); + dma_buffer->data.cfg1 = chip->CFG1_RAW | + (chip->CFG1 & CFG1_WIDE_FLASH); + } + + dma_buffer->data.addr0 = (page << 16) | oob_col; + dma_buffer->data.addr1 = (page >> 16) & 0xff; + /* chipsel_0 + enable DM interface */ + dma_buffer->data.chipsel = 0 | 4; + + + /* GO bit for the EXEC register */ + dma_buffer->data.exec = 1; + + + BUILD_BUG_ON(8 != ARRAY_SIZE(dma_buffer->data.result)); + + for (n = start_sector; n < cwperpage; n++) { + /* flash + buffer status return words */ + dma_buffer->data.result[n].flash_status = 0xeeeeeeee; + dma_buffer->data.result[n].buffer_status = 0xeeeeeeee; + + /* block on cmd ready, then + * write CMD / ADDR0 / ADDR1 / CHIPSEL + * regs in a burst + */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = MSM_NAND_FLASH_CMD; + if (n == start_sector) + cmd->len = 16; + else + cmd->len = 4; + cmd++; + + if (n == start_sector) { + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0); + cmd->dst = MSM_NAND_DEV0_CFG0; + if (enable_bch_ecc) + cmd->len = 12; + else + cmd->len = 8; + cmd++; + + dma_buffer->data.ecccfg = chip->ecc_buf_cfg; + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.ecccfg); + cmd->dst = MSM_NAND_EBI2_ECC_BUF_CFG; + cmd->len = 4; + cmd++; + } + + /* kick the execute register */ + cmd->cmd = 0; + cmd->src = + msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = MSM_NAND_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* block on data ready, then + * read the status register + */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_FLASH_STATUS; + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.result[n]); + /* MSM_NAND_FLASH_STATUS + MSM_NAND_BUFFER_STATUS */ + cmd->len = 8; + cmd++; + + /* read data block + * (only valid if status says success) + */ + if (ops->datbuf) { + if (ops->mode != MTD_OOB_RAW) + sectordatasize = (n < (cwperpage - 1)) + ? 516 : (512 - ((cwperpage - 1) << 2)); + else + sectordatasize = chip->cw_size; + + cmd->cmd = 0; + cmd->src = MSM_NAND_FLASH_BUFFER; + cmd->dst = data_dma_addr_curr; + data_dma_addr_curr += sectordatasize; + cmd->len = sectordatasize; + cmd++; + } + + if (ops->oobbuf && (n == (cwperpage - 1) + || ops->mode != MTD_OOB_AUTO)) { + cmd->cmd = 0; + if (n == (cwperpage - 1)) { + cmd->src = MSM_NAND_FLASH_BUFFER + + (512 - ((cwperpage - 1) << 2)); + sectoroobsize = (cwperpage << 2); + if (ops->mode != MTD_OOB_AUTO) + sectoroobsize += + chip->ecc_parity_bytes; + } else { + cmd->src = MSM_NAND_FLASH_BUFFER + 516; + sectoroobsize = chip->ecc_parity_bytes; + } + + cmd->dst = oob_dma_addr_curr; + if (sectoroobsize < oob_len) + cmd->len = sectoroobsize; + else + cmd->len = oob_len; + oob_dma_addr_curr += cmd->len; + oob_len -= cmd->len; + if (cmd->len > 0) + cmd++; + } + } + + BUILD_BUG_ON(8 * 5 + 2 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = + (msm_virt_to_dma(chip, dma_buffer->cmd) >> 3) + | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + + /* if any of the writes failed (0x10), or there + * was a protection violation (0x100), we lose + */ + pageerr = rawerr = 0; + for (n = start_sector; n < cwperpage; n++) { + if (dma_buffer->data.result[n].flash_status & 0x110) { + rawerr = -EIO; + break; + } + } + if (rawerr) { + if (ops->datbuf && ops->mode != MTD_OOB_RAW) { + uint8_t *datbuf = ops->datbuf + + pages_read * mtd->writesize; + + dma_sync_single_for_cpu(chip->dev, + data_dma_addr_curr-mtd->writesize, + mtd->writesize, DMA_BIDIRECTIONAL); + + for (n = 0; n < mtd->writesize; n++) { + /* empty blocks read 0x54 at + * these offsets + */ + if ((n % 516 == 3 || n % 516 == 175) + && datbuf[n] == 0x54) + datbuf[n] = 0xff; + if (datbuf[n] != 0xff) { + pageerr = rawerr; + break; + } + } + + dma_sync_single_for_device(chip->dev, + data_dma_addr_curr-mtd->writesize, + mtd->writesize, DMA_BIDIRECTIONAL); + + } + if (ops->oobbuf) { + dma_sync_single_for_cpu(chip->dev, + oob_dma_addr_curr - (ops->ooblen - oob_len), + ops->ooblen - oob_len, DMA_BIDIRECTIONAL); + + for (n = 0; n < ops->ooblen; n++) { + if (ops->oobbuf[n] != 0xff) { + pageerr = rawerr; + break; + } + } + + dma_sync_single_for_device(chip->dev, + oob_dma_addr_curr - (ops->ooblen - oob_len), + ops->ooblen - oob_len, DMA_BIDIRECTIONAL); + } + } + if (pageerr) { + for (n = start_sector; n < cwperpage; n++) { + if (enable_bch_ecc ? + (dma_buffer->data.result[n].buffer_status & 0x10) : + (dma_buffer->data.result[n].buffer_status & 0x8)) { + /* not thread safe */ + mtd->ecc_stats.failed++; + pageerr = -EBADMSG; + break; + } + } + } + if (!rawerr) { /* check for corretable errors */ + for (n = start_sector; n < cwperpage; n++) { + ecc_errors = enable_bch_ecc ? + (dma_buffer->data.result[n].buffer_status & 0xF) : + (dma_buffer->data.result[n].buffer_status & 0x7); + if (ecc_errors) { + total_ecc_errors += ecc_errors; + /* not thread safe */ + mtd->ecc_stats.corrected += ecc_errors; + if (ecc_errors > 1) + pageerr = -EUCLEAN; + } + } + } + if (pageerr && (pageerr != -EUCLEAN || err == 0)) + err = pageerr; + +#if VERBOSE + if (rawerr && !pageerr) { + pr_err("msm_nand_read_oob %llx %x %x empty page\n", + (loff_t)page * mtd->writesize, ops->len, + ops->ooblen); + } else { + for (n = start_sector; n < cwperpage; n++) + pr_info("flash_status[%d] = %x,\ + buffr_status[%d] = %x\n", + n, dma_buffer->data.result[n].flash_status, + n, dma_buffer->data.result[n].buffer_status); + } +#endif + if (err && err != -EUCLEAN && err != -EBADMSG) + break; + pages_read++; + page++; + } + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + + if (ops->oobbuf) { + dma_unmap_page(chip->dev, oob_dma_addr, + ops->ooblen, DMA_FROM_DEVICE); + } +err_dma_map_oobbuf_failed: + if (ops->datbuf) { + dma_unmap_page(chip->dev, data_dma_addr, + ops->len, DMA_BIDIRECTIONAL); + } + + if (ops->mode != MTD_OOB_RAW) + ops->retlen = mtd->writesize * pages_read; + else + ops->retlen = (mtd->writesize + mtd->oobsize) * + pages_read; + ops->oobretlen = ops->ooblen - oob_len; + if (err) + pr_err("msm_nand_read_oob %llx %x %x failed %d, corrected %d\n", + from, ops->datbuf ? ops->len : 0, ops->ooblen, err, + total_ecc_errors); +#if VERBOSE + pr_info("\n%s: ret %d, retlen %d oobretlen %d\n", + __func__, err, ops->retlen, ops->oobretlen); + + pr_info("===================================================" + "==============\n"); +#endif + return err; +} + +static int msm_nand_read_oob_dualnandc(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct msm_nand_chip *chip = mtd->priv; + + struct { + dmov_s cmd[16 * 6 + 20]; + unsigned cmdptr; + struct { + uint32_t cmd; + uint32_t nandc01_addr0; + uint32_t nandc10_addr0; + uint32_t nandc11_addr1; + uint32_t chipsel_cs0; + uint32_t chipsel_cs1; + uint32_t cfg0; + uint32_t cfg1; + uint32_t eccbchcfg; + uint32_t exec; + uint32_t ecccfg; + uint32_t ebi2_chip_select_cfg0; + uint32_t adm_mux_data_ack_req_nc01; + uint32_t adm_mux_cmd_ack_req_nc01; + uint32_t adm_mux_data_ack_req_nc10; + uint32_t adm_mux_cmd_ack_req_nc10; + uint32_t adm_default_mux; + uint32_t default_ebi2_chip_select_cfg0; + uint32_t nc10_flash_dev_cmd_vld; + uint32_t nc10_flash_dev_cmd1; + uint32_t nc10_flash_dev_cmd_vld_default; + uint32_t nc10_flash_dev_cmd1_default; + struct { + uint32_t flash_status; + uint32_t buffer_status; + } result[16]; + } data; + } *dma_buffer; + dmov_s *cmd; + unsigned n; + unsigned page = 0; + uint32_t oob_len; + uint32_t sectordatasize; + uint32_t sectoroobsize; + int err, pageerr, rawerr; + dma_addr_t data_dma_addr = 0; + dma_addr_t oob_dma_addr = 0; + dma_addr_t data_dma_addr_curr = 0; + dma_addr_t oob_dma_addr_curr = 0; + uint32_t oob_col = 0; + unsigned page_count; + unsigned pages_read = 0; + unsigned start_sector = 0; + uint32_t ecc_errors; + uint32_t total_ecc_errors = 0; + unsigned cwperpage; + unsigned cw_offset = chip->cw_size; +#if VERBOSE + pr_info("=================================================" + "============\n"); + pr_info("%s:\nfrom 0x%llx mode %d\ndatbuf 0x%p datlen 0x%x" + "\noobbuf 0x%p ooblen 0x%x\n\n", + __func__, from, ops->mode, ops->datbuf, + ops->len, ops->oobbuf, ops->ooblen); +#endif + + if (mtd->writesize == 2048) + page = from >> 11; + + if (mtd->writesize == 4096) + page = from >> 12; + + if (interleave_enable) + page = (from >> 1) >> 12; + + oob_len = ops->ooblen; + cwperpage = (mtd->writesize >> 9); + + if (from & (mtd->writesize - 1)) { + pr_err("%s: unsupported from, 0x%llx\n", + __func__, from); + return -EINVAL; + } + if (ops->mode != MTD_OOB_RAW) { + if (ops->datbuf != NULL && (ops->len % mtd->writesize) != 0) { + pr_err("%s: unsupported ops->len, %d\n", + __func__, ops->len); + return -EINVAL; + } + } else { + if (ops->datbuf != NULL && + (ops->len % (mtd->writesize + mtd->oobsize)) != 0) { + pr_err("%s: unsupported ops->len," + " %d for MTD_OOB_RAW\n", __func__, ops->len); + return -EINVAL; + } + } + + if (ops->mode != MTD_OOB_RAW && ops->ooblen != 0 && ops->ooboffs != 0) { + pr_err("%s: unsupported ops->ooboffs, %d\n", + __func__, ops->ooboffs); + return -EINVAL; + } + + if (ops->oobbuf && !ops->datbuf && ops->mode == MTD_OOB_AUTO) + start_sector = cwperpage - 1; + + if (ops->oobbuf && !ops->datbuf) { + page_count = ops->ooblen / ((ops->mode == MTD_OOB_AUTO) ? + mtd->oobavail : mtd->oobsize); + if ((page_count == 0) && (ops->ooblen)) + page_count = 1; + } else if (ops->mode != MTD_OOB_RAW) + page_count = ops->len / mtd->writesize; + else + page_count = ops->len / (mtd->writesize + mtd->oobsize); + + if (ops->datbuf) { + data_dma_addr_curr = data_dma_addr = + msm_nand_dma_map(chip->dev, ops->datbuf, ops->len, + DMA_FROM_DEVICE); + if (dma_mapping_error(chip->dev, data_dma_addr)) { + pr_err("msm_nand_read_oob_dualnandc: " + "failed to get dma addr for %p\n", + ops->datbuf); + return -EIO; + } + } + if (ops->oobbuf) { + memset(ops->oobbuf, 0xff, ops->ooblen); + oob_dma_addr_curr = oob_dma_addr = + msm_nand_dma_map(chip->dev, ops->oobbuf, + ops->ooblen, DMA_BIDIRECTIONAL); + if (dma_mapping_error(chip->dev, oob_dma_addr)) { + pr_err("msm_nand_read_oob_dualnandc: " + "failed to get dma addr for %p\n", + ops->oobbuf); + err = -EIO; + goto err_dma_map_oobbuf_failed; + } + } + + wait_event(chip->wait_queue, + (dma_buffer = msm_nand_get_dma_buffer( + chip, sizeof(*dma_buffer)))); + + oob_col = start_sector * chip->cw_size; + if (chip->CFG1 & CFG1_WIDE_FLASH) { + oob_col >>= 1; + cw_offset >>= 1; + } + + err = 0; + while (page_count-- > 0) { + cmd = dma_buffer->cmd; + + if (ops->mode != MTD_OOB_RAW) { + dma_buffer->data.cmd = MSM_NAND_CMD_PAGE_READ_ECC; + if (start_sector == (cwperpage - 1)) { + dma_buffer->data.cfg0 = (chip->CFG0 & + ~(7U << 6)); + } else { + dma_buffer->data.cfg0 = (chip->CFG0 & + ~(7U << 6)) + | (((cwperpage >> 1)-1) << 6); + } + dma_buffer->data.cfg1 = chip->CFG1; + if (enable_bch_ecc) + dma_buffer->data.eccbchcfg = chip->ecc_bch_cfg; + } else { + dma_buffer->data.cmd = MSM_NAND_CMD_PAGE_READ; + dma_buffer->data.cfg0 = ((chip->CFG0_RAW & + ~(7U << 6)) | ((((cwperpage >> 1)-1) << 6))); + dma_buffer->data.cfg1 = chip->CFG1_RAW | + (chip->CFG1 & CFG1_WIDE_FLASH); + } + + if (!interleave_enable) { + if (start_sector == (cwperpage - 1)) { + dma_buffer->data.nandc10_addr0 = + (page << 16) | oob_col; + dma_buffer->data.nc10_flash_dev_cmd_vld = 0xD; + dma_buffer->data.nc10_flash_dev_cmd1 = + 0xF00F3000; + } else { + dma_buffer->data.nandc01_addr0 = page << 16; + /* NC10 ADDR0 points to the next code word */ + dma_buffer->data.nandc10_addr0 = (page << 16) | + cw_offset; + dma_buffer->data.nc10_flash_dev_cmd_vld = 0x1D; + dma_buffer->data.nc10_flash_dev_cmd1 = + 0xF00FE005; + } + } else { + dma_buffer->data.nandc01_addr0 = + dma_buffer->data.nandc10_addr0 = + (page << 16) | oob_col; + } + /* ADDR1 */ + dma_buffer->data.nandc11_addr1 = (page >> 16) & 0xff; + + dma_buffer->data.adm_mux_data_ack_req_nc01 = 0x00000A3C; + dma_buffer->data.adm_mux_cmd_ack_req_nc01 = 0x0000053C; + dma_buffer->data.adm_mux_data_ack_req_nc10 = 0x00000F28; + dma_buffer->data.adm_mux_cmd_ack_req_nc10 = 0x00000F14; + dma_buffer->data.adm_default_mux = 0x00000FC0; + dma_buffer->data.nc10_flash_dev_cmd_vld_default = 0x1D; + dma_buffer->data.nc10_flash_dev_cmd1_default = 0xF00F3000; + + dma_buffer->data.ebi2_chip_select_cfg0 = 0x00000805; + dma_buffer->data.default_ebi2_chip_select_cfg0 = 0x00000801; + + /* chipsel_0 + enable DM interface */ + dma_buffer->data.chipsel_cs0 = (1<<4) | 4; + /* chipsel_1 + enable DM interface */ + dma_buffer->data.chipsel_cs1 = (1<<4) | 5; + + /* GO bit for the EXEC register */ + dma_buffer->data.exec = 1; + + BUILD_BUG_ON(16 != ARRAY_SIZE(dma_buffer->data.result)); + + for (n = start_sector; n < cwperpage; n++) { + /* flash + buffer status return words */ + dma_buffer->data.result[n].flash_status = 0xeeeeeeee; + dma_buffer->data.result[n].buffer_status = 0xeeeeeeee; + + if (n == start_sector) { + if (!interleave_enable) { + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.nc10_flash_dev_cmd_vld); + cmd->dst = NC10(MSM_NAND_DEV_CMD_VLD); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nc10_flash_dev_cmd1); + cmd->dst = NC10(MSM_NAND_DEV_CMD1); + cmd->len = 4; + cmd++; + + /* NC01, NC10 --> ADDR1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nandc11_addr1); + cmd->dst = NC11(MSM_NAND_ADDR1); + cmd->len = 8; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0); + cmd->dst = NC11(MSM_NAND_DEV0_CFG0); + if (enable_bch_ecc) + cmd->len = 12; + else + cmd->len = 8; + cmd++; + } else { + /* enable CS0 & CS1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.ebi2_chip_select_cfg0); + cmd->dst = EBI2_CHIP_SELECT_CFG0; + cmd->len = 4; + cmd++; + + /* NC01, NC10 --> ADDR1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nandc11_addr1); + cmd->dst = NC11(MSM_NAND_ADDR1); + cmd->len = 4; + cmd++; + + /* Enable CS0 for NC01 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.chipsel_cs0); + cmd->dst = + NC01(MSM_NAND_FLASH_CHIP_SELECT); + cmd->len = 4; + cmd++; + + /* Enable CS1 for NC10 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.chipsel_cs1); + cmd->dst = + NC10(MSM_NAND_FLASH_CHIP_SELECT); + cmd->len = 4; + cmd++; + + /* config DEV0_CFG0 & CFG1 for CS0 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0); + cmd->dst = NC01(MSM_NAND_DEV0_CFG0); + cmd->len = 8; + cmd++; + + /* config DEV1_CFG0 & CFG1 for CS1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0); + cmd->dst = NC10(MSM_NAND_DEV1_CFG0); + cmd->len = 8; + cmd++; + } + + dma_buffer->data.ecccfg = chip->ecc_buf_cfg; + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.ecccfg); + cmd->dst = NC11(MSM_NAND_EBI2_ECC_BUF_CFG); + cmd->len = 4; + cmd++; + + /* if 'only' the last code word */ + if (n == cwperpage - 1) { + /* MASK CMD ACK/REQ --> NC01 (0x53C)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.adm_mux_cmd_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* CMD */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cmd); + cmd->dst = NC10(MSM_NAND_FLASH_CMD); + cmd->len = 4; + cmd++; + + /* NC10 --> ADDR0 ( 0x0 ) */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nandc10_addr0); + cmd->dst = NC10(MSM_NAND_ADDR0); + cmd->len = 4; + cmd++; + + /* kick the execute reg for NC10 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.exec); + cmd->dst = NC10(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + + /* MASK DATA ACK/REQ --> NC01 (0xA3C)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.adm_mux_data_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* block on data ready from NC10, then + * read the status register + */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = NC10(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.result[n]); + /* MSM_NAND_FLASH_STATUS + + * MSM_NAND_BUFFER_STATUS + */ + cmd->len = 8; + cmd++; + } else { + /* NC01 --> ADDR0 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nandc01_addr0); + cmd->dst = NC01(MSM_NAND_ADDR0); + cmd->len = 4; + cmd++; + + /* NC10 --> ADDR1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nandc10_addr0); + cmd->dst = NC10(MSM_NAND_ADDR0); + cmd->len = 4; + cmd++; + + /* MASK CMD ACK/REQ --> NC10 (0xF14)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.adm_mux_cmd_ack_req_nc10); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* CMD */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cmd); + cmd->dst = NC01(MSM_NAND_FLASH_CMD); + cmd->len = 4; + cmd++; + + /* kick the execute register for NC01*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.exec); + cmd->dst = NC01(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + } + } + + /* read data block + * (only valid if status says success) + */ + if (ops->datbuf || (ops->oobbuf && + ops->mode != MTD_OOB_AUTO)) { + if (ops->mode != MTD_OOB_RAW) + sectordatasize = (n < (cwperpage - 1)) + ? 516 : (512 - ((cwperpage - 1) << 2)); + else + sectordatasize = chip->cw_size; + + if (n % 2 == 0) { + /* MASK DATA ACK/REQ --> NC10 (0xF28)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.adm_mux_data_ack_req_nc10); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* block on data ready from NC01, then + * read the status register + */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = NC01(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.result[n]); + /* MSM_NAND_FLASH_STATUS + + * MSM_NAND_BUFFER_STATUS + */ + cmd->len = 8; + cmd++; + + /* MASK CMD ACK/REQ --> NC01 (0x53C)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.adm_mux_cmd_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* CMD */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cmd); + cmd->dst = NC10(MSM_NAND_FLASH_CMD); + cmd->len = 4; + cmd++; + + /* kick the execute register for NC10 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.exec); + cmd->dst = NC10(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + + /* Read only when there is data + * buffer + */ + if (ops->datbuf) { + cmd->cmd = 0; + cmd->src = + NC01(MSM_NAND_FLASH_BUFFER); + cmd->dst = data_dma_addr_curr; + data_dma_addr_curr += + sectordatasize; + cmd->len = sectordatasize; + cmd++; + } + } else { + /* MASK DATA ACK/REQ --> + * NC01 (0xA3C) + */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.adm_mux_data_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* block on data ready from NC10 + * then read the status register + */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = + NC10(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.result[n]); + /* MSM_NAND_FLASH_STATUS + + * MSM_NAND_BUFFER_STATUS + */ + cmd->len = 8; + cmd++; + if (n != cwperpage - 1) { + /* MASK CMD ACK/REQ --> + * NC10 (0xF14) + */ + cmd->cmd = 0; + cmd->src = + msm_virt_to_dma(chip, + &dma_buffer-> + data.adm_mux_cmd_ack_req_nc10); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* CMD */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cmd); + cmd->dst = + NC01(MSM_NAND_FLASH_CMD); + cmd->len = 4; + cmd++; + + /* EXEC */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.exec); + cmd->dst = + NC01(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + } + + /* Read only when there is data + * buffer + */ + if (ops->datbuf) { + cmd->cmd = 0; + cmd->src = + NC10(MSM_NAND_FLASH_BUFFER); + cmd->dst = data_dma_addr_curr; + data_dma_addr_curr += + sectordatasize; + cmd->len = sectordatasize; + cmd++; + } + } + } + + if (ops->oobbuf && (n == (cwperpage - 1) + || ops->mode != MTD_OOB_AUTO)) { + cmd->cmd = 0; + if (n == (cwperpage - 1)) { + /* Use NC10 for reading the + * last codeword!!! + */ + cmd->src = NC10(MSM_NAND_FLASH_BUFFER) + + (512 - ((cwperpage - 1) << 2)); + sectoroobsize = (cwperpage << 2); + if (ops->mode != MTD_OOB_AUTO) + sectoroobsize += + chip->ecc_parity_bytes; + } else { + if (n % 2 == 0) + cmd->src = + NC01(MSM_NAND_FLASH_BUFFER) + + 516; + else + cmd->src = + NC10(MSM_NAND_FLASH_BUFFER) + + 516; + sectoroobsize = chip->ecc_parity_bytes; + } + cmd->dst = oob_dma_addr_curr; + if (sectoroobsize < oob_len) + cmd->len = sectoroobsize; + else + cmd->len = oob_len; + oob_dma_addr_curr += cmd->len; + oob_len -= cmd->len; + if (cmd->len > 0) + cmd++; + } + } + /* ADM --> Default mux state (0xFC0) */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_default_mux); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + if (!interleave_enable) { + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nc10_flash_dev_cmd_vld_default); + cmd->dst = NC10(MSM_NAND_DEV_CMD_VLD); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nc10_flash_dev_cmd1_default); + cmd->dst = NC10(MSM_NAND_DEV_CMD1); + cmd->len = 4; + cmd++; + } else { + /* disable CS1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.default_ebi2_chip_select_cfg0); + cmd->dst = EBI2_CHIP_SELECT_CFG0; + cmd->len = 4; + cmd++; + } + + BUILD_BUG_ON(16 * 6 + 20 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = + (msm_virt_to_dma(chip, dma_buffer->cmd) >> 3) + | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + + /* if any of the writes failed (0x10), or there + * was a protection violation (0x100), we lose + */ + pageerr = rawerr = 0; + for (n = start_sector; n < cwperpage; n++) { + if (dma_buffer->data.result[n].flash_status & 0x110) { + rawerr = -EIO; + break; + } + } + if (rawerr) { + if (ops->datbuf && ops->mode != MTD_OOB_RAW) { + uint8_t *datbuf = ops->datbuf + + pages_read * mtd->writesize; + + dma_sync_single_for_cpu(chip->dev, + data_dma_addr_curr-mtd->writesize, + mtd->writesize, DMA_BIDIRECTIONAL); + + for (n = 0; n < mtd->writesize; n++) { + /* empty blocks read 0x54 at + * these offsets + */ + if ((n % 516 == 3 || n % 516 == 175) + && datbuf[n] == 0x54) + datbuf[n] = 0xff; + if (datbuf[n] != 0xff) { + pageerr = rawerr; + break; + } + } + + dma_sync_single_for_device(chip->dev, + data_dma_addr_curr-mtd->writesize, + mtd->writesize, DMA_BIDIRECTIONAL); + + } + if (ops->oobbuf) { + dma_sync_single_for_cpu(chip->dev, + oob_dma_addr_curr - (ops->ooblen - oob_len), + ops->ooblen - oob_len, DMA_BIDIRECTIONAL); + + for (n = 0; n < ops->ooblen; n++) { + if (ops->oobbuf[n] != 0xff) { + pageerr = rawerr; + break; + } + } + + dma_sync_single_for_device(chip->dev, + oob_dma_addr_curr - (ops->ooblen - oob_len), + ops->ooblen - oob_len, DMA_BIDIRECTIONAL); + } + } + if (pageerr) { + for (n = start_sector; n < cwperpage; n++) { + if (dma_buffer->data.result[n].buffer_status + & MSM_NAND_BUF_STAT_UNCRCTBL_ERR) { + /* not thread safe */ + mtd->ecc_stats.failed++; + pageerr = -EBADMSG; + break; + } + } + } + if (!rawerr) { /* check for corretable errors */ + for (n = start_sector; n < cwperpage; n++) { + ecc_errors = dma_buffer->data. + result[n].buffer_status + & MSM_NAND_BUF_STAT_NUM_ERR_MASK; + if (ecc_errors) { + total_ecc_errors += ecc_errors; + /* not thread safe */ + mtd->ecc_stats.corrected += ecc_errors; + if (ecc_errors > 1) + pageerr = -EUCLEAN; + } + } + } + if (pageerr && (pageerr != -EUCLEAN || err == 0)) + err = pageerr; + +#if VERBOSE + if (rawerr && !pageerr) { + pr_err("msm_nand_read_oob_dualnandc " + "%llx %x %x empty page\n", + (loff_t)page * mtd->writesize, ops->len, + ops->ooblen); + } else { + for (n = start_sector; n < cwperpage; n++) { + if (n%2) { + pr_info("NC10: flash_status[%d] = %x, " + "buffr_status[%d] = %x\n", + n, dma_buffer-> + data.result[n].flash_status, + n, dma_buffer-> + data.result[n].buffer_status); + } else { + pr_info("NC01: flash_status[%d] = %x, " + "buffr_status[%d] = %x\n", + n, dma_buffer-> + data.result[n].flash_status, + n, dma_buffer-> + data.result[n].buffer_status); + } + } + } +#endif + if (err && err != -EUCLEAN && err != -EBADMSG) + break; + pages_read++; + page++; + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + + if (ops->oobbuf) { + dma_unmap_page(chip->dev, oob_dma_addr, + ops->ooblen, DMA_FROM_DEVICE); + } +err_dma_map_oobbuf_failed: + if (ops->datbuf) { + dma_unmap_page(chip->dev, data_dma_addr, + ops->len, DMA_BIDIRECTIONAL); + } + + if (ops->mode != MTD_OOB_RAW) + ops->retlen = mtd->writesize * pages_read; + else + ops->retlen = (mtd->writesize + mtd->oobsize) * + pages_read; + ops->oobretlen = ops->ooblen - oob_len; + if (err) + pr_err("msm_nand_read_oob_dualnandc " + "%llx %x %x failed %d, corrected %d\n", + from, ops->datbuf ? ops->len : 0, ops->ooblen, err, + total_ecc_errors); +#if VERBOSE + pr_info("\n%s: ret %d, retlen %d oobretlen %d\n", + __func__, err, ops->retlen, ops->oobretlen); + + pr_info("===================================================" + "==========\n"); +#endif + return err; +} + +static int +msm_nand_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + int ret; + struct mtd_oob_ops ops; + + /* printk("msm_nand_read %llx %x\n", from, len); */ + + ops.mode = MTD_OOB_PLACE; + ops.len = len; + ops.retlen = 0; + ops.ooblen = 0; + ops.datbuf = buf; + ops.oobbuf = NULL; + if (!dual_nand_ctlr_present) + ret = msm_nand_read_oob(mtd, from, &ops); + else + ret = msm_nand_read_oob_dualnandc(mtd, from, &ops); + *retlen = ops.retlen; + return ret; +} + +static int +msm_nand_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) +{ + struct msm_nand_chip *chip = mtd->priv; + struct { + dmov_s cmd[8 * 7 + 2]; + unsigned cmdptr; + struct { + uint32_t cmd; + uint32_t addr0; + uint32_t addr1; + uint32_t chipsel; + uint32_t cfg0; + uint32_t cfg1; + uint32_t eccbchcfg; + uint32_t exec; + uint32_t ecccfg; + uint32_t clrfstatus; + uint32_t clrrstatus; + uint32_t flash_status[8]; + } data; + } *dma_buffer; + dmov_s *cmd; + unsigned n; + unsigned page = 0; + uint32_t oob_len; + uint32_t sectordatawritesize; + int err = 0; + dma_addr_t data_dma_addr = 0; + dma_addr_t oob_dma_addr = 0; + dma_addr_t data_dma_addr_curr = 0; + dma_addr_t oob_dma_addr_curr = 0; + unsigned page_count; + unsigned pages_written = 0; + unsigned cwperpage; +#if VERBOSE + pr_info("=================================================" + "================\n"); + pr_info("%s:\nto 0x%llx mode %d\ndatbuf 0x%p datlen 0x%x" + "\noobbuf 0x%p ooblen 0x%x\n", + __func__, to, ops->mode, ops->datbuf, ops->len, + ops->oobbuf, ops->ooblen); +#endif + + if (mtd->writesize == 2048) + page = to >> 11; + + if (mtd->writesize == 4096) + page = to >> 12; + + oob_len = ops->ooblen; + cwperpage = (mtd->writesize >> 9); + + if (to & (mtd->writesize - 1)) { + pr_err("%s: unsupported to, 0x%llx\n", __func__, to); + return -EINVAL; + } + + if (ops->mode != MTD_OOB_RAW) { + if (ops->ooblen != 0 && ops->mode != MTD_OOB_AUTO) { + pr_err("%s: unsupported ops->mode,%d\n", + __func__, ops->mode); + return -EINVAL; + } + if ((ops->len % mtd->writesize) != 0) { + pr_err("%s: unsupported ops->len, %d\n", + __func__, ops->len); + return -EINVAL; + } + } else { + if ((ops->len % (mtd->writesize + mtd->oobsize)) != 0) { + pr_err("%s: unsupported ops->len, " + "%d for MTD_OOB_RAW mode\n", + __func__, ops->len); + return -EINVAL; + } + } + + if (ops->datbuf == NULL) { + pr_err("%s: unsupported ops->datbuf == NULL\n", __func__); + return -EINVAL; + } + if (ops->mode != MTD_OOB_RAW && ops->ooblen != 0 && ops->ooboffs != 0) { + pr_err("%s: unsupported ops->ooboffs, %d\n", + __func__, ops->ooboffs); + return -EINVAL; + } + + if (ops->datbuf) { + data_dma_addr_curr = data_dma_addr = + msm_nand_dma_map(chip->dev, ops->datbuf, + ops->len, DMA_TO_DEVICE); + if (dma_mapping_error(chip->dev, data_dma_addr)) { + pr_err("msm_nand_write_oob: failed to get dma addr " + "for %p\n", ops->datbuf); + return -EIO; + } + } + if (ops->oobbuf) { + oob_dma_addr_curr = oob_dma_addr = + msm_nand_dma_map(chip->dev, ops->oobbuf, + ops->ooblen, DMA_TO_DEVICE); + if (dma_mapping_error(chip->dev, oob_dma_addr)) { + pr_err("msm_nand_write_oob: failed to get dma addr " + "for %p\n", ops->oobbuf); + err = -EIO; + goto err_dma_map_oobbuf_failed; + } + } + if (ops->mode != MTD_OOB_RAW) + page_count = ops->len / mtd->writesize; + else + page_count = ops->len / (mtd->writesize + mtd->oobsize); + + wait_event(chip->wait_queue, (dma_buffer = + msm_nand_get_dma_buffer(chip, sizeof(*dma_buffer)))); + + while (page_count-- > 0) { + cmd = dma_buffer->cmd; + + if (ops->mode != MTD_OOB_RAW) { + dma_buffer->data.cfg0 = chip->CFG0; + dma_buffer->data.cfg1 = chip->CFG1; + if (enable_bch_ecc) + dma_buffer->data.eccbchcfg = chip->ecc_bch_cfg; + } else { + dma_buffer->data.cfg0 = (chip->CFG0_RAW & + ~(7U << 6)) | ((cwperpage-1) << 6); + dma_buffer->data.cfg1 = chip->CFG1_RAW | + (chip->CFG1 & CFG1_WIDE_FLASH); + } + + /* CMD / ADDR0 / ADDR1 / CHIPSEL program values */ + dma_buffer->data.cmd = MSM_NAND_CMD_PRG_PAGE; + dma_buffer->data.addr0 = page << 16; + dma_buffer->data.addr1 = (page >> 16) & 0xff; + /* chipsel_0 + enable DM interface */ + dma_buffer->data.chipsel = 0 | 4; + + + /* GO bit for the EXEC register */ + dma_buffer->data.exec = 1; + dma_buffer->data.clrfstatus = 0x00000020; + dma_buffer->data.clrrstatus = 0x000000C0; + + BUILD_BUG_ON(8 != ARRAY_SIZE(dma_buffer->data.flash_status)); + + for (n = 0; n < cwperpage ; n++) { + /* status return words */ + dma_buffer->data.flash_status[n] = 0xeeeeeeee; + /* block on cmd ready, then + * write CMD / ADDR0 / ADDR1 / CHIPSEL regs in a burst + */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = + msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = MSM_NAND_FLASH_CMD; + if (n == 0) + cmd->len = 16; + else + cmd->len = 4; + cmd++; + + if (n == 0) { + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0); + cmd->dst = MSM_NAND_DEV0_CFG0; + if (enable_bch_ecc) + cmd->len = 12; + else + cmd->len = 8; + cmd++; + + dma_buffer->data.ecccfg = chip->ecc_buf_cfg; + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.ecccfg); + cmd->dst = MSM_NAND_EBI2_ECC_BUF_CFG; + cmd->len = 4; + cmd++; + } + + /* write data block */ + if (ops->mode != MTD_OOB_RAW) + sectordatawritesize = (n < (cwperpage - 1)) ? + 516 : (512 - ((cwperpage - 1) << 2)); + else + sectordatawritesize = chip->cw_size; + + cmd->cmd = 0; + cmd->src = data_dma_addr_curr; + data_dma_addr_curr += sectordatawritesize; + cmd->dst = MSM_NAND_FLASH_BUFFER; + cmd->len = sectordatawritesize; + cmd++; + + if (ops->oobbuf) { + if (n == (cwperpage - 1)) { + cmd->cmd = 0; + cmd->src = oob_dma_addr_curr; + cmd->dst = MSM_NAND_FLASH_BUFFER + + (512 - ((cwperpage - 1) << 2)); + if ((cwperpage << 2) < oob_len) + cmd->len = (cwperpage << 2); + else + cmd->len = oob_len; + oob_dma_addr_curr += cmd->len; + oob_len -= cmd->len; + if (cmd->len > 0) + cmd++; + } + if (ops->mode != MTD_OOB_AUTO) { + /* skip ecc bytes in oobbuf */ + if (oob_len < chip->ecc_parity_bytes) { + oob_dma_addr_curr += + chip->ecc_parity_bytes; + oob_len -= + chip->ecc_parity_bytes; + } else { + oob_dma_addr_curr += oob_len; + oob_len = 0; + } + } + } + + /* kick the execute register */ + cmd->cmd = 0; + cmd->src = + msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = MSM_NAND_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* block on data ready, then + * read the status register + */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_FLASH_STATUS; + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.flash_status[n]); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.clrfstatus); + cmd->dst = MSM_NAND_FLASH_STATUS; + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.clrrstatus); + cmd->dst = MSM_NAND_READ_STATUS; + cmd->len = 4; + cmd++; + + } + + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + BUILD_BUG_ON(8 * 7 + 2 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmdptr = + (msm_virt_to_dma(chip, dma_buffer->cmd) >> 3) | + CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR( + msm_virt_to_dma(chip, &dma_buffer->cmdptr))); + mb(); + + /* if any of the writes failed (0x10), or there was a + * protection violation (0x100), or the program success + * bit (0x80) is unset, we lose + */ + err = 0; + for (n = 0; n < cwperpage; n++) { + if (dma_buffer->data.flash_status[n] & 0x110) { + err = -EIO; + break; + } + if (!(dma_buffer->data.flash_status[n] & 0x80)) { + err = -EIO; + break; + } + } + +#if VERBOSE + for (n = 0; n < cwperpage; n++) + pr_info("write pg %d: flash_status[%d] = %x\n", page, + n, dma_buffer->data.flash_status[n]); + +#endif + if (err) + break; + pages_written++; + page++; + } + if (ops->mode != MTD_OOB_RAW) + ops->retlen = mtd->writesize * pages_written; + else + ops->retlen = (mtd->writesize + mtd->oobsize) * pages_written; + + ops->oobretlen = ops->ooblen - oob_len; + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + + if (ops->oobbuf) + dma_unmap_page(chip->dev, oob_dma_addr, + ops->ooblen, DMA_TO_DEVICE); +err_dma_map_oobbuf_failed: + if (ops->datbuf) + dma_unmap_page(chip->dev, data_dma_addr, ops->len, + DMA_TO_DEVICE); + if (err) + pr_err("msm_nand_write_oob %llx %x %x failed %d\n", + to, ops->len, ops->ooblen, err); + +#if VERBOSE + pr_info("\n%s: ret %d, retlen %d oobretlen %d\n", + __func__, err, ops->retlen, ops->oobretlen); + + pr_info("===================================================" + "==============\n"); +#endif + return err; +} + +static int +msm_nand_write_oob_dualnandc(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct msm_nand_chip *chip = mtd->priv; + struct { + dmov_s cmd[16 * 6 + 18]; + unsigned cmdptr; + struct { + uint32_t cmd; + uint32_t nandc01_addr0; + uint32_t nandc10_addr0; + uint32_t nandc11_addr1; + uint32_t chipsel_cs0; + uint32_t chipsel_cs1; + uint32_t cfg0; + uint32_t cfg1; + uint32_t eccbchcfg; + uint32_t exec; + uint32_t ecccfg; + uint32_t cfg0_nc01; + uint32_t ebi2_chip_select_cfg0; + uint32_t adm_mux_data_ack_req_nc01; + uint32_t adm_mux_cmd_ack_req_nc01; + uint32_t adm_mux_data_ack_req_nc10; + uint32_t adm_mux_cmd_ack_req_nc10; + uint32_t adm_default_mux; + uint32_t default_ebi2_chip_select_cfg0; + uint32_t nc01_flash_dev_cmd_vld; + uint32_t nc10_flash_dev_cmd0; + uint32_t nc01_flash_dev_cmd_vld_default; + uint32_t nc10_flash_dev_cmd0_default; + uint32_t flash_status[16]; + uint32_t clrfstatus; + uint32_t clrrstatus; + } data; + } *dma_buffer; + dmov_s *cmd; + unsigned n; + unsigned page = 0; + uint32_t oob_len; + uint32_t sectordatawritesize; + int err = 0; + dma_addr_t data_dma_addr = 0; + dma_addr_t oob_dma_addr = 0; + dma_addr_t data_dma_addr_curr = 0; + dma_addr_t oob_dma_addr_curr = 0; + unsigned page_count; + unsigned pages_written = 0; + unsigned cwperpage; + unsigned cw_offset = chip->cw_size; +#if VERBOSE + pr_info("=================================================" + "============\n"); + pr_info("%s:\nto 0x%llx mode %d\ndatbuf 0x%p datlen 0x%x" + "\noobbuf 0x%p ooblen 0x%x\n\n", + __func__, to, ops->mode, ops->datbuf, ops->len, + ops->oobbuf, ops->ooblen); +#endif + + if (mtd->writesize == 2048) + page = to >> 11; + + if (mtd->writesize == 4096) + page = to >> 12; + + if (interleave_enable) + page = (to >> 1) >> 12; + + oob_len = ops->ooblen; + cwperpage = (mtd->writesize >> 9); + + if (to & (mtd->writesize - 1)) { + pr_err("%s: unsupported to, 0x%llx\n", __func__, to); + return -EINVAL; + } + + if (ops->mode != MTD_OOB_RAW) { + if (ops->ooblen != 0 && ops->mode != MTD_OOB_AUTO) { + pr_err("%s: unsupported ops->mode,%d\n", + __func__, ops->mode); + return -EINVAL; + } + if ((ops->len % mtd->writesize) != 0) { + pr_err("%s: unsupported ops->len, %d\n", + __func__, ops->len); + return -EINVAL; + } + } else { + if ((ops->len % (mtd->writesize + mtd->oobsize)) != 0) { + pr_err("%s: unsupported ops->len, " + "%d for MTD_OOB_RAW mode\n", + __func__, ops->len); + return -EINVAL; + } + } + + if (ops->datbuf == NULL) { + pr_err("%s: unsupported ops->datbuf == NULL\n", __func__); + return -EINVAL; + } + + if (ops->mode != MTD_OOB_RAW && ops->ooblen != 0 && ops->ooboffs != 0) { + pr_err("%s: unsupported ops->ooboffs, %d\n", + __func__, ops->ooboffs); + return -EINVAL; + } + + if (ops->datbuf) { + data_dma_addr_curr = data_dma_addr = + msm_nand_dma_map(chip->dev, ops->datbuf, + ops->len, DMA_TO_DEVICE); + if (dma_mapping_error(chip->dev, data_dma_addr)) { + pr_err("msm_nand_write_oob_dualnandc:" + "failed to get dma addr " + "for %p\n", ops->datbuf); + return -EIO; + } + } + if (ops->oobbuf) { + oob_dma_addr_curr = oob_dma_addr = + msm_nand_dma_map(chip->dev, ops->oobbuf, + ops->ooblen, DMA_TO_DEVICE); + if (dma_mapping_error(chip->dev, oob_dma_addr)) { + pr_err("msm_nand_write_oob_dualnandc:" + "failed to get dma addr " + "for %p\n", ops->oobbuf); + err = -EIO; + goto err_dma_map_oobbuf_failed; + } + } + if (ops->mode != MTD_OOB_RAW) + page_count = ops->len / mtd->writesize; + else + page_count = ops->len / (mtd->writesize + mtd->oobsize); + + wait_event(chip->wait_queue, (dma_buffer = + msm_nand_get_dma_buffer(chip, sizeof(*dma_buffer)))); + + if (chip->CFG1 & CFG1_WIDE_FLASH) + cw_offset >>= 1; + + dma_buffer->data.ebi2_chip_select_cfg0 = 0x00000805; + dma_buffer->data.adm_mux_data_ack_req_nc01 = 0x00000A3C; + dma_buffer->data.adm_mux_cmd_ack_req_nc01 = 0x0000053C; + dma_buffer->data.adm_mux_data_ack_req_nc10 = 0x00000F28; + dma_buffer->data.adm_mux_cmd_ack_req_nc10 = 0x00000F14; + dma_buffer->data.adm_default_mux = 0x00000FC0; + dma_buffer->data.default_ebi2_chip_select_cfg0 = 0x00000801; + dma_buffer->data.nc01_flash_dev_cmd_vld = 0x9; + dma_buffer->data.nc10_flash_dev_cmd0 = 0x1085D060; + dma_buffer->data.nc01_flash_dev_cmd_vld_default = 0x1D; + dma_buffer->data.nc10_flash_dev_cmd0_default = 0x1080D060; + dma_buffer->data.clrfstatus = 0x00000020; + dma_buffer->data.clrrstatus = 0x000000C0; + + while (page_count-- > 0) { + cmd = dma_buffer->cmd; + + if (ops->mode != MTD_OOB_RAW) { + dma_buffer->data.cfg0 = ((chip->CFG0 & ~(7U << 6)) + & ~(1 << 4)) | ((((cwperpage >> 1)-1)) << 6); + dma_buffer->data.cfg1 = chip->CFG1; + if (enable_bch_ecc) + dma_buffer->data.eccbchcfg = chip->ecc_bch_cfg; + } else { + dma_buffer->data.cfg0 = ((chip->CFG0_RAW & + ~(7U << 6)) & ~(1 << 4)) | (((cwperpage >> 1)-1) << 6); + dma_buffer->data.cfg1 = chip->CFG1_RAW | + (chip->CFG1 & CFG1_WIDE_FLASH); + } + + /* Disables the automatic issuing of the read + * status command for first NAND controller. + */ + if (!interleave_enable) + dma_buffer->data.cfg0_nc01 = dma_buffer->data.cfg0 + | (1 << 4); + else + dma_buffer->data.cfg0 |= (1 << 4); + + dma_buffer->data.cmd = MSM_NAND_CMD_PRG_PAGE; + dma_buffer->data.chipsel_cs0 = (1<<4) | 4; + dma_buffer->data.chipsel_cs1 = (1<<4) | 5; + + /* GO bit for the EXEC register */ + dma_buffer->data.exec = 1; + + if (!interleave_enable) { + dma_buffer->data.nandc01_addr0 = (page << 16) | 0x0; + /* NC10 ADDR0 points to the next code word */ + dma_buffer->data.nandc10_addr0 = + (page << 16) | cw_offset; + } else { + dma_buffer->data.nandc01_addr0 = + dma_buffer->data.nandc10_addr0 = (page << 16) | 0x0; + } + /* ADDR1 */ + dma_buffer->data.nandc11_addr1 = (page >> 16) & 0xff; + + BUILD_BUG_ON(16 != ARRAY_SIZE(dma_buffer->data.flash_status)); + + for (n = 0; n < cwperpage; n++) { + /* status return words */ + dma_buffer->data.flash_status[n] = 0xeeeeeeee; + + if (n == 0) { + if (!interleave_enable) { + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.nc01_flash_dev_cmd_vld); + cmd->dst = NC01(MSM_NAND_DEV_CMD_VLD); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nc10_flash_dev_cmd0); + cmd->dst = NC10(MSM_NAND_DEV_CMD0); + cmd->len = 4; + cmd++; + + /* common settings for both NC01 & NC10 + * NC01, NC10 --> ADDR1 / CHIPSEL + */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nandc11_addr1); + cmd->dst = NC11(MSM_NAND_ADDR1); + cmd->len = 8; + cmd++; + + /* Disables the automatic issue of the + * read status command after the write + * operation. + */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0_nc01); + cmd->dst = NC01(MSM_NAND_DEV0_CFG0); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0); + cmd->dst = NC10(MSM_NAND_DEV0_CFG0); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg1); + cmd->dst = NC11(MSM_NAND_DEV0_CFG1); + if (enable_bch_ecc) + cmd->len = 8; + else + cmd->len = 4; + cmd++; + } else { + /* enable CS1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.ebi2_chip_select_cfg0); + cmd->dst = EBI2_CHIP_SELECT_CFG0; + cmd->len = 4; + cmd++; + + /* NC11 --> ADDR1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nandc11_addr1); + cmd->dst = NC11(MSM_NAND_ADDR1); + cmd->len = 4; + cmd++; + + /* Enable CS0 for NC01 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.chipsel_cs0); + cmd->dst = + NC01(MSM_NAND_FLASH_CHIP_SELECT); + cmd->len = 4; + cmd++; + + /* Enable CS1 for NC10 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.chipsel_cs1); + cmd->dst = + NC10(MSM_NAND_FLASH_CHIP_SELECT); + cmd->len = 4; + cmd++; + + /* config DEV0_CFG0 & CFG1 for CS0 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0); + cmd->dst = NC01(MSM_NAND_DEV0_CFG0); + cmd->len = 8; + cmd++; + + /* config DEV1_CFG0 & CFG1 for CS1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cfg0); + cmd->dst = NC10(MSM_NAND_DEV1_CFG0); + cmd->len = 8; + cmd++; + } + + dma_buffer->data.ecccfg = chip->ecc_buf_cfg; + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.ecccfg); + cmd->dst = NC11(MSM_NAND_EBI2_ECC_BUF_CFG); + cmd->len = 4; + cmd++; + + /* NC01 --> ADDR0 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nandc01_addr0); + cmd->dst = NC01(MSM_NAND_ADDR0); + cmd->len = 4; + cmd++; + + /* NC10 --> ADDR0 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nandc10_addr0); + cmd->dst = NC10(MSM_NAND_ADDR0); + cmd->len = 4; + cmd++; + } + + if (n % 2 == 0) { + /* MASK CMD ACK/REQ --> NC10 (0xF14)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_cmd_ack_req_nc10); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* CMD */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cmd); + cmd->dst = NC01(MSM_NAND_FLASH_CMD); + cmd->len = 4; + cmd++; + } else { + /* MASK CMD ACK/REQ --> NC01 (0x53C)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_cmd_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* CMD */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.cmd); + cmd->dst = NC10(MSM_NAND_FLASH_CMD); + cmd->len = 4; + cmd++; + } + + if (ops->mode != MTD_OOB_RAW) + sectordatawritesize = (n < (cwperpage - 1)) ? + 516 : (512 - ((cwperpage - 1) << 2)); + else + sectordatawritesize = chip->cw_size; + + cmd->cmd = 0; + cmd->src = data_dma_addr_curr; + data_dma_addr_curr += sectordatawritesize; + + if (n % 2 == 0) + cmd->dst = NC01(MSM_NAND_FLASH_BUFFER); + else + cmd->dst = NC10(MSM_NAND_FLASH_BUFFER); + cmd->len = sectordatawritesize; + cmd++; + + if (ops->oobbuf) { + if (n == (cwperpage - 1)) { + cmd->cmd = 0; + cmd->src = oob_dma_addr_curr; + cmd->dst = NC10(MSM_NAND_FLASH_BUFFER) + + (512 - ((cwperpage - 1) << 2)); + if ((cwperpage << 2) < oob_len) + cmd->len = (cwperpage << 2); + else + cmd->len = oob_len; + oob_dma_addr_curr += cmd->len; + oob_len -= cmd->len; + if (cmd->len > 0) + cmd++; + } + if (ops->mode != MTD_OOB_AUTO) { + /* skip ecc bytes in oobbuf */ + if (oob_len < chip->ecc_parity_bytes) { + oob_dma_addr_curr += + chip->ecc_parity_bytes; + oob_len -= + chip->ecc_parity_bytes; + } else { + oob_dma_addr_curr += oob_len; + oob_len = 0; + } + } + } + + if (n % 2 == 0) { + if (n != 0) { + /* MASK DATA ACK/REQ --> NC01 (0xA3C)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer-> + data.adm_mux_data_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* block on data ready from NC10, then + * read the status register + */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = NC10(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.flash_status[n-1]); + cmd->len = 4; + cmd++; + } + /* kick the NC01 execute register */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.exec); + cmd->dst = NC01(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + } else { + /* MASK DATA ACK/REQ --> NC10 (0xF28)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_data_ack_req_nc10); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* block on data ready from NC01, then + * read the status register + */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = NC01(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.flash_status[n-1]); + cmd->len = 4; + cmd++; + + /* kick the execute register */ + cmd->cmd = 0; + cmd->src = + msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = NC10(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + } + } + + /* MASK DATA ACK/REQ --> NC01 (0xA3C)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_data_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* we should process outstanding request */ + /* block on data ready, then + * read the status register + */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = NC10(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.flash_status[n-1]); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.clrfstatus); + cmd->dst = NC11(MSM_NAND_FLASH_STATUS); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.clrrstatus); + cmd->dst = NC11(MSM_NAND_READ_STATUS); + cmd->len = 4; + cmd++; + + /* MASK DATA ACK/REQ --> NC01 (0xFC0)*/ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_default_mux); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + if (!interleave_enable) { + /* setting to defalut values back */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nc01_flash_dev_cmd_vld_default); + cmd->dst = NC01(MSM_NAND_DEV_CMD_VLD); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.nc10_flash_dev_cmd0_default); + cmd->dst = NC10(MSM_NAND_DEV_CMD0); + cmd->len = 4; + cmd++; + } else { + /* disable CS1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.default_ebi2_chip_select_cfg0); + cmd->dst = EBI2_CHIP_SELECT_CFG0; + cmd->len = 4; + cmd++; + } + + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + BUILD_BUG_ON(16 * 6 + 18 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmdptr = + ((msm_virt_to_dma(chip, dma_buffer->cmd) >> 3) | CMD_PTR_LP); + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR( + msm_virt_to_dma(chip, &dma_buffer->cmdptr))); + mb(); + + /* if any of the writes failed (0x10), or there was a + * protection violation (0x100), or the program success + * bit (0x80) is unset, we lose + */ + err = 0; + for (n = 0; n < cwperpage; n++) { + if (dma_buffer->data.flash_status[n] & 0x110) { + err = -EIO; + break; + } + if (!(dma_buffer->data.flash_status[n] & 0x80)) { + err = -EIO; + break; + } + } + /* check for flash status busy for the last codeword */ + if (!interleave_enable) + if (!(dma_buffer->data.flash_status[cwperpage - 1] + & 0x20)) { + err = -EIO; + break; + } +#if VERBOSE + for (n = 0; n < cwperpage; n++) { + if (n%2) { + pr_info("NC10: write pg %d: flash_status[%d] = %x\n", + page, n, dma_buffer->data.flash_status[n]); + } else { + pr_info("NC01: write pg %d: flash_status[%d] = %x\n", + page, n, dma_buffer->data.flash_status[n]); + } + } +#endif + if (err) + break; + pages_written++; + page++; + } + if (ops->mode != MTD_OOB_RAW) + ops->retlen = mtd->writesize * pages_written; + else + ops->retlen = (mtd->writesize + mtd->oobsize) * pages_written; + + ops->oobretlen = ops->ooblen - oob_len; + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + + if (ops->oobbuf) + dma_unmap_page(chip->dev, oob_dma_addr, + ops->ooblen, DMA_TO_DEVICE); +err_dma_map_oobbuf_failed: + if (ops->datbuf) + dma_unmap_page(chip->dev, data_dma_addr, ops->len, + DMA_TO_DEVICE); + if (err) + pr_err("msm_nand_write_oob_dualnandc %llx %x %x failed %d\n", + to, ops->len, ops->ooblen, err); + +#if VERBOSE + pr_info("\n%s: ret %d, retlen %d oobretlen %d\n", + __func__, err, ops->retlen, ops->oobretlen); + + pr_info("===================================================" + "==========\n"); +#endif + return err; +} + +static int msm_nand_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + int ret; + struct mtd_oob_ops ops; + + ops.mode = MTD_OOB_PLACE; + ops.len = len; + ops.retlen = 0; + ops.ooblen = 0; + ops.datbuf = (uint8_t *)buf; + ops.oobbuf = NULL; + if (!dual_nand_ctlr_present) + ret = msm_nand_write_oob(mtd, to, &ops); + else + ret = msm_nand_write_oob_dualnandc(mtd, to, &ops); + *retlen = ops.retlen; + return ret; +} + +static int +msm_nand_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + int err; + struct msm_nand_chip *chip = mtd->priv; + struct { + dmov_s cmd[6]; + unsigned cmdptr; + struct { + uint32_t cmd; + uint32_t addr0; + uint32_t addr1; + uint32_t chipsel; + uint32_t cfg0; + uint32_t cfg1; + uint32_t exec; + uint32_t flash_status; + uint32_t clrfstatus; + uint32_t clrrstatus; + } data; + } *dma_buffer; + dmov_s *cmd; + unsigned page = 0; + + if (mtd->writesize == 2048) + page = instr->addr >> 11; + + if (mtd->writesize == 4096) + page = instr->addr >> 12; + + if (instr->addr & (mtd->erasesize - 1)) { + pr_err("%s: unsupported erase address, 0x%llx\n", + __func__, instr->addr); + return -EINVAL; + } + if (instr->len != mtd->erasesize) { + pr_err("%s: unsupported erase len, %lld\n", + __func__, instr->len); + return -EINVAL; + } + + wait_event(chip->wait_queue, + (dma_buffer = msm_nand_get_dma_buffer( + chip, sizeof(*dma_buffer)))); + + cmd = dma_buffer->cmd; + + dma_buffer->data.cmd = MSM_NAND_CMD_BLOCK_ERASE; + dma_buffer->data.addr0 = page; + dma_buffer->data.addr1 = 0; + dma_buffer->data.chipsel = 0 | 4; + dma_buffer->data.exec = 1; + dma_buffer->data.flash_status = 0xeeeeeeee; + dma_buffer->data.cfg0 = chip->CFG0 & (~(7 << 6)); /* CW_PER_PAGE = 0 */ + dma_buffer->data.cfg1 = chip->CFG1; + dma_buffer->data.clrfstatus = 0x00000020; + dma_buffer->data.clrrstatus = 0x000000C0; + + cmd->cmd = DST_CRCI_NAND_CMD | CMD_OCB; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = MSM_NAND_FLASH_CMD; + cmd->len = 16; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cfg0); + cmd->dst = MSM_NAND_DEV0_CFG0; + cmd->len = 8; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = MSM_NAND_EXEC_CMD; + cmd->len = 4; + cmd++; + + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_FLASH_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.flash_status); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.clrfstatus); + cmd->dst = MSM_NAND_FLASH_STATUS; + cmd->len = 4; + cmd++; + + cmd->cmd = CMD_OCU | CMD_LC; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.clrrstatus); + cmd->dst = MSM_NAND_READ_STATUS; + cmd->len = 4; + cmd++; + + BUILD_BUG_ON(5 != ARRAY_SIZE(dma_buffer->cmd) - 1); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmdptr = + (msm_virt_to_dma(chip, dma_buffer->cmd) >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd( + chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(msm_virt_to_dma(chip, &dma_buffer->cmdptr))); + mb(); + + /* we fail if there was an operation error, a mpu error, or the + * erase success bit was not set. + */ + + if (dma_buffer->data.flash_status & 0x110 || + !(dma_buffer->data.flash_status & 0x80)) + err = -EIO; + else + err = 0; + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + if (err) { + pr_err("%s: erase failed, 0x%llx\n", __func__, instr->addr); + instr->fail_addr = instr->addr; + instr->state = MTD_ERASE_FAILED; + } else { + instr->state = MTD_ERASE_DONE; + instr->fail_addr = 0xffffffff; + mtd_erase_callback(instr); + } + return err; +} + +static int +msm_nand_erase_dualnandc(struct mtd_info *mtd, struct erase_info *instr) +{ + int err; + struct msm_nand_chip *chip = mtd->priv; + struct { + dmov_s cmd[18]; + unsigned cmdptr; + struct { + uint32_t cmd; + uint32_t addr0; + uint32_t addr1; + uint32_t chipsel_cs0; + uint32_t chipsel_cs1; + uint32_t cfg0; + uint32_t cfg1; + uint32_t exec; + uint32_t ecccfg; + uint32_t ebi2_chip_select_cfg0; + uint32_t adm_mux_data_ack_req_nc01; + uint32_t adm_mux_cmd_ack_req_nc01; + uint32_t adm_mux_data_ack_req_nc10; + uint32_t adm_mux_cmd_ack_req_nc10; + uint32_t adm_default_mux; + uint32_t default_ebi2_chip_select_cfg0; + uint32_t nc01_flash_dev_cmd0; + uint32_t nc01_flash_dev_cmd0_default; + uint32_t flash_status[2]; + uint32_t clrfstatus; + uint32_t clrrstatus; + } data; + } *dma_buffer; + dmov_s *cmd; + unsigned page = 0; + + if (mtd->writesize == 2048) + page = instr->addr >> 11; + + if (mtd->writesize == 4096) + page = instr->addr >> 12; + + if (mtd->writesize == 8192) + page = (instr->addr >> 1) >> 12; + + if (instr->addr & (mtd->erasesize - 1)) { + pr_err("%s: unsupported erase address, 0x%llx\n", + __func__, instr->addr); + return -EINVAL; + } + if (instr->len != mtd->erasesize) { + pr_err("%s: unsupported erase len, %lld\n", + __func__, instr->len); + return -EINVAL; + } + + wait_event(chip->wait_queue, + (dma_buffer = msm_nand_get_dma_buffer( + chip, sizeof(*dma_buffer)))); + + cmd = dma_buffer->cmd; + + dma_buffer->data.cmd = MSM_NAND_CMD_BLOCK_ERASE; + dma_buffer->data.addr0 = page; + dma_buffer->data.addr1 = 0; + dma_buffer->data.chipsel_cs0 = (1<<4) | 4; + dma_buffer->data.chipsel_cs1 = (1<<4) | 5; + dma_buffer->data.exec = 1; + dma_buffer->data.flash_status[0] = 0xeeeeeeee; + dma_buffer->data.flash_status[1] = 0xeeeeeeee; + dma_buffer->data.cfg0 = chip->CFG0 & (~(7 << 6)); /* CW_PER_PAGE = 0 */ + dma_buffer->data.cfg1 = chip->CFG1; + dma_buffer->data.clrfstatus = 0x00000020; + dma_buffer->data.clrrstatus = 0x000000C0; + + dma_buffer->data.ebi2_chip_select_cfg0 = 0x00000805; + dma_buffer->data.adm_mux_data_ack_req_nc01 = 0x00000A3C; + dma_buffer->data.adm_mux_cmd_ack_req_nc01 = 0x0000053C; + dma_buffer->data.adm_mux_data_ack_req_nc10 = 0x00000F28; + dma_buffer->data.adm_mux_cmd_ack_req_nc10 = 0x00000F14; + dma_buffer->data.adm_default_mux = 0x00000FC0; + dma_buffer->data.default_ebi2_chip_select_cfg0 = 0x00000801; + + /* enable CS1 */ + cmd->cmd = 0 | CMD_OCB; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.ebi2_chip_select_cfg0); + cmd->dst = EBI2_CHIP_SELECT_CFG0; + cmd->len = 4; + cmd++; + + /* erase CS0 block now !!! */ + /* 0xF14 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_cmd_ack_req_nc10); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = NC01(MSM_NAND_FLASH_CMD); + cmd->len = 16; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cfg0); + cmd->dst = NC01(MSM_NAND_DEV0_CFG0); + cmd->len = 8; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = NC01(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + + /* 0xF28 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_data_ack_req_nc10); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = NC01(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.flash_status[0]); + cmd->len = 4; + cmd++; + + /* erase CS1 block now !!! */ + /* 0x53C */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_cmd_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = NC10(MSM_NAND_FLASH_CMD); + cmd->len = 12; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.chipsel_cs1); + cmd->dst = NC10(MSM_NAND_FLASH_CHIP_SELECT); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cfg0); + cmd->dst = NC10(MSM_NAND_DEV1_CFG0); + cmd->len = 8; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = NC10(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + + /* 0xA3C */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_data_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = NC10(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.flash_status[1]); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.clrfstatus); + cmd->dst = NC11(MSM_NAND_FLASH_STATUS); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.clrrstatus); + cmd->dst = NC11(MSM_NAND_READ_STATUS); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_default_mux); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* disable CS1 */ + cmd->cmd = CMD_OCU | CMD_LC; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.default_ebi2_chip_select_cfg0); + cmd->dst = EBI2_CHIP_SELECT_CFG0; + cmd->len = 4; + cmd++; + + BUILD_BUG_ON(17 != ARRAY_SIZE(dma_buffer->cmd) - 1); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + + dma_buffer->cmdptr = + (msm_virt_to_dma(chip, dma_buffer->cmd) >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd( + chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(msm_virt_to_dma(chip, &dma_buffer->cmdptr))); + mb(); + + /* we fail if there was an operation error, a mpu error, or the + * erase success bit was not set. + */ + + if (dma_buffer->data.flash_status[0] & 0x110 || + !(dma_buffer->data.flash_status[0] & 0x80) || + dma_buffer->data.flash_status[1] & 0x110 || + !(dma_buffer->data.flash_status[1] & 0x80)) + err = -EIO; + else + err = 0; + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + if (err) { + pr_err("%s: erase failed, 0x%llx\n", __func__, instr->addr); + instr->fail_addr = instr->addr; + instr->state = MTD_ERASE_FAILED; + } else { + instr->state = MTD_ERASE_DONE; + instr->fail_addr = 0xffffffff; + mtd_erase_callback(instr); + } + return err; +} + +static int +msm_nand_block_isbad(struct mtd_info *mtd, loff_t ofs) +{ + struct msm_nand_chip *chip = mtd->priv; + int ret; + struct { + dmov_s cmd[5]; + unsigned cmdptr; + struct { + uint32_t cmd; + uint32_t addr0; + uint32_t addr1; + uint32_t chipsel; + uint32_t cfg0; + uint32_t cfg1; + uint32_t eccbchcfg; + uint32_t exec; + uint32_t ecccfg; + struct { + uint32_t flash_status; + uint32_t buffer_status; + } result; + } data; + } *dma_buffer; + dmov_s *cmd; + uint8_t *buf; + unsigned page = 0; + unsigned cwperpage; + + if (mtd->writesize == 2048) + page = ofs >> 11; + + if (mtd->writesize == 4096) + page = ofs >> 12; + + cwperpage = (mtd->writesize >> 9); + + /* Check for invalid offset */ + if (ofs > mtd->size) + return -EINVAL; + if (ofs & (mtd->erasesize - 1)) { + pr_err("%s: unsupported block address, 0x%x\n", + __func__, (uint32_t)ofs); + return -EINVAL; + } + + wait_event(chip->wait_queue, + (dma_buffer = msm_nand_get_dma_buffer(chip , + sizeof(*dma_buffer) + 4))); + buf = (uint8_t *)dma_buffer + sizeof(*dma_buffer); + + /* Read 4 bytes starting from the bad block marker location + * in the last code word of the page + */ + + cmd = dma_buffer->cmd; + + dma_buffer->data.cmd = MSM_NAND_CMD_PAGE_READ; + dma_buffer->data.cfg0 = chip->CFG0_RAW & ~(7U << 6); + dma_buffer->data.cfg1 = chip->CFG1_RAW | + (chip->CFG1 & CFG1_WIDE_FLASH); + if (enable_bch_ecc) + dma_buffer->data.eccbchcfg = chip->ecc_bch_cfg; + + if (chip->CFG1 & CFG1_WIDE_FLASH) + dma_buffer->data.addr0 = (page << 16) | + ((chip->cw_size * (cwperpage-1)) >> 1); + else + dma_buffer->data.addr0 = (page << 16) | + (chip->cw_size * (cwperpage-1)); + + dma_buffer->data.addr1 = (page >> 16) & 0xff; + dma_buffer->data.chipsel = 0 | 4; + + dma_buffer->data.exec = 1; + + dma_buffer->data.result.flash_status = 0xeeeeeeee; + dma_buffer->data.result.buffer_status = 0xeeeeeeee; + + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = MSM_NAND_FLASH_CMD; + cmd->len = 16; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cfg0); + cmd->dst = MSM_NAND_DEV0_CFG0; + if (enable_bch_ecc) + cmd->len = 12; + else + cmd->len = 8; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = MSM_NAND_EXEC_CMD; + cmd->len = 4; + cmd++; + + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_FLASH_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.result); + cmd->len = 8; + cmd++; + + cmd->cmd = 0; + cmd->src = MSM_NAND_FLASH_BUFFER + + (mtd->writesize - (chip->cw_size * (cwperpage-1))); + cmd->dst = msm_virt_to_dma(chip, buf); + cmd->len = 4; + cmd++; + + BUILD_BUG_ON(5 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, + dma_buffer->cmd) >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(msm_virt_to_dma(chip, &dma_buffer->cmdptr))); + mb(); + + ret = 0; + if (dma_buffer->data.result.flash_status & 0x110) + ret = -EIO; + + if (!ret) { + /* Check for bad block marker byte */ + if (chip->CFG1 & CFG1_WIDE_FLASH) { + if (buf[0] != 0xFF || buf[1] != 0xFF) + ret = 1; + } else { + if (buf[0] != 0xFF) + ret = 1; + } + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer) + 4); + return ret; +} + +static int +msm_nand_block_isbad_dualnandc(struct mtd_info *mtd, loff_t ofs) +{ + struct msm_nand_chip *chip = mtd->priv; + int ret; + struct { + dmov_s cmd[18]; + unsigned cmdptr; + struct { + uint32_t cmd; + uint32_t addr0; + uint32_t addr1; + uint32_t chipsel_cs0; + uint32_t chipsel_cs1; + uint32_t cfg0; + uint32_t cfg1; + uint32_t exec; + uint32_t ecccfg; + uint32_t ebi2_chip_select_cfg0; + uint32_t adm_mux_data_ack_req_nc01; + uint32_t adm_mux_cmd_ack_req_nc01; + uint32_t adm_mux_data_ack_req_nc10; + uint32_t adm_mux_cmd_ack_req_nc10; + uint32_t adm_default_mux; + uint32_t default_ebi2_chip_select_cfg0; + struct { + uint32_t flash_status; + uint32_t buffer_status; + } result[2]; + } data; + } *dma_buffer; + dmov_s *cmd; + uint8_t *buf01; + uint8_t *buf10; + unsigned page = 0; + unsigned cwperpage; + + if (mtd->writesize == 2048) + page = ofs >> 11; + + if (mtd->writesize == 4096) + page = ofs >> 12; + + if (mtd->writesize == 8192) + page = (ofs >> 1) >> 12; + + cwperpage = ((mtd->writesize >> 1) >> 9); + + /* Check for invalid offset */ + if (ofs > mtd->size) + return -EINVAL; + if (ofs & (mtd->erasesize - 1)) { + pr_err("%s: unsupported block address, 0x%x\n", + __func__, (uint32_t)ofs); + return -EINVAL; + } + + wait_event(chip->wait_queue, + (dma_buffer = msm_nand_get_dma_buffer(chip , + sizeof(*dma_buffer) + 8))); + buf01 = (uint8_t *)dma_buffer + sizeof(*dma_buffer); + buf10 = buf01 + 4; + + /* Read 4 bytes starting from the bad block marker location + * in the last code word of the page + */ + cmd = dma_buffer->cmd; + + dma_buffer->data.cmd = MSM_NAND_CMD_PAGE_READ; + dma_buffer->data.cfg0 = chip->CFG0_RAW & ~(7U << 6); + dma_buffer->data.cfg1 = chip->CFG1_RAW | + (chip->CFG1 & CFG1_WIDE_FLASH); + + if (chip->CFG1 & CFG1_WIDE_FLASH) + dma_buffer->data.addr0 = (page << 16) | + ((528*(cwperpage-1)) >> 1); + else + dma_buffer->data.addr0 = (page << 16) | + (528*(cwperpage-1)); + + dma_buffer->data.addr1 = (page >> 16) & 0xff; + dma_buffer->data.chipsel_cs0 = (1<<4) | 4; + dma_buffer->data.chipsel_cs1 = (1<<4) | 5; + + dma_buffer->data.exec = 1; + + dma_buffer->data.result[0].flash_status = 0xeeeeeeee; + dma_buffer->data.result[0].buffer_status = 0xeeeeeeee; + dma_buffer->data.result[1].flash_status = 0xeeeeeeee; + dma_buffer->data.result[1].buffer_status = 0xeeeeeeee; + + dma_buffer->data.ebi2_chip_select_cfg0 = 0x00000805; + dma_buffer->data.adm_mux_data_ack_req_nc01 = 0x00000A3C; + dma_buffer->data.adm_mux_cmd_ack_req_nc01 = 0x0000053C; + dma_buffer->data.adm_mux_data_ack_req_nc10 = 0x00000F28; + dma_buffer->data.adm_mux_cmd_ack_req_nc10 = 0x00000F14; + dma_buffer->data.adm_default_mux = 0x00000FC0; + dma_buffer->data.default_ebi2_chip_select_cfg0 = 0x00000801; + + /* Reading last code word from NC01 */ + /* enable CS1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.ebi2_chip_select_cfg0); + cmd->dst = EBI2_CHIP_SELECT_CFG0; + cmd->len = 4; + cmd++; + + /* 0xF14 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_cmd_ack_req_nc10); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = NC01(MSM_NAND_FLASH_CMD); + cmd->len = 16; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cfg0); + cmd->dst = NC01(MSM_NAND_DEV0_CFG0); + cmd->len = 8; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = NC01(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + + /* 0xF28 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_data_ack_req_nc10); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = NC01(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.result[0]); + cmd->len = 8; + cmd++; + + cmd->cmd = 0; + cmd->src = NC01(MSM_NAND_FLASH_BUFFER) + ((mtd->writesize >> 1) - + (528*(cwperpage-1))); + cmd->dst = msm_virt_to_dma(chip, buf01); + cmd->len = 4; + cmd++; + + /* Reading last code word from NC10 */ + /* 0x53C */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_cmd_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = NC10(MSM_NAND_FLASH_CMD); + cmd->len = 12; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.chipsel_cs1); + cmd->dst = NC10(MSM_NAND_FLASH_CHIP_SELECT); + cmd->len = 4; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cfg0); + cmd->dst = NC10(MSM_NAND_DEV1_CFG0); + cmd->len = 8; + cmd++; + + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = NC10(MSM_NAND_EXEC_CMD); + cmd->len = 4; + cmd++; + + /* A3C */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_mux_data_ack_req_nc01); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = NC10(MSM_NAND_FLASH_STATUS); + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.result[1]); + cmd->len = 8; + cmd++; + + cmd->cmd = 0; + cmd->src = NC10(MSM_NAND_FLASH_BUFFER) + ((mtd->writesize >> 1) - + (528*(cwperpage-1))); + cmd->dst = msm_virt_to_dma(chip, buf10); + cmd->len = 4; + cmd++; + + /* FC0 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.adm_default_mux); + cmd->dst = EBI2_NAND_ADM_MUX; + cmd->len = 4; + cmd++; + + /* disble CS1 */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.ebi2_chip_select_cfg0); + cmd->dst = EBI2_CHIP_SELECT_CFG0; + cmd->len = 4; + cmd++; + + BUILD_BUG_ON(18 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, + dma_buffer->cmd) >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(msm_virt_to_dma(chip, &dma_buffer->cmdptr))); + mb(); + + ret = 0; + if ((dma_buffer->data.result[0].flash_status & 0x110) || + (dma_buffer->data.result[1].flash_status & 0x110)) + ret = -EIO; + + if (!ret) { + /* Check for bad block marker byte for NC01 & NC10 */ + if (chip->CFG1 & CFG1_WIDE_FLASH) { + if ((buf01[0] != 0xFF || buf01[1] != 0xFF) || + (buf10[0] != 0xFF || buf10[1] != 0xFF)) + ret = 1; + } else { + if (buf01[0] != 0xFF || buf10[0] != 0xFF) + ret = 1; + } + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer) + 8); + return ret; +} + +static int +msm_nand_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct mtd_oob_ops ops; + int ret; + uint8_t *buf; + + /* Check for invalid offset */ + if (ofs > mtd->size) + return -EINVAL; + if (ofs & (mtd->erasesize - 1)) { + pr_err("%s: unsupported block address, 0x%x\n", + __func__, (uint32_t)ofs); + return -EINVAL; + } + + /* + Write all 0s to the first page + This will set the BB marker to 0 + */ + buf = page_address(ZERO_PAGE()); + + ops.mode = MTD_OOB_RAW; + ops.len = mtd->writesize + mtd->oobsize; + ops.retlen = 0; + ops.ooblen = 0; + ops.datbuf = buf; + ops.oobbuf = NULL; + if (!interleave_enable) + ret = msm_nand_write_oob(mtd, ofs, &ops); + else + ret = msm_nand_write_oob_dualnandc(mtd, ofs, &ops); + + return ret; +} + +/** + * msm_nand_suspend - [MTD Interface] Suspend the msm_nand flash + * @param mtd MTD device structure + */ +static int msm_nand_suspend(struct mtd_info *mtd) +{ + return 0; +} + +/** + * msm_nand_resume - [MTD Interface] Resume the msm_nand flash + * @param mtd MTD device structure + */ +static void msm_nand_resume(struct mtd_info *mtd) +{ +} + +struct onenand_information { + uint16_t manufacturer_id; + uint16_t device_id; + uint16_t version_id; + uint16_t data_buf_size; + uint16_t boot_buf_size; + uint16_t num_of_buffers; + uint16_t technology; +}; + +static struct onenand_information onenand_info; +static uint32_t nand_sfcmd_mode; + +uint32_t flash_onenand_probe(struct msm_nand_chip *chip) +{ + struct { + dmov_s cmd[7]; + unsigned cmdptr; + struct { + uint32_t bcfg; + uint32_t cmd; + uint32_t exec; + uint32_t status; + uint32_t addr0; + uint32_t addr1; + uint32_t addr2; + uint32_t addr3; + uint32_t addr4; + uint32_t addr5; + uint32_t addr6; + uint32_t data0; + uint32_t data1; + uint32_t data2; + uint32_t data3; + uint32_t data4; + uint32_t data5; + uint32_t data6; + } data; + } *dma_buffer; + dmov_s *cmd; + + int err = 0; + uint32_t initialsflashcmd = 0; + + initialsflashcmd = flash_rd_reg(chip, MSM_NAND_SFLASHC_CMD); + + if ((initialsflashcmd & 0x10) == 0x10) + nand_sfcmd_mode = MSM_NAND_SFCMD_ASYNC; + else + nand_sfcmd_mode = MSM_NAND_SFCMD_BURST; + + printk(KERN_INFO "SFLASHC Async Mode bit: %x \n", nand_sfcmd_mode); + + wait_event(chip->wait_queue, (dma_buffer = msm_nand_get_dma_buffer + (chip, sizeof(*dma_buffer)))); + + cmd = dma_buffer->cmd; + + dma_buffer->data.bcfg = SFLASH_BCFG | + (nand_sfcmd_mode ? 0 : (1 << 24)); + dma_buffer->data.cmd = SFLASH_PREPCMD(7, 0, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGRD); + dma_buffer->data.exec = 1; + dma_buffer->data.status = CLEAN_DATA_32; + dma_buffer->data.addr0 = (ONENAND_DEVICE_ID << 16) | + (ONENAND_MANUFACTURER_ID); + dma_buffer->data.addr1 = (ONENAND_DATA_BUFFER_SIZE << 16) | + (ONENAND_VERSION_ID); + dma_buffer->data.addr2 = (ONENAND_AMOUNT_OF_BUFFERS << 16) | + (ONENAND_BOOT_BUFFER_SIZE); + dma_buffer->data.addr3 = (CLEAN_DATA_16 << 16) | + (ONENAND_TECHNOLOGY << 0); + dma_buffer->data.data0 = CLEAN_DATA_32; + dma_buffer->data.data1 = CLEAN_DATA_32; + dma_buffer->data.data2 = CLEAN_DATA_32; + dma_buffer->data.data3 = CLEAN_DATA_32; + + /* Enable and configure the SFlash controller */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.bcfg); + cmd->dst = MSM_NAND_SFLASHC_BURST_CFG; + cmd->len = 4; + cmd++; + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.cmd); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Configure the ADDR0 and ADDR1 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr0); + cmd->dst = MSM_NAND_ADDR0; + cmd->len = 8; + cmd++; + + /* Configure the ADDR2 and ADDR3 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr2); + cmd->dst = MSM_NAND_ADDR2; + cmd->len = 8; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.exec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the two status registers */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.status); + cmd->len = 4; + cmd++; + + /* Read data registers - valid only if status says success */ + cmd->cmd = 0; + cmd->src = MSM_NAND_GENP_REG0; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data0); + cmd->len = 16; + cmd++; + + BUILD_BUG_ON(7 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, dma_buffer->cmd) + >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST + | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + + /* Check for errors, protection violations etc */ + if (dma_buffer->data.status & 0x110) { + pr_info("%s: MPU/OP error" + "(0x%x) during Onenand probe\n", + __func__, dma_buffer->data.status); + err = -EIO; + } else { + + onenand_info.manufacturer_id = + (dma_buffer->data.data0 >> 0) & 0x0000FFFF; + onenand_info.device_id = + (dma_buffer->data.data0 >> 16) & 0x0000FFFF; + onenand_info.version_id = + (dma_buffer->data.data1 >> 0) & 0x0000FFFF; + onenand_info.data_buf_size = + (dma_buffer->data.data1 >> 16) & 0x0000FFFF; + onenand_info.boot_buf_size = + (dma_buffer->data.data2 >> 0) & 0x0000FFFF; + onenand_info.num_of_buffers = + (dma_buffer->data.data2 >> 16) & 0x0000FFFF; + onenand_info.technology = + (dma_buffer->data.data3 >> 0) & 0x0000FFFF; + + + pr_info("=======================================" + "==========================\n"); + + pr_info("%s: manufacturer_id = 0x%x\n" + , __func__, onenand_info.manufacturer_id); + pr_info("%s: device_id = 0x%x\n" + , __func__, onenand_info.device_id); + pr_info("%s: version_id = 0x%x\n" + , __func__, onenand_info.version_id); + pr_info("%s: data_buf_size = 0x%x\n" + , __func__, onenand_info.data_buf_size); + pr_info("%s: boot_buf_size = 0x%x\n" + , __func__, onenand_info.boot_buf_size); + pr_info("%s: num_of_buffers = 0x%x\n" + , __func__, onenand_info.num_of_buffers); + pr_info("%s: technology = 0x%x\n" + , __func__, onenand_info.technology); + + pr_info("=======================================" + "==========================\n"); + + if ((onenand_info.manufacturer_id != 0x00EC) + || ((onenand_info.device_id & 0x0040) != 0x0040) + || (onenand_info.data_buf_size != 0x0800) + || (onenand_info.boot_buf_size != 0x0200) + || (onenand_info.num_of_buffers != 0x0201) + || (onenand_info.technology != 0)) { + + pr_info("%s: Detected an unsupported device\n" + , __func__); + err = -EIO; + } + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + + return err; +} + +int msm_onenand_read_oob(struct mtd_info *mtd, + loff_t from, struct mtd_oob_ops *ops) +{ + struct msm_nand_chip *chip = mtd->priv; + + struct { + dmov_s cmd[53]; + unsigned cmdptr; + struct { + uint32_t sfbcfg; + uint32_t sfcmd[9]; + uint32_t sfexec; + uint32_t sfstat[9]; + uint32_t addr0; + uint32_t addr1; + uint32_t addr2; + uint32_t addr3; + uint32_t addr4; + uint32_t addr5; + uint32_t addr6; + uint32_t data0; + uint32_t data1; + uint32_t data2; + uint32_t data3; + uint32_t data4; + uint32_t data5; + uint32_t data6; + uint32_t macro[5]; + } data; + } *dma_buffer; + dmov_s *cmd; + + int err = 0; + int i; + dma_addr_t data_dma_addr = 0; + dma_addr_t oob_dma_addr = 0; + dma_addr_t data_dma_addr_curr = 0; + dma_addr_t oob_dma_addr_curr = 0; + + loff_t from_curr = 0; + unsigned page_count; + unsigned pages_read = 0; + + uint16_t onenand_startaddr1; + uint16_t onenand_startaddr8; + uint16_t onenand_startaddr2; + uint16_t onenand_startbuffer; + uint16_t onenand_sysconfig1; + uint16_t controller_status; + uint16_t interrupt_status; + uint16_t ecc_status; +#if VERBOSE + pr_info("=================================================" + "================\n"); + pr_info("%s: from 0x%llx mode %d \ndatbuf 0x%p datlen 0x%x" + "\noobbuf 0x%p ooblen 0x%x\n", + __func__, from, ops->mode, ops->datbuf, ops->len, + ops->oobbuf, ops->ooblen); +#endif + if (!mtd) { + pr_err("%s: invalid mtd pointer, 0x%x\n", __func__, + (uint32_t)mtd); + return -EINVAL; + } + if (from & (mtd->writesize - 1)) { + pr_err("%s: unsupported from, 0x%llx\n", __func__, + from); + return -EINVAL; + } + + if ((ops->mode != MTD_OOB_PLACE) && (ops->mode != MTD_OOB_AUTO) && + (ops->mode != MTD_OOB_RAW)) { + pr_err("%s: unsupported ops->mode, %d\n", __func__, + ops->mode); + return -EINVAL; + } + + if (((ops->datbuf == NULL) || (ops->len == 0)) && + ((ops->oobbuf == NULL) || (ops->ooblen == 0))) { + pr_err("%s: incorrect ops fields - nothing to do\n", + __func__); + return -EINVAL; + } + + if ((ops->datbuf != NULL) && (ops->len == 0)) { + pr_err("%s: data buffer passed but length 0\n", + __func__); + return -EINVAL; + } + + if ((ops->oobbuf != NULL) && (ops->ooblen == 0)) { + pr_err("%s: oob buffer passed but length 0\n", + __func__); + return -EINVAL; + } + + if (ops->mode != MTD_OOB_RAW) { + if (ops->datbuf != NULL && (ops->len % mtd->writesize) != 0) { + /* when ops->datbuf is NULL, ops->len can be ooblen */ + pr_err("%s: unsupported ops->len, %d\n", __func__, + ops->len); + return -EINVAL; + } + } else { + if (ops->datbuf != NULL && + (ops->len % (mtd->writesize + mtd->oobsize)) != 0) { + pr_err("%s: unsupported ops->len," + " %d for MTD_OOB_RAW\n", __func__, ops->len); + return -EINVAL; + } + } + + if ((ops->mode == MTD_OOB_RAW) && (ops->oobbuf)) { + pr_err("%s: unsupported operation, oobbuf pointer " + "passed in for RAW mode, %x\n", __func__, + (uint32_t)ops->oobbuf); + return -EINVAL; + } + + if (ops->oobbuf && !ops->datbuf) { + page_count = ops->ooblen / ((ops->mode == MTD_OOB_AUTO) ? + mtd->oobavail : mtd->oobsize); + if ((page_count == 0) && (ops->ooblen)) + page_count = 1; + } else if (ops->mode != MTD_OOB_RAW) + page_count = ops->len / mtd->writesize; + else + page_count = ops->len / (mtd->writesize + mtd->oobsize); + + if ((ops->mode == MTD_OOB_PLACE) && (ops->oobbuf != NULL)) { + if (page_count * mtd->oobsize > ops->ooblen) { + pr_err("%s: unsupported ops->ooblen for " + "PLACE, %d\n", __func__, ops->ooblen); + return -EINVAL; + } + } + + if ((ops->mode == MTD_OOB_PLACE) && (ops->ooblen != 0) && + (ops->ooboffs != 0)) { + pr_err("%s: unsupported ops->ooboffs, %d\n", __func__, + ops->ooboffs); + return -EINVAL; + } + + if (ops->datbuf) { + memset(ops->datbuf, 0x55, ops->len); + data_dma_addr_curr = data_dma_addr = msm_nand_dma_map(chip->dev, + ops->datbuf, ops->len, DMA_FROM_DEVICE); + if (dma_mapping_error(chip->dev, data_dma_addr)) { + pr_err("%s: failed to get dma addr for %p\n", + __func__, ops->datbuf); + return -EIO; + } + } + if (ops->oobbuf) { + memset(ops->oobbuf, 0x55, ops->ooblen); + oob_dma_addr_curr = oob_dma_addr = msm_nand_dma_map(chip->dev, + ops->oobbuf, ops->ooblen, DMA_FROM_DEVICE); + if (dma_mapping_error(chip->dev, oob_dma_addr)) { + pr_err("%s: failed to get dma addr for %p\n", + __func__, ops->oobbuf); + err = -EIO; + goto err_dma_map_oobbuf_failed; + } + } + + wait_event(chip->wait_queue, (dma_buffer = msm_nand_get_dma_buffer + (chip, sizeof(*dma_buffer)))); + + from_curr = from; + + while (page_count-- > 0) { + + cmd = dma_buffer->cmd; + + if ((onenand_info.device_id & ONENAND_DEVICE_IS_DDP) + && (from_curr >= (mtd->size>>1))) { /* DDP Device */ + onenand_startaddr1 = DEVICE_FLASHCORE_1 | + (((uint32_t)(from_curr-(mtd->size>>1)) + / mtd->erasesize)); + onenand_startaddr2 = DEVICE_BUFFERRAM_1; + } else { + onenand_startaddr1 = DEVICE_FLASHCORE_0 | + ((uint32_t)from_curr / mtd->erasesize) ; + onenand_startaddr2 = DEVICE_BUFFERRAM_0; + } + + onenand_startaddr8 = (((uint32_t)from_curr & + (mtd->erasesize - 1)) / mtd->writesize) << 2; + onenand_startbuffer = DATARAM0_0 << 8; + onenand_sysconfig1 = (ops->mode == MTD_OOB_RAW) ? + ONENAND_SYSCFG1_ECCDIS(nand_sfcmd_mode) : + ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode); + + dma_buffer->data.sfbcfg = SFLASH_BCFG | + (nand_sfcmd_mode ? 0 : (1 << 24)); + dma_buffer->data.sfcmd[0] = SFLASH_PREPCMD(7, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfcmd[1] = SFLASH_PREPCMD(0, 0, 32, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_INTHI); + dma_buffer->data.sfcmd[2] = SFLASH_PREPCMD(3, 7, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGRD); + dma_buffer->data.sfcmd[3] = SFLASH_PREPCMD(256, 0, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATRD); + dma_buffer->data.sfcmd[4] = SFLASH_PREPCMD(256, 0, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATRD); + dma_buffer->data.sfcmd[5] = SFLASH_PREPCMD(256, 0, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATRD); + dma_buffer->data.sfcmd[6] = SFLASH_PREPCMD(256, 0, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATRD); + dma_buffer->data.sfcmd[7] = SFLASH_PREPCMD(32, 0, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATRD); + dma_buffer->data.sfcmd[8] = SFLASH_PREPCMD(4, 10, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfexec = 1; + dma_buffer->data.sfstat[0] = CLEAN_DATA_32; + dma_buffer->data.sfstat[1] = CLEAN_DATA_32; + dma_buffer->data.sfstat[2] = CLEAN_DATA_32; + dma_buffer->data.sfstat[3] = CLEAN_DATA_32; + dma_buffer->data.sfstat[4] = CLEAN_DATA_32; + dma_buffer->data.sfstat[5] = CLEAN_DATA_32; + dma_buffer->data.sfstat[6] = CLEAN_DATA_32; + dma_buffer->data.sfstat[7] = CLEAN_DATA_32; + dma_buffer->data.sfstat[8] = CLEAN_DATA_32; + dma_buffer->data.addr0 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr1 = (ONENAND_START_ADDRESS_8 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.addr2 = (ONENAND_START_BUFFER << 16) | + (ONENAND_START_ADDRESS_2); + dma_buffer->data.addr3 = (ONENAND_ECC_STATUS << 16) | + (ONENAND_COMMAND); + dma_buffer->data.addr4 = (ONENAND_CONTROLLER_STATUS << 16) | + (ONENAND_INTERRUPT_STATUS); + dma_buffer->data.addr5 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr6 = (ONENAND_START_ADDRESS_3 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.data0 = (ONENAND_CLRINTR << 16) | + (onenand_sysconfig1); + dma_buffer->data.data1 = (onenand_startaddr8 << 16) | + (onenand_startaddr1); + dma_buffer->data.data2 = (onenand_startbuffer << 16) | + (onenand_startaddr2); + dma_buffer->data.data3 = (CLEAN_DATA_16 << 16) | + (ONENAND_CMDLOADSPARE); + dma_buffer->data.data4 = (CLEAN_DATA_16 << 16) | + (CLEAN_DATA_16); + dma_buffer->data.data5 = (ONENAND_CLRINTR << 16) | + (ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode)); + dma_buffer->data.data6 = (ONENAND_STARTADDR3_RES << 16) | + (ONENAND_STARTADDR1_RES); + dma_buffer->data.macro[0] = 0x0200; + dma_buffer->data.macro[1] = 0x0300; + dma_buffer->data.macro[2] = 0x0400; + dma_buffer->data.macro[3] = 0x0500; + dma_buffer->data.macro[4] = 0x8010; + + /*************************************************************/ + /* Write necessary address registers in the onenand device */ + /*************************************************************/ + + /* Enable and configure the SFlash controller */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfbcfg); + cmd->dst = MSM_NAND_SFLASHC_BURST_CFG; + cmd->len = 4; + cmd++; + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[0]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Write the ADDR0 and ADDR1 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr0); + cmd->dst = MSM_NAND_ADDR0; + cmd->len = 8; + cmd++; + + /* Write the ADDR2 ADDR3 ADDR4 ADDR5 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr2); + cmd->dst = MSM_NAND_ADDR2; + cmd->len = 16; + cmd++; + + /* Write the ADDR6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr6); + cmd->dst = MSM_NAND_ADDR6; + cmd->len = 4; + cmd++; + + /* Write the GENP0, GENP1, GENP2, GENP3 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data0); + cmd->dst = MSM_NAND_GENP_REG0; + cmd->len = 16; + cmd++; + + /* Write the FLASH_DEV_CMD4,5,6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->dst = MSM_NAND_DEV_CMD4; + cmd->len = 12; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[0]); + cmd->len = 4; + cmd++; + + /*************************************************************/ + /* Wait for the interrupt from the Onenand device controller */ + /*************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[1]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[1]); + cmd->len = 4; + cmd++; + + /*************************************************************/ + /* Read necessary status registers from the onenand device */ + /*************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[2]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[2]); + cmd->len = 4; + cmd++; + + /* Read the GENP3 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_GENP_REG3; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data3); + cmd->len = 4; + cmd++; + + /* Read the DEVCMD4 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_DEV_CMD4; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->len = 4; + cmd++; + + /*************************************************************/ + /* Read the data ram area from the onenand buffer ram */ + /*************************************************************/ + + if (ops->datbuf) { + + dma_buffer->data.data3 = (CLEAN_DATA_16 << 16) | + (ONENAND_CMDLOAD); + + for (i = 0; i < 4; i++) { + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.sfcmd[3+i]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Write the MACRO1 register */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.macro[i]); + cmd->dst = MSM_NAND_MACRO1_REG; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data rdy, & read status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.sfstat[3+i]); + cmd->len = 4; + cmd++; + + /* Transfer nand ctlr buf contents to usr buf */ + cmd->cmd = 0; + cmd->src = MSM_NAND_FLASH_BUFFER; + cmd->dst = data_dma_addr_curr; + cmd->len = 512; + data_dma_addr_curr += 512; + cmd++; + } + } + + if ((ops->oobbuf) || (ops->mode == MTD_OOB_RAW)) { + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.sfcmd[7]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Write the MACRO1 register */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.macro[4]); + cmd->dst = MSM_NAND_MACRO1_REG; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.sfstat[7]); + cmd->len = 4; + cmd++; + + /* Transfer nand ctlr buffer contents into usr buf */ + if (ops->mode == MTD_OOB_AUTO) { + for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) { + cmd->cmd = 0; + cmd->src = MSM_NAND_FLASH_BUFFER + + mtd->ecclayout->oobfree[i].offset; + cmd->dst = oob_dma_addr_curr; + cmd->len = + mtd->ecclayout->oobfree[i].length; + oob_dma_addr_curr += + mtd->ecclayout->oobfree[i].length; + cmd++; + } + } + if (ops->mode == MTD_OOB_PLACE) { + cmd->cmd = 0; + cmd->src = MSM_NAND_FLASH_BUFFER; + cmd->dst = oob_dma_addr_curr; + cmd->len = mtd->oobsize; + oob_dma_addr_curr += mtd->oobsize; + cmd++; + } + if (ops->mode == MTD_OOB_RAW) { + cmd->cmd = 0; + cmd->src = MSM_NAND_FLASH_BUFFER; + cmd->dst = data_dma_addr_curr; + cmd->len = mtd->oobsize; + data_dma_addr_curr += mtd->oobsize; + cmd++; + } + } + + /*************************************************************/ + /* Restore the necessary registers to proper values */ + /*************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[8]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[8]); + cmd->len = 4; + cmd++; + + + BUILD_BUG_ON(53 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, dma_buffer->cmd) + >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + + ecc_status = (dma_buffer->data.data3 >> 16) & + 0x0000FFFF; + interrupt_status = (dma_buffer->data.data4 >> 0) & + 0x0000FFFF; + controller_status = (dma_buffer->data.data4 >> 16) & + 0x0000FFFF; + +#if VERBOSE + pr_info("\n%s: sflash status %x %x %x %x %x %x %x" + "%x %x\n", __func__, + dma_buffer->data.sfstat[0], + dma_buffer->data.sfstat[1], + dma_buffer->data.sfstat[2], + dma_buffer->data.sfstat[3], + dma_buffer->data.sfstat[4], + dma_buffer->data.sfstat[5], + dma_buffer->data.sfstat[6], + dma_buffer->data.sfstat[7], + dma_buffer->data.sfstat[8]); + + pr_info("%s: controller_status = %x\n", __func__, + controller_status); + pr_info("%s: interrupt_status = %x\n", __func__, + interrupt_status); + pr_info("%s: ecc_status = %x\n", __func__, + ecc_status); +#endif + /* Check for errors, protection violations etc */ + if ((controller_status != 0) + || (dma_buffer->data.sfstat[0] & 0x110) + || (dma_buffer->data.sfstat[1] & 0x110) + || (dma_buffer->data.sfstat[2] & 0x110) + || (dma_buffer->data.sfstat[8] & 0x110) + || ((dma_buffer->data.sfstat[3] & 0x110) && + (ops->datbuf)) + || ((dma_buffer->data.sfstat[4] & 0x110) && + (ops->datbuf)) + || ((dma_buffer->data.sfstat[5] & 0x110) && + (ops->datbuf)) + || ((dma_buffer->data.sfstat[6] & 0x110) && + (ops->datbuf)) + || ((dma_buffer->data.sfstat[7] & 0x110) && + ((ops->oobbuf) + || (ops->mode == MTD_OOB_RAW)))) { + pr_info("%s: ECC/MPU/OP error\n", __func__); + err = -EIO; + } + + if (err) + break; + pages_read++; + from_curr += mtd->writesize; + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + + if (ops->oobbuf) { + dma_unmap_page(chip->dev, oob_dma_addr, ops->ooblen, + DMA_FROM_DEVICE); + } +err_dma_map_oobbuf_failed: + if (ops->datbuf) { + dma_unmap_page(chip->dev, data_dma_addr, ops->len, + DMA_FROM_DEVICE); + } + + if (err) { + pr_err("%s: %llx %x %x failed\n", __func__, from_curr, + ops->datbuf ? ops->len : 0, ops->ooblen); + } else { + ops->retlen = ops->oobretlen = 0; + if (ops->datbuf != NULL) { + if (ops->mode != MTD_OOB_RAW) + ops->retlen = mtd->writesize * pages_read; + else + ops->retlen = (mtd->writesize + mtd->oobsize) + * pages_read; + } + if (ops->oobbuf != NULL) { + if (ops->mode == MTD_OOB_AUTO) + ops->oobretlen = mtd->oobavail * pages_read; + else + ops->oobretlen = mtd->oobsize * pages_read; + } + } + +#if VERBOSE + pr_info("\n%s: ret %d, retlen %d oobretlen %d\n", + __func__, err, ops->retlen, ops->oobretlen); + + pr_info("===================================================" + "==============\n"); +#endif + return err; +} + +int msm_onenand_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + int ret; + struct mtd_oob_ops ops; + + ops.mode = MTD_OOB_PLACE; + ops.datbuf = buf; + ops.len = len; + ops.retlen = 0; + ops.oobbuf = NULL; + ops.ooblen = 0; + ops.oobretlen = 0; + ret = msm_onenand_read_oob(mtd, from, &ops); + *retlen = ops.retlen; + + return ret; +} + +static int msm_onenand_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct msm_nand_chip *chip = mtd->priv; + + struct { + dmov_s cmd[53]; + unsigned cmdptr; + struct { + uint32_t sfbcfg; + uint32_t sfcmd[10]; + uint32_t sfexec; + uint32_t sfstat[10]; + uint32_t addr0; + uint32_t addr1; + uint32_t addr2; + uint32_t addr3; + uint32_t addr4; + uint32_t addr5; + uint32_t addr6; + uint32_t data0; + uint32_t data1; + uint32_t data2; + uint32_t data3; + uint32_t data4; + uint32_t data5; + uint32_t data6; + uint32_t macro[5]; + } data; + } *dma_buffer; + dmov_s *cmd; + + int err = 0; + int i, j, k; + dma_addr_t data_dma_addr = 0; + dma_addr_t oob_dma_addr = 0; + dma_addr_t init_dma_addr = 0; + dma_addr_t data_dma_addr_curr = 0; + dma_addr_t oob_dma_addr_curr = 0; + uint8_t *init_spare_bytes; + + loff_t to_curr = 0; + unsigned page_count; + unsigned pages_written = 0; + + uint16_t onenand_startaddr1; + uint16_t onenand_startaddr8; + uint16_t onenand_startaddr2; + uint16_t onenand_startbuffer; + uint16_t onenand_sysconfig1; + + uint16_t controller_status; + uint16_t interrupt_status; + uint16_t ecc_status; + +#if VERBOSE + pr_info("=================================================" + "================\n"); + pr_info("%s: to 0x%llx mode %d \ndatbuf 0x%p datlen 0x%x" + "\noobbuf 0x%p ooblen 0x%x\n", + __func__, to, ops->mode, ops->datbuf, ops->len, + ops->oobbuf, ops->ooblen); +#endif + if (!mtd) { + pr_err("%s: invalid mtd pointer, 0x%x\n", __func__, + (uint32_t)mtd); + return -EINVAL; + } + if (to & (mtd->writesize - 1)) { + pr_err("%s: unsupported to, 0x%llx\n", __func__, to); + return -EINVAL; + } + + if ((ops->mode != MTD_OOB_PLACE) && (ops->mode != MTD_OOB_AUTO) && + (ops->mode != MTD_OOB_RAW)) { + pr_err("%s: unsupported ops->mode, %d\n", __func__, + ops->mode); + return -EINVAL; + } + + if (((ops->datbuf == NULL) || (ops->len == 0)) && + ((ops->oobbuf == NULL) || (ops->ooblen == 0))) { + pr_err("%s: incorrect ops fields - nothing to do\n", + __func__); + return -EINVAL; + } + + if ((ops->datbuf != NULL) && (ops->len == 0)) { + pr_err("%s: data buffer passed but length 0\n", + __func__); + return -EINVAL; + } + + if ((ops->oobbuf != NULL) && (ops->ooblen == 0)) { + pr_err("%s: oob buffer passed but length 0\n", + __func__); + return -EINVAL; + } + + if (ops->mode != MTD_OOB_RAW) { + if (ops->datbuf != NULL && (ops->len % mtd->writesize) != 0) { + /* when ops->datbuf is NULL, ops->len can be ooblen */ + pr_err("%s: unsupported ops->len, %d\n", __func__, + ops->len); + return -EINVAL; + } + } else { + if (ops->datbuf != NULL && + (ops->len % (mtd->writesize + mtd->oobsize)) != 0) { + pr_err("%s: unsupported ops->len," + " %d for MTD_OOB_RAW\n", __func__, ops->len); + return -EINVAL; + } + } + + if ((ops->mode == MTD_OOB_RAW) && (ops->oobbuf)) { + pr_err("%s: unsupported operation, oobbuf pointer " + "passed in for RAW mode, %x\n", __func__, + (uint32_t)ops->oobbuf); + return -EINVAL; + } + + if (ops->oobbuf && !ops->datbuf) { + page_count = ops->ooblen / ((ops->mode == MTD_OOB_AUTO) ? + mtd->oobavail : mtd->oobsize); + if ((page_count == 0) && (ops->ooblen)) + page_count = 1; + } else if (ops->mode != MTD_OOB_RAW) + page_count = ops->len / mtd->writesize; + else + page_count = ops->len / (mtd->writesize + mtd->oobsize); + + if ((ops->mode == MTD_OOB_AUTO) && (ops->oobbuf != NULL)) { + if (page_count > 1) { + pr_err("%s: unsupported ops->ooblen for" + "AUTO, %d\n", __func__, ops->ooblen); + return -EINVAL; + } + } + + if ((ops->mode == MTD_OOB_PLACE) && (ops->oobbuf != NULL)) { + if (page_count * mtd->oobsize > ops->ooblen) { + pr_err("%s: unsupported ops->ooblen for" + "PLACE, %d\n", __func__, ops->ooblen); + return -EINVAL; + } + } + + if ((ops->mode == MTD_OOB_PLACE) && (ops->ooblen != 0) && + (ops->ooboffs != 0)) { + pr_err("%s: unsupported ops->ooboffs, %d\n", + __func__, ops->ooboffs); + return -EINVAL; + } + + init_spare_bytes = kmalloc(64, GFP_KERNEL); + if (!init_spare_bytes) { + pr_err("%s: failed to alloc init_spare_bytes buffer\n", + __func__); + return -ENOMEM; + } + for (i = 0; i < 64; i++) + init_spare_bytes[i] = 0xFF; + + if ((ops->oobbuf) && (ops->mode == MTD_OOB_AUTO)) { + for (i = 0, k = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) + for (j = 0; j < mtd->ecclayout->oobfree[i].length; + j++) { + init_spare_bytes[j + + mtd->ecclayout->oobfree[i].offset] + = (ops->oobbuf)[k]; + k++; + } + } + + if (ops->datbuf) { + data_dma_addr_curr = data_dma_addr = msm_nand_dma_map(chip->dev, + ops->datbuf, ops->len, DMA_TO_DEVICE); + if (dma_mapping_error(chip->dev, data_dma_addr)) { + pr_err("%s: failed to get dma addr for %p\n", + __func__, ops->datbuf); + return -EIO; + } + } + if (ops->oobbuf) { + oob_dma_addr_curr = oob_dma_addr = msm_nand_dma_map(chip->dev, + ops->oobbuf, ops->ooblen, DMA_TO_DEVICE); + if (dma_mapping_error(chip->dev, oob_dma_addr)) { + pr_err("%s: failed to get dma addr for %p\n", + __func__, ops->oobbuf); + err = -EIO; + goto err_dma_map_oobbuf_failed; + } + } + + init_dma_addr = msm_nand_dma_map(chip->dev, init_spare_bytes, 64, + DMA_TO_DEVICE); + if (dma_mapping_error(chip->dev, init_dma_addr)) { + pr_err("%s: failed to get dma addr for %p\n", + __func__, init_spare_bytes); + err = -EIO; + goto err_dma_map_initbuf_failed; + } + + + wait_event(chip->wait_queue, (dma_buffer = msm_nand_get_dma_buffer + (chip, sizeof(*dma_buffer)))); + + to_curr = to; + + while (page_count-- > 0) { + cmd = dma_buffer->cmd; + + if ((onenand_info.device_id & ONENAND_DEVICE_IS_DDP) + && (to_curr >= (mtd->size>>1))) { /* DDP Device */ + onenand_startaddr1 = DEVICE_FLASHCORE_1 | + (((uint32_t)(to_curr-(mtd->size>>1)) + / mtd->erasesize)); + onenand_startaddr2 = DEVICE_BUFFERRAM_1; + } else { + onenand_startaddr1 = DEVICE_FLASHCORE_0 | + ((uint32_t)to_curr / mtd->erasesize) ; + onenand_startaddr2 = DEVICE_BUFFERRAM_0; + } + + onenand_startaddr8 = (((uint32_t)to_curr & + (mtd->erasesize - 1)) / mtd->writesize) << 2; + onenand_startbuffer = DATARAM0_0 << 8; + onenand_sysconfig1 = (ops->mode == MTD_OOB_RAW) ? + ONENAND_SYSCFG1_ECCDIS(nand_sfcmd_mode) : + ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode); + + dma_buffer->data.sfbcfg = SFLASH_BCFG | + (nand_sfcmd_mode ? 0 : (1 << 24)); + dma_buffer->data.sfcmd[0] = SFLASH_PREPCMD(6, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfcmd[1] = SFLASH_PREPCMD(256, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATWR); + dma_buffer->data.sfcmd[2] = SFLASH_PREPCMD(256, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATWR); + dma_buffer->data.sfcmd[3] = SFLASH_PREPCMD(256, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATWR); + dma_buffer->data.sfcmd[4] = SFLASH_PREPCMD(256, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATWR); + dma_buffer->data.sfcmd[5] = SFLASH_PREPCMD(32, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_DATWR); + dma_buffer->data.sfcmd[6] = SFLASH_PREPCMD(1, 6, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfcmd[7] = SFLASH_PREPCMD(0, 0, 32, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_INTHI); + dma_buffer->data.sfcmd[8] = SFLASH_PREPCMD(3, 7, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGRD); + dma_buffer->data.sfcmd[9] = SFLASH_PREPCMD(4, 10, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfexec = 1; + dma_buffer->data.sfstat[0] = CLEAN_DATA_32; + dma_buffer->data.sfstat[1] = CLEAN_DATA_32; + dma_buffer->data.sfstat[2] = CLEAN_DATA_32; + dma_buffer->data.sfstat[3] = CLEAN_DATA_32; + dma_buffer->data.sfstat[4] = CLEAN_DATA_32; + dma_buffer->data.sfstat[5] = CLEAN_DATA_32; + dma_buffer->data.sfstat[6] = CLEAN_DATA_32; + dma_buffer->data.sfstat[7] = CLEAN_DATA_32; + dma_buffer->data.sfstat[8] = CLEAN_DATA_32; + dma_buffer->data.sfstat[9] = CLEAN_DATA_32; + dma_buffer->data.addr0 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr1 = (ONENAND_START_ADDRESS_8 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.addr2 = (ONENAND_START_BUFFER << 16) | + (ONENAND_START_ADDRESS_2); + dma_buffer->data.addr3 = (ONENAND_ECC_STATUS << 16) | + (ONENAND_COMMAND); + dma_buffer->data.addr4 = (ONENAND_CONTROLLER_STATUS << 16) | + (ONENAND_INTERRUPT_STATUS); + dma_buffer->data.addr5 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr6 = (ONENAND_START_ADDRESS_3 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.data0 = (ONENAND_CLRINTR << 16) | + (onenand_sysconfig1); + dma_buffer->data.data1 = (onenand_startaddr8 << 16) | + (onenand_startaddr1); + dma_buffer->data.data2 = (onenand_startbuffer << 16) | + (onenand_startaddr2); + dma_buffer->data.data3 = (CLEAN_DATA_16 << 16) | + (ONENAND_CMDPROGSPARE); + dma_buffer->data.data4 = (CLEAN_DATA_16 << 16) | + (CLEAN_DATA_16); + dma_buffer->data.data5 = (ONENAND_CLRINTR << 16) | + (ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode)); + dma_buffer->data.data6 = (ONENAND_STARTADDR3_RES << 16) | + (ONENAND_STARTADDR1_RES); + dma_buffer->data.macro[0] = 0x0200; + dma_buffer->data.macro[1] = 0x0300; + dma_buffer->data.macro[2] = 0x0400; + dma_buffer->data.macro[3] = 0x0500; + dma_buffer->data.macro[4] = 0x8010; + + + /*************************************************************/ + /* Write necessary address registers in the onenand device */ + /*************************************************************/ + + /* Enable and configure the SFlash controller */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfbcfg); + cmd->dst = MSM_NAND_SFLASHC_BURST_CFG; + cmd->len = 4; + cmd++; + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[0]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Write the ADDR0 and ADDR1 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr0); + cmd->dst = MSM_NAND_ADDR0; + cmd->len = 8; + cmd++; + + /* Write the ADDR2 ADDR3 ADDR4 ADDR5 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr2); + cmd->dst = MSM_NAND_ADDR2; + cmd->len = 16; + cmd++; + + /* Write the ADDR6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr6); + cmd->dst = MSM_NAND_ADDR6; + cmd->len = 4; + cmd++; + + /* Write the GENP0, GENP1, GENP2, GENP3 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data0); + cmd->dst = MSM_NAND_GENP_REG0; + cmd->len = 16; + cmd++; + + /* Write the FLASH_DEV_CMD4,5,6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->dst = MSM_NAND_DEV_CMD4; + cmd->len = 12; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[0]); + cmd->len = 4; + cmd++; + + /*************************************************************/ + /* Write the data ram area in the onenand buffer ram */ + /*************************************************************/ + + if (ops->datbuf) { + dma_buffer->data.data3 = (CLEAN_DATA_16 << 16) | + (ONENAND_CMDPROG); + + for (i = 0; i < 4; i++) { + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.sfcmd[1+i]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Trnsfr usr buf contents to nand ctlr buf */ + cmd->cmd = 0; + cmd->src = data_dma_addr_curr; + cmd->dst = MSM_NAND_FLASH_BUFFER; + cmd->len = 512; + data_dma_addr_curr += 512; + cmd++; + + /* Write the MACRO1 register */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.macro[i]); + cmd->dst = MSM_NAND_MACRO1_REG; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, + &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data rdy, & read status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, + &dma_buffer->data.sfstat[1+i]); + cmd->len = 4; + cmd++; + + } + } + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[5]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + if ((ops->oobbuf) || (ops->mode == MTD_OOB_RAW)) { + + /* Transfer user buf contents into nand ctlr buffer */ + if (ops->mode == MTD_OOB_AUTO) { + cmd->cmd = 0; + cmd->src = init_dma_addr; + cmd->dst = MSM_NAND_FLASH_BUFFER; + cmd->len = mtd->oobsize; + cmd++; + } + if (ops->mode == MTD_OOB_PLACE) { + cmd->cmd = 0; + cmd->src = oob_dma_addr_curr; + cmd->dst = MSM_NAND_FLASH_BUFFER; + cmd->len = mtd->oobsize; + oob_dma_addr_curr += mtd->oobsize; + cmd++; + } + if (ops->mode == MTD_OOB_RAW) { + cmd->cmd = 0; + cmd->src = data_dma_addr_curr; + cmd->dst = MSM_NAND_FLASH_BUFFER; + cmd->len = mtd->oobsize; + data_dma_addr_curr += mtd->oobsize; + cmd++; + } + } else { + cmd->cmd = 0; + cmd->src = init_dma_addr; + cmd->dst = MSM_NAND_FLASH_BUFFER; + cmd->len = mtd->oobsize; + cmd++; + } + + /* Write the MACRO1 register */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.macro[4]); + cmd->dst = MSM_NAND_MACRO1_REG; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[5]); + cmd->len = 4; + cmd++; + + /*********************************************************/ + /* Issuing write command */ + /*********************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[6]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[6]); + cmd->len = 4; + cmd++; + + /*************************************************************/ + /* Wait for the interrupt from the Onenand device controller */ + /*************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[7]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[7]); + cmd->len = 4; + cmd++; + + /*************************************************************/ + /* Read necessary status registers from the onenand device */ + /*************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[8]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[8]); + cmd->len = 4; + cmd++; + + /* Read the GENP3 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_GENP_REG3; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data3); + cmd->len = 4; + cmd++; + + /* Read the DEVCMD4 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_DEV_CMD4; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->len = 4; + cmd++; + + /*************************************************************/ + /* Restore the necessary registers to proper values */ + /*************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[9]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[9]); + cmd->len = 4; + cmd++; + + + BUILD_BUG_ON(53 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, dma_buffer->cmd) + >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + + ecc_status = (dma_buffer->data.data3 >> 16) & 0x0000FFFF; + interrupt_status = (dma_buffer->data.data4 >> 0)&0x0000FFFF; + controller_status = (dma_buffer->data.data4 >> 16)&0x0000FFFF; + +#if VERBOSE + pr_info("\n%s: sflash status %x %x %x %x %x %x %x" + " %x %x %x\n", __func__, + dma_buffer->data.sfstat[0], + dma_buffer->data.sfstat[1], + dma_buffer->data.sfstat[2], + dma_buffer->data.sfstat[3], + dma_buffer->data.sfstat[4], + dma_buffer->data.sfstat[5], + dma_buffer->data.sfstat[6], + dma_buffer->data.sfstat[7], + dma_buffer->data.sfstat[8], + dma_buffer->data.sfstat[9]); + + pr_info("%s: controller_status = %x\n", __func__, + controller_status); + pr_info("%s: interrupt_status = %x\n", __func__, + interrupt_status); + pr_info("%s: ecc_status = %x\n", __func__, + ecc_status); +#endif + /* Check for errors, protection violations etc */ + if ((controller_status != 0) + || (dma_buffer->data.sfstat[0] & 0x110) + || (dma_buffer->data.sfstat[6] & 0x110) + || (dma_buffer->data.sfstat[7] & 0x110) + || (dma_buffer->data.sfstat[8] & 0x110) + || (dma_buffer->data.sfstat[9] & 0x110) + || ((dma_buffer->data.sfstat[1] & 0x110) && + (ops->datbuf)) + || ((dma_buffer->data.sfstat[2] & 0x110) && + (ops->datbuf)) + || ((dma_buffer->data.sfstat[3] & 0x110) && + (ops->datbuf)) + || ((dma_buffer->data.sfstat[4] & 0x110) && + (ops->datbuf)) + || ((dma_buffer->data.sfstat[5] & 0x110) && + ((ops->oobbuf) + || (ops->mode == MTD_OOB_RAW)))) { + pr_info("%s: ECC/MPU/OP error\n", __func__); + err = -EIO; + } + + if (err) + break; + pages_written++; + to_curr += mtd->writesize; + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + + dma_unmap_page(chip->dev, init_dma_addr, 64, DMA_TO_DEVICE); + +err_dma_map_initbuf_failed: + if (ops->oobbuf) { + dma_unmap_page(chip->dev, oob_dma_addr, ops->ooblen, + DMA_TO_DEVICE); + } +err_dma_map_oobbuf_failed: + if (ops->datbuf) { + dma_unmap_page(chip->dev, data_dma_addr, ops->len, + DMA_TO_DEVICE); + } + + if (err) { + pr_err("%s: %llx %x %x failed\n", __func__, to_curr, + ops->datbuf ? ops->len : 0, ops->ooblen); + } else { + ops->retlen = ops->oobretlen = 0; + if (ops->datbuf != NULL) { + if (ops->mode != MTD_OOB_RAW) + ops->retlen = mtd->writesize * pages_written; + else + ops->retlen = (mtd->writesize + mtd->oobsize) + * pages_written; + } + if (ops->oobbuf != NULL) { + if (ops->mode == MTD_OOB_AUTO) + ops->oobretlen = mtd->oobavail * pages_written; + else + ops->oobretlen = mtd->oobsize * pages_written; + } + } + +#if VERBOSE + pr_info("\n%s: ret %d, retlen %d oobretlen %d\n", + __func__, err, ops->retlen, ops->oobretlen); + + pr_info("=================================================" + "================\n"); +#endif + kfree(init_spare_bytes); + return err; +} + +static int msm_onenand_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + int ret; + struct mtd_oob_ops ops; + + ops.mode = MTD_OOB_PLACE; + ops.datbuf = (uint8_t *)buf; + ops.len = len; + ops.retlen = 0; + ops.oobbuf = NULL; + ops.ooblen = 0; + ops.oobretlen = 0; + ret = msm_onenand_write_oob(mtd, to, &ops); + *retlen = ops.retlen; + + return ret; +} + +static int msm_onenand_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct msm_nand_chip *chip = mtd->priv; + + struct { + dmov_s cmd[20]; + unsigned cmdptr; + struct { + uint32_t sfbcfg; + uint32_t sfcmd[4]; + uint32_t sfexec; + uint32_t sfstat[4]; + uint32_t addr0; + uint32_t addr1; + uint32_t addr2; + uint32_t addr3; + uint32_t addr4; + uint32_t addr5; + uint32_t addr6; + uint32_t data0; + uint32_t data1; + uint32_t data2; + uint32_t data3; + uint32_t data4; + uint32_t data5; + uint32_t data6; + } data; + } *dma_buffer; + dmov_s *cmd; + + int err = 0; + + uint16_t onenand_startaddr1; + uint16_t onenand_startaddr8; + uint16_t onenand_startaddr2; + uint16_t onenand_startbuffer; + + uint16_t controller_status; + uint16_t interrupt_status; + uint16_t ecc_status; + + uint64_t temp; + +#if VERBOSE + pr_info("=================================================" + "================\n"); + pr_info("%s: addr 0x%llx len 0x%llx\n", + __func__, instr->addr, instr->len); +#endif + if (instr->addr & (mtd->erasesize - 1)) { + pr_err("%s: Unsupported erase address, 0x%llx\n", + __func__, instr->addr); + return -EINVAL; + } + if (instr->len != mtd->erasesize) { + pr_err("%s: Unsupported erase len, %lld\n", + __func__, instr->len); + return -EINVAL; + } + + wait_event(chip->wait_queue, (dma_buffer = msm_nand_get_dma_buffer + (chip, sizeof(*dma_buffer)))); + + cmd = dma_buffer->cmd; + + temp = instr->addr; + + if ((onenand_info.device_id & ONENAND_DEVICE_IS_DDP) + && (temp >= (mtd->size>>1))) { /* DDP Device */ + onenand_startaddr1 = DEVICE_FLASHCORE_1 | + (((uint32_t)(temp-(mtd->size>>1)) + / mtd->erasesize)); + onenand_startaddr2 = DEVICE_BUFFERRAM_1; + } else { + onenand_startaddr1 = DEVICE_FLASHCORE_0 | + ((uint32_t)temp / mtd->erasesize) ; + onenand_startaddr2 = DEVICE_BUFFERRAM_0; + } + + onenand_startaddr8 = 0x0000; + onenand_startbuffer = DATARAM0_0 << 8; + + dma_buffer->data.sfbcfg = SFLASH_BCFG | + (nand_sfcmd_mode ? 0 : (1 << 24)); + dma_buffer->data.sfcmd[0] = SFLASH_PREPCMD(7, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfcmd[1] = SFLASH_PREPCMD(0, 0, 32, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_INTHI); + dma_buffer->data.sfcmd[2] = SFLASH_PREPCMD(3, 7, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGRD); + dma_buffer->data.sfcmd[3] = SFLASH_PREPCMD(4, 10, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfexec = 1; + dma_buffer->data.sfstat[0] = CLEAN_DATA_32; + dma_buffer->data.sfstat[1] = CLEAN_DATA_32; + dma_buffer->data.sfstat[2] = CLEAN_DATA_32; + dma_buffer->data.sfstat[3] = CLEAN_DATA_32; + dma_buffer->data.addr0 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr1 = (ONENAND_START_ADDRESS_8 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.addr2 = (ONENAND_START_BUFFER << 16) | + (ONENAND_START_ADDRESS_2); + dma_buffer->data.addr3 = (ONENAND_ECC_STATUS << 16) | + (ONENAND_COMMAND); + dma_buffer->data.addr4 = (ONENAND_CONTROLLER_STATUS << 16) | + (ONENAND_INTERRUPT_STATUS); + dma_buffer->data.addr5 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr6 = (ONENAND_START_ADDRESS_3 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.data0 = (ONENAND_CLRINTR << 16) | + (ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode)); + dma_buffer->data.data1 = (onenand_startaddr8 << 16) | + (onenand_startaddr1); + dma_buffer->data.data2 = (onenand_startbuffer << 16) | + (onenand_startaddr2); + dma_buffer->data.data3 = (CLEAN_DATA_16 << 16) | + (ONENAND_CMDERAS); + dma_buffer->data.data4 = (CLEAN_DATA_16 << 16) | + (CLEAN_DATA_16); + dma_buffer->data.data5 = (ONENAND_CLRINTR << 16) | + (ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode)); + dma_buffer->data.data6 = (ONENAND_STARTADDR3_RES << 16) | + (ONENAND_STARTADDR1_RES); + + /***************************************************************/ + /* Write the necessary address registers in the onenand device */ + /***************************************************************/ + + /* Enable and configure the SFlash controller */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfbcfg); + cmd->dst = MSM_NAND_SFLASHC_BURST_CFG; + cmd->len = 4; + cmd++; + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[0]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Write the ADDR0 and ADDR1 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr0); + cmd->dst = MSM_NAND_ADDR0; + cmd->len = 8; + cmd++; + + /* Write the ADDR2 ADDR3 ADDR4 ADDR5 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr2); + cmd->dst = MSM_NAND_ADDR2; + cmd->len = 16; + cmd++; + + /* Write the ADDR6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr6); + cmd->dst = MSM_NAND_ADDR6; + cmd->len = 4; + cmd++; + + /* Write the GENP0, GENP1, GENP2, GENP3, GENP4 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data0); + cmd->dst = MSM_NAND_GENP_REG0; + cmd->len = 16; + cmd++; + + /* Write the FLASH_DEV_CMD4,5,6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->dst = MSM_NAND_DEV_CMD4; + cmd->len = 12; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[0]); + cmd->len = 4; + cmd++; + + /***************************************************************/ + /* Wait for the interrupt from the Onenand device controller */ + /***************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[1]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[1]); + cmd->len = 4; + cmd++; + + /***************************************************************/ + /* Read the necessary status registers from the onenand device */ + /***************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[2]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[2]); + cmd->len = 4; + cmd++; + + /* Read the GENP3 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_GENP_REG3; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data3); + cmd->len = 4; + cmd++; + + /* Read the DEVCMD4 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_DEV_CMD4; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->len = 4; + cmd++; + + /***************************************************************/ + /* Restore the necessary registers to proper values */ + /***************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[3]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[3]); + cmd->len = 4; + cmd++; + + + BUILD_BUG_ON(20 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, dma_buffer->cmd) + >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST + | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + + ecc_status = (dma_buffer->data.data3 >> 16) & 0x0000FFFF; + interrupt_status = (dma_buffer->data.data4 >> 0) & 0x0000FFFF; + controller_status = (dma_buffer->data.data4 >> 16) & 0x0000FFFF; + +#if VERBOSE + pr_info("\n%s: sflash status %x %x %x %x\n", __func__, + dma_buffer->data.sfstat[0], + dma_buffer->data.sfstat[1], + dma_buffer->data.sfstat[2], + dma_buffer->data.sfstat[3]); + + pr_info("%s: controller_status = %x\n", __func__, + controller_status); + pr_info("%s: interrupt_status = %x\n", __func__, + interrupt_status); + pr_info("%s: ecc_status = %x\n", __func__, + ecc_status); +#endif + /* Check for errors, protection violations etc */ + if ((controller_status != 0) + || (dma_buffer->data.sfstat[0] & 0x110) + || (dma_buffer->data.sfstat[1] & 0x110) + || (dma_buffer->data.sfstat[2] & 0x110) + || (dma_buffer->data.sfstat[3] & 0x110)) { + pr_err("%s: ECC/MPU/OP error\n", __func__); + err = -EIO; + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + + if (err) { + pr_err("%s: Erase failed, 0x%llx\n", __func__, + instr->addr); + instr->fail_addr = instr->addr; + instr->state = MTD_ERASE_FAILED; + } else { + instr->state = MTD_ERASE_DONE; + instr->fail_addr = 0xffffffff; + mtd_erase_callback(instr); + } + +#if VERBOSE + pr_info("\n%s: ret %d\n", __func__, err); + pr_info("====================================================" + "=============\n"); +#endif + return err; +} + +static int msm_onenand_block_isbad(struct mtd_info *mtd, loff_t ofs) +{ + struct mtd_oob_ops ops; + int rval, i; + int ret = 0; + uint8_t *buffer; + uint8_t *oobptr; + + if ((ofs > mtd->size) || (ofs & (mtd->erasesize - 1))) { + pr_err("%s: unsupported block address, 0x%x\n", + __func__, (uint32_t)ofs); + return -EINVAL; + } + + buffer = kmalloc(2112, GFP_KERNEL|GFP_DMA); + if (buffer == 0) { + pr_err("%s: Could not kmalloc for buffer\n", + __func__); + return -ENOMEM; + } + + memset(buffer, 0x00, 2112); + oobptr = &(buffer[2048]); + + ops.mode = MTD_OOB_RAW; + ops.len = 2112; + ops.retlen = 0; + ops.ooblen = 0; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = buffer; + ops.oobbuf = NULL; + + for (i = 0; i < 2; i++) { + ofs = ofs + i*mtd->writesize; + rval = msm_onenand_read_oob(mtd, ofs, &ops); + if (rval) { + pr_err("%s: Error in reading bad blk info\n", + __func__); + ret = rval; + break; + } + if ((oobptr[0] != 0xFF) || (oobptr[1] != 0xFF) || + (oobptr[16] != 0xFF) || (oobptr[17] != 0xFF) || + (oobptr[32] != 0xFF) || (oobptr[33] != 0xFF) || + (oobptr[48] != 0xFF) || (oobptr[49] != 0xFF) + ) { + ret = 1; + break; + } + } + + kfree(buffer); + +#if VERBOSE + if (ret == 1) + pr_info("%s : Block containing 0x%x is bad\n", + __func__, (unsigned int)ofs); +#endif + return ret; +} + +static int msm_onenand_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct mtd_oob_ops ops; + int rval, i; + int ret = 0; + uint8_t *buffer; + + if ((ofs > mtd->size) || (ofs & (mtd->erasesize - 1))) { + pr_err("%s: unsupported block address, 0x%x\n", + __func__, (uint32_t)ofs); + return -EINVAL; + } + + buffer = page_address(ZERO_PAGE()); + + ops.mode = MTD_OOB_RAW; + ops.len = 2112; + ops.retlen = 0; + ops.ooblen = 0; + ops.oobretlen = 0; + ops.ooboffs = 0; + ops.datbuf = buffer; + ops.oobbuf = NULL; + + for (i = 0; i < 2; i++) { + ofs = ofs + i*mtd->writesize; + rval = msm_onenand_write_oob(mtd, ofs, &ops); + if (rval) { + pr_err("%s: Error in writing bad blk info\n", + __func__); + ret = rval; + break; + } + } + + return ret; +} + +static int msm_onenand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct msm_nand_chip *chip = mtd->priv; + + struct { + dmov_s cmd[20]; + unsigned cmdptr; + struct { + uint32_t sfbcfg; + uint32_t sfcmd[4]; + uint32_t sfexec; + uint32_t sfstat[4]; + uint32_t addr0; + uint32_t addr1; + uint32_t addr2; + uint32_t addr3; + uint32_t addr4; + uint32_t addr5; + uint32_t addr6; + uint32_t data0; + uint32_t data1; + uint32_t data2; + uint32_t data3; + uint32_t data4; + uint32_t data5; + uint32_t data6; + } data; + } *dma_buffer; + dmov_s *cmd; + + int err = 0; + + uint16_t onenand_startaddr1; + uint16_t onenand_startaddr8; + uint16_t onenand_startaddr2; + uint16_t onenand_startblock; + + uint16_t controller_status; + uint16_t interrupt_status; + uint16_t write_prot_status; + + uint64_t start_ofs; + +#if VERBOSE + pr_info("====================================================" + "=============\n"); + pr_info("%s: ofs 0x%llx len %lld\n", __func__, ofs, len); +#endif + /* 'ofs' & 'len' should align to block size */ + if (ofs&(mtd->erasesize - 1)) { + pr_err("%s: Unsupported ofs address, 0x%llx\n", + __func__, ofs); + return -EINVAL; + } + + if (len&(mtd->erasesize - 1)) { + pr_err("%s: Unsupported len, %lld\n", + __func__, len); + return -EINVAL; + } + + if (ofs+len > mtd->size) { + pr_err("%s: Maximum chip size exceeded\n", __func__); + return -EINVAL; + } + + wait_event(chip->wait_queue, (dma_buffer = msm_nand_get_dma_buffer + (chip, sizeof(*dma_buffer)))); + + for (start_ofs = ofs; ofs < start_ofs+len; ofs = ofs+mtd->erasesize) { +#if VERBOSE + pr_info("%s: ofs 0x%llx len %lld\n", __func__, ofs, len); +#endif + + cmd = dma_buffer->cmd; + if ((onenand_info.device_id & ONENAND_DEVICE_IS_DDP) + && (ofs >= (mtd->size>>1))) { /* DDP Device */ + onenand_startaddr1 = DEVICE_FLASHCORE_1 | + (((uint32_t)(ofs - (mtd->size>>1)) + / mtd->erasesize)); + onenand_startaddr2 = DEVICE_BUFFERRAM_1; + onenand_startblock = ((uint32_t)(ofs - (mtd->size>>1)) + / mtd->erasesize); + } else { + onenand_startaddr1 = DEVICE_FLASHCORE_0 | + ((uint32_t)ofs / mtd->erasesize) ; + onenand_startaddr2 = DEVICE_BUFFERRAM_0; + onenand_startblock = ((uint32_t)ofs + / mtd->erasesize); + } + + onenand_startaddr8 = 0x0000; + dma_buffer->data.sfbcfg = SFLASH_BCFG | + (nand_sfcmd_mode ? 0 : (1 << 24)); + dma_buffer->data.sfcmd[0] = SFLASH_PREPCMD(7, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfcmd[1] = SFLASH_PREPCMD(0, 0, 32, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_INTHI); + dma_buffer->data.sfcmd[2] = SFLASH_PREPCMD(3, 7, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGRD); + dma_buffer->data.sfcmd[3] = SFLASH_PREPCMD(4, 10, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfexec = 1; + dma_buffer->data.sfstat[0] = CLEAN_DATA_32; + dma_buffer->data.sfstat[1] = CLEAN_DATA_32; + dma_buffer->data.sfstat[2] = CLEAN_DATA_32; + dma_buffer->data.sfstat[3] = CLEAN_DATA_32; + dma_buffer->data.addr0 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr1 = (ONENAND_START_ADDRESS_8 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.addr2 = (ONENAND_START_BLOCK_ADDRESS << 16) | + (ONENAND_START_ADDRESS_2); + dma_buffer->data.addr3 = (ONENAND_WRITE_PROT_STATUS << 16) | + (ONENAND_COMMAND); + dma_buffer->data.addr4 = (ONENAND_CONTROLLER_STATUS << 16) | + (ONENAND_INTERRUPT_STATUS); + dma_buffer->data.addr5 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr6 = (ONENAND_START_ADDRESS_3 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.data0 = (ONENAND_CLRINTR << 16) | + (ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode)); + dma_buffer->data.data1 = (onenand_startaddr8 << 16) | + (onenand_startaddr1); + dma_buffer->data.data2 = (onenand_startblock << 16) | + (onenand_startaddr2); + dma_buffer->data.data3 = (CLEAN_DATA_16 << 16) | + (ONENAND_CMD_UNLOCK); + dma_buffer->data.data4 = (CLEAN_DATA_16 << 16) | + (CLEAN_DATA_16); + dma_buffer->data.data5 = (ONENAND_CLRINTR << 16) | + (ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode)); + dma_buffer->data.data6 = (ONENAND_STARTADDR3_RES << 16) | + (ONENAND_STARTADDR1_RES); + + /*************************************************************/ + /* Write the necessary address reg in the onenand device */ + /*************************************************************/ + + /* Enable and configure the SFlash controller */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfbcfg); + cmd->dst = MSM_NAND_SFLASHC_BURST_CFG; + cmd->len = 4; + cmd++; + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[0]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Write the ADDR0 and ADDR1 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr0); + cmd->dst = MSM_NAND_ADDR0; + cmd->len = 8; + cmd++; + + /* Write the ADDR2 ADDR3 ADDR4 ADDR5 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr2); + cmd->dst = MSM_NAND_ADDR2; + cmd->len = 16; + cmd++; + + /* Write the ADDR6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr6); + cmd->dst = MSM_NAND_ADDR6; + cmd->len = 4; + cmd++; + + /* Write the GENP0, GENP1, GENP2, GENP3, GENP4 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data0); + cmd->dst = MSM_NAND_GENP_REG0; + cmd->len = 16; + cmd++; + + /* Write the FLASH_DEV_CMD4,5,6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->dst = MSM_NAND_DEV_CMD4; + cmd->len = 12; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[0]); + cmd->len = 4; + cmd++; + + /*************************************************************/ + /* Wait for the interrupt from the Onenand device controller */ + /*************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[1]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[1]); + cmd->len = 4; + cmd++; + + /*********************************************************/ + /* Read the necessary status reg from the onenand device */ + /*********************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[2]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[2]); + cmd->len = 4; + cmd++; + + /* Read the GENP3 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_GENP_REG3; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data3); + cmd->len = 4; + cmd++; + + /* Read the DEVCMD4 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_DEV_CMD4; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->len = 4; + cmd++; + + /************************************************************/ + /* Restore the necessary registers to proper values */ + /************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[3]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[3]); + cmd->len = 4; + cmd++; + + + BUILD_BUG_ON(20 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, dma_buffer->cmd) + >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + + write_prot_status = (dma_buffer->data.data3 >> 16) & 0x0000FFFF; + interrupt_status = (dma_buffer->data.data4 >> 0) & 0x0000FFFF; + controller_status = (dma_buffer->data.data4 >> 16) & 0x0000FFFF; + +#if VERBOSE + pr_info("\n%s: sflash status %x %x %x %x\n", __func__, + dma_buffer->data.sfstat[0], + dma_buffer->data.sfstat[1], + dma_buffer->data.sfstat[2], + dma_buffer->data.sfstat[3]); + + pr_info("%s: controller_status = %x\n", __func__, + controller_status); + pr_info("%s: interrupt_status = %x\n", __func__, + interrupt_status); + pr_info("%s: write_prot_status = %x\n", __func__, + write_prot_status); +#endif + /* Check for errors, protection violations etc */ + if ((controller_status != 0) + || (dma_buffer->data.sfstat[0] & 0x110) + || (dma_buffer->data.sfstat[1] & 0x110) + || (dma_buffer->data.sfstat[2] & 0x110) + || (dma_buffer->data.sfstat[3] & 0x110)) { + pr_err("%s: ECC/MPU/OP error\n", __func__); + err = -EIO; + } + + if (!(write_prot_status & ONENAND_WP_US)) { + pr_err("%s: Unexpected status ofs = 0x%llx," + "wp_status = %x\n", + __func__, ofs, write_prot_status); + err = -EIO; + } + + if (err) + break; + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + +#if VERBOSE + pr_info("\n%s: ret %d\n", __func__, err); + pr_info("====================================================" + "=============\n"); +#endif + return err; +} + +static int msm_onenand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct msm_nand_chip *chip = mtd->priv; + + struct { + dmov_s cmd[20]; + unsigned cmdptr; + struct { + uint32_t sfbcfg; + uint32_t sfcmd[4]; + uint32_t sfexec; + uint32_t sfstat[4]; + uint32_t addr0; + uint32_t addr1; + uint32_t addr2; + uint32_t addr3; + uint32_t addr4; + uint32_t addr5; + uint32_t addr6; + uint32_t data0; + uint32_t data1; + uint32_t data2; + uint32_t data3; + uint32_t data4; + uint32_t data5; + uint32_t data6; + } data; + } *dma_buffer; + dmov_s *cmd; + + int err = 0; + + uint16_t onenand_startaddr1; + uint16_t onenand_startaddr8; + uint16_t onenand_startaddr2; + uint16_t onenand_startblock; + + uint16_t controller_status; + uint16_t interrupt_status; + uint16_t write_prot_status; + + uint64_t start_ofs; + +#if VERBOSE + pr_info("====================================================" + "=============\n"); + pr_info("%s: ofs 0x%llx len %lld\n", __func__, ofs, len); +#endif + /* 'ofs' & 'len' should align to block size */ + if (ofs&(mtd->erasesize - 1)) { + pr_err("%s: Unsupported ofs address, 0x%llx\n", + __func__, ofs); + return -EINVAL; + } + + if (len&(mtd->erasesize - 1)) { + pr_err("%s: Unsupported len, %lld\n", + __func__, len); + return -EINVAL; + } + + if (ofs+len > mtd->size) { + pr_err("%s: Maximum chip size exceeded\n", __func__); + return -EINVAL; + } + + wait_event(chip->wait_queue, (dma_buffer = msm_nand_get_dma_buffer + (chip, sizeof(*dma_buffer)))); + + for (start_ofs = ofs; ofs < start_ofs+len; ofs = ofs+mtd->erasesize) { +#if VERBOSE + pr_info("%s: ofs 0x%llx len %lld\n", __func__, ofs, len); +#endif + + cmd = dma_buffer->cmd; + if ((onenand_info.device_id & ONENAND_DEVICE_IS_DDP) + && (ofs >= (mtd->size>>1))) { /* DDP Device */ + onenand_startaddr1 = DEVICE_FLASHCORE_1 | + (((uint32_t)(ofs - (mtd->size>>1)) + / mtd->erasesize)); + onenand_startaddr2 = DEVICE_BUFFERRAM_1; + onenand_startblock = ((uint32_t)(ofs - (mtd->size>>1)) + / mtd->erasesize); + } else { + onenand_startaddr1 = DEVICE_FLASHCORE_0 | + ((uint32_t)ofs / mtd->erasesize) ; + onenand_startaddr2 = DEVICE_BUFFERRAM_0; + onenand_startblock = ((uint32_t)ofs + / mtd->erasesize); + } + + onenand_startaddr8 = 0x0000; + dma_buffer->data.sfbcfg = SFLASH_BCFG | + (nand_sfcmd_mode ? 0 : (1 << 24)); + dma_buffer->data.sfcmd[0] = SFLASH_PREPCMD(7, 0, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfcmd[1] = SFLASH_PREPCMD(0, 0, 32, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_INTHI); + dma_buffer->data.sfcmd[2] = SFLASH_PREPCMD(3, 7, 0, + MSM_NAND_SFCMD_DATXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGRD); + dma_buffer->data.sfcmd[3] = SFLASH_PREPCMD(4, 10, 0, + MSM_NAND_SFCMD_CMDXS, + nand_sfcmd_mode, + MSM_NAND_SFCMD_REGWR); + dma_buffer->data.sfexec = 1; + dma_buffer->data.sfstat[0] = CLEAN_DATA_32; + dma_buffer->data.sfstat[1] = CLEAN_DATA_32; + dma_buffer->data.sfstat[2] = CLEAN_DATA_32; + dma_buffer->data.sfstat[3] = CLEAN_DATA_32; + dma_buffer->data.addr0 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr1 = (ONENAND_START_ADDRESS_8 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.addr2 = (ONENAND_START_BLOCK_ADDRESS << 16) | + (ONENAND_START_ADDRESS_2); + dma_buffer->data.addr3 = (ONENAND_WRITE_PROT_STATUS << 16) | + (ONENAND_COMMAND); + dma_buffer->data.addr4 = (ONENAND_CONTROLLER_STATUS << 16) | + (ONENAND_INTERRUPT_STATUS); + dma_buffer->data.addr5 = (ONENAND_INTERRUPT_STATUS << 16) | + (ONENAND_SYSTEM_CONFIG_1); + dma_buffer->data.addr6 = (ONENAND_START_ADDRESS_3 << 16) | + (ONENAND_START_ADDRESS_1); + dma_buffer->data.data0 = (ONENAND_CLRINTR << 16) | + (ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode)); + dma_buffer->data.data1 = (onenand_startaddr8 << 16) | + (onenand_startaddr1); + dma_buffer->data.data2 = (onenand_startblock << 16) | + (onenand_startaddr2); + dma_buffer->data.data3 = (CLEAN_DATA_16 << 16) | + (ONENAND_CMD_LOCK); + dma_buffer->data.data4 = (CLEAN_DATA_16 << 16) | + (CLEAN_DATA_16); + dma_buffer->data.data5 = (ONENAND_CLRINTR << 16) | + (ONENAND_SYSCFG1_ECCENA(nand_sfcmd_mode)); + dma_buffer->data.data6 = (ONENAND_STARTADDR3_RES << 16) | + (ONENAND_STARTADDR1_RES); + + /*************************************************************/ + /* Write the necessary address reg in the onenand device */ + /*************************************************************/ + + /* Enable and configure the SFlash controller */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfbcfg); + cmd->dst = MSM_NAND_SFLASHC_BURST_CFG; + cmd->len = 4; + cmd++; + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[0]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Write the ADDR0 and ADDR1 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr0); + cmd->dst = MSM_NAND_ADDR0; + cmd->len = 8; + cmd++; + + /* Write the ADDR2 ADDR3 ADDR4 ADDR5 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr2); + cmd->dst = MSM_NAND_ADDR2; + cmd->len = 16; + cmd++; + + /* Write the ADDR6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.addr6); + cmd->dst = MSM_NAND_ADDR6; + cmd->len = 4; + cmd++; + + /* Write the GENP0, GENP1, GENP2, GENP3, GENP4 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data0); + cmd->dst = MSM_NAND_GENP_REG0; + cmd->len = 16; + cmd++; + + /* Write the FLASH_DEV_CMD4,5,6 registers */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->dst = MSM_NAND_DEV_CMD4; + cmd->len = 12; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[0]); + cmd->len = 4; + cmd++; + + /*************************************************************/ + /* Wait for the interrupt from the Onenand device controller */ + /*************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[1]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[1]); + cmd->len = 4; + cmd++; + + /*********************************************************/ + /* Read the necessary status reg from the onenand device */ + /*********************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[2]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[2]); + cmd->len = 4; + cmd++; + + /* Read the GENP3 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_GENP_REG3; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data3); + cmd->len = 4; + cmd++; + + /* Read the DEVCMD4 register */ + cmd->cmd = 0; + cmd->src = MSM_NAND_DEV_CMD4; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.data4); + cmd->len = 4; + cmd++; + + /************************************************************/ + /* Restore the necessary registers to proper values */ + /************************************************************/ + + /* Block on cmd ready and write CMD register */ + cmd->cmd = DST_CRCI_NAND_CMD; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfcmd[3]); + cmd->dst = MSM_NAND_SFLASHC_CMD; + cmd->len = 4; + cmd++; + + /* Kick the execute command */ + cmd->cmd = 0; + cmd->src = msm_virt_to_dma(chip, &dma_buffer->data.sfexec); + cmd->dst = MSM_NAND_SFLASHC_EXEC_CMD; + cmd->len = 4; + cmd++; + + /* Block on data ready, and read the status register */ + cmd->cmd = SRC_CRCI_NAND_DATA; + cmd->src = MSM_NAND_SFLASHC_STATUS; + cmd->dst = msm_virt_to_dma(chip, &dma_buffer->data.sfstat[3]); + cmd->len = 4; + cmd++; + + + BUILD_BUG_ON(20 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + + dma_buffer->cmdptr = (msm_virt_to_dma(chip, dma_buffer->cmd) + >> 3) | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, + DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + + write_prot_status = (dma_buffer->data.data3 >> 16) & 0x0000FFFF; + interrupt_status = (dma_buffer->data.data4 >> 0) & 0x0000FFFF; + controller_status = (dma_buffer->data.data4 >> 16) & 0x0000FFFF; + +#if VERBOSE + pr_info("\n%s: sflash status %x %x %x %x\n", __func__, + dma_buffer->data.sfstat[0], + dma_buffer->data.sfstat[1], + dma_buffer->data.sfstat[2], + dma_buffer->data.sfstat[3]); + + pr_info("%s: controller_status = %x\n", __func__, + controller_status); + pr_info("%s: interrupt_status = %x\n", __func__, + interrupt_status); + pr_info("%s: write_prot_status = %x\n", __func__, + write_prot_status); +#endif + /* Check for errors, protection violations etc */ + if ((controller_status != 0) + || (dma_buffer->data.sfstat[0] & 0x110) + || (dma_buffer->data.sfstat[1] & 0x110) + || (dma_buffer->data.sfstat[2] & 0x110) + || (dma_buffer->data.sfstat[3] & 0x110)) { + pr_err("%s: ECC/MPU/OP error\n", __func__); + err = -EIO; + } + + if (!(write_prot_status & ONENAND_WP_LS)) { + pr_err("%s: Unexpected status ofs = 0x%llx," + "wp_status = %x\n", + __func__, ofs, write_prot_status); + err = -EIO; + } + + if (err) + break; + } + + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + +#if VERBOSE + pr_info("\n%s: ret %d\n", __func__, err); + pr_info("====================================================" + "=============\n"); +#endif + return err; +} + +static int msm_onenand_suspend(struct mtd_info *mtd) +{ + return 0; +} + +static void msm_onenand_resume(struct mtd_info *mtd) +{ +} + +int msm_onenand_scan(struct mtd_info *mtd, int maxchips) +{ + struct msm_nand_chip *chip = mtd->priv; + + /* Probe and check whether onenand device is present */ + if (flash_onenand_probe(chip)) + return -ENODEV; + + mtd->size = 0x1000000 << ((onenand_info.device_id & 0xF0) >> 4); + mtd->writesize = onenand_info.data_buf_size; + mtd->oobsize = mtd->writesize >> 5; + mtd->erasesize = mtd->writesize << 6; + mtd->oobavail = msm_onenand_oob_64.oobavail; + mtd->ecclayout = &msm_onenand_oob_64; + + mtd->type = MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->erase = msm_onenand_erase; + mtd->point = NULL; + mtd->unpoint = NULL; + mtd->read = msm_onenand_read; + mtd->write = msm_onenand_write; + mtd->read_oob = msm_onenand_read_oob; + mtd->write_oob = msm_onenand_write_oob; + mtd->lock = msm_onenand_lock; + mtd->unlock = msm_onenand_unlock; + mtd->suspend = msm_onenand_suspend; + mtd->resume = msm_onenand_resume; + mtd->block_isbad = msm_onenand_block_isbad; + mtd->block_markbad = msm_onenand_block_markbad; + mtd->owner = THIS_MODULE; + + pr_info("Found a supported onenand device\n"); + + return 0; +} + +/** + * msm_nand_scan - [msm_nand Interface] Scan for the msm_nand device + * @param mtd MTD device structure + * @param maxchips Number of chips to scan for + * + * This fills out all the not initialized function pointers + * with the defaults. + * The flash ID is read and the mtd/chip structures are + * filled with the appropriate values. + */ +int msm_nand_scan(struct mtd_info *mtd, int maxchips) +{ + struct msm_nand_chip *chip = mtd->priv; + uint32_t flash_id = 0, i, mtd_writesize; + uint8_t dev_found = 0; + uint8_t wide_bus; + uint32_t manid; + uint32_t devid; + uint32_t devcfg; + struct nand_flash_dev *flashdev = NULL; + struct nand_manufacturers *flashman = NULL; + + /* Probe the Flash device for ONFI compliance */ + if (!flash_onfi_probe(chip)) { + dev_found = 1; + } else { + /* Read the Flash ID from the Nand Flash Device */ + flash_id = flash_read_id(chip); + manid = flash_id & 0xFF; + devid = (flash_id >> 8) & 0xFF; + devcfg = (flash_id >> 24) & 0xFF; + + for (i = 0; !flashman && nand_manuf_ids[i].id; ++i) + if (nand_manuf_ids[i].id == manid) + flashman = &nand_manuf_ids[i]; + for (i = 0; !flashdev && nand_flash_ids[i].id; ++i) + if (nand_flash_ids[i].id == devid) + flashdev = &nand_flash_ids[i]; + if (!flashdev || !flashman) { + pr_err("ERROR: unknown nand device manuf=%x devid=%x\n", + manid, devid); + return -ENOENT; + } else + dev_found = 1; + + if (!flashdev->pagesize) { + supported_flash.flash_id = flash_id; + supported_flash.density = flashdev->chipsize << 20; + supported_flash.widebus = devcfg & (1 << 6) ? 1 : 0; + supported_flash.pagesize = 1024 << (devcfg & 0x3); + supported_flash.blksize = (64 * 1024) << + ((devcfg >> 4) & 0x3); + supported_flash.oobsize = (8 << ((devcfg >> 2) & 1)) * + (supported_flash.pagesize >> 9); + } else { + supported_flash.flash_id = flash_id; + supported_flash.density = flashdev->chipsize << 20; + supported_flash.widebus = flashdev->options & + NAND_BUSWIDTH_16 ? 1 : 0; + supported_flash.pagesize = flashdev->pagesize; + supported_flash.blksize = flashdev->erasesize; + supported_flash.oobsize = flashdev->pagesize >> 5; + } + } + + if (dev_found) { + (!interleave_enable) ? (i = 1) : (i = 2); + wide_bus = supported_flash.widebus; + mtd->size = supported_flash.density * i; + mtd->writesize = supported_flash.pagesize * i; + mtd->oobsize = supported_flash.oobsize * i; + mtd->erasesize = supported_flash.blksize * i; + + if (!interleave_enable) + mtd_writesize = mtd->writesize; + else + mtd_writesize = mtd->writesize >> 1; + + /* Check whether controller and NAND device support 8bit ECC*/ + if ((flash_rd_reg(chip, MSM_NAND_HW_INFO) == 0x307) + && (supported_flash.ecc_correctability >= 8)) { + pr_info("Found supported NAND device for %dbit ECC\n", + supported_flash.ecc_correctability); + enable_bch_ecc = 1; + } else { + pr_info("Found a supported NAND device\n"); + } + pr_info("NAND Id : 0x%x\n", supported_flash.flash_id); + pr_info("Buswidth : %d Bits\n", (wide_bus) ? 16 : 8); + pr_info("Density : %lld MByte\n", (mtd->size>>20)); + pr_info("Pagesize : %d Bytes\n", mtd->writesize); + pr_info("Erasesize: %d Bytes\n", mtd->erasesize); + pr_info("Oobsize : %d Bytes\n", mtd->oobsize); + } else { + pr_err("Unsupported Nand,Id: 0x%x \n", flash_id); + return -ENODEV; + } + + /* Size of each codeword is 532Bytes incase of 8bit BCH ECC*/ + chip->cw_size = enable_bch_ecc ? 532 : 528; + chip->CFG0 = (((mtd_writesize >> 9)-1) << 6) /* 4/8 cw/pg for 2/4k */ + | (516 << 9) /* 516 user data bytes */ + | (10 << 19) /* 10 parity bytes */ + | (5 << 27) /* 5 address cycles */ + | (0 << 30) /* Do not read status before data */ + | (1 << 31) /* Send read cmd */ + /* 0 spare bytes for 16 bit nand or 1/2 spare bytes for 8 bit */ + | (wide_bus ? 0 << 23 : (enable_bch_ecc ? 2 << 23 : 1 << 23)); + + chip->CFG1 = (0 << 0) /* Enable ecc */ + | (7 << 2) /* 8 recovery cycles */ + | (0 << 5) /* Allow CS deassertion */ + /* Bad block marker location */ + | ((mtd_writesize - (chip->cw_size * ( + (mtd_writesize >> 9) - 1)) + 1) << 6) + | (0 << 16) /* Bad block in user data area */ + | (2 << 17) /* 6 cycle tWB/tRB */ + | ((wide_bus) ? CFG1_WIDE_FLASH : 0); /* Wide flash bit */ + + chip->ecc_buf_cfg = 0x203; + chip->CFG0_RAW = 0xA80420C0; + chip->CFG1_RAW = 0x5045D; + + if (enable_bch_ecc) { + chip->CFG1 |= (1 << 27); /* Enable BCH engine */ + chip->ecc_bch_cfg = (0 << 0) /* Enable ECC*/ + | (0 << 1) /* Enable/Disable SW reset of ECC engine */ + | (1 << 4) /* 8bit ecc*/ + | ((wide_bus) ? (14 << 8) : (13 << 8))/*parity bytes*/ + | (516 << 16) /* 516 user data bytes */ + | (1 << 30); /* Turn on ECC engine clocks always */ + chip->CFG0_RAW = 0xA80428C0; /* CW size is increased to 532B */ + } + + /* + * For 4bit RS ECC (default ECC), parity bytes = 10 (for x8 and x16 I/O) + * For 8bit BCH ECC, parity bytes = 13 (x8) or 14 (x16 I/O). + */ + chip->ecc_parity_bytes = enable_bch_ecc ? (wide_bus ? 14 : 13) : 10; + + pr_info("CFG0 Init : 0x%08x\n", chip->CFG0); + pr_info("CFG1 Init : 0x%08x\n", chip->CFG1); + pr_info("ECCBUFCFG : 0x%08x\n", chip->ecc_buf_cfg); + + if (mtd->oobsize == 64) { + mtd->oobavail = msm_nand_oob_64.oobavail; + mtd->ecclayout = &msm_nand_oob_64; + } else if (mtd->oobsize == 128) { + mtd->oobavail = msm_nand_oob_128.oobavail; + mtd->ecclayout = &msm_nand_oob_128; + } else if (mtd->oobsize == 224) { + mtd->oobavail = wide_bus ? msm_nand_oob_224_x16.oobavail : + msm_nand_oob_224_x8.oobavail; + mtd->ecclayout = wide_bus ? &msm_nand_oob_224_x16 : + &msm_nand_oob_224_x8; + } else if (mtd->oobsize == 256) { + mtd->oobavail = msm_nand_oob_256.oobavail; + mtd->ecclayout = &msm_nand_oob_256; + } else { + pr_err("Unsupported Nand, oobsize: 0x%x \n", + mtd->oobsize); + return -ENODEV; + } + + /* Fill in remaining MTD driver data */ + mtd->type = MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + /* mtd->ecctype = MTD_ECC_SW; */ + mtd->erase = msm_nand_erase; + mtd->block_isbad = msm_nand_block_isbad; + mtd->block_markbad = msm_nand_block_markbad; + mtd->point = NULL; + mtd->unpoint = NULL; + mtd->read = msm_nand_read; + mtd->write = msm_nand_write; + mtd->read_oob = msm_nand_read_oob; + mtd->write_oob = msm_nand_write_oob; + if (dual_nand_ctlr_present) { + mtd->read_oob = msm_nand_read_oob_dualnandc; + mtd->write_oob = msm_nand_write_oob_dualnandc; + if (interleave_enable) { + mtd->erase = msm_nand_erase_dualnandc; + mtd->block_isbad = msm_nand_block_isbad_dualnandc; + } + } + + /* mtd->sync = msm_nand_sync; */ + mtd->lock = NULL; + /* mtd->unlock = msm_nand_unlock; */ + mtd->suspend = msm_nand_suspend; + mtd->resume = msm_nand_resume; + mtd->owner = THIS_MODULE; + + /* Unlock whole block */ + /* msm_nand_unlock_all(mtd); */ + + /* return this->scan_bbt(mtd); */ + return 0; +} +EXPORT_SYMBOL_GPL(msm_nand_scan); + +/** + * msm_nand_release - [msm_nand Interface] Free resources held by the msm_nand device + * @param mtd MTD device structure + */ +void msm_nand_release(struct mtd_info *mtd) +{ + /* struct msm_nand_chip *this = mtd->priv; */ + +#ifdef CONFIG_MTD_PARTITIONS + /* Deregister partitions */ + del_mtd_partitions(mtd); +#endif + /* Deregister the device */ + mtd_device_unregister(mtd); +} +EXPORT_SYMBOL_GPL(msm_nand_release); + +#ifdef CONFIG_MTD_PARTITIONS +static const char *part_probes[] = { "cmdlinepart", NULL, }; +#endif + +struct msm_nand_info { + struct mtd_info mtd; + struct mtd_partition *parts; + struct msm_nand_chip msm_nand; +}; + +/* duplicating the NC01 XFR contents to NC10 */ +static int msm_nand_nc10_xfr_settings(struct mtd_info *mtd) +{ + struct msm_nand_chip *chip = mtd->priv; + + struct { + dmov_s cmd[2]; + unsigned cmdptr; + } *dma_buffer; + dmov_s *cmd; + + wait_event(chip->wait_queue, + (dma_buffer = msm_nand_get_dma_buffer( + chip, sizeof(*dma_buffer)))); + + cmd = dma_buffer->cmd; + + /* Copying XFR register contents from NC01 --> NC10 */ + cmd->cmd = 0; + cmd->src = NC01(MSM_NAND_XFR_STEP1); + cmd->dst = NC10(MSM_NAND_XFR_STEP1); + cmd->len = 28; + cmd++; + + BUILD_BUG_ON(2 != ARRAY_SIZE(dma_buffer->cmd)); + BUG_ON(cmd - dma_buffer->cmd > ARRAY_SIZE(dma_buffer->cmd)); + dma_buffer->cmd[0].cmd |= CMD_OCB; + cmd[-1].cmd |= CMD_OCU | CMD_LC; + dma_buffer->cmdptr = (msm_virt_to_dma(chip, dma_buffer->cmd) >> 3) + | CMD_PTR_LP; + + mb(); + msm_dmov_exec_cmd(chip->dma_channel, crci_mask, DMOV_CMD_PTR_LIST + | DMOV_CMD_ADDR(msm_virt_to_dma(chip, + &dma_buffer->cmdptr))); + mb(); + msm_nand_release_dma_buffer(chip, dma_buffer, sizeof(*dma_buffer)); + return 0; +} + +#ifdef CONFIG_MTD_PARTITIONS +static void setup_mtd_device(struct platform_device *pdev, + struct msm_nand_info *info) +{ + int i, nr_parts; + struct flash_platform_data *pdata = pdev->dev.platform_data; + + for (i = 0; i < pdata->nr_parts; i++) { + pdata->parts[i].offset = pdata->parts[i].offset + * info->mtd.erasesize; + pdata->parts[i].size = pdata->parts[i].size + * info->mtd.erasesize; + } + + nr_parts = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, + 0); + if (nr_parts > 0) + add_mtd_partitions(&info->mtd, info->parts, nr_parts); + else if (nr_parts <= 0 && pdata && pdata->parts) + add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts); + else + mtd_device_register(&info->mtd, NULL, 0); +} +#else +static void setup_mtd_device(struct platform_device *pdev, + struct msm_nand_info *info) +{ + mtd_device_register(&info->mtd, NULL, 0); +} +#endif + +static int __devinit msm_nand_probe(struct platform_device *pdev) +{ + struct msm_nand_info *info; + struct resource *res; + int err; + struct flash_platform_data *plat_data; + + plat_data = pdev->dev.platform_data; + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "msm_nand_phys"); + if (!res || !res->start) { + pr_err("%s: msm_nand_phys resource invalid/absent\n", + __func__); + return -ENODEV; + } + msm_nand_phys = res->start; + pr_info("%s: phys addr 0x%lx \n", __func__, msm_nand_phys); + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "msm_nandc01_phys"); + if (!res || !res->start) + goto no_dual_nand_ctlr_support; + msm_nandc01_phys = res->start; + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "msm_nandc10_phys"); + if (!res || !res->start) + goto no_dual_nand_ctlr_support; + msm_nandc10_phys = res->start; + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "msm_nandc11_phys"); + if (!res || !res->start) + goto no_dual_nand_ctlr_support; + msm_nandc11_phys = res->start; + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "ebi2_reg_base"); + if (!res || !res->start) + goto no_dual_nand_ctlr_support; + ebi2_register_base = res->start; + + dual_nand_ctlr_present = 1; + if (plat_data != NULL) + interleave_enable = plat_data->interleave; + else + interleave_enable = 0; + + if (!interleave_enable) + pr_info("%s: Dual Nand Ctrl in ping-pong mode\n", __func__); + else + pr_info("%s: Dual Nand Ctrl in interleave mode\n", __func__); + +no_dual_nand_ctlr_support: + res = platform_get_resource_byname(pdev, + IORESOURCE_DMA, "msm_nand_dmac"); + if (!res || !res->start) { + pr_err("%s: invalid msm_nand_dmac resource\n", __func__); + return -ENODEV; + } + + info = kzalloc(sizeof(struct msm_nand_info), GFP_KERNEL); + if (!info) { + pr_err("%s: No memory for msm_nand_info\n", __func__); + return -ENOMEM; + } + + info->msm_nand.dev = &pdev->dev; + + init_waitqueue_head(&info->msm_nand.wait_queue); + + info->msm_nand.dma_channel = res->start; + pr_info("%s: dmac 0x%x\n", __func__, info->msm_nand.dma_channel); + + /* this currently fails if dev is passed in */ + info->msm_nand.dma_buffer = + dma_alloc_coherent(/*dev*/ NULL, MSM_NAND_DMA_BUFFER_SIZE, + &info->msm_nand.dma_addr, GFP_KERNEL); + if (info->msm_nand.dma_buffer == NULL) { + pr_err("%s: No memory for msm_nand.dma_buffer\n", __func__); + err = -ENOMEM; + goto out_free_info; + } + + pr_info("%s: allocated dma buffer at %p, dma_addr %x\n", + __func__, info->msm_nand.dma_buffer, info->msm_nand.dma_addr); + + crci_mask = msm_dmov_build_crci_mask(2, + DMOV_NAND_CRCI_DATA, DMOV_NAND_CRCI_CMD); + + info->mtd.name = dev_name(&pdev->dev); + info->mtd.priv = &info->msm_nand; + info->mtd.owner = THIS_MODULE; + + /* config ebi2_cfg register only for ping pong mode!!! */ + if (!interleave_enable && dual_nand_ctlr_present) + flash_wr_reg(&info->msm_nand, EBI2_CFG_REG, 0x4010080); + + if (dual_nand_ctlr_present) + msm_nand_nc10_xfr_settings(&info->mtd); + + if (msm_nand_scan(&info->mtd, 1)) + if (msm_onenand_scan(&info->mtd, 1)) { + pr_err("%s: No nand device found\n", __func__); + err = -ENXIO; + goto out_free_dma_buffer; + } + + setup_mtd_device(pdev, info); + dev_set_drvdata(&pdev->dev, info); + + return 0; + +out_free_dma_buffer: + dma_free_coherent(NULL, MSM_NAND_DMA_BUFFER_SIZE, + info->msm_nand.dma_buffer, + info->msm_nand.dma_addr); +out_free_info: + kfree(info); + + return err; +} + +static int __devexit msm_nand_remove(struct platform_device *pdev) +{ + struct msm_nand_info *info = dev_get_drvdata(&pdev->dev); + + dev_set_drvdata(&pdev->dev, NULL); + + if (info) { +#ifdef CONFIG_MTD_PARTITIONS + if (info->parts) + del_mtd_partitions(&info->mtd); + else +#endif + mtd_device_unregister(&info->mtd); + + msm_nand_release(&info->mtd); + dma_free_coherent(NULL, MSM_NAND_DMA_BUFFER_SIZE, + info->msm_nand.dma_buffer, + info->msm_nand.dma_addr); + kfree(info); + } + + return 0; +} + +#define DRIVER_NAME "msm_nand" + +static struct platform_driver msm_nand_driver = { + .probe = msm_nand_probe, + .remove = __devexit_p(msm_nand_remove), + .driver = { + .name = DRIVER_NAME, + } +}; + +MODULE_ALIAS(DRIVER_NAME); + +static int __init msm_nand_init(void) +{ + return platform_driver_register(&msm_nand_driver); +} + +static void __exit msm_nand_exit(void) +{ + platform_driver_unregister(&msm_nand_driver); +} + +module_init(msm_nand_init); +module_exit(msm_nand_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("msm_nand flash driver code"); diff --git a/drivers/mtd/devices/msm_nand.h b/drivers/mtd/devices/msm_nand.h new file mode 100644 index 00000000000..2729c6bae23 --- /dev/null +++ b/drivers/mtd/devices/msm_nand.h @@ -0,0 +1,195 @@ +/* drivers/mtd/devices/msm_nand.h + * + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __DRIVERS_MTD_DEVICES_MSM_NAND_H +#define __DRIVERS_MTD_DEVICES_MSM_NAND_H + +#include + +extern unsigned long msm_nand_phys; +extern unsigned long msm_nandc01_phys; +extern unsigned long msm_nandc10_phys; +extern unsigned long msm_nandc11_phys; +extern unsigned long ebi2_register_base; + +#define NC01(X) ((X) + msm_nandc01_phys - msm_nand_phys) +#define NC10(X) ((X) + msm_nandc10_phys - msm_nand_phys) +#define NC11(X) ((X) + msm_nandc11_phys - msm_nand_phys) + +#define MSM_NAND_REG(off) (msm_nand_phys + (off)) + +#define MSM_NAND_FLASH_CMD MSM_NAND_REG(0x0000) +#define MSM_NAND_ADDR0 MSM_NAND_REG(0x0004) +#define MSM_NAND_ADDR1 MSM_NAND_REG(0x0008) +#define MSM_NAND_FLASH_CHIP_SELECT MSM_NAND_REG(0x000C) +#define MSM_NAND_EXEC_CMD MSM_NAND_REG(0x0010) +#define MSM_NAND_FLASH_STATUS MSM_NAND_REG(0x0014) +#define MSM_NAND_BUFFER_STATUS MSM_NAND_REG(0x0018) +#define MSM_NAND_SFLASHC_STATUS MSM_NAND_REG(0x001C) +#define MSM_NAND_DEV0_CFG0 MSM_NAND_REG(0x0020) +#define MSM_NAND_DEV0_CFG1 MSM_NAND_REG(0x0024) +#define MSM_NAND_DEV0_ECC_CFG MSM_NAND_REG(0x0028) +#define MSM_NAND_DEV1_ECC_CFG MSM_NAND_REG(0x002C) +#define MSM_NAND_DEV1_CFG0 MSM_NAND_REG(0x0030) +#define MSM_NAND_DEV1_CFG1 MSM_NAND_REG(0x0034) +#define MSM_NAND_SFLASHC_CMD MSM_NAND_REG(0x0038) +#define MSM_NAND_SFLASHC_EXEC_CMD MSM_NAND_REG(0x003C) +#define MSM_NAND_READ_ID MSM_NAND_REG(0x0040) +#define MSM_NAND_READ_STATUS MSM_NAND_REG(0x0044) +#define MSM_NAND_CONFIG_DATA MSM_NAND_REG(0x0050) +#define MSM_NAND_CONFIG MSM_NAND_REG(0x0054) +#define MSM_NAND_CONFIG_MODE MSM_NAND_REG(0x0058) +#define MSM_NAND_CONFIG_STATUS MSM_NAND_REG(0x0060) +#define MSM_NAND_MACRO1_REG MSM_NAND_REG(0x0064) +#define MSM_NAND_XFR_STEP1 MSM_NAND_REG(0x0070) +#define MSM_NAND_XFR_STEP2 MSM_NAND_REG(0x0074) +#define MSM_NAND_XFR_STEP3 MSM_NAND_REG(0x0078) +#define MSM_NAND_XFR_STEP4 MSM_NAND_REG(0x007C) +#define MSM_NAND_XFR_STEP5 MSM_NAND_REG(0x0080) +#define MSM_NAND_XFR_STEP6 MSM_NAND_REG(0x0084) +#define MSM_NAND_XFR_STEP7 MSM_NAND_REG(0x0088) +#define MSM_NAND_GENP_REG0 MSM_NAND_REG(0x0090) +#define MSM_NAND_GENP_REG1 MSM_NAND_REG(0x0094) +#define MSM_NAND_GENP_REG2 MSM_NAND_REG(0x0098) +#define MSM_NAND_GENP_REG3 MSM_NAND_REG(0x009C) +#define MSM_NAND_DEV_CMD0 MSM_NAND_REG(0x00A0) +#define MSM_NAND_DEV_CMD1 MSM_NAND_REG(0x00A4) +#define MSM_NAND_DEV_CMD2 MSM_NAND_REG(0x00A8) +#define MSM_NAND_DEV_CMD_VLD MSM_NAND_REG(0x00AC) +#define MSM_NAND_EBI2_MISR_SIG_REG MSM_NAND_REG(0x00B0) +#define MSM_NAND_ADDR2 MSM_NAND_REG(0x00C0) +#define MSM_NAND_ADDR3 MSM_NAND_REG(0x00C4) +#define MSM_NAND_ADDR4 MSM_NAND_REG(0x00C8) +#define MSM_NAND_ADDR5 MSM_NAND_REG(0x00CC) +#define MSM_NAND_DEV_CMD3 MSM_NAND_REG(0x00D0) +#define MSM_NAND_DEV_CMD4 MSM_NAND_REG(0x00D4) +#define MSM_NAND_DEV_CMD5 MSM_NAND_REG(0x00D8) +#define MSM_NAND_DEV_CMD6 MSM_NAND_REG(0x00DC) +#define MSM_NAND_SFLASHC_BURST_CFG MSM_NAND_REG(0x00E0) +#define MSM_NAND_ADDR6 MSM_NAND_REG(0x00E4) +#define MSM_NAND_EBI2_ECC_BUF_CFG MSM_NAND_REG(0x00F0) +#define MSM_NAND_HW_INFO MSM_NAND_REG(0x00FC) +#define MSM_NAND_FLASH_BUFFER MSM_NAND_REG(0x0100) + +/* device commands */ + +#define MSM_NAND_CMD_SOFT_RESET 0x01 +#define MSM_NAND_CMD_PAGE_READ 0x32 +#define MSM_NAND_CMD_PAGE_READ_ECC 0x33 +#define MSM_NAND_CMD_PAGE_READ_ALL 0x34 +#define MSM_NAND_CMD_SEQ_PAGE_READ 0x15 +#define MSM_NAND_CMD_PRG_PAGE 0x36 +#define MSM_NAND_CMD_PRG_PAGE_ECC 0x37 +#define MSM_NAND_CMD_PRG_PAGE_ALL 0x39 +#define MSM_NAND_CMD_BLOCK_ERASE 0x3A +#define MSM_NAND_CMD_FETCH_ID 0x0B +#define MSM_NAND_CMD_STATUS 0x0C +#define MSM_NAND_CMD_RESET 0x0D + +/* Sflash Commands */ + +#define MSM_NAND_SFCMD_DATXS 0x0 +#define MSM_NAND_SFCMD_CMDXS 0x1 +#define MSM_NAND_SFCMD_BURST 0x0 +#define MSM_NAND_SFCMD_ASYNC 0x1 +#define MSM_NAND_SFCMD_ABORT 0x1 +#define MSM_NAND_SFCMD_REGRD 0x2 +#define MSM_NAND_SFCMD_REGWR 0x3 +#define MSM_NAND_SFCMD_INTLO 0x4 +#define MSM_NAND_SFCMD_INTHI 0x5 +#define MSM_NAND_SFCMD_DATRD 0x6 +#define MSM_NAND_SFCMD_DATWR 0x7 + +#define SFLASH_PREPCMD(numxfr, offval, delval, trnstp, mode, opcode) \ + ((numxfr<<20)|(offval<<12)|(delval<<6)|(trnstp<<5)|(mode<<4)|opcode) + +#define SFLASH_BCFG 0x20100327 + +/* Onenand addresses */ + +#define ONENAND_MANUFACTURER_ID 0xF000 +#define ONENAND_DEVICE_ID 0xF001 +#define ONENAND_VERSION_ID 0xF002 +#define ONENAND_DATA_BUFFER_SIZE 0xF003 +#define ONENAND_BOOT_BUFFER_SIZE 0xF004 +#define ONENAND_AMOUNT_OF_BUFFERS 0xF005 +#define ONENAND_TECHNOLOGY 0xF006 +#define ONENAND_START_ADDRESS_1 0xF100 +#define ONENAND_START_ADDRESS_2 0xF101 +#define ONENAND_START_ADDRESS_3 0xF102 +#define ONENAND_START_ADDRESS_4 0xF103 +#define ONENAND_START_ADDRESS_5 0xF104 +#define ONENAND_START_ADDRESS_6 0xF105 +#define ONENAND_START_ADDRESS_7 0xF106 +#define ONENAND_START_ADDRESS_8 0xF107 +#define ONENAND_START_BUFFER 0xF200 +#define ONENAND_COMMAND 0xF220 +#define ONENAND_SYSTEM_CONFIG_1 0xF221 +#define ONENAND_SYSTEM_CONFIG_2 0xF222 +#define ONENAND_CONTROLLER_STATUS 0xF240 +#define ONENAND_INTERRUPT_STATUS 0xF241 +#define ONENAND_START_BLOCK_ADDRESS 0xF24C +#define ONENAND_WRITE_PROT_STATUS 0xF24E +#define ONENAND_ECC_STATUS 0xFF00 +#define ONENAND_ECC_ERRPOS_MAIN0 0xFF01 +#define ONENAND_ECC_ERRPOS_SPARE0 0xFF02 +#define ONENAND_ECC_ERRPOS_MAIN1 0xFF03 +#define ONENAND_ECC_ERRPOS_SPARE1 0xFF04 +#define ONENAND_ECC_ERRPOS_MAIN2 0xFF05 +#define ONENAND_ECC_ERRPOS_SPARE2 0xFF06 +#define ONENAND_ECC_ERRPOS_MAIN3 0xFF07 +#define ONENAND_ECC_ERRPOS_SPARE3 0xFF08 + +/* Onenand commands */ +#define ONENAND_WP_US (1 << 2) +#define ONENAND_WP_LS (1 << 1) + +#define ONENAND_CMDLOAD 0x0000 +#define ONENAND_CMDLOADSPARE 0x0013 +#define ONENAND_CMDPROG 0x0080 +#define ONENAND_CMDPROGSPARE 0x001A +#define ONENAND_CMDERAS 0x0094 +#define ONENAND_CMD_UNLOCK 0x0023 +#define ONENAND_CMD_LOCK 0x002A + +#define ONENAND_SYSCFG1_ECCENA(mode) (0x40E0 | (mode ? 0 : 0x8002)) +#define ONENAND_SYSCFG1_ECCDIS(mode) (0x41E0 | (mode ? 0 : 0x8002)) + +#define ONENAND_CLRINTR 0x0000 +#define ONENAND_STARTADDR1_RES 0x07FF +#define ONENAND_STARTADDR3_RES 0x07FF + +#define DATARAM0_0 0x8 +#define DEVICE_FLASHCORE_0 (0 << 15) +#define DEVICE_FLASHCORE_1 (1 << 15) +#define DEVICE_BUFFERRAM_0 (0 << 15) +#define DEVICE_BUFFERRAM_1 (1 << 15) +#define ONENAND_DEVICE_IS_DDP (1 << 3) + +#define CLEAN_DATA_16 0xFFFF +#define CLEAN_DATA_32 0xFFFFFFFF + +#define EBI2_REG(off) (ebi2_register_base + (off)) +#define EBI2_CHIP_SELECT_CFG0 EBI2_REG(0x0000) +#define EBI2_CFG_REG EBI2_REG(0x0004) +#define EBI2_NAND_ADM_MUX EBI2_REG(0x005C) + +#define MSM_NAND_BUF_STAT_UNCRCTBL_ERR (1 << 8) +#define MSM_NAND_BUF_STAT_NUM_ERR_MASK (enable_bch_ecc ? 0x1F : 0x0F) + +extern struct flash_platform_data msm_nand_data; + +#endif diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c index ca385697446..25f66e44f5c 100644 --- a/drivers/mtd/mtd_blkdevs.c +++ b/drivers/mtd/mtd_blkdevs.c @@ -424,6 +424,13 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new) goto error3; new->rq->queuedata = new; + + /* + * Empirical measurements revealed that read ahead values larger than + * 4 slowed down boot time, so start out with this small value. + */ + new->rq->backing_dev_info.ra_pages = (4 * 1024) / PAGE_CACHE_SIZE; + blk_queue_logical_block_size(new->rq, tr->blksize); if (tr->discard) { diff --git a/drivers/mtd/tests/Makefile b/drivers/mtd/tests/Makefile index b44dcab940d..8089d9d4041 100644 --- a/drivers/mtd/tests/Makefile +++ b/drivers/mtd/tests/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_MTD_TESTS) += mtd_stresstest.o obj-$(CONFIG_MTD_TESTS) += mtd_subpagetest.o obj-$(CONFIG_MTD_TESTS) += mtd_torturetest.o obj-$(CONFIG_MTD_TESTS) += mtd_nandecctest.o +obj-$(CONFIG_MTD_TESTS) += mtd_erasepart.o diff --git a/drivers/mtd/tests/mtd_erasepart.c b/drivers/mtd/tests/mtd_erasepart.c new file mode 100644 index 00000000000..b679a7c45a1 --- /dev/null +++ b/drivers/mtd/tests/mtd_erasepart.c @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * Copyright (C) 2006-2008 Nokia Corporation + * + * 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; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Erase the given MTD partition. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PRINT_PREF KERN_INFO "mtd_erasepart: " + +static int dev; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static struct mtd_info *mtd; +static unsigned char *bbt; +static int ebcnt; + +static int erase_eraseblock(int ebnum) +{ + int err; + struct erase_info ei; + loff_t addr = ebnum * mtd->erasesize; + + memset(&ei, 0, sizeof(struct erase_info)); + ei.mtd = mtd; + ei.addr = addr; + ei.len = mtd->erasesize; + + err = mtd->erase(mtd, &ei); + if (err) { + printk(PRINT_PREF "error %d while erasing EB %d\n", err, ebnum); + return err; + } + + if (ei.state == MTD_ERASE_FAILED) { + printk(PRINT_PREF "some erase error occurred at EB %d\n", + ebnum); + return -EIO; + } + + return 0; +} + +static int erase_whole_device(void) +{ + int err; + unsigned int i; + + printk(PRINT_PREF "erasing whole device\n"); + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + err = erase_eraseblock(i); + if (err) + return err; + cond_resched(); + } + printk(PRINT_PREF "erased %u eraseblocks\n", i); + return 0; +} + +static int is_block_bad(int ebnum) +{ + int ret; + loff_t addr = ebnum * mtd->erasesize; + + ret = mtd->block_isbad(mtd, addr); + if (ret) + printk(PRINT_PREF "block %d is bad\n", ebnum); + return ret; +} + +static int scan_for_bad_eraseblocks(void) +{ + int i, bad = 0; + + bbt = kmalloc(ebcnt, GFP_KERNEL); + if (!bbt) { + printk(PRINT_PREF "error: cannot allocate memory\n"); + return -ENOMEM; + } + memset(bbt, 0 , ebcnt); + + printk(PRINT_PREF "scanning for bad eraseblocks\n"); + for (i = 0; i < ebcnt; ++i) { + bbt[i] = is_block_bad(i) ? 1 : 0; + if (bbt[i]) + bad += 1; + cond_resched(); + } + printk(PRINT_PREF "scanned %d eraseblocks, %d are bad\n", i, bad); + return 0; +} + +static int __init mtd_erasepart_init(void) +{ + int err = 0; + uint64_t tmp; + + printk(KERN_INFO "\n"); + printk(KERN_INFO "=================================================\n"); + printk(PRINT_PREF "MTD device: %d\n", dev); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + printk(PRINT_PREF "error: cannot get MTD device\n"); + return err; + } + + if (mtd->type != MTD_NANDFLASH) { + printk(PRINT_PREF "this test requires NAND flash\n"); + err = -ENODEV; + goto out2; + } + + tmp = mtd->size; + do_div(tmp, mtd->erasesize); + ebcnt = tmp; + + printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, " + "page size %u, count of eraseblocks %u", + (unsigned long long)mtd->size, mtd->erasesize, + mtd->writesize, ebcnt); + + err = scan_for_bad_eraseblocks(); + if (err) + goto out1; + + printk(PRINT_PREF "Erasing the whole mtd partition\n"); + + err = erase_whole_device(); +out1: + kfree(bbt); +out2: + put_mtd_device(mtd); + if (err) + printk(PRINT_PREF "error %d occurred\n", err); + printk(KERN_INFO "=================================================\n"); + return err; +} +module_init(mtd_erasepart_init); + +static void __exit mtd_erasepart_exit(void) +{ + return; +} +module_exit(mtd_erasepart_exit); + +MODULE_DESCRIPTION("Erase a given MTD partition"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 906ef8fa006..f6c44c65372 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -2561,6 +2561,15 @@ config PCH_GBE ML7223 is companion chip for Intel Atom E6xx series. ML7223 is completely compatible for Intel EG20T PCH. +config QFEC + tristate "QFEC ethernet driver" + select MII + depends on ARM + help + This driver supports Ethernet in the FSM9xxx. + To compile this driver as a module, choose M here: the + module will be called qfec. + endif # NETDEV_1000 # @@ -3431,6 +3440,38 @@ config NETCONSOLE If you want to log kernel messages over the network, enable this. See for details. +config MSM_RMNET + tristate "MSM RMNET Virtual Network Device" + depends on ARCH_MSM + default y + help + Virtual ethernet interface for MSM RMNET transport. + +config MSM_RMNET_SDIO + bool "RMNET SDIO Driver" + depends on MSM_SDIO_DMUX + default n + help + Implements RMNET over SDIO interface. + +config MSM_RMNET_BAM + bool "RMNET BAM Driver" + depends on MSM_BAM_DMUX + default n + help + Implements RMNET over BAM interface. + RMNET provides a virtual ethernet interface + for routing IP packets within the MSM using + BAM as a physical transport. + +config MSM_RMNET_DEBUG + bool "MSM RMNET debug interface" + depends on MSM_RMNET + default n + help + Debug stats on wakeup counts. + + config NETCONSOLE_DYNAMIC bool "Dynamic reconfiguration of logging targets" depends on NETCONSOLE && SYSFS && CONFIGFS_FS && \ diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 13ef4dfc411..43079b36994 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -269,6 +269,7 @@ obj-$(CONFIG_XTENSA_XT2000_SONIC) += xtsonic.o obj-$(CONFIG_DNET) += dnet.o obj-$(CONFIG_MACB) += macb.o obj-$(CONFIG_S6GMAC) += s6gmac.o +obj-$(CONFIG_QFEC) += qfec.o obj-$(CONFIG_ARM) += arm/ obj-$(CONFIG_DEV_APPLETALK) += appletalk/ @@ -298,6 +299,11 @@ obj-$(CONFIG_NETCONSOLE) += netconsole.o obj-$(CONFIG_FS_ENET) += fs_enet/ obj-$(CONFIG_NETXEN_NIC) += netxen/ + +obj-$(CONFIG_MSM_RMNET) += msm_rmnet.o +obj-$(CONFIG_MSM_RMNET_SDIO) += msm_rmnet_sdio.o +obj-$(CONFIG_MSM_RMNET_BAM) += msm_rmnet_bam.o + obj-$(CONFIG_NIU) += niu.o obj-$(CONFIG_VIRTIO_NET) += virtio_net.o obj-$(CONFIG_SFC) += sfc/ diff --git a/drivers/net/ks8851.c b/drivers/net/ks8851.c index bcd9ba68c9f..f79dce2ccd7 100644 --- a/drivers/net/ks8851.c +++ b/drivers/net/ks8851.c @@ -21,7 +21,7 @@ #include #include #include - +#include #include #include "ks8851.h" @@ -127,6 +127,8 @@ struct ks8851_net { struct spi_message spi_msg2; struct spi_transfer spi_xfer1; struct spi_transfer spi_xfer2[2]; + struct regulator *vdd_io; + struct regulator *vdd_phy; }; static int msg_enable; @@ -1592,6 +1594,15 @@ static int __devinit ks8851_probe(struct spi_device *spi) ks = netdev_priv(ndev); + ks->vdd_io = regulator_get(&spi->dev, "vdd_io"); + ks->vdd_phy = regulator_get(&spi->dev, "vdd_phy"); + + if (!IS_ERR(ks->vdd_io)) + regulator_enable(ks->vdd_io); + + if (!IS_ERR(ks->vdd_phy)) + regulator_enable(ks->vdd_phy); + ks->netdev = ndev; ks->spidev = spi; ks->tx_space = 6144; @@ -1686,6 +1697,16 @@ static int __devinit ks8851_probe(struct spi_device *spi) err_id: err_irq: free_netdev(ndev); + + if (!IS_ERR(ks->vdd_io)) { + regulator_disable(ks->vdd_phy); + regulator_put(ks->vdd_io); + } + + if (!IS_ERR(ks->vdd_phy)) { + regulator_disable(ks->vdd_phy); + regulator_put(ks->vdd_phy); + } return ret; } @@ -1696,6 +1717,16 @@ static int __devexit ks8851_remove(struct spi_device *spi) if (netif_msg_drv(priv)) dev_info(&spi->dev, "remove\n"); + if (!IS_ERR(priv->vdd_io)) { + regulator_disable(priv->vdd_phy); + regulator_put(priv->vdd_io); + } + + if (!IS_ERR(priv->vdd_phy)) { + regulator_disable(priv->vdd_phy); + regulator_put(priv->vdd_phy); + } + unregister_netdev(priv->netdev); free_irq(spi->irq, priv); free_netdev(priv->netdev); diff --git a/drivers/net/msm_rmnet.c b/drivers/net/msm_rmnet.c new file mode 100644 index 00000000000..6889425d022 --- /dev/null +++ b/drivers/net/msm_rmnet.c @@ -0,0 +1,849 @@ +/* linux/drivers/net/msm_rmnet.c + * + * Virtual Ethernet Interface for MSM7K Networking + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +#include +#include + +/* Debug message support */ +static int msm_rmnet_debug_mask; +module_param_named(debug_enable, msm_rmnet_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define DEBUG_MASK_LVL0 (1U << 0) +#define DEBUG_MASK_LVL1 (1U << 1) +#define DEBUG_MASK_LVL2 (1U << 2) + +#define DBG(m, x...) do { \ + if (msm_rmnet_debug_mask & m) \ + pr_info(x); \ +} while (0) +#define DBG0(x...) DBG(DEBUG_MASK_LVL0, x) +#define DBG1(x...) DBG(DEBUG_MASK_LVL1, x) +#define DBG2(x...) DBG(DEBUG_MASK_LVL2, x) + +/* Configure device instances */ +#define RMNET_DEVICE_COUNT (8) +static const char *ch_name[RMNET_DEVICE_COUNT] = { + "DATA5", + "DATA6", + "DATA7", + "DATA8", + "DATA9", + "DATA12", + "DATA13", + "DATA14", +}; + +/* XXX should come from smd headers */ +#define SMD_PORT_ETHER0 11 + +/* allow larger frames */ +#define RMNET_DATA_LEN 2000 + +#define HEADROOM_FOR_QOS 8 + +static struct completion *port_complete[RMNET_DEVICE_COUNT]; + +struct rmnet_private +{ + smd_channel_t *ch; + struct net_device_stats stats; + const char *chname; + struct wake_lock wake_lock; +#ifdef CONFIG_MSM_RMNET_DEBUG + ktime_t last_packet; + unsigned long wakeups_xmit; + unsigned long wakeups_rcv; + unsigned long timeout_us; +#endif + struct sk_buff *skb; + spinlock_t lock; + struct tasklet_struct tsklt; + u32 operation_mode; /* IOCTL specified mode (protocol, QoS header) */ + struct platform_driver pdrv; + struct completion complete; + void *pil; + struct mutex pil_lock; +}; + +static uint msm_rmnet_modem_wait; +module_param_named(modem_wait, msm_rmnet_modem_wait, + uint, S_IRUGO | S_IWUSR | S_IWGRP); + +/* Forward declaration */ +static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); + +static int count_this_packet(void *_hdr, int len) +{ + struct ethhdr *hdr = _hdr; + + if (len >= ETH_HLEN && hdr->h_proto == htons(ETH_P_ARP)) + return 0; + + return 1; +} + +#ifdef CONFIG_MSM_RMNET_DEBUG +static unsigned long timeout_us; + +#ifdef CONFIG_HAS_EARLYSUSPEND +/* + * If early suspend is enabled then we specify two timeout values, + * screen on (default), and screen is off. + */ +static unsigned long timeout_suspend_us; +static struct device *rmnet0; + +/* Set timeout in us when the screen is off. */ +static ssize_t timeout_suspend_store(struct device *d, struct device_attribute *attr, const char *buf, size_t n) +{ + timeout_suspend_us = simple_strtoul(buf, NULL, 10); + return n; +} + +static ssize_t timeout_suspend_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", (unsigned long) timeout_suspend_us); +} + +static DEVICE_ATTR(timeout_suspend, 0664, timeout_suspend_show, timeout_suspend_store); + +static void rmnet_early_suspend(struct early_suspend *handler) { + if (rmnet0) { + struct rmnet_private *p = netdev_priv(to_net_dev(rmnet0)); + p->timeout_us = timeout_suspend_us; + } +} + +static void rmnet_late_resume(struct early_suspend *handler) { + if (rmnet0) { + struct rmnet_private *p = netdev_priv(to_net_dev(rmnet0)); + p->timeout_us = timeout_us; + } +} + +static struct early_suspend rmnet_power_suspend = { + .suspend = rmnet_early_suspend, + .resume = rmnet_late_resume, +}; + +static int __init rmnet_late_init(void) +{ + register_early_suspend(&rmnet_power_suspend); + return 0; +} + +late_initcall(rmnet_late_init); +#endif + +/* Returns 1 if packet caused rmnet to wakeup, 0 otherwise. */ +static int rmnet_cause_wakeup(struct rmnet_private *p) { + int ret = 0; + ktime_t now; + if (p->timeout_us == 0) /* Check if disabled */ + return 0; + + /* Use real (wall) time. */ + now = ktime_get_real(); + + if (ktime_us_delta(now, p->last_packet) > p->timeout_us) { + ret = 1; + } + p->last_packet = now; + return ret; +} + +static ssize_t wakeups_xmit_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", p->wakeups_xmit); +} + +DEVICE_ATTR(wakeups_xmit, 0444, wakeups_xmit_show, NULL); + +static ssize_t wakeups_rcv_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", p->wakeups_rcv); +} + +DEVICE_ATTR(wakeups_rcv, 0444, wakeups_rcv_show, NULL); + +/* Set timeout in us. */ +static ssize_t timeout_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t n) +{ +#ifndef CONFIG_HAS_EARLYSUSPEND + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + p->timeout_us = timeout_us = simple_strtoul(buf, NULL, 10); +#else +/* If using early suspend/resume hooks do not write the value on store. */ + timeout_us = simple_strtoul(buf, NULL, 10); +#endif + return n; +} + +static ssize_t timeout_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", timeout_us); +} + +DEVICE_ATTR(timeout, 0664, timeout_show, timeout_store); +#endif + +static __be16 rmnet_ip_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + __be16 protocol = 0; + + skb->dev = dev; + + /* Determine L3 protocol */ + switch (skb->data[0] & 0xf0) { + case 0x40: + protocol = htons(ETH_P_IP); + break; + case 0x60: + protocol = htons(ETH_P_IPV6); + break; + default: + pr_err("[%s] rmnet_recv() L3 protocol decode error: 0x%02x", + dev->name, skb->data[0] & 0xf0); + /* skb will be dropped in uppder layer for unknown protocol */ + } + return protocol; +} + +/* Called in soft-irq context */ +static void smd_net_data_handler(unsigned long arg) +{ + struct net_device *dev = (struct net_device *) arg; + struct rmnet_private *p = netdev_priv(dev); + struct sk_buff *skb; + void *ptr = 0; + int sz; + u32 opmode = p->operation_mode; + unsigned long flags; + + for (;;) { + sz = smd_cur_packet_size(p->ch); + if (sz == 0) break; + if (smd_read_avail(p->ch) < sz) break; + + if (RMNET_IS_MODE_IP(opmode) ? (sz > dev->mtu) : + (sz > (dev->mtu + ETH_HLEN))) { + pr_err("[%s] rmnet_recv() discarding packet len %d (%d mtu)\n", + dev->name, sz, RMNET_IS_MODE_IP(opmode) ? + dev->mtu : (dev->mtu + ETH_HLEN)); + ptr = 0; + } else { + skb = dev_alloc_skb(sz + NET_IP_ALIGN); + if (skb == NULL) { + pr_err("[%s] rmnet_recv() cannot allocate skb\n", + dev->name); + } else { + skb->dev = dev; + skb_reserve(skb, NET_IP_ALIGN); + ptr = skb_put(skb, sz); + wake_lock_timeout(&p->wake_lock, HZ / 2); + if (smd_read(p->ch, ptr, sz) != sz) { + pr_err("[%s] rmnet_recv() smd lied about avail?!", + dev->name); + ptr = 0; + dev_kfree_skb_irq(skb); + } else { + /* Handle Rx frame format */ + spin_lock_irqsave(&p->lock, flags); + opmode = p->operation_mode; + spin_unlock_irqrestore(&p->lock, flags); + + if (RMNET_IS_MODE_IP(opmode)) { + /* Driver in IP mode */ + skb->protocol = + rmnet_ip_type_trans(skb, dev); + } else { + /* Driver in Ethernet mode */ + skb->protocol = + eth_type_trans(skb, dev); + } + if (RMNET_IS_MODE_IP(opmode) || + count_this_packet(ptr, skb->len)) { +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_rcv += + rmnet_cause_wakeup(p); +#endif + p->stats.rx_packets++; + p->stats.rx_bytes += skb->len; + } + DBG1("[%s] Rx packet #%lu len=%d\n", + dev->name, p->stats.rx_packets, + skb->len); + + /* Deliver to network stack */ + netif_rx(skb); + } + continue; + } + } + if (smd_read(p->ch, ptr, sz) != sz) + pr_err("[%s] rmnet_recv() smd lied about avail?!", + dev->name); + } +} + +static DECLARE_TASKLET(smd_net_data_tasklet, smd_net_data_handler, 0); + +static int _rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + smd_channel_t *ch = p->ch; + int smd_ret; + struct QMI_QOS_HDR_S *qmih; + u32 opmode; + unsigned long flags; + + /* For QoS mode, prepend QMI header and assign flow ID from skb->mark */ + spin_lock_irqsave(&p->lock, flags); + opmode = p->operation_mode; + spin_unlock_irqrestore(&p->lock, flags); + + if (RMNET_IS_MODE_QOS(opmode)) { + qmih = (struct QMI_QOS_HDR_S *) + skb_push(skb, sizeof(struct QMI_QOS_HDR_S)); + qmih->version = 1; + qmih->flags = 0; + qmih->flow_id = skb->mark; + } + + dev->trans_start = jiffies; + smd_ret = smd_write(ch, skb->data, skb->len); + if (smd_ret != skb->len) { + pr_err("[%s] %s: smd_write returned error %d", + dev->name, __func__, smd_ret); + p->stats.tx_errors++; + goto xmit_out; + } + + if (RMNET_IS_MODE_IP(opmode) || + count_this_packet(skb->data, skb->len)) { + p->stats.tx_packets++; + p->stats.tx_bytes += skb->len; +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_xmit += rmnet_cause_wakeup(p); +#endif + } + DBG1("[%s] Tx packet #%lu len=%d mark=0x%x\n", + dev->name, p->stats.tx_packets, skb->len, skb->mark); + +xmit_out: + /* data xmited, safe to release skb */ + dev_kfree_skb_irq(skb); + return 0; +} + +static void _rmnet_resume_flow(unsigned long param) +{ + struct net_device *dev = (struct net_device *)param; + struct rmnet_private *p = netdev_priv(dev); + struct sk_buff *skb = NULL; + unsigned long flags; + + /* xmit and enable the flow only once even if + multiple tasklets were scheduled by smd_net_notify */ + spin_lock_irqsave(&p->lock, flags); + if (p->skb && (smd_write_avail(p->ch) >= p->skb->len)) { + skb = p->skb; + p->skb = NULL; + spin_unlock_irqrestore(&p->lock, flags); + _rmnet_xmit(skb, dev); + netif_wake_queue(dev); + } else + spin_unlock_irqrestore(&p->lock, flags); +} + +static void msm_rmnet_unload_modem(void *pil) +{ + if (pil) + pil_put(pil); +} + +static void *msm_rmnet_load_modem(struct net_device *dev) +{ + void *pil; + int rc; + struct rmnet_private *p = netdev_priv(dev); + + pil = pil_get("modem"); + if (IS_ERR(pil)) + pr_err("[%s] %s: modem load failed\n", + dev->name, __func__); + else if (msm_rmnet_modem_wait) { + rc = wait_for_completion_interruptible_timeout( + &p->complete, + msecs_to_jiffies(msm_rmnet_modem_wait * 1000)); + if (!rc) + rc = -ETIMEDOUT; + if (rc < 0) { + pr_err("[%s] %s: wait for rmnet port failed %d\n", + dev->name, __func__, rc); + msm_rmnet_unload_modem(pil); + pil = ERR_PTR(rc); + } + } + + return pil; +} + +static void smd_net_notify(void *_dev, unsigned event) +{ + struct rmnet_private *p = netdev_priv((struct net_device *)_dev); + + switch (event) { + case SMD_EVENT_DATA: + spin_lock(&p->lock); + if (p->skb && (smd_write_avail(p->ch) >= p->skb->len)) { + smd_disable_read_intr(p->ch); + tasklet_hi_schedule(&p->tsklt); + } + + spin_unlock(&p->lock); + + if (smd_read_avail(p->ch) && + (smd_read_avail(p->ch) >= smd_cur_packet_size(p->ch))) { + smd_net_data_tasklet.data = (unsigned long) _dev; + tasklet_schedule(&smd_net_data_tasklet); + } + break; + + case SMD_EVENT_OPEN: + DBG0("%s: opening SMD port\n", __func__); + netif_carrier_on(_dev); + if (netif_queue_stopped(_dev)) { + DBG0("%s: re-starting if queue\n", __func__); + netif_wake_queue(_dev); + } + break; + + case SMD_EVENT_CLOSE: + DBG0("%s: closing SMD port\n", __func__); + netif_carrier_off(_dev); + break; + } +} + +static int __rmnet_open(struct net_device *dev) +{ + int r; + void *pil; + struct rmnet_private *p = netdev_priv(dev); + + mutex_lock(&p->pil_lock); + if (!p->pil) { + pil = msm_rmnet_load_modem(dev); + if (IS_ERR(pil)) { + mutex_unlock(&p->pil_lock); + return PTR_ERR(pil); + } + p->pil = pil; + } + mutex_unlock(&p->pil_lock); + + if (!p->ch) { + r = smd_open(p->chname, &p->ch, dev, smd_net_notify); + + if (r < 0) + return -ENODEV; + } + + smd_disable_read_intr(p->ch); + return 0; +} + +static int __rmnet_close(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + int rc; + unsigned long flags; + + if (p->ch) { + rc = smd_close(p->ch); + spin_lock_irqsave(&p->lock, flags); + p->ch = 0; + spin_unlock_irqrestore(&p->lock, flags); + return rc; + } else + return -EBADF; +} + +static int rmnet_open(struct net_device *dev) +{ + int rc = 0; + + DBG0("[%s] rmnet_open()\n", dev->name); + + rc = __rmnet_open(dev); + if (rc == 0) + netif_start_queue(dev); + + return rc; +} + +static int rmnet_stop(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + + DBG0("[%s] rmnet_stop()\n", dev->name); + + netif_stop_queue(dev); + tasklet_kill(&p->tsklt); + + /* TODO: unload modem safely, + currently, this causes unnecessary unloads */ + /* + mutex_lock(&p->pil_lock); + msm_rmnet_unload_modem(p->pil); + p->pil = NULL; + mutex_unlock(&p->pil_lock); + */ + + return 0; +} + +static int rmnet_change_mtu(struct net_device *dev, int new_mtu) +{ + if (0 > new_mtu || RMNET_DATA_LEN < new_mtu) + return -EINVAL; + + DBG0("[%s] MTU change: old=%d new=%d\n", + dev->name, dev->mtu, new_mtu); + dev->mtu = new_mtu; + + return 0; +} + +static int rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + smd_channel_t *ch = p->ch; + unsigned long flags; + + if (netif_queue_stopped(dev)) { + pr_err("[%s] fatal: rmnet_xmit called when netif_queue is stopped", + dev->name); + return 0; + } + + spin_lock_irqsave(&p->lock, flags); + smd_enable_read_intr(ch); + if (smd_write_avail(ch) < skb->len) { + netif_stop_queue(dev); + p->skb = skb; + spin_unlock_irqrestore(&p->lock, flags); + return 0; + } + smd_disable_read_intr(ch); + spin_unlock_irqrestore(&p->lock, flags); + + _rmnet_xmit(skb, dev); + + return 0; +} + +static struct net_device_stats *rmnet_get_stats(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + return &p->stats; +} + +static void rmnet_set_multicast_list(struct net_device *dev) +{ +} + +static void rmnet_tx_timeout(struct net_device *dev) +{ + pr_warning("[%s] rmnet_tx_timeout()\n", dev->name); +} + + +static const struct net_device_ops rmnet_ops_ether = { + .ndo_open = rmnet_open, + .ndo_stop = rmnet_stop, + .ndo_start_xmit = rmnet_xmit, + .ndo_get_stats = rmnet_get_stats, + .ndo_set_multicast_list = rmnet_set_multicast_list, + .ndo_tx_timeout = rmnet_tx_timeout, + .ndo_do_ioctl = rmnet_ioctl, + .ndo_change_mtu = rmnet_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static const struct net_device_ops rmnet_ops_ip = { + .ndo_open = rmnet_open, + .ndo_stop = rmnet_stop, + .ndo_start_xmit = rmnet_xmit, + .ndo_get_stats = rmnet_get_stats, + .ndo_set_multicast_list = rmnet_set_multicast_list, + .ndo_tx_timeout = rmnet_tx_timeout, + .ndo_do_ioctl = rmnet_ioctl, + .ndo_change_mtu = rmnet_change_mtu, + .ndo_set_mac_address = 0, + .ndo_validate_addr = 0, +}; + +static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct rmnet_private *p = netdev_priv(dev); + u32 old_opmode = p->operation_mode; + unsigned long flags; + int prev_mtu = dev->mtu; + int rc = 0; + + /* Process IOCTL command */ + switch (cmd) { + case RMNET_IOCTL_SET_LLP_ETHERNET: /* Set Ethernet protocol */ + /* Perform Ethernet config only if in IP mode currently*/ + if (p->operation_mode & RMNET_MODE_LLP_IP) { + ether_setup(dev); + random_ether_addr(dev->dev_addr); + dev->mtu = prev_mtu; + + dev->netdev_ops = &rmnet_ops_ether; + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_LLP_IP; + p->operation_mode |= RMNET_MODE_LLP_ETH; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): " + "set Ethernet protocol mode\n", + dev->name); + } + break; + + case RMNET_IOCTL_SET_LLP_IP: /* Set RAWIP protocol */ + /* Perform IP config only if in Ethernet mode currently*/ + if (p->operation_mode & RMNET_MODE_LLP_ETH) { + + /* Undo config done in ether_setup() */ + dev->header_ops = 0; /* No header */ + dev->type = ARPHRD_RAWIP; + dev->hard_header_len = 0; + dev->mtu = prev_mtu; + dev->addr_len = 0; + dev->flags &= ~(IFF_BROADCAST| + IFF_MULTICAST); + + dev->netdev_ops = &rmnet_ops_ip; + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_LLP_ETH; + p->operation_mode |= RMNET_MODE_LLP_IP; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): set IP protocol mode\n", + dev->name); + } + break; + + case RMNET_IOCTL_GET_LLP: /* Get link protocol state */ + ifr->ifr_ifru.ifru_data = + (void *)(p->operation_mode & + (RMNET_MODE_LLP_ETH|RMNET_MODE_LLP_IP)); + break; + + case RMNET_IOCTL_SET_QOS_ENABLE: /* Set QoS header enabled */ + spin_lock_irqsave(&p->lock, flags); + p->operation_mode |= RMNET_MODE_QOS; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): set QMI QOS header enable\n", + dev->name); + break; + + case RMNET_IOCTL_SET_QOS_DISABLE: /* Set QoS header disabled */ + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_QOS; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): set QMI QOS header disable\n", + dev->name); + break; + + case RMNET_IOCTL_GET_QOS: /* Get QoS header state */ + ifr->ifr_ifru.ifru_data = + (void *)(p->operation_mode & RMNET_MODE_QOS); + break; + + case RMNET_IOCTL_GET_OPMODE: /* Get operation mode */ + ifr->ifr_ifru.ifru_data = (void *)p->operation_mode; + break; + + case RMNET_IOCTL_OPEN: /* Open transport port */ + rc = __rmnet_open(dev); + DBG0("[%s] rmnet_ioctl(): open transport port\n", + dev->name); + break; + + case RMNET_IOCTL_CLOSE: /* Close transport port */ + rc = __rmnet_close(dev); + DBG0("[%s] rmnet_ioctl(): close transport port\n", + dev->name); + break; + + default: + pr_err("[%s] error: rmnet_ioct called for unsupported cmd[%d]", + dev->name, cmd); + return -EINVAL; + } + + DBG2("[%s] %s: cmd=0x%x opmode old=0x%08x new=0x%08x\n", + dev->name, __func__, cmd, old_opmode, p->operation_mode); + return rc; +} + + +static void __init rmnet_setup(struct net_device *dev) +{ + /* Using Ethernet mode by default */ + dev->netdev_ops = &rmnet_ops_ether; + ether_setup(dev); + + /* set this after calling ether_setup */ + dev->mtu = RMNET_DATA_LEN; + dev->needed_headroom = HEADROOM_FOR_QOS; + + random_ether_addr(dev->dev_addr); + + dev->watchdog_timeo = 1000; /* 10 seconds? */ +} + +static int msm_rmnet_smd_probe(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < RMNET_DEVICE_COUNT; i++) + if (!strcmp(pdev->name, ch_name[i])) { + complete_all(port_complete[i]); + break; + } + + return 0; +} + +static int __init rmnet_init(void) +{ + int ret; + struct device *d; + struct net_device *dev; + struct rmnet_private *p; + unsigned n; + + pr_info("%s: SMD devices[%d]\n", __func__, RMNET_DEVICE_COUNT); + +#ifdef CONFIG_MSM_RMNET_DEBUG + timeout_us = 0; +#ifdef CONFIG_HAS_EARLYSUSPEND + timeout_suspend_us = 0; +#endif +#endif + + for (n = 0; n < RMNET_DEVICE_COUNT; n++) { + dev = alloc_netdev(sizeof(struct rmnet_private), + "rmnet%d", rmnet_setup); + + if (!dev) + return -ENOMEM; + + d = &(dev->dev); + p = netdev_priv(dev); + p->chname = ch_name[n]; + /* Initial config uses Ethernet */ + p->operation_mode = RMNET_MODE_LLP_ETH; + p->skb = NULL; + spin_lock_init(&p->lock); + tasklet_init(&p->tsklt, _rmnet_resume_flow, + (unsigned long)dev); + wake_lock_init(&p->wake_lock, WAKE_LOCK_SUSPEND, ch_name[n]); +#ifdef CONFIG_MSM_RMNET_DEBUG + p->timeout_us = timeout_us; + p->wakeups_xmit = p->wakeups_rcv = 0; +#endif + + init_completion(&p->complete); + port_complete[n] = &p->complete; + mutex_init(&p->pil_lock); + p->pdrv.probe = msm_rmnet_smd_probe; + p->pdrv.driver.name = ch_name[n]; + p->pdrv.driver.owner = THIS_MODULE; + ret = platform_driver_register(&p->pdrv); + if (ret) { + free_netdev(dev); + return ret; + } + + ret = register_netdev(dev); + if (ret) { + platform_driver_unregister(&p->pdrv); + free_netdev(dev); + return ret; + } + + +#ifdef CONFIG_MSM_RMNET_DEBUG + if (device_create_file(d, &dev_attr_timeout)) + continue; + if (device_create_file(d, &dev_attr_wakeups_xmit)) + continue; + if (device_create_file(d, &dev_attr_wakeups_rcv)) + continue; +#ifdef CONFIG_HAS_EARLYSUSPEND + if (device_create_file(d, &dev_attr_timeout_suspend)) + continue; + + /* Only care about rmnet0 for suspend/resume tiemout hooks. */ + if (n == 0) + rmnet0 = d; +#endif +#endif + } + return 0; +} + +module_init(rmnet_init); diff --git a/drivers/net/msm_rmnet_bam.c b/drivers/net/msm_rmnet_bam.c new file mode 100644 index 00000000000..a8bdeb3fadd --- /dev/null +++ b/drivers/net/msm_rmnet_bam.c @@ -0,0 +1,653 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * RMNET BAM Module. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +#include + +/* Debug message support */ +static int msm_rmnet_bam_debug_mask; +module_param_named(debug_enable, msm_rmnet_bam_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define DEBUG_MASK_LVL0 (1U << 0) +#define DEBUG_MASK_LVL1 (1U << 1) +#define DEBUG_MASK_LVL2 (1U << 2) + +#define DBG(m, x...) do { \ + if (msm_rmnet_bam_debug_mask & m) \ + pr_info(x); \ +} while (0) +#define DBG0(x...) DBG(DEBUG_MASK_LVL0, x) +#define DBG1(x...) DBG(DEBUG_MASK_LVL1, x) +#define DBG2(x...) DBG(DEBUG_MASK_LVL2, x) + +/* Configure device instances */ +#define RMNET_DEVICE_COUNT (8) + +/* allow larger frames */ +#define RMNET_DATA_LEN 2000 + +#define DEVICE_ID_INVALID -1 + +#define DEVICE_INACTIVE 0 +#define DEVICE_ACTIVE 1 + +#define HEADROOM_FOR_BAM 8 /* for mux header */ +#define HEADROOM_FOR_QOS 8 +#define TAILROOM 8 /* for padding by mux layer */ + +struct rmnet_private { + struct net_device_stats stats; + uint32_t ch_id; +#ifdef CONFIG_MSM_RMNET_DEBUG + ktime_t last_packet; + unsigned long wakeups_xmit; + unsigned long wakeups_rcv; + unsigned long timeout_us; +#endif + struct sk_buff *skb; + spinlock_t lock; + struct tasklet_struct tsklt; + u32 operation_mode; /* IOCTL specified mode (protocol, QoS header) */ + uint8_t device_up; +}; + +#ifdef CONFIG_MSM_RMNET_DEBUG +static unsigned long timeout_us; + +#ifdef CONFIG_HAS_EARLYSUSPEND +/* + * If early suspend is enabled then we specify two timeout values, + * screen on (default), and screen is off. + */ +static unsigned long timeout_suspend_us; +static struct device *rmnet0; + +/* Set timeout in us when the screen is off. */ +static ssize_t timeout_suspend_store(struct device *d, + struct device_attribute *attr, + const char *buf, size_t n) +{ + timeout_suspend_us = strict_strtoul(buf, NULL, 10); + return n; +} + +static ssize_t timeout_suspend_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", (unsigned long) timeout_suspend_us); +} + +static DEVICE_ATTR(timeout_suspend, 0664, timeout_suspend_show, + timeout_suspend_store); + +static void rmnet_early_suspend(struct early_suspend *handler) +{ + if (rmnet0) { + struct rmnet_private *p = netdev_priv(to_net_dev(rmnet0)); + p->timeout_us = timeout_suspend_us; + } +} + +static void rmnet_late_resume(struct early_suspend *handler) +{ + if (rmnet0) { + struct rmnet_private *p = netdev_priv(to_net_dev(rmnet0)); + p->timeout_us = timeout_us; + } +} + +static struct early_suspend rmnet_power_suspend = { + .suspend = rmnet_early_suspend, + .resume = rmnet_late_resume, +}; + +static int __init rmnet_late_init(void) +{ + register_early_suspend(&rmnet_power_suspend); + return 0; +} + +late_initcall(rmnet_late_init); +#endif + +/* Returns 1 if packet caused rmnet to wakeup, 0 otherwise. */ +static int rmnet_cause_wakeup(struct rmnet_private *p) +{ + int ret = 0; + ktime_t now; + if (p->timeout_us == 0) /* Check if disabled */ + return 0; + + /* Use real (wall) time. */ + now = ktime_get_real(); + + if (ktime_us_delta(now, p->last_packet) > p->timeout_us) + ret = 1; + + p->last_packet = now; + return ret; +} + +static ssize_t wakeups_xmit_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", p->wakeups_xmit); +} + +DEVICE_ATTR(wakeups_xmit, 0444, wakeups_xmit_show, NULL); + +static ssize_t wakeups_rcv_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", p->wakeups_rcv); +} + +DEVICE_ATTR(wakeups_rcv, 0444, wakeups_rcv_show, NULL); + +/* Set timeout in us. */ +static ssize_t timeout_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t n) +{ +#ifndef CONFIG_HAS_EARLYSUSPEND + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + p->timeout_us = timeout_us = strict_strtoul(buf, NULL, 10); +#else +/* If using early suspend/resume hooks do not write the value on store. */ + timeout_us = strict_strtoul(buf, NULL, 10); +#endif + return n; +} + +static ssize_t timeout_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", timeout_us); +} + +DEVICE_ATTR(timeout, 0664, timeout_show, timeout_store); +#endif + + +/* Forward declaration */ +static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); + +static __be16 rmnet_ip_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + __be16 protocol = 0; + + skb->dev = dev; + + /* Determine L3 protocol */ + switch (skb->data[0] & 0xf0) { + case 0x40: + protocol = htons(ETH_P_IP); + break; + case 0x60: + protocol = htons(ETH_P_IPV6); + break; + default: + pr_err("[%s] rmnet_recv() L3 protocol decode error: 0x%02x", + dev->name, skb->data[0] & 0xf0); + /* skb will be dropped in upper layer for unknown protocol */ + } + return protocol; +} + +static int count_this_packet(void *_hdr, int len) +{ + struct ethhdr *hdr = _hdr; + + if (len >= ETH_HLEN && hdr->h_proto == htons(ETH_P_ARP)) + return 0; + + return 1; +} + +/* Rx Callback, Called in Work Queue context */ +static void bam_recv_notify(void *dev, struct sk_buff *skb) +{ + struct rmnet_private *p = netdev_priv(dev); + unsigned long flags; + u32 opmode; + + if (skb) { + skb->dev = dev; + /* Handle Rx frame format */ + spin_lock_irqsave(&p->lock, flags); + opmode = p->operation_mode; + spin_unlock_irqrestore(&p->lock, flags); + + if (RMNET_IS_MODE_IP(opmode)) { + /* Driver in IP mode */ + skb->protocol = rmnet_ip_type_trans(skb, dev); + } else { + /* Driver in Ethernet mode */ + skb->protocol = eth_type_trans(skb, dev); + } + if (RMNET_IS_MODE_IP(opmode) || + count_this_packet(skb->data, skb->len)) { +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_rcv += rmnet_cause_wakeup(p); +#endif + p->stats.rx_packets++; + p->stats.rx_bytes += skb->len; + } + DBG1("[%s] Rx packet #%lu len=%d\n", + ((struct net_device *)dev)->name, + p->stats.rx_packets, skb->len); + + /* Deliver to network stack */ + netif_rx(skb); + } else + pr_err("[%s] %s: No skb received", + ((struct net_device *)dev)->name, __func__); +} + +static int _rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + int bam_ret; + struct QMI_QOS_HDR_S *qmih; + u32 opmode; + unsigned long flags; + + /* For QoS mode, prepend QMI header and assign flow ID from skb->mark */ + spin_lock_irqsave(&p->lock, flags); + opmode = p->operation_mode; + spin_unlock_irqrestore(&p->lock, flags); + + if (RMNET_IS_MODE_QOS(opmode)) { + qmih = (struct QMI_QOS_HDR_S *) + skb_push(skb, sizeof(struct QMI_QOS_HDR_S)); + qmih->version = 1; + qmih->flags = 0; + qmih->flow_id = skb->mark; + } + + dev->trans_start = jiffies; + bam_ret = msm_bam_dmux_write(p->ch_id, skb); + + if (bam_ret != 0) { + pr_err("[%s] %s: write returned error %d", + dev->name, __func__, bam_ret); + goto xmit_out; + } + + if (count_this_packet(skb->data, skb->len)) { + p->stats.tx_packets++; + p->stats.tx_bytes += skb->len; +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_xmit += rmnet_cause_wakeup(p); +#endif + } + DBG1("[%s] Tx packet #%lu len=%d mark=0x%x\n", + dev->name, p->stats.tx_packets, skb->len, skb->mark); + + return 0; +xmit_out: + /* data xmited, safe to release skb */ + dev_kfree_skb_any(skb); + return 0; +} + +static void bam_write_done(void *dev, struct sk_buff *skb) +{ + DBG1("%s: write complete\n", __func__); + dev_kfree_skb_any(skb); + netif_wake_queue(dev); +} + +static int __rmnet_open(struct net_device *dev) +{ + int r; + struct rmnet_private *p = netdev_priv(dev); + + DBG0("[%s] __rmnet_open()\n", dev->name); + + if (!p->device_up) { + r = msm_bam_dmux_open(p->ch_id, dev, + bam_recv_notify, bam_write_done); + + if (r < 0) + return -ENODEV; + } + + p->device_up = DEVICE_ACTIVE; + return 0; +} + +static int rmnet_open(struct net_device *dev) +{ + int rc = 0; + + DBG0("[%s] rmnet_open()\n", dev->name); + + rc = __rmnet_open(dev); + + if (rc == 0) + netif_start_queue(dev); + + return rc; +} + + +static int __rmnet_close(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + int rc = 0; + + if (p->device_up) { + /* do not close rmnet port once up, this causes + remote side to hang if tried to open again */ + p->device_up = DEVICE_INACTIVE; + return rc; + } else + return -EBADF; +} + + +static int rmnet_stop(struct net_device *dev) +{ + DBG0("[%s] rmnet_stop()\n", dev->name); + + __rmnet_close(dev); + netif_stop_queue(dev); + + return 0; +} + +static int rmnet_change_mtu(struct net_device *dev, int new_mtu) +{ + if (0 > new_mtu || RMNET_DATA_LEN < new_mtu) + return -EINVAL; + + DBG0("[%s] MTU change: old=%d new=%d\n", + dev->name, dev->mtu, new_mtu); + dev->mtu = new_mtu; + + return 0; +} + +static int rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + if (netif_queue_stopped(dev)) { + pr_err("[%s]fatal: rmnet_xmit called when " + "netif_queue is stopped", dev->name); + return 0; + } + + netif_stop_queue(dev); + _rmnet_xmit(skb, dev); + + return 0; +} + +static struct net_device_stats *rmnet_get_stats(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + return &p->stats; +} + +static void rmnet_set_multicast_list(struct net_device *dev) +{ +} + +static void rmnet_tx_timeout(struct net_device *dev) +{ + pr_warning("[%s] rmnet_tx_timeout()\n", dev->name); +} + +static const struct net_device_ops rmnet_ops_ether = { + .ndo_open = rmnet_open, + .ndo_stop = rmnet_stop, + .ndo_start_xmit = rmnet_xmit, + .ndo_get_stats = rmnet_get_stats, + .ndo_set_multicast_list = rmnet_set_multicast_list, + .ndo_tx_timeout = rmnet_tx_timeout, + .ndo_do_ioctl = rmnet_ioctl, + .ndo_change_mtu = rmnet_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static const struct net_device_ops rmnet_ops_ip = { + .ndo_open = rmnet_open, + .ndo_stop = rmnet_stop, + .ndo_start_xmit = rmnet_xmit, + .ndo_get_stats = rmnet_get_stats, + .ndo_set_multicast_list = rmnet_set_multicast_list, + .ndo_tx_timeout = rmnet_tx_timeout, + .ndo_do_ioctl = rmnet_ioctl, + .ndo_change_mtu = rmnet_change_mtu, + .ndo_set_mac_address = 0, + .ndo_validate_addr = 0, +}; + +static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct rmnet_private *p = netdev_priv(dev); + u32 old_opmode = p->operation_mode; + unsigned long flags; + int prev_mtu = dev->mtu; + int rc = 0; + + /* Process IOCTL command */ + switch (cmd) { + case RMNET_IOCTL_SET_LLP_ETHERNET: /* Set Ethernet protocol */ + /* Perform Ethernet config only if in IP mode currently*/ + if (p->operation_mode & RMNET_MODE_LLP_IP) { + ether_setup(dev); + random_ether_addr(dev->dev_addr); + dev->mtu = prev_mtu; + + dev->netdev_ops = &rmnet_ops_ether; + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_LLP_IP; + p->operation_mode |= RMNET_MODE_LLP_ETH; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): " + "set Ethernet protocol mode\n", + dev->name); + } + break; + + case RMNET_IOCTL_SET_LLP_IP: /* Set RAWIP protocol */ + /* Perform IP config only if in Ethernet mode currently*/ + if (p->operation_mode & RMNET_MODE_LLP_ETH) { + + /* Undo config done in ether_setup() */ + dev->header_ops = 0; /* No header */ + dev->type = ARPHRD_RAWIP; + dev->hard_header_len = 0; + dev->mtu = prev_mtu; + dev->addr_len = 0; + dev->flags &= ~(IFF_BROADCAST| + IFF_MULTICAST); + + dev->needed_headroom = HEADROOM_FOR_BAM + + HEADROOM_FOR_QOS; + dev->needed_tailroom = TAILROOM; + dev->netdev_ops = &rmnet_ops_ip; + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_LLP_ETH; + p->operation_mode |= RMNET_MODE_LLP_IP; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): " + "set IP protocol mode\n", + dev->name); + } + break; + + case RMNET_IOCTL_GET_LLP: /* Get link protocol state */ + ifr->ifr_ifru.ifru_data = + (void *)(p->operation_mode & + (RMNET_MODE_LLP_ETH|RMNET_MODE_LLP_IP)); + break; + + case RMNET_IOCTL_SET_QOS_ENABLE: /* Set QoS header enabled */ + spin_lock_irqsave(&p->lock, flags); + p->operation_mode |= RMNET_MODE_QOS; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): set QMI QOS header enable\n", + dev->name); + break; + + case RMNET_IOCTL_SET_QOS_DISABLE: /* Set QoS header disabled */ + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_QOS; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): set QMI QOS header disable\n", + dev->name); + break; + + case RMNET_IOCTL_GET_QOS: /* Get QoS header state */ + ifr->ifr_ifru.ifru_data = + (void *)(p->operation_mode & RMNET_MODE_QOS); + break; + + case RMNET_IOCTL_GET_OPMODE: /* Get operation mode */ + ifr->ifr_ifru.ifru_data = (void *)p->operation_mode; + break; + + case RMNET_IOCTL_OPEN: /* Open transport port */ + rc = __rmnet_open(dev); + DBG0("[%s] rmnet_ioctl(): open transport port\n", + dev->name); + break; + + case RMNET_IOCTL_CLOSE: /* Close transport port */ + rc = __rmnet_close(dev); + DBG0("[%s] rmnet_ioctl(): close transport port\n", + dev->name); + break; + + default: + pr_err("[%s] error: rmnet_ioct called for unsupported cmd[%d]", + dev->name, cmd); + return -EINVAL; + } + + DBG2("[%s] %s: cmd=0x%x opmode old=0x%08x new=0x%08x\n", + dev->name, __func__, cmd, old_opmode, p->operation_mode); + return rc; +} + +static void __init rmnet_setup(struct net_device *dev) +{ + /* Using Ethernet mode by default */ + dev->netdev_ops = &rmnet_ops_ether; + ether_setup(dev); + + /* set this after calling ether_setup */ + dev->mtu = RMNET_DATA_LEN; + dev->needed_headroom = HEADROOM_FOR_BAM + HEADROOM_FOR_QOS ; + dev->needed_tailroom = TAILROOM; + random_ether_addr(dev->dev_addr); + + dev->watchdog_timeo = 1000; /* 10 seconds? */ +} + + +static int __init rmnet_init(void) +{ + int ret; + struct device *d; + struct net_device *dev; + struct rmnet_private *p; + unsigned n; + + pr_info("%s: BAM devices[%d]\n", __func__, RMNET_DEVICE_COUNT); + +#ifdef CONFIG_MSM_RMNET_DEBUG + timeout_us = 0; +#ifdef CONFIG_HAS_EARLYSUSPEND + timeout_suspend_us = 0; +#endif +#endif + + for (n = 0; n < RMNET_DEVICE_COUNT; n++) { + dev = alloc_netdev(sizeof(struct rmnet_private), + "rmnet%d", rmnet_setup); + + if (!dev) + return -ENOMEM; + + d = &(dev->dev); + p = netdev_priv(dev); + /* Initial config uses Ethernet */ + p->operation_mode = RMNET_MODE_LLP_ETH; + p->ch_id = n; + spin_lock_init(&p->lock); +#ifdef CONFIG_MSM_RMNET_DEBUG + p->timeout_us = timeout_us; + p->wakeups_xmit = p->wakeups_rcv = 0; +#endif + + ret = register_netdev(dev); + if (ret) { + free_netdev(dev); + return ret; + } + +#ifdef CONFIG_MSM_RMNET_DEBUG + if (device_create_file(d, &dev_attr_timeout)) + continue; + if (device_create_file(d, &dev_attr_wakeups_xmit)) + continue; + if (device_create_file(d, &dev_attr_wakeups_rcv)) + continue; +#ifdef CONFIG_HAS_EARLYSUSPEND + if (device_create_file(d, &dev_attr_timeout_suspend)) + continue; + + /* Only care about rmnet0 for suspend/resume tiemout hooks. */ + if (n == 0) + rmnet0 = d; +#endif +#endif + } + return 0; +} + +module_init(rmnet_init); +MODULE_DESCRIPTION("MSM RMNET BAM TRANSPORT"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/net/msm_rmnet_sdio.c b/drivers/net/msm_rmnet_sdio.c new file mode 100644 index 00000000000..883c649931b --- /dev/null +++ b/drivers/net/msm_rmnet_sdio.c @@ -0,0 +1,704 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * RMNET SDIO Module. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +#include + +/* Debug message support */ +static int msm_rmnet_sdio_debug_mask; +module_param_named(debug_enable, msm_rmnet_sdio_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define DEBUG_MASK_LVL0 (1U << 0) +#define DEBUG_MASK_LVL1 (1U << 1) +#define DEBUG_MASK_LVL2 (1U << 2) + +#define DBG(m, x...) do { \ + if (msm_rmnet_sdio_debug_mask & m) \ + pr_info(x); \ +} while (0) +#define DBG0(x...) DBG(DEBUG_MASK_LVL0, x) +#define DBG1(x...) DBG(DEBUG_MASK_LVL1, x) +#define DBG2(x...) DBG(DEBUG_MASK_LVL2, x) + +/* Configure device instances */ +#define RMNET_DEVICE_COUNT (8) + +/* allow larger frames */ +#define RMNET_DATA_LEN 2000 + +#define DEVICE_ID_INVALID -1 + +#define DEVICE_INACTIVE 0 +#define DEVICE_ACTIVE 1 + +#define HEADROOM_FOR_SDIO 8 /* for mux header */ +#define HEADROOM_FOR_QOS 8 +#define TAILROOM 8 /* for padding by mux layer */ + +struct rmnet_private { + struct net_device_stats stats; + uint32_t ch_id; +#ifdef CONFIG_MSM_RMNET_DEBUG + ktime_t last_packet; + unsigned long wakeups_xmit; + unsigned long wakeups_rcv; + unsigned long timeout_us; +#endif + struct sk_buff *skb; + spinlock_t lock; + struct tasklet_struct tsklt; + u32 operation_mode; /* IOCTL specified mode (protocol, QoS header) */ + uint8_t device_up; + uint8_t in_reset; +}; + +#ifdef CONFIG_MSM_RMNET_DEBUG +static unsigned long timeout_us; + +#ifdef CONFIG_HAS_EARLYSUSPEND +/* + * If early suspend is enabled then we specify two timeout values, + * screen on (default), and screen is off. + */ +static unsigned long timeout_suspend_us; +static struct device *rmnet0; + +/* Set timeout in us when the screen is off. */ +static ssize_t timeout_suspend_store(struct device *d, + struct device_attribute *attr, + const char *buf, size_t n) +{ + timeout_suspend_us = strict_strtoul(buf, NULL, 10); + return n; +} + +static ssize_t timeout_suspend_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", (unsigned long) timeout_suspend_us); +} + +static DEVICE_ATTR(timeout_suspend, 0664, timeout_suspend_show, + timeout_suspend_store); + +static void rmnet_early_suspend(struct early_suspend *handler) +{ + if (rmnet0) { + struct rmnet_private *p = netdev_priv(to_net_dev(rmnet0)); + p->timeout_us = timeout_suspend_us; + } +} + +static void rmnet_late_resume(struct early_suspend *handler) +{ + if (rmnet0) { + struct rmnet_private *p = netdev_priv(to_net_dev(rmnet0)); + p->timeout_us = timeout_us; + } +} + +static struct early_suspend rmnet_power_suspend = { + .suspend = rmnet_early_suspend, + .resume = rmnet_late_resume, +}; + +static int __init rmnet_late_init(void) +{ + register_early_suspend(&rmnet_power_suspend); + return 0; +} + +late_initcall(rmnet_late_init); +#endif + +/* Returns 1 if packet caused rmnet to wakeup, 0 otherwise. */ +static int rmnet_cause_wakeup(struct rmnet_private *p) +{ + int ret = 0; + ktime_t now; + if (p->timeout_us == 0) /* Check if disabled */ + return 0; + + /* Use real (wall) time. */ + now = ktime_get_real(); + + if (ktime_us_delta(now, p->last_packet) > p->timeout_us) + ret = 1; + + p->last_packet = now; + return ret; +} + +static ssize_t wakeups_xmit_show(struct device *d, + struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", p->wakeups_xmit); +} + +DEVICE_ATTR(wakeups_xmit, 0444, wakeups_xmit_show, NULL); + +static ssize_t wakeups_rcv_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", p->wakeups_rcv); +} + +DEVICE_ATTR(wakeups_rcv, 0444, wakeups_rcv_show, NULL); + +/* Set timeout in us. */ +static ssize_t timeout_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t n) +{ +#ifndef CONFIG_HAS_EARLYSUSPEND + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + p->timeout_us = timeout_us = strict_strtoul(buf, NULL, 10); +#else +/* If using early suspend/resume hooks do not write the value on store. */ + timeout_us = strict_strtoul(buf, NULL, 10); +#endif + return n; +} + +static ssize_t timeout_show(struct device *d, struct device_attribute *attr, + char *buf) +{ + struct rmnet_private *p = netdev_priv(to_net_dev(d)); + p = netdev_priv(to_net_dev(d)); + return sprintf(buf, "%lu\n", timeout_us); +} + +DEVICE_ATTR(timeout, 0664, timeout_show, timeout_store); +#endif + + +/* Forward declaration */ +static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); + +static __be16 rmnet_ip_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + __be16 protocol = 0; + + skb->dev = dev; + + /* Determine L3 protocol */ + switch (skb->data[0] & 0xf0) { + case 0x40: + protocol = htons(ETH_P_IP); + break; + case 0x60: + protocol = htons(ETH_P_IPV6); + break; + default: + pr_err("[%s] rmnet_recv() L3 protocol decode error: 0x%02x", + dev->name, skb->data[0] & 0xf0); + /* skb will be dropped in upper layer for unknown protocol */ + } + return protocol; +} + +static int count_this_packet(void *_hdr, int len) +{ + struct ethhdr *hdr = _hdr; + + if (len >= ETH_HLEN && hdr->h_proto == htons(ETH_P_ARP)) + return 0; + + return 1; +} + +static int sdio_update_reset_state(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + int new_state; + + new_state = msm_sdio_is_channel_in_reset(p->ch_id); + + if (p->in_reset != new_state) { + p->in_reset = (uint8_t)new_state; + + if (p->in_reset) + netif_carrier_off(dev); + else + netif_carrier_on(dev); + return 1; + } + return 0; +} + +/* Rx Callback, Called in Work Queue context */ +static void sdio_recv_notify(void *dev, struct sk_buff *skb) +{ + struct rmnet_private *p = netdev_priv(dev); + unsigned long flags; + u32 opmode; + + if (skb) { + skb->dev = dev; + /* Handle Rx frame format */ + spin_lock_irqsave(&p->lock, flags); + opmode = p->operation_mode; + spin_unlock_irqrestore(&p->lock, flags); + + if (RMNET_IS_MODE_IP(opmode)) { + /* Driver in IP mode */ + skb->protocol = rmnet_ip_type_trans(skb, dev); + } else { + /* Driver in Ethernet mode */ + skb->protocol = eth_type_trans(skb, dev); + } + if (RMNET_IS_MODE_IP(opmode) || + count_this_packet(skb->data, skb->len)) { +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_rcv += rmnet_cause_wakeup(p); +#endif + p->stats.rx_packets++; + p->stats.rx_bytes += skb->len; + } + DBG1("[%s] Rx packet #%lu len=%d\n", + ((struct net_device *)dev)->name, + p->stats.rx_packets, skb->len); + + /* Deliver to network stack */ + netif_rx(skb); + } else { + spin_lock_irqsave(&p->lock, flags); + if (!sdio_update_reset_state((struct net_device *)dev)) + pr_err("[%s] %s: No skb received", + ((struct net_device *)dev)->name, __func__); + spin_unlock_irqrestore(&p->lock, flags); + } +} + +static int _rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + int sdio_ret; + struct QMI_QOS_HDR_S *qmih; + u32 opmode; + unsigned long flags; + + if (!netif_carrier_ok(dev)) { + pr_err("[%s] %s: channel in reset", + dev->name, __func__); + goto xmit_out; + } + + /* For QoS mode, prepend QMI header and assign flow ID from skb->mark */ + spin_lock_irqsave(&p->lock, flags); + opmode = p->operation_mode; + spin_unlock_irqrestore(&p->lock, flags); + + if (RMNET_IS_MODE_QOS(opmode)) { + qmih = (struct QMI_QOS_HDR_S *) + skb_push(skb, sizeof(struct QMI_QOS_HDR_S)); + qmih->version = 1; + qmih->flags = 0; + qmih->flow_id = skb->mark; + } + + dev->trans_start = jiffies; + sdio_ret = msm_sdio_dmux_write(p->ch_id, skb); + + if (sdio_ret != 0) { + pr_err("[%s] %s: write returned error %d", + dev->name, __func__, sdio_ret); + goto xmit_out; + } + + if (count_this_packet(skb->data, skb->len)) { + p->stats.tx_packets++; + p->stats.tx_bytes += skb->len; +#ifdef CONFIG_MSM_RMNET_DEBUG + p->wakeups_xmit += rmnet_cause_wakeup(p); +#endif + } + DBG1("[%s] Tx packet #%lu len=%d mark=0x%x\n", + dev->name, p->stats.tx_packets, skb->len, skb->mark); + + return 0; +xmit_out: + dev_kfree_skb_any(skb); + p->stats.tx_errors++; + return 0; +} + +static void sdio_write_done(void *dev, struct sk_buff *skb) +{ + struct rmnet_private *p = netdev_priv(dev); + + if (skb) + dev_kfree_skb_any(skb); + + if (!p->in_reset) { + DBG1("%s: write complete skb=%p\n", __func__, skb); + + if (netif_queue_stopped(dev) && + msm_sdio_dmux_is_ch_low(p->ch_id)) { + DBG0("%s: Low WM hit, waking queue=%p\n", + __func__, skb); + netif_wake_queue(dev); + } + } else { + DBG1("%s: write in reset skb=%p\n", __func__, skb); + } +} + +static int __rmnet_open(struct net_device *dev) +{ + int r; + struct rmnet_private *p = netdev_priv(dev); + + DBG0("[%s] __rmnet_open()\n", dev->name); + + if (!p->device_up) { + r = msm_sdio_dmux_open(p->ch_id, dev, + sdio_recv_notify, sdio_write_done); + + if (r < 0) + return -ENODEV; + } + + p->device_up = DEVICE_ACTIVE; + return 0; +} + +static int rmnet_open(struct net_device *dev) +{ + int rc = 0; + + DBG0("[%s] rmnet_open()\n", dev->name); + + rc = __rmnet_open(dev); + + if (rc == 0) + netif_start_queue(dev); + + return rc; +} + + +static int __rmnet_close(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + int rc = 0; + + if (p->device_up) { + /* do not close rmnet port once up, this causes + remote side to hang if tried to open again */ + /* rc = msm_sdio_dmux_close(p->ch_id); */ + p->device_up = DEVICE_INACTIVE; + return rc; + } else + return -EBADF; +} + + +static int rmnet_stop(struct net_device *dev) +{ + DBG0("[%s] rmnet_stop()\n", dev->name); + + __rmnet_close(dev); + netif_stop_queue(dev); + + return 0; +} + +static int rmnet_change_mtu(struct net_device *dev, int new_mtu) +{ + if (0 > new_mtu || RMNET_DATA_LEN < new_mtu) + return -EINVAL; + + DBG0("[%s] MTU change: old=%d new=%d\n", + dev->name, dev->mtu, new_mtu); + dev->mtu = new_mtu; + + return 0; +} + +static int rmnet_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + + if (netif_queue_stopped(dev)) { + pr_err("[%s]fatal: rmnet_xmit called when " + "netif_queue is stopped", dev->name); + return 0; + } + + _rmnet_xmit(skb, dev); + + if (msm_sdio_dmux_is_ch_full(p->ch_id)) { + netif_stop_queue(dev); + DBG0("%s: High WM hit, stopping queue=%p\n", __func__, skb); + } + + return 0; +} + +static struct net_device_stats *rmnet_get_stats(struct net_device *dev) +{ + struct rmnet_private *p = netdev_priv(dev); + return &p->stats; +} + +static void rmnet_set_multicast_list(struct net_device *dev) +{ +} + +static void rmnet_tx_timeout(struct net_device *dev) +{ + pr_warning("[%s] rmnet_tx_timeout()\n", dev->name); +} + +static const struct net_device_ops rmnet_ops_ether = { + .ndo_open = rmnet_open, + .ndo_stop = rmnet_stop, + .ndo_start_xmit = rmnet_xmit, + .ndo_get_stats = rmnet_get_stats, + .ndo_set_multicast_list = rmnet_set_multicast_list, + .ndo_tx_timeout = rmnet_tx_timeout, + .ndo_do_ioctl = rmnet_ioctl, + .ndo_change_mtu = rmnet_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static const struct net_device_ops rmnet_ops_ip = { + .ndo_open = rmnet_open, + .ndo_stop = rmnet_stop, + .ndo_start_xmit = rmnet_xmit, + .ndo_get_stats = rmnet_get_stats, + .ndo_set_multicast_list = rmnet_set_multicast_list, + .ndo_tx_timeout = rmnet_tx_timeout, + .ndo_do_ioctl = rmnet_ioctl, + .ndo_change_mtu = rmnet_change_mtu, + .ndo_set_mac_address = 0, + .ndo_validate_addr = 0, +}; + +static int rmnet_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct rmnet_private *p = netdev_priv(dev); + u32 old_opmode = p->operation_mode; + unsigned long flags; + int prev_mtu = dev->mtu; + int rc = 0; + + /* Process IOCTL command */ + switch (cmd) { + case RMNET_IOCTL_SET_LLP_ETHERNET: /* Set Ethernet protocol */ + /* Perform Ethernet config only if in IP mode currently*/ + if (p->operation_mode & RMNET_MODE_LLP_IP) { + ether_setup(dev); + random_ether_addr(dev->dev_addr); + dev->mtu = prev_mtu; + + dev->netdev_ops = &rmnet_ops_ether; + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_LLP_IP; + p->operation_mode |= RMNET_MODE_LLP_ETH; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): " + "set Ethernet protocol mode\n", + dev->name); + } + break; + + case RMNET_IOCTL_SET_LLP_IP: /* Set RAWIP protocol */ + /* Perform IP config only if in Ethernet mode currently*/ + if (p->operation_mode & RMNET_MODE_LLP_ETH) { + + /* Undo config done in ether_setup() */ + dev->header_ops = 0; /* No header */ + dev->type = ARPHRD_RAWIP; + dev->hard_header_len = 0; + dev->mtu = prev_mtu; + dev->addr_len = 0; + dev->flags &= ~(IFF_BROADCAST| + IFF_MULTICAST); + + dev->needed_headroom = HEADROOM_FOR_SDIO + + HEADROOM_FOR_QOS; + dev->needed_tailroom = TAILROOM; + dev->netdev_ops = &rmnet_ops_ip; + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_LLP_ETH; + p->operation_mode |= RMNET_MODE_LLP_IP; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): " + "set IP protocol mode\n", + dev->name); + } + break; + + case RMNET_IOCTL_GET_LLP: /* Get link protocol state */ + ifr->ifr_ifru.ifru_data = + (void *)(p->operation_mode & + (RMNET_MODE_LLP_ETH|RMNET_MODE_LLP_IP)); + break; + + case RMNET_IOCTL_SET_QOS_ENABLE: /* Set QoS header enabled */ + spin_lock_irqsave(&p->lock, flags); + p->operation_mode |= RMNET_MODE_QOS; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): set QMI QOS header enable\n", + dev->name); + break; + + case RMNET_IOCTL_SET_QOS_DISABLE: /* Set QoS header disabled */ + spin_lock_irqsave(&p->lock, flags); + p->operation_mode &= ~RMNET_MODE_QOS; + spin_unlock_irqrestore(&p->lock, flags); + DBG0("[%s] rmnet_ioctl(): set QMI QOS header disable\n", + dev->name); + break; + + case RMNET_IOCTL_GET_QOS: /* Get QoS header state */ + ifr->ifr_ifru.ifru_data = + (void *)(p->operation_mode & RMNET_MODE_QOS); + break; + + case RMNET_IOCTL_GET_OPMODE: /* Get operation mode */ + ifr->ifr_ifru.ifru_data = (void *)p->operation_mode; + break; + + case RMNET_IOCTL_OPEN: /* Open transport port */ + rc = __rmnet_open(dev); + DBG0("[%s] rmnet_ioctl(): open transport port\n", + dev->name); + break; + + case RMNET_IOCTL_CLOSE: /* Close transport port */ + rc = __rmnet_close(dev); + DBG0("[%s] rmnet_ioctl(): close transport port\n", + dev->name); + break; + + default: + pr_err("[%s] error: rmnet_ioct called for unsupported cmd[%d]", + dev->name, cmd); + return -EINVAL; + } + + DBG2("[%s] %s: cmd=0x%x opmode old=0x%08x new=0x%08x\n", + dev->name, __func__, cmd, old_opmode, p->operation_mode); + return rc; +} + +static void __init rmnet_setup(struct net_device *dev) +{ + /* Using Ethernet mode by default */ + dev->netdev_ops = &rmnet_ops_ether; + ether_setup(dev); + + /* set this after calling ether_setup */ + dev->mtu = RMNET_DATA_LEN; + dev->needed_headroom = HEADROOM_FOR_SDIO + HEADROOM_FOR_QOS ; + dev->needed_tailroom = TAILROOM; + random_ether_addr(dev->dev_addr); + + dev->watchdog_timeo = 1000; /* 10 seconds? */ +} + + +static int __init rmnet_init(void) +{ + int ret; + struct device *d; + struct net_device *dev; + struct rmnet_private *p; + unsigned n; + + pr_info("%s: SDIO devices[%d]\n", __func__, RMNET_DEVICE_COUNT); + +#ifdef CONFIG_MSM_RMNET_DEBUG + timeout_us = 0; +#ifdef CONFIG_HAS_EARLYSUSPEND + timeout_suspend_us = 0; +#endif +#endif + + for (n = 0; n < RMNET_DEVICE_COUNT; n++) { + dev = alloc_netdev(sizeof(struct rmnet_private), + "rmnet_sdio%d", rmnet_setup); + + if (!dev) + return -ENOMEM; + + d = &(dev->dev); + p = netdev_priv(dev); + /* Initial config uses Ethernet */ + p->operation_mode = RMNET_MODE_LLP_ETH; + p->ch_id = n; + spin_lock_init(&p->lock); +#ifdef CONFIG_MSM_RMNET_DEBUG + p->timeout_us = timeout_us; + p->wakeups_xmit = p->wakeups_rcv = 0; +#endif + + ret = register_netdev(dev); + if (ret) { + free_netdev(dev); + return ret; + } + +#ifdef CONFIG_MSM_RMNET_DEBUG + if (device_create_file(d, &dev_attr_timeout)) + continue; + if (device_create_file(d, &dev_attr_wakeups_xmit)) + continue; + if (device_create_file(d, &dev_attr_wakeups_rcv)) + continue; +#ifdef CONFIG_HAS_EARLYSUSPEND + if (device_create_file(d, &dev_attr_timeout_suspend)) + continue; + + /* Only care about rmnet0 for suspend/resume tiemout hooks. */ + if (n == 0) + rmnet0 = d; +#endif +#endif + } + return 0; +} + +module_init(rmnet_init); +MODULE_DESCRIPTION("MSM RMNET SDIO TRANSPORT"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/net/qfec.c b/drivers/net/qfec.c new file mode 100644 index 00000000000..90e8efffa77 --- /dev/null +++ b/drivers/net/qfec.c @@ -0,0 +1,2521 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include + +#include /* size_t */ +#include /* mark_bh */ + +#include /* struct device, and other headers */ +#include /* eth_type_trans */ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "qfec.h" + +#define QFEC_NAME "qfec" +#define QFEC_DRV_VER "June 18a 2011" + +#define ETH_BUF_SIZE 0x600 +#define MAX_N_BD 50 +#define MAC_ADDR_SIZE 6 + +#define RX_TX_BD_RATIO 8 +#define RX_BD_NUM 32 +#define TX_BD_NUM (RX_BD_NUM * RX_TX_BD_RATIO) +#define TX_BD_TI_RATIO 4 + +/* + * logging macros + */ +#define QFEC_LOG_PR 1 +#define QFEC_LOG_DBG 2 +#define QFEC_LOG_DBG2 4 +#define QFEC_LOG_MDIO_W 8 +#define QFEC_LOG_MDIO_R 16 + +static int qfec_debug = QFEC_LOG_PR; + +#ifdef QFEC_DEBUG +# define QFEC_LOG(flag, ...) \ + do { \ + if (flag & qfec_debug) \ + pr_info(__VA_ARGS__); \ + } while (0) +#else +# define QFEC_LOG(flag, ...) +#endif + +#define QFEC_LOG_ERR(...) pr_err(__VA_ARGS__) + +/* + * driver buffer-descriptor + * contains the 4 word HW descriptor plus an additional 4-words. + * (See the DSL bits in the BUS-Mode register). + */ +#define BD_FLAG_LAST_BD 1 + +struct buf_desc { + struct qfec_buf_desc *p_desc; + struct sk_buff *skb; + void *buf_virt_addr; + void *buf_phys_addr; + uint32_t last_bd_flag; +}; + +/* + *inline functions accessing non-struct qfec_buf_desc elements + */ + +/* skb */ +static inline struct sk_buff *qfec_bd_skbuf_get(struct buf_desc *p_bd) +{ + return p_bd->skb; +}; + +static inline void qfec_bd_skbuf_set(struct buf_desc *p_bd, struct sk_buff *p) +{ + p_bd->skb = p; +}; + +/* virtual addr */ +static inline void qfec_bd_virt_set(struct buf_desc *p_bd, void *addr) +{ + p_bd->buf_virt_addr = addr; +}; + +static inline void *qfec_bd_virt_get(struct buf_desc *p_bd) +{ + return p_bd->buf_virt_addr; +}; + +/* physical addr */ +static inline void qfec_bd_phys_set(struct buf_desc *p_bd, void *addr) +{ + p_bd->buf_phys_addr = addr; +}; + +static inline void *qfec_bd_phys_get(struct buf_desc *p_bd) +{ + return p_bd->buf_phys_addr; +}; + +/* last_bd_flag */ +static inline uint32_t qfec_bd_last_bd(struct buf_desc *p_bd) +{ + return (p_bd->last_bd_flag != 0); +}; + +static inline void qfec_bd_last_bd_set(struct buf_desc *p_bd) +{ + p_bd->last_bd_flag = BD_FLAG_LAST_BD; +}; + +/* + *inline functions accessing struct qfec_buf_desc elements + */ + +/* ownership bit */ +static inline uint32_t qfec_bd_own(struct buf_desc *p_bd) +{ + return p_bd->p_desc->status & BUF_OWN; +}; + +static inline void qfec_bd_own_set(struct buf_desc *p_bd) +{ + p_bd->p_desc->status |= BUF_OWN ; +}; + +static inline void qfec_bd_own_clr(struct buf_desc *p_bd) +{ + p_bd->p_desc->status &= ~(BUF_OWN); +}; + +static inline uint32_t qfec_bd_status_get(struct buf_desc *p_bd) +{ + return p_bd->p_desc->status; +}; + +static inline void qfec_bd_status_set(struct buf_desc *p_bd, uint32_t status) +{ + p_bd->p_desc->status = status; +}; + +static inline uint32_t qfec_bd_status_len(struct buf_desc *p_bd) +{ + return BUF_RX_FL_GET((*p_bd->p_desc)); +}; + +/* control register */ +static inline void qfec_bd_ctl_reset(struct buf_desc *p_bd) +{ + p_bd->p_desc->ctl = 0; +}; + +static inline uint32_t qfec_bd_ctl_get(struct buf_desc *p_bd) +{ + return p_bd->p_desc->ctl; +}; + +static inline void qfec_bd_ctl_set(struct buf_desc *p_bd, uint32_t val) +{ + p_bd->p_desc->ctl |= val; +}; + +static inline void qfec_bd_ctl_wr(struct buf_desc *p_bd, uint32_t val) +{ + p_bd->p_desc->ctl = val; +}; + +/* pbuf register */ +static inline void *qfec_bd_pbuf_get(struct buf_desc *p_bd) +{ + return p_bd->p_desc->p_buf; +} + +static inline void qfec_bd_pbuf_set(struct buf_desc *p_bd, void *p) +{ + p_bd->p_desc->p_buf = p; +} + +/* next register */ +static inline void *qfec_bd_next_get(struct buf_desc *p_bd) +{ + return p_bd->p_desc->next; +}; + +/* + * initialize an RX BD w/ a new buf + */ +static int qfec_rbd_init(struct net_device *dev, struct buf_desc *p_bd) +{ + struct sk_buff *skb; + void *p; + void *v; + + /* allocate and record ptrs for sk buff */ + skb = dev_alloc_skb(ETH_BUF_SIZE); + if (!skb) + goto err; + + qfec_bd_skbuf_set(p_bd, skb); + + v = skb_put(skb, ETH_BUF_SIZE); + qfec_bd_virt_set(p_bd, v); + + p = (void *) dma_map_single(&dev->dev, + (void *)skb->data, ETH_BUF_SIZE, DMA_FROM_DEVICE); + qfec_bd_pbuf_set(p_bd, p); + qfec_bd_phys_set(p_bd, p); + + /* populate control register */ + /* mark the last BD and set end-of-ring bit */ + qfec_bd_ctl_wr(p_bd, ETH_BUF_SIZE | + (qfec_bd_last_bd(p_bd) ? BUF_RX_RER : 0)); + + qfec_bd_status_set(p_bd, BUF_OWN); + + if (!(qfec_debug & QFEC_LOG_DBG2)) + return 0; + + /* debug messages */ + QFEC_LOG(QFEC_LOG_DBG2, "%s: %p bd\n", __func__, p_bd); + + QFEC_LOG(QFEC_LOG_DBG2, "%s: %p skb\n", __func__, skb); + + QFEC_LOG(QFEC_LOG_DBG2, + "%s: %p p_bd, %p data, %p skb_put, %p virt, %p p_buf, %p p\n", + __func__, (void *)p_bd, + (void *)skb->data, v, /*(void *)skb_put(skb, ETH_BUF_SIZE), */ + (void *)qfec_bd_virt_get(p_bd), (void *)qfec_bd_pbuf_get(p_bd), + (void *)p); + + return 0; + +err: + return -ENOMEM; +}; + +/* + * ring structure used to maintain indices of buffer-descriptor (BD) usage + * + * The RX BDs are normally all pre-allocated with buffers available to be + * DMA'd into with received frames. The head indicates the first BD/buffer + * containing a received frame, and the tail indicates the oldest BD/buffer + * that needs to be restored for use. Head and tail are both initialized + * to zero, and n_free is initialized to zero, since all BD are initialized. + * + * The TX BDs are normally available for use, only being initialized as + * TX frames are requested for transmission. The head indicates the + * first available BD, and the tail indicate the oldest BD that has + * not been acknowledged as transmitted. Head and tail are both initialized + * to zero, and n_free is initialized to len, since all are available for use. + */ +struct ring { + int head; + int tail; + int n_free; + int len; +}; + +/* accessory in line functions for struct ring */ +static inline void qfec_ring_init(struct ring *p_ring, int size, int free) +{ + p_ring->head = p_ring->tail = 0; + p_ring->len = size; + p_ring->n_free = free; +} + +static inline int qfec_ring_full(struct ring *p_ring) +{ + return (p_ring->n_free == 0); +}; + +static inline int qfec_ring_empty(struct ring *p_ring) +{ + return (p_ring->n_free == p_ring->len); +} + +static inline void qfec_ring_head_adv(struct ring *p_ring) +{ + p_ring->head = ++p_ring->head % p_ring->len; + p_ring->n_free--; +}; + +static inline void qfec_ring_tail_adv(struct ring *p_ring) +{ + p_ring->tail = ++p_ring->tail % p_ring->len; + p_ring->n_free++; +}; + +static inline int qfec_ring_head(struct ring *p_ring) +{ + + return p_ring->head; +}; + +static inline int qfec_ring_tail(struct ring *p_ring) +{ + return p_ring->tail; +}; + +static inline int qfec_ring_room(struct ring *p_ring) +{ + return p_ring->n_free; +}; + +/* + * counters track normal and abnormal driver events and activity + */ +enum cntr { + isr = 0, + fatal_bus, + + early_tx, + tx_no_resource, + tx_proc_stopped, + tx_jabber_tmout, + + xmit, + tx_int, + tx_isr, + tx_owned, + tx_underflow, + + tx_replenish, + tx_skb_null, + tx_timeout, + tx_too_large, + + gmac_isr, + + /* half */ + norm_int, + abnorm_int, + + early_rx, + rx_buf_unavail, + rx_proc_stopped, + rx_watchdog, + + netif_rx_cntr, + rx_int, + rx_isr, + rx_owned, + rx_overflow, + + rx_dropped, + rx_skb_null, + queue_start, + queue_stop, + + rx_paddr_nok, + ts_ioctl, + ts_tx_en, + ts_tx_rtn, + + ts_rec, + cntr_last, +}; + +static char *cntr_name[] = { + "isr", + "fatal_bus", + + "early_tx", + "tx_no_resource", + "tx_proc_stopped", + "tx_jabber_tmout", + + "xmit", + "tx_int", + "tx_isr", + "tx_owned", + "tx_underflow", + + "tx_replenish", + "tx_skb_null", + "tx_timeout", + "tx_too_large", + + "gmac_isr", + + /* half */ + "norm_int", + "abnorm_int", + + "early_rx", + "rx_buf_unavail", + "rx_proc_stopped", + "rx_watchdog", + + "netif_rx", + "rx_int", + "rx_isr", + "rx_owned", + "rx_overflow", + + "rx_dropped", + "rx_skb_null", + "queue_start", + "queue_stop", + + "rx_paddr_nok", + "ts_ioctl", + "ts_tx_en", + "ts_tx_rtn", + + "ts_rec", + "" +}; + +/* + * private data + */ + +static struct net_device *qfec_dev; + +enum qfec_state { + timestamping = 0x04, +}; + +struct qfec_priv { + struct net_device *net_dev; + struct net_device_stats stats; /* req statistics */ + + struct device dev; + + spinlock_t xmit_lock; + spinlock_t mdio_lock; + + unsigned int state; /* driver state */ + + unsigned int bd_size; /* buf-desc alloc size */ + struct qfec_buf_desc *bd_base; /* * qfec-buf-desc */ + dma_addr_t tbd_dma; /* dma/phy-addr buf-desc */ + dma_addr_t rbd_dma; /* dma/phy-addr buf-desc */ + + struct resource *mac_res; + void *mac_base; /* mac (virt) base address */ + + struct resource *clk_res; + void *clk_base; /* clk (virt) base address */ + + struct resource *fuse_res; + void *fuse_base; /* mac addr fuses */ + + unsigned int n_tbd; /* # of TX buf-desc */ + struct ring ring_tbd; /* TX ring */ + struct buf_desc *p_tbd; + unsigned int tx_ic_mod; /* (%) val for setting IC */ + + unsigned int n_rbd; /* # of RX buf-desc */ + struct ring ring_rbd; /* RX ring */ + struct buf_desc *p_rbd; + + struct buf_desc *p_latest_rbd; + struct buf_desc *p_ending_rbd; + + unsigned long cntr[cntr_last]; /* activity counters */ + + struct mii_if_info mii; /* used by mii lib */ + + int mdio_clk; /* phy mdio clock rate */ + int phy_id; /* default PHY addr (0) */ + struct timer_list phy_tmr; /* monitor PHY state */ +}; + +/* + * cntrs display + */ + +static int qfec_cntrs_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct qfec_priv *priv = netdev_priv(to_net_dev(dev)); + int h = (cntr_last + 1) / 2; + int l; + int n; + int count = PAGE_SIZE; + + QFEC_LOG(QFEC_LOG_DBG2, "%s:\n", __func__); + + l = snprintf(&buf[0], count, "%s:\n", __func__); + for (n = 0; n < h; n++) { + l += snprintf(&buf[l], count - l, + " %12lu %-16s %12lu %s\n", + priv->cntr[n], cntr_name[n], + priv->cntr[n+h], cntr_name[n+h]); + } + + return l; +} + +# define CNTR_INC(priv, name) (priv->cntr[name]++) + +/* + * functions that manage state + */ +static inline void qfec_queue_start(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + + if (netif_queue_stopped(dev)) { + netif_wake_queue(dev); + CNTR_INC(priv, queue_start); + } +}; + +static inline void qfec_queue_stop(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + + netif_stop_queue(dev); + CNTR_INC(priv, queue_stop); +}; + +/* + * functions to access and initialize the MAC registers + */ +static inline uint32_t qfec_reg_read(struct qfec_priv *priv, uint32_t reg) +{ + return ioread32((void *) (priv->mac_base + reg)); +} + +static void qfec_reg_write(struct qfec_priv *priv, uint32_t reg, uint32_t val) +{ + uint32_t addr = (uint32_t)priv->mac_base + reg; + + QFEC_LOG(QFEC_LOG_DBG2, "%s: %08x <- %08x\n", __func__, addr, val); + iowrite32(val, (void *)addr); +} + +/* + * speed/duplex/pause settings + */ +static int qfec_config_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct qfec_priv *priv = netdev_priv(to_net_dev(dev)); + int cfg = qfec_reg_read(priv, MAC_CONFIG_REG); + int flow = qfec_reg_read(priv, FLOW_CONTROL_REG); + int l = 0; + int count = PAGE_SIZE; + + QFEC_LOG(QFEC_LOG_DBG2, "%s:\n", __func__); + + l += snprintf(&buf[l], count, "%s:", __func__); + + l += snprintf(&buf[l], count - l, " [0x%08x] %4dM %s %s", cfg, + (cfg & MAC_CONFIG_REG_PS) + ? ((cfg & MAC_CONFIG_REG_FES) ? 100 : 10) : 1000, + cfg & MAC_CONFIG_REG_DM ? "FD" : "HD", + cfg & MAC_CONFIG_REG_IPC ? "IPC" : "NoIPC"); + + flow &= FLOW_CONTROL_RFE | FLOW_CONTROL_TFE; + l += snprintf(&buf[l], count - l, " [0x%08x] %s", flow, + (flow == (FLOW_CONTROL_RFE | FLOW_CONTROL_TFE)) ? "PAUSE" + : ((flow == FLOW_CONTROL_RFE) ? "RX-PAUSE" + : ((flow == FLOW_CONTROL_TFE) ? "TX-PAUSE" : ""))); + + l += snprintf(&buf[l], count - l, " %s", QFEC_DRV_VER); + l += snprintf(&buf[l], count - l, "\n"); + return l; +} + + +/* + * table and functions to initialize controller registers + */ + +struct reg_entry { + unsigned int rdonly; + unsigned int addr; + char *label; + unsigned int val; +}; + +static struct reg_entry qfec_reg_tbl[] = { + { 0, BUS_MODE_REG, "BUS_MODE_REG", BUS_MODE_REG_DEFAULT }, + { 0, AXI_BUS_MODE_REG, "AXI_BUS_MODE_REG", AXI_BUS_MODE_DEFAULT }, + { 0, AXI_STATUS_REG, "AXI_STATUS_REG", 0 }, + + { 0, MAC_ADR_0_HIGH_REG, "MAC_ADR_0_HIGH_REG", 0x00000302 }, + { 0, MAC_ADR_0_LOW_REG, "MAC_ADR_0_LOW_REG", 0x01350702 }, + + { 1, RX_DES_LST_ADR_REG, "RX_DES_LST_ADR_REG", 0 }, + { 1, TX_DES_LST_ADR_REG, "TX_DES_LST_ADR_REG", 0 }, + { 1, STATUS_REG, "STATUS_REG", 0 }, + { 1, DEBUG_REG, "DEBUG_REG", 0 }, + + { 0, INTRP_EN_REG, "INTRP_EN_REG", QFEC_INTRP_SETUP}, + + { 1, CUR_HOST_TX_DES_REG, "CUR_HOST_TX_DES_REG", 0 }, + { 1, CUR_HOST_RX_DES_REG, "CUR_HOST_RX_DES_REG", 0 }, + { 1, CUR_HOST_TX_BU_ADR_REG, "CUR_HOST_TX_BU_ADR_REG", 0 }, + { 1, CUR_HOST_RX_BU_ADR_REG, "CUR_HOST_RX_BU_ADR_REG", 0 }, + + { 1, MAC_FR_FILTER_REG, "MAC_FR_FILTER_REG", 0 }, + + { 0, MAC_CONFIG_REG, "MAC_CONFIG_REG", MAC_CONFIG_REG_SPD_1G + | MAC_CONFIG_REG_DM + | MAC_CONFIG_REG_TE + | MAC_CONFIG_REG_RE + | MAC_CONFIG_REG_IPC }, + + { 1, INTRP_STATUS_REG, "INTRP_STATUS_REG", 0 }, + { 1, INTRP_MASK_REG, "INTRP_MASK_REG", 0 }, + + { 0, OPER_MODE_REG, "OPER_MODE_REG", OPER_MODE_REG_DEFAULT }, + + { 1, GMII_ADR_REG, "GMII_ADR_REG", 0 }, + { 1, GMII_DATA_REG, "GMII_DATA_REG", 0 }, + + { 0, MMC_INTR_MASK_RX_REG, "MMC_INTR_MASK_RX_REG", 0xFFFFFFFF }, + { 0, MMC_INTR_MASK_TX_REG, "MMC_INTR_MASK_TX_REG", 0xFFFFFFFF }, + + { 1, TS_HIGH_REG, "TS_HIGH_REG", 0 }, + { 1, TS_LOW_REG, "TS_LOW_REG", 0 }, + + { 1, TS_HI_UPDT_REG, "TS_HI_UPDATE_REG", 0 }, + { 1, TS_LO_UPDT_REG, "TS_LO_UPDATE_REG", 0 }, + { 0, TS_SUB_SEC_INCR_REG, "TS_SUB_SEC_INCR_REG", 86 }, + + { 0, TS_CTL_REG, "TS_CTL_REG", TS_CTL_TSENALL + | TS_CTL_TSCTRLSSR + | TS_CTL_TSINIT + | TS_CTL_TSENA }, +}; + +static void qfec_reg_init(struct qfec_priv *priv) +{ + struct reg_entry *p = qfec_reg_tbl; + int n = ARRAY_SIZE(qfec_reg_tbl); + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + for (; n--; p++) { + if (!p->rdonly) + qfec_reg_write(priv, p->addr, p->val); + } +} + +/* + * display registers thru sysfs + */ +static int qfec_reg_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct qfec_priv *priv = netdev_priv(to_net_dev(dev)); + struct reg_entry *p = qfec_reg_tbl; + int n = ARRAY_SIZE(qfec_reg_tbl); + int l = 0; + int count = PAGE_SIZE; + + QFEC_LOG(QFEC_LOG_DBG2, "%s:\n", __func__); + + for (; n--; p++) { + l += snprintf(&buf[l], count - l, " %8p %04x %08x %s\n", + (void *)priv->mac_base + p->addr, p->addr, + qfec_reg_read(priv, p->addr), p->label); + } + + return l; +} + +/* + * set the MAC-0 address + */ +static void qfec_set_adr_regs(struct qfec_priv *priv, uint8_t *addr) +{ + uint32_t h = 0; + uint32_t l = 0; + + h = h << 8 | addr[5]; + h = h << 8 | addr[4]; + + l = l << 8 | addr[3]; + l = l << 8 | addr[2]; + l = l << 8 | addr[1]; + l = l << 8 | addr[0]; + + qfec_reg_write(priv, MAC_ADR_0_HIGH_REG, h); + qfec_reg_write(priv, MAC_ADR_0_LOW_REG, l); + + QFEC_LOG(QFEC_LOG_DBG, "%s: %08x %08x\n", __func__, h, l); +} + +/* + * reset the controller + */ + +#define QFEC_RESET_TIMEOUT 10000 + /* reset should always clear but did not w/o test/delay + * in RgMii mode. there is no spec'd max timeout + */ + +static int qfec_hw_reset(struct qfec_priv *priv) +{ + int timeout = QFEC_RESET_TIMEOUT; + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + qfec_reg_write(priv, BUS_MODE_REG, BUS_MODE_SWR); + + while (qfec_reg_read(priv, BUS_MODE_REG) & BUS_MODE_SWR) { + if (timeout-- == 0) { + QFEC_LOG_ERR("%s: timeout\n", __func__); + return -ETIME; + } + + /* there were problems resetting the controller + * in RGMII mode when there wasn't sufficient + * delay between register reads + */ + usleep_range(100, 200); + } + + return 0; +} + +/* + * initialize controller + */ +static int qfec_hw_init(struct qfec_priv *priv) +{ + int res = 0; + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + res = qfec_hw_reset(priv); + if (res) + return res; + + qfec_reg_init(priv); + + /* config buf-desc locations */ + qfec_reg_write(priv, TX_DES_LST_ADR_REG, priv->tbd_dma); + qfec_reg_write(priv, RX_DES_LST_ADR_REG, priv->rbd_dma); + + /* clear interrupts */ + qfec_reg_write(priv, STATUS_REG, INTRP_EN_REG_NIE | INTRP_EN_REG_RIE + | INTRP_EN_REG_TIE | INTRP_EN_REG_TUE | INTRP_EN_REG_ETE); + + return res; +} + +/* + * en/disable controller + */ +static void qfec_hw_enable(struct qfec_priv *priv) +{ + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + qfec_reg_write(priv, OPER_MODE_REG, + qfec_reg_read(priv, OPER_MODE_REG) + | OPER_MODE_REG_ST | OPER_MODE_REG_SR); +} + +static void qfec_hw_disable(struct qfec_priv *priv) +{ + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + qfec_reg_write(priv, OPER_MODE_REG, + qfec_reg_read(priv, OPER_MODE_REG) + & ~(OPER_MODE_REG_ST | OPER_MODE_REG_SR)); +} + +/* + * interface selection + */ +struct intf_config { + uint32_t intf_sel; + uint32_t emac_ns; + uint32_t eth_x_en_ns; + uint32_t clkmux_sel; +}; + +#define ETH_X_EN_NS_REVMII (ETH_X_EN_NS_DEFAULT | ETH_TX_CLK_INV) +#define CLKMUX_REVMII (EMAC_CLKMUX_SEL_0 | EMAC_CLKMUX_SEL_1) + +static struct intf_config intf_config_tbl[] = { + { EMAC_PHY_INTF_SEL_MII, EMAC_NS_DEFAULT, ETH_X_EN_NS_DEFAULT, 0 }, + { EMAC_PHY_INTF_SEL_RGMII, EMAC_NS_DEFAULT, ETH_X_EN_NS_DEFAULT, 0 }, + { EMAC_PHY_INTF_SEL_REVMII, EMAC_NS_DEFAULT, ETH_X_EN_NS_REVMII, + CLKMUX_REVMII } +}; + +/* + * emac clk register read and write functions + */ +static inline uint32_t qfec_clkreg_read(struct qfec_priv *priv, uint32_t reg) +{ + return ioread32((void *) (priv->clk_base + reg)); +} + +static inline void qfec_clkreg_write(struct qfec_priv *priv, + uint32_t reg, uint32_t val) +{ + uint32_t addr = (uint32_t)priv->clk_base + reg; + + QFEC_LOG(QFEC_LOG_DBG2, "%s: %08x <- %08x\n", __func__, addr, val); + iowrite32(val, (void *)addr); +} + +/* + * configure the PHY interface and clock routing and signal bits + */ +enum phy_intfc { + intfc_mii = 0, + intfc_rgmii = 1, + intfc_revmii = 2, +}; + +static int qfec_intf_sel(struct qfec_priv *priv, unsigned int intfc) +{ + struct intf_config *p; + + QFEC_LOG(QFEC_LOG_DBG2, "%s: %d\n", __func__, intfc); + + if (intfc > intfc_revmii) { + QFEC_LOG_ERR("%s: range\n", __func__); + return -ENXIO; + } + + p = &intf_config_tbl[intfc]; + + qfec_clkreg_write(priv, EMAC_PHY_INTF_SEL_REG, p->intf_sel); + qfec_clkreg_write(priv, EMAC_NS_REG, p->emac_ns); + qfec_clkreg_write(priv, ETH_X_EN_NS_REG, p->eth_x_en_ns); + qfec_clkreg_write(priv, EMAC_CLKMUX_SEL_REG, p->clkmux_sel); + + return 0; +} + +/* + * display registers thru proc-fs + */ +static struct qfec_clk_reg { + uint32_t offset; + char *label; +} qfec_clk_regs[] = { + { ETH_MD_REG, "ETH_MD_REG" }, + { ETH_NS_REG, "ETH_NS_REG" }, + { ETH_X_EN_NS_REG, "ETH_X_EN_NS_REG" }, + { EMAC_PTP_MD_REG, "EMAC_PTP_MD_REG" }, + { EMAC_PTP_NS_REG, "EMAC_PTP_NS_REG" }, + { EMAC_NS_REG, "EMAC_NS_REG" }, + { EMAC_TX_FS_REG, "EMAC_TX_FS_REG" }, + { EMAC_RX_FS_REG, "EMAC_RX_FS_REG" }, + { EMAC_PHY_INTF_SEL_REG, "EMAC_PHY_INTF_SEL_REG" }, + { EMAC_PHY_ADDR_REG, "EMAC_PHY_ADDR_REG" }, + { EMAC_REVMII_PHY_ADDR_REG, "EMAC_REVMII_PHY_ADDR_REG" }, + { EMAC_CLKMUX_SEL_REG, "EMAC_CLKMUX_SEL_REG" }, +}; + +static int qfec_clk_reg_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct qfec_priv *priv = netdev_priv(to_net_dev(dev)); + struct qfec_clk_reg *p = qfec_clk_regs; + int n = ARRAY_SIZE(qfec_clk_regs); + int l = 0; + int count = PAGE_SIZE; + + QFEC_LOG(QFEC_LOG_DBG2, "%s:\n", __func__); + + for (; n--; p++) { + l += snprintf(&buf[l], count - l, " %8p %8x %08x %s\n", + (void *)priv->clk_base + p->offset, p->offset, + qfec_clkreg_read(priv, p->offset), p->label); + } + + return l; +} + +/* + * speed selection + */ + +struct qfec_pll_cfg { + uint32_t spd; + uint32_t eth_md; /* M [31:16], NOT 2*D [15:0] */ + uint32_t eth_ns; /* NOT(M-N) [31:16], ctl bits [11:0] */ +}; + +static struct qfec_pll_cfg qfec_pll_cfg_tbl[] = { + /* 2.5 MHz */ + { MAC_CONFIG_REG_SPD_10, ETH_MD_M(1) | ETH_MD_2D_N(100), + ETH_NS_NM(100-1) + | ETH_NS_MCNTR_EN + | ETH_NS_MCNTR_MODE_DUAL + | ETH_NS_PRE_DIV(0) + | CLK_SRC_PLL_EMAC }, + /* 25 MHz */ + { MAC_CONFIG_REG_SPD_100, ETH_MD_M(1) | ETH_MD_2D_N(10), + ETH_NS_NM(10-1) + | ETH_NS_MCNTR_EN + | ETH_NS_MCNTR_MODE_DUAL + | ETH_NS_PRE_DIV(0) + | CLK_SRC_PLL_EMAC }, + /* 125 MHz */ + {MAC_CONFIG_REG_SPD_1G, 0, ETH_NS_PRE_DIV(1) + | CLK_SRC_PLL_EMAC }, +}; + +enum speed { + spd_10 = 0, + spd_100 = 1, + spd_1000 = 2, +}; + +/* + * configure the PHY interface and clock routing and signal bits + */ +static int qfec_speed_cfg(struct net_device *dev, unsigned int spd, + unsigned int dplx) +{ + struct qfec_priv *priv = netdev_priv(dev); + struct qfec_pll_cfg *p; + + QFEC_LOG(QFEC_LOG_DBG2, "%s: %d spd, %d dplx\n", __func__, spd, dplx); + + if (spd > spd_1000) { + QFEC_LOG_ERR("%s: range\n", __func__); + return -ENODEV; + } + + p = &qfec_pll_cfg_tbl[spd]; + + /* set the MAC speed bits */ + qfec_reg_write(priv, MAC_CONFIG_REG, + (qfec_reg_read(priv, MAC_CONFIG_REG) + & ~(MAC_CONFIG_REG_SPD | MAC_CONFIG_REG_DM)) + | p->spd | (dplx ? MAC_CONFIG_REG_DM : 0)); + + qfec_clkreg_write(priv, ETH_MD_REG, p->eth_md); + qfec_clkreg_write(priv, ETH_NS_REG, p->eth_ns); + + return 0; +} + +/* + * configure PTP divider for 25 MHz assuming EMAC PLL 250 MHz + */ + +static struct qfec_pll_cfg qfec_pll_ptp = { + /* 25 MHz */ + 0, ETH_MD_M(1) | ETH_MD_2D_N(10), ETH_NS_NM(10-1) + | EMAC_PTP_NS_ROOT_EN + | EMAC_PTP_NS_CLK_EN + | ETH_NS_MCNTR_EN + | ETH_NS_MCNTR_MODE_DUAL + | ETH_NS_PRE_DIV(0) + | CLK_SRC_PLL_EMAC +}; + +#define PLLTEST_PAD_CFG 0x01E0 +#define PLLTEST_PLL_7 0x3700 + +#define CLKTEST_REG 0x01EC +#define CLKTEST_EMAC_RX 0x3fc07f7a + +static int qfec_ptp_cfg(struct qfec_priv *priv) +{ + struct qfec_pll_cfg *p = &qfec_pll_ptp; + + QFEC_LOG(QFEC_LOG_DBG2, "%s: %08x md, %08x ns\n", + __func__, p->eth_md, p->eth_ns); + + qfec_clkreg_write(priv, EMAC_PTP_MD_REG, p->eth_md); + qfec_clkreg_write(priv, EMAC_PTP_NS_REG, p->eth_ns); + + /* configure HS/LS clk test ports to verify clks */ + qfec_clkreg_write(priv, CLKTEST_REG, CLKTEST_EMAC_RX); + qfec_clkreg_write(priv, PLLTEST_PAD_CFG, PLLTEST_PLL_7); + + return 0; +} + +/* + * MDIO operations + */ + +/* + * wait reasonable amount of time for MDIO operation to complete, not busy + */ +static int qfec_mdio_busy(struct net_device *dev) +{ + int i; + + for (i = 100; i > 0; i--) { + if (!(qfec_reg_read( + netdev_priv(dev), GMII_ADR_REG) & GMII_ADR_REG_GB)) { + return 0; + } + udelay(1); + } + + return -ETIME; +} + +/* + * initiate either a read or write MDIO operation + */ + +static int qfec_mdio_oper(struct net_device *dev, int phy_id, int reg, int wr) +{ + struct qfec_priv *priv = netdev_priv(dev); + int res = 0; + + /* insure phy not busy */ + res = qfec_mdio_busy(dev); + if (res) { + QFEC_LOG_ERR("%s: busy\n", __func__); + goto done; + } + + /* initiate operation */ + qfec_reg_write(priv, GMII_ADR_REG, + GMII_ADR_REG_ADR_SET(phy_id) + | GMII_ADR_REG_REG_SET(reg) + | GMII_ADR_REG_CSR_SET(priv->mdio_clk) + | (wr ? GMII_ADR_REG_GW : 0) + | GMII_ADR_REG_GB); + + /* wait for operation to complete */ + res = qfec_mdio_busy(dev); + if (res) + QFEC_LOG_ERR("%s: timeout\n", __func__); + +done: + return res; +} + +/* + * read MDIO register + */ +static int qfec_mdio_read(struct net_device *dev, int phy_id, int reg) +{ + struct qfec_priv *priv = netdev_priv(dev); + int res = 0; + unsigned long flags; + + spin_lock_irqsave(&priv->mdio_lock, flags); + + res = qfec_mdio_oper(dev, phy_id, reg, 0); + if (res) { + QFEC_LOG_ERR("%s: oper\n", __func__); + goto done; + } + + res = qfec_reg_read(priv, GMII_DATA_REG); + QFEC_LOG(QFEC_LOG_MDIO_R, "%s: %2d reg, 0x%04x val\n", + __func__, reg, res); + +done: + spin_unlock_irqrestore(&priv->mdio_lock, flags); + return res; +} + +/* + * write MDIO register + */ +static void qfec_mdio_write(struct net_device *dev, int phy_id, int reg, + int val) +{ + struct qfec_priv *priv = netdev_priv(dev); + unsigned long flags; + + spin_lock_irqsave(&priv->mdio_lock, flags); + + QFEC_LOG(QFEC_LOG_MDIO_W, "%s: %2d reg, %04x\n", + __func__, reg, val); + + qfec_reg_write(priv, GMII_DATA_REG, val); + + if (qfec_mdio_oper(dev, phy_id, reg, 1)) + QFEC_LOG_ERR("%s: oper\n", __func__); + + spin_unlock_irqrestore(&priv->mdio_lock, flags); +} + +/* + * get auto-negotiation results + */ + +#define QFEC_100 (LPA_100HALF | LPA_100FULL | LPA_100HALF) +#define QFEC_100_FD (LPA_100FULL | LPA_100BASE4) +#define QFEC_10 (LPA_10HALF | LPA_10FULL) +#define QFEC_10_FD LPA_10FULL + +static void qfec_get_an(struct net_device *dev, uint32_t *spd, uint32_t *dplx) +{ + struct qfec_priv *priv = netdev_priv(dev); + uint32_t status; + uint32_t advert; + uint32_t lpa; + uint32_t flow; + + advert = qfec_mdio_read(dev, priv->phy_id, MII_ADVERTISE); + lpa = qfec_mdio_read(dev, priv->phy_id, MII_LPA); + status = advert & lpa; + + /* todo: check extended status register for 1G abilities */ + + if (status & QFEC_100) { + *spd = spd_100; + *dplx = status & QFEC_100_FD ? 1 : 0; + } + + else if (status & QFEC_10) { + *spd = spd_10; + *dplx = status & QFEC_10_FD ? 1 : 0; + } + + /* check pause */ + flow = qfec_reg_read(priv, FLOW_CONTROL_REG); + flow &= ~(FLOW_CONTROL_TFE | FLOW_CONTROL_RFE); + + if (status & ADVERTISE_PAUSE_CAP) { + flow |= FLOW_CONTROL_RFE | FLOW_CONTROL_TFE; + } else if (status & ADVERTISE_PAUSE_ASYM) { + if (lpa & ADVERTISE_PAUSE_CAP) + flow |= FLOW_CONTROL_TFE; + else if (advert & ADVERTISE_PAUSE_CAP) + flow |= FLOW_CONTROL_RFE; + } + + qfec_reg_write(priv, FLOW_CONTROL_REG, flow); +} + +/* + * monitor phy status, and process auto-neg results when changed + */ + +static void qfec_phy_monitor(unsigned long data) +{ + struct net_device *dev = (struct net_device *) data; + struct qfec_priv *priv = netdev_priv(dev); + unsigned int spd = 0; + unsigned int dplx = 1; + + mod_timer(&priv->phy_tmr, jiffies + HZ); + + if (mii_link_ok(&priv->mii) && !netif_carrier_ok(priv->net_dev)) { + qfec_get_an(dev, &spd, &dplx); + qfec_speed_cfg(dev, spd, dplx); + QFEC_LOG(QFEC_LOG_DBG, "%s: link up, %d spd, %d dplx\n", + __func__, spd, dplx); + + netif_carrier_on(dev); + } + + else if (!mii_link_ok(&priv->mii) && netif_carrier_ok(priv->net_dev)) { + QFEC_LOG(QFEC_LOG_DBG, "%s: link down\n", __func__); + netif_carrier_off(dev); + } +} + +/* + * dealloc buffer descriptor memory + */ + +static void qfec_mem_dealloc(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + + dma_free_coherent(&dev->dev, + priv->bd_size, priv->bd_base, priv->tbd_dma); + priv->bd_base = 0; +} + +/* + * allocate shared device memory for TX/RX buf-desc (and buffers) + */ + +static int qfec_mem_alloc(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + + QFEC_LOG(QFEC_LOG_DBG, "%s: %p dev\n", __func__, dev); + + priv->bd_size = + (priv->n_tbd + priv->n_rbd) * sizeof(struct qfec_buf_desc); + + priv->p_tbd = kcalloc(priv->n_tbd, sizeof(struct buf_desc), GFP_KERNEL); + if (!priv->p_tbd) { + QFEC_LOG_ERR("%s: kcalloc failed p_tbd\n", __func__); + return -ENOMEM; + } + + priv->p_rbd = kcalloc(priv->n_rbd, sizeof(struct buf_desc), GFP_KERNEL); + if (!priv->p_rbd) { + QFEC_LOG_ERR("%s: kcalloc failed p_rbd\n", __func__); + return -ENOMEM; + } + + /* alloc mem for buf-desc, if not already alloc'd */ + if (!priv->bd_base) { + priv->bd_base = dma_alloc_coherent(&dev->dev, + priv->bd_size, &priv->tbd_dma, + GFP_KERNEL | __GFP_DMA); + } + + if (!priv->bd_base) { + QFEC_LOG_ERR("%s: dma_alloc_coherent failed\n", __func__); + return -ENOMEM; + } + + priv->rbd_dma = priv->tbd_dma + + (priv->n_tbd * sizeof(struct qfec_buf_desc)); + + QFEC_LOG(QFEC_LOG_DBG, + " %s: 0x%08x size, %d n_tbd, %d n_rbd\n", + __func__, priv->bd_size, priv->n_tbd, priv->n_rbd); + + return 0; +} + +/* + * display buffer descriptors + */ + +static int qfec_bd_fmt(char *buf, int size, struct buf_desc *p_bd) +{ + return snprintf(buf, size, + "%8p: %08x %08x %8p %8p %8p %8p %8p %x", + p_bd, qfec_bd_status_get(p_bd), + qfec_bd_ctl_get(p_bd), qfec_bd_pbuf_get(p_bd), + qfec_bd_next_get(p_bd), qfec_bd_skbuf_get(p_bd), + qfec_bd_virt_get(p_bd), qfec_bd_phys_get(p_bd), + qfec_bd_last_bd(p_bd)); +} + +static int qfec_bd_show(char *buf, int count, struct buf_desc *p_bd, int n_bd, + struct ring *p_ring, char *label) +{ + int l = 0; + int n; + + QFEC_LOG(QFEC_LOG_DBG2, "%s: %s\n", __func__, label); + + l += snprintf(&buf[l], count, "%s: %s\n", __func__, label); + if (!p_bd) + return l; + + n_bd = n_bd > MAX_N_BD ? MAX_N_BD : n_bd; + + for (n = 0; n < n_bd; n++, p_bd++) { + l += qfec_bd_fmt(&buf[l], count - l, p_bd); + l += snprintf(&buf[l], count - l, "%s%s\n", + (qfec_ring_head(p_ring) == n ? " < h" : ""), + (qfec_ring_tail(p_ring) == n ? " < t" : "")); + } + + return l; +} + +/* + * display TX BDs + */ +static int qfec_bd_tx_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct qfec_priv *priv = netdev_priv(to_net_dev(dev)); + int count = PAGE_SIZE; + + return qfec_bd_show(buf, count, priv->p_tbd, priv->n_tbd, + &priv->ring_tbd, "TX"); +} + +/* + * display RX BDs + */ +static int qfec_bd_rx_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct qfec_priv *priv = netdev_priv(to_net_dev(dev)); + int count = PAGE_SIZE; + + return qfec_bd_show(buf, count, priv->p_rbd, priv->n_rbd, + &priv->ring_rbd, "RX"); +} + +/* + * read timestamp from buffer descriptor + * the pbuf and next fields of the buffer descriptors are overwritten + * with the timestamp high and low register values. The high register + * counts seconds, but the sub-second increment register is programmed + * with the appropriate value to increment the timestamp low register + * such that it overflows at 0x8000 0000. The low register value + * (next) must be converted to units of nano secs, * 10^9 / 2^31. + */ +static void qfec_read_timestamp(struct buf_desc *p_bd, + struct skb_shared_hwtstamps *ts) +{ + unsigned long sec = (unsigned long)qfec_bd_next_get(p_bd); + long long ns = (unsigned long)qfec_bd_pbuf_get(p_bd); + +#define BILLION 1000000000 +#define LOW_REG_BITS 31 + ns *= BILLION; + ns >>= LOW_REG_BITS; + + ts->hwtstamp = ktime_set(sec, ns); + ts->syststamp = ktime_set(sec, ns); +} + +/* + * free transmitted skbufs from buffer-descriptor no owned by HW + */ +static int qfec_tx_replenish(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + struct ring *p_ring = &priv->ring_tbd; + struct buf_desc *p_bd = &priv->p_tbd[qfec_ring_tail(p_ring)]; + struct sk_buff *skb; + unsigned long flags; + + CNTR_INC(priv, tx_replenish); + + spin_lock_irqsave(&priv->xmit_lock, flags); + + while (!qfec_ring_empty(p_ring)) { + if (qfec_bd_own(p_bd)) + break; /* done for now */ + + skb = qfec_bd_skbuf_get(p_bd); + if (unlikely(skb == NULL)) { + QFEC_LOG_ERR("%s: null sk_buff\n", __func__); + CNTR_INC(priv, tx_skb_null); + break; + } + + qfec_reg_write(priv, STATUS_REG, + STATUS_REG_TU | STATUS_REG_TI); + + /* retrieve timestamp if requested */ + if (qfec_bd_status_get(p_bd) & BUF_TX_TTSS) { + CNTR_INC(priv, ts_tx_rtn); + qfec_read_timestamp(p_bd, skb_hwtstamps(skb)); + skb_tstamp_tx(skb, skb_hwtstamps(skb)); + } + + /* update statistics before freeing skb */ + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len; + + dma_unmap_single(&dev->dev, (dma_addr_t) qfec_bd_pbuf_get(p_bd), + skb->len, DMA_TO_DEVICE); + + dev_kfree_skb_any(skb); + qfec_bd_skbuf_set(p_bd, NULL); + + qfec_ring_tail_adv(p_ring); + p_bd = &priv->p_tbd[qfec_ring_tail(p_ring)]; + } + + spin_unlock_irqrestore(&priv->xmit_lock, flags); + + qfec_queue_start(dev); + + return 0; +} + +/* + * clear ownership bits of all TX buf-desc and release the sk-bufs + */ +static void qfec_tx_timeout(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + struct buf_desc *bd = priv->p_tbd; + int n; + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + CNTR_INC(priv, tx_timeout); + + for (n = 0; n < priv->n_tbd; n++, bd++) + qfec_bd_own_clr(bd); + + qfec_tx_replenish(dev); +} + +/* + * rx() - process a received frame + */ +static void qfec_rx_int(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + struct ring *p_ring = &priv->ring_rbd; + struct buf_desc *p_bd = priv->p_latest_rbd; + uint32_t desc_status; + uint32_t mis_fr_reg; + + desc_status = qfec_bd_status_get(p_bd); + mis_fr_reg = qfec_reg_read(priv, MIS_FR_REG); + + CNTR_INC(priv, rx_int); + + /* check that valid interrupt occurred */ + if (unlikely(desc_status & BUF_OWN)) { + char s[100]; + + qfec_bd_fmt(s, sizeof(s), p_bd); + QFEC_LOG_ERR("%s: owned by DMA, %08x, %s\n", __func__, + qfec_reg_read(priv, CUR_HOST_RX_DES_REG), s); + CNTR_INC(priv, rx_owned); + return; + } + + /* accumulate missed-frame count (reg reset when read) */ + priv->stats.rx_missed_errors += mis_fr_reg + & MIS_FR_REG_MISS_CNT; + + /* process all unowned frames */ + while (!(desc_status & BUF_OWN) && (!qfec_ring_full(p_ring))) { + struct sk_buff *skb; + struct buf_desc *p_bd_next; + + skb = qfec_bd_skbuf_get(p_bd); + + if (unlikely(skb == NULL)) { + QFEC_LOG_ERR("%s: null sk_buff\n", __func__); + CNTR_INC(priv, rx_skb_null); + break; + } + + /* cache coherency before skb->data is accessed */ + dma_unmap_single(&dev->dev, + (dma_addr_t) qfec_bd_phys_get(p_bd), + ETH_BUF_SIZE, DMA_FROM_DEVICE); + prefetch(skb->data); + + if (unlikely(desc_status & BUF_RX_ES)) { + priv->stats.rx_dropped++; + CNTR_INC(priv, rx_dropped); + dev_kfree_skb(skb); + } else { + qfec_reg_write(priv, STATUS_REG, STATUS_REG_RI); + + skb->len = BUF_RX_FL_GET_FROM_STATUS(desc_status); + + if (priv->state & timestamping) { + CNTR_INC(priv, ts_rec); + qfec_read_timestamp(p_bd, skb_hwtstamps(skb)); + } + + /* update statistics before freeing skb */ + priv->stats.rx_packets++; + priv->stats.rx_bytes += skb->len; + + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_UNNECESSARY; + + if (NET_RX_DROP == netif_rx(skb)) { + priv->stats.rx_dropped++; + CNTR_INC(priv, rx_dropped); + } + CNTR_INC(priv, netif_rx_cntr); + } + + if (p_bd != priv->p_ending_rbd) + p_bd_next = p_bd + 1; + else + p_bd_next = priv->p_rbd; + desc_status = qfec_bd_status_get(p_bd_next); + + qfec_bd_skbuf_set(p_bd, NULL); + + qfec_ring_head_adv(p_ring); + p_bd = p_bd_next; + } + + priv->p_latest_rbd = p_bd; + + /* replenish bufs */ + while (!qfec_ring_empty(p_ring)) { + if (qfec_rbd_init(dev, &priv->p_rbd[qfec_ring_tail(p_ring)])) + break; + qfec_ring_tail_adv(p_ring); + } +} + +/* + * isr() - interrupt service routine + * determine cause of interrupt and invoke/schedule appropriate + * processing or error handling + */ +#define ISR_ERR_CHK(priv, status, interrupt, cntr) \ + if (status & interrupt) \ + CNTR_INC(priv, cntr) + +static irqreturn_t qfec_int(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct qfec_priv *priv = netdev_priv(dev); + uint32_t status = qfec_reg_read(priv, STATUS_REG); + uint32_t int_bits = STATUS_REG_NIS | STATUS_REG_AIS; + + QFEC_LOG(QFEC_LOG_DBG2, "%s: %s\n", __func__, dev->name); + + /* abnormal interrupt */ + if (status & STATUS_REG_AIS) { + QFEC_LOG(QFEC_LOG_DBG, "%s: abnormal status 0x%08x\n", + __func__, status); + + ISR_ERR_CHK(priv, status, STATUS_REG_RU, rx_buf_unavail); + ISR_ERR_CHK(priv, status, STATUS_REG_FBI, fatal_bus); + + ISR_ERR_CHK(priv, status, STATUS_REG_RWT, rx_watchdog); + ISR_ERR_CHK(priv, status, STATUS_REG_RPS, rx_proc_stopped); + ISR_ERR_CHK(priv, status, STATUS_REG_UNF, tx_underflow); + + ISR_ERR_CHK(priv, status, STATUS_REG_OVF, rx_overflow); + ISR_ERR_CHK(priv, status, STATUS_REG_TJT, tx_jabber_tmout); + ISR_ERR_CHK(priv, status, STATUS_REG_TPS, tx_proc_stopped); + + int_bits |= STATUS_REG_AIS_BITS; + CNTR_INC(priv, abnorm_int); + } + + if (status & STATUS_REG_NIS) + CNTR_INC(priv, norm_int); + + /* receive interrupt */ + if (status & STATUS_REG_RI) { + CNTR_INC(priv, rx_isr); + qfec_rx_int(dev); + } + + /* transmit interrupt */ + if (status & STATUS_REG_TI) { + CNTR_INC(priv, tx_isr); + qfec_tx_replenish(dev); + } + + /* gmac interrupt */ + if (status & (STATUS_REG_GPI | STATUS_REG_GMI | STATUS_REG_GLI)) { + CNTR_INC(priv, gmac_isr); + int_bits |= STATUS_REG_GMI; + } + + /* clear interrupts */ + qfec_reg_write(priv, STATUS_REG, int_bits); + CNTR_INC(priv, isr); + + return IRQ_HANDLED; +} + +/* + * open () - register system resources (IRQ, DMA, ...) + * turn on HW, perform device setup. + */ +static int qfec_open(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + struct buf_desc *p_bd; + struct ring *p_ring; + struct qfec_buf_desc *p_desc; + int n; + int res = 0; + + QFEC_LOG(QFEC_LOG_DBG, "%s: %p dev\n", __func__, dev); + + if (!dev) { + res = -EINVAL; + goto err; + } + + /* allocate TX/RX buffer-descriptors and buffers */ + + res = qfec_mem_alloc(dev); + if (res) + goto err; + + /* initialize TX */ + p_desc = priv->bd_base; + + for (n = 0, p_bd = priv->p_tbd; n < priv->n_tbd; n++, p_bd++) { + p_bd->p_desc = p_desc++; + + if (n == (priv->n_tbd - 1)) + qfec_bd_last_bd_set(p_bd); + + qfec_bd_own_clr(p_bd); /* clear ownership */ + } + + qfec_ring_init(&priv->ring_tbd, priv->n_tbd, priv->n_tbd); + + priv->tx_ic_mod = priv->n_tbd / TX_BD_TI_RATIO; + if (priv->tx_ic_mod == 0) + priv->tx_ic_mod = 1; + + /* initialize RX buffer descriptors and allocate sk_bufs */ + p_ring = &priv->ring_rbd; + qfec_ring_init(p_ring, priv->n_rbd, 0); + qfec_bd_last_bd_set(&priv->p_rbd[priv->n_rbd - 1]); + + for (n = 0, p_bd = priv->p_rbd; n < priv->n_rbd; n++, p_bd++) { + p_bd->p_desc = p_desc++; + + if (qfec_rbd_init(dev, p_bd)) + break; + qfec_ring_tail_adv(p_ring); + } + + priv->p_latest_rbd = priv->p_rbd; + priv->p_ending_rbd = priv->p_rbd + priv->n_rbd - 1; + + /* config ptp clock */ + qfec_ptp_cfg(priv); + + /* configure PHY - must be set before reset/hw_init */ + qfec_intf_sel(priv, intfc_mii); + + /* initialize controller after BDs allocated */ + res = qfec_hw_init(priv); + if (res) + goto err1; + + /* get/set (primary) MAC address */ + qfec_set_adr_regs(priv, dev->dev_addr); + + /* start phy monitor */ + QFEC_LOG(QFEC_LOG_DBG, " %s: start timer\n", __func__); + netif_carrier_off(priv->net_dev); + setup_timer(&priv->phy_tmr, qfec_phy_monitor, (unsigned long)dev); + mod_timer(&priv->phy_tmr, jiffies + HZ); + + /* initialize interrupts */ + QFEC_LOG(QFEC_LOG_DBG, " %s: request irq %d\n", __func__, dev->irq); + res = request_irq(dev->irq, qfec_int, 0, dev->name, dev); + if (res) + goto err1; + + /* enable controller */ + qfec_hw_enable(priv); + netif_start_queue(dev); + + QFEC_LOG(QFEC_LOG_DBG, "%s: %08x link, %08x carrier\n", __func__, + mii_link_ok(&priv->mii), netif_carrier_ok(priv->net_dev)); + + QFEC_LOG(QFEC_LOG_DBG, " %s: done\n", __func__); + return 0; + +err1: + qfec_mem_dealloc(dev); +err: + QFEC_LOG_ERR("%s: error - %d\n", __func__, res); + return res; +} + +/* + * stop() - "reverse operations performed at open time" + */ +static int qfec_stop(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + struct buf_desc *p_bd; + struct sk_buff *skb; + int n; + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + del_timer_sync(&priv->phy_tmr); + + qfec_hw_disable(priv); + qfec_queue_stop(dev); + free_irq(dev->irq, dev); + + /* free all pending sk_bufs */ + for (n = priv->n_rbd, p_bd = priv->p_rbd; n > 0; n--, p_bd++) { + skb = qfec_bd_skbuf_get(p_bd); + if (skb) + dev_kfree_skb(skb); + } + + for (n = priv->n_tbd, p_bd = priv->p_tbd; n > 0; n--, p_bd++) { + skb = qfec_bd_skbuf_get(p_bd); + if (skb) + dev_kfree_skb(skb); + } + + qfec_mem_dealloc(dev); + + QFEC_LOG(QFEC_LOG_DBG, " %s: done\n", __func__); + + return 0; +} + +static int qfec_set_config(struct net_device *dev, struct ifmap *map) +{ + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + return 0; +} + +/* + * pass data from skbuf to buf-desc + */ +static int qfec_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + struct ring *p_ring = &priv->ring_tbd; + struct buf_desc *p_bd; + uint32_t ctrl = 0; + int ret = NETDEV_TX_OK; + unsigned long flags; + + CNTR_INC(priv, xmit); + + spin_lock_irqsave(&priv->xmit_lock, flags); + + /* stop queuing if no resources available */ + if (qfec_ring_room(p_ring) == 0) { + qfec_queue_stop(dev); + CNTR_INC(priv, tx_no_resource); + + ret = NETDEV_TX_BUSY; + goto done; + } + + /* locate and save *sk_buff */ + p_bd = &priv->p_tbd[qfec_ring_head(p_ring)]; + qfec_bd_skbuf_set(p_bd, skb); + + /* set DMA ptr to sk_buff data and write cache to memory */ + qfec_bd_pbuf_set(p_bd, (void *) + dma_map_single(&dev->dev, + (void *)skb->data, skb->len, DMA_TO_DEVICE)); + + ctrl = skb->len; + if (!(qfec_ring_head(p_ring) % priv->tx_ic_mod)) + ctrl |= BUF_TX_IC; /* interrupt on complete */ + + /* check if timestamping enabled and requested */ + if (priv->state & timestamping) { + if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { + CNTR_INC(priv, ts_tx_en); + ctrl |= BUF_TX_IC; /* interrupt on complete */ + ctrl |= BUF_TX_TTSE; /* enable timestamp */ + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + } + } + + if (qfec_bd_last_bd(p_bd)) + ctrl |= BUF_RX_RER; + + /* no gather, no multi buf frames */ + ctrl |= BUF_TX_FS | BUF_TX_LS; /* 1st and last segment */ + + qfec_bd_ctl_wr(p_bd, ctrl); + qfec_bd_status_set(p_bd, BUF_OWN); + + qfec_ring_head_adv(p_ring); + qfec_reg_write(priv, TX_POLL_DEM_REG, 1); /* poll */ + +done: + spin_unlock_irqrestore(&priv->xmit_lock, flags); + + return ret; +} + +static int qfec_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct qfec_priv *priv = netdev_priv(dev); + struct hwtstamp_config *cfg = (struct hwtstamp_config *) ifr; + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + if (cmd == SIOCSHWTSTAMP) { + CNTR_INC(priv, ts_ioctl); + QFEC_LOG(QFEC_LOG_DBG, + "%s: SIOCSHWTSTAMP - %x flags %x tx %x rx\n", + __func__, cfg->flags, cfg->tx_type, cfg->rx_filter); + + cfg->flags = 0; + cfg->tx_type = HWTSTAMP_TX_ON; + cfg->rx_filter = HWTSTAMP_FILTER_ALL; + + priv->state |= timestamping; + qfec_reg_write(priv, TS_CTL_REG, + qfec_reg_read(priv, TS_CTL_REG) | TS_CTL_TSENALL); + + return 0; + } + + return generic_mii_ioctl(&priv->mii, if_mii(ifr), cmd, NULL); +} + +static struct net_device_stats *qfec_get_stats(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + + QFEC_LOG(QFEC_LOG_DBG2, "qfec_stats:\n"); + + return &priv->stats; +} + +/* + * accept new mac address + */ +static int qfec_set_mac_address(struct net_device *dev, void *p) +{ + struct qfec_priv *priv = netdev_priv(dev); + struct sockaddr *addr = p; + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); + + qfec_set_adr_regs(priv, dev->dev_addr); + + return 0; +} + +/* + * read discontinuous MAC address from corrected fuse memory region + */ + +static int qfec_get_mac_address(char *buf, char *mac_base, int nBytes) +{ + static int offset[] = { 0, 1, 2, 3, 4, 8 }; + int n; + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + for (n = 0; n < nBytes; n++) + buf[n] = ioread8(mac_base + offset[n]); + + /* check that MAC programmed */ + if ((buf[0] + buf[1] + buf[2] + buf[3] + buf[4] + buf[5]) == 0) { + QFEC_LOG_ERR("%s: null MAC address\n", __func__); + return -ENODATA; + } + + return 0; +} + +/* + * static definition of driver functions + */ +static const struct net_device_ops qfec_netdev_ops = { + .ndo_open = qfec_open, + .ndo_stop = qfec_stop, + .ndo_start_xmit = qfec_xmit, + + .ndo_do_ioctl = qfec_do_ioctl, + .ndo_tx_timeout = qfec_tx_timeout, + .ndo_set_mac_address = qfec_set_mac_address, + + .ndo_change_mtu = eth_change_mtu, + .ndo_validate_addr = eth_validate_addr, + + .ndo_get_stats = qfec_get_stats, + .ndo_set_config = qfec_set_config, +}; + +/* + * ethtool functions + */ + +static int qfec_nway_reset(struct net_device *dev) +{ + struct qfec_priv *priv = netdev_priv(dev); + return mii_nway_restart(&priv->mii); +} + +/* + * speed, duplex, auto-neg settings + */ +static void qfec_ethtool_getpauseparam(struct net_device *dev, + struct ethtool_pauseparam *pp) +{ + struct qfec_priv *priv = netdev_priv(dev); + u32 flow = qfec_reg_read(priv, FLOW_CONTROL_REG); + u32 advert; + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + /* report current settings */ + pp->tx_pause = (flow & FLOW_CONTROL_TFE) != 0; + pp->rx_pause = (flow & FLOW_CONTROL_RFE) != 0; + + /* report if pause is being advertised */ + advert = qfec_mdio_read(dev, priv->phy_id, MII_ADVERTISE); + pp->autoneg = + (advert & (ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM)) != 0; +} + +static int qfec_ethtool_setpauseparam(struct net_device *dev, + struct ethtool_pauseparam *pp) +{ + struct qfec_priv *priv = netdev_priv(dev); + u32 advert; + + QFEC_LOG(QFEC_LOG_DBG, "%s: %d aneg, %d rx, %d tx\n", __func__, + pp->autoneg, pp->rx_pause, pp->tx_pause); + + advert = qfec_mdio_read(dev, priv->phy_id, MII_ADVERTISE); + advert &= ~(ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); + + /* If pause autonegotiation is enabled, but both rx and tx are not + * because neither was specified in the ethtool cmd, + * enable both symetrical and asymetrical pause. + * otherwise, only enable the pause mode indicated by rx/tx. + */ + if (pp->autoneg) { + if (pp->rx_pause) + advert |= ADVERTISE_PAUSE_ASYM | ADVERTISE_PAUSE_CAP; + else if (pp->tx_pause) + advert |= ADVERTISE_PAUSE_ASYM; + else + advert |= ADVERTISE_PAUSE_CAP; + } + + qfec_mdio_write(dev, priv->phy_id, MII_ADVERTISE, advert); + + return 0; +} + +/* + * ethtool ring parameter (-g/G) support + */ + +/* + * setringparamam - change the tx/rx ring lengths + */ +#define MIN_RING_SIZE 3 +#define MAX_RING_SIZE 1000 +static int qfec_ethtool_setringparam(struct net_device *dev, + struct ethtool_ringparam *ring) +{ + struct qfec_priv *priv = netdev_priv(dev); + u32 timeout = 20; + + /* notify stack the link is down */ + netif_carrier_off(dev); + + /* allow tx to complete & free skbufs on the tx ring */ + do { + usleep_range(10000, 100000); + qfec_tx_replenish(dev); + + if (timeout-- == 0) { + QFEC_LOG_ERR("%s: timeout\n", __func__); + return -ETIME; + } + } while (!qfec_ring_empty(&priv->ring_tbd)); + + + qfec_stop(dev); + + /* set tx ring size */ + if (ring->tx_pending < MIN_RING_SIZE) + ring->tx_pending = MIN_RING_SIZE; + else if (ring->tx_pending > MAX_RING_SIZE) + ring->tx_pending = MAX_RING_SIZE; + priv->n_tbd = ring->tx_pending; + + /* set rx ring size */ + if (ring->rx_pending < MIN_RING_SIZE) + ring->rx_pending = MIN_RING_SIZE; + else if (ring->rx_pending > MAX_RING_SIZE) + ring->rx_pending = MAX_RING_SIZE; + priv->n_rbd = ring->rx_pending; + + + qfec_open(dev); + + return 0; +} + +/* + * getringparamam - returns local values + */ +static void qfec_ethtool_getringparam(struct net_device *dev, + struct ethtool_ringparam *ring) +{ + struct qfec_priv *priv = netdev_priv(dev); + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + ring->rx_max_pending = MAX_RING_SIZE; + ring->rx_mini_max_pending = 0; + ring->rx_jumbo_max_pending = 0; + ring->tx_max_pending = MAX_RING_SIZE; + + ring->rx_pending = priv->n_rbd; + ring->rx_mini_pending = 0; + ring->rx_jumbo_pending = 0; + ring->tx_pending = priv->n_tbd; +} + +/* + * speed, duplex, auto-neg settings + */ +static int +qfec_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct qfec_priv *priv = netdev_priv(dev); + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + cmd->maxrxpkt = priv->n_rbd; + cmd->maxtxpkt = priv->n_tbd; + + return mii_ethtool_gset(&priv->mii, cmd); +} + +static int +qfec_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct qfec_priv *priv = netdev_priv(dev); + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + return mii_ethtool_sset(&priv->mii, cmd); +} + +/* + * msg/debug level + */ +static u32 qfec_ethtool_getmsglevel(struct net_device *dev) +{ + return qfec_debug; +} + +static void qfec_ethtool_setmsglevel(struct net_device *dev, u32 level) +{ + qfec_debug ^= level; /* toggle on/off */ +} + +/* + * register dump + */ +#define DMA_DMP_OFFSET 0x0000 +#define DMA_REG_OFFSET 0x1000 +#define DMA_REG_LEN 23 + +#define MAC_DMP_OFFSET 0x0080 +#define MAC_REG_OFFSET 0x0000 +#define MAC_REG_LEN 55 + +#define TS_DMP_OFFSET 0x0180 +#define TS_REG_OFFSET 0x0700 +#define TS_REG_LEN 15 + +#define MDIO_DMP_OFFSET 0x0200 +#define MDIO_REG_LEN 16 + +#define REG_SIZE (MDIO_DMP_OFFSET + (MDIO_REG_LEN * sizeof(short))) + +static int qfec_ethtool_getregs_len(struct net_device *dev) +{ + return REG_SIZE; +} + +static void +qfec_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs, + void *buf) +{ + struct qfec_priv *priv = netdev_priv(dev); + u32 *data = buf; + u16 *data16; + unsigned int i; + unsigned int j; + unsigned int n; + + memset(buf, 0, REG_SIZE); + + j = DMA_DMP_OFFSET / sizeof(u32); + for (i = DMA_REG_OFFSET, n = DMA_REG_LEN; n--; i += sizeof(u32)) + data[j++] = htonl(qfec_reg_read(priv, i)); + + j = MAC_DMP_OFFSET / sizeof(u32); + for (i = MAC_REG_OFFSET, n = MAC_REG_LEN; n--; i += sizeof(u32)) + data[j++] = htonl(qfec_reg_read(priv, i)); + + j = TS_DMP_OFFSET / sizeof(u32); + for (i = TS_REG_OFFSET, n = TS_REG_LEN; n--; i += sizeof(u32)) + data[j++] = htonl(qfec_reg_read(priv, i)); + + data16 = (u16 *)&data[MDIO_DMP_OFFSET / sizeof(u32)]; + for (i = 0, n = 0; i < MDIO_REG_LEN; i++) + data16[n++] = htons(qfec_mdio_read(dev, 0, i)); + + regs->len = REG_SIZE; + + QFEC_LOG(QFEC_LOG_DBG, "%s: %d bytes\n", __func__, regs->len); +} + +/* + * statistics + * return counts of various ethernet activity. + * many of these are same as in struct net_device_stats + * + * missed-frames indicates the number of attempts made by the ethernet + * controller to write to a buffer-descriptor when the BD ownership + * bit was not set. The rxfifooverflow counter (0x1D4) is not + * available. The Missed Frame and Buffer Overflow Counter register + * (0x1020) is used, but has only 16-bits and is reset when read. + * It is read and updates the value in priv->stats.rx_missed_errors + * in qfec_rx_int(). + */ +static char qfec_stats_strings[][ETH_GSTRING_LEN] = { + "TX good/bad Bytes ", + "TX Bytes ", + "TX good/bad Frames ", + "TX Bcast Frames ", + "TX Mcast Frames ", + "TX Unicast Frames ", + "TX Pause Frames ", + "TX Vlan Frames ", + "TX Frames 64 ", + "TX Frames 65-127 ", + "TX Frames 128-255 ", + "TX Frames 256-511 ", + "TX Frames 512-1023 ", + "TX Frames 1024+ ", + "TX Pause Frames ", + "TX Collisions ", + "TX Late Collisions ", + "TX Excessive Collisions ", + + "RX good/bad Bytes ", + "RX Bytes ", + "RX good/bad Frames ", + "RX Bcast Frames ", + "RX Mcast Frames ", + "RX Unicast Frames ", + "RX Pause Frames ", + "RX Vlan Frames ", + "RX Frames 64 ", + "RX Frames 65-127 ", + "RX Frames 128-255 ", + "RX Frames 256-511 ", + "RX Frames 512-1023 ", + "RX Frames 1024+ ", + "RX Pause Frames ", + "RX Crc error Frames ", + "RX Length error Frames ", + "RX Alignment error Frames ", + "RX Runt Frames ", + "RX Oversize Frames ", + "RX Missed Frames ", + +}; + +static u32 qfec_stats_regs[] = { + + 69, 89, 70, 71, 72, 90, 92, 93, + 73, 74, 75, 76, 77, 78, 92, 84, + 86, 87, + + 97, 98, 96, 99, 100, 113, 116, 118, + 107, 108, 109, 110, 111, 112, 116, 101, + 114, 102, 103, 106 +}; + +static int qfec_stats_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct qfec_priv *priv = netdev_priv(to_net_dev(dev)); + int count = PAGE_SIZE; + int l = 0; + int n; + + QFEC_LOG(QFEC_LOG_DBG2, "%s:\n", __func__); + + for (n = 0; n < ARRAY_SIZE(qfec_stats_regs); n++) { + l += snprintf(&buf[l], count - l, " %12u %s\n", + qfec_reg_read(priv, + qfec_stats_regs[n] * sizeof(uint32_t)), + qfec_stats_strings[n]); + } + + return l; +} + +static int qfec_get_sset_count(struct net_device *dev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(qfec_stats_regs) + 1; /* missed frames */ + + default: + return -EOPNOTSUPP; + } +} + +static void qfec_ethtool_getstrings(struct net_device *dev, u32 stringset, + u8 *buf) +{ + QFEC_LOG(QFEC_LOG_DBG, "%s: %d bytes\n", __func__, + sizeof(qfec_stats_strings)); + + memcpy(buf, qfec_stats_strings, sizeof(qfec_stats_strings)); +} + +static void qfec_ethtool_getstats(struct net_device *dev, + struct ethtool_stats *stats, uint64_t *data) +{ + struct qfec_priv *priv = netdev_priv(dev); + int j = 0; + int n; + + for (n = 0; n < ARRAY_SIZE(qfec_stats_regs); n++) + data[j++] = qfec_reg_read(priv, + qfec_stats_regs[n] * sizeof(uint32_t)); + + data[j++] = priv->stats.rx_missed_errors; + + stats->n_stats = j; +} + +static void qfec_ethtool_getdrvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, QFEC_NAME, sizeof(info->driver)); + strlcpy(info->version, QFEC_DRV_VER, sizeof(info->version)); + strlcpy(info->bus_info, dev_name(dev->dev.parent), + sizeof(info->bus_info)); + + info->eedump_len = 0; + info->regdump_len = qfec_ethtool_getregs_len(dev); +} + +/* + * ethtool ops table + */ +static const struct ethtool_ops qfec_ethtool_ops = { + .nway_reset = qfec_nway_reset, + + .get_settings = qfec_ethtool_getsettings, + .set_settings = qfec_ethtool_setsettings, + .get_link = ethtool_op_get_link, + .get_drvinfo = qfec_ethtool_getdrvinfo, + .get_msglevel = qfec_ethtool_getmsglevel, + .set_msglevel = qfec_ethtool_setmsglevel, + .get_regs_len = qfec_ethtool_getregs_len, + .get_regs = qfec_ethtool_getregs, + + .get_ringparam = qfec_ethtool_getringparam, + .set_ringparam = qfec_ethtool_setringparam, + + .get_pauseparam = qfec_ethtool_getpauseparam, + .set_pauseparam = qfec_ethtool_setpauseparam, + + .get_sset_count = qfec_get_sset_count, + .get_strings = qfec_ethtool_getstrings, + .get_ethtool_stats = qfec_ethtool_getstats, +}; + +/* + * create sysfs entries + */ +static DEVICE_ATTR(bd_tx, 0444, qfec_bd_tx_show, NULL); +static DEVICE_ATTR(bd_rx, 0444, qfec_bd_rx_show, NULL); +static DEVICE_ATTR(cfg, 0444, qfec_config_show, NULL); +static DEVICE_ATTR(clk_reg, 0444, qfec_clk_reg_show, NULL); +static DEVICE_ATTR(cntrs, 0444, qfec_cntrs_show, NULL); +static DEVICE_ATTR(stats, 0444, qfec_stats_show, NULL); +static DEVICE_ATTR(reg, 0444, qfec_reg_show, NULL); + +static void qfec_sysfs_create(struct net_device *dev) +{ + if (device_create_file(&(dev->dev), &dev_attr_bd_tx) || + device_create_file(&(dev->dev), &dev_attr_bd_rx) || + device_create_file(&(dev->dev), &dev_attr_cfg) || + device_create_file(&(dev->dev), &dev_attr_clk_reg) || + device_create_file(&(dev->dev), &dev_attr_cntrs) || + device_create_file(&(dev->dev), &dev_attr_reg) || + device_create_file(&(dev->dev), &dev_attr_stats)) + pr_err("qfec_sysfs_create failed to create sysfs files\n"); +} + +/* + * map a specified resource + */ +static int qfec_map_resource(struct platform_device *plat, int resource, + struct resource **priv_res, + void **addr) +{ + struct resource *res; + + QFEC_LOG(QFEC_LOG_DBG, "%s: 0x%x resource\n", __func__, resource); + + /* allocate region to access controller registers */ + *priv_res = res = platform_get_resource(plat, resource, 0); + if (!res) { + QFEC_LOG_ERR("%s: platform_get_resource failed\n", __func__); + return -ENODEV; + } + + res = request_mem_region(res->start, res->end - res->start, QFEC_NAME); + if (!res) { + QFEC_LOG_ERR("%s: request_mem_region failed, %08x %08x\n", + __func__, res->start, res->end - res->start); + return -EBUSY; + } + + *addr = ioremap(res->start, res->end - res->start); + if (!*addr) + return -ENOMEM; + + QFEC_LOG(QFEC_LOG_DBG, " %s: io mapped from %p to %p\n", + __func__, (void *)res->start, *addr); + + return 0; +}; + +/* + * free allocated io regions + */ +static void qfec_free_res(struct resource *res, void *base) +{ + + if (res) { + if (base) + iounmap((void __iomem *)base); + + release_mem_region(res->start, res->end - res->start); + } +}; + +/* + * probe function that obtain configuration info and allocate net_device + */ +static int __devinit qfec_probe(struct platform_device *plat) +{ + struct net_device *dev; + struct qfec_priv *priv; + int ret = 0; + + /* allocate device */ + dev = alloc_etherdev(sizeof(struct qfec_priv)); + if (!dev) { + QFEC_LOG_ERR("%s: alloc_etherdev failed\n", __func__); + ret = -ENOMEM; + goto err; + } + + QFEC_LOG(QFEC_LOG_DBG, "%s: %08x dev\n", __func__, (int)dev); + + qfec_dev = dev; + SET_NETDEV_DEV(dev, &plat->dev); + + dev->netdev_ops = &qfec_netdev_ops; + dev->ethtool_ops = &qfec_ethtool_ops; + dev->watchdog_timeo = 2 * HZ; + dev->irq = platform_get_irq(plat, 0); + + dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + + /* initialize private data */ + priv = (struct qfec_priv *)netdev_priv(dev); + memset((void *)priv, 0, sizeof(priv)); + + priv->net_dev = dev; + platform_set_drvdata(plat, dev); + + priv->n_tbd = TX_BD_NUM; + priv->n_rbd = RX_BD_NUM; + + /* initialize phy structure */ + priv->mii.phy_id_mask = 0x1F; + priv->mii.reg_num_mask = 0x1F; + priv->mii.dev = dev; + priv->mii.mdio_read = qfec_mdio_read; + priv->mii.mdio_write = qfec_mdio_write; + + /* map register regions */ + ret = qfec_map_resource( + plat, IORESOURCE_MEM, &priv->mac_res, &priv->mac_base); + if (ret) { + QFEC_LOG_ERR("%s: IORESOURCE_MEM mac failed\n", __func__); + goto err1; + } + + ret = qfec_map_resource( + plat, IORESOURCE_IO, &priv->clk_res, &priv->clk_base); + if (ret) { + QFEC_LOG_ERR("%s: IORESOURCE_IO clk failed\n", __func__); + goto err2; + } + + ret = qfec_map_resource( + plat, IORESOURCE_DMA, &priv->fuse_res, &priv->fuse_base); + if (ret) { + QFEC_LOG_ERR("%s: IORESOURCE_DMA fuse failed\n", __func__); + goto err3; + } + + /* initialize MAC addr */ + ret = qfec_get_mac_address(dev->dev_addr, priv->fuse_base, + MAC_ADDR_SIZE); + if (ret) + goto err4; + + QFEC_LOG(QFEC_LOG_DBG, "%s: mac %02x:%02x:%02x:%02x:%02x:%02x\n", + __func__, + dev->dev_addr[0], dev->dev_addr[1], + dev->dev_addr[2], dev->dev_addr[3], + dev->dev_addr[4], dev->dev_addr[5]); + + ret = register_netdev(dev); + if (ret) { + QFEC_LOG_ERR("%s: register_netdev failed\n", __func__); + goto err4; + } + + spin_lock_init(&priv->mdio_lock); + spin_lock_init(&priv->xmit_lock); + qfec_sysfs_create(dev); + + return 0; + + /* error handling */ +err4: + qfec_free_res(priv->fuse_res, priv->fuse_base); +err3: + qfec_free_res(priv->clk_res, priv->clk_base); +err2: + qfec_free_res(priv->mac_res, priv->mac_base); +err1: + free_netdev(dev); +err: + QFEC_LOG_ERR("%s: err\n", __func__); + return ret; +} + +/* + * module remove + */ +static int __devexit qfec_remove(struct platform_device *plat) +{ + struct net_device *dev = platform_get_drvdata(plat); + struct qfec_priv *priv = netdev_priv(dev); + + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + platform_set_drvdata(plat, NULL); + + qfec_free_res(priv->fuse_res, priv->fuse_base); + qfec_free_res(priv->clk_res, priv->clk_base); + qfec_free_res(priv->mac_res, priv->mac_base); + + unregister_netdev(dev); + free_netdev(dev); + + return 0; +} + +/* + * module support + * the FSM9xxx is not a mobile device does not support power management + */ + +static struct platform_driver qfec_driver = { + .probe = qfec_probe, + .remove = __devexit_p(qfec_remove), + .driver = { + .name = QFEC_NAME, + .owner = THIS_MODULE, + }, +}; + +/* + * module init + */ +static int __init qfec_init_module(void) +{ + int res; + + QFEC_LOG(QFEC_LOG_DBG, "%s: %s\n", __func__, qfec_driver.driver.name); + + res = platform_driver_register(&qfec_driver); + + QFEC_LOG(QFEC_LOG_DBG, "%s: %d - platform_driver_register\n", + __func__, res); + + return res; +} + +/* + * module exit + */ +static void __exit qfec_exit_module(void) +{ + QFEC_LOG(QFEC_LOG_DBG, "%s:\n", __func__); + + platform_driver_unregister(&qfec_driver); +} + +MODULE_DESCRIPTION("FSM Network Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Rohit Vaswani "); +MODULE_VERSION("1.0"); + +module_init(qfec_init_module); +module_exit(qfec_exit_module); diff --git a/drivers/net/qfec.h b/drivers/net/qfec.h new file mode 100644 index 00000000000..63288048640 --- /dev/null +++ b/drivers/net/qfec.h @@ -0,0 +1,793 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* qualcomm fast Ethernet controller HW description */ + +#ifndef _QFEC_EMAC_H_ +# define _QFEC_EMAC_H_ + +# ifndef __KERNEL__ +# include "stdint.h" +# endif + +# define MskBits(nBits, pos) (((1 << nBits)-1)<> 16) +# define BUF_RX_FL_SET(p, x) \ + (p.status = (p.status & ~BUF_RX_FL) | ((x << 16) & BUF_RX_FL)) +# define BUF_RX_FL_GET_FROM_STATUS(status) \ + (((status) & BUF_RX_FL) >> 16) + +# define BUF_RX_ES 0x00008000 /* error summary */ +# define BUF_RX_DE 0x00004000 /* error descriptor (es) */ +# define BUF_RX_SAF 0x00002000 /* source addr filt fail */ +# define BUF_RX_LE 0x00001000 /* length error */ + +# define BUF_RX_OE 0x00000800 /* overflow error (es) */ +# define BUF_RX_VLAN 0x00000400 /* vlan tag */ +# define BUF_RX_FS 0x00000200 /* first descriptor */ +# define BUF_RX_LS 0x00000100 /* last descriptor */ + +# define BUF_RX_IPC 0x00000080 /* cksum-err/giant-frame (es) */ +# define BUF_RX_LC 0x00000040 /* late collision (es) */ +# define BUF_RX_FT 0x00000020 /* frame type */ +# define BUF_RX_RWT 0x00000010 /* rec watchdog timeout (es) */ + +# define BUF_RX_RE 0x00000008 /* rec error (es) */ +# define BUF_RX_DBE 0x00000004 /* dribble bit err */ +# define BUF_RX_CE 0x00000002 /* crc err (es) */ +# define BUF_RX_CSE 0x00000001 /* checksum err */ + +# define BUF_RX_ERRORS \ + (BUF_RX_DE | BUF_RX_SAF | BUF_RX_LE | BUF_RX_OE \ + | BUF_RX_IPC | BUF_RX_LC | BUF_RX_RWT | BUF_RX_RE \ + | BUF_RX_DBE | BUF_RX_CE | BUF_RX_CSE) + +/* RX buffer control bits */ +# define BUF_RX_DI 0x80000000 /* disable intrp on compl */ +# define BUF_RX_RER 0x02000000 /* rec end of ring */ +# define BUF_RX_RCH 0x01000000 /* 2nd addr chained */ + +# define BUF_RX_SIZ2 0x003ff800 /* buffer 2 size */ +# define BUF_RX_SIZ2_GET(p) ((p.control&BUF_RX_SIZ2) >> 11) + +# define BUF_RX_SIZ 0x000007ff /* rx buf 1 size */ +# define BUF_RX_SIZ_GET(p) (p.ctl&BUF_RX_SIZ) + +/* TX buffer status bits */ +# define BUF_TX_TTSS 0x00020000 /* time stamp status */ +# define BUF_TX_IHE 0x00010000 /* IP hdr err */ + +# define BUF_TX_ES 0x00008000 /* error summary */ +# define BUF_TX_JT 0x00004000 /* jabber timeout (es) */ +# define BUF_TX_FF 0x00002000 /* frame flushed (es) */ +# define BUF_TX_PCE 0x00001000 /* payld cksum err */ + +# define BUF_TX_LOC 0x00000800 /* loss carrier (es) */ +# define BUF_TX_NC 0x00000400 /* no carrier (es) */ +# define BUF_TX_LC 0x00000200 /* late collision (es) */ +# define BUF_TX_EC 0x00000100 /* excessive collision (es) */ + +# define BUF_TX_VLAN 0x00000080 /* VLAN frame */ +# define BUF_TX_CC MskBits(4, 3) /* collision count */ +# define BUF_TX_CC_GET(p) ((p.status&BUF_TX_CC)>>3) + +# define BUF_TX_ED 0x00000004 /* excessive deferral (es) */ +# define BUF_TX_UF 0x00000002 /* underflow err (es) */ +# define BUF_TX_DB 0x00000001 /* deferred bit */ + +/* TX buffer control bits */ +# define BUF_TX_IC 0x80000000 /* intrpt on compl */ +# define BUF_TX_LS 0x40000000 /* last segment */ +# define BUF_TX_FS 0x20000000 /* first segment */ +# define BUF_TX_CIC 0x18000000 /* cksum insert control */ +# define BUF_TX_CIC_SET(n) (BUF_TX_CIC&(n<<27)) + +# define BUF_TX_DC 0x04000000 /* disable CRC */ +# define BUF_TX_TER 0x02000000 /* end of ring */ +# define BUF_TX_TCH 0x01000000 /* 2nd addr chained */ + +# define BUF_TX_DP 0x00800000 /* disable padding */ +# define BUF_TX_TTSE 0x00400000 /* timestamp enable */ + +# define BUF_TX_SIZ2 0x003ff800 /* buffer 2 size */ +# define BUF_TX_SIZ2_SET(n) (BUF_TX_SIZ2(n<<11)) + +# define BUF_TX_SIZ 0x000007ff /* buffer 1 size */ +# define BUF_TX_SIZ_SET(n) (BUF_TX_SI1 & n) + + +/* Ethernet Controller Registers */ +# define BUS_MODE_REG 0x1000 + +# define BUS_MODE_MB 0x04000000 /* mixed burst */ +# define BUS_MODE_AAL 0x02000000 /* address alignment beats */ +# define BUS_MODE_8XPBL 0x01000000 /* */ + +# define BUS_MODE_USP 0x00800000 /* use separate PBL */ +# define BUS_MODE_RPBL 0x007e0000 /* rxDMA PBL */ +# define BUS_MODE_FB 0x00010000 /* fixed burst */ + +# define BUS_MODE_PR 0x0000c000 /* tx/rx priority */ +# define BUS_MODE_PR4 0x0000c000 /* tx/rx priority 4:1 */ +# define BUS_MODE_PR3 0x00008000 /* tx/rx priority 3:1 */ +# define BUS_MODE_PR2 0x00004000 /* tx/rx priority 2:1 */ +# define BUS_MODE_PR1 0x00000000 /* tx/rx priority 1:1 */ + +# define BUS_MODE_PBL 0x00003f00 /* programmable burst length */ +# define BUS_MODE_PBLSET(n) (BUS_MODE_PBL&(n<<8)) + +# define BUS_MODE_DSL 0x0000007c /* descriptor skip length */ +# define BUS_MODE_DSL_SET(n) (BUS_MODE_DSL & (n << 2)) + +# define BUS_MODE_DA 0x00000002 /* DMA arbitration scheme */ +# define BUS_MODE_SWR 0x00000001 /* software reset */ + +#define BUS_MODE_REG_DEFAULT (BUS_MODE_FB \ + | BUS_MODE_AAL \ + | BUS_MODE_PBLSET(16) \ + | BUS_MODE_DA \ + | BUS_MODE_DSL_SET(0)) + +# define TX_POLL_DEM_REG 0x1004 /* transmit poll demand */ +# define RX_POLL_DEM_REG 0x1008 /* receive poll demand */ + +# define RX_DES_LST_ADR_REG 0x100c /* receive buffer descriptor */ +# define TX_DES_LST_ADR_REG 0x1010 /* transmit buffer descriptor */ + +# define STATUS_REG 0x1014 + +# define STATUS_REG_RSVRD_1 0xc0000000 /* reserved */ +# define STATUS_REG_TTI 0x20000000 /* time-stamp trigger intrpt */ +# define STATUS_REG_GPI 0x10000000 /* gmac PMT interrupt */ + +# define STATUS_REG_GMI 0x08000000 /* gmac MMC interrupt */ +# define STATUS_REG_GLI 0x04000000 /* gmac line interface intrpt */ + +# define STATUS_REG_EB 0x03800000 /* error bits */ +# define STATUS_REG_EB_DATA 0x00800000 /* error during data transfer */ +# define STATUS_REG_EB_RDWR 0x01000000 /* error during rd/wr transfer */ +# define STATUS_REG_EB_DESC 0x02000000 /* error during desc access */ + +# define STATUS_REG_TS 0x00700000 /* transmit process state */ + +# define STATUS_REG_TS_STOP 0x00000000 /* stopped */ +# define STATUS_REG_TS_FETCH_DESC 0x00100000 /* fetching descriptor */ +# define STATUS_REG_TS_WAIT 0x00200000 /* waiting for status */ +# define STATUS_REG_TS_READ 0x00300000 /* reading host memory */ +# define STATUS_REG_TS_TIMESTAMP 0x00400000 /* timestamp write status */ +# define STATUS_REG_TS_RSVRD 0x00500000 /* reserved */ +# define STATUS_REG_TS_SUSPEND 0x00600000 /* desc-unavail/buffer-unflw */ +# define STATUS_REG_TS_CLOSE 0x00700000 /* closing desc */ + +# define STATUS_REG_RS 0x000e0000 /* receive process state */ + +# define STATUS_REG_RS_STOP 0x00000000 /* stopped */ +# define STATUS_REG_RS_FETCH_DESC 0x00020000 /* fetching descriptor */ +# define STATUS_REG_RS_RSVRD_1 0x00040000 /* reserved */ +# define STATUS_REG_RS_WAIT 0x00060000 /* waiting for packet */ +# define STATUS_REG_RS_SUSPEND 0x00080000 /* desc unavail */ +# define STATUS_REG_RS_CLOSE 0x000a0000 /* closing desc */ +# define STATUS_REG_RS_TIMESTAMP 0x000c0000 /* timestamp write status */ +# define STATUS_REG_RS_RSVRD_2 0x000e0000 /* writing host memory */ + +# define STATUS_REG_NIS 0x00010000 /* normal intrpt 14|6|2|0 */ +# define STATUS_REG_AIS 0x00008000 /* intrpts 13|10|9|8|7|5|4|3|1 */ + +# define STATUS_REG_ERI 0x00004000 /* early receive interrupt */ +# define STATUS_REG_FBI 0x00002000 /* fatal bus error interrupt */ +# define STATUS_REG_RSVRD_2 0x00001800 /* reserved */ + +# define STATUS_REG_ETI 0x00000400 /* early transmit interrupt */ +# define STATUS_REG_RWT 0x00000200 /* receive watchdog timeout */ +# define STATUS_REG_RPS 0x00000100 /* receive process stopped */ + +# define STATUS_REG_RU 0x00000080 /* receive buffer unavailable */ +# define STATUS_REG_RI 0x00000040 /* receive interrupt */ +# define STATUS_REG_UNF 0x00000020 /* transmit underflow */ +# define STATUS_REG_OVF 0x00000010 /* receive overflow */ + +# define STATUS_REG_TJT 0x00000008 /* transmit jabber timeout */ +# define STATUS_REG_TU 0x00000004 /* transmit buffer unavailable */ +# define STATUS_REG_TPS 0x00000002 /* transmit process stopped */ +# define STATUS_REG_TI 0x00000001 /* transmit interrupt */ + +# define STATUS_REG_AIS_BITS (STATUS_REG_FBI | STATUS_REG_ETI \ + | STATUS_REG_RWT | STATUS_REG_RPS \ + | STATUS_REG_RU | STATUS_REG_UNF \ + | STATUS_REG_OVF | STATUS_REG_TJT \ + | STATUS_REG_TPS | STATUS_REG_AIS) + +# define OPER_MODE_REG 0x1018 + +# define OPER_MODE_REG_DT 0x04000000 /* disab drop ip cksum err fr */ +# define OPER_MODE_REG_RSF 0x02000000 /* rec store and forward */ +# define OPER_MODE_REG_DFF 0x01000000 /* disable flush of rec frames */ + +# define OPER_MODE_REG_RFA2 0x00800000 /* thresh MSB for act flow-ctl */ +# define OPER_MODE_REG_RFD2 0x00400000 /* thresh MSB deAct flow-ctl */ +# define OPER_MODE_REG_TSF 0x00200000 /* tx store and forward */ +# define OPER_MODE_REG_FTF 0x00100000 /* flush tx FIFO */ + +# define OPER_MODE_REG_RSVD1 0x000e0000 /* reserved */ +# define OPER_MODE_REG_TTC 0x0001c000 /* transmit threshold control */ +# define OPER_MODE_REG_TTC_SET(x) (OPER_MODE_REG_TTC & (x << 14)) +# define OPER_MODE_REG_ST 0x00002000 /* start/stop transmission cmd */ + +# define OPER_MODE_REG_RFD 0x00001800 /* thresh for deAct flow-ctl */ +# define OPER_MODE_REG_RFA 0x00000600 /* threshold for act flow-ctl */ +# define OPER_MODE_REG_EFC 0x00000100 /* enable HW flow-ctl */ + +# define OPER_MODE_REG_FEF 0x00000080 /* forward error frames */ +# define OPER_MODE_REG_FUF 0x00000040 /* forward undersize good fr */ +# define OPER_MODE_REG_RSVD2 0x00000020 /* reserved */ +# define OPER_MODE_REG_RTC 0x00000018 /* receive threshold control */ +# define OPER_MODE_REG_RTC_SET(x) (OPER_MODE_REG_RTC & (x << 3)) + +# define OPER_MODE_REG_OSF 0x00000004 /* operate on second frame */ +# define OPER_MODE_REG_SR 0x00000002 /* start/stop receive */ +# define OPER_MODE_REG_RSVD3 0x00000001 /* reserved */ + + +#define OPER_MODE_REG_DEFAULT (OPER_MODE_REG_RSF \ + | OPER_MODE_REG_TSF \ + | OPER_MODE_REG_TTC_SET(5) \ + | OPER_MODE_REG_RTC_SET(1) \ + | OPER_MODE_REG_OSF) + +# define INTRP_EN_REG 0x101c + +# define INTRP_EN_REG_RSVD1 0xfffc0000 /* */ +# define INTRP_EN_REG_NIE 0x00010000 /* normal intrpt summ enable */ + +# define INTRP_EN_REG_AIE 0x00008000 /* abnormal intrpt summary en */ +# define INTRP_EN_REG_ERE 0x00004000 /* early receive intrpt enable */ +# define INTRP_EN_REG_FBE 0x00002000 /* fatal bus error enable */ + +# define INTRP_EN_REG_RSVD2 0x00001800 /* */ + +# define INTRP_EN_REG_ETE 0x00000400 /* early tx intrpt enable */ +# define INTRP_EN_REG_RWE 0x00000200 /* rx watchdog timeout enable */ +# define INTRP_EN_REG_RSE 0x00000100 /* rx stopped enable */ + +# define INTRP_EN_REG_RUE 0x00000080 /* rx buf unavailable enable */ +# define INTRP_EN_REG_RIE 0x00000040 /* rx interrupt enable */ +# define INTRP_EN_REG_UNE 0x00000020 /* underflow interrupt enable */ +# define INTRP_EN_REG_OVE 0x00000010 /* overflow interrupt enable */ + +# define INTRP_EN_REG_TJE 0x00000008 /* tx jabber timeout enable */ +# define INTRP_EN_REG_TUE 0x00000004 /* tx buf unavailable enable */ +# define INTRP_EN_REG_TSE 0x00000002 /* tx stopped enable */ +# define INTRP_EN_REG_TIE 0x00000001 /* tx interrupt enable */ + +# define INTRP_EN_REG_All (~(INTRP_EN_REG_RSVD1)) + +# define MIS_FR_REG 0x1020 + +# define MIS_FR_REG_FIFO_OVFL 0x10000000 /* fifo overflow */ +# define MIS_FR_REG_FIFO_CNT 0x0FFE0000 /* fifo cnt */ + +# define MIS_FR_REG_MISS_OVFL 0x00010000 /* missed-frame overflow */ +# define MIS_FR_REG_MISS_CNT 0x0000FFFF /* missed-frame cnt */ + +# define RX_INTRP_WTCHDOG_REG 0x1024 +# define AXI_BUS_MODE_REG 0x1028 + +# define AXI_BUS_MODE_EN_LPI 0x80000000 /* enable low power interface */ +# define AXI_BUS_MODE_UNLK_MGC_PKT 0x40000000 /* unlock-magic-pkt/rem-wk-up */ +# define AXI_BUS_MODE_WR_OSR_LMT 0x00F00000 /* max wr out stndg req limit */ +# define AXI_BUS_MODE_RD_OSR_LMT 0x000F0000 /* max rd out stndg req limit */ +# define AXI_BUS_MODE_AXI_AAL 0x00001000 /* address aligned beats */ +# define AXI_BUS_MODE_BLEN256 0x00000080 /* axi burst length 256 */ +# define AXI_BUS_MODE_BLEN128 0x00000040 /* axi burst length 128 */ +# define AXI_BUS_MODE_BLEN64 0x00000020 /* axi burst length 64 */ +# define AXI_BUS_MODE_BLEN32 0x00000010 /* axi burst length 32 */ +# define AXI_BUS_MODE_BLEN16 0x00000008 /* axi burst length 16 */ +# define AXI_BUS_MODE_BLEN8 0x00000004 /* axi burst length 8 */ +# define AXI_BUS_MODE_BLEN4 0x00000002 /* axi burst length 4 */ +# define AXI_BUS_MODE_UNDEF 0x00000001 /* axi undef burst length */ + +#define AXI_BUS_MODE_DEFAULT (AXI_BUS_MODE_WR_OSR_LMT \ + | AXI_BUS_MODE_RD_OSR_LMT \ + | AXI_BUS_MODE_BLEN16 \ + | AXI_BUS_MODE_BLEN8 \ + | AXI_BUS_MODE_BLEN4) + +# define AXI_STATUS_REG 0x102c + +/* 0x1030-0x1044 reserved */ +# define CUR_HOST_TX_DES_REG 0x1048 +# define CUR_HOST_RX_DES_REG 0x104c +# define CUR_HOST_TX_BU_ADR_REG 0x1050 +# define CUR_HOST_RX_BU_ADR_REG 0x1054 + +# define HW_FEATURE_REG 0x1058 + +# define MAC_CONFIG_REG 0x0000 + +# define MAC_CONFIG_REG_RSVD1 0xf8000000 /* */ + +# define MAC_CONFIG_REG_SFTERR 0x04000000 /* smii force tx error */ +# define MAC_CONFIG_REG_CST 0x02000000 /* crc strip for type frame */ +# define MAC_CONFIG_REG_TC 0x01000000 /* tx cfg in rgmii/sgmii/smii */ + +# define MAC_CONFIG_REG_WD 0x00800000 /* watchdog disable */ +# define MAC_CONFIG_REG_JD 0x00400000 /* jabber disable */ +# define MAC_CONFIG_REG_BE 0x00200000 /* frame burst enable */ +# define MAC_CONFIG_REG_JE 0x00100000 /* jumbo frame enable */ + +# define MAC_CONFIG_REG_IFG 0x000e0000 /* inter frame gap, 96-(8*n) */ +# define MAC_CONFIG_REG_DCRS 0x00010000 /* dis carrier sense during tx */ + +# define MAC_CONFIG_REG_PS 0x00008000 /* port select: 0/1 g/(10/100) */ +# define MAC_CONFIG_REG_FES 0x00004000 /* speed 100 mbps */ +# define MAC_CONFIG_REG_SPD (MAC_CONFIG_REG_PS | MAC_CONFIG_REG_FES) +# define MAC_CONFIG_REG_SPD_1G (0) +# define MAC_CONFIG_REG_SPD_100 (MAC_CONFIG_REG_PS | MAC_CONFIG_REG_FES) +# define MAC_CONFIG_REG_SPD_10 (MAC_CONFIG_REG_PS) +# define MAC_CONFIG_REG_SPD_SET(x) (MAC_CONFIG_REG_PS_FES & (x << 14)) + +# define MAC_CONFIG_REG_DO 0x00002000 /* disable receive own */ +# define MAC_CONFIG_REG_LM 0x00001000 /* loopback mode */ + +# define MAC_CONFIG_REG_DM 0x00000800 /* (full) duplex mode */ +# define MAC_CONFIG_REG_IPC 0x00000400 /* checksum offload */ +# define MAC_CONFIG_REG_DR 0x00000200 /* disable retry */ +# define MAC_CONFIG_REG_LUD 0x00000100 /* link up/down */ + +# define MAC_CONFIG_REG_ACS 0x00000080 /* auto pad/crc stripping */ +# define MAC_CONFIG_REG_BL 0x00000060 /* back-off limit */ +# define MAC_CONFIG_REG_BL_10 0x00000000 /* 10 */ +# define MAC_CONFIG_REG_BL_8 0x00000020 /* 8 */ +# define MAC_CONFIG_REG_BL_4 0x00000040 /* 4 */ +# define MAC_CONFIG_REG_BL_1 0x00000060 /* 1 */ +# define MAC_CONFIG_REG_DC 0x00000010 /* deferral check */ + +# define MAC_CONFIG_REG_TE 0x00000008 /* transmitter enable */ +# define MAC_CONFIG_REG_RE 0x00000004 /* receiver enable */ +# define MAC_CONFIG_REG_RSVD2 0x00000003 /* */ + +# define MAC_FR_FILTER_REG 0x0004 + +# define MAC_FR_FILTER_RA 0x80000000 /* receive all */ + +# define MAC_FR_FILTER_HPF 0x00000400 /* hash or perfect filter */ +# define MAC_FR_FILTER_SAF 0x00000200 /* source addr filt en */ +# define MAC_FR_FILTER_SAIF 0x00000100 /* SA inverse filter */ +# define MAC_FR_FILTER_PCF_MASK 0x000000c0 /* pass control frames */ +# define MAC_FR_FILTER_PCF_0 0x00000000 /* */ +# define MAC_FR_FILTER_PCF_1 0x00000040 /* */ +# define MAC_FR_FILTER_PCF_2 0x00000080 /* */ +# define MAC_FR_FILTER_PCF_3 0x000000c0 /* */ +# define MAC_FR_FILTER_DBF 0x00000020 /* disable broadcast frames */ +# define MAC_FR_FILTER_PM 0x00000010 /* pass all multicast */ +# define MAC_FR_FILTER_DAIF 0x00000008 /* DA inverse filtering */ +# define MAC_FR_FILTER_HMC 0x00000004 /* hash multicast */ +# define MAC_FR_FILTER_HUC 0x00000002 /* hash unicast */ +# define MAC_FR_FILTER_PR 0x00000001 /* promiscuous mode */ + +# define HASH_TABLE_HIGH_REG 0x0008 +# define HASH_TABLE_LOW_REG 0x000c + +# define GMII_ADR_REG 0x0010 + +# define GMII_ADR_REG_PA 0x0000f800 /* addr bits */ +# define GMII_ADR_REG_GR 0x000007c0 /* addr bits */ +# define GMII_ADR_REG_RSVRD1 0x00000020 /* */ +# define GMII_ADR_REG_CR 0x0000001c /* csr clock range */ +# define GMII_ADR_REG_GW 0x00000002 /* gmii write */ +# define GMII_ADR_REG_GB 0x00000001 /* gmii busy */ + +# define GMII_ADR_REG_ADR_SET(x) (GMII_ADR_REG_PA & (x << 11)) +# define GMII_ADR_REG_ADR_GET(x) ((x & GMII_ADR_REG_PA) >> 11) + +# define GMII_ADR_REG_REG_SET(x) (GMII_ADR_REG_GR & (x << 6)) +# define GMII_ADR_REG_REG_GET(x) (((x & GMII_ADR_REG_GR) >> 6) + +# define GMII_ADR_REG_CSR_SET(x) (GMII_ADR_REG_CR & (x << 2)) +# define GMII_ADR_REG_CSR_GET(x) (((x & GMII_ADR_REG_CR) >> 2) + +# define GMII_DATA_REG 0x0014 + +# define GMII_DATA_REG_DATA 0x0000ffff /* gmii data */ + +# define FLOW_CONTROL_REG 0x0018 + +# define FLOW_CONTROL_PT 0xFFFF0000 /* pause time */ +# define FLOW_CONTROL_DZPQ 0x00000080 /* disable zero-quanta pause */ +# define FLOW_CONTROL_PLT 0x00000030 /* pause level threshold */ + +# define FLOW_CONTROL_UP 0x00000008 /* unicast pause frame detect */ +# define FLOW_CONTROL_RFE 0x00000004 /* receive flow control enable */ +# define FLOW_CONTROL_TFE 0x00000002 /* transmit flow control enable */ +# define FLOW_CONTROL_FCB 0x00000001 /* flow control busy (BPA) */ + +# define VLAN_TAG_REG 0x001c + +# define VERSION_REG 0x0020 + +/* don't define these until HW if finished */ +/* # define VERSION_USER 0x10 */ +/* # define VERSION_QFEC 0x36 */ + +# define VERSION_REG_USER(x) (0xFF & (x >> 8)) +# define VERSION_REG_QFEC(x) (0xFF & x) + +# define DEBUG_REG 0x0024 + +# define DEBUG_REG_RSVD1 0xfc000000 /* */ +# define DEBUG_REG_TX_FIFO_FULL 0x02000000 /* Tx fifo full */ +# define DEBUG_REG_TX_FIFO_NEMP 0x01000000 /* Tx fifo not empty */ + +# define DEBUG_REG_RSVD2 0x00800000 /* */ +# define DEBUG_REG_TX_WR_ACTIVE 0x00400000 /* Tx fifo write ctrl active */ + +# define DEBUG_REG_TX_RD_STATE 0x00300000 /* Tx fifo rd ctrl state */ +# define DEBUG_REG_TX_RD_IDLE 0x00000000 /* idle */ +# define DEBUG_REG_TX_RD_WAIT 0x00100000 /* waiting for status */ +# define DEBUG_REG_TX_RD_PASUE 0x00200000 /* generating pause */ +# define DEBUG_REG_TX_RD_WRTG 0x00300000 /* wr stat flush fifo */ + +# define DEBUG_REG_TX_PAUSE 0x00080000 /* Tx in pause condition */ + +# define DEBUG_REG_TX_CTRL_STATE 0x00060000 /* Tx frame controller state */ +# define DEBUG_REG_TX_CTRL_IDLE 0x00090000 /* idle */ +# define DEBUG_REG_TX_CTRL_WAIT 0x00020000 /* waiting for status*/ +# define DEBUG_REG_TX_CTRL_PAUSE 0x00040000 /* generating pause */ +# define DEBUG_REG_TX_CTRL_XFER 0x00060000 /* transferring input */ + +# define DEBUG_REG_TX_ACTIVE 0x00010000 /* Tx actively transmitting */ +# define DEBUG_REG_RSVD3 0x0000fc00 /* */ + +# define DEBUG_REG_RX_STATE 0x00000300 /* Rx fifo state */ +# define DEBUG_REG_RX_EMPTY 0x00000000 /* empty */ +# define DEBUG_REG_RX_LOW 0x00000100 /* below threshold */ +# define DEBUG_REG_RX_HIGH 0x00000200 /* above threshold */ +# define DEBUG_REG_RX_FULL 0x00000300 /* full */ + +# define DEBUG_REG_RSVD4 0x00000080 /* */ + +# define DEBUG_REG_RX_RD_STATE 0x00000060 /* Rx rd ctrl state */ +# define DEBUG_REG_RX_RD_IDLE 0x00000000 /* idle */ +# define DEBUG_REG_RX_RD_RDG_FR 0x00000020 /* reading frame data */ +# define DEBUG_REG_RX_RD_RDG_STA 0x00000040 /* reading status */ +# define DEBUG_REG_RX_RD_FLUSH 0x00000060 /* flush fr data/stat */ + +# define DEBUG_REG_RX_ACTIVE 0x00000010 /* Rx wr ctlr active */ + +# define DEBUG_REG_RSVD5 0x00000008 /* */ +# define DEBUG_REG_SM_FIFO_RW_STA 0x00000006 /* small fifo rd/wr state */ +# define DEBUG_REG_RX_RECVG 0x00000001 /* Rx actively receiving data */ + +# define REM_WAKEUP_FR_REG 0x0028 +# define PMT_CTRL_STAT_REG 0x002c +/* 0x0030-0x0034 reserved */ + +# define INTRP_STATUS_REG 0x0038 + +# define INTRP_STATUS_REG_RSVD1 0x0000fc00 /* */ +# define INTRP_STATUS_REG_TSI 0x00000200 /* time stamp int stat */ +# define INTRP_STATUS_REG_RSVD2 0x00000100 /* */ + +# define INTRP_STATUS_REG_RCOI 0x00000080 /* rec checksum offload int */ +# define INTRP_STATUS_REG_TI 0x00000040 /* tx int stat */ +# define INTRP_STATUS_REG_RI 0x00000020 /* rx int stat */ +# define INTRP_STATUS_REG_NI 0x00000010 /* normal int summary */ + +# define INTRP_STATUS_REG_PMTI 0x00000008 /* PMT int */ +# define INTRP_STATUS_REG_ANC 0x00000004 /* auto negotiation complete */ +# define INTRP_STATUS_REG_LSC 0x00000002 /* link status change */ +# define INTRP_STATUS_REG_MII 0x00000001 /* rgMii/sgMii int */ + +# define INTRP_MASK_REG 0x003c + +# define INTRP_MASK_REG_RSVD1 0xfc00 /* */ +# define INTRP_MASK_REG_TSIM 0x0200 /* time stamp int mask */ +# define INTRP_MASK_REG_RSVD2 0x01f0 /* */ + +# define INTRP_MASK_REG_PMTIM 0x0000 /* PMT int mask */ +# define INTRP_MASK_REG_ANCM 0x0000 /* auto negotiation compl mask */ +# define INTRP_MASK_REG_LSCM 0x0000 /* link status change mask */ +# define INTRP_MASK_REG_MIIM 0x0000 /* rgMii/sgMii int mask */ + +# define MAC_ADR_0_HIGH_REG 0x0040 +# define MAC_ADR_0_LOW_REG 0x0044 +/* additional pairs of registers for MAC addresses 1-15 */ + +# define AN_CONTROL_REG 0x00c0 + +# define AN_CONTROL_REG_RSVRD1 0xfff80000 /* */ +# define AN_CONTROL_REG_SGM_RAL 0x00040000 /* sgmii ral control */ +# define AN_CONTROL_REG_LR 0x00020000 /* lock to reference */ +# define AN_CONTROL_REG_ECD 0x00010000 /* enable comma detect */ + +# define AN_CONTROL_REG_RSVRD2 0x00008000 /* */ +# define AN_CONTROL_REG_ELE 0x00004000 /* external loopback enable */ +# define AN_CONTROL_REG_RSVRD3 0x00002000 /* */ +# define AN_CONTROL_REG_ANE 0x00001000 /* auto negotiation enable */ + +# define AN_CONTROL_REG_RSRVD4 0x00000c00 /* */ +# define AN_CONTROL_REG_RAN 0x00000200 /* restart auto negotiation */ +# define AN_CONTROL_REG_RSVRD5 0x000001ff /* */ + +# define AN_STATUS_REG 0x00c4 + +# define AN_STATUS_REG_RSVRD1 0xfffffe00 /* */ +# define AN_STATUS_REG_ES 0x00000100 /* extended status */ +# define AN_STATUS_REG_RSVRD2 0x000000c0 /* */ +# define AN_STATUS_REG_ANC 0x00000020 /* auto-negotiation complete */ +# define AN_STATUS_REG_RSVRD3 0x00000010 /* */ +# define AN_STATUS_REG_ANA 0x00000008 /* auto-negotiation ability */ +# define AN_STATUS_REG_LS 0x00000004 /* link status */ +# define AN_STATUS_REG_RSVRD4 0x00000003 /* */ + +# define AN_ADVERTISE_REG 0x00c8 +# define AN_LNK_PRTNR_ABIL_REG 0x00cc +# define AN_EXPANDSION_REG 0x00d0 +# define TBI_EXT_STATUS_REG 0x00d4 + +# define SG_RG_SMII_STATUS_REG 0x00d8 + +# define LINK_STATUS_REG 0x00d8 + +# define LINK_STATUS_REG_RSVRD1 0xffffffc0 /* */ +# define LINK_STATUS_REG_FCD 0x00000020 /* false carrier detect */ +# define LINK_STATUS_REG_JT 0x00000010 /* jabber timeout */ +# define LINK_STATUS_REG_UP 0x00000008 /* link status */ + +# define LINK_STATUS_REG_SPD 0x00000006 /* link speed */ +# define LINK_STATUS_REG_SPD_2_5 0x00000000 /* 10M 2.5M * 4 */ +# define LINK_STATUS_REG_SPD_25 0x00000002 /* 100M 25M * 4 */ +# define LINK_STATUS_REG_SPD_125 0x00000004 /* 1G 125M * 8 */ + +# define LINK_STATUS_REG_F_DUPLEX 0x00000001 /* full duplex */ + +/* 0x00dc-0x00fc reserved */ + +/* MMC Register Map is from 0x0100-0x02fc */ +# define MMC_CNTRL_REG 0x0100 +# define MMC_INTR_RX_REG 0x0104 +# define MMC_INTR_TX_REG 0x0108 +# define MMC_INTR_MASK_RX_REG 0x010C +# define MMC_INTR_MASK_TX_REG 0x0110 + +/* 0x0300-0x06fc reserved */ + +/* precision time protocol time stamp registers */ + +# define TS_CTL_REG 0x0700 + +# define TS_CTL_ATSFC 0x00080000 +# define TS_CTL_TSENMAC 0x00040000 + +# define TS_CTL_TSCLKTYPE 0x00030000 +# define TS_CTL_TSCLK_ORD 0x00000000 +# define TS_CTL_TSCLK_BND 0x00010000 +# define TS_CTL_TSCLK_ETE 0x00020000 +# define TS_CTL_TSCLK_PTP 0x00030000 + +# define TS_CTL_TSMSTRENA 0x00008000 +# define TS_CTL_TSEVNTENA 0x00004000 +# define TS_CTL_TSIPV4ENA 0x00002000 +# define TS_CTL_TSIPV6ENA 0x00001000 + +# define TS_CTL_TSIPENA 0x00000800 +# define TS_CTL_TSVER2ENA 0x00000400 +# define TS_CTL_TSCTRLSSR 0x00000200 +# define TS_CTL_TSENALL 0x00000100 + +# define TS_CTL_TSADDREG 0x00000020 +# define TS_CTL_TSTRIG 0x00000010 + +# define TS_CTL_TSUPDT 0x00000008 +# define TS_CTL_TSINIT 0x00000004 +# define TS_CTL_TSCFUPDT 0x00000002 +# define TS_CTL_TSENA 0x00000001 + + +# define TS_SUB_SEC_INCR_REG 0x0704 +# define TS_HIGH_REG 0x0708 +# define TS_LOW_REG 0x070c +# define TS_HI_UPDT_REG 0x0710 +# define TS_LO_UPDT_REG 0x0714 +# define TS_APPEND_REG 0x0718 +# define TS_TARG_TIME_HIGH_REG 0x071c +# define TS_TARG_TIME_LOW_REG 0x0720 +# define TS_HIGHER_WD_REG 0x0724 +# define TS_STATUS_REG 0x072c + +/* 0x0730-0x07fc reserved */ + +# define MAC_ADR16_HIGH_REG 0x0800 +# define MAC_ADR16_LOW_REG 0x0804 +/* additional pairs of registers for MAC addresses 17-31 */ + +# define MAC_ADR_MAX 32 + + +# define QFEC_INTRP_SETUP (INTRP_EN_REG_AIE \ + | INTRP_EN_REG_FBE \ + | INTRP_EN_REG_RWE \ + | INTRP_EN_REG_RSE \ + | INTRP_EN_REG_RUE \ + | INTRP_EN_REG_UNE \ + | INTRP_EN_REG_OVE \ + | INTRP_EN_REG_TJE \ + | INTRP_EN_REG_TSE \ + | INTRP_EN_REG_NIE \ + | INTRP_EN_REG_RIE \ + | INTRP_EN_REG_TIE) + +/* + * ASIC Ethernet clock register definitions: + * address offsets and some register definitions + */ + +# define EMAC_CLK_REG_BASE 0x94020000 + +/* + * PHY clock PLL register locations + */ +# define ETH_MD_REG 0x02A4 +# define ETH_NS_REG 0x02A8 + +/* definitions of NS_REG control bits + */ +# define ETH_NS_SRC_SEL 0x0007 + +# define ETH_NS_PRE_DIV_MSK 0x0018 +# define ETH_NS_PRE_DIV(x) (ETH_NS_PRE_DIV_MSK & (x << 3)) + +# define ETH_NS_MCNTR_MODE_MSK 0x0060 +# define ETH_NS_MCNTR_MODE_BYPASS 0x0000 +# define ETH_NS_MCNTR_MODE_SWALLOW 0x0020 +# define ETH_NS_MCNTR_MODE_DUAL 0x0040 +# define ETH_NS_MCNTR_MODE_SINGLE 0x0060 + +# define ETH_NS_MCNTR_RST 0x0080 +# define ETH_NS_MCNTR_EN 0x0100 + +# define EMAC_PTP_NS_CLK_EN 0x0200 +# define EMAC_PTP_NS_CLK_INV 0x0400 +# define EMAC_PTP_NS_ROOT_EN 0x0800 + +/* clock sources + */ +# define CLK_SRC_TCXO 0x0 +# define CLK_SRC_PLL_GLOBAL 0x1 +# define CLK_SRC_PLL_ARM 0x2 +# define CLK_SRC_PLL_QDSP6 0x3 +# define CLK_SRC_PLL_EMAC 0x4 +# define CLK_SRC_EXT_CLK2 0x5 +# define CLK_SRC_EXT_CLK1 0x6 +# define CLK_SRC_CORE_TEST 0x7 + +# define ETH_MD_M(x) (x << 16) +# define ETH_MD_2D_N(x) ((~(x) & 0xffff)) +# define ETH_NS_NM(x) ((~(x) << 16) & 0xffff0000) + +/* + * PHY interface clock divider + */ +# define ETH_X_EN_NS_REG 0x02AC + +# define ETH_RX_CLK_FB_INV 0x80 +# define ETH_RX_CLK_FB_EN 0x40 +# define ETH_TX_CLK_FB_INV 0x20 +# define ETH_TX_CLK_FB_EN 0x10 +# define ETH_RX_CLK_INV 0x08 +# define ETH_RX_CLK_EN 0x04 +# define ETH_TX_CLK_INV 0x02 +# define ETH_TX_CLK_EN 0x01 + +# define ETH_X_EN_NS_DEFAULT \ + (ETH_RX_CLK_FB_EN | ETH_TX_CLK_FB_EN | ETH_RX_CLK_EN | ETH_TX_CLK_EN) + +# define EMAC_PTP_MD_REG 0x02B0 + +/* PTP clock divider + */ +# define EMAC_PTP_NS_REG 0x02B4 + +/* + * clock interface pin controls + */ +# define EMAC_NS_REG 0x02B8 + +# define EMAC_RX_180_CLK_INV 0x2000 +# define EMAC_RX_180_CLK_EN 0x1000 +# define EMAC_RX_180_CLK_EN_INV (EMAC_RX_180_CLK_INV | EMAC_RX_180_CLK_EN) + +# define EMAC_TX_180_CLK_INV 0x0800 +# define EMAC_TX_180_CLK_EN 0x0400 +# define EMAC_TX_180_CLK_EN_INV (EMAC_TX_180_CLK_INV | EMAC_TX_180_CLK_EN) + +# define EMAC_REVMII_RX_CLK_INV 0x0200 +# define EMAC_REVMII_RX_CLK_EN 0x0100 + +# define EMAC_RX_CLK_INV 0x0080 +# define EMAC_RX_CLK_EN 0x0040 + +# define EMAC_REVMII_TX_CLK_INV 0x0020 +# define EMAC_REVMII_TX_CLK_EN 0x0010 + +# define EMAC_TX_CLK_INV 0x0008 +# define EMAC_TX_CLK_EN 0x0004 + +# define EMAC_RX_R_CLK_EN 0x0002 +# define EMAC_TX_R_CLK_EN 0x0001 + +# define EMAC_NS_DEFAULT \ + (EMAC_RX_180_CLK_EN_INV | EMAC_TX_180_CLK_EN_INV \ + | EMAC_REVMII_RX_CLK_EN | EMAC_REVMII_TX_CLK_EN \ + | EMAC_RX_CLK_EN | EMAC_TX_CLK_EN \ + | EMAC_RX_R_CLK_EN | EMAC_TX_R_CLK_EN) + +/* + * + */ +# define EMAC_TX_FS_REG 0x02BC +# define EMAC_RX_FS_REG 0x02C0 + +/* + * Ethernet controller PHY interface select + */ +# define EMAC_PHY_INTF_SEL_REG 0x18030 + +# define EMAC_PHY_INTF_SEL_MII 0x0 +# define EMAC_PHY_INTF_SEL_RGMII 0x1 +# define EMAC_PHY_INTF_SEL_REVMII 0x7 +# define EMAC_PHY_INTF_SEL_MASK 0x7 + +/* + * MDIO addresses + */ +# define EMAC_PHY_ADDR_REG 0x18034 +# define EMAC_REVMII_PHY_ADDR_REG 0x18038 + +/* + * clock routing + */ +# define EMAC_CLKMUX_SEL_REG 0x1803c + +# define EMAC_CLKMUX_SEL_0 0x1 +# define EMAC_CLKMUX_SEL_1 0x2 + + +#endif diff --git a/drivers/net/smc91x.h b/drivers/net/smc91x.h index 5f53fbbf67b..1e1617ef160 100644 --- a/drivers/net/smc91x.h +++ b/drivers/net/smc91x.h @@ -223,6 +223,20 @@ SMC_outw(u16 val, void __iomem *ioaddr, int reg) #define SMC_outsl(a, r, p, l) writesl((a) + (r), p, l) #define SMC_IRQ_FLAGS (-1) /* from resource */ +#elif defined(CONFIG_ARCH_MSM) + +#define SMC_CAN_USE_8BIT 0 +#define SMC_CAN_USE_16BIT 1 +#define SMC_CAN_USE_32BIT 0 +#define SMC_NOWAIT 1 + +#define SMC_inw(a, r) readw((a) + (r)) +#define SMC_outw(v, a, r) writew(v, (a) + (r)) +#define SMC_insw(a, r, p, l) readsw((a) + (r), p, l) +#define SMC_outsw(a, r, p, l) writesw((a) + (r), p, l) + +#define SMC_IRQ_FLAGS IRQF_TRIGGER_HIGH + #elif defined(CONFIG_MN10300) /* diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c index c6d47d10590..1064aa027ed 100644 --- a/drivers/net/smsc911x.c +++ b/drivers/net/smsc911x.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -872,7 +873,7 @@ static void smsc911x_phy_adjust_link(struct net_device *dev) (!pdata->using_extphy)) { /* Restore original GPIO configuration */ pdata->gpio_setting = pdata->gpio_orig_setting; - smsc911x_reg_write(pdata, GPIO_CFG, + smsc911x_reg_write(pdata, SMSC_GPIO_CFG, pdata->gpio_setting); } } else { @@ -880,7 +881,7 @@ static void smsc911x_phy_adjust_link(struct net_device *dev) /* Check global setting that LED1 * usage is 10/100 indicator */ pdata->gpio_setting = smsc911x_reg_read(pdata, - GPIO_CFG); + SMSC_GPIO_CFG); if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_) && (!pdata->using_extphy)) { /* Force 10/100 LED off, after saving @@ -891,7 +892,7 @@ static void smsc911x_phy_adjust_link(struct net_device *dev) pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_ | GPIO_CFG_GPIODIR0_ | GPIO_CFG_GPIOD0_); - smsc911x_reg_write(pdata, GPIO_CFG, + smsc911x_reg_write(pdata, SMSC_GPIO_CFG, pdata->gpio_setting); } } @@ -1314,7 +1315,7 @@ static int smsc911x_open(struct net_device *dev) SMSC_WARN(pdata, ifup, "Timed out waiting for EEPROM busy bit to clear"); - smsc911x_reg_write(pdata, GPIO_CFG, 0x70070000); + smsc911x_reg_write(pdata, SMSC_GPIO_CFG, 0x70070000); /* The soft reset above cleared the device's MAC address, * restore it from local copy (set in probe) */ @@ -1758,9 +1759,9 @@ smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs, static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata) { - unsigned int temp = smsc911x_reg_read(pdata, GPIO_CFG); + unsigned int temp = smsc911x_reg_read(pdata, SMSC_GPIO_CFG); temp &= ~GPIO_CFG_EEPR_EN_; - smsc911x_reg_write(pdata, GPIO_CFG, temp); + smsc911x_reg_write(pdata, SMSC_GPIO_CFG, temp); msleep(1); } @@ -2055,6 +2056,10 @@ static int __devexit smsc911x_drv_remove(struct platform_device *pdev) SMSC_TRACE(pdata, ifdown, "Stopping driver"); + if (pdata->config.has_reset_gpio) { + gpio_set_value_cansleep(pdata->config.reset_gpio, 0); + gpio_free(pdata->config.reset_gpio); + } phy_disconnect(pdata->phy_dev); pdata->phy_dev = NULL; mdiobus_unregister(pdata->mii_bus); @@ -2185,7 +2190,7 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) smsc911x_reg_write(pdata, INT_EN, 0); smsc911x_reg_write(pdata, INT_STS, 0xFFFFFFFF); - retval = request_irq(dev->irq, smsc911x_irqhandler, + retval = request_any_context_irq(dev->irq, smsc911x_irqhandler, irq_flags | IRQF_SHARED, dev->name, dev); if (retval) { SMSC_WARN(pdata, probe, @@ -2277,6 +2282,10 @@ static int smsc911x_suspend(struct device *dev) PMT_CTRL_PM_MODE_D1_ | PMT_CTRL_WOL_EN_ | PMT_CTRL_ED_EN_ | PMT_CTRL_PME_EN_); + /* Drive the GPIO Ethernet_Reset Line low to Suspend */ + if (pdata->config.has_reset_gpio) + gpio_set_value_cansleep(pdata->config.reset_gpio, 0); + return 0; } @@ -2286,6 +2295,9 @@ static int smsc911x_resume(struct device *dev) struct smsc911x_data *pdata = netdev_priv(ndev); unsigned int to = 100; + if (pdata->config.has_reset_gpio) + gpio_set_value_cansleep(pdata->config.reset_gpio, 1); + /* Note 3.11 from the datasheet: * "When the LAN9220 is in a power saving state, a write of any * data to the BYTE_TEST register will wake-up the device." diff --git a/drivers/net/smsc911x.h b/drivers/net/smsc911x.h index 8d67aacf886..19711d2648f 100644 --- a/drivers/net/smsc911x.h +++ b/drivers/net/smsc911x.h @@ -236,7 +236,7 @@ #define PMT_CTRL_PME_EN_ 0x00000002 #define PMT_CTRL_READY_ 0x00000001 -#define GPIO_CFG 0x88 +#define SMSC_GPIO_CFG 0x88 #define GPIO_CFG_LED3_EN_ 0x40000000 #define GPIO_CFG_LED2_EN_ 0x20000000 #define GPIO_CFG_LED1_EN_ 0x10000000 diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index f1d88c571bc..50dacf94e82 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -273,6 +273,26 @@ config WIFI_CONTROL_FUNC help Enables Power/Reset/Carddetect function abstraction +config LIBRA_SDIOIF + tristate "Qualcomm libra wlan SDIO driver" + select WIRELESS_EXT + select WEXT_PRIV + select WEXT_CORE + select WEXT_SPY + depends on MMC_MSM + ---help--- + A driver for Qualcomm WLAN SDIO Libra chipset. + +config WCNSS_WLAN + tristate "Qualcomm WCNSS WLAN driver" + depends on ARCH_MSM8960 + select WIRELESS_EXT + select WEXT_PRIV + select WEXT_CORE + select WEXT_SPY + ---help--- + A driver for Qualcomm WCNSS WLAN feature + source "drivers/net/wireless/ath/Kconfig" source "drivers/net/wireless/b43/Kconfig" source "drivers/net/wireless/b43legacy/Kconfig" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 8ceae0a8ba0..f725adf65b6 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -61,3 +61,6 @@ obj-$(CONFIG_MWIFIEX) += mwifiex/ obj-$(CONFIG_BCM4329) += bcm4329/ obj-$(CONFIG_BCMDHD) += bcmdhd/ + +obj-$(CONFIG_LIBRA_SDIOIF) += libra/ +obj-$(CONFIG_WCNSS_WLAN) += wcnss/ diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c index 1be7c8bbef8..82fb6cefa41 100644 --- a/drivers/net/wireless/ath/ath9k/hw.c +++ b/drivers/net/wireless/ath/ath9k/hw.c @@ -506,6 +506,17 @@ static int __ath9k_hw_init(struct ath_hw *ah) ah->WARegVal |= (AR_WA_D3_L1_DISABLE | AR_WA_ASPM_TIMER_BASED_DISABLE); + /* + * Read back AR_WA into a permanent copy and set bits 14 and 17. + * We need to do this to avoid RMW of this register. We cannot + * read the reg when chip is asleep. + */ + ah->WARegVal = REG_READ(ah, AR_WA); + ah->WARegVal |= (AR_WA_D3_L1_DISABLE | + AR_WA_ASPM_TIMER_BASED_DISABLE); + + ath9k_hw_read_revisions(ah); + if (!ath9k_hw_set_reset_reg(ah, ATH9K_RESET_POWER_ON)) { ath_err(common, "Couldn't reset chip\n"); return -EIO; diff --git a/drivers/net/wireless/bcm4329/bcmspibrcm.c b/drivers/net/wireless/bcm4329/bcmspibrcm.c new file mode 100644 index 00000000000..0f131a40f4b --- /dev/null +++ b/drivers/net/wireless/bcm4329/bcmspibrcm.c @@ -0,0 +1,1726 @@ +/* + * Broadcom BCMSDH to gSPI Protocol Conversion Layer + * + * Copyright (C) 2010, Broadcom Corporation + * All Rights Reserved. + * + * This is UNPUBLISHED PROPRIETARY SOURCE CODE of Broadcom Corporation; + * the contents of this file may not be disclosed to third parties, copied + * or duplicated in any form, in whole or in part, without the prior + * written permission of Broadcom Corporation. + * + * $Id: bcmspibrcm.c,v 1.11.2.10.2.9.6.11 2009/05/21 13:21:57 Exp $ + */ + +#define HSMODE + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* bcmsdh to/from specific controller APIs */ +#include /* ioctl/iovars */ +#include + +#include + + +#include +#include + +#define F0_RESPONSE_DELAY 16 +#define F1_RESPONSE_DELAY 16 +#define F2_RESPONSE_DELAY F0_RESPONSE_DELAY + +#define CMDLEN 4 + +#define DWORDMODE_ON (sd->chip == BCM4329_CHIP_ID) && (sd->chiprev == 2) && (sd->dwordmode == TRUE) + +/* Globals */ +uint sd_msglevel = 0; + +uint sd_hiok = FALSE; /* Use hi-speed mode if available? */ +uint sd_sdmode = SDIOH_MODE_SPI; /* Use SD4 mode by default */ +uint sd_f2_blocksize = 64; /* Default blocksize */ + + +uint sd_divisor = 2; +uint sd_power = 1; /* Default to SD Slot powered ON */ +uint sd_clock = 1; /* Default to SD Clock turned ON */ +uint sd_crc = 0; /* Default to SPI CRC Check turned OFF */ + +uint8 spi_outbuf[SPI_MAX_PKT_LEN]; +uint8 spi_inbuf[SPI_MAX_PKT_LEN]; + +/* 128bytes buffer is enough to clear data-not-available and program response-delay F0 bits + * assuming we will not exceed F0 response delay > 100 bytes at 48MHz. + */ +#define BUF2_PKT_LEN 128 +uint8 spi_outbuf2[BUF2_PKT_LEN]; +uint8 spi_inbuf2[BUF2_PKT_LEN]; + +/* Prototypes */ +static bool bcmspi_test_card(sdioh_info_t *sd); +static bool bcmspi_host_device_init_adapt(sdioh_info_t *sd); +static int bcmspi_set_highspeed_mode(sdioh_info_t *sd, bool hsmode); +static int bcmspi_cmd_issue(sdioh_info_t *sd, bool use_dma, uint32 cmd_arg, + uint32 *data, uint32 datalen); +static int bcmspi_card_regread(sdioh_info_t *sd, int func, uint32 regaddr, + int regsize, uint32 *data); +static int bcmspi_card_regwrite(sdioh_info_t *sd, int func, uint32 regaddr, + int regsize, uint32 data); +static int bcmspi_card_bytewrite(sdioh_info_t *sd, int func, uint32 regaddr, + uint8 *data); +static int bcmspi_driver_init(sdioh_info_t *sd); +static int bcmspi_card_buf(sdioh_info_t *sd, int rw, int func, bool fifo, + uint32 addr, int nbytes, uint32 *data); +static int bcmspi_card_regread_fixedaddr(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, + uint32 *data); +static void bcmspi_cmd_getdstatus(sdioh_info_t *sd, uint32 *dstatus_buffer); +static int bcmspi_update_stats(sdioh_info_t *sd, uint32 cmd_arg); + +/* + * Public entry points & extern's + */ +extern sdioh_info_t * +sdioh_attach(osl_t *osh, void *bar0, uint irq) +{ + sdioh_info_t *sd; + + sd_trace(("%s\n", __FUNCTION__)); + if ((sd = (sdioh_info_t *)MALLOC(osh, sizeof(sdioh_info_t))) == NULL) { + sd_err(("%s: out of memory, malloced %d bytes\n", __FUNCTION__, MALLOCED(osh))); + return NULL; + } + bzero((char *)sd, sizeof(sdioh_info_t)); + sd->osh = osh; + if (spi_osinit(sd) != 0) { + sd_err(("%s: spi_osinit() failed\n", __FUNCTION__)); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + return NULL; + } + + sd->bar0 = bar0; + sd->irq = irq; + sd->intr_handler = NULL; + sd->intr_handler_arg = NULL; + sd->intr_handler_valid = FALSE; + + /* Set defaults */ + sd->use_client_ints = TRUE; + sd->sd_use_dma = FALSE; /* DMA Not supported */ + + /* Spi device default is 16bit mode, change to 4 when device is changed to 32bit + * mode + */ + sd->wordlen = 2; + + if (!spi_hw_attach(sd)) { + sd_err(("%s: spi_hw_attach() failed\n", __FUNCTION__)); + spi_osfree(sd); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + return (NULL); + } + + if (bcmspi_driver_init(sd) != SUCCESS) { + sd_err(("%s: bcmspi_driver_init() failed()\n", __FUNCTION__)); + spi_hw_detach(sd); + spi_osfree(sd); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + return (NULL); + } + + if (spi_register_irq(sd, irq) != SUCCESS) { + sd_err(("%s: spi_register_irq() failed for irq = %d\n", __FUNCTION__, irq)); + spi_hw_detach(sd); + spi_osfree(sd); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + return (NULL); + } + + sd_trace(("%s: Done\n", __FUNCTION__)); + + return sd; +} + +extern SDIOH_API_RC +sdioh_detach(osl_t *osh, sdioh_info_t *sd) +{ + sd_trace(("%s\n", __FUNCTION__)); + if (sd) { + sd_err(("%s: detaching from hardware\n", __FUNCTION__)); + spi_free_irq(sd->irq, sd); + spi_hw_detach(sd); + spi_osfree(sd); + MFREE(sd->osh, sd, sizeof(sdioh_info_t)); + } + return SDIOH_API_RC_SUCCESS; +} + +/* Configure callback to client when we recieve client interrupt */ +extern SDIOH_API_RC +sdioh_interrupt_register(sdioh_info_t *sd, sdioh_cb_fn_t fn, void *argh) +{ + sd_trace(("%s: Entering\n", __FUNCTION__)); + sd->intr_handler = fn; + sd->intr_handler_arg = argh; + sd->intr_handler_valid = TRUE; + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC +sdioh_interrupt_deregister(sdioh_info_t *sd) +{ + sd_trace(("%s: Entering\n", __FUNCTION__)); + sd->intr_handler_valid = FALSE; + sd->intr_handler = NULL; + sd->intr_handler_arg = NULL; + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC +sdioh_interrupt_query(sdioh_info_t *sd, bool *onoff) +{ + sd_trace(("%s: Entering\n", __FUNCTION__)); + *onoff = sd->client_intr_enabled; + return SDIOH_API_RC_SUCCESS; +} + +#if defined(DHD_DEBUG) +extern bool +sdioh_interrupt_pending(sdioh_info_t *sd) +{ + return 0; +} +#endif + +extern SDIOH_API_RC +sdioh_query_device(sdioh_info_t *sd) +{ + /* Return a BRCM ID appropriate to the dongle class */ + return (sd->num_funcs > 1) ? BCM4329_D11NDUAL_ID : BCM4318_D11G_ID; +} + +/* Provide dstatus bits of spi-transaction for dhd layers. */ +extern uint32 +sdioh_get_dstatus(sdioh_info_t *sd) +{ + return sd->card_dstatus; +} + +extern void +sdioh_chipinfo(sdioh_info_t *sd, uint32 chip, uint32 chiprev) +{ + sd->chip = chip; + sd->chiprev = chiprev; +} + +extern void +sdioh_dwordmode(sdioh_info_t *sd, bool set) +{ + uint8 reg = 0; + int status; + + if ((status = sdioh_request_byte(sd, SDIOH_READ, SPI_FUNC_0, SPID_STATUS_ENABLE, ®)) != + SUCCESS) { + sd_err(("%s: Failed to set dwordmode in gSPI\n", __FUNCTION__)); + return; + } + + if (set) { + reg |= DWORD_PKT_LEN_EN; + sd->dwordmode = TRUE; + sd->client_block_size[SPI_FUNC_2] = 4096; /* h2spi's limit is 4KB, we support 8KB */ + } else { + reg &= ~DWORD_PKT_LEN_EN; + sd->dwordmode = FALSE; + sd->client_block_size[SPI_FUNC_2] = 2048; + } + + if ((status = sdioh_request_byte(sd, SDIOH_WRITE, SPI_FUNC_0, SPID_STATUS_ENABLE, ®)) != + SUCCESS) { + sd_err(("%s: Failed to set dwordmode in gSPI\n", __FUNCTION__)); + return; + } +} + + +uint +sdioh_query_iofnum(sdioh_info_t *sd) +{ + return sd->num_funcs; +} + +/* IOVar table */ +enum { + IOV_MSGLEVEL = 1, + IOV_BLOCKMODE, + IOV_BLOCKSIZE, + IOV_DMA, + IOV_USEINTS, + IOV_NUMINTS, + IOV_NUMLOCALINTS, + IOV_HOSTREG, + IOV_DEVREG, + IOV_DIVISOR, + IOV_SDMODE, + IOV_HISPEED, + IOV_HCIREGS, + IOV_POWER, + IOV_CLOCK, + IOV_SPIERRSTATS, + IOV_RESP_DELAY_ALL +}; + +const bcm_iovar_t sdioh_iovars[] = { + {"sd_msglevel", IOV_MSGLEVEL, 0, IOVT_UINT32, 0 }, + {"sd_blocksize", IOV_BLOCKSIZE, 0, IOVT_UINT32, 0 }, /* ((fn << 16) | size) */ + {"sd_dma", IOV_DMA, 0, IOVT_BOOL, 0 }, + {"sd_ints", IOV_USEINTS, 0, IOVT_BOOL, 0 }, + {"sd_numints", IOV_NUMINTS, 0, IOVT_UINT32, 0 }, + {"sd_numlocalints", IOV_NUMLOCALINTS, 0, IOVT_UINT32, 0 }, + {"sd_hostreg", IOV_HOSTREG, 0, IOVT_BUFFER, sizeof(sdreg_t) }, + {"sd_devreg", IOV_DEVREG, 0, IOVT_BUFFER, sizeof(sdreg_t) }, + {"sd_divisor", IOV_DIVISOR, 0, IOVT_UINT32, 0 }, + {"sd_power", IOV_POWER, 0, IOVT_UINT32, 0 }, + {"sd_clock", IOV_CLOCK, 0, IOVT_UINT32, 0 }, + {"sd_mode", IOV_SDMODE, 0, IOVT_UINT32, 100}, + {"sd_highspeed", IOV_HISPEED, 0, IOVT_UINT32, 0}, + {"spi_errstats", IOV_SPIERRSTATS, 0, IOVT_BUFFER, sizeof(struct spierrstats_t) }, + {"spi_respdelay", IOV_RESP_DELAY_ALL, 0, IOVT_BOOL, 0 }, + {NULL, 0, 0, 0, 0 } +}; + +int +sdioh_iovar_op(sdioh_info_t *si, const char *name, + void *params, int plen, void *arg, int len, bool set) +{ + const bcm_iovar_t *vi = NULL; + int bcmerror = 0; + int val_size; + int32 int_val = 0; + bool bool_val; + uint32 actionid; +/* + sdioh_regs_t *regs; +*/ + + ASSERT(name); + ASSERT(len >= 0); + + /* Get must have return space; Set does not take qualifiers */ + ASSERT(set || (arg && len)); + ASSERT(!set || (!params && !plen)); + + sd_trace(("%s: Enter (%s %s)\n", __FUNCTION__, (set ? "set" : "get"), name)); + + if ((vi = bcm_iovar_lookup(sdioh_iovars, name)) == NULL) { + bcmerror = BCME_UNSUPPORTED; + goto exit; + } + + if ((bcmerror = bcm_iovar_lencheck(vi, arg, len, set)) != 0) + goto exit; + + /* Set up params so get and set can share the convenience variables */ + if (params == NULL) { + params = arg; + plen = len; + } + + if (vi->type == IOVT_VOID) + val_size = 0; + else if (vi->type == IOVT_BUFFER) + val_size = len; + else + val_size = sizeof(int); + + if (plen >= (int)sizeof(int_val)) + bcopy(params, &int_val, sizeof(int_val)); + + bool_val = (int_val != 0) ? TRUE : FALSE; + + actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid); + switch (actionid) { + case IOV_GVAL(IOV_MSGLEVEL): + int_val = (int32)sd_msglevel; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_MSGLEVEL): + sd_msglevel = int_val; + break; + + case IOV_GVAL(IOV_BLOCKSIZE): + if ((uint32)int_val > si->num_funcs) { + bcmerror = BCME_BADARG; + break; + } + int_val = (int32)si->client_block_size[int_val]; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_DMA): + int_val = (int32)si->sd_use_dma; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_DMA): + si->sd_use_dma = (bool)int_val; + break; + + case IOV_GVAL(IOV_USEINTS): + int_val = (int32)si->use_client_ints; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_USEINTS): + break; + + case IOV_GVAL(IOV_DIVISOR): + int_val = (uint32)sd_divisor; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_DIVISOR): + sd_divisor = int_val; + if (!spi_start_clock(si, (uint16)sd_divisor)) { + sd_err(("%s: set clock failed\n", __FUNCTION__)); + bcmerror = BCME_ERROR; + } + break; + + case IOV_GVAL(IOV_POWER): + int_val = (uint32)sd_power; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_POWER): + sd_power = int_val; + break; + + case IOV_GVAL(IOV_CLOCK): + int_val = (uint32)sd_clock; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_CLOCK): + sd_clock = int_val; + break; + + case IOV_GVAL(IOV_SDMODE): + int_val = (uint32)sd_sdmode; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_SDMODE): + sd_sdmode = int_val; + break; + + case IOV_GVAL(IOV_HISPEED): + int_val = (uint32)sd_hiok; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_HISPEED): + sd_hiok = int_val; + + if (!bcmspi_set_highspeed_mode(si, (bool)sd_hiok)) { + sd_err(("%s: Failed changing highspeed mode to %d.\n", + __FUNCTION__, sd_hiok)); + bcmerror = BCME_ERROR; + return ERROR; + } + break; + + case IOV_GVAL(IOV_NUMINTS): + int_val = (int32)si->intrcount; + bcopy(&int_val, arg, val_size); + break; + + case IOV_GVAL(IOV_NUMLOCALINTS): + int_val = (int32)si->local_intrcount; + bcopy(&int_val, arg, val_size); + break; + case IOV_GVAL(IOV_DEVREG): + { + sdreg_t *sd_ptr = (sdreg_t *)params; + uint8 data; + + if (sdioh_cfg_read(si, sd_ptr->func, sd_ptr->offset, &data)) { + bcmerror = BCME_SDIO_ERROR; + break; + } + + int_val = (int)data; + bcopy(&int_val, arg, sizeof(int_val)); + break; + } + + case IOV_SVAL(IOV_DEVREG): + { + sdreg_t *sd_ptr = (sdreg_t *)params; + uint8 data = (uint8)sd_ptr->value; + + if (sdioh_cfg_write(si, sd_ptr->func, sd_ptr->offset, &data)) { + bcmerror = BCME_SDIO_ERROR; + break; + } + break; + } + + + case IOV_GVAL(IOV_SPIERRSTATS): + { + bcopy(&si->spierrstats, arg, sizeof(struct spierrstats_t)); + break; + } + + case IOV_SVAL(IOV_SPIERRSTATS): + { + bzero(&si->spierrstats, sizeof(struct spierrstats_t)); + break; + } + + case IOV_GVAL(IOV_RESP_DELAY_ALL): + int_val = (int32)si->resp_delay_all; + bcopy(&int_val, arg, val_size); + break; + + case IOV_SVAL(IOV_RESP_DELAY_ALL): + si->resp_delay_all = (bool)int_val; + int_val = STATUS_ENABLE|INTR_WITH_STATUS; + if (si->resp_delay_all) + int_val |= RESP_DELAY_ALL; + else { + if (bcmspi_card_regwrite(si, SPI_FUNC_0, SPID_RESPONSE_DELAY, 1, + F1_RESPONSE_DELAY) != SUCCESS) { + sd_err(("%s: Unable to set response delay.\n", __FUNCTION__)); + bcmerror = BCME_SDIO_ERROR; + break; + } + } + + if (bcmspi_card_regwrite(si, SPI_FUNC_0, SPID_STATUS_ENABLE, 1, int_val) + != SUCCESS) { + sd_err(("%s: Unable to set response delay.\n", __FUNCTION__)); + bcmerror = BCME_SDIO_ERROR; + break; + } + break; + + default: + bcmerror = BCME_UNSUPPORTED; + break; + } +exit: + + return bcmerror; +} + +extern SDIOH_API_RC +sdioh_cfg_read(sdioh_info_t *sd, uint fnc_num, uint32 addr, uint8 *data) +{ + SDIOH_API_RC status; + /* No lock needed since sdioh_request_byte does locking */ + status = sdioh_request_byte(sd, SDIOH_READ, fnc_num, addr, data); + return status; +} + +extern SDIOH_API_RC +sdioh_cfg_write(sdioh_info_t *sd, uint fnc_num, uint32 addr, uint8 *data) +{ + /* No lock needed since sdioh_request_byte does locking */ + SDIOH_API_RC status; + + if ((fnc_num == SPI_FUNC_1) && (addr == SBSDIO_FUNC1_FRAMECTRL)) { + uint8 dummy_data; + status = sdioh_cfg_read(sd, fnc_num, addr, &dummy_data); + if (status) { + sd_err(("sdioh_cfg_read() failed.\n")); + return status; + } + } + + status = sdioh_request_byte(sd, SDIOH_WRITE, fnc_num, addr, data); + return status; +} + +extern SDIOH_API_RC +sdioh_cis_read(sdioh_info_t *sd, uint func, uint8 *cisd, uint32 length) +{ + uint32 count; + int offset; + uint32 cis_byte; + uint16 *cis = (uint16 *)cisd; + uint bar0 = SI_ENUM_BASE; + int status; + uint8 data; + + sd_trace(("%s: Func %d\n", __FUNCTION__, func)); + + spi_lock(sd); + + /* Set sb window address to 0x18000000 */ + data = (bar0 >> 8) & SBSDIO_SBADDRLOW_MASK; + status = bcmspi_card_bytewrite(sd, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRLOW, &data); + if (status == SUCCESS) { + data = (bar0 >> 16) & SBSDIO_SBADDRMID_MASK; + status = bcmspi_card_bytewrite(sd, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRMID, &data); + } else { + sd_err(("%s: Unable to set sb-addr-windows\n", __FUNCTION__)); + spi_unlock(sd); + return (BCME_ERROR); + } + if (status == SUCCESS) { + data = (bar0 >> 24) & SBSDIO_SBADDRHIGH_MASK; + status = bcmspi_card_bytewrite(sd, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRHIGH, &data); + } else { + sd_err(("%s: Unable to set sb-addr-windows\n", __FUNCTION__)); + spi_unlock(sd); + return (BCME_ERROR); + } + + offset = CC_OTP; /* OTP offset in chipcommon. */ + for (count = 0; count < length/2; count++) { + if (bcmspi_card_regread (sd, SDIO_FUNC_1, offset, 2, &cis_byte) < 0) { + sd_err(("%s: regread failed: Can't read CIS\n", __FUNCTION__)); + spi_unlock(sd); + return (BCME_ERROR); + } + + *cis = (uint16)cis_byte; + cis++; + offset += 2; + } + + spi_unlock(sd); + + return (BCME_OK); +} + +extern SDIOH_API_RC +sdioh_request_byte(sdioh_info_t *sd, uint rw, uint func, uint regaddr, uint8 *byte) +{ + int status; + uint32 cmd_arg; + uint32 dstatus; + uint32 data = (uint32)(*byte); + + spi_lock(sd); + + cmd_arg = 0; + cmd_arg = SFIELD(cmd_arg, SPI_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, SPI_ACCESS, 1); /* Incremental access */ + cmd_arg = SFIELD(cmd_arg, SPI_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, SPI_RW_FLAG, rw == SDIOH_READ ? 0 : 1); + cmd_arg = SFIELD(cmd_arg, SPI_LEN, 1); + + sd_trace(("%s cmd_arg = 0x%x\n", __FUNCTION__, cmd_arg)); + sd_trace(("%s: rw=%d, func=%d, regaddr=0x%08x, data=0x%x\n", __FUNCTION__, rw, func, + regaddr, data)); + + if ((status = bcmspi_cmd_issue(sd, sd->sd_use_dma, + cmd_arg, &data, 1)) != SUCCESS) { + spi_unlock(sd); + return status; + } + + if (rw == SDIOH_READ) + *byte = (uint8)data; + + bcmspi_cmd_getdstatus(sd, &dstatus); + if (dstatus) + sd_trace(("dstatus =0x%x\n", dstatus)); + + spi_unlock(sd); + return SDIOH_API_RC_SUCCESS; +} + +extern SDIOH_API_RC +sdioh_request_word(sdioh_info_t *sd, uint cmd_type, uint rw, uint func, uint addr, + uint32 *word, uint nbytes) +{ + int status; + + spi_lock(sd); + + if (rw == SDIOH_READ) + status = bcmspi_card_regread(sd, func, addr, nbytes, word); + else + status = bcmspi_card_regwrite(sd, func, addr, nbytes, *word); + + spi_unlock(sd); + return (status == SUCCESS ? SDIOH_API_RC_SUCCESS : SDIOH_API_RC_FAIL); +} + +extern SDIOH_API_RC +sdioh_request_buffer(sdioh_info_t *sd, uint pio_dma, uint fix_inc, uint rw, uint func, + uint addr, uint reg_width, uint buflen_u, uint8 *buffer, void *pkt) +{ + int len; + int buflen = (int)buflen_u; + bool fifo = (fix_inc == SDIOH_DATA_FIX); + + spi_lock(sd); + + ASSERT(reg_width == 4); + ASSERT(buflen_u < (1 << 30)); + ASSERT(sd->client_block_size[func]); + + sd_data(("%s: %c len %d r_cnt %d t_cnt %d, pkt @0x%p\n", + __FUNCTION__, rw == SDIOH_READ ? 'R' : 'W', + buflen_u, sd->r_cnt, sd->t_cnt, pkt)); + + /* Break buffer down into blocksize chunks. */ + while (buflen > 0) { + len = MIN(sd->client_block_size[func], buflen); + if (bcmspi_card_buf(sd, rw, func, fifo, addr, len, (uint32 *)buffer) != SUCCESS) { + sd_err(("%s: bcmspi_card_buf %s failed\n", + __FUNCTION__, rw == SDIOH_READ ? "Read" : "Write")); + spi_unlock(sd); + return SDIOH_API_RC_FAIL; + } + buffer += len; + buflen -= len; + if (!fifo) + addr += len; + } + spi_unlock(sd); + return SDIOH_API_RC_SUCCESS; +} + +/* This function allows write to gspi bus when another rd/wr function is deep down the call stack. + * Its main aim is to have simpler spi writes rather than recursive writes. + * e.g. When there is a need to program response delay on the fly after detecting the SPI-func + * this call will allow to program the response delay. + */ +static int +bcmspi_card_byterewrite(sdioh_info_t *sd, int func, uint32 regaddr, uint8 byte) +{ + uint32 cmd_arg; + uint32 datalen = 1; + uint32 hostlen; + + cmd_arg = 0; + + cmd_arg = SFIELD(cmd_arg, SPI_RW_FLAG, 1); + cmd_arg = SFIELD(cmd_arg, SPI_ACCESS, 1); /* Incremental access */ + cmd_arg = SFIELD(cmd_arg, SPI_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, SPI_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, SPI_LEN, datalen); + + sd_trace(("%s cmd_arg = 0x%x\n", __FUNCTION__, cmd_arg)); + + + /* Set up and issue the SPI command. MSByte goes out on bus first. Increase datalen + * according to the wordlen mode(16/32bit) the device is in. + */ + ASSERT(sd->wordlen == 4 || sd->wordlen == 2); + datalen = ROUNDUP(datalen, sd->wordlen); + + /* Start by copying command in the spi-outbuffer */ + if (sd->wordlen == 4) { /* 32bit spid */ + *(uint32 *)spi_outbuf2 = bcmswap32(cmd_arg); + if (datalen & 0x3) + datalen += (4 - (datalen & 0x3)); + } else if (sd->wordlen == 2) { /* 16bit spid */ + *(uint16 *)spi_outbuf2 = bcmswap16(cmd_arg & 0xffff); + *(uint16 *)&spi_outbuf2[2] = bcmswap16((cmd_arg & 0xffff0000) >> 16); + if (datalen & 0x1) + datalen++; + } else { + sd_err(("%s: Host is %d bit spid, could not create SPI command.\n", + __FUNCTION__, 8 * sd->wordlen)); + return ERROR; + } + + /* for Write, put the data into the output buffer */ + if (datalen != 0) { + if (sd->wordlen == 4) { /* 32bit spid */ + *(uint32 *)&spi_outbuf2[CMDLEN] = bcmswap32(byte); + } else if (sd->wordlen == 2) { /* 16bit spid */ + *(uint16 *)&spi_outbuf2[CMDLEN] = bcmswap16(byte & 0xffff); + *(uint16 *)&spi_outbuf2[CMDLEN + 2] = + bcmswap16((byte & 0xffff0000) >> 16); + } + } + + /* +4 for cmd, +4 for dstatus */ + hostlen = datalen + 8; + hostlen += (4 - (hostlen & 0x3)); + spi_sendrecv(sd, spi_outbuf2, spi_inbuf2, hostlen); + + /* Last 4bytes are dstatus. Device is configured to return status bits. */ + if (sd->wordlen == 4) { /* 32bit spid */ + sd->card_dstatus = bcmswap32(*(uint32 *)&spi_inbuf2[datalen + CMDLEN ]); + } else if (sd->wordlen == 2) { /* 16bit spid */ + sd->card_dstatus = (bcmswap16(*(uint16 *)&spi_inbuf2[datalen + CMDLEN ]) | + (bcmswap16(*(uint16 *)&spi_inbuf2[datalen + CMDLEN + 2]) << 16)); + } else { + sd_err(("%s: Host is %d bit machine, could not read SPI dstatus.\n", + __FUNCTION__, 8 * sd->wordlen)); + return ERROR; + } + + if (sd->card_dstatus) + sd_trace(("dstatus after byte rewrite = 0x%x\n", sd->card_dstatus)); + + return (BCME_OK); +} + +/* Program the response delay corresponding to the spi function */ +static int +bcmspi_prog_resp_delay(sdioh_info_t *sd, int func, uint8 resp_delay) +{ + if (sd->resp_delay_all == FALSE) + return (BCME_OK); + + if (sd->prev_fun == func) + return (BCME_OK); + + if (F0_RESPONSE_DELAY == F1_RESPONSE_DELAY) + return (BCME_OK); + + bcmspi_card_byterewrite(sd, SPI_FUNC_0, SPID_RESPONSE_DELAY, resp_delay); + + /* Remember function for which to avoid reprogramming resp-delay in next iteration */ + sd->prev_fun = func; + + return (BCME_OK); + +} + +#define GSPI_RESYNC_PATTERN 0x0 + +/* A resync pattern is a 32bit MOSI line with all zeros. Its a special command in gSPI. + * It resets the spi-bkplane logic so that all F1 related ping-pong buffer logic is + * synchronised and all queued resuests are cancelled. + */ +static int +bcmspi_resync_f1(sdioh_info_t *sd) +{ + uint32 cmd_arg = GSPI_RESYNC_PATTERN, data = 0, datalen = 0; + + + /* Set up and issue the SPI command. MSByte goes out on bus first. Increase datalen + * according to the wordlen mode(16/32bit) the device is in. + */ + ASSERT(sd->wordlen == 4 || sd->wordlen == 2); + datalen = ROUNDUP(datalen, sd->wordlen); + + /* Start by copying command in the spi-outbuffer */ + *(uint32 *)spi_outbuf2 = cmd_arg; + + /* for Write, put the data into the output buffer */ + *(uint32 *)&spi_outbuf2[CMDLEN] = data; + + /* +4 for cmd, +4 for dstatus */ + spi_sendrecv(sd, spi_outbuf2, spi_inbuf2, datalen + 8); + + /* Last 4bytes are dstatus. Device is configured to return status bits. */ + if (sd->wordlen == 4) { /* 32bit spid */ + sd->card_dstatus = bcmswap32(*(uint32 *)&spi_inbuf2[datalen + CMDLEN ]); + } else if (sd->wordlen == 2) { /* 16bit spid */ + sd->card_dstatus = (bcmswap16(*(uint16 *)&spi_inbuf2[datalen + CMDLEN ]) | + (bcmswap16(*(uint16 *)&spi_inbuf2[datalen + CMDLEN + 2]) << 16)); + } else { + sd_err(("%s: Host is %d bit machine, could not read SPI dstatus.\n", + __FUNCTION__, 8 * sd->wordlen)); + return ERROR; + } + + if (sd->card_dstatus) + sd_trace(("dstatus after resync pattern write = 0x%x\n", sd->card_dstatus)); + + return (BCME_OK); +} + +uint32 dstatus_count = 0; + +static int +bcmspi_update_stats(sdioh_info_t *sd, uint32 cmd_arg) +{ + uint32 dstatus = sd->card_dstatus; + struct spierrstats_t *spierrstats = &sd->spierrstats; + int err = SUCCESS; + + sd_trace(("cmd = 0x%x, dstatus = 0x%x\n", cmd_arg, dstatus)); + + /* Store dstatus of last few gSPI transactions */ + spierrstats->dstatus[dstatus_count % NUM_PREV_TRANSACTIONS] = dstatus; + spierrstats->spicmd[dstatus_count % NUM_PREV_TRANSACTIONS] = cmd_arg; + dstatus_count++; + + if (sd->card_init_done == FALSE) + return err; + + if (dstatus & STATUS_DATA_NOT_AVAILABLE) { + spierrstats->dna++; + sd_trace(("Read data not available on F1 addr = 0x%x\n", + GFIELD(cmd_arg, SPI_REG_ADDR))); + /* Clear dna bit */ + bcmspi_card_byterewrite(sd, SPI_FUNC_0, SPID_INTR_REG, DATA_UNAVAILABLE); + } + + if (dstatus & STATUS_UNDERFLOW) { + spierrstats->rdunderflow++; + sd_err(("FIFO underflow happened due to current F2 read command.\n")); + } + + if (dstatus & STATUS_OVERFLOW) { + spierrstats->wroverflow++; + sd_err(("FIFO overflow happened due to current (F1/F2) write command.\n")); + if ((sd->chip == BCM4329_CHIP_ID) && (sd->chiprev == 0)) { + bcmspi_card_byterewrite(sd, SPI_FUNC_0, SPID_INTR_REG, F1_OVERFLOW); + bcmspi_resync_f1(sd); + sd_err(("Recovering from F1 FIFO overflow.\n")); + } else { + err = ERROR_OF; + } + } + + if (dstatus & STATUS_F2_INTR) { + spierrstats->f2interrupt++; + sd_trace(("Interrupt from F2. SW should clear corresponding IntStatus bits\n")); + } + + if (dstatus & STATUS_F3_INTR) { + spierrstats->f3interrupt++; + sd_err(("Interrupt from F3. SW should clear corresponding IntStatus bits\n")); + } + + if (dstatus & STATUS_HOST_CMD_DATA_ERR) { + spierrstats->hostcmddataerr++; + sd_err(("Error in CMD or Host data, detected by CRC/Checksum (optional)\n")); + } + + if (dstatus & STATUS_F2_PKT_AVAILABLE) { + spierrstats->f2pktavailable++; + sd_trace(("Packet is available/ready in F2 TX FIFO\n")); + sd_trace(("Packet length = %d\n", sd->dwordmode ? + ((dstatus & STATUS_F2_PKT_LEN_MASK) >> (STATUS_F2_PKT_LEN_SHIFT - 2)) : + ((dstatus & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT))); + } + + if (dstatus & STATUS_F3_PKT_AVAILABLE) { + spierrstats->f3pktavailable++; + sd_err(("Packet is available/ready in F3 TX FIFO\n")); + sd_err(("Packet length = %d\n", + (dstatus & STATUS_F3_PKT_LEN_MASK) >> STATUS_F3_PKT_LEN_SHIFT)); + } + + return err; +} + +extern int +sdioh_abort(sdioh_info_t *sd, uint func) +{ + return 0; +} + +int +sdioh_start(sdioh_info_t *sd, int stage) +{ + return SUCCESS; +} + +int +sdioh_stop(sdioh_info_t *sd) +{ + return SUCCESS; +} + + + +/* + * Private/Static work routines + */ +static int +bcmspi_host_init(sdioh_info_t *sd) +{ + + /* Default power on mode */ + sd->sd_mode = SDIOH_MODE_SPI; + sd->polled_mode = TRUE; + sd->host_init_done = TRUE; + sd->card_init_done = FALSE; + sd->adapter_slot = 1; + + return (SUCCESS); +} + +static int +get_client_blocksize(sdioh_info_t *sd) +{ + uint32 regdata[2]; + int status; + + /* Find F1/F2/F3 max packet size */ + if ((status = bcmspi_card_regread(sd, 0, SPID_F1_INFO_REG, + 8, regdata)) != SUCCESS) { + return status; + } + + sd_trace(("pkt_size regdata[0] = 0x%x, regdata[1] = 0x%x\n", + regdata[0], regdata[1])); + + sd->client_block_size[1] = (regdata[0] & F1_MAX_PKT_SIZE) >> 2; + sd_trace(("Func1 blocksize = %d\n", sd->client_block_size[1])); + ASSERT(sd->client_block_size[1] == BLOCK_SIZE_F1); + + sd->client_block_size[2] = ((regdata[0] >> 16) & F2_MAX_PKT_SIZE) >> 2; + sd_trace(("Func2 blocksize = %d\n", sd->client_block_size[2])); + ASSERT(sd->client_block_size[2] == BLOCK_SIZE_F2); + + sd->client_block_size[3] = (regdata[1] & F3_MAX_PKT_SIZE) >> 2; + sd_trace(("Func3 blocksize = %d\n", sd->client_block_size[3])); + ASSERT(sd->client_block_size[3] == BLOCK_SIZE_F3); + + return 0; +} + +static int +bcmspi_client_init(sdioh_info_t *sd) +{ + uint32 status_en_reg = 0; + sd_trace(("%s: Powering up slot %d\n", __FUNCTION__, sd->adapter_slot)); + +#ifdef HSMODE + if (!spi_start_clock(sd, (uint16)sd_divisor)) { + sd_err(("spi_start_clock failed\n")); + return ERROR; + } +#else + /* Start at ~400KHz clock rate for initialization */ + if (!spi_start_clock(sd, 128)) { + sd_err(("spi_start_clock failed\n")); + return ERROR; + } +#endif /* HSMODE */ + + if (!bcmspi_host_device_init_adapt(sd)) { + sd_err(("bcmspi_host_device_init_adapt failed\n")); + return ERROR; + } + + if (!bcmspi_test_card(sd)) { + sd_err(("bcmspi_test_card failed\n")); + return ERROR; + } + + sd->num_funcs = SPI_MAX_IOFUNCS; + + get_client_blocksize(sd); + + /* Apply resync pattern cmd with all zeros to reset spi-bkplane F1 logic */ + bcmspi_resync_f1(sd); + + sd->dwordmode = FALSE; + + bcmspi_card_regread(sd, 0, SPID_STATUS_ENABLE, 1, &status_en_reg); + + sd_trace(("%s: Enabling interrupt with dstatus \n", __FUNCTION__)); + status_en_reg |= INTR_WITH_STATUS; + + + if (bcmspi_card_regwrite(sd, SPI_FUNC_0, SPID_STATUS_ENABLE, 1, + status_en_reg & 0xff) != SUCCESS) { + sd_err(("%s: Unable to set response delay for all fun's.\n", __FUNCTION__)); + return ERROR; + } + + +#ifndef HSMODE + /* After configuring for High-Speed mode, set the desired clock rate. */ + if (!spi_start_clock(sd, 4)) { + sd_err(("spi_start_clock failed\n")); + return ERROR; + } +#endif /* HSMODE */ + + sd->card_init_done = TRUE; + + + return SUCCESS; +} + +static int +bcmspi_set_highspeed_mode(sdioh_info_t *sd, bool hsmode) +{ + uint32 regdata; + int status; + + if ((status = bcmspi_card_regread(sd, 0, SPID_CONFIG, + 4, ®data)) != SUCCESS) + return status; + + sd_trace(("In %s spih-ctrl = 0x%x \n", __FUNCTION__, regdata)); + + + if (hsmode == TRUE) { + sd_trace(("Attempting to enable High-Speed mode.\n")); + + if (regdata & HIGH_SPEED_MODE) { + sd_trace(("Device is already in High-Speed mode.\n")); + return status; + } else { + regdata |= HIGH_SPEED_MODE; + sd_trace(("Writing %08x to device at %08x\n", regdata, SPID_CONFIG)); + if ((status = bcmspi_card_regwrite(sd, 0, SPID_CONFIG, + 4, regdata)) != SUCCESS) { + return status; + } + } + } else { + sd_trace(("Attempting to disable High-Speed mode.\n")); + + if (regdata & HIGH_SPEED_MODE) { + regdata &= ~HIGH_SPEED_MODE; + sd_trace(("Writing %08x to device at %08x\n", regdata, SPID_CONFIG)); + if ((status = bcmspi_card_regwrite(sd, 0, SPID_CONFIG, + 4, regdata)) != SUCCESS) + return status; + } + else { + sd_trace(("Device is already in Low-Speed mode.\n")); + return status; + } + } + + spi_controller_highspeed_mode(sd, hsmode); + + return TRUE; +} + +#define bcmspi_find_curr_mode(sd) { \ + sd->wordlen = 2; \ + status = bcmspi_card_regread_fixedaddr(sd, 0, SPID_TEST_READ, 4, ®data); \ + regdata &= 0xff; \ + if ((regdata == 0xad) || (regdata == 0x5b) || \ + (regdata == 0x5d) || (regdata == 0x5a)) \ + break; \ + sd->wordlen = 4; \ + status = bcmspi_card_regread_fixedaddr(sd, 0, SPID_TEST_READ, 4, ®data); \ + regdata &= 0xff; \ + if ((regdata == 0xad) || (regdata == 0x5b) || \ + (regdata == 0x5d) || (regdata == 0x5a)) \ + break; \ + sd_trace(("Silicon testability issue: regdata = 0x%x." \ + " Expected 0xad, 0x5a, 0x5b or 0x5d.\n", regdata)); \ + OSL_DELAY(100000); \ +} + +#define INIT_ADAPT_LOOP 100 + +/* Adapt clock-phase-speed-bitwidth between host and device */ +static bool +bcmspi_host_device_init_adapt(sdioh_info_t *sd) +{ + uint32 wrregdata, regdata = 0; + int status; + int i; + + /* Due to a silicon testability issue, the first command from the Host + * to the device will get corrupted (first bit will be lost). So the + * Host should poll the device with a safe read request. ie: The Host + * should try to read F0 addr 0x14 using the Fixed address mode + * (This will prevent a unintended write command to be detected by device) + */ + for (i = 0; i < INIT_ADAPT_LOOP; i++) { + /* If device was not power-cycled it will stay in 32bit mode with + * response-delay-all bit set. Alternate the iteration so that + * read either with or without response-delay for F0 to succeed. + */ + bcmspi_find_curr_mode(sd); + sd->resp_delay_all = (i & 0x1) ? TRUE : FALSE; + + bcmspi_find_curr_mode(sd); + sd->dwordmode = TRUE; + + bcmspi_find_curr_mode(sd); + sd->dwordmode = FALSE; + } + + /* Bail out, device not detected */ + if (i == INIT_ADAPT_LOOP) + return FALSE; + + /* Softreset the spid logic */ + if ((sd->dwordmode) || (sd->wordlen == 4)) { + bcmspi_card_regwrite(sd, 0, SPID_RESET_BP, 1, RESET_ON_WLAN_BP_RESET|RESET_SPI); + bcmspi_card_regread(sd, 0, SPID_RESET_BP, 1, ®data); + sd_trace(("reset reg read = 0x%x\n", regdata)); + sd_trace(("dwordmode = %d, wordlen = %d, resp_delay_all = %d\n", sd->dwordmode, + sd->wordlen, sd->resp_delay_all)); + /* Restore default state after softreset */ + sd->wordlen = 2; + sd->dwordmode = FALSE; + } + + if (sd->wordlen == 4) { + if ((status = bcmspi_card_regread(sd, 0, SPID_TEST_READ, 4, ®data)) != + SUCCESS) + return FALSE; + if (regdata == TEST_RO_DATA_32BIT_LE) { + sd_trace(("Spid is already in 32bit LE mode. Value read = 0x%x\n", + regdata)); + sd_trace(("Spid power was left on.\n")); + } else { + sd_err(("Spid power was left on but signature read failed." + " Value read = 0x%x\n", regdata)); + return FALSE; + } + } else { + sd->wordlen = 2; + +#define CTRL_REG_DEFAULT 0x00010430 /* according to the host m/c */ + + wrregdata = (CTRL_REG_DEFAULT); + sd->resp_delay_all = TRUE; + if (sd->resp_delay_all == TRUE) { + /* Enable response delay for all */ + wrregdata |= (RESP_DELAY_ALL << 16); + /* Program response delay value */ + wrregdata &= 0xffff00ff; + wrregdata |= (F1_RESPONSE_DELAY << 8); + sd->prev_fun = SPI_FUNC_1; + bcmspi_card_regwrite(sd, 0, SPID_CONFIG, 4, wrregdata); + } + + if ((status = bcmspi_card_regread(sd, 0, SPID_TEST_READ, 4, ®data)) != SUCCESS) + return FALSE; + sd_trace(("(we are still in 16bit mode) 32bit READ LE regdata = 0x%x\n", regdata)); + +#ifndef HSMODE + wrregdata |= (CLOCK_PHASE | CLOCK_POLARITY); + wrregdata &= ~HIGH_SPEED_MODE; + bcmspi_card_regwrite(sd, 0, SPID_CONFIG, 4, wrregdata); +#endif /* HSMODE */ + + for (i = 0; i < INIT_ADAPT_LOOP; i++) { + if ((regdata == 0xfdda7d5b) || (regdata == 0xfdda7d5a)) { + sd_trace(("0xfeedbead was leftshifted by 1-bit.\n")); + if ((status = bcmspi_card_regread(sd, 0, SPID_TEST_READ, 4, + ®data)) != SUCCESS) + return FALSE; + } + OSL_DELAY(1000); + } + + + /* Change to host controller intr-polarity of active-low */ + wrregdata &= ~INTR_POLARITY; + sd_trace(("(we are still in 16bit mode) 32bit Write LE reg-ctrl-data = 0x%x\n", + wrregdata)); + /* Change to 32bit mode */ + wrregdata |= WORD_LENGTH_32; + bcmspi_card_regwrite(sd, 0, SPID_CONFIG, 4, wrregdata); + + /* Change command/data packaging in 32bit LE mode */ + sd->wordlen = 4; + + if ((status = bcmspi_card_regread(sd, 0, SPID_TEST_READ, 4, ®data)) != SUCCESS) + return FALSE; + + if (regdata == TEST_RO_DATA_32BIT_LE) { + sd_trace(("Read spid passed. Value read = 0x%x\n", regdata)); + sd_trace(("Spid had power-on cycle OR spi was soft-resetted \n")); + } else { + sd_err(("Stale spid reg values read as it was kept powered. Value read =" + "0x%x\n", regdata)); + return FALSE; + } + } + + + return TRUE; +} + +static bool +bcmspi_test_card(sdioh_info_t *sd) +{ + uint32 regdata; + int status; + + if ((status = bcmspi_card_regread(sd, 0, SPID_TEST_READ, 4, ®data)) != SUCCESS) + return FALSE; + + if (regdata == (TEST_RO_DATA_32BIT_LE)) + sd_trace(("32bit LE regdata = 0x%x\n", regdata)); + else { + sd_trace(("Incorrect 32bit LE regdata = 0x%x\n", regdata)); + return FALSE; + } + + +#define RW_PATTERN1 0xA0A1A2A3 +#define RW_PATTERN2 0x4B5B6B7B + + regdata = RW_PATTERN1; + if ((status = bcmspi_card_regwrite(sd, 0, SPID_TEST_RW, 4, regdata)) != SUCCESS) + return FALSE; + regdata = 0; + if ((status = bcmspi_card_regread(sd, 0, SPID_TEST_RW, 4, ®data)) != SUCCESS) + return FALSE; + if (regdata != RW_PATTERN1) { + sd_err(("Write-Read spid failed. Value wrote = 0x%x, Value read = 0x%x\n", + RW_PATTERN1, regdata)); + return FALSE; + } else + sd_trace(("R/W spid passed. Value read = 0x%x\n", regdata)); + + regdata = RW_PATTERN2; + if ((status = bcmspi_card_regwrite(sd, 0, SPID_TEST_RW, 4, regdata)) != SUCCESS) + return FALSE; + regdata = 0; + if ((status = bcmspi_card_regread(sd, 0, SPID_TEST_RW, 4, ®data)) != SUCCESS) + return FALSE; + if (regdata != RW_PATTERN2) { + sd_err(("Write-Read spid failed. Value wrote = 0x%x, Value read = 0x%x\n", + RW_PATTERN2, regdata)); + return FALSE; + } else + sd_trace(("R/W spid passed. Value read = 0x%x\n", regdata)); + + return TRUE; +} + +static int +bcmspi_driver_init(sdioh_info_t *sd) +{ + sd_trace(("%s\n", __FUNCTION__)); + if ((bcmspi_host_init(sd)) != SUCCESS) { + return ERROR; + } + + if (bcmspi_client_init(sd) != SUCCESS) { + return ERROR; + } + + return SUCCESS; +} + +/* Read device reg */ +static int +bcmspi_card_regread(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 *data) +{ + int status; + uint32 cmd_arg, dstatus; + + ASSERT(regsize); + + if (func == 2) + sd_trace(("Reg access on F2 will generate error indication in dstatus bits.\n")); + + cmd_arg = 0; + cmd_arg = SFIELD(cmd_arg, SPI_RW_FLAG, 0); + cmd_arg = SFIELD(cmd_arg, SPI_ACCESS, 1); /* Incremental access */ + cmd_arg = SFIELD(cmd_arg, SPI_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, SPI_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, SPI_LEN, regsize == BLOCK_SIZE_F2 ? 0 : regsize); + + sd_trace(("%s cmd_arg = 0x%x\n", __FUNCTION__, cmd_arg)); + sd_trace(("%s: rw=%d, func=%d, regaddr=0x%08x, data=0x%x\n", __FUNCTION__, 0, func, + regaddr, *data)); + + if ((status = bcmspi_cmd_issue(sd, sd->sd_use_dma, cmd_arg, data, regsize)) + != SUCCESS) + return status; + + bcmspi_cmd_getdstatus(sd, &dstatus); + if (dstatus) + sd_trace(("dstatus =0x%x\n", dstatus)); + + return SUCCESS; +} + +static int +bcmspi_card_regread_fixedaddr(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 *data) +{ + + int status; + uint32 cmd_arg; + uint32 dstatus; + + ASSERT(regsize); + + if (func == 2) + sd_trace(("Reg access on F2 will generate error indication in dstatus bits.\n")); + + cmd_arg = 0; + cmd_arg = SFIELD(cmd_arg, SPI_RW_FLAG, 0); + cmd_arg = SFIELD(cmd_arg, SPI_ACCESS, 0); /* Fixed access */ + cmd_arg = SFIELD(cmd_arg, SPI_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, SPI_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, SPI_LEN, regsize); + + sd_trace(("%s cmd_arg = 0x%x\n", __FUNCTION__, cmd_arg)); + + if ((status = bcmspi_cmd_issue(sd, sd->sd_use_dma, cmd_arg, data, regsize)) + != SUCCESS) + return status; + + sd_trace(("%s: rw=%d, func=%d, regaddr=0x%08x, data=0x%x\n", __FUNCTION__, 0, func, + regaddr, *data)); + + bcmspi_cmd_getdstatus(sd, &dstatus); + sd_trace(("dstatus =0x%x\n", dstatus)); + return SUCCESS; +} + +/* write a device register */ +static int +bcmspi_card_regwrite(sdioh_info_t *sd, int func, uint32 regaddr, int regsize, uint32 data) +{ + int status; + uint32 cmd_arg, dstatus; + + ASSERT(regsize); + + cmd_arg = 0; + + cmd_arg = SFIELD(cmd_arg, SPI_RW_FLAG, 1); + cmd_arg = SFIELD(cmd_arg, SPI_ACCESS, 1); /* Incremental access */ + cmd_arg = SFIELD(cmd_arg, SPI_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, SPI_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, SPI_LEN, regsize == BLOCK_SIZE_F2 ? 0 : regsize); + + sd_trace(("%s cmd_arg = 0x%x\n", __FUNCTION__, cmd_arg)); + sd_trace(("%s: rw=%d, func=%d, regaddr=0x%08x, data=0x%x\n", __FUNCTION__, 1, func, + regaddr, data)); + + + if ((status = bcmspi_cmd_issue(sd, sd->sd_use_dma, cmd_arg, &data, regsize)) + != SUCCESS) + return status; + + bcmspi_cmd_getdstatus(sd, &dstatus); + if (dstatus) + sd_trace(("dstatus =0x%x\n", dstatus)); + + return SUCCESS; +} + +/* write a device register - 1 byte */ +static int +bcmspi_card_bytewrite(sdioh_info_t *sd, int func, uint32 regaddr, uint8 *byte) +{ + int status; + uint32 cmd_arg; + uint32 dstatus; + uint32 data = (uint32)(*byte); + + cmd_arg = 0; + cmd_arg = SFIELD(cmd_arg, SPI_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, SPI_ACCESS, 1); /* Incremental access */ + cmd_arg = SFIELD(cmd_arg, SPI_REG_ADDR, regaddr); + cmd_arg = SFIELD(cmd_arg, SPI_RW_FLAG, 1); + cmd_arg = SFIELD(cmd_arg, SPI_LEN, 1); + + sd_trace(("%s cmd_arg = 0x%x\n", __FUNCTION__, cmd_arg)); + sd_trace(("%s: func=%d, regaddr=0x%08x, data=0x%x\n", __FUNCTION__, func, + regaddr, data)); + + if ((status = bcmspi_cmd_issue(sd, sd->sd_use_dma, + cmd_arg, &data, 1)) != SUCCESS) { + return status; + } + + bcmspi_cmd_getdstatus(sd, &dstatus); + if (dstatus) + sd_trace(("dstatus =0x%x\n", dstatus)); + + return SUCCESS; +} + +void +bcmspi_cmd_getdstatus(sdioh_info_t *sd, uint32 *dstatus_buffer) +{ + *dstatus_buffer = sd->card_dstatus; +} + +/* 'data' is of type uint32 whereas other buffers are of type uint8 */ +static int +bcmspi_cmd_issue(sdioh_info_t *sd, bool use_dma, uint32 cmd_arg, + uint32 *data, uint32 datalen) +{ + uint32 i, j; + uint8 resp_delay = 0; + int err = SUCCESS; + uint32 hostlen; + uint32 spilen = 0; + uint32 dstatus_idx = 0; + uint16 templen, buslen, len, *ptr = NULL; + + sd_trace(("spi cmd = 0x%x\n", cmd_arg)); + + if (DWORDMODE_ON) { + spilen = GFIELD(cmd_arg, SPI_LEN); + if ((GFIELD(cmd_arg, SPI_FUNCTION) == SPI_FUNC_0) || + (GFIELD(cmd_arg, SPI_FUNCTION) == SPI_FUNC_1)) + dstatus_idx = spilen * 3; + + if ((GFIELD(cmd_arg, SPI_FUNCTION) == SPI_FUNC_2) && + (GFIELD(cmd_arg, SPI_RW_FLAG) == 1)) { + spilen = spilen << 2; + dstatus_idx = (spilen % 16) ? (16 - (spilen % 16)) : 0; + /* convert len to mod16 size */ + spilen = ROUNDUP(spilen, 16); + cmd_arg = SFIELD(cmd_arg, SPI_LEN, (spilen >> 2)); + } + } + + /* Set up and issue the SPI command. MSByte goes out on bus first. Increase datalen + * according to the wordlen mode(16/32bit) the device is in. + */ + if (sd->wordlen == 4) { /* 32bit spid */ + *(uint32 *)spi_outbuf = bcmswap32(cmd_arg); + if (datalen & 0x3) + datalen += (4 - (datalen & 0x3)); + } else if (sd->wordlen == 2) { /* 16bit spid */ + *(uint16 *)spi_outbuf = bcmswap16(cmd_arg & 0xffff); + *(uint16 *)&spi_outbuf[2] = bcmswap16((cmd_arg & 0xffff0000) >> 16); + if (datalen & 0x1) + datalen++; + if (datalen < 4) + datalen = ROUNDUP(datalen, 4); + } else { + sd_err(("Host is %d bit spid, could not create SPI command.\n", + 8 * sd->wordlen)); + return ERROR; + } + + /* for Write, put the data into the output buffer */ + if (GFIELD(cmd_arg, SPI_RW_FLAG) == 1) { + /* We send len field of hw-header always a mod16 size, both from host and dongle */ + if (DWORDMODE_ON) { + if (GFIELD(cmd_arg, SPI_FUNCTION) == SPI_FUNC_2) { + ptr = (uint16 *)&data[0]; + templen = *ptr; + /* ASSERT(*ptr == ~*(ptr + 1)); */ + templen = ROUNDUP(templen, 16); + *ptr = templen; + sd_trace(("actual tx len = %d\n", (uint16)(~*(ptr+1)))); + } + } + + if (datalen != 0) { + for (i = 0; i < datalen/4; i++) { + if (sd->wordlen == 4) { /* 32bit spid */ + *(uint32 *)&spi_outbuf[i * 4 + CMDLEN] = + bcmswap32(data[i]); + } else if (sd->wordlen == 2) { /* 16bit spid */ + *(uint16 *)&spi_outbuf[i * 4 + CMDLEN] = + bcmswap16(data[i] & 0xffff); + *(uint16 *)&spi_outbuf[i * 4 + CMDLEN + 2] = + bcmswap16((data[i] & 0xffff0000) >> 16); + } + } + } + } + + /* Append resp-delay number of bytes and clock them out for F0/1/2 reads. */ + if (GFIELD(cmd_arg, SPI_RW_FLAG) == 0) { + int func = GFIELD(cmd_arg, SPI_FUNCTION); + switch (func) { + case 0: + resp_delay = sd->resp_delay_all ? F0_RESPONSE_DELAY : 0; + break; + case 1: + resp_delay = F1_RESPONSE_DELAY; + break; + case 2: + resp_delay = sd->resp_delay_all ? F2_RESPONSE_DELAY : 0; + break; + default: + ASSERT(0); + break; + } + /* Program response delay */ + bcmspi_prog_resp_delay(sd, func, resp_delay); + } + + /* +4 for cmd and +4 for dstatus */ + hostlen = datalen + 8 + resp_delay; + hostlen += dstatus_idx; + hostlen += (4 - (hostlen & 0x3)); + spi_sendrecv(sd, spi_outbuf, spi_inbuf, hostlen); + + /* for Read, get the data into the input buffer */ + if (datalen != 0) { + if (GFIELD(cmd_arg, SPI_RW_FLAG) == 0) { /* if read cmd */ + for (j = 0; j < datalen/4; j++) { + if (sd->wordlen == 4) { /* 32bit spid */ + data[j] = bcmswap32(*(uint32 *)&spi_inbuf[j * 4 + + CMDLEN + resp_delay]); + } else if (sd->wordlen == 2) { /* 16bit spid */ + data[j] = (bcmswap16(*(uint16 *)&spi_inbuf[j * 4 + + CMDLEN + resp_delay])) | + ((bcmswap16(*(uint16 *)&spi_inbuf[j * 4 + + CMDLEN + resp_delay + 2])) << 16); + } + } + + if ((DWORDMODE_ON) && (GFIELD(cmd_arg, SPI_FUNCTION) == SPI_FUNC_2)) { + ptr = (uint16 *)&data[0]; + templen = *ptr; + buslen = len = ~(*(ptr + 1)); + buslen = ROUNDUP(buslen, 16); + /* populate actual len in hw-header */ + if (templen == buslen) + *ptr = len; + } + } + } + + /* Restore back the len field of the hw header */ + if (DWORDMODE_ON) { + if ((GFIELD(cmd_arg, SPI_FUNCTION) == SPI_FUNC_2) && + (GFIELD(cmd_arg, SPI_RW_FLAG) == 1)) { + ptr = (uint16 *)&data[0]; + *ptr = (uint16)(~*(ptr+1)); + } + } + + dstatus_idx += (datalen + CMDLEN + resp_delay); + /* Last 4bytes are dstatus. Device is configured to return status bits. */ + if (sd->wordlen == 4) { /* 32bit spid */ + sd->card_dstatus = bcmswap32(*(uint32 *)&spi_inbuf[dstatus_idx]); + } else if (sd->wordlen == 2) { /* 16bit spid */ + sd->card_dstatus = (bcmswap16(*(uint16 *)&spi_inbuf[dstatus_idx]) | + (bcmswap16(*(uint16 *)&spi_inbuf[dstatus_idx + 2]) << 16)); + } else { + sd_err(("Host is %d bit machine, could not read SPI dstatus.\n", + 8 * sd->wordlen)); + return ERROR; + } + if (sd->card_dstatus == 0xffffffff) { + sd_err(("looks like not a GSPI device or device is not powered.\n")); + } + + err = bcmspi_update_stats(sd, cmd_arg); + + return err; + +} + +static int +bcmspi_card_buf(sdioh_info_t *sd, int rw, int func, bool fifo, + uint32 addr, int nbytes, uint32 *data) +{ + int status; + uint32 cmd_arg; + bool write = rw == SDIOH_READ ? 0 : 1; + uint retries = 0; + + bool enable; + uint32 spilen; + + cmd_arg = 0; + + ASSERT(nbytes); + ASSERT(nbytes <= sd->client_block_size[func]); + + if (write) sd->t_cnt++; else sd->r_cnt++; + + if (func == 2) { + /* Frame len check limited by gSPI. */ + if ((nbytes > 2000) && write) { + sd_trace((">2KB write: F2 wr of %d bytes\n", nbytes)); + } + /* ASSERT(nbytes <= 2048); Fix bigger len gspi issue and uncomment. */ + /* If F2 fifo on device is not ready to receive data, don't do F2 transfer */ + if (write) { + uint32 dstatus; + /* check F2 ready with cached one */ + bcmspi_cmd_getdstatus(sd, &dstatus); + if ((dstatus & STATUS_F2_RX_READY) == 0) { + retries = WAIT_F2RXFIFORDY; + enable = 0; + while (retries-- && !enable) { + OSL_DELAY(WAIT_F2RXFIFORDY_DELAY * 1000); + bcmspi_card_regread(sd, SPI_FUNC_0, SPID_STATUS_REG, 4, + &dstatus); + if (dstatus & STATUS_F2_RX_READY) + enable = TRUE; + } + if (!enable) { + struct spierrstats_t *spierrstats = &sd->spierrstats; + spierrstats->f2rxnotready++; + sd_err(("F2 FIFO is not ready to receive data.\n")); + return ERROR; + } + sd_trace(("No of retries on F2 ready %d\n", + (WAIT_F2RXFIFORDY - retries))); + } + } + } + + /* F2 transfers happen on 0 addr */ + addr = (func == 2) ? 0 : addr; + + /* In pio mode buffer is read using fixed address fifo in func 1 */ + if ((func == 1) && (fifo)) + cmd_arg = SFIELD(cmd_arg, SPI_ACCESS, 0); + else + cmd_arg = SFIELD(cmd_arg, SPI_ACCESS, 1); + + cmd_arg = SFIELD(cmd_arg, SPI_FUNCTION, func); + cmd_arg = SFIELD(cmd_arg, SPI_REG_ADDR, addr); + cmd_arg = SFIELD(cmd_arg, SPI_RW_FLAG, write); + spilen = sd->data_xfer_count = MIN(sd->client_block_size[func], nbytes); + if ((sd->dwordmode == TRUE) && (GFIELD(cmd_arg, SPI_FUNCTION) == SPI_FUNC_2)) { + /* convert len to mod4 size */ + spilen = spilen + ((spilen & 0x3) ? (4 - (spilen & 0x3)): 0); + cmd_arg = SFIELD(cmd_arg, SPI_LEN, (spilen >> 2)); + } else + cmd_arg = SFIELD(cmd_arg, SPI_LEN, spilen); + + if ((func == 2) && (fifo == 1)) { + sd_data(("%s: %s func %d, %s, addr 0x%x, len %d bytes, r_cnt %d t_cnt %d\n", + __FUNCTION__, write ? "Wr" : "Rd", func, "INCR", + addr, nbytes, sd->r_cnt, sd->t_cnt)); + } + + sd_trace(("%s cmd_arg = 0x%x\n", __FUNCTION__, cmd_arg)); + sd_data(("%s: %s func %d, %s, addr 0x%x, len %d bytes, r_cnt %d t_cnt %d\n", + __FUNCTION__, write ? "Wd" : "Rd", func, "INCR", + addr, nbytes, sd->r_cnt, sd->t_cnt)); + + + if ((status = bcmspi_cmd_issue(sd, sd->sd_use_dma, cmd_arg, + data, nbytes)) != SUCCESS) { + sd_err(("%s: cmd_issue failed for %s\n", __FUNCTION__, + (write ? "write" : "read"))); + return status; + } + + /* gSPI expects that hw-header-len is equal to spi-command-len */ + if ((func == 2) && (rw == SDIOH_WRITE) && (sd->dwordmode == FALSE)) { + ASSERT((uint16)sd->data_xfer_count == (uint16)(*data & 0xffff)); + ASSERT((uint16)sd->data_xfer_count == (uint16)(~((*data & 0xffff0000) >> 16))); + } + + if ((nbytes > 2000) && !write) { + sd_trace((">2KB read: F2 rd of %d bytes\n", nbytes)); + } + + return SUCCESS; +} + +/* Reset and re-initialize the device */ +int +sdioh_sdio_reset(sdioh_info_t *si) +{ + si->card_init_done = FALSE; + return bcmspi_client_init(si); +} diff --git a/drivers/net/wireless/libra/Makefile b/drivers/net/wireless/libra/Makefile new file mode 100644 index 00000000000..3c606ba2c6c --- /dev/null +++ b/drivers/net/wireless/libra/Makefile @@ -0,0 +1,14 @@ + +# Makefile for wlan sdio if driver + +librasdioif-objs += libra_sdioif.o + +ifdef CONFIG_ARCH_MSM8X60 + librasdioif-objs += qcomwlan_pwrif.o +endif + +ifdef CONFIG_ARCH_MSM7X27A + librasdioif-objs += qcomwlan7x27a_pwrif.o +endif + +obj-$(CONFIG_LIBRA_SDIOIF) += librasdioif.o diff --git a/drivers/net/wireless/libra/libra_sdioif.c b/drivers/net/wireless/libra/libra_sdioif.c new file mode 100644 index 00000000000..3955642de81 --- /dev/null +++ b/drivers/net/wireless/libra/libra_sdioif.c @@ -0,0 +1,481 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +/* Libra SDIO function device */ +static struct sdio_func *libra_sdio_func; +static struct mmc_host *libra_mmc_host; +static int libra_mmc_host_index; + +/* SDIO Card ID / Device ID */ +static unsigned short libra_sdio_card_id; + +static suspend_handler_t *libra_suspend_hldr; +static resume_handler_t *libra_resume_hldr; + +/** + * libra_sdio_configure() - Function to configure the SDIO device param + * @libra_sdio_rxhandler Rx handler + * @func_drv_fn Function driver function for special setup + * @funcdrv_timeout Function Enable timeout + * @blksize Block size + * + * Configure SDIO device, enable function and set block size + */ +int libra_sdio_configure(sdio_irq_handler_t libra_sdio_rxhandler, + void (*func_drv_fn)(int *status), + unsigned int funcdrv_timeout, unsigned int blksize) +{ + int err_ret = 0; + struct sdio_func *func = libra_sdio_func; + + if (libra_sdio_func == NULL) { + printk(KERN_ERR "%s: Error SDIO card not detected\n", __func__); + goto cfg_error; + } + + sdio_claim_host(func); + + /* Currently block sizes are set here. */ + func->max_blksize = blksize; + if (sdio_set_block_size(func, blksize)) { + printk(KERN_ERR "%s: Unable to set the block size.\n", + __func__); + sdio_release_host(func); + goto cfg_error; + } + + /* Function driver specific configuration. */ + if (func_drv_fn) { + (*func_drv_fn)(&err_ret); + if (err_ret) { + printk(KERN_ERR "%s: function driver provided configure function error=%d\n", + __func__, err_ret); + sdio_release_host(func); + goto cfg_error; + } + } + + /* We set this based on the function card. */ + func->enable_timeout = funcdrv_timeout; + err_ret = sdio_enable_func(func); + if (err_ret != 0) { + printk(KERN_ERR "%s: Unable to enable function %d\n", + __func__, err_ret); + sdio_release_host(func); + goto cfg_error; + } + + if (sdio_claim_irq(func, libra_sdio_rxhandler)) { + sdio_disable_func(func); + printk(KERN_ERR "%s: Unable to claim irq.\n", __func__); + sdio_release_host(func); + goto cfg_error; + } + + sdio_release_host(func); + + return 0; + +cfg_error: + return -1; + +} +EXPORT_SYMBOL(libra_sdio_configure); + +int libra_sdio_configure_suspend_resume( + suspend_handler_t *libra_sdio_suspend_hdlr, + resume_handler_t *libra_sdio_resume_hdlr) +{ + libra_suspend_hldr = libra_sdio_suspend_hdlr; + libra_resume_hldr = libra_sdio_resume_hdlr; + return 0; +} +EXPORT_SYMBOL(libra_sdio_configure_suspend_resume); + +/* + * libra_sdio_deconfigure() - Function to reset the SDIO device param + */ +void libra_sdio_deconfigure(struct sdio_func *func) +{ + if (NULL == libra_sdio_func) + return; + + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); +} +EXPORT_SYMBOL(libra_sdio_deconfigure); + +int libra_enable_sdio_irq(struct sdio_func *func, u8 enable) +{ + if (libra_mmc_host && libra_mmc_host->ops && + libra_mmc_host->ops->enable_sdio_irq) { + libra_mmc_host->ops->enable_sdio_irq(libra_mmc_host, enable); + return 0; + } + + printk(KERN_ERR "%s: Could not enable disable irq\n", __func__); + return -EINVAL; +} +EXPORT_SYMBOL(libra_enable_sdio_irq); + +int libra_disable_sdio_irq_capability(struct sdio_func *func, u8 disable) +{ + if (libra_mmc_host) { + if (disable) + libra_mmc_host->caps &= ~MMC_CAP_SDIO_IRQ; + else + libra_mmc_host->caps |= MMC_CAP_SDIO_IRQ; + return 0; + } + printk(KERN_ERR "%s: Could not change sdio capabilities to polling\n", + __func__); + return -EINVAL; +} +EXPORT_SYMBOL(libra_disable_sdio_irq_capability); + +/* + * libra_sdio_release_irq() - Function to release IRQ + */ +void libra_sdio_release_irq(struct sdio_func *func) +{ + if (NULL == libra_sdio_func) + return; + + sdio_release_irq(func); +} +EXPORT_SYMBOL(libra_sdio_release_irq); + +/* + * libra_sdio_disable_func() - Function to disable sdio func + */ +void libra_sdio_disable_func(struct sdio_func *func) +{ + if (NULL == libra_sdio_func) + return; + + sdio_disable_func(func); +} +EXPORT_SYMBOL(libra_sdio_disable_func); + +/* + * Return the SDIO Function device + */ +struct sdio_func *libra_getsdio_funcdev(void) +{ + return libra_sdio_func; +} +EXPORT_SYMBOL(libra_getsdio_funcdev); + +/* + * Set function driver as the private data for the function device + */ +void libra_sdio_setprivdata(struct sdio_func *sdio_func_dev, + void *padapter) +{ + if (NULL == libra_sdio_func) + return; + + sdio_set_drvdata(sdio_func_dev, padapter); +} +EXPORT_SYMBOL(libra_sdio_setprivdata); + +/* + * Return private data of the function device. + */ +void *libra_sdio_getprivdata(struct sdio_func *sdio_func_dev) +{ + return sdio_get_drvdata(sdio_func_dev); +} +EXPORT_SYMBOL(libra_sdio_getprivdata); + +/* + * Function driver claims the SDIO device + */ +void libra_claim_host(struct sdio_func *sdio_func_dev, + pid_t *curr_claimed, pid_t current_pid, atomic_t *claim_count) +{ + if (NULL == libra_sdio_func) + return; + + if (*curr_claimed == current_pid) { + atomic_inc(claim_count); + return; + } + + /* Go ahead and claim the host if not locked by anybody. */ + sdio_claim_host(sdio_func_dev); + + *curr_claimed = current_pid; + atomic_inc(claim_count); + +} +EXPORT_SYMBOL(libra_claim_host); + +/* + * Function driver releases the SDIO device + */ +void libra_release_host(struct sdio_func *sdio_func_dev, + pid_t *curr_claimed, pid_t current_pid, atomic_t *claim_count) +{ + + if (NULL == libra_sdio_func) + return; + + if (*curr_claimed != current_pid) { + /* Dont release */ + return; + } + + atomic_dec(claim_count); + if (atomic_read(claim_count) == 0) { + *curr_claimed = 0; + sdio_release_host(sdio_func_dev); + } +} +EXPORT_SYMBOL(libra_release_host); + +void libra_sdiocmd52(struct sdio_func *sdio_func_dev, unsigned int addr, + u8 *byte_var, int write, int *err_ret) +{ + if (write) + sdio_writeb(sdio_func_dev, byte_var[0], addr, err_ret); + else + byte_var[0] = sdio_readb(sdio_func_dev, addr, err_ret); +} +EXPORT_SYMBOL(libra_sdiocmd52); + +u8 libra_sdio_readsb(struct sdio_func *func, void *dst, + unsigned int addr, int count) +{ + return sdio_readsb(func, dst, addr, count); +} +EXPORT_SYMBOL(libra_sdio_readsb); + +int libra_sdio_memcpy_fromio(struct sdio_func *func, + void *dst, unsigned int addr, int count) +{ + return sdio_memcpy_fromio(func, dst, addr, count); +} +EXPORT_SYMBOL(libra_sdio_memcpy_fromio); + +int libra_sdio_writesb(struct sdio_func *func, + unsigned int addr, void *src, int count) +{ + return sdio_writesb(func, addr, src, count); +} +EXPORT_SYMBOL(libra_sdio_writesb); + +int libra_sdio_memcpy_toio(struct sdio_func *func, + unsigned int addr, void *src, int count) +{ + return sdio_memcpy_toio(func, addr, src, count); +} +EXPORT_SYMBOL(libra_sdio_memcpy_toio); + +int libra_detect_card_change(void) +{ + if (libra_mmc_host) { + if (!strcmp(libra_mmc_host->class_dev.class->name, "mmc_host") + && (libra_mmc_host_index == libra_mmc_host->index)) { + mmc_detect_change(libra_mmc_host, 0); + return 0; + } + } + + printk(KERN_ERR "%s: Could not trigger card change\n", __func__); + return -EINVAL; +} +EXPORT_SYMBOL(libra_detect_card_change); + +int libra_sdio_enable_polling(void) +{ + if (libra_mmc_host) { + if (!strcmp(libra_mmc_host->class_dev.class->name, "mmc_host") + && (libra_mmc_host_index == libra_mmc_host->index)) { + libra_mmc_host->caps |= MMC_CAP_NEEDS_POLL; + mmc_detect_change(libra_mmc_host, 0); + return 0; + } + } + + printk(KERN_ERR "%s: Could not trigger SDIO scan\n", __func__); + return -1; +} +EXPORT_SYMBOL(libra_sdio_enable_polling); + +void libra_sdio_set_clock(struct sdio_func *func, unsigned int clk_freq) +{ + struct mmc_host *host = func->card->host; + host->ios.clock = clk_freq; + host->ops->set_ios(host, &host->ios); + +} +EXPORT_SYMBOL(libra_sdio_set_clock); + +/* + * API to get SDIO Device Card ID + */ +void libra_sdio_get_card_id(struct sdio_func *func, unsigned short *card_id) +{ + if (card_id) + *card_id = libra_sdio_card_id; +} +EXPORT_SYMBOL(libra_sdio_get_card_id); + +/* + * SDIO Probe + */ +static int libra_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *sdio_dev_id) +{ + libra_mmc_host = func->card->host; + libra_mmc_host_index = libra_mmc_host->index; + libra_sdio_func = func; + libra_sdio_card_id = sdio_dev_id->device; + + printk(KERN_INFO "%s: success with block size of %d device_id=0x%x\n", + __func__, + func->cur_blksize, + sdio_dev_id->device); + + /* Turn off SDIO polling from now on */ + libra_mmc_host->caps &= ~MMC_CAP_NEEDS_POLL; + return 0; +} + +static void libra_sdio_remove(struct sdio_func *func) +{ + libra_sdio_func = NULL; + + printk(KERN_INFO "%s : Module removed.\n", __func__); +} + +#ifdef CONFIG_PM +static int libra_sdio_suspend(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + int ret = 0; + + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + + if (ret) { + printk(KERN_ERR "%s: Error Host doesn't support the keep power capability\n" , + __func__); + return ret; + } + if (libra_suspend_hldr) { + /* Disable SDIO IRQ when driver is being suspended */ + libra_enable_sdio_irq(func, 0); + ret = libra_suspend_hldr(func); + if (ret) { + printk(KERN_ERR + "%s: Libra driver is not able to suspend\n" , __func__); + /* Error - Restore SDIO IRQ */ + libra_enable_sdio_irq(func, 1); + return ret; + } + } + + + return sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ); +} + +static int libra_sdio_resume(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + + if (libra_resume_hldr) { + libra_resume_hldr(func); + /* Restore SDIO IRQ */ + libra_enable_sdio_irq(func, 1); + } + + return 0; +} +#else +#define libra_sdio_suspend 0 +#define libra_sdio_resume 0 +#endif + +static struct sdio_device_id libra_sdioid[] = { + {.class = 0, .vendor = LIBRA_MAN_ID, .device = LIBRA_REV_1_0_CARD_ID}, + {.class = 0, .vendor = VOLANS_MAN_ID, .device = VOLANS_REV_2_0_CARD_ID}, + {} +}; + +static const struct dev_pm_ops libra_sdio_pm_ops = { + .suspend = libra_sdio_suspend, + .resume = libra_sdio_resume, +}; + +static struct sdio_driver libra_sdiofn_driver = { + .name = "libra_sdiofn", + .id_table = libra_sdioid, + .probe = libra_sdio_probe, + .remove = libra_sdio_remove, + .drv.pm = &libra_sdio_pm_ops, +}; + +static int __init libra_sdioif_init(void) +{ + libra_sdio_func = NULL; + libra_mmc_host = NULL; + libra_mmc_host_index = -1; + libra_suspend_hldr = NULL; + libra_resume_hldr = NULL; + + sdio_register_driver(&libra_sdiofn_driver); + + printk(KERN_INFO "%s: Loaded Successfully\n", __func__); + + return 0; +} + +static void __exit libra_sdioif_exit(void) +{ + unsigned int attempts = 0; + + if (!libra_detect_card_change()) { + do { + ++attempts; + msleep(500); + } while (libra_sdio_func != NULL && attempts < 3); + } + + if (libra_sdio_func != NULL) + printk(KERN_ERR "%s: Card removal not detected\n", __func__); + + sdio_unregister_driver(&libra_sdiofn_driver); + + libra_sdio_func = NULL; + libra_mmc_host = NULL; + libra_mmc_host_index = -1; + + printk(KERN_INFO "%s: Unloaded Successfully\n", __func__); +} + +module_init(libra_sdioif_init); +module_exit(libra_sdioif_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_DESCRIPTION("WLAN SDIODriver"); diff --git a/drivers/net/wireless/libra/qcomwlan7x27a_pwrif.c b/drivers/net/wireless/libra/qcomwlan7x27a_pwrif.c new file mode 100644 index 00000000000..ca2680fb277 --- /dev/null +++ b/drivers/net/wireless/libra/qcomwlan7x27a_pwrif.c @@ -0,0 +1,172 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#define WLAN_GPIO_EXT_POR_N 134 + +static const char *id = "WLAN"; + +enum { + WLAN_VREG_L17 = 0, + WLAN_VREG_S3, + WLAN_VREG_TCXO_L11, + WLAN_VREG_L19, + WLAN_VREG_L5, + WLAN_VREG_L6 +}; + +struct wlan_vreg_info { + const char *vreg_id; + unsigned int vreg_level; + unsigned int pmapp_id; + unsigned int is_vreg_pin_controlled; + struct vreg *vreg; +}; + + +static struct wlan_vreg_info vreg_info[] = { + {"bt", 3050, 56, 0, NULL}, + {"msme1", 1800, 2, 0, NULL}, + {"wlan_tcx0", 1800, 53, 0, NULL}, + {"wlan4", 1200, 57, 0, NULL}, + {"wlan2", 1350, 45, 0, NULL}, + {"wlan3", 1200, 51, 0, NULL} }; + +int chip_power_qrf6285(bool on) +{ + int rc = 0, index = 0; + + if (on) { + rc = gpio_request(WLAN_GPIO_EXT_POR_N, "WLAN_DEEP_SLEEP_N"); + + if (rc) { + pr_err("WLAN reset GPIO %d request failed %d\n", + WLAN_GPIO_EXT_POR_N, rc); + goto fail; + } + rc = gpio_direction_output(WLAN_GPIO_EXT_POR_N, 1); + if (rc < 0) { + pr_err("WLAN reset GPIO %d set direction failed %d\n", + WLAN_GPIO_EXT_POR_N, rc); + goto fail_gpio_dir_out; + } + } else { + gpio_set_value_cansleep(WLAN_GPIO_EXT_POR_N, 0); + gpio_free(WLAN_GPIO_EXT_POR_N); + } + + + for (index = 0; index < ARRAY_SIZE(vreg_info); index++) { + vreg_info[index].vreg = vreg_get(NULL, + vreg_info[index].vreg_id); + if (IS_ERR(vreg_info[index].vreg)) { + pr_err("%s:%s vreg get failed %ld\n", + __func__, vreg_info[index].vreg_id, + PTR_ERR(vreg_info[index].vreg)); + rc = PTR_ERR(vreg_info[index].vreg); + if (on) + goto vreg_fail; + else + continue; + } + if (on) { + rc = vreg_set_level(vreg_info[index].vreg, + vreg_info[index].vreg_level); + if (rc) { + pr_err("%s:%s vreg set level failed %d\n", + __func__, vreg_info[index].vreg_id, rc); + goto vreg_fail; + } + if (vreg_info[index].is_vreg_pin_controlled) { + rc = pmapp_vreg_pincntrl_vote(id, + vreg_info[index].pmapp_id, + PMAPP_CLOCK_ID_A0, 1); + if (rc) { + pr_err("%s:%s pmapp_vreg_pincntrl_vote" + " for enable failed %d\n", + __func__, + vreg_info[index].vreg_id, rc); + goto vreg_fail; + } + } else { + rc = vreg_enable(vreg_info[index].vreg); + if (rc) { + pr_err("%s:%s vreg enable failed %d\n", + __func__, + vreg_info[index].vreg_id, rc); + goto vreg_fail; + } + } + + if (WLAN_VREG_TCXO_L11 == index) { + /* + * Configure TCXO to be slave to + * WLAN_CLK_PWR_REQ +` */ + rc = pmapp_clock_vote(id, PMAPP_CLOCK_ID_A0, + PMAPP_CLOCK_VOTE_PIN_CTRL); + if (rc) { + pr_err("%s: Configuring TCXO to Pin" + " controllable failed %d\n", + __func__, rc); + goto vreg_clock_vote_fail; + } + } + + } else { + + if (vreg_info[index].is_vreg_pin_controlled) { + rc = pmapp_vreg_pincntrl_vote(id, + vreg_info[index].pmapp_id, + PMAPP_CLOCK_ID_A0, 0); + if (rc) { + pr_err("%s:%s pmapp_vreg_pincntrl_vote" + " for disable failed %d\n", + __func__, + vreg_info[index].vreg_id, rc); + } + } else { + rc = vreg_disable(vreg_info[index].vreg); + if (rc) { + pr_err("%s:%s vreg disable failed %d\n", + __func__, + vreg_info[index].vreg_id, rc); + } + } + } + } + return 0; +vreg_fail: + index--; +vreg_clock_vote_fail: + while (index > 0) { + rc = vreg_disable(vreg_info[index].vreg); + if (rc) { + pr_err("%s:%s vreg disable failed %d\n", + __func__, vreg_info[index].vreg_id, rc); + } + index--; + } + if (!on) + goto fail; +fail_gpio_dir_out: + gpio_free(WLAN_GPIO_EXT_POR_N); +fail: + return rc; +} +EXPORT_SYMBOL(chip_power_qrf6285); diff --git a/drivers/net/wireless/libra/qcomwlan_pwrif.c b/drivers/net/wireless/libra/qcomwlan_pwrif.c new file mode 100644 index 00000000000..bb5e1356093 --- /dev/null +++ b/drivers/net/wireless/libra/qcomwlan_pwrif.c @@ -0,0 +1,256 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include + +#define GPIO_WLAN_DEEP_SLEEP_N 230 +#define WLAN_RESET_OUT 1 +#define WLAN_RESET 0 + +static const char *id = "WLAN"; + +/** + * vos_chip_power_qrf8615() - WLAN Power Up Seq for WCN1314 rev 2.0 on QRF 8615 + * @on - Turn WLAN ON/OFF (1 or 0) + * + * Power up/down WLAN by turning on/off various regs and asserting/deasserting + * Power-on-reset pin. Also, put XO A0 buffer as slave to wlan_clk_pwr_req while + * turning ON WLAN and vice-versa. + * + * This function returns 0 on success or a non-zero value on failure. + */ +int vos_chip_power_qrf8615(int on) +{ + static char wlan_on; + static const char *vregs_qwlan_name[] = { + "8058_l20", + "8058_l8", + "8901_s4", + "8901_lvs1", + "8901_l0", + "8058_s2", + "8058_s1", + }; + static const int vregs_qwlan_val_min[] = { + 1800000, + 3050000, + 1225000, + 0, + 1200000, + 1300000, + 500000, + }; + static const int vregs_qwlan_val_max[] = { + 1800000, + 3050000, + 1225000, + 0, + 1200000, + 1300000, + 1250000, + }; + static const bool vregs_is_pin_controlled[] = { + 1, + 1, + 0, + 0, + 1, + 1, + 0, + }; + static struct regulator *vregs_qwlan[ARRAY_SIZE(vregs_qwlan_name)]; + static struct msm_xo_voter *wlan_clock; + int ret, i, rc = 0; + + /* WLAN RESET and CLK settings */ + if (on && !wlan_on) { + /* + * Program U12 GPIO expander pin IO1 to de-assert (drive 0) + * WLAN_EXT_POR_N to put WLAN in reset + */ + rc = gpio_request(GPIO_WLAN_DEEP_SLEEP_N, "WLAN_DEEP_SLEEP_N"); + if (rc) { + pr_err("WLAN reset GPIO %d request failed\n", + GPIO_WLAN_DEEP_SLEEP_N); + goto fail; + } + rc = gpio_direction_output(GPIO_WLAN_DEEP_SLEEP_N, + WLAN_RESET_OUT); + if (rc < 0) { + pr_err("WLAN reset GPIO %d set output direction failed", + GPIO_WLAN_DEEP_SLEEP_N); + goto fail_gpio_dir_out; + } + + /* Configure TCXO to be slave to WLAN_CLK_PWR_REQ */ + if (wlan_clock == NULL) { + wlan_clock = msm_xo_get(MSM_XO_TCXO_A0, id); + if (IS_ERR(wlan_clock)) { + pr_err("Failed to get TCXO_A0 voter (%ld)\n", + PTR_ERR(wlan_clock)); + goto fail_gpio_dir_out; + } + } + + rc = msm_xo_mode_vote(wlan_clock, MSM_XO_MODE_PIN_CTRL); + if (rc < 0) { + pr_err("Configuring TCXO to Pin controllable failed" + "(%d)\n", rc); + goto fail_xo_mode_vote; + } + } else if (!on && wlan_on) { + if (wlan_clock != NULL) + msm_xo_mode_vote(wlan_clock, MSM_XO_MODE_OFF); + gpio_set_value_cansleep(GPIO_WLAN_DEEP_SLEEP_N, WLAN_RESET); + gpio_free(GPIO_WLAN_DEEP_SLEEP_N); + } + + /* WLAN VREG settings */ + for (i = 0; i < ARRAY_SIZE(vregs_qwlan_name); i++) { + if (vregs_qwlan[i] == NULL) { + vregs_qwlan[i] = regulator_get(NULL, + vregs_qwlan_name[i]); + if (IS_ERR(vregs_qwlan[i])) { + pr_err("regulator get of %s failed (%ld)\n", + vregs_qwlan_name[i], + PTR_ERR(vregs_qwlan[i])); + rc = PTR_ERR(vregs_qwlan[i]); + goto vreg_get_fail; + } + if (vregs_qwlan_val_min[i] || vregs_qwlan_val_max[i]) { + rc = regulator_set_voltage(vregs_qwlan[i], + vregs_qwlan_val_min[i], + vregs_qwlan_val_max[i]); + if (rc) { + pr_err("regulator_set_voltage(%s) failed\n", + vregs_qwlan_name[i]); + goto vreg_fail; + } + } + /* vote for pin control (if needed) */ + if (vregs_is_pin_controlled[i]) { + rc = regulator_set_mode(vregs_qwlan[i], + REGULATOR_MODE_IDLE); + if (rc) { + pr_err("regulator_set_mode(%s) failed\n", + vregs_qwlan_name[i]); + goto vreg_fail; + } + } + } + if (on && !wlan_on) { + rc = regulator_enable(vregs_qwlan[i]); + if (rc < 0) { + pr_err("vreg %s enable failed (%d)\n", + vregs_qwlan_name[i], rc); + goto vreg_fail; + } + } else if (!on && wlan_on) { + rc = regulator_disable(vregs_qwlan[i]); + if (rc < 0) { + pr_err("vreg %s disable failed (%d)\n", + vregs_qwlan_name[i], rc); + goto vreg_fail; + } + } + } + if (on) + wlan_on = true; + else + wlan_on = false; + return 0; + +vreg_fail: + regulator_put(vregs_qwlan[i]); +vreg_get_fail: + i--; + while (i) { + ret = !on ? regulator_enable(vregs_qwlan[i]) : + regulator_disable(vregs_qwlan[i]); + if (ret < 0) { + pr_err("vreg %s %s failed (%d) in err path\n", + vregs_qwlan_name[i], + !on ? "enable" : "disable", ret); + } + regulator_put(vregs_qwlan[i]); + i--; + } + if (!on) + goto fail; +fail_xo_mode_vote: + msm_xo_put(wlan_clock); +fail_gpio_dir_out: + gpio_free(GPIO_WLAN_DEEP_SLEEP_N); +fail: + return rc; +} +EXPORT_SYMBOL(vos_chip_power_qrf8615); + +/** + * qcomwlan_pmic_xo_core_force_enable() - Force XO Core of PMIC to be ALWAYS ON + * @on - Force XO Core ON/OFF (1 or 0) + * + * The XO_CORE controls the XO feeding the TCXO buffers (A0, A1, etc.). WLAN + * wants to keep the XO core on even though our buffer A0 is in pin control + * because it can take a long time turn the XO back on and warm up the buffers. + * This helps in optimizing power in BMPS (power save) mode of WLAN. + * The WLAN driver wrapper function takes care that this API is not called + * consecutively. + * + * This function returns 0 on success or a non-zero value on failure. + */ +int qcomwlan_pmic_xo_core_force_enable(int on) +{ + static struct msm_xo_voter *wlan_ps; + int rc = 0; + + if (wlan_ps == NULL) { + wlan_ps = msm_xo_get(MSM_XO_CORE, id); + if (IS_ERR(wlan_ps)) { + pr_err("Failed to get XO CORE voter (%ld)\n", + PTR_ERR(wlan_ps)); + goto fail; + } + } + + if (on) + rc = msm_xo_mode_vote(wlan_ps, MSM_XO_MODE_ON); + else + rc = msm_xo_mode_vote(wlan_ps, MSM_XO_MODE_OFF); + + if (rc < 0) { + pr_err("XO Core %s failed (%d)\n", + on ? "enable" : "disable", rc); + goto fail_xo_mode_vote; + } + return 0; +fail_xo_mode_vote: + msm_xo_put(wlan_ps); +fail: + return rc; +} +EXPORT_SYMBOL(qcomwlan_pmic_xo_core_force_enable); + + +/** + * qcomwlan_freq_change_1p3v_supply() - function to change the freq for 1.3V RF supply. + * @freq - freq of the 1.3V Supply + * + * This function returns 0 on success or a non-zero value on failure. + */ + +int qcomwlan_freq_change_1p3v_supply(enum rpm_vreg_freq freq) +{ + return rpm_vreg_set_frequency(RPM_VREG_ID_PM8058_S2, freq); +} +EXPORT_SYMBOL(qcomwlan_freq_change_1p3v_supply); diff --git a/drivers/net/wireless/wcnss/Makefile b/drivers/net/wireless/wcnss/Makefile new file mode 100644 index 00000000000..d182b6e49dd --- /dev/null +++ b/drivers/net/wireless/wcnss/Makefile @@ -0,0 +1,6 @@ + +# Makefile for WCNSS WLAN driver + +wcnsswlan-objs += wcnss_wlan.o wcnss_riva.o qcomwlan_secif.o + +obj-$(CONFIG_WCNSS_WLAN) += wcnsswlan.o diff --git a/drivers/net/wireless/wcnss/qcomwlan_secif.c b/drivers/net/wireless/wcnss/qcomwlan_secif.c new file mode 100644 index 00000000000..124f387fb35 --- /dev/null +++ b/drivers/net/wireless/wcnss/qcomwlan_secif.c @@ -0,0 +1,62 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +/* + * APIs for calling crypto routines from kernel + */ +struct crypto_ahash *wcnss_wlan_crypto_alloc_ahash(const char *alg_name, + u32 type, u32 mask) +{ + return crypto_alloc_ahash(alg_name, type, mask); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_alloc_ahash); + +int wcnss_wlan_crypto_ahash_digest(struct ahash_request *req) +{ + return crypto_ahash_digest(req); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_ahash_digest); + +void wcnss_wlan_crypto_free_ahash(struct crypto_ahash *tfm) +{ + crypto_free_ahash(tfm); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_free_ahash); + +int wcnss_wlan_crypto_ahash_setkey(struct crypto_ahash *tfm, const u8 *key, + unsigned int keylen) +{ + return crypto_ahash_setkey(tfm, key, keylen); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_ahash_setkey); + +struct crypto_ablkcipher * +wcnss_wlan_crypto_alloc_ablkcipher(const char *alg_name, u32 type, u32 mask) +{ + return crypto_alloc_ablkcipher(alg_name, type, mask); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_alloc_ablkcipher); + +void wcnss_wlan_ablkcipher_request_free(struct ablkcipher_request *req) +{ + ablkcipher_request_free(req); +} +EXPORT_SYMBOL(wcnss_wlan_ablkcipher_request_free); + +void wcnss_wlan_crypto_free_ablkcipher(struct crypto_ablkcipher *tfm) +{ + crypto_free_ablkcipher(tfm); +} +EXPORT_SYMBOL(wcnss_wlan_crypto_free_ablkcipher); + diff --git a/drivers/net/wireless/wcnss/wcnss_riva.c b/drivers/net/wireless/wcnss/wcnss_riva.c new file mode 100644 index 00000000000..3617ba8c063 --- /dev/null +++ b/drivers/net/wireless/wcnss/wcnss_riva.c @@ -0,0 +1,314 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wcnss_riva.h" + +static void __iomem *msm_riva_base; +static struct msm_xo_voter *wlan_clock; +static const char *id = "WLAN"; + +#define MSM_RIVA_PHYS 0x03204000 +#define RIVA_PMU_CFG (msm_riva_base + 0x28) +#define RIVA_PMU_CFG_IRIS_XO_CFG BIT(3) +#define RIVA_PMU_CFG_IRIS_XO_EN BIT(4) +#define RIVA_PMU_CFG_GC_BUS_MUX_SEL_TOP BIT(5) +#define RIVA_PMU_CFG_IRIS_XO_CFG_STS BIT(6) /* 1: in progress, 0: done */ + +#define RIVA_PMU_CFG_IRIS_XO_MODE 0x6 +#define RIVA_PMU_CFG_IRIS_XO_MODE_48 (3 << 1) + +#define VREG_NULL_CONFIG 0x0000 +#define VREG_GET_REGULATOR_MASK 0x0001 +#define VREG_SET_VOLTAGE_MASK 0x0002 +#define VREG_OPTIMUM_MODE_MASK 0x0004 +#define VREG_ENABLE_MASK 0x0008 + +struct vregs_info { + const char * const name; + int state; + const int nominal_min; + const int low_power_min; + const int max_voltage; + const int uA_load; + struct regulator *regulator; +}; + +static struct vregs_info iris_vregs[] = { + {"iris_vddio", VREG_NULL_CONFIG, 0000000, 0, 0000000, 0, NULL}, + {"iris_vddxo", VREG_NULL_CONFIG, 1800000, 0, 1800000, 10000, NULL}, + {"iris_vddrfa", VREG_NULL_CONFIG, 1300000, 0, 1300000, 100000, NULL}, + {"iris_vddpa", VREG_NULL_CONFIG, 2900000, 0, 2900000, 515000, NULL}, + {"iris_vdddig", VREG_NULL_CONFIG, 0000000, 0, 0000000, 0, NULL}, +}; + +static struct vregs_info riva_vregs[] = { + {"riva_vddmx", VREG_NULL_CONFIG, 1050000, 0, 1150000, 0, NULL}, + {"riva_vddcx", VREG_NULL_CONFIG, 1050000, 0, 1150000, 0, NULL}, + {"riva_vddpx", VREG_NULL_CONFIG, 1800000, 0, 1800000, 0, NULL}, +}; + +static int configure_iris_xo(bool use_48mhz_xo, int on) +{ + u32 reg = 0; + int rc = 0; + + if (on) { + msm_riva_base = ioremap(MSM_RIVA_PHYS, SZ_256); + if (!msm_riva_base) { + pr_err("ioremap MSM_RIVA_PHYS failed\n"); + goto fail; + } + + /* Enable IRIS XO */ + writel_relaxed(0, RIVA_PMU_CFG); + reg = readl_relaxed(RIVA_PMU_CFG); + reg |= RIVA_PMU_CFG_GC_BUS_MUX_SEL_TOP | + RIVA_PMU_CFG_IRIS_XO_EN; + writel_relaxed(reg, RIVA_PMU_CFG); + + /* Clear XO_MODE[b2:b1] bits. Clear implies 19.2 MHz TCXO */ + reg &= ~(RIVA_PMU_CFG_IRIS_XO_MODE); + + if (use_48mhz_xo) + reg |= RIVA_PMU_CFG_IRIS_XO_MODE_48; + + writel_relaxed(reg, RIVA_PMU_CFG); + + /* Start IRIS XO configuration */ + reg |= RIVA_PMU_CFG_IRIS_XO_CFG; + writel_relaxed(reg, RIVA_PMU_CFG); + + /* Wait for XO configuration to finish */ + while (readl_relaxed(RIVA_PMU_CFG) & + RIVA_PMU_CFG_IRIS_XO_CFG_STS) + cpu_relax(); + + /* Stop IRIS XO configuration */ + reg &= ~(RIVA_PMU_CFG_GC_BUS_MUX_SEL_TOP | + RIVA_PMU_CFG_IRIS_XO_CFG); + writel_relaxed(reg, RIVA_PMU_CFG); + + if (!use_48mhz_xo) { + wlan_clock = msm_xo_get(MSM_XO_TCXO_A0, id); + if (IS_ERR(wlan_clock)) { + rc = PTR_ERR(wlan_clock); + pr_err("Failed to get MSM_XO_TCXO_A0 voter" + " (%d)\n", rc); + goto fail; + } + + rc = msm_xo_mode_vote(wlan_clock, MSM_XO_MODE_ON); + if (rc < 0) { + pr_err("Configuring MSM_XO_MODE_ON failed" + " (%d)\n", rc); + goto msm_xo_vote_fail; + } + } + } else { + if (wlan_clock != NULL && !use_48mhz_xo) { + rc = msm_xo_mode_vote(wlan_clock, MSM_XO_MODE_OFF); + if (rc < 0) + pr_err("Configuring MSM_XO_MODE_OFF failed" + " (%d)\n", rc); + } + } + + /* Add some delay for XO to settle */ + msleep(20); + + return rc; + +msm_xo_vote_fail: + msm_xo_put(wlan_clock); + +fail: + return rc; +} + +/* Helper routine to turn off all WCNSS vregs e.g. IRIS, Riva */ +static void wcnss_vregs_off(struct vregs_info regulators[], uint size) +{ + int i, rc = 0; + + /* Regulators need to be turned off in the reverse order */ + for (i = (size-1); i >= 0; i--) { + if (regulators[i].state == VREG_NULL_CONFIG) + continue; + + /* Remove PWM mode */ + if (regulators[i].state & VREG_OPTIMUM_MODE_MASK) { + rc = regulator_set_optimum_mode( + regulators[i].regulator, 0); + if (rc < 0) + pr_err("regulator_set_optimum_mode(%s) failed (%d)\n", + regulators[i].name, rc); + } + + /* Set voltage to lowest level */ + if (regulators[i].state & VREG_SET_VOLTAGE_MASK) { + rc = regulator_set_voltage(regulators[i].regulator, + regulators[i].low_power_min, + regulators[i].max_voltage); + if (rc) + pr_err("regulator_set_voltage(%s) failed (%d)\n", + regulators[i].name, rc); + } + + /* Disable regulator */ + if (regulators[i].state & VREG_ENABLE_MASK) { + rc = regulator_disable(regulators[i].regulator); + if (rc < 0) + pr_err("vreg %s disable failed (%d)\n", + regulators[i].name, rc); + } + + /* Free the regulator source */ + if (regulators[i].state & VREG_GET_REGULATOR_MASK) + regulator_put(regulators[i].regulator); + + regulators[i].state = VREG_NULL_CONFIG; + } +} + +/* Common helper routine to turn on all WCNSS vregs e.g. IRIS, Riva */ +static int wcnss_vregs_on(struct device *dev, + struct vregs_info regulators[], uint size) +{ + int i, rc = 0; + + for (i = 0; i < size; i++) { + /* Get regulator source */ + regulators[i].regulator = + regulator_get(dev, regulators[i].name); + if (IS_ERR(regulators[i].regulator)) { + rc = PTR_ERR(regulators[i].regulator); + pr_err("regulator get of %s failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_GET_REGULATOR_MASK; + + /* Set voltage to nominal. Exclude swtiches e.g. LVS */ + if (regulators[i].nominal_min || regulators[i].max_voltage) { + rc = regulator_set_voltage(regulators[i].regulator, + regulators[i].nominal_min, + regulators[i].max_voltage); + if (rc) { + pr_err("regulator_set_voltage(%s) failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_SET_VOLTAGE_MASK; + } + + /* Vote for PWM/PFM mode if needed */ + if (regulators[i].uA_load) { + rc = regulator_set_optimum_mode(regulators[i].regulator, + regulators[i].uA_load); + if (rc < 0) { + pr_err("regulator_set_optimum_mode(%s) failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_OPTIMUM_MODE_MASK; + } + + /* Enable the regulator */ + rc = regulator_enable(regulators[i].regulator); + if (rc) { + pr_err("vreg %s enable failed (%d)\n", + regulators[i].name, rc); + goto fail; + } + regulators[i].state |= VREG_ENABLE_MASK; + } + + return rc; + +fail: + wcnss_vregs_off(regulators, size); + return rc; + +} + +static void wcnss_iris_vregs_off(void) +{ + wcnss_vregs_off(iris_vregs, ARRAY_SIZE(iris_vregs)); +} + +static int wcnss_iris_vregs_on(struct device *dev) +{ + return wcnss_vregs_on(dev, iris_vregs, ARRAY_SIZE(iris_vregs)); +} + +static void wcnss_riva_vregs_off(void) +{ + wcnss_vregs_off(riva_vregs, ARRAY_SIZE(riva_vregs)); +} + +static int wcnss_riva_vregs_on(struct device *dev) +{ + return wcnss_vregs_on(dev, riva_vregs, ARRAY_SIZE(riva_vregs)); +} + +int wcnss_wlan_power(struct device *dev, + struct wcnss_wlan_config *cfg, + enum wcnss_opcode on) +{ + int rc = 0; + + if (on) { + /* RIVA regulator settings */ + rc = wcnss_riva_vregs_on(dev); + if (rc) + goto fail_riva_on; + + /* IRIS regulator settings */ + rc = wcnss_iris_vregs_on(dev); + if (rc) + goto fail_iris_on; + + /* Configure IRIS XO */ + rc = configure_iris_xo(cfg->use_48mhz_xo, WCNSS_WLAN_SWITCH_ON); + if (rc) + goto fail_iris_xo; + + } else { + configure_iris_xo(cfg->use_48mhz_xo, WCNSS_WLAN_SWITCH_OFF); + wcnss_iris_vregs_off(); + wcnss_riva_vregs_off(); + } + + return rc; + +fail_iris_xo: + wcnss_iris_vregs_off(); + +fail_iris_on: + wcnss_riva_vregs_off(); + +fail_riva_on: + return rc; +} +EXPORT_SYMBOL(wcnss_wlan_power); + diff --git a/drivers/net/wireless/wcnss/wcnss_riva.h b/drivers/net/wireless/wcnss/wcnss_riva.h new file mode 100644 index 00000000000..e037f587655 --- /dev/null +++ b/drivers/net/wireless/wcnss/wcnss_riva.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _WCNSS_RIVA_H_ +#define _WCNSS_RIVA_H_ + +#include + +enum wcnss_opcode { + WCNSS_WLAN_SWITCH_OFF = 0, + WCNSS_WLAN_SWITCH_ON, +}; + +struct wcnss_wlan_config { + int use_48mhz_xo; +}; + +int wcnss_wlan_power(struct device *dev, + struct wcnss_wlan_config *cfg, + enum wcnss_opcode opcode); + +#endif /* _WCNSS_RIVA_H_ */ diff --git a/drivers/net/wireless/wcnss/wcnss_wlan.c b/drivers/net/wireless/wcnss/wcnss_wlan.c new file mode 100644 index 00000000000..371e58ebf18 --- /dev/null +++ b/drivers/net/wireless/wcnss/wcnss_wlan.c @@ -0,0 +1,339 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcnss_riva.h" + +#define DEVICE "wcnss_wlan" +#define VERSION "1.01" +#define WCNSS_PIL_DEVICE "wcnss" +#define WCNSS_NV_NAME "wlan/prima/WCNSS_qcom_cfg.ini" + +/* By default assume 48MHz XO is populated */ +#define CONFIG_USE_48MHZ_XO_DEFAULT 1 + +static struct { + struct platform_device *pdev; + void *pil; + struct resource *mmio_res; + struct resource *tx_irq_res; + struct resource *rx_irq_res; + const struct dev_pm_ops *pm_ops; + int smd_channel_ready; + struct wcnss_wlan_config wlan_config; +} *penv = NULL; + +enum { + nv_none = -1, + nv_use_48mhz_xo, + nv_end, +}; + +static const match_table_t nv_tokens = { + {nv_use_48mhz_xo, "gUse48MHzXO=%d"}, + {nv_end, "END"}, + {nv_none, NULL} +}; + +static void wcnss_init_config(void) +{ + penv->wlan_config.use_48mhz_xo = CONFIG_USE_48MHZ_XO_DEFAULT; +} + +static void wcnss_parse_nv(char *nvp) +{ + substring_t args[MAX_OPT_ARGS]; + char *cur; + char *tok; + int token; + int intval; + + cur = nvp; + while (cur != NULL) { + if ('#' == *cur) { + /* comment, consume remainder of line */ + tok = strsep(&cur, "\r\n"); + continue; + } + + tok = strsep(&cur, " \t\r\n,"); + if (!*tok) + continue; + + token = match_token(tok, nv_tokens, args); + switch (token) { + case nv_use_48mhz_xo: + if (match_int(&args[0], &intval)) { + dev_err(&penv->pdev->dev, + "Invalid value for gUse48MHzXO: %s\n", + args[0].from); + continue; + } + if ((0 > intval) || (1 < intval)) { + dev_err(&penv->pdev->dev, + "Invalid value for gUse48MHzXO: %d\n", + intval); + continue; + } + penv->wlan_config.use_48mhz_xo = intval; + dev_info(&penv->pdev->dev, + "gUse48MHzXO set to %d\n", intval); + break; + case nv_end: + /* end of options so we are done */ + return; + default: + /* silently ignore unknown settings */ + break; + } + } +} + +static int __devinit +wcnss_wlan_ctrl_probe(struct platform_device *pdev) +{ + if (penv) + penv->smd_channel_ready = 1; + + pr_info("%s: SMD ctrl channel up\n", __func__); + + return 0; +} + +static int __devexit +wcnss_wlan_ctrl_remove(struct platform_device *pdev) +{ + if (penv) + penv->smd_channel_ready = 0; + + pr_info("%s: SMD ctrl channel down\n", __func__); + + return 0; +} + + +static struct platform_driver wcnss_wlan_ctrl_driver = { + .driver = { + .name = "WLAN_CTRL", + .owner = THIS_MODULE, + }, + .probe = wcnss_wlan_ctrl_probe, + .remove = __devexit_p(wcnss_wlan_ctrl_remove), +}; + +struct device *wcnss_wlan_get_device(void) +{ + if (penv && penv->pdev && penv->smd_channel_ready) + return &penv->pdev->dev; + return NULL; +} +EXPORT_SYMBOL(wcnss_wlan_get_device); + +struct resource *wcnss_wlan_get_memory_map(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready) + return penv->mmio_res; + return NULL; +} +EXPORT_SYMBOL(wcnss_wlan_get_memory_map); + +int wcnss_wlan_get_dxe_tx_irq(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->tx_irq_res && penv->smd_channel_ready) + return penv->tx_irq_res->start; + return WCNSS_WLAN_IRQ_INVALID; +} +EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq); + +int wcnss_wlan_get_dxe_rx_irq(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->rx_irq_res && penv->smd_channel_ready) + return penv->rx_irq_res->start; + return WCNSS_WLAN_IRQ_INVALID; +} +EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq); + +void wcnss_wlan_register_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops) +{ + if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) + penv->pm_ops = pm_ops; +} +EXPORT_SYMBOL(wcnss_wlan_register_pm_ops); + +static int wcnss_wlan_suspend(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->smd_channel_ready && + penv->pm_ops && penv->pm_ops->suspend) + return penv->pm_ops->suspend(dev); + return 0; +} + +static int wcnss_wlan_resume(struct device *dev) +{ + if (penv && dev && (dev == &penv->pdev->dev) && + penv->smd_channel_ready && + penv->pm_ops && penv->pm_ops->resume) + return penv->pm_ops->resume(dev); + return 0; +} + +static int __devinit +wcnss_wlan_probe(struct platform_device *pdev) +{ + const struct firmware *nv; + char *nvp; + int ret; + + /* verify we haven't been called more than once */ + if (penv) { + dev_err(&pdev->dev, "cannot handle multiple devices.\n"); + return -ENODEV; + } + + /* create an environment to track the device */ + penv = kzalloc(sizeof(*penv), GFP_KERNEL); + if (!penv) { + dev_err(&pdev->dev, "cannot allocate device memory.\n"); + return -ENOMEM; + } + penv->pdev = pdev; + + /* initialize the WCNSS default configuration */ + wcnss_init_config(); + + /* update the WCNSS configuration from NV if present */ + ret = request_firmware(&nv, WCNSS_NV_NAME, &pdev->dev); + if (!ret) { + /* firmware is read-only so make a NUL-terminated copy */ + nvp = kmalloc(nv->size+1, GFP_KERNEL); + if (nvp) { + memcpy(nvp, nv->data, nv->size); + nvp[nv->size] = '\0'; + wcnss_parse_nv(nvp); + kfree(nvp); + } else { + dev_err(&pdev->dev, "cannot parse NV.\n"); + } + release_firmware(nv); + } else { + dev_err(&pdev->dev, "cannot read NV.\n"); + } + + /* power up the WCNSS */ + ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config, + WCNSS_WLAN_SWITCH_ON); + if (ret) { + dev_err(&pdev->dev, "WCNSS Power-up failed.\n"); + goto fail_power; + } + + /* trigger initialization of the WCNSS */ + penv->pil = pil_get(WCNSS_PIL_DEVICE); + if (IS_ERR(penv->pil)) { + dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n"); + ret = PTR_ERR(penv->pil); + penv->pil = NULL; + goto fail_pil; + } + + /* allocate resources */ + penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "wcnss_mmio"); + penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "wcnss_wlantx_irq"); + penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "wcnss_wlanrx_irq"); + + if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) { + dev_err(&pdev->dev, "insufficient resources\n"); + ret = -ENOENT; + goto fail_res; + } + + return 0; + +fail_res: + if (penv->pil) + pil_put(penv->pil); +fail_pil: + wcnss_wlan_power(&pdev->dev, &penv->wlan_config, + WCNSS_WLAN_SWITCH_OFF); +fail_power: + kfree(penv); + penv = NULL; + return ret; +} + +static int __devexit +wcnss_wlan_remove(struct platform_device *pdev) +{ + return 0; +} + + +static const struct dev_pm_ops wcnss_wlan_pm_ops = { + .suspend = wcnss_wlan_suspend, + .resume = wcnss_wlan_resume, +}; + +static struct platform_driver wcnss_wlan_driver = { + .driver = { + .name = DEVICE, + .owner = THIS_MODULE, + .pm = &wcnss_wlan_pm_ops, + }, + .probe = wcnss_wlan_probe, + .remove = __devexit_p(wcnss_wlan_remove), +}; + +static int __init wcnss_wlan_init(void) +{ + platform_driver_register(&wcnss_wlan_driver); + platform_driver_register(&wcnss_wlan_ctrl_driver); + + return 0; +} + +static void __exit wcnss_wlan_exit(void) +{ + if (penv) { + if (penv->pil) + pil_put(penv->pil); + + wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config, + WCNSS_WLAN_SWITCH_OFF); + + kfree(penv); + penv = NULL; + } + + platform_driver_unregister(&wcnss_wlan_ctrl_driver); + platform_driver_unregister(&wcnss_wlan_driver); +} + +module_init(wcnss_wlan_init); +module_exit(wcnss_wlan_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(VERSION); +MODULE_DESCRIPTION(DEVICE "Driver"); diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 8390dca2b4e..17b5df53eb8 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -1,3 +1,6 @@ if X86 source "drivers/platform/x86/Kconfig" endif +if ARCH_MSM +source "drivers/platform/msm/Kconfig" +endif diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 782953ae4c0..58c62bd765b 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -3,3 +3,4 @@ # obj-$(CONFIG_X86) += x86/ +obj-$(CONFIG_ARCH_MSM) += msm/ diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig new file mode 100644 index 00000000000..af10b1bcc74 --- /dev/null +++ b/drivers/platform/msm/Kconfig @@ -0,0 +1,36 @@ +menu "Qualcomm MSM specific device drivers" + depends on ARCH_MSM + +config MSM_SSBI + bool "Qualcomm Single-wire Serial Bus Interface (SSBI)" + help + If you say yes to this option, support will be included for the + built-in SSBI interface on Qualcomm MSM family processors. + + This is required for communicating with Qualcomm PMICs and + other devices that have the SSBI interface. + +config SPS + bool "SPS support" + depends on (HAS_IOMEM && (ARCH_MSM8960 || ARCH_MSM8X60)) + select GENERIC_ALLOCATOR + default n + help + The SPS (Smart Peripheral Switch) is a DMA engine. + It can move data in the following modes: + 1. Peripheral-to-Peripheral. + 2. Peripheral-to-Memory. + 3. Memory-to-Memory. + +config SPS_SUPPORT_BAMDMA + bool "SPS support BAM DMA" + depends on SPS + default n + help + The BAM-DMA is used for Memory-to-Memory transfers. + The main use cases is RPC between processors. + The BAM-DMA hardware has 2 registers sets: + 1. A BAM HW like all the peripherals. + 2. A DMA channel configuration (i.e. channel priority). + +endmenu diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile new file mode 100644 index 00000000000..f6f212ec221 --- /dev/null +++ b/drivers/platform/msm/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the MSM specific device drivers. +# +obj-$(CONFIG_MSM_SSBI) += ssbi.o +obj-$(CONFIG_SPS) += sps/ diff --git a/drivers/platform/msm/sps/Makefile b/drivers/platform/msm/sps/Makefile new file mode 100755 index 00000000000..f19e162cf70 --- /dev/null +++ b/drivers/platform/msm/sps/Makefile @@ -0,0 +1,2 @@ +obj-y += bam.o sps_bam.o sps.o sps_dma.o sps_map.o sps_mem.o sps_rm.o + diff --git a/drivers/platform/msm/sps/bam.c b/drivers/platform/msm/sps/bam.c new file mode 100644 index 00000000000..f816236414b --- /dev/null +++ b/drivers/platform/msm/sps/bam.c @@ -0,0 +1,755 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* Bus-Access-Manager (BAM) Hardware manager. */ + +#include /* u32 */ +#include /* pr_info() */ +#include /* ioread32() */ +#include /* find_first_bit() */ +#include /* ENODEV */ + +#include "bam.h" +#include "sps_bam.h" + +/** + * Valid BAM Hardware version. + * + */ +#define BAM_MIN_VERSION 2 +#define BAM_MAX_VERSION 0x1f + +/* Maximum number of execution environment */ +#define BAM_MAX_EES 4 + +/** + * BAM Hardware registers. + * + */ +#define CTRL (0xf80) +#define REVISION (0xf84) +#define NUM_PIPES (0xfbc) +#define DESC_CNT_TRSHLD (0xf88) +#define IRQ_SRCS (0xf8c) +#define IRQ_SRCS_MSK (0xf90) +#define IRQ_SRCS_UNMASKED (0xfb0) +#define IRQ_STTS (0xf94) +#define IRQ_CLR (0xf98) +#define IRQ_EN (0xf9c) +#define IRQ_SIC_SEL (0xfa0) +#define AHB_MASTER_ERR_CTRLS (0xfa4) +#define AHB_MASTER_ERR_ADDR (0xfa8) +#define AHB_MASTER_ERR_DATA (0xfac) +/* The addresses for IRQ_DEST and PERIPH_IRQ_DEST become reserved */ +#define IRQ_DEST (0xfb4) +#define PERIPH_IRQ_DEST (0xfb8) +#define TEST_BUS_REG (0xff8) +#define CNFG_BITS (0xffc) +#define TEST_BUS_SEL (0xff4) +#define TRUST_REG (0xff0) +#define IRQ_SRCS_EE(n) (0x1800 + 128 * (n)) +#define IRQ_SRCS_MSK_EE(n) (0x1804 + 128 * (n)) +#define IRQ_SRCS_UNMASKED_EE(n) (0x1808 + 128 * (n)) + +#define P_CTRL(n) (0x0000 + 128 * (n)) +#define P_RST(n) (0x0004 + 128 * (n)) +#define P_HALT(n) (0x0008 + 128 * (n)) +#define P_IRQ_STTS(n) (0x0010 + 128 * (n)) +#define P_IRQ_CLR(n) (0x0014 + 128 * (n)) +#define P_IRQ_EN(n) (0x0018 + 128 * (n)) +#define P_TIMER(n) (0x001c + 128 * (n)) +#define P_TIMER_CTRL(n) (0x0020 + 128 * (n)) +#define P_PRDCR_SDBND(n) (0x0024 + 128 * (n)) +#define P_CNSMR_SDBND(n) (0x0028 + 128 * (n)) +#define P_TRUST_REG(n) (0x0030 + 128 * (n)) +#define P_EVNT_DEST_ADDR(n) (0x102c + 64 * (n)) +#define P_EVNT_REG(n) (0x1018 + 64 * (n)) +#define P_SW_OFSTS(n) (0x1000 + 64 * (n)) +#define P_DATA_FIFO_ADDR(n) (0x1024 + 64 * (n)) +#define P_DESC_FIFO_ADDR(n) (0x101c + 64 * (n)) +#define P_EVNT_GEN_TRSHLD(n) (0x1028 + 64 * (n)) +#define P_FIFO_SIZES(n) (0x1020 + 64 * (n)) +#define P_IRQ_DEST_ADDR(n) (0x103c + 64 * (n)) +#define P_RETR_CNTXT(n) (0x1034 + 64 * (n)) +#define P_SI_CNTXT(n) (0x1038 + 64 * (n)) +#define P_AU_PSM_CNTXT_1(n) (0x1004 + 64 * (n)) +#define P_PSM_CNTXT_2(n) (0x1008 + 64 * (n)) +#define P_PSM_CNTXT_3(n) (0x100c + 64 * (n)) +#define P_PSM_CNTXT_4(n) (0x1010 + 64 * (n)) +#define P_PSM_CNTXT_5(n) (0x1014 + 64 * (n)) + +/** + * BAM Hardware registers bitmask. + * format: _ + * + */ +/* CTRL */ +#define IBC_DISABLE 0x10000 +#define BAM_CACHED_DESC_STORE 0x8000 +#define BAM_DESC_CACHE_SEL 0x6000 +/* BAM_PERIPH_IRQ_SIC_SEL is an obsolete field; This bit is reserved now */ +#define BAM_PERIPH_IRQ_SIC_SEL 0x1000 +#define BAM_EN_ACCUM 0x10 +#define BAM_EN 0x2 +#define BAM_SW_RST 0x1 + +/* REVISION */ +#define BAM_INACTIV_TMR_BASE 0xff000000 +#define BAM_INACTIV_TMRS_EXST 0x80000 +#define BAM_HIGH_FREQUENCY_BAM 0x40000 +#define BAM_HAS_NO_BYPASS 0x20000 +#define BAM_SECURED 0x10000 +#define BAM_NUM_EES 0xf00 +#define BAM_REVISION 0xff + +/* NUM_PIPES */ +#define BAM_NON_PIPE_GRP 0xff000000 +#define BAM_PERIPH_NON_PIPE_GRP 0xff0000 +#define BAM_NUM_PIPES 0xff + +/* DESC_CNT_TRSHLD */ +#define BAM_DESC_CNT_TRSHLD 0xffff + +/* IRQ_SRCS */ +#define BAM_IRQ 0x80000000 +#define P_IRQ 0x7fffffff + +#define IRQ_STTS_BAM_EMPTY_IRQ 0x8 +#define IRQ_STTS_BAM_ERROR_IRQ 0x4 +#define IRQ_STTS_BAM_HRESP_ERR_IRQ 0x2 +#define IRQ_CLR_BAM_EMPTY_CLR 0x8 +#define IRQ_CLR_BAM_ERROR_CLR 0x4 +#define IRQ_CLR_BAM_HRESP_ERR_CLR 0x2 +#define IRQ_EN_BAM_EMPTY_EN 0x8 +#define IRQ_EN_BAM_ERROR_EN 0x4 +#define IRQ_EN_BAM_HRESP_ERR_EN 0x2 +#define IRQ_SIC_SEL_BAM_IRQ_SIC_SEL 0x80000000 +#define IRQ_SIC_SEL_P_IRQ_SIC_SEL 0x7fffffff +#define AHB_MASTER_ERR_CTRLS_BAM_ERR_HVMID 0x7c0000 +#define AHB_MASTER_ERR_CTRLS_BAM_ERR_DIRECT_MODE 0x20000 +#define AHB_MASTER_ERR_CTRLS_BAM_ERR_HCID 0x1f000 +#define AHB_MASTER_ERR_CTRLS_BAM_ERR_HPROT 0xf00 +#define AHB_MASTER_ERR_CTRLS_BAM_ERR_HBURST 0xe0 +#define AHB_MASTER_ERR_CTRLS_BAM_ERR_HSIZE 0x18 +#define AHB_MASTER_ERR_CTRLS_BAM_ERR_HWRITE 0x4 +#define AHB_MASTER_ERR_CTRLS_BAM_ERR_HTRANS 0x3 +#define CNFG_BITS_BAM_AU_ACCUMED 0x4000000 +#define CNFG_BITS_BAM_PSM_P_HD_DATA 0x2000000 +#define CNFG_BITS_BAM_REG_P_EN 0x1000000 +#define CNFG_BITS_BAM_WB_DSC_AVL_P_RST 0x800000 +#define CNFG_BITS_BAM_WB_RETR_SVPNT 0x400000 +#define CNFG_BITS_BAM_WB_CSW_ACK_IDL 0x200000 +#define CNFG_BITS_BAM_WB_BLK_CSW 0x100000 +#define CNFG_BITS_BAM_WB_P_RES 0x80000 +#define CNFG_BITS_BAM_SI_P_RES 0x40000 +#define CNFG_BITS_BAM_AU_P_RES 0x20000 +#define CNFG_BITS_BAM_PSM_P_RES 0x10000 +#define CNFG_BITS_BAM_PSM_CSW_REQ 0x8000 +#define CNFG_BITS_BAM_SB_CLK_REQ 0x4000 +#define CNFG_BITS_BAM_IBC_DISABLE 0x2000 +#define CNFG_BITS_BAM_NO_EXT_P_RST 0x1000 +#define CNFG_BITS_BAM_FULL_PIPE 0x800 +#define CNFG_BITS_BAM_PIPE_CNFG 0x4 + +/* TEST_BUS_SEL */ +#define BAM_DATA_ERASE 0x40000 +#define BAM_DATA_FLUSH 0x20000 +#define BAM_CLK_ALWAYS_ON 0x10000 +#define BAM_TESTBUS_SEL 0x7f + +/* TRUST_REG */ +#define BAM_VMID 0x1f00 +#define BAM_RST_BLOCK 0x80 +#define BAM_EE 0x3 + +/* P_TRUST_REGn */ +#define BAM_P_VMID 0x1f00 +#define BAM_P_EE 0x3 + +/* P_PRDCR_SDBNDn */ +#define P_PRDCR_SDBNDn_BAM_P_SB_UPDATED 0x1000000 +#define P_PRDCR_SDBNDn_BAM_P_TOGGLE 0x100000 +#define P_PRDCR_SDBNDn_BAM_P_CTRL 0xf0000 +#define P_PRDCR_SDBNDn_BAM_P_BYTES_FREE 0xffff +/* P_CNSMR_SDBNDn */ +#define P_CNSMR_SDBNDn_BAM_P_SB_UPDATED 0x1000000 +#define P_CNSMR_SDBNDn_BAM_P_WAIT_4_ACK 0x800000 +#define P_CNSMR_SDBNDn_BAM_P_ACK_TOGGLE 0x400000 +#define P_CNSMR_SDBNDn_BAM_P_ACK_TOGGLE_R 0x200000 +#define P_CNSMR_SDBNDn_BAM_P_TOGGLE 0x100000 +#define P_CNSMR_SDBNDn_BAM_P_CTRL 0xf0000 +#define P_CNSMR_SDBNDn_BAM_P_BYTES_AVAIL 0xffff + +/* P_ctrln */ +#define P_PREFETCH_LIMIT 0x600 +#define P_AUTO_EOB_SEL 0x180 +#define P_AUTO_EOB 0x40 +#define P_SYS_MODE 0x20 +#define P_SYS_STRM 0x10 +#define P_DIRECTION 0x8 +#define P_EN 0x2 + +#define P_RST_P_SW_RST 0x1 + +#define P_HALT_P_PROD_HALTED 0x2 +#define P_HALT_P_HALT 0x1 + +#define P_IRQ_STTS_P_TRNSFR_END_IRQ 0x20 +#define P_IRQ_STTS_P_ERR_IRQ 0x10 +#define P_IRQ_STTS_P_OUT_OF_DESC_IRQ 0x8 +#define P_IRQ_STTS_P_WAKE_IRQ 0x4 +#define P_IRQ_STTS_P_TIMER_IRQ 0x2 +#define P_IRQ_STTS_P_PRCSD_DESC_IRQ 0x1 + +#define P_IRQ_CLR_P_TRNSFR_END_CLR 0x20 +#define P_IRQ_CLR_P_ERR_CLR 0x10 +#define P_IRQ_CLR_P_OUT_OF_DESC_CLR 0x8 +#define P_IRQ_CLR_P_WAKE_CLR 0x4 +#define P_IRQ_CLR_P_TIMER_CLR 0x2 +#define P_IRQ_CLR_P_PRCSD_DESC_CLR 0x1 + +#define P_IRQ_EN_P_TRNSFR_END_EN 0x20 +#define P_IRQ_EN_P_ERR_EN 0x10 +#define P_IRQ_EN_P_OUT_OF_DESC_EN 0x8 +#define P_IRQ_EN_P_WAKE_EN 0x4 +#define P_IRQ_EN_P_TIMER_EN 0x2 +#define P_IRQ_EN_P_PRCSD_DESC_EN 0x1 + +#define P_TIMER_P_TIMER 0xffff + +/* P_TIMER_ctrln */ +#define P_TIMER_RST 0x80000000 +#define P_TIMER_RUN 0x40000000 +#define P_TIMER_MODE 0x20000000 +#define P_TIMER_TRSHLD 0xffff + +/* P_EVNT_regn */ +#define P_BYTES_CONSUMED 0xffff0000 +#define P_DESC_FIFO_PEER_OFST 0xffff + +/* P_SW_ofstsn */ +#define SW_OFST_IN_DESC 0xffff0000 +#define SW_DESC_OFST 0xffff + +#define P_EVNT_GEN_TRSHLD_P_TRSHLD 0xffff + +/* P_FIFO_sizesn */ +#define P_DATA_FIFO_SIZE 0xffff0000 +#define P_DESC_FIFO_SIZE 0xffff + +#define P_RETR_CNTXT_RETR_DESC_OFST 0xffff0000 +#define P_RETR_CNTXT_RETR_OFST_IN_DESC 0xffff +#define P_SI_CNTXT_SI_DESC_OFST 0xffff +#define P_AU_PSM_CNTXT_1_AU_PSM_ACCUMED 0xffff0000 +#define P_AU_PSM_CNTXT_1_AU_ACKED 0xffff +#define P_PSM_CNTXT_2_PSM_DESC_VALID 0x80000000 +#define P_PSM_CNTXT_2_PSM_DESC_IRQ 0x40000000 +#define P_PSM_CNTXT_2_PSM_DESC_IRQ_DONE 0x20000000 +#define P_PSM_CNTXT_2_PSM_GENERAL_BITS 0x1e000000 +#define P_PSM_CNTXT_2_PSM_CONS_STATE 0x1c00000 +#define P_PSM_CNTXT_2_PSM_PROD_SYS_STATE 0x380000 +#define P_PSM_CNTXT_2_PSM_PROD_B2B_STATE 0x70000 +#define P_PSM_CNTXT_2_PSM_DESC_SIZE 0xffff +#define P_PSM_CNTXT_4_PSM_DESC_OFST 0xffff0000 +#define P_PSM_CNTXT_4_PSM_SAVED_ACCUMED_SIZE 0xffff +#define P_PSM_CNTXT_5_PSM_BLOCK_BYTE_CNT 0xffff0000 +#define P_PSM_CNTXT_5_PSM_OFST_IN_DESC 0xffff + +#define BAM_ERROR (-1) + +/* AHB buffer error control */ +enum bam_nonsecure_reset { + BAM_NONSECURE_RESET_ENABLE = 0, + BAM_NONSECURE_RESET_DISABLE = 1, +}; + +/** + * + * Read register with debug info. + * + * @base - bam base virtual address. + * @offset - register offset. + * + * @return u32 + */ +static inline u32 bam_read_reg(void *base, u32 offset) +{ + u32 val = ioread32(base + offset); + SPS_DBG("sps:bam 0x%x(va) read reg 0x%x r_val 0x%x.\n", + (u32) base, offset, val); + return val; +} + +/** + * Read register masked field with debug info. + * + * @base - bam base virtual address. + * @offset - register offset. + * @mask - register bitmask. + * + * @return u32 + */ +static inline u32 bam_read_reg_field(void *base, u32 offset, const u32 mask) +{ + u32 shift = find_first_bit((void *)&mask, 32); + u32 val = ioread32(base + offset); + val &= mask; /* clear other bits */ + val >>= shift; + SPS_DBG("sps:bam 0x%x(va) read reg 0x%x mask 0x%x r_val 0x%x.\n", + (u32) base, offset, mask, val); + return val; +} + +/** + * + * Write register with debug info. + * + * @base - bam base virtual address. + * @offset - register offset. + * @val - value to write. + * + */ +static inline void bam_write_reg(void *base, u32 offset, u32 val) +{ + iowrite32(val, base + offset); + SPS_DBG("sps:bam 0x%x(va) write reg 0x%x w_val 0x%x.\n", + (u32) base, offset, val); +} + +/** + * Write register masked field with debug info. + * + * @base - bam base virtual address. + * @offset - register offset. + * @mask - register bitmask. + * @val - value to write. + * + */ +static inline void bam_write_reg_field(void *base, u32 offset, + const u32 mask, u32 val) +{ + u32 shift = find_first_bit((void *)&mask, 32); + u32 tmp = ioread32(base + offset); + + tmp &= ~mask; /* clear written bits */ + val = tmp | (val << shift); + iowrite32(val, base + offset); + SPS_DBG("sps:bam 0x%x(va) write reg 0x%x w_val 0x%x.\n", + (u32) base, offset, val); +} + +/** + * Initialize a BAM device + * + */ +int bam_init(void *base, u32 ee, + u16 summing_threshold, + u32 irq_mask, u32 *version, u32 *num_pipes) +{ + /* disable bit#11 because of HW bug */ + u32 cfg_bits = 0xffffffff & ~(1 << 11); + u32 ver = 0; + + ver = bam_read_reg_field(base, REVISION, BAM_REVISION); + + if ((ver < BAM_MIN_VERSION) || (ver > BAM_MAX_VERSION)) { + SPS_ERR("sps:bam 0x%x(va) Invalid BAM REVISION 0x%x.\n", + (u32) base, ver); + return -ENODEV; + } else + SPS_INFO("sps:REVISION of BAM 0x%x is 0x%x.\n", + (u32) base, ver); + + if (summing_threshold == 0) { + summing_threshold = 4; + SPS_ERR("sps:bam 0x%x(va) summing_threshold is zero , " + "use default 4.\n", (u32) base); + } + + bam_write_reg_field(base, CTRL, BAM_SW_RST, 1); + /* No delay needed */ + bam_write_reg_field(base, CTRL, BAM_SW_RST, 0); + + bam_write_reg_field(base, CTRL, BAM_EN, 1); + + bam_write_reg(base, DESC_CNT_TRSHLD, summing_threshold); + + bam_write_reg(base, CNFG_BITS, cfg_bits); + + /* + * Enable Global BAM Interrupt - for error reasons , + * filter with mask. + * Note: Pipes interrupts are disabled until BAM_P_IRQ_enn is set + */ + bam_write_reg_field(base, IRQ_SRCS_MSK_EE(ee), BAM_IRQ, 1); + + bam_write_reg(base, IRQ_EN, irq_mask); + + *num_pipes = bam_read_reg_field(base, NUM_PIPES, BAM_NUM_PIPES); + + *version = ver; + + return 0; +} + +/** + * Set BAM global execution environment + * + * @base - BAM virtual base address + * + * @ee - BAM execution environment index + * + * @vmid - virtual master identifier + * + * @reset - enable/disable BAM global software reset + */ +static void bam_set_ee(void *base, u32 ee, u32 vmid, + enum bam_nonsecure_reset reset) +{ + bam_write_reg_field(base, TRUST_REG, BAM_EE, ee); + bam_write_reg_field(base, TRUST_REG, BAM_VMID, vmid); + bam_write_reg_field(base, TRUST_REG, BAM_RST_BLOCK, reset); +} + +/** + * Set the pipe execution environment + * + * @base - BAM virtual base address + * + * @pipe - pipe index + * + * @ee - BAM execution environment index + * + * @vmid - virtual master identifier + */ +static void bam_pipe_set_ee(void *base, u32 pipe, u32 ee, u32 vmid) +{ + bam_write_reg_field(base, P_TRUST_REG(pipe), BAM_P_EE, ee); + bam_write_reg_field(base, P_TRUST_REG(pipe), BAM_P_VMID, vmid); +} + +/** + * Initialize BAM device security execution environment + */ +int bam_security_init(void *base, u32 ee, u32 vmid, u32 pipe_mask) +{ + u32 version; + u32 num_pipes; + u32 mask; + u32 pipe; + + /* + * Discover the hardware version number and the number of pipes + * supported by this BAM + */ + version = bam_read_reg_field(base, REVISION, BAM_REVISION); + num_pipes = bam_read_reg_field(base, NUM_PIPES, BAM_NUM_PIPES); + if (version < 3 || version > 0x1F) { + SPS_ERR("sps:bam 0x%x(va) security is not supported for this" + "BAM version 0x%x.\n", (u32) base, version); + return -ENODEV; + } + + if (num_pipes > BAM_MAX_PIPES) + return -ENODEV; + + for (pipe = 0, mask = 1; pipe < num_pipes; pipe++, mask <<= 1) + if ((mask & pipe_mask) != 0) + bam_pipe_set_ee(base, pipe, ee, vmid); + + /* If MSbit is set, assign top-level interrupt to this EE */ + mask = 1UL << 31; + if ((mask & pipe_mask) != 0) + bam_set_ee(base, ee, vmid, BAM_NONSECURE_RESET_ENABLE); + + return 0; +} + +/** + * Verify that a BAM device is enabled and gathers the hardware + * configuration. + * + */ +int bam_check(void *base, u32 *version, u32 *num_pipes) +{ + u32 ver = 0; + + if (!bam_read_reg_field(base, CTRL, BAM_EN)) + return -ENODEV; + + ver = bam_read_reg(base, REVISION) & BAM_REVISION; + + /* + * Discover the hardware version number and the number of pipes + * supported by this BAM + */ + *num_pipes = bam_read_reg(base, NUM_PIPES); + *version = ver; + + /* Check BAM version */ + if ((ver < BAM_MIN_VERSION) || (ver > BAM_MAX_VERSION)) { + SPS_ERR("sps:bam 0x%x(va) Invalid BAM version 0x%x.\n", + (u32) base, ver); + return -ENODEV; + } + + return 0; +} + +/** + * Disable a BAM device + * + */ +void bam_exit(void *base, u32 ee) +{ + bam_write_reg_field(base, IRQ_SRCS_MSK_EE(ee), BAM_IRQ, 0); + + bam_write_reg(base, IRQ_EN, 0); + + /* Disable the BAM */ + bam_write_reg_field(base, CTRL, BAM_EN, 0); +} + +/** + * Get BAM global IRQ status + */ +u32 bam_get_irq_status(void *base, u32 ee, u32 mask) +{ + u32 status = bam_read_reg(base, IRQ_SRCS_EE(ee)); + status &= mask; + + return status; +} + +/** + * Initialize a BAM pipe + */ +int bam_pipe_init(void *base, u32 pipe, struct bam_pipe_parameters *param, + u32 ee) +{ + /* Reset the BAM pipe */ + bam_write_reg(base, P_RST(pipe), 1); + /* No delay needed */ + bam_write_reg(base, P_RST(pipe), 0); + + /* Enable the Pipe Interrupt at the BAM level */ + bam_write_reg_field(base, IRQ_SRCS_MSK_EE(ee), (1 << pipe), 1); + + bam_write_reg(base, P_IRQ_EN(pipe), param->pipe_irq_mask); + + bam_write_reg_field(base, P_CTRL(pipe), P_DIRECTION, param->dir); + bam_write_reg_field(base, P_CTRL(pipe), P_SYS_MODE, param->mode); + + bam_write_reg(base, P_EVNT_GEN_TRSHLD(pipe), param->event_threshold); + + bam_write_reg(base, P_DESC_FIFO_ADDR(pipe), param->desc_base); + bam_write_reg_field(base, P_FIFO_SIZES(pipe), P_DESC_FIFO_SIZE, + param->desc_size); + + bam_write_reg_field(base, P_CTRL(pipe), P_SYS_STRM, + param->stream_mode); + + if (param->mode == BAM_PIPE_MODE_BAM2BAM) { + u32 peer_dest_addr = param->peer_phys_addr + + P_EVNT_REG(param->peer_pipe); + + bam_write_reg(base, P_DATA_FIFO_ADDR(pipe), + param->data_base); + bam_write_reg_field(base, P_FIFO_SIZES(pipe), + P_DATA_FIFO_SIZE, param->data_size); + + bam_write_reg(base, P_EVNT_DEST_ADDR(pipe), peer_dest_addr); + + SPS_DBG("sps:bam=0x%x(va).pipe=%d.peer_bam=0x%x." + "peer_pipe=%d.\n", + (u32) base, pipe, + (u32) param->peer_phys_addr, + param->peer_pipe); + } + + /* Pipe Enable - at last */ + bam_write_reg_field(base, P_CTRL(pipe), P_EN, 1); + + return 0; +} + +/** + * Reset the BAM pipe + * + */ +void bam_pipe_exit(void *base, u32 pipe, u32 ee) +{ + bam_write_reg(base, P_IRQ_EN(pipe), 0); + + /* Disable the Pipe Interrupt at the BAM level */ + bam_write_reg_field(base, IRQ_SRCS_MSK_EE(ee), (1 << pipe), 0); + + /* Pipe Disable */ + bam_write_reg_field(base, P_CTRL(pipe), P_EN, 0); +} + +/** + * Enable a BAM pipe + * + */ +void bam_pipe_enable(void *base, u32 pipe) +{ + bam_write_reg_field(base, P_CTRL(pipe), P_EN, 1); +} + +/** + * Diasble a BAM pipe + * + */ +void bam_pipe_disable(void *base, u32 pipe) +{ + bam_write_reg_field(base, P_CTRL(pipe), P_EN, 0); +} + +/** + * Check if a BAM pipe is enabled. + * + */ +int bam_pipe_is_enabled(void *base, u32 pipe) +{ + return bam_read_reg_field(base, P_CTRL(pipe), P_EN); +} + +/** + * Configure interrupt for a BAM pipe + * + */ +void bam_pipe_set_irq(void *base, u32 pipe, enum bam_enable irq_en, + u32 src_mask, u32 ee) +{ + bam_write_reg(base, P_IRQ_EN(pipe), src_mask); + bam_write_reg_field(base, IRQ_SRCS_MSK_EE(ee), (1 << pipe), irq_en); +} + +/** + * Configure a BAM pipe for satellite MTI use + * + */ +void bam_pipe_satellite_mti(void *base, u32 pipe, u32 irq_gen_addr, u32 ee) +{ + bam_write_reg(base, P_IRQ_EN(pipe), 0); + bam_write_reg(base, P_IRQ_DEST_ADDR(pipe), irq_gen_addr); + + bam_write_reg_field(base, IRQ_SIC_SEL, (1 << pipe), 1); + bam_write_reg_field(base, IRQ_SRCS_MSK, (1 << pipe), 1); +} + +/** + * Configure MTI for a BAM pipe + * + */ +void bam_pipe_set_mti(void *base, u32 pipe, enum bam_enable irq_en, + u32 src_mask, u32 irq_gen_addr) +{ + /* + * MTI use is only supported on BAMs when global config is controlled + * by a remote processor. + * Consequently, the global configuration register to enable SIC (MTI) + * support cannot be accessed. + * The remote processor must be relied upon to enable the SIC and the + * interrupt. Since the remote processor enable both SIC and interrupt, + * the interrupt enable mask must be set to zero for polling mode. + */ + + bam_write_reg(base, P_IRQ_DEST_ADDR(pipe), irq_gen_addr); + + if (!irq_en) + src_mask = 0; + + bam_write_reg(base, P_IRQ_EN(pipe), src_mask); +} + +/** + * Get and Clear BAM pipe IRQ status + * + */ +u32 bam_pipe_get_and_clear_irq_status(void *base, u32 pipe) +{ + u32 status = 0; + + status = bam_read_reg(base, P_IRQ_STTS(pipe)); + bam_write_reg(base, P_IRQ_CLR(pipe), status); + + return status; +} + +/** + * Set write offset for a BAM pipe + * + */ +void bam_pipe_set_desc_write_offset(void *base, u32 pipe, u32 next_write) +{ + /* + * It is not necessary to perform a read-modify-write masking to write + * the P_DESC_FIFO_PEER_OFST value, since the other field in the + * register (P_BYTES_CONSUMED) is read-only. + */ + bam_write_reg_field(base, P_EVNT_REG(pipe), P_DESC_FIFO_PEER_OFST, + next_write); +} + +/** + * Get write offset for a BAM pipe + * + */ +u32 bam_pipe_get_desc_write_offset(void *base, u32 pipe) +{ + return bam_read_reg_field(base, P_EVNT_REG(pipe), + P_DESC_FIFO_PEER_OFST); +} + +/** + * Get read offset for a BAM pipe + * + */ +u32 bam_pipe_get_desc_read_offset(void *base, u32 pipe) +{ + return bam_read_reg_field(base, P_SW_OFSTS(pipe), SW_DESC_OFST); +} + +/** + * Configure inactivity timer count for a BAM pipe + * + */ +void bam_pipe_timer_config(void *base, u32 pipe, enum bam_pipe_timer_mode mode, + u32 timeout_count) +{ + bam_write_reg_field(base, P_TIMER_CTRL(pipe), P_TIMER_MODE, mode); + bam_write_reg_field(base, P_TIMER_CTRL(pipe), P_TIMER_TRSHLD, + timeout_count); +} + +/** + * Reset inactivity timer for a BAM pipe + * + */ +void bam_pipe_timer_reset(void *base, u32 pipe) +{ + /* reset */ + bam_write_reg_field(base, P_TIMER_CTRL(pipe), P_TIMER_RST, 0); + /* active */ + bam_write_reg_field(base, P_TIMER_CTRL(pipe), P_TIMER_RST, 1); +} + +/** + * Get inactivity timer count for a BAM pipe + * + */ +u32 bam_pipe_timer_get_count(void *base, u32 pipe) +{ + return bam_read_reg(base, P_TIMER(pipe)); +} diff --git a/drivers/platform/msm/sps/bam.h b/drivers/platform/msm/sps/bam.h new file mode 100644 index 00000000000..522073cf368 --- /dev/null +++ b/drivers/platform/msm/sps/bam.h @@ -0,0 +1,394 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* Bus-Access-Manager (BAM) Hardware manager functions API. */ + +#ifndef _BAM_H_ +#define _BAM_H_ + +#include /* u32 */ +#include /* ioread32() */ +#include /* find_first_bit() */ + +/* Pipe mode */ +enum bam_pipe_mode { + BAM_PIPE_MODE_BAM2BAM = 0, /* BAM to BAM */ + BAM_PIPE_MODE_SYSTEM = 1, /* BAM to/from System Memory */ +}; + +/* Pipe direction */ +enum bam_pipe_dir { + /* The Pipe Reads data from data-fifo or system-memory */ + BAM_PIPE_CONSUMER = 0, + /* The Pipe Writes data to data-fifo or system-memory */ + BAM_PIPE_PRODUCER = 1, +}; + +/* Stream mode Type */ +enum bam_stream_mode { + BAM_STREAM_MODE_DISABLE = 0, + BAM_STREAM_MODE_ENABLE = 1, +}; + +/* Enable Type */ +enum bam_enable { + BAM_DISABLE = 0, + BAM_ENABLE = 1, +}; + +/* Pipe timer mode */ +enum bam_pipe_timer_mode { + BAM_PIPE_TIMER_ONESHOT = 0, + BAM_PIPE_TIMER_PERIODIC = 1, +}; + +struct transfer_descriptor { + u32 addr; /* Buffer physical address */ + u32 size:16; /* Buffer size in bytes */ + u32 flags:16; /* Flag bitmask (see SPS_IOVEC_FLAG_ #defines) */ +} __packed; + +/* BAM pipe initialization parameters */ +struct bam_pipe_parameters { + u16 event_threshold; + u32 pipe_irq_mask; + enum bam_pipe_dir dir; + enum bam_pipe_mode mode; + u32 desc_base; /* Physical address of descriptor FIFO */ + u32 desc_size; /* Size (bytes) of descriptor FIFO */ + enum bam_stream_mode stream_mode; + u32 ee; /* BAM execution environment index */ + + /* The following are only valid if mode is BAM2BAM */ + u32 peer_phys_addr; + u32 peer_pipe; + u32 data_base; /* Physical address of data FIFO */ + u32 data_size; /* Size (bytes) of data FIFO */ +}; + +/** + * Initialize a BAM device + * + * This function initializes a BAM device. + * + * @base - BAM virtual base address. + * + * @ee - BAM execution environment index + * + * @summing_threshold - summing threshold (global for all pipes) + * + * @irq_mask - error interrupts mask + * + * @version - return BAM hardware version + * + * @num_pipes - return number of pipes + * + * @return 0 on success, negative value on error + * + */ +int bam_init(void *base, + u32 ee, + u16 summing_threshold, + u32 irq_mask, u32 *version, u32 *num_pipes); + +/** + * Initialize BAM device security execution environment + * + * @base - BAM virtual base address. + * + * @ee - BAM execution environment index + * + * @vmid - virtual master identifier + * + * @pipe_mask - bit mask of pipes to assign to EE + * + * @return 0 on success, negative value on error + * + */ +int bam_security_init(void *base, u32 ee, u32 vmid, u32 pipe_mask); + +/** + * Check a BAM device + * + * This function verifies that a BAM device is enabled and gathers + * the hardware configuration. + * + * @base - BAM virtual base address. + * + * @version - return BAM hardware version + * + * @num_pipes - return number of pipes + * + * @return 0 on success, negative value on error + * + */ +int bam_check(void *base, u32 *version, u32 *num_pipes); + +/** + * Disable a BAM device + * + * This function disables a BAM device. + * + * @base - BAM virtual base address. + * + * @ee - BAM execution environment index + * + */ +void bam_exit(void *base, u32 ee); + +/** + * Get BAM global IRQ status + * + * This function gets BAM global IRQ status. + * + * @base - BAM virtual base address. + * + * @ee - BAM execution environment index + * + * @mask - active pipes mask. + * + * @return IRQ status + * + */ +u32 bam_get_irq_status(void *base, u32 ee, u32 mask); + +/** + * Initialize a BAM pipe + * + * This function initializes a BAM pipe. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @param - bam pipe parameters. + * + * @ee - BAM execution environment index + * + * @return 0 on success, negative value on error + * + */ +int bam_pipe_init(void *base, u32 pipe, struct bam_pipe_parameters *param, + u32 ee); + +/** + * Reset the BAM pipe + * + * This function resets the BAM pipe. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @ee - BAM execution environment index + * + */ +void bam_pipe_exit(void *base, u32 pipe, u32 ee); + +/** + * Enable a BAM pipe + * + * This function enables a BAM pipe. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + */ +void bam_pipe_enable(void *base, u32 pipe); + +/** + * Disable a BAM pipe + * + * This function disables a BAM pipe. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + */ +void bam_pipe_disable(void *base, u32 pipe); + +/** + * Get a BAM pipe enable state + * + * This function determines if a BAM pipe is enabled. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @return true if enabled, false if disabled + * + */ +int bam_pipe_is_enabled(void *base, u32 pipe); + +/** + * Configure interrupt for a BAM pipe + * + * This function configures the interrupt for a BAM pipe. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @irq_en - enable or disable interrupt + * + * @src_mask - interrupt source mask, set regardless of whether + * interrupt is disabled + * + * @ee - BAM execution environment index + * + */ +void bam_pipe_set_irq(void *base, u32 pipe, enum bam_enable irq_en, + u32 src_mask, u32 ee); + +/** + * Configure a BAM pipe for satellite MTI use + * + * This function configures a BAM pipe for satellite MTI use. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @irq_gen_addr - physical address written to generate MTI + * + * @ee - BAM execution environment index + * + */ +void bam_pipe_satellite_mti(void *base, u32 pipe, u32 irq_gen_addr, u32 ee); + +/** + * Configure MTI for a BAM pipe + * + * This function configures the interrupt for a BAM pipe. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @irq_en - enable or disable interrupt + * + * @src_mask - interrupt source mask, set regardless of whether + * interrupt is disabled + * + * @irq_gen_addr - physical address written to generate MTI + * + */ +void bam_pipe_set_mti(void *base, u32 pipe, enum bam_enable irq_en, + u32 src_mask, u32 irq_gen_addr); + +/** + * Get and Clear BAM pipe IRQ status + * + * This function gets and clears BAM pipe IRQ status. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @return IRQ status + * + */ +u32 bam_pipe_get_and_clear_irq_status(void *base, u32 pipe); + +/** + * Set write offset for a BAM pipe + * + * This function sets the write offset for a BAM pipe. This is + * the offset that is maintained by software in system mode. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @next_write - descriptor FIFO write offset + * + */ +void bam_pipe_set_desc_write_offset(void *base, u32 pipe, u32 next_write); + +/** + * Get write offset for a BAM pipe + * + * This function gets the write offset for a BAM pipe. This is + * the offset that is maintained by the pipe's peer pipe or by software. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @return descriptor FIFO write offset + * + */ +u32 bam_pipe_get_desc_write_offset(void *base, u32 pipe); + +/** + * Get read offset for a BAM pipe + * + * This function gets the read offset for a BAM pipe. This is + * the offset that is maintained by the pipe in system mode. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @return descriptor FIFO read offset + * + */ +u32 bam_pipe_get_desc_read_offset(void *base, u32 pipe); + +/** + * Configure inactivity timer count for a BAM pipe + * + * This function configures the inactivity timer count for a BAM pipe. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @mode - timer operating mode + * + * @timeout_count - timeout count + * + */ +void bam_pipe_timer_config(void *base, u32 pipe, + enum bam_pipe_timer_mode mode, + u32 timeout_count); + +/** + * Reset inactivity timer for a BAM pipe + * + * This function resets the inactivity timer count for a BAM pipe. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + */ +void bam_pipe_timer_reset(void *base, u32 pipe); + +/** + * Get inactivity timer count for a BAM pipe + * + * This function gets the inactivity timer count for a BAM pipe. + * + * @base - BAM virtual base address. + * + * @pipe - pipe index + * + * @return inactivity timer count + * + */ +u32 bam_pipe_timer_get_count(void *base, u32 pipe); + +#endif /* _BAM_H_ */ diff --git a/drivers/platform/msm/sps/sps.c b/drivers/platform/msm/sps/sps.c new file mode 100644 index 00000000000..9b5d87c7420 --- /dev/null +++ b/drivers/platform/msm/sps/sps.c @@ -0,0 +1,1502 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* Smart-Peripheral-Switch (SPS) Module. */ + +#include /* u32 */ +#include /* pr_info() */ +#include /* module_init() */ +#include /* kzalloc() */ +#include /* mutex */ +#include /* device */ +#include /* alloc_chrdev_region() */ +#include /* list_head */ +#include /* memset */ +#include /* ioremap() */ +#include /* clk_enable() */ +#include /* platform_get_resource_byname() */ +#include +#include +#include /* msm_sps_platform_data */ + +#include "sps_bam.h" +#include "spsi.h" +#include "sps_core.h" + +#define SPS_DRV_NAME "msm_sps" /* must match the platform_device name */ + +/** + * SPS Driver state struct + */ +struct sps_drv { + struct class *dev_class; + dev_t dev_num; + struct device *dev; + struct clk *pmem_clk; + struct clk *bamdma_clk; + struct clk *dfab_clk; + + int is_ready; + + /* Platform data */ + u32 pipemem_phys_base; + u32 pipemem_size; + u32 bamdma_bam_phys_base; + u32 bamdma_bam_size; + u32 bamdma_dma_phys_base; + u32 bamdma_dma_size; + u32 bamdma_irq; + u32 bamdma_restricted_pipes; + + /* Driver options bitflags (see SPS_OPT_*) */ + u32 options; + + /* Mutex to protect BAM and connection queues */ + struct mutex lock; + + /* BAM devices */ + struct list_head bams_q; + + char *hal_bam_version; + + /* Connection control state */ + struct sps_rm connection_ctrl; +}; + + +/** + * SPS driver state + */ +static struct sps_drv *sps; + +static void sps_device_de_init(void); + +#ifdef CONFIG_DEBUG_FS +static int sps_debugfs_enabled; +static char *debugfs_buf; +static int debugfs_buf_size; +static int debugfs_buf_used; +static int wraparound; + +/* record debug info for debugfs */ +void sps_debugfs_record(const char *msg) +{ + if (sps_debugfs_enabled) { + if (debugfs_buf_used + MAX_MSG_LEN >= debugfs_buf_size) { + debugfs_buf_used = 0; + wraparound = true; + } + debugfs_buf_used += scnprintf(debugfs_buf + debugfs_buf_used, + debugfs_buf_size - debugfs_buf_used, msg); + + if (wraparound) + scnprintf(debugfs_buf + debugfs_buf_used, + debugfs_buf_size - debugfs_buf_used, + "\n**** end line of sps log ****\n\n"); + } +} + +/* read the recorded debug info to userspace */ +static ssize_t sps_read_info(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + int ret; + int size; + + if (wraparound) + size = debugfs_buf_size - MAX_MSG_LEN; + else + size = debugfs_buf_used; + + ret = simple_read_from_buffer(ubuf, count, ppos, + debugfs_buf, size); + + return ret; +} + +/* + * set the buffer size (in KB) for debug info + * if input is 0, then stop recording debug info into buffer + */ +static ssize_t sps_set_info(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long missing; + static char str[5]; + int i, buf_size_kb = 0; + + memset(str, 0, sizeof(str)); + missing = copy_from_user(str, buf, sizeof(str)); + if (missing) + return -EFAULT; + + for (i = 0; i < sizeof(str) && (str[i] >= '0') && (str[i] <= '9'); ++i) + buf_size_kb = (buf_size_kb * 10) + (str[i] - '0'); + + pr_info("sps:debugfs buffer size is %dKB\n", buf_size_kb); + + if (sps_debugfs_enabled && (buf_size_kb == 0)) { + sps_debugfs_enabled = false; + kfree(debugfs_buf); + debugfs_buf = NULL; + debugfs_buf_used = 0; + debugfs_buf_size = 0; + wraparound = false; + } else if (!sps_debugfs_enabled && (buf_size_kb > 0)) { + debugfs_buf_size = buf_size_kb * SZ_1K; + + debugfs_buf = kzalloc(sizeof(char) * debugfs_buf_size, + GFP_KERNEL); + if (!debugfs_buf) { + debugfs_buf_size = 0; + pr_err("sps:fail to allocate memory for debug_fs.\n"); + return -ENOMEM; + } + + sps_debugfs_enabled = true; + debugfs_buf_used = 0; + wraparound = false; + } else if (sps_debugfs_enabled && (buf_size_kb > 0)) + pr_info("sps:should disable debugfs before change " + "buffer size.\n"); + + return sps_debugfs_enabled; +} + +const struct file_operations sps_info_ops = { + .read = sps_read_info, + .write = sps_set_info, +}; + +struct dentry *dent; +struct dentry *dfile; +static void sps_debugfs_init(void) +{ + sps_debugfs_enabled = false; + debugfs_buf_size = 0; + debugfs_buf_used = 0; + wraparound = false; + + dent = debugfs_create_dir("sps", 0); + if (IS_ERR(dent)) { + pr_err("sps:fail to create the folder for debug_fs.\n"); + return; + } + + dfile = debugfs_create_file("info", 0444, dent, 0, + &sps_info_ops); + if (!dfile || IS_ERR(dfile)) { + pr_err("sps:fail to create the file for debug_fs.\n"); + debugfs_remove(dent); + return; + } +} + +static void sps_debugfs_exit(void) +{ + if (dfile) + debugfs_remove(dfile); + if (dent) + debugfs_remove(dent); + kfree(debugfs_buf); + debugfs_buf = NULL; +} +#endif + +/** + * Initialize SPS device + * + * This function initializes the SPS device. + * + * @return 0 on success, negative value on error + * + */ +static int sps_device_init(void) +{ + int result; + int success; +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + struct sps_bam_props bamdma_props = {0}; +#endif + + SPS_DBG("sps_device_init"); + + success = false; + + result = sps_mem_init(sps->pipemem_phys_base, sps->pipemem_size); + if (result) { + SPS_ERR("SPS memory init failed"); + goto exit_err; + } + + INIT_LIST_HEAD(&sps->bams_q); + mutex_init(&sps->lock); + + if (sps_rm_init(&sps->connection_ctrl, sps->options)) { + SPS_ERR("Failed to init SPS resource manager"); + goto exit_err; + } + + result = sps_bam_driver_init(sps->options); + if (result) { + SPS_ERR("SPS BAM driver init failed"); + goto exit_err; + } + + /* Initialize the BAM DMA device */ +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + bamdma_props.phys_addr = sps->bamdma_bam_phys_base; + bamdma_props.virt_addr = ioremap(sps->bamdma_bam_phys_base, + sps->bamdma_bam_size); + + if (!bamdma_props.virt_addr) { + SPS_ERR("sps:Failed to IO map BAM-DMA BAM registers.\n"); + goto exit_err; + } + + SPS_DBG("sps:bamdma_bam.phys=0x%x.virt=0x%x.", + bamdma_props.phys_addr, + (u32) bamdma_props.virt_addr); + + bamdma_props.periph_phys_addr = sps->bamdma_dma_phys_base; + bamdma_props.periph_virt_size = sps->bamdma_dma_size; + bamdma_props.periph_virt_addr = ioremap(sps->bamdma_dma_phys_base, + sps->bamdma_dma_size); + + if (!bamdma_props.periph_virt_addr) { + SPS_ERR("sps:Failed to IO map BAM-DMA peripheral reg.\n"); + goto exit_err; + } + + SPS_DBG("sps:bamdma_dma.phys=0x%x.virt=0x%x.", + bamdma_props.periph_phys_addr, + (u32) bamdma_props.periph_virt_addr); + + bamdma_props.irq = sps->bamdma_irq; + + bamdma_props.event_threshold = 0x10; /* Pipe event threshold */ + bamdma_props.summing_threshold = 0x10; /* BAM event threshold */ + + bamdma_props.options = SPS_BAM_OPT_BAMDMA; + bamdma_props.restricted_pipes = sps->bamdma_restricted_pipes; + + result = sps_dma_init(&bamdma_props); + if (result) { + SPS_ERR("SPS BAM DMA driver init failed"); + goto exit_err; + } +#endif /* CONFIG_SPS_SUPPORT_BAMDMA */ + + result = sps_map_init(NULL, sps->options); + if (result) { + SPS_ERR("SPS connection mapping init failed"); + goto exit_err; + } + + success = true; +exit_err: + if (!success) { +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + sps_device_de_init(); +#endif + return SPS_ERROR; + } + + return 0; +} + +/** + * De-initialize SPS device + * + * This function de-initializes the SPS device. + * + * @return 0 on success, negative value on error + * + */ +static void sps_device_de_init(void) +{ + SPS_DBG("%s.", __func__); + + if (sps != NULL) { +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + sps_dma_de_init(); +#endif + /* Are there any remaining BAM registrations? */ + if (!list_empty(&sps->bams_q)) + SPS_ERR("SPS de-init: BAMs are still registered"); + + sps_map_de_init(); + + kfree(sps); + } + + sps_mem_de_init(); +} + +/** + * Initialize client state context + * + * This function initializes a client state context struct. + * + * @client - Pointer to client state context + * + * @return 0 on success, negative value on error + * + */ +static int sps_client_init(struct sps_pipe *client) +{ + if (client == NULL) + return -EINVAL; + + /* + * NOTE: Cannot store any state within the SPS driver because + * the driver init function may not have been called yet. + */ + memset(client, 0, sizeof(*client)); + sps_rm_config_init(&client->connect); + + client->client_state = SPS_STATE_DISCONNECT; + client->bam = NULL; + + return 0; +} + +/** + * De-initialize client state context + * + * This function de-initializes a client state context struct. + * + * @client - Pointer to client state context + * + * @return 0 on success, negative value on error + * + */ +static int sps_client_de_init(struct sps_pipe *client) +{ + if (client->client_state != SPS_STATE_DISCONNECT) { + SPS_ERR("De-init client in connected state: 0x%x", + client->client_state); + return SPS_ERROR; + } + + client->bam = NULL; + client->map = NULL; + memset(&client->connect, 0, sizeof(client->connect)); + + return 0; +} + +/** + * Find the BAM device from the physical address + * + * This function finds a BAM device in the BAM registration list that + * matches the specified physical address. + * + * @phys_addr - physical address of the BAM + * + * @return - pointer to the BAM device struct, or NULL on error + * + */ +static struct sps_bam *phy2bam(u32 phys_addr) +{ + struct sps_bam *bam; + + list_for_each_entry(bam, &sps->bams_q, list) { + if (bam->props.phys_addr == phys_addr) + return bam; + } + + return NULL; +} + +/** + * Find the BAM device from the handle + * + * This function finds a BAM device in the BAM registration list that + * matches the specified device handle. + * + * @h - device handle of the BAM + * + * @return - pointer to the BAM device struct, or NULL on error + * + */ +struct sps_bam *sps_h2bam(u32 h) +{ + struct sps_bam *bam; + + if (h == SPS_DEV_HANDLE_MEM || h == SPS_DEV_HANDLE_INVALID) + return NULL; + + list_for_each_entry(bam, &sps->bams_q, list) { + if ((u32) bam == (u32) h) + return bam; + } + + SPS_ERR("Can't find BAM device for handle 0x%x.", h); + + return NULL; +} + +/** + * Lock BAM device + * + * This function obtains the BAM mutex on the client's connection. + * + * @pipe - pointer to client pipe state + * + * @return pointer to BAM device struct, or NULL on error + * + */ +static struct sps_bam *sps_bam_lock(struct sps_pipe *pipe) +{ + struct sps_bam *bam; + u32 pipe_index; + + bam = pipe->bam; + if (bam == NULL) { + SPS_ERR("Connection not in connected state"); + return NULL; + } + + mutex_lock(&bam->lock); + + /* Verify client owns this pipe */ + pipe_index = pipe->pipe_index; + if (pipe_index >= bam->props.num_pipes || + pipe != bam->pipes[pipe_index]) { + SPS_ERR("Client not owner of BAM 0x%x pipe: %d (max %d)", + bam->props.phys_addr, pipe_index, + bam->props.num_pipes); + mutex_unlock(&bam->lock); + return NULL; + } + + return bam; +} + +/** + * Unlock BAM device + * + * This function releases the BAM mutex on the client's connection. + * + * @bam - pointer to BAM device struct + * + */ +static inline void sps_bam_unlock(struct sps_bam *bam) +{ + mutex_unlock(&bam->lock); +} + +/** + * Connect an SPS connection end point + * + */ +int sps_connect(struct sps_pipe *h, struct sps_connect *connect) +{ + struct sps_pipe *pipe = h; + u32 dev; + struct sps_bam *bam; + int result; + + if (sps == NULL) + return -ENODEV; + + if (!sps->is_ready) { + SPS_ERR("sps_connect.sps driver not ready.\n"); + return -EAGAIN; + } + + mutex_lock(&sps->lock); + /* + * Must lock the BAM device at the top level function, so must + * determine which BAM is the target for the connection + */ + if (connect->mode == SPS_MODE_SRC) + dev = connect->source; + else + dev = connect->destination; + + bam = sps_h2bam(dev); + if (bam == NULL) { + SPS_ERR("Invalid BAM device handle: 0x%x", dev); + result = SPS_ERROR; + goto exit_err; + } + + SPS_DBG("sps_connect: bam 0x%x src 0x%x dest 0x%x mode %s", + BAM_ID(bam), + connect->source, + connect->destination, + connect->mode == SPS_MODE_SRC ? "SRC" : "DEST"); + + /* Allocate resources for the specified connection */ + pipe->connect = *connect; + mutex_lock(&bam->lock); + result = sps_rm_state_change(pipe, SPS_STATE_ALLOCATE); + mutex_unlock(&bam->lock); + if (result) + goto exit_err; + + /* Configure the connection */ + mutex_lock(&bam->lock); + result = sps_rm_state_change(pipe, SPS_STATE_CONNECT); + mutex_unlock(&bam->lock); + if (result) { + sps_disconnect(h); + goto exit_err; + } + +exit_err: + mutex_unlock(&sps->lock); + + return result; +} +EXPORT_SYMBOL(sps_connect); + +/** + * Disconnect an SPS connection end point + * + * This function disconnects an SPS connection end point. + * The SPS hardware associated with that end point will be disabled. + * For a connection involving system memory (SPS_DEV_HANDLE_MEM), all + * connection resources are deallocated. For a peripheral-to-peripheral + * connection, the resources associated with the connection will not be + * deallocated until both end points are closed. + * + * The client must call sps_connect() for the handle before calling + * this function. + * + * @h - client context for SPS connection end point + * + * @return 0 on success, negative value on error + * + */ +int sps_disconnect(struct sps_pipe *h) +{ + struct sps_pipe *pipe = h; + struct sps_pipe *check; + struct sps_bam *bam; + int result; + + if (pipe == NULL) + return SPS_ERROR; + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + SPS_DBG("sps_disconnect: bam 0x%x src 0x%x dest 0x%x mode %s", + BAM_ID(bam), + pipe->connect.source, + pipe->connect.destination, + pipe->connect.mode == SPS_MODE_SRC ? "SRC" : "DEST"); + + result = SPS_ERROR; + /* Cross-check client with map table */ + if (pipe->connect.mode == SPS_MODE_SRC) + check = pipe->map->client_src; + else + check = pipe->map->client_dest; + + if (check != pipe) { + SPS_ERR("Client context is corrupt"); + goto exit_err; + } + + /* Disconnect the BAM pipe */ + result = sps_rm_state_change(pipe, SPS_STATE_DISCONNECT); + if (result) + goto exit_err; + + sps_rm_config_init(&pipe->connect); + result = 0; + +exit_err: + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_disconnect); + +/** + * Register an event object for an SPS connection end point + * + */ +int sps_register_event(struct sps_pipe *h, struct sps_register_event *reg) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + if (sps == NULL) + return -ENODEV; + + if (!sps->is_ready) { + SPS_ERR("sps_connect.sps driver not ready.\n"); + return -EAGAIN; + } + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + result = sps_bam_pipe_reg_event(bam, pipe->pipe_index, reg); + sps_bam_unlock(bam); + if (result) + SPS_ERR("Failed to register event for BAM 0x%x pipe %d", + pipe->bam->props.phys_addr, pipe->pipe_index); + + return result; +} +EXPORT_SYMBOL(sps_register_event); + +/** + * Enable an SPS connection end point + * + */ +int sps_flow_on(struct sps_pipe *h) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + /* Enable the pipe data flow */ + result = sps_rm_state_change(pipe, SPS_STATE_ENABLE); + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_flow_on); + +/** + * Disable an SPS connection end point + * + */ +int sps_flow_off(struct sps_pipe *h, enum sps_flow_off mode) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + /* Disable the pipe data flow */ + result = sps_rm_state_change(pipe, SPS_STATE_DISABLE); + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_flow_off); + +/** + * Perform a DMA transfer on an SPS connection end point + * + */ +int sps_transfer(struct sps_pipe *h, struct sps_transfer *transfer) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + result = sps_bam_pipe_transfer(bam, pipe->pipe_index, transfer); + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_transfer); + +/** + * Perform a single DMA transfer on an SPS connection end point + * + */ +int sps_transfer_one(struct sps_pipe *h, u32 addr, u32 size, + void *user, u32 flags) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + result = sps_bam_pipe_transfer_one(bam, pipe->pipe_index, + addr, size, user, flags); + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_transfer_one); + +/** + * Read event queue for an SPS connection end point + * + */ +int sps_get_event(struct sps_pipe *h, struct sps_event_notify *notify) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + result = sps_bam_pipe_get_event(bam, pipe->pipe_index, notify); + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_get_event); + +/** + * Determine whether an SPS connection end point FIFO is empty + * + */ +int sps_is_pipe_empty(struct sps_pipe *h, u32 *empty) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + result = sps_bam_pipe_is_empty(bam, pipe->pipe_index, empty); + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_is_pipe_empty); + +/** + * Get number of free transfer entries for an SPS connection end point + * + */ +int sps_get_free_count(struct sps_pipe *h, u32 *count) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + result = sps_bam_get_free_count(bam, pipe->pipe_index, count); + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_get_free_count); + +/** + * Reset an SPS BAM device + * + */ +int sps_device_reset(u32 dev) +{ + struct sps_bam *bam; + int result; + + SPS_DBG("%s: dev = 0x%x", __func__, dev); + + mutex_lock(&sps->lock); + /* Search for the target BAM device */ + bam = sps_h2bam(dev); + if (bam == NULL) { + SPS_ERR("Invalid BAM device handle: 0x%x", dev); + result = SPS_ERROR; + goto exit_err; + } + + mutex_lock(&bam->lock); + result = sps_bam_reset(bam); + mutex_unlock(&bam->lock); + if (result) { + SPS_ERR("Failed to reset BAM device: 0x%x", dev); + goto exit_err; + } + +exit_err: + mutex_unlock(&sps->lock); + + return result; +} +EXPORT_SYMBOL(sps_device_reset); + +/** + * Get the configuration parameters for an SPS connection end point + * + */ +int sps_get_config(struct sps_pipe *h, struct sps_connect *config) +{ + struct sps_pipe *pipe = h; + + if (config == NULL) { + SPS_ERR("Config pointer is NULL"); + return SPS_ERROR; + } + + /* Copy current client connection state */ + *config = pipe->connect; + + return 0; +} +EXPORT_SYMBOL(sps_get_config); + +/** + * Set the configuration parameters for an SPS connection end point + * + */ +int sps_set_config(struct sps_pipe *h, struct sps_connect *config) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + result = sps_bam_pipe_set_params(bam, pipe->pipe_index, + config->options); + if (result == 0) + pipe->connect.options = config->options; + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_set_config); + +/** + * Set ownership of an SPS connection end point + * + */ +int sps_set_owner(struct sps_pipe *h, enum sps_owner owner, + struct sps_satellite *connect) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + if (owner != SPS_OWNER_REMOTE) { + SPS_ERR("Unsupported ownership state: %d", owner); + return SPS_ERROR; + } + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + result = sps_bam_set_satellite(bam, pipe->pipe_index); + if (result) + goto exit_err; + + /* Return satellite connect info */ + if (connect == NULL) + goto exit_err; + + if (pipe->connect.mode == SPS_MODE_SRC) { + connect->dev = pipe->map->src.bam_phys; + connect->pipe_index = pipe->map->src.pipe_index; + } else { + connect->dev = pipe->map->dest.bam_phys; + connect->pipe_index = pipe->map->dest.pipe_index; + } + connect->config = SPS_CONFIG_SATELLITE; + connect->options = (enum sps_option) 0; + +exit_err: + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_set_owner); + +/** + * Allocate memory from the SPS Pipe-Memory. + * + */ +int sps_alloc_mem(struct sps_pipe *h, enum sps_mem mem, + struct sps_mem_buffer *mem_buffer) +{ + if (sps == NULL) + return -ENODEV; + + if (!sps->is_ready) { + SPS_ERR("sps_alloc_mem.sps driver not ready.\n"); + return -EAGAIN; + } + + if (mem_buffer == NULL || mem_buffer->size == 0) + return SPS_ERROR; + + mem_buffer->phys_base = sps_mem_alloc_io(mem_buffer->size); + if (mem_buffer->phys_base == SPS_ADDR_INVALID) + return SPS_ERROR; + + mem_buffer->base = spsi_get_mem_ptr(mem_buffer->phys_base); + + return 0; +} +EXPORT_SYMBOL(sps_alloc_mem); + +/** + * Free memory from the SPS Pipe-Memory. + * + */ +int sps_free_mem(struct sps_pipe *h, struct sps_mem_buffer *mem_buffer) +{ + if (mem_buffer == NULL || mem_buffer->phys_base == SPS_ADDR_INVALID) + return SPS_ERROR; + + sps_mem_free_io(mem_buffer->phys_base, mem_buffer->size); + + return 0; +} +EXPORT_SYMBOL(sps_free_mem); + +/** + * Register a BAM device + * + */ +int sps_register_bam_device(const struct sps_bam_props *bam_props, + u32 *dev_handle) +{ + struct sps_bam *bam = NULL; + void *virt_addr = NULL; + u32 manage; + int ok; + int result; + + if (sps == NULL) + return SPS_ERROR; + + /* BAM-DMA is registered internally during power-up */ + if ((!sps->is_ready) && !(bam_props->options & SPS_BAM_OPT_BAMDMA)) { + SPS_ERR("sps_register_bam_device.sps driver not ready.\n"); + return -EAGAIN; + } + + if (bam_props == NULL || dev_handle == NULL) + return SPS_ERROR; + + /* Check BAM parameters */ + manage = bam_props->manage & SPS_BAM_MGR_ACCESS_MASK; + if (manage != SPS_BAM_MGR_NONE) { + if (bam_props->virt_addr == NULL && bam_props->virt_size == 0) { + SPS_ERR("Invalid properties for BAM: %x", + bam_props->phys_addr); + return SPS_ERROR; + } + } + if ((bam_props->manage & SPS_BAM_MGR_DEVICE_REMOTE) == 0) { + /* BAM global is configured by local processor */ + if (bam_props->summing_threshold == 0) { + SPS_ERR("Invalid device ctrl properties for BAM: %x", + bam_props->phys_addr); + return SPS_ERROR; + } + } + manage = bam_props->manage & + (SPS_BAM_MGR_PIPE_NO_CONFIG | SPS_BAM_MGR_PIPE_NO_CTRL); + + /* In case of error */ + *dev_handle = SPS_DEV_HANDLE_INVALID; + result = SPS_ERROR; + + mutex_lock(&sps->lock); + /* Is this BAM already registered? */ + bam = phy2bam(bam_props->phys_addr); + if (bam != NULL) { + mutex_unlock(&sps->lock); + SPS_ERR("BAM already registered: %x", bam->props.phys_addr); + result = -EEXIST; + bam = NULL; /* Avoid error clean-up kfree(bam) */ + goto exit_err; + } + + /* Perform virtual mapping if required */ + if ((bam_props->manage & SPS_BAM_MGR_ACCESS_MASK) != + SPS_BAM_MGR_NONE && bam_props->virt_addr == NULL) { + /* Map the memory region */ + virt_addr = ioremap(bam_props->phys_addr, bam_props->virt_size); + if (virt_addr == NULL) { + SPS_ERR("Unable to map BAM IO memory: %x %x", + bam_props->phys_addr, bam_props->virt_size); + goto exit_err; + } + } + + bam = kzalloc(sizeof(*bam), GFP_KERNEL); + if (bam == NULL) { + SPS_ERR("Unable to allocate BAM device state: size 0x%x", + sizeof(*bam)); + goto exit_err; + } + memset(bam, 0, sizeof(*bam)); + + mutex_init(&bam->lock); + mutex_lock(&bam->lock); + + /* Copy configuration to BAM device descriptor */ + bam->props = *bam_props; + if (virt_addr != NULL) + bam->props.virt_addr = virt_addr; + + if ((bam_props->manage & SPS_BAM_MGR_DEVICE_REMOTE) != 0 && + (bam_props->manage & SPS_BAM_MGR_MULTI_EE) != 0 && + bam_props->ee == 0) { + /* + * BAM global is owned by a remote processor, so force EE index + * to a non-zero value to insure EE zero globals are not + * modified. + */ + SPS_INFO("Setting EE for BAM %x to non-zero", + bam_props->phys_addr); + bam->props.ee = 1; + } + + ok = sps_bam_device_init(bam); + mutex_unlock(&bam->lock); + if (ok) { + SPS_ERR("Failed to init BAM device: phys 0x%0x", + bam->props.phys_addr); + goto exit_err; + } + + /* Add BAM to the list */ + list_add_tail(&bam->list, &sps->bams_q); + *dev_handle = (u32) bam; + + result = 0; +exit_err: + mutex_unlock(&sps->lock); + + if (result) { + if (virt_addr != NULL) + iounmap(bam->props.virt_addr); + + if (bam != NULL) + kfree(bam); + + return result; + } + + /* If this BAM is attached to a BAM-DMA, init the BAM-DMA device */ +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) { + if (sps_dma_device_init((u32) bam)) { + bam->props.options &= ~SPS_BAM_OPT_BAMDMA; + sps_deregister_bam_device((u32) bam); + SPS_ERR("Failed to init BAM-DMA device: BAM phys 0x%0x", + bam->props.phys_addr); + return SPS_ERROR; + } + } +#endif /* CONFIG_SPS_SUPPORT_BAMDMA */ + + SPS_DBG("SPS registered BAM: phys 0x%x.", bam->props.phys_addr); + + return 0; +} +EXPORT_SYMBOL(sps_register_bam_device); + +/** + * Deregister a BAM device + * + */ +int sps_deregister_bam_device(u32 dev_handle) +{ + struct sps_bam *bam; + + bam = sps_h2bam(dev_handle); + if (bam == NULL) + return SPS_ERROR; + + SPS_DBG("SPS deregister BAM: phys 0x%x.", bam->props.phys_addr); + + /* If this BAM is attached to a BAM-DMA, init the BAM-DMA device */ +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) { + mutex_lock(&bam->lock); + (void)sps_dma_device_de_init((u32) bam); + bam->props.options &= ~SPS_BAM_OPT_BAMDMA; + mutex_unlock(&bam->lock); + } +#endif + + /* Remove the BAM from the registration list */ + mutex_lock(&sps->lock); + list_del(&bam->list); + mutex_unlock(&sps->lock); + + /* De-init the BAM and free resources */ + mutex_lock(&bam->lock); + sps_bam_device_de_init(bam); + mutex_unlock(&bam->lock); + if (bam->props.virt_size) + (void)iounmap(bam->props.virt_addr); + + kfree(bam); + + return 0; +} +EXPORT_SYMBOL(sps_deregister_bam_device); + +/** + * Get processed I/O vector (completed transfers) + * + */ +int sps_get_iovec(struct sps_pipe *h, struct sps_iovec *iovec) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + if (h == NULL || iovec == NULL) + return SPS_ERROR; + + SPS_DBG("%s.", __func__); + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + /* Get an iovec from the BAM pipe descriptor FIFO */ + result = sps_bam_pipe_get_iovec(bam, pipe->pipe_index, iovec); + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_get_iovec); + +/** + * Perform timer control + * + */ +int sps_timer_ctrl(struct sps_pipe *h, + struct sps_timer_ctrl *timer_ctrl, + struct sps_timer_result *timer_result) +{ + struct sps_pipe *pipe = h; + struct sps_bam *bam; + int result; + + SPS_DBG("%s.", __func__); + + if (h == NULL || timer_ctrl == NULL) + return SPS_ERROR; + + bam = sps_bam_lock(pipe); + if (bam == NULL) + return SPS_ERROR; + + /* Perform the BAM pipe timer control operation */ + result = sps_bam_pipe_timer_ctrl(bam, pipe->pipe_index, timer_ctrl, + timer_result); + sps_bam_unlock(bam); + + return result; +} +EXPORT_SYMBOL(sps_timer_ctrl); + +/** + * Allocate client state context + * + */ +struct sps_pipe *sps_alloc_endpoint(void) +{ + struct sps_pipe *ctx = NULL; + + ctx = kzalloc(sizeof(struct sps_pipe), GFP_KERNEL); + if (ctx == NULL) { + SPS_ERR("Allocate pipe context fail."); + return NULL; + } + + sps_client_init(ctx); + + return ctx; +} +EXPORT_SYMBOL(sps_alloc_endpoint); + +/** + * Free client state context + * + */ +int sps_free_endpoint(struct sps_pipe *ctx) +{ + int res; + + res = sps_client_de_init(ctx); + + if (res == 0) + kfree(ctx); + + return res; +} +EXPORT_SYMBOL(sps_free_endpoint); + +/** + * Platform Driver. + */ +static int get_platform_data(struct platform_device *pdev) +{ + struct resource *resource; + struct msm_sps_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + if (pdata == NULL) { + SPS_ERR("sps:inavlid platform data.\n"); + sps->bamdma_restricted_pipes = 0; + return -EINVAL; + } else { + sps->bamdma_restricted_pipes = pdata->bamdma_restricted_pipes; + SPS_DBG("sps:bamdma_restricted_pipes=0x%x.", + sps->bamdma_restricted_pipes); + } + + resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pipe_mem"); + if (resource) { + sps->pipemem_phys_base = resource->start; + sps->pipemem_size = resource_size(resource); + SPS_DBG("sps:pipemem.base=0x%x,size=0x%x.", + sps->pipemem_phys_base, + sps->pipemem_size); + } + +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "bamdma_bam"); + if (resource) { + sps->bamdma_bam_phys_base = resource->start; + sps->bamdma_bam_size = resource_size(resource); + SPS_DBG("sps:bamdma_bam.base=0x%x,size=0x%x.", + sps->bamdma_bam_phys_base, + sps->bamdma_bam_size); + } + + resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "bamdma_dma"); + if (resource) { + sps->bamdma_dma_phys_base = resource->start; + sps->bamdma_dma_size = resource_size(resource); + SPS_DBG("sps:bamdma_dma.base=0x%x,size=0x%x.", + sps->bamdma_dma_phys_base, + sps->bamdma_dma_size); + } + + resource = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "bamdma_irq"); + if (resource) { + sps->bamdma_irq = resource->start; + SPS_DBG("sps:bamdma_irq=%d.", sps->bamdma_irq); + } +#endif + + return 0; +} + +static int __devinit msm_sps_probe(struct platform_device *pdev) +{ + int ret; + + SPS_DBG("sps:msm_sps_probe."); + + ret = get_platform_data(pdev); + if (ret) + return -ENODEV; + + /* Create Device */ + sps->dev_class = class_create(THIS_MODULE, SPS_DRV_NAME); + + ret = alloc_chrdev_region(&sps->dev_num, 0, 1, SPS_DRV_NAME); + if (ret) { + SPS_ERR("sps:alloc_chrdev_region err."); + goto alloc_chrdev_region_err; + } + + sps->dev = device_create(sps->dev_class, NULL, sps->dev_num, sps, + SPS_DRV_NAME); + if (IS_ERR(sps->dev)) { + SPS_ERR("sps:device_create err."); + goto device_create_err; + } + + sps->dfab_clk = clk_get(sps->dev, "dfab_clk"); + if (IS_ERR(sps->dfab_clk)) { + SPS_ERR("sps:fail to get dfab_clk."); + goto clk_err; + } else { + ret = clk_enable(sps->dfab_clk); + if (ret) { + SPS_ERR("sps:failed to enable dfab_clk. ret=%d", ret); + goto clk_err; + } + } + + sps->pmem_clk = clk_get(sps->dev, "pmem_clk"); + if (IS_ERR(sps->pmem_clk)) { + SPS_ERR("sps:fail to get pmem_clk."); + goto clk_err; + } else { + ret = clk_enable(sps->pmem_clk); + if (ret) { + SPS_ERR("sps:failed to enable pmem_clk. ret=%d", ret); + goto clk_err; + } + } + +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + sps->bamdma_clk = clk_get(sps->dev, "dma_bam_pclk"); + if (IS_ERR(sps->bamdma_clk)) { + SPS_ERR("sps:fail to get bamdma_clk."); + goto clk_err; + } else { + ret = clk_enable(sps->bamdma_clk); + if (ret) { + SPS_ERR("sps:failed to enable bamdma_clk. ret=%d", ret); + goto clk_err; + } + } +#endif + + ret = sps_device_init(); + if (ret) { + SPS_ERR("sps:sps_device_init err."); + goto sps_device_init_err; + } + + sps->is_ready = true; + + SPS_INFO("sps is ready."); + + return 0; +clk_err: +sps_device_init_err: + device_destroy(sps->dev_class, sps->dev_num); +device_create_err: + unregister_chrdev_region(sps->dev_num, 1); +alloc_chrdev_region_err: + class_destroy(sps->dev_class); + + return -ENODEV; +} + +static int __devexit msm_sps_remove(struct platform_device *pdev) +{ + SPS_DBG("%s.", __func__); + + device_destroy(sps->dev_class, sps->dev_num); + unregister_chrdev_region(sps->dev_num, 1); + class_destroy(sps->dev_class); + sps_device_de_init(); + + clk_put(sps->dfab_clk); + clk_put(sps->pmem_clk); + clk_put(sps->bamdma_clk); + + return 0; +} + +static struct platform_driver msm_sps_driver = { + .probe = msm_sps_probe, + .driver = { + .name = SPS_DRV_NAME, + .owner = THIS_MODULE, + }, + .remove = __exit_p(msm_sps_remove), +}; + +/** + * Module Init. + */ +static int __init sps_init(void) +{ + int ret; + +#ifdef CONFIG_DEBUG_FS + sps_debugfs_init(); +#endif + + SPS_DBG("%s.", __func__); + + /* Allocate the SPS driver state struct */ + sps = kzalloc(sizeof(*sps), GFP_KERNEL); + if (sps == NULL) { + SPS_ERR("sps:Unable to allocate driver state context."); + return -ENOMEM; + } + + ret = platform_driver_register(&msm_sps_driver); + + return ret; +} + +/** + * Module Exit. + */ +static void __exit sps_exit(void) +{ + SPS_DBG("%s.", __func__); + + platform_driver_unregister(&msm_sps_driver); + + if (sps != NULL) { + kfree(sps); + sps = NULL; + } + +#ifdef CONFIG_DEBUG_FS + sps_debugfs_exit(); +#endif +} + +arch_initcall(sps_init); +module_exit(sps_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Smart Peripheral Switch (SPS)"); + diff --git a/drivers/platform/msm/sps/sps_bam.c b/drivers/platform/msm/sps/sps_bam.c new file mode 100644 index 00000000000..8fbf4f93a47 --- /dev/null +++ b/drivers/platform/msm/sps/sps_bam.c @@ -0,0 +1,1910 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include /* u32 */ +#include /* pr_info() */ +#include /* mutex */ +#include /* list_head */ +#include /* kzalloc() */ +#include /* request_irq() */ +#include /* memset */ + +#include "sps_bam.h" +#include "bam.h" +#include "spsi.h" + +/* All BAM global IRQ sources */ +#define BAM_IRQ_ALL (BAM_DEV_IRQ_HRESP_ERROR | BAM_DEV_IRQ_ERROR) + +/* BAM device state flags */ +#define BAM_STATE_INIT (1UL << 1) +#define BAM_STATE_IRQ (1UL << 2) +#define BAM_STATE_ENABLED (1UL << 3) +#define BAM_STATE_BAM2BAM (1UL << 4) +#define BAM_STATE_MTI (1UL << 5) +#define BAM_STATE_REMOTE (1UL << 6) + +/* Mask for valid hardware descriptor flags */ +#define BAM_IOVEC_FLAG_MASK \ + (SPS_IOVEC_FLAG_INT | SPS_IOVEC_FLAG_EOT | SPS_IOVEC_FLAG_EOB) + +/* Mask for invalid BAM-to-BAM pipe options */ +#define BAM2BAM_O_INVALID \ + (SPS_O_DESC_DONE | \ + SPS_O_EOT | \ + SPS_O_POLL | \ + SPS_O_NO_Q | \ + SPS_O_ACK_TRANSFERS) + +/** + * Pipe/client pointer value indicating pipe is allocated, but no client has + * been assigned + */ +#define BAM_PIPE_UNASSIGNED ((struct sps_pipe *)0x77777777) + +/* Check whether pipe has been assigned */ +#define BAM_PIPE_IS_ASSIGNED(p) \ + (((p) != NULL) && ((p) != BAM_PIPE_UNASSIGNED)) + +/* Is MTI use supported for a specific BAM version? */ +#define BAM_VERSION_MTI_SUPPORT(ver) (ver <= 2) + +/* Event option<->event translation table entry */ +struct sps_bam_opt_event_table { + enum sps_event event_id; + enum sps_option option; + enum bam_pipe_irq pipe_irq; +}; + +static const struct sps_bam_opt_event_table opt_event_table[] = { + {SPS_EVENT_EOT, SPS_O_EOT, BAM_PIPE_IRQ_EOT}, + {SPS_EVENT_DESC_DONE, SPS_O_DESC_DONE, BAM_PIPE_IRQ_DESC_INT}, + {SPS_EVENT_WAKEUP, SPS_O_WAKEUP, BAM_PIPE_IRQ_WAKE}, + {SPS_EVENT_INACTIVE, SPS_O_INACTIVE, BAM_PIPE_IRQ_TIMER}, + {SPS_EVENT_OUT_OF_DESC, SPS_O_OUT_OF_DESC, + BAM_PIPE_IRQ_OUT_OF_DESC}, + {SPS_EVENT_ERROR, SPS_O_ERROR, BAM_PIPE_IRQ_ERROR} +}; + +/* Pipe event source handler */ +static void pipe_handler(struct sps_bam *dev, + struct sps_pipe *pipe); + +/** + * Pipe transfer event (EOT, DESC_DONE) source handler. + * This function is called by pipe_handler() and other functions to process the + * descriptor FIFO. + */ +static void pipe_handler_eot(struct sps_bam *dev, + struct sps_pipe *pipe); + +/** + * BAM driver initialization + */ +int sps_bam_driver_init(u32 options) +{ + int n; + + /* + * Check that SPS_O_ and BAM_PIPE_IRQ_ values are identical. + * This is required so that the raw pipe IRQ status can be passed + * to the client in the SPS_EVENT_IRQ. + */ + for (n = 0; n < ARRAY_SIZE(opt_event_table); n++) { + if ((u32)opt_event_table[n].option != + (u32)opt_event_table[n].pipe_irq) { + SPS_ERR("SPS_O 0x%x != HAL IRQ 0x%x", + opt_event_table[n].option, + opt_event_table[n].pipe_irq); + return SPS_ERROR; + } + } + + return 0; +} + +/** + * BAM interrupt service routine + * + * This function is the BAM interrupt service routine. + * + * @ctxt - pointer to ISR's registered argument + * + * @return void + */ +static irqreturn_t bam_isr(int irq, void *ctxt) +{ + struct sps_bam *dev = ctxt; + struct sps_pipe *pipe; + u32 source; + unsigned long flags = 0; + + spin_lock_irqsave(&dev->isr_lock, flags); + + /* Get BAM interrupt source(s) */ + if ((dev->state & BAM_STATE_MTI) == 0) { + u32 mask = dev->pipe_active_mask; + source = bam_get_irq_status(dev->base, + dev->props.ee, + mask); + + SPS_DBG("sps:bam_isr:bam=0x%x;source=0x%x;mask=0x%x.", + BAM_ID(dev), source, mask); + + /* Mask any non-local source */ + source &= dev->pipe_active_mask; + } else { + /* If MTIs are used, must poll each active pipe */ + source = dev->pipe_active_mask; + } + + /* Process active pipe sources */ + pipe = list_first_entry(&dev->pipes_q, struct sps_pipe, list); + + list_for_each_entry(pipe, &dev->pipes_q, list) { + /* Check this pipe's bit in the source mask */ + if ((source & pipe->pipe_index_mask)) { + /* This pipe has an interrupt pending */ + pipe_handler(dev, pipe); + source &= ~pipe->pipe_index_mask; + } + if (source == 0) + break; + } + + /* Process any inactive pipe sources */ + if (source) { + SPS_ERR("IRQ from BAM 0x%x inactive pipe(s) 0x%x", + BAM_ID(dev), source); + dev->irq_from_disabled_pipe++; + } + + spin_unlock_irqrestore(&dev->isr_lock, flags); + + return IRQ_HANDLED; +} + +/** + * BAM device enable + */ +int sps_bam_enable(struct sps_bam *dev) +{ + u32 num_pipes; + u32 irq_mask; + int result; + int rc; + int MTIenabled; + + /* Is this BAM enabled? */ + if ((dev->state & BAM_STATE_ENABLED)) + return 0; /* Yes, so no work to do */ + + /* Is there any access to this BAM? */ + if ((dev->props.manage & SPS_BAM_MGR_ACCESS_MASK) == SPS_BAM_MGR_NONE) { + SPS_ERR("No local access to BAM 0x%x", BAM_ID(dev)); + return SPS_ERROR; + } + + /* Set interrupt handling */ + if ((dev->props.options & SPS_BAM_OPT_IRQ_DISABLED) != 0 || + dev->props.irq == SPS_IRQ_INVALID) { + /* Disable the BAM interrupt */ + irq_mask = 0; + dev->state &= ~BAM_STATE_IRQ; + } else { + /* Register BAM ISR */ + if (dev->props.irq > 0) + result = request_irq(dev->props.irq, + (irq_handler_t) bam_isr, + IRQF_TRIGGER_HIGH, "sps", dev); + + if (result) { + SPS_ERR("Failed to register BAM 0x%x IRQ %d", + BAM_ID(dev), dev->props.irq); + return SPS_ERROR; + } + + /* Enable the BAM interrupt */ + irq_mask = BAM_IRQ_ALL; + dev->state |= BAM_STATE_IRQ; + } + + /* Is global BAM control managed by the local processor? */ + num_pipes = 0; + if ((dev->props.manage & SPS_BAM_MGR_DEVICE_REMOTE) == 0) + /* Yes, so initialize the BAM device */ + rc = bam_init(dev->base, + dev->props.ee, + (u16) dev->props.summing_threshold, + irq_mask, + &dev->version, &num_pipes); + else + /* No, so just verify that it is enabled */ + rc = bam_check(dev->base, &dev->version, &num_pipes); + + if (rc) { + SPS_ERR("Failed to init BAM 0x%x IRQ %d", + BAM_ID(dev), dev->props.irq); + return SPS_ERROR; + } + + /* Check if this BAM supports MTIs (Message Triggered Interrupts) or + * multiple EEs (Execution Environments). + * MTI and EE support are mutually exclusive. + */ + MTIenabled = BAM_VERSION_MTI_SUPPORT(dev->version); + + if ((dev->props.manage & SPS_BAM_MGR_DEVICE_REMOTE) != 0 && + (dev->props.manage & SPS_BAM_MGR_MULTI_EE) != 0 && + dev->props.ee == 0 && MTIenabled) { + /* + * BAM global is owned by remote processor and local processor + * must use MTI. Thus, force EE index to a non-zero value to + * insure that EE zero globals can't be modified. + */ + SPS_ERR("sps: EE for satellite BAM must be set to non-zero"); + return SPS_ERROR; + } + + /* + * Enable MTI use (message triggered interrupt) + * if local processor does not control the global BAM config + * and this BAM supports MTIs. + */ + if ((dev->state & BAM_STATE_IRQ) != 0 && + (dev->props.manage & SPS_BAM_MGR_DEVICE_REMOTE) != 0 && + MTIenabled) { + if (dev->props.irq_gen_addr == 0 || + dev->props.irq_gen_addr == SPS_ADDR_INVALID) { + SPS_ERR("MTI destination address not specified " + "for BAM 0x%x", BAM_ID(dev)); + return SPS_ERROR; + } + dev->state |= BAM_STATE_MTI; + } + + if (num_pipes) { + dev->props.num_pipes = num_pipes; + SPS_DBG("BAM 0x%x number of pipes reported by hw: %d", + BAM_ID(dev), dev->props.num_pipes); + } + + /* Check EE index */ + if (!MTIenabled && dev->props.ee >= SPS_BAM_NUM_EES) { + SPS_ERR("Invalid EE BAM 0x%x: %d", BAM_ID(dev), dev->props.ee); + return SPS_ERROR; + } + + /* + * Process EE configuration parameters, + * if specified in the properties + */ + if (!MTIenabled && dev->props.sec_config == SPS_BAM_SEC_DO_CONFIG) { + struct sps_bam_sec_config_props *p_sec = + dev->props.p_sec_config_props; + if (p_sec == NULL) { + SPS_ERR("EE config table is not specified for " + "BAM 0x%x", BAM_ID(dev)); + return SPS_ERROR; + } + + /* + * Set restricted pipes based on the pipes assigned to local EE + */ + dev->props.restricted_pipes = + ~p_sec->ees[dev->props.ee].pipe_mask; + + /* + * If local processor manages the BAM, perform the EE + * configuration + */ + if ((dev->props.manage & SPS_BAM_MGR_DEVICE_REMOTE) == 0) { + u32 ee; + u32 pipe_mask; + int n, i; + + /* + * Verify that there are no overlapping pipe + * assignments + */ + for (n = 0; n < SPS_BAM_NUM_EES - 1; n++) { + for (i = n + 1; i < SPS_BAM_NUM_EES; i++) { + if ((p_sec->ees[n].pipe_mask & + p_sec->ees[i].pipe_mask) != 0) { + SPS_ERR("Overlapping pipe " + "assignments for BAM " + "0x%x: EEs %d and %d", + BAM_ID(dev), n, i); + return SPS_ERROR; + } + } + } + + for (ee = 0; ee < SPS_BAM_NUM_EES; ee++) { + /* + * MSbit specifies EE for the global (top-level) + * BAM interrupt + */ + pipe_mask = p_sec->ees[ee].pipe_mask; + if (ee == dev->props.ee) + pipe_mask |= (1UL << 31); + else + pipe_mask &= ~(1UL << 31); + + bam_security_init(dev->base, ee, + p_sec->ees[ee].vmid, pipe_mask); + } + } + } + + /* + * If local processor manages the BAM and the BAM supports MTIs + * but does not support multiple EEs, set all restricted pipes + * to MTI mode. + */ + if ((dev->props.manage & SPS_BAM_MGR_DEVICE_REMOTE) == 0 + && MTIenabled) { + u32 pipe_index; + u32 pipe_mask; + for (pipe_index = 0, pipe_mask = 1; + pipe_index < dev->props.num_pipes; + pipe_index++, pipe_mask <<= 1) { + if ((pipe_mask & dev->props.restricted_pipes) == 0) + continue; /* This is a local pipe */ + + /* + * Enable MTI with destination address of zero + * (and source mask zero). Pipe is in reset, + * so no interrupt will be generated. + */ + bam_pipe_satellite_mti(dev->base, pipe_index, 0, + dev->props.ee); + } + } + + dev->state |= BAM_STATE_ENABLED; + SPS_DBG("BAM 0x%x enabled: ver: %d, number of pipes: %d", + BAM_ID(dev), dev->version, dev->props.num_pipes); + return 0; +} + +/** + * BAM device disable + * + */ +int sps_bam_disable(struct sps_bam *dev) +{ + if ((dev->state & BAM_STATE_ENABLED) == 0) + return 0; + + /* Is there any access to this BAM? */ + if ((dev->props.manage & SPS_BAM_MGR_ACCESS_MASK) == SPS_BAM_MGR_NONE) { + SPS_ERR("No local access to BAM 0x%x", BAM_ID(dev)); + return SPS_ERROR; + } + + /* Is this BAM controlled by the local processor? */ + if ((dev->props.manage & SPS_BAM_MGR_DEVICE_REMOTE)) { + /* No, so just mark it disabled */ + dev->state &= ~BAM_STATE_ENABLED; + return 0; + } + + /* Disable BAM (interrupts) */ + if ((dev->state & BAM_STATE_IRQ)) { + bam_exit(dev->base, dev->props.ee); + + /* Deregister BAM ISR */ + if ((dev->state & BAM_STATE_IRQ)) + if (dev->props.irq > 0) + free_irq(dev->props.irq, dev); + dev->state &= ~BAM_STATE_IRQ; + } + + dev->state &= ~BAM_STATE_ENABLED; + + SPS_DBG("BAM 0x%x disabled", BAM_ID(dev)); + + return 0; +} + +/** + * BAM device initialization + */ +int sps_bam_device_init(struct sps_bam *dev) +{ + if (dev->props.virt_addr == NULL) { + SPS_ERR("NULL BAM virtual address"); + return SPS_ERROR; + } + dev->base = (void *) dev->props.virt_addr; + + if (dev->props.num_pipes == 0) { + /* Assume max number of pipes until BAM registers can be read */ + dev->props.num_pipes = BAM_MAX_PIPES; + SPS_DBG("BAM 0x%x: assuming max number of pipes: %d", + BAM_ID(dev), dev->props.num_pipes); + } + + /* Init BAM state data */ + dev->state = 0; + dev->pipe_active_mask = 0; + dev->pipe_remote_mask = 0; + INIT_LIST_HEAD(&dev->pipes_q); + + spin_lock_init(&dev->isr_lock); + + if ((dev->props.options & SPS_BAM_OPT_ENABLE_AT_BOOT)) + if (sps_bam_enable(dev)) + return SPS_ERROR; + + SPS_DBG("BAM device: phys 0x%x IRQ %d", BAM_ID(dev), dev->props.irq); + + return 0; +} + +/** + * BAM device de-initialization + * + */ +int sps_bam_device_de_init(struct sps_bam *dev) +{ + int result; + + SPS_DBG("BAM device DEINIT: phys 0x%x IRQ %d", + BAM_ID(dev), dev->props.irq); + + result = sps_bam_disable(dev); + + return result; +} + +/** + * BAM device reset + * + */ +int sps_bam_reset(struct sps_bam *dev) +{ + struct sps_pipe *pipe; + u32 pipe_index; + int result; + + SPS_DBG("BAM device RESET: phys 0x%x IRQ %d", + BAM_ID(dev), dev->props.irq); + + /* If BAM is enabled, then disable */ + result = 0; + if ((dev->state & BAM_STATE_ENABLED)) { + /* Verify that no pipes are currently allocated */ + for (pipe_index = 0; pipe_index < dev->props.num_pipes; + pipe_index++) { + pipe = dev->pipes[pipe_index]; + if (BAM_PIPE_IS_ASSIGNED(pipe)) { + SPS_ERR("BAM device 0x%x RESET failed: " + "pipe %d in use", + BAM_ID(dev), pipe_index); + result = SPS_ERROR; + break; + } + } + + if (result == 0) + result = sps_bam_disable(dev); + } + + /* BAM will be reset as part of the enable process */ + if (result == 0) + result = sps_bam_enable(dev); + + return result; +} + +/** + * Clear the BAM pipe state struct + * + * This function clears the BAM pipe state struct. + * + * @pipe - pointer to client pipe struct + * + */ +static void pipe_clear(struct sps_pipe *pipe) +{ + INIT_LIST_HEAD(&pipe->list); + + pipe->state = 0; + pipe->pipe_index = SPS_BAM_PIPE_INVALID; + pipe->pipe_index_mask = 0; + pipe->irq_mask = 0; + pipe->mode = -1; + pipe->num_descs = 0; + pipe->desc_size = 0; + memset(&pipe->sys, 0, sizeof(pipe->sys)); + INIT_LIST_HEAD(&pipe->sys.events_q); +} + +/** + * Allocate a BAM pipe + * + */ +u32 sps_bam_pipe_alloc(struct sps_bam *dev, u32 pipe_index) +{ + u32 pipe_mask; + + if (pipe_index == SPS_BAM_PIPE_INVALID) { + /* Allocate a pipe from the BAM */ + if ((dev->props.manage & SPS_BAM_MGR_PIPE_NO_ALLOC)) { + SPS_ERR("Restricted from allocating pipes on BAM 0x%x", + BAM_ID(dev)); + return SPS_BAM_PIPE_INVALID; + } + for (pipe_index = 0, pipe_mask = 1; + pipe_index < dev->props.num_pipes; + pipe_index++, pipe_mask <<= 1) { + if ((pipe_mask & dev->props.restricted_pipes)) + continue; /* This is a restricted pipe */ + + if (dev->pipes[pipe_index] == NULL) + break; /* Found an available pipe */ + } + if (pipe_index >= dev->props.num_pipes) { + SPS_ERR("Failed to allocate pipe on BAM 0x%x", + BAM_ID(dev)); + return SPS_BAM_PIPE_INVALID; + } + } else { + /* Check that client-specified pipe is available */ + if (pipe_index >= dev->props.num_pipes) { + SPS_ERR("Invalid pipe %d for allocate on BAM 0x%x", + pipe_index, BAM_ID(dev)); + return SPS_BAM_PIPE_INVALID; + } + if ((dev->props.restricted_pipes & (1UL << pipe_index))) { + SPS_ERR("BAM 0x%x pipe %d is not local", + BAM_ID(dev), pipe_index); + return SPS_BAM_PIPE_INVALID; + } + if (dev->pipes[pipe_index] != NULL) { + SPS_ERR("Pipe %d already allocated on BAM 0x%x", + pipe_index, BAM_ID(dev)); + return SPS_BAM_PIPE_INVALID; + } + } + + /* Mark pipe as allocated */ + dev->pipes[pipe_index] = BAM_PIPE_UNASSIGNED; + + return pipe_index; +} + +/** + * Free a BAM pipe + * + */ +void sps_bam_pipe_free(struct sps_bam *dev, u32 pipe_index) +{ + struct sps_pipe *pipe; + + if (pipe_index >= dev->props.num_pipes) { + SPS_ERR("Invalid BAM 0x%x pipe: %d", BAM_ID(dev), pipe_index); + return; + } + + /* Get the client pipe struct and mark the pipe free */ + pipe = dev->pipes[pipe_index]; + dev->pipes[pipe_index] = NULL; + + /* Is the pipe currently allocated? */ + if (pipe == NULL) { + SPS_ERR("Attempt to free unallocated pipe %d on BAM 0x%x", + pipe_index, BAM_ID(dev)); + return; + } + + if (pipe == BAM_PIPE_UNASSIGNED) + return; /* Never assigned, so no work to do */ + + /* Return pending items to appropriate pools */ + if (!list_empty(&pipe->sys.events_q)) { + struct sps_q_event *sps_event; + + SPS_ERR("Disconnect BAM 0x%x pipe %d with events pending", + BAM_ID(dev), pipe_index); + + list_for_each_entry(sps_event, &pipe->sys.events_q, list) { + list_del(&sps_event->list); + kfree(sps_event); + } + } + + /* Clear the BAM pipe state struct */ + pipe_clear(pipe); +} + +/** + * Establish BAM pipe connection + * + */ +int sps_bam_pipe_connect(struct sps_pipe *bam_pipe, + const struct sps_bam_connect_param *params) +{ + struct bam_pipe_parameters hw_params; + struct sps_bam *dev; + const struct sps_connection *map = bam_pipe->map; + const struct sps_conn_end_pt *map_pipe; + const struct sps_conn_end_pt *other_pipe; + void *desc_buf = NULL; + u32 pipe_index; + int result; + + /* Clear the client pipe state and hw init struct */ + pipe_clear(bam_pipe); + memset(&hw_params, 0, sizeof(hw_params)); + + /* Initialize the BAM state struct */ + bam_pipe->mode = params->mode; + + /* Set pipe streaming mode */ + if ((params->options & SPS_O_STREAMING) == 0) + hw_params.stream_mode = BAM_STREAM_MODE_DISABLE; + else + hw_params.stream_mode = BAM_STREAM_MODE_ENABLE; + + /* Determine which end point to connect */ + if (bam_pipe->mode == SPS_MODE_SRC) { + map_pipe = &map->src; + other_pipe = &map->dest; + hw_params.dir = BAM_PIPE_PRODUCER; + } else { + map_pipe = &map->dest; + other_pipe = &map->src; + hw_params.dir = BAM_PIPE_CONSUMER; + } + + /* Process map parameters */ + dev = map_pipe->bam; + pipe_index = map_pipe->pipe_index; + if (pipe_index >= dev->props.num_pipes) { + SPS_ERR("Invalid BAM 0x%x pipe: %d", BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + hw_params.event_threshold = (u16) map_pipe->event_threshold; + hw_params.ee = dev->props.ee; + + /* Verify that control of this pipe is allowed */ + if ((dev->props.manage & SPS_BAM_MGR_PIPE_NO_CTRL) || + (dev->props.restricted_pipes & (1UL << pipe_index))) { + SPS_ERR("BAM 0x%x pipe %d is not local", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + /* Control without configuration permission is not supported yet */ + if ((dev->props.manage & SPS_BAM_MGR_PIPE_NO_CONFIG)) { + SPS_ERR("BAM 0x%x pipe %d remote config is not supported", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + /* Determine operational mode */ + if (other_pipe->bam != NULL) { + /* BAM-to-BAM mode */ + bam_pipe->state |= BAM_STATE_BAM2BAM; + hw_params.mode = BAM_PIPE_MODE_BAM2BAM; + hw_params.peer_phys_addr = + ((struct sps_bam *) (other_pipe->bam))->props.phys_addr; + hw_params.peer_pipe = other_pipe->pipe_index; + + /* Verify FIFO buffers are allocated for BAM-to-BAM pipes */ + if (map->desc.phys_base == SPS_ADDR_INVALID || + map->data.phys_base == SPS_ADDR_INVALID || + map->desc.size == 0 || map->data.size == 0) { + SPS_ERR("FIFO buffers are not allocated for BAM 0x%x " + "pipe %d", BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + hw_params.data_base = map->data.phys_base; + hw_params.data_size = map->data.size; + + /* Clear the data FIFO for debug */ + if (map->data.base != NULL && bam_pipe->mode == SPS_MODE_SRC) + memset(map->data.base, 0, hw_params.data_size); + } else { + /* System mode */ + hw_params.mode = BAM_PIPE_MODE_SYSTEM; + bam_pipe->sys.desc_buf = map->desc.base; + bam_pipe->sys.desc_offset = 0; + bam_pipe->sys.acked_offset = 0; + } + + /* Initialize the client pipe state */ + bam_pipe->pipe_index = pipe_index; + bam_pipe->pipe_index_mask = 1UL << pipe_index; + + /* Get virtual address for descriptor FIFO */ + if (map->desc.phys_base != SPS_ADDR_INVALID) { + if (map->desc.size < (2 * sizeof(struct sps_iovec))) { + SPS_ERR("Invalid descriptor FIFO size " + "for BAM 0x%x pipe %d: %d", + BAM_ID(dev), pipe_index, map->desc.size); + return SPS_ERROR; + } + desc_buf = map->desc.base; + + /* + * Note that descriptor base and size will be left zero from + * the memset() above if the physical address was invalid. + * This allows a satellite driver to set the FIFO as + * local memory for system mode. + */ + hw_params.desc_base = map->desc.phys_base; + hw_params.desc_size = map->desc.size; + } + + /* Configure the descriptor FIFO for both operational modes */ + if (desc_buf != NULL) + if (bam_pipe->mode == SPS_MODE_SRC || + hw_params.mode == BAM_PIPE_MODE_SYSTEM) + memset(desc_buf, 0, hw_params.desc_size); + + bam_pipe->desc_size = hw_params.desc_size; + bam_pipe->num_descs = bam_pipe->desc_size / sizeof(struct sps_iovec); + + result = SPS_ERROR; + /* Insure that the BAM is enabled */ + if ((dev->state & BAM_STATE_ENABLED) == 0) + if (sps_bam_enable(dev)) + goto exit_init_err; + + /* Check pipe allocation */ + if (dev->pipes[pipe_index] != BAM_PIPE_UNASSIGNED) { + SPS_ERR("Invalid pipe %d on BAM 0x%x for connect", + pipe_index, BAM_ID(dev)); + goto exit_err; + } + + if (bam_pipe_is_enabled(dev->base, pipe_index)) { + SPS_ERR("BAM 0x%x pipe %d sharing violation", + BAM_ID(dev), pipe_index); + goto exit_err; + } + + if (bam_pipe_init(dev->base, pipe_index, &hw_params, dev->props.ee)) { + SPS_ERR("BAM 0x%x pipe %d init error", + BAM_ID(dev), pipe_index); + goto exit_err; + } + + /* Assign pipe to client */ + dev->pipes[pipe_index] = bam_pipe; + + /* Process configuration parameters */ + if (params->options != 0 || + (bam_pipe->state & BAM_STATE_BAM2BAM) == 0) { + /* Process init-time only parameters */ + u32 irq_gen_addr; + + /* Set interrupt mode */ + irq_gen_addr = SPS_ADDR_INVALID; + if ((params->options & SPS_O_IRQ_MTI)) + /* Client has directly specified the MTI address */ + irq_gen_addr = params->irq_gen_addr; + else if ((dev->state & BAM_STATE_MTI)) + /* This BAM has MTI use enabled */ + irq_gen_addr = dev->props.irq_gen_addr; + + if (irq_gen_addr != SPS_ADDR_INVALID) { + /* + * No checks - assume BAM is already setup for + * MTI generation, + * or the pipe will be set to satellite control. + */ + bam_pipe->state |= BAM_STATE_MTI; + bam_pipe->irq_gen_addr = irq_gen_addr; + } + + /* Process runtime parameters */ + if (sps_bam_pipe_set_params(dev, pipe_index, + params->options)) { + dev->pipes[pipe_index] = BAM_PIPE_UNASSIGNED; + goto exit_err; + } + } + + /* Indicate initialization is complete */ + dev->pipes[pipe_index] = bam_pipe; + dev->pipe_active_mask |= 1UL << pipe_index; + list_add_tail(&bam_pipe->list, &dev->pipes_q); + + bam_pipe->state |= BAM_STATE_INIT; + result = 0; +exit_err: + if (result) + bam_pipe_exit(dev->base, pipe_index, dev->props.ee); +exit_init_err: + if (result) { + /* Clear the client pipe state */ + pipe_clear(bam_pipe); + } + + return result; +} + +/** + * Disconnect a BAM pipe connection + * + */ +int sps_bam_pipe_disconnect(struct sps_bam *dev, u32 pipe_index) +{ + struct sps_pipe *pipe; + int result; + + if (pipe_index >= dev->props.num_pipes) { + SPS_ERR("Invalid BAM 0x%x pipe: %d", BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + /* Deallocate and reset the BAM pipe */ + pipe = dev->pipes[pipe_index]; + if (BAM_PIPE_IS_ASSIGNED(pipe)) { + if ((dev->pipe_active_mask & (1UL << pipe_index))) { + list_del(&pipe->list); + dev->pipe_active_mask &= ~(1UL << pipe_index); + } + dev->pipe_remote_mask &= ~(1UL << pipe_index); + bam_pipe_exit(dev->base, pipe_index, dev->props.ee); + if (pipe->sys.desc_cache != NULL) { + kfree(pipe->sys.desc_cache); + pipe->sys.desc_cache = NULL; + } + dev->pipes[pipe_index] = BAM_PIPE_UNASSIGNED; + pipe_clear(pipe); + result = 0; + } else { + result = SPS_ERROR; + } + + if (result) + SPS_ERR("BAM 0x%x pipe %d already disconnected", + BAM_ID(dev), pipe_index); + + return result; +} + +/** + * Set BAM pipe interrupt enable state + * + * This function sets the interrupt enable state for a BAM pipe. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @poll - true if SPS_O_POLL is set, false otherwise + * + */ +static void pipe_set_irq(struct sps_bam *dev, u32 pipe_index, + u32 poll) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + enum bam_enable irq_enable; + + if (poll == 0 && pipe->irq_mask != 0 && + (dev->state & BAM_STATE_IRQ)) { + if ((pipe->state & BAM_STATE_BAM2BAM) != 0 && + (pipe->state & BAM_STATE_IRQ) == 0) { + /* + * If enabling the interrupt for a BAM-to-BAM pipe, + * clear the existing interrupt status + */ + (void)bam_pipe_get_and_clear_irq_status(dev->base, + pipe_index); + } + pipe->state |= BAM_STATE_IRQ; + irq_enable = BAM_ENABLE; + pipe->polled = false; + } else { + pipe->state &= ~BAM_STATE_IRQ; + irq_enable = BAM_DISABLE; + pipe->polled = true; + if (poll == 0 && pipe->irq_mask) + SPS_INFO("BAM 0x%x pipe %d forced to use polling", + BAM_ID(dev), pipe_index); + } + if ((pipe->state & BAM_STATE_MTI) == 0) + bam_pipe_set_irq(dev->base, pipe_index, irq_enable, + pipe->irq_mask, dev->props.ee); + else + bam_pipe_set_mti(dev->base, pipe_index, irq_enable, + pipe->irq_mask, pipe->irq_gen_addr); + +} + +/** + * Set BAM pipe parameters + * + */ +int sps_bam_pipe_set_params(struct sps_bam *dev, u32 pipe_index, u32 options) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + u32 mask; + int wake_up_is_one_shot; + int no_queue; + int ack_xfers; + u32 size; + int n; + + /* Capture some options */ + wake_up_is_one_shot = ((options & SPS_O_WAKEUP_IS_ONESHOT)); + no_queue = ((options & SPS_O_NO_Q)); + ack_xfers = ((options & SPS_O_ACK_TRANSFERS)); + + /* Create interrupt source mask */ + mask = 0; + for (n = 0; n < ARRAY_SIZE(opt_event_table); n++) { + /* Is client registering for this event? */ + if ((options & opt_event_table[n].option) == 0) + continue; /* No */ + + mask |= opt_event_table[n].pipe_irq; + } + +#ifdef SPS_BAM_STATISTICS + /* Is an illegal mode change specified? */ + if (pipe->sys.desc_wr_count > 0 && + (no_queue != pipe->sys.no_queue + || ack_xfers != pipe->sys.ack_xfers)) { + SPS_ERR("Queue/ack mode change after transfer: " + "BAM 0x%x pipe %d opt 0x%x", + BAM_ID(dev), pipe_index, options); + return SPS_ERROR; + } +#endif /* SPS_BAM_STATISTICS */ + + /* Is client setting invalid options for a BAM-to-BAM connection? */ + if ((pipe->state & BAM_STATE_BAM2BAM) && + (options & BAM2BAM_O_INVALID)) { + SPS_ERR("Invalid option for BAM-to-BAM: BAM 0x%x pipe %d " + "opt 0x%x", BAM_ID(dev), pipe_index, options); + return SPS_ERROR; + } + + /* Allocate descriptor FIFO cache if NO_Q option is disabled */ + if (!no_queue && pipe->sys.desc_cache == NULL && pipe->num_descs > 0 + && (pipe->state & BAM_STATE_BAM2BAM) == 0) { + /* Allocate both descriptor cache and user pointer array */ + size = pipe->num_descs * sizeof(void *); + pipe->sys.desc_cache = + kzalloc(pipe->desc_size + size, GFP_KERNEL); + if (pipe->sys.desc_cache == NULL) { + /*** MUST BE LAST POINT OF FAILURE (see below) *****/ + SPS_ERR("Desc cache error: BAM 0x%x pipe %d: %d", + BAM_ID(dev), pipe_index, + pipe->desc_size + size); + return SPS_ERROR; + } + pipe->sys.user_ptrs = (void **)(pipe->sys.desc_cache + + pipe->desc_size); + pipe->sys.cache_offset = pipe->sys.acked_offset; + } + + /* + * No failures beyond this point. Note that malloc() is last point of + * failure, so no free() handling is needed. + */ + + /* Enable/disable the pipe's interrupt sources */ + pipe->irq_mask = mask; + pipe_set_irq(dev, pipe_index, (options & SPS_O_POLL)); + + /* Store software feature enables */ + pipe->wake_up_is_one_shot = wake_up_is_one_shot; + pipe->sys.no_queue = no_queue; + pipe->sys.ack_xfers = ack_xfers; + + return 0; +} + +/** + * Enable a BAM pipe + * + */ +int sps_bam_pipe_enable(struct sps_bam *dev, u32 pipe_index) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + + /* Enable the BAM pipe */ + bam_pipe_enable(dev->base, pipe_index); + pipe->state |= BAM_STATE_ENABLED; + + return 0; +} + +/** + * Disable a BAM pipe + * + */ +int sps_bam_pipe_disable(struct sps_bam *dev, u32 pipe_index) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + + /* Disable the BAM pipe */ + bam_pipe_disable(dev->base, pipe_index); + pipe->state &= ~BAM_STATE_ENABLED; + + return 0; +} + +/** + * Register an event for a BAM pipe + * + */ +int sps_bam_pipe_reg_event(struct sps_bam *dev, + u32 pipe_index, + struct sps_register_event *reg) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + struct sps_bam_event_reg *event_reg; + int n; + + if (pipe->sys.no_queue && reg->xfer_done != NULL && + reg->mode != SPS_TRIGGER_CALLBACK) { + SPS_ERR("Only callback events support for NO_Q: " + "BAM 0x%x pipe %d mode %d", + BAM_ID(dev), pipe_index, reg->mode); + return SPS_ERROR; + } + + for (n = 0; n < ARRAY_SIZE(opt_event_table); n++) { + int index; + + /* Is client registering for this event? */ + if ((reg->options & opt_event_table[n].option) == 0) + continue; /* No */ + + index = SPS_EVENT_INDEX(opt_event_table[n].event_id); + event_reg = &pipe->sys.event_regs[index]; + event_reg->xfer_done = reg->xfer_done; + event_reg->callback = reg->callback; + event_reg->mode = reg->mode; + event_reg->user = reg->user; + } + + return 0; +} + +/** + * Submit a transfer of a single buffer to a BAM pipe + * + */ +int sps_bam_pipe_transfer_one(struct sps_bam *dev, + u32 pipe_index, u32 addr, u32 size, + void *user, u32 flags) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + struct sps_iovec *desc; + struct sps_iovec iovec; + u32 next_write; + + /* Is this a BAM-to-BAM or satellite connection? */ + if ((pipe->state & (BAM_STATE_BAM2BAM | BAM_STATE_REMOTE))) { + SPS_ERR("Transfer on BAM-to-BAM: BAM 0x%x pipe %d", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + /* + * Client identifier (user pointer) is not supported for + * SPS_O_NO_Q option. + */ + if (pipe->sys.no_queue && user != NULL) { + SPS_ERR("User pointer arg non-NULL: BAM 0x%x pipe %d", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + /* Determine if descriptor can be queued */ + next_write = pipe->sys.desc_offset + sizeof(struct sps_iovec); + if (next_write >= pipe->desc_size) + next_write = 0; + + if (next_write == pipe->sys.acked_offset) { + /* + * If pipe is polled and client is not ACK'ing descriptors, + * perform polling operation so that any outstanding ACKs + * can occur. + */ + if (!pipe->sys.ack_xfers && pipe->polled) { + pipe_handler_eot(dev, pipe); + if (next_write == pipe->sys.acked_offset) { + SPS_DBG("Descriptor FIFO is full for " + "BAM 0x%x pipe %d", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + } else { + SPS_DBG("Descriptor FIFO is full for " + "BAM 0x%x pipe %d", BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + } + + /* Create descriptor */ + if (!pipe->sys.no_queue) + desc = (struct sps_iovec *) (pipe->sys.desc_cache + + pipe->sys.desc_offset); + else + desc = &iovec; + + desc->addr = addr; + desc->size = size; + if ((flags & SPS_IOVEC_FLAG_DEFAULT) == 0) { + desc->flags = flags & BAM_IOVEC_FLAG_MASK; + } else { + if (pipe->mode == SPS_MODE_SRC) + desc->flags = SPS_IOVEC_FLAG_INT; + else + desc->flags = SPS_IOVEC_FLAG_INT | SPS_IOVEC_FLAG_EOT; + } +#ifdef SPS_BAM_STATISTICS + if ((flags & SPS_IOVEC_FLAG_INT)) + pipe->sys.int_flags++; + if ((flags & SPS_IOVEC_FLAG_EOT)) + pipe->sys.eot_flags++; +#endif /* SPS_BAM_STATISTICS */ + + /* Update hardware descriptor FIFO - should result in burst */ + *((struct sps_iovec *) (pipe->sys.desc_buf + pipe->sys.desc_offset)) + = *desc; + + /* Record user pointer value */ + if (!pipe->sys.no_queue) { + u32 index = pipe->sys.desc_offset / sizeof(struct sps_iovec); + pipe->sys.user_ptrs[index] = user; +#ifdef SPS_BAM_STATISTICS + if (user != NULL) + pipe->sys.user_ptrs_count++; +#endif /* SPS_BAM_STATISTICS */ + } + + /* Update descriptor ACK offset */ + pipe->sys.desc_offset = next_write; + +#ifdef SPS_BAM_STATISTICS + /* Update statistics */ + pipe->sys.desc_wr_count++; +#endif /* SPS_BAM_STATISTICS */ + + /* Notify pipe */ + if ((flags & SPS_IOVEC_FLAG_NO_SUBMIT) == 0) { + wmb(); /* Memory Barrier */ + bam_pipe_set_desc_write_offset(dev->base, pipe_index, + next_write); + } + + return 0; +} + +/** + * Submit a transfer to a BAM pipe + * + */ +int sps_bam_pipe_transfer(struct sps_bam *dev, + u32 pipe_index, struct sps_transfer *transfer) +{ + struct sps_iovec *iovec; + u32 count; + u32 flags; + void *user; + int n; + int result; + + if (transfer->iovec_count == 0) { + SPS_ERR("iovec count zero: BAM 0x%x pipe %d", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + sps_bam_get_free_count(dev, pipe_index, &count); + if (count < transfer->iovec_count) { + SPS_ERR("Insufficient free desc: BAM 0x%x pipe %d: %d", + BAM_ID(dev), pipe_index, count); + return SPS_ERROR; + } + + user = NULL; /* NULL for all except last descriptor */ + for (n = (int)transfer->iovec_count - 1, iovec = transfer->iovec; + n >= 0; n--, iovec++) { + if (n > 0) { + /* This is *not* the last descriptor */ + flags = iovec->flags | SPS_IOVEC_FLAG_NO_SUBMIT; + } else { + /* This *is* the last descriptor */ + flags = iovec->flags; + user = transfer->user; + } + result = sps_bam_pipe_transfer_one(dev, pipe_index, + iovec->addr, + iovec->size, user, + flags); + if (result) + return SPS_ERROR; + } + + return 0; +} + +/** + * Allocate an event tracking struct + * + * This function allocates an event tracking struct. + * + * @pipe - pointer to pipe state + * + * @event_reg - pointer to event registration + * + * @return - pointer to event notification struct, or NULL + * + */ +static struct sps_q_event *alloc_event(struct sps_pipe *pipe, + struct sps_bam_event_reg *event_reg) +{ + struct sps_q_event *event; + + /* A callback event object is registered, so trigger with payload */ + event = &pipe->sys.event; + memset(event, 0, sizeof(*event)); + + return event; +} + +/** + * Trigger an event notification + * + * This function triggers an event notification. + * + * @dev - pointer to BAM device descriptor + * + * @pipe - pointer to pipe state + * + * @event_reg - pointer to event registration + * + * @sps_event - pointer to event struct + * + */ +static void trigger_event(struct sps_bam *dev, + struct sps_pipe *pipe, + struct sps_bam_event_reg *event_reg, + struct sps_q_event *sps_event) +{ + if (sps_event == NULL) { + SPS_DBG("sps:trigger_event.sps_event is NULL."); + return; + } + + if (event_reg->xfer_done) { + complete(event_reg->xfer_done); + SPS_DBG("sps:trigger_event.done=%d.", + event_reg->xfer_done->done); + } + + if (event_reg->callback) { + event_reg->callback(&sps_event->notify); + SPS_DBG("sps:trigger_event.using callback."); + } + +} + +/** + * Handle a BAM pipe's generic interrupt sources + * + * This function creates the event notification for a BAM pipe's + * generic interrupt sources. The caller of this function must lock the BAM + * device's mutex. + * + * @dev - pointer to BAM device descriptor + * + * @pipe - pointer to pipe state + * + * @event_id - event identifier enum + * + */ +static void pipe_handler_generic(struct sps_bam *dev, + struct sps_pipe *pipe, + enum sps_event event_id) +{ + struct sps_bam_event_reg *event_reg; + struct sps_q_event *sps_event; + int index; + + index = SPS_EVENT_INDEX(event_id); + if (index < 0 || index >= SPS_EVENT_INDEX(SPS_EVENT_MAX)) + return; + + event_reg = &pipe->sys.event_regs[index]; + sps_event = alloc_event(pipe, event_reg); + if (sps_event != NULL) { + sps_event->notify.event_id = event_id; + sps_event->notify.user = event_reg->user; + trigger_event(dev, pipe, event_reg, sps_event); + } +} + +/** + * Handle a BAM pipe's WAKEUP interrupt sources + * + * This function creates the event notification for a BAM pipe's + * WAKEUP interrupt source. The caller of this function must lock the BAM + * device's mutex. + * + * @dev - pointer to BAM device descriptor + * + * @pipe - pointer to pipe state + * + */ +static void pipe_handler_wakeup(struct sps_bam *dev, struct sps_pipe *pipe) +{ + struct sps_bam_event_reg *event_reg; + struct sps_q_event *event; + u32 pipe_index = pipe->pipe_index; + + if (pipe->wake_up_is_one_shot) { + /* Disable the pipe WAKEUP interrupt source */ + pipe->irq_mask &= ~BAM_PIPE_IRQ_WAKE; + pipe_set_irq(dev, pipe_index, pipe->polled); + } + + event_reg = &pipe->sys.event_regs[SPS_EVENT_INDEX(SPS_EVENT_WAKEUP)]; + event = alloc_event(pipe, event_reg); + if (event != NULL) { + event->notify.event_id = SPS_EVENT_WAKEUP; + event->notify.user = event_reg->user; + trigger_event(dev, pipe, event_reg, event); + } +} + +/** + * Handle a BAM pipe's EOT/INT interrupt sources + * + * This function creates the event notification for a BAM pipe's EOT interrupt + * source. The caller of this function must lock the BAM device's mutex. + * + * @dev - pointer to BAM device descriptor + * + * @pipe - pointer to pipe state + * + */ +static void pipe_handler_eot(struct sps_bam *dev, struct sps_pipe *pipe) +{ + struct sps_bam_event_reg *event_reg; + struct sps_q_event *event; + struct sps_iovec *desc; + struct sps_iovec *cache; + void **user; + u32 *update_offset; + u32 pipe_index = pipe->pipe_index; + u32 offset; + u32 end_offset; + enum sps_event event_id; + u32 flags; + u32 enabled; + int producer = (pipe->mode == SPS_MODE_SRC); + + if (pipe->sys.handler_eot) + /* + * This can happen if the pipe is configured for polling + * (IRQ disabled) and callback event generation. + * The client may perform a get_iovec() inside the callback. + */ + return; + + pipe->sys.handler_eot = true; + + /* Get offset of last descriptor completed by the pipe */ + end_offset = bam_pipe_get_desc_read_offset(dev->base, pipe_index); + + /* If no queue, then do not generate any events */ + if (pipe->sys.no_queue) { + if (!pipe->sys.ack_xfers) { + /* Client is not ACK'ing transfers, so do it now */ + pipe->sys.acked_offset = end_offset; + } + pipe->sys.handler_eot = false; + return; + } + + /* + * Get offset of last descriptor processed by software, + * and update to the last descriptor completed by the pipe + */ + if (!pipe->sys.ack_xfers) { + update_offset = &pipe->sys.acked_offset; + offset = *update_offset; + } else { + update_offset = &pipe->sys.cache_offset; + offset = *update_offset; + } + + /* Are there any completed descriptors to process? */ + if (offset == end_offset) { + pipe->sys.handler_eot = false; + return; + } + + /* Determine enabled events */ + enabled = 0; + if ((pipe->irq_mask & SPS_O_EOT)) + enabled |= SPS_IOVEC_FLAG_EOT; + + if ((pipe->irq_mask & SPS_O_DESC_DONE)) + enabled |= SPS_IOVEC_FLAG_INT; + + /* + * For producer pipe, update the cached descriptor byte count and flags. + * For consumer pipe, the BAM does not update the descriptors, so just + * use the cached copies. + */ + if (producer) { + /* + * Do copies in a tight loop to increase chance of + * multi-descriptor burst accesses on the bus + */ + struct sps_iovec *desc_end; + + /* Set starting point for copy */ + desc = (struct sps_iovec *) (pipe->sys.desc_buf + offset); + cache = (struct sps_iovec *) (pipe->sys.desc_cache + offset); + + /* Fetch all completed descriptors to end of FIFO (wrap) */ + if (end_offset < offset) { + desc_end = (struct sps_iovec *) + (pipe->sys.desc_buf + pipe->desc_size); + while (desc < desc_end) + *cache++ = *desc++; + + desc = (void *)pipe->sys.desc_buf; + cache = (void *)pipe->sys.desc_cache; + } + + /* Fetch all remaining completed descriptors (no wrap) */ + desc_end = (struct sps_iovec *) (pipe->sys.desc_buf + + end_offset); + while (desc < desc_end) + *cache++ = *desc++; + } + + /* Process all completed descriptors */ + cache = (struct sps_iovec *) (pipe->sys.desc_cache + offset); + user = &pipe->sys.user_ptrs[offset / sizeof(struct sps_iovec)]; + for (;;) { + /* + * Increment offset to next descriptor and update pipe offset + * so a client callback can fetch the I/O vector. + */ + offset += sizeof(struct sps_iovec); + if (offset >= pipe->desc_size) + /* Roll to start of descriptor FIFO */ + offset = 0; + + *update_offset = offset; +#ifdef SPS_BAM_STATISTICS + pipe->sys.desc_rd_count++; +#endif /* SPS_BAM_STATISTICS */ + + /* Did client request notification for this descriptor? */ + flags = cache->flags & enabled; + if (*user != NULL || flags) { + int index; + + if ((flags & SPS_IOVEC_FLAG_EOT)) + event_id = SPS_EVENT_EOT; + else + event_id = SPS_EVENT_DESC_DONE; + + index = SPS_EVENT_INDEX(event_id); + event_reg = &pipe->sys.event_regs[index]; + event = alloc_event(pipe, event_reg); + if (event != NULL) { + /* + * Store the descriptor and user pointer + * in the notification + */ + event->notify.data.transfer.iovec = *cache; + event->notify.data.transfer.user = *user; + + event->notify.event_id = event_id; + event->notify.user = event_reg->user; + trigger_event(dev, pipe, event_reg, event); + } +#ifdef SPS_BAM_STATISTICS + if (*user != NULL) + pipe->sys.user_found++; +#endif /* SPS_BAM_STATISTICS */ + } + + /* Increment to next descriptor */ + if (offset == end_offset) + break; /* No more descriptors */ + + if (offset) { + cache++; + user++; + } else { + cache = (void *)pipe->sys.desc_cache; + user = pipe->sys.user_ptrs; + } + } + + pipe->sys.handler_eot = false; +} + +/** + * Handle a BAM pipe's interrupt sources + * + * This function handles a BAM pipe's interrupt sources. + * The caller of this function must lock the BAM device's mutex. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @return void + * + */ +static void pipe_handler(struct sps_bam *dev, struct sps_pipe *pipe) +{ + u32 pipe_index; + u32 status; + enum sps_event event_id; + + /* Get interrupt sources and ack all */ + pipe_index = pipe->pipe_index; + status = bam_pipe_get_and_clear_irq_status(dev->base, pipe_index); + + SPS_DBG("sps:pipe_handler.bam 0x%x.pipe %d.status=0x%x.", + BAM_ID(dev), pipe_index, status); + + /* Check for enabled interrupt sources */ + status &= pipe->irq_mask; + if (status == 0) + /* No enabled interrupt sources are active */ + return; + + /* + * Process the interrupt sources in order of frequency of occurrance. + * Check for early exit opportunities. + */ + + if ((status & (SPS_O_EOT | SPS_O_DESC_DONE)) && + (pipe->state & BAM_STATE_BAM2BAM) == 0) { + pipe_handler_eot(dev, pipe); + if (pipe->sys.no_queue) { + /* + * EOT handler will not generate any event if there + * is no queue, + * so generate "empty" (no descriptor) event + */ + if ((status & SPS_O_EOT)) + event_id = SPS_EVENT_EOT; + else + event_id = SPS_EVENT_DESC_DONE; + + pipe_handler_generic(dev, pipe, event_id); + } + status &= ~(SPS_O_EOT | SPS_O_DESC_DONE); + if (status == 0) + return; + } + + if ((status & SPS_O_WAKEUP)) { + pipe_handler_wakeup(dev, pipe); + status &= ~SPS_O_WAKEUP; + if (status == 0) + return; + } + + if ((status & SPS_O_INACTIVE)) { + pipe_handler_generic(dev, pipe, SPS_EVENT_INACTIVE); + status &= ~SPS_O_INACTIVE; + if (status == 0) + return; + } + + if ((status & SPS_O_OUT_OF_DESC)) { + pipe_handler_generic(dev, pipe, + SPS_EVENT_OUT_OF_DESC); + status &= ~SPS_O_OUT_OF_DESC; + if (status == 0) + return; + } + + if ((status & SPS_EVENT_ERROR)) + pipe_handler_generic(dev, pipe, SPS_EVENT_ERROR); +} + +/** + * Get a BAM pipe event + * + */ +int sps_bam_pipe_get_event(struct sps_bam *dev, + u32 pipe_index, struct sps_event_notify *notify) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + struct sps_q_event *event_queue; + + if (pipe->sys.no_queue) { + SPS_ERR("Invalid connection for event: " + "BAM 0x%x pipe %d context 0x%x", + BAM_ID(dev), pipe_index, (u32) pipe); + notify->event_id = SPS_EVENT_INVALID; + return SPS_ERROR; + } + + /* If pipe is polled, perform polling operation */ + if (pipe->polled && (pipe->state & BAM_STATE_BAM2BAM) == 0) + pipe_handler_eot(dev, pipe); + + /* Pull an event off the synchronous event queue */ + if (list_empty(&pipe->sys.events_q)) { + event_queue = NULL; + SPS_DBG("sps:events_q of bam 0x%x is empty.", BAM_ID(dev)); + } else { + SPS_DBG("sps:events_q of bam 0x%x is not empty.", BAM_ID(dev)); + event_queue = + list_first_entry(&pipe->sys.events_q, struct sps_q_event, + list); + list_del(&event_queue->list); + } + + /* Update client's event buffer */ + if (event_queue == NULL) { + /* No event queued, so set client's event to "invalid" */ + notify->event_id = SPS_EVENT_INVALID; + } else { + /* + * Copy event into client's buffer and return the event + * to the pool + */ + *notify = event_queue->notify; + kfree(event_queue); +#ifdef SPS_BAM_STATISTICS + pipe->sys.get_events++; +#endif /* SPS_BAM_STATISTICS */ + } + + return 0; +} + +/** + * Get processed I/O vector + */ +int sps_bam_pipe_get_iovec(struct sps_bam *dev, u32 pipe_index, + struct sps_iovec *iovec) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + struct sps_iovec *desc; + u32 read_offset; + + /* Is this a valid pipe configured for get_iovec use? */ + if (!pipe->sys.ack_xfers || + (pipe->state & BAM_STATE_BAM2BAM) != 0 || + (pipe->state & BAM_STATE_REMOTE)) { + return SPS_ERROR; + } + + /* If pipe is polled and queue is enabled, perform polling operation */ + if (pipe->polled && !pipe->sys.no_queue) + pipe_handler_eot(dev, pipe); + + /* Is there a completed descriptor? */ + if (pipe->sys.no_queue) + read_offset = + bam_pipe_get_desc_read_offset(dev->base, pipe_index); + else + read_offset = pipe->sys.cache_offset; + + if (read_offset == pipe->sys.acked_offset) { + /* No, so clear the iovec to indicate FIFO is empty */ + memset(iovec, 0, sizeof(*iovec)); + return 0; + } + + /* Fetch next descriptor */ + desc = (struct sps_iovec *) (pipe->sys.desc_buf + + pipe->sys.acked_offset); + *iovec = *desc; +#ifdef SPS_BAM_STATISTICS + pipe->sys.get_iovecs++; +#endif /* SPS_BAM_STATISTICS */ + + /* Update read/ACK offset */ + pipe->sys.acked_offset += sizeof(struct sps_iovec); + if (pipe->sys.acked_offset >= pipe->desc_size) + pipe->sys.acked_offset = 0; + + return 0; +} + +/** + * Determine whether a BAM pipe descriptor FIFO is empty + * + */ +int sps_bam_pipe_is_empty(struct sps_bam *dev, u32 pipe_index, + u32 *empty) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + u32 end_offset; + u32 acked_offset; + + /* Is this a satellite connection? */ + if ((pipe->state & BAM_STATE_REMOTE)) { + SPS_ERR("Is empty on remote: BAM 0x%x pipe %d", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + /* Get offset of last descriptor completed by the pipe */ + end_offset = bam_pipe_get_desc_read_offset(dev->base, pipe_index); + + if ((pipe->state & BAM_STATE_BAM2BAM) == 0) + /* System mode */ + acked_offset = pipe->sys.acked_offset; + else + /* BAM-to-BAM */ + acked_offset = bam_pipe_get_desc_write_offset(dev->base, + pipe_index); + + + /* Determine descriptor FIFO state */ + if (end_offset == acked_offset) + *empty = true; + else + *empty = false; + + return 0; +} + +/** + * Get number of free slots in a BAM pipe descriptor FIFO + * + */ +int sps_bam_get_free_count(struct sps_bam *dev, u32 pipe_index, + u32 *count) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + u32 next_write; + u32 free; + + /* Is this a BAM-to-BAM or satellite connection? */ + if ((pipe->state & (BAM_STATE_BAM2BAM | BAM_STATE_REMOTE))) { + SPS_ERR("Free count on BAM-to-BAM or remote: BAM 0x%x pipe %d", + BAM_ID(dev), pipe_index); + *count = 0; + return SPS_ERROR; + } + + /* Determine descriptor FIFO state */ + next_write = pipe->sys.desc_offset + sizeof(struct sps_iovec); + if (next_write >= pipe->desc_size) + next_write = 0; + + if (pipe->sys.acked_offset >= next_write) + free = pipe->sys.acked_offset - next_write; + else + free = pipe->desc_size - next_write + pipe->sys.acked_offset; + + free /= sizeof(struct sps_iovec); + *count = free; + + return 0; +} + +/** + * Set BAM pipe to satellite ownership + * + */ +int sps_bam_set_satellite(struct sps_bam *dev, u32 pipe_index) +{ + struct sps_pipe *pipe = dev->pipes[pipe_index]; + + /* + * Switch to satellite control is only supported on processor + * that controls the BAM global config on multi-EE BAMs + */ + if ((dev->props.manage & SPS_BAM_MGR_MULTI_EE) == 0 || + (dev->props.manage & SPS_BAM_MGR_DEVICE_REMOTE)) { + SPS_ERR("Cannot grant satellite control to BAM 0x%x pipe %d", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + /* Is this pipe locally controlled? */ + if ((dev->pipe_active_mask & (1UL << pipe_index)) == 0) { + SPS_ERR("BAM 0x%x pipe %d not local and active", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + /* Disable local interrupts for this pipe */ + if (!pipe->polled) + bam_pipe_set_irq(dev->base, pipe_index, BAM_DISABLE, + pipe->irq_mask, dev->props.ee); + + if (BAM_VERSION_MTI_SUPPORT(dev->version)) { + /* + * Set pipe to MTI interrupt mode. + * Must be performed after IRQ disable, + * because it is necessary to re-enable the IRQ to enable + * MTI generation. + * Set both pipe IRQ mask and MTI dest address to zero. + */ + if ((pipe->state & BAM_STATE_MTI) == 0 || pipe->polled) { + bam_pipe_satellite_mti(dev->base, pipe_index, 0, + dev->props.ee); + pipe->state |= BAM_STATE_MTI; + } + } + + /* Indicate satellite control */ + list_del(&pipe->list); + dev->pipe_active_mask &= ~(1UL << pipe_index); + dev->pipe_remote_mask |= pipe->pipe_index_mask; + pipe->state |= BAM_STATE_REMOTE; + + return 0; +} + +/** + * Perform BAM pipe timer control + * + */ +int sps_bam_pipe_timer_ctrl(struct sps_bam *dev, + u32 pipe_index, + struct sps_timer_ctrl *timer_ctrl, + struct sps_timer_result *timer_result) +{ + enum bam_pipe_timer_mode mode; + int result = 0; + + /* Is this pipe locally controlled? */ + if ((dev->pipe_active_mask & (1UL << pipe_index)) == 0) { + SPS_ERR("BAM 0x%x pipe %d not local and active", + BAM_ID(dev), pipe_index); + return SPS_ERROR; + } + + /* Perform the timer operation */ + switch (timer_ctrl->op) { + case SPS_TIMER_OP_CONFIG: + mode = (timer_ctrl->mode == SPS_TIMER_MODE_ONESHOT) ? + BAM_PIPE_TIMER_ONESHOT : + BAM_PIPE_TIMER_PERIODIC; + bam_pipe_timer_config(dev->base, pipe_index, mode, + timer_ctrl->timeout_msec * 10); + break; + case SPS_TIMER_OP_RESET: + bam_pipe_timer_reset(dev->base, pipe_index); + break; + case SPS_TIMER_OP_READ: + break; + default: + result = SPS_ERROR; + break; + } + + /* Provide the current timer value */ + if (timer_result != NULL) + timer_result->current_timer = + bam_pipe_timer_get_count(dev->base, pipe_index); + + return result; +} + diff --git a/drivers/platform/msm/sps/sps_bam.h b/drivers/platform/msm/sps/sps_bam.h new file mode 100644 index 00000000000..f09948e092a --- /dev/null +++ b/drivers/platform/msm/sps/sps_bam.h @@ -0,0 +1,547 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Function and data structure declarations for SPS BAM handling. + */ + + +#ifndef _SPSBAM_H_ +#define _SPSBAM_H_ + +#include +#include +#include +#include +#include + +#include "spsi.h" + +#define BAM_MAX_PIPES 31 +#define BAM_HANDLE_INVALID 0 + +enum bam_irq { + BAM_DEV_IRQ_RDY_TO_SLEEP = 0x00000001, + BAM_DEV_IRQ_HRESP_ERROR = 0x00000002, + BAM_DEV_IRQ_ERROR = 0x00000004, +}; + +/* Pipe interrupt mask */ +enum bam_pipe_irq { + /* BAM finishes descriptor which has INT bit selected */ + BAM_PIPE_IRQ_DESC_INT = 0x00000001, + /* Inactivity timer Expires */ + BAM_PIPE_IRQ_TIMER = 0x00000002, + /* Wakeup peripheral (i.e. USB) */ + BAM_PIPE_IRQ_WAKE = 0x00000004, + /* Producer - no free space for adding a descriptor */ + /* Consumer - no descriptors for processing */ + BAM_PIPE_IRQ_OUT_OF_DESC = 0x00000008, + /* Pipe Error interrupt */ + BAM_PIPE_IRQ_ERROR = 0x00000010, + /* End-Of-Transfer */ + BAM_PIPE_IRQ_EOT = 0x00000020, +}; + +/* Halt Type */ +enum bam_halt { + BAM_HALT_OFF = 0, + BAM_HALT_ON = 1, +}; + +/* Threshold values of the DMA channels */ +enum bam_dma_thresh_dma { + BAM_DMA_THRESH_512 = 0x3, + BAM_DMA_THRESH_256 = 0x2, + BAM_DMA_THRESH_128 = 0x1, + BAM_DMA_THRESH_64 = 0x0, +}; + +/* Weight values of the DMA channels */ +enum bam_dma_weight_dma { + BAM_DMA_WEIGHT_HIGH = 7, + BAM_DMA_WEIGHT_MED = 3, + BAM_DMA_WEIGHT_LOW = 1, + BAM_DMA_WEIGHT_DEFAULT = BAM_DMA_WEIGHT_LOW, + BAM_DMA_WEIGHT_DISABLE = 0, +}; + + +/* Invalid pipe index value */ +#define SPS_BAM_PIPE_INVALID ((u32)(-1)) + +/* Parameters for sps_bam_pipe_connect() */ +struct sps_bam_connect_param { + /* which end point must be initialized */ + enum sps_mode mode; + + /* OR'd connection end point options (see SPS_O defines) */ + u32 options; + + /* SETPEND/MTI interrupt generation parameters */ + u32 irq_gen_addr; + u32 irq_gen_data; + +}; + +/* Event registration struct */ +struct sps_bam_event_reg { + /* Client's event object handle */ + struct completion *xfer_done; + void (*callback)(struct sps_event_notify *notify); + + /* Event trigger mode */ + enum sps_trigger mode; + + /* User pointer that will be provided in event payload data */ + void *user; + +}; + +/* Descriptor FIFO cache entry */ +struct sps_bam_desc_cache { + struct sps_iovec iovec; + void *user; /* User pointer registered with this transfer */ +}; + +/* Forward declaration */ +struct sps_bam; + +/* System mode control */ +struct sps_bam_sys_mode { + /* Descriptor FIFO control */ + u8 *desc_buf; /* Descriptor FIFO for BAM pipe */ + u32 desc_offset; /* Next new descriptor to be written to hardware */ + u32 acked_offset; /* Next descriptor to be retired by software */ + + /* Descriptor cache control (!no_queue only) */ + u8 *desc_cache; /* Software cache of descriptor FIFO contents */ + u32 cache_offset; /* Next descriptor to be cached (ack_xfers only) */ + + /* User pointers associated with cached descriptors */ + void **user_ptrs; + + /* Event handling */ + struct sps_bam_event_reg event_regs[SPS_EVENT_INDEX(SPS_EVENT_MAX)]; + struct list_head events_q; + + struct sps_q_event event; /* Temp storage for event creation */ + int no_queue; /* Whether events are queued */ + int ack_xfers; /* Whether client must ACK all descriptors */ + int handler_eot; /* Whether EOT handling is in progress (debug) */ + + /* Statistics */ +#ifdef SPS_BAM_STATISTICS + u32 desc_wr_count; + u32 desc_rd_count; + u32 user_ptrs_count; + u32 user_found; + u32 int_flags; + u32 eot_flags; + u32 callback_events; + u32 wait_events; + u32 queued_events; + u32 get_events; + u32 get_iovecs; +#endif /* SPS_BAM_STATISTICS */ +}; + +/* BAM pipe descriptor */ +struct sps_pipe { + struct list_head list; + + /* Client state */ + u32 client_state; + struct sps_bam *bam; + struct sps_connect connect; + const struct sps_connection *map; + + /* Pipe parameters */ + u32 state; + u32 pipe_index; + u32 pipe_index_mask; + u32 irq_mask; + int polled; + u32 irq_gen_addr; + enum sps_mode mode; + u32 num_descs; /* Size (number of elements) of descriptor FIFO */ + u32 desc_size; /* Size (bytes) of descriptor FIFO */ + int wake_up_is_one_shot; /* Whether WAKEUP event is a one-shot or not */ + + /* System mode control */ + struct sps_bam_sys_mode sys; + +}; + +/* BAM device descriptor */ +struct sps_bam { + struct list_head list; + + /* BAM device properties, including connection defaults */ + struct sps_bam_props props; + + /* BAM device state */ + u32 state; + struct mutex lock; + void *base; /* BAM virtual base address */ + u32 version; + spinlock_t isr_lock; + + /* Pipe state */ + u32 pipe_active_mask; + u32 pipe_remote_mask; + struct sps_pipe *pipes[BAM_MAX_PIPES]; + struct list_head pipes_q; + + /* Statistics */ + u32 irq_from_disabled_pipe; + u32 event_trigger_failures; + +}; + +/** + * BAM driver initialization + * + * This function initializes the BAM driver. + * + * @options - driver options bitflags (see SPS_OPT_*) + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_driver_init(u32 options); + +/** + * BAM device initialization + * + * This function initializes a BAM device. + * + * @dev - pointer to BAM device descriptor + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_device_init(struct sps_bam *dev); + +/** + * BAM device de-initialization + * + * This function de-initializes a BAM device. + * + * @dev - pointer to BAM device descriptor + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_device_de_init(struct sps_bam *dev); + +/** + * BAM device reset + * + * This Function resets a BAM device. + * + * @dev - pointer to BAM device descriptor + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_reset(struct sps_bam *dev); + +/** + * BAM device enable + * + * This function enables a BAM device. + * + * @dev - pointer to BAM device descriptor + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_enable(struct sps_bam *dev); + +/** + * BAM device disable + * + * This Function disables a BAM device. + * + * @dev - pointer to BAM device descriptor + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_disable(struct sps_bam *dev); + +/** + * Allocate a BAM pipe + * + * This function allocates a BAM pipe. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - client-specified pipe index, or SPS_BAM_PIPE_INVALID if + * any available pipe is acceptable + * + * @return - allocated pipe index, or SPS_BAM_PIPE_INVALID on error + * + */ +u32 sps_bam_pipe_alloc(struct sps_bam *dev, u32 pipe_index); + +/** + * Free a BAM pipe + * + * This function frees a BAM pipe. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + */ +void sps_bam_pipe_free(struct sps_bam *dev, u32 pipe_index); + +/** + * Establish BAM pipe connection + * + * This function establishes a connection for a BAM pipe (end point). + * + * @client - pointer to client pipe state struct + * + * @params - connection parameters + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_connect(struct sps_pipe *client, + const struct sps_bam_connect_param *params); + +/** + * Disconnect a BAM pipe connection + * + * This function disconnects a connection for a BAM pipe (end point). + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_disconnect(struct sps_bam *dev, u32 pipe_index); + +/** + * Set BAM pipe parameters + * + * This function sets parameters for a BAM pipe. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @options - bitflag options (see SPS_O_*) + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_set_params(struct sps_bam *dev, u32 pipe_index, u32 options); + +/** + * Enable a BAM pipe + * + * This function enables a BAM pipe. Note that this function + * is separate from the pipe connect function to allow proper + * sequencing of consumer enable followed by producer enable. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_enable(struct sps_bam *dev, u32 pipe_index); + +/** + * Disable a BAM pipe + * + * This function disables a BAM pipe. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_disable(struct sps_bam *dev, u32 pipe_index); + +/** + * Register an event for a BAM pipe + * + * This function registers an event for a BAM pipe. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @reg - pointer to event registration struct + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_reg_event(struct sps_bam *dev, u32 pipe_index, + struct sps_register_event *reg); + +/** + * Submit a transfer of a single buffer to a BAM pipe + * + * This function submits a transfer of a single buffer to a BAM pipe. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @addr - physical address of buffer to transfer + * + * @size - number of bytes to transfer + * + * @user - user pointer to register for event + * + * @flags - descriptor flags (see SPS_IOVEC_FLAG defines) + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_transfer_one(struct sps_bam *dev, u32 pipe_index, u32 addr, + u32 size, void *user, u32 flags); + +/** + * Submit a transfer to a BAM pipe + * + * This function submits a transfer to a BAM pipe. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @transfer - pointer to transfer struct + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_transfer(struct sps_bam *dev, u32 pipe_index, + struct sps_transfer *transfer); + +/** + * Get a BAM pipe event + * + * This function polls for a BAM pipe event. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @notify - pointer to event notification struct + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_get_event(struct sps_bam *dev, u32 pipe_index, + struct sps_event_notify *notify); + +/** + * Get processed I/O vector + * + * This function fetches the next processed I/O vector. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @iovec - Pointer to I/O vector struct (output). + * This struct will be zeroed if there are no more processed I/O vectors. + * + * @return 0 on success, negative value on error + */ +int sps_bam_pipe_get_iovec(struct sps_bam *dev, u32 pipe_index, + struct sps_iovec *iovec); + +/** + * Determine whether a BAM pipe descriptor FIFO is empty + * + * This function returns the empty state of a BAM pipe descriptor FIFO. + * + * The pipe mutex must be locked before calling this function. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @empty - pointer to client's empty status word (boolean) + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_is_empty(struct sps_bam *dev, u32 pipe_index, u32 *empty); + +/** + * Get number of free slots in a BAM pipe descriptor FIFO + * + * This function returns the number of free slots in a BAM pipe descriptor FIFO. + * + * The pipe mutex must be locked before calling this function. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @count - pointer to count status + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_get_free_count(struct sps_bam *dev, u32 pipe_index, u32 *count); + +/** + * Set BAM pipe to satellite ownership + * + * This function sets the BAM pipe to satellite ownership. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_set_satellite(struct sps_bam *dev, u32 pipe_index); + +/** + * Perform BAM pipe timer control + * + * This function performs BAM pipe timer control operations. + * + * @dev - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @timer_ctrl - Pointer to timer control specification + * + * @timer_result - Pointer to buffer for timer operation result. + * This argument can be NULL if no result is expected for the operation. + * If non-NULL, the current timer value will always provided. + * + * @return 0 on success, negative value on error + * + */ +int sps_bam_pipe_timer_ctrl(struct sps_bam *dev, u32 pipe_index, + struct sps_timer_ctrl *timer_ctrl, + struct sps_timer_result *timer_result); +#endif /* _SPSBAM_H_ */ diff --git a/drivers/platform/msm/sps/sps_core.h b/drivers/platform/msm/sps/sps_core.h new file mode 100644 index 00000000000..5bd7c655f27 --- /dev/null +++ b/drivers/platform/msm/sps/sps_core.h @@ -0,0 +1,107 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Function and data structure declarations. + */ + +#ifndef _SPS_CORE_H_ +#define _SPS_CORE_H_ + +#include /* u32 */ +#include /* mutex */ +#include /* list_head */ + +#include "spsi.h" +#include "sps_bam.h" + +/* Connection state definitions */ +#define SPS_STATE_DEF(x) ('S' | ('P' << 8) | ('S' << 16) | ((x) << 24)) +#define IS_SPS_STATE_OK(x) \ + (((x)->client_state & 0x00ffffff) == SPS_STATE_DEF(0)) + +/* Configuration indicating satellite connection */ +#define SPS_CONFIG_SATELLITE 0x11111111 + +/* Client connection state */ +#define SPS_STATE_DISCONNECT 0 +#define SPS_STATE_ALLOCATE SPS_STATE_DEF(1) +#define SPS_STATE_CONNECT SPS_STATE_DEF(2) +#define SPS_STATE_ENABLE SPS_STATE_DEF(3) +#define SPS_STATE_DISABLE SPS_STATE_DEF(4) + +/* Connection mapping control struct */ +struct sps_rm { + struct list_head connections_q; + struct mutex lock; +}; + +/** + * Find the BAM device from the handle + * + * This function finds a BAM device in the BAM registration list that + * matches the specified device handle. + * + * @h - device handle of the BAM + * + * @return - pointer to the BAM device struct, or NULL on error + * + */ +struct sps_bam *sps_h2bam(u32 h); + +/** + * Initialize resource manager module + * + * This function initializes the resource manager module. + * + * @rm - pointer to resource manager struct + * + * @options - driver options bitflags (see SPS_OPT_*) + * + * @return 0 on success, negative value on error + * + */ +int sps_rm_init(struct sps_rm *rm, u32 options); + +/** + * De-initialize resource manager module + * + * This function de-initializes the resource manager module. + * + */ +void sps_rm_de_init(void); + +/** + * Initialize client state context + * + * This function initializes a client state context struct. + * + * @connect - pointer to client connection state struct + * + */ +void sps_rm_config_init(struct sps_connect *connect); + +/** + * Process connection state change + * + * This function processes a connection state change. + * + * @pipe - pointer to pipe context + * + * @state - new state for connection + * + * @return 0 on success, negative value on error + * + */ +int sps_rm_state_change(struct sps_pipe *pipe, u32 state); + +#endif /* _SPS_CORE_H_ */ diff --git a/drivers/platform/msm/sps/sps_dma.c b/drivers/platform/msm/sps/sps_dma.c new file mode 100644 index 00000000000..9f424033905 --- /dev/null +++ b/drivers/platform/msm/sps/sps_dma.c @@ -0,0 +1,896 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* BAM-DMA Manager. */ + +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + +#include /* memset */ + +#include "spsi.h" +#include "bam.h" +#include "sps_bam.h" /* bam_dma_thresh_dma */ +#include "sps_core.h" /* sps_h2bam() */ + +/** + * registers + */ + +#define DMA_ENBL (0x00000000) +#define DMA_CHNL_CONFIG(n) (0x00000004 + 4 * (n)) +#define DMA_CONFIG (0x00000040) + +/** + * masks + */ + +/* DMA_CHNL_confign */ +#define DMA_CHNL_HALT_DONE 0x10000 +#define DMA_CHNL_HALT 0x1000 +#define DMA_CHNL_ENABLE 0x100 +#define DMA_CHNL_ACT_THRESH 0x30 +#define DMA_CHNL_WEIGHT 0x7 + +/* DMA_CONFIG */ +#define TESTBUS_SELECT 0x3 + +/** + * + * Write register with debug info. + * + * @base - bam base virtual address. + * @offset - register offset. + * @val - value to write. + * + */ +static inline void dma_write_reg(void *base, u32 offset, u32 val) +{ + iowrite32(val, base + offset); + SPS_DBG("bamdma: write reg 0x%x w_val 0x%x.", offset, val); +} + +/** + * Write register masked field with debug info. + * + * @base - bam base virtual address. + * @offset - register offset. + * @mask - register bitmask. + * @val - value to write. + * + */ +static inline void dma_write_reg_field(void *base, u32 offset, + const u32 mask, u32 val) +{ + u32 shift = find_first_bit((void *)&mask, 32); + u32 tmp = ioread32(base + offset); + + tmp &= ~mask; /* clear written bits */ + val = tmp | (val << shift); + iowrite32(val, base + offset); + SPS_DBG("bamdma: write reg 0x%x w_val 0x%x.", offset, val); +} + +/* Round max number of pipes to nearest multiple of 2 */ +#define DMA_MAX_PIPES ((BAM_MAX_PIPES / 2) * 2) + +/* Maximum number of BAM-DMAs supported */ +#define MAX_BAM_DMA_DEVICES 1 + +/* Maximum number of BAMs that will be registered */ +#define MAX_BAM_DMA_BAMS 1 + +/* Pipe enable check values */ +#define DMA_PIPES_STATE_DIFF 0 +#define DMA_PIPES_BOTH_DISABLED 1 +#define DMA_PIPES_BOTH_ENABLED 2 + +/* Even pipe is tx/dest/input/write, odd pipe is rx/src/output/read */ +#define DMA_PIPE_IS_DEST(p) (((p) & 1) == 0) +#define DMA_PIPE_IS_SRC(p) (((p) & 1) != 0) + +/* BAM DMA pipe state */ +enum bamdma_pipe_state { + PIPE_INACTIVE = 0, + PIPE_ACTIVE +}; + +/* BAM DMA channel state */ +enum bamdma_chan_state { + DMA_CHAN_STATE_FREE = 0, + DMA_CHAN_STATE_ALLOC_EXT, /* Client allocation */ + DMA_CHAN_STATE_ALLOC_INT /* Internal (resource mgr) allocation */ +}; + +struct bamdma_chan { + /* Allocation state */ + enum bamdma_chan_state state; + + /* BAM DMA channel configuration parameters */ + u32 threshold; + enum sps_dma_priority priority; + + /* HWIO channel configuration parameters */ + enum bam_dma_thresh_dma thresh; + enum bam_dma_weight_dma weight; + +}; + +/* BAM DMA device state */ +struct bamdma_device { + /* BAM-DMA device state */ + int enabled; + int local; + + /* BAM device state */ + struct sps_bam *bam; + + /* BAM handle, for deregistration */ + u32 h; + + /* BAM DMA device virtual mapping */ + void *virt_addr; + int virtual_mapped; + u32 phys_addr; + void *hwio; + + /* BAM DMA pipe/channel state */ + u32 num_pipes; + enum bamdma_pipe_state pipes[DMA_MAX_PIPES]; + struct bamdma_chan chans[DMA_MAX_PIPES / 2]; + +}; + +/* BAM-DMA devices */ +static struct bamdma_device bam_dma_dev[MAX_BAM_DMA_DEVICES]; +static struct mutex bam_dma_lock; + +/* + * The BAM DMA module registers all BAMs in the BSP properties, but only + * uses the first BAM-DMA device for allocations. References to the others + * are stored in the following data array. + */ +static int num_bams; +static u32 bam_handles[MAX_BAM_DMA_BAMS]; + +/** + * Find BAM-DMA device + * + * This function finds the BAM-DMA device associated with the BAM handle. + * + * @h - BAM handle + * + * @return - pointer to BAM-DMA device, or NULL on error + * + */ +static struct bamdma_device *sps_dma_find_device(u32 h) +{ + return &bam_dma_dev[0]; +} + +/** + * BAM DMA device enable + * + * This function enables a BAM DMA device and the associated BAM. + * + * @dev - pointer to BAM DMA device context + * + * @return 0 on success, negative value on error + * + */ +static int sps_dma_device_enable(struct bamdma_device *dev) +{ + if (dev->enabled) + return 0; + + /* + * If the BAM-DMA device is locally controlled then enable BAM-DMA + * device + */ + if (dev->local) + dma_write_reg(dev->virt_addr, DMA_ENBL, 1); + + /* Enable BAM device */ + if (sps_bam_enable(dev->bam)) { + SPS_ERR("Failed to enable BAM DMA's BAM: %x", dev->phys_addr); + return SPS_ERROR; + } + + dev->enabled = true; + + return 0; +} + +/** + * BAM DMA device enable + * + * This function initializes a BAM DMA device. + * + * @dev - pointer to BAM DMA device context + * + * @return 0 on success, negative value on error + * + */ +static int sps_dma_device_disable(struct bamdma_device *dev) +{ + u32 pipe_index; + + if (!dev->enabled) + return 0; + + /* Do not disable if channels active */ + for (pipe_index = 0; pipe_index < dev->num_pipes; pipe_index++) { + if (dev->pipes[pipe_index] != PIPE_INACTIVE) + break; + } + + if (pipe_index < dev->num_pipes) { + SPS_ERR("Failed to disable BAM-DMA %x: channels are active", + dev->phys_addr); + return SPS_ERROR; + } + + dev->enabled = false; + + /* Disable BAM device */ + if (sps_bam_disable(dev->bam)) { + SPS_ERR("Failed to disable BAM-DMA %x BAM", dev->phys_addr); + return SPS_ERROR; + } + + /* Is the BAM-DMA device locally controlled? */ + if (dev->local) + /* Disable BAM-DMA device */ + dma_write_reg(dev->virt_addr, DMA_ENBL, 0); + + return 0; +} + +/** + * Initialize BAM DMA device + * + */ +int sps_dma_device_init(u32 h) +{ + struct bamdma_device *dev; + struct sps_bam_props *props; + u32 chan; + int result = SPS_ERROR; + + mutex_lock(&bam_dma_lock); + + /* Find a free BAM-DMA device slot */ + dev = NULL; + if (bam_dma_dev[0].bam != NULL) { + SPS_ERR("BAM-DMA BAM device already initialized."); + goto exit_err; + } else { + dev = &bam_dma_dev[0]; + } + + /* Record BAM */ + memset(dev, 0, sizeof(*dev)); + dev->h = h; + dev->bam = sps_h2bam(h); + + /* Map the BAM DMA device into virtual space, if necessary */ + props = &dev->bam->props; + dev->phys_addr = props->periph_phys_addr; + if (props->periph_virt_addr != NULL) { + dev->virt_addr = props->periph_virt_addr; + dev->virtual_mapped = false; + } else { + if (props->periph_virt_size == 0) { + SPS_ERR("Unable to map BAM DMA IO memory: %x %x", + dev->phys_addr, props->periph_virt_size); + goto exit_err; + } + + dev->virt_addr = ioremap(dev->phys_addr, + props->periph_virt_size); + if (dev->virt_addr == NULL) { + SPS_ERR("Unable to map BAM DMA IO memory: %x %x", + dev->phys_addr, props->periph_virt_size); + goto exit_err; + } + dev->virtual_mapped = true; + } + dev->hwio = (void *) dev->virt_addr; + + /* Is the BAM-DMA device locally controlled? */ + if ((props->manage & SPS_BAM_MGR_DEVICE_REMOTE) == 0) { + SPS_DBG("BAM-DMA is controlled locally: %x", + dev->phys_addr); + dev->local = true; + } else { + SPS_DBG("BAM-DMA is controlled remotely: %x", + dev->phys_addr); + dev->local = false; + } + + /* + * Enable the BAM DMA and determine the number of pipes/channels. + * Leave the BAM-DMA enabled, since it is always a shared device. + */ + if (sps_dma_device_enable(dev)) + goto exit_err; + + dev->num_pipes = dev->bam->props.num_pipes; + + /* Disable all channels */ + if (dev->local) + for (chan = 0; chan < (dev->num_pipes / 2); chan++) { + dma_write_reg_field(dev->virt_addr, + DMA_CHNL_CONFIG(chan), + DMA_CHNL_ENABLE, 0); + } + + result = 0; +exit_err: + if (result) { + if (dev != NULL) { + if (dev->virtual_mapped) + iounmap(dev->virt_addr); + + dev->bam = NULL; + } + } + + mutex_unlock(&bam_dma_lock); + + return result; +} + +/** + * De-initialize BAM DMA device + * + */ +int sps_dma_device_de_init(u32 h) +{ + struct bamdma_device *dev; + u32 pipe_index; + u32 chan; + int result = 0; + + mutex_lock(&bam_dma_lock); + + dev = sps_dma_find_device(h); + if (dev == NULL) { + SPS_ERR("BAM-DMA: not registered: %x", h); + result = SPS_ERROR; + goto exit_err; + } + + /* Check for channel leaks */ + for (chan = 0; chan < dev->num_pipes / 2; chan++) { + if (dev->chans[chan].state != DMA_CHAN_STATE_FREE) { + SPS_ERR("BAM-DMA: channel not free: %d", chan); + result = SPS_ERROR; + dev->chans[chan].state = DMA_CHAN_STATE_FREE; + } + } + for (pipe_index = 0; pipe_index < dev->num_pipes; pipe_index++) { + if (dev->pipes[pipe_index] != PIPE_INACTIVE) { + SPS_ERR("BAM-DMA: pipe not inactive: %d", pipe_index); + result = SPS_ERROR; + dev->pipes[pipe_index] = PIPE_INACTIVE; + } + } + + /* Disable BAM and BAM-DMA */ + if (sps_dma_device_disable(dev)) + result = SPS_ERROR; + + dev->h = BAM_HANDLE_INVALID; + dev->bam = NULL; + if (dev->virtual_mapped) + iounmap(dev->virt_addr); + +exit_err: + mutex_unlock(&bam_dma_lock); + + return result; +} + +/** + * Initialize BAM DMA module + * + */ +int sps_dma_init(const struct sps_bam_props *bam_props) +{ + struct sps_bam_props props; + const struct sps_bam_props *bam_reg; + u32 h; + + /* Init local data */ + memset(&bam_dma_dev, 0, sizeof(bam_dma_dev)); + num_bams = 0; + memset(bam_handles, 0, sizeof(bam_handles)); + + /* Create a mutex to control access to the BAM-DMA devices */ + mutex_init(&bam_dma_lock); + + /* Are there any BAM DMA devices? */ + if (bam_props == NULL) + return 0; + + /* + * Registers all BAMs in the BSP properties, but only uses the first + * BAM-DMA device for allocations. + */ + if (bam_props->phys_addr) { + /* Force multi-EE option for all BAM-DMAs */ + bam_reg = bam_props; + if ((bam_props->options & SPS_BAM_OPT_BAMDMA) && + (bam_props->manage & SPS_BAM_MGR_MULTI_EE) == 0) { + SPS_DBG("Setting multi-EE options for BAM-DMA: %x", + bam_props->phys_addr); + props = *bam_props; + props.manage |= SPS_BAM_MGR_MULTI_EE; + bam_reg = &props; + } + + /* Register the BAM */ + if (sps_register_bam_device(bam_reg, &h)) { + SPS_ERR("Failed to register BAM-DMA BAM device: " + "phys 0x%0x", bam_props->phys_addr); + return SPS_ERROR; + } + + /* Record the BAM so that it may be deregistered later */ + if (num_bams < MAX_BAM_DMA_BAMS) { + bam_handles[num_bams] = h; + num_bams++; + } else { + SPS_ERR("BAM-DMA: BAM limit exceeded: %d", num_bams); + return SPS_ERROR; + } + } else { + SPS_ERR("BAM-DMA phys_addr is zero."); + return SPS_ERROR; + } + + + return 0; +} + +/** + * De-initialize BAM DMA module + * + */ +void sps_dma_de_init(void) +{ + int n; + + /* De-initialize the BAM devices */ + for (n = 0; n < num_bams; n++) + sps_deregister_bam_device(bam_handles[n]); + + /* Clear local data */ + memset(&bam_dma_dev, 0, sizeof(bam_dma_dev)); + num_bams = 0; + memset(bam_handles, 0, sizeof(bam_handles)); +} + +/** + * Allocate a BAM DMA channel + * + */ +int sps_alloc_dma_chan(const struct sps_alloc_dma_chan *alloc, + struct sps_dma_chan *chan_info) +{ + struct bamdma_device *dev; + struct bamdma_chan *chan; + u32 pipe_index; + enum bam_dma_thresh_dma thresh = (enum bam_dma_thresh_dma) 0; + enum bam_dma_weight_dma weight = (enum bam_dma_weight_dma) 0; + int result = SPS_ERROR; + + if (alloc == NULL || chan_info == NULL) { + SPS_ERR("sps_alloc_dma_chan. invalid parameters"); + return SPS_ERROR; + } + + /* Translate threshold and priority to hwio values */ + if (alloc->threshold != SPS_DMA_THRESHOLD_DEFAULT) { + if (alloc->threshold >= 512) + thresh = BAM_DMA_THRESH_512; + else if (alloc->threshold >= 256) + thresh = BAM_DMA_THRESH_256; + else if (alloc->threshold >= 128) + thresh = BAM_DMA_THRESH_128; + else + thresh = BAM_DMA_THRESH_64; + } + + weight = alloc->priority; + + if ((u32)alloc->priority > (u32)BAM_DMA_WEIGHT_HIGH) { + SPS_ERR("BAM-DMA: invalid priority: %x", alloc->priority); + return SPS_ERROR; + } + + mutex_lock(&bam_dma_lock); + + dev = sps_dma_find_device(alloc->dev); + if (dev == NULL) { + SPS_ERR("BAM-DMA: invalid BAM handle: %x", alloc->dev); + goto exit_err; + } + + /* Search for a free set of pipes */ + for (pipe_index = 0, chan = dev->chans; + pipe_index < dev->num_pipes; pipe_index += 2, chan++) { + if (chan->state == DMA_CHAN_STATE_FREE) { + /* Just check pipes for safety */ + if (dev->pipes[pipe_index] != PIPE_INACTIVE || + dev->pipes[pipe_index + 1] != PIPE_INACTIVE) { + SPS_ERR("BAM-DMA: channel %d state error:%d %d", + pipe_index / 2, dev->pipes[pipe_index], + dev->pipes[pipe_index + 1]); + goto exit_err; + } + break; /* Found free pipe */ + } + } + + if (pipe_index >= dev->num_pipes) { + SPS_ERR("BAM-DMA: no free channel. num_pipes = %d", + dev->num_pipes); + goto exit_err; + } + + chan->state = DMA_CHAN_STATE_ALLOC_EXT; + + /* Store config values for use when pipes are activated */ + chan = &dev->chans[pipe_index / 2]; + chan->threshold = alloc->threshold; + chan->thresh = thresh; + chan->priority = alloc->priority; + chan->weight = weight; + + SPS_DBG("sps_alloc_dma_chan. pipe %d.\n", pipe_index); + + /* Report allocated pipes to client */ + chan_info->dev = dev->h; + /* Dest/input/write pipex */ + chan_info->dest_pipe_index = pipe_index; + /* Source/output/read pipe */ + chan_info->src_pipe_index = pipe_index + 1; + + result = 0; +exit_err: + mutex_unlock(&bam_dma_lock); + + return result; +} +EXPORT_SYMBOL(sps_alloc_dma_chan); + +/** + * Free a BAM DMA channel + * + */ +int sps_free_dma_chan(struct sps_dma_chan *chan) +{ + struct bamdma_device *dev; + u32 pipe_index; + int result = 0; + + if (chan == NULL) { + SPS_ERR("sps_free_dma_chan. chan is NULL"); + return SPS_ERROR; + } + + mutex_lock(&bam_dma_lock); + + dev = sps_dma_find_device(chan->dev); + if (dev == NULL) { + SPS_ERR("BAM-DMA: invalid BAM handle: %x", chan->dev); + result = SPS_ERROR; + goto exit_err; + } + + /* Verify the pipe indices */ + pipe_index = chan->dest_pipe_index; + if (pipe_index >= dev->num_pipes || ((pipe_index & 1)) || + (pipe_index + 1) != chan->src_pipe_index) { + SPS_ERR("sps_free_dma_chan. Invalid pipe indices"); + SPS_DBG("num_pipes=%d.dest=%d.src=%d.", + dev->num_pipes, + chan->dest_pipe_index, + chan->src_pipe_index); + result = SPS_ERROR; + goto exit_err; + } + + /* Are both pipes inactive? */ + if (dev->chans[pipe_index / 2].state != DMA_CHAN_STATE_ALLOC_EXT || + dev->pipes[pipe_index] != PIPE_INACTIVE || + dev->pipes[pipe_index + 1] != PIPE_INACTIVE) { + SPS_ERR("BAM-DMA: attempt to free active chan %d: %d %d", + pipe_index / 2, dev->pipes[pipe_index], + dev->pipes[pipe_index + 1]); + result = SPS_ERROR; + goto exit_err; + } + + /* Free the channel */ + dev->chans[pipe_index / 2].state = DMA_CHAN_STATE_FREE; + +exit_err: + mutex_unlock(&bam_dma_lock); + + return result; +} +EXPORT_SYMBOL(sps_free_dma_chan); + +/** + * Activate a BAM DMA pipe + * + * This function activates a BAM DMA pipe. + * + * @dev - pointer to BAM-DMA device descriptor + * + * @pipe_index - pipe index + * + * @return 0 on success, negative value on error + * + */ +static u32 sps_dma_check_pipes(struct bamdma_device *dev, u32 pipe_index) +{ + u32 pipe_in; + u32 pipe_out; + int enabled_in; + int enabled_out; + u32 check; + + pipe_in = pipe_index & ~1; + pipe_out = pipe_in + 1; + enabled_in = bam_pipe_is_enabled(dev->bam->base, pipe_in); + enabled_out = bam_pipe_is_enabled(dev->bam->base, pipe_out); + + if (!enabled_in && !enabled_out) + check = DMA_PIPES_BOTH_DISABLED; + else if (enabled_in && enabled_out) + check = DMA_PIPES_BOTH_ENABLED; + else + check = DMA_PIPES_STATE_DIFF; + + return check; +} + +/** + * Allocate a BAM DMA pipe + * + */ +int sps_dma_pipe_alloc(void *bam_arg, u32 pipe_index, enum sps_mode dir) +{ + struct sps_bam *bam = bam_arg; + struct bamdma_device *dev; + struct bamdma_chan *chan; + u32 channel; + int result = SPS_ERROR; + + if (bam == NULL) { + SPS_ERR("BAM context is NULL"); + return SPS_ERROR; + } + + /* Check pipe direction */ + if ((DMA_PIPE_IS_DEST(pipe_index) && dir != SPS_MODE_DEST) || + (DMA_PIPE_IS_SRC(pipe_index) && dir != SPS_MODE_SRC)) { + SPS_ERR("BAM-DMA: wrong direction for BAM %x pipe %d", + bam->props.phys_addr, pipe_index); + return SPS_ERROR; + } + + mutex_lock(&bam_dma_lock); + + dev = sps_dma_find_device((u32) bam); + if (dev == NULL) { + SPS_ERR("BAM-DMA: invalid BAM: %x", + bam->props.phys_addr); + goto exit_err; + } + if (pipe_index >= dev->num_pipes) { + SPS_ERR("BAM-DMA: BAM %x invalid pipe: %d", + bam->props.phys_addr, pipe_index); + goto exit_err; + } + if (dev->pipes[pipe_index] != PIPE_INACTIVE) { + SPS_ERR("BAM-DMA: BAM %x pipe %d already active", + bam->props.phys_addr, pipe_index); + goto exit_err; + } + + /* Mark pipe active */ + dev->pipes[pipe_index] = PIPE_ACTIVE; + + /* If channel is not allocated, make an internal allocation */ + channel = pipe_index / 2; + chan = &dev->chans[channel]; + if (chan->state != DMA_CHAN_STATE_ALLOC_EXT && + chan->state != DMA_CHAN_STATE_ALLOC_INT) { + chan->state = DMA_CHAN_STATE_ALLOC_INT; + } + + result = 0; +exit_err: + mutex_unlock(&bam_dma_lock); + + return result; +} + +/** + * Enable a BAM DMA pipe + * + */ +int sps_dma_pipe_enable(void *bam_arg, u32 pipe_index) +{ + struct sps_bam *bam = bam_arg; + struct bamdma_device *dev; + struct bamdma_chan *chan; + u32 channel; + int result = SPS_ERROR; + + SPS_DBG("sps_dma_pipe_enable.pipe %d", pipe_index); + + mutex_lock(&bam_dma_lock); + + dev = sps_dma_find_device((u32) bam); + if (dev == NULL) { + SPS_ERR("BAM-DMA: invalid BAM"); + goto exit_err; + } + if (pipe_index >= dev->num_pipes) { + SPS_ERR("BAM-DMA: BAM %x invalid pipe: %d", + bam->props.phys_addr, pipe_index); + goto exit_err; + } + if (dev->pipes[pipe_index] != PIPE_ACTIVE) { + SPS_ERR("BAM-DMA: BAM %x pipe %d not active", + bam->props.phys_addr, pipe_index); + goto exit_err; + } + + /* + * The channel must be enabled when the dest/input/write pipe + * is enabled + */ + if (DMA_PIPE_IS_DEST(pipe_index)) { + /* Configure and enable the channel */ + channel = pipe_index / 2; + chan = &dev->chans[channel]; + + if (chan->threshold != SPS_DMA_THRESHOLD_DEFAULT) + dma_write_reg_field(dev->virt_addr, + DMA_CHNL_CONFIG(channel), + DMA_CHNL_ACT_THRESH, + chan->thresh); + + if (chan->priority != SPS_DMA_PRI_DEFAULT) + dma_write_reg_field(dev->virt_addr, + DMA_CHNL_CONFIG(channel), + DMA_CHNL_WEIGHT, + chan->weight); + + dma_write_reg_field(dev->virt_addr, + DMA_CHNL_CONFIG(channel), + DMA_CHNL_ENABLE, 1); + } + + result = 0; +exit_err: + mutex_unlock(&bam_dma_lock); + + return result; +} + +/** + * Deactivate a BAM DMA pipe + * + * This function deactivates a BAM DMA pipe. + * + * @dev - pointer to BAM-DMA device descriptor + * + * @bam - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @return 0 on success, negative value on error + * + */ +static int sps_dma_deactivate_pipe_atomic(struct bamdma_device *dev, + struct sps_bam *bam, + u32 pipe_index) +{ + u32 channel; + + if (dev->bam != bam) + return SPS_ERROR; + if (pipe_index >= dev->num_pipes) + return SPS_ERROR; + if (dev->pipes[pipe_index] != PIPE_ACTIVE) + return SPS_ERROR; /* Pipe is not active */ + + SPS_DBG("BAM-DMA: deactivate pipe %d", pipe_index); + + /* Mark pipe inactive */ + dev->pipes[pipe_index] = PIPE_INACTIVE; + + /* + * Channel must be reset when either pipe is disabled, so just always + * reset regardless of other pipe's state + */ + channel = pipe_index / 2; + dma_write_reg_field(dev->virt_addr, DMA_CHNL_CONFIG(channel), + DMA_CHNL_ENABLE, 0); + + /* If the peer pipe is also inactive, reset the channel */ + if (sps_dma_check_pipes(dev, pipe_index) == DMA_PIPES_BOTH_DISABLED) { + /* Free channel if allocated internally */ + if (dev->chans[channel].state == DMA_CHAN_STATE_ALLOC_INT) + dev->chans[channel].state = DMA_CHAN_STATE_FREE; + } + + return 0; +} + +/** + * Free a BAM DMA pipe + * + */ +int sps_dma_pipe_free(void *bam_arg, u32 pipe_index) +{ + struct bamdma_device *dev; + struct sps_bam *bam = bam_arg; + int result; + + mutex_lock(&bam_dma_lock); + + dev = sps_dma_find_device((u32) bam); + if (dev == NULL) { + SPS_ERR("BAM-DMA: invalid BAM"); + result = SPS_ERROR; + goto exit_err; + } + + result = sps_dma_deactivate_pipe_atomic(dev, bam, pipe_index); + +exit_err: + mutex_unlock(&bam_dma_lock); + + return result; +} + +/** + * Get the BAM handle for BAM-DMA. + * + * The BAM handle should be use as source/destination in the sps_connect(). + * + * @return bam handle on success, zero on error + */ +u32 sps_dma_get_bam_handle(void) +{ + return (u32) bam_dma_dev[0].bam; +} +EXPORT_SYMBOL(sps_dma_get_bam_handle); + +/** + * Free the BAM handle for BAM-DMA. + * + */ +void sps_dma_free_bam_handle(u32 h) +{ +} +EXPORT_SYMBOL(sps_dma_free_bam_handle); + +#endif /* CONFIG_SPS_SUPPORT_BAMDMA */ diff --git a/drivers/platform/msm/sps/sps_map.c b/drivers/platform/msm/sps/sps_map.c new file mode 100644 index 00000000000..16d5065528c --- /dev/null +++ b/drivers/platform/msm/sps/sps_map.c @@ -0,0 +1,137 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * Connection mapping table managment for SPS device driver. + */ + +#include /* u32 */ +#include /* pr_info() */ +#include /* memset */ + +#include "spsi.h" + +/* Module state */ +struct sps_map_state { + const struct sps_map *maps; + u32 num_maps; + u32 options; +}; + +static struct sps_map_state sps_maps; + +/** + * Initialize connection mapping module + * + */ +int sps_map_init(const struct sps_map *map_props, u32 options) +{ + const struct sps_map *maps; + + /* Are there any connection mappings? */ + memset(&sps_maps, 0, sizeof(sps_maps)); + if (map_props == NULL) + return 0; + + /* Init the module state */ + sps_maps.maps = map_props; + sps_maps.options = options; + for (maps = sps_maps.maps;; maps++, sps_maps.num_maps++) + if (maps->src.periph_class == SPS_CLASS_INVALID && + maps->src.periph_phy_addr == SPS_ADDR_INVALID) + break; + + SPS_DBG("SPS driver: %d mappings", sps_maps.num_maps); + + return 0; +} + +/** + * De-initialize connection mapping module + * + */ +void sps_map_de_init(void) +{ + memset(&sps_maps, 0, sizeof(sps_maps)); +} + +/** + * Find matching connection mapping + * + */ +int sps_map_find(struct sps_connect *connect) +{ + const struct sps_map *map; + u32 i; + void *desc; + void *data; + + /* Are there any connection mappings? */ + if (sps_maps.num_maps == 0) + return SPS_ERROR; + + /* Search the mapping table for a match to the specified connection */ + for (i = sps_maps.num_maps, map = sps_maps.maps; + i > 0; i--, map++) + if (map->src.periph_class == (u32) connect->source && + map->dest.periph_class == (u32) connect->destination + && map->config == (u32) connect->config) + break; + + if (i == 0) + return SPS_ERROR; + + /* + * Before modifying client parameter struct, perform all + * operations that might fail + */ + desc = spsi_get_mem_ptr(map->desc_base); + if (desc == NULL) { + SPS_ERR("Cannot get virt addr for I/O buffer: 0x%x", + map->desc_base); + return SPS_ERROR; + } + + if (map->data_size > 0 && map->data_base != SPS_ADDR_INVALID) { + data = spsi_get_mem_ptr(map->data_base); + if (data == NULL) { + SPS_ERR("Cannot get virt addr for I/O buffer: 0x%x", + map->data_base); + return SPS_ERROR; + } + } else { + data = NULL; + } + + /* Copy mapping values to client parameter struct */ + if (connect->source != SPS_DEV_HANDLE_MEM) + connect->src_pipe_index = map->src.pipe_index; + + if (connect->destination != SPS_DEV_HANDLE_MEM) + connect->dest_pipe_index = map->dest.pipe_index; + + if (connect->mode == SPS_MODE_SRC) + connect->event_thresh = map->src.event_thresh; + else + connect->event_thresh = map->dest.event_thresh; + + connect->desc.size = map->desc_size; + connect->desc.phys_base = map->desc_base; + connect->desc.base = desc; + if (map->data_size > 0 && map->data_base != SPS_ADDR_INVALID) { + connect->data.size = map->data_size; + connect->data.phys_base = map->data_base; + connect->data.base = data; + } + + return 0; +} diff --git a/drivers/platform/msm/sps/sps_map.h b/drivers/platform/msm/sps/sps_map.h new file mode 100644 index 00000000000..692e47cd1f3 --- /dev/null +++ b/drivers/platform/msm/sps/sps_map.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* SPS driver mapping table data declarations. */ + + +#ifndef _SPS_MAP_H_ +#define _SPS_MAP_H_ + +#include /* u32 */ + +/* End point parameters */ +struct sps_map_end_point { + u32 periph_class; /* Peripheral device enumeration class */ + u32 periph_phy_addr; /* Peripheral base address */ + u32 pipe_index; /* Pipe index */ + u32 event_thresh; /* Pipe event threshold */ +}; + +/* Mapping connection descriptor */ +struct sps_map { + /* Source end point parameters */ + struct sps_map_end_point src; + + /* Destination end point parameters */ + struct sps_map_end_point dest; + + /* Resource parameters */ + u32 config; /* Configuration (stream) identifier */ + u32 desc_base; /* Physical address of descriptor FIFO */ + u32 desc_size; /* Size (bytes) of descriptor FIFO */ + u32 data_base; /* Physical address of data FIFO */ + u32 data_size; /* Size (bytes) of data FIFO */ + +}; + +#endif /* _SPS_MAP_H_ */ diff --git a/drivers/platform/msm/sps/sps_mem.c b/drivers/platform/msm/sps/sps_mem.c new file mode 100644 index 00000000000..3aee4ba072e --- /dev/null +++ b/drivers/platform/msm/sps/sps_mem.c @@ -0,0 +1,156 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * Pipe-Memory allocation/free management. + */ + +#include /* u32 */ +#include /* pr_info() */ +#include /* ioremap() */ +#include /* mutex */ +#include /* list_head */ +#include /* gen_pool_alloc() */ +#include /* ENOMEM */ + +#include "sps_bam.h" +#include "spsi.h" + +static u32 iomem_phys; +static void *iomem_virt; +static u32 iomem_size; +static u32 iomem_offset; +static struct gen_pool *pool; +static u32 nid = 0xaa; + +/* Debug */ +static u32 total_alloc; +static u32 total_free; + +/** + * Translate physical to virtual address + * + */ +void *spsi_get_mem_ptr(u32 phys_addr) +{ + void *virt = NULL; + + if ((phys_addr >= iomem_phys) && + (phys_addr < (iomem_phys + iomem_size))) { + virt = (u8 *) iomem_virt + (phys_addr - iomem_phys); + } else { + virt = phys_to_virt(phys_addr); + SPS_ERR("sps:spsi_get_mem_ptr.invalid phys addr=0x%x.", + phys_addr); + } + return virt; +} + +/** + * Allocate I/O (pipe) memory + * + */ +u32 sps_mem_alloc_io(u32 bytes) +{ + u32 phys_addr = SPS_ADDR_INVALID; + u32 virt_addr = 0; + + virt_addr = gen_pool_alloc(pool, bytes); + if (virt_addr) { + iomem_offset = virt_addr - (u32) iomem_virt; + phys_addr = iomem_phys + iomem_offset; + total_alloc += bytes; + } else { + SPS_ERR("sps:gen_pool_alloc %d bytes fail.", bytes); + return SPS_ADDR_INVALID; + } + + SPS_DBG("sps:sps_mem_alloc_io.phys=0x%x.virt=0x%x.size=0x%x.", + phys_addr, virt_addr, bytes); + + return phys_addr; +} + +/** + * Free I/O memory + * + */ +void sps_mem_free_io(u32 phys_addr, u32 bytes) +{ + u32 virt_addr = 0; + + iomem_offset = phys_addr - iomem_phys; + virt_addr = (u32) iomem_virt + iomem_offset; + + SPS_DBG("sps:sps_mem_free_io.phys=0x%x.virt=0x%x.size=0x%x.", + phys_addr, virt_addr, bytes); + + gen_pool_free(pool, virt_addr, bytes); + total_free += bytes; +} + +/** + * Initialize driver memory module + * + */ +int sps_mem_init(u32 pipemem_phys_base, u32 pipemem_size) +{ + int res; + /* 2^8=128. The desc-fifo and data-fifo minimal allocation. */ + int min_alloc_order = 8; + + iomem_phys = pipemem_phys_base; + iomem_size = pipemem_size; + + if (iomem_phys == 0) { + SPS_ERR("sps:Invalid Pipe-Mem address"); + return SPS_ERROR; + } else { + iomem_virt = ioremap(iomem_phys, iomem_size); + if (!iomem_virt) { + SPS_ERR("sps:Failed to IO map pipe memory.\n"); + return -ENOMEM; + } + } + + iomem_offset = 0; + SPS_DBG("sps:sps_mem_init.iomem_phys=0x%x,iomem_virt=0x%x.", + iomem_phys, (u32) iomem_virt); + + pool = gen_pool_create(min_alloc_order, nid); + res = gen_pool_add(pool, (u32) iomem_virt, iomem_size, nid); + if (res) + return res; + + return 0; +} + +/** + * De-initialize driver memory module + * + */ +int sps_mem_de_init(void) +{ + if (iomem_virt != NULL) { + gen_pool_destroy(pool); + pool = NULL; + iounmap(iomem_virt); + iomem_virt = NULL; + } + + if (total_alloc == total_free) + return 0; + else { + SPS_ERR("sps:sps_mem_de_init:some memory not free"); + return SPS_ERROR; + } +} diff --git a/drivers/platform/msm/sps/sps_rm.c b/drivers/platform/msm/sps/sps_rm.c new file mode 100644 index 00000000000..ac1f4d15ed0 --- /dev/null +++ b/drivers/platform/msm/sps/sps_rm.c @@ -0,0 +1,807 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* Resource management for the SPS device driver. */ + +#include /* u32 */ +#include /* pr_info() */ +#include /* mutex */ +#include /* list_head */ +#include /* kzalloc() */ +#include /* memset */ + +#include "spsi.h" +#include "sps_core.h" + +/* "Clear" value for the connection parameter struct */ +#define SPSRM_CLEAR 0xcccccccc + +/* Max BAM FIFO sizes */ +#define SPSRM_MAX_DESC_FIFO_SIZE 0xffff +#define SPSRM_MAX_DATA_FIFO_SIZE 0xffff + +/* Connection control struct pointer */ +static struct sps_rm *sps_rm; + +/** + * Initialize resource manager module + */ +int sps_rm_init(struct sps_rm *rm, u32 options) +{ + /* Set the resource manager state struct pointer */ + sps_rm = rm; + + /* Initialize the state struct */ + INIT_LIST_HEAD(&sps_rm->connections_q); + mutex_init(&sps_rm->lock); + + return 0; +} + +/** + * Initialize client state context + * + */ +void sps_rm_config_init(struct sps_connect *connect) +{ + memset(connect, SPSRM_CLEAR, sizeof(*connect)); +} + +/** + * Remove reference to connection mapping + * + * This function removes a reference from a connection mapping struct. + * + * @map - pointer to connection mapping struct + * + */ +static void sps_rm_remove_ref(struct sps_connection *map) +{ + /* Free this connection */ + map->refs--; + if (map->refs <= 0) { + if (map->client_src != NULL || map->client_dest != NULL) + SPS_ERR("Failed to allocate connection struct"); + + list_del(&map->list); + kfree(map); + } +} + +/** + * Compare map to connect parameters + * + * This function compares client connect parameters to an allocated + * connection mapping. + * + * @pipe - client context for SPS connection end point + * + * @return - true if match, false otherwise + * + */ +static int sps_rm_map_match(const struct sps_connect *cfg, + const struct sps_connection *map) +{ + if (cfg->source != map->src.dev || + cfg->destination != map->dest.dev) + return false; + + if (cfg->src_pipe_index != SPSRM_CLEAR && + cfg->src_pipe_index != map->src.pipe_index) + return false; + + if (cfg->dest_pipe_index != SPSRM_CLEAR && + cfg->dest_pipe_index != map->dest.pipe_index) + return false; + + if (cfg->config != map->config) + return false; + + if (cfg->desc.size != SPSRM_CLEAR) { + if (cfg->desc.size != map->desc.size) + return false; + + if (cfg->desc.phys_base != SPSRM_CLEAR && + cfg->desc.base != (void *)SPSRM_CLEAR && + (cfg->desc.phys_base != map->desc.phys_base || + cfg->desc.base != map->desc.base)) { + return false; + } + } + + if (cfg->data.size != SPSRM_CLEAR) { + if (cfg->data.size != map->data.size) + return false; + + if (cfg->data.phys_base != SPSRM_CLEAR && + cfg->data.base != (void *)SPSRM_CLEAR && + (cfg->data.phys_base != map->data.phys_base || + cfg->data.base != map->data.base)) + return false; + } + + return true; +} + +/** + * Find unconnected mapping + * + * This function finds an allocated a connection mapping. + * + * @pipe - client context for SPS connection end point + * + * @return - pointer to allocated connection mapping, or NULL if not found + * + */ +static struct sps_connection *find_unconnected(struct sps_pipe *pipe) +{ + struct sps_connect *cfg = &pipe->connect; + struct sps_connection *map; + + /* Has this connection already been allocated? */ + list_for_each_entry(map, &sps_rm->connections_q, list) { + if (sps_rm_map_match(cfg, map)) + if ((cfg->mode == SPS_MODE_SRC + && map->client_src == NULL) + || (cfg->mode != SPS_MODE_SRC + && map->client_dest == NULL)) + return map; /* Found */ + } + + return NULL; /* Not Found */ +} + +/** + * Assign connection to client + * + * This function assigns a connection to a client. + * + * @pipe - client context for SPS connection end point + * + * @map - connection mapping + * + * @return 0 on success, negative value on error + * + */ +static int sps_rm_assign(struct sps_pipe *pipe, + struct sps_connection *map) +{ + struct sps_connect *cfg = &pipe->connect; + + /* Check ownership and BAM */ + if ((cfg->mode == SPS_MODE_SRC && map->client_src != NULL) || + (cfg->mode != SPS_MODE_SRC && map->client_dest != NULL)) + /* The end point is already connected */ + return SPS_ERROR; + + /* Check whether this end point is a BAM (not memory) */ + if ((cfg->mode == SPS_MODE_SRC && map->src.bam == NULL) || + (cfg->mode != SPS_MODE_SRC && map->dest.bam == NULL)) + return SPS_ERROR; + + /* Record the connection assignment */ + if (cfg->mode == SPS_MODE_SRC) { + map->client_src = pipe; + pipe->bam = map->src.bam; + pipe->pipe_index = map->src.pipe_index; + if (pipe->connect.event_thresh != SPSRM_CLEAR) + map->src.event_threshold = pipe->connect.event_thresh; + } else { + map->client_dest = pipe; + pipe->bam = map->dest.bam; + pipe->pipe_index = map->dest.pipe_index; + if (pipe->connect.event_thresh != SPSRM_CLEAR) + map->dest.event_threshold = + pipe->connect.event_thresh; + } + pipe->map = map; + + SPS_DBG("sps:sps_rm_assign.bam 0x%x.pipe_index=%d\n", + BAM_ID(pipe->bam), pipe->pipe_index); + + /* Copy parameters to client connect state */ + pipe->connect.src_pipe_index = map->src.pipe_index; + pipe->connect.dest_pipe_index = map->dest.pipe_index; + pipe->connect.desc = map->desc; + pipe->connect.data = map->data; + + pipe->client_state = SPS_STATE_ALLOCATE; + + return 0; +} + +/** + * Free connection mapping resources + * + * This function frees a connection mapping resources. + * + * @pipe - client context for SPS connection end point + * + */ +static void sps_rm_free_map_rsrc(struct sps_connection *map) +{ + struct sps_bam *bam; + + if (map->client_src != NULL || map->client_dest != NULL) + return; + + if (map->alloc_src_pipe != SPS_BAM_PIPE_INVALID) { + bam = map->src.bam; + sps_bam_pipe_free(bam, map->src.pipe_index); + + /* Is this a BAM-DMA pipe? */ +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) + /* Deallocate and free the BAM-DMA channel */ + sps_dma_pipe_free(bam, map->src.pipe_index); +#endif + map->alloc_src_pipe = SPS_BAM_PIPE_INVALID; + map->src.pipe_index = SPS_BAM_PIPE_INVALID; + } + if (map->alloc_dest_pipe != SPS_BAM_PIPE_INVALID) { + bam = map->dest.bam; + sps_bam_pipe_free(bam, map->dest.pipe_index); + + /* Is this a BAM-DMA pipe? */ +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) { + /* Deallocate the BAM-DMA channel */ + sps_dma_pipe_free(bam, map->dest.pipe_index); + } +#endif + map->alloc_dest_pipe = SPS_BAM_PIPE_INVALID; + map->dest.pipe_index = SPS_BAM_PIPE_INVALID; + } + if (map->alloc_desc_base != SPS_ADDR_INVALID) { + sps_mem_free_io(map->alloc_desc_base, map->desc.size); + + map->alloc_desc_base = SPS_ADDR_INVALID; + map->desc.phys_base = SPS_ADDR_INVALID; + } + if (map->alloc_data_base != SPS_ADDR_INVALID) { + sps_mem_free_io(map->alloc_data_base, map->data.size); + + map->alloc_data_base = SPS_ADDR_INVALID; + map->data.phys_base = SPS_ADDR_INVALID; + } +} + +/** + * Init connection mapping from client connect + * + * This function initializes a connection mapping from the client's + * connect parameters. + * + * @map - connection mapping struct + * + * @cfg - client connect parameters + * + * @return - pointer to allocated connection mapping, or NULL on error + * + */ +static void sps_rm_init_map(struct sps_connection *map, + const struct sps_connect *cfg) +{ + /* Clear the connection mapping struct */ + memset(map, 0, sizeof(*map)); + map->desc.phys_base = SPS_ADDR_INVALID; + map->data.phys_base = SPS_ADDR_INVALID; + map->alloc_desc_base = SPS_ADDR_INVALID; + map->alloc_data_base = SPS_ADDR_INVALID; + map->alloc_src_pipe = SPS_BAM_PIPE_INVALID; + map->alloc_dest_pipe = SPS_BAM_PIPE_INVALID; + + /* Copy client required parameters */ + map->src.dev = cfg->source; + map->dest.dev = cfg->destination; + map->desc.size = cfg->desc.size; + map->data.size = cfg->data.size; + map->config = cfg->config; + + /* Did client specify descriptor FIFO? */ + if (map->desc.size != SPSRM_CLEAR && + cfg->desc.phys_base != SPSRM_CLEAR && + cfg->desc.base != (void *)SPSRM_CLEAR) + map->desc = cfg->desc; + + /* Did client specify data FIFO? */ + if (map->data.size != SPSRM_CLEAR && + cfg->data.phys_base != SPSRM_CLEAR && + cfg->data.base != (void *)SPSRM_CLEAR) + map->data = cfg->data; + + /* Did client specify source pipe? */ + if (cfg->src_pipe_index != SPSRM_CLEAR) + map->src.pipe_index = cfg->src_pipe_index; + else + map->src.pipe_index = SPS_BAM_PIPE_INVALID; + + + /* Did client specify destination pipe? */ + if (cfg->dest_pipe_index != SPSRM_CLEAR) + map->dest.pipe_index = cfg->dest_pipe_index; + else + map->dest.pipe_index = SPS_BAM_PIPE_INVALID; +} + +/** + * Create a new connection mapping + * + * This function creates a new connection mapping. + * + * @pipe - client context for SPS connection end point + * + * @return - pointer to allocated connection mapping, or NULL on error + * + */ +static struct sps_connection *sps_rm_create(struct sps_pipe *pipe) +{ + struct sps_connection *map; + struct sps_bam *bam; + u32 desc_size; + u32 data_size; + enum sps_mode dir; + int success = false; + + /* Allocate new connection */ + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (map == NULL) { + SPS_ERR("Failed to allocate connection struct"); + return NULL; + } + + /* Initialize connection struct */ + sps_rm_init_map(map, &pipe->connect); + dir = pipe->connect.mode; + + /* Use a do/while() loop to avoid a "goto" */ + success = false; + /* Get BAMs */ + map->src.bam = sps_h2bam(map->src.dev); + if (map->src.bam == NULL) { + if (map->src.dev != SPS_DEV_HANDLE_MEM) { + SPS_ERR("Invalid BAM handle: 0x%x", map->src.dev); + goto exit_err; + } + map->src.pipe_index = SPS_BAM_PIPE_INVALID; + } + map->dest.bam = sps_h2bam(map->dest.dev); + if (map->dest.bam == NULL) { + if (map->dest.dev != SPS_DEV_HANDLE_MEM) { + SPS_ERR("Invalid BAM handle: 0x%x", map->dest.dev); + goto exit_err; + } + map->dest.pipe_index = SPS_BAM_PIPE_INVALID; + } + + /* Check the BAM device for the pipe */ + if ((dir == SPS_MODE_SRC && map->src.bam == NULL) || + (dir != SPS_MODE_SRC && map->dest.bam == NULL)) { + SPS_ERR("Invalid BAM endpt: dir %d src 0x%x dest 0x%x", + dir, map->src.dev, map->dest.dev); + goto exit_err; + } + + /* Allocate pipes and copy BAM parameters */ + if (map->src.bam != NULL) { + /* Allocate the pipe */ + bam = map->src.bam; + map->alloc_src_pipe = sps_bam_pipe_alloc(bam, + map->src.pipe_index); + if (map->alloc_src_pipe == SPS_BAM_PIPE_INVALID) + goto exit_err; + map->src.pipe_index = map->alloc_src_pipe; + + /* Is this a BAM-DMA pipe? */ +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) { + int rc; + /* Allocate the BAM-DMA channel */ + rc = sps_dma_pipe_alloc(bam, map->src.pipe_index, + SPS_MODE_SRC); + if (rc) { + SPS_ERR("Failed to alloc BAM-DMA pipe: %d", + map->src.pipe_index); + goto exit_err; + } + } +#endif + map->src.bam_phys = bam->props.phys_addr; + map->src.event_threshold = bam->props.event_threshold; + } + if (map->dest.bam != NULL) { + /* Allocate the pipe */ + bam = map->dest.bam; + map->alloc_dest_pipe = sps_bam_pipe_alloc(bam, + map->dest.pipe_index); + if (map->alloc_dest_pipe == SPS_BAM_PIPE_INVALID) + goto exit_err; + + map->dest.pipe_index = map->alloc_dest_pipe; + + /* Is this a BAM-DMA pipe? */ +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) { + int rc; + /* Allocate the BAM-DMA channel */ + rc = sps_dma_pipe_alloc(bam, map->dest.pipe_index, + SPS_MODE_DEST); + if (rc) { + SPS_ERR("Failed to alloc BAM-DMA pipe: %d", + map->dest.pipe_index); + goto exit_err; + } + } +#endif + map->dest.bam_phys = bam->props.phys_addr; + map->dest.event_threshold = + bam->props.event_threshold; + } + + /* Get default FIFO sizes */ + desc_size = 0; + data_size = 0; + if (map->src.bam != NULL) { + bam = map->src.bam; + desc_size = bam->props.desc_size; + data_size = bam->props.data_size; + } + if (map->dest.bam != NULL) { + bam = map->dest.bam; + if (bam->props.desc_size > desc_size) + desc_size = bam->props.desc_size; + if (bam->props.data_size > data_size) + data_size = bam->props.data_size; + } + + /* Set FIFO sizes */ + if (map->desc.size == SPSRM_CLEAR) + map->desc.size = desc_size; + if (map->src.bam != NULL && map->dest.bam != NULL) { + /* BAM-to-BAM requires data FIFO */ + if (map->data.size == SPSRM_CLEAR) + map->data.size = data_size; + } else { + map->data.size = 0; + } + if (map->desc.size > SPSRM_MAX_DESC_FIFO_SIZE) { + SPS_ERR("Invalid desc FIFO size: 0x%x", map->desc.size); + goto exit_err; + } + if (map->src.bam != NULL && map->dest.bam != NULL && + map->data.size > SPSRM_MAX_DATA_FIFO_SIZE) { + SPS_ERR("Invalid data FIFO size: 0x%x", map->data.size); + goto exit_err; + } + + /* Allocate descriptor FIFO if necessary */ + if (map->desc.size && map->desc.phys_base == SPS_ADDR_INVALID) { + map->alloc_desc_base = sps_mem_alloc_io(map->desc.size); + if (map->alloc_desc_base == SPS_ADDR_INVALID) { + SPS_ERR("I/O memory allocation failure: 0x%x", + map->desc.size); + goto exit_err; + } + map->desc.phys_base = map->alloc_desc_base; + map->desc.base = spsi_get_mem_ptr(map->desc.phys_base); + if (map->desc.base == NULL) { + SPS_ERR("Cannot get virt addr for I/O buffer: 0x%x", + map->desc.phys_base); + goto exit_err; + } + } + + /* Allocate data FIFO if necessary */ + if (map->data.size && map->data.phys_base == SPS_ADDR_INVALID) { + map->alloc_data_base = sps_mem_alloc_io(map->data.size); + if (map->alloc_data_base == SPS_ADDR_INVALID) { + SPS_ERR("I/O memory allocation failure: 0x%x", + map->data.size); + goto exit_err; + } + map->data.phys_base = map->alloc_data_base; + map->data.base = spsi_get_mem_ptr(map->data.phys_base); + if (map->data.base == NULL) { + SPS_ERR("Cannot get virt addr for I/O buffer: 0x%x", + map->data.phys_base); + goto exit_err; + } + } + + /* Attempt to assign this connection to the client */ + if (sps_rm_assign(pipe, map)) + goto exit_err; + + /* Initialization was successful */ + success = true; +exit_err: + + /* If initialization failed, free resources */ + if (!success) { + sps_rm_free_map_rsrc(map); + kfree(map); + return NULL; + } + + return map; +} + +/** + * Free connection mapping + * + * This function frees a connection mapping. + * + * @pipe - client context for SPS connection end point + * + * @return 0 on success, negative value on error + * + */ +static int sps_rm_free(struct sps_pipe *pipe) +{ + struct sps_connection *map = (void *)pipe->map; + struct sps_connect *cfg = &pipe->connect; + + mutex_lock(&sps_rm->lock); + + /* Free this connection */ + if (cfg->mode == SPS_MODE_SRC) + map->client_src = NULL; + else + map->client_dest = NULL; + + pipe->map = NULL; + pipe->client_state = SPS_STATE_DISCONNECT; + sps_rm_free_map_rsrc(map); + + sps_rm_remove_ref(map); + + mutex_unlock(&sps_rm->lock); + + return 0; +} + +/** + * Allocate an SPS connection end point + * + * This function allocates resources and initializes a BAM connection. + * + * @pipe - client context for SPS connection end point + * + * @return 0 on success, negative value on error + * + */ +static int sps_rm_alloc(struct sps_pipe *pipe) +{ + struct sps_connection *map; + int result = SPS_ERROR; + + if (pipe->connect.sps_reserved != SPSRM_CLEAR) { + /* + * Client did not call sps_get_config() to init + * struct sps_connect, so only use legacy members. + */ + u32 source = pipe->connect.source; + u32 destination = pipe->connect.destination; + enum sps_mode mode = pipe->connect.mode; + u32 config = pipe->connect.config; + memset(&pipe->connect, SPSRM_CLEAR, + sizeof(pipe->connect)); + pipe->connect.source = source; + pipe->connect.destination = destination; + pipe->connect.mode = mode; + pipe->connect.config = config; + } + if (pipe->connect.config == SPSRM_CLEAR) + pipe->connect.config = SPS_CONFIG_DEFAULT; + + /* + * If configuration is not default, then client is specifying a + * connection mapping. Find a matching mapping, or fail. + * If a match is found, the client's Connect struct will be updated + * with all the mapping's values. + */ + if (pipe->connect.config != SPS_CONFIG_DEFAULT) { + if (sps_map_find(&pipe->connect)) { + SPS_ERR("Failed to find connection mapping"); + return SPS_ERROR; + } + } + + mutex_lock(&sps_rm->lock); + /* Check client state */ + if (IS_SPS_STATE_OK(pipe)) { + SPS_ERR("Client connection already allocated"); + goto exit_err; + } + + /* Are the connection resources already allocated? */ + map = find_unconnected(pipe); + if (map != NULL) { + /* Attempt to assign this connection to the client */ + if (sps_rm_assign(pipe, map)) + /* Assignment failed, so must allocate new */ + map = NULL; + } + + /* Allocate a new connection if necessary */ + if (map == NULL) { + map = sps_rm_create(pipe); + if (map == NULL) { + SPS_ERR("Failed to allocate connection"); + goto exit_err; + } + list_add_tail(&map->list, &sps_rm->connections_q); + } + + /* Add the connection to the allocated queue */ + map->refs++; + + /* Initialization was successful */ + result = 0; +exit_err: + mutex_unlock(&sps_rm->lock); + + if (result) + return SPS_ERROR; + + return 0; +} + +/** + * Disconnect an SPS connection end point + * + * This function frees resources and de-initializes a BAM connection. + * + * @pipe - client context for SPS connection end point + * + * @return 0 on success, negative value on error + * + */ +static int sps_rm_disconnect(struct sps_pipe *pipe) +{ + sps_rm_free(pipe); + return 0; +} + +/** + * Process connection state change + * + * This function processes a connection state change. + * + * @pipe - pointer to client context + * + * @state - new state for connection + * + * @return 0 on success, negative value on error + * + */ +int sps_rm_state_change(struct sps_pipe *pipe, u32 state) +{ + int auto_enable = false; + int result; + + /* Allocate the pipe */ + if (pipe->client_state == SPS_STATE_DISCONNECT && + state == SPS_STATE_ALLOCATE) { + if (sps_rm_alloc(pipe)) + return SPS_ERROR; + } + + /* Configure the pipe */ + if (pipe->client_state == SPS_STATE_ALLOCATE && + state == SPS_STATE_CONNECT) { + /* Connect the BAM pipe */ + struct sps_bam_connect_param params; + memset(¶ms, 0, sizeof(params)); + params.mode = pipe->connect.mode; + if (pipe->connect.options != SPSRM_CLEAR) { + params.options = pipe->connect.options; + params.irq_gen_addr = pipe->connect.irq_gen_addr; + params.irq_gen_data = pipe->connect.irq_gen_data; + } + result = sps_bam_pipe_connect(pipe, ¶ms); + if (result) { + SPS_ERR("Failed to connect BAM 0x%x pipe %d", + (u32) pipe->bam, pipe->pipe_index); + return SPS_ERROR; + } + pipe->client_state = SPS_STATE_CONNECT; + + /* Set auto-enable for system-mode connections */ + if (pipe->connect.source == SPS_DEV_HANDLE_MEM || + pipe->connect.destination == SPS_DEV_HANDLE_MEM) { + if (pipe->map->desc.size != 0 && + pipe->map->desc.phys_base != SPS_ADDR_INVALID) + auto_enable = true; + } + } + + /* Enable the pipe data flow */ + if (pipe->client_state == SPS_STATE_CONNECT && + !(state == SPS_STATE_DISABLE + || state == SPS_STATE_DISCONNECT) + && (state == SPS_STATE_ENABLE || auto_enable + || (pipe->connect.options & SPS_O_AUTO_ENABLE))) { + result = sps_bam_pipe_enable(pipe->bam, pipe->pipe_index); + if (result) { + SPS_ERR("Failed to set BAM 0x%x pipe %d flow on", + pipe->bam->props.phys_addr, + pipe->pipe_index); + return SPS_ERROR; + } + + /* Is this a BAM-DMA pipe? */ +#ifdef CONFIG_SPS_SUPPORT_BAMDMA + if ((pipe->bam->props.options & SPS_BAM_OPT_BAMDMA)) { + /* Activate the BAM-DMA channel */ + result = sps_dma_pipe_enable(pipe->bam, + pipe->pipe_index); + if (result) { + SPS_ERR("Failed to activate BAM-DMA pipe: %d", + pipe->pipe_index); + return SPS_ERROR; + } + } +#endif + pipe->client_state = SPS_STATE_ENABLE; + } + + /* Disable the pipe data flow */ + if (pipe->client_state == SPS_STATE_ENABLE && + (state == SPS_STATE_DISABLE || state == SPS_STATE_DISCONNECT)) { + result = sps_bam_pipe_disable(pipe->bam, pipe->pipe_index); + if (result) { + SPS_ERR("Failed to set BAM 0x%x pipe %d flow off", + pipe->bam->props.phys_addr, + pipe->pipe_index); + return SPS_ERROR; + } + pipe->client_state = SPS_STATE_CONNECT; + } + + /* Disconnect the BAM pipe */ + if (pipe->client_state == SPS_STATE_CONNECT && + state == SPS_STATE_DISCONNECT) { + struct sps_connection *map; + u32 pipe_index; + + if (pipe->connect.mode == SPS_MODE_SRC) + pipe_index = pipe->map->src.pipe_index; + else + pipe_index = pipe->map->dest.pipe_index; + + + result = sps_bam_pipe_disconnect(pipe->bam, pipe_index); + if (result) { + SPS_ERR("Failed to disconnect BAM 0x%x pipe %d", + pipe->bam->props.phys_addr, + pipe->pipe_index); + return SPS_ERROR; + } + + /* Clear map state */ + map = (void *)pipe->map; + if (pipe->connect.mode == SPS_MODE_SRC) + map->client_src = NULL; + else if (pipe->connect.mode == SPS_MODE_DEST) + map->client_dest = NULL; + + sps_rm_disconnect(pipe); + + /* Clear the client state */ + pipe->map = NULL; + pipe->bam = NULL; + pipe->client_state = SPS_STATE_DISCONNECT; + } + + return 0; +} diff --git a/drivers/platform/msm/sps/spsi.h b/drivers/platform/msm/sps/spsi.h new file mode 100644 index 00000000000..847ac165531 --- /dev/null +++ b/drivers/platform/msm/sps/spsi.h @@ -0,0 +1,312 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * Smart-Peripheral-Switch (SPS) internal API. + */ + +#ifndef _SPSI_H_ +#define _SPSI_H_ + +#include /* u32 */ +#include /* list_head */ +#include /* pr_info() */ + +#include + +#include "sps_map.h" + +/* Adjust for offset of struct sps_q_event */ +#define SPS_EVENT_INDEX(e) ((e) - 1) +#define SPS_ERROR -1 + +/* BAM identifier used in log messages */ +#define BAM_ID(dev) ((dev)->props.phys_addr) + +#ifdef CONFIG_DEBUG_FS +#define MAX_MSG_LEN 80 +#define SPS_DEBUGFS(msg, args...) do { \ + char buf[MAX_MSG_LEN]; \ + snprintf(buf, MAX_MSG_LEN, msg"\n", ##args); \ + sps_debugfs_record(buf); \ + } while (0) +#define SPS_ERR(msg, args...) do { \ + pr_err(msg, ##args); \ + SPS_DEBUGFS(msg, ##args); \ + } while (0) +#define SPS_INFO(msg, args...) do { \ + pr_info(msg, ##args); \ + SPS_DEBUGFS(msg, ##args); \ + } while (0) +#define SPS_DBG(msg, args...) do { \ + pr_debug(msg, ##args); \ + SPS_DEBUGFS(msg, ##args); \ + } while (0) +#else +#define SPS_DBG(x...) pr_debug(x) +#define SPS_INFO(x...) pr_info(x) +#define SPS_ERR(x...) pr_err(x) +#endif + +/* End point parameters */ +struct sps_conn_end_pt { + u32 dev; /* Device handle of BAM */ + u32 bam_phys; /* Physical address of BAM. */ + u32 pipe_index; /* Pipe index */ + u32 event_threshold; /* Pipe event threshold */ + void *bam; +}; + +/* Connection bookkeeping descriptor struct */ +struct sps_connection { + struct list_head list; + + /* Source end point parameters */ + struct sps_conn_end_pt src; + + /* Destination end point parameters */ + struct sps_conn_end_pt dest; + + /* Resource parameters */ + struct sps_mem_buffer desc; /* Descriptor FIFO */ + struct sps_mem_buffer data; /* Data FIFO (BAM-to-BAM mode only) */ + u32 config; /* Client specified connection configuration */ + + /* Connection state */ + void *client_src; + void *client_dest; + int refs; /* Reference counter */ + + /* Dynamically allocated resouces, if required */ + u32 alloc_src_pipe; /* Source pipe index */ + u32 alloc_dest_pipe; /* Destination pipe index */ + u32 alloc_desc_base; /* Physical address of descriptor FIFO */ + u32 alloc_data_base; /* Physical address of data FIFO */ +}; + +/* Event bookkeeping descriptor struct */ +struct sps_q_event { + struct list_head list; + /* Event payload data */ + struct sps_event_notify notify; +}; + +/* Memory heap statistics */ +struct sps_mem_stats { + u32 base_addr; + u32 size; + u32 blocks_used; + u32 bytes_used; + u32 max_bytes_used; +}; + +#ifdef CONFIG_DEBUG_FS +/* record debug info for debugfs */ +void sps_debugfs_record(const char *); +#endif + +/** + * Translate physical to virtual address + * + * This Function translates physical to virtual address. + * + * @phys_addr - physical address to translate + * + * @return virtual memory pointer + * + */ +void *spsi_get_mem_ptr(u32 phys_addr); + +/** + * Allocate I/O (pipe) memory + * + * This function allocates target I/O (pipe) memory. + * + * @bytes - number of bytes to allocate + * + * @return physical address of allocated memory, or SPS_ADDR_INVALID on error + */ +u32 sps_mem_alloc_io(u32 bytes); + +/** + * Free I/O (pipe) memory + * + * This function frees target I/O (pipe) memory. + * + * @phys_addr - physical address of memory to free + * + * @bytes - number of bytes to free. + */ +void sps_mem_free_io(u32 phys_addr, u32 bytes); + +/** + * Find matching connection mapping + * + * This function searches for a connection mapping that matches the + * parameters supplied by the client. If a match is found, the client's + * parameter struct is updated with the values specified in the mapping. + * + * @connect - pointer to client connection parameters + * + * @return 0 if match is found, negative value otherwise + * + */ +int sps_map_find(struct sps_connect *connect); + +/** + * Allocate a BAM DMA pipe + * + * This function allocates a BAM DMA pipe, and is intended to be called + * internally from the BAM resource manager. Allocation implies that + * the pipe has been referenced by a client Connect() and is in use. + * + * BAM DMA is permissive with activations, and allows a pipe to be allocated + * with or without a client-initiated allocation. This allows the client to + * specify exactly which pipe should be used directly through the Connect() API. + * sps_dma_alloc_chan() does not allow the client to specify the pipes/channel. + * + * @bam - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @dir - pipe direction + * + * @return 0 on success, negative value on error + */ +int sps_dma_pipe_alloc(void *bam, u32 pipe_index, enum sps_mode dir); + +/** + * Enable a BAM DMA pipe + * + * This function enables the channel associated with a BAM DMA pipe, and + * is intended to be called internally from the BAM resource manager. + * Enable must occur *after* the pipe has been enabled so that proper + * sequencing between pipe and DMA channel enables can be enforced. + * + * @bam - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @return 0 on success, negative value on error + * + */ +int sps_dma_pipe_enable(void *bam, u32 pipe_index); + +/** + * Free a BAM DMA pipe + * + * This function disables and frees a BAM DMA pipe, and is intended to be + * called internally from the BAM resource manager. This must occur *after* + * the pipe has been disabled/reset so that proper sequencing between pipe and + * DMA channel resets can be enforced. + * + * @bam_arg - pointer to BAM device descriptor + * + * @pipe_index - pipe index + * + * @return 0 on success, negative value on error + * + */ +int sps_dma_pipe_free(void *bam, u32 pipe_index); + +/** + * Initialize driver memory module + * + * This function initializes the driver memory module. + * + * @pipemem_phys_base - Pipe-Memory physical base. + * + * @pipemem_size - Pipe-Memory size. + * + * @return 0 on success, negative value on error + * + */ +int sps_mem_init(u32 pipemem_phys_base, u32 pipemem_size); + +/** + * De-initialize driver memory module + * + * This function de-initializes the driver memory module. + * + * @return 0 on success, negative value on error + * + */ +int sps_mem_de_init(void); + +/** + * Initialize BAM DMA module + * + * This function initializes the BAM DMA module. + * + * @bam_props - pointer to BAM DMA devices BSP configuration properties + * + * @return 0 on success, negative value on error + * + */ +int sps_dma_init(const struct sps_bam_props *bam_props); + +/** + * De-initialize BAM DMA module + * + * This function de-initializes the SPS BAM DMA module. + * + */ +void sps_dma_de_init(void); + +/** + * Initialize BAM DMA device + * + * This function initializes a BAM DMA device. + * + * @h - BAM handle + * + * @return 0 on success, negative value on error + * + */ +int sps_dma_device_init(u32 h); + +/** + * De-initialize BAM DMA device + * + * This function de-initializes a BAM DMA device. + * + * @h - BAM handle + * + * @return 0 on success, negative value on error + * + */ +int sps_dma_device_de_init(u32 h); + +/** + * Initialize connection mapping module + * + * This function initializes the SPS connection mapping module. + * + * @map_props - pointer to connection mapping BSP configuration properties + * + * @options - driver options bitflags (see SPS_OPT_*) + * + * @return 0 on success, negative value on error + * + */ + +int sps_map_init(const struct sps_map *map_props, u32 options); + +/** + * De-initialize connection mapping module + * + * This function de-initializes the SPS connection mapping module. + * + */ +void sps_map_de_init(void); + +#endif /* _SPSI_H_ */ diff --git a/drivers/platform/msm/ssbi.c b/drivers/platform/msm/ssbi.c new file mode 100644 index 00000000000..b4fd02e1f6d --- /dev/null +++ b/drivers/platform/msm/ssbi.c @@ -0,0 +1,396 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2010, Google Inc. + * + * Original authors: Code Aurora Forum + * + * Author: Dima Zavin + * - Largely rewritten from original to not be an i2c driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +/* SSBI 2.0 controller registers */ +#define SSBI2_CMD 0x0008 +#define SSBI2_RD 0x0010 +#define SSBI2_STATUS 0x0014 +#define SSBI2_MODE2 0x001C + +/* SSBI_CMD fields */ +#define SSBI_CMD_RDWRN (1 << 24) + +/* SSBI_STATUS fields */ +#define SSBI_STATUS_RD_READY (1 << 2) +#define SSBI_STATUS_READY (1 << 1) +#define SSBI_STATUS_MCHN_BUSY (1 << 0) + +/* SSBI_MODE2 fields */ +#define SSBI_MODE2_REG_ADDR_15_8_SHFT 0x04 +#define SSBI_MODE2_REG_ADDR_15_8_MASK (0x7f << SSBI_MODE2_REG_ADDR_15_8_SHFT) + +#define SET_SSBI_MODE2_REG_ADDR_15_8(MD, AD) \ + (((MD) & 0x0F) | ((((AD) >> 8) << SSBI_MODE2_REG_ADDR_15_8_SHFT) & \ + SSBI_MODE2_REG_ADDR_15_8_MASK)) + +/* SSBI PMIC Arbiter command registers */ +#define SSBI_PA_CMD 0x0000 +#define SSBI_PA_RD_STATUS 0x0004 + +/* SSBI_PA_CMD fields */ +#define SSBI_PA_CMD_RDWRN (1 << 24) +#define SSBI_PA_CMD_ADDR_MASK 0x7fff /* REG_ADDR_7_0, REG_ADDR_8_14*/ + +/* SSBI_PA_RD_STATUS fields */ +#define SSBI_PA_RD_STATUS_TRANS_DONE (1 << 27) +#define SSBI_PA_RD_STATUS_TRANS_DENIED (1 << 26) + +#define SSBI_TIMEOUT_US 100 + +struct msm_ssbi { + struct device *dev; + struct device *slave; + void __iomem *base; + spinlock_t lock; + enum msm_ssbi_controller_type controller_type; + int (*read)(struct msm_ssbi *, u16 addr, u8 *buf, int len); + int (*write)(struct msm_ssbi *, u16 addr, u8 *buf, int len); +}; + +#define to_msm_ssbi(dev) platform_get_drvdata(to_platform_device(dev)) + +static inline u32 ssbi_readl(struct msm_ssbi *ssbi, u32 reg) +{ + return readl(ssbi->base + reg); +} + +static inline void ssbi_writel(struct msm_ssbi *ssbi, u32 val, u32 reg) +{ + writel(val, ssbi->base + reg); +} + +static int ssbi_wait_mask(struct msm_ssbi *ssbi, u32 set_mask, u32 clr_mask) +{ + u32 timeout = SSBI_TIMEOUT_US; + u32 val; + + while (timeout--) { + val = ssbi_readl(ssbi, SSBI2_STATUS); + if (((val & set_mask) == set_mask) && ((val & clr_mask) == 0)) + return 0; + udelay(1); + } + + dev_err(ssbi->dev, "%s: timeout (status %x set_mask %x clr_mask %x)\n", + __func__, ssbi_readl(ssbi, SSBI2_STATUS), set_mask, clr_mask); + return -ETIMEDOUT; +} + +static int +msm_ssbi_read_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + u32 cmd = SSBI_CMD_RDWRN | ((addr & 0xff) << 16); + int ret = 0; + + if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { + u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); + mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); + ssbi_writel(ssbi, mode2, SSBI2_MODE2); + } + + while (len) { + ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); + if (ret) + goto err; + + ssbi_writel(ssbi, cmd, SSBI2_CMD); + ret = ssbi_wait_mask(ssbi, SSBI_STATUS_RD_READY, 0); + if (ret) + goto err; + *buf++ = ssbi_readl(ssbi, SSBI2_RD) & 0xff; + len--; + } + +err: + return ret; +} + +static int +msm_ssbi_write_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + int ret = 0; + + if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { + u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); + mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); + ssbi_writel(ssbi, mode2, SSBI2_MODE2); + } + + while (len) { + ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); + if (ret) + goto err; + + ssbi_writel(ssbi, ((addr & 0xff) << 16) | *buf, SSBI2_CMD); + ret = ssbi_wait_mask(ssbi, 0, SSBI_STATUS_MCHN_BUSY); + if (ret) + goto err; + buf++; + len--; + } + +err: + return ret; +} + +static inline int +msm_ssbi_pa_transfer(struct msm_ssbi *ssbi, u32 cmd, u8 *data) +{ + u32 timeout = SSBI_TIMEOUT_US; + u32 rd_status = 0; + + ssbi_writel(ssbi, cmd, SSBI_PA_CMD); + + while (timeout--) { + rd_status = ssbi_readl(ssbi, SSBI_PA_RD_STATUS); + + if (rd_status & SSBI_PA_RD_STATUS_TRANS_DENIED) { + dev_err(ssbi->dev, "%s: transaction denied (0x%x)\n", + __func__, rd_status); + return -EPERM; + } + + if (rd_status & SSBI_PA_RD_STATUS_TRANS_DONE) { + if (data) + *data = rd_status & 0xff; + return 0; + } + udelay(1); + } + + dev_err(ssbi->dev, "%s: timeout, status 0x%x\n", __func__, rd_status); + return -ETIMEDOUT; +} + +static int +msm_ssbi_pa_read_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + u32 cmd; + int ret = 0; + + cmd = SSBI_PA_CMD_RDWRN | (addr & SSBI_PA_CMD_ADDR_MASK) << 8; + + while (len) { + ret = msm_ssbi_pa_transfer(ssbi, cmd, buf); + if (ret) + goto err; + buf++; + len--; + } + +err: + return ret; +} + +static int +msm_ssbi_pa_write_bytes(struct msm_ssbi *ssbi, u16 addr, u8 *buf, int len) +{ + u32 cmd; + int ret = 0; + + while (len) { + cmd = (addr & SSBI_PA_CMD_ADDR_MASK) << 8 | *buf; + ret = msm_ssbi_pa_transfer(ssbi, cmd, NULL); + if (ret) + goto err; + buf++; + len--; + } + +err: + return ret; +} + +int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) +{ + struct msm_ssbi *ssbi = to_msm_ssbi(dev); + unsigned long flags; + int ret; + + if (ssbi->dev != dev) + return -ENXIO; + + spin_lock_irqsave(&ssbi->lock, flags); + ret = ssbi->read(ssbi, addr, buf, len); + spin_unlock_irqrestore(&ssbi->lock, flags); + + return ret; +} +EXPORT_SYMBOL(msm_ssbi_read); + +int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len) +{ + struct msm_ssbi *ssbi = to_msm_ssbi(dev); + unsigned long flags; + int ret; + + if (ssbi->dev != dev) + return -ENXIO; + + spin_lock_irqsave(&ssbi->lock, flags); + ret = ssbi->write(ssbi, addr, buf, len); + spin_unlock_irqrestore(&ssbi->lock, flags); + + return ret; +} +EXPORT_SYMBOL(msm_ssbi_write); + +static int __devinit msm_ssbi_add_slave(struct msm_ssbi *ssbi, + const struct msm_ssbi_slave_info *slave) +{ + struct platform_device *slave_pdev; + int ret; + + if (ssbi->slave) { + pr_err("slave already attached??\n"); + return -EBUSY; + } + + slave_pdev = platform_device_alloc(slave->name, -1); + if (!slave_pdev) { + pr_err("cannot allocate pdev for slave '%s'", slave->name); + ret = -ENOMEM; + goto err; + } + + slave_pdev->dev.parent = ssbi->dev; + slave_pdev->dev.platform_data = slave->platform_data; + + ret = platform_device_add(slave_pdev); + if (ret) { + pr_err("cannot add slave platform device for '%s'\n", + slave->name); + goto err; + } + + ssbi->slave = &slave_pdev->dev; + return 0; + +err: + if (slave_pdev) + platform_device_put(slave_pdev); + return ret; +} + +static int __devinit msm_ssbi_probe(struct platform_device *pdev) +{ + const struct msm_ssbi_platform_data *pdata = pdev->dev.platform_data; + struct resource *mem_res; + struct msm_ssbi *ssbi; + int ret = 0; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + pr_debug("%s\n", pdata->slave.name); + + ssbi = kzalloc(sizeof(struct msm_ssbi), GFP_KERNEL); + if (!ssbi) { + pr_err("can not allocate ssbi_data\n"); + return -ENOMEM; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + pr_err("missing mem resource\n"); + ret = -EINVAL; + goto err_get_mem_res; + } + + ssbi->base = ioremap(mem_res->start, resource_size(mem_res)); + if (!ssbi->base) { + pr_err("ioremap of 0x%p failed\n", (void *)mem_res->start); + ret = -EINVAL; + goto err_ioremap; + } + ssbi->dev = &pdev->dev; + platform_set_drvdata(pdev, ssbi); + + ssbi->controller_type = pdata->controller_type; + if (ssbi->controller_type == MSM_SBI_CTRL_PMIC_ARBITER) { + ssbi->read = msm_ssbi_pa_read_bytes; + ssbi->write = msm_ssbi_pa_write_bytes; + } else { + ssbi->read = msm_ssbi_read_bytes; + ssbi->write = msm_ssbi_write_bytes; + } + + spin_lock_init(&ssbi->lock); + + ret = msm_ssbi_add_slave(ssbi, &pdata->slave); + if (ret) + goto err_ssbi_add_slave; + + return 0; + +err_ssbi_add_slave: + platform_set_drvdata(pdev, NULL); + iounmap(ssbi->base); +err_ioremap: +err_get_mem_res: + kfree(ssbi); + return ret; +} + +static int __devexit msm_ssbi_remove(struct platform_device *pdev) +{ + struct msm_ssbi *ssbi = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + iounmap(ssbi->base); + kfree(ssbi); + return 0; +} + +static struct platform_driver msm_ssbi_driver = { + .probe = msm_ssbi_probe, + .remove = __exit_p(msm_ssbi_remove), + .driver = { + .name = "msm_ssbi", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_ssbi_init(void) +{ + return platform_driver_register(&msm_ssbi_driver); +} +postcore_initcall(msm_ssbi_init); + +static void __exit msm_ssbi_exit(void) +{ + platform_driver_unregister(&msm_ssbi_driver); +} +module_exit(msm_ssbi_exit) + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:msm_ssbi"); +MODULE_AUTHOR("Dima Zavin "); diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index e57b50b3856..179a4ac0690 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -235,4 +235,106 @@ config CHARGER_GPIO This driver can be build as a module. If so, the module will be called gpio-charger. +config BATTERY_MSM + tristate "MSM battery" + depends on ARCH_MSM + default m + help + Say Y to enable support for the battery in Qualcomm MSM. + +config BATTERY_MSM8X60 + tristate "MSM8X60 battery" + select PMIC8058_BATTALARM + help + Some MSM boards have dual charging paths to charge the battery. + Say Y to enable support for the battery charging in + such devices. + +config PM8058_CHARGER + tristate "pmic8058 charger" + depends on BATTERY_MSM8X60 + depends on PMIC8058 + help + Say Y to enable support for battery charging from the pmic8058. + pmic8058 provides a linear charging circuit connected to the usb + cable on Qualcomm's msm8x60 surf board. + +config ISL9519_CHARGER + tristate "isl9519 charger" + depends on BATTERY_MSM8X60 + default n + help + The isl9519q charger chip from intersil is connected to an external + charger cable and is preferred way of charging the battery because + of its high current rating. + Choose Y if you are compiling for Qualcomm's msm8x60 surf/ffa board. + +config SMB137B_CHARGER + tristate "smb137b charger" + default n + depends on I2C + help + The smb137b charger chip from summit is a switching mode based + charging solution. + Choose Y if you are compiling for Qualcomm's msm8x60 fluid board. + To compile this driver as a module, choose M here: the module will + be called smb137b. + +config BATTERY_MSM_FAKE + tristate "Fake MSM battery" + depends on ARCH_MSM && BATTERY_MSM + default n + help + Say Y to bypass actual battery queries. + +config PM8058_FIX_USB + tristate "pmic8058 software workaround for usb removal" + depends on PMIC8058 + depends on !PM8058_CHARGER + help + Say Y to enable the software workaround to USB Vbus line + staying high even when USB cable is removed. This option + is in lieu of a complete pm8058 charging driver. + +config BATTERY_QCIBAT + tristate "Quanta Computer Inc. Battery" + depends on SENSORS_WPCE775X + default n + help + Say Y here if you want to use the Quanta battery driver for ST15 + platform. + +config BATTERY_BQ27520 + tristate "BQ27520 battery driver" + depends on I2C + default n + help + Say Y here to enable support for batteries with BQ27520 (I2C) chips. + +config BATTERY_BQ27541 + tristate "BQ27541 battery driver" + depends on I2C + default n + help + Say Y here to enable support for batteries with BQ27541 (I2C) chips. + +config BQ27520_TEST_ENABLE + bool "Enable BQ27520 Fuel Gauge Chip Test" + depends on BATTERY_BQ27520 + default n + help + Say Y here to enable Test sysfs Interface for BQ27520 Drivers. + +config PM8921_CHARGER + tristate "PM8921 Charger driver" + depends on MFD_PM8921_CORE + help + Say Y here to enable support for pm8921 chip charger subdevice + +config PM8921_BMS + tristate "PM8921 Battery Monitoring System driver" + depends on MFD_PM8921_CORE + help + Say Y here to enable support for pm8921 chip bms subdevice + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 009a90fa8ac..f61c88a4d94 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -36,3 +36,14 @@ obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o +obj-$(CONFIG_BATTERY_MSM) += msm_battery.o +obj-$(CONFIG_BATTERY_MSM8X60) += msm_charger.o +obj-$(CONFIG_PM8058_CHARGER) += pmic8058-charger.o +obj-$(CONFIG_ISL9519_CHARGER) += isl9519q.o +obj-$(CONFIG_PM8058_FIX_USB) += pm8058_usb_fix.o +obj-$(CONFIG_BATTERY_QCIBAT) += qci_battery.o +obj-$(CONFIG_BATTERY_BQ27520) += bq27520_fuelgauger.o +obj-$(CONFIG_BATTERY_BQ27541) += bq27541_fuelgauger.o +obj-$(CONFIG_SMB137B_CHARGER) += smb137b.o +obj-$(CONFIG_PM8921_BMS) += pm8921-bms.o +obj-$(CONFIG_PM8921_CHARGER) += pm8921-charger.o diff --git a/drivers/power/bq27520_fuelgauger.c b/drivers/power/bq27520_fuelgauger.c new file mode 100644 index 00000000000..284b134ddd8 --- /dev/null +++ b/drivers/power/bq27520_fuelgauger.c @@ -0,0 +1,960 @@ +/* Copyright (C) 2008 Rodolfo Giometti + * Copyright (C) 2008 Eurotech S.p.A. + * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_VERSION "1.1.0" +/* Bq27520 standard data commands */ +#define BQ27520_REG_CNTL 0x00 +#define BQ27520_REG_AR 0x02 +#define BQ27520_REG_ARTTE 0x04 +#define BQ27520_REG_TEMP 0x06 +#define BQ27520_REG_VOLT 0x08 +#define BQ27520_REG_FLAGS 0x0A +#define BQ27520_REG_NAC 0x0C +#define BQ27520_REG_FAC 0x0e +#define BQ27520_REG_RM 0x10 +#define BQ27520_REG_FCC 0x12 +#define BQ27520_REG_AI 0x14 +#define BQ27520_REG_TTE 0x16 +#define BQ27520_REG_TTF 0x18 +#define BQ27520_REG_SI 0x1a +#define BQ27520_REG_STTE 0x1c +#define BQ27520_REG_MLI 0x1e +#define BQ27520_REG_MLTTE 0x20 +#define BQ27520_REG_AE 0x22 +#define BQ27520_REG_AP 0x24 +#define BQ27520_REG_TTECP 0x26 +#define BQ27520_REG_SOH 0x28 +#define BQ27520_REG_SOC 0x2c +#define BQ27520_REG_NIC 0x2e +#define BQ27520_REG_ICR 0x30 +#define BQ27520_REG_LOGIDX 0x32 +#define BQ27520_REG_LOGBUF 0x34 +#define BQ27520_FLAG_DSC BIT(0) +#define BQ27520_FLAG_FC BIT(9) +#define BQ27520_FLAG_BAT_DET BIT(3) +#define BQ27520_CS_DLOGEN BIT(15) +#define BQ27520_CS_SS BIT(13) +/* Control subcommands */ +#define BQ27520_SUBCMD_CTNL_STATUS 0x0000 +#define BQ27520_SUBCMD_DEVCIE_TYPE 0x0001 +#define BQ27520_SUBCMD_FW_VER 0x0002 +#define BQ27520_SUBCMD_HW_VER 0x0003 +#define BQ27520_SUBCMD_DF_CSUM 0x0004 +#define BQ27520_SUBCMD_PREV_MACW 0x0007 +#define BQ27520_SUBCMD_CHEM_ID 0x0008 +#define BQ27520_SUBCMD_BD_OFFSET 0x0009 +#define BQ27520_SUBCMD_INT_OFFSET 0x000a +#define BQ27520_SUBCMD_CC_VER 0x000b +#define BQ27520_SUBCMD_OCV 0x000c +#define BQ27520_SUBCMD_BAT_INS 0x000d +#define BQ27520_SUBCMD_BAT_REM 0x000e +#define BQ27520_SUBCMD_SET_HIB 0x0011 +#define BQ27520_SUBCMD_CLR_HIB 0x0012 +#define BQ27520_SUBCMD_SET_SLP 0x0013 +#define BQ27520_SUBCMD_CLR_SLP 0x0014 +#define BQ27520_SUBCMD_FCT_RES 0x0015 +#define BQ27520_SUBCMD_ENABLE_DLOG 0x0018 +#define BQ27520_SUBCMD_DISABLE_DLOG 0x0019 +#define BQ27520_SUBCMD_SEALED 0x0020 +#define BQ27520_SUBCMD_ENABLE_IT 0x0021 +#define BQ27520_SUBCMD_DISABLE_IT 0x0023 +#define BQ27520_SUBCMD_CAL_MODE 0x0040 +#define BQ27520_SUBCMD_RESET 0x0041 + +#define ZERO_DEGREE_CELSIUS_IN_TENTH_KELVIN (-2731) +#define BQ27520_INIT_DELAY ((HZ)*1) +#define BQ27520_POLLING_STATUS ((HZ)*3) +#define BQ27520_COULOMB_POLL ((HZ)*30) + +/* If the system has several batteries we need a different name for each + * of them... + */ +static DEFINE_IDR(battery_id); +static DEFINE_MUTEX(battery_mutex); + +struct bq27520_device_info; +struct bq27520_access_methods { + int (*read)(u8 reg, int *rt_value, int b_single, + struct bq27520_device_info *di); +}; + +struct bq27520_device_info { + struct device *dev; + int id; + struct bq27520_access_methods *bus; + struct i2c_client *client; + const struct bq27520_platform_data *pdata; + struct work_struct counter; + /* 300ms delay is needed after bq27520 is powered up + * and before any successful I2C transaction + */ + struct delayed_work hw_config; + uint32_t irq; +}; + +enum { + GET_BATTERY_STATUS, + GET_BATTERY_TEMPERATURE, + GET_BATTERY_VOLTAGE, + GET_BATTERY_CAPACITY, + NUM_OF_STATUS, +}; + +struct bq27520_status { + /* Informations owned and maintained by Bq27520 driver, updated + * by poller or SOC_INT interrupt, decoupling from I/Oing + * hardware directly + */ + int status[NUM_OF_STATUS]; + spinlock_t lock; + struct delayed_work poller; +}; + +static struct bq27520_status current_battery_status; +static struct bq27520_device_info *bq27520_di; +static int coulomb_counter; +static spinlock_t lock; /* protect access to coulomb_counter */ +static struct timer_list timer; /* charge counter timer every 30 secs */ + +static int bq27520_i2c_txsubcmd(u8 reg, unsigned short subcmd, + struct bq27520_device_info *di); + +static int bq27520_read(u8 reg, int *rt_value, int b_single, + struct bq27520_device_info *di) +{ + return di->bus->read(reg, rt_value, b_single, di); +} + +/* + * Return the battery temperature in tenths of degree Celsius + * Or < 0 if something fails. + */ +static int bq27520_battery_temperature(struct bq27520_device_info *di) +{ + int ret, temp = 0; + + ret = bq27520_read(BQ27520_REG_TEMP, &temp, 0, di); + if (ret) { + dev_err(di->dev, "error %d reading temperature\n", ret); + return ret; + } + + return temp + ZERO_DEGREE_CELSIUS_IN_TENTH_KELVIN; +} + +/* + * Return the battery Voltage in milivolts + * Or < 0 if something fails. + */ +static int bq27520_battery_voltage(struct bq27520_device_info *di) +{ + int ret, volt = 0; + + ret = bq27520_read(BQ27520_REG_VOLT, &volt, 0, di); + if (ret) { + dev_err(di->dev, "error %d reading voltage\n", ret); + return ret; + } + + return volt; +} + +/* + * Return the battery Relative State-of-Charge + * Or < 0 if something fails. + */ +static int bq27520_battery_rsoc(struct bq27520_device_info *di) +{ + int ret, rsoc = 0; + + ret = bq27520_read(BQ27520_REG_SOC, &rsoc, 0, di); + + if (ret) { + dev_err(di->dev, + "error %d reading relative State-of-Charge\n", ret); + return ret; + } + + return rsoc; +} + +static void bq27520_cntl_cmd(struct bq27520_device_info *di, + int subcmd) +{ + bq27520_i2c_txsubcmd(BQ27520_REG_CNTL, subcmd, di); +} + +/* + * i2c specific code + */ +static int bq27520_i2c_txsubcmd(u8 reg, unsigned short subcmd, + struct bq27520_device_info *di) +{ + struct i2c_msg msg; + unsigned char data[3]; + + if (!di->client) + return -ENODEV; + + memset(data, 0, sizeof(data)); + data[0] = reg; + data[1] = subcmd & 0x00FF; + data[2] = (subcmd & 0xFF00) >> 8; + + msg.addr = di->client->addr; + msg.flags = 0; + msg.len = 3; + msg.buf = data; + + if (i2c_transfer(di->client->adapter, &msg, 1) < 0) + return -EIO; + + return 0; +} + +static int bq27520_chip_config(struct bq27520_device_info *di) +{ + int flags = 0, ret = 0; + + bq27520_cntl_cmd(di, BQ27520_SUBCMD_CTNL_STATUS); + udelay(66); + ret = bq27520_read(BQ27520_REG_CNTL, &flags, 0, di); + if (ret < 0) { + dev_err(di->dev, "error %d reading register %02x\n", + ret, BQ27520_REG_CNTL); + return ret; + } + udelay(66); + + bq27520_cntl_cmd(di, BQ27520_SUBCMD_ENABLE_IT); + udelay(66); + + if (di->pdata->enable_dlog && !(flags & BQ27520_CS_DLOGEN)) { + bq27520_cntl_cmd(di, BQ27520_SUBCMD_ENABLE_DLOG); + udelay(66); + } + + return 0; +} + +static void bq27520_every_30secs(unsigned long data) +{ + struct bq27520_device_info *di = (struct bq27520_device_info *)data; + + schedule_work(&di->counter); + mod_timer(&timer, jiffies + BQ27520_COULOMB_POLL); +} + +static void bq27520_coulomb_counter_work(struct work_struct *work) +{ + int value = 0, temp = 0, index = 0, ret = 0, count = 0; + struct bq27520_device_info *di; + unsigned long flags; + + di = container_of(work, struct bq27520_device_info, counter); + + /* retrieve 30 values from FIFO of coulomb data logging buffer + * and average over time + */ + do { + ret = bq27520_read(BQ27520_REG_LOGBUF, &temp, 0, di); + if (ret < 0) + break; + if (temp != 0x7FFF) { + ++count; + value += temp; + } + udelay(66); + ret = bq27520_read(BQ27520_REG_LOGIDX, &index, 0, di); + if (ret < 0) + break; + udelay(66); + } while (index != 0 || temp != 0x7FFF); + + if (ret < 0) { + dev_err(di->dev, "Error %d reading datalog register\n", ret); + return; + } + + if (count) { + spin_lock_irqsave(&lock, flags); + coulomb_counter = value/count; + spin_unlock_irqrestore(&lock, flags); + } +} + +static int bq27520_is_battery_present(void) +{ + return 1; +} + +static int bq27520_is_battery_temp_within_range(void) +{ + return 1; +} + +static int bq27520_is_battery_id_valid(void) +{ + return 1; +} + +static int bq27520_status_getter(int function) +{ + int status = 0; + unsigned long flags; + + spin_lock_irqsave(¤t_battery_status.lock, flags); + status = current_battery_status.status[function]; + spin_unlock_irqrestore(¤t_battery_status.lock, flags); + + return status; +} + +static int bq27520_get_battery_mvolts(void) +{ + return bq27520_status_getter(GET_BATTERY_VOLTAGE); +} + +static int bq27520_get_battery_temperature(void) +{ + return bq27520_status_getter(GET_BATTERY_TEMPERATURE); +} + +static int bq27520_get_battery_status(void) +{ + return bq27520_status_getter(GET_BATTERY_STATUS); +} + +static int bq27520_get_remaining_capacity(void) +{ + return bq27520_status_getter(GET_BATTERY_CAPACITY); +} + +static struct msm_battery_gauge bq27520_batt_gauge = { + .get_battery_mvolts = bq27520_get_battery_mvolts, + .get_battery_temperature = bq27520_get_battery_temperature, + .is_battery_present = bq27520_is_battery_present, + .is_battery_temp_within_range = bq27520_is_battery_temp_within_range, + .is_battery_id_valid = bq27520_is_battery_id_valid, + .get_battery_status = bq27520_get_battery_status, + .get_batt_remaining_capacity = bq27520_get_remaining_capacity, +}; + +static void update_current_battery_status(int data) +{ + int status[4], ret = 0; + unsigned long flag; + + memset(status, 0, sizeof status); + ret = bq27520_battery_rsoc(bq27520_di); + status[GET_BATTERY_CAPACITY] = (ret < 0) ? 0 : ret; + + status[GET_BATTERY_VOLTAGE] = bq27520_battery_voltage(bq27520_di); + status[GET_BATTERY_TEMPERATURE] = + bq27520_battery_temperature(bq27520_di); + + spin_lock_irqsave(¤t_battery_status.lock, flag); + current_battery_status.status[GET_BATTERY_STATUS] = data; + current_battery_status.status[GET_BATTERY_VOLTAGE] = + status[GET_BATTERY_VOLTAGE]; + current_battery_status.status[GET_BATTERY_TEMPERATURE] = + status[GET_BATTERY_TEMPERATURE]; + current_battery_status.status[GET_BATTERY_CAPACITY] = + status[GET_BATTERY_CAPACITY]; + spin_unlock_irqrestore(¤t_battery_status.lock, flag); +} + +/* only if battery charging satus changes then notify msm_charger. otherwise + * only refresh current_batter_status + */ +static int if_notify_msm_charger(int *data) +{ + int ret = 0, flags = 0, status = 0; + unsigned long flag; + + ret = bq27520_read(BQ27520_REG_FLAGS, &flags, 0, bq27520_di); + if (ret < 0) { + dev_err(bq27520_di->dev, "error %d reading register %02x\n", + ret, BQ27520_REG_FLAGS); + status = POWER_SUPPLY_STATUS_UNKNOWN; + } else { + if (flags & BQ27520_FLAG_FC) + status = POWER_SUPPLY_STATUS_FULL; + else if (flags & BQ27520_FLAG_DSC) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; + } + + *data = status; + spin_lock_irqsave(¤t_battery_status.lock, flag); + ret = (status != current_battery_status.status[GET_BATTERY_STATUS]); + spin_unlock_irqrestore(¤t_battery_status.lock, flag); + return ret; +} + +static void battery_status_poller(struct work_struct *work) +{ + int status = 0, temp = 0; + + temp = if_notify_msm_charger(&status); + update_current_battery_status(status); + if (temp) + msm_charger_notify_event(NULL, CHG_BATT_STATUS_CHANGE); + + schedule_delayed_work(¤t_battery_status.poller, + BQ27520_POLLING_STATUS); +} + +static void bq27520_hw_config(struct work_struct *work) +{ + int ret = 0, flags = 0, type = 0, fw_ver = 0, status = 0; + struct bq27520_device_info *di; + + di = container_of(work, struct bq27520_device_info, hw_config.work); + + pr_debug(KERN_INFO "Enter bq27520_hw_config\n"); + ret = bq27520_chip_config(di); + if (ret) { + dev_err(di->dev, "Failed to config Bq27520 ret = %d\n", ret); + return; + } + /* bq27520 is ready for access, update current_battery_status by reading + * from hardware + */ + if_notify_msm_charger(&status); + update_current_battery_status(status); + msm_charger_notify_event(NULL, CHG_BATT_STATUS_CHANGE); + + enable_irq(di->irq); + + /* poll battery status every 3 seconds, if charging status changes, + * notify msm_charger + */ + schedule_delayed_work(¤t_battery_status.poller, + BQ27520_POLLING_STATUS); + + if (di->pdata->enable_dlog) { + schedule_work(&di->counter); + init_timer(&timer); + timer.function = &bq27520_every_30secs; + timer.data = (unsigned long)di; + timer.expires = jiffies + BQ27520_COULOMB_POLL; + add_timer(&timer); + } + + bq27520_cntl_cmd(di, BQ27520_SUBCMD_CTNL_STATUS); + udelay(66); + bq27520_read(BQ27520_REG_CNTL, &flags, 0, di); + bq27520_cntl_cmd(di, BQ27520_SUBCMD_DEVCIE_TYPE); + udelay(66); + bq27520_read(BQ27520_REG_CNTL, &type, 0, di); + bq27520_cntl_cmd(di, BQ27520_SUBCMD_FW_VER); + udelay(66); + bq27520_read(BQ27520_REG_CNTL, &fw_ver, 0, di); + + dev_info(di->dev, "DEVICE_TYPE is 0x%02X, FIRMWARE_VERSION\ + is 0x%02X\n", type, fw_ver); + dev_info(di->dev, "Complete bq27520 configuration 0x%02X\n", flags); +} + +static int bq27520_read_i2c(u8 reg, int *rt_value, int b_single, + struct bq27520_device_info *di) +{ + struct i2c_client *client = di->client; + struct i2c_msg msg[1]; + unsigned char data[2]; + int err; + + if (!client->adapter) + return -ENODEV; + + msg->addr = client->addr; + msg->flags = 0; + msg->len = 1; + msg->buf = data; + + data[0] = reg; + err = i2c_transfer(client->adapter, msg, 1); + + if (err >= 0) { + if (!b_single) + msg->len = 2; + else + msg->len = 1; + + msg->flags = I2C_M_RD; + err = i2c_transfer(client->adapter, msg, 1); + if (err >= 0) { + if (!b_single) + *rt_value = get_unaligned_le16(data); + else + *rt_value = data[0]; + + return 0; + } + } + return err; +} + +#ifdef CONFIG_BQ27520_TEST_ENABLE +static int reg; +static int subcmd; +static ssize_t bq27520_read_stdcmd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + int temp = 0; + struct platform_device *client; + struct bq27520_device_info *di; + + client = to_platform_device(dev); + di = platform_get_drvdata(client); + + if (reg <= BQ27520_REG_ICR && reg > 0x00) { + ret = bq27520_read(reg, &temp, 0, di); + if (ret) + ret = snprintf(buf, PAGE_SIZE, "Read Error!\n"); + else + ret = snprintf(buf, PAGE_SIZE, "0x%02x\n", temp); + } else + ret = snprintf(buf, PAGE_SIZE, "Register Error!\n"); + + return ret; +} + +static ssize_t bq27520_write_stdcmd(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret = strnlen(buf, PAGE_SIZE); + int cmd; + + sscanf(buf, "%x", &cmd); + reg = cmd; + dev_info(dev, "recv'd cmd is 0x%02X\n", reg); + return ret; +} + +static ssize_t bq27520_read_subcmd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret, temp = 0; + struct platform_device *client; + struct bq27520_device_info *di; + + client = to_platform_device(dev); + di = platform_get_drvdata(client); + + if (subcmd == BQ27520_SUBCMD_DEVCIE_TYPE || + subcmd == BQ27520_SUBCMD_FW_VER || + subcmd == BQ27520_SUBCMD_HW_VER || + subcmd == BQ27520_SUBCMD_CHEM_ID) { + + bq27520_cntl_cmd(di, subcmd);/* Retrieve Chip status */ + udelay(66); + ret = bq27520_read(BQ27520_REG_CNTL, &temp, 0, di); + + if (ret) + ret = snprintf(buf, PAGE_SIZE, "Read Error!\n"); + else + ret = snprintf(buf, PAGE_SIZE, "0x%02x\n", temp); + } else + ret = snprintf(buf, PAGE_SIZE, "Register Error!\n"); + + return ret; +} + +static ssize_t bq27520_write_subcmd(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret = strnlen(buf, PAGE_SIZE); + int cmd; + + sscanf(buf, "%x", &cmd); + subcmd = cmd; + return ret; +} + +static DEVICE_ATTR(std_cmd, S_IRUGO|S_IWUGO, bq27520_read_stdcmd, + bq27520_write_stdcmd); +static DEVICE_ATTR(sub_cmd, S_IRUGO|S_IWUGO, bq27520_read_subcmd, + bq27520_write_subcmd); +static struct attribute *fs_attrs[] = { + &dev_attr_std_cmd.attr, + &dev_attr_sub_cmd.attr, + NULL, +}; +static struct attribute_group fs_attr_group = { + .attrs = fs_attrs, +}; + +static struct platform_device this_device = { + .name = "bq27520-test", + .id = -1, + .dev.platform_data = NULL, +}; +#endif + +static irqreturn_t soc_irqhandler(int irq, void *dev_id) +{ + int status = 0, temp = 0; + + temp = if_notify_msm_charger(&status); + update_current_battery_status(status); + if (temp) + msm_charger_notify_event(NULL, CHG_BATT_STATUS_CHANGE); + return IRQ_HANDLED; +} + +static struct regulator *vreg_bq27520; +static int bq27520_power(bool enable, struct bq27520_device_info *di) +{ + int rc = 0, ret; + const struct bq27520_platform_data *platdata; + + platdata = di->pdata; + if (enable) { + /* switch on Vreg_S3 */ + rc = regulator_enable(vreg_bq27520); + if (rc < 0) { + dev_err(di->dev, "%s: vreg %s %s failed (%d)\n", + __func__, platdata->vreg_name, "enable", rc); + goto vreg_fail; + } + + /* Battery gauge enable and switch on onchip 2.5V LDO */ + rc = gpio_request(platdata->chip_en, "GAUGE_EN"); + if (rc) { + dev_err(di->dev, "%s: fail to request gpio %d (%d)\n", + __func__, platdata->chip_en, rc); + goto vreg_fail; + } + + gpio_direction_output(platdata->chip_en, 0); + gpio_set_value(platdata->chip_en, 1); + rc = gpio_request(platdata->soc_int, "GAUGE_SOC_INT"); + if (rc) { + dev_err(di->dev, "%s: fail to request gpio %d (%d)\n", + __func__, platdata->soc_int, rc); + goto gpio_fail; + } + gpio_direction_input(platdata->soc_int); + di->irq = gpio_to_irq(platdata->soc_int); + rc = request_threaded_irq(di->irq, NULL, soc_irqhandler, + IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, + "BQ27520_IRQ", di); + if (rc) { + dev_err(di->dev, "%s: fail to request irq %d (%d)\n", + __func__, platdata->soc_int, rc); + goto irqreq_fail; + } else { + disable_irq_nosync(di->irq); + } + } else { + free_irq(di->irq, di); + gpio_free(platdata->soc_int); + /* switch off on-chip 2.5V LDO and disable Battery gauge */ + gpio_set_value(platdata->chip_en, 0); + gpio_free(platdata->chip_en); + /* switch off Vreg_S3 */ + rc = regulator_disable(vreg_bq27520); + if (rc < 0) { + dev_err(di->dev, "%s: vreg %s %s failed (%d)\n", + __func__, platdata->vreg_name, "disable", rc); + goto vreg_fail; + } + } + return rc; + +irqreq_fail: + gpio_free(platdata->soc_int); +gpio_fail: + gpio_set_value(platdata->chip_en, 0); + gpio_free(platdata->chip_en); +vreg_fail: + ret = !enable ? regulator_enable(vreg_bq27520) : + regulator_disable(vreg_bq27520); + if (ret < 0) { + dev_err(di->dev, "%s: vreg %s %s failed (%d) in err path\n", + __func__, platdata->vreg_name, + !enable ? "enable" : "disable", ret); + } + return rc; +} + +static int bq27520_dev_setup(bool enable, struct bq27520_device_info *di) +{ + int rc; + const struct bq27520_platform_data *platdata; + + platdata = di->pdata; + if (enable) { + /* enable and set voltage Vreg_S3 */ + vreg_bq27520 = regulator_get(NULL, + platdata->vreg_name); + if (IS_ERR(vreg_bq27520)) { + dev_err(di->dev, "%s: regulator get of %s\ + failed (%ld)\n", __func__, platdata->vreg_name, + PTR_ERR(vreg_bq27520)); + rc = PTR_ERR(vreg_bq27520); + goto vreg_get_fail; + } + rc = regulator_set_voltage(vreg_bq27520, + platdata->vreg_value, platdata->vreg_value); + if (rc) { + dev_err(di->dev, "%s: regulator_set_voltage(%s) failed\ + (%d)\n", __func__, platdata->vreg_name, rc); + goto vreg_get_fail; + } + } else { + regulator_put(vreg_bq27520); + } + return 0; + +vreg_get_fail: + regulator_put(vreg_bq27520); + return rc; +} + +static int bq27520_battery_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bq27520_device_info *di; + struct bq27520_access_methods *bus; + const struct bq27520_platform_data *pdata; + int num, retval = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + pdata = client->dev.platform_data; + + /* Get new ID for the new battery device */ + retval = idr_pre_get(&battery_id, GFP_KERNEL); + if (retval == 0) + return -ENOMEM; + mutex_lock(&battery_mutex); + retval = idr_get_new(&battery_id, client, &num); + mutex_unlock(&battery_mutex); + if (retval < 0) + return retval; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&client->dev, "failed to allocate device info data\n"); + retval = -ENOMEM; + goto batt_failed_1; + } + di->id = num; + di->pdata = pdata; + + bus = kzalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) { + dev_err(&client->dev, "failed to allocate data\n"); + retval = -ENOMEM; + goto batt_failed_2; + } + + i2c_set_clientdata(client, di); + di->dev = &client->dev; + bus->read = &bq27520_read_i2c; + di->bus = bus; + di->client = client; + +#ifdef CONFIG_BQ27520_TEST_ENABLE + platform_set_drvdata(&this_device, di); + retval = platform_device_register(&this_device); + if (!retval) { + retval = sysfs_create_group(&this_device.dev.kobj, + &fs_attr_group); + if (retval) + goto batt_failed_3; + } else + goto batt_failed_3; +#endif + + retval = bq27520_dev_setup(true, di); + if (retval) { + dev_err(&client->dev, "failed to setup ret = %d\n", retval); + goto batt_failed_3; + } + + retval = bq27520_power(true, di); + if (retval) { + dev_err(&client->dev, "failed to powerup ret = %d\n", retval); + goto batt_failed_3; + } + + spin_lock_init(&lock); + + bq27520_di = di; + if (pdata->enable_dlog) + INIT_WORK(&di->counter, bq27520_coulomb_counter_work); + + INIT_DELAYED_WORK(¤t_battery_status.poller, + battery_status_poller); + INIT_DELAYED_WORK(&di->hw_config, bq27520_hw_config); + schedule_delayed_work(&di->hw_config, BQ27520_INIT_DELAY); + + return 0; + +batt_failed_3: + kfree(bus); +batt_failed_2: + kfree(di); +batt_failed_1: + mutex_lock(&battery_mutex); + idr_remove(&battery_id, num); + mutex_unlock(&battery_mutex); + + return retval; +} + +static int bq27520_battery_remove(struct i2c_client *client) +{ + struct bq27520_device_info *di = i2c_get_clientdata(client); + + if (di->pdata->enable_dlog) { + del_timer_sync(&timer); + cancel_work_sync(&di->counter); + bq27520_cntl_cmd(di, BQ27520_SUBCMD_DISABLE_DLOG); + udelay(66); + } + + bq27520_cntl_cmd(di, BQ27520_SUBCMD_DISABLE_IT); + cancel_delayed_work_sync(&di->hw_config); + cancel_delayed_work_sync(¤t_battery_status.poller); + + bq27520_dev_setup(false, di); + bq27520_power(false, di); + + kfree(di->bus); + + mutex_lock(&battery_mutex); + idr_remove(&battery_id, di->id); + mutex_unlock(&battery_mutex); + + kfree(di); + return 0; +} + +#ifdef CONFIG_PM +static int bq27520_suspend(struct device *dev) +{ + struct bq27520_device_info *di = dev_get_drvdata(dev); + + disable_irq_nosync(di->irq); + if (di->pdata->enable_dlog) { + del_timer_sync(&timer); + cancel_work_sync(&di->counter); + } + + cancel_delayed_work_sync(¤t_battery_status.poller); + return 0; +} + +static int bq27520_resume(struct device *dev) +{ + struct bq27520_device_info *di = dev_get_drvdata(dev); + + enable_irq(di->irq); + if (di->pdata->enable_dlog) + add_timer(&timer); + + schedule_delayed_work(¤t_battery_status.poller, + BQ27520_POLLING_STATUS); + return 0; +} + +static const struct dev_pm_ops bq27520_pm_ops = { + .suspend = bq27520_suspend, + .resume = bq27520_resume, +}; +#endif + +static const struct i2c_device_id bq27520_id[] = { + { "bq27520", 1 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, BQ27520_id); + +static struct i2c_driver bq27520_battery_driver = { + .driver = { + .name = "bq27520-battery", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &bq27520_pm_ops, +#endif + }, + .probe = bq27520_battery_probe, + .remove = bq27520_battery_remove, + .id_table = bq27520_id, +}; + +static void init_battery_status(void) +{ + spin_lock_init(¤t_battery_status.lock); + current_battery_status.status[GET_BATTERY_STATUS] = + POWER_SUPPLY_STATUS_UNKNOWN; +} + +static int __init bq27520_battery_init(void) +{ + int ret; + + /* initialize current_battery_status, and register with msm-charger */ + init_battery_status(); + msm_battery_gauge_register(&bq27520_batt_gauge); + + ret = i2c_add_driver(&bq27520_battery_driver); + if (ret) + printk(KERN_ERR "Unable to register driver ret = %d\n", ret); + + return ret; +} +module_init(bq27520_battery_init); + +static void __exit bq27520_battery_exit(void) +{ + i2c_del_driver(&bq27520_battery_driver); + msm_battery_gauge_unregister(&bq27520_batt_gauge); +} +module_exit(bq27520_battery_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); +MODULE_DESCRIPTION("BQ27520 battery monitor driver"); diff --git a/drivers/power/bq27541_fuelgauger.c b/drivers/power/bq27541_fuelgauger.c new file mode 100644 index 00000000000..516a861dd4e --- /dev/null +++ b/drivers/power/bq27541_fuelgauger.c @@ -0,0 +1,623 @@ +/* Copyright (C) 2008 Rodolfo Giometti + * Copyright (C) 2008 Eurotech S.p.A. + * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. + * + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* use the same platform data as bq27520 */ + +#define DRIVER_VERSION "1.1.0" +/* Bq27541 standard data commands */ +#define BQ27541_REG_CNTL 0x00 +#define BQ27541_REG_AR 0x02 +#define BQ27541_REG_ARTTE 0x04 +#define BQ27541_REG_TEMP 0x06 +#define BQ27541_REG_VOLT 0x08 +#define BQ27541_REG_FLAGS 0x0A +#define BQ27541_REG_NAC 0x0C +#define BQ27541_REG_FAC 0x0e +#define BQ27541_REG_RM 0x10 +#define BQ27541_REG_FCC 0x12 +#define BQ27541_REG_AI 0x14 +#define BQ27541_REG_TTE 0x16 +#define BQ27541_REG_TTF 0x18 +#define BQ27541_REG_SI 0x1a +#define BQ27541_REG_STTE 0x1c +#define BQ27541_REG_MLI 0x1e +#define BQ27541_REG_MLTTE 0x20 +#define BQ27541_REG_AE 0x22 +#define BQ27541_REG_AP 0x24 +#define BQ27541_REG_TTECP 0x26 +#define BQ27541_REG_SOH 0x28 +#define BQ27541_REG_SOC 0x2c +#define BQ27541_REG_NIC 0x2e +#define BQ27541_REG_ICR 0x30 +#define BQ27541_REG_LOGIDX 0x32 +#define BQ27541_REG_LOGBUF 0x34 + +#define BQ27541_FLAG_DSC BIT(0) +#define BQ27541_FLAG_FC BIT(9) + +#define BQ27541_CS_DLOGEN BIT(15) +#define BQ27541_CS_SS BIT(13) + +/* Control subcommands */ +#define BQ27541_SUBCMD_CTNL_STATUS 0x0000 +#define BQ27541_SUBCMD_DEVCIE_TYPE 0x0001 +#define BQ27541_SUBCMD_FW_VER 0x0002 +#define BQ27541_SUBCMD_HW_VER 0x0003 +#define BQ27541_SUBCMD_DF_CSUM 0x0004 +#define BQ27541_SUBCMD_PREV_MACW 0x0007 +#define BQ27541_SUBCMD_CHEM_ID 0x0008 +#define BQ27541_SUBCMD_BD_OFFSET 0x0009 +#define BQ27541_SUBCMD_INT_OFFSET 0x000a +#define BQ27541_SUBCMD_CC_VER 0x000b +#define BQ27541_SUBCMD_OCV 0x000c +#define BQ27541_SUBCMD_BAT_INS 0x000d +#define BQ27541_SUBCMD_BAT_REM 0x000e +#define BQ27541_SUBCMD_SET_HIB 0x0011 +#define BQ27541_SUBCMD_CLR_HIB 0x0012 +#define BQ27541_SUBCMD_SET_SLP 0x0013 +#define BQ27541_SUBCMD_CLR_SLP 0x0014 +#define BQ27541_SUBCMD_FCT_RES 0x0015 +#define BQ27541_SUBCMD_ENABLE_DLOG 0x0018 +#define BQ27541_SUBCMD_DISABLE_DLOG 0x0019 +#define BQ27541_SUBCMD_SEALED 0x0020 +#define BQ27541_SUBCMD_ENABLE_IT 0x0021 +#define BQ27541_SUBCMD_DISABLE_IT 0x0023 +#define BQ27541_SUBCMD_CAL_MODE 0x0040 +#define BQ27541_SUBCMD_RESET 0x0041 +#define ZERO_DEGREE_CELSIUS_IN_TENTH_KELVIN (-2731) +#define BQ27541_INIT_DELAY ((HZ)*1) + +/* If the system has several batteries we need a different name for each + * of them... + */ +static DEFINE_IDR(battery_id); +static DEFINE_MUTEX(battery_mutex); + +struct bq27541_device_info; +struct bq27541_access_methods { + int (*read)(u8 reg, int *rt_value, int b_single, + struct bq27541_device_info *di); +}; + +struct bq27541_device_info { + struct device *dev; + int id; + struct bq27541_access_methods *bus; + struct i2c_client *client; + struct work_struct counter; + /* 300ms delay is needed after bq27541 is powered up + * and before any successful I2C transaction + */ + struct delayed_work hw_config; +}; + +static int coulomb_counter; +static spinlock_t lock; /* protect access to coulomb_counter */ + +static int bq27541_i2c_txsubcmd(u8 reg, unsigned short subcmd, + struct bq27541_device_info *di); + +static int bq27541_read(u8 reg, int *rt_value, int b_single, + struct bq27541_device_info *di) +{ + return di->bus->read(reg, rt_value, b_single, di); +} + +/* + * Return the battery temperature in tenths of degree Celsius + * Or < 0 if something fails. + */ +static int bq27541_battery_temperature(struct bq27541_device_info *di) +{ + int ret; + int temp = 0; + + ret = bq27541_read(BQ27541_REG_TEMP, &temp, 0, di); + if (ret) { + dev_err(di->dev, "error reading temperature\n"); + return ret; + } + + return temp + ZERO_DEGREE_CELSIUS_IN_TENTH_KELVIN; +} + +/* + * Return the battery Voltage in milivolts + * Or < 0 if something fails. + */ +static int bq27541_battery_voltage(struct bq27541_device_info *di) +{ + int ret; + int volt = 0; + + ret = bq27541_read(BQ27541_REG_VOLT, &volt, 0, di); + if (ret) { + dev_err(di->dev, "error reading voltage\n"); + return ret; + } + + return volt * 1000; +} + +static void bq27541_cntl_cmd(struct bq27541_device_info *di, + int subcmd) +{ + bq27541_i2c_txsubcmd(BQ27541_REG_CNTL, subcmd, di); +} + +/* + * i2c specific code + */ +static int bq27541_i2c_txsubcmd(u8 reg, unsigned short subcmd, + struct bq27541_device_info *di) +{ + struct i2c_msg msg; + unsigned char data[3]; + int ret; + + if (!di->client) + return -ENODEV; + + memset(data, 0, sizeof(data)); + data[0] = reg; + data[1] = subcmd & 0x00FF; + data[2] = (subcmd & 0xFF00) >> 8; + + msg.addr = di->client->addr; + msg.flags = 0; + msg.len = 3; + msg.buf = data; + + ret = i2c_transfer(di->client->adapter, &msg, 1); + if (ret < 0) + return -EIO; + + return 0; +} + +static int bq27541_chip_config(struct bq27541_device_info *di) +{ + int flags = 0, ret = 0; + + bq27541_cntl_cmd(di, BQ27541_SUBCMD_CTNL_STATUS); + udelay(66); + ret = bq27541_read(BQ27541_REG_CNTL, &flags, 0, di); + if (ret < 0) { + dev_err(di->dev, "error reading register %02x ret = %d\n", + BQ27541_REG_CNTL, ret); + return ret; + } + udelay(66); + + bq27541_cntl_cmd(di, BQ27541_SUBCMD_ENABLE_IT); + udelay(66); + + if (!(flags & BQ27541_CS_DLOGEN)) { + bq27541_cntl_cmd(di, BQ27541_SUBCMD_ENABLE_DLOG); + udelay(66); + } + + return 0; +} + +static void bq27541_coulomb_counter_work(struct work_struct *work) +{ + int value = 0, temp = 0, index = 0, ret = 0; + struct bq27541_device_info *di; + unsigned long flags; + int count = 0; + + di = container_of(work, struct bq27541_device_info, counter); + + /* retrieve 30 values from FIFO of coulomb data logging buffer + * and average over time + */ + do { + ret = bq27541_read(BQ27541_REG_LOGBUF, &temp, 0, di); + if (ret < 0) + break; + if (temp != 0x7FFF) { + ++count; + value += temp; + } + /* delay 66uS, waiting time between continuous reading + * results + */ + udelay(66); + ret = bq27541_read(BQ27541_REG_LOGIDX, &index, 0, di); + if (ret < 0) + break; + udelay(66); + } while (index != 0 || temp != 0x7FFF); + + if (ret < 0) { + dev_err(di->dev, "Error reading datalog register\n"); + return; + } + + if (count) { + spin_lock_irqsave(&lock, flags); + coulomb_counter = value/count; + spin_unlock_irqrestore(&lock, flags); + } +} + +struct bq27541_device_info *bq27541_di; + +static int bq27541_get_battery_mvolts(void) +{ + return bq27541_battery_voltage(bq27541_di); +} + +static int bq27541_get_battery_temperature(void) +{ + return bq27541_battery_temperature(bq27541_di); +} +static int bq27541_is_battery_present(void) +{ + return 1; +} +static int bq27541_is_battery_temp_within_range(void) +{ + return 1; +} +static int bq27541_is_battery_id_valid(void) +{ + return 1; +} + +static struct msm_battery_gauge bq27541_batt_gauge = { + .get_battery_mvolts = bq27541_get_battery_mvolts, + .get_battery_temperature = bq27541_get_battery_temperature, + .is_battery_present = bq27541_is_battery_present, + .is_battery_temp_within_range = bq27541_is_battery_temp_within_range, + .is_battery_id_valid = bq27541_is_battery_id_valid, +}; +static void bq27541_hw_config(struct work_struct *work) +{ + int ret = 0, flags = 0, type = 0, fw_ver = 0; + struct bq27541_device_info *di; + + di = container_of(work, struct bq27541_device_info, hw_config.work); + ret = bq27541_chip_config(di); + if (ret) { + dev_err(di->dev, "Failed to config Bq27541\n"); + return; + } + msm_battery_gauge_register(&bq27541_batt_gauge); + + bq27541_cntl_cmd(di, BQ27541_SUBCMD_CTNL_STATUS); + udelay(66); + bq27541_read(BQ27541_REG_CNTL, &flags, 0, di); + bq27541_cntl_cmd(di, BQ27541_SUBCMD_DEVCIE_TYPE); + udelay(66); + bq27541_read(BQ27541_REG_CNTL, &type, 0, di); + bq27541_cntl_cmd(di, BQ27541_SUBCMD_FW_VER); + udelay(66); + bq27541_read(BQ27541_REG_CNTL, &fw_ver, 0, di); + + dev_info(di->dev, "DEVICE_TYPE is 0x%02X, FIRMWARE_VERSION is 0x%02X\n", + type, fw_ver); + dev_info(di->dev, "Complete bq27541 configuration 0x%02X\n", flags); +} + +static int bq27541_read_i2c(u8 reg, int *rt_value, int b_single, + struct bq27541_device_info *di) +{ + struct i2c_client *client = di->client; + struct i2c_msg msg[1]; + unsigned char data[2]; + int err; + + if (!client->adapter) + return -ENODEV; + + msg->addr = client->addr; + msg->flags = 0; + msg->len = 1; + msg->buf = data; + + data[0] = reg; + err = i2c_transfer(client->adapter, msg, 1); + + if (err >= 0) { + if (!b_single) + msg->len = 2; + else + msg->len = 1; + + msg->flags = I2C_M_RD; + err = i2c_transfer(client->adapter, msg, 1); + if (err >= 0) { + if (!b_single) + *rt_value = get_unaligned_le16(data); + else + *rt_value = data[0]; + + return 0; + } + } + return err; +} + +#ifdef CONFIG_BQ27541_TEST_ENABLE +static int reg; +static int subcmd; +static ssize_t bq27541_read_stdcmd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + int temp = 0; + struct platform_device *client; + struct bq27541_device_info *di; + + client = to_platform_device(dev); + di = platform_get_drvdata(client); + + if (reg <= BQ27541_REG_ICR && reg > 0x00) { + ret = bq27541_read(reg, &temp, 0, di); + if (ret) + ret = snprintf(buf, PAGE_SIZE, "Read Error!\n"); + else + ret = snprintf(buf, PAGE_SIZE, "0x%02x\n", temp); + } else + ret = snprintf(buf, PAGE_SIZE, "Register Error!\n"); + + return ret; +} + +static ssize_t bq27541_write_stdcmd(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret = strnlen(buf, PAGE_SIZE); + int cmd; + + sscanf(buf, "%x", &cmd); + reg = cmd; + return ret; +} + +static ssize_t bq27541_read_subcmd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + int temp = 0; + struct platform_device *client; + struct bq27541_device_info *di; + + client = to_platform_device(dev); + di = platform_get_drvdata(client); + + if (subcmd == BQ27541_SUBCMD_DEVCIE_TYPE || + subcmd == BQ27541_SUBCMD_FW_VER || + subcmd == BQ27541_SUBCMD_HW_VER || + subcmd == BQ27541_SUBCMD_CHEM_ID) { + + bq27541_cntl_cmd(di, subcmd); /* Retrieve Chip status */ + udelay(66); + ret = bq27541_read(BQ27541_REG_CNTL, &temp, 0, di); + + if (ret) + ret = snprintf(buf, PAGE_SIZE, "Read Error!\n"); + else + ret = snprintf(buf, PAGE_SIZE, "0x%02x\n", temp); + } else + ret = snprintf(buf, PAGE_SIZE, "Register Error!\n"); + + return ret; +} + +static ssize_t bq27541_write_subcmd(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret = strnlen(buf, PAGE_SIZE); + int cmd; + + sscanf(buf, "%x", &cmd); + subcmd = cmd; + return ret; +} + +static DEVICE_ATTR(std_cmd, S_IRUGO|S_IWUGO, bq27541_read_stdcmd, + bq27541_write_stdcmd); +static DEVICE_ATTR(sub_cmd, S_IRUGO|S_IWUGO, bq27541_read_subcmd, + bq27541_write_subcmd); +static struct attribute *fs_attrs[] = { + &dev_attr_std_cmd.attr, + &dev_attr_sub_cmd.attr, + NULL, +}; +static struct attribute_group fs_attr_group = { + .attrs = fs_attrs, +}; + +static struct platform_device this_device = { + .name = "bq27541-test", + .id = -1, + .dev.platform_data = NULL, +}; +#endif + +static int bq27541_battery_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + char *name; + struct bq27541_device_info *di; + struct bq27541_access_methods *bus; + int num; + int retval = 0; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + /* Get new ID for the new battery device */ + retval = idr_pre_get(&battery_id, GFP_KERNEL); + if (retval == 0) + return -ENOMEM; + mutex_lock(&battery_mutex); + retval = idr_get_new(&battery_id, client, &num); + mutex_unlock(&battery_mutex); + if (retval < 0) + return retval; + + name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num); + if (!name) { + dev_err(&client->dev, "failed to allocate device name\n"); + retval = -ENOMEM; + goto batt_failed_1; + } + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + dev_err(&client->dev, "failed to allocate device info data\n"); + retval = -ENOMEM; + goto batt_failed_2; + } + di->id = num; + + bus = kzalloc(sizeof(*bus), GFP_KERNEL); + if (!bus) { + dev_err(&client->dev, "failed to allocate access method " + "data\n"); + retval = -ENOMEM; + goto batt_failed_3; + } + + i2c_set_clientdata(client, di); + di->dev = &client->dev; + bus->read = &bq27541_read_i2c; + di->bus = bus; + di->client = client; + +#ifdef CONFIG_BQ27541_TEST_ENABLE + platform_set_drvdata(&this_device, di); + retval = platform_device_register(&this_device); + if (!retval) { + retval = sysfs_create_group(&this_device.dev.kobj, + &fs_attr_group); + if (retval) + goto batt_failed_4; + } else + goto batt_failed_4; +#endif + + if (retval) { + dev_err(&client->dev, "failed to setup bq27541\n"); + goto batt_failed_4; + } + + if (retval) { + dev_err(&client->dev, "failed to powerup bq27541\n"); + goto batt_failed_4; + } + + spin_lock_init(&lock); + + bq27541_di = di; + INIT_WORK(&di->counter, bq27541_coulomb_counter_work); + INIT_DELAYED_WORK(&di->hw_config, bq27541_hw_config); + schedule_delayed_work(&di->hw_config, BQ27541_INIT_DELAY); + return 0; + +batt_failed_4: + kfree(bus); +batt_failed_3: + kfree(di); +batt_failed_2: + kfree(name); +batt_failed_1: + mutex_lock(&battery_mutex); + idr_remove(&battery_id, num); + mutex_unlock(&battery_mutex); + + return retval; +} + +static int bq27541_battery_remove(struct i2c_client *client) +{ + struct bq27541_device_info *di = i2c_get_clientdata(client); + + msm_battery_gauge_unregister(&bq27541_batt_gauge); + bq27541_cntl_cmd(di, BQ27541_SUBCMD_DISABLE_DLOG); + udelay(66); + bq27541_cntl_cmd(di, BQ27541_SUBCMD_DISABLE_IT); + cancel_delayed_work_sync(&di->hw_config); + + kfree(di->bus); + + mutex_lock(&battery_mutex); + idr_remove(&battery_id, di->id); + mutex_unlock(&battery_mutex); + + kfree(di); + return 0; +} + +static const struct i2c_device_id bq27541_id[] = { + { "bq27541", 1 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, BQ27541_id); + +static struct i2c_driver bq27541_battery_driver = { + .driver = { + .name = "bq27541-battery", + }, + .probe = bq27541_battery_probe, + .remove = bq27541_battery_remove, + .id_table = bq27541_id, +}; + +static int __init bq27541_battery_init(void) +{ + int ret; + + ret = i2c_add_driver(&bq27541_battery_driver); + if (ret) + printk(KERN_ERR "Unable to register BQ27541 driver\n"); + + return ret; +} +module_init(bq27541_battery_init); + +static void __exit bq27541_battery_exit(void) +{ + i2c_del_driver(&bq27541_battery_driver); +} +module_exit(bq27541_battery_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); +MODULE_DESCRIPTION("BQ27541 battery monitor driver"); diff --git a/drivers/power/isl9519q.c b/drivers/power/isl9519q.c new file mode 100644 index 00000000000..4954a4558fd --- /dev/null +++ b/drivers/power/isl9519q.c @@ -0,0 +1,517 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHG_CURRENT_REG 0x14 +#define MAX_SYS_VOLTAGE_REG 0x15 +#define CONTROL_REG 0x3D +#define MIN_SYS_VOLTAGE_REG 0x3E +#define INPUT_CURRENT_REG 0x3F +#define MANUFACTURER_ID_REG 0xFE +#define DEVICE_ID_REG 0xFF + +#define TRCKL_CHG_STATUS_BIT 0x80 + +#define ISL9519_CHG_PERIOD ((HZ) * 150) + +struct isl9519q_struct { + struct i2c_client *client; + struct delayed_work charge_work; + int present; + int batt_present; + bool charging; + int chgcurrent; + int term_current; + int input_current; + int max_system_voltage; + int min_system_voltage; + int valid_n_gpio; + struct dentry *dent; + struct msm_hardware_charger adapter_hw_chg; +}; + +static int isl9519q_read_reg(struct i2c_client *client, int reg, + u16 *val) +{ + int ret; + struct isl9519q_struct *isl_chg; + + isl_chg = i2c_get_clientdata(client); + ret = i2c_smbus_read_word_data(isl_chg->client, reg); + + if (ret < 0) { + dev_err(&isl_chg->client->dev, + "i2c read fail: can't read from %02x: %d\n", reg, ret); + return -EAGAIN; + } else + *val = ret; + + return 0; +} + +static int isl9519q_write_reg(struct i2c_client *client, int reg, + u16 val) +{ + int ret; + struct isl9519q_struct *isl_chg; + + isl_chg = i2c_get_clientdata(client); + ret = i2c_smbus_write_word_data(isl_chg->client, reg, val); + + if (ret < 0) { + dev_err(&isl_chg->client->dev, + "i2c write fail: can't write %02x to %02x: %d\n", + val, reg, ret); + return -EAGAIN; + } + return 0; +} + +static int isl_read_adc(int channel, int *mv_reading) +{ + int ret; + void *h; + struct adc_chan_result adc_chan_result; + struct completion conv_complete_evt; + + pr_debug("%s: called for %d\n", __func__, channel); + ret = adc_channel_open(channel, &h); + if (ret) { + pr_err("%s: couldnt open channel %d ret=%d\n", + __func__, channel, ret); + goto out; + } + init_completion(&conv_complete_evt); + ret = adc_channel_request_conv(h, &conv_complete_evt); + if (ret) { + pr_err("%s: couldnt request conv channel %d ret=%d\n", + __func__, channel, ret); + goto out; + } + ret = wait_for_completion_interruptible(&conv_complete_evt); + if (ret) { + pr_err("%s: wait interrupted channel %d ret=%d\n", + __func__, channel, ret); + goto out; + } + ret = adc_channel_read_result(h, &adc_chan_result); + if (ret) { + pr_err("%s: couldnt read result channel %d ret=%d\n", + __func__, channel, ret); + goto out; + } + ret = adc_channel_close(h); + if (ret) + pr_err("%s: couldnt close channel %d ret=%d\n", + __func__, channel, ret); + if (mv_reading) + *mv_reading = (int)adc_chan_result.measurement; + + pr_debug("%s: done for %d\n", __func__, channel); + return adc_chan_result.physical; +out: + *mv_reading = 0; + pr_debug("%s: done with error for %d\n", __func__, channel); + return -EINVAL; + +} + +static void isl9519q_charge(struct work_struct *isl9519_work) +{ + u16 temp; + int ret; + struct isl9519q_struct *isl_chg; + int isl_charger_current; + int mv_reading; + + isl_chg = container_of(isl9519_work, struct isl9519q_struct, + charge_work.work); + + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); + + if (isl_chg->charging) { + isl_charger_current = isl_read_adc(CHANNEL_ADC_BATT_AMON, + &mv_reading); + dev_dbg(&isl_chg->client->dev, "%s mv_reading=%d\n", + __func__, mv_reading); + dev_dbg(&isl_chg->client->dev, "%s isl_charger_current=%d\n", + __func__, isl_charger_current); + if (isl_charger_current >= 0 + && isl_charger_current <= isl_chg->term_current) { + msm_charger_notify_event( + &isl_chg->adapter_hw_chg, + CHG_DONE_EVENT); + } + isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, + isl_chg->chgcurrent); + ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, &temp); + if (!ret) { + if (!(temp & TRCKL_CHG_STATUS_BIT)) + msm_charger_notify_event( + &isl_chg->adapter_hw_chg, + CHG_BATT_BEGIN_FAST_CHARGING); + } else { + dev_err(&isl_chg->client->dev, + "%s couldnt read cntrl reg\n", __func__); + } + schedule_delayed_work(&isl_chg->charge_work, + ISL9519_CHG_PERIOD); + } +} + +static int isl9519q_start_charging(struct msm_hardware_charger *hw_chg, + int chg_voltage, int chg_current) +{ + struct isl9519q_struct *isl_chg; + int ret = 0; + + isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg); + if (isl_chg->charging) + /* we are already charging */ + return 0; + + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); + + ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, + isl_chg->chgcurrent); + if (ret) { + dev_err(&isl_chg->client->dev, + "%s coulnt write to current_reg\n", __func__); + goto out; + } + + dev_dbg(&isl_chg->client->dev, "%s starting timed work\n", + __func__); + schedule_delayed_work(&isl_chg->charge_work, + ISL9519_CHG_PERIOD); + isl_chg->charging = true; + +out: + return ret; +} + +static int isl9519q_stop_charging(struct msm_hardware_charger *hw_chg) +{ + struct isl9519q_struct *isl_chg; + int ret = 0; + + isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg); + if (!(isl_chg->charging)) + /* we arent charging */ + return 0; + + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); + + ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0); + if (ret) { + dev_err(&isl_chg->client->dev, + "%s coulnt write to current_reg\n", __func__); + goto out; + } + + isl_chg->charging = false; + cancel_delayed_work(&isl_chg->charge_work); +out: + return ret; +} + +static int isl9519q_charging_switched(struct msm_hardware_charger *hw_chg) +{ + struct isl9519q_struct *isl_chg; + + isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg); + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); + return 0; +} + +static irqreturn_t isl_valid_handler(int irq, void *dev_id) +{ + int val; + struct isl9519q_struct *isl_chg; + struct i2c_client *client = dev_id; + + isl_chg = i2c_get_clientdata(client); + val = gpio_get_value_cansleep(isl_chg->valid_n_gpio); + if (val < 0) { + dev_err(&isl_chg->client->dev, + "%s gpio_get_value failed for %d ret=%d\n", __func__, + isl_chg->valid_n_gpio, val); + goto err; + } + dev_dbg(&isl_chg->client->dev, "%s val=%d\n", __func__, val); + + if (val) { + if (isl_chg->present == 1) { + msm_charger_notify_event(&isl_chg->adapter_hw_chg, + CHG_REMOVED_EVENT); + isl_chg->present = 0; + } + } else { + if (isl_chg->present == 0) { + msm_charger_notify_event(&isl_chg->adapter_hw_chg, + CHG_INSERTED_EVENT); + isl_chg->present = 1; + } + } +err: + return IRQ_HANDLED; +} + +#define MAX_VOLTAGE_REG_MASK 0x3FF0 +#define MIN_VOLTAGE_REG_MASK 0x3F00 +#define DEFAULT_MAX_VOLTAGE_REG_VALUE 0x1070 +#define DEFAULT_MIN_VOLTAGE_REG_VALUE 0x0D00 + +static int __devinit isl9519q_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isl_platform_data *pdata; + struct isl9519q_struct *isl_chg; + int ret; + + ret = 0; + pdata = client->dev.platform_data; + + if (pdata == NULL) { + dev_err(&client->dev, "%s no platform data\n", __func__); + ret = -EINVAL; + goto out; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + ret = -EIO; + goto out; + } + + isl_chg = kzalloc(sizeof(*isl_chg), GFP_KERNEL); + if (!isl_chg) { + ret = -ENOMEM; + goto out; + } + + INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_charge); + isl_chg->client = client; + isl_chg->chgcurrent = pdata->chgcurrent; + isl_chg->term_current = pdata->term_current; + isl_chg->input_current = pdata->input_current; + isl_chg->max_system_voltage = pdata->max_system_voltage; + isl_chg->min_system_voltage = pdata->min_system_voltage; + isl_chg->valid_n_gpio = pdata->valid_n_gpio; + + /* h/w ignores lower 7 bits of charging current and input current */ + isl_chg->chgcurrent &= ~0x7F; + isl_chg->input_current &= ~0x7F; + + isl_chg->adapter_hw_chg.type = CHG_TYPE_AC; + isl_chg->adapter_hw_chg.rating = 2; + isl_chg->adapter_hw_chg.name = "isl-adapter"; + isl_chg->adapter_hw_chg.start_charging = isl9519q_start_charging; + isl_chg->adapter_hw_chg.stop_charging = isl9519q_stop_charging; + isl_chg->adapter_hw_chg.charging_switched = isl9519q_charging_switched; + + if (pdata->chg_detection_config) { + ret = pdata->chg_detection_config(); + if (ret) { + dev_err(&client->dev, "%s valid config failed ret=%d\n", + __func__, ret); + goto free_isl_chg; + } + } + + ret = gpio_request(pdata->valid_n_gpio, "isl_charger_valid"); + if (ret) { + dev_err(&client->dev, "%s gpio_request failed for %d ret=%d\n", + __func__, pdata->valid_n_gpio, ret); + goto free_isl_chg; + } + + i2c_set_clientdata(client, isl_chg); + + ret = msm_charger_register(&isl_chg->adapter_hw_chg); + if (ret) { + dev_err(&client->dev, + "%s msm_charger_register failed for ret =%d\n", + __func__, ret); + goto free_gpio; + } + + ret = request_threaded_irq(client->irq, NULL, + isl_valid_handler, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "isl_charger_valid", client); + if (ret) { + dev_err(&client->dev, + "%s request_threaded_irq failed for %d ret =%d\n", + __func__, client->irq, ret); + goto unregister; + } + irq_set_irq_wake(client->irq, 1); + + isl_chg->max_system_voltage &= MAX_VOLTAGE_REG_MASK; + isl_chg->min_system_voltage &= MIN_VOLTAGE_REG_MASK; + if (isl_chg->max_system_voltage == 0) + isl_chg->max_system_voltage = DEFAULT_MAX_VOLTAGE_REG_VALUE; + if (isl_chg->min_system_voltage == 0) + isl_chg->min_system_voltage = DEFAULT_MIN_VOLTAGE_REG_VALUE; + + ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG, + isl_chg->max_system_voltage); + if (ret) { + dev_err(&client->dev, + "%s couldnt write to MAX_SYS_VOLTAGE_REG ret=%d\n", + __func__, ret); + goto free_irq; + } + + ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG, + isl_chg->min_system_voltage); + if (ret) { + dev_err(&client->dev, + "%s couldnt write to MIN_SYS_VOLTAGE_REG ret=%d\n", + __func__, ret); + goto free_irq; + } + + if (isl_chg->input_current) { + ret = isl9519q_write_reg(isl_chg->client, + INPUT_CURRENT_REG, + isl_chg->input_current); + if (ret) { + dev_err(&client->dev, + "%s couldnt write INPUT_CURRENT_REG ret=%d\n", + __func__, ret); + goto free_irq; + } + } + + ret = gpio_get_value_cansleep(isl_chg->valid_n_gpio); + if (ret < 0) { + dev_err(&client->dev, + "%s gpio_get_value failed for %d ret=%d\n", __func__, + pdata->valid_n_gpio, ret); + /* assume absent */ + ret = 1; + } + if (!ret) { + msm_charger_notify_event(&isl_chg->adapter_hw_chg, + CHG_INSERTED_EVENT); + isl_chg->present = 1; + } + + pr_debug("%s OK chg_present=%d\n", __func__, isl_chg->present); + return 0; + +free_irq: + free_irq(client->irq, NULL); +unregister: + msm_charger_register(&isl_chg->adapter_hw_chg); +free_gpio: + gpio_free(pdata->valid_n_gpio); +free_isl_chg: + kfree(isl_chg); +out: + return ret; +} + +static int __devexit isl9519q_remove(struct i2c_client *client) +{ + struct isl_platform_data *pdata; + struct isl9519q_struct *isl_chg = i2c_get_clientdata(client); + + pdata = client->dev.platform_data; + gpio_free(pdata->valid_n_gpio); + free_irq(client->irq, client); + cancel_delayed_work_sync(&isl_chg->charge_work); + msm_charger_notify_event(&isl_chg->adapter_hw_chg, CHG_REMOVED_EVENT); + msm_charger_unregister(&isl_chg->adapter_hw_chg); + return 0; +} + +static const struct i2c_device_id isl9519q_id[] = { + {"isl9519q", 0}, + {}, +}; + +#ifdef CONFIG_PM +static int isl9519q_suspend(struct device *dev) +{ + struct isl9519q_struct *isl_chg = dev_get_drvdata(dev); + + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); + /* + * do not suspend while we are charging + * because we need to periodically update the register + * for charging to proceed + */ + if (isl_chg->charging) + return -EBUSY; + return 0; +} + +static int isl9519q_resume(struct device *dev) +{ + struct isl9519q_struct *isl_chg = dev_get_drvdata(dev); + + dev_dbg(&isl_chg->client->dev, "%s\n", __func__); + return 0; +} + +static const struct dev_pm_ops isl9519q_pm_ops = { + .suspend = isl9519q_suspend, + .resume = isl9519q_resume, +}; +#endif + +static struct i2c_driver isl9519q_driver = { + .driver = { + .name = "isl9519q", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &isl9519q_pm_ops, +#endif + }, + .probe = isl9519q_probe, + .remove = __devexit_p(isl9519q_remove), + .id_table = isl9519q_id, +}; + +static int __init isl9519q_init(void) +{ + return i2c_add_driver(&isl9519q_driver); +} + +module_init(isl9519q_init); + +static void __exit isl9519q_exit(void) +{ + return i2c_del_driver(&isl9519q_driver); +} + +module_exit(isl9519q_exit); + +MODULE_AUTHOR("Abhijeet Dharmapurikar "); +MODULE_DESCRIPTION("Driver for ISL9519Q Charger chip"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/msm_battery.c b/drivers/power/msm_battery.c new file mode 100644 index 00000000000..464a1b59ea7 --- /dev/null +++ b/drivers/power/msm_battery.c @@ -0,0 +1,1592 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * this needs to be before is loaded, + * and loads + */ +#define DEBUG 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define BATTERY_RPC_PROG 0x30000089 +#define BATTERY_RPC_VER_1_1 0x00010001 +#define BATTERY_RPC_VER_2_1 0x00020001 +#define BATTERY_RPC_VER_4_1 0x00040001 +#define BATTERY_RPC_VER_5_1 0x00050001 + +#define BATTERY_RPC_CB_PROG (BATTERY_RPC_PROG | 0x01000000) + +#define CHG_RPC_PROG 0x3000001a +#define CHG_RPC_VER_1_1 0x00010001 +#define CHG_RPC_VER_1_3 0x00010003 +#define CHG_RPC_VER_2_2 0x00020002 +#define CHG_RPC_VER_3_1 0x00030001 +#define CHG_RPC_VER_4_1 0x00040001 + +#define BATTERY_REGISTER_PROC 2 +#define BATTERY_MODIFY_CLIENT_PROC 4 +#define BATTERY_DEREGISTER_CLIENT_PROC 5 +#define BATTERY_READ_MV_PROC 12 +#define BATTERY_ENABLE_DISABLE_FILTER_PROC 14 + +#define VBATT_FILTER 2 + +#define BATTERY_CB_TYPE_PROC 1 +#define BATTERY_CB_ID_ALL_ACTIV 1 +#define BATTERY_CB_ID_LOW_VOL 2 + +#define BATTERY_LOW 3200 +#define BATTERY_HIGH 4300 + +#define ONCRPC_CHG_GET_GENERAL_STATUS_PROC 12 +#define ONCRPC_CHARGER_API_VERSIONS_PROC 0xffffffff + +#define BATT_RPC_TIMEOUT 5000 /* 5 sec */ + +#define INVALID_BATT_HANDLE -1 + +#define RPC_TYPE_REQ 0 +#define RPC_TYPE_REPLY 1 +#define RPC_REQ_REPLY_COMMON_HEADER_SIZE (3 * sizeof(uint32_t)) + + +#if DEBUG +#define DBG_LIMIT(x...) do {if (printk_ratelimit()) pr_debug(x); } while (0) +#else +#define DBG_LIMIT(x...) do {} while (0) +#endif + +enum { + BATTERY_REGISTRATION_SUCCESSFUL = 0, + BATTERY_DEREGISTRATION_SUCCESSFUL = BATTERY_REGISTRATION_SUCCESSFUL, + BATTERY_MODIFICATION_SUCCESSFUL = BATTERY_REGISTRATION_SUCCESSFUL, + BATTERY_INTERROGATION_SUCCESSFUL = BATTERY_REGISTRATION_SUCCESSFUL, + BATTERY_CLIENT_TABLE_FULL = 1, + BATTERY_REG_PARAMS_WRONG = 2, + BATTERY_DEREGISTRATION_FAILED = 4, + BATTERY_MODIFICATION_FAILED = 8, + BATTERY_INTERROGATION_FAILED = 16, + /* Client's filter could not be set because perhaps it does not exist */ + BATTERY_SET_FILTER_FAILED = 32, + /* Client's could not be found for enabling or disabling the individual + * client */ + BATTERY_ENABLE_DISABLE_INDIVIDUAL_CLIENT_FAILED = 64, + BATTERY_LAST_ERROR = 128, +}; + +enum { + BATTERY_VOLTAGE_UP = 0, + BATTERY_VOLTAGE_DOWN, + BATTERY_VOLTAGE_ABOVE_THIS_LEVEL, + BATTERY_VOLTAGE_BELOW_THIS_LEVEL, + BATTERY_VOLTAGE_LEVEL, + BATTERY_ALL_ACTIVITY, + VBATT_CHG_EVENTS, + BATTERY_VOLTAGE_UNKNOWN, +}; + +/* + * This enum contains defintions of the charger hardware status + */ +enum chg_charger_status_type { + /* The charger is good */ + CHARGER_STATUS_GOOD, + /* The charger is bad */ + CHARGER_STATUS_BAD, + /* The charger is weak */ + CHARGER_STATUS_WEAK, + /* Invalid charger status. */ + CHARGER_STATUS_INVALID +}; + +/* + *This enum contains defintions of the charger hardware type + */ +enum chg_charger_hardware_type { + /* The charger is removed */ + CHARGER_TYPE_NONE, + /* The charger is a regular wall charger */ + CHARGER_TYPE_WALL, + /* The charger is a PC USB */ + CHARGER_TYPE_USB_PC, + /* The charger is a wall USB charger */ + CHARGER_TYPE_USB_WALL, + /* The charger is a USB carkit */ + CHARGER_TYPE_USB_CARKIT, + /* Invalid charger hardware status. */ + CHARGER_TYPE_INVALID +}; + +/* + * This enum contains defintions of the battery status + */ +enum chg_battery_status_type { + /* The battery is good */ + BATTERY_STATUS_GOOD, + /* The battery is cold/hot */ + BATTERY_STATUS_BAD_TEMP, + /* The battery is bad */ + BATTERY_STATUS_BAD, + /* The battery is removed */ + BATTERY_STATUS_REMOVED, /* on v2.2 only */ + BATTERY_STATUS_INVALID_v1 = BATTERY_STATUS_REMOVED, + /* Invalid battery status. */ + BATTERY_STATUS_INVALID +}; + +/* + *This enum contains defintions of the battery voltage level + */ +enum chg_battery_level_type { + /* The battery voltage is dead/very low (less than 3.2V) */ + BATTERY_LEVEL_DEAD, + /* The battery voltage is weak/low (between 3.2V and 3.4V) */ + BATTERY_LEVEL_WEAK, + /* The battery voltage is good/normal(between 3.4V and 4.2V) */ + BATTERY_LEVEL_GOOD, + /* The battery voltage is up to full (close to 4.2V) */ + BATTERY_LEVEL_FULL, + /* Invalid battery voltage level. */ + BATTERY_LEVEL_INVALID +}; + +#ifndef CONFIG_BATTERY_MSM_FAKE +struct rpc_reply_batt_chg_v1 { + struct rpc_reply_hdr hdr; + u32 more_data; + + u32 charger_status; + u32 charger_type; + u32 battery_status; + u32 battery_level; + u32 battery_voltage; + u32 battery_temp; +}; + +struct rpc_reply_batt_chg_v2 { + struct rpc_reply_batt_chg_v1 v1; + + u32 is_charger_valid; + u32 is_charging; + u32 is_battery_valid; + u32 ui_event; +}; + +union rpc_reply_batt_chg { + struct rpc_reply_batt_chg_v1 v1; + struct rpc_reply_batt_chg_v2 v2; +}; + +static union rpc_reply_batt_chg rep_batt_chg; +#endif + +struct msm_battery_info { + u32 voltage_max_design; + u32 voltage_min_design; + u32 chg_api_version; + u32 batt_technology; + u32 batt_api_version; + + u32 avail_chg_sources; + u32 current_chg_source; + + u32 batt_status; + u32 batt_health; + u32 charger_valid; + u32 batt_valid; + u32 batt_capacity; /* in percentage */ + + u32 charger_status; + u32 charger_type; + u32 battery_status; + u32 battery_level; + u32 battery_voltage; /* in millie volts */ + u32 battery_temp; /* in celsius */ + + u32(*calculate_capacity) (u32 voltage); + + s32 batt_handle; + + struct power_supply *msm_psy_ac; + struct power_supply *msm_psy_usb; + struct power_supply *msm_psy_batt; + struct power_supply *current_ps; + + struct msm_rpc_client *batt_client; + struct msm_rpc_endpoint *chg_ep; + + wait_queue_head_t wait_q; + + u32 vbatt_modify_reply_avail; + + struct early_suspend early_suspend; +}; + +static struct msm_battery_info msm_batt_info = { + .batt_handle = INVALID_BATT_HANDLE, + .charger_status = CHARGER_STATUS_BAD, + .charger_type = CHARGER_TYPE_INVALID, + .battery_status = BATTERY_STATUS_GOOD, + .battery_level = BATTERY_LEVEL_FULL, + .battery_voltage = BATTERY_HIGH, + .batt_capacity = 100, + .batt_status = POWER_SUPPLY_STATUS_DISCHARGING, + .batt_health = POWER_SUPPLY_HEALTH_GOOD, + .batt_valid = 1, + .battery_temp = 23, + .vbatt_modify_reply_avail = 0, +}; + +static enum power_supply_property msm_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *msm_power_supplied_to[] = { + "battery", +}; + +static int msm_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (psy->type == POWER_SUPPLY_TYPE_MAINS) { + val->intval = msm_batt_info.current_chg_source & AC_CHG + ? 1 : 0; + } + if (psy->type == POWER_SUPPLY_TYPE_USB) { + val->intval = msm_batt_info.current_chg_source & USB_CHG + ? 1 : 0; + } + break; + default: + return -EINVAL; + } + return 0; +} + +static struct power_supply msm_psy_ac = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = msm_power_supplied_to, + .num_supplicants = ARRAY_SIZE(msm_power_supplied_to), + .properties = msm_power_props, + .num_properties = ARRAY_SIZE(msm_power_props), + .get_property = msm_power_get_property, +}; + +static struct power_supply msm_psy_usb = { + .name = "usb", + .type = POWER_SUPPLY_TYPE_USB, + .supplied_to = msm_power_supplied_to, + .num_supplicants = ARRAY_SIZE(msm_power_supplied_to), + .properties = msm_power_props, + .num_properties = ARRAY_SIZE(msm_power_props), + .get_property = msm_power_get_property, +}; + +static enum power_supply_property msm_batt_power_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int msm_batt_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = msm_batt_info.batt_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = msm_batt_info.batt_health; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = msm_batt_info.batt_valid; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = msm_batt_info.batt_technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = msm_batt_info.voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = msm_batt_info.voltage_min_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = msm_batt_info.battery_voltage; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = msm_batt_info.batt_capacity; + break; + default: + return -EINVAL; + } + return 0; +} + +static struct power_supply msm_psy_batt = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = msm_batt_power_props, + .num_properties = ARRAY_SIZE(msm_batt_power_props), + .get_property = msm_batt_power_get_property, +}; + +#ifndef CONFIG_BATTERY_MSM_FAKE +struct msm_batt_get_volt_ret_data { + u32 battery_voltage; +}; + +static int msm_batt_get_volt_ret_func(struct msm_rpc_client *batt_client, + void *buf, void *data) +{ + struct msm_batt_get_volt_ret_data *data_ptr, *buf_ptr; + + data_ptr = (struct msm_batt_get_volt_ret_data *)data; + buf_ptr = (struct msm_batt_get_volt_ret_data *)buf; + + data_ptr->battery_voltage = be32_to_cpu(buf_ptr->battery_voltage); + + return 0; +} + +static u32 msm_batt_get_vbatt_voltage(void) +{ + int rc; + + struct msm_batt_get_volt_ret_data rep; + + rc = msm_rpc_client_req(msm_batt_info.batt_client, + BATTERY_READ_MV_PROC, + NULL, NULL, + msm_batt_get_volt_ret_func, &rep, + msecs_to_jiffies(BATT_RPC_TIMEOUT)); + + if (rc < 0) { + pr_err("%s: FAIL: vbatt get volt. rc=%d\n", __func__, rc); + return 0; + } + + return rep.battery_voltage; +} + +#define be32_to_cpu_self(v) (v = be32_to_cpu(v)) + +static int msm_batt_get_batt_chg_status(void) +{ + int rc; + + struct rpc_req_batt_chg { + struct rpc_request_hdr hdr; + u32 more_data; + } req_batt_chg; + struct rpc_reply_batt_chg_v1 *v1p; + + req_batt_chg.more_data = cpu_to_be32(1); + + memset(&rep_batt_chg, 0, sizeof(rep_batt_chg)); + + v1p = &rep_batt_chg.v1; + rc = msm_rpc_call_reply(msm_batt_info.chg_ep, + ONCRPC_CHG_GET_GENERAL_STATUS_PROC, + &req_batt_chg, sizeof(req_batt_chg), + &rep_batt_chg, sizeof(rep_batt_chg), + msecs_to_jiffies(BATT_RPC_TIMEOUT)); + if (rc < 0) { + pr_err("%s: ERROR. msm_rpc_call_reply failed! proc=%d rc=%d\n", + __func__, ONCRPC_CHG_GET_GENERAL_STATUS_PROC, rc); + return rc; + } else if (be32_to_cpu(v1p->more_data)) { + be32_to_cpu_self(v1p->charger_status); + be32_to_cpu_self(v1p->charger_type); + be32_to_cpu_self(v1p->battery_status); + be32_to_cpu_self(v1p->battery_level); + be32_to_cpu_self(v1p->battery_voltage); + be32_to_cpu_self(v1p->battery_temp); + } else { + pr_err("%s: No battery/charger data in RPC reply\n", __func__); + return -EIO; + } + + return 0; +} + +static void msm_batt_update_psy_status(void) +{ + static u32 unnecessary_event_count; + u32 charger_status; + u32 charger_type; + u32 battery_status; + u32 battery_level; + u32 battery_voltage; + u32 battery_temp; + struct power_supply *supp; + + if (msm_batt_get_batt_chg_status()) + return; + + charger_status = rep_batt_chg.v1.charger_status; + charger_type = rep_batt_chg.v1.charger_type; + battery_status = rep_batt_chg.v1.battery_status; + battery_level = rep_batt_chg.v1.battery_level; + battery_voltage = rep_batt_chg.v1.battery_voltage; + battery_temp = rep_batt_chg.v1.battery_temp; + + /* Make correction for battery status */ + if (battery_status == BATTERY_STATUS_INVALID_v1) { + if (msm_batt_info.chg_api_version < CHG_RPC_VER_3_1) + battery_status = BATTERY_STATUS_INVALID; + } + + if (charger_status == msm_batt_info.charger_status && + charger_type == msm_batt_info.charger_type && + battery_status == msm_batt_info.battery_status && + battery_level == msm_batt_info.battery_level && + battery_voltage == msm_batt_info.battery_voltage && + battery_temp == msm_batt_info.battery_temp) { + /* Got unnecessary event from Modem PMIC VBATT driver. + * Nothing changed in Battery or charger status. + */ + unnecessary_event_count++; + if ((unnecessary_event_count % 20) == 1) + DBG_LIMIT("BATT: same event count = %u\n", + unnecessary_event_count); + return; + } + + unnecessary_event_count = 0; + + DBG_LIMIT("BATT: rcvd: %d, %d, %d, %d; %d, %d\n", + charger_status, charger_type, battery_status, + battery_level, battery_voltage, battery_temp); + + if (battery_status == BATTERY_STATUS_INVALID && + battery_level != BATTERY_LEVEL_INVALID) { + DBG_LIMIT("BATT: change status(%d) to (%d) for level=%d\n", + battery_status, BATTERY_STATUS_GOOD, battery_level); + battery_status = BATTERY_STATUS_GOOD; + } + + if (msm_batt_info.charger_type != charger_type) { + if (charger_type == CHARGER_TYPE_USB_WALL || + charger_type == CHARGER_TYPE_USB_PC || + charger_type == CHARGER_TYPE_USB_CARKIT) { + DBG_LIMIT("BATT: USB charger plugged in\n"); + msm_batt_info.current_chg_source = USB_CHG; + supp = &msm_psy_usb; + } else if (charger_type == CHARGER_TYPE_WALL) { + DBG_LIMIT("BATT: AC Wall changer plugged in\n"); + msm_batt_info.current_chg_source = AC_CHG; + supp = &msm_psy_ac; + } else { + if (msm_batt_info.current_chg_source & AC_CHG) + DBG_LIMIT("BATT: AC Wall charger removed\n"); + else if (msm_batt_info.current_chg_source & USB_CHG) + DBG_LIMIT("BATT: USB charger removed\n"); + else + DBG_LIMIT("BATT: No charger present\n"); + msm_batt_info.current_chg_source = 0; + supp = &msm_psy_batt; + + /* Correct charger status */ + if (charger_status != CHARGER_STATUS_INVALID) { + DBG_LIMIT("BATT: No charging!\n"); + charger_status = CHARGER_STATUS_INVALID; + msm_batt_info.batt_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + } + } + } else + supp = NULL; + + if (msm_batt_info.charger_status != charger_status) { + if (charger_status == CHARGER_STATUS_GOOD || + charger_status == CHARGER_STATUS_WEAK) { + if (msm_batt_info.current_chg_source) { + DBG_LIMIT("BATT: Charging.\n"); + msm_batt_info.batt_status = + POWER_SUPPLY_STATUS_CHARGING; + + /* Correct when supp==NULL */ + if (msm_batt_info.current_chg_source & AC_CHG) + supp = &msm_psy_ac; + else + supp = &msm_psy_usb; + } + } else { + DBG_LIMIT("BATT: No charging.\n"); + msm_batt_info.batt_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + supp = &msm_psy_batt; + } + } else { + /* Correct charger status */ + if (charger_type != CHARGER_TYPE_INVALID && + charger_status == CHARGER_STATUS_GOOD) { + DBG_LIMIT("BATT: In charging\n"); + msm_batt_info.batt_status = + POWER_SUPPLY_STATUS_CHARGING; + } + } + + /* Correct battery voltage and status */ + if (!battery_voltage) { + if (charger_status == CHARGER_STATUS_INVALID) { + DBG_LIMIT("BATT: Read VBATT\n"); + battery_voltage = msm_batt_get_vbatt_voltage(); + } else + /* Use previous */ + battery_voltage = msm_batt_info.battery_voltage; + } + if (battery_status == BATTERY_STATUS_INVALID) { + if (battery_voltage >= msm_batt_info.voltage_min_design && + battery_voltage <= msm_batt_info.voltage_max_design) { + DBG_LIMIT("BATT: Battery valid\n"); + msm_batt_info.batt_valid = 1; + battery_status = BATTERY_STATUS_GOOD; + } + } + + if (msm_batt_info.battery_status != battery_status) { + if (battery_status != BATTERY_STATUS_INVALID) { + msm_batt_info.batt_valid = 1; + + if (battery_status == BATTERY_STATUS_BAD) { + DBG_LIMIT("BATT: Battery bad.\n"); + msm_batt_info.batt_health = + POWER_SUPPLY_HEALTH_DEAD; + } else if (battery_status == BATTERY_STATUS_BAD_TEMP) { + DBG_LIMIT("BATT: Battery overheat.\n"); + msm_batt_info.batt_health = + POWER_SUPPLY_HEALTH_OVERHEAT; + } else { + DBG_LIMIT("BATT: Battery good.\n"); + msm_batt_info.batt_health = + POWER_SUPPLY_HEALTH_GOOD; + } + } else { + msm_batt_info.batt_valid = 0; + DBG_LIMIT("BATT: Battery invalid.\n"); + msm_batt_info.batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; + } + + if (msm_batt_info.batt_status != POWER_SUPPLY_STATUS_CHARGING) { + if (battery_status == BATTERY_STATUS_INVALID) { + DBG_LIMIT("BATT: Battery -> unknown\n"); + msm_batt_info.batt_status = + POWER_SUPPLY_STATUS_UNKNOWN; + } else { + DBG_LIMIT("BATT: Battery -> discharging\n"); + msm_batt_info.batt_status = + POWER_SUPPLY_STATUS_DISCHARGING; + } + } + + if (!supp) { + if (msm_batt_info.current_chg_source) { + if (msm_batt_info.current_chg_source & AC_CHG) + supp = &msm_psy_ac; + else + supp = &msm_psy_usb; + } else + supp = &msm_psy_batt; + } + } + + msm_batt_info.charger_status = charger_status; + msm_batt_info.charger_type = charger_type; + msm_batt_info.battery_status = battery_status; + msm_batt_info.battery_level = battery_level; + msm_batt_info.battery_temp = battery_temp; + + if (msm_batt_info.battery_voltage != battery_voltage) { + msm_batt_info.battery_voltage = battery_voltage; + msm_batt_info.batt_capacity = + msm_batt_info.calculate_capacity(battery_voltage); + DBG_LIMIT("BATT: voltage = %u mV [capacity = %d%%]\n", + battery_voltage, msm_batt_info.batt_capacity); + + if (!supp) + supp = msm_batt_info.current_ps; + } + + if (supp) { + msm_batt_info.current_ps = supp; + DBG_LIMIT("BATT: Supply = %s\n", supp->name); + power_supply_changed(supp); + } +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +struct batt_modify_client_req { + + u32 client_handle; + + /* The voltage at which callback (CB) should be called. */ + u32 desired_batt_voltage; + + /* The direction when the CB should be called. */ + u32 voltage_direction; + + /* The registered callback to be called when voltage and + * direction specs are met. */ + u32 batt_cb_id; + + /* The call back data */ + u32 cb_data; +}; + +struct batt_modify_client_rep { + u32 result; +}; + +static int msm_batt_modify_client_arg_func(struct msm_rpc_client *batt_client, + void *buf, void *data) +{ + struct batt_modify_client_req *batt_modify_client_req = + (struct batt_modify_client_req *)data; + u32 *req = (u32 *)buf; + int size = 0; + + *req = cpu_to_be32(batt_modify_client_req->client_handle); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_modify_client_req->desired_batt_voltage); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_modify_client_req->voltage_direction); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_modify_client_req->batt_cb_id); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_modify_client_req->cb_data); + size += sizeof(u32); + + return size; +} + +static int msm_batt_modify_client_ret_func(struct msm_rpc_client *batt_client, + void *buf, void *data) +{ + struct batt_modify_client_rep *data_ptr, *buf_ptr; + + data_ptr = (struct batt_modify_client_rep *)data; + buf_ptr = (struct batt_modify_client_rep *)buf; + + data_ptr->result = be32_to_cpu(buf_ptr->result); + + return 0; +} + +static int msm_batt_modify_client(u32 client_handle, u32 desired_batt_voltage, + u32 voltage_direction, u32 batt_cb_id, u32 cb_data) +{ + int rc; + + struct batt_modify_client_req req; + struct batt_modify_client_rep rep; + + req.client_handle = client_handle; + req.desired_batt_voltage = desired_batt_voltage; + req.voltage_direction = voltage_direction; + req.batt_cb_id = batt_cb_id; + req.cb_data = cb_data; + + rc = msm_rpc_client_req(msm_batt_info.batt_client, + BATTERY_MODIFY_CLIENT_PROC, + msm_batt_modify_client_arg_func, &req, + msm_batt_modify_client_ret_func, &rep, + msecs_to_jiffies(BATT_RPC_TIMEOUT)); + + if (rc < 0) { + pr_err("%s: ERROR. failed to modify Vbatt client\n", + __func__); + return rc; + } + + if (rep.result != BATTERY_MODIFICATION_SUCCESSFUL) { + pr_err("%s: ERROR. modify client failed. result = %u\n", + __func__, rep.result); + return -EIO; + } + + return 0; +} + +void msm_batt_early_suspend(struct early_suspend *h) +{ + int rc; + + pr_debug("%s: enter\n", __func__); + + if (msm_batt_info.batt_handle != INVALID_BATT_HANDLE) { + rc = msm_batt_modify_client(msm_batt_info.batt_handle, + BATTERY_LOW, BATTERY_VOLTAGE_BELOW_THIS_LEVEL, + BATTERY_CB_ID_LOW_VOL, BATTERY_LOW); + + if (rc < 0) { + pr_err("%s: msm_batt_modify_client. rc=%d\n", + __func__, rc); + return; + } + } else { + pr_err("%s: ERROR. invalid batt_handle\n", __func__); + return; + } + + pr_debug("%s: exit\n", __func__); +} + +void msm_batt_late_resume(struct early_suspend *h) +{ + int rc; + + pr_debug("%s: enter\n", __func__); + + if (msm_batt_info.batt_handle != INVALID_BATT_HANDLE) { + rc = msm_batt_modify_client(msm_batt_info.batt_handle, + BATTERY_LOW, BATTERY_ALL_ACTIVITY, + BATTERY_CB_ID_ALL_ACTIV, BATTERY_ALL_ACTIVITY); + if (rc < 0) { + pr_err("%s: msm_batt_modify_client FAIL rc=%d\n", + __func__, rc); + return; + } + } else { + pr_err("%s: ERROR. invalid batt_handle\n", __func__); + return; + } + + msm_batt_update_psy_status(); + pr_debug("%s: exit\n", __func__); +} +#endif + +struct msm_batt_vbatt_filter_req { + u32 batt_handle; + u32 enable_filter; + u32 vbatt_filter; +}; + +struct msm_batt_vbatt_filter_rep { + u32 result; +}; + +static int msm_batt_filter_arg_func(struct msm_rpc_client *batt_client, + + void *buf, void *data) +{ + struct msm_batt_vbatt_filter_req *vbatt_filter_req = + (struct msm_batt_vbatt_filter_req *)data; + u32 *req = (u32 *)buf; + int size = 0; + + *req = cpu_to_be32(vbatt_filter_req->batt_handle); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(vbatt_filter_req->enable_filter); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(vbatt_filter_req->vbatt_filter); + size += sizeof(u32); + return size; +} + +static int msm_batt_filter_ret_func(struct msm_rpc_client *batt_client, + void *buf, void *data) +{ + + struct msm_batt_vbatt_filter_rep *data_ptr, *buf_ptr; + + data_ptr = (struct msm_batt_vbatt_filter_rep *)data; + buf_ptr = (struct msm_batt_vbatt_filter_rep *)buf; + + data_ptr->result = be32_to_cpu(buf_ptr->result); + return 0; +} + +static int msm_batt_enable_filter(u32 vbatt_filter) +{ + int rc; + struct msm_batt_vbatt_filter_req vbatt_filter_req; + struct msm_batt_vbatt_filter_rep vbatt_filter_rep; + + vbatt_filter_req.batt_handle = msm_batt_info.batt_handle; + vbatt_filter_req.enable_filter = 1; + vbatt_filter_req.vbatt_filter = vbatt_filter; + + rc = msm_rpc_client_req(msm_batt_info.batt_client, + BATTERY_ENABLE_DISABLE_FILTER_PROC, + msm_batt_filter_arg_func, &vbatt_filter_req, + msm_batt_filter_ret_func, &vbatt_filter_rep, + msecs_to_jiffies(BATT_RPC_TIMEOUT)); + + if (rc < 0) { + pr_err("%s: FAIL: enable vbatt filter. rc=%d\n", + __func__, rc); + return rc; + } + + if (vbatt_filter_rep.result != BATTERY_DEREGISTRATION_SUCCESSFUL) { + pr_err("%s: FAIL: enable vbatt filter: result=%d\n", + __func__, vbatt_filter_rep.result); + return -EIO; + } + + pr_debug("%s: enable vbatt filter: OK\n", __func__); + return rc; +} + +struct batt_client_registration_req { + /* The voltage at which callback (CB) should be called. */ + u32 desired_batt_voltage; + + /* The direction when the CB should be called. */ + u32 voltage_direction; + + /* The registered callback to be called when voltage and + * direction specs are met. */ + u32 batt_cb_id; + + /* The call back data */ + u32 cb_data; + u32 more_data; + u32 batt_error; +}; + +struct batt_client_registration_req_4_1 { + /* The voltage at which callback (CB) should be called. */ + u32 desired_batt_voltage; + + /* The direction when the CB should be called. */ + u32 voltage_direction; + + /* The registered callback to be called when voltage and + * direction specs are met. */ + u32 batt_cb_id; + + /* The call back data */ + u32 cb_data; + u32 batt_error; +}; + +struct batt_client_registration_rep { + u32 batt_handle; +}; + +struct batt_client_registration_rep_4_1 { + u32 batt_handle; + u32 more_data; + u32 err; +}; + +static int msm_batt_register_arg_func(struct msm_rpc_client *batt_client, + void *buf, void *data) +{ + struct batt_client_registration_req *batt_reg_req = + (struct batt_client_registration_req *)data; + + u32 *req = (u32 *)buf; + int size = 0; + + + if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1) { + *req = cpu_to_be32(batt_reg_req->desired_batt_voltage); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_reg_req->voltage_direction); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_reg_req->batt_cb_id); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_reg_req->cb_data); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_reg_req->batt_error); + size += sizeof(u32); + + return size; + } else { + *req = cpu_to_be32(batt_reg_req->desired_batt_voltage); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_reg_req->voltage_direction); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_reg_req->batt_cb_id); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_reg_req->cb_data); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_reg_req->more_data); + size += sizeof(u32); + req++; + + *req = cpu_to_be32(batt_reg_req->batt_error); + size += sizeof(u32); + + return size; + } + +} + +static int msm_batt_register_ret_func(struct msm_rpc_client *batt_client, + void *buf, void *data) +{ + struct batt_client_registration_rep *data_ptr, *buf_ptr; + struct batt_client_registration_rep_4_1 *data_ptr_4_1, *buf_ptr_4_1; + + if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1) { + data_ptr_4_1 = (struct batt_client_registration_rep_4_1 *)data; + buf_ptr_4_1 = (struct batt_client_registration_rep_4_1 *)buf; + + data_ptr_4_1->batt_handle + = be32_to_cpu(buf_ptr_4_1->batt_handle); + data_ptr_4_1->more_data + = be32_to_cpu(buf_ptr_4_1->more_data); + data_ptr_4_1->err = be32_to_cpu(buf_ptr_4_1->err); + return 0; + } else { + data_ptr = (struct batt_client_registration_rep *)data; + buf_ptr = (struct batt_client_registration_rep *)buf; + + data_ptr->batt_handle = be32_to_cpu(buf_ptr->batt_handle); + return 0; + } +} + +static int msm_batt_register(u32 desired_batt_voltage, + u32 voltage_direction, u32 batt_cb_id, u32 cb_data) +{ + struct batt_client_registration_req batt_reg_req; + struct batt_client_registration_req_4_1 batt_reg_req_4_1; + struct batt_client_registration_rep batt_reg_rep; + struct batt_client_registration_rep_4_1 batt_reg_rep_4_1; + void *request; + void *reply; + int rc; + + if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1) { + batt_reg_req_4_1.desired_batt_voltage = desired_batt_voltage; + batt_reg_req_4_1.voltage_direction = voltage_direction; + batt_reg_req_4_1.batt_cb_id = batt_cb_id; + batt_reg_req_4_1.cb_data = cb_data; + batt_reg_req_4_1.batt_error = 1; + request = &batt_reg_req_4_1; + } else { + batt_reg_req.desired_batt_voltage = desired_batt_voltage; + batt_reg_req.voltage_direction = voltage_direction; + batt_reg_req.batt_cb_id = batt_cb_id; + batt_reg_req.cb_data = cb_data; + batt_reg_req.more_data = 1; + batt_reg_req.batt_error = 0; + request = &batt_reg_req; + } + + if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1) + reply = &batt_reg_rep_4_1; + else + reply = &batt_reg_rep; + + rc = msm_rpc_client_req(msm_batt_info.batt_client, + BATTERY_REGISTER_PROC, + msm_batt_register_arg_func, request, + msm_batt_register_ret_func, reply, + msecs_to_jiffies(BATT_RPC_TIMEOUT)); + + if (rc < 0) { + pr_err("%s: FAIL: vbatt register. rc=%d\n", __func__, rc); + return rc; + } + + if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1) { + if (batt_reg_rep_4_1.more_data != 0 + && batt_reg_rep_4_1.err + != BATTERY_REGISTRATION_SUCCESSFUL) { + pr_err("%s: vBatt Registration Failed proc_num=%d\n" + , __func__, BATTERY_REGISTER_PROC); + return -EIO; + } + msm_batt_info.batt_handle = batt_reg_rep_4_1.batt_handle; + } else + msm_batt_info.batt_handle = batt_reg_rep.batt_handle; + + return 0; +} + +struct batt_client_deregister_req { + u32 batt_handle; +}; + +struct batt_client_deregister_rep { + u32 batt_error; +}; + +static int msm_batt_deregister_arg_func(struct msm_rpc_client *batt_client, + void *buf, void *data) +{ + struct batt_client_deregister_req *deregister_req = + (struct batt_client_deregister_req *)data; + u32 *req = (u32 *)buf; + int size = 0; + + *req = cpu_to_be32(deregister_req->batt_handle); + size += sizeof(u32); + + return size; +} + +static int msm_batt_deregister_ret_func(struct msm_rpc_client *batt_client, + void *buf, void *data) +{ + struct batt_client_deregister_rep *data_ptr, *buf_ptr; + + data_ptr = (struct batt_client_deregister_rep *)data; + buf_ptr = (struct batt_client_deregister_rep *)buf; + + data_ptr->batt_error = be32_to_cpu(buf_ptr->batt_error); + + return 0; +} + +static int msm_batt_deregister(u32 batt_handle) +{ + int rc; + struct batt_client_deregister_req req; + struct batt_client_deregister_rep rep; + + req.batt_handle = batt_handle; + + rc = msm_rpc_client_req(msm_batt_info.batt_client, + BATTERY_DEREGISTER_CLIENT_PROC, + msm_batt_deregister_arg_func, &req, + msm_batt_deregister_ret_func, &rep, + msecs_to_jiffies(BATT_RPC_TIMEOUT)); + + if (rc < 0) { + pr_err("%s: FAIL: vbatt deregister. rc=%d\n", __func__, rc); + return rc; + } + + if (rep.batt_error != BATTERY_DEREGISTRATION_SUCCESSFUL) { + pr_err("%s: vbatt deregistration FAIL. error=%d, handle=%d\n", + __func__, rep.batt_error, batt_handle); + return -EIO; + } + + return 0; +} +#endif /* CONFIG_BATTERY_MSM_FAKE */ + +static int msm_batt_cleanup(void) +{ + int rc = 0; + +#ifndef CONFIG_BATTERY_MSM_FAKE + if (msm_batt_info.batt_handle != INVALID_BATT_HANDLE) { + + rc = msm_batt_deregister(msm_batt_info.batt_handle); + if (rc < 0) + pr_err("%s: FAIL: msm_batt_deregister. rc=%d\n", + __func__, rc); + } + + msm_batt_info.batt_handle = INVALID_BATT_HANDLE; + + if (msm_batt_info.batt_client) + msm_rpc_unregister_client(msm_batt_info.batt_client); +#endif /* CONFIG_BATTERY_MSM_FAKE */ + + if (msm_batt_info.msm_psy_ac) + power_supply_unregister(msm_batt_info.msm_psy_ac); + + if (msm_batt_info.msm_psy_usb) + power_supply_unregister(msm_batt_info.msm_psy_usb); + if (msm_batt_info.msm_psy_batt) + power_supply_unregister(msm_batt_info.msm_psy_batt); + +#ifndef CONFIG_BATTERY_MSM_FAKE + if (msm_batt_info.chg_ep) { + rc = msm_rpc_close(msm_batt_info.chg_ep); + if (rc < 0) { + pr_err("%s: FAIL. msm_rpc_close(chg_ep). rc=%d\n", + __func__, rc); + } + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + if (msm_batt_info.early_suspend.suspend == msm_batt_early_suspend) + unregister_early_suspend(&msm_batt_info.early_suspend); +#endif +#endif + return rc; +} + +static u32 msm_batt_capacity(u32 current_voltage) +{ + u32 low_voltage = msm_batt_info.voltage_min_design; + u32 high_voltage = msm_batt_info.voltage_max_design; + + if (current_voltage <= low_voltage) + return 0; + else if (current_voltage >= high_voltage) + return 100; + else + return (current_voltage - low_voltage) * 100 + / (high_voltage - low_voltage); +} + +#ifndef CONFIG_BATTERY_MSM_FAKE +int msm_batt_get_charger_api_version(void) +{ + int rc ; + struct rpc_reply_hdr *reply; + + struct rpc_req_chg_api_ver { + struct rpc_request_hdr hdr; + u32 more_data; + } req_chg_api_ver; + + struct rpc_rep_chg_api_ver { + struct rpc_reply_hdr hdr; + u32 num_of_chg_api_versions; + u32 *chg_api_versions; + }; + + u32 num_of_versions; + + struct rpc_rep_chg_api_ver *rep_chg_api_ver; + + + req_chg_api_ver.more_data = cpu_to_be32(1); + + msm_rpc_setup_req(&req_chg_api_ver.hdr, CHG_RPC_PROG, CHG_RPC_VER_1_1, + ONCRPC_CHARGER_API_VERSIONS_PROC); + + rc = msm_rpc_write(msm_batt_info.chg_ep, &req_chg_api_ver, + sizeof(req_chg_api_ver)); + if (rc < 0) { + pr_err("%s: FAIL: msm_rpc_write. proc=0x%08x, rc=%d\n", + __func__, ONCRPC_CHARGER_API_VERSIONS_PROC, rc); + return rc; + } + + for (;;) { + rc = msm_rpc_read(msm_batt_info.chg_ep, (void *) &reply, -1, + BATT_RPC_TIMEOUT); + if (rc < 0) + return rc; + if (rc < RPC_REQ_REPLY_COMMON_HEADER_SIZE) { + pr_err("%s: LENGTH ERR: msm_rpc_read. rc=%d (<%d)\n", + __func__, rc, RPC_REQ_REPLY_COMMON_HEADER_SIZE); + + rc = -EIO; + break; + } + /* we should not get RPC REQ or call packets -- ignore them */ + if (reply->type == RPC_TYPE_REQ) { + pr_err("%s: TYPE ERR: type=%d (!=%d)\n", + __func__, reply->type, RPC_TYPE_REQ); + kfree(reply); + continue; + } + + /* If an earlier call timed out, we could get the (no + * longer wanted) reply for it. Ignore replies that + * we don't expect + */ + if (reply->xid != req_chg_api_ver.hdr.xid) { + pr_err("%s: XID ERR: xid=%d (!=%d)\n", __func__, + reply->xid, req_chg_api_ver.hdr.xid); + kfree(reply); + continue; + } + if (reply->reply_stat != RPCMSG_REPLYSTAT_ACCEPTED) { + rc = -EPERM; + break; + } + if (reply->data.acc_hdr.accept_stat != + RPC_ACCEPTSTAT_SUCCESS) { + rc = -EINVAL; + break; + } + + rep_chg_api_ver = (struct rpc_rep_chg_api_ver *)reply; + + num_of_versions = + be32_to_cpu(rep_chg_api_ver->num_of_chg_api_versions); + + rep_chg_api_ver->chg_api_versions = (u32 *) + ((u8 *) reply + sizeof(struct rpc_reply_hdr) + + sizeof(rep_chg_api_ver->num_of_chg_api_versions)); + + rc = be32_to_cpu( + rep_chg_api_ver->chg_api_versions[num_of_versions - 1]); + + pr_debug("%s: num_of_chg_api_versions = %u. " + "The chg api version = 0x%08x\n", __func__, + num_of_versions, rc); + break; + } + kfree(reply); + return rc; +} + +static int msm_batt_cb_func(struct msm_rpc_client *client, + void *buffer, int in_size) +{ + int rc = 0; + struct rpc_request_hdr *req; + u32 procedure; + u32 accept_status; + + req = (struct rpc_request_hdr *)buffer; + procedure = be32_to_cpu(req->procedure); + + switch (procedure) { + case BATTERY_CB_TYPE_PROC: + accept_status = RPC_ACCEPTSTAT_SUCCESS; + break; + + default: + accept_status = RPC_ACCEPTSTAT_PROC_UNAVAIL; + pr_err("%s: ERROR. procedure (%d) not supported\n", + __func__, procedure); + break; + } + + msm_rpc_start_accepted_reply(msm_batt_info.batt_client, + be32_to_cpu(req->xid), accept_status); + + rc = msm_rpc_send_accepted_reply(msm_batt_info.batt_client, 0); + if (rc) + pr_err("%s: FAIL: sending reply. rc=%d\n", __func__, rc); + + if (accept_status == RPC_ACCEPTSTAT_SUCCESS) + msm_batt_update_psy_status(); + + return rc; +} +#endif /* CONFIG_BATTERY_MSM_FAKE */ + +static int __devinit msm_batt_probe(struct platform_device *pdev) +{ + int rc; + struct msm_psy_batt_pdata *pdata = pdev->dev.platform_data; + + if (pdev->id != -1) { + dev_err(&pdev->dev, + "%s: MSM chipsets Can only support one" + " battery ", __func__); + return -EINVAL; + } + +#ifndef CONFIG_BATTERY_MSM_FAKE + if (pdata->avail_chg_sources & AC_CHG) { +#else + { +#endif + rc = power_supply_register(&pdev->dev, &msm_psy_ac); + if (rc < 0) { + dev_err(&pdev->dev, + "%s: power_supply_register failed" + " rc = %d\n", __func__, rc); + msm_batt_cleanup(); + return rc; + } + msm_batt_info.msm_psy_ac = &msm_psy_ac; + msm_batt_info.avail_chg_sources |= AC_CHG; + } + + if (pdata->avail_chg_sources & USB_CHG) { + rc = power_supply_register(&pdev->dev, &msm_psy_usb); + if (rc < 0) { + dev_err(&pdev->dev, + "%s: power_supply_register failed" + " rc = %d\n", __func__, rc); + msm_batt_cleanup(); + return rc; + } + msm_batt_info.msm_psy_usb = &msm_psy_usb; + msm_batt_info.avail_chg_sources |= USB_CHG; + } + + if (!msm_batt_info.msm_psy_ac && !msm_batt_info.msm_psy_usb) { + + dev_err(&pdev->dev, + "%s: No external Power supply(AC or USB)" + "is avilable\n", __func__); + msm_batt_cleanup(); + return -ENODEV; + } + + msm_batt_info.voltage_max_design = pdata->voltage_max_design; + msm_batt_info.voltage_min_design = pdata->voltage_min_design; + msm_batt_info.batt_technology = pdata->batt_technology; + msm_batt_info.calculate_capacity = pdata->calculate_capacity; + + if (!msm_batt_info.voltage_min_design) + msm_batt_info.voltage_min_design = BATTERY_LOW; + if (!msm_batt_info.voltage_max_design) + msm_batt_info.voltage_max_design = BATTERY_HIGH; + + if (msm_batt_info.batt_technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN) + msm_batt_info.batt_technology = POWER_SUPPLY_TECHNOLOGY_LION; + + if (!msm_batt_info.calculate_capacity) + msm_batt_info.calculate_capacity = msm_batt_capacity; + + rc = power_supply_register(&pdev->dev, &msm_psy_batt); + if (rc < 0) { + dev_err(&pdev->dev, "%s: power_supply_register failed" + " rc=%d\n", __func__, rc); + msm_batt_cleanup(); + return rc; + } + msm_batt_info.msm_psy_batt = &msm_psy_batt; + +#ifndef CONFIG_BATTERY_MSM_FAKE + rc = msm_batt_register(BATTERY_LOW, BATTERY_ALL_ACTIVITY, + BATTERY_CB_ID_ALL_ACTIV, BATTERY_ALL_ACTIVITY); + if (rc < 0) { + dev_err(&pdev->dev, + "%s: msm_batt_register failed rc = %d\n", __func__, rc); + msm_batt_cleanup(); + return rc; + } + + rc = msm_batt_enable_filter(VBATT_FILTER); + + if (rc < 0) { + dev_err(&pdev->dev, + "%s: msm_batt_enable_filter failed rc = %d\n", + __func__, rc); + msm_batt_cleanup(); + return rc; + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + msm_batt_info.early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; + msm_batt_info.early_suspend.suspend = msm_batt_early_suspend; + msm_batt_info.early_suspend.resume = msm_batt_late_resume; + register_early_suspend(&msm_batt_info.early_suspend); +#endif + msm_batt_update_psy_status(); + +#else + power_supply_changed(&msm_psy_ac); +#endif /* CONFIG_BATTERY_MSM_FAKE */ + + return 0; +} + +static int __devexit msm_batt_remove(struct platform_device *pdev) +{ + int rc; + rc = msm_batt_cleanup(); + + if (rc < 0) { + dev_err(&pdev->dev, + "%s: msm_batt_cleanup failed rc=%d\n", __func__, rc); + return rc; + } + return 0; +} + +static struct platform_driver msm_batt_driver = { + .probe = msm_batt_probe, + .remove = __devexit_p(msm_batt_remove), + .driver = { + .name = "msm-battery", + .owner = THIS_MODULE, + }, +}; + +static int __devinit msm_batt_init_rpc(void) +{ + int rc; + +#ifdef CONFIG_BATTERY_MSM_FAKE + pr_info("Faking MSM battery\n"); +#else + + msm_batt_info.chg_ep = + msm_rpc_connect_compatible(CHG_RPC_PROG, CHG_RPC_VER_4_1, 0); + msm_batt_info.chg_api_version = CHG_RPC_VER_4_1; + if (msm_batt_info.chg_ep == NULL) { + pr_err("%s: rpc connect CHG_RPC_PROG = NULL\n", __func__); + return -ENODEV; + } + + if (IS_ERR(msm_batt_info.chg_ep)) { + msm_batt_info.chg_ep = msm_rpc_connect_compatible( + CHG_RPC_PROG, CHG_RPC_VER_3_1, 0); + msm_batt_info.chg_api_version = CHG_RPC_VER_3_1; + } + if (IS_ERR(msm_batt_info.chg_ep)) { + msm_batt_info.chg_ep = msm_rpc_connect_compatible( + CHG_RPC_PROG, CHG_RPC_VER_1_1, 0); + msm_batt_info.chg_api_version = CHG_RPC_VER_1_1; + } + if (IS_ERR(msm_batt_info.chg_ep)) { + msm_batt_info.chg_ep = msm_rpc_connect_compatible( + CHG_RPC_PROG, CHG_RPC_VER_1_3, 0); + msm_batt_info.chg_api_version = CHG_RPC_VER_1_3; + } + if (IS_ERR(msm_batt_info.chg_ep)) { + msm_batt_info.chg_ep = msm_rpc_connect_compatible( + CHG_RPC_PROG, CHG_RPC_VER_2_2, 0); + msm_batt_info.chg_api_version = CHG_RPC_VER_2_2; + } + if (IS_ERR(msm_batt_info.chg_ep)) { + rc = PTR_ERR(msm_batt_info.chg_ep); + pr_err("%s: FAIL: rpc connect for CHG_RPC_PROG. rc=%d\n", + __func__, rc); + msm_batt_info.chg_ep = NULL; + return rc; + } + + /* Get the real 1.x version */ + if (msm_batt_info.chg_api_version == CHG_RPC_VER_1_1) + msm_batt_info.chg_api_version = + msm_batt_get_charger_api_version(); + + /* Fall back to 1.1 for default */ + if (msm_batt_info.chg_api_version < 0) + msm_batt_info.chg_api_version = CHG_RPC_VER_1_1; + msm_batt_info.batt_api_version = BATTERY_RPC_VER_4_1; + + msm_batt_info.batt_client = + msm_rpc_register_client("battery", BATTERY_RPC_PROG, + BATTERY_RPC_VER_4_1, + 1, msm_batt_cb_func); + + if (msm_batt_info.batt_client == NULL) { + pr_err("%s: FAIL: rpc_register_client. batt_client=NULL\n", + __func__); + return -ENODEV; + } + if (IS_ERR(msm_batt_info.batt_client)) { + msm_batt_info.batt_client = + msm_rpc_register_client("battery", BATTERY_RPC_PROG, + BATTERY_RPC_VER_1_1, + 1, msm_batt_cb_func); + msm_batt_info.batt_api_version = BATTERY_RPC_VER_1_1; + } + if (IS_ERR(msm_batt_info.batt_client)) { + msm_batt_info.batt_client = + msm_rpc_register_client("battery", BATTERY_RPC_PROG, + BATTERY_RPC_VER_2_1, + 1, msm_batt_cb_func); + msm_batt_info.batt_api_version = BATTERY_RPC_VER_2_1; + } + if (IS_ERR(msm_batt_info.batt_client)) { + msm_batt_info.batt_client = + msm_rpc_register_client("battery", BATTERY_RPC_PROG, + BATTERY_RPC_VER_5_1, + 1, msm_batt_cb_func); + msm_batt_info.batt_api_version = BATTERY_RPC_VER_5_1; + } + if (IS_ERR(msm_batt_info.batt_client)) { + rc = PTR_ERR(msm_batt_info.batt_client); + pr_err("%s: ERROR: rpc_register_client: rc = %d\n ", + __func__, rc); + msm_batt_info.batt_client = NULL; + return rc; + } +#endif /* CONFIG_BATTERY_MSM_FAKE */ + + rc = platform_driver_register(&msm_batt_driver); + + if (rc < 0) + pr_err("%s: FAIL: platform_driver_register. rc = %d\n", + __func__, rc); + + return rc; +} + +static int __init msm_batt_init(void) +{ + int rc; + + pr_debug("%s: enter\n", __func__); + + rc = msm_batt_init_rpc(); + + if (rc < 0) { + pr_err("%s: FAIL: msm_batt_init_rpc. rc=%d\n", __func__, rc); + msm_batt_cleanup(); + return rc; + } + + pr_info("%s: Charger/Battery = 0x%08x/0x%08x (RPC version)\n", + __func__, msm_batt_info.chg_api_version, + msm_batt_info.batt_api_version); + + return 0; +} + +static void __exit msm_batt_exit(void) +{ + platform_driver_unregister(&msm_batt_driver); +} + +module_init(msm_batt_init); +module_exit(msm_batt_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Kiran Kandi, Qualcomm Innovation Center, Inc."); +MODULE_DESCRIPTION("Battery driver for Qualcomm MSM chipsets."); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:msm_battery"); diff --git a/drivers/power/msm_charger.c b/drivers/power/msm_charger.c new file mode 100644 index 00000000000..f40477a15f3 --- /dev/null +++ b/drivers/power/msm_charger.c @@ -0,0 +1,1250 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#define MSM_CHG_MAX_EVENTS 16 +#define CHARGING_TEOC_MS 9000000 +#define UPDATE_TIME_MS 60000 +#define RESUME_CHECK_PERIOD_MS 60000 + +#define DEFAULT_BATT_MAX_V 4200 +#define DEFAULT_BATT_MIN_V 3200 + +#define MSM_CHARGER_GAUGE_MISSING_VOLTS 3500 +#define MSM_CHARGER_GAUGE_MISSING_TEMP 35 +/** + * enum msm_battery_status + * @BATT_STATUS_ABSENT: battery not present + * @BATT_STATUS_ID_INVALID: battery present but the id is invalid + * @BATT_STATUS_DISCHARGING: battery is present and is discharging + * @BATT_STATUS_TRKL_CHARGING: battery is being trickle charged + * @BATT_STATUS_FAST_CHARGING: battery is being fast charged + * @BATT_STATUS_JUST_FINISHED_CHARGING: just finished charging, + * battery is fully charged. Do not begin charging untill the + * voltage falls below a threshold to avoid overcharging + * @BATT_STATUS_TEMPERATURE_OUT_OF_RANGE: battery present, + no charging, temp is hot/cold + */ +enum msm_battery_status { + BATT_STATUS_ABSENT, + BATT_STATUS_ID_INVALID, + BATT_STATUS_DISCHARGING, + BATT_STATUS_TRKL_CHARGING, + BATT_STATUS_FAST_CHARGING, + BATT_STATUS_JUST_FINISHED_CHARGING, + BATT_STATUS_TEMPERATURE_OUT_OF_RANGE, +}; + +struct msm_hardware_charger_priv { + struct list_head list; + struct msm_hardware_charger *hw_chg; + enum msm_hardware_charger_state hw_chg_state; + unsigned int max_source_current; + struct power_supply psy; +}; + +struct msm_charger_event { + enum msm_hardware_charger_event event; + struct msm_hardware_charger *hw_chg; +}; + +struct msm_charger_mux { + int inited; + struct list_head msm_hardware_chargers; + int count_chargers; + struct mutex msm_hardware_chargers_lock; + + struct device *dev; + + unsigned int max_voltage; + unsigned int min_voltage; + + unsigned int safety_time; + struct delayed_work teoc_work; + + unsigned int update_time; + int stop_update; + struct delayed_work update_heartbeat_work; + + struct mutex status_lock; + enum msm_battery_status batt_status; + struct msm_hardware_charger_priv *current_chg_priv; + struct msm_hardware_charger_priv *current_mon_priv; + + unsigned int (*get_batt_capacity_percent) (void); + + struct msm_charger_event *queue; + int tail; + int head; + spinlock_t queue_lock; + int queue_count; + struct work_struct queue_work; + struct workqueue_struct *event_wq_thread; + struct wake_lock wl; +}; + +static struct msm_charger_mux msm_chg; + +static struct msm_battery_gauge *msm_batt_gauge; + +static int is_chg_capable_of_charging(struct msm_hardware_charger_priv *priv) +{ + if (priv->hw_chg_state == CHG_READY_STATE + || priv->hw_chg_state == CHG_CHARGING_STATE) + return 1; + + return 0; +} + +static int is_batt_status_capable_of_charging(void) +{ + if (msm_chg.batt_status == BATT_STATUS_ABSENT + || msm_chg.batt_status == BATT_STATUS_TEMPERATURE_OUT_OF_RANGE + || msm_chg.batt_status == BATT_STATUS_ID_INVALID + || msm_chg.batt_status == BATT_STATUS_JUST_FINISHED_CHARGING) + return 0; + return 1; +} + +static int is_batt_status_charging(void) +{ + if (msm_chg.batt_status == BATT_STATUS_TRKL_CHARGING + || msm_chg.batt_status == BATT_STATUS_FAST_CHARGING) + return 1; + return 0; +} + +static int is_battery_present(void) +{ + if (msm_batt_gauge && msm_batt_gauge->is_battery_present) + return msm_batt_gauge->is_battery_present(); + else { + pr_err("msm-charger: no batt gauge batt=absent\n"); + return 0; + } +} + +static int is_battery_temp_within_range(void) +{ + if (msm_batt_gauge && msm_batt_gauge->is_battery_temp_within_range) + return msm_batt_gauge->is_battery_temp_within_range(); + else { + pr_err("msm-charger no batt gauge batt=out_of_temperatur\n"); + return 0; + } +} + +static int is_battery_id_valid(void) +{ + if (msm_batt_gauge && msm_batt_gauge->is_battery_id_valid) + return msm_batt_gauge->is_battery_id_valid(); + else { + pr_err("msm-charger no batt gauge batt=id_invalid\n"); + return 0; + } +} + +static int get_prop_battery_mvolts(void) +{ + if (msm_batt_gauge && msm_batt_gauge->get_battery_mvolts) + return msm_batt_gauge->get_battery_mvolts(); + else { + pr_err("msm-charger no batt gauge assuming 3.5V\n"); + return MSM_CHARGER_GAUGE_MISSING_VOLTS; + } +} + +static int get_battery_temperature(void) +{ + if (msm_batt_gauge && msm_batt_gauge->get_battery_temperature) + return msm_batt_gauge->get_battery_temperature(); + else { + pr_err("msm-charger no batt gauge assuming 35 deg G\n"); + return MSM_CHARGER_GAUGE_MISSING_TEMP; + } +} + +static int get_prop_batt_capacity(void) +{ + if (msm_batt_gauge && msm_batt_gauge->get_batt_remaining_capacity) + return msm_batt_gauge->get_batt_remaining_capacity(); + + return msm_chg.get_batt_capacity_percent(); +} + +static int get_prop_batt_health(void) +{ + int status = 0; + + if (msm_chg.batt_status == BATT_STATUS_TEMPERATURE_OUT_OF_RANGE) + status = POWER_SUPPLY_HEALTH_OVERHEAT; + else + status = POWER_SUPPLY_HEALTH_GOOD; + + return status; +} + +static int get_prop_charge_type(void) +{ + int status = 0; + + if (msm_chg.batt_status == BATT_STATUS_TRKL_CHARGING) + status = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + else if (msm_chg.batt_status == BATT_STATUS_FAST_CHARGING) + status = POWER_SUPPLY_CHARGE_TYPE_FAST; + else + status = POWER_SUPPLY_CHARGE_TYPE_NONE; + + return status; +} + +static int get_prop_batt_status(void) +{ + int status = 0; + + if (msm_batt_gauge && msm_batt_gauge->get_battery_status) { + status = msm_batt_gauge->get_battery_status(); + if (status == POWER_SUPPLY_STATUS_CHARGING || + status == POWER_SUPPLY_STATUS_FULL || + status == POWER_SUPPLY_STATUS_DISCHARGING) + return status; + } + + if (is_batt_status_charging()) + status = POWER_SUPPLY_STATUS_CHARGING; + else if (msm_chg.batt_status == + BATT_STATUS_JUST_FINISHED_CHARGING + && msm_chg.current_chg_priv != NULL) + status = POWER_SUPPLY_STATUS_FULL; + else + status = POWER_SUPPLY_STATUS_DISCHARGING; + + return status; +} + + /* This function should only be called within handle_event or resume */ +static void update_batt_status(void) +{ + if (is_battery_present()) { + if (is_battery_id_valid()) { + if (msm_chg.batt_status == BATT_STATUS_ABSENT + || msm_chg.batt_status + == BATT_STATUS_ID_INVALID) { + msm_chg.batt_status = BATT_STATUS_DISCHARGING; + } + } else + msm_chg.batt_status = BATT_STATUS_ID_INVALID; + } else + msm_chg.batt_status = BATT_STATUS_ABSENT; +} + +static enum power_supply_property msm_power_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, +}; + +static char *msm_power_supplied_to[] = { + "battery", +}; + +static int msm_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct msm_hardware_charger_priv *priv; + + priv = container_of(psy, struct msm_hardware_charger_priv, psy); + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !(priv->hw_chg_state == CHG_ABSENT_STATE); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = (priv->hw_chg_state == CHG_READY_STATE) + || (priv->hw_chg_state == CHG_CHARGING_STATE); + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property msm_batt_power_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int msm_batt_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = get_prop_batt_status(); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = get_prop_charge_type(); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = get_prop_batt_health(); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !(msm_chg.batt_status == BATT_STATUS_ABSENT); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = msm_chg.max_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = msm_chg.min_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = get_prop_battery_mvolts(); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_prop_batt_capacity(); + break; + default: + return -EINVAL; + } + return 0; +} + +static struct power_supply msm_psy_batt = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = msm_batt_power_props, + .num_properties = ARRAY_SIZE(msm_batt_power_props), + .get_property = msm_batt_power_get_property, +}; + +static int usb_chg_current; +static struct msm_hardware_charger_priv *usb_hw_chg_priv; +static void (*notify_vbus_state_func_ptr)(int); +static int usb_notified_of_insertion; + +/* this is passed to the hsusb via platform_data msm_otg_pdata */ +int msm_charger_register_vbus_sn(void (*callback)(int)) +{ + pr_debug(KERN_INFO "%s\n", __func__); + notify_vbus_state_func_ptr = callback; + return 0; +} + +/* this is passed to the hsusb via platform_data msm_otg_pdata */ +void msm_charger_unregister_vbus_sn(void (*callback)(int)) +{ + pr_debug(KERN_INFO "%s\n", __func__); + notify_vbus_state_func_ptr = NULL; +} + +static void notify_usb_of_the_plugin_event(struct msm_hardware_charger_priv + *hw_chg, int plugin) +{ + plugin = !!plugin; + if (plugin == 1 && usb_notified_of_insertion == 0) { + usb_notified_of_insertion = 1; + if (notify_vbus_state_func_ptr) { + dev_dbg(msm_chg.dev, "%s notifying plugin\n", __func__); + (*notify_vbus_state_func_ptr) (plugin); + } else + dev_dbg(msm_chg.dev, "%s unable to notify plugin\n", + __func__); + usb_hw_chg_priv = hw_chg; + } + if (plugin == 0 && usb_notified_of_insertion == 1) { + if (notify_vbus_state_func_ptr) { + dev_dbg(msm_chg.dev, "%s notifying unplugin\n", + __func__); + (*notify_vbus_state_func_ptr) (plugin); + } else + dev_dbg(msm_chg.dev, "%s unable to notify unplugin\n", + __func__); + usb_notified_of_insertion = 0; + usb_hw_chg_priv = NULL; + } +} + +static unsigned int msm_chg_get_batt_capacity_percent(void) +{ + unsigned int current_voltage = get_prop_battery_mvolts(); + unsigned int low_voltage = msm_chg.min_voltage; + unsigned int high_voltage = msm_chg.max_voltage; + + if (current_voltage <= low_voltage) + return 0; + else if (current_voltage >= high_voltage) + return 100; + else + return (current_voltage - low_voltage) * 100 + / (high_voltage - low_voltage); +} + +#ifdef DEBUG +static inline void debug_print(const char *func, + struct msm_hardware_charger_priv *hw_chg_priv) +{ + dev_dbg(msm_chg.dev, + "%s current=(%s)(s=%d)(r=%d) new=(%s)(s=%d)(r=%d) batt=%d En\n", + func, + msm_chg.current_chg_priv ? msm_chg.current_chg_priv-> + hw_chg->name : "none", + msm_chg.current_chg_priv ? msm_chg. + current_chg_priv->hw_chg_state : -1, + msm_chg.current_chg_priv ? msm_chg.current_chg_priv-> + hw_chg->rating : -1, + hw_chg_priv ? hw_chg_priv->hw_chg->name : "none", + hw_chg_priv ? hw_chg_priv->hw_chg_state : -1, + hw_chg_priv ? hw_chg_priv->hw_chg->rating : -1, + msm_chg.batt_status); +} +#else +static inline void debug_print(const char *func, + struct msm_hardware_charger_priv *hw_chg_priv) +{ +} +#endif + +static struct msm_hardware_charger_priv *find_best_charger(void) +{ + struct msm_hardware_charger_priv *hw_chg_priv; + struct msm_hardware_charger_priv *better; + int rating; + + better = NULL; + rating = 0; + + list_for_each_entry(hw_chg_priv, &msm_chg.msm_hardware_chargers, list) { + if (is_chg_capable_of_charging(hw_chg_priv)) { + if (hw_chg_priv->hw_chg->rating > rating) { + rating = hw_chg_priv->hw_chg->rating; + better = hw_chg_priv; + } + } + } + + return better; +} + +static int msm_charging_switched(struct msm_hardware_charger_priv *priv) +{ + int ret = 0; + + if (priv->hw_chg->charging_switched) + ret = priv->hw_chg->charging_switched(priv->hw_chg); + return ret; +} + +static int msm_stop_charging(struct msm_hardware_charger_priv *priv) +{ + int ret; + + ret = priv->hw_chg->stop_charging(priv->hw_chg); + if (!ret) + wake_unlock(&msm_chg.wl); + return ret; +} + +/* the best charger has been selected -start charging from current_chg_priv */ +static int msm_start_charging(void) +{ + int ret; + struct msm_hardware_charger_priv *priv; + + priv = msm_chg.current_chg_priv; + wake_lock(&msm_chg.wl); + ret = priv->hw_chg->start_charging(priv->hw_chg, msm_chg.max_voltage, + priv->max_source_current); + if (ret) { + wake_unlock(&msm_chg.wl); + dev_err(msm_chg.dev, "%s couldnt start chg error = %d\n", + priv->hw_chg->name, ret); + } else + priv->hw_chg_state = CHG_CHARGING_STATE; + + return ret; +} + +static void handle_charging_done(struct msm_hardware_charger_priv *priv) +{ + if (msm_chg.current_chg_priv == priv) { + if (msm_chg.current_chg_priv->hw_chg_state == + CHG_CHARGING_STATE) + if (msm_stop_charging(msm_chg.current_chg_priv)) { + dev_err(msm_chg.dev, "%s couldnt stop chg\n", + msm_chg.current_chg_priv->hw_chg->name); + } + msm_chg.current_chg_priv->hw_chg_state = CHG_READY_STATE; + + msm_chg.batt_status = BATT_STATUS_JUST_FINISHED_CHARGING; + dev_info(msm_chg.dev, "%s: stopping safety timer work\n", + __func__); + cancel_delayed_work(&msm_chg.teoc_work); + + if (msm_batt_gauge && msm_batt_gauge->monitor_for_recharging) + msm_batt_gauge->monitor_for_recharging(); + else + dev_err(msm_chg.dev, + "%s: no batt gauge recharge monitor\n", __func__); + } +} + +static void teoc(struct work_struct *work) +{ + /* we have been charging too long - stop charging */ + dev_info(msm_chg.dev, "%s: safety timer work expired\n", __func__); + + mutex_lock(&msm_chg.status_lock); + if (msm_chg.current_chg_priv != NULL + && msm_chg.current_chg_priv->hw_chg_state == CHG_CHARGING_STATE) { + handle_charging_done(msm_chg.current_chg_priv); + } + mutex_unlock(&msm_chg.status_lock); +} + +static void handle_battery_inserted(void) +{ + /* if a charger is already present start charging */ + if (msm_chg.current_chg_priv != NULL && + is_batt_status_capable_of_charging() && + !is_batt_status_charging()) { + if (msm_start_charging()) { + dev_err(msm_chg.dev, "%s couldnt start chg\n", + msm_chg.current_chg_priv->hw_chg->name); + return; + } + msm_chg.batt_status = BATT_STATUS_TRKL_CHARGING; + + dev_info(msm_chg.dev, "%s: starting safety timer work\n", + __func__); + queue_delayed_work(msm_chg.event_wq_thread, + &msm_chg.teoc_work, + round_jiffies_relative(msecs_to_jiffies + (msm_chg. + safety_time))); + } +} + +static void handle_battery_removed(void) +{ + /* if a charger is charging the battery stop it */ + if (msm_chg.current_chg_priv != NULL + && msm_chg.current_chg_priv->hw_chg_state == CHG_CHARGING_STATE) { + if (msm_stop_charging(msm_chg.current_chg_priv)) { + dev_err(msm_chg.dev, "%s couldnt stop chg\n", + msm_chg.current_chg_priv->hw_chg->name); + } + msm_chg.current_chg_priv->hw_chg_state = CHG_READY_STATE; + + dev_info(msm_chg.dev, "%s: stopping safety timer work\n", + __func__); + cancel_delayed_work(&msm_chg.teoc_work); + } +} + +static void update_heartbeat(struct work_struct *work) +{ + int temperature; + + if (msm_chg.batt_status == BATT_STATUS_ABSENT + || msm_chg.batt_status == BATT_STATUS_ID_INVALID) { + if (is_battery_present()) + if (is_battery_id_valid()) { + msm_chg.batt_status = BATT_STATUS_DISCHARGING; + handle_battery_inserted(); + } + } else { + if (!is_battery_present()) { + msm_chg.batt_status = BATT_STATUS_ABSENT; + handle_battery_removed(); + } + /* + * check battery id because a good battery could be removed + * and replaced with a invalid battery. + */ + if (!is_battery_id_valid()) { + msm_chg.batt_status = BATT_STATUS_ID_INVALID; + handle_battery_removed(); + } + } + pr_debug("msm-charger %s batt_status= %d\n", + __func__, msm_chg.batt_status); + + if (msm_chg.current_chg_priv + && msm_chg.current_chg_priv->hw_chg_state + == CHG_CHARGING_STATE) { + temperature = get_battery_temperature(); + /* TODO implement JEITA SPEC*/ + } + + /* notify that the voltage has changed + * the read of the capacity will trigger a + * voltage read*/ + power_supply_changed(&msm_psy_batt); + + if (msm_chg.stop_update) { + msm_chg.stop_update = 0; + return; + } + queue_delayed_work(msm_chg.event_wq_thread, + &msm_chg.update_heartbeat_work, + round_jiffies_relative(msecs_to_jiffies + (msm_chg.update_time))); +} + +/* set the charger state to READY before calling this */ +static void handle_charger_ready(struct msm_hardware_charger_priv *hw_chg_priv) +{ + debug_print(__func__, hw_chg_priv); + + if (msm_chg.current_chg_priv != NULL + && hw_chg_priv->hw_chg->rating > + msm_chg.current_chg_priv->hw_chg->rating) { + if (msm_chg.current_chg_priv->hw_chg_state == + CHG_CHARGING_STATE) { + if (msm_stop_charging(msm_chg.current_chg_priv)) { + dev_err(msm_chg.dev, "%s couldnt stop chg\n", + msm_chg.current_chg_priv->hw_chg->name); + return; + } + if (msm_charging_switched(msm_chg.current_chg_priv)) { + dev_err(msm_chg.dev, "%s couldnt stop chg\n", + msm_chg.current_chg_priv->hw_chg->name); + return; + } + } + msm_chg.current_chg_priv->hw_chg_state = CHG_READY_STATE; + msm_chg.current_chg_priv = NULL; + } + + if (msm_chg.current_chg_priv == NULL) { + msm_chg.current_chg_priv = hw_chg_priv; + dev_info(msm_chg.dev, + "%s: best charger = %s\n", __func__, + msm_chg.current_chg_priv->hw_chg->name); + + if (!is_batt_status_capable_of_charging()) + return; + + /* start charging from the new charger */ + if (!msm_start_charging()) { + /* if we simply switched chg continue with teoc timer + * else we update the batt state and set the teoc + * timer */ + if (!is_batt_status_charging()) { + dev_info(msm_chg.dev, + "%s: starting safety timer\n", __func__); + queue_delayed_work(msm_chg.event_wq_thread, + &msm_chg.teoc_work, + round_jiffies_relative + (msecs_to_jiffies + (msm_chg.safety_time))); + msm_chg.batt_status = BATT_STATUS_TRKL_CHARGING; + } + } else { + /* we couldnt start charging from the new readied + * charger */ + if (is_batt_status_charging()) + msm_chg.batt_status = BATT_STATUS_DISCHARGING; + } + } +} + +static void handle_charger_removed(struct msm_hardware_charger_priv + *hw_chg_removed, int new_state) +{ + struct msm_hardware_charger_priv *hw_chg_priv; + + debug_print(__func__, hw_chg_removed); + + if (msm_chg.current_chg_priv == hw_chg_removed) { + if (msm_chg.current_chg_priv->hw_chg_state + == CHG_CHARGING_STATE) { + if (msm_stop_charging(hw_chg_removed)) { + dev_err(msm_chg.dev, "%s couldnt stop chg\n", + msm_chg.current_chg_priv->hw_chg->name); + } + } + msm_chg.current_chg_priv = NULL; + } + + hw_chg_removed->hw_chg_state = new_state; + + if (msm_chg.current_chg_priv == NULL) { + hw_chg_priv = find_best_charger(); + if (hw_chg_priv == NULL) { + dev_info(msm_chg.dev, "%s: no chargers\n", __func__); + /* if the battery was Just finished charging + * we keep that state as is so that we dont rush + * in to charging the battery when a charger is + * plugged in shortly. */ + if (is_batt_status_charging()) + msm_chg.batt_status = BATT_STATUS_DISCHARGING; + } else { + msm_chg.current_chg_priv = hw_chg_priv; + dev_info(msm_chg.dev, + "%s: best charger = %s\n", __func__, + msm_chg.current_chg_priv->hw_chg->name); + + if (!is_batt_status_capable_of_charging()) + return; + + if (msm_start_charging()) { + /* we couldnt start charging for some reason */ + msm_chg.batt_status = BATT_STATUS_DISCHARGING; + } + } + } + + /* if we arent charging stop the safety timer */ + if (!is_batt_status_charging()) { + dev_info(msm_chg.dev, "%s: stopping safety timer work\n", + __func__); + cancel_delayed_work(&msm_chg.teoc_work); + } +} + +static void handle_event(struct msm_hardware_charger *hw_chg, int event) +{ + struct msm_hardware_charger_priv *priv = NULL; + + /* + * if hw_chg is NULL then this event comes from non-charger + * parties like battery gauge + */ + if (hw_chg) + priv = hw_chg->charger_private; + + mutex_lock(&msm_chg.status_lock); + + switch (event) { + case CHG_INSERTED_EVENT: + if (priv->hw_chg_state != CHG_ABSENT_STATE) { + dev_info(msm_chg.dev, + "%s insertion detected when cbl present", + hw_chg->name); + break; + } + update_batt_status(); + if (hw_chg->type == CHG_TYPE_USB) { + priv->hw_chg_state = CHG_PRESENT_STATE; + notify_usb_of_the_plugin_event(priv, 1); + if (usb_chg_current) { + priv->max_source_current = usb_chg_current; + usb_chg_current = 0; + /* usb has already indicated us to charge */ + priv->hw_chg_state = CHG_READY_STATE; + handle_charger_ready(priv); + } + } else { + priv->hw_chg_state = CHG_READY_STATE; + handle_charger_ready(priv); + } + break; + case CHG_ENUMERATED_EVENT: /* only in USB types */ + if (priv->hw_chg_state == CHG_ABSENT_STATE) { + dev_info(msm_chg.dev, "%s enum withuot presence\n", + hw_chg->name); + break; + } + update_batt_status(); + dev_dbg(msm_chg.dev, "%s enum with %dmA to draw\n", + hw_chg->name, priv->max_source_current); + if (priv->max_source_current == 0) { + /* usb subsystem doesnt want us to draw + * charging current */ + /* act as if the charge is removed */ + if (priv->hw_chg_state != CHG_PRESENT_STATE) + handle_charger_removed(priv, CHG_PRESENT_STATE); + } else { + if (priv->hw_chg_state != CHG_READY_STATE) { + priv->hw_chg_state = CHG_READY_STATE; + handle_charger_ready(priv); + } + } + break; + case CHG_REMOVED_EVENT: + if (priv->hw_chg_state == CHG_ABSENT_STATE) { + dev_info(msm_chg.dev, "%s cable already removed\n", + hw_chg->name); + break; + } + update_batt_status(); + if (hw_chg->type == CHG_TYPE_USB) { + usb_chg_current = 0; + notify_usb_of_the_plugin_event(priv, 0); + } + handle_charger_removed(priv, CHG_ABSENT_STATE); + break; + case CHG_DONE_EVENT: + if (priv->hw_chg_state == CHG_CHARGING_STATE) + handle_charging_done(priv); + break; + case CHG_BATT_BEGIN_FAST_CHARGING: + /* only update if we are TRKL charging */ + if (msm_chg.batt_status == BATT_STATUS_TRKL_CHARGING) + msm_chg.batt_status = BATT_STATUS_FAST_CHARGING; + break; + case CHG_BATT_NEEDS_RECHARGING: + msm_chg.batt_status = BATT_STATUS_DISCHARGING; + handle_battery_inserted(); + priv = msm_chg.current_chg_priv; + break; + case CHG_BATT_TEMP_OUTOFRANGE: + /* the batt_temp out of range can trigger + * when the battery is absent */ + if (!is_battery_present() + && msm_chg.batt_status != BATT_STATUS_ABSENT) { + msm_chg.batt_status = BATT_STATUS_ABSENT; + handle_battery_removed(); + break; + } + if (msm_chg.batt_status == BATT_STATUS_TEMPERATURE_OUT_OF_RANGE) + break; + msm_chg.batt_status = BATT_STATUS_TEMPERATURE_OUT_OF_RANGE; + handle_battery_removed(); + break; + case CHG_BATT_TEMP_INRANGE: + if (msm_chg.batt_status != BATT_STATUS_TEMPERATURE_OUT_OF_RANGE) + break; + msm_chg.batt_status = BATT_STATUS_ID_INVALID; + /* check id */ + if (!is_battery_id_valid()) + break; + /* assume that we are discharging from the battery + * and act as if the battery was inserted + * if a charger is present charging will be resumed */ + msm_chg.batt_status = BATT_STATUS_DISCHARGING; + handle_battery_inserted(); + break; + case CHG_BATT_INSERTED: + if (msm_chg.batt_status != BATT_STATUS_ABSENT) + break; + /* debounce */ + if (!is_battery_present()) + break; + msm_chg.batt_status = BATT_STATUS_ID_INVALID; + if (!is_battery_id_valid()) + break; + /* assume that we are discharging from the battery */ + msm_chg.batt_status = BATT_STATUS_DISCHARGING; + /* check if a charger is present */ + handle_battery_inserted(); + break; + case CHG_BATT_REMOVED: + if (msm_chg.batt_status == BATT_STATUS_ABSENT) + break; + /* debounce */ + if (is_battery_present()) + break; + msm_chg.batt_status = BATT_STATUS_ABSENT; + handle_battery_removed(); + break; + case CHG_BATT_STATUS_CHANGE: + /* TODO battery SOC like battery-alarm/charging-full features + can be added here for future improvement */ + break; + } + dev_dbg(msm_chg.dev, "%s %d done batt_status=%d\n", __func__, + event, msm_chg.batt_status); + + /* update userspace */ + if (msm_batt_gauge) + power_supply_changed(&msm_psy_batt); + if (priv) + power_supply_changed(&priv->psy); + + mutex_unlock(&msm_chg.status_lock); +} + +static int msm_chg_dequeue_event(struct msm_charger_event **event) +{ + unsigned long flags; + + spin_lock_irqsave(&msm_chg.queue_lock, flags); + if (msm_chg.queue_count == 0) { + spin_unlock_irqrestore(&msm_chg.queue_lock, flags); + return -EINVAL; + } + *event = &msm_chg.queue[msm_chg.head]; + msm_chg.head = (msm_chg.head + 1) % MSM_CHG_MAX_EVENTS; + pr_debug("%s dequeueing %d\n", __func__, (*event)->event); + msm_chg.queue_count--; + spin_unlock_irqrestore(&msm_chg.queue_lock, flags); + return 0; +} + +static int msm_chg_enqueue_event(struct msm_hardware_charger *hw_chg, + enum msm_hardware_charger_event event) +{ + unsigned long flags; + + spin_lock_irqsave(&msm_chg.queue_lock, flags); + if (msm_chg.queue_count == MSM_CHG_MAX_EVENTS) { + spin_unlock_irqrestore(&msm_chg.queue_lock, flags); + pr_err("%s: queue full cannot enqueue %d\n", + __func__, event); + return -EAGAIN; + } + pr_debug("%s queueing %d\n", __func__, event); + msm_chg.queue[msm_chg.tail].event = event; + msm_chg.queue[msm_chg.tail].hw_chg = hw_chg; + msm_chg.tail = (msm_chg.tail + 1)%MSM_CHG_MAX_EVENTS; + msm_chg.queue_count++; + spin_unlock_irqrestore(&msm_chg.queue_lock, flags); + return 0; +} + +static void process_events(struct work_struct *work) +{ + struct msm_charger_event *event; + int rc; + + do { + rc = msm_chg_dequeue_event(&event); + if (!rc) + handle_event(event->hw_chg, event->event); + } while (!rc); +} + +/* USB calls these to tell us how much charging current we should draw */ +void msm_charger_vbus_draw(unsigned int mA) +{ + if (usb_hw_chg_priv) { + usb_hw_chg_priv->max_source_current = mA; + msm_charger_notify_event(usb_hw_chg_priv->hw_chg, + CHG_ENUMERATED_EVENT); + } else + /* remember the current, to be used when charger is ready */ + usb_chg_current = mA; +} + +static int __init determine_initial_batt_status(void) +{ + int rc; + + if (is_battery_present()) + if (is_battery_id_valid()) + if (is_battery_temp_within_range()) + msm_chg.batt_status = BATT_STATUS_DISCHARGING; + else + msm_chg.batt_status + = BATT_STATUS_TEMPERATURE_OUT_OF_RANGE; + else + msm_chg.batt_status = BATT_STATUS_ID_INVALID; + else + msm_chg.batt_status = BATT_STATUS_ABSENT; + + if (is_batt_status_capable_of_charging()) + handle_battery_inserted(); + + rc = power_supply_register(msm_chg.dev, &msm_psy_batt); + if (rc < 0) { + dev_err(msm_chg.dev, "%s: power_supply_register failed" + " rc=%d\n", __func__, rc); + return rc; + } + + /* start updaing the battery powersupply every msm_chg.update_time + * milliseconds */ + queue_delayed_work(msm_chg.event_wq_thread, + &msm_chg.update_heartbeat_work, + round_jiffies_relative(msecs_to_jiffies + (msm_chg.update_time))); + + pr_debug("%s:OK batt_status=%d\n", __func__, msm_chg.batt_status); + return 0; +} + +static int __devinit msm_charger_probe(struct platform_device *pdev) +{ + msm_chg.dev = &pdev->dev; + if (pdev->dev.platform_data) { + unsigned int milli_secs; + + struct msm_charger_platform_data *pdata + = + (struct msm_charger_platform_data *)pdev->dev.platform_data; + + milli_secs = pdata->safety_time * 60 * MSEC_PER_SEC; + if (milli_secs > jiffies_to_msecs(MAX_JIFFY_OFFSET)) { + dev_warn(&pdev->dev, "%s: safety time too large" + "%dms\n", __func__, milli_secs); + milli_secs = jiffies_to_msecs(MAX_JIFFY_OFFSET); + } + msm_chg.safety_time = milli_secs; + + milli_secs = pdata->update_time * 60 * MSEC_PER_SEC; + if (milli_secs > jiffies_to_msecs(MAX_JIFFY_OFFSET)) { + dev_warn(&pdev->dev, "%s: safety time too large" + "%dms\n", __func__, milli_secs); + milli_secs = jiffies_to_msecs(MAX_JIFFY_OFFSET); + } + msm_chg.update_time = milli_secs; + + msm_chg.max_voltage = pdata->max_voltage; + msm_chg.min_voltage = pdata->min_voltage; + msm_chg.get_batt_capacity_percent = + pdata->get_batt_capacity_percent; + } + if (msm_chg.safety_time == 0) + msm_chg.safety_time = CHARGING_TEOC_MS; + if (msm_chg.update_time == 0) + msm_chg.update_time = UPDATE_TIME_MS; + if (msm_chg.max_voltage == 0) + msm_chg.max_voltage = DEFAULT_BATT_MAX_V; + if (msm_chg.min_voltage == 0) + msm_chg.min_voltage = DEFAULT_BATT_MIN_V; + if (msm_chg.get_batt_capacity_percent == NULL) + msm_chg.get_batt_capacity_percent = + msm_chg_get_batt_capacity_percent; + + mutex_init(&msm_chg.status_lock); + INIT_DELAYED_WORK(&msm_chg.teoc_work, teoc); + INIT_DELAYED_WORK(&msm_chg.update_heartbeat_work, update_heartbeat); + + wake_lock_init(&msm_chg.wl, WAKE_LOCK_SUSPEND, "msm_charger"); + return 0; +} + +static int __devexit msm_charger_remove(struct platform_device *pdev) +{ + mutex_destroy(&msm_chg.status_lock); + power_supply_unregister(&msm_psy_batt); + return 0; +} + +int msm_charger_notify_event(struct msm_hardware_charger *hw_chg, + enum msm_hardware_charger_event event) +{ + msm_chg_enqueue_event(hw_chg, event); + queue_work(msm_chg.event_wq_thread, &msm_chg.queue_work); + return 0; +} +EXPORT_SYMBOL(msm_charger_notify_event); + +int msm_charger_register(struct msm_hardware_charger *hw_chg) +{ + struct msm_hardware_charger_priv *priv; + int rc = 0; + + if (!msm_chg.inited) { + pr_err("%s: msm_chg is NULL,Too early to register\n", __func__); + return -EAGAIN; + } + + if (hw_chg->start_charging == NULL + || hw_chg->stop_charging == NULL + || hw_chg->name == NULL + || hw_chg->rating == 0) { + pr_err("%s: invalid hw_chg\n", __func__); + return -EINVAL; + } + + priv = kzalloc(sizeof *priv, GFP_KERNEL); + if (priv == NULL) { + dev_err(msm_chg.dev, "%s kzalloc failed\n", __func__); + return -ENOMEM; + } + + priv->psy.name = hw_chg->name; + if (hw_chg->type == CHG_TYPE_USB) + priv->psy.type = POWER_SUPPLY_TYPE_USB; + else + priv->psy.type = POWER_SUPPLY_TYPE_MAINS; + + priv->psy.supplied_to = msm_power_supplied_to; + priv->psy.num_supplicants = ARRAY_SIZE(msm_power_supplied_to); + priv->psy.properties = msm_power_props; + priv->psy.num_properties = ARRAY_SIZE(msm_power_props); + priv->psy.get_property = msm_power_get_property; + + rc = power_supply_register(NULL, &priv->psy); + if (rc) { + dev_err(msm_chg.dev, "%s power_supply_register failed\n", + __func__); + goto out; + } + + priv->hw_chg = hw_chg; + priv->hw_chg_state = CHG_ABSENT_STATE; + INIT_LIST_HEAD(&priv->list); + mutex_lock(&msm_chg.msm_hardware_chargers_lock); + list_add_tail(&priv->list, &msm_chg.msm_hardware_chargers); + mutex_unlock(&msm_chg.msm_hardware_chargers_lock); + hw_chg->charger_private = (void *)priv; + return 0; + +out: + wake_lock_destroy(&msm_chg.wl); + kfree(priv); + return rc; +} +EXPORT_SYMBOL(msm_charger_register); + +void msm_battery_gauge_register(struct msm_battery_gauge *batt_gauge) +{ + if (msm_batt_gauge) { + msm_batt_gauge = batt_gauge; + pr_err("msm-charger %s multiple battery gauge called\n", + __func__); + } else { + msm_batt_gauge = batt_gauge; + determine_initial_batt_status(); + } +} +EXPORT_SYMBOL(msm_battery_gauge_register); + +void msm_battery_gauge_unregister(struct msm_battery_gauge *batt_gauge) +{ + msm_batt_gauge = NULL; +} +EXPORT_SYMBOL(msm_battery_gauge_unregister); + +int msm_charger_unregister(struct msm_hardware_charger *hw_chg) +{ + struct msm_hardware_charger_priv *priv; + + priv = (struct msm_hardware_charger_priv *)(hw_chg->charger_private); + mutex_lock(&msm_chg.msm_hardware_chargers_lock); + list_del(&priv->list); + mutex_unlock(&msm_chg.msm_hardware_chargers_lock); + wake_lock_destroy(&msm_chg.wl); + power_supply_unregister(&priv->psy); + kfree(priv); + return 0; +} +EXPORT_SYMBOL(msm_charger_unregister); + +static int msm_charger_suspend(struct device *dev) +{ + dev_dbg(msm_chg.dev, "%s suspended\n", __func__); + msm_chg.stop_update = 1; + cancel_delayed_work(&msm_chg.update_heartbeat_work); + mutex_lock(&msm_chg.status_lock); + handle_battery_removed(); + mutex_unlock(&msm_chg.status_lock); + return 0; +} + +static int msm_charger_resume(struct device *dev) +{ + dev_dbg(msm_chg.dev, "%s resumed\n", __func__); + msm_chg.stop_update = 0; + /* start updaing the battery powersupply every msm_chg.update_time + * milliseconds */ + queue_delayed_work(msm_chg.event_wq_thread, + &msm_chg.update_heartbeat_work, + round_jiffies_relative(msecs_to_jiffies + (msm_chg.update_time))); + mutex_lock(&msm_chg.status_lock); + handle_battery_inserted(); + mutex_unlock(&msm_chg.status_lock); + return 0; +} + +static SIMPLE_DEV_PM_OPS(msm_charger_pm_ops, + msm_charger_suspend, msm_charger_resume); + +static struct platform_driver msm_charger_driver = { + .probe = msm_charger_probe, + .remove = __devexit_p(msm_charger_remove), + .driver = { + .name = "msm-charger", + .owner = THIS_MODULE, + .pm = &msm_charger_pm_ops, + }, +}; + +static int __init msm_charger_init(void) +{ + int rc; + + INIT_LIST_HEAD(&msm_chg.msm_hardware_chargers); + msm_chg.count_chargers = 0; + mutex_init(&msm_chg.msm_hardware_chargers_lock); + + msm_chg.queue = kzalloc(sizeof(struct msm_charger_event) + * MSM_CHG_MAX_EVENTS, + GFP_KERNEL); + if (!msm_chg.queue) { + rc = -ENOMEM; + goto out; + } + msm_chg.tail = 0; + msm_chg.head = 0; + spin_lock_init(&msm_chg.queue_lock); + msm_chg.queue_count = 0; + INIT_WORK(&msm_chg.queue_work, process_events); + msm_chg.event_wq_thread = create_workqueue("msm_charger_eventd"); + if (!msm_chg.event_wq_thread) { + rc = -ENOMEM; + goto free_queue; + } + rc = platform_driver_register(&msm_charger_driver); + if (rc < 0) { + pr_err("%s: FAIL: platform_driver_register. rc = %d\n", + __func__, rc); + goto destroy_wq_thread; + } + msm_chg.inited = 1; + return 0; + +destroy_wq_thread: + destroy_workqueue(msm_chg.event_wq_thread); +free_queue: + kfree(msm_chg.queue); +out: + return rc; +} + +static void __exit msm_charger_exit(void) +{ + flush_workqueue(msm_chg.event_wq_thread); + destroy_workqueue(msm_chg.event_wq_thread); + kfree(msm_chg.queue); + platform_driver_unregister(&msm_charger_driver); +} + +module_init(msm_charger_init); +module_exit(msm_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Abhijeet Dharmapurikar "); +MODULE_DESCRIPTION("Battery driver for Qualcomm MSM chipsets."); +MODULE_VERSION("1.0"); diff --git a/drivers/power/pm8058_usb_fix.c b/drivers/power/pm8058_usb_fix.c new file mode 100644 index 00000000000..80b1f87cd90 --- /dev/null +++ b/drivers/power/pm8058_usb_fix.c @@ -0,0 +1,357 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Config Regs and their bits*/ +#define PM8058_CHG_TEST 0x75 +#define IGNORE_LL 2 + +#define PM8058_CHG_TEST_2 0xEA +#define PM8058_CHG_TEST_3 0xEB +#define PM8058_OVP_TEST_REG 0xF6 +#define FORCE_OVP_OFF 3 + +#define PM8058_CHG_CNTRL 0x1E +#define CHG_TRICKLE_EN 7 +#define CHG_USB_SUSPEND 6 +#define CHG_IMON_CAL 5 +#define CHG_IMON_GAIN 4 +#define CHG_VBUS_FROM_BOOST_OVRD 2 +#define CHG_CHARGE_DIS 1 +#define CHG_VCP_EN 0 + +#define PM8058_CHG_CNTRL_2 0xD8 +#define ATC_DIS 7 /* coincell backed */ +#define CHARGE_AUTO_DIS 6 +#define DUMB_CHG_OVRD 5 /* coincell backed */ +#define ENUM_DONE 4 +#define CHG_TEMP_MODE 3 +#define CHG_BATT_TEMP_DIS 1 /* coincell backed */ +#define CHG_FAILED_CLEAR 0 + +#define PM8058_CHG_VMAX_SEL 0x21 +#define PM8058_CHG_VBAT_DET 0xD9 +#define PM8058_CHG_IMAX 0x1F +#define PM8058_CHG_TRICKLE 0xDB +#define PM8058_CHG_ITERM 0xDC +#define PM8058_CHG_TTRKL_MAX 0xE1 +#define PM8058_CHG_TCHG_MAX 0xE4 +#define PM8058_CHG_TEMP_THRESH 0xE2 +#define PM8058_CHG_TEMP_REG 0xE3 +#define PM8058_CHG_PULSE 0x22 + +/* IRQ STATUS and CLEAR */ +#define PM8058_CHG_STATUS_CLEAR_IRQ_1 0x31 +#define PM8058_CHG_STATUS_CLEAR_IRQ_3 0x33 +#define PM8058_CHG_STATUS_CLEAR_IRQ_10 0xB3 +#define PM8058_CHG_STATUS_CLEAR_IRQ_11 0xB4 + +/* IRQ MASKS */ +#define PM8058_CHG_MASK_IRQ_1 0x38 + +#define PM8058_CHG_MASK_IRQ_3 0x3A +#define PM8058_CHG_MASK_IRQ_10 0xBA +#define PM8058_CHG_MASK_IRQ_11 0xBB + +/* IRQ Real time status regs */ +#define PM8058_CHG_STATUS_RT_1 0x3F +#define STATUS_RTCHGVAL 7 +#define STATUS_RTCHGINVAL 6 +#define STATUS_RTBATT_REPLACE 5 +#define STATUS_RTVBATDET_LOW 4 +#define STATUS_RTCHGILIM 3 +#define STATUS_RTPCTDONE 1 +#define STATUS_RTVCP 0 +#define PM8058_CHG_STATUS_RT_3 0x41 +#define PM8058_CHG_STATUS_RT_10 0xC1 +#define PM8058_CHG_STATUS_RT_11 0xC2 + +/* VTRIM */ +#define PM8058_CHG_VTRIM 0x1D +#define PM8058_CHG_VBATDET_TRIM 0x1E +#define PM8058_CHG_ITRIM 0x1F +#define PM8058_CHG_TTRIM 0x20 + +#define AUTO_CHARGING_VMAXSEL 4200 +#define AUTO_CHARGING_FAST_TIME_MAX_MINUTES 512 +#define AUTO_CHARGING_TRICKLE_TIME_MINUTES 30 +#define AUTO_CHARGING_VEOC_ITERM 100 +#define AUTO_CHARGING_IEOC_ITERM 160 + +#define AUTO_CHARGING_VBATDET 4150 +#define AUTO_CHARGING_VEOC_VBATDET 4100 +#define AUTO_CHARGING_VEOC_TCHG 16 +#define AUTO_CHARGING_VEOC_TCHG_FINAL_CYCLE 32 +#define AUTO_CHARGING_VEOC_BEGIN_TIME_MS 5400000 + +#define AUTO_CHARGING_VEOC_VBAT_LOW_CHECK_TIME_MS 60000 +#define AUTO_CHARGING_RESUME_CHARGE_DETECTION_COUNTER 5 + +#define PM8058_CHG_I_STEP_MA 50 +#define PM8058_CHG_I_MIN_MA 50 +#define PM8058_CHG_T_TCHG_SHIFT 2 +#define PM8058_CHG_I_TERM_STEP_MA 10 +#define PM8058_CHG_V_STEP_MV 25 +#define PM8058_CHG_V_MIN_MV 2400 +/* + * enum pmic_chg_interrupts: pmic interrupts + * @CHGVAL_IRQ: charger V between 3.3 and 7.9 + * @CHGINVAL_IRQ: charger V outside 3.3 and 7.9 + * @VBATDET_LOW_IRQ: VBAT < VBATDET + * @VCP_IRQ: VDD went below VBAT: BAT_FET is turned on + * @CHGILIM_IRQ: mA consumed>IMAXSEL: chgloop draws less mA + * @ATC_DONE_IRQ: Auto Trickle done + * @ATCFAIL_IRQ: Auto Trickle fail + * @AUTO_CHGDONE_IRQ: Auto chg done + * @AUTO_CHGFAIL_IRQ: time exceeded w/o reaching term current + * @CHGSTATE_IRQ: something happend causing a state change + * @FASTCHG_IRQ: trkl charging completed: moving to fastchg + * @CHG_END_IRQ: mA has dropped to termination current + * @BATTTEMP_IRQ: batt temp is out of range + * @CHGHOT_IRQ: the pass device is too hot + * @CHGTLIMIT_IRQ: unused + * @CHG_GONE_IRQ: charger was removed + * @VCPMAJOR_IRQ: vcp major + * @VBATDET_IRQ: VBAT >= VBATDET + * @BATFET_IRQ: BATFET closed + * @BATT_REPLACE_IRQ: + * @BATTCONNECT_IRQ: + */ +enum pmic_chg_interrupts { + CHGVAL_IRQ, + CHGINVAL_IRQ, + VBATDET_LOW_IRQ, + VCP_IRQ, + CHGILIM_IRQ, + ATC_DONE_IRQ, + ATCFAIL_IRQ, + AUTO_CHGDONE_IRQ, + AUTO_CHGFAIL_IRQ, + CHGSTATE_IRQ, + FASTCHG_IRQ, + CHG_END_IRQ, + BATTTEMP_IRQ, + CHGHOT_IRQ, + CHGTLIMIT_IRQ, + CHG_GONE_IRQ, + VCPMAJOR_IRQ, + VBATDET_IRQ, + BATFET_IRQ, + BATT_REPLACE_IRQ, + BATTCONNECT_IRQ, + PMIC_CHG_MAX_INTS +}; + +struct pm8058_charger { + struct pmic_charger_pdata *pdata; + struct pm8058_chip *pm_chip; + struct device *dev; + + int pmic_chg_irq[PMIC_CHG_MAX_INTS]; + DECLARE_BITMAP(enabled_irqs, PMIC_CHG_MAX_INTS); + + struct delayed_work check_vbat_low_work; + struct delayed_work veoc_begin_work; + int waiting_for_topoff; + int waiting_for_veoc; + int current_charger_current; + + struct msm_xo_voter *voter; + struct dentry *dent; +}; + +static struct pm8058_charger pm8058_chg; + +static int pm_chg_get_rt_status(int irq) +{ + int count = 3; + int ret; + + while ((ret = + pm8058_irq_get_rt_status(pm8058_chg.pm_chip, irq)) == -EAGAIN + && count--) { + dev_info(pm8058_chg.dev, "%s trycount=%d\n", __func__, count); + cpu_relax(); + } + if (ret == -EAGAIN) + return 0; + else + return ret; +} + +static int is_chg_plugged_in(void) +{ + return pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]); +} + +static irqreturn_t pm8058_chg_chgval_handler(int irq, void *dev_id) +{ + u8 old, temp; + int ret; + + if (!is_chg_plugged_in()) { /*this debounces it */ + ret = pm8058_read(pm8058_chg.pm_chip, PM8058_OVP_TEST_REG, + &old, 1); + temp = old | BIT(FORCE_OVP_OFF); + ret = pm8058_write(pm8058_chg.pm_chip, PM8058_OVP_TEST_REG, + &temp, 1); + temp = 0xFC; + ret = pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, + &temp, 1); + pr_debug("%s forced wrote 0xFC to test ret=%d\n", + __func__, ret); + /* 20 ms sleep is for the VCHG to discharge */ + msleep(20); + temp = 0xF0; + ret = pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, + &temp, 1); + ret = pm8058_write(pm8058_chg.pm_chip, PM8058_OVP_TEST_REG, + &old, 1); + } + + return IRQ_HANDLED; +} + +static void free_irqs(void) +{ + int i; + + for (i = 0; i < PMIC_CHG_MAX_INTS; i++) + if (pm8058_chg.pmic_chg_irq[i]) { + free_irq(pm8058_chg.pmic_chg_irq[i], NULL); + pm8058_chg.pmic_chg_irq[i] = 0; + } +} + +static int __devinit request_irqs(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + ret = 0; + bitmap_fill(pm8058_chg.enabled_irqs, PMIC_CHG_MAX_INTS); + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "CHGVAL"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource CHGVAL\n", __func__); + goto err_out; + } else { + ret = request_any_context_irq(res->start, + pm8058_chg_chgval_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[CHGVAL_IRQ] = res->start; + } + } + + return 0; + +err_out: + free_irqs(); + return -EINVAL; +} + +static int pm8058_usb_voltage_lower_limit(void) +{ + u8 temp, old; + int ret = 0; + + temp = 0x10; + ret |= pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1); + ret |= pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEST, &old, 1); + old = old & ~BIT(IGNORE_LL); + temp = 0x90 | (0xF & old); + pr_debug("%s writing 0x%x to test\n", __func__, temp); + ret |= pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1); + + return ret; +} + +static int __devinit pm8058_charger_probe(struct platform_device *pdev) +{ + struct pm8058_chip *pm_chip; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + pr_err("%s:no parent data passed in.\n", __func__); + return -EFAULT; + } + + pm8058_chg.pm_chip = pm_chip; + pm8058_chg.pdata = pdev->dev.platform_data; + pm8058_chg.dev = &pdev->dev; + + if (request_irqs(pdev)) { + pr_err("%s: couldnt register interrupts\n", __func__); + return -EINVAL; + } + + if (pm8058_usb_voltage_lower_limit()) { + pr_err("%s: couldnt write to IGNORE_LL\n", __func__); + return -EINVAL; + } + + return 0; +} + +static int __devexit pm8058_charger_remove(struct platform_device *pdev) +{ + free_irqs(); + return 0; +} + +static struct platform_driver pm8058_charger_driver = { + .probe = pm8058_charger_probe, + .remove = __devexit_p(pm8058_charger_remove), + .driver = { + .name = "pm-usb-fix", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_charger_init(void) +{ + return platform_driver_register(&pm8058_charger_driver); +} + +static void __exit pm8058_charger_exit(void) +{ + platform_driver_unregister(&pm8058_charger_driver); +} + +late_initcall(pm8058_charger_init); +module_exit(pm8058_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 BATTERY driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8058_charger"); diff --git a/drivers/power/pm8921-bms.c b/drivers/power/pm8921-bms.c new file mode 100644 index 00000000000..6ad6a18f98c --- /dev/null +++ b/drivers/power/pm8921-bms.c @@ -0,0 +1,1178 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BMS_CONTROL 0x224 +#define BMS_OUTPUT0 0x230 +#define BMS_OUTPUT1 0x231 +#define BMS_TEST1 0x237 +#define CCADC_ANA_PARAM 0x240 +#define CCADC_DIG_PARAM 0x241 +#define CCADC_RSV 0x242 +#define CCADC_DATA0 0x244 +#define CCADC_DATA1 0x245 +#define CCADC_OFFSET_TRIM1 0x34A +#define CCADC_OFFSET_TRIM0 0x34B + +#define ADC_ARB_SECP_CNTRL 0x190 +#define ADC_ARB_SECP_AMUX_CNTRL 0x191 +#define ADC_ARB_SECP_ANA_PARAM 0x192 +#define ADC_ARB_SECP_RSV 0x194 +#define ADC_ARB_SECP_DATA1 0x195 +#define ADC_ARB_SECP_DATA0 0x196 + +enum pmic_bms_interrupts { + PM8921_BMS_SBI_WRITE_OK, + PM8921_BMS_CC_THR, + PM8921_BMS_VSENSE_THR, + PM8921_BMS_VSENSE_FOR_R, + PM8921_BMS_OCV_FOR_R, + PM8921_BMS_GOOD_OCV, + PM8921_BMS_VSENSE_AVG, + PM_BMS_MAX_INTS, +}; + +/** + * struct pm8921_bms_chip -device information + * @dev: device pointer to access the parent + * @dent: debugfs directory + * @r_sense: batt sense resistance value + * @i_test: peak current + * @v_failure: battery dead voltage + * @fcc: battery capacity + * + */ +struct pm8921_bms_chip { + struct device *dev; + struct dentry *dent; + unsigned int r_sense; + unsigned int i_test; + unsigned int v_failure; + unsigned int fcc; + struct single_row_lut *fcc_temp_lut; + struct single_row_lut *fcc_sf_lut; + struct pc_temp_ocv_lut *pc_temp_ocv_lut; + struct pc_sf_lut *pc_sf_lut; + struct delayed_work calib_work; + unsigned int calib_delay_ms; + unsigned int pmic_bms_irq[PM_BMS_MAX_INTS]; + DECLARE_BITMAP(enabled_irqs, PM_BMS_MAX_INTS); +}; + +static struct pm8921_bms_chip *the_chip; + +#define DEFAULT_RBATT_MOHMS 128 +#define DEFAULT_UNUSABLE_CHARGE_MAH 10 +#define DEFAULT_OCV_MICROVOLTS 3900000 +#define DEFAULT_REMAINING_CHARGE_MAH 990 +#define DEFAULT_COULUMB_COUNTER 0 +#define DEFAULT_CHARGE_CYCLES 0 + +static int last_rbatt = -EINVAL; +static int last_fcc = -EINVAL; +static int last_unusable_charge = -EINVAL; +static int last_ocv_uv = -EINVAL; +static int last_remaining_charge = -EINVAL; +static int last_coulumb_counter = -EINVAL; +static int last_soc = -EINVAL; + +static int last_chargecycles = DEFAULT_CHARGE_CYCLES; +static int last_charge_increase; + +module_param(last_rbatt, int, 0644); +module_param(last_fcc, int, 0644); +module_param(last_unusable_charge, int, 0644); +module_param(last_ocv_uv, int, 0644); +module_param(last_remaining_charge, int, 0644); +module_param(last_coulumb_counter, int, 0644); +module_param(last_chargecycles, int, 0644); +module_param(last_charge_increase, int, 0644); + +static int pm_bms_get_rt_status(struct pm8921_bms_chip *chip, int irq_id) +{ + return pm8xxx_read_irq_stat(chip->dev->parent, + chip->pmic_bms_irq[irq_id]); +} + +static void pm8921_bms_disable_irq(struct pm8921_bms_chip *chip, int interrupt) +{ + if (__test_and_clear_bit(interrupt, chip->enabled_irqs)) { + dev_dbg(chip->dev, "%d\n", chip->pmic_bms_irq[interrupt]); + disable_irq_nosync(chip->pmic_bms_irq[interrupt]); + } +} + +static int pm_bms_masked_write(struct pm8921_bms_chip *chip, u16 addr, + u8 mask, u8 val) +{ + int rc; + u8 reg; + + rc = pm8xxx_readb(chip->dev->parent, addr, ®); + if (rc) { + pr_err("read failed addr = %03X, rc = %d\n", addr, rc); + return rc; + } + reg &= ~mask; + reg |= val & mask; + rc = pm8xxx_writeb(chip->dev->parent, addr, reg); + if (rc) { + pr_err("write failed addr = %03X, rc = %d\n", addr, rc); + return rc; + } + return 0; +} + +#define SELECT_OUTPUT_DATA 0x1C +#define SELECT_OUTPUT_TYPE_SHIFT 2 +#define OCV_FOR_RBATT 0x0 +#define VSENSE_FOR_RBATT 0x1 +#define VBATT_FOR_RBATT 0x2 +#define CC_MSB 0x3 +#define CC_LSB 0x4 +#define LAST_GOOD_OCV_VALUE 0x5 +#define VSENSE_AVG 0x6 +#define VBATT_AVG 0x7 + +static int pm_bms_read_output_data(struct pm8921_bms_chip *chip, int type, + int16_t *result) +{ + int rc; + u8 reg; + + if (!result) { + pr_err("result pointer null\n"); + return -EINVAL; + } + *result = 0; + if (type < OCV_FOR_RBATT || type > VBATT_AVG) { + pr_err("invalid type %d asked to read\n", type); + return -EINVAL; + } + rc = pm_bms_masked_write(chip, BMS_CONTROL, SELECT_OUTPUT_DATA, + type << SELECT_OUTPUT_TYPE_SHIFT); + if (rc) { + pr_err("fail to select %d type in BMS_CONTROL rc = %d\n", + type, rc); + return rc; + } + + rc = pm8xxx_readb(chip->dev->parent, BMS_OUTPUT0, ®); + if (rc) { + pr_err("fail to read BMS_OUTPUT0 for type %d rc = %d\n", + type, rc); + return rc; + } + *result = reg; + rc = pm8xxx_readb(chip->dev->parent, BMS_OUTPUT1, ®); + if (rc) { + pr_err("fail to read BMS_OUTPUT1 for type %d rc = %d\n", + type, rc); + return rc; + } + *result |= reg << 8; + pr_debug("type %d result %x", type, *result); + return 0; +} + +#define V_PER_BIT_MUL_FACTOR 977 +#define V_PER_BIT_DIV_FACTOR 10 +#define CONV_READING(a) (((a) * (int)V_PER_BIT_MUL_FACTOR)\ + /V_PER_BIT_DIV_FACTOR) + +/* returns the signed value read from the hardware */ +static int read_cc(struct pm8921_bms_chip *chip, int *result) +{ + int rc; + uint16_t msw, lsw; + + rc = pm_bms_read_output_data(chip, CC_LSB, &lsw); + if (rc) { + pr_err("fail to read CC_LSB rc = %d\n", rc); + return rc; + } + rc = pm_bms_read_output_data(chip, CC_MSB, &msw); + if (rc) { + pr_err("fail to read CC_MSB rc = %d\n", rc); + return rc; + } + *result = msw << 16 | lsw; + pr_debug("msw = %04x lsw = %04x cc = %d\n", msw, lsw, *result); + return 0; +} + +static int read_last_good_ocv(struct pm8921_bms_chip *chip, uint *result) +{ + int rc; + uint16_t reading; + + rc = pm_bms_read_output_data(chip, LAST_GOOD_OCV_VALUE, &reading); + if (rc) { + pr_err("fail to read LAST_GOOD_OCV_VALUE rc = %d\n", rc); + return rc; + } + *result = CONV_READING(reading); + pr_debug("raw = %04x ocv_microV = %u\n", reading, *result); + return 0; +} + +static int read_vbatt_for_rbatt(struct pm8921_bms_chip *chip, uint *result) +{ + int rc; + uint16_t reading; + + rc = pm_bms_read_output_data(chip, VBATT_FOR_RBATT, &reading); + if (rc) { + pr_err("fail to read VBATT_FOR_RBATT rc = %d\n", rc); + return rc; + } + *result = CONV_READING(reading); + pr_debug("raw = %04x vbatt_for_r_microV = %u\n", reading, *result); + return 0; +} + +static int read_vsense_for_rbatt(struct pm8921_bms_chip *chip, uint *result) +{ + int rc; + uint16_t reading; + + rc = pm_bms_read_output_data(chip, VSENSE_FOR_RBATT, &reading); + if (rc) { + pr_err("fail to read VSENSE_FOR_RBATT rc = %d\n", rc); + return rc; + } + *result = CONV_READING(reading); + pr_debug("raw = %04x vsense_for_r_microV = %u\n", reading, *result); + return 0; +} + +static int read_ocv_for_rbatt(struct pm8921_bms_chip *chip, uint *result) +{ + int rc; + uint16_t reading; + + rc = pm_bms_read_output_data(chip, OCV_FOR_RBATT, &reading); + if (rc) { + pr_err("fail to read OCV_FOR_RBATT rc = %d\n", rc); + return rc; + } + *result = CONV_READING(reading); + pr_debug("read = %04x ocv_for_r_microV = %u\n", reading, *result); + return 0; +} + +static int linear_interpolate(int y0, int x0, int y1, int x1, int x) +{ + if (y0 == y1 || x == x0) + return y0; + if (x1 == x0 || x == x1) + return y1; + + return y0 + ((y1 - y0) * (x - x0) / (x1 - x0)); +} + +static int interpolate_single_lut(struct single_row_lut *lut, int x) +{ + int i, result; + + if (x < lut->x[0]) { + pr_err("x %d less than known range returning y = %d\n", + x, lut->y[0]); + return lut->y[0]; + } + if (x > lut->x[lut->cols - 1]) { + pr_err("x %d more than known range returning y = %d\n", + x, lut->y[lut->cols - 1]); + return lut->y[lut->cols - 1]; + } + + for (i = 0; i < lut->cols; i++) + if (x <= lut->x[i]) + break; + if (x == lut->x[i]) { + result = lut->y[i]; + } else { + result = linear_interpolate( + lut->y[i - 1], + lut->x[i - 1], + lut->y[i], + lut->x[i], + x); + } + return result; +} + +static int interpolate_fcc(struct pm8921_bms_chip *chip, int batt_temp) +{ + return interpolate_single_lut(chip->fcc_temp_lut, batt_temp); +} + +static int interpolate_scalingfactor_fcc(struct pm8921_bms_chip *chip, + int cycles) +{ + return interpolate_single_lut(chip->fcc_sf_lut, cycles); +} + +static int interpolate_scalingfactor_pc(struct pm8921_bms_chip *chip, + int cycles, int pc) +{ + int i, scalefactorrow1, scalefactorrow2, scalefactor, row1, row2; + int rows = chip->pc_sf_lut->rows; + int cols = chip->pc_sf_lut->cols; + + if (pc > chip->pc_sf_lut->percent[0]) { + pr_err("pc %d greater than known pc ranges for sfd\n", pc); + row1 = 0; + row2 = 0; + } + if (pc < chip->pc_sf_lut->percent[rows - 1]) { + pr_err("pc %d less than known pc ranges for sf", pc); + row1 = rows - 1; + row2 = rows - 1; + } + for (i = 0; i < rows; i++) { + if (pc == chip->pc_sf_lut->percent[i]) { + row1 = i; + row2 = i; + break; + } + if (pc > chip->pc_sf_lut->percent[i]) { + row1 = i - 1; + row2 = i; + break; + } + } + + if (cycles < chip->pc_sf_lut->cycles[0]) + cycles = chip->pc_sf_lut->cycles[0]; + if (cycles > chip->pc_sf_lut->cycles[cols - 1]) + cycles = chip->pc_sf_lut->cycles[cols - 1]; + + for (i = 0; i < cols; i++) + if (cycles <= chip->pc_sf_lut->cycles[i]) + break; + if (cycles == chip->pc_sf_lut->cycles[i]) { + scalefactor = linear_interpolate( + chip->pc_sf_lut->sf[row1][i], + chip->pc_sf_lut->percent[row1], + chip->pc_sf_lut->sf[row2][i], + chip->pc_sf_lut->percent[row2], + pc); + return scalefactor; + } + + scalefactorrow1 = linear_interpolate( + chip->pc_sf_lut->sf[row1][i - 1], + chip->pc_sf_lut->cycles[i - 1], + chip->pc_sf_lut->sf[row1][i], + chip->pc_sf_lut->cycles[i], + cycles); + + scalefactorrow2 = linear_interpolate( + chip->pc_sf_lut->sf[row2][i - 1], + chip->pc_sf_lut->cycles[i - 1], + chip->pc_sf_lut->sf[row2][i], + chip->pc_sf_lut->cycles[i], + cycles); + + scalefactor = linear_interpolate( + scalefactorrow1, + chip->pc_sf_lut->percent[row1], + scalefactorrow2, + chip->pc_sf_lut->percent[row2], + pc); + + return scalefactor; +} + +static int interpolate_pc(struct pm8921_bms_chip *chip, + int batt_temp, int ocv) +{ + int i, j, ocvi, ocviplusone, pc = 0; + int rows = chip->pc_temp_ocv_lut->rows; + int cols = chip->pc_temp_ocv_lut->cols; + + if (batt_temp < chip->pc_temp_ocv_lut->temp[0]) { + pr_err("batt_temp %d < known temp range for pc\n", batt_temp); + batt_temp = chip->pc_temp_ocv_lut->temp[0]; + } + if (batt_temp > chip->pc_temp_ocv_lut->temp[cols - 1]) { + pr_err("batt_temp %d > known temp range for pc\n", batt_temp); + batt_temp = chip->pc_temp_ocv_lut->temp[cols - 1]; + } + + for (j = 0; j < cols; j++) + if (batt_temp <= chip->pc_temp_ocv_lut->temp[j]) + break; + if (batt_temp == chip->pc_temp_ocv_lut->temp[j]) { + /* found an exact match for temp in the table */ + if (ocv >= chip->pc_temp_ocv_lut->ocv[0][j]) + return chip->pc_temp_ocv_lut->percent[0]; + if (ocv <= chip->pc_temp_ocv_lut->ocv[rows - 1][j]) + return chip->pc_temp_ocv_lut->percent[rows - 1]; + for (i = 0; i < rows; i++) { + if (ocv >= chip->pc_temp_ocv_lut->ocv[i][j]) { + if (ocv == chip->pc_temp_ocv_lut->ocv[i][j]) + return + chip->pc_temp_ocv_lut->percent[i]; + pc = linear_interpolate( + chip->pc_temp_ocv_lut->percent[i], + chip->pc_temp_ocv_lut->ocv[i][j], + chip->pc_temp_ocv_lut->percent[i - 1], + chip->pc_temp_ocv_lut->ocv[i - 1][j], + ocv); + return pc; + } + } + } + + if (ocv >= chip->pc_temp_ocv_lut->ocv[0][j]) + return chip->pc_temp_ocv_lut->percent[0]; + if (ocv <= chip->pc_temp_ocv_lut->ocv[rows - 1][j - 1]) + return chip->pc_temp_ocv_lut->percent[rows - 1]; + for (i = 0; i < rows; i++) { + if (ocv >= chip->pc_temp_ocv_lut->ocv[i][j - 1] + && ocv <= chip->pc_temp_ocv_lut->ocv[i][j]) { + pc = chip->pc_temp_ocv_lut->percent[i]; + + if (i < rows - 1 + && ocv >= + chip->pc_temp_ocv_lut->ocv[i + 1][j - 1] + && ocv <= + chip->pc_temp_ocv_lut->ocv[i + 1][j]) { + ocvi = linear_interpolate( + chip->pc_temp_ocv_lut->ocv[i][j - 1], + chip->pc_temp_ocv_lut->temp[j - 1], + chip->pc_temp_ocv_lut->ocv[i][j], + chip->pc_temp_ocv_lut->temp[j], + batt_temp); + + ocviplusone = linear_interpolate( + chip->pc_temp_ocv_lut + ->ocv[i + 1][j - 1], + chip->pc_temp_ocv_lut->temp[j - 1], + chip->pc_temp_ocv_lut->ocv[i + 1][j], + chip->pc_temp_ocv_lut->temp[j], + batt_temp); + + pc = linear_interpolate( + chip->pc_temp_ocv_lut->percent[i], + ocvi, + chip->pc_temp_ocv_lut->percent[i + 1], + ocviplusone, + ocv); + } + return pc; + } + } + + pr_err("%d ocv wasn't found for temp %d in the LUT returning pc = %d", + ocv, batt_temp, pc); + return pc; +} + +static int calculate_rbatt(struct pm8921_bms_chip *chip) +{ + int rc; + unsigned int ocv, vsense, vbatt, r_batt; + + rc = read_ocv_for_rbatt(chip, &ocv); + if (rc) { + pr_err("fail to read ocv_for_rbatt rc = %d\n", rc); + ocv = 0; + } + rc = read_vbatt_for_rbatt(chip, &vbatt); + if (rc) { + pr_err("fail to read vbatt_for_rbatt rc = %d\n", rc); + ocv = 0; + } + rc = read_vsense_for_rbatt(chip, &vsense); + if (rc) { + pr_err("fail to read vsense_for_rbatt rc = %d\n", rc); + ocv = 0; + } + if (ocv == 0 + || ocv == vbatt + || vsense == 0) { + pr_warning("incorret reading ocv = %d, vbatt = %d, vsen = %d\n", + ocv, vbatt, vsense); + return -EINVAL; + } + r_batt = ((ocv - vbatt) * chip->r_sense) / vsense; + pr_debug("r_batt = %umilliOhms", r_batt); + return r_batt; +} + +static int calculate_fcc(struct pm8921_bms_chip *chip, int batt_temp, + int chargecycles) +{ + int initfcc, result, scalefactor = 0; + + initfcc = interpolate_fcc(chip, batt_temp); + pr_debug("intfcc = %umAh batt_temp = %d\n", initfcc, batt_temp); + + scalefactor = interpolate_scalingfactor_fcc(chip, chargecycles); + pr_debug("scalefactor = %d batt_temp = %d\n", scalefactor, batt_temp); + + /* Multiply the initial FCC value by the scale factor. */ + result = (initfcc * scalefactor) / 100; + pr_debug("fcc mAh = %d\n", result); + return result; +} + +static int calculate_pc(struct pm8921_bms_chip *chip, int ocv_uv, int batt_temp, + int chargecycles) +{ + int pc, scalefactor; + + pc = interpolate_pc(chip, batt_temp, ocv_uv / 1000); + pr_debug("pc = %u for ocv = %dmicroVolts batt_temp = %d\n", + pc, ocv_uv, batt_temp); + + scalefactor = interpolate_scalingfactor_pc(chip, chargecycles, pc); + pr_debug("scalefactor = %u batt_temp = %d\n", scalefactor, batt_temp); + + /* Multiply the initial FCC value by the scale factor. */ + pc = (pc * scalefactor) / 100; + return pc; +} + +#define CC_TO_MICROVOLT(cc) div_s64(cc * 1085069, 100000); +#define CCMICROVOLT_TO_UVH(cc_uv) div_s64(cc_uv * 55, 32768 * 3600) + +static void calculate_cc_mvh(struct pm8921_bms_chip *chip, int64_t *val, + int *coulumb_counter, int *update_userspace) +{ + int rc; + int64_t cc_voltage_uv, cc_uvh, cc_mah; + + rc = read_cc(the_chip, coulumb_counter); + if (rc) { + *coulumb_counter = (last_coulumb_counter < 0) ? + DEFAULT_COULUMB_COUNTER : last_coulumb_counter; + pr_err("couldn't read coulumb counter err = %d assuming %d\n", + rc, *coulumb_counter); + *update_userspace = 0; + } + cc_voltage_uv = (int64_t)*coulumb_counter; + cc_voltage_uv = CC_TO_MICROVOLT(cc_voltage_uv); + pr_debug("cc_voltage_uv = %lld microvolts\n", cc_voltage_uv); + cc_uvh = CCMICROVOLT_TO_UVH(cc_voltage_uv); + pr_debug("cc_uvh = %lld micro_volt_hour\n", cc_uvh); + cc_mah = div_s64(cc_uvh, chip->r_sense); + *val = cc_mah; +} + +static int calculate_state_of_charge(struct pm8921_bms_chip *chip, + int batt_temp, int chargecycles) +{ + int remaining_usable_charge, fcc, unusable_charge; + int remaining_charge, soc, rc, ocv, pc, coulumb_counter; + int rbatt, voltage_unusable_uv, pc_unusable; + int update_userspace = 1; + int64_t cc_mah; + + rbatt = calculate_rbatt(chip); + if (rbatt < 0) { + rbatt = (last_rbatt < 0) ? DEFAULT_RBATT_MOHMS : last_rbatt; + pr_err("failed to read rbatt assuming %d\n", + rbatt); + update_userspace = 0; + } + pr_debug("rbatt = %umilliOhms", rbatt); + + fcc = calculate_fcc(chip, batt_temp, chargecycles); + if (fcc < -EINVAL) { + fcc = (last_fcc < 0) ? chip->fcc : last_fcc; + pr_err("failed to read fcc assuming %d\n", fcc); + update_userspace = 0; + } + pr_debug("fcc = %umAh", fcc); + + /* calculate unusable charge */ + voltage_unusable_uv = (rbatt * chip->i_test) + + (chip->v_failure * 1000); + pc_unusable = calculate_pc(chip, voltage_unusable_uv, + batt_temp, chargecycles); + if (pc_unusable < 0) { + unusable_charge = (last_unusable_charge < 0) ? + DEFAULT_UNUSABLE_CHARGE_MAH : last_unusable_charge; + pr_err("unusable_charge failed assuming %d\n", unusable_charge); + } else { + unusable_charge = (fcc * pc_unusable) / 100; + } + pr_debug("unusable_charge = %umAh at temp = %d, fcc = %umAh" + "unusable_voltage = %umicroVolts pc_unusable = %d\n", + unusable_charge, batt_temp, fcc, + voltage_unusable_uv, pc_unusable); + + /* calculate remainging charge */ + rc = read_last_good_ocv(chip, &ocv); + if (rc || ocv == 0) { + ocv = (last_ocv_uv < 0) ? DEFAULT_OCV_MICROVOLTS : last_ocv_uv; + pr_err("ocv failed assuming %d rc = %d\n", ocv, rc); + update_userspace = 0; + } + pc = calculate_pc(chip, ocv, batt_temp, chargecycles); + if (pc < 0) { + remaining_charge = (last_remaining_charge < 0) ? + DEFAULT_REMAINING_CHARGE_MAH : last_remaining_charge; + pr_err("calculate remaining charge failed assuming %d\n", + remaining_charge); + update_userspace = 0; + } else { + remaining_charge = (fcc * pc) / 100; + } + pr_debug("remaining_charge = %umAh ocv = %d pc = %d\n", + remaining_charge, ocv, pc); + + /* calculate cc milli_volt_hour */ + calculate_cc_mvh(chip, &cc_mah, &coulumb_counter, &update_userspace); + pr_debug("cc_mah = %lldmAh cc = %d\n", cc_mah, coulumb_counter); + + /* calculate remaining usable charge */ + remaining_usable_charge = remaining_charge - cc_mah - unusable_charge; + pr_debug("remaining_usable_charge = %dmAh\n", remaining_usable_charge); + if (remaining_usable_charge < 0) { + pr_err("bad rem_usb_chg cc_mah %lld, rem_chg %d unusb_chg %d\n", + cc_mah, remaining_charge, unusable_charge); + update_userspace = 0; + } + + soc = (remaining_usable_charge * 100) / (fcc - unusable_charge); + if (soc > 100 || soc < 0) { + pr_err("bad soc rem_usb_chg %d fcc %d unusb_chg %d\n", + remaining_usable_charge, fcc, unusable_charge); + update_userspace = 0; + } + pr_debug("soc = %u%%\n", soc); + + if (update_userspace) { + last_rbatt = rbatt; + last_fcc = fcc; + last_unusable_charge = unusable_charge; + last_ocv_uv = ocv; + last_remaining_charge = remaining_charge; + last_coulumb_counter = coulumb_counter; + last_soc = soc; + } + return soc; +} + +int pm8921_bms_get_percent_charge(void) +{ + /* TODO get batt_temp from ADC */ + int batt_temp = 73; + + return calculate_state_of_charge(the_chip, + batt_temp, last_chargecycles); +} +EXPORT_SYMBOL_GPL(pm8921_bms_get_percent_charge); + +static int start_percent; +static int end_percent; +void pm8921_bms_charging_began(void) +{ + /* TODO get batt_temp from ADC */ + int batt_temp = 73; + + start_percent = calculate_state_of_charge(the_chip, + batt_temp, last_chargecycles); + pr_debug("start_percent = %u%%\n", start_percent); +} +EXPORT_SYMBOL_GPL(pm8921_bms_charging_began); + +void pm8921_bms_charging_end(void) +{ + /* TODO get batt_temp from ADC */ + int batt_temp = 73; + + end_percent = calculate_state_of_charge(the_chip, + batt_temp, last_chargecycles); + if (end_percent > start_percent) { + last_charge_increase = end_percent - start_percent; + if (last_charge_increase > 100) { + last_chargecycles++; + last_charge_increase = last_charge_increase % 100; + } + } + pr_debug("end_percent = %u%% last_charge_increase = %d" + "last_chargecycles = %d\n", + end_percent, + last_charge_increase, + last_chargecycles); +} +EXPORT_SYMBOL_GPL(pm8921_bms_charging_end); + +static irqreturn_t pm8921_bms_sbi_write_ok_handler(int irq, void *data) +{ + return IRQ_HANDLED; +} + +static irqreturn_t pm8921_bms_cc_thr_handler(int irq, void *data) +{ + return IRQ_HANDLED; +} +static irqreturn_t pm8921_bms_vsense_thr_handler(int irq, void *data) +{ + return IRQ_HANDLED; +} +static irqreturn_t pm8921_bms_vsense_for_r_handler(int irq, void *data) +{ + return IRQ_HANDLED; +} +static irqreturn_t pm8921_bms_ocv_for_r_handler(int irq, void *data) +{ + return IRQ_HANDLED; +} +static irqreturn_t pm8921_bms_good_ocv_handler(int irq, void *data) +{ + return IRQ_HANDLED; +} +static irqreturn_t pm8921_bms_vsense_avg_handler(int irq, void *data) +{ + return IRQ_HANDLED; +} + +struct pm_bms_irq_init_data { + unsigned int irq_id; + char *name; + unsigned long flags; + irqreturn_t (*handler)(int, void *); +}; + +#define BMS_IRQ(_id, _flags, _handler) \ +{ \ + .irq_id = _id, \ + .name = #_id, \ + .flags = _flags, \ + .handler = _handler, \ +} + +struct pm_bms_irq_init_data bms_irq_data[] = { + BMS_IRQ(PM8921_BMS_SBI_WRITE_OK, IRQF_TRIGGER_RISING, + pm8921_bms_sbi_write_ok_handler), + BMS_IRQ(PM8921_BMS_CC_THR, IRQF_TRIGGER_RISING, + pm8921_bms_cc_thr_handler), + BMS_IRQ(PM8921_BMS_VSENSE_THR, IRQF_TRIGGER_RISING, + pm8921_bms_vsense_thr_handler), + BMS_IRQ(PM8921_BMS_VSENSE_FOR_R, IRQF_TRIGGER_RISING, + pm8921_bms_vsense_for_r_handler), + BMS_IRQ(PM8921_BMS_OCV_FOR_R, IRQF_TRIGGER_RISING, + pm8921_bms_ocv_for_r_handler), + BMS_IRQ(PM8921_BMS_GOOD_OCV, IRQF_TRIGGER_RISING, + pm8921_bms_good_ocv_handler), + BMS_IRQ(PM8921_BMS_VSENSE_AVG, IRQF_TRIGGER_RISING, + pm8921_bms_vsense_avg_handler), +}; + +static void free_irqs(struct pm8921_bms_chip *chip) +{ + int i; + + for (i = 0; i < PM_BMS_MAX_INTS; i++) + if (chip->pmic_bms_irq[i]) { + free_irq(chip->pmic_bms_irq[i], NULL); + chip->pmic_bms_irq[i] = 0; + } +} + +static int __devinit request_irqs(struct pm8921_bms_chip *chip, + struct platform_device *pdev) +{ + struct resource *res; + int ret, i; + + ret = 0; + bitmap_fill(chip->enabled_irqs, PM_BMS_MAX_INTS); + + for (i = 0; i < ARRAY_SIZE(bms_irq_data); i++) { + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + bms_irq_data[i].name); + if (res == NULL) { + pr_err("couldn't find %s\n", bms_irq_data[i].name); + goto err_out; + } + ret = request_irq(res->start, bms_irq_data[i].handler, + bms_irq_data[i].flags, + bms_irq_data[i].name, chip); + if (ret < 0) { + pr_err("couldn't request %d (%s) %d\n", res->start, + bms_irq_data[i].name, ret); + goto err_out; + } + chip->pmic_bms_irq[bms_irq_data[i].irq_id] = res->start; + pm8921_bms_disable_irq(chip, bms_irq_data[i].irq_id); + } + return 0; + +err_out: + free_irqs(chip); + return -EINVAL; +} + +#define EN_BMS_BIT BIT(7) +#define EN_PON_HS_BIT BIT(0) +static int __devinit pm8921_bms_hw_init(struct pm8921_bms_chip *chip) +{ + int rc; + + rc = pm_bms_masked_write(chip, BMS_CONTROL, + EN_BMS_BIT | EN_PON_HS_BIT, EN_BMS_BIT | EN_PON_HS_BIT); + if (rc) { + pr_err("failed to enable pon and bms addr = %d %d", + BMS_CONTROL, rc); + } + + return 0; +} + +enum { + CALC_RBATT, + CALC_FCC, + CALC_PC, + CALC_SOC, +}; + +static int test_batt_temp = 5; +static int test_chargecycle = 150; +static int test_ocv = 3900000; +enum { + TEST_BATT_TEMP, + TEST_CHARGE_CYCLE, + TEST_OCV, +}; +static int get_test_param(void *data, u64 * val) +{ + switch ((int)data) { + case TEST_BATT_TEMP: + *val = test_batt_temp; + break; + case TEST_CHARGE_CYCLE: + *val = test_chargecycle; + break; + case TEST_OCV: + *val = test_ocv; + break; + default: + return -EINVAL; + } + return 0; +} +static int set_test_param(void *data, u64 val) +{ + switch ((int)data) { + case TEST_BATT_TEMP: + test_batt_temp = (int)val; + break; + case TEST_CHARGE_CYCLE: + test_chargecycle = (int)val; + break; + case TEST_OCV: + test_ocv = (int)val; + break; + default: + return -EINVAL; + } + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(temp_fops, get_test_param, set_test_param, "%llu\n"); + +static int get_calc(void *data, u64 * val) +{ + int param = (int)data; + int ret = 0; + + *val = 0; + + /* global irq number passed in via data */ + switch (param) { + case CALC_RBATT: + *val = calculate_rbatt(the_chip); + break; + case CALC_FCC: + *val = calculate_fcc(the_chip, test_batt_temp, + test_chargecycle); + break; + case CALC_PC: + *val = calculate_pc(the_chip, test_ocv, test_batt_temp, + test_chargecycle); + break; + case CALC_SOC: + *val = calculate_state_of_charge(the_chip, + test_batt_temp, test_chargecycle); + break; + default: + ret = -EINVAL; + } + return ret; +} +DEFINE_SIMPLE_ATTRIBUTE(calc_fops, get_calc, NULL, "%llu\n"); + +static int get_reading(void *data, u64 * val) +{ + int param = (int)data; + int ret = 0; + + *val = 0; + + /* global irq number passed in via data */ + switch (param) { + case CC_MSB: + case CC_LSB: + read_cc(the_chip, (int *)val); + break; + case LAST_GOOD_OCV_VALUE: + read_last_good_ocv(the_chip, (uint *)val); + break; + case VBATT_FOR_RBATT: + read_vbatt_for_rbatt(the_chip, (uint *)val); + break; + case VSENSE_FOR_RBATT: + read_vsense_for_rbatt(the_chip, (uint *)val); + break; + case OCV_FOR_RBATT: + read_ocv_for_rbatt(the_chip, (uint *)val); + break; + default: + ret = -EINVAL; + } + return ret; +} +DEFINE_SIMPLE_ATTRIBUTE(reading_fops, get_reading, NULL, "%llu\n"); + +static int get_rt_status(void *data, u64 * val) +{ + int i = (int)data; + int ret; + + /* global irq number passed in via data */ + ret = pm_bms_get_rt_status(the_chip, i); + *val = ret; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(rt_fops, get_rt_status, NULL, "%llu\n"); + +static int get_reg(void *data, u64 * val) +{ + int addr = (int)data; + int ret; + u8 temp; + + ret = pm8xxx_readb(the_chip->dev->parent, addr, &temp); + if (ret) { + pr_err("pm8xxx_readb to %x value = %d errored = %d\n", + addr, temp, ret); + return -EAGAIN; + } + *val = temp; + return 0; +} + +static int set_reg(void *data, u64 val) +{ + int addr = (int)data; + int ret; + u8 temp; + + temp = (u8) val; + ret = pm8xxx_writeb(the_chip->dev->parent, addr, temp); + if (ret) { + pr_err("pm8xxx_writeb to %x value = %d errored = %d\n", + addr, temp, ret); + return -EAGAIN; + } + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n"); + +static void create_debugfs_entries(struct pm8921_bms_chip *chip) +{ + int i; + + chip->dent = debugfs_create_dir("pm8921_bms", NULL); + + if (IS_ERR(chip->dent)) { + pr_err("pmic bms couldnt create debugfs dir\n"); + return; + } + + debugfs_create_file("BMS_CONTROL", 0644, chip->dent, + (void *)BMS_CONTROL, ®_fops); + debugfs_create_file("BMS_OUTPUT0", 0644, chip->dent, + (void *)BMS_OUTPUT0, ®_fops); + debugfs_create_file("BMS_OUTPUT1", 0644, chip->dent, + (void *)BMS_OUTPUT1, ®_fops); + debugfs_create_file("BMS_TEST1", 0644, chip->dent, + (void *)BMS_TEST1, ®_fops); + debugfs_create_file("CCADC_ANA_PARAM", 0644, chip->dent, + (void *)CCADC_ANA_PARAM, ®_fops); + debugfs_create_file("CCADC_DIG_PARAM", 0644, chip->dent, + (void *)CCADC_DIG_PARAM, ®_fops); + debugfs_create_file("CCADC_RSV", 0644, chip->dent, + (void *)CCADC_RSV, ®_fops); + debugfs_create_file("CCADC_DATA0", 0644, chip->dent, + (void *)CCADC_DATA0, ®_fops); + debugfs_create_file("CCADC_DATA1", 0644, chip->dent, + (void *)CCADC_DATA1, ®_fops); + debugfs_create_file("CCADC_OFFSET_TRIM1", 0644, chip->dent, + (void *)CCADC_OFFSET_TRIM1, ®_fops); + debugfs_create_file("CCADC_OFFSET_TRIM0", 0644, chip->dent, + (void *)CCADC_OFFSET_TRIM0, ®_fops); + + debugfs_create_file("test_batt_temp", 0644, chip->dent, + (void *)TEST_BATT_TEMP, &temp_fops); + debugfs_create_file("test_chargecycle", 0644, chip->dent, + (void *)TEST_CHARGE_CYCLE, &temp_fops); + debugfs_create_file("test_ocv", 0644, chip->dent, + (void *)TEST_OCV, &temp_fops); + + debugfs_create_file("read_cc", 0644, chip->dent, + (void *)CC_MSB, &reading_fops); + debugfs_create_file("read_last_good_ocv", 0644, chip->dent, + (void *)LAST_GOOD_OCV_VALUE, &reading_fops); + debugfs_create_file("read_vbatt_for_rbatt", 0644, chip->dent, + (void *)VBATT_FOR_RBATT, &reading_fops); + debugfs_create_file("read_vsense_for_rbatt", 0644, chip->dent, + (void *)VSENSE_FOR_RBATT, &reading_fops); + debugfs_create_file("read_ocv_for_rbatt", 0644, chip->dent, + (void *)OCV_FOR_RBATT, &reading_fops); + + debugfs_create_file("show_rbatt", 0644, chip->dent, + (void *)CALC_RBATT, &calc_fops); + debugfs_create_file("show_fcc", 0644, chip->dent, + (void *)CALC_FCC, &calc_fops); + debugfs_create_file("show_pc", 0644, chip->dent, + (void *)CALC_PC, &calc_fops); + debugfs_create_file("show_soc", 0644, chip->dent, + (void *)CALC_SOC, &calc_fops); + + for (i = 0; i < ARRAY_SIZE(bms_irq_data); i++) { + if (chip->pmic_bms_irq[bms_irq_data[i].irq_id]) + debugfs_create_file(bms_irq_data[i].name, 0444, + chip->dent, + (void *)bms_irq_data[i].irq_id, + &rt_fops); + } +} + +static void calibrate_work(struct work_struct *work) +{ + /* TODO */ +} + +static int __devinit pm8921_bms_probe(struct platform_device *pdev) +{ + int rc = 0; + struct pm8921_bms_chip *chip; + const struct pm8921_bms_platform_data *pdata + = pdev->dev.platform_data; + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct pm8921_bms_chip), + GFP_KERNEL); + if (!chip) { + pr_err("Cannot allocate pm_bms_chip\n"); + return -ENOMEM; + } + + chip->dev = &pdev->dev; + chip->r_sense = pdata->r_sense; + chip->i_test = pdata->i_test; + chip->v_failure = pdata->v_failure; + chip->calib_delay_ms = pdata->calib_delay_ms; + chip->fcc = pdata->batt_data->fcc; + + chip->fcc_temp_lut = pdata->batt_data->fcc_temp_lut; + chip->fcc_sf_lut = pdata->batt_data->fcc_sf_lut; + chip->pc_temp_ocv_lut = pdata->batt_data->pc_temp_ocv_lut; + chip->pc_sf_lut = pdata->batt_data->pc_sf_lut; + + rc = pm8921_bms_hw_init(chip); + if (rc) { + pr_err("couldn't init hardware rc = %d\n", rc); + goto free_chip; + } + + rc = request_irqs(chip, pdev); + if (rc) { + pr_err("couldn't register interrupts rc = %d\n", rc); + goto free_chip; + } + + platform_set_drvdata(pdev, chip); + the_chip = chip; + create_debugfs_entries(chip); + + INIT_DELAYED_WORK(&chip->calib_work, calibrate_work); + schedule_delayed_work(&chip->calib_work, + round_jiffies_relative(msecs_to_jiffies + (chip->calib_delay_ms))); + return 0; + +free_chip: + kfree(chip); + return rc; +} + +static int __devexit pm8921_bms_remove(struct platform_device *pdev) +{ + struct pm8921_bms_chip *chip = platform_get_drvdata(pdev); + + free_irqs(chip); + platform_set_drvdata(pdev, NULL); + the_chip = NULL; + kfree(chip); + return 0; +} + +static struct platform_driver pm8921_bms_driver = { + .probe = pm8921_bms_probe, + .remove = __devexit_p(pm8921_bms_remove), + .driver = { + .name = PM8921_BMS_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8921_bms_init(void) +{ + return platform_driver_register(&pm8921_bms_driver); +} + +static void __exit pm8921_bms_exit(void) +{ + platform_driver_unregister(&pm8921_bms_driver); +} + +late_initcall(pm8921_bms_init); +module_exit(pm8921_bms_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8921 bms driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8921_BMS_DEV_NAME); diff --git a/drivers/power/pm8921-charger.c b/drivers/power/pm8921-charger.c new file mode 100644 index 00000000000..6775fbae9ed --- /dev/null +++ b/drivers/power/pm8921-charger.c @@ -0,0 +1,1560 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define CHG_BUCK_CLOCK_CTRL 0x14 + +#define PBL_ACCESS1 0x04 +#define PBL_ACCESS2 0x05 +#define SYS_CONFIG_1 0x06 +#define SYS_CONFIG_2 0x07 +#define CHG_CNTRL 0x204 +#define CHG_IBAT_MAX 0x205 +#define CHG_TEST 0x206 +#define CHG_BUCK_CTRL_TEST1 0x207 +#define CHG_BUCK_CTRL_TEST2 0x208 +#define CHG_BUCK_CTRL_TEST3 0x209 +#define COMPARATOR_OVERRIDE 0x20A +#define PSI_TXRX_SAMPLE_DATA_0 0x20B +#define PSI_TXRX_SAMPLE_DATA_1 0x20C +#define PSI_TXRX_SAMPLE_DATA_2 0x20D +#define PSI_TXRX_SAMPLE_DATA_3 0x20E +#define PSI_CONFIG_STATUS 0x20F +#define CHG_IBAT_SAFE 0x210 +#define CHG_ITRICKLE 0x211 +#define CHG_CNTRL_2 0x212 +#define CHG_VBAT_DET 0x213 +#define CHG_VTRICKLE 0x214 +#define CHG_ITERM 0x215 +#define CHG_CNTRL_3 0x216 +#define CHG_VIN_MIN 0x217 +#define CHG_TWDOG 0x218 +#define CHG_TTRKL_MAX 0x219 +#define CHG_TEMP_THRESH 0x21A +#define CHG_TCHG_MAX 0x21B +#define USB_OVP_CONTROL 0x21C +#define DC_OVP_CONTROL 0x21D +#define USB_OVP_TEST 0x21E +#define DC_OVP_TEST 0x21F +#define CHG_VDD_MAX 0x220 +#define CHG_VDD_SAFE 0x221 +#define CHG_VBAT_BOOT_THRESH 0x222 +#define USB_OVP_TRIM 0x355 +#define BUCK_CONTROL_TRIM1 0x356 +#define BUCK_CONTROL_TRIM2 0x357 +#define BUCK_CONTROL_TRIM3 0x358 +#define BUCK_CONTROL_TRIM4 0x359 +#define CHG_DEFAULTS_TRIM 0x35A +#define CHG_ITRIM 0x35B +#define CHG_TTRIM 0x35C +#define CHG_COMP_OVR 0x20A + +enum chg_fsm_state { + FSM_STATE_OFF_0 = 0, + FSM_STATE_BATFETDET_START_12 = 12, + FSM_STATE_BATFETDET_END_16 = 16, + FSM_STATE_ON_CHG_HIGHI_1 = 1, + FSM_STATE_ATC_2A = 2, + FSM_STATE_ATC_2B = 18, + FSM_STATE_ON_BAT_3 = 3, + FSM_STATE_ATC_FAIL_4 = 4 , + FSM_STATE_DELAY_5 = 5, + FSM_STATE_ON_CHG_AND_BAT_6 = 6, + FSM_STATE_FAST_CHG_7 = 7, + FSM_STATE_TRKL_CHG_8 = 8, + FSM_STATE_CHG_FAIL_9 = 9, + FSM_STATE_EOC_10 = 10, + FSM_STATE_ON_CHG_VREGOK_11 = 11, + FSM_STATE_ATC_PAUSE_13 = 13, + FSM_STATE_FAST_CHG_PAUSE_14 = 14, + FSM_STATE_TRKL_CHG_PAUSE_15 = 15, + FSM_STATE_START_BOOT = 20, + FSM_STATE_FLCB_VREGOK = 21, + FSM_STATE_FLCB = 22, +}; + +enum pmic_chg_interrupts { + USBIN_VALID_IRQ = 0, + USBIN_OV_IRQ, + BATT_INSERTED_IRQ, + VBATDET_LOW_IRQ, + USBIN_UV_IRQ, + VBAT_OV_IRQ, + CHGWDOG_IRQ, + VCP_IRQ, + ATCDONE_IRQ, + ATCFAIL_IRQ, + CHGDONE_IRQ, + CHGFAIL_IRQ, + CHGSTATE_IRQ, + LOOP_CHANGE_IRQ, + FASTCHG_IRQ, + TRKLCHG_IRQ, + BATT_REMOVED_IRQ, + BATTTEMP_HOT_IRQ, + CHGHOT_IRQ, + BATTTEMP_COLD_IRQ, + CHG_GONE_IRQ, + BAT_TEMP_OK_IRQ, + COARSE_DET_LOW_IRQ, + VDD_LOOP_IRQ, + VREG_OV_IRQ, + VBATDET_IRQ, + BATFET_IRQ, + PSI_IRQ, + DCIN_VALID_IRQ, + DCIN_OV_IRQ, + DCIN_UV_IRQ, + PM_CHG_MAX_INTS, +}; + +struct bms_notify { + int is_charging; + struct work_struct work; +}; + +/** + * struct pm8921_chg_chip -device information + * @dev: device pointer to access the parent + * @is_usb_path_used: indicates whether USB charging is used at all + * @is_usb_path_used: indicates whether DC charging is used at all + * @usb_present: present status of usb + * @dc_present: present status of dc + * @usb_charger_current: usb current to charge the battery with used when + * the usb path is enabled or charging is resumed + * @safety_time: max time for which charging will happen + * @update_time: how frequently the userland needs to be updated + * @max_voltage: the max volts the batt should be charged up to + * @min_voltage: the min battery voltage before turning the FETon + * @resume_voltage: the voltage at which the battery should resume + * charging + * @term_current: The charging based term current + * + */ +struct pm8921_chg_chip { + struct device *dev; + unsigned int usb_present; + unsigned int dc_present; + unsigned int usb_charger_current; + unsigned int pmic_chg_irq[PM_CHG_MAX_INTS]; + unsigned int safety_time; + unsigned int update_time; + unsigned int max_voltage; + unsigned int min_voltage; + unsigned int resume_voltage; + unsigned int term_current; + unsigned int vbat_channel; + struct power_supply usb_psy; + struct power_supply dc_psy; + struct power_supply batt_psy; + struct dentry *dent; + struct bms_notify bms_notify; + DECLARE_BITMAP(enabled_irqs, PM_CHG_MAX_INTS); +}; + +static int charging_disabled; + +static struct pm8921_chg_chip *the_chip; + +static int pm_chg_masked_write(struct pm8921_chg_chip *chip, u16 addr, + u8 mask, u8 val) +{ + int rc; + u8 reg; + + rc = pm8xxx_readb(chip->dev->parent, addr, ®); + if (rc) { + pr_err("pm8xxx_readb failed: addr=%03X, rc=%d\n", addr, rc); + return rc; + } + reg &= ~mask; + reg |= val & mask; + rc = pm8xxx_writeb(chip->dev->parent, addr, reg); + if (rc) { + pr_err("pm8xxx_writeb failed: addr=%03X, rc=%d\n", addr, rc); + return rc; + } + return 0; +} + +#define CAPTURE_FSM_STATE_CMD 0xC2 +#define READ_BANK_7 0x70 +#define READ_BANK_4 0x40 +static int pm_chg_get_fsm_state(struct pm8921_chg_chip *chip) +{ + u8 temp; + int err, ret = 0; + + temp = CAPTURE_FSM_STATE_CMD; + err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + if (err) { + pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); + return err; + } + + temp = READ_BANK_7; + err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + if (err) { + pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); + return err; + } + + err = pm8xxx_readb(chip->dev->parent, CHG_TEST, &temp); + if (err) { + pr_err("pm8xxx_readb fail: addr=%03X, rc=%d\n", CHG_TEST, err); + return err; + } + /* get the lower 4 bits */ + ret = temp & 0xF; + + temp = READ_BANK_4; + err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + if (err) { + pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); + return err; + } + + err = pm8xxx_readb(chip->dev->parent, CHG_TEST, &temp); + if (err) { + pr_err("pm8xxx_readb fail: addr=%03X, rc=%d\n", CHG_TEST, err); + return err; + } + /* get the upper 1 bit */ + ret |= (temp & 0x1) << 4; + return ret; +} + +#define CHG_USB_SUSPEND_BIT BIT(2) +static int pm_chg_usb_suspend_enable(struct pm8921_chg_chip *chip, int enable) +{ + return pm_chg_masked_write(chip, CHG_CNTRL_3, CHG_USB_SUSPEND_BIT, + enable ? CHG_USB_SUSPEND_BIT : 0); +} + +#define CHG_EN_BIT BIT(7) +static int pm_chg_auto_enable(struct pm8921_chg_chip *chip, int enable) +{ + return pm_chg_masked_write(chip, CHG_CNTRL_3, CHG_EN_BIT, + enable ? CHG_EN_BIT : 0); +} + +#define CHG_CHARGE_DIS_BIT BIT(1) +static int pm_chg_charge_dis(struct pm8921_chg_chip *chip, int disable) +{ + return pm_chg_masked_write(chip, CHG_CNTRL, CHG_CHARGE_DIS_BIT, + disable ? CHG_CHARGE_DIS_BIT : 0); +} + +#define PM8921_CHG_V_MIN_MV 3240 +#define PM8921_CHG_V_STEP_MV 20 +#define PM8921_CHG_VDDMAX_MAX 4500 +#define PM8921_CHG_VDDMAX_MIN 3400 +#define PM8921_CHG_V_MASK 0x7F +static int pm_chg_vddmax_set(struct pm8921_chg_chip *chip, int voltage) +{ + u8 temp; + + if (voltage < PM8921_CHG_VDDMAX_MIN + || voltage > PM8921_CHG_VDDMAX_MAX) { + pr_err("bad mV=%d asked to set\n", voltage); + return -EINVAL; + } + temp = (voltage - PM8921_CHG_V_MIN_MV) / PM8921_CHG_V_STEP_MV; + pr_debug("voltage=%d setting %02x\n", voltage, temp); + return pm_chg_masked_write(chip, CHG_VDD_MAX, PM8921_CHG_V_MASK, temp); +} + +#define PM8921_CHG_VDDSAFE_MIN 3400 +#define PM8921_CHG_VDDSAFE_MAX 4500 +static int pm_chg_vddsafe_set(struct pm8921_chg_chip *chip, int voltage) +{ + u8 temp; + + if (voltage < PM8921_CHG_VDDSAFE_MIN + || voltage > PM8921_CHG_VDDSAFE_MAX) { + pr_err("bad mV=%d asked to set\n", voltage); + return -EINVAL; + } + temp = (voltage - PM8921_CHG_V_MIN_MV) / PM8921_CHG_V_STEP_MV; + pr_debug("voltage=%d setting %02x\n", voltage, temp); + return pm_chg_masked_write(chip, CHG_VDD_SAFE, PM8921_CHG_V_MASK, temp); +} + +#define PM8921_CHG_VBATDET_MIN 3240 +#define PM8921_CHG_VBATDET_MAX 5780 +static int pm_chg_vbatdet_set(struct pm8921_chg_chip *chip, int voltage) +{ + u8 temp; + + if (voltage < PM8921_CHG_VBATDET_MIN + || voltage > PM8921_CHG_VBATDET_MAX) { + pr_err("bad mV=%d asked to set\n", voltage); + return -EINVAL; + } + temp = (voltage - PM8921_CHG_V_MIN_MV) / PM8921_CHG_V_STEP_MV; + pr_debug("voltage=%d setting %02x\n", voltage, temp); + return pm_chg_masked_write(chip, CHG_VBAT_DET, PM8921_CHG_V_MASK, temp); +} + +#define PM8921_CHG_IBATMAX_MIN 325 +#define PM8921_CHG_IBATMAX_MAX 2000 +#define PM8921_CHG_I_MIN_MA 225 +#define PM8921_CHG_I_STEP_MA 50 +#define PM8921_CHG_I_MASK 0x3F +static int pm_chg_ibatmax_set(struct pm8921_chg_chip *chip, int chg_current) +{ + u8 temp; + + if (chg_current < PM8921_CHG_IBATMAX_MIN + || chg_current > PM8921_CHG_IBATMAX_MAX) { + pr_err("bad mA=%d asked to set\n", chg_current); + return -EINVAL; + } + temp = (chg_current - PM8921_CHG_I_MIN_MA) / PM8921_CHG_I_STEP_MA; + return pm_chg_masked_write(chip, CHG_IBAT_MAX, PM8921_CHG_I_MASK, temp); +} + +#define PM8921_CHG_IBATSAFE_MIN 225 +#define PM8921_CHG_IBATSAFE_MAX 3375 +static int pm_chg_ibatsafe_set(struct pm8921_chg_chip *chip, int chg_current) +{ + u8 temp; + + if (chg_current < PM8921_CHG_IBATSAFE_MIN + || chg_current > PM8921_CHG_IBATSAFE_MAX) { + pr_err("bad mA=%d asked to set\n", chg_current); + return -EINVAL; + } + temp = (chg_current - PM8921_CHG_I_MIN_MA) / PM8921_CHG_I_STEP_MA; + return pm_chg_masked_write(chip, CHG_IBAT_SAFE, + PM8921_CHG_I_MASK, temp); +} + +#define PM8921_CHG_ITERM_MIN 50 +#define PM8921_CHG_ITERM_MAX 200 +#define PM8921_CHG_ITERM_MIN_MA 50 +#define PM8921_CHG_ITERM_STEP_MA 10 +#define PM8921_CHG_ITERM_MASK 0xF +static int pm_chg_iterm_set(struct pm8921_chg_chip *chip, int chg_current) +{ + u8 temp; + + if (chg_current < PM8921_CHG_ITERM_MIN + || chg_current > PM8921_CHG_ITERM_MAX) { + pr_err("bad mA=%d asked to set\n", chg_current); + return -EINVAL; + } + + temp = (chg_current - PM8921_CHG_ITERM_MIN_MA) + / PM8921_CHG_ITERM_STEP_MA; + return pm_chg_masked_write(chip, CHG_IBAT_SAFE, PM8921_CHG_ITERM_MASK, + temp); +} + +#define PM8921_CHG_IUSB_MASK 0x1C +#define PM8921_CHG_IUSB_MAX 7 +#define PM8921_CHG_IUSB_MIN 0 +static int pm_chg_iusbmax_set(struct pm8921_chg_chip *chip, int chg_current) +{ + u8 temp; + + if (chg_current < PM8921_CHG_IUSB_MIN + || chg_current > PM8921_CHG_IUSB_MAX) { + pr_err("bad mA=%d asked to set\n", chg_current); + return -EINVAL; + } + temp = chg_current << 2; + return pm_chg_masked_write(chip, PBL_ACCESS2, PM8921_CHG_IUSB_MASK, + temp); +} + +#define PM8921_CHG_WD_MASK 0x1F +static int pm_chg_disable_wd(struct pm8921_chg_chip *chip) +{ + /* writing 0 to the wd timer disables it */ + return pm_chg_masked_write(chip, CHG_TWDOG, PM8921_CHG_WD_MASK, + 0); +} + +static void pm8921_chg_enable_irq(struct pm8921_chg_chip *chip, int interrupt) +{ + if (!__test_and_set_bit(interrupt, chip->enabled_irqs)) { + dev_dbg(chip->dev, "%d\n", chip->pmic_chg_irq[interrupt]); + enable_irq(chip->pmic_chg_irq[interrupt]); + } +} + +static void pm8921_chg_disable_irq(struct pm8921_chg_chip *chip, int interrupt) +{ + if (__test_and_clear_bit(interrupt, chip->enabled_irqs)) { + dev_dbg(chip->dev, "%d\n", chip->pmic_chg_irq[interrupt]); + disable_irq_nosync(chip->pmic_chg_irq[interrupt]); + } +} + +static int pm_chg_get_rt_status(struct pm8921_chg_chip *chip, int irq_id) +{ + return pm8xxx_read_irq_stat(chip->dev->parent, + chip->pmic_chg_irq[irq_id]); +} + +/* Treat OverVoltage/UnderVoltage as source missing */ +static int is_usb_chg_plugged_in(struct pm8921_chg_chip *chip) +{ + int pres, ov, uv; + + pres = pm_chg_get_rt_status(chip, USBIN_VALID_IRQ); + ov = pm_chg_get_rt_status(chip, USBIN_OV_IRQ); + uv = pm_chg_get_rt_status(chip, USBIN_UV_IRQ); + + return pres && !ov && !uv; +} + +/* Treat OverVoltage/UnderVoltage as source missing */ +static int is_dc_chg_plugged_in(struct pm8921_chg_chip *chip) +{ + int pres, ov, uv; + + pres = pm_chg_get_rt_status(chip, DCIN_VALID_IRQ); + ov = pm_chg_get_rt_status(chip, DCIN_OV_IRQ); + uv = pm_chg_get_rt_status(chip, DCIN_UV_IRQ); + + return pres && !ov && !uv; +} + +static int is_battery_charging(int fsm_state) +{ + switch (fsm_state) { + case FSM_STATE_ATC_2A: + case FSM_STATE_ATC_2B: + case FSM_STATE_ON_CHG_AND_BAT_6: + case FSM_STATE_FAST_CHG_7: + case FSM_STATE_TRKL_CHG_8: + return 1; + } + return 0; +} + +static void bms_notify(struct work_struct *work) +{ + struct bms_notify *n = container_of(work, struct bms_notify, work); + + if (n->is_charging) + pm8921_bms_charging_began(); + else + pm8921_bms_charging_end(); +} + +static enum power_supply_property pm_power_props[] = { + POWER_SUPPLY_PROP_PRESENT, +}; + +static char *pm_power_supplied_to[] = { + "battery", +}; + +static int pm_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pm8921_chg_chip *chip; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + if (psy->type == POWER_SUPPLY_TYPE_MAINS) { + chip = container_of(psy, struct pm8921_chg_chip, + dc_psy); + val->intval = is_dc_chg_plugged_in(chip); + } + if (psy->type == POWER_SUPPLY_TYPE_USB) { + chip = container_of(psy, struct pm8921_chg_chip, + usb_psy); + val->intval = is_usb_chg_plugged_in(chip); + } + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property msm_batt_power_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int get_prop_battery_mvolts(struct pm8921_chg_chip *chip) +{ + int rc; + struct pm8921_adc_chan_result result; + + rc = pm8921_adc_read(chip->vbat_channel, &result); + if (rc) { + pr_err("error reading adc channel = %d, rc = %d\n", + chip->vbat_channel, rc); + return rc; + } + pr_debug("mvolts phy = %lld meas = 0x%llx", result.physical, + result.measurement); + return (int)result.physical; +} + +static int get_prop_batt_capacity(struct pm8921_chg_chip *chip) +{ + return pm8921_bms_get_percent_charge(); +} + +static int get_prop_batt_health(struct pm8921_chg_chip *chip) +{ + int temp; + + temp = pm_chg_get_rt_status(chip, BATTTEMP_HOT_IRQ); + if (temp) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + temp = pm_chg_get_rt_status(chip, BATTTEMP_COLD_IRQ); + if (temp) + return POWER_SUPPLY_HEALTH_COLD; + + return POWER_SUPPLY_HEALTH_GOOD; +} + +static int get_prop_batt_present(struct pm8921_chg_chip *chip) +{ + return pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ); +} + +static int get_prop_charge_type(struct pm8921_chg_chip *chip) +{ + int temp; + + temp = pm_chg_get_rt_status(chip, TRKLCHG_IRQ); + if (temp) + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + + temp = pm_chg_get_rt_status(chip, FASTCHG_IRQ); + if (temp) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int get_prop_batt_status(struct pm8921_chg_chip *chip) +{ + int temp = 0; + + /* TODO reading the FSM state is more reliable */ + temp = pm_chg_get_rt_status(chip, TRKLCHG_IRQ); + + temp |= pm_chg_get_rt_status(chip, FASTCHG_IRQ); + if (temp) + return POWER_SUPPLY_STATUS_CHARGING; + /* + * The battery is not charging + * check the FET - if on battery is discharging + * - if off battery is isolated(full) and the system + * is being driven from a charger + */ + temp = pm_chg_get_rt_status(chip, BATFET_IRQ); + if (temp) + return POWER_SUPPLY_STATUS_DISCHARGING; + + return POWER_SUPPLY_STATUS_FULL; +} + +static int pm_batt_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct pm8921_chg_chip *chip = container_of(psy, struct pm8921_chg_chip, + batt_psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = get_prop_charge_type(chip); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = get_prop_batt_health(chip); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = chip->max_voltage; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = chip->min_voltage; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = get_prop_battery_mvolts(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_prop_batt_capacity(chip); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void (*notify_vbus_state_func_ptr)(int); +static int usb_chg_current; +static DEFINE_SPINLOCK(vbus_lock); + +int pm8921_charger_register_vbus_sn(void (*callback)(int)) +{ + pr_debug("%p\n", callback); + notify_vbus_state_func_ptr = callback; + return 0; +} +EXPORT_SYMBOL_GPL(pm8921_charger_register_vbus_sn); + +/* this is passed to the hsusb via platform_data msm_otg_pdata */ +void pm8921_charger_unregister_vbus_sn(void (*callback)(int)) +{ + pr_debug("%p\n", callback); + notify_vbus_state_func_ptr = NULL; +} +EXPORT_SYMBOL_GPL(pm8921_charger_unregister_vbus_sn); + +static void notify_usb_of_the_plugin_event(int plugin) +{ + plugin = !!plugin; + if (notify_vbus_state_func_ptr) { + pr_debug("notifying plugin\n"); + (*notify_vbus_state_func_ptr) (plugin); + } else { + pr_debug("unable to notify plugin\n"); + } +} + +struct usb_ma_limit_entry { + int usb_ma; + u8 chg_iusb_value; +}; + +static struct usb_ma_limit_entry usb_ma_table[] = { + {100, 0}, + {500, 1}, + {700, 2}, + {850, 3}, + {900, 4}, + {1100, 5}, + {1300, 6}, + {1500, 7}, +}; + +/* assumes vbus_lock is held */ +static void __pm8921_charger_vbus_draw(unsigned int mA) +{ + int i, rc; + + if (mA > 0 && mA <= 2) { + usb_chg_current = 0; + rc = pm_chg_iusbmax_set(the_chip, + usb_ma_table[0].chg_iusb_value); + if (rc) { + pr_err("unable to set iusb to %d rc = %d\n", + usb_ma_table[0].chg_iusb_value, rc); + } + rc = pm_chg_usb_suspend_enable(the_chip, 1); + if (rc) + pr_err("fail to set suspend bit rc=%d\n", rc); + } else { + rc = pm_chg_usb_suspend_enable(the_chip, 0); + if (rc) + pr_err("fail to reset suspend bit rc=%d\n", rc); + for (i = ARRAY_SIZE(usb_ma_table) - 1; i >= 0; i--) { + if (usb_ma_table[i].usb_ma <= mA) + break; + } + if (i < 0) + i = 0; + rc = pm_chg_iusbmax_set(the_chip, + usb_ma_table[i].chg_iusb_value); + if (rc) { + pr_err("unable to set iusb to %d rc = %d\n", + usb_ma_table[i].chg_iusb_value, rc); + } + } +} + +/* USB calls these to tell us how much max usb current the system can draw */ +void pm8921_charger_vbus_draw(unsigned int mA) +{ + unsigned long flags; + + pr_debug("Enter charge=%d\n", mA); + spin_lock_irqsave(&vbus_lock, flags); + if (the_chip) { + __pm8921_charger_vbus_draw(mA); + } else { + /* + * called before pmic initialized, + * save this value and use it at probe + */ + usb_chg_current = mA; + } + spin_unlock_irqrestore(&vbus_lock, flags); +} +EXPORT_SYMBOL_GPL(pm8921_charger_vbus_draw); + +static void handle_usb_insertion_removal(struct pm8921_chg_chip *chip) +{ + int usb_present; + + usb_present = is_usb_chg_plugged_in(chip); + if (chip->usb_present ^ usb_present) { + notify_usb_of_the_plugin_event(usb_present); + chip->usb_present = usb_present; + power_supply_changed(&chip->usb_psy); + } +} + +static void handle_dc_removal_insertion(struct pm8921_chg_chip *chip) +{ + int dc_present; + + dc_present = is_dc_chg_plugged_in(chip); + if (chip->dc_present ^ dc_present) { + chip->dc_present = dc_present; + power_supply_changed(&chip->dc_psy); + } +} + +static irqreturn_t usbin_valid_irq_handler(int irq, void *data) +{ + handle_usb_insertion_removal(data); + return IRQ_HANDLED; +} + +static irqreturn_t usbin_ov_irq_handler(int irq, void *data) +{ + handle_usb_insertion_removal(data); + return IRQ_HANDLED; +} + +static irqreturn_t batt_inserted_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + int status; + + status = pm_chg_get_rt_status(chip, + BATT_INSERTED_IRQ); + pr_debug("battery present=%d", status); + power_supply_changed(&chip->batt_psy); + return IRQ_HANDLED; +} +/* this interrupt used to restart charging a battery */ +static irqreturn_t vbatdet_low_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + power_supply_changed(&chip->dc_psy); + return IRQ_HANDLED; +} + +static irqreturn_t usbin_uv_irq_handler(int irq, void *data) +{ + handle_usb_insertion_removal(data); + return IRQ_HANDLED; +} + +static irqreturn_t vbat_ov_irq_handler(int irq, void *data) +{ + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t chgwdog_irq_handler(int irq, void *data) +{ + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t vcp_irq_handler(int irq, void *data) +{ + pr_warning("VCP triggered BATDET forced on\n"); + pr_debug("state_changed_to=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t atcdone_irq_handler(int irq, void *data) +{ + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t atcfail_irq_handler(int irq, void *data) +{ + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t chgdone_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + pr_debug("state_changed_to=%d\n", pm_chg_get_fsm_state(data)); + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + power_supply_changed(&chip->dc_psy); + return IRQ_HANDLED; +} + +static irqreturn_t chgfail_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + pr_debug("state_changed_to=%d\n", pm_chg_get_fsm_state(data)); + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + power_supply_changed(&chip->dc_psy); + return IRQ_HANDLED; +} + +static irqreturn_t chgstate_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + int new_is_charging = 0, fsm_state; + + pr_debug("state_changed_to=%d\n", pm_chg_get_fsm_state(data)); + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + power_supply_changed(&chip->dc_psy); + + fsm_state = pm_chg_get_fsm_state(chip); + new_is_charging = is_battery_charging(fsm_state); + + if (chip->bms_notify.is_charging ^ new_is_charging) { + chip->bms_notify.is_charging = new_is_charging; + schedule_work(&(chip->bms_notify.work)); + } + return IRQ_HANDLED; +} + +static irqreturn_t loop_change_irq_handler(int irq, void *data) +{ + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t fastchg_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + power_supply_changed(&chip->batt_psy); + return IRQ_HANDLED; +} + +static irqreturn_t trklchg_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + power_supply_changed(&chip->batt_psy); + return IRQ_HANDLED; +} + +static irqreturn_t batt_removed_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + int status; + + status = pm_chg_get_rt_status(chip, BATT_REMOVED_IRQ); + pr_debug("battery present=%d state=%d", !status, + pm_chg_get_fsm_state(data)); + power_supply_changed(&chip->batt_psy); + return IRQ_HANDLED; +} + +static irqreturn_t batttemp_hot_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + power_supply_changed(&chip->batt_psy); + return IRQ_HANDLED; +} + +static irqreturn_t chghot_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + pr_debug("Chg hot fsm_state=%d\n", pm_chg_get_fsm_state(data)); + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + power_supply_changed(&chip->dc_psy); + return IRQ_HANDLED; +} + +static irqreturn_t batttemp_cold_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + pr_debug("Batt cold fsm_state=%d\n", pm_chg_get_fsm_state(data)); + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + power_supply_changed(&chip->dc_psy); + return IRQ_HANDLED; +} + +static irqreturn_t chg_gone_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + pr_debug("Chg gone fsm_state=%d\n", pm_chg_get_fsm_state(data)); + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + power_supply_changed(&chip->dc_psy); + return IRQ_HANDLED; +} + +static irqreturn_t bat_temp_ok_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + pr_debug("batt temp ok fsm_state=%d\n", pm_chg_get_fsm_state(data)); + power_supply_changed(&chip->batt_psy); + power_supply_changed(&chip->usb_psy); + power_supply_changed(&chip->dc_psy); + return IRQ_HANDLED; +} + +static irqreturn_t coarse_det_low_irq_handler(int irq, void *data) +{ + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t vdd_loop_irq_handler(int irq, void *data) +{ + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t vreg_ov_irq_handler(int irq, void *data) +{ + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t vbatdet_irq_handler(int irq, void *data) +{ + pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); + return IRQ_HANDLED; +} + +static irqreturn_t batfet_irq_handler(int irq, void *data) +{ + struct pm8921_chg_chip *chip = data; + + pr_debug("vreg ov\n"); + power_supply_changed(&chip->batt_psy); + return IRQ_HANDLED; +} + +static irqreturn_t dcin_valid_irq_handler(int irq, void *data) +{ + handle_dc_removal_insertion(data); + return IRQ_HANDLED; +} + +static irqreturn_t dcin_ov_irq_handler(int irq, void *data) +{ + handle_dc_removal_insertion(data); + return IRQ_HANDLED; +} + +static irqreturn_t dcin_uv_irq_handler(int irq, void *data) +{ + handle_dc_removal_insertion(data); + return IRQ_HANDLED; +} + +static int set_disable_status_param(const char *val, struct kernel_param *kp) +{ + int ret; + struct pm8921_chg_chip *chip = the_chip; + + ret = param_set_int(val, kp); + if (ret) { + pr_err("error setting value %d\n", ret); + return ret; + } + pr_info("factory set disable param to %d\n", charging_disabled); + if (chip) { + pm_chg_auto_enable(chip, !charging_disabled); + pm_chg_charge_dis(chip, charging_disabled); + } + return 0; +} +module_param_call(disabled, set_disable_status_param, param_get_uint, + &charging_disabled, 0644); + +static void free_irqs(struct pm8921_chg_chip *chip) +{ + int i; + + for (i = 0; i < PM_CHG_MAX_INTS; i++) + if (chip->pmic_chg_irq[i]) { + free_irq(chip->pmic_chg_irq[i], chip); + chip->pmic_chg_irq[i] = 0; + } +} + +/* determines the initial present states and notifies msm_charger */ +static void __devinit determine_initial_state(struct pm8921_chg_chip *chip) +{ + unsigned long flags; + int fsm_state; + + chip->dc_present = !!is_dc_chg_plugged_in(chip); + chip->usb_present = !!is_usb_chg_plugged_in(chip); + + notify_usb_of_the_plugin_event(chip->usb_present); + + pm8921_chg_enable_irq(chip, DCIN_VALID_IRQ); + pm8921_chg_enable_irq(chip, USBIN_VALID_IRQ); + pm8921_chg_enable_irq(chip, BATT_REMOVED_IRQ); + pm8921_chg_enable_irq(chip, BATT_REMOVED_IRQ); + pm8921_chg_enable_irq(chip, CHGSTATE_IRQ); + + spin_lock_irqsave(&vbus_lock, flags); + if (usb_chg_current) { + /* reissue a vbus draw call */ + __pm8921_charger_vbus_draw(usb_chg_current); + } + spin_unlock_irqrestore(&vbus_lock, flags); + + fsm_state = pm_chg_get_fsm_state(chip); + if (is_battery_charging(fsm_state)) { + chip->bms_notify.is_charging = 1; + pm8921_bms_charging_began(); + } + + pr_debug("usb = %d, dc = %d batt = %d state=%d\n", + chip->usb_present, + chip->dc_present, + get_prop_batt_present(chip), + fsm_state); +} + +struct pm_chg_irq_init_data { + unsigned int irq_id; + char *name; + unsigned long flags; + irqreturn_t (*handler)(int, void *); +}; + +#define CHG_IRQ(_id, _flags, _handler) \ +{ \ + .irq_id = _id, \ + .name = #_id, \ + .flags = _flags, \ + .handler = _handler, \ +} +struct pm_chg_irq_init_data chg_irq_data[] = { + CHG_IRQ(USBIN_VALID_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + usbin_valid_irq_handler), + CHG_IRQ(USBIN_OV_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + usbin_ov_irq_handler), + CHG_IRQ(BATT_INSERTED_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + batt_inserted_irq_handler), + CHG_IRQ(VBATDET_LOW_IRQ, IRQF_TRIGGER_RISING, vbatdet_low_irq_handler), + CHG_IRQ(USBIN_UV_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + usbin_uv_irq_handler), + CHG_IRQ(VBAT_OV_IRQ, IRQF_TRIGGER_RISING, vbat_ov_irq_handler), + CHG_IRQ(CHGWDOG_IRQ, IRQF_TRIGGER_RISING, chgwdog_irq_handler), + CHG_IRQ(VCP_IRQ, IRQF_TRIGGER_RISING, vcp_irq_handler), + CHG_IRQ(ATCDONE_IRQ, IRQF_TRIGGER_RISING, atcdone_irq_handler), + CHG_IRQ(ATCFAIL_IRQ, IRQF_TRIGGER_RISING, atcfail_irq_handler), + CHG_IRQ(CHGDONE_IRQ, IRQF_TRIGGER_RISING, chgdone_irq_handler), + CHG_IRQ(CHGFAIL_IRQ, IRQF_TRIGGER_RISING, chgfail_irq_handler), + CHG_IRQ(CHGSTATE_IRQ, IRQF_TRIGGER_RISING, chgstate_irq_handler), + CHG_IRQ(LOOP_CHANGE_IRQ, IRQF_TRIGGER_RISING, loop_change_irq_handler), + CHG_IRQ(FASTCHG_IRQ, IRQF_TRIGGER_RISING, fastchg_irq_handler), + CHG_IRQ(TRKLCHG_IRQ, IRQF_TRIGGER_RISING, trklchg_irq_handler), + CHG_IRQ(BATT_REMOVED_IRQ, IRQF_TRIGGER_RISING, + batt_removed_irq_handler), + CHG_IRQ(BATTTEMP_HOT_IRQ, IRQF_TRIGGER_RISING, + batttemp_hot_irq_handler), + CHG_IRQ(CHGHOT_IRQ, IRQF_TRIGGER_RISING, chghot_irq_handler), + CHG_IRQ(BATTTEMP_COLD_IRQ, IRQF_TRIGGER_RISING, + batttemp_cold_irq_handler), + CHG_IRQ(CHG_GONE_IRQ, IRQF_TRIGGER_RISING, chg_gone_irq_handler), + CHG_IRQ(BAT_TEMP_OK_IRQ, IRQF_TRIGGER_RISING, bat_temp_ok_irq_handler), + CHG_IRQ(COARSE_DET_LOW_IRQ, IRQF_TRIGGER_RISING, + coarse_det_low_irq_handler), + CHG_IRQ(VDD_LOOP_IRQ, IRQF_TRIGGER_RISING, vdd_loop_irq_handler), + CHG_IRQ(VREG_OV_IRQ, IRQF_TRIGGER_RISING, vreg_ov_irq_handler), + CHG_IRQ(VBATDET_IRQ, IRQF_TRIGGER_RISING, vbatdet_irq_handler), + CHG_IRQ(BATFET_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + batfet_irq_handler), + CHG_IRQ(DCIN_VALID_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dcin_valid_irq_handler), + CHG_IRQ(DCIN_OV_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dcin_ov_irq_handler), + CHG_IRQ(DCIN_UV_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dcin_uv_irq_handler), +}; + +static int __devinit request_irqs(struct pm8921_chg_chip *chip, + struct platform_device *pdev) +{ + struct resource *res; + int ret, i; + + ret = 0; + bitmap_fill(chip->enabled_irqs, PM_CHG_MAX_INTS); + + for (i = 0; i < ARRAY_SIZE(chg_irq_data); i++) { + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + chg_irq_data[i].name); + if (res == NULL) { + pr_err("couldn't find %s\n", chg_irq_data[i].name); + goto err_out; + } + ret = request_irq(res->start, chg_irq_data[i].handler, + chg_irq_data[i].flags, + chg_irq_data[i].name, chip); + if (ret < 0) { + pr_err("couldn't request %d (%s) %d\n", res->start, + chg_irq_data[i].name, ret); + goto err_out; + } + chip->pmic_chg_irq[chg_irq_data[i].irq_id] = res->start; + pm8921_chg_disable_irq(chip, chg_irq_data[i].irq_id); + } + return 0; + +err_out: + free_irqs(chip); + return -EINVAL; +} + +#define ENUM_TIMER_STOP_BIT BIT(1) +#define BOOT_DONE_BIT BIT(6) +#define CHG_BATFET_ON_BIT BIT(3) +#define CHG_VCP_EN BIT(0) +#define CHG_BAT_TEMP_DIS_BIT BIT(2) +#define SAFE_CURRENT_MA 1500 +static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip) +{ + int rc; + + rc = pm_chg_masked_write(chip, SYS_CONFIG_2, + BOOT_DONE_BIT, BOOT_DONE_BIT); + if (rc) { + pr_err("Failed to set BOOT_DONE_BIT rc=%d\n", rc); + return rc; + } + + rc = pm_chg_vddsafe_set(chip, chip->max_voltage); + if (rc) { + pr_err("Failed to set safe voltage to %d rc=%d\n", + chip->max_voltage, rc); + return rc; + } + rc = pm_chg_vbatdet_set(chip, chip->resume_voltage); + if (rc) { + pr_err("Failed to set vbatdet comprator voltage to %d rc=%d\n", + chip->resume_voltage, rc); + return rc; + } + + rc = pm_chg_vddmax_set(chip, chip->max_voltage); + if (rc) { + pr_err("Failed to set max voltage to %d rc=%d\n", + chip->max_voltage, rc); + return rc; + } + rc = pm_chg_ibatsafe_set(chip, SAFE_CURRENT_MA); + if (rc) { + pr_err("Failed to set max voltage to %d rc=%d\n", + SAFE_CURRENT_MA, rc); + return rc; + } + + /* TODO needs to be changed as per the temeperature of the battery */ + rc = pm_chg_ibatmax_set(chip, 400); + if (rc) { + pr_err("Failed to set max current to 400 rc=%d\n", rc); + return rc; + } + + rc = pm_chg_iterm_set(chip, chip->term_current); + if (rc) { + pr_err("Failed to set term current to %d rc=%d\n", + chip->term_current, rc); + return rc; + } + + /* Disable the ENUM TIMER */ + rc = pm_chg_masked_write(chip, PBL_ACCESS2, ENUM_TIMER_STOP_BIT, + ENUM_TIMER_STOP_BIT); + if (rc) { + pr_err("Failed to set enum timer stop rc=%d\n", rc); + return rc; + } + + /* init with the lowest USB current */ + rc = pm_chg_iusbmax_set(chip, usb_ma_table[0].chg_iusb_value); + if (rc) { + pr_err("Failed to set usb max to %d rc=%d\n", + usb_ma_table[0].chg_iusb_value, rc); + return rc; + } + rc = pm_chg_disable_wd(chip); + if (rc) { + pr_err("Failed to disable wd rc=%d\n", rc); + return rc; + } + + rc = pm_chg_masked_write(chip, CHG_CNTRL_2, + CHG_BAT_TEMP_DIS_BIT, 0); + if (rc) { + pr_err("Failed to enable temp control chg rc=%d\n", rc); + return rc; + } + /* switch to a 3.2Mhz for the buck */ + rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CLOCK_CTRL, 0x15); + if (rc) { + pr_err("Failed to switch buck clk rc=%d\n", rc); + return rc; + } + + /* Workarounds for die 1.1 and 1.0 */ + if (pm8xxx_get_revision(chip->dev->parent) < PM8XXX_REVISION_8921_2p0) { + pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST2, 0xF1); + pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x8C); + pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xCE); + pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xD8); + } + + rc = pm_chg_charge_dis(chip, charging_disabled); + if (rc) { + pr_err("Failed to disable CHG_CHARGE_DIS bit rc=%d\n", rc); + return rc; + } + + rc = pm_chg_auto_enable(chip, !charging_disabled); + if (rc) { + pr_err("Failed to enable charging rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int get_rt_status(void *data, u64 * val) +{ + int i = (int)data; + int ret; + + /* global irq number is passed in via data */ + ret = pm_chg_get_rt_status(the_chip, i); + *val = ret; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(rt_fops, get_rt_status, NULL, "%llu\n"); + +static int get_fsm_status(void *data, u64 * val) +{ + u8 temp; + + temp = pm_chg_get_fsm_state(the_chip); + *val = temp; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(fsm_fops, get_fsm_status, NULL, "%llu\n"); + +static int get_reg(void *data, u64 * val) +{ + int addr = (int)data; + int ret; + u8 temp; + + ret = pm8xxx_readb(the_chip->dev->parent, addr, &temp); + if (ret) { + pr_err("pm8xxx_readb to %x value =%d errored = %d\n", + addr, temp, ret); + return -EAGAIN; + } + *val = temp; + return 0; +} + +static int set_reg(void *data, u64 val) +{ + int addr = (int)data; + int ret; + u8 temp; + + temp = (u8) val; + ret = pm8xxx_writeb(the_chip->dev->parent, addr, temp); + if (ret) { + pr_err("pm8xxx_writeb to %x value =%d errored = %d\n", + addr, temp, ret); + return -EAGAIN; + } + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n"); + +static void create_debugfs_entries(struct pm8921_chg_chip *chip) +{ + int i; + + chip->dent = debugfs_create_dir("pm8921_chg", NULL); + + if (IS_ERR(chip->dent)) { + pr_err("pmic charger couldnt create debugfs dir\n"); + return; + } + + debugfs_create_file("CHG_CNTRL", 0644, chip->dent, + (void *)CHG_CNTRL, ®_fops); + debugfs_create_file("CHG_CNTRL_2", 0644, chip->dent, + (void *)CHG_CNTRL_2, ®_fops); + debugfs_create_file("CHG_CNTRL_3", 0644, chip->dent, + (void *)CHG_CNTRL_3, ®_fops); + debugfs_create_file("PBL_ACCESS1", 0644, chip->dent, + (void *)PBL_ACCESS1, ®_fops); + debugfs_create_file("PBL_ACCESS2", 0644, chip->dent, + (void *)PBL_ACCESS2, ®_fops); + debugfs_create_file("SYS_CONFIG_1", 0644, chip->dent, + (void *)SYS_CONFIG_1, ®_fops); + debugfs_create_file("SYS_CONFIG_2", 0644, chip->dent, + (void *)SYS_CONFIG_2, ®_fops); + debugfs_create_file("CHG_VDD_MAX", 0644, chip->dent, + (void *)CHG_VDD_MAX, ®_fops); + debugfs_create_file("CHG_VDD_SAFE", 0644, chip->dent, + (void *)CHG_VDD_SAFE, ®_fops); + debugfs_create_file("CHG_VBAT_DET", 0644, chip->dent, + (void *)CHG_VBAT_DET, ®_fops); + debugfs_create_file("CHG_IBAT_MAX", 0644, chip->dent, + (void *)CHG_IBAT_MAX, ®_fops); + debugfs_create_file("CHG_IBAT_SAFE", 0644, chip->dent, + (void *)CHG_IBAT_SAFE, ®_fops); + debugfs_create_file("CHG_VIN_MIN", 0644, chip->dent, + (void *)CHG_VIN_MIN, ®_fops); + debugfs_create_file("CHG_VTRICKLE", 0644, chip->dent, + (void *)CHG_VTRICKLE, ®_fops); + debugfs_create_file("CHG_ITRICKLE", 0644, chip->dent, + (void *)CHG_ITRICKLE, ®_fops); + debugfs_create_file("CHG_ITERM", 0644, chip->dent, + (void *)CHG_ITERM, ®_fops); + debugfs_create_file("CHG_TCHG_MAX", 0644, chip->dent, + (void *)CHG_TCHG_MAX, ®_fops); + debugfs_create_file("CHG_TWDOG", 0644, chip->dent, + (void *)CHG_TWDOG, ®_fops); + debugfs_create_file("CHG_TEMP_THRESH", 0644, chip->dent, + (void *)CHG_TEMP_THRESH, ®_fops); + debugfs_create_file("CHG_COMP_OVR", 0644, chip->dent, + (void *)CHG_COMP_OVR, ®_fops); + debugfs_create_file("CHG_BUCK_CTRL_TEST1", 0644, chip->dent, + (void *)CHG_BUCK_CTRL_TEST1, ®_fops); + debugfs_create_file("CHG_BUCK_CTRL_TEST2", 0644, chip->dent, + (void *)CHG_BUCK_CTRL_TEST2, ®_fops); + debugfs_create_file("CHG_BUCK_CTRL_TEST3", 0644, chip->dent, + (void *)CHG_BUCK_CTRL_TEST3, ®_fops); + debugfs_create_file("CHG_TEST", 0644, chip->dent, + (void *)CHG_TEST, ®_fops); + + debugfs_create_file("FSM_STATE", 0644, chip->dent, NULL, + &fsm_fops); + + for (i = 0; i < ARRAY_SIZE(chg_irq_data); i++) { + if (chip->pmic_chg_irq[chg_irq_data[i].irq_id]) + debugfs_create_file(chg_irq_data[i].name, 0444, + chip->dent, + (void *)chg_irq_data[i].irq_id, + &rt_fops); + } +} + +static int __devinit pm8921_charger_probe(struct platform_device *pdev) +{ + int rc = 0; + struct pm8921_chg_chip *chip; + const struct pm8921_charger_platform_data *pdata + = pdev->dev.platform_data; + + if (!pdata) { + pr_err("missing platform data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct pm8921_chg_chip), + GFP_KERNEL); + if (!chip) { + pr_err("Cannot allocate pm_chg_chip\n"); + return -ENOMEM; + } + + chip->dev = &pdev->dev; + chip->safety_time = pdata->safety_time; + chip->update_time = pdata->update_time; + chip->max_voltage = pdata->max_voltage; + chip->min_voltage = pdata->min_voltage; + chip->resume_voltage = pdata->resume_voltage; + chip->term_current = pdata->term_current; + chip->vbat_channel = pdata->charger_cdata.vbat_channel; + + rc = pm8921_chg_hw_init(chip); + if (rc) { + pr_err("couldn't init hardware rc=%d\n", rc); + goto free_chip; + } + + chip->usb_psy.name = "usb", + chip->usb_psy.type = POWER_SUPPLY_TYPE_USB, + chip->usb_psy.supplied_to = pm_power_supplied_to, + chip->usb_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to), + chip->usb_psy.properties = pm_power_props, + chip->usb_psy.num_properties = ARRAY_SIZE(pm_power_props), + chip->usb_psy.get_property = pm_power_get_property, + + chip->dc_psy.name = "ac", + chip->dc_psy.type = POWER_SUPPLY_TYPE_MAINS, + chip->dc_psy.supplied_to = pm_power_supplied_to, + chip->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to), + chip->dc_psy.properties = pm_power_props, + chip->dc_psy.num_properties = ARRAY_SIZE(pm_power_props), + chip->dc_psy.get_property = pm_power_get_property, + + chip->batt_psy.name = "battery", + chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY, + chip->batt_psy.properties = msm_batt_power_props, + chip->batt_psy.num_properties = ARRAY_SIZE(msm_batt_power_props), + chip->batt_psy.get_property = pm_batt_power_get_property, + + rc = power_supply_register(chip->dev, &chip->usb_psy); + if (rc < 0) { + pr_err("power_supply_register usb failed rc = %d\n", rc); + goto free_irq; + } + + rc = power_supply_register(chip->dev, &chip->dc_psy); + if (rc < 0) { + pr_err("power_supply_register dc failed rc = %d\n", rc); + goto unregister_usb; + } + + rc = power_supply_register(chip->dev, &chip->batt_psy); + if (rc < 0) { + pr_err("power_supply_register batt failed rc = %d\n", rc); + goto unregister_dc; + } + + rc = request_irqs(chip, pdev); + if (rc) { + pr_err("couldn't register interrupts rc=%d\n", rc); + goto unregister_batt; + } + + platform_set_drvdata(pdev, chip); + the_chip = chip; + create_debugfs_entries(chip); + + INIT_WORK(&chip->bms_notify.work, bms_notify); + /* determine what state the charger is in */ + determine_initial_state(chip); + + return 0; + +free_irq: + free_irqs(chip); +unregister_batt: + power_supply_unregister(&chip->batt_psy); +unregister_dc: + power_supply_unregister(&chip->dc_psy); +unregister_usb: + power_supply_unregister(&chip->usb_psy); +free_chip: + kfree(chip); + return rc; +} + +static int __devexit pm8921_charger_remove(struct platform_device *pdev) +{ + struct pm8921_chg_chip *chip = platform_get_drvdata(pdev); + + free_irqs(chip); + platform_set_drvdata(pdev, NULL); + the_chip = NULL; + kfree(chip); + return 0; +} + +static struct platform_driver pm8921_charger_driver = { + .probe = pm8921_charger_probe, + .remove = __devexit_p(pm8921_charger_remove), + .driver = { + .name = PM8921_CHARGER_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8921_charger_init(void) +{ + return platform_driver_register(&pm8921_charger_driver); +} + +static void __exit pm8921_charger_exit(void) +{ + platform_driver_unregister(&pm8921_charger_driver); +} + +late_initcall(pm8921_charger_init); +module_exit(pm8921_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8921 charger/battery driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8921_CHARGER_DEV_NAME); diff --git a/drivers/power/pmic8058-charger.c b/drivers/power/pmic8058-charger.c new file mode 100644 index 00000000000..8ea794924e3 --- /dev/null +++ b/drivers/power/pmic8058-charger.c @@ -0,0 +1,1954 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Config Regs and their bits*/ +#define PM8058_CHG_TEST 0x75 +#define IGNORE_LL 2 +#define PM8058_CHG_TEST_2 0xEA +#define PM8058_CHG_TEST_3 0xEB +#define PM8058_OVP_TEST_REG 0xF6 +#define FORCE_OVP_OFF 3 + +#define PM8058_CHG_CNTRL 0x1E +#define CHG_TRICKLE_EN 7 +#define CHG_USB_SUSPEND 6 +#define CHG_IMON_CAL 5 +#define CHG_IMON_GAIN 4 +#define CHG_CHARGE_BAT 3 +#define CHG_VBUS_FROM_BOOST_OVRD 2 +#define CHG_CHARGE_DIS 1 +#define CHG_VCP_EN 0 + +#define PM8058_CHG_CNTRL_2 0xD8 +#define ATC_DIS 7 /* coincell backed */ +#define CHARGE_AUTO_DIS 6 +#define DUMB_CHG_OVRD 5 /* coincell backed */ +#define ENUM_DONE 4 +#define CHG_TEMP_MODE 3 +#define CHG_BATT_TEMP_DIS 1 /* coincell backed */ +#define CHG_FAILED_CLEAR 0 + +#define PM8058_CHG_VMAX_SEL 0x21 +#define PM8058_CHG_VBAT_DET 0xD9 +#define PM8058_CHG_IMAX 0x1F +#define PM8058_CHG_TRICKLE 0xDB +#define PM8058_CHG_ITERM 0xDC +#define PM8058_CHG_TTRKL_MAX 0xE1 +#define PM8058_CHG_TCHG_MAX 0xE4 +#define PM8058_CHG_TEMP_THRESH 0xE2 +#define PM8058_CHG_TEMP_REG 0xE3 +#define PM8058_CHG_PULSE 0x22 + +/* IRQ STATUS and CLEAR */ +#define PM8058_CHG_STATUS_CLEAR_IRQ_1 0x31 +#define PM8058_CHG_STATUS_CLEAR_IRQ_3 0x33 +#define PM8058_CHG_STATUS_CLEAR_IRQ_10 0xB3 +#define PM8058_CHG_STATUS_CLEAR_IRQ_11 0xB4 + +/* IRQ MASKS */ +#define PM8058_CHG_MASK_IRQ_1 0x38 + +#define PM8058_CHG_MASK_IRQ_3 0x3A +#define PM8058_CHG_MASK_IRQ_10 0xBA +#define PM8058_CHG_MASK_IRQ_11 0xBB + +/* IRQ Real time status regs */ +#define PM8058_CHG_STATUS_RT_1 0x3F +#define STATUS_RTCHGVAL 7 +#define STATUS_RTCHGINVAL 6 +#define STATUS_RTBATT_REPLACE 5 +#define STATUS_RTVBATDET_LOW 4 +#define STATUS_RTCHGILIM 3 +#define STATUS_RTPCTDONE 1 +#define STATUS_RTVCP 0 +#define PM8058_CHG_STATUS_RT_3 0x41 +#define PM8058_CHG_STATUS_RT_10 0xC1 +#define PM8058_CHG_STATUS_RT_11 0xC2 + +/* VTRIM */ +#define PM8058_CHG_VTRIM 0x1D +#define PM8058_CHG_VBATDET_TRIM 0x1E +#define PM8058_CHG_ITRIM 0x1F +#define PM8058_CHG_TTRIM 0x20 + +#define AUTO_CHARGING_VMAXSEL 4200 +#define AUTO_CHARGING_FAST_TIME_MAX_MINUTES 512 +#define AUTO_CHARGING_TRICKLE_TIME_MINUTES 30 +#define AUTO_CHARGING_VEOC_ITERM 100 +#define AUTO_CHARGING_IEOC_ITERM 160 +#define AUTO_CHARGING_RESUME_MV 4100 + +#define AUTO_CHARGING_VBATDET 4150 +#define AUTO_CHARGING_VBATDET_DEBOUNCE_TIME_MS 3000 +#define AUTO_CHARGING_VEOC_VBATDET 4100 +#define AUTO_CHARGING_VEOC_TCHG 16 +#define AUTO_CHARGING_VEOC_TCHG_FINAL_CYCLE 32 +#define AUTO_CHARGING_VEOC_BEGIN_TIME_MS 5400000 + +#define AUTO_CHARGING_VEOC_VBAT_LOW_CHECK_TIME_MS 60000 +#define AUTO_CHARGING_RESUME_CHARGE_DETECTION_COUNTER 5 + +#define AUTO_CHARGING_DONE_CHECK_TIME_MS 1000 + +#define PM8058_CHG_I_STEP_MA 50 +#define PM8058_CHG_I_MIN_MA 50 +#define PM8058_CHG_T_TCHG_SHIFT 2 +#define PM8058_CHG_I_TERM_STEP_MA 10 +#define PM8058_CHG_V_STEP_MV 25 +#define PM8058_CHG_V_MIN_MV 2400 +/* + * enum pmic_chg_interrupts: pmic interrupts + * @CHGVAL_IRQ: charger V between 3.3 and 7.9 + * @CHGINVAL_IRQ: charger V outside 3.3 and 7.9 + * @VBATDET_LOW_IRQ: VBAT < VBATDET + * @VCP_IRQ: VDD went below VBAT: BAT_FET is turned on + * @CHGILIM_IRQ: mA consumed>IMAXSEL: chgloop draws less mA + * @ATC_DONE_IRQ: Auto Trickle done + * @ATCFAIL_IRQ: Auto Trickle fail + * @AUTO_CHGDONE_IRQ: Auto chg done + * @AUTO_CHGFAIL_IRQ: time exceeded w/o reaching term current + * @CHGSTATE_IRQ: something happend causing a state change + * @FASTCHG_IRQ: trkl charging completed: moving to fastchg + * @CHG_END_IRQ: mA has dropped to termination current + * @BATTTEMP_IRQ: batt temp is out of range + * @CHGHOT_IRQ: the pass device is too hot + * @CHGTLIMIT_IRQ: unused + * @CHG_GONE_IRQ: charger was removed + * @VCPMAJOR_IRQ: vcp major + * @VBATDET_IRQ: VBAT >= VBATDET + * @BATFET_IRQ: BATFET closed + * @BATT_REPLACE_IRQ: + * @BATTCONNECT_IRQ: + */ +enum pmic_chg_interrupts { + CHGVAL_IRQ, + CHGINVAL_IRQ, + VBATDET_LOW_IRQ, + VCP_IRQ, + CHGILIM_IRQ, + ATC_DONE_IRQ, + ATCFAIL_IRQ, + AUTO_CHGDONE_IRQ, + AUTO_CHGFAIL_IRQ, + CHGSTATE_IRQ, + FASTCHG_IRQ, + CHG_END_IRQ, + BATTTEMP_IRQ, + CHGHOT_IRQ, + CHGTLIMIT_IRQ, + CHG_GONE_IRQ, + VCPMAJOR_IRQ, + VBATDET_IRQ, + BATFET_IRQ, + BATT_REPLACE_IRQ, + BATTCONNECT_IRQ, + PMIC_CHG_MAX_INTS +}; + +struct pm8058_charger { + struct pmic_charger_pdata *pdata; + struct pm8058_chip *pm_chip; + struct device *dev; + + int pmic_chg_irq[PMIC_CHG_MAX_INTS]; + DECLARE_BITMAP(enabled_irqs, PMIC_CHG_MAX_INTS); + + struct delayed_work chg_done_check_work; + struct delayed_work check_vbat_low_work; + struct delayed_work veoc_begin_work; + struct delayed_work charging_check_work; + int waiting_for_topoff; + int waiting_for_veoc; + int vbatdet; + struct msm_hardware_charger hw_chg; + int current_charger_current; + int disabled; + + struct msm_xo_voter *voter; + struct dentry *dent; + + int inited; + int present; +}; + +static struct pm8058_charger pm8058_chg; +static struct msm_hardware_charger usb_hw_chg; + + +static int msm_battery_gauge_alarm_notify(struct notifier_block *nb, + unsigned long status, void *unused); + +static struct notifier_block alarm_notifier = { + .notifier_call = msm_battery_gauge_alarm_notify, +}; + +static int resume_mv = AUTO_CHARGING_RESUME_MV; +static DEFINE_MUTEX(batt_alarm_lock); +static int resume_mv_set(const char *val, struct kernel_param *kp); +module_param_call(resume_mv, resume_mv_set, param_get_int, + &resume_mv, S_IRUGO | S_IWUSR); + +static int resume_mv_set(const char *val, struct kernel_param *kp) +{ + int rc; + + mutex_lock(&batt_alarm_lock); + + rc = param_set_int(val, kp); + if (rc) + goto out; + + rc = pm8058_batt_alarm_threshold_set(resume_mv, 4300); + +out: + mutex_unlock(&batt_alarm_lock); + return rc; +} + +static void pm8058_chg_enable_irq(int interrupt) +{ + if (!__test_and_set_bit(interrupt, pm8058_chg.enabled_irqs)) { + dev_dbg(pm8058_chg.dev, "%s %d\n", __func__, + pm8058_chg.pmic_chg_irq[interrupt]); + enable_irq(pm8058_chg.pmic_chg_irq[interrupt]); + } +} + +static void pm8058_chg_disable_irq(int interrupt) +{ + if (__test_and_clear_bit(interrupt, pm8058_chg.enabled_irqs)) { + dev_dbg(pm8058_chg.dev, "%s %d\n", __func__, + pm8058_chg.pmic_chg_irq[interrupt]); + disable_irq_nosync(pm8058_chg.pmic_chg_irq[interrupt]); + } +} + +static int pm_chg_get_rt_status(int irq) +{ + int count = 3; + int ret; + + while ((ret = + pm8058_irq_get_rt_status(pm8058_chg.pm_chip, irq)) == -EAGAIN + && count--) { + dev_info(pm8058_chg.dev, "%s trycount=%d\n", __func__, count); + cpu_relax(); + } + if (ret == -EAGAIN) + return 0; + else + return ret; +} + +static int is_chg_plugged_in(void) +{ + return pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]); +} + +#ifdef DEBUG +static void __dump_chg_regs(void) +{ + u8 temp; + int temp2; + + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_CNTRL = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_CNTRL_2 = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_VMAX_SEL, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_VMAX_SEL = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_VBAT_DET, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_VBAT_DET = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_IMAX, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_IMAX = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TRICKLE, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TRICKLE = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_ITERM, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_ITERM = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TTRKL_MAX, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TTRKL_MAX = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TCHG_MAX, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TCHG_MAX = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEMP_THRESH, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TEMP_THRESH = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEMP_REG, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_TEMP_REG = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_PULSE, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_PULSE = 0x%x\n", temp); + + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_STATUS_CLEAR_IRQ_1, + &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_1 = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_STATUS_CLEAR_IRQ_3, + &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_3 = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_STATUS_CLEAR_IRQ_10, + &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_10 = 0x%x\n", + temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_STATUS_CLEAR_IRQ_11, + &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_11 = 0x%x\n", + temp); + + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_MASK_IRQ_1, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_1 = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_MASK_IRQ_3, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_3 = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_MASK_IRQ_10, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_10 = 0x%x\n", temp); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_MASK_IRQ_11, &temp, 1); + dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_11 = 0x%x\n", temp); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]); + dev_dbg(pm8058_chg.dev, "CHGVAL_IRQ = %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGINVAL_IRQ]); + dev_dbg(pm8058_chg.dev, "CHGINVAL_IRQ = %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VBATDET_LOW_IRQ]); + dev_dbg(pm8058_chg.dev, "VBATDET_LOW_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VCP_IRQ]); + dev_dbg(pm8058_chg.dev, "VCP_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGILIM_IRQ]); + dev_dbg(pm8058_chg.dev, "CHGILIM_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[ATC_DONE_IRQ]); + dev_dbg(pm8058_chg.dev, "ATC_DONE_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[ATCFAIL_IRQ]); + dev_dbg(pm8058_chg.dev, "ATCFAIL_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[AUTO_CHGDONE_IRQ]); + dev_dbg(pm8058_chg.dev, "AUTO_CHGDONE_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[AUTO_CHGFAIL_IRQ]); + dev_dbg(pm8058_chg.dev, "AUTO_CHGFAIL_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGSTATE_IRQ]); + dev_dbg(pm8058_chg.dev, "CHGSTATE_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[FASTCHG_IRQ]); + dev_dbg(pm8058_chg.dev, "FASTCHG_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHG_END_IRQ]); + dev_dbg(pm8058_chg.dev, "CHG_END_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ]); + dev_dbg(pm8058_chg.dev, "BATTTEMP_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGHOT_IRQ]); + dev_dbg(pm8058_chg.dev, "CHGHOT_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGTLIMIT_IRQ]); + dev_dbg(pm8058_chg.dev, "CHGTLIMIT_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHG_GONE_IRQ]); + dev_dbg(pm8058_chg.dev, "CHG_GONE_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VCPMAJOR_IRQ]); + dev_dbg(pm8058_chg.dev, "VCPMAJOR_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VBATDET_IRQ]); + dev_dbg(pm8058_chg.dev, "VBATDET_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATFET_IRQ]); + dev_dbg(pm8058_chg.dev, "BATFET_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ]); + dev_dbg(pm8058_chg.dev, "BATT_REPLACE_IRQ= %d\n", temp2); + + temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ]); + dev_dbg(pm8058_chg.dev, "BATTCONNECT_IRQ= %d\n", temp2); +} +#else +static inline void __dump_chg_regs(void) +{ +} +#endif + +/* SSBI register access helper functions */ +static int pm_chg_suspend(int value) +{ + u8 temp; + int ret; + + ret = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1); + if (ret) + return ret; + if (value) + temp |= BIT(CHG_USB_SUSPEND); + else + temp &= ~BIT(CHG_USB_SUSPEND); + + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1); +} + +static int pm_chg_auto_disable(int value) +{ + u8 temp; + int ret; + + ret = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1); + if (ret) + return ret; + if (value) + temp |= BIT(CHARGE_AUTO_DIS); + else + temp &= ~BIT(CHARGE_AUTO_DIS); + + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1); +} + +static int pm_chg_batt_temp_disable(int value) +{ + u8 temp; + int ret; + + ret = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1); + if (ret) + return ret; + if (value) + temp |= BIT(CHG_BATT_TEMP_DIS); + else + temp &= ~BIT(CHG_BATT_TEMP_DIS); + + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1); +} + +static int pm_chg_vbatdet_set(int voltage) +{ + u8 temp; + int diff; + + diff = (voltage - PM8058_CHG_V_MIN_MV); + if (diff < 0) { + dev_warn(pm8058_chg.dev, "%s bad mV=%d asked to set\n", + __func__, voltage); + return -EINVAL; + } + + temp = diff / PM8058_CHG_V_STEP_MV; + dev_dbg(pm8058_chg.dev, "%s voltage=%d setting %02x\n", __func__, + voltage, temp); + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_VBAT_DET, &temp, 1); +} + +static int pm_chg_imaxsel_set(int chg_current) +{ + u8 temp; + int diff; + + diff = chg_current - PM8058_CHG_I_MIN_MA; + if (diff < 0) { + dev_warn(pm8058_chg.dev, "%s bad mA=%d asked to set\n", + __func__, chg_current); + return -EINVAL; + } + temp = diff / PM8058_CHG_I_STEP_MA; + /* make sure we arent writing more than 5 bits of data */ + if (temp > 31) { + dev_warn(pm8058_chg.dev, "%s max mA=1500 requested mA=%d\n", + __func__, chg_current); + temp = 31; + } + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_IMAX, &temp, 1); +} + +#define PM8058_CHG_VMAX_MIN 3300 +#define PM8058_CHG_VMAX_MAX 5500 +static int pm_chg_vmaxsel_set(int voltage) +{ + u8 temp; + + if (voltage < PM8058_CHG_VMAX_MIN || voltage > PM8058_CHG_VMAX_MAX) { + dev_warn(pm8058_chg.dev, "%s bad mV=%d asked to set\n", + __func__, voltage); + return -EINVAL; + } + temp = (voltage - PM8058_CHG_V_MIN_MV) / PM8058_CHG_V_STEP_MV; + dev_dbg(pm8058_chg.dev, "%s mV=%d setting %02x\n", __func__, voltage, + temp); + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_VMAX_SEL, &temp, 1); +} + +static int pm_chg_failed_clear(int value) +{ + u8 temp; + int ret; + + ret = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1); + if (ret) + return ret; + if (value) + temp |= BIT(CHG_FAILED_CLEAR); + else + temp &= ~BIT(CHG_FAILED_CLEAR); + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1); +} + +static int pm_chg_iterm_set(int chg_current) +{ + u8 temp; + + temp = (chg_current / PM8058_CHG_I_TERM_STEP_MA) - 1; + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_ITERM, &temp, 1); +} + +static int pm_chg_tchg_set(int minutes) +{ + u8 temp; + + temp = (minutes >> PM8058_CHG_T_TCHG_SHIFT) - 1; + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TCHG_MAX, &temp, 1); +} + +static int pm_chg_ttrkl_set(int minutes) +{ + u8 temp; + + temp = minutes - 1; + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TTRKL_MAX, &temp, 1); +} + +static int pm_chg_enum_done_enable(int value) +{ + u8 temp; + int ret; + + ret = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1); + if (ret) + return ret; + if (value) + temp |= BIT(ENUM_DONE); + else + temp &= ~BIT(ENUM_DONE); + + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1); +} + +static uint32_t get_fsm_state(void) +{ + u8 temp; + + temp = 0x00; + pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_3, &temp, 1); + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEST_3, &temp, 1); + return (uint32_t)temp; +} + +static int get_fsm_status(void *data, u64 * val) +{ + *val = get_fsm_state(); + return 0; +} + +static int __pm8058_start_charging(int chg_current, int termination_current, + int time) +{ + int ret = 0; + + if (pm8058_chg.disabled) + goto out; + + dev_info(pm8058_chg.dev, "%s %dmA %dmin\n", + __func__, chg_current, time); + + ret = pm_chg_auto_disable(1); + if (ret) + goto out; + + ret = pm_chg_suspend(0); + if (ret) + goto out; + + ret = pm_chg_imaxsel_set(chg_current); + if (ret) + goto out; + + ret = pm_chg_failed_clear(1); + if (ret) + goto out; + + ret = pm_chg_iterm_set(termination_current); + if (ret) + goto out; + + ret = pm_chg_tchg_set(time); + if (ret) + goto out; + + ret = pm_chg_ttrkl_set(AUTO_CHARGING_TRICKLE_TIME_MINUTES); + if (ret) + goto out; + + ret = pm_chg_batt_temp_disable(0); + if (ret) + goto out; + + if (pm8058_chg.voter == NULL) + pm8058_chg.voter = msm_xo_get(MSM_XO_TCXO_D1, "pm8058_charger"); + msm_xo_mode_vote(pm8058_chg.voter, MSM_XO_MODE_ON); + + ret = pm_chg_enum_done_enable(1); + if (ret) + goto out; + + wmb(); + + ret = pm_chg_auto_disable(0); + if (ret) + goto out; + + /* wait for the enable to update interrupt status*/ + msleep(20); + + pm8058_chg_enable_irq(AUTO_CHGFAIL_IRQ); + pm8058_chg_enable_irq(CHGHOT_IRQ); + pm8058_chg_enable_irq(AUTO_CHGDONE_IRQ); + pm8058_chg_enable_irq(CHG_END_IRQ); + pm8058_chg_enable_irq(CHGSTATE_IRQ); + +out: + return ret; +} + +static void chg_done_cleanup(void) +{ + dev_info(pm8058_chg.dev, "%s notify pm8058 charging completion" + "\n", __func__); + + pm8058_chg_disable_irq(AUTO_CHGDONE_IRQ); + cancel_delayed_work_sync(&pm8058_chg.veoc_begin_work); + cancel_delayed_work_sync(&pm8058_chg.check_vbat_low_work); + + pm8058_chg_disable_irq(CHG_END_IRQ); + + pm8058_chg_disable_irq(VBATDET_LOW_IRQ); + pm8058_chg_disable_irq(VBATDET_IRQ); + pm8058_chg.waiting_for_veoc = 0; + pm8058_chg.waiting_for_topoff = 0; + + pm_chg_auto_disable(1); + + msm_charger_notify_event(&usb_hw_chg, CHG_DONE_EVENT); +} + +static void chg_done_check_work(struct work_struct *work) +{ + chg_done_cleanup(); +} + +static void charging_check_work(struct work_struct *work) +{ + uint32_t fsm_state = get_fsm_state(); + int rc; + + switch (fsm_state) { + /* We're charging, so disarm alarm */ + case 2: + case 7: + case 8: + rc = pm8058_batt_alarm_state_set(0, 0); + if (rc) + dev_err(pm8058_chg.dev, + "%s: unable to set alarm state\n", __func__); + break; + default: + /* Still not charging, so update driver state */ + chg_done_cleanup(); + break; + }; +} + +static int pm8058_start_charging(struct msm_hardware_charger *hw_chg, + int chg_voltage, int chg_current) +{ + int vbat_higher_than_vbatdet; + int ret = 0; + + cancel_delayed_work_sync(&pm8058_chg.charging_check_work); + + /* + * adjust the max current for PC USB connection - set the higher limit + * to 450 and make sure we never cross it + */ + if (chg_current == 500) + chg_current = 450; + pm8058_chg.current_charger_current = chg_current; + pm8058_chg_enable_irq(FASTCHG_IRQ); + + ret = pm_chg_vmaxsel_set(chg_voltage); + if (ret) + goto out; + + /* set vbat to CC to CV threshold */ + ret = pm_chg_vbatdet_set(AUTO_CHARGING_VBATDET); + if (ret) + goto out; + + pm8058_chg.vbatdet = AUTO_CHARGING_VBATDET; + /* + * get the state of vbat and if it is higher than + * AUTO_CHARGING_VBATDET we start the veoc start timer + * else wait for the voltage to go to AUTO_CHARGING_VBATDET + * and then start the 90 min timer + */ + vbat_higher_than_vbatdet = + pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VBATDET_IRQ]); + if (vbat_higher_than_vbatdet) { + /* + * we are in constant voltage phase of charging + * IEOC should happen withing 90 mins of this instant + * else we enable VEOC + */ + dev_info(pm8058_chg.dev, "%s begin veoc timer\n", __func__); + schedule_delayed_work(&pm8058_chg.veoc_begin_work, + round_jiffies_relative(msecs_to_jiffies + (AUTO_CHARGING_VEOC_BEGIN_TIME_MS))); + } else + pm8058_chg_enable_irq(VBATDET_IRQ); + + ret = __pm8058_start_charging(chg_current, AUTO_CHARGING_IEOC_ITERM, + AUTO_CHARGING_FAST_TIME_MAX_MINUTES); + pm8058_chg.current_charger_current = chg_current; + + /* + * We want to check the FSM state to verify we're charging. We must + * wait before doing this to allow the VBATDET to settle. The worst + * case for this is two seconds. The batt alarm does not have this + * delay. + */ + schedule_delayed_work(&pm8058_chg.charging_check_work, + round_jiffies_relative(msecs_to_jiffies + (AUTO_CHARGING_VBATDET_DEBOUNCE_TIME_MS))); + +out: + return ret; +} + +static void veoc_begin_work(struct work_struct *work) +{ + /* we have been doing CV for 90mins with no signs of IEOC + * start checking for VEOC in addition with 16min charges*/ + dev_info(pm8058_chg.dev, "%s begin veoc detection\n", __func__); + pm8058_chg.waiting_for_veoc = 1; + /* + * disable VBATDET irq we dont need it unless we are at the end of + * charge cycle + */ + pm8058_chg_disable_irq(VBATDET_IRQ); + __pm8058_start_charging(pm8058_chg.current_charger_current, + AUTO_CHARGING_VEOC_ITERM, + AUTO_CHARGING_VEOC_TCHG); +} + +static void vbat_low_work(struct work_struct *work) +{ + /* + * It has been one minute and the battery still holds voltage + * start the final topoff - charging is almost done + */ + dev_info(pm8058_chg.dev, "%s vbatt maintains for a minute" + "starting topoff\n", __func__); + pm8058_chg.waiting_for_veoc = 0; + pm8058_chg.waiting_for_topoff = 1; + pm8058_chg_disable_irq(VBATDET_LOW_IRQ); + pm8058_chg_disable_irq(VBATDET_IRQ); + __pm8058_start_charging(pm8058_chg.current_charger_current, + AUTO_CHARGING_VEOC_ITERM, + AUTO_CHARGING_VEOC_TCHG_FINAL_CYCLE); +} + + +static irqreturn_t pm8058_chg_chgval_handler(int irq, void *dev_id) +{ + u8 old, temp; + int ret; + + if (is_chg_plugged_in()) { /* this debounces it */ + if (!pm8058_chg.present) { + msm_charger_notify_event(&usb_hw_chg, + CHG_INSERTED_EVENT); + pm8058_chg.present = 1; + } + } else { + if (pm8058_chg.present) { + ret = pm8058_read(pm8058_chg.pm_chip, + PM8058_OVP_TEST_REG, + &old, 1); + temp = old | BIT(FORCE_OVP_OFF); + ret = pm8058_write(pm8058_chg.pm_chip, + PM8058_OVP_TEST_REG, + &temp, 1); + temp = 0xFC; + ret = pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, + &temp, 1); + /* 10 ms sleep is for the VCHG to discharge */ + msleep(10); + temp = 0xF0; + ret = pm8058_write(pm8058_chg.pm_chip, + PM8058_CHG_TEST, + &temp, 1); + ret = pm8058_write(pm8058_chg.pm_chip, + PM8058_OVP_TEST_REG, + &old, 1); + + pm_chg_enum_done_enable(0); + pm_chg_auto_disable(1); + msm_charger_notify_event(&usb_hw_chg, + CHG_REMOVED_EVENT); + pm8058_chg.present = 0; + } + } + + return IRQ_HANDLED; +} + +static irqreturn_t pm8058_chg_chginval_handler(int irq, void *dev_id) +{ + u8 old, temp; + int ret; + + if (pm8058_chg.present) { + pm8058_chg_disable_irq(CHGINVAL_IRQ); + + pm_chg_enum_done_enable(0); + pm_chg_auto_disable(1); + ret = pm8058_read(pm8058_chg.pm_chip, + PM8058_OVP_TEST_REG, &old, 1); + temp = old | BIT(FORCE_OVP_OFF); + ret = pm8058_write(pm8058_chg.pm_chip, + PM8058_OVP_TEST_REG, &temp, 1); + temp = 0xFC; + ret = pm8058_write(pm8058_chg.pm_chip, + PM8058_CHG_TEST, &temp, 1); + /* 10 ms sleep is for the VCHG to discharge */ + msleep(10); + temp = 0xF0; + ret = pm8058_write(pm8058_chg.pm_chip, + PM8058_CHG_TEST, &temp, 1); + ret = pm8058_write(pm8058_chg.pm_chip, + PM8058_OVP_TEST_REG, &old, 1); + + if (!is_chg_plugged_in()) { + msm_charger_notify_event(&usb_hw_chg, + CHG_REMOVED_EVENT); + pm8058_chg.present = 0; + } else { + /* was a fake */ + pm8058_chg_enable_irq(CHGINVAL_IRQ); + } + } + + return IRQ_HANDLED; +} + +static irqreturn_t pm8058_chg_auto_chgdone_handler(int irq, void *dev_id) +{ + dev_info(pm8058_chg.dev, "%s waiting a sec to confirm\n", + __func__); + pm8058_chg_disable_irq(VBATDET_IRQ); + if (!delayed_work_pending(&pm8058_chg.chg_done_check_work)) { + schedule_delayed_work(&pm8058_chg.chg_done_check_work, + round_jiffies_relative(msecs_to_jiffies + (AUTO_CHARGING_DONE_CHECK_TIME_MS))); + } + return IRQ_HANDLED; +} + +/* can only happen with the pmic charger when it has been charing + * for either 16 mins wating for VEOC or 32 mins for topoff + * without a IEOC indication */ +static irqreturn_t pm8058_chg_auto_chgfail_handler(int irq, void *dev_id) +{ + pm8058_chg_disable_irq(AUTO_CHGFAIL_IRQ); + + if (pm8058_chg.waiting_for_topoff == 1) { + dev_info(pm8058_chg.dev, "%s topoff done, charging done\n", + __func__); + pm8058_chg.waiting_for_topoff = 0; + /* notify we are done charging */ + msm_charger_notify_event(&usb_hw_chg, CHG_DONE_EVENT); + } else { + /* start one minute timer and monitor VBATDET_LOW */ + dev_info(pm8058_chg.dev, "%s monitoring vbat_low for a" + "minute\n", __func__); + schedule_delayed_work(&pm8058_chg.check_vbat_low_work, + round_jiffies_relative(msecs_to_jiffies + (AUTO_CHARGING_VEOC_VBAT_LOW_CHECK_TIME_MS))); + + /* note we are waiting on veoc */ + pm8058_chg.waiting_for_veoc = 1; + + pm_chg_vbatdet_set(AUTO_CHARGING_VEOC_VBATDET); + pm8058_chg.vbatdet = AUTO_CHARGING_VEOC_VBATDET; + pm8058_chg_enable_irq(VBATDET_LOW_IRQ); + } + return IRQ_HANDLED; +} + +static irqreturn_t pm8058_chg_chgstate_handler(int irq, void *dev_id) +{ + u8 temp; + + temp = 0x00; + if (!pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_3, &temp, 1)) { + pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEST_3, &temp, 1); + dev_dbg(pm8058_chg.dev, "%s state=%d\n", __func__, temp); + } + return IRQ_HANDLED; +} + +static irqreturn_t pm8058_chg_fastchg_handler(int irq, void *dev_id) +{ + pm8058_chg_disable_irq(FASTCHG_IRQ); + + /* we have begun the fast charging state */ + dev_info(pm8058_chg.dev, "%s begin fast charging" + , __func__); + msm_charger_notify_event(&usb_hw_chg, CHG_BATT_BEGIN_FAST_CHARGING); + return IRQ_HANDLED; +} + +static irqreturn_t pm8058_chg_batttemp_handler(int irq, void *dev_id) +{ + int ret; + + /* we could get temperature + * interrupt when the battery is plugged out + */ + ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ]); + if (ret) { + msm_charger_notify_event(&usb_hw_chg, CHG_BATT_REMOVED); + } else { + /* read status to determine we are inrange or outofrange */ + ret = + pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ]); + if (ret) + msm_charger_notify_event(&usb_hw_chg, + CHG_BATT_TEMP_OUTOFRANGE); + else + msm_charger_notify_event(&usb_hw_chg, + CHG_BATT_TEMP_INRANGE); + } + + return IRQ_HANDLED; +} + +static irqreturn_t pm8058_chg_vbatdet_handler(int irq, void *dev_id) +{ + int ret; + + /* settling time */ + msleep(20); + ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VBATDET_IRQ]); + + if (ret) { + if (pm8058_chg.vbatdet == AUTO_CHARGING_VBATDET + && !delayed_work_pending(&pm8058_chg.veoc_begin_work)) { + /* + * we are in constant voltage phase of charging + * IEOC should happen withing 90 mins of this instant + * else we enable VEOC + */ + dev_info(pm8058_chg.dev, "%s entered constant voltage" + "begin veoc timer\n", __func__); + schedule_delayed_work(&pm8058_chg.veoc_begin_work, + round_jiffies_relative + (msecs_to_jiffies + (AUTO_CHARGING_VEOC_BEGIN_TIME_MS))); + } + } else { + if (pm8058_chg.vbatdet == AUTO_CHARGING_VEOC_VBATDET) { + cancel_delayed_work_sync( + &pm8058_chg.check_vbat_low_work); + + if (pm8058_chg.waiting_for_topoff + || pm8058_chg.waiting_for_veoc) { + /* + * the battery dropped its voltage below 4100 + * around a minute charge the battery for 16 + * mins and check vbat again for a minute + */ + dev_info(pm8058_chg.dev, "%s batt dropped vlt" + "within a minute\n", __func__); + pm8058_chg.waiting_for_topoff = 0; + pm8058_chg.waiting_for_veoc = 1; + pm8058_chg_disable_irq(VBATDET_IRQ); + __pm8058_start_charging(pm8058_chg. + current_charger_current, + AUTO_CHARGING_VEOC_ITERM, + AUTO_CHARGING_VEOC_TCHG); + } + } + } + return IRQ_HANDLED; +} + +static irqreturn_t pm8058_chg_batt_replace_handler(int irq, void *dev_id) +{ + int ret; + + pm8058_chg_disable_irq(BATT_REPLACE_IRQ); + ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ]); + if (ret) { + msm_charger_notify_event(&usb_hw_chg, CHG_BATT_INSERTED); + /* + * battery is present enable batt removal + * and batt temperatture interrupt + */ + pm8058_chg_enable_irq(BATTCONNECT_IRQ); + } + return IRQ_HANDLED; +} + +static irqreturn_t pm8058_chg_battconnect_handler(int irq, void *dev_id) +{ + int ret; + + ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ]); + if (ret) { + msm_charger_notify_event(&usb_hw_chg, CHG_BATT_REMOVED); + } else { + msm_charger_notify_event(&usb_hw_chg, CHG_BATT_INSERTED); + pm8058_chg_enable_irq(BATTTEMP_IRQ); + } + + return IRQ_HANDLED; +} + +static int get_rt_status(void *data, u64 * val) +{ + int i = (int)data; + int ret; + + ret = pm_chg_get_rt_status(i); + *val = ret; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(rt_fops, get_rt_status, NULL, "%llu\n"); +DEFINE_SIMPLE_ATTRIBUTE(fsm_fops, get_fsm_status, NULL, "%llu\n"); + +static void free_irqs(void) +{ + int i; + + for (i = 0; i < PMIC_CHG_MAX_INTS; i++) + if (pm8058_chg.pmic_chg_irq[i]) { + free_irq(pm8058_chg.pmic_chg_irq[i], NULL); + pm8058_chg.pmic_chg_irq[i] = 0; + } +} + +static int __devinit request_irqs(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + ret = 0; + bitmap_fill(pm8058_chg.enabled_irqs, PMIC_CHG_MAX_INTS); + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "CHGVAL"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource CHGVAL\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_chgval_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[CHGVAL_IRQ] = res->start; + pm8058_chg_disable_irq(CHGVAL_IRQ); + enable_irq_wake(pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "CHGINVAL"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource CHGINVAL\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_chginval_handler, + IRQF_TRIGGER_RISING, res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[CHGINVAL_IRQ] = res->start; + pm8058_chg_disable_irq(CHGINVAL_IRQ); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "AUTO_CHGDONE"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource AUTO_CHGDONE\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_auto_chgdone_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[AUTO_CHGDONE_IRQ] = res->start; + pm8058_chg_disable_irq(AUTO_CHGDONE_IRQ); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "AUTO_CHGFAIL"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource AUTO_CHGFAIL\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_auto_chgfail_handler, + IRQF_TRIGGER_RISING, res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[AUTO_CHGFAIL_IRQ] = res->start; + pm8058_chg_disable_irq(AUTO_CHGFAIL_IRQ); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "CHGSTATE"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource CHGSTATE\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_chgstate_handler, + IRQF_TRIGGER_RISING, res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[CHGSTATE_IRQ] = res->start; + pm8058_chg_disable_irq(CHGSTATE_IRQ); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "FASTCHG"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource FASTCHG\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_fastchg_handler, + IRQF_TRIGGER_RISING, res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[FASTCHG_IRQ] = res->start; + pm8058_chg_disable_irq(FASTCHG_IRQ); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "BATTTEMP"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource CHG_END\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_batttemp_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ] = res->start; + pm8058_chg_disable_irq(BATTTEMP_IRQ); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "BATT_REPLACE"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource BATT_REPLACE\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_batt_replace_handler, + IRQF_TRIGGER_RISING, res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ] = res->start; + pm8058_chg_disable_irq(BATT_REPLACE_IRQ); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "BATTCONNECT"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource BATTCONNECT\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_battconnect_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ] = res->start; + pm8058_chg_disable_irq(BATTCONNECT_IRQ); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "VBATDET"); + if (res == NULL) { + dev_err(pm8058_chg.dev, + "%s:couldnt find resource VBATDET\n", __func__); + goto err_out; + } else { + ret = request_threaded_irq(res->start, NULL, + pm8058_chg_vbatdet_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + res->name, NULL); + if (ret < 0) { + dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n", + __func__, res->start, ret); + goto err_out; + } else { + pm8058_chg.pmic_chg_irq[VBATDET_IRQ] = res->start; + pm8058_chg_disable_irq(VBATDET_IRQ); + } + } + + return 0; + +err_out: + free_irqs(); + return -EINVAL; +} + +static int pm8058_get_charge_batt(void) +{ + u8 temp; + int rc; + + rc = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1); + if (rc) + return rc; + + temp &= BIT(CHG_CHARGE_BAT); + if (temp) + temp = 1; + return temp; +} +EXPORT_SYMBOL(pm8058_get_charge_batt); + +static int pm8058_set_charge_batt(int on) +{ + u8 temp; + int rc; + + rc = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1); + if (rc) + return rc; + if (on) + temp |= BIT(CHG_CHARGE_BAT); + else + temp &= ~BIT(CHG_CHARGE_BAT); + + return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1); + +} +EXPORT_SYMBOL(pm8058_set_charge_batt); + +static int get_charge_batt(void *data, u64 * val) +{ + int ret; + + ret = pm8058_get_charge_batt(); + if (ret < 0) + return ret; + + *val = ret; + return 0; +} + +static int set_charge_batt(void *data, u64 val) +{ + return pm8058_set_charge_batt(val); +} +DEFINE_SIMPLE_ATTRIBUTE(fet_fops, get_charge_batt, set_charge_batt, "%llu\n"); + +static void pm8058_chg_determine_initial_state(void) +{ + if (is_chg_plugged_in()) { + pm8058_chg.present = 1; + msm_charger_notify_event(&usb_hw_chg, CHG_INSERTED_EVENT); + dev_info(pm8058_chg.dev, "%s charger present\n", __func__); + } else { + pm8058_chg.present = 0; + dev_info(pm8058_chg.dev, "%s charger absent\n", __func__); + } + pm8058_chg_enable_irq(CHGVAL_IRQ); +} + +static int pm8058_stop_charging(struct msm_hardware_charger *hw_chg) +{ + int ret; + + dev_info(pm8058_chg.dev, "%s stopping charging\n", __func__); + cancel_delayed_work_sync(&pm8058_chg.veoc_begin_work); + cancel_delayed_work_sync(&pm8058_chg.check_vbat_low_work); + cancel_delayed_work_sync(&pm8058_chg.chg_done_check_work); + cancel_delayed_work_sync(&pm8058_chg.charging_check_work); + + ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[FASTCHG_IRQ]); + if (ret == 1) + pm_chg_suspend(1); + else + dev_err(pm8058_chg.dev, + "%s called when not fast-charging\n", __func__); + + pm_chg_failed_clear(1); + + pm8058_chg.waiting_for_veoc = 0; + pm8058_chg.waiting_for_topoff = 0; + + /* disable the irqs enabled while charging */ + pm8058_chg_disable_irq(AUTO_CHGFAIL_IRQ); + pm8058_chg_disable_irq(CHGHOT_IRQ); + pm8058_chg_disable_irq(AUTO_CHGDONE_IRQ); + pm8058_chg_disable_irq(FASTCHG_IRQ); + pm8058_chg_disable_irq(CHG_END_IRQ); + pm8058_chg_disable_irq(VBATDET_IRQ); + pm8058_chg_disable_irq(VBATDET_LOW_IRQ); + if (pm8058_chg.voter) + msm_xo_mode_vote(pm8058_chg.voter, MSM_XO_MODE_OFF); + + return 0; +} + +static int get_status(void *data, u64 * val) +{ + *val = pm8058_chg.current_charger_current; + return 0; +} + +static int set_status(void *data, u64 val) +{ + + pm8058_chg.current_charger_current = val; + if (pm8058_chg.current_charger_current) + pm8058_start_charging(NULL, + AUTO_CHARGING_VMAXSEL, + pm8058_chg.current_charger_current); + else + pm8058_stop_charging(NULL); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(chg_fops, get_status, set_status, "%llu\n"); + +static int set_disable_status_param(const char *val, struct kernel_param *kp) +{ + int ret; + + ret = param_set_int(val, kp); + if (ret) + return ret; + + if (pm8058_chg.inited && pm8058_chg.disabled) { + /* + * stop_charging is called during usb suspend + * act as the usb is removed by disabling auto and enum + */ + pm_chg_enum_done_enable(0); + pm_chg_auto_disable(1); + pm8058_stop_charging(NULL); + } + return 0; +} +module_param_call(disabled, set_disable_status_param, param_get_uint, + &(pm8058_chg.disabled), 0644); + +static int pm8058_charging_switched(struct msm_hardware_charger *hw_chg) +{ + u8 temp; + + temp = 0xA3; + pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_2, &temp, 1); + temp = 0x84; + pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_2, &temp, 1); + msleep(2); + temp = 0x80; + pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_2, &temp, 1); + return 0; +} + +static int get_reg(void *data, u64 * val) +{ + int i = (int)data; + int ret; + u8 temp; + + ret = pm8058_read(pm8058_chg.pm_chip, i, &temp, 1); + if (ret) + return -EAGAIN; + *val = temp; + return 0; +} + +static int set_reg(void *data, u64 val) +{ + int i = (int)data; + int ret; + u8 temp; + + temp = (u8) val; + ret = pm8058_write(pm8058_chg.pm_chip, i, &temp, 1); + mb(); + if (ret) + return -EAGAIN; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "%llu\n"); + +#ifdef CONFIG_DEBUG_FS +static void create_debugfs_entries(void) +{ + pm8058_chg.dent = debugfs_create_dir("pm8058_usb_chg", NULL); + + if (IS_ERR(pm8058_chg.dent)) { + pr_err("pmic charger couldnt create debugfs dir\n"); + return; + } + + debugfs_create_file("CHG_CNTRL", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_CNTRL, ®_fops); + debugfs_create_file("CHG_CNTRL_2", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_CNTRL_2, ®_fops); + debugfs_create_file("CHG_VMAX_SEL", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_VMAX_SEL, ®_fops); + debugfs_create_file("CHG_VBAT_DET", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_VBAT_DET, ®_fops); + debugfs_create_file("CHG_IMAX", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_IMAX, ®_fops); + debugfs_create_file("CHG_TRICKLE", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_TRICKLE, ®_fops); + debugfs_create_file("CHG_ITERM", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_ITERM, ®_fops); + debugfs_create_file("CHG_TTRKL_MAX", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_TTRKL_MAX, ®_fops); + debugfs_create_file("CHG_TCHG_MAX", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_TCHG_MAX, ®_fops); + debugfs_create_file("CHG_TEMP_THRESH", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_TEMP_THRESH, ®_fops); + debugfs_create_file("CHG_TEMP_REG", 0644, pm8058_chg.dent, + (void *)PM8058_CHG_TEMP_REG, ®_fops); + + debugfs_create_file("FSM_STATE", 0644, pm8058_chg.dent, NULL, + &fsm_fops); + + debugfs_create_file("stop", 0644, pm8058_chg.dent, NULL, + &chg_fops); + + if (pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]) + debugfs_create_file("CHGVAL", 0444, pm8058_chg.dent, + (void *)pm8058_chg.pmic_chg_irq[CHGVAL_IRQ], + &rt_fops); + + if (pm8058_chg.pmic_chg_irq[CHGINVAL_IRQ]) + debugfs_create_file("CHGINVAL", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[CHGINVAL_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[CHGILIM_IRQ]) + debugfs_create_file("CHGILIM", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[CHGILIM_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[VCP_IRQ]) + debugfs_create_file("VCP", 0444, pm8058_chg.dent, + (void *)pm8058_chg.pmic_chg_irq[VCP_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[ATC_DONE_IRQ]) + debugfs_create_file("ATC_DONE", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[ATC_DONE_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[ATCFAIL_IRQ]) + debugfs_create_file("ATCFAIL", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[ATCFAIL_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[AUTO_CHGDONE_IRQ]) + debugfs_create_file("AUTO_CHGDONE", 0444, pm8058_chg.dent, + (void *) + pm8058_chg.pmic_chg_irq[AUTO_CHGDONE_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[AUTO_CHGFAIL_IRQ]) + debugfs_create_file("AUTO_CHGFAIL", 0444, pm8058_chg.dent, + (void *) + pm8058_chg.pmic_chg_irq[AUTO_CHGFAIL_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[CHGSTATE_IRQ]) + debugfs_create_file("CHGSTATE", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[CHGSTATE_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[FASTCHG_IRQ]) + debugfs_create_file("FASTCHG", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[FASTCHG_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[CHG_END_IRQ]) + debugfs_create_file("CHG_END", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[CHG_END_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ]) + debugfs_create_file("BATTTEMP", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[CHGHOT_IRQ]) + debugfs_create_file("CHGHOT", 0444, pm8058_chg.dent, + (void *)pm8058_chg.pmic_chg_irq[CHGHOT_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[CHGTLIMIT_IRQ]) + debugfs_create_file("CHGTLIMIT", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[CHGTLIMIT_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[CHG_GONE_IRQ]) + debugfs_create_file("CHG_GONE", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[CHG_GONE_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[VCPMAJOR_IRQ]) + debugfs_create_file("VCPMAJOR", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[VCPMAJOR_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[BATFET_IRQ]) + debugfs_create_file("BATFET", 0444, pm8058_chg.dent, + (void *)pm8058_chg.pmic_chg_irq[BATFET_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ]) + debugfs_create_file("BATT_REPLACE", 0444, pm8058_chg.dent, + (void *) + pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ]) + debugfs_create_file("BATTCONNECT", 0444, pm8058_chg.dent, + (void *) + pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[VBATDET_IRQ]) + debugfs_create_file("VBATDET", 0444, pm8058_chg.dent, (void *) + pm8058_chg.pmic_chg_irq[VBATDET_IRQ], + &rt_fops); + if (pm8058_chg.pmic_chg_irq[VBATDET_LOW_IRQ]) + debugfs_create_file("VBATDET_LOW", 0444, pm8058_chg.dent, + (void *) + pm8058_chg.pmic_chg_irq[VBATDET_LOW_IRQ], + &rt_fops); + debugfs_create_file("CHARGE_BATT", 0444, pm8058_chg.dent, + NULL, + &fet_fops); +} +#else +static inline void create_debugfs_entries(void) +{ +} +#endif + +static void remove_debugfs_entries(void) +{ + debugfs_remove_recursive(pm8058_chg.dent); +} + +static struct msm_hardware_charger usb_hw_chg = { + .type = CHG_TYPE_USB, + .rating = 1, + .name = "pm8058-usb", + .start_charging = pm8058_start_charging, + .stop_charging = pm8058_stop_charging, + .charging_switched = pm8058_charging_switched, +}; + +static int batt_read_adc(int channel, int *mv_reading) +{ + int ret; + void *h; + struct adc_chan_result adc_chan_result; + struct completion conv_complete_evt; + + pr_debug("%s: called for %d\n", __func__, channel); + ret = adc_channel_open(channel, &h); + if (ret) { + pr_err("%s: couldnt open channel %d ret=%d\n", + __func__, channel, ret); + goto out; + } + init_completion(&conv_complete_evt); + ret = adc_channel_request_conv(h, &conv_complete_evt); + if (ret) { + pr_err("%s: couldnt request conv channel %d ret=%d\n", + __func__, channel, ret); + goto out; + } + wait_for_completion(&conv_complete_evt); + ret = adc_channel_read_result(h, &adc_chan_result); + if (ret) { + pr_err("%s: couldnt read result channel %d ret=%d\n", + __func__, channel, ret); + goto out; + } + ret = adc_channel_close(h); + if (ret) { + pr_err("%s: couldnt close channel %d ret=%d\n", + __func__, channel, ret); + } + if (mv_reading) + *mv_reading = adc_chan_result.measurement; + + pr_debug("%s: done for %d\n", __func__, channel); + return adc_chan_result.physical; +out: + pr_debug("%s: done for %d\n", __func__, channel); + return -EINVAL; + +} + +#define BATT_THERM_OPEN_MV 2000 +static int pm8058_is_battery_present(void) +{ + int mv_reading; + + mv_reading = 0; + batt_read_adc(CHANNEL_ADC_BATT_THERM, &mv_reading); + pr_debug("%s: therm_raw is %d\n", __func__, mv_reading); + if (mv_reading > 0 && mv_reading < BATT_THERM_OPEN_MV) + return 1; + + return 0; +} + +static int pm8058_get_battery_temperature(void) +{ + return batt_read_adc(CHANNEL_ADC_BATT_THERM, NULL); +} + +#define BATT_THERM_OPERATIONAL_MAX_CELCIUS 40 +#define BATT_THERM_OPERATIONAL_MIN_CELCIUS 0 +static int pm8058_is_battery_temp_within_range(void) +{ + int therm_celcius; + + therm_celcius = pm8058_get_battery_temperature(); + pr_debug("%s: therm_celcius is %d\n", __func__, therm_celcius); + if (therm_celcius > 0 + && therm_celcius > BATT_THERM_OPERATIONAL_MIN_CELCIUS + && therm_celcius < BATT_THERM_OPERATIONAL_MAX_CELCIUS) + return 1; + + return 0; +} + +#define BATT_ID_MAX_MV 800 +#define BATT_ID_MIN_MV 600 +static int pm8058_is_battery_id_valid(void) +{ + int batt_id_mv; + + batt_id_mv = batt_read_adc(CHANNEL_ADC_BATT_ID, NULL); + pr_debug("%s: batt_id_mv is %d\n", __func__, batt_id_mv); + + /* + * The readings are not in range + * assume battery is present for now + */ + return 1; + + if (batt_id_mv > 0 + && batt_id_mv > BATT_ID_MIN_MV + && batt_id_mv < BATT_ID_MAX_MV) + return 1; + + return 0; +} + +/* returns voltage in mV */ +static int pm8058_get_battery_mvolts(void) +{ + int vbatt_mv; + + vbatt_mv = batt_read_adc(CHANNEL_ADC_VBATT, NULL); + pr_debug("%s: vbatt_mv is %d\n", __func__, vbatt_mv); + if (vbatt_mv > 0) + return vbatt_mv; + /* + * return 0 to tell the upper layers + * we couldnt read the battery voltage + */ + return 0; +} + +static int msm_battery_gauge_alarm_notify(struct notifier_block *nb, + unsigned long status, void *unused) +{ + int rc; + + pr_info("%s: status: %lu\n", __func__, status); + + switch (status) { + case 0: + dev_err(pm8058_chg.dev, + "%s: spurious interrupt\n", __func__); + break; + /* expected case - trip of low threshold */ + case 1: + rc = pm8058_batt_alarm_state_set(0, 0); + if (rc) + dev_err(pm8058_chg.dev, + "%s: unable to set alarm state\n", __func__); + msm_charger_notify_event(NULL, CHG_BATT_NEEDS_RECHARGING); + break; + case 2: + dev_err(pm8058_chg.dev, + "%s: trip of high threshold\n", __func__); + break; + default: + dev_err(pm8058_chg.dev, + "%s: error received\n", __func__); + }; + + return 0; +} + +static int pm8058_monitor_for_recharging(void) +{ + /* enable low comparator */ + return pm8058_batt_alarm_state_set(1, 0); +} + +static struct msm_battery_gauge pm8058_batt_gauge = { + .get_battery_mvolts = pm8058_get_battery_mvolts, + .get_battery_temperature = pm8058_get_battery_temperature, + .is_battery_present = pm8058_is_battery_present, + .is_battery_temp_within_range = pm8058_is_battery_temp_within_range, + .is_battery_id_valid = pm8058_is_battery_id_valid, + .monitor_for_recharging = pm8058_monitor_for_recharging, +}; + +static int pm8058_usb_voltage_lower_limit(void) +{ + u8 temp, old; + int ret = 0; + + temp = 0x10; + ret |= pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1); + ret |= pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEST, &old, 1); + old = old & ~BIT(IGNORE_LL); + temp = 0x90 | (0xF & old); + ret |= pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1); + + return ret; +} + +static int __devinit pm8058_charger_probe(struct platform_device *pdev) +{ + struct pm8058_chip *pm_chip; + int rc = 0; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + pr_err("%s:no parent data passed in.\n", __func__); + return -EFAULT; + } + + pm8058_chg.pm_chip = pm_chip; + pm8058_chg.pdata = pdev->dev.platform_data; + pm8058_chg.dev = &pdev->dev; + + rc = request_irqs(pdev); + if (rc) { + pr_err("%s: couldnt register interrupts\n", __func__); + goto out; + } + + rc = pm8058_usb_voltage_lower_limit(); + if (rc) { + pr_err("%s: couldnt set ignore lower limit bit to 0\n", + __func__); + goto free_irq; + } + + rc = msm_charger_register(&usb_hw_chg); + if (rc) { + pr_err("%s: msm_charger_register failed ret=%d\n", + __func__, rc); + goto free_irq; + } + + pm_chg_batt_temp_disable(0); + msm_battery_gauge_register(&pm8058_batt_gauge); + __dump_chg_regs(); + + create_debugfs_entries(); + INIT_DELAYED_WORK(&pm8058_chg.veoc_begin_work, veoc_begin_work); + INIT_DELAYED_WORK(&pm8058_chg.check_vbat_low_work, vbat_low_work); + INIT_DELAYED_WORK(&pm8058_chg.chg_done_check_work, chg_done_check_work); + INIT_DELAYED_WORK(&pm8058_chg.charging_check_work, charging_check_work); + + /* determine what state the charger is in */ + pm8058_chg_determine_initial_state(); + + pm8058_chg_enable_irq(BATTTEMP_IRQ); + pm8058_chg_enable_irq(BATTCONNECT_IRQ); + + rc = pm8058_batt_alarm_state_set(0, 0); + if (rc) { + pr_err("%s: unable to set batt alarm state\n", __func__); + goto free_irq; + } + + /* + * The batt-alarm driver requires sane values for both min / max, + * regardless of whether they're both activated. + */ + rc = pm8058_batt_alarm_threshold_set(resume_mv, 4300); + if (rc) { + pr_err("%s: unable to set batt alarm threshold\n", __func__); + goto free_irq; + } + + rc = pm8058_batt_alarm_hold_time_set(PM8058_BATT_ALARM_HOLD_TIME_16_MS); + if (rc) { + pr_err("%s: unable to set batt alarm hold time\n", __func__); + goto free_irq; + } + + /* PWM enabled at 2Hz */ + rc = pm8058_batt_alarm_pwm_rate_set(1, 7, 4); + if (rc) { + pr_err("%s: unable to set batt alarm pwm rate\n", __func__); + goto free_irq; + } + + rc = pm8058_batt_alarm_register_notifier(&alarm_notifier); + if (rc) { + pr_err("%s: unable to register alarm notifier\n", __func__); + goto free_irq; + } + + pm8058_chg.inited = 1; + + return 0; + +free_irq: + free_irqs(); +out: + return rc; +} + +static int __devexit pm8058_charger_remove(struct platform_device *pdev) +{ + struct pm8058_charger_chip *chip = platform_get_drvdata(pdev); + int rc; + + msm_charger_notify_event(&usb_hw_chg, CHG_REMOVED_EVENT); + msm_charger_unregister(&usb_hw_chg); + cancel_delayed_work_sync(&pm8058_chg.veoc_begin_work); + cancel_delayed_work_sync(&pm8058_chg.check_vbat_low_work); + cancel_delayed_work_sync(&pm8058_chg.charging_check_work); + free_irqs(); + remove_debugfs_entries(); + kfree(chip); + + rc = pm8058_batt_alarm_state_set(0, 0); + if (rc) + pr_err("%s: unable to set batt alarm state\n", __func__); + + rc |= pm8058_batt_alarm_unregister_notifier(&alarm_notifier); + if (rc) + pr_err("%s: unable to register alarm notifier\n", __func__); + return rc; +} + +static struct platform_driver pm8058_charger_driver = { + .probe = pm8058_charger_probe, + .remove = __devexit_p(pm8058_charger_remove), + .driver = { + .name = "pm8058-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_charger_init(void) +{ + return platform_driver_register(&pm8058_charger_driver); +} + +static void __exit pm8058_charger_exit(void) +{ + platform_driver_unregister(&pm8058_charger_driver); +} + +late_initcall(pm8058_charger_init); +module_exit(pm8058_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 BATTERY driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8058_charger"); diff --git a/drivers/power/qci_battery.c b/drivers/power/qci_battery.c new file mode 100644 index 00000000000..724bcba27ae --- /dev/null +++ b/drivers/power/qci_battery.c @@ -0,0 +1,662 @@ +/* Quanta I2C Battery Driver + * + * Copyright (C) 2009 Quanta Computer 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. + * + */ + +/* + * + * The Driver with I/O communications via the I2C Interface for ST15 platform. + * And it is only working on the nuvoTon WPCE775x Embedded Controller. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qci_battery.h" + +#define QCIBAT_DEFAULT_CHARGE_FULL_CAPACITY 2200 /* 2200 mAh */ +#define QCIBAT_DEFAULT_CHARGE_FULL_DESIGN 2200 +#define QCIBAT_DEFAULT_VOLTAGE_DESIGN 10800 /* 10.8 V */ +#define QCIBAT_STRING_SIZE 16 + +/* General structure to hold the driver data */ +struct i2cbat_drv_data { + struct i2c_client *bi2c_client; + struct work_struct work; + unsigned int qcibat_irq; + unsigned int qcibat_gpio; + u8 battery_state; + u8 battery_dev_name[QCIBAT_STRING_SIZE]; + u8 serial_number[QCIBAT_STRING_SIZE]; + u8 manufacturer_name[QCIBAT_STRING_SIZE]; + unsigned int charge_full; + unsigned int charge_full_design; + unsigned int voltage_full_design; + unsigned int energy_full; +}; + +static struct i2cbat_drv_data context; +static struct mutex qci_i2c_lock; +static struct mutex qci_transaction_lock; +/********************************************************************* + * Power + *********************************************************************/ + +static int get_bat_info(u8 ec_data) +{ + u8 byte_read; + + mutex_lock(&qci_i2c_lock); + i2c_smbus_write_byte(context.bi2c_client, ec_data); + byte_read = i2c_smbus_read_byte(context.bi2c_client); + mutex_unlock(&qci_i2c_lock); + return byte_read; +} + +static int qci_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (get_bat_info(ECRAM_POWER_SOURCE) & EC_FLAG_ADAPTER_IN) + val->intval = EC_ADAPTER_PRESENT; + else + val->intval = EC_ADAPTER_NOT_PRESENT; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property qci_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property qci_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_EMPTY, +}; + +static int read_data_from_battery(u8 smb_cmd, u8 smb_prtcl) +{ + if (context.battery_state & MAIN_BATTERY_STATUS_BAT_IN) { + mutex_lock(&qci_i2c_lock); + i2c_smbus_write_byte_data(context.bi2c_client, + ECRAM_SMB_STS, 0); + i2c_smbus_write_byte_data(context.bi2c_client, ECRAM_SMB_ADDR, + BATTERY_SLAVE_ADDRESS); + i2c_smbus_write_byte_data(context.bi2c_client, + ECRAM_SMB_CMD, smb_cmd); + i2c_smbus_write_byte_data(context.bi2c_client, + ECRAM_SMB_PRTCL, smb_prtcl); + mutex_unlock(&qci_i2c_lock); + msleep(100); + return get_bat_info(ECRAM_SMB_STS); + } else + return SMBUS_DEVICE_NOACK; +} + +static int qbat_get_status(union power_supply_propval *val) +{ + int status; + + status = get_bat_info(ECRAM_BATTERY_STATUS); + + if ((status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0) + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else if (status & MAIN_BATTERY_STATUS_BAT_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (status & MAIN_BATTERY_STATUS_BAT_FULL) + val->intval = POWER_SUPPLY_STATUS_FULL; + else if (status & MAIN_BATTERY_STATUS_BAT_DISCHRG) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + + return 0; +} + +static int qbat_get_present(union power_supply_propval *val) +{ + if (context.battery_state & MAIN_BATTERY_STATUS_BAT_IN) + val->intval = EC_BAT_PRESENT; + else + val->intval = EC_BAT_NOT_PRESENT; + return 0; +} + +static int qbat_get_health(union power_supply_propval *val) +{ + u8 health; + + health = get_bat_info(ECRAM_CHARGER_ALARM); + if (!(context.battery_state & MAIN_BATTERY_STATUS_BAT_IN)) + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + else if (health & ALARM_OVER_TEMP) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (health & ALARM_REMAIN_CAPACITY) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (health & ALARM_OVER_CHARGE) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + return 0; +} + +static int qbat_get_voltage_avg(union power_supply_propval *val) +{ + val->intval = (get_bat_info(ECRAM_BATTERY_VOLTAGE_MSB) << 8 | + get_bat_info(ECRAM_BATTERY_VOLTAGE_LSB)) * 1000; + return 0; +} + +static int qbat_get_current_avg(union power_supply_propval *val) +{ + val->intval = (get_bat_info(ECRAM_BATTERY_CURRENT_MSB) << 8 | + get_bat_info(ECRAM_BATTERY_CURRENT_LSB)); + return 0; +} + +static int qbat_get_capacity(union power_supply_propval *val) +{ + if (!(context.battery_state & MAIN_BATTERY_STATUS_BAT_IN)) + val->intval = 0xFF; + else + val->intval = get_bat_info(ECRAM_BATTERY_CAPACITY); + return 0; +} + +static int qbat_get_temp_avg(union power_supply_propval *val) +{ + int temp; + int rc = 0; + + if (!(context.battery_state & MAIN_BATTERY_STATUS_BAT_IN)) { + val->intval = 0xFFFF; + rc = -ENODATA; + } else { + temp = (get_bat_info(ECRAM_BATTERY_TEMP_MSB) << 8) | + get_bat_info(ECRAM_BATTERY_TEMP_LSB); + val->intval = (temp - 2730) / 10; + } + return rc; +} + +static int qbat_get_charge_full_design(union power_supply_propval *val) +{ + val->intval = context.charge_full_design; + return 0; +} + +static int qbat_get_charge_full(union power_supply_propval *val) +{ + val->intval = context.charge_full; + return 0; +} + +static int qbat_get_charge_counter(union power_supply_propval *val) +{ + u16 charge = 0; + int rc = 0; + + mutex_lock(&qci_transaction_lock); + if (read_data_from_battery(BATTERY_CYCLE_COUNT, + SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) { + charge = get_bat_info(ECRAM_SMB_DATA1); + charge = charge << 8; + charge |= get_bat_info(ECRAM_SMB_DATA0); + } else + rc = -ENODATA; + mutex_unlock(&qci_transaction_lock); + val->intval = charge; + return rc; +} + +static int qbat_get_time_empty_avg(union power_supply_propval *val) +{ + u16 avg = 0; + int rc = 0; + + mutex_lock(&qci_transaction_lock); + if (read_data_from_battery(BATTERY_AVERAGE_TIME_TO_EMPTY, + SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) { + avg = get_bat_info(ECRAM_SMB_DATA1); + avg = avg << 8; + avg |= get_bat_info(ECRAM_SMB_DATA0); + } else + rc = -ENODATA; + mutex_unlock(&qci_transaction_lock); + val->intval = avg; + return rc; +} + +static int qbat_get_time_full_avg(union power_supply_propval *val) +{ + u16 avg = 0; + int rc = 0; + + mutex_lock(&qci_transaction_lock); + if (read_data_from_battery(BATTERY_AVERAGE_TIME_TO_FULL, + SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) { + avg = get_bat_info(ECRAM_SMB_DATA1); + avg = avg << 8; + avg |= get_bat_info(ECRAM_SMB_DATA0); + } else + rc = -ENODATA; + mutex_unlock(&qci_transaction_lock); + val->intval = avg; + return rc; +} + +static int qbat_get_model_name(union power_supply_propval *val) +{ + unsigned char i, size; + + mutex_lock(&qci_transaction_lock); + if (read_data_from_battery(BATTERY_DEVICE_NAME, + SMBUS_READ_BLOCK_PRTCL) == SMBUS_DONE) { + size = min(get_bat_info(ECRAM_SMB_BCNT), QCIBAT_STRING_SIZE); + for (i = 0; i < size; i++) { + context.battery_dev_name[i] = + get_bat_info(ECRAM_SMB_DATA_START + i); + } + val->strval = context.battery_dev_name; + } else + val->strval = "Unknown"; + mutex_unlock(&qci_transaction_lock); + return 0; +} + +static int qbat_get_manufacturer_name(union power_supply_propval *val) +{ + val->strval = context.manufacturer_name; + return 0; +} + +static int qbat_get_serial_number(union power_supply_propval *val) +{ + val->strval = context.serial_number; + return 0; +} + +static int qbat_get_technology(union power_supply_propval *val) +{ + val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + return 0; +} + +static int qbat_get_energy_now(union power_supply_propval *val) +{ + if (!(get_bat_info(ECRAM_BATTERY_STATUS) & MAIN_BATTERY_STATUS_BAT_IN)) + val->intval = 0; + else + val->intval = (get_bat_info(ECRAM_BATTERY_CAPACITY) * + context.energy_full) / 100; + return 0; +} + +static int qbat_get_energy_full(union power_supply_propval *val) +{ + val->intval = context.energy_full; + return 0; +} + +static int qbat_get_energy_empty(union power_supply_propval *val) +{ + val->intval = 0; + return 0; +} + +static void qbat_init_get_charge_full(void) +{ + u16 charge = QCIBAT_DEFAULT_CHARGE_FULL_CAPACITY; + + mutex_lock(&qci_transaction_lock); + if (read_data_from_battery(BATTERY_FULL_CAPACITY, + SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) { + charge = get_bat_info(ECRAM_SMB_DATA1); + charge = charge << 8; + charge |= get_bat_info(ECRAM_SMB_DATA0); + } + mutex_unlock(&qci_transaction_lock); + context.charge_full = charge; +} + +static void qbat_init_get_charge_full_design(void) +{ + u16 charge = QCIBAT_DEFAULT_CHARGE_FULL_DESIGN; + + mutex_lock(&qci_transaction_lock); + if (read_data_from_battery(BATTERY_DESIGN_CAPACITY, + SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) { + charge = get_bat_info(ECRAM_SMB_DATA1); + charge = charge << 8; + charge |= get_bat_info(ECRAM_SMB_DATA0); + } + mutex_unlock(&qci_transaction_lock); + context.charge_full_design = charge; +} + +static void qbat_init_get_voltage_full_design(void) +{ + u16 voltage = QCIBAT_DEFAULT_VOLTAGE_DESIGN; + + mutex_lock(&qci_transaction_lock); + if (read_data_from_battery(BATTERY_DESIGN_VOLTAGE, + SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) { + voltage = get_bat_info(ECRAM_SMB_DATA1); + voltage = voltage << 8; + voltage |= get_bat_info(ECRAM_SMB_DATA0); + } + mutex_unlock(&qci_transaction_lock); + context.voltage_full_design = voltage; +} + +static void qbat_init_get_manufacturer_name(void) +{ + u8 size; + u8 i; + int rc; + + mutex_lock(&qci_transaction_lock); + rc = read_data_from_battery(BATTERY_MANUFACTURE_NAME, + SMBUS_READ_BLOCK_PRTCL); + if (rc == SMBUS_DONE) { + size = min(get_bat_info(ECRAM_SMB_BCNT), QCIBAT_STRING_SIZE); + for (i = 0; i < size; i++) { + context.manufacturer_name[i] = + get_bat_info(ECRAM_SMB_DATA_START + i); + } + } else + strcpy(context.manufacturer_name, "Unknown"); + mutex_unlock(&qci_transaction_lock); +} + +static void qbat_init_get_serial_number(void) +{ + u8 size; + u8 i; + int rc; + + mutex_lock(&qci_transaction_lock); + rc = read_data_from_battery(BATTERY_SERIAL_NUMBER, + SMBUS_READ_BLOCK_PRTCL); + if (rc == SMBUS_DONE) { + size = min(get_bat_info(ECRAM_SMB_BCNT), QCIBAT_STRING_SIZE); + for (i = 0; i < size; i++) { + context.serial_number[i] = + get_bat_info(ECRAM_SMB_DATA_START + i); + } + } else + strcpy(context.serial_number, "Unknown"); + mutex_unlock(&qci_transaction_lock); +} + +static void init_battery_stats(void) +{ + int i; + + context.battery_state = get_bat_info(ECRAM_BATTERY_STATUS); + if (!(context.battery_state & MAIN_BATTERY_STATUS_BAT_IN)) + return; + /* EC bug? needs some initial priming */ + for (i = 0; i < 5; i++) { + read_data_from_battery(BATTERY_DESIGN_CAPACITY, + SMBUS_READ_WORD_PRTCL); + } + + qbat_init_get_charge_full_design(); + qbat_init_get_charge_full(); + qbat_init_get_voltage_full_design(); + + context.energy_full = context.voltage_full_design * + context.charge_full; + + qbat_init_get_serial_number(); + qbat_init_get_manufacturer_name(); +} + +/********************************************************************* + * Battery properties + *********************************************************************/ +static int qbat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = qbat_get_status(val); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = qbat_get_present(val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = qbat_get_health(val); + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + ret = qbat_get_manufacturer_name(val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + ret = qbat_get_technology(val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + ret = qbat_get_voltage_avg(val); + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = qbat_get_current_avg(val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = qbat_get_capacity(val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = qbat_get_temp_avg(val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = qbat_get_charge_full_design(val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = qbat_get_charge_full(val); + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = qbat_get_charge_counter(val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + ret = qbat_get_time_empty_avg(val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + ret = qbat_get_time_full_avg(val); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + ret = qbat_get_model_name(val); + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + ret = qbat_get_serial_number(val); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + ret = qbat_get_energy_now(val); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + ret = qbat_get_energy_full(val); + break; + case POWER_SUPPLY_PROP_ENERGY_EMPTY: + ret = qbat_get_energy_empty(val); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static struct power_supply qci_ac = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = qci_ac_props, + .num_properties = ARRAY_SIZE(qci_ac_props), + .get_property = qci_ac_get_prop, +}; + +static struct power_supply qci_bat = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = qci_bat_props, + .num_properties = ARRAY_SIZE(qci_bat_props), + .get_property = qbat_get_property, + .use_for_apm = 1, +}; + +static irqreturn_t qbat_interrupt(int irq, void *dev_id) +{ + struct i2cbat_drv_data *ibat_drv_data = dev_id; + schedule_work(&ibat_drv_data->work); + return IRQ_HANDLED; +} + +static void qbat_work(struct work_struct *_work) +{ + u8 status; + + status = get_bat_info(ECRAM_BATTERY_EVENTS); + if (status & EC_EVENT_AC) { + context.battery_state = get_bat_info(ECRAM_BATTERY_STATUS); + power_supply_changed(&qci_ac); + } + + if (status & (EC_EVENT_BATTERY | EC_EVENT_CHARGER | EC_EVENT_TIMER)) { + context.battery_state = get_bat_info(ECRAM_BATTERY_STATUS); + power_supply_changed(&qci_bat); + if (status & EC_EVENT_BATTERY) + init_battery_stats(); + } +} + +static struct platform_device *bat_pdev; + +static int __init qbat_init(void) +{ + int err = 0; + + mutex_init(&qci_i2c_lock); + mutex_init(&qci_transaction_lock); + + context.bi2c_client = wpce_get_i2c_client(); + if (context.bi2c_client == NULL) + return -1; + + i2c_set_clientdata(context.bi2c_client, &context); + context.qcibat_gpio = context.bi2c_client->irq; + + /*battery device register*/ + bat_pdev = platform_device_register_simple("battery", 0, NULL, 0); + if (IS_ERR(bat_pdev)) + return PTR_ERR(bat_pdev); + + err = power_supply_register(&bat_pdev->dev, &qci_ac); + if (err) + goto ac_failed; + + qci_bat.name = bat_pdev->name; + err = power_supply_register(&bat_pdev->dev, &qci_bat); + if (err) + goto battery_failed; + + /*battery irq configure*/ + INIT_WORK(&context.work, qbat_work); + err = gpio_request(context.qcibat_gpio, "qci-bat"); + if (err) { + dev_err(&context.bi2c_client->dev, + "[BAT] err gpio request\n"); + goto gpio_request_fail; + } + context.qcibat_irq = gpio_to_irq(context.qcibat_gpio); + err = request_irq(context.qcibat_irq, qbat_interrupt, + IRQF_TRIGGER_FALLING, BATTERY_ID_NAME, &context); + if (err) { + dev_err(&context.bi2c_client->dev, + "[BAT] unable to get IRQ\n"); + goto request_irq_fail; + } + + init_battery_stats(); + goto success; + +request_irq_fail: + gpio_free(context.qcibat_gpio); + +gpio_request_fail: + power_supply_unregister(&qci_bat); + +battery_failed: + power_supply_unregister(&qci_ac); + +ac_failed: + platform_device_unregister(bat_pdev); + + i2c_set_clientdata(context.bi2c_client, NULL); +success: + return err; +} + +static void __exit qbat_exit(void) +{ + free_irq(context.qcibat_irq, &context); + gpio_free(context.qcibat_gpio); + power_supply_unregister(&qci_bat); + power_supply_unregister(&qci_ac); + platform_device_unregister(bat_pdev); + i2c_set_clientdata(context.bi2c_client, NULL); +} + +late_initcall(qbat_init); +module_exit(qbat_exit); + +MODULE_AUTHOR("Quanta Computer Inc."); +MODULE_DESCRIPTION("Quanta Embedded Controller I2C Battery Driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/power/qci_battery.h b/drivers/power/qci_battery.h new file mode 100644 index 00000000000..dcbb62b5331 --- /dev/null +++ b/drivers/power/qci_battery.h @@ -0,0 +1,121 @@ +/* Header file for Quanta I2C Battery Driver + * + * Copyright (C) 2009 Quanta Computer 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. + * + */ + + /* + * + * The Driver with I/O communications via the I2C Interface for ON2 of AP BU. + * And it is only working on the nuvoTon WPCE775x Embedded Controller. + * + */ + +#ifndef __QCI_BATTERY_H__ +#define __QCI_BATTERY_H__ + +#define BAT_I2C_ADDRESS 0x1A +#define BATTERY_ID_NAME "qci-i2cbat" +#define EC_FLAG_ADAPTER_IN 0x01 +#define EC_FLAG_POWER_ON 0x02 +#define EC_FLAG_ENTER_S3 0x04 +#define EC_FLAG_ENTER_S4 0x08 +#define EC_FLAG_IN_STANDBY 0x10 +#define EC_FLAG_SYSTEM_ON 0x20 +#define EC_FLAG_WAIT_HWPG 0x40 +#define EC_FLAG_S5_POWER_ON 0x80 + +#define MAIN_BATTERY_STATUS_BAT_DISCHRG 0x01 +#define MAIN_BATTERY_STATUS_BAT_CHARGING 0x02 +#define MAIN_BATTERY_STATUS_BAT_ABNORMAL 0x04 +#define MAIN_BATTERY_STATUS_BAT_IN 0x08 +#define MAIN_BATTERY_STATUS_BAT_FULL 0x10 +#define MAIN_BATTERY_STATUS_BAT_LOW 0x20 +#define MAIN_BATTERY_STATUS_BAT_SMB_VALID 0x80 + +#define CHG_STATUS_BAT_CHARGE 0x01 +#define CHG_STATUS_BAT_PRECHG 0x02 +#define CHG_STATUS_BAT_OVERTEMP 0x04 +#define CHG_STATUS_BAT_TYPE 0x08 +#define CHG_STATUS_BAT_GWROK 0x10 +#define CHG_STATUS_BAT_INCHARGE 0x20 +#define CHG_STATUS_BAT_WAKECHRG 0x40 +#define CHG_STATUS_BAT_CHGTIMEOUT 0x80 + +#define EC_ADAPTER_PRESENT 0x1 +#define EC_BAT_PRESENT 0x1 +#define EC_ADAPTER_NOT_PRESENT 0x0 +#define EC_BAT_NOT_PRESENT 0x0 + +#define ECRAM_POWER_SOURCE 0x40 +#define ECRAM_CHARGER_ALARM 0x42 +#define ECRAM_BATTERY_STATUS 0x82 +#define ECRAM_BATTERY_CURRENT_LSB 0x83 +#define ECRAM_BATTERY_CURRENT_MSB 0x84 +#define ECRAM_BATTERY_VOLTAGE_LSB 0x87 +#define ECRAM_BATTERY_VOLTAGE_MSB 0x88 +#define ECRAM_BATTERY_CAPACITY 0x89 +#define ECRAM_BATTERY_TEMP_LSB 0x8C +#define ECRAM_BATTERY_TEMP_MSB 0x8D +#define ECRAM_BATTERY_EVENTS 0x99 + +#define EC_EVENT_BATTERY 0x01 +#define EC_EVENT_CHARGER 0x02 +#define EC_EVENT_AC 0x10 +#define EC_EVENT_TIMER 0x40 + +/* smbus access */ +#define SMBUS_READ_BYTE_PRTCL 0x07 +#define SMBUS_READ_WORD_PRTCL 0x09 +#define SMBUS_READ_BLOCK_PRTCL 0x0B + +/* smbus status code */ +#define SMBUS_OK 0x00 +#define SMBUS_DONE 0x80 +#define SMBUS_ALARM 0x40 +#define SMBUS_UNKNOW_FAILURE 0x07 +#define SMBUS_DEVICE_NOACK 0x10 +#define SMBUS_DEVICE_ERROR 0x11 +#define SMBUS_UNKNOW_ERROR 0x13 +#define SMBUS_TIME_OUT 0x18 +#define SMBUS_BUSY 0x1A + +/* ec ram mapping */ +#define ECRAM_SMB_PRTCL 0 +#define ECRAM_SMB_STS 1 +#define ECRAM_SMB_ADDR 2 +#define ECRAM_SMB_CMD 3 +#define ECRAM_SMB_DATA_START 4 +#define ECRAM_SMB_DATA0 4 +#define ECRAM_SMB_DATA1 5 +#define ECRAM_SMB_BCNT 36 +#define ECRAM_SMB_ALARM_ADDR 37 +#define ECRAM_SMB_ALARM_DATA0 38 +#define ECRAM_SMB_ALARM_DATA1 39 + +/* smart battery commands */ +#define BATTERY_SLAVE_ADDRESS 0x16 +#define BATTERY_FULL_CAPACITY 0x10 +#define BATTERY_AVERAGE_TIME_TO_EMPTY 0x12 +#define BATTERY_AVERAGE_TIME_TO_FULL 0x13 +#define BATTERY_CYCLE_COUNT 0x17 +#define BATTERY_DESIGN_CAPACITY 0x18 +#define BATTERY_DESIGN_VOLTAGE 0x19 +#define BATTERY_SERIAL_NUMBER 0x1C +#define BATTERY_MANUFACTURE_NAME 0x20 +#define BATTERY_DEVICE_NAME 0x21 + +/* alarm bit */ +#define ALARM_REMAIN_CAPACITY 0x02 +#define ALARM_OVER_TEMP 0x10 +#define ALARM_OVER_CHARGE 0x80 +#endif diff --git a/drivers/power/smb137b.c b/drivers/power/smb137b.c new file mode 100644 index 00000000000..7ff8e2871b9 --- /dev/null +++ b/drivers/power/smb137b.c @@ -0,0 +1,857 @@ +/* Copyright (c) 2010-2011 Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SMB137B_MASK(BITS, POS) ((unsigned char)(((1 << BITS) - 1) << POS)) + +#define CHG_CURRENT_REG 0x00 +#define FAST_CHG_CURRENT_MASK SMB137B_MASK(3, 5) +#define PRE_CHG_CURRENT_MASK SMB137B_MASK(2, 3) +#define TERM_CHG_CURRENT_MASK SMB137B_MASK(2, 1) + +#define INPUT_CURRENT_LIMIT_REG 0x01 +#define IN_CURRENT_MASK SMB137B_MASK(3, 5) +#define IN_CURRENT_LIMIT_EN_BIT BIT(2) +#define IN_CURRENT_DET_THRESH_MASK SMB137B_MASK(2, 0) + +#define FLOAT_VOLTAGE_REG 0x02 +#define STAT_OUT_POLARITY_BIT BIT(7) +#define FLOAT_VOLTAGE_MASK SMB137B_MASK(7, 0) + +#define CONTROL_A_REG 0x03 +#define AUTO_RECHARGE_DIS_BIT BIT(7) +#define CURR_CYCLE_TERM_BIT BIT(6) +#define PRE_TO_FAST_V_MASK SMB137B_MASK(3, 3) +#define TEMP_BEHAV_BIT BIT(2) +#define THERM_NTC_CURR_MODE_BIT BIT(1) +#define THERM_NTC_47KOHM_BIT BIT(0) + +#define CONTROL_B_REG 0x04 +#define STAT_OUTPUT_MODE_MASK SMB137B_MASK(2, 6) +#define BATT_OV_ENDS_CYCLE_BIT BIT(5) +#define AUTO_PRE_TO_FAST_DIS_BIT BIT(4) +#define SAFETY_TIMER_EN_BIT BIT(3) +#define OTG_LBR_WD_EN_BIT BIT(2) +#define CHG_WD_TIMER_EN_BIT BIT(1) +#define IRQ_OP_MASK BIT(0) + +#define PIN_CTRL_REG 0x05 +#define AUTO_CHG_EN_BIT BIT(7) +#define AUTO_LBR_EN_BIT BIT(6) +#define OTG_LBR_BIT BIT(5) +#define I2C_PIN_BIT BIT(4) +#define PIN_EN_CTRL_MASK SMB137B_MASK(2, 2) +#define OTG_LBR_PIN_CTRL_MASK SMB137B_MASK(2, 0) + +#define OTG_LBR_CTRL_REG 0x06 +#define BATT_MISSING_DET_EN_BIT BIT(7) +#define AUTO_RECHARGE_THRESH_MASK BIT(6) +#define USB_DP_DN_DET_EN_MASK BIT(5) +#define OTG_LBR_BATT_CURRENT_LIMIT_MASK SMB137B_MASK(2, 3) +#define OTG_LBR_UVLO_THRESH_MASK SMB137B_MASK(3, 0) + +#define FAULT_INTR_REG 0x07 +#define SAFETY_TIMER_EXP_MASK SMB137B_MASK(1, 7) +#define BATT_TEMP_UNSAFE_MASK SMB137B_MASK(1, 6) +#define INPUT_OVLO_IVLO_MASK SMB137B_MASK(1, 5) +#define BATT_OVLO_MASK SMB137B_MASK(1, 4) +#define INTERNAL_OVER_TEMP_MASK SMB137B_MASK(1, 2) +#define ENTER_TAPER_CHG_MASK SMB137B_MASK(1, 1) +#define CHG_MASK SMB137B_MASK(1, 0) + +#define CELL_TEMP_MON_REG 0x08 +#define THERMISTOR_CURR_MASK SMB137B_MASK(2, 6) +#define LOW_TEMP_CHG_INHIBIT_MASK SMB137B_MASK(3, 3) +#define HIGH_TEMP_CHG_INHIBIT_MASK SMB137B_MASK(3, 0) + +#define SAFETY_TIMER_THERMAL_SHUTDOWN_REG 0x09 +#define DCIN_OVLO_SEL_MASK SMB137B_MASK(2, 7) +#define RELOAD_EN_INPUT_VOLTAGE_MASK SMB137B_MASK(1, 6) +#define THERM_SHUTDN_EN_MASK SMB137B_MASK(1, 5) +#define STANDBY_WD_TIMER_EN_MASK SMB137B_MASK(1, 4) +#define COMPLETE_CHG_TMOUT_MASK SMB137B_MASK(2, 2) +#define PRE_CHG_TMOUT_MASK SMB137B_MASK(2, 0) + +#define VSYS_REG 0x0A +#define VSYS_MASK SMB137B_MASK(3, 4) + +#define IRQ_RESET_REG 0x30 + +#define COMMAND_A_REG 0x31 +#define VOLATILE_REGS_WRITE_PERM_BIT BIT(7) +#define POR_BIT BIT(6) +#define FAST_CHG_SETTINGS_BIT BIT(5) +#define BATT_CHG_EN_BIT BIT(4) +#define USBIN_MODE_500_BIT BIT(3) +#define USBIN_MODE_HCMODE_BIT BIT(2) +#define OTG_LBR_EN_BIT BIT(1) +#define STAT_OE_BIT BIT(0) + +#define STATUS_A_REG 0x32 +#define INTERNAL_TEMP_IRQ_STAT BIT(4) +#define DCIN_OV_IRQ_STAT BIT(3) +#define DCIN_UV_IRQ_STAT BIT(2) +#define USBIN_OV_IRQ_STAT BIT(1) +#define USBIN_UV_IRQ_STAT BIT(0) + +#define STATUS_B_REG 0x33 +#define USB_PIN_STAT BIT(7) +#define USB51_MODE_STAT BIT(6) +#define USB51_HC_MODE_STAT BIT(5) +#define INTERNAL_TEMP_LIMIT_B_STAT BIT(4) +#define DC_IN_OV_STAT BIT(3) +#define DC_IN_UV_STAT BIT(2) +#define USB_IN_OV_STAT BIT(1) +#define USB_IN_UV_STAT BIT(0) + +#define STATUS_C_REG 0x34 +#define AUTO_IN_CURR_LIMIT_MASK SMB137B_MASK(4, 4) +#define AUTO_IN_CURR_LIMIT_STAT BIT(3) +#define AUTO_SOURCE_DET_COMP_STAT_MASK SMB137B_MASK(2, 1) +#define AUTO_SOURCE_DET_RESULT_STAT BIT(0) + +#define STATUS_D_REG 0x35 +#define VBATT_LESS_THAN_VSYS_STAT BIT(7) +#define USB_FAIL_STAT BIT(6) +#define BATT_TEMP_STAT_MASK SMB137B_MASK(2, 4) +#define INTERNAL_TEMP_LIMIT_STAT BIT(2) +#define OTG_LBR_MODE_EN_STAT BIT(1) +#define OTG_LBR_VBATT_UVLO_STAT BIT(0) + +#define STATUS_E_REG 0x36 +#define CHARGE_CYCLE_COUNT_STAT BIT(7) +#define CHARGER_TERM_STAT BIT(6) +#define SAFETY_TIMER_STAT_MASK SMB137B_MASK(2, 4) +#define CHARGER_ERROR_STAT BIT(3) +#define CHARGING_STAT_E SMB137B_MASK(2, 1) +#define CHARGING_EN BIT(0) + +#define STATUS_F_REG 0x37 +#define WD_IRQ_ACTIVE_STAT BIT(7) +#define OTG_OVERCURRENT_STAT BIT(6) +#define BATT_PRESENT_STAT BIT(4) +#define BATT_OV_LATCHED_STAT BIT(3) +#define CHARGER_OVLO_STAT BIT(2) +#define CHARGER_UVLO_STAT BIT(1) +#define BATT_LOW_STAT BIT(0) + +#define STATUS_G_REG 0x38 +#define CHARGE_TIMEOUT_IRQ_STAT BIT(7) +#define PRECHARGE_TIMEOUT_IRQ_STAT BIT(6) +#define BATT_HOT_IRQ_STAT BIT(5) +#define BATT_COLD_IRQ_STAT BIT(4) +#define BATT_OV_IRQ_STAT BIT(3) +#define TAPER_CHG_IRQ_STAT BIT(2) +#define FAST_CHG_IRQ_STAT BIT(1) +#define CHARGING_IRQ_STAT BIT(0) + +#define STATUS_H_REG 0x39 +#define CHARGE_TIMEOUT_STAT BIT(7) +#define PRECHARGE_TIMEOUT_STAT BIT(6) +#define BATT_HOT_STAT BIT(5) +#define BATT_COLD_STAT BIT(4) +#define BATT_OV_STAT BIT(3) +#define TAPER_CHG_STAT BIT(2) +#define FAST_CHG_STAT BIT(1) +#define CHARGING_STAT_H BIT(0) + +#define DEV_ID_REG 0x3B + +#define COMMAND_B_REG 0x3C +#define THERM_NTC_CURR_VERRIDE BIT(7) + +#define SMB137B_CHG_PERIOD ((HZ) * 150) + +#define INPUT_CURRENT_REG_DEFAULT 0xE1 +#define INPUT_CURRENT_REG_MIN 0x01 +#define COMMAND_A_REG_DEFAULT 0xA0 +#define COMMAND_A_REG_OTG_MODE 0xA2 + +#define PIN_CTRL_REG_DEFAULT 0x00 +#define PIN_CTRL_REG_CHG_OFF 0x04 + +#define FAST_CHG_E_STATUS 0x2 + +#define SMB137B_DEFAULT_BATT_RATING 950 +struct smb137b_data { + struct i2c_client *client; + struct delayed_work charge_work; + + bool charging; + int chgcurrent; + int cur_charging_mode; + int max_system_voltage; + int min_system_voltage; + + int valid_n_gpio; + + int batt_status; + int batt_chg_type; + int batt_present; + int min_design; + int max_design; + int batt_mah_rating; + + int usb_status; + + u8 dev_id_reg; + struct msm_hardware_charger adapter_hw_chg; +}; + +static unsigned int disabled; +static DEFINE_MUTEX(init_lock); +static unsigned int init_otg_power; + +enum charger_stat { + SMB137B_ABSENT, + SMB137B_PRESENT, + SMB137B_ENUMERATED, +}; + +static struct smb137b_data *usb_smb137b_chg; + +static int smb137b_read_reg(struct i2c_client *client, int reg, + u8 *val) +{ + s32 ret; + struct smb137b_data *smb137b_chg; + + smb137b_chg = i2c_get_clientdata(client); + ret = i2c_smbus_read_byte_data(smb137b_chg->client, reg); + if (ret < 0) { + dev_err(&smb137b_chg->client->dev, + "i2c read fail: can't read from %02x: %d\n", reg, ret); + return ret; + } else + *val = ret; + + return 0; +} + +static int smb137b_write_reg(struct i2c_client *client, int reg, + u8 val) +{ + s32 ret; + struct smb137b_data *smb137b_chg; + + smb137b_chg = i2c_get_clientdata(client); + ret = i2c_smbus_write_byte_data(smb137b_chg->client, reg, val); + if (ret < 0) { + dev_err(&smb137b_chg->client->dev, + "i2c write fail: can't write %02x to %02x: %d\n", + val, reg, ret); + return ret; + } + return 0; +} + +static ssize_t id_reg_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct smb137b_data *smb137b_chg; + + smb137b_chg = i2c_get_clientdata(to_i2c_client(dev)); + + return sprintf(buf, "%02x\n", smb137b_chg->dev_id_reg); +} +static DEVICE_ATTR(id_reg, S_IRUGO | S_IWUSR, id_reg_show, NULL); + +#ifdef DEBUG +static void smb137b_dbg_print_status_regs(struct smb137b_data *smb137b_chg) +{ + int ret; + u8 temp; + + ret = smb137b_read_reg(smb137b_chg->client, STATUS_A_REG, &temp); + dev_dbg(&smb137b_chg->client->dev, "%s A=0x%x\n", __func__, temp); + ret = smb137b_read_reg(smb137b_chg->client, STATUS_B_REG, &temp); + dev_dbg(&smb137b_chg->client->dev, "%s B=0x%x\n", __func__, temp); + ret = smb137b_read_reg(smb137b_chg->client, STATUS_C_REG, &temp); + dev_dbg(&smb137b_chg->client->dev, "%s C=0x%x\n", __func__, temp); + ret = smb137b_read_reg(smb137b_chg->client, STATUS_D_REG, &temp); + dev_dbg(&smb137b_chg->client->dev, "%s D=0x%x\n", __func__, temp); + ret = smb137b_read_reg(smb137b_chg->client, STATUS_E_REG, &temp); + dev_dbg(&smb137b_chg->client->dev, "%s E=0x%x\n", __func__, temp); + ret = smb137b_read_reg(smb137b_chg->client, STATUS_F_REG, &temp); + dev_dbg(&smb137b_chg->client->dev, "%s F=0x%x\n", __func__, temp); + ret = smb137b_read_reg(smb137b_chg->client, STATUS_G_REG, &temp); + dev_dbg(&smb137b_chg->client->dev, "%s G=0x%x\n", __func__, temp); + ret = smb137b_read_reg(smb137b_chg->client, STATUS_H_REG, &temp); + dev_dbg(&smb137b_chg->client->dev, "%s H=0x%x\n", __func__, temp); +} +#else +static void smb137b_dbg_print_status_regs(struct smb137b_data *smb137b_chg) +{ +} +#endif + +static int smb137b_start_charging(struct msm_hardware_charger *hw_chg, + int chg_voltage, int chg_current) +{ + int cmd_val = COMMAND_A_REG_DEFAULT; + u8 temp = 0; + int ret = 0; + struct smb137b_data *smb137b_chg; + + smb137b_chg = container_of(hw_chg, struct smb137b_data, adapter_hw_chg); + if (disabled) { + dev_err(&smb137b_chg->client->dev, + "%s called when disabled\n", __func__); + goto out; + } + + if (smb137b_chg->charging == true + && smb137b_chg->chgcurrent == chg_current) + /* we are already charging with the same current*/ + dev_err(&smb137b_chg->client->dev, + "%s charge with same current %d called again\n", + __func__, chg_current); + + dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__); + if (chg_current < 500) + cmd_val &= ~USBIN_MODE_500_BIT; + else if (chg_current == 500) + cmd_val |= USBIN_MODE_500_BIT; + else + cmd_val |= USBIN_MODE_HCMODE_BIT; + + smb137b_chg->chgcurrent = chg_current; + smb137b_chg->cur_charging_mode = cmd_val; + + /* Due to non-volatile reload feature,always enable volatile + * mirror writes before modifying any 00h~09h control register. + * Current mode needs to be programmed according to what's detected + * Otherwise default 100mA mode might cause VOUTL drop and fail + * the system in case of dead battery. + */ + ret = smb137b_write_reg(smb137b_chg->client, + COMMAND_A_REG, cmd_val); + if (ret) { + dev_err(&smb137b_chg->client->dev, + "%s couldn't write to command_reg\n", __func__); + goto out; + } + ret = smb137b_write_reg(smb137b_chg->client, + PIN_CTRL_REG, PIN_CTRL_REG_DEFAULT); + if (ret) { + dev_err(&smb137b_chg->client->dev, + "%s couldn't write to pin ctrl reg\n", __func__); + goto out; + } + smb137b_chg->charging = true; + smb137b_chg->batt_status = POWER_SUPPLY_STATUS_CHARGING; + smb137b_chg->batt_chg_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + + ret = smb137b_read_reg(smb137b_chg->client, STATUS_E_REG, &temp); + if (ret) { + dev_err(&smb137b_chg->client->dev, + "%s couldn't read status e reg %d\n", __func__, ret); + } else { + if (temp & CHARGER_ERROR_STAT) { + dev_err(&smb137b_chg->client->dev, + "%s chg error E=0x%x\n", __func__, temp); + smb137b_dbg_print_status_regs(smb137b_chg); + } + if (((temp & CHARGING_STAT_E) >> 1) >= FAST_CHG_E_STATUS) + smb137b_chg->batt_chg_type + = POWER_SUPPLY_CHARGE_TYPE_FAST; + } + /*schedule charge_work to keep track of battery charging state*/ + schedule_delayed_work(&smb137b_chg->charge_work, SMB137B_CHG_PERIOD); + +out: + return ret; +} + +static int smb137b_stop_charging(struct msm_hardware_charger *hw_chg) +{ + int ret = 0; + struct smb137b_data *smb137b_chg; + + smb137b_chg = container_of(hw_chg, struct smb137b_data, adapter_hw_chg); + + dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__); + if (smb137b_chg->charging == false) + return 0; + + smb137b_chg->charging = false; + smb137b_chg->batt_status = POWER_SUPPLY_STATUS_DISCHARGING; + smb137b_chg->batt_chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE; + + ret = smb137b_write_reg(smb137b_chg->client, COMMAND_A_REG, + smb137b_chg->cur_charging_mode); + if (ret) { + dev_err(&smb137b_chg->client->dev, + "%s couldn't write to command_reg\n", __func__); + goto out; + } + + ret = smb137b_write_reg(smb137b_chg->client, + PIN_CTRL_REG, PIN_CTRL_REG_CHG_OFF); + if (ret) + dev_err(&smb137b_chg->client->dev, + "%s couldn't write to pin ctrl reg\n", __func__); + +out: + return ret; +} + +static int smb137b_charger_switch(struct msm_hardware_charger *hw_chg) +{ + struct smb137b_data *smb137b_chg; + + smb137b_chg = container_of(hw_chg, struct smb137b_data, adapter_hw_chg); + dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__); + return 0; +} + +static irqreturn_t smb137b_valid_handler(int irq, void *dev_id) +{ + int val; + struct smb137b_data *smb137b_chg; + struct i2c_client *client = dev_id; + + smb137b_chg = i2c_get_clientdata(client); + + pr_debug("%s Cable Detected USB inserted\n", __func__); + /*extra delay needed to allow CABLE_DET_N settling down and debounce + before trying to sample its correct value*/ + usleep_range(1000, 1200); + val = gpio_get_value_cansleep(smb137b_chg->valid_n_gpio); + if (val < 0) { + dev_err(&smb137b_chg->client->dev, + "%s gpio_get_value failed for %d ret=%d\n", __func__, + smb137b_chg->valid_n_gpio, val); + goto err; + } + dev_dbg(&smb137b_chg->client->dev, "%s val=%d\n", __func__, val); + + if (val) { + if (smb137b_chg->usb_status != SMB137B_ABSENT) { + smb137b_chg->usb_status = SMB137B_ABSENT; + msm_charger_notify_event(&smb137b_chg->adapter_hw_chg, + CHG_REMOVED_EVENT); + } + } else { + if (smb137b_chg->usb_status == SMB137B_ABSENT) { + smb137b_chg->usb_status = SMB137B_PRESENT; + msm_charger_notify_event(&smb137b_chg->adapter_hw_chg, + CHG_INSERTED_EVENT); + } + } +err: + return IRQ_HANDLED; +} + +#ifdef CONFIG_DEBUG_FS +static struct dentry *dent; +static int debug_fs_otg; +static int otg_get(void *data, u64 *value) +{ + *value = debug_fs_otg; + return 0; +} +static int otg_set(void *data, u64 value) +{ + smb137b_otg_power(debug_fs_otg); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(smb137b_otg_fops, otg_get, otg_set, "%llu\n"); + +static void smb137b_create_debugfs_entries(struct smb137b_data *smb137b_chg) +{ + dent = debugfs_create_dir("smb137b", NULL); + if (dent) { + debugfs_create_file("otg", 0644, dent, NULL, &smb137b_otg_fops); + } +} +static void smb137b_destroy_debugfs_entries(void) +{ + if (dent) + debugfs_remove_recursive(dent); +} +#else +static void smb137b_create_debugfs_entries(struct smb137b_data *smb137b_chg) +{ +} +static void smb137b_destroy_debugfs_entries(void) +{ +} +#endif + +static int set_disable_status_param(const char *val, struct kernel_param *kp) +{ + int ret; + + ret = param_set_int(val, kp); + if (ret) + return ret; + + if (usb_smb137b_chg && disabled) + msm_charger_notify_event(&usb_smb137b_chg->adapter_hw_chg, + CHG_DONE_EVENT); + + pr_debug("%s disabled =%d\n", __func__, disabled); + return 0; +} +module_param_call(disabled, set_disable_status_param, param_get_uint, + &disabled, 0644); +static void smb137b_charge_sm(struct work_struct *smb137b_work) +{ + int ret; + struct smb137b_data *smb137b_chg; + u8 temp = 0; + int notify_batt_changed = 0; + + smb137b_chg = container_of(smb137b_work, struct smb137b_data, + charge_work.work); + + /*if not charging, exit smb137b charging state transition*/ + if (!smb137b_chg->charging) + return; + + dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__); + + ret = smb137b_read_reg(smb137b_chg->client, STATUS_F_REG, &temp); + if (ret) { + dev_err(&smb137b_chg->client->dev, + "%s couldn't read status f reg %d\n", __func__, ret); + goto out; + } + if (smb137b_chg->batt_present != !(temp & BATT_PRESENT_STAT)) { + smb137b_chg->batt_present = !(temp & BATT_PRESENT_STAT); + notify_batt_changed = 1; + } + + if (!smb137b_chg->batt_present) + smb137b_chg->batt_chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE; + + if (!smb137b_chg->batt_present && smb137b_chg->charging) + msm_charger_notify_event(&smb137b_chg->adapter_hw_chg, + CHG_DONE_EVENT); + + if (smb137b_chg->batt_present + && smb137b_chg->charging + && smb137b_chg->batt_chg_type + != POWER_SUPPLY_CHARGE_TYPE_FAST) { + ret = smb137b_read_reg(smb137b_chg->client, + STATUS_E_REG, &temp); + if (ret) { + dev_err(&smb137b_chg->client->dev, + "%s couldn't read cntrl reg\n", __func__); + goto out; + + } else { + if (temp & CHARGER_ERROR_STAT) { + dev_err(&smb137b_chg->client->dev, + "%s error E=0x%x\n", __func__, temp); + smb137b_dbg_print_status_regs(smb137b_chg); + } + if (((temp & CHARGING_STAT_E) >> 1) + >= FAST_CHG_E_STATUS) { + smb137b_chg->batt_chg_type + = POWER_SUPPLY_CHARGE_TYPE_FAST; + notify_batt_changed = 1; + msm_charger_notify_event( + &smb137b_chg->adapter_hw_chg, + CHG_BATT_BEGIN_FAST_CHARGING); + } else { + smb137b_chg->batt_chg_type + = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + } + } + } + +out: + schedule_delayed_work(&smb137b_chg->charge_work, + SMB137B_CHG_PERIOD); +} + +static void __smb137b_otg_power(int on) +{ + int ret; + + if (on) { + ret = smb137b_write_reg(usb_smb137b_chg->client, + PIN_CTRL_REG, PIN_CTRL_REG_CHG_OFF); + if (ret) { + pr_err("%s turning off charging in pin_ctrl err=%d\n", + __func__, ret); + /* + * don't change the command register if charging in + * pin control cannot be turned off + */ + return; + } + + ret = smb137b_write_reg(usb_smb137b_chg->client, + COMMAND_A_REG, COMMAND_A_REG_OTG_MODE); + if (ret) + pr_err("%s failed turning on OTG mode ret=%d\n", + __func__, ret); + } else { + ret = smb137b_write_reg(usb_smb137b_chg->client, + COMMAND_A_REG, COMMAND_A_REG_DEFAULT); + if (ret) + pr_err("%s failed turning off OTG mode ret=%d\n", + __func__, ret); + ret = smb137b_write_reg(usb_smb137b_chg->client, + PIN_CTRL_REG, PIN_CTRL_REG_DEFAULT); + if (ret) + pr_err("%s failed writing to pn_ctrl ret=%d\n", + __func__, ret); + } +} +static int __devinit smb137b_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct smb137b_platform_data *pdata; + struct smb137b_data *smb137b_chg; + int ret = 0; + + pdata = client->dev.platform_data; + + if (pdata == NULL) { + dev_err(&client->dev, "%s no platform data\n", __func__); + ret = -EINVAL; + goto out; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + ret = -EIO; + goto out; + } + + smb137b_chg = kzalloc(sizeof(*smb137b_chg), GFP_KERNEL); + if (!smb137b_chg) { + ret = -ENOMEM; + goto out; + } + + INIT_DELAYED_WORK(&smb137b_chg->charge_work, smb137b_charge_sm); + smb137b_chg->client = client; + smb137b_chg->valid_n_gpio = pdata->valid_n_gpio; + smb137b_chg->batt_mah_rating = pdata->batt_mah_rating; + if (smb137b_chg->batt_mah_rating == 0) + smb137b_chg->batt_mah_rating = SMB137B_DEFAULT_BATT_RATING; + + /*It supports USB-WALL charger and PC USB charger */ + smb137b_chg->adapter_hw_chg.type = CHG_TYPE_USB; + smb137b_chg->adapter_hw_chg.rating = pdata->batt_mah_rating; + smb137b_chg->adapter_hw_chg.name = "smb137b-charger"; + smb137b_chg->adapter_hw_chg.start_charging = smb137b_start_charging; + smb137b_chg->adapter_hw_chg.stop_charging = smb137b_stop_charging; + smb137b_chg->adapter_hw_chg.charging_switched = smb137b_charger_switch; + + if (pdata->chg_detection_config) + ret = pdata->chg_detection_config(); + if (ret) { + dev_err(&client->dev, "%s valid config failed ret=%d\n", + __func__, ret); + goto free_smb137b_chg; + } + + ret = gpio_request(pdata->valid_n_gpio, "smb137b_charger_valid"); + if (ret) { + dev_err(&client->dev, "%s gpio_request failed for %d ret=%d\n", + __func__, pdata->valid_n_gpio, ret); + goto free_smb137b_chg; + } + + i2c_set_clientdata(client, smb137b_chg); + + ret = msm_charger_register(&smb137b_chg->adapter_hw_chg); + if (ret) { + dev_err(&client->dev, "%s msm_charger_register\ + failed for ret=%d\n", __func__, ret); + goto free_valid_gpio; + } + + ret = irq_set_irq_wake(client->irq, 1); + if (ret) { + dev_err(&client->dev, "%s failed for irq_set_irq_wake %d ret =%d\n", + __func__, client->irq, ret); + goto unregister_charger; + } + + ret = request_threaded_irq(client->irq, NULL, + smb137b_valid_handler, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "smb137b_charger_valid", client); + if (ret) { + dev_err(&client->dev, + "%s request_threaded_irq failed for %d ret =%d\n", + __func__, client->irq, ret); + goto disable_irq_wake; + } + + ret = gpio_get_value_cansleep(smb137b_chg->valid_n_gpio); + if (ret < 0) { + dev_err(&client->dev, + "%s gpio_get_value failed for %d ret=%d\n", __func__, + pdata->valid_n_gpio, ret); + /* assume absent */ + ret = 1; + } + if (!ret) { + msm_charger_notify_event(&smb137b_chg->adapter_hw_chg, + CHG_INSERTED_EVENT); + smb137b_chg->usb_status = SMB137B_PRESENT; + } + + ret = smb137b_read_reg(smb137b_chg->client, DEV_ID_REG, + &smb137b_chg->dev_id_reg); + + ret = device_create_file(&smb137b_chg->client->dev, &dev_attr_id_reg); + + /* TODO read min_design and max_design from chip registers */ + smb137b_chg->min_design = 3200; + smb137b_chg->max_design = 4200; + + smb137b_chg->batt_status = POWER_SUPPLY_STATUS_DISCHARGING; + smb137b_chg->batt_chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE; + + device_init_wakeup(&client->dev, 1); + + mutex_lock(&init_lock); + usb_smb137b_chg = smb137b_chg; + if (init_otg_power) + __smb137b_otg_power(init_otg_power); + mutex_unlock(&init_lock); + + smb137b_create_debugfs_entries(smb137b_chg); + dev_dbg(&client->dev, + "%s OK device_id = %x chg_state=%d\n", __func__, + smb137b_chg->dev_id_reg, smb137b_chg->usb_status); + return 0; + +disable_irq_wake: + irq_set_irq_wake(client->irq, 0); +unregister_charger: + msm_charger_unregister(&smb137b_chg->adapter_hw_chg); +free_valid_gpio: + gpio_free(pdata->valid_n_gpio); +free_smb137b_chg: + kfree(smb137b_chg); +out: + return ret; +} + +void smb137b_otg_power(int on) +{ + pr_debug("%s Enter on=%d\n", __func__, on); + + mutex_lock(&init_lock); + if (!usb_smb137b_chg) { + init_otg_power = !!on; + pr_warning("%s called when not initialized\n", __func__); + mutex_unlock(&init_lock); + return; + } + __smb137b_otg_power(on); + mutex_unlock(&init_lock); +} + +static int __devexit smb137b_remove(struct i2c_client *client) +{ + const struct smb137b_platform_data *pdata; + struct smb137b_data *smb137b_chg = i2c_get_clientdata(client); + + pdata = client->dev.platform_data; + device_init_wakeup(&client->dev, 0); + irq_set_irq_wake(client->irq, 0); + free_irq(client->irq, client); + gpio_free(pdata->valid_n_gpio); + cancel_delayed_work_sync(&smb137b_chg->charge_work); + + msm_charger_notify_event(&smb137b_chg->adapter_hw_chg, + CHG_REMOVED_EVENT); + msm_charger_unregister(&smb137b_chg->adapter_hw_chg); + smb137b_destroy_debugfs_entries(); + kfree(smb137b_chg); + return 0; +} + +#ifdef CONFIG_PM +static int smb137b_suspend(struct device *dev) +{ + struct smb137b_data *smb137b_chg = dev_get_drvdata(dev); + + dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__); + if (smb137b_chg->charging) + return -EBUSY; + return 0; +} + +static int smb137b_resume(struct device *dev) +{ + struct smb137b_data *smb137b_chg = dev_get_drvdata(dev); + + dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__); + return 0; +} + +static const struct dev_pm_ops smb137b_pm_ops = { + .suspend = smb137b_suspend, + .resume = smb137b_resume, +}; +#endif + +static const struct i2c_device_id smb137b_id[] = { + {"smb137b", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, smb137b_id); + +static struct i2c_driver smb137b_driver = { + .driver = { + .name = "smb137b", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &smb137b_pm_ops, +#endif + }, + .probe = smb137b_probe, + .remove = __devexit_p(smb137b_remove), + .id_table = smb137b_id, +}; + +static int __init smb137b_init(void) +{ + return i2c_add_driver(&smb137b_driver); +} +module_init(smb137b_init); + +static void __exit smb137b_exit(void) +{ + return i2c_del_driver(&smb137b_driver); +} +module_exit(smb137b_exit); + +MODULE_AUTHOR("Abhijeet Dharmapurikar "); +MODULE_DESCRIPTION("Driver for SMB137B Charger chip"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:smb137b"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index d7ed20f293d..3ebf2aa5180 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -303,5 +303,47 @@ config REGULATOR_TPS65910 help This driver supports TPS65910 voltage regulator chips. +config REGULATOR_PMIC8058 + tristate "PMIC8058 regulator driver" + depends on PMIC8058 && (ARCH_MSM8X60 || ARCH_FSM9XXX) + default y if PMIC8058 && (ARCH_MSM8X60 || ARCH_FSM9XXX) + help + Say Y here to support the voltage regulators on PMIC8058 + +config REGULATOR_PMIC8901 + tristate "PMIC8901 regulator driver" + depends on PMIC8901 && ARCH_MSM8X60 + default y if PMIC8901 && ARCH_MSM8X60 + help + Say Y here to support the voltage regulators on PMIC8901 + +config REGULATOR_PM8921 + tristate "Qualcomm PM8921 PMIC Power regulators" + depends on MFD_PM8921_CORE + default y if MFD_PM8921_CORE + help + This driver supports voltage regulators in the Qualcomm PM8921 PMIC + chip. The PM8921 provides several different varieties of LDO and + switching regulators. It also provides a negative charge pump and + voltage switches. + +config REGULATOR_GPIO + tristate "GPIO regulator" + depends on GPIOLIB + help + This driver provides a regulator wrapper around a GPIO pin that is set + to output. It is intended to be used for GPIO pins that provide the + enable signal to a physical regulator. The GPIO enable signal can + be configured to be active high (default) or active low. + +config REGULATOR_PM8058_XO + tristate "PM8058 XO Buffer driver" + depends on PMIC8058 + default n + help + This driver supports xo buffer control in the Qualcomm PM8058 PMIC + chip. It is only supposed to be used when Linux on application + processor is the master in control of XO buffers. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 3932d2ec38f..1dfaa3cda69 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -43,5 +43,10 @@ obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o obj-$(CONFIG_REGULATOR_AB8500) += ab8500.o obj-$(CONFIG_REGULATOR_DB8500_PRCMU) += db8500-prcmu.o obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o +obj-$(CONFIG_REGULATOR_PMIC8058) += pmic8058-regulator.o +obj-$(CONFIG_REGULATOR_PMIC8901) += pmic8901-regulator.o +obj-$(CONFIG_REGULATOR_PM8921) += pm8921-regulator.o +obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o +obj-$(CONFIG_REGULATOR_PM8058_XO) += pm8058-xo.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index d3e38790906..d4d055dc350 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -24,6 +24,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -47,6 +50,7 @@ static LIST_HEAD(regulator_list); static LIST_HEAD(regulator_map_list); static bool has_full_constraints; static bool board_wants_dummy_regulator; +static int suppress_info_printing; #ifdef CONFIG_DEBUG_FS static struct dentry *debugfs_root; @@ -75,6 +79,7 @@ struct regulator { int uA_load; int min_uV; int max_uV; + int enabled; char *supply_name; struct device_attribute dev_attr; struct regulator_dev *rdev; @@ -138,6 +143,15 @@ static int regulator_check_voltage(struct regulator_dev *rdev, return -EPERM; } + /* check if requested voltage range actually overlaps the constraints */ + if (*max_uV < rdev->constraints->min_uV || + *min_uV > rdev->constraints->max_uV) { + rdev_err(rdev, "requested voltage range [%d, %d] does not fit " + "within constraints: [%d, %d]\n", *min_uV, *max_uV, + rdev->constraints->min_uV, rdev->constraints->max_uV); + return -EINVAL; + } + if (*max_uV > rdev->constraints->max_uV) *max_uV = rdev->constraints->max_uV; if (*min_uV < rdev->constraints->min_uV) @@ -587,13 +601,87 @@ static struct class regulator_class = { .dev_attrs = regulator_dev_attrs, }; +static int regulator_check_voltage_update(struct regulator_dev *rdev) +{ + if (!rdev->constraints) + return -ENODEV; + if (!(rdev->constraints->valid_ops_mask & REGULATOR_CHANGE_VOLTAGE)) + return -EPERM; + if (!rdev->desc->ops->set_voltage) + return -EINVAL; + + return 0; +} + +static int update_voltage(struct regulator *regulator, int min_uV, int max_uV) +{ + struct regulator_dev *rdev = regulator->rdev; + struct regulator *sibling; + int ret = 0; + + list_for_each_entry(sibling, &rdev->consumer_list, list) { + if (regulator == sibling || !sibling->enabled) + continue; + if (max_uV < sibling->min_uV || min_uV > sibling->max_uV) { + printk(KERN_ERR "%s: requested voltage range [%d, %d] " + "for %s does not fit within previously voted " + "range: [%d, %d]\n", + __func__, min_uV, max_uV, + rdev_get_name(regulator->rdev), + sibling->min_uV, + sibling->max_uV); + ret = -EINVAL; + goto out; + } + if (sibling->max_uV < max_uV) + max_uV = sibling->max_uV; + if (sibling->min_uV > min_uV) + min_uV = sibling->min_uV; + } + + ret = _regulator_do_set_voltage(rdev, min_uV, max_uV); + if (!ret) + goto out; + + _notifier_call_chain(rdev, REGULATOR_EVENT_VOLTAGE_CHANGE, NULL); + +out: + return ret; +} + +static int update_voltage_prev(struct regulator_dev *rdev) +{ + int ret, min_uV = INT_MIN, max_uV = INT_MAX; + struct regulator *consumer; + + list_for_each_entry(consumer, &rdev->consumer_list, list) { + if (!consumer->enabled) + continue; + if (consumer->max_uV < max_uV) + max_uV = consumer->max_uV; + if (consumer->min_uV > min_uV) + min_uV = consumer->min_uV; + } + + if (min_uV == INT_MIN) + return 0; + + ret = _regulator_do_set_voltage(rdev, min_uV, max_uV); + if (!ret) + return ret; + + _notifier_call_chain(rdev, REGULATOR_EVENT_VOLTAGE_CHANGE, NULL); + + return ret; +} + /* Calculate the new optimum regulator operating mode based on the new total * consumer load. All locks held by caller */ static void drms_uA_update(struct regulator_dev *rdev) { struct regulator *sibling; int current_uA = 0, output_uV, input_uV, err; - unsigned int mode; + unsigned int regulator_curr_mode, mode; err = regulator_check_drms(rdev); if (err < 0 || !rdev->desc->ops->get_optimum_mode || @@ -626,6 +714,14 @@ static void drms_uA_update(struct regulator_dev *rdev) /* check the new mode is allowed */ err = regulator_mode_constrain(rdev, &mode); + /* return if the same mode is requested */ + if (rdev->desc->ops->get_mode) { + regulator_curr_mode = rdev->desc->ops->get_mode(rdev); + if (regulator_curr_mode == mode) + return; + } else + return; + if (err == 0) rdev->desc->ops->set_mode(rdev, mode); } @@ -914,7 +1010,8 @@ static int set_machine_constraints(struct regulator_dev *rdev, } } - print_constraints(rdev); + if (!suppress_info_printing) + print_constraints(rdev); out: return ret; } @@ -1389,7 +1486,33 @@ int regulator_enable(struct regulator *regulator) int ret = 0; mutex_lock(&rdev->mutex); + + if (!regulator_check_voltage_update(rdev)) { + if (regulator->min_uV < rdev->constraints->min_uV || + regulator->max_uV > rdev->constraints->max_uV) { + rdev_err(rdev, "invalid input - constraint: [%d, %d], " + "set point: [%d, %d]\n", + rdev->constraints->min_uV, + rdev->constraints->max_uV, + regulator->min_uV, + regulator->max_uV); + ret = -EINVAL; + goto out; + } + + ret = update_voltage(regulator, regulator->min_uV, + regulator->max_uV); + if (ret) + goto out; + } + ret = _regulator_enable(rdev); + if (ret) + goto out; + + regulator->enabled++; + +out: mutex_unlock(&rdev->mutex); return ret; } @@ -1463,6 +1586,15 @@ int regulator_disable(struct regulator *regulator) mutex_lock(&rdev->mutex); ret = _regulator_disable(rdev, &supply_rdev); + if (ret) + goto out; + + regulator->enabled--; + + if (!regulator_check_voltage_update(rdev)) + update_voltage_prev(rdev); + +out: mutex_unlock(&rdev->mutex); /* decrease our supplies ref count and disable if required */ @@ -1770,14 +1902,15 @@ int regulator_set_voltage(struct regulator *regulator, int min_uV, int max_uV) ret = regulator_check_voltage(rdev, &min_uV, &max_uV); if (ret < 0) goto out; - regulator->min_uV = min_uV; - regulator->max_uV = max_uV; - ret = regulator_check_consumers(rdev, &min_uV, &max_uV); - if (ret < 0) - goto out; + if (regulator->enabled) { + ret = update_voltage(regulator, min_uV, max_uV); + if (ret) + goto out; + } - ret = _regulator_do_set_voltage(rdev, min_uV, max_uV); + regulator->min_uV = min_uV; + regulator->max_uV = max_uV; out: mutex_unlock(&rdev->mutex); @@ -2514,22 +2647,356 @@ static int add_regulator_attributes(struct regulator_dev *rdev) return status; } +#ifdef CONFIG_DEBUG_FS + +#define MAX_DEBUG_BUF_LEN 50 + +static DEFINE_MUTEX(debug_buf_mutex); +static char debug_buf[MAX_DEBUG_BUF_LEN]; + +static int reg_debug_enable_set(void *data, u64 val) +{ + int err_info; + if (IS_ERR(data) || data == NULL) { + pr_err("Function Input Error %ld\n", PTR_ERR(data)); + return -ENOMEM; + } + + if (val) + err_info = regulator_enable(data); + else + err_info = regulator_disable(data); + + return err_info; +} + +static int reg_debug_enable_get(void *data, u64 *val) +{ + if (IS_ERR(data) || data == NULL) { + pr_err("Function Input Error %ld\n", PTR_ERR(data)); + return -ENOMEM; + } + + *val = regulator_is_enabled(data); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(reg_enable_fops, reg_debug_enable_get, + reg_debug_enable_set, "%llu\n"); + +static int reg_debug_fdisable_set(void *data, u64 val) +{ + int err_info; + if (IS_ERR(data) || data == NULL) { + pr_err("Function Input Error %ld\n", PTR_ERR(data)); + return -ENOMEM; + } + + if (val > 0) + err_info = regulator_force_disable(data); + else + err_info = 0; + + return err_info; +} + +DEFINE_SIMPLE_ATTRIBUTE(reg_fdisable_fops, reg_debug_enable_get, + reg_debug_fdisable_set, "%llu\n"); + +static ssize_t reg_debug_volt_set(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int err_info, filled; + int min, max = -1; + if (IS_ERR(file) || file == NULL) { + pr_err("Function Input Error %ld\n", PTR_ERR(file)); + return -ENOMEM; + } + + if (count < MAX_DEBUG_BUF_LEN) { + mutex_lock(&debug_buf_mutex); + + if (copy_from_user(debug_buf, (void __user *) buf, count)) + return -EFAULT; + + debug_buf[count] = '\0'; + filled = sscanf(debug_buf, "%d %d", &min, &max); + + mutex_unlock(&debug_buf_mutex); + /* check that user entered two numbers */ + if (filled < 2 || min < 0 || max < min) { + pr_info("Error, correct format: 'echo \"min max\"" + " > voltage"); + return -ENOMEM; + } else { + err_info = regulator_set_voltage(file->private_data, + min, max); + } + } else { + pr_err("Error-Input voltage pair" + " string exceeds maximum buffer length"); + + return -ENOMEM; + } + + return count; +} + +static ssize_t reg_debug_volt_get(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int voltage, output, rc; + if (IS_ERR(file) || file == NULL) { + pr_err("Function Input Error %ld\n", PTR_ERR(file)); + return -ENOMEM; + } + + voltage = regulator_get_voltage(file->private_data); + mutex_lock(&debug_buf_mutex); + + output = snprintf(debug_buf, MAX_DEBUG_BUF_LEN-1, "%d\n", voltage); + rc = simple_read_from_buffer((void __user *) buf, output, ppos, + (void *) debug_buf, output); + + mutex_unlock(&debug_buf_mutex); + + return rc; +} + +static int reg_debug_volt_open(struct inode *inode, struct file *file) +{ + if (IS_ERR(file) || file == NULL) { + pr_err("Function Input Error %ld\n", PTR_ERR(file)); + return -ENOMEM; + } + + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations reg_volt_fops = { + .write = reg_debug_volt_set, + .open = reg_debug_volt_open, + .read = reg_debug_volt_get, +}; + +static int reg_debug_mode_set(void *data, u64 val) +{ + int err_info; + if (IS_ERR(data) || data == NULL) { + pr_err("Function Input Error %ld\n", PTR_ERR(data)); + return -ENOMEM; + } + + err_info = regulator_set_mode(data, (unsigned int)val); + + return err_info; +} + +static int reg_debug_mode_get(void *data, u64 *val) +{ + int err_info; + if (IS_ERR(data) || data == NULL) { + pr_err("Function Input Error %ld\n", PTR_ERR(data)); + return -ENOMEM; + } + + err_info = regulator_get_mode(data); + + if (err_info < 0) { + pr_err("Regulator_get_mode returned an error!\n"); + return -ENOMEM; + } else { + *val = err_info; + return 0; + } +} + +DEFINE_SIMPLE_ATTRIBUTE(reg_mode_fops, reg_debug_mode_get, + reg_debug_mode_set, "%llu\n"); + +static int reg_debug_optimum_mode_set(void *data, u64 val) +{ + int err_info; + if (IS_ERR(data) || data == NULL) { + pr_err("Function Input Error %ld\n", PTR_ERR(data)); + return -ENOMEM; + } + + err_info = regulator_set_optimum_mode(data, (unsigned int)val); + + if (err_info < 0) { + pr_err("Regulator_set_optimum_mode returned an error!\n"); + return err_info; + } + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(reg_optimum_mode_fops, reg_debug_mode_get, + reg_debug_optimum_mode_set, "%llu\n"); + +static int reg_debug_consumers_show(struct seq_file *m, void *v) +{ + struct regulator_dev *rdev = m->private; + struct regulator *reg; + char *supply_name; + + if (!rdev) { + pr_err("regulator device missing"); + return -EINVAL; + } + + mutex_lock(&rdev->mutex); + + /* Print a header if there are consumers. */ + if (rdev->open_count) + seq_printf(m, "Device-Supply " + "EN Min_uV Max_uV load_uA\n"); + + list_for_each_entry(reg, &rdev->consumer_list, list) { + if (reg->supply_name) + supply_name = reg->supply_name; + else + supply_name = "(null)-(null)"; + + seq_printf(m, "%-32s %c %8d %8d %8d\n", supply_name, + (reg->enabled ? 'Y' : 'N'), reg->min_uV, reg->max_uV, + reg->uA_load); + } + + mutex_unlock(&rdev->mutex); + + return 0; +} + +static int reg_debug_consumers_open(struct inode *inode, struct file *file) +{ + return single_open(file, reg_debug_consumers_show, inode->i_private); +} + +static const struct file_operations reg_consumers_fops = { + .owner = THIS_MODULE, + .open = reg_debug_consumers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + static void rdev_init_debugfs(struct regulator_dev *rdev) { -#ifdef CONFIG_DEBUG_FS + struct dentry *err_ptr = NULL; + struct regulator *reg; + struct regulator_ops *reg_ops; + mode_t mode; + + if (IS_ERR(rdev) || rdev == NULL || + IS_ERR(debugfs_root) || debugfs_root == NULL) { + pr_err("Error-Bad Function Input\n"); + goto error; + } + rdev->debugfs = debugfs_create_dir(rdev_get_name(rdev), debugfs_root); if (IS_ERR(rdev->debugfs) || !rdev->debugfs) { rdev_warn(rdev, "Failed to create debugfs directory\n"); rdev->debugfs = NULL; - return; + goto error; } debugfs_create_u32("use_count", 0444, rdev->debugfs, &rdev->use_count); debugfs_create_u32("open_count", 0444, rdev->debugfs, &rdev->open_count); -#endif + debugfs_create_file("consumers", 0444, rdev->debugfs, rdev, + ®_consumers_fops); + + reg = regulator_get(NULL, rdev->desc->name); + if (IS_ERR(reg) || reg == NULL) { + pr_err("Error-Bad Function Input\n"); + goto error; + } + + reg_ops = rdev->desc->ops; + mode = S_IRUGO | S_IWUSR; + /* Enabled File */ + if (mode) + err_ptr = debugfs_create_file("enable", mode, rdev->debugfs, + reg, ®_enable_fops); + if (IS_ERR(err_ptr)) { + pr_err("Error-Could not create enable file\n"); + debugfs_remove_recursive(rdev->debugfs); + goto error; + } + + mode = 0; + /* Force-Disable File */ + if (reg_ops->is_enabled) + mode |= S_IRUGO; + if (reg_ops->enable || reg_ops->disable) + mode |= S_IWUSR; + if (mode) + err_ptr = debugfs_create_file("force_disable", mode, + rdev->debugfs, reg, ®_fdisable_fops); + if (IS_ERR(err_ptr)) { + pr_err("Error-Could not create force_disable file\n"); + debugfs_remove_recursive(rdev->debugfs); + goto error; + } + + mode = 0; + /* Voltage File */ + if (reg_ops->get_voltage) + mode |= S_IRUGO; + if (reg_ops->set_voltage) + mode |= S_IWUSR; + if (mode) + err_ptr = debugfs_create_file("voltage", mode, rdev->debugfs, + reg, ®_volt_fops); + if (IS_ERR(err_ptr)) { + pr_err("Error-Could not create voltage file\n"); + debugfs_remove_recursive(rdev->debugfs); + goto error; + } + + mode = 0; + /* Mode File */ + if (reg_ops->get_mode) + mode |= S_IRUGO; + if (reg_ops->set_mode) + mode |= S_IWUSR; + if (mode) + err_ptr = debugfs_create_file("mode", mode, rdev->debugfs, + reg, ®_mode_fops); + if (IS_ERR(err_ptr)) { + pr_err("Error-Could not create mode file\n"); + debugfs_remove_recursive(rdev->debugfs); + goto error; + } + + mode = 0; + /* Optimum Mode File */ + if (reg_ops->get_mode) + mode |= S_IRUGO; + if (reg_ops->set_mode) + mode |= S_IWUSR; + if (mode) + err_ptr = debugfs_create_file("optimum_mode", mode, + rdev->debugfs, reg, ®_optimum_mode_fops); + if (IS_ERR(err_ptr)) { + pr_err("Error-Could not create optimum_mode file\n"); + debugfs_remove_recursive(rdev->debugfs); + goto error; + } + +error: + return; +} +#else +static inline void rdev_init_debugfs(struct regulator_dev *rdev) +{ + return; } +#endif /** * regulator_register - register regulator @@ -2663,9 +3130,9 @@ struct regulator_dev *regulator_register(struct regulator_desc *regulator_desc, list_add(&rdev->list, ®ulator_list); - rdev_init_debugfs(rdev); out: mutex_unlock(®ulator_list_mutex); + rdev_init_debugfs(rdev); return rdev; unset_supplies: @@ -2818,6 +3285,22 @@ void regulator_use_dummy_regulator(void) } EXPORT_SYMBOL_GPL(regulator_use_dummy_regulator); +/** + * regulator_suppress_info_printing - disable printing of info messages + * + * The regulator framework calls print_constraints() when a regulator is + * registered. It also prints a disable message for each unused regulator in + * regulator_init_complete(). + * + * Calling this function ensures that such messages do not end up in the + * log. + */ +void regulator_suppress_info_printing(void) +{ + suppress_info_printing = 1; +} +EXPORT_SYMBOL_GPL(regulator_suppress_info_printing); + /** * rdev_get_drvdata - get rdev regulator driver data * @rdev: regulator @@ -2936,7 +3419,8 @@ static int __init regulator_init_complete(void) if (has_full_constraints) { /* We log since this may kill the system if it * goes wrong. */ - rdev_info(rdev, "disabling\n"); + if (!suppress_info_printing) + rdev_info(rdev, "disabling\n"); ret = ops->disable(rdev); if (ret != 0) { rdev_err(rdev, "couldn't disable: %d\n", ret); @@ -2947,7 +3431,9 @@ static int __init regulator_init_complete(void) * so warn even if we aren't going to do * anything here. */ - rdev_warn(rdev, "incomplete constraints, leaving on\n"); + if (!suppress_info_printing) + rdev_warn(rdev, "incomplete constraints, " + "leaving on\n"); } unlock: diff --git a/drivers/regulator/gpio-regulator.c b/drivers/regulator/gpio-regulator.c new file mode 100644 index 00000000000..b91e32f98b2 --- /dev/null +++ b/drivers/regulator/gpio-regulator.c @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpio_vreg { + struct regulator_desc desc; + struct regulator_dev *rdev; + char *gpio_label; + char *name; + unsigned gpio; + int active_low; + bool gpio_requested; +}; + +static int gpio_vreg_request_gpio(struct gpio_vreg *vreg) +{ + int rc = 0; + + /* Request GPIO now if it hasn't been requested before. */ + if (!vreg->gpio_requested) { + rc = gpio_request(vreg->gpio, vreg->gpio_label); + if (rc < 0) + pr_err("failed to request gpio %u (%s), rc=%d\n", + vreg->gpio, vreg->gpio_label, rc); + else + vreg->gpio_requested = true; + + rc = gpio_sysfs_set_active_low(vreg->gpio, vreg->active_low); + if (rc < 0) + pr_err("active_low=%d failed for gpio %u, rc=%d\n", + vreg->active_low, vreg->gpio, rc); + } + + return rc; +} + +static int gpio_vreg_is_enabled(struct regulator_dev *rdev) +{ + struct gpio_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = gpio_vreg_request_gpio(vreg); + if (rc < 0) + return rc; + + return (gpio_get_value_cansleep(vreg->gpio) ? 1 : 0) ^ vreg->active_low; +} + +static int gpio_vreg_enable(struct regulator_dev *rdev) +{ + struct gpio_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = gpio_vreg_request_gpio(vreg); + if (rc < 0) + return rc; + + return gpio_direction_output(vreg->gpio, !vreg->active_low); +} + +static int gpio_vreg_disable(struct regulator_dev *rdev) +{ + struct gpio_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = gpio_vreg_request_gpio(vreg); + if (rc < 0) + return rc; + + return gpio_direction_output(vreg->gpio, vreg->active_low); +} + +static struct regulator_ops gpio_vreg_ops = { + .enable = gpio_vreg_enable, + .disable = gpio_vreg_disable, + .is_enabled = gpio_vreg_is_enabled, +}; + +static int __devinit gpio_vreg_probe(struct platform_device *pdev) +{ + const struct gpio_regulator_platform_data *pdata; + struct gpio_vreg *vreg; + int rc = 0; + + pdata = pdev->dev.platform_data; + + if (!pdata) { + pr_err("platform data required.\n"); + return -EINVAL; + } + + if (!pdata->gpio_label) { + pr_err("gpio_label required.\n"); + return -EINVAL; + } + + if (!pdata->regulator_name) { + pr_err("regulator_name required.\n"); + return -EINVAL; + } + + vreg = kzalloc(sizeof(struct gpio_vreg), GFP_KERNEL); + if (!vreg) { + pr_err("kzalloc failed.\n"); + return -ENOMEM; + } + + vreg->name = kstrdup(pdata->regulator_name, GFP_KERNEL); + if (!vreg->name) { + pr_err("kzalloc failed.\n"); + rc = -ENOMEM; + goto free_vreg; + } + + vreg->gpio_label = kstrdup(pdata->gpio_label, GFP_KERNEL); + if (!vreg->gpio_label) { + pr_err("kzalloc failed.\n"); + rc = -ENOMEM; + goto free_name; + } + + vreg->gpio = pdata->gpio; + vreg->active_low = (pdata->active_low ? 1 : 0); + vreg->gpio_requested = false; + + vreg->desc.name = vreg->name; + vreg->desc.id = pdev->id; + vreg->desc.ops = &gpio_vreg_ops; + vreg->desc.type = REGULATOR_VOLTAGE; + vreg->desc.owner = THIS_MODULE; + + vreg->rdev = regulator_register(&vreg->desc, &pdev->dev, + &pdata->init_data, vreg); + if (IS_ERR(vreg->rdev)) { + rc = PTR_ERR(vreg->rdev); + pr_err("%s: regulator_register failed, rc=%d.\n", vreg->name, + rc); + goto free_gpio_label; + } + + platform_set_drvdata(pdev, vreg); + + pr_info("id=%d, name=%s, gpio=%u, gpio_label=%s\n", pdev->id, + vreg->name, vreg->gpio, vreg->gpio_label); + + return rc; + +free_gpio_label: + kfree(vreg->gpio_label); +free_name: + kfree(vreg->name); +free_vreg: + kfree(vreg); + + return rc; +} + +static int __devexit gpio_vreg_remove(struct platform_device *pdev) +{ + struct gpio_vreg *vreg = platform_get_drvdata(pdev); + + if (vreg->gpio_requested) + gpio_free(vreg->gpio); + + regulator_unregister(vreg->rdev); + kfree(vreg->name); + kfree(vreg->gpio_label); + kfree(vreg); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver gpio_vreg_driver = { + .probe = gpio_vreg_probe, + .remove = __devexit_p(gpio_vreg_remove), + .driver = { + .name = GPIO_REGULATOR_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init gpio_vreg_init(void) +{ + return platform_driver_register(&gpio_vreg_driver); +} + +static void __exit gpio_vreg_exit(void) +{ + platform_driver_unregister(&gpio_vreg_driver); +} + +postcore_initcall(gpio_vreg_init); +module_exit(gpio_vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("GPIO regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" GPIO_REGULATOR_DEV_NAME); diff --git a/drivers/regulator/pm8058-xo.c b/drivers/regulator/pm8058-xo.c new file mode 100644 index 00000000000..ac65395ea5d --- /dev/null +++ b/drivers/regulator/pm8058-xo.c @@ -0,0 +1,215 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* XO buffer masks and values */ + +#define XO_PULLDOWN_MASK 0x08 +#define XO_PULLDOWN_ENABLE 0x08 +#define XO_PULLDOWN_DISABLE 0x00 + +#define XO_BUFFER_MASK 0x04 +#define XO_BUFFER_ENABLE 0x04 +#define XO_BUFFER_DISABLE 0x00 + +#define XO_MODE_MASK 0x01 +#define XO_MODE_MANUAL 0x00 + +#define XO_ENABLE_MASK (XO_MODE_MASK | XO_BUFFER_MASK) +#define XO_ENABLE (XO_MODE_MANUAL | XO_BUFFER_ENABLE) +#define XO_DISABLE (XO_MODE_MANUAL | XO_BUFFER_DISABLE) + +struct pm8058_xo_buffer { + struct pm8058_xo_pdata *pdata; + struct regulator_dev *rdev; + u16 ctrl_addr; + u8 ctrl_reg; +}; + +#define XO_BUFFER(_id, _ctrl_addr) \ + [PM8058_XO_ID_##_id] = { \ + .ctrl_addr = _ctrl_addr, \ + } + +static struct pm8058_xo_buffer pm8058_xo_buffer[] = { + XO_BUFFER(A0, 0x185), + XO_BUFFER(A1, 0x186), +}; + +static int pm8058_xo_buffer_write(struct pm8058_chip *chip, + u16 addr, u8 val, u8 mask, u8 *reg_save) +{ + u8 reg; + int rc = 0; + + reg = (*reg_save & ~mask) | (val & mask); + if (reg != *reg_save) + rc = pm8058_write(chip, addr, ®, 1); + + if (rc) + pr_err("FAIL: pm8058_write: rc=%d\n", rc); + else + *reg_save = reg; + return rc; +} + +static int pm8058_xo_buffer_enable(struct regulator_dev *dev) +{ + struct pm8058_xo_buffer *xo = rdev_get_drvdata(dev); + struct pm8058_chip *chip = dev_get_drvdata(dev->dev.parent); + int rc; + + rc = pm8058_xo_buffer_write(chip, xo->ctrl_addr, XO_ENABLE, + XO_ENABLE_MASK, &xo->ctrl_reg); + if (rc) + pr_err("FAIL: pm8058_xo_buffer_write: rc=%d\n", rc); + + return rc; +} + +static int pm8058_xo_buffer_is_enabled(struct regulator_dev *dev) +{ + struct pm8058_xo_buffer *xo = rdev_get_drvdata(dev); + + if (xo->ctrl_reg & XO_BUFFER_ENABLE) + return 1; + else + return 0; +} + +static int pm8058_xo_buffer_disable(struct regulator_dev *dev) +{ + struct pm8058_xo_buffer *xo = rdev_get_drvdata(dev); + struct pm8058_chip *chip = dev_get_drvdata(dev->dev.parent); + int rc; + + rc = pm8058_xo_buffer_write(chip, xo->ctrl_addr, XO_DISABLE, + XO_ENABLE_MASK, &xo->ctrl_reg); + if (rc) + pr_err("FAIL: pm8058_xo_buffer_write: rc=%d\n", rc); + + return rc; +} + +static struct regulator_ops pm8058_xo_ops = { + .enable = pm8058_xo_buffer_enable, + .disable = pm8058_xo_buffer_disable, + .is_enabled = pm8058_xo_buffer_is_enabled, +}; + +#define VREG_DESCRIP(_id, _name, _ops) \ + [_id] = { \ + .id = _id, \ + .name = _name, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static struct regulator_desc pm8058_xo_buffer_desc[] = { + VREG_DESCRIP(PM8058_XO_ID_A0, "8058_xo_a0", &pm8058_xo_ops), + VREG_DESCRIP(PM8058_XO_ID_A1, "8058_xo_a1", &pm8058_xo_ops), +}; + +static int pm8058_init_xo_buffer(struct pm8058_chip *chip, + struct pm8058_xo_buffer *xo) +{ + int rc; + + /* Save the current control register state */ + rc = pm8058_read(chip, xo->ctrl_addr, &xo->ctrl_reg, 1); + + if (rc) + pr_err("FAIL: pm8058_read: rc=%d\n", rc); + return rc; +} + +static int __devinit pm8058_xo_buffer_probe(struct platform_device *pdev) +{ + struct regulator_desc *rdesc; + struct pm8058_chip *chip; + struct pm8058_xo_buffer *xo; + int rc = 0; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= 0 && pdev->id < PM8058_XO_ID_MAX) { + chip = platform_get_drvdata(pdev); + rdesc = &pm8058_xo_buffer_desc[pdev->id]; + xo = &pm8058_xo_buffer[pdev->id]; + xo->pdata = pdev->dev.platform_data; + + rc = pm8058_init_xo_buffer(chip, xo); + if (rc) + goto bail; + + xo->rdev = regulator_register(rdesc, &pdev->dev, + &xo->pdata->init_data, xo); + if (IS_ERR(xo->rdev)) { + rc = PTR_ERR(xo->rdev); + pr_err("FAIL: regulator_register(%s): rc=%d\n", + pm8058_xo_buffer_desc[pdev->id].name, rc); + } + } else { + rc = -ENODEV; + } + +bail: + if (rc) + pr_err("Error: xo-id=%d, rc=%d\n", pdev->id, rc); + + return rc; +} + +static int __devexit pm8058_xo_buffer_remove(struct platform_device *pdev) +{ + regulator_unregister(pm8058_xo_buffer[pdev->id].rdev); + return 0; +} + +static struct platform_driver pm8058_xo_buffer_driver = { + .probe = pm8058_xo_buffer_probe, + .remove = __devexit_p(pm8058_xo_buffer_remove), + .driver = { + .name = PM8058_XO_BUFFER_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_xo_buffer_init(void) +{ + return platform_driver_register(&pm8058_xo_buffer_driver); +} + +static void __exit pm8058_xo_buffer_exit(void) +{ + platform_driver_unregister(&pm8058_xo_buffer_driver); +} + +subsys_initcall(pm8058_xo_buffer_init); +module_exit(pm8058_xo_buffer_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 XO buffer driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8058_XO_BUFFER_DEV_NAME); diff --git a/drivers/regulator/pm8921-regulator.c b/drivers/regulator/pm8921-regulator.c new file mode 100644 index 00000000000..a2246eb790e --- /dev/null +++ b/drivers/regulator/pm8921-regulator.c @@ -0,0 +1,3274 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Debug Flag Definitions */ +enum { + PM8921_VREG_DEBUG_REQUEST = BIT(0), + PM8921_VREG_DEBUG_DUPLICATE = BIT(1), + PM8921_VREG_DEBUG_INIT = BIT(2), + PM8921_VREG_DEBUG_WRITES = BIT(3), /* SSBI writes */ +}; + +static int pm8921_vreg_debug_mask; +module_param_named( + debug_mask, pm8921_vreg_debug_mask, int, S_IRUSR | S_IWUSR +); + +#define REGULATOR_TYPE_PLDO 0 +#define REGULATOR_TYPE_NLDO 1 +#define REGULATOR_TYPE_NLDO1200 2 +#define REGULATOR_TYPE_SMPS 3 +#define REGULATOR_TYPE_FTSMPS 4 +#define REGULATOR_TYPE_VS 5 +#define REGULATOR_TYPE_VS300 6 +#define REGULATOR_TYPE_NCP 7 + +/* Common Masks */ +#define REGULATOR_ENABLE_MASK 0x80 +#define REGULATOR_ENABLE 0x80 +#define REGULATOR_DISABLE 0x00 + +#define REGULATOR_BANK_MASK 0xF0 +#define REGULATOR_BANK_SEL(n) ((n) << 4) +#define REGULATOR_BANK_WRITE 0x80 + +#define LDO_TEST_BANKS 7 +#define NLDO1200_TEST_BANKS 5 +#define SMPS_TEST_BANKS 8 +#define REGULATOR_TEST_BANKS_MAX SMPS_TEST_BANKS + +/* + * This voltage in uV is returned by get_voltage functions when there is no way + * to determine the current voltage level. It is needed because the regulator + * framework treats a 0 uV voltage as an error. + */ +#define VOLTAGE_UNKNOWN 1 + +/* LDO masks and values */ + +/* CTRL register */ +#define LDO_ENABLE_MASK 0x80 +#define LDO_DISABLE 0x00 +#define LDO_ENABLE 0x80 +#define LDO_PULL_DOWN_ENABLE_MASK 0x40 +#define LDO_PULL_DOWN_ENABLE 0x40 + +#define LDO_CTRL_PM_MASK 0x20 +#define LDO_CTRL_PM_HPM 0x00 +#define LDO_CTRL_PM_LPM 0x20 + +#define LDO_CTRL_VPROG_MASK 0x1F + +/* TEST register bank 0 */ +#define LDO_TEST_LPM_MASK 0x40 +#define LDO_TEST_LPM_SEL_CTRL 0x00 +#define LDO_TEST_LPM_SEL_TCXO 0x40 + +/* TEST register bank 2 */ +#define LDO_TEST_VPROG_UPDATE_MASK 0x08 +#define LDO_TEST_RANGE_SEL_MASK 0x04 +#define LDO_TEST_FINE_STEP_MASK 0x02 +#define LDO_TEST_FINE_STEP_SHIFT 1 + +/* TEST register bank 4 */ +#define LDO_TEST_RANGE_EXT_MASK 0x01 + +/* TEST register bank 5 */ +#define LDO_TEST_PIN_CTRL_MASK 0x0F +#define LDO_TEST_PIN_CTRL_EN3 0x08 +#define LDO_TEST_PIN_CTRL_EN2 0x04 +#define LDO_TEST_PIN_CTRL_EN1 0x02 +#define LDO_TEST_PIN_CTRL_EN0 0x01 + +/* TEST register bank 6 */ +#define LDO_TEST_PIN_CTRL_LPM_MASK 0x0F + + +/* + * If a given voltage could be output by two ranges, then the preferred one must + * be determined by the range limits. Specified voltage ranges should must + * not overlap. + * + * Allowable voltage ranges: + */ +#define PLDO_LOW_UV_MIN 750000 +#define PLDO_LOW_UV_MAX 1487500 +#define PLDO_LOW_UV_FINE_STEP 12500 + +#define PLDO_NORM_UV_MIN 1500000 +#define PLDO_NORM_UV_MAX 3075000 +#define PLDO_NORM_UV_FINE_STEP 25000 + +#define PLDO_HIGH_UV_MIN 1750000 +#define PLDO_HIGH_UV_SET_POINT_MIN 3100000 +#define PLDO_HIGH_UV_MAX 4900000 +#define PLDO_HIGH_UV_FINE_STEP 50000 + +#define PLDO_LOW_SET_POINTS ((PLDO_LOW_UV_MAX - PLDO_LOW_UV_MIN) \ + / PLDO_LOW_UV_FINE_STEP + 1) +#define PLDO_NORM_SET_POINTS ((PLDO_NORM_UV_MAX - PLDO_NORM_UV_MIN) \ + / PLDO_NORM_UV_FINE_STEP + 1) +#define PLDO_HIGH_SET_POINTS ((PLDO_HIGH_UV_MAX \ + - PLDO_HIGH_UV_SET_POINT_MIN) \ + / PLDO_HIGH_UV_FINE_STEP + 1) +#define PLDO_SET_POINTS (PLDO_LOW_SET_POINTS \ + + PLDO_NORM_SET_POINTS \ + + PLDO_HIGH_SET_POINTS) + +#define NLDO_UV_MIN 750000 +#define NLDO_UV_MAX 1537500 +#define NLDO_UV_FINE_STEP 12500 + +#define NLDO_SET_POINTS ((NLDO_UV_MAX - NLDO_UV_MIN) \ + / NLDO_UV_FINE_STEP + 1) + +/* NLDO1200 masks and values */ + +/* CTRL register */ +#define NLDO1200_ENABLE_MASK 0x80 +#define NLDO1200_DISABLE 0x00 +#define NLDO1200_ENABLE 0x80 + +/* Legacy mode */ +#define NLDO1200_LEGACY_PM_MASK 0x20 +#define NLDO1200_LEGACY_PM_HPM 0x00 +#define NLDO1200_LEGACY_PM_LPM 0x20 + +/* Advanced mode */ +#define NLDO1200_CTRL_RANGE_MASK 0x40 +#define NLDO1200_CTRL_RANGE_HIGH 0x00 +#define NLDO1200_CTRL_RANGE_LOW 0x40 +#define NLDO1200_CTRL_VPROG_MASK 0x3F + +#define NLDO1200_LOW_UV_MIN 375000 +#define NLDO1200_LOW_UV_MAX 743750 +#define NLDO1200_LOW_UV_STEP 6250 + +#define NLDO1200_HIGH_UV_MIN 750000 +#define NLDO1200_HIGH_UV_MAX 1537500 +#define NLDO1200_HIGH_UV_STEP 12500 + +#define NLDO1200_LOW_SET_POINTS ((NLDO1200_LOW_UV_MAX \ + - NLDO1200_LOW_UV_MIN) \ + / NLDO1200_LOW_UV_STEP + 1) +#define NLDO1200_HIGH_SET_POINTS ((NLDO1200_HIGH_UV_MAX \ + - NLDO1200_HIGH_UV_MIN) \ + / NLDO1200_HIGH_UV_STEP + 1) +#define NLDO1200_SET_POINTS (NLDO1200_LOW_SET_POINTS \ + + NLDO1200_HIGH_SET_POINTS) + +/* TEST register bank 0 */ +#define NLDO1200_TEST_LPM_MASK 0x04 +#define NLDO1200_TEST_LPM_SEL_CTRL 0x00 +#define NLDO1200_TEST_LPM_SEL_TCXO 0x04 + +/* TEST register bank 1 */ +#define NLDO1200_PULL_DOWN_ENABLE_MASK 0x02 +#define NLDO1200_PULL_DOWN_ENABLE 0x02 + +/* TEST register bank 2 */ +#define NLDO1200_ADVANCED_MODE_MASK 0x08 +#define NLDO1200_ADVANCED_MODE 0x00 +#define NLDO1200_LEGACY_MODE 0x08 + +/* Advanced mode power mode control */ +#define NLDO1200_ADVANCED_PM_MASK 0x02 +#define NLDO1200_ADVANCED_PM_HPM 0x00 +#define NLDO1200_ADVANCED_PM_LPM 0x02 + +#define NLDO1200_IN_ADVANCED_MODE(vreg) \ + ((vreg->test_reg[2] & NLDO1200_ADVANCED_MODE_MASK) \ + == NLDO1200_ADVANCED_MODE) + +/* SMPS masks and values */ + +/* CTRL register */ + +/* Legacy mode */ +#define SMPS_LEGACY_ENABLE_MASK 0x80 +#define SMPS_LEGACY_DISABLE 0x00 +#define SMPS_LEGACY_ENABLE 0x80 +#define SMPS_LEGACY_PULL_DOWN_ENABLE 0x40 +#define SMPS_LEGACY_VREF_SEL_MASK 0x20 +#define SMPS_LEGACY_VPROG_MASK 0x1F + +/* Advanced mode */ +#define SMPS_ADVANCED_BAND_MASK 0xC0 +#define SMPS_ADVANCED_BAND_OFF 0x00 +#define SMPS_ADVANCED_BAND_1 0x40 +#define SMPS_ADVANCED_BAND_2 0x80 +#define SMPS_ADVANCED_BAND_3 0xC0 +#define SMPS_ADVANCED_VPROG_MASK 0x3F + +/* Legacy mode voltage ranges */ +#define SMPS_MODE3_UV_MIN 375000 +#define SMPS_MODE3_UV_MAX 725000 +#define SMPS_MODE3_UV_STEP 25000 + +#define SMPS_MODE2_UV_MIN 750000 +#define SMPS_MODE2_UV_MAX 1475000 +#define SMPS_MODE2_UV_STEP 25000 + +#define SMPS_MODE1_UV_MIN 1500000 +#define SMPS_MODE1_UV_MAX 3050000 +#define SMPS_MODE1_UV_STEP 50000 + +#define SMPS_MODE3_SET_POINTS ((SMPS_MODE3_UV_MAX \ + - SMPS_MODE3_UV_MIN) \ + / SMPS_MODE3_UV_STEP + 1) +#define SMPS_MODE2_SET_POINTS ((SMPS_MODE2_UV_MAX \ + - SMPS_MODE2_UV_MIN) \ + / SMPS_MODE2_UV_STEP + 1) +#define SMPS_MODE1_SET_POINTS ((SMPS_MODE1_UV_MAX \ + - SMPS_MODE1_UV_MIN) \ + / SMPS_MODE1_UV_STEP + 1) +#define SMPS_LEGACY_SET_POINTS (SMPS_MODE3_SET_POINTS \ + + SMPS_MODE2_SET_POINTS \ + + SMPS_MODE1_SET_POINTS) + +/* Advanced mode voltage ranges */ +#define SMPS_BAND1_UV_MIN 375000 +#define SMPS_BAND1_UV_MAX 737500 +#define SMPS_BAND1_UV_STEP 12500 + +#define SMPS_BAND2_UV_MIN 750000 +#define SMPS_BAND2_UV_MAX 1487500 +#define SMPS_BAND2_UV_STEP 12500 + +#define SMPS_BAND3_UV_MIN 1500000 +#define SMPS_BAND3_UV_MAX 3075000 +#define SMPS_BAND3_UV_STEP 25000 + +#define SMPS_BAND1_SET_POINTS ((SMPS_BAND1_UV_MAX \ + - SMPS_BAND1_UV_MIN) \ + / SMPS_BAND1_UV_STEP + 1) +#define SMPS_BAND2_SET_POINTS ((SMPS_BAND2_UV_MAX \ + - SMPS_BAND2_UV_MIN) \ + / SMPS_BAND2_UV_STEP + 1) +#define SMPS_BAND3_SET_POINTS ((SMPS_BAND3_UV_MAX \ + - SMPS_BAND3_UV_MIN) \ + / SMPS_BAND3_UV_STEP + 1) +#define SMPS_ADVANCED_SET_POINTS (SMPS_BAND1_SET_POINTS \ + + SMPS_BAND2_SET_POINTS \ + + SMPS_BAND3_SET_POINTS) + +/* Test2 register bank 1 */ +#define SMPS_LEGACY_VLOW_SEL_MASK 0x01 + +/* Test2 register bank 6 */ +#define SMPS_ADVANCED_PULL_DOWN_ENABLE 0x08 + +/* Test2 register bank 7 */ +#define SMPS_ADVANCED_MODE_MASK 0x02 +#define SMPS_ADVANCED_MODE 0x02 +#define SMPS_LEGACY_MODE 0x00 + +#define SMPS_IN_ADVANCED_MODE(vreg) \ + ((vreg->test_reg[7] & SMPS_ADVANCED_MODE_MASK) == SMPS_ADVANCED_MODE) + +/* BUCK_SLEEP_CNTRL register */ +#define SMPS_PIN_CTRL_MASK 0xF0 +#define SMPS_PIN_CTRL_EN3 0x80 +#define SMPS_PIN_CTRL_EN2 0x40 +#define SMPS_PIN_CTRL_EN1 0x20 +#define SMPS_PIN_CTRL_EN0 0x10 + +#define SMPS_PIN_CTRL_LPM_MASK 0x0F +#define SMPS_PIN_CTRL_LPM_EN3 0x08 +#define SMPS_PIN_CTRL_LPM_EN2 0x04 +#define SMPS_PIN_CTRL_LPM_EN1 0x02 +#define SMPS_PIN_CTRL_LPM_EN0 0x01 + +/* BUCK_CLOCK_CNTRL register */ +#define SMPS_CLK_DIVIDE2 0x40 + +#define SMPS_CLK_CTRL_MASK 0x30 +#define SMPS_CLK_CTRL_FOLLOW_TCXO 0x00 +#define SMPS_CLK_CTRL_PWM 0x10 +#define SMPS_CLK_CTRL_PFM 0x20 + +/* FTSMPS masks and values */ + +/* CTRL register */ +#define FTSMPS_VCTRL_BAND_MASK 0xC0 +#define FTSMPS_VCTRL_BAND_OFF 0x00 +#define FTSMPS_VCTRL_BAND_1 0x40 +#define FTSMPS_VCTRL_BAND_2 0x80 +#define FTSMPS_VCTRL_BAND_3 0xC0 +#define FTSMPS_VCTRL_VPROG_MASK 0x3F + +#define FTSMPS_BAND1_UV_MIN 350000 +#define FTSMPS_BAND1_UV_MAX 650000 +/* 3 LSB's of program voltage must be 0 in band 1. */ +/* Logical step size */ +#define FTSMPS_BAND1_UV_LOG_STEP 50000 +/* Physical step size */ +#define FTSMPS_BAND1_UV_PHYS_STEP 6250 + +#define FTSMPS_BAND2_UV_MIN 700000 +#define FTSMPS_BAND2_UV_MAX 1400000 +#define FTSMPS_BAND2_UV_STEP 12500 + +#define FTSMPS_BAND3_UV_MIN 1400000 +#define FTSMPS_BAND3_UV_SET_POINT_MIN 1500000 +#define FTSMPS_BAND3_UV_MAX 3300000 +#define FTSMPS_BAND3_UV_STEP 50000 + +#define FTSMPS_BAND1_SET_POINTS ((FTSMPS_BAND1_UV_MAX \ + - FTSMPS_BAND1_UV_MIN) \ + / FTSMPS_BAND1_UV_LOG_STEP + 1) +#define FTSMPS_BAND2_SET_POINTS ((FTSMPS_BAND2_UV_MAX \ + - FTSMPS_BAND2_UV_MIN) \ + / FTSMPS_BAND2_UV_STEP + 1) +#define FTSMPS_BAND3_SET_POINTS ((FTSMPS_BAND3_UV_MAX \ + - FTSMPS_BAND3_UV_SET_POINT_MIN) \ + / FTSMPS_BAND3_UV_STEP + 1) +#define FTSMPS_SET_POINTS (FTSMPS_BAND1_SET_POINTS \ + + FTSMPS_BAND2_SET_POINTS \ + + FTSMPS_BAND3_SET_POINTS) + +/* FTS_CNFG1 register bank 0 */ +#define FTSMPS_CNFG1_PM_MASK 0x0C +#define FTSMPS_CNFG1_PM_PWM 0x00 +#define FTSMPS_CNFG1_PM_PFM 0x08 + +/* PWR_CNFG register */ +#define FTSMPS_PULL_DOWN_ENABLE_MASK 0x40 +#define FTSMPS_PULL_DOWN_ENABLE 0x40 + +/* VS masks and values */ + +/* CTRL register */ +#define VS_ENABLE_MASK 0x80 +#define VS_DISABLE 0x00 +#define VS_ENABLE 0x80 +#define VS_PULL_DOWN_ENABLE_MASK 0x40 +#define VS_PULL_DOWN_ENABLE 0x40 + +#define VS_PIN_CTRL_MASK 0x0F +#define VS_PIN_CTRL_EN0 0x08 +#define VS_PIN_CTRL_EN1 0x04 +#define VS_PIN_CTRL_EN2 0x02 +#define VS_PIN_CTRL_EN3 0x01 + +/* VS300 masks and values */ + +/* CTRL register */ +#define VS300_CTRL_ENABLE_MASK 0xC0 +#define VS300_CTRL_DISABLE 0x00 +#define VS300_CTRL_ENABLE 0x40 + +#define VS300_PULL_DOWN_ENABLE_MASK 0x20 +#define VS300_PULL_DOWN_ENABLE 0x20 + +/* NCP masks and values */ + +/* CTRL register */ +#define NCP_ENABLE_MASK 0x80 +#define NCP_DISABLE 0x00 +#define NCP_ENABLE 0x80 +#define NCP_VPROG_MASK 0x1F + +#define NCP_UV_MIN 1500000 +#define NCP_UV_MAX 3050000 +#define NCP_UV_STEP 50000 + +#define NCP_SET_POINTS ((NCP_UV_MAX - NCP_UV_MIN) \ + / NCP_UV_STEP + 1) + +#define IS_REAL_REGULATOR(id) ((id) >= 0 && \ + (id) < PM8921_VREG_ID_L1_PC) + +struct pm8921_vreg { + /* Configuration data */ + struct regulator_dev *rdev; + struct regulator_dev *rdev_pc; + struct device *dev; + struct device *dev_pc; + const char *name; + struct pm8921_regulator_platform_data pdata; + const int hpm_min_load; + const u16 ctrl_addr; + const u16 test_addr; + const u16 clk_ctrl_addr; + const u16 sleep_ctrl_addr; + const u16 pfm_ctrl_addr; + const u16 pwr_cnfg_addr; + const u8 type; + /* State data */ + struct mutex pc_lock; + int save_uV; + int mode; + u32 write_count; + u32 prev_write_count; + bool is_enabled; + bool is_enabled_pc; + u8 test_reg[REGULATOR_TEST_BANKS_MAX]; + u8 ctrl_reg; + u8 clk_ctrl_reg; + u8 sleep_ctrl_reg; + u8 pfm_ctrl_reg; + u8 pwr_cnfg_reg; +}; + +#define vreg_err(vreg, fmt, ...) \ + pr_err("%s: " fmt, vreg->name, ##__VA_ARGS__) + +#define PLDO(_id, _ctrl_addr, _test_addr, _hpm_min_load) \ + [PM8921_VREG_ID_##_id] = { \ + .type = REGULATOR_TYPE_PLDO, \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .hpm_min_load = PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + } + +#define NLDO(_id, _ctrl_addr, _test_addr, _hpm_min_load) \ + [PM8921_VREG_ID_##_id] = { \ + .type = REGULATOR_TYPE_NLDO, \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .hpm_min_load = PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + } + +#define NLDO1200(_id, _ctrl_addr, _test_addr, _hpm_min_load) \ + [PM8921_VREG_ID_##_id] = { \ + .type = REGULATOR_TYPE_NLDO1200, \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .hpm_min_load = PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + } + +#define SMPS(_id, _ctrl_addr, _test_addr, _clk_ctrl_addr, _sleep_ctrl_addr, \ + _hpm_min_load) \ + [PM8921_VREG_ID_##_id] = { \ + .type = REGULATOR_TYPE_SMPS, \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .clk_ctrl_addr = _clk_ctrl_addr, \ + .sleep_ctrl_addr = _sleep_ctrl_addr, \ + .hpm_min_load = PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + } + +#define FTSMPS(_id, _pwm_ctrl_addr, _fts_cnfg1_addr, _pfm_ctrl_addr, \ + _pwr_cnfg_addr, _hpm_min_load) \ + [PM8921_VREG_ID_##_id] = { \ + .type = REGULATOR_TYPE_FTSMPS, \ + .ctrl_addr = _pwm_ctrl_addr, \ + .test_addr = _fts_cnfg1_addr, \ + .pfm_ctrl_addr = _pfm_ctrl_addr, \ + .pwr_cnfg_addr = _pwr_cnfg_addr, \ + .hpm_min_load = PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + } + +#define VS(_id, _ctrl_addr) \ + [PM8921_VREG_ID_##_id] = { \ + .type = REGULATOR_TYPE_VS, \ + .ctrl_addr = _ctrl_addr, \ + } + +#define VS300(_id, _ctrl_addr) \ + [PM8921_VREG_ID_##_id] = { \ + .type = REGULATOR_TYPE_VS300, \ + .ctrl_addr = _ctrl_addr, \ + } + +#define NCP(_id, _ctrl_addr) \ + [PM8921_VREG_ID_##_id] = { \ + .type = REGULATOR_TYPE_NCP, \ + .ctrl_addr = _ctrl_addr, \ + } + +static struct pm8921_vreg pm8921_vreg[] = { + /* id ctrl test hpm_min */ + NLDO(L1, 0x0AE, 0x0AF, LDO_150), + NLDO(L2, 0x0B0, 0x0B1, LDO_150), + PLDO(L3, 0x0B2, 0x0B3, LDO_150), + PLDO(L4, 0x0B4, 0x0B5, LDO_50), + PLDO(L5, 0x0B6, 0x0B7, LDO_300), + PLDO(L6, 0x0B8, 0x0B9, LDO_600), + PLDO(L7, 0x0BA, 0x0BB, LDO_150), + PLDO(L8, 0x0BC, 0x0BD, LDO_300), + PLDO(L9, 0x0BE, 0x0BF, LDO_300), + PLDO(L10, 0x0C0, 0x0C1, LDO_600), + PLDO(L11, 0x0C2, 0x0C3, LDO_150), + NLDO(L12, 0x0C4, 0x0C5, LDO_150), + PLDO(L14, 0x0C8, 0x0C9, LDO_50), + PLDO(L15, 0x0CA, 0x0CB, LDO_150), + PLDO(L16, 0x0CC, 0x0CD, LDO_300), + PLDO(L17, 0x0CE, 0x0CF, LDO_150), + NLDO(L18, 0x0D0, 0x0D1, LDO_150), + PLDO(L21, 0x0D6, 0x0D7, LDO_150), + PLDO(L22, 0x0D8, 0x0D9, LDO_150), + PLDO(L23, 0x0DA, 0x0DB, LDO_150), + + /* id ctrl test hpm_min */ + NLDO1200(L24, 0x0DC, 0x0DD, LDO_1200), + NLDO1200(L25, 0x0DE, 0x0DF, LDO_1200), + NLDO1200(L26, 0x0E0, 0x0E1, LDO_1200), + NLDO1200(L27, 0x0E2, 0x0E3, LDO_1200), + NLDO1200(L28, 0x0E4, 0x0E5, LDO_1200), + + /* id ctrl test hpm_min */ + PLDO(L29, 0x0E6, 0x0E7, LDO_150), + + /* id ctrl test2 clk sleep hpm_min */ + SMPS(S1, 0x1D0, 0x1D5, 0x009, 0x1D2, SMPS_1500), + SMPS(S2, 0x1D8, 0x1DD, 0x00A, 0x1DA, SMPS_1500), + SMPS(S3, 0x1E0, 0x1E5, 0x00B, 0x1E2, SMPS_1500), + SMPS(S4, 0x1E8, 0x1ED, 0x011, 0x1EA, SMPS_1500), + + /* id ctrl fts_cnfg1 pfm pwr_cnfg hpm_min */ + FTSMPS(S5, 0x025, 0x02E, 0x026, 0x032, SMPS_2000), + FTSMPS(S6, 0x036, 0x03F, 0x037, 0x043, SMPS_2000), + + /* id ctrl test2 clk sleep hpm_min */ + SMPS(S7, 0x1F0, 0x1F5, 0x012, 0x1F2, SMPS_1500), + SMPS(S8, 0x1F8, 0x1FD, 0x013, 0x1FA, SMPS_1500), + + /* id ctrl */ + VS(LVS1, 0x060), + VS300(LVS2, 0x062), + VS(LVS3, 0x064), + VS(LVS4, 0x066), + VS(LVS5, 0x068), + VS(LVS6, 0x06A), + VS(LVS7, 0x06C), + VS300(USB_OTG, 0x06E), + VS300(HDMI_MVS, 0x070), + + /* id ctrl */ + NCP(NCP, 0x090), +}; + +/* Determines which label to add to the print. */ +enum pm8921_regulator_action { + PM8921_REGULATOR_ACTION_INIT, + PM8921_REGULATOR_ACTION_ENABLE, + PM8921_REGULATOR_ACTION_DISABLE, + PM8921_REGULATOR_ACTION_VOLTAGE, + PM8921_REGULATOR_ACTION_MODE, + PM8921_REGULATOR_ACTION_PIN_CTRL, +}; + +/* Debug state printing */ +static void pm8921_vreg_show_state(struct regulator_dev *rdev, + enum pm8921_regulator_action action); + +/* + * Perform a masked write to a PMIC register only if the new value differs + * from the last value written to the register. This removes redundant + * register writing. + * + * No locking is required because registers are not shared between regulators. + */ +static int pm8921_vreg_masked_write(struct pm8921_vreg *vreg, u16 addr, u8 val, + u8 mask, u8 *reg_save) +{ + int rc = 0; + u8 reg; + + reg = (*reg_save & ~mask) | (val & mask); + if (reg != *reg_save) { + rc = pm8xxx_writeb(vreg->dev->parent, addr, reg); + + if (rc) { + pr_err("%s: pm8xxx_writeb failed; addr=0x%03X, rc=%d\n", + vreg->name, addr, rc); + } else { + *reg_save = reg; + vreg->write_count++; + if (pm8921_vreg_debug_mask & PM8921_VREG_DEBUG_WRITES) + pr_info("%s: write(0x%03X)=0x%02X", vreg->name, + addr, reg); + } + } + + return rc; +} + +/* + * Perform a masked write to a PMIC register without checking the previously + * written value. This is needed for registers that must be rewritten even if + * the value hasn't changed in order for changes in other registers to take + * effect. + */ +static int pm8921_vreg_masked_write_forced(struct pm8921_vreg *vreg, u16 addr, + u8 val, u8 mask, u8 *reg_save) +{ + int rc = 0; + u8 reg; + + reg = (*reg_save & ~mask) | (val & mask); + rc = pm8xxx_writeb(vreg->dev->parent, addr, reg); + + if (rc) { + pr_err("%s: pm8xxx_writeb failed; addr=0x%03X, rc=%d\n", + vreg->name, addr, rc); + } else { + *reg_save = reg; + vreg->write_count++; + if (pm8921_vreg_debug_mask & PM8921_VREG_DEBUG_WRITES) + pr_info("%s: write(0x%03X)=0x%02X", vreg->name, + addr, reg); + } + + return rc; +} + +static int pm8921_vreg_is_pin_controlled(struct pm8921_vreg *vreg) +{ + int ret = 0; + + switch (vreg->type) { + case REGULATOR_TYPE_PLDO: + case REGULATOR_TYPE_NLDO: + ret = ((vreg->test_reg[5] & LDO_TEST_PIN_CTRL_MASK) << 4) + | (vreg->test_reg[6] & LDO_TEST_PIN_CTRL_LPM_MASK); + break; + case REGULATOR_TYPE_SMPS: + ret = vreg->sleep_ctrl_reg + & (SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK); + break; + case REGULATOR_TYPE_VS: + ret = vreg->ctrl_reg & VS_PIN_CTRL_MASK; + break; + } + + return ret; +} + +/* + * Returns the logical pin control enable state because the pin control options + * present in the hardware out of restart could be different from those desired + * by the consumer. + */ +static int pm8921_vreg_pin_control_is_enabled(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int enabled; + + mutex_lock(&vreg->pc_lock); + enabled = vreg->is_enabled_pc; + mutex_unlock(&vreg->pc_lock); + + return enabled; +} + +/* Returns the physical enable state of the regulator. */ +static int _pm8921_vreg_is_enabled(struct pm8921_vreg *vreg) +{ + int rc = 0; + + /* + * All regulator types except advanced mode SMPS, FTSMPS, and VS300 have + * enable bit in bit 7 of the control register. + */ + switch (vreg->type) { + case REGULATOR_TYPE_FTSMPS: + if ((vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK) + != FTSMPS_VCTRL_BAND_OFF) + rc = 1; + break; + case REGULATOR_TYPE_VS300: + if ((vreg->ctrl_reg & VS300_CTRL_ENABLE_MASK) + != VS300_CTRL_DISABLE) + rc = 1; + break; + case REGULATOR_TYPE_SMPS: + if (SMPS_IN_ADVANCED_MODE(vreg)) { + if ((vreg->ctrl_reg & SMPS_ADVANCED_BAND_MASK) + != SMPS_ADVANCED_BAND_OFF) + rc = 1; + break; + } + /* Fall through for legacy mode SMPS. */ + default: + if ((vreg->ctrl_reg & REGULATOR_ENABLE_MASK) + == REGULATOR_ENABLE) + rc = 1; + } + + return rc; +} + +/* + * Returns the logical enable state of the regulator which may be different from + * the physical enable state thanks to HPM/LPM pin control. + */ +static int pm8921_vreg_is_enabled(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int enabled; + + if (vreg->type == REGULATOR_TYPE_PLDO + || vreg->type == REGULATOR_TYPE_NLDO + || vreg->type == REGULATOR_TYPE_SMPS + || vreg->type == REGULATOR_TYPE_VS) { + /* Pin controllable */ + mutex_lock(&vreg->pc_lock); + enabled = vreg->is_enabled; + mutex_unlock(&vreg->pc_lock); + } else { + /* Not pin controlable */ + enabled = _pm8921_vreg_is_enabled(vreg); + } + + return enabled; +} + +static int pm8921_pldo_get_voltage(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int vmin, fine_step; + u8 range_ext, range_sel, vprog, fine_step_reg; + + mutex_lock(&vreg->pc_lock); + + fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK; + range_sel = vreg->test_reg[2] & LDO_TEST_RANGE_SEL_MASK; + range_ext = vreg->test_reg[4] & LDO_TEST_RANGE_EXT_MASK; + vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK; + + mutex_unlock(&vreg->pc_lock); + + vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT); + + if (range_sel) { + /* low range mode */ + fine_step = PLDO_LOW_UV_FINE_STEP; + vmin = PLDO_LOW_UV_MIN; + } else if (!range_ext) { + /* normal mode */ + fine_step = PLDO_NORM_UV_FINE_STEP; + vmin = PLDO_NORM_UV_MIN; + } else { + /* high range mode */ + fine_step = PLDO_HIGH_UV_FINE_STEP; + vmin = PLDO_HIGH_UV_MIN; + } + + return fine_step * vprog + vmin; +} + +static int pm8921_pldo_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + int uV; + + if (selector >= PLDO_SET_POINTS) + return 0; + + if (selector < PLDO_LOW_SET_POINTS) + uV = selector * PLDO_LOW_UV_FINE_STEP + PLDO_LOW_UV_MIN; + else if (selector < (PLDO_LOW_SET_POINTS + PLDO_NORM_SET_POINTS)) + uV = (selector - PLDO_LOW_SET_POINTS) * PLDO_NORM_UV_FINE_STEP + + PLDO_NORM_UV_MIN; + else + uV = (selector - PLDO_LOW_SET_POINTS - PLDO_NORM_SET_POINTS) + * PLDO_HIGH_UV_FINE_STEP + + PLDO_HIGH_UV_SET_POINT_MIN; + + return uV; +} + +static int pm8921_pldo_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0, uV = min_uV; + int vmin; + unsigned vprog, fine_step; + u8 range_ext, range_sel, fine_step_reg, prev_reg; + bool reg_changed = false; + + if (uV < PLDO_LOW_UV_MIN && max_uV >= PLDO_LOW_UV_MIN) + uV = PLDO_LOW_UV_MIN; + + if (uV < PLDO_LOW_UV_MIN || uV > PLDO_HIGH_UV_MAX) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, PLDO_LOW_UV_MIN, PLDO_HIGH_UV_MAX); + return -EINVAL; + } + + if (uV > PLDO_NORM_UV_MAX) { + vmin = PLDO_HIGH_UV_MIN; + fine_step = PLDO_HIGH_UV_FINE_STEP; + range_ext = LDO_TEST_RANGE_EXT_MASK; + range_sel = 0; + } else if (uV > PLDO_LOW_UV_MAX) { + vmin = PLDO_NORM_UV_MIN; + fine_step = PLDO_NORM_UV_FINE_STEP; + range_ext = 0; + range_sel = 0; + } else { + vmin = PLDO_LOW_UV_MIN; + fine_step = PLDO_LOW_UV_FINE_STEP; + range_ext = 0; + range_sel = LDO_TEST_RANGE_SEL_MASK; + } + + vprog = (uV - vmin + fine_step - 1) / fine_step; + uV = vprog * fine_step + vmin; + fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT; + vprog >>= 1; + + if (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point\n", + min_uV, max_uV); + return -EINVAL; + } + + mutex_lock(&vreg->pc_lock); + + /* Write fine step, range select and program voltage update. */ + prev_reg = vreg->test_reg[2]; + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + fine_step_reg | range_sel | REGULATOR_BANK_SEL(2) + | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK, + LDO_TEST_FINE_STEP_MASK | LDO_TEST_RANGE_SEL_MASK + | REGULATOR_BANK_MASK | LDO_TEST_VPROG_UPDATE_MASK, + &vreg->test_reg[2]); + if (rc) + goto bail; + if (prev_reg != vreg->test_reg[2]) + reg_changed = true; + + /* Write range extension. */ + prev_reg = vreg->test_reg[4]; + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + range_ext | REGULATOR_BANK_SEL(4) + | REGULATOR_BANK_WRITE, + LDO_TEST_RANGE_EXT_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[4]); + if (rc) + goto bail; + if (prev_reg != vreg->test_reg[4]) + reg_changed = true; + + /* Write new voltage. */ + if (reg_changed) { + /* + * Force a CTRL register write even if the value hasn't changed. + * This is neccessary because range select, range extension, and + * fine step will not update until a value is written into the + * control register. + */ + rc = pm8921_vreg_masked_write_forced(vreg, vreg->ctrl_addr, + vprog, LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + } else { + /* Only write to control register if new value is different. */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, vprog, + LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + } +bail: + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int pm8921_nldo_get_voltage(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + u8 vprog, fine_step_reg; + + mutex_lock(&vreg->pc_lock); + + fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK; + vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK; + + mutex_unlock(&vreg->pc_lock); + + vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT); + + return NLDO_UV_FINE_STEP * vprog + NLDO_UV_MIN; +} + +static int pm8921_nldo_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector >= NLDO_SET_POINTS) + return 0; + + return selector * NLDO_UV_FINE_STEP + NLDO_UV_MIN; +} + +static int pm8921_nldo_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + unsigned vprog, fine_step_reg, prev_reg; + int rc; + int uV = min_uV; + + if (uV < NLDO_UV_MIN && max_uV >= NLDO_UV_MIN) + uV = NLDO_UV_MIN; + + if (uV < NLDO_UV_MIN || uV > NLDO_UV_MAX) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, NLDO_UV_MIN, NLDO_UV_MAX); + return -EINVAL; + } + + vprog = (uV - NLDO_UV_MIN + NLDO_UV_FINE_STEP - 1) / NLDO_UV_FINE_STEP; + uV = vprog * NLDO_UV_FINE_STEP + NLDO_UV_MIN; + fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT; + vprog >>= 1; + + if (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point\n", + min_uV, max_uV); + return -EINVAL; + } + + mutex_lock(&vreg->pc_lock); + + /* Write fine step. */ + prev_reg = vreg->test_reg[2]; + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + fine_step_reg | REGULATOR_BANK_SEL(2) + | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK, + LDO_TEST_FINE_STEP_MASK | REGULATOR_BANK_MASK + | LDO_TEST_VPROG_UPDATE_MASK, + &vreg->test_reg[2]); + if (rc) + goto bail; + + /* Write new voltage. */ + if (prev_reg != vreg->test_reg[2]) { + /* + * Force a CTRL register write even if the value hasn't changed. + * This is neccessary because fine step will not update until a + * value is written into the control register. + */ + rc = pm8921_vreg_masked_write_forced(vreg, vreg->ctrl_addr, + vprog, LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + } else { + /* Only write to control register if new value is different. */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, vprog, + LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + } +bail: + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int _pm8921_nldo1200_get_voltage(struct pm8921_vreg *vreg) +{ + int uV = 0; + int vprog; + + if (!NLDO1200_IN_ADVANCED_MODE(vreg)) { + pr_warn("%s: currently in legacy mode; voltage unknown.\n", + vreg->name); + return vreg->save_uV; + } + + vprog = vreg->ctrl_reg & NLDO1200_CTRL_VPROG_MASK; + + if ((vreg->ctrl_reg & NLDO1200_CTRL_RANGE_MASK) + == NLDO1200_CTRL_RANGE_LOW) + uV = vprog * NLDO1200_LOW_UV_STEP + NLDO1200_LOW_UV_MIN; + else + uV = vprog * NLDO1200_HIGH_UV_STEP + NLDO1200_HIGH_UV_MIN; + + return uV; +} + +static int pm8921_nldo1200_get_voltage(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + + return _pm8921_nldo1200_get_voltage(vreg); +} + +static int pm8921_nldo1200_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + int uV; + + if (selector >= NLDO1200_SET_POINTS) + return 0; + + if (selector < NLDO1200_LOW_SET_POINTS) + uV = selector * NLDO1200_LOW_UV_STEP + NLDO1200_LOW_UV_MIN; + else + uV = (selector - NLDO1200_LOW_SET_POINTS) + * NLDO1200_HIGH_UV_STEP + + NLDO1200_HIGH_UV_MIN; + + return uV; +} + +static int _pm8921_nldo1200_set_voltage(struct pm8921_vreg *vreg, int min_uV, + int max_uV) +{ + u8 vprog, range; + int rc; + int uV = min_uV; + + if (uV < NLDO1200_LOW_UV_MIN && max_uV >= NLDO1200_LOW_UV_MIN) + uV = NLDO1200_LOW_UV_MIN; + + if (uV < NLDO1200_LOW_UV_MIN || uV > NLDO1200_HIGH_UV_MAX) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, NLDO_UV_MIN, NLDO_UV_MAX); + return -EINVAL; + } + + if (uV > NLDO1200_LOW_UV_MAX) { + vprog = (uV - NLDO1200_HIGH_UV_MIN + NLDO1200_HIGH_UV_STEP - 1) + / NLDO1200_HIGH_UV_STEP; + uV = vprog * NLDO1200_HIGH_UV_STEP + NLDO1200_HIGH_UV_MIN; + vprog &= NLDO1200_CTRL_VPROG_MASK; + range = NLDO1200_CTRL_RANGE_HIGH; + } else { + vprog = (uV - NLDO1200_LOW_UV_MIN + NLDO1200_LOW_UV_STEP - 1) + / NLDO1200_LOW_UV_STEP; + uV = vprog * NLDO1200_LOW_UV_STEP + NLDO1200_LOW_UV_MIN; + vprog &= NLDO1200_CTRL_VPROG_MASK; + range = NLDO1200_CTRL_RANGE_LOW; + } + + if (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point\n", + min_uV, max_uV); + return -EINVAL; + } + + /* Set to advanced mode */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + NLDO1200_ADVANCED_MODE | REGULATOR_BANK_SEL(2) + | REGULATOR_BANK_WRITE, NLDO1200_ADVANCED_MODE_MASK + | REGULATOR_BANK_MASK, &vreg->test_reg[2]); + if (rc) + goto bail; + + /* Set voltage and range selection. */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, vprog | range, + NLDO1200_CTRL_VPROG_MASK | NLDO1200_CTRL_RANGE_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + + vreg->save_uV = uV; + +bail: + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8921_nldo1200_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = _pm8921_nldo1200_set_voltage(vreg, min_uV, max_uV); + + if (!rc) + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int pm8921_smps_get_voltage_advanced(struct pm8921_vreg *vreg) +{ + u8 vprog, band; + int uV = 0; + + vprog = vreg->ctrl_reg & SMPS_ADVANCED_VPROG_MASK; + band = vreg->ctrl_reg & SMPS_ADVANCED_BAND_MASK; + + if (band == SMPS_ADVANCED_BAND_1) + uV = vprog * SMPS_BAND1_UV_STEP + SMPS_BAND1_UV_MIN; + else if (band == SMPS_ADVANCED_BAND_2) + uV = vprog * SMPS_BAND2_UV_STEP + SMPS_BAND2_UV_MIN; + else if (band == SMPS_ADVANCED_BAND_3) + uV = vprog * SMPS_BAND3_UV_STEP + SMPS_BAND3_UV_MIN; + else if (vreg->save_uV > 0) + uV = vreg->save_uV; + else + uV = VOLTAGE_UNKNOWN; + + return uV; +} + +static int pm8921_smps_get_voltage_legacy(struct pm8921_vreg *vreg) +{ + u8 vlow, vref, vprog; + int uV; + + vlow = vreg->test_reg[1] & SMPS_LEGACY_VLOW_SEL_MASK; + vref = vreg->ctrl_reg & SMPS_LEGACY_VREF_SEL_MASK; + vprog = vreg->ctrl_reg & SMPS_LEGACY_VPROG_MASK; + + if (vlow && vref) { + /* mode 3 */ + uV = vprog * SMPS_MODE3_UV_STEP + SMPS_MODE3_UV_MIN; + } else if (vref) { + /* mode 2 */ + uV = vprog * SMPS_MODE2_UV_STEP + SMPS_MODE2_UV_MIN; + } else { + /* mode 1 */ + uV = vprog * SMPS_MODE1_UV_STEP + SMPS_MODE1_UV_MIN; + } + + return uV; +} + +static int _pm8921_smps_get_voltage(struct pm8921_vreg *vreg) +{ + if (SMPS_IN_ADVANCED_MODE(vreg)) + return pm8921_smps_get_voltage_advanced(vreg); + + return pm8921_smps_get_voltage_legacy(vreg); +} + +static int pm8921_smps_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + int uV; + + if (selector >= SMPS_ADVANCED_SET_POINTS) + return 0; + + if (selector < SMPS_BAND1_SET_POINTS) + uV = selector * SMPS_BAND1_UV_STEP + SMPS_BAND1_UV_MIN; + else if (selector < (SMPS_BAND1_SET_POINTS + SMPS_BAND2_SET_POINTS)) + uV = (selector - SMPS_BAND1_SET_POINTS) * SMPS_BAND2_UV_STEP + + SMPS_BAND2_UV_MIN; + else + uV = (selector - SMPS_BAND1_SET_POINTS - SMPS_BAND2_SET_POINTS) + * SMPS_BAND3_UV_STEP + + SMPS_BAND3_UV_MIN; + + return uV; +} + +static int pm8921_smps_get_voltage(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int uV; + + mutex_lock(&vreg->pc_lock); + uV = _pm8921_smps_get_voltage(vreg); + mutex_unlock(&vreg->pc_lock); + + return uV; +} + +static int pm8921_smps_set_voltage_advanced(struct pm8921_vreg *vreg, + int min_uV, int max_uV, int force_on) +{ + u8 vprog, band; + int rc; + int uV = min_uV; + + if (uV < SMPS_BAND1_UV_MIN && max_uV >= SMPS_BAND1_UV_MIN) + uV = SMPS_BAND1_UV_MIN; + + if (uV < SMPS_BAND1_UV_MIN || uV > SMPS_BAND3_UV_MAX) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, SMPS_BAND1_UV_MIN, SMPS_BAND3_UV_MAX); + return -EINVAL; + } + + if (uV > SMPS_BAND2_UV_MAX) { + vprog = (uV - SMPS_BAND3_UV_MIN + SMPS_BAND3_UV_STEP - 1) + / SMPS_BAND3_UV_STEP; + band = SMPS_ADVANCED_BAND_3; + uV = SMPS_BAND3_UV_MIN + vprog * SMPS_BAND3_UV_STEP; + } else if (uV > SMPS_BAND1_UV_MAX) { + vprog = (uV - SMPS_BAND2_UV_MIN + SMPS_BAND2_UV_STEP - 1) + / SMPS_BAND2_UV_STEP; + band = SMPS_ADVANCED_BAND_2; + uV = SMPS_BAND2_UV_MIN + vprog * SMPS_BAND2_UV_STEP; + } else { + vprog = (uV - SMPS_BAND1_UV_MIN + SMPS_BAND1_UV_STEP - 1) + / SMPS_BAND1_UV_STEP; + band = SMPS_ADVANCED_BAND_1; + uV = SMPS_BAND1_UV_MIN + vprog * SMPS_BAND1_UV_STEP; + } + + if (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point\n", + min_uV, max_uV); + return -EINVAL; + } + + /* Do not set band if regulator currently disabled. */ + if (!_pm8921_vreg_is_enabled(vreg) && !force_on) + band = SMPS_ADVANCED_BAND_OFF; + + /* Set advanced mode bit to 1. */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, SMPS_ADVANCED_MODE + | REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(7), + SMPS_ADVANCED_MODE_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[7]); + if (rc) + goto bail; + + /* Set voltage and voltage band. */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, band | vprog, + SMPS_ADVANCED_BAND_MASK | SMPS_ADVANCED_VPROG_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + + vreg->save_uV = uV; + +bail: + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8921_smps_set_voltage_legacy(struct pm8921_vreg *vreg, int min_uV, + int max_uV) +{ + u8 vlow, vref, vprog, pd, en; + int rc; + int uV = min_uV; + + + if (uV < SMPS_MODE3_UV_MIN && max_uV >= SMPS_MODE3_UV_MIN) + uV = SMPS_MODE3_UV_MIN; + + if (uV < SMPS_MODE3_UV_MIN || uV > SMPS_MODE1_UV_MAX) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, SMPS_MODE3_UV_MIN, SMPS_MODE1_UV_MAX); + return -EINVAL; + } + + if (uV > SMPS_MODE2_UV_MAX) { + vprog = (uV - SMPS_MODE1_UV_MIN + SMPS_MODE1_UV_STEP - 1) + / SMPS_MODE1_UV_STEP; + vref = 0; + vlow = 0; + uV = SMPS_MODE1_UV_MIN + vprog * SMPS_MODE1_UV_STEP; + } else if (uV > SMPS_MODE3_UV_MAX) { + vprog = (uV - SMPS_MODE2_UV_MIN + SMPS_MODE2_UV_STEP - 1) + / SMPS_MODE2_UV_STEP; + vref = SMPS_LEGACY_VREF_SEL_MASK; + vlow = 0; + uV = SMPS_MODE2_UV_MIN + vprog * SMPS_MODE2_UV_STEP; + } else { + vprog = (uV - SMPS_MODE3_UV_MIN + SMPS_MODE3_UV_STEP - 1) + / SMPS_MODE3_UV_STEP; + vref = SMPS_LEGACY_VREF_SEL_MASK; + vlow = SMPS_LEGACY_VLOW_SEL_MASK; + uV = SMPS_MODE3_UV_MIN + vprog * SMPS_MODE3_UV_STEP; + } + + if (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point\n", + min_uV, max_uV); + return -EINVAL; + } + + /* set vlow bit for ultra low voltage mode */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + vlow | REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(1), + REGULATOR_BANK_MASK | SMPS_LEGACY_VLOW_SEL_MASK, + &vreg->test_reg[1]); + if (rc) + goto bail; + + /* Set advanced mode bit to 0. */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, SMPS_LEGACY_MODE + | REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(7), + SMPS_ADVANCED_MODE_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[7]); + if (rc) + goto bail; + + en = (_pm8921_vreg_is_enabled(vreg) ? SMPS_LEGACY_ENABLE : 0); + pd = (vreg->pdata.pull_down_enable ? SMPS_LEGACY_PULL_DOWN_ENABLE : 0); + + /* Set voltage (and the rest of the control register). */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + en | pd | vref | vprog, + SMPS_LEGACY_ENABLE_MASK | SMPS_LEGACY_PULL_DOWN_ENABLE + | SMPS_LEGACY_VREF_SEL_MASK | SMPS_LEGACY_VPROG_MASK, + &vreg->ctrl_reg); + + vreg->save_uV = uV; + +bail: + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8921_smps_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + + mutex_lock(&vreg->pc_lock); + + if (SMPS_IN_ADVANCED_MODE(vreg) || !pm8921_vreg_is_pin_controlled(vreg)) + rc = pm8921_smps_set_voltage_advanced(vreg, min_uV, max_uV, 0); + else + rc = pm8921_smps_set_voltage_legacy(vreg, min_uV, max_uV); + + mutex_unlock(&vreg->pc_lock); + + if (!rc) + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int _pm8921_ftsmps_get_voltage(struct pm8921_vreg *vreg) +{ + u8 vprog, band; + int uV = 0; + + if ((vreg->test_reg[0] & FTSMPS_CNFG1_PM_MASK) == FTSMPS_CNFG1_PM_PFM) { + vprog = vreg->pfm_ctrl_reg & FTSMPS_VCTRL_VPROG_MASK; + band = vreg->pfm_ctrl_reg & FTSMPS_VCTRL_BAND_MASK; + if (band == FTSMPS_VCTRL_BAND_OFF && vprog == 0) { + /* PWM_VCTRL overrides PFM_VCTRL */ + vprog = vreg->ctrl_reg & FTSMPS_VCTRL_VPROG_MASK; + band = vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK; + } + } else { + vprog = vreg->ctrl_reg & FTSMPS_VCTRL_VPROG_MASK; + band = vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK; + } + + if (band == FTSMPS_VCTRL_BAND_1) + uV = vprog * FTSMPS_BAND1_UV_PHYS_STEP + FTSMPS_BAND1_UV_MIN; + else if (band == FTSMPS_VCTRL_BAND_2) + uV = vprog * FTSMPS_BAND2_UV_STEP + FTSMPS_BAND2_UV_MIN; + else if (band == FTSMPS_VCTRL_BAND_3) + uV = vprog * FTSMPS_BAND3_UV_STEP + FTSMPS_BAND3_UV_MIN; + else if (vreg->save_uV > 0) + uV = vreg->save_uV; + else + uV = VOLTAGE_UNKNOWN; + + return uV; +} + +static int pm8921_ftsmps_get_voltage(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + + return _pm8921_ftsmps_get_voltage(vreg); +} + +static int pm8921_ftsmps_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + int uV; + + if (selector >= FTSMPS_SET_POINTS) + return 0; + + if (selector < FTSMPS_BAND1_SET_POINTS) + uV = selector * FTSMPS_BAND1_UV_LOG_STEP + FTSMPS_BAND1_UV_MIN; + else if (selector < (FTSMPS_BAND1_SET_POINTS + FTSMPS_BAND2_SET_POINTS)) + uV = (selector - FTSMPS_BAND1_SET_POINTS) * FTSMPS_BAND2_UV_STEP + + FTSMPS_BAND2_UV_MIN; + else + uV = (selector - FTSMPS_BAND1_SET_POINTS + - FTSMPS_BAND2_SET_POINTS) + * FTSMPS_BAND3_UV_STEP + + FTSMPS_BAND3_UV_SET_POINT_MIN; + + return uV; +} + +static int _pm8921_ftsmps_set_voltage(struct pm8921_vreg *vreg, int min_uV, + int max_uV, int force_on) +{ + int rc; + u8 vprog, band; + int uV = min_uV; + + if (uV < FTSMPS_BAND1_UV_MIN && max_uV >= FTSMPS_BAND1_UV_MIN) + uV = FTSMPS_BAND1_UV_MIN; + + if (uV < FTSMPS_BAND1_UV_MIN || uV > FTSMPS_BAND3_UV_MAX) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, FTSMPS_BAND1_UV_MIN, + FTSMPS_BAND3_UV_MAX); + return -EINVAL; + } + + /* Round up for set points in the gaps between bands. */ + if (uV > FTSMPS_BAND1_UV_MAX && uV < FTSMPS_BAND2_UV_MIN) + uV = FTSMPS_BAND2_UV_MIN; + else if (uV > FTSMPS_BAND2_UV_MAX + && uV < FTSMPS_BAND3_UV_SET_POINT_MIN) + uV = FTSMPS_BAND3_UV_SET_POINT_MIN; + + if (uV > FTSMPS_BAND2_UV_MAX) { + vprog = (uV - FTSMPS_BAND3_UV_MIN + FTSMPS_BAND3_UV_STEP - 1) + / FTSMPS_BAND3_UV_STEP; + band = FTSMPS_VCTRL_BAND_3; + uV = FTSMPS_BAND3_UV_MIN + vprog * FTSMPS_BAND3_UV_STEP; + } else if (uV > FTSMPS_BAND1_UV_MAX) { + vprog = (uV - FTSMPS_BAND2_UV_MIN + FTSMPS_BAND2_UV_STEP - 1) + / FTSMPS_BAND2_UV_STEP; + band = FTSMPS_VCTRL_BAND_2; + uV = FTSMPS_BAND2_UV_MIN + vprog * FTSMPS_BAND2_UV_STEP; + } else { + vprog = (uV - FTSMPS_BAND1_UV_MIN + + FTSMPS_BAND1_UV_LOG_STEP - 1) + / FTSMPS_BAND1_UV_LOG_STEP; + uV = FTSMPS_BAND1_UV_MIN + vprog * FTSMPS_BAND1_UV_LOG_STEP; + vprog *= FTSMPS_BAND1_UV_LOG_STEP / FTSMPS_BAND1_UV_PHYS_STEP; + band = FTSMPS_VCTRL_BAND_1; + } + + if (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point\n", + min_uV, max_uV); + return -EINVAL; + } + + /* + * Do not set voltage if regulator is currently disabled because doing + * so will enable it. + */ + if (_pm8921_vreg_is_enabled(vreg) || force_on) { + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + band | vprog, + FTSMPS_VCTRL_BAND_MASK | FTSMPS_VCTRL_VPROG_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Program PFM_VCTRL as 0x00 so that PWM_VCTRL overrides it. */ + rc = pm8921_vreg_masked_write(vreg, vreg->pfm_ctrl_addr, 0x00, + FTSMPS_VCTRL_BAND_MASK | FTSMPS_VCTRL_VPROG_MASK, + &vreg->pfm_ctrl_reg); + if (rc) + goto bail; + } + + vreg->save_uV = uV; + +bail: + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8921_ftsmps_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = _pm8921_ftsmps_set_voltage(vreg, min_uV, max_uV, 0); + + if (!rc) + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static int pm8921_ncp_get_voltage(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + u8 vprog; + + vprog = vreg->ctrl_reg & NCP_VPROG_MASK; + + return NCP_UV_MIN + vprog * NCP_UV_STEP; +} + +static int pm8921_ncp_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + if (selector >= NCP_SET_POINTS) + return 0; + + return selector * NCP_UV_STEP + NCP_UV_MIN; +} + +static int pm8921_ncp_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + int uV = min_uV; + u8 val; + + if (uV < NCP_UV_MIN && max_uV >= NCP_UV_MIN) + uV = NCP_UV_MIN; + + if (uV < NCP_UV_MIN || uV > NCP_UV_MAX) { + vreg_err(vreg, + "request v=[%d, %d] is outside possible v=[%d, %d]\n", + min_uV, max_uV, NCP_UV_MIN, NCP_UV_MAX); + return -EINVAL; + } + + val = (uV - NCP_UV_MIN + NCP_UV_STEP - 1) / NCP_UV_STEP; + uV = val * NCP_UV_STEP + NCP_UV_MIN; + + if (uV > max_uV) { + vreg_err(vreg, + "request v=[%d, %d] cannot be met by any set point\n", + min_uV, max_uV); + return -EINVAL; + } + + /* voltage setting */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val, + NCP_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_VOLTAGE); + + return rc; +} + +static unsigned int pm8921_ldo_get_mode(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mode = 0; + + mutex_lock(&vreg->pc_lock); + mode = vreg->mode; + mutex_unlock(&vreg->pc_lock); + + return mode; +} + +static int pm8921_ldo_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + + if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) { + vreg_err(vreg, "invalid mode: %u\n", mode); + return -EINVAL; + } + + mutex_lock(&vreg->pc_lock); + + if (mode == REGULATOR_MODE_NORMAL + || (vreg->is_enabled_pc + && vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE)) { + /* HPM */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_CTRL_PM_HPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + } else { + /* LPM */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_CTRL_PM_LPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(0), + LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[0]); + } + +bail: + if (!rc) + vreg->mode = mode; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int pm8921_nldo1200_get_mode(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mode = 0; + + if (NLDO1200_IN_ADVANCED_MODE(vreg)) { + /* Advanced mode */ + if ((vreg->test_reg[2] & NLDO1200_ADVANCED_PM_MASK) + == NLDO1200_ADVANCED_PM_LPM) + mode = REGULATOR_MODE_IDLE; + else + mode = REGULATOR_MODE_NORMAL; + } else { + /* Legacy mode */ + if ((vreg->ctrl_reg & NLDO1200_LEGACY_PM_MASK) + == NLDO1200_LEGACY_PM_LPM) + mode = REGULATOR_MODE_IDLE; + else + mode = REGULATOR_MODE_NORMAL; + } + + return mode; +} + +static int pm8921_nldo1200_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + + if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) { + vreg_err(vreg, "invalid mode: %u\n", mode); + return -EINVAL; + } + + /* + * Make sure that advanced mode is in use. If it isn't, then set it + * and update the voltage accordingly. + */ + if (!NLDO1200_IN_ADVANCED_MODE(vreg)) { + rc = _pm8921_nldo1200_set_voltage(vreg, vreg->save_uV, + vreg->save_uV); + if (rc) + goto bail; + } + + if (mode == REGULATOR_MODE_NORMAL) { + /* HPM */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + NLDO1200_ADVANCED_PM_HPM | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(2), NLDO1200_ADVANCED_PM_MASK + | REGULATOR_BANK_MASK, &vreg->test_reg[2]); + } else { + /* LPM */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + NLDO1200_ADVANCED_PM_LPM | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(2), NLDO1200_ADVANCED_PM_MASK + | REGULATOR_BANK_MASK, &vreg->test_reg[2]); + } + +bail: + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int pm8921_smps_get_mode(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mode = 0; + + mutex_lock(&vreg->pc_lock); + mode = vreg->mode; + mutex_unlock(&vreg->pc_lock); + + return mode; +} + +static int pm8921_smps_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + + if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) { + vreg_err(vreg, "invalid mode: %u\n", mode); + return -EINVAL; + } + + mutex_lock(&vreg->pc_lock); + + if (mode == REGULATOR_MODE_NORMAL + || (vreg->is_enabled_pc + && vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE)) { + /* HPM */ + rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr, + SMPS_CLK_CTRL_PWM, SMPS_CLK_CTRL_MASK, + &vreg->clk_ctrl_reg); + } else { + /* LPM */ + rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr, + SMPS_CLK_CTRL_PFM, SMPS_CLK_CTRL_MASK, + &vreg->clk_ctrl_reg); + } + + if (!rc) + vreg->mode = mode; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int pm8921_ftsmps_get_mode(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mode = 0; + + if ((vreg->test_reg[0] & FTSMPS_CNFG1_PM_MASK) == FTSMPS_CNFG1_PM_PFM) + mode = REGULATOR_MODE_IDLE; + else + mode = REGULATOR_MODE_NORMAL; + + return mode; +} + +static int pm8921_ftsmps_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + + if (mode == REGULATOR_MODE_NORMAL) { + /* HPM */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + FTSMPS_CNFG1_PM_PWM | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(0), FTSMPS_CNFG1_PM_MASK + | REGULATOR_BANK_MASK, &vreg->test_reg[0]); + } else if (mode == REGULATOR_MODE_IDLE) { + /* LPM */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + FTSMPS_CNFG1_PM_PFM | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(0), FTSMPS_CNFG1_PM_MASK + | REGULATOR_BANK_MASK, &vreg->test_reg[0]); + } else { + vreg_err(vreg, "invalid mode: %u\n", mode); + return -EINVAL; + } + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_MODE); + + return rc; +} + +static unsigned int pm8921_vreg_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, int load_uA) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + unsigned int mode; + + if (load_uA + vreg->pdata.system_uA >= vreg->hpm_min_load) + mode = REGULATOR_MODE_NORMAL; + else + mode = REGULATOR_MODE_IDLE; + + return mode; +} + +static int pm8921_ldo_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc, val; + + mutex_lock(&vreg->pc_lock); + + /* + * Choose HPM if previously set to HPM or if pin control is enabled in + * on/off mode. + */ + val = LDO_CTRL_PM_LPM; + if (vreg->mode == REGULATOR_MODE_NORMAL + || (vreg->is_enabled_pc + && vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE)) + val = LDO_CTRL_PM_HPM; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val | LDO_ENABLE, + LDO_ENABLE_MASK | LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + + if (!rc) + vreg->is_enabled = true; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8921_ldo_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + /* + * Only disable the regulator if it isn't still required for HPM/LPM + * pin control. + */ + if (!vreg->is_enabled_pc + || vreg->pdata.pin_fn != PM8921_VREG_PIN_FN_MODE) { + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_DISABLE, LDO_ENABLE_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + } + + /* Change to LPM if HPM/LPM pin control is enabled. */ + if (vreg->is_enabled_pc + && vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_MODE) { + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_CTRL_PM_LPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(0), + LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[0]); + } + + if (!rc) + vreg->is_enabled = false; +bail: + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8921_nldo1200_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, NLDO1200_ENABLE, + NLDO1200_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8921_nldo1200_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, NLDO1200_DISABLE, + NLDO1200_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8921_smps_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + int val; + + mutex_lock(&vreg->pc_lock); + + if (SMPS_IN_ADVANCED_MODE(vreg) + || !pm8921_vreg_is_pin_controlled(vreg)) { + /* Enable in advanced mode if not using pin control. */ + rc = pm8921_smps_set_voltage_advanced(vreg, vreg->save_uV, + vreg->save_uV, 1); + } else { + rc = pm8921_smps_set_voltage_legacy(vreg, vreg->save_uV, + vreg->save_uV); + if (rc) + goto bail; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + SMPS_LEGACY_ENABLE, SMPS_LEGACY_ENABLE_MASK, + &vreg->ctrl_reg); + } + + /* + * Choose HPM if previously set to HPM or if pin control is enabled in + * on/off mode. + */ + val = SMPS_CLK_CTRL_PFM; + if (vreg->mode == REGULATOR_MODE_NORMAL + || (vreg->is_enabled_pc + && vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE)) + val = SMPS_CLK_CTRL_PWM; + + rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr, val, + SMPS_CLK_CTRL_MASK, &vreg->clk_ctrl_reg); + + if (!rc) + vreg->is_enabled = true; +bail: + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8921_smps_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + if (SMPS_IN_ADVANCED_MODE(vreg)) { + /* Change SMPS to legacy mode before disabling. */ + rc = pm8921_smps_set_voltage_legacy(vreg, vreg->save_uV, + vreg->save_uV); + if (rc) + goto bail; + } + + /* + * Only disable the regulator if it isn't still required for HPM/LPM + * pin control. + */ + if (!vreg->is_enabled_pc + || vreg->pdata.pin_fn != PM8921_VREG_PIN_FN_MODE) { + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + SMPS_LEGACY_DISABLE, SMPS_LEGACY_ENABLE_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + } + + /* Change to LPM if HPM/LPM pin control is enabled. */ + if (vreg->is_enabled_pc + && vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_MODE) + rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr, + SMPS_CLK_CTRL_PFM, SMPS_CLK_CTRL_MASK, + &vreg->clk_ctrl_reg); + + if (!rc) + vreg->is_enabled = false; + +bail: + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8921_ftsmps_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = _pm8921_ftsmps_set_voltage(vreg, vreg->save_uV, vreg->save_uV, 1); + + if (rc) + vreg_err(vreg, "set voltage failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8921_ftsmps_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + FTSMPS_VCTRL_BAND_OFF, FTSMPS_VCTRL_BAND_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8921_vreg_masked_write(vreg, vreg->pfm_ctrl_addr, + FTSMPS_VCTRL_BAND_OFF, FTSMPS_VCTRL_BAND_MASK, + &vreg->pfm_ctrl_reg); +bail: + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8921_vs_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, VS_ENABLE, + VS_ENABLE_MASK, &vreg->ctrl_reg); + + if (!rc) + vreg->is_enabled = true; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8921_vs_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, VS_DISABLE, + VS_ENABLE_MASK, &vreg->ctrl_reg); + + if (!rc) + vreg->is_enabled = false; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8921_vs300_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, VS300_CTRL_ENABLE, + VS300_CTRL_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8921_vs300_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, VS300_CTRL_DISABLE, + VS300_CTRL_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8921_ncp_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, NCP_ENABLE, + NCP_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_ENABLE); + + return rc; +} + +static int pm8921_ncp_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, NCP_DISABLE, + NCP_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_DISABLE); + + return rc; +} + +static int pm8921_ldo_pin_control_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + int bank; + u8 val = 0; + u8 mask; + + mutex_lock(&vreg->pc_lock); + + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_D1) + val |= LDO_TEST_PIN_CTRL_EN0; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A0) + val |= LDO_TEST_PIN_CTRL_EN1; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A1) + val |= LDO_TEST_PIN_CTRL_EN2; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A2) + val |= LDO_TEST_PIN_CTRL_EN3; + + bank = (vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE ? 5 : 6); + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + val | REGULATOR_BANK_SEL(bank) | REGULATOR_BANK_WRITE, + LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[bank]); + if (rc) + goto bail; + + /* Unset pin control bits in unused bank. */ + bank = (bank == 5 ? 6 : 5); + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + REGULATOR_BANK_SEL(bank) | REGULATOR_BANK_WRITE, + LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[bank]); + if (rc) + goto bail; + + val = LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(0); + mask = LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK; + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, val, mask, + &vreg->test_reg[0]); + if (rc) + goto bail; + + if (vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE) { + /* Pin control ON/OFF */ + val = LDO_CTRL_PM_HPM; + /* Leave physically enabled if already enabled. */ + val |= (vreg->is_enabled ? LDO_ENABLE : LDO_DISABLE); + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val, + LDO_ENABLE_MASK | LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + } else { + /* Pin control LPM/HPM */ + val = LDO_ENABLE; + /* Leave in HPM if already enabled in HPM. */ + val |= (vreg->is_enabled && vreg->mode == REGULATOR_MODE_NORMAL + ? LDO_CTRL_PM_HPM : LDO_CTRL_PM_LPM); + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val, + LDO_ENABLE_MASK | LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + } + +bail: + if (!rc) + vreg->is_enabled_pc = true; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8921_ldo_pin_control_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + REGULATOR_BANK_SEL(5) | REGULATOR_BANK_WRITE, + LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[5]); + if (rc) + goto bail; + + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + REGULATOR_BANK_SEL(6) | REGULATOR_BANK_WRITE, + LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[6]); + + /* + * Physically disable the regulator if it was enabled in HPM/LPM pin + * control mode previously and it logically should not be enabled. + */ + if ((vreg->ctrl_reg & LDO_ENABLE_MASK) == LDO_ENABLE + && !vreg->is_enabled) { + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_DISABLE, LDO_ENABLE_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + } + + /* Change to LPM if LPM was enabled. */ + if (vreg->is_enabled && vreg->mode == REGULATOR_MODE_IDLE) { + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + LDO_CTRL_PM_LPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(0), + LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[0]); + if (rc) + goto bail; + } + +bail: + if (!rc) + vreg->is_enabled_pc = false; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8921_smps_pin_control_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc = 0; + u8 val = 0; + + mutex_lock(&vreg->pc_lock); + + if (vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE) { + /* Pin control ON/OFF */ + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_D1) + val |= SMPS_PIN_CTRL_EN0; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A0) + val |= SMPS_PIN_CTRL_EN1; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A1) + val |= SMPS_PIN_CTRL_EN2; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A2) + val |= SMPS_PIN_CTRL_EN3; + } else { + /* Pin control LPM/HPM */ + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_D1) + val |= SMPS_PIN_CTRL_LPM_EN0; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A0) + val |= SMPS_PIN_CTRL_LPM_EN1; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A1) + val |= SMPS_PIN_CTRL_LPM_EN2; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A2) + val |= SMPS_PIN_CTRL_LPM_EN3; + } + + rc = pm8921_smps_set_voltage_legacy(vreg, vreg->save_uV, vreg->save_uV); + if (rc) + goto bail; + + rc = pm8921_vreg_masked_write(vreg, vreg->sleep_ctrl_addr, val, + SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK, + &vreg->sleep_ctrl_reg); + if (rc) + goto bail; + + /* + * Physically enable the regulator if using HPM/LPM pin control mode or + * if the regulator should be logically left on. + */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + ((vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_MODE + || vreg->is_enabled) ? + SMPS_LEGACY_ENABLE : SMPS_LEGACY_DISABLE), + SMPS_LEGACY_ENABLE_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* + * Set regulator to HPM if using on/off pin control or if the regulator + * is already enabled in HPM. Otherwise, set it to LPM. + */ + rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr, + (vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE + || (vreg->is_enabled + && vreg->mode == REGULATOR_MODE_NORMAL) + ? SMPS_CLK_CTRL_PWM : SMPS_CLK_CTRL_PFM), + SMPS_CLK_CTRL_MASK, &vreg->clk_ctrl_reg); + +bail: + if (!rc) + vreg->is_enabled_pc = true; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8921_smps_pin_control_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + rc = pm8921_vreg_masked_write(vreg, vreg->sleep_ctrl_addr, 0, + SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK, + &vreg->sleep_ctrl_reg); + if (rc) + goto bail; + + /* + * Physically disable the regulator if it was enabled in HPM/LPM pin + * control mode previously and it logically should not be enabled. + */ + if ((vreg->ctrl_reg & SMPS_LEGACY_ENABLE_MASK) == SMPS_LEGACY_ENABLE + && vreg->is_enabled == false) { + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + SMPS_LEGACY_DISABLE, SMPS_LEGACY_ENABLE_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + } + + /* Change to LPM if LPM was enabled. */ + if (vreg->is_enabled && vreg->mode == REGULATOR_MODE_IDLE) { + rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr, + SMPS_CLK_CTRL_PFM, SMPS_CLK_CTRL_MASK, + &vreg->clk_ctrl_reg); + if (rc) + goto bail; + } + + rc = pm8921_smps_set_voltage_advanced(vreg, vreg->save_uV, + vreg->save_uV, 0); + +bail: + if (!rc) + vreg->is_enabled_pc = false; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8921_vs_pin_control_enable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + u8 val = 0; + + mutex_lock(&vreg->pc_lock); + + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_D1) + val |= VS_PIN_CTRL_EN0; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A0) + val |= VS_PIN_CTRL_EN1; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A1) + val |= VS_PIN_CTRL_EN2; + if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A2) + val |= VS_PIN_CTRL_EN3; + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val, + VS_PIN_CTRL_MASK | VS_ENABLE_MASK, &vreg->ctrl_reg); + + if (!rc) + vreg->is_enabled_pc = true; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static int pm8921_vs_pin_control_disable(struct regulator_dev *rdev) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int rc; + + mutex_lock(&vreg->pc_lock); + + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, 0, + VS_PIN_CTRL_MASK, &vreg->ctrl_reg); + + if (!rc) + vreg->is_enabled_pc = false; + + mutex_unlock(&vreg->pc_lock); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + else + pm8921_vreg_show_state(rdev, PM8921_REGULATOR_ACTION_PIN_CTRL); + + return rc; +} + +static const char const *pm8921_print_actions[] = { + [PM8921_REGULATOR_ACTION_INIT] = "initial ", + [PM8921_REGULATOR_ACTION_ENABLE] = "enable ", + [PM8921_REGULATOR_ACTION_DISABLE] = "disable ", + [PM8921_REGULATOR_ACTION_VOLTAGE] = "set voltage", + [PM8921_REGULATOR_ACTION_MODE] = "set mode ", + [PM8921_REGULATOR_ACTION_PIN_CTRL] = "pin control", +}; + +static void pm8921_vreg_show_state(struct regulator_dev *rdev, + enum pm8921_regulator_action action) +{ + struct pm8921_vreg *vreg = rdev_get_drvdata(rdev); + int uV, pc; + unsigned int mode; + const char *pc_en0 = "", *pc_en1 = "", *pc_en2 = "", *pc_en3 = ""; + const char *pc_total = ""; + const char *action_label = pm8921_print_actions[action]; + const char *enable_label; + + mutex_lock(&vreg->pc_lock); + + /* + * Do not print unless REQUEST is specified and SSBI writes have taken + * place, or DUPLICATE is specified. + */ + if (!((pm8921_vreg_debug_mask & PM8921_VREG_DEBUG_DUPLICATE) + || ((pm8921_vreg_debug_mask & PM8921_VREG_DEBUG_REQUEST) + && (vreg->write_count != vreg->prev_write_count)))) { + mutex_unlock(&vreg->pc_lock); + return; + } + + vreg->prev_write_count = vreg->write_count; + + pc = vreg->pdata.pin_ctrl; + if (vreg->is_enabled_pc) { + if (pc & PM8921_VREG_PIN_CTRL_D1) + pc_en0 = " D1"; + if (pc & PM8921_VREG_PIN_CTRL_A0) + pc_en1 = " A0"; + if (pc & PM8921_VREG_PIN_CTRL_A1) + pc_en2 = " A1"; + if (pc & PM8921_VREG_PIN_CTRL_A2) + pc_en3 = " A2"; + if (pc == PM8921_VREG_PIN_CTRL_NONE) + pc_total = " none"; + } else { + pc_total = " none"; + } + + mutex_unlock(&vreg->pc_lock); + + enable_label = pm8921_vreg_is_enabled(rdev) ? "on " : "off"; + + switch (vreg->type) { + case REGULATOR_TYPE_PLDO: + uV = pm8921_pldo_get_voltage(rdev); + mode = pm8921_ldo_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s, pc=%s%s%s%s%s\n", + action_label, vreg->name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM"), + pc_en0, pc_en1, pc_en2, pc_en3, pc_total); + break; + case REGULATOR_TYPE_NLDO: + uV = pm8921_nldo_get_voltage(rdev); + mode = pm8921_ldo_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s, pc=%s%s%s%s%s\n", + action_label, vreg->name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM"), + pc_en0, pc_en1, pc_en2, pc_en3, pc_total); + break; + case REGULATOR_TYPE_NLDO1200: + uV = pm8921_nldo1200_get_voltage(rdev); + mode = pm8921_nldo1200_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s\n", + action_label, vreg->name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM")); + break; + case REGULATOR_TYPE_SMPS: + uV = pm8921_smps_get_voltage(rdev); + mode = pm8921_smps_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s, pc=%s%s%s%s%s\n", + action_label, vreg->name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM"), + pc_en0, pc_en1, pc_en2, pc_en3, pc_total); + break; + case REGULATOR_TYPE_FTSMPS: + uV = pm8921_ftsmps_get_voltage(rdev); + mode = pm8921_ftsmps_get_mode(rdev); + pr_info("%s %-9s: %s, v=%7d uV, mode=%s\n", + action_label, vreg->name, enable_label, uV, + (mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM")); + break; + case REGULATOR_TYPE_VS: + pr_info("%s %-9s: %s, pc=%s%s%s%s%s\n", + action_label, vreg->name, enable_label, + pc_en0, pc_en1, pc_en2, pc_en3, pc_total); + break; + case REGULATOR_TYPE_VS300: + pr_info("%s %-9s: %s\n", + action_label, vreg->name, enable_label); + break; + case REGULATOR_TYPE_NCP: + uV = pm8921_ncp_get_voltage(rdev); + pr_info("%s %-9s: %s, v=%7d uV\n", + action_label, vreg->name, enable_label, uV); + break; + default: + break; + } +} + + +/* Real regulator operations. */ +static struct regulator_ops pm8921_pldo_ops = { + .enable = pm8921_ldo_enable, + .disable = pm8921_ldo_disable, + .is_enabled = pm8921_vreg_is_enabled, + .set_voltage = pm8921_pldo_set_voltage, + .get_voltage = pm8921_pldo_get_voltage, + .list_voltage = pm8921_pldo_list_voltage, + .set_mode = pm8921_ldo_set_mode, + .get_mode = pm8921_ldo_get_mode, + .get_optimum_mode = pm8921_vreg_get_optimum_mode, +}; + +static struct regulator_ops pm8921_nldo_ops = { + .enable = pm8921_ldo_enable, + .disable = pm8921_ldo_disable, + .is_enabled = pm8921_vreg_is_enabled, + .set_voltage = pm8921_nldo_set_voltage, + .get_voltage = pm8921_nldo_get_voltage, + .list_voltage = pm8921_nldo_list_voltage, + .set_mode = pm8921_ldo_set_mode, + .get_mode = pm8921_ldo_get_mode, + .get_optimum_mode = pm8921_vreg_get_optimum_mode, +}; + +static struct regulator_ops pm8921_nldo1200_ops = { + .enable = pm8921_nldo1200_enable, + .disable = pm8921_nldo1200_disable, + .is_enabled = pm8921_vreg_is_enabled, + .set_voltage = pm8921_nldo1200_set_voltage, + .get_voltage = pm8921_nldo1200_get_voltage, + .list_voltage = pm8921_nldo1200_list_voltage, + .set_mode = pm8921_nldo1200_set_mode, + .get_mode = pm8921_nldo1200_get_mode, + .get_optimum_mode = pm8921_vreg_get_optimum_mode, +}; + +static struct regulator_ops pm8921_smps_ops = { + .enable = pm8921_smps_enable, + .disable = pm8921_smps_disable, + .is_enabled = pm8921_vreg_is_enabled, + .set_voltage = pm8921_smps_set_voltage, + .get_voltage = pm8921_smps_get_voltage, + .list_voltage = pm8921_smps_list_voltage, + .set_mode = pm8921_smps_set_mode, + .get_mode = pm8921_smps_get_mode, + .get_optimum_mode = pm8921_vreg_get_optimum_mode, +}; + +static struct regulator_ops pm8921_ftsmps_ops = { + .enable = pm8921_ftsmps_enable, + .disable = pm8921_ftsmps_disable, + .is_enabled = pm8921_vreg_is_enabled, + .set_voltage = pm8921_ftsmps_set_voltage, + .get_voltage = pm8921_ftsmps_get_voltage, + .list_voltage = pm8921_ftsmps_list_voltage, + .set_mode = pm8921_ftsmps_set_mode, + .get_mode = pm8921_ftsmps_get_mode, + .get_optimum_mode = pm8921_vreg_get_optimum_mode, +}; + +static struct regulator_ops pm8921_vs_ops = { + .enable = pm8921_vs_enable, + .disable = pm8921_vs_disable, + .is_enabled = pm8921_vreg_is_enabled, +}; + +static struct regulator_ops pm8921_vs300_ops = { + .enable = pm8921_vs300_enable, + .disable = pm8921_vs300_disable, + .is_enabled = pm8921_vreg_is_enabled, +}; + +static struct regulator_ops pm8921_ncp_ops = { + .enable = pm8921_ncp_enable, + .disable = pm8921_ncp_disable, + .is_enabled = pm8921_vreg_is_enabled, + .set_voltage = pm8921_ncp_set_voltage, + .get_voltage = pm8921_ncp_get_voltage, + .list_voltage = pm8921_ncp_list_voltage, +}; + +/* Pin control regulator operations. */ +static struct regulator_ops pm8921_ldo_pc_ops = { + .enable = pm8921_ldo_pin_control_enable, + .disable = pm8921_ldo_pin_control_disable, + .is_enabled = pm8921_vreg_pin_control_is_enabled, +}; + +static struct regulator_ops pm8921_smps_pc_ops = { + .enable = pm8921_smps_pin_control_enable, + .disable = pm8921_smps_pin_control_disable, + .is_enabled = pm8921_vreg_pin_control_is_enabled, +}; + +static struct regulator_ops pm8921_vs_pc_ops = { + .enable = pm8921_vs_pin_control_enable, + .disable = pm8921_vs_pin_control_disable, + .is_enabled = pm8921_vreg_pin_control_is_enabled, +}; + +#define VREG_DESC(_id, _name, _ops, _n_voltages) \ + [PM8921_VREG_ID_##_id] = { \ + .id = PM8921_VREG_ID_##_id, \ + .name = _name, \ + .n_voltages = _n_voltages, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static struct regulator_desc pm8921_vreg_description[] = { + VREG_DESC(L1, "8921_l1", &pm8921_nldo_ops, NLDO_SET_POINTS), + VREG_DESC(L2, "8921_l2", &pm8921_nldo_ops, NLDO_SET_POINTS), + VREG_DESC(L3, "8921_l3", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L4, "8921_l4", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L5, "8921_l5", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L6, "8921_l6", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L7, "8921_l7", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L8, "8921_l8", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L9, "8921_l9", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L10, "8921_l10", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L11, "8921_l11", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L12, "8921_l12", &pm8921_nldo_ops, NLDO_SET_POINTS), + VREG_DESC(L14, "8921_l14", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L15, "8921_l15", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L16, "8921_l16", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L17, "8921_l17", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L18, "8921_l18", &pm8921_nldo_ops, NLDO_SET_POINTS), + VREG_DESC(L21, "8921_l21", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L22, "8921_l22", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L23, "8921_l23", &pm8921_pldo_ops, PLDO_SET_POINTS), + VREG_DESC(L24, "8921_l24", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS), + VREG_DESC(L25, "8921_l25", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS), + VREG_DESC(L26, "8921_l26", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS), + VREG_DESC(L27, "8921_l27", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS), + VREG_DESC(L28, "8921_l28", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS), + VREG_DESC(L29, "8921_l29", &pm8921_pldo_ops, PLDO_SET_POINTS), + + VREG_DESC(S1, "8921_s1", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS), + VREG_DESC(S2, "8921_s2", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS), + VREG_DESC(S3, "8921_s3", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS), + VREG_DESC(S4, "8921_s4", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS), + VREG_DESC(S5, "8921_s5", &pm8921_ftsmps_ops, FTSMPS_SET_POINTS), + VREG_DESC(S6, "8921_s6", &pm8921_ftsmps_ops, FTSMPS_SET_POINTS), + VREG_DESC(S7, "8921_s7", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS), + VREG_DESC(S8, "8921_s8", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS), + + VREG_DESC(LVS1, "8921_lvs1", &pm8921_vs_ops, 0), + VREG_DESC(LVS2, "8921_lvs2", &pm8921_vs300_ops, 0), + VREG_DESC(LVS3, "8921_lvs3", &pm8921_vs_ops, 0), + VREG_DESC(LVS4, "8921_lvs4", &pm8921_vs_ops, 0), + VREG_DESC(LVS5, "8921_lvs5", &pm8921_vs_ops, 0), + VREG_DESC(LVS6, "8921_lvs6", &pm8921_vs_ops, 0), + VREG_DESC(LVS7, "8921_lvs7", &pm8921_vs_ops, 0), + + VREG_DESC(USB_OTG, "8921_usb_otg", &pm8921_vs300_ops, 0), + VREG_DESC(HDMI_MVS, "8921_hdmi_mvs", &pm8921_vs300_ops, 0), + VREG_DESC(NCP, "8921_ncp", &pm8921_ncp_ops, NCP_SET_POINTS), + + VREG_DESC(L1_PC, "8921_l1_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L2_PC, "8921_l2_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L3_PC, "8921_l3_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L4_PC, "8921_l4_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L5_PC, "8921_l5_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L6_PC, "8921_l6_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L7_PC, "8921_l7_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L8_PC, "8921_l8_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L9_PC, "8921_l9_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L10_PC, "8921_l10_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L11_PC, "8921_l11_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L12_PC, "8921_l12_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L14_PC, "8921_l14_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L15_PC, "8921_l15_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L16_PC, "8921_l16_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L17_PC, "8921_l17_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L18_PC, "8921_l18_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L21_PC, "8921_l21_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L22_PC, "8921_l22_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L23_PC, "8921_l23_pc", &pm8921_ldo_pc_ops, 0), + VREG_DESC(L29_PC, "8921_l29_pc", &pm8921_ldo_pc_ops, 0), + + VREG_DESC(S1_PC, "8921_s1_pc", &pm8921_smps_pc_ops, 0), + VREG_DESC(S2_PC, "8921_s2_pc", &pm8921_smps_pc_ops, 0), + VREG_DESC(S3_PC, "8921_s3_pc", &pm8921_smps_pc_ops, 0), + VREG_DESC(S4_PC, "8921_s4_pc", &pm8921_smps_pc_ops, 0), + VREG_DESC(S7_PC, "8921_s7_pc", &pm8921_smps_pc_ops, 0), + VREG_DESC(S8_PC, "8921_s8_pc", &pm8921_smps_pc_ops, 0), + + VREG_DESC(LVS1_PC, "8921_lvs1_pc", &pm8921_vs_pc_ops, 0), + VREG_DESC(LVS3_PC, "8921_lvs3_pc", &pm8921_vs_pc_ops, 0), + VREG_DESC(LVS4_PC, "8921_lvs4_pc", &pm8921_vs_pc_ops, 0), + VREG_DESC(LVS5_PC, "8921_lvs5_pc", &pm8921_vs_pc_ops, 0), + VREG_DESC(LVS6_PC, "8921_lvs6_pc", &pm8921_vs_pc_ops, 0), + VREG_DESC(LVS7_PC, "8921_lvs7_pc", &pm8921_vs_pc_ops, 0), +}; + +static int pm8921_init_ldo(struct pm8921_vreg *vreg, bool is_real) +{ + int rc = 0; + int i; + u8 bank; + + /* Save the current control register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Save the current test register state. */ + for (i = 0; i < LDO_TEST_BANKS; i++) { + bank = REGULATOR_BANK_SEL(i); + rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank); + if (rc) + goto bail; + + rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr, + &vreg->test_reg[i]); + if (rc) + goto bail; + vreg->test_reg[i] |= REGULATOR_BANK_WRITE; + } + + if (is_real) { + /* Set pull down enable based on platform data. */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + (vreg->pdata.pull_down_enable ? LDO_PULL_DOWN_ENABLE : 0), + LDO_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg); + + vreg->is_enabled = !!_pm8921_vreg_is_enabled(vreg); + + vreg->mode = ((vreg->ctrl_reg & LDO_CTRL_PM_MASK) + == LDO_CTRL_PM_LPM ? + REGULATOR_MODE_IDLE : REGULATOR_MODE_NORMAL); + } +bail: + if (rc) + vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc); + + return rc; +} + +static int pm8921_init_nldo1200(struct pm8921_vreg *vreg) +{ + int rc = 0; + int i; + u8 bank; + + /* Save the current control register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Save the current test register state. */ + for (i = 0; i < LDO_TEST_BANKS; i++) { + bank = REGULATOR_BANK_SEL(i); + rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank); + if (rc) + goto bail; + + rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr, + &vreg->test_reg[i]); + if (rc) + goto bail; + vreg->test_reg[i] |= REGULATOR_BANK_WRITE; + } + + vreg->save_uV = _pm8921_nldo1200_get_voltage(vreg); + + /* Set pull down enable based on platform data. */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + (vreg->pdata.pull_down_enable ? NLDO1200_PULL_DOWN_ENABLE : 0) + | REGULATOR_BANK_SEL(1) | REGULATOR_BANK_WRITE, + NLDO1200_PULL_DOWN_ENABLE_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[1]); + +bail: + if (rc) + vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc); + + return rc; +} + +static int pm8921_init_smps(struct pm8921_vreg *vreg, bool is_real) +{ + int rc = 0; + int i; + u8 bank; + + /* Save the current control register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Save the current test2 register state. */ + for (i = 0; i < SMPS_TEST_BANKS; i++) { + bank = REGULATOR_BANK_SEL(i); + rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank); + if (rc) + goto bail; + + rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr, + &vreg->test_reg[i]); + if (rc) + goto bail; + vreg->test_reg[i] |= REGULATOR_BANK_WRITE; + } + + /* Save the current clock control register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->clk_ctrl_addr, + &vreg->clk_ctrl_reg); + if (rc) + goto bail; + + /* Save the current sleep control register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->sleep_ctrl_addr, + &vreg->sleep_ctrl_reg); + if (rc) + goto bail; + + vreg->save_uV = _pm8921_smps_get_voltage(vreg); + + if (is_real) { + /* Set advanced mode pull down enable based on platform data. */ + rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, + (vreg->pdata.pull_down_enable + ? SMPS_ADVANCED_PULL_DOWN_ENABLE : 0) + | REGULATOR_BANK_SEL(6) | REGULATOR_BANK_WRITE, + REGULATOR_BANK_MASK | SMPS_ADVANCED_PULL_DOWN_ENABLE, + &vreg->test_reg[6]); + if (rc) + goto bail; + + vreg->is_enabled = !!_pm8921_vreg_is_enabled(vreg); + + vreg->mode = ((vreg->clk_ctrl_reg & SMPS_CLK_CTRL_MASK) + == SMPS_CLK_CTRL_PFM ? + REGULATOR_MODE_IDLE : REGULATOR_MODE_NORMAL); + } + + if (!SMPS_IN_ADVANCED_MODE(vreg) && is_real) { + /* Set legacy mode pull down enable based on platform data. */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + (vreg->pdata.pull_down_enable + ? SMPS_LEGACY_PULL_DOWN_ENABLE : 0), + SMPS_LEGACY_PULL_DOWN_ENABLE, &vreg->ctrl_reg); + if (rc) + goto bail; + } + +bail: + if (rc) + vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc); + + return rc; +} + +static int pm8921_init_ftsmps(struct pm8921_vreg *vreg) +{ + int rc, i; + u8 bank; + + /* Save the current control register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Store current regulator register values. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->pfm_ctrl_addr, + &vreg->pfm_ctrl_reg); + if (rc) + goto bail; + + rc = pm8xxx_readb(vreg->dev->parent, vreg->pwr_cnfg_addr, + &vreg->pwr_cnfg_reg); + if (rc) + goto bail; + + /* Save the current fts_cnfg1 register state (uses 'test' member). */ + for (i = 0; i < SMPS_TEST_BANKS; i++) { + bank = REGULATOR_BANK_SEL(i); + rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank); + if (rc) + goto bail; + + rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr, + &vreg->test_reg[i]); + if (rc) + goto bail; + vreg->test_reg[i] |= REGULATOR_BANK_WRITE; + } + + vreg->save_uV = _pm8921_ftsmps_get_voltage(vreg); + + /* Set pull down enable based on platform data. */ + rc = pm8921_vreg_masked_write(vreg, vreg->pwr_cnfg_addr, + (vreg->pdata.pull_down_enable ? FTSMPS_PULL_DOWN_ENABLE : 0), + FTSMPS_PULL_DOWN_ENABLE_MASK, &vreg->pwr_cnfg_reg); + +bail: + if (rc) + vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc); + + return rc; +} + +static int pm8921_init_vs(struct pm8921_vreg *vreg, bool is_real) +{ + int rc = 0; + + /* Save the current control register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg); + if (rc) { + vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc); + return rc; + } + + if (is_real) { + /* Set pull down enable based on platform data. */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + (vreg->pdata.pull_down_enable ? VS_PULL_DOWN_ENABLE : 0), + VS_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, + "pm8921_vreg_masked_write failed, rc=%d\n", rc); + + vreg->is_enabled = !!_pm8921_vreg_is_enabled(vreg); + } + + return rc; +} + +static int pm8921_init_vs300(struct pm8921_vreg *vreg) +{ + int rc; + + /* Save the current control register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg); + if (rc) { + vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc); + return rc; + } + + /* Set pull down enable based on platform data. */ + rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, + (vreg->pdata.pull_down_enable ? VS300_PULL_DOWN_ENABLE : 0), + VS300_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg); + + if (rc) + vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc); + + return rc; +} + +static int pm8921_init_ncp(struct pm8921_vreg *vreg) +{ + int rc; + + /* Save the current control register state. */ + rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg); + if (rc) { + vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc); + return rc; + } + + return rc; +} + +int pc_id_to_real_id(int id) +{ + int real_id; + + if (id >= PM8921_VREG_ID_L1_PC && id <= PM8921_VREG_ID_L23_PC) + real_id = id - PM8921_VREG_ID_L1_PC; + else if (id >= PM8921_VREG_ID_L29_PC && id <= PM8921_VREG_ID_S4_PC) + real_id = id - PM8921_VREG_ID_L29_PC + PM8921_VREG_ID_L29; + else if (id >= PM8921_VREG_ID_S7_PC && id <= PM8921_VREG_ID_LVS1_PC) + real_id = id - PM8921_VREG_ID_S7_PC + PM8921_VREG_ID_S7; + else + real_id = id - PM8921_VREG_ID_LVS3_PC + PM8921_VREG_ID_LVS3; + + return real_id; +} + +static int __devinit pm8921_vreg_probe(struct platform_device *pdev) +{ + const struct pm8921_regulator_platform_data *pdata; + enum pm8921_vreg_pin_function pin_fn; + struct regulator_desc *rdesc; + struct pm8921_vreg *vreg; + const char *reg_name = ""; + unsigned pin_ctrl; + int rc = 0, id = pdev->id; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= 0 && pdev->id < PM8921_VREG_ID_MAX) { + pdata = pdev->dev.platform_data; + rdesc = &pm8921_vreg_description[pdev->id]; + if (!IS_REAL_REGULATOR(pdev->id)) + id = pc_id_to_real_id(pdev->id); + vreg = &pm8921_vreg[id]; + reg_name = pm8921_vreg_description[pdev->id].name; + if (!pdata) { + pr_err("%s requires platform data\n", reg_name); + return -EINVAL; + } + + mutex_lock(&vreg->pc_lock); + + if (IS_REAL_REGULATOR(pdev->id)) { + /* Do not modify pin control and pin function values. */ + pin_ctrl = vreg->pdata.pin_ctrl; + pin_fn = vreg->pdata.pin_fn; + memcpy(&(vreg->pdata), pdata, + sizeof(struct pm8921_regulator_platform_data)); + vreg->pdata.pin_ctrl = pin_ctrl; + vreg->pdata.pin_fn = pin_fn; + vreg->dev = &pdev->dev; + vreg->name = reg_name; + } else { + /* Pin control regulator */ + if ((pdata->pin_ctrl & + (PM8921_VREG_PIN_CTRL_D1 | PM8921_VREG_PIN_CTRL_A0 + | PM8921_VREG_PIN_CTRL_A1 | PM8921_VREG_PIN_CTRL_A2)) + == PM8921_VREG_PIN_CTRL_NONE) { + pr_err("%s: no pin control input specified\n", + reg_name); + mutex_unlock(&vreg->pc_lock); + return -EINVAL; + } + vreg->pdata.pin_ctrl = pdata->pin_ctrl; + vreg->pdata.pin_fn = pdata->pin_fn; + vreg->dev_pc = &pdev->dev; + if (!vreg->dev) + vreg->dev = &pdev->dev; + if (!vreg->name) + vreg->name = reg_name; + } + + /* Initialize register values. */ + switch (vreg->type) { + case REGULATOR_TYPE_PLDO: + case REGULATOR_TYPE_NLDO: + rc = pm8921_init_ldo(vreg, IS_REAL_REGULATOR(pdev->id)); + break; + case REGULATOR_TYPE_NLDO1200: + rc = pm8921_init_nldo1200(vreg); + break; + case REGULATOR_TYPE_SMPS: + rc = pm8921_init_smps(vreg, + IS_REAL_REGULATOR(pdev->id)); + break; + case REGULATOR_TYPE_FTSMPS: + rc = pm8921_init_ftsmps(vreg); + break; + case REGULATOR_TYPE_VS: + rc = pm8921_init_vs(vreg, IS_REAL_REGULATOR(pdev->id)); + break; + case REGULATOR_TYPE_VS300: + rc = pm8921_init_vs300(vreg); + break; + case REGULATOR_TYPE_NCP: + rc = pm8921_init_ncp(vreg); + break; + } + + mutex_unlock(&vreg->pc_lock); + + if (rc) + goto bail; + + if (IS_REAL_REGULATOR(pdev->id)) { + vreg->rdev = regulator_register(rdesc, &pdev->dev, + &(pdata->init_data), vreg); + if (IS_ERR(vreg->rdev)) { + rc = PTR_ERR(vreg->rdev); + vreg->rdev = NULL; + pr_err("regulator_register failed: %s, rc=%d\n", + reg_name, rc); + } + } else { + vreg->rdev_pc = regulator_register(rdesc, &pdev->dev, + &(pdata->init_data), vreg); + if (IS_ERR(vreg->rdev_pc)) { + rc = PTR_ERR(vreg->rdev_pc); + vreg->rdev_pc = NULL; + pr_err("regulator_register failed: %s, rc=%d\n", + reg_name, rc); + } + } + if ((pm8921_vreg_debug_mask & PM8921_VREG_DEBUG_INIT) && !rc + && vreg->rdev) + pm8921_vreg_show_state(vreg->rdev, + PM8921_REGULATOR_ACTION_INIT); + } else { + rc = -ENODEV; + } + +bail: + if (rc) + pr_err("error for %s, rc=%d\n", reg_name, rc); + + return rc; +} + +static int __devexit pm8921_vreg_remove(struct platform_device *pdev) +{ + if (IS_REAL_REGULATOR(pdev->id)) + regulator_unregister(pm8921_vreg[pdev->id].rdev); + else + regulator_unregister( + pm8921_vreg[pc_id_to_real_id(pdev->id)].rdev_pc); + + return 0; +} + +static struct platform_driver pm8921_vreg_driver = { + .probe = pm8921_vreg_probe, + .remove = __devexit_p(pm8921_vreg_remove), + .driver = { + .name = PM8921_REGULATOR_DEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pm8921_vreg_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pm8921_vreg); i++) { + mutex_init(&pm8921_vreg[i].pc_lock); + pm8921_vreg[i].write_count = 0; + pm8921_vreg[i].prev_write_count = -1; + } + + return platform_driver_register(&pm8921_vreg_driver); +} +postcore_initcall(pm8921_vreg_init); + +static void __exit pm8921_vreg_exit(void) +{ + int i; + + platform_driver_unregister(&pm8921_vreg_driver); + + for (i = 0; i < ARRAY_SIZE(pm8921_vreg); i++) + mutex_destroy(&pm8921_vreg[i].pc_lock); +} +module_exit(pm8921_vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8921 regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8921_REGULATOR_DEV_NAME); diff --git a/drivers/regulator/pmic8058-regulator.c b/drivers/regulator/pmic8058-regulator.c new file mode 100644 index 00000000000..98ba1630dd4 --- /dev/null +++ b/drivers/regulator/pmic8058-regulator.c @@ -0,0 +1,1769 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Regulator types */ +#define REGULATOR_TYPE_LDO 0 +#define REGULATOR_TYPE_SMPS 1 +#define REGULATOR_TYPE_LVS 2 +#define REGULATOR_TYPE_NCP 3 + +/* Common masks */ +#define REGULATOR_EN_MASK 0x80 + +#define REGULATOR_BANK_MASK 0xF0 +#define REGULATOR_BANK_SEL(n) ((n) << 4) +#define REGULATOR_BANK_WRITE 0x80 + +#define LDO_TEST_BANKS 7 +#define SMPS_TEST_BANKS 8 +#define REGULATOR_TEST_BANKS_MAX SMPS_TEST_BANKS + +/* LDO programming */ + +/* CTRL register */ +#define LDO_ENABLE_MASK 0x80 +#define LDO_ENABLE 0x80 +#define LDO_PULL_DOWN_ENABLE_MASK 0x40 +#define LDO_PULL_DOWN_ENABLE 0x40 + +#define LDO_CTRL_PM_MASK 0x20 +#define LDO_CTRL_PM_HPM 0x00 +#define LDO_CTRL_PM_LPM 0x20 + +#define LDO_CTRL_VPROG_MASK 0x1F + +/* TEST register bank 0 */ +#define LDO_TEST_LPM_MASK 0x40 +#define LDO_TEST_LPM_SEL_CTRL 0x00 +#define LDO_TEST_LPM_SEL_TCXO 0x40 + +/* TEST register bank 2 */ +#define LDO_TEST_VPROG_UPDATE_MASK 0x08 +#define LDO_TEST_RANGE_SEL_MASK 0x04 +#define LDO_TEST_FINE_STEP_MASK 0x02 +#define LDO_TEST_FINE_STEP_SHIFT 1 + +/* TEST register bank 4 */ +#define LDO_TEST_RANGE_EXT_MASK 0x01 + +/* TEST register bank 5 */ +#define LDO_TEST_PIN_CTRL_MASK 0x0F +#define LDO_TEST_PIN_CTRL_EN3 0x08 +#define LDO_TEST_PIN_CTRL_EN2 0x04 +#define LDO_TEST_PIN_CTRL_EN1 0x02 +#define LDO_TEST_PIN_CTRL_EN0 0x01 + +/* TEST register bank 6 */ +#define LDO_TEST_PIN_CTRL_LPM_MASK 0x0F + +/* Allowable voltage ranges */ +#define PLDO_LOW_UV_MIN 750000 +#define PLDO_LOW_UV_MAX 1537500 +#define PLDO_LOW_FINE_STEP_UV 12500 + +#define PLDO_NORM_UV_MIN 1500000 +#define PLDO_NORM_UV_MAX 3075000 +#define PLDO_NORM_FINE_STEP_UV 25000 + +#define PLDO_HIGH_UV_MIN 1750000 +#define PLDO_HIGH_UV_MAX 4900000 +#define PLDO_HIGH_FINE_STEP_UV 50000 + +#define NLDO_UV_MIN 750000 +#define NLDO_UV_MAX 1537500 +#define NLDO_FINE_STEP_UV 12500 + +/* SMPS masks and values */ + +/* CTRL register */ + +/* Legacy mode */ +#define SMPS_LEGACY_ENABLE 0x80 +#define SMPS_LEGACY_PULL_DOWN_ENABLE 0x40 +#define SMPS_LEGACY_VREF_SEL_MASK 0x20 +#define SMPS_LEGACY_VPROG_MASK 0x1F + +/* Advanced mode */ +#define SMPS_ADVANCED_BAND_MASK 0xC0 +#define SMPS_ADVANCED_BAND_OFF 0x00 +#define SMPS_ADVANCED_BAND_1 0x40 +#define SMPS_ADVANCED_BAND_2 0x80 +#define SMPS_ADVANCED_BAND_3 0xC0 +#define SMPS_ADVANCED_VPROG_MASK 0x3F + +/* Legacy mode voltage ranges */ +#define SMPS_MODE1_UV_MIN 1500000 +#define SMPS_MODE1_UV_MAX 3050000 +#define SMPS_MODE1_UV_STEP 50000 + +#define SMPS_MODE2_UV_MIN 750000 +#define SMPS_MODE2_UV_MAX 1525000 +#define SMPS_MODE2_UV_STEP 25000 + +#define SMPS_MODE3_UV_MIN 375000 +#define SMPS_MODE3_UV_MAX 1150000 +#define SMPS_MODE3_UV_STEP 25000 + +/* Advanced mode voltage ranges */ +#define SMPS_BAND3_UV_MIN 1500000 +#define SMPS_BAND3_UV_MAX 3075000 +#define SMPS_BAND3_UV_STEP 25000 + +#define SMPS_BAND2_UV_MIN 750000 +#define SMPS_BAND2_UV_MAX 1537500 +#define SMPS_BAND2_UV_STEP 12500 + +#define SMPS_BAND1_UV_MIN 375000 +#define SMPS_BAND1_UV_MAX 1162500 +#define SMPS_BAND1_UV_STEP 12500 + +#define SMPS_UV_MIN SMPS_MODE3_UV_MIN +#define SMPS_UV_MAX SMPS_MODE1_UV_MAX + +/* Test2 register bank 1 */ +#define SMPS_LEGACY_VLOW_SEL_MASK 0x01 + +/* Test2 register bank 6 */ +#define SMPS_ADVANCED_PULL_DOWN_ENABLE 0x08 + +/* Test2 register bank 7 */ +#define SMPS_ADVANCED_MODE_MASK 0x02 +#define SMPS_ADVANCED_MODE 0x02 +#define SMPS_LEGACY_MODE 0x00 + +#define SMPS_IN_ADVANCED_MODE(vreg) \ + ((vreg->test_reg[7] & SMPS_ADVANCED_MODE_MASK) == SMPS_ADVANCED_MODE) + +/* BUCK_SLEEP_CNTRL register */ +#define SMPS_PIN_CTRL_MASK 0xF0 +#define SMPS_PIN_CTRL_A1 0x80 +#define SMPS_PIN_CTRL_A0 0x40 +#define SMPS_PIN_CTRL_D1 0x20 +#define SMPS_PIN_CTRL_D0 0x10 + +#define SMPS_PIN_CTRL_LPM_MASK 0x0F +#define SMPS_PIN_CTRL_LPM_A1 0x08 +#define SMPS_PIN_CTRL_LPM_A0 0x04 +#define SMPS_PIN_CTRL_LPM_D1 0x02 +#define SMPS_PIN_CTRL_LPM_D0 0x01 + +/* BUCK_CLOCK_CNTRL register */ +#define SMPS_CLK_DIVIDE2 0x40 + +#define SMPS_CLK_CTRL_MASK 0x30 +#define SMPS_CLK_CTRL_FOLLOW_TCXO 0x00 +#define SMPS_CLK_CTRL_PWM 0x10 +#define SMPS_CLK_CTRL_PFM 0x20 + +/* LVS masks and values */ + +/* CTRL register */ +#define LVS_ENABLE_MASK 0x80 +#define LVS_ENABLE 0x80 +#define LVS_PULL_DOWN_ENABLE_MASK 0x40 +#define LVS_PULL_DOWN_ENABLE 0x00 +#define LVS_PULL_DOWN_DISABLE 0x40 + +#define LVS_PIN_CTRL_MASK 0x0F +#define LVS_PIN_CTRL_EN0 0x08 +#define LVS_PIN_CTRL_EN1 0x04 +#define LVS_PIN_CTRL_EN2 0x02 +#define LVS_PIN_CTRL_EN3 0x01 + +/* NCP masks and values */ + +/* CTRL register */ +#define NCP_VPROG_MASK 0x1F + +#define NCP_UV_MIN 1500000 +#define NCP_UV_MAX 3050000 +#define NCP_UV_STEP 50000 + +#define GLOBAL_ENABLE_MAX (2) +struct pm8058_enable { + u16 addr; + u8 reg; +}; + +struct pm8058_vreg { + struct pm8058_vreg_pdata *pdata; + struct regulator_dev *rdev; + struct pm8058_enable *global_enable[GLOBAL_ENABLE_MAX]; + int hpm_min_load; + int save_uV; + unsigned pc_vote; + unsigned optimum; + unsigned mode_initialized; + u16 ctrl_addr; + u16 test_addr; + u16 clk_ctrl_addr; + u16 sleep_ctrl_addr; + u8 type; + u8 ctrl_reg; + u8 test_reg[REGULATOR_TEST_BANKS_MAX]; + u8 clk_ctrl_reg; + u8 sleep_ctrl_reg; + u8 is_nmos; + u8 global_enable_mask[GLOBAL_ENABLE_MAX]; +}; + +#define LDO_M2(_id, _ctrl_addr, _test_addr, _is_nmos, _hpm_min_load, \ + _en0, _en0_mask, _en1, _en1_mask) \ + [PM8058_VREG_ID_##_id] = { \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .type = REGULATOR_TYPE_LDO, \ + .hpm_min_load = PM8058_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + .is_nmos = _is_nmos, \ + .global_enable = { \ + [0] = _en0, \ + [1] = _en1, \ + }, \ + .global_enable_mask = { \ + [0] = _en0_mask, \ + [1] = _en1_mask, \ + }, \ + } + +#define LDO(_id, _ctrl_addr, _test_addr, _is_nmos, _hpm_min_load, \ + _en0, _en0_mask) \ + LDO_M2(_id, _ctrl_addr, _test_addr, _is_nmos, _hpm_min_load, \ + _en0, _en0_mask, NULL, 0) + +#define SMPS(_id, _ctrl_addr, _test_addr, _clk_ctrl_addr, _sleep_ctrl_addr, \ + _hpm_min_load, _en0, _en0_mask) \ + [PM8058_VREG_ID_##_id] = { \ + .ctrl_addr = _ctrl_addr, \ + .test_addr = _test_addr, \ + .clk_ctrl_addr = _clk_ctrl_addr, \ + .sleep_ctrl_addr = _sleep_ctrl_addr, \ + .type = REGULATOR_TYPE_SMPS, \ + .hpm_min_load = PM8058_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \ + .global_enable = { \ + [0] = _en0, \ + [1] = NULL, \ + }, \ + .global_enable_mask = { \ + [0] = _en0_mask, \ + [1] = 0, \ + }, \ + } + +#define LVS(_id, _ctrl_addr, _en0, _en0_mask) \ + [PM8058_VREG_ID_##_id] = { \ + .ctrl_addr = _ctrl_addr, \ + .type = REGULATOR_TYPE_LVS, \ + .global_enable = { \ + [0] = _en0, \ + [1] = NULL, \ + }, \ + .global_enable_mask = { \ + [0] = _en0_mask, \ + [1] = 0, \ + }, \ + } + +#define NCP(_id, _ctrl_addr, _test1) \ + [PM8058_VREG_ID_##_id] = { \ + .ctrl_addr = _ctrl_addr, \ + .type = REGULATOR_TYPE_NCP, \ + .test_addr = _test1, \ + .global_enable = { \ + [0] = NULL, \ + [1] = NULL, \ + }, \ + .global_enable_mask = { \ + [0] = 0, \ + [1] = 0, \ + }, \ + } + +#define MASTER_ENABLE_COUNT 6 + +#define EN_MSM 0 +#define EN_PH 1 +#define EN_RF 2 +#define EN_GRP_5_4 3 +#define EN_GRP_3_2 4 +#define EN_GRP_1_0 5 + +/* Master regulator control registers */ +static struct pm8058_enable m_en[MASTER_ENABLE_COUNT] = { + [EN_MSM] = { + .addr = 0x018, /* VREG_EN_MSM */ + }, + [EN_PH] = { + .addr = 0x019, /* VREG_EN_PH */ + }, + [EN_RF] = { + .addr = 0x01A, /* VREG_EN_RF */ + }, + [EN_GRP_5_4] = { + .addr = 0x1C8, /* VREG_EN_MSM_GRP_5-4 */ + }, + [EN_GRP_3_2] = { + .addr = 0x1C9, /* VREG_EN_MSM_GRP_3-2 */ + }, + [EN_GRP_1_0] = { + .addr = 0x1CA, /* VREG_EN_MSM_GRP_1-0 */ + }, +}; + + +static struct pm8058_vreg pm8058_vreg[] = { + /* id ctrl test n/p hpm_min m_en m_en_mask */ + LDO(L0, 0x009, 0x065, 1, LDO_150, &m_en[EN_GRP_5_4], BIT(3)), + LDO(L1, 0x00A, 0x066, 1, LDO_300, &m_en[EN_GRP_5_4], BIT(6) | BIT(2)), + LDO(L2, 0x00B, 0x067, 0, LDO_300, &m_en[EN_GRP_3_2], BIT(2)), + LDO(L3, 0x00C, 0x068, 0, LDO_150, &m_en[EN_GRP_1_0], BIT(1)), + LDO(L4, 0x00D, 0x069, 0, LDO_50, &m_en[EN_MSM], 0), + LDO(L5, 0x00E, 0x06A, 0, LDO_300, &m_en[EN_GRP_1_0], BIT(7)), + LDO(L6, 0x00F, 0x06B, 0, LDO_50, &m_en[EN_GRP_1_0], BIT(2)), + LDO(L7, 0x010, 0x06C, 0, LDO_50, &m_en[EN_GRP_3_2], BIT(3)), + LDO(L8, 0x011, 0x06D, 0, LDO_300, &m_en[EN_PH], BIT(7)), + LDO(L9, 0x012, 0x06E, 0, LDO_300, &m_en[EN_GRP_1_0], BIT(3)), + LDO(L10, 0x013, 0x06F, 0, LDO_300, &m_en[EN_GRP_3_2], BIT(4)), + LDO(L11, 0x014, 0x070, 0, LDO_150, &m_en[EN_PH], BIT(4)), + LDO(L12, 0x015, 0x071, 0, LDO_150, &m_en[EN_PH], BIT(3)), + LDO(L13, 0x016, 0x072, 0, LDO_300, &m_en[EN_GRP_3_2], BIT(1)), + LDO(L14, 0x017, 0x073, 0, LDO_300, &m_en[EN_GRP_1_0], BIT(5)), + LDO(L15, 0x089, 0x0E5, 0, LDO_300, &m_en[EN_GRP_1_0], BIT(4)), + LDO(L16, 0x08A, 0x0E6, 0, LDO_300, &m_en[EN_GRP_3_2], BIT(0)), + LDO(L17, 0x08B, 0x0E7, 0, LDO_150, &m_en[EN_RF], BIT(7)), + LDO(L18, 0x11D, 0x125, 0, LDO_150, &m_en[EN_RF], BIT(6)), + LDO(L19, 0x11E, 0x126, 0, LDO_150, &m_en[EN_RF], BIT(5)), + LDO(L20, 0x11F, 0x127, 0, LDO_150, &m_en[EN_RF], BIT(4)), + LDO_M2(L21, 0x120, 0x128, 1, LDO_150, &m_en[EN_GRP_5_4], BIT(1), + &m_en[EN_GRP_1_0], BIT(6)), + LDO(L22, 0x121, 0x129, 1, LDO_300, &m_en[EN_GRP_3_2], BIT(7)), + LDO(L23, 0x122, 0x12A, 1, LDO_300, &m_en[EN_GRP_5_4], BIT(0)), + LDO(L24, 0x123, 0x12B, 1, LDO_150, &m_en[EN_RF], BIT(3)), + LDO(L25, 0x124, 0x12C, 1, LDO_150, &m_en[EN_RF], BIT(2)), + + /* id ctrl test2 clk sleep hpm_min m_en m_en_mask */ + SMPS(S0, 0x004, 0x084, 0x1D1, 0x1D8, SMPS, &m_en[EN_MSM], BIT(7)), + SMPS(S1, 0x005, 0x085, 0x1D2, 0x1DB, SMPS, &m_en[EN_MSM], BIT(6)), + SMPS(S2, 0x110, 0x119, 0x1D3, 0x1DE, SMPS, &m_en[EN_GRP_5_4], BIT(5)), + SMPS(S3, 0x111, 0x11A, 0x1D4, 0x1E1, SMPS, &m_en[EN_GRP_5_4], + BIT(7) | BIT(4)), + SMPS(S4, 0x112, 0x11B, 0x1D5, 0x1E4, SMPS, &m_en[EN_GRP_3_2], BIT(5)), + + /* id ctrl m_en m_en_mask */ + LVS(LVS0, 0x12D, &m_en[EN_RF], BIT(1)), + LVS(LVS1, 0x12F, &m_en[EN_GRP_1_0], BIT(0)), + + /* id ctrl test1 */ + NCP(NCP, 0x090, 0x0EC), +}; + +static int pm8058_smps_set_voltage_advanced(struct pm8058_vreg *vreg, + struct pm8058_chip *chip, int uV, + int force_on); +static int pm8058_smps_set_voltage_legacy(struct pm8058_vreg *vreg, + struct pm8058_chip *chip, int uV); +static int _pm8058_vreg_is_enabled(struct pm8058_vreg *vreg); + +static unsigned int pm8058_vreg_get_mode(struct regulator_dev *dev); + +static void print_write_error(struct pm8058_vreg *vreg, int rc, + const char *func); + +static int pm8058_vreg_write(struct pm8058_chip *chip, + u16 addr, u8 val, u8 mask, u8 *reg_save) +{ + int rc = 0; + u8 reg; + + reg = (*reg_save & ~mask) | (val & mask); + if (reg != *reg_save) + rc = pm8058_write(chip, addr, ®, 1); + if (rc) + pr_err("%s: pm8058_write failed, rc=%d\n", __func__, rc); + else + *reg_save = reg; + return rc; +} + +static int pm8058_vreg_is_global_enabled(struct pm8058_vreg *vreg) +{ + int ret = 0, i; + + for (i = 0; + (i < GLOBAL_ENABLE_MAX) && !ret && vreg->global_enable[i]; i++) + ret = vreg->global_enable[i]->reg & + vreg->global_enable_mask[i]; + + return ret; +} + + +static int pm8058_vreg_set_global_enable(struct pm8058_vreg *vreg, + struct pm8058_chip *chip, int on) +{ + int rc = 0, i; + + for (i = 0; + (i < GLOBAL_ENABLE_MAX) && !rc && vreg->global_enable[i]; i++) + rc = pm8058_vreg_write(chip, vreg->global_enable[i]->addr, + (on ? vreg->global_enable_mask[i] : 0), + vreg->global_enable_mask[i], + &vreg->global_enable[i]->reg); + + return rc; +} + +static int pm8058_vreg_using_pin_ctrl(struct pm8058_vreg *vreg) +{ + int ret = 0; + + switch (vreg->type) { + case REGULATOR_TYPE_LDO: + ret = ((vreg->test_reg[5] & LDO_TEST_PIN_CTRL_MASK) << 4) + | (vreg->test_reg[6] & LDO_TEST_PIN_CTRL_LPM_MASK); + break; + case REGULATOR_TYPE_SMPS: + ret = vreg->sleep_ctrl_reg + & (SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK); + break; + case REGULATOR_TYPE_LVS: + ret = vreg->ctrl_reg & LVS_PIN_CTRL_MASK; + break; + } + + return ret; +} + +static int pm8058_vreg_set_pin_ctrl(struct pm8058_vreg *vreg, + struct pm8058_chip *chip, int on) +{ + int rc = 0, bank; + u8 val = 0, mask; + unsigned pc = vreg->pdata->pin_ctrl; + unsigned pf = vreg->pdata->pin_fn; + + switch (vreg->type) { + case REGULATOR_TYPE_LDO: + if (on) { + if (pc & PM8058_VREG_PIN_CTRL_D0) + val |= LDO_TEST_PIN_CTRL_EN0; + if (pc & PM8058_VREG_PIN_CTRL_D1) + val |= LDO_TEST_PIN_CTRL_EN1; + if (pc & PM8058_VREG_PIN_CTRL_A0) + val |= LDO_TEST_PIN_CTRL_EN2; + if (pc & PM8058_VREG_PIN_CTRL_A1) + val |= LDO_TEST_PIN_CTRL_EN3; + + bank = (pf == PM8058_VREG_PIN_FN_ENABLE ? 5 : 6); + rc = pm8058_vreg_write(chip, vreg->test_addr, + val | REGULATOR_BANK_SEL(bank) + | REGULATOR_BANK_WRITE, + LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[bank]); + if (rc) + goto bail; + + val = LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(0); + mask = LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK; + rc = pm8058_vreg_write(chip, vreg->test_addr, val, mask, + &vreg->test_reg[0]); + if (rc) + goto bail; + + if (pf == PM8058_VREG_PIN_FN_ENABLE) { + /* Pin control ON/OFF */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, + LDO_CTRL_PM_HPM, + LDO_ENABLE_MASK | LDO_CTRL_PM_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + rc = pm8058_vreg_set_global_enable(vreg, chip, + 0); + if (rc) + goto bail; + } else { + /* Pin control LPM/HPM */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, + LDO_ENABLE | LDO_CTRL_PM_LPM, + LDO_ENABLE_MASK | LDO_CTRL_PM_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + } + } else { + /* Pin control off */ + rc = pm8058_vreg_write(chip, vreg->test_addr, + REGULATOR_BANK_SEL(5) | REGULATOR_BANK_WRITE, + LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[5]); + if (rc) + goto bail; + + rc = pm8058_vreg_write(chip, vreg->test_addr, + REGULATOR_BANK_SEL(6) | REGULATOR_BANK_WRITE, + LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[6]); + if (rc) + goto bail; + } + break; + + case REGULATOR_TYPE_SMPS: + if (on) { + if (pf == PM8058_VREG_PIN_FN_ENABLE) { + /* Pin control ON/OFF */ + if (pc & PM8058_VREG_PIN_CTRL_D0) + val |= SMPS_PIN_CTRL_D0; + if (pc & PM8058_VREG_PIN_CTRL_D1) + val |= SMPS_PIN_CTRL_D1; + if (pc & PM8058_VREG_PIN_CTRL_A0) + val |= SMPS_PIN_CTRL_A0; + if (pc & PM8058_VREG_PIN_CTRL_A1) + val |= SMPS_PIN_CTRL_A1; + } else { + /* Pin control LPM/HPM */ + if (pc & PM8058_VREG_PIN_CTRL_D0) + val |= SMPS_PIN_CTRL_LPM_D0; + if (pc & PM8058_VREG_PIN_CTRL_D1) + val |= SMPS_PIN_CTRL_LPM_D1; + if (pc & PM8058_VREG_PIN_CTRL_A0) + val |= SMPS_PIN_CTRL_LPM_A0; + if (pc & PM8058_VREG_PIN_CTRL_A1) + val |= SMPS_PIN_CTRL_LPM_A1; + } + rc = pm8058_vreg_set_global_enable(vreg, chip, 0); + if (rc) + goto bail; + + rc = pm8058_smps_set_voltage_legacy(vreg, chip, + vreg->save_uV); + if (rc) + goto bail; + + rc = pm8058_vreg_write(chip, vreg->sleep_ctrl_addr, val, + SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK, + &vreg->sleep_ctrl_reg); + if (rc) + goto bail; + + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, + (pf == PM8058_VREG_PIN_FN_ENABLE + ? 0 : SMPS_LEGACY_ENABLE), + SMPS_LEGACY_ENABLE, &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8058_vreg_write(chip, vreg->clk_ctrl_addr, + (pf == PM8058_VREG_PIN_FN_ENABLE + ? SMPS_CLK_CTRL_PWM : SMPS_CLK_CTRL_PFM), + SMPS_CLK_CTRL_MASK, &vreg->clk_ctrl_reg); + if (rc) + goto bail; + } else { + /* Pin control off */ + if (!SMPS_IN_ADVANCED_MODE(vreg)) { + if (_pm8058_vreg_is_enabled(vreg)) + val = SMPS_LEGACY_ENABLE; + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, + val, SMPS_LEGACY_ENABLE, + &vreg->ctrl_reg); + if (rc) + goto bail; + } + + rc = pm8058_vreg_write(chip, vreg->sleep_ctrl_addr, 0, + SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK, + &vreg->sleep_ctrl_reg); + if (rc) + goto bail; + + rc = pm8058_smps_set_voltage_advanced(vreg, chip, + vreg->save_uV, 0); + if (rc) + goto bail; + } + break; + + case REGULATOR_TYPE_LVS: + if (on) { + if (pc & PM8058_VREG_PIN_CTRL_D0) + val |= LVS_PIN_CTRL_EN0; + if (pc & PM8058_VREG_PIN_CTRL_D1) + val |= LVS_PIN_CTRL_EN1; + if (pc & PM8058_VREG_PIN_CTRL_A0) + val |= LVS_PIN_CTRL_EN2; + if (pc & PM8058_VREG_PIN_CTRL_A1) + val |= LVS_PIN_CTRL_EN3; + + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, val, + LVS_PIN_CTRL_MASK | LVS_ENABLE_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8058_vreg_set_global_enable(vreg, chip, 0); + if (rc) + goto bail; + } else { + /* Pin control off */ + if (_pm8058_vreg_is_enabled(vreg)) + val = LVS_ENABLE; + + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, val, + LVS_ENABLE_MASK | LVS_PIN_CTRL_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + + } + break; + } + +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8058_vreg_enable(struct regulator_dev *dev) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + struct pm8058_chip *chip = dev_get_drvdata(dev->dev.parent); + int mode; + int rc = 0; + + mode = pm8058_vreg_get_mode(dev); + + if (mode == REGULATOR_MODE_IDLE) { + /* Turn on pin control. */ + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 1); + if (rc) + goto bail; + return rc; + } + if (vreg->type == REGULATOR_TYPE_SMPS && SMPS_IN_ADVANCED_MODE(vreg)) + rc = pm8058_smps_set_voltage_advanced(vreg, chip, + vreg->save_uV, 1); + else + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, REGULATOR_EN_MASK, + REGULATOR_EN_MASK, &vreg->ctrl_reg); +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int _pm8058_vreg_is_enabled(struct pm8058_vreg *vreg) +{ + /* + * All regulator types except advanced mode SMPS have enable bit in + * bit 7 of the control register. Global enable and pin control also + * do not work for advanced mode SMPS. + */ + if (!(vreg->type == REGULATOR_TYPE_SMPS && SMPS_IN_ADVANCED_MODE(vreg)) + && ((vreg->ctrl_reg & REGULATOR_EN_MASK) + || pm8058_vreg_is_global_enabled(vreg) + || pm8058_vreg_using_pin_ctrl(vreg))) + return 1; + else if (vreg->type == REGULATOR_TYPE_SMPS + && SMPS_IN_ADVANCED_MODE(vreg) + && ((vreg->ctrl_reg & SMPS_ADVANCED_BAND_MASK) + != SMPS_ADVANCED_BAND_OFF)) + return 1; + + return 0; +} + +static int pm8058_vreg_is_enabled(struct regulator_dev *dev) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + + return _pm8058_vreg_is_enabled(vreg); +} + +static int pm8058_vreg_disable(struct regulator_dev *dev) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + struct pm8058_chip *chip = dev_get_drvdata(dev->dev.parent); + int rc = 0; + + /* Disable in global control register. */ + rc = pm8058_vreg_set_global_enable(vreg, chip, 0); + if (rc) + goto bail; + + /* Turn off pin control. */ + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 0); + if (rc) + goto bail; + + /* Disable in local control register. */ + if (vreg->type == REGULATOR_TYPE_SMPS && SMPS_IN_ADVANCED_MODE(vreg)) + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, + SMPS_ADVANCED_BAND_OFF, SMPS_ADVANCED_BAND_MASK, + &vreg->ctrl_reg); + else + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, 0, + REGULATOR_EN_MASK, &vreg->ctrl_reg); + +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8058_pldo_set_voltage(struct pm8058_chip *chip, + struct pm8058_vreg *vreg, int uV) +{ + int vmin, rc = 0; + unsigned vprog, fine_step; + u8 range_ext, range_sel, fine_step_reg; + + if (uV < PLDO_LOW_UV_MIN || uV > PLDO_HIGH_UV_MAX) + return -EINVAL; + + if (uV < PLDO_LOW_UV_MAX + PLDO_LOW_FINE_STEP_UV) { + vmin = PLDO_LOW_UV_MIN; + fine_step = PLDO_LOW_FINE_STEP_UV; + range_ext = 0; + range_sel = LDO_TEST_RANGE_SEL_MASK; + } else if (uV < PLDO_NORM_UV_MAX + PLDO_NORM_FINE_STEP_UV) { + vmin = PLDO_NORM_UV_MIN; + fine_step = PLDO_NORM_FINE_STEP_UV; + range_ext = 0; + range_sel = 0; + } else { + vmin = PLDO_HIGH_UV_MIN; + fine_step = PLDO_HIGH_FINE_STEP_UV; + range_ext = LDO_TEST_RANGE_EXT_MASK; + range_sel = 0; + } + + vprog = (uV - vmin) / fine_step; + fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT; + vprog >>= 1; + + /* + * Disable program voltage update if range extension, range select, + * or fine step have changed and the regulator is enabled. + */ + if (_pm8058_vreg_is_enabled(vreg) && + (((range_ext ^ vreg->test_reg[4]) & LDO_TEST_RANGE_EXT_MASK) + || ((range_sel ^ vreg->test_reg[2]) & LDO_TEST_RANGE_SEL_MASK) + || ((fine_step_reg ^ vreg->test_reg[2]) + & LDO_TEST_FINE_STEP_MASK))) { + rc = pm8058_vreg_write(chip, vreg->test_addr, + REGULATOR_BANK_SEL(2) | REGULATOR_BANK_WRITE, + REGULATOR_BANK_MASK | LDO_TEST_VPROG_UPDATE_MASK, + &vreg->test_reg[2]); + if (rc) + goto bail; + } + + /* Write new voltage. */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, vprog, + LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Write range extension. */ + rc = pm8058_vreg_write(chip, vreg->test_addr, + range_ext | REGULATOR_BANK_SEL(4) + | REGULATOR_BANK_WRITE, + LDO_TEST_RANGE_EXT_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[4]); + if (rc) + goto bail; + + /* Write fine step, range select and program voltage update. */ + rc = pm8058_vreg_write(chip, vreg->test_addr, + fine_step_reg | range_sel | REGULATOR_BANK_SEL(2) + | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK, + LDO_TEST_FINE_STEP_MASK | LDO_TEST_RANGE_SEL_MASK + | REGULATOR_BANK_MASK | LDO_TEST_VPROG_UPDATE_MASK, + &vreg->test_reg[2]); +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8058_nldo_set_voltage(struct pm8058_chip *chip, + struct pm8058_vreg *vreg, int uV) +{ + unsigned vprog, fine_step_reg; + int rc; + + if (uV < NLDO_UV_MIN || uV > NLDO_UV_MAX) + return -EINVAL; + + vprog = (uV - NLDO_UV_MIN) / NLDO_FINE_STEP_UV; + fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT; + vprog >>= 1; + + /* Write new voltage. */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, vprog, + LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Write fine step. */ + rc = pm8058_vreg_write(chip, vreg->test_addr, + fine_step_reg | REGULATOR_BANK_SEL(2) + | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK, + LDO_TEST_FINE_STEP_MASK | REGULATOR_BANK_MASK + | LDO_TEST_VPROG_UPDATE_MASK, + &vreg->test_reg[2]); +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8058_ldo_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + struct pm8058_chip *chip = dev_get_drvdata(dev->dev.parent); + + if (vreg->is_nmos) + return pm8058_nldo_set_voltage(chip, vreg, min_uV); + else + return pm8058_pldo_set_voltage(chip, vreg, min_uV); +} + +static int pm8058_pldo_get_voltage(struct pm8058_vreg *vreg) +{ + int vmin, fine_step; + u8 range_ext, range_sel, vprog, fine_step_reg; + + fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK; + range_sel = vreg->test_reg[2] & LDO_TEST_RANGE_SEL_MASK; + range_ext = vreg->test_reg[4] & LDO_TEST_RANGE_EXT_MASK; + vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK; + + vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT); + + if (range_sel) { + /* low range mode */ + fine_step = PLDO_LOW_FINE_STEP_UV; + vmin = PLDO_LOW_UV_MIN; + } else if (!range_ext) { + /* normal mode */ + fine_step = PLDO_NORM_FINE_STEP_UV; + vmin = PLDO_NORM_UV_MIN; + } else { + /* high range mode */ + fine_step = PLDO_HIGH_FINE_STEP_UV; + vmin = PLDO_HIGH_UV_MIN; + } + + return fine_step * vprog + vmin; +} + +static int pm8058_nldo_get_voltage(struct pm8058_vreg *vreg) +{ + u8 vprog, fine_step_reg; + + fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK; + vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK; + + vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT); + + return NLDO_FINE_STEP_UV * vprog + NLDO_UV_MIN; +} + +static int pm8058_ldo_get_voltage(struct regulator_dev *dev) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + + if (vreg->is_nmos) + return pm8058_nldo_get_voltage(vreg); + else + return pm8058_pldo_get_voltage(vreg); +} + +static int pm8058_smps_get_voltage_advanced(struct pm8058_vreg *vreg) +{ + u8 vprog, band; + int uV = 0; + + vprog = vreg->ctrl_reg & SMPS_ADVANCED_VPROG_MASK; + band = vreg->ctrl_reg & SMPS_ADVANCED_BAND_MASK; + + if (band == SMPS_ADVANCED_BAND_1) + uV = vprog * SMPS_BAND1_UV_STEP + SMPS_BAND1_UV_MIN; + else if (band == SMPS_ADVANCED_BAND_2) + uV = vprog * SMPS_BAND2_UV_STEP + SMPS_BAND2_UV_MIN; + else if (band == SMPS_ADVANCED_BAND_3) + uV = vprog * SMPS_BAND3_UV_STEP + SMPS_BAND3_UV_MIN; + else + uV = vreg->save_uV; + + return uV; +} + +static int pm8058_smps_get_voltage_legacy(struct pm8058_vreg *vreg) +{ + u8 vlow, vref, vprog; + int uV; + + vlow = vreg->test_reg[1] & SMPS_LEGACY_VLOW_SEL_MASK; + vref = vreg->ctrl_reg & SMPS_LEGACY_VREF_SEL_MASK; + vprog = vreg->ctrl_reg & SMPS_LEGACY_VPROG_MASK; + + if (vlow && vref) { + /* mode 3 */ + uV = vprog * SMPS_MODE3_UV_STEP + SMPS_MODE3_UV_MIN; + } else if (vref) { + /* mode 2 */ + uV = vprog * SMPS_MODE2_UV_STEP + SMPS_MODE2_UV_MIN; + } else { + /* mode 1 */ + uV = vprog * SMPS_MODE1_UV_STEP + SMPS_MODE1_UV_MIN; + } + + return uV; +} + +static int _pm8058_smps_get_voltage(struct pm8058_vreg *vreg) +{ + if (SMPS_IN_ADVANCED_MODE(vreg)) + return pm8058_smps_get_voltage_advanced(vreg); + + return pm8058_smps_get_voltage_legacy(vreg); +} + +static int pm8058_smps_get_voltage(struct regulator_dev *dev) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + + return _pm8058_smps_get_voltage(vreg); +} + +static int pm8058_smps_set_voltage_advanced(struct pm8058_vreg *vreg, + struct pm8058_chip *chip, int uV, + int force_on) +{ + u8 vprog, band; + int rc, new_uV; + + if (uV < SMPS_BAND1_UV_MAX + SMPS_BAND1_UV_STEP) { + vprog = ((uV - SMPS_BAND1_UV_MIN) / SMPS_BAND1_UV_STEP); + band = SMPS_ADVANCED_BAND_1; + new_uV = SMPS_BAND1_UV_MIN + vprog * SMPS_BAND1_UV_STEP; + } else if (uV < SMPS_BAND2_UV_MAX + SMPS_BAND2_UV_STEP) { + vprog = ((uV - SMPS_BAND2_UV_MIN) / SMPS_BAND2_UV_STEP); + band = SMPS_ADVANCED_BAND_2; + new_uV = SMPS_BAND2_UV_MIN + vprog * SMPS_BAND2_UV_STEP; + } else { + vprog = ((uV - SMPS_BAND3_UV_MIN) / SMPS_BAND3_UV_STEP); + band = SMPS_ADVANCED_BAND_3; + new_uV = SMPS_BAND3_UV_MIN + vprog * SMPS_BAND3_UV_STEP; + } + + /* Do not set band if regulator currently disabled. */ + if (!_pm8058_vreg_is_enabled(vreg) && !force_on) + band = SMPS_ADVANCED_BAND_OFF; + + /* Set advanced mode bit to 1. */ + rc = pm8058_vreg_write(chip, vreg->test_addr, SMPS_ADVANCED_MODE + | REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(7), + SMPS_ADVANCED_MODE_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[7]); + if (rc) + goto bail; + + /* Set voltage and voltage band. */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, band | vprog, + SMPS_ADVANCED_BAND_MASK | SMPS_ADVANCED_VPROG_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + + vreg->save_uV = new_uV; + +bail: + return rc; +} + +static int pm8058_smps_set_voltage_legacy(struct pm8058_vreg *vreg, + struct pm8058_chip *chip, int uV) +{ + u8 vlow, vref, vprog, pd, en; + int rc; + + if (uV < SMPS_MODE3_UV_MAX + SMPS_MODE3_UV_STEP) { + vprog = ((uV - SMPS_MODE3_UV_MIN) / SMPS_MODE3_UV_STEP); + vref = SMPS_LEGACY_VREF_SEL_MASK; + vlow = SMPS_LEGACY_VLOW_SEL_MASK; + } else if (uV < SMPS_MODE2_UV_MAX + SMPS_MODE2_UV_STEP) { + vprog = ((uV - SMPS_MODE2_UV_MIN) / SMPS_MODE2_UV_STEP); + vref = SMPS_LEGACY_VREF_SEL_MASK; + vlow = 0; + } else { + vprog = ((uV - SMPS_MODE1_UV_MIN) / SMPS_MODE1_UV_STEP); + vref = 0; + vlow = 0; + } + + /* set vlow bit for ultra low voltage mode */ + rc = pm8058_vreg_write(chip, vreg->test_addr, + vlow | REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(1), + REGULATOR_BANK_MASK | SMPS_LEGACY_VLOW_SEL_MASK, + &vreg->test_reg[1]); + if (rc) + goto bail; + + /* Set advanced mode bit to 0. */ + rc = pm8058_vreg_write(chip, vreg->test_addr, SMPS_LEGACY_MODE + | REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(7), + SMPS_ADVANCED_MODE_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[7]); + if (rc) + goto bail; + + en = (_pm8058_vreg_is_enabled(vreg) ? SMPS_LEGACY_ENABLE : 0); + pd = (vreg->pdata->pull_down_enable ? SMPS_LEGACY_PULL_DOWN_ENABLE : 0); + + /* Set voltage (and the rest of the control register). */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, en | pd | vref | vprog, + SMPS_LEGACY_ENABLE | SMPS_LEGACY_PULL_DOWN_ENABLE + | SMPS_LEGACY_VREF_SEL_MASK | SMPS_LEGACY_VPROG_MASK, + &vreg->ctrl_reg); + + vreg->save_uV = pm8058_smps_get_voltage_legacy(vreg); + +bail: + return rc; +} + +static int pm8058_smps_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + struct pm8058_chip *chip = dev_get_drvdata(dev->dev.parent); + int rc = 0; + + if (min_uV < SMPS_UV_MIN || min_uV > SMPS_UV_MAX) + return -EINVAL; + + if (SMPS_IN_ADVANCED_MODE(vreg)) + rc = pm8058_smps_set_voltage_advanced(vreg, chip, min_uV, 0); + else + rc = pm8058_smps_set_voltage_legacy(vreg, chip, min_uV); + + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8058_ncp_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + struct pm8058_chip *chip = dev_get_drvdata(dev->dev.parent); + int rc; + u8 val; + + if (min_uV < NCP_UV_MIN || min_uV > NCP_UV_MAX) + return -EINVAL; + + val = (min_uV - NCP_UV_MIN) / NCP_UV_STEP; + + /* voltage setting */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, val, NCP_VPROG_MASK, + &vreg->ctrl_reg); + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8058_ncp_get_voltage(struct regulator_dev *dev) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + u8 vprog = vreg->ctrl_reg & NCP_VPROG_MASK; + return NCP_UV_MIN + vprog * NCP_UV_STEP; +} + +static int pm8058_ldo_set_mode(struct pm8058_vreg *vreg, + struct pm8058_chip *chip, unsigned int mode) +{ + int rc = 0; + u8 mask, val; + + switch (mode) { + case REGULATOR_MODE_FAST: + /* HPM */ + val = (_pm8058_vreg_is_enabled(vreg) ? LDO_ENABLE : 0) + | LDO_CTRL_PM_HPM; + mask = LDO_ENABLE_MASK | LDO_CTRL_PM_MASK; + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, val, mask, + &vreg->ctrl_reg); + if (rc) + goto bail; + + if (pm8058_vreg_using_pin_ctrl(vreg)) + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 0); + if (rc) + goto bail; + break; + + case REGULATOR_MODE_STANDBY: + /* LPM */ + val = (_pm8058_vreg_is_enabled(vreg) ? LDO_ENABLE : 0) + | LDO_CTRL_PM_LPM; + mask = LDO_ENABLE_MASK | LDO_CTRL_PM_MASK; + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, val, mask, + &vreg->ctrl_reg); + if (rc) + goto bail; + + val = LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE + | REGULATOR_BANK_SEL(0); + mask = LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK; + rc = pm8058_vreg_write(chip, vreg->test_addr, val, mask, + &vreg->test_reg[0]); + if (rc) + goto bail; + + if (pm8058_vreg_using_pin_ctrl(vreg)) + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 0); + if (rc) + goto bail; + break; + + case REGULATOR_MODE_IDLE: + /* Pin Control */ + if (_pm8058_vreg_is_enabled(vreg)) + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 1); + if (rc) + goto bail; + break; + + default: + pr_err("%s: invalid mode: %u\n", __func__, mode); + return -EINVAL; + } + +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8058_smps_set_mode(struct pm8058_vreg *vreg, + struct pm8058_chip *chip, unsigned int mode) +{ + int rc = 0; + u8 mask, val; + + switch (mode) { + case REGULATOR_MODE_FAST: + /* HPM */ + val = SMPS_CLK_CTRL_PWM; + mask = SMPS_CLK_CTRL_MASK; + rc = pm8058_vreg_write(chip, vreg->clk_ctrl_addr, val, mask, + &vreg->clk_ctrl_reg); + if (rc) + goto bail; + + if (pm8058_vreg_using_pin_ctrl(vreg)) + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 0); + if (rc) + goto bail; + break; + + case REGULATOR_MODE_STANDBY: + /* LPM */ + val = SMPS_CLK_CTRL_PFM; + mask = SMPS_CLK_CTRL_MASK; + rc = pm8058_vreg_write(chip, vreg->clk_ctrl_addr, val, mask, + &vreg->clk_ctrl_reg); + if (rc) + goto bail; + + if (pm8058_vreg_using_pin_ctrl(vreg)) + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 0); + if (rc) + goto bail; + break; + + case REGULATOR_MODE_IDLE: + /* Pin Control */ + if (_pm8058_vreg_is_enabled(vreg)) + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 1); + if (rc) + goto bail; + break; + + default: + pr_err("%s: invalid mode: %u\n", __func__, mode); + return -EINVAL; + } + +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8058_lvs_set_mode(struct pm8058_vreg *vreg, + struct pm8058_chip *chip, unsigned int mode) +{ + int rc = 0; + + if (mode == REGULATOR_MODE_IDLE) { + /* Use pin control. */ + if (_pm8058_vreg_is_enabled(vreg)) + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 1); + } else { + /* Turn off pin control. */ + rc = pm8058_vreg_set_pin_ctrl(vreg, chip, 0); + } + + return rc; +} + +/* + * Optimum mode programming: + * REGULATOR_MODE_FAST: Go to HPM (highest priority) + * REGULATOR_MODE_STANDBY: Go to pin ctrl mode if there are any pin ctrl + * votes, else go to LPM + * + * Pin ctrl mode voting via regulator set_mode: + * REGULATOR_MODE_IDLE: Go to pin ctrl mode if the optimum mode is LPM, else + * go to HPM + * REGULATOR_MODE_NORMAL: Go to LPM if it is the optimum mode, else go to HPM + */ +static int pm8058_vreg_set_mode(struct regulator_dev *dev, unsigned int mode) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + struct pm8058_chip *chip = dev_get_drvdata(dev->dev.parent); + unsigned prev_optimum = vreg->optimum; + unsigned prev_pc_vote = vreg->pc_vote; + unsigned prev_mode_initialized = vreg->mode_initialized; + int new_mode = REGULATOR_MODE_FAST; + int rc = 0; + + /* Determine new mode to go into. */ + switch (mode) { + case REGULATOR_MODE_FAST: + new_mode = REGULATOR_MODE_FAST; + vreg->optimum = mode; + vreg->mode_initialized = 1; + break; + + case REGULATOR_MODE_STANDBY: + if (vreg->pc_vote) + new_mode = REGULATOR_MODE_IDLE; + else + new_mode = REGULATOR_MODE_STANDBY; + vreg->optimum = mode; + vreg->mode_initialized = 1; + break; + + case REGULATOR_MODE_IDLE: + if (vreg->pc_vote++) + goto done; /* already taken care of */ + + if (vreg->mode_initialized + && vreg->optimum == REGULATOR_MODE_FAST) + new_mode = REGULATOR_MODE_FAST; + else + new_mode = REGULATOR_MODE_IDLE; + break; + + case REGULATOR_MODE_NORMAL: + if (vreg->pc_vote && --(vreg->pc_vote)) + goto done; /* already taken care of */ + + if (vreg->optimum == REGULATOR_MODE_STANDBY) + new_mode = REGULATOR_MODE_STANDBY; + else + new_mode = REGULATOR_MODE_FAST; + break; + + default: + pr_err("%s: unknown mode, mode=%u\n", __func__, mode); + return -EINVAL; + } + + switch (vreg->type) { + case REGULATOR_TYPE_LDO: + rc = pm8058_ldo_set_mode(vreg, chip, new_mode); + break; + case REGULATOR_TYPE_SMPS: + rc = pm8058_smps_set_mode(vreg, chip, new_mode); + break; + case REGULATOR_TYPE_LVS: + rc = pm8058_lvs_set_mode(vreg, chip, new_mode); + break; + } + + if (rc) { + print_write_error(vreg, rc, __func__); + vreg->mode_initialized = prev_mode_initialized; + vreg->optimum = prev_optimum; + vreg->pc_vote = prev_pc_vote; + return rc; + } + +done: + return 0; +} + +static unsigned int pm8058_vreg_get_mode(struct regulator_dev *dev) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + + if (!vreg->mode_initialized && vreg->pc_vote) + return REGULATOR_MODE_IDLE; + + /* Check physical pin control state. */ + switch (vreg->type) { + case REGULATOR_TYPE_LDO: + if (!(vreg->ctrl_reg & LDO_ENABLE_MASK) + && !pm8058_vreg_is_global_enabled(vreg) + && (vreg->test_reg[5] & LDO_TEST_PIN_CTRL_MASK)) + return REGULATOR_MODE_IDLE; + else if (((vreg->ctrl_reg & LDO_ENABLE_MASK) + || pm8058_vreg_is_global_enabled(vreg)) + && (vreg->ctrl_reg & LDO_CTRL_PM_MASK) + && (vreg->test_reg[6] & LDO_TEST_PIN_CTRL_LPM_MASK)) + return REGULATOR_MODE_IDLE; + break; + case REGULATOR_TYPE_SMPS: + if (!SMPS_IN_ADVANCED_MODE(vreg) + && !(vreg->ctrl_reg & REGULATOR_EN_MASK) + && !pm8058_vreg_is_global_enabled(vreg) + && (vreg->sleep_ctrl_reg & SMPS_PIN_CTRL_MASK)) + return REGULATOR_MODE_IDLE; + else if (!SMPS_IN_ADVANCED_MODE(vreg) + && ((vreg->ctrl_reg & REGULATOR_EN_MASK) + || pm8058_vreg_is_global_enabled(vreg)) + && ((vreg->clk_ctrl_reg & SMPS_CLK_CTRL_MASK) + == SMPS_CLK_CTRL_PFM) + && (vreg->sleep_ctrl_reg & SMPS_PIN_CTRL_LPM_MASK)) + return REGULATOR_MODE_IDLE; + break; + case REGULATOR_TYPE_LVS: + if (!(vreg->ctrl_reg & LVS_ENABLE_MASK) + && !pm8058_vreg_is_global_enabled(vreg) + && (vreg->ctrl_reg & LVS_PIN_CTRL_MASK)) + return REGULATOR_MODE_IDLE; + } + + if (vreg->optimum == REGULATOR_MODE_FAST) + return REGULATOR_MODE_FAST; + else if (vreg->pc_vote) + return REGULATOR_MODE_IDLE; + else if (vreg->optimum == REGULATOR_MODE_STANDBY) + return REGULATOR_MODE_STANDBY; + return REGULATOR_MODE_FAST; +} + +unsigned int pm8058_vreg_get_optimum_mode(struct regulator_dev *dev, + int input_uV, int output_uV, int load_uA) +{ + struct pm8058_vreg *vreg = rdev_get_drvdata(dev); + + if (load_uA <= 0) { + /* + * pm8058_vreg_get_optimum_mode is being called before consumers + * have specified their load currents via + * regulator_set_optimum_mode. Return whatever the existing mode + * is. + */ + return pm8058_vreg_get_mode(dev); + } + + if (load_uA >= vreg->hpm_min_load) + return REGULATOR_MODE_FAST; + return REGULATOR_MODE_STANDBY; +} + +static struct regulator_ops pm8058_ldo_ops = { + .enable = pm8058_vreg_enable, + .disable = pm8058_vreg_disable, + .is_enabled = pm8058_vreg_is_enabled, + .set_voltage = pm8058_ldo_set_voltage, + .get_voltage = pm8058_ldo_get_voltage, + .set_mode = pm8058_vreg_set_mode, + .get_mode = pm8058_vreg_get_mode, + .get_optimum_mode = pm8058_vreg_get_optimum_mode, +}; + +static struct regulator_ops pm8058_smps_ops = { + .enable = pm8058_vreg_enable, + .disable = pm8058_vreg_disable, + .is_enabled = pm8058_vreg_is_enabled, + .set_voltage = pm8058_smps_set_voltage, + .get_voltage = pm8058_smps_get_voltage, + .set_mode = pm8058_vreg_set_mode, + .get_mode = pm8058_vreg_get_mode, + .get_optimum_mode = pm8058_vreg_get_optimum_mode, +}; + +static struct regulator_ops pm8058_lvs_ops = { + .enable = pm8058_vreg_enable, + .disable = pm8058_vreg_disable, + .is_enabled = pm8058_vreg_is_enabled, + .set_mode = pm8058_vreg_set_mode, + .get_mode = pm8058_vreg_get_mode, +}; + +static struct regulator_ops pm8058_ncp_ops = { + .enable = pm8058_vreg_enable, + .disable = pm8058_vreg_disable, + .is_enabled = pm8058_vreg_is_enabled, + .set_voltage = pm8058_ncp_set_voltage, + .get_voltage = pm8058_ncp_get_voltage, +}; + +#define VREG_DESCRIP(_id, _name, _ops) \ + [_id] = { \ + .id = _id, \ + .name = _name, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static struct regulator_desc pm8058_vreg_descrip[] = { + VREG_DESCRIP(PM8058_VREG_ID_L0, "8058_l0", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L1, "8058_l1", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L2, "8058_l2", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L3, "8058_l3", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L4, "8058_l4", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L5, "8058_l5", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L6, "8058_l6", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L7, "8058_l7", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L8, "8058_l8", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L9, "8058_l9", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L10, "8058_l10", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L11, "8058_l11", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L12, "8058_l12", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L13, "8058_l13", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L14, "8058_l14", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L15, "8058_l15", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L16, "8058_l16", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L17, "8058_l17", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L18, "8058_l18", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L19, "8058_l19", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L20, "8058_l20", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L21, "8058_l21", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L22, "8058_l22", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L23, "8058_l23", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L24, "8058_l24", &pm8058_ldo_ops), + VREG_DESCRIP(PM8058_VREG_ID_L25, "8058_l25", &pm8058_ldo_ops), + + VREG_DESCRIP(PM8058_VREG_ID_S0, "8058_s0", &pm8058_smps_ops), + VREG_DESCRIP(PM8058_VREG_ID_S1, "8058_s1", &pm8058_smps_ops), + VREG_DESCRIP(PM8058_VREG_ID_S2, "8058_s2", &pm8058_smps_ops), + VREG_DESCRIP(PM8058_VREG_ID_S3, "8058_s3", &pm8058_smps_ops), + VREG_DESCRIP(PM8058_VREG_ID_S4, "8058_s4", &pm8058_smps_ops), + + VREG_DESCRIP(PM8058_VREG_ID_LVS0, "8058_lvs0", &pm8058_lvs_ops), + VREG_DESCRIP(PM8058_VREG_ID_LVS1, "8058_lvs1", &pm8058_lvs_ops), + + VREG_DESCRIP(PM8058_VREG_ID_NCP, "8058_ncp", &pm8058_ncp_ops), +}; + +static int pm8058_master_enable_init(struct pm8058_chip *chip) +{ + int rc = 0, i; + + for (i = 0; i < MASTER_ENABLE_COUNT; i++) { + rc = pm8058_read(chip, m_en[i].addr, &(m_en[i].reg), 1); + if (rc) + goto bail; + } + +bail: + if (rc) + pr_err("%s: pm8058_read failed, rc=%d\n", __func__, rc); + + return rc; +} + +static int pm8058_init_ldo(struct pm8058_chip *chip, struct pm8058_vreg *vreg) +{ + int rc = 0, i; + u8 bank; + + /* Save the current test register state. */ + for (i = 0; i < LDO_TEST_BANKS; i++) { + bank = REGULATOR_BANK_SEL(i); + rc = pm8058_write(chip, vreg->test_addr, &bank, 1); + if (rc) + goto bail; + + rc = pm8058_read(chip, vreg->test_addr, &vreg->test_reg[i], 1); + if (rc) + goto bail; + vreg->test_reg[i] |= REGULATOR_BANK_WRITE; + } + + if ((vreg->ctrl_reg & LDO_CTRL_PM_MASK) == LDO_CTRL_PM_LPM) + vreg->optimum = REGULATOR_MODE_STANDBY; + else + vreg->optimum = REGULATOR_MODE_FAST; + + /* Set pull down enable based on platform data. */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, + (vreg->pdata->pull_down_enable ? LDO_PULL_DOWN_ENABLE : 0), + LDO_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg); +bail: + return rc; +} + +static int pm8058_init_smps(struct pm8058_chip *chip, struct pm8058_vreg *vreg) +{ + int rc = 0, i; + u8 bank; + + /* Save the current test2 register state. */ + for (i = 0; i < SMPS_TEST_BANKS; i++) { + bank = REGULATOR_BANK_SEL(i); + rc = pm8058_write(chip, vreg->test_addr, &bank, 1); + if (rc) + goto bail; + + rc = pm8058_read(chip, vreg->test_addr, &vreg->test_reg[i], + 1); + if (rc) + goto bail; + vreg->test_reg[i] |= REGULATOR_BANK_WRITE; + } + + /* Save the current clock control register state. */ + rc = pm8058_read(chip, vreg->clk_ctrl_addr, &vreg->clk_ctrl_reg, 1); + if (rc) + goto bail; + + /* Save the current sleep control register state. */ + rc = pm8058_read(chip, vreg->sleep_ctrl_addr, &vreg->sleep_ctrl_reg, 1); + if (rc) + goto bail; + + vreg->save_uV = 1; /* This is not a no-op. */ + vreg->save_uV = _pm8058_smps_get_voltage(vreg); + + if ((vreg->clk_ctrl_reg & SMPS_CLK_CTRL_MASK) == SMPS_CLK_CTRL_PFM) + vreg->optimum = REGULATOR_MODE_STANDBY; + else + vreg->optimum = REGULATOR_MODE_FAST; + + /* Set advanced mode pull down enable based on platform data. */ + rc = pm8058_vreg_write(chip, vreg->test_addr, + (vreg->pdata->pull_down_enable + ? SMPS_ADVANCED_PULL_DOWN_ENABLE : 0) + | REGULATOR_BANK_SEL(6) | REGULATOR_BANK_WRITE, + REGULATOR_BANK_MASK | SMPS_ADVANCED_PULL_DOWN_ENABLE, + &vreg->test_reg[6]); + if (rc) + goto bail; + + if (!SMPS_IN_ADVANCED_MODE(vreg)) { + /* Set legacy mode pull down enable based on platform data. */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, + (vreg->pdata->pull_down_enable + ? SMPS_LEGACY_PULL_DOWN_ENABLE : 0), + SMPS_LEGACY_PULL_DOWN_ENABLE, &vreg->ctrl_reg); + if (rc) + goto bail; + } + +bail: + return rc; +} + +static int pm8058_init_lvs(struct pm8058_chip *chip, struct pm8058_vreg *vreg) +{ + int rc = 0; + + vreg->optimum = REGULATOR_MODE_FAST; + + /* Set pull down enable based on platform data. */ + rc = pm8058_vreg_write(chip, vreg->ctrl_addr, + (vreg->pdata->pull_down_enable + ? LVS_PULL_DOWN_ENABLE : LVS_PULL_DOWN_DISABLE), + LVS_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg); + return rc; +} + +static int pm8058_init_ncp(struct pm8058_chip *chip, struct pm8058_vreg *vreg) +{ + int rc = 0; + + /* Save the current test1 register state. */ + rc = pm8058_read(chip, vreg->test_addr, &vreg->test_reg[0], 1); + if (rc) + goto bail; + + vreg->optimum = REGULATOR_MODE_FAST; + +bail: + return rc; +} + +static int pm8058_init_regulator(struct pm8058_chip *chip, + struct pm8058_vreg *vreg) +{ + static int master_enable_inited; + int rc = 0; + + vreg->mode_initialized = 0; + + if (!master_enable_inited) { + rc = pm8058_master_enable_init(chip); + if (!rc) + master_enable_inited = 1; + } + + /* save the current control register state */ + rc = pm8058_read(chip, vreg->ctrl_addr, &vreg->ctrl_reg, 1); + if (rc) + goto bail; + + switch (vreg->type) { + case REGULATOR_TYPE_LDO: + rc = pm8058_init_ldo(chip, vreg); + break; + case REGULATOR_TYPE_SMPS: + rc = pm8058_init_smps(chip, vreg); + break; + case REGULATOR_TYPE_LVS: + rc = pm8058_init_lvs(chip, vreg); + break; + case REGULATOR_TYPE_NCP: + rc = pm8058_init_ncp(chip, vreg); + break; + } + +bail: + if (rc) + pr_err("%s: pm8058_read/write failed; initial register states " + "unknown, rc=%d\n", __func__, rc); + return rc; +} + +static int __devinit pm8058_vreg_probe(struct platform_device *pdev) +{ + struct regulator_desc *rdesc; + struct pm8058_chip *chip; + struct pm8058_vreg *vreg; + const char *reg_name = NULL; + int rc = 0; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= 0 && pdev->id < PM8058_VREG_MAX) { + chip = platform_get_drvdata(pdev); + rdesc = &pm8058_vreg_descrip[pdev->id]; + vreg = &pm8058_vreg[pdev->id]; + vreg->pdata = pdev->dev.platform_data; + reg_name = pm8058_vreg_descrip[pdev->id].name; + + rc = pm8058_init_regulator(chip, vreg); + if (rc) + goto bail; + + /* Disallow idle and normal modes if pin control isn't set. */ + if (vreg->pdata->pin_ctrl == 0) + vreg->pdata->init_data.constraints.valid_modes_mask + &= ~(REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE); + + vreg->rdev = regulator_register(rdesc, &pdev->dev, + &vreg->pdata->init_data, vreg); + if (IS_ERR(vreg->rdev)) { + rc = PTR_ERR(vreg->rdev); + pr_err("%s: regulator_register failed for %s, rc=%d\n", + __func__, reg_name, rc); + } + } else { + rc = -ENODEV; + } + +bail: + if (rc) + pr_err("%s: error for %s, rc=%d\n", __func__, reg_name, rc); + + return rc; +} + +static int __devexit pm8058_vreg_remove(struct platform_device *pdev) +{ + regulator_unregister(pm8058_vreg[pdev->id].rdev); + return 0; +} + +static struct platform_driver pm8058_vreg_driver = { + .probe = pm8058_vreg_probe, + .remove = __devexit_p(pm8058_vreg_remove), + .driver = { + .name = "pm8058-regulator", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8058_vreg_init(void) +{ + return platform_driver_register(&pm8058_vreg_driver); +} + +static void __exit pm8058_vreg_exit(void) +{ + platform_driver_unregister(&pm8058_vreg_driver); +} + +static void print_write_error(struct pm8058_vreg *vreg, int rc, + const char *func) +{ + const char *reg_name = NULL; + ptrdiff_t id = vreg - pm8058_vreg; + + if (id >= 0 && id < PM8058_VREG_MAX) + reg_name = pm8058_vreg_descrip[id].name; + pr_err("%s: pm8058_vreg_write failed for %s, rc=%d\n", + func, reg_name, rc); +} + +subsys_initcall(pm8058_vreg_init); +module_exit(pm8058_vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8058-regulator"); diff --git a/drivers/regulator/pmic8901-regulator.c b/drivers/regulator/pmic8901-regulator.c new file mode 100644 index 00000000000..5b4b907508e --- /dev/null +++ b/drivers/regulator/pmic8901-regulator.c @@ -0,0 +1,1097 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Regulator types */ +#define REGULATOR_TYPE_LDO 0 +#define REGULATOR_TYPE_SMPS 1 +#define REGULATOR_TYPE_VS 2 +#define REGULATOR_TYPE_MPP 3 + +/* Bank select/write macros */ +#define REGULATOR_BANK_SEL(n) ((n) << 4) +#define REGULATOR_BANK_WRITE 0x80 +#define LDO_TEST_BANKS 7 +#define REGULATOR_BANK_MASK 0xF0 + +/* Pin mask resource register programming */ +#define VREG_PMR_STATE_MASK 0x60 +#define VREG_PMR_STATE_HPM 0x60 +#define VREG_PMR_STATE_LPM 0x40 +#define VREG_PMR_STATE_OFF 0x20 +#define VREG_PMR_STATE_PIN_CTRL 0x20 + +#define VREG_PMR_MODE_ACTION_MASK 0x10 +#define VREG_PMR_MODE_ACTION_SLEEP 0x10 +#define VREG_PMR_MODE_ACTION_OFF 0x00 + +#define VREG_PMR_MODE_PIN_MASK 0x08 +#define VREG_PMR_MODE_PIN_MASKED 0x08 + +#define VREG_PMR_CTRL_PIN2_MASK 0x04 +#define VREG_PMR_CTRL_PIN2_MASKED 0x04 + +#define VREG_PMR_CTRL_PIN1_MASK 0x02 +#define VREG_PMR_CTRL_PIN1_MASKED 0x02 + +#define VREG_PMR_CTRL_PIN0_MASK 0x01 +#define VREG_PMR_CTRL_PIN0_MASKED 0x01 + +#define VREG_PMR_PIN_CTRL_ALL_MASK 0x1F +#define VREG_PMR_PIN_CTRL_ALL_MASKED 0x1F + +#define REGULATOR_IS_EN(pmr_reg) \ + ((pmr_reg & VREG_PMR_STATE_MASK) == VREG_PMR_STATE_HPM || \ + (pmr_reg & VREG_PMR_STATE_MASK) == VREG_PMR_STATE_LPM) + +/* FTSMPS programming */ + +/* CTRL register */ +#define SMPS_VCTRL_BAND_MASK 0xC0 +#define SMPS_VCTRL_BAND_OFF 0x00 +#define SMPS_VCTRL_BAND_1 0x40 +#define SMPS_VCTRL_BAND_2 0x80 +#define SMPS_VCTRL_BAND_3 0xC0 +#define SMPS_VCTRL_VPROG_MASK 0x3F + +#define SMPS_BAND_1_UV_MIN 350000 +#define SMPS_BAND_1_UV_MAX 650000 +#define SMPS_BAND_1_UV_STEP 6250 + +#define SMPS_BAND_2_UV_MIN 700000 +#define SMPS_BAND_2_UV_MAX 1400000 +#define SMPS_BAND_2_UV_STEP 12500 + +#define SMPS_BAND_3_UV_SETPOINT_MIN 1500000 +#define SMPS_BAND_3_UV_MIN 1400000 +#define SMPS_BAND_3_UV_MAX 3300000 +#define SMPS_BAND_3_UV_STEP 50000 + +#define SMPS_UV_MIN SMPS_BAND_1_UV_MIN +#define SMPS_UV_MAX SMPS_BAND_3_UV_MAX + +/* PWR_CNFG register */ +#define SMPS_PULL_DOWN_ENABLE_MASK 0x40 +#define SMPS_PULL_DOWN_ENABLE 0x40 + +/* LDO programming */ + +/* CTRL register */ +#define LDO_LOCAL_ENABLE_MASK 0x80 +#define LDO_LOCAL_ENABLE 0x80 + +#define LDO_PULL_DOWN_ENABLE_MASK 0x40 +#define LDO_PULL_DOWN_ENABLE 0x40 + +#define LDO_CTRL_VPROG_MASK 0x1F + +/* TEST register bank 2 */ +#define LDO_TEST_VPROG_UPDATE_MASK 0x08 +#define LDO_TEST_RANGE_SEL_MASK 0x04 +#define LDO_TEST_FINE_STEP_MASK 0x02 +#define LDO_TEST_FINE_STEP_SHIFT 1 + +/* TEST register bank 4 */ +#define LDO_TEST_RANGE_EXT_MASK 0x01 + +/* Allowable voltage ranges */ +#define PLDO_LOW_UV_MIN 750000 +#define PLDO_LOW_UV_MAX 1537500 +#define PLDO_LOW_FINE_STEP_UV 12500 + +#define PLDO_NORM_UV_MIN 1500000 +#define PLDO_NORM_UV_MAX 3075000 +#define PLDO_NORM_FINE_STEP_UV 25000 + +#define PLDO_HIGH_UV_MIN 1750000 +#define PLDO_HIGH_UV_MAX 4900000 +#define PLDO_HIGH_FINE_STEP_UV 50000 + +#define NLDO_UV_MIN 750000 +#define NLDO_UV_MAX 1537500 +#define NLDO_FINE_STEP_UV 12500 + +/* VS programming */ + +/* CTRL register */ +#define VS_CTRL_ENABLE_MASK 0xC0 +#define VS_CTRL_DISABLE 0x00 +#define VS_CTRL_ENABLE 0x40 +#define VS_CTRL_USE_PMR 0xC0 + +#define VS_PULL_DOWN_ENABLE_MASK 0x20 +#define VS_PULL_DOWN_ENABLE 0x20 + +struct pm8901_vreg { + struct pm8901_vreg_pdata *pdata; + struct regulator_dev *rdev; + struct pm8901_chip *chip; + int hpm_min_load; + unsigned pc_vote; + unsigned optimum; + unsigned mode_initialized; + u16 ctrl_addr; + u16 pmr_addr; + u16 test_addr; + u16 pfm_ctrl_addr; + u16 pwr_cnfg_addr; + u8 type; + u8 ctrl_reg; + u8 pmr_reg; + u8 test_reg[LDO_TEST_BANKS]; + u8 pfm_ctrl_reg; + u8 pwr_cnfg_reg; + u8 is_nmos; + u8 mpp_id; + u8 state; +}; + +/* + * These are used to compensate for the PMIC 8901 v1 FTS regulators which + * output ~10% higher than the programmed set point. + */ +#define IS_PMIC_8901_V1(rev) ((rev) == PM_8901_REV_1p0 || \ + (rev) == PM_8901_REV_1p1) + +#define PMIC_8901_V1_SCALE(uV) ((((uV) - 62100) * 23) / 25) + +#define PMIC_8901_V1_SCALE_INV(uV) (((uV) * 25) / 23 + 62100) + +/* + * Band 1 of PMIC 8901 SMPS regulators only supports set points with the 3 LSB's + * equal to 0. This is accomplished in the macro by truncating the bits. + */ +#define PM8901_SMPS_BAND_1_COMPENSATE(vprog) ((vprog) & 0xF8) + +#define LDO(_id, _ctrl_addr, _pmr_addr, _test_addr, _is_nmos) \ + [_id] = { \ + .ctrl_addr = _ctrl_addr, \ + .pmr_addr = _pmr_addr, \ + .test_addr = _test_addr, \ + .type = REGULATOR_TYPE_LDO, \ + .is_nmos = _is_nmos, \ + .hpm_min_load = PM8901_VREG_LDO_300_HPM_MIN_LOAD, \ + } + +#define SMPS(_id, _ctrl_addr, _pmr_addr, _pfm_ctrl_addr, _pwr_cnfg_addr) \ + [_id] = { \ + .ctrl_addr = _ctrl_addr, \ + .pmr_addr = _pmr_addr, \ + .pfm_ctrl_addr = _pfm_ctrl_addr, \ + .pwr_cnfg_addr = _pwr_cnfg_addr, \ + .type = REGULATOR_TYPE_SMPS, \ + .hpm_min_load = PM8901_VREG_FTSMPS_HPM_MIN_LOAD, \ + } + +#define VS(_id, _ctrl_addr, _pmr_addr) \ + [_id] = { \ + .ctrl_addr = _ctrl_addr, \ + .pmr_addr = _pmr_addr, \ + .type = REGULATOR_TYPE_VS, \ + } + +#define MPP(_id, _mpp_id) \ + [_id] = { \ + .mpp_id = _mpp_id, \ + .type = REGULATOR_TYPE_MPP, \ + } + +static struct pm8901_vreg pm8901_vreg[] = { + /* id ctrl pmr tst n/p */ + LDO(PM8901_VREG_ID_L0, 0x02F, 0x0AB, 0x030, 1), + LDO(PM8901_VREG_ID_L1, 0x031, 0x0AC, 0x032, 0), + LDO(PM8901_VREG_ID_L2, 0x033, 0x0AD, 0x034, 0), + LDO(PM8901_VREG_ID_L3, 0x035, 0x0AE, 0x036, 0), + LDO(PM8901_VREG_ID_L4, 0x037, 0x0AF, 0x038, 0), + LDO(PM8901_VREG_ID_L5, 0x039, 0x0B0, 0x03A, 0), + LDO(PM8901_VREG_ID_L6, 0x03B, 0x0B1, 0x03C, 0), + + /* id ctrl pmr pfm pwr */ + SMPS(PM8901_VREG_ID_S0, 0x05B, 0x0A6, 0x05C, 0x0E3), + SMPS(PM8901_VREG_ID_S1, 0x06A, 0x0A7, 0x06B, 0x0EC), + SMPS(PM8901_VREG_ID_S2, 0x079, 0x0A8, 0x07A, 0x0F1), + SMPS(PM8901_VREG_ID_S3, 0x088, 0x0A9, 0x089, 0x0F6), + SMPS(PM8901_VREG_ID_S4, 0x097, 0x0AA, 0x098, 0x0FB), + + /* id MPP ID */ + MPP(PM8901_VREG_ID_MPP0, 0), + + /* id ctrl pmr */ + VS(PM8901_VREG_ID_LVS0, 0x046, 0x0B2), + VS(PM8901_VREG_ID_LVS1, 0x048, 0x0B3), + VS(PM8901_VREG_ID_LVS2, 0x04A, 0x0B4), + VS(PM8901_VREG_ID_LVS3, 0x04C, 0x0B5), + VS(PM8901_VREG_ID_MVS0, 0x052, 0x0B6), + VS(PM8901_VREG_ID_USB_OTG, 0x055, 0x0B7), + VS(PM8901_VREG_ID_HDMI_MVS, 0x058, 0x0B8), +}; + +static void print_write_error(struct pm8901_vreg *vreg, int rc, + const char *func); + +static int pm8901_vreg_write(struct pm8901_chip *chip, + u16 addr, u8 val, u8 mask, u8 *reg_save) +{ + int rc = 0; + u8 reg; + + reg = (*reg_save & ~mask) | (val & mask); + if (reg != *reg_save) + rc = pm8901_write(chip, addr, ®, 1); + if (!rc) + *reg_save = reg; + return rc; +} + +/* Set pin control bits based on new mode. */ +static int pm8901_vreg_select_pin_ctrl(struct pm8901_vreg *vreg, u8 *pmr_reg) +{ + *pmr_reg |= VREG_PMR_PIN_CTRL_ALL_MASKED; + + if ((*pmr_reg & VREG_PMR_STATE_MASK) == VREG_PMR_STATE_PIN_CTRL) { + if (vreg->pdata->pin_fn == PM8901_VREG_PIN_FN_MODE) + *pmr_reg = (*pmr_reg & ~VREG_PMR_STATE_MASK) + | VREG_PMR_STATE_LPM; + if (vreg->pdata->pin_ctrl & PM8901_VREG_PIN_CTRL_A0) + *pmr_reg &= ~VREG_PMR_CTRL_PIN0_MASKED; + if (vreg->pdata->pin_ctrl & PM8901_VREG_PIN_CTRL_A1) + *pmr_reg &= ~VREG_PMR_CTRL_PIN1_MASKED; + if (vreg->pdata->pin_ctrl & PM8901_VREG_PIN_CTRL_D0) + *pmr_reg &= ~VREG_PMR_CTRL_PIN2_MASKED; + } + + return 0; +} + +static int pm8901_vreg_enable(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + struct pm8901_chip *chip = vreg->chip; + u8 val = VREG_PMR_STATE_HPM; + int rc; + + if (!vreg->mode_initialized && vreg->pc_vote) + val = VREG_PMR_STATE_PIN_CTRL; + else if (vreg->optimum == REGULATOR_MODE_FAST) + val = VREG_PMR_STATE_HPM; + else if (vreg->pc_vote) + val = VREG_PMR_STATE_PIN_CTRL; + else if (vreg->optimum == REGULATOR_MODE_STANDBY) + val = VREG_PMR_STATE_LPM; + + pm8901_vreg_select_pin_ctrl(vreg, &val); + + rc = pm8901_vreg_write(chip, vreg->pmr_addr, + val, + VREG_PMR_STATE_MASK | VREG_PMR_PIN_CTRL_ALL_MASK, + &vreg->pmr_reg); + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8901_vreg_disable(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + struct pm8901_chip *chip = vreg->chip; + int rc; + + rc = pm8901_vreg_write(chip, vreg->pmr_addr, + VREG_PMR_STATE_OFF | VREG_PMR_PIN_CTRL_ALL_MASKED, + VREG_PMR_STATE_MASK | VREG_PMR_PIN_CTRL_ALL_MASK, + &vreg->pmr_reg); + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +/* + * Cases that count as enabled: + * + * 1. PMR register has mode == HPM or LPM. + * 2. Any pin control bits are unmasked. + * 3. The regulator is an LDO and its local enable bit is set. + */ +static int _pm8901_vreg_is_enabled(struct pm8901_vreg *vreg) +{ + if ((vreg->type == REGULATOR_TYPE_LDO) + && (vreg->ctrl_reg & LDO_LOCAL_ENABLE_MASK)) + return 1; + else if (vreg->type == REGULATOR_TYPE_VS) { + if ((vreg->ctrl_reg & VS_CTRL_ENABLE_MASK) == VS_CTRL_ENABLE) + return 1; + else if ((vreg->ctrl_reg & VS_CTRL_ENABLE_MASK) + == VS_CTRL_DISABLE) + return 0; + } + + return REGULATOR_IS_EN(vreg->pmr_reg) + || ((vreg->pmr_reg & VREG_PMR_PIN_CTRL_ALL_MASK) + != VREG_PMR_PIN_CTRL_ALL_MASKED); +} + +static int pm8901_vreg_is_enabled(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + + return _pm8901_vreg_is_enabled(vreg); +} + +static int pm8901_ldo_disable(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + struct pm8901_chip *chip = vreg->chip; + int rc; + + /* Disassert local enable bit in CTRL register. */ + rc = pm8901_vreg_write(chip, vreg->ctrl_addr, 0, LDO_LOCAL_ENABLE_MASK, + &vreg->ctrl_reg); + if (rc) + print_write_error(vreg, rc, __func__); + + /* Disassert enable bit in PMR register. */ + rc = pm8901_vreg_disable(dev); + + return rc; +} + +static int pm8901_pldo_set_voltage(struct pm8901_chip *chip, + struct pm8901_vreg *vreg, int uV) +{ + int vmin, rc = 0; + unsigned vprog, fine_step; + u8 range_ext, range_sel, fine_step_reg; + + if (uV < PLDO_LOW_UV_MIN || uV > PLDO_HIGH_UV_MAX) + return -EINVAL; + + if (uV < PLDO_LOW_UV_MAX + PLDO_LOW_FINE_STEP_UV) { + vmin = PLDO_LOW_UV_MIN; + fine_step = PLDO_LOW_FINE_STEP_UV; + range_ext = 0; + range_sel = LDO_TEST_RANGE_SEL_MASK; + } else if (uV < PLDO_NORM_UV_MAX + PLDO_NORM_FINE_STEP_UV) { + vmin = PLDO_NORM_UV_MIN; + fine_step = PLDO_NORM_FINE_STEP_UV; + range_ext = 0; + range_sel = 0; + } else { + vmin = PLDO_HIGH_UV_MIN; + fine_step = PLDO_HIGH_FINE_STEP_UV; + range_ext = LDO_TEST_RANGE_EXT_MASK; + range_sel = 0; + } + + vprog = (uV - vmin) / fine_step; + fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT; + vprog >>= 1; + + /* + * Disable program voltage update if range extension, range select, + * or fine step have changed and the regulator is enabled. + */ + if (_pm8901_vreg_is_enabled(vreg) && + (((range_ext ^ vreg->test_reg[4]) & LDO_TEST_RANGE_EXT_MASK) + || ((range_sel ^ vreg->test_reg[2]) & LDO_TEST_RANGE_SEL_MASK) + || ((fine_step_reg ^ vreg->test_reg[2]) + & LDO_TEST_FINE_STEP_MASK))) { + rc = pm8901_vreg_write(chip, vreg->test_addr, + REGULATOR_BANK_SEL(2) | REGULATOR_BANK_WRITE, + REGULATOR_BANK_MASK | LDO_TEST_VPROG_UPDATE_MASK, + &vreg->test_reg[2]); + if (rc) + goto bail; + } + + /* Write new voltage. */ + rc = pm8901_vreg_write(chip, vreg->ctrl_addr, vprog, + LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + goto bail; + + /* Write range extension. */ + rc = pm8901_vreg_write(chip, vreg->test_addr, + range_ext | REGULATOR_BANK_SEL(4) + | REGULATOR_BANK_WRITE, + LDO_TEST_RANGE_EXT_MASK | REGULATOR_BANK_MASK, + &vreg->test_reg[4]); + if (rc) + goto bail; + + /* Write fine step, range select and program voltage update. */ + rc = pm8901_vreg_write(chip, vreg->test_addr, + fine_step_reg | range_sel | REGULATOR_BANK_SEL(2) + | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK, + LDO_TEST_FINE_STEP_MASK | LDO_TEST_RANGE_SEL_MASK + | REGULATOR_BANK_MASK | LDO_TEST_VPROG_UPDATE_MASK, + &vreg->test_reg[2]); +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8901_nldo_set_voltage(struct pm8901_chip *chip, + struct pm8901_vreg *vreg, int uV) +{ + unsigned vprog, fine_step_reg; + int rc; + + if (uV < NLDO_UV_MIN || uV > NLDO_UV_MAX) + return -EINVAL; + + vprog = (uV - NLDO_UV_MIN) / NLDO_FINE_STEP_UV; + fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT; + vprog >>= 1; + + /* Write new voltage. */ + rc = pm8901_vreg_write(chip, vreg->ctrl_addr, vprog, + LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg); + if (rc) + print_write_error(vreg, rc, __func__); + + /* Write fine step. */ + rc = pm8901_vreg_write(chip, vreg->test_addr, + fine_step_reg | REGULATOR_BANK_SEL(2) + | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK, + LDO_TEST_FINE_STEP_MASK | REGULATOR_BANK_MASK + | LDO_TEST_VPROG_UPDATE_MASK, + &vreg->test_reg[2]); + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8901_ldo_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + struct pm8901_chip *chip = vreg->chip; + + if (vreg->is_nmos) + return pm8901_nldo_set_voltage(chip, vreg, min_uV); + else + return pm8901_pldo_set_voltage(chip, vreg, min_uV); +} + +static int pm8901_pldo_get_voltage(struct pm8901_vreg *vreg) +{ + int vmin, fine_step; + u8 range_ext, range_sel, vprog, fine_step_reg; + + fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK; + range_sel = vreg->test_reg[2] & LDO_TEST_RANGE_SEL_MASK; + range_ext = vreg->test_reg[4] & LDO_TEST_RANGE_EXT_MASK; + vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK; + + vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT); + + if (range_sel) { + /* low range mode */ + fine_step = PLDO_LOW_FINE_STEP_UV; + vmin = PLDO_LOW_UV_MIN; + } else if (!range_ext) { + /* normal mode */ + fine_step = PLDO_NORM_FINE_STEP_UV; + vmin = PLDO_NORM_UV_MIN; + } else { + /* high range mode */ + fine_step = PLDO_HIGH_FINE_STEP_UV; + vmin = PLDO_HIGH_UV_MIN; + } + + return fine_step * vprog + vmin; +} + +static int pm8901_nldo_get_voltage(struct pm8901_vreg *vreg) +{ + u8 vprog, fine_step_reg; + + fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK; + vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK; + + vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT); + + return NLDO_FINE_STEP_UV * vprog + NLDO_UV_MIN; +} + +static int pm8901_ldo_get_voltage(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + + if (vreg->is_nmos) + return pm8901_nldo_get_voltage(vreg); + else + return pm8901_pldo_get_voltage(vreg); +} + +/* + * Optimum mode programming: + * REGULATOR_MODE_FAST: Go to HPM (highest priority) + * REGULATOR_MODE_STANDBY: Go to pin ctrl mode if there are any pin ctrl + * votes, else go to LPM + * + * Pin ctrl mode voting via regulator set_mode: + * REGULATOR_MODE_IDLE: Go to pin ctrl mode if the optimum mode is LPM, else + * go to HPM + * REGULATOR_MODE_NORMAL: Go to LPM if it is the optimum mode, else go to HPM + */ +static int pm8901_vreg_set_mode(struct regulator_dev *dev, unsigned int mode) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + struct pm8901_chip *chip = vreg->chip; + unsigned optimum = vreg->optimum; + unsigned pc_vote = vreg->pc_vote; + unsigned mode_initialized = vreg->mode_initialized; + u8 val = 0; + int rc = 0; + + /* Determine new mode to go into. */ + switch (mode) { + case REGULATOR_MODE_FAST: + val = VREG_PMR_STATE_HPM; + optimum = mode; + mode_initialized = 1; + break; + + case REGULATOR_MODE_STANDBY: + if (pc_vote) + val = VREG_PMR_STATE_PIN_CTRL; + else + val = VREG_PMR_STATE_LPM; + optimum = mode; + mode_initialized = 1; + break; + + case REGULATOR_MODE_IDLE: + if (pc_vote++) + goto done; /* already taken care of */ + + if (mode_initialized && optimum == REGULATOR_MODE_FAST) + val = VREG_PMR_STATE_HPM; + else + val = VREG_PMR_STATE_PIN_CTRL; + break; + + case REGULATOR_MODE_NORMAL: + if (pc_vote && --pc_vote) + goto done; /* already taken care of */ + + if (optimum == REGULATOR_MODE_STANDBY) + val = VREG_PMR_STATE_LPM; + else + val = VREG_PMR_STATE_HPM; + break; + + default: + pr_err("%s: unknown mode, mode=%u\n", __func__, mode); + return -EINVAL; + } + + /* Set pin control bits based on new mode. */ + pm8901_vreg_select_pin_ctrl(vreg, &val); + + /* Only apply mode setting to hardware if currently enabled. */ + if (pm8901_vreg_is_enabled(dev)) + rc = pm8901_vreg_write(chip, vreg->pmr_addr, val, + VREG_PMR_STATE_MASK | VREG_PMR_PIN_CTRL_ALL_MASK, + &vreg->pmr_reg); + + if (rc) { + print_write_error(vreg, rc, __func__); + return rc; + } + +done: + vreg->mode_initialized = mode_initialized; + vreg->optimum = optimum; + vreg->pc_vote = pc_vote; + + return 0; +} + +static unsigned int pm8901_vreg_get_mode(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + int pin_mask = VREG_PMR_CTRL_PIN0_MASK | VREG_PMR_CTRL_PIN1_MASK + | VREG_PMR_CTRL_PIN2_MASK; + + if (!vreg->mode_initialized && vreg->pc_vote) + return REGULATOR_MODE_IDLE; + else if (((vreg->pmr_reg & VREG_PMR_STATE_MASK) == VREG_PMR_STATE_OFF) + && ((vreg->pmr_reg & pin_mask) != pin_mask)) + return REGULATOR_MODE_IDLE; + else if (((vreg->pmr_reg & VREG_PMR_STATE_MASK) == VREG_PMR_STATE_LPM) + && ((vreg->pmr_reg & pin_mask) != pin_mask)) + return REGULATOR_MODE_IDLE; + else if (vreg->optimum == REGULATOR_MODE_FAST) + return REGULATOR_MODE_FAST; + else if (vreg->pc_vote) + return REGULATOR_MODE_IDLE; + else if (vreg->optimum == REGULATOR_MODE_STANDBY) + return REGULATOR_MODE_STANDBY; + return REGULATOR_MODE_FAST; +} + +unsigned int pm8901_vreg_get_optimum_mode(struct regulator_dev *dev, + int input_uV, int output_uV, int load_uA) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + + if (load_uA <= 0) { + /* + * pm8901_vreg_get_optimum_mode is being called before consumers + * have specified their load currents via + * regulator_set_optimum_mode. Return whatever the existing mode + * is. + */ + return pm8901_vreg_get_mode(dev); + } + + if (load_uA >= vreg->hpm_min_load) + return REGULATOR_MODE_FAST; + return REGULATOR_MODE_STANDBY; +} + +static int pm8901_smps_set_voltage(struct regulator_dev *dev, + int min_uV, int max_uV, unsigned *selector) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + struct pm8901_chip *chip = vreg->chip; + int rc; + u8 val, band; + + if (IS_PMIC_8901_V1(pm8901_rev(chip))) + min_uV = PMIC_8901_V1_SCALE(min_uV); + + if (min_uV < SMPS_BAND_1_UV_MIN || min_uV > SMPS_BAND_3_UV_MAX) + return -EINVAL; + + /* Round down for set points in the gaps between bands. */ + if (min_uV > SMPS_BAND_1_UV_MAX && min_uV < SMPS_BAND_2_UV_MIN) + min_uV = SMPS_BAND_1_UV_MAX; + else if (min_uV > SMPS_BAND_2_UV_MAX + && min_uV < SMPS_BAND_3_UV_SETPOINT_MIN) + min_uV = SMPS_BAND_2_UV_MAX; + + if (min_uV < SMPS_BAND_2_UV_MIN) { + val = ((min_uV - SMPS_BAND_1_UV_MIN) / SMPS_BAND_1_UV_STEP); + val = PM8901_SMPS_BAND_1_COMPENSATE(val); + band = SMPS_VCTRL_BAND_1; + } else if (min_uV < SMPS_BAND_3_UV_SETPOINT_MIN) { + val = ((min_uV - SMPS_BAND_2_UV_MIN) / SMPS_BAND_2_UV_STEP); + band = SMPS_VCTRL_BAND_2; + } else { + val = ((min_uV - SMPS_BAND_3_UV_MIN) / SMPS_BAND_3_UV_STEP); + band = SMPS_VCTRL_BAND_3; + } + + rc = pm8901_vreg_write(chip, vreg->ctrl_addr, band | val, + SMPS_VCTRL_BAND_MASK | SMPS_VCTRL_VPROG_MASK, + &vreg->ctrl_reg); + if (rc) + goto bail; + + rc = pm8901_vreg_write(chip, vreg->pfm_ctrl_addr, band | val, + SMPS_VCTRL_BAND_MASK | SMPS_VCTRL_VPROG_MASK, + &vreg->pfm_ctrl_reg); +bail: + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8901_smps_get_voltage(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + struct pm8901_chip *chip = vreg->chip; + u8 vprog, band; + int ret = 0; + + if ((vreg->pmr_reg & VREG_PMR_STATE_MASK) == VREG_PMR_STATE_LPM) { + vprog = vreg->pfm_ctrl_reg & SMPS_VCTRL_VPROG_MASK; + band = vreg->pfm_ctrl_reg & SMPS_VCTRL_BAND_MASK; + } else { + vprog = vreg->ctrl_reg & SMPS_VCTRL_VPROG_MASK; + band = vreg->ctrl_reg & SMPS_VCTRL_BAND_MASK; + } + + if (band == SMPS_VCTRL_BAND_1) + ret = vprog * SMPS_BAND_1_UV_STEP + SMPS_BAND_1_UV_MIN; + else if (band == SMPS_VCTRL_BAND_2) + ret = vprog * SMPS_BAND_2_UV_STEP + SMPS_BAND_2_UV_MIN; + else + ret = vprog * SMPS_BAND_3_UV_STEP + SMPS_BAND_3_UV_MIN; + + if (IS_PMIC_8901_V1(pm8901_rev(chip))) + ret = PMIC_8901_V1_SCALE_INV(ret); + + return ret; +} + +static int pm8901_vs_enable(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + struct pm8901_chip *chip = vreg->chip; + int rc; + + /* Assert enable bit in PMR register. */ + rc = pm8901_vreg_enable(dev); + + /* Make sure that switch is controlled via PMR register */ + rc = pm8901_vreg_write(chip, vreg->ctrl_addr, VS_CTRL_USE_PMR, + VS_CTRL_ENABLE_MASK, &vreg->ctrl_reg); + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8901_vs_disable(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + struct pm8901_chip *chip = vreg->chip; + int rc; + + /* Disassert enable bit in PMR register. */ + rc = pm8901_vreg_disable(dev); + + /* Make sure that switch is controlled via PMR register */ + rc = pm8901_vreg_write(chip, vreg->ctrl_addr, VS_CTRL_USE_PMR, + VS_CTRL_ENABLE_MASK, &vreg->ctrl_reg); + if (rc) + print_write_error(vreg, rc, __func__); + + return rc; +} + +static int pm8901_mpp_enable(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + int out_val; + int rc; + + out_val = (vreg->pdata->active_high + ? PM_MPP_DOUT_CTL_HIGH : PM_MPP_DOUT_CTL_LOW); + + rc = pm8901_mpp_config(vreg->mpp_id, PM_MPP_TYPE_D_OUTPUT, + PM8901_MPP_DIG_LEVEL_VPH, out_val); + + if (rc) + pr_err("%s: pm8901_mpp_config failed, rc=%d\n", __func__, rc); + else + vreg->state = 1; + + return rc; +} + +static int pm8901_mpp_disable(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + int out_val; + int rc; + + out_val = (vreg->pdata->active_high + ? PM_MPP_DOUT_CTL_LOW : PM_MPP_DOUT_CTL_HIGH); + + rc = pm8901_mpp_config(vreg->mpp_id, PM_MPP_TYPE_D_OUTPUT, + PM8901_MPP_DIG_LEVEL_VPH, out_val); + + if (rc) + pr_err("%s: pm8901_mpp_config failed, rc=%d\n", __func__, rc); + else + vreg->state = 0; + + return rc; +} + +static int pm8901_mpp_is_enabled(struct regulator_dev *dev) +{ + struct pm8901_vreg *vreg = rdev_get_drvdata(dev); + return vreg->state; +} + +static struct regulator_ops pm8901_ldo_ops = { + .enable = pm8901_vreg_enable, + .disable = pm8901_ldo_disable, + .is_enabled = pm8901_vreg_is_enabled, + .set_voltage = pm8901_ldo_set_voltage, + .get_voltage = pm8901_ldo_get_voltage, + .set_mode = pm8901_vreg_set_mode, + .get_mode = pm8901_vreg_get_mode, + .get_optimum_mode = pm8901_vreg_get_optimum_mode, +}; + +static struct regulator_ops pm8901_smps_ops = { + .enable = pm8901_vreg_enable, + .disable = pm8901_vreg_disable, + .is_enabled = pm8901_vreg_is_enabled, + .set_voltage = pm8901_smps_set_voltage, + .get_voltage = pm8901_smps_get_voltage, + .set_mode = pm8901_vreg_set_mode, + .get_mode = pm8901_vreg_get_mode, + .get_optimum_mode = pm8901_vreg_get_optimum_mode, +}; + +static struct regulator_ops pm8901_vs_ops = { + .enable = pm8901_vs_enable, + .disable = pm8901_vs_disable, + .is_enabled = pm8901_vreg_is_enabled, + .set_mode = pm8901_vreg_set_mode, + .get_mode = pm8901_vreg_get_mode, +}; + +static struct regulator_ops pm8901_mpp_ops = { + .enable = pm8901_mpp_enable, + .disable = pm8901_mpp_disable, + .is_enabled = pm8901_mpp_is_enabled, +}; + +#define VREG_DESCRIP(_id, _name, _ops) \ + [_id] = { \ + .name = _name, \ + .id = _id, \ + .ops = _ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + +static struct regulator_desc pm8901_vreg_descrip[] = { + VREG_DESCRIP(PM8901_VREG_ID_L0, "8901_l0", &pm8901_ldo_ops), + VREG_DESCRIP(PM8901_VREG_ID_L1, "8901_l1", &pm8901_ldo_ops), + VREG_DESCRIP(PM8901_VREG_ID_L2, "8901_l2", &pm8901_ldo_ops), + VREG_DESCRIP(PM8901_VREG_ID_L3, "8901_l3", &pm8901_ldo_ops), + VREG_DESCRIP(PM8901_VREG_ID_L4, "8901_l4", &pm8901_ldo_ops), + VREG_DESCRIP(PM8901_VREG_ID_L5, "8901_l5", &pm8901_ldo_ops), + VREG_DESCRIP(PM8901_VREG_ID_L6, "8901_l6", &pm8901_ldo_ops), + + VREG_DESCRIP(PM8901_VREG_ID_S0, "8901_s0", &pm8901_smps_ops), + VREG_DESCRIP(PM8901_VREG_ID_S1, "8901_s1", &pm8901_smps_ops), + VREG_DESCRIP(PM8901_VREG_ID_S2, "8901_s2", &pm8901_smps_ops), + VREG_DESCRIP(PM8901_VREG_ID_S3, "8901_s3", &pm8901_smps_ops), + VREG_DESCRIP(PM8901_VREG_ID_S4, "8901_s4", &pm8901_smps_ops), + + VREG_DESCRIP(PM8901_VREG_ID_MPP0, "8901_mpp0", &pm8901_mpp_ops), + + VREG_DESCRIP(PM8901_VREG_ID_LVS0, "8901_lvs0", &pm8901_vs_ops), + VREG_DESCRIP(PM8901_VREG_ID_LVS1, "8901_lvs1", &pm8901_vs_ops), + VREG_DESCRIP(PM8901_VREG_ID_LVS2, "8901_lvs2", &pm8901_vs_ops), + VREG_DESCRIP(PM8901_VREG_ID_LVS3, "8901_lvs3", &pm8901_vs_ops), + VREG_DESCRIP(PM8901_VREG_ID_MVS0, "8901_mvs0", &pm8901_vs_ops), + VREG_DESCRIP(PM8901_VREG_ID_USB_OTG, "8901_usb_otg", &pm8901_vs_ops), + VREG_DESCRIP(PM8901_VREG_ID_HDMI_MVS, "8901_hdmi_mvs", &pm8901_vs_ops), +}; + +static int pm8901_init_ldo(struct pm8901_chip *chip, struct pm8901_vreg *vreg) +{ + int rc = 0, i; + u8 bank; + + /* Store current regulator register values. */ + for (i = 0; i < LDO_TEST_BANKS; i++) { + bank = REGULATOR_BANK_SEL(i); + rc = pm8901_write(chip, vreg->test_addr, &bank, 1); + if (rc) + goto bail; + + rc = pm8901_read(chip, vreg->test_addr, &vreg->test_reg[i], 1); + if (rc) + goto bail; + + vreg->test_reg[i] |= REGULATOR_BANK_WRITE; + } + + /* Set pull down enable based on platform data. */ + rc = pm8901_vreg_write(chip, vreg->ctrl_addr, + (vreg->pdata->pull_down_enable ? LDO_PULL_DOWN_ENABLE : 0), + LDO_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg); +bail: + return rc; +} + +static int pm8901_init_smps(struct pm8901_chip *chip, struct pm8901_vreg *vreg) +{ + int rc; + + /* Store current regulator register values. */ + rc = pm8901_read(chip, vreg->pfm_ctrl_addr, + &vreg->pfm_ctrl_reg, 1); + if (rc) + goto bail; + + rc = pm8901_read(chip, vreg->pwr_cnfg_addr, + &vreg->pwr_cnfg_reg, 1); + if (rc) + goto bail; + + /* Set pull down enable based on platform data. */ + rc = pm8901_vreg_write(chip, vreg->pwr_cnfg_addr, + (vreg->pdata->pull_down_enable ? SMPS_PULL_DOWN_ENABLE : 0), + SMPS_PULL_DOWN_ENABLE_MASK, &vreg->pwr_cnfg_reg); + +bail: + return rc; +} + +static int pm8901_init_vs(struct pm8901_chip *chip, struct pm8901_vreg *vreg) +{ + int rc = 0; + + /* Set pull down enable based on platform data. */ + rc = pm8901_vreg_write(chip, vreg->ctrl_addr, + (vreg->pdata->pull_down_enable ? VS_PULL_DOWN_ENABLE : 0), + VS_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg); + + return rc; +} + +static int pm8901_init_regulator(struct pm8901_chip *chip, + struct pm8901_vreg *vreg) +{ + int rc; + + /* Store current regulator register values. */ + if (vreg->type != REGULATOR_TYPE_MPP) { + rc = pm8901_read(chip, vreg->ctrl_addr, &vreg->ctrl_reg, 1); + if (rc) + goto bail; + + rc = pm8901_read(chip, vreg->pmr_addr, &vreg->pmr_reg, 1); + if (rc) + goto bail; + } + + /* Set initial mode based on hardware state. */ + if ((vreg->pmr_reg & VREG_PMR_STATE_MASK) == VREG_PMR_STATE_LPM) + vreg->optimum = REGULATOR_MODE_STANDBY; + else + vreg->optimum = REGULATOR_MODE_FAST; + + vreg->mode_initialized = 0; + + if (vreg->type == REGULATOR_TYPE_LDO) + rc = pm8901_init_ldo(chip, vreg); + else if (vreg->type == REGULATOR_TYPE_SMPS) + rc = pm8901_init_smps(chip, vreg); + else if (vreg->type == REGULATOR_TYPE_VS) + rc = pm8901_init_vs(chip, vreg); +bail: + if (rc) + pr_err("%s: pm8901_read/write failed; initial register states " + "unknown, rc=%d\n", __func__, rc); + + return rc; +} + +static int __devinit pm8901_vreg_probe(struct platform_device *pdev) +{ + struct regulator_desc *rdesc; + struct pm8901_chip *chip; + struct pm8901_vreg *vreg; + const char *reg_name = NULL; + int rc = 0; + + if (pdev == NULL) + return -EINVAL; + + if (pdev->id >= 0 && pdev->id < PM8901_VREG_MAX) { + chip = dev_get_drvdata(pdev->dev.parent); + rdesc = &pm8901_vreg_descrip[pdev->id]; + vreg = &pm8901_vreg[pdev->id]; + vreg->pdata = pdev->dev.platform_data; + vreg->chip = chip; + reg_name = pm8901_vreg_descrip[pdev->id].name; + + rc = pm8901_init_regulator(chip, vreg); + if (rc) + goto bail; + + /* Disallow idle and normal modes if pin control isn't set. */ + if (vreg->pdata->pin_ctrl == 0) + vreg->pdata->init_data.constraints.valid_modes_mask + &= ~(REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE); + + vreg->rdev = regulator_register(rdesc, &pdev->dev, + &vreg->pdata->init_data, vreg); + if (IS_ERR(vreg->rdev)) { + rc = PTR_ERR(vreg->rdev); + pr_err("%s: regulator_register failed for %s, rc=%d\n", + __func__, reg_name, rc); + } + } else { + rc = -ENODEV; + } + +bail: + if (rc) + pr_err("%s: error for %s, rc=%d\n", __func__, reg_name, rc); + + return rc; +} + +static int __devexit pm8901_vreg_remove(struct platform_device *pdev) +{ + regulator_unregister(pm8901_vreg[pdev->id].rdev); + return 0; +} + +static struct platform_driver pm8901_vreg_driver = { + .probe = pm8901_vreg_probe, + .remove = __devexit_p(pm8901_vreg_remove), + .driver = { + .name = "pm8901-regulator", + .owner = THIS_MODULE, + }, +}; + +static int __init pm8901_vreg_init(void) +{ + return platform_driver_register(&pm8901_vreg_driver); +} + +static void __exit pm8901_vreg_exit(void) +{ + platform_driver_unregister(&pm8901_vreg_driver); +} + +static void print_write_error(struct pm8901_vreg *vreg, int rc, + const char *func) +{ + const char *reg_name = NULL; + ptrdiff_t id = vreg - pm8901_vreg; + + if (id >= 0 && id < PM8901_VREG_MAX) + reg_name = pm8901_vreg_descrip[id].name; + pr_err("%s: pm8901_vreg_write failed for %s, rc=%d\n", + func, reg_name, rc); +} + +subsys_initcall(pm8901_vreg_init); +module_exit(pm8901_vreg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8901 regulator driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pm8901-regulator"); diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 27c37743e2c..93feb810e04 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -94,6 +94,23 @@ config RTC_INTF_DEV If unsure, say Y. +config RTC_INTF_ALARM + bool "Android alarm driver" + depends on RTC_CLASS + default y + help + Provides non-wakeup and rtc backed wakeup alarms based on rtc or + elapsed realtime, and a non-wakeup alarm on the monotonic clock. + Also provides an interface to set the wall time which must be used + for elapsed realtime to work. + +config RTC_INTF_ALARM_DEV + bool "Android alarm device" + depends on RTC_INTF_ALARM + default y + help + Exports the alarm interface to user-space. + config RTC_INTF_DEV_UIE_EMUL bool "RTC UIE emulation on dev interface" depends on RTC_INTF_DEV @@ -739,6 +756,37 @@ config RTC_DRV_NUC900 comment "on-CPU RTC drivers" +config RTC_DRV_MSM + tristate "RTC on Qualcomm Chipsets" + depends on ARCH_MSM + default y + help + RTC driver for Qualcomm chipsets + + +config RTC_SECURE_TIME_SUPPORT + bool "Support for secure time on Qualcomm Chipsets" + depends on RTC_DRV_MSM = y + default y + help + Say yes here to have additional handle for reading secure time + maintained by ARM9. + +config RTC_ASYNC_MODEM_SUPPORT + bool "Support for time update on async modem boot" + depends on RTC_DRV_MSM && (ARCH_MSM8X60 || ARCH_QSD8X50) + default n + help + Say yes here to have the system time updated if there is + an asynchronous MODEM boot. + +config RTC_DRV_MSM7X00A + tristate "MSM7X00A" + depends on ARCH_MSM + default n + help + RTC driver for Qualcomm MSM7K chipsets + config RTC_DRV_DAVINCI tristate "TI DaVinci RTC" depends on ARCH_DAVINCI_DM365 @@ -749,6 +797,13 @@ config RTC_DRV_DAVINCI This driver can also be built as a module. If so, the module will be called rtc-davinci. +config RTC_DRV_MSM7X00A + tristate "MSM7X00A" + depends on ARCH_MSM + default y + help + RTC driver for Qualcomm MSM7K chipsets + config RTC_DRV_OMAP tristate "TI OMAP1" depends on ARCH_OMAP15XX || ARCH_OMAP16XX || ARCH_OMAP730 || ARCH_DAVINCI_DA8XX @@ -1078,4 +1133,33 @@ config RTC_DRV_PUV3 This drive can also be built as a module. If so, the module will be called rtc-puv3. +config RTC_PM8058 + tristate "PMIC8058 RTC support" + default n + depends on PMIC8058 + help + Say Y here if you want support for the PMIC8058 RTC. + + To compile this driver as a module, choose M here: the + module will be called pmic8058-rtc. + +config RTC_PM8058_WRITE_ENABLE + bool "PM8058 RTC write enable" + default n + depends on RTC_PM8058 + help + Say Y here if you want to support the write operation for + PMIC8058 RTC. + + By default the write operation is not supported. + +config RTC_DRV_PM8XXX + tristate "Qualcomm PMIC8XXX RTC" + depends on MFD_PM8XXX + help + Say Y here if you want to support the Qualcomm PMIC8XXX RTC. + + To compile this driver as a module, choose M here: the + module will be called rtc-pm8xxx. + endif # RTC_CLASS diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 7d279581043..9e412974ca0 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -67,6 +67,8 @@ obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o +obj-$(CONFIG_RTC_DRV_MSM) += rtc-msm.o +obj-$(CONFIG_RTC_DRV_MSM7X00A) += rtc-msm7x00a.o obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o obj-$(CONFIG_RTC_DRV_MV) += rtc-mv.o @@ -77,6 +79,7 @@ obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o +obj-$(CONFIG_RTC_DRV_PM8XXX) += rtc-pm8xxx.o obj-$(CONFIG_RTC_DRV_PL030) += rtc-pl030.o obj-$(CONFIG_RTC_DRV_PL031) += rtc-pl031.o obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o @@ -110,3 +113,4 @@ obj-$(CONFIG_RTC_DRV_VT8500) += rtc-vt8500.o obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o +obj-$(CONFIG_RTC_PM8058) += rtc-pm8058.o diff --git a/drivers/rtc/alarm.c b/drivers/rtc/alarm.c index e0e98dd1eac..3e7f698f7c0 100644 --- a/drivers/rtc/alarm.c +++ b/drivers/rtc/alarm.c @@ -299,6 +299,30 @@ int alarm_set_rtc(struct timespec new_time) return ret; } + +void +alarm_update_timedelta(struct timespec tmp_time, struct timespec new_time) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&alarm_slock, flags); + for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) { + hrtimer_try_to_cancel(&alarms[i].timer); + alarms[i].stopped = true; + alarms[i].stopped_time = timespec_to_ktime(tmp_time); + } + alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].delta = + alarms[ANDROID_ALARM_ELAPSED_REALTIME].delta = + ktime_sub(alarms[ANDROID_ALARM_ELAPSED_REALTIME].delta, + timespec_to_ktime(timespec_sub(tmp_time, new_time))); + for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) { + alarms[i].stopped = false; + update_timer_locked(&alarms[i], false); + } + spin_unlock_irqrestore(&alarm_slock, flags); +} + /** * alarm_get_elapsed_realtime - get the elapsed real time in ktime_t format * diff --git a/drivers/rtc/hctosys.c b/drivers/rtc/hctosys.c index bc90b091f19..29735c23fe8 100644 --- a/drivers/rtc/hctosys.c +++ b/drivers/rtc/hctosys.c @@ -24,7 +24,7 @@ int rtc_hctosys_ret = -ENODEV; -static int __init rtc_hctosys(void) +int rtc_hctosys(void) { int err = -ENODEV; struct rtc_time tm; diff --git a/drivers/rtc/rtc-msm.c b/drivers/rtc/rtc-msm.c new file mode 100644 index 00000000000..c17e461be40 --- /dev/null +++ b/drivers/rtc/rtc-msm.c @@ -0,0 +1,819 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2011 Code Aurora Forum. All rights reserved. + * Author: San Mehat + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define APP_TIMEREMOTE_PDEV_NAME "rs00000000" + +#define TIMEREMOTE_PROCEEDURE_SET_JULIAN 6 +#define TIMEREMOTE_PROCEEDURE_GET_JULIAN 7 +#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT +#define TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN 11 +#define TIMEREMOTE_PROCEEDURE_SET_SECURE_JULIAN 16 +#endif +#define TIMEREMOTE_PROG_NUMBER 0x30000048 +#define TIMEREMOTE_PROG_VER_1 0x00010001 +#define TIMEREMOTE_PROG_VER_2 0x00040001 + +#define RTC_REQUEST_CB_PROC 0x17 +#define RTC_CLIENT_INIT_PROC 0x12 +#define RTC_EVENT_CB_PROC 0x1 +#define RTC_CB_ID 0x1 + +/* Client request errors */ +enum rtc_rpc_err { + ERR_NONE, + ERR_CLIENT_ID_PTR, /* Invalid client ID pointer */ + ERR_CLIENT_TYPE, /* Invalid client type */ + ERR_CLIENT_ID, /* Invalid client ID */ + ERR_TASK_NOT_READY, /* task is not ready for clients */ + ERR_INVALID_PROCESSOR, /* Invalid processor id */ + ERR_UNSUPPORTED, /* Unsupported request */ + ERR_GENERAL, /* Any General Error */ + ERR_RPC, /* Any ONCRPC Error */ + ERR_ALREADY_REG, /* Client already registered */ + ERR_MAX +}; + +enum processor_type { + CLIENT_PROCESSOR_NONE = 0, + CLIENT_PROCESSOR_MODEM, + CLIENT_PROCESSOR_APP1, + CLIENT_PROCESSOR_APP2, + CLIENT_PROCESSOR_MAX +}; + +/* Client types */ +enum client_type { + CLIENT_TYPE_GEN1 = 0, + CLIENT_FLOATING1, + CLIENT_FLOATING2, + CLIENT_TYPE_INTERNAL, + CLIENT_TYPE_GENOFF_UPDATE, + CLIENT_TYPE_MAX +}; + +/* Event types */ +enum event_type { + EVENT_TOD_CHANGE = 0, + EVENT_GENOFF_CHANGE, + EVENT_MAX +}; + +struct tod_update_info { + uint32_t tick; + uint64_t stamp; + uint32_t freq; +}; + +enum time_bases_info { + TIME_RTC = 0, + TIME_TOD, + TIME_USER, + TIME_SECURE, + TIME_INVALID +}; + +struct genoff_update_info { + enum time_bases_info time_base; + uint64_t offset; +}; + +union cb_info { + struct tod_update_info tod_update; + struct genoff_update_info genoff_update; +}; + +struct rtc_cb_recv { + uint32_t client_cb_id; + enum event_type event; + uint32_t cb_info_ptr; + union cb_info cb_info_data; +}; + +struct msm_rtc { + int proc; + struct msm_rpc_client *rpc_client; + u8 client_id; + struct rtc_device *rtc; +#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT + struct rtc_device *rtcsecure; +#endif + unsigned long rtcalarm_time; +}; + +struct rpc_time_julian { + uint32_t year; + uint32_t month; + uint32_t day; + uint32_t hour; + uint32_t minute; + uint32_t second; + uint32_t day_of_week; +}; + +struct rtc_tod_args { + int proc; + struct rtc_time *tm; +}; + +#ifdef CONFIG_PM +struct suspend_state_info { + atomic_t state; + int64_t tick_at_suspend; +}; + +static struct suspend_state_info suspend_state = {ATOMIC_INIT(0), 0}; + +void msmrtc_updateatsuspend(struct timespec *ts) +{ + int64_t now, sleep, sclk_max; + + if (atomic_read(&suspend_state.state)) { + now = msm_timer_get_sclk_time(&sclk_max); + + if (now && suspend_state.tick_at_suspend) { + if (now < suspend_state.tick_at_suspend) { + sleep = sclk_max - + suspend_state.tick_at_suspend + now; + } else + sleep = now - suspend_state.tick_at_suspend; + + timespec_add_ns(ts, sleep); + suspend_state.tick_at_suspend = now; + } else + pr_err("%s: Invalid ticks from SCLK now=%lld" + "tick_at_suspend=%lld", __func__, now, + suspend_state.tick_at_suspend); + } + +} +#else +void msmrtc_updateatsuspend(struct timespec *ts) { } +#endif +EXPORT_SYMBOL(msmrtc_updateatsuspend); + +static int msmrtc_tod_proc_args(struct msm_rpc_client *client, void *buff, + void *data) +{ + struct rtc_tod_args *rtc_args = data; + + if ((rtc_args->proc == TIMEREMOTE_PROCEEDURE_SET_JULIAN) +#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT + || (rtc_args->proc == TIMEREMOTE_PROCEEDURE_SET_SECURE_JULIAN) +#endif + ) { + struct timeremote_set_julian_req { + uint32_t opt_arg; + struct rpc_time_julian time; + }; + struct timeremote_set_julian_req *set_req = buff; + + set_req->opt_arg = cpu_to_be32(0x1); + set_req->time.year = cpu_to_be32(rtc_args->tm->tm_year); + set_req->time.month = cpu_to_be32(rtc_args->tm->tm_mon + 1); + set_req->time.day = cpu_to_be32(rtc_args->tm->tm_mday); + set_req->time.hour = cpu_to_be32(rtc_args->tm->tm_hour); + set_req->time.minute = cpu_to_be32(rtc_args->tm->tm_min); + set_req->time.second = cpu_to_be32(rtc_args->tm->tm_sec); + set_req->time.day_of_week = cpu_to_be32(rtc_args->tm->tm_wday); + + return sizeof(*set_req); + + } else if ((rtc_args->proc == TIMEREMOTE_PROCEEDURE_GET_JULIAN) +#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT + || (rtc_args->proc == TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN) +#endif + ) { + *(uint32_t *)buff = (uint32_t) cpu_to_be32(0x1); + + return sizeof(uint32_t); + } else + return 0; +} + +static bool rtc_check_overflow(struct rtc_time *tm) +{ + if (tm->tm_year < 138) + return false; + + if (tm->tm_year > 138) + return true; + + if ((tm->tm_year == 138) && (tm->tm_mon == 0) && (tm->tm_mday < 19)) + return false; + + return true; +} + +static int msmrtc_tod_proc_result(struct msm_rpc_client *client, void *buff, + void *data) +{ + struct rtc_tod_args *rtc_args = data; + + if ((rtc_args->proc == TIMEREMOTE_PROCEEDURE_GET_JULIAN) +#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT + || (rtc_args->proc == TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN) +#endif + ) { + struct timeremote_get_julian_rep { + uint32_t opt_arg; + struct rpc_time_julian time; + }; + struct timeremote_get_julian_rep *result = buff; + + if (be32_to_cpu(result->opt_arg) != 0x1) + return -ENODATA; + + rtc_args->tm->tm_year = be32_to_cpu(result->time.year); + rtc_args->tm->tm_mon = be32_to_cpu(result->time.month); + rtc_args->tm->tm_mday = be32_to_cpu(result->time.day); + rtc_args->tm->tm_hour = be32_to_cpu(result->time.hour); + rtc_args->tm->tm_min = be32_to_cpu(result->time.minute); + rtc_args->tm->tm_sec = be32_to_cpu(result->time.second); + rtc_args->tm->tm_wday = be32_to_cpu(result->time.day_of_week); + + pr_debug("%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n", + __func__, rtc_args->tm->tm_mon, rtc_args->tm->tm_mday, + rtc_args->tm->tm_year, rtc_args->tm->tm_hour, + rtc_args->tm->tm_min, rtc_args->tm->tm_sec, + rtc_args->tm->tm_wday); + + /* RTC layer expects years to start at 1900 */ + rtc_args->tm->tm_year -= 1900; + /* RTC layer expects mons to be 0 based */ + rtc_args->tm->tm_mon--; + + if (rtc_valid_tm(rtc_args->tm) < 0) { + pr_err("%s: Retrieved data/time not valid\n", __func__); + rtc_time_to_tm(0, rtc_args->tm); + } + + /* + * Check if the time received is > 01-19-2038, to prevent + * overflow. In such a case, return the EPOCH time. + */ + if (rtc_check_overflow(rtc_args->tm) == true) { + pr_err("Invalid time (year > 2038)\n"); + rtc_time_to_tm(0, rtc_args->tm); + } + + return 0; + } else + return 0; +} + +static int +msmrtc_timeremote_set_time(struct device *dev, struct rtc_time *tm) +{ + int rc; + struct rtc_tod_args rtc_args; + struct msm_rtc *rtc_pdata = dev_get_drvdata(dev); + + if (tm->tm_year < 1900) + tm->tm_year += 1900; + + if (tm->tm_year < 1970) + return -EINVAL; + + dev_dbg(dev, "%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n", + __func__, tm->tm_mon, tm->tm_mday, tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday); + + rtc_args.proc = TIMEREMOTE_PROCEEDURE_SET_JULIAN; + rtc_args.tm = tm; + rc = msm_rpc_client_req(rtc_pdata->rpc_client, + TIMEREMOTE_PROCEEDURE_SET_JULIAN, + msmrtc_tod_proc_args, &rtc_args, + NULL, NULL, -1); + if (rc) { + dev_err(dev, "%s: rtc time (TOD) could not be set\n", __func__); + return rc; + } + + return 0; +} + +static int +msmrtc_timeremote_read_time(struct device *dev, struct rtc_time *tm) +{ + int rc; + struct rtc_tod_args rtc_args; + struct msm_rtc *rtc_pdata = dev_get_drvdata(dev); + + rtc_args.proc = TIMEREMOTE_PROCEEDURE_GET_JULIAN; + rtc_args.tm = tm; + + rc = msm_rpc_client_req(rtc_pdata->rpc_client, + TIMEREMOTE_PROCEEDURE_GET_JULIAN, + msmrtc_tod_proc_args, &rtc_args, + msmrtc_tod_proc_result, &rtc_args, -1); + + if (rc) { + dev_err(dev, "%s: Error retrieving rtc (TOD) time\n", __func__); + return rc; + } + + return 0; +} + +static int +msmrtc_virtual_alarm_set(struct device *dev, struct rtc_wkalrm *a) +{ + struct msm_rtc *rtc_pdata = dev_get_drvdata(dev); + unsigned long now = get_seconds(); + + if (!a->enabled) { + rtc_pdata->rtcalarm_time = 0; + return 0; + } else + rtc_tm_to_time(&a->time, &(rtc_pdata->rtcalarm_time)); + + if (now > rtc_pdata->rtcalarm_time) { + dev_err(dev, "%s: Attempt to set alarm in the past\n", + __func__); + rtc_pdata->rtcalarm_time = 0; + return -EINVAL; + } + + return 0; +} + +static struct rtc_class_ops msm_rtc_ops = { + .read_time = msmrtc_timeremote_read_time, + .set_time = msmrtc_timeremote_set_time, + .set_alarm = msmrtc_virtual_alarm_set, +}; + +#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT +static int +msmrtc_timeremote_set_time_secure(struct device *dev, struct rtc_time *tm) +{ + int rc; + struct rtc_tod_args rtc_args; + struct msm_rtc *rtc_pdata = dev_get_drvdata(dev); + + if (tm->tm_year < 1900) + tm->tm_year += 1900; + + if (tm->tm_year < 1970) + return -EINVAL; + + dev_dbg(dev, "%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n", + __func__, tm->tm_mon, tm->tm_mday, tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday); + + rtc_args.proc = TIMEREMOTE_PROCEEDURE_SET_SECURE_JULIAN; + rtc_args.tm = tm; + + rc = msm_rpc_client_req(rtc_pdata->rpc_client, + TIMEREMOTE_PROCEEDURE_SET_SECURE_JULIAN, + msmrtc_tod_proc_args, &rtc_args, + NULL, NULL, -1); + if (rc) { + dev_err(dev, + "%s: rtc secure time could not be set\n", __func__); + return rc; + } + + return 0; +} + +static int +msmrtc_timeremote_read_time_secure(struct device *dev, struct rtc_time *tm) +{ + int rc; + struct rtc_tod_args rtc_args; + struct msm_rtc *rtc_pdata = dev_get_drvdata(dev); + rtc_args.proc = TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN; + rtc_args.tm = tm; + + rc = msm_rpc_client_req(rtc_pdata->rpc_client, + TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN, msmrtc_tod_proc_args, + &rtc_args, msmrtc_tod_proc_result, &rtc_args, -1); + + if (rc) { + dev_err(dev, + "%s: Error retrieving secure rtc time\n", __func__); + return rc; + } + + return 0; +} + +static struct rtc_class_ops msm_rtc_ops_secure = { + .read_time = msmrtc_timeremote_read_time_secure, + .set_time = msmrtc_timeremote_set_time_secure, +}; +#endif + +static void process_cb_request(void *buffer) +{ + struct rtc_cb_recv *rtc_cb = buffer; + struct timespec ts, tv; + + rtc_cb->client_cb_id = be32_to_cpu(rtc_cb->client_cb_id); + rtc_cb->event = be32_to_cpu(rtc_cb->event); + rtc_cb->cb_info_ptr = be32_to_cpu(rtc_cb->cb_info_ptr); + + if (rtc_cb->event == EVENT_TOD_CHANGE) { + /* A TOD update has been received from the Modem */ + rtc_cb->cb_info_data.tod_update.tick = + be32_to_cpu(rtc_cb->cb_info_data.tod_update.tick); + rtc_cb->cb_info_data.tod_update.stamp = + be64_to_cpu(rtc_cb->cb_info_data.tod_update.stamp); + rtc_cb->cb_info_data.tod_update.freq = + be32_to_cpu(rtc_cb->cb_info_data.tod_update.freq); + pr_info("RPC CALL -- TOD TIME UPDATE: ttick = %d\n" + "stamp=%lld, freq = %d\n", + rtc_cb->cb_info_data.tod_update.tick, + rtc_cb->cb_info_data.tod_update.stamp, + rtc_cb->cb_info_data.tod_update.freq); + + getnstimeofday(&ts); + msmrtc_updateatsuspend(&ts); + rtc_hctosys(); + getnstimeofday(&tv); + /* Update the alarm information with the new time info. */ + alarm_update_timedelta(ts, tv); + + } else + pr_err("%s: Unknown event EVENT=%x\n", + __func__, rtc_cb->event); +} + +static int msmrtc_cb_func(struct msm_rpc_client *client, void *buffer, int size) +{ + int rc = -1; + struct rpc_request_hdr *recv = buffer; + + recv->xid = be32_to_cpu(recv->xid); + recv->type = be32_to_cpu(recv->type); + recv->rpc_vers = be32_to_cpu(recv->rpc_vers); + recv->prog = be32_to_cpu(recv->prog); + recv->vers = be32_to_cpu(recv->vers); + recv->procedure = be32_to_cpu(recv->procedure); + + if (recv->procedure == RTC_EVENT_CB_PROC) + process_cb_request((void *) (recv + 1)); + + msm_rpc_start_accepted_reply(client, recv->xid, + RPC_ACCEPTSTAT_SUCCESS); + + rc = msm_rpc_send_accepted_reply(client, 0); + if (rc) { + pr_debug("%s: sending reply failed: %d\n", __func__, rc); + return rc; + } + + return 0; +} + +static int msmrtc_rpc_proc_args(struct msm_rpc_client *client, void *buff, + void *data) +{ + struct msm_rtc *rtc_pdata = data; + + if (rtc_pdata->proc == RTC_CLIENT_INIT_PROC) { + /* arguments passed to the client_init function */ + struct rtc_client_init_req { + enum client_type client; + uint32_t client_id_ptr; + u8 client_id; + enum processor_type processor; + }; + struct rtc_client_init_req *req_1 = buff; + + req_1->client = cpu_to_be32(CLIENT_TYPE_INTERNAL); + req_1->client_id_ptr = cpu_to_be32(0x1); + req_1->client_id = (u8) cpu_to_be32(0x1); + req_1->processor = cpu_to_be32(CLIENT_PROCESSOR_APP1); + + return sizeof(*req_1); + + } else if (rtc_pdata->proc == RTC_REQUEST_CB_PROC) { + /* arguments passed to the request_cb function */ + struct rtc_event_req { + u8 client_id; + uint32_t rtc_cb_id; + }; + struct rtc_event_req *req_2 = buff; + + req_2->client_id = (u8) cpu_to_be32(rtc_pdata->client_id); + req_2->rtc_cb_id = cpu_to_be32(RTC_CB_ID); + + return sizeof(*req_2); + } else + return 0; +} + +static int msmrtc_rpc_proc_result(struct msm_rpc_client *client, void *buff, + void *data) +{ + uint32_t result = -EINVAL; + struct msm_rtc *rtc_pdata = data; + + if (rtc_pdata->proc == RTC_CLIENT_INIT_PROC) { + /* process reply received from client_init function */ + uint32_t client_id_ptr; + result = be32_to_cpu(*(uint32_t *)buff); + buff += sizeof(uint32_t); + client_id_ptr = be32_to_cpu(*(uint32_t *)(buff)); + buff += sizeof(uint32_t); + if (client_id_ptr == 1) + rtc_pdata->client_id = (u8) + be32_to_cpu(*(uint32_t *)(buff)); + else { + pr_debug("%s: Client-id not received from Modem\n", + __func__); + return -EINVAL; + } + } else if (rtc_pdata->proc == RTC_REQUEST_CB_PROC) { + /* process reply received from request_cb function */ + result = be32_to_cpu(*(uint32_t *)buff); + } + + if (result == ERR_NONE) { + pr_debug("%s: RPC client reply for PROC=%x success\n", + __func__, rtc_pdata->proc); + return 0; + } + + pr_debug("%s: RPC client registration failed ERROR=%x\n", + __func__, result); + return -EINVAL; +} + +static int msmrtc_setup_cb(struct msm_rtc *rtc_pdata) +{ + int rc; + + /* Register with the server with client specific info */ + rtc_pdata->proc = RTC_CLIENT_INIT_PROC; + rc = msm_rpc_client_req(rtc_pdata->rpc_client, RTC_CLIENT_INIT_PROC, + msmrtc_rpc_proc_args, rtc_pdata, + msmrtc_rpc_proc_result, rtc_pdata, -1); + if (rc) { + pr_debug("%s: RPC client registration for PROC:%x failed\n", + __func__, RTC_CLIENT_INIT_PROC); + return rc; + } + + /* Register with server for the callback event */ + rtc_pdata->proc = RTC_REQUEST_CB_PROC; + rc = msm_rpc_client_req(rtc_pdata->rpc_client, RTC_REQUEST_CB_PROC, + msmrtc_rpc_proc_args, rtc_pdata, + msmrtc_rpc_proc_result, rtc_pdata, -1); + if (rc) { + pr_debug("%s: RPC client registration for PROC:%x failed\n", + __func__, RTC_REQUEST_CB_PROC); + } + + return rc; +} + +static int __devinit +msmrtc_probe(struct platform_device *pdev) +{ + int rc; + struct msm_rtc *rtc_pdata = NULL; + struct rpcsvr_platform_device *rdev = + container_of(pdev, struct rpcsvr_platform_device, base); + uint32_t prog_version; + + + if (pdev->id == (TIMEREMOTE_PROG_VER_1 & RPC_VERSION_MAJOR_MASK)) + prog_version = TIMEREMOTE_PROG_VER_1; + else if (pdev->id == (TIMEREMOTE_PROG_VER_2 & + RPC_VERSION_MAJOR_MASK)) + prog_version = TIMEREMOTE_PROG_VER_2; + else + return -EINVAL; + + rtc_pdata = kzalloc(sizeof(*rtc_pdata), GFP_KERNEL); + if (rtc_pdata == NULL) { + dev_err(&pdev->dev, + "%s: Unable to allocate memory\n", __func__); + return -ENOMEM; + } + rtc_pdata->rpc_client = msm_rpc_register_client("rtc", rdev->prog, + prog_version, 1, msmrtc_cb_func); + if (IS_ERR(rtc_pdata->rpc_client)) { + dev_err(&pdev->dev, + "%s: init RPC failed! VERS = %x\n", __func__, + prog_version); + rc = PTR_ERR(rtc_pdata->rpc_client); + kfree(rtc_pdata); + return rc; + } + + /* + * Set up the callback client. + * For older targets this initialization will fail + */ + rc = msmrtc_setup_cb(rtc_pdata); + if (rc) + dev_dbg(&pdev->dev, "%s: Could not initialize RPC callback\n", + __func__); + + rtc_pdata->rtcalarm_time = 0; + platform_set_drvdata(pdev, rtc_pdata); + + rtc_pdata->rtc = rtc_device_register("msm_rtc", + &pdev->dev, + &msm_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc_pdata->rtc)) { + dev_err(&pdev->dev, "%s: Can't register RTC device (%ld)\n", + pdev->name, PTR_ERR(rtc_pdata->rtc)); + rc = PTR_ERR(rtc_pdata->rtc); + goto fail_cb_setup; + } + +#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT + rtc_pdata->rtcsecure = rtc_device_register("msm_rtc_secure", + &pdev->dev, + &msm_rtc_ops_secure, + THIS_MODULE); + + if (IS_ERR(rtc_pdata->rtcsecure)) { + dev_err(&pdev->dev, + "%s: Can't register RTC Secure device (%ld)\n", + pdev->name, PTR_ERR(rtc_pdata->rtcsecure)); + rtc_device_unregister(rtc_pdata->rtc); + rc = PTR_ERR(rtc_pdata->rtcsecure); + goto fail_cb_setup; + } +#endif + +#ifdef CONFIG_RTC_ASYNC_MODEM_SUPPORT + rtc_hctosys(); +#endif + + return 0; + +fail_cb_setup: + msm_rpc_unregister_client(rtc_pdata->rpc_client); + kfree(rtc_pdata); + return rc; +} + + +#ifdef CONFIG_PM + +static void +msmrtc_alarmtimer_expired(unsigned long _data, + struct msm_rtc *rtc_pdata) +{ + pr_debug("%s: Generating alarm event (src %lu)\n", + rtc_pdata->rtc->name, _data); + + rtc_update_irq(rtc_pdata->rtc, 1, RTC_IRQF | RTC_AF); + rtc_pdata->rtcalarm_time = 0; +} + +static int +msmrtc_suspend(struct platform_device *dev, pm_message_t state) +{ + int rc, diff; + struct rtc_time tm; + unsigned long now; + struct msm_rtc *rtc_pdata = platform_get_drvdata(dev); + + suspend_state.tick_at_suspend = msm_timer_get_sclk_time(NULL); + if (rtc_pdata->rtcalarm_time) { + rc = msmrtc_timeremote_read_time(&dev->dev, &tm); + if (rc) { + dev_err(&dev->dev, + "%s: Unable to read from RTC\n", __func__); + return rc; + } + rtc_tm_to_time(&tm, &now); + diff = rtc_pdata->rtcalarm_time - now; + if (diff <= 0) { + msmrtc_alarmtimer_expired(1 , rtc_pdata); + msm_pm_set_max_sleep_time(0); + atomic_inc(&suspend_state.state); + return 0; + } + msm_pm_set_max_sleep_time((int64_t) + ((int64_t) diff * NSEC_PER_SEC)); + } else + msm_pm_set_max_sleep_time(0); + atomic_inc(&suspend_state.state); + return 0; +} + +static int +msmrtc_resume(struct platform_device *dev) +{ + int rc, diff; + struct rtc_time tm; + unsigned long now; + struct msm_rtc *rtc_pdata = platform_get_drvdata(dev); + + if (rtc_pdata->rtcalarm_time) { + rc = msmrtc_timeremote_read_time(&dev->dev, &tm); + if (rc) { + dev_err(&dev->dev, + "%s: Unable to read from RTC\n", __func__); + return rc; + } + rtc_tm_to_time(&tm, &now); + diff = rtc_pdata->rtcalarm_time - now; + if (diff <= 0) + msmrtc_alarmtimer_expired(2 , rtc_pdata); + } + suspend_state.tick_at_suspend = 0; + atomic_dec(&suspend_state.state); + return 0; +} +#else +#define msmrtc_suspend NULL +#define msmrtc_resume NULL +#endif + +static int __devexit msmrtc_remove(struct platform_device *pdev) +{ + struct msm_rtc *rtc_pdata = platform_get_drvdata(pdev); + + rtc_device_unregister(rtc_pdata->rtc); +#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT + rtc_device_unregister(rtc_pdata->rtcsecure); +#endif + msm_rpc_unregister_client(rtc_pdata->rpc_client); + kfree(rtc_pdata); + + return 0; +} + +static struct platform_driver msmrtc_driver = { + .probe = msmrtc_probe, + .suspend = msmrtc_suspend, + .resume = msmrtc_resume, + .remove = __devexit_p(msmrtc_remove), + .driver = { + .name = APP_TIMEREMOTE_PDEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msmrtc_init(void) +{ + int rc; + + /* + * For backward compatibility, register multiple platform + * drivers with the RPC PROG_VERS to be supported. + * + * Explicit cast away of 'constness' for driver.name in order to + * initialize it here. + */ + snprintf((char *)msmrtc_driver.driver.name, + strlen(msmrtc_driver.driver.name)+1, + "rs%08x", TIMEREMOTE_PROG_NUMBER); + pr_debug("RTC Registering with %s\n", msmrtc_driver.driver.name); + + rc = platform_driver_register(&msmrtc_driver); + if (rc) + pr_err("%s: platfrom_driver_register failed\n", __func__); + + return rc; +} + +static void __exit msmrtc_exit(void) +{ + platform_driver_unregister(&msmrtc_driver); +} + +module_init(msmrtc_init); +module_exit(msmrtc_exit); + +MODULE_DESCRIPTION("RTC driver for Qualcomm MSM7x00a chipsets"); +MODULE_AUTHOR("San Mehat "); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-msm7x00a.c b/drivers/rtc/rtc-msm7x00a.c new file mode 100644 index 00000000000..690bc398cd6 --- /dev/null +++ b/drivers/rtc/rtc-msm7x00a.c @@ -0,0 +1,280 @@ +/* drivers/rtc/rtc-msm7x00a.c + * + * Copyright (C) 2008 Google, Inc. + * Author: San Mehat + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define RTC_DEBUG 0 + +extern void msm_pm_set_max_sleep_time(int64_t sleep_time_ns); + +#if CONFIG_MSM_AMSS_VERSION >= 6350 || defined(CONFIG_ARCH_QSD8X50) +#define APP_TIMEREMOTE_PDEV_NAME "rs30000048:00010000" +#else +#define APP_TIMEREMOTE_PDEV_NAME "rs30000048:0da5b528" +#endif + +#define TIMEREMOTE_PROCEEDURE_SET_JULIAN 6 +#define TIMEREMOTE_PROCEEDURE_GET_JULIAN 7 + +struct rpc_time_julian { + uint32_t year; + uint32_t month; + uint32_t day; + uint32_t hour; + uint32_t minute; + uint32_t second; + uint32_t day_of_week; +}; + +static struct msm_rpc_endpoint *ep; +static struct rtc_device *rtc; +static unsigned long rtcalarm_time; + +static int +msmrtc_timeremote_set_time(struct device *dev, struct rtc_time *tm) +{ + int rc; + + struct timeremote_set_julian_req { + struct rpc_request_hdr hdr; + uint32_t opt_arg; + + struct rpc_time_julian time; + } req; + + struct timeremote_set_julian_rep { + struct rpc_reply_hdr hdr; + } rep; + + if (tm->tm_year < 1900) + tm->tm_year += 1900; + + if (tm->tm_year < 1970) + return -EINVAL; + +#if RTC_DEBUG + printk(KERN_DEBUG "%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n", + __func__, tm->tm_mon, tm->tm_mday, tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday); +#endif + + req.opt_arg = cpu_to_be32(1); + req.time.year = cpu_to_be32(tm->tm_year); + req.time.month = cpu_to_be32(tm->tm_mon + 1); + req.time.day = cpu_to_be32(tm->tm_mday); + req.time.hour = cpu_to_be32(tm->tm_hour); + req.time.minute = cpu_to_be32(tm->tm_min); + req.time.second = cpu_to_be32(tm->tm_sec); + req.time.day_of_week = cpu_to_be32(tm->tm_wday); + + + rc = msm_rpc_call_reply(ep, TIMEREMOTE_PROCEEDURE_SET_JULIAN, + &req, sizeof(req), + &rep, sizeof(rep), + 5 * HZ); + return rc; +} + +static int +msmrtc_timeremote_read_time(struct device *dev, struct rtc_time *tm) +{ + int rc; + + struct timeremote_get_julian_req { + struct rpc_request_hdr hdr; + uint32_t julian_time_not_null; + } req; + + struct timeremote_get_julian_rep { + struct rpc_reply_hdr hdr; + uint32_t opt_arg; + struct rpc_time_julian time; + } rep; + + req.julian_time_not_null = cpu_to_be32(1); + + rc = msm_rpc_call_reply(ep, TIMEREMOTE_PROCEEDURE_GET_JULIAN, + &req, sizeof(req), + &rep, sizeof(rep), + 5 * HZ); + if (rc < 0) + return rc; + + if (!be32_to_cpu(rep.opt_arg)) { + printk(KERN_ERR "%s: No data from RTC\n", __func__); + return -ENODATA; + } + + tm->tm_year = be32_to_cpu(rep.time.year); + tm->tm_mon = be32_to_cpu(rep.time.month); + tm->tm_mday = be32_to_cpu(rep.time.day); + tm->tm_hour = be32_to_cpu(rep.time.hour); + tm->tm_min = be32_to_cpu(rep.time.minute); + tm->tm_sec = be32_to_cpu(rep.time.second); + tm->tm_wday = be32_to_cpu(rep.time.day_of_week); + +#if RTC_DEBUG + printk(KERN_DEBUG "%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n", + __func__, tm->tm_mon, tm->tm_mday, tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday); +#endif + + tm->tm_year -= 1900; /* RTC layer expects years to start at 1900 */ + tm->tm_mon--; /* RTC layer expects mons to be 0 based */ + + if (rtc_valid_tm(tm) < 0) { + dev_err(dev, "retrieved date/time is not valid.\n"); + rtc_time_to_tm(0, tm); + } + + return 0; +} + + +static int +msmrtc_virtual_alarm_set(struct device *dev, struct rtc_wkalrm *a) +{ + unsigned long now = get_seconds(); + + if (!a->enabled) { + rtcalarm_time = 0; + return 0; + } else + rtc_tm_to_time(&a->time, &rtcalarm_time); + + if (now > rtcalarm_time) { + printk(KERN_ERR "%s: Attempt to set alarm in the past\n", + __func__); + rtcalarm_time = 0; + return -EINVAL; + } + + return 0; +} + +static struct rtc_class_ops msm_rtc_ops = { + .read_time = msmrtc_timeremote_read_time, + .set_time = msmrtc_timeremote_set_time, + .set_alarm = msmrtc_virtual_alarm_set, +}; + +static void +msmrtc_alarmtimer_expired(unsigned long _data) +{ +#if RTC_DEBUG + printk(KERN_DEBUG "%s: Generating alarm event (src %lu)\n", + rtc->name, _data); +#endif + rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF); + rtcalarm_time = 0; +} + +static int +msmrtc_probe(struct platform_device *pdev) +{ + struct rpcsvr_platform_device *rdev = + container_of(pdev, struct rpcsvr_platform_device, base); + + ep = msm_rpc_connect(rdev->prog, rdev->vers, 0); + if (IS_ERR(ep)) { + printk(KERN_ERR "%s: init rpc failed! rc = %ld\n", + __func__, PTR_ERR(ep)); + return PTR_ERR(ep); + } + + rtc = rtc_device_register("msm_rtc", + &pdev->dev, + &msm_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) { + printk(KERN_ERR "%s: Can't register RTC device (%ld)\n", + pdev->name, PTR_ERR(rtc)); + return PTR_ERR(rtc); + } + return 0; +} + + +static unsigned long msmrtc_get_seconds(void) +{ + struct rtc_time tm; + unsigned long now; + + msmrtc_timeremote_read_time(NULL, &tm); + rtc_tm_to_time(&tm, &now); + return now; +} + +static int +msmrtc_suspend(struct platform_device *dev, pm_message_t state) +{ + if (rtcalarm_time) { + unsigned long now = msmrtc_get_seconds(); + int diff = rtcalarm_time - now; + if (diff <= 0) { + msmrtc_alarmtimer_expired(1); + msm_pm_set_max_sleep_time(0); + return 0; + } + msm_pm_set_max_sleep_time((int64_t) ((int64_t) diff * NSEC_PER_SEC)); + } else + msm_pm_set_max_sleep_time(0); + return 0; +} + +static int +msmrtc_resume(struct platform_device *dev) +{ + if (rtcalarm_time) { + unsigned long now = msmrtc_get_seconds(); + int diff = rtcalarm_time - now; + if (diff <= 0) + msmrtc_alarmtimer_expired(2); + } + return 0; +} + +static struct platform_driver msmrtc_driver = { + .probe = msmrtc_probe, + .suspend = msmrtc_suspend, + .resume = msmrtc_resume, + .driver = { + .name = APP_TIMEREMOTE_PDEV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msmrtc_init(void) +{ + rtcalarm_time = 0; + return platform_driver_register(&msmrtc_driver); +} + +module_init(msmrtc_init); + +MODULE_DESCRIPTION("RTC driver for Qualcomm MSM7x00a chipsets"); +MODULE_AUTHOR("San Mehat "); +MODULE_LICENSE("GPL"); diff --git a/drivers/rtc/rtc-pm8058.c b/drivers/rtc/rtc-pm8058.c new file mode 100644 index 00000000000..5d9111adf95 --- /dev/null +++ b/drivers/rtc/rtc-pm8058.c @@ -0,0 +1,563 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PM8058_RTC_CTRL 0x1E8 + #define PM8058_RTC_ENABLE BIT(7) + #define PM8058_RTC_ALARM_ENABLE BIT(1) +#define PM8058_RTC_ALARM_CTRL 0x1E9 + #define PM8058_RTC_ALARM_CLEAR BIT(0) +#define PM8058_RTC_TEST 0x1F6 +#define PM8058_RTC_READ_BASE 0x1EE +#define PM8058_RTC_WRITE_BASE 0x1EA +#define PM8058_RTC_ALARM_BASE 0x1F2 + +struct pm8058_rtc { + struct rtc_device *rtc0; + u8 rtc_ctrl_reg; + int rtc_irq; + int rtc_alarm_irq; + struct pm8058_chip *pm_chip; +}; + +static int +pm8058_rtc_read_bytes(struct pm8058_rtc *rtc_dd, u8 *rtc_val, int base) +{ + int i, rc; + + /* + * Read the 32-bit RTC/Alarm Value. + * These values have to be read 8-bit at a time. + */ + for (i = 0; i < 4; i++) { + rc = pm8058_read(rtc_dd->pm_chip, base + i, &rtc_val[i], 1); + if (rc < 0) { + pr_err("%s: PM8058 read failed\n", __func__); + return rc; + } + } + + return 0; +} + +static int +pm8058_rtc_write_bytes(struct pm8058_rtc *rtc_dd, u8 *rtc_val, int base) +{ + int i, rc; + + /* + * Write the 32-bit Value. + * These values have to be written 8-bit at a time. + */ + for (i = 0; i < 4; i++) { + rc = pm8058_write(rtc_dd->pm_chip, base + i, &rtc_val[i], 1); + if (rc < 0) { + pr_err("%s: PM8058 read failed\n", __func__); + return rc; + } + } + + return 0; +} + +/* + * Steps to write the RTC registers. + * 1. Disable alarm if enabled. + * 2. Write 0x00 to LSB. + * 3. Write Byte[1], Byte[2], Byte[3] then Byte[0]. + * 4. Enable alarm if disabled earlier. + */ +#ifdef CONFIG_RTC_PM8058_WRITE_ENABLE +static int +pm8058_rtc0_set_time(struct device *dev, struct rtc_time *tm) +{ + int rc; + unsigned long secs = 0; + u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg = 0, i; + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev); + + ctrl_reg = rtc_dd->rtc_ctrl_reg; + + rtc_tm_to_time(tm, &secs); + + value[0] = secs & 0xFF; + value[1] = (secs >> 8) & 0xFF; + value[2] = (secs >> 16) & 0xFF; + value[3] = (secs >> 24) & 0xFF; + + pr_debug("%s: Seconds value to be written to RTC = %lu\n", __func__, + secs); + /* Disable alarm before updating RTC */ + if (ctrl_reg & PM8058_RTC_ALARM_ENABLE) { + alarm_enabled = 1; + ctrl_reg &= ~PM8058_RTC_ALARM_ENABLE; + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, + &ctrl_reg, 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + return rc; + } + } + + /* Write Byte[1], Byte[2], Byte[3], Byte[0] */ + reg = 0; + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE, ®, 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + return rc; + } + + for (i = 1; i < 4; i++) { + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE + i, + &value[i], 1); + if (rc < 0) { + pr_err("%s:Write to RTC registers failed\n", __func__); + return rc; + } + } + + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE, + &value[0], 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + return rc; + } + + if (alarm_enabled) { + ctrl_reg |= PM8058_RTC_ALARM_ENABLE; + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, + &ctrl_reg, 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + return rc; + } + } + + rtc_dd->rtc_ctrl_reg = ctrl_reg; + + return 0; +} +#endif + +static int +pm8058_rtc0_read_time(struct device *dev, struct rtc_time *tm) +{ + int rc; + u8 value[4], reg; + unsigned long secs = 0; + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev); + + rc = pm8058_rtc_read_bytes(rtc_dd, value, PM8058_RTC_READ_BASE); + if (rc < 0) { + pr_err("%s: RTC time read failed\n", __func__); + return rc; + } + + /* + * Read the LSB again and check if there has been a carry over. + * If there is, redo the read operation. + */ + rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_READ_BASE, ®, 1); + if (rc < 0) { + pr_err("%s: PM8058 read failed\n", __func__); + return rc; + } + + if (unlikely(reg < value[0])) { + rc = pm8058_rtc_read_bytes(rtc_dd, value, + PM8058_RTC_READ_BASE); + if (rc < 0) { + pr_err("%s: RTC time read failed\n", __func__); + return rc; + } + } + + secs = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); + + rtc_time_to_tm(secs, tm); + + rc = rtc_valid_tm(tm); + if (rc < 0) { + pr_err("%s: Invalid time read from PMIC8058\n", __func__); + return rc; + } + + pr_debug("%s: secs = %lu, h::m:s == %d::%d::%d, d/m/y = %d/%d/%d\n", + __func__, secs, tm->tm_hour, tm->tm_min, tm->tm_sec, + tm->tm_mday, tm->tm_mon, tm->tm_year); + + return 0; +} + +static int +pm8058_rtc0_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + int rc; + u8 value[4], reg; + struct rtc_time rtc_tm; + unsigned long secs_alarm, secs_rtc; + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev); + + reg = rtc_dd->rtc_ctrl_reg; + + /* Check if the alarm is valid */ + rc = rtc_valid_tm(&alarm->time); + if (rc < 0) { + pr_err("%s: Alarm time invalid\n", __func__); + return -EINVAL; + } + + rtc_tm_to_time(&alarm->time, &secs_alarm); + + /* + * Read the current RTC time and verify if the alarm time is in the + * past. If yes, return invalid. + */ + rc = pm8058_rtc0_read_time(dev, &rtc_tm); + if (rc) { + pr_err("%s: Unable to read RTC time\n", __func__); + return -EINVAL; + } + rtc_tm_to_time(&rtc_tm, &secs_rtc); + + if (secs_alarm < secs_rtc) { + pr_err("%s: Trying to set alarm in the past\n", __func__); + return -EINVAL; + } + + value[0] = secs_alarm & 0xFF; + value[1] = (secs_alarm >> 8) & 0xFF; + value[2] = (secs_alarm >> 16) & 0xFF; + value[3] = (secs_alarm >> 24) & 0xFF; + + rc = pm8058_rtc_write_bytes(rtc_dd, value, PM8058_RTC_ALARM_BASE); + if (rc < 0) { + pr_err("%s: Alarm could not be set\n", __func__); + return rc; + } + + reg = (alarm->enabled) ? (reg | PM8058_RTC_ALARM_ENABLE) : + (reg & ~PM8058_RTC_ALARM_ENABLE); + + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + return rc; + } + + rtc_dd->rtc_ctrl_reg = reg; + + pr_debug("%s: Alarm Set for h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n", + __func__, alarm->time.tm_hour, alarm->time.tm_min, + alarm->time.tm_sec, alarm->time.tm_mday, + alarm->time.tm_mon, alarm->time.tm_year); + + return 0; +} + +static int +pm8058_rtc0_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + int rc; + u8 value[4], reg; + unsigned long secs = 0; + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev); + + reg = rtc_dd->rtc_ctrl_reg; + + alarm->enabled = !!(reg & PM8058_RTC_ALARM_ENABLE); + + rc = pm8058_rtc_read_bytes(rtc_dd, value, + PM8058_RTC_ALARM_BASE); + if (rc < 0) { + pr_err("%s: RTC alarm time read failed\n", __func__); + return rc; + } + + secs = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); + + rtc_time_to_tm(secs, &alarm->time); + + rc = rtc_valid_tm(&alarm->time); + if (rc < 0) { + pr_err("%s: Invalid time read from PMIC8058\n", __func__); + return rc; + } + + pr_debug("%s: Alarm set for - h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n", + __func__, alarm->time.tm_hour, alarm->time.tm_min, + alarm->time.tm_sec, alarm->time.tm_mday, + alarm->time.tm_mon, alarm->time.tm_year); + + return 0; +} + + +static int +pm8058_rtc0_alarm_irq_enable(struct device *dev, unsigned int enable) +{ + int rc; + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev); + u8 reg; + + reg = rtc_dd->rtc_ctrl_reg; + reg = (enable) ? (reg | PM8058_RTC_ALARM_ENABLE) : + (reg & ~PM8058_RTC_ALARM_ENABLE); + + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + return rc; + } + + rtc_dd->rtc_ctrl_reg = reg; + + return rc; +} + +static struct rtc_class_ops pm8058_rtc0_ops = { + .read_time = pm8058_rtc0_read_time, + .set_alarm = pm8058_rtc0_set_alarm, + .read_alarm = pm8058_rtc0_read_alarm, + .alarm_irq_enable = pm8058_rtc0_alarm_irq_enable, +}; + +static irqreturn_t pm8058_alarm_trigger(int irq, void *dev_id) +{ + u8 reg; + int rc; + unsigned long events = 0; + struct pm8058_rtc *rtc_dd = dev_id; + + events = RTC_IRQF | RTC_AF; + rtc_update_irq(rtc_dd->rtc0, 1, events); + + pr_debug("%s: Alarm Triggered !!\n", __func__); + + /* Clear the alarm enable bit */ + reg = rtc_dd->rtc_ctrl_reg; + + reg &= ~PM8058_RTC_ALARM_ENABLE; + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, + ®, 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + goto rtc_alarm_handled; + } + + rtc_dd->rtc_ctrl_reg = reg; + + /* Clear RTC alarm register */ + rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_ALARM_CTRL, ®, 1); + if (rc < 0) { + pr_err("%s: PM8058 read failed\n", __func__); + goto rtc_alarm_handled; + } + + reg &= ~PM8058_RTC_ALARM_CLEAR; + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_ALARM_CTRL, ®, 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + goto rtc_alarm_handled; + } + +rtc_alarm_handled: + return IRQ_HANDLED; +} + +static int __devinit pm8058_rtc_probe(struct platform_device *pdev) +{ + int rc; + u8 reg, reg_alarm; + struct pm8058_rtc *rtc_dd; + struct pm8058_chip *pm_chip; + + pm_chip = platform_get_drvdata(pdev); + if (pm_chip == NULL) { + pr_err("%s: Invalid driver information\n", __func__); + return -ENXIO; + } + + rtc_dd = kzalloc(sizeof(*rtc_dd), GFP_KERNEL); + if (rtc_dd == NULL) { + pr_err("%s: Unable to allocate memory\n", __func__); + return -ENOMEM; + } + + /* Enable runtime PM ops, start in ACTIVE mode */ + rc = pm_runtime_set_active(&pdev->dev); + if (rc < 0) + dev_dbg(&pdev->dev, "unable to set runtime pm state\n"); + pm_runtime_enable(&pdev->dev); + + rtc_dd->rtc_irq = platform_get_irq(pdev, 0); + rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 1); + if (!rtc_dd->rtc_alarm_irq || !rtc_dd->rtc_irq) { + pr_err("%s: RTC Alarm IRQ absent\n", __func__); + rc = -ENXIO; + goto fail_rtc_enable; + } + + rtc_dd->pm_chip = pm_chip; + + rc = pm8058_read(pm_chip, PM8058_RTC_CTRL, ®, 1); + if (rc < 0) { + pr_err("%s: PM8058 read failed\n", __func__); + goto fail_rtc_enable; + } + + if (!(reg & PM8058_RTC_ENABLE)) { + /* Enable RTC, clear alarm register */ + reg |= PM8058_RTC_ENABLE; + reg &= ~PM8058_RTC_ALARM_ENABLE; + rc = pm8058_write(pm_chip, PM8058_RTC_CTRL, ®, 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + goto fail_rtc_enable; + } + + /* Clear RTC alarm register */ + rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_ALARM_CTRL, + ®_alarm, 1); + if (rc < 0) { + pr_err("%s: PM8058 read failed\n", __func__); + goto fail_rtc_enable; + } + + reg_alarm &= ~PM8058_RTC_ALARM_CLEAR; + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_ALARM_CTRL, + ®_alarm, 1); + if (rc < 0) { + pr_err("%s: PM8058 write failed\n", __func__); + goto fail_rtc_enable; + } + } + rtc_dd->rtc_ctrl_reg = reg; + +#ifdef CONFIG_RTC_PM8058_WRITE_ENABLE + pm8058_rtc0_ops.set_time = pm8058_rtc0_set_time, +#endif + + /* Register the RTC device */ + rtc_dd->rtc0 = rtc_device_register("pm8058_rtc0", &pdev->dev, + &pm8058_rtc0_ops, THIS_MODULE); + if (IS_ERR(rtc_dd->rtc0)) { + pr_err("%s: RTC device registration failed (%ld)\n", + __func__, PTR_ERR(rtc_dd->rtc0)); + rc = PTR_ERR(rtc_dd->rtc0); + goto fail_rtc_enable; + } + + platform_set_drvdata(pdev, rtc_dd); + + /* Request the alarm IRQ */ + rc = request_threaded_irq(rtc_dd->rtc_alarm_irq, NULL, + pm8058_alarm_trigger, IRQF_TRIGGER_RISING, + "pm8058_rtc_alarm", rtc_dd); + if (rc < 0) { + pr_err("%s: Request IRQ failed (%d)\n", __func__, rc); + goto fail_req_irq; + } + + device_init_wakeup(&pdev->dev, 1); + + pr_debug("%s: Probe success !!\n", __func__); + + return 0; + +fail_req_irq: + rtc_device_unregister(rtc_dd->rtc0); +fail_rtc_enable: + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + kfree(rtc_dd); + return rc; +} + +#ifdef CONFIG_PM +static int pm8058_rtc_resume(struct device *dev) +{ + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(rtc_dd->rtc_alarm_irq); + + return 0; +} + +static int pm8058_rtc_suspend(struct device *dev) +{ + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(rtc_dd->rtc_alarm_irq); + + return 0; +} + +static struct dev_pm_ops pm8058_rtc_pm_ops = { + .suspend = pm8058_rtc_suspend, + .resume = pm8058_rtc_resume, +}; +#endif + +static int __devexit pm8058_rtc_remove(struct platform_device *pdev) +{ + struct pm8058_rtc *rtc_dd = platform_get_drvdata(pdev); + + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + device_init_wakeup(&pdev->dev, 0); + free_irq(rtc_dd->rtc_alarm_irq, rtc_dd); + rtc_device_unregister(rtc_dd->rtc0); + kfree(rtc_dd); + + return 0; +} + +static struct platform_driver pm8058_rtc_driver = { + .probe = pm8058_rtc_probe, + .remove = __devexit_p(pm8058_rtc_remove), + .driver = { + .name = "pm8058-rtc", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &pm8058_rtc_pm_ops, +#endif + }, +}; + +static int __init pm8058_rtc_init(void) +{ + return platform_driver_register(&pm8058_rtc_driver); +} + +static void __exit pm8058_rtc_exit(void) +{ + platform_driver_unregister(&pm8058_rtc_driver); +} + +module_init(pm8058_rtc_init); +module_exit(pm8058_rtc_exit); + +MODULE_ALIAS("platform:pm8058-rtc"); +MODULE_DESCRIPTION("PMIC8058 RTC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rtc/rtc-pm8xxx.c b/drivers/rtc/rtc-pm8xxx.c new file mode 100644 index 00000000000..0bdb89eb8b1 --- /dev/null +++ b/drivers/rtc/rtc-pm8xxx.c @@ -0,0 +1,569 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + + +/* RTC Register offsets from RTC CTRL REG */ +#define PM8XXX_ALARM_CTRL_OFFSET 0x01 +#define PM8XXX_RTC_WRITE_OFFSET 0x02 +#define PM8XXX_RTC_READ_OFFSET 0x06 +#define PM8XXX_ALARM_RW_OFFSET 0x0A + +/* RTC_CTRL register bit fields */ +#define PM8xxx_RTC_ENABLE BIT(7) +#define PM8xxx_RTC_ALARM_ENABLE BIT(1) +#define PM8xxx_RTC_ALARM_CLEAR BIT(0) + +#define NUM_8_BIT_RTC_REGS 0x4 + +/** + * struct pm8xxx_rtc - rtc driver internal structure + * @rtc: rtc device for this driver + * @rtc_alarm_irq: rtc alarm irq number + */ +struct pm8xxx_rtc { + struct rtc_device *rtc; + int rtc_alarm_irq; + int rtc_base; + int rtc_read_base; + int rtc_write_base; + int alarm_rw_base; + u8 ctrl_reg; + struct device *rtc_dev; + spinlock_t ctrl_reg_lock; +}; + +/* + * The RTC registers need to be read/written one byte at a time. This is a + * hardware limitation. + */ + +static int pm8xxx_read_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val, + int base, int count) +{ + int i, rc; + struct device *parent = rtc_dd->rtc_dev->parent; + + for (i = 0; i < count; i++) { + rc = pm8xxx_readb(parent, base + i, &rtc_val[i]); + if (rc < 0) { + dev_err(rtc_dd->rtc_dev, "PM8xxx read failed\n"); + return rc; + } + } + + return 0; +} + +static int pm8xxx_write_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val, + int base, int count) +{ + int i, rc; + struct device *parent = rtc_dd->rtc_dev->parent; + + for (i = 0; i < count; i++) { + rc = pm8xxx_writeb(parent, base + i, rtc_val[i]); + if (rc < 0) { + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed\n"); + return rc; + } + } + + return 0; +} + + +/* + * Steps to write the RTC registers. + * 1. Disable alarm if enabled. + * 2. Write 0x00 to LSB. + * 3. Write Byte[1], Byte[2], Byte[3] then Byte[0]. + * 4. Enable alarm if disabled in step 1. + */ +static int +pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + int rc; + unsigned long secs, irq_flags; + u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg; + struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); + + rtc_tm_to_time(tm, &secs); + + value[0] = secs & 0xFF; + value[1] = (secs >> 8) & 0xFF; + value[2] = (secs >> 16) & 0xFF; + value[3] = (secs >> 24) & 0xFF; + + dev_dbg(dev, "Seconds value to be written to RTC = %lu\n", secs); + + spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags); + ctrl_reg = rtc_dd->ctrl_reg; + + if (ctrl_reg & PM8xxx_RTC_ALARM_ENABLE) { + alarm_enabled = 1; + ctrl_reg &= ~PM8xxx_RTC_ALARM_ENABLE; + rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, + 1); + if (rc < 0) { + dev_err(dev, "PM8xxx write failed\n"); + goto rtc_rw_fail; + } + } else + spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags); + + /* Write Byte[1], Byte[2], Byte[3], Byte[0] */ + /* Write 0 to Byte[0] */ + reg = 0; + rc = pm8xxx_write_wrapper(rtc_dd, ®, rtc_dd->rtc_write_base, 1); + if (rc < 0) { + dev_err(dev, "PM8xxx write failed\n"); + goto rtc_rw_fail; + } + + /* Write Byte[1], Byte[2], Byte[3] */ + rc = pm8xxx_write_wrapper(rtc_dd, value + 1, + rtc_dd->rtc_write_base + 1, 3); + if (rc < 0) { + dev_err(dev, "Write to RTC registers failed\n"); + goto rtc_rw_fail; + } + + /* Write Byte[0] */ + rc = pm8xxx_write_wrapper(rtc_dd, value, rtc_dd->rtc_write_base, 1); + if (rc < 0) { + dev_err(dev, "Write to RTC register failed\n"); + goto rtc_rw_fail; + } + + if (alarm_enabled) { + ctrl_reg |= PM8xxx_RTC_ALARM_ENABLE; + rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, + 1); + if (rc < 0) { + dev_err(dev, "PM8xxx write failed\n"); + goto rtc_rw_fail; + } + } + + rtc_dd->ctrl_reg = ctrl_reg; + +rtc_rw_fail: + if (alarm_enabled) + spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags); + + return rc; +} + +static int +pm8xxx_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + int rc; + u8 value[4], reg; + unsigned long secs; + struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); + + rc = pm8xxx_read_wrapper(rtc_dd, value, rtc_dd->rtc_read_base, + NUM_8_BIT_RTC_REGS); + if (rc < 0) { + dev_err(dev, "RTC time read failed\n"); + return rc; + } + + /* + * Read the LSB again and check if there has been a carry over. + * If there is, redo the read operation. + */ + rc = pm8xxx_read_wrapper(rtc_dd, ®, rtc_dd->rtc_read_base, 1); + if (rc < 0) { + dev_err(dev, "PM8xxx read failed\n"); + return rc; + } + + if (unlikely(reg < value[0])) { + rc = pm8xxx_read_wrapper(rtc_dd, value, + rtc_dd->rtc_read_base, NUM_8_BIT_RTC_REGS); + if (rc < 0) { + dev_err(dev, "RTC time read failed\n"); + return rc; + } + } + + secs = value[0] | (value[1] << 8) | (value[2] << 16) \ + | (value[3] << 24); + + rtc_time_to_tm(secs, tm); + + rc = rtc_valid_tm(tm); + if (rc < 0) { + dev_err(dev, "Invalid time read from PM8xxx\n"); + return rc; + } + + dev_dbg(dev, "secs = %lu, h:m:s == %d:%d:%d, d/m/y = %d/%d/%d\n", + secs, tm->tm_hour, tm->tm_min, tm->tm_sec, + tm->tm_mday, tm->tm_mon, tm->tm_year); + + return 0; +} + +static int +pm8xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + int rc; + u8 value[4], ctrl_reg; + unsigned long secs, secs_rtc, irq_flags; + struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); + struct rtc_time rtc_tm; + + rtc_tm_to_time(&alarm->time, &secs); + + /* + * Read the current RTC time and verify if the alarm time is in the + * past. If yes, return invalid. + */ + rc = pm8xxx_rtc_read_time(dev, &rtc_tm); + if (rc < 0) { + dev_err(dev, "Unamble to read RTC time\n"); + return -EINVAL; + } + + rtc_tm_to_time(&rtc_tm, &secs_rtc); + if (secs < secs_rtc) { + dev_err(dev, "Trying to set alarm in the past\n"); + return -EINVAL; + } + + value[0] = secs & 0xFF; + value[1] = (secs >> 8) & 0xFF; + value[2] = (secs >> 16) & 0xFF; + value[3] = (secs >> 24) & 0xFF; + + spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags); + + rc = pm8xxx_write_wrapper(rtc_dd, value, rtc_dd->alarm_rw_base, + NUM_8_BIT_RTC_REGS); + if (rc < 0) { + dev_err(dev, "Write to RTC ALARM registers failed\n"); + goto rtc_rw_fail; + } + + ctrl_reg = rtc_dd->ctrl_reg; + ctrl_reg = (alarm->enabled) ? (ctrl_reg | PM8xxx_RTC_ALARM_ENABLE) : + (ctrl_reg & ~PM8xxx_RTC_ALARM_ENABLE); + + rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1); + if (rc < 0) { + dev_err(dev, "PM8xxx write failed\n"); + goto rtc_rw_fail; + } + + rtc_dd->ctrl_reg = ctrl_reg; + + dev_dbg(dev, "Alarm Set for h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n", + alarm->time.tm_hour, alarm->time.tm_min, + alarm->time.tm_sec, alarm->time.tm_mday, + alarm->time.tm_mon, alarm->time.tm_year); +rtc_rw_fail: + spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags); + return rc; +} + +static int +pm8xxx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + int rc; + u8 value[4]; + unsigned long secs; + struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); + + rc = pm8xxx_read_wrapper(rtc_dd, value, rtc_dd->alarm_rw_base, + NUM_8_BIT_RTC_REGS); + if (rc < 0) { + dev_err(dev, "RTC alarm time read failed\n"); + return rc; + } + + secs = value[0] | (value[1] << 8) | (value[2] << 16) | \ + (value[3] << 24); + + rtc_time_to_tm(secs, &alarm->time); + + rc = rtc_valid_tm(&alarm->time); + if (rc < 0) { + dev_err(dev, "Invalid time read from PM8xxx\n"); + return rc; + } + + dev_dbg(dev, "Alarm set for - h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n", + alarm->time.tm_hour, alarm->time.tm_min, + alarm->time.tm_sec, alarm->time.tm_mday, + alarm->time.tm_mon, alarm->time.tm_year); + + return 0; +} + + +static int +pm8xxx_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + int rc; + unsigned long irq_flags; + struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); + u8 ctrl_reg; + + spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags); + ctrl_reg = rtc_dd->ctrl_reg; + ctrl_reg = (enabled) ? (ctrl_reg | PM8xxx_RTC_ALARM_ENABLE) : + (ctrl_reg & ~PM8xxx_RTC_ALARM_ENABLE); + + rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1); + if (rc < 0) { + dev_err(dev, "PM8xxx write failed\n"); + goto rtc_rw_fail; + } + + rtc_dd->ctrl_reg = ctrl_reg; + +rtc_rw_fail: + spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags); + return rc; +} + +static struct rtc_class_ops pm8xxx_rtc_ops = { + .read_time = pm8xxx_rtc_read_time, + .set_alarm = pm8xxx_rtc_set_alarm, + .read_alarm = pm8xxx_rtc_read_alarm, + .alarm_irq_enable = pm8xxx_rtc_alarm_irq_enable, +}; + +static irqreturn_t pm8xxx_alarm_trigger(int irq, void *dev_id) +{ + struct pm8xxx_rtc *rtc_dd = dev_id; + u8 ctrl_reg; + int rc; + unsigned long irq_flags; + + rtc_update_irq(rtc_dd->rtc, 1, RTC_IRQF | RTC_AF); + + spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags); + + /* Clear the alarm enable bit */ + ctrl_reg = rtc_dd->ctrl_reg; + ctrl_reg &= ~PM8xxx_RTC_ALARM_ENABLE; + + rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1); + if (rc < 0) { + spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags); + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed!\n"); + goto rtc_alarm_handled; + } + + rtc_dd->ctrl_reg = ctrl_reg; + spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags); + + /* Clear RTC alarm register */ + rc = pm8xxx_read_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base + + PM8XXX_ALARM_CTRL_OFFSET, 1); + if (rc < 0) { + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed!\n"); + goto rtc_alarm_handled; + } + + ctrl_reg &= ~PM8xxx_RTC_ALARM_CLEAR; + rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base + + PM8XXX_ALARM_CTRL_OFFSET, 1); + if (rc < 0) + dev_err(rtc_dd->rtc_dev, "PM8xxx write failed!\n"); + +rtc_alarm_handled: + return IRQ_HANDLED; +} + +static int __devinit pm8xxx_rtc_probe(struct platform_device *pdev) +{ + int rc; + u8 ctrl_reg; + bool rtc_write_enable = false; + struct pm8xxx_rtc *rtc_dd; + struct resource *rtc_resource; + const struct pm8xxx_rtc_platform_data *pdata = + pdev->dev.platform_data; + + if (pdata != NULL) + rtc_write_enable = pdata->rtc_write_enable; + + rtc_dd = kzalloc(sizeof(*rtc_dd), GFP_KERNEL); + if (rtc_dd == NULL) { + dev_err(&pdev->dev, "Unable to allocate memory!\n"); + return -ENOMEM; + } + + /* Initialise spinlock to protect RTC cntrol register */ + spin_lock_init(&rtc_dd->ctrl_reg_lock); + + rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 0); + if (rtc_dd->rtc_alarm_irq < 0) { + dev_err(&pdev->dev, "Alarm IRQ resource absent!\n"); + rc = -ENXIO; + goto fail_rtc_enable; + } + + rtc_resource = platform_get_resource_byname(pdev, IORESOURCE_IO, + "pmic_rtc_base"); + if (!(rtc_resource && rtc_resource->start)) { + dev_err(&pdev->dev, "RTC IO resource absent!\n"); + rc = -ENXIO; + goto fail_rtc_enable; + } + + rtc_dd->rtc_base = rtc_resource->start; + + /* Setup RTC register addresses */ + rtc_dd->rtc_write_base = rtc_dd->rtc_base + PM8XXX_RTC_WRITE_OFFSET; + rtc_dd->rtc_read_base = rtc_dd->rtc_base + PM8XXX_RTC_READ_OFFSET; + rtc_dd->alarm_rw_base = rtc_dd->rtc_base + PM8XXX_ALARM_RW_OFFSET; + + rtc_dd->rtc_dev = &(pdev->dev); + + /* Check if the RTC is on, else turn it on */ + rc = pm8xxx_read_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1); + if (rc < 0) { + dev_err(&pdev->dev, "PM8xxx read failed!\n"); + goto fail_rtc_enable; + } + + if (!(ctrl_reg & PM8xxx_RTC_ENABLE)) { + ctrl_reg |= PM8xxx_RTC_ENABLE; + rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, + 1); + if (rc < 0) { + dev_err(&pdev->dev, "PM8xxx write failed!\n"); + goto fail_rtc_enable; + } + } + + rtc_dd->ctrl_reg = ctrl_reg; + if (rtc_write_enable == true) + pm8xxx_rtc_ops.set_time = pm8xxx_rtc_set_time; + + platform_set_drvdata(pdev, rtc_dd); + + /* Register the RTC device */ + rtc_dd->rtc = rtc_device_register("pm8xxx_rtc", &pdev->dev, + &pm8xxx_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc_dd->rtc)) { + dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n", + __func__, PTR_ERR(rtc_dd->rtc)); + rc = PTR_ERR(rtc_dd->rtc); + goto fail_rtc_enable; + } + + /* Request the alarm IRQ */ + rc = request_any_context_irq(rtc_dd->rtc_alarm_irq, + pm8xxx_alarm_trigger, IRQF_TRIGGER_RISING, + "pm8xxx_rtc_alarm", rtc_dd); + if (rc < 0) { + dev_err(&pdev->dev, "Request IRQ failed (%d)\n", rc); + goto fail_req_irq; + } + + device_init_wakeup(&pdev->dev, 1); + + dev_dbg(&pdev->dev, "Probe success !!\n"); + + return 0; + +fail_req_irq: + rtc_device_unregister(rtc_dd->rtc); +fail_rtc_enable: + platform_set_drvdata(pdev, NULL); + kfree(rtc_dd); + return rc; +} + +#ifdef CONFIG_PM +static int pm8xxx_rtc_resume(struct device *dev) +{ + struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(rtc_dd->rtc_alarm_irq); + + return 0; +} + +static int pm8xxx_rtc_suspend(struct device *dev) +{ + struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(rtc_dd->rtc_alarm_irq); + + return 0; +} + +static const struct dev_pm_ops pm8xxx_rtc_pm_ops = { + .suspend = pm8xxx_rtc_suspend, + .resume = pm8xxx_rtc_resume, +}; +#endif +static int __devexit pm8xxx_rtc_remove(struct platform_device *pdev) +{ + struct pm8xxx_rtc *rtc_dd = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + free_irq(rtc_dd->rtc_alarm_irq, rtc_dd); + rtc_device_unregister(rtc_dd->rtc); + platform_set_drvdata(pdev, NULL); + kfree(rtc_dd); + + return 0; +} + +static struct platform_driver pm8xxx_rtc_driver = { + .probe = pm8xxx_rtc_probe, + .remove = __devexit_p(pm8xxx_rtc_remove), + .driver = { + .name = PM8XXX_RTC_DEV_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &pm8xxx_rtc_pm_ops, +#endif + }, +}; + +static int __init pm8xxx_rtc_init(void) +{ + return platform_driver_register(&pm8xxx_rtc_driver); +} +module_init(pm8xxx_rtc_init); + +static void __exit pm8xxx_rtc_exit(void) +{ + platform_driver_unregister(&pm8xxx_rtc_driver); +} +module_exit(pm8xxx_rtc_exit); + +MODULE_ALIAS("platform:rtc-pm8xxx"); +MODULE_DESCRIPTION("PMIC8xxx RTC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Anirudh Ghayal "); diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig new file mode 100644 index 00000000000..a6a068d3f49 --- /dev/null +++ b/drivers/slimbus/Kconfig @@ -0,0 +1,19 @@ +# +# SLIMBUS driver configuration +# +menuconfig SLIMBUS + bool "Slimbus support" + depends on HAS_IOMEM + help + Slimbus is standard interface between baseband and + application processors and peripheral components in mobile + terminals. + +if SLIMBUS +config SLIMBUS_MSM_CTRL + tristate "Qualcomm Slimbus Master Component" + default n + help + Select driver for Qualcomm's Slimbus Master Component. + +endif diff --git a/drivers/slimbus/Makefile b/drivers/slimbus/Makefile new file mode 100644 index 00000000000..436822d1f1e --- /dev/null +++ b/drivers/slimbus/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for kernel slimbus framework. +# +obj-$(CONFIG_SLIMBUS) += slimbus.o +obj-$(CONFIG_SLIMBUS_MSM_CTRL) += slim-msm-ctrl.o diff --git a/drivers/slimbus/slim-msm-ctrl.c b/drivers/slimbus/slim-msm-ctrl.c new file mode 100644 index 00000000000..decc77f137c --- /dev/null +++ b/drivers/slimbus/slim-msm-ctrl.c @@ -0,0 +1,1769 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Per spec.max 40 bytes per received message */ +#define SLIM_RX_MSGQ_BUF_LEN 40 + +#define SLIM_USR_MC_GENERIC_ACK 0x25 +#define SLIM_USR_MC_MASTER_CAPABILITY 0x0 +#define SLIM_USR_MC_REPORT_SATELLITE 0x1 +#define SLIM_USR_MC_ADDR_QUERY 0xD +#define SLIM_USR_MC_ADDR_REPLY 0xE +#define SLIM_USR_MC_DEFINE_CHAN 0x20 +#define SLIM_USR_MC_DEF_ACT_CHAN 0x21 +#define SLIM_USR_MC_CHAN_CTRL 0x23 +#define SLIM_USR_MC_RECONFIG_NOW 0x24 +#define SLIM_USR_MC_REQ_BW 0x28 +#define SLIM_USR_MC_CONNECT_SRC 0x2C +#define SLIM_USR_MC_CONNECT_SINK 0x2D +#define SLIM_USR_MC_DISCONNECT_PORT 0x2E + +/* MSM Slimbus peripheral settings */ +#define MSM_SLIM_PERF_SUMM_THRESHOLD 0x8000 +#define MSM_SLIM_NCHANS 32 +#define MSM_SLIM_NPORTS 24 + +/* + * Need enough descriptors to receive present messages from slaves + * if received simultaneously. Present message needs 3 descriptors + * and this size will ensure around 10 simultaneous reports. + */ +#define MSM_SLIM_DESC_NUM 32 + +#define SLIM_MSG_ASM_FIRST_WORD(l, mt, mc, dt, ad) \ + ((l) | ((mt) << 5) | ((mc) << 8) | ((dt) << 15) | ((ad) << 16)) + +#define MSM_SLIM_NAME "msm_slim_ctrl" +#define SLIM_ROOT_FREQ 24576000 + +#define MSM_CONCUR_MSG 8 +#define SAT_CONCUR_MSG 8 +#define DEF_WATERMARK (8 << 1) +#define DEF_ALIGN 0 +#define DEF_PACK (1 << 6) +#define ENABLE_PORT 1 + +#define DEF_BLKSZ 0 +#define DEF_TRANSZ 0 + +#define SAT_MAGIC_LSB 0xD9 +#define SAT_MAGIC_MSB 0xC5 +#define SAT_MSG_VER 0x1 +#define SAT_MSG_PROT 0x1 +#define MSM_SAT_SUCCSS 0x20 + +#define QC_MFGID_LSB 0x2 +#define QC_MFGID_MSB 0x17 +#define QC_CHIPID_SL 0x10 +#define QC_DEVID_SAT1 0x3 +#define QC_DEVID_SAT2 0x4 +#define QC_DEVID_PGD 0x5 + +/* Component registers */ +enum comp_reg { + COMP_CFG = 0, + COMP_TRUST_CFG = 0x14, +}; + +/* Manager registers */ +enum mgr_reg { + MGR_CFG = 0x200, + MGR_STATUS = 0x204, + MGR_RX_MSGQ_CFG = 0x208, + MGR_INT_EN = 0x210, + MGR_INT_STAT = 0x214, + MGR_INT_CLR = 0x218, + MGR_TX_MSG = 0x230, + MGR_RX_MSG = 0x270, + MGR_VE_STAT = 0x300, +}; + +enum msg_cfg { + MGR_CFG_ENABLE = 1, + MGR_CFG_RX_MSGQ_EN = 1 << 1, + MGR_CFG_TX_MSGQ_EN_HIGH = 1 << 2, + MGR_CFG_TX_MSGQ_EN_LOW = 1 << 3, +}; +/* Message queue types */ +enum msm_slim_msgq_type { + MSGQ_RX = 0, + MSGQ_TX_LOW = 1, + MSGQ_TX_HIGH = 2, +}; +/* Framer registers */ +enum frm_reg { + FRM_CFG = 0x400, + FRM_STAT = 0x404, + FRM_INT_EN = 0x410, + FRM_INT_STAT = 0x414, + FRM_INT_CLR = 0x418, + FRM_WAKEUP = 0x41C, + FRM_CLKCTL_DONE = 0x420, + FRM_IE_STAT = 0x430, + FRM_VE_STAT = 0x440, +}; + +/* Interface registers */ +enum intf_reg { + INTF_CFG = 0x600, + INTF_STAT = 0x604, + INTF_INT_EN = 0x610, + INTF_INT_STAT = 0x614, + INTF_INT_CLR = 0x618, + INTF_IE_STAT = 0x630, + INTF_VE_STAT = 0x640, +}; + +/* Manager PGD registers */ +enum pgd_reg { + PGD_CFG = 0x1000, + PGD_STAT = 0x1004, + PGD_INT_EN = 0x1010, + PGD_INT_STAT = 0x1014, + PGD_INT_CLR = 0x1018, + PGD_OWN_EEn = 0x1020, + PGD_PORT_INT_EN_EEn = 0x1030, + PGD_PORT_INT_ST_EEn = 0x1034, + PGD_PORT_INT_CL_EEn = 0x1038, + PGD_PORT_CFGn = 0x1080, + PGD_PORT_STATn = 0x1084, + PGD_PORT_PARAMn = 0x1088, + PGD_PORT_BLKn = 0x108C, + PGD_PORT_TRANn = 0x1090, + PGD_PORT_MCHANn = 0x1094, + PGD_PORT_PSHPLLn = 0x1098, + PGD_PORT_PC_CFGn = 0x1600, + PGD_PORT_PC_VALn = 0x1604, + PGD_PORT_PC_VFR_TSn = 0x1608, + PGD_PORT_PC_VFR_STn = 0x160C, + PGD_PORT_PC_VFR_CLn = 0x1610, + PGD_IE_STAT = 0x1700, + PGD_VE_STAT = 0x1710, +}; + +enum rsc_grp { + EE_MGR_RSC_GRP = 1 << 10, + EE_NGD_2 = 2 << 6, + EE_NGD_1 = 0, +}; + +enum mgr_intr { + MGR_INT_RECFG_DONE = 1 << 24, + MGR_INT_TX_NACKED_2 = 1 << 25, + MGR_INT_MSG_BUF_CONTE = 1 << 26, + MGR_INT_RX_MSG_RCVD = 1 << 30, + MGR_INT_TX_MSG_SENT = 1 << 31, +}; + +enum frm_cfg { + FRM_ACTIVE = 1, + CLK_GEAR = 7, + ROOT_FREQ = 11, + REF_CLK_GEAR = 15, +}; + +struct msm_slim_sps_bam { + u32 hdl; + void __iomem *base; + int irq; +}; + +struct msm_slim_endp { + struct sps_pipe *sps; + struct sps_connect config; + struct sps_register_event event; + struct sps_mem_buffer buf; + struct completion *xcomp; + bool connected; +}; + +struct msm_slim_ctrl { + struct slim_controller ctrl; + struct slim_framer framer; + struct device *dev; + void __iomem *base; + u32 curr_bw; + u8 msg_cnt; + u32 tx_buf[10]; + u8 rx_msgs[MSM_CONCUR_MSG][SLIM_RX_MSGQ_BUF_LEN]; + spinlock_t rx_lock; + int head; + int tail; + int irq; + int err; + int ee; + struct completion *wr_comp; + struct msm_slim_sat *satd; + struct msm_slim_endp pipes[7]; + struct msm_slim_sps_bam bam; + struct msm_slim_endp rx_msgq; + struct completion rx_msgq_notify; + struct task_struct *rx_msgq_thread; + struct clk *rclk; + struct mutex tx_lock; + u8 pgdla; + bool use_rx_msgqs; + int suspended; + int pipe_b; + struct completion reconf; + bool reconf_busy; +}; + +struct msm_slim_sat { + struct slim_device satcl; + struct msm_slim_ctrl *dev; + struct workqueue_struct *wq; + struct work_struct wd; + u8 sat_msgs[SAT_CONCUR_MSG][40]; + u16 *satch; + u8 nsatch; + bool sent_capability; + int shead; + int stail; + spinlock_t lock; +}; + +static int msm_slim_rx_enqueue(struct msm_slim_ctrl *dev, u32 *buf, u8 len) +{ + spin_lock(&dev->rx_lock); + if ((dev->tail + 1) % MSM_CONCUR_MSG == dev->head) { + spin_unlock(&dev->rx_lock); + dev_err(dev->dev, "RX QUEUE full!"); + return -EXFULL; + } + memcpy((u8 *)dev->rx_msgs[dev->tail], (u8 *)buf, len); + dev->tail = (dev->tail + 1) % MSM_CONCUR_MSG; + spin_unlock(&dev->rx_lock); + return 0; +} + +static int msm_slim_rx_dequeue(struct msm_slim_ctrl *dev, u8 *buf) +{ + unsigned long flags; + spin_lock_irqsave(&dev->rx_lock, flags); + if (dev->tail == dev->head) { + spin_unlock_irqrestore(&dev->rx_lock, flags); + return -ENODATA; + } + memcpy(buf, (u8 *)dev->rx_msgs[dev->head], 40); + dev->head = (dev->head + 1) % MSM_CONCUR_MSG; + spin_unlock_irqrestore(&dev->rx_lock, flags); + return 0; +} + +static int msm_sat_enqueue(struct msm_slim_sat *sat, u32 *buf, u8 len) +{ + struct msm_slim_ctrl *dev = sat->dev; + spin_lock(&sat->lock); + if ((sat->stail + 1) % SAT_CONCUR_MSG == sat->shead) { + spin_unlock(&sat->lock); + dev_err(dev->dev, "SAT QUEUE full!"); + return -EXFULL; + } + memcpy(sat->sat_msgs[sat->stail], (u8 *)buf, len); + sat->stail = (sat->stail + 1) % SAT_CONCUR_MSG; + spin_unlock(&sat->lock); + return 0; +} + +static int msm_sat_dequeue(struct msm_slim_sat *sat, u8 *buf) +{ + unsigned long flags; + spin_lock_irqsave(&sat->lock, flags); + if (sat->stail == sat->shead) { + spin_unlock_irqrestore(&sat->lock, flags); + return -ENODATA; + } + memcpy(buf, sat->sat_msgs[sat->shead], 40); + sat->shead = (sat->shead + 1) % SAT_CONCUR_MSG; + spin_unlock_irqrestore(&sat->lock, flags); + return 0; +} + +static void msm_get_eaddr(u8 *e_addr, u32 *buffer) +{ + e_addr[0] = (buffer[1] >> 24) & 0xff; + e_addr[1] = (buffer[1] >> 16) & 0xff; + e_addr[2] = (buffer[1] >> 8) & 0xff; + e_addr[3] = buffer[1] & 0xff; + e_addr[4] = (buffer[0] >> 24) & 0xff; + e_addr[5] = (buffer[0] >> 16) & 0xff; +} + +static bool msm_is_sat_dev(u8 *e_addr) +{ + if (e_addr[5] == QC_MFGID_LSB && e_addr[4] == QC_MFGID_MSB && + e_addr[2] != QC_CHIPID_SL && + (e_addr[1] == QC_DEVID_SAT1 || e_addr[1] == QC_DEVID_SAT2)) + return true; + return false; +} + +static irqreturn_t msm_slim_interrupt(int irq, void *d) +{ + struct msm_slim_ctrl *dev = d; + u32 pstat; + u32 stat = readl_relaxed(dev->base + MGR_INT_STAT); + + if (stat & MGR_INT_TX_MSG_SENT || stat & MGR_INT_TX_NACKED_2) { + if (stat & MGR_INT_TX_MSG_SENT) + writel_relaxed(MGR_INT_TX_MSG_SENT, + dev->base + MGR_INT_CLR); + else { + writel_relaxed(MGR_INT_TX_NACKED_2, + dev->base + MGR_INT_CLR); + dev->err = -EIO; + } + /* + * Guarantee that interrupt clear bit write goes through before + * signalling completion/exiting ISR + */ + mb(); + if (dev->wr_comp) + complete(dev->wr_comp); + } + if (stat & MGR_INT_RX_MSG_RCVD) { + u32 rx_buf[10]; + u32 mc, mt; + u8 len, i; + rx_buf[0] = readl_relaxed(dev->base + MGR_RX_MSG); + len = rx_buf[0] & 0x1F; + for (i = 1; i < ((len + 3) >> 2); i++) { + rx_buf[i] = readl_relaxed(dev->base + MGR_RX_MSG + + (4 * i)); + dev_dbg(dev->dev, "reading data: %x\n", rx_buf[i]); + } + mt = (rx_buf[0] >> 5) & 0x7; + mc = (rx_buf[0] >> 8) & 0xff; + dev_dbg(dev->dev, "MC: %x, MT: %x\n", mc, mt); + if (mt == SLIM_MSG_MT_DEST_REFERRED_USER || + mt == SLIM_MSG_MT_SRC_REFERRED_USER) { + struct msm_slim_sat *sat = dev->satd; + msm_sat_enqueue(sat, rx_buf, len); + writel_relaxed(MGR_INT_RX_MSG_RCVD, + dev->base + MGR_INT_CLR); + /* + * Guarantee that CLR bit write goes through before + * queuing work + */ + mb(); + queue_work(sat->wq, &sat->wd); + } else if (mt == SLIM_MSG_MT_CORE && + mc == SLIM_MSG_MC_REPORT_PRESENT) { + u8 e_addr[6]; + msm_get_eaddr(e_addr, rx_buf); + if (msm_is_sat_dev(e_addr)) { + /* + * Consider possibility that this device may + * be reporting more than once? + */ + struct msm_slim_sat *sat = dev->satd; + msm_sat_enqueue(sat, rx_buf, len); + writel_relaxed(MGR_INT_RX_MSG_RCVD, dev->base + + MGR_INT_CLR); + /* + * Guarantee that CLR bit write goes through + * before queuing work + */ + mb(); + queue_work(sat->wq, &sat->wd); + } else { + msm_slim_rx_enqueue(dev, rx_buf, len); + writel_relaxed(MGR_INT_RX_MSG_RCVD, dev->base + + MGR_INT_CLR); + /* + * Guarantee that CLR bit write goes through + * before signalling completion + */ + mb(); + complete(&dev->rx_msgq_notify); + } + } else if (mc == SLIM_MSG_MC_REPLY_INFORMATION || + mc == SLIM_MSG_MC_REPLY_VALUE) { + msm_slim_rx_enqueue(dev, rx_buf, len); + writel_relaxed(MGR_INT_RX_MSG_RCVD, dev->base + + MGR_INT_CLR); + /* + * Guarantee that CLR bit write goes through + * before signalling completion + */ + mb(); + complete(&dev->rx_msgq_notify); + } else { + dev_err(dev->dev, "Unexpected MC,%x MT:%x, len:%d", + mc, mt, len); + for (i = 0; i < ((len + 3) >> 2); i++) + dev_err(dev->dev, "error msg: %x", rx_buf[i]); + writel_relaxed(MGR_INT_RX_MSG_RCVD, dev->base + + MGR_INT_CLR); + /* + * Guarantee that CLR bit write goes through + * before exiting + */ + mb(); + } + } + if (stat & MGR_INT_RECFG_DONE) { + writel_relaxed(MGR_INT_RECFG_DONE, dev->base + MGR_INT_CLR); + /* + * Guarantee that CLR bit write goes through + * before exiting ISR + */ + mb(); + complete(&dev->reconf); + } + pstat = readl_relaxed(dev->base + PGD_PORT_INT_ST_EEn + (16 * dev->ee)); + if (pstat != 0) { + int i = 0; + for (i = dev->pipe_b; i < MSM_SLIM_NPORTS; i++) { + if (pstat & 1 << i) { + u32 val = readl_relaxed(dev->base + + PGD_PORT_STATn + (i * 32)); + if (val & (1 << 19)) { + dev->ctrl.ports[i].err = + SLIM_P_DISCONNECT; + dev->pipes[i-dev->pipe_b].connected = + false; + /* + * SPS will call completion since + * ERROR flags are registered + */ + } else if (val & (1 << 2)) + dev->ctrl.ports[i].err = + SLIM_P_OVERFLOW; + else if (val & (1 << 3)) + dev->ctrl.ports[i].err = + SLIM_P_UNDERFLOW; + } + writel_relaxed(1, dev->base + PGD_PORT_INT_CL_EEn + + (dev->ee * 16)); + } + /* + * Guarantee that port interrupt bit(s) clearing writes go + * through before exiting ISR + */ + mb(); + } + + return IRQ_HANDLED; +} + +static int +msm_slim_init_endpoint(struct msm_slim_ctrl *dev, struct msm_slim_endp *ep) +{ + int ret; + struct sps_pipe *endpoint; + struct sps_connect *config = &ep->config; + + /* Allocate the endpoint */ + endpoint = sps_alloc_endpoint(); + if (!endpoint) { + dev_err(dev->dev, "sps_alloc_endpoint failed\n"); + return -ENOMEM; + } + + /* Get default connection configuration for an endpoint */ + ret = sps_get_config(endpoint, config); + if (ret) { + dev_err(dev->dev, "sps_get_config failed 0x%x\n", ret); + goto sps_config_failed; + } + + ep->sps = endpoint; + return 0; + +sps_config_failed: + sps_free_endpoint(endpoint); + return ret; +} + +static void +msm_slim_free_endpoint(struct msm_slim_endp *ep) +{ + sps_free_endpoint(ep->sps); + ep->sps = NULL; +} + +static int msm_slim_sps_mem_alloc( + struct msm_slim_ctrl *dev, struct sps_mem_buffer *mem, u32 len) +{ + dma_addr_t phys; + + mem->size = len; + mem->min_size = 0; + mem->base = dma_alloc_coherent(dev->dev, mem->size, &phys, GFP_KERNEL); + + if (!mem->base) { + dev_err(dev->dev, "dma_alloc_coherent(%d) failed\n", len); + return -ENOMEM; + } + + mem->phys_base = phys; + memset(mem->base, 0x00, mem->size); + return 0; +} + +static void +msm_slim_sps_mem_free(struct msm_slim_ctrl *dev, struct sps_mem_buffer *mem) +{ + dma_free_coherent(dev->dev, mem->size, mem->base, mem->phys_base); + mem->size = 0; + mem->base = NULL; + mem->phys_base = 0; +} + +static void msm_hw_set_port(struct msm_slim_ctrl *dev, u8 pn) +{ + u32 set_cfg = DEF_WATERMARK | DEF_ALIGN | DEF_PACK | ENABLE_PORT; + u32 int_port = readl_relaxed(dev->base + PGD_PORT_INT_EN_EEn + + (dev->ee * 16)); + writel_relaxed(set_cfg, dev->base + PGD_PORT_CFGn + (pn * 32)); + writel_relaxed(DEF_BLKSZ, dev->base + PGD_PORT_BLKn + (pn * 32)); + writel_relaxed(DEF_TRANSZ, dev->base + PGD_PORT_TRANn + (pn * 32)); + writel_relaxed((int_port | 1 << pn) , dev->base + PGD_PORT_INT_EN_EEn + + (dev->ee * 16)); + /* Make sure that port registers are updated before returning */ + mb(); +} + +static int msm_slim_connect_pipe_port(struct msm_slim_ctrl *dev, u8 pn) +{ + struct msm_slim_endp *endpoint = &dev->pipes[pn]; + struct sps_connect *cfg = &endpoint->config; + u32 stat; + int ret = sps_get_config(dev->pipes[pn].sps, cfg); + if (ret) { + dev_err(dev->dev, "sps pipe-port get config error%x\n", ret); + return ret; + } + cfg->options = SPS_O_DESC_DONE | SPS_O_ERROR | + SPS_O_ACK_TRANSFERS | SPS_O_AUTO_ENABLE; + + if (dev->pipes[pn].connected) { + ret = sps_set_config(dev->pipes[pn].sps, cfg); + if (ret) { + dev_err(dev->dev, "sps pipe-port set config erro:%x\n", + ret); + return ret; + } + } + + stat = readl_relaxed(dev->base + PGD_PORT_STATn + + (32 * (pn + dev->pipe_b))); + if (dev->ctrl.ports[pn].flow == SLIM_SRC) { + cfg->destination = dev->bam.hdl; + cfg->source = SPS_DEV_HANDLE_MEM; + cfg->dest_pipe_index = ((stat & (0xFF << 4)) >> 4); + cfg->src_pipe_index = 0; + dev_dbg(dev->dev, "flow src:pipe num:%d", + cfg->dest_pipe_index); + cfg->mode = SPS_MODE_DEST; + } else { + cfg->source = dev->bam.hdl; + cfg->destination = SPS_DEV_HANDLE_MEM; + cfg->src_pipe_index = ((stat & (0xFF << 4)) >> 4); + cfg->dest_pipe_index = 0; + dev_dbg(dev->dev, "flow dest:pipe num:%d", + cfg->src_pipe_index); + cfg->mode = SPS_MODE_SRC; + } + /* Space for desciptor FIFOs */ + cfg->desc.size = MSM_SLIM_DESC_NUM * sizeof(struct sps_iovec); + cfg->config = SPS_CONFIG_DEFAULT; + ret = sps_connect(dev->pipes[pn].sps, cfg); + if (!ret) { + dev->pipes[pn].connected = true; + msm_hw_set_port(dev, pn + dev->pipe_b); + } + return ret; +} + +static u32 *msm_get_msg_buf(struct slim_controller *ctrl, int len) +{ + struct msm_slim_ctrl *dev = slim_get_ctrldata(ctrl); + /* + * Currently we block a transaction until the current one completes. + * In case we need multiple transactions, use message Q + */ + return dev->tx_buf; +} + +static int msm_send_msg_buf(struct slim_controller *ctrl, u32 *buf, u8 len) +{ + int i; + struct msm_slim_ctrl *dev = slim_get_ctrldata(ctrl); + for (i = 0; i < (len + 3) >> 2; i++) { + dev_dbg(dev->dev, "TX data:0x%x\n", buf[i]); + writel_relaxed(buf[i], dev->base + MGR_TX_MSG + (i * 4)); + } + /* Guarantee that message is sent before returning */ + mb(); + return 0; +} + +static int msm_xfer_msg(struct slim_controller *ctrl, struct slim_msg_txn *txn) +{ + DECLARE_COMPLETION_ONSTACK(done); + struct msm_slim_ctrl *dev = slim_get_ctrldata(ctrl); + u32 *pbuf; + u8 *puc; + int timeout; + u8 la = txn->la; + mutex_lock(&dev->tx_lock); + if (txn->mt == SLIM_MSG_MT_CORE && + txn->mc == SLIM_MSG_MC_BEGIN_RECONFIGURATION && + dev->reconf_busy) { + wait_for_completion(&dev->reconf); + dev->reconf_busy = false; + } + if (dev->suspended) { + dev_err(dev->dev, "No transaction in suspended state"); + mutex_unlock(&dev->tx_lock); + return -EBUSY; + } + txn->rl--; + pbuf = msm_get_msg_buf(ctrl, txn->rl); + dev->wr_comp = NULL; + dev->err = 0; + + if (txn->dt == SLIM_MSG_DEST_ENUMADDR) { + mutex_unlock(&dev->tx_lock); + return -EPROTONOSUPPORT; + } + if (txn->mt == SLIM_MSG_MT_CORE && txn->la == 0xFF && + (txn->mc == SLIM_MSG_MC_CONNECT_SOURCE || + txn->mc == SLIM_MSG_MC_CONNECT_SINK || + txn->mc == SLIM_MSG_MC_DISCONNECT_PORT)) + la = dev->pgdla; + if (txn->dt == SLIM_MSG_DEST_LOGICALADDR) + *pbuf = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt, txn->mc, + 0, la); + else + *pbuf = SLIM_MSG_ASM_FIRST_WORD(txn->rl, txn->mt, txn->mc, + 1, la); + if (txn->dt == SLIM_MSG_DEST_LOGICALADDR) + puc = ((u8 *)pbuf) + 3; + else + puc = ((u8 *)pbuf) + 2; + if (txn->rbuf) + *(puc++) = txn->tid; + if ((txn->mt == SLIM_MSG_MT_CORE) && + ((txn->mc >= SLIM_MSG_MC_REQUEST_INFORMATION && + txn->mc <= SLIM_MSG_MC_REPORT_INFORMATION) || + (txn->mc >= SLIM_MSG_MC_REQUEST_VALUE && + txn->mc <= SLIM_MSG_MC_CHANGE_VALUE))) { + *(puc++) = (txn->ec & 0xFF); + *(puc++) = (txn->ec >> 8)&0xFF; + } + if (txn->wbuf) + memcpy(puc, txn->wbuf, txn->len); + if (txn->mt == SLIM_MSG_MT_CORE && txn->la == 0xFF && + (txn->mc == SLIM_MSG_MC_CONNECT_SOURCE || + txn->mc == SLIM_MSG_MC_CONNECT_SINK || + txn->mc == SLIM_MSG_MC_DISCONNECT_PORT)) { + if (txn->mc != SLIM_MSG_MC_DISCONNECT_PORT) + dev->err = msm_slim_connect_pipe_port(dev, *puc); + else { + struct msm_slim_endp *endpoint = &dev->pipes[*puc]; + struct sps_register_event sps_event; + memset(&sps_event, 0, sizeof(sps_event)); + sps_register_event(endpoint->sps, &sps_event); + sps_disconnect(endpoint->sps); + /* + * Remove channel disconnects master-side ports from + * channel. No need to send that again on the bus + */ + dev->pipes[*puc].connected = false; + mutex_unlock(&dev->tx_lock); + return 0; + } + if (dev->err) { + dev_err(dev->dev, "pipe-port connect err:%d", dev->err); + mutex_unlock(&dev->tx_lock); + return dev->err; + } + *(puc) = *(puc) + dev->pipe_b; + } + if (txn->mt == SLIM_MSG_MT_CORE && + txn->mc == SLIM_MSG_MC_BEGIN_RECONFIGURATION) + dev->reconf_busy = true; + dev->wr_comp = &done; + msm_send_msg_buf(ctrl, pbuf, txn->rl); + timeout = wait_for_completion_timeout(&done, HZ); + if (!timeout) + dev_err(dev->dev, "TX timed out:MC:0x%x,mt:0x%x", txn->mc, + txn->mt); + mutex_unlock(&dev->tx_lock); + return timeout ? dev->err : -ETIMEDOUT; +} + +static int msm_set_laddr(struct slim_controller *ctrl, const u8 *ea, + u8 elen, u8 laddr) +{ + struct msm_slim_ctrl *dev = slim_get_ctrldata(ctrl); + DECLARE_COMPLETION_ONSTACK(done); + int timeout; + u32 *buf; + mutex_lock(&dev->tx_lock); + buf = msm_get_msg_buf(ctrl, 9); + buf[0] = SLIM_MSG_ASM_FIRST_WORD(9, SLIM_MSG_MT_CORE, + SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS, + SLIM_MSG_DEST_LOGICALADDR, + ea[5] | ea[4] << 8); + buf[1] = ea[3] | (ea[2] << 8) | (ea[1] << 16) | (ea[0] << 24); + buf[2] = laddr; + + dev->wr_comp = &done; + msm_send_msg_buf(ctrl, buf, 9); + timeout = wait_for_completion_timeout(&done, HZ); + mutex_unlock(&dev->tx_lock); + return timeout ? dev->err : -ETIMEDOUT; +} + +static int msm_config_port(struct slim_controller *ctrl, u8 pn) +{ + struct msm_slim_ctrl *dev = slim_get_ctrldata(ctrl); + struct msm_slim_endp *endpoint; + int ret = 0; + if (ctrl->ports[pn].req == SLIM_REQ_HALF_DUP || + ctrl->ports[pn].req == SLIM_REQ_MULTI_CH) + return -EPROTONOSUPPORT; + if (pn >= (MSM_SLIM_NPORTS - dev->pipe_b)) + return -ENODEV; + + endpoint = &dev->pipes[pn]; + ret = msm_slim_init_endpoint(dev, endpoint); + dev_dbg(dev->dev, "sps register bam error code:%x\n", ret); + return ret; +} + +static enum slim_port_err msm_slim_port_xfer_status(struct slim_controller *ctr, + u8 pn, u8 **done_buf, u32 *done_len) +{ + struct msm_slim_ctrl *dev = slim_get_ctrldata(ctr); + struct sps_iovec sio; + int ret; + if (done_len) + *done_len = 0; + if (done_buf) + *done_buf = NULL; + if (!dev->pipes[pn].connected) + return SLIM_P_DISCONNECT; + ret = sps_get_iovec(dev->pipes[pn].sps, &sio); + if (!ret) { + if (done_len) + *done_len = sio.size; + if (done_buf) + *done_buf = (u8 *)sio.addr; + } + dev_dbg(dev->dev, "get iovec returned %d\n", ret); + return SLIM_P_INPROGRESS; +} + +static int msm_slim_port_xfer(struct slim_controller *ctrl, u8 pn, u8 *iobuf, + u32 len, struct completion *comp) +{ + struct sps_register_event sreg; + int ret; + struct msm_slim_ctrl *dev = slim_get_ctrldata(ctrl); + if (pn > 7) + return -ENODEV; + + + ctrl->ports[pn].xcomp = comp; + sreg.options = (SPS_EVENT_DESC_DONE|SPS_EVENT_ERROR); + sreg.mode = SPS_TRIGGER_WAIT; + sreg.xfer_done = comp; + sreg.callback = NULL; + sreg.user = &ctrl->ports[pn]; + ret = sps_register_event(dev->pipes[pn].sps, &sreg); + if (ret) { + dev_dbg(dev->dev, "sps register event error:%x\n", ret); + return ret; + } + ret = sps_transfer_one(dev->pipes[pn].sps, (u32)iobuf, len, NULL, + SPS_IOVEC_FLAG_INT); + dev_dbg(dev->dev, "sps submit xfer error code:%x\n", ret); + + return ret; +} + +static int msm_sat_define_ch(struct msm_slim_sat *sat, u8 *buf, u8 len, u8 mc) +{ + struct msm_slim_ctrl *dev = sat->dev; + enum slim_ch_control oper; + int i; + int ret = 0; + if (mc == SLIM_USR_MC_CHAN_CTRL) { + u16 chanh = sat->satch[buf[5]]; + oper = ((buf[3] & 0xC0) >> 6); + /* part of grp. activating/removing 1 will take care of rest */ + ret = slim_control_ch(&sat->satcl, chanh, oper, false); + } else { + u16 chh[40]; + struct slim_ch prop; + u32 exp; + u8 coeff, cc; + u8 prrate = buf[6]; + for (i = 8; i < len; i++) + chh[i-8] = sat->satch[buf[i]]; + prop.dataf = (enum slim_ch_dataf)((buf[3] & 0xE0) >> 5); + prop.auxf = (enum slim_ch_auxf)((buf[4] & 0xC0) >> 5); + prop.baser = SLIM_RATE_4000HZ; + if (prrate & 0x8) + prop.baser = SLIM_RATE_11025HZ; + else + prop.baser = SLIM_RATE_4000HZ; + prop.prot = (enum slim_ch_proto)(buf[5] & 0x0F); + prop.sampleszbits = (buf[4] & 0x1F)*SLIM_CL_PER_SL; + exp = (u32)((buf[5] & 0xF0) >> 4); + coeff = (buf[4] & 0x20) >> 5; + cc = (coeff ? 3 : 1); + prop.ratem = cc * (1 << exp); + if (i > 9) + ret = slim_define_ch(&sat->satcl, &prop, chh, len - 8, + true, &sat->satch[buf[8]]); + else + ret = slim_define_ch(&sat->satcl, &prop, + &sat->satch[buf[8]], 1, false, + NULL); + dev_dbg(dev->dev, "define sat grp returned:%d", ret); + + /* part of group so activating 1 will take care of rest */ + if (mc == SLIM_USR_MC_DEF_ACT_CHAN) + ret = slim_control_ch(&sat->satcl, + sat->satch[buf[8]], + SLIM_CH_ACTIVATE, false); + } + return ret; +} + +static void msm_slim_rxwq(struct msm_slim_ctrl *dev) +{ + u8 buf[40]; + u8 mc, mt, len; + int i, ret; + if ((msm_slim_rx_dequeue(dev, (u8 *)buf)) != -ENODATA) { + len = buf[0] & 0x1F; + mt = (buf[0] >> 5) & 0x7; + mc = buf[1]; + if (mt == SLIM_MSG_MT_CORE && + mc == SLIM_MSG_MC_REPORT_PRESENT) { + u8 laddr; + u8 e_addr[6]; + for (i = 0; i < 6; i++) + e_addr[i] = buf[7-i]; + + ret = slim_assign_laddr(&dev->ctrl, e_addr, 6, &laddr); + /* Is this Qualcomm ported generic device? */ + if (!ret && e_addr[5] == QC_MFGID_LSB && + e_addr[4] == QC_MFGID_MSB && + e_addr[1] == QC_DEVID_PGD && + e_addr[2] != QC_CHIPID_SL) + dev->pgdla = laddr; + + } else if (mc == SLIM_MSG_MC_REPLY_INFORMATION || + mc == SLIM_MSG_MC_REPLY_VALUE) { + u8 tid = buf[3]; + dev_dbg(dev->dev, "tid:%d, len:%d\n", tid, len - 4); + slim_msg_response(&dev->ctrl, &buf[4], tid, + len - 4); + } else { + dev_err(dev->dev, "unexpected message:mc:%x, mt:%x", + mc, mt); + for (i = 0; i < len; i++) + dev_err(dev->dev, "error msg: %x", buf[i]); + + } + } else + dev_err(dev->dev, "rxwq called and no dequeue"); +} + +static void slim_sat_rxprocess(struct work_struct *work) +{ + struct msm_slim_sat *sat = container_of(work, struct msm_slim_sat, wd); + struct msm_slim_ctrl *dev = sat->dev; + u8 buf[40]; + + while ((msm_sat_dequeue(sat, buf)) != -ENODATA) { + struct slim_msg_txn txn; + int i; + u8 len, mc, mt; + u32 bw_sl; + int ret = 0; + bool gen_ack = false; + u8 tid; + u8 wbuf[8]; + txn.mt = SLIM_MSG_MT_SRC_REFERRED_USER; + txn.dt = SLIM_MSG_DEST_LOGICALADDR; + txn.ec = 0; + txn.rbuf = NULL; + txn.la = sat->satcl.laddr; + /* satellite handling */ + len = buf[0] & 0x1F; + mc = buf[1]; + mt = (buf[0] >> 5) & 0x7; + + if (mt == SLIM_MSG_MT_CORE && + mc == SLIM_MSG_MC_REPORT_PRESENT) { + u8 laddr; + u8 e_addr[6]; + for (i = 0; i < 6; i++) + e_addr[i] = buf[7-i]; + + slim_assign_laddr(&dev->ctrl, e_addr, 6, &laddr); + sat->satcl.laddr = laddr; + } + switch (mc) { + case SLIM_MSG_MC_REPORT_PRESENT: + /* send a Manager capability msg */ + if (sat->sent_capability) + continue; + ret = slim_add_device(&dev->ctrl, &sat->satcl); + if (ret) { + dev_err(dev->dev, + "Satellite-init failed"); + continue; + } + /* Satellite owns first 21 channels */ + sat->satch = kzalloc(21 * sizeof(u16), GFP_KERNEL); + sat->nsatch = 20; + /* alloc all sat chans */ + for (i = 0; i < 21; i++) + slim_alloc_ch(&sat->satcl, &sat->satch[i]); + txn.mc = SLIM_USR_MC_MASTER_CAPABILITY; + txn.mt = SLIM_MSG_MT_SRC_REFERRED_USER; + txn.la = sat->satcl.laddr; + txn.rl = 8; + wbuf[0] = SAT_MAGIC_LSB; + wbuf[1] = SAT_MAGIC_MSB; + wbuf[2] = SAT_MSG_VER; + wbuf[3] = SAT_MSG_PROT; + txn.wbuf = wbuf; + txn.len = 4; + sat->sent_capability = true; + msm_xfer_msg(&dev->ctrl, &txn); + break; + case SLIM_USR_MC_ADDR_QUERY: + memcpy(&wbuf[1], &buf[4], 6); + ret = slim_get_logical_addr(&sat->satcl, + &wbuf[1], 6, &wbuf[7]); + if (ret) + memset(&wbuf[1], 0, 6); + wbuf[0] = buf[3]; + txn.mc = SLIM_USR_MC_ADDR_REPLY; + txn.rl = 12; + txn.len = 8; + txn.wbuf = wbuf; + msm_xfer_msg(&dev->ctrl, &txn); + break; + case SLIM_USR_MC_DEFINE_CHAN: + case SLIM_USR_MC_DEF_ACT_CHAN: + case SLIM_USR_MC_CHAN_CTRL: + if (mc != SLIM_USR_MC_CHAN_CTRL) + tid = buf[7]; + else + tid = buf[4]; + gen_ack = true; + ret = msm_sat_define_ch(sat, buf, len, mc); + if (ret) { + dev_err(dev->dev, + "SAT define_ch returned:%d", + ret); + } + break; + case SLIM_USR_MC_RECONFIG_NOW: + tid = buf[3]; + gen_ack = true; + ret = slim_reconfigure_now(&sat->satcl); + break; + case SLIM_USR_MC_REQ_BW: + /* what we get is in SLOTS */ + bw_sl = (u32)buf[4] << 3 | + ((buf[3] & 0xE0) >> 5); + sat->satcl.pending_msgsl = bw_sl; + tid = buf[5]; + gen_ack = true; + break; + case SLIM_USR_MC_CONNECT_SRC: + case SLIM_USR_MC_CONNECT_SINK: + if (mc == SLIM_USR_MC_CONNECT_SRC) + txn.mc = SLIM_MSG_MC_CONNECT_SOURCE; + else + txn.mc = SLIM_MSG_MC_CONNECT_SINK; + wbuf[0] = buf[4] & 0x1F; + wbuf[1] = buf[5]; + tid = buf[6]; + txn.la = buf[3]; + txn.mt = SLIM_MSG_MT_CORE; + txn.rl = 6; + txn.len = 2; + txn.wbuf = wbuf; + gen_ack = true; + ret = msm_xfer_msg(&dev->ctrl, &txn); + break; + case SLIM_USR_MC_DISCONNECT_PORT: + txn.mc = SLIM_MSG_MC_DISCONNECT_PORT; + wbuf[0] = buf[4] & 0x1F; + tid = buf[5]; + txn.la = buf[3]; + txn.rl = 5; + txn.len = 1; + txn.mt = SLIM_MSG_MT_CORE; + txn.wbuf = wbuf; + gen_ack = true; + ret = msm_xfer_msg(&dev->ctrl, &txn); + default: + break; + } + if (!gen_ack) + continue; + wbuf[0] = tid; + if (!ret) + wbuf[1] = MSM_SAT_SUCCSS; + else + wbuf[1] = 0; + txn.mc = SLIM_USR_MC_GENERIC_ACK; + txn.la = sat->satcl.laddr; + txn.rl = 6; + txn.len = 2; + txn.wbuf = wbuf; + txn.mt = SLIM_MSG_MT_SRC_REFERRED_USER; + msm_xfer_msg(&dev->ctrl, &txn); + } +} + +static void +msm_slim_rx_msgq_event(struct msm_slim_ctrl *dev, struct sps_event_notify *ev) +{ + u32 *buf = ev->data.transfer.user; + struct sps_iovec *iovec = &ev->data.transfer.iovec; + + /* + * Note the virtual address needs to be offset by the same index + * as the physical address or just pass in the actual virtual address + * if the sps_mem_buffer is not needed. Note that if completion is + * used, the virtual address won't be available and will need to be + * calculated based on the offset of the physical address + */ + if (ev->event_id == SPS_EVENT_DESC_DONE) { + + pr_debug("buf = 0x%p, data = 0x%x\n", buf, *buf); + + pr_debug("iovec = (0x%x 0x%x 0x%x)\n", + iovec->addr, iovec->size, iovec->flags); + + } else { + dev_err(dev->dev, "%s: unknown event %d\n", + __func__, ev->event_id); + } +} + +static void msm_slim_rx_msgq_cb(struct sps_event_notify *notify) +{ + struct msm_slim_ctrl *dev = (struct msm_slim_ctrl *)notify->user; + msm_slim_rx_msgq_event(dev, notify); +} + +/* Queue up Rx message buffer */ +static inline int +msm_slim_post_rx_msgq(struct msm_slim_ctrl *dev, int ix) +{ + int ret; + u32 flags = SPS_IOVEC_FLAG_INT; + struct msm_slim_endp *endpoint = &dev->rx_msgq; + struct sps_mem_buffer *mem = &endpoint->buf; + struct sps_pipe *pipe = endpoint->sps; + + /* Rx message queue buffers are 4 bytes in length */ + u8 *virt_addr = mem->base + (4 * ix); + u32 phys_addr = mem->phys_base + (4 * ix); + + pr_debug("index:%d, phys:0x%x, virt:0x%p\n", ix, phys_addr, virt_addr); + + ret = sps_transfer_one(pipe, phys_addr, 4, virt_addr, flags); + if (ret) + dev_err(dev->dev, "transfer_one() failed 0x%x, %d\n", ret, ix); + + return ret; +} + +static inline int +msm_slim_rx_msgq_get(struct msm_slim_ctrl *dev, u32 *data, int offset) +{ + struct msm_slim_endp *endpoint = &dev->rx_msgq; + struct sps_mem_buffer *mem = &endpoint->buf; + struct sps_pipe *pipe = endpoint->sps; + struct sps_iovec iovec; + int index; + int ret; + + ret = sps_get_iovec(pipe, &iovec); + if (ret) { + dev_err(dev->dev, "sps_get_iovec() failed 0x%x\n", ret); + goto err_exit; + } + + pr_debug("iovec = (0x%x 0x%x 0x%x)\n", + iovec.addr, iovec.size, iovec.flags); + BUG_ON(iovec.addr < mem->phys_base); + BUG_ON(iovec.addr >= mem->phys_base + mem->size); + + /* Calculate buffer index */ + index = (iovec.addr - mem->phys_base) / 4; + *(data + offset) = *((u32 *)mem->base + index); + + pr_debug("buf = 0x%p, data = 0x%x\n", (u32 *)mem->base + index, *data); + + /* Add buffer back to the queue */ + (void)msm_slim_post_rx_msgq(dev, index); + +err_exit: + return ret; +} + +static int msm_slim_rx_msgq_thread(void *data) +{ + struct msm_slim_ctrl *dev = (struct msm_slim_ctrl *)data; + struct completion *notify = &dev->rx_msgq_notify; + struct msm_slim_sat *sat = NULL; + u32 mc = 0; + u32 mt = 0; + u32 buffer[10]; + int index = 0; + u8 msg_len = 0; + int ret; + + dev_dbg(dev->dev, "rx thread started"); + + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + ret = wait_for_completion_interruptible(notify); + + if (ret) + dev_err(dev->dev, "rx thread wait error:%d", ret); + + /* 1 irq notification per message */ + if (!dev->use_rx_msgqs) { + msm_slim_rxwq(dev); + continue; + } + + ret = msm_slim_rx_msgq_get(dev, buffer, index); + if (ret) { + dev_err(dev->dev, "rx_msgq_get() failed 0x%x\n", ret); + continue; + } + + pr_debug("message[%d] = 0x%x\n", index, *buffer); + + /* Decide if we use generic RX or satellite RX */ + if (index++ == 0) { + msg_len = *buffer & 0x1F; + pr_debug("Start of new message, len = %d\n", msg_len); + mt = (buffer[0] >> 5) & 0x7; + mc = (buffer[0] >> 8) & 0xff; + dev_dbg(dev->dev, "MC: %x, MT: %x\n", mc, mt); + if (mt == SLIM_MSG_MT_DEST_REFERRED_USER || + mt == SLIM_MSG_MT_SRC_REFERRED_USER) + sat = dev->satd; + + } else if ((index * 4) >= msg_len) { + index = 0; + if (mt == SLIM_MSG_MT_CORE && + mc == SLIM_MSG_MC_REPORT_PRESENT) { + u8 e_addr[6]; + msm_get_eaddr(e_addr, buffer); + if (msm_is_sat_dev(e_addr)) + sat = dev->satd; + } + if (sat) { + msm_sat_enqueue(sat, buffer, msg_len); + queue_work(sat->wq, &sat->wd); + sat = NULL; + } else { + msm_slim_rx_enqueue(dev, buffer, msg_len); + msm_slim_rxwq(dev); + } + } + } + + return 0; +} + +static int __devinit msm_slim_init_rx_msgq(struct msm_slim_ctrl *dev) +{ + int i, ret; + u32 pipe_offset; + struct msm_slim_endp *endpoint = &dev->rx_msgq; + struct sps_connect *config = &endpoint->config; + struct sps_mem_buffer *descr = &config->desc; + struct sps_mem_buffer *mem = &endpoint->buf; + struct completion *notify = &dev->rx_msgq_notify; + + struct sps_register_event sps_error_event; /* SPS_ERROR */ + struct sps_register_event sps_descr_event; /* DESCR_DONE */ + + /* Allocate the endpoint */ + ret = msm_slim_init_endpoint(dev, endpoint); + if (ret) { + dev_err(dev->dev, "init_endpoint failed 0x%x\n", ret); + goto sps_init_endpoint_failed; + } + + /* Get the pipe indices for the message queues */ + pipe_offset = (readl_relaxed(dev->base + MGR_STATUS) & 0xfc) >> 2; + dev_dbg(dev->dev, "Message queue pipe offset %d\n", pipe_offset); + + config->mode = SPS_MODE_SRC; + config->source = dev->bam.hdl; + config->destination = SPS_DEV_HANDLE_MEM; + config->src_pipe_index = pipe_offset; + config->options = SPS_O_DESC_DONE | SPS_O_ERROR | + SPS_O_ACK_TRANSFERS | SPS_O_AUTO_ENABLE; + + /* Allocate memory for the FIFO descriptors */ + ret = msm_slim_sps_mem_alloc(dev, descr, + MSM_SLIM_DESC_NUM * sizeof(struct sps_iovec)); + if (ret) { + dev_err(dev->dev, "unable to allocate SPS descriptors\n"); + goto alloc_descr_failed; + } + + ret = sps_connect(endpoint->sps, config); + if (ret) { + dev_err(dev->dev, "sps_connect failed 0x%x\n", ret); + goto sps_connect_failed; + } + + /* Register completion for DESC_DONE */ + init_completion(notify); + memset(&sps_descr_event, 0x00, sizeof(sps_descr_event)); + + sps_descr_event.mode = SPS_TRIGGER_CALLBACK; + sps_descr_event.options = SPS_O_DESC_DONE; + sps_descr_event.user = (void *)dev; + sps_descr_event.xfer_done = notify; + + ret = sps_register_event(endpoint->sps, &sps_descr_event); + if (ret) { + dev_err(dev->dev, "sps_connect() failed 0x%x\n", ret); + goto sps_reg_event_failed; + } + + /* Register callback for errors */ + memset(&sps_error_event, 0x00, sizeof(sps_error_event)); + sps_error_event.mode = SPS_TRIGGER_CALLBACK; + sps_error_event.options = SPS_O_ERROR; + sps_error_event.user = (void *)dev; + sps_error_event.callback = msm_slim_rx_msgq_cb; + + ret = sps_register_event(endpoint->sps, &sps_error_event); + if (ret) { + dev_err(dev->dev, "sps_connect() failed 0x%x\n", ret); + goto sps_reg_event_failed; + } + + /* Allocate memory for the message buffer(s), N descrs, 4-byte mesg */ + ret = msm_slim_sps_mem_alloc(dev, mem, MSM_SLIM_DESC_NUM * 4); + if (ret) { + dev_err(dev->dev, "dma_alloc_coherent failed\n"); + goto alloc_buffer_failed; + } + + /* + * Call transfer_one for each 4-byte buffer + * Use (buf->size/4) - 1 for the number of buffer to post + */ + + /* Setup the transfer */ + for (i = 0; i < (MSM_SLIM_DESC_NUM - 1); i++) { + ret = msm_slim_post_rx_msgq(dev, i); + if (ret) { + dev_err(dev->dev, "post_rx_msgq() failed 0x%x\n", ret); + goto sps_transfer_failed; + } + } + + /* Fire up the Rx message queue thread */ + dev->rx_msgq_thread = kthread_run(msm_slim_rx_msgq_thread, dev, + MSM_SLIM_NAME "_rx_msgq_thread"); + if (!dev->rx_msgq_thread) { + dev_err(dev->dev, "Failed to start Rx message queue thread\n"); + ret = -EIO; + } else + return 0; + +sps_transfer_failed: + msm_slim_sps_mem_free(dev, mem); +alloc_buffer_failed: + memset(&sps_error_event, 0x00, sizeof(sps_error_event)); + sps_register_event(endpoint->sps, &sps_error_event); +sps_reg_event_failed: + sps_disconnect(endpoint->sps); +sps_connect_failed: + msm_slim_sps_mem_free(dev, descr); +alloc_descr_failed: + msm_slim_free_endpoint(endpoint); +sps_init_endpoint_failed: + return ret; +} + +/* Registers BAM h/w resource with SPS driver and initializes msgq endpoints */ +static int __devinit +msm_slim_sps_init(struct msm_slim_ctrl *dev, struct resource *bam_mem) +{ + int i, ret; + u32 bam_handle; + struct sps_bam_props bam_props = {0}; + + static struct sps_bam_sec_config_props sec_props = { + .ees = { + [0] = { /* LPASS */ + .vmid = 0, + .pipe_mask = 0xFFFF98, + }, + [1] = { /* Krait Apps */ + .vmid = 1, + .pipe_mask = 0x3F000007, + }, + [2] = { /* Modem */ + .vmid = 2, + .pipe_mask = 0x00000060, + }, + }, + }; + + bam_props.ee = dev->ee; + bam_props.virt_addr = dev->bam.base; + bam_props.phys_addr = bam_mem->start; + bam_props.irq = dev->bam.irq; + bam_props.manage = SPS_BAM_MGR_LOCAL; + bam_props.summing_threshold = MSM_SLIM_PERF_SUMM_THRESHOLD; + + bam_props.sec_config = SPS_BAM_SEC_DO_CONFIG; + bam_props.p_sec_config_props = &sec_props; + + bam_props.options = SPS_O_DESC_DONE | SPS_O_ERROR | + SPS_O_ACK_TRANSFERS | SPS_O_AUTO_ENABLE; + + /* First 7 bits are for message Qs */ + for (i = 7; i < 32; i++) { + /* Check what pipes are owned by Apps. */ + if ((sec_props.ees[dev->ee].pipe_mask >> i) & 0x1) + break; + } + dev->pipe_b = i - 7; + + /* Register the BAM device with the SPS driver */ + ret = sps_register_bam_device(&bam_props, &bam_handle); + if (ret) { + dev_err(dev->dev, "sps_register_bam_device failed 0x%x\n", ret); + return ret; + } + dev->bam.hdl = bam_handle; + dev_dbg(dev->dev, "SLIM BAM registered, handle = 0x%x\n", bam_handle); + + ret = msm_slim_init_rx_msgq(dev); + if (ret) { + dev_err(dev->dev, "msm_slim_init_rx_msgq failed 0x%x\n", ret); + goto rx_msgq_init_failed; + } + + return 0; +rx_msgq_init_failed: + sps_deregister_bam_device(bam_handle); + dev->bam.hdl = 0L; + return ret; +} + +static void msm_slim_sps_exit(struct msm_slim_ctrl *dev) +{ + if (dev->use_rx_msgqs) { + struct msm_slim_endp *endpoint = &dev->rx_msgq; + struct sps_connect *config = &endpoint->config; + struct sps_mem_buffer *descr = &config->desc; + struct sps_mem_buffer *mem = &endpoint->buf; + struct sps_register_event sps_event; + memset(&sps_event, 0x00, sizeof(sps_event)); + msm_slim_sps_mem_free(dev, mem); + sps_register_event(endpoint->sps, &sps_event); + sps_disconnect(endpoint->sps); + msm_slim_sps_mem_free(dev, descr); + msm_slim_free_endpoint(endpoint); + } + sps_deregister_bam_device(dev->bam.hdl); +} + +static int __devinit msm_slim_probe(struct platform_device *pdev) +{ + struct msm_slim_ctrl *dev; + int ret; + struct resource *bam_mem, *bam_io; + struct resource *slim_mem, *slim_io; + struct resource *irq, *bam_irq; + slim_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "slimbus_physical"); + if (!slim_mem) { + dev_err(&pdev->dev, "no slimbus physical memory resource\n"); + return -ENODEV; + } + slim_io = request_mem_region(slim_mem->start, resource_size(slim_mem), + pdev->name); + if (!slim_io) { + dev_err(&pdev->dev, "slimbus memory already claimed\n"); + return -EBUSY; + } + + bam_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "slimbus_bam_physical"); + if (!bam_mem) { + dev_err(&pdev->dev, "no slimbus BAM memory resource\n"); + ret = -ENODEV; + goto err_get_res_bam_failed; + } + bam_io = request_mem_region(bam_mem->start, resource_size(bam_mem), + pdev->name); + if (!bam_io) { + release_mem_region(slim_mem->start, resource_size(slim_mem)); + dev_err(&pdev->dev, "slimbus BAM memory already claimed\n"); + ret = -EBUSY; + goto err_get_res_bam_failed; + } + irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "slimbus_irq"); + if (!irq) { + dev_err(&pdev->dev, "no slimbus IRQ resource\n"); + ret = -ENODEV; + goto err_get_res_failed; + } + bam_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "slimbus_bam_irq"); + if (!bam_irq) { + dev_err(&pdev->dev, "no slimbus BAM IRQ resource\n"); + ret = -ENODEV; + goto err_get_res_failed; + } + + dev = kzalloc(sizeof(struct msm_slim_ctrl), GFP_KERNEL); + if (!dev) { + dev_err(&pdev->dev, "no memory for MSM slimbus controller\n"); + ret = -ENOMEM; + goto err_get_res_failed; + } + dev->dev = &pdev->dev; + platform_set_drvdata(pdev, dev); + slim_set_ctrldata(&dev->ctrl, dev); + dev->base = ioremap(slim_mem->start, resource_size(slim_mem)); + if (!dev->base) { + dev_err(&pdev->dev, "IOremap failed\n"); + ret = -ENOMEM; + goto err_ioremap_failed; + } + dev->bam.base = ioremap(bam_mem->start, resource_size(bam_mem)); + if (!dev->bam.base) { + dev_err(&pdev->dev, "BAM IOremap failed\n"); + ret = -ENOMEM; + goto err_ioremap_bam_failed; + } + dev->ctrl.nr = pdev->id; + dev->ctrl.nchans = MSM_SLIM_NCHANS; + dev->ctrl.nports = MSM_SLIM_NPORTS; + dev->ctrl.set_laddr = msm_set_laddr; + dev->ctrl.xfer_msg = msm_xfer_msg; + dev->ctrl.config_port = msm_config_port; + dev->ctrl.port_xfer = msm_slim_port_xfer; + dev->ctrl.port_xfer_status = msm_slim_port_xfer_status; + /* Reserve some messaging BW for satellite-apps driver communication */ + dev->ctrl.sched.pending_msgsl = 30; + + init_completion(&dev->reconf); + mutex_init(&dev->tx_lock); + spin_lock_init(&dev->rx_lock); + dev->ee = 1; + dev->use_rx_msgqs = 1; + dev->irq = irq->start; + dev->bam.irq = bam_irq->start; + + ret = msm_slim_sps_init(dev, bam_mem); + if (ret != 0) { + dev_err(dev->dev, "error SPS init\n"); + goto err_sps_init_failed; + } + + + dev->rclk = clk_get(dev->dev, "audio_slimbus_clk"); + if (dev->rclk) { + clk_set_rate(dev->rclk, SLIM_ROOT_FREQ); + clk_enable(dev->rclk); + } else { + dev_err(dev->dev, "slimbus clock not found"); + goto err_clk_get_failed; + } + dev->framer.rootfreq = SLIM_ROOT_FREQ >> 3; + dev->framer.superfreq = + dev->framer.rootfreq / SLIM_CL_PER_SUPERFRAME_DIV8; + dev->ctrl.a_framer = &dev->framer; + dev->ctrl.clkgear = SLIM_MAX_CLK_GEAR; + ret = slim_add_numbered_controller(&dev->ctrl); + if (ret) { + dev_err(dev->dev, "error adding controller\n"); + goto err_ctrl_failed; + } + + ret = request_irq(dev->irq, msm_slim_interrupt, IRQF_TRIGGER_HIGH, + "msm_slim_irq", dev); + if (ret) { + dev_err(&pdev->dev, "request IRQ failed\n"); + goto err_request_irq_failed; + } + + dev->satd = kzalloc(sizeof(struct msm_slim_sat), GFP_KERNEL); + if (!dev->satd) { + ret = -ENOMEM; + goto err_sat_failed; + } + dev->satd->dev = dev; + dev->satd->satcl.name = "msm_sat_dev"; + spin_lock_init(&dev->satd->lock); + INIT_WORK(&dev->satd->wd, slim_sat_rxprocess); + dev->satd->wq = create_singlethread_workqueue("msm_slim_sat"); + /* Component register initialization */ + writel_relaxed(1, dev->base + COMP_CFG); + writel_relaxed((EE_MGR_RSC_GRP | EE_NGD_2 | EE_NGD_1), + dev->base + COMP_TRUST_CFG); + + /* + * Manager register initialization + * If RX msg Q is used, disable RX_MSG_RCVD interrupt + */ + if (dev->use_rx_msgqs) + writel_relaxed((MGR_INT_RECFG_DONE | MGR_INT_TX_NACKED_2 | + MGR_INT_MSG_BUF_CONTE | /* MGR_INT_RX_MSG_RCVD | */ + MGR_INT_TX_MSG_SENT), dev->base + MGR_INT_EN); + else + writel_relaxed((MGR_INT_RECFG_DONE | MGR_INT_TX_NACKED_2 | + MGR_INT_MSG_BUF_CONTE | MGR_INT_RX_MSG_RCVD | + MGR_INT_TX_MSG_SENT), dev->base + MGR_INT_EN); + writel_relaxed(1, dev->base + MGR_CFG); + /* + * Framer registers are beyond 1K memory region after Manager and/or + * component registers. Make sure those writes are ordered + * before framer register writes + */ + wmb(); + + /* Framer register initialization */ + writel_relaxed(1, dev->base + FRM_WAKEUP); + writel_relaxed((0xA << REF_CLK_GEAR) | (0xA << CLK_GEAR) | + (1 << ROOT_FREQ) | (1 << FRM_ACTIVE) | 1, + dev->base + FRM_CFG); + /* + * Make sure that framer wake-up and enabling writes go through + * before any other component is enabled. Framer is responsible for + * clocking the bus and enabling framer first will ensure that other + * devices can report presence when they are enabled + */ + mb(); + + /* Enable RX msg Q */ + if (dev->use_rx_msgqs) + writel_relaxed(MGR_CFG_ENABLE | MGR_CFG_RX_MSGQ_EN, + dev->base + MGR_CFG); + else + writel_relaxed(MGR_CFG_ENABLE, dev->base + MGR_CFG); + /* + * Make sure that manager-enable is written through before interface + * device is enabled + */ + mb(); + writel_relaxed(1, dev->base + INTF_CFG); + /* + * Make sure that interface-enable is written through before enabling + * ported generic device inside MSM manager + */ + mb(); + writel_relaxed(1, dev->base + PGD_CFG); + writel_relaxed(0x3F<<17, dev->base + (PGD_OWN_EEn + (4 * dev->ee))); + /* + * Make sure that ported generic device is enabled and port-EE settings + * are written through before finally enabling the component + */ + mb(); + + writel_relaxed(1, dev->base + COMP_CFG); + /* + * Make sure that all writes have gone through before exiting this + * function + */ + mb(); + dev_dbg(dev->dev, "MSM SB controller is up!\n"); + return 0; + +err_sat_failed: + free_irq(dev->irq, dev); +err_request_irq_failed: + slim_del_controller(&dev->ctrl); +err_ctrl_failed: + clk_disable(dev->rclk); + clk_put(dev->rclk); +err_clk_get_failed: + msm_slim_sps_exit(dev); +err_sps_init_failed: + iounmap(dev->bam.base); +err_ioremap_bam_failed: + iounmap(dev->base); +err_ioremap_failed: + kfree(dev); +err_get_res_failed: + release_mem_region(bam_mem->start, resource_size(bam_mem)); +err_get_res_bam_failed: + release_mem_region(slim_mem->start, resource_size(slim_mem)); + return ret; +} + +static int __devexit msm_slim_remove(struct platform_device *pdev) +{ + struct msm_slim_ctrl *dev = platform_get_drvdata(pdev); + struct resource *bam_mem; + struct resource *slim_mem; + struct msm_slim_sat *sat = dev->satd; + slim_remove_device(&sat->satcl); + kfree(sat->satch); + destroy_workqueue(sat->wq); + kfree(sat); + free_irq(dev->irq, dev); + slim_del_controller(&dev->ctrl); + clk_disable(dev->rclk); + clk_put(dev->rclk); + msm_slim_sps_exit(dev); + kthread_stop(dev->rx_msgq_thread); + iounmap(dev->bam.base); + iounmap(dev->base); + kfree(dev); + bam_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "slimbus_bam_physical"); + release_mem_region(bam_mem->start, resource_size(bam_mem)); + slim_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "slimbus_physical"); + release_mem_region(slim_mem->start, resource_size(slim_mem)); + return 0; +} + +#ifdef CONFIG_PM +static int msm_slim_suspend(struct device *device) +{ + struct platform_device *pdev = to_platform_device(device); + struct msm_slim_ctrl *dev = platform_get_drvdata(pdev); + + /* Make sure we are not transmitting anything */ + mutex_lock(&dev->tx_lock); + if (dev->reconf_busy) { + wait_for_completion(&dev->reconf); + dev->reconf_busy = false; + } + dev->suspended = 1; + mutex_unlock(&dev->tx_lock); + clk_disable(dev->rclk); + disable_irq(dev->irq); + return 0; +} + +static int msm_slim_resume(struct device *device) +{ + struct platform_device *pdev = to_platform_device(device); + struct msm_slim_ctrl *dev = platform_get_drvdata(pdev); + enable_irq(dev->irq); + clk_enable(dev->rclk); + dev->suspended = 0; + return 0; +} +#else +#define msm_slim_suspend NULL +#define msm_slim_resume NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME +static int msm_slim_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: idle...\n"); + return 0; +} + +static int msm_slim_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int msm_slim_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} +#else +#define msm_slim_runtime_idle NULL +#define msm_slim_runtime_suspend NULL +#define msm_slim_runtime_resume NULL +#endif + +static const struct dev_pm_ops msm_slim_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS( + msm_slim_suspend, + msm_slim_resume + ) + SET_RUNTIME_PM_OPS( + msm_slim_runtime_suspend, + msm_slim_runtime_resume, + msm_slim_runtime_idle + ) +}; + +static struct platform_driver msm_slim_driver = { + .probe = msm_slim_probe, + .remove = msm_slim_remove, + .driver = { + .name = MSM_SLIM_NAME, + .owner = THIS_MODULE, + .pm = &msm_slim_dev_pm_ops, + }, +}; + +static int msm_slim_init(void) +{ + return platform_driver_register(&msm_slim_driver); +} +subsys_initcall(msm_slim_init); + +static void msm_slim_exit(void) +{ + platform_driver_unregister(&msm_slim_driver); +} +module_exit(msm_slim_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); +MODULE_DESCRIPTION("MSM Slimbus controller"); +MODULE_ALIAS("platform:msm-slim"); diff --git a/drivers/slimbus/slimbus.c b/drivers/slimbus/slimbus.c new file mode 100644 index 00000000000..59bb008addd --- /dev/null +++ b/drivers/slimbus/slimbus.c @@ -0,0 +1,2625 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SLIM_PORT_HDL(la, f, p) ((la)<<24 | (f) << 16 | (p)) + +#define SLIM_HDL_TO_LA(hdl) ((u32)((hdl) & 0xFF000000) >> 24) +#define SLIM_HDL_TO_FLOW(hdl) (((u32)(hdl) & 0xFF0000) >> 16) +#define SLIM_HDL_TO_PORT(hdl) ((u32)(hdl) & 0xFF) + +#define SLIM_SLAVE_PORT(p, la) (((la)<<16) | (p)) +#define SLIM_MGR_PORT(p) ((0xFF << 16) | (p)) +#define SLIM_LA_MANAGER 0xFF + +#define SLIM_START_GRP (1 << 8) +#define SLIM_END_GRP (1 << 9) + +#define SLIM_MAX_INTR_COEFF_3 (SLIM_SL_PER_SUPERFRAME/3) +#define SLIM_MAX_INTR_COEFF_1 SLIM_SL_PER_SUPERFRAME + +static DEFINE_MUTEX(slim_lock); +static DEFINE_IDR(ctrl_idr); +static struct device_type slim_dev_type; +static struct device_type slim_ctrl_type; + +static const struct slim_device_id *slim_match(const struct slim_device_id *id, + const struct slim_device *slim_dev) +{ + while (id->name[0]) { + if (strncmp(slim_dev->name, id->name, SLIMBUS_NAME_SIZE) == 0) + return id; + id++; + } + return NULL; +} + +static int slim_device_match(struct device *dev, struct device_driver *driver) +{ + struct slim_device *slim_dev; + struct slim_driver *drv = to_slim_driver(driver); + + if (dev->type == &slim_dev_type) + slim_dev = to_slim_device(dev); + else + return 0; + if (drv->id_table) + return slim_match(drv->id_table, slim_dev) != NULL; + + if (driver->name) + return strncmp(slim_dev->name, driver->name, SLIMBUS_NAME_SIZE) + == 0; + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int slim_legacy_suspend(struct device *dev, pm_message_t mesg) +{ + struct slim_device *slim_dev = NULL; + struct slim_driver *driver; + if (dev->type == &slim_dev_type) + slim_dev = to_slim_device(dev); + + if (!slim_dev || !dev->driver) + return 0; + + driver = to_slim_driver(dev->driver); + if (!driver->suspend) + return 0; + + return driver->suspend(slim_dev, mesg); +} + +static int slim_legacy_resume(struct device *dev) +{ + struct slim_device *slim_dev = NULL; + struct slim_driver *driver; + if (dev->type == &slim_dev_type) + slim_dev = to_slim_device(dev); + + if (!slim_dev || !dev->driver) + return 0; + + driver = to_slim_driver(dev->driver); + if (!driver->resume) + return 0; + + return driver->resume(slim_dev); +} + +static int slim_pm_suspend(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_suspend(dev); + else + return slim_legacy_suspend(dev, PMSG_SUSPEND); +} + +static int slim_pm_resume(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + if (pm) + return pm_generic_resume(dev); + else + return slim_legacy_resume(dev); +} + +#else +#define slim_pm_suspend NULL +#define slim_pm_resume NULL +#endif + +static const struct dev_pm_ops slimbus_pm = { + .suspend = slim_pm_suspend, + .resume = slim_pm_resume, + SET_RUNTIME_PM_OPS( + pm_generic_suspend, + pm_generic_resume, + pm_generic_runtime_idle + ) +}; +struct bus_type slimbus_type = { + .name = "slimbus", + .match = slim_device_match, + .pm = &slimbus_pm, +}; +EXPORT_SYMBOL_GPL(slimbus_type); + +struct device slimbus_dev = { + .init_name = "slimbus", +}; + +static void __exit slimbus_exit(void) +{ + device_unregister(&slimbus_dev); + bus_unregister(&slimbus_type); +} + +static int __init slimbus_init(void) +{ + int retval; + + retval = bus_register(&slimbus_type); + if (!retval) + retval = device_register(&slimbus_dev); + + if (retval) + bus_unregister(&slimbus_type); + + return retval; +} +postcore_initcall(slimbus_init); +module_exit(slimbus_exit); + +static int slim_drv_probe(struct device *dev) +{ + const struct slim_driver *sdrv = to_slim_driver(dev->driver); + + if (sdrv->probe) + return sdrv->probe(to_slim_device(dev)); + return -ENODEV; +} + +static int slim_drv_remove(struct device *dev) +{ + const struct slim_driver *sdrv = to_slim_driver(dev->driver); + + if (sdrv->remove) + return sdrv->remove(to_slim_device(dev)); + return -ENODEV; +} + +static void slim_drv_shutdown(struct device *dev) +{ + const struct slim_driver *sdrv = to_slim_driver(dev->driver); + + if (sdrv->shutdown) + sdrv->shutdown(to_slim_device(dev)); +} + +/* + * slim_driver_register: Client driver registration with slimbus + * @drv:Client driver to be associated with client-device. + * This API will register the client driver with the slimbus + * It is called from the driver's module-init function. + */ +int slim_driver_register(struct slim_driver *drv) +{ + drv->driver.bus = &slimbus_type; + if (drv->probe) + drv->driver.probe = slim_drv_probe; + + if (drv->remove) + drv->driver.remove = slim_drv_remove; + + if (drv->shutdown) + drv->driver.shutdown = slim_drv_shutdown; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(slim_driver_register); + +#define slim_ctrl_attr_gr NULL + +static void slim_ctrl_release(struct device *dev) +{ + struct slim_controller *ctrl = to_slim_controller(dev); + + complete(&ctrl->dev_released); +} + +static struct device_type slim_ctrl_type = { + .groups = slim_ctrl_attr_gr, + .release = slim_ctrl_release, +}; + +static struct slim_controller *slim_ctrl_get(struct slim_controller *ctrl) +{ + if (!ctrl || !get_device(&ctrl->dev)) + return NULL; + + return ctrl; +} + +static void slim_ctrl_put(struct slim_controller *ctrl) +{ + if (ctrl) + put_device(&ctrl->dev); +} + +#define slim_device_attr_gr NULL +#define slim_device_uevent NULL +static void slim_dev_release(struct device *dev) +{ + struct slim_device *sbdev = to_slim_device(dev); + slim_ctrl_put(sbdev->ctrl); +} + +static struct device_type slim_dev_type = { + .groups = slim_device_attr_gr, + .uevent = slim_device_uevent, + .release = slim_dev_release, +}; + +/* + * slim_add_device: Add a new device without register board info. + * @ctrl: Controller to which this device is to be added to. + * Called when device doesn't have an explicit client-driver to be probed, or + * the client-driver is a module installed dynamically. + */ +int slim_add_device(struct slim_controller *ctrl, struct slim_device *sbdev) +{ + int ret = 0; + + sbdev->dev.bus = &slimbus_type; + sbdev->dev.parent = ctrl->dev.parent; + sbdev->dev.type = &slim_dev_type; + sbdev->ctrl = ctrl; + slim_ctrl_get(ctrl); + dev_set_name(&sbdev->dev, "%s", sbdev->name); + /* probe slave on this controller */ + ret = device_register(&sbdev->dev); + + if (ret) + return ret; + + mutex_init(&sbdev->sldev_reconf); + INIT_LIST_HEAD(&sbdev->mark_define); + INIT_LIST_HEAD(&sbdev->mark_suspend); + INIT_LIST_HEAD(&sbdev->mark_removal); + return 0; +} +EXPORT_SYMBOL_GPL(slim_add_device); + +struct sbi_boardinfo { + struct list_head list; + struct slim_boardinfo board_info; +}; + +static LIST_HEAD(board_list); +static LIST_HEAD(slim_ctrl_list); +static DEFINE_MUTEX(board_lock); + +/* If controller is not present, only add to boards list */ +static void slim_match_ctrl_to_boardinfo(struct slim_controller *ctrl, + struct slim_boardinfo *bi) +{ + int ret; + if (ctrl->nr != bi->bus_num) + return; + + ret = slim_add_device(ctrl, bi->slim_slave); + if (ret != 0) + dev_err(ctrl->dev.parent, "can't create new device for %s\n", + bi->slim_slave->name); +} + +/* + * slim_register_board_info: Board-initialization routine. + * @info: List of all devices on all controllers present on the board. + * @n: number of entries. + * API enumerates respective devices on corresponding controller. + * Called from board-init function. + */ +int slim_register_board_info(struct slim_boardinfo const *info, unsigned n) +{ + struct sbi_boardinfo *bi; + int i; + + bi = kzalloc(n * sizeof(*bi), GFP_KERNEL); + if (!bi) + return -ENOMEM; + + for (i = 0; i < n; i++, bi++, info++) { + struct slim_controller *ctrl; + + memcpy(&bi->board_info, info, sizeof(*info)); + mutex_lock(&board_lock); + list_add_tail(&bi->list, &board_list); + list_for_each_entry(ctrl, &slim_ctrl_list, list) + slim_match_ctrl_to_boardinfo(ctrl, &bi->board_info); + mutex_unlock(&board_lock); + } + return 0; +} +EXPORT_SYMBOL_GPL(slim_register_board_info); + +/* + * slim_busnum_to_ctrl: Map bus number to controller + * @busnum: Bus number + * Returns controller representing this bus number + */ +struct slim_controller *slim_busnum_to_ctrl(u32 bus_num) +{ + struct slim_controller *ctrl; + mutex_lock(&board_lock); + list_for_each_entry(ctrl, &slim_ctrl_list, list) + if (bus_num == ctrl->nr) { + mutex_unlock(&board_lock); + return ctrl; + } + mutex_unlock(&board_lock); + return NULL; +} +EXPORT_SYMBOL_GPL(slim_busnum_to_ctrl); + +static int slim_register_controller(struct slim_controller *ctrl) +{ + int ret = 0; + struct sbi_boardinfo *bi; + + /* Can't register until after driver model init */ + if (WARN_ON(!slimbus_type.p)) { + ret = -EAGAIN; + goto out_list; + } + + dev_set_name(&ctrl->dev, "sb-%d", ctrl->nr); + ctrl->dev.bus = &slimbus_type; + ctrl->dev.type = &slim_ctrl_type; + ctrl->dev.parent = &slimbus_dev; + ctrl->num_dev = 0; + mutex_init(&ctrl->m_ctrl); + mutex_init(&ctrl->sched.m_reconf); + ret = device_register(&ctrl->dev); + if (ret) + goto out_list; + + dev_dbg(&ctrl->dev, "Bus [%s] registered:dev:%x\n", ctrl->name, + (u32)&ctrl->dev); + + if (ctrl->nports) { + ctrl->ports = kzalloc(ctrl->nports * sizeof(struct slim_port), + GFP_KERNEL); + if (!ctrl->ports) { + ret = -ENOMEM; + goto err_port_failed; + } + } + if (ctrl->nchans) { + ctrl->chans = kzalloc(ctrl->nchans * sizeof(struct slim_ich), + GFP_KERNEL); + if (!ctrl->chans) { + ret = -ENOMEM; + goto err_chan_failed; + } + + ctrl->sched.chc1 = + kzalloc(ctrl->nchans * sizeof(struct slim_ich *), + GFP_KERNEL); + if (!ctrl->sched.chc1) { + kfree(ctrl->chans); + ret = -ENOMEM; + goto err_chan_failed; + } + ctrl->sched.chc3 = + kzalloc(ctrl->nchans * sizeof(struct slim_ich *), + GFP_KERNEL); + if (!ctrl->sched.chc3) { + kfree(ctrl->sched.chc1); + kfree(ctrl->chans); + ret = -ENOMEM; + goto err_chan_failed; + } + } +#ifdef DEBUG + ctrl->sched.slots = kzalloc(SLIM_SL_PER_SUPERFRAME, GFP_KERNEL); +#endif + /* + * If devices on a controller were registered before controller, + * this will make sure that they get probed now that controller is up + */ + mutex_lock(&board_lock); + list_add_tail(&ctrl->list, &slim_ctrl_list); + list_for_each_entry(bi, &board_list, list) + slim_match_ctrl_to_boardinfo(ctrl, &bi->board_info); + mutex_unlock(&board_lock); + + return 0; + +err_chan_failed: + kfree(ctrl->ports); +err_port_failed: + device_unregister(&ctrl->dev); +out_list: + mutex_lock(&slim_lock); + idr_remove(&ctrl_idr, ctrl->nr); + mutex_unlock(&slim_lock); + return ret; +} + +/* slim_remove_device: Remove the effect of slim_add_device() */ +void slim_remove_device(struct slim_device *sbdev) +{ + device_unregister(&sbdev->dev); +} +EXPORT_SYMBOL_GPL(slim_remove_device); + +static void slim_ctrl_remove_device(struct slim_controller *ctrl, + struct slim_boardinfo *bi) +{ + if (ctrl->nr == bi->bus_num) + slim_remove_device(bi->slim_slave); +} + +/* + * slim_del_controller: Controller tear-down. + * Controller added with the above API is teared down using this API. + */ +int slim_del_controller(struct slim_controller *ctrl) +{ + struct slim_controller *found; + struct sbi_boardinfo *bi; + + /* First make sure that this bus was added */ + mutex_lock(&slim_lock); + found = idr_find(&ctrl_idr, ctrl->nr); + mutex_unlock(&slim_lock); + if (found != ctrl) + return -EINVAL; + + /* Remove all clients */ + mutex_lock(&board_lock); + list_for_each_entry(bi, &board_list, list) + slim_ctrl_remove_device(ctrl, &bi->board_info); + mutex_unlock(&board_lock); + + init_completion(&ctrl->dev_released); + device_unregister(&ctrl->dev); + + wait_for_completion(&ctrl->dev_released); + list_del(&ctrl->list); + /* free bus id */ + mutex_lock(&slim_lock); + idr_remove(&ctrl_idr, ctrl->nr); + mutex_unlock(&slim_lock); + + kfree(ctrl->sched.chc1); + kfree(ctrl->sched.chc3); +#ifdef DEBUG + kfree(ctrl->sched.slots); +#endif + kfree(ctrl->chans); + kfree(ctrl->ports); + + return 0; +} +EXPORT_SYMBOL_GPL(slim_del_controller); + +/* + * slim_add_numbered_controller: Controller bring-up. + * @ctrl: Controller to be registered. + * A controller is registered with the framework using this API. ctrl->nr is the + * desired number with which slimbus framework registers the controller. + * Function will return -EBUSY if the number is in use. + */ +int slim_add_numbered_controller(struct slim_controller *ctrl) +{ + int id; + int status; + + if (ctrl->nr & ~MAX_ID_MASK) + return -EINVAL; + +retry: + if (idr_pre_get(&ctrl_idr, GFP_KERNEL) == 0) + return -ENOMEM; + + mutex_lock(&slim_lock); + status = idr_get_new_above(&ctrl_idr, ctrl, ctrl->nr, &id); + if (status == 0 && id != ctrl->nr) { + status = -EAGAIN; + idr_remove(&ctrl_idr, id); + } + mutex_unlock(&slim_lock); + if (status == -EAGAIN) + goto retry; + + if (status == 0) + status = slim_register_controller(ctrl); + return status; +} +EXPORT_SYMBOL_GPL(slim_add_numbered_controller); + +/* + * slim_msg_response: Deliver Message response received from a device to the + * framework. + * @ctrl: Controller handle + * @reply: Reply received from the device + * @len: Length of the reply + * @tid: Transaction ID received with which framework can associate reply. + * Called by controller to inform framework about the response received. + * This helps in making the API asynchronous, and controller-driver doesn't need + * to manage 1 more table other than the one managed by framework mapping TID + * with buffers + */ +void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 len) +{ + int i; + struct slim_msg_txn *txn; + + mutex_lock(&ctrl->m_ctrl); + txn = ctrl->txnt[tid]; + if (txn == NULL) { + dev_err(&ctrl->dev, "Got response to invalid TID:%d, len:%d", + tid, len); + mutex_unlock(&ctrl->m_ctrl); + return; + } + for (i = 0; i < len; i++) + txn->rbuf[i] = reply[i]; + if (txn->comp) + complete(txn->comp); + ctrl->txnt[tid] = NULL; + mutex_unlock(&ctrl->m_ctrl); + kfree(txn); +} +EXPORT_SYMBOL_GPL(slim_msg_response); + +static int slim_processtxn(struct slim_controller *ctrl, u8 dt, u8 mc, u16 ec, + u8 mt, u8 *rbuf, const u8 *wbuf, u8 len, u8 mlen, + struct completion *comp, u8 la, u8 *tid) +{ + u8 i = 0; + int ret = 0; + struct slim_msg_txn *txn = kmalloc(sizeof(struct slim_msg_txn), + GFP_KERNEL); + if (!txn) + return -ENOMEM; + if (tid) { + mutex_lock(&ctrl->m_ctrl); + for (i = 0; i < ctrl->last_tid; i++) { + if (ctrl->txnt[i] == NULL) + break; + } + if (i >= ctrl->last_tid) { + if (ctrl->last_tid == 255) { + mutex_unlock(&ctrl->m_ctrl); + kfree(txn); + return -ENOMEM; + } + ctrl->txnt = krealloc(ctrl->txnt, + (i + 1) * sizeof(struct slim_msg_txn *), + GFP_KERNEL); + if (!ctrl->txnt) { + mutex_unlock(&ctrl->m_ctrl); + kfree(txn); + return -ENOMEM; + } + ctrl->last_tid++; + } + ctrl->txnt[i] = txn; + mutex_unlock(&ctrl->m_ctrl); + txn->tid = i; + *tid = i; + } + txn->mc = mc; + txn->mt = mt; + txn->dt = dt; + txn->ec = ec; + txn->la = la; + txn->rbuf = rbuf; + txn->wbuf = wbuf; + txn->rl = mlen; + txn->len = len; + txn->comp = comp; + + ret = ctrl->xfer_msg(ctrl, txn); + if (!tid) + kfree(txn); + return ret; +} + +static int ctrl_getlogical_addr(struct slim_controller *ctrl, const u8 *eaddr, + u8 e_len, u8 *laddr) +{ + u8 i; + for (i = 0; i < ctrl->num_dev; i++) { + if (ctrl->addrt[i].valid && + memcmp(ctrl->addrt[i].eaddr, eaddr, e_len) == 0) { + *laddr = i; + return 0; + } + } + return -ENXIO; +} + +/* + * slim_assign_laddr: Assign logical address to a device enumerated. + * @ctrl: Controller with which device is enumerated. + * @e_addr: 6-byte elemental address of the device. + * @e_len: buffer length for e_addr + * @laddr: Return logical address. + * Called by controller in response to REPORT_PRESENT. Framework will assign + * a logical address to this enumeration address. + * Function returns -EXFULL to indicate that all logical addresses are already + * taken. + */ +int slim_assign_laddr(struct slim_controller *ctrl, const u8 *e_addr, + u8 e_len, u8 *laddr) +{ + int ret; + u8 i; + mutex_lock(&ctrl->m_ctrl); + /* already assigned */ + if (ctrl_getlogical_addr(ctrl, e_addr, e_len, laddr) == 0) + i = *laddr; + else { + if (ctrl->num_dev >= 254) { + ret = -EXFULL; + goto ret_assigned_laddr; + } + for (i = 0; i < ctrl->num_dev; i++) { + if (ctrl->addrt[i].valid == false) + break; + } + if (i == ctrl->num_dev) { + ctrl->addrt = krealloc(ctrl->addrt, + (ctrl->num_dev + 1) * + sizeof(struct slim_addrt), + GFP_KERNEL); + if (!ctrl->addrt) { + ret = -ENOMEM; + goto ret_assigned_laddr; + } + ctrl->num_dev++; + } + memcpy(ctrl->addrt[i].eaddr, e_addr, e_len); + ctrl->addrt[i].valid = true; + } + + ret = ctrl->set_laddr(ctrl, ctrl->addrt[i].eaddr, 6, i); + if (ret) { + ctrl->addrt[i].valid = false; + goto ret_assigned_laddr; + } + *laddr = i; + + dev_dbg(&ctrl->dev, "setting slimbus l-addr:%x\n", i); +ret_assigned_laddr: + mutex_unlock(&ctrl->m_ctrl); + return ret; +} +EXPORT_SYMBOL_GPL(slim_assign_laddr); + +/* + * slim_get_logical_addr: Return the logical address of a slimbus device. + * @sb: client handle requesting the adddress. + * @e_addr: Elemental address of the device. + * @e_len: Length of e_addr + * @laddr: output buffer to store the address + * context: can sleep + * -EINVAL is returned in case of invalid parameters, and -ENXIO is returned if + * the device with this elemental address is not found. + */ +int slim_get_logical_addr(struct slim_device *sb, const u8 *e_addr, + u8 e_len, u8 *laddr) +{ + int ret = 0; + struct slim_controller *ctrl = sb->ctrl; + if (!ctrl || !laddr || !e_addr || e_len != 6) + return -EINVAL; + mutex_lock(&ctrl->m_ctrl); + ret = ctrl_getlogical_addr(ctrl, e_addr, e_len, laddr); + mutex_unlock(&ctrl->m_ctrl); + return ret; +} +EXPORT_SYMBOL_GPL(slim_get_logical_addr); + +static int slim_ele_access_sanity(struct slim_ele_access *msg, int oper, + u8 *rbuf, const u8 *wbuf, u8 len) +{ + if (!msg || msg->num_bytes > 16 || msg->start_offset + len > 0xC00) + return -EINVAL; + switch (oper) { + case SLIM_MSG_MC_REQUEST_VALUE: + case SLIM_MSG_MC_REQUEST_INFORMATION: + if (rbuf == NULL) + return -EINVAL; + return 0; + case SLIM_MSG_MC_CHANGE_VALUE: + case SLIM_MSG_MC_CLEAR_INFORMATION: + if (wbuf == NULL) + return -EINVAL; + return 0; + case SLIM_MSG_MC_REQUEST_CHANGE_VALUE: + case SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION: + if (rbuf == NULL || wbuf == NULL) + return -EINVAL; + return 0; + default: + return -EINVAL; + } +} + +static u16 slim_slicecodefromsize(u32 req) +{ + u8 codetosize[8] = {1, 2, 3, 4, 6, 8, 12, 16}; + if (req >= 8) + return 0; + else + return codetosize[req]; +} + +static u16 slim_slicesize(u32 code) +{ + u8 sizetocode[16] = {0, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7}; + if (code == 0) + code = 1; + if (code > 16) + code = 16; + return sizetocode[code - 1]; +} + + +/* Message APIs Unicast message APIs used by slimbus slave drivers */ + +/* + * Message API access routines. + * @sb: client handle requesting elemental message reads, writes. + * @msg: Input structure for start-offset, number of bytes to read. + * @rbuf: data buffer to be filled with values read. + * @len: data buffer size + * @wbuf: data buffer containing value/information to be written + * context: can sleep + * Returns: + * -EINVAL: Invalid parameters + * -ETIMEDOUT: If controller could not complete the request. This may happen if + * the bus lines are not clocked, controller is not powered-on, slave with + * given address is not enumerated/responding. + */ +int slim_request_val_element(struct slim_device *sb, + struct slim_ele_access *msg, u8 *buf, u8 len) +{ + struct slim_controller *ctrl = sb->ctrl; + if (!ctrl) + return -EINVAL; + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_VALUE, buf, + NULL, len); +} +EXPORT_SYMBOL_GPL(slim_request_val_element); + +int slim_request_inf_element(struct slim_device *sb, + struct slim_ele_access *msg, u8 *buf, u8 len) +{ + struct slim_controller *ctrl = sb->ctrl; + if (!ctrl) + return -EINVAL; + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_INFORMATION, + buf, NULL, len); +} +EXPORT_SYMBOL_GPL(slim_request_inf_element); + +int slim_change_val_element(struct slim_device *sb, struct slim_ele_access *msg, + const u8 *buf, u8 len) +{ + struct slim_controller *ctrl = sb->ctrl; + if (!ctrl) + return -EINVAL; + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_CHANGE_VALUE, NULL, buf, + len); +} +EXPORT_SYMBOL_GPL(slim_change_val_element); + +int slim_clear_inf_element(struct slim_device *sb, struct slim_ele_access *msg, + u8 *buf, u8 len) +{ + struct slim_controller *ctrl = sb->ctrl; + if (!ctrl) + return -EINVAL; + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_CLEAR_INFORMATION, NULL, + buf, len); +} +EXPORT_SYMBOL_GPL(slim_clear_inf_element); + +int slim_request_change_val_element(struct slim_device *sb, + struct slim_ele_access *msg, u8 *rbuf, + const u8 *wbuf, u8 len) +{ + struct slim_controller *ctrl = sb->ctrl; + if (!ctrl) + return -EINVAL; + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_CHANGE_VALUE, + rbuf, wbuf, len); +} +EXPORT_SYMBOL_GPL(slim_request_change_val_element); + +int slim_request_clear_inf_element(struct slim_device *sb, + struct slim_ele_access *msg, u8 *rbuf, + const u8 *wbuf, u8 len) +{ + struct slim_controller *ctrl = sb->ctrl; + if (!ctrl) + return -EINVAL; + return slim_xfer_msg(ctrl, sb, msg, + SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION, + rbuf, wbuf, len); +} +EXPORT_SYMBOL_GPL(slim_request_clear_inf_element); + +/* + * Broadcast message API: + * call this API directly with sbdev = NULL. + * For broadcast reads, make sure that buffers are big-enough to incorporate + * replies from all logical addresses. + * All controllers may not support broadcast + */ +int slim_xfer_msg(struct slim_controller *ctrl, struct slim_device *sbdev, + struct slim_ele_access *msg, u8 mc, u8 *rbuf, + const u8 *wbuf, u8 len) +{ + DECLARE_COMPLETION_ONSTACK(complete); + int ret; + u16 sl, cur; + u16 ec; + u8 tid, mlen = 6; + + if (sbdev->laddr != SLIM_LA_MANAGER && sbdev->laddr >= ctrl->num_dev) + return -ENXIO; + ret = slim_ele_access_sanity(msg, mc, rbuf, wbuf, len); + if (ret) + goto xfer_err; + + sl = slim_slicesize(len); + dev_dbg(&ctrl->dev, "SB xfer msg:os:%x, len:%d, MC:%x, sl:%x\n", + msg->start_offset, len, mc, sl); + + cur = slim_slicecodefromsize(sl); + ec = ((sl | (1 << 3)) | ((msg->start_offset & 0xFFF) << 4)); + + if (wbuf) + mlen += len; + if (rbuf) { + mlen++; + if (!msg->comp) + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, + mc, ec, SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen, + &complete, sbdev->laddr, &tid); + else + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, + mc, ec, SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen, + msg->comp, sbdev->laddr, &tid); + /* sync read */ + if (!ret && !msg->comp) + wait_for_completion_timeout(&complete, HZ); + } else + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, ec, + SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen, + NULL, sbdev->laddr, NULL); +xfer_err: + return ret; +} +EXPORT_SYMBOL_GPL(slim_xfer_msg); + +/* + * slim_alloc_mgrports: Allocate port on manager side. + * @sb: device/client handle. + * @req: Port request type. + * @nports: Number of ports requested + * @rh: output buffer to store the port handles + * @hsz: size of buffer storing handles + * context: can sleep + * This port will be typically used by SW. e.g. client driver wants to receive + * some data from audio codec HW using a data channel. + * Port allocated using this API will be used to receive the data. + * If half-duplex ports are requested, two adjacent ports are allocated for + * 1 half-duplex port. So the handle-buffer size should be twice the number + * of half-duplex ports to be allocated. + * -EDQUOT is returned if all ports are in use. + */ +int slim_alloc_mgrports(struct slim_device *sb, enum slim_port_req req, + int nports, u32 *rh, int hsz) +{ + int i, j, ret; + int nphysp = nports; + struct slim_controller *ctrl = sb->ctrl; + + if (!rh || !ctrl) + return -EINVAL; + if (req == SLIM_REQ_HALF_DUP) + nphysp *= 2; + if (hsz/sizeof(u32) < nphysp) + return -EINVAL; + mutex_lock(&ctrl->m_ctrl); + + for (i = 0; i < ctrl->nports; i++) { + bool multiok = true; + if (ctrl->ports[i].state != SLIM_P_FREE) + continue; + /* Start half duplex channel at even port */ + if (req == SLIM_REQ_HALF_DUP && (i % 2)) + continue; + /* Allocate ports contiguously for multi-ch */ + if (ctrl->nports < (i + nphysp)) { + i = ctrl->nports; + break; + } + if (req == SLIM_REQ_MULTI_CH) { + multiok = true; + for (j = i; j < i + nphysp; j++) { + if (ctrl->ports[j].state != SLIM_P_FREE) { + multiok = false; + break; + } + } + if (!multiok) + continue; + } + break; + } + if (i >= ctrl->nports) + ret = -EDQUOT; + for (j = i; j < i + nphysp; j++) { + ctrl->ports[j].state = SLIM_P_UNCFG; + ctrl->ports[j].req = req; + if (req == SLIM_REQ_HALF_DUP && (j % 2)) + ctrl->ports[j].flow = SLIM_SINK; + else + ctrl->ports[j].flow = SLIM_SRC; + ret = ctrl->config_port(ctrl, j); + if (ret) { + for (; j >= i; j--) + ctrl->ports[j].state = SLIM_P_FREE; + goto alloc_err; + } + *rh++ = SLIM_PORT_HDL(SLIM_LA_MANAGER, 0, j); + } +alloc_err: + mutex_unlock(&ctrl->m_ctrl); + return ret; +} +EXPORT_SYMBOL_GPL(slim_alloc_mgrports); + +/* Deallocate the port(s) allocated using the API above */ +int slim_dealloc_mgrports(struct slim_device *sb, u32 *hdl, int nports) +{ + int i; + struct slim_controller *ctrl = sb->ctrl; + + if (!ctrl || !hdl) + return -EINVAL; + + mutex_lock(&ctrl->m_ctrl); + + for (i = 0; i < nports; i++) { + u8 pn; + pn = SLIM_HDL_TO_PORT(hdl[i]); + if (ctrl->ports[pn].state == SLIM_P_CFG) { + int j; + dev_err(&ctrl->dev, "Can't dealloc connected port:%d", + i); + for (j = i - 1; j >= 0; j--) { + pn = SLIM_HDL_TO_PORT(hdl[j]); + ctrl->ports[pn].state = SLIM_P_UNCFG; + } + mutex_unlock(&ctrl->m_ctrl); + return -EISCONN; + } + ctrl->ports[pn].state = SLIM_P_FREE; + } + mutex_unlock(&ctrl->m_ctrl); + return 0; +} +EXPORT_SYMBOL_GPL(slim_dealloc_mgrports); + +/* + * slim_get_slaveport: Get slave port handle + * @la: slave device logical address. + * @idx: port index at slave + * @rh: return handle + * @flw: Flow type (source or destination) + * This API only returns a slave port's representation as expected by slimbus + * driver. This port is not managed by the slimbus driver. Caller is expected + * to have visibility of this port since it's a device-port. + */ +int slim_get_slaveport(u8 la, int idx, u32 *rh, enum slim_port_flow flw) +{ + if (rh == NULL) + return -EINVAL; + *rh = SLIM_PORT_HDL(la, flw, idx); + return 0; +} +EXPORT_SYMBOL_GPL(slim_get_slaveport); + +static int connect_port_ch(struct slim_controller *ctrl, u8 ch, u32 ph, + enum slim_port_flow flow) +{ + int ret; + u8 mc; + u8 buf[2]; + u32 la = SLIM_HDL_TO_LA(ph); + u8 pn = (u8)SLIM_HDL_TO_PORT(ph); + + if (flow == SLIM_SRC) + mc = SLIM_MSG_MC_CONNECT_SOURCE; + else + mc = SLIM_MSG_MC_CONNECT_SINK; + buf[0] = pn; + buf[1] = ch; + if (la == SLIM_LA_MANAGER) + ctrl->ports[pn].flow = flow; + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, 0, + SLIM_MSG_MT_CORE, NULL, buf, 2, 6, NULL, la, + NULL); + if (!ret && la == SLIM_LA_MANAGER) + ctrl->ports[pn].state = SLIM_P_CFG; + return ret; +} + +static int disconnect_port_ch(struct slim_controller *ctrl, u32 ph) +{ + int ret; + u8 mc; + u32 la = SLIM_HDL_TO_LA(ph); + u8 pn = (u8)SLIM_HDL_TO_PORT(ph); + + mc = SLIM_MSG_MC_DISCONNECT_PORT; + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, 0, + SLIM_MSG_MT_CORE, NULL, &pn, 1, 5, + NULL, la, NULL); + if (ret) + return ret; + if (la == SLIM_LA_MANAGER) + ctrl->ports[pn].state = SLIM_P_UNCFG; + return 0; +} + +/* + * slim_connect_ports: Connect port(s) to channel. + * @sb: client handle + * @srch: source handles to be connected to this channel + * @nrsc: number of source ports + * @sinkh: sink handle to be connected to this channel + * @chanh: Channel with which the ports need to be associated with. + * Per slimbus specification, a channel may have multiple source-ports and 1 + * sink port.Channel specified in chanh needs to be allocated first. + */ +int slim_connect_ports(struct slim_device *sb, u32 *srch, int nsrc, u32 sinkh, + u16 chanh) +{ + struct slim_controller *ctrl = sb->ctrl; + int j; + int ret = 0; + u8 chan = (u8)(chanh & 0xFF); + struct slim_ich *slc = &ctrl->chans[chan]; + + mutex_lock(&ctrl->m_ctrl); + /* Make sure the channel is not already pending reconf. or active */ + if (slc->state >= SLIM_CH_PENDING_ACTIVE) { + dev_err(&ctrl->dev, "Channel %d already active", chan); + ret = -EISCONN; + goto connect_port_err; + } + + /* + * Once channel is removed, its ports can be considered disconnected + * So its ports can be reassigned. Source port array is freed + * when channel is deallocated. + */ + slc->srch = krealloc(slc->srch, (sizeof(u32) * nsrc), GFP_KERNEL); + if (!slc->srch) { + ret = -ENOMEM; + goto connect_port_err; + } + /* connect source */ + for (j = 0; j < nsrc; j++) { + ret = connect_port_ch(ctrl, chan, srch[j], SLIM_SRC); + if (ret) { + for ( ; j >= 0 ; j--) + disconnect_port_ch(ctrl, + srch[j]); + kfree(slc->srch); + slc->srch = NULL; + goto connect_port_err; + } + } + /* connect sink */ + ret = connect_port_ch(ctrl, chan, sinkh, SLIM_SINK); + if (ret) { + for (j = 0; j < nsrc; j++) + disconnect_port_ch(ctrl, srch[j]); + kfree(slc->srch); + slc->srch = NULL; + goto connect_port_err; + } + + memcpy(slc->srch, srch, (sizeof(u32) * nsrc)); + slc->nsrc = nsrc; + if (sinkh) + slc->sinkh = sinkh; + +connect_port_err: + mutex_unlock(&ctrl->m_ctrl); + return ret; +} +EXPORT_SYMBOL_GPL(slim_connect_ports); + +/* + * slim_disconnect_ports: Disconnect port(s) from channel + * @sb: client handle + * @ph: ports to be disconnected + * @nph: number of ports. + * Disconnects ports from a channel. + */ +int slim_disconnect_ports(struct slim_device *sb, u32 *ph, int nph) +{ + struct slim_controller *ctrl = sb->ctrl; + int i; + mutex_lock(&ctrl->m_ctrl); + for (i = 0; i < nph; i++) + disconnect_port_ch(ctrl, ph[i]); + mutex_unlock(&ctrl->m_ctrl); + return 0; +} +EXPORT_SYMBOL_GPL(slim_disconnect_ports); + +/* + * slim_port_xfer: Schedule buffer to be transferred/received using port-handle. + * @sb: client handle + * @ph: port-handle + * @iobuf: buffer to be transferred or populated + * @len: buffer size. + * @comp: completion signal to indicate transfer done or error. + * context: can sleep + * Returns number of bytes transferred/received if used synchronously. + * Will return 0 if used asynchronously. + * Client will call slim_port_get_xfer_status to get error and/or number of + * bytes transferred if used asynchronously. + */ +int slim_port_xfer(struct slim_device *sb, u32 ph, u8 *iobuf, u32 len, + struct completion *comp) +{ + struct slim_controller *ctrl = sb->ctrl; + u8 pn = SLIM_HDL_TO_PORT(ph); + dev_dbg(&ctrl->dev, "port xfer: num:%d", pn); + return ctrl->port_xfer(ctrl, pn, iobuf, len, comp); +} +EXPORT_SYMBOL_GPL(slim_port_xfer); + +/* + * slim_port_get_xfer_status: Poll for port transfers, or get transfer status + * after completion is done. + * @sb: client handle + * @ph: port-handle + * @done_buf: return pointer (iobuf from slim_port_xfer) which is processed. + * @done_len: Number of bytes transferred. + * This can be called when port_xfer complition is signalled. + * The API will return port transfer error (underflow/overflow/disconnect) + * and/or done_len will reflect number of bytes transferred. Note that + * done_len may be valid even if port error (overflow/underflow) has happened. + * e.g. If the transfer was scheduled with a few bytes to be transferred and + * client has not supplied more data to be transferred, done_len will indicate + * number of bytes transferred with underflow error. To avoid frequent underflow + * errors, multiple transfers can be queued (e.g. ping-pong buffers) so that + * channel has data to be transferred even if client is not ready to transfer + * data all the time. done_buf will indicate address of the last buffer + * processed from the multiple transfers. + */ +enum slim_port_err slim_port_get_xfer_status(struct slim_device *sb, u32 ph, + u8 **done_buf, u32 *done_len) +{ + struct slim_controller *ctrl = sb->ctrl; + u8 pn = SLIM_HDL_TO_PORT(ph); + u32 la = SLIM_HDL_TO_LA(ph); + enum slim_port_err err; + dev_dbg(&ctrl->dev, "get status port num:%d", pn); + /* + * Framework only has insight into ports managed by ported device + * used by the manager and not slave + */ + if (la != SLIM_LA_MANAGER) { + if (done_buf) + *done_buf = NULL; + if (done_len) + *done_len = 0; + return SLIM_P_NOT_OWNED; + } + err = ctrl->port_xfer_status(ctrl, pn, done_buf, done_len); + if (err == SLIM_P_INPROGRESS) + err = ctrl->ports[pn].err; + return err; +} +EXPORT_SYMBOL_GPL(slim_port_get_xfer_status); + +static void slim_add_ch(struct slim_controller *ctrl, struct slim_ich *slc) +{ + struct slim_ich **arr; + int i, j; + int *len; + int sl = slc->seglen << slc->rootexp; + if (slc->coeff == SLIM_COEFF_1) { + arr = ctrl->sched.chc1; + len = &ctrl->sched.num_cc1; + } else { + arr = ctrl->sched.chc3; + len = &ctrl->sched.num_cc3; + sl *= 3; + } + + *len += 1; + + /* Insert the channel based on rootexp and seglen */ + for (i = 0; i < *len - 1; i++) { + /* + * Primary key: exp low to high. + * Secondary key: seglen: high to low + */ + if ((slc->rootexp > arr[i]->rootexp) || + ((slc->rootexp == arr[i]->rootexp) && + (slc->seglen < arr[i]->seglen))) + continue; + else + break; + } + for (j = *len - 1; j > i; j--) + arr[j] = arr[j - 1]; + arr[i] = slc; + ctrl->sched.usedslots += sl; + + return; +} + +static int slim_remove_ch(struct slim_controller *ctrl, struct slim_ich *slc) +{ + struct slim_ich **arr; + int i; + u32 la, ph; + int *len; + if (slc->coeff == SLIM_COEFF_1) { + arr = ctrl->sched.chc1; + len = &ctrl->sched.num_cc1; + } else { + arr = ctrl->sched.chc3; + len = &ctrl->sched.num_cc3; + } + + for (i = 0; i < *len; i++) { + if (arr[i] == slc) + break; + } + if (i >= *len) + return -EXFULL; + for (; i < *len - 1; i++) + arr[i] = arr[i + 1]; + *len -= 1; + arr[*len] = NULL; + + slc->state = SLIM_CH_ALLOCATED; + slc->newintr = 0; + slc->newoff = 0; + for (i = 0; i < slc->nsrc; i++) { + ph = slc->srch[i]; + la = SLIM_HDL_TO_LA(ph); + /* + * For ports managed by manager's ported device, no need to send + * disconnect. It is client's responsibility to call disconnect + * on ports owned by the slave device + */ + if (la == SLIM_LA_MANAGER) + ctrl->ports[SLIM_HDL_TO_PORT(ph)].state = SLIM_P_UNCFG; + } + + ph = slc->sinkh; + la = SLIM_HDL_TO_LA(ph); + if (la == SLIM_LA_MANAGER) + ctrl->ports[SLIM_HDL_TO_PORT(ph)].state = SLIM_P_UNCFG; + + return 0; +} + +static u32 slim_calc_prrate(struct slim_controller *ctrl, struct slim_ch *prop) +{ + u32 rate = 0, rate4k = 0, rate11k = 0; + u32 exp = 0; + u32 pr = 0; + bool exact = true; + bool done = false; + enum slim_ch_rate ratefam; + + if (prop->prot >= SLIM_PUSH) + return 0; + if (prop->baser == SLIM_RATE_1HZ) { + rate = prop->ratem / 4000; + rate4k = rate; + if (rate * 4000 == prop->ratem) + ratefam = SLIM_RATE_4000HZ; + else { + rate = prop->ratem / 11025; + rate11k = rate; + if (rate * 11025 == prop->ratem) + ratefam = SLIM_RATE_11025HZ; + else + ratefam = SLIM_RATE_1HZ; + } + } else { + ratefam = prop->baser; + rate = prop->ratem; + } + if (ratefam == SLIM_RATE_1HZ) { + exact = false; + if ((rate4k + 1) * 4000 < (rate11k + 1) * 11025) { + rate = rate4k + 1; + ratefam = SLIM_RATE_4000HZ; + } else { + rate = rate11k + 1; + ratefam = SLIM_RATE_11025HZ; + } + } + /* covert rate to coeff-exp */ + while (!done) { + while ((rate & 0x1) != 0x1) { + rate >>= 1; + exp++; + } + if (rate > 3) { + /* roundup if not exact */ + rate++; + exact = false; + } else + done = true; + } + if (ratefam == SLIM_RATE_4000HZ) { + if (rate == 1) + pr = 0x10; + else { + pr = 0; + exp++; + } + } else { + pr = 8; + exp++; + } + if (exp <= 7) { + pr |= exp; + if (exact) + pr |= 0x80; + } else + pr = 0; + return pr; +} + +static int slim_nextdefine_ch(struct slim_device *sb, u8 chan) +{ + struct slim_controller *ctrl = sb->ctrl; + u32 chrate = 0; + u32 exp = 0; + u32 coeff = 0; + bool exact = true; + bool done = false; + int ret = 0; + struct slim_ich *slc = &ctrl->chans[chan]; + struct slim_ch *prop = &slc->prop; + + slc->prrate = slim_calc_prrate(ctrl, prop); + dev_dbg(&ctrl->dev, "ch:%d, chan PR rate:%x\n", chan, slc->prrate); + if (prop->baser == SLIM_RATE_4000HZ) + chrate = 4000 * prop->ratem; + else if (prop->baser == SLIM_RATE_11025HZ) + chrate = 11025 * prop->ratem; + else + chrate = prop->ratem; + /* max allowed sample freq = 768 seg/frame */ + if (chrate > 3600000) + return -EDQUOT; + if (prop->baser == SLIM_RATE_4000HZ && + ctrl->a_framer->superfreq == 4000) + coeff = prop->ratem; + else if (prop->baser == SLIM_RATE_11025HZ && + ctrl->a_framer->superfreq == 3675) + coeff = 3 * prop->ratem; + else { + u32 tempr = 0; + tempr = chrate * SLIM_CL_PER_SUPERFRAME_DIV8; + coeff = tempr / ctrl->a_framer->rootfreq; + if (coeff * ctrl->a_framer->rootfreq != tempr) { + coeff++; + exact = false; + } + } + + /* convert coeff to coeff-exponent */ + exp = 0; + while (!done) { + while ((coeff & 0x1) != 0x1) { + coeff >>= 1; + exp++; + } + if (coeff > 3) { + coeff++; + exact = false; + } else + done = true; + } + if (prop->prot == SLIM_HARD_ISO && !exact) + return -EPROTONOSUPPORT; + else if (prop->prot == SLIM_AUTO_ISO) { + if (exact) + prop->prot = SLIM_HARD_ISO; + else { + /* Push-Pull not supported for now */ + return -EPROTONOSUPPORT; + } + } + slc->rootexp = exp; + slc->seglen = prop->sampleszbits/SLIM_CL_PER_SL; + if (prop->prot != SLIM_HARD_ISO) + slc->seglen++; + if (prop->prot >= SLIM_EXT_SMPLX) + slc->seglen++; + /* convert coeff to enum */ + if (coeff == 1) { + if (exp > 9) + ret = -EIO; + coeff = SLIM_COEFF_1; + } else { + if (exp > 8) + ret = -EIO; + coeff = SLIM_COEFF_3; + } + slc->coeff = coeff; + + return ret; +} + +/* + * slim_alloc_ch: Allocate a slimbus channel and return its handle. + * @sb: client handle. + * @chanh: return channel handle + * Slimbus channels are limited to 256 per specification. LSB of the handle + * indicates channel number and MSB of the handle is used by the slimbus + * framework. -EXFULL is returned if all channels are in use. + * Although slimbus specification supports 256 channels, a controller may not + * support that many channels. + */ +int slim_alloc_ch(struct slim_device *sb, u16 *chanh) +{ + struct slim_controller *ctrl = sb->ctrl; + u16 i; + + if (!ctrl) + return -EINVAL; + mutex_lock(&ctrl->m_ctrl); + for (i = 0; i < ctrl->nchans; i++) { + if (ctrl->chans[i].state == SLIM_CH_FREE) + break; + } + if (i >= ctrl->nchans) { + mutex_unlock(&ctrl->m_ctrl); + return -EXFULL; + } + *chanh = i; + ctrl->chans[i].nextgrp = 0; + ctrl->chans[i].state = SLIM_CH_ALLOCATED; + + mutex_unlock(&ctrl->m_ctrl); + return 0; +} +EXPORT_SYMBOL_GPL(slim_alloc_ch); + +/* + * slim_dealloc_ch: Deallocate channel allocated using the API above + * -EISCONN is returned if the channel is tried to be deallocated without + * being removed first. + */ +int slim_dealloc_ch(struct slim_device *sb, u16 chanh) +{ + struct slim_controller *ctrl = sb->ctrl; + u8 chan = (u8)(chanh & 0xFF); + struct slim_ich *slc = &ctrl->chans[chan]; + if (!ctrl) + return -EINVAL; + + mutex_lock(&ctrl->m_ctrl); + if (slc->state >= SLIM_CH_PENDING_ACTIVE) { + dev_err(&ctrl->dev, "Channel:%d should be removed first", chan); + mutex_unlock(&ctrl->m_ctrl); + return -EISCONN; + } + kfree(slc->srch); + slc->srch = NULL; + slc->state = SLIM_CH_FREE; + mutex_unlock(&ctrl->m_ctrl); + return 0; +} +EXPORT_SYMBOL_GPL(slim_dealloc_ch); + +/* + * slim_get_ch_state: Channel state. + * This API returns the channel's state (active, suspended, inactive etc) + */ +enum slim_ch_state slim_get_ch_state(struct slim_device *sb, u16 chanh) +{ + u8 chan = (u8)(chanh & 0xFF); + struct slim_ich *slc = &sb->ctrl->chans[chan]; + return slc->state; +} +EXPORT_SYMBOL_GPL(slim_get_ch_state); + +/* + * slim_define_ch: Define a channel.This API defines channel parameters for a + * given channel. + * @sb: client handle. + * @prop: slim_ch structure with channel parameters desired to be used. + * @chanh: list of channels to be defined. + * @nchan: number of channels in a group (1 if grp is false) + * @grp: Are the channels grouped + * @grph: return group handle if grouping of channels is desired. + * Channels can be grouped if multiple channels use same parameters + * (e.g. 5.1 audio has 6 channels with same parameters. They will all be grouped + * and given 1 handle for simplicity and avoid repeatedly calling the API) + * -EISCONN is returned if the channel is already connected. -EBUSY is + * returned if the channel is already allocated to some other client. + */ +int slim_define_ch(struct slim_device *sb, struct slim_ch *prop, u16 *chanh, + u8 nchan, bool grp, u16 *grph) +{ + struct slim_controller *ctrl = sb->ctrl; + int i, ret = 0; + + if (!ctrl || !chanh || !prop || !nchan) + return -EINVAL; + mutex_lock(&ctrl->m_ctrl); + for (i = 0; i < nchan; i++) { + u8 chan = (u8)(chanh[i] & 0xFF); + dev_dbg(&ctrl->dev, "define_ch: port:%d, state:%d", chanh[i], + (int)ctrl->chans[chan].state); + if (ctrl->chans[chan].state < SLIM_CH_ALLOCATED || + ctrl->chans[chan].state > SLIM_CH_DEFINED) { + int j; + for (j = i - 1; j >= 0; j--) + ctrl->chans[chan].state = SLIM_CH_ALLOCATED; + ret = -EBUSY; + goto err_define_ch; + } + ctrl->chans[chan].prop = *prop; + ret = slim_nextdefine_ch(sb, chan); + if (ret) { + int j; + for (j = i - 1; j >= 0; j--) { + chan = chanh[j] & 0xFF; + ctrl->chans[chan].nextgrp = 0; + ctrl->chans[chan].state = SLIM_CH_ALLOCATED; + } + goto err_define_ch; + } + if (i < (nchan - 1)) + ctrl->chans[chan].nextgrp = chanh[i + 1]; + if (i == 0) + ctrl->chans[chan].nextgrp |= SLIM_START_GRP; + if (i == (nchan - 1)) + ctrl->chans[chan].nextgrp |= SLIM_END_GRP; + + ctrl->chans[chan].state = SLIM_CH_DEFINED; + } + + if (grp) + *grph = chanh[0]; +err_define_ch: + mutex_unlock(&ctrl->m_ctrl); + return ret; +} +EXPORT_SYMBOL_GPL(slim_define_ch); + +static u32 getsubfrmcoding(u32 *ctrlw, u32 *subfrml, u32 *msgsl) +{ + u32 code = 0; + if (*ctrlw == *subfrml) { + *ctrlw = 8; + *subfrml = 8; + *msgsl = SLIM_SL_PER_SUPERFRAME - SLIM_FRM_SLOTS_PER_SUPERFRAME + - SLIM_GDE_SLOTS_PER_SUPERFRAME; + return 0; + } + if (*subfrml == 6) { + code = 0; + *msgsl = 256; + } else if (*subfrml == 8) { + code = 1; + *msgsl = 192; + } else if (*subfrml == 24) { + code = 2; + *msgsl = 64; + } else { /* 32 */ + code = 3; + *msgsl = 48; + } + + if (*ctrlw < 8) { + if (*ctrlw >= 6) { + *ctrlw = 6; + code |= 0x14; + } else { + if (*ctrlw == 5) + *ctrlw = 4; + code |= (*ctrlw << 2); + } + } else { + code -= 2; + if (*ctrlw >= 24) { + *ctrlw = 24; + code |= 0x1e; + } else if (*ctrlw >= 16) { + *ctrlw = 16; + code |= 0x1c; + } else if (*ctrlw >= 12) { + *ctrlw = 12; + code |= 0x1a; + } else { + *ctrlw = 8; + code |= 0x18; + } + } + + *msgsl = (*msgsl * *ctrlw) - SLIM_FRM_SLOTS_PER_SUPERFRAME - + SLIM_GDE_SLOTS_PER_SUPERFRAME; + return code; +} + +static void shiftsegoffsets(struct slim_controller *ctrl, struct slim_ich **ach, + int sz, u32 shft) +{ + int i; + u32 oldoff; + for (i = 0; i < sz; i++) { + struct slim_ich *slc; + if (ach[i] == NULL) + continue; + slc = ach[i]; + if (slc->state == SLIM_CH_PENDING_REMOVAL) + continue; + oldoff = slc->newoff; + slc->newoff += shft; + /* seg. offset must be <= interval */ + if (slc->newoff >= slc->newintr) + slc->newoff -= slc->newintr; + } +} + +static int slim_sched_chans(struct slim_device *sb, u32 clkgear, + u32 *ctrlw, u32 *subfrml) +{ + int coeff1, coeff3; + enum slim_ch_coeff bias; + struct slim_controller *ctrl = sb->ctrl; + int last1 = ctrl->sched.num_cc1 - 1; + int last3 = ctrl->sched.num_cc3 - 1; + + /* + * Find first channels with coeff 1 & 3 as starting points for + * scheduling + */ + for (coeff3 = 0; coeff3 < ctrl->sched.num_cc3; coeff3++) { + struct slim_ich *slc = ctrl->sched.chc3[coeff3]; + if (slc->state == SLIM_CH_PENDING_REMOVAL) + continue; + else + break; + } + for (coeff1 = 0; coeff1 < ctrl->sched.num_cc1; coeff1++) { + struct slim_ich *slc = ctrl->sched.chc1[coeff1]; + if (slc->state == SLIM_CH_PENDING_REMOVAL) + continue; + else + break; + } + if (coeff3 == ctrl->sched.num_cc3 && coeff1 == ctrl->sched.num_cc1) { + *ctrlw = 8; + *subfrml = 8; + return 0; + } else if (coeff3 == ctrl->sched.num_cc3) + bias = SLIM_COEFF_1; + else + bias = SLIM_COEFF_3; + + /* + * Find last chan in coeff1, 3 list, we will use to know when we + * have done scheduling all coeff1 channels + */ + while (last1 >= 0) { + if (ctrl->sched.chc1[last1] != NULL && + (ctrl->sched.chc1[last1])->state != + SLIM_CH_PENDING_REMOVAL) + break; + last1--; + } + while (last3 >= 0) { + if (ctrl->sched.chc3[last3] != NULL && + (ctrl->sched.chc3[last3])->state != + SLIM_CH_PENDING_REMOVAL) + break; + last3--; + } + + if (bias == SLIM_COEFF_1) { + struct slim_ich *slc1 = ctrl->sched.chc1[coeff1]; + u32 expshft = SLIM_MAX_CLK_GEAR - clkgear; + int curexp, finalexp; + u32 curintr, curmaxsl; + int opensl1[2]; + int maxctrlw1; + + finalexp = (ctrl->sched.chc1[last1])->rootexp; + curexp = (int)expshft - 1; + + curintr = (SLIM_MAX_INTR_COEFF_1 * 2) >> (curexp + 1); + curmaxsl = curintr >> 1; + opensl1[0] = opensl1[1] = curmaxsl; + + while ((coeff1 < ctrl->sched.num_cc1) || (curintr > 24)) { + curintr >>= 1; + curmaxsl >>= 1; + + /* update 4K family open slot records */ + if (opensl1[1] < opensl1[0]) + opensl1[1] -= curmaxsl; + else + opensl1[1] = opensl1[0] - curmaxsl; + opensl1[0] = curmaxsl; + if (opensl1[1] < 0) { + opensl1[0] += opensl1[1]; + opensl1[1] = 0; + } + if (opensl1[0] <= 0) { + dev_dbg(&ctrl->dev, "reconfig failed:%d\n", + __LINE__); + return -EXFULL; + } + curexp++; + /* schedule 4k family channels */ + + while ((coeff1 < ctrl->sched.num_cc1) && (curexp == + (int)(slc1->rootexp + expshft))) { + if (slc1->state == SLIM_CH_PENDING_REMOVAL) { + coeff1++; + slc1 = ctrl->sched.chc1[coeff1]; + continue; + } + if (opensl1[1] >= opensl1[0] || + (finalexp == (int)slc1->rootexp && + curintr <= 24 && + opensl1[0] == curmaxsl)) { + opensl1[1] -= slc1->seglen; + slc1->newoff = curmaxsl + opensl1[1]; + if (opensl1[1] < 0 && + opensl1[0] == curmaxsl) { + opensl1[0] += opensl1[1]; + opensl1[1] = 0; + if (opensl1[0] < 0) { + dev_dbg(&ctrl->dev, + "reconfig failed:%d\n", + __LINE__); + return -EXFULL; + } + } + } else { + if (slc1->seglen > opensl1[0]) { + dev_dbg(&ctrl->dev, + "reconfig failed:%d\n", + __LINE__); + return -EXFULL; + } + slc1->newoff = opensl1[0] - + slc1->seglen; + opensl1[0] = slc1->newoff; + } + slc1->newintr = curintr; + coeff1++; + slc1 = ctrl->sched.chc1[coeff1]; + } + } + if (opensl1[1] > opensl1[0]) { + int temp = opensl1[0]; + opensl1[0] = opensl1[1]; + opensl1[1] = temp; + shiftsegoffsets(ctrl, ctrl->sched.chc1, + ctrl->sched.num_cc1, curmaxsl); + } + /* choose subframe mode to maximize bw */ + maxctrlw1 = opensl1[0]; + if (opensl1[0] == curmaxsl) + maxctrlw1 += opensl1[1]; + if (curintr >= 24) { + *subfrml = 24; + *ctrlw = maxctrlw1; + } else if (curintr == 12) { + if (maxctrlw1 > opensl1[1] * 4) { + *subfrml = 24; + *ctrlw = maxctrlw1; + } else { + *subfrml = 6; + *ctrlw = opensl1[1]; + } + } else { + *subfrml = 6; + *ctrlw = maxctrlw1; + } + } else { + struct slim_ich *slc1; + struct slim_ich *slc3 = ctrl->sched.chc3[coeff3]; + u32 expshft = SLIM_MAX_CLK_GEAR - clkgear; + int curexp, finalexp, exp1; + u32 curintr, curmaxsl; + int opensl3[2]; + int opensl1[6]; + bool opensl1valid = false; + int maxctrlw1, maxctrlw3, i; + finalexp = (ctrl->sched.chc3[last3])->rootexp; + if (last1 >= 0) { + slc1 = ctrl->sched.chc1[coeff1]; + exp1 = (ctrl->sched.chc1[last1])->rootexp; + if (exp1 > finalexp) + finalexp = exp1; + } + curexp = (int)expshft - 1; + + curintr = (SLIM_MAX_INTR_COEFF_3 * 2) >> (curexp + 1); + curmaxsl = curintr >> 1; + opensl3[0] = opensl3[1] = curmaxsl; + + while (coeff1 < ctrl->sched.num_cc1 || + coeff3 < ctrl->sched.num_cc3 || + curintr > 32) { + curintr >>= 1; + curmaxsl >>= 1; + + /* update 12k family open slot records */ + if (opensl3[1] < opensl3[0]) + opensl3[1] -= curmaxsl; + else + opensl3[1] = opensl3[0] - curmaxsl; + opensl3[0] = curmaxsl; + if (opensl3[1] < 0) { + opensl3[0] += opensl3[1]; + opensl3[1] = 0; + } + if (opensl3[0] <= 0) { + dev_dbg(&ctrl->dev, "reconfig failed:%d\n", + __LINE__); + return -EXFULL; + } + curexp++; + + /* schedule 12k family channels */ + while (coeff3 < ctrl->sched.num_cc3 && + curexp == (int)slc3->rootexp + expshft) { + if (slc3->state == SLIM_CH_PENDING_REMOVAL) { + coeff3++; + slc3 = ctrl->sched.chc3[coeff3]; + continue; + } + opensl1valid = false; + if (opensl3[1] >= opensl3[0] || + (finalexp == (int)slc3->rootexp && + curintr <= 32 && + opensl3[0] == curmaxsl && + last1 < 0)) { + opensl3[1] -= slc3->seglen; + slc3->newoff = curmaxsl + opensl3[1]; + if (opensl3[1] < 0 && + opensl3[0] == curmaxsl) { + opensl3[0] += opensl3[1]; + opensl3[1] = 0; + } + if (opensl3[0] < 0) { + dev_dbg(&ctrl->dev, + "reconfig failed:%d\n", + __LINE__); + return -EXFULL; + } + } else { + if (slc3->seglen > opensl3[0]) { + dev_dbg(&ctrl->dev, + "reconfig failed:%d\n", + __LINE__); + return -EXFULL; + } + slc3->newoff = opensl3[0] - + slc3->seglen; + opensl3[0] = slc3->newoff; + } + slc3->newintr = curintr; + coeff3++; + slc3 = ctrl->sched.chc3[coeff3]; + } + /* update 4k openslot records */ + if (opensl1valid == false) { + for (i = 0; i < 3; i++) { + opensl1[i * 2] = opensl3[0]; + opensl1[(i * 2) + 1] = opensl3[1]; + } + } else { + int opensl1p[6]; + memcpy(opensl1p, opensl1, sizeof(opensl1)); + for (i = 0; i < 3; i++) { + if (opensl1p[i] < opensl1p[i + 3]) + opensl1[(i * 2) + 1] = + opensl1p[i]; + else + opensl1[(i * 2) + 1] = + opensl1p[i + 3]; + } + for (i = 0; i < 3; i++) { + opensl1[(i * 2) + 1] -= curmaxsl; + opensl1[i * 2] = curmaxsl; + if (opensl1[(i * 2) + 1] < 0) { + opensl1[i * 2] += + opensl1[(i * 2) + 1]; + opensl1[(i * 2) + 1] = 0; + } + if (opensl1[i * 2] < 0) { + dev_dbg(&ctrl->dev, + "reconfig failed:%d\n", + __LINE__); + return -EXFULL; + } + } + } + /* schedule 4k family channels */ + while (coeff1 < ctrl->sched.num_cc1 && + curexp == (int)slc1->rootexp + expshft) { + /* searchorder effective when opensl valid */ + static const int srcho[] = { 5, 2, 4, 1, 3, 0 }; + int maxopensl = 0; + int maxi = 0; + if (slc1->state == SLIM_CH_PENDING_REMOVAL) { + coeff1++; + slc1 = ctrl->sched.chc1[coeff1]; + continue; + } + opensl1valid = true; + for (i = 0; i < 6; i++) { + if (opensl1[srcho[i]] > maxopensl) { + maxopensl = opensl1[srcho[i]]; + maxi = srcho[i]; + } + } + opensl1[maxi] -= slc1->seglen; + slc1->newoff = (curmaxsl * maxi) + + opensl1[maxi]; + if (opensl1[maxi] < 0) { + if (((maxi & 1) == 1) && + (opensl1[maxi - 1] == curmaxsl)) { + opensl1[maxi - 1] += + opensl1[maxi]; + if (opensl3[0] > + opensl1[maxi - 1]) + opensl3[0] = + opensl1[maxi - 1]; + opensl3[1] = 0; + opensl1[maxi] = 0; + if (opensl1[maxi - 1] < 0) { + dev_dbg(&ctrl->dev, + "reconfig failed:%d\n", + __LINE__); + return -EXFULL; + } + } else { + dev_dbg(&ctrl->dev, + "reconfig failed:%d\n", + __LINE__); + return -EXFULL; + } + } else { + if (opensl3[maxi & 1] > opensl1[maxi]) + opensl3[maxi & 1] = + opensl1[maxi]; + } + slc1->newintr = curintr * 3; + coeff1++; + slc1 = ctrl->sched.chc1[coeff1]; + } + } + /* swap 1st and 2nd bucket if 2nd bucket has more open slots */ + if (opensl3[1] > opensl3[0]) { + int temp = opensl3[0]; + opensl3[0] = opensl3[1]; + opensl3[1] = temp; + temp = opensl1[5]; + opensl1[5] = opensl1[4]; + opensl1[4] = opensl1[3]; + opensl1[3] = opensl1[2]; + opensl1[2] = opensl1[1]; + opensl1[1] = opensl1[0]; + opensl1[0] = temp; + shiftsegoffsets(ctrl, ctrl->sched.chc1, + ctrl->sched.num_cc1, curmaxsl); + shiftsegoffsets(ctrl, ctrl->sched.chc3, + ctrl->sched.num_cc3, curmaxsl); + } + /* subframe mode to maximize BW */ + maxctrlw3 = opensl3[0]; + maxctrlw1 = opensl1[0]; + if (opensl3[0] == curmaxsl) + maxctrlw3 += opensl3[1]; + for (i = 0; i < 5 && opensl1[i] == curmaxsl; i++) + maxctrlw1 += opensl1[i + 1]; + if (curintr >= 32) { + *subfrml = 32; + *ctrlw = maxctrlw3; + } else if (curintr == 16) { + if (maxctrlw3 > (opensl3[1] * 4)) { + *subfrml = 32; + *ctrlw = maxctrlw3; + } else { + *subfrml = 8; + *ctrlw = opensl3[1]; + } + } else { + if ((maxctrlw1 * 8) >= (maxctrlw3 * 24)) { + *subfrml = 24; + *ctrlw = maxctrlw1; + } else { + *subfrml = 8; + *ctrlw = maxctrlw3; + } + } + } + return 0; +} + +#ifdef DEBUG +static int slim_verifychansched(struct slim_controller *ctrl, u32 ctrlw, + u32 subfrml, u32 clkgear) +{ + int sl, i; + int cc1 = 0; + int cc3 = 0; + struct slim_ich *slc = NULL; + if (!ctrl->sched.slots) + return 0; + memset(ctrl->sched.slots, 0, SLIM_SL_PER_SUPERFRAME); + dev_dbg(&ctrl->dev, "Clock gear is:%d\n", clkgear); + for (sl = 0; sl < SLIM_SL_PER_SUPERFRAME; sl += subfrml) { + for (i = 0; i < ctrlw; i++) + ctrl->sched.slots[sl + i] = 33; + } + while (cc1 < ctrl->sched.num_cc1) { + slc = ctrl->sched.chc1[cc1]; + if (slc == NULL) { + dev_err(&ctrl->dev, "SLC1 null in verify: chan%d\n", + cc1); + return -EIO; + } + dev_dbg(&ctrl->dev, "chan:%d, offset:%d, intr:%d, seglen:%d\n", + (slc - ctrl->chans), slc->newoff, + slc->newintr, slc->seglen); + + if (slc->state != SLIM_CH_PENDING_REMOVAL) { + for (sl = slc->newoff; + sl < SLIM_SL_PER_SUPERFRAME; + sl += slc->newintr) { + for (i = 0; i < slc->seglen; i++) { + if (ctrl->sched.slots[sl + i]) + return -EXFULL; + ctrl->sched.slots[sl + i] = cc1 + 1; + } + } + } + cc1++; + } + while (cc3 < ctrl->sched.num_cc3) { + slc = ctrl->sched.chc3[cc3]; + if (slc == NULL) { + dev_err(&ctrl->dev, "SLC3 null in verify: chan%d\n", + cc3); + return -EIO; + } + dev_dbg(&ctrl->dev, "chan:%d, offset:%d, intr:%d, seglen:%d\n", + (slc - ctrl->chans), slc->newoff, + slc->newintr, slc->seglen); + if (slc->state != SLIM_CH_PENDING_REMOVAL) { + for (sl = slc->newoff; + sl < SLIM_SL_PER_SUPERFRAME; + sl += slc->newintr) { + for (i = 0; i < slc->seglen; i++) { + if (ctrl->sched.slots[sl + i]) + return -EXFULL; + ctrl->sched.slots[sl + i] = cc3 + 1; + } + } + } + cc3++; + } + + return 0; +} +#else +static int slim_verifychansched(struct slim_controller *ctrl, u32 ctrlw, + u32 subfrml, u32 clkgear) +{ + return 0; +} +#endif + +static void slim_sort_chan_grp(struct slim_controller *ctrl, + struct slim_ich *slc) +{ + u8 last = (u8)-1; + u8 second = 0; + + for (; last > 0; last--) { + struct slim_ich *slc1 = slc; + struct slim_ich *slc2; + u8 next = (u8)(slc1->nextgrp & 0xFF); + slc2 = &ctrl->chans[next]; + for (second = 1; second <= last && slc2 && + (slc2->state == SLIM_CH_ACTIVE || + slc2->state == SLIM_CH_PENDING_ACTIVE); second++) { + if (slc1->newoff > slc2->newoff) { + u32 temp = slc2->newoff; + slc2->newoff = slc1->newoff; + slc1->newoff = temp; + } + if (slc2->nextgrp & SLIM_END_GRP) { + last = second; + break; + } + slc1 = slc2; + next = (u8)(slc1->nextgrp & 0xFF); + slc2 = &ctrl->chans[next]; + } + if (slc2 == NULL) + last = second - 1; + } +} + + +static int slim_allocbw(struct slim_device *sb, int *subfrmc, int *clkgear) +{ + u32 msgsl = 0; + u32 ctrlw = 0; + u32 subfrml = 0; + int ret = -EIO; + struct slim_controller *ctrl = sb->ctrl; + u32 usedsl = ctrl->sched.usedslots + ctrl->sched.pending_msgsl; + u32 availsl = SLIM_SL_PER_SUPERFRAME - SLIM_FRM_SLOTS_PER_SUPERFRAME - + SLIM_GDE_SLOTS_PER_SUPERFRAME; + *clkgear = SLIM_MAX_CLK_GEAR; + + dev_dbg(&ctrl->dev, "used sl:%u, availlable sl:%u\n", usedsl, availsl); + dev_dbg(&ctrl->dev, "pending:chan sl:%u, :msg sl:%u, clkgear:%u\n", + ctrl->sched.usedslots, + ctrl->sched.pending_msgsl, *clkgear); + while ((usedsl * 2 <= availsl) && (*clkgear > 1)) { + *clkgear -= 1; + usedsl *= 2; + } + + /* + * Try scheduling data channels at current clock gear, if all channels + * can be scheduled, or reserved BW can't be satisfied, increase clock + * gear and try again + */ + for (; *clkgear <= SLIM_MAX_CLK_GEAR; (*clkgear)++) { + ret = slim_sched_chans(sb, *clkgear, &ctrlw, &subfrml); + + if (ret == 0) { + *subfrmc = getsubfrmcoding(&ctrlw, &subfrml, &msgsl); + if ((msgsl >> (SLIM_MAX_CLK_GEAR - *clkgear) < + ctrl->sched.pending_msgsl) && + (*clkgear < SLIM_MAX_CLK_GEAR)) + continue; + else + break; + } + } + if (ret == 0) { + int i; + /* Sort channel-groups */ + for (i = 0; i < ctrl->sched.num_cc1; i++) { + struct slim_ich *slc = ctrl->sched.chc1[i]; + if (slc->state == SLIM_CH_PENDING_REMOVAL) + continue; + if ((slc->nextgrp & SLIM_START_GRP) && + !(slc->nextgrp & SLIM_END_GRP)) { + slim_sort_chan_grp(ctrl, slc); + } + } + for (i = 0; i < ctrl->sched.num_cc3; i++) { + struct slim_ich *slc = ctrl->sched.chc3[i]; + if (slc->state == SLIM_CH_PENDING_REMOVAL) + continue; + if ((slc->nextgrp & SLIM_START_GRP) && + !(slc->nextgrp & SLIM_END_GRP)) { + slim_sort_chan_grp(ctrl, slc); + } + } + + ret = slim_verifychansched(ctrl, ctrlw, subfrml, *clkgear); + } + + return ret; +} + +static void slim_chan_changes(struct slim_device *sb, bool revert) +{ + struct slim_controller *ctrl = sb->ctrl; + while (!list_empty(&sb->mark_define)) { + struct slim_ich *slc; + struct slim_pending_ch *pch = + list_entry(sb->mark_define.next, + struct slim_pending_ch, pending); + slc = &ctrl->chans[pch->chan]; + if (revert) { + u32 sl = slc->seglen << slc->rootexp; + if (slc->coeff == SLIM_COEFF_3) + sl *= 3; + ctrl->sched.usedslots -= sl; + slim_remove_ch(ctrl, slc); + slc->state = SLIM_CH_DEFINED; + } else { + slc->offset = slc->newoff; + slc->interval = slc->newintr; + slc->state = SLIM_CH_ACTIVE; + } + list_del_init(&pch->pending); + kfree(pch); + } + + while (!list_empty(&sb->mark_removal)) { + struct slim_pending_ch *pch = + list_entry(sb->mark_removal.next, + struct slim_pending_ch, pending); + struct slim_ich *slc = &ctrl->chans[pch->chan]; + u32 sl = slc->seglen << slc->rootexp; + if (revert) { + if (slc->coeff == SLIM_COEFF_3) + sl *= 3; + ctrl->sched.usedslots += sl; + slc->state = SLIM_CH_ACTIVE; + } else + slim_remove_ch(ctrl, slc); + list_del_init(&pch->pending); + kfree(pch); + } + + while (!list_empty(&sb->mark_suspend)) { + struct slim_pending_ch *pch = + list_entry(sb->mark_suspend.next, + struct slim_pending_ch, pending); + struct slim_ich *slc = &ctrl->chans[pch->chan]; + if (revert) + slc->state = SLIM_CH_ACTIVE; + list_del_init(&pch->pending); + kfree(pch); + } +} + +/* + * slim_reconfigure_now: Request reconfiguration now. + * @sb: client handle + * This API does what commit flag in other scheduling APIs do. + * -EXFULL is returned if there is no space in TDM to reserve the + * bandwidth. -EBUSY is returned if reconfiguration request is already in + * progress. + */ +int slim_reconfigure_now(struct slim_device *sb) +{ + u8 i; + u8 wbuf[4]; + u32 clkgear, subframe; + u32 curexp; + int ret; + struct slim_controller *ctrl = sb->ctrl; + u32 expshft; + u32 segdist; + struct slim_pending_ch *pch; + + mutex_lock(&ctrl->sched.m_reconf); + mutex_lock(&ctrl->m_ctrl); + ctrl->sched.pending_msgsl += sb->pending_msgsl - sb->cur_msgsl; + list_for_each_entry(pch, &sb->mark_define, pending) { + struct slim_ich *slc = &ctrl->chans[pch->chan]; + slim_add_ch(ctrl, slc); + slc->state = SLIM_CH_PENDING_ACTIVE; + } + + list_for_each_entry(pch, &sb->mark_removal, pending) { + struct slim_ich *slc = &ctrl->chans[pch->chan]; + u32 sl = slc->seglen << slc->rootexp; + if (slc->coeff == SLIM_COEFF_3) + sl *= 3; + ctrl->sched.usedslots -= sl; + slc->state = SLIM_CH_PENDING_REMOVAL; + } + list_for_each_entry(pch, &sb->mark_suspend, pending) { + struct slim_ich *slc = &ctrl->chans[pch->chan]; + slc->state = SLIM_CH_SUSPENDED; + } + mutex_unlock(&ctrl->m_ctrl); + + ret = slim_allocbw(sb, &subframe, &clkgear); + + if (!ret) { + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_BEGIN_RECONFIGURATION, 0, SLIM_MSG_MT_CORE, + NULL, NULL, 0, 3, NULL, 0, NULL); + dev_dbg(&ctrl->dev, "sending begin_reconfig:ret:%d\n", ret); + } + + if (!ret && subframe != ctrl->sched.subfrmcode) { + wbuf[0] = (u8)(subframe & 0xFF); + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_NEXT_SUBFRAME_MODE, 0, SLIM_MSG_MT_CORE, + NULL, (u8 *)&subframe, 1, 4, NULL, 0, NULL); + dev_dbg(&ctrl->dev, "sending subframe:%d,ret:%d\n", + (int)wbuf[0], ret); + } + if (!ret && clkgear != ctrl->clkgear) { + wbuf[0] = (u8)(clkgear & 0xFF); + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_NEXT_CLOCK_GEAR, 0, SLIM_MSG_MT_CORE, + NULL, wbuf, 1, 4, NULL, 0, NULL); + dev_dbg(&ctrl->dev, "sending clkgear:%d,ret:%d\n", + (int)wbuf[0], ret); + } + if (ret) + goto revert_reconfig; + + expshft = SLIM_MAX_CLK_GEAR - clkgear; + /* activate/remove channel */ + list_for_each_entry(pch, &sb->mark_define, pending) { + struct slim_ich *slc = &ctrl->chans[pch->chan]; + /* Define content */ + wbuf[0] = pch->chan; + wbuf[1] = slc->prrate; + wbuf[2] = slc->prop.dataf | (slc->prop.auxf << 4); + wbuf[3] = slc->prop.sampleszbits / SLIM_CL_PER_SL; + dev_dbg(&ctrl->dev, "define content, activate:%x, %x, %x, %x\n", + wbuf[0], wbuf[1], wbuf[2], wbuf[3]); + /* Right now, channel link bit is not supported */ + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_NEXT_DEFINE_CONTENT, 0, + SLIM_MSG_MT_CORE, NULL, (u8 *)&wbuf, 4, 7, + NULL, 0, NULL); + if (ret) + goto revert_reconfig; + + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL, 0, + SLIM_MSG_MT_CORE, NULL, (u8 *)&wbuf, 1, 4, + NULL, 0, NULL); + if (ret) + goto revert_reconfig; + } + + list_for_each_entry(pch, &sb->mark_removal, pending) { + dev_dbg(&ctrl->dev, "remove chan:%x\n", pch->chan); + wbuf[0] = pch->chan; + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_NEXT_REMOVE_CHANNEL, 0, + SLIM_MSG_MT_CORE, NULL, wbuf, 1, 4, + NULL, 0, NULL); + if (ret) + goto revert_reconfig; + } + list_for_each_entry(pch, &sb->mark_suspend, pending) { + dev_dbg(&ctrl->dev, "suspend chan:%x\n", pch->chan); + wbuf[0] = pch->chan; + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL, 0, + SLIM_MSG_MT_CORE, NULL, wbuf, 1, 4, + NULL, 0, NULL); + if (ret) + goto revert_reconfig; + } + + /* Define CC1 channel */ + for (i = 0; i < ctrl->sched.num_cc1; i++) { + struct slim_ich *slc = ctrl->sched.chc1[i]; + if (slc->state == SLIM_CH_PENDING_REMOVAL) + continue; + curexp = slc->rootexp + expshft; + segdist = (slc->newoff << curexp) & 0x1FF; + expshft = SLIM_MAX_CLK_GEAR - clkgear; + + if (slc->state < SLIM_CH_ACTIVE || + slc->newintr != slc->interval || + slc->newoff != slc->offset) { + segdist |= 0x200; + segdist >>= curexp; + segdist |= (slc->newoff << (curexp + 1)) & 0xC00; + wbuf[0] = (u8)(slc - ctrl->chans); + wbuf[1] = (u8)(segdist & 0xFF); + wbuf[2] = (u8)((segdist & 0xF00) >> 8) | + (slc->prop.prot << 4); + wbuf[3] = slc->seglen; + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_NEXT_DEFINE_CHANNEL, 0, + SLIM_MSG_MT_CORE, NULL, (u8 *)wbuf, 4, + 7, NULL, 0, NULL); + if (ret) + goto revert_reconfig; + } + } + + /* Define CC3 channels */ + for (i = 0; i < ctrl->sched.num_cc3; i++) { + struct slim_ich *slc = ctrl->sched.chc3[i]; + if (slc->state == SLIM_CH_PENDING_REMOVAL) + continue; + curexp = slc->rootexp + expshft; + segdist = (slc->newoff << curexp) & 0x1FF; + expshft = SLIM_MAX_CLK_GEAR - clkgear; + + if (slc->state < SLIM_CH_ACTIVE || + slc->newintr != slc->interval || + slc->newoff != slc->offset) { + segdist |= 0x200; + segdist >>= curexp; + segdist |= 0xC00; + wbuf[0] = (u8)(slc - ctrl->chans); + wbuf[1] = (u8)(segdist & 0xFF); + wbuf[2] = (u8)((segdist & 0xF00) >> 8) | + (slc->prop.prot << 4); + wbuf[3] = (u8)(slc->seglen); + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_NEXT_DEFINE_CHANNEL, 0, + SLIM_MSG_MT_CORE, NULL, (u8 *)wbuf, 4, + 7, NULL, 0, NULL); + if (ret) + goto revert_reconfig; + } + } + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST, + SLIM_MSG_MC_RECONFIGURE_NOW, 0, SLIM_MSG_MT_CORE, NULL, + NULL, 0, 3, NULL, 0, NULL); + dev_dbg(&ctrl->dev, "reconfig now:ret:%d\n", ret); + if (!ret) { + mutex_lock(&ctrl->m_ctrl); + ctrl->sched.subfrmcode = subframe; + ctrl->clkgear = clkgear; + ctrl->sched.msgsl = ctrl->sched.pending_msgsl; + sb->cur_msgsl = sb->pending_msgsl; + slim_chan_changes(sb, false); + mutex_unlock(&ctrl->m_ctrl); + mutex_unlock(&ctrl->sched.m_reconf); + return 0; + } + +revert_reconfig: + mutex_lock(&ctrl->m_ctrl); + /* Revert channel changes */ + slim_chan_changes(sb, true); + mutex_unlock(&ctrl->m_ctrl); + mutex_unlock(&ctrl->sched.m_reconf); + return ret; +} +EXPORT_SYMBOL_GPL(slim_reconfigure_now); + +static int add_pending_ch(struct list_head *listh, u8 chan) +{ + struct slim_pending_ch *pch; + pch = kmalloc(sizeof(struct slim_pending_ch), GFP_KERNEL); + if (!pch) + return -ENOMEM; + pch->chan = chan; + list_add_tail(&pch->pending, listh); + return 0; +} + +/* + * slim_control_ch: Channel control API. + * @sb: client handle + * @chanh: group or channel handle to be controlled + * @chctrl: Control command (activate/suspend/remove) + * @commit: flag to indicate whether the control should take effect right-away. + * This API activates, removes or suspends a channel (or group of channels) + * chanh indicates the channel or group handle (returned by the define_ch API). + * Reconfiguration may be time-consuming since it can change all other active + * channel allocations on the bus, change in clock gear used by the slimbus, + * and change in the control space width used for messaging. + * commit makes sure that multiple channels can be activated/deactivated before + * reconfiguration is started. + * -EXFULL is returned if there is no space in TDM to reserve the bandwidth. + * -EISCONN/-ENOTCONN is returned if the channel is already connected or not + * yet defined. + */ +int slim_control_ch(struct slim_device *sb, u16 chanh, + enum slim_ch_control chctrl, bool commit) +{ + struct slim_controller *ctrl = sb->ctrl; + struct slim_ich *slc; + int ret = 0; + /* Get rid of the group flag in MSB if any */ + u8 chan = (u8)(chanh & 0xFF); + mutex_lock(&sb->sldev_reconf); + mutex_lock(&ctrl->m_ctrl); + do { + slc = &ctrl->chans[chan]; + if (slc->state < SLIM_CH_DEFINED) { + ret = -ENOTCONN; + break; + } + if (chctrl == SLIM_CH_SUSPEND) { + ret = add_pending_ch(&sb->mark_suspend, chan); + if (ret) + break; + } else if (chctrl == SLIM_CH_ACTIVATE) { + if (slc->state >= SLIM_CH_ACTIVE) { + ret = -EISCONN; + break; + } + ret = add_pending_ch(&sb->mark_define, chan); + if (ret) + break; + } else { + if (slc->state < SLIM_CH_ACTIVE) { + ret = -ENOTCONN; + break; + } + ret = add_pending_ch(&sb->mark_removal, chan); + if (ret) + break; + } + + if (!(slc->nextgrp & SLIM_END_GRP)) + chan = (u8)(slc->nextgrp & 0xFF); + } while (!(slc->nextgrp & SLIM_END_GRP)); + mutex_unlock(&ctrl->m_ctrl); + if (!ret && commit == true) + ret = slim_reconfigure_now(sb); + mutex_unlock(&sb->sldev_reconf); + return ret; +} +EXPORT_SYMBOL_GPL(slim_control_ch); + +/* + * slim_reservemsg_bw: Request to reserve bandwidth for messages. + * @sb: client handle + * @bw_bps: message bandwidth in bits per second to be requested + * @commit: indicates whether the reconfiguration needs to be acted upon. + * This API call can be grouped with slim_control_ch API call with only one of + * the APIs specifying the commit flag to avoid reconfiguration being called too + * frequently. -EXFULL is returned if there is no space in TDM to reserve the + * bandwidth. -EBUSY is returned if reconfiguration is requested, but a request + * is already in progress. + */ +int slim_reservemsg_bw(struct slim_device *sb, u32 bw_bps, bool commit) +{ + struct slim_controller *ctrl = sb->ctrl; + int ret = 0; + int sl; + mutex_lock(&sb->sldev_reconf); + if ((bw_bps >> 3) >= ctrl->a_framer->rootfreq) + sl = SLIM_SL_PER_SUPERFRAME; + else { + sl = (bw_bps * (SLIM_CL_PER_SUPERFRAME_DIV8/SLIM_CL_PER_SL/2) + + (ctrl->a_framer->rootfreq/2 - 1)) / + (ctrl->a_framer->rootfreq/2); + } + dev_dbg(&ctrl->dev, "request:bw:%d, slots:%d, current:%d\n", bw_bps, sl, + sb->cur_msgsl); + sb->pending_msgsl = sl; + if (commit == true) + ret = slim_reconfigure_now(sb); + mutex_unlock(&sb->sldev_reconf); + return ret; +} +EXPORT_SYMBOL_GPL(slim_reservemsg_bw); + + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); +MODULE_DESCRIPTION("Slimbus module"); +MODULE_ALIAS("platform:slimbus"); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index de35c3ad8a6..7ad57d6ded9 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -310,6 +310,11 @@ config SPI_PXA2XX config SPI_PXA2XX_PCI def_bool SPI_PXA2XX && X86_32 && PCI +config SPI_QSD + tristate "Qualcomm MSM SPI support" + default n + depends on ARCH_MSM_SCORPION && !MSM_SMP + config SPI_S3C24XX tristate "Samsung S3C24XX series SPI" depends on ARCH_S3C2410 && EXPERIMENTAL @@ -420,6 +425,26 @@ config SPI_NUC900 help SPI driver for Nuvoton NUC900 series ARM SoCs +config SPI_QSD + tristate "Qualcomm MSM SPI support" + default n + depends on ARCH_MSM_SCORPION && !MSM_SMP + help + Support for Serial Peripheral Interface for Qualcomm MSM + + This driver can also be built as a module. If so, the module + will be called spi_qsd. + +config SPI_QUP + tristate "Qualcomm MSM SPI QUPe Support" + depends on ARCH_MSM && !SPI_QSD + default n + help + Support for Serial Peripheral Interface for Qualcomm Universal + Peripheral. + + This driver can also be built as a module. If so, the module + will be called spi_qsd. # # Add new SPI master controllers in alphabetical order above this line # diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 0f8c69b6b19..560ef93a256 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -55,6 +55,8 @@ obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o obj-$(CONFIG_SPI_SH_MSIOF) += spi_sh_msiof.o obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o obj-$(CONFIG_SPI_NUC900) += spi_nuc900.o +obj-$(CONFIG_SPI_QSD) += spi_qsd.o +obj-$(CONFIG_SPI_QUP) += spi_qsd.o # special build for s3c24xx spi driver with fiq support spi_s3c24xx_hw-y := spi_s3c24xx.o diff --git a/drivers/spi/spi_qsd.c b/drivers/spi/spi_qsd.c new file mode 100644 index 00000000000..28d3955bcbf --- /dev/null +++ b/drivers/spi/spi_qsd.c @@ -0,0 +1,2539 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * SPI driver for Qualcomm MSM platforms + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPI_DRV_NAME "spi_qsd" +#if defined(CONFIG_SPI_QSD) || defined(CONFIG_SPI_QSD_MODULE) + +#define QSD_REG(x) (x) +#define QUP_REG(x) + +#define SPI_FIFO_WORD_CNT 0x0048 + +#elif defined(CONFIG_SPI_QUP) || defined(CONFIG_SPI_QUP_MODULE) + +#define QSD_REG(x) +#define QUP_REG(x) (x) + +#define QUP_CONFIG 0x0000 /* N & NO_INPUT/NO_OUPUT bits */ +#define QUP_ERROR_FLAGS 0x0308 +#define QUP_ERROR_FLAGS_EN 0x030C +#define QUP_ERR_MASK 0x3 +#define SPI_OUTPUT_FIFO_WORD_CNT 0x010C +#define SPI_INPUT_FIFO_WORD_CNT 0x0214 +#define QUP_MX_WRITE_COUNT 0x0150 +#define QUP_MX_WRITE_CNT_CURRENT 0x0154 + +#define QUP_CONFIG_SPI_MODE 0x0100 + +#define GSBI_CTRL_REG 0x0 +#define GSBI_SPI_CONFIG 0x30 +#endif + +#define SPI_CONFIG QSD_REG(0x0000) QUP_REG(0x0300) +#define SPI_IO_CONTROL QSD_REG(0x0004) QUP_REG(0x0304) +#define SPI_IO_MODES QSD_REG(0x0008) QUP_REG(0x0008) +#define SPI_SW_RESET QSD_REG(0x000C) QUP_REG(0x000C) +#define SPI_TIME_OUT QSD_REG(0x0010) QUP_REG(0x0010) +#define SPI_TIME_OUT_CURRENT QSD_REG(0x0014) QUP_REG(0x0014) +#define SPI_MX_OUTPUT_COUNT QSD_REG(0x0018) QUP_REG(0x0100) +#define SPI_MX_OUTPUT_CNT_CURRENT QSD_REG(0x001C) QUP_REG(0x0104) +#define SPI_MX_INPUT_COUNT QSD_REG(0x0020) QUP_REG(0x0200) +#define SPI_MX_INPUT_CNT_CURRENT QSD_REG(0x0024) QUP_REG(0x0204) +#define SPI_MX_READ_COUNT QSD_REG(0x0028) QUP_REG(0x0208) +#define SPI_MX_READ_CNT_CURRENT QSD_REG(0x002C) QUP_REG(0x020C) +#define SPI_OPERATIONAL QSD_REG(0x0030) QUP_REG(0x0018) +#define SPI_ERROR_FLAGS QSD_REG(0x0034) QUP_REG(0x001C) +#define SPI_ERROR_FLAGS_EN QSD_REG(0x0038) QUP_REG(0x0020) +#define SPI_DEASSERT_WAIT QSD_REG(0x003C) QUP_REG(0x0310) +#define SPI_OUTPUT_DEBUG QSD_REG(0x0040) QUP_REG(0x0108) +#define SPI_INPUT_DEBUG QSD_REG(0x0044) QUP_REG(0x0210) +#define SPI_TEST_CTRL QSD_REG(0x004C) QUP_REG(0x0024) +#define SPI_OUTPUT_FIFO QSD_REG(0x0100) QUP_REG(0x0110) +#define SPI_INPUT_FIFO QSD_REG(0x0200) QUP_REG(0x0218) +#define SPI_STATE QSD_REG(SPI_OPERATIONAL) QUP_REG(0x0004) + +/* SPI_CONFIG fields */ +#define SPI_CFG_INPUT_FIRST 0x00000200 +#define SPI_NO_INPUT 0x00000080 +#define SPI_NO_OUTPUT 0x00000040 +#define SPI_CFG_LOOPBACK 0x00000100 +#define SPI_CFG_N 0x0000001F + +/* SPI_IO_CONTROL fields */ +#define SPI_IO_C_CLK_IDLE_HIGH 0x00000400 +#define SPI_IO_C_MX_CS_MODE 0x00000100 +#define SPI_IO_C_CS_N_POLARITY 0x000000F0 +#define SPI_IO_C_CS_N_POLARITY_0 0x00000010 +#define SPI_IO_C_CS_SELECT 0x0000000C +#define SPI_IO_C_TRISTATE_CS 0x00000002 +#define SPI_IO_C_NO_TRI_STATE 0x00000001 + +/* SPI_IO_MODES fields */ +#define SPI_IO_M_OUTPUT_BIT_SHIFT_EN QSD_REG(0x00004000) QUP_REG(0x00010000) +#define SPI_IO_M_PACK_EN QSD_REG(0x00002000) QUP_REG(0x00008000) +#define SPI_IO_M_UNPACK_EN QSD_REG(0x00001000) QUP_REG(0x00004000) +#define SPI_IO_M_INPUT_MODE QSD_REG(0x00000C00) QUP_REG(0x00003000) +#define SPI_IO_M_OUTPUT_MODE QSD_REG(0x00000300) QUP_REG(0x00000C00) +#define SPI_IO_M_INPUT_FIFO_SIZE QSD_REG(0x000000C0) QUP_REG(0x00000380) +#define SPI_IO_M_INPUT_BLOCK_SIZE QSD_REG(0x00000030) QUP_REG(0x00000060) +#define SPI_IO_M_OUTPUT_FIFO_SIZE QSD_REG(0x0000000C) QUP_REG(0x0000001C) +#define SPI_IO_M_OUTPUT_BLOCK_SIZE QSD_REG(0x00000003) QUP_REG(0x00000003) + +#define INPUT_BLOCK_SZ_SHIFT QSD_REG(4) QUP_REG(5) +#define INPUT_FIFO_SZ_SHIFT QSD_REG(6) QUP_REG(7) +#define OUTPUT_BLOCK_SZ_SHIFT QSD_REG(0) QUP_REG(0) +#define OUTPUT_FIFO_SZ_SHIFT QSD_REG(2) QUP_REG(2) +#define OUTPUT_MODE_SHIFT QSD_REG(8) QUP_REG(10) +#define INPUT_MODE_SHIFT QSD_REG(10) QUP_REG(12) + +/* SPI_OPERATIONAL fields */ +#define SPI_OP_MAX_INPUT_DONE_FLAG 0x00000800 +#define SPI_OP_MAX_OUTPUT_DONE_FLAG 0x00000400 +#define SPI_OP_INPUT_SERVICE_FLAG 0x00000200 +#define SPI_OP_OUTPUT_SERVICE_FLAG 0x00000100 +#define SPI_OP_INPUT_FIFO_FULL 0x00000080 +#define SPI_OP_OUTPUT_FIFO_FULL 0x00000040 +#define SPI_OP_IP_FIFO_NOT_EMPTY 0x00000020 +#define SPI_OP_OP_FIFO_NOT_EMPTY 0x00000010 +#define SPI_OP_STATE_VALID 0x00000004 +#define SPI_OP_STATE 0x00000003 + +#define SPI_OP_STATE_CLEAR_BITS 0x2 +enum msm_spi_state { + SPI_OP_STATE_RESET = 0x00000000, + SPI_OP_STATE_RUN = 0x00000001, + SPI_OP_STATE_PAUSE = 0x00000003, +}; + +/* SPI_ERROR_FLAGS fields */ +#define SPI_ERR_OUTPUT_OVER_RUN_ERR 0x00000020 +#define SPI_ERR_INPUT_UNDER_RUN_ERR 0x00000010 +#define SPI_ERR_OUTPUT_UNDER_RUN_ERR 0x00000008 +#define SPI_ERR_INPUT_OVER_RUN_ERR 0x00000004 +#define SPI_ERR_CLK_OVER_RUN_ERR 0x00000002 +#define SPI_ERR_CLK_UNDER_RUN_ERR 0x00000001 + +/* We don't allow transactions larger than 4K-64 or 64K-64 due to + mx_input/output_cnt register size */ +#define SPI_MAX_TRANSFERS QSD_REG(0xFC0) QUP_REG(0xFC0) +#define SPI_MAX_LEN (SPI_MAX_TRANSFERS * dd->bytes_per_word) + +#define SPI_NUM_CHIPSELECTS 4 +#define SPI_SUPPORTED_MODES (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP) + +#define SPI_DELAY_THRESHOLD 1 +/* Default timeout is 10 milliseconds */ +#define SPI_DEFAULT_TIMEOUT 10 +/* 250 microseconds */ +#define SPI_TRYLOCK_DELAY 250 + +/* Data Mover burst size */ +#define DM_BURST_SIZE 16 +/* Data Mover commands should be aligned to 64 bit(8 bytes) */ +#define DM_BYTE_ALIGN 8 + +static char const * const spi_rsrcs[] = { + "spi_clk", + "spi_cs", + "spi_miso", + "spi_mosi" +}; + +enum msm_spi_mode { + SPI_FIFO_MODE = 0x0, /* 00 */ + SPI_BLOCK_MODE = 0x1, /* 01 */ + SPI_DMOV_MODE = 0x2, /* 10 */ + SPI_MODE_NONE = 0xFF, /* invalid value */ +}; + +/* Structures for Data Mover */ +struct spi_dmov_cmd { + dmov_box box; /* data aligned to max(dm_burst_size, block_size) + (<= fifo_size) */ + dmov_s single_pad; /* data unaligned to max(dm_burst_size, block_size) + padded to fit */ + dma_addr_t cmd_ptr; +}; + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.3"); +MODULE_ALIAS("platform:"SPI_DRV_NAME); + +static struct pm_qos_request_list qos_req_list; + +#ifdef CONFIG_DEBUG_FS +/* Used to create debugfs entries */ +static const struct { + const char *name; + mode_t mode; + int offset; +} debugfs_spi_regs[] = { + {"config", S_IRUGO | S_IWUSR, SPI_CONFIG}, + {"io_control", S_IRUGO | S_IWUSR, SPI_IO_CONTROL}, + {"io_modes", S_IRUGO | S_IWUSR, SPI_IO_MODES}, + {"sw_reset", S_IWUSR, SPI_SW_RESET}, + {"time_out", S_IRUGO | S_IWUSR, SPI_TIME_OUT}, + {"time_out_current", S_IRUGO, SPI_TIME_OUT_CURRENT}, + {"mx_output_count", S_IRUGO | S_IWUSR, SPI_MX_OUTPUT_COUNT}, + {"mx_output_cnt_current", S_IRUGO, SPI_MX_OUTPUT_CNT_CURRENT}, + {"mx_input_count", S_IRUGO | S_IWUSR, SPI_MX_INPUT_COUNT}, + {"mx_input_cnt_current", S_IRUGO, SPI_MX_INPUT_CNT_CURRENT}, + {"mx_read_count", S_IRUGO | S_IWUSR, SPI_MX_READ_COUNT}, + {"mx_read_cnt_current", S_IRUGO, SPI_MX_READ_CNT_CURRENT}, + {"operational", S_IRUGO | S_IWUSR, SPI_OPERATIONAL}, + {"error_flags", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS}, + {"error_flags_en", S_IRUGO | S_IWUSR, SPI_ERROR_FLAGS_EN}, + {"deassert_wait", S_IRUGO | S_IWUSR, SPI_DEASSERT_WAIT}, + {"output_debug", S_IRUGO, SPI_OUTPUT_DEBUG}, + {"input_debug", S_IRUGO, SPI_INPUT_DEBUG}, + {"test_ctrl", S_IRUGO | S_IWUSR, SPI_TEST_CTRL}, + {"output_fifo", S_IWUSR, SPI_OUTPUT_FIFO}, + {"input_fifo" , S_IRUSR, SPI_INPUT_FIFO}, + {"spi_state", S_IRUGO | S_IWUSR, SPI_STATE}, +#if defined(CONFIG_SPI_QSD) || defined(CONFIG_SPI_QSD_MODULE) + {"fifo_word_cnt", S_IRUGO, SPI_FIFO_WORD_CNT}, +#elif defined(CONFIG_SPI_QUP) || defined(CONFIG_SPI_QUP_MODULE) + {"qup_config", S_IRUGO | S_IWUSR, QUP_CONFIG}, + {"qup_error_flags", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS}, + {"qup_error_flags_en", S_IRUGO | S_IWUSR, QUP_ERROR_FLAGS_EN}, + {"mx_write_cnt", S_IRUGO | S_IWUSR, QUP_MX_WRITE_COUNT}, + {"mx_write_cnt_current", S_IRUGO, QUP_MX_WRITE_CNT_CURRENT}, + {"output_fifo_word_cnt", S_IRUGO, SPI_OUTPUT_FIFO_WORD_CNT}, + {"input_fifo_word_cnt", S_IRUGO, SPI_INPUT_FIFO_WORD_CNT}, +#endif +}; +#endif + +struct msm_spi { + u8 *read_buf; + const u8 *write_buf; + void __iomem *base; + void __iomem *gsbi_base; + struct device *dev; + spinlock_t queue_lock; + struct mutex core_lock; + struct list_head queue; + struct workqueue_struct *workqueue; + struct work_struct work_data; + struct spi_message *cur_msg; + struct spi_transfer *cur_transfer; + struct completion transfer_complete; + struct clk *clk; + struct clk *pclk; + unsigned long mem_phys_addr; + size_t mem_size; + unsigned long gsbi_mem_phys_addr; + size_t gsbi_mem_size; + int input_fifo_size; + int output_fifo_size; + u32 rx_bytes_remaining; + u32 tx_bytes_remaining; + u32 clock_speed; + u32 irq_in; + int read_xfr_cnt; + int write_xfr_cnt; + int write_len; + int read_len; +#if defined(CONFIG_SPI_QSD) || defined(CONFIG_SPI_QSD_MODULE) + u32 irq_out; + u32 irq_err; +#endif + int bytes_per_word; + bool suspended; + bool transfer_pending; + wait_queue_head_t continue_suspend; + /* DMA data */ + enum msm_spi_mode mode; + bool use_dma; + int tx_dma_chan; + int tx_dma_crci; + int rx_dma_chan; + int rx_dma_crci; + /* Data Mover Commands */ + struct spi_dmov_cmd *tx_dmov_cmd; + struct spi_dmov_cmd *rx_dmov_cmd; + /* Physical address of the tx dmov box command */ + dma_addr_t tx_dmov_cmd_dma; + dma_addr_t rx_dmov_cmd_dma; + struct msm_dmov_cmd tx_hdr; + struct msm_dmov_cmd rx_hdr; + int input_block_size; + int output_block_size; + int burst_size; + atomic_t rx_irq_called; + /* Used to pad messages unaligned to block size */ + u8 *tx_padding; + dma_addr_t tx_padding_dma; + u8 *rx_padding; + dma_addr_t rx_padding_dma; + u32 unaligned_len; + /* DMA statistics */ + int stat_dmov_tx_err; + int stat_dmov_rx_err; + int stat_rx; + int stat_dmov_rx; + int stat_tx; + int stat_dmov_tx; +#ifdef CONFIG_DEBUG_FS + struct dentry *dent_spi; + struct dentry *debugfs_spi_regs[ARRAY_SIZE(debugfs_spi_regs)]; +#endif + struct msm_spi_platform_data *pdata; /* Platform data */ + /* Remote Spinlock Data */ + bool use_rlock; + remote_mutex_t r_lock; + uint32_t pm_lat; + /* When set indicates multiple transfers in a single message */ + bool multi_xfr; + bool done; + u32 cur_msg_len; + /* Used in FIFO mode to keep track of the transfer being processed */ + struct spi_transfer *cur_tx_transfer; + struct spi_transfer *cur_rx_transfer; + /* Temporary buffer used for WR-WR or WR-RD transfers */ + u8 *temp_buf; + /* GPIO pin numbers for SPI clk, cs, miso and mosi */ + int spi_gpios[ARRAY_SIZE(spi_rsrcs)]; +}; + +/* Forward declaration */ +static irqreturn_t msm_spi_input_irq(int irq, void *dev_id); +static irqreturn_t msm_spi_output_irq(int irq, void *dev_id); +static irqreturn_t msm_spi_error_irq(int irq, void *dev_id); +static inline int msm_spi_set_state(struct msm_spi *dd, + enum msm_spi_state state); +static void msm_spi_write_word_to_fifo(struct msm_spi *dd); +static inline void msm_spi_write_rmn_to_fifo(struct msm_spi *dd); + +#if defined(CONFIG_SPI_QSD) || defined(CONFIG_SPI_QSD_MODULE) +/* Interrupt Handling */ +static inline int msm_spi_get_irq_data(struct msm_spi *dd, + struct platform_device *pdev) +{ + dd->irq_in = platform_get_irq_byname(pdev, "spi_irq_in"); + dd->irq_out = platform_get_irq_byname(pdev, "spi_irq_out"); + dd->irq_err = platform_get_irq_byname(pdev, "spi_irq_err"); + if ((dd->irq_in < 0) || (dd->irq_out < 0) || (dd->irq_err < 0)) + return -1; + return 0; +} + +static inline int msm_spi_get_gsbi_resource(struct msm_spi *dd, + struct platform_device *pdev) +{ + return 0; +} + +static inline int msm_spi_request_gsbi(struct msm_spi *dd) { return 0; } +static inline void msm_spi_release_gsbi(struct msm_spi *dd) {} +static inline void msm_spi_init_gsbi(struct msm_spi *dd) {} + +static inline void msm_spi_disable_irqs(struct msm_spi *dd) +{ + disable_irq(dd->irq_in); + disable_irq(dd->irq_out); + disable_irq(dd->irq_err); +} + +static inline void msm_spi_enable_irqs(struct msm_spi *dd) +{ + enable_irq(dd->irq_in); + enable_irq(dd->irq_out); + enable_irq(dd->irq_err); +} + +static inline int msm_spi_request_irq(struct msm_spi *dd, + const char *name, + struct spi_master *master) +{ + int rc; + rc = request_irq(dd->irq_in, msm_spi_input_irq, IRQF_TRIGGER_RISING, + name, dd); + if (rc) + goto error_irq1; + rc = request_irq(dd->irq_out, msm_spi_output_irq, IRQF_TRIGGER_RISING, + name, dd); + if (rc) + goto error_irq2; + rc = request_irq(dd->irq_err, msm_spi_error_irq, IRQF_TRIGGER_RISING, + name, master); + if (rc) + goto error_irq3; + return 0; + +error_irq3: + free_irq(dd->irq_out, dd); +error_irq2: + free_irq(dd->irq_in, dd); +error_irq1: + return rc; +} + +static inline void msm_spi_free_irq(struct msm_spi *dd, + struct spi_master *master) +{ + free_irq(dd->irq_err, master); + free_irq(dd->irq_out, dd); + free_irq(dd->irq_in, dd); +} + +static inline void msm_spi_get_clk_err(struct msm_spi *dd, u32 *spi_err) {} +static inline void msm_spi_ack_clk_err(struct msm_spi *dd) {} +static inline void msm_spi_set_qup_config(struct msm_spi *dd, int bpw) {} + +static inline int msm_spi_prepare_for_write(struct msm_spi *dd) { return 0; } +static inline void msm_spi_start_write(struct msm_spi *dd, u32 read_count) +{ + msm_spi_write_word_to_fifo(dd); +} +static inline void msm_spi_set_write_count(struct msm_spi *dd, int val) {} + +static inline void msm_spi_complete(struct msm_spi *dd) +{ + complete(&dd->transfer_complete); +} + +static inline void msm_spi_enable_error_flags(struct msm_spi *dd) +{ + writel_relaxed(0x0000007B, dd->base + SPI_ERROR_FLAGS_EN); +} + +static inline void msm_spi_clear_error_flags(struct msm_spi *dd) +{ + writel_relaxed(0x0000007F, dd->base + SPI_ERROR_FLAGS); +} + +#elif defined(CONFIG_SPI_QUP) || defined(CONFIG_SPI_QUP_MODULE) + +/* Interrupt Handling */ +/* In QUP the same interrupt line is used for intput, output and error*/ +static inline int msm_spi_get_irq_data(struct msm_spi *dd, + struct platform_device *pdev) +{ + dd->irq_in = platform_get_irq_byname(pdev, "spi_irq_in"); + if (dd->irq_in < 0) + return -1; + return 0; +} + +static inline int msm_spi_get_gsbi_resource(struct msm_spi *dd, + struct platform_device *pdev) +{ + struct resource *resource; + + resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "gsbi_base"); + if (!resource) + return -ENXIO; + dd->gsbi_mem_phys_addr = resource->start; + dd->gsbi_mem_size = resource_size(resource); + + return 0; +} + +static inline void msm_spi_release_gsbi(struct msm_spi *dd) +{ + iounmap(dd->gsbi_base); + release_mem_region(dd->gsbi_mem_phys_addr, dd->gsbi_mem_size); +} + +static inline int msm_spi_request_gsbi(struct msm_spi *dd) +{ + if (!request_mem_region(dd->gsbi_mem_phys_addr, dd->gsbi_mem_size, + SPI_DRV_NAME)) { + return -ENXIO; + } + dd->gsbi_base = ioremap(dd->gsbi_mem_phys_addr, dd->gsbi_mem_size); + if (!dd->gsbi_base) { + release_mem_region(dd->gsbi_mem_phys_addr, dd->gsbi_mem_size); + return -ENXIO; + } + return 0; +} + +static inline void msm_spi_init_gsbi(struct msm_spi *dd) +{ + /* Set GSBI to SPI mode, and CRCI_MUX_CTRL to SPI CRCI ports */ + writel_relaxed(GSBI_SPI_CONFIG, dd->gsbi_base + GSBI_CTRL_REG); +} + +/* Figure which irq occured and call the relevant functions */ +static irqreturn_t msm_spi_qup_irq(int irq, void *dev_id) +{ + u32 op, ret = IRQ_NONE; + struct msm_spi *dd = dev_id; + + if (readl_relaxed(dd->base + SPI_ERROR_FLAGS) || + readl_relaxed(dd->base + QUP_ERROR_FLAGS)) { + struct spi_master *master = dev_get_drvdata(dd->dev); + ret |= msm_spi_error_irq(irq, master); + } + + op = readl_relaxed(dd->base + SPI_OPERATIONAL); + if (op & SPI_OP_INPUT_SERVICE_FLAG) { + writel_relaxed(SPI_OP_INPUT_SERVICE_FLAG, + dd->base + SPI_OPERATIONAL); + /* + * Ensure service flag was cleared before further + * processing of interrupt. + */ + mb(); + ret |= msm_spi_input_irq(irq, dev_id); + } + + if (op & SPI_OP_OUTPUT_SERVICE_FLAG) { + writel_relaxed(SPI_OP_OUTPUT_SERVICE_FLAG, + dd->base + SPI_OPERATIONAL); + /* + * Ensure service flag was cleared before further + * processing of interrupt. + */ + mb(); + ret |= msm_spi_output_irq(irq, dev_id); + } + + if (dd->done) { + complete(&dd->transfer_complete); + dd->done = 0; + } + return ret; +} + +static inline int msm_spi_request_irq(struct msm_spi *dd, + const char *name, + struct spi_master *master) +{ + return request_irq(dd->irq_in, msm_spi_qup_irq, IRQF_TRIGGER_HIGH, + name, dd); +} + +static inline void msm_spi_free_irq(struct msm_spi *dd, + struct spi_master *master) +{ + free_irq(dd->irq_in, dd); +} + +static inline void msm_spi_free_output_irq(struct msm_spi *dd) { } +static inline void msm_spi_free_error_irq(struct msm_spi *dd, + struct spi_master *master) { } + +static inline void msm_spi_disable_irqs(struct msm_spi *dd) +{ + disable_irq(dd->irq_in); +} + +static inline void msm_spi_enable_irqs(struct msm_spi *dd) +{ + enable_irq(dd->irq_in); +} + +static inline void msm_spi_get_clk_err(struct msm_spi *dd, u32 *spi_err) +{ + *spi_err = readl_relaxed(dd->base + QUP_ERROR_FLAGS); +} + +static inline void msm_spi_ack_clk_err(struct msm_spi *dd) +{ + writel_relaxed(QUP_ERR_MASK, dd->base + QUP_ERROR_FLAGS); +} + +static inline void msm_spi_add_configs(struct msm_spi *dd, u32 *config, int n); + +/* QUP has no_input, no_output, and N bits at QUP_CONFIG */ +static inline void msm_spi_set_qup_config(struct msm_spi *dd, int bpw) +{ + u32 qup_config = readl_relaxed(dd->base + QUP_CONFIG); + + msm_spi_add_configs(dd, &qup_config, bpw-1); + writel_relaxed(qup_config | QUP_CONFIG_SPI_MODE, + dd->base + QUP_CONFIG); +} + +static inline int msm_spi_prepare_for_write(struct msm_spi *dd) +{ + if (msm_spi_set_state(dd, SPI_OP_STATE_RUN)) + return -1; + if (msm_spi_set_state(dd, SPI_OP_STATE_PAUSE)) + return -1; + return 0; +} + +static inline void msm_spi_start_write(struct msm_spi *dd, u32 read_count) +{ + if (read_count <= dd->input_fifo_size) + msm_spi_write_rmn_to_fifo(dd); + else + msm_spi_write_word_to_fifo(dd); +} + +static inline void msm_spi_set_write_count(struct msm_spi *dd, int val) +{ + writel_relaxed(val, dd->base + QUP_MX_WRITE_COUNT); +} + +static inline void msm_spi_complete(struct msm_spi *dd) +{ + dd->done = 1; +} + +static inline void msm_spi_enable_error_flags(struct msm_spi *dd) +{ + writel_relaxed(0x00000078, dd->base + SPI_ERROR_FLAGS_EN); +} + +static inline void msm_spi_clear_error_flags(struct msm_spi *dd) +{ + writel_relaxed(0x0000007C, dd->base + SPI_ERROR_FLAGS); +} + +#endif + +static inline int msm_spi_request_gpios(struct msm_spi *dd) +{ + int i; + int result = 0; + + for (i = 0; i < ARRAY_SIZE(spi_rsrcs); ++i) { + if (dd->spi_gpios[i] >= 0) { + result = gpio_request(dd->spi_gpios[i], spi_rsrcs[i]); + if (result) { + pr_err("%s: gpio_request for pin %d failed\ + with error%d\n", __func__, + dd->spi_gpios[i], result); + goto error; + } + } + } + return 0; + +error: + for (; --i >= 0;) { + if (dd->spi_gpios[i] >= 0) + gpio_free(dd->spi_gpios[i]); + } + return result; +} + +static inline void msm_spi_free_gpios(struct msm_spi *dd) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(spi_rsrcs); ++i) { + if (dd->spi_gpios[i] >= 0) + gpio_free(dd->spi_gpios[i]); + } +} + +static void msm_spi_clock_set(struct msm_spi *dd, int speed) +{ + int rc; + + rc = clk_set_rate(dd->clk, speed); + if (!rc) + dd->clock_speed = speed; +} + +static int msm_spi_calculate_size(int *fifo_size, + int *block_size, + int block, + int mult) +{ + int words; + + switch (block) { + case 0: + words = 1; /* 4 bytes */ + break; + case 1: + words = 4; /* 16 bytes */ + break; + case 2: + words = 8; /* 32 bytes */ + break; + default: + return -1; + } + switch (mult) { + case 0: + *fifo_size = words * 2; + break; + case 1: + *fifo_size = words * 4; + break; + case 2: + *fifo_size = words * 8; + break; + case 3: + *fifo_size = words * 16; + break; + default: + return -1; + } + *block_size = words * sizeof(u32); /* in bytes */ + return 0; +} + +static void get_next_transfer(struct msm_spi *dd) +{ + struct spi_transfer *t = dd->cur_transfer; + + if (t->transfer_list.next != &dd->cur_msg->transfers) { + dd->cur_transfer = list_entry(t->transfer_list.next, + struct spi_transfer, + transfer_list); + dd->write_buf = dd->cur_transfer->tx_buf; + dd->read_buf = dd->cur_transfer->rx_buf; + } +} + +static void __init msm_spi_calculate_fifo_size(struct msm_spi *dd) +{ + u32 spi_iom; + int block; + int mult; + + spi_iom = readl_relaxed(dd->base + SPI_IO_MODES); + + block = (spi_iom & SPI_IO_M_INPUT_BLOCK_SIZE) >> INPUT_BLOCK_SZ_SHIFT; + mult = (spi_iom & SPI_IO_M_INPUT_FIFO_SIZE) >> INPUT_FIFO_SZ_SHIFT; + if (msm_spi_calculate_size(&dd->input_fifo_size, &dd->input_block_size, + block, mult)) { + goto fifo_size_err; + } + + block = (spi_iom & SPI_IO_M_OUTPUT_BLOCK_SIZE) >> OUTPUT_BLOCK_SZ_SHIFT; + mult = (spi_iom & SPI_IO_M_OUTPUT_FIFO_SIZE) >> OUTPUT_FIFO_SZ_SHIFT; + if (msm_spi_calculate_size(&dd->output_fifo_size, + &dd->output_block_size, block, mult)) { + goto fifo_size_err; + } + /* DM mode is not available for this block size */ + if (dd->input_block_size == 4 || dd->output_block_size == 4) + dd->use_dma = 0; + + /* DM mode is currently unsupported for different block sizes */ + if (dd->input_block_size != dd->output_block_size) + dd->use_dma = 0; + + if (dd->use_dma) + dd->burst_size = max(dd->input_block_size, DM_BURST_SIZE); + + return; + +fifo_size_err: + dd->use_dma = 0; + printk(KERN_WARNING "%s: invalid FIFO size, SPI_IO_MODES=0x%x\n", + __func__, spi_iom); + return; +} + +static void msm_spi_read_word_from_fifo(struct msm_spi *dd) +{ + u32 data_in; + int i; + int shift; + + data_in = readl_relaxed(dd->base + SPI_INPUT_FIFO); + if (dd->read_buf) { + for (i = 0; (i < dd->bytes_per_word) && + dd->rx_bytes_remaining; i++) { + /* The data format depends on bytes_per_word: + 4 bytes: 0x12345678 + 3 bytes: 0x00123456 + 2 bytes: 0x00001234 + 1 byte : 0x00000012 + */ + shift = 8 * (dd->bytes_per_word - i - 1); + *dd->read_buf++ = (data_in & (0xFF << shift)) >> shift; + dd->rx_bytes_remaining--; + } + } else { + if (dd->rx_bytes_remaining >= dd->bytes_per_word) + dd->rx_bytes_remaining -= dd->bytes_per_word; + else + dd->rx_bytes_remaining = 0; + } + dd->read_xfr_cnt++; + if (dd->multi_xfr) { + if (!dd->rx_bytes_remaining) + dd->read_xfr_cnt = 0; + else if ((dd->read_xfr_cnt * dd->bytes_per_word) == + dd->read_len) { + struct spi_transfer *t = dd->cur_rx_transfer; + if (t->transfer_list.next != &dd->cur_msg->transfers) { + t = list_entry(t->transfer_list.next, + struct spi_transfer, + transfer_list); + dd->read_buf = t->rx_buf; + dd->read_len = t->len; + dd->read_xfr_cnt = 0; + dd->cur_rx_transfer = t; + } + } + } +} + +static inline bool msm_spi_is_valid_state(struct msm_spi *dd) +{ + u32 spi_op = readl_relaxed(dd->base + SPI_STATE); + + return spi_op & SPI_OP_STATE_VALID; +} + +static inline int msm_spi_wait_valid(struct msm_spi *dd) +{ + unsigned long delay = 0; + unsigned long timeout = 0; + + if (dd->clock_speed == 0) + return -EINVAL; + /* + * Based on the SPI clock speed, sufficient time + * should be given for the SPI state transition + * to occur + */ + delay = (10 * USEC_PER_SEC) / dd->clock_speed; + /* + * For small delay values, the default timeout would + * be one jiffy + */ + if (delay < SPI_DELAY_THRESHOLD) + delay = SPI_DELAY_THRESHOLD; + timeout = jiffies + msecs_to_jiffies(delay * SPI_DEFAULT_TIMEOUT); + while (!msm_spi_is_valid_state(dd)) { + if (time_after(jiffies, timeout)) { + if (dd->cur_msg) + dd->cur_msg->status = -EIO; + dev_err(dd->dev, "%s: SPI operational state not valid" + "\n", __func__); + return -1; + } + /* + * For smaller values of delay, context switch time + * would negate the usage of usleep + */ + if (delay > 20) + usleep(delay); + else if (delay) + udelay(delay); + } + return 0; +} + +static inline int msm_spi_set_state(struct msm_spi *dd, + enum msm_spi_state state) +{ + enum msm_spi_state cur_state; + if (msm_spi_wait_valid(dd)) + return -1; + cur_state = readl_relaxed(dd->base + SPI_STATE); + /* Per spec: + For PAUSE_STATE to RESET_STATE, two writes of (10) are required */ + if (((cur_state & SPI_OP_STATE) == SPI_OP_STATE_PAUSE) && + (state == SPI_OP_STATE_RESET)) { + writel_relaxed(SPI_OP_STATE_CLEAR_BITS, dd->base + SPI_STATE); + writel_relaxed(SPI_OP_STATE_CLEAR_BITS, dd->base + SPI_STATE); + } else { + writel_relaxed((cur_state & ~SPI_OP_STATE) | state, + dd->base + SPI_STATE); + } + if (msm_spi_wait_valid(dd)) + return -1; + + return 0; +} + +static inline void msm_spi_add_configs(struct msm_spi *dd, u32 *config, int n) +{ + *config &= ~(SPI_NO_INPUT|SPI_NO_OUTPUT); + + if (n != (*config & SPI_CFG_N)) + *config = (*config & ~SPI_CFG_N) | n; + + if ((dd->mode == SPI_DMOV_MODE) && (!dd->read_len)) { + if (dd->read_buf == NULL) + *config |= SPI_NO_INPUT; + if (dd->write_buf == NULL) + *config |= SPI_NO_OUTPUT; + } +} + +static void msm_spi_set_config(struct msm_spi *dd, int bpw) +{ + u32 spi_config; + + spi_config = readl_relaxed(dd->base + SPI_CONFIG); + + if (dd->cur_msg->spi->mode & SPI_CPHA) + spi_config &= ~SPI_CFG_INPUT_FIRST; + else + spi_config |= SPI_CFG_INPUT_FIRST; + if (dd->cur_msg->spi->mode & SPI_LOOP) + spi_config |= SPI_CFG_LOOPBACK; + else + spi_config &= ~SPI_CFG_LOOPBACK; + msm_spi_add_configs(dd, &spi_config, bpw-1); + writel_relaxed(spi_config, dd->base + SPI_CONFIG); + msm_spi_set_qup_config(dd, bpw); +} + +static void msm_spi_setup_dm_transfer(struct msm_spi *dd) +{ + dmov_box *box; + int bytes_to_send, num_rows, bytes_sent; + u32 num_transfers; + + atomic_set(&dd->rx_irq_called, 0); + if (dd->write_len && !dd->read_len) { + /* WR-WR transfer */ + bytes_sent = dd->cur_msg_len - dd->tx_bytes_remaining; + dd->write_buf = dd->temp_buf; + } else { + bytes_sent = dd->cur_transfer->len - dd->tx_bytes_remaining; + /* For WR-RD transfer, bytes_sent can be negative */ + if (bytes_sent < 0) + bytes_sent = 0; + } + + /* We'll send in chunks of SPI_MAX_LEN if larger */ + bytes_to_send = dd->tx_bytes_remaining / SPI_MAX_LEN ? + SPI_MAX_LEN : dd->tx_bytes_remaining; + num_transfers = DIV_ROUND_UP(bytes_to_send, dd->bytes_per_word); + dd->unaligned_len = bytes_to_send % dd->burst_size; + num_rows = bytes_to_send / dd->burst_size; + + dd->mode = SPI_DMOV_MODE; + + if (num_rows) { + /* src in 16 MSB, dst in 16 LSB */ + box = &dd->tx_dmov_cmd->box; + box->src_row_addr = dd->cur_transfer->tx_dma + bytes_sent; + box->src_dst_len = (dd->burst_size << 16) | dd->burst_size; + box->num_rows = (num_rows << 16) | num_rows; + box->row_offset = (dd->burst_size << 16) | 0; + + box = &dd->rx_dmov_cmd->box; + box->dst_row_addr = dd->cur_transfer->rx_dma + bytes_sent; + box->src_dst_len = (dd->burst_size << 16) | dd->burst_size; + box->num_rows = (num_rows << 16) | num_rows; + box->row_offset = (0 << 16) | dd->burst_size; + + dd->tx_dmov_cmd->cmd_ptr = CMD_PTR_LP | + DMOV_CMD_ADDR(dd->tx_dmov_cmd_dma + + offsetof(struct spi_dmov_cmd, box)); + dd->rx_dmov_cmd->cmd_ptr = CMD_PTR_LP | + DMOV_CMD_ADDR(dd->rx_dmov_cmd_dma + + offsetof(struct spi_dmov_cmd, box)); + } else { + dd->tx_dmov_cmd->cmd_ptr = CMD_PTR_LP | + DMOV_CMD_ADDR(dd->tx_dmov_cmd_dma + + offsetof(struct spi_dmov_cmd, single_pad)); + dd->rx_dmov_cmd->cmd_ptr = CMD_PTR_LP | + DMOV_CMD_ADDR(dd->rx_dmov_cmd_dma + + offsetof(struct spi_dmov_cmd, single_pad)); + } + + if (!dd->unaligned_len) { + dd->tx_dmov_cmd->box.cmd |= CMD_LC; + dd->rx_dmov_cmd->box.cmd |= CMD_LC; + } else { + dmov_s *tx_cmd = &(dd->tx_dmov_cmd->single_pad); + dmov_s *rx_cmd = &(dd->rx_dmov_cmd->single_pad); + u32 offset = dd->cur_transfer->len - dd->unaligned_len; + + if ((dd->multi_xfr) && (dd->read_len <= 0)) + offset = dd->cur_msg_len - dd->unaligned_len; + + dd->tx_dmov_cmd->box.cmd &= ~CMD_LC; + dd->rx_dmov_cmd->box.cmd &= ~CMD_LC; + + memset(dd->tx_padding, 0, dd->burst_size); + memset(dd->rx_padding, 0, dd->burst_size); + if (dd->write_buf) + memcpy(dd->tx_padding, dd->write_buf + offset, + dd->unaligned_len); + + tx_cmd->src = dd->tx_padding_dma; + rx_cmd->dst = dd->rx_padding_dma; + tx_cmd->len = rx_cmd->len = dd->burst_size; + } + /* This also takes care of the padding dummy buf + Since this is set to the correct length, the + dummy bytes won't be actually sent */ + if (dd->multi_xfr) { + u32 write_transfers = 0; + u32 read_transfers = 0; + + if (dd->write_len > 0) { + write_transfers = DIV_ROUND_UP(dd->write_len, + dd->bytes_per_word); + writel_relaxed(write_transfers, + dd->base + SPI_MX_OUTPUT_COUNT); + } + if (dd->read_len > 0) { + /* + * The read following a write transfer must take + * into account, that the bytes pertaining to + * the write transfer needs to be discarded, + * before the actual read begins. + */ + read_transfers = DIV_ROUND_UP(dd->read_len + + dd->write_len, + dd->bytes_per_word); + writel_relaxed(read_transfers, + dd->base + SPI_MX_INPUT_COUNT); + } + } else { + if (dd->write_buf) + writel_relaxed(num_transfers, + dd->base + SPI_MX_OUTPUT_COUNT); + if (dd->read_buf) + writel_relaxed(num_transfers, + dd->base + SPI_MX_INPUT_COUNT); + } +} + +static void msm_spi_enqueue_dm_commands(struct msm_spi *dd) +{ + dma_coherent_pre_ops(); + if (dd->write_buf) + msm_dmov_enqueue_cmd(dd->tx_dma_chan, &dd->tx_hdr); + if (dd->read_buf) + msm_dmov_enqueue_cmd(dd->rx_dma_chan, &dd->rx_hdr); +} + +/* SPI core can send maximum of 4K transfers, because there is HW problem + with infinite mode. + Therefore, we are sending several chunks of 3K or less (depending on how + much is left). + Upon completion we send the next chunk, or complete the transfer if + everything is finished. +*/ +static int msm_spi_dm_send_next(struct msm_spi *dd) +{ + /* By now we should have sent all the bytes in FIFO mode, + * However to make things right, we'll check anyway. + */ + if (dd->mode != SPI_DMOV_MODE) + return 0; + + /* We need to send more chunks, if we sent max last time */ + if (dd->tx_bytes_remaining > SPI_MAX_LEN) { + dd->tx_bytes_remaining -= SPI_MAX_LEN; + if (msm_spi_set_state(dd, SPI_OP_STATE_RESET)) + return 0; + dd->read_len = dd->write_len = 0; + msm_spi_setup_dm_transfer(dd); + msm_spi_enqueue_dm_commands(dd); + if (msm_spi_set_state(dd, SPI_OP_STATE_RUN)) + return 0; + return 1; + } else if (dd->read_len && dd->write_len) { + dd->tx_bytes_remaining -= dd->cur_transfer->len; + if (list_is_last(&dd->cur_transfer->transfer_list, + &dd->cur_msg->transfers)) + return 0; + get_next_transfer(dd); + if (msm_spi_set_state(dd, SPI_OP_STATE_PAUSE)) + return 0; + dd->tx_bytes_remaining = dd->read_len + dd->write_len; + dd->read_buf = dd->temp_buf; + dd->read_len = dd->write_len = -1; + msm_spi_setup_dm_transfer(dd); + msm_spi_enqueue_dm_commands(dd); + if (msm_spi_set_state(dd, SPI_OP_STATE_RUN)) + return 0; + return 1; + } + return 0; +} + +static inline void msm_spi_ack_transfer(struct msm_spi *dd) +{ + writel_relaxed(SPI_OP_MAX_INPUT_DONE_FLAG | + SPI_OP_MAX_OUTPUT_DONE_FLAG, + dd->base + SPI_OPERATIONAL); + /* Ensure done flag was cleared before proceeding further */ + mb(); +} + +static irqreturn_t msm_spi_input_irq(int irq, void *dev_id) +{ + struct msm_spi *dd = dev_id; + + dd->stat_rx++; + + if (dd->mode == SPI_MODE_NONE) + return IRQ_HANDLED; + + if (dd->mode == SPI_DMOV_MODE) { + u32 op = readl_relaxed(dd->base + SPI_OPERATIONAL); + if ((!dd->read_buf || op & SPI_OP_MAX_INPUT_DONE_FLAG) && + (!dd->write_buf || op & SPI_OP_MAX_OUTPUT_DONE_FLAG)) { + msm_spi_ack_transfer(dd); + if (dd->unaligned_len == 0) { + if (atomic_inc_return(&dd->rx_irq_called) == 1) + return IRQ_HANDLED; + } + msm_spi_complete(dd); + return IRQ_HANDLED; + } + return IRQ_NONE; + } + + if (dd->mode == SPI_FIFO_MODE) { + while ((readl_relaxed(dd->base + SPI_OPERATIONAL) & + SPI_OP_IP_FIFO_NOT_EMPTY) && + (dd->rx_bytes_remaining > 0)) { + msm_spi_read_word_from_fifo(dd); + } + if (dd->rx_bytes_remaining == 0) + msm_spi_complete(dd); + } + + return IRQ_HANDLED; +} + +static void msm_spi_write_word_to_fifo(struct msm_spi *dd) +{ + u32 word; + u8 byte; + int i; + + word = 0; + if (dd->write_buf) { + for (i = 0; (i < dd->bytes_per_word) && + dd->tx_bytes_remaining; i++) { + dd->tx_bytes_remaining--; + byte = *dd->write_buf++; + word |= (byte << (BITS_PER_BYTE * (3 - i))); + } + } else + if (dd->tx_bytes_remaining > dd->bytes_per_word) + dd->tx_bytes_remaining -= dd->bytes_per_word; + else + dd->tx_bytes_remaining = 0; + dd->write_xfr_cnt++; + if (dd->multi_xfr) { + if (!dd->tx_bytes_remaining) + dd->write_xfr_cnt = 0; + else if ((dd->write_xfr_cnt * dd->bytes_per_word) == + dd->write_len) { + struct spi_transfer *t = dd->cur_tx_transfer; + if (t->transfer_list.next != &dd->cur_msg->transfers) { + t = list_entry(t->transfer_list.next, + struct spi_transfer, + transfer_list); + dd->write_buf = t->tx_buf; + dd->write_len = t->len; + dd->write_xfr_cnt = 0; + dd->cur_tx_transfer = t; + } + } + } + writel_relaxed(word, dd->base + SPI_OUTPUT_FIFO); +} + +static inline void msm_spi_write_rmn_to_fifo(struct msm_spi *dd) +{ + int count = 0; + + while ((dd->tx_bytes_remaining > 0) && (count < dd->input_fifo_size) && + !(readl_relaxed(dd->base + SPI_OPERATIONAL) & + SPI_OP_OUTPUT_FIFO_FULL)) { + msm_spi_write_word_to_fifo(dd); + count++; + } +} + +static irqreturn_t msm_spi_output_irq(int irq, void *dev_id) +{ + struct msm_spi *dd = dev_id; + + dd->stat_tx++; + + if (dd->mode == SPI_MODE_NONE) + return IRQ_HANDLED; + + if (dd->mode == SPI_DMOV_MODE) { + /* TX_ONLY transaction is handled here + This is the only place we send complete at tx and not rx */ + if (dd->read_buf == NULL && + readl_relaxed(dd->base + SPI_OPERATIONAL) & + SPI_OP_MAX_OUTPUT_DONE_FLAG) { + msm_spi_ack_transfer(dd); + msm_spi_complete(dd); + return IRQ_HANDLED; + } + return IRQ_NONE; + } + + /* Output FIFO is empty. Transmit any outstanding write data. */ + if (dd->mode == SPI_FIFO_MODE) + msm_spi_write_rmn_to_fifo(dd); + + return IRQ_HANDLED; +} + +static irqreturn_t msm_spi_error_irq(int irq, void *dev_id) +{ + struct spi_master *master = dev_id; + struct msm_spi *dd = spi_master_get_devdata(master); + u32 spi_err; + + spi_err = readl_relaxed(dd->base + SPI_ERROR_FLAGS); + if (spi_err & SPI_ERR_OUTPUT_OVER_RUN_ERR) + dev_warn(master->dev.parent, "SPI output overrun error\n"); + if (spi_err & SPI_ERR_INPUT_UNDER_RUN_ERR) + dev_warn(master->dev.parent, "SPI input underrun error\n"); + if (spi_err & SPI_ERR_OUTPUT_UNDER_RUN_ERR) + dev_warn(master->dev.parent, "SPI output underrun error\n"); + msm_spi_get_clk_err(dd, &spi_err); + if (spi_err & SPI_ERR_CLK_OVER_RUN_ERR) + dev_warn(master->dev.parent, "SPI clock overrun error\n"); + if (spi_err & SPI_ERR_CLK_UNDER_RUN_ERR) + dev_warn(master->dev.parent, "SPI clock underrun error\n"); + msm_spi_clear_error_flags(dd); + msm_spi_ack_clk_err(dd); + /* Ensure clearing of QUP_ERROR_FLAGS was completed */ + mb(); + return IRQ_HANDLED; +} + +static int msm_spi_map_dma_buffers(struct msm_spi *dd) +{ + struct device *dev; + struct spi_transfer *first_xfr; + struct spi_transfer *nxt_xfr; + void *tx_buf, *rx_buf; + unsigned tx_len, rx_len; + int ret = -EINVAL; + + dev = &dd->cur_msg->spi->dev; + first_xfr = dd->cur_transfer; + tx_buf = (void *)first_xfr->tx_buf; + rx_buf = first_xfr->rx_buf; + tx_len = rx_len = first_xfr->len; + + /* + * For WR-WR and WR-RD transfers, we allocate our own temporary + * buffer and copy the data to/from the client buffers. + */ + if (dd->multi_xfr) { + dd->temp_buf = kzalloc(dd->cur_msg_len, + GFP_KERNEL | __GFP_DMA); + if (!dd->temp_buf) + return -ENOMEM; + nxt_xfr = list_entry(first_xfr->transfer_list.next, + struct spi_transfer, transfer_list); + + if (dd->write_len && !dd->read_len) { + if (!first_xfr->tx_buf || !nxt_xfr->tx_buf) + goto error; + + memcpy(dd->temp_buf, first_xfr->tx_buf, first_xfr->len); + memcpy(dd->temp_buf + first_xfr->len, nxt_xfr->tx_buf, + nxt_xfr->len); + tx_buf = dd->temp_buf; + tx_len = dd->cur_msg_len; + } else { + if (!first_xfr->tx_buf || !nxt_xfr->rx_buf) + goto error; + + rx_buf = dd->temp_buf; + rx_len = dd->cur_msg_len; + } + } + if (tx_buf != NULL) { + first_xfr->tx_dma = dma_map_single(dev, tx_buf, + tx_len, DMA_TO_DEVICE); + if (dma_mapping_error(NULL, first_xfr->tx_dma)) { + dev_err(dev, "dma %cX %d bytes error\n", + 'T', tx_len); + ret = -ENOMEM; + goto error; + } + } + if (rx_buf != NULL) { + dma_addr_t dma_handle; + dma_handle = dma_map_single(dev, rx_buf, + rx_len, DMA_FROM_DEVICE); + if (dma_mapping_error(NULL, dma_handle)) { + dev_err(dev, "dma %cX %d bytes error\n", + 'R', rx_len); + if (tx_buf != NULL) + dma_unmap_single(NULL, first_xfr->tx_dma, + tx_len, DMA_TO_DEVICE); + ret = -ENOMEM; + goto error; + } + if (dd->multi_xfr) + nxt_xfr->rx_dma = dma_handle; + else + first_xfr->rx_dma = dma_handle; + } + return 0; + +error: + kfree(dd->temp_buf); + dd->temp_buf = NULL; + return ret; +} + +static void msm_spi_unmap_dma_buffers(struct msm_spi *dd) +{ + struct device *dev; + u32 offset; + + dev = &dd->cur_msg->spi->dev; + if (dd->cur_msg->is_dma_mapped) + goto unmap_end; + + if (dd->multi_xfr) { + if (dd->write_len && !dd->read_len) { + dma_unmap_single(dev, + dd->cur_transfer->tx_dma, + dd->cur_msg_len, + DMA_TO_DEVICE); + } else { + struct spi_transfer *prev_xfr; + prev_xfr = list_entry( + dd->cur_transfer->transfer_list.prev, + struct spi_transfer, + transfer_list); + if (dd->cur_transfer->rx_buf) { + dma_unmap_single(dev, + dd->cur_transfer->rx_dma, + dd->cur_msg_len, + DMA_FROM_DEVICE); + } + if (prev_xfr->tx_buf) { + dma_unmap_single(dev, + prev_xfr->tx_dma, + prev_xfr->len, + DMA_TO_DEVICE); + } + if (dd->unaligned_len && dd->read_buf) { + offset = dd->cur_msg_len - dd->unaligned_len; + dma_coherent_post_ops(); + memcpy(dd->read_buf + offset, dd->rx_padding, + dd->unaligned_len); + memcpy(dd->cur_transfer->rx_buf, + dd->read_buf + prev_xfr->len, + dd->cur_transfer->len); + } + } + kfree(dd->temp_buf); + dd->temp_buf = NULL; + return; + } else { + if (dd->cur_transfer->rx_buf) + dma_unmap_single(dev, dd->cur_transfer->rx_dma, + dd->cur_transfer->len, + DMA_FROM_DEVICE); + if (dd->cur_transfer->tx_buf) + dma_unmap_single(dev, dd->cur_transfer->tx_dma, + dd->cur_transfer->len, + DMA_TO_DEVICE); + } + +unmap_end: + /* If we padded the transfer, we copy it from the padding buf */ + if (dd->unaligned_len && dd->read_buf) { + offset = dd->cur_transfer->len - dd->unaligned_len; + dma_coherent_post_ops(); + memcpy(dd->read_buf + offset, dd->rx_padding, + dd->unaligned_len); + } +} + +/** + * msm_use_dm - decides whether to use data mover for this + * transfer + * @dd: device + * @tr: transfer + * + * Start using DM if: + * 1. Transfer is longer than 3*block size. + * 2. Buffers should be aligned to cache line. + * 3. For WR-RD or WR-WR transfers, if condition (1) and (2) above are met. + */ +static inline int msm_use_dm(struct msm_spi *dd, struct spi_transfer *tr, + u8 bpw) +{ + u32 cache_line = dma_get_cache_alignment(); + + if (!dd->use_dma) + return 0; + + if (dd->cur_msg_len < 3*dd->input_block_size) + return 0; + + if (dd->multi_xfr && !dd->read_len && !dd->write_len) + return 0; + + if (tr->tx_buf) { + if (!IS_ALIGNED((size_t)tr->tx_buf, cache_line)) + return 0; + } + if (tr->rx_buf) { + if (!IS_ALIGNED((size_t)tr->rx_buf, cache_line)) + return 0; + } + + if (tr->cs_change && + ((bpw != 8) || (bpw != 16) || (bpw != 32))) + return 0; + return 1; +} + +static void msm_spi_process_transfer(struct msm_spi *dd) +{ + u8 bpw; + u32 spi_ioc; + u32 spi_iom; + u32 spi_ioc_orig; + u32 max_speed; + u32 chip_select; + u32 read_count; + u32 timeout; + u32 int_loopback = 0; + + dd->tx_bytes_remaining = dd->cur_msg_len; + dd->rx_bytes_remaining = dd->cur_msg_len; + dd->read_buf = dd->cur_transfer->rx_buf; + dd->write_buf = dd->cur_transfer->tx_buf; + init_completion(&dd->transfer_complete); + if (dd->cur_transfer->bits_per_word) + bpw = dd->cur_transfer->bits_per_word; + else + if (dd->cur_msg->spi->bits_per_word) + bpw = dd->cur_msg->spi->bits_per_word; + else + bpw = 8; + dd->bytes_per_word = (bpw + 7) / 8; + + if (dd->cur_transfer->speed_hz) + max_speed = dd->cur_transfer->speed_hz; + else + max_speed = dd->cur_msg->spi->max_speed_hz; + if (!dd->clock_speed || max_speed != dd->clock_speed) + msm_spi_clock_set(dd, max_speed); + + read_count = DIV_ROUND_UP(dd->cur_msg_len, dd->bytes_per_word); + if (dd->cur_msg->spi->mode & SPI_LOOP) + int_loopback = 1; + if (int_loopback && dd->multi_xfr && + (read_count > dd->input_fifo_size)) { + if (dd->read_len && dd->write_len) + printk(KERN_WARNING + "%s:Internal Loopback does not support > fifo size\ + for write-then-read transactions\n", + __func__); + else if (dd->write_len && !dd->read_len) + printk(KERN_WARNING + "%s:Internal Loopback does not support > fifo size\ + for write-then-write transactions\n", + __func__); + return; + } + if (!msm_use_dm(dd, dd->cur_transfer, bpw)) { + dd->mode = SPI_FIFO_MODE; + if (dd->multi_xfr) { + dd->read_len = dd->cur_transfer->len; + dd->write_len = dd->cur_transfer->len; + } + /* read_count cannot exceed fifo_size, and only one READ COUNT + interrupt is generated per transaction, so for transactions + larger than fifo size READ COUNT must be disabled. + For those transactions we usually move to Data Mover mode. + */ + if (read_count <= dd->input_fifo_size) { + writel_relaxed(read_count, + dd->base + SPI_MX_READ_COUNT); + msm_spi_set_write_count(dd, read_count); + } else { + writel_relaxed(0, dd->base + SPI_MX_READ_COUNT); + msm_spi_set_write_count(dd, 0); + } + } else { + dd->mode = SPI_DMOV_MODE; + if (dd->write_len && dd->read_len) { + dd->tx_bytes_remaining = dd->write_len; + dd->rx_bytes_remaining = dd->read_len; + } + } + + /* Write mode - fifo or data mover*/ + spi_iom = readl_relaxed(dd->base + SPI_IO_MODES); + spi_iom &= ~(SPI_IO_M_INPUT_MODE | SPI_IO_M_OUTPUT_MODE); + spi_iom = (spi_iom | (dd->mode << OUTPUT_MODE_SHIFT)); + spi_iom = (spi_iom | (dd->mode << INPUT_MODE_SHIFT)); + /* Turn on packing for data mover */ + if (dd->mode == SPI_DMOV_MODE) + spi_iom |= SPI_IO_M_PACK_EN | SPI_IO_M_UNPACK_EN; + else + spi_iom &= ~(SPI_IO_M_PACK_EN | SPI_IO_M_UNPACK_EN); + writel_relaxed(spi_iom, dd->base + SPI_IO_MODES); + + msm_spi_set_config(dd, bpw); + + spi_ioc = readl_relaxed(dd->base + SPI_IO_CONTROL); + spi_ioc_orig = spi_ioc; + if (dd->cur_msg->spi->mode & SPI_CPOL) + spi_ioc |= SPI_IO_C_CLK_IDLE_HIGH; + else + spi_ioc &= ~SPI_IO_C_CLK_IDLE_HIGH; + chip_select = dd->cur_msg->spi->chip_select << 2; + if ((spi_ioc & SPI_IO_C_CS_SELECT) != chip_select) + spi_ioc = (spi_ioc & ~SPI_IO_C_CS_SELECT) | chip_select; + if (!dd->cur_transfer->cs_change) + spi_ioc |= SPI_IO_C_MX_CS_MODE; + if (spi_ioc != spi_ioc_orig) + writel_relaxed(spi_ioc, dd->base + SPI_IO_CONTROL); + + if (dd->mode == SPI_DMOV_MODE) { + msm_spi_setup_dm_transfer(dd); + msm_spi_enqueue_dm_commands(dd); + } + /* The output fifo interrupt handler will handle all writes after + the first. Restricting this to one write avoids contention + issues and race conditions between this thread and the int handler + */ + else if (dd->mode == SPI_FIFO_MODE) { + if (msm_spi_prepare_for_write(dd)) + goto transfer_end; + msm_spi_start_write(dd, read_count); + } + + /* Only enter the RUN state after the first word is written into + the output FIFO. Otherwise, the output FIFO EMPTY interrupt + might fire before the first word is written resulting in a + possible race condition. + */ + if (msm_spi_set_state(dd, SPI_OP_STATE_RUN)) + goto transfer_end; + + timeout = 100 * msecs_to_jiffies( + DIV_ROUND_UP(dd->cur_msg_len * 8, + DIV_ROUND_UP(max_speed, MSEC_PER_SEC))); + + /* Assume success, this might change later upon transaction result */ + dd->cur_msg->status = 0; + do { + if (!wait_for_completion_timeout(&dd->transfer_complete, + timeout)) { + dev_err(dd->dev, "%s: SPI transaction " + "timeout\n", __func__); + dd->cur_msg->status = -EIO; + if (dd->mode == SPI_DMOV_MODE) { + msm_dmov_flush(dd->tx_dma_chan); + msm_dmov_flush(dd->rx_dma_chan); + } + break; + } + } while (msm_spi_dm_send_next(dd)); + +transfer_end: + if (dd->mode == SPI_DMOV_MODE) + msm_spi_unmap_dma_buffers(dd); + dd->mode = SPI_MODE_NONE; + + msm_spi_set_state(dd, SPI_OP_STATE_RESET); + writel_relaxed(spi_ioc & ~SPI_IO_C_MX_CS_MODE, + dd->base + SPI_IO_CONTROL); +} + +static void get_transfer_length(struct msm_spi *dd) +{ + struct spi_transfer *tr; + int num_xfrs = 0; + int readlen = 0; + int writelen = 0; + + dd->cur_msg_len = 0; + dd->multi_xfr = 0; + dd->read_len = dd->write_len = 0; + + list_for_each_entry(tr, &dd->cur_msg->transfers, transfer_list) { + if (tr->tx_buf) + writelen += tr->len; + if (tr->rx_buf) + readlen += tr->len; + dd->cur_msg_len += tr->len; + num_xfrs++; + } + + if (num_xfrs == 2) { + struct spi_transfer *first_xfr = dd->cur_transfer; + + dd->multi_xfr = 1; + tr = list_entry(first_xfr->transfer_list.next, + struct spi_transfer, + transfer_list); + /* + * We update dd->read_len and dd->write_len only + * for WR-WR and WR-RD transfers. + */ + if ((first_xfr->tx_buf) && (!first_xfr->rx_buf)) { + if (((tr->tx_buf) && (!tr->rx_buf)) || + ((!tr->tx_buf) && (tr->rx_buf))) { + dd->read_len = readlen; + dd->write_len = writelen; + } + } + } else if (num_xfrs > 1) + dd->multi_xfr = 1; +} + +static inline int combine_transfers(struct msm_spi *dd) +{ + struct spi_transfer *t = dd->cur_transfer; + struct spi_transfer *nxt; + int xfrs_grped = 1; + + dd->cur_msg_len = dd->cur_transfer->len; + while (t->transfer_list.next != &dd->cur_msg->transfers) { + nxt = list_entry(t->transfer_list.next, + struct spi_transfer, + transfer_list); + if (t->cs_change != nxt->cs_change) + return xfrs_grped; + dd->cur_msg_len += nxt->len; + xfrs_grped++; + t = nxt; + } + return xfrs_grped; +} + +static void msm_spi_process_message(struct msm_spi *dd) +{ + int xfrs_grped = 0; + dd->write_xfr_cnt = dd->read_xfr_cnt = 0; + + dd->cur_transfer = list_first_entry(&dd->cur_msg->transfers, + struct spi_transfer, + transfer_list); + get_transfer_length(dd); + if (dd->multi_xfr && !dd->read_len && !dd->write_len) { + /* Handling of multi-transfers. FIFO mode is used by default */ + list_for_each_entry(dd->cur_transfer, + &dd->cur_msg->transfers, + transfer_list) { + if (!dd->cur_transfer->len) + return; + if (xfrs_grped) { + xfrs_grped--; + continue; + } else { + dd->read_len = dd->write_len = 0; + xfrs_grped = combine_transfers(dd); + } + dd->cur_tx_transfer = dd->cur_transfer; + dd->cur_rx_transfer = dd->cur_transfer; + msm_spi_process_transfer(dd); + xfrs_grped--; + } + } else { + /* Handling of a single transfer or WR-WR or WR-RD transfers */ + if ((!dd->cur_msg->is_dma_mapped) && + (msm_use_dm(dd, dd->cur_transfer, + dd->cur_transfer->bits_per_word))) { + /* Mapping of DMA buffers */ + int ret = msm_spi_map_dma_buffers(dd); + if (ret < 0) { + dd->cur_msg->status = ret; + return; + } + } + dd->cur_tx_transfer = dd->cur_rx_transfer = dd->cur_transfer; + msm_spi_process_transfer(dd); + } +} + +/* workqueue - pull messages from queue & process */ +static void msm_spi_workq(struct work_struct *work) +{ + struct msm_spi *dd = + container_of(work, struct msm_spi, work_data); + unsigned long flags; + u32 status_error = 0; + + mutex_lock(&dd->core_lock); + + /* Don't allow power collapse until we release mutex */ + if (pm_qos_request_active(&qos_req_list)) + pm_qos_update_request(&qos_req_list, + dd->pm_lat); + if (dd->use_rlock) + remote_mutex_lock(&dd->r_lock); + + clk_enable(dd->clk); + clk_enable(dd->pclk); + msm_spi_enable_irqs(dd); + + if (!msm_spi_is_valid_state(dd)) { + dev_err(dd->dev, "%s: SPI operational state not valid\n", + __func__); + status_error = 1; + } + + spin_lock_irqsave(&dd->queue_lock, flags); + while (!list_empty(&dd->queue)) { + dd->cur_msg = list_entry(dd->queue.next, + struct spi_message, queue); + list_del_init(&dd->cur_msg->queue); + spin_unlock_irqrestore(&dd->queue_lock, flags); + if (status_error) + dd->cur_msg->status = -EIO; + else + msm_spi_process_message(dd); + if (dd->cur_msg->complete) + dd->cur_msg->complete(dd->cur_msg->context); + spin_lock_irqsave(&dd->queue_lock, flags); + } + dd->transfer_pending = 0; + spin_unlock_irqrestore(&dd->queue_lock, flags); + + msm_spi_disable_irqs(dd); + clk_disable(dd->clk); + clk_disable(dd->pclk); + + if (dd->use_rlock) + remote_mutex_unlock(&dd->r_lock); + + if (pm_qos_request_active(&qos_req_list)) + pm_qos_update_request(&qos_req_list, + PM_QOS_DEFAULT_VALUE); + + mutex_unlock(&dd->core_lock); + /* If needed, this can be done after the current message is complete, + and work can be continued upon resume. No motivation for now. */ + if (dd->suspended) + wake_up_interruptible(&dd->continue_suspend); +} + +static int msm_spi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct msm_spi *dd; + unsigned long flags; + struct spi_transfer *tr; + + dd = spi_master_get_devdata(spi->master); + if (dd->suspended) + return -EBUSY; + + if (list_empty(&msg->transfers) || !msg->complete) + return -EINVAL; + + list_for_each_entry(tr, &msg->transfers, transfer_list) { + /* Check message parameters */ + if (tr->speed_hz > dd->pdata->max_clock_speed || + (tr->bits_per_word && + (tr->bits_per_word < 4 || tr->bits_per_word > 32)) || + (tr->tx_buf == NULL && tr->rx_buf == NULL)) { + dev_err(&spi->dev, "Invalid transfer: %d Hz, %d bpw" + "tx=%p, rx=%p\n", + tr->speed_hz, tr->bits_per_word, + tr->tx_buf, tr->rx_buf); + return -EINVAL; + } + } + + spin_lock_irqsave(&dd->queue_lock, flags); + if (dd->suspended) { + spin_unlock_irqrestore(&dd->queue_lock, flags); + return -EBUSY; + } + dd->transfer_pending = 1; + list_add_tail(&msg->queue, &dd->queue); + spin_unlock_irqrestore(&dd->queue_lock, flags); + queue_work(dd->workqueue, &dd->work_data); + return 0; +} + +static int msm_spi_setup(struct spi_device *spi) +{ + struct msm_spi *dd; + int rc = 0; + u32 spi_ioc; + u32 spi_config; + u32 mask; + + if (spi->bits_per_word < 4 || spi->bits_per_word > 32) { + dev_err(&spi->dev, "%s: invalid bits_per_word %d\n", + __func__, spi->bits_per_word); + rc = -EINVAL; + } + if (spi->chip_select > SPI_NUM_CHIPSELECTS-1) { + dev_err(&spi->dev, "%s, chip select %d exceeds max value %d\n", + __func__, spi->chip_select, SPI_NUM_CHIPSELECTS - 1); + rc = -EINVAL; + } + + if (rc) + goto err_setup_exit; + + dd = spi_master_get_devdata(spi->master); + + mutex_lock(&dd->core_lock); + if (dd->suspended) { + mutex_unlock(&dd->core_lock); + return -EBUSY; + } + + if (dd->use_rlock) + remote_mutex_lock(&dd->r_lock); + + clk_enable(dd->clk); + clk_enable(dd->pclk); + + spi_ioc = readl_relaxed(dd->base + SPI_IO_CONTROL); + mask = SPI_IO_C_CS_N_POLARITY_0 << spi->chip_select; + if (spi->mode & SPI_CS_HIGH) + spi_ioc |= mask; + else + spi_ioc &= ~mask; + if (spi->mode & SPI_CPOL) + spi_ioc |= SPI_IO_C_CLK_IDLE_HIGH; + else + spi_ioc &= ~SPI_IO_C_CLK_IDLE_HIGH; + + writel_relaxed(spi_ioc, dd->base + SPI_IO_CONTROL); + + spi_config = readl_relaxed(dd->base + SPI_CONFIG); + if (spi->mode & SPI_LOOP) + spi_config |= SPI_CFG_LOOPBACK; + else + spi_config &= ~SPI_CFG_LOOPBACK; + if (spi->mode & SPI_CPHA) + spi_config &= ~SPI_CFG_INPUT_FIRST; + else + spi_config |= SPI_CFG_INPUT_FIRST; + writel_relaxed(spi_config, dd->base + SPI_CONFIG); + + /* Ensure previous write completed before disabling the clocks */ + mb(); + clk_disable(dd->clk); + clk_disable(dd->pclk); + + if (dd->use_rlock) + remote_mutex_unlock(&dd->r_lock); + mutex_unlock(&dd->core_lock); + +err_setup_exit: + return rc; +} + +#ifdef CONFIG_DEBUG_FS +static int debugfs_iomem_x32_set(void *data, u64 val) +{ + writel_relaxed(val, data); + /* Ensure the previous write completed. */ + mb(); + return 0; +} + +static int debugfs_iomem_x32_get(void *data, u64 *val) +{ + *val = readl_relaxed(data); + /* Ensure the previous read completed. */ + mb(); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_x32_get, + debugfs_iomem_x32_set, "0x%08llx\n"); + +static void spi_debugfs_init(struct msm_spi *dd) +{ + dd->dent_spi = debugfs_create_dir(dev_name(dd->dev), NULL); + if (dd->dent_spi) { + int i; + for (i = 0; i < ARRAY_SIZE(debugfs_spi_regs); i++) { + dd->debugfs_spi_regs[i] = + debugfs_create_file( + debugfs_spi_regs[i].name, + debugfs_spi_regs[i].mode, + dd->dent_spi, + dd->base + debugfs_spi_regs[i].offset, + &fops_iomem_x32); + } + } +} + +static void spi_debugfs_exit(struct msm_spi *dd) +{ + if (dd->dent_spi) { + int i; + debugfs_remove_recursive(dd->dent_spi); + dd->dent_spi = NULL; + for (i = 0; i < ARRAY_SIZE(debugfs_spi_regs); i++) + dd->debugfs_spi_regs[i] = NULL; + } +} +#else +static void spi_debugfs_init(struct msm_spi *dd) {} +static void spi_debugfs_exit(struct msm_spi *dd) {} +#endif + +/* ===Device attributes begin=== */ +static ssize_t show_stats(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct msm_spi *dd = spi_master_get_devdata(master); + + return snprintf(buf, PAGE_SIZE, + "Device %s\n" + "rx fifo_size = %d spi words\n" + "tx fifo_size = %d spi words\n" + "use_dma ? %s\n" + "rx block size = %d bytes\n" + "tx block size = %d bytes\n" + "burst size = %d bytes\n" + "DMA configuration:\n" + "tx_ch=%d, rx_ch=%d, tx_crci= %d, rx_crci=%d\n" + "--statistics--\n" + "Rx isrs = %d\n" + "Tx isrs = %d\n" + "DMA error = %d\n" + "--debug--\n" + "NA yet\n", + dev_name(dev), + dd->input_fifo_size, + dd->output_fifo_size, + dd->use_dma ? "yes" : "no", + dd->input_block_size, + dd->output_block_size, + dd->burst_size, + dd->tx_dma_chan, + dd->rx_dma_chan, + dd->tx_dma_crci, + dd->rx_dma_crci, + dd->stat_rx + dd->stat_dmov_rx, + dd->stat_tx + dd->stat_dmov_tx, + dd->stat_dmov_tx_err + dd->stat_dmov_rx_err + ); +} + +/* Reset statistics on write */ +static ssize_t set_stats(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_spi *dd = dev_get_drvdata(dev); + dd->stat_rx = 0; + dd->stat_tx = 0; + dd->stat_dmov_rx = 0; + dd->stat_dmov_tx = 0; + dd->stat_dmov_rx_err = 0; + dd->stat_dmov_tx_err = 0; + return count; +} + +static DEVICE_ATTR(stats, S_IRUGO | S_IWUSR, show_stats, set_stats); + +static struct attribute *dev_attrs[] = { + &dev_attr_stats.attr, + NULL, +}; + +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, +}; +/* ===Device attributes end=== */ + +/** + * spi_dmov_tx_complete_func - DataMover tx completion callback + * + * Executed in IRQ context (Data Mover's IRQ) DataMover's + * spinlock @msm_dmov_lock held. + */ +static void spi_dmov_tx_complete_func(struct msm_dmov_cmd *cmd, + unsigned int result, + struct msm_dmov_errdata *err) +{ + struct msm_spi *dd; + + if (!(result & DMOV_RSLT_VALID)) { + pr_err("Invalid DMOV result: rc=0x%08x, cmd = %p", result, cmd); + return; + } + /* restore original context */ + dd = container_of(cmd, struct msm_spi, tx_hdr); + if (result & DMOV_RSLT_DONE) + dd->stat_dmov_tx++; + else { + /* Error or flush */ + if (result & DMOV_RSLT_ERROR) { + dev_err(dd->dev, "DMA error (0x%08x)\n", result); + dd->stat_dmov_tx_err++; + } + if (result & DMOV_RSLT_FLUSH) { + /* + * Flushing normally happens in process of + * removing, when we are waiting for outstanding + * DMA commands to be flushed. + */ + dev_info(dd->dev, + "DMA channel flushed (0x%08x)\n", result); + } + if (err) + dev_err(dd->dev, + "Flush data(%08x %08x %08x %08x %08x %08x)\n", + err->flush[0], err->flush[1], err->flush[2], + err->flush[3], err->flush[4], err->flush[5]); + dd->cur_msg->status = -EIO; + complete(&dd->transfer_complete); + } +} + +/** + * spi_dmov_rx_complete_func - DataMover rx completion callback + * + * Executed in IRQ context (Data Mover's IRQ) + * DataMover's spinlock @msm_dmov_lock held. + */ +static void spi_dmov_rx_complete_func(struct msm_dmov_cmd *cmd, + unsigned int result, + struct msm_dmov_errdata *err) +{ + struct msm_spi *dd; + + if (!(result & DMOV_RSLT_VALID)) { + pr_err("Invalid DMOV result(rc = 0x%08x, cmd = %p)", + result, cmd); + return; + } + /* restore original context */ + dd = container_of(cmd, struct msm_spi, rx_hdr); + if (result & DMOV_RSLT_DONE) { + dd->stat_dmov_rx++; + if (atomic_inc_return(&dd->rx_irq_called) == 1) + return; + complete(&dd->transfer_complete); + } else { + /** Error or flush */ + if (result & DMOV_RSLT_ERROR) { + dev_err(dd->dev, "DMA error(0x%08x)\n", result); + dd->stat_dmov_rx_err++; + } + if (result & DMOV_RSLT_FLUSH) { + dev_info(dd->dev, + "DMA channel flushed(0x%08x)\n", result); + } + if (err) + dev_err(dd->dev, + "Flush data(%08x %08x %08x %08x %08x %08x)\n", + err->flush[0], err->flush[1], err->flush[2], + err->flush[3], err->flush[4], err->flush[5]); + dd->cur_msg->status = -EIO; + complete(&dd->transfer_complete); + } +} + +static inline u32 get_chunk_size(struct msm_spi *dd) +{ + u32 cache_line = dma_get_cache_alignment(); + + return (roundup(sizeof(struct spi_dmov_cmd), DM_BYTE_ALIGN) + + roundup(dd->burst_size, cache_line))*2; +} + +static void msm_spi_teardown_dma(struct msm_spi *dd) +{ + int limit = 0; + + if (!dd->use_dma) + return; + + while (dd->mode == SPI_DMOV_MODE && limit++ < 50) { + msm_dmov_flush(dd->tx_dma_chan); + msm_dmov_flush(dd->rx_dma_chan); + msleep(10); + } + + dma_free_coherent(NULL, get_chunk_size(dd), dd->tx_dmov_cmd, + dd->tx_dmov_cmd_dma); + dd->tx_dmov_cmd = dd->rx_dmov_cmd = NULL; + dd->tx_padding = dd->rx_padding = NULL; +} + +static __init int msm_spi_init_dma(struct msm_spi *dd) +{ + dmov_box *box; + u32 cache_line = dma_get_cache_alignment(); + + /* Allocate all as one chunk, since all is smaller than page size */ + + /* We send NULL device, since it requires coherent_dma_mask id + device definition, we're okay with using system pool */ + dd->tx_dmov_cmd = dma_alloc_coherent(NULL, get_chunk_size(dd), + &dd->tx_dmov_cmd_dma, GFP_KERNEL); + if (dd->tx_dmov_cmd == NULL) + return -ENOMEM; + + /* DMA addresses should be 64 bit aligned aligned */ + dd->rx_dmov_cmd = (struct spi_dmov_cmd *) + ALIGN((size_t)&dd->tx_dmov_cmd[1], DM_BYTE_ALIGN); + dd->rx_dmov_cmd_dma = ALIGN(dd->tx_dmov_cmd_dma + + sizeof(struct spi_dmov_cmd), DM_BYTE_ALIGN); + + /* Buffers should be aligned to cache line */ + dd->tx_padding = (u8 *)ALIGN((size_t)&dd->rx_dmov_cmd[1], cache_line); + dd->tx_padding_dma = ALIGN(dd->rx_dmov_cmd_dma + + sizeof(struct spi_dmov_cmd), cache_line); + dd->rx_padding = (u8 *)ALIGN((size_t)(dd->tx_padding + dd->burst_size), + cache_line); + dd->rx_padding_dma = ALIGN(dd->tx_padding_dma + dd->burst_size, + cache_line); + + /* Setup DM commands */ + box = &(dd->rx_dmov_cmd->box); + box->cmd = CMD_MODE_BOX | CMD_SRC_CRCI(dd->rx_dma_crci); + box->src_row_addr = (uint32_t)dd->mem_phys_addr + SPI_INPUT_FIFO; + dd->rx_hdr.cmdptr = DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(dd->rx_dmov_cmd_dma + + offsetof(struct spi_dmov_cmd, cmd_ptr)); + dd->rx_hdr.complete_func = spi_dmov_rx_complete_func; + dd->rx_hdr.crci_mask = msm_dmov_build_crci_mask(1, dd->rx_dma_crci); + + box = &(dd->tx_dmov_cmd->box); + box->cmd = CMD_MODE_BOX | CMD_DST_CRCI(dd->tx_dma_crci); + box->dst_row_addr = (uint32_t)dd->mem_phys_addr + SPI_OUTPUT_FIFO; + dd->tx_hdr.cmdptr = DMOV_CMD_PTR_LIST | + DMOV_CMD_ADDR(dd->tx_dmov_cmd_dma + + offsetof(struct spi_dmov_cmd, cmd_ptr)); + dd->tx_hdr.complete_func = spi_dmov_tx_complete_func; + dd->tx_hdr.crci_mask = msm_dmov_build_crci_mask(1, dd->tx_dma_crci); + + dd->tx_dmov_cmd->single_pad.cmd = CMD_MODE_SINGLE | CMD_LC | + CMD_DST_CRCI(dd->tx_dma_crci); + dd->tx_dmov_cmd->single_pad.dst = (uint32_t)dd->mem_phys_addr + + SPI_OUTPUT_FIFO; + dd->rx_dmov_cmd->single_pad.cmd = CMD_MODE_SINGLE | CMD_LC | + CMD_SRC_CRCI(dd->rx_dma_crci); + dd->rx_dmov_cmd->single_pad.src = (uint32_t)dd->mem_phys_addr + + SPI_INPUT_FIFO; + + /* Clear remaining activities on channel */ + msm_dmov_flush(dd->tx_dma_chan); + msm_dmov_flush(dd->rx_dma_chan); + + return 0; +} + +static int __init msm_spi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct msm_spi *dd; + struct resource *resource; + int rc = -ENXIO; + int locked = 0; + int i = 0; + int clk_enabled = 0; + int pclk_enabled = 0; + struct msm_spi_platform_data *pdata = pdev->dev.platform_data; + + master = spi_alloc_master(&pdev->dev, sizeof(struct msm_spi)); + if (!master) { + rc = -ENOMEM; + dev_err(&pdev->dev, "master allocation failed\n"); + goto err_probe_exit; + } + + master->bus_num = pdev->id; + master->mode_bits = SPI_SUPPORTED_MODES; + master->num_chipselect = SPI_NUM_CHIPSELECTS; + master->setup = msm_spi_setup; + master->transfer = msm_spi_transfer; + platform_set_drvdata(pdev, master); + dd = spi_master_get_devdata(master); + + dd->pdata = pdata; + rc = msm_spi_get_irq_data(dd, pdev); + if (rc) + goto err_probe_res; + resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "spi_base"); + if (!resource) { + rc = -ENXIO; + goto err_probe_res; + } + dd->mem_phys_addr = resource->start; + dd->mem_size = resource_size(resource); + + rc = msm_spi_get_gsbi_resource(dd, pdev); + if (rc) + goto err_probe_res2; + + if (pdata) { + if (pdata->dma_config) { + rc = pdata->dma_config(); + if (rc) { + dev_warn(&pdev->dev, + "%s: DM mode not supported\n", + __func__); + dd->use_dma = 0; + goto skip_dma_resources; + } + } + resource = platform_get_resource_byname(pdev, + IORESOURCE_DMA, + "spidm_channels"); + if (resource) { + dd->rx_dma_chan = resource->start; + dd->tx_dma_chan = resource->end; + + resource = platform_get_resource_byname(pdev, + IORESOURCE_DMA, + "spidm_crci"); + if (!resource) { + rc = -ENXIO; + goto err_probe_res; + } + dd->rx_dma_crci = resource->start; + dd->tx_dma_crci = resource->end; + dd->use_dma = 1; + master->dma_alignment = dma_get_cache_alignment(); + } + +skip_dma_resources: + if (pdata->gpio_config) { + rc = pdata->gpio_config(); + if (rc) { + dev_err(&pdev->dev, + "%s: error configuring GPIOs\n", + __func__); + goto err_probe_gpio; + } + } + } + + for (i = 0; i < ARRAY_SIZE(spi_rsrcs); ++i) { + resource = platform_get_resource_byname(pdev, IORESOURCE_IO, + spi_rsrcs[i]); + dd->spi_gpios[i] = resource ? resource->start : -1; + } + + rc = msm_spi_request_gpios(dd); + if (rc) + goto err_probe_gpio; + spin_lock_init(&dd->queue_lock); + mutex_init(&dd->core_lock); + INIT_LIST_HEAD(&dd->queue); + INIT_WORK(&dd->work_data, msm_spi_workq); + init_waitqueue_head(&dd->continue_suspend); + dd->workqueue = create_singlethread_workqueue( + dev_name(master->dev.parent)); + if (!dd->workqueue) + goto err_probe_workq; + + if (!request_mem_region(dd->mem_phys_addr, dd->mem_size, + SPI_DRV_NAME)) { + rc = -ENXIO; + goto err_probe_reqmem; + } + + dd->base = ioremap(dd->mem_phys_addr, dd->mem_size); + if (!dd->base) + goto err_probe_ioremap; + rc = msm_spi_request_gsbi(dd); + if (rc) + goto err_probe_ioremap2; + if (pdata && pdata->rsl_id) { + struct remote_mutex_id rmid; + rmid.r_spinlock_id = pdata->rsl_id; + rmid.delay_us = SPI_TRYLOCK_DELAY; + + rc = remote_mutex_init(&dd->r_lock, &rmid); + if (rc) { + dev_err(&pdev->dev, "%s: unable to init remote_mutex " + "(%s), (rc=%d)\n", rmid.r_spinlock_id, + __func__, rc); + goto err_probe_rlock_init; + } + dd->use_rlock = 1; + dd->pm_lat = pdata->pm_lat; + pm_qos_add_request(&qos_req_list, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + } + mutex_lock(&dd->core_lock); + if (dd->use_rlock) + remote_mutex_lock(&dd->r_lock); + locked = 1; + + dd->dev = &pdev->dev; + dd->clk = clk_get(&pdev->dev, "spi_clk"); + if (IS_ERR(dd->clk)) { + dev_err(&pdev->dev, "%s: unable to get spi_clk\n", __func__); + rc = PTR_ERR(dd->clk); + goto err_probe_clk_get; + } + + dd->pclk = clk_get(&pdev->dev, "spi_pclk"); + if (IS_ERR(dd->pclk)) { + dev_err(&pdev->dev, "%s: unable to get spi_pclk\n", __func__); + rc = PTR_ERR(dd->pclk); + goto err_probe_pclk_get; + } + + if (pdata && pdata->max_clock_speed) + msm_spi_clock_set(dd, dd->pdata->max_clock_speed); + + rc = clk_enable(dd->clk); + if (rc) { + dev_err(&pdev->dev, "%s: unable to enable spi_clk\n", + __func__); + goto err_probe_clk_enable; + } + clk_enabled = 1; + + rc = clk_enable(dd->pclk); + if (rc) { + dev_err(&pdev->dev, "%s: unable to enable spi_pclk\n", + __func__); + goto err_probe_pclk_enable; + } + pclk_enabled = 1; + msm_spi_init_gsbi(dd); + msm_spi_calculate_fifo_size(dd); + if (dd->use_dma) { + rc = msm_spi_init_dma(dd); + if (rc) + goto err_probe_dma; + } + + /* Initialize registers */ + writel_relaxed(0x00000001, dd->base + SPI_SW_RESET); + msm_spi_set_state(dd, SPI_OP_STATE_RESET); + + writel_relaxed(0x00000000, dd->base + SPI_OPERATIONAL); + writel_relaxed(0x00000000, dd->base + SPI_CONFIG); + writel_relaxed(0x00000000, dd->base + SPI_IO_MODES); + /* + * The SPI core generates a bogus input overrun error on some targets, + * when a transition from run to reset state occurs and if the FIFO has + * an odd number of entries. Hence we disable the INPUT_OVER_RUN_ERR_EN + * bit. + */ + msm_spi_enable_error_flags(dd); + + writel_relaxed(SPI_IO_C_NO_TRI_STATE, dd->base + SPI_IO_CONTROL); + rc = msm_spi_set_state(dd, SPI_OP_STATE_RESET); + if (rc) + goto err_probe_state; + + clk_disable(dd->clk); + clk_disable(dd->pclk); + clk_enabled = 0; + pclk_enabled = 0; + + dd->suspended = 0; + dd->transfer_pending = 0; + dd->multi_xfr = 0; + dd->mode = SPI_MODE_NONE; + + rc = msm_spi_request_irq(dd, pdev->name, master); + if (rc) + goto err_probe_irq; + + msm_spi_disable_irqs(dd); + if (dd->use_rlock) + remote_mutex_unlock(&dd->r_lock); + + mutex_unlock(&dd->core_lock); + locked = 0; + + rc = spi_register_master(master); + if (rc) + goto err_probe_reg_master; + + rc = sysfs_create_group(&(dd->dev->kobj), &dev_attr_grp); + if (rc) { + dev_err(&pdev->dev, "failed to create dev. attrs : %d\n", rc); + goto err_attrs; + } + + spi_debugfs_init(dd); + + return 0; + +err_attrs: +err_probe_reg_master: + msm_spi_free_irq(dd, master); +err_probe_irq: +err_probe_state: + msm_spi_teardown_dma(dd); +err_probe_dma: + if (pclk_enabled) + clk_disable(dd->pclk); +err_probe_pclk_enable: + if (clk_enabled) + clk_disable(dd->clk); +err_probe_clk_enable: + clk_put(dd->pclk); +err_probe_pclk_get: + clk_put(dd->clk); +err_probe_clk_get: + if (locked) { + if (dd->use_rlock) + remote_mutex_unlock(&dd->r_lock); + mutex_unlock(&dd->core_lock); + } +err_probe_rlock_init: + msm_spi_release_gsbi(dd); +err_probe_ioremap2: + iounmap(dd->base); +err_probe_ioremap: + release_mem_region(dd->mem_phys_addr, dd->mem_size); +err_probe_reqmem: + destroy_workqueue(dd->workqueue); +err_probe_workq: + msm_spi_free_gpios(dd); +err_probe_gpio: + if (pdata && pdata->gpio_release) + pdata->gpio_release(); +err_probe_res2: +err_probe_res: + spi_master_put(master); +err_probe_exit: + return rc; +} + +#ifdef CONFIG_PM +static int msm_spi_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct msm_spi *dd; + unsigned long flags; + + if (!master) + goto suspend_exit; + dd = spi_master_get_devdata(master); + if (!dd) + goto suspend_exit; + + /* Make sure nothing is added to the queue while we're suspending */ + spin_lock_irqsave(&dd->queue_lock, flags); + dd->suspended = 1; + spin_unlock_irqrestore(&dd->queue_lock, flags); + + /* Wait for transactions to end, or time out */ + wait_event_interruptible(dd->continue_suspend, !dd->transfer_pending); + msm_spi_free_gpios(dd); + +suspend_exit: + return 0; +} + +static int msm_spi_resume(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct msm_spi *dd; + + if (!master) + goto resume_exit; + dd = spi_master_get_devdata(master); + if (!dd) + goto resume_exit; + + BUG_ON(msm_spi_request_gpios(dd) != 0); + dd->suspended = 0; +resume_exit: + return 0; +} +#else +#define msm_spi_suspend NULL +#define msm_spi_resume NULL +#endif /* CONFIG_PM */ + +static int __devexit msm_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct msm_spi *dd = spi_master_get_devdata(master); + struct msm_spi_platform_data *pdata = pdev->dev.platform_data; + + pm_qos_remove_request(&qos_req_list); + spi_debugfs_exit(dd); + sysfs_remove_group(&pdev->dev.kobj, &dev_attr_grp); + + msm_spi_free_irq(dd, master); + msm_spi_teardown_dma(dd); + + if (pdata && pdata->gpio_release) + pdata->gpio_release(); + + msm_spi_free_gpios(dd); + iounmap(dd->base); + release_mem_region(dd->mem_phys_addr, dd->mem_size); + msm_spi_release_gsbi(dd); + clk_put(dd->clk); + clk_put(dd->pclk); + destroy_workqueue(dd->workqueue); + platform_set_drvdata(pdev, 0); + spi_unregister_master(master); + spi_master_put(master); + + return 0; +} + +static struct platform_driver msm_spi_driver = { + .driver = { + .name = SPI_DRV_NAME, + .owner = THIS_MODULE, + }, + .suspend = msm_spi_suspend, + .resume = msm_spi_resume, + .remove = __exit_p(msm_spi_remove), +}; + +static int __init msm_spi_init(void) +{ + return platform_driver_probe(&msm_spi_driver, msm_spi_probe); +} +module_init(msm_spi_init); + +static void __exit msm_spi_exit(void) +{ + platform_driver_unregister(&msm_spi_driver); +} +module_exit(msm_spi_exit); diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index d9fd8621136..c174306590f 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -83,6 +83,7 @@ struct spidev_data { struct mutex buf_lock; unsigned users; u8 *buffer; + u8 *bufferrx; }; static LIST_HEAD(device_list); @@ -92,6 +93,30 @@ static unsigned bufsiz = 4096; module_param(bufsiz, uint, S_IRUGO); MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); +/* + * This can be used for testing the controller, given the busnum and the + * cs required to use. If those parameters are used, spidev is + * dynamically added as device on the busnum, and messages can be sent + * via this interface. + */ +static int busnum = -1; +module_param(busnum, int, S_IRUGO); +MODULE_PARM_DESC(busnum, "bus num of the controller"); + +static int chipselect = -1; +module_param(chipselect, int, S_IRUGO); +MODULE_PARM_DESC(chipselect, "chip select of the desired device"); + +static int maxspeed = 10000000; +module_param(maxspeed, int, S_IRUGO); +MODULE_PARM_DESC(maxspeed, "max_speed of the desired device"); + +static int spimode = SPI_MODE_3; +module_param(spimode, int, S_IRUGO); +MODULE_PARM_DESC(spimode, "mode of the desired device"); + +static struct spi_device *spi; + /*-------------------------------------------------------------------------*/ /* @@ -221,7 +246,7 @@ static int spidev_message(struct spidev_data *spidev, struct spi_transfer *k_tmp; struct spi_ioc_transfer *u_tmp; unsigned n, total; - u8 *buf; + u8 *buf, *bufrx; int status = -EFAULT; spi_message_init(&msg); @@ -234,6 +259,7 @@ static int spidev_message(struct spidev_data *spidev, * to initialize a kernel version of the same transfer. */ buf = spidev->buffer; + bufrx = spidev->bufferrx; total = 0; for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; n; @@ -247,7 +273,7 @@ static int spidev_message(struct spidev_data *spidev, } if (u_tmp->rx_buf) { - k_tmp->rx_buf = buf; + k_tmp->rx_buf = bufrx; if (!access_ok(VERIFY_WRITE, (u8 __user *) (uintptr_t) u_tmp->rx_buf, u_tmp->len)) @@ -261,6 +287,7 @@ static int spidev_message(struct spidev_data *spidev, goto done; } buf += k_tmp->len; + bufrx += k_tmp->len; k_tmp->cs_change = !!u_tmp->cs_change; k_tmp->bits_per_word = u_tmp->bits_per_word; @@ -285,7 +312,7 @@ static int spidev_message(struct spidev_data *spidev, goto done; /* copy any rx data out of bounce buffer */ - buf = spidev->buffer; + buf = spidev->bufferrx; for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) { if (u_tmp->rx_buf) { if (__copy_to_user((u8 __user *) @@ -503,6 +530,15 @@ static int spidev_open(struct inode *inode, struct file *filp) status = -ENOMEM; } } + if (!spidev->bufferrx) { + spidev->bufferrx = kmalloc(bufsiz, GFP_KERNEL); + if (!spidev->bufferrx) { + dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); + kfree(spidev->buffer); + spidev->buffer = NULL; + status = -ENOMEM; + } + } if (status == 0) { spidev->users++; filp->private_data = spidev; @@ -531,6 +567,8 @@ static int spidev_release(struct inode *inode, struct file *filp) kfree(spidev->buffer); spidev->buffer = NULL; + kfree(spidev->bufferrx); + spidev->bufferrx = NULL; /* ... after we unbound from the underlying device? */ spin_lock_irq(&spidev->spi_lock); @@ -674,21 +712,58 @@ static int __init spidev_init(void) spidev_class = class_create(THIS_MODULE, "spidev"); if (IS_ERR(spidev_class)) { - unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); - return PTR_ERR(spidev_class); + status = PTR_ERR(spidev_class); + goto error_class; } status = spi_register_driver(&spidev_spi_driver); - if (status < 0) { - class_destroy(spidev_class); - unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); + if (status < 0) + goto error_register; + + if (busnum != -1 && chipselect != -1) { + struct spi_board_info chip = { + .modalias = "spidev", + .mode = spimode, + .bus_num = busnum, + .chip_select = chipselect, + .max_speed_hz = maxspeed, + }; + + struct spi_master *master; + + master = spi_busnum_to_master(busnum); + if (!master) { + status = -ENODEV; + goto error_busnum; + } + + /* We create a virtual device that will sit on the bus */ + spi = spi_new_device(master, &chip); + if (!spi) { + status = -EBUSY; + goto error_mem; + } + dev_dbg(&spi->dev, "busnum=%d cs=%d bufsiz=%d maxspeed=%d", + busnum, chipselect, bufsiz, maxspeed); } + return 0; +error_mem: +error_busnum: + spi_unregister_driver(&spidev_spi_driver); +error_register: + class_destroy(spidev_class); +error_class: + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); return status; } module_init(spidev_init); static void __exit spidev_exit(void) { + if (spi) { + spi_unregister_device(spi); + spi = NULL; + } spi_unregister_driver(&spidev_spi_driver); class_destroy(spidev_class); unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 11a4b5b3596..d0554125932 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -122,8 +122,6 @@ source "drivers/staging/cxt1e1/Kconfig" source "drivers/staging/xgifb/Kconfig" -source "drivers/staging/msm/Kconfig" - source "drivers/staging/lirc/Kconfig" source "drivers/staging/easycap/Kconfig" diff --git a/drivers/staging/ath6kl/os/linux/include/athendpack_linux.h b/drivers/staging/ath6kl/os/linux/include/athendpack_linux.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/drivers/staging/ath6kl/os/linux/include/athstartpack_linux.h b/drivers/staging/ath6kl/os/linux/include/athstartpack_linux.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/drivers/staging/dream/Kconfig b/drivers/staging/dream/Kconfig new file mode 100644 index 00000000000..0c30b19a5a7 --- /dev/null +++ b/drivers/staging/dream/Kconfig @@ -0,0 +1,13 @@ +config DREAM + tristate "HTC Dream support" + depends on MACH_TROUT + +if DREAM + +source "drivers/staging/dream/camera/Kconfig" + +config INPUT_GPIO + tristate "GPIO driver support" + help + Say Y here if you want to support gpio based keys, wheels etc... +endif diff --git a/drivers/staging/dream/Makefile b/drivers/staging/dream/Makefile new file mode 100644 index 00000000000..fbea0abcc86 --- /dev/null +++ b/drivers/staging/dream/Makefile @@ -0,0 +1,5 @@ +EXTRA_CFLAGS=-Idrivers/staging/dream/include +obj-$(CONFIG_MSM_ADSP) += qdsp5/ +obj-$(CONFIG_MSM_CAMERA) += camera/ +obj-$(CONFIG_INPUT_GPIO) += gpio_axis.o gpio_event.o gpio_input.o gpio_matrix.o gpio_output.o + diff --git a/drivers/staging/gobi/Kconfig b/drivers/staging/gobi/Kconfig new file mode 100644 index 00000000000..99d3b8dd798 --- /dev/null +++ b/drivers/staging/gobi/Kconfig @@ -0,0 +1,5 @@ +config GOBI_USBNET + tristate "Qualcomm GOBI2k and QCQMI support" + help + This module adds network device support for GOBI2k 3G radios. +~ diff --git a/drivers/staging/gobi/QCUSBNet2k/Makefile b/drivers/staging/gobi/QCUSBNet2k/Makefile new file mode 100755 index 00000000000..66c1590d393 --- /dev/null +++ b/drivers/staging/gobi/QCUSBNet2k/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_GOBI_USBNET) += QCUSBNet2k.o +QCUSBNet2k-objs += QCUSBNet.o QMIDevice.o QMI.o diff --git a/drivers/staging/gobi/QCUSBNet2k/QCUSBNet.c b/drivers/staging/gobi/QCUSBNet2k/QCUSBNet.c new file mode 100644 index 00000000000..e7f72e72fb2 --- /dev/null +++ b/drivers/staging/gobi/QCUSBNet2k/QCUSBNet.c @@ -0,0 +1,1227 @@ +/*=========================================================================== +FILE: + QCUSBNet.c + +DESCRIPTION: + Qualcomm USB Network device for Gobi 2000 + +FUNCTIONS: + QCSuspend + QCResume + QCNetDriverBind + QCNetDriverUnbind + QCUSBNetURBCallback + QCUSBNetTXTimeout + QCUSBNetAutoPMThread + QCUSBNetStartXmit + QCUSBNetOpen + QCUSBNetStop + QCUSBNetProbe + QCUSBNetModInit + QCUSBNetModExit + +Copyright (c) 2010, Code Aurora Forum. 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 and +only version 2 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- + +#include "Structs.h" +#include "QMIDevice.h" +#include "QMI.h" + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +// Version Information +#define DRIVER_VERSION "1.0.110" +#define DRIVER_DESC "QCUSBNet2k" + +// Debug flag +int debug; + +// Class should be created during module init, so needs to be global +static struct class * gpClass; + +/*=========================================================================== +METHOD: + QCSuspend (Public Method) + +DESCRIPTION: + Stops QMI traffic while device is suspended + +PARAMETERS + pIntf [ I ] - Pointer to interface + powerEvent [ I ] - Power management event + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int QCSuspend( + struct usb_interface * pIntf, + pm_message_t powerEvent ) +{ + struct usbnet * pDev; + sQCUSBNet * pQCDev; + + if (pIntf == 0) + { + return -ENOMEM; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 )) + pDev = usb_get_intfdata( pIntf ); +#else + pDev = (struct usbnet *)pIntf->dev.platform_data; +#endif + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get netdevice\n" ); + return -ENXIO; + } + + pQCDev = (sQCUSBNet *)pDev->data[0]; + if (pQCDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return -ENXIO; + } + + // Is this autosuspend or system suspend? + // do we allow remote wakeup? +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) + if (pDev->udev->auto_pm == 0) +#else + if ((powerEvent.event & PM_EVENT_AUTO) == 0) +#endif + { + DBG( "device suspended to power level %d\n", + powerEvent.event ); + QSetDownReason( pQCDev, DRIVER_SUSPENDED ); + } + else + { + DBG( "device autosuspend\n" ); + } + + if (powerEvent.event & PM_EVENT_SUSPEND) + { + // Stop QMI read callbacks + KillRead( pQCDev ); + pDev->udev->reset_resume = 0; + + // Store power state to avoid duplicate resumes + pIntf->dev.power.power_state.event = powerEvent.event; + } + else + { + // Other power modes cause QMI connection to be lost + pDev->udev->reset_resume = 1; + } + + // Run usbnet's suspend function + return usbnet_suspend( pIntf, powerEvent ); +} + +/*=========================================================================== +METHOD: + QCResume (Public Method) + +DESCRIPTION: + Resume QMI traffic or recreate QMI device + +PARAMETERS + pIntf [ I ] - Pointer to interface + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int QCResume( struct usb_interface * pIntf ) +{ + struct usbnet * pDev; + sQCUSBNet * pQCDev; + int nRet; + int oldPowerState; + + if (pIntf == 0) + { + return -ENOMEM; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 )) + pDev = usb_get_intfdata( pIntf ); +#else + pDev = (struct usbnet *)pIntf->dev.platform_data; +#endif + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get netdevice\n" ); + return -ENXIO; + } + + pQCDev = (sQCUSBNet *)pDev->data[0]; + if (pQCDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return -ENXIO; + } + + oldPowerState = pIntf->dev.power.power_state.event; + pIntf->dev.power.power_state.event = PM_EVENT_ON; + DBG( "resuming from power mode %d\n", oldPowerState ); + + if (oldPowerState & PM_EVENT_SUSPEND) + { + // It doesn't matter if this is autoresume or system resume + QClearDownReason( pQCDev, DRIVER_SUSPENDED ); + + nRet = usbnet_resume( pIntf ); + if (nRet != 0) + { + DBG( "usbnet_resume error %d\n", nRet ); + return nRet; + } + + // Restart QMI read callbacks + nRet = StartRead( pQCDev ); + if (nRet != 0) + { + DBG( "StartRead error %d\n", nRet ); + return nRet; + } + + // Kick Auto PM thread to process any queued URBs + up( &pQCDev->mAutoPM.mThreadDoWork ); + } + else + { + DBG( "nothing to resume\n" ); + return 0; + } + + return nRet; +} + +/*=========================================================================== +METHOD: + QCNetDriverBind (Public Method) + +DESCRIPTION: + Setup in and out pipes + +PARAMETERS + pDev [ I ] - Pointer to usbnet device + pIntf [ I ] - Pointer to interface + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +static int QCNetDriverBind( + struct usbnet * pDev, + struct usb_interface * pIntf ) +{ + int numEndpoints; + int endpointIndex; + struct usb_host_endpoint * pEndpoint = NULL; + struct usb_host_endpoint * pIn = NULL; + struct usb_host_endpoint * pOut = NULL; + + // Verify one altsetting + if (pIntf->num_altsetting != 1) + { + DBG( "invalid num_altsetting %u\n", pIntf->num_altsetting ); + return -EINVAL; + } + + // Verify correct interface (0) + if (pIntf->cur_altsetting->desc.bInterfaceNumber != 0) + { + DBG( "invalid interface %d\n", + pIntf->cur_altsetting->desc.bInterfaceNumber ); + return -EINVAL; + } + + // Collect In and Out endpoints + numEndpoints = pIntf->cur_altsetting->desc.bNumEndpoints; + for (endpointIndex = 0; endpointIndex < numEndpoints; endpointIndex++) + { + pEndpoint = pIntf->cur_altsetting->endpoint + endpointIndex; + if (pEndpoint == NULL) + { + DBG( "invalid endpoint %u\n", endpointIndex ); + return -EINVAL; + } + + if (usb_endpoint_dir_in( &pEndpoint->desc ) == true + && usb_endpoint_xfer_int( &pEndpoint->desc ) == false) + { + pIn = pEndpoint; + } + else if (usb_endpoint_dir_out( &pEndpoint->desc ) == true) + { + pOut = pEndpoint; + } + } + + if (pIn == NULL || pOut == NULL) + { + DBG( "invalid endpoints\n" ); + return -EINVAL; + } + + if (usb_set_interface( pDev->udev, + pIntf->cur_altsetting->desc.bInterfaceNumber, + 0 ) != 0) + { + DBG( "unable to set interface\n" ); + return -EINVAL; + } + + pDev->in = usb_rcvbulkpipe( pDev->udev, + pIn->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK ); + pDev->out = usb_sndbulkpipe( pDev->udev, + pOut->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK ); + + DBG( "in %x, out %x\n", + pIn->desc.bEndpointAddress, + pOut->desc.bEndpointAddress ); + + // In later versions of the kernel, usbnet helps with this +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 )) + pIntf->dev.platform_data = (void *)pDev; +#endif + + return 0; +} + +/*=========================================================================== +METHOD: + QCNetDriverUnbind (Public Method) + +DESCRIPTION: + Deregisters QMI device (Registration happened in the probe function) + +PARAMETERS + pDev [ I ] - Pointer to usbnet device + pIntfUnused [ I ] - Pointer to interface + +RETURN VALUE: + None +===========================================================================*/ +static void QCNetDriverUnbind( + struct usbnet * pDev, + struct usb_interface * pIntf) +{ + sQCUSBNet * pQCDev = (sQCUSBNet *)pDev->data[0]; + + // Should already be down, but just in case... + netif_carrier_off( pDev->net ); + + DeregisterQMIDevice( pQCDev ); + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 )) + kfree( pDev->net->netdev_ops ); + pDev->net->netdev_ops = NULL; +#endif + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,23 )) + pIntf->dev.platform_data = NULL; +#endif + + kfree( pQCDev ); + pQCDev = NULL; +} + +/*=========================================================================== +METHOD: + QCUSBNetURBCallback (Public Method) + +DESCRIPTION: + Write is complete, cleanup and signal that we're ready for next packet + +PARAMETERS + pURB [ I ] - Pointer to sAutoPM struct + +RETURN VALUE: + None +===========================================================================*/ +void QCUSBNetURBCallback( struct urb * pURB ) +{ + unsigned long activeURBflags; + sAutoPM * pAutoPM = (sAutoPM *)pURB->context; + if (pAutoPM == NULL) + { + // Should never happen + DBG( "bad context\n" ); + return; + } + + if (pURB->status != 0) + { + // Note that in case of an error, the behaviour is no different + DBG( "urb finished with error %d\n", pURB->status ); + } + + // Remove activeURB (memory to be freed later) + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + + // EAGAIN used to signify callback is done + pAutoPM->mpActiveURB = ERR_PTR( -EAGAIN ); + + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + up( &pAutoPM->mThreadDoWork ); + + usb_free_urb( pURB ); +} + +/*=========================================================================== +METHOD: + QCUSBNetTXTimeout (Public Method) + +DESCRIPTION: + Timeout declared by the net driver. Stop all transfers + +PARAMETERS + pNet [ I ] - Pointer to net device + +RETURN VALUE: + None +===========================================================================*/ +void QCUSBNetTXTimeout( struct net_device * pNet ) +{ + struct sQCUSBNet * pQCDev; + sAutoPM * pAutoPM; + sURBList * pURBListEntry; + unsigned long activeURBflags, URBListFlags; + struct usbnet * pDev = netdev_priv( pNet ); + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get usbnet device\n" ); + return; + } + + pQCDev = (sQCUSBNet *)pDev->data[0]; + if (pQCDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return; + } + pAutoPM = &pQCDev->mAutoPM; + + DBG( "\n" ); + + // Stop activeURB + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + + if (pAutoPM->mpActiveURB != NULL) + { + usb_kill_urb( pAutoPM->mpActiveURB ); + } + + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // Cleanup URB List + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + + pURBListEntry = pAutoPM->mpURBList; + while (pURBListEntry != NULL) + { + pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; + usb_free_urb( pURBListEntry->mpURB ); + kfree( pURBListEntry ); + pURBListEntry = pAutoPM->mpURBList; + } + + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + up( &pAutoPM->mThreadDoWork ); + + return; +} + +/*=========================================================================== +METHOD: + QCUSBNetAutoPMThread (Public Method) + +DESCRIPTION: + Handle device Auto PM state asynchronously + Handle network packet transmission asynchronously + +PARAMETERS + pData [ I ] - Pointer to sAutoPM struct + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +static int QCUSBNetAutoPMThread( void * pData ) +{ + unsigned long activeURBflags, URBListFlags; + sURBList * pURBListEntry; + int status; + struct usb_device * pUdev; + sAutoPM * pAutoPM = (sAutoPM *)pData; + if (pAutoPM == NULL) + { + DBG( "passed null pointer\n" ); + return -EINVAL; + } + + pUdev = interface_to_usbdev( pAutoPM->mpIntf ); + + DBG( "traffic thread started\n" ); + + while (pAutoPM->mbExit == false) + { + // Wait for someone to poke us + down( &pAutoPM->mThreadDoWork ); + + // Time to exit? + if (pAutoPM->mbExit == true) + { + // Stop activeURB + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + + if (pAutoPM->mpActiveURB != NULL) + { + usb_kill_urb( pAutoPM->mpActiveURB ); + } + // Will be freed in callback function + + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // Cleanup URB List + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + + pURBListEntry = pAutoPM->mpURBList; + while (pURBListEntry != NULL) + { + pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; + usb_free_urb( pURBListEntry->mpURB ); + kfree( pURBListEntry ); + pURBListEntry = pAutoPM->mpURBList; + } + + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + break; + } + + // Is our URB active? + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + + // EAGAIN used to signify callback is done + if (IS_ERR( pAutoPM->mpActiveURB ) + && PTR_ERR( pAutoPM->mpActiveURB ) == -EAGAIN ) + { + pAutoPM->mpActiveURB = NULL; + + // Restore IRQs so task can sleep + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // URB is done, decrement the Auto PM usage count + usb_autopm_put_interface( pAutoPM->mpIntf ); + + // Lock ActiveURB again + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + } + + if (pAutoPM->mpActiveURB != NULL) + { + // There is already a URB active, go back to sleep + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + continue; + } + + // Is there a URB waiting to be submitted? + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + if (pAutoPM->mpURBList == NULL) + { + // No more URBs to submit, go back to sleep + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + continue; + } + + // Pop an element + pURBListEntry = pAutoPM->mpURBList; + pAutoPM->mpURBList = pAutoPM->mpURBList->mpNext; + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + // Set ActiveURB + pAutoPM->mpActiveURB = pURBListEntry->mpURB; + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // Tell autopm core we need device woken up + status = usb_autopm_get_interface( pAutoPM->mpIntf ); + if (status < 0) + { + DBG( "unable to autoresume interface: %d\n", status ); + + // likely caused by device going from autosuspend -> full suspend + if (status == -EPERM) + { +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) + pUdev->auto_pm = 0; +#endif + QCSuspend( pAutoPM->mpIntf, PMSG_SUSPEND ); + } + + // Add pURBListEntry back onto pAutoPM->mpURBList + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + pURBListEntry->mpNext = pAutoPM->mpURBList; + pAutoPM->mpURBList = pURBListEntry; + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + pAutoPM->mpActiveURB = NULL; + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + + // Go back to sleep + continue; + } + + // Submit URB + status = usb_submit_urb( pAutoPM->mpActiveURB, GFP_KERNEL ); + if (status < 0) + { + // Could happen for a number of reasons + DBG( "Failed to submit URB: %d. Packet dropped\n", status ); + spin_lock_irqsave( &pAutoPM->mActiveURBLock, activeURBflags ); + usb_free_urb( pAutoPM->mpActiveURB ); + pAutoPM->mpActiveURB = NULL; + spin_unlock_irqrestore( &pAutoPM->mActiveURBLock, activeURBflags ); + usb_autopm_put_interface( pAutoPM->mpIntf ); + + // Loop again + up( &pAutoPM->mThreadDoWork ); + } + + kfree( pURBListEntry ); + } + + DBG( "traffic thread exiting\n" ); + pAutoPM->mpThread = NULL; + return 0; +} + +/*=========================================================================== +METHOD: + QCUSBNetStartXmit (Public Method) + +DESCRIPTION: + Convert sk_buff to usb URB and queue for transmit + +PARAMETERS + pNet [ I ] - Pointer to net device + +RETURN VALUE: + NETDEV_TX_OK on success + NETDEV_TX_BUSY on error +===========================================================================*/ +int QCUSBNetStartXmit( + struct sk_buff * pSKB, + struct net_device * pNet ) +{ + unsigned long URBListFlags; + struct sQCUSBNet * pQCDev; + sAutoPM * pAutoPM; + sURBList * pURBListEntry, ** ppURBListEnd; + void * pURBData; + struct usbnet * pDev = netdev_priv( pNet ); + + DBG( "\n" ); + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get usbnet device\n" ); + return NETDEV_TX_BUSY; + } + + pQCDev = (sQCUSBNet *)pDev->data[0]; + if (pQCDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return NETDEV_TX_BUSY; + } + pAutoPM = &pQCDev->mAutoPM; + + if (QTestDownReason( pQCDev, DRIVER_SUSPENDED ) == true) + { + // Should not happen + DBG( "device is suspended\n" ); + dump_stack(); + return NETDEV_TX_BUSY; + } + + // Convert the sk_buff into a URB + + // Allocate URBListEntry + pURBListEntry = kmalloc( sizeof( sURBList ), GFP_ATOMIC ); + if (pURBListEntry == NULL) + { + DBG( "unable to allocate URBList memory\n" ); + return NETDEV_TX_BUSY; + } + pURBListEntry->mpNext = NULL; + + // Allocate URB + pURBListEntry->mpURB = usb_alloc_urb( 0, GFP_ATOMIC ); + if (pURBListEntry->mpURB == NULL) + { + DBG( "unable to allocate URB\n" ); + return NETDEV_TX_BUSY; + } + + // Allocate URB transfer_buffer + pURBData = kmalloc( pSKB->len, GFP_ATOMIC ); + if (pURBData == NULL) + { + DBG( "unable to allocate URB data\n" ); + return NETDEV_TX_BUSY; + } + // Fill will SKB's data + memcpy( pURBData, pSKB->data, pSKB->len ); + + usb_fill_bulk_urb( pURBListEntry->mpURB, + pQCDev->mpNetDev->udev, + pQCDev->mpNetDev->out, + pURBData, + pSKB->len, + QCUSBNetURBCallback, + pAutoPM ); + + // Aquire lock on URBList + spin_lock_irqsave( &pAutoPM->mURBListLock, URBListFlags ); + + // Add URB to end of list + ppURBListEnd = &pAutoPM->mpURBList; + while ((*ppURBListEnd) != NULL) + { + ppURBListEnd = &(*ppURBListEnd)->mpNext; + } + *ppURBListEnd = pURBListEntry; + + spin_unlock_irqrestore( &pAutoPM->mURBListLock, URBListFlags ); + + up( &pAutoPM->mThreadDoWork ); + + // Start transfer timer + pNet->trans_start = jiffies; + // Free SKB + dev_kfree_skb_any( pSKB ); + + return NETDEV_TX_OK; +} + +/*=========================================================================== +METHOD: + QCUSBNetOpen (Public Method) + +DESCRIPTION: + Wrapper to usbnet_open, correctly handling autosuspend + Start AutoPM thread + +PARAMETERS + pNet [ I ] - Pointer to net device + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QCUSBNetOpen( struct net_device * pNet ) +{ + int status = 0; + struct sQCUSBNet * pQCDev; + struct usbnet * pDev = netdev_priv( pNet ); + + if (pDev == NULL) + { + DBG( "failed to get usbnet device\n" ); + return -ENXIO; + } + + pQCDev = (sQCUSBNet *)pDev->data[0]; + if (pQCDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return -ENXIO; + } + + DBG( "\n" ); + + // Start the AutoPM thread + pQCDev->mAutoPM.mpIntf = pQCDev->mpIntf; + pQCDev->mAutoPM.mbExit = false; + pQCDev->mAutoPM.mpURBList = NULL; + pQCDev->mAutoPM.mpActiveURB = NULL; + spin_lock_init( &pQCDev->mAutoPM.mURBListLock ); + spin_lock_init( &pQCDev->mAutoPM.mActiveURBLock ); + sema_init( &pQCDev->mAutoPM.mThreadDoWork, 0 ); + + pQCDev->mAutoPM.mpThread = kthread_run( QCUSBNetAutoPMThread, + &pQCDev->mAutoPM, + "QCUSBNetAutoPMThread" ); + if (IS_ERR( pQCDev->mAutoPM.mpThread )) + { + DBG( "AutoPM thread creation error\n" ); + return PTR_ERR( pQCDev->mAutoPM.mpThread ); + } + + // Allow traffic + QClearDownReason( pQCDev, NET_IFACE_STOPPED ); + + // Pass to usbnet_open if defined + if (pQCDev->mpUSBNetOpen != NULL) + { + status = pQCDev->mpUSBNetOpen( pNet ); + + // If usbnet_open was successful enable Auto PM + if (status == 0) + { +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) + usb_autopm_enable( pQCDev->mpIntf ); +#else + usb_autopm_put_interface( pQCDev->mpIntf ); +#endif + } + } + else + { + DBG( "no USBNetOpen defined\n" ); + } + + return status; +} + +/*=========================================================================== +METHOD: + QCUSBNetStop (Public Method) + +DESCRIPTION: + Wrapper to usbnet_stop, correctly handling autosuspend + Stop AutoPM thread + +PARAMETERS + pNet [ I ] - Pointer to net device + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QCUSBNetStop( struct net_device * pNet ) +{ + struct sQCUSBNet * pQCDev; + struct usbnet * pDev = netdev_priv( pNet ); + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get netdevice\n" ); + return -ENXIO; + } + + pQCDev = (sQCUSBNet *)pDev->data[0]; + if (pQCDev == NULL) + { + DBG( "failed to get QMIDevice\n" ); + return -ENXIO; + } + + // Stop traffic + QSetDownReason( pQCDev, NET_IFACE_STOPPED ); + + // Tell traffic thread to exit + pQCDev->mAutoPM.mbExit = true; + up( &pQCDev->mAutoPM.mThreadDoWork ); + + // Wait for it to exit + while( pQCDev->mAutoPM.mpThread != NULL ) + { + msleep( 100 ); + } + DBG( "thread stopped\n" ); + + // Pass to usbnet_stop, if defined + if (pQCDev->mpUSBNetStop != NULL) + { + return pQCDev->mpUSBNetStop( pNet ); + } + else + { + return 0; + } +} + +/*=========================================================================*/ +// Struct driver_info +/*=========================================================================*/ +static const struct driver_info QCNetInfo = +{ + .description = "QCUSBNet Ethernet Device", + .flags = FLAG_ETHER, + .bind = QCNetDriverBind, + .unbind = QCNetDriverUnbind, + .data = 0, +}; + +/*=========================================================================*/ +// Qualcomm Gobi 2000 VID/PIDs +/*=========================================================================*/ +static const struct usb_device_id QCVIDPIDTable [] = +{ + // Acer Gobi 2000 + { + USB_DEVICE( 0x05c6, 0x9215 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Asus Gobi 2000 + { + USB_DEVICE( 0x05c6, 0x9265 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // CMOTech Gobi 2000 + { + USB_DEVICE( 0x16d8, 0x8002 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Dell Gobi 2000 + { + USB_DEVICE( 0x413c, 0x8186 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Entourage Gobi 2000 + { + USB_DEVICE( 0x1410, 0xa010 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Entourage Gobi 2000 + { + USB_DEVICE( 0x1410, 0xa011 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Entourage Gobi 2000 + { + USB_DEVICE( 0x1410, 0xa012 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Entourage Gobi 2000 + { + USB_DEVICE( 0x1410, 0xa013 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // HP Gobi 2000 + { + USB_DEVICE( 0x03f0, 0x251d ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Lenovo Gobi 2000 + { + USB_DEVICE( 0x05c6, 0x9205 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Panasonic Gobi 2000 + { + USB_DEVICE( 0x04da, 0x250f ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Samsung Gobi 2000 + { + USB_DEVICE( 0x05c6, 0x9245 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x9001 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x9002 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x9003 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x9004 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x9005 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x9006 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x9007 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x9008 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x9009 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sierra Wireless Gobi 2000 + { + USB_DEVICE( 0x1199, 0x900a ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Sony Gobi 2000 + { + USB_DEVICE( 0x05c6, 0x9225 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Top Global Gobi 2000 + { + USB_DEVICE( 0x05c6, 0x9235 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // iRex Technologies Gobi 2000 + { + USB_DEVICE( 0x05c6, 0x9275 ), + .driver_info = (unsigned long)&QCNetInfo + }, + // Generic Gobi 2000 + { + USB_DEVICE( 0x5c6, 0x920B ), + .driver_info = (unsigned long)&QCNetInfo + }, + + //Terminating entry + { } +}; + +MODULE_DEVICE_TABLE( usb, QCVIDPIDTable ); + +/*=========================================================================== +METHOD: + QCUSBNetProbe (Public Method) + +DESCRIPTION: + Run usbnet_probe + Setup QMI device + +PARAMETERS + pIntf [ I ] - Pointer to interface + pVIDPIDs [ I ] - Pointer to VID/PID table + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QCUSBNetProbe( + struct usb_interface * pIntf, + const struct usb_device_id * pVIDPIDs ) +{ + int status; + struct usbnet * pDev; + sQCUSBNet * pQCDev; +#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,29 )) + struct net_device_ops * pNetDevOps; +#endif + + status = usbnet_probe( pIntf, pVIDPIDs ); + if(status < 0 ) + { + DBG( "usbnet_probe failed %d\n", status ); + return status; + } + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,23 )) + pDev = usb_get_intfdata( pIntf ); +#else + pDev = (struct usbnet *)pIntf->dev.platform_data; +#endif + + if (pDev == NULL || pDev->net == NULL) + { + DBG( "failed to get netdevice\n" ); + return -ENXIO; + } + + pQCDev = kmalloc( sizeof( sQCUSBNet ), GFP_KERNEL ); + if (pQCDev == NULL) + { + DBG( "falied to allocate device buffers" ); + return -ENOMEM; + } + + pDev->data[0] = (unsigned long)pQCDev; + + pQCDev->mpNetDev = pDev; + + // Overload PM related network functions +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,29 )) + pQCDev->mpUSBNetOpen = pDev->net->open; + pDev->net->open = QCUSBNetOpen; + pQCDev->mpUSBNetStop = pDev->net->stop; + pDev->net->stop = QCUSBNetStop; + pDev->net->hard_start_xmit = QCUSBNetStartXmit; + pDev->net->tx_timeout = QCUSBNetTXTimeout; +#else + pNetDevOps = kmalloc( sizeof( struct net_device_ops ), GFP_KERNEL ); + if (pNetDevOps == NULL) + { + DBG( "falied to allocate net device ops" ); + return -ENOMEM; + } + memcpy( pNetDevOps, pDev->net->netdev_ops, sizeof( struct net_device_ops ) ); + + pQCDev->mpUSBNetOpen = pNetDevOps->ndo_open; + pNetDevOps->ndo_open = QCUSBNetOpen; + pQCDev->mpUSBNetStop = pNetDevOps->ndo_stop; + pNetDevOps->ndo_stop = QCUSBNetStop; + pNetDevOps->ndo_start_xmit = QCUSBNetStartXmit; + pNetDevOps->ndo_tx_timeout = QCUSBNetTXTimeout; + + pDev->net->netdev_ops = pNetDevOps; +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 )) + memset( &(pQCDev->mpNetDev->stats), 0, sizeof( struct net_device_stats ) ); +#else + memset( &(pQCDev->mpNetDev->net->stats), 0, sizeof( struct net_device_stats ) ); +#endif + + pQCDev->mpIntf = pIntf; + memset( &(pQCDev->mMEID), '0', 14 ); + + DBG( "Mac Address:\n" ); + PrintHex( &pQCDev->mpNetDev->net->dev_addr[0], 6 ); + + pQCDev->mbQMIValid = false; + memset( &pQCDev->mQMIDev, 0, sizeof( sQMIDev ) ); + + pQCDev->mQMIDev.mpDevClass = gpClass; + + sema_init( &pQCDev->mAutoPM.mThreadDoWork, 0 ); + spin_lock_init( &pQCDev->mQMIDev.mClientMemLock ); + + // Default to device down + pQCDev->mDownReason = 0; + QSetDownReason( pQCDev, NO_NDIS_CONNECTION ); + QSetDownReason( pQCDev, NET_IFACE_STOPPED ); + + // Register QMI + status = RegisterQMIDevice( pQCDev ); + if (status != 0) + { + // Clean up + DeregisterQMIDevice( pQCDev ); + return status; + } + + // Success + return status; +} + +EXPORT_SYMBOL_GPL( QCUSBNetProbe ); + +static struct usb_driver QCUSBNet = +{ + .name = "QCUSBNet2k", + .id_table = QCVIDPIDTable, + .probe = QCUSBNetProbe, + .disconnect = usbnet_disconnect, + .suspend = QCSuspend, + .resume = QCResume, + .supports_autosuspend = true, +}; + +/*=========================================================================== +METHOD: + QCUSBNetModInit (Public Method) + +DESCRIPTION: + Initialize module + Create device class + Register out usb_driver struct + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +static int __init QCUSBNetModInit( void ) +{ + gpClass = class_create( THIS_MODULE, "QCQMI" ); + if (IS_ERR( gpClass ) == true) + { + DBG( "error at class_create %ld\n", + PTR_ERR( gpClass ) ); + return -ENOMEM; + } + + // This will be shown whenever driver is loaded + printk( KERN_INFO "%s: %s\n", DRIVER_DESC, DRIVER_VERSION ); + + return usb_register( &QCUSBNet ); +} +module_init( QCUSBNetModInit ); + +/*=========================================================================== +METHOD: + QCUSBNetModExit (Public Method) + +DESCRIPTION: + Deregister module + Destroy device class + +RETURN VALUE: + void +===========================================================================*/ +static void __exit QCUSBNetModExit( void ) +{ + usb_deregister( &QCUSBNet ); + + class_destroy( gpClass ); +} +module_exit( QCUSBNetModExit ); + +#ifdef bool +#undef bool +#endif + +MODULE_VERSION( DRIVER_VERSION ); +MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_LICENSE( "GPL v2" ); + +module_param( debug, bool, S_IRUGO | S_IWUSR ); +MODULE_PARM_DESC( debug, "Debuging enabled or not" ); + diff --git a/drivers/staging/gobi/QCUSBNet2k/QMI.c b/drivers/staging/gobi/QCUSBNet2k/QMI.c new file mode 100644 index 00000000000..fe7eebe3a9d --- /dev/null +++ b/drivers/staging/gobi/QCUSBNet2k/QMI.c @@ -0,0 +1,954 @@ +/*=========================================================================== +FILE: + QMI.c + +DESCRIPTION: + Qualcomm QMI driver code + +FUNCTIONS: + Generic QMUX functions + ParseQMUX + FillQMUX + + Generic QMI functions + GetTLV + ValidQMIMessage + GetQMIMessageID + + Fill Buffers with QMI requests + QMICTLGetClientIDReq + QMICTLReleaseClientIDReq + QMICTLReadyReq + QMIWDSSetEventReportReq + QMIWDSGetPKGSRVCStatusReq + QMIDMSGetMEIDReq + + Parse data from QMI responses + QMICTLGetClientIDResp + QMICTLReleaseClientIDResp + QMIWDSEventResp + QMIDMSGetMEIDResp + +Copyright (c) 2010, Code Aurora Forum. 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 and +only version 2 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include "QMI.h" + + +/*=========================================================================*/ +// Get sizes of buffers needed by QMI requests +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMUXHeaderSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMUXHeaderSize( void ) +{ + return sizeof( sQMUX ); +} + +/*=========================================================================== +METHOD: + QMICTLGetClientIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLGetClientIDReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLGetClientIDReqSize( void ) +{ + return sizeof( sQMUX ) + 10; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq + +RETURN VALUE: + u16 - size of header +===========================================================================*/ +u16 QMICTLReleaseClientIDReqSize( void ) +{ + return sizeof( sQMUX ) + 11; +} + +/*=========================================================================== +METHOD: + QMICTLReadyReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMICTLReadyReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMICTLReadyReqSize( void ) +{ + return sizeof( sQMUX ) + 6; +} + +/*=========================================================================== +METHOD: + QMIWDSSetEventReportReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDSSetEventReportReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIWDSSetEventReportReqSize( void ) +{ + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +METHOD: + QMIWDSGetPKGSRVCStatusReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIWDSGetPKGSRVCStatusReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDReqSize (Public Method) + +DESCRIPTION: + Get size of buffer needed for QMUX + QMIDMSGetMEIDReq + +RETURN VALUE: + u16 - size of buffer +===========================================================================*/ +u16 QMIDMSGetMEIDReqSize( void ) +{ + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================*/ +// Generic QMUX functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + ParseQMUX (Public Method) + +DESCRIPTION: + Remove QMUX headers from a buffer + +PARAMETERS + pClientID [ O ] - On success, will point to Client ID + pBuffer [ I ] - Full Message passed in + buffSize [ I ] - Size of pBuffer + +RETURN VALUE: + int - Positive for size of QMUX header + Negative errno for error +===========================================================================*/ +int ParseQMUX( + u16 * pClientID, + void * pBuffer, + u16 buffSize ) +{ + sQMUX * pQMUXHeader; + + if (pBuffer == 0 || buffSize < 12) + { + return -ENOMEM; + } + + // QMUX Header + pQMUXHeader = (sQMUX *)pBuffer; + + if (pQMUXHeader->mTF != 1 + || pQMUXHeader->mLength != buffSize - 1 + || pQMUXHeader->mCtrlFlag != 0x80 ) + { + return -EINVAL; + } + + // Client ID + *pClientID = (pQMUXHeader->mQMIClientID << 8) + + pQMUXHeader->mQMIService; + + return sizeof( sQMUX ); +} + +/*=========================================================================== +METHOD: + FillQMUX (Public Method) + +DESCRIPTION: + Fill buffer with QMUX headers + +PARAMETERS + clientID [ I ] - Client ID + pBuffer [ O ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer (must be at least 6) + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int FillQMUX( + u16 clientID, + void * pBuffer, + u16 buffSize ) +{ + sQMUX * pQMUXHeader; + + if (pBuffer == 0 || buffSize < sizeof( sQMUX )) + { + return -ENOMEM; + } + + // QMUX Header + pQMUXHeader = (sQMUX *)pBuffer; + + pQMUXHeader->mTF = 1; + pQMUXHeader->mLength = buffSize - 1; + pQMUXHeader->mCtrlFlag = 0; + + // Service and Client ID + pQMUXHeader->mQMIService = clientID & 0xff; + pQMUXHeader->mQMIClientID = clientID >> 8; + + return 0; +} + +/*=========================================================================*/ +// Generic QMI functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + GetTLV (Public Method) + +DESCRIPTION: + Get data bufffer of a specified TLV from a QMI message + + QMI Message shall NOT include SDU + +PARAMETERS + pQMIMessage [ I ] - QMI Message buffer + messageLen [ I ] - Size of QMI Message buffer + type [ I ] - Desired Type + pOutDataBuf [ O ] - Buffer to be filled with TLV + messageLen [ I ] - Size of QMI Message buffer + +RETURN VALUE: + u16 - Size of TLV for success + Negative errno for error +===========================================================================*/ +u16 GetTLV( + void * pQMIMessage, + u16 messageLen, + u8 type, + void * pOutDataBuf, + u16 bufferLen ) +{ + u16 pos; + u16 tlvSize = 0; + u16 cpyCount; + + if (pQMIMessage == 0 || pOutDataBuf == 0) + { + return -ENOMEM; + } + + for (pos = 4; + pos + 3 < messageLen; + pos += tlvSize + 3) + { + tlvSize = *(u16 *)(pQMIMessage + pos + 1); + if (*(u8 *)(pQMIMessage + pos) == type) + { + if (bufferLen < tlvSize) + { + return -ENOMEM; + } + + /* replacement memcpy + memcpy( pOutDataBuf, + pQMIMessage + pos + 3, + tlvSize ); */ + + for (cpyCount = 0; cpyCount < tlvSize; cpyCount++) + { + *((char*)(pOutDataBuf + cpyCount)) = *((char*)(pQMIMessage + pos + 3 + cpyCount)); + } + + return tlvSize; + } + } + + return -ENOMSG; +} + +/*=========================================================================== +METHOD: + ValidQMIMessage (Public Method) + +DESCRIPTION: + Check mandatory TLV in a QMI message + + QMI Message shall NOT include SDU + +PARAMETERS + pQMIMessage [ I ] - QMI Message buffer + messageLen [ I ] - Size of QMI Message buffer + +RETURN VALUE: + int - 0 for success (no error) + Negative errno for error + Positive for QMI error code +===========================================================================*/ +int ValidQMIMessage( + void * pQMIMessage, + u16 messageLen ) +{ + char mandTLV[4]; + + if (GetTLV( pQMIMessage, messageLen, 2, &mandTLV[0], 4 ) == 4) + { + // Found TLV + if (*(u16 *)&mandTLV[0] != 0) + { + return *(u16 *)&mandTLV[2]; + } + else + { + return 0; + } + } + else + { + return -ENOMSG; + } +} + +/*=========================================================================== +METHOD: + GetQMIMessageID (Public Method) + +DESCRIPTION: + Get the message ID of a QMI message + + QMI Message shall NOT include SDU + +PARAMETERS + pQMIMessage [ I ] - QMI Message buffer + messageLen [ I ] - Size of QMI Message buffer + +RETURN VALUE: + int - Positive for message ID + Negative errno for error +===========================================================================*/ +int GetQMIMessageID( + void * pQMIMessage, + u16 messageLen ) +{ + if (messageLen < 2) + { + return -ENODATA; + } + else + { + return *(u16 *)pQMIMessage; + } +} + +/*=========================================================================*/ +// Fill Buffers with QMI requests +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMICTLGetClientIDReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Get Client ID Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + serviceType [ I ] - Service type requested + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLGetClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u8 serviceType ) +{ + if (pBuffer == 0 || buffSize < QMICTLGetClientIDReqSize() ) + { + return -ENOMEM; + } + + // QMI CTL GET CLIENT ID + // Request + *(u8 *)(pBuffer + sizeof( sQMUX ))= 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 2) = 0x0022; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 4) = 0x0004; + // QMI Service Type + *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01; + // Size + *(u16 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x0001; + // QMI svc type + *(u8 *)(pBuffer + sizeof( sQMUX ) + 9) = serviceType; + + // success + return sizeof( sQMUX ) + 10; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Release Client ID Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + clientID [ I ] - Service type requested + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLReleaseClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u16 clientID ) +{ + if (pBuffer == 0 || buffSize < QMICTLReleaseClientIDReqSize() ) + { + return -ENOMEM; + } + + // QMI CTL RELEASE CLIENT ID REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1 ) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 2) = 0x0023; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 4) = 0x0005; + // Release client ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 6) = 0x01; + // Size + *(u16 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x0002; + // QMI svs type / Client ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 9) = clientID; + + // success + return sizeof( sQMUX ) + 11; +} + +/*=========================================================================== +METHOD: + QMICTLReadyReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI CTL Get Version Info Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMICTLReadyReq( + void * pBuffer, + u16 buffSize, + u8 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMICTLReadyReqSize() ) + { + return -ENOMEM; + } + + // QMI CTL GET VERSION INFO REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u8 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 2) = 0x0021; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 4) = 0x0000; + + // success + return sizeof( sQMUX ) + 6; +} + +/*=========================================================================== +METHOD: + QMIWDSSetEventReportReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI WDS Set Event Report Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIWDSSetEventReportReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIWDSSetEventReportReqSize() ) + { + return -ENOMEM; + } + + // QMI WDS SET EVENT REPORT REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 3) = 0x0001; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 5) = 0x0008; + // Report channel rate TLV + *(u8 *)(pBuffer + sizeof( sQMUX ) + 7) = 0x11; + // Size + *(u16 *)(pBuffer + sizeof( sQMUX ) + 8) = 0x0005; + // Stats period + *(u8 *)(pBuffer + sizeof( sQMUX ) + 10) = 0x01; + // Stats mask + *(u32 *)(pBuffer + sizeof( sQMUX ) + 11) = 0x000000ff; + + // success + return sizeof( sQMUX ) + 15; +} + +/*=========================================================================== +METHOD: + QMIWDSGetPKGSRVCStatusReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI WDS Get PKG SRVC Status Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIWDSGetPKGSRVCStatusReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIWDSGetPKGSRVCStatusReqSize() ) + { + return -ENOMEM; + } + + // QMI WDS Get PKG SRVC Status REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 3) = 0x0022; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 5) = 0x0000; + + // success + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDReq (Public Method) + +DESCRIPTION: + Fill buffer with QMI DMS Get Serial Numbers Request + +PARAMETERS + pBuffer [ 0 ] - Buffer to be filled + buffSize [ I ] - Size of pBuffer + transactionID [ I ] - Transaction ID + +RETURN VALUE: + int - Positive for resulting size of pBuffer + Negative errno for error +===========================================================================*/ +int QMIDMSGetMEIDReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ) +{ + if (pBuffer == 0 || buffSize < QMIDMSGetMEIDReqSize() ) + { + return -ENOMEM; + } + + // QMI DMS GET SERIAL NUMBERS REQ + // Request + *(u8 *)(pBuffer + sizeof( sQMUX )) = 0x00; + // Transaction ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 1) = transactionID; + // Message ID + *(u16 *)(pBuffer + sizeof( sQMUX ) + 3) = 0x0025; + // Size of TLV's + *(u16 *)(pBuffer + sizeof( sQMUX ) + 5) = 0x0000; + + // success + return sizeof( sQMUX ) + 7; +} + +/*=========================================================================*/ +// Parse data from QMI responses +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMICTLGetClientIDResp (Public Method) + +DESCRIPTION: + Parse the QMI CTL Get Client ID Resp + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + pClientID [ 0 ] - Recieved client ID + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMICTLGetClientIDResp( + void * pBuffer, + u16 buffSize, + u16 * pClientID ) +{ + int result; + + // Ignore QMUX and SDU + // QMI CTL SDU is 2 bytes, not 3 + u8 offset = sizeof( sQMUX ) + 2; + + if (pBuffer == 0 || buffSize < offset ) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x22) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + return -EFAULT; + } + + result = GetTLV( pBuffer, buffSize, 0x01, pClientID, 2 ); + if (result != 2) + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMICTLReleaseClientIDResp (Public Method) + +DESCRIPTION: + Verify the QMI CTL Release Client ID Resp is valid + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMICTLReleaseClientIDResp( + void * pBuffer, + u16 buffSize ) +{ + int result; + + // Ignore QMUX and SDU + // QMI CTL SDU is 2 bytes, not 3 + u8 offset = sizeof( sQMUX ) + 2; + + if (pBuffer == 0 || buffSize < offset ) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x23) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMIWDSEventResp (Public Method) + +DESCRIPTION: + Parse the QMI WDS Set Event Report Resp/Indication or + QMI WDS Get PKG SRVC Status Resp/Indication + + Return parameters will only be updated if value was received + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + pTXOk [ O ] - Number of transmitted packets without errors + pRXOk [ O ] - Number of recieved packets without errors + pTXErr [ O ] - Number of transmitted packets with framing errors + pRXErr [ O ] - Number of recieved packets with framing errors + pTXOfl [ O ] - Number of transmitted packets dropped due to overflow + pRXOfl [ O ] - Number of recieved packets dropped due to overflow + pTXBytesOk [ O ] - Number of transmitted bytes without errors + pRXBytesOk [ O ] - Number of recieved bytes without errors + pbLinkState [ 0 ] - Is the link active? + pbReconfigure [ 0 ] - Must interface be reconfigured? (reset IP address) + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMIWDSEventResp( + void * pBuffer, + u16 buffSize, + u32 * pTXOk, + u32 * pRXOk, + u32 * pTXErr, + u32 * pRXErr, + u32 * pTXOfl, + u32 * pRXOfl, + u64 * pTXBytesOk, + u64 * pRXBytesOk, + bool * pbLinkState, + bool * pbReconfigure ) +{ + int result; + u8 pktStatusRead[2]; + + // Ignore QMUX and SDU + u8 offset = sizeof( sQMUX ) + 3; + + if (pBuffer == 0 + || buffSize < offset + || pTXOk == 0 + || pRXOk == 0 + || pTXErr == 0 + || pRXErr == 0 + || pTXOfl == 0 + || pRXOfl == 0 + || pTXBytesOk == 0 + || pRXBytesOk == 0 + || pbLinkState == 0 + || pbReconfigure == 0 ) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + // Note: Indications. No Mandatory TLV required + + result = GetQMIMessageID( pBuffer, buffSize ); + // QMI WDS Set Event Report Resp + if (result == 0x01) + { + // TLV's are not mandatory + GetTLV( pBuffer, buffSize, 0x10, (void*)pTXOk, 4 ); + GetTLV( pBuffer, buffSize, 0x11, (void*)pRXOk, 4 ); + GetTLV( pBuffer, buffSize, 0x12, (void*)pTXErr, 4 ); + GetTLV( pBuffer, buffSize, 0x13, (void*)pRXErr, 4 ); + GetTLV( pBuffer, buffSize, 0x14, (void*)pTXOfl, 4 ); + GetTLV( pBuffer, buffSize, 0x15, (void*)pRXOfl, 4 ); + GetTLV( pBuffer, buffSize, 0x19, (void*)pTXBytesOk, 8 ); + GetTLV( pBuffer, buffSize, 0x1A, (void*)pRXBytesOk, 8 ); + } + // QMI WDS Get PKG SRVC Status Resp + else if (result == 0x22) + { + result = GetTLV( pBuffer, buffSize, 0x01, &pktStatusRead[0], 2 ); + // 1 or 2 bytes may be received + if (result >= 1) + { + if (pktStatusRead[0] == 0x02) + { + *pbLinkState = true; + } + else + { + *pbLinkState = false; + } + } + if (result == 2) + { + if (pktStatusRead[1] == 0x01) + { + *pbReconfigure = true; + } + else + { + *pbReconfigure = false; + } + } + + if (result < 0) + { + return result; + } + } + else + { + return -EFAULT; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEIDResp (Public Method) + +DESCRIPTION: + Parse the QMI DMS Get Serial Numbers Resp + +PARAMETERS + pBuffer [ I ] - Buffer to be parsed + buffSize [ I ] - Size of pBuffer + pMEID [ O ] - Device MEID + meidSize [ I ] - Size of MEID buffer (at least 14) + +RETURN VALUE: + int - 0 for success + Negative errno for error +===========================================================================*/ +int QMIDMSGetMEIDResp( + void * pBuffer, + u16 buffSize, + char * pMEID, + int meidSize ) +{ + int result; + + // Ignore QMUX and SDU + u8 offset = sizeof( sQMUX ) + 3; + + if (pBuffer == 0 || buffSize < offset || meidSize < 14 ) + { + return -ENOMEM; + } + + pBuffer = pBuffer + offset; + buffSize -= offset; + + result = GetQMIMessageID( pBuffer, buffSize ); + if (result != 0x25) + { + return -EFAULT; + } + + result = ValidQMIMessage( pBuffer, buffSize ); + if (result != 0) + { + return -EFAULT; + } + + result = GetTLV( pBuffer, buffSize, 0x12, (void*)pMEID, 14 ); + if (result != 14) + { + return -EFAULT; + } + + return 0; +} + diff --git a/drivers/staging/gobi/QCUSBNet2k/QMI.h b/drivers/staging/gobi/QCUSBNet2k/QMI.h new file mode 100644 index 00000000000..4da12853879 --- /dev/null +++ b/drivers/staging/gobi/QCUSBNet2k/QMI.h @@ -0,0 +1,251 @@ +/*=========================================================================== +FILE: + QMI.h + +DESCRIPTION: + Qualcomm QMI driver header + +FUNCTIONS: + Generic QMUX functions + ParseQMUX + FillQMUX + + Generic QMI functions + GetTLV + ValidQMIMessage + GetQMIMessageID + + Get sizes of buffers needed by QMI requests + QMUXHeaderSize + QMICTLGetClientIDReqSize + QMICTLReleaseClientIDReqSize + QMICTLReadyReqSize + QMIWDSSetEventReportReqSize + QMIWDSGetPKGSRVCStatusReqSize + QMIDMSGetMEIDReqSize + + Fill Buffers with QMI requests + QMICTLGetClientIDReq + QMICTLReleaseClientIDReq + QMICTLReadyReq + QMIWDSSetEventReportReq + QMIWDSGetPKGSRVCStatusReq + QMIDMSGetMEIDReq + + Parse data from QMI responses + QMICTLGetClientIDResp + QMICTLReleaseClientIDResp + QMIWDSEventResp + QMIDMSGetMEIDResp + +Copyright (c) 2010, Code Aurora Forum. 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 and +only version 2 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +===========================================================================*/ + +#pragma once + +/*=========================================================================*/ +// Definitions +/*=========================================================================*/ + +// QMI Service Types +#define QMICTL 0 +#define QMIWDS 1 +#define QMIDMS 2 + +#define u8 unsigned char +#define u16 unsigned short +#define u32 unsigned int +#define u64 unsigned long long + +#define bool u8 +#define true 1 +#define false 0 + +#define ENOMEM 12 +#define EFAULT 14 +#define EINVAL 22 +#define ENOMSG 42 +#define ENODATA 61 + +/*=========================================================================*/ +// Struct sQMUX +// +// Structure that defines a QMUX header +/*=========================================================================*/ +typedef struct sQMUX +{ + /* T\F, always 1 */ + u8 mTF; + + /* Size of message */ + u16 mLength; + + /* Control flag */ + u8 mCtrlFlag; + + /* Service Type */ + u8 mQMIService; + + /* Client ID */ + u8 mQMIClientID; + +}__attribute__((__packed__)) sQMUX; + +/*=========================================================================*/ +// Generic QMUX functions +/*=========================================================================*/ + +// Remove QMUX headers from a buffer +int ParseQMUX( + u16 * pClientID, + void * pBuffer, + u16 buffSize ); + +// Fill buffer with QMUX headers +int FillQMUX( + u16 clientID, + void * pBuffer, + u16 buffSize ); + +/*=========================================================================*/ +// Generic QMI functions +/*=========================================================================*/ + +// Get data bufffer of a specified TLV from a QMI message +u16 GetTLV( + void * pQMIMessage, + u16 messageLen, + u8 type, + void * pOutDataBuf, + u16 bufferLen ); + +// Check mandatory TLV in a QMI message +int ValidQMIMessage( + void * pQMIMessage, + u16 messageLen ); + +// Get the message ID of a QMI message +int GetQMIMessageID( + void * pQMIMessage, + u16 messageLen ); + +/*=========================================================================*/ +// Get sizes of buffers needed by QMI requests +/*=========================================================================*/ + +// Get size of buffer needed for QMUX +u16 QMUXHeaderSize( void ); + +// Get size of buffer needed for QMUX + QMICTLGetClientIDReq +u16 QMICTLGetClientIDReqSize( void ); + +// Get size of buffer needed for QMUX + QMICTLReleaseClientIDReq +u16 QMICTLReleaseClientIDReqSize( void ); + +// Get size of buffer needed for QMUX + QMICTLReadyReq +u16 QMICTLReadyReqSize( void ); + +// Get size of buffer needed for QMUX + QMIWDSSetEventReportReq +u16 QMIWDSSetEventReportReqSize( void ); + +// Get size of buffer needed for QMUX + QMIWDSGetPKGSRVCStatusReq +u16 QMIWDSGetPKGSRVCStatusReqSize( void ); + +// Get size of buffer needed for QMUX + QMIDMSGetMEIDReq +u16 QMIDMSGetMEIDReqSize( void ); + +/*=========================================================================*/ +// Fill Buffers with QMI requests +/*=========================================================================*/ + +// Fill buffer with QMI CTL Get Client ID Request +int QMICTLGetClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u8 serviceType ); + +// Fill buffer with QMI CTL Release Client ID Request +int QMICTLReleaseClientIDReq( + void * pBuffer, + u16 buffSize, + u8 transactionID, + u16 clientID ); + +// Fill buffer with QMI CTL Get Version Info Request +int QMICTLReadyReq( + void * pBuffer, + u16 buffSize, + u8 transactionID ); + +// Fill buffer with QMI WDS Set Event Report Request +int QMIWDSSetEventReportReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ); + +// Fill buffer with QMI WDS Get PKG SRVC Status Request +int QMIWDSGetPKGSRVCStatusReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ); + +// Fill buffer with QMI DMS Get Serial Numbers Request +int QMIDMSGetMEIDReq( + void * pBuffer, + u16 buffSize, + u16 transactionID ); + +/*=========================================================================*/ +// Parse data from QMI responses +/*=========================================================================*/ + +// Parse the QMI CTL Get Client ID Resp +int QMICTLGetClientIDResp( + void * pBuffer, + u16 buffSize, + u16 * pClientID ); + +// Verify the QMI CTL Release Client ID Resp is valid +int QMICTLReleaseClientIDResp( + void * pBuffer, + u16 buffSize ); + +// Parse the QMI WDS Set Event Report Resp/Indication or +// QMI WDS Get PKG SRVC Status Resp/Indication +int QMIWDSEventResp( + void * pBuffer, + u16 buffSize, + u32 * pTXOk, + u32 * pRXOk, + u32 * pTXErr, + u32 * pRXErr, + u32 * pTXOfl, + u32 * pRXOfl, + u64 * pTXBytesOk, + u64 * pRXBytesOk, + bool * pbLinkState, + bool * pbReconfigure ); + +// Parse the QMI DMS Get Serial Numbers Resp +int QMIDMSGetMEIDResp( + void * pBuffer, + u16 buffSize, + char * pMEID, + int meidSize ); + diff --git a/drivers/staging/gobi/QCUSBNet2k/QMIDevice.c b/drivers/staging/gobi/QCUSBNet2k/QMIDevice.c new file mode 100644 index 00000000000..668328cfe25 --- /dev/null +++ b/drivers/staging/gobi/QCUSBNet2k/QMIDevice.c @@ -0,0 +1,3129 @@ +/*=========================================================================== +FILE: + QMIDevice.c + +DESCRIPTION: + Functions related to the QMI interface device + +FUNCTIONS: + Generic functions + IsDeviceValid + PrintHex + QSetDownReason + QClearDownReason + QTestDownReason + + Driver level asynchronous read functions + ReadCallback + IntCallback + StartRead + KillRead + + Internal read/write functions + ReadAsync + UpSem + ReadSync + WriteSyncCallback + WriteSync + + Internal memory management functions + GetClientID + ReleaseClientID + FindClientMem + AddToReadMemList + PopFromReadMemList + AddToNotifyList + NotifyAndPopNotifyList + AddToURBList + PopFromURBList + + Userspace wrappers + UserspaceOpen + UserspaceIOCTL + UserspaceClose + UserspaceRead + UserspaceWrite + + Initializer and destructor + RegisterQMIDevice + DeregisterQMIDevice + + Driver level client management + QMIReady + QMIWDSCallback + SetupQMIWDSCallback + QMIDMSGetMEID + +Copyright (c) 2010, Code Aurora Forum. 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 and +only version 2 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include "QMIDevice.h" + +//----------------------------------------------------------------------------- +// Definitions +//----------------------------------------------------------------------------- + +extern int debug; + +// Prototype to QCSuspend function +int QCSuspend( + struct usb_interface * pIntf, + pm_message_t powerEvent ); + +// IOCTL to generate a client ID for this service type +#define IOCTL_QMI_GET_SERVICE_FILE 0x8BE0 + 1 + +// IOCTL to get the VIDPID of the device +#define IOCTL_QMI_GET_DEVICE_VIDPID 0x8BE0 + 2 + +// IOCTL to get the MEID of the device +#define IOCTL_QMI_GET_DEVICE_MEID 0x8BE0 + 3 + +// CDC GET_ENCAPSULATED_RESPONSE packet +#define CDC_GET_ENCAPSULATED_RESPONSE 0x01A1ll + +// CDC CONNECTION_SPEED_CHANGE indication packet +#define CDC_CONNECTION_SPEED_CHANGE 0x08000000002AA1ll + +/*=========================================================================*/ +// UserspaceQMIFops +// QMI device's userspace file operations +/*=========================================================================*/ +struct file_operations UserspaceQMIFops = +{ + .owner = THIS_MODULE, + .read = UserspaceRead, + .write = UserspaceWrite, + .ioctl = UserspaceIOCTL, + .open = UserspaceOpen, + .flush = UserspaceClose, +}; + +/*=========================================================================*/ +// Generic functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + IsDeviceValid (Public Method) + +DESCRIPTION: + Basic test to see if device memory is valid + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + bool +===========================================================================*/ +bool IsDeviceValid( sQCUSBNet * pDev ) +{ + if (pDev == NULL) + { + return false; + } + + if (pDev->mbQMIValid == false) + { + return false; + } + + return true; +} + +/*=========================================================================== +METHOD: + PrintHex (Public Method) + +DESCRIPTION: + Print Hex data, for debug purposes + +PARAMETERS: + pBuffer [ I ] - Data buffer + bufSize [ I ] - Size of data buffer + +RETURN VALUE: + None +===========================================================================*/ +void PrintHex( + void * pBuffer, + u16 bufSize ) +{ + char * pPrintBuf; + u16 pos; + int status; + + pPrintBuf = kmalloc( bufSize * 3 + 1, GFP_ATOMIC ); + if (pPrintBuf == NULL) + { + DBG( "Unable to allocate buffer\n" ); + return; + } + memset( pPrintBuf, 0 , bufSize * 3 + 1 ); + + for (pos = 0; pos < bufSize; pos++) + { + status = snprintf( (pPrintBuf + (pos * 3)), + 4, + "%02X ", + *(u8 *)(pBuffer + pos) ); + if (status != 3) + { + DBG( "snprintf error %d\n", status ); + return; + } + } + + DBG( " : %s\n", pPrintBuf ); + + kfree( pPrintBuf ); + pPrintBuf = NULL; + return; +} + +/*=========================================================================== +METHOD: + QSetDownReason (Public Method) + +DESCRIPTION: + Sets mDownReason and turns carrier off + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is down + +RETURN VALUE: + None +===========================================================================*/ +void QSetDownReason( + sQCUSBNet * pDev, + u8 reason ) +{ + set_bit( reason, &pDev->mDownReason ); + + netif_carrier_off( pDev->mpNetDev->net ); +} + +/*=========================================================================== +METHOD: + QClearDownReason (Public Method) + +DESCRIPTION: + Clear mDownReason and may turn carrier on + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is no longer down + +RETURN VALUE: + None +===========================================================================*/ +void QClearDownReason( + sQCUSBNet * pDev, + u8 reason ) +{ + clear_bit( reason, &pDev->mDownReason ); + + if (pDev->mDownReason == 0) + { + netif_carrier_on( pDev->mpNetDev->net ); + } +} + +/*=========================================================================== +METHOD: + QTestDownReason (Public Method) + +DESCRIPTION: + Test mDownReason and returns whether reason is set + +PARAMETERS + pDev [ I ] - Device specific memory + reason [ I ] - Reason device is down + +RETURN VALUE: + bool +===========================================================================*/ +bool QTestDownReason( + sQCUSBNet * pDev, + u8 reason ) +{ + return test_bit( reason, &pDev->mDownReason ); +} + +/*=========================================================================*/ +// Driver level asynchronous read functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + ReadCallback (Public Method) + +DESCRIPTION: + Put the data in storage and notify anyone waiting for data + +PARAMETERS + pReadURB [ I ] - URB this callback is run for + +RETURN VALUE: + None +===========================================================================*/ +void ReadCallback( struct urb * pReadURB ) +{ + int result; + u16 clientID; + sClientMemList * pClientMem; + void * pData; + void * pDataCopy; + u16 dataSize; + sQCUSBNet * pDev; + unsigned long flags; + u16 transactionID; + + if (pReadURB == NULL) + { + DBG( "bad read URB\n" ); + return; + } + + pDev = pReadURB->context; + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return; + } + + if (pReadURB->status != 0) + { + DBG( "Read status = %d\n", pReadURB->status ); + return; + } + DBG( "Read %d bytes\n", pReadURB->actual_length ); + + pData = pReadURB->transfer_buffer; + dataSize = pReadURB->actual_length; + + PrintHex( pData, dataSize ); + + result = ParseQMUX( &clientID, + pData, + dataSize ); + if (result < 0) + { + DBG( "Read error parsing QMUX %d\n", result ); + return; + } + + // Grab transaction ID + + // Data large enough? + if (dataSize < result + 3) + { + DBG( "Data buffer too small to parse\n" ); + return; + } + + // Transaction ID size is 1 for QMICTL, 2 for others + if (clientID == QMICTL) + { + transactionID = *(u8*)(pData + result + 1); + } + else + { + transactionID = *(u16*)(pData + result + 1); + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Find memory storage for this service and Client ID + // Not using FindClientMem because it can't handle broadcasts + pClientMem = pDev->mQMIDev.mpClientMemList; + while (pClientMem != NULL) + { + if (pClientMem->mClientID == clientID + || (pClientMem->mClientID | 0xff00) == clientID) + { + // Make copy of pData + pDataCopy = kmalloc( dataSize, GFP_ATOMIC ); + memcpy( pDataCopy, pData, dataSize ); + + if (AddToReadMemList( pDev, + pClientMem->mClientID, + transactionID, + pDataCopy, + dataSize ) == false) + { + DBG( "Error allocating pReadMemListEntry " + "read will be discarded\n" ); + kfree( pDataCopy ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return; + } + + // Success + DBG( "Creating new readListEntry for client 0x%04X, TID %x\n", + clientID, + transactionID ); + + // Notify this client data exists + NotifyAndPopNotifyList( pDev, + pClientMem->mClientID, + transactionID ); + + // Not a broadcast + if (clientID >> 8 != 0xff) + { + break; + } + } + + // Next element + pClientMem = pClientMem->mpNext; + } + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); +} + +/*=========================================================================== +METHOD: + IntCallback (Public Method) + +DESCRIPTION: + Data is available, fire off a read URB + +PARAMETERS + pIntURB [ I ] - URB this callback is run for + +RETURN VALUE: + None +===========================================================================*/ +void IntCallback( struct urb * pIntURB ) +{ + int status; + int interval; + + sQCUSBNet * pDev = (sQCUSBNet *)pIntURB->context; + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return; + } + + // Verify this was a normal interrupt + if (pIntURB->status != 0) + { + DBG( "Int status = %d\n", pIntURB->status ); + + // Ignore EOVERFLOW errors + if (pIntURB->status != -EOVERFLOW) + { + // Read 'thread' dies here + return; + } + } + else + { + // CDC GET_ENCAPSULATED_RESPONSE + if ((pIntURB->actual_length == 8) + && (*(u64*)pIntURB->transfer_buffer == CDC_GET_ENCAPSULATED_RESPONSE)) + { + // Time to read + usb_fill_control_urb( pDev->mQMIDev.mpReadURB, + pDev->mpNetDev->udev, + usb_rcvctrlpipe( pDev->mpNetDev->udev, 0 ), + (unsigned char *)pDev->mQMIDev.mpReadSetupPacket, + pDev->mQMIDev.mpReadBuffer, + DEFAULT_READ_URB_LENGTH, + ReadCallback, + pDev ); + status = usb_submit_urb( pDev->mQMIDev.mpReadURB, GFP_ATOMIC ); + if (status != 0) + { + DBG( "Error submitting Read URB %d\n", status ); + return; + } + } + // CDC CONNECTION_SPEED_CHANGE + else if ((pIntURB->actual_length == 16) + && (*(u64*)pIntURB->transfer_buffer == CDC_CONNECTION_SPEED_CHANGE)) + { + // if upstream or downstream is 0, stop traffic. Otherwise resume it + if ((*(u32*)(pIntURB->transfer_buffer + 8) == 0) + || (*(u32*)(pIntURB->transfer_buffer + 12) == 0)) + { + QSetDownReason( pDev, CDC_CONNECTION_SPEED ); + DBG( "traffic stopping due to CONNECTION_SPEED_CHANGE\n" ); + } + else + { + QClearDownReason( pDev, CDC_CONNECTION_SPEED ); + DBG( "resuming traffic due to CONNECTION_SPEED_CHANGE\n" ); + } + } + else + { + DBG( "ignoring invalid interrupt in packet\n" ); + PrintHex( pIntURB->transfer_buffer, pIntURB->actual_length ); + } + } + + interval = (pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ? 7 : 3; + + // Reschedule interrupt URB + usb_fill_int_urb( pIntURB, + pIntURB->dev, + pIntURB->pipe, + pIntURB->transfer_buffer, + pIntURB->transfer_buffer_length, + pIntURB->complete, + pIntURB->context, + interval ); + status = usb_submit_urb( pIntURB, GFP_ATOMIC ); + if (status != 0) + { + DBG( "Error re-submitting Int URB %d\n", status ); + } + return; +} + +/*=========================================================================== +METHOD: + StartRead (Public Method) + +DESCRIPTION: + Start continuous read "thread" (callback driven) + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int StartRead( sQCUSBNet * pDev ) +{ + int interval; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Allocate URB buffers + pDev->mQMIDev.mpReadURB = usb_alloc_urb( 0, GFP_KERNEL ); + if (pDev->mQMIDev.mpReadURB == NULL) + { + DBG( "Error allocating read urb\n" ); + return -ENOMEM; + } + + pDev->mQMIDev.mpIntURB = usb_alloc_urb( 0, GFP_KERNEL ); + if (pDev->mQMIDev.mpIntURB == NULL) + { + DBG( "Error allocating int urb\n" ); + return -ENOMEM; + } + + // Create data buffers + pDev->mQMIDev.mpReadBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL ); + if (pDev->mQMIDev.mpReadBuffer == NULL) + { + DBG( "Error allocating read buffer\n" ); + return -ENOMEM; + } + + pDev->mQMIDev.mpIntBuffer = kmalloc( DEFAULT_READ_URB_LENGTH, GFP_KERNEL ); + if (pDev->mQMIDev.mpIntBuffer == NULL) + { + DBG( "Error allocating int buffer\n" ); + return -ENOMEM; + } + + pDev->mQMIDev.mpReadSetupPacket = kmalloc( sizeof( sURBSetupPacket ), + GFP_KERNEL ); + if (pDev->mQMIDev.mpReadSetupPacket == NULL) + { + DBG( "Error allocating setup packet buffer\n" ); + return -ENOMEM; + } + + // CDC Get Encapsulated Response packet + pDev->mQMIDev.mpReadSetupPacket->mRequestType = 0xA1; + pDev->mQMIDev.mpReadSetupPacket->mRequestCode = 1; + pDev->mQMIDev.mpReadSetupPacket->mValue = 0; + pDev->mQMIDev.mpReadSetupPacket->mIndex = 0; + pDev->mQMIDev.mpReadSetupPacket->mLength = DEFAULT_READ_URB_LENGTH; + + interval = (pDev->mpNetDev->udev->speed == USB_SPEED_HIGH) ? 7 : 3; + + // Schedule interrupt URB + usb_fill_int_urb( pDev->mQMIDev.mpIntURB, + pDev->mpNetDev->udev, + usb_rcvintpipe( pDev->mpNetDev->udev, 0x81 ), + pDev->mQMIDev.mpIntBuffer, + DEFAULT_READ_URB_LENGTH, + IntCallback, + pDev, + interval ); + return usb_submit_urb( pDev->mQMIDev.mpIntURB, GFP_KERNEL ); +} + +/*=========================================================================== +METHOD: + KillRead (Public Method) + +DESCRIPTION: + Kill continuous read "thread" + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + None +===========================================================================*/ +void KillRead( sQCUSBNet * pDev ) +{ + // Stop reading + if (pDev->mQMIDev.mpReadURB != NULL) + { + DBG( "Killng read URB\n" ); + usb_kill_urb( pDev->mQMIDev.mpReadURB ); + } + + if (pDev->mQMIDev.mpIntURB != NULL) + { + DBG( "Killng int URB\n" ); + usb_kill_urb( pDev->mQMIDev.mpIntURB ); + } + + // Release buffers + kfree( pDev->mQMIDev.mpReadSetupPacket ); + pDev->mQMIDev.mpReadSetupPacket = NULL; + kfree( pDev->mQMIDev.mpReadBuffer ); + pDev->mQMIDev.mpReadBuffer = NULL; + kfree( pDev->mQMIDev.mpIntBuffer ); + pDev->mQMIDev.mpIntBuffer = NULL; + + // Release URB's + usb_free_urb( pDev->mQMIDev.mpReadURB ); + pDev->mQMIDev.mpReadURB = NULL; + usb_free_urb( pDev->mQMIDev.mpIntURB ); + pDev->mQMIDev.mpIntURB = NULL; +} + +/*=========================================================================*/ +// Internal read/write functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + ReadAsync (Public Method) + +DESCRIPTION: + Start asynchronous read + NOTE: Reading client's data store, not device + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + pCallback [ I ] - Callback to be executed when data is available + pData [ I ] - Data buffer that willl be passed (unmodified) + to callback + +RETURN VALUE: + int - 0 for success + negative errno for failure +===========================================================================*/ +int ReadAsync( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (*pCallback)(sQCUSBNet*, u16, void *), + void * pData ) +{ + sClientMemList * pClientMem; + sReadMemList ** ppReadMemList; + + unsigned long flags; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Find memory storage for this client ID + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find matching client ID 0x%04X\n", + clientID ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -ENXIO; + } + + ppReadMemList = &(pClientMem->mpList); + + // Does data already exist? + while (*ppReadMemList != NULL) + { + // Is this element our data? + if (transactionID == 0 + || transactionID == (*ppReadMemList)->mTransactionID) + { + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Run our own callback + pCallback( pDev, clientID, pData ); + + return 0; + } + + // Next + ppReadMemList = &(*ppReadMemList)->mpNext; + } + + // Data not found, add ourself to list of waiters + if (AddToNotifyList( pDev, + clientID, + transactionID, + pCallback, + pData ) == false) + { + DBG( "Unable to register for notification\n" ); + } + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Success + return 0; +} + +/*=========================================================================== +METHOD: + UpSem (Public Method) + +DESCRIPTION: + Notification function for synchronous read + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + pData [ I ] - Buffer that holds semaphore to be up()-ed + +RETURN VALUE: + None +===========================================================================*/ +void UpSem( + sQCUSBNet * pDev, + u16 clientID, + void * pData ) +{ + DBG( "0x%04X\n", clientID ); + + up( (struct semaphore *)pData ); + return; +} + +/*=========================================================================== +METHOD: + ReadSync (Public Method) + +DESCRIPTION: + Start synchronous read + NOTE: Reading client's data store, not device + +PARAMETERS: + pDev [ I ] - Device specific memory + ppOutBuffer [I/O] - On success, will be filled with a + pointer to read buffer + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + +RETURN VALUE: + int - size of data read for success + negative errno for failure +===========================================================================*/ +int ReadSync( + sQCUSBNet * pDev, + void ** ppOutBuffer, + u16 clientID, + u16 transactionID ) +{ + int result; + sClientMemList * pClientMem; + sNotifyList ** ppNotifyList, * pDelNotifyListEntry; + struct semaphore readSem; + void * pData; + unsigned long flags; + u16 dataSize; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Find memory storage for this Client ID + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find matching client ID 0x%04X\n", + clientID ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -ENXIO; + } + + // Note: in cases where read is interrupted, + // this will verify client is still valid + while (PopFromReadMemList( pDev, + clientID, + transactionID, + &pData, + &dataSize ) == false) + { + // Data does not yet exist, wait + sema_init( &readSem, 0 ); + + // Add ourself to list of waiters + if (AddToNotifyList( pDev, + clientID, + transactionID, + UpSem, + &readSem ) == false) + { + DBG( "unable to register for notification\n" ); + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -EFAULT; + } + + // End critical section while we block + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Wait for notification + result = down_interruptible( &readSem ); + if (result != 0) + { + DBG( "Interrupted %d\n", result ); + + // readSem will fall out of scope, + // remove from notify list so it's not referenced + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + ppNotifyList = &(pClientMem->mpReadNotifyList); + pDelNotifyListEntry = NULL; + + // Find and delete matching entry + while (*ppNotifyList != NULL) + { + if ((*ppNotifyList)->mpData == &readSem) + { + pDelNotifyListEntry = *ppNotifyList; + *ppNotifyList = (*ppNotifyList)->mpNext; + kfree( pDelNotifyListEntry ); + break; + } + + // Next + ppNotifyList = &(*ppNotifyList)->mpNext; + } + + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -EINTR; + } + + // Verify device is still valid + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Restart critical section and continue loop + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + } + + // End Critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Success + *ppOutBuffer = pData; + + return dataSize; +} + +/*=========================================================================== +METHOD: + WriteSyncCallback (Public Method) + +DESCRIPTION: + Write callback + +PARAMETERS + pWriteURB [ I ] - URB this callback is run for + +RETURN VALUE: + None +===========================================================================*/ +void WriteSyncCallback( struct urb * pWriteURB ) +{ + if (pWriteURB == NULL) + { + DBG( "null urb\n" ); + return; + } + + DBG( "Write status/size %d/%d\n", + pWriteURB->status, + pWriteURB->actual_length ); + + // Notify that write has completed by up()-ing semeaphore + up( (struct semaphore * )pWriteURB->context ); + + return; +} + +/*=========================================================================== +METHOD: + WriteSync (Public Method) + +DESCRIPTION: + Start synchronous write + +PARAMETERS: + pDev [ I ] - Device specific memory + pWriteBuffer [ I ] - Data to be written + writeBufferSize [ I ] - Size of data to be written + clientID [ I ] - Client ID of requester + +RETURN VALUE: + int - write size (includes QMUX) + negative errno for failure +===========================================================================*/ +int WriteSync( + sQCUSBNet * pDev, + char * pWriteBuffer, + int writeBufferSize, + u16 clientID ) +{ + int result; + struct semaphore writeSem; + struct urb * pWriteURB; + sURBSetupPacket writeSetup; + unsigned long flags; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + pWriteURB = usb_alloc_urb( 0, GFP_KERNEL ); + if (pWriteURB == NULL) + { + DBG( "URB mem error\n" ); + return -ENOMEM; + } + + // Fill writeBuffer with QMUX + result = FillQMUX( clientID, pWriteBuffer, writeBufferSize ); + if (result < 0) + { + usb_free_urb( pWriteURB ); + return result; + } + + // CDC Send Encapsulated Request packet + writeSetup.mRequestType = 0x21; + writeSetup.mRequestCode = 0; + writeSetup.mValue = 0; + writeSetup.mIndex = 0; + writeSetup.mLength = 0; + writeSetup.mLength = writeBufferSize; + + // Create URB + usb_fill_control_urb( pWriteURB, + pDev->mpNetDev->udev, + usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ), + (unsigned char *)&writeSetup, + (void*)pWriteBuffer, + writeBufferSize, + NULL, + pDev ); + + DBG( "Actual Write:\n" ); + PrintHex( pWriteBuffer, writeBufferSize ); + + sema_init( &writeSem, 0 ); + + pWriteURB->complete = WriteSyncCallback; + pWriteURB->context = &writeSem; + + // Wake device + result = usb_autopm_get_interface( pDev->mpIntf ); + if (result < 0) + { + DBG( "unable to resume interface: %d\n", result ); + + // Likely caused by device going from autosuspend -> full suspend + if (result == -EPERM) + { +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,33 )) + pDev->mpNetDev->udev->auto_pm = 0; +#endif + QCSuspend( pDev->mpIntf, PMSG_SUSPEND ); + } + + return result; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + if (AddToURBList( pDev, clientID, pWriteURB ) == false) + { + usb_free_urb( pWriteURB ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + usb_autopm_put_interface( pDev->mpIntf ); + return -EINVAL; + } + + result = usb_submit_urb( pWriteURB, GFP_KERNEL ); + if (result < 0) + { + DBG( "submit URB error %d\n", result ); + + // Get URB back so we can destroy it + if (PopFromURBList( pDev, clientID ) != pWriteURB) + { + // This shouldn't happen + DBG( "Didn't get write URB back\n" ); + } + + usb_free_urb( pWriteURB ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + usb_autopm_put_interface( pDev->mpIntf ); + return result; + } + + // End critical section while we block + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // Wait for write to finish + result = down_interruptible( &writeSem ); + + // Verify device is still valid + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Write is done, release device + usb_autopm_put_interface( pDev->mpIntf ); + + // Restart critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Get URB back so we can destroy it + if (PopFromURBList( pDev, clientID ) != pWriteURB) + { + // This shouldn't happen + DBG( "Didn't get write URB back\n" ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -EINVAL; + } + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + if (result == 0) + { + // Write is finished + if (pWriteURB->status == 0) + { + // Return number of bytes that were supposed to have been written, + // not size of QMI request + result = writeBufferSize; + } + else + { + DBG( "bad status = %d\n", pWriteURB->status ); + + // Return error value + result = pWriteURB->status; + } + } + else + { + // We have been forcibly interrupted + DBG( "Interrupted %d !!!\n", result ); + DBG( "Device may be in bad state and need reset !!!\n" ); + + // URB has not finished + usb_kill_urb( pWriteURB ); + } + + usb_free_urb( pWriteURB ); + + return result; +} + +/*=========================================================================*/ +// Internal memory management functions +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + GetClientID (Public Method) + +DESCRIPTION: + Construct object/load file into memory + +PARAMETERS: + pDev [ I ] - Device specific memory + serviceType [ I ] - Desired QMI service type + +RETURN VALUE: + int - Client ID for success (positive) + Negative errno for error +===========================================================================*/ +int GetClientID( + sQCUSBNet * pDev, + u8 serviceType ) +{ + u16 clientID; + sClientMemList ** ppClientMem; + int result; + void * pWriteBuffer; + u16 writeBufferSize; + void * pReadBuffer; + u16 readBufferSize; + unsigned long flags; + u8 transactionID; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device!\n" ); + return -ENXIO; + } + + // Run QMI request to be asigned a Client ID + if (serviceType != 0) + { + writeBufferSize = QMICTLGetClientIDReqSize(); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + return -ENOMEM; + } + + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + if (transactionID == 0) + { + atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + } + result = QMICTLGetClientIDReq( pWriteBuffer, + writeBufferSize, + transactionID, + serviceType ); + if (result < 0) + { + kfree( pWriteBuffer ); + return result; + } + + result = WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + QMICTL ); + kfree( pWriteBuffer ); + + if (result < 0) + { + return result; + } + + result = ReadSync( pDev, + &pReadBuffer, + QMICTL, + transactionID ); + if (result < 0) + { + DBG( "bad read data %d\n", result ); + return result; + } + readBufferSize = result; + + result = QMICTLGetClientIDResp( pReadBuffer, + readBufferSize, + &clientID ); + kfree( pReadBuffer ); + + if (result < 0) + { + return result; + } + } + else + { + // QMI CTL will always have client ID 0 + clientID = 0; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Verify client is not already allocated + if (FindClientMem( pDev, clientID ) != NULL) + { + DBG( "Client memory already exists\n" ); + + // End Critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -ETOOMANYREFS; + } + + // Go to last entry in client mem list + ppClientMem = &pDev->mQMIDev.mpClientMemList; + while (*ppClientMem != NULL) + { + ppClientMem = &(*ppClientMem)->mpNext; + } + + // Create locations for read to place data into + *ppClientMem = kmalloc( sizeof( sClientMemList ), GFP_ATOMIC ); + if (*ppClientMem == NULL) + { + DBG( "Error allocating read list\n" ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + return -ENOMEM; + } + + (*ppClientMem)->mClientID = clientID; + (*ppClientMem)->mpList = NULL; + (*ppClientMem)->mpReadNotifyList = NULL; + (*ppClientMem)->mpURBList = NULL; + (*ppClientMem)->mpNext = NULL; + + + // End Critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + return clientID; +} + +/*=========================================================================== +METHOD: + ReleaseClientID (Public Method) + +DESCRIPTION: + Release QMI client and free memory + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + +RETURN VALUE: + None +===========================================================================*/ +void ReleaseClientID( + sQCUSBNet * pDev, + u16 clientID ) +{ + int result; + sClientMemList ** ppDelClientMem; + sClientMemList * pNextClientMem; + struct urb * pDelURB; + void * pDelData; + u16 dataSize; + void * pWriteBuffer; + u16 writeBufferSize; + void * pReadBuffer; + u16 readBufferSize; + unsigned long flags; + u8 transactionID; + + // Is device is still valid? + if (IsDeviceValid( pDev ) == false) + { + DBG( "invalid device\n" ); + return; + } + + DBG( "releasing 0x%04X\n", clientID ); + + // Run QMI ReleaseClientID if this isn't QMICTL + if (clientID != QMICTL) + { + // Note: all errors are non fatal, as we always want to delete + // client memory in latter part of function + + writeBufferSize = QMICTLReleaseClientIDReqSize(); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + DBG( "memory error\n" ); + } + else + { + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + if (transactionID == 0) + { + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + } + result = QMICTLReleaseClientIDReq( pWriteBuffer, + writeBufferSize, + transactionID, + clientID ); + if (result < 0) + { + kfree( pWriteBuffer ); + DBG( "error %d filling req buffer\n", result ); + } + else + { + result = WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + QMICTL ); + kfree( pWriteBuffer ); + + if (result < 0) + { + DBG( "bad write status %d\n", result ); + } + else + { + result = ReadSync( pDev, + &pReadBuffer, + QMICTL, + transactionID ); + if (result < 0) + { + DBG( "bad read status %d\n", result ); + } + else + { + readBufferSize = result; + + result = QMICTLReleaseClientIDResp( pReadBuffer, + readBufferSize ); + kfree( pReadBuffer ); + + if (result < 0) + { + DBG( "error %d parsing response\n", result ); + } + } + } + } + } + } + + // Cleaning up client memory + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Can't use FindClientMem, I need to keep pointer of previous + ppDelClientMem = &pDev->mQMIDev.mpClientMemList; + while (*ppDelClientMem != NULL) + { + if ((*ppDelClientMem)->mClientID == clientID) + { + pNextClientMem = (*ppDelClientMem)->mpNext; + + // Notify all clients + while (NotifyAndPopNotifyList( pDev, + clientID, + 0 ) == true ); + + // Kill and free all URB's + pDelURB = PopFromURBList( pDev, clientID ); + while (pDelURB != NULL) + { + usb_kill_urb( pDelURB ); + usb_free_urb( pDelURB ); + pDelURB = PopFromURBList( pDev, clientID ); + } + + // Free any unread data + while (PopFromReadMemList( pDev, + clientID, + 0, + &pDelData, + &dataSize ) == true ) + { + kfree( pDelData ); + } + + // Delete client Mem + kfree( *ppDelClientMem ); + + // Overwrite the pointer that was to this client mem + *ppDelClientMem = pNextClientMem; + } + else + { + // I now point to ( a pointer of ((the node I was at)'s mpNext)) + ppDelClientMem = &(*ppDelClientMem)->mpNext; + } + } + + // End Critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + return; +} + +/*=========================================================================== +METHOD: + FindClientMem (Public Method) + +DESCRIPTION: + Find this client's memory + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + +RETURN VALUE: + sClientMemList - Pointer to requested sClientMemList for success + NULL for error +===========================================================================*/ +sClientMemList * FindClientMem( + sQCUSBNet * pDev, + u16 clientID ) +{ + sClientMemList * pClientMem; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return NULL; + } + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + pClientMem = pDev->mQMIDev.mpClientMemList; + while (pClientMem != NULL) + { + if (pClientMem->mClientID == clientID) + { + // Success + //DBG( "Found client mem %p\n", pClientMem ); + return pClientMem; + } + + pClientMem = pClientMem->mpNext; + } + + DBG( "Could not find client mem 0x%04X\n", clientID ); + return NULL; +} + +/*=========================================================================== +METHOD: + AddToReadMemList (Public Method) + +DESCRIPTION: + Add Data to this client's ReadMem list + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + pData [ I ] - Data to add + dataSize [ I ] - Size of data to add + +RETURN VALUE: + bool +===========================================================================*/ +bool AddToReadMemList( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID, + void * pData, + u16 dataSize ) +{ + sClientMemList * pClientMem; + sReadMemList ** ppThisReadMemList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", + clientID ); + + return false; + } + + // Go to last ReadMemList entry + ppThisReadMemList = &pClientMem->mpList; + while (*ppThisReadMemList != NULL) + { + ppThisReadMemList = &(*ppThisReadMemList)->mpNext; + } + + *ppThisReadMemList = kmalloc( sizeof( sReadMemList ), GFP_ATOMIC ); + if (*ppThisReadMemList == NULL) + { + DBG( "Mem error\n" ); + + return false; + } + + (*ppThisReadMemList)->mpNext = NULL; + (*ppThisReadMemList)->mpData = pData; + (*ppThisReadMemList)->mDataSize = dataSize; + (*ppThisReadMemList)->mTransactionID = transactionID; + + return true; +} + +/*=========================================================================== +METHOD: + PopFromReadMemList (Public Method) + +DESCRIPTION: + Remove data from this client's ReadMem list if it matches + the specified transaction ID. + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + ppData [I/O] - On success, will be filled with a + pointer to read buffer + pDataSize [I/O] - On succces, will be filled with the + read buffer's size + +RETURN VALUE: + bool +===========================================================================*/ +bool PopFromReadMemList( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID, + void ** ppData, + u16 * pDataSize ) +{ + sClientMemList * pClientMem; + sReadMemList * pDelReadMemList, ** ppReadMemList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", + clientID ); + + return false; + } + + ppReadMemList = &(pClientMem->mpList); + pDelReadMemList = NULL; + + // Find first message that matches this transaction ID + while (*ppReadMemList != NULL) + { + // Do we care about transaction ID? + if (transactionID == 0 + || transactionID == (*ppReadMemList)->mTransactionID ) + { + pDelReadMemList = *ppReadMemList; + break; + } + + DBG( "skipping 0x%04X data TID = %x\n", clientID, (*ppReadMemList)->mTransactionID ); + + // Next + ppReadMemList = &(*ppReadMemList)->mpNext; + } + + if (pDelReadMemList != NULL) + { + *ppReadMemList = (*ppReadMemList)->mpNext; + + // Copy to output + *ppData = pDelReadMemList->mpData; + *pDataSize = pDelReadMemList->mDataSize; + + // Free memory + kfree( pDelReadMemList ); + + return true; + } + else + { + DBG( "No read memory to pop, Client 0x%04X, TID = %x\n", + clientID, + transactionID ); + return false; + } +} + +/*=========================================================================== +METHOD: + AddToNotifyList (Public Method) + +DESCRIPTION: + Add Notify entry to this client's notify List + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + pNotifyFunct [ I ] - Callback function to be run when data is available + pData [ I ] - Data buffer that willl be passed (unmodified) + to callback + +RETURN VALUE: + bool +===========================================================================*/ +bool AddToNotifyList( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (* pNotifyFunct)(sQCUSBNet *, u16, void *), + void * pData ) +{ + sClientMemList * pClientMem; + sNotifyList ** ppThisNotifyList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", clientID ); + return false; + } + + // Go to last URBList entry + ppThisNotifyList = &pClientMem->mpReadNotifyList; + while (*ppThisNotifyList != NULL) + { + ppThisNotifyList = &(*ppThisNotifyList)->mpNext; + } + + *ppThisNotifyList = kmalloc( sizeof( sNotifyList ), GFP_ATOMIC ); + if (*ppThisNotifyList == NULL) + { + DBG( "Mem error\n" ); + return false; + } + + (*ppThisNotifyList)->mpNext = NULL; + (*ppThisNotifyList)->mpNotifyFunct = pNotifyFunct; + (*ppThisNotifyList)->mpData = pData; + (*ppThisNotifyList)->mTransactionID = transactionID; + + return true; +} + +/*=========================================================================== +METHOD: + NotifyAndPopNotifyList (Public Method) + +DESCRIPTION: + Remove first Notify entry from this client's notify list + and Run function + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + transactionID [ I ] - Transaction ID or 0 for any + +RETURN VALUE: + bool +===========================================================================*/ +bool NotifyAndPopNotifyList( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID ) +{ + sClientMemList * pClientMem; + sNotifyList * pDelNotifyList, ** ppNotifyList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", clientID ); + return false; + } + + ppNotifyList = &(pClientMem->mpReadNotifyList); + pDelNotifyList = NULL; + + // Remove from list + while (*ppNotifyList != NULL) + { + // Do we care about transaction ID? + if (transactionID == 0 + || (*ppNotifyList)->mTransactionID == 0 + || transactionID == (*ppNotifyList)->mTransactionID) + { + pDelNotifyList = *ppNotifyList; + break; + } + + DBG( "skipping data TID = %x\n", (*ppNotifyList)->mTransactionID ); + + // next + ppNotifyList = &(*ppNotifyList)->mpNext; + } + + if (pDelNotifyList != NULL) + { + // Remove element + *ppNotifyList = (*ppNotifyList)->mpNext; + + // Run notification function + if (pDelNotifyList->mpNotifyFunct != NULL) + { + // Unlock for callback + spin_unlock( &pDev->mQMIDev.mClientMemLock ); + + pDelNotifyList->mpNotifyFunct( pDev, + clientID, + pDelNotifyList->mpData ); + + // Restore lock + spin_lock( &pDev->mQMIDev.mClientMemLock ); + } + + // Delete memory + kfree( pDelNotifyList ); + + return true; + } + else + { + DBG( "no one to notify for TID %x\n", transactionID ); + + return false; + } +} + +/*=========================================================================== +METHOD: + AddToURBList (Public Method) + +DESCRIPTION: + Add URB to this client's URB list + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + pURB [ I ] - URB to be added + +RETURN VALUE: + bool +===========================================================================*/ +bool AddToURBList( + sQCUSBNet * pDev, + u16 clientID, + struct urb * pURB ) +{ + sClientMemList * pClientMem; + sURBList ** ppThisURBList; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", clientID ); + return false; + } + + // Go to last URBList entry + ppThisURBList = &pClientMem->mpURBList; + while (*ppThisURBList != NULL) + { + ppThisURBList = &(*ppThisURBList)->mpNext; + } + + *ppThisURBList = kmalloc( sizeof( sURBList ), GFP_ATOMIC ); + if (*ppThisURBList == NULL) + { + DBG( "Mem error\n" ); + return false; + } + + (*ppThisURBList)->mpNext = NULL; + (*ppThisURBList)->mpURB = pURB; + + return true; +} + +/*=========================================================================== +METHOD: + PopFromURBList (Public Method) + +DESCRIPTION: + Remove URB from this client's URB list + + Caller MUST have lock on mClientMemLock + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Requester's client ID + +RETURN VALUE: + struct urb - Pointer to requested client's URB + NULL for error +===========================================================================*/ +struct urb * PopFromURBList( + sQCUSBNet * pDev, + u16 clientID ) +{ + sClientMemList * pClientMem; + sURBList * pDelURBList; + struct urb * pURB; + +#ifdef CONFIG_SMP + // Verify Lock + if (spin_is_locked( &pDev->mQMIDev.mClientMemLock ) == 0) + { + DBG( "unlocked\n" ); + BUG(); + } +#endif + + // Get this client's memory location + pClientMem = FindClientMem( pDev, clientID ); + if (pClientMem == NULL) + { + DBG( "Could not find this client's memory 0x%04X\n", clientID ); + return NULL; + } + + // Remove from list + if (pClientMem->mpURBList != NULL) + { + pDelURBList = pClientMem->mpURBList; + pClientMem->mpURBList = pClientMem->mpURBList->mpNext; + + // Copy to output + pURB = pDelURBList->mpURB; + + // Delete memory + kfree( pDelURBList ); + + return pURB; + } + else + { + DBG( "No URB's to pop\n" ); + + return NULL; + } +} + +/*=========================================================================*/ +// Userspace wrappers +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + UserspaceOpen (Public Method) + +DESCRIPTION: + Userspace open + IOCTL must be called before reads or writes + +PARAMETERS + pInode [ I ] - kernel file descriptor + pFilp [ I ] - userspace file descriptor + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +int UserspaceOpen( + struct inode * pInode, + struct file * pFilp ) +{ + sQMIFilpStorage * pFilpData; + + // Optain device pointer from pInode + sQMIDev * pQMIDev = container_of( pInode->i_cdev, + sQMIDev, + mCdev ); + sQCUSBNet * pDev = container_of( pQMIDev, + sQCUSBNet, + mQMIDev ); + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return -ENXIO; + } + + // Setup data in pFilp->private_data + pFilp->private_data = kmalloc( sizeof( sQMIFilpStorage ), GFP_KERNEL ); + if (pFilp->private_data == NULL) + { + DBG( "Mem error\n" ); + return -ENOMEM; + } + + pFilpData = (sQMIFilpStorage *)pFilp->private_data; + pFilpData->mClientID = (u16)-1; + pFilpData->mpDev = pDev; + + return 0; +} + +/*=========================================================================== +METHOD: + UserspaceIOCTL (Public Method) + +DESCRIPTION: + Userspace IOCTL functions + +PARAMETERS + pUnusedInode [ I ] - (unused) kernel file descriptor + pFilp [ I ] - userspace file descriptor + cmd [ I ] - IOCTL command + arg [ I ] - IOCTL argument + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +int UserspaceIOCTL( + struct inode * pUnusedInode, + struct file * pFilp, + unsigned int cmd, + unsigned long arg ) +{ + int result; + u32 devVIDPID; + + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + + if (pFilpData == NULL) + { + DBG( "Bad file data\n" ); + return -EBADF; + } + + if (IsDeviceValid( pFilpData->mpDev ) == false) + { + DBG( "Invalid device! Updating f_ops\n" ); + pFilp->f_op = pFilp->f_dentry->d_inode->i_fop; + return -ENXIO; + } + + switch (cmd) + { + case IOCTL_QMI_GET_SERVICE_FILE: + + DBG( "Setting up QMI for service %lu\n", arg ); + if ((u8)arg == 0) + { + DBG( "Cannot use QMICTL from userspace\n" ); + return -EINVAL; + } + + // Connection is already setup + if (pFilpData->mClientID != (u16)-1) + { + DBG( "Close the current connection before opening a new one\n" ); + return -EBADR; + } + + result = GetClientID( pFilpData->mpDev, (u8)arg ); + if (result < 0) + { + return result; + } + pFilpData->mClientID = result; + + return 0; + break; + + + case IOCTL_QMI_GET_DEVICE_VIDPID: + if (arg == 0) + { + DBG( "Bad VIDPID buffer\n" ); + return -EINVAL; + } + + // Extra verification + if (pFilpData->mpDev->mpNetDev == 0) + { + DBG( "Bad mpNetDev\n" ); + return -ENOMEM; + } + if (pFilpData->mpDev->mpNetDev->udev == 0) + { + DBG( "Bad udev\n" ); + return -ENOMEM; + } + + devVIDPID = ((le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idVendor ) << 16) + + le16_to_cpu( pFilpData->mpDev->mpNetDev->udev->descriptor.idProduct ) ); + + result = copy_to_user( (unsigned int *)arg, &devVIDPID, 4 ); + if (result != 0) + { + DBG( "Copy to userspace failure\n" ); + } + + return result; + + break; + + case IOCTL_QMI_GET_DEVICE_MEID: + if (arg == 0) + { + DBG( "Bad MEID buffer\n" ); + return -EINVAL; + } + + result = copy_to_user( (unsigned int *)arg, &pFilpData->mpDev->mMEID[0], 14 ); + if (result != 0) + { + DBG( "copy to userspace failure\n" ); + } + + return result; + + break; + + default: + return -EBADRQC; + } +} + +/*=========================================================================== +METHOD: + UserspaceClose (Public Method) + +DESCRIPTION: + Userspace close + Release client ID and free memory + +PARAMETERS + pFilp [ I ] - userspace file descriptor + unusedFileTable [ I ] - (unused) file table + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +int UserspaceClose( + struct file * pFilp, + fl_owner_t unusedFileTable ) +{ + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + struct list_head * pTasks; + struct task_struct * pEachTask; + struct fdtable * pFDT; + int count = 0; + int used = 0; + unsigned long flags; + + if (pFilpData == NULL) + { + DBG( "bad file data\n" ); + return -EBADF; + } + + // Fallthough. If f_count == 1 no need to do more checks + if (atomic_read( &pFilp->f_count ) != 1) + { + // "group_leader" points to the main process' task, which resides in + // the global "tasks" list. + list_for_each( pTasks, ¤t->group_leader->tasks ) + { + pEachTask = container_of( pTasks, struct task_struct, tasks ); + if (pEachTask == NULL || pEachTask->files == NULL) + { + // Some tasks may not have files (e.g. Xsession) + continue; + } + spin_lock_irqsave( &pEachTask->files->file_lock, flags ); + pFDT = files_fdtable( pEachTask->files ); + for (count = 0; count < pFDT->max_fds; count++) + { + // Before this function was called, this file was removed + // from our task's file table so if we find it in a file + // table then it is being used by another task + if (pFDT->fd[count] == pFilp) + { + used++; + break; + } + } + spin_unlock_irqrestore( &pEachTask->files->file_lock, flags ); + } + + if (used > 0) + { + DBG( "not closing, as this FD is open by %d other process\n", used ); + return 0; + } + } + + if (IsDeviceValid( pFilpData->mpDev ) == false) + { + DBG( "Invalid device! Updating f_ops\n" ); + pFilp->f_op = pFilp->f_dentry->d_inode->i_fop; + return -ENXIO; + } + + DBG( "0x%04X\n", pFilpData->mClientID ); + + // Disable pFilpData so they can't keep sending read or write + // should this function hang + // Note: memory pointer is still saved in pFilpData to be deleted later + pFilp->private_data = NULL; + + if (pFilpData->mClientID != (u16)-1) + { + ReleaseClientID( pFilpData->mpDev, + pFilpData->mClientID ); + } + + kfree( pFilpData ); + return 0; +} + +/*=========================================================================== +METHOD: + UserspaceRead (Public Method) + +DESCRIPTION: + Userspace read (synchronous) + +PARAMETERS + pFilp [ I ] - userspace file descriptor + pBuf [ I ] - read buffer + size [ I ] - size of read buffer + pUnusedFpos [ I ] - (unused) file position + +RETURN VALUE: + ssize_t - Number of bytes read for success + Negative errno for failure +===========================================================================*/ +ssize_t UserspaceRead( + struct file * pFilp, + char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ) +{ + int result; + void * pReadData = NULL; + void * pSmallReadData; + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + + if (pFilpData == NULL) + { + DBG( "Bad file data\n" ); + return -EBADF; + } + + if (IsDeviceValid( pFilpData->mpDev ) == false) + { + DBG( "Invalid device! Updating f_ops\n" ); + pFilp->f_op = pFilp->f_dentry->d_inode->i_fop; + return -ENXIO; + } + + if (pFilpData->mClientID == (u16)-1) + { + DBG( "Client ID must be set before reading 0x%04X\n", + pFilpData->mClientID ); + return -EBADR; + } + + // Perform synchronous read + result = ReadSync( pFilpData->mpDev, + &pReadData, + pFilpData->mClientID, + 0 ); + if (result <= 0) + { + return result; + } + + // Discard QMUX header + result -= QMUXHeaderSize(); + pSmallReadData = pReadData + QMUXHeaderSize(); + + if (result > size) + { + DBG( "Read data is too large for amount user has requested\n" ); + kfree( pReadData ); + return -EOVERFLOW; + } + + if (copy_to_user( pBuf, pSmallReadData, result ) != 0) + { + DBG( "Error copying read data to user\n" ); + result = -EFAULT; + } + + // Reader is responsible for freeing read buffer + kfree( pReadData ); + + return result; +} + +/*=========================================================================== +METHOD: + UserspaceWrite (Public Method) + +DESCRIPTION: + Userspace write (synchronous) + +PARAMETERS + pFilp [ I ] - userspace file descriptor + pBuf [ I ] - write buffer + size [ I ] - size of write buffer + pUnusedFpos [ I ] - (unused) file position + +RETURN VALUE: + ssize_t - Number of bytes read for success + Negative errno for failure +===========================================================================*/ +ssize_t UserspaceWrite ( + struct file * pFilp, + const char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ) +{ + int status; + void * pWriteBuffer; + sQMIFilpStorage * pFilpData = (sQMIFilpStorage *)pFilp->private_data; + + if (pFilpData == NULL) + { + DBG( "Bad file data\n" ); + return -EBADF; + } + + if (IsDeviceValid( pFilpData->mpDev ) == false) + { + DBG( "Invalid device! Updating f_ops\n" ); + pFilp->f_op = pFilp->f_dentry->d_inode->i_fop; + return -ENXIO; + } + + if (pFilpData->mClientID == (u16)-1) + { + DBG( "Client ID must be set before writing 0x%04X\n", + pFilpData->mClientID ); + return -EBADR; + } + + // Copy data from user to kernel space + pWriteBuffer = kmalloc( size + QMUXHeaderSize(), GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + return -ENOMEM; + } + status = copy_from_user( pWriteBuffer + QMUXHeaderSize(), pBuf, size ); + if (status != 0) + { + DBG( "Unable to copy data from userspace %d\n", status ); + kfree( pWriteBuffer ); + return status; + } + + status = WriteSync( pFilpData->mpDev, + pWriteBuffer, + size + QMUXHeaderSize(), + pFilpData->mClientID ); + + kfree( pWriteBuffer ); + + // On success, return requested size, not full QMI reqest size + if (status == size + QMUXHeaderSize()) + { + return size; + } + else + { + return status; + } +} + +/*=========================================================================*/ +// Initializer and destructor +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + RegisterQMIDevice (Public Method) + +DESCRIPTION: + QMI Device initialization function + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +int RegisterQMIDevice( sQCUSBNet * pDev ) +{ + int result; + int QCQMIIndex = 0; + dev_t devno; + char * pDevName; + + pDev->mbQMIValid = true; + + // Set up for QMICTL + // (does not send QMI message, just sets up memory) + result = GetClientID( pDev, QMICTL ); + if (result != 0) + { + pDev->mbQMIValid = false; + return result; + } + atomic_set( &pDev->mQMIDev.mQMICTLTransactionID, 1 ); + + // Start Async reading + result = StartRead( pDev ); + if (result != 0) + { + pDev->mbQMIValid = false; + return result; + } + + // Device is not ready for QMI connections right away + // Wait up to 30 seconds before failing + if (QMIReady( pDev, 30000 ) == false) + { + DBG( "Device unresponsive to QMI\n" ); + return -ETIMEDOUT; + } + + // Setup WDS callback + result = SetupQMIWDSCallback( pDev ); + if (result != 0) + { + pDev->mbQMIValid = false; + return result; + } + + // Fill MEID for device + result = QMIDMSGetMEID( pDev ); + if (result != 0) + { + pDev->mbQMIValid = false; + return result; + } + + // allocate and fill devno with numbers + result = alloc_chrdev_region( &devno, 0, 1, "qcqmi" ); + if (result < 0) + { + return result; + } + + // Create cdev + cdev_init( &pDev->mQMIDev.mCdev, &UserspaceQMIFops ); + pDev->mQMIDev.mCdev.owner = THIS_MODULE; + pDev->mQMIDev.mCdev.ops = &UserspaceQMIFops; + + result = cdev_add( &pDev->mQMIDev.mCdev, devno, 1 ); + if (result != 0) + { + DBG( "error adding cdev\n" ); + return result; + } + + // Match interface number (usb#) + pDevName = strstr( pDev->mpNetDev->net->name, "usb" ); + if (pDevName == NULL) + { + DBG( "Bad net name: %s\n", pDev->mpNetDev->net->name ); + return -ENXIO; + } + pDevName += strlen("usb"); + QCQMIIndex = simple_strtoul( pDevName, NULL, 10 ); + if(QCQMIIndex < 0 ) + { + DBG( "Bad minor number\n" ); + return -ENXIO; + } + + // Always print this output + printk( KERN_INFO "creating qcqmi%d\n", + QCQMIIndex ); +#if (LINUX_VERSION_CODE >= KERNEL_VERSION( 2,6,27 )) + // kernel 2.6.27 added a new fourth parameter to device_create + // void * drvdata : the data to be added to the device for callbacks + device_create( pDev->mQMIDev.mpDevClass, + NULL, + devno, + NULL, + "qcqmi%d", + QCQMIIndex ); +#else + device_create( pDev->mQMIDev.mpDevClass, + NULL, + devno, + "qcqmi%d", + QCQMIIndex ); +#endif + + pDev->mQMIDev.mDevNum = devno; + + // Success + return 0; +} + +/*=========================================================================== +METHOD: + DeregisterQMIDevice (Public Method) + +DESCRIPTION: + QMI Device cleanup function + + NOTE: When this function is run the device is no longer valid + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + None +===========================================================================*/ +void DeregisterQMIDevice( sQCUSBNet * pDev ) +{ + struct inode * pOpenInode; + struct list_head * pInodeList; + struct list_head * pTasks; + struct task_struct * pEachTask; + struct fdtable * pFDT; + struct file * pFilp; + unsigned long flags; + int count = 0; + + // Should never happen, but check anyway + if (IsDeviceValid( pDev ) == false) + { + DBG( "wrong device\n" ); + return; + } + + // Release all clients + while (pDev->mQMIDev.mpClientMemList != NULL) + { + DBG( "release 0x%04X\n", pDev->mQMIDev.mpClientMemList->mClientID ); + + ReleaseClientID( pDev, + pDev->mQMIDev.mpClientMemList->mClientID ); + // NOTE: pDev->mQMIDev.mpClientMemList will + // be updated in ReleaseClientID() + } + + // Stop all reads + KillRead( pDev ); + + pDev->mbQMIValid = false; + + // Find each open file handle, and manually close it + + // Generally there will only be only one inode, but more are possible + list_for_each( pInodeList, &pDev->mQMIDev.mCdev.list ) + { + // Get the inode + pOpenInode = container_of( pInodeList, struct inode, i_devices ); + if (pOpenInode != NULL && (IS_ERR( pOpenInode ) == false)) + { + // Look for this inode in each task + + // "group_leader" points to the main process' task, which resides in + // the global "tasks" list. + list_for_each( pTasks, ¤t->group_leader->tasks ) + { + pEachTask = container_of( pTasks, struct task_struct, tasks ); + if (pEachTask == NULL || pEachTask->files == NULL) + { + // Some tasks may not have files (e.g. Xsession) + continue; + } + // For each file this task has open, check if it's referencing + // our inode. + spin_lock_irqsave( &pEachTask->files->file_lock, flags ); + pFDT = files_fdtable( pEachTask->files ); + for (count = 0; count < pFDT->max_fds; count++) + { + pFilp = pFDT->fd[count]; + if (pFilp != NULL && pFilp->f_dentry != NULL ) + { + if (pFilp->f_dentry->d_inode == pOpenInode) + { + // Close this file handle + rcu_assign_pointer( pFDT->fd[count], NULL ); + spin_unlock_irqrestore( &pEachTask->files->file_lock, flags ); + + DBG( "forcing close of open file handle\n" ); + filp_close( pFilp, pEachTask->files ); + + spin_lock_irqsave( &pEachTask->files->file_lock, flags ); + } + } + } + spin_unlock_irqrestore( &pEachTask->files->file_lock, flags ); + } + } + } + + // Remove device (so no more calls can be made by users) + if (IS_ERR(pDev->mQMIDev.mpDevClass) == false) + { + device_destroy( pDev->mQMIDev.mpDevClass, + pDev->mQMIDev.mDevNum ); + } + cdev_del( &pDev->mQMIDev.mCdev ); + + unregister_chrdev_region( pDev->mQMIDev.mDevNum, 1 ); + + return; +} + +/*=========================================================================*/ +// Driver level client management +/*=========================================================================*/ + +/*=========================================================================== +METHOD: + QMIReady (Public Method) + +DESCRIPTION: + Send QMI CTL GET VERSION INFO REQ + Wait for response or timeout + +PARAMETERS: + pDev [ I ] - Device specific memory + timeout [ I ] - Milliseconds to wait for response + +RETURN VALUE: + bool +===========================================================================*/ +bool QMIReady( + sQCUSBNet * pDev, + u16 timeout ) +{ + int result; + void * pWriteBuffer; + u16 writeBufferSize; + void * pReadBuffer; + u16 readBufferSize; + struct semaphore readSem; + u16 curTime; + unsigned long flags; + u8 transactionID; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return -EFAULT; + } + + writeBufferSize = QMICTLReadyReqSize(); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + return -ENOMEM; + } + + // An implimentation of down_timeout has not been agreed on, + // so it's been added and removed from the kernel several times. + // We're just going to ignore it and poll the semaphore. + + // Send a write every 100 ms and see if we get a response + for (curTime = 0; curTime < timeout; curTime += 100) + { + // Start read + sema_init( &readSem, 0 ); + + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + if (transactionID == 0) + { + transactionID = atomic_add_return( 1, &pDev->mQMIDev.mQMICTLTransactionID ); + } + result = ReadAsync( pDev, QMICTL, transactionID, UpSem, &readSem ); + if (result != 0) + { + return false; + } + + // Fill buffer + result = QMICTLReadyReq( pWriteBuffer, + writeBufferSize, + transactionID ); + if (result < 0) + { + kfree( pWriteBuffer ); + return false; + } + + // Disregard status. On errors, just try again + WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + QMICTL ); + + msleep( 100 ); + if (down_trylock( &readSem ) == 0) + { + // Enter critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Pop the read data + if (PopFromReadMemList( pDev, + QMICTL, + transactionID, + &pReadBuffer, + &readBufferSize ) == true) + { + // Success + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + // We don't care about the result + kfree( pReadBuffer ); + + break; + } + } + else + { + // Enter critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + // Timeout, remove the async read + NotifyAndPopNotifyList( pDev, QMICTL, transactionID ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + } + } + + kfree( pWriteBuffer ); + + // Did we time out? + if (curTime >= timeout) + { + return false; + } + + DBG( "QMI Ready after %u milliseconds\n", curTime ); + + // TODO: 3580 and newer firmware does not require this delay + msleep( 5000 ); + + // Success + return true; +} + +/*=========================================================================== +METHOD: + QMIWDSCallback (Public Method) + +DESCRIPTION: + QMI WDS callback function + Update net stats or link state + +PARAMETERS: + pDev [ I ] - Device specific memory + clientID [ I ] - Client ID + pData [ I ] - Callback data (unused) + +RETURN VALUE: + None +===========================================================================*/ +void QMIWDSCallback( + sQCUSBNet * pDev, + u16 clientID, + void * pData ) +{ + bool bRet; + int result; + void * pReadBuffer; + u16 readBufferSize; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION( 2,6,31 )) + struct net_device_stats * pStats = &(pDev->mpNetDev->stats); +#else + struct net_device_stats * pStats = &(pDev->mpNetDev->net->stats); +#endif + + u32 TXOk = (u32)-1; + u32 RXOk = (u32)-1; + u32 TXErr = (u32)-1; + u32 RXErr = (u32)-1; + u32 TXOfl = (u32)-1; + u32 RXOfl = (u32)-1; + u64 TXBytesOk = (u64)-1; + u64 RXBytesOk = (u64)-1; + bool bLinkState; + bool bReconfigure; + unsigned long flags; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return; + } + + // Critical section + spin_lock_irqsave( &pDev->mQMIDev.mClientMemLock, flags ); + + bRet = PopFromReadMemList( pDev, + clientID, + 0, + &pReadBuffer, + &readBufferSize ); + + // End critical section + spin_unlock_irqrestore( &pDev->mQMIDev.mClientMemLock, flags ); + + if (bRet == false) + { + DBG( "WDS callback failed to get data\n" ); + return; + } + + // Default values + bLinkState = ! QTestDownReason( pDev, NO_NDIS_CONNECTION ); + bReconfigure = false; + + result = QMIWDSEventResp( pReadBuffer, + readBufferSize, + &TXOk, + &RXOk, + &TXErr, + &RXErr, + &TXOfl, + &RXOfl, + &TXBytesOk, + &RXBytesOk, + &bLinkState, + &bReconfigure ); + if (result < 0) + { + DBG( "bad WDS packet\n" ); + } + else + { + + // Fill in new values, ignore max values + if (TXOfl != (u32)-1) + { + pStats->tx_fifo_errors = TXOfl; + } + + if (RXOfl != (u32)-1) + { + pStats->rx_fifo_errors = RXOfl; + } + + if (TXErr != (u32)-1) + { + pStats->tx_errors = TXErr; + } + + if (RXErr != (u32)-1) + { + pStats->rx_errors = RXErr; + } + + if (TXOk != (u32)-1) + { + pStats->tx_packets = TXOk + pStats->tx_errors; + } + + if (RXOk != (u32)-1) + { + pStats->rx_packets = RXOk + pStats->rx_errors; + } + + if (TXBytesOk != (u64)-1) + { + pStats->tx_bytes = TXBytesOk; + } + + if (RXBytesOk != (u64)-1) + { + pStats->rx_bytes = RXBytesOk; + } + + if (bReconfigure == true) + { + DBG( "Net device link reset\n" ); + QSetDownReason( pDev, NO_NDIS_CONNECTION ); + QClearDownReason( pDev, NO_NDIS_CONNECTION ); + } + else + { + if (bLinkState == true) + { + DBG( "Net device link is connected\n" ); + QClearDownReason( pDev, NO_NDIS_CONNECTION ); + } + else + { + DBG( "Net device link is disconnected\n" ); + QSetDownReason( pDev, NO_NDIS_CONNECTION ); + } + } + } + + kfree( pReadBuffer ); + + // Setup next read + result = ReadAsync( pDev, + clientID, + 0, + QMIWDSCallback, + pData ); + if (result != 0) + { + DBG( "unable to setup next async read\n" ); + } + + return; +} + +/*=========================================================================== +METHOD: + SetupQMIWDSCallback (Public Method) + +DESCRIPTION: + Request client and fire off reqests and start async read for + QMI WDS callback + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + int - 0 for success + Negative errno for failure +===========================================================================*/ +int SetupQMIWDSCallback( sQCUSBNet * pDev ) +{ + int result; + void * pWriteBuffer; + u16 writeBufferSize; + u16 WDSClientID; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return -EFAULT; + } + + result = GetClientID( pDev, QMIWDS ); + if (result < 0) + { + return result; + } + WDSClientID = result; + + // QMI WDS Set Event Report + writeBufferSize = QMIWDSSetEventReportReqSize(); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + return -ENOMEM; + } + + result = QMIWDSSetEventReportReq( pWriteBuffer, + writeBufferSize, + 1 ); + if (result < 0) + { + kfree( pWriteBuffer ); + return result; + } + + result = WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + WDSClientID ); + kfree( pWriteBuffer ); + + if (result < 0) + { + return result; + } + + // QMI WDS Get PKG SRVC Status + writeBufferSize = QMIWDSGetPKGSRVCStatusReqSize(); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + return -ENOMEM; + } + + result = QMIWDSGetPKGSRVCStatusReq( pWriteBuffer, + writeBufferSize, + 2 ); + if (result < 0) + { + kfree( pWriteBuffer ); + return result; + } + + result = WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + WDSClientID ); + kfree( pWriteBuffer ); + + if (result < 0) + { + return result; + } + + // Setup asnyc read callback + result = ReadAsync( pDev, + WDSClientID, + 0, + QMIWDSCallback, + NULL ); + if (result != 0) + { + DBG( "unable to setup async read\n" ); + return result; + } + + // Send SetControlLineState request (USB_CDC) + // Required for Autoconnect + result = usb_control_msg( pDev->mpNetDev->udev, + usb_sndctrlpipe( pDev->mpNetDev->udev, 0 ), + 0x22, + 0x21, + 1, // DTR present + 0, + NULL, + 0, + 100 ); + if (result < 0) + { + DBG( "Bad SetControlLineState status %d\n", result ); + return result; + } + + return 0; +} + +/*=========================================================================== +METHOD: + QMIDMSGetMEID (Public Method) + +DESCRIPTION: + Register DMS client + send MEID req and parse response + Release DMS client + +PARAMETERS: + pDev [ I ] - Device specific memory + +RETURN VALUE: + None +===========================================================================*/ +int QMIDMSGetMEID( sQCUSBNet * pDev ) +{ + int result; + void * pWriteBuffer; + u16 writeBufferSize; + void * pReadBuffer; + u16 readBufferSize; + u16 DMSClientID; + + if (IsDeviceValid( pDev ) == false) + { + DBG( "Invalid device\n" ); + return -EFAULT; + } + + result = GetClientID( pDev, QMIDMS ); + if (result < 0) + { + return result; + } + DMSClientID = result; + + // QMI DMS Get Serial numbers Req + writeBufferSize = QMIDMSGetMEIDReqSize(); + pWriteBuffer = kmalloc( writeBufferSize, GFP_KERNEL ); + if (pWriteBuffer == NULL) + { + return -ENOMEM; + } + + result = QMIDMSGetMEIDReq( pWriteBuffer, + writeBufferSize, + 1 ); + if (result < 0) + { + kfree( pWriteBuffer ); + return result; + } + + result = WriteSync( pDev, + pWriteBuffer, + writeBufferSize, + DMSClientID ); + kfree( pWriteBuffer ); + + if (result < 0) + { + return result; + } + + // QMI DMS Get Serial numbers Resp + result = ReadSync( pDev, + &pReadBuffer, + DMSClientID, + 1 ); + if (result < 0) + { + return result; + } + readBufferSize = result; + + result = QMIDMSGetMEIDResp( pReadBuffer, + readBufferSize, + &pDev->mMEID[0], + 14 ); + kfree( pReadBuffer ); + + if (result < 0) + { + DBG( "bad get MEID resp\n" ); + + // Non fatal error, device did not return any MEID + // Fill with 0's + memset( &pDev->mMEID[0], '0', 14 ); + } + + ReleaseClientID( pDev, DMSClientID ); + + // Success + return 0; +} diff --git a/drivers/staging/gobi/QCUSBNet2k/QMIDevice.h b/drivers/staging/gobi/QCUSBNet2k/QMIDevice.h new file mode 100644 index 00000000000..6fb9c4737fc --- /dev/null +++ b/drivers/staging/gobi/QCUSBNet2k/QMIDevice.h @@ -0,0 +1,297 @@ +/*=========================================================================== +FILE: + QMIDevice.h + +DESCRIPTION: + Functions related to the QMI interface device + +FUNCTIONS: + Generic functions + IsDeviceValid + PrintHex + QSetDownReason + QClearDownReason + QTestDownReason + + Driver level asynchronous read functions + ReadCallback + IntCallback + StartRead + KillRead + + Internal read/write functions + ReadAsync + UpSem + ReadSync + WriteSyncCallback + WriteSync + + Internal memory management functions + GetClientID + ReleaseClientID + FindClientMem + AddToReadMemList + PopFromReadMemList + AddToNotifyList + NotifyAndPopNotifyList + AddToURBList + PopFromURBList + + Userspace wrappers + UserspaceOpen + UserspaceIOCTL + UserspaceClose + UserspaceRead + UserspaceWrite + + Initializer and destructor + RegisterQMIDevice + DeregisterQMIDevice + + Driver level client management + QMIReady + QMIWDSCallback + SetupQMIWDSCallback + QMIDMSGetMEID + +Copyright (c) 2010, Code Aurora Forum. 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 and +only version 2 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Pragmas +//--------------------------------------------------------------------------- +#pragma once + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include "Structs.h" +#include "QMI.h" + +/*=========================================================================*/ +// Generic functions +/*=========================================================================*/ + +// Basic test to see if device memory is valid +bool IsDeviceValid( sQCUSBNet * pDev ); + +// Print Hex data, for debug purposes +void PrintHex( + void * pBuffer, + u16 bufSize ); + +// Sets mDownReason and turns carrier off +void QSetDownReason( + sQCUSBNet * pDev, + u8 reason ); + +// Clear mDownReason and may turn carrier on +void QClearDownReason( + sQCUSBNet * pDev, + u8 reason ); + +// Tests mDownReason and returns whether reason is set +bool QTestDownReason( + sQCUSBNet * pDev, + u8 reason ); + +/*=========================================================================*/ +// Driver level asynchronous read functions +/*=========================================================================*/ + +// Read callback +// Put the data in storage and notify anyone waiting for data +void ReadCallback( struct urb * pReadURB ); + +// Inturrupt callback +// Data is available, start a read URB +void IntCallback( struct urb * pIntURB ); + +// Start continuous read "thread" +int StartRead( sQCUSBNet * pDev ); + +// Kill continuous read "thread" +void KillRead( sQCUSBNet * pDev ); + +/*=========================================================================*/ +// Internal read/write functions +/*=========================================================================*/ + +// Start asynchronous read +// Reading client's data store, not device +int ReadAsync( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (*pCallback)(sQCUSBNet *, u16, void *), + void * pData ); + +// Notification function for synchronous read +void UpSem( + sQCUSBNet * pDev, + u16 clientID, + void * pData ); + +// Start synchronous read +// Reading client's data store, not device +int ReadSync( + sQCUSBNet * pDev, + void ** ppOutBuffer, + u16 clientID, + u16 transactionID ); + +// Write callback +void WriteSyncCallback( struct urb * pWriteURB ); + +// Start synchronous write +int WriteSync( + sQCUSBNet * pDev, + char * pInWriteBuffer, + int size, + u16 clientID ); + +/*=========================================================================*/ +// Internal memory management functions +/*=========================================================================*/ + +// Create client and allocate memory +int GetClientID( + sQCUSBNet * pDev, + u8 serviceType ); + +// Release client and free memory +void ReleaseClientID( + sQCUSBNet * pDev, + u16 clientID ); + +// Find this client's memory +sClientMemList * FindClientMem( + sQCUSBNet * pDev, + u16 clientID ); + +// Add Data to this client's ReadMem list +bool AddToReadMemList( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID, + void * pData, + u16 dataSize ); + +// Remove data from this client's ReadMem list if it matches +// the specified transaction ID. +bool PopFromReadMemList( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID, + void ** ppData, + u16 * pDataSize ); + +// Add Notify entry to this client's notify List +bool AddToNotifyList( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID, + void (* pNotifyFunct)(sQCUSBNet *, u16, void *), + void * pData ); + +// Remove first Notify entry from this client's notify list +// and Run function +bool NotifyAndPopNotifyList( + sQCUSBNet * pDev, + u16 clientID, + u16 transactionID ); + +// Add URB to this client's URB list +bool AddToURBList( + sQCUSBNet * pDev, + u16 clientID, + struct urb * pURB ); + +// Remove URB from this client's URB list +struct urb * PopFromURBList( + sQCUSBNet * pDev, + u16 clientID ); + +/*=========================================================================*/ +// Userspace wrappers +/*=========================================================================*/ + +// Userspace open +int UserspaceOpen( + struct inode * pInode, + struct file * pFilp ); + +// Userspace ioctl +int UserspaceIOCTL( + struct inode * pUnusedInode, + struct file * pFilp, + unsigned int cmd, + unsigned long arg ); + +// Userspace close +int UserspaceClose( + struct file * pFilp, + fl_owner_t unusedFileTable ); + +// Userspace read (synchronous) +ssize_t UserspaceRead( + struct file * pFilp, + char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ); + +// Userspace write (synchronous) +ssize_t UserspaceWrite( + struct file * pFilp, + const char __user * pBuf, + size_t size, + loff_t * pUnusedFpos ); + +/*=========================================================================*/ +// Initializer and destructor +/*=========================================================================*/ + +// QMI Device initialization function +int RegisterQMIDevice( sQCUSBNet * pDev ); + +// QMI Device cleanup function +void DeregisterQMIDevice( sQCUSBNet * pDev ); + +/*=========================================================================*/ +// Driver level client management +/*=========================================================================*/ + +// Check if QMI is ready for use +bool QMIReady( + sQCUSBNet * pDev, + u16 timeout ); + +// QMI WDS callback function +void QMIWDSCallback( + sQCUSBNet * pDev, + u16 clientID, + void * pData ); + +// Fire off reqests and start async read for QMI WDS callback +int SetupQMIWDSCallback( sQCUSBNet * pDev ); + +// Register client, send req and parse MEID response, release client +int QMIDMSGetMEID( sQCUSBNet * pDev ); + + + diff --git a/drivers/staging/gobi/QCUSBNet2k/Structs.h b/drivers/staging/gobi/QCUSBNet2k/Structs.h new file mode 100644 index 00000000000..07e31933fce --- /dev/null +++ b/drivers/staging/gobi/QCUSBNet2k/Structs.h @@ -0,0 +1,318 @@ +/*=========================================================================== +FILE: + Structs.h + +DESCRIPTION: + Declaration of structures used by the Qualcomm Linux USB Network driver + +FUNCTIONS: + none + +Copyright (c) 2010, Code Aurora Forum. 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 and +only version 2 as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +===========================================================================*/ + +//--------------------------------------------------------------------------- +// Pragmas +//--------------------------------------------------------------------------- +#pragma once + +//--------------------------------------------------------------------------- +// Include Files +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include + +#if (LINUX_VERSION_CODE <= KERNEL_VERSION( 2,6,24 )) + #include "usbnet.h" +#else + #include +#endif + +#if (LINUX_VERSION_CODE > KERNEL_VERSION( 2,6,25 )) + #include +#else + #include +#endif + +// DBG macro +#define DBG( format, arg... ) \ + if (debug == 1)\ + { \ + printk( KERN_INFO "QCUSBNet2k::%s " format, __FUNCTION__, ## arg ); \ + } \ + + +// Used in recursion, defined later below +struct sQCUSBNet; + +/*=========================================================================*/ +// Struct sReadMemList +// +// Structure that defines an entry in a Read Memory linked list +/*=========================================================================*/ +typedef struct sReadMemList +{ + /* Data buffer */ + void * mpData; + + /* Transaction ID */ + u16 mTransactionID; + + /* Size of data buffer */ + u16 mDataSize; + + /* Next entry in linked list */ + struct sReadMemList * mpNext; + +} sReadMemList; + +/*=========================================================================*/ +// Struct sNotifyList +// +// Structure that defines an entry in a Notification linked list +/*=========================================================================*/ +typedef struct sNotifyList +{ + /* Function to be run when data becomes available */ + void (* mpNotifyFunct)(struct sQCUSBNet *, u16, void *); + + /* Transaction ID */ + u16 mTransactionID; + + /* Data to provide as parameter to mpNotifyFunct */ + void * mpData; + + /* Next entry in linked list */ + struct sNotifyList * mpNext; + +} sNotifyList; + +/*=========================================================================*/ +// Struct sURBList +// +// Structure that defines an entry in a URB linked list +/*=========================================================================*/ +typedef struct sURBList +{ + /* The current URB */ + struct urb * mpURB; + + /* Next entry in linked list */ + struct sURBList * mpNext; + +} sURBList; + +/*=========================================================================*/ +// Struct sClientMemList +// +// Structure that defines an entry in a Client Memory linked list +// Stores data specific to a Service Type and Client ID +/*=========================================================================*/ +typedef struct sClientMemList +{ + /* Client ID for this Client */ + u16 mClientID; + + /* Linked list of Read entries */ + /* Stores data read from device before sending to client */ + sReadMemList * mpList; + + /* Linked list of Notification entries */ + /* Stores notification functions to be run as data becomes + available or the device is removed */ + sNotifyList * mpReadNotifyList; + + /* Linked list of URB entries */ + /* Stores pointers to outstanding URBs which need canceled + when the client is deregistered or the device is removed */ + sURBList * mpURBList; + + /* Next entry in linked list */ + struct sClientMemList * mpNext; + +} sClientMemList; + +/*=========================================================================*/ +// Struct sURBSetupPacket +// +// Structure that defines a USB Setup packet for Control URBs +// Taken from USB CDC specifications +/*=========================================================================*/ +typedef struct sURBSetupPacket +{ + /* Request type */ + u8 mRequestType; + + /* Request code */ + u8 mRequestCode; + + /* Value */ + u16 mValue; + + /* Index */ + u16 mIndex; + + /* Length of Control URB */ + u16 mLength; + +} sURBSetupPacket; + +// Common value for sURBSetupPacket.mLength +#define DEFAULT_READ_URB_LENGTH 0x1000 + + +/*=========================================================================*/ +// Struct sAutoPM +// +// Structure used to manage AutoPM thread which determines whether the +// device is in use or may enter autosuspend. Also submits net +// transmissions asynchronously. +/*=========================================================================*/ +typedef struct sAutoPM +{ + /* Thread for atomic autopm function */ + struct task_struct * mpThread; + + /* Up this semaphore when it's time for the thread to work */ + struct semaphore mThreadDoWork; + + /* Time to exit? */ + bool mbExit; + + /* List of URB's queued to be sent to the device */ + sURBList * mpURBList; + + /* URB list lock (for adding and removing elements) */ + spinlock_t mURBListLock; + + /* Active URB */ + struct urb * mpActiveURB; + + /* Active URB lock (for adding and removing elements) */ + spinlock_t mActiveURBLock; + + /* Duplicate pointer to USB device interface */ + struct usb_interface * mpIntf; + +} sAutoPM; + + +/*=========================================================================*/ +// Struct sQMIDev +// +// Structure that defines the data for the QMI device +/*=========================================================================*/ +typedef struct sQMIDev +{ + /* Device number */ + dev_t mDevNum; + + /* Device class */ + struct class * mpDevClass; + + /* cdev struct */ + struct cdev mCdev; + + /* Pointer to read URB */ + struct urb * mpReadURB; + + /* Read setup packet */ + sURBSetupPacket * mpReadSetupPacket; + + /* Read buffer attached to current read URB */ + void * mpReadBuffer; + + /* Inturrupt URB */ + /* Used to asynchronously notify when read data is available */ + struct urb * mpIntURB; + + /* Buffer used by Inturrupt URB */ + void * mpIntBuffer; + + /* Pointer to memory linked list for all clients */ + sClientMemList * mpClientMemList; + + /* Spinlock for client Memory entries */ + spinlock_t mClientMemLock; + + /* Transaction ID associated with QMICTL "client" */ + atomic_t mQMICTLTransactionID; + +} sQMIDev; + +/*=========================================================================*/ +// Struct sQCUSBNet +// +// Structure that defines the data associated with the Qualcomm USB device +/*=========================================================================*/ +typedef struct sQCUSBNet +{ + /* Net device structure */ + struct usbnet * mpNetDev; + + /* Usb device interface */ + struct usb_interface * mpIntf; + + /* Pointers to usbnet_open and usbnet_stop functions */ + int (* mpUSBNetOpen)(struct net_device *); + int (* mpUSBNetStop)(struct net_device *); + + /* Reason(s) why interface is down */ + /* Used by Q*DownReason */ + unsigned long mDownReason; +#define NO_NDIS_CONNECTION 0 +#define CDC_CONNECTION_SPEED 1 +#define DRIVER_SUSPENDED 2 +#define NET_IFACE_STOPPED 3 + + /* QMI "device" status */ + bool mbQMIValid; + + /* QMI "device" memory */ + sQMIDev mQMIDev; + + /* Device MEID */ + char mMEID[14]; + + /* AutoPM thread */ + sAutoPM mAutoPM; + +} sQCUSBNet; + +/*=========================================================================*/ +// Struct sQMIFilpStorage +// +// Structure that defines the storage each file handle contains +// Relates the file handle to a client +/*=========================================================================*/ +typedef struct sQMIFilpStorage +{ + /* Client ID */ + u16 mClientID; + + /* Device pointer */ + sQCUSBNet * mpDev; + +} sQMIFilpStorage; + + diff --git a/drivers/staging/msm/ebi2_l2f.c b/drivers/staging/msm/ebi2_l2f.c index eea891d8f0f..0798019069c 100644 --- a/drivers/staging/msm/ebi2_l2f.c +++ b/drivers/staging/msm/ebi2_l2f.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/ebi2_lcd.c b/drivers/staging/msm/ebi2_lcd.c index b41e1230cec..5f8dd0c1450 100644 --- a/drivers/staging/msm/ebi2_lcd.c +++ b/drivers/staging/msm/ebi2_lcd.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/ebi2_tmd20.c b/drivers/staging/msm/ebi2_tmd20.c index d66d0397825..0b1dda2a91f 100644 --- a/drivers/staging/msm/ebi2_tmd20.c +++ b/drivers/staging/msm/ebi2_tmd20.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/hdmi_sii9022.c b/drivers/staging/msm/hdmi_sii9022.c index 6b82b56a77b..156434aedb5 100644 --- a/drivers/staging/msm/hdmi_sii9022.c +++ b/drivers/staging/msm/hdmi_sii9022.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/lcdc.c b/drivers/staging/msm/lcdc.c index 8183394aef7..6c0f6bbbe2d 100644 --- a/drivers/staging/msm/lcdc.c +++ b/drivers/staging/msm/lcdc.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/lcdc_external.c b/drivers/staging/msm/lcdc_external.c index 45ff7852711..6d40f1b8f69 100644 --- a/drivers/staging/msm/lcdc_external.c +++ b/drivers/staging/msm/lcdc_external.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/lcdc_gordon.c b/drivers/staging/msm/lcdc_gordon.c index 399ec8c791e..1a7fe77e20f 100644 --- a/drivers/staging/msm/lcdc_gordon.c +++ b/drivers/staging/msm/lcdc_gordon.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/lcdc_panel.c b/drivers/staging/msm/lcdc_panel.c index b40974e1f27..e494251a51e 100644 --- a/drivers/staging/msm/lcdc_panel.c +++ b/drivers/staging/msm/lcdc_panel.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/lcdc_prism.c b/drivers/staging/msm/lcdc_prism.c index d102c98447c..e04f93708bf 100644 --- a/drivers/staging/msm/lcdc_prism.c +++ b/drivers/staging/msm/lcdc_prism.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/lcdc_sharp_wvga_pt.c b/drivers/staging/msm/lcdc_sharp_wvga_pt.c index 1f08cf9bc21..b39b6e6d8c7 100644 --- a/drivers/staging/msm/lcdc_sharp_wvga_pt.c +++ b/drivers/staging/msm/lcdc_sharp_wvga_pt.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/lcdc_st15.c b/drivers/staging/msm/lcdc_st15.c index fed8278eb15..ccfadfcca70 100644 --- a/drivers/staging/msm/lcdc_st15.c +++ b/drivers/staging/msm/lcdc_st15.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/lcdc_st1_wxga.c b/drivers/staging/msm/lcdc_st1_wxga.c new file mode 100644 index 00000000000..4b853af12f5 --- /dev/null +++ b/drivers/staging/msm/lcdc_st1_wxga.c @@ -0,0 +1,49 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "msm_fb.h" + +static int __init lcdc_st1_wxga_init(void) +{ + int ret; + struct msm_panel_info pinfo; + + if (msm_fb_detect_client("lcdc_st1_wxga")) + return 0; + + pinfo.xres = 1280; + pinfo.yres = 720; + pinfo.type = LCDC_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 18; + pinfo.fb_num = 2; + pinfo.clk_rate = 74250000; + + pinfo.lcdc.h_back_porch = 124; + pinfo.lcdc.h_front_porch = 110; + pinfo.lcdc.h_pulse_width = 136; + pinfo.lcdc.v_back_porch = 19; + pinfo.lcdc.v_front_porch = 5; + pinfo.lcdc.v_pulse_width = 6; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + + ret = lcdc_device_register(&pinfo); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(lcdc_st1_wxga_init); diff --git a/drivers/staging/msm/lcdc_toshiba_wvga_pt.c b/drivers/staging/msm/lcdc_toshiba_wvga_pt.c index edba78a3afc..64896c7a762 100644 --- a/drivers/staging/msm/lcdc_toshiba_wvga_pt.c +++ b/drivers/staging/msm/lcdc_toshiba_wvga_pt.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/lcdc_wxga.c b/drivers/staging/msm/lcdc_wxga.c new file mode 100644 index 00000000000..a2dcbc6a0c5 --- /dev/null +++ b/drivers/staging/msm/lcdc_wxga.c @@ -0,0 +1,51 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "msm_fb.h" + +static int __init lcdc_wxga_init(void) +{ + int ret; + struct msm_panel_info pinfo; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + if (msm_fb_detect_client("lcdc_wxga")) + return 0; +#endif + + pinfo.xres = 1280; + pinfo.yres = 720; + pinfo.type = LCDC_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.fb_num = 2; + pinfo.clk_rate = 74250000; + + pinfo.lcdc.h_back_porch = 124; + pinfo.lcdc.h_front_porch = 110; + pinfo.lcdc.h_pulse_width = 136; + pinfo.lcdc.v_back_porch = 19; + pinfo.lcdc.v_front_porch = 5; + pinfo.lcdc.v_pulse_width = 6; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + + ret = lcdc_device_register(&pinfo); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(lcdc_wxga_init); diff --git a/drivers/staging/msm/mddi.c b/drivers/staging/msm/mddi.c index 132eb1adff1..a1f2fd16f4a 100644 --- a/drivers/staging/msm/mddi.c +++ b/drivers/staging/msm/mddi.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mddi_ext.c b/drivers/staging/msm/mddi_ext.c index c0c168c7199..6a024defb63 100644 --- a/drivers/staging/msm/mddi_ext.c +++ b/drivers/staging/msm/mddi_ext.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mddi_ext_lcd.c b/drivers/staging/msm/mddi_ext_lcd.c index 502e80d17ec..0c7226319ac 100644 --- a/drivers/staging/msm/mddi_ext_lcd.c +++ b/drivers/staging/msm/mddi_ext_lcd.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/mddi_prism.c b/drivers/staging/msm/mddi_prism.c index 489d40405a5..dd3f972fd95 100644 --- a/drivers/staging/msm/mddi_prism.c +++ b/drivers/staging/msm/mddi_prism.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/mddi_sharp.c b/drivers/staging/msm/mddi_sharp.c index 1da1be4052d..ba9999bdfbf 100644 --- a/drivers/staging/msm/mddi_sharp.c +++ b/drivers/staging/msm/mddi_sharp.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/mddi_toshiba.c b/drivers/staging/msm/mddi_toshiba.c index e96342d477a..70b3dac7b55 100644 --- a/drivers/staging/msm/mddi_toshiba.c +++ b/drivers/staging/msm/mddi_toshiba.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/mddi_toshiba_vga.c b/drivers/staging/msm/mddi_toshiba_vga.c index 7e61d3a5b8f..b58e4837797 100644 --- a/drivers/staging/msm/mddi_toshiba_vga.c +++ b/drivers/staging/msm/mddi_toshiba_vga.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/mddi_toshiba_wvga.c b/drivers/staging/msm/mddi_toshiba_wvga.c new file mode 100644 index 00000000000..ea9ff7ca7fb --- /dev/null +++ b/drivers/staging/msm/mddi_toshiba_wvga.c @@ -0,0 +1,58 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddi_toshiba.h" + +static int __init mddi_toshiba_wvga_init(void) +{ + int ret; + struct msm_panel_info pinfo; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + if (msm_fb_detect_client("mddi_toshiba_wvga")) + return 0; +#endif + + pinfo.xres = 800; + pinfo.yres = 480; + pinfo.pdest = DISPLAY_2; + pinfo.type = MDDI_PANEL; + pinfo.mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; + pinfo.wait_cycle = 0; + pinfo.bpp = 18; + pinfo.lcd.vsync_enable = TRUE; + pinfo.lcd.refx100 = 6118; + pinfo.lcd.v_back_porch = 6; + pinfo.lcd.v_front_porch = 0; + pinfo.lcd.v_pulse_width = 0; + pinfo.lcd.hw_vsync_mode = FALSE; + pinfo.lcd.vsync_notifier_period = (1 * HZ); + pinfo.bl_max = 4; + pinfo.bl_min = 1; + pinfo.clk_rate = 192000000; + pinfo.clk_min = 190000000; + pinfo.clk_max = 200000000; + pinfo.fb_num = 2; + + ret = mddi_toshiba_device_register(&pinfo, TOSHIBA_VGA_PRIM, + LCD_TOSHIBA_2P4_WVGA); + if (ret) { + printk(KERN_ERR "%s: failed to register device!\n", __func__); + return ret; + } + + return ret; +} + +module_init(mddi_toshiba_wvga_init); diff --git a/drivers/staging/msm/mddi_toshiba_wvga_pt.c b/drivers/staging/msm/mddi_toshiba_wvga_pt.c index fc7d4e0d294..6789f39fe3c 100644 --- a/drivers/staging/msm/mddi_toshiba_wvga_pt.c +++ b/drivers/staging/msm/mddi_toshiba_wvga_pt.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "msm_fb.h" diff --git a/drivers/staging/msm/mddihost.c b/drivers/staging/msm/mddihost.c index 58a86d5d995..78ec8e2983c 100644 --- a/drivers/staging/msm/mddihost.c +++ b/drivers/staging/msm/mddihost.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mddihost_e.c b/drivers/staging/msm/mddihost_e.c index 7de5eda71ce..64579ebc01b 100644 --- a/drivers/staging/msm/mddihost_e.c +++ b/drivers/staging/msm/mddihost_e.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mddihosti.c b/drivers/staging/msm/mddihosti.c index f9d6e91e8d5..2e89e4b4660 100644 --- a/drivers/staging/msm/mddihosti.c +++ b/drivers/staging/msm/mddihosti.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp.c b/drivers/staging/msm/mdp.c index 58cb4046293..c5c4c41912f 100644 --- a/drivers/staging/msm/mdp.c +++ b/drivers/staging/msm/mdp.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp4_debugfs.c b/drivers/staging/msm/mdp4_debugfs.c index 36954e89478..b124d9c85cb 100644 --- a/drivers/staging/msm/mdp4_debugfs.c +++ b/drivers/staging/msm/mdp4_debugfs.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include #include diff --git a/drivers/staging/msm/mdp4_overlay.c b/drivers/staging/msm/mdp4_overlay.c index b9acf529929..78b469f812c 100644 --- a/drivers/staging/msm/mdp4_overlay.c +++ b/drivers/staging/msm/mdp4_overlay.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp4_overlay_lcdc.c b/drivers/staging/msm/mdp4_overlay_lcdc.c index a6ab8ec83f5..8f49caed4da 100644 --- a/drivers/staging/msm/mdp4_overlay_lcdc.c +++ b/drivers/staging/msm/mdp4_overlay_lcdc.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp4_overlay_mddi.c b/drivers/staging/msm/mdp4_overlay_mddi.c index be1b2874185..9ee8608880f 100644 --- a/drivers/staging/msm/mdp4_overlay_mddi.c +++ b/drivers/staging/msm/mdp4_overlay_mddi.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp4_util.c b/drivers/staging/msm/mdp4_util.c index fd97f520599..893b883e088 100644 --- a/drivers/staging/msm/mdp4_util.c +++ b/drivers/staging/msm/mdp4_util.c @@ -9,11 +9,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include #include diff --git a/drivers/staging/msm/mdp_cursor.c b/drivers/staging/msm/mdp_cursor.c index 7d28f30d931..5ff395a271c 100644 --- a/drivers/staging/msm/mdp_cursor.c +++ b/drivers/staging/msm/mdp_cursor.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp_dma.c b/drivers/staging/msm/mdp_dma.c index 639918b143b..7c551611093 100644 --- a/drivers/staging/msm/mdp_dma.c +++ b/drivers/staging/msm/mdp_dma.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp_dma_lcdc.c b/drivers/staging/msm/mdp_dma_lcdc.c index b57fa1a0ceb..11fe40a14cb 100644 --- a/drivers/staging/msm/mdp_dma_lcdc.c +++ b/drivers/staging/msm/mdp_dma_lcdc.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp_dma_s.c b/drivers/staging/msm/mdp_dma_s.c index 0c34a1010f1..13516f95a2b 100644 --- a/drivers/staging/msm/mdp_dma_s.c +++ b/drivers/staging/msm/mdp_dma_s.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp_dma_tv.c b/drivers/staging/msm/mdp_dma_tv.c index 70989fb32c1..16f55d47eaa 100644 --- a/drivers/staging/msm/mdp_dma_tv.c +++ b/drivers/staging/msm/mdp_dma_tv.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp_hw_init.c b/drivers/staging/msm/mdp_hw_init.c index 807362ac592..d184afb71de 100644 --- a/drivers/staging/msm/mdp_hw_init.c +++ b/drivers/staging/msm/mdp_hw_init.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "mdp.h" diff --git a/drivers/staging/msm/mdp_ppp_dq.c b/drivers/staging/msm/mdp_ppp_dq.c index 3a687c7a569..f52445f3bc9 100644 --- a/drivers/staging/msm/mdp_ppp_dq.c +++ b/drivers/staging/msm/mdp_ppp_dq.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include "mdp.h" diff --git a/drivers/staging/msm/mdp_ppp_v20.c b/drivers/staging/msm/mdp_ppp_v20.c index b5b7271921e..ceff8edf1cd 100644 --- a/drivers/staging/msm/mdp_ppp_v20.c +++ b/drivers/staging/msm/mdp_ppp_v20.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp_ppp_v31.c b/drivers/staging/msm/mdp_ppp_v31.c index 76495dbe4e6..244271792da 100644 --- a/drivers/staging/msm/mdp_ppp_v31.c +++ b/drivers/staging/msm/mdp_ppp_v31.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/mdp_vsync.c b/drivers/staging/msm/mdp_vsync.c index bbd45604435..1f1489e18c5 100644 --- a/drivers/staging/msm/mdp_vsync.c +++ b/drivers/staging/msm/mdp_vsync.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/memory_ll.h b/drivers/staging/msm/memory_ll.h index 18a239a89a7..6e6d7d963f7 100644 --- a/drivers/staging/msm/memory_ll.h +++ b/drivers/staging/msm/memory_ll.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2007 Google, Inc. - * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. + * Copyright (c) 2009-2011, Code Aurora Forum. 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 @@ -47,7 +47,7 @@ extern void l2x0_cache_sync(void); #endif -#ifdef CONFIG_ARCH_MSM_SCORPION +#ifdef CONFIG_ARCH_MSM_SCORPION || CONFIG_ARCH_MSM_KRAIT #define arch_has_speculative_dfetch() 1 #endif diff --git a/drivers/staging/msm/msm_fb_bl.c b/drivers/staging/msm/msm_fb_bl.c index 2a8077511fc..46523c07494 100644 --- a/drivers/staging/msm/msm_fb_bl.c +++ b/drivers/staging/msm/msm_fb_bl.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/msm_fb_panel.c b/drivers/staging/msm/msm_fb_panel.c index b17a239a1bc..098435e52e5 100644 --- a/drivers/staging/msm/msm_fb_panel.c +++ b/drivers/staging/msm/msm_fb_panel.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/staging-devices.c b/drivers/staging/msm/staging-devices.c index d6cd919469d..9f55158e7ac 100644 --- a/drivers/staging/msm/staging-devices.c +++ b/drivers/staging/msm/staging-devices.c @@ -40,7 +40,6 @@ #define MSM_PMEM_ADSP_SIZE 0x1C00000 #define MSM_FB_SIZE 0x500000 -#define MSM_FB_SIZE_ST15 0x800000 #define MSM_AUDIO_SIZE 0x80000 #define MSM_GPU_PHYS_SIZE SZ_2M @@ -106,20 +105,14 @@ static int msm_fb_detect_panel(const char *name) { int ret = -EPERM; - if (machine_is_qsd8x50_ffa() || machine_is_qsd8x50a_ffa()) { + if (machine_is_qsd8x50_ffa()) { if (!strncmp(name, "mddi_toshiba_wvga_pt", 20)) ret = 0; else ret = -ENODEV; - } else if ((machine_is_qsd8x50_surf() || machine_is_qsd8x50a_surf()) + } else if ((machine_is_qsd8x50_surf()) { && !strcmp(name, "lcdc_external")) ret = 0; - else if (machine_is_qsd8x50a_st1_5()) { - if (!strcmp(name, "lcdc_st15") || - !strcmp(name, "hdmi_sii9022")) - ret = 0; - else - ret = -ENODEV; } return ret; @@ -130,8 +123,7 @@ static int msm_fb_detect_panel(const char *name) static int msm_fb_allow_set_offset(void) { - return (machine_is_qsd8x50_st1() || - machine_is_qsd8x50a_st1_5()) ? 1 : 0; + return 0; } @@ -154,10 +146,7 @@ static void __init qsd8x50_allocate_memory_regions(void) { void *addr; unsigned long size; - if (machine_is_qsd8x50a_st1_5()) - size = MSM_FB_SIZE_ST15; - else - size = MSM_FB_SIZE; + size = MSM_FB_SIZE; addr = alloc_bootmem(size); // (void *)MSM_FB_BASE; if (!addr) @@ -187,25 +176,6 @@ static int msm_fb_lcdc_gpio_config(int on) mdelay(100); gpio_set_value(32, 0); } - } else if (machine_is_qsd8x50a_st1_5()) { - if (on) { - gpio_set_value(17, 1); - gpio_set_value(19, 1); - gpio_set_value(20, 1); - gpio_set_value(22, 0); - gpio_set_value(32, 1); - gpio_set_value(155, 1); - //st15_hdmi_power(1); - gpio_set_value(22, 1); - - } else { - gpio_set_value(17, 0); - gpio_set_value(19, 0); - gpio_set_value(22, 0); - gpio_set_value(32, 0); - gpio_set_value(155, 0); - // st15_hdmi_power(0); - } } return 0; } @@ -276,23 +246,7 @@ static void __init msm_fb_add_devices(void) // msm_fb_register_device("pmdh", &mddi_pdata); // msm_fb_register_device("emdh", &mddi_pdata); // msm_fb_register_device("tvenc", 0); - - if (machine_is_qsd8x50a_st1_5()) { -/* rc = st15_hdmi_vreg_init(); - if (rc) - return; -*/ - rc = msm_gpios_request_enable( - msm_fb_st15_gpio_config_data, - ARRAY_SIZE(msm_fb_st15_gpio_config_data)); - if (rc) { - printk(KERN_ERR "%s: unable to init lcdc gpios\n", - __func__); - return; - } - msm_fb_register_device("lcdc", &lcdc_pdata); - } else - msm_fb_register_device("lcdc", 0); + msm_fb_register_device("lcdc", 0); } int __init staging_init_pmem(void) diff --git a/drivers/staging/msm/tv_ntsc.c b/drivers/staging/msm/tv_ntsc.c index 5eb67611661..63e5cd38100 100644 --- a/drivers/staging/msm/tv_ntsc.c +++ b/drivers/staging/msm/tv_ntsc.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/staging/msm/tv_pal.c b/drivers/staging/msm/tv_pal.c index 204da514660..ff5b51d3283 100644 --- a/drivers/staging/msm/tv_pal.c +++ b/drivers/staging/msm/tv_pal.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include #include diff --git a/drivers/staging/msm/tvenc.c b/drivers/staging/msm/tvenc.c index 4fbb77b253d..c7480f72801 100644 --- a/drivers/staging/msm/tvenc.c +++ b/drivers/staging/msm/tvenc.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index bf7c687519e..de5ded33dc7 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -22,3 +22,62 @@ config THERMAL_HWMON requires a 2.10.7/3.0.2 or later lm-sensors userspace. Say Y if your user-space is new enough. + +config THERMAL_PM8901 + tristate "Qualcomm PM8901 Temperature Alarm" + depends on PMIC8901 + depends on THERMAL + default n + help + This enables a thermal Sysfs driver for the PMIC 8901 device. It + shows up in Sysfs as a thermal zone with multiple trip points. + Enabling the thermal zone device via the mode file results in + shifting over temperature shutdown control of the PMIC from hardware + to software. + +config THERMAL_PM8058 + tristate "Qualcomm PM8058 Temperature Alarm" + depends on PMIC8058 + depends on THERMAL + depends on SENSORS_MSM_ADC + default n + help + This enables a thermal Sysfs driver for the PMIC 8058 device. It + shows up in Sysfs as a thermal zone with multiple trip points. + Enabling the thermal zone device via the mode file results in + shifting over temperature shutdown control of the PMIC from hardware + to software. + +config THERMAL_MSM_POPMEM + tristate "Qualcomm MSM POP memory temperature sensor" + depends on THERMAL + default n + help + This enables a thermal sysfs driver for MSM POP memory. It shows up in + sysfs as a thermal zone with one trip point. Due to hardware + limitations, the temperatures are reported as "Low Temperature" (20 C) + "Normal Temperature" (50 C) and "Out of Spec High Temperature" (85 C). + This driver is designed to be used in conjunction with a user space + application to make all policy decisions. + +config THERMAL_TSENS + tristate "Qualcomm Tsens Temperature Alarm" + depends on THERMAL + default n + help + This enables the thermal sysfs driver for the Tsens device. It shows + up in Sysfs as a thermal zone with mutiple trip points. Disabling the + thermal zone device via the mode file results in disabling the sensor. + Also able to set threshold temperature for both hot and cold and update + when a threshold is reached. + +config THERMAL_PM8XXX + tristate "Qualcomm PMIC PM8xxx Temperature Alarm" + depends on THERMAL + depends on MFD_PM8XXX + help + This enables a thermal Sysfs driver for the PMIC PM8xxx devices. It + shows up in Sysfs as a thermal zone with multiple trip points. + Enabling the thermal zone device via the mode file results in + shifting over temperature shutdown control of the PMIC from hardware + to software. diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 31108a01c22..d1bb4663c22 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -3,3 +3,8 @@ # obj-$(CONFIG_THERMAL) += thermal_sys.o +obj-$(CONFIG_THERMAL_PM8901) += pmic8901-tm.o +obj-$(CONFIG_THERMAL_PM8058) += pmic8058-tm.o +obj-$(CONFIG_THERMAL_MSM_POPMEM) += msm_popmem-tm.o +obj-$(CONFIG_THERMAL_TSENS) += msm_tsens.o +obj-$(CONFIG_THERMAL_PM8XXX) += pm8xxx-tm.o diff --git a/drivers/thermal/msm_popmem-tm.c b/drivers/thermal/msm_popmem-tm.c new file mode 100644 index 00000000000..583b2db07db --- /dev/null +++ b/drivers/thermal/msm_popmem-tm.c @@ -0,0 +1,284 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define POP_MEM_LPDDR1_REFRESH_MASK 0x00000700 +#define POP_MEM_LPDDR1_REFRESH_SHIFT 0x8 + +#define POP_MEM_LPDDR2_REFRESH_MASK 0x00000007 +#define POP_MEM_LPDDR2_REFRESH_SHIFT 0x0 + +#define POP_MEM_REFRESH_REG 0x3C + +#define POP_MEM_LOW_TEMPERATURE 25000 +#define POP_MEM_NORMAL_TEMPERATURE 50000 +#define POP_MEM_HIGH_TEMPERATURE 85000 + +#define POP_MEM_TRIP_OUT_OF_SPEC 0 +#define POP_MEM_TRIP_NUM 1 + +struct pop_mem_tm_device { + unsigned long baseaddr; + struct thermal_zone_device *tz_dev; + unsigned long refresh_mask; + unsigned int refresh_shift; +}; + + +static int pop_mem_tm_read_refresh(struct pop_mem_tm_device *tm, + unsigned int *ref_rate){ + unsigned int ref; + + ref = __raw_readl(tm->baseaddr + POP_MEM_REFRESH_REG); + *ref_rate = (ref & tm->refresh_mask) >> tm->refresh_shift; + + return 0; +} + + +static int pop_mem_tm_get_temperature(struct thermal_zone_device *thermal, + unsigned long *temperature) +{ + struct pop_mem_tm_device *tm = thermal->devdata; + unsigned int ref_rate; + int rc; + + if (!tm || !temperature) + return -EINVAL; + + rc = pop_mem_tm_read_refresh(tm, &ref_rate); + if (rc < 0) + return rc; + + switch (ref_rate) { + case 0: + case 1: + case 2: + *temperature = POP_MEM_LOW_TEMPERATURE; + break; + case 3: + case 4: + *temperature = POP_MEM_NORMAL_TEMPERATURE; + break; + case 5: + case 6: + case 7: + *temperature = POP_MEM_HIGH_TEMPERATURE; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pop_mem_tm_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + struct pop_mem_tm_device *tm = thermal->devdata; + + if (!tm || trip < 0 || !type) + return -EINVAL; + + if (trip == POP_MEM_TRIP_OUT_OF_SPEC) + *type = THERMAL_TRIP_CRITICAL; + else + return -EINVAL; + + return 0; +} + +static int pop_mem_tm_get_trip_temperature(struct thermal_zone_device *thermal, + int trip, unsigned long *temperature) +{ + struct pop_mem_tm_device *tm = thermal->devdata; + + if (!tm || trip < 0 || !temperature) + return -EINVAL; + + if (trip == POP_MEM_TRIP_OUT_OF_SPEC) + *temperature = POP_MEM_HIGH_TEMPERATURE; + else + return -EINVAL; + + return 0; +} + + +static int pop_mem_tm_get_crit_temperature(struct thermal_zone_device *thermal, + unsigned long *temperature) +{ + struct pop_mem_tm_device *tm = thermal->devdata; + + if (!tm || !temperature) + return -EINVAL; + + *temperature = POP_MEM_HIGH_TEMPERATURE; + + return 0; +} + + +static struct thermal_zone_device_ops pop_mem_thermal_zone_ops = { + .get_temp = pop_mem_tm_get_temperature, + .get_trip_type = pop_mem_tm_get_trip_type, + .get_trip_temp = pop_mem_tm_get_trip_temperature, + .get_crit_temp = pop_mem_tm_get_crit_temperature, +}; + + +static int __devinit pop_mem_tm_probe(struct platform_device *pdev) +{ + int rc, len, numcontrollers; + struct resource *controller_mem = NULL; + struct resource *res_mem = NULL; + struct pop_mem_tm_device *tmdev = NULL; + void __iomem *base = NULL; + + rc = len = 0; + numcontrollers = get_num_populated_chipselects(); + + if (pdev->id >= numcontrollers) { + pr_err("%s: memory controller %d does not exist", __func__, + pdev->id); + rc = -ENODEV; + goto fail; + } + + controller_mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "physbase"); + if (!controller_mem) { + pr_err("%s: could not get resources for controller %d", + __func__, pdev->id); + rc = -EFAULT; + goto fail; + } + + len = controller_mem->end - controller_mem->start + 1; + + res_mem = request_mem_region(controller_mem->start, len, + controller_mem->name); + if (!res_mem) { + pr_err("%s: Could not request memory region: " + "start=%p, len=%d\n", __func__, + (void *) controller_mem->start, len); + rc = -EBUSY; + goto fail; + + } + + base = ioremap(res_mem->start, len); + if (!base) { + pr_err("%s: Could not ioremap: start=%p, len=%d\n", + __func__, (void *) controller_mem->start, len); + rc = -EBUSY; + goto fail; + + } + + tmdev = kzalloc(sizeof(*tmdev), GFP_KERNEL); + if (tmdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + rc = -ENOMEM; + goto fail; + } + + if (numcontrollers == 1) { + tmdev->refresh_mask = POP_MEM_LPDDR1_REFRESH_MASK; + tmdev->refresh_shift = POP_MEM_LPDDR1_REFRESH_SHIFT; + } else { + tmdev->refresh_mask = POP_MEM_LPDDR2_REFRESH_MASK; + tmdev->refresh_shift = POP_MEM_LPDDR2_REFRESH_SHIFT; + } + tmdev->baseaddr = (unsigned long) base; + tmdev->tz_dev = thermal_zone_device_register("msm_popmem_tz", + POP_MEM_TRIP_NUM, tmdev, + &pop_mem_thermal_zone_ops, + 0, 0, 0, 0); + + if (tmdev->tz_dev == NULL) { + pr_err("%s: thermal_zone_device_register() failed.\n", + __func__); + goto fail; + } + + platform_set_drvdata(pdev, tmdev); + + pr_notice("%s: device %d probed successfully\n", __func__, pdev->id); + + return rc; + +fail: + if (base) + iounmap(base); + if (res_mem) + release_mem_region(controller_mem->start, len); + kfree(tmdev); + + return rc; +} + +static int __devexit pop_mem_tm_remove(struct platform_device *pdev) +{ + + int len; + struct pop_mem_tm_device *tmdev = platform_get_drvdata(pdev); + struct resource *controller_mem; + + iounmap((void __iomem *)tmdev->baseaddr); + + controller_mem = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "physbase"); + len = controller_mem->end - controller_mem->start + 1; + release_mem_region(controller_mem->start, len); + + thermal_zone_device_unregister(tmdev->tz_dev); + platform_set_drvdata(pdev, NULL); + kfree(tmdev); + + return 0; +} + +static struct platform_driver pop_mem_tm_driver = { + .probe = pop_mem_tm_probe, + .remove = pop_mem_tm_remove, + .driver = { + .name = "msm_popmem-tm", + .owner = THIS_MODULE + }, +}; + +static int __init pop_mem_tm_init(void) +{ + return platform_driver_register(&pop_mem_tm_driver); +} + +static void __exit pop_mem_tm_exit(void) +{ + platform_driver_unregister(&pop_mem_tm_driver); +} + +module_init(pop_mem_tm_init); +module_exit(pop_mem_tm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Pop memory thermal manager driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:popmem-tm"); diff --git a/drivers/thermal/msm_tsens.c b/drivers/thermal/msm_tsens.c new file mode 100644 index 00000000000..f4e094ecda8 --- /dev/null +++ b/drivers/thermal/msm_tsens.c @@ -0,0 +1,618 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm TSENS Thermal Manager driver + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Trips: from very hot to very cold */ +enum tsens_trip_type { + TSENS_TRIP_STAGE3 = 0, + TSENS_TRIP_STAGE2, + TSENS_TRIP_STAGE1, + TSENS_TRIP_STAGE0, + TSENS_TRIP_NUM, +}; + +#define TSENS_NUM_SENSORS 1 /* There are 5 but only 1 is useful now */ +#define TSENS_CAL_DEGC 30 /* degree C used for calibration */ +#define TSENS_QFPROM_ADDR (MSM_QFPROM_BASE + 0x000000bc) +#define TSENS_QFPROM_RED_TEMP_SENSOR0_SHIFT 24 +#define TSENS_QFPROM_TEMP_SENSOR0_SHIFT 16 +#define TSENS_QFPROM_TEMP_SENSOR0_MASK (255 << TSENS_QFPROM_TEMP_SENSOR0_SHIFT) +#define TSENS_SLOPE (0.702) /* slope in (degrees_C / ADC_code) */ +#define TSENS_FACTOR (1000) /* convert floating-point into integer */ +#define TSENS_CONFIG 01 /* this setting found to be optimal */ +#define TSENS_CONFIG_SHIFT 28 +#define TSENS_CONFIG_MASK (3 << TSENS_CONFIG_SHIFT) +#define TSENS_CNTL_ADDR (MSM_CLK_CTL_BASE + 0x00003620) +#define TSENS_EN (1 << 0) +#define TSENS_SW_RST (1 << 1) +#define SENSOR0_EN (1 << 3) +#define SENSOR1_EN (1 << 4) +#define SENSOR2_EN (1 << 5) +#define SENSOR3_EN (1 << 6) +#define SENSOR4_EN (1 << 7) +#define TSENS_MIN_STATUS_MASK (1 << 8) +#define TSENS_LOWER_STATUS_CLR (1 << 9) +#define TSENS_UPPER_STATUS_CLR (1 << 10) +#define TSENS_MAX_STATUS_MASK (1 << 11) +#define TSENS_MEASURE_PERIOD 4 /* 1 sec. default as required by Willie */ +#define TSENS_SLP_CLK_ENA (1 << 24) +#define TSENS_THRESHOLD_ADDR (MSM_CLK_CTL_BASE + 0x00003624) +#define TSENS_THRESHOLD_MAX_CODE (0xff) +#define TSENS_THRESHOLD_MAX_LIMIT_MASK (TSENS_THRESHOLD_MAX_CODE << 24) +#define TSENS_THRESHOLD_MIN_LIMIT_MASK (TSENS_THRESHOLD_MAX_CODE << 16) +#define TSENS_THRESHOLD_UPPER_LIMIT_MASK (TSENS_THRESHOLD_MAX_CODE << 8) +#define TSENS_THRESHOLD_LOWER_LIMIT_MASK (TSENS_THRESHOLD_MAX_CODE << 0) +/* Initial temperature threshold values */ +#define TSENS_LOWER_LIMIT_TH 0x50 +#define TSENS_UPPER_LIMIT_TH 0xdf +#define TSENS_MIN_LIMIT_TH 0x38 +#define TSENS_MAX_LIMIT_TH 0xff + +#define TSENS_S0_STATUS_ADDR (MSM_CLK_CTL_BASE + 0x00003628) +#define TSENS_INT_STATUS_ADDR (MSM_CLK_CTL_BASE + 0x0000363c) +#define TSENS_LOWER_INT_MASK (1 << 1) +#define TSENS_UPPER_INT_MASK (1 << 2) +#define TSENS_TRDY_MASK (1 << 7) + +struct tsens_tm_device_sensor { + struct thermal_zone_device *tz_dev; + enum thermal_device_mode mode; + unsigned int sensor_num; +}; + +struct tsens_tm_device { + struct tsens_tm_device_sensor sensor[TSENS_NUM_SENSORS]; + bool prev_reading_avail; + int offset; + struct work_struct work; +}; + +struct tsens_tm_device *tmdev; + +/* Temperature on y axis and ADC-code on x-axis */ +static int tsens_tz_code_to_degC(int adc_code) +{ + int degC, degcbeforefactor; + degcbeforefactor = adc_code * (int)(TSENS_SLOPE * TSENS_FACTOR) + + tmdev->offset; + if (degcbeforefactor == 0) + degC = degcbeforefactor; + else if (degcbeforefactor > 0) + degC = (degcbeforefactor + TSENS_FACTOR/2) / TSENS_FACTOR; + else /* rounding for negative degrees */ + degC = (degcbeforefactor - TSENS_FACTOR/2) / TSENS_FACTOR; + return degC; +} + +static int tsens_tz_degC_to_code(int degC) +{ + int code = (degC * TSENS_FACTOR - tmdev->offset + + (int)(TSENS_FACTOR * TSENS_SLOPE)/2) + / (int)(TSENS_FACTOR * TSENS_SLOPE); + if (code > 255) /* upper bound */ + code = 255; + else if (code < 0) /* lower bound */ + code = 0; + return code; +} + +static int tsens_tz_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + unsigned int code; + + if (!tm_sensor || tm_sensor->mode != THERMAL_DEVICE_ENABLED || !temp) + return -EINVAL; + + if (!tmdev->prev_reading_avail) { + while (!(readl(TSENS_INT_STATUS_ADDR) & TSENS_TRDY_MASK)) + msleep(1); + tmdev->prev_reading_avail = 1; + } + + code = readl(TSENS_S0_STATUS_ADDR + (tm_sensor->sensor_num << 2)); + *temp = tsens_tz_code_to_degC(code); + + return 0; +} + +static int tsens_tz_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + + if (!tm_sensor || !mode) + return -EINVAL; + + *mode = tm_sensor->mode; + + return 0; +} + +static int tsens_tz_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + unsigned int reg, mask; + + if (!tm_sensor) + return -EINVAL; + + if (mode != tm_sensor->mode) { + pr_info("%s: mode: %d --> %d\n", __func__, tm_sensor->mode, + mode); + + reg = readl(TSENS_CNTL_ADDR); + mask = 1 << (tm_sensor->sensor_num + 3); + if (mode == THERMAL_DEVICE_ENABLED) { + writel(reg | TSENS_SW_RST, TSENS_CNTL_ADDR); + reg |= mask | TSENS_SLP_CLK_ENA | TSENS_EN; + tmdev->prev_reading_avail = 0; + } else { + reg &= ~mask; + if (!(reg & (((1 << TSENS_NUM_SENSORS) - 1) << 3))) + reg &= ~(TSENS_SLP_CLK_ENA | TSENS_EN); + } + + writel(reg, TSENS_CNTL_ADDR); + } + tm_sensor->mode = mode; + + return 0; +} + +static int tsens_tz_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + + if (!tm_sensor || trip < 0 || !type) + return -EINVAL; + + switch (trip) { + case TSENS_TRIP_STAGE3: + *type = THERMAL_TRIP_CRITICAL; + break; + case TSENS_TRIP_STAGE2: + *type = THERMAL_TRIP_CONFIGURABLE_HI; + break; + case TSENS_TRIP_STAGE1: + *type = THERMAL_TRIP_CONFIGURABLE_LOW; + break; + case TSENS_TRIP_STAGE0: + *type = THERMAL_TRIP_CRITICAL_LOW; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tsens_tz_activate_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_activation_mode mode) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + unsigned int reg_cntl, reg_th, code, hi_code, lo_code, mask; + + if (!tm_sensor || trip < 0) + return -EINVAL; + + lo_code = 0; + hi_code = TSENS_THRESHOLD_MAX_CODE; + + reg_cntl = readl(TSENS_CNTL_ADDR); + reg_th = readl(TSENS_THRESHOLD_ADDR); + switch (trip) { + case TSENS_TRIP_STAGE3: + code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) >> 24; + mask = TSENS_MAX_STATUS_MASK; + + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> 8; + else if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK); + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> 16; + break; + case TSENS_TRIP_STAGE2: + code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) >> 8; + mask = TSENS_UPPER_STATUS_CLR; + + if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> 24; + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK); + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> 16; + break; + case TSENS_TRIP_STAGE1: + code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK) >> 0; + mask = TSENS_LOWER_STATUS_CLR; + + if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> 16; + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> 8; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> 24; + break; + case TSENS_TRIP_STAGE0: + code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) >> 16; + mask = TSENS_MIN_STATUS_MASK; + + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK); + else if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> 8; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> 24; + break; + default: + return -EINVAL; + } + + if (mode == THERMAL_TRIP_ACTIVATION_DISABLED) + writel(reg_cntl | mask, TSENS_CNTL_ADDR); + else { + if (code < lo_code || code > hi_code) + return -EINVAL; + writel(reg_cntl & ~mask, TSENS_CNTL_ADDR); + } + + return 0; +} + +static int tsens_tz_get_trip_temp(struct thermal_zone_device *thermal, + int trip, unsigned long *temp) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + unsigned int reg; + + if (!tm_sensor || trip < 0 || !temp) + return -EINVAL; + + reg = readl(TSENS_THRESHOLD_ADDR); + switch (trip) { + case TSENS_TRIP_STAGE3: + reg = (reg & TSENS_THRESHOLD_MAX_LIMIT_MASK) >> 24; + break; + case TSENS_TRIP_STAGE2: + reg = (reg & TSENS_THRESHOLD_UPPER_LIMIT_MASK) >> 8; + break; + case TSENS_TRIP_STAGE1: + reg = (reg & TSENS_THRESHOLD_LOWER_LIMIT_MASK) >> 0; + break; + case TSENS_TRIP_STAGE0: + reg = (reg & TSENS_THRESHOLD_MIN_LIMIT_MASK) >> 16; + break; + default: + return -EINVAL; + } + + *temp = tsens_tz_code_to_degC(reg); + + return 0; +} + +static int tsens_tz_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + return tsens_tz_get_trip_temp(thermal, TSENS_TRIP_STAGE3, temp); +} + +static int tsens_tz_set_trip_temp(struct thermal_zone_device *thermal, + int trip, long temp) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + unsigned int reg_th, reg_cntl; + int code, hi_code, lo_code, code_err_chk; + + code_err_chk = code = tsens_tz_degC_to_code(temp); + if (!tm_sensor || trip < 0) + return -EINVAL; + + lo_code = 0; + hi_code = TSENS_THRESHOLD_MAX_CODE; + + reg_cntl = readl(TSENS_CNTL_ADDR); + reg_th = readl(TSENS_THRESHOLD_ADDR); + switch (trip) { + case TSENS_TRIP_STAGE3: + code <<= 24; + reg_th &= ~TSENS_THRESHOLD_MAX_LIMIT_MASK; + + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> 8; + else if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK); + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> 16; + break; + case TSENS_TRIP_STAGE2: + code <<= 8; + reg_th &= ~TSENS_THRESHOLD_UPPER_LIMIT_MASK; + + if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> 24; + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + lo_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK); + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> 16; + break; + case TSENS_TRIP_STAGE1: + reg_th &= ~TSENS_THRESHOLD_LOWER_LIMIT_MASK; + + if (!(reg_cntl & TSENS_MIN_STATUS_MASK)) + lo_code = (reg_th & TSENS_THRESHOLD_MIN_LIMIT_MASK) + >> 16; + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> 8; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> 24; + break; + case TSENS_TRIP_STAGE0: + code <<= 16; + reg_th &= ~TSENS_THRESHOLD_MIN_LIMIT_MASK; + + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_LOWER_LIMIT_MASK); + else if (!(reg_cntl & TSENS_UPPER_STATUS_CLR)) + hi_code = (reg_th & TSENS_THRESHOLD_UPPER_LIMIT_MASK) + >> 8; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK)) + hi_code = (reg_th & TSENS_THRESHOLD_MAX_LIMIT_MASK) + >> 24; + break; + default: + return -EINVAL; + } + + if (code_err_chk < lo_code || code_err_chk > hi_code) + return -EINVAL; + + writel(reg_th | code, TSENS_THRESHOLD_ADDR); + return 0; +} + +static struct thermal_zone_device_ops tsens_thermal_zone_ops = { + .get_temp = tsens_tz_get_temp, + .get_mode = tsens_tz_get_mode, + .set_mode = tsens_tz_set_mode, + .get_trip_type = tsens_tz_get_trip_type, + .activate_trip_type = tsens_tz_activate_trip_type, + .get_trip_temp = tsens_tz_get_trip_temp, + .set_trip_temp = tsens_tz_set_trip_temp, + .get_crit_temp = tsens_tz_get_crit_temp, +}; + +static void notify_uspace_tsens_fn(struct work_struct *work) +{ + struct tsens_tm_device *tm = container_of(work, struct tsens_tm_device, + work); + /* Currently only Sensor0 is supported. We added support + to notify only the supported Sensor and this portion + needs to be revisited once other sensors are supported */ + sysfs_notify(&tm->sensor[0].tz_dev->device.kobj, + NULL, "type"); +} + +static irqreturn_t tsens_isr(int irq, void *data) +{ + unsigned int reg = readl(TSENS_CNTL_ADDR); + + writel(reg | TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR, + TSENS_CNTL_ADDR); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t tsens_isr_thread(int irq, void *data) +{ + struct tsens_tm_device *tm = data; + unsigned int threshold, threshold_low, i, code, reg, sensor, mask; + bool upper_th_x, lower_th_x; + int adc_code; + + mask = ~(TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR); + threshold = readl(TSENS_THRESHOLD_ADDR); + threshold_low = threshold & TSENS_THRESHOLD_LOWER_LIMIT_MASK; + threshold = (threshold & TSENS_THRESHOLD_UPPER_LIMIT_MASK) >> 8; + reg = sensor = readl(TSENS_CNTL_ADDR); + sensor &= (SENSOR0_EN | SENSOR1_EN | SENSOR2_EN | + SENSOR3_EN | SENSOR4_EN); + sensor >>= 3; + for (i = 0; i < TSENS_NUM_SENSORS; i++) { + if (sensor & 1) { + code = readl(TSENS_S0_STATUS_ADDR + (i << 2)); + upper_th_x = code >= threshold; + lower_th_x = code <= threshold_low; + if (upper_th_x) + mask |= TSENS_UPPER_STATUS_CLR; + if (lower_th_x) + mask |= TSENS_LOWER_STATUS_CLR; + if (upper_th_x || lower_th_x) { + thermal_zone_device_update( + tm->sensor[i].tz_dev); + + /* Notify user space */ + schedule_work(&tm->work); + adc_code = readl(TSENS_S0_STATUS_ADDR + + (i << 2)); + printk(KERN_INFO"\nTrip point triggered by " + "current temperature (%d degrees) " + "measured by Temperature-Sensor %d\n", + tsens_tz_code_to_degC(adc_code), i); + } + } + sensor >>= 1; + } + writel(reg & mask, TSENS_CNTL_ADDR); + return IRQ_HANDLED; +} + +static int __devinit tsens_tm_probe(struct platform_device *pdev) +{ + unsigned int reg, i, calib_data, calib_data_backup; + int rc; + + calib_data = (readl(TSENS_QFPROM_ADDR) & TSENS_QFPROM_TEMP_SENSOR0_MASK) + >> TSENS_QFPROM_TEMP_SENSOR0_SHIFT; + calib_data_backup = readl(TSENS_QFPROM_ADDR) + >> TSENS_QFPROM_RED_TEMP_SENSOR0_SHIFT; + + if (calib_data_backup) + calib_data = calib_data_backup; + + if (!calib_data) { + pr_err("%s: No temperature sensor data for calibration" + " in QFPROM!\n", __func__); + return -ENODEV; + } + + tmdev = kzalloc(sizeof(struct tsens_tm_device), GFP_KERNEL); + if (tmdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + platform_set_drvdata(pdev, tmdev); + + tmdev->offset = TSENS_FACTOR * TSENS_CAL_DEGC + - (int)(TSENS_FACTOR * TSENS_SLOPE) * calib_data; + tmdev->prev_reading_avail = 0; + + INIT_WORK(&tmdev->work, notify_uspace_tsens_fn); + + reg = readl(TSENS_CNTL_ADDR); + writel(reg | TSENS_SW_RST, TSENS_CNTL_ADDR); + reg |= TSENS_SLP_CLK_ENA | TSENS_EN | (TSENS_MEASURE_PERIOD << 16) | + TSENS_LOWER_STATUS_CLR | TSENS_UPPER_STATUS_CLR | + TSENS_MIN_STATUS_MASK | TSENS_MAX_STATUS_MASK | + (((1 << TSENS_NUM_SENSORS) - 1) << 3); + + /* set TSENS_CONFIG bits (bits 29:28 of TSENS_CNTL) to '01'; + this setting found to be optimal. */ + reg = (reg & ~TSENS_CONFIG_MASK) | (TSENS_CONFIG << TSENS_CONFIG_SHIFT); + + writel(reg, TSENS_CNTL_ADDR); + + writel((TSENS_LOWER_LIMIT_TH << 0) | (TSENS_UPPER_LIMIT_TH << 8) | + (TSENS_MIN_LIMIT_TH << 16) | (TSENS_MAX_LIMIT_TH << 24), + TSENS_THRESHOLD_ADDR); + + for (i = 0; i < TSENS_NUM_SENSORS; i++) { + char name[17]; + sprintf(name, "tsens_tz_sensor%d", i); + + tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED; + tmdev->sensor[i].tz_dev = thermal_zone_device_register(name, + TSENS_TRIP_NUM, &tmdev->sensor[i], + &tsens_thermal_zone_ops, 0, 0, 0, 0); + if (tmdev->sensor[i].tz_dev == NULL) { + pr_err("%s: thermal_zone_device_register() failed.\n", + __func__); + kfree(tmdev); + return -ENODEV; + } + tmdev->sensor[i].sensor_num = i; + thermal_zone_device_update(tmdev->sensor[i].tz_dev); + tmdev->sensor[i].mode = THERMAL_DEVICE_DISABLED; + } + + rc = request_threaded_irq(TSENS_UPPER_LOWER_INT, tsens_isr, + tsens_isr_thread, 0, "tsens", tmdev); + if (rc < 0) { + pr_err("%s: request_irq FAIL: %d\n", __func__, rc); + kfree(tmdev); + return rc; + } + + writel(reg & ~((((1 << TSENS_NUM_SENSORS) - 1) << 3) + | TSENS_SLP_CLK_ENA | TSENS_EN), TSENS_CNTL_ADDR); + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit tsens_tm_remove(struct platform_device *pdev) +{ + struct tsens_tm_device *tmdev = platform_get_drvdata(pdev); + unsigned int reg, i; + + reg = readl(TSENS_CNTL_ADDR); + writel(reg & ~(TSENS_SLP_CLK_ENA | TSENS_EN), TSENS_CNTL_ADDR); + + for (i = 0; i < TSENS_NUM_SENSORS; i++) + thermal_zone_device_unregister(tmdev->sensor[i].tz_dev); + platform_set_drvdata(pdev, NULL); + free_irq(TSENS_UPPER_LOWER_INT, tmdev); + kfree(tmdev); + + return 0; +} + +static struct platform_driver tsens_tm_driver = { + .probe = tsens_tm_probe, + .remove = __devexit_p(tsens_tm_remove), + .driver = { + .name = "tsens-tm", + .owner = THIS_MODULE, + }, +}; + +static int __init tsens_init(void) +{ + return platform_driver_register(&tsens_tm_driver); +} + +static void __exit tsens_exit(void) +{ + platform_driver_unregister(&tsens_tm_driver); +} + +module_init(tsens_init); +module_exit(tsens_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM Temperature Sensor driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:tsens-tm"); diff --git a/drivers/thermal/pm8xxx-tm.c b/drivers/thermal/pm8xxx-tm.c new file mode 100644 index 00000000000..a094aeda07e --- /dev/null +++ b/drivers/thermal/pm8xxx-tm.c @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Qualcomm PMIC PM8xxx Thermal Manager driver + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register TEMP_ALARM_CTRL bits */ +#define TEMP_ALARM_CTRL_ST3_SD 0x80 +#define TEMP_ALARM_CTRL_ST2_SD 0x40 +#define TEMP_ALARM_CTRL_STATUS_MASK 0x30 +#define TEMP_ALARM_CTRL_STATUS_SHIFT 4 +#define TEMP_ALARM_CTRL_THRESH_MASK 0x0C +#define TEMP_ALARM_CTRL_THRESH_SHIFT 2 +#define TEMP_ALARM_CTRL_OVRD_ST3 0x02 +#define TEMP_ALARM_CTRL_OVRD_ST2 0x01 +#define TEMP_ALARM_CTRL_OVRD_MASK 0x03 + +#define TEMP_STAGE_STEP 20000 /* Stage step: 20.000 C */ +#define TEMP_STAGE_HYSTERESIS 2000 + +#define TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */ +#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */ + +/* Register TEMP_ALARM_PWM bits */ +#define TEMP_ALARM_PWM_EN_MASK 0xC0 +#define TEMP_ALARM_PWM_EN_SHIFT 6 +#define TEMP_ALARM_PWM_PER_PRE_MASK 0x38 +#define TEMP_ALARM_PWM_PER_PRE_SHIFT 3 +#define TEMP_ALARM_PWM_PER_DIV_MASK 0x07 +#define TEMP_ALARM_PWM_PER_DIV_SHIFT 0 + +/* Trips: from critical to less critical */ +#define TRIP_STAGE3 0 +#define TRIP_STAGE2 1 +#define TRIP_STAGE1 2 +#define TRIP_NUM 3 + +struct pm8xxx_tm_chip { + struct pm8xxx_tm_core_data cdata; + struct work_struct irq_work; + struct device *dev; + struct thermal_zone_device *tz_dev; + unsigned long temp; + enum thermal_device_mode mode; + unsigned int thresh; + unsigned int stage; + unsigned int tempstat_irq; + unsigned int overtemp_irq; + void *adc_handle; +}; + +enum pmic_thermal_override_mode { + SOFTWARE_OVERRIDE_DISABLED = 0, + SOFTWARE_OVERRIDE_ENABLED, +}; + +static inline int pm8xxx_tm_read_ctrl(struct pm8xxx_tm_chip *chip, u8 *reg) +{ + int rc; + + rc = pm8xxx_readb(chip->dev->parent, + chip->cdata.reg_addr_temp_alarm_ctrl, reg); + if (rc) + pr_err("%s: pm8xxx_readb(0x%03X) failed, rc=%d\n", + chip->cdata.tm_name, + chip->cdata.reg_addr_temp_alarm_ctrl, rc); + + return rc; +} + +static inline int pm8xxx_tm_write_ctrl(struct pm8xxx_tm_chip *chip, u8 reg) +{ + int rc; + + rc = pm8xxx_writeb(chip->dev->parent, + chip->cdata.reg_addr_temp_alarm_ctrl, reg); + if (rc) + pr_err("%s: pm8xxx_writeb(0x%03X)=0x%02X failed, rc=%d\n", + chip->cdata.tm_name, + chip->cdata.reg_addr_temp_alarm_ctrl, reg, rc); + + return rc; +} + +static inline int pm8xxx_tm_write_pwm(struct pm8xxx_tm_chip *chip, u8 reg) +{ + int rc; + + rc = pm8xxx_writeb(chip->dev->parent, + chip->cdata.reg_addr_temp_alarm_pwm, reg); + if (rc) + pr_err("%s: pm8xxx_writeb(0x%03X)=0x%02X failed, rc=%d\n", + chip->cdata.tm_name, + chip->cdata.reg_addr_temp_alarm_pwm, reg, rc); + + return rc; +} + +static inline int +pm8xxx_tm_shutdown_override(struct pm8xxx_tm_chip *chip, + enum pmic_thermal_override_mode mode) +{ + int rc; + u8 reg; + + rc = pm8xxx_tm_read_ctrl(chip, ®); + if (rc < 0) + return rc; + + reg &= ~(TEMP_ALARM_CTRL_OVRD_MASK | TEMP_ALARM_CTRL_STATUS_MASK); + if (mode == SOFTWARE_OVERRIDE_ENABLED) + reg |= (TEMP_ALARM_CTRL_OVRD_ST3 | TEMP_ALARM_CTRL_OVRD_ST2) & + TEMP_ALARM_CTRL_OVRD_MASK; + + rc = pm8xxx_tm_write_ctrl(chip, reg); + + return rc; +} + +/* + * This function initializes the internal temperature value based on only the + * current thermal stage and threshold. + */ +static int pm8xxx_tm_init_temp_no_adc(struct pm8xxx_tm_chip *chip) +{ + int rc; + u8 reg; + + rc = pm8xxx_tm_read_ctrl(chip, ®); + if (rc < 0) + return rc; + + chip->stage = (reg & TEMP_ALARM_CTRL_STATUS_MASK) + >> TEMP_ALARM_CTRL_STATUS_SHIFT; + chip->thresh = (reg & TEMP_ALARM_CTRL_THRESH_MASK) + >> TEMP_ALARM_CTRL_THRESH_SHIFT; + + if (chip->stage) + chip->temp = chip->thresh * TEMP_THRESH_MIN + + (chip->stage - 1) * TEMP_STAGE_STEP + + TEMP_THRESH_MIN; + else + chip->temp = chip->cdata.default_no_adc_temp; + + return 0; +} + +/* + * This function updates the internal temperature value based on the + * current thermal stage and threshold as well as the previous stage + */ +static int pm8xxx_tm_update_temp_no_adc(struct pm8xxx_tm_chip *chip) +{ + unsigned int stage; + int rc; + u8 reg; + + rc = pm8xxx_tm_read_ctrl(chip, ®); + if (rc < 0) + return rc; + + stage = (reg & TEMP_ALARM_CTRL_STATUS_MASK) + >> TEMP_ALARM_CTRL_STATUS_SHIFT; + chip->thresh = (reg & TEMP_ALARM_CTRL_THRESH_MASK) + >> TEMP_ALARM_CTRL_THRESH_SHIFT; + + if (stage > chip->stage) { + /* increasing stage, use lower bound */ + chip->temp = (stage - 1) * TEMP_STAGE_STEP + + chip->thresh * TEMP_THRESH_STEP + + TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN; + } else if (stage < chip->stage) { + /* decreasing stage, use upper bound */ + chip->temp = stage * TEMP_STAGE_STEP + + chip->thresh * TEMP_THRESH_STEP + - TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN; + } + + chip->stage = stage; + + return 0; +} + +static int pm8xxx_tz_get_temp_no_adc(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct pm8xxx_tm_chip *chip = thermal->devdata; + int rc; + + if (!chip || !temp) + return -EINVAL; + + rc = pm8xxx_tm_update_temp_no_adc(chip); + if (rc < 0) + return rc; + + *temp = chip->temp; + + return 0; +} + +static int pm8xxx_tz_get_temp_pm8921_adc(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct pm8xxx_tm_chip *chip = thermal->devdata; + struct pm8921_adc_chan_result result = { + .physical = 0lu, + }; + int rc; + + if (!chip || !temp) + return -EINVAL; + + *temp = chip->temp; + + rc = pm8921_adc_read(chip->cdata.adc_channel, &result); + if (rc < 0) { + pr_err("%s: adc_channel_read_result() failed, rc = %d\n", + chip->cdata.tm_name, rc); + return rc; + } + + *temp = result.physical; + chip->temp = result.physical; + + return 0; +} + +static int pm8xxx_tz_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct pm8xxx_tm_chip *chip = thermal->devdata; + + if (!chip || !mode) + return -EINVAL; + + *mode = chip->mode; + + return 0; +} + +static int pm8xxx_tz_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct pm8xxx_tm_chip *chip = thermal->devdata; + + if (!chip) + return -EINVAL; + + if (mode != chip->mode) { + if (mode == THERMAL_DEVICE_ENABLED) + pm8xxx_tm_shutdown_override(chip, + SOFTWARE_OVERRIDE_ENABLED); + else + pm8xxx_tm_shutdown_override(chip, + SOFTWARE_OVERRIDE_DISABLED); + } + chip->mode = mode; + + return 0; +} + +static int pm8xxx_tz_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + if (trip < 0 || !type) + return -EINVAL; + + switch (trip) { + case TRIP_STAGE3: + *type = THERMAL_TRIP_CRITICAL; + break; + case TRIP_STAGE2: + *type = THERMAL_TRIP_HOT; + break; + case TRIP_STAGE1: + *type = THERMAL_TRIP_HOT; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pm8xxx_tz_get_trip_temp(struct thermal_zone_device *thermal, + int trip, unsigned long *temp) +{ + struct pm8xxx_tm_chip *chip = thermal->devdata; + int thresh_temp; + + if (!chip || trip < 0 || !temp) + return -EINVAL; + + thresh_temp = chip->thresh * TEMP_THRESH_STEP + + TEMP_THRESH_MIN; + + switch (trip) { + case TRIP_STAGE3: + thresh_temp += 2 * TEMP_STAGE_STEP; + break; + case TRIP_STAGE2: + thresh_temp += TEMP_STAGE_STEP; + break; + case TRIP_STAGE1: + break; + default: + return -EINVAL; + } + + *temp = thresh_temp; + + return 0; +} + +static int pm8xxx_tz_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct pm8xxx_tm_chip *chip = thermal->devdata; + + if (!chip || !temp) + return -EINVAL; + + *temp = chip->thresh * TEMP_THRESH_STEP + TEMP_THRESH_MIN + + 2 * TEMP_STAGE_STEP; + + return 0; +} + +static struct thermal_zone_device_ops pm8xxx_thermal_zone_ops_no_adc = { + .get_temp = pm8xxx_tz_get_temp_no_adc, + .get_mode = pm8xxx_tz_get_mode, + .set_mode = pm8xxx_tz_set_mode, + .get_trip_type = pm8xxx_tz_get_trip_type, + .get_trip_temp = pm8xxx_tz_get_trip_temp, + .get_crit_temp = pm8xxx_tz_get_crit_temp, +}; + +static struct thermal_zone_device_ops pm8xxx_thermal_zone_ops_pm8921_adc = { + .get_temp = pm8xxx_tz_get_temp_pm8921_adc, + .get_mode = pm8xxx_tz_get_mode, + .set_mode = pm8xxx_tz_set_mode, + .get_trip_type = pm8xxx_tz_get_trip_type, + .get_trip_temp = pm8xxx_tz_get_trip_temp, + .get_crit_temp = pm8xxx_tz_get_crit_temp, +}; + +static void pm8xxx_tm_work(struct work_struct *work) +{ + struct pm8xxx_tm_chip *chip + = container_of(work, struct pm8xxx_tm_chip, irq_work); + int rc; + u8 reg; + + rc = pm8xxx_tm_read_ctrl(chip, ®); + if (rc < 0) + goto bail; + + if (chip->cdata.adc_type == PM8XXX_TM_ADC_NONE) { + rc = pm8xxx_tm_update_temp_no_adc(chip); + if (rc < 0) + goto bail; + pr_info("%s: Temp Alarm - stage=%u, threshold=%u, " + "temp=%lu mC\n", chip->cdata.tm_name, chip->stage, + chip->thresh, chip->temp); + } else { + chip->stage = (reg & TEMP_ALARM_CTRL_STATUS_MASK) + >> TEMP_ALARM_CTRL_STATUS_SHIFT; + chip->thresh = (reg & TEMP_ALARM_CTRL_THRESH_MASK) + >> TEMP_ALARM_CTRL_THRESH_SHIFT; + pr_info("%s: Temp Alarm - stage=%u, threshold=%u\n", + chip->cdata.tm_name, chip->stage, chip->thresh); + } + + /* Clear status bits. */ + if (reg & (TEMP_ALARM_CTRL_ST2_SD | TEMP_ALARM_CTRL_ST3_SD)) { + reg &= ~(TEMP_ALARM_CTRL_ST2_SD | TEMP_ALARM_CTRL_ST3_SD + | TEMP_ALARM_CTRL_STATUS_MASK); + + pm8xxx_tm_write_ctrl(chip, reg); + } + + thermal_zone_device_update(chip->tz_dev); + + /* Notify user space */ + if (chip->mode == THERMAL_DEVICE_ENABLED) + kobject_uevent(&chip->tz_dev->device.kobj, KOBJ_CHANGE); + +bail: + enable_irq(chip->tempstat_irq); + enable_irq(chip->overtemp_irq); +} + +static irqreturn_t pm8xxx_tm_isr(int irq, void *data) +{ + struct pm8xxx_tm_chip *chip = data; + + disable_irq_nosync(chip->tempstat_irq); + disable_irq_nosync(chip->overtemp_irq); + schedule_work(&chip->irq_work); + + return IRQ_HANDLED; +} + +static int pm8xxx_tm_init_reg(struct pm8xxx_tm_chip *chip) +{ + int rc; + u8 reg; + + rc = pm8xxx_tm_read_ctrl(chip, ®); + if (rc < 0) + return rc; + + chip->stage = (reg & TEMP_ALARM_CTRL_STATUS_MASK) + >> TEMP_ALARM_CTRL_STATUS_SHIFT; + chip->temp = 0; + + /* Use temperature threshold set 0: (105, 125, 145) */ + chip->thresh = 0; + reg = (chip->thresh << TEMP_ALARM_CTRL_THRESH_SHIFT) + & TEMP_ALARM_CTRL_THRESH_MASK; + rc = pm8xxx_tm_write_ctrl(chip, reg); + if (rc < 0) + return rc; + + /* + * Set the PMIC alarm module PWM to have a frequency of 8 Hz. This + * helps cut down on the number of unnecessary interrupts fired when + * changing between thermal stages. Also, Enable the over temperature + * PWM whenever the PMIC is enabled. + */ + reg = (1 << TEMP_ALARM_PWM_EN_SHIFT) + | (3 << TEMP_ALARM_PWM_PER_PRE_SHIFT) + | (3 << TEMP_ALARM_PWM_PER_DIV_SHIFT); + + rc = pm8xxx_tm_write_pwm(chip, reg); + + return rc; +} + +static int __devinit pm8xxx_tm_probe(struct platform_device *pdev) +{ + const struct pm8xxx_tm_core_data *cdata = pdev->dev.platform_data; + struct thermal_zone_device_ops *tz_ops; + struct pm8xxx_tm_chip *chip; + struct resource *res; + int rc = 0; + + if (!cdata) { + pr_err("missing core data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct pm8xxx_tm_chip), GFP_KERNEL); + if (chip == NULL) { + pr_err("kzalloc() failed.\n"); + return -ENOMEM; + } + + chip->dev = &pdev->dev; + memcpy(&(chip->cdata), cdata, sizeof(struct pm8xxx_tm_core_data)); + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + chip->cdata.irq_name_temp_stat); + if (res) { + chip->tempstat_irq = res->start; + } else { + pr_err("temp stat IRQ not specified\n"); + goto err_free_chip; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + chip->cdata.irq_name_over_temp); + if (res) { + chip->overtemp_irq = res->start; + } else { + pr_err("over temp IRQ not specified\n"); + goto err_free_chip; + } + + /* Select proper thermal zone ops functions based on ADC type. */ + if (chip->cdata.adc_type == PM8XXX_TM_ADC_PM8921_ADC) + tz_ops = &pm8xxx_thermal_zone_ops_pm8921_adc; + else + tz_ops = &pm8xxx_thermal_zone_ops_no_adc; + + chip->tz_dev = thermal_zone_device_register(chip->cdata.tm_name, + TRIP_NUM, chip, tz_ops, 0, 0, 0, 0); + if (chip->tz_dev == NULL) { + pr_err("thermal_zone_device_register() failed.\n"); + rc = -ENODEV; + goto err_free_chip; + } + + rc = pm8xxx_tm_init_reg(chip); + if (rc < 0) + goto err_free_tz; + rc = pm8xxx_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_DISABLED); + if (rc < 0) + goto err_free_tz; + + if (chip->cdata.adc_type == PM8XXX_TM_ADC_NONE) { + rc = pm8xxx_tm_init_temp_no_adc(chip); + if (rc < 0) + goto err_free_tz; + } + + /* Start in HW control; switch to SW control when user changes mode. */ + chip->mode = THERMAL_DEVICE_DISABLED; + thermal_zone_device_update(chip->tz_dev); + + INIT_WORK(&chip->irq_work, pm8xxx_tm_work); + + rc = request_irq(chip->tempstat_irq, pm8xxx_tm_isr, IRQF_TRIGGER_RISING, + chip->cdata.irq_name_temp_stat, chip); + if (rc < 0) { + pr_err("request_irq(%d) failed: %d\n", chip->tempstat_irq, rc); + goto err_cancel_work; + } + + rc = request_irq(chip->overtemp_irq, pm8xxx_tm_isr, IRQF_TRIGGER_RISING, + chip->cdata.irq_name_over_temp, chip); + if (rc < 0) { + pr_err("request_irq(%d) failed: %d\n", chip->overtemp_irq, rc); + goto err_free_irq_tempstat; + } + + platform_set_drvdata(pdev, chip); + + pr_info("OK\n"); + + return 0; + +err_free_irq_tempstat: + free_irq(chip->tempstat_irq, chip); +err_cancel_work: + cancel_work_sync(&chip->irq_work); +err_free_tz: + thermal_zone_device_unregister(chip->tz_dev); +err_free_chip: + kfree(chip); + return rc; +} + +static int __devexit pm8xxx_tm_remove(struct platform_device *pdev) +{ + struct pm8xxx_tm_chip *chip = platform_get_drvdata(pdev); + + if (chip) { + platform_set_drvdata(pdev, NULL); + cancel_work_sync(&chip->irq_work); + free_irq(chip->overtemp_irq, chip); + free_irq(chip->tempstat_irq, chip); + pm8xxx_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_DISABLED); + thermal_zone_device_unregister(chip->tz_dev); + kfree(chip); + } + return 0; +} + +#ifdef CONFIG_PM +static int pm8xxx_tm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm8xxx_tm_chip *chip = platform_get_drvdata(pdev); + + /* Clear override bits in suspend to allow hardware control */ + pm8xxx_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_DISABLED); + + return 0; +} + +static int pm8xxx_tm_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm8xxx_tm_chip *chip = platform_get_drvdata(pdev); + + /* Override hardware actions so software can control */ + if (chip->mode == THERMAL_DEVICE_ENABLED) + pm8xxx_tm_shutdown_override(chip, SOFTWARE_OVERRIDE_ENABLED); + + return 0; +} + +static const struct dev_pm_ops pm8xxx_tm_pm_ops = { + .suspend = pm8xxx_tm_suspend, + .resume = pm8xxx_tm_resume, +}; + +#define PM8XXX_TM_PM_OPS (&pm8xxx_tm_pm_ops) +#else +#define PM8XXX_TM_PM_OPS NULL +#endif + +static struct platform_driver pm8xxx_tm_driver = { + .probe = pm8xxx_tm_probe, + .remove = __devexit_p(pm8xxx_tm_remove), + .driver = { + .name = PM8XXX_TM_DEV_NAME, + .owner = THIS_MODULE, + .pm = PM8XXX_TM_PM_OPS, + }, +}; + +static int __init pm8xxx_tm_init(void) +{ + return platform_driver_register(&pm8xxx_tm_driver); +} + +static void __exit pm8xxx_tm_exit(void) +{ + platform_driver_unregister(&pm8xxx_tm_driver); +} + +module_init(pm8xxx_tm_init); +module_exit(pm8xxx_tm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PM8xxx Thermal Manager driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:" PM8XXX_TM_DEV_NAME); diff --git a/drivers/thermal/pmic8058-tm.c b/drivers/thermal/pmic8058-tm.c new file mode 100644 index 00000000000..cc98f379f22 --- /dev/null +++ b/drivers/thermal/pmic8058-tm.c @@ -0,0 +1,509 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8058 Thermal Manager driver + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* PMIC8058 TEMP_ALRM registers */ +#define SSBI_REG_TEMP_ALRM_CTRL 0x1B +#define SSBI_REG_TEMP_ALRM_PWM 0x9B +#define SSBI_REG_TEMP_ALRM_TEST1 0x7A +#define SSBI_REG_TEMP_ALRM_TEST2 0xAB + +/* TEMP_ALRM_CTRL */ +#define PM8058_TEMP_ST3_SD 0x80 +#define PM8058_TEMP_ST2_SD 0x40 +#define PM8058_TEMP_STATUS_MASK 0x30 +#define PM8058_TEMP_STATUS_SHIFT 4 +#define PM8058_TEMP_THRESH_MASK 0x0C +#define PM8058_TEMP_THRESH_SHIFT 2 +#define PM8058_TEMP_OVRD_ST3 0x02 +#define PM8058_TEMP_OVRD_ST2 0x01 +#define PM8058_TEMP_OVRD_MASK 0x03 + +#define PM8058_TEMP_STAGE_STEP 20000 /* Stage step: 20 C */ +#define PM8058_TEMP_STAGE_HYSTERESIS 2000 + +#define PM8058_TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */ +#define PM8058_TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */ + +/* TEMP_ALRM_PWM */ +#define PM8058_TEMP_PWM_EN_MASK 0xC0 +#define PM8058_TEMP_PWM_EN_SHIFT 6 +#define PM8058_TEMP_PWM_PER_PRE_MASK 0x38 +#define PM8058_TEMP_PWM_PER_PRE_SHIFT 3 +#define PM8058_TEMP_PWM_PER_DIV_MASK 0x07 +#define PM8058_TEMP_PWM_PER_DIV_SHIFT 0 + +/* Trips: from critical to less critical */ +#define PM8058_TRIP_STAGE3 0 +#define PM8058_TRIP_STAGE2 1 +#define PM8058_TRIP_STAGE1 2 +#define PM8058_TRIP_NUM 3 + +#define PM8058_TEMP_ADC_CH CHANNEL_ADC_DIE_TEMP + +struct pm8058_tm_device { + struct pm8058_chip *pm_chip; + struct thermal_zone_device *tz_dev; + unsigned long temp; + enum thermal_device_mode mode; + unsigned int thresh; + unsigned int stage; + unsigned int irq; + void *adc_handle; +}; + +enum pmic_thermal_override_mode { + SOFTWARE_OVERRIDE_DISABLED = 0, + SOFTWARE_OVERRIDE_ENABLED, +}; + +static inline int pm8058_tm_read_ctrl(struct pm8058_chip *chip, u8 *reg) +{ + int rc; + + rc = pm8058_read(chip, SSBI_REG_TEMP_ALRM_CTRL, reg, 1); + if (rc) + pr_err("%s: pm8058_read FAIL: rc=%d\n", __func__, rc); + + return rc; +} + +static inline int pm8058_tm_write_ctrl(struct pm8058_chip *chip, u8 reg) +{ + int rc; + + rc = pm8058_write(chip, SSBI_REG_TEMP_ALRM_CTRL, ®, 1); + if (rc) + pr_err("%s: pm8058_write FAIL: rc=%d\n", __func__, rc); + + return rc; +} + +static inline int pm8058_tm_write_pwm(struct pm8058_chip *chip, u8 reg) +{ + int rc; + + rc = pm8058_write(chip, SSBI_REG_TEMP_ALRM_PWM, ®, 1); + if (rc) + pr_err("%s: pm8058_write FAIL: rc=%d\n", __func__, rc); + + return rc; +} + +static inline int +pm8058_tm_shutdown_override(struct pm8058_chip *chip, + enum pmic_thermal_override_mode mode) +{ + int rc; + u8 reg; + + rc = pm8058_tm_read_ctrl(chip, ®); + if (rc < 0) + return rc; + + reg &= ~(PM8058_TEMP_OVRD_MASK | PM8058_TEMP_STATUS_MASK); + if (mode == SOFTWARE_OVERRIDE_ENABLED) + reg |= (PM8058_TEMP_OVRD_ST3 | PM8058_TEMP_OVRD_ST2) & + PM8058_TEMP_OVRD_MASK; + + rc = pm8058_tm_write_ctrl(chip, reg); + + return rc; +} + +static int pm8058_tz_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct pm8058_tm_device *tm = thermal->devdata; + DECLARE_COMPLETION_ONSTACK(wait); + struct adc_chan_result adc_result = { + .physical = 0lu, + }; + int rc; + + if (!tm || !temp) + return -EINVAL; + + *temp = tm->temp; + + rc = adc_channel_request_conv(tm->adc_handle, &wait); + if (rc < 0) { + pr_err("%s: adc_channel_request_conv() failed, rc = %d\n", + __func__, rc); + return rc; + } + + wait_for_completion(&wait); + + rc = adc_channel_read_result(tm->adc_handle, &adc_result); + if (rc < 0) { + pr_err("%s: adc_channel_read_result() failed, rc = %d\n", + __func__, rc); + return rc; + } + + *temp = adc_result.physical; + tm->temp = adc_result.physical; + + return 0; +} + +static int pm8058_tz_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct pm8058_tm_device *tm = thermal->devdata; + + if (!tm || !mode) + return -EINVAL; + + *mode = tm->mode; + + return 0; +} + +static int pm8058_tz_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct pm8058_tm_device *tm = thermal->devdata; + + if (!tm) + return -EINVAL; + + if (mode != tm->mode) { + if (mode == THERMAL_DEVICE_ENABLED) + pm8058_tm_shutdown_override(tm->pm_chip, + SOFTWARE_OVERRIDE_ENABLED); + else + pm8058_tm_shutdown_override(tm->pm_chip, + SOFTWARE_OVERRIDE_DISABLED); + } + tm->mode = mode; + + return 0; +} + +static int pm8058_tz_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + struct pm8058_tm_device *tm = thermal->devdata; + + if (!tm || trip < 0 || !type) + return -EINVAL; + + switch (trip) { + case PM8058_TRIP_STAGE3: + *type = THERMAL_TRIP_CRITICAL; + break; + case PM8058_TRIP_STAGE2: + *type = THERMAL_TRIP_HOT; + break; + case PM8058_TRIP_STAGE1: + *type = THERMAL_TRIP_HOT; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pm8058_tz_get_trip_temp(struct thermal_zone_device *thermal, + int trip, unsigned long *temp) +{ + struct pm8058_tm_device *tm = thermal->devdata; + int thresh_temp; + + if (!tm || trip < 0 || !temp) + return -EINVAL; + + thresh_temp = tm->thresh * PM8058_TEMP_THRESH_STEP + + PM8058_TEMP_THRESH_MIN; + + switch (trip) { + case PM8058_TRIP_STAGE3: + thresh_temp += 2 * PM8058_TEMP_STAGE_STEP; + break; + case PM8058_TRIP_STAGE2: + thresh_temp += PM8058_TEMP_STAGE_STEP; + break; + case PM8058_TRIP_STAGE1: + break; + default: + return -EINVAL; + } + + *temp = thresh_temp; + + return 0; +} + +static int pm8058_tz_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct pm8058_tm_device *tm = thermal->devdata; + + if (!tm || !temp) + return -EINVAL; + + *temp = tm->thresh * PM8058_TEMP_THRESH_STEP + PM8058_TEMP_THRESH_MIN + + 2 * PM8058_TEMP_STAGE_STEP; + + return 0; +} + +static struct thermal_zone_device_ops pm8058_thermal_zone_ops = { + .get_temp = pm8058_tz_get_temp, + .get_mode = pm8058_tz_get_mode, + .set_mode = pm8058_tz_set_mode, + .get_trip_type = pm8058_tz_get_trip_type, + .get_trip_temp = pm8058_tz_get_trip_temp, + .get_crit_temp = pm8058_tz_get_crit_temp, +}; + +static irqreturn_t pm8058_tm_isr(int irq, void *data) +{ + struct pm8058_tm_device *tm = data; + int rc; + u8 reg; + + rc = pm8058_tm_read_ctrl(tm->pm_chip, ®); + if (rc < 0) + goto isr_handled; + + tm->stage = (reg & PM8058_TEMP_STATUS_MASK) >> PM8058_TEMP_STATUS_SHIFT; + tm->thresh = (reg & PM8058_TEMP_THRESH_MASK) >> + PM8058_TEMP_THRESH_SHIFT; + + if (reg & (PM8058_TEMP_ST2_SD | PM8058_TEMP_ST3_SD)) { + reg &= ~(PM8058_TEMP_ST2_SD | PM8058_TEMP_ST3_SD | + PM8058_TEMP_STATUS_MASK); + pm8058_tm_write_ctrl(tm->pm_chip, reg); + } + + thermal_zone_device_update(tm->tz_dev); + + /* Notify user space */ + if (tm->mode == THERMAL_DEVICE_ENABLED) + kobject_uevent(&tm->tz_dev->device.kobj, KOBJ_CHANGE); + +isr_handled: + return IRQ_HANDLED; +} + +static int pm8058_tm_init_reg(struct pm8058_tm_device *tm) +{ + int rc; + u8 reg; + + rc = pm8058_tm_read_ctrl(tm->pm_chip, ®); + if (rc < 0) + return rc; + + tm->stage = (reg & PM8058_TEMP_STATUS_MASK) >> PM8058_TEMP_STATUS_SHIFT; + tm->temp = 0; + + /* Use temperature threshold set 0: (105, 125, 145) */ + tm->thresh = 0; + reg = (tm->thresh << PM8058_TEMP_THRESH_SHIFT) & + PM8058_TEMP_THRESH_MASK; + rc = pm8058_tm_write_ctrl(tm->pm_chip, reg); + if (rc < 0) + return rc; + + /* + * Set the PMIC alarm module PWM to have a frequency of 8 Hz. This + * helps cut down on the number of unnecessary interrupts fired when + * changing between thermal stages. Also, Enable the over temperature + * PWM whenever the PMIC is enabled. + */ + reg = 1 << PM8058_TEMP_PWM_EN_SHIFT | + 3 << PM8058_TEMP_PWM_PER_PRE_SHIFT | + 3 << PM8058_TEMP_PWM_PER_DIV_SHIFT; + + rc = pm8058_tm_write_pwm(tm->pm_chip, reg); + + return rc; +} + +static int __devinit pmic8058_tm_probe(struct platform_device *pdev) +{ + DECLARE_COMPLETION_ONSTACK(wait); + struct pm8058_tm_device *tmdev; + struct pm8058_chip *pm_chip; + unsigned int irq; + int rc; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + pr_err("%s: no driver data passed in.\n", __func__); + return -EFAULT; + } + + irq = platform_get_irq(pdev, 0); + if (!irq) { + pr_err("%s: no IRQ passed in.\n", __func__); + return -EFAULT; + } + + tmdev = kzalloc(sizeof *tmdev, GFP_KERNEL); + if (tmdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + rc = adc_channel_open(PM8058_TEMP_ADC_CH, &(tmdev->adc_handle)); + if (rc < 0) { + pr_err("%s: adc_channel_open() failed.\n", __func__); + kfree(tmdev); + return rc; + } + + /* calibrate the die temperature sensor */ + if (adc_calib_request(tmdev->adc_handle, &wait) == CALIB_STARTED) + wait_for_completion(&wait); + + tmdev->pm_chip = pm_chip; + tmdev->tz_dev = thermal_zone_device_register("pm8058_tz", + PM8058_TRIP_NUM, tmdev, + &pm8058_thermal_zone_ops, + 0, 0, 0, 0); + if (tmdev->tz_dev == NULL) { + pr_err("%s: thermal_zone_device_register() failed.\n", + __func__); + adc_channel_close(tmdev->adc_handle); + kfree(tmdev); + return -ENODEV; + } + + rc = pm8058_tm_init_reg(tmdev); + pm8058_tm_shutdown_override(tmdev->pm_chip, SOFTWARE_OVERRIDE_DISABLED); + if (rc < 0) { + thermal_zone_device_unregister(tmdev->tz_dev); + adc_channel_close(tmdev->adc_handle); + kfree(tmdev); + return rc; + } + + /* start in HW control, switch to SW control when user changes mode */ + tmdev->mode = THERMAL_DEVICE_DISABLED; + thermal_zone_device_update(tmdev->tz_dev); + + platform_set_drvdata(pdev, tmdev); + + rc = request_threaded_irq(irq, NULL, pm8058_tm_isr, + IRQF_TRIGGER_RISING | IRQF_DISABLED, + "pm8058-tm-irq", tmdev); + if (rc < 0) { + pr_err("%s: request_irq(%d) FAIL: %d\n", __func__, irq, rc); + thermal_zone_device_unregister(tmdev->tz_dev); + platform_set_drvdata(pdev, tmdev->pm_chip); + adc_channel_close(tmdev->adc_handle); + kfree(tmdev); + return rc; + } + tmdev->irq = irq; + + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit pmic8058_tm_remove(struct platform_device *pdev) +{ + struct pm8058_tm_device *tmdev = platform_get_drvdata(pdev); + + thermal_zone_device_unregister(tmdev->tz_dev); + platform_set_drvdata(pdev, tmdev->pm_chip); + pm8058_tm_shutdown_override(tmdev->pm_chip, THERMAL_DEVICE_DISABLED); + adc_channel_close(tmdev->adc_handle); + free_irq(tmdev->irq, tmdev); + kfree(tmdev); + + return 0; +} + +#ifdef CONFIG_PM +static int pmic8058_tm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm8058_tm_device *tm = platform_get_drvdata(pdev); + + /* Clear override bits in suspend to allow hardware control */ + pm8058_tm_shutdown_override(tm->pm_chip, SOFTWARE_OVERRIDE_DISABLED); + + return 0; +} + +static int pmic8058_tm_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm8058_tm_device *tm = platform_get_drvdata(pdev); + + /* Override hardware actions so software can control */ + if (tm->mode == THERMAL_DEVICE_ENABLED) + pm8058_tm_shutdown_override(tm->pm_chip, + SOFTWARE_OVERRIDE_ENABLED); + + return 0; +} + +static const struct dev_pm_ops pmic8058_tm_pm_ops = { + .suspend = pmic8058_tm_suspend, + .resume = pmic8058_tm_resume, +}; + +#define PM8058_TM_PM_OPS (&pmic8058_tm_pm_ops) +#else +#define PM8058_TM_PM_OPS NULL +#endif + +static struct platform_driver pmic8058_tm_driver = { + .probe = pmic8058_tm_probe, + .remove = __devexit_p(pmic8058_tm_remove), + .driver = { + .name = "pm8058-tm", + .owner = THIS_MODULE, + .pm = PM8058_TM_PM_OPS, + }, +}; + +static int __init pm8058_tm_init(void) +{ + return platform_driver_register(&pmic8058_tm_driver); +} + +static void __exit pm8058_tm_exit(void) +{ + platform_driver_unregister(&pmic8058_tm_driver); +} + +module_init(pm8058_tm_init); +module_exit(pm8058_tm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8058 Thermal Manager driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8058-tm"); diff --git a/drivers/thermal/pmic8901-tm.c b/drivers/thermal/pmic8901-tm.c new file mode 100644 index 00000000000..0ff57885d00 --- /dev/null +++ b/drivers/thermal/pmic8901-tm.c @@ -0,0 +1,594 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8901 Thermal Manager driver + * + */ + +#include +#include +#include +#include +#include +#include +#include + +/* PMIC8901 TEMP_ALRM registers */ +#define SSBI_REG_TEMP_ALRM_CTRL 0x23 +#define SSBI_REG_TEMP_ALRM_PWM 0x24 + +/* TEMP_ALRM_CTRL */ +#define PM8901_TEMP_ST3_SD 0x80 +#define PM8901_TEMP_ST2_SD 0x40 +#define PM8901_TEMP_STATUS_MASK 0x30 +#define PM8901_TEMP_STATUS_SHIFT 4 +#define PM8901_TEMP_THRESH_MASK 0x0C +#define PM8901_TEMP_THRESH_SHIFT 2 +#define PM8901_TEMP_OVRD_ST3 0x02 +#define PM8901_TEMP_OVRD_ST2 0x01 +#define PM8901_TEMP_OVRD_MASK 0x03 + +#define PM8901_TEMP_STAGE_STEP 20000 /* Stage step: 20 C */ +#define PM8901_TEMP_STAGE_HYSTERESIS 2000 + +#define PM8901_TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */ +#define PM8901_TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */ + +/* TEMP_ALRM_PWM */ +#define PM8901_TEMP_PWM_EN_MASK 0xC0 +#define PM8901_TEMP_PWM_EN_SHIFT 6 +#define PM8901_TEMP_PWM_PER_PRE_MASK 0x38 +#define PM8901_TEMP_PWM_PER_PRE_SHIFT 3 +#define PM8901_TEMP_PWM_PER_DIV_MASK 0x07 +#define PM8901_TEMP_PWM_PER_DIV_SHIFT 0 + +/* Trips: from critical to less critical */ +#define PM8901_TRIP_STAGE3 0 +#define PM8901_TRIP_STAGE2 1 +#define PM8901_TRIP_STAGE1 2 +#define PM8901_TRIP_NUM 3 + +/* Used because there is no means to read the die temperature */ +#define DEFAULT_NO_ADC_TEMP 37000 + +struct pm8901_tm_device { + struct pm8901_chip *pm_chip; + struct thermal_zone_device *tz_dev; + unsigned long temp; + enum thermal_device_mode mode; + unsigned int thresh; + unsigned int stage; + unsigned int irq; + unsigned int hi_irq; +}; + +enum pmic_thermal_override_mode { + SOFTWARE_OVERRIDE_DISABLED = 0, + SOFTWARE_OVERRIDE_ENABLED, +}; + +static inline int pm8901_tm_read_ctrl(struct pm8901_chip *chip, u8 *reg) +{ + int rc; + + rc = pm8901_read(chip, SSBI_REG_TEMP_ALRM_CTRL, reg, 1); + if (rc) + pr_err("%s: pm8901_read FAIL: rc=%d\n", __func__, rc); + + return rc; +} + +static inline int pm8901_tm_write_ctrl(struct pm8901_chip *chip, u8 reg) +{ + int rc; + + rc = pm8901_write(chip, SSBI_REG_TEMP_ALRM_CTRL, ®, 1); + if (rc) + pr_err("%s: pm8901_write FAIL: rc=%d\n", __func__, rc); + + return rc; +} + +static inline int pm8901_tm_read_pwm(struct pm8901_chip *chip, u8 *reg) +{ + int rc; + + rc = pm8901_read(chip, SSBI_REG_TEMP_ALRM_PWM, reg, 1); + if (rc) + pr_err("%s: pm8901_read FAIL: rc=%d\n", __func__, rc); + + return rc; +} + +static inline int pm8901_tm_write_pwm(struct pm8901_chip *chip, u8 reg) +{ + int rc; + + rc = pm8901_write(chip, SSBI_REG_TEMP_ALRM_PWM, ®, 1); + if (rc) + pr_err("%s: pm8901_write FAIL: rc=%d\n", __func__, rc); + + return rc; +} + +static inline int +pm8901_tm_shutdown_override(struct pm8901_chip *chip, + enum pmic_thermal_override_mode mode) +{ + int rc; + u8 reg; + + rc = pm8901_tm_read_ctrl(chip, ®); + if (rc < 0) + return rc; + + reg &= ~(PM8901_TEMP_OVRD_MASK | PM8901_TEMP_STATUS_MASK); + if (mode == SOFTWARE_OVERRIDE_ENABLED) + reg |= (PM8901_TEMP_OVRD_ST3 | PM8901_TEMP_OVRD_ST2) & + PM8901_TEMP_OVRD_MASK; + + rc = pm8901_tm_write_ctrl(chip, reg); + + return rc; +} + +/* + * This function initializes the internal temperature value based on only the + * current thermal stage and threshold. + */ +static int pm8901_tm_init_temp(struct pm8901_tm_device *tm) +{ + int rc; + u8 reg; + + rc = pm8901_tm_read_ctrl(tm->pm_chip, ®); + if (rc < 0) + return rc; + + tm->stage = (reg & PM8901_TEMP_STATUS_MASK) >> PM8901_TEMP_STATUS_SHIFT; + tm->thresh = (reg & PM8901_TEMP_THRESH_MASK) >> + PM8901_TEMP_THRESH_SHIFT; + + if (tm->stage) { + tm->temp = tm->thresh * PM8901_TEMP_THRESH_STEP + + (tm->stage - 1) * PM8901_TEMP_STAGE_STEP + + PM8901_TEMP_THRESH_MIN; + } else + tm->temp = DEFAULT_NO_ADC_TEMP; + + return 0; +} + +/* + * This function updates the internal temperature value based on the + * current thermal stage and threshold as well as the previous stage + */ +static int pm8901_tm_update_temp(struct pm8901_tm_device *tm) +{ + unsigned int stage; + int rc; + u8 reg; + + rc = pm8901_tm_read_ctrl(tm->pm_chip, ®); + if (rc < 0) + return rc; + + stage = (reg & PM8901_TEMP_STATUS_MASK) >> PM8901_TEMP_STATUS_SHIFT; + tm->thresh = (reg & PM8901_TEMP_THRESH_MASK) >> + PM8901_TEMP_THRESH_SHIFT; + + if (stage > tm->stage) { + /* increasing stage, use lower bound */ + tm->temp = (stage-1) * PM8901_TEMP_STAGE_STEP + + tm->thresh * PM8901_TEMP_THRESH_STEP + + PM8901_TEMP_STAGE_HYSTERESIS + + PM8901_TEMP_THRESH_MIN; + } else if (stage < tm->stage) { + /* decreasing stage, use upper bound */ + tm->temp = stage * PM8901_TEMP_STAGE_STEP + + tm->thresh * PM8901_TEMP_THRESH_STEP - + PM8901_TEMP_STAGE_HYSTERESIS + + PM8901_TEMP_THRESH_MIN; + } + + tm->stage = stage; + + return 0; +} + +static int pm8901_tz_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct pm8901_tm_device *tm = thermal->devdata; + int rc; + + if (!tm || !temp) + return -EINVAL; + + rc = pm8901_tm_update_temp(tm); + if (rc < 0) + return rc; + + *temp = tm->temp; + + return 0; +} + +static int pm8901_tz_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct pm8901_tm_device *tm = thermal->devdata; + + if (!tm || !mode) + return -EINVAL; + + *mode = tm->mode; + + return 0; +} + +static int pm8901_tz_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct pm8901_tm_device *tm = thermal->devdata; + + if (!tm) + return -EINVAL; + + if (mode != tm->mode) { + pr_info("%s: mode: %d --> %d\n", __func__, tm->mode, mode); + + if (mode == THERMAL_DEVICE_ENABLED) + pm8901_tm_shutdown_override(tm->pm_chip, + SOFTWARE_OVERRIDE_ENABLED); + else + pm8901_tm_shutdown_override(tm->pm_chip, + SOFTWARE_OVERRIDE_DISABLED); + } + tm->mode = mode; + + return 0; +} + +static int pm8901_tz_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + struct pm8901_tm_device *tm = thermal->devdata; + + if (!tm || trip < 0 || !type) + return -EINVAL; + + switch (trip) { + case PM8901_TRIP_STAGE3: + *type = THERMAL_TRIP_CRITICAL; + break; + case PM8901_TRIP_STAGE2: + *type = THERMAL_TRIP_HOT; + break; + case PM8901_TRIP_STAGE1: + *type = THERMAL_TRIP_HOT; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pm8901_tz_get_trip_temp(struct thermal_zone_device *thermal, + int trip, unsigned long *temp) +{ + struct pm8901_tm_device *tm = thermal->devdata; + int thresh_temp; + + if (!tm || trip < 0 || !temp) + return -EINVAL; + + thresh_temp = tm->thresh * PM8901_TEMP_THRESH_STEP + + PM8901_TEMP_THRESH_MIN; + + switch (trip) { + case PM8901_TRIP_STAGE3: + thresh_temp += 2 * PM8901_TEMP_STAGE_STEP; + break; + case PM8901_TRIP_STAGE2: + thresh_temp += PM8901_TEMP_STAGE_STEP; + break; + case PM8901_TRIP_STAGE1: + break; + default: + return -EINVAL; + } + *temp = thresh_temp; + + return 0; +} + +static int pm8901_tz_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct pm8901_tm_device *tm = thermal->devdata; + + if (!tm || !temp) + return -EINVAL; + + *temp = tm->thresh * PM8901_TEMP_THRESH_STEP + + PM8901_TEMP_THRESH_MIN + 2 * PM8901_TEMP_STAGE_STEP; + + return 0; +} + +static struct thermal_zone_device_ops pm8901_thermal_zone_ops = { + .get_temp = pm8901_tz_get_temp, + .get_mode = pm8901_tz_get_mode, + .set_mode = pm8901_tz_set_mode, + .get_trip_type = pm8901_tz_get_trip_type, + .get_trip_temp = pm8901_tz_get_trip_temp, + .get_crit_temp = pm8901_tz_get_crit_temp, +}; + +static irqreturn_t pm8901_tm_isr(int irq, void *data) +{ + struct pm8901_tm_device *tm = data; + int rc; + u8 reg; + + rc = pm8901_tm_update_temp(tm); + if (rc < 0) + goto isr_handled; + + rc = pm8901_tm_read_ctrl(tm->pm_chip, ®); + if (rc < 0) + goto isr_handled; + + pr_info("%s: Temp Alarm - stage=%u, threshold=%u, temp=%lu\n", + __func__, tm->stage, tm->thresh, tm->temp); + + if (reg & (PM8901_TEMP_ST2_SD | PM8901_TEMP_ST3_SD)) { + reg &= ~(PM8901_TEMP_ST2_SD | PM8901_TEMP_ST3_SD | + PM8901_TEMP_STATUS_MASK); + + pm8901_tm_write_ctrl(tm->pm_chip, reg); + } + + thermal_zone_device_update(tm->tz_dev); + + /* Notify user space */ + if (tm->mode == THERMAL_DEVICE_ENABLED) + kobject_uevent(&tm->tz_dev->device.kobj, KOBJ_CHANGE); + +isr_handled: + return IRQ_HANDLED; +} + +static irqreturn_t pm8901_tm_isr1(int irq, void *data) +{ + struct pm8901_tm_device *tm = data; + irqreturn_t rc; + + disable_irq(tm->hi_irq); + rc = pm8901_tm_isr(irq, data); + enable_irq(tm->hi_irq); + + return rc; +} + +static irqreturn_t pm8901_tm_isr2(int irq, void *data) +{ + struct pm8901_tm_device *tm = data; + irqreturn_t rc; + + disable_irq(tm->irq); + rc = pm8901_tm_isr(irq, data); + enable_irq(tm->irq); + + return rc; +} + +static int pm8901_tm_init_reg(struct pm8901_tm_device *tm) +{ + int rc; + u8 reg; + + rc = pm8901_tm_init_temp(tm); + if (rc < 0) + return rc; + + /* Use temperature threshold set 0: (105, 125, 145) */ + tm->thresh = 0; + reg = (tm->thresh << PM8901_TEMP_THRESH_SHIFT) & + PM8901_TEMP_THRESH_MASK; + rc = pm8901_tm_write_ctrl(tm->pm_chip, reg); + if (rc < 0) + return rc; + + /* + * Set the PMIC alarm module PWM to have a frequency of 8 Hz. This + * helps cut down on the number of unnecessary interrupts fired when + * changing between thermal stages. Also, Enable the over temperature + * PWM whenever the PMIC is enabled. + */ + reg = 1 << PM8901_TEMP_PWM_EN_SHIFT | + 3 << PM8901_TEMP_PWM_PER_PRE_SHIFT | + 3 << PM8901_TEMP_PWM_PER_DIV_SHIFT; + + rc = pm8901_tm_write_pwm(tm->pm_chip, reg); + + return rc; +} + +static int __devinit pmic8901_tm_probe(struct platform_device *pdev) +{ + struct pm8901_tm_device *tmdev; + struct pm8901_chip *pm_chip; + unsigned int irq, hi_irq; + int rc; + + pm_chip = dev_get_drvdata(pdev->dev.parent); + if (pm_chip == NULL) { + pr_err("%s: no driver data passed in.\n", __func__); + return -EFAULT; + } + + irq = platform_get_irq(pdev, 0); + if (!irq) { + pr_err("%s: no IRQ passed in.\n", __func__); + return -EFAULT; + } + hi_irq = platform_get_irq(pdev, 1); + if (!hi_irq) { + pr_err("%s: no HI IRQ passed in.\n", __func__); + return -EFAULT; + } + + tmdev = kzalloc(sizeof *tmdev, GFP_KERNEL); + if (tmdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + tmdev->pm_chip = pm_chip; + tmdev->tz_dev = thermal_zone_device_register("pm8901_tz", + PM8901_TRIP_NUM, tmdev, + &pm8901_thermal_zone_ops, + 0, 0, 0, 0); + if (tmdev->tz_dev == NULL) { + pr_err("%s: thermal_zone_device_register() failed.\n", + __func__); + kfree(tmdev); + return -ENODEV; + } + + rc = pm8901_tm_init_reg(tmdev); + pm8901_tm_shutdown_override(tmdev->pm_chip, SOFTWARE_OVERRIDE_DISABLED); + if (rc < 0) { + thermal_zone_device_unregister(tmdev->tz_dev); + kfree(tmdev); + return rc; + } + + /* start in HW control, switch to SW control when user changes mode */ + tmdev->mode = THERMAL_DEVICE_DISABLED; + thermal_zone_device_update(tmdev->tz_dev); + + platform_set_drvdata(pdev, tmdev); + + rc = request_threaded_irq(irq, pm8901_tm_isr1, NULL, + IRQF_TRIGGER_RISING | IRQF_DISABLED, + "pm8901-tm-irq", tmdev); + if (rc < 0) { + pr_err("%s: request_threaded_irq(%d) FAIL: %d\n", + __func__, irq, rc); + + thermal_zone_device_unregister(tmdev->tz_dev); + platform_set_drvdata(pdev, tmdev->pm_chip); + kfree(tmdev); + return -ENODEV; + } + tmdev->irq = irq; + + rc = request_threaded_irq(hi_irq, pm8901_tm_isr2, NULL, + IRQF_TRIGGER_RISING | IRQF_DISABLED, + "pm8901-tm-irq2", tmdev); + if (rc < 0) { + pr_err("%s: request_threaded_irq(%d) FAIL: %d\n", + __func__, hi_irq, rc); + + free_irq(irq, tmdev); + thermal_zone_device_unregister(tmdev->tz_dev); + platform_set_drvdata(pdev, tmdev->pm_chip); + kfree(tmdev); + return -ENODEV; + } + tmdev->hi_irq = hi_irq; + + pr_notice("%s: OK\n", __func__); + return 0; +} + +static int __devexit pmic8901_tm_remove(struct platform_device *pdev) +{ + struct pm8901_tm_device *tmdev = platform_get_drvdata(pdev); + + free_irq(tmdev->hi_irq, tmdev); + free_irq(tmdev->irq, tmdev); + thermal_zone_device_unregister(tmdev->tz_dev); + platform_set_drvdata(pdev, tmdev->pm_chip); + pm8901_tm_shutdown_override(tmdev->pm_chip, SOFTWARE_OVERRIDE_DISABLED); + kfree(tmdev); + + return 0; +} + +#ifdef CONFIG_PM +static int pmic8901_tm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm8901_tm_device *tm = platform_get_drvdata(pdev); + + pm8901_tm_shutdown_override(tm->pm_chip, SOFTWARE_OVERRIDE_DISABLED); + + return 0; +} + +static int pmic8901_tm_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm8901_tm_device *tm = platform_get_drvdata(pdev); + + pm8901_tm_init_temp(tm); + + if (tm->mode == THERMAL_DEVICE_ENABLED) + pm8901_tm_shutdown_override(tm->pm_chip, + SOFTWARE_OVERRIDE_ENABLED); + + return 0; +} + +static const struct dev_pm_ops pmic8901_tm_pm_ops = { + .suspend = pmic8901_tm_suspend, + .resume = pmic8901_tm_resume, +}; + +#define PM8901_TM_PM_OPS (&pmic8901_tm_pm_ops) +#else +#define PM8901_TM_PM_OPS NULL +#endif + +static struct platform_driver pmic8901_tm_driver = { + .probe = pmic8901_tm_probe, + .remove = __devexit_p(pmic8901_tm_remove), + .driver = { + .name = "pm8901-tm", + .owner = THIS_MODULE, + .pm = PM8901_TM_PM_OPS, + }, +}; + +static int __init pm8901_tm_init(void) +{ + return platform_driver_register(&pmic8901_tm_driver); +} + +static void __exit pm8901_tm_exit(void) +{ + platform_driver_unregister(&pmic8901_tm_driver); +} + +module_init(pm8901_tm_init); +module_exit(pm8901_tm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PMIC8901 Thermal Manager driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:pmic8901-tm"); diff --git a/drivers/thermal/thermal_sys.c b/drivers/thermal/thermal_sys.c index 0b1c82ad680..e0d8ef79e51 100644 --- a/drivers/thermal/thermal_sys.c +++ b/drivers/thermal/thermal_sys.c @@ -188,6 +188,12 @@ trip_point_type_show(struct device *dev, struct device_attribute *attr, return sprintf(buf, "critical\n"); case THERMAL_TRIP_HOT: return sprintf(buf, "hot\n"); + case THERMAL_TRIP_CONFIGURABLE_HI: + return sprintf(buf, "configurable_hi\n"); + case THERMAL_TRIP_CONFIGURABLE_LOW: + return sprintf(buf, "configurable_low\n"); + case THERMAL_TRIP_CRITICAL_LOW: + return sprintf(buf, "critical_low\n"); case THERMAL_TRIP_PASSIVE: return sprintf(buf, "passive\n"); case THERMAL_TRIP_ACTIVE: @@ -197,6 +203,34 @@ trip_point_type_show(struct device *dev, struct device_attribute *attr, } } +static ssize_t +trip_point_type_activate(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + int trip, result; + + if (!tz->ops->activate_trip_type) + return -EPERM; + + if (!sscanf(attr->attr.name, "trip_point_%d_type", &trip)) + return -EINVAL; + + if (!strncmp(buf, "enabled", sizeof("enabled"))) + result = tz->ops->activate_trip_type(tz, trip, + THERMAL_TRIP_ACTIVATION_ENABLED); + else if (!strncmp(buf, "disabled", sizeof("disabled"))) + result = tz->ops->activate_trip_type(tz, trip, + THERMAL_TRIP_ACTIVATION_DISABLED); + else + result = -EINVAL; + + if (result) + return result; + + return count; +} + static ssize_t trip_point_temp_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -219,6 +253,30 @@ trip_point_temp_show(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%ld\n", temperature); } +static ssize_t +trip_point_temp_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct thermal_zone_device *tz = to_thermal_zone(dev); + int trip, ret; + long temperature; + + if (!tz->ops->set_trip_temp) + return -EPERM; + + if (!sscanf(attr->attr.name, "trip_point_%d_temp", &trip)) + return -EINVAL; + + if (!sscanf(buf, "%ld", &temperature)) + return -EINVAL; + + ret = tz->ops->set_trip_temp(tz, trip, temperature); + if (ret) + return ret; + + return count; +} + static ssize_t passive_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -287,30 +345,54 @@ static DEVICE_ATTR(passive, S_IRUGO | S_IWUSR, passive_show, \ passive_store); static struct device_attribute trip_point_attrs[] = { - __ATTR(trip_point_0_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_0_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_1_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_1_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_2_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_2_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_3_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_3_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_4_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_4_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_5_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_5_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_6_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_6_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_7_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_7_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_8_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_8_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_9_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_9_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_10_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_10_temp, 0444, trip_point_temp_show, NULL), - __ATTR(trip_point_11_type, 0444, trip_point_type_show, NULL), - __ATTR(trip_point_11_temp, 0444, trip_point_temp_show, NULL), + __ATTR(trip_point_0_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_0_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_1_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_1_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_2_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_2_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_3_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_3_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_4_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_4_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_5_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_5_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_6_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_6_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_7_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_7_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_8_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_8_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_9_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_9_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_10_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_10_temp, 0644, trip_point_temp_show, + trip_point_temp_set), + __ATTR(trip_point_11_type, 0644, trip_point_type_show, + trip_point_type_activate), + __ATTR(trip_point_11_temp, 0644, trip_point_temp_show, + trip_point_temp_set), }; #define TRIP_POINT_ATTR_ADD(_dev, _index, result) \ @@ -992,6 +1074,29 @@ void thermal_zone_device_update(struct thermal_zone_device *tz) if (tz->ops->notify) tz->ops->notify(tz, count, trip_type); break; + case THERMAL_TRIP_CONFIGURABLE_HI: + if (temp >= trip_temp) + if (tz->ops->notify) + tz->ops->notify(tz, count, trip_type); + break; + case THERMAL_TRIP_CONFIGURABLE_LOW: + if (temp <= trip_temp) + if (tz->ops->notify) + tz->ops->notify(tz, count, trip_type); + break; + case THERMAL_TRIP_CRITICAL_LOW: + if (temp <= trip_temp) { + if (tz->ops->notify) + ret = tz->ops->notify(tz, count, + trip_type); + if (!ret) { + printk(KERN_EMERG + "Critical temperature reached (%ld C), \ + shutting down.\n", temp/1000); + orderly_poweroff(true); + } + } + break; case THERMAL_TRIP_ACTIVE: list_for_each_entry(instance, &tz->cooling_devices, node) { diff --git a/drivers/tty/hvc/hvc_dcc.c b/drivers/tty/hvc/hvc_dcc.c index 435f6facbc2..7ff13123fc2 100644 --- a/drivers/tty/hvc/hvc_dcc.c +++ b/drivers/tty/hvc/hvc_dcc.c @@ -8,11 +8,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #include diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index b3692e6e3c1..852cff594f8 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1308,6 +1308,44 @@ config SERIAL_MSM_HS Choose M here to compile it as a module. The module will be called msm_serial_hs. +config SERIAL_MSM_CLOCK_CONTROL + bool "Allow tty clients to make clock requests to msm uarts." + depends on SERIAL_MSM=y + default y + help + Provides an interface for tty clients to request the msm uart clock + to be turned on or off for power savings. + +config SERIAL_MSM_RX_WAKEUP + bool "Wakeup the msm uart clock on GPIO activity." + depends on SERIAL_MSM_CLOCK_CONTROL + default n + help + Requires SERIAL_MSM_CLOCK_CONTROL. Wake up the uart while the uart + clock is off, using a wakeup GPIO. + +config SERIAL_MSM_HSL + tristate "MSM UART High Speed : Legacy mode Serial Driver" + depends on ARM && ARCH_MSM + select SERIAL_CORE + default n + help + Select this module to enable MSM high speed UART driver. + +config SERIAL_MSM_HSL_CONSOLE + bool "MSM High speed serial legacy mode console support" + depends on SERIAL_MSM_HSL=y + select SERIAL_CORE_CONSOLE + default n + +config SERIAL_BCM_BT_LPM + tristate "Broadcom Bluetooth Low Power Mode" + depends on ARM && ARCH_MSM + select SERIAL_CORE + default n + help + Select this module for Broadcom Bluetooth low power management. + config SERIAL_VT8500 bool "VIA VT8500 on-chip serial port support" depends on ARM && ARCH_VT8500 diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index cb2628fee4c..122c9928b9c 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -76,6 +76,8 @@ obj-$(CONFIG_SERIAL_ATMEL) += atmel_serial.o obj-$(CONFIG_SERIAL_UARTLITE) += uartlite.o obj-$(CONFIG_SERIAL_MSM) += msm_serial.o obj-$(CONFIG_SERIAL_MSM_HS) += msm_serial_hs.o +obj-$(CONFIG_SERIAL_MSM_HSL) += msm_serial_hs_lite.o +obj-$(CONFIG_MSM_SERIAL_DEBUGGER) += msm_serial_debugger.o obj-$(CONFIG_SERIAL_NETX) += netx-serial.o obj-$(CONFIG_SERIAL_OF_PLATFORM) += of_serial.o obj-$(CONFIG_SERIAL_OF_PLATFORM_NWPSERIAL) += nwpserial.o diff --git a/drivers/tty/serial/msm_serial.c b/drivers/tty/serial/msm_serial.c index e6ba8387650..e6646ab4879 100644 --- a/drivers/tty/serial/msm_serial.c +++ b/drivers/tty/serial/msm_serial.c @@ -1,9 +1,9 @@ /* - * Driver for msm7k serial device and console + * drivers/serial/msm_serial.c - driver for msm7k serial device and console * * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. * Author: Robert Love - * Copyright (c) 2011, Code Aurora Forum. 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 @@ -25,121 +25,242 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include -#include - +#include +#include #include "msm_serial.h" + +#ifdef CONFIG_SERIAL_MSM_CLOCK_CONTROL +enum msm_clk_states_e { + MSM_CLK_PORT_OFF, /* uart port not in use */ + MSM_CLK_OFF, /* clock enabled */ + MSM_CLK_REQUEST_OFF, /* disable after TX flushed */ + MSM_CLK_ON, /* clock disabled */ +}; +#endif + +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP +/* optional low power wakeup, typically on a GPIO RX irq */ +struct msm_wakeup { + int irq; /* < 0 indicates low power wakeup disabled */ + unsigned char ignore; /* bool */ + + /* bool: inject char into rx tty on wakeup */ + unsigned char inject_rx; + char rx_to_inject; +}; +#endif + struct msm_port { struct uart_port uart; char name[16]; struct clk *clk; - struct clk *pclk; unsigned int imr; - unsigned int *gsbi_base; - int is_uartdm; - unsigned int old_snap_state; +#ifdef CONFIG_SERIAL_MSM_CLOCK_CONTROL + enum msm_clk_states_e clk_state; + struct hrtimer clk_off_timer; + ktime_t clk_off_delay; +#endif +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP + struct msm_wakeup wakeup; +#endif }; -static inline void wait_for_xmitr(struct uart_port *port, int bits) +#define UART_TO_MSM(uart_port) ((struct msm_port *) uart_port) +#define is_console(port) ((port)->cons && \ + (port)->cons->index == (port)->line) + + +static inline void msm_write(struct uart_port *port, unsigned int val, + unsigned int off) +{ + __raw_writel(val, port->membase + off); +} + +static inline unsigned int msm_read(struct uart_port *port, unsigned int off) { - if (!(msm_read(port, UART_SR) & UART_SR_TX_EMPTY)) - while ((msm_read(port, UART_ISR) & bits) != bits) - cpu_relax(); + return __raw_readl(port->membase + off); } +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP +static inline unsigned int use_low_power_wakeup(struct msm_port *msm_port) +{ + return (msm_port->wakeup.irq >= 0); +} +#endif + static void msm_stop_tx(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); + clk_enable(msm_port->clk); + msm_port->imr &= ~UART_IMR_TXLEV; msm_write(port, msm_port->imr, UART_IMR); + + clk_disable(msm_port->clk); } static void msm_start_tx(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); + clk_enable(msm_port->clk); + msm_port->imr |= UART_IMR_TXLEV; msm_write(port, msm_port->imr, UART_IMR); + + clk_disable(msm_port->clk); } static void msm_stop_rx(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); + clk_enable(msm_port->clk); + msm_port->imr &= ~(UART_IMR_RXLEV | UART_IMR_RXSTALE); msm_write(port, msm_port->imr, UART_IMR); + + clk_disable(msm_port->clk); } static void msm_enable_ms(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); + clk_enable(msm_port->clk); + msm_port->imr |= UART_IMR_DELTA_CTS; msm_write(port, msm_port->imr, UART_IMR); + + clk_disable(msm_port->clk); } -static void handle_rx_dm(struct uart_port *port, unsigned int misr) -{ - struct tty_struct *tty = port->state->port.tty; - unsigned int sr; - int count = 0; +#ifdef CONFIG_SERIAL_MSM_CLOCK_CONTROL +/* turn clock off if TX buffer is empty, otherwise reschedule */ +static enum hrtimer_restart msm_serial_clock_off(struct hrtimer *timer) { + struct msm_port *msm_port = container_of(timer, struct msm_port, + clk_off_timer); + struct uart_port *port = &msm_port->uart; + struct circ_buf *xmit = &port->state->xmit; + unsigned long flags; + int ret = HRTIMER_NORESTART; + + spin_lock_irqsave(&port->lock, flags); + + if (msm_port->clk_state == MSM_CLK_REQUEST_OFF) { + if (uart_circ_empty(xmit)) { + struct msm_port *msm_port = UART_TO_MSM(port); + clk_disable(msm_port->clk); + msm_port->clk_state = MSM_CLK_OFF; +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP + if (use_low_power_wakeup(msm_port)) { + msm_port->wakeup.ignore = 1; + enable_irq(msm_port->wakeup.irq); + } +#endif + } else { + hrtimer_forward_now(timer, msm_port->clk_off_delay); + ret = HRTIMER_RESTART; + } + } + + spin_unlock_irqrestore(&port->lock, flags); + + return HRTIMER_NORESTART; +} + +/* request to turn off uart clock once pending TX is flushed */ +void msm_serial_clock_request_off(struct uart_port *port) { + unsigned long flags; struct msm_port *msm_port = UART_TO_MSM(port); - if ((msm_read(port, UART_SR) & UART_SR_OVERRUN)) { - port->icount.overrun++; - tty_insert_flip_char(tty, 0, TTY_OVERRUN); - msm_write(port, UART_CR_CMD_RESET_ERR, UART_CR); + spin_lock_irqsave(&port->lock, flags); + if (msm_port->clk_state == MSM_CLK_ON) { + msm_port->clk_state = MSM_CLK_REQUEST_OFF; + /* turn off TX later. unfortunately not all msm uart's have a + * TXDONE available, and TXLEV does not wait until completely + * flushed, so a timer is our only option + */ + hrtimer_start(&msm_port->clk_off_timer, + msm_port->clk_off_delay, HRTIMER_MODE_REL); } + spin_unlock_irqrestore(&port->lock, flags); +} - if (misr & UART_IMR_RXSTALE) { - count = msm_read(port, UARTDM_RX_TOTAL_SNAP) - - msm_port->old_snap_state; - msm_port->old_snap_state = 0; - } else { - count = 4 * (msm_read(port, UART_RFWR)); - msm_port->old_snap_state += count; +/* request to immediately turn on uart clock. + * ignored if there is a pending off request, unless force = 1. + */ +void msm_serial_clock_on(struct uart_port *port, int force) { + unsigned long flags; + struct msm_port *msm_port = UART_TO_MSM(port); + + spin_lock_irqsave(&port->lock, flags); + + switch (msm_port->clk_state) { + case MSM_CLK_OFF: + clk_enable(msm_port->clk); +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP + if (use_low_power_wakeup(msm_port)) + disable_irq(msm_port->wakeup.irq); +#endif + force = 1; + case MSM_CLK_REQUEST_OFF: + if (force) { + hrtimer_try_to_cancel(&msm_port->clk_off_timer); + msm_port->clk_state = MSM_CLK_ON; + } + break; + case MSM_CLK_ON: break; + case MSM_CLK_PORT_OFF: break; } - /* TODO: Precise error reporting */ + spin_unlock_irqrestore(&port->lock, flags); +} +#endif - port->icount.rx += count; +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP +static irqreturn_t msm_rx_irq(int irq, void *dev_id) +{ + struct uart_port *port = dev_id; + struct msm_port *msm_port = UART_TO_MSM(port); + int inject_wakeup = 0; - while (count > 0) { - unsigned int c; + spin_lock(&port->lock); - sr = msm_read(port, UART_SR); - if ((sr & UART_SR_RX_READY) == 0) { - msm_port->old_snap_state -= count; - break; - } - c = msm_read(port, UARTDM_RF); - if (sr & UART_SR_RX_BREAK) { - port->icount.brk++; - if (uart_handle_break(port)) - continue; - } else if (sr & UART_SR_PAR_FRAME_ERR) - port->icount.frame++; + if (msm_port->clk_state == MSM_CLK_OFF) { + /* ignore the first irq - it is a pending irq that occured + * before enable_irq() */ + if (msm_port->wakeup.ignore) + msm_port->wakeup.ignore = 0; + else + inject_wakeup = 1; + } - /* TODO: handle sysrq */ - tty_insert_flip_string(tty, (char *) &c, - (count > 4) ? 4 : count); - count -= 4; + msm_serial_clock_on(port, 0); + + /* we missed an rx while asleep - it must be a wakeup indicator + */ + if (inject_wakeup) { + struct tty_struct *tty = port->state->port.tty; + tty_insert_flip_char(tty, WAKE_UP_IND, TTY_NORMAL); + tty_flip_buffer_push(tty); } - tty_flip_buffer_push(tty); - if (misr & (UART_IMR_RXSTALE)) - msm_write(port, UART_CR_CMD_RESET_STALE_INT, UART_CR); - msm_write(port, 0xFFFFFF, UARTDM_DMRX); - msm_write(port, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR); + spin_unlock(&port->lock); + return IRQ_HANDLED; } +#endif static void handle_rx(struct uart_port *port) { @@ -189,12 +310,6 @@ static void handle_rx(struct uart_port *port) tty_flip_buffer_push(tty); } -static void reset_dm_count(struct uart_port *port) -{ - wait_for_xmitr(port, UART_ISR_TX_READY); - msm_write(port, 1, UARTDM_NCF_TX); -} - static void handle_tx(struct uart_port *port) { struct circ_buf *xmit = &port->state->xmit; @@ -202,18 +317,11 @@ static void handle_tx(struct uart_port *port) int sent_tx; if (port->x_char) { - if (msm_port->is_uartdm) - reset_dm_count(port); - - msm_write(port, port->x_char, - msm_port->is_uartdm ? UARTDM_TF : UART_TF); + msm_write(port, port->x_char, UART_TF); port->icount.tx++; port->x_char = 0; } - if (msm_port->is_uartdm) - reset_dm_count(port); - while (msm_read(port, UART_SR) & UART_SR_TX_READY) { if (uart_circ_empty(xmit)) { /* disable tx interrupts */ @@ -221,17 +329,22 @@ static void handle_tx(struct uart_port *port) msm_write(port, msm_port->imr, UART_IMR); break; } - msm_write(port, xmit->buf[xmit->tail], - msm_port->is_uartdm ? UARTDM_TF : UART_TF); - if (msm_port->is_uartdm) - reset_dm_count(port); + msm_write(port, xmit->buf[xmit->tail], UART_TF); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); port->icount.tx++; sent_tx = 1; } +#ifdef CONFIG_SERIAL_MSM_CLOCK_CONTROL + if (sent_tx && msm_port->clk_state == MSM_CLK_REQUEST_OFF) + /* new TX - restart the timer */ + if (hrtimer_try_to_cancel(&msm_port->clk_off_timer) == 1) + hrtimer_start(&msm_port->clk_off_timer, + msm_port->clk_off_delay, HRTIMER_MODE_REL); +#endif + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(port); } @@ -250,21 +363,19 @@ static irqreturn_t msm_irq(int irq, void *dev_id) unsigned int misr; spin_lock(&port->lock); + clk_enable(msm_port->clk); misr = msm_read(port, UART_MISR); msm_write(port, 0, UART_IMR); /* disable interrupt */ - if (misr & (UART_IMR_RXLEV | UART_IMR_RXSTALE)) { - if (msm_port->is_uartdm) - handle_rx_dm(port, misr); - else - handle_rx(port); - } + if (misr & (UART_IMR_RXLEV | UART_IMR_RXSTALE)) + handle_rx(port); if (misr & UART_IMR_TXLEV) handle_tx(port); if (misr & UART_IMR_DELTA_CTS) handle_delta_cts(port); msm_write(port, msm_port->imr, UART_IMR); /* restore interrupt */ + clk_disable(msm_port->clk); spin_unlock(&port->lock); return IRQ_HANDLED; @@ -272,7 +383,14 @@ static irqreturn_t msm_irq(int irq, void *dev_id) static unsigned int msm_tx_empty(struct uart_port *port) { - return (msm_read(port, UART_SR) & UART_SR_TX_EMPTY) ? TIOCSER_TEMT : 0; + unsigned int ret; + struct msm_port *msm_port = UART_TO_MSM(port); + + clk_enable(msm_port->clk); + ret = (msm_read(port, UART_SR) & UART_SR_TX_EMPTY) ? TIOCSER_TEMT : 0; + clk_disable(msm_port->clk); + + return ret; } static unsigned int msm_get_mctrl(struct uart_port *port) @@ -280,21 +398,13 @@ static unsigned int msm_get_mctrl(struct uart_port *port) return TIOCM_CAR | TIOCM_CTS | TIOCM_DSR | TIOCM_RTS; } - -static void msm_reset(struct uart_port *port) -{ - /* reset everything */ - msm_write(port, UART_CR_CMD_RESET_RX, UART_CR); - msm_write(port, UART_CR_CMD_RESET_TX, UART_CR); - msm_write(port, UART_CR_CMD_RESET_ERR, UART_CR); - msm_write(port, UART_CR_CMD_RESET_BREAK_INT, UART_CR); - msm_write(port, UART_CR_CMD_RESET_CTS, UART_CR); - msm_write(port, UART_CR_CMD_SET_RFR, UART_CR); -} - -void msm_set_mctrl(struct uart_port *port, unsigned int mctrl) +static void msm_set_mctrl(struct uart_port *port, unsigned int mctrl) { unsigned int mr; + struct msm_port *msm_port = UART_TO_MSM(port); + + clk_enable(msm_port->clk); + mr = msm_read(port, UART_MR1); if (!(mctrl & TIOCM_RTS)) { @@ -305,20 +415,27 @@ void msm_set_mctrl(struct uart_port *port, unsigned int mctrl) mr |= UART_MR1_RX_RDY_CTL; msm_write(port, mr, UART_MR1); } + + clk_disable(msm_port->clk); } static void msm_break_ctl(struct uart_port *port, int break_ctl) { + struct msm_port *msm_port = UART_TO_MSM(port); + + clk_enable(msm_port->clk); + if (break_ctl) msm_write(port, UART_CR_CMD_START_BREAK, UART_CR); else msm_write(port, UART_CR_CMD_STOP_BREAK, UART_CR); + + clk_disable(msm_port->clk); } -static int msm_set_baud_rate(struct uart_port *port, unsigned int baud) +static void msm_set_baud_rate(struct uart_port *port, unsigned int baud) { unsigned int baud_code, rxstale, watermark; - struct msm_port *msm_port = UART_TO_MSM(port); switch (baud) { case 300: @@ -368,14 +485,10 @@ static int msm_set_baud_rate(struct uart_port *port, unsigned int baud) case 115200: default: baud_code = UART_CSR_115200; - baud = 115200; rxstale = 31; break; } - if (msm_port->is_uartdm) - msm_write(port, UART_CR_CMD_RESET_RX, UART_CR); - msm_write(port, baud_code, UART_CSR); /* RX stale watermark */ @@ -390,27 +503,57 @@ static int msm_set_baud_rate(struct uart_port *port, unsigned int baud) /* set TX watermark */ msm_write(port, 10, UART_TFWR); - - if (msm_port->is_uartdm) { - msm_write(port, UART_CR_CMD_RESET_STALE_INT, UART_CR); - msm_write(port, 0xFFFFFF, UARTDM_DMRX); - msm_write(port, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR); - } - - return baud; } +static void msm_reset(struct uart_port *port) +{ + /* reset everything */ + msm_write(port, UART_CR_CMD_RESET_RX, UART_CR); + msm_write(port, UART_CR_CMD_RESET_TX, UART_CR); + msm_write(port, UART_CR_CMD_RESET_ERR, UART_CR); + msm_write(port, UART_CR_CMD_RESET_BREAK_INT, UART_CR); + msm_write(port, UART_CR_CMD_RESET_CTS, UART_CR); + msm_write(port, UART_CR_CMD_SET_RFR, UART_CR); +} static void msm_init_clock(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); clk_enable(msm_port->clk); - if (!IS_ERR(msm_port->pclk)) - clk_enable(msm_port->pclk); - msm_serial_set_mnd_regs(port); + +#ifdef CONFIG_SERIAL_MSM_CLOCK_CONTROL + msm_port->clk_state = MSM_CLK_ON; +#endif + + if (port->uartclk == 19200000) { + /* clock is TCXO (19.2MHz) */ + msm_write(port, 0x06, UART_MREG); + msm_write(port, 0xF1, UART_NREG); + msm_write(port, 0x0F, UART_DREG); + msm_write(port, 0x1A, UART_MNDREG); + } else { + /* clock must be TCXO/4 */ + msm_write(port, 0x18, UART_MREG); + msm_write(port, 0xF6, UART_NREG); + msm_write(port, 0x0F, UART_DREG); + msm_write(port, 0x0A, UART_MNDREG); + } } +static void msm_deinit_clock(struct uart_port *port) +{ + struct msm_port *msm_port = UART_TO_MSM(port); + +#ifdef CONFIG_SERIAL_MSM_CLOCK_CONTROL + if (msm_port->clk_state != MSM_CLK_OFF) + clk_disable(msm_port->clk); + msm_port->clk_state = MSM_CLK_PORT_OFF; +#else + clk_disable(msm_port->clk); +#endif + +} static int msm_startup(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); @@ -425,7 +568,15 @@ static int msm_startup(struct uart_port *port) if (unlikely(ret)) return ret; + if (unlikely(irq_set_irq_wake(port->irq, 1))) { + free_irq(port->irq, port); + return -ENXIO; + } + +#ifndef CONFIG_PM_RUNTIME msm_init_clock(port); +#endif + pm_runtime_get_sync(port->dev); if (likely(port->fifosize > 12)) rfr_level = port->fifosize - 12; @@ -448,31 +599,29 @@ static int msm_startup(struct uart_port *port) msm_write(port, data, UART_IPR); } - data = 0; - if (!port->cons || (port->cons && !(port->cons->flags & CON_ENABLED))) { - msm_write(port, UART_CR_CMD_PROTECTION_EN, UART_CR); - msm_reset(port); - data = UART_CR_TX_ENABLE; - } - - data |= UART_CR_RX_ENABLE; - msm_write(port, data, UART_CR); /* enable TX & RX */ + msm_reset(port); - /* Make sure IPR is not 0 to start with*/ - if (msm_port->is_uartdm) - msm_write(port, UART_IPR_STALE_LSB, UART_IPR); + msm_write(port, 0x05, UART_CR); /* enable TX & RX */ /* turn on RX and CTS interrupts */ msm_port->imr = UART_IMR_RXLEV | UART_IMR_RXSTALE | UART_IMR_CURRENT_CTS; + msm_write(port, msm_port->imr, UART_IMR); - if (msm_port->is_uartdm) { - msm_write(port, 0xFFFFFF, UARTDM_DMRX); - msm_write(port, UART_CR_CMD_RESET_STALE_INT, UART_CR); - msm_write(port, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR); +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP + if (use_low_power_wakeup(msm_port)) { + ret = irq_set_irq_wake(msm_port->wakeup.irq, 1); + if (unlikely(ret)) + return ret; + ret = request_irq(msm_port->wakeup.irq, msm_rx_irq, + IRQF_TRIGGER_FALLING, + "msm_serial_wakeup", msm_port); + if (unlikely(ret)) + return ret; + disable_irq(msm_port->wakeup.irq); } +#endif - msm_write(port, msm_port->imr, UART_IMR); return 0; } @@ -480,12 +629,25 @@ static void msm_shutdown(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); + clk_enable(msm_port->clk); + msm_port->imr = 0; msm_write(port, 0, UART_IMR); /* disable interrupts */ clk_disable(msm_port->clk); free_irq(port->irq, port); + +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP + if (use_low_power_wakeup(msm_port)) { + irq_set_irq_wake(msm_port->wakeup.irq, 0); + free_irq(msm_port->wakeup.irq, msm_port); + } +#endif +#ifndef CONFIG_PM_RUNTIME + msm_deinit_clock(port); +#endif + pm_runtime_put_sync(port->dev); } static void msm_set_termios(struct uart_port *port, struct ktermios *termios, @@ -493,14 +655,14 @@ static void msm_set_termios(struct uart_port *port, struct ktermios *termios, { unsigned long flags; unsigned int baud, mr; + struct msm_port *msm_port = UART_TO_MSM(port); spin_lock_irqsave(&port->lock, flags); + clk_enable(msm_port->clk); /* calculate and set baud rate */ baud = uart_get_baud_rate(port, termios, old, 300, 115200); - baud = msm_set_baud_rate(port, baud); - if (tty_termios_baud_rate(termios)) - tty_termios_encode_baud_rate(termios, baud, baud); + msm_set_baud_rate(port, baud); /* calculate parity */ mr = msm_read(port, UART_MR2); @@ -560,6 +722,7 @@ static void msm_set_termios(struct uart_port *port, struct ktermios *termios, uart_update_timeout(port, termios->c_cflag, baud); + clk_disable(msm_port->clk); spin_unlock_irqrestore(&port->lock, flags); } @@ -571,105 +734,48 @@ static const char *msm_type(struct uart_port *port) static void msm_release_port(struct uart_port *port) { struct platform_device *pdev = to_platform_device(port->dev); - struct msm_port *msm_port = UART_TO_MSM(port); - struct resource *uart_resource; - struct resource *gsbi_resource; + struct resource *resource; resource_size_t size; - uart_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (unlikely(!uart_resource)) + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!resource)) return; - size = resource_size(uart_resource); + size = resource->end - resource->start + 1; release_mem_region(port->mapbase, size); iounmap(port->membase); port->membase = NULL; - - if (msm_port->gsbi_base) { - iowrite32(GSBI_PROTOCOL_IDLE, msm_port->gsbi_base + - GSBI_CONTROL); - - gsbi_resource = platform_get_resource_byname(pdev, - IORESOURCE_MEM, - "gsbi_resource"); - - if (unlikely(!gsbi_resource)) - return; - - size = resource_size(gsbi_resource); - release_mem_region(gsbi_resource->start, size); - iounmap(msm_port->gsbi_base); - msm_port->gsbi_base = NULL; - } } static int msm_request_port(struct uart_port *port) { - struct msm_port *msm_port = UART_TO_MSM(port); struct platform_device *pdev = to_platform_device(port->dev); - struct resource *uart_resource; - struct resource *gsbi_resource; + struct resource *resource; resource_size_t size; - int ret; - uart_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "uart_resource"); - if (unlikely(!uart_resource)) + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!resource)) return -ENXIO; + size = resource->end - resource->start + 1; - size = resource_size(uart_resource); - - if (!request_mem_region(port->mapbase, size, "msm_serial")) + if (unlikely(!request_mem_region(port->mapbase, size, "msm_serial"))) return -EBUSY; port->membase = ioremap(port->mapbase, size); if (!port->membase) { - ret = -EBUSY; - goto fail_release_port; - } - - gsbi_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "gsbi_resource"); - /* Is this a GSBI-based port? */ - if (gsbi_resource) { - size = resource_size(gsbi_resource); - - if (!request_mem_region(gsbi_resource->start, size, - "msm_serial")) { - ret = -EBUSY; - goto fail_release_port; - } - - msm_port->gsbi_base = ioremap(gsbi_resource->start, size); - if (!msm_port->gsbi_base) { - ret = -EBUSY; - goto fail_release_gsbi; - } + release_mem_region(port->mapbase, size); + return -EBUSY; } return 0; - -fail_release_gsbi: - release_mem_region(gsbi_resource->start, size); -fail_release_port: - release_mem_region(port->mapbase, size); - return ret; } static void msm_config_port(struct uart_port *port, int flags) { - struct msm_port *msm_port = UART_TO_MSM(port); - int ret; if (flags & UART_CONFIG_TYPE) { port->type = PORT_MSM; - ret = msm_request_port(port); - if (ret) - return; + msm_request_port(port); } - - if (msm_port->is_uartdm) - iowrite32(GSBI_PROTOCOL_UART, msm_port->gsbi_base + - GSBI_CONTROL); } static int msm_verify_port(struct uart_port *port, struct serial_struct *ser) @@ -684,22 +790,20 @@ static int msm_verify_port(struct uart_port *port, struct serial_struct *ser) static void msm_power(struct uart_port *port, unsigned int state, unsigned int oldstate) { +#ifndef CONFIG_SERIAL_MSM_CLOCK_CONTROL struct msm_port *msm_port = UART_TO_MSM(port); switch (state) { case 0: clk_enable(msm_port->clk); - if (!IS_ERR(msm_port->pclk)) - clk_enable(msm_port->pclk); break; case 3: clk_disable(msm_port->clk); - if (!IS_ERR(msm_port->pclk)) - clk_disable(msm_port->pclk); break; default: printk(KERN_ERR "msm_serial: Unknown PM state %d\n", state); } +#endif } static struct uart_ops msm_uart_pops = { @@ -728,7 +832,7 @@ static struct msm_port msm_uart_ports[] = { .iotype = UPIO_MEM, .ops = &msm_uart_pops, .flags = UPF_BOOT_AUTOCONF, - .fifosize = 64, + .fifosize = 512, .line = 0, }, }, @@ -737,7 +841,7 @@ static struct msm_port msm_uart_ports[] = { .iotype = UPIO_MEM, .ops = &msm_uart_pops, .flags = UPF_BOOT_AUTOCONF, - .fifosize = 64, + .fifosize = 512, .line = 1, }, }, @@ -754,23 +858,56 @@ static struct msm_port msm_uart_ports[] = { #define UART_NR ARRAY_SIZE(msm_uart_ports) -static inline struct uart_port *get_port_from_line(unsigned int line) +static inline struct uart_port * get_port_from_line(unsigned int line) { return &msm_uart_ports[line].uart; } #ifdef CONFIG_SERIAL_MSM_CONSOLE -static void msm_console_putchar(struct uart_port *port, int c) +/* + * Wait for transmitter & holding register to empty + * Derived from wait_for_xmitr in 8250 serial driver by Russell King + */ +static inline void wait_for_xmitr(struct uart_port *port, int bits) { - struct msm_port *msm_port = UART_TO_MSM(port); + unsigned int status, mr, tmout = 10000; + + /* Wait up to 10ms for the character(s) to be sent. */ + do { + status = msm_read(port, UART_SR); - if (msm_port->is_uartdm) - reset_dm_count(port); + if (--tmout == 0) + break; + udelay(1); + } while ((status & bits) != bits); - while (!(msm_read(port, UART_SR) & UART_SR_TX_READY)) - ; - msm_write(port, c, msm_port->is_uartdm ? UARTDM_TF : UART_TF); + mr = msm_read(port, UART_MR1); + + /* Wait up to 1s for flow control if necessary */ + if (mr & UART_MR1_CTS_CTL) { + unsigned int tmout; + for (tmout = 1000000; tmout; tmout--) { + unsigned int isr = msm_read(port, UART_ISR); + + /* CTS input is active lo */ + if (!(isr & UART_IMR_CURRENT_CTS)) + break; + udelay(1); + touch_nmi_watchdog(); + } + } +} + + +static void msm_console_putchar(struct uart_port *port, int c) +{ + /* This call can incur significant delay if CTS flowcontrol is enabled + * on port and no serial cable is attached. + */ + wait_for_xmitr(port, UART_SR_TX_READY); + + msm_write(port, c, UART_TF); } static void msm_console_write(struct console *co, const char *s, @@ -778,35 +915,48 @@ static void msm_console_write(struct console *co, const char *s, { struct uart_port *port; struct msm_port *msm_port; + int locked; BUG_ON(co->index < 0 || co->index >= UART_NR); port = get_port_from_line(co->index); msm_port = UART_TO_MSM(port); - spin_lock(&port->lock); + /* not pretty, but we can end up here via various convoluted paths */ + if (port->sysrq || oops_in_progress) + locked = spin_trylock(&port->lock); + else { + locked = 1; + spin_lock(&port->lock); + } + uart_console_write(port, s, count, msm_console_putchar); - spin_unlock(&port->lock); + + if (locked) + spin_unlock(&port->lock); } static int __init msm_console_setup(struct console *co, char *options) { struct uart_port *port; - struct msm_port *msm_port; int baud, flow, bits, parity; if (unlikely(co->index >= UART_NR || co->index < 0)) return -ENXIO; port = get_port_from_line(co->index); - msm_port = UART_TO_MSM(port); if (unlikely(!port->membase)) return -ENXIO; port->cons = co; + pm_runtime_get_noresume(port->dev); + +#ifndef CONFIG_PM_RUNTIME msm_init_clock(port); +#endif + pm_runtime_resume(port->dev); if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); @@ -823,11 +973,6 @@ static int __init msm_console_setup(struct console *co, char *options) msm_reset(port); - if (msm_port->is_uartdm) { - msm_write(port, UART_CR_CMD_PROTECTION_EN, UART_CR); - msm_write(port, UART_CR_TX_ENABLE, UART_CR); - } - printk(KERN_INFO "msm_serial: console setup on port #%d\n", port->line); return uart_set_options(port, co, baud, parity, bits, flow); @@ -845,7 +990,7 @@ static struct console msm_console = { .data = &msm_uart_driver, }; -#define MSM_CONSOLE (&msm_console) +#define MSM_CONSOLE &msm_console #else #define MSM_CONSOLE NULL @@ -865,6 +1010,9 @@ static int __init msm_serial_probe(struct platform_device *pdev) struct resource *resource; struct uart_port *port; int irq; +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP + struct msm_serial_platform_data *pdata = pdev->dev.platform_data; +#endif if (unlikely(pdev->id < 0 || pdev->id >= UART_NR)) return -ENXIO; @@ -875,32 +1023,14 @@ static int __init msm_serial_probe(struct platform_device *pdev) port->dev = &pdev->dev; msm_port = UART_TO_MSM(port); - if (platform_get_resource_byname(pdev, IORESOURCE_MEM, "gsbi_resource")) - msm_port->is_uartdm = 1; - else - msm_port->is_uartdm = 0; - - if (msm_port->is_uartdm) { - msm_port->clk = clk_get(&pdev->dev, "gsbi_uart_clk"); - msm_port->pclk = clk_get(&pdev->dev, "gsbi_pclk"); - } else { - msm_port->clk = clk_get(&pdev->dev, "uart_clk"); - msm_port->pclk = ERR_PTR(-ENOENT); - } - - if (unlikely(IS_ERR(msm_port->clk) || (IS_ERR(msm_port->pclk) && - msm_port->is_uartdm))) - return PTR_ERR(msm_port->clk); - - if (msm_port->is_uartdm) - clk_set_rate(msm_port->clk, 7372800); - + msm_port->clk = clk_get(&pdev->dev, "uart_clk"); + if (unlikely(IS_ERR(msm_port->clk))) + return PTR_ERR(msm_port->clk); port->uartclk = clk_get_rate(msm_port->clk); - printk(KERN_INFO "uartclk = %d\n", port->uartclk); + if (!port->uartclk) + port->uartclk = 19200000; - - resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "uart_resource"); + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (unlikely(!resource)) return -ENXIO; port->mapbase = resource->start; @@ -912,6 +1042,29 @@ static int __init msm_serial_probe(struct platform_device *pdev) platform_set_drvdata(pdev, port); + +#ifdef CONFIG_SERIAL_MSM_RX_WAKEUP + if (pdata == NULL) + msm_port->wakeup.irq = -1; + else { + msm_port->wakeup.irq = pdata->wakeup_irq; + msm_port->wakeup.ignore = 1; + msm_port->wakeup.inject_rx = pdata->inject_rx_on_wakeup; + msm_port->wakeup.rx_to_inject = pdata->rx_to_inject; + + if (unlikely(msm_port->wakeup.irq <= 0)) + return -EINVAL; + } +#endif + +#ifdef CONFIG_SERIAL_MSM_CLOCK_CONTROL + msm_port->clk_state = MSM_CLK_PORT_OFF; + hrtimer_init(&msm_port->clk_off_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + msm_port->clk_off_timer.function = msm_serial_clock_off; + msm_port->clk_off_delay = ktime_set(0, 1000000); /* 1 ms */ +#endif + + pm_runtime_enable(port->dev); return uart_add_one_port(&msm_uart_driver, port); } @@ -919,16 +1072,84 @@ static int __devexit msm_serial_remove(struct platform_device *pdev) { struct msm_port *msm_port = platform_get_drvdata(pdev); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + clk_put(msm_port->clk); return 0; } +#ifdef CONFIG_PM +static int msm_serial_suspend(struct device *dev) +{ + struct uart_port *port; + struct platform_device *pdev = to_platform_device(dev); + port = get_port_from_line(pdev->id); + + if (port) { + uart_suspend_port(&msm_uart_driver, port); + if (is_console(port)) + msm_deinit_clock(port); + } + + return 0; +} + +static int msm_serial_resume(struct device *dev) +{ + struct uart_port *port; + struct platform_device *pdev = to_platform_device(dev); + port = get_port_from_line(pdev->id); + + if (port) { + if (is_console(port)) + msm_init_clock(port); + uart_resume_port(&msm_uart_driver, port); + } + + return 0; +} +#else +#define msm_serial_suspend NULL +#define msm_serial_resume NULL +#endif + +static int msm_serial_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct uart_port *port; + port = get_port_from_line(pdev->id); + + dev_dbg(dev, "pm_runtime: suspending\n"); + msm_deinit_clock(port); + return 0; +} + +static int msm_serial_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct uart_port *port; + port = get_port_from_line(pdev->id); + + dev_dbg(dev, "pm_runtime: resuming\n"); + msm_init_clock(port); + return 0; +} + +static struct dev_pm_ops msm_serial_dev_pm_ops = { + .suspend = msm_serial_suspend, + .resume = msm_serial_resume, + .runtime_suspend = msm_serial_runtime_suspend, + .runtime_resume = msm_serial_runtime_resume, +}; + static struct platform_driver msm_platform_driver = { .remove = msm_serial_remove, .driver = { .name = "msm_serial", .owner = THIS_MODULE, + .pm = &msm_serial_dev_pm_ops, }, }; @@ -963,4 +1184,4 @@ module_exit(msm_serial_exit); MODULE_AUTHOR("Robert Love "); MODULE_DESCRIPTION("Driver for msm7x serial device"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/tty/serial/msm_serial.h b/drivers/tty/serial/msm_serial.h index e4acef5de77..65d0e30cd79 100644 --- a/drivers/tty/serial/msm_serial.h +++ b/drivers/tty/serial/msm_serial.h @@ -46,11 +46,14 @@ #define UART_CSR_19200 0xBB #define UART_CSR_14400 0xAA #define UART_CSR_9600 0x99 +#define UART_CSR_7200 0x88 #define UART_CSR_4800 0x77 #define UART_CSR_2400 0x55 #define UART_CSR_1200 0x44 #define UART_CSR_600 0x33 #define UART_CSR_300 0x22 +#define UART_CSR_150 0x11 +#define UART_CSR_75 0x00 #define UART_TF 0x000C #define UARTDM_TF 0x0070 @@ -128,60 +131,4 @@ #define UARTDM_NCF_TX 0x40 #define UARTDM_RX_TOTAL_SNAP 0x38 -#define UART_TO_MSM(uart_port) ((struct msm_port *) uart_port) - -static inline -void msm_write(struct uart_port *port, unsigned int val, unsigned int off) -{ - __raw_writel(val, port->membase + off); -} - -static inline -unsigned int msm_read(struct uart_port *port, unsigned int off) -{ - return __raw_readl(port->membase + off); -} - -/* - * Setup the MND registers to use the TCXO clock. - */ -static inline void msm_serial_set_mnd_regs_tcxo(struct uart_port *port) -{ - msm_write(port, 0x06, UART_MREG); - msm_write(port, 0xF1, UART_NREG); - msm_write(port, 0x0F, UART_DREG); - msm_write(port, 0x1A, UART_MNDREG); -} - -/* - * Setup the MND registers to use the TCXO clock divided by 4. - */ -static inline void msm_serial_set_mnd_regs_tcxoby4(struct uart_port *port) -{ - msm_write(port, 0x18, UART_MREG); - msm_write(port, 0xF6, UART_NREG); - msm_write(port, 0x0F, UART_DREG); - msm_write(port, 0x0A, UART_MNDREG); -} - -static inline -void msm_serial_set_mnd_regs_from_uartclk(struct uart_port *port) -{ - if (port->uartclk == 19200000) - msm_serial_set_mnd_regs_tcxo(port); - else - msm_serial_set_mnd_regs_tcxoby4(port); -} - -/* - * TROUT has a specific defect that makes it report it's uartclk - * as 19.2Mhz (TCXO) when it's actually 4.8Mhz (TCXO/4). This special - * cases TROUT to use the right clock. - */ -#ifdef CONFIG_MACH_TROUT -#define msm_serial_set_mnd_regs msm_serial_set_mnd_regs_tcxoby4 -#else -#define msm_serial_set_mnd_regs msm_serial_set_mnd_regs_from_uartclk -#endif - #endif /* __DRIVERS_SERIAL_MSM_SERIAL_H */ diff --git a/drivers/tty/serial/msm_serial_debugger.c b/drivers/tty/serial/msm_serial_debugger.c new file mode 100644 index 00000000000..88b67840852 --- /dev/null +++ b/drivers/tty/serial/msm_serial_debugger.c @@ -0,0 +1,421 @@ +/* + * drivers/serial/msm_serial_debuger.c + * + * Serial Debugger Interface for MSM7K + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "msm_serial.h" + +static unsigned int debug_port_base; +static int debug_signal_irq; +static struct clk *debug_clk; +static int debug_enable; +static int debugger_enable; +static struct { + unsigned int base; + int irq; + struct device *clk_device; + int signal_irq; +} init_data; + +static inline void msm_write(unsigned int val, unsigned int off) +{ + __raw_writel(val, debug_port_base + off); +} + +static inline unsigned int msm_read(unsigned int off) +{ + return __raw_readl(debug_port_base + off); +} + +static void debug_port_init(void) +{ + /* reset everything */ + msm_write(UART_CR_CMD_RESET_RX, UART_CR); + msm_write(UART_CR_CMD_RESET_TX, UART_CR); + msm_write(UART_CR_CMD_RESET_ERR, UART_CR); + msm_write(UART_CR_CMD_RESET_BREAK_INT, UART_CR); + msm_write(UART_CR_CMD_RESET_CTS, UART_CR); + msm_write(UART_CR_CMD_SET_RFR, UART_CR); + + /* setup clock dividers */ + if (clk_get_rate(debug_clk) == 19200000) { + /* clock is TCXO (19.2MHz) */ + msm_write(0x06, UART_MREG); + msm_write(0xF1, UART_NREG); + msm_write(0x0F, UART_DREG); + msm_write(0x1A, UART_MNDREG); + } else { + /* clock must be TCXO/4 */ + msm_write(0x18, UART_MREG); + msm_write(0xF6, UART_NREG); + msm_write(0x0F, UART_DREG); + msm_write(0x0A, UART_MNDREG); + } + + msm_write(UART_CSR_115200, UART_CSR); + + /* rx interrupt on every character -- keep it simple */ + msm_write(0, UART_RFWR); + + /* enable TX and RX */ + msm_write(0x05, UART_CR); + + /* enable RX interrupt */ + msm_write(UART_IMR_RXLEV, UART_IMR); +} + +static inline int debug_getc(void) +{ + if (msm_read(UART_SR) & UART_SR_RX_READY) { + return msm_read(UART_RF); + } else { + return -1; + } +} + +static inline void debug_putc(unsigned int c) +{ + while (!(msm_read(UART_SR) & UART_SR_TX_READY)) ; + msm_write(c, UART_TF); +} + +static inline void debug_flush(void) +{ + while (!(msm_read(UART_SR) & UART_SR_TX_EMPTY)) ; +} + +static void debug_puts(char *s) +{ + unsigned c; + while ((c = *s++)) { + if (c == '\n') + debug_putc('\r'); + debug_putc(c); + } +} + +static void debug_prompt(void) +{ + debug_puts("debug> "); +} + +int log_buf_copy(char *dest, int idx, int len); +static void dump_kernel_log(void) +{ + char buf[1024]; + int idx = 0; + int ret; + int saved_oip; + + /* setting oops_in_progress prevents log_buf_copy() + * from trying to take a spinlock which will make it + * very unhappy in some cases... + */ + saved_oip = oops_in_progress; + oops_in_progress = 1; + for (;;) { + ret = log_buf_copy(buf, idx, 1023); + if (ret <= 0) + break; + buf[ret] = 0; + debug_puts(buf); + idx += ret; + } + oops_in_progress = saved_oip; +} + +static char *mode_name(unsigned cpsr) +{ + switch (cpsr & MODE_MASK) { + case USR_MODE: return "USR"; + case FIQ_MODE: return "FIQ"; + case IRQ_MODE: return "IRQ"; + case SVC_MODE: return "SVC"; + case ABT_MODE: return "ABT"; + case UND_MODE: return "UND"; + case SYSTEM_MODE: return "SYS"; + default: return "???"; + } +} + +#define DEBUG_MAX 64 +static char debug_cmd[DEBUG_MAX]; +static int debug_busy; +static int debug_abort; + +static int debug_printf(void *cookie, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, 128, fmt, ap); + va_end(ap); + + debug_puts(buf); + return debug_abort; +} + +/* Safe outside fiq context */ +static int debug_printf_nfiq(void *cookie, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + unsigned long irq_flags; + + va_start(ap, fmt); + vsnprintf(buf, 128, fmt, ap); + va_end(ap); + + local_irq_save(irq_flags); + debug_puts(buf); + debug_flush(); + local_irq_restore(irq_flags); + return debug_abort; +} + +#define dprintf(fmt...) debug_printf(0, fmt) + +unsigned int last_irqs[NR_IRQS]; + +static void dump_irqs(void) +{ + int n; + dprintf("irqnr total since-last status name\n"); + for (n = 1; n < NR_IRQS; n++) { + struct irqaction *act = irq_desc[n].action; + if (!act && !kstat_cpu(0).irqs[n]) + continue; + dprintf("%5d: %10u %11u %8x %s\n", n, + kstat_cpu(0).irqs[n], + kstat_cpu(0).irqs[n] - last_irqs[n], + irq_desc[n].status, + (act && act->name) ? act->name : "???"); + last_irqs[n] = kstat_cpu(0).irqs[n]; + } +} + +static void debug_exec(const char *cmd, unsigned *regs) +{ + if (!strcmp(cmd, "pc")) { + dprintf(" pc %08x cpsr %08x mode %s\n", + regs[15], regs[16], mode_name(regs[16])); + } else if (!strcmp(cmd, "regs")) { + dprintf(" r0 %08x r1 %08x r2 %08x r3 %08x\n", + regs[0], regs[1], regs[2], regs[3]); + dprintf(" r4 %08x r5 %08x r6 %08x r7 %08x\n", + regs[4], regs[5], regs[6], regs[7]); + dprintf(" r8 %08x r9 %08x r10 %08x r11 %08x mode %s\n", + regs[8], regs[9], regs[10], regs[11], + mode_name(regs[16])); + dprintf(" ip %08x sp %08x lr %08x pc %08x cpsr %08x\n", + regs[10], regs[13], regs[14], regs[15], regs[16]); + } else if (!strcmp(cmd, "reboot")) { + if (msm_hw_reset_hook) + msm_hw_reset_hook(); + } else if (!strcmp(cmd, "irqs")) { + dump_irqs(); + } else if (!strcmp(cmd, "kmsg")) { + dump_kernel_log(); + } else if (!strcmp(cmd, "version")) { + dprintf("%s\n", linux_banner); + } else { + if (debug_busy) { + dprintf("command processor busy. trying to abort.\n"); + debug_abort = -1; + } else { + strcpy(debug_cmd, cmd); + debug_busy = 1; + } + msm_trigger_irq(debug_signal_irq); + return; + } + debug_prompt(); +} + +static irqreturn_t debug_irq(int irq, void *dev) +{ + if (debug_busy) { + struct kdbg_ctxt ctxt; + + ctxt.printf = debug_printf_nfiq; + kernel_debugger(&ctxt, debug_cmd); + debug_prompt(); + + debug_busy = 0; + } + return IRQ_HANDLED; +} + +static char debug_buf[DEBUG_MAX]; +static int debug_count; + +static void debug_fiq(void *data, void *regs) +{ + int c; + static int last_c; + + while ((c = debug_getc()) != -1) { + if (!debug_enable) { + if ((c == 13) || (c == 10)) { + debug_enable = true; + debug_count = 0; + debug_prompt(); + } + } else if ((c >= ' ') && (c < 127)) { + if (debug_count < (DEBUG_MAX - 1)) { + debug_buf[debug_count++] = c; + debug_putc(c); + } + } else if ((c == 8) || (c == 127)) { + if (debug_count > 0) { + debug_count--; + debug_putc(8); + debug_putc(' '); + debug_putc(8); + } + } else if ((c == 13) || (c == 10)) { + if (c == '\r' || (c == '\n' && last_c != '\r')) { + debug_putc('\r'); + debug_putc('\n'); + } + if (debug_count) { + debug_buf[debug_count] = 0; + debug_count = 0; + debug_exec(debug_buf, regs); + } else { + debug_prompt(); + } + } + last_c = c; + } + debug_flush(); +} + +#if defined(CONFIG_MSM_SERIAL_DEBUGGER_CONSOLE) +static void debug_console_write(struct console *co, + const char *s, unsigned int count) +{ + unsigned long irq_flags; + + /* disable irq's while TXing outside of FIQ context */ + local_irq_save(irq_flags); + while (count--) { + if (*s == '\n') + debug_putc('\r'); + debug_putc(*s++); + } + debug_flush(); + local_irq_restore(irq_flags); +} + +static struct console msm_serial_debug_console = { + .name = "debug_console", + .write = debug_console_write, + .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_ENABLED, +}; +#endif + +void msm_serial_debug_enable(int enable) { + debug_enable = enable; +} + +void msm_serial_debug_init(unsigned int base, int irq, + struct device *clk_device, int signal_irq) +{ + int ret; + void *port; + + debug_clk = clk_get(clk_device, "uart_clk"); + if (debug_clk) + clk_enable(debug_clk); + + port = ioremap(base, 4096); + if (!port) + return; + + init_data.base = base; + init_data.irq = irq; + init_data.clk_device = clk_device; + init_data.signal_irq = signal_irq; + debug_port_base = (unsigned int) port; + debug_signal_irq = signal_irq; + debug_port_init(); + + debug_prompt(); + + msm_fiq_select(irq); + msm_fiq_set_handler(debug_fiq, 0); + msm_fiq_enable(irq); + + ret = request_irq(signal_irq, debug_irq, + IRQF_TRIGGER_RISING, "debug", 0); + if (ret) + printk(KERN_ERR + "serial_debugger: could not install signal_irq"); + +#if defined(CONFIG_MSM_SERIAL_DEBUGGER_CONSOLE) + register_console(&msm_serial_debug_console); +#endif + debugger_enable = 1; +} +static int msm_serial_debug_remove(const char *val, struct kernel_param *kp) +{ + int ret; + static int pre_stat = 1; + ret = param_set_bool(val, kp); + if (ret) + return ret; + + if (pre_stat == *(int *)kp->arg) + return 0; + + pre_stat = *(int *)kp->arg; + + if (*(int *)kp->arg) { + msm_serial_debug_init(init_data.base, init_data.irq, + init_data.clk_device, init_data.signal_irq); + printk(KERN_INFO "enable FIQ serial debugger\n"); + return 0; + } + +#if defined(CONFIG_MSM_SERIAL_DEBUGGER_CONSOLE) + unregister_console(&msm_serial_debug_console); +#endif + free_irq(init_data.signal_irq, 0); + msm_fiq_set_handler(NULL, 0); + msm_fiq_disable(init_data.irq); + msm_fiq_unselect(init_data.irq); + clk_disable(debug_clk); + printk(KERN_INFO "disable FIQ serial debugger\n"); + return 0; +} +module_param_call(enable, msm_serial_debug_remove, param_get_bool, + &debugger_enable, S_IWUSR | S_IRUGO); diff --git a/drivers/tty/serial/msm_serial_hs.c b/drivers/tty/serial/msm_serial_hs.c index 624701f8138..7c1a9e80abb 100644 --- a/drivers/tty/serial/msm_serial_hs.c +++ b/drivers/tty/serial/msm_serial_hs.c @@ -1,10 +1,14 @@ -/* - * MSM 7k/8k High speed uart driver +/* drivers/serial/msm_serial_hs.c + * + * MSM 7k High speed uart driver * - * Copyright (c) 2007-2011, Code Aurora Forum. All rights reserved. * Copyright (c) 2008 Google Inc. + * Copyright (c) 2007-2011, Code Aurora Forum. All rights reserved. * Modified: Nick Pelly * + * All source code in this file is licensed under the following license + * except where indicated. + * * 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. @@ -44,163 +48,25 @@ #include #include #include -#include - -#include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include - -/* HSUART Registers */ -#define UARTDM_MR1_ADDR 0x0 -#define UARTDM_MR2_ADDR 0x4 - -/* Data Mover result codes */ -#define RSLT_FIFO_CNTR_BMSK (0xE << 28) -#define RSLT_VLD BIT(1) - -/* write only register */ -#define UARTDM_CSR_ADDR 0x8 -#define UARTDM_CSR_115200 0xFF -#define UARTDM_CSR_57600 0xEE -#define UARTDM_CSR_38400 0xDD -#define UARTDM_CSR_28800 0xCC -#define UARTDM_CSR_19200 0xBB -#define UARTDM_CSR_14400 0xAA -#define UARTDM_CSR_9600 0x99 -#define UARTDM_CSR_7200 0x88 -#define UARTDM_CSR_4800 0x77 -#define UARTDM_CSR_3600 0x66 -#define UARTDM_CSR_2400 0x55 -#define UARTDM_CSR_1200 0x44 -#define UARTDM_CSR_600 0x33 -#define UARTDM_CSR_300 0x22 -#define UARTDM_CSR_150 0x11 -#define UARTDM_CSR_75 0x00 - -/* write only register */ -#define UARTDM_TF_ADDR 0x70 -#define UARTDM_TF2_ADDR 0x74 -#define UARTDM_TF3_ADDR 0x78 -#define UARTDM_TF4_ADDR 0x7C - -/* write only register */ -#define UARTDM_CR_ADDR 0x10 -#define UARTDM_IMR_ADDR 0x14 - -#define UARTDM_IPR_ADDR 0x18 -#define UARTDM_TFWR_ADDR 0x1c -#define UARTDM_RFWR_ADDR 0x20 -#define UARTDM_HCR_ADDR 0x24 -#define UARTDM_DMRX_ADDR 0x34 -#define UARTDM_IRDA_ADDR 0x38 -#define UARTDM_DMEN_ADDR 0x3c - -/* UART_DM_NO_CHARS_FOR_TX */ -#define UARTDM_NCF_TX_ADDR 0x40 - -#define UARTDM_BADR_ADDR 0x44 - -#define UARTDM_SIM_CFG_ADDR 0x80 -/* Read Only register */ -#define UARTDM_SR_ADDR 0x8 - -/* Read Only register */ -#define UARTDM_RF_ADDR 0x70 -#define UARTDM_RF2_ADDR 0x74 -#define UARTDM_RF3_ADDR 0x78 -#define UARTDM_RF4_ADDR 0x7C - -/* Read Only register */ -#define UARTDM_MISR_ADDR 0x10 - -/* Read Only register */ -#define UARTDM_ISR_ADDR 0x14 -#define UARTDM_RX_TOTAL_SNAP_ADDR 0x38 - -#define UARTDM_RXFS_ADDR 0x50 - -/* Register field Mask Mapping */ -#define UARTDM_SR_PAR_FRAME_BMSK BIT(5) -#define UARTDM_SR_OVERRUN_BMSK BIT(4) -#define UARTDM_SR_TXEMT_BMSK BIT(3) -#define UARTDM_SR_TXRDY_BMSK BIT(2) -#define UARTDM_SR_RXRDY_BMSK BIT(0) - -#define UARTDM_CR_TX_DISABLE_BMSK BIT(3) -#define UARTDM_CR_RX_DISABLE_BMSK BIT(1) -#define UARTDM_CR_TX_EN_BMSK BIT(2) -#define UARTDM_CR_RX_EN_BMSK BIT(0) - -/* UARTDM_CR channel_comman bit value (register field is bits 8:4) */ -#define RESET_RX 0x10 -#define RESET_TX 0x20 -#define RESET_ERROR_STATUS 0x30 -#define RESET_BREAK_INT 0x40 -#define START_BREAK 0x50 -#define STOP_BREAK 0x60 -#define RESET_CTS 0x70 -#define RESET_STALE_INT 0x80 -#define RFR_LOW 0xD0 -#define RFR_HIGH 0xE0 -#define CR_PROTECTION_EN 0x100 -#define STALE_EVENT_ENABLE 0x500 -#define STALE_EVENT_DISABLE 0x600 -#define FORCE_STALE_EVENT 0x400 -#define CLEAR_TX_READY 0x300 -#define RESET_TX_ERROR 0x800 -#define RESET_TX_DONE 0x810 - -#define UARTDM_MR1_AUTO_RFR_LEVEL1_BMSK 0xffffff00 -#define UARTDM_MR1_AUTO_RFR_LEVEL0_BMSK 0x3f -#define UARTDM_MR1_CTS_CTL_BMSK 0x40 -#define UARTDM_MR1_RX_RDY_CTL_BMSK 0x80 - -#define UARTDM_MR2_ERROR_MODE_BMSK 0x40 -#define UARTDM_MR2_BITS_PER_CHAR_BMSK 0x30 - -/* bits per character configuration */ -#define FIVE_BPC (0 << 4) -#define SIX_BPC (1 << 4) -#define SEVEN_BPC (2 << 4) -#define EIGHT_BPC (3 << 4) - -#define UARTDM_MR2_STOP_BIT_LEN_BMSK 0xc -#define STOP_BIT_ONE (1 << 2) -#define STOP_BIT_TWO (3 << 2) - -#define UARTDM_MR2_PARITY_MODE_BMSK 0x3 - -/* Parity configuration */ -#define NO_PARITY 0x0 -#define EVEN_PARITY 0x1 -#define ODD_PARITY 0x2 -#define SPACE_PARITY 0x3 - -#define UARTDM_IPR_STALE_TIMEOUT_MSB_BMSK 0xffffff80 -#define UARTDM_IPR_STALE_LSB_BMSK 0x1f - -/* These can be used for both ISR and IMR register */ -#define UARTDM_ISR_TX_READY_BMSK BIT(7) -#define UARTDM_ISR_CURRENT_CTS_BMSK BIT(6) -#define UARTDM_ISR_DELTA_CTS_BMSK BIT(5) -#define UARTDM_ISR_RXLEV_BMSK BIT(4) -#define UARTDM_ISR_RXSTALE_BMSK BIT(3) -#define UARTDM_ISR_RXBREAK_BMSK BIT(2) -#define UARTDM_ISR_RXHUNT_BMSK BIT(1) -#define UARTDM_ISR_TXLEV_BMSK BIT(0) - -/* Field definitions for UART_DM_DMEN*/ -#define UARTDM_TX_DM_EN_BMSK 0x1 -#define UARTDM_RX_DM_EN_BMSK 0x2 - -#define UART_FIFOSIZE 64 -#define UARTCLK 7372800 - -/* Rx DMA request states */ +#include + +#include "msm_serial_hs_hwreg.h" + +static int hs_serial_debug_mask = 1; +module_param_named(debug_mask, hs_serial_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + enum flush_reason { FLUSH_NONE, FLUSH_DATA_READY, @@ -210,7 +76,6 @@ enum flush_reason { FLUSH_SHUTDOWN, }; -/* UART clock states */ enum msm_hs_clk_states_e { MSM_HS_CLK_PORT_OFF, /* port not in use */ MSM_HS_CLK_OFF, /* clock disabled */ @@ -227,27 +92,9 @@ enum msm_hs_clk_req_off_state_e { CLK_REQ_OFF_RXSTALE_FLUSHED, }; -/** - * struct msm_hs_tx - * @tx_ready_int_en: ok to dma more tx? - * @dma_in_flight: tx dma in progress - * @xfer: top level DMA command pointer structure - * @command_ptr: third level command struct pointer - * @command_ptr_ptr: second level command list struct pointer - * @mapped_cmd_ptr: DMA view of third level command struct - * @mapped_cmd_ptr_ptr: DMA view of second level command list struct - * @tx_count: number of bytes to transfer in DMA transfer - * @dma_base: DMA view of UART xmit buffer - * - * This structure describes a single Tx DMA transaction. MSM DMA - * commands have two levels of indirection. The top level command - * ptr points to a list of command ptr which in turn points to a - * single DMA 'command'. In our case each Tx transaction consists - * of a single second level pointer pointing to a 'box type' command. - */ struct msm_hs_tx { - unsigned int tx_ready_int_en; - unsigned int dma_in_flight; + unsigned int tx_ready_int_en; /* ok to dma more tx */ + unsigned int dma_in_flight; /* tx dma in progress */ struct msm_dmov_cmd xfer; dmov_box *command_ptr; u32 *command_ptr_ptr; @@ -255,25 +102,9 @@ struct msm_hs_tx { dma_addr_t mapped_cmd_ptr_ptr; int tx_count; dma_addr_t dma_base; + struct tasklet_struct tlet; }; -/** - * struct msm_hs_rx - * @flush: Rx DMA request state - * @xfer: top level DMA command pointer structure - * @cmdptr_dmaaddr: DMA view of second level command structure - * @command_ptr: third level DMA command pointer structure - * @command_ptr_ptr: second level DMA command list pointer - * @mapped_cmd_ptr: DMA view of the third level command structure - * @wait: wait for DMA completion before shutdown - * @buffer: destination buffer for RX DMA - * @rbuffer: DMA view of buffer - * @pool: dma pool out of which coherent rx buffer is allocated - * @tty_work: private work-queue for tty flip buffer push task - * - * This structure describes a single Rx DMA transaction. Rx DMA - * transactions use box mode DMA commands. - */ struct msm_hs_rx { enum flush_reason flush; struct msm_dmov_cmd xfer; @@ -284,127 +115,271 @@ struct msm_hs_rx { wait_queue_head_t wait; dma_addr_t rbuffer; unsigned char *buffer; + unsigned int buffer_pending; struct dma_pool *pool; - struct work_struct tty_work; + struct wake_lock wake_lock; + struct delayed_work flip_insert_work; + struct tasklet_struct tlet; }; -/** - * struct msm_hs_rx_wakeup - * @irq: IRQ line to be configured as interrupt source on Rx activity - * @ignore: boolean value. 1 = ignore the wakeup interrupt - * @rx_to_inject: extra character to be inserted to Rx tty on wakeup - * @inject_rx: 1 = insert rx_to_inject. 0 = do not insert extra character - * - * This is an optional structure required for UART Rx GPIO IRQ based - * wakeup from low power state. UART wakeup can be triggered by RX activity - * (using a wakeup GPIO on the UART RX pin). This should only be used if - * there is not a wakeup GPIO on the UART CTS, and the first RX byte is - * known (eg., with the Bluetooth Texas Instruments HCILL protocol), - * since the first RX byte will always be lost. RTS will be asserted even - * while the UART is clocked off in this mode of operation. - */ -struct msm_hs_rx_wakeup { +enum buffer_states { + NONE_PENDING = 0x0, + FIFO_OVERRUN = 0x1, + PARITY_ERROR = 0x2, + CHARS_NORMAL = 0x4, +}; + +/* optional low power wakeup, typically on a GPIO RX irq */ +struct msm_hs_wakeup { int irq; /* < 0 indicates low power wakeup disabled */ - unsigned char ignore; + unsigned char ignore; /* bool */ + + /* bool: inject char into rx tty on wakeup */ unsigned char inject_rx; char rx_to_inject; }; -/** - * struct msm_hs_port - * @uport: embedded uart port structure - * @imr_reg: shadow value of UARTDM_IMR - * @clk: uart input clock handle - * @tx: Tx transaction related data structure - * @rx: Rx transaction related data structure - * @dma_tx_channel: Tx DMA command channel - * @dma_rx_channel Rx DMA command channel - * @dma_tx_crci: Tx channel rate control interface number - * @dma_rx_crci: Rx channel rate control interface number - * @clk_off_timer: Timer to poll DMA event completion before clock off - * @clk_off_delay: clk_off_timer poll interval - * @clk_state: overall clock state - * @clk_req_off_state: post flush clock states - * @rx_wakeup: optional rx_wakeup feature related data - * @exit_lpm_cb: optional callback to exit low power mode - * - * Low level serial port structure. - */ struct msm_hs_port { struct uart_port uport; - unsigned long imr_reg; + unsigned long imr_reg; /* shadow value of UARTDM_IMR */ struct clk *clk; + struct clk *pclk; struct msm_hs_tx tx; struct msm_hs_rx rx; - + /* gsbi uarts have to do additional writes to gsbi memory */ + /* block and top control status block. The following pointers */ + /* keep a handle to these blocks. */ + unsigned char __iomem *mapped_gsbi; int dma_tx_channel; int dma_rx_channel; int dma_tx_crci; int dma_rx_crci; - - struct hrtimer clk_off_timer; + struct hrtimer clk_off_timer; /* to poll TXEMT before clock off */ ktime_t clk_off_delay; enum msm_hs_clk_states_e clk_state; enum msm_hs_clk_req_off_state_e clk_req_off_state; - struct msm_hs_rx_wakeup rx_wakeup; - void (*exit_lpm_cb)(struct uart_port *); + struct msm_hs_wakeup wakeup; + struct wake_lock dma_wake_lock; /* held while any DMA active */ }; #define MSM_UARTDM_BURST_SIZE 16 /* DM burst size (in bytes) */ #define UARTDM_TX_BUF_SIZE UART_XMIT_SIZE #define UARTDM_RX_BUF_SIZE 512 - +#define RETRY_TIMEOUT 5 #define UARTDM_NR 2 +static struct dentry *debug_base; static struct msm_hs_port q_uart_port[UARTDM_NR]; static struct platform_driver msm_serial_hs_platform_driver; static struct uart_driver msm_hs_driver; static struct uart_ops msm_hs_ops; -static struct workqueue_struct *msm_hs_workqueue; #define UARTDM_TO_MSM(uart_port) \ container_of((uart_port), struct msm_hs_port, uport) -static unsigned int use_low_power_rx_wakeup(struct msm_hs_port - *msm_uport) +static ssize_t show_clock(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int state = 1; + enum msm_hs_clk_states_e clk_state; + unsigned long flags; + + struct platform_device *pdev = container_of(dev, struct + platform_device, dev); + struct msm_hs_port *msm_uport = &q_uart_port[pdev->id]; + + spin_lock_irqsave(&msm_uport->uport.lock, flags); + clk_state = msm_uport->clk_state; + spin_unlock_irqrestore(&msm_uport->uport.lock, flags); + + if (clk_state <= MSM_HS_CLK_OFF) + state = 0; + + return sprintf(buf, "%d\n", state); +} + +static ssize_t set_clock(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { - return (msm_uport->rx_wakeup.irq >= 0); + int state; + struct platform_device *pdev = container_of(dev, struct + platform_device, dev); + struct msm_hs_port *msm_uport = &q_uart_port[pdev->id]; + + state = buf[0] - '0'; + switch (state) { + case 0: { + msm_hs_request_clock_off(&msm_uport->uport); + break; + } + case 1: { + msm_hs_request_clock_on(&msm_uport->uport); + break; + } + default: { + return -EINVAL; + } + } + return count; +} + +static DEVICE_ATTR(clock, S_IWUSR | S_IRUGO, show_clock, set_clock); + +static inline unsigned int use_low_power_wakeup(struct msm_hs_port *msm_uport) +{ + return (msm_uport->wakeup.irq > 0); } -static unsigned int msm_hs_read(struct uart_port *uport, +static inline int is_gsbi_uart(struct msm_hs_port *msm_uport) +{ + /* assume gsbi uart if gsbi resource found in pdata */ + return ((msm_uport->mapped_gsbi != NULL)); +} + +static inline unsigned int msm_hs_read(struct uart_port *uport, unsigned int offset) { - return ioread32(uport->membase + offset); + return readl_relaxed(uport->membase + offset); } -static void msm_hs_write(struct uart_port *uport, unsigned int offset, +static inline void msm_hs_write(struct uart_port *uport, unsigned int offset, unsigned int value) { - iowrite32(value, uport->membase + offset); + writel_relaxed(value, uport->membase + offset); } static void msm_hs_release_port(struct uart_port *port) { - iounmap(port->membase); + struct msm_hs_port *msm_uport = UARTDM_TO_MSM(port); + struct platform_device *pdev = to_platform_device(port->dev); + struct resource *gsbi_resource; + resource_size_t size; + + if (is_gsbi_uart(msm_uport)) { + iowrite32(GSBI_PROTOCOL_IDLE, msm_uport->mapped_gsbi + + GSBI_CONTROL_ADDR); + gsbi_resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "gsbi_resource"); + size = gsbi_resource->end - gsbi_resource->start + 1; + release_mem_region(gsbi_resource->start, size); + iounmap(msm_uport->mapped_gsbi); + msm_uport->mapped_gsbi = NULL; + } } static int msm_hs_request_port(struct uart_port *port) { - port->membase = ioremap(port->mapbase, PAGE_SIZE); - if (unlikely(!port->membase)) - return -ENOMEM; + struct msm_hs_port *msm_uport = UARTDM_TO_MSM(port); + struct platform_device *pdev = to_platform_device(port->dev); + struct resource *gsbi_resource; + resource_size_t size; + + gsbi_resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "gsbi_resource"); + if (gsbi_resource) { + size = gsbi_resource->end - gsbi_resource->start + 1; + if (unlikely(!request_mem_region(gsbi_resource->start, size, + "msm_serial_hs"))) + return -EBUSY; + msm_uport->mapped_gsbi = ioremap(gsbi_resource->start, + size); + if (!msm_uport->mapped_gsbi) { + release_mem_region(gsbi_resource->start, size); + return -EBUSY; + } + } + /* no gsbi uart */ + return 0; +} + +static int msm_serial_loopback_enable_set(void *data, u64 val) +{ + struct msm_hs_port *msm_uport = data; + struct uart_port *uport = &(msm_uport->uport); + unsigned long flags; + int ret = 0; + + clk_enable(msm_uport->clk); + if (msm_uport->pclk) + clk_enable(msm_uport->pclk); + + if (val) { + spin_lock_irqsave(&uport->lock, flags); + ret = msm_hs_read(uport, UARTDM_MR2_ADDR); + ret |= UARTDM_MR2_LOOP_MODE_BMSK; + msm_hs_write(uport, UARTDM_MR2_ADDR, ret); + spin_unlock_irqrestore(&uport->lock, flags); + } else { + spin_lock_irqsave(&uport->lock, flags); + ret = msm_hs_read(uport, UARTDM_MR2_ADDR); + ret &= ~UARTDM_MR2_LOOP_MODE_BMSK; + msm_hs_write(uport, UARTDM_MR2_ADDR, ret); + spin_unlock_irqrestore(&uport->lock, flags); + } + /* Calling CLOCK API. Hence mb() requires here. */ + mb(); + clk_disable(msm_uport->clk); + if (msm_uport->pclk) + clk_disable(msm_uport->pclk); - /* configure the CR Protection to Enable */ - msm_hs_write(port, UARTDM_CR_ADDR, CR_PROTECTION_EN); return 0; } +static int msm_serial_loopback_enable_get(void *data, u64 *val) +{ + struct msm_hs_port *msm_uport = data; + struct uart_port *uport = &(msm_uport->uport); + unsigned long flags; + int ret = 0; + + clk_enable(msm_uport->clk); + if (msm_uport->pclk) + clk_enable(msm_uport->pclk); + + spin_lock_irqsave(&uport->lock, flags); + ret = msm_hs_read(&msm_uport->uport, UARTDM_MR2_ADDR); + spin_unlock_irqrestore(&uport->lock, flags); + + clk_disable(msm_uport->clk); + if (msm_uport->pclk) + clk_disable(msm_uport->pclk); + + *val = (ret & UARTDM_MR2_LOOP_MODE_BMSK) ? 1 : 0; + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(loopback_enable_fops, msm_serial_loopback_enable_get, + msm_serial_loopback_enable_set, "%llu\n"); + +/* + * msm_serial_hs debugfs node: /msm_serial_hs/loopback. + * writing 1 turns on internal loopback mode in HW. Useful for automation + * test scripts. + * writing 0 disables the internal loopback mode. Default is disabled. + */ +static void __init msm_serial_debugfs_init(struct msm_hs_port *msm_uport, + int id) +{ + char node_name[15]; + snprintf(node_name, sizeof(node_name), "loopback.%d", id); + if (IS_ERR_OR_NULL(debugfs_create_file(node_name, + S_IRUGO | S_IWUSR, + debug_base, + msm_uport, + &loopback_enable_fops))) { + debugfs_remove_recursive(debug_base); + } +} + static int __devexit msm_hs_remove(struct platform_device *pdev) { struct msm_hs_port *msm_uport; struct device *dev; + struct msm_serial_hs_platform_data *pdata = pdev->dev.platform_data; + if (pdev->id < 0 || pdev->id >= UARTDM_NR) { printk(KERN_ERR "Invalid plaform device ID = %d\n", pdev->id); @@ -414,6 +389,13 @@ static int __devexit msm_hs_remove(struct platform_device *pdev) msm_uport = &q_uart_port[pdev->id]; dev = msm_uport->uport.dev; + if (pdata && pdata->gpio_config) + if (pdata->gpio_config(0)) + dev_err(dev, "GPIO config error\n"); + + sysfs_remove_file(&pdev->dev.kobj, &dev_attr_clock.attr); + debugfs_remove_recursive(debug_base); + dma_unmap_single(dev, msm_uport->rx.mapped_cmd_ptr, sizeof(dmov_box), DMA_TO_DEVICE); dma_pool_free(msm_uport->rx.pool, msm_uport->rx.buffer, @@ -427,6 +409,9 @@ static int __devexit msm_hs_remove(struct platform_device *pdev) dma_unmap_single(dev, msm_uport->tx.mapped_cmd_ptr, sizeof(dmov_box), DMA_TO_DEVICE); + wake_lock_destroy(&msm_uport->rx.wake_lock); + wake_lock_destroy(&msm_uport->dma_wake_lock); + uart_remove_one_port(&msm_hs_driver, &msm_uport->uport); clk_put(msm_uport->clk); @@ -443,64 +428,48 @@ static int __devexit msm_hs_remove(struct platform_device *pdev) return 0; } -static int msm_hs_init_clk_locked(struct uart_port *uport) +static int msm_hs_init_clk(struct uart_port *uport) { int ret; struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - ret = clk_enable(msm_uport->clk); + wake_lock(&msm_uport->dma_wake_lock); + /* Set up the MREG/NREG/DREG/MNDREG */ + ret = clk_set_rate(msm_uport->clk, uport->uartclk); if (ret) { - printk(KERN_ERR "Error could not turn on UART clk\n"); + printk(KERN_WARNING "Error setting clock rate on UART\n"); return ret; } - /* Set up the MREG/NREG/DREG/MNDREG */ - ret = clk_set_rate(msm_uport->clk, uport->uartclk); + ret = clk_enable(msm_uport->clk); if (ret) { - printk(KERN_WARNING "Error setting clock rate on UART\n"); - clk_disable(msm_uport->clk); + printk(KERN_ERR "Error could not turn on UART clk\n"); return ret; } + if (msm_uport->pclk) { + ret = clk_enable(msm_uport->pclk); + if (ret) { + dev_err(uport->dev, + "Error could not turn on UART pclk\n"); + return ret; + } + } msm_uport->clk_state = MSM_HS_CLK_ON; return 0; } -/* Enable and Disable clocks (Used for power management) */ -static void msm_hs_pm(struct uart_port *uport, unsigned int state, - unsigned int oldstate) -{ - struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - - if (use_low_power_rx_wakeup(msm_uport) || - msm_uport->exit_lpm_cb) - return; /* ignore linux PM states, - use msm_hs_request_clock API */ - - switch (state) { - case 0: - clk_enable(msm_uport->clk); - break; - case 3: - clk_disable(msm_uport->clk); - break; - default: - dev_err(uport->dev, "msm_serial: Unknown PM state %d\n", - state); - } -} - /* * programs the UARTDM_CSR register with correct bit rates * * Interrupts should be disabled before we are called, as * we modify Set Baud rate - * Set receive stale interrupt level, dependent on Bit Rate + * Set receive stale interrupt level, dependant on Bit Rate * Goal is to have around 8 ms before indicate stale. * roundup (((Bit Rate * .008) / 10) + 1 */ static void msm_hs_set_bps_locked(struct uart_port *uport, - unsigned int bps) + unsigned int bps) { unsigned long rxstale; unsigned long data; @@ -508,63 +477,63 @@ static void msm_hs_set_bps_locked(struct uart_port *uport, switch (bps) { case 300: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_75); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x00); rxstale = 1; break; case 600: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_150); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x11); rxstale = 1; break; case 1200: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_300); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x22); rxstale = 1; break; case 2400: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_600); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x33); rxstale = 1; break; case 4800: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_1200); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x44); rxstale = 1; break; case 9600: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_2400); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x55); rxstale = 2; break; case 14400: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_3600); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x66); rxstale = 3; break; case 19200: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_4800); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x77); rxstale = 4; break; case 28800: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_7200); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x88); rxstale = 6; break; case 38400: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_9600); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x99); rxstale = 8; break; case 57600: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_14400); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xaa); rxstale = 16; break; case 76800: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_19200); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xbb); rxstale = 16; break; case 115200: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_28800); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xcc); rxstale = 31; break; case 230400: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_57600); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xee); rxstale = 31; break; case 460800: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_115200); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xff); rxstale = 31; break; case 4000000: @@ -577,21 +546,28 @@ static void msm_hs_set_bps_locked(struct uart_port *uport, case 1152000: case 1000000: case 921600: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_115200); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xff); rxstale = 31; break; default: - msm_hs_write(uport, UARTDM_CSR_ADDR, UARTDM_CSR_2400); + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xff); /* default to 9600 */ bps = 9600; rxstale = 2; break; } - if (bps > 460800) + /* + * uart baud rate depends on CSR and MND Values + * we are updating CSR before and then calling + * clk_set_rate which updates MND Values. Hence + * dsb requires here. + */ + mb(); + if (bps > 460800) { uport->uartclk = bps * 16; - else - uport->uartclk = UARTCLK; - + } else { + uport->uartclk = 7372800; + } if (clk_set_rate(msm_uport->clk, uport->uartclk)) { printk(KERN_WARNING "Error setting clock rate on UART\n"); return; @@ -603,6 +579,56 @@ static void msm_hs_set_bps_locked(struct uart_port *uport, msm_hs_write(uport, UARTDM_IPR_ADDR, data); } + +static void msm_hs_set_std_bps_locked(struct uart_port *uport, + unsigned int bps) +{ + unsigned long rxstale; + unsigned long data; + + switch (bps) { + case 9600: + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x99); + rxstale = 2; + break; + case 14400: + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xaa); + rxstale = 3; + break; + case 19200: + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xbb); + rxstale = 4; + break; + case 28800: + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xcc); + rxstale = 6; + break; + case 38400: + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xdd); + rxstale = 8; + break; + case 57600: + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xee); + rxstale = 16; + break; + case 115200: + msm_hs_write(uport, UARTDM_CSR_ADDR, 0xff); + rxstale = 31; + break; + default: + msm_hs_write(uport, UARTDM_CSR_ADDR, 0x99); + /* default to 9600 */ + bps = 9600; + rxstale = 2; + break; + } + + data = rxstale & UARTDM_IPR_STALE_LSB_BMSK; + data |= UARTDM_IPR_STALE_TIMEOUT_MSB_BMSK & (rxstale << 2); + + msm_hs_write(uport, UARTDM_IPR_ADDR, data); +} + /* * termios : new ktermios * oldtermios: old ktermios previous setting @@ -610,8 +636,8 @@ static void msm_hs_set_bps_locked(struct uart_port *uport, * Configure the serial port */ static void msm_hs_set_termios(struct uart_port *uport, - struct ktermios *termios, - struct ktermios *oldtermios) + struct ktermios *termios, + struct ktermios *oldtermios) { unsigned int bps; unsigned long data; @@ -629,18 +655,23 @@ static void msm_hs_set_termios(struct uart_port *uport, if (bps == 200) bps = 3200000; - msm_hs_set_bps_locked(uport, bps); + uport->uartclk = clk_get_rate(msm_uport->clk); + if (!uport->uartclk) + msm_hs_set_std_bps_locked(uport, bps); + else + msm_hs_set_bps_locked(uport, bps); data = msm_hs_read(uport, UARTDM_MR2_ADDR); data &= ~UARTDM_MR2_PARITY_MODE_BMSK; /* set parity */ if (PARENB == (c_cflag & PARENB)) { - if (PARODD == (c_cflag & PARODD)) + if (PARODD == (c_cflag & PARODD)) { data |= ODD_PARITY; - else if (CMSPAR == (c_cflag & CMSPAR)) + } else if (CMSPAR == (c_cflag & CMSPAR)) { data |= SPACE_PARITY; - else + } else { data |= EVEN_PARITY; + } } /* Set bits per char */ @@ -696,12 +727,22 @@ static void msm_hs_set_termios(struct uart_port *uport, msm_hs_write(uport, UARTDM_CR_ADDR, RESET_TX); if (msm_uport->rx.flush == FLUSH_NONE) { + wake_lock(&msm_uport->rx.wake_lock); msm_uport->rx.flush = FLUSH_IGNORE; - msm_dmov_stop_cmd(msm_uport->dma_rx_channel, NULL, 1); + /* + * Before using dmov APIs make sure that + * previous writel are completed. Hence + * dsb requires here. + */ + mb(); + /* do discard flush */ + msm_dmov_stop_cmd(msm_uport->dma_rx_channel, + &msm_uport->rx.xfer, 0); } msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); - + /* calling other hardware component here clk_disable API. */ + mb(); clk_disable(msm_uport->clk); spin_unlock_irqrestore(&uport->lock, flags); } @@ -710,7 +751,7 @@ static void msm_hs_set_termios(struct uart_port *uport, * Standard API, Transmitter * Any character in the transmit shift register is sent */ -static unsigned int msm_hs_tx_empty(struct uart_port *uport) +unsigned int msm_hs_tx_empty(struct uart_port *uport) { unsigned int data; unsigned int ret = 0; @@ -726,6 +767,7 @@ static unsigned int msm_hs_tx_empty(struct uart_port *uport) return ret; } +EXPORT_SYMBOL(msm_hs_tx_empty); /* * Standard API, Stop transmitter. @@ -759,10 +801,15 @@ static void msm_hs_stop_rx_locked(struct uart_port *uport) data &= ~UARTDM_RX_DM_EN_BMSK; msm_hs_write(uport, UARTDM_DMEN_ADDR, data); + /* calling DMOV or CLOCK API. Hence mb() */ + mb(); /* Disable the receiver */ - if (msm_uport->rx.flush == FLUSH_NONE) - msm_dmov_stop_cmd(msm_uport->dma_rx_channel, NULL, 1); - + if (msm_uport->rx.flush == FLUSH_NONE) { + wake_lock(&msm_uport->rx.wake_lock); + /* do discard flush */ + msm_dmov_stop_cmd(msm_uport->dma_rx_channel, + &msm_uport->rx.xfer, 0); + } if (msm_uport->rx.flush != FLUSH_SHUTDOWN) msm_uport->rx.flush = FLUSH_STOP; @@ -774,7 +821,9 @@ static void msm_hs_submit_tx_locked(struct uart_port *uport) { int left; int tx_count; + int aligned_tx_count; dma_addr_t src_addr; + dma_addr_t aligned_src_addr; struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); struct msm_hs_tx *tx = &msm_uport->tx; struct circ_buf *tx_buf = &msm_uport->uport.state->xmit; @@ -797,8 +846,13 @@ static void msm_hs_submit_tx_locked(struct uart_port *uport) tx_count = left; src_addr = tx->dma_base + tx_buf->tail; - dma_sync_single_for_device(uport->dev, src_addr, tx_count, - DMA_TO_DEVICE); + /* Mask the src_addr to align on a cache + * and add those bytes to tx_count */ + aligned_src_addr = src_addr & ~(dma_get_cache_alignment() - 1); + aligned_tx_count = tx_count + src_addr - aligned_src_addr; + + dma_sync_single_for_device(uport->dev, aligned_src_addr, + aligned_tx_count, DMA_TO_DEVICE); tx->command_ptr->num_rows = (((tx_count + 15) >> 4) << 16) | ((tx_count + 15) >> 4); @@ -809,9 +863,6 @@ static void msm_hs_submit_tx_locked(struct uart_port *uport) *tx->command_ptr_ptr = CMD_PTR_LP | DMOV_CMD_ADDR(tx->mapped_cmd_ptr); - dma_sync_single_for_device(uport->dev, tx->mapped_cmd_ptr_ptr, - sizeof(u32 *), DMA_TO_DEVICE); - /* Save tx_count to use in Callback */ tx->tx_count = tx_count; msm_hs_write(uport, UARTDM_NCF_TX_ADDR, tx_count); @@ -819,6 +870,12 @@ static void msm_hs_submit_tx_locked(struct uart_port *uport) /* Disable the tx_ready interrupt */ msm_uport->imr_reg &= ~UARTDM_ISR_TX_READY_BMSK; msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); + /* Calling next DMOV API. Hence mb() here. */ + mb(); + + dma_sync_single_for_device(uport->dev, tx->mapped_cmd_ptr_ptr, + sizeof(u32 *), DMA_TO_DEVICE); + msm_dmov_enqueue_cmd(msm_uport->dma_tx_channel, &tx->xfer); } @@ -826,106 +883,111 @@ static void msm_hs_submit_tx_locked(struct uart_port *uport) static void msm_hs_start_rx_locked(struct uart_port *uport) { struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); + unsigned int buffer_pending = msm_uport->rx.buffer_pending; + + msm_uport->rx.buffer_pending = 0; + if (buffer_pending && hs_serial_debug_mask) + printk(KERN_ERR "Error: rx started in buffer state = %x", + buffer_pending); msm_hs_write(uport, UARTDM_CR_ADDR, RESET_STALE_INT); msm_hs_write(uport, UARTDM_DMRX_ADDR, UARTDM_RX_BUF_SIZE); msm_hs_write(uport, UARTDM_CR_ADDR, STALE_EVENT_ENABLE); msm_uport->imr_reg |= UARTDM_ISR_RXLEV_BMSK; msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); + /* Calling next DMOV API. Hence mb() here. */ + mb(); msm_uport->rx.flush = FLUSH_NONE; msm_dmov_enqueue_cmd(msm_uport->dma_rx_channel, &msm_uport->rx.xfer); - /* might have finished RX and be ready to clock off */ - hrtimer_start(&msm_uport->clk_off_timer, msm_uport->clk_off_delay, - HRTIMER_MODE_REL); -} - -/* Enable the transmitter Interrupt */ -static void msm_hs_start_tx_locked(struct uart_port *uport) -{ - struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - - clk_enable(msm_uport->clk); - - if (msm_uport->exit_lpm_cb) - msm_uport->exit_lpm_cb(uport); - - if (msm_uport->tx.tx_ready_int_en == 0) { - msm_uport->tx.tx_ready_int_en = 1; - msm_hs_submit_tx_locked(uport); - } - - clk_disable(msm_uport->clk); } -/* - * This routine is called when we are done with a DMA transfer - * - * This routine is registered with Data mover when we set - * up a Data Mover transfer. It is called from Data mover ISR - * when the DMA transfer is done. - */ -static void msm_hs_dmov_tx_callback(struct msm_dmov_cmd *cmd_ptr, - unsigned int result, - struct msm_dmov_errdata *err) +static void flip_insert_work(struct work_struct *work) { unsigned long flags; - struct msm_hs_port *msm_uport; - - /* DMA did not finish properly */ - WARN_ON((((result & RSLT_FIFO_CNTR_BMSK) >> 28) == 1) && - !(result & RSLT_VLD)); - - msm_uport = container_of(cmd_ptr, struct msm_hs_port, tx.xfer); + int retval; + struct msm_hs_port *msm_uport = + container_of(work, struct msm_hs_port, + rx.flip_insert_work.work); + struct tty_struct *tty = msm_uport->uport.state->port.tty; spin_lock_irqsave(&msm_uport->uport.lock, flags); - clk_enable(msm_uport->clk); - - msm_uport->imr_reg |= UARTDM_ISR_TX_READY_BMSK; - msm_hs_write(&msm_uport->uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); - - clk_disable(msm_uport->clk); + if (msm_uport->rx.buffer_pending == NONE_PENDING) { + if (hs_serial_debug_mask) + printk(KERN_ERR "Error: No buffer pending in %s", + __func__); + return; + } + if (msm_uport->rx.buffer_pending & FIFO_OVERRUN) { + retval = tty_insert_flip_char(tty, 0, TTY_OVERRUN); + if (retval) + msm_uport->rx.buffer_pending &= ~FIFO_OVERRUN; + } + if (msm_uport->rx.buffer_pending & PARITY_ERROR) { + retval = tty_insert_flip_char(tty, 0, TTY_PARITY); + if (retval) + msm_uport->rx.buffer_pending &= ~PARITY_ERROR; + } + if (msm_uport->rx.buffer_pending & CHARS_NORMAL) { + int rx_count, rx_offset; + rx_count = (msm_uport->rx.buffer_pending & 0xFFFF0000) >> 16; + rx_offset = (msm_uport->rx.buffer_pending & 0xFFD0) >> 5; + retval = tty_insert_flip_string(tty, msm_uport->rx.buffer + + rx_offset, rx_count); + msm_uport->rx.buffer_pending &= (FIFO_OVERRUN | + PARITY_ERROR); + if (retval != rx_count) + msm_uport->rx.buffer_pending |= CHARS_NORMAL | + retval << 8 | (rx_count - retval) << 16; + } + if (msm_uport->rx.buffer_pending) + schedule_delayed_work(&msm_uport->rx.flip_insert_work, + msecs_to_jiffies(RETRY_TIMEOUT)); + else + if ((msm_uport->clk_state == MSM_HS_CLK_ON) && + (msm_uport->rx.flush <= FLUSH_IGNORE)) { + if (hs_serial_debug_mask) + printk(KERN_WARNING + "msm_serial_hs: " + "Pending buffers cleared. " + "Restarting\n"); + msm_hs_start_rx_locked(&msm_uport->uport); + } spin_unlock_irqrestore(&msm_uport->uport.lock, flags); + tty_flip_buffer_push(tty); } -/* - * This routine is called when we are done with a DMA transfer or the - * a flush has been sent to the data mover driver. - * - * This routine is registered with Data mover when we set up a Data Mover - * transfer. It is called from Data mover ISR when the DMA transfer is done. - */ -static void msm_hs_dmov_rx_callback(struct msm_dmov_cmd *cmd_ptr, - unsigned int result, - struct msm_dmov_errdata *err) +static void msm_serial_hs_rx_tlet(unsigned long tlet_ptr) { int retval; int rx_count; unsigned long status; - unsigned int error_f = 0; unsigned long flags; - unsigned int flush; - struct tty_struct *tty; + unsigned int error_f = 0; struct uart_port *uport; struct msm_hs_port *msm_uport; + unsigned int flush; + struct tty_struct *tty; - msm_uport = container_of(cmd_ptr, struct msm_hs_port, rx.xfer); + msm_uport = container_of((struct tasklet_struct *)tlet_ptr, + struct msm_hs_port, rx.tlet); uport = &msm_uport->uport; + tty = uport->state->port.tty; - spin_lock_irqsave(&uport->lock, flags); - clk_enable(msm_uport->clk); + status = msm_hs_read(uport, UARTDM_SR_ADDR); - tty = uport->state->port.tty; + spin_lock_irqsave(&uport->lock, flags); + clk_enable(msm_uport->clk); msm_hs_write(uport, UARTDM_CR_ADDR, STALE_EVENT_DISABLE); - status = msm_hs_read(uport, UARTDM_SR_ADDR); - /* overflow is not connect to data in a FIFO */ if (unlikely((status & UARTDM_SR_OVERRUN_BMSK) && (uport->read_status_mask & CREAD))) { - tty_insert_flip_char(tty, 0, TTY_OVERRUN); + retval = tty_insert_flip_char(tty, 0, TTY_OVERRUN); + if (!retval) + msm_uport->rx.buffer_pending |= TTY_OVERRUN; uport->icount.buf_overrun++; error_f = 1; } @@ -937,8 +999,11 @@ static void msm_hs_dmov_rx_callback(struct msm_dmov_cmd *cmd_ptr, /* Can not tell difference between parity & frame error */ uport->icount.parity++; error_f = 1; - if (uport->ignore_status_mask & IGNPAR) - tty_insert_flip_char(tty, 0, TTY_PARITY); + if (uport->ignore_status_mask & IGNPAR) { + retval = tty_insert_flip_char(tty, 0, TTY_PARITY); + if (!retval) + msm_uport->rx.buffer_pending |= TTY_PARITY; + } } if (error_f) @@ -946,41 +1011,128 @@ static void msm_hs_dmov_rx_callback(struct msm_dmov_cmd *cmd_ptr, if (msm_uport->clk_req_off_state == CLK_REQ_OFF_FLUSH_ISSUED) msm_uport->clk_req_off_state = CLK_REQ_OFF_RXSTALE_FLUSHED; - flush = msm_uport->rx.flush; if (flush == FLUSH_IGNORE) - msm_hs_start_rx_locked(uport); - if (flush == FLUSH_STOP) + if (!msm_uport->rx.buffer_pending) + msm_hs_start_rx_locked(uport); + + if (flush == FLUSH_STOP) { msm_uport->rx.flush = FLUSH_SHUTDOWN; + wake_up(&msm_uport->rx.wait); + } if (flush >= FLUSH_DATA_INVALID) goto out; rx_count = msm_hs_read(uport, UARTDM_RX_TOTAL_SNAP_ADDR); + /* order the read of rx.buffer */ + rmb(); + if (0 != (uport->read_status_mask & CREAD)) { retval = tty_insert_flip_string(tty, msm_uport->rx.buffer, rx_count); - BUG_ON(retval != rx_count); + if (retval != rx_count) { + msm_uport->rx.buffer_pending |= CHARS_NORMAL | + retval << 5 | (rx_count - retval) << 16; + } } - msm_hs_start_rx_locked(uport); + /* order the read of rx.buffer and the start of next rx xfer */ + wmb(); + + if (!msm_uport->rx.buffer_pending) + msm_hs_start_rx_locked(uport); out: + if (msm_uport->rx.buffer_pending) { + if (hs_serial_debug_mask) + printk(KERN_WARNING + "msm_serial_hs: " + "tty buffer exhausted. " + "Stalling\n"); + schedule_delayed_work(&msm_uport->rx.flip_insert_work + , msecs_to_jiffies(RETRY_TIMEOUT)); + } clk_disable(msm_uport->clk); - + /* release wakelock in 500ms, not immediately, because higher layers + * don't always take wakelocks when they should */ + wake_lock_timeout(&msm_uport->rx.wake_lock, HZ / 2); + /* tty_flip_buffer_push() might call msm_hs_start(), so unlock */ spin_unlock_irqrestore(&uport->lock, flags); - if (flush < FLUSH_DATA_INVALID) - queue_work(msm_hs_workqueue, &msm_uport->rx.tty_work); + tty_flip_buffer_push(tty); } -static void msm_hs_tty_flip_buffer_work(struct work_struct *work) +/* Enable the transmitter Interrupt */ +static void msm_hs_start_tx_locked(struct uart_port *uport ) { - struct msm_hs_port *msm_uport = - container_of(work, struct msm_hs_port, rx.tty_work); - struct tty_struct *tty = msm_uport->uport.state->port.tty; + struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - tty_flip_buffer_push(tty); + clk_enable(msm_uport->clk); + + if (msm_uport->tx.tx_ready_int_en == 0) { + msm_uport->tx.tx_ready_int_en = 1; + if (msm_uport->tx.dma_in_flight == 0) + msm_hs_submit_tx_locked(uport); + } + + clk_disable(msm_uport->clk); +} + +/* + * This routine is called when we are done with a DMA transfer + * + * This routine is registered with Data mover when we set + * up a Data Mover transfer. It is called from Data mover ISR + * when the DMA transfer is done. + */ +static void msm_hs_dmov_tx_callback(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, + struct msm_dmov_errdata *err) +{ + struct msm_hs_port *msm_uport; + + WARN_ON(result != 0x80000002); /* DMA did not finish properly */ + + msm_uport = container_of(cmd_ptr, struct msm_hs_port, tx.xfer); + + tasklet_schedule(&msm_uport->tx.tlet); +} + +static void msm_serial_hs_tx_tlet(unsigned long tlet_ptr) +{ + unsigned long flags; + struct msm_hs_port *msm_uport = container_of((struct tasklet_struct *) + tlet_ptr, struct msm_hs_port, tx.tlet); + + spin_lock_irqsave(&(msm_uport->uport.lock), flags); + clk_enable(msm_uport->clk); + + msm_uport->imr_reg |= UARTDM_ISR_TX_READY_BMSK; + msm_hs_write(&(msm_uport->uport), UARTDM_IMR_ADDR, msm_uport->imr_reg); + /* Calling clk API. Hence mb() requires. */ + mb(); + + clk_disable(msm_uport->clk); + spin_unlock_irqrestore(&(msm_uport->uport.lock), flags); +} + +/* + * This routine is called when we are done with a DMA transfer or the + * a flush has been sent to the data mover driver. + * + * This routine is registered with Data mover when we set up a Data Mover + * transfer. It is called from Data mover ISR when the DMA transfer is done. + */ +static void msm_hs_dmov_rx_callback(struct msm_dmov_cmd *cmd_ptr, + unsigned int result, + struct msm_dmov_errdata *err) +{ + struct msm_hs_port *msm_uport; + + msm_uport = container_of(cmd_ptr, struct msm_hs_port, rx.xfer); + + tasklet_schedule(&msm_uport->rx.tlet); } /* @@ -1002,45 +1154,52 @@ static unsigned int msm_hs_get_mctrl_locked(struct uart_port *uport) } /* - * True enables UART auto RFR, which indicates we are ready for data if the RX - * buffer is not full. False disables auto RFR, and deasserts RFR to indicate - * we are not ready for data. Must be called with UART clock on. + * Standard API, Set or clear RFR_signal + * + * Set RFR high, (Indicate we are not ready for data), we disable auto + * ready for receiving and then set RFR_N high. To set RFR to low we just turn + * back auto ready for receiving and it should lower RFR signal + * when hardware is ready */ -static void set_rfr_locked(struct uart_port *uport, int auto_rfr) +void msm_hs_set_mctrl_locked(struct uart_port *uport, + unsigned int mctrl) { + unsigned int set_rts; unsigned int data; + struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - data = msm_hs_read(uport, UARTDM_MR1_ADDR); + clk_enable(msm_uport->clk); - if (auto_rfr) { - /* enable auto ready-for-receiving */ - data |= UARTDM_MR1_RX_RDY_CTL_BMSK; - msm_hs_write(uport, UARTDM_MR1_ADDR, data); - } else { - /* disable auto ready-for-receiving */ + /* RTS is active low */ + set_rts = TIOCM_RTS & mctrl ? 0 : 1; + + data = msm_hs_read(uport, UARTDM_MR1_ADDR); + if (set_rts) { + /*disable auto ready-for-receiving */ data &= ~UARTDM_MR1_RX_RDY_CTL_BMSK; msm_hs_write(uport, UARTDM_MR1_ADDR, data); - /* RFR is active low, set high */ + /* set RFR_N to high */ msm_hs_write(uport, UARTDM_CR_ADDR, RFR_HIGH); + } else { + /* Enable auto ready-for-receiving */ + data |= UARTDM_MR1_RX_RDY_CTL_BMSK; + msm_hs_write(uport, UARTDM_MR1_ADDR, data); } + /* Calling CLOCK API. Hence mb() requires. */ + mb(); + clk_disable(msm_uport->clk); } -/* - * Standard API, used to set or clear RFR - */ -static void msm_hs_set_mctrl_locked(struct uart_port *uport, +void msm_hs_set_mctrl(struct uart_port *uport, unsigned int mctrl) { - unsigned int auto_rfr; - struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - - clk_enable(msm_uport->clk); - - auto_rfr = TIOCM_RTS & mctrl ? 1 : 0; - set_rfr_locked(uport, auto_rfr); + unsigned long flags; - clk_disable(msm_uport->clk); + spin_lock_irqsave(&uport->lock, flags); + msm_hs_set_mctrl_locked(uport, mctrl); + spin_unlock_irqrestore(&uport->lock, flags); } +EXPORT_SYMBOL(msm_hs_set_mctrl); /* Standard API, Enable modem status (CTS) interrupt */ static void msm_hs_enable_ms_locked(struct uart_port *uport) @@ -1052,6 +1211,8 @@ static void msm_hs_enable_ms_locked(struct uart_port *uport) /* Enable DELTA_CTS Interrupt */ msm_uport->imr_reg |= UARTDM_ISR_DELTA_CTS_BMSK; msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); + /* Calling CLOCK API. Hence mb() requires here. */ + mb(); clk_disable(msm_uport->clk); @@ -1065,40 +1226,49 @@ static void msm_hs_enable_ms_locked(struct uart_port *uport) */ static void msm_hs_break_ctl(struct uart_port *uport, int ctl) { + unsigned long flags; struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); + spin_lock_irqsave(&uport->lock, flags); clk_enable(msm_uport->clk); msm_hs_write(uport, UARTDM_CR_ADDR, ctl ? START_BREAK : STOP_BREAK); + /* Calling CLOCK API. Hence mb() requires here. */ + mb(); clk_disable(msm_uport->clk); + spin_unlock_irqrestore(&uport->lock, flags); } static void msm_hs_config_port(struct uart_port *uport, int cfg_flags) { unsigned long flags; + struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); spin_lock_irqsave(&uport->lock, flags); if (cfg_flags & UART_CONFIG_TYPE) { uport->type = PORT_MSM; msm_hs_request_port(uport); } + if (is_gsbi_uart(msm_uport)) { + iowrite32(GSBI_PROTOCOL_UART, msm_uport->mapped_gsbi + + GSBI_CONTROL_ADDR); + } spin_unlock_irqrestore(&uport->lock, flags); } /* Handle CTS changes (Called from interrupt handler) */ -static void msm_hs_handle_delta_cts(struct uart_port *uport) +static void msm_hs_handle_delta_cts_locked(struct uart_port *uport) { - unsigned long flags; struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); - spin_lock_irqsave(&uport->lock, flags); clk_enable(msm_uport->clk); /* clear interrupt */ msm_hs_write(uport, UARTDM_CR_ADDR, RESET_CTS); + /* Calling CLOCK API. Hence mb() requires here. */ + mb(); uport->icount.cts++; clk_disable(msm_uport->clk); - spin_unlock_irqrestore(&uport->lock, flags); /* clear the IOCTL TIOCMIWAIT if called */ wake_up_interruptible(&uport->state->port.delta_msr_wait); @@ -1116,11 +1286,10 @@ static int msm_hs_check_clock_off_locked(struct uart_port *uport) struct circ_buf *tx_buf = &uport->state->xmit; /* Cancel if tx tty buffer is not empty, dma is in flight, - * or tx fifo is not empty, or rx fifo is not empty */ + * or tx fifo is not empty */ if (msm_uport->clk_state != MSM_HS_CLK_REQUEST_OFF || !uart_circ_empty(tx_buf) || msm_uport->tx.dma_in_flight || - (msm_uport->imr_reg & UARTDM_ISR_TXLEV_BMSK) || - !(msm_uport->imr_reg & UARTDM_ISR_RXLEV_BMSK)) { + msm_uport->imr_reg & UARTDM_ISR_TXLEV_BMSK) { return -1; } @@ -1134,6 +1303,11 @@ static int msm_hs_check_clock_off_locked(struct uart_port *uport) case CLK_REQ_OFF_START: msm_uport->clk_req_off_state = CLK_REQ_OFF_RXSTALE_ISSUED; msm_hs_write(uport, UARTDM_CR_ADDR, FORCE_STALE_EVENT); + /* + * Before returning make sure that device writel completed. + * Hence mb() requires here. + */ + mb(); return 0; /* RXSTALE flush not complete - retry */ case CLK_REQ_OFF_RXSTALE_ISSUED: case CLK_REQ_OFF_FLUSH_ISSUED: @@ -1150,17 +1324,18 @@ static int msm_hs_check_clock_off_locked(struct uart_port *uport) /* we really want to clock off */ clk_disable(msm_uport->clk); + if (msm_uport->pclk) + clk_disable(msm_uport->pclk); msm_uport->clk_state = MSM_HS_CLK_OFF; - - if (use_low_power_rx_wakeup(msm_uport)) { - msm_uport->rx_wakeup.ignore = 1; - enable_irq(msm_uport->rx_wakeup.irq); + if (use_low_power_wakeup(msm_uport)) { + msm_uport->wakeup.ignore = 1; + enable_irq(msm_uport->wakeup.irq); } + wake_unlock(&msm_uport->dma_wake_lock); return 1; } -static enum hrtimer_restart msm_hs_clk_off_retry(struct hrtimer *timer) -{ +static enum hrtimer_restart msm_hs_clk_off_retry(struct hrtimer *timer) { unsigned long flags; int ret = HRTIMER_NORESTART; struct msm_hs_port *msm_uport = container_of(timer, struct msm_hs_port, @@ -1183,7 +1358,7 @@ static irqreturn_t msm_hs_isr(int irq, void *dev) { unsigned long flags; unsigned long isr_status; - struct msm_hs_port *msm_uport = dev; + struct msm_hs_port *msm_uport = (struct msm_hs_port *)dev; struct uart_port *uport = &msm_uport->uport; struct circ_buf *tx_buf = &uport->state->xmit; struct msm_hs_tx *tx = &msm_uport->tx; @@ -1195,20 +1370,29 @@ static irqreturn_t msm_hs_isr(int irq, void *dev) /* Uart RX starting */ if (isr_status & UARTDM_ISR_RXLEV_BMSK) { + wake_lock(&rx->wake_lock); /* hold wakelock while rx dma */ msm_uport->imr_reg &= ~UARTDM_ISR_RXLEV_BMSK; msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); + /* Complete device write for IMR. Hence mb() requires. */ + mb(); } /* Stale rx interrupt */ if (isr_status & UARTDM_ISR_RXSTALE_BMSK) { msm_hs_write(uport, UARTDM_CR_ADDR, STALE_EVENT_DISABLE); msm_hs_write(uport, UARTDM_CR_ADDR, RESET_STALE_INT); + /* + * Complete device write before calling DMOV API. Hence + * mb() requires here. + */ + mb(); if (msm_uport->clk_req_off_state == CLK_REQ_OFF_RXSTALE_ISSUED) msm_uport->clk_req_off_state = - CLK_REQ_OFF_FLUSH_ISSUED; + CLK_REQ_OFF_FLUSH_ISSUED; + if (rx->flush == FLUSH_NONE) { rx->flush = FLUSH_DATA_READY; - msm_dmov_stop_cmd(msm_uport->dma_rx_channel, NULL, 1); + msm_dmov_flush(msm_uport->dma_rx_channel); } } /* tx ready interrupt */ @@ -1221,7 +1405,11 @@ static irqreturn_t msm_hs_isr(int irq, void *dev) msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); } - + /* + * Complete both writes before starting new TX. + * Hence mb() requires here. + */ + mb(); /* Complete DMA TX transactions and submit new transactions */ tx_buf->tail = (tx_buf->tail + tx->tx_count) & ~UART_XMIT_SIZE; @@ -1238,6 +1426,11 @@ static irqreturn_t msm_hs_isr(int irq, void *dev) /* TX FIFO is empty */ msm_uport->imr_reg &= ~UARTDM_ISR_TXLEV_BMSK; msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); + /* + * Complete device write before starting clock_off request. + * Hence mb() requires here. + */ + mb(); if (!msm_hs_check_clock_off_locked(uport)) hrtimer_start(&msm_uport->clk_off_timer, msm_uport->clk_off_delay, @@ -1246,58 +1439,52 @@ static irqreturn_t msm_hs_isr(int irq, void *dev) /* Change in CTS interrupt */ if (isr_status & UARTDM_ISR_DELTA_CTS_BMSK) - msm_hs_handle_delta_cts(uport); + msm_hs_handle_delta_cts_locked(uport); spin_unlock_irqrestore(&uport->lock, flags); return IRQ_HANDLED; } -void msm_hs_request_clock_off_locked(struct uart_port *uport) -{ +/* request to turn off uart clock once pending TX is flushed */ +void msm_hs_request_clock_off(struct uart_port *uport) { + unsigned long flags; struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); + spin_lock_irqsave(&uport->lock, flags); if (msm_uport->clk_state == MSM_HS_CLK_ON) { msm_uport->clk_state = MSM_HS_CLK_REQUEST_OFF; msm_uport->clk_req_off_state = CLK_REQ_OFF_START; - if (!use_low_power_rx_wakeup(msm_uport)) - set_rfr_locked(uport, 0); msm_uport->imr_reg |= UARTDM_ISR_TXLEV_BMSK; msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); + /* + * Complete device write before retuning back. + * Hence mb() requires here. + */ + mb(); } -} - -/** - * msm_hs_request_clock_off - request to (i.e. asynchronously) turn off uart - * clock once pending TX is flushed and Rx DMA command is terminated. - * @uport: uart_port structure for the device instance. - * - * This functions puts the device into a partially active low power mode. It - * waits to complete all pending tx transactions, flushes ongoing Rx DMA - * command and terminates UART side Rx transaction, puts UART HW in non DMA - * mode and then clocks off the device. A client calls this when no UART - * data is expected. msm_request_clock_on() must be called before any further - * UART can be sent or received. - */ -void msm_hs_request_clock_off(struct uart_port *uport) -{ - unsigned long flags; - - spin_lock_irqsave(&uport->lock, flags); - msm_hs_request_clock_off_locked(uport); spin_unlock_irqrestore(&uport->lock, flags); } +EXPORT_SYMBOL(msm_hs_request_clock_off); -void msm_hs_request_clock_on_locked(struct uart_port *uport) -{ +static void msm_hs_request_clock_on_locked(struct uart_port *uport) { struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); unsigned int data; + int ret = 0; switch (msm_uport->clk_state) { case MSM_HS_CLK_OFF: + wake_lock(&msm_uport->dma_wake_lock); clk_enable(msm_uport->clk); - disable_irq_nosync(msm_uport->rx_wakeup.irq); - /* fall-through */ + if (msm_uport->pclk) + ret = clk_enable(msm_uport->pclk); + disable_irq_nosync(msm_uport->wakeup.irq); + if (unlikely(ret)) { + dev_err(uport->dev, "Clock ON Failure" + "Stalling HSUART\n"); + break; + } + /* else fall-through */ case MSM_HS_CLK_REQUEST_OFF: if (msm_uport->rx.flush == FLUSH_STOP || msm_uport->rx.flush == FLUSH_SHUTDOWN) { @@ -1305,12 +1492,12 @@ void msm_hs_request_clock_on_locked(struct uart_port *uport) data = msm_hs_read(uport, UARTDM_DMEN_ADDR); data |= UARTDM_RX_DM_EN_BMSK; msm_hs_write(uport, UARTDM_DMEN_ADDR, data); + /* Complete above device write. Hence mb() here. */ + mb(); } hrtimer_try_to_cancel(&msm_uport->clk_off_timer); if (msm_uport->rx.flush == FLUSH_SHUTDOWN) msm_hs_start_rx_locked(uport); - if (!use_low_power_rx_wakeup(msm_uport)) - set_rfr_locked(uport, 1); if (msm_uport->rx.flush == FLUSH_STOP) msm_uport->rx.flush = FLUSH_IGNORE; msm_uport->clk_state = MSM_HS_CLK_ON; @@ -1322,38 +1509,28 @@ void msm_hs_request_clock_on_locked(struct uart_port *uport) } } -/** - * msm_hs_request_clock_on - Switch the device from partially active low - * power mode to fully active (i.e. clock on) mode. - * @uport: uart_port structure for the device. - * - * This function switches on the input clock, puts UART HW into DMA mode - * and enqueues an Rx DMA command if the device was in partially active - * mode. It has no effect if called with the device in inactive state. - */ -void msm_hs_request_clock_on(struct uart_port *uport) -{ +void msm_hs_request_clock_on(struct uart_port *uport) { unsigned long flags; - spin_lock_irqsave(&uport->lock, flags); msm_hs_request_clock_on_locked(uport); spin_unlock_irqrestore(&uport->lock, flags); } +EXPORT_SYMBOL(msm_hs_request_clock_on); -static irqreturn_t msm_hs_rx_wakeup_isr(int irq, void *dev) +static irqreturn_t msm_hs_wakeup_isr(int irq, void *dev) { unsigned int wakeup = 0; unsigned long flags; - struct msm_hs_port *msm_uport = dev; + struct msm_hs_port *msm_uport = (struct msm_hs_port *)dev; struct uart_port *uport = &msm_uport->uport; struct tty_struct *tty = NULL; spin_lock_irqsave(&uport->lock, flags); - if (msm_uport->clk_state == MSM_HS_CLK_OFF) { - /* ignore the first irq - it is a pending irq that occurred + if (msm_uport->clk_state == MSM_HS_CLK_OFF) { + /* ignore the first irq - it is a pending irq that occured * before enable_irq() */ - if (msm_uport->rx_wakeup.ignore) - msm_uport->rx_wakeup.ignore = 0; + if (msm_uport->wakeup.ignore) + msm_uport->wakeup.ignore = 0; else wakeup = 1; } @@ -1362,23 +1539,24 @@ static irqreturn_t msm_hs_rx_wakeup_isr(int irq, void *dev) /* the uart was clocked off during an rx, wake up and * optionally inject char into tty rx */ msm_hs_request_clock_on_locked(uport); - if (msm_uport->rx_wakeup.inject_rx) { + if (msm_uport->wakeup.inject_rx) { tty = uport->state->port.tty; tty_insert_flip_char(tty, - msm_uport->rx_wakeup.rx_to_inject, + msm_uport->wakeup.rx_to_inject, TTY_NORMAL); - queue_work(msm_hs_workqueue, &msm_uport->rx.tty_work); } } spin_unlock_irqrestore(&uport->lock, flags); + if (wakeup && msm_uport->wakeup.inject_rx) + tty_flip_buffer_push(tty); return IRQ_HANDLED; } static const char *msm_hs_type(struct uart_port *port) { - return (port->type == PORT_MSM) ? "MSM_HS_UART" : NULL; + return ("MSM HS UART"); } /* Called when port is opened */ @@ -1391,7 +1569,6 @@ static int msm_hs_startup(struct uart_port *uport) struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); struct circ_buf *tx_buf = &uport->state->xmit; struct msm_hs_tx *tx = &msm_uport->tx; - struct msm_hs_rx *rx = &msm_uport->rx; rfr_level = uport->fifosize; if (rfr_level > 16) @@ -1400,16 +1577,10 @@ static int msm_hs_startup(struct uart_port *uport) tx->dma_base = dma_map_single(uport->dev, tx_buf->buf, UART_XMIT_SIZE, DMA_TO_DEVICE); - /* do not let tty layer execute RX in global workqueue, use a - * dedicated workqueue managed by this driver */ - uport->state->port.tty->low_latency = 1; - /* turn on uart clk */ - ret = msm_hs_init_clk_locked(uport); - if (unlikely(ret)) { - printk(KERN_ERR "Turning uartclk failed!\n"); - goto err_msm_hs_init_clk; - } + ret = msm_hs_init_clk(uport); + if (unlikely(ret)) + return ret; /* Set auto RFR Level */ data = msm_hs_read(uport, UARTDM_MR1_ADDR); @@ -1449,7 +1620,9 @@ static int msm_hs_startup(struct uart_port *uport) tx->dma_in_flight = 0; tx->xfer.complete_func = msm_hs_dmov_tx_callback; - tx->xfer.execute_func = NULL; + + tx->xfer.crci_mask = msm_dmov_build_crci_mask(1, + msm_uport->dma_tx_crci); tx->command_ptr->cmd = CMD_LC | CMD_DST_CRCI(msm_uport->dma_tx_crci) | CMD_MODE_BOX; @@ -1462,49 +1635,38 @@ static int msm_hs_startup(struct uart_port *uport) tx->command_ptr->dst_row_addr = msm_uport->uport.mapbase + UARTDM_TF_ADDR; - - /* Turn on Uart Receive */ - rx->xfer.complete_func = msm_hs_dmov_rx_callback; - rx->xfer.execute_func = NULL; - - rx->command_ptr->cmd = CMD_LC | - CMD_SRC_CRCI(msm_uport->dma_rx_crci) | CMD_MODE_BOX; - - rx->command_ptr->src_dst_len = (MSM_UARTDM_BURST_SIZE << 16) - | (MSM_UARTDM_BURST_SIZE); - rx->command_ptr->row_offset = MSM_UARTDM_BURST_SIZE; - rx->command_ptr->src_row_addr = uport->mapbase + UARTDM_RF_ADDR; - - msm_uport->imr_reg |= UARTDM_ISR_RXSTALE_BMSK; /* Enable reading the current CTS, no harm even if CTS is ignored */ msm_uport->imr_reg |= UARTDM_ISR_CURRENT_CTS_BMSK; msm_hs_write(uport, UARTDM_TFWR_ADDR, 0); /* TXLEV on empty TX fifo */ + /* + * Complete all device write related configuration before + * queuing RX request. Hence mb() requires here. + */ + mb(); + if (use_low_power_wakeup(msm_uport)) { + ret = irq_set_irq_wake(msm_uport->wakeup.irq, 1); + if (unlikely(ret)) + return ret; + } ret = request_irq(uport->irq, msm_hs_isr, IRQF_TRIGGER_HIGH, "msm_hs_uart", msm_uport); - if (unlikely(ret)) { - printk(KERN_ERR "Request msm_hs_uart IRQ failed!\n"); - goto err_request_irq; - } - if (use_low_power_rx_wakeup(msm_uport)) { - ret = request_irq(msm_uport->rx_wakeup.irq, - msm_hs_rx_wakeup_isr, + if (unlikely(ret)) + return ret; + if (use_low_power_wakeup(msm_uport)) { + ret = request_irq(msm_uport->wakeup.irq, msm_hs_wakeup_isr, IRQF_TRIGGER_FALLING, - "msm_hs_rx_wakeup", msm_uport); - if (unlikely(ret)) { - printk(KERN_ERR "Request msm_hs_rx_wakeup IRQ failed!\n"); - free_irq(uport->irq, msm_uport); - goto err_request_irq; - } - disable_irq(msm_uport->rx_wakeup.irq); + "msm_hs_wakeup", msm_uport); + if (unlikely(ret)) + return ret; + disable_irq(msm_uport->wakeup.irq); } spin_lock_irqsave(&uport->lock, flags); - msm_hs_write(uport, UARTDM_RFWR_ADDR, 0); msm_hs_start_rx_locked(uport); spin_unlock_irqrestore(&uport->lock, flags); @@ -1513,17 +1675,12 @@ static int msm_hs_startup(struct uart_port *uport) dev_err(uport->dev, "set active error:%d\n", ret); pm_runtime_enable(uport->dev); - return 0; -err_request_irq: -err_msm_hs_init_clk: - dma_unmap_single(uport->dev, tx->dma_base, - UART_XMIT_SIZE, DMA_TO_DEVICE); - return ret; + return 0; } /* Initialize tx and rx data structures */ -static int __devinit uartdm_init_port(struct uart_port *uport) +static int uartdm_init_port(struct uart_port *uport) { int ret = 0; struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); @@ -1538,7 +1695,7 @@ static int __devinit uartdm_init_port(struct uart_port *uport) tx->command_ptr_ptr = kmalloc(sizeof(u32 *), GFP_KERNEL | __GFP_DMA); if (!tx->command_ptr_ptr) { ret = -ENOMEM; - goto err_tx_command_ptr_ptr; + goto free_tx_command_ptr; } tx->mapped_cmd_ptr = dma_map_single(uport->dev, tx->command_ptr, @@ -1549,20 +1706,28 @@ static int __devinit uartdm_init_port(struct uart_port *uport) tx->xfer.cmdptr = DMOV_CMD_ADDR(tx->mapped_cmd_ptr_ptr); init_waitqueue_head(&rx->wait); + wake_lock_init(&rx->wake_lock, WAKE_LOCK_SUSPEND, "msm_serial_hs_rx"); + wake_lock_init(&msm_uport->dma_wake_lock, WAKE_LOCK_SUSPEND, + "msm_serial_hs_dma"); + + tasklet_init(&rx->tlet, msm_serial_hs_rx_tlet, + (unsigned long) &rx->tlet); + tasklet_init(&tx->tlet, msm_serial_hs_tx_tlet, + (unsigned long) &tx->tlet); rx->pool = dma_pool_create("rx_buffer_pool", uport->dev, UARTDM_RX_BUF_SIZE, 16, 0); if (!rx->pool) { pr_err("%s(): cannot allocate rx_buffer_pool", __func__); ret = -ENOMEM; - goto err_dma_pool_create; + goto exit_tasket_init; } rx->buffer = dma_pool_alloc(rx->pool, GFP_KERNEL, &rx->rbuffer); if (!rx->buffer) { pr_err("%s(): cannot allocate rx->buffer", __func__); ret = -ENOMEM; - goto err_dma_pool_alloc; + goto free_pool; } /* Allocate the command pointer. Needs to be 64 bit aligned */ @@ -1570,14 +1735,14 @@ static int __devinit uartdm_init_port(struct uart_port *uport) if (!rx->command_ptr) { pr_err("%s(): cannot allocate rx->command_ptr", __func__); ret = -ENOMEM; - goto err_rx_command_ptr; + goto free_rx_buffer; } rx->command_ptr_ptr = kmalloc(sizeof(u32 *), GFP_KERNEL | __GFP_DMA); if (!rx->command_ptr_ptr) { pr_err("%s(): cannot allocate rx->command_ptr_ptr", __func__); ret = -ENOMEM; - goto err_rx_command_ptr_ptr; + goto free_rx_command_ptr; } rx->command_ptr->num_rows = ((UARTDM_RX_BUF_SIZE >> 4) << 16) | @@ -1585,6 +1750,22 @@ static int __devinit uartdm_init_port(struct uart_port *uport) rx->command_ptr->dst_row_addr = rx->rbuffer; + /* Set up Uart Receive */ + msm_hs_write(uport, UARTDM_RFWR_ADDR, 0); + + rx->xfer.complete_func = msm_hs_dmov_rx_callback; + + rx->xfer.crci_mask = msm_dmov_build_crci_mask(1, + msm_uport->dma_rx_crci); + + rx->command_ptr->cmd = CMD_LC | + CMD_SRC_CRCI(msm_uport->dma_rx_crci) | CMD_MODE_BOX; + + rx->command_ptr->src_dst_len = (MSM_UARTDM_BURST_SIZE << 16) + | (MSM_UARTDM_BURST_SIZE); + rx->command_ptr->row_offset = MSM_UARTDM_BURST_SIZE; + rx->command_ptr->src_row_addr = uport->mapbase + UARTDM_RF_ADDR; + rx->mapped_cmd_ptr = dma_map_single(uport->dev, rx->command_ptr, sizeof(dmov_box), DMA_TO_DEVICE); @@ -1594,36 +1775,43 @@ static int __devinit uartdm_init_port(struct uart_port *uport) sizeof(u32 *), DMA_TO_DEVICE); rx->xfer.cmdptr = DMOV_CMD_ADDR(rx->cmdptr_dmaaddr); - INIT_WORK(&rx->tty_work, msm_hs_tty_flip_buffer_work); + INIT_DELAYED_WORK(&rx->flip_insert_work, flip_insert_work); return ret; -err_rx_command_ptr_ptr: +free_rx_command_ptr: kfree(rx->command_ptr); -err_rx_command_ptr: + +free_rx_buffer: dma_pool_free(msm_uport->rx.pool, msm_uport->rx.buffer, - msm_uport->rx.rbuffer); -err_dma_pool_alloc: + msm_uport->rx.rbuffer); + +free_pool: dma_pool_destroy(msm_uport->rx.pool); -err_dma_pool_create: + +exit_tasket_init: + wake_lock_destroy(&msm_uport->rx.wake_lock); + wake_lock_destroy(&msm_uport->dma_wake_lock); + tasklet_kill(&msm_uport->tx.tlet); + tasklet_kill(&msm_uport->rx.tlet); dma_unmap_single(uport->dev, msm_uport->tx.mapped_cmd_ptr_ptr, - sizeof(u32 *), DMA_TO_DEVICE); + sizeof(u32 *), DMA_TO_DEVICE); dma_unmap_single(uport->dev, msm_uport->tx.mapped_cmd_ptr, - sizeof(dmov_box), DMA_TO_DEVICE); + sizeof(dmov_box), DMA_TO_DEVICE); kfree(msm_uport->tx.command_ptr_ptr); -err_tx_command_ptr_ptr: + +free_tx_command_ptr: kfree(msm_uport->tx.command_ptr); return ret; } -static int __devinit msm_hs_probe(struct platform_device *pdev) +static int __init msm_hs_probe(struct platform_device *pdev) { int ret; struct uart_port *uport; struct msm_hs_port *msm_uport; struct resource *resource; - const struct msm_serial_hs_platform_data *pdata = - pdev->dev.platform_data; + struct msm_serial_hs_platform_data *pdata = pdev->dev.platform_data; if (pdev->id < 0 || pdev->id >= UARTDM_NR) { printk(KERN_ERR "Invalid plaform device ID = %d\n", pdev->id); @@ -1638,40 +1826,37 @@ static int __devinit msm_hs_probe(struct platform_device *pdev) resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (unlikely(!resource)) return -ENXIO; + uport->mapbase = resource->start; /* virtual address */ + + uport->membase = ioremap(uport->mapbase, PAGE_SIZE); + if (unlikely(!uport->membase)) + return -ENOMEM; - uport->mapbase = resource->start; uport->irq = platform_get_irq(pdev, 0); if (unlikely(uport->irq < 0)) return -ENXIO; - if (unlikely(irq_set_irq_wake(uport->irq, 1))) - return -ENXIO; - - if (pdata == NULL || pdata->rx_wakeup_irq < 0) - msm_uport->rx_wakeup.irq = -1; + if (pdata == NULL) + msm_uport->wakeup.irq = -1; else { - msm_uport->rx_wakeup.irq = pdata->rx_wakeup_irq; - msm_uport->rx_wakeup.ignore = 1; - msm_uport->rx_wakeup.inject_rx = pdata->inject_rx_on_wakeup; - msm_uport->rx_wakeup.rx_to_inject = pdata->rx_to_inject; + msm_uport->wakeup.irq = pdata->wakeup_irq; + msm_uport->wakeup.ignore = 1; + msm_uport->wakeup.inject_rx = pdata->inject_rx_on_wakeup; + msm_uport->wakeup.rx_to_inject = pdata->rx_to_inject; - if (unlikely(msm_uport->rx_wakeup.irq < 0)) + if (unlikely(msm_uport->wakeup.irq < 0)) return -ENXIO; - if (unlikely(irq_set_irq_wake(msm_uport->rx_wakeup.irq, 1))) - return -ENXIO; + if (pdata->gpio_config) + if (unlikely(pdata->gpio_config(1))) + dev_err(uport->dev, "Cannot configure" + "gpios\n"); } - if (pdata == NULL) - msm_uport->exit_lpm_cb = NULL; - else - msm_uport->exit_lpm_cb = pdata->exit_lpm_cb; - resource = platform_get_resource_byname(pdev, IORESOURCE_DMA, "uartdm_channels"); if (unlikely(!resource)) return -ENXIO; - msm_uport->dma_tx_channel = resource->start; msm_uport->dma_rx_channel = resource->end; @@ -1679,67 +1864,93 @@ static int __devinit msm_hs_probe(struct platform_device *pdev) "uartdm_crci"); if (unlikely(!resource)) return -ENXIO; - msm_uport->dma_tx_crci = resource->start; msm_uport->dma_rx_crci = resource->end; uport->iotype = UPIO_MEM; - uport->fifosize = UART_FIFOSIZE; + uport->fifosize = 64; uport->ops = &msm_hs_ops; uport->flags = UPF_BOOT_AUTOCONF; - uport->uartclk = UARTCLK; + uport->uartclk = 7372800; msm_uport->imr_reg = 0x0; + msm_uport->clk = clk_get(&pdev->dev, "uartdm_clk"); if (IS_ERR(msm_uport->clk)) return PTR_ERR(msm_uport->clk); + msm_uport->pclk = clk_get(&pdev->dev, "uartdm_pclk"); + /* + * Some configurations do not require explicit pclk control so + * do not flag error on pclk get failure. + */ + if (IS_ERR(msm_uport->pclk)) + msm_uport->pclk = NULL; + + ret = clk_set_rate(msm_uport->clk, uport->uartclk); + if (ret) { + printk(KERN_WARNING "Error setting clock rate on UART\n"); + return ret; + } + ret = uartdm_init_port(uport); if (unlikely(ret)) return ret; + /* configure the CR Protection to Enable */ + msm_hs_write(uport, UARTDM_CR_ADDR, CR_PROTECTION_EN); + /* + * Enable Command register protection before going ahead as this hw + * configuration makes sure that issued cmd to CR register gets complete + * before next issued cmd start. Hence mb() requires here. + */ + mb(); + msm_uport->clk_state = MSM_HS_CLK_PORT_OFF; hrtimer_init(&msm_uport->clk_off_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); msm_uport->clk_off_timer.function = msm_hs_clk_off_retry; msm_uport->clk_off_delay = ktime_set(0, 1000000); /* 1ms */ + ret = sysfs_create_file(&pdev->dev.kobj, &dev_attr_clock.attr); + if (unlikely(ret)) + return ret; + + msm_serial_debugfs_init(msm_uport, pdev->id); + uport->line = pdev->id; return uart_add_one_port(&msm_hs_driver, uport); } static int __init msm_serial_hs_init(void) { - int ret, i; + int ret; + int i; /* Init all UARTS as non-configured */ for (i = 0; i < UARTDM_NR; i++) q_uart_port[i].uport.type = PORT_UNKNOWN; - msm_hs_workqueue = create_singlethread_workqueue("msm_serial_hs"); - if (unlikely(!msm_hs_workqueue)) - return -ENOMEM; - ret = uart_register_driver(&msm_hs_driver); if (unlikely(ret)) { - printk(KERN_ERR "%s failed to load\n", __func__); - goto err_uart_register_driver; + printk(KERN_ERR "%s failed to load\n", __FUNCTION__); + return ret; } + debug_base = debugfs_create_dir("msm_serial_hs", NULL); + if (IS_ERR_OR_NULL(debug_base)) + pr_info("msm_serial_hs: Cannot create debugfs dir\n"); - ret = platform_driver_register(&msm_serial_hs_platform_driver); + ret = platform_driver_probe(&msm_serial_hs_platform_driver, + msm_hs_probe); if (ret) { - printk(KERN_ERR "%s failed to load\n", __func__); - goto err_platform_driver_register; + printk(KERN_ERR "%s failed to load\n", __FUNCTION__); + debugfs_remove_recursive(debug_base); + uart_unregister_driver(&msm_hs_driver); + return ret; } - return ret; - -err_platform_driver_register: - uart_unregister_driver(&msm_hs_driver); -err_uart_register_driver: - destroy_workqueue(msm_hs_workqueue); + printk(KERN_INFO "msm_serial_hs module loaded\n"); return ret; } -module_init(msm_serial_hs_init); /* * Called by the upper layer when port is closed. @@ -1752,31 +1963,37 @@ static void msm_hs_shutdown(struct uart_port *uport) struct msm_hs_port *msm_uport = UARTDM_TO_MSM(uport); BUG_ON(msm_uport->rx.flush < FLUSH_STOP); + tasklet_kill(&msm_uport->tx.tlet); + wait_event(msm_uport->rx.wait, msm_uport->rx.flush == FLUSH_SHUTDOWN); + tasklet_kill(&msm_uport->rx.tlet); + cancel_delayed_work_sync(&msm_uport->rx.flip_insert_work); spin_lock_irqsave(&uport->lock, flags); clk_enable(msm_uport->clk); + pm_runtime_disable(uport->dev); + pm_runtime_set_suspended(uport->dev); + /* Disable the transmitter */ msm_hs_write(uport, UARTDM_CR_ADDR, UARTDM_CR_TX_DISABLE_BMSK); /* Disable the receiver */ msm_hs_write(uport, UARTDM_CR_ADDR, UARTDM_CR_RX_DISABLE_BMSK); - pm_runtime_disable(uport->dev); - pm_runtime_set_suspended(uport->dev); - - /* Free the interrupt */ - free_irq(uport->irq, msm_uport); - if (use_low_power_rx_wakeup(msm_uport)) - free_irq(msm_uport->rx_wakeup.irq, msm_uport); - msm_uport->imr_reg = 0; msm_hs_write(uport, UARTDM_IMR_ADDR, msm_uport->imr_reg); - - wait_event(msm_uport->rx.wait, msm_uport->rx.flush == FLUSH_SHUTDOWN); + /* + * Complete all device write before actually disabling uartclk. + * Hence mb() requires here. + */ + mb(); clk_disable(msm_uport->clk); /* to balance local clk_enable() */ - if (msm_uport->clk_state != MSM_HS_CLK_OFF) + if (msm_uport->clk_state != MSM_HS_CLK_OFF) { clk_disable(msm_uport->clk); /* to balance clk_state */ + if (msm_uport->pclk) + clk_disable(msm_uport->pclk); + wake_unlock(&msm_uport->dma_wake_lock); + } msm_uport->clk_state = MSM_HS_CLK_PORT_OFF; dma_unmap_single(uport->dev, msm_uport->tx.dma_base, @@ -1784,20 +2001,22 @@ static void msm_hs_shutdown(struct uart_port *uport) spin_unlock_irqrestore(&uport->lock, flags); - if (cancel_work_sync(&msm_uport->rx.tty_work)) - msm_hs_tty_flip_buffer_work(&msm_uport->rx.tty_work); + if (use_low_power_wakeup(msm_uport)) + irq_set_irq_wake(msm_uport->wakeup.irq, 0); + + /* Free the interrupt */ + free_irq(uport->irq, msm_uport); + if (use_low_power_wakeup(msm_uport)) + free_irq(msm_uport->wakeup.irq, msm_uport); } static void __exit msm_serial_hs_exit(void) { - flush_workqueue(msm_hs_workqueue); - destroy_workqueue(msm_hs_workqueue); + printk(KERN_INFO "msm_serial_hs module removed\n"); platform_driver_unregister(&msm_serial_hs_platform_driver); uart_unregister_driver(&msm_hs_driver); } -module_exit(msm_serial_hs_exit); -#ifdef CONFIG_PM_RUNTIME static int msm_hs_runtime_idle(struct device *dev) { /* @@ -1812,7 +2031,6 @@ static int msm_hs_runtime_resume(struct device *dev) struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct msm_hs_port *msm_uport = &q_uart_port[pdev->id]; - msm_hs_request_clock_on(&msm_uport->uport); return 0; } @@ -1822,15 +2040,9 @@ static int msm_hs_runtime_suspend(struct device *dev) struct platform_device *pdev = container_of(dev, struct platform_device, dev); struct msm_hs_port *msm_uport = &q_uart_port[pdev->id]; - msm_hs_request_clock_off(&msm_uport->uport); return 0; } -#else -#define msm_hs_runtime_idle NULL -#define msm_hs_runtime_resume NULL -#define msm_hs_runtime_suspend NULL -#endif static const struct dev_pm_ops msm_hs_dev_pm_ops = { .runtime_suspend = msm_hs_runtime_suspend, @@ -1839,11 +2051,9 @@ static const struct dev_pm_ops msm_hs_dev_pm_ops = { }; static struct platform_driver msm_serial_hs_platform_driver = { - .probe = msm_hs_probe, - .remove = __devexit_p(msm_hs_remove), + .remove = msm_hs_remove, .driver = { .name = "msm_serial_hs", - .owner = THIS_MODULE, .pm = &msm_hs_dev_pm_ops, }, }; @@ -1868,13 +2078,14 @@ static struct uart_ops msm_hs_ops = { .startup = msm_hs_startup, .shutdown = msm_hs_shutdown, .set_termios = msm_hs_set_termios, - .pm = msm_hs_pm, .type = msm_hs_type, .config_port = msm_hs_config_port, .release_port = msm_hs_release_port, .request_port = msm_hs_request_port, }; +module_init(msm_serial_hs_init); +module_exit(msm_serial_hs_exit); MODULE_DESCRIPTION("High Speed UART Driver for the MSM chipset"); MODULE_VERSION("1.2"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/tty/serial/msm_serial_hs_hwreg.h b/drivers/tty/serial/msm_serial_hs_hwreg.h new file mode 100644 index 00000000000..001d555bcbc --- /dev/null +++ b/drivers/tty/serial/msm_serial_hs_hwreg.h @@ -0,0 +1,179 @@ +/* drivers/serial/msm_serial_hs_hwreg.h + * + * Copyright (c) 2007-2009, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license + * except where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#ifndef MSM_SERIAL_HS_HWREG_H +#define MSM_SERIAL_HS_HWREG_H + +#define GSBI_CONTROL_ADDR 0x0 +#define GSBI_PROTOCOL_CODE_MASK 0x30 +#define GSBI_PROTOCOL_I2C_UART 0x60 +#define GSBI_PROTOCOL_UART 0x40 +#define GSBI_PROTOCOL_IDLE 0x0 + +#define TCSR_ADM_1_A_CRCI_MUX_SEL 0x78 +#define TCSR_ADM_1_B_CRCI_MUX_SEL 0x7C +#define ADM1_CRCI_GSBI6_RX_SEL 0x800 +#define ADM1_CRCI_GSBI6_TX_SEL 0x400 + +#define UARTDM_MR1_ADDR 0x0 +#define UARTDM_MR2_ADDR 0x4 + +/* write only register */ +#define UARTDM_CSR_ADDR 0x8 +#define UARTDM_CSR_115200 0xFF +#define UARTDM_CSR_57600 0xEE +#define UARTDM_CSR_38400 0xDD +#define UARTDM_CSR_28800 0xCC +#define UARTDM_CSR_19200 0xBB +#define UARTDM_CSR_14400 0xAA +#define UARTDM_CSR_9600 0x99 +#define UARTDM_CSR_7200 0x88 +#define UARTDM_CSR_4800 0x77 +#define UARTDM_CSR_3600 0x66 +#define UARTDM_CSR_2400 0x55 +#define UARTDM_CSR_1200 0x44 +#define UARTDM_CSR_600 0x33 +#define UARTDM_CSR_300 0x22 +#define UARTDM_CSR_150 0x11 +#define UARTDM_CSR_75 0x00 + +/* write only register */ +#define UARTDM_TF_ADDR 0x70 +#define UARTDM_TF2_ADDR 0x74 +#define UARTDM_TF3_ADDR 0x78 +#define UARTDM_TF4_ADDR 0x7C + +/* write only register */ +#define UARTDM_CR_ADDR 0x10 +/* write only register */ +#define UARTDM_IMR_ADDR 0x14 + +#define UARTDM_IPR_ADDR 0x18 +#define UARTDM_TFWR_ADDR 0x1c +#define UARTDM_RFWR_ADDR 0x20 +#define UARTDM_HCR_ADDR 0x24 +#define UARTDM_DMRX_ADDR 0x34 +#define UARTDM_IRDA_ADDR 0x38 +#define UARTDM_DMEN_ADDR 0x3c + +/* UART_DM_NO_CHARS_FOR_TX */ +#define UARTDM_NCF_TX_ADDR 0x40 + +#define UARTDM_BADR_ADDR 0x44 + +#define UARTDM_SIM_CFG_ADDR 0x80 + +/* Read Only register */ +#define UARTDM_SR_ADDR 0x8 + +/* Read Only register */ +#define UARTDM_RF_ADDR 0x70 +#define UARTDM_RF2_ADDR 0x74 +#define UARTDM_RF3_ADDR 0x78 +#define UARTDM_RF4_ADDR 0x7C + +/* Read Only register */ +#define UARTDM_MISR_ADDR 0x10 + +/* Read Only register */ +#define UARTDM_ISR_ADDR 0x14 +#define UARTDM_RX_TOTAL_SNAP_ADDR 0x38 + +#define UARTDM_RXFS_ADDR 0x50 + +/* Register field Mask Mapping */ +#define UARTDM_SR_RX_BREAK_BMSK BIT(6) +#define UARTDM_SR_PAR_FRAME_BMSK BIT(5) +#define UARTDM_SR_OVERRUN_BMSK BIT(4) +#define UARTDM_SR_TXEMT_BMSK BIT(3) +#define UARTDM_SR_TXRDY_BMSK BIT(2) +#define UARTDM_SR_RXRDY_BMSK BIT(0) + +#define UARTDM_CR_TX_DISABLE_BMSK BIT(3) +#define UARTDM_CR_RX_DISABLE_BMSK BIT(1) +#define UARTDM_CR_TX_EN_BMSK BIT(2) +#define UARTDM_CR_RX_EN_BMSK BIT(0) + +/* UARTDM_CR channel_comman bit value (register field is bits 8:4) */ +#define RESET_RX 0x10 +#define RESET_TX 0x20 +#define RESET_ERROR_STATUS 0x30 +#define RESET_BREAK_INT 0x40 +#define START_BREAK 0x50 +#define STOP_BREAK 0x60 +#define RESET_CTS 0x70 +#define RESET_STALE_INT 0x80 +#define RFR_LOW 0xD0 +#define RFR_HIGH 0xE0 +#define CR_PROTECTION_EN 0x100 +#define STALE_EVENT_ENABLE 0x500 +#define STALE_EVENT_DISABLE 0x600 +#define FORCE_STALE_EVENT 0x400 +#define CLEAR_TX_READY 0x300 +#define RESET_TX_ERROR 0x800 +#define RESET_TX_DONE 0x810 + +#define UARTDM_MR1_AUTO_RFR_LEVEL1_BMSK 0xffffff00 +#define UARTDM_MR1_AUTO_RFR_LEVEL0_BMSK 0x3f +#define UARTDM_MR1_CTS_CTL_BMSK 0x40 +#define UARTDM_MR1_RX_RDY_CTL_BMSK 0x80 + +#define UARTDM_MR2_LOOP_MODE_BMSK 0x80 +#define UARTDM_MR2_ERROR_MODE_BMSK 0x40 +#define UARTDM_MR2_BITS_PER_CHAR_BMSK 0x30 + +#define UARTDM_MR2_BITS_PER_CHAR_8 (0x3 << 4) + +/* bits per character configuration */ +#define FIVE_BPC (0 << 4) +#define SIX_BPC (1 << 4) +#define SEVEN_BPC (2 << 4) +#define EIGHT_BPC (3 << 4) + +#define UARTDM_MR2_STOP_BIT_LEN_BMSK 0xc +#define STOP_BIT_ONE (1 << 2) +#define STOP_BIT_TWO (3 << 2) + +#define UARTDM_MR2_PARITY_MODE_BMSK 0x3 + +/* Parity configuration */ +#define NO_PARITY 0x0 +#define EVEN_PARITY 0x1 +#define ODD_PARITY 0x2 +#define SPACE_PARITY 0x3 + +#define UARTDM_IPR_STALE_TIMEOUT_MSB_BMSK 0xffffff80 +#define UARTDM_IPR_STALE_LSB_BMSK 0x1f + +/* These can be used for both ISR and IMR register */ +#define UARTDM_ISR_TX_READY_BMSK BIT(7) +#define UARTDM_ISR_CURRENT_CTS_BMSK BIT(6) +#define UARTDM_ISR_DELTA_CTS_BMSK BIT(5) +#define UARTDM_ISR_RXLEV_BMSK BIT(4) +#define UARTDM_ISR_RXSTALE_BMSK BIT(3) +#define UARTDM_ISR_RXBREAK_BMSK BIT(2) +#define UARTDM_ISR_RXHUNT_BMSK BIT(1) +#define UARTDM_ISR_TXLEV_BMSK BIT(0) + +/* Field definitions for UART_DM_DMEN*/ +#define UARTDM_TX_DM_EN_BMSK 0x1 +#define UARTDM_RX_DM_EN_BMSK 0x2 + +#endif /* MSM_SERIAL_HS_HWREG_H */ diff --git a/drivers/tty/serial/msm_serial_hs_lite.c b/drivers/tty/serial/msm_serial_hs_lite.c new file mode 100644 index 00000000000..83b734e50e3 --- /dev/null +++ b/drivers/tty/serial/msm_serial_hs_lite.c @@ -0,0 +1,1216 @@ +/* + * drivers/serial/msm_serial.c - driver for msm7k serial device and console + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2010-2011, Code Aurora Forum. 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. + */ + +/* Acknowledgements: + * This file is based on msm_serial.c, originally + * Written by Robert Love */ + +#if defined(CONFIG_SERIAL_MSM_HSL_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_serial_hs_hwreg.h" + +struct msm_hsl_port { + struct uart_port uart; + char name[16]; + struct clk *clk; + struct clk *pclk; + unsigned int imr; + unsigned int *uart_csr_code; + unsigned int *gsbi_mapbase; + unsigned int *mapped_gsbi; + int is_uartdm; + unsigned int old_snap_state; +}; + +#define UART_TO_MSM(uart_port) ((struct msm_hsl_port *) uart_port) +#define is_console(port) ((port)->cons && \ + (port)->cons->index == (port)->line) +static inline void wait_for_xmitr(struct uart_port *port, int bits); +static inline void msm_hsl_write(struct uart_port *port, + unsigned int val, unsigned int off) +{ + iowrite32(val, port->membase + off); +} +static inline unsigned int msm_hsl_read(struct uart_port *port, + unsigned int off) +{ + return ioread32(port->membase + off); +} + +static unsigned int msm_serial_hsl_has_gsbi(struct uart_port *port) +{ + return UART_TO_MSM(port)->is_uartdm; +} + +static int clk_en(struct uart_port *port, int enable) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + int ret = 0; + + if (enable) { + + ret = clk_enable(msm_hsl_port->clk); + if (ret) + goto err; + if (msm_hsl_port->pclk) { + ret = clk_enable(msm_hsl_port->pclk); + if (ret) { + clk_disable(msm_hsl_port->clk); + goto err; + } + } + } else { + clk_disable(msm_hsl_port->clk); + if (msm_hsl_port->pclk) + clk_disable(msm_hsl_port->pclk); + } +err: + return ret; +} + +static void msm_hsl_stop_tx(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + clk_en(port, 1); + + msm_hsl_port->imr &= ~UARTDM_ISR_TXLEV_BMSK; + msm_hsl_write(port, msm_hsl_port->imr, UARTDM_IMR_ADDR); + + clk_en(port, 0); +} + +static void msm_hsl_start_tx(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + clk_en(port, 1); + + msm_hsl_port->imr |= UARTDM_ISR_TXLEV_BMSK; + msm_hsl_write(port, msm_hsl_port->imr, UARTDM_IMR_ADDR); + + clk_en(port, 0); +} + +static void msm_hsl_stop_rx(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + clk_en(port, 1); + + msm_hsl_port->imr &= ~(UARTDM_ISR_RXLEV_BMSK | + UARTDM_ISR_RXSTALE_BMSK); + msm_hsl_write(port, msm_hsl_port->imr, UARTDM_IMR_ADDR); + + clk_en(port, 0); +} + +static void msm_hsl_enable_ms(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + clk_en(port, 1); + + msm_hsl_port->imr |= UARTDM_ISR_DELTA_CTS_BMSK; + msm_hsl_write(port, msm_hsl_port->imr, UARTDM_IMR_ADDR); + + clk_en(port, 0); +} + +static void handle_rx(struct uart_port *port, unsigned int misr) +{ + struct tty_struct *tty = port->state->port.tty; + unsigned int sr; + int count = 0; + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + /* + * Handle overrun. My understanding of the hardware is that overrun + * is not tied to the RX buffer, so we handle the case out of band. + */ + if ((msm_hsl_read(port, UARTDM_SR_ADDR) & UARTDM_SR_OVERRUN_BMSK)) { + port->icount.overrun++; + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + msm_hsl_write(port, RESET_ERROR_STATUS, UARTDM_CR_ADDR); + } + + if (misr & UARTDM_ISR_RXSTALE_BMSK) { + count = msm_hsl_read(port, UARTDM_RX_TOTAL_SNAP_ADDR) - + msm_hsl_port->old_snap_state; + msm_hsl_port->old_snap_state = 0; + } else { + count = 4 * (msm_hsl_read(port, UARTDM_RFWR_ADDR)); + msm_hsl_port->old_snap_state += count; + } + + /* and now the main RX loop */ + while (count > 0) { + unsigned int c; + char flag = TTY_NORMAL; + + sr = msm_hsl_read(port, UARTDM_SR_ADDR); + if ((sr & + UARTDM_SR_RXRDY_BMSK) == 0) { + msm_hsl_port->old_snap_state -= count; + break; + } + c = msm_hsl_read(port, UARTDM_RF_ADDR); + if (sr & UARTDM_SR_RX_BREAK_BMSK) { + port->icount.brk++; + if (uart_handle_break(port)) + continue; + } else if (sr & UARTDM_SR_PAR_FRAME_BMSK) { + port->icount.frame++; + } else { + port->icount.rx++; + } + + /* Mask conditions we're ignorning. */ + sr &= port->read_status_mask; + if (sr & UARTDM_SR_RX_BREAK_BMSK) + flag = TTY_BREAK; + else if (sr & UARTDM_SR_PAR_FRAME_BMSK) + flag = TTY_FRAME; + + /* TODO: handle sysrq */ + /* if (!uart_handle_sysrq_char(port, c)) */ + tty_insert_flip_string(tty, (char *) &c, + (count > 4) ? 4 : count); + count -= 4; + } + + tty_flip_buffer_push(tty); +} + +static void handle_tx(struct uart_port *port) +{ + struct circ_buf *xmit = &port->state->xmit; + int sent_tx; + int tx_count; + int x; + unsigned int tf_pointer = 0; + + tx_count = uart_circ_chars_pending(xmit); + + if (tx_count > (UART_XMIT_SIZE - xmit->tail)) + tx_count = UART_XMIT_SIZE - xmit->tail; + if (tx_count >= port->fifosize) + tx_count = port->fifosize; + + /* Handle x_char */ + if (port->x_char) { + wait_for_xmitr(port, UARTDM_ISR_TX_READY_BMSK); + msm_hsl_write(port, tx_count + 1, UARTDM_NCF_TX_ADDR); + msm_hsl_write(port, port->x_char, UARTDM_TF_ADDR); + port->icount.tx++; + port->x_char = 0; + } else if (tx_count) { + wait_for_xmitr(port, UARTDM_ISR_TX_READY_BMSK); + msm_hsl_write(port, tx_count, UARTDM_NCF_TX_ADDR); + } + if (!tx_count) { + msm_hsl_stop_tx(port); + return; + } + + while (tf_pointer < tx_count) { + if (unlikely(!(msm_hsl_read(port, UARTDM_SR_ADDR) & + UARTDM_SR_TXRDY_BMSK))) + continue; + switch (tx_count - tf_pointer) { + case 1: { + x = xmit->buf[xmit->tail]; + port->icount.tx++; + break; + } + case 2: { + x = xmit->buf[xmit->tail] + | xmit->buf[xmit->tail+1] << 8; + port->icount.tx += 2; + break; + } + case 3: { + x = xmit->buf[xmit->tail] + | xmit->buf[xmit->tail+1] << 8 + | xmit->buf[xmit->tail + 2] << 16; + port->icount.tx += 3; + break; + } + default: { + x = *((int *)&(xmit->buf[xmit->tail])); + port->icount.tx += 4; + break; + } + } + msm_hsl_write(port, x, UARTDM_TF_ADDR); + xmit->tail = ((tx_count - tf_pointer < 4) ? + (tx_count - tf_pointer + xmit->tail) : + (xmit->tail + 4)) & (UART_XMIT_SIZE - 1); + tf_pointer += 4; + sent_tx = 1; + } + + if (uart_circ_empty(xmit)) + msm_hsl_stop_tx(port); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + +} + +static void handle_delta_cts(struct uart_port *port) +{ + msm_hsl_write(port, RESET_CTS, UARTDM_CR_ADDR); + port->icount.cts++; + wake_up_interruptible(&port->state->port.delta_msr_wait); +} + +static irqreturn_t msm_hsl_irq(int irq, void *dev_id) +{ + struct uart_port *port = dev_id; + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + unsigned int misr; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + clk_en(port, 1); + misr = msm_hsl_read(port, UARTDM_MISR_ADDR); + msm_hsl_write(port, 0, UARTDM_IMR_ADDR); /* disable interrupt */ + + if (misr & (UARTDM_ISR_RXSTALE_BMSK | UARTDM_ISR_RXLEV_BMSK)) { + handle_rx(port, misr); + if (misr & (UARTDM_ISR_RXSTALE_BMSK)) + msm_hsl_write(port, RESET_STALE_INT, UARTDM_CR_ADDR); + msm_hsl_write(port, 6500, UARTDM_DMRX_ADDR); + msm_hsl_write(port, STALE_EVENT_ENABLE, UARTDM_CR_ADDR); + } + if (misr & UARTDM_ISR_TXLEV_BMSK) + handle_tx(port); + + if (misr & UARTDM_ISR_DELTA_CTS_BMSK) + handle_delta_cts(port); + + /* restore interrupt */ + msm_hsl_write(port, msm_hsl_port->imr, UARTDM_IMR_ADDR); + clk_en(port, 0); + spin_unlock_irqrestore(&port->lock, flags); + + return IRQ_HANDLED; +} + +static unsigned int msm_hsl_tx_empty(struct uart_port *port) +{ + unsigned int ret; + + clk_en(port, 1); + ret = (msm_hsl_read(port, UARTDM_SR_ADDR) & + UARTDM_SR_TXEMT_BMSK) ? TIOCSER_TEMT : 0; + clk_en(port, 0); + + return ret; +} + +static void msm_hsl_reset(struct uart_port *port) +{ + /* reset everything */ + msm_hsl_write(port, RESET_RX, UARTDM_CR_ADDR); + msm_hsl_write(port, RESET_TX, UARTDM_CR_ADDR); + msm_hsl_write(port, RESET_ERROR_STATUS, UARTDM_CR_ADDR); + msm_hsl_write(port, RESET_BREAK_INT, UARTDM_CR_ADDR); + msm_hsl_write(port, RESET_CTS, UARTDM_CR_ADDR); + msm_hsl_write(port, RFR_LOW, UARTDM_CR_ADDR); +} + +static unsigned int msm_hsl_get_mctrl(struct uart_port *port) +{ + return TIOCM_CAR | TIOCM_CTS | TIOCM_DSR | TIOCM_RTS; +} + +static void msm_hsl_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + unsigned int mr; + unsigned int loop_mode; + + clk_en(port, 1); + + mr = msm_hsl_read(port, UARTDM_MR1_ADDR); + + if (!(mctrl & TIOCM_RTS)) { + mr &= ~UARTDM_MR1_RX_RDY_CTL_BMSK; + msm_hsl_write(port, mr, UARTDM_MR1_ADDR); + msm_hsl_write(port, RFR_HIGH, UARTDM_CR_ADDR); + } else { + mr |= UARTDM_MR1_RX_RDY_CTL_BMSK; + msm_hsl_write(port, mr, UARTDM_MR1_ADDR); + } + + loop_mode = TIOCM_LOOP & mctrl; + if (loop_mode) { + mr = msm_hsl_read(port, UARTDM_MR2_ADDR); + mr |= UARTDM_MR2_LOOP_MODE_BMSK; + msm_hsl_write(port, mr, UARTDM_MR2_ADDR); + + /* Reset TX */ + msm_hsl_reset(port); + + /* Turn on Uart Receiver & Transmitter*/ + msm_hsl_write(port, UARTDM_CR_RX_EN_BMSK + | UARTDM_CR_TX_EN_BMSK, UARTDM_CR_ADDR); + } + + clk_en(port, 0); +} + +static void msm_hsl_break_ctl(struct uart_port *port, int break_ctl) +{ + clk_en(port, 1); + + if (break_ctl) + msm_hsl_write(port, START_BREAK, UARTDM_CR_ADDR); + else + msm_hsl_write(port, STOP_BREAK, UARTDM_CR_ADDR); + + clk_en(port, 0); +} + +static void msm_hsl_set_baud_rate(struct uart_port *port, unsigned int baud) +{ + unsigned int baud_code, rxstale, watermark; + + switch (baud) { + case 300: + baud_code = UARTDM_CSR_300; + rxstale = 1; + break; + case 600: + baud_code = UARTDM_CSR_600; + rxstale = 1; + break; + case 1200: + baud_code = UARTDM_CSR_1200; + rxstale = 1; + break; + case 2400: + baud_code = UARTDM_CSR_2400; + rxstale = 1; + break; + case 4800: + baud_code = UARTDM_CSR_4800; + rxstale = 1; + break; + case 9600: + baud_code = UARTDM_CSR_9600; + rxstale = 2; + break; + case 14400: + baud_code = UARTDM_CSR_14400; + rxstale = 3; + break; + case 19200: + baud_code = UARTDM_CSR_19200; + rxstale = 4; + break; + case 28800: + baud_code = UARTDM_CSR_28800; + rxstale = 6; + break; + case 38400: + baud_code = UARTDM_CSR_38400; + rxstale = 8; + break; + case 57600: + baud_code = UARTDM_CSR_57600; + rxstale = 16; + break; + case 115200: + default: + baud_code = UARTDM_CSR_115200; + rxstale = 31; + break; + } + + msm_hsl_write(port, RESET_RX, UARTDM_CR_ADDR); + msm_hsl_write(port, baud_code, UARTDM_CSR_ADDR); + + /* RX stale watermark */ + watermark = UARTDM_IPR_STALE_LSB_BMSK & rxstale; + watermark |= UARTDM_IPR_STALE_TIMEOUT_MSB_BMSK & (rxstale << 2); + msm_hsl_write(port, watermark, UARTDM_IPR_ADDR); + + /* set RX watermark */ + watermark = (port->fifosize * 3) / 4; + msm_hsl_write(port, watermark, UARTDM_RFWR_ADDR); + + /* set TX watermark */ + msm_hsl_write(port, 0, UARTDM_TFWR_ADDR); + + msm_hsl_write(port, RESET_STALE_INT, UARTDM_CR_ADDR); + msm_hsl_write(port, 6500, UARTDM_DMRX_ADDR); + msm_hsl_write(port, STALE_EVENT_ENABLE, UARTDM_CR_ADDR); +} + +static void msm_hsl_init_clock(struct uart_port *port) +{ + clk_en(port, 1); +} + +static void msm_hsl_deinit_clock(struct uart_port *port) +{ + clk_en(port, 0); +} + +static int msm_hsl_startup(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + struct platform_device *pdev = to_platform_device(port->dev); + const struct msm_serial_hslite_platform_data *pdata = + pdev->dev.platform_data; + unsigned int data, rfr_level; + int ret; + unsigned long flags; + + snprintf(msm_hsl_port->name, sizeof(msm_hsl_port->name), + "msm_serial_hsl%d", port->line); + + if (!(is_console(port)) || (!port->cons) || + (port->cons && (!(port->cons->flags & CON_ENABLED)))) { + + if (msm_serial_hsl_has_gsbi(port)) + if ((ioread32(msm_hsl_port->mapped_gsbi + + GSBI_CONTROL_ADDR) & GSBI_PROTOCOL_I2C_UART) + != GSBI_PROTOCOL_I2C_UART) + iowrite32(GSBI_PROTOCOL_I2C_UART, + msm_hsl_port->mapped_gsbi + + GSBI_CONTROL_ADDR); + + if (pdata && pdata->config_gpio) { + ret = gpio_request(pdata->uart_tx_gpio, + "UART_TX_GPIO"); + if (unlikely(ret)) { + pr_err("%s: gpio request failed for:%d\n", + __func__, pdata->uart_tx_gpio); + return ret; + } + + ret = gpio_request(pdata->uart_rx_gpio, "UART_RX_GPIO"); + if (unlikely(ret)) { + pr_err("%s: gpio request failed for:%d\n", + __func__, pdata->uart_rx_gpio); + gpio_free(pdata->uart_tx_gpio); + return ret; + } + } + } +#ifndef CONFIG_PM_RUNTIME + msm_hsl_init_clock(port); +#endif + pm_runtime_get_sync(port->dev); + + if (likely(port->fifosize > 12)) + rfr_level = port->fifosize - 12; + else + rfr_level = port->fifosize; + + spin_lock_irqsave(&port->lock, flags); + + /* set automatic RFR level */ + data = msm_hsl_read(port, UARTDM_MR1_ADDR); + data &= ~UARTDM_MR1_AUTO_RFR_LEVEL1_BMSK; + data &= ~UARTDM_MR1_AUTO_RFR_LEVEL0_BMSK; + data |= UARTDM_MR1_AUTO_RFR_LEVEL1_BMSK & (rfr_level << 2); + data |= UARTDM_MR1_AUTO_RFR_LEVEL0_BMSK & rfr_level; + msm_hsl_write(port, data, UARTDM_MR1_ADDR); + + + /* Make sure IPR is not 0 to start with*/ + msm_hsl_write(port, UARTDM_IPR_STALE_LSB_BMSK, UARTDM_IPR_ADDR); + data = 0; + + if (!(is_console(port)) || (!port->cons) || + (port->cons && (!(port->cons->flags & CON_ENABLED)))) { + msm_hsl_write(port, CR_PROTECTION_EN, UARTDM_CR_ADDR); + msm_hsl_write(port, UARTDM_MR2_BITS_PER_CHAR_8 | STOP_BIT_ONE, + UARTDM_MR2_ADDR); /* 8N1 */ + msm_hsl_reset(port); + data = UARTDM_CR_TX_EN_BMSK; + } + + data |= UARTDM_CR_RX_EN_BMSK; + + msm_hsl_write(port, data, UARTDM_CR_ADDR); /* enable TX & RX */ + + /* turn on RX and CTS interrupts */ + msm_hsl_port->imr = UARTDM_ISR_RXSTALE_BMSK + | UARTDM_ISR_DELTA_CTS_BMSK | UARTDM_ISR_RXLEV_BMSK; + + spin_unlock_irqrestore(&port->lock, flags); + + ret = request_irq(port->irq, msm_hsl_irq, IRQF_TRIGGER_HIGH, + msm_hsl_port->name, port); + if (unlikely(ret)) { + printk(KERN_ERR "%s: failed to request_irq\n", __func__); + return ret; + } + + spin_lock_irqsave(&port->lock, flags); + msm_hsl_write(port, RESET_STALE_INT, UARTDM_CR_ADDR); + msm_hsl_write(port, 6500, UARTDM_DMRX_ADDR); + msm_hsl_write(port, STALE_EVENT_ENABLE, UARTDM_CR_ADDR); + msm_hsl_write(port, msm_hsl_port->imr, UARTDM_IMR_ADDR); + spin_unlock_irqrestore(&port->lock, flags); + + return 0; +} + +static void msm_hsl_shutdown(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + struct platform_device *pdev = to_platform_device(port->dev); + const struct msm_serial_hslite_platform_data *pdata = + pdev->dev.platform_data; + + clk_en(port, 1); + + msm_hsl_port->imr = 0; + msm_hsl_write(port, 0, UARTDM_IMR_ADDR); /* disable interrupts */ + + clk_en(port, 0); + + free_irq(port->irq, port); + +#ifndef CONFIG_PM_RUNTIME + msm_hsl_deinit_clock(port); +#endif + pm_runtime_put_sync(port->dev); + if (!(is_console(port)) || (!port->cons) || + (port->cons && (!(port->cons->flags & CON_ENABLED)))) { + if (pdata && pdata->config_gpio) { + gpio_free(pdata->uart_tx_gpio); + gpio_free(pdata->uart_rx_gpio); + } + } +} + +static void msm_hsl_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned long flags; + unsigned int baud, mr; + + spin_lock_irqsave(&port->lock, flags); + clk_en(port, 1); + + /* calculate and set baud rate */ + baud = uart_get_baud_rate(port, termios, old, 300, 115200); + + msm_hsl_set_baud_rate(port, baud); + + /* calculate parity */ + mr = msm_hsl_read(port, UARTDM_MR2_ADDR); + mr &= ~UARTDM_MR2_PARITY_MODE_BMSK; + if (termios->c_cflag & PARENB) { + if (termios->c_cflag & PARODD) + mr |= ODD_PARITY; + else if (termios->c_cflag & CMSPAR) + mr |= SPACE_PARITY; + else + mr |= EVEN_PARITY; + } + + /* calculate bits per char */ + mr &= ~UARTDM_MR2_BITS_PER_CHAR_BMSK; + switch (termios->c_cflag & CSIZE) { + case CS5: + mr |= FIVE_BPC; + break; + case CS6: + mr |= SIX_BPC; + break; + case CS7: + mr |= SEVEN_BPC; + break; + case CS8: + default: + mr |= EIGHT_BPC; + break; + } + + /* calculate stop bits */ + mr &= ~(STOP_BIT_ONE | STOP_BIT_TWO); + if (termios->c_cflag & CSTOPB) + mr |= STOP_BIT_TWO; + else + mr |= STOP_BIT_ONE; + + /* set parity, bits per char, and stop bit */ + msm_hsl_write(port, mr, UARTDM_MR2_ADDR); + + /* calculate and set hardware flow control */ + mr = msm_hsl_read(port, UARTDM_MR1_ADDR); + mr &= ~(UARTDM_MR1_CTS_CTL_BMSK | UARTDM_MR1_RX_RDY_CTL_BMSK); + if (termios->c_cflag & CRTSCTS) { + mr |= UARTDM_MR1_CTS_CTL_BMSK; + mr |= UARTDM_MR1_RX_RDY_CTL_BMSK; + } + msm_hsl_write(port, mr, UARTDM_MR1_ADDR); + + /* Configure status bits to ignore based on termio flags. */ + port->read_status_mask = 0; + if (termios->c_iflag & INPCK) + port->read_status_mask |= UARTDM_SR_PAR_FRAME_BMSK; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= UARTDM_SR_RX_BREAK_BMSK; + + uart_update_timeout(port, termios->c_cflag, baud); + + clk_en(port, 0); + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *msm_hsl_type(struct uart_port *port) +{ + return "MSM"; +} + +static void msm_hsl_release_port(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + struct platform_device *pdev = to_platform_device(port->dev); + struct resource *uart_resource; + struct resource *gsbi_resource; + resource_size_t size; + + uart_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "uartdm_resource"); + if (unlikely(!uart_resource)) + return; + size = uart_resource->end - uart_resource->start + 1; + + release_mem_region(port->mapbase, size); + iounmap(port->membase); + port->membase = NULL; + + if (msm_serial_hsl_has_gsbi(port)) { + iowrite32(GSBI_PROTOCOL_IDLE, msm_hsl_port->mapped_gsbi + + GSBI_CONTROL_ADDR); + gsbi_resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "gsbi_resource"); + + size = gsbi_resource->end - gsbi_resource->start + 1; + iounmap(msm_hsl_port->mapped_gsbi); + msm_hsl_port->mapped_gsbi = NULL; + } +} + +static int msm_hsl_request_port(struct uart_port *port) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + struct platform_device *pdev = to_platform_device(port->dev); + struct resource *uart_resource; + struct resource *gsbi_resource; + resource_size_t size; + + uart_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "uartdm_resource"); + if (unlikely(!uart_resource)) { + pr_err("%s: can't get uartdm resource\n", __func__); + return -ENXIO; + } + size = uart_resource->end - uart_resource->start + 1; + + if (unlikely(!request_mem_region(port->mapbase, size, + "msm_serial_hsl"))) { + pr_err("%s: can't get mem region for uartdm\n", __func__); + return -EBUSY; + } + + port->membase = ioremap(port->mapbase, size); + if (!port->membase) { + release_mem_region(port->mapbase, size); + return -EBUSY; + } + + if (msm_serial_hsl_has_gsbi(port)) { + gsbi_resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "gsbi_resource"); + if (unlikely(!gsbi_resource)) { + pr_err("%s: can't get gsbi resource\n", __func__); + return -ENXIO; + } + + size = gsbi_resource->end - gsbi_resource->start + 1; + msm_hsl_port->mapped_gsbi = ioremap(gsbi_resource->start, + size); + if (!msm_hsl_port->mapped_gsbi) { + return -EBUSY; + } + } + + return 0; +} + +static void msm_hsl_config_port(struct uart_port *port, int flags) +{ + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + if (flags & UART_CONFIG_TYPE) { + port->type = PORT_MSM; + if (msm_hsl_request_port(port)) + return; + } + if (msm_serial_hsl_has_gsbi(port)) + if ((ioread32(msm_hsl_port->mapped_gsbi + GSBI_CONTROL_ADDR) & + GSBI_PROTOCOL_I2C_UART) != GSBI_PROTOCOL_I2C_UART) + iowrite32(GSBI_PROTOCOL_I2C_UART, + msm_hsl_port->mapped_gsbi + GSBI_CONTROL_ADDR); +} + +static int msm_hsl_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if (unlikely(ser->type != PORT_UNKNOWN && ser->type != PORT_MSM)) + return -EINVAL; + if (unlikely(port->irq != ser->irq)) + return -EINVAL; + return 0; +} + +static void msm_hsl_power(struct uart_port *port, unsigned int state, + unsigned int oldstate) +{ + int ret; + struct msm_hsl_port *msm_hsl_port = UART_TO_MSM(port); + + switch (state) { + case 0: + ret = clk_set_rate(msm_hsl_port->clk, 1843200); + if (ret) + pr_err("%s(): Error setting UART clock rate\n", + __func__); + clk_en(port, 1); + break; + case 3: + clk_en(port, 0); + break; + default: + pr_err("%s(): msm_serial_hsl: Unknown PM state %d\n", + __func__, state); + } +} + +static struct uart_ops msm_hsl_uart_pops = { + .tx_empty = msm_hsl_tx_empty, + .set_mctrl = msm_hsl_set_mctrl, + .get_mctrl = msm_hsl_get_mctrl, + .stop_tx = msm_hsl_stop_tx, + .start_tx = msm_hsl_start_tx, + .stop_rx = msm_hsl_stop_rx, + .enable_ms = msm_hsl_enable_ms, + .break_ctl = msm_hsl_break_ctl, + .startup = msm_hsl_startup, + .shutdown = msm_hsl_shutdown, + .set_termios = msm_hsl_set_termios, + .type = msm_hsl_type, + .release_port = msm_hsl_release_port, + .request_port = msm_hsl_request_port, + .config_port = msm_hsl_config_port, + .verify_port = msm_hsl_verify_port, + .pm = msm_hsl_power, +}; + +static struct msm_hsl_port msm_hsl_uart_ports[] = { + { + .uart = { + .iotype = UPIO_MEM, + .ops = &msm_hsl_uart_pops, + .flags = UPF_BOOT_AUTOCONF, + .fifosize = 64, + .line = 0, + }, + }, + { + .uart = { + .iotype = UPIO_MEM, + .ops = &msm_hsl_uart_pops, + .flags = UPF_BOOT_AUTOCONF, + .fifosize = 64, + .line = 1, + }, + }, + { + .uart = { + .iotype = UPIO_MEM, + .ops = &msm_hsl_uart_pops, + .flags = UPF_BOOT_AUTOCONF, + .fifosize = 64, + .line = 2, + }, + }, +}; + +#define UART_NR ARRAY_SIZE(msm_hsl_uart_ports) + +static inline struct uart_port *get_port_from_line(unsigned int line) +{ + return &msm_hsl_uart_ports[line].uart; +} + +/* + * Wait for transmitter & holding register to empty + * Derived from wait_for_xmitr in 8250 serial driver by Russell King */ +void wait_for_xmitr(struct uart_port *port, int bits) +{ + if (!(msm_hsl_read(port, UARTDM_SR_ADDR) & UARTDM_SR_TXEMT_BMSK)) { + while ((msm_hsl_read(port, UARTDM_ISR_ADDR) & bits) != bits) { + udelay(1); + touch_nmi_watchdog(); + cpu_relax(); + } + msm_hsl_write(port, CLEAR_TX_READY, UARTDM_CR_ADDR); + } +} + +#ifdef CONFIG_SERIAL_MSM_HSL_CONSOLE +static void msm_hsl_console_putchar(struct uart_port *port, int ch) +{ + wait_for_xmitr(port, UARTDM_ISR_TX_READY_BMSK); + msm_hsl_write(port, 1, UARTDM_NCF_TX_ADDR); + + while (!(msm_hsl_read(port, UARTDM_SR_ADDR) & UARTDM_SR_TXRDY_BMSK)) { + udelay(1); + touch_nmi_watchdog(); + } + + msm_hsl_write(port, ch, UARTDM_TF_ADDR); +} + +static void msm_hsl_console_write(struct console *co, const char *s, + unsigned int count) +{ + struct uart_port *port; + struct msm_hsl_port *msm_hsl_port; + int locked; + + BUG_ON(co->index < 0 || co->index >= UART_NR); + + port = get_port_from_line(co->index); + msm_hsl_port = UART_TO_MSM(port); + + /* not pretty, but we can end up here via various convoluted paths */ + if (port->sysrq || oops_in_progress) + locked = spin_trylock(&port->lock); + else { + locked = 1; + spin_lock(&port->lock); + } + msm_hsl_write(port, 0, UARTDM_IMR_ADDR); + uart_console_write(port, s, count, msm_hsl_console_putchar); + msm_hsl_write(port, msm_hsl_port->imr, UARTDM_IMR_ADDR); + if (locked == 1) + spin_unlock(&port->lock); +} + +static int __init msm_hsl_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + int baud, flow, bits, parity; + int ret; + + if (unlikely(co->index >= UART_NR || co->index < 0)) + return -ENXIO; + + port = get_port_from_line(co->index); + + if (unlikely(!port->membase)) + return -ENXIO; + + port->cons = co; + + pm_runtime_get_noresume(port->dev); + +#ifndef CONFIG_PM_RUNTIME + msm_hsl_init_clock(port); +#endif + pm_runtime_resume(port->dev); + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + bits = 8; + parity = 'n'; + flow = 'n'; + msm_hsl_write(port, UARTDM_MR2_BITS_PER_CHAR_8 | STOP_BIT_ONE, + UARTDM_MR2_ADDR); /* 8N1 */ + + if (baud < 300 || baud > 115200) + baud = 115200; + msm_hsl_set_baud_rate(port, baud); + + ret = uart_set_options(port, co, baud, parity, bits, flow); + msm_hsl_reset(port); + /* Enable transmitter */ + msm_hsl_write(port, CR_PROTECTION_EN, UARTDM_CR_ADDR); + msm_hsl_write(port, UARTDM_CR_TX_EN_BMSK, UARTDM_CR_ADDR); + + printk(KERN_INFO "msm_serial_hsl: console setup on port #%d\n", + port->line); + + return ret; +} + +static struct uart_driver msm_hsl_uart_driver; + +static struct console msm_hsl_console = { + .name = "ttyHSL", + .write = msm_hsl_console_write, + .device = uart_console_device, + .setup = msm_hsl_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &msm_hsl_uart_driver, +}; + +#define MSM_HSL_CONSOLE (&msm_hsl_console) + +#else +#define MSM_HSL_CONSOLE NULL +#endif + +static struct uart_driver msm_hsl_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "msm_serial_hsl", + .dev_name = "ttyHSL", + .nr = UART_NR, + .cons = MSM_HSL_CONSOLE, +}; + +static int __devinit msm_serial_hsl_probe(struct platform_device *pdev) +{ + struct msm_hsl_port *msm_hsl_port; + struct resource *uart_resource; + struct resource *gsbi_resource; + struct uart_port *port; + int ret; + + if (unlikely(pdev->id < 0 || pdev->id >= UART_NR)) + return -ENXIO; + + printk(KERN_INFO "msm_serial_hsl: detected port #%d\n", pdev->id); + + port = get_port_from_line(pdev->id); + port->dev = &pdev->dev; + msm_hsl_port = UART_TO_MSM(port); + + gsbi_resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "gsbi_resource"); + if (gsbi_resource) { + msm_hsl_port->is_uartdm = 1; + msm_hsl_port->clk = clk_get(&pdev->dev, "gsbi_uart_clk"); + msm_hsl_port->pclk = clk_get(&pdev->dev, "gsbi_pclk"); + } else { + msm_hsl_port->is_uartdm = 0; + msm_hsl_port->clk = clk_get(&pdev->dev, "uartdm_clk"); + msm_hsl_port->pclk = NULL; + } + + if (unlikely(IS_ERR(msm_hsl_port->clk))) { + printk(KERN_ERR "%s: Error getting clk\n", __func__); + return PTR_ERR(msm_hsl_port->clk); + } + if (unlikely(IS_ERR(msm_hsl_port->pclk))) { + printk(KERN_ERR "%s: Error getting pclk\n", __func__); + return PTR_ERR(msm_hsl_port->pclk); + } + + + uart_resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "uartdm_resource"); + if (unlikely(!uart_resource)) { + printk(KERN_ERR "getting uartdm_resource failed\n"); + return -ENXIO; + } + port->mapbase = uart_resource->start; + + port->irq = platform_get_irq(pdev, 0); + if (unlikely(port->irq < 0)) { + printk(KERN_ERR "%s: getting irq failed\n", __func__); + return -ENXIO; + } + + device_set_wakeup_capable(&pdev->dev, 1); + platform_set_drvdata(pdev, port); + pm_runtime_enable(port->dev); + ret = uart_add_one_port(&msm_hsl_uart_driver, port); + + return ret; +} + +static int __devexit msm_serial_hsl_remove(struct platform_device *pdev) +{ + struct msm_hsl_port *msm_hsl_port = platform_get_drvdata(pdev); + struct uart_port *port; + + port = get_port_from_line(pdev->id); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + device_set_wakeup_capable(&pdev->dev, 0); + platform_set_drvdata(pdev, NULL); + uart_remove_one_port(&msm_hsl_uart_driver, port); + + clk_put(msm_hsl_port->pclk); + clk_put(msm_hsl_port->clk); + + return 0; +} + +#ifdef CONFIG_PM +static int msm_serial_hsl_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct uart_port *port; + port = get_port_from_line(pdev->id); + + if (port) { + uart_suspend_port(&msm_hsl_uart_driver, port); + if (device_may_wakeup(dev)) + enable_irq_wake(port->irq); + + if (is_console(port)) + msm_hsl_deinit_clock(port); + } + + return 0; +} + +static int msm_serial_hsl_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct uart_port *port; + port = get_port_from_line(pdev->id); + + if (port) { + if (is_console(port)) + msm_hsl_init_clock(port); + uart_resume_port(&msm_hsl_uart_driver, port); + + if (device_may_wakeup(dev)) + disable_irq_wake(port->irq); + } + + return 0; +} +#else +#define msm_serial_hsl_suspend NULL +#define msm_serial_hsl_resume NULL +#endif + +static int msm_hsl_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct uart_port *port; + port = get_port_from_line(pdev->id); + + dev_dbg(dev, "pm_runtime: suspending\n"); + msm_hsl_deinit_clock(port); + return 0; +} + +static int msm_hsl_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct uart_port *port; + port = get_port_from_line(pdev->id); + + dev_dbg(dev, "pm_runtime: resuming\n"); + msm_hsl_init_clock(port); + return 0; +} + +static struct dev_pm_ops msm_hsl_dev_pm_ops = { + .suspend = msm_serial_hsl_suspend, + .resume = msm_serial_hsl_resume, + .runtime_suspend = msm_hsl_runtime_suspend, + .runtime_resume = msm_hsl_runtime_resume, +}; + +static struct platform_driver msm_hsl_platform_driver = { + .probe = msm_serial_hsl_probe, + .remove = __devexit_p(msm_serial_hsl_remove), + .driver = { + .name = "msm_serial_hsl", + .owner = THIS_MODULE, + .pm = &msm_hsl_dev_pm_ops, + }, +}; + +static int __init msm_serial_hsl_init(void) +{ + int ret; + + ret = uart_register_driver(&msm_hsl_uart_driver); + if (unlikely(ret)) + return ret; + + ret = platform_driver_register(&msm_hsl_platform_driver); + if (unlikely(ret)) + uart_unregister_driver(&msm_hsl_uart_driver); + + printk(KERN_INFO "msm_serial_hsl: driver initialized\n"); + + return ret; +} + +static void __exit msm_serial_hsl_exit(void) +{ +#ifdef CONFIG_SERIAL_MSM_HSL_CONSOLE + unregister_console(&msm_hsl_console); +#endif + platform_driver_unregister(&msm_hsl_platform_driver); + uart_unregister_driver(&msm_hsl_uart_driver); +} + +module_init(msm_serial_hsl_init); +module_exit(msm_serial_hsl_exit); + +MODULE_DESCRIPTION("Driver for msm HSUART serial device"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 48f1781352f..afe0033d03c 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -168,4 +168,6 @@ source "drivers/usb/gadget/Kconfig" source "drivers/usb/otg/Kconfig" +source "drivers/usb/function/Kconfig" + endif # USB_SUPPORT diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 30ddf8dc4f7..ef690487bca 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_USB) += core/ obj-$(CONFIG_USB_MON) += mon/ +obj-$(CONFIG_USB_OTG_UTILS) += otg/ obj-$(CONFIG_PCI) += host/ obj-$(CONFIG_USB_EHCI_HCD) += host/ obj-$(CONFIG_USB_ISP116X_HCD) += host/ @@ -23,6 +24,7 @@ obj-$(CONFIG_USB_HWA_HCD) += host/ obj-$(CONFIG_USB_ISP1760_HCD) += host/ obj-$(CONFIG_USB_IMX21_HCD) += host/ obj-$(CONFIG_USB_FSL_MPH_DR_OF) += host/ +obj-$(CONFIG_USB_PEHCI_HCD) += host/ obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ @@ -49,5 +51,4 @@ obj-$(CONFIG_USB_SPEEDTOUCH) += atm/ obj-$(CONFIG_USB_MUSB_HDRC) += musb/ obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs/ -obj-$(CONFIG_USB_OTG_UTILS) += otg/ obj-$(CONFIG_USB_GADGET) += gadget/ diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 34e3da5aa72..97c569061db 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1278,6 +1278,44 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg) return status; } +#ifdef CONFIG_USB_OTG +void usb_hnp_polling_work(struct work_struct *work) +{ + int ret; + struct usb_bus *bus = + container_of(work, struct usb_bus, hnp_polling.work); + struct usb_device *udev = bus->root_hub->children[bus->otg_port - 1]; + u8 *status = kmalloc(sizeof(*status), GFP_KERNEL); + + if (!status) + return; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RECIP_DEVICE, + 0, OTG_STATUS_SELECTOR, status, sizeof(*status), + USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + /* Peripheral may not be supporting HNP polling */ + dev_info(&udev->dev, "HNP polling failed. status %d\n", ret); + goto out; + } + + /* Spec says host must suspend the bus with in 2 sec. */ + if (*status & (1 << HOST_REQUEST_FLAG)) { + do_unbind_rebind(udev, DO_UNBIND); + udev->do_remote_wakeup = device_may_wakeup(&udev->dev); + ret = usb_suspend_both(udev, PMSG_USER_SUSPEND); + if (ret) + dev_info(&udev->dev, "suspend failed\n"); + } else { + schedule_delayed_work(&bus->hnp_polling, + msecs_to_jiffies(THOST_REQ_POLL)); + } +out: + kfree(status); +} +#endif + static void choose_wakeup(struct usb_device *udev, pm_message_t msg) { int w; diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index ace9f8442e5..54338fcc464 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -872,6 +872,9 @@ static void usb_bus_init (struct usb_bus *bus) bus->bandwidth_isoc_reqs = 0; INIT_LIST_HEAD (&bus->bus_list); +#ifdef CONFIG_USB_OTG + INIT_DELAYED_WORK(&bus->hnp_polling, usb_hnp_polling_work); +#endif } /*-------------------------------------------------------------------------*/ @@ -901,6 +904,11 @@ static int usb_register_bus(struct usb_bus *bus) /* Add it to the local list of buses */ list_add (&bus->bus_list, &usb_bus_list); mutex_unlock(&usb_bus_list_lock); +#ifdef CONFIG_USB_OTG + /* Obvioulsy HNP is supported on B-host */ + if (bus->is_b_host) + bus->hnp_support = 1; +#endif usb_notify_add_bus(bus); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index a428aa080a3..5442297caba 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -24,12 +24,38 @@ #include #include #include +#include #include #include #include "usb.h" +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) +#include +#include + +int portno; +int No_Data_Phase; +EXPORT_SYMBOL(No_Data_Phase); +int No_Status_Phase; +EXPORT_SYMBOL(No_Status_Phase); +unsigned char hub_tier; + +#define PDC_HOST_NOTIFY 0x8001 /*completion from core */ +#define UNSUPPORTED_DEVICE 0x8099 +#define UNWANTED_SUSPEND 0x8098 +#define PDC_POWERMANAGEMENT 0x8097 + +int Unwanted_SecondReset; +EXPORT_SYMBOL(Unwanted_SecondReset); +int HostComplianceTest; +EXPORT_SYMBOL(HostComplianceTest); +int HostTest; +EXPORT_SYMBOL(HostTest); +#endif + + /* if we are in debug mode, always announce new devices */ #ifdef DEBUG #ifndef CONFIG_USB_ANNOUNCE_NEW_DEVICES @@ -356,8 +382,11 @@ static int get_port_status(struct usb_device *hdev, int port1, { int i, status = -ETIMEDOUT; + /* ISP1763A HUB sometimes returns 2 bytes instead of 4 bytes, retry + * if this happens + */ for (i = 0; i < USB_STS_RETRIES && - (status == -ETIMEDOUT || status == -EPIPE); i++) { + (status == -ETIMEDOUT || status == -EPIPE || status == 2); i++) { status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1, data, sizeof(*data), USB_STS_TIMEOUT); @@ -724,6 +753,10 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) */ if (type == HUB_INIT) { delay = hub_power_on(hub, false); +#ifdef CONFIG_USB_OTG + if (hdev->bus->is_b_host) + goto init2; +#endif PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func2); schedule_delayed_work(&hub->init_work, msecs_to_jiffies(delay)); @@ -858,6 +891,11 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) * will see them later and handle them normally. */ if (need_debounce_delay) { +#ifdef CONFIG_USB_OTG + if (hdev->bus->is_b_host && type == HUB_INIT) + goto init3; +#endif + delay = HUB_DEBOUNCE_STABLE; /* Don't do a long sleep inside a workqueue routine */ @@ -1301,6 +1339,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) #ifdef CONFIG_USB_OTG_BLACKLIST_HUB if (hdev->parent) { dev_warn(&intf->dev, "ignoring external hub\n"); + otg_send_event(OTG_EVENT_HUB_NOT_SUPPORTED); return -ENODEV; } #endif @@ -1649,6 +1688,13 @@ void usb_disconnect(struct usb_device **pdev) dev_info(&udev->dev, "USB disconnect, device number %d\n", udev->devnum); +#ifdef CONFIG_USB_OTG + if (udev->bus->hnp_support && udev->portnum == udev->bus->otg_port) { + cancel_delayed_work(&udev->bus->hnp_polling); + udev->bus->hnp_support = 0; + } +#endif + usb_lock_device(udev); /* Free up all the children before we remove this device */ @@ -1755,15 +1801,30 @@ static int usb_enumerate_device_otg(struct usb_device *udev) (port1 == bus->otg_port) ? "" : "non-"); + /* a_alt_hnp_support is obsoleted */ + if (port1 != bus->otg_port) + goto out; + + bus->hnp_support = 1; + + /* a_hnp_support is not required for devices + * compliant to revision 2.0 or subsequent + * versions. + */ + if (le16_to_cpu(desc->bcdOTG) >= 0x0200) + goto out; + + /* Legacy B-device i.e compliant to spec + * revision 1.3 expect A-device to set + * a_hnp_support or b_hnp_enable before + * selecting configuration. + */ + /* enable HNP before suspend, it's simpler */ - if (port1 == bus->otg_port) - bus->b_hnp_enable = 1; err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), USB_REQ_SET_FEATURE, 0, - bus->b_hnp_enable - ? USB_DEVICE_B_HNP_ENABLE - : USB_DEVICE_A_ALT_HNP_SUPPORT, + USB_DEVICE_A_HNP_SUPPORT, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); if (err < 0) { /* OTG MESSAGE: report errors here, @@ -1772,26 +1833,35 @@ static int usb_enumerate_device_otg(struct usb_device *udev) dev_info(&udev->dev, "can't set HNP mode: %d\n", err); - bus->b_hnp_enable = 0; + bus->hnp_support = 0; } } } } - +out: if (!is_targeted(udev)) { + otg_send_event(OTG_EVENT_DEV_NOT_SUPPORTED); + /* Maybe it can talk to us, though we can't talk to it. * (Includes HNP test device.) */ - if (udev->bus->b_hnp_enable || udev->bus->is_b_host) { + if (udev->bus->hnp_support) { err = usb_port_suspend(udev, PMSG_SUSPEND); if (err < 0) dev_dbg(&udev->dev, "HNP fail, %d\n", err); } err = -ENOTSUPP; - goto fail; + } else if (udev->bus->hnp_support && + udev->portnum == udev->bus->otg_port) { + /* HNP polling is introduced in OTG supplement Rev 2.0 + * and older devices may not support. Work is not + * re-armed if device returns STALL. B-Host also perform + * HNP polling. + */ + schedule_delayed_work(&udev->bus->hnp_polling, + msecs_to_jiffies(THOST_REQ_POLL)); } -fail: #endif return err; } @@ -2346,6 +2416,22 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) return status; } } +#ifdef CONFIG_USB_OTG + if (!udev->bus->is_b_host && udev->bus->hnp_support && + udev->portnum == udev->bus->otg_port) { + status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, 0, + USB_DEVICE_B_HNP_ENABLE, + 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + if (status < 0) { + otg_send_event(OTG_EVENT_NO_RESP_FOR_HNP_ENABLE); + dev_dbg(&udev->dev, "can't enable HNP on port %d, " + "status %d\n", port1, status); + } else { + udev->bus->b_hnp_enable = 1; + } + } +#endif /* see 7.1.7.6 */ if (hub_is_superspeed(hub->hdev)) @@ -2949,14 +3035,22 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, buf->bMaxPacketSize0; kfree(buf); - retval = hub_port_reset(hub, port1, udev, delay); - if (retval < 0) /* error or disconnect */ - goto fail; - if (oldspeed != udev->speed) { - dev_dbg(&udev->dev, - "device reset changed speed!\n"); - retval = -ENODEV; - goto fail; + /* + * If it is a HSET Test device, we don't issue a + * second reset which results in failure due to + * speed change. + */ + if (le16_to_cpu(buf->idVendor) != 0x1a0a) { + retval = hub_port_reset(hub, port1, udev, + delay); + if (retval < 0) /* error or disconnect */ + goto fail; + if (oldspeed != udev->speed) { + dev_dbg(&udev->dev, + "device reset changed speed!\n"); + retval = -ENODEV; + goto fail; + } } if (r) { dev_err(&udev->dev, @@ -3199,6 +3293,9 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, (portchange & USB_PORT_STAT_C_CONNECTION)) clear_bit(port1, hub->removed_bits); +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + if (Unwanted_SecondReset == 0) /*stericsson*/ +#endif if (portchange & (USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE)) { status = hub_port_debounce(hub, port1); @@ -3337,7 +3434,32 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, status = hub_power_remaining(hub); if (status) dev_dbg(hub_dev, "%dmA power budget left\n", status); +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + if (HostComplianceTest == 1 && udev->devnum > 1) { + if (HostTest == 7) { /*SINGLE_STEP_GET_DEV_DESC */ + dev_info(hub_dev, "Testing " + "SINGLE_STEP_GET_DEV_DESC\n"); + /* Test the Single Step Get Device Descriptor , + * take care it should not get status phase + */ + No_Data_Phase = 1; + No_Status_Phase = 1; + + usb_get_device_descriptor(udev, 8); + No_Data_Phase = 0; + No_Status_Phase = 0; + } + if (HostTest == 8) { + dev_info(hub_dev, "Testing " + "SINGLE_STEP_SET_FEATURE\n"); + /* Test Single Step Set Feature */ + No_Status_Phase = 1; + usb_get_device_descriptor(udev, 8); + No_Status_Phase = 0; + } + } +#endif return; loop_disable: @@ -3375,7 +3497,11 @@ static void hub_events(void) u16 portchange; int i, ret; int connect_change; - +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + int j; + int otgport = 0; + struct usb_port_status port_status; +#endif /* * We restart the list every time to avoid a deadlock with * deleting hubs downstream from this one. This should be @@ -3450,6 +3576,171 @@ static void hub_events(void) /* deal with port status changes */ for (i = 1; i <= hub->descriptor->bNbrPorts; i++) { +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + struct usb_port_status portsts; + + /*if we have something to do on + * otg port + * */ + if ((hdev->otgstate & USB_OTG_SUSPEND) || + (hdev->otgstate & USB_OTG_ENUMERATE) || + (hdev->otgstate & USB_OTG_DISCONNECT) || + (hdev->otgstate & USB_OTG_RESUME)) { + otgport = 1; + } + + + if (hdev->otgstate & USB_OTG_RESUME) { + ret = clear_port_feature(hdev, i, + USB_PORT_FEAT_SUSPEND); + if (ret < 0) { + dev_err(hub_dev, "usb otg port Resume" + " fails, %d\n", ret); + } + hdev->otgstate &= ~USB_OTG_RESUME; + } + if ((hdev->otgstate & USB_OTG_SUSPEND) + && (hdev->children[0])) { + hdev->otgstate &= ~USB_OTG_SUSPEND; + + ret = set_port_feature(hdev, 1, + USB_PORT_FEAT_SUSPEND); + if (ret < 0) { + dev_err(hub_dev, "usb otg port suspend" + " fails, %d\n", ret); + break; + } + msleep(1); + ret = get_port_status(hdev, i, &portsts); + if (ret < 0) { + dev_err(hub_dev, "usb otg get port" + " status fails, %d\n", ret); + break; + } + portchange = le16_to_cpu(portsts.wPortChange); + if (portchange & USB_PORT_STAT_C_SUSPEND) { + clear_port_feature(hdev, i, + USB_PORT_FEAT_C_SUSPEND); + } + break; + } + + if (hdev->otgstate & USB_OTG_REMOTEWAKEUP) { + + for (j = 1; j <= hub->descriptor->bNbrPorts; + j++) { + if (hdev->children[j - 1]) { + dev_dbg(hub_dev, "child" + " found at port %d\n", j); + ret = usb_control_msg(hdev-> + children[j - 1], + usb_sndctrlpipe(hdev-> + children[j - 1], + 0), + USB_REQ_SET_FEATURE, + USB_RECIP_DEVICE, + USB_DEVICE_REMOTE_WAKEUP, + 0, NULL, + 0, + USB_CTRL_SET_TIMEOUT); + if (ret < 0) { + dev_err(hub_dev, "Port" + " %d doesn't support" + "remote wakeup\n", j); + } else { + dev_dbg(hub_dev, "Port" + " %d supports" + "remote wakeup\n", j); + } + ret = set_port_feature(hdev, j, + USB_PORT_FEAT_SUSPEND); + if (ret < 0) { + dev_err(hub_dev, "Port" + " %d NOT ABLE TO" + " SUSPEND\n", j); + } else { + dev_dbg(hub_dev, "Port" + " %d is ABLE TO" + " SUSPEND\n", j); + } + } + } + ret = usb_control_msg(hdev, + usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, + USB_RECIP_DEVICE, + USB_DEVICE_REMOTE_WAKEUP, + 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + if (ret < 0) { + dev_err(hub_dev, "HUB doesn't support" + " REMOTE WAKEUP\n"); + } else { + dev_dbg(hub_dev, "HUB supports" + " REMOTE WAKEUP\n"); + } + ret = 0; + msleep(10); + if (hdev->parent == hdev->bus->root_hub) { + if (hdev->hcd_suspend && + hdev->hcd_priv) { + dev_dbg(hub_dev, "calling" + " suspend after remote wakeup" + " command is issued\n"); + hdev->hcd_suspend(hdev-> + hcd_priv); + } + if (hdev->otg_notif) + hdev->otg_notif(hdev->otgpriv, + PDC_POWERMANAGEMENT, 10); + } + } + + if (hdev->otgstate & USB_OTG_WAKEUP_ALL) { + (void) usb_control_msg(hdev, + usb_sndctrlpipe(hdev, 0), + USB_REQ_CLEAR_FEATURE, + USB_RECIP_DEVICE, + USB_DEVICE_REMOTE_WAKEUP, + 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + dev_dbg(hub_dev, "Hub CLEARED REMOTE WAKEUP\n"); + for (j = 1; j <= hub->descriptor->bNbrPorts; + j++) { + if (hdev->children[j - 1]) { + dev_dbg(hub_dev, "PORT %d" + " SUSPEND IS CLEARD\n", j); + clear_port_feature(hdev, j, + USB_PORT_FEAT_C_SUSPEND); + msleep(50); + (void) usb_control_msg(hdev-> + children[j - 1], + usb_sndctrlpipe( + hdev->children[j - 1], + 0), + USB_REQ_CLEAR_FEATURE, + USB_RECIP_DEVICE, + USB_DEVICE_REMOTE_WAKEUP, + 0, NULL, + 0, + USB_CTRL_SET_TIMEOUT); + dev_dbg(hub_dev, "PORT %d " + "REMOTE WAKEUP IS " + "CLEARD\n", j); + msleep(10); + } + } + + + } + + + /* + * reset the state of otg device, + * regardless of otg device + */ + hdev->otgstate = 0; +#endif if (test_bit(i, hub->busy_bits)) continue; connect_change = test_bit(i, hub->change_bits); @@ -3573,9 +3864,19 @@ static void hub_events(void) hub_port_warm_reset(hub, i); } - if (connect_change) + if (connect_change) { +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + if (hdev->parent == hdev->bus->root_hub) + if (hdev->otg_notif + && (HostComplianceTest == 0)) + hdev->otg_notif(hdev->otgpriv, + PDC_HOST_NOTIFY, + 5); + portno = i; +#endif hub_port_connect_change(hub, i, portstatus, portchange); + } } /* end for i */ /* deal with hub status changes */ @@ -3607,7 +3908,105 @@ static void hub_events(void) "condition\n"); } } +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + /* if we have something on otg */ + if (otgport) { + otgport = 0; + /* notify otg controller about it */ + if (hdev->parent == hdev->bus->root_hub) + if (hdev->otg_notif) + hdev->otg_notif(hdev->otgpriv, + PDC_HOST_NOTIFY, 0); + } + if (HostComplianceTest && hdev->devnum > 1) { + /* TEST_SE0_NAK */ + if (HostTest == 1) { + dev_info(hub_dev, "Testing for TEST_SE0_NAK\n"); + ret = clear_port_feature(hdev, portno, + USB_PORT_FEAT_C_CONNECTION); + ret = set_port_feature(hdev, portno, + USB_PORT_FEAT_SUSPEND); + ret = set_port_feature(hdev, portno | 0x300, + USB_PORT_FEAT_TEST); + ret = get_port_status(hdev, portno, + &port_status); + } + /*TEST_J*/ + if (HostTest == 2) { + dev_info(hub_dev, "Testing TEST_J\n"); + ret = clear_port_feature(hdev, portno, + USB_PORT_FEAT_C_CONNECTION); + ret = set_port_feature(hdev, portno, + USB_PORT_FEAT_SUSPEND); + ret = set_port_feature(hdev, portno | 0x100, + USB_PORT_FEAT_TEST); + ret = get_port_status(hdev, portno, + &port_status); + } + if (HostTest == 3) { + dev_info(hub_dev, "Testing TEST_K\n"); + ret = clear_port_feature(hdev, portno, + USB_PORT_FEAT_C_CONNECTION); + ret = set_port_feature(hdev, portno, + USB_PORT_FEAT_SUSPEND); + ret = set_port_feature(hdev, portno | 0x200, + USB_PORT_FEAT_TEST); + ret = get_port_status(hdev, portno, + &port_status); + } + if (HostTest == 4) { + dev_info(hub_dev, "Testing TEST_PACKET at Port" + " %d\n", portno); + ret = clear_port_feature(hdev, portno, + USB_PORT_FEAT_C_CONNECTION); + if (ret < 0) + dev_err(hub_dev, "Clear port feature" + " C_CONNECTION failed\n"); + + ret = set_port_feature(hdev, portno, + USB_PORT_FEAT_SUSPEND); + if (ret < 0) + dev_err(hub_dev, "Clear port feature" + " SUSPEND failed\n"); + + ret = set_port_feature(hdev, portno | 0x400, + USB_PORT_FEAT_TEST); + if (ret < 0) + dev_err(hub_dev, "Clear port feature" + " TEST failed\n"); + + ret = get_port_status(hdev, portno, + &port_status); + if (ret < 0) + dev_err(hub_dev, "Get port status" + " failed\n"); + } + if (HostTest == 5) { + dev_info(hub_dev, "Testing TEST_FORCE_ENBLE\n"); + ret = clear_port_feature(hdev, portno, + USB_PORT_FEAT_C_CONNECTION); + ret = set_port_feature(hdev, portno, + USB_PORT_FEAT_SUSPEND); + ret = set_port_feature(hdev, portno | 0x500, + USB_PORT_FEAT_TEST); + ret = get_port_status(hdev, portno, + &port_status); + } + if (HostTest == 6) { + dev_info(hub_dev, "Testing " + "HS_HOST_PORT_SUSPEND_RESUME\n"); + ret = clear_port_feature(hdev, portno, + USB_PORT_FEAT_C_CONNECTION); + ret = set_port_feature(hdev, portno, + USB_PORT_FEAT_SUSPEND); + msleep(3000); + ret = clear_port_feature(hdev, portno, + USB_PORT_FEAT_SUSPEND); + HostTest = 0; + } + } +#endif loop_autopm: /* Balance the usb_autopm_get_interface() above */ usb_autopm_put_interface_no_suspend(intf); diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 0b5ec234c78..415593cacf2 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1769,6 +1769,9 @@ int usb_set_configuration(struct usb_device *dev, int configuration) goto free_interfaces; } + dev->actconfig = cp; + if (cp) + usb_notify_config_device(dev); ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION, 0, configuration, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); @@ -1776,11 +1779,11 @@ int usb_set_configuration(struct usb_device *dev, int configuration) /* All the old state is gone, so what else can we do? * The device is probably useless now anyway. */ - cp = NULL; + dev->actconfig = cp = NULL; } - dev->actconfig = cp; if (!cp) { + usb_notify_config_device(dev); usb_set_device_state(dev, USB_STATE_ADDRESS); usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); mutex_unlock(hcd->bandwidth_mutex); diff --git a/drivers/usb/core/notify.c b/drivers/usb/core/notify.c index 7542dce3f5a..15311d8244e 100644 --- a/drivers/usb/core/notify.c +++ b/drivers/usb/core/notify.c @@ -57,6 +57,12 @@ void usb_notify_remove_device(struct usb_device *udev) mutex_unlock(&usbfs_mutex); } +void usb_notify_config_device(struct usb_device *udev) +{ + blocking_notifier_call_chain(&usb_notifier_list, + USB_DEVICE_CONFIG, udev); +} + void usb_notify_add_bus(struct usb_bus *ubus) { blocking_notifier_call_chain(&usb_notifier_list, USB_BUS_ADD, ubus); diff --git a/drivers/usb/core/otg_whitelist.h b/drivers/usb/core/otg_whitelist.h index e8cdce571bb..cec41675a69 100644 --- a/drivers/usb/core/otg_whitelist.h +++ b/drivers/usb/core/otg_whitelist.h @@ -92,7 +92,30 @@ static int is_targeted(struct usb_device *dev) if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) && (id->bDeviceProtocol != dev->descriptor.bDeviceProtocol)) continue; - +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + /*Hub is targeted device,so code execution should reach here */ + if (USB_CLASS_HUB == dev->descriptor.bDeviceClass) { + /* count the tiers and if it is more than 6, return 0 */ + unsigned char tier = 0; + struct usb_device *root_hub; + + root_hub = dev->bus->root_hub; + while ((dev->parent != NULL) && /* root hub not count */ + (dev->parent != root_hub) && + (tier != 6)) {/* interal hub not count */ + tier++; + dev = dev->parent; + } + + if (tier == 6) { + dev_err(&dev->dev, "5 tier of hubs reached," + " newly added hub will not be" + " supported!\n"); + hub_tier = 1; + return 0; + } + } +#endif return 1; } diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index d44d4b7bbf1..c36c72acc07 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -96,6 +96,10 @@ static inline int usb_remote_wakeup(struct usb_device *udev) #endif +#ifdef CONFIG_USB_OTG +extern void usb_hnp_polling_work(struct work_struct *work); +#endif + extern struct bus_type usb_bus_type; extern struct device_type usb_device_type; extern struct device_type usb_if_device_type; @@ -159,6 +163,7 @@ extern void usb_devio_cleanup(void); /* internal notify stuff */ extern void usb_notify_add_device(struct usb_device *udev); extern void usb_notify_remove_device(struct usb_device *udev); +extern void usb_notify_config_device(struct usb_device *udev); extern void usb_notify_add_bus(struct usb_bus *ubus); extern void usb_notify_remove_bus(struct usb_bus *ubus); diff --git a/drivers/usb/function/Kconfig b/drivers/usb/function/Kconfig new file mode 100644 index 00000000000..90d776cf2aa --- /dev/null +++ b/drivers/usb/function/Kconfig @@ -0,0 +1,163 @@ +menu "USB Function Support" + depends on !USB_GADGET + +config USB_MSM_OTG + bool "OTG support for Qualcomm on-chip USB controller" + depends on USB && USB_FUNCTION && USB_EHCI_MSM + help + USB OTG driver. + This driver is required if you want to use USB in + Host mode and Device mode. + +config USB_FUNCTION + boolean "Support for USB Function Drivers" + help + The USB Function framework is similar to the Gadget framework + but a little simpler and a little more plugable. It trades + some flexibility in the framework for smaller and simpler + function drivers that can be combined into a composite driver. + +choice + prompt "USB Peripheral Controller" + depends on USB_FUNCTION + help + USB devices interfaces with the host using a controller. + Many controller drivers are platform-specific; these + often need board-specific hooks. + +config USB_FUNCTION_MSM_HSUSB + boolean "MSM Highspeed USB Peripheral Controller" + depends on ARCH_MSM + help + High speed USB device controller for Qualcomm chipsets using + USB Function framework. Controller supports IAD and + 32 endpoints(16 IN and 16 OUT). + +endchoice + +config USB_FUNCTION_NULL + boolean "Null Function -- eats packets" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB + default n + +config USB_FUNCTION_ZERO + boolean "Zero Function -- generates packets" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB + default n + +config USB_FUNCTION_LOOPBACK + boolean "Loopback Function -- returns packets" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB + default n + +config USB_FUNCTION_ADB + tristate "ADB Transport Function" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB + help + Function Driver for the Android ADB Protocol + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "adb" + + default USB_FUNCTION_MSM_HSUSB + +config USB_FUNCTION_UMS + boolean "USB Mass Storage Function (userspace)" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB + default n + +config USB_FUNCTION_MASS_STORAGE + tristate "USB Mass Storage Function (kernel based)" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB && SWITCH + help + The File-backed Storage function driver acts as a USB Mass Storage + disk drive. As its storage repository it can use a regular + file or a block device specified as a module parameter. Initial + driver version is derived from Gadget framework and ported to + Function driver framework. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "file_storage". + + default USB_FUNCTION_MSM_HSUSB + +config USB_CSW_HACK + boolean "USB Mass storage csw hack Feature" + depends on USB_FUNCTION + depends on USB_FUNCTION_MASS_STORAGE + help + This csw hack feature is for increasing the performance of the mass + storage + + default n + +config USB_FUNCTION_DIAG + tristate "USB MSM Diag Function" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB + help + Simple bridge driver between smd and debug client(host side) + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "diag". + + default USB_FUNCTION_MSM_HSUSB + +config USB_FUNCTION_ETHER + tristate "USB Ethernet Function" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB + help + Implements the Ethernet style communication using CDC/ECM. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "ether". + + default USB_FUNCTION_MSM_HSUSB + +config USB_FUNCTION_SERIAL + tristate "USB Serial Function" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB + help + Implements serial communication using single interface; uses + two endpoints(bulk-in and bulk out) for data transfer and a + interrupt endpoint for control data transfer. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "serial". + + default USB_FUNCTION_MSM_HSUSB + +config USB_FUNCTION_RMNET + bool "RmNet function driver" + depends on USB_FUNCTION + depends on USB_FUNCTION_MSM_HSUSB + default n + help + Implements Rmnet function. + Rmnet is an alternative to CDC-ECM and Windows RNDIS. It uses + QUALCOMM MSM Interface for control transfers. It acts like a + bridge between Host and modem found in MSM chipsets. + +config RMNET_SMD_CTL_CHANNEL + string "control SMD channel name" + depends on USB_FUNCTION_RMNET + default "" + help + Control SMD channel for transferring QMI messages + +config RMNET_SMD_DATA_CHANNEL + string "Data SMD channel name" + depends on USB_FUNCTION_RMNET + default "" + help + Data SMD channel for transferring network data + +endmenu diff --git a/drivers/usb/function/Makefile b/drivers/usb/function/Makefile new file mode 100644 index 00000000000..7614d3b1f05 --- /dev/null +++ b/drivers/usb/function/Makefile @@ -0,0 +1,13 @@ + +obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o +obj-$(CONFIG_USB_FUNCTION_MSM_HSUSB) += msm_hsusb.o +obj-$(CONFIG_USB_FUNCTION_NULL) += null.o +obj-$(CONFIG_USB_FUNCTION_NULL) += zero.o +obj-$(CONFIG_USB_FUNCTION_LOOPBACK) += loopback.o +obj-$(CONFIG_USB_FUNCTION_ADB) += adb.o +obj-$(CONFIG_USB_FUNCTION_UMS) += ums.o +obj-$(CONFIG_USB_FUNCTION_MASS_STORAGE) += mass_storage.o +obj-$(CONFIG_USB_FUNCTION_DIAG) += diag.o +obj-$(CONFIG_USB_FUNCTION_SERIAL) += serial.o +obj-$(CONFIG_USB_FUNCTION_ETHER) += ether_cdc_ecm.o +obj-$(CONFIG_USB_FUNCTION_RMNET) += rmnet.o diff --git a/drivers/usb/function/adb.c b/drivers/usb/function/adb.c new file mode 100644 index 00000000000..dd91be387b4 --- /dev/null +++ b/drivers/usb/function/adb.c @@ -0,0 +1,624 @@ +/* drivers/usb/function/adb.c + * + * Function Device for the Android ADB Protocol + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "usb_function.h" + +#if 1 +#define DBG(x...) do {} while (0) +#else +#define DBG(x...) printk(x) +#endif + +#define TXN_MAX 4096 + +/* number of rx and tx requests to allocate */ +#define RX_REQ_MAX 4 +#define TX_REQ_MAX 4 + +#define ADB_FUNCTION_NAME "adb" + +struct adb_context +{ + int online; + int error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + atomic_t enable_excl; + spinlock_t lock; + + struct usb_endpoint *out; + struct usb_endpoint *in; + + struct list_head tx_idle; + struct list_head rx_idle; + struct list_head rx_done; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + + /* the request we're currently reading from */ + struct usb_request *read_req; + unsigned char *read_buf; + unsigned read_count; + unsigned bound; +}; + +static struct adb_context _context; + +static struct usb_interface_descriptor intf_desc = { + .bLength = sizeof intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0x42, + .bInterfaceProtocol = 0x01, +}; + +static struct usb_endpoint_descriptor hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; +static struct usb_endpoint_descriptor fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +static struct usb_function usb_func_adb; + +static inline int _lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +void req_put(struct adb_context *ctxt, struct list_head *head, struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&ctxt->lock, flags); +} + +/* remove a request from the head of a list */ +struct usb_request *req_get(struct adb_context *ctxt, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&ctxt->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&ctxt->lock, flags); + return req; +} + +static void adb_complete_in(struct usb_endpoint *ept, struct usb_request *req) +{ + struct adb_context *ctxt = req->context; + + if (req->status != 0) + ctxt->error = 1; + + req_put(ctxt, &ctxt->tx_idle, req); + + wake_up(&ctxt->write_wq); +} + +static void adb_complete_out(struct usb_endpoint *ept, struct usb_request *req) +{ + struct adb_context *ctxt = req->context; + + if (req->status != 0) { + ctxt->error = 1; + req_put(ctxt, &ctxt->rx_idle, req); + } else { + req_put(ctxt, &ctxt->rx_done, req); + } + + wake_up(&ctxt->read_wq); +} + +static ssize_t adb_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_context *ctxt = &_context; + struct usb_request *req; + int r = count, xfer; + int ret; + + DBG("adb_read(%d)\n", count); + + if (_lock(&ctxt->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(ctxt->online || ctxt->error)) { + DBG("adb_read: waiting for online state\n"); + ret = wait_event_interruptible(ctxt->read_wq, (ctxt->online || ctxt->error)); + if (ret < 0) { + _unlock(&ctxt->read_excl); + return ret; + } + } + + while (count > 0) { + if (ctxt->error) { + r = -EIO; + break; + } + + /* if we have idle read requests, get them queued */ + while ((req = req_get(ctxt, &ctxt->rx_idle))) { +requeue_req: + req->length = TXN_MAX; + ret = usb_ept_queue_xfer(ctxt->out, req); + if (ret < 0) { + DBG("adb_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + ctxt->error = 1; + req_put(ctxt, &ctxt->rx_idle, req); + goto fail; + } else { + DBG("rx %p queue\n", req); + } + } + + /* if we have data pending, give it to userspace */ + if (ctxt->read_count > 0) { + xfer = (ctxt->read_count < count) ? ctxt->read_count : count; + + if (copy_to_user(buf, ctxt->read_buf, xfer)) { + r = -EFAULT; + break; + } + ctxt->read_buf += xfer; + ctxt->read_count -= xfer; + buf += xfer; + count -= xfer; + + /* if we've emptied the buffer, release the request */ + if (ctxt->read_count == 0) { + req_put(ctxt, &ctxt->rx_idle, ctxt->read_req); + ctxt->read_req = 0; + } + continue; + } + + /* wait for a request to complete */ + req = 0; + ret = wait_event_interruptible(ctxt->read_wq, + ((req = req_get(ctxt, &ctxt->rx_done)) || ctxt->error)); + + if (req != 0) { + /* if we got a 0-len one we need to put it back into + ** service. if we made it the current read req we'd + ** be stuck forever + */ + if (req->actual == 0) + goto requeue_req; + + ctxt->read_req = req; + ctxt->read_count = req->actual; + ctxt->read_buf = req->buf; + DBG("rx %p %d\n", req, req->actual); + } + + if (ret < 0) { + r = ret; + break; + } + } + +fail: + _unlock(&ctxt->read_excl); + return r; +} + +static ssize_t adb_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_context *ctxt = &_context; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + DBG("adb_write(%d)\n", count); + + if (_lock(&ctxt->write_excl)) + return -EBUSY; + + while (count > 0) { + if (ctxt->error) { + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(ctxt->write_wq, + ((req = req_get(ctxt, &ctxt->tx_idle)) || ctxt->error)); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + xfer = count > TXN_MAX ? TXN_MAX : count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ept_queue_xfer(ctxt->in, req); + if (ret < 0) { + DBG("adb_write: xfer error %d\n", ret); + ctxt->error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + + if (req) + req_put(ctxt, &ctxt->tx_idle, req); + + _unlock(&ctxt->write_excl); + return r; +} + +static int adb_open(struct inode *ip, struct file *fp) +{ + struct adb_context *ctxt = &_context; + + if (_lock(&ctxt->open_excl)) + return -EBUSY; + + /* clear the error latch */ + ctxt->error = 0; + + return 0; +} + +static int adb_release(struct inode *ip, struct file *fp) +{ + struct adb_context *ctxt = &_context; + + _unlock(&ctxt->open_excl); + return 0; +} + +static struct file_operations adb_fops = { + .owner = THIS_MODULE, + .read = adb_read, + .write = adb_write, + .open = adb_open, + .release = adb_release, +}; + +static struct miscdevice adb_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "android_adb", + .fops = &adb_fops, +}; + +static int adb_enable_open(struct inode *ip, struct file *fp) +{ + struct adb_context *ctxt = &_context; + + if (_lock(&ctxt->enable_excl)) + return -EBUSY; + + printk(KERN_INFO "enabling adb function\n"); + usb_function_enable(ADB_FUNCTION_NAME, 1); + /* clear the error latch */ + ctxt->error = 0; + + return 0; +} + +static int adb_enable_release(struct inode *ip, struct file *fp) +{ + struct adb_context *ctxt = &_context; + + printk(KERN_INFO "disabling adb function\n"); + usb_function_enable(ADB_FUNCTION_NAME, 0); + _unlock(&ctxt->enable_excl); + return 0; +} + +static struct file_operations adb_enable_fops = { + .owner = THIS_MODULE, + .open = adb_enable_open, + .release = adb_enable_release, +}; + +static struct miscdevice adb_enable_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "android_adb_enable", + .fops = &adb_enable_fops, +}; + +static void adb_unbind(void *_ctxt) +{ + struct adb_context *ctxt = _ctxt; + struct usb_request *req; + + if (!ctxt->bound) + return; + + while ((req = req_get(ctxt, &ctxt->rx_idle))) { + usb_ept_free_req(ctxt->out, req); + } + while ((req = req_get(ctxt, &ctxt->tx_idle))) { + usb_ept_free_req(ctxt->in, req); + } + if (ctxt->in) { + usb_ept_fifo_flush(ctxt->in); + usb_ept_enable(ctxt->in, 0); + usb_free_endpoint(ctxt->in); + } + if (ctxt->out) { + usb_ept_fifo_flush(ctxt->out); + usb_ept_enable(ctxt->out, 0); + usb_free_endpoint(ctxt->out); + } + + ctxt->online = 0; + ctxt->error = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&ctxt->read_wq); + ctxt->bound = 0; +} + +static void adb_bind(void *_ctxt) +{ + struct adb_context *ctxt = _ctxt; + struct usb_request *req; + int n; + + intf_desc.bInterfaceNumber = + usb_msm_get_next_ifc_number(&usb_func_adb); + + ctxt->in = usb_alloc_endpoint(USB_DIR_IN); + if (ctxt->in) { + hs_bulk_in_desc.bEndpointAddress = USB_DIR_IN | ctxt->in->num; + fs_bulk_in_desc.bEndpointAddress = USB_DIR_IN | ctxt->in->num; + } + + ctxt->out = usb_alloc_endpoint(USB_DIR_OUT); + if (ctxt->out) { + hs_bulk_out_desc.bEndpointAddress = USB_DIR_OUT|ctxt->out->num; + fs_bulk_out_desc.bEndpointAddress = USB_DIR_OUT|ctxt->out->num; + } + + for (n = 0; n < RX_REQ_MAX; n++) { + req = usb_ept_alloc_req(ctxt->out, 4096); + if (req == 0) { + ctxt->bound = 1; + goto fail; + } + req->context = ctxt; + req->complete = adb_complete_out; + req_put(ctxt, &ctxt->rx_idle, req); + } + + for (n = 0; n < TX_REQ_MAX; n++) { + req = usb_ept_alloc_req(ctxt->in, 4096); + if (req == 0) { + ctxt->bound = 1; + goto fail; + } + req->context = ctxt; + req->complete = adb_complete_in; + req_put(ctxt, &ctxt->tx_idle, req); + } + ctxt->bound = 1; + return; + +fail: + printk(KERN_ERR "adb_bind() could not allocate requests\n"); + adb_unbind(ctxt); +} + +static void adb_configure(int configured, void *_ctxt) +{ + struct adb_context *ctxt = _ctxt; + struct usb_request *req; + + if (configured) { + ctxt->online = 1; + + if (usb_msm_get_speed() == USB_SPEED_HIGH) { + usb_configure_endpoint(ctxt->in, &hs_bulk_in_desc); + usb_configure_endpoint(ctxt->out, &hs_bulk_out_desc); + } else { + usb_configure_endpoint(ctxt->in, &fs_bulk_in_desc); + usb_configure_endpoint(ctxt->out, &fs_bulk_out_desc); + } + usb_ept_enable(ctxt->in, 1); + usb_ept_enable(ctxt->out, 1); + + /* if we have a stale request being read, recycle it */ + ctxt->read_buf = 0; + ctxt->read_count = 0; + if (ctxt->read_req) { + req_put(ctxt, &ctxt->rx_idle, ctxt->read_req); + ctxt->read_req = 0; + } + + /* retire any completed rx requests from previous session */ + while ((req = req_get(ctxt, &ctxt->rx_done))) + req_put(ctxt, &ctxt->rx_idle, req); + + } else { + ctxt->online = 0; + ctxt->error = 1; + } + + /* readers may be blocked waiting for us to go online */ + wake_up(&ctxt->read_wq); +} + +static struct usb_function usb_func_adb = { + .bind = adb_bind, + .unbind = adb_unbind, + .configure = adb_configure, + + .name = ADB_FUNCTION_NAME, + .context = &_context, + +}; + +struct usb_descriptor_header *adb_hs_descriptors[4]; +struct usb_descriptor_header *adb_fs_descriptors[4]; +static int __init adb_init(void) +{ + int ret = 0; + struct adb_context *ctxt = &_context; + DBG("adb_init()\n"); + + init_waitqueue_head(&ctxt->read_wq); + init_waitqueue_head(&ctxt->write_wq); + + atomic_set(&ctxt->open_excl, 0); + atomic_set(&ctxt->read_excl, 0); + atomic_set(&ctxt->write_excl, 0); + atomic_set(&ctxt->enable_excl, 0); + + spin_lock_init(&ctxt->lock); + + INIT_LIST_HEAD(&ctxt->rx_idle); + INIT_LIST_HEAD(&ctxt->rx_done); + INIT_LIST_HEAD(&ctxt->tx_idle); + + adb_hs_descriptors[0] = (struct usb_descriptor_header *)&intf_desc; + adb_hs_descriptors[1] = + (struct usb_descriptor_header *)&hs_bulk_in_desc; + adb_hs_descriptors[2] = + (struct usb_descriptor_header *)&hs_bulk_out_desc; + adb_hs_descriptors[3] = NULL; + + adb_fs_descriptors[0] = (struct usb_descriptor_header *)&intf_desc; + adb_fs_descriptors[1] = + (struct usb_descriptor_header *)&fs_bulk_in_desc; + adb_fs_descriptors[2] = + (struct usb_descriptor_header *)&fs_bulk_out_desc; + adb_fs_descriptors[3] = NULL; + + usb_func_adb.hs_descriptors = adb_hs_descriptors; + usb_func_adb.fs_descriptors = adb_fs_descriptors; + + ret = misc_register(&adb_device); + if (ret) { + printk(KERN_ERR "adb Can't register misc device %d \n", + MISC_DYNAMIC_MINOR); + return ret; + } + ret = misc_register(&adb_enable_device); + if (ret) { + printk(KERN_ERR "adb Can't register misc enable device %d \n", + MISC_DYNAMIC_MINOR); + misc_deregister(&adb_device); + return ret; + } + + ret = usb_function_register(&usb_func_adb); + if (ret) { + misc_deregister(&adb_device); + misc_deregister(&adb_enable_device); + } + return ret; +} + +module_init(adb_init); + +static void __exit adb_exit(void) +{ + misc_deregister(&adb_device); + misc_deregister(&adb_enable_device); + + usb_function_unregister(&usb_func_adb); +} +module_exit(adb_exit); diff --git a/drivers/usb/function/diag.c b/drivers/usb/function/diag.c new file mode 100644 index 00000000000..94c32e7a0a0 --- /dev/null +++ b/drivers/usb/function/diag.c @@ -0,0 +1,567 @@ +/* drivers/usb/function/diag.c + * + * Diag Function Device - Route DIAG frames between SMD and USB + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "usb_function.h" + +#define WRITE_COMPLETE 0 +#define READ_COMPLETE 0 +static struct usb_interface_descriptor intf_desc = { + .bLength = sizeof intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0xFF, + .bInterfaceProtocol = 0xFF, +}; + +static struct usb_endpoint_descriptor hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; +static struct usb_endpoint_descriptor fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +/* list of requests */ +struct diag_req_entry { + struct list_head re_entry; + struct usb_request *usb_req; + void *diag_request; +}; +struct diag_context { + struct usb_endpoint *epout; + struct usb_endpoint *epin; + spinlock_t dev_lock; + /* linked list of read requets*/ + struct list_head dev_read_req_list; + /* linked list of write requets*/ + struct list_head dev_write_req_list; + struct diag_operations *operations; + struct workqueue_struct *diag_wq; + struct work_struct usb_config_work; + unsigned configured; + unsigned bound; + int diag_opened; +}; + +static struct usb_function usb_func_diag; +static struct diag_context _context; +static void diag_write_complete(struct usb_endpoint *, + struct usb_request *); +static struct diag_req_entry *diag_alloc_req_entry(struct usb_endpoint *, + unsigned len, gfp_t); +static void diag_free_req_entry(struct usb_endpoint *, struct diag_req_entry *); +static void diag_read_complete(struct usb_endpoint *, struct usb_request *); + + +static void diag_unbind(void *context) +{ + + struct diag_context *ctxt = context; + + if (!ctxt) + return; + if (!ctxt->bound) + return; + if (ctxt->epin) { + usb_ept_fifo_flush(ctxt->epin); + usb_ept_enable(ctxt->epin, 0); + usb_free_endpoint(ctxt->epin); + } + if (ctxt->epout) { + usb_ept_fifo_flush(ctxt->epout); + usb_ept_enable(ctxt->epout, 0); + usb_free_endpoint(ctxt->epout); + } + ctxt->bound = 0; +} +static void diag_bind(void *context) +{ + struct diag_context *ctxt = context; + + if (!ctxt) + return; + + intf_desc.bInterfaceNumber = + usb_msm_get_next_ifc_number(&usb_func_diag); + + ctxt->epin = usb_alloc_endpoint(USB_DIR_IN); + if (ctxt->epin) { + hs_bulk_in_desc.bEndpointAddress = + USB_DIR_IN | ctxt->epin->num; + fs_bulk_in_desc.bEndpointAddress = + USB_DIR_IN | ctxt->epin->num; + } + + ctxt->epout = usb_alloc_endpoint(USB_DIR_OUT); + if (ctxt->epout) { + hs_bulk_out_desc.bEndpointAddress = + USB_DIR_OUT | ctxt->epout->num; + fs_bulk_out_desc.bEndpointAddress = + USB_DIR_OUT | ctxt->epout->num; + } + + ctxt->bound = 1; +} +static void diag_configure(int configured, void *_ctxt) + +{ + struct diag_context *ctxt = _ctxt; + + if (!ctxt) + return; + if (configured) { + if (usb_msm_get_speed() == USB_SPEED_HIGH) { + usb_configure_endpoint(ctxt->epin, &hs_bulk_in_desc); + usb_configure_endpoint(ctxt->epout, &hs_bulk_out_desc); + } else { + usb_configure_endpoint(ctxt->epin, &fs_bulk_in_desc); + usb_configure_endpoint(ctxt->epout, &fs_bulk_out_desc); + } + usb_ept_enable(ctxt->epin, 1); + usb_ept_enable(ctxt->epout, 1); + ctxt->configured = 1; + queue_work(_context.diag_wq, &(_context.usb_config_work)); + } else { + /* all pending requests will be canceled */ + ctxt->configured = 0; + if (ctxt->epin) { + usb_ept_fifo_flush(ctxt->epin); + usb_ept_enable(ctxt->epin, 0); + } + if (ctxt->epout) { + usb_ept_fifo_flush(ctxt->epout); + usb_ept_enable(ctxt->epout, 0); + } + if ((ctxt->operations) && + (ctxt->operations->diag_disconnect)) + ctxt->operations->diag_disconnect(); + } + +} +static struct usb_function usb_func_diag = { + .bind = diag_bind, + .configure = diag_configure, + .unbind = diag_unbind, + + + .name = "diag", + .context = &_context, +}; +int diag_usb_register(struct diag_operations *func) +{ + struct diag_context *ctxt = &_context; + + if (func == NULL) { + printk(KERN_ERR "diag_usb_register:registering" + "diag char operations NULL\n"); + return -1; + } + ctxt->operations = func; + if (ctxt->configured == 1) + if ((ctxt->operations) && + (ctxt->operations->diag_connect)) + ctxt->operations->diag_connect(); + return 0; +} +EXPORT_SYMBOL(diag_usb_register); + +int diag_usb_unregister(void) +{ + struct diag_context *ctxt = &_context; + + ctxt->operations = NULL; + return 0; +} +EXPORT_SYMBOL(diag_usb_unregister); + +int diag_open(int num_req) +{ + struct diag_context *ctxt = &_context; + struct diag_req_entry *write_entry; + struct diag_req_entry *read_entry; + int i = 0; + + for (i = 0; i < num_req; i++) { + write_entry = diag_alloc_req_entry(ctxt->epin, 0, GFP_KERNEL); + if (write_entry) { + write_entry->usb_req->complete = diag_write_complete; + write_entry->usb_req->device = (void *)ctxt; + list_add(&write_entry->re_entry, + &ctxt->dev_write_req_list); + } else + goto write_error; + } + + for (i = 0; i < num_req ; i++) { + read_entry = diag_alloc_req_entry(ctxt->epout, 0 , GFP_KERNEL); + if (read_entry) { + read_entry->usb_req->complete = diag_read_complete; + read_entry->usb_req->device = (void *)ctxt; + list_add(&read_entry->re_entry , + &ctxt->dev_read_req_list); + } else + goto read_error; + } + ctxt->diag_opened = 1; + return 0; +read_error: + printk(KERN_ERR "%s:read requests allocation failure\n", __func__); + while (!list_empty(&ctxt->dev_read_req_list)) { + read_entry = list_entry(ctxt->dev_read_req_list.next, + struct diag_req_entry, re_entry); + list_del(&read_entry->re_entry); + diag_free_req_entry(ctxt->epout, read_entry); + } +write_error: + printk(KERN_ERR "%s: write requests allocation failure\n", __func__); + while (!list_empty(&ctxt->dev_write_req_list)) { + write_entry = list_entry(ctxt->dev_write_req_list.next, + struct diag_req_entry, re_entry); + list_del(&write_entry->re_entry); + diag_free_req_entry(ctxt->epin, write_entry); + } + ctxt->diag_opened = 0; + return -ENOMEM; +} +EXPORT_SYMBOL(diag_open); + +void diag_close(void) +{ + struct diag_context *ctxt = &_context; + struct diag_req_entry *req_entry; + /* free write requests */ + + while (!list_empty(&ctxt->dev_write_req_list)) { + req_entry = list_entry(ctxt->dev_write_req_list.next, + struct diag_req_entry, re_entry); + list_del(&req_entry->re_entry); + diag_free_req_entry(ctxt->epin, req_entry); + } + + /* free read requests */ + while (!list_empty(&ctxt->dev_read_req_list)) { + req_entry = list_entry(ctxt->dev_read_req_list.next, + struct diag_req_entry, re_entry); + list_del(&req_entry->re_entry); + diag_free_req_entry(ctxt->epout, req_entry); + } + return; +} +EXPORT_SYMBOL(diag_close); + +static void diag_free_req_entry(struct usb_endpoint *ep, + struct diag_req_entry *req) +{ + if (ep != NULL && req != NULL) { + if (req->usb_req != NULL) + usb_ept_free_req(ep, req->usb_req); + kfree(req); + } +} + + +static struct diag_req_entry *diag_alloc_req_entry(struct usb_endpoint *ep, + unsigned len, gfp_t kmalloc_flags) +{ + struct diag_req_entry *req; + + req = kmalloc(sizeof(struct diag_req_entry), kmalloc_flags); + if (req == NULL) + return ERR_PTR(-ENOMEM); + + + req->usb_req = usb_ept_alloc_req(ep , 0); + if (req->usb_req == NULL) { + kfree(req); + return ERR_PTR(-ENOMEM); + } + + req->usb_req->context = req; + return req; +} + +int diag_read(struct diag_request *d_req) +{ + unsigned long flags; + struct usb_request *req = NULL; + struct diag_req_entry *req_entry = NULL; + struct diag_context *ctxt = &_context; + + + if (ctxt->diag_opened != 1) + return -EIO; + spin_lock_irqsave(&ctxt->dev_lock , flags); + if (!list_empty(&ctxt->dev_read_req_list)) { + req_entry = list_entry(ctxt->dev_read_req_list.next , + struct diag_req_entry , re_entry); + req_entry->diag_request = d_req; + req = req_entry->usb_req; + list_del(&req_entry->re_entry); + } + spin_unlock_irqrestore(&ctxt->dev_lock , flags); + if (req) { + req->buf = d_req->buf; + req->length = d_req->length; + req->device = ctxt; + if (usb_ept_queue_xfer(ctxt->epout, req)) { + /* If error add the link to the linked list again. */ + spin_lock_irqsave(&ctxt->dev_lock , flags); + list_add_tail(&req_entry->re_entry , + &ctxt->dev_read_req_list); + spin_unlock_irqrestore(&ctxt->dev_lock , flags); + printk(KERN_ERR "diag_read:can't queue the request\n"); + return -EIO; + } + } else { + printk(KERN_ERR + "diag_read:no requests avialable\n"); + return -EIO; + } + return 0; +} +EXPORT_SYMBOL(diag_read); + +int diag_write(struct diag_request *d_req) +{ + unsigned long flags; + struct usb_request *req = NULL; + struct diag_req_entry *req_entry = NULL; + struct diag_context *ctxt = &_context; + + if (ctxt->diag_opened != 1) + return -EIO; + spin_lock_irqsave(&ctxt->dev_lock , flags); + if (!list_empty(&ctxt->dev_write_req_list)) { + req_entry = list_entry(ctxt->dev_write_req_list.next , + struct diag_req_entry , re_entry); + req_entry->diag_request = d_req; + req = req_entry->usb_req; + list_del(&req_entry->re_entry); + } + spin_unlock_irqrestore(&ctxt->dev_lock, flags); + if (req) { + req->buf = d_req->buf; + req->length = d_req->length; + req->device = ctxt; + if (usb_ept_queue_xfer(ctxt->epin, req)) { + /* If error add the link to linked list again*/ + spin_lock_irqsave(&ctxt->dev_lock, flags); + list_add_tail(&req_entry->re_entry , + &ctxt->dev_write_req_list); + spin_unlock_irqrestore(&ctxt->dev_lock, flags); + printk(KERN_ERR "diag_write: cannot queue" + " read request\n"); + return -EIO; + } + } else { + printk(KERN_ERR "diag_write: no requests available\n"); + return -EIO; + } + return 0; +} +EXPORT_SYMBOL(diag_write); + +static void diag_write_complete(struct usb_endpoint *ep , + struct usb_request *req) +{ + struct diag_context *ctxt = (struct diag_context *)req->device; + struct diag_req_entry *diag_req = req->context; + struct diag_request *d_req = (struct diag_request *) + diag_req->diag_request; + unsigned long flags; + + if (ctxt == NULL) { + printk(KERN_ERR "diag_write_complete : requesting" + "NULL device pointer\n"); + return; + } + if (req->status == WRITE_COMPLETE) { + if ((req->length >= ep->max_pkt) && + ((req->length % ep->max_pkt) == 0)) { + req->length = 0; + req->device = ctxt; + d_req->actual = req->actual; + d_req->status = req->status; + /* Queue zero length packet */ + usb_ept_queue_xfer(ctxt->epin, req); + return; + } + /* normal completion*/ + spin_lock_irqsave(&ctxt->dev_lock, flags); + list_add_tail(&diag_req->re_entry , + &ctxt->dev_write_req_list); + if (req->length != 0) { + d_req->actual = req->actual; + d_req->status = req->status; + } + spin_unlock_irqrestore(&ctxt->dev_lock , flags); + if ((ctxt->operations) && + (ctxt->operations->diag_char_write_complete)) + ctxt->operations->diag_char_write_complete( + d_req); + } else { + spin_lock_irqsave(&ctxt->dev_lock, flags); + list_add_tail(&diag_req->re_entry , + &ctxt->dev_write_req_list); + d_req->actual = req->actual; + d_req->status = req->status; + spin_unlock_irqrestore(&ctxt->dev_lock , flags); + if ((ctxt->operations) && + (ctxt->operations->diag_char_write_complete)) + ctxt->operations->diag_char_write_complete( + d_req); + } +} +static void diag_read_complete(struct usb_endpoint *ep , + struct usb_request *req) +{ + struct diag_context *ctxt = (struct diag_context *)req->device; + struct diag_req_entry *diag_req = req->context; + struct diag_request *d_req = (struct diag_request *) + diag_req->diag_request; + unsigned long flags; + + if (ctxt == NULL) { + printk(KERN_ERR "diag_read_complete : requesting" + "NULL device pointer\n"); + return; + } + if (req->status == READ_COMPLETE) { + /* normal completion*/ + spin_lock_irqsave(&ctxt->dev_lock, flags); + list_add_tail(&diag_req->re_entry , + &ctxt->dev_read_req_list); + d_req->actual = req->actual; + d_req->status = req->status; + spin_unlock_irqrestore(&ctxt->dev_lock, flags); + if ((ctxt->operations) && + (ctxt->operations->diag_char_read_complete)) + ctxt->operations->diag_char_read_complete( + d_req); + } else { + spin_lock_irqsave(&ctxt->dev_lock, flags); + list_add_tail(&diag_req->re_entry , + &ctxt->dev_read_req_list); + d_req->actual = req->actual; + d_req->status = req->status; + spin_unlock_irqrestore(&ctxt->dev_lock, flags); + if ((ctxt->operations) && + (ctxt->operations->diag_char_read_complete)) + ctxt->operations->diag_char_read_complete( + d_req); + } +} +void usb_config_work_func(struct work_struct *work) +{ + struct diag_context *ctxt = &_context; + if ((ctxt->operations) && + (ctxt->operations->diag_connect)) + ctxt->operations->diag_connect(); +} + +struct usb_descriptor_header *diag_hs_descriptors[4]; +struct usb_descriptor_header *diag_fs_descriptors[4]; + +static int __init diag_init(void) +{ + int r; + struct diag_context *ctxt = &_context; + + diag_hs_descriptors[0] = (struct usb_descriptor_header *)&intf_desc; + diag_hs_descriptors[1] = + (struct usb_descriptor_header *)&hs_bulk_in_desc; + diag_hs_descriptors[2] = + (struct usb_descriptor_header *)&hs_bulk_out_desc; + diag_hs_descriptors[3] = NULL; + + diag_fs_descriptors[0] = (struct usb_descriptor_header *)&intf_desc; + diag_fs_descriptors[1] = + (struct usb_descriptor_header *)&fs_bulk_in_desc; + diag_fs_descriptors[2] = + (struct usb_descriptor_header *)&fs_bulk_out_desc; + diag_fs_descriptors[3] = NULL; + INIT_LIST_HEAD(&ctxt->dev_read_req_list); + INIT_LIST_HEAD(&ctxt->dev_write_req_list); + ctxt->diag_wq = create_singlethread_workqueue("diag"); + if (ctxt->diag_wq == NULL) + return -1; + INIT_WORK(&_context.usb_config_work , usb_config_work_func); + + usb_func_diag.hs_descriptors = diag_hs_descriptors; + usb_func_diag.fs_descriptors = diag_fs_descriptors; + spin_lock_init(&_context.dev_lock); + r = usb_function_register(&usb_func_diag); + if (r < 0) + destroy_workqueue(ctxt->diag_wq); + return r; +} + +module_init(diag_init); +static void __exit diag_exit(void) +{ + struct diag_context *ctxt = &_context; + if (!ctxt) + return; + if (!ctxt) + BUG_ON(1); + + usb_function_unregister(&usb_func_diag); + destroy_workqueue(ctxt->diag_wq); +} +module_exit(diag_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/function/ether.c b/drivers/usb/function/ether.c new file mode 100644 index 00000000000..f31032e9ee0 --- /dev/null +++ b/drivers/usb/function/ether.c @@ -0,0 +1,327 @@ +/* drivers/usb/function/ether.c + * + * Simple Ethernet Function Device + * + * Copyright (C) 2008 Google, Inc. + * Author: Brian Swetland + * + * 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. + * + * Implements the "cdc_subset" bulk-only protocol supported by Linux. + * + */ + +#include +#include +#include + +#include +#include +#include + +#include "usb_function.h" + +/* Ethernet frame is 1514 + FCS, but round up to 512 * 3 so we + * always queue a multiple of the USB max packet size (64 or 512) + */ +#define USB_MTU 1536 + +#define MAX_TX 8 +#define MAX_RX 8 + +struct ether_context { + spinlock_t lock; + struct net_device *dev; + struct usb_endpoint *out; + struct usb_endpoint *in; + + struct list_head rx_reqs; + struct list_head tx_reqs; + + struct net_device_stats stats; +}; + +static int ether_queue_out(struct ether_context *ctxt, + struct usb_request *req); +static void ether_in_complete(struct usb_endpoint *ept, + struct usb_request *req); +static void ether_out_complete(struct usb_endpoint *ept, + struct usb_request *req); + +static void ether_bind(struct usb_endpoint **ept, void *_ctxt) +{ + struct ether_context *ctxt = _ctxt; + struct usb_request *req; + unsigned long flags; + int n; + + ctxt->out = ept[0]; + ctxt->in = ept[1]; + + for (n = 0; n < MAX_RX; n++) { + req = usb_ept_alloc_req(ctxt->out, 0); + if (!req) + break; + req->complete = ether_out_complete; + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->rx_reqs); + spin_unlock_irqrestore(&ctxt->lock, flags); + } + for (n = 0; n < MAX_TX; n++) { + req = usb_ept_alloc_req(ctxt->in, 0); + if (!req) + break; + req->complete = ether_in_complete; + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->tx_reqs); + spin_unlock_irqrestore(&ctxt->lock, flags); + } +} + +static void ether_in_complete(struct usb_endpoint *ept, + struct usb_request *req) +{ + unsigned long flags; + struct sk_buff *skb = req->context; + struct ether_context *ctxt = *((void **) skb->cb); + + if (req->status == 0) { + ctxt->stats.tx_packets++; + ctxt->stats.tx_bytes += req->actual; + } else { + ctxt->stats.tx_errors++; + } + + dev_kfree_skb_any(skb); + + spin_lock_irqsave(&ctxt->lock, flags); + if (list_empty(&ctxt->tx_reqs)) + netif_start_queue(ctxt->dev); + list_add_tail(&req->list, &ctxt->tx_reqs); + spin_unlock_irqrestore(&ctxt->lock, flags); +} + +static void ether_out_complete(struct usb_endpoint *ept, + struct usb_request *req) +{ + struct sk_buff *skb = req->context; + struct ether_context *ctxt = *((void **) skb->cb); + + if (req->status == 0) { + skb_put(skb, req->actual); + skb->protocol = eth_type_trans(skb, ctxt->dev); + ctxt->stats.rx_packets++; + ctxt->stats.rx_bytes += req->actual; + netif_rx(skb); + } else { + dev_kfree_skb_any(skb); + ctxt->stats.rx_errors++; + } + + /* don't bother requeuing if we just went offline */ + if (req->status == -ENODEV) { + unsigned long flags; + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->rx_reqs); + spin_unlock_irqrestore(&ctxt->lock, flags); + } else { + if (ether_queue_out(ctxt, req)) + pr_err("ether_out: cannot requeue\n"); + } +} + +static int ether_queue_out(struct ether_context *ctxt, + struct usb_request *req) +{ + unsigned long flags; + struct sk_buff *skb; + int ret; + + skb = alloc_skb(USB_MTU + NET_IP_ALIGN, GFP_ATOMIC); + if (!skb) { + pr_err("ether_queue_out: failed to alloc skb\n"); + ret = -ENOMEM; + goto fail; + } + + skb_reserve(skb, NET_IP_ALIGN); + + *((void **) skb->cb) = ctxt; + req->buf = skb->data; + req->length = USB_MTU; + req->context = skb; + + ret = usb_ept_queue_xfer(ctxt->out, req); + if (ret) { +fail: + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->rx_reqs); + spin_unlock_irqrestore(&ctxt->lock, flags); + } + + return ret; +} + +static void ether_configure(int configured, void *_ctxt) +{ + unsigned long flags; + struct ether_context *ctxt = _ctxt; + struct usb_request *req; + + pr_info("ether_configure() %d\n", configured); + + if (configured) { + /* we're online -- get all rx requests queued */ + for (;;) { + spin_lock_irqsave(&ctxt->lock, flags); + if (list_empty(&ctxt->rx_reqs)) { + req = 0; + } else { + req = list_first_entry(&ctxt->rx_reqs, + struct usb_request, + list); + list_del(&req->list); + } + spin_unlock_irqrestore(&ctxt->lock, flags); + if (!req) + break; + if (ether_queue_out(ctxt, req)) + break; + } + } else { + /* all pending requests will be canceled */ + } +} + +static struct usb_function usb_func_ether = { + .bind = ether_bind, + .configure = ether_configure, + + .name = "ether", + + .ifc_class = 0x02, + .ifc_subclass = 0x0a, + .ifc_protocol = 0x00, + + .ifc_name = "ether", + + .ifc_ept_count = 2, + .ifc_ept_type = { EPT_BULK_OUT, EPT_BULK_IN }, +}; + +static int usb_ether_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ether_context *ctxt = netdev_priv(dev); + struct usb_request *req; + unsigned long flags; + unsigned len; + + spin_lock_irqsave(&ctxt->lock, flags); + if (list_empty(&ctxt->tx_reqs)) { + req = 0; + } else { + req = list_first_entry(&ctxt->tx_reqs, + struct usb_request, list); + list_del(&req->list); + if (list_empty(&ctxt->tx_reqs)) + netif_stop_queue(dev); + } + spin_unlock_irqrestore(&ctxt->lock, flags); + + if (!req) { + pr_err("usb_ether_xmit: could not obtain tx request\n"); + return 1; + } + + /* ensure that we end with a short packet */ + len = skb->len; + if (!(len & 63) || !(len & 511)) + len++; + + *((void **) skb->cb) = ctxt; + req->context = skb; + req->buf = skb->data; + req->length = len; + + if (usb_ept_queue_xfer(ctxt->in, req)) { + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->tx_reqs); + netif_start_queue(dev); + spin_unlock_irqrestore(&ctxt->lock, flags); + + dev_kfree_skb_any(skb); + ctxt->stats.tx_dropped++; + + pr_err("usb_ether_xmit: could not queue tx request\n"); + } + + return 0; +} + +static int usb_ether_open(struct net_device *dev) +{ + return 0; +} + +static int usb_ether_stop(struct net_device *dev) +{ + return 0; +} + +static struct net_device_stats *usb_ether_get_stats(struct net_device *dev) +{ + struct ether_context *ctxt = netdev_priv(dev); + return &ctxt->stats; +} + +static void __init usb_ether_setup(struct net_device *dev) +{ + struct ether_context *ctxt = netdev_priv(dev); + + pr_info("usb_ether_setup()\n"); + + INIT_LIST_HEAD(&ctxt->rx_reqs); + INIT_LIST_HEAD(&ctxt->tx_reqs); + spin_lock_init(&ctxt->lock); + ctxt->dev = dev; + + dev->open = usb_ether_open; + dev->stop = usb_ether_stop; + dev->hard_start_xmit = usb_ether_xmit; + dev->get_stats = usb_ether_get_stats; + dev->watchdog_timeo = 20; + + ether_setup(dev); + + random_ether_addr(dev->dev_addr); +} + +static int __init ether_init(void) +{ + struct net_device *dev; + int ret; + + dev = alloc_netdev(sizeof(struct ether_context), + "usb%d", usb_ether_setup); + if (!dev) + return -ENOMEM; + + ret = register_netdev(dev); + if (ret) { + free_netdev(dev); + } else { + struct ether_context *ctxt = netdev_priv(dev); + usb_func_ether.context = ctxt; + usb_function_register(&usb_func_ether); + } + return ret; +} + +module_init(ether_init); diff --git a/drivers/usb/function/ether_cdc_ecm.c b/drivers/usb/function/ether_cdc_ecm.c new file mode 100644 index 00000000000..8fa5af13887 --- /dev/null +++ b/drivers/usb/function/ether_cdc_ecm.c @@ -0,0 +1,1337 @@ +/* + * ether_cdc_ecm.c -- Ethernet Function driver, with CDC + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * This file has been derived from gadget/ether.c + * + * Copyright (C) 2003-2005 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +/* #define VERBOSE_DEBUG */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "usb_function.h" + +/*-------------------------------------------------------------------------*/ + +/* + * Ethernet function driver -- with CDC options + * Builds on hardware support for a full duplex link. + * + * CDC Ethernet is the standard USB solution for sending Ethernet frames + * using USB. Real hardware tends to use the same framing protocol but look + * different for control features. This driver strongly prefers to use + * this USB-IF standard as its open-systems interoperability solution; + * most host side USB stacks (except from Microsoft) support it. + */ + +#define DRIVER_DESC "Ethernet Function CDC ECM" +#define DRIVER_VERSION "1.0" + +static const char shortname[] = "ether"; +static const char driver_desc[] = DRIVER_DESC; + +static unsigned int string_data; +static unsigned int string_control; +static unsigned int string_ethaddr; +#define RX_EXTRA 20 /* guard against rx overflows */ + + + +/* outgoing packet filters. */ +#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \ + | USB_CDC_PACKET_TYPE_ALL_MULTICAST \ + | USB_CDC_PACKET_TYPE_PROMISCUOUS \ + | USB_CDC_PACKET_TYPE_DIRECTED) + +/*-------------------------------------------------------------------------*/ + +struct eth_dev { + spinlock_t lock; + struct usb_request *req; /* for control responses */ + struct usb_request *stat_req; /* for cdc status */ + + unsigned configured:1; + struct usb_endpoint *in_ep, *out_ep, *status_ep; + + spinlock_t req_lock; + struct list_head tx_reqs, rx_reqs; + + struct net_device *net; + struct net_device_stats stats; + atomic_t tx_qlen; + + struct work_struct work; + unsigned zlp:1; + unsigned suspended:1; + u16 cdc_filter; + unsigned long todo; +#define WORK_RX_MEMORY 0 + u8 host_mac[ETH_ALEN]; + + int alt_set; +}; + +static struct usb_function usb_func_ether; + +/* Ethernet function descriptors */ +#define USB_DT_IAD_SIZE 8 +struct usb_interface_assoc_descriptor eth_IAD = { + .bLength = USB_DT_IAD_SIZE, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + .bInterfaceCount = 2, + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bFunctionProtocol = USB_CDC_PROTO_NONE, + .iFunction = 0, +}; + +struct usb_interface_descriptor eth_control_intf = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, +}; + +struct usb_cdc_header_desc eth_header_desc = { + .bLength = sizeof(struct usb_cdc_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = __constant_cpu_to_le16(0x0110), +}; + +struct usb_cdc_union_desc eth_union_desc = { + .bLength = sizeof(struct usb_cdc_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, +}; + +struct usb_cdc_ether_desc eth_ether_desc = { + .bLength = sizeof(struct usb_cdc_ether_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, + /* this descriptor actually adds value, surprise! */ + .bmEthernetStatistics = __constant_cpu_to_le32(0), /* no statistics */ + .wMaxSegmentSize = __constant_cpu_to_le16(ETH_FRAME_LEN), + .wNumberMCFilters = __constant_cpu_to_le16(0), + .bNumberPowerFilters = 0, +}; + +struct usb_endpoint_descriptor eth_control_intf_hs_int_in_ep_desc = { + .bDescriptorType = USB_DT_ENDPOINT, + .bLength = USB_DT_ENDPOINT_SIZE, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .bInterval = 4, + .wMaxPacketSize = 64, +}; + +struct usb_endpoint_descriptor eth_control_intf_fs_int_in_ep_desc = { + .bDescriptorType = USB_DT_ENDPOINT, + .bLength = USB_DT_ENDPOINT_SIZE, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .bInterval = 4, + .wMaxPacketSize = 64, +}; + +struct usb_interface_descriptor eth_data_alt_zero_intf = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, +}; + +struct usb_interface_descriptor eth_data_alt_one_intf = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA , + .bInterfaceSubClass = 0, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, +}; + +struct usb_endpoint_descriptor eth_data_intf_hs_bulk_out_ep_desc = { + .bDescriptorType = USB_DT_ENDPOINT, + .bLength = USB_DT_ENDPOINT_SIZE, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +struct usb_endpoint_descriptor eth_data_intf_fs_bulk_out_ep_desc = { + .bDescriptorType = USB_DT_ENDPOINT, + .bLength = USB_DT_ENDPOINT_SIZE, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +struct usb_endpoint_descriptor eth_data_intf_hs_bulk_in_ep_desc = { + .bDescriptorType = USB_DT_ENDPOINT, + .bLength = USB_DT_ENDPOINT_SIZE, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +struct usb_endpoint_descriptor eth_data_intf_fs_bulk_in_ep_desc = { + .bDescriptorType = USB_DT_ENDPOINT, + .bLength = USB_DT_ENDPOINT_SIZE, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +struct eth_dev *eth_device; + +/* Some systems will want different product identifers published in the + * device descriptor, either numbers or strings or both. These string + * parameters are in UTF-8 (superset of ASCII's 7 bit characters). + */ + + +/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */ +static char *dev_addr; +module_param(dev_addr, charp, S_IRUGO); +MODULE_PARM_DESC(dev_addr, "Device Ethernet Address"); + +/* this address is invisible to ifconfig */ +static char *host_addr; +module_param(host_addr, charp, S_IRUGO); +MODULE_PARM_DESC(host_addr, "Host Ethernet Address"); + +static char ethaddr[2 * ETH_ALEN + 1]; +static int eth_bound; + +#define DEFAULT_QLEN 2 /* double buffering by default */ + +/* peak bulk transfer bits-per-second */ +#define HS_BPS (13 * 512 * 8 * 1000 * 8) + +/* for dual-speed hardware, use deeper queues at highspeed */ +#define qlen (DEFAULT_QLEN * 5) /* High Speed */ + +/*-------------------------------------------------------------------------*/ + +#define xprintk(d, level, fmt, args...) \ + printk(level "%s: " fmt, (d)->net->name, ## args) + +#ifdef DEBUG +#undef DEBUG +#define DEBUG(dev, fmt, args...) \ + xprintk(dev, KERN_DEBUG, fmt, ## args) +#else +#define DEBUG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VDEBUG DEBUG +#else +#define VDEBUG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + +#define ERROR(dev, fmt, args...) \ + xprintk(dev, KERN_ERR, fmt, ## args) +#ifdef WARN +#undef WARN +#endif +#define WARN(dev, fmt, args...) \ + xprintk(dev, KERN_WARNING, fmt, ## args) +#define INFO(dev, fmt, args...) \ + xprintk(dev, KERN_INFO, fmt, ## args) + +/*-------------------------------------------------------------------------*/ + +/* include the status endpoint if we can, even where it's optional. + * use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one + * packet, to simplify cancellation; and a big transfer interval, to + * waste less bandwidth. + * + * some drivers (like Linux 2.4 cdc-ether!) "need" it to exist even + * if they ignore the connect/disconnect notifications that real ether + * can provide. more advanced cdc configurations might want to support + * encapsulated commands (vendor-specific, using control-OUT). + */ +#define STATUS_BYTECOUNT 16 /* 8 byte header + data */ + + +static void eth_start(struct eth_dev *dev, gfp_t gfp_flags); +static int alloc_requests(struct eth_dev *dev, unsigned n, gfp_t gfp_flags); + +static int set_ether_config(struct eth_dev *dev, gfp_t gfp_flags) +{ + int result = 0; + + if (dev->status_ep) + usb_ept_enable(dev->status_ep, 1); + + result = alloc_requests(dev, qlen , gfp_flags); + if (result == 0) + DEBUG(dev, "qlen %d\n", qlen); + + /* caller is responsible for cleanup on error */ + return result; +} + +static void eth_reset_config(struct eth_dev *dev) +{ + struct usb_request *req; + unsigned long flags; + + DEBUG(dev, "%s\n", __func__); + + if (!dev) + return; + if (!dev->net) + return; + + if (dev->configured == 0) + return; + netif_stop_queue(dev->net); + netif_carrier_off(dev->net); + + /* disable endpoints, forcing (synchronous) completion of + * pending i/o. then free the requests. + */ + if (dev->in_ep) { + usb_ept_enable(dev->in_ep, 0); + spin_lock_irqsave(&dev->req_lock, flags); + while (likely(!list_empty(&dev->tx_reqs))) { + req = container_of(dev->tx_reqs.next, + struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&dev->req_lock, flags); + usb_ept_free_req(dev->in_ep, req); + spin_lock_irqsave(&dev->req_lock, flags); + } + spin_unlock_irqrestore(&dev->req_lock, flags); + } + if (dev->out_ep) { + usb_ept_enable(dev->out_ep, 0); + spin_lock_irqsave(&dev->req_lock, flags); + while (likely(!list_empty(&dev->rx_reqs))) { + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&dev->req_lock, flags); + usb_ept_free_req(dev->out_ep, req); + spin_lock_irqsave(&dev->req_lock, flags); + } + spin_unlock_irqrestore(&dev->req_lock, flags); + } + + if (dev->status_ep) + usb_ept_free_req(dev->status_ep, 0); + dev->cdc_filter = 0; + dev->configured = 0; +} + +/* change our operational config. must agree with the code + * that returns config descriptors, and altsetting code. + */ +static int eth_set_config(struct eth_dev *dev, gfp_t gfp_flags) +{ + int result = 0; + + eth_reset_config(dev); + result = set_ether_config(dev, gfp_flags); + if (result) + eth_reset_config(dev); + else + dev->configured = 1; + return result; +} + +static void eth_configure(int configured, void *_ctxt) +{ + int result = 0; + struct eth_dev *dev = (struct eth_dev *) _ctxt; + if (!dev) + return ; + if (!eth_bound) + return; + + if (!configured) { + eth_reset_config(dev); + return ; + } + if (dev->configured == 1) + return ; + if (usb_msm_get_speed() == USB_SPEED_HIGH) { + usb_configure_endpoint(dev->status_ep, + ð_control_intf_hs_int_in_ep_desc); + usb_configure_endpoint(dev->in_ep, + ð_data_intf_hs_bulk_in_ep_desc); + usb_configure_endpoint(dev->out_ep, + ð_data_intf_hs_bulk_out_ep_desc); + } else { + usb_configure_endpoint(dev->status_ep, + ð_control_intf_fs_int_in_ep_desc); + usb_configure_endpoint(dev->in_ep, + ð_data_intf_fs_bulk_in_ep_desc); + usb_configure_endpoint(dev->out_ep, + ð_data_intf_fs_bulk_out_ep_desc); + } + result = eth_set_config(dev, GFP_ATOMIC); +} +/* The interrupt endpoint is used in CDC networking models (Ethernet, ATM) + * only to notify the host about link status changes (which we support) + * Since we want this CDC Ethernet code to be vendor-neutral, only one + * status request is ever queued. + */ + +static void +eth_status_complete(struct usb_endpoint *ep, struct usb_request *req) +{ + struct usb_cdc_notification *event = req->buf; + int value = req->status; + + /* issue the second notification if host reads the first */ + if (event->bNotificationType == USB_CDC_NOTIFY_NETWORK_CONNECTION + && value == 0) { + __le32 *data = req->buf + sizeof *event; + + event->bmRequestType = 0xA1; + event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE; + event->wValue = __constant_cpu_to_le16(0); + event->wIndex = __constant_cpu_to_le16( + eth_data_alt_one_intf.bInterfaceNumber); + event->wLength = __constant_cpu_to_le16(8); + + /* SPEED_CHANGE data is up/down speeds in bits/sec */ + data[0] = data[1] = cpu_to_le32(HS_BPS); + + req->length = STATUS_BYTECOUNT; + value = usb_ept_queue_xfer(ep, req); + DEBUG(dev, "send SPEED_CHANGE --> %d\n", value); + if (value == 0) + return; + } else if (value != -ECONNRESET) + DEBUG(dev, "event %02x --> %d\n", + event->bNotificationType, value); + req->context = NULL; +} + +static void issue_start_status(struct eth_dev *dev) +{ + struct usb_request *req = dev->stat_req; + struct usb_cdc_notification *event; + int value; + + DEBUG(dev, "%s, flush old status first\n", __func__); + + /* flush old status + * + * FIXME ugly idiom, maybe we'd be better with just + * a "cancel the whole queue" primitive since any + * unlink-one primitive has way too many error modes. + * here, we "know" toggle is already clear... + * + * FIXME iff req->context != null just dequeue it + */ + usb_ept_enable(dev->status_ep, 0); + usb_ept_enable(dev->status_ep, 1); + + /* 3.8.1 says to issue first NETWORK_CONNECTION, then + * a SPEED_CHANGE. could be useful in some configs. + */ + event = req->buf; + event->bmRequestType = 0xA1; + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + event->wValue = __constant_cpu_to_le16(1); /* connected */ + event->wIndex = __constant_cpu_to_le16( + eth_data_alt_one_intf.bInterfaceNumber); + event->wLength = 0; + + req->length = sizeof *event; + req->complete = eth_status_complete; + req->context = dev; + + value = usb_ept_queue_xfer(dev->status_ep, req); + if (value < 0) + DEBUG(dev, "status buf queue --> %d\n", value); +} + +static int eth_set_interface(int wIndex, int wValue, void *_ctxt) +{ + struct eth_dev *dev = eth_device; + unsigned long flags; + + if (dev == NULL) + return 1; + + if ((wIndex == eth_data_alt_one_intf.bInterfaceNumber) + && (wValue == 1)) { + dev->alt_set = 1; + usb_ept_enable(dev->in_ep, 1); + usb_ept_enable(dev->out_ep, 1); + dev->cdc_filter = DEFAULT_FILTER; + netif_carrier_on(dev->net); + issue_start_status(dev); + if (netif_running(dev->net)) { + spin_lock_irqsave(&dev->lock, flags); + eth_start(dev, GFP_ATOMIC); + spin_unlock_irqrestore(&dev->lock, flags); + } + } else { + dev->alt_set = 0; + netif_stop_queue(dev->net); + netif_carrier_off(dev->net); + } + return 0; +} + +static int eth_get_interface(int wIndex, void *_ctxt) +{ + struct eth_dev *dev = eth_device; + + return dev->alt_set; +} + +/* + * The setup() callback implements all the ep0 functionality that's not + * handled lower down. CDC has a number of less-common features: + * + * - class-specific descriptors for the control interface + * - class-specific control requests + */ +static int +eth_setup(struct usb_ctrlrequest *ctrl, void *buf, int len, void *_ctxt) +{ + struct eth_dev *dev = (struct eth_dev *) _ctxt; + int value = -EOPNOTSUPP; + u16 wIndex = le16_to_cpu(ctrl->wIndex); + u16 wValue = le16_to_cpu(ctrl->wValue); + u16 wLength = le16_to_cpu(ctrl->wLength); + u16 data_int = eth_data_alt_one_intf.bInterfaceNumber; + u16 ctrl_int = eth_control_intf.bInterfaceNumber; + switch (ctrl->bRequest) { + case USB_CDC_SET_ETHERNET_PACKET_FILTER: + /* see 6.2.30: no data, wIndex = interface, + * wValue = packet filter bitmap + */ + if (ctrl->bRequestType != (USB_TYPE_CLASS|USB_RECIP_INTERFACE) + || wLength != 0 + || ((wIndex != data_int) && (wIndex != ctrl_int))) + break; + DEBUG(dev, "packet filter %02x\n", wValue); + dev->cdc_filter = wValue; + value = 0; + break; + + /* and potentially: + * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS: + * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_STATISTIC: + */ + + default: + VDEBUG(dev, + "unknown control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + wValue, wIndex, wLength); + } + return value; +} + + +static void eth_disconnect(void *_ctxt) +{ + struct eth_dev *dev = (struct eth_dev *) _ctxt; + unsigned long flags; + + printk(KERN_INFO "eth_disconnect()\n"); + spin_lock_irqsave(&dev->lock, flags); + netif_stop_queue(dev->net); + netif_carrier_off(dev->net); + eth_reset_config(dev); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/*-------------------------------------------------------------------------*/ + +/* NETWORK DRIVER HOOKUP (to the layer above this driver) */ + +static int usb_eth_change_mtu(struct net_device *net, int new_mtu) +{ + struct eth_dev *dev = netdev_priv(net); + + if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN) + return -ERANGE; + /* no zero-length packet read wanted after mtu-sized packets */ + if (((new_mtu + sizeof(struct ethhdr)) % + (usb_ept_get_max_packet(dev->in_ep))) == 0) + return -EDOM; + net->mtu = new_mtu; + return 0; +} + +static struct net_device_stats *eth_get_stats(struct net_device *net) +{ + return &((struct eth_dev *)netdev_priv(net))->stats; +} + +static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p) +{ + strlcpy(p->driver, shortname, sizeof p->driver); + strlcpy(p->version, DRIVER_VERSION, sizeof p->version); + strlcpy(p->fw_version, "ethernet", sizeof p->fw_version); +} + +static u32 eth_get_link(struct net_device *net) +{ + return 1; +} + +static struct ethtool_ops ops = { + .get_drvinfo = eth_get_drvinfo, + .get_link = eth_get_link +}; + +static void defer_kevent(struct eth_dev *dev, int flag) +{ + if (test_and_set_bit(flag, &dev->todo)) + return; + if (!schedule_work(&dev->work)) + ERROR(dev, "kevent %d may have been dropped\n", flag); + else + DEBUG(dev, "kevent %d scheduled\n", flag); +} + +static void rx_complete(struct usb_endpoint *ep, struct usb_request *req); + +static int +rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) +{ + struct sk_buff *skb; + int retval = -ENOMEM; + size_t size; + unsigned long flags; + /* Padding up to RX_EXTRA handles minor disagreements with host. + * Normally we use the USB "terminate on short read" convention; + * so allow up to (N*max_pkt), since that memory is normally + * already allocated. Some hardware doesn't deal well with short + * reads (e.g. DMA must be N*max_pkt), so for now don't trim a + * byte off the end (to force hardware errors on overflow). + */ + size = (sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA); + size += usb_ept_get_max_packet(dev->out_ep) - 1; + size -= size % usb_ept_get_max_packet(dev->out_ep); + skb = alloc_skb(size + NET_IP_ALIGN, gfp_flags); + if (skb == NULL) { + DEBUG(dev, "no rx skb\n"); + goto enomem; + } + + /* Some platforms perform better when IP packets are aligned, + * but on at least one, checksumming fails otherwise. + */ + skb_reserve(skb, NET_IP_ALIGN); + + req->buf = skb->data; + req->length = size; + req->complete = rx_complete; + req->context = skb; + + retval = usb_ept_queue_xfer(dev->out_ep, req); + if (retval == -ENOMEM) +enomem: + defer_kevent(dev, WORK_RX_MEMORY); + if (retval) { + DEBUG(dev, "rx submit --> %d\n", retval); + if (skb) + dev_kfree_skb_any(skb); + spin_lock_irqsave(&dev->req_lock, flags); + list_add(&req->list, &dev->rx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); + } + return retval; +} + +static void rx_complete(struct usb_endpoint *ep, struct usb_request *req) +{ + struct sk_buff *skb = req->context; + struct eth_dev *dev = eth_device; + int status = req->status; + switch (status) { + + /* normal completion */ + case 0: + skb_put(skb, req->actual); + /* we know MaxPacketsPerTransfer == 1 here */ + if (status < 0 + || ETH_HLEN > skb->len + || skb->len > ETH_FRAME_LEN) { + dev->stats.rx_errors++; + dev->stats.rx_length_errors++; + DEBUG(dev, "rx length %d\n", skb->len); + break; + } + + skb->protocol = eth_type_trans(skb, dev->net); + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + + /* no buffer copies needed, unless hardware can't + * use skb buffers. + */ + status = netif_rx(skb); + skb = NULL; + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + VDEBUG(dev, "rx shutdown, code %d\n", status); + goto quiesce; + + /* for hardware automagic (such as pxa) */ + case -ECONNABORTED: /* endpoint reset */ + DEBUG(dev, "rx %s reset\n", ep->name); + defer_kevent(dev, WORK_RX_MEMORY); +quiesce: + dev_kfree_skb_any(skb); + goto clean; + + /* data overrun */ + case -EOVERFLOW: + dev->stats.rx_over_errors++; + /* FALLTHROUGH */ + + default: + dev->stats.rx_errors++; + DEBUG(dev, "rx status %d\n", status); + break; + } + + if (skb) + dev_kfree_skb_any(skb); + if (!netif_running(dev->net)) { +clean: + spin_lock(&dev->req_lock); + list_add(&req->list, &dev->rx_reqs); + spin_unlock(&dev->req_lock); + req = NULL; + } + if (req) + rx_submit(dev, req, GFP_ATOMIC); +} + +static int prealloc(struct list_head *list, struct usb_endpoint *ep, + unsigned n, gfp_t gfp_flags) +{ + unsigned i; + struct usb_request *req; + + if (!n) + return -ENOMEM; + + /* queue/recycle up to N requests */ + i = n; + list_for_each_entry(req, list, list) { + if (i-- == 0) + goto extra; + } + while (i--) { + /* CDC ECM uses skb buffer pointer for requests */ + req = usb_ept_alloc_req(ep, 0); + if (!req) + return list_empty(list) ? -ENOMEM : 0; + list_add(&req->list, list); + } + return 0; + +extra: + /* free extras */ + for (;;) { + struct list_head *next; + + next = req->list.next; + list_del(&req->list); + usb_ept_free_req(ep, req); + + if (next == list) + break; + + req = container_of(next, struct usb_request, list); + } + return 0; +} + +static int alloc_requests(struct eth_dev *dev, unsigned n, gfp_t gfp_flags) +{ + int status; + unsigned long flags; + + spin_lock_irqsave(&dev->req_lock, flags); + status = prealloc(&dev->tx_reqs, dev->in_ep, n, gfp_flags); + if (status < 0) + goto fail; + status = prealloc(&dev->rx_reqs, dev->out_ep, n, gfp_flags); + if (status < 0) + goto fail; + goto done; +fail: + DEBUG(dev, "can't alloc requests\n"); +done: + spin_unlock_irqrestore(&dev->req_lock, flags); + return status; +} + +static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags) +{ + struct usb_request *req; + unsigned long flags; + /* fill unused rxq slots with some skb */ + spin_lock_irqsave(&dev->req_lock, flags); + while (!list_empty(&dev->rx_reqs)) { + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del_init(&req->list); + spin_unlock_irqrestore(&dev->req_lock, flags); + + if (rx_submit(dev, req, gfp_flags) < 0) { + defer_kevent(dev, WORK_RX_MEMORY); + return; + } + + spin_lock_irqsave(&dev->req_lock, flags); + } + spin_unlock_irqrestore(&dev->req_lock, flags); +} + +static void eth_work(struct work_struct *work) +{ + struct eth_dev *dev = container_of(work, struct eth_dev, work); + + if (test_and_clear_bit(WORK_RX_MEMORY, &dev->todo)) { + if (netif_running(dev->net)) + rx_fill(dev, GFP_KERNEL); + } + + if (dev->todo) + DEBUG(dev, "work done, flags = 0x%lx\n", dev->todo); +} + +static void tx_complete(struct usb_endpoint *ep, struct usb_request *req) +{ + struct sk_buff *skb = req->context; + struct eth_dev *dev = eth_device; + + switch (req->status) { + default: + dev->stats.tx_errors++; + VDEBUG(dev, "tx err %d\n", req->status); + /* FALLTHROUGH */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + break; + case 0: + dev->stats.tx_bytes += skb->len; + } + dev->stats.tx_packets++; + + spin_lock(&dev->req_lock); + list_add(&req->list, &dev->tx_reqs); + spin_unlock(&dev->req_lock); + dev_kfree_skb_any(skb); + + atomic_dec(&dev->tx_qlen); + if (netif_carrier_ok(dev->net)) + netif_wake_queue(dev->net); +} + +static inline int eth_is_promisc(struct eth_dev *dev) +{ + return dev->cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS; +} + +static int eth_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + int length = skb->len; + int retval; + struct usb_request *req = NULL; + unsigned long flags; + + /* apply outgoing CDC filters */ + if (!eth_is_promisc(dev)) { + u8 *dest = skb->data; + + if (is_multicast_ether_addr(dest)) { + u16 type; + + /* ignores USB_CDC_PACKET_TYPE_MULTICAST and host + * SET_ETHERNET_MULTICAST_FILTERS requests + */ + if (is_broadcast_ether_addr(dest)) + type = USB_CDC_PACKET_TYPE_BROADCAST; + else + type = USB_CDC_PACKET_TYPE_ALL_MULTICAST; + if (!(dev->cdc_filter & type)) { + dev_kfree_skb_any(skb); + return 0; + } + } + /* ignores USB_CDC_PACKET_TYPE_DIRECTED */ + } + + spin_lock_irqsave(&dev->req_lock, flags); + /* + * this freelist can be empty if an interrupt triggered disconnect() + * and reconfigured the function (shutting down this queue) after the + * network stack decided to xmit but before we got the spinlock. + */ + if (list_empty(&dev->tx_reqs)) { + spin_unlock_irqrestore(&dev->req_lock, flags); + return 1; + } + + req = container_of(dev->tx_reqs.next, struct usb_request, list); + list_del(&req->list); + + /* temporarily stop TX queue when the freelist empties */ + if (list_empty(&dev->tx_reqs)) + netif_stop_queue(net); + spin_unlock_irqrestore(&dev->req_lock, flags); + + /* no buffer copies needed, unless the network stack did it + * or the hardware can't use skb buffers. + */ + req->buf = skb->data; + req->context = skb; + req->complete = tx_complete; + + /* use zlp framing on tx for strict CDC-Ether conformance, + * though any robust network rx path ignores extra padding. + * and some hardware doesn't like to write zlps. + */ + if (!dev->zlp && (length % usb_ept_get_max_packet(dev->in_ep)) == 0) + length++; + + req->length = length; + + retval = usb_ept_queue_xfer(dev->in_ep, req); + switch (retval) { + default: + DEBUG(dev, "tx queue err %d\n", retval); + break; + case 0: + net->trans_start = jiffies; + atomic_inc(&dev->tx_qlen); + } + if (retval) { + dev->stats.tx_dropped++; + dev_kfree_skb_any(skb); + spin_lock_irqsave(&dev->req_lock, flags); + if (list_empty(&dev->tx_reqs)) + netif_start_queue(net); + list_add(&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); + } + return 0; +} + + +static void eth_start(struct eth_dev *dev, gfp_t gfp_flags) +{ + DEBUG(dev, "%s\n", __func__); + + /* fill the rx queue */ + rx_fill(dev, gfp_flags); + + /* and open the tx floodgates */ + atomic_set(&dev->tx_qlen, 0); + netif_wake_queue(dev->net); +} + +static int eth_open(struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + + DEBUG(dev, "%s\n", __func__); + if (netif_carrier_ok(dev->net)) + eth_start(dev, GFP_KERNEL); + return 0; +} + +static int eth_stop(struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + + VDEBUG(dev, "%s\n", __func__); + netif_stop_queue(net); + + DEBUG(dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n", + dev->stats.rx_packets, dev->stats.tx_packets, + dev->stats.rx_errors, dev->stats.tx_errors + ); + + /* ensure there are no more active requests */ + if (dev->configured) { + usb_ept_enable(dev->in_ep, 0); + usb_ept_enable(dev->out_ep, 0); + if (netif_carrier_ok(dev->net)) { + DEBUG(dev, "host still using in/out endpoints\n"); + /* FIXME idiom may leave toggle wrong here */ + usb_ept_enable(dev->in_ep, 1); + usb_ept_enable(dev->out_ep, 1); + } + if (dev->status_ep) { + usb_ept_enable(dev->status_ep, 0); + usb_ept_enable(dev->status_ep, 1); + } + } + + return 0; +} + + +static u8 __devinit nibble(unsigned char c) +{ + if (likely(isdigit(c))) + return c - '0'; + c = toupper(c); + if (likely(isxdigit(c))) + return 10 + c - 'A'; + return 0; +} + +static int __devinit get_ether_addr(const char *str, u8 *dev_addr) +{ + if (str) { + unsigned i; + + for (i = 0; i < 6; i++) { + unsigned char num; + + if ((*str == '.') || (*str == ':')) + str++; + num = nibble(*str++) << 4; + num |= (nibble(*str++)); + dev_addr[i] = num; + } + if (is_valid_ether_addr(dev_addr)) + return 0; + } + random_ether_addr(dev_addr); + return 1; +} + +static void eth_unbind(void *_ctxt) +{ + struct eth_dev *dev = (struct eth_dev *)_ctxt ; + + pr_debug("%s ()\n", __func__); + if (!dev) + return ; + if (!eth_bound) + return; + + if (dev->in_ep) { + usb_ept_fifo_flush(dev->in_ep); + usb_ept_enable(dev->in_ep, 0); + usb_free_endpoint(dev->in_ep); + } + if (dev->out_ep) { + usb_ept_fifo_flush(dev->out_ep); + usb_ept_enable(dev->out_ep, 0); + usb_free_endpoint(dev->out_ep); + } + if (dev->status_ep) { + usb_ept_fifo_flush(dev->status_ep); + usb_ept_enable(dev->status_ep, 0); + usb_free_endpoint(dev->status_ep); + } + + + if (dev->net) { + unregister_netdev(dev->net); + free_netdev(dev->net); + } + eth_bound = 0; + return ; +} + +static void eth_bind(void *_ctxt) +{ + struct eth_dev *dev; + struct net_device *net; + u8 zlp = 1; + struct usb_endpoint *in_ep, *out_ep, *status_ep = NULL; + int status = -ENOMEM; + int ret; + struct device *get_dev; + + get_dev = usb_get_device(); + + ret = usb_msm_get_next_ifc_number(&usb_func_ether); + eth_control_intf.bInterfaceNumber = ret; + eth_control_intf.iInterface = string_control; + eth_IAD.bFirstInterface = ret; + eth_union_desc.bMasterInterface0 = ret; + + ret = usb_msm_get_next_ifc_number(&usb_func_ether); + eth_data_alt_zero_intf.bInterfaceNumber = ret; + eth_data_alt_zero_intf.iInterface = 0; + eth_data_alt_one_intf.bInterfaceNumber = ret; + eth_data_alt_one_intf.iInterface = string_data; + eth_union_desc.bSlaveInterface0 = ret; + + /* Enable IAD */ + usb_msm_enable_iad(); + + /* Configuring STATUS endpoint */ + status_ep = usb_alloc_endpoint(USB_DIR_IN); + status_ep->max_pkt = 64; + + eth_control_intf_hs_int_in_ep_desc.bEndpointAddress = + USB_DIR_IN | status_ep->num; + eth_control_intf_hs_int_in_ep_desc.wMaxPacketSize = + status_ep->max_pkt; + eth_control_intf_fs_int_in_ep_desc.bEndpointAddress = + USB_DIR_IN | status_ep->num; + eth_control_intf_hs_int_in_ep_desc.bInterval = 4; + + /* Configuring OUT endpoint */ + out_ep = usb_alloc_endpoint(USB_DIR_OUT); + out_ep->max_pkt = 512; + eth_data_intf_hs_bulk_out_ep_desc.bEndpointAddress = + USB_DIR_OUT | out_ep->num; + eth_data_intf_hs_bulk_out_ep_desc.wMaxPacketSize = out_ep->max_pkt; + eth_data_intf_fs_bulk_out_ep_desc.bEndpointAddress = + USB_DIR_OUT | out_ep->num; + + /*Configuring IN Endpoint*/ + in_ep = usb_alloc_endpoint(USB_DIR_IN); + in_ep->max_pkt = 512; + eth_data_intf_hs_bulk_in_ep_desc.bEndpointAddress = + USB_DIR_IN | in_ep->num; + eth_data_intf_hs_bulk_in_ep_desc.wMaxPacketSize = in_ep->max_pkt; + eth_data_intf_fs_bulk_in_ep_desc.bEndpointAddress = + USB_DIR_IN | in_ep->num; + + net = alloc_etherdev(sizeof *dev); + if (!net) { + printk(KERN_DEBUG "eth_bind: alloc_etherdev failed \n"); + return ; + } + dev = netdev_priv(net); + spin_lock_init(&dev->lock); + spin_lock_init(&dev->req_lock); + INIT_WORK(&dev->work, eth_work); + INIT_LIST_HEAD(&dev->tx_reqs); + INIT_LIST_HEAD(&dev->rx_reqs); + + /* network device setup */ + dev->net = net; + strcpy(net->name, "usb%d"); + dev->zlp = zlp; + dev->in_ep = in_ep; + dev->out_ep = out_ep; + dev->status_ep = status_ep; + + eth_device = dev; + usb_func_ether.context = eth_device; + + /* Module params for these addresses should come from ID proms. + * The host side address is used with CDC, and commonly + * ends up in a persistent config database. It's not clear if + * host side code for the SAFE thing cares -- its original BLAN + * thing didn't, Sharp never assigned those addresses on Zaurii. + */ + if (get_ether_addr(dev_addr, net->dev_addr)) + dev_warn(get_dev, + "using random %s ethernet address\n", "self"); + if (get_ether_addr(host_addr, dev->host_mac)) + dev_warn(get_dev, + "using random %s ethernet address\n", "host"); + snprintf(ethaddr, sizeof ethaddr, "%02X%02X%02X%02X%02X%02X", + dev->host_mac[0], dev->host_mac[1], + dev->host_mac[2], dev->host_mac[3], + dev->host_mac[4], dev->host_mac[5]); + + net->change_mtu = usb_eth_change_mtu; + net->get_stats = eth_get_stats; + net->hard_start_xmit = eth_start_xmit; + net->open = eth_open; + net->stop = eth_stop; + /* watchdog_timeo, tx_timeout ... + * set_multicast_list */ + SET_ETHTOOL_OPS(net, &ops); + /* ... and maybe likewise for status transfer */ + if (dev->status_ep) { + dev->stat_req = usb_ept_alloc_req(dev->status_ep, + STATUS_BYTECOUNT); + if (!dev->stat_req) { + usb_ept_free_req(dev->status_ep, dev->req); + goto fail; + } + dev->stat_req->context = NULL; + } + /* finish hookup to lower layer ... */ + /* two kinds of host-initiated state changes: + * - iff DATA transfer is active, carrier is "on" + * - tx queueing enabled if open *and* carrier is "on" + */ + netif_stop_queue(dev->net); + netif_carrier_off(dev->net); + + SET_NETDEV_DEV(dev->net, get_dev); + status = register_netdev(dev->net); + if (status < 0) + goto fail1; + + INFO(dev, "%s, version: " DRIVER_VERSION "\n", driver_desc); + INFO(dev, "MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + net->dev_addr[0], net->dev_addr[1], + net->dev_addr[2], net->dev_addr[3], + net->dev_addr[4], net->dev_addr[5]); + + INFO(dev, "HOST MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + dev->host_mac[0], dev->host_mac[1], + dev->host_mac[2], dev->host_mac[3], + dev->host_mac[4], dev->host_mac[5]); + + string_data = usb_msm_get_next_strdesc_id("Ethernet Data"); + if (string_data != 0) { + string_control = usb_msm_get_next_strdesc_id + ("CDC Communications Control"); + if (string_control != 0) { + string_ethaddr = usb_msm_get_next_strdesc_id(ethaddr); + if (string_ethaddr != 0) { + eth_ether_desc.iMACAddress = string_ethaddr; + eth_bound = 1; + return ; + } + } + } +fail1: + dev_dbg(get_dev, "register_netdev failed, %d\n", status); +fail: + eth_bound = 1; + printk(KERN_INFO"eth_bind: returning from eth_bind\n"); + return ; +} + + +static struct usb_function usb_func_ether = { + .name = "ethernet", + .bind = eth_bind, + .unbind = eth_unbind, + .configure = eth_configure, + .disconnect = eth_disconnect, + .setup = eth_setup, + .set_interface = eth_set_interface, + .get_interface = eth_get_interface, +}; + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); + +#define TOTAL_ETH_DESCRIPTORS 11 +struct usb_descriptor_header *eth_hs_descriptors[TOTAL_ETH_DESCRIPTORS]; +struct usb_descriptor_header *eth_fs_descriptors[TOTAL_ETH_DESCRIPTORS]; + +static int __init init(void) +{ + int rc; + + eth_hs_descriptors[0] = (struct usb_descriptor_header *) + ð_IAD; + eth_hs_descriptors[1] = (struct usb_descriptor_header *) + ð_control_intf; + eth_hs_descriptors[2] = (struct usb_descriptor_header *) + ð_header_desc; + eth_hs_descriptors[3] = (struct usb_descriptor_header *) + ð_union_desc; + eth_hs_descriptors[4] = (struct usb_descriptor_header *) + ð_ether_desc; + eth_hs_descriptors[5] = (struct usb_descriptor_header *) + ð_control_intf_hs_int_in_ep_desc; + eth_hs_descriptors[6] = (struct usb_descriptor_header *) + ð_data_alt_zero_intf; + eth_hs_descriptors[7] = (struct usb_descriptor_header *) + ð_data_alt_one_intf; + eth_hs_descriptors[8] = (struct usb_descriptor_header *) + ð_data_intf_hs_bulk_out_ep_desc; + eth_hs_descriptors[9] = (struct usb_descriptor_header *) + ð_data_intf_hs_bulk_in_ep_desc; + eth_hs_descriptors[10] = NULL; + + eth_fs_descriptors[0] = (struct usb_descriptor_header *)ð_IAD; + eth_fs_descriptors[1] = (struct usb_descriptor_header *) + ð_control_intf; + eth_fs_descriptors[2] = (struct usb_descriptor_header *) + ð_header_desc; + eth_fs_descriptors[3] = (struct usb_descriptor_header *)ð_union_desc; + eth_fs_descriptors[4] = (struct usb_descriptor_header *)ð_ether_desc; + eth_fs_descriptors[5] = (struct usb_descriptor_header *) + ð_control_intf_fs_int_in_ep_desc; + eth_fs_descriptors[6] = (struct usb_descriptor_header *) + ð_data_alt_zero_intf; + eth_fs_descriptors[7] = (struct usb_descriptor_header *) + ð_data_alt_one_intf; + eth_fs_descriptors[8] = (struct usb_descriptor_header *) + ð_data_intf_fs_bulk_out_ep_desc; + eth_fs_descriptors[9] = (struct usb_descriptor_header *) + ð_data_intf_fs_bulk_in_ep_desc; + eth_fs_descriptors[10] = NULL; + + usb_func_ether.hs_descriptors = eth_hs_descriptors; + usb_func_ether.fs_descriptors = eth_fs_descriptors; + rc = usb_function_register(&usb_func_ether); + + if (rc < 0) + printk(KERN_INFO "cdcecm init:usb function register failed \n"); + return rc; +} +module_init(init); + +static void __exit eth_cleanup(void) +{ + struct eth_dev *dev = eth_device; + + usb_function_unregister(&usb_func_ether); + if (dev) { + dev->net = NULL; + dev = NULL; + } +} +module_exit(eth_cleanup); diff --git a/drivers/usb/function/loopback.c b/drivers/usb/function/loopback.c new file mode 100644 index 00000000000..d7c93a39c96 --- /dev/null +++ b/drivers/usb/function/loopback.c @@ -0,0 +1,128 @@ +/* drivers/usb/function/loopback.c + * + * Simple Loopback Function Device + * + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include "usb_function.h" + +struct loopback_context +{ + struct usb_endpoint *out; + struct usb_endpoint *in; + struct usb_request *req_out; + struct usb_request *req_in; +}; + +static struct loopback_context _context; + +static void loopback_bind(struct usb_endpoint **ept, void *_ctxt) +{ + struct loopback_context *ctxt = _ctxt; + + ctxt->out = ept[0]; + ctxt->in = ept[1]; + + printk(KERN_INFO "loopback_bind() %p, %p\n", ctxt->out, ctxt->in); + + ctxt->req_out = usb_ept_alloc_req(ctxt->out, 4096); + ctxt->req_in = usb_ept_alloc_req(ctxt->in, 4096); +} + +static void loopback_queue_in(struct loopback_context *ctxt, void *data, unsigned len); +static void loopback_queue_out(struct loopback_context *ctxt); + +static void loopback_in_complete(struct usb_endpoint *ept, struct usb_request *req) +{ + struct loopback_context *ctxt = req->context; + printk(KERN_INFO "loopback_out_complete (%d)\n", req->actual); + loopback_queue_out(ctxt); +} + +static void loopback_out_complete(struct usb_endpoint *ept, struct usb_request *req) +{ + struct loopback_context *ctxt = req->context; + printk(KERN_INFO "loopback_in_complete (%d)\n", req->actual); + + if (req->status == 0) { + loopback_queue_in(ctxt, req->buf, req->actual); + } else { + loopback_queue_out(ctxt); + } +} + +static void loopback_queue_out(struct loopback_context *ctxt) +{ + struct usb_request *req = ctxt->req_out; + + req->complete = loopback_out_complete; + req->context = ctxt; + req->length = 4096; + + usb_ept_queue_xfer(ctxt->out, req); +} + +static void loopback_queue_in(struct loopback_context *ctxt, void *data, unsigned len) +{ + struct usb_request *req = ctxt->req_in; + + memcpy(req->buf, data, len); + req->complete = loopback_in_complete; + req->context = ctxt; + req->length = len; + + usb_ept_queue_xfer(ctxt->in, req); +} + +static void loopback_configure(int configured, void *_ctxt) +{ + struct loopback_context *ctxt = _ctxt; + printk(KERN_INFO "loopback_configure() %d\n", configured); + + if (configured) { + loopback_queue_out(ctxt); + } else { + /* all pending requests will be canceled */ + } +} + +static struct usb_function usb_func_loopback = { + .bind = loopback_bind, + .configure = loopback_configure, + + .name = "loopback", + .context = &_context, + + .ifc_class = 0xff, + .ifc_subclass = 0xff, + .ifc_protocol = 0xff, + + .ifc_name = "loopback", + + .ifc_ept_count = 2, + .ifc_ept_type = { EPT_BULK_OUT, EPT_BULK_IN }, +}; + +static int __init loopback_init(void) +{ + printk(KERN_INFO "loopback_init()\n"); + return usb_function_register(&usb_func_loopback); +} + +module_init(loopback_init); diff --git a/drivers/usb/function/mass_storage.c b/drivers/usb/function/mass_storage.c new file mode 100644 index 00000000000..f679cd01588 --- /dev/null +++ b/drivers/usb/function/mass_storage.c @@ -0,0 +1,3009 @@ +/* drivers/usb/function/mass_storage.c + * + * Function Driver for USB Mass Storage + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Author: Mike Lockwood + * + * Based heavily on the file_storage gadget driver in + * drivers/usb/gadget/file_storage.c and licensed under the same terms: + * + * Copyright (C) 2003-2007 Alan Stern + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//#define DEBUG +//#define VERBOSE_DEBUG +//#define DUMP_MSGS + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb_function.h" + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_NAME "usb_mass_storage" +#define MAX_LUNS 8 + +#ifdef DEBUG +#define LDBG(lun, fmt, args...) \ + dev_dbg(&(lun)->dev , fmt , ## args) +#define MDBG(fmt,args...) \ + printk(KERN_DEBUG DRIVER_NAME ": " fmt , ## args) +#else +#define LDBG(lun, fmt, args...) \ + do { } while (0) +#define MDBG(fmt,args...) \ + do { } while (0) +#undef VERBOSE_DEBUG +#undef DUMP_MSGS +#endif /* DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VLDBG LDBG +#else +#define VLDBG(lun, fmt, args...) \ + do { } while (0) +#endif /* VERBOSE_DEBUG */ + +#define LERROR(lun, fmt, args...) \ + dev_err(&(lun)->dev , fmt , ## args) +#define LWARN(lun, fmt, args...) \ + dev_warn(&(lun)->dev , fmt , ## args) +#define LINFO(lun, fmt, args...) \ + dev_info(&(lun)->dev , fmt , ## args) + +#define MINFO(fmt,args...) \ + printk(KERN_INFO DRIVER_NAME ": " fmt , ## args) + +#define DBG(d, fmt, args...) \ + dev_dbg(&(d)->pdev->dev , fmt , ## args) +#define VDBG(d, fmt, args...) \ + dev_vdbg(&(d)->pdev->dev , fmt , ## args) +#define ERROR(d, fmt, args...) \ + dev_err(&(d)->pdev->dev , fmt , ## args) +#define MS_WARN(d, fmt, args...) \ + dev_warn(&(d)->pdev->dev , fmt , ## args) +#define INFO(d, fmt, args...) \ + dev_info(&(d)->pdev->dev , fmt , ## args) + + +/*-------------------------------------------------------------------------*/ + +/* Bulk-only data structures */ + +/* Command Block Wrapper */ +struct bulk_cb_wrap { + __le32 Signature; /* Contains 'USBC' */ + u32 Tag; /* Unique per command id */ + __le32 DataTransferLength; /* Size of the data */ + u8 Flags; /* Direction in bit 7 */ + u8 Lun; /* LUN (normally 0) */ + u8 Length; /* Of the CDB, <= MAX_COMMAND_SIZE */ + u8 CDB[16]; /* Command Data Block */ +}; + +#define USB_BULK_CB_WRAP_LEN 31 +#define USB_BULK_CB_SIG 0x43425355 /* Spells out USBC */ +#define USB_BULK_IN_FLAG 0x80 + +/* Command Status Wrapper */ +struct bulk_cs_wrap { + __le32 Signature; /* Should = 'USBS' */ + u32 Tag; /* Same as original command */ + __le32 Residue; /* Amount not transferred */ + u8 Status; /* See below */ +}; + +#define USB_BULK_CS_WRAP_LEN 13 +#define USB_BULK_CS_SIG 0x53425355 /* Spells out 'USBS' */ +#define USB_STATUS_PASS 0 +#define USB_STATUS_FAIL 1 +#define USB_STATUS_PHASE_ERROR 2 + +/* Bulk-only class specific requests */ +#define USB_BULK_RESET_REQUEST 0xff +#define USB_BULK_GET_MAX_LUN_REQUEST 0xfe + +/* Length of a SCSI Command Data Block */ +#define MAX_COMMAND_SIZE 16 + +/* SCSI commands that we recognize */ +#define SC_FORMAT_UNIT 0x04 +#define SC_INQUIRY 0x12 +#define SC_MODE_SELECT_6 0x15 +#define SC_MODE_SELECT_10 0x55 +#define SC_MODE_SENSE_6 0x1a +#define SC_MODE_SENSE_10 0x5a +#define SC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e +#define SC_READ_6 0x08 +#define SC_READ_10 0x28 +#define SC_READ_12 0xa8 +#define SC_READ_CAPACITY 0x25 +#define SC_READ_FORMAT_CAPACITIES 0x23 +#define SC_RELEASE 0x17 +#define SC_REQUEST_SENSE 0x03 +#define SC_RESERVE 0x16 +#define SC_SEND_DIAGNOSTIC 0x1d +#define SC_START_STOP_UNIT 0x1b +#define SC_SYNCHRONIZE_CACHE 0x35 +#define SC_TEST_UNIT_READY 0x00 +#define SC_VERIFY 0x2f +#define SC_WRITE_6 0x0a +#define SC_WRITE_10 0x2a +#define SC_WRITE_12 0xaa + +/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ +#define SS_NO_SENSE 0 +#define SS_COMMUNICATION_FAILURE 0x040800 +#define SS_INVALID_COMMAND 0x052000 +#define SS_INVALID_FIELD_IN_CDB 0x052400 +#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100 +#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500 +#define SS_MEDIUM_NOT_PRESENT 0x023a00 +#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302 +#define SS_NOT_READY_TO_READY_TRANSITION 0x062800 +#define SS_RESET_OCCURRED 0x062900 +#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900 +#define SS_UNRECOVERED_READ_ERROR 0x031100 +#define SS_WRITE_ERROR 0x030c02 +#define SS_WRITE_PROTECTED 0x072700 + +#define SK(x) ((u8) ((x) >> 16)) /* Sense Key byte, etc. */ +#define ASC(x) ((u8) ((x) >> 8)) +#define ASCQ(x) ((u8) (x)) + + +/*-------------------------------------------------------------------------*/ + +struct lun { + struct file *filp; + loff_t file_length; + loff_t num_sectors; + + unsigned int ro : 1; + unsigned int prevent_medium_removal : 1; + unsigned int registered : 1; + unsigned int info_valid : 1; + + u32 sense_data; + u32 sense_data_info; + u32 unit_attention_data; + + struct device dev; +}; + +#define backing_file_is_open(curlun) ((curlun)->filp != NULL) + + +static struct lun *dev_to_lun(struct device *dev) +{ + return container_of(dev, struct lun, dev); +} + +/* Big enough to hold our biggest descriptor */ +#define EP0_BUFSIZE 256 +#define DELAYED_STATUS (EP0_BUFSIZE + 999) /* An impossibly large value */ + +/* Number of buffers for CBW, DATA and CSW */ +#ifdef CONFIG_USB_CSW_HACK +#define NUM_BUFFERS 4 +#else +#define NUM_BUFFERS 2 +#endif + +enum fsg_buffer_state { + BUF_STATE_EMPTY = 0, + BUF_STATE_FULL, + BUF_STATE_BUSY +}; + +struct fsg_buffhd { + void *buf; + enum fsg_buffer_state state; + struct fsg_buffhd *next; + + /* The NetChip 2280 is faster, and handles some protocol faults + * better, if we don't submit any short bulk-out read requests. + * So we will record the intended request length here. */ + unsigned int bulk_out_intended_length; + + struct usb_request *inreq; + int inreq_busy; + struct usb_request *outreq; + int outreq_busy; +}; + +enum fsg_state { + /* This one isn't used anywhere */ + FSG_STATE_COMMAND_PHASE = -10, + + FSG_STATE_DATA_PHASE, + FSG_STATE_STATUS_PHASE, + + FSG_STATE_IDLE = 0, + FSG_STATE_ABORT_BULK_OUT, + FSG_STATE_RESET, + FSG_STATE_CONFIG_CHANGE, + FSG_STATE_EXIT, + FSG_STATE_TERMINATED +}; + +enum data_direction { + DATA_DIR_UNKNOWN = 0, + DATA_DIR_FROM_HOST, + DATA_DIR_TO_HOST, + DATA_DIR_NONE +}; +int can_stall = 1; + +struct fsg_dev { + /* lock protects: state and all the req_busy's */ + spinlock_t lock; + + /* filesem protects: backing files in use */ + struct rw_semaphore filesem; + + /* reference counting: wait until all LUNs are released */ + struct kref ref; + + unsigned int bulk_out_maxpacket; + enum fsg_state state; /* For exception handling */ + + u8 config, new_config; + + unsigned int running : 1; + unsigned int phase_error : 1; + unsigned int short_packet_received : 1; + unsigned int bad_lun_okay : 1; + + unsigned long atomic_bitflags; +#define REGISTERED 0 +#define CLEAR_BULK_HALTS 1 +#define SUSPENDED 2 + + struct usb_endpoint *bulk_in; + struct usb_endpoint *bulk_out; + + struct fsg_buffhd *next_buffhd_to_fill; + struct fsg_buffhd *next_buffhd_to_drain; + struct fsg_buffhd buffhds[NUM_BUFFERS]; + + int thread_wakeup_needed; + struct completion thread_notifier; + struct task_struct *thread_task; + + int cmnd_size; + u8 cmnd[MAX_COMMAND_SIZE]; + enum data_direction data_dir; + u32 data_size; + u32 data_size_from_cmnd; + u32 tag; + unsigned int lun; + u32 residue; + u32 usb_amount_left; + + unsigned int nluns; + struct lun *luns; + struct lun *curlun; + + u32 buf_size; + const char *vendor; + const char *product; + int release; + + struct platform_device *pdev; + struct switch_dev sdev; + int bound; + struct wake_lock wake_lock, wake_lock_idle; +}; +static int send_status(struct fsg_dev *fsg); + +static int exception_in_progress(struct fsg_dev *fsg) +{ + return (fsg->state > FSG_STATE_IDLE); +} + +/* Make bulk-out requests be divisible by the maxpacket size */ +static void set_bulk_out_req_length(struct fsg_dev *fsg, + struct fsg_buffhd *bh, unsigned int length) +{ + unsigned int rem; + + bh->bulk_out_intended_length = length; + rem = length % fsg->bulk_out_maxpacket; + if (rem > 0) + length += fsg->bulk_out_maxpacket - rem; + bh->outreq->length = length; +} + +static struct fsg_dev *the_fsg; + +static void close_backing_file(struct fsg_dev *fsg, struct lun *curlun); +static void close_all_backing_files(struct fsg_dev *fsg); + + +static struct usb_function fsg_function; +/*-------------------------------------------------------------------------*/ + +#ifdef DUMP_MSGS + +static void dump_msg(struct fsg_dev *fsg, const char *label, + const u8 *buf, unsigned int length) +{ + if (length < 512) { + DBG(fsg, "%s, length %u:\n", label, length); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, + 16, 1, buf, length, 0); + } +} + +static void dump_cdb(struct fsg_dev *fsg) +{} + +#else + +static void dump_msg(struct fsg_dev *fsg, const char *label, + const u8 *buf, unsigned int length) +{} + +#ifdef VERBOSE_DEBUG + +static void dump_cdb(struct fsg_dev *fsg) +{ + print_hex_dump(KERN_DEBUG, "SCSI CDB: ", DUMP_PREFIX_NONE, + 16, 1, fsg->cmnd, fsg->cmnd_size, 0); +} + +#else + +static void dump_cdb(struct fsg_dev *fsg) +{} + +#endif /* VERBOSE_DEBUG */ +#endif /* DUMP_MSGS */ + +static int fsg_set_halt(struct fsg_dev *fsg, struct usb_endpoint *ep) +{ + const char *name; + + if (ep == fsg->bulk_in) + name = "bulk-in"; + else if (ep == fsg->bulk_out) + name = "bulk-out"; + else + return -1; + + DBG(fsg, "%s set halt\n", name); + return usb_ept_set_halt(ep); +} + +/*-------------------------------------------------------------------------*/ + +/* Routines for unaligned data access */ + +static u16 get_be16(u8 *buf) +{ + return ((u16) buf[0] << 8) | ((u16) buf[1]); +} + +static u32 get_be32(u8 *buf) +{ + return ((u32) buf[0] << 24) | ((u32) buf[1] << 16) | + ((u32) buf[2] << 8) | ((u32) buf[3]); +} + +static void put_be16(u8 *buf, u16 val) +{ + buf[0] = val >> 8; + buf[1] = val; +} + +static void put_be32(u8 *buf, u32 val) +{ + buf[0] = val >> 24; + buf[1] = val >> 16; + buf[2] = val >> 8; + buf[3] = val & 0xff; +} + +/*-------------------------------------------------------------------------*/ + + +/* There is only one interface. */ +#define USB_SC_SCSI 0x06 /* Transparent SCSI */ +#define USB_PR_BULK 0x50 /* Bulk-only */ +static struct usb_interface_descriptor +intf_desc = { + .bLength = sizeof intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, + .bInterfaceProtocol = USB_PR_BULK, +}; + + +static struct usb_endpoint_descriptor +hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor +hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor +fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor +fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + + +static struct usb_descriptor_header *hs_function[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &hs_bulk_in_desc, + (struct usb_descriptor_header *) &hs_bulk_out_desc, + NULL, +}; +static struct usb_descriptor_header *fs_function[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &fs_bulk_in_desc, + (struct usb_descriptor_header *) &fs_bulk_out_desc, + NULL, +}; +/*-------------------------------------------------------------------------*/ + +/* These routines may be called in process context or in_irq */ + +/* Caller must hold fsg->lock */ +static void wakeup_thread(struct fsg_dev *fsg) +{ + /* Tell the main thread that something has happened */ + fsg->thread_wakeup_needed = 1; + if (fsg->thread_task) + wake_up_process(fsg->thread_task); +} + + +static void raise_exception(struct fsg_dev *fsg, enum fsg_state new_state) +{ + unsigned long flags; + + DBG(fsg, "raise_exception %d\n", (int)new_state); + /* Do nothing if a higher-priority exception is already in progress. + * If a lower-or-equal priority exception is in progress, preempt it + * and notify the main thread by sending it a signal. */ + spin_lock_irqsave(&fsg->lock, flags); + if (fsg->state <= new_state) { + fsg->state = new_state; + if (fsg->thread_task) + send_sig_info(SIGUSR1, SEND_SIG_FORCED, + fsg->thread_task); + } + spin_unlock_irqrestore(&fsg->lock, flags); +} + + +/*-------------------------------------------------------------------------*/ + +/* Bulk and interrupt endpoint completion handlers. + * These always run in_irq. */ + +static void bulk_in_complete(struct usb_endpoint *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = the_fsg; + struct fsg_buffhd *bh = req->context; + unsigned long flags; + + if (req->status || req->actual != req->length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + + /* Hold the lock while we update the request and buffer states */ + if (req->status == 0) { + smp_wmb(); + spin_lock_irqsave(&fsg->lock, flags); + bh->inreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(fsg); + spin_unlock_irqrestore(&fsg->lock, flags); + } else + bh->inreq_busy = 0; +} + +static void bulk_out_complete(struct usb_endpoint *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = the_fsg; + struct fsg_buffhd *bh = req->context; + unsigned long flags; + + dump_msg(fsg, "bulk-out", req->buf, req->actual); + if (req->status || req->actual != bh->bulk_out_intended_length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, + bh->bulk_out_intended_length); + + /* Hold the lock while we update the request and buffer states */ + if (req->status == 0) { + smp_wmb(); + spin_lock_irqsave(&fsg->lock, flags); + bh->outreq_busy = 0; + bh->state = BUF_STATE_FULL; + wakeup_thread(fsg); + spin_unlock_irqrestore(&fsg->lock, flags); + } else + bh->outreq_busy = 0; +} + +static int fsg_setup(struct usb_ctrlrequest *ctrl, void *buf, + int len, void *context) +{ + struct fsg_dev *fsg = context; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + if (!fsg->config) + return value; + + if (w_index != intf_desc.bInterfaceNumber) + return value; + + /* Handle Bulk-only class-specific requests */ + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + switch (ctrl->bRequest) { + case USB_BULK_RESET_REQUEST: + if (ctrl->bRequestType != (USB_DIR_OUT | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_value != 0) { + value = -EDOM; + break; + } + + /* Raise an exception to stop the current operation + * and reinitialize our state. */ + DBG(fsg, "bulk reset request\n"); + value = 0; + break; + + case USB_BULK_GET_MAX_LUN_REQUEST: + if (ctrl->bRequestType != (USB_DIR_IN | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_value != 0) { + value = -EDOM; + break; + } + VDBG(fsg, "get max LUN\n"); + *(u8 *) buf = fsg->nluns - 1; + value = 1; + break; + } + } + + if (value == -EOPNOTSUPP) + VDBG(fsg, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + le16_to_cpu(ctrl->wValue), w_index, w_length); + return value; +} + +/*-------------------------------------------------------------------------*/ + +/* All the following routines run in process context */ + + +/* Use this for bulk or interrupt transfers, not ep0 */ +static void start_transfer(struct fsg_dev *fsg, struct usb_endpoint *ep, + struct usb_request *req, int *pbusy, + enum fsg_buffer_state *state) +{ + int rc; + unsigned long flags; + + if (ep == fsg->bulk_in) + dump_msg(fsg, "bulk-in", req->buf, req->length); + + spin_lock_irqsave(&fsg->lock, flags); + *pbusy = 1; + *state = BUF_STATE_BUSY; + spin_unlock_irqrestore(&fsg->lock, flags); + rc = usb_ept_queue_xfer(ep, req); + if (rc != 0) { + *pbusy = 0; + *state = BUF_STATE_EMPTY; + + /* We can't do much more than wait for a reset */ + + /* Note: currently the net2280 driver fails zero-length + * submissions if DMA is enabled. */ + if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && + req->length == 0)) + MS_WARN(fsg, "error in submission: %s --> %d\n", + (ep == fsg->bulk_in ? "bulk-in" : "bulk-out"), + rc); + } +} + + +static int sleep_thread(struct fsg_dev *fsg) +{ + int rc = 0; + + /* Wait until a signal arrives or we are woken up */ + for (;;) { + try_to_freeze(); + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + rc = -EINTR; + break; + } + if (fsg->thread_wakeup_needed) + break; + schedule(); + } + __set_current_state(TASK_RUNNING); + fsg->thread_wakeup_needed = 0; + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_read(struct fsg_dev *fsg) +{ + struct lun *curlun = fsg->curlun; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset, file_offset_tmp; + unsigned int amount; + unsigned int partial_page; + ssize_t nread; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (fsg->cmnd[0] == SC_READ_6) + lba = (fsg->cmnd[1] << 16) | get_be16(&fsg->cmnd[2]); + else { + lba = get_be32(&fsg->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = don't read from the + * cache), but we don't implement them. */ + if ((fsg->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + file_offset = ((loff_t) lba) << 9; + + /* Carry out the file reads */ + amount_left = fsg->data_size_from_cmnd; + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply */ + + for (;;) { + + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. */ + amount = min((unsigned int) amount_left, + (unsigned int)fsg->buf_size); + amount = min((loff_t) amount, + curlun->file_length - file_offset); + partial_page = file_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, (unsigned int) PAGE_CACHE_SIZE - + partial_page); + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file read: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); /* Round down to a block */ + } + file_offset += nread; + amount_left -= nread; + fsg->residue -= nread; + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; /* No more left to read */ + + /* Send this buffer and go read some more */ + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + } + + return -EIO; /* No default reply */ +} + + +/*-------------------------------------------------------------------------*/ + +static int do_write(struct fsg_dev *fsg) +{ + struct lun *curlun = fsg->curlun; + u32 lba; + struct fsg_buffhd *bh; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + loff_t usb_offset, file_offset, file_offset_tmp; + unsigned int amount; + unsigned int partial_page; + ssize_t nwritten; + int rc; + +#ifdef CONFIG_USB_CSW_HACK + int csw_hack_sent = 0; + int i; +#endif + if (curlun->ro) { + curlun->sense_data = SS_WRITE_PROTECTED; + return -EINVAL; + } + curlun->filp->f_flags &= ~O_SYNC; /* Default is not to wait */ + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (fsg->cmnd[0] == SC_WRITE_6) + lba = (fsg->cmnd[1] << 16) | get_be16(&fsg->cmnd[2]); + else { + lba = get_be32(&fsg->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = write directly to the + * medium). We don't implement DPO; we implement FUA by + * performing synchronous output. */ + if ((fsg->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (fsg->cmnd[1] & 0x08) /* FUA */ + curlun->filp->f_flags |= O_SYNC; + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* Carry out the file writes */ + get_some_more = 1; + file_offset = usb_offset = ((loff_t) lba) << 9; + amount_left_to_req = amount_left_to_write = fsg->data_size_from_cmnd; + + while (amount_left_to_write > 0) { + + /* Queue a request for more data from the host */ + bh = fsg->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + + /* Figure out how much we want to get: + * Try to get the remaining amount. + * But don't get more than the buffer size. + * And don't try to go past the end of the file. + * If we're not at a page boundary, + * don't go past the next page. + * If this means getting 0, then we were asked + * to write past the end of file. + * Finally, round down to a block boundary. */ + amount = min(amount_left_to_req, (u32)fsg->buf_size); + amount = min((loff_t) amount, curlun->file_length - + usb_offset); + partial_page = usb_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, + (unsigned int) PAGE_CACHE_SIZE - partial_page); + + if (amount == 0) { + get_some_more = 0; + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = usb_offset >> 9; + curlun->info_valid = 1; + continue; + } + amount -= (amount & 511); + if (amount == 0) { + + /* Why were we were asked to transfer a + * partial block? */ + get_some_more = 0; + continue; + } + + /* Get the next buffer */ + usb_offset += amount; + fsg->usb_amount_left -= amount; + amount_left_to_req -= amount; + if (amount_left_to_req == 0) + get_some_more = 0; + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + bh->outreq->length = bh->bulk_out_intended_length = + amount; + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + continue; + } + + /* Write the received data to the backing file */ + bh = fsg->next_buffhd_to_drain; + if (bh->state == BUF_STATE_EMPTY && !get_some_more) + break; /* We stopped early */ +#ifdef CONFIG_USB_CSW_HACK + /* + * If the csw packet is already submmitted to the DCD, + * by marking the state of buffer as full, then by checking + * the residue, we make sure that this csw packet is not + * written on to the storage media. + */ + if (bh->state == BUF_STATE_FULL && fsg->residue) { +#else + if (bh->state == BUF_STATE_FULL) { +#endif + smp_rmb(); + fsg->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) { + curlun->sense_data = SS_COMMUNICATION_FAILURE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + amount = bh->outreq->actual; + if (curlun->file_length - file_offset < amount) { + LERROR(curlun, + "write %u @ %llu beyond end %llu\n", + amount, (unsigned long long) file_offset, + (unsigned long long) curlun->file_length); + amount = curlun->file_length - file_offset; + } + + /* Perform the write */ + file_offset_tmp = file_offset; + nwritten = vfs_write(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nwritten); + if (signal_pending(current)) + return -EINTR; /* Interrupted! */ + + if (nwritten < 0) { + LDBG(curlun, "error in file write: %d\n", + (int) nwritten); + nwritten = 0; + } else if (nwritten < amount) { + LDBG(curlun, "partial file write: %d/%u\n", + (int) nwritten, amount); + nwritten -= (nwritten & 511); + /* Round down to a block */ + } + file_offset += nwritten; + amount_left_to_write -= nwritten; + fsg->residue -= nwritten; + + /* If an error occurred, report it and its position */ + if (nwritten < amount) { +#ifdef CONFIG_USB_CSW_HACK + /* + * If csw is already sent & write failure + * occured, then detach the storage media + * from the corresponding lun, and cable must + * be disconnected to recover fom this error. + */ + if (csw_hack_sent) { + if (backing_file_is_open(curlun)) { + close_backing_file(fsg, curlun); + curlun->unit_attention_data = + SS_MEDIUM_NOT_PRESENT; + } + break; + } +#endif + curlun->sense_data = SS_WRITE_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + +#ifdef CONFIG_USB_CSW_HACK + if ((nwritten == amount) && !csw_hack_sent) { + /* + * Check if any of the buffer is in the + * busy state, if any buffer is in busy state, + * means the complete data is not received + * yet from the host. So there is no point in + * csw right away without the complete data. + */ + for (i = 0; i < NUM_BUFFERS; i++) { + if (fsg->buffhds[i].state == + BUF_STATE_BUSY) + break; + } + /* Check whether we received the complete + * data from the host, before sending csw */ + if (!amount_left_to_req && i == NUM_BUFFERS) { + csw_hack_sent = 1; + send_status(fsg); + } + } +#endif + /* Did the host decide to stop early? */ + if (bh->outreq->actual != bh->outreq->length) { + fsg->short_packet_received = 1; + break; + } + continue; + } + + /* Wait for something to happen */ + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + return -EIO; /* No default reply */ +} + + +/*-------------------------------------------------------------------------*/ + +/* Sync the file data, don't bother with the metadata. + * The caller must own fsg->filesem. + * This code was copied from fs/buffer.c:sys_fdatasync(). */ +static int fsync_sub(struct lun *curlun) +{ + struct file *filp = curlun->filp; + struct inode *inode; + int rc, err; + + if (curlun->ro || !filp) + return 0; + if (!filp->f_op->fsync) + return -EINVAL; + + inode = filp->f_path.dentry->d_inode; + mutex_lock(&inode->i_mutex); + rc = filemap_fdatawrite(inode->i_mapping); + err = filp->f_op->fsync(filp, filp->f_path.dentry, 1); + if (!rc) + rc = err; + err = filemap_fdatawait(inode->i_mapping); + if (!rc) + rc = err; + mutex_unlock(&inode->i_mutex); + VLDBG(curlun, "fdatasync -> %d\n", rc); + return rc; +} + +static void fsync_all(struct fsg_dev *fsg) +{ + int i; + + for (i = 0; i < fsg->nluns; ++i) + fsync_sub(&fsg->luns[i]); +} + +static int do_synchronize_cache(struct fsg_dev *fsg) +{ + struct lun *curlun = fsg->curlun; + int rc; + + /* We ignore the requested LBA and write out all file's + * dirty data buffers. */ + rc = fsync_sub(curlun); + if (rc) + curlun->sense_data = SS_WRITE_ERROR; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static void invalidate_sub(struct lun *curlun) +{ + struct file *filp = curlun->filp; + struct inode *inode = filp->f_path.dentry->d_inode; + unsigned long rc; + + rc = invalidate_mapping_pages(inode->i_mapping, 0, -1); + VLDBG(curlun, "invalidate_inode_pages -> %ld\n", rc); +} + +static int do_verify(struct fsg_dev *fsg) +{ + struct lun *curlun = fsg->curlun; + u32 lba; + u32 verification_length; + struct fsg_buffhd *bh = fsg->next_buffhd_to_fill; + loff_t file_offset, file_offset_tmp; + u32 amount_left; + unsigned int amount; + ssize_t nread; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + lba = get_be32(&fsg->cmnd[2]); + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) but we don't implement it. */ + if ((fsg->cmnd[1] & ~0x10) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + verification_length = get_be16(&fsg->cmnd[7]); + if (unlikely(verification_length == 0)) + return -EIO; /* No default reply */ + + /* Prepare to carry out the file verify */ + amount_left = verification_length << 9; + file_offset = ((loff_t) lba) << 9; + + /* Write out all the dirty buffers before invalidating them */ + fsync_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + invalidate_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + /* Just try to read the requested blocks */ + while (amount_left > 0) { + + /* Figure out how much we need to read: + * Try to read the remaining amount, but not more than + * the buffer size. + * And don't try to read past the end of the file. + * If this means reading 0 then we were asked to read + * past the end of file. */ + amount = min((unsigned int) amount_left, + (unsigned int)fsg->buf_size); + amount = min((loff_t) amount, + curlun->file_length - file_offset); + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file verify: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file verify: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); /* Round down to a sector */ + } + if (nread == 0) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + file_offset += nread; + amount_left -= nread; + } + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_inquiry(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + u8 *buf = (u8 *) bh->buf; + + if (!fsg->curlun) { /* Unsupported LUNs are okay */ + fsg->bad_lun_okay = 1; + memset(buf, 0, 36); + buf[0] = 0x7f; /* Unsupported, no device-type */ + return 36; + } + + memset(buf, 0, 8); /* Non-removable, direct-access device */ + + buf[1] = 0x80; /* set removable bit */ + buf[2] = 2; /* ANSI SCSI level 2 */ + buf[3] = 2; /* SCSI-2 INQUIRY data format */ + buf[4] = 31; /* Additional length */ + /* No special options */ + sprintf(buf + 8, "%-8s%-16s%04x", fsg->vendor, + fsg->product, fsg->release); + return 36; +} + + +static int do_request_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct lun *curlun = fsg->curlun; + u8 *buf = (u8 *) bh->buf; + u32 sd, sdinfo; + int valid; + + /* + * From the SCSI-2 spec., section 7.9 (Unit attention condition): + * + * If a REQUEST SENSE command is received from an initiator + * with a pending unit attention condition (before the target + * generates the contingent allegiance condition), then the + * target shall either: + * a) report any pending sense data and preserve the unit + * attention condition on the logical unit, or, + * b) report the unit attention condition, may discard any + * pending sense data, and clear the unit attention + * condition on the logical unit for that initiator. + * + * FSG normally uses option a); enable this code to use option b). + */ +#if 0 + if (curlun && curlun->unit_attention_data != SS_NO_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + } +#endif + + if (!curlun) { /* Unsupported LUNs are okay */ + fsg->bad_lun_okay = 1; + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + sdinfo = 0; + valid = 0; + } else { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + valid = curlun->info_valid << 7; + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + + memset(buf, 0, 18); + buf[0] = valid | 0x70; /* Valid, current error */ + buf[2] = SK(sd); + put_be32(&buf[3], sdinfo); /* Sense information */ + buf[7] = 18 - 8; /* Additional sense length */ + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + return 18; +} + + +static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct lun *curlun = fsg->curlun; + u32 lba = get_be32(&fsg->cmnd[2]); + int pmi = fsg->cmnd[8]; + u8 *buf = (u8 *) bh->buf; + + /* Check the PMI and LBA fields */ + if (pmi > 1 || (pmi == 0 && lba != 0)) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + put_be32(&buf[0], curlun->num_sectors - 1); /* Max logical block */ + put_be32(&buf[4], 512); /* Block length */ + return 8; +} + + +static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct lun *curlun = fsg->curlun; + int mscmnd = fsg->cmnd[0]; + u8 *buf = (u8 *) bh->buf; + u8 *buf0 = buf; + int pc, page_code; + int changeable_values, all_pages; + int valid_page = 0; + int len, limit; + + if ((fsg->cmnd[1] & ~0x08) != 0) { /* Mask away DBD */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + pc = fsg->cmnd[2] >> 6; + page_code = fsg->cmnd[2] & 0x3f; + if (pc == 3) { + curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + return -EINVAL; + } + changeable_values = (pc == 1); + all_pages = (page_code == 0x3f); + + /* Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. */ + memset(buf, 0, 8); + if (mscmnd == SC_MODE_SENSE_6) { + buf[2] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 4; + limit = 255; + } else { /* SC_MODE_SENSE_10 */ + buf[3] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 8; + limit = 65535; + } + + /* No block descriptors */ + + /* Disabled to workaround USB reset problems with a Vista host. + */ +#if 0 + /* The mode pages, in numerical order. The only page we support + * is the Caching page. */ + if (page_code == 0x08 || all_pages) { + valid_page = 1; + buf[0] = 0x08; /* Page code */ + buf[1] = 10; /* Page length */ + memset(buf+2, 0, 10); /* None of the fields are changeable */ + + if (!changeable_values) { + buf[2] = 0x04; /* Write cache enable, */ + /* Read cache not disabled */ + /* No cache retention priorities */ + put_be16(&buf[4], 0xffff); /* Don't disable prefetch */ + /* Minimum prefetch = 0 */ + put_be16(&buf[8], 0xffff); /* Maximum prefetch */ + /* Maximum prefetch ceiling */ + put_be16(&buf[10], 0xffff); + } + buf += 12; + } +#else + valid_page = 1; +#endif + + /* Check that a valid page was requested and the mode data length + * isn't too long. */ + len = buf - buf0; + if (!valid_page || len > limit) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + /* Store the mode data length */ + if (mscmnd == SC_MODE_SENSE_6) + buf0[0] = len - 1; + else + put_be16(buf0, len - 2); + return len; +} + +static int do_start_stop(struct fsg_dev *fsg) +{ + struct lun *curlun = fsg->curlun; + int loej, start; + + /* int immed = fsg->cmnd[1] & 0x01; */ + loej = fsg->cmnd[4] & 0x02; + start = fsg->cmnd[4] & 0x01; + + if (loej) { + /* eject request from the host */ + if (backing_file_is_open(curlun)) { + close_backing_file(fsg, curlun); + curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT; + } + } + + return 0; +} + +static int do_prevent_allow(struct fsg_dev *fsg) +{ + struct lun *curlun = fsg->curlun; + int prevent; + + prevent = fsg->cmnd[4] & 0x01; + if ((fsg->cmnd[4] & ~0x01) != 0) { /* Mask away Prevent */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (curlun->prevent_medium_removal && !prevent) + fsync_sub(curlun); + curlun->prevent_medium_removal = prevent; + return 0; +} + + +static int do_read_format_capacities(struct fsg_dev *fsg, + struct fsg_buffhd *bh) +{ + struct lun *curlun = fsg->curlun; + u8 *buf = (u8 *) bh->buf; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; /* Only the Current/Maximum Capacity Descriptor */ + buf += 4; + + put_be32(&buf[0], curlun->num_sectors); /* Number of blocks */ + put_be32(&buf[4], 512); /* Block length */ + buf[4] = 0x02; /* Current capacity */ + return 12; +} + + +static int do_mode_select(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct lun *curlun = fsg->curlun; + + /* We don't support MODE SELECT */ + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; +} + + +static int halt_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + rc = fsg_set_halt(fsg, fsg->bulk_in); + if (rc == -EAGAIN) + DBG(fsg, "delayed bulk-in endpoint halt\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + DBG(fsg, "usb_ep_set_halt -> %d\n", rc); + rc = 0; + break; + } + /* Wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + rc = usb_ept_set_halt(fsg->bulk_in); + } + return rc; +} +/*-------------------------------------------------------------------------*/ +#if 0 +static int write_zero(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + int rc; + + DBG(fsg, "write_zero\n"); + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + bh->inreq->length = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + + fsg->next_buffhd_to_fill = bh->next; + return 0; +} +#endif + +static int throw_away_data(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + u32 amount; + int rc; + + DBG(fsg, "throw_away_data\n"); + while ((bh = fsg->next_buffhd_to_drain)->state != BUF_STATE_EMPTY || + fsg->usb_amount_left > 0) { + + /* Throw away the data in a filled buffer */ + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + bh->state = BUF_STATE_EMPTY; + fsg->next_buffhd_to_drain = bh->next; + + /* A short packet or an error ends everything */ + if (bh->outreq->actual != bh->outreq->length || + bh->outreq->status != 0) { + raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); + return -EINTR; + } + continue; + } + + /* Try to submit another request if we need one */ + bh = fsg->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && fsg->usb_amount_left > 0) { + amount = min(fsg->usb_amount_left, (u32) fsg->buf_size); + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + bh->outreq->length = bh->bulk_out_intended_length = + amount; + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + fsg->usb_amount_left -= amount; + continue; + } + + /* Otherwise wait for something to happen */ + rc = sleep_thread(fsg); + if (rc) + return rc; + } + return 0; +} + + +static int finish_reply(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh = fsg->next_buffhd_to_fill; + int rc = 0; + int i; + + switch (fsg->data_dir) { + case DATA_DIR_NONE: + break; /* Nothing to send */ + + case DATA_DIR_UNKNOWN: + rc = -EINVAL; + break; + + /* All but the last buffer of data must have already been sent */ + case DATA_DIR_TO_HOST: + if (fsg->data_size == 0) + ; /* Nothing to send */ + + /* If there's no residue, simply send the last buffer */ + else if (fsg->residue == 0) { + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + } else { + if (can_stall) { + bh->state = BUF_STATE_EMPTY; + for (i = 0; i < NUM_BUFFERS; ++i) { + struct fsg_buffhd + *bh = &fsg->buffhds[i]; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + } + rc = halt_bulk_in_endpoint(fsg); + } else { + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + } +#if 0 + /* this is unnecessary, and was causing problems with MacOS */ + if (length > 0) + write_zero(fsg); +#endif + } + break; + + /* We have processed all we want from the data the host has sent. + * There may still be outstanding bulk-out requests. */ + case DATA_DIR_FROM_HOST: + if (fsg->residue == 0) + ; /* Nothing to receive */ + + /* Did the host stop sending unexpectedly early? */ + else if (fsg->short_packet_received) { + raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; + } + + /* We haven't processed all the incoming data. Even though + * we may be allowed to stall, doing so would cause a race. + * The controller may already have ACK'ed all the remaining + * bulk-out packets, in which case the host wouldn't see a + * STALL. Not realizing the endpoint was halted, it wouldn't + * clear the halt -- leading to problems later on. */ +#if 0 + fsg_set_halt(fsg, fsg->bulk_out); + raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; +#endif + + /* We can't stall. Read in the excess data and throw it + * all away. */ + else + rc = throw_away_data(fsg); + break; + } + return rc; +} + + +static int send_status(struct fsg_dev *fsg) +{ + struct lun *curlun = fsg->curlun; + struct fsg_buffhd *bh; + int rc; + u8 status = USB_STATUS_PASS; + u32 sd, sdinfo = 0; + struct bulk_cs_wrap *csw; + + DBG(fsg, "send_status\n"); + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + if (curlun) { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + } else if (fsg->bad_lun_okay) + sd = SS_NO_SENSE; + else + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + + if (fsg->phase_error) { + DBG(fsg, "sending phase-error status\n"); + status = USB_STATUS_PHASE_ERROR; + sd = SS_INVALID_COMMAND; + } else if (sd != SS_NO_SENSE) { + DBG(fsg, "sending command-failure status\n"); + status = USB_STATUS_FAIL; + VDBG(fsg, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;" + " info x%x\n", + SK(sd), ASC(sd), ASCQ(sd), sdinfo); + } + + csw = bh->buf; + + /* Store and send the Bulk-only CSW */ + csw->Signature = __constant_cpu_to_le32(USB_BULK_CS_SIG); + csw->Tag = fsg->tag; +#ifdef CONFIG_USB_CSW_HACK + /* Since csw is being sent early, before + * writing on to storage media, need to set + * residue to zero,assuming that write will succeed. + */ + csw->Residue = 0; +#else + csw->Residue = cpu_to_le32(fsg->residue); +#endif + csw->Status = status; + + bh->inreq->length = USB_BULK_CS_WRAP_LEN; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + + fsg->next_buffhd_to_fill = bh->next; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* Check whether the command is properly formed and whether its data size + * and direction agree with the values we already have. */ +static int check_command(struct fsg_dev *fsg, int cmnd_size, + enum data_direction data_dir, unsigned int mask, + int needs_medium, const char *name) +{ + int i; + int lun = fsg->cmnd[1] >> 5; + static const char dirletter[4] = {'u', 'o', 'i', 'n'}; + char hdlen[20]; + struct lun *curlun; + + hdlen[0] = 0; + if (fsg->data_dir != DATA_DIR_UNKNOWN) + sprintf(hdlen, ", H%c=%u", dirletter[(int) fsg->data_dir], + fsg->data_size); + VDBG(fsg, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n", + name, cmnd_size, dirletter[(int) data_dir], + fsg->data_size_from_cmnd, fsg->cmnd_size, hdlen); + + /* We can't reply at all until we know the correct data direction + * and size. */ + if (fsg->data_size_from_cmnd == 0) + data_dir = DATA_DIR_NONE; + if (fsg->data_dir == DATA_DIR_UNKNOWN) { /* CB or CBI */ + fsg->data_dir = data_dir; + fsg->data_size = fsg->data_size_from_cmnd; + + } else { /* Bulk-only */ + if (fsg->data_size < fsg->data_size_from_cmnd) { + + /* Host data size < Device data size is a phase error. + * Carry out the command, but only transfer as much + * as we are allowed. */ + DBG(fsg, "phase error 1\n"); + fsg->data_size_from_cmnd = fsg->data_size; + fsg->phase_error = 1; + } + } + fsg->residue = fsg->usb_amount_left = fsg->data_size; + + /* Conflicting data directions is a phase error */ + if (fsg->data_dir != data_dir && fsg->data_size_from_cmnd > 0) { + fsg->phase_error = 1; + DBG(fsg, "phase error 2\n"); + return -EINVAL; + } + + /* Verify the length of the command itself */ + if (cmnd_size != fsg->cmnd_size) { + + /* Special case workaround: MS-Windows issues REQUEST SENSE/ + * INQUIRY with cbw->Length == 12 (it should be 6). */ + if ((fsg->cmnd[0] == SC_REQUEST_SENSE && fsg->cmnd_size == 12) + || (fsg->cmnd[0] == SC_INQUIRY && fsg->cmnd_size == 12)) + cmnd_size = fsg->cmnd_size; + else { + fsg->phase_error = 1; + return -EINVAL; + } + } + + /* Check that the LUN values are consistent */ + if (fsg->lun != lun) + DBG(fsg, "using LUN %d from CBW, " + "not LUN %d from CDB\n", + fsg->lun, lun); + + /* Check the LUN */ + if (fsg->lun >= 0 && fsg->lun < fsg->nluns) { + fsg->curlun = curlun = &fsg->luns[fsg->lun]; + if (fsg->cmnd[0] != SC_REQUEST_SENSE) { + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + } else { + fsg->curlun = curlun = NULL; + fsg->bad_lun_okay = 0; + + /* INQUIRY and REQUEST SENSE commands are explicitly allowed + * to use unsupported LUNs; all others may not. */ + if (fsg->cmnd[0] != SC_INQUIRY && + fsg->cmnd[0] != SC_REQUEST_SENSE) { + DBG(fsg, "unsupported LUN %d\n", fsg->lun); + return -EINVAL; + } + } + + /* If a unit attention condition exists, only INQUIRY and + * REQUEST SENSE commands are allowed; anything else must fail. */ + if (curlun && curlun->unit_attention_data != SS_NO_SENSE && + fsg->cmnd[0] != SC_INQUIRY && + fsg->cmnd[0] != SC_REQUEST_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + return -EINVAL; + } + + /* Check that only command bytes listed in the mask are non-zero */ + fsg->cmnd[1] &= 0x1f; /* Mask away the LUN */ + for (i = 1; i < cmnd_size; ++i) { + if (fsg->cmnd[i] && !(mask & (1 << i))) { + if (curlun) + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + DBG(fsg, "SS_INVALID_FIELD_IN_CDB\n"); + return -EINVAL; + } + } + + /* If the medium isn't mounted and the command needs to access + * it, return an error. */ + if (curlun && !backing_file_is_open(curlun) && needs_medium) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + DBG(fsg, "SS_MEDIUM_NOT_PRESENT\n"); + return -EINVAL; + } + + return 0; +} + + +static int do_scsi_command(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + int rc; + int reply = -EINVAL; + int i; + static char unknown[16]; + + dump_cdb(fsg); + + /* Wait for the next buffer to become available for data or status */ + bh = fsg->next_buffhd_to_drain = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + fsg->phase_error = 0; + fsg->short_packet_received = 0; + + down_read(&fsg->filesem); /* We're using the backing file */ + switch (fsg->cmnd[0]) { + + case SC_INQUIRY: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "INQUIRY")) == 0) + reply = do_inquiry(fsg, bh); + break; + + case SC_MODE_SELECT_6: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST, + (1<<1) | (1<<4), 0, + "MODE SELECT(6)")) == 0) + reply = do_mode_select(fsg, bh); + break; + + case SC_MODE_SELECT_10: + fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, + (1<<1) | (3<<7), 0, + "MODE SELECT(10)")) == 0) + reply = do_mode_select(fsg, bh); + break; + + case SC_MODE_SENSE_6: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (1<<4), 0, + "MODE SENSE(6)")) == 0) + reply = do_mode_sense(fsg, bh); + break; + + case SC_MODE_SENSE_10: + fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (3<<7), 0, + "MODE SENSE(10)")) == 0) + reply = do_mode_sense(fsg, bh); + break; + + case SC_PREVENT_ALLOW_MEDIUM_REMOVAL: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 6, DATA_DIR_NONE, + (1<<4), 0, + "PREVENT-ALLOW MEDIUM REMOVAL")) == 0) + reply = do_prevent_allow(fsg); + break; + + case SC_READ_6: + i = fsg->cmnd[4]; + fsg->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (7<<1) | (1<<4), 1, + "READ(6)")) == 0) + reply = do_read(fsg); + break; + + case SC_READ_10: + fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]) << 9; + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "READ(10)")) == 0) + reply = do_read(fsg); + break; + + case SC_READ_12: + fsg->data_size_from_cmnd = get_be32(&fsg->cmnd[6]) << 9; + if ((reply = check_command(fsg, 12, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "READ(12)")) == 0) + reply = do_read(fsg); + break; + + case SC_READ_CAPACITY: + fsg->data_size_from_cmnd = 8; + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (0xf<<2) | (1<<8), 1, + "READ CAPACITY")) == 0) + reply = do_read_capacity(fsg, bh); + break; + + case SC_READ_FORMAT_CAPACITIES: + fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (3<<7), 1, + "READ FORMAT CAPACITIES")) == 0) + reply = do_read_format_capacities(fsg, bh); + break; + + case SC_REQUEST_SENSE: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "REQUEST SENSE")) == 0) + reply = do_request_sense(fsg, bh); + break; + + case SC_START_STOP_UNIT: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 6, DATA_DIR_NONE, + (1<<1) | (1<<4), 0, + "START-STOP UNIT")) == 0) + reply = do_start_stop(fsg); + break; + + case SC_SYNCHRONIZE_CACHE: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 10, DATA_DIR_NONE, + (0xf<<2) | (3<<7), 1, + "SYNCHRONIZE CACHE")) == 0) + reply = do_synchronize_cache(fsg); + break; + + case SC_TEST_UNIT_READY: + fsg->data_size_from_cmnd = 0; + reply = check_command(fsg, 6, DATA_DIR_NONE, + 0, 1, + "TEST UNIT READY"); + break; + + /* Although optional, this command is used by MS-Windows. We + * support a minimal version: BytChk must be 0. */ + case SC_VERIFY: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 10, DATA_DIR_NONE, + (1<<1) | (0xf<<2) | (3<<7), 1, + "VERIFY")) == 0) + reply = do_verify(fsg); + break; + + case SC_WRITE_6: + i = fsg->cmnd[4]; + fsg->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST, + (7<<1) | (1<<4), 1, + "WRITE(6)")) == 0) + reply = do_write(fsg); + break; + + case SC_WRITE_10: + fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]) << 9; + if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "WRITE(10)")) == 0) + reply = do_write(fsg); + break; + + case SC_WRITE_12: + fsg->data_size_from_cmnd = get_be32(&fsg->cmnd[6]) << 9; + if ((reply = check_command(fsg, 12, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "WRITE(12)")) == 0) + reply = do_write(fsg); + break; + + /* Some mandatory commands that we recognize but don't implement. + * They don't mean much in this setting. It's left as an exercise + * for anyone interested to implement RESERVE and RELEASE in terms + * of Posix locks. */ + case SC_FORMAT_UNIT: + case SC_RELEASE: + case SC_RESERVE: + case SC_SEND_DIAGNOSTIC: + /* Fall through */ + + default: + fsg->data_size_from_cmnd = 0; + sprintf(unknown, "Unknown x%02x", fsg->cmnd[0]); + if ((reply = check_command(fsg, fsg->cmnd_size, + DATA_DIR_UNKNOWN, 0xff, 0, unknown)) == 0) { + fsg->curlun->sense_data = SS_INVALID_COMMAND; + reply = -EINVAL; + } + break; + } + up_read(&fsg->filesem); + + VDBG(fsg, "reply: %d, fsg->data_size_from_cmnd: %d\n", + reply, fsg->data_size_from_cmnd); + if (reply == -EINTR || signal_pending(current)) + return -EINTR; + + /* Set up the single reply buffer for finish_reply() */ + if (reply == -EINVAL) + reply = 0; /* Error reply length */ + if (reply >= 0 && fsg->data_dir == DATA_DIR_TO_HOST) { + reply = min((u32) reply, fsg->data_size_from_cmnd); + bh->inreq->length = reply; + bh->state = BUF_STATE_FULL; + fsg->residue -= reply; + } /* Otherwise it's already set */ + + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct usb_request *req = bh->outreq; + struct bulk_cb_wrap *cbw = req->buf; + + /* Was this a real packet? */ + if (req->status) + return -EINVAL; + + /* Is the CBW valid? */ + if (req->actual != USB_BULK_CB_WRAP_LEN || + cbw->Signature != __constant_cpu_to_le32( + USB_BULK_CB_SIG)) { + DBG(fsg, "invalid CBW: len %u sig 0x%x\n", + req->actual, + le32_to_cpu(cbw->Signature)); + return -EINVAL; + } + + /* Is the CBW meaningful? */ + if (cbw->Lun >= MAX_LUNS || cbw->Flags & ~USB_BULK_IN_FLAG || + cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) { + DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, " + "cmdlen %u\n", + cbw->Lun, cbw->Flags, cbw->Length); + return -EINVAL; + } + + /* Save the command for later */ + fsg->cmnd_size = cbw->Length; + memcpy(fsg->cmnd, cbw->CDB, fsg->cmnd_size); + if (cbw->Flags & USB_BULK_IN_FLAG) + fsg->data_dir = DATA_DIR_TO_HOST; + else + fsg->data_dir = DATA_DIR_FROM_HOST; + fsg->data_size = le32_to_cpu(cbw->DataTransferLength); + if (fsg->data_size == 0) + fsg->data_dir = DATA_DIR_NONE; + fsg->lun = cbw->Lun; + fsg->tag = cbw->Tag; + return 0; +} + + +static int get_next_command(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + int rc = 0; + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + /* Queue a request to read a Bulk-only CBW */ + set_bulk_out_req_length(fsg, bh, USB_BULK_CB_WRAP_LEN); + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + + /* We will drain the buffer in software, which means we + * can reuse it for the next filling. No need to advance + * next_buffhd_to_fill. */ + + /* Wait for the CBW to arrive */ + while (bh->state != BUF_STATE_FULL) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + smp_rmb(); + rc = received_cbw(fsg, bh); + bh->state = BUF_STATE_EMPTY; + + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int alloc_request(struct fsg_dev *fsg, struct usb_endpoint *ep, + struct usb_request **preq) +{ + *preq = usb_ept_alloc_req(ep, 0); + if (*preq) + return 0; + ERROR(fsg, "can't allocate request for bulk %s\n", + (ep == fsg->bulk_in ? "IN" : "OUT")); + return -ENOMEM; +} + +/* + * Reset interface setting and re-init endpoint state (toggle etc). + * Call with altsetting < 0 to disable the interface. The only other + * available altsetting is 0, which enables the interface. + */ +static int do_set_interface(struct fsg_dev *fsg, int altsetting) +{ + int rc = 0; + int i; + + if (fsg->running) + DBG(fsg, "reset interface\n"); + +reset: + /* Deallocate the requests */ + for (i = 0; i < NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &fsg->buffhds[i]; + + if (bh->inreq) { + usb_ept_cancel_xfer(fsg->bulk_in, bh->inreq); + usb_ept_free_req(fsg->bulk_in, bh->inreq); + bh->inreq = NULL; + } + if (bh->outreq) { + usb_ept_cancel_xfer(fsg->bulk_out, bh->outreq); + usb_ept_free_req(fsg->bulk_out, bh->outreq); + bh->outreq = NULL; + } + } + + fsg->running = 0; + if (altsetting < 0 || rc != 0) + return rc; + + DBG(fsg, "set interface %d\n", altsetting); + + fsg->bulk_out_maxpacket = usb_ept_get_max_packet(fsg->bulk_out); + + /* Allocate the requests */ + for (i = 0; i < NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &fsg->buffhds[i]; + + rc = alloc_request(fsg, fsg->bulk_in, &bh->inreq); + if (rc != 0) + goto reset; + rc = alloc_request(fsg, fsg->bulk_out, &bh->outreq); + if (rc != 0) + goto reset; + bh->inreq->buf = bh->outreq->buf = bh->buf; + bh->inreq->context = bh->outreq->context = bh; + bh->inreq->complete = bulk_in_complete; + bh->outreq->complete = bulk_out_complete; + } + + fsg->running = 1; + for (i = 0; i < fsg->nluns; ++i) + fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED; + + return rc; +} + +static void adjust_wake_lock(struct fsg_dev *fsg) +{ + int ums_active = 0; + int i; + unsigned long flags; + + spin_lock_irqsave(&fsg->lock, flags); + + if (fsg->config) { + for (i = 0; i < fsg->nluns; ++i) { + if (backing_file_is_open(&fsg->luns[i])) + ums_active = 1; + } + } + + if (ums_active) + wake_lock(&fsg->wake_lock); + else + wake_unlock(&fsg->wake_lock); + + spin_unlock_irqrestore(&fsg->lock, flags); +} + +/* + * Change our operational configuration. This code must agree with the code + * that returns config descriptors, and with interface altsetting code. + * + * It's also responsible for power management interactions. Some + * configurations might not work with our current power sources. + * For now we just assume the gadget is always self-powered. + */ +static int do_set_config(struct fsg_dev *fsg, u8 new_config) +{ + int rc = 0; + + if (new_config == fsg->config) + return rc; + + /* Disable the single interface */ + if (fsg->config != 0) { + DBG(fsg, "reset config\n"); + fsg->config = 0; + rc = do_set_interface(fsg, -1); + } + + /* Enable the interface */ + if (new_config != 0) { + fsg->config = new_config; + rc = do_set_interface(fsg, 0); + if (rc != 0) + fsg->config = 0; /* Reset on errors */ + else + INFO(fsg, "config #%d\n", fsg->config); + } + + switch_set_state(&fsg->sdev, new_config); + adjust_wake_lock(fsg); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static void handle_exception(struct fsg_dev *fsg) +{ + siginfo_t info; + int sig; + int i; + struct fsg_buffhd *bh; + enum fsg_state old_state; + u8 new_config; + struct lun *curlun; + int rc; + unsigned long flags; + + DBG(fsg, "handle_exception state: %d\n", (int)fsg->state); + /* Clear the existing signals. Anything but SIGUSR1 is converted + * into a high-priority EXIT exception. */ + for (;;) { + sig = dequeue_signal_lock(current, ¤t->blocked, &info); + if (!sig) + break; + if (sig != SIGUSR1) { + if (fsg->state < FSG_STATE_EXIT) + DBG(fsg, "Main thread exiting on signal\n"); + raise_exception(fsg, FSG_STATE_EXIT); + } + } + + /* Reset the I/O buffer states and pointers, the SCSI + * state, and the exception. Then invoke the handler. */ + spin_lock_irqsave(&fsg->lock, flags); + + for (i = 0; i < NUM_BUFFERS; ++i) { + bh = &fsg->buffhds[i]; + bh->state = BUF_STATE_EMPTY; + } + fsg->next_buffhd_to_fill = fsg->next_buffhd_to_drain = + &fsg->buffhds[0]; + + new_config = fsg->new_config; + old_state = fsg->state; + + if (old_state == FSG_STATE_ABORT_BULK_OUT) + fsg->state = FSG_STATE_STATUS_PHASE; + else { + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + curlun->prevent_medium_removal = 0; + curlun->sense_data = curlun->unit_attention_data = + SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + fsg->state = FSG_STATE_IDLE; + } + spin_unlock_irqrestore(&fsg->lock, flags); + + /* Carry out any extra actions required for the exception */ + switch (old_state) { + default: + break; + + case FSG_STATE_ABORT_BULK_OUT: + DBG(fsg, "FSG_STATE_ABORT_BULK_OUT\n"); + spin_lock_irqsave(&fsg->lock, flags); + if (fsg->state == FSG_STATE_STATUS_PHASE) + fsg->state = FSG_STATE_IDLE; + spin_unlock_irqrestore(&fsg->lock, flags); + break; + + case FSG_STATE_RESET: + /* really not much to do here */ + break; + + case FSG_STATE_CONFIG_CHANGE: + rc = do_set_config(fsg, new_config); + if (new_config == 0) { + /* We're using the backing file */ + down_read(&fsg->filesem); + fsync_all(fsg); + up_read(&fsg->filesem); + } + break; + + case FSG_STATE_EXIT: + case FSG_STATE_TERMINATED: + do_set_config(fsg, 0); /* Free resources */ + spin_lock_irqsave(&fsg->lock, flags); + fsg->state = FSG_STATE_TERMINATED; /* Stop the thread */ + spin_unlock_irqrestore(&fsg->lock, flags); + break; + } +} + + +/*-------------------------------------------------------------------------*/ + +static int fsg_main_thread(void *fsg_) +{ + struct fsg_dev *fsg = fsg_; + unsigned long flags; + + /* Allow the thread to be killed by a signal, but set the signal mask + * to block everything but INT, TERM, KILL, and USR1. */ + allow_signal(SIGINT); + allow_signal(SIGTERM); + allow_signal(SIGKILL); + allow_signal(SIGUSR1); + + /* Allow the thread to be frozen */ + set_freezable(); + + /* Arrange for userspace references to be interpreted as kernel + * pointers. That way we can pass a kernel pointer to a routine + * that expects a __user pointer and it will work okay. */ + set_fs(get_ds()); + + /* The main loop */ + while (fsg->state != FSG_STATE_TERMINATED) { + if (exception_in_progress(fsg) || signal_pending(current)) { + handle_exception(fsg); + continue; + } + + if (!fsg->running) { + sleep_thread(fsg); + continue; + } + + if (get_next_command(fsg)) + continue; + + spin_lock_irqsave(&fsg->lock, flags); + if (!exception_in_progress(fsg)) + fsg->state = FSG_STATE_DATA_PHASE; + spin_unlock_irqrestore(&fsg->lock, flags); + + if (do_scsi_command(fsg) || finish_reply(fsg)) + continue; + + spin_lock_irqsave(&fsg->lock, flags); + if (!exception_in_progress(fsg)) + fsg->state = FSG_STATE_STATUS_PHASE; + spin_unlock_irqrestore(&fsg->lock, flags); + +#ifdef CONFIG_USB_CSW_HACK + /* Since status is already sent for write scsi command, + * need to skip sending status once again if it is a + * write scsi command. + */ + if (fsg->cmnd[0] == SC_WRITE_6 || fsg->cmnd[0] == SC_WRITE_10 + || fsg->cmnd[0] == SC_WRITE_12) + continue; +#endif + if (send_status(fsg)) + continue; + + spin_lock_irqsave(&fsg->lock, flags); + if (!exception_in_progress(fsg)) + fsg->state = FSG_STATE_IDLE; + spin_unlock_irqrestore(&fsg->lock, flags); + } + + spin_lock_irqsave(&fsg->lock, flags); + fsg->thread_task = NULL; + spin_unlock_irqrestore(&fsg->lock, flags); + + /* In case we are exiting because of a signal, unregister the + * gadget driver and close the backing file. */ + if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags)) + close_all_backing_files(fsg); + + /* Let the unbind and cleanup routines know the thread has exited */ + complete_and_exit(&fsg->thread_notifier, 0); +} + + +/*-------------------------------------------------------------------------*/ + +/* If the next two routines are called while the gadget is registered, + * the caller must own fsg->filesem for writing. */ + +static int open_backing_file(struct fsg_dev *fsg, struct lun *curlun, const char *filename) +{ + int ro; + struct file *filp = NULL; + int rc = -EINVAL; + struct inode *inode = NULL; + loff_t size; + loff_t num_sectors; + + /* R/W if we can, R/O if we must */ + ro = curlun->ro; + if (!ro) { + filp = filp_open(filename, O_RDWR | O_LARGEFILE, 0); + if (-EROFS == PTR_ERR(filp)) + ro = 1; + } + if (ro) + filp = filp_open(filename, O_RDONLY | O_LARGEFILE, 0); + if (IS_ERR(filp)) { + LINFO(curlun, "unable to open backing file: %s\n", filename); + return PTR_ERR(filp); + } + + if (!(filp->f_mode & FMODE_WRITE)) + ro = 1; + + if (filp->f_path.dentry) + inode = filp->f_path.dentry->d_inode; + if (inode && S_ISBLK(inode->i_mode)) { + if (bdev_read_only(inode->i_bdev)) + ro = 1; + } else if (!inode || !S_ISREG(inode->i_mode)) { + LINFO(curlun, "invalid file type: %s\n", filename); + goto out; + } + + /* If we can't read the file, it's no good. + * If we can't write the file, use it read-only. */ + if (!filp->f_op || !(filp->f_op->read || filp->f_op->aio_read)) { + LINFO(curlun, "file not readable: %s\n", filename); + goto out; + } + if (!(filp->f_op->write || filp->f_op->aio_write)) + ro = 1; + + size = i_size_read(inode->i_mapping->host); + if (size < 0) { + LINFO(curlun, "unable to find file size: %s\n", filename); + rc = (int) size; + goto out; + } + num_sectors = size >> 9; /* File size in 512-byte sectors */ + if (num_sectors == 0) { + LINFO(curlun, "file too small: %s\n", filename); + rc = -ETOOSMALL; + goto out; + } + + get_file(filp); + curlun->ro = ro; + curlun->filp = filp; + curlun->file_length = size; + curlun->num_sectors = num_sectors; + LDBG(curlun, "open backing file: %s size: %lld num_sectors: %lld\n", + filename, size, num_sectors); + rc = 0; + adjust_wake_lock(fsg); + +out: + filp_close(filp, current->files); + return rc; +} + + +static void close_backing_file(struct fsg_dev *fsg, struct lun *curlun) +{ + if (curlun->filp) { + int rc; + + /* + * XXX: San: Ugly hack here added to ensure that + * our pages get synced to disk. + * Also drop caches here just to be extra-safe + */ + rc = vfs_fsync(curlun->filp, curlun->filp->f_path.dentry, 1); + if (rc < 0) + printk(KERN_ERR "ums: Error syncing data (%d)\n", rc); + /* drop_pagecache and drop_slab are no longer available */ + /* drop_pagecache(); */ + /* drop_slab(); */ + + LDBG(curlun, "close backing file\n"); + fput(curlun->filp); + curlun->filp = NULL; + adjust_wake_lock(fsg); + } +} + +static void close_all_backing_files(struct fsg_dev *fsg) +{ + int i; + + for (i = 0; i < fsg->nluns; ++i) + close_backing_file(fsg, &fsg->luns[i]); +} + +static ssize_t show_file(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lun *curlun = dev_to_lun(dev); + struct fsg_dev *fsg = dev_get_drvdata(dev); + char *p; + ssize_t rc; + + down_read(&fsg->filesem); + if (backing_file_is_open(curlun)) { /* Get the complete pathname */ + p = d_path(&curlun->filp->f_path, buf, PAGE_SIZE - 1); + if (IS_ERR(p)) + rc = PTR_ERR(p); + else { + rc = strlen(p); + memmove(buf, p, rc); + buf[rc] = '\n'; /* Add a newline */ + buf[++rc] = 0; + } + } else { /* No file, return 0 bytes */ + *buf = 0; + rc = 0; + } + up_read(&fsg->filesem); + return rc; +} + +static ssize_t store_file(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lun *curlun = dev_to_lun(dev); + struct fsg_dev *fsg = dev_get_drvdata(dev); + int rc = 0; + + DBG(fsg, "store_file: \"%s\"\n", buf); +#if 0 + /* disabled because we need to allow closing the backing file if the media was removed */ + if (curlun->prevent_medium_removal && backing_file_is_open(curlun)) { + LDBG(curlun, "eject attempt prevented\n"); + return -EBUSY; /* "Door is locked" */ + } +#endif + + /* Remove a trailing newline */ + if (count > 0 && buf[count-1] == '\n') + ((char *) buf)[count-1] = 0; + + /* Eject current medium */ + down_write(&fsg->filesem); + if (backing_file_is_open(curlun)) { + close_backing_file(fsg, curlun); + curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT; + } + + /* Load new medium */ + if (count > 0 && buf[0]) { + rc = open_backing_file(fsg, curlun, buf); + if (rc == 0) + curlun->unit_attention_data = + SS_NOT_READY_TO_READY_TRANSITION; + } + up_write(&fsg->filesem); + return (rc < 0 ? rc : count); +} + + +static DEVICE_ATTR(file, 0444, show_file, store_file); + +/*-------------------------------------------------------------------------*/ + +static void fsg_release(struct kref *ref) +{ + struct fsg_dev *fsg = container_of(ref, struct fsg_dev, ref); + + kfree(fsg->luns); + kfree(fsg); +} + +static void lun_release(struct device *dev) +{ + struct fsg_dev *fsg = dev_get_drvdata(dev); + + kref_put(&fsg->ref, fsg_release); +} + +static void /* __init_or_exit */ fsg_unbind(void *_ctxt) +{ + struct fsg_dev *fsg = _ctxt; + int i; + struct lun *curlun; + + pr_debug("%s ()\n", __func__); + if (!fsg) + return; + if (!fsg->bound) + return; + + fsg->running = 0; + clear_bit(REGISTERED, &fsg->atomic_bitflags); + + /* Unregister the sysfs attribute files and the LUNs */ + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + if (curlun->registered) { + device_remove_file(&curlun->dev, &dev_attr_file); + device_unregister(&curlun->dev); + curlun->registered = 0; + } + } + + /* If the thread isn't already dead, tell it to exit now */ + if (fsg->state != FSG_STATE_TERMINATED) { + raise_exception(fsg, FSG_STATE_EXIT); + wait_for_completion(&fsg->thread_notifier); + + } + + /* Free the data buffers */ + for (i = 0; i < NUM_BUFFERS; ++i) { + kfree(fsg->buffhds[i].buf); + fsg->buffhds[i].buf = NULL; + } + + if (fsg->bulk_in) { + usb_ept_fifo_flush(fsg->bulk_in); + usb_ept_enable(fsg->bulk_in, 0); + usb_free_endpoint(fsg->bulk_in); + } + if (fsg->bulk_out) { + usb_ept_fifo_flush(fsg->bulk_out); + usb_ept_enable(fsg->bulk_out, 0); + usb_free_endpoint(fsg->bulk_out); + } + fsg->bound = 0; +} + +static void fsg_bind(void *_ctxt) +{ + struct fsg_dev *fsg = the_fsg; + int rc; + int i; + unsigned int ret; + struct lun *curlun; + char *pathbuf, *p; + struct usb_function *usb_func = &fsg_function; + struct usb_endpoint *ep; + + + dev_attr_file.attr.mode = 0644; + fsg->running = 0; + + /* Find out how many LUNs there should be */ + i = fsg->nluns; + if (i == 0) + i = 1; + if (i > MAX_LUNS) { + ERROR(fsg, "invalid number of LUNs: %d\n", i); + rc = -EINVAL; + goto out; + } + + /* Create the LUNs, open their backing files, and register the + * LUN devices in sysfs. */ + fsg->luns = kzalloc(i * sizeof(struct lun), GFP_KERNEL); + if (!fsg->luns) { + rc = -ENOMEM; + goto out; + } + fsg->nluns = i; + + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + curlun->ro = 0; + curlun->dev.release = lun_release; + curlun->dev.parent = &fsg->pdev->dev; + dev_set_drvdata(&curlun->dev, fsg); + snprintf(curlun->dev.bus_id, BUS_ID_SIZE, + "lun%d", i); + + rc = device_register(&curlun->dev); + if (rc != 0) { + INFO(fsg, "failed to register LUN%d: %d\n", i, rc); + goto out; + } + rc = device_create_file(&curlun->dev, &dev_attr_file); + if (rc != 0) { + ERROR(fsg, "device_create_file failed: %d\n", rc); + device_unregister(&curlun->dev); + goto out; + } + curlun->registered = 1; + kref_get(&fsg->ref); + } + ret = usb_msm_get_next_ifc_number(usb_func); + intf_desc.bInterfaceNumber = ret; + pr_debug("%s: interface number = %d\n", __func__, ret); + + ep = fsg->bulk_in = usb_alloc_endpoint(USB_DIR_IN); + hs_bulk_in_desc.bEndpointAddress = USB_DIR_IN | ep->num; + fs_bulk_in_desc.bEndpointAddress = USB_DIR_IN | ep->num; + pr_debug("%s: bulk in endpoint number = %d\n", + __func__, ep->num); + + ep = fsg->bulk_out = usb_alloc_endpoint(USB_DIR_OUT); + hs_bulk_out_desc.bEndpointAddress = USB_DIR_OUT | ep->num; + fs_bulk_out_desc.bEndpointAddress = USB_DIR_OUT | ep->num; + pr_debug("%s: bulk out endpoint number = %d\n", + __func__, ep->num); + + /* Allocate the data buffers */ + for (i = 0; i < NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &fsg->buffhds[i]; + + /* Allocate for the bulk-in endpoint. We assume that + * the buffer will also work with the bulk-out (and + * interrupt-in) endpoint. */ + bh->buf = kmalloc(fsg->buf_size, GFP_KERNEL); + if (!bh->buf) + goto out; + bh->next = bh + 1; + } + fsg->buffhds[NUM_BUFFERS - 1].next = &fsg->buffhds[0]; + + fsg->state = FSG_STATE_IDLE; + fsg->thread_task = kthread_create(fsg_main_thread, fsg, + "USB mass_storage"); + if (IS_ERR(fsg->thread_task)) { + rc = PTR_ERR(fsg->thread_task); + ERROR(fsg, "kthread_create failed: %d\n", rc); + goto out; + } + + DBG(fsg, "Number of LUNs=%d\n", fsg->nluns); + + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + if (backing_file_is_open(curlun)) { + p = NULL; + if (pathbuf) { + p = d_path(&curlun->filp->f_path, + pathbuf, PATH_MAX); + if (IS_ERR(p)) + p = NULL; + } + LINFO(curlun, "ro=%d, file: %s\n", + curlun->ro, (p ? p : "(error)")); + } + } + kfree(pathbuf); + + set_bit(REGISTERED, &fsg->atomic_bitflags); + + /* Tell the thread to start working */ + wake_up_process(fsg->thread_task); + fsg->bound = 1; + return; + +out: + fsg->state = FSG_STATE_TERMINATED; /* The thread is dead */ + fsg->bound = 1; + fsg_unbind(fsg); + close_all_backing_files(fsg); +} + +static void fsg_configure(int configured, void *_ctxt) +{ + struct fsg_dev *fsg = _ctxt; + + if (!fsg) + return; + if (!fsg->bound) + return; + + /* Clear out the controller's fifos */ + if ((fsg->new_config) && (fsg->bulk_in)) + usb_ept_fifo_flush(fsg->bulk_in); + if ((fsg->new_config) && (fsg->bulk_out)) + usb_ept_fifo_flush(fsg->bulk_out); + + if (configured) { + if (usb_msm_get_speed() == USB_SPEED_HIGH) { + usb_configure_endpoint(fsg->bulk_in, &hs_bulk_in_desc); + usb_configure_endpoint(fsg->bulk_out, + &hs_bulk_out_desc); + } else { + usb_configure_endpoint(fsg->bulk_in, &fs_bulk_in_desc); + usb_configure_endpoint(fsg->bulk_out, + &fs_bulk_out_desc); + } + + usb_ept_enable(fsg->bulk_in, 1); + usb_ept_enable(fsg->bulk_out, 1); + wake_lock(&fsg->wake_lock_idle); + } else + wake_unlock(&fsg->wake_lock_idle); + + fsg->new_config = configured; + raise_exception(fsg, FSG_STATE_CONFIG_CHANGE); +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_function fsg_function = { + .bind = fsg_bind, + .unbind = fsg_unbind, + .configure = fsg_configure, + .setup = fsg_setup, + + .name = "mass_storage", + +}; + + +static int __init fsg_alloc(void) +{ + struct fsg_dev *fsg; + + fsg = kzalloc(sizeof *fsg, GFP_KERNEL); + if (!fsg) + return -ENOMEM; + spin_lock_init(&fsg->lock); + init_rwsem(&fsg->filesem); + kref_init(&fsg->ref); + init_completion(&fsg->thread_notifier); + + the_fsg = fsg; + return 0; +} + +static ssize_t print_switch_name(struct switch_dev *sdev, char *buf) +{ + return sprintf(buf, "%s\n", DRIVER_NAME); +} + +static ssize_t print_switch_state(struct switch_dev *sdev, char *buf) +{ + struct fsg_dev *fsg = container_of(sdev, struct fsg_dev, sdev); + return sprintf(buf, "%s\n", (fsg->config ? "online" : "offline")); +} +static int __exit fsg_remove(struct platform_device *pdev) +{ + struct fsg_dev *fsg = the_fsg; + + usb_function_unregister(&fsg_function); + wake_lock_destroy(&fsg->wake_lock_idle); + switch_dev_unregister(&fsg->sdev); + test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags); + close_all_backing_files(fsg); + kref_put(&fsg->ref, fsg_release); + + return 0; +} + +static int __init fsg_probe(struct platform_device *pdev) +{ + struct usb_mass_storage_platform_data *pdata = pdev->dev.platform_data; + int rc; + + rc = fsg_alloc(); + if (rc != 0) + return rc; + + the_fsg->pdev = pdev; + the_fsg->sdev.name = DRIVER_NAME; + the_fsg->nluns = pdata->nluns; + the_fsg->buf_size = pdata->buf_size; + the_fsg->vendor = pdata->vendor; + the_fsg->product = pdata->product; + the_fsg->release = pdata->release; + the_fsg->sdev.print_name = print_switch_name; + the_fsg->sdev.print_state = print_switch_state; + rc = switch_dev_register(&the_fsg->sdev); + if (rc < 0) + goto err_switch_dev_register; + + wake_lock_init(&the_fsg->wake_lock, WAKE_LOCK_SUSPEND, + "usb_mass_storage"); + wake_lock_init(&the_fsg->wake_lock_idle, WAKE_LOCK_IDLE, + "mass_storage_hold_idle"); + + fsg_function.hs_descriptors = hs_function; + fsg_function.fs_descriptors = fs_function; + fsg_function.context = the_fsg; + rc = usb_function_register(&fsg_function); + if (rc != 0) + goto err_usb_function_register; + + return 0; + +err_usb_function_register: + switch_dev_unregister(&the_fsg->sdev); +err_switch_dev_register: + kref_put(&the_fsg->ref, fsg_release); + + return rc; +} + +static struct platform_driver fsg_driver = { + .probe = fsg_probe, + .remove = __exit_p(fsg_remove), + .driver = { .name = DRIVER_NAME, }, +}; + +static int __init fsg_init(void) +{ + return platform_driver_register(&fsg_driver); +} +module_init(fsg_init); + +static void __exit fsg_cleanup(void) +{ + platform_driver_unregister(&fsg_driver); + +} +module_exit(fsg_cleanup); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/function/msm_hsusb.c b/drivers/usb/function/msm_hsusb.c new file mode 100644 index 00000000000..eebd9d42678 --- /dev/null +++ b/drivers/usb/function/msm_hsusb.c @@ -0,0 +1,3948 @@ +/* drivers/usb/function/msm_hsusb.c + * + * Driver for HighSpeed USB Client Controller in MSM7K + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSM_USB_BASE ((unsigned) ui->addr) + +#include "usb_function.h" + +#define EPT_FLAG_IN 0x0001 +#define USB_DIR_MASK USB_DIR_IN +#define SETUP_BUF_SIZE 4096 + +/* IDs for string descriptors */ +#define STRING_LANGUAGE_ID 0 +#define STRING_SERIAL 1 +#define STRING_PRODUCT 2 +#define STRING_MANUFACTURER 3 + +#define LANGUAGE_ID 0x0409 /* en-US */ +#define SOC_ROC_2_0 0x10002 /* ROC 2.0 */ + +#define TRUE 1 +#define FALSE 0 +#define USB_LINK_RESET_TIMEOUT (msecs_to_jiffies(10)) +#define USB_CHG_DET_DELAY msecs_to_jiffies(1000) + +#define is_phy_45nm() (PHY_MODEL(ui->phy_info) == USB_PHY_MODEL_45NM) +#define is_phy_external() (PHY_TYPE(ui->phy_info) == USB_PHY_EXTERNAL) + +static int pid = 0x9018; + +struct usb_fi_ept { + struct usb_endpoint *ept; + struct usb_endpoint_descriptor desc; +}; + +struct usb_function_info { + struct list_head list; + unsigned enabled; + struct usb_function *func; +}; + +struct msm_request { + struct usb_request req; + + struct usb_info *ui; + struct msm_request *next; + + unsigned busy:1; + unsigned live:1; + unsigned alloced:1; + unsigned dead:1; + + dma_addr_t dma; + + struct ept_queue_item *item; + dma_addr_t item_dma; +}; +static unsigned char str_lang_desc[] = {4, + USB_DT_STRING, + (unsigned char)LANGUAGE_ID, + (unsigned char)(LANGUAGE_ID >> 8)}; + +#define to_msm_request(r) container_of(r, struct msm_request, req) +static int usb_hw_reset(struct usb_info *ui); +static void usb_vbus_online(struct usb_info *); +static void usb_vbus_offline(struct usb_info *ui); +static void usb_lpm_exit(struct usb_info *ui); +static void usb_lpm_wakeup_phy(struct work_struct *); +static void usb_exit(void); +static int usb_is_online(struct usb_info *ui); +static void usb_do_work(struct work_struct *w); +static int usb_lpm_enter(struct usb_info *ui); +int (*usb_lpm_config_gpio)(int); +static void usb_enable_pullup(struct usb_info *ui); +static void usb_disable_pullup(struct usb_info *ui); + +static struct workqueue_struct *usb_work; +static void usb_chg_stop(struct work_struct *w); + +#define USB_STATE_IDLE 0 +#define USB_STATE_ONLINE 1 +#define USB_STATE_OFFLINE 2 + +#define USB_FLAG_START 0x0001 +#define USB_FLAG_VBUS_ONLINE 0x0002 +#define USB_FLAG_VBUS_OFFLINE 0x0004 +#define USB_FLAG_RESET 0x0008 +#define USB_FLAG_SUSPEND 0x0010 +#define USB_FLAG_CONFIGURE 0x0020 +#define USB_FLAG_RESUME 0x0040 +#define USB_FLAG_REG_OTG 0x0080 + +#define USB_MSC_ONLY_FUNC_MAP 0x10 +#define DRIVER_NAME "msm_hsusb_peripheral" + +struct lpm_info { + struct work_struct wakeup_phy; +}; + +enum charger_type { + USB_CHG_TYPE__SDP, + USB_CHG_TYPE__CARKIT, + USB_CHG_TYPE__WALLCHARGER, + USB_CHG_TYPE__INVALID +}; + +struct usb_info { + /* lock for register/queue/device state changes */ + spinlock_t lock; + + /* single request used for handling setup transactions */ + struct usb_request *setup_req; + struct usb_request *ep0out_req; + + struct platform_device *pdev; + struct msm_hsusb_platform_data *pdata; + int irq; + int gpio_irq[2]; + void *addr; + + unsigned state; + unsigned flags; + + unsigned online; + unsigned running; + unsigned bound; + + struct dma_pool *pool; + + /* dma page to back the queue heads and items */ + unsigned char *buf; + dma_addr_t dma; + + struct ept_queue_head *head; + + /* used for allocation */ + unsigned next_item; + unsigned next_ifc_num; + unsigned stopped:1; + unsigned remote_wakeup:1; + unsigned configured:1; + unsigned selfpowered:1; + unsigned iad:1; + unsigned char maxpower; + enum usb_device_speed speed; + unsigned phy_info; + + /* endpoints are ordered based on their status bits, + ** so they are OUT0, OUT1, ... OUT15, IN0, IN1, ... IN15 + */ + struct usb_endpoint ept[32]; + + struct delayed_work work; + struct delayed_work chg_legacy_det; + unsigned phy_status; + unsigned phy_fail_count; + struct usb_composition *composition; + + struct usb_function_info **func; + unsigned num_funcs; + struct usb_function_map *functions_map; + +#define MAX_INTERFACE_NUM 15 + struct usb_function *func2ifc_map[MAX_INTERFACE_NUM]; + +#define ep0out ept[0] +#define ep0in ept[16] + + struct clk *clk; + struct clk *pclk; + struct clk *cclk; + unsigned int clk_enabled; + + struct vreg *vreg; + unsigned int vreg_enabled; + + unsigned in_lpm; + struct lpm_info li; + + enum charger_type chg_type; + struct work_struct chg_stop; +#define MAX_STRDESC_NUM 100 + char **strdesc; + int strdesc_index; + + u16 test_mode; + struct wake_lock wlock; + struct msm_otg_transceiver *xceiv; + int active; + enum usb_device_state usb_state; + int vbus_sn_notif; + struct switch_dev sdev; +}; +static struct usb_info *the_usb_info; + +static unsigned short usb_validate_product_id(unsigned short pid); +static unsigned short usb_get_product_id(unsigned long enabled_functions); +static void usb_switch_composition(unsigned short pid); +static unsigned short usb_set_composition(unsigned short pid); +static void usb_configure_device_descriptor(struct usb_info *ui); +static void usb_uninit(struct usb_info *ui); + +static unsigned ulpi_read(struct usb_info *ui, unsigned reg); +static int ulpi_write(struct usb_info *ui, unsigned val, unsigned reg); + + + +struct usb_device_descriptor desc_device = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + /* the following fields are filled in by usb_probe */ + .idVendor = 0, + .idProduct = 0, + .bcdDevice = 0, + .iManufacturer = 0, + .iProduct = 0, + .iSerialNumber = 0, + .bNumConfigurations = 1, +}; + +static void flush_endpoint(struct usb_endpoint *ept); +static void msm_hsusb_suspend_locks_acquire(struct usb_info *, int); + +static ssize_t print_switch_name(struct switch_dev *sdev, char *buf) +{ + return sprintf(buf, "%s\n", DRIVER_NAME); +} + +static ssize_t print_switch_state(struct switch_dev *sdev, char *buf) +{ + struct usb_info *ui = the_usb_info; + + return sprintf(buf, "%s\n", (ui->online ? "online" : "offline")); +} + +#define USB_WALLCHARGER_CHG_CURRENT 1800 +static int usb_get_max_power(struct usb_info *ui) +{ + unsigned long flags; + enum charger_type temp; + int suspended; + int configured; + + spin_lock_irqsave(&ui->lock, flags); + temp = ui->chg_type; + suspended = ui->usb_state == USB_STATE_SUSPENDED ? 1 : 0; + configured = ui->configured; + spin_unlock_irqrestore(&ui->lock, flags); + + if (temp == USB_CHG_TYPE__INVALID) + return -ENODEV; + + if (temp == USB_CHG_TYPE__WALLCHARGER) + return USB_WALLCHARGER_CHG_CURRENT; + + if (suspended || !configured) + return 0; + + return ui->maxpower * 2; +} + +static void usb_chg_legacy_detect(struct work_struct *w) +{ + struct usb_info *ui = the_usb_info; + unsigned long flags; + enum charger_type temp = USB_CHG_TYPE__INVALID; + int maxpower; + int ret = 0; + + spin_lock_irqsave(&ui->lock, flags); + + if (ui->usb_state == USB_STATE_NOTATTACHED) { + ret = -ENODEV; + goto chg_legacy_det_out; + } + + if ((readl(USB_PORTSC) & PORTSC_LS) == PORTSC_LS) { + ui->chg_type = temp = USB_CHG_TYPE__WALLCHARGER; + goto chg_legacy_det_out; + } + + ui->chg_type = temp = USB_CHG_TYPE__SDP; +chg_legacy_det_out: + spin_unlock_irqrestore(&ui->lock, flags); + + if (ret) + return; + + msm_chg_usb_charger_connected(temp); + maxpower = usb_get_max_power(ui); + if (maxpower > 0) + msm_chg_usb_i_is_available(maxpower); + + /* USB driver prevents idle and suspend power collapse(pc) + * while usb cable is connected. But when dedicated charger is + * connected, driver can vote for idle and suspend pc. In order + * to allow pc, driver has to initiate low power mode which it + * cannot do as phy cannot be accessed when dedicated charger + * is connected due to phy lockup issues. Just to allow idle & + * suspend pc when dedicated charger is connected, release the + * wakelock, set driver latency to default and act as if we are + * in low power mode so that, driver will re-acquire wakelocks + * for any sub-sequent usb interrupts. + */ + if (temp == USB_CHG_TYPE__WALLCHARGER) { + pr_info("\n%s: WALL-CHARGER\n", __func__); + spin_lock_irqsave(&ui->lock, flags); + if (ui->usb_state == USB_STATE_NOTATTACHED) { + spin_unlock_irqrestore(&ui->lock, flags); + return; + } + ui->in_lpm = 1; + spin_unlock_irqrestore(&ui->lock, flags); + + msm_hsusb_suspend_locks_acquire(ui, 0); + } else + pr_info("\n%s: Standard Downstream Port\n", __func__); +} + +int usb_msm_get_next_strdesc_id(char *str) +{ + struct usb_info *ui = the_usb_info; + unsigned id; + unsigned long flags; + int len; + + len = strlen(str); + if (!len) { + printk(KERN_ERR "usb next_strdesc_id(); null string\n"); + return -EPERM; + } + /* for null character */ + len = len + 1; + + spin_lock_irqsave(&ui->lock, flags); + + id = ui->strdesc_index; + if (id >= MAX_STRDESC_NUM) { + id = -EPERM; + printk(KERN_ERR "reached max strdesc number\n"); + goto get_strd_id_exit; + } + + ui->strdesc[id] = kmalloc(len, GFP_ATOMIC); + if (ui->strdesc[id]) { + memcpy(ui->strdesc[id], str, len); + ui->strdesc_index++; + } else { + id = -EPERM; + printk(KERN_ERR "usb next_strdesc_id(); Out of memory:(%s)\n", + str); + } + +get_strd_id_exit: + spin_unlock_irqrestore(&ui->lock, flags); + return id; +} +EXPORT_SYMBOL(usb_msm_get_next_strdesc_id); + + +inline int usb_msm_is_iad(void) +{ + return the_usb_info->iad; +} +EXPORT_SYMBOL(usb_msm_is_iad); + +inline void usb_msm_enable_iad(void) +{ + the_usb_info->iad = 1; +} +EXPORT_SYMBOL(usb_msm_enable_iad); + +int usb_msm_get_speed() +{ + return the_usb_info->speed; +} +EXPORT_SYMBOL(usb_msm_get_speed); + +int usb_msm_get_next_ifc_number(struct usb_function *driver) +{ + struct usb_info *ui = the_usb_info; + int ifc_num = -1; + unsigned long flags; + int i; + + spin_lock_irqsave(&ui->lock, flags); + for (i = 0; i < ui->pdata->num_functions; i++) { + if (strcmp(ui->functions_map[i].name, driver->name)) + continue; + if (!(ui->composition->functions & (1 << i))) + continue; + ifc_num = ui->next_ifc_num++; + ui->func2ifc_map[ifc_num] = driver; + break; + } + spin_unlock_irqrestore(&ui->lock, flags); + return ifc_num; +} +EXPORT_SYMBOL(usb_msm_get_next_ifc_number); + +static inline int usb_msm_get_selfpowered(void) +{ + struct usb_info *ui = the_usb_info; + + return ui->selfpowered; +} +static inline int usb_msm_get_remotewakeup(void) +{ + struct usb_info *ui = the_usb_info; + + return ui->remote_wakeup; +} + +static void usb_clk_enable(struct usb_info *ui) +{ + if (!ui->clk_enabled) { + clk_enable(ui->pclk); + if (ui->cclk) + clk_enable(ui->cclk); + ui->clk_enabled = 1; + } +} + +static void usb_clk_disable(struct usb_info *ui) +{ + if (ui->clk_enabled) { + clk_disable(ui->pclk); + if (ui->cclk) + clk_disable(ui->cclk); + ui->clk_enabled = 0; + } +} + +static void usb_vreg_enable(struct usb_info *ui) +{ + if (ui->vreg && !IS_ERR(ui->vreg) && !ui->vreg_enabled) { + vreg_enable(ui->vreg); + ui->vreg_enabled = 1; + } +} + +static void usb_vreg_disable(struct usb_info *ui) +{ + if (ui->vreg && !IS_ERR(ui->vreg) && ui->vreg_enabled) { + vreg_disable(ui->vreg); + ui->vreg_enabled = 0; + } +} + +static unsigned ulpi_read(struct usb_info *ui, unsigned reg) +{ + unsigned timeout = 100000; + + /* initiate read operation */ + writel(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while ((readl(USB_ULPI_VIEWPORT) & ULPI_RUN) && (--timeout)) ; + + if (timeout == 0) { + printk(KERN_ERR "ulpi_read: timeout %08x\n", + readl(USB_ULPI_VIEWPORT)); + return 0xffffffff; + } + return ULPI_DATA_READ(readl(USB_ULPI_VIEWPORT)); +} + +static int ulpi_write(struct usb_info *ui, unsigned val, unsigned reg) +{ + unsigned timeout = 10000; + + /* initiate write operation */ + writel(ULPI_RUN | ULPI_WRITE | + ULPI_ADDR(reg) | ULPI_DATA(val), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while((readl(USB_ULPI_VIEWPORT) & ULPI_RUN) && (--timeout)) ; + + if (timeout == 0) { + printk(KERN_ERR "ulpi_write: timeout\n"); + return -1; + } + + return 0; +} + +static void msm_hsusb_suspend_locks_acquire(struct usb_info *ui, int acquire) +{ + if (acquire) { + wake_lock(&ui->wlock); + pm_qos_update_requirement(PM_QOS_CPU_DMA_LATENCY, + DRIVER_NAME, ui->pdata->swfi_latency); + /* targets like 7x30 have introduced core clock + * to remove the dependency on max axi frequency + */ + if (!ui->cclk) + pm_qos_update_requirement(PM_QOS_SYSTEM_BUS_FREQ, + DRIVER_NAME, MSM_AXI_MAX_FREQ); + } else { + wake_lock_timeout(&ui->wlock, HZ / 2); + pm_qos_update_requirement(PM_QOS_CPU_DMA_LATENCY, + DRIVER_NAME, + PM_QOS_DEFAULT_VALUE); + if (!ui->cclk) + pm_qos_update_requirement(PM_QOS_SYSTEM_BUS_FREQ, + DRIVER_NAME, PM_QOS_DEFAULT_VALUE); + } +} + +static void msm_hsusb_suspend_locks_init(struct usb_info *ui, int init) +{ + if (init) { + wake_lock_init(&ui->wlock, WAKE_LOCK_SUSPEND, + "usb_bus_active"); + pm_qos_add_requirement(PM_QOS_CPU_DMA_LATENCY, + DRIVER_NAME, + PM_QOS_DEFAULT_VALUE); + pm_qos_add_requirement(PM_QOS_SYSTEM_BUS_FREQ, + DRIVER_NAME, PM_QOS_DEFAULT_VALUE); + } else { + wake_lock_destroy(&ui->wlock); + pm_qos_remove_requirement(PM_QOS_CPU_DMA_LATENCY, DRIVER_NAME); + pm_qos_remove_requirement(PM_QOS_SYSTEM_BUS_FREQ, DRIVER_NAME); + } +} + +static void init_endpoints(struct usb_info *ui) +{ + unsigned n; + + for (n = 0; n < 32; n++) { + struct usb_endpoint *ept = ui->ept + n; + + ept->ui = ui; + ept->bit = n; + ept->num = n & 15; + ept->alloced = 0; + + if (ept->bit > 15) { + /* IN endpoint */ + ept->head = ui->head + (ept->num << 1) + 1; + ept->flags = EPT_FLAG_IN; + } else { + /* OUT endpoint */ + ept->head = ui->head + (ept->num << 1); + ept->flags = 0; + } + } +} + +void usb_configure_endpoint(struct usb_endpoint *ep, + struct usb_endpoint_descriptor *ep_desc) +{ + unsigned cfg = 0; + unsigned long flags; + struct usb_info *ui = ep->ui; + + if (!ui) + return; + spin_lock_irqsave(&ui->lock, flags); + + if (ep_desc) { + ep->max_pkt = ep_desc->wMaxPacketSize; + ep->ep_descriptor = ep_desc; + } + + if (!ep->max_pkt) { + printk(KERN_ERR "cannot configure zero length max pkt\n"); + goto cfg_ept_end; + } + + cfg = CONFIG_MAX_PKT(ep->max_pkt) | CONFIG_ZLT; + /* ep0 out needs interrupt-on-setup */ + if (ep->bit == 0) + cfg |= CONFIG_IOS; + ep->head->config = cfg; + ep->head->next = TERMINATE; + + pr_debug("ept #%d %s max:%d head:%p bit:%d\n", + ep->num, + (ep->flags & EPT_FLAG_IN) ? "in" : "out", + ep->max_pkt, ep->head, ep->bit); + +cfg_ept_end: + spin_unlock_irqrestore(&ui->lock, flags); +} +EXPORT_SYMBOL(usb_configure_endpoint); + +#define NUM_EPTS 15 /* number of in or out non-ctrl endpoints */ +struct usb_endpoint *usb_alloc_endpoint(unsigned direction) +{ + struct usb_info *ui = the_usb_info; + struct usb_endpoint *ept = NULL; + int i; + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + if (direction & USB_DIR_IN) + ept = (&ui->ep0in); + else + ept = (&ui->ep0out); + + for (i = 0; i < NUM_EPTS; i++) { + ept++; + if (!ept->alloced) { + ept->alloced = 1; + ept->ui = ui; + spin_unlock_irqrestore(&ui->lock, flags); + return ept; + } + } + spin_unlock_irqrestore(&ui->lock, flags); + + return NULL; +} +EXPORT_SYMBOL(usb_alloc_endpoint); + +int usb_free_endpoint(struct usb_endpoint *ept) +{ + struct usb_info *ui = the_usb_info; + unsigned long flags; + + if (!ept) + return -EINVAL; + spin_lock_irqsave(&ui->lock, flags); + ept->alloced = 0; + ept->ui = 0; + spin_unlock_irqrestore(&ui->lock, flags); + + return 0; +} +EXPORT_SYMBOL(usb_free_endpoint); + +struct usb_request *usb_ept_alloc_req(struct usb_endpoint *ept, + unsigned bufsize) +{ + struct usb_info *ui = ept->ui; + struct msm_request *req; + + if (!ui) + return NULL; + + req = kzalloc(sizeof(*req), GFP_ATOMIC); + if (!req) + goto fail1; + + req->item = dma_pool_alloc(ui->pool, GFP_ATOMIC, &req->item_dma); + if (!req->item) + goto fail2; + + if (bufsize) { + req->req.buf = kmalloc(bufsize, GFP_ATOMIC); + if (!req->req.buf) + goto fail3; + req->alloced = 1; + } + + return &req->req; + +fail3: + dma_pool_free(ui->pool, req->item, req->item_dma); +fail2: + kfree(req); +fail1: + return NULL; +} +EXPORT_SYMBOL(usb_ept_alloc_req); + +static void do_free_req(struct usb_info *ui, struct msm_request *req) +{ + if (req->alloced) + kfree(req->req.buf); + + dma_pool_free(ui->pool, req->item, req->item_dma); + kfree(req); +} + +void usb_ept_free_req(struct usb_endpoint *ept, struct usb_request *_req) +{ + struct msm_request *req, *temp_req, *prev_req; + struct usb_info *ui; + unsigned long flags; + int dead = 0; + if (!ept || !_req) + return; + + ui = ept->ui; + if (!ui) + return; + + req = to_msm_request(_req); + spin_lock_irqsave(&ui->lock, flags); + /* defer freeing resources if request is still busy */ + if (req->busy) + dead = req->dead = 1; + spin_unlock_irqrestore(&ui->lock, flags); + + /* if req->dead, then we will clean up when the request finishes */ + if (!dead) { + temp_req = ept->req; + prev_req = temp_req; + while (temp_req != NULL) { + if (req == temp_req && ept->req != temp_req) + prev_req->next = temp_req->next; + + prev_req = temp_req; + temp_req = temp_req->next; + } + if (ept->req == req) + ept->req = req->next; + req->req.complete = NULL; + do_free_req(ui, req); + } else + pr_err("%s: req is busy, can't free req\n", __func__); +} +EXPORT_SYMBOL(usb_ept_free_req); + +void usb_ept_enable(struct usb_endpoint *ept, int yes) +{ + struct usb_info *ui; + int in; + unsigned n; + unsigned char xfer; + + if (!ept || !ept->ui) + return; + ui = ept->ui; + in = ept->flags & EPT_FLAG_IN; + if (!ept->ep_descriptor) + return; + + if (ui->in_lpm) { + pr_err("%s: controller is in lpm, cannot proceed\n", __func__); + return; + } + + xfer = ept->ep_descriptor->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + n = readl(USB_ENDPTCTRL(ept->num)); + + if (in) { + if (xfer == USB_ENDPOINT_XFER_BULK) + n = (n & (~CTRL_TXT_MASK)) | CTRL_TXT_BULK; + else if (xfer == USB_ENDPOINT_XFER_INT) + n = (n & (~CTRL_TXT_MASK)) | CTRL_TXT_INT; + if (yes) + n |= CTRL_TXE | CTRL_TXR; + else + n &= (~CTRL_TXE); + } else { + if (xfer == USB_ENDPOINT_XFER_BULK) + n = (n & (~CTRL_RXT_MASK)) | CTRL_RXT_BULK; + else if (xfer == USB_ENDPOINT_XFER_INT) + n = (n & (~CTRL_RXT_MASK)) | CTRL_RXT_INT; + if (yes) + n |= CTRL_RXE | CTRL_RXR; + else + n &= ~(CTRL_RXE); + } + /* complete all the updates to ept->head before enabling endpoint*/ + dma_coherent_pre_ops(); + writel(n, USB_ENDPTCTRL(ept->num)); +} +EXPORT_SYMBOL(usb_ept_enable); + +static void usb_ept_start(struct usb_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + struct msm_request *req = ept->req; + + BUG_ON(req->live); + + /* link the hw queue head to the request's transaction item */ + ept->head->next = req->item_dma; + ept->head->info = 0; + + /* memory barrier to flush the data before priming endpoint*/ + dma_coherent_pre_ops(); + /* start the endpoint */ + writel(1 << ept->bit, USB_ENDPTPRIME); + + /* mark this chain of requests as live */ + while (req) { + req->live = 1; + if (req->item->next == TERMINATE) + break; + req = req->next; + } +} + +int usb_ept_queue_xfer(struct usb_endpoint *ept, struct usb_request *_req) +{ + unsigned long flags; + struct msm_request *req = to_msm_request(_req); + struct msm_request *last; + struct usb_info *ui = ept->ui; + struct ept_queue_item *item = req->item; + unsigned length = req->req.length; + + if (length > 0x4000) + return -EMSGSIZE; + + if (ui->in_lpm) { + req->req.status = usb_remote_wakeup(); + if (req->req.status) { + pr_debug("%s:RWakeup generation failed, EP = %x\n", + __func__, ept->bit); + return req->req.status; + } + } + + spin_lock_irqsave(&ui->lock, flags); + + if (req->busy) { + req->req.status = -EBUSY; + spin_unlock_irqrestore(&ui->lock, flags); + printk(KERN_INFO + "usb_ept_queue_xfer() tried to queue busy request\n"); + return -EBUSY; + } + + if (!ui->online && (ept->num != 0)) { + req->req.status = -ENODEV; + spin_unlock_irqrestore(&ui->lock, flags); + printk(KERN_INFO "usb_ept_queue_xfer() tried to queue request" + "while offline; ept->bit: %x\n", ept->bit); + return -ENODEV; + } + + req->busy = 1; + req->live = 0; + req->next = 0; + req->req.status = -EBUSY; + + req->dma = dma_map_single(NULL, req->req.buf, length, + (ept->flags & EPT_FLAG_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + + /* prepare the transaction descriptor item for the hardware */ + item->next = TERMINATE; + item->info = INFO_BYTES(length) | INFO_IOC | INFO_ACTIVE; + item->page0 = req->dma; + item->page1 = (req->dma + 0x1000) & 0xfffff000; + item->page2 = (req->dma + 0x2000) & 0xfffff000; + item->page3 = (req->dma + 0x3000) & 0xfffff000; + + /* Add the new request to the end of the queue */ + last = ept->last; + if (last) { + /* Already requests in the queue. add us to the + * end, but let the completion interrupt actually + * start things going, to avoid hw issues + */ + last->next = req; + + /* only modify the hw transaction next pointer if + * that request is not live + */ + if (!last->live) + last->item->next = req->item_dma; + } else { + /* queue was empty -- kick the hardware */ + ept->req = req; + usb_ept_start(ept); + } + ept->last = req; + + spin_unlock_irqrestore(&ui->lock, flags); + return 0; +} +EXPORT_SYMBOL(usb_ept_queue_xfer); + +int usb_ept_flush(struct usb_endpoint *ept) +{ + printk("usb_ept_flush \n"); + flush_endpoint(ept); + return 0; +} + +int usb_ept_get_max_packet(struct usb_endpoint *ept) +{ + return ept->max_pkt; +} +EXPORT_SYMBOL(usb_ept_get_max_packet); + +int usb_remote_wakeup(void) +{ + struct usb_info *ui = the_usb_info; + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + if (!ui->remote_wakeup) { + spin_unlock_irqrestore(&ui->lock, flags); + pr_err("%s: remote wakeup not supported\n", __func__); + return -ENOTSUPP; + } + + if (!ui->online) { + spin_unlock_irqrestore(&ui->lock, flags); + pr_err("%s: device is not configured\n", __func__); + return -ENODEV; + } + + if (ui->in_lpm) + usb_lpm_exit(ui); + spin_unlock_irqrestore(&ui->lock, flags); + + /* if usb_lpm_exit is unable to set PHCD, + * it would initiate workthread to set the PHCD + */ + if (cancel_work_sync(&ui->li.wakeup_phy)) + usb_lpm_wakeup_phy(NULL); + + spin_lock_irqsave(&ui->lock, flags); + if (ui->in_lpm) { + spin_unlock_irqrestore(&ui->lock, flags); + pr_err("%s: cannot bring controller out of lpm\n", __func__); + return -ENODEV; + } + + if (!usb_is_online(ui)) { + pr_debug("%s: enabling force resume\n", __func__); + writel(readl(USB_PORTSC) | PORTSC_FPR, USB_PORTSC); + } else + pr_debug("%s: controller seems to be out of suspend already\n", + __func__); + spin_unlock_irqrestore(&ui->lock, flags); + + return 0; +} +EXPORT_SYMBOL(usb_remote_wakeup); + +/* --- endpoint 0 handling --- */ + +static void set_configuration(struct usb_info *ui, int yes) +{ + unsigned i; + + ui->online = !!yes; + + for (i = 0; i < ui->num_funcs; i++) { + struct usb_function_info *fi = ui->func[i]; + if (!fi || !(ui->composition->functions & (1 << i))) + continue; + if (fi->func->configure) + fi->func->configure(yes, fi->func->context); + } +} + +static void ep0out_complete(struct usb_endpoint *ept, struct usb_request *req) +{ + req->complete = 0; +} + +static void ep0in_complete(struct usb_endpoint *ept, struct usb_request *req) +{ + /* queue up the receive of the ACK response from the host */ + if (req->status == 0) { + struct usb_info *ui = ept->ui; + req->length = 0; + req->complete = ep0out_complete; + usb_ept_queue_xfer(&ui->ep0out, req); + } +} + +static void ep0in_complete_sendzero( + struct usb_endpoint *ept, struct usb_request *req) +{ + if (req->status == 0) { + struct usb_info *ui = ept->ui; + req->length = 0; + req->complete = ep0in_complete; + usb_ept_queue_xfer(&ui->ep0in, req); + } +} + +static void ep0_status_complete( + struct usb_endpoint *ept, struct usb_request *req) +{ + struct usb_info *ui = ept->ui; + unsigned int i; + + if (!ui->test_mode) + return; + + switch (ui->test_mode) { + case J_TEST: + pr_info("usb electrical test mode: (J)\n"); + i = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(i | PORTSC_PTC_J_STATE, USB_PORTSC); + break; + + case K_TEST: + pr_info("usb electrical test mode: (K)\n"); + i = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(i | PORTSC_PTC_K_STATE, USB_PORTSC); + break; + + case SE0_NAK_TEST: + pr_info("usb electrical test mode: (SE0-NAK)\n"); + i = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(i | PORTSC_PTC_SE0_NAK, USB_PORTSC); + break; + + case TST_PKT_TEST: + pr_info("usb electrical test mode: (TEST_PKT)\n"); + i = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(i | PORTSC_PTC_TST_PKT, USB_PORTSC); + break; + default: + pr_err("usb:%s: undefined test mode: (%x)\n", + __func__, ui->test_mode); + } + +} + +static void ep0_setup_ack(struct usb_info *ui) +{ + struct usb_request *req = ui->setup_req; + req->length = 0; + req->complete = ep0_status_complete; + usb_ept_queue_xfer(&ui->ep0in, req); +} + +static void ep0_setup_stall(struct usb_info *ui) +{ + writel((1<<16) | (1<<0), USB_ENDPTCTRL(0)); +} + +static void ep0_setup_receive(struct usb_info *ui, unsigned len) +{ + ui->ep0out_req->length = len; + usb_ept_queue_xfer(&ui->ep0out, ui->ep0out_req); +} + +static void ep0_setup_send(struct usb_info *ui, unsigned wlen) +{ + struct usb_request *req = ui->setup_req; + struct usb_endpoint *ept = &ui->ep0in; + + /* never send more data than the host requested */ + if (req->length > wlen) + req->length = wlen; + + /* if we are sending a short response that ends on + * a packet boundary, we'll need to send a zero length + * packet as well. + */ + if ((req->length != wlen) && ((req->length & 63) == 0)) { + req->complete = ep0in_complete_sendzero; + } else { + req->complete = ep0in_complete; + } + + usb_ept_queue_xfer(ept, req); +} + + +static int usb_find_descriptor(struct usb_info *ui, struct usb_ctrlrequest *ctl, + struct usb_request *req); + +static void handle_setup(struct usb_info *ui) +{ + struct usb_ctrlrequest ctl; + + memcpy(&ctl, ui->ep0out.head->setup_data, sizeof(ctl)); + writel(EPT_RX(0), USB_ENDPTSETUPSTAT); + + /* any pending ep0 transactions must be canceled */ + flush_endpoint(&ui->ep0out); + flush_endpoint(&ui->ep0in); + + /* let functions handle vendor and class requests */ + if ((ctl.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) { + struct usb_function *func; + + /* Send stall if received interface number is invalid */ + if (ctl.wIndex >= ui->next_ifc_num) + goto stall; + + func = ui->func2ifc_map[ctl.wIndex]; + if (func && func->setup) { + if (ctl.bRequestType & USB_DIR_IN) { + struct usb_request *req = ui->setup_req; + int ret = func->setup(&ctl, + req->buf, SETUP_BUF_SIZE, + func->context); + if (ret >= 0) { + req->length = ret; + ep0_setup_send(ui, ctl.wLength); + return; + } + } else { + int ret = func->setup(&ctl, NULL, 0, + func->context); + if (ret == 0) { + ep0_setup_ack(ui); + return; + } else if (ret > 0) { + ep0_setup_receive(ui, ret); + return; + } + } + } + goto stall; + return; + } + + switch (ctl.bRequest) { + case USB_REQ_GET_STATUS: + { + struct usb_request *req = ui->setup_req; + if ((ctl.bRequestType & (USB_DIR_MASK)) != (USB_DIR_IN)) + break; + if (ctl.wLength != 2) + break; + req->length = 2; + switch (ctl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_ENDPOINT: + { + unsigned num = ctl.wIndex & USB_ENDPOINT_NUMBER_MASK; + struct usb_endpoint *ept; + + if (num == 0) + break; + if (ctl.wIndex & USB_ENDPOINT_DIR_MASK) + num += 16; + ept = ui->ept + num; + memcpy(req->buf, &ept->ept_halted, 2); + break; + } + + case USB_RECIP_DEVICE: + { + unsigned short temp = 0; + if (usb_msm_get_selfpowered()) + temp = 1 << USB_DEVICE_SELF_POWERED; + if (usb_msm_get_remotewakeup()) + temp |= 1 << USB_DEVICE_REMOTE_WAKEUP; + memcpy(req->buf, &temp, 2); + break; + } + + case USB_RECIP_INTERFACE: + memset(req->buf, 0, 2); + break; + default: + printk(KERN_ERR "Unreconginized recipient\n"); + break; + } + + ep0_setup_send(ui, 2); + return; + } + + case USB_REQ_GET_DESCRIPTOR: + { + struct usb_request *req; + + if ((ctl.bRequestType & (USB_DIR_MASK)) != (USB_DIR_IN)) + break; + + req = ui->setup_req; + if (!usb_find_descriptor(ui, &ctl, req)) { + if (req->length > ctl.wLength) + req->length = ctl.wLength; + ep0_setup_send(ui, ctl.wLength); + return; + } + break; + } + + case USB_REQ_SET_FEATURE: + if ((ctl.bRequestType & (USB_DIR_MASK)) != (USB_DIR_OUT)) + break; + if (ctl.wLength != 0) + break; + switch (ctl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + if (ctl.wValue == USB_DEVICE_REMOTE_WAKEUP) { + ui->remote_wakeup = 1; + ep0_setup_ack(ui); + return; + } else if (ctl.wValue == USB_DEVICE_TEST_MODE) { + if (ctl.wIndex & 0x0f) + break; + ui->test_mode = ctl.wIndex; + ep0_setup_ack(ui); + return; + } + break; + + case USB_RECIP_ENDPOINT: + { + unsigned num = ctl.wIndex & USB_ENDPOINT_NUMBER_MASK; + if ((num == 0) || (ctl.wValue != 0)) + break; + if (ctl.wIndex & USB_ENDPOINT_DIR_MASK) + num += 16; + usb_ept_set_halt(ui->ept + num); + ep0_setup_ack(ui); + return; + } + + default: + pr_err("usb: %s: set_feature: unrecognized recipient\n", + __func__); + break; + } + break; + + case USB_REQ_CLEAR_FEATURE: + { + if ((ctl.bRequestType & (USB_DIR_MASK)) != (USB_DIR_OUT)) + break; + if (ctl.wLength != 0) + break; + + switch (ctl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + if (ctl.wValue != USB_DEVICE_REMOTE_WAKEUP) + break; + ui->remote_wakeup = 0; + ep0_setup_ack(ui); + return; + case USB_RECIP_ENDPOINT: + { + unsigned num; + if (ctl.wValue != USB_ENDPOINT_HALT) + break; + num = ctl.wIndex & USB_ENDPOINT_NUMBER_MASK; + if (num != 0) { + if (ctl.wIndex & USB_ENDPOINT_DIR_MASK) + num += 16; + usb_ept_clear_halt(ui->ept + num); + } + ep0_setup_ack(ui); + return; + } + default: + pr_info("unsupported clear feature command\n"); + pr_info("Request-type:(%08x) wValue:(%08x) " + "wIndex:(%08x) wLength:(%08x)\n", + ctl.bRequestType, ctl.wValue, + ctl.wIndex, ctl.wLength); + break; + } + break; + } + + case USB_REQ_SET_INTERFACE: + if ((ctl.bRequestType & (USB_DIR_MASK | USB_RECIP_MASK)) + != (USB_DIR_OUT | USB_RECIP_INTERFACE)) + break; + if (ui->func2ifc_map[ctl.wIndex]->set_interface) { + ui->func2ifc_map[ctl.wIndex]->set_interface(ctl.wIndex, + ctl.wValue, + ui->func2ifc_map[ctl.wIndex]->context); + ep0_setup_ack(ui); + return; + } + break; + case USB_REQ_GET_INTERFACE: + { + struct usb_function *f; + struct usb_request *req = ui->setup_req; + int ifc_num = ctl.wIndex; + int ret = 0; + + if ((ctl.bRequestType & (USB_DIR_MASK | USB_RECIP_MASK)) + != (USB_DIR_IN | USB_RECIP_INTERFACE)) + break; + + f = ui->func2ifc_map[ifc_num]; + if (!f->get_interface) + break; + ret = f->get_interface(ifc_num, + ui->func2ifc_map[ifc_num]->context); + if (ret < 0) + break; + req->length = ctl.wLength; + memcpy(req->buf, &ret, req->length); + ep0_setup_send(ui, ctl.wLength); + return; + } + case USB_REQ_SET_CONFIGURATION: + if ((ctl.bRequestType & USB_DIR_MASK) != USB_DIR_OUT) + break; + ui->configured = ctl.wValue; + pr_info("hsusb set_configuration wValue = %d usbcmd = %x\n", + ctl.wValue, readl(USB_USBCMD)); + set_configuration(ui, ctl.wValue); + ep0_setup_ack(ui); + ui->flags = USB_FLAG_CONFIGURE; + if (ui->configured) + ui->usb_state = USB_STATE_CONFIGURED; + queue_delayed_work(usb_work, &ui->work, 0); + return; + + case USB_REQ_GET_CONFIGURATION: + { + unsigned conf; + struct usb_request *req = ui->setup_req; + req->length = 1; + conf = ui->configured; + memcpy(req->buf, &conf, req->length); + ep0_setup_send(ui, ctl.wLength); + return; + } + + case USB_REQ_SET_ADDRESS: + if ((ctl.bRequestType & (USB_DIR_MASK | USB_RECIP_MASK)) + != (USB_DIR_OUT | USB_RECIP_DEVICE)) + break; + ui->usb_state = USB_STATE_ADDRESS; + writel((ctl.wValue << 25) | (1 << 24), USB_DEVICEADDR); + ep0_setup_ack(ui); + return; + } + +stall: + ep0_setup_stall(ui); + return; + +} + +static void handle_endpoint(struct usb_info *ui, unsigned bit) +{ + struct usb_endpoint *ept = ui->ept + bit; + struct msm_request *req; + unsigned long flags; + unsigned info; + +#if 0 + printk(KERN_INFO "handle_endpoint() %d %s req=%p(%08x)\n", + ept->num, (ept->flags & EPT_FLAG_IN) ? "in" : "out", + ept->req, ept->req ? ept->req->item_dma : 0); +#endif + if (!ept) { + pr_err("%s: ept is null: ep bit = %d\n", __func__, bit); + return; + } + + /* expire all requests that are no longer active */ + spin_lock_irqsave(&ui->lock, flags); + while ((req = ept->req)) { + /* clean speculative fetches on req->item->info */ + dma_coherent_post_ops(); + info = req->item->info; + + /* if we've processed all live requests, time to + * restart the hardware on the next non-live request + */ + if (!req->live) { + usb_ept_start(ept); + break; + } + + /* if the transaction is still in-flight, stop here */ + if (info & INFO_ACTIVE) + break; + + /* advance ept queue to the next request */ + ept->req = req->next; + if (ept->req == 0) + ept->last = 0; + + dma_unmap_single(NULL, req->dma, req->req.length, + (ept->flags & EPT_FLAG_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + + if (info & (INFO_HALTED | INFO_BUFFER_ERROR | INFO_TXN_ERROR)) { + /* XXX pass on more specific error code */ + req->req.status = -EIO; + req->req.actual = 0; + printk(KERN_INFO "hsusb: ept %d %s error. info=%08x\n", + ept->num, + (ept->flags & EPT_FLAG_IN) ? "in" : "out", + info); + } else { + req->req.status = 0; + req->req.actual = req->req.length - ((info >> 16) & 0x7FFF); + } + req->busy = 0; + req->live = 0; + if (req->dead) + do_free_req(ui, req); + + if (req->req.complete) { + spin_unlock_irqrestore(&ui->lock, flags); + req->req.complete(ept, &req->req); + spin_lock_irqsave(&ui->lock, flags); + } + } + spin_unlock_irqrestore(&ui->lock, flags); +} + +static void flush_endpoint_hw(struct usb_info *ui, unsigned bits) +{ + /* flush endpoint, canceling transactions + ** - this can take a "large amount of time" (per databook) + ** - the flush can fail in some cases, thus we check STAT + ** and repeat if we're still operating + ** (does the fact that this doesn't use the tripwire matter?!) + */ + + if (ui->in_lpm) { + pr_err("%s: controller is in lpm, cannot proceed\n", __func__); + return; + } + + do { + writel(bits, USB_ENDPTFLUSH); + while (readl(USB_ENDPTFLUSH) & bits) + udelay(100); + } while (readl(USB_ENDPTSTAT) & bits); +} + +static void flush_endpoint_sw(struct usb_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + struct msm_request *req, *next; + unsigned long flags; + + /* inactive endpoints have nothing to do here */ + if (!ui || !ept->alloced || !ept->max_pkt) + return; + + /* put the queue head in a sane state */ + ept->head->info = 0; + ept->head->next = TERMINATE; + + /* cancel any pending requests */ + spin_lock_irqsave(&ui->lock, flags); + req = ept->req; + ept->req = 0; + ept->last = 0; + while (req != 0) { + next = req->next; + + req->busy = 0; + req->live = 0; + req->req.status = -ENODEV; + req->req.actual = 0; + if (req->req.complete) { + spin_unlock_irqrestore(&ui->lock, flags); + req->req.complete(ept, &req->req); + spin_lock_irqsave(&ui->lock, flags); + } + if (req->dead) + do_free_req(ui, req); + req = req->next; + } + spin_unlock_irqrestore(&ui->lock, flags); +} + +static void flush_endpoint(struct usb_endpoint *ept) +{ + if (!ept->ui) + return; + + flush_endpoint_hw(ept->ui, (1 << ept->bit)); + flush_endpoint_sw(ept); +} + +static void flush_all_endpoints(struct usb_info *ui) +{ + unsigned n; + + flush_endpoint_hw(ui, 0xffffffff); + + for (n = 0; n < 32; n++) + flush_endpoint_sw(ui->ept + n); +} + +#define HW_DELAY_FOR_LPM msecs_to_jiffies(1000) +#define DELAY_FOR_USB_VBUS_STABILIZE msecs_to_jiffies(500) +static irqreturn_t usb_interrupt(int irq, void *data) +{ + struct usb_info *ui = data; + unsigned n; + unsigned speed; + + if (!ui->active) + return IRQ_HANDLED; + + if (ui->in_lpm) { + usb_lpm_exit(ui); + return IRQ_HANDLED; + } + + n = readl(USB_USBSTS); + writel(n, USB_USBSTS); + + /* somehow we got an IRQ while in the reset sequence: ignore it */ + if (ui->running == 0) { + pr_err("%s: ui->running is zero\n", __func__); + return IRQ_HANDLED; + } + + if (n & STS_PCI) { + if (!(readl(USB_PORTSC) & PORTSC_PORT_RESET)) { + speed = (readl(USB_PORTSC) & PORTSC_PORT_SPEED_MASK); + switch (speed) { + case PORTSC_PORT_SPEED_HIGH: + pr_info("hsusb resume: speed = HIGH\n"); + ui->speed = USB_SPEED_HIGH; + break; + + case PORTSC_PORT_SPEED_FULL: + pr_info("hsusb resume: speed = FULL\n"); + ui->speed = USB_SPEED_FULL; + break; + + default: + pr_err("hsusb resume: Unknown Speed\n"); + ui->speed = USB_SPEED_UNKNOWN; + break; + } + } + + /* pci interrutpt would also be generated when resuming + * from bus suspend, following check would avoid kick + * starting usb main thread in case of pci interrupts + * during enumeration + */ + if (ui->configured && ui->chg_type == USB_CHG_TYPE__SDP) { + ui->usb_state = USB_STATE_CONFIGURED; + ui->flags = USB_FLAG_RESUME; + queue_delayed_work(usb_work, &ui->work, 0); + } + } + + if (n & STS_URI) { + pr_info("hsusb reset interrupt\n"); + ui->usb_state = USB_STATE_DEFAULT; + ui->configured = 0; + schedule_work(&ui->chg_stop); + + writel(readl(USB_ENDPTSETUPSTAT), USB_ENDPTSETUPSTAT); + writel(readl(USB_ENDPTCOMPLETE), USB_ENDPTCOMPLETE); + writel(0xffffffff, USB_ENDPTFLUSH); + writel(0, USB_ENDPTCTRL(1)); + + if (ui->online != 0) { + /* marking us offline will cause ept queue attempts to fail */ + ui->online = 0; + + flush_all_endpoints(ui); + + /* XXX: we can't seem to detect going offline, so deconfigure + * XXX: on reset for the time being + */ + set_configuration(ui, 0); + } + } + + if (n & STS_SLI) { + pr_info("hsusb suspend interrupt\n"); + ui->usb_state = USB_STATE_SUSPENDED; + + /* stop usb charging */ + schedule_work(&ui->chg_stop); + } + + if (n & STS_UI) { + n = readl(USB_ENDPTSETUPSTAT); + if (n & EPT_RX(0)) + handle_setup(ui); + + n = readl(USB_ENDPTCOMPLETE); + writel(n, USB_ENDPTCOMPLETE); + while (n) { + unsigned bit = __ffs(n); + handle_endpoint(ui, bit); + n = n & (~(1 << bit)); + } + } + + n = readl(USB_OTGSC); + writel(n, USB_OTGSC); + + if (n & OTGSC_BSVIS) { + /*Verify B Session Valid Bit to verify vbus status*/ + if (B_SESSION_VALID & n) { + pr_info("usb cable connected\n"); + ui->usb_state = USB_STATE_POWERED; + ui->flags = USB_FLAG_VBUS_ONLINE; + /* Wait for 100ms to stabilize VBUS before initializing + * USB and detecting charger type + */ + queue_delayed_work(usb_work, &ui->work, 0); + } else { + int i; + + usb_disable_pullup(ui); + + printk(KERN_INFO "usb cable disconnected\n"); + ui->usb_state = USB_STATE_NOTATTACHED; + ui->configured = 0; + for (i = 0; i < ui->num_funcs; i++) { + struct usb_function_info *fi = ui->func[i]; + if (!fi || + !(ui->composition->functions & (1 << i))) + continue; + if (fi->func->disconnect) + fi->func->disconnect + (fi->func->context); + } + ui->flags = USB_FLAG_VBUS_OFFLINE; + queue_delayed_work(usb_work, &ui->work, 0); + } + } + + return IRQ_HANDLED; +} + +static void usb_prepare(struct usb_info *ui) +{ + memset(ui->buf, 0, 4096); + ui->head = (void *) (ui->buf + 0); + + /* only important for reset/reinit */ + memset(ui->ept, 0, sizeof(ui->ept)); + ui->next_item = 0; + ui->speed = USB_SPEED_UNKNOWN; + + init_endpoints(ui); + + ui->ep0in.max_pkt = 64; + ui->ep0in.ui = ui; + ui->ep0in.alloced = 1; + ui->ep0out.max_pkt = 64; + ui->ep0out.ui = ui; + ui->ep0out.alloced = 1; + + ui->setup_req = usb_ept_alloc_req(&ui->ep0in, SETUP_BUF_SIZE); + ui->ep0out_req = usb_ept_alloc_req(&ui->ep0out, ui->ep0out.max_pkt); + + INIT_WORK(&ui->chg_stop, usb_chg_stop); + INIT_WORK(&ui->li.wakeup_phy, usb_lpm_wakeup_phy); + INIT_DELAYED_WORK(&ui->work, usb_do_work); + INIT_DELAYED_WORK(&ui->chg_legacy_det, usb_chg_legacy_detect); +} + +static int usb_is_online(struct usb_info *ui) +{ + /* continue lpm if bus is suspended or disconnected or stopped*/ + if (((readl(USB_PORTSC) & PORTSC_SUSP) == PORTSC_SUSP) || + ((readl(USB_PORTSC) & PORTSC_CCS) == 0) || + ((readl(USB_USBCMD) & USBCMD_RS) == 0)) + return 0; + + pr_debug("usb is online\n"); + pr_debug("usbcmd:(%08x) usbsts:(%08x) portsc:(%08x)\n", + readl(USB_USBCMD), + readl(USB_USBSTS), + readl(USB_PORTSC)); + return -1; +} + +static int usb_wakeup_phy(struct usb_info *ui) +{ + int i; + + writel(readl(USB_USBCMD) & ~ULPI_STP_CTRL, USB_USBCMD); + + /* some circuits automatically clear PHCD bit */ + for (i = 0; i < 5 && (readl(USB_PORTSC) & PORTSC_PHCD); i++) { + writel(readl(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC); + msleep(1); + } + + if ((readl(USB_PORTSC) & PORTSC_PHCD)) { + pr_err("%s: cannot clear phcd bit\n", __func__); + return -1; + } + + return 0; +} + +static int usb_suspend_phy(struct usb_info *ui) +{ + int i; + unsigned long flags; + + if (usb_is_online(ui)) + return -1; + + /* spec talks about following bits in LPM for external phy. + * But they are ignored because + * 1. disabling interface protection circuit: by disabling + * interface protection curcuit we cannot come out + * of lpm as async interrupts would be disabled + * 2. setting the suspendM bit: this bit would be set by usb + * controller once we set phcd bit. + */ + switch (PHY_TYPE(ui->phy_info)) { + case USB_PHY_INTEGRATED: + if (!is_phy_45nm()) + ulpi_read(ui, 0x14); + + /* turn on/off otg comparators */ + if (ui->vbus_sn_notif && + ui->usb_state == USB_STATE_NOTATTACHED) + ulpi_write(ui, 0x00, 0x30); + else + ulpi_write(ui, 0x01, 0x30); + + if (!is_phy_45nm()) + ulpi_write(ui, 0x08, 0x09); + + break; + + case USB_PHY_UNDEFINED: + pr_err("%s: undefined phy type\n", __func__); + return -1; + } + + /* loop for large amount of time */ + for (i = 0; i < 500; i++) { + spin_lock_irqsave(&ui->lock, flags); + if (usb_is_online(ui)) { + spin_unlock_irqrestore(&ui->lock, flags); + return -1; + } + /* set phy to be in lpm */ + writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); + spin_unlock_irqrestore(&ui->lock, flags); + + msleep(1); + if (readl(USB_PORTSC) & PORTSC_PHCD) + goto blk_stp_sig; + } + + if (!(readl(USB_PORTSC) & PORTSC_PHCD)) { + pr_err("unable to set phcd of portsc reg\n"); + pr_err("Reset HW link and phy to recover from phcd error\n"); + usb_hw_reset(ui); + return -1; + } + + /* we have to set this bit again to work-around h/w bug */ + writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); + +blk_stp_sig: + /* block the stop signal */ + writel(readl(USB_USBCMD) | ULPI_STP_CTRL, USB_USBCMD); + + return 0; +} + +/* SW workarounds +Issue#2 - Integrated PHY Calibration +Symptom - Electrical compliance failure in eye-diagram tests +SW workaround - Try to raise amplitude to 400mV + +Issue#3 - AHB Posted Writes +Symptom - USB stability +SW workaround - This programs xtor ON, BURST disabled and + unspecified length of INCR burst enabled +*/ +static int usb_hw_reset(struct usb_info *ui) +{ + unsigned i; + struct msm_hsusb_platform_data *pdata; + unsigned long timeout; + unsigned val = 0; + + pdata = ui->pdev->dev.platform_data; + + clk_enable(ui->clk); + /* reset the phy before resetting link */ + if (readl(USB_PORTSC) & PORTSC_PHCD) + usb_wakeup_phy(ui); + /* rpc call for phy_reset */ + if (ui->pdata->phy_reset) + ui->pdata->phy_reset(ui->addr); + else + msm_hsusb_phy_reset(); + /* Give some delay to settle phy after reset */ + msleep(100); + + /* RESET */ + writel(USBCMD_RESET, USB_USBCMD); + timeout = jiffies + USB_LINK_RESET_TIMEOUT; + while (readl(USB_USBCMD) & USBCMD_RESET) { + if (time_after(jiffies, timeout)) { + dev_err(&ui->pdev->dev, "usb link reset timeout\n"); + break; + } + msleep(1); + } + + /* select DEVICE mode with SDIS active */ + writel((USBMODE_SDIS | USBMODE_DEVICE), USB_USBMODE); + msleep(1); + + /* select ULPI phy */ + i = (readl(USB_PORTSC) & ~PORTSC_PTS); + writel(i | PORTSC_PTS_ULPI, USB_PORTSC); + /* set usb controller interrupt latency to zero*/ + writel((readl(USB_USBCMD) & ~USBCMD_ITC_MASK) | USBCMD_ITC(0), + USB_USBCMD); + + /* If the target is 7x01 and roc version is > 1.2, set + * the AHB mode to 2 for maximum performance, else set + * it to 1, to bypass the AHB transactor for stability. + */ + if (PHY_TYPE(ui->phy_info) == USB_PHY_EXTERNAL) { + if (pdata->soc_version >= SOC_ROC_2_0) + writel(0x02, USB_ROC_AHB_MODE); + else + writel(0x01, USB_ROC_AHB_MODE); + } else { + unsigned cfg_val; + + /* Raise amplitude to 400mV + * SW workaround, Issue#2 + */ + cfg_val = ulpi_read(ui, ULPI_CONFIG_REG); + cfg_val |= ULPI_AMPLITUDE_MAX; + ulpi_write(ui, cfg_val, ULPI_CONFIG_REG); + + writel(0x0, USB_AHB_BURST); + writel(0x00, USB_AHB_MODE); + } + + /* TBD: do we have to add DpRise, ChargerRise and + * IdFloatRise for 45nm + */ + /* Disable VbusValid and SessionEnd comparators */ + val = ULPI_VBUS_VALID | ULPI_SESS_END; + + /* enable id interrupt only when transceiver is available */ + if (ui->xceiv) + writel(readl(USB_OTGSC) | OTGSC_BSVIE | OTGSC_IDIE, USB_OTGSC); + else { + writel((readl(USB_OTGSC) | OTGSC_BSVIE) & ~OTGSC_IDPU, + USB_OTGSC); + ulpi_write(ui, ULPI_IDPU, ULPI_OTG_CTRL_CLR); + val |= ULPI_HOST_DISCONNECT | ULPI_ID_GND; + } + ulpi_write(ui, val, ULPI_INT_RISE_CLR); + ulpi_write(ui, val, ULPI_INT_FALL_CLR); + + /* we are just setting the pointer in the hwblock. Since the + * endpoint isnt enabled the hw block doenst read the contents + * of ui->dma - so we dont need a barrier here + * */ + writel(ui->dma, USB_ENDPOINTLISTADDR); + + clk_disable(ui->clk); + + return 0; +} + +static void usb_reset(struct usb_info *ui) +{ + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + ui->running = 0; + spin_unlock_irqrestore(&ui->lock, flags); + +#if 0 + /* we should flush and shutdown cleanly if already running */ + writel(0xffffffff, USB_ENDPTFLUSH); + msleep(2); +#endif + + if (usb_hw_reset(ui)) { + pr_info("%s: h/w reset failed\n", __func__); + return; + } + + usb_configure_endpoint(&ui->ep0in, NULL); + usb_configure_endpoint(&ui->ep0out, NULL); + + /* marking us offline will cause ept queue attempts to fail */ + ui->online = 0; + + /* terminate any pending transactions */ + flush_all_endpoints(ui); + + set_configuration(ui, 0); + + spin_lock_irqsave(&ui->lock, flags); + ui->running = 1; + spin_unlock_irqrestore(&ui->lock, flags); +} + +static void usb_enable(void *handle, int enable) +{ + struct usb_info *ui = handle; + unsigned long flags; + spin_lock_irqsave(&ui->lock, flags); + + if (enable) { + ui->flags |= USB_FLAG_RESET; + ui->active = 1; + spin_unlock_irqrestore(&ui->lock, flags); + usb_do_work(&ui->work.work); + } else { + ui->active = 0; + spin_unlock_irqrestore(&ui->lock, flags); + usb_clk_disable(ui); + msm_hsusb_suspend_locks_acquire(ui, 0); + } +} + +static struct msm_otg_ops dcd_ops = { + .request = usb_enable, +}; + +void usb_start(struct usb_info *ui) +{ + int i, ret; + + for (i = 0; i < ui->num_funcs; i++) { + struct usb_function_info *fi = ui->func[i]; + if (!fi || !(ui->composition->functions & (1<enabled) { + pr_info("usb_bind_func() (%s)\n", fi->func->name); + fi->func->bind(fi->func->context); + } + } + + ui->clk_enabled = 0; + ui->vreg_enabled = 0; + + ui->xceiv = msm_otg_get_transceiver(); + if (ui->xceiv) { + ui->flags = USB_FLAG_REG_OTG; + queue_delayed_work(usb_work, &ui->work, 0); + } else { + /*Initialize pm app RPC */ + ret = msm_pm_app_rpc_init(); + if (ret) { + pr_err("%s: pm_app_rpc connect failed\n", __func__); + goto out; + } + pr_info("%s: pm_app_rpc connect success\n", __func__); + + ret = msm_pm_app_register_vbus_sn(&msm_hsusb_set_vbus_state); + if (ret) { + pr_err("%s:PMIC VBUS SN notif not supported\n", \ + __func__); + msm_pm_app_rpc_deinit(); + goto out; + } + pr_info("%s:PMIC VBUS SN notif supported\n", \ + __func__); + + ret = msm_pm_app_enable_usb_ldo(1); + if (ret) { + pr_err("%s: unable to turn on internal LDO", \ + __func__); + msm_pm_app_unregister_vbus_sn( + &msm_hsusb_set_vbus_state); + msm_pm_app_rpc_deinit(); + goto out; + } + ui->vbus_sn_notif = 1; +out: + ui->active = 1; + ui->flags |= (USB_FLAG_START | USB_FLAG_RESET); + queue_delayed_work(usb_work, &ui->work, 0); + } + +} + +static LIST_HEAD(usb_function_list); +static DEFINE_MUTEX(usb_function_list_lock); + + +static struct usb_function_info *usb_find_function(const char *name) +{ + struct list_head *entry; + list_for_each(entry, &usb_function_list) { + struct usb_function_info *fi = + list_entry(entry, struct usb_function_info, list); + if (fi) { + if (!strcmp(name, fi->func->name)) + return fi; + } + } + + return NULL; +} + +static void usb_try_to_bind(void) +{ + struct usb_info *ui = the_usb_info; + unsigned long enabled_functions = 0; + int i; + + if (!ui || ui->bound || !ui->pdev || !ui->composition) + return; + + for (i = 0; i < ui->num_funcs; i++) { + if (ui->func[i]) + enabled_functions |= (1 << i); + } + if ((enabled_functions & ui->composition->functions) + != ui->composition->functions) + return; + + usb_set_composition(ui->composition->product_id); + usb_configure_device_descriptor(ui); + + /* we have found all the needed functions */ + ui->bound = 1; + printk(KERN_INFO "msm_hsusb: functions bound. starting.\n"); + usb_start(ui); +} + +static int usb_get_function_index(const char *name) +{ + struct usb_info *ui = the_usb_info; + int i; + + for (i = 0; i < ui->num_funcs; i++) { + if (!strcmp(name, ui->functions_map[i].name)) + return i; + } + return -1; +} + +int usb_function_register(struct usb_function *driver) +{ + struct usb_info *ui = the_usb_info; + struct usb_function_info *fi; + int ret = 0; + int index; + + mutex_lock(&usb_function_list_lock); + + index = usb_get_function_index(driver->name); + if (index < 0) { + pr_err("%s: unsupported function = %s\n", + __func__, driver->name); + ret = -EINVAL; + goto fail; + } + + fi = kzalloc(sizeof(*fi), GFP_KERNEL); + if (!fi) { + ret = -ENOMEM; + goto fail; + } + + fi->func = driver; + list_add(&fi->list, &usb_function_list); + ui->func[index] = fi; + fi->func->ep0_out_req = ui->ep0out_req; + fi->func->ep0_in_req = ui->setup_req; + fi->func->ep0_out = &ui->ep0out; + fi->func->ep0_in = &ui->ep0in; + pr_info("%s: name = '%s', map = %d\n", __func__, driver->name, index); + + usb_try_to_bind(); +fail: + mutex_unlock(&usb_function_list_lock); + return ret; +} +EXPORT_SYMBOL(usb_function_register); + +static unsigned short usb_validate_product_id(unsigned short pid) +{ + struct usb_info *ui = the_usb_info; + int i; + + if (!ui || !ui->pdata) + return -1; + + /* set idProduct based on which functions are enabled */ + for (i = 0; i < ui->pdata->num_compositions; i++) { + if (ui->pdata->compositions[i].product_id == pid) + break; + } + + if (i < ui->pdata->num_compositions) { + struct usb_composition *comp = &ui->pdata->compositions[i]; + for (i = 0; i < ui->num_funcs; i++) { + if (comp->functions & (1 << i)) { + if (!ui->func[i]) { + pr_err("%s: func(%d) not available\n", + __func__, i); + return 0; + } + } + } + return comp->product_id; + } else + pr_err("%s: Product id (%x) is not supported\n", __func__, pid); + return 0; +} + +static unsigned short usb_get_product_id(unsigned long enabled_functions) +{ + struct usb_info *ui = the_usb_info; + int i; + + if (!(ui && ui->pdata)) + return -1; + + /* set idProduct based on which functions are enabled */ + for (i = 0; i < ui->pdata->num_compositions; i++) { + if (ui->pdata->compositions[i].functions == enabled_functions) + return ui->pdata->compositions[i].product_id; + } + return 0; +} + +static void usb_uninit(struct usb_info *ui) +{ + int i; + + for (i = 0; i < ui->strdesc_index; i++) + kfree(ui->strdesc[i]); + ui->strdesc_index = 1; + ui->next_ifc_num = 0; +} + +static unsigned short usb_set_composition(unsigned short pid) +{ + struct usb_info *ui = the_usb_info; + int i; + + if (!(ui && ui->pdata)) + return 0; + + /* Retrieve product id on enabled functions */ + for (i = 0; i < ui->pdata->num_compositions; i++) { + if (ui->pdata->compositions[i].product_id == pid) { + ui->composition = &ui->pdata->compositions[i]; + for (i = 0; i < ui->num_funcs; i++) { + struct usb_function_info *fi = ui->func[i]; + if (ui->func && fi && fi->func) { + fi->enabled = (ui->composition-> + functions >> i) & 1; + } + } + pr_info("%s: composition set to product id = %x\n", + __func__, ui->composition->product_id); + return ui->composition->product_id; + } + } + pr_err("%s: product id (%x) not supported\n", __func__, pid); + return 0; +} + +static void usb_switch_composition(unsigned short pid) +{ + struct usb_info *ui = the_usb_info; + int i; + unsigned long flags; + + + if (!ui->active) + return; + if (!usb_validate_product_id(pid)) + return; + + disable_irq(ui->irq); + if (cancel_delayed_work_sync(&ui->work)) + pr_info("%s: Removed work successfully\n", __func__); + if (ui->running) { + spin_lock_irqsave(&ui->lock, flags); + ui->running = 0; + ui->online = 0; + ui->bound = 0; + spin_unlock_irqrestore(&ui->lock, flags); + /* we should come out of lpm to access registers */ + if (ui->in_lpm) { + if (PHY_TYPE(ui->phy_info) == USB_PHY_EXTERNAL) { + disable_irq(ui->gpio_irq[0]); + disable_irq(ui->gpio_irq[1]); + } + + if (ui->usb_state == USB_STATE_NOTATTACHED + && ui->vbus_sn_notif) + msm_pm_app_enable_usb_ldo(1); + + usb_lpm_exit(ui); + if (cancel_work_sync(&ui->li.wakeup_phy)) + usb_lpm_wakeup_phy(NULL); + ui->in_lpm = 0; + } + /* disable usb and session valid interrupts */ + writel(0, USB_USBINTR); + writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); + + /* stop the controller */ + usb_disable_pullup(ui); + ui->usb_state = USB_STATE_NOTATTACHED; + switch_set_state(&ui->sdev, 0); + /* Before starting again, wait for 300ms + * to make sure host detects soft disconnection + **/ + msleep(300); + } + + for (i = 0; i < ui->num_funcs; i++) { + struct usb_function_info *fi = ui->func[i]; + if (!fi || !fi->func || !fi->enabled) + continue; + if (fi->func->configure) + fi->func->configure(0, fi->func->context); + if (fi->func->unbind) + fi->func->unbind(fi->func->context); + } + + usb_uninit(ui); + usb_set_composition(pid); + usb_configure_device_descriptor(ui); + + /* initialize functions */ + for (i = 0; i < ui->num_funcs; i++) { + struct usb_function_info *fi = ui->func[i]; + if (!fi || !(ui->composition->functions & (1 << i))) + continue; + if (fi->enabled) { + if (fi->func->bind) + fi->func->bind(fi->func->context); + } + } + + ui->bound = 1; + ui->flags = USB_FLAG_RESET; + queue_delayed_work(usb_work, &ui->work, 0); + enable_irq(ui->irq); +} + +void usb_function_enable(const char *function, int enable) +{ + struct usb_function_info *fi; + struct usb_info *ui = the_usb_info; + unsigned long functions_mask; + int curr_enable; + unsigned short pid; + int i; + + if (!ui) + return; + + pr_info("%s: name = %s, enable = %d\n", __func__, function, enable); + + fi = usb_find_function(function); + if (!fi) { + pr_err("%s: function (%s) not registered with DCD\n", + __func__, function); + return; + } + if (fi->enabled == enable) { + pr_err("%s: function (%s) state is same\n", + __func__, function); + return; + } + functions_mask = 0; + curr_enable = fi->enabled; + fi->enabled = enable; + for (i = 0; i < ui->num_funcs; i++) { + struct usb_function_info *fi = ui->func[i]; + if (fi && fi->enabled) + functions_mask |= (1 << i); + } + + pid = usb_get_product_id(functions_mask); + if (!pid) { + fi->enabled = curr_enable; + pr_err("%s: mask (%lx) not matching with any products\n", + __func__, functions_mask); + pr_err("%s: continuing with current composition\n", __func__); + return; + } + usb_switch_composition(pid); +} +EXPORT_SYMBOL(usb_function_enable); + +static int usb_free(struct usb_info *ui, int ret) +{ + disable_irq_wake(ui->irq); + free_irq(ui->irq, ui); + if (ui->gpio_irq[0]) + free_irq(ui->gpio_irq[0], NULL); + if (ui->gpio_irq[1]) + free_irq(ui->gpio_irq[1], NULL); + + dma_pool_destroy(ui->pool); + dma_free_coherent(&ui->pdev->dev, 4096, ui->buf, ui->dma); + kfree(ui->func); + kfree(ui->strdesc); + iounmap(ui->addr); + clk_put(ui->clk); + clk_put(ui->pclk); + clk_put(ui->cclk); + msm_hsusb_suspend_locks_init(ui, 0); + kfree(ui); + + return ret; +} + +static int usb_vbus_is_on(struct usb_info *ui) +{ + unsigned tmp; + + /* disable session valid raising and falling interrupts */ + ulpi_write(ui, ULPI_SESSION_VALID_RAISE, ULPI_USBINTR_ENABLE_RASING_C); + ulpi_write(ui, ULPI_SESSION_VALID_FALL, ULPI_USBINTR_ENABLE_FALLING_C); + + tmp = ulpi_read(ui, ULPI_USBINTR_STATUS); + + /* enable session valid raising and falling interrupts */ + ulpi_write(ui, ULPI_SESSION_VALID_RAISE, ULPI_USBINTR_ENABLE_RASING_S); + ulpi_write(ui, ULPI_SESSION_VALID_FALL, ULPI_USBINTR_ENABLE_FALLING_S); + + if (tmp & (1 << 2)) + return 1; + return 0; +} +static void usb_do_work(struct work_struct *w) +{ + struct usb_info *ui = container_of(w, struct usb_info, work.work); + unsigned long iflags; + unsigned long flags, ret; + + for (;;) { + spin_lock_irqsave(&ui->lock, iflags); + flags = ui->flags; + ui->flags = 0; + spin_unlock_irqrestore(&ui->lock, iflags); + + /* give up if we have nothing to do */ + if (flags == 0) + break; + + switch (ui->state) { + case USB_STATE_IDLE: + if (flags & USB_FLAG_REG_OTG) { + dcd_ops.handle = (void *) ui; + ret = ui->xceiv->set_peripheral(ui->xceiv, + &dcd_ops); + if (ret) + pr_err("%s: Can't register peripheral" + "driver with OTG", __func__); + break; + } + if ((flags & USB_FLAG_START) || + (flags & USB_FLAG_RESET)) { + disable_irq(ui->irq); + if (ui->vbus_sn_notif) + msm_pm_app_enable_usb_ldo(1); + usb_clk_enable(ui); + usb_vreg_enable(ui); + usb_vbus_online(ui); + + /* if VBUS is present move to ONLINE state + * otherwise move to OFFLINE state + */ + if (usb_vbus_is_on(ui)) { + ui->usb_state = USB_STATE_POWERED; + msm_hsusb_suspend_locks_acquire(ui, 1); + ui->state = USB_STATE_ONLINE; + usb_enable_pullup(ui); + schedule_delayed_work( + &ui->chg_legacy_det, + USB_CHG_DET_DELAY); + pr_info("hsusb: IDLE -> ONLINE\n"); + } else { + ui->usb_state = USB_STATE_NOTATTACHED; + ui->state = USB_STATE_OFFLINE; + + msleep(500); + usb_lpm_enter(ui); + pr_info("hsusb: IDLE -> OFFLINE\n"); + if (ui->vbus_sn_notif) + msm_pm_app_enable_usb_ldo(0); + } + enable_irq(ui->irq); + break; + } + goto reset; + + case USB_STATE_ONLINE: + /* If at any point when we were online, we received + * the signal to go offline, we must honor it + */ + if (flags & USB_FLAG_VBUS_OFFLINE) { + enum charger_type temp; + unsigned long f; + + cancel_delayed_work_sync(&ui->chg_legacy_det); + + spin_lock_irqsave(&ui->lock, f); + temp = ui->chg_type; + ui->chg_type = USB_CHG_TYPE__INVALID; + spin_unlock_irqrestore(&ui->lock, f); + + if (temp != USB_CHG_TYPE__INVALID) { + /* re-acquire wakelock and restore axi + * freq if they have been reduced by + * charger work item + */ + msm_hsusb_suspend_locks_acquire(ui, 1); + + msm_chg_usb_i_is_not_available(); + msm_chg_usb_charger_disconnected(); + } + + /* reset usb core and usb phy */ + disable_irq(ui->irq); + if (ui->in_lpm) + usb_lpm_exit(ui); + usb_vbus_offline(ui); + usb_lpm_enter(ui); + if ((ui->vbus_sn_notif) && + (ui->usb_state == USB_STATE_NOTATTACHED)) + msm_pm_app_enable_usb_ldo(0); + ui->state = USB_STATE_OFFLINE; + enable_irq(ui->irq); + switch_set_state(&ui->sdev, 0); + pr_info("hsusb: ONLINE -> OFFLINE\n"); + break; + } + if (flags & USB_FLAG_SUSPEND) { + ui->usb_state = USB_STATE_SUSPENDED; + usb_lpm_enter(ui); + msm_hsusb_suspend_locks_acquire(ui, 1); + break; + } + if ((flags & USB_FLAG_RESUME) || + (flags & USB_FLAG_CONFIGURE)) { + int maxpower = usb_get_max_power(ui); + + if (maxpower > 0) + msm_chg_usb_i_is_available(maxpower); + + if (flags & USB_FLAG_CONFIGURE) + switch_set_state(&ui->sdev, 1); + + break; + } + goto reset; + + case USB_STATE_OFFLINE: + /* If we were signaled to go online and vbus is still + * present when we received the signal, go online. + */ + if ((flags & USB_FLAG_VBUS_ONLINE)) { + msm_hsusb_suspend_locks_acquire(ui, 1); + disable_irq(ui->irq); + ui->state = USB_STATE_ONLINE; + if (ui->in_lpm) + usb_lpm_exit(ui); + usb_vbus_online(ui); + if (!(B_SESSION_VALID & readl(USB_OTGSC))) { + writel(((readl(USB_OTGSC) & + ~OTGSC_INTR_STS_MASK) | + OTGSC_BSVIS), USB_OTGSC); + enable_irq(ui->irq); + goto reset; + } + usb_enable_pullup(ui); + schedule_delayed_work( + &ui->chg_legacy_det, + USB_CHG_DET_DELAY); + pr_info("hsusb: OFFLINE -> ONLINE\n"); + enable_irq(ui->irq); + break; + } + if (flags & USB_FLAG_SUSPEND) { + usb_lpm_enter(ui); + wake_unlock(&ui->wlock); + break; + } + default: +reset: + /* For RESET or any unknown flag in a particular state + * go to IDLE state and reset HW to bring to known state + */ + ui->flags = USB_FLAG_RESET; + ui->state = USB_STATE_IDLE; + } + } +} + +void msm_hsusb_set_vbus_state(int online) +{ + struct usb_info *ui = the_usb_info; + + if (ui && online) { + msm_pm_app_enable_usb_ldo(1); + usb_lpm_exit(ui); + /* Turn on PHY comparators */ + if (!(ulpi_read(ui, 0x30) & 0x01)) + ulpi_write(ui, 0x01, 0x30); + } +} + +static irqreturn_t usb_lpm_gpio_isr(int irq, void *data) +{ + disable_irq(irq); + + return IRQ_HANDLED; +} + +static void usb_lpm_exit(struct usb_info *ui) +{ + if (ui->in_lpm == 0) + return; + + if (usb_lpm_config_gpio) + usb_lpm_config_gpio(0); + + wake_lock(&ui->wlock); + usb_clk_enable(ui); + usb_vreg_enable(ui); + + writel(readl(USB_USBCMD) & ~ASYNC_INTR_CTRL, USB_USBCMD); + writel(readl(USB_USBCMD) & ~ULPI_STP_CTRL, USB_USBCMD); + + if (readl(USB_PORTSC) & PORTSC_PHCD) { + disable_irq(ui->irq); + schedule_work(&ui->li.wakeup_phy); + } else { + ui->in_lpm = 0; + if (ui->xceiv) + ui->xceiv->set_suspend(ui->xceiv, 0); + } + pr_info("%s(): USB exited from low power mode\n", __func__); +} + +static int usb_lpm_enter(struct usb_info *ui) +{ + unsigned long flags; + unsigned connected; + + spin_lock_irqsave(&ui->lock, flags); + if (ui->in_lpm) { + spin_unlock_irqrestore(&ui->lock, flags); + pr_debug("already in lpm, nothing to do\n"); + return 0; + } + + if (usb_is_online(ui)) { + spin_unlock_irqrestore(&ui->lock, flags); + pr_info("%s: lpm procedure aborted\n", __func__); + return -1; + } + + ui->in_lpm = 1; + if (ui->xceiv) + ui->xceiv->set_suspend(ui->xceiv, 1); + disable_irq(ui->irq); + spin_unlock_irqrestore(&ui->lock, flags); + + if (usb_suspend_phy(ui)) { + ui->in_lpm = 0; + ui->flags = USB_FLAG_RESET; + enable_irq(ui->irq); + pr_err("%s: phy suspend failed, lpm procedure aborted\n", + __func__); + return -1; + } + + if ((B_SESSION_VALID & readl(USB_OTGSC)) && + (ui->usb_state == USB_STATE_NOTATTACHED)) { + ui->in_lpm = 0; + writel(((readl(USB_OTGSC) & ~OTGSC_INTR_STS_MASK) | + OTGSC_BSVIS), USB_OTGSC); + ui->flags = USB_FLAG_VBUS_ONLINE; + ui->usb_state = USB_STATE_POWERED; + usb_wakeup_phy(ui); + enable_irq(ui->irq); + return -1; + } + + /* enable async interrupt */ + writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL, USB_USBCMD); + connected = readl(USB_USBCMD) & USBCMD_RS; + + usb_vreg_disable(ui); + usb_clk_disable(ui); + + if (usb_lpm_config_gpio) { + if (usb_lpm_config_gpio(1)) { + spin_lock_irqsave(&ui->lock, flags); + usb_lpm_exit(ui); + spin_unlock_irqrestore(&ui->lock, flags); + enable_irq(ui->irq); + return -1; + } + enable_irq(ui->gpio_irq[0]); + enable_irq(ui->gpio_irq[1]); + } + + enable_irq(ui->irq); + msm_hsusb_suspend_locks_acquire(ui, 0); + pr_info("%s: usb in low power mode\n", __func__); + return 0; +} + +static void usb_enable_pullup(struct usb_info *ui) +{ + disable_irq(ui->irq); + writel(STS_URI | STS_SLI | STS_UI | STS_PCI, USB_USBINTR); + writel(readl(USB_USBCMD) | USBCMD_RS, USB_USBCMD); + enable_irq(ui->irq); +} + +/* SW workarounds +Issue #1 - USB Spoof Disconnect Failure +Symptom - Writing 0 to run/stop bit of USBCMD doesn't cause disconnect +SW workaround - Making opmode non-driving and SuspendM set in function + register of SMSC phy +*/ +static void usb_disable_pullup(struct usb_info *ui) +{ + disable_irq(ui->irq); + writel(readl(USB_USBINTR) & ~(STS_URI | STS_SLI | STS_UI | STS_PCI), + USB_USBINTR); + writel(readl(USB_USBCMD) & ~USBCMD_RS, USB_USBCMD); + + /* S/W workaround, Issue#1 */ + if (!is_phy_external() && !is_phy_45nm()) + ulpi_write(ui, 0x48, 0x04); + + enable_irq(ui->irq); +} + +static void usb_chg_stop(struct work_struct *w) +{ + struct usb_info *ui = the_usb_info; + enum charger_type temp; + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + temp = ui->chg_type; + spin_unlock_irqrestore(&ui->lock, flags); + + if (temp == USB_CHG_TYPE__SDP) + msm_chg_usb_i_is_not_available(); +} + +static void usb_vbus_online(struct usb_info *ui) +{ + if (ui->in_lpm) { + if (usb_lpm_config_gpio) + usb_lpm_config_gpio(0); + usb_vreg_enable(ui); + usb_clk_enable(ui); + usb_wakeup_phy(ui); + ui->in_lpm = 0; + } + + usb_reset(ui); +} + +static void usb_vbus_offline(struct usb_info *ui) +{ + unsigned long timeout; + unsigned val = 0; + + if (ui->online != 0) { + ui->online = 0; + flush_all_endpoints(ui); + set_configuration(ui, 0); + } + + /* reset h/w at cable disconnetion becasuse + * of h/w bugs and to flush any resource that + * h/w might be holding + */ + clk_enable(ui->clk); + + if (readl(USB_PORTSC) & PORTSC_PHCD) + usb_wakeup_phy(ui); + + if (ui->pdata->phy_reset) + ui->pdata->phy_reset(ui->addr); + else + msm_hsusb_phy_reset(); + /* Give some delay to settle phy after reset */ + msleep(100); + + writel(USBCMD_RESET, USB_USBCMD); + timeout = jiffies + USB_LINK_RESET_TIMEOUT; + while (readl(USB_USBCMD) & USBCMD_RESET) { + if (time_after(jiffies, timeout)) { + dev_err(&ui->pdev->dev, "usb link reset timeout\n"); + break; + } + msleep(1); + } + + /* Disable VbusValid and SessionEnd comparators */ + val = ULPI_VBUS_VALID | ULPI_SESS_END; + + /* enable id interrupt only when transceiver is available */ + if (ui->xceiv) + writel(readl(USB_OTGSC) | OTGSC_BSVIE | OTGSC_IDIE, USB_OTGSC); + else { + writel((readl(USB_OTGSC) | OTGSC_BSVIE) & ~OTGSC_IDPU, + USB_OTGSC); + ulpi_write(ui, ULPI_IDPU, ULPI_OTG_CTRL_CLR); + val |= ULPI_HOST_DISCONNECT | ULPI_ID_GND; + } + ulpi_write(ui, val, ULPI_INT_RISE_CLR); + ulpi_write(ui, val, ULPI_INT_FALL_CLR); + + clk_disable(ui->clk); +} + +static void usb_lpm_wakeup_phy(struct work_struct *w) +{ + struct usb_info *ui = the_usb_info; + unsigned long flags; + + if (usb_wakeup_phy(ui)) { + pr_err("fatal error: cannot bring phy out of lpm\n"); + pr_err("%s: resetting controller\n", __func__); + + spin_lock_irqsave(&ui->lock, flags); + usb_disable_pullup(ui); + ui->flags = USB_FLAG_RESET; + queue_delayed_work(usb_work, &ui->work, 0); + enable_irq(ui->irq); + spin_unlock_irqrestore(&ui->lock, flags); + return; + } + + ui->in_lpm = 0; + if (ui->xceiv) + ui->xceiv->set_suspend(ui->xceiv, 0); + enable_irq(ui->irq); +} + +void usb_function_reenumerate(void) +{ + struct usb_info *ui = the_usb_info; + + /* disable and re-enable the D+ pullup */ + pr_info("hsusb: disable pullup\n"); + usb_disable_pullup(ui); + + msleep(10); + + pr_info("hsusb: enable pullup\n"); + usb_enable_pullup(ui); +} + +#if defined(CONFIG_DEBUG_FS) +static char debug_buffer[PAGE_SIZE]; + +static ssize_t debug_read_status(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct usb_info *ui = file->private_data; + char *buf = debug_buffer; + unsigned long flags; + struct usb_endpoint *ept; + struct msm_request *req; + int n; + int i = 0; + + spin_lock_irqsave(&ui->lock, flags); + + i += scnprintf(buf + i, PAGE_SIZE - i, + "regs: setup=%08x prime=%08x stat=%08x done=%08x\n", + readl(USB_ENDPTSETUPSTAT), + readl(USB_ENDPTPRIME), + readl(USB_ENDPTSTAT), + readl(USB_ENDPTCOMPLETE)); + i += scnprintf(buf + i, PAGE_SIZE - i, + "regs: cmd=%08x sts=%08x intr=%08x port=%08x\n\n", + readl(USB_USBCMD), + readl(USB_USBSTS), + readl(USB_USBINTR), + readl(USB_PORTSC)); + + + for (n = 0; n < 32; n++) { + ept = ui->ept + n; + if (ept->max_pkt == 0) + continue; + + i += scnprintf(buf + i, PAGE_SIZE - i, + "ept%d %s cfg=%08x active=%08x next=%08x info=%08x\n", + ept->num, (ept->flags & EPT_FLAG_IN) ? "in " : "out", + ept->head->config, ept->head->active, + ept->head->next, ept->head->info); + + for (req = ept->req; req; req = req->next) + i += scnprintf(buf + i, PAGE_SIZE - i, + " req @%08x next=%08x info=%08x page0=%08x %c %c\n", + req->item_dma, req->item->next, + req->item->info, req->item->page0, + req->busy ? 'B' : ' ', + req->live ? 'L' : ' ' + ); + } + + i += scnprintf(buf + i, PAGE_SIZE - i, + "phy failure count: %d\n", ui->phy_fail_count); + + spin_unlock_irqrestore(&ui->lock, flags); + + return simple_read_from_buffer(ubuf, count, ppos, buf, i); +} + + +static ssize_t debug_write_reset(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct usb_info *ui = file->private_data; + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + ui->flags |= USB_FLAG_RESET; + queue_delayed_work(usb_work, &ui->work, 0); + spin_unlock_irqrestore(&ui->lock, flags); + + return count; +} + + +static ssize_t debug_write_cycle(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + usb_function_reenumerate(); + return count; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +const struct file_operations debug_stat_ops = { + .open = debug_open, + .read = debug_read_status, +}; + + + +const struct file_operations debug_reset_ops = { + .open = debug_open, + .write = debug_write_reset, +}; + +const struct file_operations debug_cycle_ops = { + .open = debug_open, + .write = debug_write_cycle, +}; + +static struct dentry *debugfs_dent; +static struct dentry *debugfs_status; +static struct dentry *debugfs_reset; +static struct dentry *debugfs_cycle; +static void usb_debugfs_init(struct usb_info *ui) +{ + debugfs_dent = debugfs_create_dir("usb", 0); + if (IS_ERR(debugfs_dent)) + return; + + debugfs_status = debugfs_create_file("status", 0444, + debugfs_dent, ui, &debug_stat_ops); + debugfs_reset = debugfs_create_file("reset", 0222, + debugfs_dent, ui, &debug_reset_ops); + debugfs_cycle = debugfs_create_file("cycle", 0222, + debugfs_dent, ui, &debug_cycle_ops); +} + +static void usb_debugfs_uninit(void) +{ + debugfs_remove(debugfs_status); + debugfs_remove(debugfs_reset); + debugfs_remove(debugfs_cycle); + debugfs_remove(debugfs_dent); +} + +#else +static void usb_debugfs_init(struct usb_info *ui) {} +static void usb_debugfs_uninit(void) {} +#endif + +static void usb_configure_device_descriptor(struct usb_info *ui) +{ + desc_device.idVendor = ui->pdata->vendor_id; + desc_device.idProduct = ui->composition->product_id; + desc_device.bcdDevice = ui->pdata->version; + + if (ui->pdata->serial_number) + desc_device.iSerialNumber = + usb_msm_get_next_strdesc_id(ui->pdata->serial_number); + if (ui->pdata->product_name) + desc_device.iProduct = + usb_msm_get_next_strdesc_id(ui->pdata->product_name); + if (ui->pdata->manufacturer_name) + desc_device.iManufacturer = + usb_msm_get_next_strdesc_id( + ui->pdata->manufacturer_name); + + /* Send Serial number to A9 for software download */ + if (ui->pdata->serial_number) { + msm_hsusb_is_serial_num_null(FALSE); + msm_hsusb_send_serial_number(ui->pdata->serial_number); + } else + msm_hsusb_is_serial_num_null(TRUE); + + msm_hsusb_send_productID(desc_device.idProduct); + +} +static ssize_t msm_hsusb_store_func_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + char name[20]; + int enable = 0; + int i; + + for (i = 0; buf[i] != 0; i++) { + if (buf[i] == '=') + break; + name[i] = buf[i]; + } + name[i++] = 0; + if (buf[i] == '0' || buf[i] == '1') + enable = buf[i] - '0'; + else + return size; + + pr_info("%s: name = %s, enable = %d\n", __func__, name, enable); + usb_function_enable(name, enable); + return size; +} +static ssize_t msm_hsusb_show_compswitch(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_info *ui = the_usb_info; + int i; + + if (ui->composition) + i = scnprintf(buf, PAGE_SIZE, + "composition product id = %x\n", + ui->composition->product_id); + else + i = scnprintf(buf, PAGE_SIZE, + "composition product id = 0\n"); + return i; +} + +static ssize_t msm_hsusb_store_compswitch(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long pid; + + if (!strict_strtoul(buf, 16, &pid)) { + pr_info("%s: Requested New Product id = %lx\n", __func__, pid); + usb_switch_composition((unsigned short)pid); + } else + pr_info("%s: strict_strtoul conversion failed\n", __func__); + + return size; +} +static ssize_t msm_hsusb_store_autoresume(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + usb_remote_wakeup(); + + return size; +} + +static ssize_t msm_hsusb_show_state(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_info *ui = the_usb_info; + int i; + char *state[] = {"USB_STATE_NOTATTACHED", "USB_STATE_ATTACHED", + "USB_STATE_POWERED", "USB_STATE_UNAUTHENTICATED", + "USB_STATE_RECONNECTING", "USB_STATE_DEFAULT", + "USB_STATE_ADDRESS", "USB_STATE_CONFIGURED", + "USB_STATE_SUSPENDED" + }; + + i = scnprintf(buf, PAGE_SIZE, "%s\n", state[ui->usb_state]); + return i; +} + +static ssize_t msm_hsusb_show_lpm(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_info *ui = the_usb_info; + int i; + + i = scnprintf(buf, PAGE_SIZE, "%d\n", ui->in_lpm); + return i; +} + +static ssize_t msm_hsusb_show_speed(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_info *ui = the_usb_info; + int i; + char *speed[] = {"USB_SPEED_UNKNOWN", "USB_SPEED_LOW", + "USB_SPEED_FULL", "USB_SPEED_HIGH"}; + + i = scnprintf(buf, PAGE_SIZE, "%s\n", speed[ui->speed]); + return i; +} + +static DEVICE_ATTR(composition, 0664, + msm_hsusb_show_compswitch, msm_hsusb_store_compswitch); +static DEVICE_ATTR(func_enable, S_IWUSR, + NULL, msm_hsusb_store_func_enable); +static DEVICE_ATTR(autoresume, 0222, + NULL, msm_hsusb_store_autoresume); +static DEVICE_ATTR(state, 0664, msm_hsusb_show_state, NULL); +static DEVICE_ATTR(lpm, 0664, msm_hsusb_show_lpm, NULL); +static DEVICE_ATTR(speed, 0664, msm_hsusb_show_speed, NULL); + +static struct attribute *msm_hsusb_attrs[] = { + &dev_attr_composition.attr, + &dev_attr_func_enable.attr, + &dev_attr_autoresume.attr, + &dev_attr_state.attr, + &dev_attr_lpm.attr, + &dev_attr_speed.attr, + NULL, +}; +static struct attribute_group msm_hsusb_attr_grp = { + .attrs = msm_hsusb_attrs, +}; + +#define msm_hsusb_func_attr(function, index) \ +static ssize_t show_##function(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_info *ui = the_usb_info; \ + struct usb_function_info *fi = ui->func[index]; \ + \ + return sprintf(buf, "%d", fi->enabled); \ + \ +} \ + \ +static DEVICE_ATTR(function, S_IRUGO, show_##function, NULL); + +msm_hsusb_func_attr(diag, 0); +msm_hsusb_func_attr(adb, 1); +msm_hsusb_func_attr(modem, 2); +msm_hsusb_func_attr(nmea, 3); +msm_hsusb_func_attr(mass_storage, 4); +msm_hsusb_func_attr(ethernet, 5); +msm_hsusb_func_attr(rmnet, 6); + +static struct attribute *msm_hsusb_func_attrs[] = { + &dev_attr_diag.attr, + &dev_attr_adb.attr, + &dev_attr_modem.attr, + &dev_attr_nmea.attr, + &dev_attr_mass_storage.attr, + &dev_attr_ethernet.attr, + &dev_attr_rmnet.attr, + NULL, +}; + +static struct attribute_group msm_hsusb_func_attr_grp = { + .name = "functions", + .attrs = msm_hsusb_func_attrs, +}; + +static int __init usb_probe(struct platform_device *pdev) +{ + struct resource *res; + struct usb_info *ui; + int irq; + int ulpi_irq1 = 0; + int ulpi_irq2 = 0; + int i; + int ret = 0; + + if (!pdev || !pdev->dev.platform_data) { + pr_err("%s:pdev or platform data is null\n", __func__); + return -ENODEV; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + pr_err("%s: failed to get irq num from platform_get_irq\n", + __func__); + return -ENODEV; + } + + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + pr_err("%s: failed to get mem resource\n", __func__); + return -ENODEV; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &msm_hsusb_attr_grp); + if (ret) { + pr_err("%s: unable to create sysfs group\n", __func__); + return ret; + } + + usb_work = create_singlethread_workqueue("usb_work"); + if (!usb_work) { + pr_err("%s: unable to create work queue\n", __func__); + ret = -ENOMEM; + goto free_sysfs_grp; + } + + ui = kzalloc(sizeof(struct usb_info), GFP_KERNEL); + if (!ui) { + pr_err("%s: unable to allocate memory for ui\n", __func__); + ret = -ENOMEM; + goto free_workqueue; + } + + ui->pdev = pdev; + ui->pdata = pdev->dev.platform_data; + + for (i = 0; i < ui->pdata->num_compositions; i++) + if (ui->pdata->compositions[i].product_id == pid) { + ui->composition = &ui->pdata->compositions[i]; + break; + } + if (!ui->composition) { + pr_err("%s: unable to find the composition with pid:(%d)\n", + __func__, pid); + ret = -ENODEV; + goto free_ui; + } + + ui->phy_info = ui->pdata->phy_info; + if (ui->phy_info == USB_PHY_UNDEFINED) { + pr_err("undefined phy_info: (%d)\n", ui->phy_info); + ret = -ENOMEM; + goto free_ui; + } + + /* zero is reserved for language id */ + ui->strdesc_index = 1; + ui->strdesc = kzalloc(sizeof(char *) * MAX_STRDESC_NUM, GFP_KERNEL); + if (!ui->strdesc) { + pr_err("%s: unable allocate mem for string descriptors\n", + __func__); + ret = -ENOMEM; + goto free_ui; + } + + ui->num_funcs = ui->pdata->num_functions; + ui->func = kzalloc(sizeof(struct usb_function *) * ui->num_funcs, + GFP_KERNEL); + if (!ui->func) { + pr_err("%s: unable allocate mem for functions\n", __func__); + ret = -ENOMEM; + goto free_str_desc; + } + + ret = sysfs_create_group(&pdev->dev.kobj, &msm_hsusb_func_attr_grp); + if (ret) { + pr_err("%s: unable to create functions sysfs group\n", + __func__); + goto free_func; + } + + ui->addr = ioremap(res->start, resource_size(res)); + if (!ui->addr) { + pr_err("%s: unable ioremap\n", __func__); + ret = -ENOMEM; + goto free_func_sysfs_grp; + } + + ui->buf = dma_alloc_coherent(&pdev->dev, 4096, &ui->dma, GFP_KERNEL); + if (!ui->buf) { + pr_err("%s: failed allocate dma coherent memory\n", __func__); + ret = -ENOMEM; + goto free_iounmap; + } + + ui->pool = dma_pool_create("hsusb", NULL, 32, 32, 0); + if (!ui->pool) { + pr_err("%s: unable to allocate dma pool\n", __func__); + ret = -ENOMEM; + goto free_dma_coherent; + } + + ui->clk = clk_get(&pdev->dev, "usb_hs_clk"); + if (IS_ERR(ui->clk)) { + pr_err("%s: unable get usb_hs_clk\n", __func__); + ret = PTR_ERR(ui->clk); + goto free_dma_pool; + } + + ui->pclk = clk_get(&pdev->dev, "usb_hs_pclk"); + if (IS_ERR(ui->pclk)) { + pr_err("%s: unable get usb_hs_pclk\n", __func__); + ret = PTR_ERR(ui->pclk); + goto free_hs_clk; + } + + if (ui->pdata->core_clk) { + ui->cclk = clk_get(&pdev->dev, "usb_hs_core_clk"); + if (IS_ERR(ui->cclk)) { + pr_err("%s: unable get usb_hs_core_clk\n", __func__); + ret = PTR_ERR(ui->cclk); + goto free_hs_pclk; + } + } + + if (ui->pdata->vreg5v_required) { + ui->vreg = vreg_get(NULL, "boost"); + if (IS_ERR(ui->vreg)) { + pr_err("%s: vreg get failed\n", __func__); + ui->vreg = NULL; + ret = PTR_ERR(ui->vreg); + goto free_hs_cclk; + } + } + + /* disable interrupts before requesting irq */ + usb_clk_enable(ui); + writel(0, USB_USBINTR); + writel(readl(USB_OTGSC) & ~OTGSC_INTR_MASK, USB_OTGSC); + usb_clk_disable(ui); + + ret = request_irq(irq, usb_interrupt, IRQF_SHARED, pdev->name, ui); + if (ret) { + pr_err("%s: request_irq failed\n", __func__); + goto free_vreg5v; + } + ui->irq = irq; + + if (ui->pdata->config_gpio) { + usb_lpm_config_gpio = ui->pdata->config_gpio; + + ulpi_irq1 = platform_get_irq_byname(pdev, "vbus_interrupt"); + if (ulpi_irq1 < 0) { + pr_err("%s: failed to get vbus gpio interrupt\n", + __func__); + return -ENODEV; + } + + ulpi_irq2 = platform_get_irq_byname(pdev, "id_interrupt"); + if (ulpi_irq2 < 0) { + pr_err("%s: failed to get id gpio interrupt\n", + __func__); + return -ENODEV; + } + + ret = request_irq(ulpi_irq1, + &usb_lpm_gpio_isr, + IRQF_TRIGGER_HIGH, + "vbus_interrupt", NULL); + if (ret) { + pr_err("%s: failed to request vbus interrupt:(%d)\n", + __func__, ulpi_irq1); + goto free_irq; + } + + ret = request_irq(ulpi_irq2, + &usb_lpm_gpio_isr, + IRQF_TRIGGER_RISING, + "usb_ulpi_data3", NULL); + if (ret) { + pr_err("%s: failed to request irq ulpi_data_3:(%d)\n", + __func__, ulpi_irq2); + goto free_ulpi_irq1; + } + + ui->gpio_irq[0] = ulpi_irq1; + ui->gpio_irq[1] = ulpi_irq2; + } + + ui->sdev.name = DRIVER_NAME; + ui->sdev.print_name = print_switch_name; + ui->sdev.print_state = print_switch_state; + + ret = switch_dev_register(&ui->sdev); + if (ret < 0) { + pr_err("%s(): switch_dev_register failed ret = %d\n", + __func__, ret); + goto free_ulpi_irq2; + } + + the_usb_info = ui; + ui->functions_map = ui->pdata->function_map; + ui->selfpowered = 0; + ui->remote_wakeup = 0; + ui->maxpower = 0xFA; + ui->chg_type = USB_CHG_TYPE__INVALID; + /* to allow swfi latency, driver latency + * must be above listed swfi latency + */ + ui->pdata->swfi_latency += 1; + + spin_lock_init(&ui->lock); + msm_hsusb_suspend_locks_init(ui, 1); + enable_irq_wake(irq); + + /* memory barrier initialization in non-interrupt context */ + dmb(); + + usb_debugfs_init(ui); + usb_prepare(ui); + + pr_info("%s: io=%p, irq=%d, dma=%p(%x)\n", + __func__, ui->addr, ui->irq, ui->buf, ui->dma); + return 0; + +free_ulpi_irq2: + free_irq(ulpi_irq2, NULL); +free_ulpi_irq1: + free_irq(ulpi_irq1, NULL); +free_irq: + free_irq(ui->irq, ui); +free_vreg5v: + if (ui->pdata->vreg5v_required) + vreg_put(ui->vreg); +free_hs_cclk: + clk_put(ui->cclk); +free_hs_pclk: + clk_put(ui->pclk); +free_hs_clk: + clk_put(ui->clk); +free_dma_pool: + dma_pool_destroy(ui->pool); +free_dma_coherent: + dma_free_coherent(&pdev->dev, 4096, ui->buf, ui->dma); +free_iounmap: + iounmap(ui->addr); +free_func_sysfs_grp: + sysfs_remove_group(&pdev->dev.kobj, &msm_hsusb_func_attr_grp); +free_func: + kfree(ui->func); +free_str_desc: + kfree(ui->strdesc); +free_ui: + kfree(ui); +free_workqueue: + destroy_workqueue(usb_work); +free_sysfs_grp: + sysfs_remove_group(&pdev->dev.kobj, &msm_hsusb_attr_grp); + + return ret; +} + +#ifdef CONFIG_PM +static int usb_platform_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct usb_info *ui = the_usb_info; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ui->lock, flags); + + if (!ui->active) { + spin_unlock_irqrestore(&ui->lock, flags); + pr_info("%s: peripheral mode is not active" + "nothing to be done\n", __func__); + return 0; + } + + if (ui->in_lpm) { + spin_unlock_irqrestore(&ui->lock, flags); + pr_info("%s: we are already in lpm, nothing to be done\n", + __func__); + return 0; + } + spin_unlock_irqrestore(&ui->lock, flags); + + ret = usb_lpm_enter(ui); + if (ret) + pr_err("%s: failed to enter lpm\n", __func__); + + return ret; +} +#endif + +static struct platform_driver usb_driver = { + .probe = usb_probe, +#ifdef CONFIG_PM + .suspend = usb_platform_suspend, +#endif + .driver = { .name = DRIVER_NAME, }, +}; + +static int __init usb_module_init(void) +{ + /* rpc connect for phy_reset */ + msm_hsusb_rpc_connect(); + /* rpc connect for charging */ + msm_chg_rpc_connect(); + + return platform_driver_register(&usb_driver); +} + +static void free_usb_info(void) +{ + struct usb_info *ui = the_usb_info; + unsigned long flags; + int i; + if (ui) { + INIT_LIST_HEAD(&usb_function_list); + + for (i = 0; i < ui->num_funcs; i++) + kfree(ui->func[i]); + ui->num_funcs = 0; + usb_uninit(ui); + kfree(ui->strdesc); + usb_ept_free_req(&ui->ep0in, ui->setup_req); + if (ui->ept[0].ui == ui) + flush_all_endpoints(ui); + spin_lock_irqsave(&ui->lock, flags); + usb_clk_disable(ui); + usb_vreg_disable(ui); + spin_unlock_irqrestore(&ui->lock, flags); + usb_free(ui, 0); + the_usb_info = NULL; + } +} +static void usb_exit(void) +{ + struct usb_info *ui = the_usb_info; + /* free the dev state structure */ + if (!ui) + return; + + if (ui->xceiv) { + ui->xceiv->set_peripheral(ui->xceiv, NULL); + msm_otg_put_transceiver(ui->xceiv); + } + + cancel_work_sync(&ui->li.wakeup_phy); + + destroy_workqueue(usb_work); + /* free the usb_info structure */ + free_usb_info(); + switch_dev_unregister(&ui->sdev); + sysfs_remove_group(&ui->pdev->dev.kobj, &msm_hsusb_func_attr_grp); + sysfs_remove_group(&ui->pdev->dev.kobj, &msm_hsusb_attr_grp); + usb_debugfs_uninit(); + platform_driver_unregister(&usb_driver); + msm_hsusb_rpc_close(); + msm_chg_rpc_close(); + msm_pm_app_unregister_vbus_sn(&msm_hsusb_set_vbus_state); + msm_pm_app_rpc_deinit(); +} + +static void __exit usb_module_exit(void) +{ + usb_exit(); +} + +module_param(pid, int, 0); +MODULE_PARM_DESC(pid, "Product ID of the desired composition"); + +module_init(usb_module_init); +module_exit(usb_module_exit); + +static void copy_string_descriptor(char *string, char *buffer) +{ + int length, i; + + if (string) { + length = strlen(string); + buffer[0] = 2 * length + 2; + buffer[1] = USB_DT_STRING; + for (i = 0; i < length; i++) { + buffer[2 * i + 2] = string[i]; + buffer[2 * i + 3] = 0; + } + } +} +static int get_qualifier_descriptor(struct usb_qualifier_descriptor *dq) +{ + struct usb_qualifier_descriptor *dev_qualifier = dq; + dev_qualifier->bLength = sizeof(struct usb_qualifier_descriptor), + dev_qualifier->bDescriptorType = USB_DT_DEVICE_QUALIFIER, + dev_qualifier->bcdUSB = __constant_cpu_to_le16(0x0200), + dev_qualifier->bDeviceClass = USB_CLASS_PER_INTERFACE, + dev_qualifier->bDeviceSubClass = 0; + dev_qualifier->bDeviceProtocol = 0; + dev_qualifier->bMaxPacketSize0 = 64; + dev_qualifier->bNumConfigurations = 1; + dev_qualifier->bRESERVED = 0; + return sizeof(struct usb_qualifier_descriptor); +} + +static int usb_fill_descriptors(void *ptr, + struct usb_descriptor_header **descriptors) +{ + unsigned char *buf = ptr; + struct usb_descriptor_header *item = descriptors[0]; + unsigned cnt = 0; + + while (NULL != item) { + unsigned len = item->bLength; + memcpy(buf, item, len); + buf += len; + cnt++; + item = descriptors[cnt]; + } + + return buf-(u8 *)ptr; +} + +static int usb_find_descriptor(struct usb_info *ui, struct usb_ctrlrequest *ctl, + struct usb_request *req) +{ + int i; + unsigned short id = ctl->wValue; + unsigned short type = id >> 8; + id &= 0xff; + + if ((type == USB_DT_DEVICE) && (id == 0)) { + req->length = sizeof(desc_device); + if (usb_msm_is_iad()) { + desc_device.bDeviceClass = 0xEF; + desc_device.bDeviceSubClass = 0x02; + desc_device.bDeviceProtocol = 0x01; + } + memcpy(req->buf, &desc_device, req->length); + return 0; + } + if ((type == USB_DT_DEVICE_QUALIFIER) && (id == 0)) { + struct usb_qualifier_descriptor dq; + req->length = get_qualifier_descriptor(&dq); + if (usb_msm_is_iad()) { + dq.bDeviceClass = 0xEF; + dq.bDeviceSubClass = 0x02; + dq.bDeviceProtocol = 0x01; + } + memcpy(req->buf, &dq, req->length); + return 0; + } + + if ((type == USB_DT_OTHER_SPEED_CONFIG) && (id == 0)) + goto get_config; + + if ((type == USB_DT_CONFIG) && (id == 0)) { + struct usb_config_descriptor cfg; + unsigned ifc_count = 0; + char *ptr, *start; +get_config: + ifc_count = 0; + start = req->buf; + ptr = start + USB_DT_CONFIG_SIZE; + ifc_count = ui->next_ifc_num; + + for (i = 0; i < ui->num_funcs; i++) { + struct usb_function_info *fi = ui->func[i]; + struct usb_descriptor_header **dh = NULL; + + if (!fi || !(ui->composition->functions & (1 << i))) + continue; + switch (ui->speed) { + case USB_SPEED_HIGH: + if (type == USB_DT_OTHER_SPEED_CONFIG) + dh = fi->func->fs_descriptors; + else + dh = fi->func->hs_descriptors; + break; + + case USB_SPEED_FULL: + if (type == USB_DT_OTHER_SPEED_CONFIG) + dh = fi->func->hs_descriptors; + else + dh = fi->func->fs_descriptors; + break; + + default: + printk(KERN_ERR "Unsupported speed(%x)\n", + ui->speed); + return -1; + } + ptr += usb_fill_descriptors(ptr, dh); + } + +#define USB_REMOTE_WAKEUP_SUPPORT 1 + cfg.bLength = USB_DT_CONFIG_SIZE; + if (type == USB_DT_OTHER_SPEED_CONFIG) + cfg.bDescriptorType = USB_DT_OTHER_SPEED_CONFIG; + else + cfg.bDescriptorType = USB_DT_CONFIG; + cfg.wTotalLength = ptr - start; + cfg.bNumInterfaces = ifc_count; + cfg.bConfigurationValue = 1; + cfg.iConfiguration = 0; + cfg.bmAttributes = USB_CONFIG_ATT_ONE | + ui->selfpowered << USB_CONFIG_ATT_SELFPOWER_POS | + USB_REMOTE_WAKEUP_SUPPORT << USB_CONFIG_ATT_WAKEUP_POS; + cfg.bMaxPower = ui->maxpower; + + memcpy(start, &cfg, USB_DT_CONFIG_SIZE); + + req->length = ptr - start; + return 0; + } + + if (type == USB_DT_STRING) { + char *buffer = req->buf; + + buffer[0] = 0; + if (id > ui->strdesc_index) + return -1; + if (id == STRING_LANGUAGE_ID) + memcpy(buffer, str_lang_desc, str_lang_desc[0]); + else + copy_string_descriptor(ui->strdesc[id], buffer); + + if (buffer[0]) { + req->length = buffer[0]; + return 0; + } else + return -1; + } + return -1; +} + +/*****Gadget Framework Functions***/ +struct device *usb_get_device(void) +{ + if (the_usb_info) { + if (the_usb_info->pdev) + return &(the_usb_info->pdev->dev); + } + return NULL; +} +EXPORT_SYMBOL(usb_get_device); + +int usb_ept_cancel_xfer(struct usb_endpoint *ept, struct usb_request *_req) +{ + struct usb_info *ui = the_usb_info; + struct msm_request *req = to_msm_request(_req); + struct msm_request *temp_req, *prev_req; + unsigned long flags; + + if (!(ui && req && ept->req)) + return -EINVAL; + + spin_lock_irqsave(&ui->lock, flags); + if (req->busy) { + req->req.status = 0; + req->busy = 0; + + /* See if the request is the first request in the ept queue */ + if (ept->req == req) { + /* Stop the transfer */ + do { + writel((1 << ept->bit), USB_ENDPTFLUSH); + while (readl(USB_ENDPTFLUSH) & (1 << ept->bit)) + udelay(100); + } while (readl(USB_ENDPTSTAT) & (1 << ept->bit)); + if (!req->next) + ept->last = NULL; + ept->req = req->next; + ept->head->next = req->item->next; + goto cancel_req; + } + /* Request could be in the middle of ept queue */ + prev_req = temp_req = ept->req; + do { + if (req == temp_req) { + if (req->live) { + /* Stop the transfer */ + do { + writel((1 << ept->bit), + USB_ENDPTFLUSH); + while (readl(USB_ENDPTFLUSH) & + (1 << ept->bit)) + udelay(100); + } while (readl(USB_ENDPTSTAT) & + (1 << ept->bit)); + } + prev_req->next = temp_req->next; + prev_req->item->next = temp_req->item->next; + if (!req->next) + ept->last = prev_req; + goto cancel_req; + } + prev_req = temp_req; + temp_req = temp_req->next; + } while (temp_req != NULL); + goto error; +cancel_req: + if (req->live) { + /* prepare the transaction descriptor item for the hardware */ + req->item->next = TERMINATE; + req->item->info = 0; + req->live = 0; + dma_unmap_single(NULL, req->dma, req->req.length, + (ept->flags & EPT_FLAG_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + /* Reprime the endpoint for the remaining transfers */ + if (ept->req) { + temp_req = ept->req; + while (temp_req != NULL) { + temp_req->live = 0; + temp_req = temp_req->next; + } + usb_ept_start(ept); + } + } else + dma_unmap_single(NULL, req->dma, req->req.length, + (ept->flags & EPT_FLAG_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + spin_unlock_irqrestore(&ui->lock, flags); + return 0; + } +error: + spin_unlock_irqrestore(&ui->lock, flags); + return -EINVAL; +} +EXPORT_SYMBOL(usb_ept_cancel_xfer); + +int usb_ept_set_halt(struct usb_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + int in = ept->flags & EPT_FLAG_IN; + unsigned n; + + if (ui->in_lpm) { + pr_err("%s: controller is in lpm, cannot proceed\n", __func__); + return -1; + } + + ept->ept_halted = 1; + + n = readl(USB_ENDPTCTRL(ept->num)); + + if (in) + n |= CTRL_TXS; + else + n |= CTRL_RXS; + + writel(n, USB_ENDPTCTRL(ept->num)); + + return 0; +} +EXPORT_SYMBOL(usb_ept_set_halt); + +int usb_ept_clear_halt(struct usb_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + int in = ept->flags & EPT_FLAG_IN; + unsigned n; + + if (ui->in_lpm) { + pr_err("%s: controller is in lpm, cannot proceed\n", __func__); + return -1; + } + + if (ept->ept_halted) + ept->ept_halted = 0; + + n = readl(USB_ENDPTCTRL(ept->num)); + + /*clear stall bit and set data toggle bit*/ + if (in) { + n &= (~CTRL_TXS); + n |= (CTRL_TXR); + } else { + n &= ~(CTRL_RXS); + n |= (CTRL_RXR); + } + + writel(n, USB_ENDPTCTRL(ept->num)); + + return 0; +} +EXPORT_SYMBOL(usb_ept_clear_halt); + +int usb_ept_is_stalled(struct usb_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + int in = ept->flags & EPT_FLAG_IN; + unsigned n; + + n = readl(USB_ENDPTCTRL(ept->num)); + + if (in && (n & CTRL_TXS)) + return 1; + else if (n & CTRL_RXS) + return 1; + return 0; +} + +void usb_ept_fifo_flush(struct usb_endpoint *ept) +{ + flush_endpoint(ept); +} +EXPORT_SYMBOL(usb_ept_fifo_flush); + +struct usb_function *usb_ept_get_function(struct usb_endpoint *ept) +{ + return NULL; +} +EXPORT_SYMBOL(usb_ept_get_function); + + +void usb_free_endpoint_all_req(struct usb_endpoint *ep) +{ + struct msm_request *temp; + struct msm_request *req; + if (!ep) + return; + req = ep->req; + while (req) { + temp = req->next; + req->busy = 0; + if (&req->req) + usb_ept_free_req(ep, &req->req); + req = temp; + } +} +EXPORT_SYMBOL(usb_free_endpoint_all_req); + +int usb_function_unregister(struct usb_function *func) +{ + struct usb_info *ui = the_usb_info; + int i; + struct usb_function_info *fi; + unsigned long flags; + + if (!func) + return -EINVAL; + + fi = usb_find_function(func->name); + if (!fi) + return -EINVAL; + + if (ui->running) { + disable_irq(ui->irq); + spin_lock_irqsave(&ui->lock, flags); + ui->running = 0; + ui->online = 0; + ui->bound = 0; + spin_unlock_irqrestore(&ui->lock, flags); + usb_uninit(ui); + /* we should come out of lpm to access registers */ + if (ui->in_lpm) { + if (PHY_TYPE(ui->phy_info) == USB_PHY_EXTERNAL) { + disable_irq(ui->gpio_irq[0]); + disable_irq(ui->gpio_irq[1]); + } + usb_lpm_exit(ui); + if (cancel_work_sync(&ui->li.wakeup_phy)) + usb_lpm_wakeup_phy(NULL); + ui->in_lpm = 0; + } + /* disable usb and session valid interrupts */ + writel(0, USB_USBINTR); + writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); + + /* stop the controller */ + usb_disable_pullup(ui); + msleep(100); + enable_irq(ui->irq); + } + + pr_info("%s: func->name = %s\n", __func__, func->name); + + ui->composition = NULL; + + if (func->configure) + func->configure(0, func->context); + if (func->unbind) + func->unbind(func->context); + + list_del(&fi->list); + for (i = 0; i < ui->num_funcs; i++) + if (fi == ui->func[i]) + ui->func[i] = NULL; + kfree(fi); + return 0; +} +EXPORT_SYMBOL(usb_function_unregister); + +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/function/msm_hsusb_hw.h b/drivers/usb/function/msm_hsusb_hw.h new file mode 100644 index 00000000000..c016c3fa276 --- /dev/null +++ b/drivers/usb/function/msm_hsusb_hw.h @@ -0,0 +1,163 @@ +/* drivers/usb/function/msm_hsusb_hw.h + * + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * 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 _USB_FUNCTION_MSM_HSUSB_HW_H +#define _USB_FUNCTION_MSM_HSUSB_HW_H + +#define USB_ID (MSM_USB_BASE + 0x0000) +#define USB_HWGENERAL (MSM_USB_BASE + 0x0004) +#define USB_HWHOST (MSM_USB_BASE + 0x0008) +#define USB_HWDEVICE (MSM_USB_BASE + 0x000C) +#define USB_HWTXBUF (MSM_USB_BASE + 0x0010) +#define USB_HWRXBUF (MSM_USB_BASE + 0x0014) +#define USB_SBUSCFG (MSM_USB_BASE + 0x0090) + +#define USB_CAPLENGTH (MSM_USB_BASE + 0x0100) /* 8 bit */ +#define USB_HCIVERSION (MSM_USB_BASE + 0x0102) /* 16 bit */ +#define USB_HCSPARAMS (MSM_USB_BASE + 0x0104) +#define USB_HCCPARAMS (MSM_USB_BASE + 0x0108) +#define USB_DCIVERSION (MSM_USB_BASE + 0x0120) /* 16 bit */ +#define USB_USBCMD (MSM_USB_BASE + 0x0140) +#define USB_USBSTS (MSM_USB_BASE + 0x0144) +#define USB_USBINTR (MSM_USB_BASE + 0x0148) +#define USB_FRINDEX (MSM_USB_BASE + 0x014C) +#define USB_DEVICEADDR (MSM_USB_BASE + 0x0154) +#define USB_ENDPOINTLISTADDR (MSM_USB_BASE + 0x0158) +#define USB_BURSTSIZE (MSM_USB_BASE + 0x0160) +#define USB_TXFILLTUNING (MSM_USB_BASE + 0x0164) +#define USB_ULPI_VIEWPORT (MSM_USB_BASE + 0x0170) +#define USB_ENDPTNAK (MSM_USB_BASE + 0x0178) +#define USB_ENDPTNAKEN (MSM_USB_BASE + 0x017C) +#define USB_PORTSC (MSM_USB_BASE + 0x0184) +#define USB_OTGSC (MSM_USB_BASE + 0x01A4) +#define USB_USBMODE (MSM_USB_BASE + 0x01A8) +#define USB_ENDPTSETUPSTAT (MSM_USB_BASE + 0x01AC) +#define USB_ENDPTPRIME (MSM_USB_BASE + 0x01B0) +#define USB_ENDPTFLUSH (MSM_USB_BASE + 0x01B4) +#define USB_ENDPTSTAT (MSM_USB_BASE + 0x01B8) +#define USB_ENDPTCOMPLETE (MSM_USB_BASE + 0x01BC) +#define USB_ENDPTCTRL(n) (MSM_USB_BASE + 0x01C0 + (4 * (n))) + + +#define USBCMD_RESET 2 +#define USBCMD_ATTACH 1 +#define USBCMD_ATDTW (1 << 14) + +#define USBMODE_DEVICE 2 +#define USBMODE_HOST 3 + +struct ept_queue_head +{ + unsigned config; + unsigned active; /* read-only */ + + unsigned next; + unsigned info; + unsigned page0; + unsigned page1; + unsigned page2; + unsigned page3; + unsigned page4; + unsigned reserved_0; + + unsigned char setup_data[8]; + + unsigned reserved_1; + unsigned reserved_2; + unsigned reserved_3; + unsigned reserved_4; +}; + +#define CONFIG_MAX_PKT(n) ((n) << 16) +#define CONFIG_ZLT (1 << 29) /* stop on zero-len xfer */ +#define CONFIG_IOS (1 << 15) /* IRQ on setup */ + +struct ept_queue_item +{ + unsigned next; + unsigned info; + unsigned page0; + unsigned page1; + unsigned page2; + unsigned page3; + unsigned page4; + unsigned reserved; +}; + +#define TERMINATE 1 + +#define INFO_BYTES(n) ((n) << 16) +#define INFO_IOC (1 << 15) +#define INFO_ACTIVE (1 << 7) +#define INFO_HALTED (1 << 6) +#define INFO_BUFFER_ERROR (1 << 5) +#define INFO_TXN_ERROR (1 << 3) + + +#define STS_NAKI (1 << 16) /* */ +#define STS_SLI (1 << 8) /* R/WC - suspend state entered */ +#define STS_SRI (1 << 7) /* R/WC - SOF recv'd */ +#define STS_URI (1 << 6) /* R/WC - RESET recv'd - write to clear */ +#define STS_FRI (1 << 3) /* R/WC - Frame List Rollover */ +#define STS_PCI (1 << 2) /* R/WC - Port Change Detect */ +#define STS_UEI (1 << 1) /* R/WC - USB Error */ +#define STS_UI (1 << 0) /* R/WC - USB Transaction Complete */ + + +/* bits used in all the endpoint status registers */ +#define EPT_TX(n) (1 << ((n) + 16)) +#define EPT_RX(n) (1 << (n)) + + +#define CTRL_TXE (1 << 23) +#define CTRL_TXR (1 << 22) +#define CTRL_TXI (1 << 21) +#define CTRL_TXD (1 << 17) +#define CTRL_TXS (1 << 16) +#define CTRL_RXE (1 << 7) +#define CTRL_RXR (1 << 6) +#define CTRL_RXI (1 << 5) +#define CTRL_RXD (1 << 1) +#define CTRL_RXS (1 << 0) + +#define CTRL_TXT_MASK (3 << 18) +#define CTRL_TXT_CTRL (0 << 18) +#define CTRL_TXT_ISOCH (1 << 18) +#define CTRL_TXT_BULK (2 << 18) +#define CTRL_TXT_INT (3 << 18) + +#define CTRL_RXT_MASK (3 << 2) +#define CTRL_RXT_CTRL (0 << 2) +#define CTRL_RXT_ISOCH (1 << 2) +#define CTRL_RXT_BULK (2 << 2) +#define CTRL_RXT_INT (3 << 2) + +#define ULPI_WAKEUP (1 << 31) +#define ULPI_RUN (1 << 30) +#define ULPI_WRITE (1 << 29) +#define ULPI_READ (0 << 29) +#define ULPI_STATE_NORMAL (1 << 27) +#define ULPI_ADDR(n) (((n) & 255) << 16) +#define ULPI_DATA(n) ((n) & 255) +#define ULPI_DATA_READ(n) (((n) >> 8) & 255) + +/* USB_PORTSC bits for determining port speed */ +#define PORTSC_PSPD_FS (0 << 26) +#define PORTSC_PSPD_LS (1 << 26) +#define PORTSC_PSPD_HS (2 << 26) +#define PORTSC_PSPD_MASK (3 << 26) + +#endif diff --git a/drivers/usb/function/msm_otg.c b/drivers/usb/function/msm_otg.c new file mode 100644 index 00000000000..c931290a419 --- /dev/null +++ b/drivers/usb/function/msm_otg.c @@ -0,0 +1,368 @@ +/* drivers/usb/otg/msm_otg.c + * + * OTG Driver for HighSpeed USB + * + * Copyright (c) 2009, Code Aurora Forum. 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MSM_USB_BASE (xceiv->regs) + +#define A_HOST 0 +#define B_DEVICE 1 +#define A_TO_B 0 +#define B_TO_A 1 + +static struct msm_otg_transceiver *xceiv; + +struct msm_otg_transceiver *msm_otg_get_transceiver(void) +{ + if (xceiv) + get_device(xceiv->dev); + return xceiv; +} +EXPORT_SYMBOL(msm_otg_get_transceiver); + +void msm_otg_put_transceiver(struct msm_otg_transceiver *xceiv) +{ + if (xceiv) + put_device(xceiv->dev); +} +EXPORT_SYMBOL(msm_otg_put_transceiver); + +static void msm_otg_set_clk(int on) +{ + if (on) { + clk_enable(xceiv->clk); + clk_enable(xceiv->pclk); + } else { + clk_disable(xceiv->clk); + clk_disable(xceiv->pclk); + } +} + +static inline int is_host(void) +{ + int ret; + + ret = (OTGSC_ID & readl(USB_OTGSC)) ? 0 : 1; + return ret; +} + +static void msm_otg_enable(void) +{ + msm_otg_set_clk(1); + /* Enable ID interrupts */ + writel(readl(USB_OTGSC) | OTGSC_IDIE, USB_OTGSC); + + if (is_host()) { + pr_info("%s: configuring USB in host mode\n", __func__); + xceiv->hcd_ops->request(xceiv->hcd_ops->handle, REQUEST_START); + xceiv->state = A_HOST; + } else { + pr_info("%s: configuring USB in device mode\n", __func__); + xceiv->dcd_ops->request(xceiv->dcd_ops->handle, REQUEST_START); + xceiv->state = B_DEVICE; + } + msm_otg_set_clk(0); + xceiv->active = 1; + wake_lock_timeout(&xceiv->wlock, HZ/2); + enable_irq(xceiv->irq); +} + +static void msm_otg_disable(int mode) +{ + unsigned long flags; + + spin_lock_irqsave(&xceiv->lock, flags); + xceiv->active = 0; + spin_unlock_irqrestore(&xceiv->lock, flags); + + pr_info("%s: OTG is disabled\n", __func__); + + if (mode != xceiv->state) + return; + switch (mode) { + case A_HOST: + if (xceiv->state == A_HOST) { + pr_info("%s: configuring USB in device mode\n", + __func__); + xceiv->dcd_ops->request(xceiv->dcd_ops->handle, + REQUEST_START); + xceiv->state = B_DEVICE; + } + break; + case B_DEVICE: + if (xceiv->state == B_DEVICE) { + pr_info("%s: configuring USB in host mode\n", + __func__); + xceiv->hcd_ops->request(xceiv->hcd_ops->handle, + REQUEST_START); + xceiv->state = A_HOST; + } + break; + } + +} + +static void msm_otg_do_work(struct work_struct *w) +{ + switch (xceiv->state) { + case A_HOST: + if (xceiv->flags == A_TO_B) { + xceiv->hcd_ops->request(xceiv->hcd_ops->handle, + REQUEST_STOP); + pr_info("%s: configuring USB in device mode\n", + __func__); + xceiv->dcd_ops->request(xceiv->dcd_ops->handle, + REQUEST_START); + xceiv->state = B_DEVICE; + } + break; + case B_DEVICE: + if (xceiv->flags == B_TO_A) { + xceiv->dcd_ops->request(xceiv->dcd_ops->handle, + REQUEST_STOP); + pr_info("%s: configuring USB in host mode\n", + __func__); + xceiv->hcd_ops->request(xceiv->hcd_ops->handle, + REQUEST_START); + xceiv->state = A_HOST; + } + break; + } + wake_lock_timeout(&xceiv->wlock, HZ/2); + enable_irq(xceiv->irq); +} + +static irqreturn_t msm_otg_irq(int irq, void *data) +{ + u32 otgsc; + u32 temp; + + if (!xceiv->active) + return IRQ_HANDLED; + + if (xceiv->in_lpm) + return IRQ_HANDLED; + + otgsc = readl(USB_OTGSC); + temp = otgsc & ~OTGSC_INTR_STS_MASK; + if (otgsc & OTGSC_IDIS) { + wake_lock(&xceiv->wlock); + if (is_host()) { + xceiv->flags = B_TO_A; + schedule_work(&xceiv->work); + } else { + xceiv->flags = A_TO_B; + schedule_work(&xceiv->work); + } + disable_irq(xceiv->irq); + writel(temp | OTGSC_IDIS, USB_OTGSC); + } + + return IRQ_HANDLED; + +} + +static DEFINE_MUTEX(otg_register_lock); + +static int msm_otg_set_peripheral(struct msm_otg_transceiver *xceiv, + struct msm_otg_ops *ops) +{ + int ret = 0; + + mutex_lock(&otg_register_lock); + if (!xceiv) { + ret = -EINVAL; + goto unlock; + } + if (!ops) { + xceiv->dcd_ops = NULL; + pr_info("%s: Peripheral driver is deregistered with OTG\n", + __func__); + msm_otg_disable(B_DEVICE); + goto unlock; + } + if (xceiv->dcd_ops) { + ret = -EBUSY; + goto unlock; + } + + xceiv->dcd_ops = ops; + xceiv->dcd_ops->request(xceiv->dcd_ops->handle, REQUEST_STOP); + if (xceiv->hcd_ops) + msm_otg_enable(); +unlock: + mutex_unlock(&otg_register_lock); + return ret; +} + +static int msm_otg_set_host(struct msm_otg_transceiver *xceiv, + struct msm_otg_ops *hcd_ops) +{ + int ret = 0; + + mutex_lock(&otg_register_lock); + if (!xceiv) { + ret = -EINVAL; + goto unlock; + } + if (!hcd_ops) { + xceiv->hcd_ops = NULL; + pr_info("%s: Host driver is deregistered with OTG\n", + __func__); + msm_otg_disable(A_HOST); + goto unlock; + } + if (xceiv->hcd_ops) { + ret = -EBUSY; + goto unlock; + } + + xceiv->hcd_ops = hcd_ops; + xceiv->hcd_ops->request(xceiv->hcd_ops->handle, REQUEST_STOP); + if (xceiv->dcd_ops) + msm_otg_enable(); + +unlock: + mutex_unlock(&otg_register_lock); + return ret; +} + +static int msm_otg_set_suspend(struct msm_otg_transceiver *otg, int suspend) +{ + unsigned long flags; + + spin_lock_irqsave(&xceiv->lock, flags); + xceiv->in_lpm = suspend; + spin_unlock_irqrestore(&xceiv->lock, flags); + return 0; +} + +static int __init msm_otg_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + xceiv = kzalloc(sizeof(struct msm_otg_transceiver), GFP_KERNEL); + if (!xceiv) + return -ENOMEM; + + xceiv->clk = clk_get(NULL, "usb_hs_clk"); + if (IS_ERR(xceiv->clk)) { + ret = PTR_ERR(xceiv->clk); + goto free_xceiv; + } + xceiv->pclk = clk_get(NULL, "usb_hs_pclk"); + if (IS_ERR(xceiv->clk)) { + ret = PTR_ERR(xceiv->pclk); + goto put_clk; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto put_pclk; + } + + xceiv->regs = ioremap(res->start, resource_size(res)); + if (!xceiv->regs) { + ret = -ENOMEM; + goto put_pclk; + } + xceiv->irq = platform_get_irq(pdev, 0); + if (!xceiv->irq) { + ret = -ENODEV; + goto free_regs; + } + + /* disable interrupts before requesting irq */ + msm_otg_set_clk(1); + writel(0, USB_USBINTR); + writel(readl(USB_OTGSC) & ~OTGSC_INTR_MASK, USB_OTGSC); + msm_otg_set_clk(0); + + ret = request_irq(xceiv->irq, msm_otg_irq, IRQF_SHARED, + "msm_otg", pdev); + if (ret) + goto free_regs; + disable_irq(xceiv->irq); + + INIT_WORK(&xceiv->work, msm_otg_do_work); + spin_lock_init(&xceiv->lock); + wake_lock_init(&xceiv->wlock, WAKE_LOCK_SUSPEND, "usb_otg"); + wake_lock(&xceiv->wlock); + + xceiv->set_host = msm_otg_set_host; + xceiv->set_peripheral = msm_otg_set_peripheral; + xceiv->set_suspend = msm_otg_set_suspend; + + return 0; +free_regs: + iounmap(xceiv->regs); +put_pclk: + clk_put(xceiv->pclk); +put_clk: + clk_put(xceiv->clk); +free_xceiv: + kfree(xceiv); + return ret; + +} + +static int __exit msm_otg_remove(struct platform_device *pdev) +{ + cancel_work_sync(&xceiv->work); + free_irq(xceiv->irq, pdev); + iounmap(xceiv->regs); + clk_put(xceiv->pclk); + clk_put(xceiv->clk); + kfree(xceiv); + return 0; +} + +static struct platform_driver msm_otg_driver = { + .remove = __exit_p(msm_otg_remove), + .driver = { + .name = "msm_hsusb_otg", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_otg_init(void) +{ + return platform_driver_probe(&msm_otg_driver, msm_otg_probe); +} + +static void __exit msm_otg_exit(void) +{ + platform_driver_unregister(&msm_otg_driver); +} + +subsys_initcall(msm_otg_init); +module_exit(msm_otg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM USB OTG driver"); +MODULE_VERSION("1.00"); diff --git a/drivers/usb/function/null.c b/drivers/usb/function/null.c new file mode 100644 index 00000000000..68f1e350d74 --- /dev/null +++ b/drivers/usb/function/null.c @@ -0,0 +1,118 @@ +/* driver/usb/function/null.c + * + * Null Function Device - A Data Sink + * + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include "usb_function.h" + +struct null_context +{ + struct usb_endpoint *out; + struct usb_request *req0; + struct usb_request *req1; +}; + +static struct null_context _context; + +static void null_bind(struct usb_endpoint **ept, void *_ctxt) +{ + struct null_context *ctxt = _ctxt; + ctxt->out = ept[0]; + printk(KERN_INFO "null_bind() %p\n", ctxt->out); + + ctxt->req0 = usb_ept_alloc_req(ctxt->out, 4096); + ctxt->req1 = usb_ept_alloc_req(ctxt->out, 4096); +} + +static void null_unbind(void *_ctxt) +{ + struct null_context *ctxt = _ctxt; + printk(KERN_INFO "null_unbind()\n"); + if (ctxt->req0) { + usb_ept_free_req(ctxt->out, ctxt->req0); + ctxt->req0 = 0; + } + if (ctxt->req1) { + usb_ept_free_req(ctxt->out, ctxt->req1); + ctxt->req1 = 0; + } + ctxt->out = 0; +} + + +static void null_queue_out(struct null_context *ctxt, struct usb_request *req); + +static void null_out_complete(struct usb_endpoint *ept, struct usb_request *req) +{ + struct null_context *ctxt = req->context; + unsigned char *data = req->buf; + + if (req->status != -ENODEV) + null_queue_out(ctxt, req); +} + +static void null_queue_out(struct null_context *ctxt, struct usb_request *req) +{ + req->complete = null_out_complete; + req->context = ctxt; + req->length = 4096; + + usb_ept_queue_xfer(ctxt->out, req); +} + +static void null_configure(int configured, void *_ctxt) +{ + struct null_context *ctxt = _ctxt; + printk(KERN_INFO "null_configure() %d\n", configured); + + if (configured) { + null_queue_out(ctxt, ctxt->req0); + null_queue_out(ctxt, ctxt->req1); + } else { + /* all pending requests will be canceled */ + } +} + +static struct usb_function usb_func_null = { + .bind = null_bind, + .unbind = null_unbind, + .configure = null_configure, + + .name = "null", + .context = &_context, + + .ifc_class = 0xff, + .ifc_subclass = 0xfe, + .ifc_protocol = 0x01, + + .ifc_name = "null", + + .ifc_ept_count = 1, + .ifc_ept_type = { EPT_BULK_OUT }, +}; + +static int __init null_init(void) +{ + printk(KERN_INFO "null_init()\n"); + usb_function_register(&usb_func_null); + return 0; +} + +module_init(null_init); diff --git a/drivers/usb/function/rmnet.c b/drivers/usb/function/rmnet.c new file mode 100644 index 00000000000..e618ec06b42 --- /dev/null +++ b/drivers/usb/function/rmnet.c @@ -0,0 +1,1086 @@ +/* + * rmnet.c -- RmNet function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 Nokia Corporation + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "usb_function.h" + +static char *rmnet_ctl_ch = CONFIG_RMNET_SMD_CTL_CHANNEL; +module_param(rmnet_ctl_ch, charp, S_IRUGO); +MODULE_PARM_DESC(rmnet_ctl_ch, "RmNet control SMD channel"); + +static char *rmnet_data_ch = CONFIG_RMNET_SMD_DATA_CHANNEL; +module_param(rmnet_data_ch, charp, S_IRUGO); +MODULE_PARM_DESC(rmnet_data_ch, "RmNet data SMD channel"); + +#define RMNET_NOTIFY_INTERVAL 5 +#define RMNET_MAX_NOTIFY_SIZE sizeof(struct usb_cdc_notification) + +#define QMI_REQ_MAX 4 +#define QMI_REQ_SIZE 2048 +#define QMI_RESP_MAX 8 +#define QMI_RESP_SIZE 2048 + +#define RX_REQ_MAX 8 +#define RX_REQ_SIZE 2048 +#define TX_REQ_MAX 8 +#define TX_REQ_SIZE 2048 + +#define TXN_MAX 2048 + +static struct usb_interface_descriptor rmnet_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, + /* .iInterface = DYNAMIC */ +}; + +/* Full speed support */ +static struct usb_endpoint_descriptor rmnet_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_MAX_NOTIFY_SIZE), + .bInterval = 1 << RMNET_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor rmnet_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor rmnet_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_MAX_NOTIFY_SIZE), + .bInterval = RMNET_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor rmnet_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor rmnet_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +/* QMI requests & responses buffer*/ +struct qmi_buf { + void *buf; + int len; + struct list_head list; +}; + +/* Control & data SMD channel private data */ +struct rmnet_smd_info { + struct smd_channel *ch; + struct tasklet_struct tx_tlet; + struct tasklet_struct rx_tlet; +#define CH_OPENED 0 + unsigned long flags; + /* pending rx packet length */ + atomic_t rx_pkt; + /* wait for smd open event*/ + wait_queue_head_t wait; +}; + +struct rmnet_dev { + struct usb_endpoint *epout; + struct usb_endpoint *epin; + struct usb_endpoint *epnotify; + struct usb_request *notify_req; + + u8 ifc_id; + /* QMI lists */ + struct list_head qmi_req_pool; + struct list_head qmi_resp_pool; + struct list_head qmi_req_q; + struct list_head qmi_resp_q; + /* Tx/Rx lists */ + struct list_head tx_idle; + struct list_head rx_idle; + struct list_head rx_queue; + + spinlock_t lock; + atomic_t online; + atomic_t notify_count; + + struct rmnet_smd_info smd_ctl; + struct rmnet_smd_info smd_data; + + struct workqueue_struct *wq; + struct work_struct connect_work; + struct work_struct disconnect_work; +}; + +static struct usb_function rmnet_function; + +struct qmi_buf * +rmnet_alloc_qmi(unsigned len, gfp_t kmalloc_flags) +{ + struct qmi_buf *qmi; + + qmi = kmalloc(sizeof(struct qmi_buf), kmalloc_flags); + if (qmi != NULL) { + qmi->buf = kmalloc(len, kmalloc_flags); + if (qmi->buf == NULL) { + kfree(qmi); + qmi = NULL; + } + } + + return qmi ? qmi : ERR_PTR(-ENOMEM); +} + +void rmnet_free_qmi(struct qmi_buf *qmi) +{ + kfree(qmi->buf); + kfree(qmi); +} +/* + * Allocate a usb_request and its buffer. Returns a pointer to the + * usb_request or NULL if there is an error. + */ +struct usb_request * +rmnet_alloc_req(struct usb_endpoint *ep, unsigned len, gfp_t kmalloc_flags) +{ + struct usb_request *req; + + req = usb_ept_alloc_req(ep, 0); + + if (req != NULL) { + req->length = len; + req->buf = kmalloc(len, kmalloc_flags); + if (req->buf == NULL) { + usb_ept_free_req(ep, req); + req = NULL; + } + } + + return req ? req : ERR_PTR(-ENOMEM); +} + +/* + * Free a usb_request and its buffer. + */ +void rmnet_free_req(struct usb_endpoint *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ept_free_req(ep, req); +} + +static void rmnet_notify_complete(struct usb_endpoint *ep, + struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + int status = req->status; + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + case -ENODEV: + /* connection gone */ + atomic_set(&dev->notify_count, 0); + break; + default: + pr_err("%s: rmnet notify ep error %d\n", __func__, status); + /* FALLTHROUGH */ + case 0: + if (ep != dev->epnotify) + break; + + /* handle multiple pending QMI_RESPONSE_AVAILABLE + * notifications by resending until we're done + */ + if (atomic_dec_and_test(&dev->notify_count)) + break; + + status = usb_ept_queue_xfer(dev->epnotify, dev->notify_req); + if (status) { + atomic_dec(&dev->notify_count); + pr_err("%s: rmnet notify ep enqueue error %d\n", + __func__, status); + } + break; + } +} + +static void qmi_response_available(struct rmnet_dev *dev) +{ + struct usb_request *req = dev->notify_req; + struct usb_cdc_notification *event = req->buf; + int status; + + /* Response will be sent later */ + if (atomic_inc_return(&dev->notify_count) != 1) + return; + + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; + event->wValue = cpu_to_le16(0); + event->wIndex = cpu_to_le16(dev->ifc_id); + event->wLength = cpu_to_le16(0); + + status = usb_ept_queue_xfer(dev->epnotify, dev->notify_req); + if (status < 0) { + atomic_dec(&dev->notify_count); + pr_err("%s: rmnet notify ep enqueue error %d\n", + __func__, status); + } +} + +/* TODO + * handle modem restart events + */ +static void rmnet_smd_notify(void *priv, unsigned event) +{ + struct rmnet_smd_info *smd_info = priv; + int len = atomic_read(&smd_info->rx_pkt); + + switch (event) { + case SMD_EVENT_DATA: { + + if (len && (smd_write_avail(smd_info->ch) >= len)) + tasklet_schedule(&smd_info->rx_tlet); + + if (smd_read_avail(smd_info->ch)) + tasklet_schedule(&smd_info->tx_tlet); + + break; + } + case SMD_EVENT_OPEN: + /* usb endpoints are not enabled untill smd channels + * are opened. wake up worker thread to continue + * connection processing + */ + set_bit(CH_OPENED, &smd_info->flags); + wake_up(&smd_info->wait); + break; + case SMD_EVENT_CLOSE: + /* We will never come here. + * reset flags after closing smd channel + * */ + clear_bit(CH_OPENED, &smd_info->flags); + break; + } +} + +static void rmnet_control_tx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct qmi_buf *qmi_resp; + int sz; + unsigned long flags; + + while (1) { + sz = smd_cur_packet_size(dev->smd_ctl.ch); + if (sz == 0) + break; + if (smd_read_avail(dev->smd_ctl.ch) < sz) + break; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(&dev->qmi_resp_pool)) { + pr_err("%s: rmnet QMI Tx buffers full\n", __func__); + spin_unlock_irqrestore(&dev->lock, flags); + break; + } + qmi_resp = list_first_entry(&dev->qmi_resp_pool, + struct qmi_buf, list); + list_del(&qmi_resp->list); + spin_unlock_irqrestore(&dev->lock, flags); + + qmi_resp->len = smd_read(dev->smd_ctl.ch, qmi_resp->buf, sz); + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&qmi_resp->list, &dev->qmi_resp_q); + spin_unlock_irqrestore(&dev->lock, flags); + + qmi_response_available(dev); + } + +} + +static void rmnet_control_rx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct qmi_buf *qmi_req; + int ret; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + while (1) { + + if (list_empty(&dev->qmi_req_q)) { + atomic_set(&dev->smd_ctl.rx_pkt, 0); + break; + } + qmi_req = list_first_entry(&dev->qmi_req_q, + struct qmi_buf, list); + if (smd_write_avail(dev->smd_ctl.ch) < qmi_req->len) { + atomic_set(&dev->smd_ctl.rx_pkt, qmi_req->len); + pr_debug("%s: rmnet control smd channel full\n", + __func__); + break; + } + + list_del(&qmi_req->list); + spin_unlock_irqrestore(&dev->lock, flags); + ret = smd_write(dev->smd_ctl.ch, qmi_req->buf, qmi_req->len); + spin_lock_irqsave(&dev->lock, flags); + if (ret != qmi_req->len) { + pr_err("%s: rmnet control smd write failed\n", + __func__); + break; + } + + list_add_tail(&qmi_req->list, &dev->qmi_req_pool); + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_command_complete(struct usb_endpoint *ep, + struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_function *func = &rmnet_function; + struct usb_request *in_req; + struct qmi_buf *qmi_req; + int ret; + + if (req->status < 0) { + pr_err("%s: rmnet command error %d\n", __func__, req->status); + return; + } + + spin_lock(&dev->lock); + /* no pending control rx packet */ + if (!atomic_read(&dev->smd_ctl.rx_pkt)) { + if (smd_write_avail(dev->smd_ctl.ch) < req->actual) { + atomic_set(&dev->smd_ctl.rx_pkt, req->actual); + goto queue_req; + } + spin_unlock(&dev->lock); + ret = smd_write(dev->smd_ctl.ch, req->buf, req->actual); + /* This should never happen */ + if (ret != req->actual) + pr_err("%s: rmnet control smd write failed\n", + __func__); + goto ep0_ack; + } +queue_req: + if (list_empty(&dev->qmi_req_pool)) { + spin_unlock(&dev->lock); + pr_err("%s: rmnet QMI pool is empty\n", __func__); + return; + } + + qmi_req = list_first_entry(&dev->qmi_req_pool, struct qmi_buf, list); + list_del(&qmi_req->list); + spin_unlock(&dev->lock); + memcpy(qmi_req->buf, req->buf, req->actual); + qmi_req->len = req->actual; + spin_lock(&dev->lock); + list_add_tail(&qmi_req->list, &dev->qmi_req_q); + spin_unlock(&dev->lock); +ep0_ack: + /* Send ACK on EP0 IN */ + in_req = func->ep0_in_req; + in_req->length = 0; + in_req->complete = 0; + usb_ept_queue_xfer(func->ep0_in, in_req); +} + +static int rmnet_setup(struct usb_ctrlrequest *ctrl, void *buf, + int len, void *context) +{ + struct rmnet_dev *dev = context; + struct usb_request *req = rmnet_function.ep0_out_req; + int ret = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + struct qmi_buf *resp; + int schedule = 0; + + if (!atomic_read(&dev->online)) + return -ENOTCONN; + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (w_value || w_index != dev->ifc_id) + goto invalid; + ret = w_length; + req->complete = rmnet_command_complete; + req->context = dev; + break; + + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (w_value || w_index != dev->ifc_id) + goto invalid; + else { + spin_lock(&dev->lock); + resp = list_first_entry(&dev->qmi_resp_q, + struct qmi_buf, list); + list_del(&resp->list); + spin_unlock(&dev->lock); + memcpy(buf, resp->buf, resp->len); + ret = resp->len; + spin_lock(&dev->lock); + + if (list_empty(&dev->qmi_resp_pool)) + schedule = 1; + list_add_tail(&resp->list, &dev->qmi_resp_pool); + + if (schedule) + tasklet_schedule(&dev->smd_ctl.tx_tlet); + spin_unlock(&dev->lock); + } + break; + default: + +invalid: + pr_debug("%s: invalid control req%02x.%02x v%04x i%04x l%d\n", + __func__, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + return ret; +} + +static void rmnet_start_rx(struct rmnet_dev *dev) +{ + int status; + struct usb_request *req; + struct list_head *act, *tmp; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_for_each_safe(act, tmp, &dev->rx_idle) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + + spin_unlock_irqrestore(&dev->lock, flags); + status = usb_ept_queue_xfer(dev->epout, req); + spin_lock_irqsave(&dev->lock, flags); + + if (status) { + pr_err("%s: rmnet data rx enqueue err %d\n", + __func__, status); + list_add_tail(&req->list, &dev->rx_idle); + break; + } + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_data_tx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct usb_request *req; + int status; + int sz; + unsigned long flags; + + while (1) { + + sz = smd_cur_packet_size(dev->smd_data.ch); + if (sz == 0) + break; + if (smd_read_avail(dev->smd_data.ch) < sz) + break; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(&dev->tx_idle)) { + spin_unlock_irqrestore(&dev->lock, flags); + pr_debug("%s: rmnet data Tx buffers full\n", __func__); + break; + } + req = list_first_entry(&dev->tx_idle, struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&dev->lock, flags); + + req->length = smd_read(dev->smd_data.ch, req->buf, sz); + status = usb_ept_queue_xfer(dev->epin, req); + if (status) { + pr_err("%s: rmnet tx data enqueue err %d\n", + __func__, status); + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, &dev->tx_idle); + spin_unlock_irqrestore(&dev->lock, flags); + break; + } + } + +} + +static void rmnet_data_rx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct usb_request *req; + int ret; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + while (1) { + if (list_empty(&dev->rx_queue)) { + atomic_set(&dev->smd_data.rx_pkt, 0); + break; + } + req = list_first_entry(&dev->rx_queue, + struct usb_request, list); + if (smd_write_avail(dev->smd_data.ch) < req->actual) { + atomic_set(&dev->smd_data.rx_pkt, req->actual); + pr_debug("%s: rmnet SMD data channel full\n", __func__); + break; + } + + list_del(&req->list); + spin_unlock_irqrestore(&dev->lock, flags); + ret = smd_write(dev->smd_data.ch, req->buf, req->actual); + spin_lock_irqsave(&dev->lock, flags); + if (ret != req->actual) { + pr_err("%s: rmnet SMD data write failed\n", __func__); + break; + } + list_add_tail(&req->list, &dev->rx_idle); + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* We have free rx data requests. */ + rmnet_start_rx(dev); +} + +/* If SMD has enough room to accommodate a data rx packet, + * write into SMD directly. Otherwise enqueue to rx_queue. + * We will not write into SMD directly untill rx_queue is + * empty to strictly follow the ordering requests. + */ +static void rmnet_complete_epout(struct usb_endpoint *ep, + struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + int status = req->status; + int ret; + + switch (status) { + case 0: + /* normal completion */ + break; + case -ECONNRESET: + case -ESHUTDOWN: + case -ENODEV: + /* connection gone */ + spin_lock(&dev->lock); + list_add_tail(&req->list, &dev->rx_idle); + spin_unlock(&dev->lock); + return; + default: + /* unexpected failure */ + pr_err("%s: response error %d, %d/%d\n", + __func__, status, req->actual, + req->length); + spin_lock(&dev->lock); + list_add_tail(&req->list, &dev->rx_idle); + spin_unlock(&dev->lock); + return; + } + + spin_lock(&dev->lock); + if (!atomic_read(&dev->smd_data.rx_pkt)) { + if (smd_write_avail(dev->smd_data.ch) < req->actual) { + atomic_set(&dev->smd_data.rx_pkt, req->actual); + goto queue_req; + } + spin_unlock(&dev->lock); + ret = smd_write(dev->smd_data.ch, req->buf, req->actual); + /* This should never happen */ + if (ret != req->actual) + pr_err("%s: rmnet data smd write failed\n", __func__); + /* Restart Rx */ + spin_lock(&dev->lock); + list_add_tail(&req->list, &dev->rx_idle); + spin_unlock(&dev->lock); + rmnet_start_rx(dev); + return; + } +queue_req: + list_add_tail(&req->list, &dev->rx_queue); + spin_unlock(&dev->lock); +} + +static void rmnet_complete_epin(struct usb_endpoint *ep, + struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + int status = req->status; + int schedule = 0; + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + case -ENODEV: + /* connection gone */ + spin_lock(&dev->lock); + list_add_tail(&req->list, &dev->tx_idle); + spin_unlock(&dev->lock); + break; + default: + pr_err("%s: rmnet data tx ep error %d\n", __func__, status); + /* FALLTHROUGH */ + case 0: + spin_lock(&dev->lock); + if (list_empty(&dev->tx_idle)) + schedule = 1; + list_add_tail(&req->list, &dev->tx_idle); + + if (schedule) + tasklet_schedule(&dev->smd_data.tx_tlet); + spin_unlock(&dev->lock); + break; + } + +} + +static void rmnet_disconnect_work(struct work_struct *w) +{ + struct qmi_buf *qmi; + struct usb_request *req; + struct list_head *act, *tmp; + struct rmnet_dev *dev = container_of(w, struct rmnet_dev, + disconnect_work); + + atomic_set(&dev->notify_count, 0); + + tasklet_kill(&dev->smd_ctl.rx_tlet); + tasklet_kill(&dev->smd_ctl.tx_tlet); + tasklet_kill(&dev->smd_data.rx_tlet); + tasklet_kill(&dev->smd_data.rx_tlet); + + list_for_each_safe(act, tmp, &dev->rx_queue) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + list_add_tail(&req->list, &dev->rx_idle); + } + + list_for_each_safe(act, tmp, &dev->qmi_req_q) { + qmi = list_entry(act, struct qmi_buf, list); + list_del(&qmi->list); + list_add_tail(&qmi->list, &dev->qmi_req_pool); + } + + list_for_each_safe(act, tmp, &dev->qmi_resp_q) { + qmi = list_entry(act, struct qmi_buf, list); + list_del(&qmi->list); + list_add_tail(&qmi->list, &dev->qmi_resp_pool); + } + + smd_close(dev->smd_ctl.ch); + dev->smd_ctl.flags = 0; + + smd_close(dev->smd_data.ch); + dev->smd_data.flags = 0; +} + +static void rmnet_connect_work(struct work_struct *w) +{ + struct rmnet_dev *dev = container_of(w, struct rmnet_dev, connect_work); + int ret; + + /* Control channel for QMI messages */ + ret = smd_open(rmnet_ctl_ch, &dev->smd_ctl.ch, + &dev->smd_ctl, rmnet_smd_notify); + if (ret) { + pr_err("%s: Unable to open control smd channel\n", __func__); + return; + } + wait_event(dev->smd_ctl.wait, test_bit(CH_OPENED, + &dev->smd_ctl.flags)); + + /* Data channel for network packets */ + ret = smd_open(rmnet_data_ch, &dev->smd_data.ch, + &dev->smd_data, rmnet_smd_notify); + if (ret) { + pr_err("%s: Unable to open data smd channel\n", __func__); + smd_close(dev->smd_ctl.ch); + } + wait_event(dev->smd_data.wait, test_bit(CH_OPENED, + &dev->smd_data.flags)); + + if (usb_msm_get_speed() == USB_SPEED_HIGH) { + usb_configure_endpoint(dev->epin, &rmnet_hs_in_desc); + usb_configure_endpoint(dev->epout, &rmnet_hs_out_desc); + usb_configure_endpoint(dev->epnotify, &rmnet_hs_notify_desc); + } else { + usb_configure_endpoint(dev->epin, &rmnet_fs_in_desc); + usb_configure_endpoint(dev->epout, &rmnet_fs_out_desc); + usb_configure_endpoint(dev->epnotify, &rmnet_fs_notify_desc); + } + + usb_ept_enable(dev->epin, 1); + usb_ept_enable(dev->epout, 1); + usb_ept_enable(dev->epnotify, 1); + + atomic_set(&dev->online, 1); + /* Queue Rx data requests */ + rmnet_start_rx(dev); +} + +static void rmnet_configure(int configured, void *context) + +{ + struct rmnet_dev *dev = context; + + if (configured) { + queue_work(dev->wq, &dev->connect_work); + } else { + /* all pending requests will be canceled */ + if (!atomic_read(&dev->online)) + return; + + atomic_set(&dev->online, 0); + + usb_ept_fifo_flush(dev->epnotify); + usb_ept_enable(dev->epnotify, 0); + + usb_ept_fifo_flush(dev->epout); + usb_ept_enable(dev->epout, 0); + + usb_ept_fifo_flush(dev->epin); + usb_ept_enable(dev->epin, 0); + + /* cleanup work */ + queue_work(dev->wq, &dev->disconnect_work); + } + +} + +static void rmnet_free_buf(struct rmnet_dev *dev) +{ + struct qmi_buf *qmi; + struct usb_request *req; + struct list_head *act, *tmp; + + /* free all usb requests in tx pool */ + list_for_each_safe(act, tmp, &dev->tx_idle) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epout, req); + } + + /* free all usb requests in rx pool */ + list_for_each_safe(act, tmp, &dev->rx_idle) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epin, req); + } + + /* free all buffers in qmi request pool */ + list_for_each_safe(act, tmp, &dev->qmi_req_pool) { + qmi = list_entry(act, struct qmi_buf, list); + list_del(&qmi->list); + rmnet_free_qmi(qmi); + } + + /* free all buffers in qmi request pool */ + list_for_each_safe(act, tmp, &dev->qmi_resp_pool) { + qmi = list_entry(act, struct qmi_buf, list); + list_del(&qmi->list); + rmnet_free_qmi(qmi); + } + + rmnet_free_req(dev->epnotify, dev->notify_req); +} + +static void rmnet_bind(void *context) +{ + struct rmnet_dev *dev = context; + int i, ret; + struct usb_request *req; + struct qmi_buf *qmi; + + dev->ifc_id = usb_msm_get_next_ifc_number(&rmnet_function); + rmnet_interface_desc.bInterfaceNumber = dev->ifc_id; + + /*Configuring IN Endpoint*/ + dev->epin = usb_alloc_endpoint(USB_DIR_IN); + if (!dev->epin) + return; + + rmnet_hs_in_desc.bEndpointAddress = USB_DIR_IN | + dev->epin->num; + rmnet_fs_in_desc.bEndpointAddress = USB_DIR_IN | + dev->epin->num; + + /*Configuring OUT Endpoint*/ + dev->epout = usb_alloc_endpoint(USB_DIR_OUT); + if (!dev->epout) + goto free_epin; + + rmnet_hs_out_desc.bEndpointAddress = USB_DIR_OUT | + dev->epout->num; + rmnet_fs_out_desc.bEndpointAddress = USB_DIR_OUT | + dev->epout->num; + + /*Configuring NOTIFY Endpoint*/ + dev->epnotify = usb_alloc_endpoint(USB_DIR_IN); + if (!dev->epnotify) + goto free_epout; + + rmnet_hs_notify_desc.bEndpointAddress = USB_DIR_IN | + dev->epnotify->num; + rmnet_fs_notify_desc.bEndpointAddress = USB_DIR_IN | + dev->epnotify->num; + + dev->notify_req = usb_ept_alloc_req(dev->epnotify, 0); + if (!dev->notify_req) + goto free_epnotify; + + dev->notify_req->buf = kmalloc(RMNET_MAX_NOTIFY_SIZE, GFP_KERNEL); + if (!dev->notify_req->buf) + goto free_buf;; + + dev->notify_req->complete = rmnet_notify_complete; + dev->notify_req->context = dev; + dev->notify_req->length = RMNET_MAX_NOTIFY_SIZE; + + /* Allocate the qmi request and response buffers */ + for (i = 0; i < QMI_REQ_MAX; i++) { + qmi = rmnet_alloc_qmi(QMI_REQ_SIZE, GFP_KERNEL); + if (IS_ERR(qmi)) { + ret = PTR_ERR(qmi); + goto free_buf; + } + list_add_tail(&qmi->list, &dev->qmi_req_pool); + } + + for (i = 0; i < QMI_RESP_MAX; i++) { + qmi = rmnet_alloc_qmi(QMI_RESP_SIZE, GFP_KERNEL); + if (IS_ERR(qmi)) { + ret = PTR_ERR(qmi); + goto free_buf; + } + list_add_tail(&qmi->list, &dev->qmi_resp_pool); + } + + /* Allocate bulk in/out requests for data transfer */ + for (i = 0; i < RX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epout, RX_REQ_SIZE, GFP_KERNEL); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + goto free_buf; + } + req->length = TXN_MAX; + req->context = dev; + req->complete = rmnet_complete_epout; + list_add_tail(&req->list, &dev->rx_idle); + } + + for (i = 0; i < TX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epout, TX_REQ_SIZE, GFP_KERNEL); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + goto free_buf; + } + req->context = dev; + req->complete = rmnet_complete_epin; + list_add_tail(&req->list, &dev->tx_idle); + } + + + pr_info("Rmnet function bind completed\n"); + + return; + +free_buf: + rmnet_free_buf(dev); +free_epnotify: + usb_free_endpoint(dev->epnotify); +free_epout: + usb_free_endpoint(dev->epout); +free_epin: + usb_free_endpoint(dev->epin); + +} + +static void rmnet_unbind(void *context) +{ + struct rmnet_dev *dev = context; + + tasklet_kill(&dev->smd_ctl.rx_tlet); + tasklet_kill(&dev->smd_ctl.tx_tlet); + tasklet_kill(&dev->smd_data.rx_tlet); + tasklet_kill(&dev->smd_data.rx_tlet); + flush_workqueue(dev->wq); + + rmnet_free_buf(dev); + usb_free_endpoint(dev->epin); + usb_free_endpoint(dev->epout); + usb_free_endpoint(dev->epnotify); + + kfree(dev); + +} +static struct usb_function rmnet_function = { + .bind = rmnet_bind, + .configure = rmnet_configure, + .unbind = rmnet_unbind, + .setup = rmnet_setup, + .name = "rmnet", +}; + +struct usb_descriptor_header *rmnet_hs_descriptors[5]; +struct usb_descriptor_header *rmnet_fs_descriptors[5]; +static int __init rmnet_init(void) +{ + struct rmnet_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->wq = create_singlethread_workqueue("k_rmnet_work"); + if (!dev->wq) { + ret = -ENOMEM; + goto free_dev; + } + + spin_lock_init(&dev->lock); + atomic_set(&dev->notify_count, 0); + atomic_set(&dev->online, 0); + atomic_set(&dev->smd_ctl.rx_pkt, 0); + atomic_set(&dev->smd_data.rx_pkt, 0); + + INIT_WORK(&dev->connect_work, rmnet_connect_work); + INIT_WORK(&dev->disconnect_work, rmnet_disconnect_work); + + tasklet_init(&dev->smd_ctl.rx_tlet, rmnet_control_rx_tlet, + (unsigned long) dev); + tasklet_init(&dev->smd_ctl.tx_tlet, rmnet_control_tx_tlet, + (unsigned long) dev); + tasklet_init(&dev->smd_data.rx_tlet, rmnet_data_rx_tlet, + (unsigned long) dev); + tasklet_init(&dev->smd_data.tx_tlet, rmnet_data_tx_tlet, + (unsigned long) dev); + + init_waitqueue_head(&dev->smd_ctl.wait); + init_waitqueue_head(&dev->smd_data.wait); + + INIT_LIST_HEAD(&dev->qmi_req_pool); + INIT_LIST_HEAD(&dev->qmi_req_q); + INIT_LIST_HEAD(&dev->qmi_resp_pool); + INIT_LIST_HEAD(&dev->qmi_resp_q); + INIT_LIST_HEAD(&dev->rx_idle); + INIT_LIST_HEAD(&dev->rx_queue); + INIT_LIST_HEAD(&dev->tx_idle); + + rmnet_hs_descriptors[0] = + (struct usb_descriptor_header *)&rmnet_interface_desc; + rmnet_hs_descriptors[1] = + (struct usb_descriptor_header *)&rmnet_hs_in_desc; + rmnet_hs_descriptors[2] = + (struct usb_descriptor_header *)&rmnet_hs_out_desc; + rmnet_hs_descriptors[3] = + (struct usb_descriptor_header *)&rmnet_hs_notify_desc; + rmnet_hs_descriptors[4] = NULL; + + rmnet_fs_descriptors[0] = + (struct usb_descriptor_header *)&rmnet_interface_desc; + rmnet_fs_descriptors[1] = + (struct usb_descriptor_header *)&rmnet_fs_in_desc; + rmnet_fs_descriptors[2] = + (struct usb_descriptor_header *)&rmnet_fs_out_desc; + rmnet_fs_descriptors[3] = + (struct usb_descriptor_header *)&rmnet_fs_notify_desc; + rmnet_fs_descriptors[4] = NULL; + + rmnet_function.hs_descriptors = rmnet_hs_descriptors; + rmnet_function.fs_descriptors = rmnet_fs_descriptors; + rmnet_function.context = dev; + + ret = usb_function_register(&rmnet_function); + if (ret) + goto free_wq; + + return 0; + +free_wq: + destroy_workqueue(dev->wq); +free_dev: + kfree(dev); + + return ret; +} + +static void __exit rmnet_exit(void) +{ + usb_function_unregister(&rmnet_function); +} + +module_init(rmnet_init); +module_exit(rmnet_exit); +MODULE_DESCRIPTION("RmNet usb function driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/function/serial.c b/drivers/usb/function/serial.c new file mode 100644 index 00000000000..05393518b2c --- /dev/null +++ b/drivers/usb/function/serial.c @@ -0,0 +1,2252 @@ +/* + * serial.c -- USB Serial Function driver + * + * Copyright 2003 (C) Al Borchers (alborchers@steinerpoint.com) + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * This code is based in part on the Gadget Zero driver, which + * is Copyright (C) 2003 by David Brownell, all rights reserved. + * + * This code also borrows from usbserial.c, which is + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2000 Peter Berger (pberger@brimson.com) + * Copyright (C) 2000 Al Borchers (alborchers@steinerpoint.com) + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "usb_function.h" + +#include +/* Defines */ + +#define GS_VERSION_STR "v2.2" +#define GS_VERSION_NUM 0x0202 + +#define GS_LONG_NAME "Serial Function" +#define GS_SHORT_NAME "serial" + +static int instances = 2; +#define MAX_INSTANCES 2 + +#define GS_MAJOR 127 +#define GS_MINOR_START 0 + +#define GS_NUM_PORTS 16 + +#define GS_NO_CONFIG_ID 0 +#define GS_ACM_CONFIG_ID 2 + +#define GS_MAX_DESC_LEN 256 + +/* defines for maintaining serial states */ +#define MSR_CTS (1 << 4) +#define MSR_DSR (1 << 5) +#define MSR_RI (1 << 6) +#define MSR_CD (1 << 7) +#define MCR_DTR (1 << 0) +#define MCR_RTS (1 << 1) +#define MCR_LOOP (1 << 4) + +/* USB CDC control line state defines */ +#define USB_CDC_SET_CONTROL_LINE_STATE_DTR 0x1 +#define USB_CDC_SET_CONTROL_LINE_STATE_RTS 0x2 + +#define GS_DEFAULT_READ_Q_SIZE 16 +#define GS_DEFAULT_WRITE_Q_SIZE 16 +#define GS_DEFAULT_INT_REQ 1 + +#define GS_DEFAULT_WRITE_BUF_SIZE 8192 +#define GS_TMP_BUF_SIZE 8192 + +#define GS_CLOSE_TIMEOUT 15 + +#define GS_DEFAULT_USE_ACM 0 + +#define GS_DEFAULT_DTE_RATE 9600 +#define GS_DEFAULT_DATA_BITS 8 +#define GS_DEFAULT_PARITY USB_CDC_NO_PARITY +#define GS_DEFAULT_CHAR_FORMAT USB_CDC_1_STOP_BITS + +/* #define GS_DEBUG */ + +/* debug settings */ +#ifdef GS_DEBUG +static int debug = 1; + +#define gs_debug(format, arg...) \ + do { if (debug) printk(KERN_DEBUG format, ## arg); } while (0) +#define gs_debug_level(level, format, arg...) \ + do { if (debug >= level) printk(KERN_DEBUG format, ## arg); } while (0) + +#else + +#define gs_debug(format, arg...) \ + do { } while (0) +#define gs_debug_level(level, format, arg...) \ + do { } while (0) + +#endif /* GS_DEBUG */ + +#define GS_LOG2_NOTIFY_INTERVAL 5 /* 1 << 5 == 32 msec */ +#define GS_NOTIFY_MAXPACKET 8 +#define SERIAL_CONFIGURED 1 +#define SERIAL_UNCONFIGURED 0 + +/* Structures */ + +struct gs_dev; + +/* circular buffer */ +struct gs_buf { + unsigned int buf_size; + char *buf_buf; + char *buf_get; + char *buf_put; +}; + +/* list of requests */ +struct gs_req_entry { + struct list_head re_entry; + struct usb_request *re_req; +}; + +/* the port structure holds info for each port, one for each minor number */ +struct gs_port { + struct gs_dev *port_dev; /* pointer to device struct */ + struct tty_struct *port_tty; /* pointer to tty struct */ + spinlock_t port_lock; + struct mutex mutex_lock; /* protect open/close */ + int port_num; + int port_open_count; + int port_in_use; /* open/close in progress */ + wait_queue_head_t port_write_wait; /* waiting to write */ + struct gs_buf *port_write_buf; + struct usb_cdc_line_coding port_line_coding; + struct list_head read_pool; + struct list_head read_queue; + struct list_head write_pool; + unsigned n_read; + unsigned int msr; + unsigned int prev_msr; + unsigned int mcr; + struct work_struct push_work; +}; + +/*-------------------------------------------------------------*/ +/*Allocate DMA buffer in non interrupt context(gs_bind)*/ + +struct gs_reqbuf { + void *buf; +}; + +/*-------------------------------------------------------------*/ + +/* the device structure holds info for the USB device */ +struct gs_dev { + /* lock for set/reset config */ + spinlock_t dev_lock; + /* configuration number */ + int dev_config; + /* address of notify endpoint */ + struct usb_endpoint *dev_notify_ep; + /* address of in endpoint */ + struct usb_endpoint *dev_in_ep; + struct usb_request *notify_req; + unsigned long notify_queued; + /* address of out endpoint */ + struct usb_endpoint *dev_out_ep; + /* list of write requests */ + struct list_head dev_req_list; + /* round robin port scheduled */ + int dev_sched_port; + struct gs_port *dev_port[GS_NUM_PORTS]; /* the ports */ + struct gs_reqbuf statusreqbuf; + u16 interface_num; + + /*interface, endpoint descriptors*/ + struct usb_interface_descriptor gs_ifc_desc; + struct usb_endpoint_descriptor gs_hs_bulkin_desc, gs_fs_bulkin_desc; + struct usb_endpoint_descriptor gs_hs_bulkout_desc, gs_fs_bulkout_desc; + struct usb_endpoint_descriptor gs_hs_notifyin_desc, gs_fs_notifyin_desc; + struct usb_descriptor_header **gs_fullspeed_header; + struct usb_descriptor_header **gs_highspeed_header; + + struct usb_function *func; + int configured; + int bound; +}; + +/* Functions */ + +/* module */ +static int __init gs_module_init(void); +static void __exit gs_module_exit(void); + +static void send_notify_data(struct usb_endpoint *ep, struct usb_request *req); +/* tty driver */ +static int gs_open(struct tty_struct *tty, struct file *file); +static void gs_close(struct tty_struct *tty, struct file *file); +static int gs_write(struct tty_struct *tty, + const unsigned char *buf, int count); +static int gs_put_char(struct tty_struct *tty, unsigned char ch); +static void gs_flush_chars(struct tty_struct *tty); +static int gs_write_room(struct tty_struct *tty); +static int gs_chars_in_buffer(struct tty_struct *tty); +static void gs_throttle(struct tty_struct *tty); +static void gs_unthrottle(struct tty_struct *tty); +static int gs_break(struct tty_struct *tty, int break_state); +static int gs_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg); +static void gs_set_termios(struct tty_struct *tty, struct ktermios *old); +static unsigned gs_start_rx(struct gs_dev *dev); + +static int gs_send(struct gs_dev *dev); +static int gs_send_packet(struct gs_dev *dev, char *packet, unsigned int size); +static void gs_read_complete(struct usb_endpoint *ep, struct usb_request *req); +static void gs_write_complete(struct usb_endpoint *ep, struct usb_request *req); +static int gs_tiocmget(struct tty_struct *tty, struct file *file); +static int gs_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear); + +/* Function driver */ +static void gs_bind(void *); +static void gs_unbind(void *); +static int gs_setup(struct usb_ctrlrequest *req, + void *buf, int len, void *_ctxt); + +static void gs_configure(int config, void *_ctxt); +static void gs_disconnect(void *_ctxt); +static void gs_reset_config(struct gs_dev *dev); + +static struct usb_request *gs_alloc_req(struct usb_endpoint *ep, + unsigned int len); +static void gs_free_req(struct usb_endpoint *ep, struct usb_request *req); + +static int gs_alloc_ports(struct gs_dev *dev, gfp_t kmalloc_flags); +static void gs_free_ports(struct gs_dev *dev); + +/* circular buffer */ +static struct gs_buf *gs_buf_alloc(unsigned int size, gfp_t kmalloc_flags); +static void gs_buf_free(struct gs_buf *gb); +static void gs_buf_clear(struct gs_buf *gb); +static unsigned int gs_buf_data_avail(struct gs_buf *gb); +static unsigned int gs_buf_space_avail(struct gs_buf *gb); +static unsigned int gs_buf_put(struct gs_buf *gb, const char *buf, + unsigned int count); +static unsigned int gs_buf_get(struct gs_buf *gb, char *buf, + unsigned int count); + +/* Globals */ +static struct gs_dev **gs_devices; + +static struct semaphore gs_open_close_sem[GS_NUM_PORTS]; + +static unsigned int read_q_size = GS_DEFAULT_READ_Q_SIZE; +static unsigned int write_q_size = GS_DEFAULT_WRITE_Q_SIZE; + +static unsigned int write_buf_size = GS_DEFAULT_WRITE_BUF_SIZE; + +static struct workqueue_struct *gs_tty_wq; + + +/* tty driver struct */ +static const struct tty_operations gs_tty_ops = { + .open = gs_open, + .close = gs_close, + .write = gs_write, + .put_char = gs_put_char, + .flush_chars = gs_flush_chars, + .write_room = gs_write_room, + .ioctl = gs_ioctl, + .set_termios = gs_set_termios, + .throttle = gs_throttle, + .unthrottle = gs_unthrottle, + .break_ctl = gs_break, + .chars_in_buffer = gs_chars_in_buffer, + .tiocmget = gs_tiocmget, + .tiocmset = gs_tiocmset, +}; +static struct tty_driver *gs_tty_driver; + +/* Function driver struct */ +static struct usb_function usb_function_serial[2]; + +struct usb_function *global_func_serial; +struct gs_dev **dum_device; + +/* Module */ +MODULE_DESCRIPTION(GS_LONG_NAME); +MODULE_AUTHOR("Al Borchers"); +MODULE_LICENSE("GPL"); + +#ifdef GS_DEBUG +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Enable debugging, 0=off, 1=on"); +#endif + +module_param(read_q_size, uint, S_IRUGO); +MODULE_PARM_DESC(read_q_size, "Read request queue size, default=32"); + +module_param(write_q_size, uint, S_IRUGO); +MODULE_PARM_DESC(write_q_size, "Write request queue size, default=32"); + +module_param(write_buf_size, uint, S_IRUGO); +MODULE_PARM_DESC(write_buf_size, "Write buffer size, default=8192"); + +module_param(instances, int, 0); +MODULE_PARM_DESC(instances, "Number of serial instances"); + +module_init(gs_module_init); +module_exit(gs_module_exit); + +/******************************************************************************/ + +/* + * CDC-ACM Class specific Descriptors + */ + +static const struct usb_cdc_header_desc gs_header_desc = { + .bLength = sizeof(gs_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = __constant_cpu_to_le16(0x0110), +}; + +static const struct usb_cdc_call_mgmt_descriptor gs_call_mgmt_descriptor = { + .bLength = sizeof(gs_call_mgmt_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + .bmCapabilities = 0, + .bDataInterface = 0, +}; + +static struct usb_cdc_acm_descriptor gs_acm_descriptor = { + .bLength = sizeof(gs_acm_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = 3, /* bits should be 00000011 (refer to 5.2.3.3) */ +}; + +static const struct usb_cdc_union_desc gs_union_desc = { + .bLength = sizeof(gs_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + .bMasterInterface0 = 0, + .bSlaveInterface0 = 0, +}; + +static void gs_init_ifc_desc(struct usb_interface_descriptor *ifc_desc) +{ + ifc_desc->bLength = USB_DT_INTERFACE_SIZE; + ifc_desc->bDescriptorType = USB_DT_INTERFACE; + ifc_desc->bNumEndpoints = 3; + ifc_desc->bInterfaceClass = USB_CLASS_VENDOR_SPEC; + ifc_desc->bInterfaceSubClass = USB_CLASS_VENDOR_SPEC; + ifc_desc->bInterfaceProtocol = USB_CLASS_VENDOR_SPEC; + ifc_desc->iInterface = 0; +} + +#define HIGHSPEED 1 +#define FULLSPEED 2 + +#define BULK 1 +#define INTERRUPT 2 +static void gs_init_ep_desc(struct usb_endpoint_descriptor *ep_desc, + unsigned type, unsigned speed) +{ + ep_desc->bLength = USB_DT_ENDPOINT_SIZE; + ep_desc->bDescriptorType = USB_DT_ENDPOINT; + + if (type == BULK) { + ep_desc->bmAttributes = USB_ENDPOINT_XFER_BULK; + if (speed == HIGHSPEED) + ep_desc->wMaxPacketSize = 512; + else + ep_desc->wMaxPacketSize = 64; + } else { + + ep_desc->bmAttributes = USB_ENDPOINT_XFER_INT; + ep_desc->wMaxPacketSize = 64; + ep_desc->bInterval = 4; + } +} + +static void gs_init_header_desc(struct gs_dev *dev) +{ + dev->gs_highspeed_header[0] = + (struct usb_descriptor_header *)&dev->gs_ifc_desc; + dev->gs_highspeed_header[1] = + (struct usb_descriptor_header *)&dev->gs_hs_bulkin_desc; + dev->gs_highspeed_header[2] = + (struct usb_descriptor_header *)&dev->gs_hs_bulkout_desc; + dev->gs_highspeed_header[3] = + (struct usb_descriptor_header *)&dev->gs_hs_notifyin_desc; + dev->gs_highspeed_header[4] = NULL; + + dev->gs_fullspeed_header[0] = + (struct usb_descriptor_header *)&dev->gs_ifc_desc; + dev->gs_fullspeed_header[1] = + (struct usb_descriptor_header *)&dev->gs_fs_bulkin_desc; + dev->gs_fullspeed_header[2] = + (struct usb_descriptor_header *)&dev->gs_fs_bulkout_desc; + dev->gs_fullspeed_header[3] = + (struct usb_descriptor_header *)&dev->gs_fs_notifyin_desc; + dev->gs_fullspeed_header[4] = NULL; +} + +/*****************************************************************************/ +/* + * gs_module_init + * + * Register as a USB gadget driver and a tty driver. + */ + +char *a[] = {"modem", "nmea"}; + +static int __init gs_module_init(void) +{ + int i, retval; + struct usb_function *func; + + if (instances > MAX_INSTANCES || instances == 0) { + printk(KERN_ERR "Incorrect instances entered \n"); + return -ENODEV; + } + + gs_tty_wq = create_singlethread_workqueue("gs_tty"); + if (gs_tty_wq == 0) + return -ENOMEM; + gs_tty_driver = alloc_tty_driver(GS_NUM_PORTS); + if (!gs_tty_driver) { + destroy_workqueue(gs_tty_wq); + return -ENOMEM; + } + gs_tty_driver->owner = THIS_MODULE; + gs_tty_driver->driver_name = GS_SHORT_NAME; + gs_tty_driver->name = "ttyHSUSB"; + gs_tty_driver->major = GS_MAJOR; + gs_tty_driver->minor_start = GS_MINOR_START; + gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + gs_tty_driver->subtype = SERIAL_TYPE_NORMAL; + gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV + | TTY_DRIVER_RESET_TERMIOS; + gs_tty_driver->init_termios = tty_std_termios; + gs_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL + | CLOCAL; + tty_set_operations(gs_tty_driver, &gs_tty_ops); + + for (i = 0; i < GS_NUM_PORTS; i++) + sema_init(&gs_open_close_sem[i], 1); + + retval = tty_register_driver(gs_tty_driver); + if (retval) { + /*usb_function_unregister(&usb_func_serial); */ + put_tty_driver(gs_tty_driver); + printk(KERN_ERR + "gs_module_init: cannot register tty driver,ret = %d\n", + retval); + return retval; + } + for (i = 0; i < MAX_INSTANCES; i++) + tty_register_device(gs_tty_driver, i, NULL); + + gs_devices = kzalloc(sizeof(struct gs_dev *) * instances, + GFP_KERNEL); + if (!gs_devices) + return -ENOMEM; + + for (i = 0; i < instances; i++) { + func = &usb_function_serial[i]; + + gs_devices[i] = kzalloc(sizeof(struct gs_dev), GFP_KERNEL); + if (!gs_devices[i]) + return -ENOMEM; + spin_lock_init(&gs_devices[i]->dev_lock); + INIT_LIST_HEAD(&gs_devices[i]->dev_req_list); + gs_devices[i]->func = func; + /*1 - Interface, 3 Endpoints-> Total 4 + 1 for NULL*/ + gs_devices[i]->gs_fullspeed_header = + kmalloc(sizeof(struct usb_descriptor_header *) * 5, GFP_KERNEL); + gs_devices[i]->gs_highspeed_header = + kmalloc(sizeof(struct usb_descriptor_header *) * 5, GFP_KERNEL); + + gs_init_ifc_desc(&gs_devices[i]->gs_ifc_desc); + gs_init_ep_desc(&gs_devices[i]->gs_hs_bulkin_desc, BULK, + HIGHSPEED); + gs_init_ep_desc(&gs_devices[i]->gs_hs_bulkout_desc, BULK, + HIGHSPEED); + gs_init_ep_desc(&gs_devices[i]->gs_hs_notifyin_desc, INTERRUPT, + HIGHSPEED); + + gs_init_ep_desc(&gs_devices[i]->gs_fs_bulkin_desc, BULK, + FULLSPEED); + gs_init_ep_desc(&gs_devices[i]->gs_fs_bulkout_desc, BULK, + FULLSPEED); + gs_init_ep_desc(&gs_devices[i]->gs_fs_notifyin_desc, INTERRUPT, + FULLSPEED); + gs_init_header_desc(gs_devices[i]); + + /*Initializing Directions*/ + gs_devices[i]->gs_hs_bulkin_desc.bEndpointAddress = USB_DIR_IN; + gs_devices[i]->gs_hs_bulkout_desc.bEndpointAddress = + USB_DIR_OUT; + gs_devices[i]->gs_hs_notifyin_desc.bEndpointAddress = + USB_DIR_IN; + gs_devices[i]->gs_fs_bulkin_desc.bEndpointAddress = USB_DIR_IN; + gs_devices[i]->gs_fs_bulkout_desc.bEndpointAddress = + USB_DIR_OUT; + gs_devices[i]->gs_fs_notifyin_desc.bEndpointAddress = + USB_DIR_IN; + + func->bind = gs_bind; + func->unbind = gs_unbind; + func->configure = gs_configure; + func->disconnect = gs_disconnect; + func->setup = gs_setup; + func->name = a[i]; + func->context = gs_devices[i]; + func->fs_descriptors = gs_devices[i]->gs_fullspeed_header; + func->hs_descriptors = gs_devices[i]->gs_highspeed_header; + + retval = usb_function_register(func); + if (retval) { + printk(KERN_ERR + "gs_module_init: cannot register Function driver, ret = %d\n", + retval); + return retval; + } + } + + return 0; +} + +/* +* gs_module_exit +* +* Unregister as a tty driver and a USB gadget driver. +*/ +static void __exit gs_module_exit(void) +{ + int i; + for (i = 0; i < instances; i++) + usb_function_unregister(&usb_function_serial[i]); + + for (i = 0; i < instances; ++i) { + kfree(gs_devices[i]->gs_fullspeed_header); + kfree(gs_devices[i]->gs_highspeed_header); + kfree(gs_devices[i]); + } + for (i = 0; i < MAX_INSTANCES; i++) + tty_unregister_device(gs_tty_driver, i); + tty_unregister_driver(gs_tty_driver); + put_tty_driver(gs_tty_driver); + printk(KERN_INFO "gs_module_exit: %s %s unloaded\n", GS_LONG_NAME, + GS_VERSION_STR); +} + +/* TTY Driver */ +/* + * gs_open + */ +static int gs_open(struct tty_struct *tty, struct file *file) +{ + int port_num; + unsigned long flags; + struct gs_port *port; + struct gs_dev *dev; + struct gs_buf *buf; + struct semaphore *sem; + int ret; + + port_num = tty->index; + + gs_debug("gs_open: (%d,%p,%p)\n", port_num, tty, file); + + if (port_num < 0 || port_num >= GS_NUM_PORTS) { + printk(KERN_ERR "gs_open: (%d,%p,%p) invalid port number\n", + port_num, tty, file); + return -ENODEV; + } + + dev = gs_devices[tty->index]; + + if (dev == NULL) { + printk(KERN_ERR "gs_open: (%d,%p,%p) NULL device pointer\n", + port_num, tty, file); + return -ENODEV; + } + + sem = &gs_open_close_sem[port_num]; + if (down_interruptible(sem)) { + printk(KERN_ERR + "gs_open: (%d,%p,%p) interrupted waiting for semaphore\n", + port_num, tty, file); + return -ERESTARTSYS; + } + + spin_lock_irqsave(&dev->dev_lock, flags); + port = dev->dev_port[0]; + + if (port == NULL) { + printk(KERN_ERR "gs_open: (%d,%p,%p) NULL port pointer\n", + port_num, tty, file); + ret = -ENODEV; + goto exit_unlock_dev; + } + + spin_unlock_irqrestore(&dev->dev_lock, flags); + + spin_lock_irqsave(&port->port_lock, flags); + + if (port->port_dev == NULL) { + printk(KERN_ERR "gs_open: (%d,%p,%p) port disconnected (1)\n", + port_num, tty, file); + ret = -EIO; + goto exit_unlock_port; + } + + if (port->port_open_count > 0) { + ++port->port_open_count; + gs_debug("gs_open: (%d,%p,%p) already open\n", + port_num, tty, file); + ret = 0; + goto exit_unlock_port; + } + + tty->driver_data = NULL; + + /* mark port as in use, we can drop port lock and sleep if necessary */ + port->port_in_use = 1; + + /* allocate write buffer on first open */ + if (port->port_write_buf == NULL) { + spin_unlock_irqrestore(&port->port_lock, flags); + buf = gs_buf_alloc(write_buf_size, GFP_KERNEL); + spin_lock_irqsave(&port->port_lock, flags); + + /* might have been disconnected while asleep, check */ + if (port->port_dev == NULL) { + printk(KERN_ERR + "gs_open: (%d,%p,%p) port disconnected (2)\n", + port_num, tty, file); + port->port_in_use = 0; + ret = -EIO; + goto exit_unlock_port; + } + + port->port_write_buf = buf; + if (port->port_write_buf == NULL) { + printk(KERN_ERR + "gs_open: (%d,%p,%p) cannot allocate port write buffer\n", + port_num, tty, file); + port->port_in_use = 0; + ret = -ENOMEM; + goto exit_unlock_port; + } + + } + + /* wait for carrier detect (not implemented) */ + + /* might have been disconnected while asleep, check */ + if (port->port_dev == NULL) { + printk(KERN_ERR "gs_open: (%d,%p,%p) port disconnected (3)\n", + port_num, tty, file); + port->port_in_use = 0; + ret = -EIO; + goto exit_unlock_port; + } + + tty->driver_data = port; + port->port_tty = tty; + port->port_tty->low_latency = 1; + port->port_open_count = 1; + port->port_in_use = 0; + + gs_debug("gs_open: (%d,%p,%p) completed\n", port_num, tty, file); + /* Queue RX requests */ + port->n_read = 0; + gs_start_rx(dev); + + ret = 0; + +exit_unlock_port: + spin_unlock_irqrestore(&port->port_lock, flags); + up(sem); + return ret; + +exit_unlock_dev: + spin_unlock_irqrestore(&dev->dev_lock, flags); + up(sem); + return ret; + +} + +/* + * gs_close + */ + +#define GS_WRITE_FINISHED_EVENT_SAFELY(p) \ +({ \ + int cond; \ + \ + spin_lock_irq(&(p)->port_lock); \ + cond = !(p)->port_dev || !gs_buf_data_avail((p)->port_write_buf); \ + spin_unlock_irq(&(p)->port_lock); \ + cond; \ +}) + +static void gs_close(struct tty_struct *tty, struct file *file) +{ + struct gs_port *port = tty->driver_data; + struct semaphore *sem; + + if (port == NULL) { + printk(KERN_ERR "gs_close: NULL port pointer\n"); + return; + } + + gs_debug("gs_close: (%d,%p,%p)\n", port->port_num, tty, file); + + sem = &gs_open_close_sem[port->port_num]; + down(sem); + + spin_lock_irq(&port->port_lock); + + if (port->port_open_count == 0) { + printk(KERN_ERR + "gs_close: (%d,%p,%p) port is already closed\n", + port->port_num, tty, file); + goto exit; + } + + if (port->port_open_count > 1) { + --port->port_open_count; + goto exit; + } + + /* free disconnected port on final close */ + if (port->port_dev == NULL) + goto exit; + + + /* mark port as closed but in use, we can drop port lock */ + /* and sleep if necessary */ + port->port_in_use = 1; + port->port_open_count = 0; + + /* wait for write buffer to drain, or */ + /* at most GS_CLOSE_TIMEOUT seconds */ + if (gs_buf_data_avail(port->port_write_buf) > 0) { + spin_unlock_irq(&port->port_lock); + wait_event_interruptible_timeout(port->port_write_wait, + GS_WRITE_FINISHED_EVENT_SAFELY + (port), GS_CLOSE_TIMEOUT * HZ); + spin_lock_irq(&port->port_lock); + } + + /* free disconnected port on final close */ + /* (might have happened during the above sleep) */ + if (port->port_dev == NULL) + goto exit; + + + gs_buf_clear(port->port_write_buf); + + /* Flush bulk-out pipe */ + usb_ept_fifo_flush(port->port_dev->dev_out_ep); + tty->driver_data = NULL; + port->port_tty = NULL; + port->port_in_use = 0; + + gs_debug("gs_close: (%d,%p,%p) completed\n", port->port_num, tty, file); + +exit: + spin_unlock_irq(&port->port_lock); + up(sem); + if (port->port_dev == NULL) + kfree(port); +} + +/* + * gs_write + */ +static int gs_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + unsigned long flags; + struct gs_port *port = tty->driver_data; + int ret; + + if (port == NULL) { + printk(KERN_ERR "gs_write: NULL port pointer\n"); + return -EIO; + } + + gs_debug("gs_write: (%d,%p) writing %d bytes\n", port->port_num, tty, + count); + + if (count == 0) + return 0; + + spin_lock_irqsave(&port->port_lock, flags); + + if (port->port_dev == NULL) { + printk(KERN_ERR "gs_write: (%d,%p) port is not connected\n", + port->port_num, tty); + ret = -EIO; + goto exit; + } + + if (port->port_open_count == 0) { + printk(KERN_ERR "gs_write: (%d,%p) port is closed\n", + port->port_num, tty); + ret = -EBADF; + goto exit; + } + + count = gs_buf_put(port->port_write_buf, buf, count); + + + if (port->port_dev->dev_config) + gs_send(gs_devices[tty->index]); + spin_unlock_irqrestore(&port->port_lock, flags); + + gs_debug("gs_write: (%d,%p) wrote %d bytes\n", port->port_num, tty, + count); + + return count; + +exit: + spin_unlock_irqrestore(&port->port_lock, flags); + return ret; +} + +/* + * gs_put_char + */ +static int gs_put_char(struct tty_struct *tty, unsigned char ch) +{ + unsigned long flags; + int ret = 0; + struct gs_port *port = tty->driver_data; + + if (port == NULL) { + printk(KERN_ERR "gs_put_char: NULL port pointer\n"); + goto out; + } + + gs_debug("gs_put_char: (%d,%p) char=0x%x, called from %p, %p, %p\n", + port->port_num, tty, ch, __builtin_return_address(0), + __builtin_return_address(1), __builtin_return_address(2)); + + spin_lock_irqsave(&port->port_lock, flags); + + if (port->port_dev == NULL) { + printk(KERN_ERR "gs_put_char: (%d,%p) port is not connected\n", + port->port_num, tty); + goto exit_unlock; + } + + if (port->port_open_count == 0) { + printk(KERN_ERR "gs_put_char: (%d,%p) port is closed\n", + port->port_num, tty); + goto exit_unlock; + } + + ret = gs_buf_put(port->port_write_buf, &ch, 1); + +exit_unlock: + spin_unlock_irqrestore(&port->port_lock, flags); +out: + return ret; +} + +/* + * gs_flush_chars + */ +static void gs_flush_chars(struct tty_struct *tty) +{ + unsigned long flags; + struct gs_port *port = tty->driver_data; + + if (port == NULL) { + printk(KERN_ERR "gs_flush_chars: NULL port pointer\n"); + return; + } + + gs_debug("gs_flush_chars: (%d,%p)\n", port->port_num, tty); + + spin_lock_irqsave(&port->port_lock, flags); + + if (port->port_dev == NULL) { + printk(KERN_ERR + "gs_flush_chars: (%d,%p) port is not connected\n", + port->port_num, tty); + goto exit; + } + + if (port->port_open_count == 0) { + printk(KERN_ERR "gs_flush_chars: (%d,%p) port is closed\n", + port->port_num, tty); + goto exit; + } + + if (port->port_dev->dev_config) + gs_send(gs_devices[tty->index]); + spin_unlock_irqrestore(&port->port_lock, flags); + + + return; + +exit: + spin_unlock_irqrestore(&port->port_lock, flags); +} + +/* + * gs_write_room + */ +static int gs_write_room(struct tty_struct *tty) +{ + + int room = 0; + unsigned long flags; + struct gs_port *port = tty->driver_data; + + if (port == NULL) + return 0; + + spin_lock_irqsave(&port->port_lock, flags); + + if (port->port_dev != NULL && port->port_open_count > 0 + && port->port_write_buf != NULL) + room = gs_buf_space_avail(port->port_write_buf); + + spin_unlock_irqrestore(&port->port_lock, flags); + + gs_debug("gs_write_room: (%d,%p) room=%d\n", port->port_num, tty, room); + + return room; +} + +/* + * gs_chars_in_buffer + */ +static int gs_chars_in_buffer(struct tty_struct *tty) +{ + int chars = 0; + unsigned long flags; + struct gs_port *port = tty->driver_data; + + if (port == NULL) + return 0; + + spin_lock_irqsave(&port->port_lock, flags); + + if (port->port_dev != NULL && port->port_open_count > 0 + && port->port_write_buf != NULL) + chars = gs_buf_data_avail(port->port_write_buf); + + spin_unlock_irqrestore(&port->port_lock, flags); + + gs_debug("gs_chars_in_buffer: (%d,%p) chars=%d\n", + port->port_num, tty, chars); + + return chars; +} + +/* + * gs_throttle + */ +static void gs_throttle(struct tty_struct *tty) +{ +} + +/* + * gs_unthrottle + */ +static void gs_unthrottle(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + queue_work(gs_tty_wq, &port->push_work); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +/* + * gs_break + */ +static int gs_break(struct tty_struct *tty, int break_state) +{ + return 0; +} + +/* + * gs_ioctl + */ +static int gs_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + /* could not handle ioctl */ + return -ENOIOCTLCMD; +} + +/* + * gs_set_termios + */ +static void gs_set_termios(struct tty_struct *tty, struct ktermios *old) +{ +} + +/* +* gs_send +* +* This function finds available write requests, calls +* gs_send_packet to fill these packets with data, and +* continues until either there are no more write requests +* available or no more data to send. This function is +* run whenever data arrives or write requests are available. +*/ +static int gs_send(struct gs_dev *dev) +{ + struct gs_port *port = dev->dev_port[0]; + struct list_head *pool = &port->write_pool; + int status = 0; + static long prev_len; + bool do_tty_wake = false; + struct usb_endpoint *ep = dev->dev_in_ep; + + while (!list_empty(pool)) { + struct usb_request *req; + int len; + req = list_entry(pool->next, struct usb_request, list); + len = gs_send_packet(dev, req->buf, usb_ept_get_max_packet(ep)); + if (len == 0) { + /* Queue zero length packet */ + if (prev_len == usb_ept_get_max_packet(ep)) { + req->length = 0; + list_del(&req->list); + + spin_unlock(&port->port_lock); + status = usb_ept_queue_xfer(ep, req); + spin_lock(&port->port_lock); + if (status) { + printk(KERN_ERR "%s: %s err %d\n", + __func__, "queue", status); + list_add(&req->list, pool); + } + prev_len = 0; + } + wake_up_interruptible(&port->port_write_wait); + break; + } + do_tty_wake = true; + + req->length = len; + list_del(&req->list); + + /* Drop lock while we call out of driver; completions + * could be issued while we do so. Disconnection may + * happen too; maybe immediately before we queue this! + * NOTE that we may keep sending data for a while after + * the TTY closed (dev->ioport->port_tty is NULL). + */ + spin_unlock(&port->port_lock); + status = usb_ept_queue_xfer(ep, req); + spin_lock(&port->port_lock); + + if (status) { + printk(KERN_ERR "%s: %s err %d\n", + __func__, "queue", status); + list_add(&req->list, pool); + break; + } + prev_len = req->length; + + } + + if (do_tty_wake && port->port_tty) + tty_wakeup(port->port_tty); + return status; + +} + +/* + * gs_send_packet + * + * If there is data to send, a packet is built in the given + * buffer and the size is returned. If there is no data to + * send, 0 is returned. If there is any error a negative + * error number is returned. + * + * Called during USB completion routine, on interrupt time. + * + * We assume that disconnect will not happen until all completion + * routines have completed, so we can assume that the dev_port + * array does not change during the lifetime of this function. + */ +static int gs_send_packet(struct gs_dev *dev, char *packet, unsigned int size) +{ + unsigned int len; + struct gs_port *port; + + if (dev == NULL) { + printk(KERN_ERR "gs_recv_packet:NULL device pointer\n"); + return -EIO; + } + + /* TEMPORARY -- only port 0 is supported right now */ + port = dev->dev_port[0]; + if (port == NULL) { + printk(KERN_ERR + "gs_send_packet: port=%d, NULL port pointer\n", 0); + return -EIO; + } + + + len = gs_buf_data_avail(port->port_write_buf); + if (len < size) + size = len; + if (size != 0) + size = gs_buf_get(port->port_write_buf, packet, size); + + + + if (port->port_tty) + tty_wakeup(port->port_tty); + + return size; +} + +static void gs_rx_push(struct work_struct *work) +{ + struct gs_port *port = container_of(work, + struct gs_port, + push_work); + struct tty_struct *tty; + struct list_head *queue = &port->read_queue; + bool do_push = false; + struct gs_dev *dev = port->port_dev; + + /* hand any queued data to the tty */ + spin_lock_irq(&port->port_lock); + tty = port->port_tty; + while (!list_empty(queue)) { + struct usb_request *req; + + req = list_first_entry(queue, struct usb_request, list); + + /* discard data if tty was closed */ + if (!tty) + goto recycle; + + if (req->actual) { + char *packet = req->buf; + unsigned size = req->actual; + unsigned n; + int count; + /* we may have pushed part of this packet already... */ + n = port->n_read; + if (n) { + packet += n; + size -= n; + } + /*printk(KERN_INFO "tty_push:%d\n",size);*/ + count = tty_insert_flip_string(tty, packet, size); + if (count == 0) + printk(KERN_INFO "%s: tty buffer is full: throttle\n", + __func__); + if (count) + do_push = true; + if (count != size) { + /* stop pushing; TTY layer can't handle more */ + port->n_read += count; + break; + } + port->n_read = 0; + } +recycle: + list_move(&req->list, &port->read_pool); + } + if (tty && do_push) { + spin_unlock_irq(&port->port_lock); + tty_flip_buffer_push(tty); + wake_up_interruptible(&tty->read_wait); + spin_lock_irq(&port->port_lock); + /* tty may have been closed */ + tty = port->port_tty; + } + if (!list_empty(queue) && tty) { + if (!test_bit(TTY_THROTTLED, &tty->flags)) { + if (do_push) + queue_work(gs_tty_wq, &port->push_work); + } + } + gs_start_rx(dev); + spin_unlock_irq(&port->port_lock); +} + +/* +* gs_read_complete +*/ +static void gs_read_complete(struct usb_endpoint *ep, struct usb_request *req) +{ + /* used global variable */ + struct gs_dev *dev = (struct gs_dev *)req->device; + struct gs_port *port; + struct tty_struct *tty; + + if (dev == NULL) { + printk(KERN_ERR "gs_read_complete: NULL device pointer\n"); + return; + } + + port = dev->dev_port[0]; + tty = port->port_tty; + switch (req->status) { + case 0: + spin_lock(&port->port_lock); + list_add_tail(&req->list, &port->read_queue); + if (!test_bit(TTY_THROTTLED, &tty->flags)) + queue_work(gs_tty_wq, &port->push_work); + spin_unlock(&port->port_lock); + break; + + case -ESHUTDOWN: + /* disconnect */ + gs_debug("gs_read_complete: shutdown\n"); + gs_free_req(ep, req); + break; + + case -ENODEV: + list_add_tail(&req->list, &port->read_pool); + /* Implemented handling in future if needed */ + break; + default: + list_add_tail(&req->list, &port->read_pool); + printk(KERN_ERR + "gs_read_complete: unexpected status error, status=%d\n", + req->status); + /* goto requeue; */ + break; + } +} + +/* +* gs_write_complete +*/ +static void gs_write_complete(struct usb_endpoint *ep, struct usb_request *req) +{ + struct gs_dev *dev = (struct gs_dev *)req->device; + struct gs_port *port = dev->dev_port[0]; + unsigned long flags; + + if (dev == NULL) { + printk(KERN_ERR "gs_write_complete: NULL device pointer\n"); + return; + } + spin_lock_irqsave(&port->port_lock, flags); + list_add(&req->list, &port->write_pool); + + switch (req->status) { + default: + /* presumably a transient fault */ + printk(KERN_ERR "%s: unexpected status %d\n", + __func__, req->status); + /* FALL THROUGH */ + case 0: + /* normal completion */ + + if ((req->length == 0) && + (gs_buf_data_avail(port->port_write_buf) == 0)) { + break; + } + if (dev->dev_config) + gs_send(dev); + + break; + + case -ESHUTDOWN: + /* disconnect */ + printk(KERN_DEBUG "%s: shutdown\n", __func__); + break; + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +/* Send Notification to host if Status changes */ +static void send_notify_data(struct usb_endpoint *ep, struct usb_request *req) +{ + struct gs_dev *dev = (struct gs_dev *)req->device; + struct usb_cdc_notification *notify; + struct gs_port *port; + unsigned int msr, ret; + __le16 *data; + + if (dev == NULL) { + printk(KERN_ERR "send_notify_data: NULL device pointer\n"); + return; + } + + port = dev->dev_port[0]; + + if (port == NULL) { + printk(KERN_ERR"send_notify_data:port is NULL\n"); + return; + } + + if (test_bit(0, &dev->notify_queued)) + usb_ept_cancel_xfer(dev->dev_notify_ep, + dev->notify_req); + notify = req->buf; + msr = port->msr; + notify->bmRequestType = 0xA1; + notify->bNotificationType = USB_CDC_NOTIFY_SERIAL_STATE; + notify->wValue = __constant_cpu_to_le16(0); + notify->wIndex = __constant_cpu_to_le16(dev->interface_num); + notify->wLength = __constant_cpu_to_le16(2); + data = req->buf + sizeof *notify; + data[0] = __constant_cpu_to_le16(((msr & MSR_CD) ? 1 : 0) + | ((msr & MSR_DSR) ? (1<<1) : (0<<1)) + | ((msr & MSR_RI) ? (1<<3) : (0<<3))); + + set_bit(0, &dev->notify_queued); + ret = usb_ept_queue_xfer(ep, req); + if (ret) { + clear_bit(0, &dev->notify_queued); + printk(KERN_ERR + "send_notify_data: cannot queue status request,ret = %d\n", + ret); + } +} + +/* Free request if -ESHUTDOWN */ +static void gs_status_complete(struct usb_endpoint *ep, + struct usb_request *req) +{ + struct gs_dev *dev = (struct gs_dev *)req->device; + struct gs_port *port; + + if (dev == NULL) { + printk(KERN_ERR"gs_status_complete : NULL device pointer\n"); + return; + } + + port = dev->dev_port[0]; + + if (port == NULL) { + printk(KERN_ERR "gs_status_complete: NULL port pointer\n"); + return; + } + + clear_bit(0, &dev->notify_queued); + switch (req->status) { + case 0: + + gs_debug("%s:port->msr=%x,dev=%p,ep=%p,req=%p", __func__, + port->msr, dev, dev->dev_notify_ep, dev->notify_req); + /* executed only if data missed because of + ** request already in queue and user modifies using tiocmset */ + if (port->prev_msr != port->msr) { + send_notify_data(dev->dev_notify_ep, dev->notify_req); + port->prev_msr = port->msr; + } + break; + + case -ESHUTDOWN: + /* disconnect */ + gs_debug("gs_status_complete: shutdown\n"); + gs_free_req(ep, req); + break; + + default: + printk(KERN_ERR + "gs_status_complete: unexpected status error, status=%d\n", + req->status); + break; + } +} + +/* Function Driver */ +/* + * gs_bind + * + * Called on module load. Allocates and initializes the device + * structure and a control request. + */ +static void gs_bind(void *_ctxt) +{ + struct usb_endpoint *ep; + struct gs_dev *dev = _ctxt; + struct usb_function *func = dev->func; + int i = 0; + int ret; + + if (func == NULL) { + pr_err("%s: NULL function pointer\n", __func__); + return; + } + + ret = gs_alloc_ports(dev, GFP_KERNEL); + if (ret != 0) { + pr_err("%s: cannot allocate ports\n", __func__); + gs_unbind(_ctxt); + return; + } + + ret = usb_msm_get_next_ifc_number(func); + dev->gs_ifc_desc.bInterfaceNumber = ret; + dev->gs_ifc_desc.iInterface = 0; + + /*Configuring IN Endpoint*/ + ep = dev->dev_in_ep = usb_alloc_endpoint(USB_DIR_IN); + if (!ep) { + pr_err("%s: in endpoint allocation failed\n", __func__); + return; + } + dev->gs_hs_bulkin_desc.bEndpointAddress = USB_DIR_IN | ep->num; + dev->gs_fs_bulkin_desc.bEndpointAddress = USB_DIR_IN | ep->num; + pr_debug("%s: bulk_in_endpoint Number = %d\n", + __func__, ep->num); + + /*Configuring OUT endpoint*/ + ep = dev->dev_out_ep = usb_alloc_endpoint(USB_DIR_OUT); + if (!ep) { + pr_err("out endpoint allocation failed\n"); + return; + } + dev->gs_hs_bulkout_desc.bEndpointAddress = USB_DIR_OUT | ep->num; + dev->gs_fs_bulkout_desc.bEndpointAddress = USB_DIR_OUT | ep->num; + pr_debug("%s: bulk_out_endpoint Number = %d\n", + __func__, ep->num); + + /*Configuring NOTIFY endpoint*/ + ep = dev->dev_notify_ep = usb_alloc_endpoint(USB_DIR_IN); + if (!ep) { + pr_err("notify endpoint allocation failed\n"); + return; + } + dev->gs_hs_notifyin_desc.bEndpointAddress = USB_DIR_IN | ep->num; + dev->gs_fs_notifyin_desc.bEndpointAddress = USB_DIR_IN | ep->num; + pr_debug("%s: notify_in_endpoint Number = %d\n", + __func__, ep->num); + + + + for (i = 0; i < GS_DEFAULT_INT_REQ; ++i) { + struct gs_reqbuf *bh = &dev->statusreqbuf; + bh->buf = kmalloc(64, GFP_KERNEL); + if (!bh->buf) + return; + } + + dev->bound = 1; + return; +} + +/* + * gs_unbind + * + * Called on module unload. Frees the control request and device + * structure. + */ +static void /* __init_or_exit */ gs_unbind(void *_ctxt) +{ + struct gs_dev *dev = _ctxt; + + if (!dev) { + pr_err("%s: error: null device\n", __func__); + return; + } + if (!dev->bound) + return; + + kfree(dev->statusreqbuf.buf); + + if (dev->dev_in_ep) { + usb_ept_fifo_flush(dev->dev_in_ep); + usb_ept_enable(dev->dev_in_ep, 0); + usb_free_endpoint(dev->dev_in_ep); + } + if (dev->dev_out_ep) { + usb_ept_fifo_flush(dev->dev_out_ep); + usb_ept_enable(dev->dev_out_ep, 0); + usb_free_endpoint(dev->dev_out_ep); + } + if (dev->dev_notify_ep) { + usb_ept_fifo_flush(dev->dev_notify_ep); + usb_ept_enable(dev->dev_notify_ep, 0); + usb_free_endpoint(dev->dev_notify_ep); + } + + gs_free_ports(dev); + dev->bound = 0; + pr_debug("%s: %s %s\n", __func__, GS_LONG_NAME, GS_VERSION_STR); +} + +static void gser_complete_set_line_coding(struct usb_endpoint *ep, + struct usb_request *req) +{ + struct gs_dev *dev = (struct gs_dev *)req->device; + struct gs_port *port; + struct usb_cdc_line_coding *value; + struct usb_request *in_req; + + port = dev->dev_port[0]; + if (!(dev && dev->dev_port[0])) { + printk(KERN_ERR "%s(): dev or dev_port is null\n", __func__); + usb_ept_set_halt(dev->func->ep0_in); + return; + } + if (req->actual != sizeof(port->port_line_coding)) { + printk(KERN_ERR "%s(): received wrong data\n", __func__); + usb_ept_set_halt(dev->func->ep0_in); + return; + } + + port = dev->dev_port[0]; + + /* Use Host assigned port_line setting */ + value = req->buf; + port->port_line_coding = *value; + + /* Send ACK on EP0 IN */ + in_req = dev->func->ep0_in_req; + in_req->length = 0; + in_req->complete = 0; + usb_ept_queue_xfer(dev->func->ep0_in, in_req); +} + +static int gs_setup(struct usb_ctrlrequest *ctrl, + void *buf, int len, void *_ctxt) +{ + int ret = -EOPNOTSUPP; + struct gs_dev *dev = _ctxt; + struct gs_port *port;/* ACM only has one port */ + u16 wIndex = le16_to_cpu(ctrl->wIndex); + u16 wValue = le16_to_cpu(ctrl->wValue); + u16 wLength = le16_to_cpu(ctrl->wLength); + + if (dev == NULL) { + printk(KERN_ERR"gs_setup:device pointer NULL\n"); + return 0; + } + port = dev->dev_port[0]; + + if (port == NULL) { + printk(KERN_ERR"gs_setup: port pointer is NULL\n"); + return 0; + } + switch (ctrl->bRequest) { + + case USB_CDC_REQ_SET_LINE_CODING: + if (port) { + struct usb_request *req = dev->func->ep0_out_req; + ret = min(wLength, + (u16) sizeof(struct usb_cdc_line_coding)); + if (ret != sizeof(struct usb_cdc_line_coding)) + ret = -EOPNOTSUPP; + else { + req->device = dev; + req->complete = gser_complete_set_line_coding; + } + } else + ret = -ENODEV; + break; + + case USB_CDC_REQ_GET_LINE_CODING: + port = dev->dev_port[0];/* ACM only has one port */ + ret = min(wLength, (u16) sizeof(struct usb_cdc_line_coding)); + if (port) { + spin_lock(&port->port_lock); + memcpy(buf, &port->port_line_coding, ret); + spin_unlock(&port->port_lock); + } + break; + case USB_CDC_REQ_SET_CONTROL_LINE_STATE: + port = dev->dev_port[0];/* ACM only has one port */ + if (wValue & USB_CDC_SET_CONTROL_LINE_STATE_DTR) { + port->mcr |= MCR_DTR; + } else { + port->mcr &= ~MCR_DTR; + } + if (wValue & USB_CDC_SET_CONTROL_LINE_STATE_RTS) + port->mcr |= MCR_RTS; + else + port->mcr &= ~MCR_RTS; + + dev->interface_num = wIndex; + ret = 0; + break; + + default: + break; + } + + return ret; +} + +static void gs_disconnect(void *_ctxt) +{ + struct gs_dev *dev = _ctxt; + struct gs_port *port = dev->dev_port[0]; + unsigned long flags; + + /* tell the TTY glue not to do I/O here any more */ + spin_lock_irqsave(&port->port_lock, flags); + dev->dev_config = 0; + if (port->port_open_count > 0 || port->port_in_use) { + wake_up_interruptible(&port->port_write_wait); + if (port->port_tty) { + wake_up_interruptible(&port->port_tty->read_wait); + wake_up_interruptible(&port->port_tty->write_wait); + tty_hangup(port->port_tty); + } + } + port->mcr = 0; + port->msr = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + +} +/* + * gs_configure + * + * Configures the device by enabling device specific + * optimizations, setting up the endpoints, allocating + * read and write requests and queuing read requests. + * + * The device lock must be held when calling this function. + */ +static void gs_configure(int config, void *_ctxt) +{ + int i, ret = 0; + unsigned MaxPacketSize; + struct gs_dev *dev = _ctxt; + struct usb_endpoint *ep; + struct usb_request *req; + struct gs_port *port; + struct list_head *rhead; + struct list_head *whead; + unsigned started = 0; + + if (dev == NULL) { + printk(KERN_ERR "gs_configure: NULL device pointer\n"); + return; + } + if (!dev->bound) + return; + + port = dev->dev_port[0]; + rhead = &port->read_pool; + whead = &port->write_pool; + if (port == NULL) { + printk(KERN_ERR "gs_configure:port is NULL\n"); + return; + } + + + if (!config) { + gs_debug("gs_configure: Deconfigure\n"); + dev->configured = SERIAL_UNCONFIGURED; + gs_reset_config(dev); + return; + } + dev->dev_config = config; + + if (dev->dev_in_ep == NULL || dev->dev_out_ep == NULL || + (dev->dev_notify_ep == NULL)) { + printk(KERN_ERR "gs_configure : cannot find endpoints\n"); + ret = -ENODEV; + goto reset_config; + } + + if (usb_msm_get_speed() == USB_SPEED_HIGH) { + usb_configure_endpoint(dev->dev_in_ep, &dev->gs_hs_bulkin_desc); + usb_configure_endpoint(dev->dev_out_ep, + &dev->gs_hs_bulkout_desc); + usb_configure_endpoint(dev->dev_notify_ep, + &dev->gs_hs_notifyin_desc); + } else { + usb_configure_endpoint(dev->dev_in_ep, &dev->gs_fs_bulkin_desc); + usb_configure_endpoint(dev->dev_out_ep, + &dev->gs_fs_bulkout_desc); + usb_configure_endpoint(dev->dev_notify_ep, + &dev->gs_fs_notifyin_desc); + } + usb_ept_enable(dev->dev_in_ep, 1); + usb_ept_enable(dev->dev_out_ep, 1); + usb_ept_enable(dev->dev_notify_ep, 1); + + gs_debug("gs_configure: endpoint sizes and buffers\n"); + /* allocate and queue read requests */ + ep = dev->dev_out_ep; + MaxPacketSize = usb_ept_get_max_packet(ep); + for (i = 0; i < read_q_size; i++) { + req = gs_alloc_req(ep, MaxPacketSize); + if (req) { + req->device = (void *)dev; + req->length = MaxPacketSize; + req->complete = gs_read_complete; + list_add_tail(&req->list, rhead); + gs_debug("gs_configure: queuing read request(%d)\n", i); + } else { + printk(KERN_ERR + "gs_configure: cannot allocate read request(%d)\n", i); + goto reset_config; + } + } + + /* allocate write requests, and put on free list */ + ep = dev->dev_in_ep; + MaxPacketSize = usb_ept_get_max_packet(ep); + for (i = 0; i < write_q_size; i++) { + req = gs_alloc_req(ep, MaxPacketSize); + if (req) { + req->device = (void *)dev; + req->length = MaxPacketSize; + req->complete = gs_write_complete; + list_add_tail(&req->list, whead); + } else { + printk(KERN_ERR + "gs_configure: cannot allocate write request(%d)\n", i); + goto reset_config; + } + } + + ep = dev->dev_notify_ep; + MaxPacketSize = usb_ept_get_max_packet(ep); + for (i = 0; i < GS_DEFAULT_INT_REQ; ++i) { + struct gs_reqbuf *bh = &dev->statusreqbuf; + dev->notify_req = req = gs_alloc_req(ep, 0); + if (req) { + req->device = (void *)dev; + req->buf = bh->buf; + req->length = MaxPacketSize; + req->complete = gs_status_complete; + } + } + if (port->port_open_count) { + unsigned long flags; + spin_lock_irqsave(&port->port_lock, flags); + started = gs_start_rx(dev); + spin_unlock_irqrestore(&port->port_lock, flags); + if (started) + tty_wakeup(port->port_tty); + } + + dev->configured = SERIAL_CONFIGURED; + + return; + +reset_config: + printk(KERN_ERR "gs_configure(end): error, calling gs_reset_config\n"); + gs_reset_config(dev); + return; +} +static unsigned gs_start_rx(struct gs_dev *dev) +{ + struct gs_port *port = dev->dev_port[0]; + struct list_head *pool = &port->read_pool; + unsigned ret = 0; + struct usb_endpoint *ep = dev->dev_out_ep; + unsigned started = 0; + + while (!list_empty(pool)) { + struct usb_request *req; + struct tty_struct *tty; + tty = port->port_tty; + if (!tty) { + printk(KERN_ERR "%s: tty is null\n", __func__); + break; + } + + req = list_entry(pool->next, struct usb_request, list); + list_del(&req->list); + spin_unlock(&port->port_lock); + ret = usb_ept_queue_xfer(ep, req); + spin_lock(&port->port_lock); + if (ret) { + list_add(&req->list, pool); + break; + } + started++; + + } + return started; +} +/* + * gs_reset_config + * + * Mark the device as not configured, disable all endpoints, + * which forces completion of pending I/O and frees queued + * requests, and free the remaining write requests on the + * free list. + * + * The device lock must be held when calling this function. + */ +static void gs_reset_config(struct gs_dev *dev) +{ + struct gs_port *port; + struct usb_request *req; + unsigned long flags; + + if (dev == NULL) { + printk(KERN_ERR "gs_reset_config: NULL device pointer\n"); + return; + } + + port = dev->dev_port[0]; + + + + if (dev->dev_out_ep) + usb_free_endpoint_all_req(dev->dev_out_ep); + if (dev->dev_in_ep) + usb_free_endpoint_all_req(dev->dev_in_ep); + if (dev->dev_notify_ep) + usb_free_endpoint_all_req(dev->dev_notify_ep); + + + spin_lock_irqsave(&port->port_lock, flags); + dev->dev_config = GS_NO_CONFIG_ID; + /* free write requests on the free list */ + while (!list_empty(&port->write_pool)) { + req = list_entry(port->write_pool.next, + struct usb_request, list); + list_del(&req->list); + gs_free_req(dev->dev_in_ep, req); + } + + /* free read requests from read pool */ + while (!list_empty(&port->read_pool)) { + req = list_entry(port->read_pool.next, + struct usb_request, list); + list_del(&req->list); + gs_free_req(dev->dev_out_ep, req); + } + + /* free read requests from read queue */ + while (!list_empty(&port->read_queue)) { + req = list_entry(port->read_queue.next, + struct usb_request, list); + list_del(&req->list); + gs_free_req(dev->dev_out_ep, req); + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +/* + * gs_alloc_req + * + * Allocate a usb_request and its buffer. Returns a pointer to the + * usb_request or NULL if there is an error. + */ +static struct usb_request *gs_alloc_req(struct usb_endpoint *ep, + unsigned int len) +{ + struct usb_request *req; + if (ep == NULL) + return NULL; + req = usb_ept_alloc_req(ep, len); + return req; +} + +/* + * gs_free_req + * + * Free a usb_request and its buffer. + */ +static void gs_free_req(struct usb_endpoint *ep, struct usb_request *req) +{ + if (ep != NULL && req != NULL) + usb_ept_free_req(ep, req); +} + +/* + * gs_alloc_ports + * + * Allocate all ports and set the gs_dev struct to point to them. + * Return 0 if successful, or a negative error number. + * + * The device lock is normally held when calling this function. + */ +static int gs_alloc_ports(struct gs_dev *dev, gfp_t kmalloc_flags) +{ + int i; + struct gs_port *port; + + if (dev == NULL) + return -EIO; + + for (i = 0; i < GS_NUM_PORTS; i++) { + port = kzalloc(sizeof(struct gs_port), kmalloc_flags); + if (port == NULL) + return -ENOMEM; + + INIT_WORK(&port->push_work, gs_rx_push); + INIT_LIST_HEAD(&port->read_pool); + INIT_LIST_HEAD(&port->read_queue); + INIT_LIST_HEAD(&port->write_pool); + port->msr = 0; + port->prev_msr = 0; + port->mcr = 0; + port->port_dev = dev; + port->port_num = i; + port->port_line_coding.dwDTERate = + cpu_to_le32(GS_DEFAULT_DTE_RATE); + port->port_line_coding.bCharFormat = GS_DEFAULT_CHAR_FORMAT; + port->port_line_coding.bParityType = GS_DEFAULT_PARITY; + port->port_line_coding.bDataBits = GS_DEFAULT_DATA_BITS; + spin_lock_init(&port->port_lock); + mutex_init(&port->mutex_lock); + init_waitqueue_head(&port->port_write_wait); + + dev->dev_port[i] = port; + } + + return 0; +} + +/* + * gs_free_ports + * + * Free all closed ports. Open ports are disconnected by + * freeing their write buffers, setting their device pointers + * and the pointers to them in the device to NULL. These + * ports will be freed when closed. + * + * The device lock is normally held when calling this function. + */ +static void gs_free_ports(struct gs_dev *dev) +{ + int i; + unsigned long flags; + struct gs_port *port; + + if (dev == NULL) + return; + + for (i = 0; i < GS_NUM_PORTS; i++) { + port = dev->dev_port[i]; + if (port != NULL) { + dev->dev_port[i] = NULL; + + spin_lock_irqsave(&port->port_lock, flags); + + if (port->port_write_buf != NULL) { + gs_buf_free(port->port_write_buf); + port->port_write_buf = NULL; + } + + if (port->port_open_count > 0 || port->port_in_use) { + port->port_dev = NULL; + wake_up_interruptible(&port->port_write_wait); + if (port->port_tty) { + wake_up_interruptible + (&port->port_tty->read_wait); + wake_up_interruptible + (&port->port_tty->write_wait); + } + spin_unlock_irqrestore(&port->port_lock, flags); + } else { + spin_unlock_irqrestore(&port->port_lock, flags); + kfree(port); + } + + } + } +} + +/* Circular Buffer */ + +/* + * gs_buf_alloc + * + * Allocate a circular buffer and all associated memory. + */ +static struct gs_buf *gs_buf_alloc(unsigned int size, gfp_t kmalloc_flags) +{ + struct gs_buf *gb; + + if (size == 0) + return NULL; + + gb = kmalloc(sizeof(struct gs_buf), kmalloc_flags); + if (gb == NULL) + return NULL; + + gb->buf_buf = kmalloc(size, kmalloc_flags); + if (gb->buf_buf == NULL) { + kfree(gb); + return NULL; + } + + gb->buf_size = size; + gb->buf_get = gb->buf_put = gb->buf_buf; + + return gb; +} + +/* + * gs_buf_free + * + * Free the buffer and all associated memory. + */ +void gs_buf_free(struct gs_buf *gb) +{ + if (gb) { + kfree(gb->buf_buf); + kfree(gb); + } +} + +/* + * gs_buf_clear + * + * Clear out all data in the circular buffer. + */ +void gs_buf_clear(struct gs_buf *gb) +{ + if (gb != NULL) + gb->buf_get = gb->buf_put; + /* equivalent to a get of all data available */ +} + +/* + * gs_buf_data_avail + * + * Return the number of bytes of data available in the circular + * buffer. + */ +unsigned int gs_buf_data_avail(struct gs_buf *gb) +{ + if (gb != NULL) + return (gb->buf_size + gb->buf_put - gb->buf_get) + % gb->buf_size; + else + return 0; +} + +/* + * gs_buf_space_avail + * + * Return the number of bytes of space available in the circular + * buffer. + */ +unsigned int gs_buf_space_avail(struct gs_buf *gb) +{ + if (gb != NULL) + return (gb->buf_size + gb->buf_get - gb->buf_put - 1) + % gb->buf_size; + else + return 0; +} + +/* + * gs_buf_put + * + * Copy data data from a user buffer and put it into the circular buffer. + * Restrict to the amount of space available. + * + * Return the number of bytes copied. + */ +unsigned int gs_buf_put(struct gs_buf *gb, const char *buf, unsigned int count) +{ + unsigned int len; + + if (gb == NULL) + return 0; + + len = gs_buf_space_avail(gb); + if (count > len) + count = len; + + if (count == 0) + return 0; + + len = gb->buf_buf + gb->buf_size - gb->buf_put; + if (count > len) { + memcpy(gb->buf_put, buf, len); + memcpy(gb->buf_buf, buf + len, count - len); + gb->buf_put = gb->buf_buf + count - len; + } else { + memcpy(gb->buf_put, buf, count); + if (count < len) + gb->buf_put += count; + else /* count == len */ + gb->buf_put = gb->buf_buf; + } + + return count; +} + +/* + * gs_buf_get + * + * Get data from the circular buffer and copy to the given buffer. + * Restrict to the amount of data available. + * + * Return the number of bytes copied. + */ +unsigned int gs_buf_get(struct gs_buf *gb, char *buf, unsigned int count) +{ + unsigned int len; + + if (gb == NULL) + return 0; + + len = gs_buf_data_avail(gb); + if (count > len) + count = len; + + if (count == 0) + return 0; + + len = gb->buf_buf + gb->buf_size - gb->buf_get; + if (count > len) { + memcpy(buf, gb->buf_get, len); + memcpy(buf + len, gb->buf_buf, count - len); + gb->buf_get = gb->buf_buf + count - len; + } else { + memcpy(buf, gb->buf_get, count); + if (count < len) + gb->buf_get += count; + else /* count == len */ + gb->buf_get = gb->buf_buf; + } + + return count; +} + +/* +* gs_tiocmget +*/ +static int gs_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct gs_port *port; + unsigned int mcr, msr; + unsigned int result = 0; + struct gs_dev *dev = gs_devices[tty->index]; + + if (dev == NULL) + return -EIO; + + port = dev->dev_port[0]; + if (port == NULL) + return -EIO; + + mutex_lock(&port->mutex_lock); + mcr = port->mcr; + msr = port->msr; + + result = ((mcr & MCR_RTS) ? TIOCM_RTS : 0) + | ((mcr & MCR_DTR) ? TIOCM_DTR : 0) + | ((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) + | ((msr & MSR_CD) ? TIOCM_CD : 0) + | ((msr & MSR_RI) ? TIOCM_RI : 0) + | ((msr & MSR_DSR) ? TIOCM_DSR : 0) + | ((msr & MSR_CTS) ? TIOCM_CTS : 0); + + mutex_unlock(&port->mutex_lock); + return result; +} + +/* +* gs_tiocmset +*/ +static int gs_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct gs_port *port; + unsigned int mcr; + unsigned int msr; + struct gs_dev *dev = gs_devices[tty->index]; + + if (dev == NULL) + return -EIO; + port = dev->dev_port[0]; + + if (port == NULL) + return -EIO; + + mcr = port->mcr; + msr = port->msr; + if (dev->configured != SERIAL_CONFIGURED) + return -EIO; + + set &= TIOCM_DSR | TIOCM_RI | TIOCM_CD | TIOCM_CTS; + + if (set & TIOCM_DSR) + msr |= MSR_DSR; + if (set & TIOCM_RI) + msr |= MSR_RI; + if (set & TIOCM_CD) + msr |= MSR_CD; + if (set & TIOCM_CTS) + msr |= MSR_CTS; + + clear &= TIOCM_DSR | TIOCM_RI | TIOCM_CD | TIOCM_CTS; + + if (clear & TIOCM_RI) + msr &= ~MSR_RI; + if (clear & TIOCM_DSR) + msr &= ~MSR_DSR; + if (clear & TIOCM_CD) + msr &= ~MSR_CD; + if (clear & TIOCM_CTS) + msr &= ~MSR_CTS; + + mutex_lock(&port->mutex_lock); + port->mcr = mcr; + port->msr = msr; + + if (port->prev_msr != port->msr) { + send_notify_data(dev->dev_notify_ep, dev->notify_req); + port->prev_msr = port->msr; + } + mutex_unlock(&port->mutex_lock); + + return 0; +} diff --git a/drivers/usb/function/ums.c b/drivers/usb/function/ums.c new file mode 100644 index 00000000000..509387fcf01 --- /dev/null +++ b/drivers/usb/function/ums.c @@ -0,0 +1,469 @@ +/* drivers/usb/function/ums.c + * + * Function Device for USB Mass Storage + * + * Copyright (C) 2007 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * +*/ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include "usb_function.h" + +#if 1 +#define DBG(x...) do {} while (0) +#else +#define DBG(x...) printk(x) +#endif + +#define TXN_MAX 4096 + +/* UMS setup class requests */ +#define USB_BULK_GET_MAX_LUN_REQUEST 0xFE +#define USB_BULK_RESET_REQUEST 0xFF + +/* number of rx and tx requests to allocate */ +#define RX_REQ_MAX 4 +#define TX_REQ_MAX 4 + +/* FIXME - add ioctl() support for LUN count */ +int lun_count = 1; + +struct ums_context +{ + int online; + int error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + spinlock_t lock; + + struct usb_endpoint *out; + struct usb_endpoint *in; + + struct list_head tx_idle; + struct list_head rx_idle; + struct list_head rx_done; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + + /* the request we're currently reading from */ + struct usb_request *read_req; + unsigned char *read_buf; +}; + +static struct ums_context _context; + +static inline int _lock(atomic_t *excl) +{ + if(atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void req_put(struct ums_context *ctxt, struct list_head *head, struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&ctxt->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *req_get(struct ums_context *ctxt, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&ctxt->lock, flags); + if(list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&ctxt->lock, flags); + return req; +} + +static void ums_complete_in(struct usb_endpoint *ept, struct usb_request *req) +{ + struct ums_context *ctxt = req->context; + + DBG("ums_complete_in length: %d, actual: %d \n", req->length, req->actual); + + if(req->status != 0) + ctxt->error = 1; + + req_put(ctxt, &ctxt->tx_idle, req); + + wake_up(&ctxt->write_wq); +} + +static void ums_complete_out(struct usb_endpoint *ept, struct usb_request *req) +{ + struct ums_context *ctxt = req->context; + + DBG("ums_complete_out length: %d, actual: %d \n", req->length, req->actual); + + if(req->status != 0) { + ctxt->error = 1; + req_put(ctxt, &ctxt->rx_idle, req); + } else { + req_put(ctxt, &ctxt->rx_done, req); + } + + wake_up(&ctxt->read_wq); +} + +static ssize_t ums_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct ums_context *ctxt = &_context; + struct usb_request *req; + int r = count, xfer; + int ret; + + DBG("ums_read(%d)\n", count); + + if(_lock(&ctxt->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while(!(ctxt->online || ctxt->error)) { + DBG("ums_read: waiting for online state\n"); + ret = wait_event_interruptible(ctxt->read_wq, (ctxt->online || ctxt->error)); + if(ret < 0) { + _unlock(&ctxt->read_excl); + return ret; + } + } + + if(ctxt->error) { + r = -EIO; + goto fail; + } + + /* if we have idle read requests, get them queued */ + if((req = req_get(ctxt, &ctxt->rx_idle))) { + req->length = count; + ret = usb_ept_queue_xfer(ctxt->out, req); + if(ret < 0) { + DBG("ums_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + ctxt->error = 1; + req_put(ctxt, &ctxt->rx_idle, req); + goto fail; + } else { + DBG("rx %p queue\n", req); + } + } else { + DBG("req_get failed!\n"); + goto fail; + } + + /* wait for a request to complete */ + req = 0; + ret = wait_event_interruptible(ctxt->read_wq, + ((req = req_get(ctxt, &ctxt->rx_done)) || ctxt->error)); + + if(req != 0) { + ctxt->read_req = req; + ctxt->read_buf = req->buf; + DBG("rx %p %d\n", req, req->actual); + + xfer = req->actual; + if (xfer > count) { + xfer = count; + } + r = xfer; + + if (xfer > 0) { + DBG("copy_to_user %d bytes\n", xfer); + if(copy_to_user(buf, ctxt->read_buf, xfer)) { + r = -EFAULT; + } + + } + req_put(ctxt, &ctxt->rx_idle, ctxt->read_req); + ctxt->read_req = 0; + } else { + r = ret; + } + +fail: + _unlock(&ctxt->read_excl); + DBG("ums_read returning %d\n", r); + return r; +} + +static ssize_t ums_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct ums_context *ctxt = &_context; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + DBG("ums_write(%d)\n", count); + + if(_lock(&ctxt->write_excl)) + return -EBUSY; + + while(count >= 0) { + if(ctxt->error) { + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(ctxt->write_wq, + ((req = req_get(ctxt, &ctxt->tx_idle)) || ctxt->error)); + + if(ret < 0) { + r = ret; + break; + } + + if(req != 0) { + xfer = count > TXN_MAX ? TXN_MAX : count; + if(copy_from_user(req->buf, buf, xfer)){ + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ept_queue_xfer(ctxt->in, req); + if(ret < 0) { + DBG("ums_write: xfer error %d\n", ret); + ctxt->error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + if (count == 0) { + break; + } + } + } + + + if(req) + req_put(ctxt, &ctxt->tx_idle, req); + + _unlock(&ctxt->write_excl); + DBG("ums_write returning %d\n", r); + return r; +} + +static int ums_open(struct inode *ip, struct file *fp) +{ + struct ums_context *ctxt = &_context; + + if(_lock(&ctxt->open_excl)) + return -EBUSY; + + /* clear the error latch */ + ctxt->error = 0; + + return 0; +} + +static int ums_release(struct inode *ip, struct file *fp) +{ + struct ums_context *ctxt = &_context; + + _unlock(&ctxt->open_excl); + return 0; +} + +static struct file_operations ums_fops = { + .owner = THIS_MODULE, + .read = ums_read, + .write = ums_write, + .open = ums_open, + .release = ums_release, +}; + +static struct miscdevice ums_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "android_ums", + .fops = &ums_fops, +}; + +static void ums_bind(struct usb_endpoint **ept, void *_ctxt) +{ + struct ums_context *ctxt = _ctxt; + struct usb_request *req; + int n; + + ctxt->out = ept[0]; + ctxt->in = ept[1]; + + DBG("ums_bind() %p, %p\n", ctxt->out, ctxt->in); + + for(n = 0; n < RX_REQ_MAX; n++) { + req = usb_ept_alloc_req(ctxt->out, 4096); + if(req == 0) goto fail; + req->context = ctxt; + req->complete = ums_complete_out; + req_put(ctxt, &ctxt->rx_idle, req); + } + + for(n = 0; n < TX_REQ_MAX; n++) { + req = usb_ept_alloc_req(ctxt->in, 4096); + if(req == 0) goto fail; + req->context = ctxt; + req->complete = ums_complete_in; + req_put(ctxt, &ctxt->tx_idle, req); + } + + printk("ums_bind() allocated %d rx and %d tx requests\n", + RX_REQ_MAX, TX_REQ_MAX); + + misc_register(&ums_device); + return; + +fail: + printk("ums_bind() could not allocate requests\n"); + + /* XXX release any we did allocate */ +} + +static int ums_setup(struct usb_ctrlrequest* req, void* buf, int len, void *_ctxt) +{ + if ((req->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + if (req->bRequest == USB_BULK_GET_MAX_LUN_REQUEST) { + if ((req->bRequestType & USB_DIR_IN) != USB_DIR_IN + || req->wValue != 0 || req->wIndex != 0) + return -1; + + ((u8*)buf)[0] = lun_count - 1; + printk("USB_BULK_GET_MAX_LUN_REQUEST returning %d\n", lun_count - 1); + return 1; + } else if (req->bRequest == USB_BULK_RESET_REQUEST) { + if ((req->bRequestType & USB_DIR_OUT) != USB_DIR_IN + || req->wValue != 0 || req->wIndex != 0) + return -1; + + /* FIXME - I'm not sure what to do here */ + printk("USB_BULK_RESET_REQUEST\n"); + return 0; + } + } + + return -1; +} + +static void ums_configure(int configured, void *_ctxt) +{ + struct ums_context *ctxt = _ctxt; + struct usb_request *req; + + DBG("ums_configure() %d\n", configured); + + if(configured) { + ctxt->online = 1; + + /* if we have a stale request being read, recycle it */ + ctxt->read_buf = 0; + if(ctxt->read_req) { + req_put(ctxt, &ctxt->rx_idle, ctxt->read_req); + ctxt->read_req = 0; + } + + /* retire any completed rx requests from previous session */ + while((req = req_get(ctxt, &ctxt->rx_done))) { + req_put(ctxt, &ctxt->rx_idle, req); + } + + } else { + ctxt->online = 0; + ctxt->error = 1; + } + + /* readers may be blocked waiting for us to go online */ + wake_up(&ctxt->read_wq); +} + +static struct usb_function usb_func_ums = { + .bind = ums_bind, + .configure = ums_configure, + .setup = ums_setup, + + .name = "ums", + .context = &_context, + + .ifc_class = USB_CLASS_MASS_STORAGE, + .ifc_subclass = US_SC_SCSI, + .ifc_protocol = US_PR_BULK, + + .ifc_name = "ums", + + .ifc_ept_count = 2, + .ifc_ept_type = { EPT_BULK_OUT, EPT_BULK_IN }, +}; + +static int __init ums_init(void) +{ + struct ums_context *ctxt = &_context; + DBG("ums_init()\n"); + + spin_lock_init(&ctxt->lock); + + init_waitqueue_head(&ctxt->read_wq); + init_waitqueue_head(&ctxt->write_wq); + + atomic_set(&ctxt->open_excl, 0); + atomic_set(&ctxt->read_excl, 0); + atomic_set(&ctxt->write_excl, 0); + + INIT_LIST_HEAD(&ctxt->rx_idle); + INIT_LIST_HEAD(&ctxt->rx_done); + INIT_LIST_HEAD(&ctxt->tx_idle); + + return usb_function_register(&usb_func_ums); +} + +module_init(ums_init); diff --git a/drivers/usb/function/usb_function.h b/drivers/usb/function/usb_function.h new file mode 100644 index 00000000000..35eb257783d --- /dev/null +++ b/drivers/usb/function/usb_function.h @@ -0,0 +1,187 @@ +/* drivers/usb/function/usb_function.h + * + * USB Function Device Interface + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * 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 _DRIVERS_USB_FUNCTION_USB_FUNCTION_H_ +#define _DRIVERS_USB_FUNCTION_USB_FUNCTION_H_ + +#include +#include + +#define EPT_BULK_IN 1 +#define EPT_BULK_OUT 2 +#define EPT_INT_IN 3 + +#define USB_CONFIG_ATT_SELFPOWER_POS (6) /* self powered */ +#define USB_CONFIG_ATT_WAKEUP_POS (5) /* can wakeup */ + +struct usb_endpoint { + struct usb_info *ui; + struct msm_request *req; /* head of pending requests */ + struct msm_request *last; + unsigned flags; + + /* bit number (0-31) in various status registers + ** as well as the index into the usb_info's array + ** of all endpoints + */ + unsigned char bit; + unsigned char num; + + unsigned short max_pkt; + + unsigned ept_halted; + + /* pointers to DMA transfer list area */ + /* these are allocated from the usb_info dma space */ + struct ept_queue_head *head; + struct usb_endpoint_descriptor *ep_descriptor; + unsigned int alloced; +}; + +struct usb_request { + void *buf; /* pointer to associated data buffer */ + unsigned length; /* requested transfer length */ + int status; /* status upon completion */ + unsigned actual; /* actual bytes transferred */ + + void (*complete)(struct usb_endpoint *ep, struct usb_request *req); + void *context; + + void *device; + + struct list_head list; +}; + +struct usb_function { + /* bind() is called once when the function has had its endpoints + ** allocated, but before the bus is active. + ** + ** might be a good place to allocate some usb_request objects + */ + void (*bind)(void *); + + /* unbind() is called when the function is being removed. + ** it is illegal to call and usb_ept_* hooks at this point + ** and all endpoints must be released. + */ + void (*unbind)(void *); + + /* configure() is called when the usb client has been configured + ** by the host and again when the device is unconfigured (or + ** when the client is detached) + ** + ** currently called from interrupt context. + */ + void (*configure)(int configured, void *); + void (*disconnect)(void *); + + /* setup() is called to allow functions to handle class and vendor + ** setup requests. If the request is unsupported or can not be handled, + ** setup() should return -1. + ** For OUT requests, buf will point to a buffer to data received in the + ** request's data phase, and len will contain the length of the data. + ** setup() should return 0 after handling an OUT request successfully. + ** for IN requests, buf will contain a pointer to a buffer for setup() + ** to write data to, and len will be the maximum size of the data to + ** be written back to the host. + ** After successfully handling an IN request, setup() should return + ** the number of bytes written to buf that should be sent in the + ** response to the host. + */ + int (*setup)(struct usb_ctrlrequest *req, void *buf, + int len, void *); + + int (*set_interface)(int ifc_num, int alt_set, void *_ctxt); + int (*get_interface)(int ifc_num, void *ctxt); + /* driver name */ + const char *name; + void *context; + + /* interface class/subclass/protocol for descriptor */ + unsigned char ifc_class; + unsigned char ifc_subclass; + unsigned char ifc_protocol; + + /* name string for descriptor */ + const char *ifc_name; + + /* number of needed endpoints and their types */ + unsigned char ifc_ept_count; + unsigned char ifc_ept_type[8]; + + /* if the endpoint is disabled, its interface will not be + ** included in the configuration descriptor + */ + unsigned char disabled; + + struct usb_descriptor_header **fs_descriptors; + struct usb_descriptor_header **hs_descriptors; + + struct usb_request *ep0_out_req, *ep0_in_req; + struct usb_endpoint *ep0_out, *ep0_in; +}; + +int usb_function_register(struct usb_function *driver); +int usb_function_unregister(struct usb_function *driver); + +int usb_msm_get_speed(void); +void usb_configure_endpoint(struct usb_endpoint *ep, + struct usb_endpoint_descriptor *ep_desc); +int usb_remote_wakeup(void); +/* To allocate endpoint from function driver*/ +struct usb_endpoint *usb_alloc_endpoint(unsigned direction); +int usb_free_endpoint(struct usb_endpoint *ept); +/* To enable endpoint from frunction driver*/ +void usb_ept_enable(struct usb_endpoint *ept, int yes); +int usb_msm_get_next_ifc_number(struct usb_function *); +int usb_msm_get_next_strdesc_id(char *); +void usb_msm_enable_iad(void); + +void usb_function_enable(const char *function, int enable); + +/* Allocate a USB request. +** Must be called from a context that can sleep. +** If bufsize is nonzero, req->buf will be allocated for +** you and free'd when the request is free'd. Otherwise +** it is your responsibility to provide. +*/ +struct usb_request *usb_ept_alloc_req(struct usb_endpoint *ept, unsigned bufsize); +void usb_ept_free_req(struct usb_endpoint *ept, struct usb_request *req); + +/* safely callable from any context +** returns 0 if successfully queued and sets req->status = -EBUSY +** req->status will change to a different value upon completion +** (0 for success, -EIO, -ENODEV, etc for error) +*/ +int usb_ept_queue_xfer(struct usb_endpoint *ept, struct usb_request *req); +int usb_ept_flush(struct usb_endpoint *ept); +int usb_ept_get_max_packet(struct usb_endpoint *ept); +int usb_ept_cancel_xfer(struct usb_endpoint *ept, struct usb_request *_req); +void usb_ept_fifo_flush(struct usb_endpoint *ept); +int usb_ept_set_halt(struct usb_endpoint *ept); +int usb_ept_clear_halt(struct usb_endpoint *ept); +struct device *usb_get_device(void); +struct usb_endpoint *usb_ept_find(struct usb_endpoint **ept, int type); +struct usb_function *usb_ept_get_function(struct usb_endpoint *ept); +int usb_ept_is_stalled(struct usb_endpoint *ept); +void usb_request_set_buffer(struct usb_request *req, void *buf, dma_addr_t dma); +void usb_free_endpoint_all_req(struct usb_endpoint *ep); +void usb_remove_function_driver(struct usb_function *func); +int usb_remote_wakeup(void); +#endif diff --git a/drivers/usb/function/zero.c b/drivers/usb/function/zero.c new file mode 100644 index 00000000000..449bcbfecea --- /dev/null +++ b/drivers/usb/function/zero.c @@ -0,0 +1,120 @@ +/* driver/usb/function/zero.c + * + * Zero Function Device - A Trivial Data Source + * + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include "usb_function.h" + +struct zero_context +{ + struct usb_endpoint *in; + struct usb_request *req0; + struct usb_request *req1; +}; + +static struct zero_context _context; + +static void zero_bind(struct usb_endpoint **ept, void *_ctxt) +{ + struct zero_context *ctxt = _ctxt; + ctxt->in = ept[0]; + printk(KERN_INFO "zero_bind() %p\n", ctxt->in); + + ctxt->req0 = usb_ept_alloc_req(ctxt->in, 4096); + ctxt->req1 = usb_ept_alloc_req(ctxt->in, 4096); + + memset(ctxt->req0->buf, 0, 4096); + memset(ctxt->req1->buf, 0, 4096); +} + +static void zero_unbind(void *_ctxt) +{ + struct zero_context *ctxt = _ctxt; + printk(KERN_INFO "null_unbind()\n"); + if (ctxt->req0) { + usb_ept_free_req(ctxt->in, ctxt->req0); + ctxt->req0 = 0; + } + if (ctxt->req1) { + usb_ept_free_req(ctxt->in, ctxt->req1); + ctxt->req1 = 0; + } + ctxt->in = 0; +} + +static void zero_queue_in(struct zero_context *ctxt, struct usb_request *req); + +static void zero_in_complete(struct usb_endpoint *ept, struct usb_request *req) +{ + struct zero_context *ctxt = req->context; + unsigned char *data = req->buf; + + if (req->status != -ENODEV) + zero_queue_in(ctxt, req); +} + +static void zero_queue_in(struct zero_context *ctxt, struct usb_request *req) +{ + req->complete = zero_in_complete; + req->context = ctxt; + req->length = 4096; + + usb_ept_queue_xfer(ctxt->in, req); +} + +static void zero_configure(int configured, void *_ctxt) +{ + struct zero_context *ctxt = _ctxt; + printk(KERN_INFO "zero_configure() %d\n", configured); + + if (configured) { + zero_queue_in(ctxt, ctxt->req0); + zero_queue_in(ctxt, ctxt->req1); + } else { + /* all pending requests will be canceled */ + } +} + +static struct usb_function usb_func_zero = { + .bind = zero_bind, + .unbind = zero_unbind, + .configure = zero_configure, + + .name = "zero", + .context = &_context, + + .ifc_class = 0xff, + .ifc_subclass = 0xfe, + .ifc_protocol = 0x02, + + .ifc_name = "zero", + + .ifc_ept_count = 1, + .ifc_ept_type = { EPT_BULK_IN }, +}; + +static int __init zero_init(void) +{ + printk(KERN_INFO "zero_init()\n"); + usb_function_register(&usb_func_zero); + return 0; +} + +module_init(zero_init); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 144a8c863b3..117d3bf9b69 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -598,6 +598,24 @@ config USB_CI13XXX_MSM # LAST -- dummy/emulated controller # +config USB_GADGET_MSM_72K + boolean "MSM 72K Device Controller" + depends on ARCH_MSM + select USB_GADGET_SELECTED + select USB_GADGET_DUALSPEED + help + USB gadget driver for Qualcomm MSM 72K architecture. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "msm72k" and force all + gadget drivers to also be dynamically linked. + +config USB_MSM_72K + tristate + depends on USB_GADGET_MSM_72K + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_DUMMY_HCD boolean "Dummy HCD (DEVELOPMENT)" depends on USB=y || (USB=m && USB_GADGET=m) @@ -1064,4 +1082,84 @@ config USB_G_WEBCAM endchoice +config USB_CSW_HACK + boolean "USB Mass storage csw hack Feature" + default y + help + This csw hack feature is for increasing the performance of the mass + storage + +config MODEM_SUPPORT + boolean "modem support in generic serial function driver" + depends on USB_G_ANDROID + default y + help + This feature enables the modem functionality in the + generic serial. + adds interrupt endpoint support to send modem notifications + to host. + adds CDC descriptors to enumerate the generic serial as MODEM. + adds CDC class requests to configure MODEM line settings. + Say "y" to enable MODEM support in the generic serial driver. + +config RMNET_SMD_CTL_CHANNEL + string "RMNET control SMD channel name" + depends on USB_G_ANDROID && MSM_SMD + default "" + help + Control SMD channel for transferring QMI messages + +config RMNET_SMD_DATA_CHANNEL + string "RMNET Data SMD channel name" + depends on USB_G_ANDROID && MSM_SMD + default "" + help + Data SMD channel for transferring network data + +config RMNET_SDIO_CTL_CHANNEL + int "RMNET control SDIO channel id" + default 8 + depends on MSM_SDIO_CMUX && MSM_SDIO_DMUX + help + Control SDIO channel for transferring RMNET QMI messages + +config RMNET_SDIO_DATA_CHANNEL + int "RMNET Data SDIO channel id" + default 8 + depends on MSM_SDIO_CMUX && MSM_SDIO_DMUX + help + Data SDIO channel for transferring network data + +config RMNET_SMD_SDIO_CTL_CHANNEL + int "RMNET(sdio_smd) Control SDIO channel id" + depends on MSM_SDIO_CMUX && MSM_SDIO_DMUX + default 8 + help + Control SDIO channel for transferring QMI messages + +config RMNET_SMD_SDIO_DATA_CHANNEL + int "RMNET(sdio_smd) Data SDIO channel id" + default 8 + depends on MSM_SDIO_CMUX && MSM_SDIO_DMUX + help + Data SDIO channel for transferring network data + +config RMNET_SDIO_SMD_DATA_CHANNEL + string "RMNET(sdio_smd) Data SMD channel name" + depends on MSM_SDIO_CMUX && MSM_SDIO_DMUX + default "DATA40" + help + Data SMD channel for transferring network data + +config USB_ANDROID_RMNET_CTRL_SMD + boolean "RmNet(BAM) control over SMD driver" + depends on MSM_SMD + help + Enabling this option adds rmnet control over SMD + support to the android gadget. Rmnet is an + alternative to CDC-ECM and Windows RNDIS. + It uses QUALCOMM MSM Interface for control + transfers. This option enables only control interface. + Data interface used is BAM. + endif # USB_GADGET diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index ab17a4cadba..064960c173b 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_USB_PXA_U2O) += mv_udc.o mv_udc-y := mv_udc_core.o mv_udc_phy.o obj-$(CONFIG_USB_CI13XXX_MSM) += ci13xxx_msm.o obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o +obj-$(CONFIG_USB_MSM_72K) += msm72k_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index b13633bb684..8146af7f149 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "gadget_chips.h" @@ -45,9 +46,10 @@ #include "epautoconf.c" #include "composite.c" +#include "f_diag.c" #include "f_mass_storage.c" -#include "u_serial.c" -#include "f_acm.c" +//#include "u_serial.c" +//#include "f_acm.c" #include "f_adb.c" #include "f_mtp.c" #include "f_accessory.c" @@ -99,6 +101,7 @@ struct android_dev { struct list_head enabled_functions; struct usb_composite_dev *cdev; struct device *dev; + struct android_usb_platform_data *pdata; bool enabled; bool connected; @@ -187,6 +190,68 @@ static void android_work(struct work_struct *data) /*-------------------------------------------------------------------------*/ /* Supported functions initialization */ +char diag_clients[32]; /* enabled DIAG clients - "diag[,diag_mdm]" */ +static ssize_t clients_store( + struct device *device, struct device_attribute *attr, + const char *buff, size_t size) +{ + strncpy(diag_clients, buff, sizeof(diag_clients)); + + return size; +} + +static DEVICE_ATTR(clients, S_IWUSR, NULL, clients_store); +static struct device_attribute *diag_function_attributes[] = + { &dev_attr_clients, NULL }; + +static int diag_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return diag_setup(); +} + +static void diag_function_cleanup(struct android_usb_function *f) +{ + diag_cleanup(); +} + +static int diag_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + char *name; + char buf[32], *b; + int once = 0, err = -1; + int (*notify)(uint32_t, const char *); + + strncpy(buf, diag_clients, sizeof(buf)); + b = strim(buf); + + while (b) { + name = strsep(&b, ","); + /* Allow only first diag channel to update pid and serial no */ + if (!once++) + notify = _android_dev->pdata->update_pid_and_serial_num; + else + notify = NULL; + + if (name) { + err = diag_function_add(c, name, notify); + if (err) + pr_err("diag: Cannot open channel '%s'", name); + } + } + + return err; +} + +static struct android_usb_function diag_function = { + .name = "diag", + .init = diag_function_init, + .cleanup = diag_function_cleanup, + .bind_config = diag_function_bind_config, + .attributes = diag_function_attributes, +}; + static int adb_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) { return adb_setup(); @@ -209,7 +274,7 @@ static struct android_usb_function adb_function = { .bind_config = adb_function_bind_config, }; - +#if 0 #define MAX_ACM_INSTANCES 4 struct acm_function_config { int instances; @@ -280,7 +345,7 @@ static struct android_usb_function acm_function = { .bind_config = acm_function_bind_config, .attributes = acm_function_attributes, }; - +#endif static int mtp_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) { @@ -644,8 +709,9 @@ static struct android_usb_function accessory_function = { static struct android_usb_function *supported_functions[] = { + &diag_function, &adb_function, - &acm_function, +// &acm_function, &mtp_function, &ptp_function, &rndis_function, @@ -1104,6 +1170,19 @@ static int android_create_device(struct android_dev *dev) return 0; } +static int __devinit android_probe(struct platform_device *pdev) +{ + struct android_usb_platform_data *pdata = pdev->dev.platform_data; + struct android_dev *dev = _android_dev; + + dev->pdata = pdata; + + return 0; +} + +static struct platform_driver android_platform_driver = { + .driver = { .name = "android_usb"}, +}; static int __init init(void) { @@ -1135,6 +1214,8 @@ static int __init init(void) composite_driver.setup = android_setup; composite_driver.disconnect = android_disconnect; + platform_driver_probe(&android_platform_driver, android_probe); + return usb_composite_probe(&android_usb_driver, android_bind); } module_init(init); diff --git a/drivers/usb/gadget/ci13xxx_msm.c b/drivers/usb/gadget/ci13xxx_msm.c index 139ac941959..8a754201b98 100644 --- a/drivers/usb/gadget/ci13xxx_msm.c +++ b/drivers/usb/gadget/ci13xxx_msm.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and @@ -9,11 +9,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * */ #include @@ -64,7 +59,8 @@ static struct ci13xxx_udc_driver ci13xxx_msm_udc_driver = { .flags = CI13XXX_REGS_SHARED | CI13XXX_REQUIRE_TRANSCEIVER | CI13XXX_PULLUP_ON_VBUS | - CI13XXX_DISABLE_STREAMING, + CI13XXX_DISABLE_STREAMING | + CI13XXX_ZERO_ITC, .notify_event = ci13xxx_msm_notify_event, }; diff --git a/drivers/usb/gadget/ci13xxx_udc.c b/drivers/usb/gadget/ci13xxx_udc.c index baaf87ed768..9a03ca7f92c 100644 --- a/drivers/usb/gadget/ci13xxx_udc.c +++ b/drivers/usb/gadget/ci13xxx_udc.c @@ -318,6 +318,17 @@ static int hw_device_reset(struct ci13xxx *udc) hw_cwrite(CAP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE); hw_cwrite(CAP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); /* HW >= 2.3 */ + /* + * ITC (Interrupt Threshold Control) field is to set the maximum + * rate at which the device controller will issue interrupts. + * The maximum interrupt interval measured in micro frames. + * Valid values are 0, 1, 2, 4, 8, 16, 32, 64. The default value is + * 8 micro frames. If CPU can handle interrupts at faster rate, ITC + * can be set to lesser value to gain performance. + */ + if (udc->udc_driver->flags & CI13XXX_ZERO_ITC) + hw_cwrite(CAP_USBCMD, USBCMD_ITC_MASK, USBCMD_ITC(0)); + if (hw_cread(CAP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) { pr_err("cannot enter in device mode"); pr_err("lpm = %i", hw_bank.lpm); @@ -417,6 +428,10 @@ static int hw_ep_enable(int num, int dir, int type) data |= ENDPTCTRL_RXE; } hw_cwrite(CAP_ENDPTCTRL + num * sizeof(u32), mask, data); + + /* make sure endpoint is enabled before returning */ + mb(); + return 0; } @@ -1219,7 +1234,7 @@ static ssize_t show_registers(struct device *dev, { struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); unsigned long flags; - u32 dump[512]; + u32 *dump; unsigned i, k, n = 0; dbg_trace("[%s] %p\n", __func__, buf); @@ -1227,9 +1242,12 @@ static ssize_t show_registers(struct device *dev, dev_err(dev, "[%s] EINVAL\n", __func__); return 0; } + dump = kmalloc(2048, GFP_KERNEL); + if (dump == NULL) + return -ENOMEM; spin_lock_irqsave(udc->lock, flags); - k = hw_register_read(dump, sizeof(dump)/sizeof(u32)); + k = hw_register_read(dump, 512); spin_unlock_irqrestore(udc->lock, flags); for (i = 0; i < k; i++) { @@ -1237,7 +1255,7 @@ static ssize_t show_registers(struct device *dev, "reg[0x%04X] = 0x%08X\n", i * (unsigned)sizeof(u32), dump[i]); } - + kfree(dump); return n; } @@ -1317,6 +1335,42 @@ static ssize_t show_requests(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL); +static int ci13xxx_wakeup(struct usb_gadget *_gadget) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; + int ret = 0; + + trace(); + + spin_lock_irqsave(udc->lock, flags); + if (!udc->remote_wakeup) { + ret = -EOPNOTSUPP; + dbg_trace("remote wakeup feature is not enabled\n"); + goto out; + } + if (!hw_cread(CAP_PORTSC, PORTSC_SUSP)) { + ret = -EINVAL; + dbg_trace("port is not suspended\n"); + goto out; + } + hw_cwrite(CAP_PORTSC, PORTSC_FPR, PORTSC_FPR); +out: + spin_unlock_irqrestore(udc->lock, flags); + return ret; +} + +static ssize_t usb_remote_wakeup(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + + ci13xxx_wakeup(&udc->gadget); + + return count; +} +static DEVICE_ATTR(wakeup, S_IWUSR, 0, usb_remote_wakeup); + /** * dbg_create_files: initializes the attribute interface * @dev: device @@ -1353,8 +1407,13 @@ __maybe_unused static int dbg_create_files(struct device *dev) retval = device_create_file(dev, &dev_attr_requests); if (retval) goto rm_registers; + retval = device_create_file(dev, &dev_attr_wakeup); + if (retval) + goto rm_remote_wakeup; return 0; +rm_remote_wakeup: + device_remove_file(dev, &dev_attr_wakeup); rm_registers: device_remove_file(dev, &dev_attr_registers); rm_qheads: @@ -1391,6 +1450,7 @@ __maybe_unused static int dbg_remove_files(struct device *dev) device_remove_file(dev, &dev_attr_events); device_remove_file(dev, &dev_attr_driver); device_remove_file(dev, &dev_attr_device); + device_remove_file(dev, &dev_attr_wakeup); return 0; } @@ -1619,6 +1679,7 @@ static int _gadget_stop_activity(struct usb_gadget *gadget) udc->gadget.speed = USB_SPEED_UNKNOWN; udc->remote_wakeup = 0; udc->suspended = 0; + udc->configured = 0; spin_unlock_irqrestore(udc->lock, flags); /* flush all endpoints */ @@ -1930,6 +1991,8 @@ __acquires(udc->lock) do { hw_test_and_set_setup_guard(); memcpy(&req, &mEp->qh.ptr->setup, sizeof(req)); + /* Ensure buffer is read before acknowledging to h/w */ + mb(); } while (!hw_test_and_clear_setup_guard()); type = req.bRequestType; @@ -1991,6 +2054,10 @@ __acquires(udc->lock) break; err = isr_setup_status_phase(udc); break; + case USB_REQ_SET_CONFIGURATION: + if (type == (USB_DIR_OUT|USB_TYPE_STANDARD)) + udc->configured = !!req.wValue; + goto delegate; case USB_REQ_SET_FEATURE: if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && le16_to_cpu(req.wValue) == @@ -2104,12 +2171,15 @@ static int ep_enable(struct usb_ep *ep, else if (mEp->type == USB_ENDPOINT_XFER_ISOC) mEp->qh.ptr->cap &= ~QH_MULT; else - mEp->qh.ptr->cap &= ~QH_ZLT; + mEp->qh.ptr->cap |= QH_ZLT; mEp->qh.ptr->cap |= (mEp->ep.maxpacket << ffs_nr(QH_MAX_PKT)) & QH_MAX_PKT; mEp->qh.ptr->td.next |= TD_TERMINATE; /* needed? */ + /* complete all the updates to ept->head before enabling endpoint*/ + mb(); + /* * Enable endpoints in the HW other than ep0 as ep0 * is always enabled @@ -2467,7 +2537,8 @@ static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) if (is_active) { pm_runtime_get_sync(&_gadget->dev); hw_device_reset(udc); - hw_device_state(udc->ep0out.qh.dma); + if (udc->softconnect) + hw_device_state(udc->ep0out.qh.dma); } else { hw_device_state(0); if (udc->udc_driver->notify_event) @@ -2481,40 +2552,41 @@ static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) return 0; } -static int ci13xxx_wakeup(struct usb_gadget *_gadget) +static int ci13xxx_vbus_draw(struct usb_gadget *_gadget, unsigned mA) { struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); - unsigned long flags; - int ret = 0; - - trace(); - spin_lock_irqsave(udc->lock, flags); - if (!udc->remote_wakeup) { - ret = -EOPNOTSUPP; - dbg_trace("remote wakeup feature is not enabled\n"); - goto out; - } - if (!hw_cread(CAP_PORTSC, PORTSC_SUSP)) { - ret = -EINVAL; - dbg_trace("port is not suspended\n"); - goto out; - } - hw_cwrite(CAP_PORTSC, PORTSC_FPR, PORTSC_FPR); -out: - spin_unlock_irqrestore(udc->lock, flags); - return ret; + if (udc->transceiver) + return otg_set_power(udc->transceiver, mA); + return -ENOTSUPP; } -static int ci13xxx_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +static int ci13xxx_pullup(struct usb_gadget *_gadget, int is_active) { struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; - if (udc->transceiver) - return otg_set_power(udc->transceiver, mA); - return -ENOTSUPP; + spin_lock_irqsave(udc->lock, flags); + udc->softconnect = is_active; + if (((udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) && + !udc->vbus_active) || !udc->driver) { + spin_unlock_irqrestore(udc->lock, flags); + return 0; + } + spin_unlock_irqrestore(udc->lock, flags); + + if (is_active) { + hw_device_state(udc->ep0out.qh.dma); + } else { + hw_device_state(0); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_STOPPED_EVENT); + } + return 0; } + /** * Device operations part of the API to the USB controller hardware, * which don't involve endpoints (or i/o) @@ -2524,6 +2596,7 @@ static const struct usb_gadget_ops usb_gadget_ops = { .vbus_session = ci13xxx_vbus_session, .wakeup = ci13xxx_wakeup, .vbus_draw = ci13xxx_vbus_draw, + .pullup = ci13xxx_pullup, }; /** @@ -2627,6 +2700,7 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, /* bind gadget */ driver->driver.bus = NULL; udc->gadget.dev.driver = &driver->driver; + udc->softconnect = 1; spin_unlock_irqrestore(udc->lock, flags); retval = bind(&udc->gadget); /* MAY SLEEP */ @@ -2649,6 +2723,9 @@ int usb_gadget_probe_driver(struct usb_gadget_driver *driver, } } + if (!udc->softconnect) + goto done; + retval = hw_device_state(udc->ep0out.qh.dma); if (retval) pm_runtime_put_sync(&udc->gadget.dev); diff --git a/drivers/usb/gadget/ci13xxx_udc.h b/drivers/usb/gadget/ci13xxx_udc.h index 23707775cb4..27af8aa1917 100644 --- a/drivers/usb/gadget/ci13xxx_udc.h +++ b/drivers/usb/gadget/ci13xxx_udc.h @@ -108,6 +108,7 @@ struct ci13xxx_udc_driver { #define CI13XXX_REQUIRE_TRANSCEIVER BIT(1) #define CI13XXX_PULLUP_ON_VBUS BIT(2) #define CI13XXX_DISABLE_STREAMING BIT(3) +#define CI13XXX_ZERO_ITC BIT(4) #define CI13XXX_CONTROLLER_RESET_EVENT 0 #define CI13XXX_CONTROLLER_STOPPED_EVENT 1 @@ -131,11 +132,13 @@ struct ci13xxx { u8 remote_wakeup; /* Is remote wakeup feature enabled by the host? */ u8 suspended; /* suspended by the host */ + u8 configured; /* is device configured */ u8 test_mode; /* the selected test mode */ struct usb_gadget_driver *driver; /* 3rd party gadget driver */ struct ci13xxx_udc_driver *udc_driver; /* device controller driver */ int vbus_active; /* is VBUS active */ + int softconnect; /* is pull-up enable allowed */ struct otg_transceiver *transceiver; /* Transceiver struct */ }; @@ -189,6 +192,8 @@ struct ci13xxx { #define USBMODE_CM_HOST (0x03UL << 0) #define USBMODE_SLOM BIT(3) #define USBMODE_SDIS BIT(4) +#define USBCMD_ITC(n) (n << 16) /* n = 0, 1, 2, 4, 8, 16, 32, 64 */ +#define USBCMD_ITC_MASK (0xFF << 16) /* ENDPTCTRL */ #define ENDPTCTRL_RXS BIT(0) diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c index 68b1a8e86c0..3fd12b1fe53 100644 --- a/drivers/usb/gadget/f_acm.c +++ b/drivers/usb/gadget/f_acm.c @@ -5,6 +5,7 @@ * Copyright (C) 2008 by David Brownell * Copyright (C) 2008 by Nokia Corporation * Copyright (C) 2009 by Samsung Electronics + * Copyright (c) 2011 Code Aurora Forum. All rights reserved. * Author: Michal Nazarewicz (m.nazarewicz@samsung.com) * * This software is distributed under the terms of the GNU General @@ -17,6 +18,7 @@ #include #include #include +#include #include "u_serial.h" #include "gadget_chips.h" @@ -49,6 +51,7 @@ struct f_acm { struct gserial port; u8 ctrl_id, data_id; u8 port_num; + enum transport_type transport; u8 pending; @@ -83,6 +86,17 @@ struct f_acm { #define ACM_CTRL_DCD (1 << 0) }; +static unsigned int no_tty_ports; +static unsigned int no_sdio_ports; +static unsigned int no_smd_ports; +static unsigned int nr_ports; + +static struct port_info { + enum transport_type transport; + unsigned port_num; + unsigned client_port_num; +} gacm_ports[GSERIAL_NO_PORTS]; + static inline struct f_acm *func_to_acm(struct usb_function *f) { return container_of(f, struct f_acm, port.func); @@ -93,6 +107,95 @@ static inline struct f_acm *port_to_acm(struct gserial *p) return container_of(p, struct f_acm, port); } +static char *transport_to_str(enum transport_type t) +{ + switch (t) { + case USB_GADGET_FSERIAL_TRANSPORT_TTY: + return "TTY"; + case USB_GADGET_FSERIAL_TRANSPORT_SDIO: + return "SDIO"; + case USB_GADGET_FSERIAL_TRANSPORT_SMD: + return "SMD"; + } + + return "NONE"; +} + +static int gport_setup(struct usb_configuration *c) +{ + int ret = 0; + + pr_debug("%s: no_tty_ports:%u no_sdio_ports: %u nr_ports:%u\n", + __func__, no_tty_ports, no_sdio_ports, nr_ports); + + if (no_tty_ports) + ret = gserial_setup(c->cdev->gadget, no_tty_ports); + if (no_sdio_ports) + ret = gsdio_setup(c->cdev->gadget, no_sdio_ports); + if (no_smd_ports) + ret = gsmd_setup(c->cdev->gadget, no_smd_ports); + + return ret; +} + +static int gport_connect(struct f_acm *acm) +{ + unsigned port_num; + + port_num = gacm_ports[acm->port_num].client_port_num; + + + pr_debug("%s: transport:%s f_acm:%p gserial:%p port_num:%d cl_port_no:%d\n", + __func__, transport_to_str(acm->transport), + acm, &acm->port, acm->port_num, port_num); + + switch (acm->transport) { + case USB_GADGET_FSERIAL_TRANSPORT_TTY: + gserial_connect(&acm->port, port_num); + break; + case USB_GADGET_FSERIAL_TRANSPORT_SDIO: + gsdio_connect(&acm->port, port_num); + break; + case USB_GADGET_FSERIAL_TRANSPORT_SMD: + gsmd_connect(&acm->port, port_num); + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + transport_to_str(acm->transport)); + return -ENODEV; + } + + return 0; +} + +static int gport_disconnect(struct f_acm *acm) +{ + unsigned port_num; + + port_num = gacm_ports[acm->port_num].client_port_num; + + pr_debug("%s: transport:%s f_acm:%p gserial:%p port_num:%d cl_pno:%d\n", + __func__, transport_to_str(acm->transport), + acm, &acm->port, acm->port_num, port_num); + + switch (acm->transport) { + case USB_GADGET_FSERIAL_TRANSPORT_TTY: + gserial_disconnect(&acm->port); + break; + case USB_GADGET_FSERIAL_TRANSPORT_SDIO: + gsdio_disconnect(&acm->port, port_num); + break; + case USB_GADGET_FSERIAL_TRANSPORT_SMD: + gsmd_disconnect(&acm->port, port_num); + break; + default: + pr_err("%s: Un-supported transport:%s\n", __func__, + transport_to_str(acm->transport)); + return -ENODEV; + } + + return 0; +} /*-------------------------------------------------------------------------*/ /* notification endpoint uses smallish and infrequent fixed-size messages */ @@ -333,8 +436,7 @@ static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) /* SET_LINE_CODING ... just read and save what the host sends */ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) | USB_CDC_REQ_SET_LINE_CODING: - if (w_length != sizeof(struct usb_cdc_line_coding) - || w_index != acm->ctrl_id) + if (w_length != sizeof(struct usb_cdc_line_coding)) goto invalid; value = w_length; @@ -345,8 +447,6 @@ static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) /* GET_LINE_CODING ... return what host sent, or initial value */ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) | USB_CDC_REQ_GET_LINE_CODING: - if (w_index != acm->ctrl_id) - goto invalid; value = min_t(unsigned, w_length, sizeof(struct usb_cdc_line_coding)); @@ -356,9 +456,6 @@ static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) /* SET_CONTROL_LINE_STATE ... save what the host sent */ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) | USB_CDC_REQ_SET_CONTROL_LINE_STATE: - if (w_index != acm->ctrl_id) - goto invalid; - value = 0; /* FIXME we should not allow data to flow until the @@ -366,6 +463,12 @@ static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) * that bit, we should return to that no-flow state. */ acm->port_handshake_bits = w_value; + if (acm->port.notify_modem) { + unsigned port_num = + gacm_ports[acm->port_num].client_port_num; + + acm->port.notify_modem(&acm->port, port_num, w_value); + } break; default: @@ -415,7 +518,7 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) } else if (intf == acm->data_id) { if (acm->port.in->driver_data) { DBG(cdev, "reset acm ttyGS%d\n", acm->port_num); - gserial_disconnect(&acm->port); + gport_disconnect(acm); } else { DBG(cdev, "activate acm ttyGS%d\n", acm->port_num); } @@ -423,7 +526,7 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) acm->hs.in, acm->fs.in); acm->port.out_desc = ep_choose(cdev->gadget, acm->hs.out, acm->fs.out); - gserial_connect(&acm->port, acm->port_num); + gport_connect(acm); } else return -EINVAL; @@ -437,7 +540,7 @@ static void acm_disable(struct usb_function *f) struct usb_composite_dev *cdev = f->config->cdev; DBG(cdev, "acm ttyGS%d deactivated\n", acm->port_num); - gserial_disconnect(&acm->port); + gport_disconnect(acm); usb_ep_disable(acm->notify); acm->notify->driver_data = NULL; } @@ -568,6 +671,15 @@ static int acm_send_break(struct gserial *port, int duration) return acm_notify_serial_state(acm); } +static int acm_send_modem_ctrl_bits(struct gserial *port, int ctrl_bits) +{ + struct f_acm *acm = port_to_acm(port); + + acm->serial_state = ctrl_bits; + + return acm_notify_serial_state(acm); +} + /*-------------------------------------------------------------------------*/ /* ACM function driver setup/binding */ @@ -764,12 +876,14 @@ int acm_bind_config(struct usb_configuration *c, u8 port_num) spin_lock_init(&acm->lock); acm->port_num = port_num; + acm->transport = gacm_ports[port_num].transport; acm->port.connect = acm_connect; acm->port.disconnect = acm_disconnect; acm->port.send_break = acm_send_break; + acm->port.send_modem_ctrl_bits = acm_send_modem_ctrl_bits; - acm->port.func.name = kasprintf(GFP_KERNEL, "acm%u", port_num); + acm->port.func.name = kasprintf(GFP_KERNEL, "acm%u", port_num + 1); if (!acm->port.func.name) { kfree(acm); return -ENOMEM; @@ -787,3 +901,117 @@ int acm_bind_config(struct usb_configuration *c, u8 port_num) kfree(acm); return status; } + +#ifdef CONFIG_USB_ANDROID_ACM +#include + +static struct acm_platform_data *acm_pdata; + +static int acm_probe(struct platform_device *pdev) +{ + acm_pdata = pdev->dev.platform_data; + return 0; +} + +static struct platform_driver acm_platform_driver = { + .driver = { .name = "acm", }, + .probe = acm_probe, +}; + +int acm1_function_bind_config(struct usb_configuration *c) +{ + int ret = acm_bind_config(c, 0); + if (ret == 0) + gport_setup(c); + return ret; +} + +int acm2_function_bind_config(struct usb_configuration *c) +{ + int ret = acm_bind_config(c, 1); + + return ret; +} + +static struct android_usb_function acm1_function = { + .name = "acm1", + .bind_config = acm1_function_bind_config, +}; + +static struct android_usb_function acm2_function = { + .name = "acm2", + .bind_config = acm2_function_bind_config, +}; + +static int facm_remove(struct platform_device *pdev) +{ + gserial_cleanup(); + + return 0; +} + +static struct platform_driver usb_facm = { + .remove = facm_remove, + .driver = { + .name = "usb_facm", + .owner = THIS_MODULE, + }, +}; + +static int __init facm_probe(struct platform_device *pdev) +{ + struct usb_gadget_facm_pdata *pdata = pdev->dev.platform_data; + int i; + + dev_dbg(&pdev->dev, "%s: probe\n", __func__); + + if (!pdata) + goto probe_android_register; + + for (i = 0; i < GSERIAL_NO_PORTS; i++) { + gacm_ports[i].transport = pdata->transport[i]; + gacm_ports[i].port_num = i; + + switch (gacm_ports[i].transport) { + case USB_GADGET_FSERIAL_TRANSPORT_TTY: + gacm_ports[i].client_port_num = no_tty_ports; + no_tty_ports++; + break; + case USB_GADGET_FSERIAL_TRANSPORT_SDIO: + gacm_ports[i].client_port_num = no_sdio_ports; + no_sdio_ports++; + break; + case USB_GADGET_FSERIAL_TRANSPORT_SMD: + gacm_ports[i].client_port_num = no_smd_ports; + no_smd_ports++; + break; + default: + pr_err("%s: Un-supported transport transport: %u\n", + __func__, gacm_ports[i].transport); + return -ENODEV; + } + + nr_ports++; + } + + pr_info("%s:gport:tty_ports:%u sdio_ports:%u " + "smd_ports:%u nr_ports:%u\n", + __func__, no_tty_ports, no_sdio_ports, + no_smd_ports, nr_ports); + +probe_android_register: + android_register_function(&acm1_function); + android_register_function(&acm2_function); + + return 0; +} + +static int __init init(void) +{ + printk(KERN_INFO "f_acm init\n"); + + return platform_driver_probe(&usb_facm, facm_probe); +} +module_init(init); + +#endif /* CONFIG_USB_ANDROID_ACM */ diff --git a/drivers/usb/gadget/f_diag.c b/drivers/usb/gadget/f_diag.c new file mode 100644 index 00000000000..53660186bb2 --- /dev/null +++ b/drivers/usb/gadget/f_diag.c @@ -0,0 +1,752 @@ +/* drivers/usb/gadget/f_diag.c + * Diag Function Device - Route ARM9 and ARM11 DIAG messages + * between HOST and DEVICE. + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +static DEFINE_SPINLOCK(ch_lock); +static LIST_HEAD(usb_diag_ch_list); + +static struct usb_interface_descriptor intf_desc = { + .bLength = sizeof intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0xFF, + .bInterfaceProtocol = 0xFF, +}; + +static struct usb_endpoint_descriptor hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; +static struct usb_endpoint_descriptor fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +static struct usb_descriptor_header *fs_diag_desc[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &fs_bulk_in_desc, + (struct usb_descriptor_header *) &fs_bulk_out_desc, + NULL, + }; +static struct usb_descriptor_header *hs_diag_desc[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &hs_bulk_in_desc, + (struct usb_descriptor_header *) &hs_bulk_out_desc, + NULL, +}; + +/** + * struct diag_context - USB diag function driver private structure + * @function: function structure for USB interface + * @out: USB OUT endpoint struct + * @in: USB IN endpoint struct + * @in_desc: USB IN endpoint descriptor struct + * @out_desc: USB OUT endpoint descriptor struct + * @read_pool: List of requests used for Rx (OUT ep) + * @write_pool: List of requests used for Tx (IN ep) + * @config_work: Work item schedule after interface is configured to notify + * CONNECT event to diag char driver and updating product id + * and serial number to MODEM/IMEM. + * @lock: Spinlock to proctect read_pool, write_pool lists + * @cdev: USB composite device struct + * @ch: USB diag channel + * + */ +struct diag_context { + struct usb_function function; + struct usb_ep *out; + struct usb_ep *in; + struct usb_endpoint_descriptor *in_desc; + struct usb_endpoint_descriptor *out_desc; + struct list_head read_pool; + struct list_head write_pool; + struct work_struct config_work; + spinlock_t lock; + unsigned configured; + struct usb_composite_dev *cdev; + int (*update_pid_and_serial_num)(uint32_t, const char *); + struct usb_diag_ch ch; + + /* pkt counters */ + unsigned long dpkts_tolaptop; + unsigned long dpkts_tomodem; + unsigned dpkts_tolaptop_pending; +}; + +static inline struct diag_context *func_to_diag(struct usb_function *f) +{ + return container_of(f, struct diag_context, function); +} + +static void usb_config_work_func(struct work_struct *work) +{ + struct diag_context *ctxt = container_of(work, + struct diag_context, config_work); + struct usb_composite_dev *cdev = ctxt->cdev; + struct usb_gadget_strings *table; + struct usb_string *s; + + if (ctxt->ch.notify) + ctxt->ch.notify(ctxt->ch.priv, USB_DIAG_CONNECT, NULL); + + if (!ctxt->update_pid_and_serial_num) + return; + + /* pass on product id and serial number to dload */ + if (!cdev->desc.iSerialNumber) { + ctxt->update_pid_and_serial_num( + cdev->desc.idProduct, 0); + return; + } + + /* + * Serial number is filled by the composite driver. So + * it is fair enough to assume that it will always be + * found at first table of strings. + */ + table = *(cdev->driver->strings); + for (s = table->strings; s && s->s; s++) + if (s->id == cdev->desc.iSerialNumber) { + ctxt->update_pid_and_serial_num( + cdev->desc.idProduct, s->s); + break; + } +} + +static void diag_write_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct diag_context *ctxt = ep->driver_data; + struct diag_request *d_req = req->context; + unsigned long flags; + + ctxt->dpkts_tolaptop_pending--; + + if (!req->status) { + if ((req->length >= ep->maxpacket) && + ((req->length % ep->maxpacket) == 0)) { + ctxt->dpkts_tolaptop_pending++; + req->length = 0; + d_req->actual = req->actual; + d_req->status = req->status; + /* Queue zero length packet */ + usb_ep_queue(ctxt->in, req, GFP_ATOMIC); + return; + } + } + + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->write_pool); + if (req->length != 0) { + d_req->actual = req->actual; + d_req->status = req->status; + } + spin_unlock_irqrestore(&ctxt->lock, flags); + + if (ctxt->ch.notify) + ctxt->ch.notify(ctxt->ch.priv, USB_DIAG_WRITE_DONE, d_req); +} + +static void diag_read_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct diag_context *ctxt = ep->driver_data; + struct diag_request *d_req = req->context; + unsigned long flags; + + d_req->actual = req->actual; + d_req->status = req->status; + + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->read_pool); + spin_unlock_irqrestore(&ctxt->lock, flags); + + ctxt->dpkts_tomodem++; + + if (ctxt->ch.notify) + ctxt->ch.notify(ctxt->ch.priv, USB_DIAG_READ_DONE, d_req); +} + +/** + * usb_diag_open() - Open a diag channel over USB + * @name: Name of the channel + * @priv: Private structure pointer which will be passed in notify() + * @notify: Callback function to receive notifications + * + * This function iterates overs the available channels and returns + * the channel handler if the name matches. The notify callback is called + * for CONNECT, DISCONNECT, READ_DONE and WRITE_DONE events. + * + */ +struct usb_diag_ch *usb_diag_open(const char *name, void *priv, + void (*notify)(void *, unsigned, struct diag_request *)) +{ + struct usb_diag_ch *ch; + struct diag_context *ctxt; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&ch_lock, flags); + /* Check if we already have a channel with this name */ + list_for_each_entry(ch, &usb_diag_ch_list, list) { + if (!strcmp(name, ch->name)) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&ch_lock, flags); + + if (!found) { + ctxt = kzalloc(sizeof(*ctxt), GFP_KERNEL); + if (!ctxt) + return ERR_PTR(-ENOMEM); + + ch = &ctxt->ch; + } + + ch->name = name; + ch->priv = priv; + ch->notify = notify; + + spin_lock_irqsave(&ch_lock, flags); + list_add_tail(&ch->list, &usb_diag_ch_list); + spin_unlock_irqrestore(&ch_lock, flags); + + return ch; +} +EXPORT_SYMBOL(usb_diag_open); + +/** + * usb_diag_close() - Close a diag channel over USB + * @ch: Channel handler + * + * This function closes the diag channel. + * + */ +void usb_diag_close(struct usb_diag_ch *ch) +{ + struct diag_context *dev = container_of(ch, struct diag_context, ch); + unsigned long flags; + + spin_lock_irqsave(&ch_lock, flags); + ch->priv = NULL; + ch->notify = NULL; + /* Free-up the resources if channel is no more active */ + if (!ch->priv_usb) { + list_del(&ch->list); + kfree(dev); + } + + spin_unlock_irqrestore(&ch_lock, flags); +} +EXPORT_SYMBOL(usb_diag_close); + +/** + * usb_diag_free_req() - Free USB requests + * @ch: Channel handler + * + * This function free read and write USB requests for the interface + * associated with this channel. + * + */ +void usb_diag_free_req(struct usb_diag_ch *ch) +{ + struct diag_context *ctxt = ch->priv_usb; + struct usb_request *req; + struct list_head *act, *tmp; + + if (!ctxt) + return; + + list_for_each_safe(act, tmp, &ctxt->write_pool) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + usb_ep_free_request(ctxt->in, req); + } + + list_for_each_safe(act, tmp, &ctxt->read_pool) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + usb_ep_free_request(ctxt->out, req); + } +} +EXPORT_SYMBOL(usb_diag_free_req); + +/** + * usb_diag_alloc_req() - Allocate USB requests + * @ch: Channel handler + * @n_write: Number of requests for Tx + * @n_read: Number of requests for Rx + * + * This function allocate read and write USB requests for the interface + * associated with this channel. The actual buffer is not allocated. + * The buffer is passed by diag char driver. + * + */ +int usb_diag_alloc_req(struct usb_diag_ch *ch, int n_write, int n_read) +{ + struct diag_context *ctxt = ch->priv_usb; + struct usb_request *req; + int i; + + if (!ctxt) + return -ENODEV; + + for (i = 0; i < n_write; i++) { + req = usb_ep_alloc_request(ctxt->in, GFP_ATOMIC); + if (!req) + goto fail; + req->complete = diag_write_complete; + list_add_tail(&req->list, &ctxt->write_pool); + } + + for (i = 0; i < n_read; i++) { + req = usb_ep_alloc_request(ctxt->out, GFP_ATOMIC); + if (!req) + goto fail; + req->complete = diag_read_complete; + list_add_tail(&req->list, &ctxt->read_pool); + } + + return 0; + +fail: + usb_diag_free_req(ch); + return -ENOMEM; + +} +EXPORT_SYMBOL(usb_diag_alloc_req); + +/** + * usb_diag_read() - Read data from USB diag channel + * @ch: Channel handler + * @d_req: Diag request struct + * + * Enqueue a request on OUT endpoint of the interface corresponding to this + * channel. This function returns proper error code when interface is not + * in configured state, no Rx requests available and ep queue is failed. + * + * This function operates asynchronously. READ_DONE event is notified after + * completion of OUT request. + * + */ +int usb_diag_read(struct usb_diag_ch *ch, struct diag_request *d_req) +{ + struct diag_context *ctxt = ch->priv_usb; + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&ctxt->lock, flags); + + if (!ctxt->configured) { + spin_unlock_irqrestore(&ctxt->lock, flags); + return -EIO; + } + + if (list_empty(&ctxt->read_pool)) { + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: no requests available\n", __func__); + return -EAGAIN; + } + + req = list_first_entry(&ctxt->read_pool, struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&ctxt->lock, flags); + + req->buf = d_req->buf; + req->length = d_req->length; + req->context = d_req; + if (usb_ep_queue(ctxt->out, req, GFP_ATOMIC)) { + /* If error add the link to linked list again*/ + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->read_pool); + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: cannot queue" + " read request\n", __func__); + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL(usb_diag_read); + +/** + * usb_diag_write() - Write data from USB diag channel + * @ch: Channel handler + * @d_req: Diag request struct + * + * Enqueue a request on IN endpoint of the interface corresponding to this + * channel. This function returns proper error code when interface is not + * in configured state, no Tx requests available and ep queue is failed. + * + * This function operates asynchronously. WRITE_DONE event is notified after + * completion of IN request. + * + */ +int usb_diag_write(struct usb_diag_ch *ch, struct diag_request *d_req) +{ + struct diag_context *ctxt = ch->priv_usb; + unsigned long flags; + struct usb_request *req = NULL; + + spin_lock_irqsave(&ctxt->lock, flags); + + if (!ctxt->configured) { + spin_unlock_irqrestore(&ctxt->lock, flags); + return -EIO; + } + + if (list_empty(&ctxt->write_pool)) { + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: no requests available\n", __func__); + return -EAGAIN; + } + + req = list_first_entry(&ctxt->write_pool, struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&ctxt->lock, flags); + + req->buf = d_req->buf; + req->length = d_req->length; + req->context = d_req; + if (usb_ep_queue(ctxt->in, req, GFP_ATOMIC)) { + /* If error add the link to linked list again*/ + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->write_pool); + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: cannot queue" + " read request\n", __func__); + return -EIO; + } + + ctxt->dpkts_tolaptop++; + ctxt->dpkts_tolaptop_pending++; + + return 0; +} +EXPORT_SYMBOL(usb_diag_write); + +static void diag_function_disable(struct usb_function *f) +{ + struct diag_context *dev = func_to_diag(f); + unsigned long flags; + + DBG(dev->cdev, "diag_function_disable\n"); + + spin_lock_irqsave(&dev->lock, flags); + dev->configured = 0; + spin_unlock_irqrestore(&dev->lock, flags); + + if (dev->ch.notify) + dev->ch.notify(dev->ch.priv, USB_DIAG_DISCONNECT, NULL); + + usb_ep_disable(dev->in); + dev->in->driver_data = NULL; + + usb_ep_disable(dev->out); + dev->out->driver_data = NULL; + +} + +static int diag_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct diag_context *dev = func_to_diag(f); + struct usb_composite_dev *cdev = f->config->cdev; + unsigned long flags; + struct usb_diag_ch *ch; + int rc = 0; + + dev->in_desc = ep_choose(cdev->gadget, + &hs_bulk_in_desc, &fs_bulk_in_desc); + dev->out_desc = ep_choose(cdev->gadget, + &hs_bulk_out_desc, &fs_bulk_out_desc); + dev->in->driver_data = dev; + rc = usb_ep_enable(dev->in, dev->in_desc); + if (rc) { + ERROR(dev->cdev, "can't enable %s, result %d\n", + dev->in->name, rc); + return rc; + } + dev->out->driver_data = dev; + rc = usb_ep_enable(dev->out, dev->out_desc); + if (rc) { + ERROR(dev->cdev, "can't enable %s, result %d\n", + dev->out->name, rc); + usb_ep_disable(dev->in); + return rc; + } + schedule_work(&dev->config_work); + + list_for_each_entry(ch, &usb_diag_ch_list, list) { + struct diag_context *ctxt; + + ctxt = ch->priv_usb; + ctxt->dpkts_tolaptop = 0; + ctxt->dpkts_tomodem = 0; + ctxt->dpkts_tolaptop_pending = 0; + } + + spin_lock_irqsave(&dev->lock, flags); + dev->configured = 1; + spin_unlock_irqrestore(&dev->lock, flags); + + return rc; +} + +static void diag_function_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + struct diag_context *ctxt = func_to_diag(f); + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + + usb_free_descriptors(f->descriptors); + ctxt->ch.priv_usb = NULL; +} + +static int diag_function_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct diag_context *ctxt = func_to_diag(f); + struct usb_ep *ep; + int status = -ENODEV; + + intf_desc.bInterfaceNumber = usb_interface_id(c, f); + + ep = usb_ep_autoconfig(cdev->gadget, &fs_bulk_in_desc); + ctxt->in = ep; + ep->driver_data = ctxt; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_bulk_out_desc); + ctxt->out = ep; + ep->driver_data = ctxt; + + /* copy descriptors, and track endpoint copies */ + f->descriptors = usb_copy_descriptors(fs_diag_desc); + if (!f->descriptors) + goto fail; + + if (gadget_is_dualspeed(c->cdev->gadget)) { + hs_bulk_in_desc.bEndpointAddress = + fs_bulk_in_desc.bEndpointAddress; + hs_bulk_out_desc.bEndpointAddress = + fs_bulk_out_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(hs_diag_desc); + } + return 0; +fail: + if (ctxt->out) + ctxt->out->driver_data = NULL; + if (ctxt->in) + ctxt->in->driver_data = NULL; + return status; + +} + +int diag_function_add(struct usb_configuration *c, const char *name, + int (*update_pid)(uint32_t, const char *)) +{ + struct diag_context *dev; + struct usb_diag_ch *_ch; + int found = 0, ret; + + DBG(c->cdev, "diag_function_add\n"); + + list_for_each_entry(_ch, &usb_diag_ch_list, list) { + if (!strcmp(name, _ch->name)) { + found = 1; + break; + } + } + if (!found) { + ERROR(c->cdev, "unable to get diag usb channel\n"); + return -ENODEV; + } + + dev = container_of(_ch, struct diag_context, ch); + /* claim the channel for this USB interface */ + _ch->priv_usb = dev; + + dev->update_pid_and_serial_num = update_pid; + dev->cdev = c->cdev; + dev->function.name = _ch->name; + dev->function.descriptors = fs_diag_desc; + dev->function.hs_descriptors = hs_diag_desc; + dev->function.bind = diag_function_bind; + dev->function.unbind = diag_function_unbind; + dev->function.set_alt = diag_function_set_alt; + dev->function.disable = diag_function_disable; + spin_lock_init(&dev->lock); + INIT_LIST_HEAD(&dev->read_pool); + INIT_LIST_HEAD(&dev->write_pool); + INIT_WORK(&dev->config_work, usb_config_work_func); + + ret = usb_add_function(c, &dev->function); + if (ret) { + INFO(c->cdev, "usb_add_function failed\n"); + _ch->priv_usb = NULL; + } + + return ret; +} + + +#if defined(CONFIG_DEBUG_FS) +static char debug_buffer[PAGE_SIZE]; + +static ssize_t debug_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf = debug_buffer; + int temp = 0; + struct usb_diag_ch *ch; + + list_for_each_entry(ch, &usb_diag_ch_list, list) { + struct diag_context *ctxt; + + ctxt = ch->priv_usb; + + temp += scnprintf(buf + temp, PAGE_SIZE - temp, + "---Name: %s---\n" + "dpkts_tolaptop: %lu\n" + "dpkts_tomodem: %lu\n" + "pkts_tolaptop_pending: %u\n", + ch->name, ctxt->dpkts_tolaptop, + ctxt->dpkts_tomodem, + ctxt->dpkts_tolaptop_pending); + } + + return simple_read_from_buffer(ubuf, count, ppos, buf, temp); +} + +static ssize_t debug_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct usb_diag_ch *ch; + + list_for_each_entry(ch, &usb_diag_ch_list, list) { + struct diag_context *ctxt; + + ctxt = ch->priv_usb; + + ctxt->dpkts_tolaptop = 0; + ctxt->dpkts_tomodem = 0; + ctxt->dpkts_tolaptop_pending = 0; + } + + return count; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations debug_fdiag_ops = { + .open = debug_open, + .read = debug_read_stats, + .write = debug_reset_stats, +}; + +static void fdiag_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("usb_diag", 0); + if (IS_ERR(dent)) + return; + + debugfs_create_file("status", 0444, dent, 0, &debug_fdiag_ops); +} +#else +static void fdiag_debugfs_init(void) +{ + return; +} +#endif + +static void diag_cleanup(void) +{ + struct diag_context *dev; + struct list_head *act, *tmp; + struct usb_diag_ch *_ch; + unsigned long flags; + + list_for_each_safe(act, tmp, &usb_diag_ch_list) { + _ch = list_entry(act, struct usb_diag_ch, list); + dev = container_of(_ch, struct diag_context, ch); + + spin_lock_irqsave(&ch_lock, flags); + /* Free if diagchar is not using the channel anymore */ + if (!_ch->priv) { + list_del(&_ch->list); + kfree(dev); + } + spin_unlock_irqrestore(&ch_lock, flags); + + } +} + +static int diag_setup(void) +{ + fdiag_debugfs_init(); + + return 0; +} diff --git a/drivers/usb/gadget/f_diag.h b/drivers/usb/gadget/f_diag.h new file mode 100644 index 00000000000..82d9a25245f --- /dev/null +++ b/drivers/usb/gadget/f_diag.h @@ -0,0 +1,24 @@ +/* drivers/usb/gadget/f_diag.h + * + * Diag Function Device - Route DIAG frames between SMD and USB + * + * Copyright (C) 2008-2009 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * 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 __F_DIAG_H +#define __F_DIAG_H + +int diag_function_add(struct usb_configuration *c, const char *); + +#endif /* __F_DIAG_H */ + diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 5440c6d8fc3..ccd9c2df538 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -312,7 +312,10 @@ static const char fsg_string_interface[] = "Mass Storage"; #include "storage_common.c" - +#ifdef CONFIG_USB_CSW_HACK +static int write_error_after_csw_sent; +static int csw_hack_sent; +#endif /*-------------------------------------------------------------------------*/ struct fsg_dev; @@ -469,6 +472,7 @@ static inline struct fsg_dev *fsg_from_func(struct usb_function *f) } typedef void (*fsg_routine_t)(struct fsg_dev *); +static int send_status(struct fsg_common *common); static int exception_in_progress(struct fsg_common *common) { @@ -625,7 +629,7 @@ static int fsg_setup(struct usb_function *f, if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) break; - if (w_index != fsg->interface_number || w_value != 0) + if (w_value != 0) return -EDOM; /* @@ -640,7 +644,7 @@ static int fsg_setup(struct usb_function *f, if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) break; - if (w_index != fsg->interface_number || w_value != 0) + if (w_value != 0) return -EDOM; VDBG(fsg, "get max LUN\n"); *(u8 *)req->buf = fsg->common->nluns - 1; @@ -881,6 +885,9 @@ static int do_write(struct fsg_common *common) ssize_t nwritten; int rc; +#ifdef CONFIG_USB_CSW_HACK + int i; +#endif if (curlun->ro) { curlun->sense_data = SS_WRITE_PROTECTED; return -EINVAL; @@ -994,7 +1001,17 @@ static int do_write(struct fsg_common *common) bh = common->next_buffhd_to_drain; if (bh->state == BUF_STATE_EMPTY && !get_some_more) break; /* We stopped early */ +#ifdef CONFIG_USB_CSW_HACK + /* + * If the csw packet is already submmitted to the hardware, + * by marking the state of buffer as full, then by checking + * the residue, we make sure that this csw packet is not + * written on to the storage media. + */ + if (bh->state == BUF_STATE_FULL && common->residue) { +#else if (bh->state == BUF_STATE_FULL) { +#endif smp_rmb(); common->next_buffhd_to_drain = bh->next; bh->state = BUF_STATE_EMPTY; @@ -1045,9 +1062,36 @@ static int do_write(struct fsg_common *common) curlun->sense_data = SS_WRITE_ERROR; curlun->sense_data_info = file_offset >> 9; curlun->info_valid = 1; +#ifdef CONFIG_USB_CSW_HACK + write_error_after_csw_sent = 1; + goto write_error; +#endif break; } +#ifdef CONFIG_USB_CSW_HACK +write_error: + if ((nwritten == amount) && !csw_hack_sent) { + if (write_error_after_csw_sent) + break; + /* + * Check if any of the buffer is in the + * busy state, if any buffer is in busy state, + * means the complete data is not received + * yet from the host. So there is no point in + * csw right away without the complete data. + */ + for (i = 0; i < FSG_NUM_BUFFERS; i++) { + if (common->buffhds[i].state == + BUF_STATE_BUSY) + break; + } + if (!amount_left_to_req && i == FSG_NUM_BUFFERS) { + csw_hack_sent = 1; + send_status(common); + } + } +#endif /* Did the host decide to stop early? */ if (bh->outreq->actual != bh->outreq->length) { common->short_packet_received = 1; @@ -1508,8 +1552,7 @@ static int do_prevent_allow(struct fsg_common *common) curlun->sense_data = SS_INVALID_FIELD_IN_CDB; return -EINVAL; } - - if (curlun->prevent_medium_removal && !prevent) + if (!curlun->nofua && curlun->prevent_medium_removal && !prevent) fsg_lun_fsync_sub(curlun); curlun->prevent_medium_removal = prevent; return 0; @@ -1790,6 +1833,19 @@ static int send_status(struct fsg_common *common) csw->Signature = cpu_to_le32(USB_BULK_CS_SIG); csw->Tag = common->tag; csw->Residue = cpu_to_le32(common->residue); +#ifdef CONFIG_USB_CSW_HACK + /* Since csw is being sent early, before + * writing on to storage media, need to set + * residue to zero,assuming that write will succeed. + */ + if (write_error_after_csw_sent) { + write_error_after_csw_sent = 0; + csw->Residue = cpu_to_le32(common->residue); + } else + csw->Residue = 0; +#else + csw->Residue = cpu_to_le32(common->residue); +#endif csw->Status = status; bh->inreq->length = USB_BULK_CS_WRAP_LEN; @@ -2349,7 +2405,6 @@ static int alloc_request(struct fsg_common *common, struct usb_ep *ep, /* Reset interface setting and re-init endpoint state (toggle etc). */ static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) { - const struct usb_endpoint_descriptor *d; struct fsg_dev *fsg; int i, rc = 0; @@ -2374,15 +2429,6 @@ static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) } } - /* Disable the endpoints */ - if (fsg->bulk_in_enabled) { - usb_ep_disable(fsg->bulk_in); - fsg->bulk_in_enabled = 0; - } - if (fsg->bulk_out_enabled) { - usb_ep_disable(fsg->bulk_out); - fsg->bulk_out_enabled = 0; - } common->fsg = NULL; wake_up(&common->fsg_wait); @@ -2395,22 +2441,6 @@ static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) common->fsg = new_fsg; fsg = common->fsg; - /* Enable the endpoints */ - d = fsg_ep_desc(common->gadget, - &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc); - rc = enable_endpoint(common, fsg->bulk_in, d); - if (rc) - goto reset; - fsg->bulk_in_enabled = 1; - - d = fsg_ep_desc(common->gadget, - &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc); - rc = enable_endpoint(common, fsg->bulk_out, d); - if (rc) - goto reset; - fsg->bulk_out_enabled = 1; - common->bulk_out_maxpacket = le16_to_cpu(d->wMaxPacketSize); - clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); /* Allocate the requests */ for (i = 0; i < FSG_NUM_BUFFERS; ++i) { @@ -2440,6 +2470,29 @@ static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct fsg_dev *fsg = fsg_from_func(f); + struct fsg_common *common = fsg->common; + const struct usb_endpoint_descriptor *d; + int rc; + + /* Enable the endpoints */ + d = fsg_ep_desc(common->gadget, + &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc); + rc = enable_endpoint(common, fsg->bulk_in, d); + if (rc) + return rc; + fsg->bulk_in_enabled = 1; + + d = fsg_ep_desc(common->gadget, + &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc); + rc = enable_endpoint(common, fsg->bulk_out, d); + if (rc) { + usb_ep_disable(fsg->bulk_in); + fsg->bulk_in_enabled = 0; + return rc; + } + fsg->bulk_out_enabled = 1; + common->bulk_out_maxpacket = le16_to_cpu(d->wMaxPacketSize); + clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); fsg->common->new_fsg = fsg; raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); return USB_GADGET_DELAYED_STATUS; @@ -2448,6 +2501,18 @@ static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) static void fsg_disable(struct usb_function *f) { struct fsg_dev *fsg = fsg_from_func(f); + + /* Disable the endpoints */ + if (fsg->bulk_in_enabled) { + usb_ep_disable(fsg->bulk_in); + fsg->bulk_in_enabled = 0; + fsg->bulk_in->driver_data = NULL; + } + if (fsg->bulk_out_enabled) { + usb_ep_disable(fsg->bulk_out); + fsg->bulk_out_enabled = 0; + fsg->bulk_out->driver_data = NULL; + } fsg->common->new_fsg = NULL; raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); } @@ -2559,6 +2624,7 @@ static void handle_exception(struct fsg_common *common) */ if (!fsg_is_set(common)) break; + common->ep0req->length = 0; if (test_and_clear_bit(IGNORE_BULK_OUT, &common->fsg->atomic_bitflags)) usb_ep_clear_halt(common->fsg->bulk_in); @@ -2654,6 +2720,16 @@ static int fsg_main_thread(void *common_) common->state = FSG_STATE_STATUS_PHASE; spin_unlock_irq(&common->lock); +#ifdef CONFIG_USB_CSW_HACK + /* Since status is already sent for write scsi command, + * need to skip sending status once again if it is a + * write scsi command. + */ + if (csw_hack_sent) { + csw_hack_sent = 0; + continue; + } +#endif if (send_status(common)) continue; @@ -2779,6 +2855,7 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common, curlun->ro = lcfg->cdrom || lcfg->ro; curlun->initially_ro = curlun->ro; curlun->removable = lcfg->removable; + curlun->nofua = lcfg->nofua; curlun->dev.release = fsg_lun_release; curlun->dev.parent = &gadget->dev; /* curlun->dev.driver = &fsg_driver.driver; XXX */ diff --git a/drivers/usb/gadget/f_rmnet.c b/drivers/usb/gadget/f_rmnet.c new file mode 100644 index 00000000000..770a225e1a0 --- /dev/null +++ b/drivers/usb/gadget/f_rmnet.c @@ -0,0 +1,819 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include +#include "u_rmnet.h" +#include "gadget_chips.h" + + +#define RMNET_NOTIFY_INTERVAL 5 +#define RMNET_MAX_NOTIFY_SIZE sizeof(struct usb_cdc_notification) + +struct rmnet_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; + struct usb_endpoint_descriptor *notify; +}; + +#define ACM_CTRL_DTR (1 << 0) + +/* TODO: use separate structures for data and + * control paths + */ +struct f_rmnet { + struct grmnet port; + int ifc_id; + u8 port_num; + atomic_t online; + struct usb_composite_dev *cdev; + + spinlock_t lock; + + /* usb descriptors */ + struct rmnet_descs fs; + struct rmnet_descs hs; + + /* usb eps*/ + struct usb_ep *notify; + struct usb_endpoint_descriptor *notify_desc; + struct usb_request *notify_req; + + /* control info */ + struct list_head cpkt_resp_q; + atomic_t notify_count; + unsigned long cpkts_len; +}; + +#define NR_PORTS 1 +static unsigned int nr_ports; +static struct rmnet_ports { + unsigned port_num; + struct f_rmnet *port; +#ifdef CONFIG_USB_ANDROID + struct android_usb_function android_f; +#endif +} ports[NR_PORTS]; + +static struct usb_interface_descriptor rmnet_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, + /* .iInterface = DYNAMIC */ +}; + +/* Full speed support */ +static struct usb_endpoint_descriptor rmnet_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_MAX_NOTIFY_SIZE), + .bInterval = 1 << RMNET_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor rmnet_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor rmnet_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_descriptor_header *rmnet_fs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_fs_notify_desc, + (struct usb_descriptor_header *) &rmnet_fs_in_desc, + (struct usb_descriptor_header *) &rmnet_fs_out_desc, + NULL, +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_MAX_NOTIFY_SIZE), + .bInterval = RMNET_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor rmnet_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor rmnet_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_descriptor_header *rmnet_hs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_hs_notify_desc, + (struct usb_descriptor_header *) &rmnet_hs_in_desc, + (struct usb_descriptor_header *) &rmnet_hs_out_desc, + NULL, +}; + +/* String descriptors */ + +static struct usb_string rmnet_string_defs[] = { + [0].s = "RmNet", + { } /* end of list */ +}; + +static struct usb_gadget_strings rmnet_string_table = { + .language = 0x0409, /* en-us */ + .strings = rmnet_string_defs, +}; + +static struct usb_gadget_strings *rmnet_strings[] = { + &rmnet_string_table, + NULL, +}; + +/* ------- misc functions --------------------*/ + +static inline struct f_rmnet *func_to_rmnet(struct usb_function *f) +{ + return container_of(f, struct f_rmnet, port.func); +} + +static inline struct f_rmnet *port_to_rmnet(struct grmnet *r) +{ + return container_of(r, struct f_rmnet, port); +} + +static struct usb_request * +frmnet_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, flags); + if (!req) + return ERR_PTR(-ENOMEM); + + req->buf = kmalloc(len, flags); + if (!req->buf) { + usb_ep_free_request(ep, req); + return ERR_PTR(-ENOMEM); + } + + req->length = len; + + return req; +} + +void frmnet_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static struct rmnet_ctrl_pkt *rmnet_alloc_ctrl_pkt(unsigned len, gfp_t flags) +{ + struct rmnet_ctrl_pkt *pkt; + + pkt = kzalloc(sizeof(struct rmnet_ctrl_pkt), flags); + if (!pkt) + return ERR_PTR(-ENOMEM); + + pkt->buf = kmalloc(len, flags); + if (!pkt->buf) { + kfree(pkt); + return ERR_PTR(-ENOMEM); + } + pkt->len = len; + + return pkt; +} + +static void rmnet_free_ctrl_pkt(struct rmnet_ctrl_pkt *pkt) +{ + kfree(pkt->buf); + kfree(pkt); +} + +/* -------------------------------------------*/ + +static int gport_setup(int no_ports) +{ + int ret; + + pr_debug("%s: no_ports:%d\n", __func__, no_ports); + + ret = gbam_setup(no_ports); + if (ret) + return ret; + + ret = gsmd_ctrl_setup(no_ports); + if (ret) + return ret; + + return 0; +} + +static int gport_connect(struct f_rmnet *dev) +{ + int ret; + + pr_debug("%s:dev:%p portno:%d\n", + __func__, dev, dev->port_num); + + ret = gsmd_ctrl_connect(&dev->port, dev->port_num); + if (ret) { + pr_err("%s: gsmd_ctrl_connect failed: err:%d\n", + __func__, ret); + return ret; + } + + ret = gbam_connect(&dev->port, dev->port_num); + if (ret) { + pr_err("%s: gbam_connect failed: err:%d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int gport_disconnect(struct f_rmnet *dev) +{ + pr_debug("%s:dev:%p portno:%d\n", + __func__, dev, dev->port_num); + + gbam_disconnect(&dev->port, dev->port_num); + + gsmd_ctrl_disconnect(&dev->port, dev->port_num); + + return 0; +} + +static int frmnet_remove(struct platform_device *dev) +{ + /* TBD: + * 1. Unregister android function + * 2. Free name from ports + * 3. Free rmnet device + * 4. Free Copy Descriptors + */ + return 0; +} + +static void frmnet_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + + pr_debug("%s: portno:%d\n", __func__, dev->port_num); + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + + frmnet_free_req(dev->notify, dev->notify_req); + + kfree(dev); +} + +static void frmnet_disable(struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + + pr_debug("%s: port#%d\n", __func__, dev->port_num); + + usb_ep_disable(dev->notify); + + atomic_set(&dev->online, 0); + + gport_disconnect(dev); +} + +static int +frmnet_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_rmnet *dev = func_to_rmnet(f); + struct usb_composite_dev *cdev = dev->cdev; + int ret; + + pr_debug("%s:dev:%p port#%d\n", __func__, dev, dev->port_num); + + if (dev->notify->driver_data) { + pr_debug("%s: reset port:%d\n", __func__, dev->port_num); + usb_ep_disable(dev->notify); + } + dev->notify_desc = ep_choose(cdev->gadget, + dev->hs.notify, + dev->fs.notify); + ret = usb_ep_enable(dev->notify, dev->notify_desc); + if (ret) { + pr_err("%s: usb ep#%s enable failed, err#%d\n", + __func__, dev->notify->name, ret); + return ret; + } + dev->notify->driver_data = dev; + + if (dev->port.in->driver_data) { + pr_debug("%s: reset port:%d\n", __func__, dev->port_num); + gport_disconnect(dev); + } + + dev->port.in_desc = ep_choose(cdev->gadget, + dev->hs.in, dev->fs.in); + dev->port.out_desc = ep_choose(cdev->gadget, + dev->hs.out, dev->fs.out); + + ret = gport_connect(dev); + + atomic_set(&dev->online, 1); + + return ret; +} + +static void frmnet_ctrl_response_available(struct f_rmnet *dev) +{ + struct usb_request *req = dev->notify_req; + struct usb_cdc_notification *event; + unsigned long flags; + int ret; + + pr_debug("%s:dev:%p portno#%d\n", __func__, dev, dev->port_num); + + spin_lock_irqsave(&dev->lock, flags); + if (!atomic_read(&dev->online) || !req || !req->buf) { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + if (atomic_inc_return(&dev->notify_count) != 1) { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + event = req->buf; + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; + event->wValue = cpu_to_le16(0); + event->wIndex = cpu_to_le16(dev->ifc_id); + event->wLength = cpu_to_le16(0); + spin_unlock_irqrestore(&dev->lock, flags); + + ret = usb_ep_queue(dev->notify, dev->notify_req, GFP_ATOMIC); + if (ret) { + atomic_dec(&dev->notify_count); + pr_debug("ep enqueue error %d\n", ret); + } +} + +static int +frmnet_send_cpkt_response(struct grmnet *gr, struct rmnet_ctrl_pkt *cpkt) +{ + struct f_rmnet *dev; + unsigned long flags; + + if (!gr || !cpkt) { + pr_err("%s: Invalid grmnet/cpkt, grmnet:%p cpkt:%p\n", + __func__, gr, cpkt); + return -ENODEV; + } + + dev = port_to_rmnet(gr); + + pr_debug("%s: dev:%p port#%d\n", __func__, dev, dev->port_num); + + if (!atomic_read(&dev->online)) { + rmnet_free_ctrl_pkt(cpkt); + return 0; + } + + spin_lock_irqsave(&dev->lock, flags); + list_add(&cpkt->list, &dev->cpkt_resp_q); + spin_unlock_irqrestore(&dev->lock, flags); + + frmnet_ctrl_response_available(dev); + + return 0; +} + +static void +frmnet_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rmnet *dev = req->context; + struct usb_composite_dev *cdev; + struct rmnet_ctrl_pkt *cpkt; + + if (!dev) { + pr_err("%s: rmnet dev is null\n", __func__); + return; + } + + pr_debug("%s: dev:%p port#%d\n", __func__, dev, dev->port_num); + + cdev = dev->cdev; + + cpkt = rmnet_alloc_ctrl_pkt(req->actual, GFP_ATOMIC); + if (IS_ERR(cpkt)) { + pr_err("%s: Unable to allocate ctrl pkt\n", __func__); + return; + } + + memcpy(cpkt->buf, req->buf, req->actual); + + if (dev->port.send_cpkt_request) + dev->port.send_cpkt_request(&dev->port, dev->port_num, cpkt); +} + +static void frmnet_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rmnet *dev = req->context; + int status = req->status; + + pr_debug("%s: dev:%p port#%d\n", __func__, dev, dev->port_num); + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + atomic_set(&dev->notify_count, 0); + break; + default: + pr_err("rmnet notify ep error %d\n", status); + /* FALLTHROUGH */ + case 0: + if (atomic_dec_and_test(&dev->notify_count)) + break; + + status = usb_ep_queue(dev->notify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&dev->notify_count); + pr_debug("ep enqueue error %d\n", status); + } + break; + } +} + +static int +frmnet_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_rmnet *dev = func_to_rmnet(f); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = cdev->req; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + int ret = -EOPNOTSUPP; + + pr_debug("%s:dev:%p port#%d\n", __func__, dev, dev->port_num); + + if (!atomic_read(&dev->online)) { + pr_debug("%s: usb cable is not connected\n", __func__); + return -ENOTCONN; + } + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (w_length > req->length) + goto invalid; + ret = w_length; + req->complete = frmnet_cmd_complete; + req->context = dev; + break; + + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (w_value) + goto invalid; + else { + unsigned len; + struct rmnet_ctrl_pkt *cpkt; + + spin_lock(&dev->lock); + if (list_empty(&dev->cpkt_resp_q)) { + pr_err("ctrl resp queue empty " + " req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + spin_unlock(&dev->lock); + goto invalid; + } + + cpkt = list_first_entry(&dev->cpkt_resp_q, + struct rmnet_ctrl_pkt, list); + list_del(&cpkt->list); + spin_unlock(&dev->lock); + + len = min_t(unsigned, w_length, cpkt->len); + memcpy(req->buf, cpkt->buf, len); + ret = len; + + rmnet_free_ctrl_pkt(cpkt); + } + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + if (dev->port.send_cbits_tomodem) + dev->port.send_cbits_tomodem(&dev->port, + dev->port_num, + w_value); + ret = 0; + + break; + default: + +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (ret >= 0) { + VDBG(cdev, "rmnet req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = (ret < w_length); + req->length = ret; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret < 0) + ERROR(cdev, "rmnet ep0 enqueue err %d\n", ret); + } + + return ret; +} + +static int frmnet_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rmnet *dev = func_to_rmnet(f); + struct usb_ep *ep; + struct usb_composite_dev *cdev = c->cdev; + int ret = -ENODEV; + + dev->ifc_id = usb_interface_id(c, f); + if (dev->ifc_id < 0) { + pr_err("%s: unable to allocate ifc id, err:%d", + __func__, dev->ifc_id); + return dev->ifc_id; + } + rmnet_interface_desc.bInterfaceNumber = dev->ifc_id; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_in_desc); + if (!ep) { + pr_err("%s: usb epin autoconfig failed\n", __func__); + return -ENODEV; + } + dev->port.in = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_out_desc); + if (!ep) { + pr_err("%s: usb epout autoconfig failed\n", __func__); + ret = -ENODEV; + goto ep_auto_out_fail; + } + dev->port.out = ep; + ep->driver_data = cdev; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_notify_desc); + if (!ep) { + pr_err("%s: usb epnotify autoconfig failed\n", __func__); + ret = -ENODEV; + goto ep_auto_notify_fail; + } + dev->notify = ep; + ep->driver_data = cdev; + + dev->notify_req = frmnet_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (IS_ERR(dev->notify_req)) { + pr_err("%s: unable to allocate memory for notify req\n", + __func__); + ret = -ENOMEM; + goto ep_notify_alloc_fail; + } + + dev->notify_req->complete = frmnet_notify_complete; + dev->notify_req->context = dev; + + f->descriptors = usb_copy_descriptors(rmnet_fs_function); + + dev->fs.in = usb_find_endpoint(rmnet_fs_function, + f->descriptors, + &rmnet_fs_in_desc); + dev->fs.out = usb_find_endpoint(rmnet_fs_function, + f->descriptors, + &rmnet_fs_out_desc); + dev->fs.notify = usb_find_endpoint(rmnet_fs_function, + f->descriptors, + &rmnet_fs_notify_desc); + + if (gadget_is_dualspeed(cdev->gadget)) { + rmnet_hs_in_desc.bEndpointAddress = + rmnet_fs_in_desc.bEndpointAddress; + rmnet_hs_out_desc.bEndpointAddress = + rmnet_fs_out_desc.bEndpointAddress; + rmnet_hs_notify_desc.bEndpointAddress = + rmnet_fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(rmnet_hs_function); + + dev->hs.in = usb_find_endpoint(rmnet_hs_function, + f->hs_descriptors, &rmnet_hs_in_desc); + dev->hs.out = usb_find_endpoint(rmnet_hs_function, + f->hs_descriptors, &rmnet_hs_out_desc); + dev->hs.notify = usb_find_endpoint(rmnet_hs_function, + f->hs_descriptors, &rmnet_hs_notify_desc); + } + + pr_info("%s: RmNet(%d) %s Speed, IN:%s OUT:%s\n", + __func__, dev->port_num, + gadget_is_dualspeed(cdev->gadget) ? "dual" : "full", + dev->port.in->name, dev->port.out->name); + + return 0; + +ep_notify_alloc_fail: + dev->notify->driver_data = NULL; + dev->notify = NULL; +ep_auto_notify_fail: + dev->port.out->driver_data = NULL; + dev->port.out = NULL; +ep_auto_out_fail: + dev->port.in->driver_data = NULL; + dev->port.in = NULL; + + return ret; +} + +#ifdef CONFIG_USB_ANDROID +static int frmnet_bind_config(struct usb_configuration *c) +{ + static unsigned portno; + int status; + struct f_rmnet *dev; + struct usb_function *f; + unsigned long flags; + + pr_debug("%s: usb config:%p\n", __func__, c); + + if (portno >= nr_ports) { + pr_err("%s: supporting ports#%u port_id:%u", __func__, + nr_ports, portno); + return -ENODEV; + } + + if (rmnet_string_defs[0].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) { + pr_err("%s: failed to get string id, err:%d\n", + __func__, status); + return status; + } + rmnet_string_defs[0].id = status; + } + + dev = ports[portno].port; + + spin_lock_irqsave(&dev->lock, flags); + dev->cdev = c->cdev; + f = &dev->port.func; + f->name = ports[portno].android_f.name; + portno++; + spin_unlock_irqrestore(&dev->lock, flags); + + f->strings = rmnet_strings; + f->bind = frmnet_bind; + f->unbind = frmnet_unbind; + f->disable = frmnet_disable; + f->set_alt = frmnet_set_alt; + f->setup = frmnet_setup; + dev->port.send_cpkt_response = frmnet_send_cpkt_response; + + status = usb_add_function(c, f); + if (status) { + pr_err("%s: usb add function failed: %d\n", + __func__, status); + kfree(ports[portno].android_f.name); + kfree(dev); + return status; + } + + pr_debug("%s: complete\n", __func__); + + return status; +} + +static struct platform_driver usb_rmnet = { + .remove = frmnet_remove, + .driver = { + .name = "usb_rmnet", + .owner = THIS_MODULE, + }, +}; + +static int __devinit frmnet_probe(struct platform_device *pdev) +{ + struct usb_rmnet_pdata *pdata = pdev->dev.platform_data; + int i; + struct f_rmnet *dev; + int ret; + int instances; + + instances = 1; + if (pdata) + instances = pdata->num_instances; + + pr_debug("%s: instances :%d\n", __func__, instances); + + for (i = 0; i < instances; i++) { + dev = kzalloc(sizeof(struct f_rmnet), GFP_KERNEL); + if (!dev) { + pr_err("%s: Unable to allocate rmnet device\n", + __func__); + ret = -ENOMEM; + goto fail_probe; + } + + dev->port_num = i; + spin_lock_init(&dev->lock); + INIT_LIST_HEAD(&dev->cpkt_resp_q); + + ports[i].port = dev; + ports[i].port_num = i; + ports[i].android_f.name = kasprintf(GFP_KERNEL, "rmnet%d", i); + ports[i].android_f.bind_config = frmnet_bind_config; + + pr_debug("%s: anroid f_name:%s\n", __func__, + ports[i].android_f.name); + + nr_ports++; + + android_register_function(&ports[i].android_f); + } + + gport_setup(nr_ports); + + return 0; + +fail_probe: + for (i = 0; i < nr_ports; i++) { + /* android_unregister_function(&ports[i].android_f); */ + kfree(ports[i].android_f.name); + kfree(ports[i].port); + } + + return ret; +} + +static int __init frmnet_init(void) +{ + return platform_driver_probe(&usb_rmnet, frmnet_probe); +} +module_init(frmnet_init); + +static void __exit frmnet_exit(void) +{ + platform_driver_unregister(&usb_rmnet); +} +module_exit(frmnet_exit); + +MODULE_DESCRIPTION("rmnet function driver"); +MODULE_LICENSE("GPL v2"); +#endif diff --git a/drivers/usb/gadget/f_rmnet.h b/drivers/usb/gadget/f_rmnet.h new file mode 100644 index 00000000000..2d816c653f1 --- /dev/null +++ b/drivers/usb/gadget/f_rmnet.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __F_RMNET_H +#define __F_RMNET_H + +int rmnet_function_add(struct usb_configuration *c); + +#endif /* __F_RMNET_H */ diff --git a/drivers/usb/gadget/f_rmnet_sdio.c b/drivers/usb/gadget/f_rmnet_sdio.c new file mode 100644 index 00000000000..aa8fd3a02e6 --- /dev/null +++ b/drivers/usb/gadget/f_rmnet_sdio.c @@ -0,0 +1,1314 @@ +/* + * f_rmnet_sdio.c -- RmNet SDIO function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 Nokia Corporation + * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +static uint32_t rmnet_sdio_ctl_ch = CONFIG_RMNET_SDIO_CTL_CHANNEL; +module_param(rmnet_sdio_ctl_ch, uint, S_IRUGO); +MODULE_PARM_DESC(rmnet_sdio_ctl_ch, "RmNet control SDIO channel ID"); + +static uint32_t rmnet_sdio_data_ch = CONFIG_RMNET_SDIO_DATA_CHANNEL; +module_param(rmnet_sdio_data_ch, uint, S_IRUGO); +MODULE_PARM_DESC(rmnet_sdio_data_ch, "RmNet data SDIO channel ID"); + +#define ACM_CTRL_DTR (1 << 0) + +#define SDIO_MUX_HDR 8 +#define RMNET_SDIO_NOTIFY_INTERVAL 5 +#define RMNET_SDIO_MAX_NFY_SZE sizeof(struct usb_cdc_notification) + +#define RMNET_SDIO_RX_REQ_MAX 16 +#define RMNET_SDIO_RX_REQ_SIZE 2048 +#define RMNET_SDIO_TX_REQ_MAX 200 + +#define TX_PKT_DROP_THRESHOLD 1000 +#define RX_PKT_FLOW_CTRL_EN_THRESHOLD 1000 +#define RX_PKT_FLOW_CTRL_DISABLE 500 + +unsigned int tx_pkt_drop_thld = TX_PKT_DROP_THRESHOLD; +module_param(tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int rx_fctrl_en_thld = RX_PKT_FLOW_CTRL_EN_THRESHOLD; +module_param(rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int rx_fctrl_dis_thld = RX_PKT_FLOW_CTRL_DISABLE; +module_param(rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + +/* QMI requests & responses buffer*/ +struct rmnet_sdio_qmi_buf { + void *buf; + int len; + struct list_head list; +}; + +struct rmnet_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + + struct usb_ep *epout; + struct usb_ep *epin; + struct usb_ep *epnotify; + struct usb_request *notify_req; + + u8 ifc_id; + /* QMI lists */ + struct list_head qmi_req_q; + struct list_head qmi_resp_q; + /* Tx/Rx lists */ + struct list_head tx_idle; + struct sk_buff_head tx_skb_queue; + struct list_head rx_idle; + struct sk_buff_head rx_skb_queue; + + spinlock_t lock; + atomic_t online; + atomic_t notify_count; + + struct workqueue_struct *wq; + struct work_struct disconnect_work; + + struct work_struct ctl_rx_work; + struct work_struct data_rx_work; + + struct delayed_work sdio_open_work; + atomic_t sdio_open; + + unsigned int dpkts_pending_atdmux; + int cbits_to_modem; + struct work_struct set_modem_ctl_bits_work; + + /* pkt logging dpkt - data pkt; cpkt - control pkt*/ + unsigned long dpkt_tolaptop; + unsigned long dpkt_tomodem; + unsigned long tx_drp_cnt; + unsigned long cpkt_tolaptop; + unsigned long cpkt_tomodem; +}; + +static struct usb_interface_descriptor rmnet_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, + /* .iInterface = DYNAMIC */ +}; + +/* Full speed support */ +static struct usb_endpoint_descriptor rmnet_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_SDIO_MAX_NFY_SZE), + .bInterval = 1 << RMNET_SDIO_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor rmnet_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor rmnet_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_descriptor_header *rmnet_fs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_fs_notify_desc, + (struct usb_descriptor_header *) &rmnet_fs_in_desc, + (struct usb_descriptor_header *) &rmnet_fs_out_desc, + NULL, +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_SDIO_MAX_NFY_SZE), + .bInterval = RMNET_SDIO_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor rmnet_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor rmnet_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_descriptor_header *rmnet_hs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_hs_notify_desc, + (struct usb_descriptor_header *) &rmnet_hs_in_desc, + (struct usb_descriptor_header *) &rmnet_hs_out_desc, + NULL, +}; + +/* String descriptors */ + +static struct usb_string rmnet_string_defs[] = { + [0].s = "QMI RmNet", + { } /* end of list */ +}; + +static struct usb_gadget_strings rmnet_string_table = { + .language = 0x0409, /* en-us */ + .strings = rmnet_string_defs, +}; + +static struct usb_gadget_strings *rmnet_strings[] = { + &rmnet_string_table, + NULL, +}; + +static struct rmnet_sdio_qmi_buf * +rmnet_alloc_qmi(unsigned len, gfp_t kmalloc_flags) + +{ + struct rmnet_sdio_qmi_buf *qmi; + + qmi = kmalloc(sizeof(struct rmnet_sdio_qmi_buf), kmalloc_flags); + if (qmi != NULL) { + qmi->buf = kmalloc(len, kmalloc_flags); + if (qmi->buf == NULL) { + kfree(qmi); + qmi = NULL; + } + } + + return qmi ? qmi : ERR_PTR(-ENOMEM); +} + +static void rmnet_free_qmi(struct rmnet_sdio_qmi_buf *qmi) +{ + kfree(qmi->buf); + kfree(qmi); +} +/* + * Allocate a usb_request and its buffer. Returns a pointer to the + * usb_request or a pointer with an error code if there is an error. + */ +static struct usb_request * +rmnet_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, kmalloc_flags); + + if (len && req != NULL) { + req->length = len; + req->buf = kmalloc(len, kmalloc_flags); + if (req->buf == NULL) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + + return req ? req : ERR_PTR(-ENOMEM); +} + +/* + * Free a usb_request and its buffer. + */ +static void rmnet_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static void rmnet_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + atomic_set(&dev->notify_count, 0); + break; + default: + ERROR(cdev, "rmnet notifyep error %d\n", status); + /* FALLTHROUGH */ + case 0: + + /* handle multiple pending QMI_RESPONSE_AVAILABLE + * notifications by resending until we're done + */ + if (atomic_dec_and_test(&dev->notify_count)) + break; + + status = usb_ep_queue(dev->epnotify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&dev->notify_count); + ERROR(cdev, "rmnet notify ep enq error %d\n", status); + } + break; + } +} + +static void qmi_response_available(struct rmnet_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = dev->notify_req; + struct usb_cdc_notification *event = req->buf; + int status; + + /* Response will be sent later */ + if (atomic_inc_return(&dev->notify_count) != 1) + return; + + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; + event->wValue = cpu_to_le16(0); + event->wIndex = cpu_to_le16(dev->ifc_id); + event->wLength = cpu_to_le16(0); + + status = usb_ep_queue(dev->epnotify, dev->notify_req, GFP_ATOMIC); + if (status < 0) { + atomic_dec(&dev->notify_count); + ERROR(cdev, "rmnet notify ep enqueue error %d\n", status); + } +} + +#define MAX_CTRL_PKT_SIZE 4096 +static void rmnet_ctl_receive_cb(void *data, int size, void *priv) +{ + struct rmnet_dev *dev = priv; + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_sdio_qmi_buf *qmi_resp; + unsigned long flags; + + if (!data || !size) + return; + + if (size > MAX_CTRL_PKT_SIZE) { + ERROR(cdev, "ctrl pkt size:%d exceeds max pkt size:%d\n", + size, MAX_CTRL_PKT_SIZE); + return; + } + + if (!atomic_read(&dev->online)) { + DBG(cdev, "USB disconnected\n"); + return; + } + + qmi_resp = rmnet_alloc_qmi(size, GFP_KERNEL); + if (IS_ERR(qmi_resp)) { + DBG(cdev, "unable to allocate memory for QMI resp\n"); + return; + } + memcpy(qmi_resp->buf, data, size); + qmi_resp->len = size; + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&qmi_resp->list, &dev->qmi_resp_q); + spin_unlock_irqrestore(&dev->lock, flags); + + qmi_response_available(dev); +} + +static void rmnet_ctl_write_done(void *data, int size, void *priv) +{ + struct rmnet_dev *dev = priv; + struct usb_composite_dev *cdev = dev->cdev; + + VDBG(cdev, "rmnet control write done = %d bytes\n", size); +} + +static void rmnet_sts_callback(int id, void *priv) +{ + struct rmnet_dev *dev = priv; + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "rmnet_sts_callback: id: %d\n", id); +} + +static void rmnet_control_rx_work(struct work_struct *w) +{ + struct rmnet_dev *dev = container_of(w, struct rmnet_dev, ctl_rx_work); + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_sdio_qmi_buf *qmi_req; + unsigned long flags; + int ret; + + while (1) { + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(&dev->qmi_req_q)) + goto unlock; + + qmi_req = list_first_entry(&dev->qmi_req_q, + struct rmnet_sdio_qmi_buf, list); + list_del(&qmi_req->list); + spin_unlock_irqrestore(&dev->lock, flags); + + ret = sdio_cmux_write(rmnet_sdio_ctl_ch, qmi_req->buf, + qmi_req->len); + if (ret != qmi_req->len) { + ERROR(cdev, "rmnet control SDIO write failed\n"); + return; + } + + dev->cpkt_tomodem++; + + /* + * cmux_write API copies the buffer and gives it to sdio_al. + * Hence freeing the memory before write is completed. + */ + rmnet_free_qmi(qmi_req); + } +unlock: + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_response_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + + switch (req->status) { + case -ECONNRESET: + case -ESHUTDOWN: + case 0: + return; + default: + INFO(cdev, "rmnet %s response error %d, %d/%d\n", + ep->name, req->status, + req->actual, req->length); + } +} + +static void rmnet_command_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_sdio_qmi_buf *qmi_req; + int len = req->actual; + + if (req->status < 0) { + ERROR(cdev, "rmnet command error %d\n", req->status); + return; + } + + /* discard the packet if sdio is not available */ + if (!atomic_read(&dev->sdio_open)) + return; + + qmi_req = rmnet_alloc_qmi(len, GFP_ATOMIC); + if (IS_ERR(qmi_req)) { + ERROR(cdev, "unable to allocate memory for QMI req\n"); + return; + } + memcpy(qmi_req->buf, req->buf, len); + qmi_req->len = len; + spin_lock(&dev->lock); + list_add_tail(&qmi_req->list, &dev->qmi_req_q); + spin_unlock(&dev->lock); + queue_work(dev->wq, &dev->ctl_rx_work); +} + +static int +rmnet_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int ret = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + struct rmnet_sdio_qmi_buf *resp; + + if (!atomic_read(&dev->online)) + return -ENOTCONN; + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (w_length > req->length) + goto invalid; + ret = w_length; + req->complete = rmnet_command_complete; + req->context = dev; + break; + + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (w_value) + goto invalid; + else { + unsigned len; + + spin_lock(&dev->lock); + + if (list_empty(&dev->qmi_resp_q)) { + INFO(cdev, "qmi resp empty " + " req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + spin_unlock(&dev->lock); + goto invalid; + } + + resp = list_first_entry(&dev->qmi_resp_q, + struct rmnet_sdio_qmi_buf, list); + list_del(&resp->list); + spin_unlock(&dev->lock); + + len = min_t(unsigned, w_length, resp->len); + memcpy(req->buf, resp->buf, len); + ret = len; + req->context = dev; + req->complete = rmnet_response_complete; + rmnet_free_qmi(resp); + + /* check if its the right place to add */ + dev->cpkt_tolaptop++; + } + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + /* This is a workaround for RmNet and is borrowed from the + * CDC/ACM standard. The host driver will issue the above ACM + * standard request to the RmNet interface in the following + * scenario: Once the network adapter is disabled from device + * manager, the above request will be sent from the qcusbnet + * host driver, with DTR being '0'. Once network adapter is + * enabled from device manager (or during enumeration), the + * request will be sent with DTR being '1'. + */ + if (w_value & ACM_CTRL_DTR) + dev->cbits_to_modem |= TIOCM_DTR; + else + dev->cbits_to_modem &= ~TIOCM_DTR; + queue_work(dev->wq, &dev->set_modem_ctl_bits_work); + + ret = 0; + + break; + default: + +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (ret >= 0) { + VDBG(cdev, "rmnet req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = (ret < w_length); + req->length = ret; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret < 0) + ERROR(cdev, "rmnet ep0 enqueue err %d\n", ret); + } + + return ret; +} + +static int +rmnet_rx_submit(struct rmnet_dev *dev, struct usb_request *req, gfp_t gfp_flags) +{ + struct sk_buff *skb; + int retval; + + skb = alloc_skb(RMNET_SDIO_RX_REQ_SIZE + SDIO_MUX_HDR, gfp_flags); + if (skb == NULL) + return -ENOMEM; + skb_reserve(skb, SDIO_MUX_HDR); + + req->buf = skb->data; + req->length = RMNET_SDIO_RX_REQ_SIZE; + req->context = skb; + + retval = usb_ep_queue(dev->epout, req, gfp_flags); + if (retval) + dev_kfree_skb_any(skb); + + return retval; +} + +static void rmnet_start_rx(struct rmnet_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + int status; + struct usb_request *req; + struct list_head *act, *tmp; + unsigned long flags; + + if (!atomic_read(&dev->online)) { + pr_err("%s: USB not connected\n", __func__); + return; + } + + spin_lock_irqsave(&dev->lock, flags); + list_for_each_safe(act, tmp, &dev->rx_idle) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + + spin_unlock_irqrestore(&dev->lock, flags); + status = rmnet_rx_submit(dev, req, GFP_ATOMIC); + spin_lock_irqsave(&dev->lock, flags); + + if (status) { + ERROR(cdev, "rmnet data rx enqueue err %d\n", status); + list_add_tail(&req->list, &dev->rx_idle); + break; + } + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void usb_rmnet_sdio_start_tx(struct rmnet_dev *dev) +{ + unsigned long flags; + int status; + struct sk_buff *skb; + struct usb_request *req; + struct usb_composite_dev *cdev = dev->cdev; + + if (!atomic_read(&dev->online)) + return; + + spin_lock_irqsave(&dev->lock, flags); + while (!list_empty(&dev->tx_idle)) { + skb = __skb_dequeue(&dev->tx_skb_queue); + if (!skb) { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + req = list_first_entry(&dev->tx_idle, struct usb_request, list); + req->context = skb; + req->buf = skb->data; + req->length = skb->len; + + list_del(&req->list); + spin_unlock(&dev->lock); + status = usb_ep_queue(dev->epin, req, GFP_ATOMIC); + spin_lock(&dev->lock); + if (status) { + /* USB still online, queue requests back */ + if (atomic_read(&dev->online)) { + ERROR(cdev, "rmnet tx data enqueue err %d\n", + status); + list_add_tail(&req->list, &dev->tx_idle); + __skb_queue_head(&dev->tx_skb_queue, skb); + } else { + req->buf = 0; + rmnet_free_req(dev->epin, req); + dev_kfree_skb_any(skb); + } + break; + } + dev->dpkt_tolaptop++; + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_data_receive_cb(void *priv, struct sk_buff *skb) +{ + struct rmnet_dev *dev = priv; + unsigned long flags; + + /* SDIO mux sends NULL SKB when link state changes */ + if (!skb) + return; + + if (!atomic_read(&dev->online)) { + dev_kfree_skb_any(skb); + return; + } + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->tx_skb_queue.qlen > tx_pkt_drop_thld) { + if (printk_ratelimit()) + pr_err("%s: tx pkt dropped: tx_drop_cnt:%lu\n", + __func__, dev->tx_drp_cnt); + dev->tx_drp_cnt++; + spin_unlock_irqrestore(&dev->lock, flags); + dev_kfree_skb_any(skb); + return; + } + + __skb_queue_tail(&dev->tx_skb_queue, skb); + spin_unlock_irqrestore(&dev->lock, flags); + + usb_rmnet_sdio_start_tx(dev); +} + +static void rmnet_data_write_done(void *priv, struct sk_buff *skb) +{ + struct rmnet_dev *dev = priv; + + /* SDIO mux sends NULL SKB when link state changes */ + if (!skb) + return; + + dev_kfree_skb_any(skb); + /* this function is called from + * sdio mux from spin_lock_irqsave + */ + spin_lock(&dev->lock); + dev->dpkts_pending_atdmux--; + + if (dev->dpkts_pending_atdmux >= rx_fctrl_dis_thld) { + spin_unlock(&dev->lock); + return; + } + spin_unlock(&dev->lock); + + rmnet_start_rx(dev); +} + +static void rmnet_data_rx_work(struct work_struct *w) +{ + struct rmnet_dev *dev = container_of(w, struct rmnet_dev, data_rx_work); + struct usb_composite_dev *cdev = dev->cdev; + struct sk_buff *skb; + int ret; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + while ((skb = __skb_dequeue(&dev->rx_skb_queue))) { + spin_unlock_irqrestore(&dev->lock, flags); + ret = msm_sdio_dmux_write(rmnet_sdio_data_ch, skb); + spin_lock_irqsave(&dev->lock, flags); + if (ret < 0) { + ERROR(cdev, "rmnet SDIO data write failed\n"); + dev_kfree_skb_any(skb); + } else { + dev->dpkt_tomodem++; + dev->dpkts_pending_atdmux++; + } + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_complete_epout(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = ep->driver_data; + struct usb_composite_dev *cdev = dev->cdev; + struct sk_buff *skb = req->context; + int status = req->status; + int queue = 0; + + switch (status) { + case 0: + /* successful completion */ + skb_put(skb, req->actual); + queue = 1; + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + dev_kfree_skb_any(skb); + req->buf = 0; + rmnet_free_req(ep, req); + return; + default: + /* unexpected failure */ + ERROR(cdev, "RMNET %s response error %d, %d/%d\n", + ep->name, status, + req->actual, req->length); + dev_kfree_skb_any(skb); + break; + } + + spin_lock(&dev->lock); + if (queue) { + __skb_queue_tail(&dev->rx_skb_queue, skb); + queue_work(dev->wq, &dev->data_rx_work); + } + + if (dev->dpkts_pending_atdmux >= rx_fctrl_en_thld) { + list_add_tail(&req->list, &dev->rx_idle); + spin_unlock(&dev->lock); + return; + } + spin_unlock(&dev->lock); + + status = rmnet_rx_submit(dev, req, GFP_ATOMIC); + if (status) { + ERROR(cdev, "rmnet data rx enqueue err %d\n", status); + list_add_tail(&req->list, &dev->rx_idle); + } +} + +static void rmnet_complete_epin(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = ep->driver_data; + struct sk_buff *skb = req->context; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + + switch (status) { + case 0: + /* successful completion */ + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + break; + default: + ERROR(cdev, "rmnet data tx ep error %d\n", status); + break; + } + + spin_lock(&dev->lock); + list_add_tail(&req->list, &dev->tx_idle); + spin_unlock(&dev->lock); + dev_kfree_skb_any(skb); + + usb_rmnet_sdio_start_tx(dev); +} + +static void rmnet_free_buf(struct rmnet_dev *dev) +{ + struct rmnet_sdio_qmi_buf *qmi; + struct usb_request *req; + struct list_head *act, *tmp; + struct sk_buff *skb; + unsigned long flags; + + + spin_lock_irqsave(&dev->lock, flags); + + dev->dpkt_tolaptop = 0; + dev->dpkt_tomodem = 0; + dev->cpkt_tolaptop = 0; + dev->cpkt_tomodem = 0; + dev->dpkts_pending_atdmux = 0; + dev->tx_drp_cnt = 0; + + /* free all usb requests in tx pool */ + list_for_each_safe(act, tmp, &dev->tx_idle) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + req->buf = NULL; + rmnet_free_req(dev->epout, req); + } + + /* free all usb requests in rx pool */ + list_for_each_safe(act, tmp, &dev->rx_idle) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + req->buf = NULL; + rmnet_free_req(dev->epin, req); + } + + /* free all buffers in qmi request pool */ + list_for_each_safe(act, tmp, &dev->qmi_req_q) { + qmi = list_entry(act, struct rmnet_sdio_qmi_buf, list); + list_del(&qmi->list); + rmnet_free_qmi(qmi); + } + + /* free all buffers in qmi request pool */ + list_for_each_safe(act, tmp, &dev->qmi_resp_q) { + qmi = list_entry(act, struct rmnet_sdio_qmi_buf, list); + list_del(&qmi->list); + rmnet_free_qmi(qmi); + } + + while ((skb = __skb_dequeue(&dev->tx_skb_queue))) + dev_kfree_skb_any(skb); + + while ((skb = __skb_dequeue(&dev->rx_skb_queue))) + dev_kfree_skb_any(skb); + + rmnet_free_req(dev->epnotify, dev->notify_req); + + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_set_modem_ctl_bits_work(struct work_struct *w) +{ + struct rmnet_dev *dev; + + dev = container_of(w, struct rmnet_dev, set_modem_ctl_bits_work); + + if (!atomic_read(&dev->sdio_open)) + return; + + pr_debug("%s: cbits_to_modem:%d\n", + __func__, dev->cbits_to_modem); + + sdio_cmux_tiocmset(rmnet_sdio_ctl_ch, + dev->cbits_to_modem, + ~dev->cbits_to_modem); +} + +static void rmnet_disconnect_work(struct work_struct *w) +{ + /* REVISIT: Push all the data to sdio if anythign is pending */ +} +static void rmnet_suspend(struct usb_function *f) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + + if (!atomic_read(&dev->online)) + return; + /* This is a workaround for Windows Host bug during suspend. + * Windows 7/xp Hosts are suppose to drop DTR, when Host suspended. + * Since it is not beind done, Hence exclusively dropping the DTR + * from function driver suspend. + */ + dev->cbits_to_modem &= ~TIOCM_DTR; + queue_work(dev->wq, &dev->set_modem_ctl_bits_work); +} +static void rmnet_disable(struct usb_function *f) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + + if (!atomic_read(&dev->online)) + return; + + usb_ep_disable(dev->epnotify); + usb_ep_disable(dev->epout); + usb_ep_disable(dev->epin); + + atomic_set(&dev->online, 0); + atomic_set(&dev->notify_count, 0); + rmnet_free_buf(dev); + + /* cleanup work */ + queue_work(dev->wq, &dev->disconnect_work); + dev->cbits_to_modem = 0; + queue_work(dev->wq, &dev->set_modem_ctl_bits_work); +} + +#define SDIO_OPEN_RETRY_DELAY msecs_to_jiffies(2000) +#define SDIO_OPEN_MAX_RETRY 90 +static void rmnet_open_sdio_work(struct work_struct *w) +{ + struct rmnet_dev *dev = + container_of(w, struct rmnet_dev, sdio_open_work.work); + struct usb_composite_dev *cdev = dev->cdev; + int ret; + static int retry_cnt; + static bool ctl_ch_opened, data_ch_opened; + + if (!ctl_ch_opened) { + /* Control channel for QMI messages */ + ret = sdio_cmux_open(rmnet_sdio_ctl_ch, rmnet_ctl_receive_cb, + rmnet_ctl_write_done, rmnet_sts_callback, dev); + if (!ret) + ctl_ch_opened = true; + } + if (!data_ch_opened) { + /* Data channel for network packets */ + ret = msm_sdio_dmux_open(rmnet_sdio_data_ch, dev, + rmnet_data_receive_cb, + rmnet_data_write_done); + if (!ret) + data_ch_opened = true; + } + + if (ctl_ch_opened && data_ch_opened) { + atomic_set(&dev->sdio_open, 1); + + /* if usb cable is connected, update DTR status to modem */ + if (atomic_read(&dev->online)) + queue_work(dev->wq, &dev->set_modem_ctl_bits_work); + + pr_info("%s: usb rmnet sdio channels are open retry_cnt:%d\n", + __func__, retry_cnt); + return; + } + + retry_cnt++; + pr_debug("%s: usb rmnet sdio open retry_cnt:%d\n", + __func__, retry_cnt); + + if (retry_cnt > SDIO_OPEN_MAX_RETRY) { + if (!ctl_ch_opened) + ERROR(cdev, "Unable to open control SDIO channel\n"); + else + sdio_cmux_close(rmnet_sdio_ctl_ch); + + if (!data_ch_opened) + ERROR(cdev, "Unable to open DATA SDIO channel\n"); + else + msm_sdio_dmux_close(rmnet_sdio_data_ch); + + } else { + queue_delayed_work(dev->wq, &dev->sdio_open_work, + SDIO_OPEN_RETRY_DELAY); + } +} + +static int rmnet_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int ret, i; + + /* allocate notification */ + dev->notify_req = rmnet_alloc_req(dev->epnotify, + RMNET_SDIO_MAX_NFY_SZE, GFP_ATOMIC); + + if (IS_ERR(dev->notify_req)) { + ret = PTR_ERR(dev->notify_req); + goto free_buf; + } + for (i = 0; i < RMNET_SDIO_RX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epout, 0, GFP_ATOMIC); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + goto free_buf; + } + req->complete = rmnet_complete_epout; + list_add_tail(&req->list, &dev->rx_idle); + } + for (i = 0; i < RMNET_SDIO_TX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epin, 0, GFP_ATOMIC); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + goto free_buf; + } + req->complete = rmnet_complete_epin; + list_add_tail(&req->list, &dev->tx_idle); + } + + dev->notify_req->complete = rmnet_notify_complete; + dev->notify_req->context = dev; + dev->notify_req->length = RMNET_SDIO_MAX_NFY_SZE; + + dev->epin->driver_data = dev; + usb_ep_enable(dev->epin, ep_choose(cdev->gadget, + &rmnet_hs_in_desc, + &rmnet_fs_in_desc)); + dev->epout->driver_data = dev; + usb_ep_enable(dev->epout, ep_choose(cdev->gadget, + &rmnet_hs_out_desc, + &rmnet_fs_out_desc)); + usb_ep_enable(dev->epnotify, ep_choose(cdev->gadget, + &rmnet_hs_notify_desc, + &rmnet_fs_notify_desc)); + + atomic_set(&dev->online, 1); + + /* Queue Rx data requests */ + rmnet_start_rx(dev); + + return 0; + +free_buf: + rmnet_free_buf(dev); + dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ + return ret; +} + +static int rmnet_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + int id; + struct usb_ep *ep; + + dev->cdev = cdev; + + /* allocate interface ID */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + dev->ifc_id = id; + rmnet_interface_desc.bInterfaceNumber = id; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_in_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epin = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_out_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epout = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_notify_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epnotify = ep; + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + rmnet_hs_in_desc.bEndpointAddress = + rmnet_fs_in_desc.bEndpointAddress; + rmnet_hs_out_desc.bEndpointAddress = + rmnet_fs_out_desc.bEndpointAddress; + rmnet_hs_notify_desc.bEndpointAddress = + rmnet_fs_notify_desc.bEndpointAddress; + } + + queue_delayed_work(dev->wq, &dev->sdio_open_work, 0); + + return 0; + +out: + if (dev->epnotify) + dev->epnotify->driver_data = NULL; + if (dev->epout) + dev->epout->driver_data = NULL; + if (dev->epin) + dev->epin->driver_data = NULL; + + return -ENODEV; +} + +static void +rmnet_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + + destroy_workqueue(dev->wq); + + rmnet_free_buf(dev); + dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ + + msm_sdio_dmux_close(rmnet_sdio_data_ch); + sdio_cmux_close(rmnet_sdio_ctl_ch); + + atomic_set(&dev->sdio_open, 0); + + kfree(dev); +} + +#if defined(CONFIG_DEBUG_FS) +static ssize_t debug_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct rmnet_dev *dev = file->private_data; + char *buf; + unsigned long flags; + int ret; + + buf = kzalloc(sizeof(char) * 1024, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + spin_lock_irqsave(&dev->lock, flags); + ret = scnprintf(buf, PAGE_SIZE, + "dpkts_to_modem: %lu\n" + "dpkts_to_laptop: %lu\n" + "cpkts_to_modem: %lu\n" + "cpkts_to_laptop: %lu\n" + "cbits_to_modem: %d\n" + "tx skb size: %u\n" + "rx_skb_size: %u\n" + "dpkts_pending_at_dmux: %u\n" + "tx drp cnt: %lu\n" + "cbits_tomodem: %d", + dev->dpkt_tomodem, dev->dpkt_tolaptop, + dev->cpkt_tomodem, dev->cpkt_tolaptop, + dev->cbits_to_modem, + dev->tx_skb_queue.qlen, dev->rx_skb_queue.qlen, + dev->dpkts_pending_atdmux, dev->tx_drp_cnt, + dev->cbits_to_modem); + + spin_unlock_irqrestore(&dev->lock, flags); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + + kfree(buf); + + return ret; +} + +static ssize_t debug_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct rmnet_dev *dev = file->private_data; + + dev->dpkt_tolaptop = 0; + dev->dpkt_tomodem = 0; + dev->cpkt_tolaptop = 0; + dev->cpkt_tomodem = 0; + dev->dpkts_pending_atdmux = 0; + dev->tx_drp_cnt = 0; + + /* TBD: How do we reset skb qlen + * it might have side effects + */ + + return count; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +const struct file_operations debug_stats_ops = { + .open = debug_open, + .read = debug_read_stats, + .write = debug_reset_stats, +}; + +static void usb_debugfs_init(struct rmnet_dev *dev) +{ + struct dentry *dent; + + dent = debugfs_create_dir("usb_rmnet", 0); + if (IS_ERR(dent)) + return; + + debugfs_create_file("status", 0444, dent, dev, &debug_stats_ops); +} +#else +static void usb_debugfs_init(struct rmnet_dev *dev) +{ + return; +} +#endif + +int rmnet_sdio_function_add(struct usb_configuration *c) +{ + struct rmnet_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->wq = create_singlethread_workqueue("k_rmnet_work"); + if (!dev->wq) { + ret = -ENOMEM; + goto free_dev; + } + + spin_lock_init(&dev->lock); + atomic_set(&dev->notify_count, 0); + atomic_set(&dev->online, 0); + + INIT_WORK(&dev->disconnect_work, rmnet_disconnect_work); + INIT_WORK(&dev->set_modem_ctl_bits_work, rmnet_set_modem_ctl_bits_work); + + INIT_WORK(&dev->ctl_rx_work, rmnet_control_rx_work); + INIT_WORK(&dev->data_rx_work, rmnet_data_rx_work); + + INIT_DELAYED_WORK(&dev->sdio_open_work, rmnet_open_sdio_work); + + INIT_LIST_HEAD(&dev->qmi_req_q); + INIT_LIST_HEAD(&dev->qmi_resp_q); + + INIT_LIST_HEAD(&dev->rx_idle); + INIT_LIST_HEAD(&dev->tx_idle); + skb_queue_head_init(&dev->tx_skb_queue); + skb_queue_head_init(&dev->rx_skb_queue); + + dev->function.name = "rmnet_sdio"; + dev->function.strings = rmnet_strings; + dev->function.descriptors = rmnet_fs_function; + dev->function.hs_descriptors = rmnet_hs_function; + dev->function.bind = rmnet_bind; + dev->function.unbind = rmnet_unbind; + dev->function.setup = rmnet_setup; + dev->function.set_alt = rmnet_set_alt; + dev->function.disable = rmnet_disable; + dev->function.suspend = rmnet_suspend; + + ret = usb_add_function(c, &dev->function); + if (ret) + goto free_wq; + + usb_debugfs_init(dev); + + return 0; + +free_wq: + destroy_workqueue(dev->wq); +free_dev: + kfree(dev); + + return ret; +} + +#ifdef CONFIG_USB_ANDROID_RMNET_SDIO +static struct android_usb_function rmnet_function = { + .name = "rmnet_sdio", + .bind_config = rmnet_sdio_function_add, +}; + +static int __init rmnet_init(void) +{ + android_register_function(&rmnet_function); + return 0; +} +module_init(rmnet_init); + +#endif /* CONFIG_USB_ANDROID_RMNET_SDIO */ diff --git a/drivers/usb/gadget/f_rmnet_smd.c b/drivers/usb/gadget/f_rmnet_smd.c new file mode 100644 index 00000000000..00925f9fcaa --- /dev/null +++ b/drivers/usb/gadget/f_rmnet_smd.c @@ -0,0 +1,1333 @@ +/* + * f_rmnet.c -- RmNet function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 Nokia Corporation + * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gadget_chips.h" + +static char *rmnet_ctl_ch = CONFIG_RMNET_SMD_CTL_CHANNEL; +module_param(rmnet_ctl_ch, charp, S_IRUGO); +MODULE_PARM_DESC(rmnet_ctl_ch, "RmNet control SMD channel"); + +static char *rmnet_data_ch = CONFIG_RMNET_SMD_DATA_CHANNEL; +module_param(rmnet_data_ch, charp, S_IRUGO); +MODULE_PARM_DESC(rmnet_data_ch, "RmNet data SMD channel"); + +#define ACM_CTRL_DTR (1 << 0) + +#define RMNET_NOTIFY_INTERVAL 5 +#define RMNET_MAX_NOTIFY_SIZE sizeof(struct usb_cdc_notification) + +#define QMI_REQ_MAX 4 +#define QMI_REQ_SIZE 2048 +#define QMI_RESP_MAX 8 +#define QMI_RESP_SIZE 2048 + +#define RX_REQ_MAX 8 +#define RX_REQ_SIZE 2048 +#define TX_REQ_MAX 8 +#define TX_REQ_SIZE 2048 + +#define TXN_MAX 2048 + +/* QMI requests & responses buffer*/ +struct qmi_buf { + void *buf; + int len; + struct list_head list; +}; + +/* Control & data SMD channel private data */ +struct rmnet_smd_info { + struct smd_channel *ch; + struct tasklet_struct tx_tlet; + struct tasklet_struct rx_tlet; +#define CH_OPENED 0 + unsigned long flags; + /* pending rx packet length */ + atomic_t rx_pkt; + /* wait for smd open event*/ + wait_queue_head_t wait; +}; + +struct rmnet_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + + struct usb_ep *epout; + struct usb_ep *epin; + struct usb_ep *epnotify; + struct usb_request *notify_req; + + u8 ifc_id; + /* QMI lists */ + struct list_head qmi_req_pool; + struct list_head qmi_resp_pool; + struct list_head qmi_req_q; + struct list_head qmi_resp_q; + /* Tx/Rx lists */ + struct list_head tx_idle; + struct list_head rx_idle; + struct list_head rx_queue; + + spinlock_t lock; + atomic_t online; + atomic_t notify_count; + + struct rmnet_smd_info smd_ctl; + struct rmnet_smd_info smd_data; + + struct workqueue_struct *wq; + struct work_struct connect_work; + struct work_struct disconnect_work; + + unsigned long dpkts_to_host; + unsigned long dpkts_from_modem; + unsigned long dpkts_from_host; + unsigned long dpkts_to_modem; + + unsigned long cpkts_to_host; + unsigned long cpkts_from_modem; + unsigned long cpkts_from_host; + unsigned long cpkts_to_modem; +}; + +static struct usb_interface_descriptor rmnet_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, + /* .iInterface = DYNAMIC */ +}; + +/* Full speed support */ +static struct usb_endpoint_descriptor rmnet_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_MAX_NOTIFY_SIZE), + .bInterval = 1 << RMNET_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor rmnet_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor rmnet_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_descriptor_header *rmnet_fs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_fs_notify_desc, + (struct usb_descriptor_header *) &rmnet_fs_in_desc, + (struct usb_descriptor_header *) &rmnet_fs_out_desc, + NULL, +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_MAX_NOTIFY_SIZE), + .bInterval = RMNET_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor rmnet_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor rmnet_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_descriptor_header *rmnet_hs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_hs_notify_desc, + (struct usb_descriptor_header *) &rmnet_hs_in_desc, + (struct usb_descriptor_header *) &rmnet_hs_out_desc, + NULL, +}; + +/* String descriptors */ + +static struct usb_string rmnet_string_defs[] = { + [0].s = "QMI RmNet", + { } /* end of list */ +}; + +static struct usb_gadget_strings rmnet_string_table = { + .language = 0x0409, /* en-us */ + .strings = rmnet_string_defs, +}; + +static struct usb_gadget_strings *rmnet_strings[] = { + &rmnet_string_table, + NULL, +}; + +static struct qmi_buf * +rmnet_alloc_qmi(unsigned len, gfp_t kmalloc_flags) +{ + struct qmi_buf *qmi; + + qmi = kmalloc(sizeof(struct qmi_buf), kmalloc_flags); + if (qmi != NULL) { + qmi->buf = kmalloc(len, kmalloc_flags); + if (qmi->buf == NULL) { + kfree(qmi); + qmi = NULL; + } + } + + return qmi ? qmi : ERR_PTR(-ENOMEM); +} + +static void rmnet_free_qmi(struct qmi_buf *qmi) +{ + kfree(qmi->buf); + kfree(qmi); +} +/* + * Allocate a usb_request and its buffer. Returns a pointer to the + * usb_request or a error code if there is an error. + */ +static struct usb_request * +rmnet_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, kmalloc_flags); + + if (req != NULL) { + req->length = len; + req->buf = kmalloc(len, kmalloc_flags); + if (req->buf == NULL) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + + return req ? req : ERR_PTR(-ENOMEM); +} + +/* + * Free a usb_request and its buffer. + */ +static void rmnet_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static void rmnet_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + atomic_set(&dev->notify_count, 0); + break; + default: + ERROR(cdev, "rmnet notify ep error %d\n", status); + /* FALLTHROUGH */ + case 0: + if (ep != dev->epnotify) + break; + + /* handle multiple pending QMI_RESPONSE_AVAILABLE + * notifications by resending until we're done + */ + if (atomic_dec_and_test(&dev->notify_count)) + break; + + status = usb_ep_queue(dev->epnotify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&dev->notify_count); + ERROR(cdev, "rmnet notify ep enqueue error %d\n", + status); + } + break; + } +} + +static void qmi_response_available(struct rmnet_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = dev->notify_req; + struct usb_cdc_notification *event = req->buf; + int status; + + /* Response will be sent later */ + if (atomic_inc_return(&dev->notify_count) != 1) + return; + + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; + event->wValue = cpu_to_le16(0); + event->wIndex = cpu_to_le16(dev->ifc_id); + event->wLength = cpu_to_le16(0); + + status = usb_ep_queue(dev->epnotify, dev->notify_req, GFP_ATOMIC); + if (status < 0) { + atomic_dec(&dev->notify_count); + ERROR(cdev, "rmnet notify ep enqueue error %d\n", status); + } +} + +/* TODO + * handle modem restart events + */ +static void rmnet_smd_notify(void *priv, unsigned event) +{ + struct rmnet_smd_info *smd_info = priv; + int len = atomic_read(&smd_info->rx_pkt); + struct rmnet_dev *dev = (struct rmnet_dev *) smd_info->tx_tlet.data; + + switch (event) { + case SMD_EVENT_DATA: { + if (!atomic_read(&dev->online)) + break; + if (len && (smd_write_avail(smd_info->ch) >= len)) + tasklet_schedule(&smd_info->rx_tlet); + + if (smd_read_avail(smd_info->ch)) + tasklet_schedule(&smd_info->tx_tlet); + + break; + } + case SMD_EVENT_OPEN: + /* usb endpoints are not enabled untill smd channels + * are opened. wake up worker thread to continue + * connection processing + */ + set_bit(CH_OPENED, &smd_info->flags); + wake_up(&smd_info->wait); + break; + case SMD_EVENT_CLOSE: + /* We will never come here. + * reset flags after closing smd channel + * */ + clear_bit(CH_OPENED, &smd_info->flags); + break; + } +} + +static void rmnet_control_tx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct usb_composite_dev *cdev = dev->cdev; + struct qmi_buf *qmi_resp; + int sz; + unsigned long flags; + + while (1) { + sz = smd_cur_packet_size(dev->smd_ctl.ch); + if (sz == 0) + break; + if (smd_read_avail(dev->smd_ctl.ch) < sz) + break; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(&dev->qmi_resp_pool)) { + ERROR(cdev, "rmnet QMI Tx buffers full\n"); + spin_unlock_irqrestore(&dev->lock, flags); + break; + } + qmi_resp = list_first_entry(&dev->qmi_resp_pool, + struct qmi_buf, list); + list_del(&qmi_resp->list); + spin_unlock_irqrestore(&dev->lock, flags); + + qmi_resp->len = smd_read(dev->smd_ctl.ch, qmi_resp->buf, sz); + + spin_lock_irqsave(&dev->lock, flags); + dev->cpkts_from_modem++; + list_add_tail(&qmi_resp->list, &dev->qmi_resp_q); + spin_unlock_irqrestore(&dev->lock, flags); + + qmi_response_available(dev); + } + +} + +static void rmnet_control_rx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct usb_composite_dev *cdev = dev->cdev; + struct qmi_buf *qmi_req; + int ret; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + while (1) { + + if (list_empty(&dev->qmi_req_q)) { + atomic_set(&dev->smd_ctl.rx_pkt, 0); + break; + } + qmi_req = list_first_entry(&dev->qmi_req_q, + struct qmi_buf, list); + if (smd_write_avail(dev->smd_ctl.ch) < qmi_req->len) { + atomic_set(&dev->smd_ctl.rx_pkt, qmi_req->len); + DBG(cdev, "rmnet control smd channel full\n"); + break; + } + + list_del(&qmi_req->list); + dev->cpkts_from_host++; + spin_unlock_irqrestore(&dev->lock, flags); + ret = smd_write(dev->smd_ctl.ch, qmi_req->buf, qmi_req->len); + spin_lock_irqsave(&dev->lock, flags); + if (ret != qmi_req->len) { + ERROR(cdev, "rmnet control smd write failed\n"); + break; + } + dev->cpkts_to_modem++; + list_add_tail(&qmi_req->list, &dev->qmi_req_pool); + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_command_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + struct qmi_buf *qmi_req; + int ret; + + if (req->status < 0) { + ERROR(cdev, "rmnet command error %d\n", req->status); + return; + } + + spin_lock(&dev->lock); + dev->cpkts_from_host++; + /* no pending control rx packet */ + if (!atomic_read(&dev->smd_ctl.rx_pkt)) { + if (smd_write_avail(dev->smd_ctl.ch) < req->actual) { + atomic_set(&dev->smd_ctl.rx_pkt, req->actual); + goto queue_req; + } + spin_unlock(&dev->lock); + ret = smd_write(dev->smd_ctl.ch, req->buf, req->actual); + /* This should never happen */ + if (ret != req->actual) + ERROR(cdev, "rmnet control smd write failed\n"); + spin_lock(&dev->lock); + dev->cpkts_to_modem++; + spin_unlock(&dev->lock); + return; + } +queue_req: + if (list_empty(&dev->qmi_req_pool)) { + spin_unlock(&dev->lock); + ERROR(cdev, "rmnet QMI pool is empty\n"); + return; + } + + qmi_req = list_first_entry(&dev->qmi_req_pool, struct qmi_buf, list); + list_del(&qmi_req->list); + spin_unlock(&dev->lock); + memcpy(qmi_req->buf, req->buf, req->actual); + qmi_req->len = req->actual; + spin_lock(&dev->lock); + list_add_tail(&qmi_req->list, &dev->qmi_req_q); + spin_unlock(&dev->lock); +} +static void rmnet_txcommand_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + + spin_lock(&dev->lock); + dev->cpkts_to_host++; + spin_unlock(&dev->lock); +} + +static int +rmnet_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int ret = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + struct qmi_buf *resp; + int schedule = 0; + + if (!atomic_read(&dev->online)) + return -ENOTCONN; + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (w_length > req->length) + goto invalid; + ret = w_length; + req->complete = rmnet_command_complete; + req->context = dev; + break; + + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (w_value) + goto invalid; + else { + spin_lock(&dev->lock); + if (list_empty(&dev->qmi_resp_q)) { + INFO(cdev, "qmi resp empty " + " req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + spin_unlock(&dev->lock); + goto invalid; + } + resp = list_first_entry(&dev->qmi_resp_q, + struct qmi_buf, list); + list_del(&resp->list); + spin_unlock(&dev->lock); + memcpy(req->buf, resp->buf, resp->len); + ret = resp->len; + spin_lock(&dev->lock); + + if (list_empty(&dev->qmi_resp_pool)) + schedule = 1; + list_add_tail(&resp->list, &dev->qmi_resp_pool); + + if (schedule) + tasklet_schedule(&dev->smd_ctl.tx_tlet); + spin_unlock(&dev->lock); + req->complete = rmnet_txcommand_complete; + req->context = dev; + } + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + /* This is a workaround for RmNet and is borrowed from the + * CDC/ACM standard. The host driver will issue the above ACM + * standard request to the RmNet interface in the following + * scenario: Once the network adapter is disabled from device + * manager, the above request will be sent from the qcusbnet + * host driver, with DTR being '0'. Once network adapter is + * enabled from device manager (or during enumeration), the + * request will be sent with DTR being '1'. + */ + if (w_value & ACM_CTRL_DTR) + ret = smd_tiocmset(dev->smd_ctl.ch, TIOCM_DTR, 0); + else + ret = smd_tiocmset(dev->smd_ctl.ch, 0, TIOCM_DTR); + + break; + default: + +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (ret >= 0) { + VDBG(cdev, "rmnet req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = ret; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret < 0) + ERROR(cdev, "rmnet ep0 enqueue err %d\n", ret); + } + + return ret; +} + +static void rmnet_start_rx(struct rmnet_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + int status; + struct usb_request *req; + struct list_head *pool = &dev->rx_idle; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + while (!list_empty(pool)) { + req = list_entry(pool->next, struct usb_request, list); + list_del(&req->list); + + spin_unlock_irqrestore(&dev->lock, flags); + status = usb_ep_queue(dev->epout, req, GFP_ATOMIC); + spin_lock_irqsave(&dev->lock, flags); + + if (status) { + ERROR(cdev, "rmnet data rx enqueue err %d\n", status); + list_add_tail(&req->list, pool); + break; + } + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_data_tx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int status; + int sz; + unsigned long flags; + + while (1) { + + sz = smd_cur_packet_size(dev->smd_data.ch); + if (sz == 0) + break; + if (smd_read_avail(dev->smd_data.ch) < sz) + break; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(&dev->tx_idle)) { + spin_unlock_irqrestore(&dev->lock, flags); + DBG(cdev, "rmnet data Tx buffers full\n"); + break; + } + req = list_first_entry(&dev->tx_idle, struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&dev->lock, flags); + + req->length = smd_read(dev->smd_data.ch, req->buf, sz); + status = usb_ep_queue(dev->epin, req, GFP_ATOMIC); + if (status) { + ERROR(cdev, "rmnet tx data enqueue err %d\n", status); + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, &dev->tx_idle); + spin_unlock_irqrestore(&dev->lock, flags); + break; + } + spin_lock_irqsave(&dev->lock, flags); + dev->dpkts_from_modem++; + spin_unlock_irqrestore(&dev->lock, flags); + } + +} + +static void rmnet_data_rx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int ret; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + while (1) { + if (list_empty(&dev->rx_queue)) { + atomic_set(&dev->smd_data.rx_pkt, 0); + break; + } + req = list_first_entry(&dev->rx_queue, + struct usb_request, list); + if (smd_write_avail(dev->smd_data.ch) < req->actual) { + atomic_set(&dev->smd_data.rx_pkt, req->actual); + DBG(cdev, "rmnet SMD data channel full\n"); + break; + } + + list_del(&req->list); + spin_unlock_irqrestore(&dev->lock, flags); + ret = smd_write(dev->smd_data.ch, req->buf, req->actual); + spin_lock_irqsave(&dev->lock, flags); + if (ret != req->actual) { + ERROR(cdev, "rmnet SMD data write failed\n"); + break; + } + dev->dpkts_to_modem++; + list_add_tail(&req->list, &dev->rx_idle); + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* We have free rx data requests. */ + rmnet_start_rx(dev); +} + +/* If SMD has enough room to accommodate a data rx packet, + * write into SMD directly. Otherwise enqueue to rx_queue. + * We will not write into SMD directly untill rx_queue is + * empty to strictly follow the ordering requests. + */ +static void rmnet_complete_epout(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + int ret; + + switch (status) { + case 0: + /* normal completion */ + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + spin_lock(&dev->lock); + list_add_tail(&req->list, &dev->rx_idle); + spin_unlock(&dev->lock); + return; + default: + /* unexpected failure */ + ERROR(cdev, "RMNET %s response error %d, %d/%d\n", + ep->name, status, + req->actual, req->length); + spin_lock(&dev->lock); + list_add_tail(&req->list, &dev->rx_idle); + spin_unlock(&dev->lock); + return; + } + + spin_lock(&dev->lock); + dev->dpkts_from_host++; + if (!atomic_read(&dev->smd_data.rx_pkt)) { + if (smd_write_avail(dev->smd_data.ch) < req->actual) { + atomic_set(&dev->smd_data.rx_pkt, req->actual); + goto queue_req; + } + spin_unlock(&dev->lock); + ret = smd_write(dev->smd_data.ch, req->buf, req->actual); + /* This should never happen */ + if (ret != req->actual) + ERROR(cdev, "rmnet data smd write failed\n"); + /* Restart Rx */ + spin_lock(&dev->lock); + dev->dpkts_to_modem++; + list_add_tail(&req->list, &dev->rx_idle); + spin_unlock(&dev->lock); + rmnet_start_rx(dev); + return; + } +queue_req: + list_add_tail(&req->list, &dev->rx_queue); + spin_unlock(&dev->lock); +} + +static void rmnet_complete_epin(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + int schedule = 0; + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + spin_lock(&dev->lock); + list_add_tail(&req->list, &dev->tx_idle); + spin_unlock(&dev->lock); + break; + default: + ERROR(cdev, "rmnet data tx ep error %d\n", status); + /* FALLTHROUGH */ + case 0: + spin_lock(&dev->lock); + if (list_empty(&dev->tx_idle)) + schedule = 1; + list_add_tail(&req->list, &dev->tx_idle); + dev->dpkts_to_host++; + if (schedule) + tasklet_schedule(&dev->smd_data.tx_tlet); + spin_unlock(&dev->lock); + break; + } + +} + +static void rmnet_disconnect_work(struct work_struct *w) +{ + struct qmi_buf *qmi; + struct usb_request *req; + struct list_head *act, *tmp; + struct rmnet_dev *dev = container_of(w, struct rmnet_dev, + disconnect_work); + + tasklet_kill(&dev->smd_ctl.rx_tlet); + tasklet_kill(&dev->smd_ctl.tx_tlet); + tasklet_kill(&dev->smd_data.rx_tlet); + tasklet_kill(&dev->smd_data.tx_tlet); + + smd_close(dev->smd_ctl.ch); + dev->smd_ctl.flags = 0; + + smd_close(dev->smd_data.ch); + dev->smd_data.flags = 0; + + atomic_set(&dev->notify_count, 0); + + list_for_each_safe(act, tmp, &dev->rx_queue) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + list_add_tail(&req->list, &dev->rx_idle); + } + + list_for_each_safe(act, tmp, &dev->qmi_req_q) { + qmi = list_entry(act, struct qmi_buf, list); + list_del(&qmi->list); + list_add_tail(&qmi->list, &dev->qmi_req_pool); + } + + list_for_each_safe(act, tmp, &dev->qmi_resp_q) { + qmi = list_entry(act, struct qmi_buf, list); + list_del(&qmi->list); + list_add_tail(&qmi->list, &dev->qmi_resp_pool); + } + +} + +/* SMD close may sleep + * schedule a work to close smd channels + */ +static void rmnet_disable(struct usb_function *f) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + + if (!atomic_read(&dev->online)) + return; + + atomic_set(&dev->online, 0); + + usb_ep_fifo_flush(dev->epnotify); + usb_ep_disable(dev->epnotify); + usb_ep_fifo_flush(dev->epout); + usb_ep_disable(dev->epout); + + usb_ep_fifo_flush(dev->epin); + usb_ep_disable(dev->epin); + + /* cleanup work */ + queue_work(dev->wq, &dev->disconnect_work); +} + +static void rmnet_connect_work(struct work_struct *w) +{ + struct rmnet_dev *dev = container_of(w, struct rmnet_dev, connect_work); + struct usb_composite_dev *cdev = dev->cdev; + int ret = 0; + + /* Control channel for QMI messages */ + ret = smd_open(rmnet_ctl_ch, &dev->smd_ctl.ch, + &dev->smd_ctl, rmnet_smd_notify); + if (ret) { + ERROR(cdev, "Unable to open control smd channel\n"); + return; + } + wait_event(dev->smd_ctl.wait, test_bit(CH_OPENED, + &dev->smd_ctl.flags)); + + /* Data channel for network packets */ + ret = smd_open(rmnet_data_ch, &dev->smd_data.ch, + &dev->smd_data, rmnet_smd_notify); + if (ret) { + ERROR(cdev, "Unable to open data smd channel\n"); + smd_close(dev->smd_ctl.ch); + return; + } + wait_event(dev->smd_data.wait, test_bit(CH_OPENED, + &dev->smd_data.flags)); + + atomic_set(&dev->online, 1); + /* Queue Rx data requests */ + rmnet_start_rx(dev); +} + +/* SMD open may sleep. + * Schedule a work to open smd channels and enable + * endpoints if smd channels are opened successfully. + */ +static int rmnet_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct usb_composite_dev *cdev = dev->cdev; + int ret = 0; + + ret = usb_ep_enable(dev->epin, ep_choose(cdev->gadget, + &rmnet_hs_in_desc, + &rmnet_fs_in_desc)); + if (ret) { + ERROR(cdev, "can't enable %s, result %d\n", + dev->epin->name, ret); + return ret; + } + ret = usb_ep_enable(dev->epout, ep_choose(cdev->gadget, + &rmnet_hs_out_desc, + &rmnet_fs_out_desc)); + if (ret) { + ERROR(cdev, "can't enable %s, result %d\n", + dev->epout->name, ret); + usb_ep_disable(dev->epin); + return ret; + } + + ret = usb_ep_enable(dev->epnotify, ep_choose(cdev->gadget, + &rmnet_hs_notify_desc, + &rmnet_fs_notify_desc)); + if (ret) { + ERROR(cdev, "can't enable %s, result %d\n", + dev->epnotify->name, ret); + usb_ep_disable(dev->epin); + usb_ep_disable(dev->epout); + return ret; + } + + queue_work(dev->wq, &dev->connect_work); + return 0; +} + +static void rmnet_free_buf(struct rmnet_dev *dev) +{ + struct qmi_buf *qmi; + struct usb_request *req; + struct list_head *act, *tmp; + + dev->dpkts_to_host = 0; + dev->dpkts_from_modem = 0; + dev->dpkts_from_host = 0; + dev->dpkts_to_modem = 0; + + dev->cpkts_to_host = 0; + dev->cpkts_from_modem = 0; + dev->cpkts_from_host = 0; + dev->cpkts_to_modem = 0; + /* free all usb requests in tx pool */ + list_for_each_safe(act, tmp, &dev->tx_idle) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epout, req); + } + + /* free all usb requests in rx pool */ + list_for_each_safe(act, tmp, &dev->rx_idle) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epin, req); + } + + /* free all buffers in qmi request pool */ + list_for_each_safe(act, tmp, &dev->qmi_req_pool) { + qmi = list_entry(act, struct qmi_buf, list); + list_del(&qmi->list); + rmnet_free_qmi(qmi); + } + + /* free all buffers in qmi request pool */ + list_for_each_safe(act, tmp, &dev->qmi_resp_pool) { + qmi = list_entry(act, struct qmi_buf, list); + list_del(&qmi->list); + rmnet_free_qmi(qmi); + } + + rmnet_free_req(dev->epnotify, dev->notify_req); +} +static int rmnet_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + int i, id, ret; + struct qmi_buf *qmi; + struct usb_request *req; + struct usb_ep *ep; + + dev->cdev = cdev; + + /* allocate interface ID */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + dev->ifc_id = id; + rmnet_interface_desc.bInterfaceNumber = id; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_in_desc); + if (!ep) + return -ENODEV; + ep->driver_data = cdev; /* claim endpoint */ + dev->epin = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_out_desc); + if (!ep) + return -ENODEV; + ep->driver_data = cdev; /* claim endpoint */ + dev->epout = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_notify_desc); + if (!ep) + return -ENODEV; + ep->driver_data = cdev; /* clain endpoint */ + dev->epnotify = ep; + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + rmnet_hs_in_desc.bEndpointAddress = + rmnet_fs_in_desc.bEndpointAddress; + rmnet_hs_out_desc.bEndpointAddress = + rmnet_fs_out_desc.bEndpointAddress; + rmnet_hs_notify_desc.bEndpointAddress = + rmnet_fs_notify_desc.bEndpointAddress; + + } + + /* allocate notification */ + dev->notify_req = rmnet_alloc_req(dev->epnotify, RMNET_MAX_NOTIFY_SIZE, + GFP_KERNEL); + if (IS_ERR(dev->notify_req)) + return PTR_ERR(dev->notify_req); + + dev->notify_req->complete = rmnet_notify_complete; + dev->notify_req->context = dev; + dev->notify_req->length = RMNET_MAX_NOTIFY_SIZE; + + /* Allocate the qmi request and response buffers */ + for (i = 0; i < QMI_REQ_MAX; i++) { + qmi = rmnet_alloc_qmi(QMI_REQ_SIZE, GFP_KERNEL); + if (IS_ERR(qmi)) { + ret = PTR_ERR(qmi); + goto free_buf; + } + list_add_tail(&qmi->list, &dev->qmi_req_pool); + } + + for (i = 0; i < QMI_RESP_MAX; i++) { + qmi = rmnet_alloc_qmi(QMI_RESP_SIZE, GFP_KERNEL); + if (IS_ERR(qmi)) { + ret = PTR_ERR(qmi); + goto free_buf; + } + list_add_tail(&qmi->list, &dev->qmi_resp_pool); + } + + /* Allocate bulk in/out requests for data transfer */ + for (i = 0; i < RX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epout, RX_REQ_SIZE, GFP_KERNEL); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + goto free_buf; + } + req->length = TXN_MAX; + req->context = dev; + req->complete = rmnet_complete_epout; + list_add_tail(&req->list, &dev->rx_idle); + } + + for (i = 0; i < TX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epin, TX_REQ_SIZE, GFP_KERNEL); + if (IS_ERR(req)) { + ret = PTR_ERR(req); + goto free_buf; + } + req->context = dev; + req->complete = rmnet_complete_epin; + list_add_tail(&req->list, &dev->tx_idle); + } + + return 0; + +free_buf: + rmnet_free_buf(dev); + dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ + return ret; +} + +#if defined(CONFIG_DEBUG_FS) +static ssize_t debug_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct rmnet_dev *dev = file->private_data; + struct rmnet_smd_info smd_ctl_info = dev->smd_ctl; + struct rmnet_smd_info smd_data_info = dev->smd_data; + char *buf; + unsigned long flags; + int ret; + + buf = kzalloc(sizeof(char) * 512, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + spin_lock_irqsave(&dev->lock, flags); + ret = scnprintf(buf, 512, + "smd_control_ch_opened: %lu\n" + "smd_data_ch_opened: %lu\n" + "usb online : %d\n" + "dpkts_from_modem: %lu\n" + "dpkts_to_host: %lu\n" + "pending_dpkts_to_host: %lu\n" + "dpkts_from_host: %lu\n" + "dpkts_to_modem: %lu\n" + "pending_dpkts_to_modem: %lu\n" + "cpkts_from_modem: %lu\n" + "cpkts_to_host: %lu\n" + "pending_cpkts_to_host: %lu\n" + "cpkts_from_host: %lu\n" + "cpkts_to_modem: %lu\n" + "pending_cpkts_to_modem: %lu\n" + "smd_read_avail_ctrl: %d\n" + "smd_write_avail_ctrl: %d\n" + "smd_read_avail_data: %d\n" + "smd_write_avail_data: %d\n", + smd_ctl_info.flags, smd_data_info.flags, + atomic_read(&dev->online), + dev->dpkts_from_modem, dev->dpkts_to_host, + (dev->dpkts_from_modem - dev->dpkts_to_host), + dev->dpkts_from_host, dev->dpkts_to_modem, + (dev->dpkts_from_host - dev->dpkts_to_modem), + dev->cpkts_from_modem, dev->cpkts_to_host, + (dev->cpkts_from_modem - dev->cpkts_to_host), + dev->cpkts_from_host, dev->cpkts_to_modem, + (dev->cpkts_from_host - dev->cpkts_to_modem), + smd_read_avail(dev->smd_ctl.ch), + smd_write_avail(dev->smd_ctl.ch), + smd_read_avail(dev->smd_data.ch), + smd_write_avail(dev->smd_data.ch)); + + spin_unlock_irqrestore(&dev->lock, flags); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + + kfree(buf); + + return ret; +} + +static ssize_t debug_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct rmnet_dev *dev = file->private_data; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + dev->dpkts_to_host = 0; + dev->dpkts_from_modem = 0; + dev->dpkts_from_host = 0; + dev->dpkts_to_modem = 0; + + dev->cpkts_to_host = 0; + dev->cpkts_from_modem = 0; + dev->cpkts_from_host = 0; + dev->cpkts_to_modem = 0; + + spin_unlock_irqrestore(&dev->lock, flags); + + return count; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +const struct file_operations rmnet_debug_stats_ops = { + .open = debug_open, + .read = debug_read_stats, + .write = debug_reset_stats, +}; + +struct dentry *dent; +struct dentry *dent_status; + +static void usb_debugfs_init(struct rmnet_dev *dev) +{ + + dent = debugfs_create_dir("usb_rmnet", 0); + if (IS_ERR(dent)) + return; + + dent_status = debugfs_create_file("status", 0444, dent, dev, + &rmnet_debug_stats_ops); + + if (!dent_status) { + debugfs_remove(dent); + dent = NULL; + return; + } + + return; +} +#else +static void usb_debugfs_init(struct rmnet_dev *dev) {} +#endif + +static void +rmnet_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + + tasklet_kill(&dev->smd_ctl.rx_tlet); + tasklet_kill(&dev->smd_ctl.tx_tlet); + tasklet_kill(&dev->smd_data.rx_tlet); + tasklet_kill(&dev->smd_data.tx_tlet); + + flush_workqueue(dev->wq); + rmnet_free_buf(dev); + dev->epout = dev->epin = dev->epnotify = NULL; /* release endpoints */ + + destroy_workqueue(dev->wq); + debugfs_remove_recursive(dent); + kfree(dev); + +} + +int rmnet_function_add(struct usb_configuration *c) +{ + struct rmnet_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->wq = create_singlethread_workqueue("k_rmnet_work"); + if (!dev->wq) { + ret = -ENOMEM; + goto free_dev; + } + + spin_lock_init(&dev->lock); + atomic_set(&dev->notify_count, 0); + atomic_set(&dev->online, 0); + atomic_set(&dev->smd_ctl.rx_pkt, 0); + atomic_set(&dev->smd_data.rx_pkt, 0); + + INIT_WORK(&dev->connect_work, rmnet_connect_work); + INIT_WORK(&dev->disconnect_work, rmnet_disconnect_work); + + tasklet_init(&dev->smd_ctl.rx_tlet, rmnet_control_rx_tlet, + (unsigned long) dev); + tasklet_init(&dev->smd_ctl.tx_tlet, rmnet_control_tx_tlet, + (unsigned long) dev); + tasklet_init(&dev->smd_data.rx_tlet, rmnet_data_rx_tlet, + (unsigned long) dev); + tasklet_init(&dev->smd_data.tx_tlet, rmnet_data_tx_tlet, + (unsigned long) dev); + + init_waitqueue_head(&dev->smd_ctl.wait); + init_waitqueue_head(&dev->smd_data.wait); + + INIT_LIST_HEAD(&dev->qmi_req_pool); + INIT_LIST_HEAD(&dev->qmi_req_q); + INIT_LIST_HEAD(&dev->qmi_resp_pool); + INIT_LIST_HEAD(&dev->qmi_resp_q); + INIT_LIST_HEAD(&dev->rx_idle); + INIT_LIST_HEAD(&dev->rx_queue); + INIT_LIST_HEAD(&dev->tx_idle); + + dev->function.name = "rmnet"; + dev->function.strings = rmnet_strings; + dev->function.descriptors = rmnet_fs_function; + dev->function.hs_descriptors = rmnet_hs_function; + dev->function.bind = rmnet_bind; + dev->function.unbind = rmnet_unbind; + dev->function.setup = rmnet_setup; + dev->function.set_alt = rmnet_set_alt; + dev->function.disable = rmnet_disable; + + ret = usb_add_function(c, &dev->function); + if (ret) + goto free_wq; + + usb_debugfs_init(dev); + + return 0; + +free_wq: + destroy_workqueue(dev->wq); +free_dev: + kfree(dev); + + return ret; +} + +#ifdef CONFIG_USB_ANDROID_RMNET +static struct android_usb_function rmnet_function = { + .name = "rmnet", + .bind_config = rmnet_function_add, +}; + +static int __init init(void) +{ + android_register_function(&rmnet_function); + return 0; +} +module_init(init); + +#endif /* CONFIG_USB_ANDROID_RMNET */ diff --git a/drivers/usb/gadget/f_rmnet_smd_sdio.c b/drivers/usb/gadget/f_rmnet_smd_sdio.c new file mode 100644 index 00000000000..e99716b7cf5 --- /dev/null +++ b/drivers/usb/gadget/f_rmnet_smd_sdio.c @@ -0,0 +1,1995 @@ +/* + * f_rmnet_smd_sdio.c -- RmNet SMD & SDIO function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 Nokia Corporation + * Copyright (c) 2011 Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static uint32_t rmnet_sdio_ctl_ch = CONFIG_RMNET_SMD_SDIO_CTL_CHANNEL; +module_param(rmnet_sdio_ctl_ch, uint, S_IRUGO); +MODULE_PARM_DESC(rmnet_sdio_ctl_ch, "RmNet control SDIO channel ID"); + +static uint32_t rmnet_sdio_data_ch = CONFIG_RMNET_SMD_SDIO_DATA_CHANNEL; +module_param(rmnet_sdio_data_ch, uint, S_IRUGO); +MODULE_PARM_DESC(rmnet_sdio_data_ch, "RmNet data SDIO channel ID"); + +static char *rmnet_smd_data_ch = CONFIG_RMNET_SDIO_SMD_DATA_CHANNEL; +module_param(rmnet_smd_data_ch, charp, S_IRUGO); +MODULE_PARM_DESC(rmnet_smd_data_ch, "RmNet data SMD channel"); + +#define ACM_CTRL_DTR (1 << 0) + +#define SDIO_MUX_HDR 8 +#define RMNET_SDIO_NOTIFY_INTERVAL 5 +#define RMNET_SDIO_MAX_NFY_SZE sizeof(struct usb_cdc_notification) + +#define RMNET_SDIO_RX_REQ_MAX 16 +#define RMNET_SDIO_RX_REQ_SIZE 2048 +#define RMNET_SDIO_TX_REQ_MAX 100 + +#define RMNET_SDIO_TX_PKT_DROP_THRESHOLD 1000 +#define RMNET_SDIO_RX_PKT_FLOW_CTRL_EN_THRESHOLD 1000 +#define RMNET_SDIO_RX_PKT_FLOW_CTRL_DISABLE 500 + +static uint32_t sdio_tx_pkt_drop_thld = RMNET_SDIO_TX_PKT_DROP_THRESHOLD; +module_param(sdio_tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR); + +static uint32_t sdio_rx_fctrl_en_thld = + RMNET_SDIO_RX_PKT_FLOW_CTRL_EN_THRESHOLD; +module_param(sdio_rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +static uint32_t sdio_rx_fctrl_dis_thld = RMNET_SDIO_RX_PKT_FLOW_CTRL_DISABLE; +module_param(sdio_rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + + +#define RMNET_SMD_RX_REQ_MAX 8 +#define RMNET_SMD_RX_REQ_SIZE 2048 +#define RMNET_SMD_TX_REQ_MAX 8 +#define RMNET_SMD_TX_REQ_SIZE 2048 +#define RMNET_SMD_TXN_MAX 2048 + +struct rmnet_ctrl_pkt { + void *buf; + int len; + struct list_head list; +}; + +enum usb_rmnet_xport_type { + USB_RMNET_XPORT_UNDEFINED, + USB_RMNET_XPORT_SDIO, + USB_RMNET_XPORT_SMD, +}; + +struct rmnet_ctrl_dev { + struct list_head tx_q; + wait_queue_head_t tx_wait_q; + unsigned long tx_len; + + struct list_head rx_q; + unsigned long rx_len; + + unsigned long cbits_to_modem; + + unsigned opened; +}; + +struct rmnet_sdio_dev { + /* Tx/Rx lists */ + struct list_head tx_idle; + struct sk_buff_head tx_skb_queue; + struct list_head rx_idle; + struct sk_buff_head rx_skb_queue; + + + + struct work_struct data_rx_work; + + struct delayed_work open_work; + atomic_t sdio_open; + + unsigned int dpkts_pending_atdmux; +}; + +/* Data SMD channel */ +struct rmnet_smd_info { + struct smd_channel *ch; + struct tasklet_struct tx_tlet; + struct tasklet_struct rx_tlet; +#define CH_OPENED 0 + unsigned long flags; + /* pending rx packet length */ + atomic_t rx_pkt; + /* wait for smd open event*/ + wait_queue_head_t wait; +}; + +struct rmnet_smd_dev { + /* Tx/Rx lists */ + struct list_head tx_idle; + struct list_head rx_idle; + struct list_head rx_queue; + + struct rmnet_smd_info smd_data; +}; + +struct rmnet_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + + struct usb_ep *epout; + struct usb_ep *epin; + struct usb_ep *epnotify; + struct usb_request *notify_req; + + struct rmnet_smd_dev smd_dev; + struct rmnet_sdio_dev sdio_dev; + struct rmnet_ctrl_dev ctrl_dev; + + u8 ifc_id; + enum usb_rmnet_xport_type xport; + spinlock_t lock; + atomic_t online; + atomic_t notify_count; + struct workqueue_struct *wq; + struct work_struct disconnect_work; + + /* pkt counters */ + unsigned long dpkts_tomsm; + unsigned long dpkts_tomdm; + unsigned long dpkts_tolaptop; + unsigned long tx_drp_cnt; + unsigned long cpkts_tolaptop; + unsigned long cpkts_tomdm; + unsigned long cpkts_drp_cnt; +}; + +static struct rmnet_dev *_dev; + +static struct usb_interface_descriptor rmnet_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, +}; + +/* Full speed support */ +static struct usb_endpoint_descriptor rmnet_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_SDIO_MAX_NFY_SZE), + .bInterval = 1 << RMNET_SDIO_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor rmnet_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_endpoint_descriptor rmnet_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), +}; + +static struct usb_descriptor_header *rmnet_fs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_fs_notify_desc, + (struct usb_descriptor_header *) &rmnet_fs_in_desc, + (struct usb_descriptor_header *) &rmnet_fs_out_desc, + NULL, +}; + +/* High speed support */ +static struct usb_endpoint_descriptor rmnet_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(RMNET_SDIO_MAX_NFY_SZE), + .bInterval = RMNET_SDIO_NOTIFY_INTERVAL + 4, +}; + +static struct usb_endpoint_descriptor rmnet_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor rmnet_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_descriptor_header *rmnet_hs_function[] = { + (struct usb_descriptor_header *) &rmnet_interface_desc, + (struct usb_descriptor_header *) &rmnet_hs_notify_desc, + (struct usb_descriptor_header *) &rmnet_hs_in_desc, + (struct usb_descriptor_header *) &rmnet_hs_out_desc, + NULL, +}; + +/* String descriptors */ + +static struct usb_string rmnet_string_defs[] = { + [0].s = "RmNet", + { } /* end of list */ +}; + +static struct usb_gadget_strings rmnet_string_table = { + .language = 0x0409, /* en-us */ + .strings = rmnet_string_defs, +}; + +static struct usb_gadget_strings *rmnet_strings[] = { + &rmnet_string_table, + NULL, +}; + +static char *xport_to_str(enum usb_rmnet_xport_type t) +{ + switch (t) { + case USB_RMNET_XPORT_SDIO: + return "SDIO"; + case USB_RMNET_XPORT_SMD: + return "SMD"; + default: + return "UNDEFINED"; + } +} + +static struct rmnet_ctrl_pkt *rmnet_alloc_ctrl_pkt(unsigned len, gfp_t flags) +{ + struct rmnet_ctrl_pkt *cpkt; + + cpkt = kzalloc(sizeof(struct rmnet_ctrl_pkt), flags); + if (!cpkt) + return 0; + + cpkt->buf = kzalloc(len, flags); + if (!cpkt->buf) { + kfree(cpkt); + return 0; + } + + cpkt->len = len; + + return cpkt; + +} + +static void rmnet_free_ctrl_pkt(struct rmnet_ctrl_pkt *cpkt) +{ + kfree(cpkt->buf); + kfree(cpkt); +} + +/* + * Allocate a usb_request and its buffer. Returns a pointer to the + * usb_request or a pointer with an error code if there is an error. + */ +static struct usb_request * +rmnet_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, kmalloc_flags); + + if (len && req != NULL) { + req->length = len; + req->buf = kmalloc(len, kmalloc_flags); + if (req->buf == NULL) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + + return req ? req : ERR_PTR(-ENOMEM); +} + +/* + * Free a usb_request and its buffer. + */ +static void rmnet_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static int rmnet_sdio_rx_submit(struct rmnet_dev *dev, struct usb_request *req, + gfp_t gfp_flags) +{ + struct sk_buff *skb; + int retval; + + skb = alloc_skb(RMNET_SDIO_RX_REQ_SIZE + SDIO_MUX_HDR, gfp_flags); + if (skb == NULL) + return -ENOMEM; + skb_reserve(skb, SDIO_MUX_HDR); + + req->buf = skb->data; + req->length = RMNET_SDIO_RX_REQ_SIZE; + req->context = skb; + + retval = usb_ep_queue(dev->epout, req, gfp_flags); + if (retval) + dev_kfree_skb_any(skb); + + return retval; +} + +static void rmnet_sdio_start_rx(struct rmnet_dev *dev) +{ + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct usb_composite_dev *cdev = dev->cdev; + int status; + struct usb_request *req; + struct list_head *pool; + unsigned long flags; + + if (!atomic_read(&dev->online)) { + pr_debug("%s: USB not connected\n", __func__); + return; + } + + spin_lock_irqsave(&dev->lock, flags); + pool = &sdio_dev->rx_idle; + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + + spin_unlock_irqrestore(&dev->lock, flags); + status = rmnet_sdio_rx_submit(dev, req, GFP_KERNEL); + spin_lock_irqsave(&dev->lock, flags); + + if (status) { + ERROR(cdev, "rmnet data rx enqueue err %d\n", status); + list_add_tail(&req->list, &sdio_dev->rx_idle); + break; + } + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_sdio_start_tx(struct rmnet_dev *dev) +{ + unsigned long flags; + int status; + struct sk_buff *skb; + struct usb_request *req; + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct usb_composite_dev *cdev = dev->cdev; + + + if (!atomic_read(&dev->online)) + return; + + spin_lock_irqsave(&dev->lock, flags); + while (!list_empty(&sdio_dev->tx_idle)) { + skb = __skb_dequeue(&sdio_dev->tx_skb_queue); + if (!skb) { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + req = list_first_entry(&sdio_dev->tx_idle, + struct usb_request, list); + req->context = skb; + req->buf = skb->data; + req->length = skb->len; + + list_del(&req->list); + spin_unlock(&dev->lock); + status = usb_ep_queue(dev->epin, req, GFP_ATOMIC); + spin_lock(&dev->lock); + if (status) { + /* USB still online, queue requests back */ + if (atomic_read(&dev->online)) { + ERROR(cdev, "rmnet tx data enqueue err %d\n", + status); + list_add_tail(&req->list, &sdio_dev->tx_idle); + __skb_queue_head(&sdio_dev->tx_skb_queue, skb); + } else { + req->buf = 0; + rmnet_free_req(dev->epin, req); + dev_kfree_skb_any(skb); + } + break; + } + dev->dpkts_tolaptop++; + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_sdio_data_receive_cb(void *priv, struct sk_buff *skb) +{ + struct rmnet_dev *dev = priv; + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + unsigned long flags; + + if (!skb) + return; + if (!atomic_read(&dev->online)) { + dev_kfree_skb_any(skb); + return; + } + spin_lock_irqsave(&dev->lock, flags); + if (sdio_dev->tx_skb_queue.qlen > sdio_tx_pkt_drop_thld) { + pr_err_ratelimited("%s: tx pkt dropped: tx_drop_cnt:%lu\n", + __func__, dev->tx_drp_cnt); + dev->tx_drp_cnt++; + spin_unlock_irqrestore(&dev->lock, flags); + dev_kfree_skb_any(skb); + return; + } + __skb_queue_tail(&sdio_dev->tx_skb_queue, skb); + spin_unlock_irqrestore(&dev->lock, flags); + rmnet_sdio_start_tx(dev); +} + +static void rmnet_sdio_data_write_done(void *priv, struct sk_buff *skb) +{ + struct rmnet_dev *dev = priv; + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + + if (!skb) + return; + + dev_kfree_skb_any(skb); + /* this function is called from + * sdio mux from spin_lock_irqsave + */ + spin_lock(&dev->lock); + sdio_dev->dpkts_pending_atdmux--; + + if (sdio_dev->dpkts_pending_atdmux >= sdio_rx_fctrl_dis_thld) { + spin_unlock(&dev->lock); + return; + } + spin_unlock(&dev->lock); + + rmnet_sdio_start_rx(dev); +} + +static void rmnet_sdio_data_rx_work(struct work_struct *w) +{ + struct rmnet_dev *dev = container_of(w, struct rmnet_dev, + sdio_dev.data_rx_work); + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct usb_composite_dev *cdev = dev->cdev; + + struct sk_buff *skb; + int ret; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + while ((skb = __skb_dequeue(&sdio_dev->rx_skb_queue))) { + spin_unlock_irqrestore(&dev->lock, flags); + ret = msm_sdio_dmux_write(rmnet_sdio_data_ch, skb); + spin_lock_irqsave(&dev->lock, flags); + if (ret < 0) { + ERROR(cdev, "rmnet SDIO data write failed\n"); + dev_kfree_skb_any(skb); + } else { + dev->dpkts_tomdm++; + sdio_dev->dpkts_pending_atdmux++; + } + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void +rmnet_sdio_complete_epout(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = ep->driver_data; + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct usb_composite_dev *cdev = dev->cdev; + struct sk_buff *skb = req->context; + int status = req->status; + int queue = 0; + + if (dev->xport == USB_RMNET_XPORT_UNDEFINED) { + dev_kfree_skb_any(skb); + req->buf = 0; + rmnet_free_req(ep, req); + return; + } + + switch (status) { + case 0: + /* successful completion */ + skb_put(skb, req->actual); + queue = 1; + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + dev_kfree_skb_any(skb); + req->buf = 0; + rmnet_free_req(ep, req); + return; + default: + /* unexpected failure */ + ERROR(cdev, "RMNET %s response error %d, %d/%d\n", + ep->name, status, + req->actual, req->length); + dev_kfree_skb_any(skb); + break; + } + + spin_lock(&dev->lock); + if (queue) { + __skb_queue_tail(&sdio_dev->rx_skb_queue, skb); + queue_work(dev->wq, &sdio_dev->data_rx_work); + } + + if (sdio_dev->dpkts_pending_atdmux >= sdio_rx_fctrl_en_thld) { + list_add_tail(&req->list, &sdio_dev->rx_idle); + spin_unlock(&dev->lock); + return; + } + spin_unlock(&dev->lock); + + status = rmnet_sdio_rx_submit(dev, req, GFP_ATOMIC); + if (status) { + ERROR(cdev, "rmnet data rx enqueue err %d\n", status); + list_add_tail(&req->list, &sdio_dev->rx_idle); + } +} + +static void +rmnet_sdio_complete_epin(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = ep->driver_data; + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct sk_buff *skb = req->context; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + + if (dev->xport == USB_RMNET_XPORT_UNDEFINED) { + dev_kfree_skb_any(skb); + req->buf = 0; + rmnet_free_req(ep, req); + return; + } + + switch (status) { + case 0: + /* successful completion */ + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + break; + default: + ERROR(cdev, "rmnet data tx ep error %d\n", status); + break; + } + + spin_lock(&dev->lock); + list_add_tail(&req->list, &sdio_dev->tx_idle); + spin_unlock(&dev->lock); + dev_kfree_skb_any(skb); + + rmnet_sdio_start_tx(dev); +} + +static int rmnet_sdio_enable(struct rmnet_dev *dev) +{ + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + int i; + struct usb_request *req; + + /* + * If the memory allocation fails, all the allocated + * requests will be freed upon cable disconnect. + */ + for (i = 0; i < RMNET_SDIO_RX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epout, 0, GFP_KERNEL); + if (IS_ERR(req)) + return PTR_ERR(req); + req->complete = rmnet_sdio_complete_epout; + list_add_tail(&req->list, &sdio_dev->rx_idle); + } + for (i = 0; i < RMNET_SDIO_TX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epin, 0, GFP_KERNEL); + if (IS_ERR(req)) + return PTR_ERR(req); + req->complete = rmnet_sdio_complete_epin; + list_add_tail(&req->list, &sdio_dev->tx_idle); + } + + rmnet_sdio_start_rx(dev); + return 0; +} + +static void rmnet_smd_start_rx(struct rmnet_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + int status; + struct usb_request *req; + struct list_head *pool = &smd_dev->rx_idle; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + while (!list_empty(pool)) { + req = list_entry(pool->next, struct usb_request, list); + list_del(&req->list); + + spin_unlock_irqrestore(&dev->lock, flags); + status = usb_ep_queue(dev->epout, req, GFP_ATOMIC); + spin_lock_irqsave(&dev->lock, flags); + + if (status) { + ERROR(cdev, "rmnet data rx enqueue err %d\n", status); + list_add_tail(&req->list, pool); + break; + } + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_smd_data_tx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int status; + int sz; + unsigned long flags; + + while (1) { + if (!atomic_read(&dev->online)) + break; + sz = smd_cur_packet_size(smd_dev->smd_data.ch); + if (sz == 0) + break; + if (smd_read_avail(smd_dev->smd_data.ch) < sz) + break; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(&smd_dev->tx_idle)) { + spin_unlock_irqrestore(&dev->lock, flags); + DBG(cdev, "rmnet data Tx buffers full\n"); + break; + } + req = list_first_entry(&smd_dev->tx_idle, + struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&dev->lock, flags); + + req->length = smd_read(smd_dev->smd_data.ch, req->buf, sz); + status = usb_ep_queue(dev->epin, req, GFP_ATOMIC); + if (status) { + ERROR(cdev, "rmnet tx data enqueue err %d\n", status); + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, &smd_dev->tx_idle); + spin_unlock_irqrestore(&dev->lock, flags); + break; + } + dev->dpkts_tolaptop++; + } + +} + +static void rmnet_smd_data_rx_tlet(unsigned long arg) +{ + struct rmnet_dev *dev = (struct rmnet_dev *) arg; + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int ret; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + while (1) { + if (!atomic_read(&dev->online)) + break; + if (list_empty(&smd_dev->rx_queue)) { + atomic_set(&smd_dev->smd_data.rx_pkt, 0); + break; + } + req = list_first_entry(&smd_dev->rx_queue, + struct usb_request, list); + if (smd_write_avail(smd_dev->smd_data.ch) < req->actual) { + atomic_set(&smd_dev->smd_data.rx_pkt, req->actual); + DBG(cdev, "rmnet SMD data channel full\n"); + break; + } + + list_del(&req->list); + spin_unlock_irqrestore(&dev->lock, flags); + ret = smd_write(smd_dev->smd_data.ch, req->buf, req->actual); + spin_lock_irqsave(&dev->lock, flags); + if (ret != req->actual) { + ERROR(cdev, "rmnet SMD data write failed\n"); + break; + } + dev->dpkts_tomsm++; + list_add_tail(&req->list, &smd_dev->rx_idle); + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* We have free rx data requests. */ + rmnet_smd_start_rx(dev); +} + +/* If SMD has enough room to accommodate a data rx packet, + * write into SMD directly. Otherwise enqueue to rx_queue. + * We will not write into SMD directly untill rx_queue is + * empty to strictly follow the ordering requests. + */ +static void +rmnet_smd_complete_epout(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + int ret; + + if (dev->xport == USB_RMNET_XPORT_UNDEFINED) { + rmnet_free_req(ep, req); + return; + } + + switch (status) { + case 0: + /* normal completion */ + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + spin_lock(&dev->lock); + list_add_tail(&req->list, &smd_dev->rx_idle); + spin_unlock(&dev->lock); + return; + default: + /* unexpected failure */ + ERROR(cdev, "RMNET %s response error %d, %d/%d\n", + ep->name, status, + req->actual, req->length); + spin_lock(&dev->lock); + list_add_tail(&req->list, &smd_dev->rx_idle); + spin_unlock(&dev->lock); + return; + } + + spin_lock(&dev->lock); + if (!atomic_read(&smd_dev->smd_data.rx_pkt)) { + if (smd_write_avail(smd_dev->smd_data.ch) < req->actual) { + atomic_set(&smd_dev->smd_data.rx_pkt, req->actual); + goto queue_req; + } + spin_unlock(&dev->lock); + ret = smd_write(smd_dev->smd_data.ch, req->buf, req->actual); + /* This should never happen */ + if (ret != req->actual) + ERROR(cdev, "rmnet data smd write failed\n"); + /* Restart Rx */ + dev->dpkts_tomsm++; + spin_lock(&dev->lock); + list_add_tail(&req->list, &smd_dev->rx_idle); + spin_unlock(&dev->lock); + rmnet_smd_start_rx(dev); + return; + } +queue_req: + list_add_tail(&req->list, &smd_dev->rx_queue); + spin_unlock(&dev->lock); +} + +static void rmnet_smd_complete_epin(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + int schedule = 0; + + if (dev->xport == USB_RMNET_XPORT_UNDEFINED) { + rmnet_free_req(ep, req); + return; + } + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + spin_lock(&dev->lock); + list_add_tail(&req->list, &smd_dev->tx_idle); + spin_unlock(&dev->lock); + break; + default: + ERROR(cdev, "rmnet data tx ep error %d\n", status); + /* FALLTHROUGH */ + case 0: + spin_lock(&dev->lock); + if (list_empty(&smd_dev->tx_idle)) + schedule = 1; + list_add_tail(&req->list, &smd_dev->tx_idle); + + if (schedule) + tasklet_schedule(&smd_dev->smd_data.tx_tlet); + spin_unlock(&dev->lock); + break; + } + +} + + +static void rmnet_smd_notify(void *priv, unsigned event) +{ + struct rmnet_dev *dev = priv; + struct rmnet_smd_info *smd_info = &dev->smd_dev.smd_data; + int len = atomic_read(&smd_info->rx_pkt); + + switch (event) { + case SMD_EVENT_DATA: { + if (!atomic_read(&dev->online)) + break; + if (len && (smd_write_avail(smd_info->ch) >= len)) + tasklet_schedule(&smd_info->rx_tlet); + + if (smd_read_avail(smd_info->ch)) + tasklet_schedule(&smd_info->tx_tlet); + + break; + } + case SMD_EVENT_OPEN: + /* usb endpoints are not enabled untill smd channels + * are opened. wake up worker thread to continue + * connection processing + */ + set_bit(CH_OPENED, &smd_info->flags); + wake_up(&smd_info->wait); + break; + case SMD_EVENT_CLOSE: + /* We will never come here. + * reset flags after closing smd channel + * */ + clear_bit(CH_OPENED, &smd_info->flags); + break; + } +} + +static int rmnet_smd_enable(struct rmnet_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + int i, ret; + struct usb_request *req; + + if (test_bit(CH_OPENED, &smd_dev->smd_data.flags)) + goto smd_alloc_req; + + ret = smd_open(rmnet_smd_data_ch, &smd_dev->smd_data.ch, + dev, rmnet_smd_notify); + if (ret) { + ERROR(cdev, "Unable to open data smd channel\n"); + return ret; + } + + wait_event(smd_dev->smd_data.wait, test_bit(CH_OPENED, + &smd_dev->smd_data.flags)); + + /* Allocate bulk in/out requests for data transfer. + * If the memory allocation fails, all the allocated + * requests will be freed upon cable disconnect. + */ +smd_alloc_req: + for (i = 0; i < RMNET_SMD_RX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epout, RMNET_SMD_RX_REQ_SIZE, + GFP_KERNEL); + if (IS_ERR(req)) + return PTR_ERR(req); + req->length = RMNET_SMD_TXN_MAX; + req->context = dev; + req->complete = rmnet_smd_complete_epout; + list_add_tail(&req->list, &smd_dev->rx_idle); + } + + for (i = 0; i < RMNET_SMD_TX_REQ_MAX; i++) { + req = rmnet_alloc_req(dev->epin, RMNET_SMD_TX_REQ_SIZE, + GFP_KERNEL); + if (IS_ERR(req)) + return PTR_ERR(req); + req->context = dev; + req->complete = rmnet_smd_complete_epin; + list_add_tail(&req->list, &smd_dev->tx_idle); + } + + rmnet_smd_start_rx(dev); + return 0; +} + +static void rmnet_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + int status = req->status; + + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + atomic_set(&dev->notify_count, 0); + break; + default: + ERROR(cdev, "rmnet notifyep error %d\n", status); + /* FALLTHROUGH */ + case 0: + + if (atomic_dec_and_test(&dev->notify_count)) + break; + + status = usb_ep_queue(dev->epnotify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&dev->notify_count); + ERROR(cdev, "rmnet notify ep enq error %d\n", status); + } + break; + } +} + +static void ctrl_response_available(struct rmnet_dev *dev) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = dev->notify_req; + struct usb_cdc_notification *event = req->buf; + int status; + + /* Response will be sent later */ + if (atomic_inc_return(&dev->notify_count) != 1) + return; + + event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE; + event->wValue = cpu_to_le16(0); + event->wIndex = cpu_to_le16(dev->ifc_id); + event->wLength = cpu_to_le16(0); + + status = usb_ep_queue(dev->epnotify, dev->notify_req, GFP_ATOMIC); + if (status < 0) { + atomic_dec(&dev->notify_count); + ERROR(cdev, "rmnet notify ep enqueue error %d\n", status); + } +} + +#define MAX_CTRL_PKT_SIZE 4096 + +static void rmnet_response_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + + switch (req->status) { + case -ECONNRESET: + case -ESHUTDOWN: + case 0: + return; + default: + INFO(cdev, "rmnet %s response error %d, %d/%d\n", + ep->name, req->status, + req->actual, req->length); + } +} + +static void rmnet_command_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct rmnet_dev *dev = req->context; + struct usb_composite_dev *cdev = dev->cdev; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + struct rmnet_ctrl_pkt *cpkt; + int len = req->actual; + + if (req->status < 0) { + ERROR(cdev, "rmnet command error %d\n", req->status); + return; + } + + cpkt = rmnet_alloc_ctrl_pkt(len, GFP_ATOMIC); + if (!cpkt) { + ERROR(cdev, "unable to allocate memory for ctrl req\n"); + return; + } + + spin_lock(&dev->lock); + if (!ctrl_dev->opened) { + spin_unlock(&dev->lock); + kfree(cpkt); + dev->cpkts_drp_cnt++; + pr_err_ratelimited( + "%s: ctrl pkts dropped: cpkts_drp_cnt: %lu\n", + __func__, dev->cpkts_drp_cnt); + return; + } + + memcpy(cpkt->buf, req->buf, len); + + list_add_tail(&cpkt->list, &ctrl_dev->tx_q); + ctrl_dev->tx_len++; + spin_unlock(&dev->lock); + + /* wakeup read thread */ + wake_up(&ctrl_dev->tx_wait_q); +} + +static int +rmnet_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int ret = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + struct rmnet_ctrl_pkt *cpkt; + + if (!atomic_read(&dev->online)) + return -ENOTCONN; + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (w_length > req->length) + goto invalid; + ret = w_length; + req->complete = rmnet_command_complete; + req->context = dev; + break; + + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (w_value) + goto invalid; + else { + unsigned len; + + spin_lock(&dev->lock); + if (list_empty(&ctrl_dev->rx_q)) { + DBG(cdev, "ctrl resp queue empty" + " %02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + spin_unlock(&dev->lock); + goto invalid; + + } + cpkt = list_first_entry(&ctrl_dev->rx_q, + struct rmnet_ctrl_pkt, list); + list_del(&cpkt->list); + ctrl_dev->rx_len--; + spin_unlock(&dev->lock); + + len = min_t(unsigned, w_length, cpkt->len); + memcpy(req->buf, cpkt->buf, len); + ret = len; + req->complete = rmnet_response_complete; + req->context = dev; + rmnet_free_ctrl_pkt(cpkt); + + dev->cpkts_tolaptop++; + } + break; + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + /* This is a workaround for RmNet and is borrowed from the + * CDC/ACM standard. The host driver will issue the above ACM + * standard request to the RmNet interface in the following + * scenario: Once the network adapter is disabled from device + * manager, the above request will be sent from the qcusbnet + * host driver, with DTR being '0'. Once network adapter is + * enabled from device manager (or during enumeration), the + * request will be sent with DTR being '1'. + */ + if (w_value & ACM_CTRL_DTR) + ctrl_dev->cbits_to_modem |= TIOCM_DTR; + else + ctrl_dev->cbits_to_modem &= ~TIOCM_DTR; + + ret = 0; + + break; + default: + +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (ret >= 0) { + VDBG(cdev, "rmnet req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = (ret < w_length); + req->length = ret; + ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (ret < 0) + ERROR(cdev, "rmnet ep0 enqueue err %d\n", ret); + } + + return ret; +} + +static void rmnet_free_buf(struct rmnet_dev *dev) +{ + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + struct rmnet_ctrl_pkt *cpkt; + struct usb_request *req; + struct list_head *pool; + struct sk_buff *skb; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + /* free all usb requests in SDIO tx pool */ + pool = &sdio_dev->tx_idle; + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + req->buf = NULL; + rmnet_free_req(dev->epout, req); + } + + pool = &sdio_dev->rx_idle; + /* free all usb requests in SDIO rx pool */ + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + req->buf = NULL; + rmnet_free_req(dev->epin, req); + } + + while ((skb = __skb_dequeue(&sdio_dev->tx_skb_queue))) + dev_kfree_skb_any(skb); + + while ((skb = __skb_dequeue(&sdio_dev->rx_skb_queue))) + dev_kfree_skb_any(skb); + + /* free all usb requests in SMD tx pool */ + pool = &smd_dev->tx_idle; + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epout, req); + } + + pool = &smd_dev->rx_idle; + /* free all usb requests in SMD rx pool */ + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epin, req); + } + + /* free all usb requests in SMD rx queue */ + pool = &smd_dev->rx_queue; + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epin, req); + } + + pool = &ctrl_dev->tx_q; + while (!list_empty(pool)) { + cpkt = list_first_entry(pool, struct rmnet_ctrl_pkt, list); + list_del(&cpkt->list); + rmnet_free_ctrl_pkt(cpkt); + ctrl_dev->tx_len--; + } + + pool = &ctrl_dev->rx_q; + while (!list_empty(pool)) { + cpkt = list_first_entry(pool, struct rmnet_ctrl_pkt, list); + list_del(&cpkt->list); + rmnet_free_ctrl_pkt(cpkt); + ctrl_dev->rx_len--; + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +static void rmnet_disconnect_work(struct work_struct *w) +{ + struct rmnet_dev *dev = container_of(w, struct rmnet_dev, + disconnect_work); + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + + if (dev->xport == USB_RMNET_XPORT_SMD) { + tasklet_kill(&smd_dev->smd_data.rx_tlet); + tasklet_kill(&smd_dev->smd_data.tx_tlet); + } + + rmnet_free_buf(dev); + dev->xport = 0; + + /* wakeup read thread */ + wake_up(&ctrl_dev->tx_wait_q); +} + +static void rmnet_suspend(struct usb_function *f) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + + if (!atomic_read(&dev->online)) + return; + /* This is a workaround for Windows Host bug during suspend. + * Windows 7/xp Hosts are suppose to drop DTR, when Host suspended. + * Since it is not being done, Hence exclusively dropping the DTR + * from function driver suspend. + */ + ctrl_dev->cbits_to_modem &= ~TIOCM_DTR; +} + +static void rmnet_disable(struct usb_function *f) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + + if (!atomic_read(&dev->online)) + return; + + atomic_set(&dev->online, 0); + + usb_ep_fifo_flush(dev->epnotify); + usb_ep_disable(dev->epnotify); + rmnet_free_req(dev->epnotify, dev->notify_req); + + usb_ep_fifo_flush(dev->epout); + usb_ep_disable(dev->epout); + + usb_ep_fifo_flush(dev->epin); + usb_ep_disable(dev->epin); + + /* cleanup work */ + ctrl_dev->cbits_to_modem = 0; + queue_work(dev->wq, &dev->disconnect_work); +} + +#define SDIO_OPEN_RETRY_DELAY msecs_to_jiffies(2000) +#define SDIO_OPEN_MAX_RETRY 90 +static void rmnet_open_sdio_work(struct work_struct *w) +{ + struct rmnet_dev *dev = + container_of(w, struct rmnet_dev, sdio_dev.open_work.work); + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct usb_composite_dev *cdev = dev->cdev; + int ret; + static int retry_cnt; + + /* Data channel for network packets */ + ret = msm_sdio_dmux_open(rmnet_sdio_data_ch, dev, + rmnet_sdio_data_receive_cb, + rmnet_sdio_data_write_done); + if (ret) { + if (retry_cnt > SDIO_OPEN_MAX_RETRY) { + ERROR(cdev, "Unable to open SDIO DATA channel\n"); + return; + } + retry_cnt++; + queue_delayed_work(dev->wq, &sdio_dev->open_work, + SDIO_OPEN_RETRY_DELAY); + return; + } + + + atomic_set(&sdio_dev->sdio_open, 1); + pr_info("%s: usb rmnet sdio channels are open retry_cnt:%d\n", + __func__, retry_cnt); + retry_cnt = 0; + return; +} + +static int rmnet_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct usb_composite_dev *cdev = dev->cdev; + + /* allocate notification */ + dev->notify_req = rmnet_alloc_req(dev->epnotify, + RMNET_SDIO_MAX_NFY_SZE, GFP_ATOMIC); + + if (IS_ERR(dev->notify_req)) + return PTR_ERR(dev->notify_req); + + dev->notify_req->complete = rmnet_notify_complete; + dev->notify_req->context = dev; + dev->notify_req->length = RMNET_SDIO_MAX_NFY_SZE; + usb_ep_enable(dev->epnotify, ep_choose(cdev->gadget, + &rmnet_hs_notify_desc, + &rmnet_fs_notify_desc)); + + dev->epin->driver_data = dev; + usb_ep_enable(dev->epin, ep_choose(cdev->gadget, + &rmnet_hs_in_desc, + &rmnet_fs_in_desc)); + dev->epout->driver_data = dev; + usb_ep_enable(dev->epout, ep_choose(cdev->gadget, + &rmnet_hs_out_desc, + &rmnet_fs_out_desc)); + + dev->dpkts_tolaptop = 0; + dev->cpkts_tolaptop = 0; + dev->cpkts_tomdm = 0; + dev->dpkts_tomdm = 0; + dev->dpkts_tomsm = 0; + dev->tx_drp_cnt = 0; + dev->cpkts_drp_cnt = 0; + sdio_dev->dpkts_pending_atdmux = 0; + atomic_set(&dev->online, 1); + + return 0; +} + +static ssize_t transport_store( + struct device *device, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_function *f = dev_get_drvdata(device); + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + int value; + enum usb_rmnet_xport_type given_xport; + enum usb_rmnet_xport_type t; + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct list_head *pool; + struct sk_buff_head *skb_pool; + struct sk_buff *skb; + struct usb_request *req; + unsigned long flags; + + if (!atomic_read(&dev->online)) { + pr_err("%s: usb cable is not connected\n", __func__); + return -EINVAL; + } + + sscanf(buf, "%d", &value); + if (value) + given_xport = USB_RMNET_XPORT_SDIO; + else + given_xport = USB_RMNET_XPORT_SMD; + + if (given_xport == dev->xport) { + pr_err("%s: given_xport:%s cur_xport:%s doing nothing\n", + __func__, xport_to_str(given_xport), + xport_to_str(dev->xport)); + return 0; + } + + pr_debug("usb_rmnet: TransportRequested: %s\n", + xport_to_str(given_xport)); + + /* prevent any other pkts to/from usb */ + t = dev->xport; + dev->xport = USB_RMNET_XPORT_UNDEFINED; + if (t != USB_RMNET_XPORT_UNDEFINED) { + usb_ep_fifo_flush(dev->epin); + usb_ep_fifo_flush(dev->epout); + } + + switch (t) { + case USB_RMNET_XPORT_SDIO: + spin_lock_irqsave(&dev->lock, flags); + /* tx_idle */ + + sdio_dev->dpkts_pending_atdmux = 0; + + pool = &sdio_dev->tx_idle; + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + req->buf = NULL; + rmnet_free_req(dev->epout, req); + } + + /* rx_idle */ + pool = &sdio_dev->rx_idle; + /* free all usb requests in SDIO rx pool */ + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + req->buf = NULL; + rmnet_free_req(dev->epin, req); + } + + /* tx_skb_queue */ + skb_pool = &sdio_dev->tx_skb_queue; + while ((skb = __skb_dequeue(skb_pool))) + dev_kfree_skb_any(skb); + /* rx_skb_queue */ + skb_pool = &sdio_dev->rx_skb_queue; + while ((skb = __skb_dequeue(skb_pool))) + dev_kfree_skb_any(skb); + + spin_unlock_irqrestore(&dev->lock, flags); + break; + case USB_RMNET_XPORT_SMD: + /* close smd xport */ + tasklet_kill(&smd_dev->smd_data.rx_tlet); + tasklet_kill(&smd_dev->smd_data.tx_tlet); + + spin_lock_irqsave(&dev->lock, flags); + /* free all usb requests in SMD tx pool */ + pool = &smd_dev->tx_idle; + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epout, req); + } + + pool = &smd_dev->rx_idle; + /* free all usb requests in SMD rx pool */ + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epin, req); + } + + /* free all usb requests in SMD rx queue */ + pool = &smd_dev->rx_queue; + while (!list_empty(pool)) { + req = list_first_entry(pool, struct usb_request, list); + list_del(&req->list); + rmnet_free_req(dev->epin, req); + } + + spin_unlock_irqrestore(&dev->lock, flags); + break; + default: + pr_debug("%s: undefined xport, do nothing\n", __func__); + } + + dev->xport = given_xport; + + switch (dev->xport) { + case USB_RMNET_XPORT_SDIO: + rmnet_sdio_enable(dev); + break; + case USB_RMNET_XPORT_SMD: + rmnet_smd_enable(dev); + break; + default: + /* we should never come here */ + pr_err("%s: undefined transport\n", __func__); + } + + return size; +} +static DEVICE_ATTR(transport, S_IRUGO | S_IWUSR, NULL, transport_store); + +static int rmnet_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + int id, ret; + struct usb_ep *ep; + + dev->cdev = cdev; + + /* allocate interface ID */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + dev->ifc_id = id; + rmnet_interface_desc.bInterfaceNumber = id; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_in_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epin = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_out_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epout = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_notify_desc); + if (!ep) + goto out; + ep->driver_data = cdev; /* claim endpoint */ + dev->epnotify = ep; + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + rmnet_hs_in_desc.bEndpointAddress = + rmnet_fs_in_desc.bEndpointAddress; + rmnet_hs_out_desc.bEndpointAddress = + rmnet_fs_out_desc.bEndpointAddress; + rmnet_hs_notify_desc.bEndpointAddress = + rmnet_fs_notify_desc.bEndpointAddress; + } + + ret = device_create_file(f->dev, &dev_attr_transport); + if (ret) + goto out; + + queue_delayed_work(dev->wq, &sdio_dev->open_work, 0); + + return 0; + +out: + if (dev->epnotify) + dev->epnotify->driver_data = NULL; + if (dev->epout) + dev->epout->driver_data = NULL; + if (dev->epin) + dev->epin->driver_data = NULL; + + return -ENODEV; +} + +static void rmnet_smd_init(struct rmnet_smd_dev *smd_dev) +{ + struct rmnet_dev *dev = container_of(smd_dev, + struct rmnet_dev, smd_dev); + + atomic_set(&smd_dev->smd_data.rx_pkt, 0); + tasklet_init(&smd_dev->smd_data.rx_tlet, rmnet_smd_data_rx_tlet, + (unsigned long) dev); + tasklet_init(&smd_dev->smd_data.tx_tlet, rmnet_smd_data_tx_tlet, + (unsigned long) dev); + + init_waitqueue_head(&smd_dev->smd_data.wait); + + INIT_LIST_HEAD(&smd_dev->rx_idle); + INIT_LIST_HEAD(&smd_dev->rx_queue); + INIT_LIST_HEAD(&smd_dev->tx_idle); +} + +static void rmnet_sdio_init(struct rmnet_sdio_dev *sdio_dev) +{ + INIT_WORK(&sdio_dev->data_rx_work, rmnet_sdio_data_rx_work); + + INIT_DELAYED_WORK(&sdio_dev->open_work, rmnet_open_sdio_work); + + INIT_LIST_HEAD(&sdio_dev->rx_idle); + INIT_LIST_HEAD(&sdio_dev->tx_idle); + skb_queue_head_init(&sdio_dev->tx_skb_queue); + skb_queue_head_init(&sdio_dev->rx_skb_queue); +} + +static void +rmnet_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function); + struct rmnet_smd_dev *smd_dev = &dev->smd_dev; + + smd_close(smd_dev->smd_data.ch); + smd_dev->smd_data.flags = 0; + +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t debug_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct rmnet_dev *dev = file->private_data; + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + char *debug_buf; + unsigned long flags; + int ret; + + debug_buf = kmalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!debug_buf) + return -ENOMEM; + + spin_lock_irqsave(&dev->lock, flags); + ret = scnprintf(debug_buf, DEBUG_BUF_SIZE, + "dpkts_tomsm: %lu\n" + "dpkts_tomdm: %lu\n" + "cpkts_tomdm: %lu\n" + "dpkts_tolaptop: %lu\n" + "cpkts_tolaptop: %lu\n" + "cbits_to_modem: %lu\n" + "tx skb size: %u\n" + "rx_skb_size: %u\n" + "dpkts_pending_at_dmux: %u\n" + "tx drp cnt: %lu\n" + "cpkts_drp_cnt: %lu\n" + "cpkt_tx_qlen: %lu\n" + "cpkt_rx_qlen_to_modem: %lu\n" + "xport: %s\n" + "ctr_ch_opened: %d\n", + dev->dpkts_tomsm, dev->dpkts_tomdm, + dev->cpkts_tomdm, dev->dpkts_tolaptop, + dev->cpkts_tolaptop, ctrl_dev->cbits_to_modem, + sdio_dev->tx_skb_queue.qlen, + sdio_dev->rx_skb_queue.qlen, + sdio_dev->dpkts_pending_atdmux, dev->tx_drp_cnt, + dev->cpkts_drp_cnt, + ctrl_dev->tx_len, ctrl_dev->rx_len, + xport_to_str(dev->xport), ctrl_dev->opened); + + spin_unlock_irqrestore(&dev->lock, flags); + + ret = simple_read_from_buffer(ubuf, count, ppos, debug_buf, ret); + + kfree(debug_buf); + + return ret; +} + +static ssize_t debug_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct rmnet_dev *dev = file->private_data; + struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev; + + dev->dpkts_tolaptop = 0; + dev->cpkts_tolaptop = 0; + dev->cpkts_tomdm = 0; + dev->dpkts_tomdm = 0; + dev->dpkts_tomsm = 0; + sdio_dev->dpkts_pending_atdmux = 0; + dev->tx_drp_cnt = 0; + dev->cpkts_drp_cnt = 0; + return count; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return 0; +} + +const struct file_operations rmnet_svlte_debug_stats_ops = { + .open = debug_open, + .read = debug_read_stats, + .write = debug_reset_stats, +}; + +static void usb_debugfs_init(struct rmnet_dev *dev) +{ + struct dentry *dent; + + dent = debugfs_create_dir("usb_rmnet", 0); + if (IS_ERR(dent)) + return; + + debugfs_create_file("status", 0444, dent, dev, + &rmnet_svlte_debug_stats_ops); +} +#else +static void usb_debugfs_init(struct rmnet_dev *dev) {} +#endif + +int usb_rmnet_ctrl_open(struct inode *inode, struct file *fp) +{ + struct rmnet_dev *dev = _dev; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (ctrl_dev->opened) { + spin_unlock_irqrestore(&dev->lock, flags); + pr_err("%s: device is already opened\n", __func__); + return -EBUSY; + } + + ctrl_dev->opened = 1; + fp->private_data = dev; + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + + +int usb_rmnet_ctrl_release(struct inode *inode, struct file *fp) +{ + struct rmnet_dev *dev = fp->private_data; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + ctrl_dev->opened = 0; + fp->private_data = 0; + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +ssize_t usb_rmnet_ctrl_read(struct file *fp, + char __user *buf, + size_t count, + loff_t *ppos) +{ + struct rmnet_dev *dev = fp->private_data; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + struct rmnet_ctrl_pkt *cpkt; + unsigned long flags; + int ret = 0; + +ctrl_read: + if (!atomic_read(&dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(&ctrl_dev->tx_q)) { + spin_unlock_irqrestore(&dev->lock, flags); + /* Implement sleep and wakeup here */ + ret = wait_event_interruptible(ctrl_dev->tx_wait_q, + !list_empty(&ctrl_dev->tx_q) || + !atomic_read(&dev->online)); + if (ret < 0) + return ret; + + goto ctrl_read; + } + + cpkt = list_first_entry(&ctrl_dev->tx_q, struct rmnet_ctrl_pkt, list); + if (cpkt->len > count) { + spin_unlock_irqrestore(&dev->lock, flags); + pr_err("%s: cpkt size:%d > buf size:%d\n", + __func__, cpkt->len, count); + return -ENOMEM; + } + list_del(&cpkt->list); + ctrl_dev->tx_len--; + spin_unlock_irqrestore(&dev->lock, flags); + + count = cpkt->len; + + ret = copy_to_user(buf, cpkt->buf, count); + dev->cpkts_tomdm++; + + rmnet_free_ctrl_pkt(cpkt); + + if (ret) + return ret; + + return count; +} + +ssize_t usb_rmnet_ctrl_write(struct file *fp, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + struct rmnet_dev *dev = fp->private_data; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + struct rmnet_ctrl_pkt *cpkt; + unsigned long flags; + int ret = 0; + + if (!atomic_read(&dev->online)) { + pr_debug("%s: USB cable not connected\n", __func__); + return -ENODEV; + } + + if (!count) { + pr_err("%s: zero length ctrl pkt\n", __func__); + return -ENODEV; + } + + if (count > MAX_CTRL_PKT_SIZE) { + pr_err("%s: max_pkt_size:%d given_pkt_size:%d\n", + __func__, MAX_CTRL_PKT_SIZE, count); + return -ENOMEM; + } + + cpkt = rmnet_alloc_ctrl_pkt(count, GFP_KERNEL); + if (!cpkt) { + pr_err("%s: cannot allocate rmnet ctrl pkt\n", __func__); + return -ENOMEM; + } + + ret = copy_from_user(cpkt->buf, buf, count); + if (ret) { + pr_err("%s: copy_from_user failed err:%d\n", + __func__, ret); + rmnet_free_ctrl_pkt(cpkt); + return ret; + } + + spin_lock_irqsave(&dev->lock, flags); + ctrl_dev->rx_len++; + list_add(&cpkt->list, &ctrl_dev->rx_q); + spin_unlock_irqrestore(&dev->lock, flags); + + ctrl_response_available(dev); + + return count; +} + + +#define RMNET_CTRL_GET_DTR _IOR(0xFE, 0, int) +static long +usb_rmnet_ctrl_ioctl(struct file *fp, unsigned c, unsigned long value) +{ + struct rmnet_dev *dev = fp->private_data; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + unsigned long *temp = (unsigned long *)value; + int ret = 0; + + if (c != RMNET_CTRL_GET_DTR) + return -ENODEV; + + ret = copy_to_user(temp, + &ctrl_dev->cbits_to_modem, + sizeof(*temp)); + if (ret) + return ret; + + return 0; +} + +static const struct file_operations rmnet_ctrl_fops = { + .owner = THIS_MODULE, + .open = usb_rmnet_ctrl_open, + .release = usb_rmnet_ctrl_release, + .read = usb_rmnet_ctrl_read, + .write = usb_rmnet_ctrl_write, + .unlocked_ioctl = usb_rmnet_ctrl_ioctl, +}; + +static struct miscdevice rmnet_ctrl_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "rmnet_ctrl", + .fops = &rmnet_ctrl_fops, +}; + +static int rmnet_ctrl_device_init(struct rmnet_dev *dev) +{ + int ret; + struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev; + + INIT_LIST_HEAD(&ctrl_dev->tx_q); + INIT_LIST_HEAD(&ctrl_dev->rx_q); + init_waitqueue_head(&ctrl_dev->tx_wait_q); + + ret = misc_register(&rmnet_ctrl_dev); + if (ret) { + pr_err("%s: failed to register misc device\n", __func__); + return ret; + } + + return 0; +} + +static int rmnet_function_add(struct usb_configuration *c) +{ + struct rmnet_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + _dev = dev; + + dev->wq = create_singlethread_workqueue("k_rmnet_work"); + if (!dev->wq) { + ret = -ENOMEM; + goto free_dev; + } + + spin_lock_init(&dev->lock); + atomic_set(&dev->notify_count, 0); + atomic_set(&dev->online, 0); + INIT_WORK(&dev->disconnect_work, rmnet_disconnect_work); + rmnet_smd_init(&dev->smd_dev); + rmnet_sdio_init(&dev->sdio_dev); + + ret = rmnet_ctrl_device_init(dev); + if (ret) { + pr_debug("%s: rmnet_ctrl_device_init failed, err:%d\n", + __func__, ret); + goto free_wq; + } + + dev->function.name = "rmnet_smd_sdio"; + dev->function.strings = rmnet_strings; + dev->function.descriptors = rmnet_fs_function; + dev->function.hs_descriptors = rmnet_hs_function; + dev->function.bind = rmnet_bind; + dev->function.unbind = rmnet_unbind; + dev->function.setup = rmnet_setup; + dev->function.set_alt = rmnet_set_alt; + dev->function.disable = rmnet_disable; + dev->function.suspend = rmnet_suspend; + + ret = usb_add_function(c, &dev->function); + if (ret) + goto free_wq; + + usb_debugfs_init(dev); + + return 0; + +free_wq: + destroy_workqueue(dev->wq); +free_dev: + kfree(dev); + + return ret; +} + +#ifdef CONFIG_USB_ANDROID_RMNET_SMD_SDIO +static struct android_usb_function rmnet_function = { + .name = "rmnet_smd_sdio", + .bind_config = rmnet_function_add, +}; + +static int __init rmnet_init(void) +{ + android_register_function(&rmnet_function); + return 0; +} +module_init(rmnet_init); + +#endif /* CONFIG_USB_ANDROID_RMNET_SDIO */ diff --git a/drivers/usb/gadget/f_serial.c b/drivers/usb/gadget/f_serial.c index 490b00b01a7..0c315443125 100644 --- a/drivers/usb/gadget/f_serial.c +++ b/drivers/usb/gadget/f_serial.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include "u_serial.h" #include "gadget_chips.h" @@ -30,6 +32,9 @@ struct gser_descs { struct usb_endpoint_descriptor *in; struct usb_endpoint_descriptor *out; +#ifdef CONFIG_MODEM_SUPPORT + struct usb_endpoint_descriptor *notify; +#endif }; struct f_gser { @@ -39,29 +44,129 @@ struct f_gser { struct gser_descs fs; struct gser_descs hs; + u8 online; + enum transport_type transport; + +#ifdef CONFIG_MODEM_SUPPORT + u8 pending; + spinlock_t lock; + struct usb_ep *notify; + struct usb_endpoint_descriptor *notify_desc; + struct usb_request *notify_req; + + struct usb_cdc_line_coding port_line_coding; + + /* SetControlLineState request */ + u16 port_handshake_bits; +#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ +#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ + + /* SerialState notification */ + u16 serial_state; +#define ACM_CTRL_OVERRUN (1 << 6) +#define ACM_CTRL_PARITY (1 << 5) +#define ACM_CTRL_FRAMING (1 << 4) +#define ACM_CTRL_RI (1 << 3) +#define ACM_CTRL_BRK (1 << 2) +#define ACM_CTRL_DSR (1 << 1) +#define ACM_CTRL_DCD (1 << 0) +#endif }; +#ifdef CONFIG_USB_F_SERIAL +static unsigned int no_tty_ports; +static unsigned int no_sdio_ports; +static unsigned int no_smd_ports; +static unsigned int nr_ports; +#endif + +static struct port_info { + enum transport_type transport; + unsigned port_num; + unsigned client_port_num; +} gserial_ports[GSERIAL_NO_PORTS]; + +static inline bool is_transport_sdio(enum transport_type t) +{ + if (t == USB_GADGET_FSERIAL_TRANSPORT_SDIO) + return 1; + return 0; +} + static inline struct f_gser *func_to_gser(struct usb_function *f) { return container_of(f, struct f_gser, port.func); } +#ifdef CONFIG_MODEM_SUPPORT +static inline struct f_gser *port_to_gser(struct gserial *p) +{ + return container_of(p, struct f_gser, port); +} +#define GS_LOG2_NOTIFY_INTERVAL 5 /* 1 << 5 == 32 msec */ +#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */ +#endif /*-------------------------------------------------------------------------*/ /* interface descriptor: */ -static struct usb_interface_descriptor gser_interface_desc __initdata = { +static struct usb_interface_descriptor gser_interface_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ +#ifdef CONFIG_MODEM_SUPPORT + .bNumEndpoints = 3, +#else .bNumEndpoints = 2, +#endif .bInterfaceClass = USB_CLASS_VENDOR_SPEC, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, /* .iInterface = DYNAMIC */ }; +#ifdef CONFIG_MODEM_SUPPORT +static struct usb_cdc_header_desc gser_header_desc = { + .bLength = sizeof(gser_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = __constant_cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor +gser_call_mgmt_descriptor = { + .bLength = sizeof(gser_call_mgmt_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + .bmCapabilities = 0, + /* .bDataInterface = DYNAMIC */ +}; +static struct usb_cdc_acm_descriptor gser_descriptor = { + .bLength = sizeof(gser_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = USB_CDC_CAP_LINE, +}; + +static struct usb_cdc_union_desc gser_union_desc = { + .bLength = sizeof(gser_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; +#endif /* full speed support: */ +#ifdef CONFIG_MODEM_SUPPORT +static struct usb_endpoint_descriptor gser_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = 1 << GS_LOG2_NOTIFY_INTERVAL, +}; +#endif static struct usb_endpoint_descriptor gser_fs_in_desc __initdata = { .bLength = USB_DT_ENDPOINT_SIZE, @@ -79,29 +184,53 @@ static struct usb_endpoint_descriptor gser_fs_out_desc __initdata = { static struct usb_descriptor_header *gser_fs_function[] __initdata = { (struct usb_descriptor_header *) &gser_interface_desc, +#ifdef CONFIG_MODEM_SUPPORT + (struct usb_descriptor_header *) &gser_header_desc, + (struct usb_descriptor_header *) &gser_call_mgmt_descriptor, + (struct usb_descriptor_header *) &gser_descriptor, + (struct usb_descriptor_header *) &gser_union_desc, + (struct usb_descriptor_header *) &gser_fs_notify_desc, +#endif (struct usb_descriptor_header *) &gser_fs_in_desc, (struct usb_descriptor_header *) &gser_fs_out_desc, NULL, }; /* high speed support: */ +#ifdef CONFIG_MODEM_SUPPORT +static struct usb_endpoint_descriptor gser_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = GS_LOG2_NOTIFY_INTERVAL+4, +}; +#endif static struct usb_endpoint_descriptor gser_hs_in_desc __initdata = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(512), + .wMaxPacketSize = __constant_cpu_to_le16(512), }; -static struct usb_endpoint_descriptor gser_hs_out_desc __initdata = { +static struct usb_endpoint_descriptor gser_hs_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(512), + .wMaxPacketSize = __constant_cpu_to_le16(512), }; static struct usb_descriptor_header *gser_hs_function[] __initdata = { (struct usb_descriptor_header *) &gser_interface_desc, +#ifdef CONFIG_MODEM_SUPPORT + (struct usb_descriptor_header *) &gser_header_desc, + (struct usb_descriptor_header *) &gser_call_mgmt_descriptor, + (struct usb_descriptor_header *) &gser_descriptor, + (struct usb_descriptor_header *) &gser_union_desc, + (struct usb_descriptor_header *) &gser_hs_notify_desc, +#endif (struct usb_descriptor_header *) &gser_hs_in_desc, (struct usb_descriptor_header *) &gser_hs_out_desc, NULL, @@ -124,27 +253,232 @@ static struct usb_gadget_strings *gser_strings[] = { NULL, }; +static char *transport_to_str(enum transport_type t) +{ + switch (t) { + case USB_GADGET_FSERIAL_TRANSPORT_TTY: + return "TTY"; + case USB_GADGET_FSERIAL_TRANSPORT_SDIO: + return "SDIO"; + case USB_GADGET_FSERIAL_TRANSPORT_SMD: + return "SMD"; + } + + return "NONE"; +} + +#ifdef CONFIG_USB_F_SERIAL +static int gport_setup(struct usb_configuration *c) +{ + int ret = 0; + + pr_debug("%s: no_tty_ports:%u no_sdio_ports: %u nr_ports:%u\n", + __func__, no_tty_ports, no_sdio_ports, nr_ports); + + if (no_tty_ports) + ret = gserial_setup(c->cdev->gadget, no_tty_ports); + if (no_sdio_ports) + ret = gsdio_setup(c->cdev->gadget, no_sdio_ports); + if (no_smd_ports) + ret = gsmd_setup(c->cdev->gadget, no_smd_ports); + + return ret; +} +#endif +static int gport_connect(struct f_gser *gser) +{ + unsigned port_num; + + pr_debug("%s: transport:%s f_gser:%p gserial:%p port_num:%d\n", + __func__, transport_to_str(gser->transport), + gser, &gser->port, gser->port_num); + + port_num = gserial_ports[gser->port_num].client_port_num; + + switch (gser->transport) { + case USB_GADGET_FSERIAL_TRANSPORT_TTY: + gserial_connect(&gser->port, port_num); + break; + case USB_GADGET_FSERIAL_TRANSPORT_SDIO: + gsdio_connect(&gser->port, port_num); + break; + case USB_GADGET_FSERIAL_TRANSPORT_SMD: + gsmd_connect(&gser->port, port_num); + break; + default: + pr_err("%s: Un-supported transport: %s\n", __func__, + transport_to_str(gser->transport)); + return -ENODEV; + } + + return 0; +} + +static int gport_disconnect(struct f_gser *gser) +{ + unsigned port_num; + + pr_debug("%s: transport:%s f_gser:%p gserial:%p port_num:%d\n", + __func__, transport_to_str(gser->transport), + gser, &gser->port, gser->port_num); + + port_num = gserial_ports[gser->port_num].client_port_num; + + switch (gser->transport) { + case USB_GADGET_FSERIAL_TRANSPORT_TTY: + gserial_disconnect(&gser->port); + break; + case USB_GADGET_FSERIAL_TRANSPORT_SDIO: + gsdio_disconnect(&gser->port, port_num); + break; + case USB_GADGET_FSERIAL_TRANSPORT_SMD: + gsmd_disconnect(&gser->port, port_num); + break; + default: + pr_err("%s: Un-supported transport:%s\n", __func__, + transport_to_str(gser->transport)); + return -ENODEV; + } + + return 0; +} + +#ifdef CONFIG_MODEM_SUPPORT +static void gser_complete_set_line_coding(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_gser *gser = ep->driver_data; + struct usb_composite_dev *cdev = gser->port.func.config->cdev; + + if (req->status != 0) { + DBG(cdev, "gser ttyGS%d completion, err %d\n", + gser->port_num, req->status); + return; + } + + /* normal completion */ + if (req->actual != sizeof(gser->port_line_coding)) { + DBG(cdev, "gser ttyGS%d short resp, len %d\n", + gser->port_num, req->actual); + usb_ep_set_halt(ep); + } else { + struct usb_cdc_line_coding *value = req->buf; + gser->port_line_coding = *value; + } +} /*-------------------------------------------------------------------------*/ +static int +gser_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_gser *gser = func_to_gser(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* SET_LINE_CODING ... just read and save what the host sends */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_LINE_CODING: + if (w_length != sizeof(struct usb_cdc_line_coding)) + goto invalid; + + value = w_length; + cdev->gadget->ep0->driver_data = gser; + req->complete = gser_complete_set_line_coding; + break; + + /* GET_LINE_CODING ... return what host sent, or initial value */ + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_GET_LINE_CODING: + value = min_t(unsigned, w_length, + sizeof(struct usb_cdc_line_coding)); + memcpy(req->buf, &gser->port_line_coding, value); + break; + + /* SET_CONTROL_LINE_STATE ... save what the host sent */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + + value = 0; + gser->port_handshake_bits = w_value; + if (gser->port.notify_modem) { + unsigned port_num = + gserial_ports[gser->port_num].client_port_num; + + gser->port.notify_modem(&gser->port, + port_num, w_value); + } + break; + + default: +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "gser ttyGS%d req%02x.%02x v%04x i%04x l%d\n", + gser->port_num, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "gser response on ttyGS%d, err %d\n", + gser->port_num, value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} +#endif static int gser_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct f_gser *gser = func_to_gser(f); struct usb_composite_dev *cdev = f->config->cdev; + int rc = 0; /* we know alt == 0, so this is an activation or a reset */ +#ifdef CONFIG_MODEM_SUPPORT + if (gser->notify->driver_data) { + DBG(cdev, "reset generic ctl ttyGS%d\n", gser->port_num); + usb_ep_disable(gser->notify); + } + gser->notify_desc = ep_choose(cdev->gadget, + gser->hs.notify, + gser->fs.notify); + rc = usb_ep_enable(gser->notify, gser->notify_desc); + if (rc) { + ERROR(cdev, "can't enable %s, result %d\n", + gser->notify->name, rc); + return rc; + } + gser->notify->driver_data = gser; +#endif + if (gser->port.in->driver_data) { - DBG(cdev, "reset generic ttyGS%d\n", gser->port_num); - gserial_disconnect(&gser->port); + DBG(cdev, "reset generic data ttyGS%d\n", gser->port_num); + gport_disconnect(gser); } else { - DBG(cdev, "activate generic ttyGS%d\n", gser->port_num); - gser->port.in_desc = ep_choose(cdev->gadget, - gser->hs.in, gser->fs.in); - gser->port.out_desc = ep_choose(cdev->gadget, - gser->hs.out, gser->fs.out); + DBG(cdev, "activate generic data ttyGS%d\n", gser->port_num); } - gserial_connect(&gser->port, gser->port_num); - return 0; + gser->port.in_desc = ep_choose(cdev->gadget, + gser->hs.in, gser->fs.in); + gser->port.out_desc = ep_choose(cdev->gadget, + gser->hs.out, gser->fs.out); + + gport_connect(gser); + + gser->online = 1; + return rc; } static void gser_disable(struct usb_function *f) @@ -153,9 +487,180 @@ static void gser_disable(struct usb_function *f) struct usb_composite_dev *cdev = f->config->cdev; DBG(cdev, "generic ttyGS%d deactivated\n", gser->port_num); - gserial_disconnect(&gser->port); + + gport_disconnect(gser); + +#ifdef CONFIG_MODEM_SUPPORT + usb_ep_fifo_flush(gser->notify); + usb_ep_disable(gser->notify); +#endif + gser->online = 0; +} +#ifdef CONFIG_MODEM_SUPPORT +static int gser_notify(struct f_gser *gser, u8 type, u16 value, + void *data, unsigned length) +{ + struct usb_ep *ep = gser->notify; + struct usb_request *req; + struct usb_cdc_notification *notify; + const unsigned len = sizeof(*notify) + length; + void *buf; + int status; + struct usb_composite_dev *cdev = gser->port.func.config->cdev; + + req = gser->notify_req; + gser->notify_req = NULL; + gser->pending = false; + + req->length = len; + notify = req->buf; + buf = notify + 1; + + notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + notify->bNotificationType = type; + notify->wValue = cpu_to_le16(value); + notify->wIndex = cpu_to_le16(gser->data_id); + notify->wLength = cpu_to_le16(length); + memcpy(buf, data, length); + + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status < 0) { + ERROR(cdev, "gser ttyGS%d can't notify serial state, %d\n", + gser->port_num, status); + gser->notify_req = req; + } + + return status; +} + +static int gser_notify_serial_state(struct f_gser *gser) +{ + int status; + unsigned long flags; + struct usb_composite_dev *cdev = gser->port.func.config->cdev; + + spin_lock_irqsave(&gser->lock, flags); + if (gser->notify_req) { + DBG(cdev, "gser ttyGS%d serial state %04x\n", + gser->port_num, gser->serial_state); + status = gser_notify(gser, USB_CDC_NOTIFY_SERIAL_STATE, + 0, &gser->serial_state, + sizeof(gser->serial_state)); + } else { + gser->pending = true; + status = 0; + } + spin_unlock_irqrestore(&gser->lock, flags); + return status; } +static void gser_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_gser *gser = req->context; + u8 doit = false; + unsigned long flags; + + /* on this call path we do NOT hold the port spinlock, + * which is why ACM needs its own spinlock + */ + spin_lock_irqsave(&gser->lock, flags); + if (req->status != -ESHUTDOWN) + doit = gser->pending; + gser->notify_req = req; + spin_unlock_irqrestore(&gser->lock, flags); + + if (doit && gser->online) + gser_notify_serial_state(gser); +} +static void gser_connect(struct gserial *port) +{ + struct f_gser *gser = port_to_gser(port); + + gser->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD; + gser_notify_serial_state(gser); +} + +unsigned int gser_get_dtr(struct gserial *port) +{ + struct f_gser *gser = port_to_gser(port); + + if (gser->port_handshake_bits & ACM_CTRL_DTR) + return 1; + else + return 0; +} + +unsigned int gser_get_rts(struct gserial *port) +{ + struct f_gser *gser = port_to_gser(port); + + if (gser->port_handshake_bits & ACM_CTRL_RTS) + return 1; + else + return 0; +} + +unsigned int gser_send_carrier_detect(struct gserial *port, unsigned int yes) +{ + struct f_gser *gser = port_to_gser(port); + u16 state; + + state = gser->serial_state; + state &= ~ACM_CTRL_DCD; + if (yes) + state |= ACM_CTRL_DCD; + + gser->serial_state = state; + return gser_notify_serial_state(gser); + +} + +unsigned int gser_send_ring_indicator(struct gserial *port, unsigned int yes) +{ + struct f_gser *gser = port_to_gser(port); + u16 state; + + state = gser->serial_state; + state &= ~ACM_CTRL_RI; + if (yes) + state |= ACM_CTRL_RI; + + gser->serial_state = state; + return gser_notify_serial_state(gser); + +} +static void gser_disconnect(struct gserial *port) +{ + struct f_gser *gser = port_to_gser(port); + + gser->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD); + gser_notify_serial_state(gser); +} + +static int gser_send_break(struct gserial *port, int duration) +{ + struct f_gser *gser = port_to_gser(port); + u16 state; + + state = gser->serial_state; + state &= ~ACM_CTRL_BRK; + if (duration) + state |= ACM_CTRL_BRK; + + gser->serial_state = state; + return gser_notify_serial_state(gser); +} + +static int gser_send_modem_ctrl_bits(struct gserial *port, int ctrl_bits) +{ + struct f_gser *gser = port_to_gser(port); + + gser->serial_state = ctrl_bits; + + return gser_notify_serial_state(gser); +} +#endif /*-------------------------------------------------------------------------*/ /* serial function driver setup/binding */ @@ -190,6 +695,23 @@ gser_bind(struct usb_configuration *c, struct usb_function *f) gser->port.out = ep; ep->driver_data = cdev; /* claim */ +#ifdef CONFIG_MODEM_SUPPORT + ep = usb_ep_autoconfig(cdev->gadget, &gser_fs_notify_desc); + if (!ep) + goto fail; + gser->notify = ep; + ep->driver_data = cdev; /* claim */ + /* allocate notification */ + gser->notify_req = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!gser->notify_req) + goto fail; + + gser->notify_req->complete = gser_notify_complete; + gser->notify_req->context = gser; +#endif + /* copy descriptors, and track endpoint copies */ f->descriptors = usb_copy_descriptors(gser_fs_function); @@ -197,6 +719,10 @@ gser_bind(struct usb_configuration *c, struct usb_function *f) f->descriptors, &gser_fs_in_desc); gser->fs.out = usb_find_endpoint(gser_fs_function, f->descriptors, &gser_fs_out_desc); +#ifdef CONFIG_MODEM_SUPPORT + gser->fs.notify = usb_find_endpoint(gser_fs_function, + f->descriptors, &gser_fs_notify_desc); +#endif /* support all relevant hardware speeds... we expect that when @@ -208,6 +734,10 @@ gser_bind(struct usb_configuration *c, struct usb_function *f) gser_fs_in_desc.bEndpointAddress; gser_hs_out_desc.bEndpointAddress = gser_fs_out_desc.bEndpointAddress; +#ifdef CONFIG_MODEM_SUPPORT + gser_hs_notify_desc.bEndpointAddress = + gser_fs_notify_desc.bEndpointAddress; +#endif /* copy descriptors, and track endpoint copies */ f->hs_descriptors = usb_copy_descriptors(gser_hs_function); @@ -216,6 +746,10 @@ gser_bind(struct usb_configuration *c, struct usb_function *f) f->hs_descriptors, &gser_hs_in_desc); gser->hs.out = usb_find_endpoint(gser_hs_function, f->hs_descriptors, &gser_hs_out_desc); +#ifdef CONFIG_MODEM_SUPPORT + gser->hs.notify = usb_find_endpoint(gser_hs_function, + f->hs_descriptors, &gser_hs_notify_desc); +#endif } DBG(cdev, "generic ttyGS%d: %s speed IN/%s OUT/%s\n", @@ -225,6 +759,14 @@ gser_bind(struct usb_configuration *c, struct usb_function *f) return 0; fail: +#ifdef CONFIG_MODEM_SUPPORT + if (gser->notify_req) + gs_free_req(gser->notify, gser->notify_req); + + /* we might as well release our claims on endpoints */ + if (gser->notify) + gser->notify->driver_data = NULL; +#endif /* we might as well release our claims on endpoints */ if (gser->port.out) gser->port.out->driver_data = NULL; @@ -239,9 +781,15 @@ gser_bind(struct usb_configuration *c, struct usb_function *f) static void gser_unbind(struct usb_configuration *c, struct usb_function *f) { +#ifdef CONFIG_MODEM_SUPPORT + struct f_gser *gser = func_to_gser(f); +#endif if (gadget_is_dualspeed(c->cdev->gadget)) usb_free_descriptors(f->hs_descriptors); usb_free_descriptors(f->descriptors); +#ifdef CONFIG_MODEM_SUPPORT + gs_free_req(gser->notify, gser->notify_req); +#endif kfree(func_to_gser(f)); } @@ -279,6 +827,9 @@ int __init gser_bind_config(struct usb_configuration *c, u8 port_num) if (!gser) return -ENOMEM; +#ifdef CONFIG_MODEM_SUPPORT + spin_lock_init(&gser->lock); +#endif gser->port_num = port_num; gser->port.func.name = "gser"; @@ -287,9 +838,130 @@ int __init gser_bind_config(struct usb_configuration *c, u8 port_num) gser->port.func.unbind = gser_unbind; gser->port.func.set_alt = gser_set_alt; gser->port.func.disable = gser_disable; + gser->transport = gserial_ports[port_num].transport; +#ifdef CONFIG_MODEM_SUPPORT + /* We support only two ports for now */ + if (port_num == 0) + gser->port.func.name = "modem"; + else + gser->port.func.name = "nmea"; + gser->port.func.setup = gser_setup; + gser->port.connect = gser_connect; + gser->port.get_dtr = gser_get_dtr; + gser->port.get_rts = gser_get_rts; + gser->port.send_carrier_detect = gser_send_carrier_detect; + gser->port.send_ring_indicator = gser_send_ring_indicator; + gser->port.send_modem_ctrl_bits = gser_send_modem_ctrl_bits; + gser->port.disconnect = gser_disconnect; + gser->port.send_break = gser_send_break; +#endif status = usb_add_function(c, &gser->port.func); if (status) kfree(gser); return status; } + +#ifdef CONFIG_USB_F_SERIAL + +int fserial_nmea_bind_config(struct usb_configuration *c) +{ + return gser_bind_config(c, 1); +} + +static struct android_usb_function nmea_function = { + .name = "nmea", + .bind_config = fserial_nmea_bind_config, +}; + +int fserial_modem_bind_config(struct usb_configuration *c) +{ + int ret; + + /* See if composite driver can allocate + * serial ports. But for now allocate + * two ports for modem and nmea. + */ + ret = gport_setup(c); + + if (ret) + return ret; + return gser_bind_config(c, 0); +} + +static struct android_usb_function modem_function = { + .name = "modem", + .bind_config = fserial_modem_bind_config, +}; + +static int fserial_remove(struct platform_device *dev) +{ + gserial_cleanup(); + + return 0; +} + +static struct platform_driver usb_fserial = { + .remove = fserial_remove, + .driver = { + .name = "usb_fserial", + .owner = THIS_MODULE, + }, +}; + +static int __init fserial_probe(struct platform_device *pdev) +{ + struct usb_gadget_fserial_platform_data *pdata = + pdev->dev.platform_data; + int i; + + dev_dbg(&pdev->dev, "%s: probe\n", __func__); + + if (!pdata) + goto probe_android_register; + + for (i = 0; i < GSERIAL_NO_PORTS; i++) { + gserial_ports[i].transport = pdata->transport[i]; + gserial_ports[i].port_num = i; + + switch (gserial_ports[i].transport) { + case USB_GADGET_FSERIAL_TRANSPORT_TTY: + gserial_ports[i].client_port_num = no_tty_ports; + no_tty_ports++; + break; + case USB_GADGET_FSERIAL_TRANSPORT_SDIO: + gserial_ports[i].client_port_num = no_sdio_ports; + no_sdio_ports++; + break; + case USB_GADGET_FSERIAL_TRANSPORT_SMD: + gserial_ports[i].client_port_num = no_smd_ports; + no_smd_ports++; + break; + default: + pr_err("%s: Un-supported transport transport: %u\n", + __func__, gserial_ports[i].transport); + return -ENODEV; + } + + nr_ports++; + } + + pr_info("%s:gport:tty_ports:%u sdio_ports:%u " + "smd_ports:%u nr_ports:%u\n", + __func__, no_tty_ports, no_sdio_ports, + no_smd_ports, nr_ports); + +probe_android_register: + android_register_function(&modem_function); + android_register_function(&nmea_function); + + return 0; +} + +static int __init fserial_init(void) +{ + return platform_driver_probe(&usb_fserial, fserial_probe); +} +module_init(fserial_init); + +#endif /* CONFIG_USB_ANDROID_ACM */ diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index bcdac7c73e8..05692bbe51b 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -120,6 +120,12 @@ #define gadget_is_ci13xxx_pci(g) 0 #endif +#ifdef CONFIG_USB_GADGET_MSM_72K +#define gadget_is_msm72k(g) !strcmp("msm72k_udc", (g)->name) +#else +#define gadget_is_msm72k(g) 0 +#endif + // CONFIG_USB_GADGET_SX2 // CONFIG_USB_GADGET_AU1X00 // ... @@ -223,6 +229,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x29; else if (gadget_is_s3c_hsudc(gadget)) return 0x30; + else if (gadget_is_msm72k(gadget)) + return 0x31; return -ENOENT; } diff --git a/drivers/usb/gadget/msm72k_udc.c b/drivers/usb/gadget/msm72k_udc.c new file mode 100644 index 00000000000..24ba619d8e8 --- /dev/null +++ b/drivers/usb/gadget/msm72k_udc.c @@ -0,0 +1,2653 @@ +/* + * Driver for HighSpeed USB Client Controller in MSM7K + * + * Copyright (C) 2008 Google, Inc. + * Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. + * Author: Mike Lockwood + * Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +static const char driver_name[] = "msm72k_udc"; + +/* #define DEBUG */ +/* #define VERBOSE */ + +#define MSM_USB_BASE ((unsigned) ui->addr) + +#define DRIVER_DESC "MSM 72K USB Peripheral Controller" +#define DRIVER_NAME "MSM72K_UDC" + +#define EPT_FLAG_IN 0x0001 + +#define SETUP_BUF_SIZE 8 + + +static const char *const ep_name[] = { + "ep0out", "ep1out", "ep2out", "ep3out", + "ep4out", "ep5out", "ep6out", "ep7out", + "ep8out", "ep9out", "ep10out", "ep11out", + "ep12out", "ep13out", "ep14out", "ep15out", + "ep0in", "ep1in", "ep2in", "ep3in", + "ep4in", "ep5in", "ep6in", "ep7in", + "ep8in", "ep9in", "ep10in", "ep11in", + "ep12in", "ep13in", "ep14in", "ep15in" +}; + +/*To release the wakelock from debugfs*/ +static int release_wlocks; + +struct msm_request { + struct usb_request req; + + /* saved copy of req.complete */ + void (*gadget_complete)(struct usb_ep *ep, + struct usb_request *req); + + + struct usb_info *ui; + struct msm_request *next; + struct msm_request *prev; + + unsigned busy:1; + unsigned live:1; + unsigned alloced:1; + + dma_addr_t dma; + dma_addr_t item_dma; + + struct ept_queue_item *item; +}; + +#define to_msm_request(r) container_of(r, struct msm_request, req) +#define to_msm_endpoint(r) container_of(r, struct msm_endpoint, ep) +#define to_msm_otg(xceiv) container_of(xceiv, struct msm_otg, otg) +#define is_b_sess_vld() ((OTGSC_BSV & readl(USB_OTGSC)) ? 1 : 0) +#define is_usb_online(ui) (ui->usb_state != USB_STATE_NOTATTACHED) + +struct msm_endpoint { + struct usb_ep ep; + struct usb_info *ui; + struct msm_request *req; /* head of pending requests */ + struct msm_request *last; + unsigned flags; + + /* bit number (0-31) in various status registers + ** as well as the index into the usb_info's array + ** of all endpoints + */ + unsigned char bit; + unsigned char num; + + unsigned wedged:1; + /* pointers to DMA transfer list area */ + /* these are allocated from the usb_info dma space */ + struct ept_queue_head *head; +}; + +/* PHY status check timer to monitor phy stuck up on reset */ +static struct timer_list phy_status_timer; + +static void usb_do_work(struct work_struct *w); +static void usb_do_remote_wakeup(struct work_struct *w); + + +#define USB_STATE_IDLE 0 +#define USB_STATE_ONLINE 1 +#define USB_STATE_OFFLINE 2 + +#define USB_FLAG_START 0x0001 +#define USB_FLAG_VBUS_ONLINE 0x0002 +#define USB_FLAG_VBUS_OFFLINE 0x0004 +#define USB_FLAG_RESET 0x0008 +#define USB_FLAG_SUSPEND 0x0010 +#define USB_FLAG_CONFIGURED 0x0020 + +#define USB_CHG_DET_DELAY msecs_to_jiffies(1000) +#define REMOTE_WAKEUP_DELAY msecs_to_jiffies(1000) +#define PHY_STATUS_CHECK_DELAY (jiffies + msecs_to_jiffies(1000)) + +struct usb_info { + /* lock for register/queue/device state changes */ + spinlock_t lock; + + /* single request used for handling setup transactions */ + struct usb_request *setup_req; + + struct platform_device *pdev; + int irq; + void *addr; + + unsigned state; + unsigned flags; + + atomic_t configured; + atomic_t running; + + struct dma_pool *pool; + + /* dma page to back the queue heads and items */ + unsigned char *buf; + dma_addr_t dma; + + struct ept_queue_head *head; + + /* used for allocation */ + unsigned next_item; + unsigned next_ifc_num; + + /* endpoints are ordered based on their status bits, + ** so they are OUT0, OUT1, ... OUT15, IN0, IN1, ... IN15 + */ + struct msm_endpoint ept[32]; + + + /* max power requested by selected configuration */ + unsigned b_max_pow; + unsigned chg_current; + struct delayed_work chg_det; + struct delayed_work chg_stop; + struct msm_hsusb_gadget_platform_data *pdata; + struct work_struct phy_status_check; + + struct work_struct work; + unsigned phy_status; + unsigned phy_fail_count; + + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct switch_dev sdev; + +#define ep0out ept[0] +#define ep0in ept[16] + + atomic_t ep0_dir; + atomic_t test_mode; + atomic_t offline_pending; + atomic_t softconnect; +#ifdef CONFIG_USB_OTG + u8 hnp_avail; +#endif + + atomic_t remote_wakeup; + atomic_t self_powered; + struct delayed_work rw_work; + + struct otg_transceiver *xceiv; + enum usb_device_state usb_state; + struct wake_lock wlock; +}; + +static const struct usb_ep_ops msm72k_ep_ops; +static struct usb_info *the_usb_info; + +static int msm72k_wakeup(struct usb_gadget *_gadget); +static int msm72k_pullup_internal(struct usb_gadget *_gadget, int is_active); +static int msm72k_set_halt(struct usb_ep *_ep, int value); +static void flush_endpoint(struct msm_endpoint *ept); +static void usb_reset(struct usb_info *ui); +static int usb_ept_set_halt(struct usb_ep *_ep, int value); + +static void msm_hsusb_set_speed(struct usb_info *ui) +{ + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + switch (readl(USB_PORTSC) & PORTSC_PSPD_MASK) { + case PORTSC_PSPD_FS: + dev_dbg(&ui->pdev->dev, "portchange USB_SPEED_FULL\n"); + ui->gadget.speed = USB_SPEED_FULL; + break; + case PORTSC_PSPD_LS: + dev_dbg(&ui->pdev->dev, "portchange USB_SPEED_LOW\n"); + ui->gadget.speed = USB_SPEED_LOW; + break; + case PORTSC_PSPD_HS: + dev_dbg(&ui->pdev->dev, "portchange USB_SPEED_HIGH\n"); + ui->gadget.speed = USB_SPEED_HIGH; + break; + } + spin_unlock_irqrestore(&ui->lock, flags); +} + +static void msm_hsusb_set_state(enum usb_device_state state) +{ + unsigned long flags; + + spin_lock_irqsave(&the_usb_info->lock, flags); + the_usb_info->usb_state = state; + spin_unlock_irqrestore(&the_usb_info->lock, flags); +} + +static enum usb_device_state msm_hsusb_get_state(void) +{ + unsigned long flags; + enum usb_device_state state; + + spin_lock_irqsave(&the_usb_info->lock, flags); + state = the_usb_info->usb_state; + spin_unlock_irqrestore(&the_usb_info->lock, flags); + + return state; +} + +static ssize_t print_switch_name(struct switch_dev *sdev, char *buf) +{ + return sprintf(buf, "%s\n", DRIVER_NAME); +} + +static ssize_t print_switch_state(struct switch_dev *sdev, char *buf) +{ + return sprintf(buf, "%s\n", sdev->state ? "online" : "offline"); +} + +static inline enum chg_type usb_get_chg_type(struct usb_info *ui) +{ + if ((readl(USB_PORTSC) & PORTSC_LS) == PORTSC_LS) + return USB_CHG_TYPE__WALLCHARGER; + else + return USB_CHG_TYPE__SDP; +} + +#define USB_WALLCHARGER_CHG_CURRENT 1800 +static int usb_get_max_power(struct usb_info *ui) +{ + struct msm_otg *otg = to_msm_otg(ui->xceiv); + unsigned long flags; + enum chg_type temp; + int suspended; + int configured; + unsigned bmaxpow; + + if (ui->gadget.is_a_peripheral) + return -EINVAL; + + temp = atomic_read(&otg->chg_type); + spin_lock_irqsave(&ui->lock, flags); + suspended = ui->usb_state == USB_STATE_SUSPENDED ? 1 : 0; + configured = atomic_read(&ui->configured); + bmaxpow = ui->b_max_pow; + spin_unlock_irqrestore(&ui->lock, flags); + + if (temp == USB_CHG_TYPE__INVALID) + return -ENODEV; + + if (temp == USB_CHG_TYPE__WALLCHARGER) + return USB_WALLCHARGER_CHG_CURRENT; + + if (suspended || !configured) + return 0; + + return bmaxpow; +} + +static int usb_phy_stuck_check(struct usb_info *ui) +{ + /* + * write some value (0xAA) into scratch reg (0x16) and read it back, + * If the read value is same as written value, means PHY is normal + * otherwise, PHY seems to have stuck. + */ + + if (otg_io_write(ui->xceiv, 0xAA, 0x16) == -1) { + dev_dbg(&ui->pdev->dev, + "%s(): ulpi write timeout\n", __func__); + return -EIO; + } + + if (otg_io_read(ui->xceiv, 0x16) != 0xAA) { + dev_dbg(&ui->pdev->dev, + "%s(): read value is incorrect\n", __func__); + return -EIO; + } + + return 0; +} + +/* + * This function checks the phy status by reading/writing to the + * phy scratch register. If the phy is stuck resets the HW + * */ +static void usb_phy_stuck_recover(struct work_struct *w) +{ + struct usb_info *ui = the_usb_info; + struct msm_otg *otg = to_msm_otg(ui->xceiv); + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + if (ui->gadget.speed != USB_SPEED_UNKNOWN || + ui->usb_state == USB_STATE_NOTATTACHED || + ui->driver == NULL) { + spin_unlock_irqrestore(&ui->lock, flags); + return; + } + spin_unlock_irqrestore(&ui->lock, flags); + + disable_irq(otg->irq); + if (usb_phy_stuck_check(ui)) { +#ifdef CONFIG_USB_MSM_ACA + del_timer_sync(&otg->id_timer); +#endif + ui->phy_fail_count++; + dev_err(&ui->pdev->dev, + "%s():PHY stuck, resetting HW\n", __func__); + /* + * PHY seems to have stuck, + * reset the PHY and HW link to recover the PHY + */ + usb_reset(ui); +#ifdef CONFIG_USB_MSM_ACA + mod_timer(&otg->id_timer, jiffies + + msecs_to_jiffies(OTG_ID_POLL_MS)); +#endif + msm72k_pullup_internal(&ui->gadget, 1); + } + enable_irq(otg->irq); +} + +static void usb_phy_status_check_timer(unsigned long data) +{ + struct usb_info *ui = the_usb_info; + + schedule_work(&ui->phy_status_check); +} + +static void usb_chg_stop(struct work_struct *w) +{ + struct usb_info *ui = container_of(w, struct usb_info, chg_stop.work); + struct msm_otg *otg = to_msm_otg(ui->xceiv); + enum chg_type temp; + + temp = atomic_read(&otg->chg_type); + + if (temp == USB_CHG_TYPE__SDP) + otg_set_power(ui->xceiv, 0); +} + +static void usb_chg_detect(struct work_struct *w) +{ + struct usb_info *ui = container_of(w, struct usb_info, chg_det.work); + struct msm_otg *otg = to_msm_otg(ui->xceiv); + enum chg_type temp = USB_CHG_TYPE__INVALID; + unsigned long flags; + int maxpower; + + spin_lock_irqsave(&ui->lock, flags); + if (ui->usb_state == USB_STATE_NOTATTACHED) { + spin_unlock_irqrestore(&ui->lock, flags); + return; + } + + temp = usb_get_chg_type(ui); + spin_unlock_irqrestore(&ui->lock, flags); + + atomic_set(&otg->chg_type, temp); + maxpower = usb_get_max_power(ui); + if (maxpower > 0) + otg_set_power(ui->xceiv, maxpower); + + /* USB driver prevents idle and suspend power collapse(pc) + * while USB cable is connected. But when dedicated charger is + * connected, driver can vote for idle and suspend pc. + * OTG driver handles idle pc as part of above otg_set_power call + * when wallcharger is attached. To allow suspend pc, release the + * wakelock which will be re-acquired for any sub-sequent usb interrupts + * */ + if (temp == USB_CHG_TYPE__WALLCHARGER) { + pm_runtime_put_sync(&ui->pdev->dev); + wake_unlock(&ui->wlock); + } +} + +static int usb_ep_get_stall(struct msm_endpoint *ept) +{ + unsigned int n; + struct usb_info *ui = ept->ui; + + n = readl(USB_ENDPTCTRL(ept->num)); + if (ept->flags & EPT_FLAG_IN) + return (CTRL_TXS & n) ? 1 : 0; + else + return (CTRL_RXS & n) ? 1 : 0; +} + +static void init_endpoints(struct usb_info *ui) +{ + unsigned n; + + for (n = 0; n < 32; n++) { + struct msm_endpoint *ept = ui->ept + n; + + ept->ui = ui; + ept->bit = n; + ept->num = n & 15; + ept->ep.name = ep_name[n]; + ept->ep.ops = &msm72k_ep_ops; + + if (ept->bit > 15) { + /* IN endpoint */ + ept->head = ui->head + (ept->num << 1) + 1; + ept->flags = EPT_FLAG_IN; + } else { + /* OUT endpoint */ + ept->head = ui->head + (ept->num << 1); + ept->flags = 0; + } + + } +} + +static void config_ept(struct msm_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + unsigned cfg = CONFIG_MAX_PKT(ept->ep.maxpacket) | CONFIG_ZLT; + + /* ep0 out needs interrupt-on-setup */ + if (ept->bit == 0) + cfg |= CONFIG_IOS; + + ept->head->config = cfg; + ept->head->next = TERMINATE; + + if (ept->ep.maxpacket) + dev_dbg(&ui->pdev->dev, + "ept #%d %s max:%d head:%p bit:%d\n", + ept->num, + (ept->flags & EPT_FLAG_IN) ? "in" : "out", + ept->ep.maxpacket, ept->head, ept->bit); +} + +static void configure_endpoints(struct usb_info *ui) +{ + unsigned n; + + for (n = 0; n < 32; n++) + config_ept(ui->ept + n); +} + +struct usb_request *usb_ept_alloc_req(struct msm_endpoint *ept, + unsigned bufsize, gfp_t gfp_flags) +{ + struct usb_info *ui = ept->ui; + struct msm_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + goto fail1; + + req->item = dma_pool_alloc(ui->pool, gfp_flags, &req->item_dma); + if (!req->item) + goto fail2; + + if (bufsize) { + req->req.buf = kmalloc(bufsize, gfp_flags); + if (!req->req.buf) + goto fail3; + req->alloced = 1; + } + + return &req->req; + +fail3: + dma_pool_free(ui->pool, req->item, req->item_dma); +fail2: + kfree(req); +fail1: + return 0; +} + +static void usb_ept_enable(struct msm_endpoint *ept, int yes, + unsigned char ep_type) +{ + struct usb_info *ui = ept->ui; + int in = ept->flags & EPT_FLAG_IN; + unsigned n; + + n = readl(USB_ENDPTCTRL(ept->num)); + + if (in) { + if (yes) { + n = (n & (~CTRL_TXT_MASK)) | + (ep_type << CTRL_TXT_EP_TYPE_SHIFT); + n |= CTRL_TXE | CTRL_TXR; + } else + n &= (~CTRL_TXE); + } else { + if (yes) { + n = (n & (~CTRL_RXT_MASK)) | + (ep_type << CTRL_RXT_EP_TYPE_SHIFT); + n |= CTRL_RXE | CTRL_RXR; + } else + n &= ~(CTRL_RXE); + } + /* complete all the updates to ept->head before enabling endpoint*/ + mb(); + writel(n, USB_ENDPTCTRL(ept->num)); + + /* Ensure endpoint is enabled before returning */ + mb(); + + dev_dbg(&ui->pdev->dev, "ept %d %s %s\n", + ept->num, in ? "in" : "out", yes ? "enabled" : "disabled"); +} + +static void usb_ept_start(struct msm_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + struct msm_request *req = ept->req; + struct msm_request *f_req = ept->req; + unsigned n = 1 << ept->bit; + unsigned info; + int reprime_cnt = 0; + + BUG_ON(req->live); + + while (req) { + req->live = 1; + /* prepare the transaction descriptor item for the hardware */ + req->item->info = + INFO_BYTES(req->req.length) | INFO_IOC | INFO_ACTIVE; + req->item->page0 = req->dma; + req->item->page1 = (req->dma + 0x1000) & 0xfffff000; + req->item->page2 = (req->dma + 0x2000) & 0xfffff000; + req->item->page3 = (req->dma + 0x3000) & 0xfffff000; + + if (req->next == NULL) { + req->item->next = TERMINATE; + break; + } + req->item->next = req->next->item_dma; + req = req->next; + } + + rmb(); + /* link the hw queue head to the request's transaction item */ + ept->head->next = ept->req->item_dma; + ept->head->info = 0; + +reprime_ept: + /* flush buffers before priming ept */ + mb(); + /* during high throughput testing it is observed that + * ept stat bit is not set even thoguh all the data + * structures are updated properly and ept prime bit + * is set. To workaround the issue, use dTD INFO bit + * to make decision on re-prime or not. + */ + writel_relaxed(n, USB_ENDPTPRIME); + /* busy wait till endptprime gets clear */ + while ((readl_relaxed(USB_ENDPTPRIME) & n)) + ; + if (readl_relaxed(USB_ENDPTSTAT) & n) + return; + + rmb(); + info = f_req->item->info; + if (info & INFO_ACTIVE) { + if (reprime_cnt++ < 3) + goto reprime_ept; + else + pr_err("%s(): ept%d%s prime failed. ept: config: %x" + "active: %x next: %x info: %x\n" + " req@ %x next: %x info: %x\n", + __func__, ept->num, + ept->flags & EPT_FLAG_IN ? "in" : "out", + ept->head->config, ept->head->active, + ept->head->next, ept->head->info, + f_req->item_dma, f_req->item->next, info); + } +} + +int usb_ept_queue_xfer(struct msm_endpoint *ept, struct usb_request *_req) +{ + unsigned long flags; + struct msm_request *req = to_msm_request(_req); + struct msm_request *last; + struct usb_info *ui = ept->ui; + unsigned length = req->req.length; + + if (length > 0x4000) + return -EMSGSIZE; + + spin_lock_irqsave(&ui->lock, flags); + + if (req->busy) { + req->req.status = -EBUSY; + spin_unlock_irqrestore(&ui->lock, flags); + dev_err(&ui->pdev->dev, + "usb_ept_queue_xfer() tried to queue busy request\n"); + return -EBUSY; + } + + if (!atomic_read(&ui->configured) && (ept->num != 0)) { + req->req.status = -ESHUTDOWN; + spin_unlock_irqrestore(&ui->lock, flags); + if (printk_ratelimit()) + dev_err(&ui->pdev->dev, + "%s: called while offline\n", __func__); + return -ESHUTDOWN; + } + + if (ui->usb_state == USB_STATE_SUSPENDED) { + if (!atomic_read(&ui->remote_wakeup)) { + req->req.status = -EAGAIN; + spin_unlock_irqrestore(&ui->lock, flags); + if (printk_ratelimit()) + dev_err(&ui->pdev->dev, + "%s: cannot queue as bus is suspended " + "ept #%d %s max:%d head:%p bit:%d\n", + __func__, ept->num, + (ept->flags & EPT_FLAG_IN) ? "in" : "out", + ept->ep.maxpacket, ept->head, ept->bit); + + return -EAGAIN; + } + + wake_lock(&ui->wlock); + otg_set_suspend(ui->xceiv, 0); + schedule_delayed_work(&ui->rw_work, REMOTE_WAKEUP_DELAY); + } + + req->busy = 1; + req->live = 0; + req->next = 0; + req->req.status = -EBUSY; + + req->dma = dma_map_single(NULL, req->req.buf, length, + (ept->flags & EPT_FLAG_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + + + /* Add the new request to the end of the queue */ + last = ept->last; + if (last) { + /* Already requests in the queue. add us to the + * end, but let the completion interrupt actually + * start things going, to avoid hw issues + */ + last->next = req; + req->prev = last; + + } else { + /* queue was empty -- kick the hardware */ + ept->req = req; + req->prev = NULL; + usb_ept_start(ept); + } + ept->last = req; + + spin_unlock_irqrestore(&ui->lock, flags); + return 0; +} + +/* --- endpoint 0 handling --- */ + +static void ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct msm_request *r = to_msm_request(req); + struct msm_endpoint *ept = to_msm_endpoint(ep); + struct usb_info *ui = ept->ui; + + req->complete = r->gadget_complete; + r->gadget_complete = 0; + if (req->complete) + req->complete(&ui->ep0in.ep, req); +} + +static void ep0_status_complete(struct usb_ep *ep, struct usb_request *_req) +{ + struct usb_request *req = _req->context; + struct msm_request *r; + struct msm_endpoint *ept; + struct usb_info *ui; + + pr_debug("%s:\n", __func__); + if (!req) + return; + + r = to_msm_request(req); + ept = to_msm_endpoint(ep); + ui = ept->ui; + _req->context = 0; + + req->complete = r->gadget_complete; + req->zero = 0; + r->gadget_complete = 0; + if (req->complete) + req->complete(&ui->ep0in.ep, req); + +} + +static void ep0_status_phase(struct usb_ep *ep, struct usb_request *req) +{ + struct msm_endpoint *ept = to_msm_endpoint(ep); + struct usb_info *ui = ept->ui; + + pr_debug("%s:\n", __func__); + + req->length = 0; + req->complete = ep0_status_complete; + + /* status phase */ + if (atomic_read(&ui->ep0_dir) == USB_DIR_IN) + usb_ept_queue_xfer(&ui->ep0out, req); + else + usb_ept_queue_xfer(&ui->ep0in, req); +} + +static void ep0in_send_zero_leng_pkt(struct msm_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + struct usb_request *req = ui->setup_req; + + pr_debug("%s:\n", __func__); + + req->length = 0; + req->complete = ep0_status_phase; + usb_ept_queue_xfer(&ui->ep0in, req); +} + +static void ep0_queue_ack_complete(struct usb_ep *ep, + struct usb_request *_req) +{ + struct msm_endpoint *ept = to_msm_endpoint(ep); + struct usb_info *ui = ept->ui; + struct usb_request *req = ui->setup_req; + + pr_debug("%s: _req:%p actual:%d length:%d zero:%d\n", + __func__, _req, _req->actual, + _req->length, _req->zero); + + /* queue up the receive of the ACK response from the host */ + if (_req->status == 0 && _req->actual == _req->length) { + req->context = _req; + if (atomic_read(&ui->ep0_dir) == USB_DIR_IN) { + if (_req->zero && _req->length && + !(_req->length % ep->maxpacket)) { + ep0in_send_zero_leng_pkt(&ui->ep0in); + return; + } + } + ep0_status_phase(ep, req); + } else + ep0_complete(ep, _req); +} + +static void ep0_setup_ack_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct msm_endpoint *ept = to_msm_endpoint(ep); + struct usb_info *ui = ept->ui; + unsigned int temp; + int test_mode = atomic_read(&ui->test_mode); + + if (!test_mode) + return; + + switch (test_mode) { + case J_TEST: + dev_info(&ui->pdev->dev, "usb electrical test mode: (J)\n"); + temp = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(temp | PORTSC_PTC_J_STATE, USB_PORTSC); + break; + + case K_TEST: + dev_info(&ui->pdev->dev, "usb electrical test mode: (K)\n"); + temp = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(temp | PORTSC_PTC_K_STATE, USB_PORTSC); + break; + + case SE0_NAK_TEST: + dev_info(&ui->pdev->dev, + "usb electrical test mode: (SE0-NAK)\n"); + temp = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(temp | PORTSC_PTC_SE0_NAK, USB_PORTSC); + break; + + case TST_PKT_TEST: + dev_info(&ui->pdev->dev, + "usb electrical test mode: (TEST_PKT)\n"); + temp = readl(USB_PORTSC) & (~PORTSC_PTC); + writel(temp | PORTSC_PTC_TST_PKT, USB_PORTSC); + break; + } +} + +static void ep0_setup_ack(struct usb_info *ui) +{ + struct usb_request *req = ui->setup_req; + req->length = 0; + req->complete = ep0_setup_ack_complete; + usb_ept_queue_xfer(&ui->ep0in, req); +} + +static void ep0_setup_stall(struct usb_info *ui) +{ + writel((1<<16) | (1<<0), USB_ENDPTCTRL(0)); +} + +static void ep0_setup_send(struct usb_info *ui, unsigned length) +{ + struct usb_request *req = ui->setup_req; + struct msm_request *r = to_msm_request(req); + struct msm_endpoint *ept = &ui->ep0in; + + req->length = length; + req->complete = ep0_queue_ack_complete; + r->gadget_complete = 0; + usb_ept_queue_xfer(ept, req); +} + +static void handle_setup(struct usb_info *ui) +{ + struct usb_ctrlrequest ctl; + struct usb_request *req = ui->setup_req; + int ret; +#ifdef CONFIG_USB_OTG + u8 hnp; + unsigned long flags; +#endif + + memcpy(&ctl, ui->ep0out.head->setup_data, sizeof(ctl)); + /* Ensure buffer is read before acknowledging to h/w */ + mb(); + + writel(EPT_RX(0), USB_ENDPTSETUPSTAT); + + if (ctl.bRequestType & USB_DIR_IN) + atomic_set(&ui->ep0_dir, USB_DIR_IN); + else + atomic_set(&ui->ep0_dir, USB_DIR_OUT); + + /* any pending ep0 transactions must be canceled */ + flush_endpoint(&ui->ep0out); + flush_endpoint(&ui->ep0in); + + dev_dbg(&ui->pdev->dev, + "setup: type=%02x req=%02x val=%04x idx=%04x len=%04x\n", + ctl.bRequestType, ctl.bRequest, ctl.wValue, + ctl.wIndex, ctl.wLength); + + if ((ctl.bRequestType & (USB_DIR_IN | USB_TYPE_MASK)) == + (USB_DIR_IN | USB_TYPE_STANDARD)) { + if (ctl.bRequest == USB_REQ_GET_STATUS) { + /* OTG supplement Rev 2.0 introduces another device + * GET_STATUS request for HNP polling with length = 1. + */ + u8 len = 2; + switch (ctl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_ENDPOINT: + { + struct msm_endpoint *ept; + unsigned num = + ctl.wIndex & USB_ENDPOINT_NUMBER_MASK; + u16 temp = 0; + + if (num == 0) { + memset(req->buf, 0, 2); + break; + } + if (ctl.wIndex & USB_ENDPOINT_DIR_MASK) + num += 16; + ept = &ui->ep0out + num; + temp = usb_ep_get_stall(ept); + temp = temp << USB_ENDPOINT_HALT; + memcpy(req->buf, &temp, 2); + break; + } + case USB_RECIP_DEVICE: + { + u16 temp = 0; + + if (ctl.wIndex == OTG_STATUS_SELECTOR) { +#ifdef CONFIG_USB_OTG + spin_lock_irqsave(&ui->lock, flags); + hnp = (ui->gadget.host_request << + HOST_REQUEST_FLAG); + ui->hnp_avail = 1; + spin_unlock_irqrestore(&ui->lock, + flags); + memcpy(req->buf, &hnp, 1); + len = 1; +#else + goto stall; +#endif + } else { + temp = (atomic_read(&ui->self_powered) + << USB_DEVICE_SELF_POWERED); + temp |= (atomic_read(&ui->remote_wakeup) + << USB_DEVICE_REMOTE_WAKEUP); + memcpy(req->buf, &temp, 2); + } + break; + } + case USB_RECIP_INTERFACE: + memset(req->buf, 0, 2); + break; + default: + goto stall; + } + ep0_setup_send(ui, len); + return; + } + } + if (ctl.bRequestType == + (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT)) { + if ((ctl.bRequest == USB_REQ_CLEAR_FEATURE) || + (ctl.bRequest == USB_REQ_SET_FEATURE)) { + if ((ctl.wValue == 0) && (ctl.wLength == 0)) { + unsigned num = ctl.wIndex & 0x0f; + + if (num != 0) { + struct msm_endpoint *ept; + + if (ctl.wIndex & 0x80) + num += 16; + ept = &ui->ep0out + num; + + if (ept->wedged) + goto ack; + if (ctl.bRequest == USB_REQ_SET_FEATURE) + usb_ept_set_halt(&ept->ep, 1); + else + usb_ept_set_halt(&ept->ep, 0); + } + goto ack; + } + } + } + if (ctl.bRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD)) { + if (ctl.bRequest == USB_REQ_SET_CONFIGURATION) { + atomic_set(&ui->configured, !!ctl.wValue); + msm_hsusb_set_state(USB_STATE_CONFIGURED); + } else if (ctl.bRequest == USB_REQ_SET_ADDRESS) { + /* + * Gadget speed should be set when PCI interrupt + * occurs. But sometimes, PCI interrupt is not + * occuring after reset. Hence update the gadget + * speed here. + */ + if (ui->gadget.speed == USB_SPEED_UNKNOWN) { + dev_info(&ui->pdev->dev, + "PCI intr missed" + "set speed explictly\n"); + msm_hsusb_set_speed(ui); + } + msm_hsusb_set_state(USB_STATE_ADDRESS); + + /* write address delayed (will take effect + ** after the next IN txn) + */ + writel((ctl.wValue << 25) | (1 << 24), USB_DEVICEADDR); + goto ack; + } else if (ctl.bRequest == USB_REQ_SET_FEATURE) { + switch (ctl.wValue) { + case USB_DEVICE_TEST_MODE: + switch (ctl.wIndex) { + case J_TEST: + case K_TEST: + case SE0_NAK_TEST: + case TST_PKT_TEST: + atomic_set(&ui->test_mode, ctl.wIndex); + goto ack; + } + goto stall; + case USB_DEVICE_REMOTE_WAKEUP: + atomic_set(&ui->remote_wakeup, 1); + goto ack; +#ifdef CONFIG_USB_OTG + case USB_DEVICE_B_HNP_ENABLE: + ui->gadget.b_hnp_enable = 1; + goto ack; + case USB_DEVICE_A_HNP_SUPPORT: + case USB_DEVICE_A_ALT_HNP_SUPPORT: + /* B-devices compliant to OTG spec + * Rev 2.0 are not required to + * suppport these features. + */ + goto stall; +#endif + } + } else if ((ctl.bRequest == USB_REQ_CLEAR_FEATURE) && + (ctl.wValue == USB_DEVICE_REMOTE_WAKEUP)) { + atomic_set(&ui->remote_wakeup, 0); + goto ack; + } + } + + /* delegate if we get here */ + if (ui->driver) { + ret = ui->driver->setup(&ui->gadget, &ctl); + if (ret >= 0) + return; + } + +stall: + /* stall ep0 on error */ + ep0_setup_stall(ui); + return; + +ack: + ep0_setup_ack(ui); +} + +static void handle_endpoint(struct usb_info *ui, unsigned bit) +{ + struct msm_endpoint *ept = ui->ept + bit; + struct msm_request *req; + unsigned long flags; + unsigned info; + + /* + INFO("handle_endpoint() %d %s req=%p(%08x)\n", + ept->num, (ept->flags & EPT_FLAG_IN) ? "in" : "out", + ept->req, ept->req ? ept->req->item_dma : 0); + */ + + /* expire all requests that are no longer active */ + spin_lock_irqsave(&ui->lock, flags); + while ((req = ept->req)) { + /* if we've processed all live requests, time to + * restart the hardware on the next non-live request + */ + if (!req->live) { + usb_ept_start(ept); + break; + } + + /* clean speculative fetches on req->item->info */ + dma_coherent_post_ops(); + info = req->item->info; + /* if the transaction is still in-flight, stop here */ + if (info & INFO_ACTIVE) + break; + + /* advance ept queue to the next request */ + ept->req = req->next; + if (ept->req == 0) + ept->last = 0; + + dma_unmap_single(NULL, req->dma, req->req.length, + (ept->flags & EPT_FLAG_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + + if (info & (INFO_HALTED | INFO_BUFFER_ERROR | INFO_TXN_ERROR)) { + /* XXX pass on more specific error code */ + req->req.status = -EIO; + req->req.actual = 0; + dev_err(&ui->pdev->dev, + "ept %d %s error. info=%08x\n", + ept->num, + (ept->flags & EPT_FLAG_IN) ? "in" : "out", + info); + } else { + req->req.status = 0; + req->req.actual = + req->req.length - ((info >> 16) & 0x7FFF); + } + req->busy = 0; + req->live = 0; + + if (req->req.complete) { + spin_unlock_irqrestore(&ui->lock, flags); + req->req.complete(&ept->ep, &req->req); + spin_lock_irqsave(&ui->lock, flags); + } + } + spin_unlock_irqrestore(&ui->lock, flags); +} + +static void flush_endpoint_hw(struct usb_info *ui, unsigned bits) +{ + /* flush endpoint, canceling transactions + ** - this can take a "large amount of time" (per databook) + ** - the flush can fail in some cases, thus we check STAT + ** and repeat if we're still operating + ** (does the fact that this doesn't use the tripwire matter?!) + */ + do { + writel(bits, USB_ENDPTFLUSH); + while (readl(USB_ENDPTFLUSH) & bits) + udelay(100); + } while (readl(USB_ENDPTSTAT) & bits); +} + +static void flush_endpoint_sw(struct msm_endpoint *ept) +{ + struct usb_info *ui = ept->ui; + struct msm_request *req, *next_req = NULL; + unsigned long flags; + + /* inactive endpoints have nothing to do here */ + if (ept->ep.maxpacket == 0) + return; + + /* put the queue head in a sane state */ + ept->head->info = 0; + ept->head->next = TERMINATE; + + /* cancel any pending requests */ + spin_lock_irqsave(&ui->lock, flags); + req = ept->req; + ept->req = 0; + ept->last = 0; + while (req != 0) { + req->busy = 0; + req->live = 0; + req->req.status = -ESHUTDOWN; + req->req.actual = 0; + + /* Gadget driver may free the request in completion + * handler. So keep a copy of next req pointer + * before calling completion handler. + */ + next_req = req->next; + if (req->req.complete) { + spin_unlock_irqrestore(&ui->lock, flags); + req->req.complete(&ept->ep, &req->req); + spin_lock_irqsave(&ui->lock, flags); + } + req = next_req; + } + spin_unlock_irqrestore(&ui->lock, flags); +} + +static void flush_endpoint(struct msm_endpoint *ept) +{ + flush_endpoint_hw(ept->ui, (1 << ept->bit)); + flush_endpoint_sw(ept); +} + +static irqreturn_t usb_interrupt(int irq, void *data) +{ + struct usb_info *ui = data; + unsigned n; + unsigned long flags; + + n = readl(USB_USBSTS); + writel(n, USB_USBSTS); + + /* somehow we got an IRQ while in the reset sequence: ignore it */ + if (!atomic_read(&ui->running)) + return IRQ_HANDLED; + + if (n & STS_PCI) { + msm_hsusb_set_speed(ui); + if (atomic_read(&ui->configured)) { + wake_lock(&ui->wlock); + + spin_lock_irqsave(&ui->lock, flags); + ui->usb_state = USB_STATE_CONFIGURED; + ui->flags = USB_FLAG_CONFIGURED; + spin_unlock_irqrestore(&ui->lock, flags); + + ui->driver->resume(&ui->gadget); + schedule_work(&ui->work); + } else { + msm_hsusb_set_state(USB_STATE_DEFAULT); + } + +#ifdef CONFIG_USB_OTG + /* notify otg to clear A_BIDL_ADIS timer */ + if (ui->gadget.is_a_peripheral) + otg_set_suspend(ui->xceiv, 0); +#endif + } + + if (n & STS_URI) { + dev_dbg(&ui->pdev->dev, "reset\n"); + spin_lock_irqsave(&ui->lock, flags); + ui->gadget.speed = USB_SPEED_UNKNOWN; + spin_unlock_irqrestore(&ui->lock, flags); +#ifdef CONFIG_USB_OTG + /* notify otg to clear A_BIDL_ADIS timer */ + if (ui->gadget.is_a_peripheral) + otg_set_suspend(ui->xceiv, 0); + spin_lock_irqsave(&ui->lock, flags); + /* Host request is persistent across reset */ + ui->gadget.b_hnp_enable = 0; + ui->hnp_avail = 0; + spin_unlock_irqrestore(&ui->lock, flags); +#endif + msm_hsusb_set_state(USB_STATE_DEFAULT); + atomic_set(&ui->remote_wakeup, 0); + if (!ui->gadget.is_a_peripheral) + schedule_delayed_work(&ui->chg_stop, 0); + + writel(readl(USB_ENDPTSETUPSTAT), USB_ENDPTSETUPSTAT); + writel(readl(USB_ENDPTCOMPLETE), USB_ENDPTCOMPLETE); + writel(0xffffffff, USB_ENDPTFLUSH); + writel(0, USB_ENDPTCTRL(1)); + + wake_lock(&ui->wlock); + if (atomic_read(&ui->configured)) { + /* marking us offline will cause ept queue attempts + ** to fail + */ + atomic_set(&ui->configured, 0); + /* Defer sending offline uevent to userspace */ + atomic_set(&ui->offline_pending, 1); + + /* XXX: we can't seem to detect going offline, + * XXX: so deconfigure on reset for the time being + */ + if (ui->driver) { + dev_dbg(&ui->pdev->dev, + "usb: notify offline\n"); + ui->driver->disconnect(&ui->gadget); + } + /* cancel pending ep0 transactions */ + flush_endpoint(&ui->ep0out); + flush_endpoint(&ui->ep0in); + + } + /* Start phy stuck timer */ + if (ui->pdata && ui->pdata->is_phy_status_timer_on) + mod_timer(&phy_status_timer, PHY_STATUS_CHECK_DELAY); + } + + if (n & STS_SLI) { + dev_dbg(&ui->pdev->dev, "suspend\n"); + + spin_lock_irqsave(&ui->lock, flags); + ui->usb_state = USB_STATE_SUSPENDED; + ui->flags = USB_FLAG_SUSPEND; + spin_unlock_irqrestore(&ui->lock, flags); + + ui->driver->suspend(&ui->gadget); + schedule_work(&ui->work); +#ifdef CONFIG_USB_OTG + /* notify otg for + * 1. kicking A_BIDL_ADIS timer in case of A-peripheral + * 2. disabling pull-up and kicking B_ASE0_RST timer + */ + if (ui->gadget.b_hnp_enable || ui->gadget.is_a_peripheral) + otg_set_suspend(ui->xceiv, 1); +#endif + } + + if (n & STS_UI) { + n = readl(USB_ENDPTSETUPSTAT); + if (n & EPT_RX(0)) + handle_setup(ui); + + n = readl(USB_ENDPTCOMPLETE); + writel(n, USB_ENDPTCOMPLETE); + while (n) { + unsigned bit = __ffs(n); + handle_endpoint(ui, bit); + n = n & (~(1 << bit)); + } + } + return IRQ_HANDLED; +} + +static void usb_prepare(struct usb_info *ui) +{ + spin_lock_init(&ui->lock); + + memset(ui->buf, 0, 4096); + ui->head = (void *) (ui->buf + 0); + + /* only important for reset/reinit */ + memset(ui->ept, 0, sizeof(ui->ept)); + ui->next_item = 0; + ui->next_ifc_num = 0; + + init_endpoints(ui); + + ui->ep0in.ep.maxpacket = 64; + ui->ep0out.ep.maxpacket = 64; + + ui->setup_req = + usb_ept_alloc_req(&ui->ep0in, SETUP_BUF_SIZE, GFP_KERNEL); + + INIT_WORK(&ui->work, usb_do_work); + INIT_DELAYED_WORK(&ui->chg_det, usb_chg_detect); + INIT_DELAYED_WORK(&ui->chg_stop, usb_chg_stop); + INIT_DELAYED_WORK(&ui->rw_work, usb_do_remote_wakeup); + if (ui->pdata && ui->pdata->is_phy_status_timer_on) + INIT_WORK(&ui->phy_status_check, usb_phy_stuck_recover); +} + +static void usb_reset(struct usb_info *ui) +{ + struct msm_otg *otg = to_msm_otg(ui->xceiv); + + dev_dbg(&ui->pdev->dev, "reset controller\n"); + + atomic_set(&ui->running, 0); + + /* + * PHY reset takes minimum 100 msec. Hence reset only link + * during HNP. Reset PHY and link in B-peripheral mode. + */ + if (ui->gadget.is_a_peripheral) + otg->reset(ui->xceiv, 0); + else + otg->reset(ui->xceiv, 1); + + /* set usb controller interrupt threshold to zero*/ + writel((readl(USB_USBCMD) & ~USBCMD_ITC_MASK) | USBCMD_ITC(0), + USB_USBCMD); + + writel(ui->dma, USB_ENDPOINTLISTADDR); + + configure_endpoints(ui); + + /* marking us offline will cause ept queue attempts to fail */ + atomic_set(&ui->configured, 0); + + if (ui->driver) { + dev_dbg(&ui->pdev->dev, "usb: notify offline\n"); + ui->driver->disconnect(&ui->gadget); + } + + /* cancel pending ep0 transactions */ + flush_endpoint(&ui->ep0out); + flush_endpoint(&ui->ep0in); + + /* enable interrupts */ + writel(STS_URI | STS_SLI | STS_UI | STS_PCI, USB_USBINTR); + + /* Ensure that h/w RESET is completed before returning */ + mb(); + + atomic_set(&ui->running, 1); +} + +static void usb_start(struct usb_info *ui) +{ + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + ui->flags |= USB_FLAG_START; + schedule_work(&ui->work); + spin_unlock_irqrestore(&ui->lock, flags); +} + +static int usb_free(struct usb_info *ui, int ret) +{ + dev_dbg(&ui->pdev->dev, "usb_free(%d)\n", ret); + + if (ui->xceiv) + otg_put_transceiver(ui->xceiv); + + if (ui->irq) + free_irq(ui->irq, 0); + if (ui->pool) + dma_pool_destroy(ui->pool); + if (ui->dma) + dma_free_coherent(&ui->pdev->dev, 4096, ui->buf, ui->dma); + kfree(ui); + return ret; +} + +static void usb_do_work_check_vbus(struct usb_info *ui) +{ + unsigned long iflags; + + spin_lock_irqsave(&ui->lock, iflags); + if (is_usb_online(ui)) + ui->flags |= USB_FLAG_VBUS_ONLINE; + else + ui->flags |= USB_FLAG_VBUS_OFFLINE; + spin_unlock_irqrestore(&ui->lock, iflags); +} + +static void usb_do_work(struct work_struct *w) +{ + struct usb_info *ui = container_of(w, struct usb_info, work); + struct msm_otg *otg = to_msm_otg(ui->xceiv); + unsigned long iflags; + unsigned flags, _vbus; + + for (;;) { + spin_lock_irqsave(&ui->lock, iflags); + flags = ui->flags; + ui->flags = 0; + _vbus = is_usb_online(ui); + spin_unlock_irqrestore(&ui->lock, iflags); + + /* give up if we have nothing to do */ + if (flags == 0) + break; + + switch (ui->state) { + case USB_STATE_IDLE: + if (flags & USB_FLAG_START) { + int ret; + + if (!_vbus) { + ui->state = USB_STATE_OFFLINE; + break; + } + + pm_runtime_get_noresume(&ui->pdev->dev); + pm_runtime_resume(&ui->pdev->dev); + dev_dbg(&ui->pdev->dev, + "msm72k_udc: IDLE -> ONLINE\n"); + usb_reset(ui); + ret = request_irq(otg->irq, usb_interrupt, + IRQF_SHARED, + ui->pdev->name, ui); + /* FIXME: should we call BUG_ON when + * requst irq fails + */ + if (ret) { + dev_err(&ui->pdev->dev, + "hsusb: peripheral: request irq" + " failed:(%d)", ret); + break; + } + ui->irq = otg->irq; + ui->state = USB_STATE_ONLINE; + usb_do_work_check_vbus(ui); + + if (!atomic_read(&ui->softconnect)) + break; + + msm72k_pullup_internal(&ui->gadget, 1); + + if (!ui->gadget.is_a_peripheral) + schedule_delayed_work( + &ui->chg_det, + USB_CHG_DET_DELAY); + + } + break; + case USB_STATE_ONLINE: + if (atomic_read(&ui->offline_pending)) { + switch_set_state(&ui->sdev, 0); + atomic_set(&ui->offline_pending, 0); + } + + /* If at any point when we were online, we received + * the signal to go offline, we must honor it + */ + if (flags & USB_FLAG_VBUS_OFFLINE) { + + ui->chg_current = 0; + /* wait incase chg_detect is running */ + if (!ui->gadget.is_a_peripheral) + cancel_delayed_work_sync(&ui->chg_det); + + dev_dbg(&ui->pdev->dev, + "msm72k_udc: ONLINE -> OFFLINE\n"); + + atomic_set(&ui->running, 0); + atomic_set(&ui->remote_wakeup, 0); + atomic_set(&ui->configured, 0); + + if (ui->driver) { + dev_dbg(&ui->pdev->dev, + "usb: notify offline\n"); + ui->driver->disconnect(&ui->gadget); + } + /* cancel pending ep0 transactions */ + flush_endpoint(&ui->ep0out); + flush_endpoint(&ui->ep0in); + + /* synchronize with irq context */ + spin_lock_irqsave(&ui->lock, iflags); +#ifdef CONFIG_USB_OTG + ui->gadget.host_request = 0; + ui->gadget.b_hnp_enable = 0; + ui->hnp_avail = 0; +#endif + msm72k_pullup_internal(&ui->gadget, 0); + spin_unlock_irqrestore(&ui->lock, iflags); + + + /* if charger is initialized to known type + * we must let modem know about charger + * disconnection + */ + otg_set_power(ui->xceiv, 0); + + if (ui->irq) { + free_irq(ui->irq, ui); + ui->irq = 0; + } + + + switch_set_state(&ui->sdev, 0); + + ui->state = USB_STATE_OFFLINE; + usb_do_work_check_vbus(ui); + pm_runtime_put_noidle(&ui->pdev->dev); + pm_runtime_suspend(&ui->pdev->dev); + wake_unlock(&ui->wlock); + break; + } + if (flags & USB_FLAG_SUSPEND) { + int maxpower = usb_get_max_power(ui); + + if (maxpower < 0) + break; + + otg_set_power(ui->xceiv, 0); + /* To support TCXO during bus suspend + * This might be dummy check since bus suspend + * is not implemented as of now + * */ + if (release_wlocks) + wake_unlock(&ui->wlock); + + /* TBD: Initiate LPM at usb bus suspend */ + break; + } + if (flags & USB_FLAG_CONFIGURED) { + int maxpower = usb_get_max_power(ui); + + /* We may come here even when no configuration + * is selected. Send online/offline event + * accordingly. + */ + switch_set_state(&ui->sdev, + atomic_read(&ui->configured)); + + if (maxpower < 0) + break; + + ui->chg_current = maxpower; + otg_set_power(ui->xceiv, maxpower); + break; + } + if (flags & USB_FLAG_RESET) { + dev_dbg(&ui->pdev->dev, + "msm72k_udc: ONLINE -> RESET\n"); + msm72k_pullup_internal(&ui->gadget, 0); + usb_reset(ui); + msm72k_pullup_internal(&ui->gadget, 1); + dev_dbg(&ui->pdev->dev, + "msm72k_udc: RESET -> ONLINE\n"); + break; + } + break; + case USB_STATE_OFFLINE: + /* If we were signaled to go online and vbus is still + * present when we received the signal, go online. + */ + if ((flags & USB_FLAG_VBUS_ONLINE) && _vbus) { + int ret; + + pm_runtime_get_noresume(&ui->pdev->dev); + pm_runtime_resume(&ui->pdev->dev); + dev_dbg(&ui->pdev->dev, + "msm72k_udc: OFFLINE -> ONLINE\n"); + + usb_reset(ui); + ui->state = USB_STATE_ONLINE; + usb_do_work_check_vbus(ui); + ret = request_irq(otg->irq, usb_interrupt, + IRQF_SHARED, + ui->pdev->name, ui); + /* FIXME: should we call BUG_ON when + * requst irq fails + */ + if (ret) { + dev_err(&ui->pdev->dev, + "hsusb: peripheral: request irq" + " failed:(%d)", ret); + break; + } + ui->irq = otg->irq; + enable_irq_wake(otg->irq); + + if (!atomic_read(&ui->softconnect)) + break; + msm72k_pullup_internal(&ui->gadget, 1); + + if (!ui->gadget.is_a_peripheral) + schedule_delayed_work( + &ui->chg_det, + USB_CHG_DET_DELAY); + } + break; + } + } +} + +/* FIXME - the callers of this function should use a gadget API instead. + * This is called from htc_battery.c and board-halibut.c + * WARNING - this can get called before this driver is initialized. + */ +void msm_hsusb_set_vbus_state(int online) +{ + unsigned long flags; + struct usb_info *ui = the_usb_info; + + if (!ui) { + pr_err("%s called before driver initialized\n", __func__); + return; + } + + spin_lock_irqsave(&ui->lock, flags); + + if (is_usb_online(ui) == online) + goto out; + + if (online) { + ui->usb_state = USB_STATE_POWERED; + ui->flags |= USB_FLAG_VBUS_ONLINE; + } else { + ui->gadget.speed = USB_SPEED_UNKNOWN; + ui->usb_state = USB_STATE_NOTATTACHED; + ui->flags |= USB_FLAG_VBUS_OFFLINE; + } + if (in_interrupt()) { + schedule_work(&ui->work); + } else { + spin_unlock_irqrestore(&ui->lock, flags); + usb_do_work(&ui->work); + return; + } +out: + spin_unlock_irqrestore(&ui->lock, flags); +} + +#if defined(CONFIG_DEBUG_FS) + +void usb_function_reenumerate(void) +{ + struct usb_info *ui = the_usb_info; + + /* disable and re-enable the D+ pullup */ + dev_dbg(&ui->pdev->dev, "disable pullup\n"); + writel(readl(USB_USBCMD) & ~USBCMD_RS, USB_USBCMD); + + msleep(10); + + dev_dbg(&ui->pdev->dev, "enable pullup\n"); + writel(readl(USB_USBCMD) | USBCMD_RS, USB_USBCMD); +} + +static char debug_buffer[PAGE_SIZE]; + +static ssize_t debug_read_status(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct usb_info *ui = file->private_data; + char *buf = debug_buffer; + unsigned long flags; + struct msm_endpoint *ept; + struct msm_request *req; + int n; + int i = 0; + + spin_lock_irqsave(&ui->lock, flags); + + i += scnprintf(buf + i, PAGE_SIZE - i, + "regs: setup=%08x prime=%08x stat=%08x done=%08x\n", + readl(USB_ENDPTSETUPSTAT), + readl(USB_ENDPTPRIME), + readl(USB_ENDPTSTAT), + readl(USB_ENDPTCOMPLETE)); + i += scnprintf(buf + i, PAGE_SIZE - i, + "regs: cmd=%08x sts=%08x intr=%08x port=%08x\n\n", + readl(USB_USBCMD), + readl(USB_USBSTS), + readl(USB_USBINTR), + readl(USB_PORTSC)); + + + for (n = 0; n < 32; n++) { + ept = ui->ept + n; + if (ept->ep.maxpacket == 0) + continue; + + i += scnprintf(buf + i, PAGE_SIZE - i, + "ept%d %s cfg=%08x active=%08x next=%08x info=%08x\n", + ept->num, (ept->flags & EPT_FLAG_IN) ? "in " : "out", + ept->head->config, ept->head->active, + ept->head->next, ept->head->info); + + for (req = ept->req; req; req = req->next) + i += scnprintf(buf + i, PAGE_SIZE - i, + " req @%08x next=%08x info=%08x page0=%08x %c %c\n", + req->item_dma, req->item->next, + req->item->info, req->item->page0, + req->busy ? 'B' : ' ', + req->live ? 'L' : ' '); + } + + i += scnprintf(buf + i, PAGE_SIZE - i, + "phy failure count: %d\n", ui->phy_fail_count); + + spin_unlock_irqrestore(&ui->lock, flags); + + return simple_read_from_buffer(ubuf, count, ppos, buf, i); +} + +static ssize_t debug_write_reset(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct usb_info *ui = file->private_data; + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + ui->flags |= USB_FLAG_RESET; + schedule_work(&ui->work); + spin_unlock_irqrestore(&ui->lock, flags); + + return count; +} + +static ssize_t debug_write_cycle(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + usb_function_reenumerate(); + return count; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +const struct file_operations debug_stat_ops = { + .open = debug_open, + .read = debug_read_status, +}; + +const struct file_operations debug_reset_ops = { + .open = debug_open, + .write = debug_write_reset, +}; + +const struct file_operations debug_cycle_ops = { + .open = debug_open, + .write = debug_write_cycle, +}; + +static ssize_t debug_read_release_wlocks(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char kbuf[10]; + size_t c = 0; + + memset(kbuf, 0, 10); + + c = scnprintf(kbuf, 10, "%d", release_wlocks); + + if (copy_to_user(ubuf, kbuf, c)) + return -EFAULT; + + return c; +} +static ssize_t debug_write_release_wlocks(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + char kbuf[10]; + long temp; + + memset(kbuf, 0, 10); + + if (copy_from_user(kbuf, buf, count > 10 ? 10 : count)) + return -EFAULT; + + if (strict_strtol(kbuf, 10, &temp)) + return -EINVAL; + + if (temp) + release_wlocks = 1; + else + release_wlocks = 0; + + return count; +} +static int debug_wake_lock_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +const struct file_operations debug_wlocks_ops = { + .open = debug_wake_lock_open, + .read = debug_read_release_wlocks, + .write = debug_write_release_wlocks, +}; +static void usb_debugfs_init(struct usb_info *ui) +{ + struct dentry *dent; + dent = debugfs_create_dir(dev_name(&ui->pdev->dev), 0); + if (IS_ERR(dent)) + return; + + debugfs_create_file("status", 0444, dent, ui, &debug_stat_ops); + debugfs_create_file("reset", 0222, dent, ui, &debug_reset_ops); + debugfs_create_file("cycle", 0222, dent, ui, &debug_cycle_ops); + debugfs_create_file("release_wlocks", 0666, dent, ui, + &debug_wlocks_ops); +} +#else +static void usb_debugfs_init(struct usb_info *ui) {} +#endif + +static int +msm72k_enable(struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc) +{ + struct msm_endpoint *ept = to_msm_endpoint(_ep); + unsigned char ep_type = + desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + _ep->maxpacket = le16_to_cpu(desc->wMaxPacketSize); + config_ept(ept); + ept->wedged = 0; + usb_ept_enable(ept, 1, ep_type); + return 0; +} + +static int msm72k_disable(struct usb_ep *_ep) +{ + struct msm_endpoint *ept = to_msm_endpoint(_ep); + + usb_ept_enable(ept, 0, 0); + flush_endpoint(ept); + return 0; +} + +static struct usb_request * +msm72k_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + return usb_ept_alloc_req(to_msm_endpoint(_ep), 0, gfp_flags); +} + +static void +msm72k_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct msm_request *req = to_msm_request(_req); + struct msm_endpoint *ept = to_msm_endpoint(_ep); + struct usb_info *ui = ept->ui; + + /* request should not be busy */ + BUG_ON(req->busy); + if (req->alloced) + kfree(req->req.buf); + dma_pool_free(ui->pool, req->item, req->item_dma); + kfree(req); +} + +static int +msm72k_queue(struct usb_ep *_ep, struct usb_request *req, gfp_t gfp_flags) +{ + struct msm_endpoint *ep = to_msm_endpoint(_ep); + struct usb_info *ui = ep->ui; + + if (ep == &ui->ep0in) { + struct msm_request *r = to_msm_request(req); + if (!req->length) + goto ep_queue_done; + r->gadget_complete = req->complete; + /* ep0_queue_ack_complete queue a receive for ACK before + ** calling req->complete + */ + req->complete = ep0_queue_ack_complete; + if (atomic_read(&ui->ep0_dir) == USB_DIR_OUT) + ep = &ui->ep0out; + goto ep_queue_done; + } + +ep_queue_done: + return usb_ept_queue_xfer(ep, req); +} + +static int msm72k_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct msm_endpoint *ep = to_msm_endpoint(_ep); + struct msm_request *req = to_msm_request(_req); + struct usb_info *ui = ep->ui; + + struct msm_request *temp_req; + unsigned long flags; + + if (!(ui && req && ep->req)) + return -EINVAL; + + spin_lock_irqsave(&ui->lock, flags); + if (!req->busy) { + dev_dbg(&ui->pdev->dev, "%s: !req->busy\n", __func__); + spin_unlock_irqrestore(&ui->lock, flags); + return -EINVAL; + } + /* Stop the transfer */ + do { + writel((1 << ep->bit), USB_ENDPTFLUSH); + while (readl(USB_ENDPTFLUSH) & (1 << ep->bit)) + udelay(100); + } while (readl(USB_ENDPTSTAT) & (1 << ep->bit)); + + req->req.status = 0; + req->busy = 0; + + if (ep->req == req) { + ep->req = req->next; + ep->head->next = req->item->next; + } else { + req->prev->next = req->next; + if (req->next) + req->next->prev = req->prev; + req->prev->item->next = req->item->next; + } + + if (!req->next) + ep->last = req->prev; + + /* initialize request to default */ + req->item->next = TERMINATE; + req->item->info = 0; + req->live = 0; + dma_unmap_single(NULL, req->dma, req->req.length, + (ep->flags & EPT_FLAG_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + + if (req->req.complete) { + req->req.status = -ECONNRESET; + spin_unlock_irqrestore(&ui->lock, flags); + req->req.complete(&ep->ep, &req->req); + spin_lock_irqsave(&ui->lock, flags); + } + + if (!req->live) { + /* Reprime the endpoint for the remaining transfers */ + for (temp_req = ep->req ; temp_req ; temp_req = temp_req->next) + temp_req->live = 0; + if (ep->req) + usb_ept_start(ep); + spin_unlock_irqrestore(&ui->lock, flags); + return 0; + } + spin_unlock_irqrestore(&ui->lock, flags); + return 0; +} + +static int +usb_ept_set_halt(struct usb_ep *_ep, int value) +{ + struct msm_endpoint *ept = to_msm_endpoint(_ep); + struct usb_info *ui = ept->ui; + unsigned int in = ept->flags & EPT_FLAG_IN; + unsigned int n; + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + + n = readl(USB_ENDPTCTRL(ept->num)); + + if (in) { + if (value) + n |= CTRL_TXS; + else { + n &= ~CTRL_TXS; + n |= CTRL_TXR; + } + } else { + if (value) + n |= CTRL_RXS; + else { + n &= ~CTRL_RXS; + n |= CTRL_RXR; + } + } + writel(n, USB_ENDPTCTRL(ept->num)); + if (!value) + ept->wedged = 0; + spin_unlock_irqrestore(&ui->lock, flags); + + return 0; +} + +static int +msm72k_set_halt(struct usb_ep *_ep, int value) +{ + struct msm_endpoint *ept = to_msm_endpoint(_ep); + unsigned int in = ept->flags & EPT_FLAG_IN; + + if (value && in && ept->req) + return -EAGAIN; + + usb_ept_set_halt(_ep, value); + + return 0; +} + +static int +msm72k_fifo_status(struct usb_ep *_ep) +{ + return -EOPNOTSUPP; +} + +static void +msm72k_fifo_flush(struct usb_ep *_ep) +{ + flush_endpoint(to_msm_endpoint(_ep)); +} +static int msm72k_set_wedge(struct usb_ep *_ep) +{ + struct msm_endpoint *ept = to_msm_endpoint(_ep); + + if (ept->num == 0) + return -EINVAL; + + ept->wedged = 1; + + return msm72k_set_halt(_ep, 1); +} + +static const struct usb_ep_ops msm72k_ep_ops = { + .enable = msm72k_enable, + .disable = msm72k_disable, + + .alloc_request = msm72k_alloc_request, + .free_request = msm72k_free_request, + + .queue = msm72k_queue, + .dequeue = msm72k_dequeue, + + .set_halt = msm72k_set_halt, + .set_wedge = msm72k_set_wedge, + .fifo_status = msm72k_fifo_status, + .fifo_flush = msm72k_fifo_flush, +}; + +static int msm72k_get_frame(struct usb_gadget *_gadget) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + + /* frame number is in bits 13:3 */ + return (readl(USB_FRINDEX) >> 3) & 0x000007FF; +} + +/* VBUS reporting logically comes from a transceiver */ +static int msm72k_udc_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + struct msm_otg *otg = to_msm_otg(ui->xceiv); + + if (is_active || atomic_read(&otg->chg_type) + == USB_CHG_TYPE__WALLCHARGER) + wake_lock(&ui->wlock); + + msm_hsusb_set_vbus_state(is_active); + return 0; +} + +/* SW workarounds +Issue #1 - USB Spoof Disconnect Failure +Symptom - Writing 0 to run/stop bit of USBCMD doesn't cause disconnect +SW workaround - Making opmode non-driving and SuspendM set in function + register of SMSC phy +*/ +/* drivers may have software control over D+ pullup */ +static int msm72k_pullup_internal(struct usb_gadget *_gadget, int is_active) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + unsigned long flags; + + if (is_active) { + spin_lock_irqsave(&ui->lock, flags); + if (is_usb_online(ui) && ui->driver) + writel(readl(USB_USBCMD) | USBCMD_RS, USB_USBCMD); + spin_unlock_irqrestore(&ui->lock, flags); + } else { + writel(readl(USB_USBCMD) & ~USBCMD_RS, USB_USBCMD); + /* S/W workaround, Issue#1 */ + otg_io_write(ui->xceiv, 0x48, 0x04); + } + + /* Ensure pull-up operation is completed before returning */ + mb(); + + return 0; +} + +static int msm72k_pullup(struct usb_gadget *_gadget, int is_active) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + unsigned long flags; + + + atomic_set(&ui->softconnect, is_active); + + spin_lock_irqsave(&ui->lock, flags); + if (ui->usb_state == USB_STATE_NOTATTACHED || ui->driver == NULL) { + spin_unlock_irqrestore(&ui->lock, flags); + return 0; + } + spin_unlock_irqrestore(&ui->lock, flags); + + msm72k_pullup_internal(_gadget, is_active); + + if (is_active && !ui->gadget.is_a_peripheral) + schedule_delayed_work(&ui->chg_det, USB_CHG_DET_DELAY); + + return 0; +} + +static int msm72k_wakeup(struct usb_gadget *_gadget) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + struct msm_otg *otg = to_msm_otg(ui->xceiv); + + if (!atomic_read(&ui->remote_wakeup)) { + dev_err(&ui->pdev->dev, + "%s: remote wakeup not supported\n", __func__); + return -ENOTSUPP; + } + + if (!atomic_read(&ui->configured)) { + dev_err(&ui->pdev->dev, + "%s: device is not configured\n", __func__); + return -ENODEV; + } + otg_set_suspend(ui->xceiv, 0); + + disable_irq(otg->irq); + + if (!is_usb_active()) + writel(readl(USB_PORTSC) | PORTSC_FPR, USB_PORTSC); + + /* Ensure that USB port is resumed before enabling the IRQ */ + mb(); + + enable_irq(otg->irq); + + return 0; +} + +/* when Gadget is configured, it will indicate how much power + * can be pulled from vbus, as specified in configuiration descriptor + */ +static int msm72k_udc_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + unsigned long flags; + + + spin_lock_irqsave(&ui->lock, flags); + ui->b_max_pow = mA; + ui->flags = USB_FLAG_CONFIGURED; + spin_unlock_irqrestore(&ui->lock, flags); + + schedule_work(&ui->work); + + return 0; +} + +static int msm72k_set_selfpowered(struct usb_gadget *_gadget, int set) +{ + struct usb_info *ui = container_of(_gadget, struct usb_info, gadget); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ui->lock, flags); + if (set) { + if (ui->pdata && ui->pdata->self_powered) + atomic_set(&ui->self_powered, 1); + else + ret = -EOPNOTSUPP; + } else { + /* We can always work as a bus powered device */ + atomic_set(&ui->self_powered, 0); + } + spin_unlock_irqrestore(&ui->lock, flags); + + return ret; + +} + +static const struct usb_gadget_ops msm72k_ops = { + .get_frame = msm72k_get_frame, + .vbus_session = msm72k_udc_vbus_session, + .vbus_draw = msm72k_udc_vbus_draw, + .pullup = msm72k_pullup, + .wakeup = msm72k_wakeup, + .set_selfpowered = msm72k_set_selfpowered, +}; + +static void usb_do_remote_wakeup(struct work_struct *w) +{ + struct usb_info *ui = the_usb_info; + + msm72k_wakeup(&ui->gadget); +} + +static ssize_t usb_remote_wakeup(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_info *ui = the_usb_info; + + msm72k_wakeup(&ui->gadget); + + return count; +} + +static ssize_t show_usb_state(struct device *dev, struct device_attribute *attr, + char *buf) +{ + size_t i; + char *state[] = {"USB_STATE_NOTATTACHED", "USB_STATE_ATTACHED", + "USB_STATE_POWERED", "USB_STATE_UNAUTHENTICATED", + "USB_STATE_RECONNECTING", "USB_STATE_DEFAULT", + "USB_STATE_ADDRESS", "USB_STATE_CONFIGURED", + "USB_STATE_SUSPENDED" + }; + + i = scnprintf(buf, PAGE_SIZE, "%s\n", state[msm_hsusb_get_state()]); + return i; +} + +static ssize_t show_usb_speed(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_info *ui = the_usb_info; + size_t i; + char *speed[] = {"USB_SPEED_UNKNOWN", "USB_SPEED_LOW", + "USB_SPEED_FULL", "USB_SPEED_HIGH"}; + + i = scnprintf(buf, PAGE_SIZE, "%s\n", speed[ui->gadget.speed]); + return i; +} + +static ssize_t store_usb_chg_current(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_info *ui = the_usb_info; + unsigned long mA; + + if (ui->gadget.is_a_peripheral) + return -EINVAL; + + if (strict_strtoul(buf, 10, &mA)) + return -EINVAL; + + ui->chg_current = mA; + otg_set_power(ui->xceiv, mA); + + return count; +} + +static ssize_t show_usb_chg_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_info *ui = the_usb_info; + size_t count; + + count = sprintf(buf, "%d", ui->chg_current); + + return count; +} + +static ssize_t show_usb_chg_type(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_info *ui = the_usb_info; + struct msm_otg *otg = to_msm_otg(ui->xceiv); + size_t count; + char *chg_type[] = {"STD DOWNSTREAM PORT", + "CARKIT", + "DEDICATED CHARGER", + "INVALID"}; + + count = sprintf(buf, "%s", + chg_type[atomic_read(&otg->chg_type)]); + + return count; +} +static DEVICE_ATTR(wakeup, S_IWUSR, 0, usb_remote_wakeup); +static DEVICE_ATTR(usb_state, S_IRUSR, show_usb_state, 0); +static DEVICE_ATTR(usb_speed, S_IRUSR, show_usb_speed, 0); +static DEVICE_ATTR(chg_type, S_IRUSR, show_usb_chg_type, 0); +static DEVICE_ATTR(chg_current, S_IWUSR | S_IRUSR, + show_usb_chg_current, store_usb_chg_current); + +#ifdef CONFIG_USB_OTG +static ssize_t store_host_req(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_info *ui = the_usb_info; + unsigned long val, flags; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + dev_dbg(&ui->pdev->dev, "%s host request\n", + val ? "set" : "clear"); + + spin_lock_irqsave(&ui->lock, flags); + if (ui->hnp_avail) + ui->gadget.host_request = !!val; + spin_unlock_irqrestore(&ui->lock, flags); + + return count; +} +static DEVICE_ATTR(host_request, S_IWUSR, NULL, store_host_req); + +/* How do we notify user space about HNP availability? + * As we are compliant to Rev 2.0, Host will not set a_hnp_support. + * Introduce hnp_avail flag and set when HNP polling request arrives. + * The expectation is that user space checks hnp availability before + * requesting host role via above sysfs node. + */ +static ssize_t show_host_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_info *ui = the_usb_info; + size_t count; + unsigned long flags; + + spin_lock_irqsave(&ui->lock, flags); + count = sprintf(buf, "%d\n", ui->hnp_avail); + spin_unlock_irqrestore(&ui->lock, flags); + + return count; +} +static DEVICE_ATTR(host_avail, S_IRUSR, show_host_avail, NULL); + +static struct attribute *otg_attrs[] = { + &dev_attr_host_request.attr, + &dev_attr_host_avail.attr, + NULL, +}; + +static struct attribute_group otg_attr_grp = { + .name = "otg", + .attrs = otg_attrs, +}; +#endif + +static int msm72k_probe(struct platform_device *pdev) +{ + struct usb_info *ui; + struct msm_otg *otg; + int retval; + + dev_dbg(&pdev->dev, "msm72k_probe\n"); + ui = kzalloc(sizeof(struct usb_info), GFP_KERNEL); + if (!ui) + return -ENOMEM; + + ui->pdev = pdev; + ui->pdata = pdev->dev.platform_data; + + ui->buf = dma_alloc_coherent(&pdev->dev, 4096, &ui->dma, GFP_KERNEL); + if (!ui->buf) + return usb_free(ui, -ENOMEM); + + ui->pool = dma_pool_create("msm72k_udc", NULL, 32, 32, 0); + if (!ui->pool) + return usb_free(ui, -ENOMEM); + + ui->xceiv = otg_get_transceiver(); + if (!ui->xceiv) + return usb_free(ui, -ENODEV); + + otg = to_msm_otg(ui->xceiv); + ui->addr = otg->regs; + + ui->gadget.ops = &msm72k_ops; + ui->gadget.is_dualspeed = 1; + device_initialize(&ui->gadget.dev); + dev_set_name(&ui->gadget.dev, "gadget"); + ui->gadget.dev.parent = &pdev->dev; + ui->gadget.dev.dma_mask = pdev->dev.dma_mask; + +#ifdef CONFIG_USB_OTG + ui->gadget.is_otg = 1; +#endif + + ui->sdev.name = DRIVER_NAME; + ui->sdev.print_name = print_switch_name; + ui->sdev.print_state = print_switch_state; + + retval = switch_dev_register(&ui->sdev); + if (retval) + return usb_free(ui, retval); + + the_usb_info = ui; + + wake_lock_init(&ui->wlock, + WAKE_LOCK_SUSPEND, "usb_bus_active"); + + usb_debugfs_init(ui); + + usb_prepare(ui); + +#ifdef CONFIG_USB_OTG + retval = sysfs_create_group(&pdev->dev.kobj, &otg_attr_grp); + if (retval) { + dev_err(&ui->pdev->dev, + "failed to create otg sysfs directory:" + "err:(%d)\n", retval); + } +#endif + + retval = otg_set_peripheral(ui->xceiv, &ui->gadget); + if (retval) { + dev_err(&ui->pdev->dev, + "%s: Cannot bind the transceiver, retval:(%d)\n", + __func__, retval); + switch_dev_unregister(&ui->sdev); + wake_lock_destroy(&ui->wlock); + return usb_free(ui, retval); + } + + pm_runtime_enable(&pdev->dev); + + /* Setup phy stuck timer */ + if (ui->pdata && ui->pdata->is_phy_status_timer_on) + setup_timer(&phy_status_timer, usb_phy_status_check_timer, 0); + return 0; +} + +int usb_gadget_probe_driver(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)) +{ + struct usb_info *ui = the_usb_info; + int retval, n; + + if (!driver + || driver->speed < USB_SPEED_FULL + || !bind + || !driver->disconnect + || !driver->setup) + return -EINVAL; + if (!ui) + return -ENODEV; + if (ui->driver) + return -EBUSY; + + /* first hook up the driver ... */ + ui->driver = driver; + ui->gadget.dev.driver = &driver->driver; + ui->gadget.name = driver_name; + INIT_LIST_HEAD(&ui->gadget.ep_list); + ui->gadget.ep0 = &ui->ep0in.ep; + INIT_LIST_HEAD(&ui->gadget.ep0->ep_list); + ui->gadget.speed = USB_SPEED_UNKNOWN; + atomic_set(&ui->softconnect, 1); + + for (n = 1; n < 16; n++) { + struct msm_endpoint *ept = ui->ept + n; + list_add_tail(&ept->ep.ep_list, &ui->gadget.ep_list); + ept->ep.maxpacket = 512; + } + for (n = 17; n < 32; n++) { + struct msm_endpoint *ept = ui->ept + n; + list_add_tail(&ept->ep.ep_list, &ui->gadget.ep_list); + ept->ep.maxpacket = 512; + } + + retval = device_add(&ui->gadget.dev); + if (retval) + goto fail; + + retval = bind(&ui->gadget); + if (retval) { + dev_err(&ui->pdev->dev, "bind to driver %s --> error %d\n", + driver->driver.name, retval); + device_del(&ui->gadget.dev); + goto fail; + } + + retval = device_create_file(&ui->gadget.dev, &dev_attr_wakeup); + if (retval != 0) + dev_err(&ui->pdev->dev, "failed to create sysfs entry:" + "(wakeup) error: (%d)\n", retval); + retval = device_create_file(&ui->gadget.dev, &dev_attr_usb_state); + if (retval != 0) + dev_err(&ui->pdev->dev, "failed to create sysfs entry:" + " (usb_state) error: (%d)\n", retval); + + retval = device_create_file(&ui->gadget.dev, &dev_attr_usb_speed); + if (retval != 0) + dev_err(&ui->pdev->dev, "failed to create sysfs entry:" + " (usb_speed) error: (%d)\n", retval); + + retval = device_create_file(&ui->gadget.dev, &dev_attr_chg_type); + if (retval != 0) + dev_err(&ui->pdev->dev, + "failed to create sysfs entry(chg_type): err:(%d)\n", + retval); + retval = device_create_file(&ui->gadget.dev, &dev_attr_chg_current); + if (retval != 0) + dev_err(&ui->pdev->dev, + "failed to create sysfs entry(chg_current):" + "err:(%d)\n", retval); + + dev_dbg(&ui->pdev->dev, "registered gadget driver '%s'\n", + driver->driver.name); + usb_start(ui); + + return 0; + +fail: + ui->driver = NULL; + ui->gadget.dev.driver = NULL; + return retval; +} +EXPORT_SYMBOL(usb_gadget_probe_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct usb_info *dev = the_usb_info; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver || !driver->unbind) + return -EINVAL; + + msm72k_pullup_internal(&dev->gadget, 0); + if (dev->irq) { + free_irq(dev->irq, dev); + dev->irq = 0; + } + dev->state = USB_STATE_IDLE; + atomic_set(&dev->configured, 0); + switch_set_state(&dev->sdev, 0); + /* cancel pending ep0 transactions */ + flush_endpoint(&dev->ep0out); + flush_endpoint(&dev->ep0in); + + device_remove_file(&dev->gadget.dev, &dev_attr_wakeup); + device_remove_file(&dev->gadget.dev, &dev_attr_usb_state); + device_remove_file(&dev->gadget.dev, &dev_attr_usb_speed); + device_remove_file(&dev->gadget.dev, &dev_attr_chg_type); + device_remove_file(&dev->gadget.dev, &dev_attr_chg_current); + driver->disconnect(&dev->gadget); + driver->unbind(&dev->gadget); + dev->gadget.dev.driver = NULL; + dev->driver = NULL; + + device_del(&dev->gadget.dev); + + dev_dbg(&dev->pdev->dev, + "unregistered gadget driver '%s'\n", driver->driver.name); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + + +static int msm72k_udc_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int msm72k_udc_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static int msm72k_udc_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: idling...\n"); + return 0; +} + +static struct dev_pm_ops msm72k_udc_dev_pm_ops = { + .runtime_suspend = msm72k_udc_runtime_suspend, + .runtime_resume = msm72k_udc_runtime_resume, + .runtime_idle = msm72k_udc_runtime_idle +}; + +static struct platform_driver usb_driver = { + .probe = msm72k_probe, + .driver = { .name = "msm_hsusb", + .pm = &msm72k_udc_dev_pm_ops, }, +}; + +static int __init init(void) +{ + return platform_driver_register(&usb_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + platform_driver_unregister(&usb_driver); +} +module_exit(cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Mike Lockwood, Brian Swetland"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/qcom_maemo.c b/drivers/usb/gadget/qcom_maemo.c new file mode 100644 index 00000000000..39686c4e05c --- /dev/null +++ b/drivers/usb/gadget/qcom_maemo.c @@ -0,0 +1,304 @@ +/* + * Qualcomm Maemo Composite driver + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2009 Samsung Electronics + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * This program from the Code Aurora Forum is free software; you can + * redistribute it and/or modify it under the GNU General Public License + * version 2 and only version 2 as published by the Free Software Foundation. + * The original work available from [git.kernel.org ] is subject to the + * notice below. + * + * 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 +#include +#include +#include + + +#define DRIVER_DESC "Qcom Maemo Composite Gadget" +#define VENDOR_ID 0x05c6 +#define PRODUCT_ID 0x902E + +/* + * kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ + +#include "composite.c" +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +#define USB_ETH + +#define USB_ETH_RNDIS +#ifdef USB_ETH_RNDIS +# include "f_rndis.c" +# include "rndis.c" +#endif + + +#include "u_serial.c" +#include "f_serial.c" + +#include "u_ether.c" + +#undef DBG /* u_ether.c has broken idea about macros */ +#undef VDBG /* so clean up after it */ +#undef ERROR +#undef INFO + +#include "f_mass_storage.c" +#include "f_diag.c" +#include "f_rmnet.c" + +/*-------------------------------------------------------------------------*/ +/* string IDs are assigned dynamically */ + +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +/* String Table */ +static struct usb_string strings_dev[] = { + /* These dummy values should be overridden by platform data */ + [STRING_MANUFACTURER_IDX].s = "Qualcomm Incorporated", + [STRING_PRODUCT_IDX].s = "Usb composition", + [STRING_SERIAL_IDX].s = "0123456789ABCDEF", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof(device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .idVendor = __constant_cpu_to_le16(VENDOR_ID), + .idProduct = __constant_cpu_to_le16(PRODUCT_ID), + .bcdDevice = __constant_cpu_to_le16(0xffff), + .bNumConfigurations = 1, +}; + +static u8 hostaddr[ETH_ALEN]; +static struct usb_diag_ch *diag_ch; +static struct usb_diag_platform_data usb_diag_pdata = { + .ch_name = DIAG_LEGACY, +}; + +/****************************** Configurations ******************************/ +static struct fsg_module_parameters mod_data = { + .stall = 0 +}; +FSG_MODULE_PARAMETERS(/* no prefix */, mod_data); + +static struct fsg_common *fsg_common; +static int maemo_setup_config(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl); + +static int maemo_do_config(struct usb_configuration *c) +{ + int ret; + + ret = rndis_bind_config(c, hostaddr); + if (ret < 0) + return ret; + + ret = diag_function_add(c); + if (ret < 0) + return ret; + + ret = gser_bind_config(c, 0); + if (ret < 0) + return ret; + + ret = gser_bind_config(c, 1); + if (ret < 0) + return ret; + + ret = rmnet_function_add(c); + if (ret < 0) + return ret; + + ret = fsg_add(c->cdev, c, fsg_common); + if (ret < 0) + return ret; + + return 0; +} + +static struct usb_configuration maemo_config_driver = { + .label = "Qcom Maemo Gadget", + .bind = maemo_do_config, + .setup = maemo_setup_config, + .bConfigurationValue = 1, + .bMaxPower = 0xFA, +}; +static int maemo_setup_config(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl) +{ + int i; + int ret = -EOPNOTSUPP; + + for (i = 0; i < maemo_config_driver.next_interface_id; i++) { + if (maemo_config_driver.interface[i]->setup) { + ret = maemo_config_driver.interface[i]->setup( + maemo_config_driver.interface[i], ctrl); + if (ret >= 0) + return ret; + } + } + + return ret; +} + +static int maemo_bind(struct usb_composite_dev *cdev) +{ + struct usb_gadget *gadget = cdev->gadget; + int status, gcnum; + + /* set up diag channel */ + diag_ch = diag_setup(&usb_diag_pdata); + if (IS_ERR(diag_ch)) + return PTR_ERR(diag_ch); + + /* set up network link layer */ + status = gether_setup(cdev->gadget, hostaddr); + if (status < 0) + goto diag_clean; + + /* set up serial link layer */ + status = gserial_setup(cdev->gadget, 2); + if (status < 0) + goto fail0; + + /* set up mass storage function */ + fsg_common = fsg_common_from_params(0, cdev, &mod_data); + if (IS_ERR(fsg_common)) { + status = PTR_ERR(fsg_common); + goto fail1; + } + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0300 | gcnum); + else { + /* gadget zero is so simple (for now, no altsettings) that + * it SHOULD NOT have problems with bulk-capable hardware. + * so just warn about unrcognized controllers -- don't panic. + * + * things like configuration and altsetting numbering + * can need hardware-specific attention though. + */ + WARNING(cdev, "controller '%s' not recognized\n", + gadget->name); + device_desc.bcdDevice = __constant_cpu_to_le16(0x9999); + } + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + status = usb_string_id(cdev); + if (status < 0) + goto fail2; + strings_dev[STRING_MANUFACTURER_IDX].id = status; + device_desc.iManufacturer = status; + + status = usb_string_id(cdev); + if (status < 0) + goto fail2; + strings_dev[STRING_PRODUCT_IDX].id = status; + device_desc.iProduct = status; + + if (!usb_gadget_set_selfpowered(gadget)) + maemo_config_driver.bmAttributes |= USB_CONFIG_ATT_SELFPOWER; + + if (gadget->ops->wakeup) + maemo_config_driver.bmAttributes |= USB_CONFIG_ATT_WAKEUP; + + /* register our first configuration */ + status = usb_add_config(cdev, &maemo_config_driver); + if (status < 0) + goto fail2; + + usb_gadget_set_selfpowered(gadget); + dev_info(&gadget->dev, DRIVER_DESC "\n"); + fsg_common_put(fsg_common); + return 0; + +fail2: + fsg_common_put(fsg_common); +fail1: + gserial_cleanup(); +fail0: + gether_cleanup(); +diag_clean: + diag_cleanup(diag_ch); + + return status; +} + +static int __exit maemo_unbind(struct usb_composite_dev *cdev) +{ + gserial_cleanup(); + gether_cleanup(); + diag_cleanup(diag_ch); + return 0; +} + +static struct usb_composite_driver qcom_maemo_driver = { + .name = "Qcom Maemo Gadget", + .dev = &device_desc, + .strings = dev_strings, + .bind = maemo_bind, + .unbind = __exit_p(maemo_unbind), +}; + +static int __init qcom_maemo_usb_init(void) +{ + return usb_composite_register(&qcom_maemo_driver); +} +module_init(qcom_maemo_usb_init); + +static void __exit qcom_maemo_usb_cleanup(void) +{ + usb_composite_unregister(&qcom_maemo_driver); +} +module_exit(qcom_maemo_usb_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index a872248f37d..c3ccb724e1f 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -262,8 +262,13 @@ static struct fsg_lun *fsg_lun_from_dev(struct device *dev) #define EP0_BUFSIZE 256 #define DELAYED_STATUS (EP0_BUFSIZE + 999) /* An impossibly large value */ -/* Number of buffers we will use. 2 is enough for double-buffering */ -#define FSG_NUM_BUFFERS 2 +/* Number of buffers for CBW, DATA and CSW */ +#ifdef CONFIG_USB_CSW_HACK +#define FSG_NUM_BUFFERS 4 +#else +#define FSG_NUM_BUFFERS 2 +#endif + /* Default size of buffer length. */ #define FSG_BUFLEN ((u32)16384) diff --git a/drivers/usb/gadget/u_bam.c b/drivers/usb/gadget/u_bam.c new file mode 100644 index 00000000000..a047cfceee4 --- /dev/null +++ b/drivers/usb/gadget/u_bam.c @@ -0,0 +1,812 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_rmnet.h" + +#define BAM_N_PORTS 1 + +static struct workqueue_struct *gbam_wq; +static int n_bam_ports; +static unsigned bam_ch_ids[] = { 8 }; + +#define TX_PKT_DROP_THRESHOLD 1000 +#define RX_PKT_FLOW_CTRL_EN_THRESHOLD 1000 +#define RX_PKT_FLOW_CTRL_DISABLE 500 +#define RX_PKT_FLOW_CTRL_SUPPORT 1 + +#define BAM_MUX_HDR 8 + +#define RX_Q_SIZE 16 +#define TX_Q_SIZE 200 +#define RX_REQ_SIZE (2048 - BAM_MUX_HDR) + +unsigned int tx_pkt_drop_thld = TX_PKT_DROP_THRESHOLD; +module_param(tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int rx_fctrl_en_thld = RX_PKT_FLOW_CTRL_EN_THRESHOLD; +module_param(rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int rx_fctrl_support = RX_PKT_FLOW_CTRL_SUPPORT; +module_param(rx_fctrl_support, uint, S_IRUGO | S_IWUSR); + +unsigned int rx_fctrl_dis_thld = RX_PKT_FLOW_CTRL_DISABLE; +module_param(rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int tx_q_size = TX_Q_SIZE; +module_param(tx_q_size, uint, S_IRUGO | S_IWUSR); + +unsigned int rx_q_size = RX_Q_SIZE; +module_param(rx_q_size, uint, S_IRUGO | S_IWUSR); + +unsigned int rx_req_size = RX_REQ_SIZE; +module_param(rx_req_size, uint, S_IRUGO | S_IWUSR); + +struct bam_ch_info { + atomic_t opened; + unsigned id; + + struct list_head tx_idle; + struct sk_buff_head tx_skb_q; + + struct list_head rx_idle; + struct sk_buff_head rx_skb_q; + + struct gbam_port *port; + struct work_struct write_tobam_w; + + /* stats */ + unsigned int pending_with_bam; + unsigned int tohost_drp_cnt; + unsigned int tomodem_drp_cnt; + unsigned int tx_len; + unsigned int rx_len; + unsigned long to_modem; + unsigned long to_host; +}; + +struct gbam_port { + unsigned port_num; + spinlock_t port_lock; + + struct grmnet *port_usb; + + struct bam_ch_info data_ch; + + struct work_struct connect_w; +}; + +static struct bam_portmaster { + struct gbam_port *port; +} bam_ports[N_PORTS]; + +static void gbam_start_rx(struct gbam_port *port); + +/*---------------misc functions---------------- */ +static void gbam_free_requests(struct usb_ep *ep, struct list_head *head) +{ + struct usb_request *req; + + while (!list_empty(head)) { + req = list_entry(head->next, struct usb_request, list); + list_del(&req->list); + usb_ep_free_request(ep, req); + } +} + +static int gbam_alloc_requests(struct usb_ep *ep, struct list_head *head, + int num, + void (*cb)(struct usb_ep *ep, struct usb_request *), + gfp_t flags) +{ + int i; + struct usb_request *req; + + pr_debug("%s: ep:%p head:%p num:%d cb:%p", __func__, + ep, head, num, cb); + + for (i = 0; i < num; i++) { + req = usb_ep_alloc_request(ep, flags); + if (!req) { + pr_debug("%s: req allocated:%d\n", __func__, i); + return list_empty(head) ? -ENOMEM : 0; + } + req->complete = cb; + list_add(&req->list, head); + } + + return 0; +} +/*--------------------------------------------- */ + +/*------------data_path----------------------------*/ +static void gbam_write_data_tohost(struct gbam_port *port) +{ + unsigned long flags; + struct bam_ch_info *d = &port->data_ch; + struct sk_buff *skb; + int ret; + struct usb_request *req; + struct usb_ep *ep; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + ep = port->port_usb->in; + + while (!list_empty(&d->tx_idle)) { + skb = __skb_dequeue(&d->tx_skb_q); + if (!skb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + req = list_first_entry(&d->tx_idle, + struct usb_request, + list); + req->context = skb; + req->buf = skb->data; + req->length = skb->len; + + list_del(&req->list); + + spin_unlock(&port->port_lock); + ret = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock(&port->port_lock); + if (ret) { + pr_err("%s: usb epIn failed\n", __func__); + list_add(&req->list, &d->tx_idle); + dev_kfree_skb_any(skb); + break; + } + d->to_host++; + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +void gbam_data_recv_cb(void *p, struct sk_buff *skb) +{ + struct gbam_port *port = p; + struct bam_ch_info *d = &port->data_ch; + unsigned long flags; + + if (!skb) + return; + + pr_debug("%s: p:%p#%d d:%p skb_len:%d\n", __func__, + port, port->port_num, d, skb->len); + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + dev_kfree_skb_any(skb); + return; + } + + if (d->tx_skb_q.qlen > tx_pkt_drop_thld) { + d->tohost_drp_cnt++; + if (printk_ratelimit()) + pr_err("%s: tx pkt dropped: tx_drop_cnt:%u\n", + __func__, d->tohost_drp_cnt); + spin_unlock_irqrestore(&port->port_lock, flags); + dev_kfree_skb_any(skb); + return; + } + + __skb_queue_tail(&d->tx_skb_q, skb); + spin_unlock_irqrestore(&port->port_lock, flags); + + gbam_write_data_tohost(port); +} + +void gbam_data_write_done(void *p, struct sk_buff *skb) +{ + struct gbam_port *port = p; + struct bam_ch_info *d = &port->data_ch; + unsigned long flags; + + if (!skb) + return; + + dev_kfree_skb_any(skb); + + spin_lock_irqsave(&port->port_lock, flags); + + d->pending_with_bam--; + + pr_debug("%s: port:%p d:%p tom:%lu pbam:%u, pno:%d\n", __func__, + port, d, d->to_modem, + d->pending_with_bam, port->port_num); + + if (rx_fctrl_support && + d->pending_with_bam >= rx_fctrl_dis_thld) { + + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + spin_unlock_irqrestore(&port->port_lock, flags); + + gbam_start_rx(port); +} + +static void gbam_data_write_tobam(struct work_struct *w) +{ + struct gbam_port *port; + struct bam_ch_info *d; + struct sk_buff *skb; + unsigned long flags; + int ret; + + d = container_of(w, struct bam_ch_info, write_tobam_w); + port = d->port; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + while ((skb = __skb_dequeue(&d->rx_skb_q))) { + d->pending_with_bam++; + d->to_modem++; + + pr_debug("%s: port:%p d:%p tom:%lu pbam:%u pno:%d\n", __func__, + port, d, d->to_modem, d->pending_with_bam, + port->port_num); + + spin_unlock_irqrestore(&port->port_lock, flags); + ret = msm_bam_dmux_write(d->id, skb); + spin_lock_irqsave(&port->port_lock, flags); + if (ret) { + pr_debug("%s: write error:%d\n", __func__, ret); + d->pending_with_bam--; + d->to_modem--; + d->tomodem_drp_cnt++; + dev_kfree_skb_any(skb); + break; + } + } + spin_unlock_irqrestore(&port->port_lock, flags); +} +/*-------------------------------------------------------------*/ + +static void gbam_epin_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gbam_port *port = ep->driver_data; + struct bam_ch_info *d; + struct sk_buff *skb = req->context; + int status = req->status; + + switch (status) { + case 0: + /* successful completion */ + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + break; + default: + pr_err("%s: data tx ep error %d\n", + __func__, status); + break; + } + + dev_kfree_skb_any(skb); + + if (!port) + return; + + spin_lock(&port->port_lock); + d = &port->data_ch; + list_add_tail(&req->list, &d->tx_idle); + spin_unlock(&port->port_lock); + + gbam_write_data_tohost(port); +} + +static void +gbam_epout_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gbam_port *port = ep->driver_data; + struct bam_ch_info *d = &port->data_ch; + struct sk_buff *skb = req->context; + int status = req->status; + int queue = 0; + + switch (status) { + case 0: + skb_put(skb, req->actual); + queue = 1; + break; + case -ECONNRESET: + case -ESHUTDOWN: + /* cable disconnection */ + dev_kfree_skb_any(skb); + req->buf = 0; + usb_ep_free_request(ep, req); + return; + default: + if (printk_ratelimit()) + pr_err("%s: %s response error %d, %d/%d\n", + __func__, ep->name, status, + req->actual, req->length); + dev_kfree_skb_any(skb); + break; + } + + spin_lock(&port->port_lock); + if (queue) { + __skb_queue_tail(&d->rx_skb_q, skb); + queue_work(gbam_wq, &d->write_tobam_w); + } + + /* TODO: Handle flow control gracefully by having + * having call back mechanism from bam driver + */ + if (rx_fctrl_support && + d->pending_with_bam >= rx_fctrl_en_thld) { + + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock); + return; + } + spin_unlock(&port->port_lock); + + skb = alloc_skb(rx_req_size + BAM_MUX_HDR, GFP_ATOMIC); + if (!skb) { + spin_lock(&port->port_lock); + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock); + return; + } + skb_reserve(skb, BAM_MUX_HDR); + + req->buf = skb->data; + req->length = rx_req_size; + req->context = skb; + + status = usb_ep_queue(ep, req, GFP_ATOMIC); + if (status) { + dev_kfree_skb_any(skb); + + if (printk_ratelimit()) + pr_err("%s: data rx enqueue err %d\n", + __func__, status); + + spin_lock(&port->port_lock); + list_add_tail(&req->list, &d->rx_idle); + spin_unlock(&port->port_lock); + } +} + +static void gbam_start_rx(struct gbam_port *port) +{ + struct usb_request *req; + struct bam_ch_info *d; + struct usb_ep *ep; + unsigned long flags; + int ret; + struct sk_buff *skb; + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + d = &port->data_ch; + ep = port->port_usb->out; + + while (port->port_usb && !list_empty(&d->rx_idle)) { + req = list_first_entry(&d->rx_idle, struct usb_request, list); + + skb = alloc_skb(rx_req_size + BAM_MUX_HDR, GFP_ATOMIC); + if (!skb) + break; + skb_reserve(skb, BAM_MUX_HDR); + + list_del(&req->list); + req->buf = skb->data; + req->length = rx_req_size; + req->context = skb; + + spin_unlock_irqrestore(&port->port_lock, flags); + ret = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock_irqsave(&port->port_lock, flags); + if (ret) { + dev_kfree_skb_any(skb); + + if (printk_ratelimit()) + pr_err("%s: rx queue failed\n", __func__); + + if (port->port_usb) + list_add(&req->list, &d->rx_idle); + else + usb_ep_free_request(ep, req); + break; + } + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static void gbam_start_io(struct gbam_port *port) +{ + unsigned long flags; + struct usb_ep *ep; + int ret; + struct bam_ch_info *d; + + pr_debug("%s: port:%p\n", __func__, port); + + spin_lock_irqsave(&port->port_lock, flags); + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return; + } + + d = &port->data_ch; + ep = port->port_usb->out; + ret = gbam_alloc_requests(ep, &d->rx_idle, rx_q_size, + gbam_epout_complete, GFP_ATOMIC); + if (ret) { + pr_err("%s: rx req allocation failed\n", __func__); + return; + } + + ep = port->port_usb->in; + ret = gbam_alloc_requests(ep, &d->tx_idle, tx_q_size, + gbam_epin_complete, GFP_ATOMIC); + if (ret) { + pr_err("%s: tx req allocation failed\n", __func__); + gbam_free_requests(ep, &d->rx_idle); + return; + } + + spin_unlock_irqrestore(&port->port_lock, flags); + + /* queue out requests */ + gbam_start_rx(port); +} + +static void gbam_connect_work(struct work_struct *w) +{ + struct gbam_port *port = container_of(w, struct gbam_port, connect_w); + struct bam_ch_info *d = &port->data_ch; + int ret; + + ret = msm_bam_dmux_open(d->id, port, + gbam_data_recv_cb, + gbam_data_write_done); + if (ret) { + pr_err("%s: unable open bam ch:%d err:%d\n", + __func__, d->id, ret); + return; + } + atomic_set(&d->opened, 1); + + gbam_start_io(port); + + pr_debug("%s: done\n", __func__); +} + +static void gbam_port_free(int portno) +{ + struct gbam_port *port = bam_ports[portno].port; + + if (!port) + kfree(port); +} + +static int gbam_port_alloc(int portno) +{ + struct gbam_port *port; + struct bam_ch_info *d; + + port = kzalloc(sizeof(struct gbam_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->port_num = portno; + + /* port initialization */ + spin_lock_init(&port->port_lock); + INIT_WORK(&port->connect_w, gbam_connect_work); + + /* data ch */ + d = &port->data_ch; + d->port = port; + INIT_LIST_HEAD(&d->tx_idle); + INIT_LIST_HEAD(&d->rx_idle); + INIT_WORK(&d->write_tobam_w, gbam_data_write_tobam); + skb_queue_head_init(&d->tx_skb_q); + skb_queue_head_init(&d->rx_skb_q); + d->id = bam_ch_ids[portno]; + + bam_ports[portno].port = port; + + pr_debug("%s: port:%p portno:%d\n", __func__, port, portno); + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t gbam_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct gbam_port *port; + struct bam_ch_info *d; + char *buf; + unsigned long flags; + int ret; + int i; + int temp = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < n_bam_ports; i++) { + port = bam_ports[i].port; + if (!port) + continue; + spin_lock_irqsave(&port->port_lock, flags); + + d = &port->data_ch; + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "#PORT:%d port:%p data_ch:%p#\n" + "dpkts_to_usbhost: %lu\n" + "dpkts_to_modem: %lu\n" + "dpkts_pwith_bam: %u\n" + "to_usbhost_dcnt: %u\n" + "tomodem__dcnt: %u\n" + "tx_buf_len: %u\n" + "data_ch_opened: %d\n", + i, port, &port->data_ch, + d->to_host, d->to_modem, + d->pending_with_bam, + d->tohost_drp_cnt, d->tomodem_drp_cnt, + d->tx_skb_q.qlen, atomic_read(&d->opened)); + + spin_unlock_irqrestore(&port->port_lock, flags); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t gbam_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct gbam_port *port; + struct bam_ch_info *d; + int i; + unsigned long flags; + + for (i = 0; i < n_bam_ports; i++) { + port = bam_ports[i].port; + if (!port) + continue; + + spin_lock_irqsave(&port->port_lock, flags); + + d = &port->data_ch; + + d->to_host = 0; + d->to_modem = 0; + d->pending_with_bam = 0; + d->tohost_drp_cnt = 0; + d->tomodem_drp_cnt = 0; + + spin_unlock_irqrestore(&port->port_lock, flags); + } + return count; +} + +const struct file_operations gbam_stats_ops = { + .read = gbam_read_stats, + .write = gbam_reset_stats, +}; + +static void gbam_debugfs_init(void) +{ + struct dentry *dent; + struct dentry *dfile; + + dent = debugfs_create_dir("usb_rmnet", 0); + if (IS_ERR(dent)) + return; + + /* TODO: Implement cleanup function to remove created file */ + dfile = debugfs_create_file("status", 0444, dent, 0, &gbam_stats_ops); + if (!dfile || IS_ERR(dfile)) + debugfs_remove(dent); +} +#else +static void gam_debugfs_init(void) { } +#endif + +static void gbam_free_buffers(struct gbam_port *port) +{ + struct sk_buff *skb; + unsigned long flags; + struct bam_ch_info *d; + + spin_lock_irqsave(&port->port_lock, flags); + + if (!port || !port->port_usb) + goto free_buf_out; + + d = &port->data_ch; + + gbam_free_requests(port->port_usb->in, &d->tx_idle); + gbam_free_requests(port->port_usb->out, &d->rx_idle); + + while ((skb = __skb_dequeue(&d->tx_skb_q))) + dev_kfree_skb_any(skb); + + while ((skb = __skb_dequeue(&d->rx_skb_q))) + dev_kfree_skb_any(skb); + +free_buf_out: + spin_unlock_irqrestore(&port->port_lock, flags); +} + +void gbam_disconnect(struct grmnet *gr, u8 port_num) +{ + struct gbam_port *port; + unsigned long flags; + struct bam_ch_info *d; + + pr_debug("%s: grmnet:%p port#%d\n", __func__, gr, port_num); + + if (port_num >= n_bam_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return; + } + + if (!gr) { + pr_err("%s: grmnet port is null\n", __func__); + return; + } + + port = bam_ports[port_num].port; + d = &port->data_ch; + + gbam_free_buffers(port); + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + /* disable endpoints */ + usb_ep_disable(gr->out); + usb_ep_disable(gr->in); + + if (atomic_read(&d->opened)) + msm_bam_dmux_close(d->id); + + atomic_set(&d->opened, 0); +} + +int gbam_connect(struct grmnet *gr, u8 port_num) +{ + struct gbam_port *port; + struct bam_ch_info *d; + int ret; + unsigned long flags; + + pr_debug("%s: grmnet:%p port#%d\n", __func__, gr, port_num); + + if (port_num >= n_bam_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return -ENODEV; + } + + if (!gr) { + pr_err("%s: grmnet port is null\n", __func__); + return -ENODEV; + } + + port = bam_ports[port_num].port; + d = &port->data_ch; + + ret = usb_ep_enable(gr->in, gr->in_desc); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:IN ep:%p", + __func__, gr->in); + return ret; + } + gr->in->driver_data = port; + + ret = usb_ep_enable(gr->out, gr->out_desc); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p", + __func__, gr->out); + gr->in->driver_data = 0; + return ret; + } + gr->out->driver_data = port; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = gr; + + d->to_host = 0; + d->to_modem = 0; + d->pending_with_bam = 0; + d->tohost_drp_cnt = 0; + d->tomodem_drp_cnt = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + + queue_work(gbam_wq, &port->connect_w); + + return 0; +} + +int gbam_setup(unsigned int count) +{ + int i; + int ret; + + pr_debug("%s: requested ports:%d\n", __func__, count); + + if (!count || count > BAM_N_PORTS) { + pr_err("%s: Invalid num of ports count:%d\n", + __func__, count); + return -EINVAL; + } + + gbam_wq = alloc_workqueue("k_gbam", WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!gbam_wq) { + pr_err("%s: Unable to create workqueue gbam_wq\n", + __func__); + return -ENOMEM; + } + + for (i = 0; i < count; i++) { + ret = gbam_port_alloc(i); + if (ret) { + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_bam_ports; + } + n_bam_ports++; + } + + gbam_debugfs_init(); + + return 0; +free_bam_ports: + for (i = 0; i < n_bam_ports; i++) + gbam_port_free(i); + + destroy_workqueue(gbam_wq); + + return ret; +} diff --git a/drivers/usb/gadget/u_rmnet.h b/drivers/usb/gadget/u_rmnet.h new file mode 100644 index 00000000000..aeaddee3a59 --- /dev/null +++ b/drivers/usb/gadget/u_rmnet.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __U_RMNET_H +#define __U_RMNET_H + +#include +#include +#include +#include + +struct rmnet_ctrl_pkt { + void *buf; + int len; + struct list_head list; +}; + +struct grmnet { + struct usb_function func; + + struct usb_ep *in; + struct usb_ep *out; + struct usb_endpoint_descriptor *in_desc; + struct usb_endpoint_descriptor *out_desc; + + /* to usb host, aka laptop, windows pc etc. Will + * be filled by usb driver of rmnet functionality + */ + int (*send_cpkt_response)(struct grmnet *g, + struct rmnet_ctrl_pkt *pkt); + + /* to modem, and to be filled by driver implementing + * control function + */ + int (*send_cpkt_request)(struct grmnet *g, + u8 port_num, + struct rmnet_ctrl_pkt *pkt); + + void (*send_cbits_tomodem)(struct grmnet *g, + u8 port_num, + int cbits); +}; + +int gbam_setup(unsigned int count); +int gbam_connect(struct grmnet *, u8 port_num); +void gbam_disconnect(struct grmnet *, u8 port_num); + +int gsmd_ctrl_connect(struct grmnet *gr, int port_num); +void gsmd_ctrl_disconnect(struct grmnet *gr, u8 port_num); +int gsmd_ctrl_setup(unsigned int count); + +#endif /* __U_RMNET_H*/ diff --git a/drivers/usb/gadget/u_rmnet_ctrl_smd.c b/drivers/usb/gadget/u_rmnet_ctrl_smd.c new file mode 100644 index 00000000000..4449d9e91e1 --- /dev/null +++ b/drivers/usb/gadget/u_rmnet_ctrl_smd.c @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_rmnet.h" + +#define NR_PORTS 1 +static int n_ports; +static char *rmnet_ctrl_names[] = { "DATA40_CNTL" }; +static struct workqueue_struct *grmnet_ctrl_wq; + +#define SMD_CH_MAX_LEN 20 +#define CH_OPENED 0 +#define CH_READY 1 +struct smd_ch_info { + struct smd_channel *ch; + char *name; + unsigned long flags; + wait_queue_head_t wait; + unsigned dtr; + + struct list_head tx_q; + unsigned long tx_len; + + struct work_struct read_w; + struct work_struct write_w; + + struct rmnet_ctrl_port *port; + + int cbits_tomodem; + /* stats */ + unsigned long to_modem; + unsigned long to_host; +}; + +struct rmnet_ctrl_port { + struct smd_ch_info ctrl_ch; + unsigned int port_num; + struct grmnet *port_usb; + + spinlock_t port_lock; + struct work_struct connect_w; +}; + +static struct rmnet_ctrl_ports { + struct rmnet_ctrl_port *port; + struct platform_driver pdrv; +} ports[NR_PORTS]; + + +/*---------------misc functions---------------- */ + +static struct rmnet_ctrl_pkt *rmnet_alloc_ctrl_pkt(unsigned len, gfp_t flags) +{ + struct rmnet_ctrl_pkt *pkt; + + pkt = kzalloc(sizeof(struct rmnet_ctrl_pkt), flags); + if (!pkt) + return ERR_PTR(-ENOMEM); + + pkt->buf = kmalloc(len, flags); + if (!pkt->buf) { + kfree(pkt); + return ERR_PTR(-ENOMEM); + } + pkt->len = len; + + return pkt; +} + +static void rmnet_ctrl_pkt_free(struct rmnet_ctrl_pkt *pkt) +{ + kfree(pkt->buf); + kfree(pkt); +} + +/*--------------------------------------------- */ + +/*---------------control/smd channel functions---------------- */ + +static void grmnet_ctrl_smd_read_w(struct work_struct *w) +{ + struct smd_ch_info *c = container_of(w, struct smd_ch_info, read_w); + struct rmnet_ctrl_port *port = c->port; + int sz; + struct rmnet_ctrl_pkt *cpkt; + unsigned long flags; + + while (1) { + sz = smd_cur_packet_size(c->ch); + if (sz == 0) + break; + + if (smd_read_avail(c->ch) < sz) + break; + + cpkt = rmnet_alloc_ctrl_pkt(sz, GFP_KERNEL); + if (IS_ERR(cpkt)) { + pr_err("%s: unable to allocate rmnet control pkt\n", + __func__); + return; + } + cpkt->len = smd_read(c->ch, cpkt->buf, sz); + + /* send it to USB here */ + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb && port->port_usb->send_cpkt_response) { + port->port_usb->send_cpkt_response( + port->port_usb, + cpkt); + c->to_host++; + } + spin_unlock_irqrestore(&port->port_lock, flags); + } +} + +static void grmnet_ctrl_smd_write_w(struct work_struct *w) +{ + struct smd_ch_info *c = container_of(w, struct smd_ch_info, write_w); + struct rmnet_ctrl_port *port = c->port; + unsigned long flags; + struct rmnet_ctrl_pkt *cpkt; + int ret; + + spin_lock_irqsave(&port->port_lock, flags); + while (1) { + if (list_empty(&c->tx_q)) + break; + + cpkt = list_first_entry(&c->tx_q, struct rmnet_ctrl_pkt, list); + + if (smd_write_avail(c->ch) < cpkt->len) + break; + + list_del(&cpkt->list); + spin_unlock_irqrestore(&port->port_lock, flags); + ret = smd_write(c->ch, cpkt->buf, cpkt->len); + spin_lock_irqsave(&port->port_lock, flags); + if (ret != cpkt->len) { + pr_err("%s: smd_write failed err:%d\n", + __func__, ret); + rmnet_ctrl_pkt_free(cpkt); + break; + } + rmnet_ctrl_pkt_free(cpkt); + c->to_modem++; + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static int +grmnet_ctrl_smd_send_cpkt_tomodem(struct grmnet *gr, u8 portno, + struct rmnet_ctrl_pkt *cpkt) +{ + unsigned long flags; + struct rmnet_ctrl_port *port; + struct smd_ch_info *c; + + if (portno >= n_ports) { + pr_err("%s: Invalid portno#%d\n", __func__, portno); + return -ENODEV; + } + + if (!gr) { + pr_err("%s: grmnet is null\n", __func__); + return -ENODEV; + } + + port = ports[portno].port; + + spin_lock_irqsave(&port->port_lock, flags); + c = &port->ctrl_ch; + + /* drop cpkt if ch is not open */ + if (!test_bit(CH_OPENED, &c->flags)) { + rmnet_ctrl_pkt_free(cpkt); + spin_unlock_irqrestore(&port->port_lock, flags); + return 0; + } + + list_add_tail(&cpkt->list, &c->tx_q); + queue_work(grmnet_ctrl_wq, &c->write_w); + spin_unlock_irqrestore(&port->port_lock, flags); + + return 0; +} + +#define ACM_CTRL_DTR 0x01 +static void +gsmd_ctrl_send_cbits_tomodem(struct grmnet *gr, u8 portno, int cbits) +{ + struct rmnet_ctrl_port *port; + struct smd_ch_info *c; + int set_bits = 0; + int clear_bits = 0; + int temp = 0; + + if (portno >= n_ports) { + pr_err("%s: Invalid portno#%d\n", __func__, portno); + return; + } + + if (!gr) { + pr_err("%s: grmnet is null\n", __func__); + return; + } + + port = ports[portno].port; + cbits = cbits & ACM_CTRL_DTR; + c = &port->ctrl_ch; + + /* host driver will only send DTR, but to have generic + * set and clear bit implementation using two separate + * checks + */ + if (cbits & ACM_CTRL_DTR) + set_bits |= TIOCM_DTR; + else + clear_bits |= TIOCM_DTR; + + temp |= set_bits; + temp &= ~clear_bits; + + if (temp == c->cbits_tomodem) + return; + + c->cbits_tomodem = temp; + + if (!test_bit(CH_OPENED, &c->flags)) + return; + + pr_debug("%s: ctrl_tomodem:%d ctrl_bits:%d setbits:%d clearbits:%d\n", + __func__, temp, cbits, set_bits, clear_bits); + + smd_tiocmset(c->ch, set_bits, clear_bits); +} + +static char *get_smd_event(unsigned event) +{ + switch (event) { + case SMD_EVENT_DATA: + return "DATA"; + case SMD_EVENT_OPEN: + return "OPEN"; + case SMD_EVENT_CLOSE: + return "CLOSE"; + } + + return "UNDEFINED"; +} + +static void grmnet_ctrl_smd_notify(void *p, unsigned event) +{ + struct rmnet_ctrl_port *port = p; + struct smd_ch_info *c = &port->ctrl_ch; + + pr_debug("%s: EVENT_(%s)\n", __func__, get_smd_event(event)); + + switch (event) { + case SMD_EVENT_DATA: + if (smd_read_avail(c->ch)) + queue_work(grmnet_ctrl_wq, &c->read_w); + if (smd_write_avail(c->ch)) + queue_work(grmnet_ctrl_wq, &c->write_w); + break; + case SMD_EVENT_OPEN: + set_bit(CH_OPENED, &c->flags); + wake_up(&c->wait); + break; + case SMD_EVENT_CLOSE: + clear_bit(CH_OPENED, &c->flags); + break; + } +} +/*------------------------------------------------------------ */ + +static void grmnet_ctrl_smd_connect_w(struct work_struct *w) +{ + struct rmnet_ctrl_port *port = + container_of(w, struct rmnet_ctrl_port, connect_w); + struct smd_ch_info *c = &port->ctrl_ch; + unsigned long flags; + int ret; + + pr_debug("%s:\n", __func__); + + if (!test_bit(CH_READY, &c->flags)) + return; + + ret = smd_open(c->name, &c->ch, port, grmnet_ctrl_smd_notify); + if (ret) { + pr_err("%s: Unable to open smd ch:%s err:%d\n", + __func__, c->name, ret); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + smd_tiocmset(c->ch, c->cbits_tomodem, ~c->cbits_tomodem); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +int gsmd_ctrl_connect(struct grmnet *gr, int port_num) +{ + struct rmnet_ctrl_port *port; + struct smd_ch_info *c; + unsigned long flags; + + pr_debug("%s: grmnet:%p port#%d\n", __func__, gr, port_num); + + if (port_num >= n_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return -ENODEV; + } + + if (!gr) { + pr_err("%s: grmnet port is null\n", __func__); + return -ENODEV; + } + + port = ports[port_num].port; + c = &port->ctrl_ch; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = gr; + gr->send_cpkt_request = grmnet_ctrl_smd_send_cpkt_tomodem; + gr->send_cbits_tomodem = gsmd_ctrl_send_cbits_tomodem; + spin_unlock_irqrestore(&port->port_lock, flags); + + queue_work(grmnet_ctrl_wq, &port->connect_w); + + return 0; +} + +void gsmd_ctrl_disconnect(struct grmnet *gr, u8 port_num) +{ + struct rmnet_ctrl_port *port; + unsigned long flags; + struct smd_ch_info *c; + + pr_debug("%s: grmnet:%p port#%d\n", __func__, gr, port_num); + + if (port_num >= n_ports) { + pr_err("%s: invalid portno#%d\n", __func__, port_num); + return; + } + + if (!gr) { + pr_err("%s: grmnet port is null\n", __func__); + return; + } + + port = ports[port_num].port; + c = &port->ctrl_ch; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = 0; + gr->send_cpkt_request = 0; + gr->send_cbits_tomodem = 0; + c->cbits_tomodem = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + if (test_bit(CH_OPENED, &c->flags)) { + /* this should send the dtr zero */ + smd_close(c->ch); + clear_bit(CH_OPENED, &c->flags); + } +} + +#define SMD_CH_MAX_LEN 20 +static int grmnet_ctrl_smd_ch_probe(struct platform_device *pdev) +{ + struct rmnet_ctrl_port *port; + struct smd_ch_info *c; + int i; + unsigned long flags; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + for (i = 0; i < n_ports; i++) { + port = ports[i].port; + c = &port->ctrl_ch; + + if (!strncmp(c->name, pdev->name, SMD_CH_MAX_LEN)) { + set_bit(CH_READY, &c->flags); + + /* if usb is online, try opening smd_ch */ + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + queue_work(grmnet_ctrl_wq, &port->connect_w); + spin_unlock_irqrestore(&port->port_lock, flags); + + break; + } + } + + return 0; +} + +static int grmnet_ctrl_smd_ch_remove(struct platform_device *pdev) +{ + struct rmnet_ctrl_port *port; + struct smd_ch_info *c; + int i; + + pr_debug("%s: name:%s\n", __func__, pdev->name); + + for (i = 0; i < n_ports; i++) { + port = ports[i].port; + c = &port->ctrl_ch; + + if (!strncmp(c->name, pdev->name, SMD_CH_MAX_LEN)) { + clear_bit(CH_READY, &c->flags); + clear_bit(CH_OPENED, &c->flags); + smd_close(c->ch); + break; + } + } + + return 0; +} + + +static void grmnet_ctrl_smd_port_free(int portno) +{ + struct rmnet_ctrl_port *port = ports[portno].port; + + if (!port) + kfree(port); +} + +static int grmnet_ctrl_smd_port_alloc(int portno) +{ + struct rmnet_ctrl_port *port; + struct smd_ch_info *c; + struct platform_driver *pdrv; + + port = kzalloc(sizeof(struct rmnet_ctrl_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->port_num = portno; + + spin_lock_init(&port->port_lock); + INIT_WORK(&port->connect_w, grmnet_ctrl_smd_connect_w); + + c = &port->ctrl_ch; + c->name = rmnet_ctrl_names[portno]; + c->port = port; + init_waitqueue_head(&c->wait); + INIT_LIST_HEAD(&c->tx_q); + INIT_WORK(&c->read_w, grmnet_ctrl_smd_read_w); + INIT_WORK(&c->write_w, grmnet_ctrl_smd_write_w); + + ports[portno].port = port; + + pdrv = &ports[portno].pdrv; + pdrv->probe = grmnet_ctrl_smd_ch_probe; + pdrv->remove = grmnet_ctrl_smd_ch_remove; + pdrv->driver.name = c->name; + pdrv->driver.owner = THIS_MODULE; + + platform_driver_register(pdrv); + + pr_debug("%s: port:%p portno:%d\n", __func__, port, portno); + + return 0; +} + +int gsmd_ctrl_setup(unsigned int count) +{ + int i; + int ret; + + pr_debug("%s: requested ports:%d\n", __func__, count); + + if (!count || count > NR_PORTS) { + pr_err("%s: Invalid num of ports count:%d\n", + __func__, count); + return -EINVAL; + } + + grmnet_ctrl_wq = alloc_workqueue("gsmd_ctrl", + WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!grmnet_ctrl_wq) { + pr_err("%s: Unable to create workqueue grmnet_ctrl\n", + __func__); + return -ENOMEM; + } + + for (i = 0; i < count; i++) { + ret = grmnet_ctrl_smd_port_alloc(i); + if (ret) { + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_ports; + } + n_ports++; + } + + return 0; + +free_ports: + for (i = 0; i < n_ports; i++) + grmnet_ctrl_smd_port_free(i); + + destroy_workqueue(grmnet_ctrl_wq); + + return ret; +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t gsmd_ctrl_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct rmnet_ctrl_port *port; + struct smd_ch_info *c; + char *buf; + unsigned long flags; + int ret; + int i; + int temp = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < n_ports; i++) { + port = ports[i].port; + if (!port) + continue; + spin_lock_irqsave(&port->port_lock, flags); + + c = &port->ctrl_ch; + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "#PORT:%d port:%p ctrl_ch:%p#\n" + "to_usbhost: %lu\n" + "to_modem: %lu\n" + "DTR: %s\n" + "ch_open: %d\n" + "ch_ready: %d\n" + "read_avail: %d\n" + "write_avail:%d\n", + i, port, &port->ctrl_ch, + c->to_host, c->to_modem, + c->cbits_tomodem ? "HIGH" : "LOW", + test_bit(CH_OPENED, &c->flags), + test_bit(CH_READY, &c->flags), + smd_read_avail(c->ch), + smd_write_avail(c->ch)); + + spin_unlock_irqrestore(&port->port_lock, flags); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t gsmd_ctrl_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct rmnet_ctrl_port *port; + struct smd_ch_info *c; + int i; + unsigned long flags; + + for (i = 0; i < n_ports; i++) { + port = ports[i].port; + if (!port) + continue; + + spin_lock_irqsave(&port->port_lock, flags); + + c = &port->ctrl_ch; + + c->to_host = 0; + c->to_modem = 0; + + spin_unlock_irqrestore(&port->port_lock, flags); + } + return count; +} + +const struct file_operations gsmd_ctrl_stats_ops = { + .read = gsmd_ctrl_read_stats, + .write = gsmd_ctrl_reset_stats, +}; + +struct dentry *smd_ctrl_dent; +struct dentry *smd_ctrl_dfile; +static void gsmd_ctrl_debugfs_init(void) +{ + smd_ctrl_dent = debugfs_create_dir("usb_rmnet_ctrl_smd", 0); + if (IS_ERR(smd_ctrl_dent)) + return; + + smd_ctrl_dfile = debugfs_create_file("status", 0444, smd_ctrl_dent, 0, + &gsmd_ctrl_stats_ops); + if (!smd_ctrl_dfile || IS_ERR(smd_ctrl_dfile)) + debugfs_remove(smd_ctrl_dent); +} + +static void gsmd_ctrl_debugfs_exit(void) +{ + debugfs_remove(smd_ctrl_dfile); + debugfs_remove(smd_ctrl_dent); +} + +#else +static void gsmd_ctrl_debugfs_init(void) { } +static void gsmd_ctrl_debugfs_exit(void) { } +#endif + +static int __init gsmd_ctrl_init(void) +{ + gsmd_ctrl_debugfs_init(); + + return 0; +} +module_init(gsmd_ctrl_init); + +static void __exit gsmd_ctrl_exit(void) +{ + gsmd_ctrl_debugfs_exit(); +} +module_exit(gsmd_ctrl_exit); +MODULE_DESCRIPTION("smd control driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/gadget/u_sdio.c b/drivers/usb/gadget/u_sdio.c new file mode 100644 index 00000000000..09d898f458e --- /dev/null +++ b/drivers/usb/gadget/u_sdio.c @@ -0,0 +1,1097 @@ +/* + * u_sdio.c - utilities for USB gadget serial over sdio + * + * This code also borrows from drivers/usb/gadget/u_serial.c, which is + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This program from the Code Aurora Forum is free software; you can + * redistribute it and/or modify it under the GNU General Public License + * version 2 and only version 2 as published by the Free Software Foundation. + * The original work available from [kernel.org] is subject to the notice below. + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "u_serial.h" + +#define SDIO_RX_QUEUE_SIZE 8 +#define SDIO_RX_BUF_SIZE 2048 + +#define SDIO_TX_QUEUE_SIZE 8 +#define SDIO_TX_BUF_SIZE 2048 + +/* 1 - DUN, 2-NMEA/GPS */ +#define SDIO_N_PORTS 2 +static struct sdio_portmaster { + struct mutex lock; + struct gsdio_port *port; + struct platform_driver gsdio_ch; +} sdio_ports[SDIO_N_PORTS]; +static unsigned n_sdio_ports; + +struct sdio_port_info { + /* data channel info */ + char *data_ch_name; + struct sdio_channel *ch; + + /* control channel info */ + int ctrl_ch_id; +}; + +struct sdio_port_info sport_info[SDIO_N_PORTS] = { + { + .data_ch_name = "SDIO_DUN", + .ctrl_ch_id = 9, + }, + { + .data_ch_name = "SDIO_NMEA", + .ctrl_ch_id = 10, + }, +}; + +static struct workqueue_struct *gsdio_wq; + +struct gsdio_port { + unsigned port_num; + spinlock_t port_lock; + + unsigned n_read; + struct list_head read_pool; + struct list_head read_queue; + struct work_struct push; + unsigned long rp_len; + unsigned long rq_len; + + struct list_head write_pool; + struct work_struct pull; + unsigned long wp_len; + + struct work_struct notify_modem; + + struct gserial *port_usb; + struct usb_cdc_line_coding line_coding; + + int sdio_open; + int ctrl_ch_err; + struct sdio_port_info *sport_info; + struct delayed_work sdio_open_work; + +#define SDIO_ACM_CTRL_RI (1 << 3) +#define SDIO_ACM_CTRL_DSR (1 << 1) +#define SDIO_ACM_CTRL_DCD (1 << 0) + int cbits_to_laptop; + +#define SDIO_ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ +#define SDIO_ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ + int cbits_to_modem; + + /* pkt logging */ + unsigned long nbytes_tolaptop; + unsigned long nbytes_tomodem; +}; + +void gsdio_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +struct usb_request * +gsdio_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, flags); + if (!req) { + pr_err("%s: usb alloc request failed\n", __func__); + return NULL; + } + + req->length = len; + req->buf = kmalloc(len, flags); + if (!req->buf) { + pr_err("%s: request buf allocation failed\n", __func__); + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +void gsdio_free_requests(struct usb_ep *ep, struct list_head *head) +{ + struct usb_request *req; + + while (!list_empty(head)) { + req = list_entry(head->next, struct usb_request, list); + list_del(&req->list); + gsdio_free_req(ep, req); + } +} + +int gsdio_alloc_requests(struct usb_ep *ep, struct list_head *head, + int num, int size, + void (*cb)(struct usb_ep *ep, struct usb_request *)) +{ + int i; + struct usb_request *req; + + pr_debug("%s: ep:%p head:%p num:%d size:%d cb:%p", __func__, + ep, head, num, size, cb); + + for (i = 0; i < num; i++) { + req = gsdio_alloc_req(ep, size, GFP_ATOMIC); + if (!req) { + pr_debug("%s: req allocated:%d\n", __func__, i); + return list_empty(head) ? -ENOMEM : 0; + } + req->complete = cb; + list_add(&req->list, head); + } + + return 0; +} + +void gsdio_start_rx(struct gsdio_port *port) +{ + struct list_head *pool; + struct usb_ep *out; + int ret; + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num); + + spin_lock_irq(&port->port_lock); + + if (!port->port_usb) { + pr_debug("%s: usb is disconnected\n", __func__); + goto start_rx_end; + } + + pool = &port->read_pool; + out = port->port_usb->out; + + while (!list_empty(pool)) { + struct usb_request *req; + + req = list_entry(pool->next, struct usb_request, list); + list_del(&req->list); + req->length = SDIO_RX_BUF_SIZE; + port->rp_len--; + + spin_unlock_irq(&port->port_lock); + ret = usb_ep_queue(out, req, GFP_ATOMIC); + spin_lock_irq(&port->port_lock); + if (ret) { + pr_err("%s: usb ep out queue failed" + "port:%p, port#%d\n", + __func__, port, port->port_num); + list_add_tail(&req->list, pool); + port->rp_len++; + break; + } + + /* usb could have disconnected while we released spin lock */ + if (!port->port_usb) { + pr_debug("%s: usb is disconnected\n", __func__); + goto start_rx_end; + } + } + +start_rx_end: + spin_unlock_irq(&port->port_lock); +} + +int gsdio_write(struct gsdio_port *port, struct usb_request *req) +{ + unsigned avail; + char *packet = req->buf; + unsigned size = req->actual; + unsigned n; + int ret = 0; + + + if (!port) { + pr_err("%s: port is null\n", __func__); + return -ENODEV; + } + + if (!req) { + pr_err("%s: usb request is null port#%d\n", + __func__, port->port_num); + return -ENODEV; + } + + pr_debug("%s: port:%p port#%d req:%p actual:%d n_read:%d\n", + __func__, port, port->port_num, req, + req->actual, port->n_read); + + if (!port->sdio_open) { + pr_debug("%s: SDIO IO is not supported\n", __func__); + return -ENODEV; + } + + avail = sdio_write_avail(port->sport_info->ch); + + pr_debug("%s: sdio_write_avail:%d", __func__, avail); + + if (!avail) + return -EBUSY; + + if (!req->actual) { + pr_debug("%s: req->actual is already zero,update bytes read\n", + __func__); + port->n_read = 0; + return -ENODEV; + } + + packet = req->buf; + n = port->n_read; + if (n) { + packet += n; + size -= n; + } + + if (size > avail) + size = avail; + + spin_unlock_irq(&port->port_lock); + ret = sdio_write(port->sport_info->ch, packet, size); + spin_lock_irq(&port->port_lock); + if (ret) { + pr_err("%s: port#%d sdio write failed err:%d", + __func__, port->port_num, ret); + /* try again later */ + return ret; + } + + port->nbytes_tomodem += size; + + if (size + n == req->actual) + port->n_read = 0; + else + port->n_read += size; + + return ret; +} + +void gsdio_rx_push(struct work_struct *w) +{ + struct gsdio_port *port = container_of(w, struct gsdio_port, push); + struct list_head *q = &port->read_queue; + struct usb_ep *out; + int ret; + + pr_debug("%s: port:%p port#%d read_queue:%p", __func__, + port, port->port_num, q); + + spin_lock_irq(&port->port_lock); + + if (!port->port_usb) { + pr_debug("%s: usb cable is disconencted\n", __func__); + spin_unlock_irq(&port->port_lock); + return; + } + + out = port->port_usb->out; + + while (!list_empty(q)) { + struct usb_request *req; + + req = list_first_entry(q, struct usb_request, list); + + switch (req->status) { + case -ESHUTDOWN: + pr_debug("%s: req status shutdown portno#%d port:%p", + __func__, port->port_num, port); + goto rx_push_end; + default: + pr_warning("%s: port:%p port#%d" + " Unexpected Rx Status:%d\n", __func__, + port, port->port_num, req->status); + /* FALL THROUGH */ + case 0: + /* normal completion */ + break; + } + + if (!port->sdio_open) { + pr_err("%s: sio channel is not open\n", __func__); + list_move(&req->list, &port->read_pool); + port->rp_len++; + port->rq_len--; + goto rx_push_end; + } + + + list_del(&req->list); + port->rq_len--; + + ret = gsdio_write(port, req); + /* as gsdio_write drops spin_lock while writing data + * to sdio usb cable may have been disconnected + */ + if (!port->port_usb) { + port->n_read = 0; + gsdio_free_req(out, req); + spin_unlock_irq(&port->port_lock); + return; + } + + if (ret || port->n_read) { + list_add(&req->list, &port->read_queue); + port->rq_len++; + goto rx_push_end; + } + + list_add(&req->list, &port->read_pool); + port->rp_len++; + } + + if (port->sdio_open && !list_empty(q)) { + if (sdio_write_avail(port->sport_info->ch)) + queue_work(gsdio_wq, &port->push); + } +rx_push_end: + spin_unlock_irq(&port->port_lock); + + /* start queuing out requests again to host */ + gsdio_start_rx(port); +} + +void gsdio_read_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gsdio_port *port = ep->driver_data; + unsigned long flags; + + pr_debug("%s: ep:%p port:%p\n", __func__, ep, port); + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + list_add_tail(&req->list, &port->read_queue); + port->rq_len++; + queue_work(gsdio_wq, &port->push); + spin_unlock_irqrestore(&port->port_lock, flags); + + return; +} + +void gsdio_write_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gsdio_port *port = ep->driver_data; + unsigned long flags; + + pr_debug("%s: ep:%p port:%p\n", __func__, ep, port); + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + list_add(&req->list, &port->write_pool); + port->wp_len++; + + switch (req->status) { + default: + pr_warning("%s: port:%p port#%d unexpected %s status %d\n", + __func__, port, port->port_num, + ep->name, req->status); + /* FALL THROUGH */ + case 0: + queue_work(gsdio_wq, &port->pull); + break; + + case -ESHUTDOWN: + /* disconnect */ + pr_debug("%s: %s shutdown\n", __func__, ep->name); + break; + } + + spin_unlock_irqrestore(&port->port_lock, flags); + + return; +} + +void gsdio_read_pending(struct gsdio_port *port) +{ + struct sdio_channel *ch; + char buf[1024]; + int avail; + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + ch = port->sport_info->ch; + + if (!ch) + return; + + while ((avail = sdio_read_avail(ch))) { + if (avail > 1024) + avail = 1024; + sdio_read(ch, buf, avail); + + pr_debug("%s: flushed out %d bytes\n", __func__, avail); + } +} + +void gsdio_tx_pull(struct work_struct *w) +{ + struct gsdio_port *port = container_of(w, struct gsdio_port, pull); + struct list_head *pool = &port->write_pool; + + pr_debug("%s: port:%p port#%d pool:%p\n", __func__, + port, port->port_num, pool); + + if (!port->port_usb) { + pr_err("%s: usb disconnected\n", __func__); + + /* take out all the pending data from sdio */ + gsdio_read_pending(port); + + return; + } + + spin_lock_irq(&port->port_lock); + + while (!list_empty(pool)) { + int avail; + struct usb_ep *in = port->port_usb->in; + struct sdio_channel *ch = port->sport_info->ch; + struct usb_request *req; + unsigned len = SDIO_TX_BUF_SIZE; + int ret; + + + req = list_entry(pool->next, struct usb_request, list); + + if (!port->sdio_open) { + pr_debug("%s: SDIO channel is not open\n", __func__); + goto tx_pull_end; + } + + avail = sdio_read_avail(ch); + if (!avail) { + /* REVISIT: for ZLP */ + pr_debug("%s: read_avail:%d port:%p port#%d\n", + __func__, avail, port, port->port_num); + goto tx_pull_end; + } + + if (avail > len) + avail = len; + + list_del(&req->list); + port->wp_len--; + + spin_unlock_irq(&port->port_lock); + ret = sdio_read(ch, req->buf, avail); + spin_lock_irq(&port->port_lock); + if (ret) { + pr_err("%s: port:%p port#%d sdio read failed err:%d", + __func__, port, port->port_num, ret); + + /* check if usb is still active */ + if (!port->port_usb) { + gsdio_free_req(in, req); + } else { + list_add(&req->list, pool); + port->wp_len++; + } + goto tx_pull_end; + } + + req->length = avail; + + spin_unlock_irq(&port->port_lock); + ret = usb_ep_queue(in, req, GFP_KERNEL); + spin_lock_irq(&port->port_lock); + if (ret) { + pr_err("%s: usb ep out queue failed" + "port:%p, port#%d err:%d\n", + __func__, port, port->port_num, ret); + + /* could be usb disconnected */ + if (!port->port_usb) { + gsdio_free_req(in, req); + } else { + list_add(&req->list, pool); + port->wp_len++; + } + goto tx_pull_end; + } + + port->nbytes_tolaptop += avail; + } +tx_pull_end: + spin_unlock_irq(&port->port_lock); +} + +int gsdio_start_io(struct gsdio_port *port) +{ + int ret; + unsigned long flags; + + pr_debug("%s:\n", __func__); + + spin_lock_irqsave(&port->port_lock, flags); + + if (!port->port_usb) { + spin_unlock_irqrestore(&port->port_lock, flags); + return -ENODEV; + } + + /* start usb out queue */ + ret = gsdio_alloc_requests(port->port_usb->out, + &port->read_pool, + SDIO_RX_QUEUE_SIZE, SDIO_RX_BUF_SIZE, + gsdio_read_complete); + if (ret) { + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s: unable to allocate out reqs\n", __func__); + return ret; + } + port->rp_len = SDIO_RX_QUEUE_SIZE; + + ret = gsdio_alloc_requests(port->port_usb->in, + &port->write_pool, + SDIO_TX_QUEUE_SIZE, SDIO_TX_BUF_SIZE, + gsdio_write_complete); + if (ret) { + gsdio_free_requests(port->port_usb->out, &port->read_pool); + port->rp_len = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + pr_err("%s: unable to allocate in reqs\n", __func__); + return ret; + } + port->wp_len = SDIO_TX_QUEUE_SIZE; + spin_unlock_irqrestore(&port->port_lock, flags); + + gsdio_start_rx(port); + queue_work(gsdio_wq, &port->pull); + + return 0; +} + +void gsdio_port_free(unsigned portno) +{ + struct gsdio_port *port = sdio_ports[portno].port; + struct platform_driver *pdriver = &sdio_ports[portno].gsdio_ch; + + if (!port) { + pr_err("%s: invalid portno#%d\n", __func__, portno); + return; + } + + platform_driver_unregister(pdriver); + + kfree(port); +} + +void gsdio_ctrl_wq(struct work_struct *w) +{ + struct gsdio_port *port; + + port = container_of(w, struct gsdio_port, notify_modem); + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + if (!port->sdio_open || port->ctrl_ch_err) + return; + + sdio_cmux_tiocmset(port->sport_info->ctrl_ch_id, + port->cbits_to_modem, ~(port->cbits_to_modem)); +} + +void gsdio_ctrl_notify_modem(struct gserial *gser, u8 portno, int ctrl_bits) +{ + struct gsdio_port *port; + int temp; + + if (portno >= n_sdio_ports) { + pr_err("%s: invalid portno#%d\n", __func__, portno); + return; + } + + if (!gser) { + pr_err("%s: gser is null\n", __func__); + return; + } + + port = sdio_ports[portno].port; + + temp = ctrl_bits & SDIO_ACM_CTRL_DTR ? TIOCM_DTR : 0; + + if (port->cbits_to_modem == temp) + return; + + port->cbits_to_modem = temp; + + /* TIOCM_DTR - 0x002 - bit(1) */ + pr_debug("%s: port:%p port#%d ctrl_bits:%08x\n", __func__, + port, port->port_num, ctrl_bits); + + if (!port->sdio_open) { + pr_err("%s: port:%p port#%d sdio not connected\n", + __func__, port, port->port_num); + return; + } + + /* whenever DTR is high let laptop know that modem status */ + if (port->cbits_to_modem && gser->send_modem_ctrl_bits) + gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop); + + queue_work(gsdio_wq, &port->notify_modem); +} + +void gsdio_ctrl_modem_status(int ctrl_bits, void *_dev) +{ + struct gsdio_port *port = _dev; + + /* TIOCM_CD - 0x040 - bit(6) + * TIOCM_RI - 0x080 - bit(7) + * TIOCM_DSR- 0x100 - bit(8) + */ + pr_debug("%s: port:%p port#%d event:%08x\n", __func__, + port, port->port_num, ctrl_bits); + + port->cbits_to_laptop = 0; + ctrl_bits &= TIOCM_RI | TIOCM_CD | TIOCM_DSR; + if (ctrl_bits & TIOCM_RI) + port->cbits_to_laptop |= SDIO_ACM_CTRL_RI; + if (ctrl_bits & TIOCM_CD) + port->cbits_to_laptop |= SDIO_ACM_CTRL_DCD; + if (ctrl_bits & TIOCM_DSR) + port->cbits_to_laptop |= SDIO_ACM_CTRL_DSR; + + if (port->port_usb && port->port_usb->send_modem_ctrl_bits) + port->port_usb->send_modem_ctrl_bits(port->port_usb, + port->cbits_to_laptop); +} + +void gsdio_ch_notify(void *_dev, unsigned event) +{ + struct gsdio_port *port = _dev; + + pr_debug("%s: port:%p port#%d event:%s\n", __func__, + port, port->port_num, + event == 1 ? "READ AVAIL" : "WRITE_AVAIL"); + + if (event == SDIO_EVENT_DATA_WRITE_AVAIL) + queue_work(gsdio_wq, &port->push); + if (event == SDIO_EVENT_DATA_READ_AVAIL) + queue_work(gsdio_wq, &port->pull); +} + +static void gsdio_open_work(struct work_struct *w) +{ + struct gsdio_port *port = + container_of(w, struct gsdio_port, sdio_open_work.work); + struct sdio_port_info *pi = port->sport_info; + struct gserial *gser; + int ret; + int ctrl_bits; + int startio; + + ret = sdio_open(pi->data_ch_name, &pi->ch, port, gsdio_ch_notify); + if (ret) { + pr_err("%s: port:%p port#%d unable to open sdio ch:%s\n", + __func__, port, port->port_num, + pi->data_ch_name); + return; + } + + ret = sdio_cmux_open(pi->ctrl_ch_id, 0, 0, + gsdio_ctrl_modem_status, port); + if (ret) { + pr_err("%s: port:%p port#%d unable to open ctrl ch:%d\n", + __func__, port, port->port_num, pi->ctrl_ch_id); + port->ctrl_ch_err = 1; + } + + /* check for latest status update from modem */ + if (!port->ctrl_ch_err) { + ctrl_bits = sdio_cmux_tiocmget(pi->ctrl_ch_id); + gsdio_ctrl_modem_status(ctrl_bits, port); + } + + pr_debug("%s: SDIO data:%s ctrl:%d are open\n", __func__, + pi->data_ch_name, + pi->ctrl_ch_id); + + port->sdio_open = 1; + + /* start tx if usb is open already */ + spin_lock_irq(&port->port_lock); + startio = port->port_usb ? 1 : 0; + gser = port->port_usb; + spin_unlock_irq(&port->port_lock); + + if (startio) { + pr_debug("%s: USB is already open, start io\n", __func__); + gsdio_start_io(port); + if (gser->send_modem_ctrl_bits) + gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop); + } +} + +#define SDIO_CH_NAME_MAX_LEN 9 +#define SDIO_OPEN_DELAY msecs_to_jiffies(10000) +static int gsdio_ch_probe(struct platform_device *dev) +{ + struct gsdio_port *port; + struct sdio_port_info *pi; + int i; + + pr_debug("%s: name:%s\n", __func__, dev->name); + + for (i = 0; i < n_sdio_ports; i++) { + port = sdio_ports[i].port; + pi = port->sport_info; + + pr_debug("%s: sdio_ch_name:%s dev_name:%s\n", __func__, + pi->data_ch_name, dev->name); + + /* unfortunately cmux channle might not be ready even if + * sdio channel is ready. as we dont have good notification + * mechanism schedule a delayed work + */ + if (!strncmp(pi->data_ch_name, dev->name, + SDIO_CH_NAME_MAX_LEN)) { + queue_delayed_work(gsdio_wq, + &port->sdio_open_work, SDIO_OPEN_DELAY); + return 0; + } + } + + pr_info("%s: name:%s is not found\n", __func__, dev->name); + + return -ENODEV; +} + +int gsdio_port_alloc(unsigned portno, + struct usb_cdc_line_coding *coding, + struct sdio_port_info *pi) +{ + struct gsdio_port *port; + struct platform_driver *pdriver; + + port = kzalloc(sizeof(struct gsdio_port), GFP_KERNEL); + if (!port) { + pr_err("%s: port allocation failed\n", __func__); + return -ENOMEM; + } + + port->port_num = portno; + spin_lock_init(&port->port_lock); + port->line_coding = *coding; + + /* READ: read from usb and write into sdio */ + INIT_LIST_HEAD(&port->read_pool); + INIT_LIST_HEAD(&port->read_queue); + INIT_WORK(&port->push, gsdio_rx_push); + + INIT_LIST_HEAD(&port->write_pool); + INIT_WORK(&port->pull, gsdio_tx_pull); + + INIT_WORK(&port->notify_modem, gsdio_ctrl_wq); + + INIT_DELAYED_WORK(&port->sdio_open_work, gsdio_open_work); + + sdio_ports[portno].port = port; + + port->sport_info = pi; + pdriver = &sdio_ports[portno].gsdio_ch; + + pdriver->probe = gsdio_ch_probe; + pdriver->driver.name = pi->data_ch_name; + pdriver->driver.owner = THIS_MODULE; + + pr_debug("%s: port:%p port#%d sdio_name: %s\n", __func__, + port, port->port_num, pi->data_ch_name); + + platform_driver_register(pdriver); + + pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num); + + return 0; +} + +int gsdio_connect(struct gserial *gser, u8 portno) +{ + struct gsdio_port *port; + int ret = 0; + unsigned long flags; + + if (portno >= n_sdio_ports) { + pr_err("%s: invalid portno#%d\n", __func__, portno); + return -EINVAL; + } + + if (!gser) { + pr_err("%s: gser is null\n", __func__); + return -EINVAL; + } + + port = sdio_ports[portno].port; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = gser; + gser->notify_modem = gsdio_ctrl_notify_modem; + spin_unlock_irqrestore(&port->port_lock, flags); + + ret = usb_ep_enable(gser->in, gser->in_desc); + if (ret) { + pr_err("%s: failed to enable in ep w/ err:%d\n", + __func__, ret); + port->port_usb = 0; + return ret; + } + gser->in->driver_data = port; + + ret = usb_ep_enable(gser->out, gser->out_desc); + if (ret) { + pr_err("%s: failed to enable in ep w/ err:%d\n", + __func__, ret); + usb_ep_disable(gser->in); + port->port_usb = 0; + gser->in->driver_data = 0; + return ret; + } + gser->out->driver_data = port; + + if (port->sdio_open) { + pr_debug("%s: sdio is already open, start io\n", __func__); + gsdio_start_io(port); + if (gser->send_modem_ctrl_bits) + gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop); + } + + return 0; +} + +void gsdio_disconnect(struct gserial *gser, u8 portno) +{ + unsigned long flags; + struct gsdio_port *port; + + if (portno >= n_sdio_ports) { + pr_err("%s: invalid portno#%d\n", __func__, portno); + return; + } + + if (!gser) { + pr_err("%s: gser is null\n", __func__); + return; + } + + port = sdio_ports[portno].port; + + /* send dtr zero to modem to notify disconnect */ + port->cbits_to_modem = 0; + queue_work(gsdio_wq, &port->notify_modem); + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = 0; + port->nbytes_tomodem = 0; + port->nbytes_tolaptop = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + /* disable endpoints, aborting down any active I/O */ + usb_ep_disable(gser->out); + + usb_ep_disable(gser->in); + + spin_lock_irqsave(&port->port_lock, flags); + gsdio_free_requests(gser->out, &port->read_pool); + gsdio_free_requests(gser->out, &port->read_queue); + gsdio_free_requests(gser->in, &port->write_pool); + + port->rp_len = 0; + port->rq_len = 0; + port->wp_len = 0; + port->n_read = 0; + spin_unlock_irqrestore(&port->port_lock, flags); +} + +#if defined(CONFIG_DEBUG_FS) +static char debug_buffer[PAGE_SIZE]; + +static ssize_t debug_sdio_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct gsdio_port *port; + char *buf = debug_buffer; + unsigned long flags; + int i = 0; + int temp = 0; + + while (i < n_sdio_ports) { + port = sdio_ports[i].port; + spin_lock_irqsave(&port->port_lock, flags); + temp += scnprintf(buf + temp, PAGE_SIZE - temp, + "###PORT:%d port:%p###\n" + "nbytes_tolaptop: %lu\n" + "nbytes_tomodem: %lu\n" + "cbits_to_modem: %u\n" + "cbits_to_laptop: %u\n" + "read_pool_len: %lu\n" + "read_queue_len: %lu\n" + "write_pool_len: %lu\n" + "n_read: %u\n", + i, port, + port->nbytes_tolaptop, port->nbytes_tomodem, + port->cbits_to_modem, port->cbits_to_laptop, + port->rp_len, port->rq_len, port->wp_len, + port->n_read); + spin_unlock_irqrestore(&port->port_lock, flags); + i++; + } + + return simple_read_from_buffer(ubuf, count, ppos, buf, temp); +} + +static ssize_t debug_sdio_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct gsdio_port *port; + unsigned long flags; + int i = 0; + + while (i < n_sdio_ports) { + port = sdio_ports[i].port; + + spin_lock_irqsave(&port->port_lock, flags); + port->nbytes_tolaptop = 0; + port->nbytes_tomodem = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + i++; + } + + return count; +} + +static int debug_sdio_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations debug_gsdio_ops = { + .open = debug_sdio_open, + .read = debug_sdio_read_stats, + .write = debug_sdio_reset_stats, +}; + +static void gsdio_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("usb_gsdio", 0); + if (IS_ERR(dent)) + return; + + debugfs_create_file("status", 0444, dent, 0, &debug_gsdio_ops); +} +#else +static void gsdio_debugfs_init(void) +{ + return; +} +#endif + +/* connect, disconnect, alloc_requests, free_requests */ +int gsdio_setup(struct usb_gadget *g, unsigned count) +{ + struct usb_cdc_line_coding coding; + int i; + int ret = 0; + struct sdio_port_info *port_info; + + pr_debug("%s: gadget:(%p) count:%d\n", __func__, g, count); + + if (count == 0 || count > SDIO_N_PORTS) { + pr_err("%s: invalid number of ports count:%d max_ports:%d\n", + __func__, count, SDIO_N_PORTS); + return -EINVAL; + } + + coding.dwDTERate = cpu_to_le32(9600); + coding.bCharFormat = 8; + coding.bParityType = USB_CDC_NO_PARITY; + coding.bDataBits = USB_CDC_1_STOP_BITS; + + gsdio_wq = create_singlethread_workqueue("k_gserial"); + if (!gsdio_wq) { + pr_err("%s: unable to create workqueue gsdio_wq\n", + __func__); + return -ENOMEM; + } + + for (i = 0; i < count; i++) { + mutex_init(&sdio_ports[i].lock); + ret = gsdio_port_alloc(i, &coding, sport_info + i); + if (ret) { + pr_err("%s: sdio logical port allocation failed\n", + __func__); + goto free_sdio_ports; + } + n_sdio_ports++; + port_info++; + +#ifdef DEBUG + /* REVISIT: create one file per port + * or do not create any file + */ + if (i == 0) { + ret = device_create_file(&g->dev, &dev_attr_input); + if (ret) + pr_err("%s: unable to create device file\n", + __func__); + } +#endif + + } + + gsdio_debugfs_init(); + + return 0; + +free_sdio_ports: + for (i = 0; i < n_sdio_ports; i++) + gsdio_port_free(i); + destroy_workqueue(gsdio_wq); + + return ret; +} + +/* TODO: Add gserial_cleanup */ diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c index 3fdcc9a698f..7bd9f332eec 100644 --- a/drivers/usb/gadget/u_serial.c +++ b/drivers/usb/gadget/u_serial.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "u_serial.h" @@ -77,9 +78,14 @@ * next layer of buffering. For TX that's a circular buffer; for RX * consider it a NOP. A third layer is provided by the TTY code. */ -#define QUEUE_SIZE 16 +#define TX_QUEUE_SIZE 8 +#define TX_BUF_SIZE 4096 #define WRITE_BUF_SIZE 8192 /* TX only */ +#define RX_QUEUE_SIZE 8 +#define RX_BUF_SIZE 4096 + + /* circular buffer */ struct gs_buf { unsigned buf_size; @@ -109,7 +115,7 @@ struct gs_port { int read_allocated; struct list_head read_queue; unsigned n_read; - struct tasklet_struct push; + struct work_struct push; struct list_head write_pool; int write_started; @@ -119,6 +125,10 @@ struct gs_port { /* REVISIT this state ... */ struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ + unsigned long nbytes_from_host; + unsigned long nbytes_to_tty; + unsigned long nbytes_from_tty; + unsigned long nbytes_to_host; }; /* increase N_PORTS if you need more */ @@ -129,6 +139,8 @@ static struct portmaster { } ports[N_PORTS]; static unsigned n_ports; +static struct workqueue_struct *gserial_wq; + #define GS_CLOSE_TIMEOUT 15 /* seconds */ @@ -361,18 +373,37 @@ __acquires(&port->port_lock) struct list_head *pool = &port->write_pool; struct usb_ep *in = port->port_usb->in; int status = 0; + static long prev_len; bool do_tty_wake = false; while (!list_empty(pool)) { struct usb_request *req; int len; - if (port->write_started >= QUEUE_SIZE) + if (port->write_started >= TX_QUEUE_SIZE) break; req = list_entry(pool->next, struct usb_request, list); - len = gs_send_packet(port, req->buf, in->maxpacket); + len = gs_send_packet(port, req->buf, TX_BUF_SIZE); if (len == 0) { + /* Queue zero length packet */ + if (prev_len && (prev_len % in->maxpacket == 0)) { + req->length = 0; + list_del(&req->list); + spin_unlock(&port->port_lock); + status = usb_ep_queue(in, req, GFP_ATOMIC); + spin_lock(&port->port_lock); + if (!port->port_usb) { + gs_free_req(in, req); + break; + } + if (status) { + printk(KERN_ERR "%s: %s err %d\n", + __func__, "queue", status); + list_add(&req->list, pool); + } + prev_len = 0; + } wake_up_interruptible(&port->drain_wait); break; } @@ -396,19 +427,25 @@ __acquires(&port->port_lock) spin_unlock(&port->port_lock); status = usb_ep_queue(in, req, GFP_ATOMIC); spin_lock(&port->port_lock); - + /* + * If port_usb is NULL, gserial disconnect is called + * while the spinlock is dropped and all requests are + * freed. Free the current request here. + */ + if (!port->port_usb) { + do_tty_wake = false; + gs_free_req(in, req); + break; + } if (status) { pr_debug("%s: %s %s err %d\n", __func__, "queue", in->name, status); list_add(&req->list, pool); break; } + prev_len = req->length; + port->nbytes_from_tty += req->length; - port->write_started++; - - /* abort immediately after disconnect */ - if (!port->port_usb) - break; } if (do_tty_wake && port->port_tty) @@ -427,6 +464,7 @@ __acquires(&port->port_lock) { struct list_head *pool = &port->read_pool; struct usb_ep *out = port->port_usb->out; + unsigned started = 0; while (!list_empty(pool)) { struct usb_request *req; @@ -438,12 +476,12 @@ __acquires(&port->port_lock) if (!tty) break; - if (port->read_started >= QUEUE_SIZE) + if (port->read_started >= RX_QUEUE_SIZE) break; req = list_entry(pool->next, struct usb_request, list); list_del(&req->list); - req->length = out->maxpacket; + req->length = RX_BUF_SIZE; /* drop lock while we call out; the controller driver * may need to call us back (e.g. for disconnect) @@ -451,7 +489,16 @@ __acquires(&port->port_lock) spin_unlock(&port->port_lock); status = usb_ep_queue(out, req, GFP_ATOMIC); spin_lock(&port->port_lock); - + /* + * If port_usb is NULL, gserial disconnect is called + * while the spinlock is dropped and all requests are + * freed. Free the current request here. + */ + if (!port->port_usb) { + started = 0; + gs_free_req(out, req); + break; + } if (status) { pr_debug("%s: %s %s err %d\n", __func__, "queue", out->name, status); @@ -460,9 +507,6 @@ __acquires(&port->port_lock) } port->read_started++; - /* abort immediately after disconnect */ - if (!port->port_usb) - break; } return port->read_started; } @@ -477,9 +521,9 @@ __acquires(&port->port_lock) * So QUEUE_SIZE packets plus however many the FIFO holds (usually two) * can be buffered before the TTY layer's buffers (currently 64 KB). */ -static void gs_rx_push(unsigned long _port) +static void gs_rx_push(struct work_struct *w) { - struct gs_port *port = (void *)_port; + struct gs_port *port = container_of(w, struct gs_port, push); struct tty_struct *tty; struct list_head *queue = &port->read_queue; bool disconnect = false; @@ -532,6 +576,7 @@ static void gs_rx_push(unsigned long _port) } count = tty_insert_flip_string(tty, packet, size); + port->nbytes_to_tty += count; if (count) do_push = true; if (count != size) { @@ -549,11 +594,17 @@ static void gs_rx_push(unsigned long _port) port->read_started--; } - /* Push from tty to ldisc; without low_latency set this is handled by - * a workqueue, so we won't get callbacks and can hold port_lock + /* Push from tty to ldisc; this is immediate with low_latency, and + * may trigger callbacks to this driver ... so drop the spinlock. */ if (tty && do_push) { + spin_unlock_irq(&port->port_lock); tty_flip_buffer_push(tty); + wake_up_interruptible(&tty->read_wait); + spin_lock_irq(&port->port_lock); + + /* tty may have been closed */ + tty = port->port_tty; } @@ -562,13 +613,13 @@ static void gs_rx_push(unsigned long _port) * this time around, there may be trouble unless there's an * implicit tty_unthrottle() call on its way... * - * REVISIT we should probably add a timer to keep the tasklet + * REVISIT we should probably add a timer to keep the work queue * from starving ... but it's not clear that case ever happens. */ if (!list_empty(queue) && tty) { if (!test_bit(TTY_THROTTLED, &tty->flags)) { if (do_push) - tasklet_schedule(&port->push); + queue_work(gserial_wq, &port->push); else pr_warning(PREFIX "%d: RX not scheduled?\n", port->port_num); @@ -585,19 +636,23 @@ static void gs_rx_push(unsigned long _port) static void gs_read_complete(struct usb_ep *ep, struct usb_request *req) { struct gs_port *port = ep->driver_data; + unsigned long flags; /* Queue all received data until the tty layer is ready for it. */ - spin_lock(&port->port_lock); + spin_lock_irqsave(&port->port_lock, flags); + port->nbytes_from_host += req->actual; list_add_tail(&req->list, &port->read_queue); - tasklet_schedule(&port->push); - spin_unlock(&port->port_lock); + queue_work(gserial_wq, &port->push); + spin_unlock_irqrestore(&port->port_lock, flags); } static void gs_write_complete(struct usb_ep *ep, struct usb_request *req) { struct gs_port *port = ep->driver_data; + unsigned long flags; - spin_lock(&port->port_lock); + spin_lock_irqsave(&port->port_lock, flags); + port->nbytes_to_host += req->actual; list_add(&req->list, &port->write_pool); port->write_started--; @@ -609,7 +664,8 @@ static void gs_write_complete(struct usb_ep *ep, struct usb_request *req) /* FALL THROUGH */ case 0: /* normal completion */ - gs_start_tx(port); + if (port->port_usb) + gs_start_tx(port); break; case -ESHUTDOWN: @@ -618,7 +674,7 @@ static void gs_write_complete(struct usb_ep *ep, struct usb_request *req) break; } - spin_unlock(&port->port_lock); + spin_unlock_irqrestore(&port->port_lock, flags); } static void gs_free_requests(struct usb_ep *ep, struct list_head *head, @@ -636,19 +692,18 @@ static void gs_free_requests(struct usb_ep *ep, struct list_head *head, } static int gs_alloc_requests(struct usb_ep *ep, struct list_head *head, - void (*fn)(struct usb_ep *, struct usb_request *), + int num, int size, void (*fn)(struct usb_ep *, struct usb_request *), int *allocated) { int i; struct usb_request *req; - int n = allocated ? QUEUE_SIZE - *allocated : QUEUE_SIZE; /* Pre-allocate up to QUEUE_SIZE transfers, but if we can't * do quite that many this time, don't fail ... we just won't * be as speedy as we might otherwise be. */ - for (i = 0; i < n; i++) { - req = gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC); + for (i = 0; i < num; i++) { + req = gs_alloc_req(ep, size, GFP_ATOMIC); if (!req) return list_empty(head) ? -ENOMEM : 0; req->complete = fn; @@ -681,13 +736,13 @@ static int gs_start_io(struct gs_port *port) * configurations may use different endpoints with a given port; * and high speed vs full speed changes packet sizes too. */ - status = gs_alloc_requests(ep, head, gs_read_complete, - &port->read_allocated); + status = gs_alloc_requests(ep, head, RX_QUEUE_SIZE, RX_BUF_SIZE, + gs_read_complete, &port->read_allocated); if (status) return status; status = gs_alloc_requests(port->port_usb->in, &port->write_pool, - gs_write_complete, &port->write_allocated); + TX_QUEUE_SIZE, TX_BUF_SIZE, gs_write_complete, &port->write_allocated); if (status) { gs_free_requests(ep, head, &port->read_allocated); return status; @@ -697,6 +752,8 @@ static int gs_start_io(struct gs_port *port) port->n_read = 0; started = gs_start_rx(port); + if (!port->port_usb) + return -EIO; /* unblock any pending writes into our circular buffer */ if (started) { tty_wakeup(port->port_tty); @@ -801,6 +858,13 @@ static int gs_open(struct tty_struct *tty, struct file *file) port->open_count = 1; port->openclose = false; + /* low_latency means ldiscs work is carried in the same context + * of tty_flip_buffer_push. The same can be called from IRQ with + * low_latency = 0. But better to use a dedicated worker thread + * to push the data. + */ + tty->low_latency = 1; + /* if connected, start the I/O stream */ if (port->port_usb) { struct gserial *gser = port->port_usb; @@ -874,7 +938,7 @@ static void gs_close(struct tty_struct *tty, struct file *file) /* Iff we're disconnected, there can be no I/O in flight so it's * ok to free the circular buffer; else just scrub it. And don't - * let the push tasklet fire again until we're re-opened. + * let the push work queue fire again until we're re-opened. */ if (gser == NULL) gs_buf_free(&port->port_write_buf); @@ -890,6 +954,22 @@ static void gs_close(struct tty_struct *tty, struct file *file) port->port_num, tty, file); wake_up_interruptible(&port->close_wait); + + /* + * Freeing the previously queued requests as they are + * allocated again as a part of gs_open() + */ + if (port->port_usb) { + spin_unlock_irq(&port->port_lock); + usb_ep_fifo_flush(gser->out); + usb_ep_fifo_flush(gser->in); + spin_lock_irq(&port->port_lock); + gs_free_requests(gser->out, &port->read_queue, NULL); + gs_free_requests(gser->out, &port->read_pool, NULL); + gs_free_requests(gser->in, &port->write_pool, NULL); + } + port->read_allocated = port->read_started = + port->write_allocated = port->write_started = 0; exit: spin_unlock_irq(&port->port_lock); } @@ -988,7 +1068,7 @@ static void gs_unthrottle(struct tty_struct *tty) * rts/cts, or other handshaking with the host, but if the * read queue backs up enough we'll be NAKing OUT packets. */ - tasklet_schedule(&port->push); + queue_work(gserial_wq, &port->push); pr_vdebug(PREFIX "%d: unthrottle\n", port->port_num); } spin_unlock_irqrestore(&port->port_lock, flags); @@ -1012,6 +1092,77 @@ static int gs_break_ctl(struct tty_struct *tty, int duration) return status; } +static int gs_tiocmget(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + struct gserial *gser; + unsigned int result = 0; + + spin_lock_irq(&port->port_lock); + gser = port->port_usb; + if (!gser) { + result = -ENODEV; + goto fail; + } + + if (gser->get_dtr) + result |= (gser->get_dtr(gser) ? TIOCM_DTR : 0); + + if (gser->get_rts) + result |= (gser->get_rts(gser) ? TIOCM_RTS : 0); + + if (gser->serial_state & TIOCM_CD) + result |= TIOCM_CD; + + if (gser->serial_state & TIOCM_RI) + result |= TIOCM_RI; +fail: + spin_unlock_irq(&port->port_lock); + return result; +} + +static int gs_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct gs_port *port = tty->driver_data; + struct gserial *gser; + int status = 0; + + spin_lock_irq(&port->port_lock); + gser = port->port_usb; + if (!gser) { + status = -ENODEV; + goto fail; + } + + if (set & TIOCM_RI) { + if (gser->send_ring_indicator) { + gser->serial_state |= TIOCM_RI; + status = gser->send_ring_indicator(gser, 1); + } + } + if (clear & TIOCM_RI) { + if (gser->send_ring_indicator) { + gser->serial_state &= ~TIOCM_RI; + status = gser->send_ring_indicator(gser, 0); + } + } + if (set & TIOCM_CD) { + if (gser->send_carrier_detect) { + gser->serial_state |= TIOCM_CD; + status = gser->send_carrier_detect(gser, 1); + } + } + if (clear & TIOCM_CD) { + if (gser->send_carrier_detect) { + gser->serial_state &= ~TIOCM_CD; + status = gser->send_carrier_detect(gser, 0); + } + } +fail: + spin_unlock_irq(&port->port_lock); + return status; +} static const struct tty_operations gs_tty_ops = { .open = gs_open, .close = gs_close, @@ -1022,6 +1173,8 @@ static const struct tty_operations gs_tty_ops = { .chars_in_buffer = gs_chars_in_buffer, .unthrottle = gs_unthrottle, .break_ctl = gs_break_ctl, + .tiocmget = gs_tiocmget, + .tiocmset = gs_tiocmset, }; /*-------------------------------------------------------------------------*/ @@ -1041,7 +1194,7 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) init_waitqueue_head(&port->close_wait); init_waitqueue_head(&port->drain_wait); - tasklet_init(&port->push, gs_rx_push, (unsigned long) port); + INIT_WORK(&port->push, gs_rx_push); INIT_LIST_HEAD(&port->read_pool); INIT_LIST_HEAD(&port->read_queue); @@ -1055,6 +1208,116 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) return 0; } + +#if defined(CONFIG_DEBUG_FS) + +#define BUF_SIZE 512 + +static ssize_t debug_read_status(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct gs_port *ui_dev = file->private_data; + struct tty_struct *tty; + struct gserial *gser; + char *buf; + unsigned long flags; + int i = 0; + int ret; + int result = 0; + + tty = ui_dev->port_tty; + gser = ui_dev->port_usb; + + buf = kzalloc(sizeof(char) * BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + spin_lock_irqsave(&ui_dev->port_lock, flags); + + i += scnprintf(buf + i, BUF_SIZE - i, + "nbytes_from_host: %lu\n", ui_dev->nbytes_from_host); + + i += scnprintf(buf + i, BUF_SIZE - i, + "nbytes_to_tty: %lu\n", ui_dev->nbytes_to_tty); + + i += scnprintf(buf + i, BUF_SIZE - i, "nbytes_with_usb_OUT_txr: %lu\n", + (ui_dev->nbytes_from_host - ui_dev->nbytes_to_tty)); + + i += scnprintf(buf + i, BUF_SIZE - i, + "nbytes_from_tty: %lu\n", ui_dev->nbytes_from_tty); + + i += scnprintf(buf + i, BUF_SIZE - i, + "nbytes_to_host: %lu\n", ui_dev->nbytes_to_host); + + i += scnprintf(buf + i, BUF_SIZE - i, "nbytes_with_usb_IN_txr: %lu\n", + (ui_dev->nbytes_from_tty - ui_dev->nbytes_to_host)); + + if (tty) + i += scnprintf(buf + i, BUF_SIZE - i, + "tty_flags: %lu\n", tty->flags); + + if (gser->get_dtr) { + result |= (gser->get_dtr(gser) ? TIOCM_DTR : 0); + i += scnprintf(buf + i, BUF_SIZE - i, + "DTR_status: %d\n", result); + } + + spin_unlock_irqrestore(&ui_dev->port_lock, flags); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, i); + + kfree(buf); + + return ret; +} + +static ssize_t debug_write_reset(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct gs_port *ui_dev = file->private_data; + unsigned long flags; + + spin_lock_irqsave(&ui_dev->port_lock, flags); + ui_dev->nbytes_from_host = ui_dev->nbytes_to_tty = + ui_dev->nbytes_from_tty = ui_dev->nbytes_to_host = 0; + spin_unlock_irqrestore(&ui_dev->port_lock, flags); + + return count; +} + +static int serial_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +const struct file_operations debug_rst_ops = { + .open = serial_debug_open, + .write = debug_write_reset, +}; + +const struct file_operations debug_adb_ops = { + .open = serial_debug_open, + .read = debug_read_status, +}; + +static void usb_debugfs_init(struct gs_port *ui_dev, int port_num) +{ + struct dentry *dent; + char buf[48]; + + snprintf(buf, 48, "usb_serial%d", port_num); + dent = debugfs_create_dir(buf, 0); + if (IS_ERR(dent)) + return; + + debugfs_create_file("readstatus", 0444, dent, ui_dev, &debug_adb_ops); + debugfs_create_file("reset", 0222, dent, ui_dev, &debug_rst_ops); +} +#else +static void usb_debugfs_init(struct gs_port *ui_dev) {} +#endif + /** * gserial_setup - initialize TTY driver for one or more ports * @g: gadget to associate with these ports @@ -1094,7 +1357,8 @@ int gserial_setup(struct usb_gadget *g, unsigned count) gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; gs_tty_driver->subtype = SERIAL_TYPE_NORMAL; - gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV + | TTY_DRIVER_RESET_TERMIOS; gs_tty_driver->init_termios = tty_std_termios; /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on @@ -1113,6 +1377,12 @@ int gserial_setup(struct usb_gadget *g, unsigned count) tty_set_operations(gs_tty_driver, &gs_tty_ops); + gserial_wq = create_singlethread_workqueue("k_gserial"); + if (!gserial_wq) { + status = -ENOMEM; + goto fail; + } + /* make devices be openable */ for (i = 0; i < count; i++) { mutex_init(&ports[i].lock); @@ -1127,6 +1397,7 @@ int gserial_setup(struct usb_gadget *g, unsigned count) /* export the driver ... */ status = tty_register_driver(gs_tty_driver); if (status) { + put_tty_driver(gs_tty_driver); pr_err("%s: cannot register, err %d\n", __func__, status); goto fail; @@ -1142,6 +1413,9 @@ int gserial_setup(struct usb_gadget *g, unsigned count) __func__, i, PTR_ERR(tty_dev)); } + for (i = 0; i < count; i++) + usb_debugfs_init(ports[i].port, i); + pr_debug("%s: registered %d ttyGS* device%s\n", __func__, count, (count == 1) ? "" : "s"); @@ -1149,6 +1423,7 @@ int gserial_setup(struct usb_gadget *g, unsigned count) fail: while (count--) kfree(ports[count].port); + destroy_workqueue(gserial_wq); put_tty_driver(gs_tty_driver); gs_tty_driver = NULL; return status; @@ -1195,7 +1470,7 @@ void gserial_cleanup(void) ports[i].port = NULL; mutex_unlock(&ports[i].lock); - tasklet_kill(&port->push); + cancel_work_sync(&port->push); /* wait for old opens to finish */ wait_event(port->close_wait, gs_closed(port)); @@ -1206,6 +1481,7 @@ void gserial_cleanup(void) } n_ports = 0; + destroy_workqueue(gserial_wq); tty_unregister_driver(gs_tty_driver); put_tty_driver(gs_tty_driver); gs_tty_driver = NULL; @@ -1344,5 +1620,8 @@ void gserial_disconnect(struct gserial *gser) port->read_allocated = port->read_started = port->write_allocated = port->write_started = 0; + port->nbytes_from_host = port->nbytes_to_tty = + port->nbytes_from_tty = port->nbytes_to_host = 0; + spin_unlock_irqrestore(&port->port_lock, flags); } diff --git a/drivers/usb/gadget/u_serial.h b/drivers/usb/gadget/u_serial.h index 300f0ed9475..fea53d8cee5 100644 --- a/drivers/usb/gadget/u_serial.h +++ b/drivers/usb/gadget/u_serial.h @@ -40,11 +40,22 @@ struct gserial { /* REVISIT avoid this CDC-ACM support harder ... */ struct usb_cdc_line_coding port_line_coding; /* 9600-8-N-1 etc */ + u16 serial_state; + + /* control signal callbacks*/ + unsigned int (*get_dtr)(struct gserial *p); + unsigned int (*get_rts)(struct gserial *p); /* notification callbacks */ void (*connect)(struct gserial *p); void (*disconnect)(struct gserial *p); int (*send_break)(struct gserial *p, int duration); + unsigned int (*send_carrier_detect)(struct gserial *p, unsigned int); + unsigned int (*send_ring_indicator)(struct gserial *p, unsigned int); + int (*send_modem_ctrl_bits)(struct gserial *p, int ctrl_bits); + + /* notification changes to modem */ + void (*notify_modem)(struct gserial *gser, u8 portno, int ctrl_bits); }; /* utilities to allocate/free request and buffer */ @@ -59,6 +70,15 @@ void gserial_cleanup(void); int gserial_connect(struct gserial *, u8 port_num); void gserial_disconnect(struct gserial *); +/* sdio related functions */ +int gsdio_setup(struct usb_gadget *g, unsigned n_ports); +int gsdio_connect(struct gserial *, u8 port_num); +void gsdio_disconnect(struct gserial *, u8 portno); + +int gsmd_setup(struct usb_gadget *g, unsigned n_ports); +int gsmd_connect(struct gserial *, u8 port_num); +void gsmd_disconnect(struct gserial *, u8 portno); + /* functions are bound to configurations by a config or gadget driver */ int acm_bind_config(struct usb_configuration *c, u8 port_num); int gser_bind_config(struct usb_configuration *c, u8 port_num); diff --git a/drivers/usb/gadget/u_smd.c b/drivers/usb/gadget/u_smd.c new file mode 100644 index 00000000000..0e8f24718ac --- /dev/null +++ b/drivers/usb/gadget/u_smd.c @@ -0,0 +1,887 @@ +/* + * u_smd.c - utilities for USB gadget serial over smd + * + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * This code also borrows from drivers/usb/gadget/u_serial.c, which is + * Copyright (C) 2000 - 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2000 Peter Berger (pberger@brimson.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_serial.h" + +#define SMD_RX_QUEUE_SIZE 8 +#define SMD_RX_BUF_SIZE 2048 + +#define SMD_TX_QUEUE_SIZE 8 +#define SMD_TX_BUF_SIZE 2048 + +static struct workqueue_struct *gsmd_wq; + +#define SMD_N_PORTS 2 +#define CH_OPENED 0 +struct smd_port_info { + struct smd_channel *ch; + char *name; + unsigned long flags; + wait_queue_head_t wait; +}; + +struct smd_port_info smd_pi[SMD_N_PORTS] = { + { + .name = "DS", + }, + { + .name = "UNUSED", + }, +}; + +struct gsmd_port { + unsigned port_num; + spinlock_t port_lock; + + unsigned n_read; + struct list_head read_pool; + struct list_head read_queue; + struct work_struct push; + + struct list_head write_pool; + struct work_struct pull; + + struct gserial *port_usb; + + struct smd_port_info *pi; + struct work_struct connect_work; + + /* At present, smd does not notify + * control bit change info from modem + */ + struct work_struct update_modem_ctrl_sig; + +#define SMD_ACM_CTRL_DTR 0x01 +#define SMD_ACM_CTRL_RTS 0x02 + unsigned cbits_to_modem; + +#define SMD_ACM_CTRL_DCD 0x01 +#define SMD_ACM_CTRL_DSR 0x02 +#define SMD_ACM_CTRL_BRK 0x04 +#define SMD_ACM_CTRL_RI 0x08 + unsigned cbits_to_laptop; + + /* pkt counters */ + unsigned long nbytes_tomodem; + unsigned long nbytes_tolaptop; +}; + +static struct smd_portmaster { + struct mutex lock; + struct gsmd_port *port; +} smd_ports[SMD_N_PORTS]; +static unsigned n_smd_ports; + +static void gsmd_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static void gsmd_free_requests(struct usb_ep *ep, struct list_head *head) +{ + struct usb_request *req; + + while (!list_empty(head)) { + req = list_entry(head->next, struct usb_request, list); + list_del(&req->list); + gsmd_free_req(ep, req); + } +} + +static struct usb_request * +gsmd_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, flags); + if (!req) { + pr_err("%s: usb alloc request failed\n", __func__); + return 0; + } + + req->length = len; + req->buf = kmalloc(len, flags); + if (!req->buf) { + pr_err("%s: request buf allocation failed\n", __func__); + usb_ep_free_request(ep, req); + return 0; + } + + return req; +} + +static int gsmd_alloc_requests(struct usb_ep *ep, struct list_head *head, + int num, int size, + void (*cb)(struct usb_ep *ep, struct usb_request *)) +{ + int i; + struct usb_request *req; + + pr_debug("%s: ep:%p head:%p num:%d size:%d cb:%p", __func__, + ep, head, num, size, cb); + + for (i = 0; i < num; i++) { + req = gsmd_alloc_req(ep, size, GFP_ATOMIC); + if (!req) { + pr_debug("%s: req allocated:%d\n", __func__, i); + return list_empty(head) ? -ENOMEM : 0; + } + req->complete = cb; + list_add(&req->list, head); + } + + return 0; +} + +static void gsmd_start_rx(struct gsmd_port *port) +{ + struct list_head *pool; + struct usb_ep *out; + int ret; + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + spin_lock_irq(&port->port_lock); + + if (!port->port_usb) { + pr_debug("%s: USB disconnected\n", __func__); + goto start_rx_end; + } + + pool = &port->read_pool; + out = port->port_usb->out; + + while (!list_empty(pool)) { + struct usb_request *req; + + req = list_entry(pool->next, struct usb_request, list); + list_del(&req->list); + req->length = SMD_RX_BUF_SIZE; + + spin_unlock_irq(&port->port_lock); + ret = usb_ep_queue(out, req, GFP_KERNEL); + spin_lock_irq(&port->port_lock); + if (ret) { + pr_err("%s: usb ep out queue failed" + "port:%p, port#%d\n", + __func__, port, port->port_num); + list_add_tail(&req->list, pool); + break; + } + } +start_rx_end: + spin_unlock_irq(&port->port_lock); +} + +static void gsmd_rx_push(struct work_struct *w) +{ + struct gsmd_port *port = container_of(w, struct gsmd_port, push); + struct list_head *q; + + pr_debug("%s: port:%p port#%d", __func__, port, port->port_num); + + spin_lock_irq(&port->port_lock); + + q = &port->read_queue; + while (!list_empty(q)) { + struct usb_request *req; + int avail; + struct smd_port_info *pi = port->pi; + + req = list_first_entry(q, struct usb_request, list); + + switch (req->status) { + case -ESHUTDOWN: + pr_debug("%s: req status shutdown portno#%d port:%p\n", + __func__, port->port_num, port); + goto rx_push_end; + default: + pr_warning("%s: port:%p port#%d" + " Unexpected Rx Status:%d\n", __func__, + port, port->port_num, req->status); + case 0: + /* normal completion */ + break; + } + + avail = smd_write_avail(pi->ch); + if (!avail) + goto rx_push_end; + + if (req->actual) { + char *packet = req->buf; + unsigned size = req->actual; + unsigned n; + unsigned count; + + n = port->n_read; + if (n) { + packet += n; + size -= n; + } + + count = smd_write(pi->ch, packet, size); + if (count < 0) { + pr_err("%s: smd write failed err:%d\n", + __func__, count); + goto rx_push_end; + } + + if (count != size) { + port->n_read += count; + goto rx_push_end; + } + + port->nbytes_tomodem += count; + } + + port->n_read = 0; + list_move(&req->list, &port->read_pool); + } + +rx_push_end: + spin_unlock_irq(&port->port_lock); + + gsmd_start_rx(port); +} + +static void gsmd_read_pending(struct gsmd_port *port) +{ + int avail; + + if (!port || !port->pi->ch) + return; + + /* passing null buffer discards the data */ + while ((avail = smd_read_avail(port->pi->ch))) + smd_read(port->pi->ch, 0, avail); + + return; +} + +static void gsmd_tx_pull(struct work_struct *w) +{ + struct gsmd_port *port = container_of(w, struct gsmd_port, pull); + struct list_head *pool = &port->write_pool; + + pr_debug("%s: port:%p port#%d pool:%p\n", __func__, + port, port->port_num, pool); + + if (!port->port_usb) { + pr_debug("%s: usb is disconnected\n", __func__); + gsmd_read_pending(port); + return; + } + + spin_lock_irq(&port->port_lock); + while (!list_empty(pool)) { + struct usb_request *req; + struct usb_ep *in = port->port_usb->in; + struct smd_port_info *pi = port->pi; + int avail; + int ret; + + avail = smd_read_avail(pi->ch); + if (!avail) + break; + + avail = avail > SMD_TX_BUF_SIZE ? SMD_TX_BUF_SIZE : avail; + + req = list_entry(pool->next, struct usb_request, list); + list_del(&req->list); + req->length = smd_read(pi->ch, req->buf, avail); + + spin_unlock_irq(&port->port_lock); + ret = usb_ep_queue(in, req, GFP_KERNEL); + spin_lock_irq(&port->port_lock); + if (ret) { + pr_err("%s: usb ep out queue failed" + "port:%p, port#%d err:%d\n", + __func__, port, port->port_num, ret); + /* could be usb disconnected */ + if (!port->port_usb) + gsmd_free_req(in, req); + else + list_add(&req->list, pool); + goto tx_pull_end; + } + + port->nbytes_tolaptop += req->length; + } + +tx_pull_end: + /* TBD: Check how code behaves on USB bus suspend */ + if (port->port_usb && smd_read_avail(port->pi->ch) && !list_empty(pool)) + queue_work(gsmd_wq, &port->pull); + + spin_unlock_irq(&port->port_lock); + + return; +} + +static void gsmd_read_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gsmd_port *port = ep->driver_data; + unsigned long flags; + + pr_debug("%s: ep:%p port:%p\n", __func__, ep, port); + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + list_add_tail(&req->list, &port->read_queue); + queue_work(gsmd_wq, &port->push); + spin_unlock_irqrestore(&port->port_lock, flags); + + return; +} + +static void gsmd_write_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gsmd_port *port = ep->driver_data; + unsigned long flags; + + pr_debug("%s: ep:%p port:%p\n", __func__, ep, port); + + if (!port) { + pr_err("%s: port is null\n", __func__); + return; + } + + spin_lock_irqsave(&port->port_lock, flags); + list_add(&req->list, &port->write_pool); + + switch (req->status) { + default: + pr_warning("%s: port:%p port#%d unexpected %s status %d\n", + __func__, port, port->port_num, + ep->name, req->status); + /* FALL THROUGH */ + case 0: + queue_work(gsmd_wq, &port->pull); + break; + + case -ESHUTDOWN: + /* disconnect */ + pr_debug("%s: %s shutdown\n", __func__, ep->name); + gsmd_free_req(ep, req); + break; + } + + spin_unlock_irqrestore(&port->port_lock, flags); + + return; +} + +static void gsmd_start_io(struct gsmd_port *port) +{ + int ret = -ENODEV; + unsigned long flags; + + pr_debug("%s: port: %p\n", __func__, port); + + spin_lock_irqsave(&port->port_lock, flags); + + if (!port->port_usb) + goto start_io_out; + + ret = gsmd_alloc_requests(port->port_usb->out, + &port->read_pool, + SMD_RX_QUEUE_SIZE, SMD_RX_BUF_SIZE, + gsmd_read_complete); + if (ret) { + pr_err("%s: unable to allocate out requests\n", + __func__); + goto start_io_out; + } + + ret = gsmd_alloc_requests(port->port_usb->in, + &port->write_pool, + SMD_TX_QUEUE_SIZE, SMD_TX_BUF_SIZE, + gsmd_write_complete); + if (ret) { + gsmd_free_requests(port->port_usb->out, &port->read_pool); + pr_err("%s: unable to allocate IN requests\n", + __func__); + goto start_io_out; + } + +start_io_out: + spin_unlock_irqrestore(&port->port_lock, flags); + + if (ret) + return; + + gsmd_start_rx(port); +} + +static unsigned int convert_uart_sigs_to_acm(unsigned uart_sig) +{ + unsigned int acm_sig = 0; + + /* should this needs to be in calling functions ??? */ + uart_sig &= (TIOCM_RI | TIOCM_CD | TIOCM_DSR); + + if (uart_sig & TIOCM_RI) + acm_sig |= SMD_ACM_CTRL_RI; + if (uart_sig & TIOCM_CD) + acm_sig |= SMD_ACM_CTRL_DCD; + if (uart_sig & TIOCM_DSR) + acm_sig |= SMD_ACM_CTRL_DSR; + + return acm_sig; +} + +static unsigned int convert_acm_sigs_to_uart(unsigned acm_sig) +{ + unsigned int uart_sig = 0; + + /* should this needs to be in calling functions ??? */ + acm_sig &= (SMD_ACM_CTRL_DTR | SMD_ACM_CTRL_RTS); + + if (acm_sig & SMD_ACM_CTRL_DTR) + uart_sig |= TIOCM_DTR; + if (acm_sig & SMD_ACM_CTRL_RTS) + uart_sig |= TIOCM_RTS; + + return uart_sig; +} + +static void gsmd_notify(void *priv, unsigned event) +{ + struct gsmd_port *port = priv; + struct smd_port_info *pi = port->pi; + int i; + + switch (event) { + case SMD_EVENT_DATA: + pr_debug("%s: Event data\n", __func__); + if (smd_read_avail(pi->ch)) + queue_work(gsmd_wq, &port->pull); + if (smd_write_avail(pi->ch)) + queue_work(gsmd_wq, &port->push); + break; + case SMD_EVENT_OPEN: + pr_debug("%s: Event Open\n", __func__); + set_bit(CH_OPENED, &pi->flags); + wake_up(&pi->wait); + break; + case SMD_EVENT_CLOSE: + pr_debug("%s: Event Close\n", __func__); + clear_bit(CH_OPENED, &pi->flags); + break; + case SMD_EVENT_STATUS: + i = smd_tiocmget(port->pi->ch); + port->cbits_to_laptop = convert_uart_sigs_to_acm(i); + if (port->port_usb && port->port_usb->send_modem_ctrl_bits) + port->port_usb->send_modem_ctrl_bits(port->port_usb, + port->cbits_to_laptop); + break; + } +} + +#define MAX_SMD_RETRY_CNT 20 +static void gsmd_connect_work(struct work_struct *w) +{ + struct gsmd_port *port; + struct smd_port_info *pi; + int ret; + int retry_cnt = 0; + + port = container_of(w, struct gsmd_port, connect_work); + pi = port->pi; + + pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num); + + /* SMD driver comes online gets initialized and loads modem + * 10 seconds after boot up. If USB cable is connected at boot-up, + * this might result smd open failure. To work-around, retry + * opening multiple times. + */ + do { + if (!port->port_usb) + return; + + ret = smd_named_open_on_edge(pi->name, SMD_APPS_MODEM, + &pi->ch, port, gsmd_notify); + if (!ret) + break; + + retry_cnt++; + msleep(1000); + } while (retry_cnt < MAX_SMD_RETRY_CNT); + + if (ret) { + pr_err("%s: unable to open smd port:%s err:%d\n", + __func__, pi->name, ret); + return; + } + + pr_debug("%s: SMD port open successful retrycnt:%d\n", + __func__, retry_cnt); + + wait_event(pi->wait, test_bit(CH_OPENED, &pi->flags)); + + if (!port->port_usb) + return; + + /* update usb control signals to modem */ + if (port->cbits_to_modem) + smd_tiocmset(port->pi->ch, + port->cbits_to_modem, + ~port->cbits_to_modem); + + gsmd_start_io(port); +} + +static void gsmd_notify_modem(struct gserial *gser, u8 portno, int ctrl_bits) +{ + struct gsmd_port *port; + int temp; + + if (portno >= n_smd_ports) { + pr_err("%s: invalid portno#%d\n", __func__, portno); + return; + } + + if (!gser) { + pr_err("%s: gser is null\n", __func__); + return; + } + + port = smd_ports[portno].port; + + temp = convert_acm_sigs_to_uart(ctrl_bits); + + if (temp == port->cbits_to_modem) + return; + + port->cbits_to_modem = temp; + + /* usb could send control signal before smd is ready */ + if (!test_bit(CH_OPENED, &port->pi->flags)) + return; + + /* if DTR is high, update latest modem info to laptop */ + if (port->cbits_to_modem & TIOCM_DTR) { + unsigned i; + + i = smd_tiocmget(port->pi->ch); + port->cbits_to_laptop = convert_uart_sigs_to_acm(i); + + if (gser->send_modem_ctrl_bits) + gser->send_modem_ctrl_bits( + port->port_usb, + port->cbits_to_laptop); + } + + smd_tiocmset(port->pi->ch, + port->cbits_to_modem, + ~port->cbits_to_modem); +} + +int gsmd_connect(struct gserial *gser, u8 portno) +{ + unsigned long flags; + int ret; + struct gsmd_port *port; + + pr_debug("%s: gserial:%p portno:%u\n", __func__, gser, portno); + + if (portno >= n_smd_ports) { + pr_err("%s: Invalid port no#%d", __func__, portno); + return -EINVAL; + } + + if (!gser) { + pr_err("%s: gser is null\n", __func__); + return -EINVAL; + } + + port = smd_ports[portno].port; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = gser; + gser->notify_modem = gsmd_notify_modem; + port->nbytes_tomodem = 0; + port->nbytes_tolaptop = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + ret = usb_ep_enable(gser->in, gser->in_desc); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:IN ep:%p", + __func__, gser->in); + port->port_usb = 0; + return ret; + } + gser->in->driver_data = port; + + ret = usb_ep_enable(gser->out, gser->out_desc); + if (ret) { + pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p", + __func__, gser->out); + port->port_usb = 0; + gser->in->driver_data = 0; + return ret; + } + gser->out->driver_data = port; + + queue_work(gsmd_wq, &port->connect_work); + + return 0; +} + +void gsmd_disconnect(struct gserial *gser, u8 portno) +{ + unsigned long flags; + struct gsmd_port *port; + + pr_debug("%s: gserial:%p portno:%u\n", __func__, gser, portno); + + if (portno >= n_smd_ports) { + pr_err("%s: invalid portno#%d\n", __func__, portno); + return; + } + + if (!gser) { + pr_err("%s: gser is null\n", __func__); + return; + } + + port = smd_ports[portno].port; + + spin_lock_irqsave(&port->port_lock, flags); + port->port_usb = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + /* disable endpoints, aborting down any active I/O */ + usb_ep_disable(gser->out); + usb_ep_disable(gser->in); + + spin_lock_irqsave(&port->port_lock, flags); + gsmd_free_requests(gser->out, &port->read_pool); + gsmd_free_requests(gser->out, &port->read_queue); + gsmd_free_requests(gser->in, &port->write_pool); + port->n_read = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + + if (!test_bit(CH_OPENED, &port->pi->flags)) + return; + + /* lower the dtr */ + port->cbits_to_modem = 0; + smd_tiocmset(port->pi->ch, + port->cbits_to_modem, + ~port->cbits_to_modem); + + smd_close(port->pi->ch); + port->pi->flags = 0; +} + +static void gsmd_port_free(int portno) +{ + struct gsmd_port *port = smd_ports[portno].port; + + if (!port) + kfree(port); +} + +static int gsmd_port_alloc(int portno, struct usb_cdc_line_coding *coding) +{ + struct gsmd_port *port; + + port = kzalloc(sizeof(struct gsmd_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->port_num = portno; + port->pi = &smd_pi[portno]; + + spin_lock_init(&port->port_lock); + + INIT_LIST_HEAD(&port->read_pool); + INIT_LIST_HEAD(&port->read_queue); + INIT_WORK(&port->push, gsmd_rx_push); + + INIT_LIST_HEAD(&port->write_pool); + INIT_WORK(&port->pull, gsmd_tx_pull); + + INIT_WORK(&port->connect_work, gsmd_connect_work); + init_waitqueue_head(&port->pi->wait); + + smd_ports[portno].port = port; + + pr_debug("%s: port:%p portno:%d\n", __func__, port, portno); + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +static ssize_t debug_smd_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct gsmd_port *port; + char *buf; + unsigned long flags; + int temp = 0; + int i; + int ret; + + buf = kzalloc(sizeof(char) * 512, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < n_smd_ports; i++) { + port = smd_ports[i].port; + spin_lock_irqsave(&port->port_lock, flags); + temp += scnprintf(buf + temp, 512 - temp, + "###PORT:%d###\n" + "nbytes_tolaptop: %lu\n" + "nbytes_tomodem: %lu\n" + "cbits_to_modem: %u\n" + "cbits_to_laptop: %u\n" + "n_read: %u\n", + i, port->nbytes_tolaptop, port->nbytes_tomodem, + port->cbits_to_modem, port->cbits_to_laptop, + port->n_read); + spin_unlock_irqrestore(&port->port_lock, flags); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; + +} + +static ssize_t debug_smd_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct gsmd_port *port; + unsigned long flags; + int i; + + for (i = 0; i < n_smd_ports; i++) { + port = smd_ports[i].port; + + spin_lock_irqsave(&port->port_lock, flags); + port->nbytes_tolaptop = 0; + port->nbytes_tomodem = 0; + spin_unlock_irqrestore(&port->port_lock, flags); + } + + return count; +} + +static int debug_smd_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations debug_gsmd_ops = { + .open = debug_smd_open, + .read = debug_smd_read_stats, + .write = debug_smd_reset_stats, +}; + +static void gsmd_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("usb_gsmd", 0); + if (IS_ERR(dent)) + return; + + debugfs_create_file("status", 0444, dent, 0, &debug_gsmd_ops); +} +#else +static void gsmd_debugfs_init(void) {} +#endif + +int gsmd_setup(struct usb_gadget *g, unsigned count) +{ + struct usb_cdc_line_coding coding; + int ret; + int i; + + pr_debug("%s: g:%p count: %d\n", __func__, g, count); + + if (!count || count > SMD_N_PORTS) { + pr_err("%s: Invalid num of ports count:%d gadget:%p\n", + __func__, count, g); + return -EINVAL; + } + + coding.dwDTERate = cpu_to_le32(9600); + coding.bCharFormat = 8; + coding.bParityType = USB_CDC_NO_PARITY; + coding.bDataBits = USB_CDC_1_STOP_BITS; + + gsmd_wq = create_singlethread_workqueue("k_gsmd"); + if (!gsmd_wq) { + pr_err("%s: Unable to create workqueue gsmd_wq\n", + __func__); + return -ENOMEM; + } + + for (i = 0; i < count; i++) { + mutex_init(&smd_ports[i].lock); + ret = gsmd_port_alloc(i, &coding); + if (ret) { + pr_err("%s: Unable to alloc port:%d\n", __func__, i); + goto free_smd_ports; + } + n_smd_ports++; + } + + gsmd_debugfs_init(); + + return 0; +free_smd_ports: + for (i = 0; i < n_smd_ports; i++) + gsmd_port_free(i); + + destroy_workqueue(gsmd_wq); + + return ret; +} + +void gsmd_cleanup(struct usb_gadget *g, unsigned count) +{ + /* TBD */ +} diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index ab085f12d57..4447b0fcecb 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -59,6 +59,19 @@ config USB_EHCI_HCD To compile this driver as a module, choose M here: the module will be called ehci-hcd. +config USB_EHCI_EHSET + bool "Embedded High-speed Host Electrical Test Support" + depends on USB_EHCI_HCD + ---help--- + This option is required for EHSET Host Compliance Tests support on an + embedded Hi-speed USB Host or OTG port. + + This enables the software support for the "Single Step Set Featue" test. + Apart from this test, other EHSET tests TEST_SE0/J/K/PACKET are part + of EHCI specification and their support already exists in the EHCI driver. + + If unsure, say N. + config USB_EHCI_ROOT_HUB_TT bool "Root Hub Transaction Translators" depends on USB_EHCI_HCD @@ -230,6 +243,22 @@ config USB_OXU210HP_HCD To compile this driver as a module, choose M here: the module will be called oxu210hp-hcd. +config USB_EHCI_MSM_72K + bool "Support for Legacy Qualcomm on-chip EHCI USB controller" + depends on USB_EHCI_HCD && USB_MSM_OTG_72K && ARCH_MSM + ---help--- + This driver enables support for USB host controller + in pre 8660 qualcomm chipsets(8660, 7X30, 8X50 and 7X27). + +config USB_FS_HOST + bool "Support for Full Speed Host Mode" + depends on USB_EHCI_MSM_72K && ARCH_QSD8X50 + default n + ---help--- + Enables support for the full speed USB controller core present + on the Qualcomm chipsets + + config USB_ISP116X_HCD tristate "ISP116X HCD support" depends on USB @@ -528,6 +557,15 @@ config USB_WHCI_HCD To compile this driver a module, choose M here: the module will be called "whci-hcd". +config USB_PEHCI_HCD + tristate "ST-E ISP1763A Host Controller" + depends on USB + help + Driver for ST-E isp1763A USB Host 2.0 Controllers. + + To compile this driver a module, choose M here: the module + will be called "pehci". + config USB_HWA_HCD tristate "Host Wire Adapter (HWA) driver (EXPERIMENTAL)" depends on EXPERIMENTAL diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 624a362f2fe..6f5b0e12daf 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -15,6 +15,7 @@ xhci-hcd-y := xhci.o xhci-mem.o xhci-pci.o xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o obj-$(CONFIG_USB_WHCI_HCD) += whci/ +obj-$(CONFIG_USB_PEHCI_HCD) += pehci/ obj-$(CONFIG_PCI) += pci-quirks.o diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 40a844c1dbb..dd67cad1351 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -39,7 +39,7 @@ * (host controller _Structural_ parameters) * see EHCI spec, Table 2-4 for each value */ -static void dbg_hcs_params (struct ehci_hcd *ehci, char *label) +static void __maybe_unused dbg_hcs_params (struct ehci_hcd *ehci, char *label) { u32 params = ehci_readl(ehci, &ehci->caps->hcs_params); @@ -83,7 +83,7 @@ static inline void dbg_hcs_params (struct ehci_hcd *ehci, char *label) {} * (host controller _Capability_ parameters) * see EHCI Spec, Table 2-5 for each value * */ -static void dbg_hcc_params (struct ehci_hcd *ehci, char *label) +static void __maybe_unused dbg_hcc_params (struct ehci_hcd *ehci, char *label) { u32 params = ehci_readl(ehci, &ehci->caps->hcc_params); diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 9ff9abc7e3a..8e6ef28075d 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -451,7 +451,7 @@ static void ehci_shutdown(struct usb_hcd *hcd) spin_unlock_irq(&ehci->lock); } -static void ehci_port_power (struct ehci_hcd *ehci, int is_on) +static void __maybe_unused ehci_port_power (struct ehci_hcd *ehci, int is_on) { unsigned port; @@ -667,7 +667,7 @@ static int ehci_init(struct usb_hcd *hcd) } /* start HC running; it's halted, ehci_init() has been run (once) */ -static int ehci_run (struct usb_hcd *hcd) +static int __maybe_unused ehci_run (struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); int retval; @@ -1115,7 +1115,7 @@ ehci_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep) spin_unlock_irqrestore (&ehci->lock, flags); } -static void +static void __maybe_unused ehci_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -1254,6 +1254,11 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER spear_ehci_hcd_driver #endif +#ifdef CONFIG_USB_EHCI_MSM_72K +#include "ehci-msm72k.c" +#define PLATFORM_DRIVER ehci_msm_driver +#endif + #ifdef CONFIG_USB_EHCI_MSM #include "ehci-msm.c" #define PLATFORM_DRIVER ehci_msm_driver diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 88cfb8fadb7..15cac20c62d 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -734,6 +734,151 @@ ehci_hub_descriptor ( desc->wHubCharacteristics = cpu_to_le16(temp); } +/*-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_EHCI_EHSET + +#define EHSET_TEST_SINGLE_STEP_SET_FEATURE 0x06 + +static void usb_ehset_completion(struct urb *urb) +{ + struct completion *done = urb->context; + + complete(done); +} +static int submit_single_step_set_feature( + struct usb_hcd *hcd, + struct urb *urb, + int is_setup +); + +/* Allocate a URB and initialize the various fields of it. + * This API is used by the single_step_set_feature test of + * EHSET where IN packet of the GetDescriptor request is + * sent after 15secs of the SETUP packet. + * Return NULL if failed. + */ +static struct urb * +request_single_step_set_feature_urb( + struct usb_device *udev, + void *dr, + void *buf, + struct completion *done +) { + struct urb *urb; + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct usb_host_endpoint *ep; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return NULL; + + urb->pipe = usb_rcvctrlpipe(udev, 0); + ep = (usb_pipein(urb->pipe) ? udev->ep_in : udev->ep_out) + [usb_pipeendpoint(urb->pipe)]; + if (!ep) { + usb_free_urb(urb); + return NULL; + } + + /* Initialize the various URB fields as these are used + * by the HCD driver to queue it and as well as + * when completion happens. + */ + urb->ep = ep; + urb->dev = udev; + urb->setup_packet = (void *)dr; + urb->transfer_buffer = buf; + urb->transfer_buffer_length = USB_DT_DEVICE_SIZE; + urb->complete = usb_ehset_completion; + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urb->transfer_flags = (urb->transfer_flags & ~URB_DIR_MASK) + | URB_DIR_IN ; + usb_get_urb(urb); + atomic_inc(&urb->use_count); + atomic_inc(&urb->dev->urbnum); + urb->setup_dma = dma_map_single( + hcd->self.controller, + urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + urb->transfer_dma = dma_map_single( + hcd->self.controller, + urb->transfer_buffer, + urb->transfer_buffer_length, + DMA_FROM_DEVICE); + urb->context = done; + return urb; +} + +static int ehset_single_step_set_feature(struct usb_hcd *hcd, int port) +{ + int retval = -ENOMEM; + struct usb_ctrlrequest *dr; + struct urb *urb; + struct usb_device *udev ; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct usb_device_descriptor *buf; + DECLARE_COMPLETION_ONSTACK(done); + + /*Obtain udev of the rhub's child port */ + udev = hcd->self.root_hub->children[port]; + if (!udev) { + ehci_err(ehci, "No device attached to the RootHub\n"); + return -ENODEV; + } + buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!dr) { + kfree(buf); + return -ENOMEM; + } + + /* Fill Setup packet for GetDescriptor */ + dr->bRequestType = USB_DIR_IN; + dr->bRequest = USB_REQ_GET_DESCRIPTOR; + dr->wValue = cpu_to_le16(USB_DT_DEVICE << 8); + dr->wIndex = 0; + dr->wLength = cpu_to_le16(USB_DT_DEVICE_SIZE); + urb = request_single_step_set_feature_urb(udev, dr, buf, &done); + if (!urb) + goto cleanup; + + /* Now complete just the SETUP stage */ + retval = submit_single_step_set_feature(hcd, urb, 1); + if (retval) + goto out1; + if (!wait_for_completion_timeout(&done, msecs_to_jiffies(2000))) { + usb_kill_urb(urb); + retval = -ETIMEDOUT; + ehci_err(ehci, "%s SETUP stage timed out on ep0\n", __func__); + goto out1; + } + msleep(15 * 1000); + /* Complete remaining DATA and status stages */ + /* No need to free the URB, we can reuse the same */ + urb->status = -EINPROGRESS; + usb_get_urb(urb); + atomic_inc(&urb->use_count); + atomic_inc(&urb->dev->urbnum); + retval = submit_single_step_set_feature(hcd, urb, 0); + if (!retval && !wait_for_completion_timeout(&done, + msecs_to_jiffies(2000))) { + usb_kill_urb(urb); + retval = -ETIMEDOUT; + ehci_err(ehci, "%s IN stage timed out on ep0\n", __func__); + } +out1: + usb_free_urb(urb); +cleanup: + kfree(dr); + kfree(buf); + return retval; +} +#endif /*-------------------------------------------------------------------------*/ static int ehci_hub_control ( @@ -1056,6 +1201,16 @@ static int ehci_hub_control ( || (temp & PORT_RESET) != 0) goto error; + ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); +#ifdef CONFIG_USB_OTG + if (hcd->self.otg_port == (wIndex + 1) && + hcd->self.b_hnp_enable && + ehci->start_hnp) { + set_bit(wIndex, &ehci->suspended_ports); + ehci->start_hnp(ehci); + break; + } +#endif /* After above check the port must be connected. * Set appropriate bit thus could put phy into low power * mode if we have hostpc feature @@ -1118,12 +1273,23 @@ static int ehci_hub_control ( * about the EHCI-specific stuff. */ case USB_PORT_FEAT_TEST: - if (!selector || selector > 5) + if (selector && selector <= 5) { + ehci_quiesce(ehci); + ehci_halt(ehci); + temp |= selector << 16; + ehci_writel(ehci, temp, status_reg); + } +#ifdef CONFIG_USB_EHCI_EHSET + else if (selector + == EHSET_TEST_SINGLE_STEP_SET_FEATURE) { + spin_unlock_irqrestore(&ehci->lock, flags); + retval = ehset_single_step_set_feature(hcd, + wIndex); + spin_lock_irqsave(&ehci->lock, flags); + } +#endif + else goto error; - ehci_quiesce(ehci); - ehci_halt(ehci); - temp |= selector << 16; - ehci_writel(ehci, temp, status_reg); break; default: @@ -1151,7 +1317,7 @@ static void ehci_relinquish_port(struct usb_hcd *hcd, int portnum) set_owner(ehci, --portnum, PORT_OWNER); } -static int ehci_port_handed_over(struct usb_hcd *hcd, int portnum) +static int __maybe_unused ehci_port_handed_over(struct usb_hcd *hcd, int portnum) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); u32 __iomem *reg; diff --git a/drivers/usb/host/ehci-msm.c b/drivers/usb/host/ehci-msm.c index b5a0bf649c9..411fa977957 100644 --- a/drivers/usb/host/ehci-msm.c +++ b/drivers/usb/host/ehci-msm.c @@ -220,6 +220,9 @@ static int ehci_msm_pm_suspend(struct device *dev) dev_dbg(dev, "ehci-msm PM suspend\n"); + if (!hcd->rh_registered) + return 0; + /* * EHCI helper function has also the same check before manipulating * port wakeup flags. We do check here the same condition before @@ -241,6 +244,10 @@ static int ehci_msm_pm_resume(struct device *dev) struct usb_hcd *hcd = dev_get_drvdata(dev); dev_dbg(dev, "ehci-msm PM resume\n"); + + if (!hcd->rh_registered) + return 0; + ehci_prepare_ports_for_controller_resume(hcd_to_ehci(hcd)); return 0; diff --git a/drivers/usb/host/ehci-msm72k.c b/drivers/usb/host/ehci-msm72k.c new file mode 100644 index 00000000000..e550e2b54ca --- /dev/null +++ b/drivers/usb/host/ehci-msm72k.c @@ -0,0 +1,823 @@ +/* ehci-msm.c - HSUSB Host Controller Driver Implementation + * + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * + * Partly derived from ehci-fsl.c and ehci-hcd.c + * Copyright (c) 2000-2004 by David Brownell + * Copyright (c) 2005 MontaVista Software + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MSM_USB_BASE (hcd->regs) + +struct msmusb_hcd { + struct ehci_hcd ehci; + struct clk *clk; + struct clk *pclk; + unsigned in_lpm; + struct work_struct lpm_exit_work; + spinlock_t lock; + struct wake_lock wlock; + unsigned int clk_enabled; + struct msm_usb_host_platform_data *pdata; + unsigned running; + struct otg_transceiver *xceiv; + struct work_struct otg_work; + unsigned flags; + struct msm_otg_ops otg_ops; +}; + +static inline struct msmusb_hcd *hcd_to_mhcd(struct usb_hcd *hcd) +{ + return (struct msmusb_hcd *) (hcd->hcd_priv); +} + +static inline struct usb_hcd *mhcd_to_hcd(struct msmusb_hcd *mhcd) +{ + return container_of((void *) mhcd, struct usb_hcd, hcd_priv); +} + +static void msm_xusb_pm_qos_update(struct msmusb_hcd *mhcd, int vote) +{ + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + + /* if otg driver is available, it would take + * care of voting for appropriate pclk source + */ + if (mhcd->xceiv) + return; + + if (vote) + clk_enable(pdata->ebi1_clk); + else + clk_disable(pdata->ebi1_clk); +} + +static void msm_xusb_enable_clks(struct msmusb_hcd *mhcd) +{ + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + + if (mhcd->clk_enabled) + return; + + switch (PHY_TYPE(pdata->phy_info)) { + case USB_PHY_INTEGRATED: + /* OTG driver takes care of clock management */ + break; + case USB_PHY_SERIAL_PMIC: + clk_enable(mhcd->clk); + clk_enable(mhcd->pclk); + break; + default: + pr_err("%s: undefined phy type ( %X ) \n", __func__, + pdata->phy_info); + return; + } + mhcd->clk_enabled = 1; +} + +static void msm_xusb_disable_clks(struct msmusb_hcd *mhcd) +{ + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + + if (!mhcd->clk_enabled) + return; + + switch (PHY_TYPE(pdata->phy_info)) { + case USB_PHY_INTEGRATED: + /* OTG driver takes care of clock management */ + break; + case USB_PHY_SERIAL_PMIC: + clk_disable(mhcd->clk); + clk_disable(mhcd->pclk); + break; + default: + pr_err("%s: undefined phy type ( %X ) \n", __func__, + pdata->phy_info); + return; + } + mhcd->clk_enabled = 0; + +} + +static int usb_wakeup_phy(struct usb_hcd *hcd) +{ + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + int ret = -ENODEV; + + switch (PHY_TYPE(pdata->phy_info)) { + case USB_PHY_INTEGRATED: + break; + case USB_PHY_SERIAL_PMIC: + ret = msm_fsusb_resume_phy(); + break; + default: + pr_err("%s: undefined phy type ( %X ) \n", __func__, + pdata->phy_info); + } + + return ret; +} + +#ifdef CONFIG_PM +static int usb_suspend_phy(struct usb_hcd *hcd) +{ + int ret = 0; + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + + switch (PHY_TYPE(pdata->phy_info)) { + case USB_PHY_INTEGRATED: + break; + case USB_PHY_SERIAL_PMIC: + ret = msm_fsusb_set_remote_wakeup(); + ret = msm_fsusb_suspend_phy(); + break; + default: + pr_err("%s: undefined phy type ( %X ) \n", __func__, + pdata->phy_info); + ret = -ENODEV; + break; + } + + return ret; +} + +static int usb_lpm_enter(struct usb_hcd *hcd) +{ + struct device *dev = container_of((void *)hcd, struct device, + platform_data); + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + + disable_irq(hcd->irq); + if (mhcd->in_lpm) { + pr_info("%s: already in lpm. nothing to do\n", __func__); + enable_irq(hcd->irq); + return 0; + } + + if (HC_IS_RUNNING(hcd->state)) { + pr_info("%s: can't enter into lpm. controller is runnning\n", + __func__); + enable_irq(hcd->irq); + return -1; + } + + pr_info("%s: lpm enter procedure started\n", __func__); + + mhcd->in_lpm = 1; + + if (usb_suspend_phy(hcd)) { + mhcd->in_lpm = 0; + enable_irq(hcd->irq); + pr_info("phy suspend failed\n"); + pr_info("%s: lpm enter procedure end\n", __func__); + return -1; + } + + msm_xusb_disable_clks(mhcd); + + if (mhcd->xceiv && mhcd->xceiv->set_suspend) + mhcd->xceiv->set_suspend(mhcd->xceiv, 1); + + if (device_may_wakeup(dev)) + enable_irq_wake(hcd->irq); + enable_irq(hcd->irq); + pr_info("%s: lpm enter procedure end\n", __func__); + return 0; +} +#endif + +void usb_lpm_exit_w(struct work_struct *work) +{ + struct msmusb_hcd *mhcd = container_of((void *) work, + struct msmusb_hcd, lpm_exit_work); + + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + + struct device *dev = container_of((void *)hcd, struct device, + platform_data); + msm_xusb_enable_clks(mhcd); + + + if (usb_wakeup_phy(hcd)) { + pr_err("fatal error: cannot bring phy out of lpm\n"); + return; + } + + /* If resume signalling finishes before lpm exit, PCD is not set in + * USBSTS register. Drive resume signal to the downstream device now + * so that EHCI can process the upcoming port change interrupt.*/ + + writel(readl(USB_PORTSC) | PORTSC_FPR, USB_PORTSC); + + if (mhcd->xceiv && mhcd->xceiv->set_suspend) + mhcd->xceiv->set_suspend(mhcd->xceiv, 0); + + if (device_may_wakeup(dev)) + disable_irq_wake(hcd->irq); + enable_irq(hcd->irq); +} + +static void usb_lpm_exit(struct usb_hcd *hcd) +{ + unsigned long flags; + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + + spin_lock_irqsave(&mhcd->lock, flags); + if (!mhcd->in_lpm) { + spin_unlock_irqrestore(&mhcd->lock, flags); + return; + } + mhcd->in_lpm = 0; + disable_irq_nosync(hcd->irq); + schedule_work(&mhcd->lpm_exit_work); + spin_unlock_irqrestore(&mhcd->lock, flags); +} + +static irqreturn_t ehci_msm_irq(struct usb_hcd *hcd) +{ + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + struct msm_otg *otg = container_of(mhcd->xceiv, struct msm_otg, otg); + + /* + * OTG scheduled a work to get Integrated PHY out of LPM, + * WAIT till then */ + if (PHY_TYPE(mhcd->pdata->phy_info) == USB_PHY_INTEGRATED) + if (atomic_read(&otg->in_lpm)) + return IRQ_HANDLED; + + return ehci_irq(hcd); +} + +#ifdef CONFIG_PM + +static int ehci_msm_bus_suspend(struct usb_hcd *hcd) +{ + int ret; + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + struct device *dev = hcd->self.controller; + + ret = ehci_bus_suspend(hcd); + if (ret) { + pr_err("ehci_bus suspend faield\n"); + return ret; + } + if (PHY_TYPE(mhcd->pdata->phy_info) == USB_PHY_INTEGRATED) + ret = otg_set_suspend(mhcd->xceiv, 1); + else + ret = usb_lpm_enter(hcd); + + pm_runtime_put_noidle(dev); + pm_runtime_suspend(dev); + wake_unlock(&mhcd->wlock); + return ret; +} + +static int ehci_msm_bus_resume(struct usb_hcd *hcd) +{ + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + struct device *dev = hcd->self.controller; + + wake_lock(&mhcd->wlock); + pm_runtime_get_noresume(dev); + pm_runtime_resume(dev); + + if (PHY_TYPE(mhcd->pdata->phy_info) == USB_PHY_INTEGRATED) { + otg_set_suspend(mhcd->xceiv, 0); + } else { /* PMIC serial phy */ + usb_lpm_exit(hcd); + if (cancel_work_sync(&(mhcd->lpm_exit_work))) + usb_lpm_exit_w(&mhcd->lpm_exit_work); + } + + return ehci_bus_resume(hcd); + +} + +#else + +#define ehci_msm_bus_suspend NULL +#define ehci_msm_bus_resume NULL + +#endif /* CONFIG_PM */ + +static int ehci_msm_reset(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + ehci->caps = USB_CAPLENGTH; + ehci->regs = USB_CAPLENGTH + + HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + + /* cache the data to minimize the chip reads*/ + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + + retval = ehci_init(hcd); + if (retval) + return retval; + + hcd->has_tt = 1; + ehci->sbrn = HCD_USB2; + + retval = ehci_reset(ehci); + + /* SW workaround for USB stability issues*/ + writel(0x0, USB_AHB_MODE); + writel(0x0, USB_AHB_BURST); + + return retval; +} + +#define PTS_VAL(x) (PHY_TYPE(x) == USB_PHY_SERIAL_PMIC) ? PORTSC_PTS_SERIAL : \ + PORTSC_PTS_ULPI + +static int ehci_msm_run(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + int retval = 0; + int port = HCS_N_PORTS(ehci->hcs_params); + u32 __iomem *reg_ptr; + u32 hcc_params; + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + + hcd->uses_new_polling = 1; + set_bit(HCD_FLAG_POLL_RH, &hcd->flags); + + /* set hostmode */ + reg_ptr = (u32 __iomem *)(((u8 __iomem *)ehci->regs) + USBMODE); + ehci_writel(ehci, (USBMODE_VBUS | USBMODE_SDIS), reg_ptr); + + /* port configuration - phy, port speed, port power, port enable */ + while (port--) + ehci_writel(ehci, (PTS_VAL(pdata->phy_info) | PORT_POWER | + PORT_PE), &ehci->regs->port_status[port]); + + ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); + ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); + + hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params); + if (HCC_64BIT_ADDR(hcc_params)) + ehci_writel(ehci, 0, &ehci->regs->segment); + + ehci->command &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); + ehci->command |= CMD_RUN; + ehci_writel(ehci, ehci->command, &ehci->regs->command); + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ + + hcd->state = HC_STATE_RUNNING; + + /*Enable appropriate Interrupts*/ + ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); + + return retval; +} + +static struct hc_driver msm_hc_driver = { + .description = hcd_name, + .product_desc = "Qualcomm On-Chip EHCI Host Controller", + .hcd_priv_size = sizeof(struct msmusb_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_msm_irq, + .flags = HCD_USB2, + + .reset = ehci_msm_reset, + .start = ehci_msm_run, + + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_msm_bus_suspend, + .bus_resume = ehci_msm_bus_resume, + .relinquish_port = ehci_relinquish_port, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static void msm_hsusb_request_host(void *handle, int request) +{ + struct msmusb_hcd *mhcd = handle; + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + struct msm_otg *otg = container_of(mhcd->xceiv, struct msm_otg, otg); +#ifdef CONFIG_USB_OTG + struct usb_device *udev = hcd->self.root_hub; +#endif + struct device *dev = hcd->self.controller; + + switch (request) { +#ifdef CONFIG_USB_OTG + case REQUEST_HNP_SUSPEND: + /* disable Root hub auto suspend. As hardware is configured + * for peripheral mode, mark hardware is not available. + */ + if (PHY_TYPE(pdata->phy_info) == USB_PHY_INTEGRATED) { + pm_runtime_disable(&udev->dev); + /* Mark root hub as disconnected. This would + * protect suspend/resume via sysfs. + */ + udev->state = USB_STATE_NOTATTACHED; + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + hcd->state = HC_STATE_HALT; + pm_runtime_put_noidle(dev); + pm_runtime_suspend(dev); + } + break; + case REQUEST_HNP_RESUME: + if (PHY_TYPE(pdata->phy_info) == USB_PHY_INTEGRATED) { + pm_runtime_get_noresume(dev); + pm_runtime_resume(dev); + disable_irq(hcd->irq); + ehci_msm_reset(hcd); + ehci_msm_run(hcd); + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + pm_runtime_enable(&udev->dev); + udev->state = USB_STATE_CONFIGURED; + enable_irq(hcd->irq); + } + break; +#endif + case REQUEST_RESUME: + usb_hcd_resume_root_hub(hcd); + break; + case REQUEST_START: + if (mhcd->running) + break; + pm_runtime_get_noresume(dev); + pm_runtime_resume(dev); + wake_lock(&mhcd->wlock); + msm_xusb_pm_qos_update(mhcd, 1); + msm_xusb_enable_clks(mhcd); + if (PHY_TYPE(pdata->phy_info) == USB_PHY_INTEGRATED) + if (otg->set_clk) + otg->set_clk(mhcd->xceiv, 1); + if (pdata->vbus_power) + pdata->vbus_power(pdata->phy_info, 1); + if (pdata->config_gpio) + pdata->config_gpio(1); + usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); + mhcd->running = 1; + if (PHY_TYPE(pdata->phy_info) == USB_PHY_INTEGRATED) + if (otg->set_clk) + otg->set_clk(mhcd->xceiv, 0); + break; + case REQUEST_STOP: + if (!mhcd->running) + break; + mhcd->running = 0; + /* come out of lpm before deregistration */ + if (PHY_TYPE(pdata->phy_info) == USB_PHY_SERIAL_PMIC) { + usb_lpm_exit(hcd); + if (cancel_work_sync(&(mhcd->lpm_exit_work))) + usb_lpm_exit_w(&mhcd->lpm_exit_work); + } + usb_remove_hcd(hcd); + if (pdata->config_gpio) + pdata->config_gpio(0); + if (pdata->vbus_power) + pdata->vbus_power(pdata->phy_info, 0); + msm_xusb_disable_clks(mhcd); + wake_lock_timeout(&mhcd->wlock, HZ/2); + msm_xusb_pm_qos_update(mhcd, 0); + pm_runtime_put_noidle(dev); + pm_runtime_suspend(dev); + break; + } +} + +static void msm_hsusb_otg_work(struct work_struct *work) +{ + struct msmusb_hcd *mhcd; + + mhcd = container_of(work, struct msmusb_hcd, otg_work); + msm_hsusb_request_host((void *)mhcd, mhcd->flags); +} +static void msm_hsusb_start_host(struct usb_bus *bus, int start) +{ + struct usb_hcd *hcd = bus_to_hcd(bus); + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + + mhcd->flags = start; + if (in_interrupt()) + schedule_work(&mhcd->otg_work); + else + msm_hsusb_request_host((void *)mhcd, mhcd->flags); + +} + +static int msm_xusb_init_phy(struct msmusb_hcd *mhcd) +{ + int ret = -ENODEV; + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + + switch (PHY_TYPE(pdata->phy_info)) { + case USB_PHY_INTEGRATED: + ret = 0; + case USB_PHY_SERIAL_PMIC: + msm_xusb_enable_clks(mhcd); + writel(0, USB_USBINTR); + ret = msm_fsusb_rpc_init(&mhcd->otg_ops); + if (!ret) + msm_fsusb_init_phy(); + msm_xusb_disable_clks(mhcd); + break; + default: + pr_err("%s: undefined phy type ( %X ) \n", __func__, + pdata->phy_info); + } + + return ret; +} + +static int msm_xusb_rpc_close(struct msmusb_hcd *mhcd) +{ + int retval = -ENODEV; + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + + switch (PHY_TYPE(pdata->phy_info)) { + case USB_PHY_INTEGRATED: + if (!mhcd->xceiv) + retval = msm_hsusb_rpc_close(); + break; + case USB_PHY_SERIAL_PMIC: + retval = msm_fsusb_reset_phy(); + msm_fsusb_rpc_deinit(); + break; + default: + pr_err("%s: undefined phy type ( %X ) \n", __func__, + pdata->phy_info); + } + return retval; +} + +#ifdef CONFIG_USB_OTG +static void ehci_msm_start_hnp(struct ehci_hcd *ehci) +{ + struct usb_hcd *hcd = ehci_to_hcd(ehci); + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + + /* OTG driver handles HNP */ + otg_start_hnp(mhcd->xceiv); +} +#else +#define ehci_msm_start_hnp NULL +#endif + +static int msm_xusb_init_host(struct msmusb_hcd *mhcd) +{ + int ret = 0; + struct msm_otg *otg; + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + + switch (PHY_TYPE(pdata->phy_info)) { + case USB_PHY_INTEGRATED: + msm_hsusb_rpc_connect(); + + if (pdata->vbus_init) + pdata->vbus_init(1); + + /* VBUS might be present. Turn off vbus */ + if (pdata->vbus_power) + pdata->vbus_power(pdata->phy_info, 0); + + INIT_WORK(&mhcd->otg_work, msm_hsusb_otg_work); + mhcd->xceiv = otg_get_transceiver(); + if (!mhcd->xceiv) + return -ENODEV; + otg = container_of(mhcd->xceiv, struct msm_otg, otg); + hcd->regs = otg->regs; + otg->start_host = msm_hsusb_start_host; + ehci->start_hnp = ehci_msm_start_hnp; + + ret = otg_set_host(mhcd->xceiv, &hcd->self); + break; + case USB_PHY_SERIAL_PMIC: + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + + if (!hcd->regs) + return -EFAULT; + /* get usb clocks */ + mhcd->clk = clk_get(NULL, "usb_hs2_clk"); + if (IS_ERR(mhcd->clk)) { + iounmap(hcd->regs); + return PTR_ERR(mhcd->clk); + } + + mhcd->pclk = clk_get(NULL, "usb_hs2_pclk"); + if (IS_ERR(mhcd->pclk)) { + iounmap(hcd->regs); + clk_put(mhcd->clk); + return PTR_ERR(mhcd->pclk); + } + mhcd->otg_ops.request = msm_hsusb_request_host; + mhcd->otg_ops.handle = (void *) mhcd; + ret = msm_xusb_init_phy(mhcd); + if (ret < 0) { + iounmap(hcd->regs); + clk_put(mhcd->clk); + clk_put(mhcd->pclk); + } + break; + default: + pr_err("phy type is bad\n"); + } + return ret; +} + +static int __devinit ehci_msm_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + struct resource *res; + struct msm_usb_host_platform_data *pdata; + int retval; + struct msmusb_hcd *mhcd; + + hcd = usb_create_hcd(&msm_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) + return -ENOMEM; + + hcd->irq = platform_get_irq(pdev, 0); + if (hcd->irq < 0) { + usb_put_hcd(hcd); + return hcd->irq; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + usb_put_hcd(hcd); + return -ENODEV; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + mhcd = hcd_to_mhcd(hcd); + spin_lock_init(&mhcd->lock); + mhcd->in_lpm = 0; + mhcd->running = 0; + device_init_wakeup(&pdev->dev, 1); + + pdata = pdev->dev.platform_data; + if (PHY_TYPE(pdata->phy_info) == USB_PHY_UNDEFINED) { + usb_put_hcd(hcd); + return -ENODEV; + } + hcd->power_budget = pdata->power_budget; + mhcd->pdata = pdata; + INIT_WORK(&mhcd->lpm_exit_work, usb_lpm_exit_w); + + wake_lock_init(&mhcd->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev)); + pdata->ebi1_clk = clk_get(NULL, "ebi1_usb_clk"); + if (IS_ERR(pdata->ebi1_clk)) + pdata->ebi1_clk = NULL; + else + clk_set_rate(pdata->ebi1_clk, INT_MAX); + + retval = msm_xusb_init_host(mhcd); + + if (retval < 0) { + wake_lock_destroy(&mhcd->wlock); + usb_put_hcd(hcd); + clk_put(pdata->ebi1_clk); + } + + pm_runtime_enable(&pdev->dev); + + return retval; +} + +static void msm_xusb_uninit_host(struct msmusb_hcd *mhcd) +{ + struct usb_hcd *hcd = mhcd_to_hcd(mhcd); + struct msm_usb_host_platform_data *pdata = mhcd->pdata; + + switch (PHY_TYPE(pdata->phy_info)) { + case USB_PHY_INTEGRATED: + if (pdata->vbus_init) + pdata->vbus_init(0); + otg_set_host(mhcd->xceiv, NULL); + otg_put_transceiver(mhcd->xceiv); + cancel_work_sync(&mhcd->otg_work); + break; + case USB_PHY_SERIAL_PMIC: + iounmap(hcd->regs); + clk_put(mhcd->clk); + clk_put(mhcd->pclk); + msm_fsusb_reset_phy(); + msm_fsusb_rpc_deinit(); + break; + default: + pr_err("phy type is bad\n"); + } +} +static int __exit ehci_msm_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); + struct msm_usb_host_platform_data *pdata; + int retval = 0; + + pdata = pdev->dev.platform_data; + device_init_wakeup(&pdev->dev, 0); + + msm_hsusb_request_host((void *)mhcd, REQUEST_STOP); + msm_xusb_uninit_host(mhcd); + retval = msm_xusb_rpc_close(mhcd); + + wake_lock_destroy(&mhcd->wlock); + usb_put_hcd(hcd); + clk_put(pdata->ebi1_clk); + + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + + return retval; +} + +static int ehci_msm_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int ehci_msm_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static int ehci_msm_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: idling...\n"); + return 0; +} + +static const struct dev_pm_ops ehci_msm_dev_pm_ops = { + .runtime_suspend = ehci_msm_runtime_suspend, + .runtime_resume = ehci_msm_runtime_resume, + .runtime_idle = ehci_msm_runtime_idle +}; + +static struct platform_driver ehci_msm_driver = { + .probe = ehci_msm_probe, + .remove = __exit_p(ehci_msm_remove), + .driver = {.name = "msm_hsusb_host", + .pm = &ehci_msm_dev_pm_ops, }, +}; diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 0917e3a3246..a5bb387e3de 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1144,7 +1144,110 @@ submit_async ( qtd_list_free (ehci, urb, qtd_list); return rc; } +/*-------------------------------------------------------------------------*/ +/* This function creates the qtds and submits them for the + * SINGLE_STEP_SET_FEATURE Test. + * This is done in two parts: first SETUP req for GetDesc is sent then + * 15 seconds later, the IN stage for GetDesc starts to req data from dev + * + * is_setup : i/p arguement decides which of the two stage needs to be + * performed; TRUE - SETUP and FALSE - IN+STATUS + * Returns 0 if success + */ +#ifdef CONFIG_USB_EHCI_EHSET +static int +submit_single_step_set_feature( + struct usb_hcd *hcd, + struct urb *urb, + int is_setup +) { + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct list_head qtd_list; + struct list_head *head ; + + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, maxpacket; + u32 token; + + INIT_LIST_HEAD(&qtd_list); + head = &qtd_list; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = ehci_qtd_alloc(ehci, GFP_KERNEL); + if (unlikely(!qtd)) + return -1; + list_add_tail(&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + len = urb->transfer_buffer_length; + /* Check if the request is to perform just the SETUP stage (getDesc) + * as in SINGLE_STEP_SET_FEATURE test, DATA stage (IN) happens + * 15 secs after the setup + */ + if (is_setup) { + /* SETUP pid */ + qtd_fill(ehci, qtd, urb->setup_dma, + sizeof(struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); + + submit_async(ehci, urb, &qtd_list, GFP_ATOMIC); + return 0; /*Return now; we shall come back after 15 seconds*/ + } + + /*--------------------------------------------------------------------- + * IN: data transfer stage: buffer setup : start the IN txn phase for + * the get_Desc SETUP which was sent 15seconds back + */ + token ^= QTD_TOGGLE; /*We need to start IN with DATA-1 Pid-sequence*/ + buf = urb->transfer_dma; + + token |= (1 /* "in" */ << 8); /*This is IN stage*/ + + maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, 0)); + + qtd_fill(ehci, qtd, buf, len, token, maxpacket); + + /* Our IN phase shall always be a short read; so keep the queue running + * and let it advance to the next qtd which zero length OUT status */ + + qtd->hw_alt_next = EHCI_LIST_END(ehci); + + /*---------------------------------------------------------------------- + * STATUS stage for GetDesc control request + */ + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + + qtd_prev = qtd; + qtd = ehci_qtd_alloc(ehci, GFP_ATOMIC); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* dont fill any data in such packets */ + qtd_fill(ehci, qtd, 0, 0, token, 0); + + /* by default, enable interrupt on urb completion */ + if (likely(!(urb->transfer_flags & URB_NO_INTERRUPT))) + qtd->hw_token |= cpu_to_hc32(ehci, QTD_IOC); + + submit_async(ehci, urb, &qtd_list, GFP_KERNEL); + + return 0; + +cleanup: + qtd_list_free(ehci, urb, head); + return -1; +} +#endif /*-------------------------------------------------------------------------*/ /* the async qh for the qtds being reclaimed are now unlinked from the HC */ diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 989e0a8e01f..95802d9a680 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -124,6 +124,8 @@ struct ehci_hcd { /* one per controller */ ktime_t last_periodic_enable; u32 command; + void (*start_hnp)(struct ehci_hcd *ehci); + /* SILICON QUIRKS */ unsigned no_selective_suspend:1; unsigned has_fsl_port_bug:1; /* FreeScale */ diff --git a/drivers/usb/host/pehci/Makefile b/drivers/usb/host/pehci/Makefile new file mode 100644 index 00000000000..8c0d17fedf0 --- /dev/null +++ b/drivers/usb/host/pehci/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the pehci driver (if driver is inside kernel tree). +# + +obj-$(CONFIG_USB_PEHCI_HCD) += hal/ host/ + diff --git a/drivers/usb/host/pehci/hal/Makefile b/drivers/usb/host/pehci/hal/Makefile new file mode 100644 index 00000000000..91408e5393d --- /dev/null +++ b/drivers/usb/host/pehci/hal/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the pehci driver (if driver is inside kernel tree). +# + +obj-$(CONFIG_USB_PEHCI_HCD) += hal_msm.o + diff --git a/drivers/usb/host/pehci/hal/hal_intf.h b/drivers/usb/host/pehci/hal/hal_intf.h new file mode 100644 index 00000000000..2d66e5765bb --- /dev/null +++ b/drivers/usb/host/pehci/hal/hal_intf.h @@ -0,0 +1,313 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux OTG Controller driver : hal +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* This is a hardware abstraction layer header file. +* +* Author : wired support +* +*/ + +#ifndef HAL_INTF_H +#define HAL_INTF_H + + +/* Specify package here instead of including package.h */ +/* #include "package.h" */ +#define HCD_PACKAGE + +#define NON_PCI +//#define PXA300 + +//#define MSEC_INT_BASED +#ifdef MSEC_INT_BASED +#define THREAD_BASED +#endif + +#ifndef DATABUS_WIDTH_16 +#define DATABUS_WIDTH_16 +#endif + +#ifdef DATABUS_WIDTH_16 +/*DMA SUPPORT */ +/* #define ENABLE_PLX_DMA */ +//#undef ENABLE_PLX_DMA//PXA300 +#endif + +//#define EDGE_INTERRUPT +//#define POL_HIGH_INTERRUPT + +#define DMA_BUF_SIZE (4096 * 2) + +#define ISP1763_CHIPID 0x176320 + +/* Values for id_flags filed of isp1763_driver_t */ +#define ISP1763_HC 0 /* Host Controller Driver */ +#define ISP1763_DC 1 /* Device Controller Driver */ +#define ISP1763_OTG 2 /* Otg Controller Driver */ +#define ISP1763_LAST_DEV (ISP1763_OTG + 1) +#define ISP1763_1ST_DEV (ISP1763_HC) + +#ifdef PXA300 +#define HC_SPARAMS_REG (0x04<<1) /* Structural Parameters Register */ +#define HC_CPARAMS_REG (0x08<<1) /* Capability Parameters Register */ + +#define HC_USBCMD_REG (0x8C<<1) /* USB Command Register */ +#define HC_USBSTS_REG (0x90<<1) /* USB Status Register */ +#define HC_INTERRUPT_REG_EHCI (0x94<<1) /* INterrupt Enable Register */ +#define HC_FRINDEX_REG (0x98<<1) /* Frame Index Register */ + +#define HC_CONFIGFLAG_REG (0x9C<<1) /* Conigured Flag Register */ +#define HC_PORTSC1_REG (0xA0<<1) /* Port Status Control for Port1 */ + +/*ISO Transfer Registers */ +#define HC_ISO_PTD_DONEMAP_REG (0xA4<<1) /* ISO PTD Done Map Register */ +#define HC_ISO_PTD_SKIPMAP_REG (0xA6<<1) /* ISO PTD Skip Map Register */ +#define HC_ISO_PTD_LASTPTD_REG (0xA8<<1) /* ISO PTD Last PTD Register */ + +/*INT Transfer Registers */ +#define HC_INT_PTD_DONEMAP_REG (0xAA<<1) /* INT PTD Done Map Register */ +#define HC_INT_PTD_SKIPMAP_REG (0xAC<<1) /* INT PTD Skip Map Register */ +#define HC_INT_PTD_LASTPTD_REG (0xAE<<1) /* INT PTD Last PTD Register */ + +/*ATL Transfer Registers */ +#define HC_ATL_PTD_DONEMAP_REG (0xB0<<1) /* ATL PTD Last PTD Register */ +#define HC_ATL_PTD_SKIPMAP_REG (0xB2<<1) /* ATL PTD Last PTD Register */ +#define HC_ATL_PTD_LASTPTD_REG (0xB4<<1) /* ATL PTD Last PTD Register */ + +/*General Purpose Registers */ +#define HC_HW_MODE_REG (0x0C<<1) /* H/W Mode Register */ +#define HC_CHIP_ID_REG (0x70<<1) /* Chip ID Register */ +#define HC_SCRATCH_REG (0x78<<1) /* Scratch Register */ +#define HC_RESET_REG (0xB8<<1) /* HC Reset Register */ +#define HC_HWMODECTRL_REG (0xB6<<1) +#define HC_UNLOCK_DEVICE (0x7C<<1) + +/* Interrupt Registers */ +#define HC_INTERRUPT_REG (0xD4<<1) /* Interrupt Register */ +#define HC_INTENABLE_REG (0xD6<<1) /* Interrupt enable Register */ +#define HC_ISO_IRQ_MASK_OR_REG (0xD8<<1) /* ISO Mask OR Register */ +#define HC_INT_IRQ_MASK_OR_REG (0xDA<<1) /* INT Mask OR Register */ +#define HC_ATL_IRQ_MASK_OR_REG (0xDC<<1) /* ATL Mask OR Register */ +#define HC_ISO_IRQ_MASK_AND_REG (0xDE<<1) /* ISO Mask AND Register */ +#define HC_INT_IRQ_MASK_AND_REG (0xE0<<1) /* INT Mask AND Register */ +#define HC_ATL_IRQ_MASK_AND_REG (0xE2<<1) /* ATL Mask AND Register */ + +/*power control reg */ +#define HC_POWER_DOWN_CONTROL_REG (0xD0<<1) + +/*RAM Registers */ +#define HC_DMACONFIG_REG (0xBC<<1) /* DMA Config Register */ +#define HC_MEM_READ_REG (0xC4<<1) /* Memory Register */ +#define HC_DATA_REG (0xC6<<1) /* Data Register */ + +#define OTG_CTRL_SET_REG (0xE4<<1) +#define OTG_CTRL_CLEAR_REG (0xE6<<1) +#define OTG_SOURCE_REG (0xE8<<1) + +#define OTG_INTR_EN_F_SET_REG (0xF0<<1) +#define OTG_INTR_EN_R_SET_REG (0xF4<<1) /* OTG Interrupt Enable Rise register */ + +#else +#define HC_SPARAMS_REG 0x04 /* Structural Parameters Register */ +#define HC_CPARAMS_REG 0x08 /* Capability Parameters Register */ + +#define HC_USBCMD_REG 0x8C /* USB Command Register */ +#define HC_USBSTS_REG 0x90 /* USB Status Register */ +#define HC_INTERRUPT_REG_EHCI 0x94 /* INterrupt Enable Register */ +#define HC_FRINDEX_REG 0x98 /* Frame Index Register */ + +#define HC_CONFIGFLAG_REG 0x9C /* Conigured Flag Register */ +#define HC_PORTSC1_REG 0xA0 /* Port Status Control for Port1 */ + +/*ISO Transfer Registers */ +#define HC_ISO_PTD_DONEMAP_REG 0xA4 /* ISO PTD Done Map Register */ +#define HC_ISO_PTD_SKIPMAP_REG 0xA6 /* ISO PTD Skip Map Register */ +#define HC_ISO_PTD_LASTPTD_REG 0xA8 /* ISO PTD Last PTD Register */ + +/*INT Transfer Registers */ +#define HC_INT_PTD_DONEMAP_REG 0xAA /* INT PTD Done Map Register */ +#define HC_INT_PTD_SKIPMAP_REG 0xAC /* INT PTD Skip Map Register */ +#define HC_INT_PTD_LASTPTD_REG 0xAE /* INT PTD Last PTD Register */ + +/*ATL Transfer Registers */ +#define HC_ATL_PTD_DONEMAP_REG 0xB0 /* ATL PTD Last PTD Register */ +#define HC_ATL_PTD_SKIPMAP_REG 0xB2 /* ATL PTD Last PTD Register */ +#define HC_ATL_PTD_LASTPTD_REG 0xB4 /* ATL PTD Last PTD Register */ + +/*General Purpose Registers */ +#define HC_HW_MODE_REG 0x0C //0xB6 /* H/W Mode Register */ +#define HC_CHIP_ID_REG 0x70 /* Chip ID Register */ +#define HC_SCRATCH_REG 0x78 /* Scratch Register */ +#define HC_RESET_REG 0xB8 /* HC Reset Register */ +#define HC_HWMODECTRL_REG 0xB6 //0x0C /* H/W Mode control Register */ +#define HC_UNLOCK_DEVICE 0x7C + +/* Interrupt Registers */ +#define HC_INTERRUPT_REG 0xD4 /* Interrupt Register */ +#define HC_INTENABLE_REG 0xD6 /* Interrupt enable Register */ +#define HC_ISO_IRQ_MASK_OR_REG 0xD8 /* ISO Mask OR Register */ +#define HC_INT_IRQ_MASK_OR_REG 0xDA /* INT Mask OR Register */ +#define HC_ATL_IRQ_MASK_OR_REG 0xDC /* ATL Mask OR Register */ +#define HC_ISO_IRQ_MASK_AND_REG 0xDE /* ISO Mask AND Register */ +#define HC_INT_IRQ_MASK_AND_REG 0xE0 /* INT Mask AND Register */ +#define HC_ATL_IRQ_MASK_AND_REG 0xE2 /* ATL Mask AND Register */ + +/*power control reg */ +#define HC_POWER_DOWN_CONTROL_REG 0xD0 + +/*RAM Registers */ +#define HC_DMACONFIG_REG 0xBC /* DMA Config Register */ +#define HC_MEM_READ_REG 0xC4 /* Memory Register */ +#define HC_DATA_REG 0xC6 /* Data Register */ + +#define OTG_CTRL_SET_REG 0xE4 +#define OTG_CTRL_CLEAR_REG 0xE6 +#define OTG_SOURCE_REG 0xE8 + +#define OTG_INTR_EN_F_SET_REG 0xF0 /* OTG Interrupt Enable Fall register */ +#define OTG_INTR_EN_R_SET_REG 0xF4 /* OTG Interrupt Enable Rise register */ + +#endif + +#define OTG_CTRL_DPPULLUP 0x0001 +#define OTG_CTRL_DPPULLDOWN 0x0002 +#define OTG_CTRL_DMPULLDOWN 0x0004 +#define OTG_CTRL_VBUS_DRV 0x0010 +#define OTG_CTRL_VBUS_DISCHRG 0x0020 +#define OTG_CTRL_VBUS_CHRG 0x0040 +#define OTG_CTRL_SW_SEL_HC_DC 0x0080 +#define OTG_CTRL_BDIS_ACON_EN 0x0100 +#define OTG_CTRL_OTG_SE0_EN 0x0200 +#define OTG_CTRL_OTG_DISABLE 0x0400 +#define OTG_CTRL_VBUS_DRV_PORT2 0x1000 +#define OTG_CTRL_SW_SEL_HC_2 0x8000 + +/*interrupt count and buffer status register*/ + + +#ifdef PXA300 +#define HC_BUFFER_STATUS_REG (0xBA<<1) +#define HC_INT_THRESHOLD_REG (0xC8<<1) +#else +#define HC_BUFFER_STATUS_REG 0xBA +#define HC_INT_THRESHOLD_REG 0xC8 +#endif + +#define HC_OTG_INTERRUPT 0x400 + +#ifdef PXA300 +#define DC_CHIPID (0x70<<1) +#else +#define DC_CHIPID 0x70 +#endif + + +#ifdef PXA300 +#define FPGA_CONFIG_REG (0x100<<1) +#else +#define FPGA_CONFIG_REG 0x100 +#endif + +#define HC_HW_MODE_GOBAL_INTR_ENABLE 0x01 +#define HC_HW_MODE_INTR_EDGE 0x02 +#define HC_HW_MODE_INTR_POLARITY_HIGH 0x04 +#define HC_HW_MODE_LOCK 0x08 +#define HC_HW_MODE_DATABUSWIDTH_8 0x10 +#define HC_HW_MODE_DREQ_POL_HIGH 0x20 +#define HC_HW_MODE_DACK_POL_HIGH 0x40 +#define HC_HW_MODE_COMN_INT 0x80 + +struct isp1763_driver; +typedef struct _isp1763_id { + u16 idVendor; + u16 idProduct; + u32 driver_info; +} isp1763_id; + +typedef struct isp1763_dev { + /*added for pci device */ +#ifdef NON_PCI + struct platform_device *dev; +#else /*PCI*/ + struct pci_dev *pcidev; +#endif + struct isp1763_driver *driver; /* which driver has allocated this device */ + void *driver_data; /* data private to the host controller driver */ + void *otg_driver_data; /*data private for otg controler */ + unsigned char index; /* local controller (HC/DC/OTG) */ + unsigned int irq; /*Interrupt Channel allocated for this device */ + void (*handler) (struct isp1763_dev * dev, void *isr_data); /* Interrupt Serrvice Routine */ + void *isr_data; /* isr data of the driver */ + unsigned long int_reg; /* Interrupt register */ + unsigned long alt_int_reg; /* Interrupt register 2 */ + unsigned long start; + unsigned long length; + struct resource *mem_res; + unsigned long io_base; /* Start Io address space for this device */ + unsigned long io_len; /* IO address space length for this device */ + + unsigned long chip_id; /* Chip Id */ + + char name[80]; /* device name */ + int active; /* device status */ + + /* DMA resources should come here */ + unsigned long dma; + u8 *baseaddress; /*base address for i/o ops */ + u8 *dmabase; + isp1763_id *id; +} isp1763_dev_t; + + +typedef struct isp1763_driver { + char *name; + unsigned long index; /* HC or DC or OTG */ + isp1763_id *id; /*device ids */ + int (*probe) (struct isp1763_dev * dev, isp1763_id * id); /* New device inserted */ + void (*remove) (struct isp1763_dev * dev); /* Device removed (NULL if not a hot-plug capable driver) */ + + void (*suspend) (struct isp1763_dev * dev); /* Device suspended */ + void (*resume) (struct isp1763_dev * dev); /* Device woken up */ + void (*remotewakeup) (struct isp1763_dev *dev); /* Remote Wakeup */ + void (*powerup) (struct isp1763_dev *dev); /* Device poweup mode */ + void (*powerdown) (struct isp1763_dev *dev); /* Device power down mode */ +} isp_1763_driver_t; + +struct usb_device *phci_register_otg_device(struct isp1763_dev *dev); + +/*otg exported function from host*/ +int phci_suspend_otg_port(struct isp1763_dev *dev, u32 command); +int phci_enumerate_otg_port(struct isp1763_dev *dev, u32 command); + +extern int isp1763_register_driver(struct isp1763_driver *drv); +extern void isp1763_unregister_driver(struct isp1763_driver *drv); +extern int isp1763_request_irq(void (*handler)(struct isp1763_dev * dev, void *isr_data), + struct isp1763_dev *dev, void *isr_data); +extern void isp1763_free_irq(struct isp1763_dev *dev, void *isr_data); + +extern u32 isp1763_reg_read32(isp1763_dev_t * dev, u16 reg, u32 data); +extern u16 isp1763_reg_read16(isp1763_dev_t * dev, u16 reg, u16 data); +extern u8 isp1763_reg_read8(struct isp1763_dev *dev, u16 reg, u8 data); +extern void isp1763_reg_write32(isp1763_dev_t * dev, u16 reg, u32 data); +extern void isp1763_reg_write16(isp1763_dev_t * dev, u16 reg, u16 data); +extern void isp1763_reg_write8(struct isp1763_dev *dev, u16 reg, u8 data); +extern int isp1763_mem_read(isp1763_dev_t * dev, u32 start_add, + u32 end_add, u32 * buffer, u32 length, u16 dir); +extern int isp1763_mem_write(isp1763_dev_t * dev, u32 start_add, + u32 end_add, u32 * buffer, u32 length, u16 dir); +#endif /* __HAL_INTF_H__ */ diff --git a/drivers/usb/host/pehci/hal/hal_msm.c b/drivers/usb/host/pehci/hal/hal_msm.c new file mode 100644 index 00000000000..35c0203307a --- /dev/null +++ b/drivers/usb/host/pehci/hal/hal_msm.c @@ -0,0 +1,748 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux HCD Controller driver : hal +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* This is the main hardware abstraction layer file. Hardware initialization, interupt +* processing and read/write routines are handled here. +* +* Author : wired support +* +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/*--------------------------------------------------------------* + * linux system include files + *--------------------------------------------------------------*/ +#include "hal_msm.h" +#include "../hal/hal_intf.h" +#include "../hal/isp1763.h" + + +/*--------------------------------------------------------------* + * Local variable Definitions + *--------------------------------------------------------------*/ +struct isp1763_dev isp1763_loc_dev[ISP1763_LAST_DEV]; + + +/*--------------------------------------------------------------* + * Local # Definitions + *--------------------------------------------------------------*/ +#define PCI_ACCESS_RETRY_COUNT 20 +#define ISP1763_DRIVER_NAME "isp1763_usb" + +/*--------------------------------------------------------------* + * Local Function + *--------------------------------------------------------------*/ + +static int __devexit isp1763_remove(struct platform_device *pdev); +static int __devinit isp1763_probe(struct platform_device *pdev); + + +/*--------------------------------------------------------------* + * Platform Driver Interface Functions + *--------------------------------------------------------------*/ + +static struct platform_driver isp1763_usb_driver = { + .remove = __exit_p(isp1763_remove), + .driver = { + .name = ISP1763_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + + +/*--------------------------------------------------------------* + * ISP1763 Read write routine + *--------------------------------------------------------------*/ +/* + * EBI2 on 8660 ignores the first bit and shifts the address by + * one bit to the right. + * Hence, shift left all the register addresses before accessing + * them over EBI2. + * This logic applies only for the register read/writes, for + * read/write from ISP memory this conversion is not needed + * as the ISP obtains the memory address from 'memory' register + */ + +/* Write a 32 bit Register of isp1763 */ +void +isp1763_reg_write32(struct isp1763_dev *dev, u16 reg, u32 data) +{ + /* Write the 32bit to the register address given to us */ + + reg <<= 1; +#ifdef DATABUS_WIDTH_16 + writew((u16) data, dev->baseaddress + ((reg))); + writew((u16) (data >> 16), dev->baseaddress + (((reg + 4)))); +#else + writeb((u8) data, dev->baseaddress + (reg)); + writeb((u8) (data >> 8), dev->baseaddress + ((reg + 1))); + writeb((u8) (data >> 16), dev->baseaddress + ((reg + 2))); + writeb((u8) (data >> 24), dev->baseaddress + ((reg + 3))); +#endif + +} +EXPORT_SYMBOL(isp1763_reg_write32); + + +/* Read a 32 bit Register of isp1763 */ +u32 +isp1763_reg_read32(struct isp1763_dev *dev, u16 reg, u32 data) +{ + +#ifdef DATABUS_WIDTH_16 + u16 wvalue1, wvalue2; +#else + u8 bval1, bval2, bval3, bval4; +#endif + data = 0; + reg <<= 1; +#ifdef DATABUS_WIDTH_16 + wvalue1 = readw(dev->baseaddress + ((reg))); + wvalue2 = readw(dev->baseaddress + (((reg + 4)))); + data |= wvalue2; + data <<= 16; + data |= wvalue1; +#else + + bval1 = readb(dev->baseaddress + (reg)); + bval2 = readb(dev->baseaddress + (reg + 1)); + bval3 = readb(dev->baseaddress + (reg + 2)); + bval4 = readb(dev->baseaddress + (reg + 3)); + data = 0; + data |= bval4; + data <<= 8; + data |= bval3; + data <<= 8; + data |= bval2; + data <<= 8; + data |= bval1; + +#endif + + return data; +} +EXPORT_SYMBOL(isp1763_reg_read32); + + +/* Read a 16 bit Register of isp1763 */ +u16 +isp1763_reg_read16(struct isp1763_dev * dev, u16 reg, u16 data) +{ + reg <<= 1; +#ifdef DATABUS_WIDTH_16 + data = readw(dev->baseaddress + ((reg))); +#else + u8 bval1, bval2; + bval1 = readb(dev->baseaddress + (reg)); + if (reg == HC_DATA_REG){ + bval2 = readb(dev->baseaddress + (reg)); + } else { + bval2 = readb(dev->baseaddress + ((reg + 1))); + } + data = 0; + data |= bval2; + data <<= 8; + data |= bval1; + +#endif + return data; +} +EXPORT_SYMBOL(isp1763_reg_read16); + +/* Write a 16 bit Register of isp1763 */ +void +isp1763_reg_write16(struct isp1763_dev *dev, u16 reg, u16 data) +{ + reg <<= 1; +#ifdef DATABUS_WIDTH_16 + writew(data, dev->baseaddress + ((reg))); +#else + writeb((u8) data, dev->baseaddress + (reg)); + if (reg == HC_DATA_REG){ + writeb((u8) (data >> 8), dev->baseaddress + (reg)); + }else{ + writeb((u8) (data >> 8), dev->baseaddress + ((reg + 1))); + } + +#endif +} +EXPORT_SYMBOL(isp1763_reg_write16); + +/* Read a 8 bit Register of isp1763 */ +u8 +isp1763_reg_read8(struct isp1763_dev *dev, u16 reg, u8 data) +{ + reg <<= 1; + data = readb((dev->baseaddress + (reg))); + return data; +} +EXPORT_SYMBOL(isp1763_reg_read8); + +/* Write a 8 bit Register of isp1763 */ +void +isp1763_reg_write8(struct isp1763_dev *dev, u16 reg, u8 data) +{ + reg <<= 1; + writeb(data, (dev->baseaddress + (reg))); +} +EXPORT_SYMBOL(isp1763_reg_write8); + + +/*--------------------------------------------------------------* + * + * Module dtatils: isp1763_mem_read + * + * Memory read using PIO method. + * + * Input: struct isp1763_driver *drv --> Driver structure. + * u32 start_add --> Starting address of memory + * u32 end_add ---> End address + * + * u32 * buffer --> Buffer pointer. + * u32 length ---> Length + * u16 dir ---> Direction ( Inc or Dec) + * + * Output int Length ----> Number of bytes read + * + * Called by: system function + * + * + *--------------------------------------------------------------*/ +/* Memory read function PIO */ + +int +isp1763_mem_read(struct isp1763_dev *dev, u32 start_add, + u32 end_add, u32 * buffer, u32 length, u16 dir) +{ + u8 *one = (u8 *) buffer; + u16 *two = (u16 *) buffer; + u32 a = (u32) length; + u32 w; + u32 w2; + + if (buffer == 0) { + printk("Buffer address zero\n"); + return 0; + } + + + isp1763_reg_write16(dev, HC_MEM_READ_REG, start_add); + /* This delay requirement comes from the ISP1763A programming guide */ + ndelay(100); +last: + w = isp1763_reg_read16(dev, HC_DATA_REG, w); + w2 = isp1763_reg_read16(dev, HC_DATA_REG, w); + w2 <<= 16; + w = w | w2; + if (a == 1) { + *one = (u8) w; + return 0; + } + if (a == 2) { + *two = (u16) w; + return 0; + } + + if (a == 3) { + *two = (u16) w; + two += 1; + w >>= 16; + *two = (u8) (w); + return 0; + + } + while (a > 0) { + *buffer = w; + a -= 4; + if (a <= 0) { + break; + } + if (a < 4) { + buffer += 1; + one = (u8 *) buffer; + two = (u16 *) buffer; + goto last; + } + buffer += 1; + w = isp1763_reg_read16(dev, HC_DATA_REG, w); + w2 = isp1763_reg_read16(dev, HC_DATA_REG, w); + w2 <<= 16; + w = w | w2; + } + return ((a < 0) || (a == 0)) ? 0 : (-1); + +} +EXPORT_SYMBOL(isp1763_mem_read); + + +/*--------------------------------------------------------------* + * + * Module dtatils: isp1763_mem_write + * + * Memory write using PIO method. + * + * Input: struct isp1763_driver *drv --> Driver structure. + * u32 start_add --> Starting address of memory + * u32 end_add ---> End address + * + * u32 * buffer --> Buffer pointer. + * u32 length ---> Length + * u16 dir ---> Direction ( Inc or Dec) + * + * Output int Length ----> Number of bytes read + * + * Called by: system function + * + * + *--------------------------------------------------------------*/ + +/* Memory read function IO */ + +int +isp1763_mem_write(struct isp1763_dev *dev, + u32 start_add, u32 end_add, u32 * buffer, u32 length, u16 dir) +{ + int a = length; + u8 one = (u8) (*buffer); + u16 two = (u16) (*buffer); + + + isp1763_reg_write16(dev, HC_MEM_READ_REG, start_add); + /* This delay requirement comes from the ISP1763A programming guide */ + ndelay(100); + + if (a == 1) { + isp1763_reg_write16(dev, HC_DATA_REG, one); + return 0; + } + if (a == 2) { + isp1763_reg_write16(dev, HC_DATA_REG, two); + return 0; + } + + while (a > 0) { + isp1763_reg_write16(dev, HC_DATA_REG, (u16) (*buffer)); + if (a >= 3) + isp1763_reg_write16(dev, HC_DATA_REG, + (u16) ((*buffer) >> 16)); + start_add += 4; + a -= 4; + if (a <= 0) + break; + buffer += 1; + + } + + return ((a < 0) || (a == 0)) ? 0 : (-1); + +} +EXPORT_SYMBOL(isp1763_mem_write); + + +/*--------------------------------------------------------------* + * + * Module dtatils: isp1763_register_driver + * + * This function is used by top driver (OTG, HCD, DCD) to register + * their communication functions (probe, remove, suspend, resume) using + * the drv data structure. + * This function will call the probe function of the driver if the ISP1763 + * corresponding to the driver is enabled + * + * Input: struct isp1763_driver *drv --> Driver structure. + * Output result + * 0= complete + * 1= error. + * + * Called by: system function module_init + * + * + *--------------------------------------------------------------*/ + +int +isp1763_register_driver(struct isp1763_driver *drv) +{ + struct isp1763_dev *dev; + int result = -EINVAL; + + hal_entry("%s: Entered\n", __FUNCTION__); + info("isp1763_register_driver(drv=%p)\n", drv); + + if (!drv) { + return -EINVAL; + } + + dev = &isp1763_loc_dev[drv->index]; + if (!dev->baseaddress) + return -EINVAL; + + dev->active = 1; /* set the driver as active*/ + + if (drv->probe) { + result = drv->probe(dev, drv->id); + } else { + printk("%s no probe function for indes %d \n", __FUNCTION__, + (int)drv->index); + } + + if (result >= 0) { + pr_debug(KERN_INFO __FILE__ ": Registered Driver %s\n", + drv->name); + dev->driver = drv; + } + hal_entry("%s: Exit\n", __FUNCTION__); + return result; +} /* End of isp1763_register_driver */ +EXPORT_SYMBOL(isp1763_register_driver); + + +/*--------------------------------------------------------------* + * + * Module dtatils: isp1763_unregister_driver + * + * This function is used by top driver (OTG, HCD, DCD) to de-register + * their communication functions (probe, remove, suspend, resume) using + * the drv data structure. + * This function will check whether the driver is registered or not and + * call the remove function of the driver if registered + * + * Input: struct isp1763_driver *drv --> Driver structure. + * Output result + * 0= complete + * 1= error. + * + * Called by: system function module_init + * + * + *--------------------------------------------------------------*/ + +void +isp1763_unregister_driver(struct isp1763_driver *drv) +{ + struct isp1763_dev *dev; + hal_entry("%s: Entered\n", __FUNCTION__); + + info("isp1763_unregister_driver(drv=%p)\n", drv); + dev = &isp1763_loc_dev[drv->index]; + if (dev->driver == drv) { + /* driver registered is same as the requestig driver */ + drv->remove(dev); + dev->driver = NULL; + info(": De-registered Driver %s\n", drv->name); + return; + } + hal_entry("%s: Exit\n", __FUNCTION__); +} /* End of isp1763_unregister_driver */ +EXPORT_SYMBOL(isp1763_unregister_driver); + + +/*--------------------------------------------------------------* + * ISP1763 Platform driver interface routine. + *--------------------------------------------------------------*/ + + +/*--------------------------------------------------------------* + * + * Module dtatils: isp1763_module_init + * + * This is the module initialization function. It registers to + * driver for a isp1763 platform device. And also resets the + * internal data structures. + * + * Input: void + * Output result + * 0= complete + * 1= error. + * + * Called by: system function module_init + * + * + * + -------------------------------------------------------------------*/ +static int __init +isp1763_module_init(void) +{ + int result = 0; + hal_entry("%s: Entered\n", __FUNCTION__); + pr_debug(KERN_NOTICE "+isp1763_module_init\n"); + memset(isp1763_loc_dev, 0, sizeof(isp1763_loc_dev)); + + result = platform_driver_probe(&isp1763_usb_driver, isp1763_probe); + + pr_debug(KERN_NOTICE "-isp1763_module_init\n"); + hal_entry("%s: Exit\n", __FUNCTION__); + return result; +} + +/*--------------------------------------------------------------* + * + * Module dtatils: isp1763_module_cleanup + * + * This is the module cleanup function. It de-registers the + * Platform driver and resets the internal data structures. + * + * Input: void + * Output void + * + * Called by: system function module_cleanup + * + * + * + --------------------------------------------------------------*/ + +static void __exit +isp1763_module_cleanup(void) +{ + pr_debug("Hal Module Cleanup\n"); + platform_driver_unregister(&isp1763_usb_driver); + + memset(isp1763_loc_dev, 0, sizeof(isp1763_loc_dev)); +} + +void dummy_mem_read(struct isp1763_dev *dev) +{ + u32 w = 0; + isp1763_reg_write16(dev, HC_MEM_READ_REG, 0x0400); + w = isp1763_reg_read16(dev, HC_DATA_REG, w); + + pr_debug("dummy_read DONE: %x\n", w); + msleep(10); +} +/*--------------------------------------------------------------* + * + * Module dtatils: isp1763_probe + * + * probe function of ISP1763 + * This function is called from module_init if the corresponding platform + * device is present. This function initializes the information + * for the Host Controller with the assigned resources and tests the register + * access to the controller and do a software reset and makes it ready + * for the driver to play with. It also calls setup_gpio passed from pdata + * to setup GPIOs (e.g. used for IRQ and RST lines). + * + * Input: + * struct platform_device *dev ----> Platform Device structure + * Output void + * + * Called by: system function module_cleanup + * + * + * + --------------------------------------------------------------**/ + +static int __devinit +isp1763_probe(struct platform_device *pdev) +{ + u32 reg_data = 0; + struct isp1763_dev *loc_dev; + int status = 1; + u32 hwmodectrl = 0; + u16 us_reset_hc = 0; + u32 chipid = 0; + struct isp1763_platform_data *pdata = pdev->dev.platform_data; + + hal_entry("%s: Entered\n", __FUNCTION__); + + hal_init(("isp1763_probe(dev=%p)\n", dev)); + + loc_dev = &(isp1763_loc_dev[ISP1763_HC]); + loc_dev->dev = pdev; + + /* Get the Host Controller IO and INT resources */ + loc_dev->mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!loc_dev->mem_res) { + pr_err("%s: failed to get platform resource mem\n", __func__); + return -ENODEV; + } + + loc_dev->baseaddress = ioremap_nocache(loc_dev->mem_res->start, + resource_size(loc_dev->mem_res)); + if (!loc_dev->baseaddress) { + pr_err("%s: ioremap failed\n", __func__); + status = -ENOMEM; + goto put_mem_res; + } + pr_info("%s: ioremap done at: %x\n", __func__, + (int)loc_dev->baseaddress); + loc_dev->irq = platform_get_irq(pdev, 0); + if (!loc_dev->irq) { + pr_err("%s: platform_get_irq failed\n", __func__); + status = -ENODEV; + goto free_regs; + } + + loc_dev->index = ISP1763_HC; /*zero */ + loc_dev->length = resource_size(loc_dev->mem_res); + + hal_init(("isp1763 HC MEM Base= %p irq = %d\n", + loc_dev->baseaddress, loc_dev->irq)); + + /* Setup GPIOs and isssue RESET_N to Controller */ + if (pdata->setup_gpio) + if (pdata->setup_gpio(1)) + pr_err("%s: Failed to setup GPIOs for isp1763\n", + __func__); + if (pdata->reset_gpio) { + gpio_set_value(pdata->reset_gpio, 0); + msleep(10); + gpio_set_value(pdata->reset_gpio, 1); + } else { + pr_err("%s: Failed to issue RESET_N to isp1763\n", __func__); + } + + dummy_mem_read(loc_dev); + + chipid = isp1763_reg_read32(loc_dev, DC_CHIPID, chipid); + pr_info("START: chip id:%x\n", chipid); + + /*reset the host controller */ + pr_debug("RESETTING\n"); + us_reset_hc |= 0x1; + isp1763_reg_write16(loc_dev, 0xB8, us_reset_hc); + msleep(20); + us_reset_hc = 0; + us_reset_hc |= 0x2; + isp1763_reg_write16(loc_dev, 0xB8, us_reset_hc); + + chipid = isp1763_reg_read32(loc_dev, DC_CHIPID, chipid); + pr_info("after HC reset, chipid:%x\n", chipid); + + msleep(20); + hwmodectrl = isp1763_reg_read16(loc_dev, HC_HWMODECTRL_REG, hwmodectrl); + pr_debug("Mode Ctrl Value b4 setting buswidth: %x\n", hwmodectrl); +#ifdef DATABUS_WIDTH_16 + hwmodectrl &= 0xFFEF; /*enable the 16 bit bus */ +#else + pr_debug("Setting 8-BIT mode\n"); + hwmodectrl |= 0x0010; /*enable the 8 bit bus */ +#endif + isp1763_reg_write16(loc_dev, HC_HWMODECTRL_REG, hwmodectrl); + pr_debug("writing 0x%x to hw mode reg\n", hwmodectrl); + + hwmodectrl = isp1763_reg_read16(loc_dev, HC_HWMODECTRL_REG, hwmodectrl); + msleep(100); + + pr_debug("Mode Ctrl Value after setting buswidth: %x\n", hwmodectrl); + + + chipid = isp1763_reg_read32(loc_dev, DC_CHIPID, chipid); + pr_debug("after setting HW MODE to 8bit, chipid:%x\n", chipid); + + + + hal_init(("isp1763 DC MEM Base= %lx irq = %d\n", + loc_dev->io_base, loc_dev->irq)); + reg_data = isp1763_reg_read16(loc_dev, HC_SCRATCH_REG, reg_data); + pr_debug("Scratch register is 0x%x\n", reg_data); + reg_data = 0xABCD; + isp1763_reg_write16(loc_dev, HC_SCRATCH_REG, reg_data); + reg_data = isp1763_reg_read16(loc_dev, HC_SCRATCH_REG, reg_data); + pr_debug("After write, Scratch register is 0x%x\n", reg_data); + + if (reg_data != 0xABCD) { + pr_err("%s: Scratch register write mismatch!!\n", __func__); + status = -ENODEV; + goto free_gpios; + } + + memcpy(loc_dev->name, ISP1763_DRIVER_NAME, sizeof(ISP1763_DRIVER_NAME)); + loc_dev->name[sizeof(ISP1763_DRIVER_NAME)] = 0; + + pr_debug(KERN_NOTICE "-isp1763_pci_probe\n"); + hal_entry("%s: Exit\n", __FUNCTION__); + return 0; + +free_gpios: + if (pdata->setup_gpio) + pdata->setup_gpio(0); +free_regs: + iounmap(loc_dev->baseaddress); +put_mem_res: + loc_dev->baseaddress = NULL; + hal_entry("%s: Exit\n", __FUNCTION__); + return status; +} /* End of isp1763_probe */ + + +/*--------------------------------------------------------------* + * + * Module details: isp1763_remove + * + * cleanup function of ISP1763 + * This functions de-initializes the local variables, frees GPIOs + * and releases memory resource. + * + * Input: + * struct platform_device *dev ----> Platform Device structure + * + * Output void + * + * Called by: system function module_cleanup + * + * + * + --------------------------------------------------------------*/ +static int __devexit +isp1763_remove(struct platform_device *pdev) +{ + struct isp1763_dev *loc_dev; + struct isp1763_platform_data *pdata = pdev->dev.platform_data; + + hal_init(("isp1763_pci_remove(dev=%p)\n", dev)); + + loc_dev = &isp1763_loc_dev[ISP1763_HC]; + iounmap(loc_dev->baseaddress); + loc_dev->baseaddress = NULL; + if (pdata->setup_gpio) + return pdata->setup_gpio(0); + + return 0; +} /* End of isp1763_remove */ + + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +module_init(isp1763_module_init); +module_exit(isp1763_module_cleanup); diff --git a/drivers/usb/host/pehci/hal/hal_msm.h b/drivers/usb/host/pehci/hal/hal_msm.h new file mode 100644 index 00000000000..a7a65b7ef2a --- /dev/null +++ b/drivers/usb/host/pehci/hal/hal_msm.h @@ -0,0 +1,85 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux OTG Controller driver : hal +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* This is a hardware abstraction layer header file. +* +* Author : wired support +* +*/ + +#ifndef HAL_X86_H +#define HAL_X86_H + +#define DRIVER_AUTHOR "ST-ERICSSON " +#define DRIVER_DESC "ISP1763 bus driver" + +/* Driver tuning, per ST-ERICSSON requirements: */ + +#define MEM_TO_CHECK 4096 /*bytes, must be multiple of 2 */ + +/* BIT defines */ +#define BIT0 (1 << 0) +#define BIT1 (1 << 1) +#define BIT2 (1 << 2) +#define BIT3 (1 << 3) +#define BIT4 (1 << 4) +#define BIT5 (1 << 5) +#define BIT6 (1 << 6) +#define BIT7 (1 << 7) +#define BIT8 (1 << 8) +#define BIT9 (1 << 9) +#define BIT10 (1 << 10) +#define BIT11 (1 << 11) +#define BIT12 (1 << 12) +#define BIT13 (1 << 13) +#define BIT14 (1 << 14) +#define BIT15 (1 << 15) +#define BIT16 (1 << 16) +#define BIT17 (1 << 17) +#define BIT18 (1 << 18) +#define BIT19 (1 << 19) +#define BIT20 (1 << 20) +#define BIT21 (1 << 21) +#define BIT22 (1 << 22) +#define BIT23 (1 << 23) +#define BIT24 (1 << 24) +#define BIT25 (1 << 26) +#define BIT27 (1 << 27) +#define BIT28 (1 << 28) +#define BIT29 (1 << 29) +#define BIT30 (1 << 30) +#define BIT31 (1 << 31) + +/* Definitions Related to Chip Address and CPU Physical Address + * cpu_phy_add: CPU Physical Address , it uses 32 bit data per address + * chip_add : Chip Address, it uses double word(64) bit data per address + */ +#define chip_add(cpu_phy_add) (((cpu_phy_add) - 0x400) / 8) +#define cpu_phy_add(chip_add) ((8 * (chip_add)) + 0x400) + +/* for getting end add, and start add, provided we have one address with us */ +/* IMPORTANT length hex(base16) and dec(base10) works fine*/ +#define end_add(start_add, length) (start_add + (length - 4)) +#define start_add(end_add, length) (end_add - (length - 4)) + +/* Device Registers*/ +#define DEV_UNLOCK_REGISTER 0x7C +#define DEV_INTERRUPT_REGISTER 0x18 +#define INT_ENABLE_REGISTER 0x14 + +#endif /*_HAL_X86_H_ */ diff --git a/drivers/usb/host/pehci/hal/isp1763.h b/drivers/usb/host/pehci/hal/isp1763.h new file mode 100644 index 00000000000..7355185bd54 --- /dev/null +++ b/drivers/usb/host/pehci/hal/isp1763.h @@ -0,0 +1,227 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux OTG Controller driver : hal +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* This is a hardware abstraction layer header file. +* +* Author : wired support +* +*/ + +#ifndef ISP1763_H +#define ISP1763_H + + + +/* For debugging option: ------------------- */ +#define PTD_DUMP_SCHEDULE +#undef PTD_DUMP_SCHEDULE + +#define PTD_DUMP_COMPLETE +#undef PTD_DUMP_COMPLETE +/* ------------------------------------*/ +#define CONFIG_ISO_SUPPORT + +#ifdef CONFIG_ISO_SUPPORT + +#define ISO_DBG_ENTRY 1 +#define ISO_DBG_EXIT 1 +#define ISO_DBG_ADDR 1 +#define ISO_DBG_DATA 1 +#define ISO_DBG_ERR 1 +#define ISO_DBG_INFO 1 + +#if 0 /* Set to 1 to enable isochronous debugging */ +#define iso_dbg(category, format, arg...) \ +do \ +{ \ + if(category) \ + { \ + printk(format, ## arg); \ + } \ +} while(0) +#else +#define iso_dbg(category, format, arg...) while(0) +#endif + +#endif /* CONFIG_ISO_SUPPORT */ + +/*Debug For Entry/Exit of the functions */ +//#define HCD_DEBUG_LEVEL1 +#ifdef HCD_DEBUG_LEVEL1 +#define pehci_entry(format, args... ) printk(format, ##args) +#else +#define pehci_entry(format, args...) do { } while(0) +#endif + +/*Debug for Port Info and Errors */ +//#define HCD_DEBUG_LEVEL2 +#ifdef HCD_DEBUG_LEVEL2 +#define pehci_print(format, args... ) printk(format, ##args) +#else +#define pehci_print(format, args...) do { } while(0) +#endif + +/*Debug For the Port changes and Enumeration */ +//#define HCD_DEBUG_LEVEL3 +#ifdef HCD_DEBUG_LEVEL3 +#define pehci_info(format,arg...) printk(format, ##arg) +#else +#define pehci_info(format,arg...) do {} while (0) +#endif + +/*Debug For Transfer flow */ +// #define HCD_DEBUG_LEVEL4 +#ifdef HCD_DEBUG_LEVEL4 +#define pehci_check(format,args...) printk(format, ##args) +#else +#define pehci_check(format,args...) +#endif +/*******************END HOST CONTROLLER**********************************/ + + + +/*******************START DEVICE CONTROLLER******************************/ + +/* For MTP support */ +#undef MTP_ENABLE /* Enable to add MTP support; But requires MTP class driver to be present to work */ +/*For CHAPTER8 TEST */ +#undef CHAPTER8_TEST /* Enable to Pass Chapter 8 Test */ + +/* Debug Entery/Exit of Function as well as some other Info */ +//#define DEV_DEBUG_LEVEL2 +#ifdef DEV_DEBUG_LEVEL2 +#define dev_print(format,arg...) printk(format, ##arg) +#else +#define dev_print(format,arg...) do {} while (0) +#endif + +/*Debug for Interrupt , Registers , device Enable/Disable and some other info */ +//#define DEV_DEBUG_LEVEL3 +#undef dev_info +#ifdef DEV_DEBUG_LEVEL3 +#define dev_info(format,arg...) printk(format, ##arg) +#else +#define dev_info(format,arg...) do {} while (0) +#endif + +/*Debug for Tranffer flow , Enumeration and Packet info */ +//#define DEV_DEBUG_LEVEL4 +#ifdef DEV_DEBUG_LEVEL4 +#define dev_check(format,args...) printk(format, ##args) +#else +#define dev_check(format,args...) do{}while(0) +#endif +/*******************END DEVICE CONTROLLER********************************/ + + +/*******************START MSCD*******************************************/ +/*Debug Entery/Exit of Function as well as some other Information*/ +//#define MSCD_DEBUG_LEVEL2 +#ifdef MSCD_DEBUG_LEVEL2 +#define mscd_print(format,arg...) printk(format, ##arg) +#else +#define mscd_print(format,arg...) do {} while (0) +#endif + +/*Debug for Info */ +//#define MSCD_DEBUG_LEVEL3 +#ifdef MSCD_DEBUG_LEVEL3 +#define mscd_info(format,arg...) printk(format, ##arg) +#else +#define mscd_info(format,arg...) do {} while (0) +#endif +/*******************END MSCD*********************************************/ + + +/*******************START OTG CONTROLLER*********************************/ +/*#define OTG */ /*undef for Device only and Host only */ +#define ALL_FSM_FLAGS +/*Debug for Entry/Exit and Info */ +/* #define OTG_DEBUG_LEVEL1 */ +#ifdef OTG_DEBUG_LEVEL1 +#define otg_entry(format, args... ) printk(format, ##args) +#else +#define otg_entry(format, args...) do { } while(0) +#endif + +/*Debug for State Machine Flow */ +/* #define OTG_DEBUG_LEVEL2 */ +#ifdef OTG_DEBUG_LEVEL2 +#define otg_print(format,arg...) printk(format, ##arg) +#else +#define otg_print(format,arg...) do {} while (0) +#endif +/*Debug for Info */ +/* #define OTG_DEBUG_LEVEL3 */ +#ifdef OTG_DEBUG_LEVEL3 +#define otg_info(format,arg...) printk(format, ##arg) +#else +#define otg_info(format,arg...) do {} while (0) +#endif + +/* #define OTG_DEBUG_LEVEL4 */ +#ifdef OTG_DEBUG_LEVEL4 +#define otg_printB(format,arg...) printk(format, ##arg) +#else +#define otg_printB(format,arg...) do {} while (0) +#endif +/*******************END OTG CONTROLLER***********************************/ + + + +/*******************START FOR HAL ***************************************/ +#define info pr_debug +#define warn pr_warn +/*Debug For Entry and Exit of the functions */ +#undef HAL_DEBUG_LEVEL1 +#ifdef HAL_DEBUG_LEVEL1 +#define hal_entry(format, args... ) printk(format, ##args) +#else +#define hal_entry(format, args...) do { } while(0) +#endif + +/*Debug For Interrupt information */ +#undef HAL_DEBUG_LEVEL2 +#ifdef HAL_DEBUG_LEVEL2 +#define hal_int(format, args... ) printk(format, ##args) +#else +#define hal_int(format, args...) do { } while(0) +#endif + +/*Debug For HAL Initialisation and Mem Initialisation */ +#undef HAL_DEBUG_LEVEL3 +#ifdef HAL_DEBUG_LEVEL3 +#define hal_init(format, args... ) printk(format, ##args) +#else +#define hal_init(format, args...) do { } while(0) +#endif +/*******************END FOR HAL*******************************************/ + + + +/*******************START FOR ALL CONTROLLERS*****************************/ +/*#define CONFIG_USB_OTG */ /*undef for Device only and Host only */ +/*#define ISP1763_DEVICE */ + +#ifdef CONFIG_USB_DEBUG +#define DEBUG +#else +#undef DEBUG +#endif +/*******************END FOR ALL CONTROLLERS*******************************/ +#endif diff --git a/drivers/usb/host/pehci/host/Makefile b/drivers/usb/host/pehci/host/Makefile new file mode 100644 index 00000000000..0c8552efb0d --- /dev/null +++ b/drivers/usb/host/pehci/host/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the pehci driver (if driver is inside kernel tree). +# + +obj-$(CONFIG_USB_PEHCI_HCD) += pehci.o + diff --git a/drivers/usb/host/pehci/host/itdptd.c b/drivers/usb/host/pehci/host/itdptd.c new file mode 100644 index 00000000000..6699c3af33e --- /dev/null +++ b/drivers/usb/host/pehci/host/itdptd.c @@ -0,0 +1,2156 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux OTG Controller driver : host +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* This is a host controller driver file. Isochronous event processing is handled here. +* +* Author : wired support +* +*/ +#ifdef CONFIG_ISO_SUPPORT +void phcd_clean_periodic_ep(void); +#endif + +#ifdef CONFIG_ISO_SUPPORT + +#define MAX_URBS 8 +#define MAX_EPS 2/*maximum 2 endpoints supported in ISO transfers.*/ +/*number of microframe per frame which is scheduled, for high speed device +* actually , NUMMICROFRAME should be 8 , but the micro frame #7 is fail , so +* there's just 4 microframe is used (#0 -> #4) +* Writer : LyNguyen - 25Nov09 +*/ +#define NUMMICROFRAME 8 +struct urb *gstUrb_pending[MAX_URBS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +struct usb_host_endpoint *periodic_ep[MAX_EPS]; + +int giUrbCount = 0; /* count the pending urb*/ +int giUrbIndex = 0; /*the index of urb need to be scheduled next*/ +/* + * phcd_iso_sitd_to_ptd - convert an SITD into a PTD + * + * phci_hcd *hcd + * - Main host controller driver structure + * struct ehci_sitd *sitd + * - Isochronous Transfer Descriptor, contains elements as defined by the + * EHCI standard plus a few more specific elements. + * struct urb *urb + * - USB Request Block, contains information regarding the type and how much data + * is requested to be transferred. + * void * ptd + * - Points to the ISO ptd structure that needs to be initialized + * + * API Description + * This is mainly responsible for: + * -Initializing the PTD that will be used for the ISO transfer + */ +void * +phcd_iso_sitd_to_ptd(phci_hcd * hcd, + struct ehci_sitd *sitd, struct urb *urb, void *ptd) +{ + struct _isp1763_isoptd *iso_ptd; + struct isp1763_mem_addr *mem_addr; + + unsigned long max_packet, mult, length, td_info1, td_info3; + unsigned long token, port_num, hub_num, data_addr; + unsigned long frame_number; + + iso_dbg(ISO_DBG_ENTRY, "phcd_iso_sitd_to_ptd entry\n"); + + /* Variable initialization */ + iso_ptd = (struct _isp1763_isoptd *) ptd; + mem_addr = &sitd->mem_addr; + + /* + * For both ISO and INT endpoints descriptors, new bit fields we added to + * specify whether or not the endpoint supports high bandwidth, and if so + * the number of additional packets that the endpoint can support during a + * single microframe. + * Bits 12:11 specify whether the endpoint supports high-bandwidth transfers + * Valid values: + * 00 None (1 transaction/uFrame) + * 01 1 additional transaction + * 10 2 additional transactions + * 11 reserved + */ + max_packet = usb_maxpacket(urb->dev, urb->pipe,usb_pipeout(urb->pipe)); + + /* + * We need to add 1 since our Multi starts with 1 instead of the USB specs defined + * zero (0). + */ + mult = 1 + ((max_packet >> 11) & 0x3); + max_packet &= 0x7ff; + + /* This is the size of the request (bytes to write or bytes to read) */ + length = sitd->length; + + /* + * Set V bit to indicate that there is payload to be sent or received. And + * indicate that the current PTD is active. + */ + td_info1 = QHA_VALID; + + /* + * Set the number of bytes that can be transferred by this PTD. This indicates + * the depth of the data field. + */ + td_info1 |= (length << 3); + + /* + * Set the maximum packet length which indicates the maximum number of bytes that + * can be sent to or received from the endpoint in a single data packet. + */ + if (urb->dev->speed != USB_SPEED_HIGH) { + /* + * According to the ISP1763 specs for sITDs, OUT token max packet should + * not be more than 188 bytes, while IN token max packet not more than + * 192 bytes (ISP1763 Rev 3.01, Table 72, page 79 + */ + if (usb_pipein(urb->pipe) && (max_packet > 192)) { + iso_dbg(ISO_DBG_INFO, + "IN Max packet over maximum\n"); + max_packet = 192; + } + + if ((!usb_pipein(urb->pipe)) && (max_packet > 188)) { + iso_dbg(ISO_DBG_INFO, + "OUT Max packet over maximum\n"); + max_packet = 188; + } + } + td_info1 |= (max_packet << 18); + + /* + * Place the FIRST BIT of the endpoint number here. + */ + td_info1 |= (usb_pipeendpoint(urb->pipe) << 31); + + /* + * Set the number of successive packets the HC can submit to the endpoint. + */ + if (urb->dev->speed == USB_SPEED_HIGH) { + td_info1 |= MULTI(mult); + } + + /* Set the first DWORD */ + iso_ptd->td_info1 = td_info1; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD0 = 0x%08x\n", + iso_ptd->td_info1); + + /* + * Since the first bit have already been added on the first DWORD of the PTD + * we only need to add the last 3-bits of the endpoint number. + */ + token = (usb_pipeendpoint(urb->pipe) & 0xE) >> 1; + + /* + * Get the device address and set it accordingly to its assigned bits of the 2nd + * DWORD. + */ + token |= usb_pipedevice(urb->pipe) << 3; + + /* See a split transaction is needed */ + if (urb->dev->speed != USB_SPEED_HIGH) { + /* + * If we are performing a SPLIT transaction indicate that it is so by setting + * the S bit of the second DWORD. + */ + token |= 1 << 14; + + port_num = urb->dev->ttport; + hub_num = urb->dev->tt->hub->devnum; + + /* Set the the port number of the hub or embedded TT */ + token |= port_num << 18; + + /* + * Set the hub address, this should be zero for the internal or + * embedded hub + */ + token |= hub_num << 25; + } + + /* if(urb->dev->speed != USB_SPEED_HIGH) */ + /* + * Determine if the direction of this pipe is IN, if so set the Token bit of + * the second DWORD to indicate it as IN. Since it is initialized to zero and + * zero indicates an OUT token, then we do not need anything to the Token bit + * if it is an OUT token. + */ + if (usb_pipein(urb->pipe)) { + token |= (IN_PID << 10); + } + + /* Set endpoint type to Isochronous */ + token |= EPTYPE_ISO; + + /* Set the second DWORD */ + iso_ptd->td_info2 = token; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD1 = 0x%08x\n", + iso_ptd->td_info2); + + /* + * Get the physical address of the memory location that was allocated for this PTD + * in the PAYLOAD region, using the formula indicated in sectin 7.2.2 of the ISP1763 specs + * rev 3.01 page 17 to 18. + */ + data_addr = ((unsigned long) (mem_addr->phy_addr) & 0xffff) - 0x400; + data_addr >>= 3; + + /* Set it to its location in the third DWORD */ + td_info3 =( 0xffff&data_addr) << 8; + + /* + * Set the frame number when this PTD will be sent for ISO OUT or IN + * Bits 0 to 2 are don't care, only bits 3 to 7. + */ + frame_number = sitd->framenumber; + frame_number = sitd->start_frame; + td_info3 |= (0xff& ((frame_number) << 3)); + + /* Set the third DWORD */ + iso_ptd->td_info3 = td_info3; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD2 = 0x%08x\n", + iso_ptd->td_info3); + + /* + * Set the A bit of the fourth DWORD to 1 to indicate that this PTD is active. + * This have the same functionality with the V bit of DWORD0 + */ + iso_ptd->td_info4 = QHA_ACTIVE; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD3 = 0x%08x\n", + iso_ptd->td_info4); + + /* Set the fourth DWORD to specify which uSOFs the start split needs to be placed */ + if (usb_pipein(urb->pipe)){ + iso_ptd->td_info5 = (sitd->ssplit); + }else{ + iso_ptd->td_info5 = (sitd->ssplit << 2); + } + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD4 = 0x%08x\n", + iso_ptd->td_info5); + + /* + * Set the fifth DWORD to specify which uSOFs the complete split needs to be sent. + * This is VALID only for IN (since ISO transfers don't have handshake stages) + */ + iso_ptd->td_info6 = sitd->csplit; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD5 = 0x%08x\n", + iso_ptd->td_info6); + + /*printk(" [phcd_iso_itd_to_ptd]: DWORD0 = 0x%08x\n",iso_ptd->td_info1); + printk(" [phcd_iso_itd_to_ptd]: DWORD1 = 0x%08x\n",iso_ptd->td_info2); + printk(" [phcd_iso_itd_to_ptd]: DWORD2 = 0x%08x\n",iso_ptd->td_info3); + printk(" [phcd_iso_itd_to_ptd]: DWORD3 = 0x%08x\n",iso_ptd->td_info4); + printk(" [phcd_iso_itd_to_ptd]: DWORD4 = 0x%08x\n",iso_ptd->td_info5); + printk(" [phcd_iso_itd_to_ptd]: DWORD5 = 0x%08x\n",iso_ptd->td_info6);*/ + iso_dbg(ISO_DBG_EXIT, "phcd_iso_itd_to_ptd exit\n"); + return iso_ptd; +} + + +/* + * phcd_iso_itd_to_ptd - convert an ITD into a PTD + * + * phci_hcd *hcd + * - Main host controller driver structure + * struct ehci_itd *itd + * - Isochronous Transfer Descriptor, contains elements as defined by the + * EHCI standard plus a few more ST-ERICSSON specific elements. + * struct urb *urb + * - USB Request Block, contains information regarding the type and how much data + * is requested to be transferred. + * void * ptd + * - Points to the ISO ptd structure that needs to be initialized + * + * API Description + * This is mainly responsible for: + * -Initializing the PTD that will be used for the ISO transfer + */ +void * +phcd_iso_itd_to_ptd(phci_hcd * hcd, + struct ehci_itd *itd, struct urb *urb, void *ptd) +{ + struct _isp1763_isoptd *iso_ptd; + struct isp1763_mem_addr *mem_addr; + + unsigned long max_packet, mult, length, td_info1, td_info3; + unsigned long token, port_num, hub_num, data_addr; + unsigned long frame_number; + int maxpacket; + iso_dbg(ISO_DBG_ENTRY, "phcd_iso_itd_to_ptd entry\n"); + + /* Variable initialization */ + iso_ptd = (struct _isp1763_isoptd *) ptd; + mem_addr = &itd->mem_addr; + + /* + * For both ISO and INT endpoints descriptors, new bit fields we added to + * specify whether or not the endpoint supports high bandwidth, and if so + * the number of additional packets that the endpoint can support during a + * single microframe. + * Bits 12:11 specify whether the endpoint supports high-bandwidth transfers + * Valid values: + * 00 None (1 transaction/uFrame) + * 01 1 additional transaction + * 10 2 additional transactions + * 11 reserved + */ + max_packet = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + + maxpacket = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + + /* + * We need to add 1 since our Multi starts with 1 instead of the USB specs defined + * zero (0). + */ + maxpacket &= 0x7ff; + mult = 1 + ((max_packet >> 11) & 0x3); + + + max_packet &= 0x7ff; + + /* This is the size of the request (bytes to write or bytes to read) */ + length = itd->length; + + /* + * Set V bit to indicate that there is payload to be sent or received. And + * indicate that the current PTD is active. + */ + td_info1 = QHA_VALID; + + /* + * Set the number of bytes that can be transferred by this PTD. This indicates + * the depth of the data field. + */ + td_info1 |= (length << 3); + + /* + * Set the maximum packet length which indicates the maximum number of bytes that + * can be sent to or received from the endpoint in a single data packet. + */ + if (urb->dev->speed != USB_SPEED_HIGH) { + /* + * According to the ISP1763 specs for sITDs, OUT token max packet should + * not be more than 188 bytes, while IN token max packet not more than + * 192 bytes (ISP1763 Rev 3.01, Table 72, page 79 + */ + if (usb_pipein(urb->pipe) && (max_packet > 192)) { + iso_dbg(ISO_DBG_INFO, + "[phcd_iso_itd_to_ptd]: IN Max packet over maximum\n"); + max_packet = 192; + } + + if ((!usb_pipein(urb->pipe)) && (max_packet > 188)) { + iso_dbg(ISO_DBG_INFO, + "[phcd_iso_itd_to_ptd]: OUT Max packet over maximum\n"); + max_packet = 188; + } + } else { /*HIGH SPEED */ + + if (max_packet > 1024){ + max_packet = 1024; + } + } + td_info1 |= (max_packet << 18); + + /* + * Place the FIRST BIT of the endpoint number here. + */ + td_info1 |= (usb_pipeendpoint(urb->pipe) << 31); + + /* + * Set the number of successive packets the HC can submit to the endpoint. + */ + if (urb->dev->speed == USB_SPEED_HIGH) { + td_info1 |= MULTI(mult); + } + + /* Set the first DWORD */ + iso_ptd->td_info1 = td_info1; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD0 = 0x%08x\n", + iso_ptd->td_info1); + + /* + * Since the first bit have already been added on the first DWORD of the PTD + * we only need to add the last 3-bits of the endpoint number. + */ + token = (usb_pipeendpoint(urb->pipe) & 0xE) >> 1; + + /* + * Get the device address and set it accordingly to its assigned bits of the 2nd + * DWORD. + */ + token |= usb_pipedevice(urb->pipe) << 3; + + /* See a split transaction is needed */ + if (urb->dev->speed != USB_SPEED_HIGH) { + /* + * If we are performing a SPLIT transaction indicate that it is so by setting + * the S bit of the second DWORD. + */ + token |= 1 << 14; + + port_num = urb->dev->ttport; + hub_num = urb->dev->tt->hub->devnum; + + /* Set the the port number of the hub or embedded TT */ + token |= port_num << 18; + + /* + * Set the hub address, this should be zero for the internal or + * embedded hub + */ + token |= hub_num << 25; + } + + /* if(urb->dev->speed != USB_SPEED_HIGH) */ + /* + * Determine if the direction of this pipe is IN, if so set the Token bit of + * the second DWORD to indicate it as IN. Since it is initialized to zero and + * zero indicates an OUT token, then we do not need anything to the Token bit + * if it is an OUT token. + */ + if (usb_pipein(urb->pipe)){ + token |= (IN_PID << 10); + } + + /* Set endpoint type to Isochronous */ + token |= EPTYPE_ISO; + + /* Set the second DWORD */ + iso_ptd->td_info2 = token; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD1 = 0x%08x\n", + iso_ptd->td_info2); + + /* + * Get the physical address of the memory location that was allocated for this PTD + * in the PAYLOAD region, using the formula indicated in sectin 7.2.2 of the ISP1763 specs + * rev 3.01 page 17 to 18. + */ + data_addr = ((unsigned long) (mem_addr->phy_addr) & 0xffff) - 0x400; + data_addr >>= 3; + + /* Set it to its location in the third DWORD */ + td_info3 = (data_addr&0xffff) << 8; + + /* + * Set the frame number when this PTD will be sent for ISO OUT or IN + * Bits 0 to 2 are don't care, only bits 3 to 7. + */ + frame_number = itd->framenumber; + td_info3 |= (0xff&(frame_number << 3)); + + /* Set the third DWORD */ + iso_ptd->td_info3 = td_info3; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD2 = 0x%08x\n", + iso_ptd->td_info3); + + /* + * Set the A bit of the fourth DWORD to 1 to indicate that this PTD is active. + * This have the same functionality with the V bit of DWORD0 + */ + iso_ptd->td_info4 = QHA_ACTIVE; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD3 = 0x%08x\n", + iso_ptd->td_info4); + + /* Set the fourth DWORD to specify which uSOFs the start split needs to be placed */ + iso_ptd->td_info5 = itd->ssplit; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD4 = 0x%08x\n", + iso_ptd->td_info5); + + /* + * Set the fifth DWORD to specify which uSOFs the complete split needs to be sent. + * This is VALID only for IN (since ISO transfers don't have handshake stages) + */ + iso_ptd->td_info6 = itd->csplit; + iso_dbg(ISO_DBG_DATA, "[phcd_iso_itd_to_ptd]: DWORD5 = 0x%08x\n", + iso_ptd->td_info6); + + iso_dbg(ISO_DBG_EXIT, "phcd_iso_itd_to_ptd exit\n"); + return iso_ptd; +} /* phcd_iso_itd_to_ptd */ + +/* + * phcd_iso_scheduling_info - Initializing the start split and complete split. + * + * phci_hcd *hcd + * - Main host controller driver structure + * struct ehci_qh *qhead + * - Contains information about the endpoint. + * unsigned long max_pkt + * - Maximum packet size that the endpoint in capable of handling + * unsigned long high_speed + * - Indicates if the bus is a high speed bus + * unsigned long ep_in + * - Inidcates if the endpoint is an IN endpoint + * + * API Description + * This is mainly responsible for: + * - Determining the number of start split needed during an OUT transaction or + * the number of complete splits needed during an IN transaction. + */ +unsigned long +phcd_iso_scheduling_info(phci_hcd * hcd, + struct ehci_qh *qhead, + unsigned long max_pkt, + unsigned long high_speed, unsigned long ep_in) +{ + unsigned long count, usof, temp; + + /* Local variable initialization */ + usof = 0x1; + + if (high_speed) { + qhead->csplit = 0; + + /* Always send high speed transfers in first uframes */ + qhead->ssplit = 0x1; + return 0; + } + + /* Determine how many 188 byte-transfers are needed to send all data */ + count = max_pkt / 188; + + /* + * Check is the data is not a factor of 188, if it is not then we need + * one more 188 transfer to move the last set of data less than 188. + */ + if (max_pkt % 188){ + count += 1; + } + + /* + * Remember that usof was initialized to 0x1 so that means + * that usof is always guranteed a value of 0x1 and then + * depending on the maxp, other bits of usof will also be set. + */ + for (temp = 0; temp < count; temp++){ + usof |= (0x1 << temp); + } + + if (ep_in) { + /* + * Send start split into first frame. + */ + qhead->ssplit = 0x1; + + /* + * Inidicate that we can send a complete split starting from + * the third uFrame to how much complete split is needed to + * retrieve all data. + * + * Of course, the first uFrame is reserved for the start split, the + * second is reserved for the TT to send the request and get some + * data. + */ + qhead->csplit = (usof << 2); + } else { + /* + * For ISO OUT we don't need to send out a complete split + * since we do not require and data coming in to us (since ISO + * do not have integrity checking/handshake). + * + * For start split we indicate that we send a start split from the + * first uFrame up to the the last uFrame needed to retrieve all + * data + */ + qhead->ssplit = usof; + qhead->csplit = 0; + } /* else for if(ep_in) */ + return 0; +} /* phcd_iso_scheduling_info */ + +/* + * phcd_iso_sitd_fill - Allocate memory from the PAYLOAD memory region + * + * phci_hcd *pHcd_st + * - Main host controller driver structure + * struct ehci_sitd *sitd + * - Isochronous Transfer Descriptor, contains elements as defined by the + * EHCI standard plus a few more specific elements. + * struct urb *urb + * - USB Request Block, contains information regarding the type and how much data + * is requested to be transferred. + * unsigned long packets + * - Total number of packets to completely transfer this ISO transfer request. + * + * API Description + * This is mainly responsible for: + * - Initialize the following elements of the ITS structure + * > sitd->length = length; -- the size of the request + * > sitd->multi = multi; -- the number of transactions for + * this EP per micro frame + * > sitd->hw_bufp[0] = buf_dma; -- The base address of the buffer where + * to put the data (this base address was + * the buffer provided plus the offset) + * - Allocating memory from the PAYLOAD memory area, where the data coming from + * the requesting party will be placed or data requested by the requesting party will + * be retrieved when it is available. + */ +unsigned long +phcd_iso_sitd_fill(phci_hcd * hcd, + struct ehci_sitd *sitd, + struct urb *urb, unsigned long packets) +{ + unsigned long length, offset, pipe; + unsigned long max_pkt; + dma_addr_t buff_dma; + struct isp1763_mem_addr *mem_addr; + +#ifdef COMMON_MEMORY + struct ehci_qh *qhead = NULL; +#endif + + iso_dbg(ISO_DBG_ENTRY, "phcd_iso_itd_fill entry\n"); + /* + * The value for both these variables are supplied by the one + * who submitted the URB. + */ + length = urb->iso_frame_desc[packets].length; + offset = urb->iso_frame_desc[packets].offset; + + /* Initialize the status and actual length of this packet */ + urb->iso_frame_desc[packets].actual_length = 0; + urb->iso_frame_desc[packets].status = -EXDEV; + + /* Buffer for this packet */ + buff_dma = (u32) ((unsigned char *) urb->transfer_buffer + offset); + + /* Memory for this packet */ + mem_addr = &sitd->mem_addr; + + pipe = urb->pipe; + max_pkt = usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)); + + max_pkt = max_pkt & 0x7FF; + + if ((length < 0) || (max_pkt < length)) { + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_fill Error]: No available memory.\n"); + return -ENOSPC; + } + sitd->buf_dma = buff_dma; + + +#ifndef COMMON_MEMORY + /* + * Allocate memory in the PAYLOAD memory region for the + * data buffer for this SITD + */ + phci_hcd_mem_alloc(length, mem_addr, 0); + if (length && ((mem_addr->phy_addr == 0) || (mem_addr->virt_addr == 0))) { + mem_addr = 0; + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_fill Error]: No payload memory available\n"); + return -ENOMEM; + } +#else +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qhead=urb->hcpriv; +#else + qhead = urb->ep->hcpriv; +#endif + if (qhead) { + + mem_addr->phy_addr = qhead->memory_addr.phy_addr + offset; + + mem_addr->virt_addr = qhead->memory_addr.phy_addr + offset; + } else { + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_fill Error]: No payload memory available\n"); + return -ENOMEM; + } + + +#endif + /* Length of this packet */ + sitd->length = length; + + /* Buffer address, one ptd per packet */ + sitd->hw_bufp[0] = buff_dma; + + iso_dbg(ISO_DBG_EXIT, "phcd_iso_sitd_fill exit\n"); + return 0; +} + +/* + * phcd_iso_itd_fill - Allocate memory from the PAYLOAD memory region + * + * phci_hcd *pHcd_st + * - Main host controller driver structure + * struct ehci_itd *itd + * - Isochronous Transfer Descriptor, contains elements as defined by the + * EHCI standard plus a few more IC specific elements. + * struct urb *urb + * - USB Request Block, contains information regarding the type and how much data + * is requested to be transferred. + * unsigned long packets + * - Total number of packets to completely transfer this ISO transfer request. + * + * API Description + * This is mainly responsible for: + * - Initialize the following elements of the ITS structure + * > itd->length = length; -- the size of the request + * > itd->multi = multi; -- the number of transactions for + * this EP per micro frame + * > itd->hw_bufp[0] = buf_dma; -- The base address of the buffer where + * to put the data (this base address was + * the buffer provided plus the offset) + * - Allocating memory from the PAYLOAD memory area, where the data coming from + * the requesting party will be placed or data requested by the requesting party will + * be retrieved when it is available. + */ +unsigned long +phcd_iso_itd_fill(phci_hcd * hcd, + struct ehci_itd *itd, + struct urb *urb, + unsigned long packets, unsigned char numofPkts) +{ + unsigned long length, offset, pipe; + unsigned long max_pkt, mult; + dma_addr_t buff_dma; + struct isp1763_mem_addr *mem_addr; +#ifdef COMMON_MEMORY + struct ehci_qh *qhead = NULL; +#endif + int i = 0; + + iso_dbg(ISO_DBG_ENTRY, "phcd_iso_itd_fill entry\n"); + for (i = 0; i < 8; i++){ + itd->hw_transaction[i] = 0; + } + /* + * The value for both these variables are supplied by the one + * who submitted the URB. + */ + length = urb->iso_frame_desc[packets].length; + offset = urb->iso_frame_desc[packets].offset; + + /* Initialize the status and actual length of this packet */ + urb->iso_frame_desc[packets].actual_length = 0; + urb->iso_frame_desc[packets].status = -EXDEV; + + /* Buffer for this packet */ + buff_dma = cpu_to_le32((unsigned char *) urb->transfer_buffer + offset); + + /* Memory for this packet */ + mem_addr = &itd->mem_addr; + + pipe = urb->pipe; + max_pkt = usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe)); + + mult = 1 + ((max_pkt >> 11) & 0x3); + max_pkt = max_pkt & 0x7FF; + max_pkt *= mult; + + if ((length < 0) || (max_pkt < length)) { + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_fill Error]: No available memory.\n"); + return -ENOSPC; + } + itd->buf_dma = buff_dma; + for (i = packets + 1; i < numofPkts + packets; i++) + length += urb->iso_frame_desc[i].length; + + /* + * Allocate memory in the PAYLOAD memory region for the + * data buffer for this ITD + */ +#ifndef COMMON_MEMORY + + phci_hcd_mem_alloc(length, mem_addr, 0); + if (length && ((mem_addr->phy_addr == 0) || (mem_addr->virt_addr == 0))) { + mem_addr = 0; + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_fill Error]: No payload memory available\n"); + return -ENOMEM; + } +#else +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + qhead = urb->ep->hcpriv; +#else + qhead=urb->hcpriv; +#endif + if (qhead) { + + mem_addr->phy_addr = qhead->memory_addr.phy_addr + offset; + + mem_addr->virt_addr = qhead->memory_addr.phy_addr + offset; + } else { + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_fill Error]: No payload memory available\n"); + return -ENOMEM; + } + + +#endif + /* Length of this packet */ + itd->length = length; + + /* Number of transaction per uframe */ + itd->multi = mult; + + /* Buffer address, one ptd per packet */ + itd->hw_bufp[0] = buff_dma; + + iso_dbg(ISO_DBG_EXIT, "phcd_iso_itd_fill exit\n"); + return 0; +} /* phcd_iso_itd_fill */ + +/* + * phcd_iso_get_sitd_ptd_index - Allocate an ISO PTD from the ISO PTD map list + * + * phci_hcd *hcd + * - Main host controller driver structure + * struct ehci_sitd *sitd + * - Isochronous Transfer Descriptor, contains elements as defined by the + * EHCI standard plus a few more specific elements. + * + * API Description + * This is mainly responsible for: + * - Allocating an ISO PTD from the ISO PTD map list + * - Set the equivalent bit of the allocated PTD to active + * in the bitmap so that this PTD will be included into + * the periodic schedule + */ +void +phcd_iso_get_sitd_ptd_index(phci_hcd * hcd, struct ehci_sitd *sitd) +{ + td_ptd_map_buff_t *ptd_map_buff; + unsigned long buff_type, max_ptds; + unsigned char sitd_index, bitmap; + + /* Local variable initialization */ + bitmap = 0x1; + buff_type = td_ptd_pipe_x_buff_type[TD_PTD_BUFF_TYPE_ISTL]; + ptd_map_buff = (td_ptd_map_buff_t *) & (td_ptd_map_buff[buff_type]); + max_ptds = ptd_map_buff->max_ptds; + sitd->sitd_index = TD_PTD_INV_PTD_INDEX; + + for (sitd_index = 0; sitd_index < max_ptds; sitd_index++) { + /* + * ISO have 32 PTDs, the first thing to do is look for a free PTD. + */ + if (ptd_map_buff->map_list[sitd_index].state == TD_PTD_NEW) { + iso_dbg(ISO_DBG_INFO, + "[phcd_iso_get_itd_ptd_index] There's a free PTD No. %d\n", + sitd_index); + /* + * Determine if this is a newly allocated SITD by checking the + * itd_index, since it was set to TD_PTD_INV_PTD_INDEX during + * initialization + */ + if (sitd->sitd_index == TD_PTD_INV_PTD_INDEX) { + sitd->sitd_index = sitd_index; + } + + /* Once there is a free slot, indicate that it is already taken */ + ptd_map_buff->map_list[sitd_index].datatoggle = 0; + ptd_map_buff->map_list[sitd_index].state = + TD_PTD_ACTIVE; + ptd_map_buff->map_list[sitd_index].qtd = NULL; + + /* Put a connection to the SITD with the PTD maplist */ + ptd_map_buff->map_list[sitd_index].sitd = sitd; + ptd_map_buff->map_list[sitd_index].itd = NULL; + ptd_map_buff->map_list[sitd_index].qh = NULL; + + /* ptd_bitmap just holds the bit assigned to this PTD. */ + ptd_map_buff->map_list[sitd_index].ptd_bitmap = + bitmap << sitd_index; + + phci_hcd_fill_ptd_addresses(&ptd_map_buff-> + map_list[sitd_index], sitd->sitd_index, + buff_type); + + /* + * Indicate that this SITD is the last in the list and update + * the number of active PTDs + */ + ptd_map_buff->map_list[sitd_index].lasttd = 0; + ptd_map_buff->total_ptds++; + + + ptd_map_buff->active_ptd_bitmap |= + (bitmap << sitd_index); + ptd_map_buff->pending_ptd_bitmap |= (bitmap << sitd_index); + break; + } /* if(ptd_map_buff->map_list[sitd_index].state == TD_PTD_NEW) */ + } /* for(itd_index = 0; itd_index < max_ptds; itd_index++) */ + return; +} + +/* + * phcd_iso_get_itd_ptd_index - Allocate an ISO PTD from the ISO PTD map list + * + * phci_hcd *hcd + * - Main host controller driver structure + * struct ehci_itd *itd + * - Isochronous Transfer Descriptor, contains elements as defined by the + * EHCI standard plus a few more IC specific elements. + * + * API Description + * This is mainly responsible for: + * - Allocating an ISO PTD from the ISO PTD map list + * - Set the equivalent bit of the allocated PTD to active + * in the bitmap so that this PTD will be included into + * the periodic schedule + */ +void +phcd_iso_get_itd_ptd_index(phci_hcd * hcd, struct ehci_itd *itd) +{ + td_ptd_map_buff_t *ptd_map_buff; + unsigned long buff_type, max_ptds; + unsigned char itd_index, bitmap; + + /* Local variable initialization */ + bitmap = 0x1; + buff_type = td_ptd_pipe_x_buff_type[TD_PTD_BUFF_TYPE_ISTL]; + ptd_map_buff = (td_ptd_map_buff_t *) & (td_ptd_map_buff[buff_type]); + max_ptds = ptd_map_buff->max_ptds; + + itd->itd_index = TD_PTD_INV_PTD_INDEX; + + for (itd_index = 0; itd_index < max_ptds; itd_index++) { + /* + * ISO have 32 PTDs, the first thing to do is look for a free PTD. + */ + if (ptd_map_buff->map_list[itd_index].state == TD_PTD_NEW) { + /* + * Determine if this is a newly allocated ITD by checking the + * itd_index, since it was set to TD_PTD_INV_PTD_INDEX during + * initialization + */ + if (itd->itd_index == TD_PTD_INV_PTD_INDEX) { + itd->itd_index = itd_index; + } + + /* Once there is a free slot, indicate that it is already taken */ + ptd_map_buff->map_list[itd_index].datatoggle = 0; + ptd_map_buff->map_list[itd_index].state = TD_PTD_ACTIVE; + ptd_map_buff->map_list[itd_index].qtd = NULL; + + /* Put a connection to the ITD with the PTD maplist */ + ptd_map_buff->map_list[itd_index].itd = itd; + ptd_map_buff->map_list[itd_index].qh = NULL; + + /* ptd_bitmap just holds the bit assigned to this PTD. */ + ptd_map_buff->map_list[itd_index].ptd_bitmap = + bitmap << itd_index; + + phci_hcd_fill_ptd_addresses(&ptd_map_buff-> + map_list[itd_index], + itd->itd_index, buff_type); + + /* + * Indicate that this ITD is the last in the list and update + * the number of active PTDs + */ + ptd_map_buff->map_list[itd_index].lasttd = 0; + ptd_map_buff->total_ptds++; + + ptd_map_buff->active_ptd_bitmap |= + (bitmap << itd_index); + ptd_map_buff->pending_ptd_bitmap |= (bitmap << itd_index); + break; + } /* if(ptd_map_buff->map_list[itd_index].state == TD_PTD_NEW) */ + } /* for(itd_index = 0; itd_index < max_ptds; itd_index++) */ + return; +} /* phcd_iso_get_itd_ptd_index */ + +/* + * phcd_iso_sitd_free_list - Free memory used by SITDs in SITD list + * + * phci_hcd *hcd + * - Main host controller driver structure + * struct urb *urb + * - USB Request Block, contains information regarding the type and how much data + * is requested to be transferred. + * unsigned long status + * - Variable provided by the calling routine that contain the status of the + * SITD list. + * + * API Description + * This is mainly responsible for: + * - Cleaning up memory used by each SITD in the SITD list + */ +void +phcd_iso_sitd_free_list(phci_hcd * hcd, struct urb *urb, unsigned long status) +{ + td_ptd_map_buff_t *ptd_map_buff; + struct ehci_sitd *first_sitd, *next_sitd, *sitd; + td_ptd_map_t *td_ptd_map; + + /* Local variable initialization */ + ptd_map_buff = &(td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]); + first_sitd = (struct ehci_sitd *) urb->hcpriv; + sitd = first_sitd; + + /* + * Check if there is only one SITD, if so immediately + * go and clean it up. + */ + if (sitd->hw_next == EHCI_LIST_END) { + if (sitd->sitd_index != TD_PTD_INV_PTD_INDEX) { + td_ptd_map = &ptd_map_buff->map_list[sitd->sitd_index]; + td_ptd_map->state = TD_PTD_NEW; + } + + if (status != -ENOMEM) { + phci_hcd_mem_free(&sitd->mem_addr); + } + + list_del(&sitd->sitd_list); + qha_free(qha_cache, sitd); + + urb->hcpriv = 0; + return; + } + /* if(sitd->hw_next == EHCI_LIST_END) */ + while (1) { + /* Get the SITD following the head SITD */ + next_sitd = (struct ehci_sitd *) (sitd->hw_next); + if (next_sitd->hw_next == EHCI_LIST_END) { + /* + * If the next SITD is the end of the list, check if space have + * already been allocated in the PTD array. + */ + if (next_sitd->sitd_index != TD_PTD_INV_PTD_INDEX) { + /* Free up its allocation */ + td_ptd_map = + &ptd_map_buff->map_list[next_sitd-> + sitd_index]; + td_ptd_map->state = TD_PTD_NEW; + } + + /* + * If the error is not about memory allocation problems, then + * free up the memory used. + */ + if (status != -ENOMEM) { + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_free_list Error]: Memory not available\n"); + phci_hcd_mem_free(&next_sitd->mem_addr); + } + + /* Remove from the SITD list and free up space allocated for SITD structure */ + list_del(&next_sitd->sitd_list); + qha_free(qha_cache, next_sitd); + break; + } + + /* if(next_itd->hw_next == EHCI_LIST_END) */ + /* + * If SITD is not the end of the list, it only means that it already have everything allocated + * and there is no need to check which procedure failed. So just free all resourcs immediately + */ + sitd->hw_next = next_sitd->hw_next; + + td_ptd_map = &ptd_map_buff->map_list[next_sitd->sitd_index]; + td_ptd_map->state = TD_PTD_NEW; + phci_hcd_mem_free(&next_sitd->mem_addr); + list_del(&next_sitd->sitd_list); + qha_free(qha_cache, next_sitd); + } /* while(1) */ + + /* Now work on the head SITD, it is the last one processed. */ + if (first_sitd->sitd_index != TD_PTD_INV_PTD_INDEX) { + td_ptd_map = &ptd_map_buff->map_list[first_sitd->sitd_index]; + td_ptd_map->state = TD_PTD_NEW; + } + + if (status != -ENOMEM) { + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_free_list Error]: No memory\n"); + phci_hcd_mem_free(&first_sitd->mem_addr); + } + + list_del(&first_sitd->sitd_list); + qha_free(qha_cache, first_sitd); + urb->hcpriv = 0; + return; +} + +/* + * phcd_iso_itd_free_list - Free memory used by ITDs in ITD list + * + * phci_hcd *hcd + * - Main host controller driver structure + * struct urb *urb + * - USB Request Block, contains information regarding the type and how much data + * is requested to be transferred. + * unsigned long status + * - Variable provided by the calling routine that contain the status of the + * ITD list. + * + * API Description + * This is mainly responsible for: + * - Cleaning up memory used by each ITD in the ITD list + */ +void +phcd_iso_itd_free_list(phci_hcd * hcd, struct urb *urb, unsigned long status) +{ + td_ptd_map_buff_t *ptd_map_buff; + struct ehci_itd *first_itd, *next_itd, *itd; + td_ptd_map_t *td_ptd_map; + + /* Local variable initialization */ + ptd_map_buff = &(td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]); + first_itd = (struct ehci_itd *) urb->hcpriv; + itd = first_itd; + + /* + * Check if there is only one ITD, if so immediately + * go and clean it up. + */ + if (itd->hw_next == EHCI_LIST_END) { + if (itd->itd_index != TD_PTD_INV_PTD_INDEX) { + td_ptd_map = &ptd_map_buff->map_list[itd->itd_index]; + td_ptd_map->state = TD_PTD_NEW; + } + + if (status != -ENOMEM) { + phci_hcd_mem_free(&itd->mem_addr); + } + + list_del(&itd->itd_list); + qha_free(qha_cache, itd); + + urb->hcpriv = 0; + return; + } + /* if(itd->hw_next == EHCI_LIST_END) */ + while (1) { + /* Get the ITD following the head ITD */ + next_itd = (struct ehci_itd *) le32_to_cpu(itd->hw_next); + if (next_itd->hw_next == EHCI_LIST_END) { + /* + * If the next ITD is the end of the list, check if space have + * already been allocated in the PTD array. + */ + if (next_itd->itd_index != TD_PTD_INV_PTD_INDEX) { + /* Free up its allocation */ + td_ptd_map = + &ptd_map_buff->map_list[next_itd-> + itd_index]; + td_ptd_map->state = TD_PTD_NEW; + } + + /* + * If the error is not about memory allocation problems, then + * free up the memory used. + */ + if (status != -ENOMEM) { + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_free_list Error]: Memory not available\n"); + phci_hcd_mem_free(&next_itd->mem_addr); + } + + /* Remove from the ITD list and free up space allocated for ITD structure */ + list_del(&next_itd->itd_list); + qha_free(qha_cache, next_itd); + break; + } + + /* if(next_itd->hw_next == EHCI_LIST_END) */ + /* + * If ITD is not the end of the list, it only means that it already have everything allocated + * and there is no need to check which procedure failed. So just free all resourcs immediately + */ + itd->hw_next = next_itd->hw_next; + + td_ptd_map = &ptd_map_buff->map_list[next_itd->itd_index]; + td_ptd_map->state = TD_PTD_NEW; + phci_hcd_mem_free(&next_itd->mem_addr); + list_del(&next_itd->itd_list); + qha_free(qha_cache, next_itd); + } /* while(1) */ + + /* Now work on the head ITD, it is the last one processed. */ + if (first_itd->itd_index != TD_PTD_INV_PTD_INDEX) { + td_ptd_map = &ptd_map_buff->map_list[first_itd->itd_index]; + td_ptd_map->state = TD_PTD_NEW; + } + + if (status != -ENOMEM) { + iso_dbg(ISO_DBG_ERR, + "[phcd_iso_itd_free_list Error]: No memory\n"); + phci_hcd_mem_free(&first_itd->mem_addr); + } + + list_del(&first_itd->itd_list); + qha_free(qha_cache, first_itd); + urb->hcpriv = 0; + return; +} /* phcd_iso_itd_free_list */ + +void +phcd_clean_iso_qh(phci_hcd * hcd, struct ehci_qh *qh) +{ + unsigned int i = 0; + u16 skipmap=0; + struct ehci_sitd *sitd; + struct ehci_itd *itd; + + iso_dbg(ISO_DBG_ERR, "phcd_clean_iso_qh \n"); + if (!qh){ + return; + } + skipmap = isp1763_reg_read16(hcd->dev, hcd->regs.isotdskipmap, skipmap); + skipmap |= qh->periodic_list.ptdlocation; + isp1763_reg_write16(hcd->dev, hcd->regs.isotdskipmap, skipmap); +#ifdef COMMON_MEMORY + phci_hcd_mem_free(&qh->memory_addr); +#endif + for (i = 0; i < 16 && qh->periodic_list.ptdlocation; i++) { + if (qh->periodic_list.ptdlocation & (0x1 << i)) { + printk("[phcd_clean_iso_qh] : %x \n", + qh->periodic_list.high_speed); + + qh->periodic_list.ptdlocation &= ~(0x1 << i); + + if (qh->periodic_list.high_speed == 0) { + if (td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].sitd) { + + printk("SITD found \n"); + sitd = td_ptd_map_buff + [TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].sitd; +#ifndef COMMON_MEMORY + phci_hcd_mem_free(&sitd->mem_addr); +#endif + /* + if(sitd->urb) + urb=sitd->urb; + */ + sitd->urb = NULL; + td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].state = TD_PTD_NEW; + td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].sitd = NULL; + qha_free(qha_cache, sitd); + } + } else { + if (td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].itd) { + + printk("ITD found \n"); + itd = td_ptd_map_buff + [TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].itd; +#ifdef COMMON_MEMORY + phci_hcd_mem_free(&itd->mem_addr); +#endif + + /* + if(itd->urb) + urb=itd->urb; + */ + itd->urb = NULL; + td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].state = TD_PTD_NEW; + td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].itd = NULL; + qha_free(qha_cache, itd); + } + } + + } + } + + +} + + +/* + * phcd_store_urb_pending - store requested URB into a queue + * + * phci_hcd *hcd + * - Main host controller driver structure + * struct urb *urb + * - USB Request Block, contains information regarding the type and how much data + * is requested to be transferred. + * unsigned long *status + * - Variable provided by the calling routine that will contain the status of the + * phcd_submit_iso actions + * + * API Description + * This is mainly responsible for: + * - Store URB into a queue + * - If ther's enough free PTD slots , repairing the PTDs + */ +void phcd_clean_periodic_ep(void){ + periodic_ep[0] = NULL; + periodic_ep[1] = NULL; +} + +int +phcd_clean_urb_pending(phci_hcd * hcd, struct urb *urb) +{ + unsigned int i = 0; + struct ehci_qh *qhead; + struct ehci_sitd *sitd; + struct ehci_itd *itd; + u16 skipmap=0;; + + iso_dbg(ISO_DBG_ENTRY, "[phcd_clean_urb_pending] : Enter\n"); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qhead=urb->hcpriv; + if (periodic_ep[0] == qhead->ep) { + periodic_ep[0] = NULL; + + } + + if (periodic_ep[1] == qhead->ep) { + periodic_ep[1] = NULL; + } +#else + qhead = urb->ep->hcpriv; + if (periodic_ep[0] == urb->ep) { + periodic_ep[0] = NULL; + + } + + if (periodic_ep[1] == urb->ep) { + periodic_ep[1] = NULL; + } +#endif + if (!qhead) { + return 0; + } + skipmap = isp1763_reg_read16(hcd->dev, hcd->regs.isotdskipmap, skipmap); + skipmap |= qhead->periodic_list.ptdlocation; + isp1763_reg_write16(hcd->dev, hcd->regs.isotdskipmap, skipmap); +#ifdef COMMON_MEMORY + phci_hcd_mem_free(&qhead->memory_addr); +#endif + + for (i = 0; i < 16 && qhead->periodic_list.ptdlocation; i++) { + + qhead->periodic_list.ptdlocation &= ~(0x1 << i); + + if (qhead->periodic_list.ptdlocation & (0x1 << i)) { + + printk("[phcd_clean_urb_pending] : %x \n", + qhead->periodic_list.high_speed); + + if (qhead->periodic_list.high_speed == 0) { + + if (td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].sitd) { + + sitd = td_ptd_map_buff + [TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].sitd; +#ifndef COMMON_MEMORY + phci_hcd_mem_free(&sitd->mem_addr); +#endif + td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].state = TD_PTD_NEW; + td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].sitd = NULL; + qha_free(qha_cache, sitd); + } + } else { + + if (td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].itd) { + + itd = td_ptd_map_buff + [TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].itd; +#ifdef COMMON_MEMORY + phci_hcd_mem_free(&itd->mem_addr); +#endif + td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].state = TD_PTD_NEW; + td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]. + map_list[i].itd = NULL; + qha_free(qha_cache, itd); + } + } + + } + + } + INIT_LIST_HEAD(&qhead->periodic_list.sitd_itd_head); + iso_dbg(ISO_DBG_ENTRY, "[phcd_clean_urb_pending] : Exit\n"); + return 0; +} + + + +int +phcd_store_urb_pending(phci_hcd * hcd, int index, struct urb *urb, int *status) +{ + unsigned int uiNumofPTDs = 0; + unsigned int uiNumofSlots = 0; + unsigned int uiMult = 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + iso_dbg(ISO_DBG_ENTRY, "[phcd_store_urb_pending] : Enter\n"); + if (urb != NULL) { + if (periodic_ep[0] != urb->ep && periodic_ep[1] != urb->ep) { + if (periodic_ep[0] == NULL) { + // printk("storing in 0 %x %x\n",urb,urb->pipe); + periodic_ep[0] = urb->ep; + } else if (periodic_ep[1] == NULL) { + printk("storing in 1\n"); + periodic_ep[1] = urb->ep; + usb_hcd_link_urb_to_ep(&(hcd->usb_hcd), urb); + return -1; + } else { + iso_dbg(ISO_DBG_ERR, + "Support only 2 ISO endpoints simultaneously \n"); + *status = -1; + return -1; + } + } + usb_hcd_link_urb_to_ep(&(hcd->usb_hcd), urb); + iso_dbg(ISO_DBG_DATA, + "[phcd_store_urb_pending] : Add an urb into gstUrb_pending array at index : %d\n", + giUrbCount); + giUrbCount++; + } else { + + iso_dbg(ISO_DBG_ENTRY, + "[phcd_store_urb_pending] : getting urb from list \n"); + if (index > 0 && index < 2) { + if (periodic_ep[index - 1]){ + urb = container_of(periodic_ep[index - 1]-> + urb_list.next, struct urb, + urb_list); + } + } else { + iso_dbg(ISO_DBG_ERR, " Unknown enpoints Error \n"); + *status = -1; + return -1; + } + + } + + + if ((urb != NULL && (urb->ep->urb_list.next == &urb->urb_list))){ + iso_dbg(ISO_DBG_DATA, + "[phcd_store_urb_pending] : periodic_sched : %d\n", + hcd->periodic_sched); + iso_dbg(ISO_DBG_DATA, + "[phcd_store_urb_pending] : number_of_packets : %d\n", + urb->number_of_packets); + iso_dbg(ISO_DBG_DATA, + "[phcd_store_urb_pending] : Maximum PacketSize : %d\n", + usb_maxpacket(urb->dev,urb->pipe, usb_pipeout(urb->pipe))); + /*if enough free slots */ + if (urb->dev->speed == USB_SPEED_FULL) { /*for FULL SPEED */ + // if (hcd->periodic_sched < + // MAX_PERIODIC_SIZE - urb->number_of_packets) { + if(1){ + if (phcd_submit_iso(hcd, + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + struct usb_host_endpoint *ep, + #endif + urb, + ( unsigned long *) &status) == 0) { + pehci_hcd_iso_schedule(hcd, urb); + } else{ + //*status = 0; + } + } + } else if (urb->dev->speed == USB_SPEED_HIGH) { /*for HIGH SPEED */ + /*number of slots for 1 PTD */ + uiNumofSlots = NUMMICROFRAME / urb->interval; + /*max packets size */ + uiMult = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + /*mult */ + uiMult = 1 + ((uiMult >> 11) & 0x3); + /*number of PTDs need to schedule for this PTD */ + uiNumofPTDs = + (urb->number_of_packets / uiMult) / + uiNumofSlots; + if ((urb->number_of_packets / uiMult) % uiNumofSlots != 0){ + uiNumofPTDs += 1; + } + + iso_dbg(ISO_DBG_DATA, + "[phcd_store_urb_pending] : interval : %d\n", + urb->interval); + iso_dbg(ISO_DBG_DATA, + "[phcd_store_urb_pending] : uiMult : %d\n", + uiMult); + iso_dbg(ISO_DBG_DATA, + "[phcd_store_urb_pending] : uiNumofPTDs : %d\n", + uiNumofPTDs); + + if (hcd->periodic_sched <= + MAX_PERIODIC_SIZE - uiNumofPTDs) { + + if (phcd_submit_iso(hcd, + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + struct usb_host_endpoint *ep, + #endif + urb, (unsigned long *) &status)== 0) { + + pehci_hcd_iso_schedule(hcd, urb); + } + } else{ + *status = 0; + } + } + } else{ + iso_dbg(ISO_DBG_DATA, + "[phcd_store_urb_pending] : nextUrb is NULL\n"); + } +#endif + iso_dbg(ISO_DBG_ENTRY, "[phcd_store_urb_pending] : Exit\n"); + return 0; +} + +/* + * phcd_submit_iso - ISO transfer URB submit routine + * + * phci_hcd *hcd + * - Main host controller driver structure + * struct urb *urb + * - USB Request Block, contains information regarding the type and how much data + * is requested to be transferred. + * unsigned long *status + * - Variable provided by the calling routine that will contain the status of the + * phcd_submit_iso actions + * + * API Description + * This is mainly responsible for: + * - Allocating memory for the endpoint information structure (pQHead_st) + * - Requesting for bus bandwidth from the USB core + * - Allocating and initializing Payload and PTD memory + */ +unsigned long +phcd_submit_iso(phci_hcd * hcd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + struct usb_host_endpoint *ep, +#else +#endif + struct urb *urb, unsigned long *status) +{ + struct _periodic_list *periodic_list; + struct hcd_dev *dev; + struct ehci_qh *qhead; + struct ehci_itd *itd, *prev_itd; + struct ehci_sitd *sitd, *prev_sitd; + struct list_head *sitd_itd_list; + unsigned long ep_in, max_pkt, mult; + unsigned long bus_time, high_speed, start_frame; + unsigned long temp; + unsigned long packets; + /*for high speed device */ + unsigned int iMicroIndex = 0; + unsigned int iNumofSlots = 0; + unsigned int iNumofPTDs = 0; + unsigned int iPTDIndex = 0; + unsigned int iNumofPks = 0; + int iPG = 0; + dma_addr_t buff_dma; + unsigned long length, offset; + int i = 0; + + iso_dbg(ISO_DBG_ENTRY, "phcd_submit_iso Entry\n"); + + *status = 0; + /* Local variable initialization */ + high_speed = 0; + periodic_list = &hcd->periodic_list[0]; + dev = (struct hcd_dev *) urb->hcpriv; + urb->hcpriv = (void *) 0; + prev_itd = (struct ehci_itd *) 0; + itd = (struct ehci_itd *) 0; + prev_sitd = (struct ehci_sitd *) 0; + sitd = (struct ehci_sitd *) 0; + start_frame = 0; + + ep_in = usb_pipein(urb->pipe); + + /* + * Take the endpoint, if there is still no memory allocated + * for it allocate some and indicate this is for ISO. + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qhead = ep->hcpriv; +#else + qhead = urb->ep->hcpriv; +#endif + if (!qhead) { + + qhead = phci_hcd_qh_alloc(hcd); + if (qhead == 0) { + iso_dbg(ISO_DBG_ERR, + "[phcd_submit_iso Error]: Not enough memory\n"); + return -ENOMEM; + } + + qhead->type = TD_PTD_BUFF_TYPE_ISTL; + INIT_LIST_HEAD(&qhead->periodic_list.sitd_itd_head); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qhead->ep=ep; + ep->hcpriv = qhead; + urb->hcpriv=qhead; +#else + urb->ep->hcpriv = qhead; +#endif + } + + urb->hcpriv=qhead; + + /* if(!qhead) */ + /* + * Get the number of additional packets that the endpoint can support during a + * single microframe. + */ + max_pkt = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + + /* + * We need to add 1 since our Multi starts with 1 instead of the USB specs defined + * zero (0). + */ + mult = 1 + ((max_pkt >> 11) & 0x3); + + /* This is the actual length per for the whole transaction */ + max_pkt *= mult; + + /* Check bandwidth */ + bus_time = 0; + + if (urb->dev->speed == USB_SPEED_FULL) { + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + if (urb->bandwidth == 0) { + bus_time = usb_check_bandwidth(urb->dev, urb); + if (bus_time < 0) { + usb_dec_dev_use(urb->dev); + *status = bus_time; + return *status; + } + } +#else +#endif + } else { /*HIGH SPEED */ + + high_speed = 1; + + /* + * Calculate bustime as dictated by the USB Specs Section 5.11.3 + * for high speed ISO + */ + bus_time = 633232L; + bus_time += + (2083L * ((3167L + BitTime(max_pkt) * 1000L) / 1000L)); + bus_time = bus_time / 1000L; + bus_time += BW_HOST_DELAY; + bus_time = NS_TO_US(bus_time); + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + usb_claim_bandwidth(urb->dev, urb, bus_time, 1); +#else +#endif + + qhead->periodic_list.ptdlocation = 0; + /* Initialize the start split (ssplit) and complete split (csplit) variables of qhead */ + if (phcd_iso_scheduling_info(hcd, qhead, max_pkt, high_speed, ep_in) < + 0) { + + iso_dbg(ISO_DBG_ERR, + "[phcd_submit_iso Error]: No space available\n"); + return -ENOSPC; + } + + if (urb->dev->speed == USB_SPEED_HIGH) { + iNumofSlots = NUMMICROFRAME / urb->interval; + /*number of PTDs need to schedule for this PTD */ + iNumofPTDs = (urb->number_of_packets / mult) / iNumofSlots; + if ((urb->number_of_packets / mult) % iNumofSlots != 0){ + /*get remainder */ + iNumofPTDs += 1; + } + } + if (urb->iso_frame_desc[0].offset != 0) { + *status = -EINVAL; + iso_dbg(ISO_DBG_ERR, + "[phcd_submit_iso Error]: Invalid value\n"); + return *status; + } + if (1) { + /* Calculate the current frame number */ + if (0){ + if (urb->transfer_flags & URB_ISO_ASAP){ + start_frame = + isp1763_reg_read16(hcd->dev, + hcd->regs.frameindex, + start_frame); + } else { + start_frame = urb->start_frame; + } + } + + start_frame = + isp1763_reg_read16(hcd->dev, hcd->regs.frameindex, + start_frame); + + /* The only valid bits of the frame index is the lower 14 bits. */ + + /* + * Remove the count for the micro frame (uSOF) and just leave the + * count for the frame (SOF). Since 1 SOF is equal to 8 uSOF then + * shift right by three is like dividing it by 8 (each shift is divide by two) + */ + start_frame >>= 3; + if (urb->dev->speed != USB_SPEED_HIGH){ + start_frame += 1; + }else{ + start_frame += 2; + } + start_frame = start_frame & PTD_FRAME_MASK; + temp = start_frame; + if (urb->dev->speed != USB_SPEED_HIGH) { + qhead->next_uframe = + start_frame + urb->number_of_packets; + } else { + qhead->next_uframe = start_frame + iNumofPTDs; + } + qhead->next_uframe %= PTD_FRAME_MASK; + iso_dbg(ISO_DBG_DATA, "[phcd_submit_iso]: startframe = %ld\n", + start_frame); + } else { + /* + * The periodic frame list size is only 32 elements deep, so we need + * the frame index to be less than or equal to 32 (actually 31 if we + * start from 0) + */ + start_frame = (qhead->next_uframe) % PTD_FRAME_MASK; + if (urb->dev->speed != USB_SPEED_HIGH){ + qhead->next_uframe = + start_frame + urb->number_of_packets; + iNumofPTDs=urb->number_of_packets; + } else { + qhead->next_uframe = start_frame + iNumofPTDs; + } + + qhead->next_uframe %= PTD_FRAME_MASK; + } + + + iso_dbg(ISO_DBG_DATA, "[phcd_submit_iso]: Start frame index: %ld\n", + start_frame); + iso_dbg(ISO_DBG_DATA, "[phcd_submit_iso]: Max packet: %d\n", + (int) max_pkt); + +#ifdef COMMON_MEMORY + if(urb->number_of_packets>8 && urb->dev->speed!=USB_SPEED_HIGH) + phci_hcd_mem_alloc(8*max_pkt, &qhead->memory_addr, 0); + else + phci_hcd_mem_alloc(urb->transfer_buffer_length, &qhead->memory_addr, 0); + if (urb->transfer_buffer_length && ((qhead->memory_addr.phy_addr == 0) + || (qhead->memory_addr.virt_addr ==0))) { + iso_dbg(ISO_DBG_ERR, + "[URB FILL MEMORY Error]: No payload memory available\n"); + return -ENOMEM; + } +#endif + + if (urb->dev->speed != USB_SPEED_HIGH) { + iNumofPks = urb->number_of_packets; + qhead->totalptds=urb->number_of_packets; + qhead->actualptds=0; + + /* Make as many tds as number of packets */ + for (packets = 0; packets < urb->number_of_packets; packets++) { + /* + * Allocate memory for the SITD data structure and initialize it. + * + * This data structure follows the format of the SITD + * structure defined by the EHCI standard on the top part + * but also contains specific elements in the bottom + * part + */ + sitd = kmalloc(sizeof(*sitd), GFP_ATOMIC); + if (!sitd) { + *status = -ENOMEM; + if (((int)(qhead->next_uframe - + urb->number_of_packets)) < 0){ + /*plus max PTDs*/ + qhead->next_uframe = qhead->next_uframe + PTD_PERIODIC_SIZE; + + } + qhead->next_uframe -= urb->number_of_packets; + + /* Handle SITD list cleanup */ + if (urb->hcpriv) { + phcd_iso_sitd_free_list(hcd, urb, + *status); + } + iso_dbg(ISO_DBG_ERR, + "[phcd_submit_iso Error]: No memory available\n"); + return *status; + } + + memset(sitd, 0, sizeof(struct ehci_sitd)); + + INIT_LIST_HEAD(&sitd->sitd_list); + + sitd->sitd_dma = (u32) (sitd); + sitd->urb = urb; + + /* + * Indicate that this SITD is the last in the list. + * + * Also set the itd_index to TD_PTD_INV_PTD_INDEX + * (0xFFFFFFFF). This would indicate when we allocate + * a PTD that this SITD did not have a PTD allocated + * before. + */ + + sitd->hw_next = EHCI_LIST_END; + sitd->sitd_index = TD_PTD_INV_PTD_INDEX; + + /* This SITD will go into this frame */ + sitd->framenumber = start_frame + packets; + sitd->start_frame = temp + packets; + + /* Number of the packet */ + sitd->index = packets; + + sitd->framenumber = sitd->framenumber & PTD_FRAME_MASK; + sitd->ssplit = qhead->ssplit; + sitd->csplit = qhead->csplit; + + /* Initialize the following elements of the ITS structure + * > sitd->length = length; -- the size of the request + * > sitd->multi = multi; -- the number of transactions for + * this EP per micro frame + * > sitd->hw_bufp[0] = buf_dma; -- The base address of the buffer where + * to put the data (this base address was + * the buffer provided plus the offset) + * And then, allocating memory from the PAYLOAD memory area, where the data + * coming from the requesting party will be placed or data requested by the + * requesting party will be retrieved when it is available. + */ + *status = phcd_iso_sitd_fill(hcd, sitd, urb, packets); + + if (*status != 0) { + if (((int)(qhead->next_uframe - + urb->number_of_packets)) < 0){ + /*plus max PTDs*/ + qhead->next_uframe = qhead->next_uframe + + PTD_PERIODIC_SIZE; + } + qhead->next_uframe -= urb->number_of_packets; + + /* Handle SITD list cleanup */ + if (urb->hcpriv) { + phcd_iso_sitd_free_list(hcd, urb, + *status); + } + iso_dbg(ISO_DBG_ERR, + "[phcd_submit_iso Error]: Error in filling up SITD\n"); + return *status; + } + + /* + * If this SITD is not the head/root SITD, link this SITD to the SITD + * that came before it. + */ + if (prev_sitd) { + prev_sitd->hw_next = (u32) (sitd); + } + + prev_sitd = sitd; + + if(packets<8){ //bcs of memory constraint , we use only first 8 PTDs if number_of_packets is more than 8. + /* + * Allocate an ISO PTD from the ISO PTD map list and + * set the equivalent bit of the allocated PTD to active + * in the bitmap so that this PTD will be included into + * the periodic schedule + */ + phcd_iso_get_sitd_ptd_index(hcd, sitd); + iso_dbg(ISO_DBG_DATA, + "[phcd_submit_iso]: SITD index %d\n", + sitd->sitd_index); + + /*if we dont have any space left */ + if (sitd->sitd_index == TD_PTD_INV_PTD_INDEX) { + *status = -ENOSPC; + if (((int) (qhead->next_uframe - + urb->number_of_packets)) < 0){ + /*plus max PTDs*/ + qhead->next_uframe = qhead->next_uframe + PTD_PERIODIC_SIZE; + } + qhead->next_uframe -= urb->number_of_packets; + + /* Handle SITD list cleanup */ + if (urb->hcpriv) { + phcd_iso_sitd_free_list(hcd, urb, + *status); + } + return *status; + } + qhead->actualptds++; + } + /* Insert this td into the periodic list */ + + sitd_itd_list = &qhead->periodic_list.sitd_itd_head; + list_add_tail(&sitd->sitd_list, sitd_itd_list); + qhead->periodic_list.high_speed = 0; + if(sitd->sitd_index!=TD_PTD_INV_PTD_INDEX) + qhead->periodic_list.ptdlocation |= + 0x1 << sitd->sitd_index; + /* Inidcate that a new SITD have been scheduled */ + hcd->periodic_sched++; + + /* Determine if there are any SITD scheduled before this one. */ + if (urb->hcpriv == 0){ + urb->hcpriv = sitd; + } + } /* for(packets = 0; packets... */ + } else if (urb->dev->speed == USB_SPEED_HIGH) { + iNumofPks = iNumofPTDs; + + packets = 0; + iPTDIndex = 0; + while (packets < urb->number_of_packets) { + iNumofSlots = NUMMICROFRAME / urb->interval; + /* + * Allocate memory for the ITD data structure and initialize it. + * + * This data structure follows the format of the ITD + * structure defined by the EHCI standard on the top part + * but also contains specific elements in the bottom + * part + */ + itd = kmalloc(sizeof(*itd), GFP_ATOMIC); + if (!itd) { + *status = -ENOMEM; + if(((int) (qhead->next_uframe - iNumofPTDs))<0){ + /*plus max PTDs*/ + qhead->next_uframe = qhead->next_uframe + + PTD_PERIODIC_SIZE; + } + qhead->next_uframe -= iNumofPTDs; + + /* Handle ITD list cleanup */ + if (urb->hcpriv) { + phcd_iso_itd_free_list(hcd, urb, + *status); + } + iso_dbg(ISO_DBG_ERR, + "[phcd_submit_iso Error]: No memory available\n"); + return *status; + } + memset(itd, 0, sizeof(struct ehci_itd)); + + INIT_LIST_HEAD(&itd->itd_list); + + itd->itd_dma = (u32) (itd); + itd->urb = urb; + /* + * Indicate that this ITD is the last in the list. + * + * Also set the itd_index to TD_PTD_INV_PTD_INDEX + * (0xFFFFFFFF). This would indicate when we allocate + * a PTD that this SITD did not have a PTD allocated + * before. + */ + + itd->hw_next = EHCI_LIST_END; + itd->itd_index = TD_PTD_INV_PTD_INDEX; + /* This ITD will go into this frame */ + itd->framenumber = start_frame + iPTDIndex; + /* Number of the packet */ + itd->index = packets; + + itd->framenumber = itd->framenumber & 0x1F; + + itd->ssplit = qhead->ssplit; + itd->csplit = qhead->csplit; + + /*caculate the number of packets for this itd */ + itd->num_of_pkts = iNumofSlots * mult; + /*for the case , urb number_of_packets is less than (number of slot*mult*x times) */ + if (itd->num_of_pkts >= urb->number_of_packets) + { + itd->num_of_pkts = urb->number_of_packets; + } + else { + if (itd->num_of_pkts > + urb->number_of_packets - packets){ + itd->num_of_pkts = + urb->number_of_packets - + packets; + } + } + + /* Initialize the following elements of the ITS structure + * > itd->length = length; -- the size of the request + * > itd->multi = multi; -- the number of transactions for + * this EP per micro frame + * > itd->hw_bufp[0] = buf_dma; -- The base address of the buffer where + * to put the data (this base address was + * the buffer provided plus the offset) + * And then, allocating memory from the PAYLOAD memory area, where the data + * coming from the requesting party will be placed or data requested by the + * requesting party will be retrieved when it is available. + */ + iso_dbg(ISO_DBG_DATA, + "[phcd_submit_iso] packets index = %ld itd->num_of_pkts = %d\n", + packets, itd->num_of_pkts); + *status = + phcd_iso_itd_fill(hcd, itd, urb, packets, + itd->num_of_pkts); + if (*status != 0) { + if (((int) (qhead->next_uframe - iNumofPTDs)) < + 0) { + qhead->next_uframe = qhead->next_uframe + PTD_PERIODIC_SIZE; /*plus max PTDs*/ + } + qhead->next_uframe -= iNumofPTDs; + + /* Handle SITD list cleanup */ + if (urb->hcpriv) { + phcd_iso_itd_free_list(hcd, urb, + *status); + } + iso_dbg(ISO_DBG_ERR, + "[phcd_submit_iso Error]: Error in filling up ITD\n"); + return *status; + } + + iPG = 0; + iMicroIndex = 0; + while (iNumofSlots > 0) { + offset = urb->iso_frame_desc[packets].offset; + /* Buffer for this packet */ + buff_dma = + (u32) ((unsigned char *) urb-> + transfer_buffer + offset); + + /*for the case mult is 2 or 3 */ + length = 0; + for (i = packets; i < packets + mult; i++) { + length += urb->iso_frame_desc[i].length; + } + itd->hw_transaction[iMicroIndex] = + EHCI_ISOC_ACTIVE | (length & + EHCI_ITD_TRANLENGTH) + << 16 | iPG << 12 | buff_dma; + + if (itd->hw_bufp[iPG] != buff_dma){ + itd->hw_bufp[++iPG] = buff_dma; + } + + iso_dbg(ISO_DBG_DATA, + "[%s] offset : %ld buff_dma : 0x%08x length : %ld\n", + __FUNCTION__, offset, + (unsigned int) buff_dma, length); + + itd->ssplit |= 1 << iMicroIndex; + packets++; + iMicroIndex += urb->interval; + iNumofSlots--; + + /*last packets or last slot */ + if (packets == urb->number_of_packets + || iNumofSlots == 0) { + + itd->hw_transaction[iMicroIndex] |= + EHCI_ITD_IOC; + + break; + + } + } + + /* + * If this SITD is not the head/root SITD, link this SITD to the SITD + * that came before it. + */ + if (prev_itd) { + prev_itd->hw_next = (u32) (itd); + } + + prev_itd = itd; + + /* + * Allocate an ISO PTD from the ISO PTD map list and + * set the equivalent bit of the allocated PTD to active + * in the bitmap so that this PTD will be included into + * the periodic schedule + */ + + + iso_dbg(ISO_DBG_DATA, + "[phcd_submit_iso]: ITD index %d\n", + itd->framenumber); + phcd_iso_get_itd_ptd_index(hcd, itd); + iso_dbg(ISO_DBG_DATA, + "[phcd_submit_iso]: ITD index %d\n", + itd->itd_index); + + /*if we dont have any space left */ + if (itd->itd_index == TD_PTD_INV_PTD_INDEX) { + *status = -ENOSPC; + if (((int) (qhead->next_uframe - iNumofPTDs)) < + 0){ + /*plus max PTDs*/ + qhead->next_uframe = qhead->next_uframe + PTD_PERIODIC_SIZE; + } + qhead->next_uframe -= iNumofPTDs; + + /* Handle SITD list cleanup */ + if (urb->hcpriv) { + phcd_iso_itd_free_list(hcd, urb, + *status); + } + return *status; + } + + sitd_itd_list = &qhead->periodic_list.sitd_itd_head; + list_add_tail(&itd->itd_list, sitd_itd_list); + qhead->periodic_list.high_speed = 1; + qhead->periodic_list.ptdlocation |= + 0x1 << itd->itd_index; + + /* Inidcate that a new SITD have been scheduled */ + hcd->periodic_sched++; + + /* Determine if there are any ITD scheduled before this one. */ + if (urb->hcpriv == 0){ + urb->hcpriv = itd; + } + iPTDIndex++; + + } /*end of while */ + } + + /*end of HIGH SPEED */ + /* Last td of current transaction */ + if (high_speed == 0){ + sitd->hw_next = EHCI_LIST_END; + } + urb->error_count = 0; + return *status; +} /* phcd_submit_iso */ +#endif /* CONFIG_ISO_SUPPORT */ diff --git a/drivers/usb/host/pehci/host/mem.c b/drivers/usb/host/pehci/host/mem.c new file mode 100644 index 00000000000..dbf28a92e14 --- /dev/null +++ b/drivers/usb/host/pehci/host/mem.c @@ -0,0 +1,355 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux OTG Controller driver : host +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* This is a host controller driver file. Memory initialization, allocation, and +* deallocation are handled here. +* +* Author : wired support +* +*/ + +#ifdef CONFIG_ISO_SUPPORT + +/*memory utilization fuctions*/ +void +phci_hcd_mem_init(void) +{ + int i = 0; + u32 start_addr = 0x1000; + struct isp1763_mem_addr *memaddr; + for (i = 0; i < BLK_TOTAL; i++) { + memaddr = &memalloc[i]; + memset(memaddr, 0, sizeof *memaddr); + } + /*initialize block of 128bytes */ + for (i = 0; i < BLK_128_; i++) { + memaddr = &memalloc[i]; + memaddr->blk_num = i; + memaddr->used = 0; + memaddr->blk_size = BLK_SIZE_128; + memaddr->phy_addr = start_addr; + start_addr += BLK_SIZE_128; + } + /*initialize block of 256bytes */ + for (i = BLK_128_; i < BLK_256_; i++) { + memaddr = &memalloc[i]; + memaddr->blk_num = i; + memaddr->used = 0; + memaddr->blk_size = BLK_SIZE_256; + memaddr->phy_addr = start_addr; + start_addr += BLK_SIZE_256; + } + /*initialize block of 1024bytes */ + for (i = BLK_128_ + BLK_256_; i < (BLK_128_ + BLK_256_ + BLK_1024_); + i++) { + memaddr = &memalloc[i]; + memaddr->blk_num = i; + memaddr->used = 0; + memaddr->blk_size = BLK_SIZE_1024; + memaddr->phy_addr = start_addr; + start_addr += BLK_SIZE_1024; + } + + /*initialize block of 2kbytes */ + for (i = (BLK_128_ + BLK_256_ + BLK_1024_); + i < (BLK_128_ + BLK_256_ + BLK_1024_ + BLK_2048_); i++) { + memaddr = &memalloc[i]; + memaddr->blk_num = i; + memaddr->used = 0; + memaddr->blk_size = BLK_SIZE_2048; + memaddr->phy_addr = start_addr; + start_addr += BLK_SIZE_2048; + } + /* initialize block of 4kbytes */ + for (i = (BLK_128_ + BLK_256_ + BLK_1024_ + BLK_2048_); + i < (BLK_128_ + BLK_256_ + BLK_1024_ + BLK_2048_ + BLK_4096_); + i++){ + memaddr = &memalloc[i]; + memaddr->blk_num = i; + memaddr->used = 0; + memaddr->blk_size = BLK_SIZE_4096; + memaddr->phy_addr = start_addr; + start_addr += BLK_SIZE_4096; + } + /* initialize block of 8kbytes */ + for (i = (BLK_128_ + BLK_256_ + BLK_1024_ + BLK_2048_); i < + (BLK_128_ + BLK_256_ + BLK_1024_ + BLK_2048_ + BLK_4096_ + + BLK_8196_); i++) { + memaddr = &memalloc[i]; + memaddr->blk_num = i; + memaddr->used = 0; + memaddr->blk_size = BLK_SIZE_8192; + memaddr->phy_addr = start_addr; + start_addr += BLK_SIZE_8192; + } + +} + + +/*free memory*/ +static void +phci_hcd_mem_free(struct isp1763_mem_addr *memptr) +{ + /*block number to be freed */ + int block = memptr->blk_num; + + if (block < BLK_TOTAL){ + if ((memptr->blk_size) && (memalloc[block].used != 0)) { + memalloc[block].used = 0; + memptr->used = 0; + } + } +} + + +/*allocate memory*/ +static void +phci_hcd_mem_alloc(u32 size, struct isp1763_mem_addr *memptr, u32 flag) +{ + u32 blk_size = size; + u16 i; + u32 nextblk1 = 0, nextblk4 = 0; + u32 start = 0, end = 0; + struct isp1763_mem_addr *memaddr = 0; + + memset(memptr, 0, sizeof *memptr); + + pehci_print("phci_hcd_mem_alloc(size = %d)\n", size); + + if (blk_size == 0) { + memptr->phy_addr = 0; + memptr->virt_addr = 0; + memptr->blk_size = 0; + memptr->num_alloc = 0; + memptr->blk_num = 0; + return; + } + + for (i = 0; i < BLK_TOTAL; i++) { + memaddr = &memalloc[i]; + if (!memaddr->used && size <= memaddr->blk_size) { + memaddr->used = 1; + memptr->used = 1; + memptr->blk_num = i; + memptr->blk_size = memaddr->blk_size; + memptr->phy_addr = memaddr->phy_addr; + memptr->virt_addr = memptr->phy_addr; + return; + } + } + + return; + /*end of the 1k blocks */ + nextblk1 = BLK_256_ + BLK_1024_; + /*end of the 4k blocks */ + nextblk4 = nextblk1 + BLK_4096_; + + if (blk_size <= BLK_SIZE_128) { + blk_size = BLK_SIZE_128; + start = 0; + end = BLK_256_; + } + if (blk_size <= BLK_SIZE_256) { + blk_size = BLK_SIZE_256; + start = 0; + end = BLK_256_; + } else if (blk_size <= BLK_SIZE_1024) { + blk_size = BLK_SIZE_1024; + start = BLK_256_; + end = start + BLK_1024_; + } else if (blk_size > BLK_SIZE_1024) { + blk_size = BLK_SIZE_4096; + start = BLK_256_ + BLK_1024_; + end = start + BLK_4096_; + } + + for (i = start; i < end; i++) { + memaddr = &memalloc[i]; + if (!memaddr->used) { + memaddr->used = 1; + memptr->blk_num = i; + memptr->used = 1; + memptr->blk_size = blk_size; + memptr->phy_addr = memaddr->phy_addr; + memptr->virt_addr = memptr->phy_addr; + return; + } + } + + /*look for in the next block if memory is free */ + /*start from the first place of the next block */ + start = end; + + /*for 1k and 256 size request only 4k can be returned */ + end = nextblk4; + + for (i = start; i < end; i++) { + memaddr = &memalloc[i]; + if (!memaddr->used) { + memaddr->used = 1; + memptr->used = 1; + memptr->blk_num = i; + memptr->blk_size = blk_size; + memptr->phy_addr = memaddr->phy_addr; + memptr->virt_addr = memptr->phy_addr; + return; + } + } + +} + +#else + +void +phci_hcd_mem_init(void) +{ + int i = 0; + u32 start_addr = 0x1000; + struct isp1763_mem_addr *memaddr; + for (i = 0; i < BLK_TOTAL; i++) { + memaddr = &memalloc[i]; + memset(memaddr, 0, sizeof *memaddr); + } + + /*initialize block of 256bytes */ + for (i = 0; i < BLK_256_; i++) { + memaddr = &memalloc[i]; + memaddr->blk_num = i; + memaddr->used = 0; + memaddr->blk_size = BLK_SIZE_256; + memaddr->phy_addr = start_addr; + start_addr += BLK_SIZE_256; + } + /*initialize block of 1024bytes */ + for (i = BLK_256_; i < (BLK_256_ + BLK_1024_); i++) { + memaddr = &memalloc[i]; + memaddr->blk_num = i; + memaddr->used = 0; + memaddr->blk_size = BLK_SIZE_1024; + memaddr->phy_addr = start_addr; + start_addr += BLK_SIZE_1024; + } + + /*initialize block of 4kbytes */ + for (i = (BLK_256_ + BLK_1024_); i < (BLK_256_ + BLK_1024_ + BLK_4096_); + i++) { + memaddr = &memalloc[i]; + memaddr->blk_num = i; + memaddr->used = 0; + memaddr->blk_size = BLK_SIZE_4096; + memaddr->phy_addr = start_addr; + start_addr += BLK_SIZE_4096; + } + +} + + +/*free memory*/ +static void +phci_hcd_mem_free(struct isp1763_mem_addr *memptr) +{ + /*block number to be freed */ + int block = memptr->blk_num; + + if (block < BLK_TOTAL) + if ((memptr->blk_size) && (memalloc[block].used != 0)) { + memalloc[block].used = 0; + memptr->used = 0; + } +} + + +/*allocate memory*/ +static void +phci_hcd_mem_alloc(u32 size, struct isp1763_mem_addr *memptr, u32 flag) +{ + u32 blk_size = size; + u16 i; + u32 nextblk1 = 0, nextblk4 = 0; + u32 start = 0, end = 0; + struct isp1763_mem_addr *memaddr = 0; + + memset(memptr, 0, sizeof *memptr); + + pehci_print("phci_hcd_mem_alloc(size = %d)\n", size); + + if (blk_size == 0) { + memptr->phy_addr = 0; + memptr->virt_addr = 0; + memptr->blk_size = 0; + memptr->num_alloc = 0; + memptr->blk_num = 0; + return; + } + + /*end of the 1k blocks */ + nextblk1 = BLK_256_ + BLK_1024_; + /*end of the 4k blocks */ + nextblk4 = nextblk1 + BLK_4096_; + + + if (blk_size <= BLK_SIZE_256) { + blk_size = BLK_SIZE_256; + start = 0; + end = BLK_256_; + } else if (blk_size <= BLK_SIZE_1024) { + blk_size = BLK_SIZE_1024; + start = BLK_256_; + end = start + BLK_1024_; + } else if (blk_size > BLK_SIZE_1024) { + blk_size = BLK_SIZE_4096; + start = BLK_256_ + BLK_1024_; + end = start + BLK_4096_; + } + + for (i = start; i < end; i++) { + memaddr = &memalloc[i]; + if (!memaddr->used) { + memaddr->used = 1; + memptr->blk_num = i; + memptr->used = 1; + memptr->blk_size = blk_size; + memptr->phy_addr = memaddr->phy_addr; + memptr->virt_addr = memptr->phy_addr; + return; + } + } + + /*look for in the next block if memory is free */ + /*start from the first place of the next block */ + start = end; + + /*for 1k and 256 size request only 4k can be returned */ + end = nextblk4; + + for (i = start; i < end; i++) { + memaddr = &memalloc[i]; + if (!memaddr->used) { + memaddr->used = 1; + memptr->used = 1; + memptr->blk_num = i; + memptr->blk_size = blk_size; + memptr->phy_addr = memaddr->phy_addr; + memptr->virt_addr = memptr->phy_addr; + return; + } + } + +} + +#endif diff --git a/drivers/usb/host/pehci/host/otg.c b/drivers/usb/host/pehci/host/otg.c new file mode 100755 index 00000000000..546d9e9b82d --- /dev/null +++ b/drivers/usb/host/pehci/host/otg.c @@ -0,0 +1,189 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux OTG Controller driver : host +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* This is a host controller driver file. OTG related events are handled here. +* +* Author : wired support +* +*/ + + +/*hub device which connected with root port*/ +struct usb_device *hubdev = 0; +/* hub interrupt urb*/ +struct urb *huburb; + +/*return otghub from here*/ +struct usb_device * +phci_register_otg_device(struct isp1763_dev *dev) +{ + printk("OTG dev %x %d\n",(u32) hubdev, hubdev->devnum); + if (hubdev && hubdev->devnum >= 0x2) { + return hubdev; + } + + return NULL; +} +EXPORT_SYMBOL(phci_register_otg_device); + +/*suspend the otg port(0) + * needed when port is switching + * from host to device + * */ +int +phci_suspend_otg_port(struct isp1763_dev *dev, u32 command) +{ + int status = 0; + hubdev->otgstate = USB_OTG_SUSPEND; + if (huburb->status == -EINPROGRESS) { + huburb->status = 0; + } + + huburb->status = 0; + huburb->complete(huburb); + return status; +} +EXPORT_SYMBOL(phci_suspend_otg_port); + +/*set the flag to enumerate the device*/ +int +phci_enumerate_otg_port(struct isp1763_dev *dev, u32 command) +{ + /*set the flag to enumerate */ + /*connect change interrupt will happen from + * phci_intl_worker only + * */ + hubdev->otgstate = USB_OTG_ENUMERATE; + if (huburb->status == -EINPROGRESS) { + huburb->status = 0; + } + /*complete the urb */ + + huburb->complete(huburb); + + /*reset the otghub urb status */ + huburb->status = -EINPROGRESS; + return 0; +} +EXPORT_SYMBOL(phci_enumerate_otg_port); + +/*host controller resume sequence at otg port*/ +int +phci_resume_otg_port(struct isp1763_dev *dev, u32 command) +{ + printk("Resume is called\n"); + hubdev->otgstate = USB_OTG_RESUME; + if (huburb->status == -EINPROGRESS) { + huburb->status = 0; + } + /*complete the urb */ + + huburb->complete(huburb); + + /*reset the otghub urb status */ + huburb->status = -EINPROGRESS; + return 0; +} +EXPORT_SYMBOL(phci_resume_otg_port); +/*host controller remote wakeup sequence at otg port*/ +int +phci_remotewakeup(struct isp1763_dev *dev) +{ + printk("phci_remotewakeup_otg_port is called\n"); + hubdev->otgstate = USB_OTG_REMOTEWAKEUP; + if(huburb->status == -EINPROGRESS) + huburb->status = 0; + /*complete the urb*/ +#if ((defined LINUX_269) || defined (LINUX_2611)) + huburb->complete(huburb,NULL); +#else + huburb->complete(huburb); +#endif + /*reset the otghub urb status*/ + huburb->status = -EINPROGRESS; + return 0; +} +EXPORT_SYMBOL(phci_remotewakeup); + +/*host controller wakeup sequence at otg port*/ +int +phci_resume_wakeup(struct isp1763_dev *dev) +{ + printk("phci_wakeup_otg_port is called\n"); +#if 0 + hubdev->otgstate = USB_OTG_WAKEUP_ALL; + if(huburb->status == -EINPROGRESS) +#endif + huburb->status = 0; + /*complete the urb*/ +#if ((defined LINUX_269) || defined (LINUX_2611)) + huburb->complete(huburb,NULL); +#else + huburb->complete(huburb); +#endif + /*reset the otghub urb status*/ + huburb->status = -EINPROGRESS; + return 0; +} +EXPORT_SYMBOL(phci_resume_wakeup); + +struct isp1763_driver *host_driver; +struct isp1763_driver *device_driver; + +void +pehci_delrhtimer(struct isp1763_dev *dev) +{ + + struct usb_hcd *usb_hcd = + container_of(huburb->dev->parent->bus, struct usb_hcd, self); + del_timer_sync(&usb_hcd->rh_timer); + del_timer(&usb_hcd->rh_timer); + +} +EXPORT_SYMBOL(pehci_delrhtimer); + +int +pehci_Deinitialize(struct isp1763_dev *dev) +{ + dev -= 2; + if (dev->index == 0) { + if (dev->driver) { + if (dev->driver->powerdown) { + dev->driver->powerdown(dev); + } + } + } +return 0; +} +EXPORT_SYMBOL(pehci_Deinitialize); + +int +pehci_Reinitialize(struct isp1763_dev *dev) +{ + + dev -= 2; + if (dev->index == 0) { + if(dev->driver->powerup){ + dev->driver->powerup(dev); + } + } +return 0; +} +EXPORT_SYMBOL(pehci_Reinitialize); + + diff --git a/drivers/usb/host/pehci/host/pehci.c b/drivers/usb/host/pehci/host/pehci.c new file mode 100644 index 00000000000..19e9441722e --- /dev/null +++ b/drivers/usb/host/pehci/host/pehci.c @@ -0,0 +1,6567 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux OTG Controller driver : host +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* Refer to the follwing files in ~/drivers/usb/host for copyright owners: +* ehci-dbg.c, ehci-hcd.c, ehci-hub.c, ehci-mem.c, ehci-q.c and ehic-sched.c (kernel version 2.6.9) +* Code is modified for ST-Ericsson product +* +* Author : wired support +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../hal/isp1763.h" +#include "pehci.h" +#include "../hal/hal_intf.h" +#include +#include + +extern int HostComplianceTest; +extern int HostTest; +extern int No_Data_Phase; +extern int No_Status_Phase; +#define EHCI_TUNE_CERR 3 +#define URB_NO_INTERRUPT 0x0080 +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_TT 1 +#define EHCI_TUNE_RL_HS 0 +#define EHCI_TUNE_MULT_HS 1 + + +#define POWER_DOWN_CTRL_NORMAL_VALUE 0xffff1ba0 +#define POWER_DOWN_CTRL_SUSPEND_VALUE 0xffff08b0 + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) +//This macro is not supported in linux-2.6.35 +#define USB_PORT_FEAT_HIGHSPEED 10 +#endif + +#ifdef CONFIG_ISO_SUPPORT + +#define FALSE 0 +#define TRUE (!FALSE) +extern void *phcd_iso_sitd_to_ptd(phci_hcd * hcd, + struct ehci_sitd *sitd, + struct urb *urb, void *ptd); +extern void *phcd_iso_itd_to_ptd(phci_hcd * hcd, + struct ehci_itd *itd, + struct urb *urb, void *ptd); + +extern unsigned long phcd_submit_iso(phci_hcd * hcd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + struct usb_host_endpoint *ep, +#else +#endif + struct urb *urb, unsigned long *status); +void pehci_hcd_iso_schedule(phci_hcd * hcd, struct urb *); +unsigned long lgFrameIndex = 0; +unsigned long lgScheduledPTDIndex = 0; +int igNumOfPkts = 0; +#endif /* CONFIG_ISO_SUPPORT */ + +struct isp1763_dev *isp1763_hcd; + +#ifdef HCD_PACKAGE +/*file operation*/ +struct fasync_struct *fasync_q; +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +static void +pehci_hcd_urb_complete(phci_hcd * hcd, struct ehci_qh *qh, struct urb *urb, + td_ptd_map_t * td_ptd_map, struct pt_regs *regs); +#else +static void +pehci_hcd_urb_complete(phci_hcd * hcd, struct ehci_qh *qh, struct urb *urb, + td_ptd_map_t * td_ptd_map); +#endif + +#include "otg.c" /*OTG and HCD package needs it */ + + +int hcdpowerdown = 0; +int portchange=0; //for remotewakeup +EXPORT_SYMBOL(hcdpowerdown); +unsigned char otg_se0_enable; +EXPORT_SYMBOL(otg_se0_enable); + + +/*Enable all other interrupt.*/ + +#ifdef MSEC_INT_BASED +#ifdef THREAD_BASED//This is to test interrupt mapping problem +//#define INTR_ENABLE_MASK (HC_OPR_REG_INT|HC_CLK_RDY_INT ) +#define INTR_ENABLE_MASK (/*HC_MSEC_INT |*/ HC_INTL_INT | HC_ATL_INT| HC_ISO_INT /*| HC_EOT_INT | HC_ISO_INT*/) +#else +#define INTR_ENABLE_MASK (HC_MSEC_INT|HC_OPR_REG_INT|HC_CLK_RDY_INT ) +#endif +#else +#define INTR_ENABLE_MASK ( HC_INTL_INT | HC_ATL_INT |HC_ISO_INT| HC_EOT_INT|HC_OPR_REG_INT|HC_CLK_RDY_INT) +#endif + + + +#ifdef THREAD_BASED + +#define NO_SOF_REQ_IN_TSK 0x1 +#define NO_SOF_REQ_IN_ISR 0x2 +#define NO_SOF_REQ_IN_REQ 0x3 +#define MSEC_INTERVAL_CHECKING 5 + +typedef struct _st_UsbIt_Msg_Struc { + struct usb_hcd *usb_hcd; + u8 uIntStatus; + struct list_head list; +} st_UsbIt_Msg_Struc, *pst_UsbIt_Msg_Struc ; + +typedef struct _st_UsbIt_Thread { + wait_queue_head_t ulThrdWaitQhead; + int lThrdWakeUpNeeded; + struct task_struct *phThreadTask; + spinlock_t lock; +} st_UsbIt_Thread, *pst_UsbIt_Thread; + +st_UsbIt_Thread g_stUsbItThreadHandler; + +st_UsbIt_Msg_Struc g_messList; +st_UsbIt_Msg_Struc g_enqueueMessList; +spinlock_t enqueue_lock; + +int pehci_hcd_process_irq_it_handle(struct usb_hcd* usb_hcd_); +int pehci_hcd_process_irq_in_thread(struct usb_hcd *usb_hcd_); + +#endif /*THREAD_BASED*/ + +#ifdef THREAD_BASED +phci_hcd *g_pehci_hcd; +#endif + + +struct wake_lock pehci_wake_lock; + +/*--------------------------------------------------- + * Globals for EHCI + -----------------------------------------------------*/ + +/* used when updating hcd data */ +static spinlock_t hcd_data_lock = SPIN_LOCK_UNLOCKED; + +static const char hcd_name[] = "ST-Ericsson ISP1763"; +static td_ptd_map_buff_t td_ptd_map_buff[TD_PTD_TOTAL_BUFF_TYPES]; /* td-ptd map buffer for all 1362 buffers */ + +static u8 td_ptd_pipe_x_buff_type[TD_PTD_TOTAL_BUFF_TYPES] = { + TD_PTD_BUFF_TYPE_ATL, + TD_PTD_BUFF_TYPE_INTL, + TD_PTD_BUFF_TYPE_ISTL +}; + + +/*global memory blocks*/ +isp1763_mem_addr_t memalloc[BLK_TOTAL]; +#include "mem.c" +#include "qtdptd.c" + +#ifdef CONFIG_ISO_SUPPORT +#include "itdptd.c" +#endif /* CONFIG_ISO_SUPPORT */ + +static int +pehci_rh_control(struct usb_hcd *usb_hcd, u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength); + +static int pehci_bus_suspend(struct usb_hcd *usb_hcd); +static int pehci_bus_resume(struct usb_hcd *usb_hcd); +/*----------------------------------------------------*/ +static void +pehci_complete_device_removal(phci_hcd * hcd, struct ehci_qh *qh) +{ + td_ptd_map_t *td_ptd_map; + td_ptd_map_buff_t *td_ptd_buff; + struct urb * urb; + urb_priv_t *urb_priv; + struct ehci_qtd *qtd = 0; +// struct usb_hcd *usb_hcd=&hcd->usb_hcd; + u16 skipmap=0; + + if (qh->type == TD_PTD_BUFF_TYPE_ISTL) { +#ifdef COMMON_MEMORY + phci_hcd_mem_free(&qh->memory_addr); +#endif + return; + } + + td_ptd_buff = &td_ptd_map_buff[qh->type]; + td_ptd_map = &td_ptd_buff->map_list[qh->qtd_ptd_index]; + + /*this flag should only be set when device is going */ + td_ptd_map->state = TD_PTD_REMOVE; + /*if nothing there */ + if (list_empty(&qh->qtd_list)) { + if (td_ptd_map->state != TD_PTD_NEW) { + phci_hcd_release_td_ptd_index(qh); + } + qha_free(qha_cache, qh); + qh = 0; + return; + } else { + + if(!list_empty(&qh->qtd_list)){ + qtd=NULL; + qtd = list_entry(qh->qtd_list.next, struct ehci_qtd, qtd_list); + if(qtd){ + urb=qtd->urb; + urb_priv= urb->hcpriv; + + if(urb) + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + break; + case PIPE_INTERRUPT: + td_ptd_buff = &td_ptd_map_buff[TD_PTD_BUFF_TYPE_INTL]; + td_ptd_map = &td_ptd_buff->map_list[qh->qtd_ptd_index]; + + /*urb is already been removed */ + // if (td_ptd_map->state == TD_PTD_NEW) { + // kfree(urb_priv); + // break; + // } + + /* These TDs are not pending anymore */ + td_ptd_buff->pending_ptd_bitmap &= ~td_ptd_map->ptd_bitmap; + + td_ptd_map->state = TD_PTD_REMOVE; + urb_priv->state |= DELETE_URB; + + /*read the skipmap, to see if this transfer has to be rescheduled */ + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.inttdskipmap, + skipmap); + + isp1763_reg_write16(hcd->dev, hcd->regs.inttdskipmap, + skipmap | td_ptd_map->ptd_bitmap); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map, NULL); +#else + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map); +#endif + break; + } + + + }else{ + //break; + } + } + qha_free(qha_cache, qh); + qh = 0; + return; + } + /*MUST not come down below this */ + err("Never Error: Should not come to this portion of code\n"); + + return; +} + +/*functions looks for the values in register + specified in ptr, if register values masked + with the mask and result is equal to done, + operation is successful else fails with timeout*/ +static int +pehci_hcd_handshake(phci_hcd * hcd, u32 ptr, u32 mask, u32 done, int usec) +{ + u32 result = 0; + do { + result = isp1763_reg_read16(hcd->dev, ptr, result); + printk(KERN_NOTICE "Registr %x val is %x\n", ptr, result); + if (result == ~(u32) 0) {/* card removed */ + return -ENODEV; + } + result &= mask; + if (result == done) { + return 0; + } + udelay(1); + usec--; + } while (usec > 0); + + return -ETIMEDOUT; +} + +#ifndef MSEC_INT_BASED +/*schedule atl and interrupt tds, + only when we are not running on sof interrupt + */ +static void +pehci_hcd_td_ptd_submit_urb(phci_hcd * hcd, struct ehci_qh *qh, u8 bufftype) +{ + unsigned long flags=0; + struct ehci_qtd *qtd = 0; + struct urb *urb = 0; + struct _isp1763_qha *qha = 0; + u16 location = 0; + u16 skipmap = 0; + u16 buffstatus = 0; + u16 ormask = 0; + u16 intormask = 0; + u32 length = 0; + struct list_head *head; + + td_ptd_map_t *td_ptd_map; + td_ptd_map_buff_t *ptd_map_buff; + struct isp1763_mem_addr *mem_addr = 0; + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + pehci_print("Buuffer type %d\n", bufftype); + + spin_lock_irqsave(&hcd->lock, flags); + ptd_map_buff = &td_ptd_map_buff[bufftype]; + + qha = &hcd->qha; + + switch (bufftype) { + case TD_PTD_BUFF_TYPE_ATL: + + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.atltdskipmap, + skipmap); + + ormask = isp1763_reg_read16(hcd->dev, hcd->regs.atl_irq_mask_or, + ormask); + break; + case TD_PTD_BUFF_TYPE_INTL: + + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.inttdskipmap, + skipmap); + + intormask = + isp1763_reg_read16(hcd->dev, hcd->regs.int_irq_mask_or, + intormask); + break; + default: + + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.isotdskipmap, + skipmap); + break; + + } + + + buffstatus = + isp1763_reg_read16(hcd->dev, hcd->regs.buffer_status, + buffstatus); + + /*header, qtd, and urb of current transfer */ + location = qh->qtd_ptd_index; + td_ptd_map = &ptd_map_buff->map_list[location]; + + if (!(qh->qh_state & QH_STATE_TAKE_NEXT)) { + pehci_check("qh will schdule from interrupt routine,map %x\n", + td_ptd_map->ptd_bitmap); + spin_unlock_irqrestore(&hcd->lock, flags); + return; + } + head = &qh->qtd_list; + qtd = list_entry(head->next, struct ehci_qtd, qtd_list); + + /*already scheduled, may be from interrupt */ + if (!(qtd->state & QTD_STATE_NEW)) { + pehci_check("qtd already in, state %x\n", qtd->state); + spin_unlock_irqrestore(&hcd->lock, flags); + return; + } + + qtd->state &= ~QTD_STATE_NEW; + qtd->state |= QTD_STATE_SCHEDULED; + + qh->qh_state &= ~QH_STATE_TAKE_NEXT; + /*take the first td */ + td_ptd_map->qtd = qtd; + /*take the urb */ + urb = qtd->urb; + ptd_map_buff->active_ptds++; + + /*trust the atl worker, at this location there wont be any td */ + /*if this td is the last one */ + if (qtd->state & QTD_STATE_LAST) { + qh->hw_current = cpu_to_le32(0); + /*else update the hw_next of qh to the next td */ + } else { + qh->hw_current = qtd->hw_next; + } + memset(qha, 0, sizeof(isp1763_qha)); + + pehci_check("td being scheduled : length: %d, device: %d, map: %x\n", + qtd->length, urb->dev->devnum, td_ptd_map->ptd_bitmap); + /*NEW, now need to get the memory for this transfer */ + length = qtd->length; + mem_addr = &qtd->mem_addr; + phci_hcd_mem_alloc(length, mem_addr, 0); + if (length && ((mem_addr->phy_addr == 0) || (mem_addr->virt_addr == 0))) { + err("Never Error: Can not allocate memory for the current td,length %d\n", length); + /*should not happen */ + /*can happen only when we exceed the limit of devices we support + MAX 4 mass storage at a time */ + } + phci_hcd_qha_from_qtd(hcd, qtd, qtd->urb, (void *) qha, + td_ptd_map->ptd_ram_data_addr, qh); + if (qh->type == TD_PTD_BUFF_TYPE_INTL) { + phci_hcd_qhint_schedule(hcd, qh, qtd, (isp1763_qhint *) qha, + qtd->urb); + } + /*write qha into the header of the host controller */ + isp1763_mem_write(hcd->dev, td_ptd_map->ptd_header_addr, 0, + (u32 *) (qha), PHCI_QHA_LENGTH, 0); + + /*if this is SETUP/OUT token , then need to write into the buffer */ + /*length should be valid and supported by the ptd */ + if (qtd->length && (qtd->length <= HC_ATL_PL_SIZE)){ + switch (PTD_PID(qha->td_info2)) { + case OUT_PID: + case SETUP_PID: + + isp1763_mem_write(hcd->dev, (u32) mem_addr->phy_addr, 0, + (void *) qtd->hw_buf[0], length, 0); + + +#if 0 + int i=0; + int *data_addr= qtd->hw_buf[0]; + printk("\n"); + for(i=0;iptd_bitmap; + /*enable atl interrupts on donemap */ + ormask |= td_ptd_map->ptd_bitmap; + + isp1763_reg_write16(hcd->dev, hcd->regs.atl_irq_mask_or, + ormask); + break; + + case TD_PTD_BUFF_TYPE_INTL: + skipmap &= ~td_ptd_map->ptd_bitmap; + intormask |= td_ptd_map->ptd_bitmap; + + isp1763_reg_write16(hcd->dev, hcd->regs.int_irq_mask_or, + intormask); + break; + + case TD_PTD_BUFF_TYPE_ISTL: + skipmap &= ~td_ptd_map->ptd_bitmap; + + isp1763_reg_write16(hcd->dev, hcd->regs.isotdskipmap, skipmap); + break; + } + + /*if any new schedule, enable the atl buffer */ + switch (bufftype) { + case TD_PTD_BUFF_TYPE_ATL: + + isp1763_reg_write16(hcd->dev, hcd->regs.buffer_status, + buffstatus | ATL_BUFFER); + + isp1763_reg_write16(hcd->dev, hcd->regs.atltdskipmap, skipmap); + buffstatus |= ATL_BUFFER; + break; + case TD_PTD_BUFF_TYPE_INTL: + + isp1763_reg_write16(hcd->dev, hcd->regs.buffer_status, + buffstatus | INT_BUFFER); + + isp1763_reg_write16(hcd->dev, hcd->regs.inttdskipmap, skipmap); + break; + case TD_PTD_BUFF_TYPE_ISTL: + /*not supposed to be seen here */ + + isp1763_reg_write16(hcd->dev, hcd->regs.buffer_status, + buffstatus | ISO_BUFFER); + break; + } + spin_unlock_irqrestore(&hcd->lock, flags); + pehci_entry("-- %s: Exit\n", __FUNCTION__); + return; + +} +#endif + + + +#ifdef MSEC_INT_BASED +/*schedule next (atl/int)tds and any pending tds*/ +static void +pehci_hcd_schedule_pending_ptds(phci_hcd * hcd, u16 donemap, u8 bufftype, + u16 only) +{ + struct ehci_qtd *qtd = 0; + struct ehci_qh *qh = 0; + struct list_head *qtd_list = 0; + struct _isp1763_qha allqha; + struct _isp1763_qha *qha = 0; + u16 mask = 0x1, index = 0; + u16 location = 0; + u16 skipmap = 0; + u32 newschedule = 0; + u16 buffstatus = 0; + u16 schedulemap = 0; +#ifndef CONFIG_ISO_SUPPORT + u16 lasttd = 1; +#endif + u16 lastmap = 0; + struct urb *urb = 0; + urb_priv_t *urbpriv = 0; + int length = 0; + u16 ormask = 0, andmask = 0; + u16 intormask = 0; + td_ptd_map_t *td_ptd_map; + td_ptd_map_buff_t *ptd_map_buff; + struct isp1763_mem_addr *mem_addr = 0; + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + pehci_print("Buffer type %d\n", bufftype); + + /*need to hold this lock if another interrupt is comming + for previously scheduled transfer, while scheduling new tds + */ + spin_lock(&hcd_data_lock); + ptd_map_buff = &td_ptd_map_buff[bufftype]; + qha = &allqha; + switch (bufftype) { + case TD_PTD_BUFF_TYPE_ATL: + + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.atltdskipmap, + skipmap); + rmb(); + + ormask = isp1763_reg_read16(hcd->dev, hcd->regs.atl_irq_mask_or, + ormask); + + andmask = + isp1763_reg_read16(hcd->dev, hcd->regs.atl_irq_mask_and, + andmask); + break; + case TD_PTD_BUFF_TYPE_INTL: + + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.inttdskipmap, + skipmap); + /*read the interrupt mask registers */ + + intormask = + isp1763_reg_read16(hcd->dev, hcd->regs.int_irq_mask_or, + intormask); + break; + default: + err("Never Error: Bogus type of bufer\n"); + return; + } + + buffstatus = + isp1763_reg_read16(hcd->dev, hcd->regs.buffer_status, + buffstatus); + /*td headers need attention */ + schedulemap = donemap; + while (schedulemap) { + index = schedulemap & mask; + schedulemap &= ~mask; + mask <<= 1; + + if (!index) { + location++; + continue; + } + + td_ptd_map = &ptd_map_buff->map_list[location]; + /* can happen if donemap comes after + removal of the urb and associated tds + */ + if ((td_ptd_map->state == TD_PTD_NEW) || + (td_ptd_map->state == TD_PTD_REMOVE)) { + qh = td_ptd_map->qh; + pehci_check + ("should not come here, map %x,pending map %x\n", + td_ptd_map->ptd_bitmap, + ptd_map_buff->pending_ptd_bitmap); + + pehci_check("buffer type %s\n", + (bufftype == 0) ? "ATL" : "INTL"); + donemap &= ~td_ptd_map->ptd_bitmap; + /*clear the pending map */ + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + location++; + continue; + } + + /*no endpoint at this location */ + if (!(td_ptd_map->qh)) { + err("queue head can not be null here\n"); + /*move to the next location */ + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + location++; + continue; + } + + /*current endpoint */ + qh = td_ptd_map->qh; + if (!(skipmap & td_ptd_map->ptd_bitmap)) { + /*should not happen, if happening, then */ + pehci_check("buffertype %d,td_ptd_map %x,skipnap %x\n", + bufftype, td_ptd_map->ptd_bitmap, skipmap); + lastmap = td_ptd_map->ptd_bitmap; + donemap &= ~td_ptd_map->ptd_bitmap; + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + location++; + continue; + } + + /*if we processed all the tds in ths transfer */ + if (td_ptd_map->lasttd) { + err("should not show map %x,qtd %p\n", + td_ptd_map->ptd_bitmap, td_ptd_map->qtd); + /*this can happen in case the transfer is not being + * procesed by the host , tho the transfer is there + * */ + qh->hw_current = cpu_to_le32(td_ptd_map->qtd); + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + location++; + continue; + } + + /*if we have ptd that is going for reload */ + if ((td_ptd_map->qtd) && (td_ptd_map->state & TD_PTD_RELOAD)) { + warn("%s: reload td\n", __FUNCTION__); + td_ptd_map->state &= ~TD_PTD_RELOAD; + qtd = td_ptd_map->qtd; + goto loadtd; + } + + /* qh is there but no qtd so it means fresh transfer */ + if ((td_ptd_map->qh) && !(td_ptd_map->qtd)) { + if (list_empty(&qh->qtd_list)) { + /*should not hapen again, as it comes here + when it has td in its map + */ + pehci_check + ("must not come here any more, td map %x\n", + td_ptd_map->ptd_bitmap); + /*this location is idle and can be free next time if + no new transfers are comming for this */ + donemap &= ~td_ptd_map->ptd_bitmap; + td_ptd_map->state |= TD_PTD_IDLE; + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + location++; + continue; + } + qtd_list = &qh->qtd_list; + qtd = td_ptd_map->qtd = + list_entry(qtd_list->next, struct ehci_qtd, + qtd_list); + /*got the td, now goto reload */ + goto loadtd; + } + + /*if there is already one qtd there in the transfer */ + if (td_ptd_map->qtd) { + /*new schedule */ + qtd = td_ptd_map->qtd; + } + loadtd: + /*should not happen */ + if (!qtd) { + err("this piece of code should not be executed\n"); + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + location++; + continue; + } + + ptd_map_buff->active_ptds++; + /*clear the pending map here */ + ptd_map_buff->pending_ptd_bitmap &= ~td_ptd_map->ptd_bitmap; + + + + /*if this td is the last one */ + if (qtd->state & QTD_STATE_LAST) { + /*no qtd anymore */ + qh->hw_current = cpu_to_le32(0); + + /*else update the hw_next of qh to the next td */ + } else { + qh->hw_current = qtd->hw_next; + } + + if (location != qh->qtd_ptd_index) { + err("Never Error: Endpoint header location and scheduling information are not same\n"); + } + + /*next location */ + location++; + /*found new transfer */ + newschedule = 1; + /*take the urb */ + urb = qtd->urb; + /*sometimes we miss due to skipmap + so to make sure that we dont put again the + same stuff + */ + if (!(qtd->state & QTD_STATE_NEW)) { + err("Never Error: We should not put the same stuff\n"); + continue; + } + + urbpriv = (urb_priv_t *) urb->hcpriv; + urbpriv->timeout = 0; + + /*no more new */ + qtd->state &= ~QTD_STATE_NEW; + qtd->state |= QTD_STATE_SCHEDULED; + + + + /*NEW, now need to get the memory for this transfer */ + length = qtd->length; + mem_addr = &qtd->mem_addr; + phci_hcd_mem_alloc(length, mem_addr, 0); + if (length && ((mem_addr->phy_addr == 0) + || (mem_addr->virt_addr == 0))) { + + err("Never Error: Can not allocate memory for the current td,length %d\n", length); + location++; + continue; + } + + pehci_check("qtd being scheduled %p, device %d,map %x\n", qtd, + urb->dev->devnum, td_ptd_map->ptd_bitmap); + + + memset(qha, 0, sizeof(isp1763_qha)); + /*convert qtd to qha */ + phci_hcd_qha_from_qtd(hcd, qtd, qtd->urb, (void *) qha, + td_ptd_map->ptd_ram_data_addr, qh); + + if (qh->type == TD_PTD_BUFF_TYPE_INTL) { + phci_hcd_qhint_schedule(hcd, qh, qtd, + (isp1763_qhint *) qha, + qtd->urb); + + } + + + length = PTD_XFERRED_LENGTH(qha->td_info1 >> 3); + if (length > HC_ATL_PL_SIZE) { + err("Never Error: Bogus length,length %d(max %d)\n", + qtd->length, HC_ATL_PL_SIZE); + } + + /*write qha into the header of the host controller */ + isp1763_mem_write(hcd->dev, td_ptd_map->ptd_header_addr, 0, + (u32 *) (qha), PHCI_QHA_LENGTH, 0); + +#ifdef PTD_DUMP_SCHEDULE + printk("SCHEDULE next (atl/int)tds PTD header\n"); + printk("DW0: 0x%08X\n", qha->td_info1); + printk("DW1: 0x%08X\n", qha->td_info2); + printk("DW2: 0x%08X\n", qha->td_info3); + printk("DW3: 0x%08X\n", qha->td_info4); +#endif + + /*if this is SETUP/OUT token , then need to write into the buffer */ + /*length should be valid */ + if (qtd->length && (length <= HC_ATL_PL_SIZE)){ + switch (PTD_PID(qha->td_info2)) { + case OUT_PID: + case SETUP_PID: + + isp1763_mem_write(hcd->dev, + (u32) mem_addr->phy_addr, 0, + (void *) qtd->hw_buf[0], + length, 0); +#if 0 + int i=0; + int *data_addr= qtd->hw_buf[0]; + printk("\n"); + for(i=0;iptd_bitmap; + lastmap = td_ptd_map->ptd_bitmap; + /*try to reduce the interrupts */ + ormask |= td_ptd_map->ptd_bitmap; + + isp1763_reg_write16(hcd->dev, hcd->regs.atl_irq_mask_or, + ormask); + break; + + case TD_PTD_BUFF_TYPE_INTL: + skipmap &= ~td_ptd_map->ptd_bitmap; + lastmap = td_ptd_map->ptd_bitmap; + intormask |= td_ptd_map->ptd_bitmap; + ; + isp1763_reg_write16(hcd->dev, hcd->regs.int_irq_mask_or, + intormask); + break; + + case TD_PTD_BUFF_TYPE_ISTL: +#ifdef CONFIG_ISO_SUPPORT + iso_dbg(ISO_DBG_INFO, + "Never Error: Should not come here\n"); +#else + skipmap &= ~td_ptd_map->ptd_bitmap; + + isp1763_reg_write16(hcd->dev, hcd->regs.isotdskipmap, + skipmap); + + isp1763_reg_write16(hcd->dev, hcd->regs.isotdlastmap, + lasttd); +#endif /* CONFIG_ISO_SUPPORT */ + break; + } + + + } + /*if any new schedule, enable the atl buffer */ + + if (newschedule) { + switch (bufftype) { + case TD_PTD_BUFF_TYPE_ATL: + + isp1763_reg_write16(hcd->dev, hcd->regs.buffer_status, + buffstatus | ATL_BUFFER); + /*i am comming here to only those tds that has to be scheduled */ + /*so skip map must be in place */ + if (skipmap & donemap) { + pehci_check + ("must be both ones compliment of each other\n"); + pehci_check + ("problem, skipmap %x, donemap %x,\n", + skipmap, donemap); + + } + skipmap &= ~donemap; + + isp1763_reg_write16(hcd->dev, hcd->regs.atltdskipmap, + skipmap); + + break; + case TD_PTD_BUFF_TYPE_INTL: + + isp1763_reg_write16(hcd->dev, hcd->regs.buffer_status, + buffstatus | INT_BUFFER); + skipmap &= ~donemap; + + isp1763_reg_write16(hcd->dev, hcd->regs.inttdskipmap, + skipmap); + break; + case TD_PTD_BUFF_TYPE_ISTL: +#ifndef CONFIG_ISO_SUPPORT + + isp1763_reg_write16(hcd->dev, hcd->regs.buffer_status, + buffstatus | ISO_BUFFER); +#endif + break; + } + } + spin_unlock(&hcd_data_lock); + pehci_entry("-- %s: Exit\n", __FUNCTION__); +} +#endif + + + +static void +pehci_hcd_qtd_schedule(phci_hcd * hcd, struct ehci_qtd *qtd, + struct ehci_qh *qh, td_ptd_map_t * td_ptd_map) +{ + struct urb *urb; + urb_priv_t *urbpriv = 0; + u32 length=0; + struct isp1763_mem_addr *mem_addr = 0; + struct _isp1763_qha *qha, qhtemp; + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + + if (qtd->state & QTD_STATE_SCHEDULED) { + return; + } + /*redundant */ + qha = &qhtemp; + + /*if this td is the last one */ + if (qtd->state & QTD_STATE_LAST) { + /*no qtd anymore */ + qh->hw_current = cpu_to_le32(0); + + /*else update the hw_next of qh to the next td */ + } else { + qh->hw_current = qtd->hw_next; + } + + urb = qtd->urb; + urbpriv = (urb_priv_t *) urb->hcpriv; + urbpriv->timeout = 0; + + /*NEW, now need to get the memory for this transfer */ + length = qtd->length; + mem_addr = &qtd->mem_addr; + phci_hcd_mem_alloc(length, mem_addr, 0); + if (length && ((mem_addr->phy_addr == 0) || (mem_addr->virt_addr == 0))) { + err("Never Error: Cannot allocate memory for the current td,length %d\n", length); + return; + } + + pehci_check("newqtd being scheduled, device: %d,map: %x\n", + urb->dev->devnum, td_ptd_map->ptd_bitmap); + + //udelay(100); + + memset(qha, 0, sizeof(isp1763_qha)); + /*convert qtd to qha */ + phci_hcd_qha_from_qtd(hcd, qtd, qtd->urb, (void *) qha, + td_ptd_map->ptd_ram_data_addr, qh + /*td_ptd_map->datatoggle */ ); + + if (qh->type == TD_PTD_BUFF_TYPE_INTL) { + phci_hcd_qhint_schedule(hcd, qh, qtd, (isp1763_qhint *) qha, + qtd->urb); + } + + + length = PTD_XFERRED_LENGTH(qha->td_info1 >> 3); + if (length > HC_ATL_PL_SIZE) { + err("Never Error: Bogus length,length %d(max %d)\n", + qtd->length, HC_ATL_PL_SIZE); + } + + /*write qha into the header of the host controller */ + isp1763_mem_write(hcd->dev, td_ptd_map->ptd_header_addr, 0, + (u32 *) (qha), PHCI_QHA_LENGTH, 0); + +#if 0 //def PTD_DUMP_SCHEDULE + printk("SCHEDULE Next qtd\n"); + printk("DW0: 0x%08X\n", qha->td_info1); + printk("DW1: 0x%08X\n", qha->td_info2); + printk("DW2: 0x%08X\n", qha->td_info3); + printk("DW3: 0x%08X\n", qha->td_info4); +#endif + + /*if this is SETUP/OUT token , then need to write into the buffer */ + /*length should be valid */ + if (qtd->length && (length <= HC_ATL_PL_SIZE)){ + switch (PTD_PID(qha->td_info2)) { + case OUT_PID: + case SETUP_PID: + + isp1763_mem_write(hcd->dev, (u32) mem_addr->phy_addr, 0, + (void *) qtd->hw_buf[0], length, 0); + +#if 0 + int i=0; + int *data_addr= qtd->hw_buf[0]; + printk("\n"); + for(i=0;istate &= ~QTD_STATE_NEW; + qtd->state |= QTD_STATE_SCHEDULED; + + pehci_entry("-- %s: Exit\n", __FUNCTION__); + return; +} +#ifdef USBNET + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +static void +pehci_hcd_urb_delayed_complete(phci_hcd * hcd, struct ehci_qh *qh, struct urb *urb, + td_ptd_map_t * td_ptd_map, struct pt_regs *regs) +#else +static void +pehci_hcd_urb_delayed_complete(phci_hcd * hcd, struct ehci_qh *qh, struct urb *urb, + td_ptd_map_t * td_ptd_map) +#endif +{ + static u32 remove = 0; + static u32 qh_state = 0; + + urb_priv_t *urb_priv = (urb_priv_t *) urb->hcpriv; + +#ifdef USBNET + struct isp1763_async_cleanup_urb *urb_st = 0; +#endif + + + + urb_priv->timeout = 0; + + if((td_ptd_map->state == TD_PTD_REMOVE ) || + (urb_priv->state == DELETE_URB) || + !HCD_IS_RUNNING(hcd->state)){ + remove=1; + } + qh_state=qh->qh_state; + qh->qh_state = QH_STATE_COMPLETING; + /*remove the done tds */ + spin_lock(&hcd_data_lock); + phci_hcd_urb_free_priv(hcd, urb_priv, qh); + spin_unlock(&hcd_data_lock); + + urb_priv->timeout = 0; + kfree(urb_priv); + urb->hcpriv = 0; + + + /*if normal completion */ + if (urb->status == -EINPROGRESS) { + urb->status = 0; + } + + if(remove) + if (list_empty(&qh->qtd_list)) { + phci_hcd_release_td_ptd_index(qh); + } + remove=0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + if(!usb_hcd_check_unlink_urb(&hcd->usb_hcd, urb,0)) + usb_hcd_unlink_urb_from_ep(&hcd->usb_hcd,urb); +#endif + +//if(qh_state!=QH_STATE_COMPLETING) +{ +// spin_unlock(&hcd->lock); + /* assume interrupt has been disabled and has acquired hcd->lock */ + urb_st = (struct isp1763_async_cleanup_urb *)kmalloc(sizeof(struct isp1763_async_cleanup_urb), GFP_ATOMIC); + urb_st->urb = urb; + list_add_tail(&urb_st->urb_list, &(hcd->cleanup_urb.urb_list)); + +// isp1763_reg_write16(hcd->dev, hcd->regs.interruptenable, INTR_ENABLE_MASK | HC_SOF_INT); + isp1763_reg_write16(hcd->dev, hcd->regs.interruptenable, HC_MSOF_INT); +// spin_lock(&hcd->lock); +} + + pehci_entry("-- %s: Exit\n", __FUNCTION__); +} +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +static void +pehci_hcd_urb_complete(phci_hcd * hcd, struct ehci_qh *qh, struct urb *urb, + td_ptd_map_t * td_ptd_map, struct pt_regs *regs) +#else +static void +pehci_hcd_urb_complete(phci_hcd * hcd, struct ehci_qh *qh, struct urb *urb, + td_ptd_map_t * td_ptd_map) +#endif +{ + static u32 remove = 0; + static u32 qh_state = 0; + urb_priv_t *urb_priv = (urb_priv_t *) urb->hcpriv; + + if(urb_priv==NULL){ + printk("***************urb_priv is NULL ************ %s: Entered\n", __FUNCTION__); + goto exit; + } + pehci_check("complete the td , length: %d\n", td_ptd_map->qtd->length); + urb_priv->timeout = 0; + + if((td_ptd_map->state == TD_PTD_REMOVE ) || + (urb_priv->state == DELETE_URB) || + !HCD_IS_RUNNING(hcd->state)){ + remove=1; + } + + + qh_state=qh->qh_state; + + qh->qh_state = QH_STATE_COMPLETING; + /*remove the done tds */ + spin_lock(&hcd_data_lock); + phci_hcd_urb_free_priv(hcd, urb_priv, qh); + spin_unlock(&hcd_data_lock); + + urb_priv->timeout = 0; + kfree(urb_priv); + urb->hcpriv = 0; + + + /*if normal completion */ + if (urb->status == -EINPROGRESS) { + urb->status = 0; + } + + if(remove) + if (list_empty(&qh->qtd_list)) { + phci_hcd_release_td_ptd_index(qh); + } + remove=0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + if(!usb_hcd_check_unlink_urb(&hcd->usb_hcd, urb,0)) + { + usb_hcd_unlink_urb_from_ep(&hcd->usb_hcd,urb); + } +#endif + spin_unlock(&hcd->lock); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + usb_hcd_giveback_urb(&hcd->usb_hcd, urb); +#else + usb_hcd_giveback_urb(&hcd->usb_hcd, urb, urb->status); +#endif + spin_lock(&hcd->lock); +exit: + pehci_entry("-- %s: Exit\n", __FUNCTION__); + +} + +/*update the error status of the td*/ +static void +pehci_hcd_update_error_status(u32 ptdstatus, struct urb *urb) +{ + /*if ptd status is halted */ + if (ptdstatus & PTD_STATUS_HALTED) { + if (ptdstatus & PTD_XACT_ERROR) { + /*transaction error results due to retry count goes to zero */ + if (PTD_RETRY(ptdstatus)) { + /*halt the endpoint */ + printk("transaction error , retries %d\n", + PTD_RETRY(ptdstatus)); + urb->status = -EPIPE; + } else { + printk("transaction error , retries %d\n", + PTD_RETRY(ptdstatus)); + /*protocol error */ + urb->status = -EPROTO; + } + } else if (ptdstatus & PTD_BABBLE) { + printk("babble error, qha %x\n", ptdstatus); + /*babble error */ + urb->status = -EOVERFLOW; + } else if (PTD_RETRY(ptdstatus)) { + printk("endpoint halted with retrie remaining %d\n", + PTD_RETRY(ptdstatus)); + urb->status = -EPIPE; + } else { /*unknown error, i will report it as halted, as i will never see xact error bit set */ + printk("protocol error, qha %x\n", ptdstatus); + urb->status = -EPIPE; + } + + /*if halted need to recover */ + if (urb->status == -EPIPE) { + } + } +} + +#ifdef CONFIG_ISO_SUPPORT /* New code for ISO support */ + +/******************************************************************* + * phcd_iso_handler - ISOCHRONOUS Transfer handler + * + * phci_hcd *hcd, + * Host controller driver structure which contains almost all data + * needed by the host controller driver to process data and interact + * with the host controller. + * + * struct pt_regs *regs + * + * API Description + * This is the ISOCHRONOUS Transfer handler, mainly responsible for: + * - Checking the periodic list if there are any ITDs for scheduling or + * removal. + * - For ITD scheduling, converting an ITD into a PTD, which is the data + * structure that the host contrtoller can understand and process. + * - For ITD completion, checking the transfer status and performing the + * required actions depending on status. + * - Freeing up memory used by an ITDs once it is not needed anymore. + ************************************************************************/ +void +pehci_hcd_iso_sitd_schedule(phci_hcd *hcd,struct urb* urb,struct ehci_sitd* sitd){ + td_ptd_map_t *td_ptd_map; + td_ptd_map_buff_t *ptd_map_buff; + struct _isp1763_isoptd *iso_ptd; + u32 ormask = 0, skip_map = 0,last_map=0,buff_stat=0; + struct isp1763_mem_addr *mem_addr; + ptd_map_buff = &(td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]); + + /* Get the PTD allocated for this SITD. */ + td_ptd_map = + &ptd_map_buff->map_list[sitd-> + sitd_index]; + iso_ptd = &hcd->isotd; + + memset(iso_ptd, 0, sizeof(struct _isp1763_isoptd)); + /* Read buffer status register to check later if the ISO buffer is + filled or not */ + buff_stat = + isp1763_reg_read16(hcd->dev, hcd->regs.buffer_status,buff_stat); + + /* Read the contents of the ISO skipmap register */ + skip_map = + isp1763_reg_read16(hcd->dev, hcd->regs.isotdskipmap, + skip_map); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_sitd_schedule]: Read skip map: 0x%08x\n", + (unsigned int) skip_map); + + /* Read the contents of the ISO lastmap register */ + last_map = + isp1763_reg_read16(hcd->dev, hcd->regs.isotdlastmap, + last_map); + + /* Read the contents of the ISO ormask register */ + ormask = isp1763_reg_read16(hcd->dev, hcd->regs.iso_irq_mask_or, + ormask); + + /* Create a PTD from an SITD */ + phcd_iso_sitd_to_ptd(hcd, sitd, sitd->urb, + (void *) iso_ptd); + /* Indicate that this SITD's PTD have been + filled up */ + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + + /* + * Place the newly initialized ISO PTD structure into + the location allocated for this PTD in the ISO PTD + memory region. + */ +#ifdef SWAP + isp1763_mem_write(hcd->dev, + td_ptd_map->ptd_header_addr, 0, + (u32 *) iso_ptd, PHCI_QHA_LENGTH, 0, + PTD_HED); +#else /* NO_SWAP */ + isp1763_mem_write(hcd->dev, + td_ptd_map->ptd_header_addr, 0, + (u32 *) iso_ptd,PHCI_QHA_LENGTH, 0); +#endif + + /* + * Set this flag to avoid unlinking before + schedule at particular frame number + */ + td_ptd_map->state = TD_PTD_IN_SCHEDULE; + + /* + * If the length is not zero and the direction is + OUT then copy the data to be transferred + into the PAYLOAD memory area. + */ + if (sitd->length) { + switch (PTD_PID(iso_ptd->td_info2)) { + case OUT_PID: + /* Get the Payload memory + allocated for this PTD */ + mem_addr = &sitd->mem_addr; +#ifdef SWAP + isp1763_mem_write(hcd->dev, + (unsigned long) + mem_addr-> phy_addr, + 0, (u32*) + ((sitd->hw_bufp[0])), + sitd->length, 0, + PTD_PAY); +#else /* NO_SWAP */ + isp1763_mem_write(hcd->dev, + (unsigned long) + mem_addr->phy_addr, + 0, (u32 *) + sitd->hw_bufp[0], + sitd->length, 0); +#endif + break; + } + /* switch(PTD_PID(iso_ptd->td_info2))*/ + } + + /* if(sitd->length) */ + /* If this is the last td, indicate to complete + the URB */ + if (sitd->hw_next == EHCI_LIST_END) { + td_ptd_map->lasttd = 1; + } + + /* + * Clear the bit corresponding to this PTD in + the skip map so that it will be processed on + the next schedule traversal. + */ + skip_map &= ~td_ptd_map->ptd_bitmap; + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_sitd_schedule]: Skip map:0x%08x\n",(unsigned int) skip_map); + + /* + * Update the last map register to indicate + that the newly created PTD is the last PTD + added only if it is larger than the previous + bitmap. + */ + if (last_map < td_ptd_map->ptd_bitmap) { + isp1763_reg_write16(hcd->dev, + hcd->regs.isotdlastmap, + td_ptd_map->ptd_bitmap); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_sitd_schedule]:Last Map: 0x%08x\n", + td_ptd_map->ptd_bitmap); + } + + /* + * Set the ISO_BUF_FILL bit to 1 to indicate + that there is a PTD for ISO that needs to + * be processed. + */ + isp1763_reg_write16(hcd->dev, + hcd->regs.buffer_status, + (buff_stat | ISO_BUFFER)); + + isp1763_reg_write16(hcd->dev, hcd->regs.isotdskipmap,skip_map); + +} + +/******************************************************************* + * phcd_iso_handler - ISOCHRONOUS Transfer handler + * + * phci_hcd *hcd, + * Host controller driver structure which contains almost all data + * needed by the host controller driver to process data and interact + * with the host controller. + * + * struct pt_regs *regs + * + * API Description + * This is the ISOCHRONOUS Transfer handler, mainly responsible for: + * - Checking the periodic list if there are any ITDs for scheduling or + * removal. + * - For ITD scheduling, converting an ITD into a PTD, which is the data + * structure that the host contrtoller can understand and process. + * - For ITD completion, checking the transfer status and performing the + * required actions depending on status. + * - Freeing up memory used by an ITDs once it is not needed anymore. + ************************************************************************/ +void +pehci_hcd_iso_schedule(phci_hcd * hcd, struct urb *urb) +{ + struct list_head *sitd_itd_sched, *position; + struct ehci_itd *itd; + struct ehci_sitd *sitd; + td_ptd_map_t *td_ptd_map; + unsigned long last_map; + td_ptd_map_buff_t *ptd_map_buff; + struct _isp1763_isoptd *iso_ptd; + unsigned long buff_stat; + struct isp1763_mem_addr *mem_addr; + u32 ormask = 0, skip_map = 0; + u32 iNumofPkts; + unsigned int iNumofSlots = 0, mult = 0; + struct ehci_qh *qhead; + + buff_stat = 0; + iso_dbg(ISO_DBG_ENTRY, "[pehci_hcd_iso_schedule]: Enter\n"); + iso_ptd = &hcd->isotd; + + last_map = 0; + /* Check if there are any ITDs scheduled for processing */ + if (hcd->periodic_sched == 0) { + return; + } + if (urb->dev->speed == USB_SPEED_HIGH) { + mult = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + mult = 1 + ((mult >> 11) & 0x3); + iNumofSlots = NUMMICROFRAME / urb->interval; + /*number of PTDs need to schedule for this PTD */ + iNumofPkts = (urb->number_of_packets / mult) / iNumofSlots; + if ((urb->number_of_packets / mult) % iNumofSlots != 0){ + /*get remainder */ + iNumofPkts += 1; + } + } else{ + iNumofPkts = urb->number_of_packets; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qhead = urb->hcpriv; +#else + qhead = urb->ep->hcpriv; +#endif + if (!qhead) { + iso_dbg(ISO_DBG_ENTRY, + "[pehci_hcd_iso_schedule]: Qhead==NULL\n"); + return ; + } + ptd_map_buff = &(td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]); + + while (iNumofPkts > 0) { + /* Read buffer status register to check later if the ISO buffer is + filled or not */ + buff_stat = + isp1763_reg_read16(hcd->dev, hcd->regs.buffer_status,buff_stat); + + /* Read the contents of the ISO skipmap register */ + skip_map = + isp1763_reg_read16(hcd->dev, hcd->regs.isotdskipmap, + skip_map); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_schedule]: Read skip map: 0x%08x\n", + (unsigned int) skip_map); + + /* Read the contents of the ISO lastmap register */ + last_map = + isp1763_reg_read16(hcd->dev, hcd->regs.isotdlastmap, + last_map); + + /* Read the contents of the ISO ormask register */ + ormask = isp1763_reg_read16(hcd->dev, hcd->regs.iso_irq_mask_or, + ormask); + + /* Process ITDs linked to this frame, checking if there are any that needs to + be scheduled */ + sitd_itd_sched = &qhead->periodic_list.sitd_itd_head; + if (list_empty(sitd_itd_sched)) { + iso_dbg(ISO_DBG_INFO, + "[pehci_hcd_iso_schedule]: ISO schedule list's empty. Nothing to schedule.\n"); + return; + } + + list_for_each(position, sitd_itd_sched) { + if (qhead->periodic_list.high_speed == 0){ + /* Get an SITD in the list for processing */ + sitd = list_entry(position, struct ehci_sitd, + sitd_list); + iNumofPkts--; + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_schedule]: SITD Index:%d\n", sitd->sitd_index); + if(sitd->sitd_index==TD_PTD_INV_PTD_INDEX) + continue; + /* Get the PTD allocated for this SITD. */ + td_ptd_map = + &ptd_map_buff->map_list[sitd-> + sitd_index]; + memset(iso_ptd, 0, + sizeof(struct _isp1763_isoptd)); + + /* Create a PTD from an SITD */ + phcd_iso_sitd_to_ptd(hcd, sitd, sitd->urb, + (void *) iso_ptd); + + /* Indicate that this SITD's PTD have been + filled up */ + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + + /* + * Place the newly initialized ISO PTD structure into + the location allocated for this PTD in the ISO PTD + memory region. + */ +#ifdef SWAP + isp1763_mem_write(hcd->dev, + td_ptd_map->ptd_header_addr, 0, + (u32 *) iso_ptd, PHCI_QHA_LENGTH, 0, + PTD_HED); +#else /* NO_SWAP */ + isp1763_mem_write(hcd->dev, + td_ptd_map->ptd_header_addr, 0, + (u32 *) iso_ptd,PHCI_QHA_LENGTH, 0); +#endif + + /* + * Set this flag to avoid unlinking before + schedule at particular frame number + */ + td_ptd_map->state = TD_PTD_IN_SCHEDULE; + + /* + * If the length is not zero and the direction is + OUT then copy the data to be transferred + into the PAYLOAD memory area. + */ + if (sitd->length) { + switch (PTD_PID(iso_ptd->td_info2)) { + case OUT_PID: + /* Get the Payload memory + allocated for this PTD */ + mem_addr = &sitd->mem_addr; +#ifdef SWAP + isp1763_mem_write(hcd->dev, + (unsigned long) + mem_addr-> phy_addr, + 0, (u32*) + ((sitd->hw_bufp[0])), + sitd->length, 0, + PTD_PAY); +#else /* NO_SWAP */ + isp1763_mem_write(hcd->dev, + (unsigned long) + mem_addr->phy_addr, + 0, (u32 *) + sitd->hw_bufp[0], + sitd->length, 0); +#endif + break; + } + /* switch(PTD_PID(iso_ptd->td_info2))*/ + } + + /* if(sitd->length) */ + /* If this is the last td, indicate to complete + the URB */ + if (sitd->hw_next == EHCI_LIST_END) { + td_ptd_map->lasttd = 1; + } + + /* + * Clear the bit corresponding to this PTD in + the skip map so that it will be processed on + the next schedule traversal. + */ + skip_map &= ~td_ptd_map->ptd_bitmap; + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_schedule]: Skip map:0x%08x\n",(unsigned int) skip_map); + + /* + * Update the last map register to indicate + that the newly created PTD is the last PTD + added only if it is larger than the previous + bitmap. + */ + if (last_map < td_ptd_map->ptd_bitmap) { + isp1763_reg_write16(hcd->dev, + hcd->regs.isotdlastmap, + td_ptd_map->ptd_bitmap); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_schedule]:Last Map: 0x%08x\n", + td_ptd_map->ptd_bitmap); + } + + /* + * Set the ISO_BUF_FILL bit to 1 to indicate + that there is a PTD for ISO that needs to + * be processed. + */ + isp1763_reg_write16(hcd->dev, + hcd->regs.buffer_status, + (buff_stat | ISO_BUFFER)); + + } else { /*HIGH SPEED */ + + /* Get an ITD in the list for processing */ + itd = list_entry(position, struct ehci_itd, + itd_list); + iNumofPkts--; + + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_schedule]: ITD Index: %d\n", itd->itd_index); + /* Get the PTD allocated for this ITD. */ + td_ptd_map = + &ptd_map_buff->map_list[itd->itd_index]; + memset(iso_ptd, 0, + sizeof(struct _isp1763_isoptd)); + + /* Create a PTD from an ITD */ + phcd_iso_itd_to_ptd(hcd, itd, itd->urb, + (void *) iso_ptd); + + /* Indicate that this SITD's PTD have been + filled up */ + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + + /* + * Place the newly initialized ISO PTD + structure into the location allocated + * for this PTD in the ISO PTD memory region. + */ +#ifdef SWAP + isp1763_mem_write(hcd->dev, + td_ptd_map->ptd_header_addr, 0, + (u32 *) iso_ptd,PHCI_QHA_LENGTH, 0, + PTD_HED); +#else /* NO_SWAP */ + isp1763_mem_write(hcd->dev, + td_ptd_map->ptd_header_addr, 0, + (u32 *) iso_ptd,PHCI_QHA_LENGTH, 0); +#endif + /* + * Set this flag to avoid unlinking before schedule + * at particular frame number + */ + td_ptd_map->state = TD_PTD_IN_SCHEDULE; + + /* + * If the length is not zero and the direction + is OUT then copy the data to be transferred + into the PAYLOAD memory area. + */ + if (itd->length) { + switch (PTD_PID(iso_ptd->td_info2)) { + case OUT_PID: + /* Get the Payload memory + allocated for this PTD */ + mem_addr = &itd->mem_addr; +#ifdef SWAP + isp1763_mem_write(hcd->dev, + (unsigned long) + mem_addr->phy_addr, 0, + (u32*) + ((itd->hw_bufp[0])), + itd->length, 0, + PTD_PAY); +#else /* NO_SWAP */ + isp1763_mem_write(hcd->dev, + (unsigned long) + mem_addr->phy_addr, 0, + (u32 *)itd->hw_bufp[0], + itd->length, 0); +#endif + break; + } + /* switch(PTD_PID(iso_ptd->td_info2)) */ + } + + + /* If this is the last td, indicate to + complete the URB */ + if (itd->hw_next == EHCI_LIST_END) { + td_ptd_map->lasttd = 1; + } + + /* + * Clear the bit corresponding to this PT D + in the skip map so that it will be processed + on the next schedule traversal. + */ + skip_map &= ~td_ptd_map->ptd_bitmap; + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_schedule]: Skip map:0x%08x\n",(unsigned int) skip_map); + isp1763_reg_write16(hcd->dev, + hcd->regs.isotdskipmap, + skip_map); + + /* + * Update the last map register to indicate + that the newly created PTD is the last PTD + added only if it is larger than the previous + bitmap. + */ + if (last_map < td_ptd_map->ptd_bitmap) { + isp1763_reg_write16(hcd->dev, + hcd->regs.isotdlastmap, + td_ptd_map->ptd_bitmap); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_schedule]:Last Map: 0x%08x\n", + td_ptd_map->ptd_bitmap); + } + + /* + * Set the ISO_BUF_FILL bit to 1 to indicate + that there is a PTD for ISO that needs to + * be processed. + */ + isp1763_reg_write16(hcd->dev, + hcd->regs.buffer_status, + (buff_stat | ISO_BUFFER)); + } + } /* list_for_each(position, itd_sched) */ + isp1763_reg_write16(hcd->dev, hcd->regs.isotdskipmap,skip_map); + }/*end of while (igNumOfPkts) */ + + iso_dbg(ISO_DBG_INFO, + "[pehci_hcd_iso_schedule]: ISO-Frame scheduling done\n"); + iso_dbg(ISO_DBG_ENTRY, "[pehci_hcd_iso_schedule]: Exit\n"); +} + +/******************************************************************* + * phcd_iso_handler - ISOCHRONOUS Transfer handler + * + * phci_hcd *hcd, + * Host controller driver structure which contains almost all data + * needed by the host controller driver to process data and interact + * with the host controller. + * + * struct pt_regs *regs + * + * API Description + * This is the ISOCHRONOUS Transfer handler, mainly responsible for: + * - Checking the periodic list if there are any ITDs for scheduling or + * removal. + * - For ITD scheduling, converting an ITD into a PTD, which is the data + * structure that the host contrtoller can understand and process. + * - For ITD completion, checking the transfer status and performing the + * required actions depending on status. + * - Freeing up memory used by an ITDs once it is not needed anymore. + ************************************************************************/ + +int debugiso = 0; + +void +pehci_hcd_iso_worker(phci_hcd * hcd) +{ + u32 donemap = 0, skipmap = 0; /*ormask = 0, buff_stat = 0;*/ + u32 pendingmap = 0; + u32 mask = 0x1, index = 0, donetoclear = 0; + u32 uFrIndex = 0; + unsigned char last_td = FALSE, iReject = 0; + struct isp1763_mem_addr *mem_addr; + struct _isp1763_isoptd *iso_ptd; + unsigned long length = 0, uframe_cnt, usof_stat; + struct ehci_qh *qhead; + struct ehci_itd *itd, *current_itd; + struct ehci_sitd *sitd=0, *current_sitd=0; + td_ptd_map_t *td_ptd_map; + td_ptd_map_buff_t *ptd_map_buff; + struct list_head *sitd_itd_remove, *position;// *lst_temp; + struct urb *urb; + u8 i = 0; + unsigned long startAdd = 0; + int ret = 0; + + + iso_ptd = &hcd->isotd; + + /* Check if there are any ITDs scheduled for processing */ + if (hcd->periodic_sched == 0) { + goto exit; + } + ptd_map_buff = &(td_ptd_map_buff[TD_PTD_BUFF_TYPE_ISTL]); + pendingmap = ptd_map_buff->pending_ptd_bitmap; + + + /*read the done map for interrupt transfers */ + donemap = isp1763_reg_read16(hcd->dev, hcd->regs.isotddonemap, donemap); + + iso_dbg(ISO_DBG_ENTRY, "[pehci_hcd_iso_worker]: Enter %x \n", donemap); + if (!donemap) { /*there isnt any completed PTD */ + goto exit; + } + donetoclear = donemap; + uFrIndex = 0; + while (donetoclear) { + mask = 0x1 << uFrIndex; + index = uFrIndex; + uFrIndex++; + if (!(donetoclear & mask)) + continue; + donetoclear &= ~mask; + iso_dbg(ISO_DBG_DATA, "[pehci_hcd_iso_worker]: uFrIndex = %d\n", index); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]:donetoclear = 0x%x mask = 0x%x\n", + donetoclear, mask); + + + if (ptd_map_buff->map_list[index].sitd) { + urb = ptd_map_buff->map_list[index].sitd->urb; + if (!urb) { + printk("ERROR : URB is NULL \n"); + continue; + } + sitd = ptd_map_buff->map_list[index].sitd; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qhead=urb->hcpriv; +#else + qhead = urb->ep->hcpriv; +#endif + if (!qhead) { + printk("ERROR : Qhead is NULL \n"); + continue; + } + + sitd_itd_remove = &qhead->periodic_list.sitd_itd_head; + } else if (ptd_map_buff->map_list[index].itd) { + urb = ptd_map_buff->map_list[index].itd->urb; + if (!urb) { + printk("ERROR : URB is NULL \n"); + continue; + } + itd = ptd_map_buff->map_list[index].itd; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qhead=urb->hcpriv; +#else + qhead = urb->ep->hcpriv; +#endif + if (!qhead) { + printk("ERROR : Qhead is NULL \n"); + continue; + } + + sitd_itd_remove = &qhead->periodic_list.sitd_itd_head; + + } else { + printk("ERROR : NO sitd in that PTD location : \n"); + continue; + } + /* Process ITDs linked to this frame, checking for completed ITDs */ + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: Removal Frame number: %d\n", + (int) index); + if (list_empty(sitd_itd_remove)) { + continue; + } + + if (urb) { + last_td = FALSE; + if (qhead->periodic_list.high_speed == 0)/*FULL SPEED*/ + { + + /* Get the PTD that was allocated for this + particular SITD*/ + td_ptd_map = + &ptd_map_buff->map_list[sitd-> + sitd_index]; + + iso_dbg(ISO_DBG_INFO, + "[pehci_hcd_iso_worker]: PTD is done,%d\n",index); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: SITD Index: %d\n",sitd->sitd_index); + urb = sitd->urb; + + /* + * Get the base address of the memory allocated + in the PAYLOAD region for this SITD + */ + mem_addr = &sitd->mem_addr; + memset(iso_ptd, 0, + sizeof(struct _isp1763_isoptd)); + + /* + * Read this ptd from the ram address, + address is in the td_ptd_map->ptd_header_addr + */ + + isp1763_mem_read(hcd->dev, + td_ptd_map->ptd_header_addr, + 0, (u32 *) iso_ptd, + PHCI_QHA_LENGTH, 0); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD0 = 0x%08x\n", iso_ptd->td_info1); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD1 = 0x%08x\n", iso_ptd->td_info2); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD2 = 0x%08x\n", iso_ptd->td_info3); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD3 = 0x%08x\n", iso_ptd->td_info4); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD4 = 0x%08x\n", iso_ptd->td_info5); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD5 = 0x%08x\n", iso_ptd->td_info6); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD6 = 0x%08x\n", iso_ptd->td_info7); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD7 = 0x%08x\n", iso_ptd->td_info8); + + /* Go over the status of each of the 8 Micro Frames */ + for (uframe_cnt = 0; uframe_cnt < 8; + uframe_cnt++) { + /* + * We go over the status one at a time. The status bits and their + * equivalent status are: + * Bit 0 - Transaction Error (IN and OUT) + * Bit 1 - Babble (IN token only) + * Bit 2 - Underrun (OUT token only) + */ + usof_stat = + iso_ptd->td_info5 >> (8 + + (uframe_cnt * 3)); + + switch (usof_stat & 0x7) { + case INT_UNDERRUN: + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: Buffer underrun\n"); + urb->error_count++; + break; + case INT_EXACT: + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: Transaction error\n"); + printk("[pehci_hcd_iso_worker Error]: Transaction error\n"); + urb->error_count++; + break; + case INT_BABBLE: + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: Babble error\n"); + printk("[pehci_hcd_iso_worker Error]: Babble error\n"); + urb->iso_frame_desc[sitd->sitd_index].status + = -EOVERFLOW; + urb->error_count++; + break; + } /* switch(usof_stat & 0x7) */ + } /* end of for( ulMicroFrmCnt = 0; ulMicroFrmCnt < 8; ulMicroFrmCnt++) */ + + /* + * Get the number of bytes transferred. This indicates the number of + * bytes sent or received for this transaction. + */ + if (urb->dev->speed != USB_SPEED_HIGH) { + /* Length is 1K for full/low speed device */ + length = PTD_XFERRED_NONHSLENGTH + (iso_ptd->td_info4); + } else { + /* Length is 32K for high speed device */ + length = PTD_XFERRED_LENGTH(iso_ptd-> + td_info4); + } + + /* Halted, need to finish all the transfer on this endpoint */ + if (iso_ptd->td_info4 & PTD_STATUS_HALTED) { + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error] PTD Halted\n"); + printk("[pehci_hcd_iso_worker Error] PTD Halted\n"); + /* + * When there is an error, do not process the other PTDs. + * Stop at the PTD with the error and remove all other PTDs. + */ + td_ptd_map->lasttd = 1; + + /* + * In case of halt, next transfer will start with toggle zero, + * USB specs, 5.8.5 + */ + td_ptd_map->datatoggle = 0; + } + + /* if(iso_ptd->td_info4 & PTD_STATUS_HALTED) */ + /* Update the actual length of the transfer from the data we got earlier */ + urb->iso_frame_desc[sitd->index].actual_length = + length; + + /* If the PTD have been executed properly the V bit should be cleared */ + if (iso_ptd->td_info1 & QHA_VALID) { + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: Valid bit not cleared\n"); + printk("[pehci_hcd_iso_worker Error]: Valid bit not cleared\n"); + urb->iso_frame_desc[sitd->index]. + status = -ENOSPC; + } else { + urb->iso_frame_desc[sitd->index]. + status = 0; + } + + /* Check if this is the last SITD either due to some error or normal completion */ + if ((td_ptd_map->lasttd) + || (sitd->hw_next == EHCI_LIST_END)) { + last_td = TRUE; + } + + /* Copy data to/from */ + if (length && (length <= MAX_PTD_BUFFER_SIZE)) { + switch (PTD_PID(iso_ptd->td_info2)) { + case IN_PID: + /* + * Get the data from the PAYLOAD area and place it into + * the buffer provided by the requestor. + */ + + isp1763_mem_read(hcd->dev, + (unsigned long)mem_addr-> + phy_addr, 0,(u32 *) + sitd->hw_bufp[0], + length, 0); + + case OUT_PID: + /* + * urb->actual length was initialized to zero, so for the first + * uFrame having it incremented immediately is not a problem. + */ + urb->actual_length += length; + break; + }/* switch(PTD_PID(iso_ptd->td_info2)) */ + } + /* if(length && (length <= MAX_PTD_BUFFER_SIZE)) */ +// removesitd: + /*read skip-map */ + skipmap = + isp1763_reg_read16(hcd->dev, + hcd->regs.isotdskipmap, + skipmap); + iso_dbg(ISO_DBG_DATA, + "[%s] : read skipmap =0x%x\n", + __FUNCTION__, skipmap); + if (last_td == TRUE) { + /* Start removing the ITDs in the list */ + while (1) { + /* + * This indicates that we are processing the tail PTD. + * Perform cleanup procedure on this last PTD + */ + if (sitd->hw_next == EHCI_LIST_END) { + td_ptd_map = + &ptd_map_buff-> + map_list[sitd-> + sitd_index]; + + /* + * Free up our allocation in the PAYLOAD area so that others can use + * it. + */ +#ifndef COMMON_MEMORY + phci_hcd_mem_free + (&sitd-> + mem_addr); +#endif + /* Remove this SITD entry in the SITD list */ + list_del(&sitd-> + sitd_list); + + /* Free up the memory allocated for the SITD structure */ + qha_free(qha_cache, + sitd); + + /* Indicate that the PTD we have used is now free */ + td_ptd_map->state = + TD_PTD_NEW; + td_ptd_map->sitd = NULL; + td_ptd_map->itd = NULL; + + /* Decrease the number of active PTDs scheduled */ + hcd->periodic_sched--; + + /* Skip this PTD during the next PTD processing. */ + skipmap |= + td_ptd_map->ptd_bitmap; + isp1763_reg_write16 + (hcd->dev, + hcd->regs. + isotdskipmap, + skipmap); + + /* All ITDs in this list have been successfully removed. */ + break; + } else { + /* + * This indicates that we stopped due to an error on a PTD that is + * not the last in the list. We need to free up this PTD as well as + * the PTDs after it. + */ + /* + * Put the current SITD error onto this variable. + * We will be unlinking this from the list and free up its + * resources later. + */ + current_sitd = sitd; + + td_ptd_map = + &ptd_map_buff-> + map_list[sitd-> + sitd_index]; + + /* + * Get the next SITD, and place it to the sitd variable. + * In a way we are moving forward in the SITD list. + */ + sitd = (struct ehci_sitd + *) + (current_sitd-> + hw_next); + /* Free up the current SITD's resources */ +#ifndef COMMON_MEMORY + phci_hcd_mem_free + (¤t_sitd-> + mem_addr); +#endif + /* Remove this SITD entry in the SITD list */ + list_del(¤t_sitd-> + sitd_list); + + /* Free up the memory allocated for the SITD structure */ + qha_free(qha_cache, + current_sitd); + + /* Inidicate that the PTD we have used is now free */ + td_ptd_map->state = + TD_PTD_NEW; + td_ptd_map->sitd = NULL; + td_ptd_map->itd = NULL; + + /* Decrease the number of active PTDs scheduled */ + hcd->periodic_sched--; + + /* Sine it is done, skip this PTD during the next PTD processing. */ + skipmap |= + td_ptd_map-> + ptd_bitmap; + isp1763_reg_write16 + (hcd->dev, + hcd->regs. + isotdskipmap, + skipmap); + /* + * Start all over again until it gets to the tail of the + * list of PTDs/ITDs + */ + continue; + } /* else of if(sitd->hw_next == EHCI_LIST_END) */ + + /* It should never get here, but I put this as a precaution */ + break; + } /*end of while(1) */ + + /* Check if there were ITDs that were not processed due to the error */ + if (urb->status == -EINPROGRESS) { + if ((urb->actual_length != + urb->transfer_buffer_length) + && (urb->transfer_flags & + URB_SHORT_NOT_OK)) { + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: Short Packet\n"); + urb->status = + -EREMOTEIO; + } else { + urb->status = 0; + } + } + + urb->hcpriv = 0; + iso_dbg(ISO_DBG_DATA, + "[%s] : remain skipmap =0x%x\n", + __FUNCTION__, skipmap); +#ifdef COMMON_MEMORY + phci_hcd_mem_free(&qhead->memory_addr); +#endif + /* We need to unlock this here, since this was locked when we are called + * from the interrupt handler */ + spin_unlock(&hcd->lock); + /* Perform URB cleanup */ + iso_dbg(ISO_DBG_INFO, + "[pehci_hcd_iso_worker] Complete a URB\n"); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + if(!usb_hcd_check_unlink_urb(&hcd->usb_hcd, urb,0)) + usb_hcd_unlink_urb_from_ep(&hcd->usb_hcd, + urb); +#endif + hcd->periodic_more_urb = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qhead=urb->hcpriv; + if (!list_empty(&qhead->ep->urb_list)) +#else + if (!list_empty(&urb->ep->urb_list)) +#endif + hcd->periodic_more_urb = 1; + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + usb_hcd_giveback_urb(&hcd->usb_hcd, urb); +#else + usb_hcd_giveback_urb(&hcd->usb_hcd, urb, urb->status); +#endif + + spin_lock(&hcd->lock); + continue; + } + + /* if( last_td == TRUE ) */ + /* + * If the last_td is not set then we do not need to check for errors and directly + * proceed with the cleaning sequence. + */ + iso_dbg(ISO_DBG_INFO, + "[pehci_hcd_iso_worker]: last_td is not set\n"); + /*update skipmap */ + skipmap |= td_ptd_map->ptd_bitmap; + isp1763_reg_write16(hcd->dev, + hcd->regs.isotdskipmap, + skipmap); + iso_dbg(ISO_DBG_DATA, + "%s : remain skipmap =0x%x\n", + __FUNCTION__, skipmap); + + /* Decrement the count of active PTDs */ + hcd->periodic_sched--; + /*schedule next PTD for this URB */ + if(qhead->actualptdstotalptds) + { + sitd_itd_remove = &qhead->periodic_list.sitd_itd_head; + /* find sitd to schedule */ + list_for_each(position, sitd_itd_remove) { + + if (qhead->periodic_list.high_speed == 0){ + /* Get an SITD in the list for processing */ + current_sitd= list_entry(position, struct ehci_sitd, + sitd_list); + if(current_sitd->sitd_index==TD_PTD_INV_PTD_INDEX) + break; + } + } + if(current_sitd->sitd_index==TD_PTD_INV_PTD_INDEX){ + qhead->actualptds++; + /*allocate memory and PTD index */ + memcpy(¤t_sitd->mem_addr,&sitd->mem_addr,sizeof(struct isp1763_mem_addr)); +// printk("current %x\n",sitd->sitd_index); + current_sitd->sitd_index=sitd->sitd_index; + /*schedule PTD */ + td_ptd_map->sitd = current_sitd; + hcd->periodic_sched++; + pehci_hcd_iso_sitd_schedule(hcd, urb,current_sitd); + } + + /* Remove this SITD from the list of active ITDs */ + list_del(&sitd->sitd_list); + + /* Free up the memory we allocated for the SITD structure */ + qha_free(qha_cache, sitd); + + + }else{ +#ifndef COMMON_MEMORY + phci_hcd_mem_free(&sitd->mem_addr); +#endif + /* Remove this SITD from the list of active ITDs */ + list_del(&sitd->sitd_list); + + /* Free up the memory we allocated for the SITD structure */ + qha_free(qha_cache, sitd); + + /* + * Clear the bit associated with this PTD from the grouptdmap and + * make this PTD available for other transfers + */ + td_ptd_map->state = TD_PTD_NEW; + td_ptd_map->sitd = NULL; + td_ptd_map->itd = NULL; + + } + + + + } else { /*HIGH SPEED */ + + /* Get an ITD in the list for processing */ + itd = ptd_map_buff->map_list[index].itd; + + /* Get the PTD that was allocated for this particular ITD. */ + td_ptd_map = + &ptd_map_buff->map_list[itd->itd_index]; + + iso_dbg(ISO_DBG_INFO, + "[pehci_hcd_iso_worker]: PTD is done , %d\n", + index); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: ITD Index: %d\n", + itd->itd_index); + + urb = itd->urb; + + /* + * Get the base address of the memory allocated in the + * PAYLOAD region for this ITD + */ + mem_addr = &itd->mem_addr; + memset(iso_ptd, 0, + sizeof(struct _isp1763_isoptd)); + + /* + * Read this ptd from the ram address,address is in the + * td_ptd_map->ptd_header_addr + */ + + isp1763_mem_read(hcd->dev, + td_ptd_map->ptd_header_addr, + 0, (u32 *) iso_ptd, + PHCI_QHA_LENGTH, 0); + + /* + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD0 = + 0x%08x\n", iso_ptd->td_info1); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD1 = + 0x%08x\n", iso_ptd->td_info2); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD2 = + 0x%08x\n", iso_ptd->td_info3); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD3 = + 0x%08x\n", iso_ptd->td_info4); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD4 = + 0x%08x\n",iso_ptd->td_info5); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD5 = + 0x%08x\n", iso_ptd->td_info6); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD6 = + 0x%08x\n", iso_ptd->td_info7); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_iso_worker]: DWORD7 = + 0x%08x\n", iso_ptd->td_info8); + */ + + + /* If the PTD have been executed properly, + the V bit should be cleared */ + if (iso_ptd->td_info1 & QHA_VALID) { + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: Valid bit not cleared\n"); + for(i = 0; inum_of_pkts; i++){ + urb->iso_frame_desc[itd->index + + i].status = -ENOSPC; + } + } else { + for (i = 0; inum_of_pkts; i++){ + urb->iso_frame_desc[itd->index + +i].status = 0; + } + } + + /* Go over the status of each of the 8 Micro Frames */ + for (uframe_cnt = 0; (uframe_cnt < 8) + && (uframe_cnt < itd->num_of_pkts); + uframe_cnt++) { + /* + * We go over the status one at a time. The status bits and their + * equivalent status are: + * Bit 0 - Transaction Error (IN and OUT) + * Bit 1 - Babble (IN token only) + * Bit 2 - Underrun (OUT token only) + */ + usof_stat = + iso_ptd->td_info5 >> (8 + + (uframe_cnt * 3)); + + switch (usof_stat & 0x7) { + case INT_UNDERRUN: + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: Buffer underrun\n"); + urb->iso_frame_desc[itd->index + + uframe_cnt]. + status = -ECOMM; + urb->error_count++; + break; + case INT_EXACT: + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: %p Transaction error\n", + urb); + urb->iso_frame_desc[itd->index + + uframe_cnt]. + status = -EPROTO; + urb->error_count++; + debugiso = 25; + break; + case INT_BABBLE: + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: Babble error\n"); + urb->iso_frame_desc[itd->index + + uframe_cnt]. + status = -EOVERFLOW; + urb->error_count++; + break; + }/* switch(usof_stat & 0x7) */ + }/* end of for( ulMicroFrmCnt = 0; ulMicroFrmCnt < 8; ulMicroFrmCnt++) */ + + /* + * Get the number of bytes transferred. This indicates the number of + * bytes sent or received for this transaction. + */ + + /* Length is 32K for high speed device */ + length = PTD_XFERRED_LENGTH(iso_ptd->td_info4); + + /* Halted, need to finish all the transfer on this endpoint */ + if (iso_ptd->td_info4 & PTD_STATUS_HALTED) { + + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error] PTD Halted\n"); + printk("[pehci_hcd_iso_worker Error] PTD Halted===============\n"); + /* + * When there is an error, do not process the other PTDs. + * Stop at the PTD with the error and remove all other PTDs. + */ + td_ptd_map->lasttd = 1; + + /* + * In case of halt, next transfer will start with toggle zero, + * USB specs, 5.8.5 + */ + td_ptd_map->datatoggle = 0; + } + /* if(iso_ptd->td_info4 & PTD_STATUS_HALTED) */ + /* Update the actual length of the transfer from the data we got earlier */ + if (PTD_PID(iso_ptd->td_info2) == OUT_PID) { + for (i = 0; i < itd->num_of_pkts; i++){ + urb->iso_frame_desc[itd->index + + i].actual_length =(unsigned int) + length / itd->num_of_pkts; + } + } else{ + iso_dbg(ISO_DBG_DATA, + "itd->num_of_pkts = %d, itd->ssplit = %x\n", + itd->num_of_pkts, itd->ssplit); + urb->iso_frame_desc[itd->index + + 0].actual_length = + iso_ptd->td_info6 & 0x00000FFF; + iso_dbg(ISO_DBG_DATA, + "actual length[0] = %d\n", + urb->iso_frame_desc[itd->index +0]. + actual_length); + + if((itd->num_of_pkts > 1) + && ((itd->ssplit & 0x2) == 0x2) + && (urb->iso_frame_desc[itd->index + + 1].status ==0)) { + + urb->iso_frame_desc[itd->index +1]. + actual_length = (iso_ptd-> + td_info6 & 0x00FFF000)>> 12; + + iso_dbg(ISO_DBG_DATA, + "actual length[1] = %d\n", + urb-> + iso_frame_desc[itd-> + index + 1]. + actual_length); + }else{ + urb->iso_frame_desc[itd->index +1]. + actual_length = 0; + } + + if ((itd->num_of_pkts > 2) + && ((itd->ssplit & 0x4) == 0x4) + && (urb-> + iso_frame_desc[itd->index + + 2].status ==0)) { + + urb->iso_frame_desc[itd->index + + 2].actual_length = + ((iso_ptd->td_info6 & + 0xFF000000 )>> 24) + | ((iso_ptd->td_info7 + & 0x0000000F)<< 8); + + iso_dbg(ISO_DBG_DATA, + "actual length[2] = %d\n", + urb->iso_frame_desc[itd-> + index + 2].actual_length); + } else{ + urb->iso_frame_desc[itd->index +2]. + actual_length = 0; + } + + if ((itd->num_of_pkts > 3) + && ((itd->ssplit & 0x8) == 0x8) + && (urb->iso_frame_desc[itd->index + + 3].status == 0)) { + + urb->iso_frame_desc[itd->index + 3]. + actual_length =(iso_ptd-> + td_info7 & 0x0000FFF0)>> 4; + + iso_dbg(ISO_DBG_DATA, + "actual length[3] = %d\n", + urb->iso_frame_desc[itd-> + index + 3].actual_length); + } else { + urb->iso_frame_desc[itd->index +3]. + actual_length = 0; + } + + if ((itd->num_of_pkts > 4) + && ((itd->ssplit & 0x10) == 0x10) + && (urb-> + iso_frame_desc[itd->index + + 4].status ==0)) { + + urb->iso_frame_desc[itd->index + + 4].actual_length = + (iso_ptd-> + td_info7 & 0x0FFF0000) >> 16; + + iso_dbg(ISO_DBG_DATA, + "actual length[4] = %d\n", + urb->iso_frame_desc[itd->index + + 4].actual_length); + } else { + urb->iso_frame_desc[itd->index + + 4].actual_length = 0; + } + + if ((itd->num_of_pkts > 5) + && ((itd->ssplit & 0x20) == 0x20) + && (urb-> + iso_frame_desc[itd->index + + 5].status == + 0)) { + + urb->iso_frame_desc[itd->index + + 5].actual_length = + ((iso_ptd-> + td_info7 & 0xF0000000) >> 28) | + ((iso_ptd->td_info8 & + 0x000000FF) + << 4); + + iso_dbg(ISO_DBG_DATA, + "actual length[5] = %d\n", + urb-> + iso_frame_desc[itd-> + index + + 5].actual_length); + } else { + urb->iso_frame_desc[itd->index + + 5].actual_length = 0; + } + + if ((itd->num_of_pkts > 6) + && ((itd->ssplit & 0x40) == 0x40) + && (urb-> + iso_frame_desc[itd->index + + 6].status ==0)) { + + urb->iso_frame_desc[itd->index + + 6].actual_length = + (iso_ptd-> + td_info8 & 0x000FFF00) + >> 8; + + iso_dbg(ISO_DBG_DATA, + "actual length[6] = %d\n", + urb-> + iso_frame_desc[itd-> + index + + 6].actual_length); + } else { + urb->iso_frame_desc[itd->index + + 6].actual_length = 0; + } + + if ((itd->num_of_pkts > 7) + && ((itd->ssplit & 0x80) == 0x80) + && (urb-> + iso_frame_desc[itd->index + + 7].status == + 0)) { + + urb->iso_frame_desc[itd->index + + 7].actual_length = + (iso_ptd-> + td_info8 & 0xFFF00000) >> 20; + + iso_dbg(ISO_DBG_DATA, + "actual length[7] = %d\n", + urb-> + iso_frame_desc[itd-> + index + + 7].actual_length); + } else { + urb->iso_frame_desc[itd->index + + 7].actual_length = 0; + } + } + /* Check if this is the last ITD either due to some error or normal completion */ + if ((td_ptd_map->lasttd) + || (itd->hw_next == EHCI_LIST_END)) { + + last_td = TRUE; + + } + + /* Copy data to/from */ + if (length && (length <= MAX_PTD_BUFFER_SIZE)) { + switch (PTD_PID(iso_ptd->td_info2)) { + case IN_PID: + /* + * Get the data from the PAYLOAD area and place it into + * the buffer provided by the requestor. + */ + /*for first packet*/ + startAdd = mem_addr->phy_addr; + iso_dbg(ISO_DBG_DATA, + "start add = %ld hw_bufp[0] = 0x%08x length = %d\n", + startAdd, + itd->hw_bufp[0], + urb-> + iso_frame_desc[itd-> + index].actual_length); + if (urb-> + iso_frame_desc[itd->index]. + status == 0) { + + if (itd->hw_bufp[0] ==0) { + dma_addr_t + buff_dma; + + buff_dma = + (u32) ((unsigned char *) urb->transfer_buffer + + urb->iso_frame_desc[itd->index].offset); + itd->buf_dma = + buff_dma; + itd->hw_bufp[0] + = + buff_dma; + } + if (itd->hw_bufp[0] !=0) { + + ret = isp1763_mem_read(hcd->dev, (unsigned long) + startAdd, + 0,(u32*)itd-> + hw_bufp[0], + urb-> + iso_frame_desc + [itd-> + index]. + actual_length, + 0); + + } else { + printk("isp1763_mem_read data payload fail\n"); + printk("start add = %ld hw_bufp[0] = 0x%08x length = %d\n", + startAdd, itd->hw_bufp[0], + urb->iso_frame_desc[itd->index].actual_length); + urb->iso_frame_desc[itd->index].status = -EPROTO; + urb->error_count++; + } + } + + + for (i = 1; + i < itd->num_of_pkts; + i++) { + startAdd += + (unsigned + long) (urb-> + iso_frame_desc + [itd-> + index + + i - 1]. + actual_length); + + iso_dbg(ISO_DBG_DATA, + "start add = %ld hw_bufp[%d] = 0x%08x length = %d\n", + startAdd, i, + itd->hw_bufp[i], + urb-> + iso_frame_desc + [itd->index + + i]. + actual_length); + if (urb-> + iso_frame_desc[itd-> + index + i]. + status == 0) { + + isp1763_mem_read + (hcd->dev, + startAdd, + 0,(u32*) + itd-> + hw_bufp + [i],urb-> + iso_frame_desc + [itd-> + index + i]. + actual_length, + 0); + + if (ret == -EINVAL){ + printk("isp1763_mem_read data payload fail %d\n", i); + } + } + } + + case OUT_PID: + /* + * urb->actual length was initialized to zero, so for the first + * uFrame having it incremented immediately is not a problem. + */ + urb->actual_length += length; + break; + } /* switch(PTD_PID(iso_ptd->td_info2)) */ + } + + /* if(length && (length <= MAX_PTD_BUFFER_SIZE)) */ +// removeitd: + /*read skip-map */ + skipmap = + isp1763_reg_read16(hcd->dev, + hcd->regs.isotdskipmap, + skipmap); + + iso_dbg(ISO_DBG_DATA, + "[%s] : read skipmap =0x%x\n", + __FUNCTION__, skipmap); + if (last_td == TRUE) { + /* Start removing the ITDs in the list */ + while (1) { + /* + * This indicates that we are processing the tail PTD. + * Perform cleanup procedure on this last PTD + */ + if (itd->hw_next == + EHCI_LIST_END) { + td_ptd_map = + &ptd_map_buff-> + map_list[itd-> + itd_index]; + + /* + * Free up our allocation in the PAYLOAD area so that others can use + * it. + */ +#ifndef COMMON_MEMORY + phci_hcd_mem_free(&itd-> + mem_addr); +#endif + + /* Remove this ITD entry in the ITD list */ + list_del(&itd-> + itd_list); + + /* Free up the memory allocated for the ITD structure */ + qha_free(qha_cache, + itd); + + /* Indicate that the PTD we have used is now free */ + td_ptd_map->state = + TD_PTD_NEW; + td_ptd_map->sitd = NULL; + td_ptd_map->itd = NULL; + + /* Decrease the number of active PTDs scheduled */ + hcd->periodic_sched--; + + /* Skip this PTD during the next PTD processing. */ + skipmap |= + td_ptd_map-> + ptd_bitmap; + + isp1763_reg_write16 + (hcd->dev, + hcd->regs. + isotdskipmap, + skipmap); + + /* All ITDs in this list have been successfully removed. */ + break; + } + /* if(itd->hw_next == EHCI_LIST_END) */ + /* + * This indicates that we stopped due to an error on a PTD that is + * not the last in the list. We need to free up this PTD as well as + * the PTDs after it. + */ + else { + /* + * Put the current ITD error onto this variable. + * We will be unlinking this from the list and free up its + * resources later. + */ + current_itd = itd; + + td_ptd_map = + &ptd_map_buff-> + map_list[itd-> + itd_index]; + + /* + * Get the next ITD, and place it to the itd variable. + * In a way we are moving forward in the ITD list. + */ + itd = (struct ehci_itd + *) (current_itd-> + hw_next); +#ifndef COMMON_MEMORY + /* Free up the current ITD's resources */ + phci_hcd_mem_free + (¤t_itd-> + mem_addr); +#endif + + /* Remove this ITD entry in the ITD list */ + list_del(¤t_itd-> + itd_list); + + /* Free up the memory allocated for the ITD structure */ + qha_free(qha_cache, + current_itd); + + /* Inidicate that the PTD we have used is now free */ + td_ptd_map->state = + TD_PTD_NEW; + td_ptd_map->sitd = NULL; + td_ptd_map->itd = NULL; + + /* Decrease the number of active PTDs scheduled */ + hcd->periodic_sched--; + + /* Sine it is done, skip this PTD during the next PTD processing. */ + skipmap |= + td_ptd_map-> + ptd_bitmap; + isp1763_reg_write16 + (hcd->dev, + hcd->regs. + isotdskipmap, + skipmap); + /* + * Start all over again until it gets to the tail of the + * list of PTDs/ITDs + */ + continue; + }/* else of if(itd->hw_next == EHCI_LIST_END) */ + /* It should never get here, but I put this as a precaution */ + break; + } /*end of while(1) */ + /* Check if there were ITDs that were not processed due to the error */ + if (urb->status == -EINPROGRESS) { + if ((urb->actual_length != + urb->transfer_buffer_length) + && (urb-> + transfer_flags & + URB_SHORT_NOT_OK)) { + + iso_dbg(ISO_DBG_ERR, + "[pehci_hcd_iso_worker Error]: Short Packet\n"); + + urb->status = + -EREMOTEIO; + } else { + urb->status = 0; + } + } + + urb->hcpriv = 0; + iso_dbg(ISO_DBG_DATA, + "[%s] : remain skipmap =0x%x\n", + __FUNCTION__, skipmap); + + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +// if (urb->reject.counter) { + if (unlikely(atomic_read(&urb->reject))) {// kernel reference code hcd.c + iso_dbg("ISO_DBG_INFO, [%s] urb reject\n", __FUNCTION__); + iReject = 1; + } +#else + if (unlikely(urb->reject)) { + iso_dbg("ISO_DBG_INFO, [%s] urb reject\n", __FUNCTION__); + iReject = 1; + } +#endif + +/* +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,28) + + if (urb->reject.counter) { + iso_dbg("ISO_DBG_INFO, [%s] urb reject\n", __FUNCTION__); + iReject = 1; + } +#else + if (unlikely(urb->reject)) { + + + iso_dbg("ISO_DBG_INFO, [%s] urb reject\n", __FUNCTION__); + iReject = 1; + } +#endif +*/ + +#ifdef COMMON_MEMORY + phci_hcd_mem_free(&qhead->memory_addr); +#endif + /* We need to unlock this here, since this was locked when we are called */ + /* from the interrupt handler */ + spin_unlock(&hcd->lock); + /* Perform URB cleanup */ + iso_dbg(ISO_DBG_INFO, + "[pehci_hcd_iso_worker] Complete a URB\n"); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + if(!usb_hcd_check_unlink_urb(&hcd->usb_hcd, urb,0)) + usb_hcd_unlink_urb_from_ep(&hcd->usb_hcd, urb); +#endif + hcd->periodic_more_urb = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qhead=urb->hcpriv; + if (!list_empty(&qhead->ep->urb_list)){ + +#else + if (!list_empty(&urb->ep->urb_list)){ +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + if (urb->hcpriv== periodic_ep[0]){ +#else + if (urb->ep == periodic_ep[0]){ +#endif + hcd->periodic_more_urb = + 1; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + } else if (urb->hcpriv== + periodic_ep[1]){ +#else + } else if (urb->ep == + periodic_ep[1]){ +#endif + hcd->periodic_more_urb = + 2; + } else { + hcd->periodic_more_urb = + 0; + } + + + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + usb_hcd_giveback_urb(&hcd->usb_hcd, urb); +#else + usb_hcd_giveback_urb(&hcd->usb_hcd, urb, + urb->status); +#endif + + spin_lock(&hcd->lock); + continue; + } + /* if( last_td == TRUE ) */ + /* + * If the last_td is not set then we do not need to check for errors and directly + * proceed with the cleaning sequence. + */ + iso_dbg(ISO_DBG_INFO, + "[pehci_hcd_iso_worker]: last_td is not set\n"); + /*update skipmap */ + skipmap |= td_ptd_map->ptd_bitmap; + isp1763_reg_write16(hcd->dev, + hcd->regs.isotdskipmap, + skipmap); + iso_dbg(ISO_DBG_DATA, + "%s : remain skipmap =0x%x\n", + __FUNCTION__, skipmap); + + /* Decrement the count of active PTDs */ + hcd->periodic_sched--; +#ifndef COMMON_MEMORY + /* Free up the memory we allocated in the PAYLOAD area */ + phci_hcd_mem_free(&itd->mem_addr); +#endif + /* Remove this ITD from the list of active ITDs */ + list_del(&itd->itd_list); + + /* Free up the memory we allocated for the ITD structure */ + qha_free(qha_cache, itd); + /* + * Clear the bit associated with this PTD from the grouptdmap and + * make this PTD available for other transfers + */ + td_ptd_map->state = TD_PTD_NEW; + td_ptd_map->sitd = NULL; + td_ptd_map->itd = NULL; + } /*end of HIGH SPEED */ + } /* end of list_for_each_safe(position, lst_temp, itd_remove) */ + iso_dbg(ISO_DBG_INFO, + "[pehci_hcd_iso_worker]: ISO-Frame removal done\n"); + + + } /* while donetoclear */ + + + if (iReject) { + spin_unlock(&hcd->lock); + if (hcd->periodic_more_urb) { + + if(periodic_ep[hcd->periodic_more_urb]) + while (&periodic_ep[hcd->periodic_more_urb - 1]-> + urb_list) { + + urb = container_of(periodic_ep + [hcd->periodic_more_urb - + 1]->urb_list.next, + struct urb, urb_list); + + if (urb) { + urb->status = -ENOENT; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + if(!usb_hcd_check_unlink_urb(&hcd->usb_hcd, urb,0)) + usb_hcd_unlink_urb_from_ep(&hcd-> + usb_hcd,urb); +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + usb_hcd_giveback_urb(&hcd->usb_hcd, urb); +#else + usb_hcd_giveback_urb(&hcd->usb_hcd, urb, + urb->status); +#endif + } + } + } + + spin_lock(&hcd->lock); + } + + /* When there is no more PTDs queued for scheduling or removal + * clear the buffer status to indicate there are no more PTDs for + * processing and set the skip map to 1 to indicate that the first + * PTD is also the last PTD. + */ + + if (hcd->periodic_more_urb) { + int status = 0; + iso_dbg(ISO_DBG_INFO, + "[phcd_iso_handler]: No more PTDs queued\n"); + hcd->periodic_sched = 0; + phcd_store_urb_pending(hcd, hcd->periodic_more_urb, NULL, + &status); + hcd->periodic_more_urb = 0; + } +exit: + iso_dbg(ISO_DBG_ENTRY, "-- %s: Exit\n", __FUNCTION__); +} /* end of pehci_hcd_iso_worker */ + +#endif /* CONFIG_ISO_SUPPORT */ + +/*interrupt transfer handler*/ +/******************************************************** + 1. read done map + 2. read the ptd to see any errors + 3. copy the payload to and from + 4. update ehci td + 5. make new ptd if transfer there and earlier done + 6. schedule + *********************************************************/ +static void +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +pehci_hcd_intl_worker(phci_hcd * hcd, struct pt_regs *regs) +#else +pehci_hcd_intl_worker(phci_hcd * hcd) +#endif +{ + int i = 0; + u16 donemap = 0, donetoclear; + u16 mask = 0x1, index = 0; + u16 pendingmap = 0; + u16 location = 0; + u32 length = 0; + u16 skipmap = 0; + u16 ormask = 0; + u32 usofstatus = 0; + struct urb *urb; + struct ehci_qtd *qtd = 0; + struct ehci_qh *qh = 0; + + struct _isp1763_qhint *qhint = &hcd->qhint; + + td_ptd_map_t *td_ptd_map; + td_ptd_map_buff_t *ptd_map_buff; + struct isp1763_mem_addr *mem_addr = 0; + u16 dontschedule = 0; + + ptd_map_buff = &(td_ptd_map_buff[TD_PTD_BUFF_TYPE_INTL]); + pendingmap = ptd_map_buff->pending_ptd_bitmap; + + /*read the done map for interrupt transfers */ + donetoclear = donemap = + isp1763_reg_read16(hcd->dev, hcd->regs.inttddonemap, donemap); + if (donemap) { + /*skip done tds */ + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.inttdskipmap, + skipmap); + skipmap |= donemap; + isp1763_reg_write16(hcd->dev, hcd->regs.inttdskipmap, skipmap); + donemap |= pendingmap; + } + /*if sof interrupt is enabled */ +#ifdef MSEC_INT_BASED + else { + /*if there is something pending , put this transfer in */ + if (ptd_map_buff->pending_ptd_bitmap) { + pehci_hcd_schedule_pending_ptds(hcd, pendingmap, (u8) + TD_PTD_BUFF_TYPE_INTL, + 1); + } + //return 0; + goto exit; + } +#else + else { + goto exit; + //return 0; + } + +#endif + + + ormask = isp1763_reg_read16(hcd->dev, hcd->regs.int_irq_mask_or, + ormask); + /*process all the endpoints first those are done */ + donetoclear = donemap; + while (donetoclear) { + /*index is the number of endpoints open currently */ + index = donetoclear & mask; + donetoclear &= ~mask; + mask <<= 1; + /*what if we are in the middle of schedule + where nothing is done */ + if (!index) { + location++; + continue; + } + + /*read our td_ptd_map */ + td_ptd_map = &ptd_map_buff->map_list[location]; + + /*if this one is already in the removal */ + if (td_ptd_map->state == TD_PTD_REMOVE || + td_ptd_map->state == TD_PTD_NEW) { + pehci_check("interrupt td is being removed\n"); + /*this will be handled by urb_remove */ + /*if this is last urb no need to complete it again */ + donemap &= ~td_ptd_map->ptd_bitmap; + /*if there is something pending */ + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + continue; + } + + + /*if we found something already in */ + if (!(skipmap & td_ptd_map->ptd_bitmap)) { + pehci_check("intr td_ptd_map %x,skipnap %x\n", + td_ptd_map->ptd_bitmap, skipmap); + donemap &= ~td_ptd_map->ptd_bitmap; + /*in case pending */ + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap;; + location++; + continue; + } + + + if (td_ptd_map->state == TD_PTD_NEW) { + pehci_check + ("interrupt not come here, map %x,location %d\n", + td_ptd_map->ptd_bitmap, location); + donemap &= ~td_ptd_map->ptd_bitmap; + /*in case pending */ + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + donemap &= ~td_ptd_map->ptd_bitmap; + location++; + continue; + } + + /*move to the next schedule */ + location++; + /*endpoint, td, urb and memory + * for current transfer*/ + qh = td_ptd_map->qh; + qtd = td_ptd_map->qtd; + if (qtd->state & QTD_STATE_NEW) { + /*we need to schedule it */ + goto schedule; + } + urb = qtd->urb; + mem_addr = &qtd->mem_addr; + + /*clear the irq mask for this transfer */ + ormask &= ~td_ptd_map->ptd_bitmap; + isp1763_reg_write16(hcd->dev, hcd->regs.int_irq_mask_or, + ormask); + + ptd_map_buff->active_ptds--; + memset(qhint, 0, sizeof(struct _isp1763_qhint)); + + /*read this ptd from the ram address,address is in the + td_ptd_map->ptd_header_addr */ + isp1763_mem_read(hcd->dev, td_ptd_map->ptd_header_addr, 0, + (u32 *) (qhint), PHCI_QHA_LENGTH, 0); + +#ifdef PTD_DUMP_COMPLETE + printk("INTL PTD header after COMPLETION\n"); + printk("CDW0: 0x%08X\n", qhint->td_info1); + printk("CDW1: 0x%08X\n", qhint->td_info2); + printk("CDW2: 0x%08X\n", qhint->td_info3); + printk("CDW3: 0x%08X\n", qhint->td_info4); +#endif + + /*statuc of 8 uframes */ + for (i = 0; i < 8; i++) { + /*take care of errors */ + usofstatus = qhint->td_info5 >> (8 + i * 3); + switch (usofstatus & 0x7) { + case INT_UNDERRUN: + pehci_print("under run , %x\n", usofstatus); + break; + case INT_EXACT: + pehci_print("transaction error, %x\n", + usofstatus); + break; + case INT_BABBLE: + pehci_print("babble error, %x\n", usofstatus); + break; + } + } + + if (urb->dev->speed != USB_SPEED_HIGH) { + /*length is 1K for full/low speed device */ + length = PTD_XFERRED_NONHSLENGTH(qhint->td_info4); + } else { + /*length is 32K for high speed device */ + length = PTD_XFERRED_LENGTH(qhint->td_info4); + } + + pehci_hcd_update_error_status(qhint->td_info4, urb); + /*halted, need to finish all the transfer on this endpoint */ + if (qhint->td_info4 & PTD_STATUS_HALTED) { + qtd->state |= QTD_STATE_LAST; + /*in case of halt, next transfer will start with toggle zero, + *USB speck, 5.8.5*/ + qh->datatoggle = td_ptd_map->datatoggle = 0; + donemap &= ~td_ptd_map->ptd_bitmap; + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + dontschedule = 1; + goto copylength; + } + + + copylength: + /*preserve the current data toggle */ + qh->datatoggle = td_ptd_map->datatoggle = + PTD_NEXTTOGGLE(qhint->td_info4); + /*copy data from the host */ + switch (PTD_PID(qhint->td_info2)) { + case IN_PID: + if (length && (length <= MAX_PTD_BUFFER_SIZE)) + /*do read only when there is somedata */ + isp1763_mem_read(hcd->dev, + (u32) mem_addr->phy_addr, 0, + urb->transfer_buffer + + urb->actual_length, length, 0); + + case OUT_PID: + urb->actual_length += length; + qh->hw_current = qtd->hw_next; + phci_hcd_mem_free(&qtd->mem_addr); + qtd->state &= ~QTD_STATE_NEW; + qtd->state |= QTD_STATE_DONE; + break; + } + + if (qtd->state & QTD_STATE_LAST) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map, regs); +#else + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map); +#endif + if (dontschedule) { /*cleanup will start from drivers */ + dontschedule = 0; + continue; + } + + /*take the next if in the queue */ + if (!list_empty(&qh->qtd_list)) { + struct list_head *head; + /*last td of previous urb */ + head = &qh->qtd_list; + qtd = list_entry(head->next, struct ehci_qtd, + qtd_list); + td_ptd_map->qtd = qtd; + qh->hw_current = cpu_to_le32(qtd); + qh->qh_state = QH_STATE_LINKED; + + } else { + td_ptd_map->qtd = + (struct ehci_qtd *) le32_to_cpu(0); + qh->hw_current = cpu_to_le32(0); + qh->qh_state = QH_STATE_IDLE; + donemap &= ~td_ptd_map->ptd_bitmap; + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + td_ptd_map->state=TD_PTD_NEW; + continue; + } + + } + + schedule: + { + /*current td comes from qh->hw_current */ + ptd_map_buff->pending_ptd_bitmap &= + ~td_ptd_map->ptd_bitmap; + ormask |= td_ptd_map->ptd_bitmap; + ptd_map_buff->active_ptds++; + pehci_check + ("inter schedule next qtd %p, active tds %d\n", + qtd, ptd_map_buff->active_ptds); + pehci_hcd_qtd_schedule(hcd, qtd, qh, td_ptd_map); + } + + } /*end of while */ + + + /*clear all the tds inside this routine */ + skipmap &= ~donemap; + isp1763_reg_write16(hcd->dev, hcd->regs.inttdskipmap, skipmap); + ormask |= donemap; + isp1763_reg_write16(hcd->dev, hcd->regs.int_irq_mask_or, ormask); +exit: + pehci_entry("-- %s: Exit\n", __FUNCTION__); + +// return (int)0; +} + +/*atl(bulk/control) transfer handler*/ +/*1. read done map + 2. read the ptd to see any errors + 3. copy the payload to and from + 4. update ehci td + 5. make new ptd if transfer there and earlier done + 6. schedule + */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +static void +pehci_hcd_atl_worker(phci_hcd * hcd, struct pt_regs *regs) +#else +static void +pehci_hcd_atl_worker(phci_hcd * hcd) +#endif +{ + u16 donemap = 0, donetoclear = 0; + u16 pendingmap = 0; + u32 rl = 0; + u16 mask = 0x1, index = 0; + u16 location = 0; + u32 nakcount = 0; + u32 active = 0; + u32 length = 0; + u16 skipmap = 0; + u16 tempskipmap = 0; + u16 ormask = 0; + struct urb *urb; + struct ehci_qtd *qtd = 0; + struct ehci_qh *qh; + struct _isp1763_qha atlqha; + struct _isp1763_qha *qha; + td_ptd_map_t *td_ptd_map; + td_ptd_map_buff_t *ptd_map_buff; + urb_priv_t *urbpriv = 0; + struct isp1763_mem_addr *mem_addr = 0; + u16 dontschedule = 0; + ptd_map_buff = &(td_ptd_map_buff[TD_PTD_BUFF_TYPE_ATL]); + pendingmap = ptd_map_buff->pending_ptd_bitmap; + +#ifdef MSEC_INT_BASED + /*running on skipmap rather donemap, + some cases donemap may not be set + for complete transfer + */ + skipmap = isp1763_reg_read16(hcd->dev, hcd->regs.atltdskipmap, skipmap); + tempskipmap = ~skipmap; + tempskipmap &= 0xffff; + + if (tempskipmap) { + donemap = + isp1763_reg_read16(hcd->dev, hcd->regs.atltddonemap, + donemap); + skipmap |= donemap; + isp1763_reg_write16(hcd->dev, hcd->regs.atltdskipmap, skipmap); + qha = &atlqha; + donemap |= pendingmap; + tempskipmap &= ~donemap; + } else { + + /*if sof interrupt enabled */ + + /*if there is something pending , put this transfer in */ + if (pendingmap) { + pehci_hcd_schedule_pending_ptds(hcd, pendingmap, (u8) + TD_PTD_BUFF_TYPE_ATL, + 1); + } + goto exit; + } +#else + + donemap = isp1763_reg_read16(hcd->dev, hcd->regs.atltddonemap, donemap); + if (donemap) { + + + pehci_info("DoneMap Value in ATL Worker %x\n", donemap); + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.atltdskipmap, + skipmap); + skipmap |= donemap; + isp1763_reg_write16(hcd->dev, hcd->regs.atltdskipmap, skipmap); + qha = &atlqha; + } else { + pehci_info("Done Map Value is 0x%X \n", donemap); + pehci_entry("-- %s: Exit abnormally with DoneMap all zero \n", + __FUNCTION__); + goto exit; + + } +#endif + + /*read the interrupt mask registers */ + ormask = isp1763_reg_read16(hcd->dev, hcd->regs.atl_irq_mask_or, + ormask); + + + /*this map is used only to update and + * scheduling for the tds who are not + * complete. the tds those are complete + * new schedule will happen from + * td_ptd_submit_urb routine + * */ + donetoclear = donemap; + /*we will be processing skipped tds also */ + donetoclear |= tempskipmap; + /*process all the endpoints first those are done */ + while (donetoclear) { + /*index is the number of endpoint open currently */ + index = donetoclear & mask; + donetoclear &= ~mask; + mask <<= 1; + /*what if we are in the middle of schedule + where nothing is done + */ + if (!index) { + location++; + continue; + } + + /*read our td_ptd_map */ + td_ptd_map = &ptd_map_buff->map_list[location]; + + /*urb is in remove */ + if (td_ptd_map->state == TD_PTD_NEW || + td_ptd_map->state == TD_PTD_REMOVE) { + pehci_check + ("atl td is being removed,map %x, skipmap %x\n", + td_ptd_map->ptd_bitmap, skipmap); + pehci_check("temp skipmap %x, pendign map %x,done %x\n", + tempskipmap, pendingmap, donemap); + + /*unlink urb will take care of this */ + donemap &= ((~td_ptd_map->ptd_bitmap) & 0xffff); + /*in case pending */ + ptd_map_buff->pending_ptd_bitmap &= + ((~td_ptd_map->ptd_bitmap) & 0xffff); + location++; + continue; + } + + + /*move to the next endpoint */ + location++; + /*endpoint, td, urb and memory + * for current endpoint*/ + qh = td_ptd_map->qh; + qtd = td_ptd_map->qtd; + if (!qh || !qtd) { + donemap &= ((~td_ptd_map->ptd_bitmap) & 0xffff); + /*in case pending */ + ptd_map_buff->pending_ptd_bitmap &= + ((~td_ptd_map->ptd_bitmap) & 0xffff); + continue; + } +#ifdef MSEC_INT_BASED + /*new td must be scheduled */ + if ((qtd->state & QTD_STATE_NEW) /*&& + (pendingmap & td_ptd_map->ptd_bitmap) */ ) { + /*this td will come here first time from + *pending tds, so its qh->hw_current needs to + * adjusted + */ + qh->hw_current = QTD_NEXT(qtd->qtd_dma); + goto schedule; + } +#endif + urb = qtd->urb; + if (urb == NULL) { + donemap &= ((~td_ptd_map->ptd_bitmap) & 0xffff); + /*in case pending */ + ptd_map_buff->pending_ptd_bitmap &= + ((~td_ptd_map->ptd_bitmap) & 0xffff); + continue; + } + urbpriv = (urb_priv_t *) urb->hcpriv; + mem_addr = &qtd->mem_addr; + +#ifdef MSEC_INT_BASED + /*check here for the td if its done */ + if (donemap & td_ptd_map->ptd_bitmap) { + /*nothing to do */ + ; + } else { + /*if td is not done, lets check how long + its been scheduled + */ + if (tempskipmap & td_ptd_map->ptd_bitmap) { + /*i will give 20 msec to complete */ + if (urbpriv->timeout < 20) { + urbpriv->timeout++; + continue; + } + urbpriv->timeout++; + /*otherwise check its status */ + } + + } +#endif + memset(qha, 0, sizeof(struct _isp1763_qha)); + + /*read this ptd from the ram address,address is in the + td_ptd_map->ptd_header_addr */ + isp1763_mem_read(hcd->dev, td_ptd_map->ptd_header_addr, 0, + (u32 *) (qha), PHCI_QHA_LENGTH, 0); + +#ifdef PTD_DUMP_COMPLETE + printk("ATL PTD header after COMPLETION\n"); + printk("CDW0: 0x%08X\n", qha->td_info1); + printk("CDW1: 0x%08X\n", qha->td_info2); + printk("CDW2: 0x%08X\n", qha->td_info3); + printk("CDW3: 0x%08X\n", qha->td_info4); +#endif + +#ifdef MSEC_INT_BASED + /*since we are running on skipmap + tds will be checked for completion state + */ + if ((qha->td_info1 & QHA_VALID)) { + + pehci_check + ("pendign map %x, donemap %x, tempskipmap %x\n", + pendingmap, donemap, tempskipmap); + /*this could be one of the unprotected urbs, clear it */ + ptd_map_buff->pending_ptd_bitmap &= + ((~td_ptd_map->ptd_bitmap) & 0xffff); + /*here also we need to increment the tds timeout count */ + urbpriv->timeout++; + continue; + } else { + /*this td is going to be done, + this td could be the one un-skipped but no donemap or + maybe it could be one of those where we get unprotected urbs, + so checking against tempskipmap may not give us correct td + */ + + skipmap |= td_ptd_map->ptd_bitmap; + isp1763_reg_write16(hcd->dev, hcd->regs.atltdskipmap, + skipmap); + + /*of course this is going to be as good + as td that is done and donemap is set + also skipmap is set + */ + donemap |= td_ptd_map->ptd_bitmap; + } +#endif + /*clear the corrosponding mask register */ + ormask &= ((~td_ptd_map->ptd_bitmap) & 0xffff); + isp1763_reg_write16(hcd->dev, hcd->regs.atl_irq_mask_or, + ormask); + + ptd_map_buff->active_ptds--; + + urbpriv->timeout = 0; + + /*take care of errors */ + pehci_hcd_update_error_status(qha->td_info4, urb); + /*halted, need to finish all the transfer on this endpoint */ + if (qha->td_info4 & PTD_STATUS_HALTED) { + + printk(KERN_NOTICE "Endpoint is halted\n"); + qtd->state |= QTD_STATE_LAST; + + donemap &= ((~td_ptd_map->ptd_bitmap) & 0xffff); + /*in case pending */ + ptd_map_buff->pending_ptd_bitmap &= + ((~td_ptd_map->ptd_bitmap) & 0xffff); + /*in case of halt, next transfer will start with toggle + zero,USB speck, 5.8.5 */ + qh->datatoggle = td_ptd_map->datatoggle = 0; + /*cleanup the ping */ + qh->ping = 0; + /*force cleanup after this */ + dontschedule = 1; + goto copylength; + } + + + + /*read the reload count */ + rl = (qha->td_info3 >> 23); + rl &= 0xf; + + + + /*if there is a transaction error and the status is not halted, + * process whatever the length we got.if the length is what we + * expected complete the transfer*/ + if ((qha->td_info4 & PTD_XACT_ERROR) && + !(qha->td_info4 & PTD_STATUS_HALTED) && + (qha->td_info4 & QHA_ACTIVE)) { + + if (PTD_XFERRED_LENGTH(qha->td_info4) == qtd->length) { + ; /*nothing to do its fake */ + } else { + + pehci_print + ("xact error, info1 0x%08x,info4 0x%08x\n", + qha->td_info1, qha->td_info4); + + /*if this is the case then we need to + resubmit the td again */ + qha->td_info1 |= QHA_VALID; + skipmap &= ~td_ptd_map->ptd_bitmap; + ormask |= td_ptd_map->ptd_bitmap; + donemap &= ((~td_ptd_map->ptd_bitmap) & 0xffff); + + /*set the retry count to 3 again */ + qha->td_info4 |= (rl << 19); + /*set the active bit, if cleared, will be cleared if we have some length */ + qha->td_info4 |= QHA_ACTIVE; + + /*clear the xact error */ + qha->td_info4 &= ~PTD_XACT_ERROR; + isp1763_reg_write16(hcd->dev, + hcd->regs.atl_irq_mask_or, + ormask); + + /*copy back into the header, payload is already + * present no need to write again + */ + isp1763_mem_write(hcd->dev, + td_ptd_map->ptd_header_addr, + 0, (u32 *) (qha), + PHCI_QHA_LENGTH, 0); + /*unskip this td */ + isp1763_reg_write16(hcd->dev, + hcd->regs.atltdskipmap, + skipmap); + continue; + } + goto copylength; + } + + /*check for the nak count and active condition + * to reload the ptd if needed*/ + nakcount = qha->td_info4 >> 19; + nakcount &= 0xf; + active = qha->td_info4 & QHA_ACTIVE; + /*if nak count is zero and active bit is set , it + *means that device is naking and need to reload + *the same td*/ + if (!nakcount && active) { + pehci_info("%s: ptd is going for reload,length %d\n", + __FUNCTION__, length); + /*make this td valid */ + qha->td_info1 |= QHA_VALID; + donemap &= ((~td_ptd_map->ptd_bitmap & 0xffff)); + /*just like fresh td */ + + /*set the retry count to 3 again */ + qha->td_info4 |= (rl << 19); + qha->td_info4 &= ~0x3; + qha->td_info4 |= (0x2 << 23); + ptd_map_buff->active_ptds++; + skipmap &= ((~td_ptd_map->ptd_bitmap) & 0xffff); + ormask |= td_ptd_map->ptd_bitmap; + isp1763_reg_write16(hcd->dev, hcd->regs.atl_irq_mask_or, + ormask); + /*copy back into the header, payload is already + * present no need to write again */ + isp1763_mem_write(hcd->dev, td_ptd_map->ptd_header_addr, + 0, (u32 *) (qha), PHCI_QHA_LENGTH, 0); + /*unskip this td */ + isp1763_reg_write16(hcd->dev, hcd->regs.atltdskipmap, + skipmap); + continue; + } + + copylength: + /*read the length transferred */ + length = PTD_XFERRED_LENGTH(qha->td_info4); + + + /*short complete in case of BULK only */ + if ((length < qtd->length) && usb_pipebulk(urb->pipe)) { + + /*if current ptd is not able to fetech enough data as + * been asked then device has no data, so complete this transfer + * */ + /*can we complete our transfer here */ + if ((urb->transfer_flags & URB_SHORT_NOT_OK)) { + pehci_check + ("short read, length %d(expected %d)\n", + length, qtd->length); + urb->status = -EREMOTEIO; + /*if this is the only td,donemap will be cleared + at completion, otherwise take the next one + */ + donemap &= ((~td_ptd_map->ptd_bitmap) & 0xffff); + ptd_map_buff->pending_ptd_bitmap &= + ((~td_ptd_map->ptd_bitmap) & 0xffff); + /*force the cleanup from here */ + dontschedule = 1; + } + + /*this will be the last td,in case of short read/write */ + /*donemap, pending maps will be handled at the while scheduling or completion */ + qtd->state |= QTD_STATE_LAST; + + } + /*preserve the current data toggle */ + qh->datatoggle = td_ptd_map->datatoggle = + PTD_NEXTTOGGLE(qha->td_info4); + qh->ping = PTD_PING_STATE(qha->td_info4); + /*copy data from */ + switch (PTD_PID(qha->td_info2)) { + case IN_PID: + qh->ping = 0; + /*do read only when there is some data */ + if (length && (length <= HC_ATL_PL_SIZE)) { + isp1763_mem_read(hcd->dev, + (u32) mem_addr->phy_addr, 0, + (u32*) (le32_to_cpu(qtd->hw_buf[0])), length, 0); +#if 0 + // printk("IN PayLoad length:%d\n", length); + if(length<=4) { + int i=0; + int *data_addr= qtd->hw_buf[0]; + printk("\n"); + for(i=0;iactual_length += length; + qh->hw_current = qtd->hw_next; + phci_hcd_mem_free(&qtd->mem_addr); + qtd->state |= QTD_STATE_DONE; + + break; + case SETUP_PID: + qh->hw_current = qtd->hw_next; + phci_hcd_mem_free(&qtd->mem_addr); + qtd->state |= QTD_STATE_DONE; + break; + } + + if (qtd->state & QTD_STATE_LAST) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map, regs); +#else + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map); +#endif + if (dontschedule) { /*cleanup will start from drivers */ + dontschedule = 0; + /*so that we can take next one */ + qh->qh_state = QH_STATE_TAKE_NEXT; + continue; + } + /*take the next if in the queue */ + if (!list_empty(&qh->qtd_list)) { + struct list_head *head; + /*last td of previous urb */ + head = &qh->qtd_list; + qtd = list_entry(head->next, struct ehci_qtd, + qtd_list); + td_ptd_map->qtd = qtd; + qh->hw_current = cpu_to_le32(qtd); + qh->qh_state = QH_STATE_LINKED; + + } else { + td_ptd_map->qtd = + (struct ehci_qtd *) le32_to_cpu(0); + qh->hw_current = cpu_to_le32(0); + qh->qh_state = QH_STATE_TAKE_NEXT; + donemap &= ((~td_ptd_map->ptd_bitmap & 0xffff)); + ptd_map_buff->pending_ptd_bitmap &= + ((~td_ptd_map->ptd_bitmap) & 0xffff); + continue; + } + } + +#ifdef MSEC_INT_BASED + schedule: +#endif + { + /*current td comes from qh->hw_current */ + ptd_map_buff->pending_ptd_bitmap &= + ((~td_ptd_map->ptd_bitmap) & 0xffff); + td_ptd_map->qtd = + (struct ehci_qtd + *) (le32_to_cpu(qh->hw_current)); + qtd = td_ptd_map->qtd; + ormask |= td_ptd_map->ptd_bitmap; + ptd_map_buff->active_ptds++; + pehci_hcd_qtd_schedule(hcd, qtd, qh, td_ptd_map); + } + + } /*end of while */ + +/*clear all the tds inside this routine*/ + skipmap &= ((~donemap) & 0xffff); + isp1763_reg_write16(hcd->dev, hcd->regs.atltdskipmap, skipmap); + ormask |= donemap; + isp1763_reg_write16(hcd->dev, hcd->regs.atl_irq_mask_or, ormask); +exit: + pehci_entry("-- %s: Exit\n", __FUNCTION__); +} + +/*--------------------------------------------------------* + root hub functions + *--------------------------------------------------------*/ + +/*return root hub descriptor, can not fail*/ +static void +pehci_hub_descriptor(phci_hcd * hcd, struct usb_hub_descriptor *desc) +{ + u32 ports = 0; + u16 temp = 0; + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + + ports = 0x11; + ports = ports & 0xf; + + pehci_info("%s: number of ports %d\n", __FUNCTION__, ports); + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; + + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + + memset(&desc->DeviceRemovable[0], 0, temp); + memset(&desc->PortPwrCtrlMask[temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + temp |= 0x0001; /* per-port power control */ + temp |= 0x0080; /* per-port indicators (LEDs) */ + desc->wHubCharacteristics = cpu_to_le16(temp); + pehci_entry("-- %s: Exit\n", __FUNCTION__); +} + +/*after reset on root hub, + * device high speed or non-high speed + * */ +static int +phci_check_reset_complete(phci_hcd * hcd, int index, int port_status) +{ + pehci_print("check reset complete\n"); + if (!(port_status & PORT_CONNECT)) { + hcd->reset_done[index] = 0; + return port_status; + } + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + printk("port %d full speed --> companion\n", index + 1); + port_status |= PORT_OWNER; + isp1763_reg_write32(hcd->dev, hcd->regs.ports[index], + port_status); + + } else { + pehci_print("port %d high speed\n", index + 1); + } + + return port_status; + +} + +/*----------------------------------------------* + host controller initialization, removal functions + *----------------------------------------------*/ + + +/*initialize all three buffer(iso/atl/int) type headers*/ +static void +pehci_hcd_init_map_buffers(phci_hcd * phci) +{ + td_ptd_map_buff_t *ptd_map_buff; + u8 buff_type, ptd_index; + u32 bitmap; + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + pehci_print("phci_init_map_buffers(phci = 0x%p)\n", phci); + /* initialize for each buffer type */ + for (buff_type = 0; buff_type < TD_PTD_TOTAL_BUFF_TYPES; buff_type++) { + ptd_map_buff = &(td_ptd_map_buff[buff_type]); + ptd_map_buff->buffer_type = buff_type; + ptd_map_buff->active_ptds = 0; + ptd_map_buff->total_ptds = 0; + /*each bufer type can have atleast 32 ptds */ + ptd_map_buff->max_ptds = 16; + ptd_map_buff->active_ptd_bitmap = 0; + /*everything skipped */ + /*nothing is pending */ + ptd_map_buff->pending_ptd_bitmap = 0x00000000; + + /* For each ptd index of this buffer, set the fiedls */ + bitmap = 0x00000001; + for (ptd_index = 0; ptd_index < TD_PTD_MAX_BUFF_TDS; + ptd_index++) { + /*datatoggle zero */ + ptd_map_buff->map_list[ptd_index].datatoggle = 0; + /*td state is not used */ + ptd_map_buff->map_list[ptd_index].state = TD_PTD_NEW; + /*no endpoint, no qtd */ + ptd_map_buff->map_list[ptd_index].qh = NULL; + ptd_map_buff->map_list[ptd_index].qtd = NULL; + ptd_map_buff->map_list[ptd_index].ptd_header_addr = + 0xFFFF; + } /* for( ptd_index */ + } /* for(buff_type */ + pehci_entry("-- %s: Exit\n", __FUNCTION__); +} /* phci_init_map_buffers */ + + +/*put the host controller into operational mode + * called phci_hcd_start routine, + * return 0, success else + * timeout, fails*/ + +static int +pehci_hcd_start_controller(phci_hcd * hcd) +{ + u32 temp = 0; + u32 command = 0; + int retval = 0; + pehci_entry("++ %s: Entered\n", __FUNCTION__); + printk(KERN_NOTICE "++ %s: Entered\n", __FUNCTION__); + + + command = isp1763_reg_read16(hcd->dev, hcd->regs.command, command); + printk(KERN_NOTICE "HC Command Reg val ...1 %x\n", command); + + /*initialize the host controller */ + command |= CMD_RUN; + + isp1763_reg_write16(hcd->dev, hcd->regs.command, command); + + + command &= 0; + + command = isp1763_reg_read16(hcd->dev, hcd->regs.command, command); + printk(KERN_NOTICE "HC Command Reg val ...2 %x\n", command); + + /*should be in operation in 1000 usecs */ + if ((retval = + pehci_hcd_handshake(hcd, hcd->regs.command, CMD_RUN, CMD_RUN, + 100000))) { + err("Host is not up(CMD_RUN) in 1000 usecs\n"); + return retval; + } + + printk(KERN_NOTICE "ISP1763 HC is running \n"); + + + /*put the host controller to ehci mode */ + command &= 0; + command |= 1; + + isp1763_reg_write16(hcd->dev, hcd->regs.configflag, command); + mdelay(5); + + temp = isp1763_reg_read16(hcd->dev, hcd->regs.configflag, temp); + pehci_print("%s: Config Flag reg value: 0x%08x\n", __FUNCTION__, temp); + + /*check if ehci mode switching is correct or not */ + if ((retval = + pehci_hcd_handshake(hcd, hcd->regs.configflag, 1, 1, 100))) { + err("Host is not into ehci mode in 100 usecs\n"); + return retval; + } + + mdelay(5); + + pehci_entry("-- %s: Exit\n", __FUNCTION__); + printk(KERN_NOTICE "-- %s: Exit\n", __FUNCTION__); + return retval; +} + + +/*enable the interrupts + *called phci_1763_start routine + * return void*/ +static void +pehci_hcd_enable_interrupts(phci_hcd * hcd) +{ + u32 temp = 0; + pehci_entry("++ %s: Entered\n", __FUNCTION__); + printk(KERN_NOTICE "++ %s: Entered\n", __FUNCTION__); + /*disable the interrupt source */ + temp &= 0; + /*clear all the interrupts that may be there */ + temp |= INTR_ENABLE_MASK; + isp1763_reg_write16(hcd->dev, hcd->regs.interrupt, temp); + + /*enable interrupts */ + temp = 0; + +#ifdef OTG_PACKAGE + temp |= INTR_ENABLE_MASK | HC_OTG_INT; +#else + temp |= INTR_ENABLE_MASK; +#endif + pehci_print("%s: enabled mask 0x%08x\n", __FUNCTION__, temp); + isp1763_reg_write16(hcd->dev, hcd->regs.interruptenable, temp); + + temp = isp1763_reg_read16(hcd->dev, hcd->regs.interruptenable, temp); + pehci_print("%s: Intr enable reg value: 0x%08x\n", __FUNCTION__, temp); + +#ifdef HCD_PACKAGE + temp = 0; + temp = isp1763_reg_read32(hcd->dev, HC_INT_THRESHOLD_REG, temp); +// temp |= 0x0800000F; + temp |= 0x0100000F;//125 micro second minimum width between two edge interrupts, 500ns int will remain low + // 15/30MHz=500 ns + isp1763_reg_write32(hcd->dev, HC_INT_THRESHOLD_REG, temp); +#endif + /*enable the global interrupt */ + temp &= 0; + temp = isp1763_reg_read16(hcd->dev, hcd->regs.hwmodecontrol, temp); + temp |= 0x01; /*enable the global interrupt */ +#ifdef EDGE_INTERRUPT + temp |= 0x02; /*enable the edge interrupt */ +#endif + +#ifdef POL_HIGH_INTERRUPT + temp |= 0x04; /* enable interrupt polarity high */ +#endif + + isp1763_reg_write16(hcd->dev, hcd->regs.hwmodecontrol, temp); + + /*maximum rate is one msec */ + /*enable the atl interrupts OR and AND mask */ + temp = 0; + isp1763_reg_write16(hcd->dev, hcd->regs.atl_irq_mask_and, temp); + temp = 0; + isp1763_reg_write16(hcd->dev, hcd->regs.atl_irq_mask_or, temp); + temp = 0; + isp1763_reg_write16(hcd->dev, hcd->regs.int_irq_mask_and, temp); + temp = 0x0; + isp1763_reg_write16(hcd->dev, hcd->regs.int_irq_mask_or, temp); + temp = 0; + isp1763_reg_write16(hcd->dev, hcd->regs.iso_irq_mask_and, temp); + temp = 0xffff; + isp1763_reg_write16(hcd->dev, hcd->regs.iso_irq_mask_or, temp); + + temp = isp1763_reg_read16(hcd->dev, hcd->regs.iso_irq_mask_or, temp); + pehci_print("%s:Iso irq mask reg value: 0x%08x\n", __FUNCTION__, temp); + + pehci_entry("-- %s: Exit\n", __FUNCTION__); +} + +/*initialize the host controller register map from Isp1763 to EHCI */ +static void +pehci_hcd_init_reg(phci_hcd * hcd) +{ + pehci_entry("++ %s: Entered\n", __FUNCTION__); + /* scratch pad for the test */ + hcd->regs.scratch = HC_SCRATCH_REG; + + /*make a copy of our interrupt locations */ + hcd->regs.command = HC_USBCMD_REG; + hcd->regs.usbstatus = HC_USBSTS_REG; + hcd->regs.usbinterrupt = HC_INTERRUPT_REG_EHCI; + + hcd->regs.hcsparams = HC_SPARAMS_REG; + hcd->regs.frameindex = HC_FRINDEX_REG; + + /*transfer specific registers */ + hcd->regs.hwmodecontrol = HC_HWMODECTRL_REG; + hcd->regs.interrupt = HC_INTERRUPT_REG; + hcd->regs.interruptenable = HC_INTENABLE_REG; + hcd->regs.atl_irq_mask_and = HC_ATL_IRQ_MASK_AND_REG; + hcd->regs.atl_irq_mask_or = HC_ATL_IRQ_MASK_OR_REG; + + hcd->regs.int_irq_mask_and = HC_INT_IRQ_MASK_AND_REG; + hcd->regs.int_irq_mask_or = HC_INT_IRQ_MASK_OR_REG; + hcd->regs.iso_irq_mask_and = HC_ISO_IRQ_MASK_AND_REG; + hcd->regs.iso_irq_mask_or = HC_ISO_IRQ_MASK_OR_REG; + hcd->regs.buffer_status = HC_BUFFER_STATUS_REG; + hcd->regs.interruptthreshold = HC_INT_THRESHOLD_REG; + /*initialization specific */ + hcd->regs.reset = HC_RESET_REG; + hcd->regs.configflag = HC_CONFIGFLAG_REG; + hcd->regs.ports[0] = HC_PORTSC1_REG; + hcd->regs.ports[1] = 0; /*port1,port2,port3 status reg are removed */ + hcd->regs.ports[2] = 0; + hcd->regs.ports[3] = 0; + hcd->regs.pwrdwn_ctrl = HC_POWER_DOWN_CONTROL_REG; + /*transfer registers */ + hcd->regs.isotddonemap = HC_ISO_PTD_DONEMAP_REG; + hcd->regs.isotdskipmap = HC_ISO_PTD_SKIPMAP_REG; + hcd->regs.isotdlastmap = HC_ISO_PTD_LASTPTD_REG; + + hcd->regs.inttddonemap = HC_INT_PTD_DONEMAP_REG; + + hcd->regs.inttdskipmap = HC_INT_PTD_SKIPMAP_REG; + hcd->regs.inttdlastmap = HC_INT_PTD_LASTPTD_REG; + + hcd->regs.atltddonemap = HC_ATL_PTD_DONEMAP_REG; + hcd->regs.atltdskipmap = HC_ATL_PTD_SKIPMAP_REG; + hcd->regs.atltdlastmap = HC_ATL_PTD_LASTPTD_REG; + + pehci_entry("-- %s: Exit\n", __FUNCTION__); +} + + + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +static void +pehci_interrupt_handler(phci_hcd * hcd, struct pt_regs *regs) +{ + spin_lock(&hcd->lock); +#ifdef CONFIG_ISO_SUPPORT + phcd_iso_handler(hcd, regs); +#endif + pehci_hcd_intl_worker(hcd, regs); + pehci_hcd_atl_worker(hcd, regs); + spin_unlock(&hcd->lock); + return; +} +#else +static void +pehci_interrupt_handler(phci_hcd * hcd) +{ + spin_lock(&hcd->lock); +#ifdef CONFIG_ISO_SUPPORT + pehci_hcd_iso_worker(hcd); +#endif + pehci_hcd_intl_worker(hcd); + pehci_hcd_atl_worker(hcd); + spin_unlock(&hcd->lock); + return; +} +#endif +irqreturn_t pehci_hcd_irq(struct usb_hcd *usb_hcd) +{ + + int work = 0; + phci_hcd *pehci_hcd; + struct isp1763_dev *dev; + u32 intr = 0; + u32 resume=0; + u32 temp=0; + u32 irq_mask = 0; + + if (!(usb_hcd->state & USB_STATE_READY)) { + info("interrupt handler state not ready yet\n"); + usb_hcd->state=USB_STATE_READY; + // return IRQ_NONE; + } + + /*our host */ + pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd); + dev = pehci_hcd->dev; + + spin_lock(&pehci_hcd->lock); + dev->int_reg = isp1763_reg_read16(dev, HC_INTERRUPT_REG, dev->int_reg); + /*Clear the interrupt*/ + isp1763_reg_write16(dev, HC_INTERRUPT_REG, dev->int_reg); + + irq_mask = isp1763_reg_read16(dev, HC_INTENABLE_REG, irq_mask); + dev->int_reg &= irq_mask; + + intr = dev->int_reg; + + + if (atomic_read(&pehci_hcd->nuofsofs)) { + spin_unlock(&pehci_hcd->lock); + return IRQ_HANDLED; + } + atomic_inc(&pehci_hcd->nuofsofs); + + irq_mask=isp1763_reg_read32(dev,HC_USBSTS_REG,0); + isp1763_reg_write32(dev,HC_USBSTS_REG,irq_mask); + if(irq_mask & 0x4){ // port status register. + if(intr & 0x50) { // OPR register change + temp=isp1763_reg_read32(dev,HC_PORTSC1_REG,0); + if(temp & 0x4){ // Force resume bit is set + if (dev) { + if (dev->driver) { + if (dev->driver->resume) { + dev->driver->resume(dev); + resume=1; + } + } + } + } + } + } + + set_bit(HCD_FLAG_SAW_IRQ, &usb_hcd->flags); + +#ifndef THREAD_BASED +/*-----------------------------------------------------------*/ +#ifdef MSEC_INT_BASED + work = 1; +#else + if (intr & (HC_MSEC_INT & INTR_ENABLE_MASK)) { + work = 1; /* phci_iso_worker(hcd); */ + } + +#ifdef USBNET + if (intr & HC_MSOF_INT ) { + struct list_head *pos, *q; + + list_for_each_safe(pos, q, &pehci_hcd->cleanup_urb.urb_list) { + struct isp1763_async_cleanup_urb *tmp; + + tmp = list_entry(pos, struct isp1763_async_cleanup_urb, urb_list); + if (tmp) { + spin_unlock(&pehci_hcd->lock); + usb_hcd_giveback_urb(usb_hcd, tmp->urb, tmp->urb->status); + spin_lock(&pehci_hcd->lock); + + list_del(pos); + if(tmp) + kfree(tmp); + } + } + isp1763_reg_write16(dev, HC_INTENABLE_REG, INTR_ENABLE_MASK ); + } +#endif + + + if (intr & (HC_INTL_INT & INTR_ENABLE_MASK)) { + // spin_lock(&pehci_hcd->lock); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + pehci_hcd_intl_worker(pehci_hcd, regs); +#else + pehci_hcd_intl_worker(pehci_hcd); +#endif + // spin_unlock(&pehci_hcd->lock); + work = 0; /*phci_intl_worker(hcd); */ + } + + if (intr & (HC_ATL_INT & INTR_ENABLE_MASK)) { + // spin_lock(&pehci_hcd->lock); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + pehci_hcd_atl_worker(pehci_hcd, regs); +#else + pehci_hcd_atl_worker(pehci_hcd); +#endif + // spin_unlock(&pehci_hcd->lock); + work = 0; /*phci_atl_worker(hcd); */ + } +#ifdef CONFIG_ISO_SUPPORT + if (intr & (HC_ISO_INT & INTR_ENABLE_MASK)) { + // spin_lock(&pehci_hcd->lock); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + pehci_hcd_iso_worker(pehci_hcd); +#else + pehci_hcd_iso_worker(pehci_hcd); +#endif + // spin_unlock(&pehci_hcd->lock); + work = 0; /*phci_atl_worker(hcd); */ + } +#endif +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + if (work){ + spin_unlock(&pehci_hcd->lock); + pehci_interrupt_handler(pehci_hcd, regs); + spin_lock(&pehci_hcd->lock); + } +#else + if (work){ + spin_unlock(&pehci_hcd->lock); + pehci_interrupt_handler(pehci_hcd); + spin_lock(&pehci_hcd->lock); + } +#endif + +/*-----------------------------------------------------------*/ +#else + if ((intr & (HC_INTL_INT & INTR_ENABLE_MASK)) ||(intr & (HC_ATL_INT & INTR_ENABLE_MASK))) + { //send + st_UsbIt_Msg_Struc *stUsbItMsgSnd ; + + stUsbItMsgSnd = (st_UsbIt_Msg_Struc *)kmalloc(sizeof(st_UsbIt_Msg_Struc), GFP_ATOMIC); + if (!stUsbItMsgSnd) return -ENOMEM; + + memset(stUsbItMsgSnd, 0, sizeof(stUsbItMsgSnd)); + + stUsbItMsgSnd->usb_hcd = usb_hcd; + stUsbItMsgSnd->uIntStatus = NO_SOF_REQ_IN_ISR; + list_add_tail(&(stUsbItMsgSnd->list), &(g_messList.list)); + + pehci_print("\n------------- send mess : %d------------\n",stUsbItMsgSnd->uIntStatus); + if ((g_stUsbItThreadHandler.phThreadTask != NULL) && (g_stUsbItThreadHandler.lThrdWakeUpNeeded == 0)) + { + pehci_print("\n------- wake up thread : %d-----\n",stUsbItMsgSnd->uIntStatus); + g_stUsbItThreadHandler.lThrdWakeUpNeeded = 1; + wake_up(&(g_stUsbItThreadHandler.ulThrdWaitQhead)); + } + } +/*-----------------------------------------------------------*/ +#endif + + atomic_dec(&pehci_hcd->nuofsofs); + spin_unlock(&pehci_hcd->lock); + if(resume){ + usb_hcd_poll_rh_status(usb_hcd); + } + return IRQ_HANDLED; +} + +/*reset the host controller + *called phci_hcd_start routine + *return 0, success else + *timeout, fails*/ +static int +pehci_hcd_reset(struct usb_hcd *usb_hcd) +{ + u32 command = 0; + u32 temp = 0; + phci_hcd *hcd = usb_hcd_to_pehci_hcd(usb_hcd); + printk(KERN_NOTICE "++ %s: Entered\n", __FUNCTION__); + pehci_hcd_init_reg(hcd); + printk("chipid %x \n", isp1763_reg_read32(hcd->dev, HC_CHIP_ID_REG, temp)); //0x70 + + /*reset the atx controller */ + temp &= 0; + temp |= 8; + isp1763_reg_write16(hcd->dev, hcd->regs.reset, temp); + mdelay(10); + + /*reset the host controller */ + temp &= 0; + temp |= 1; + isp1763_reg_write16(hcd->dev, hcd->regs.reset, temp); + + command = 0; + do { + + temp = isp1763_reg_read16(hcd->dev, hcd->regs.reset, temp); + mdelay(10); + command++; + if (command > 100) { + printk("not able to reset\n"); + break; + } + } while (temp & 0x01); + + + /*reset the ehci controller registers */ + temp = 0; + temp |= (1 << 1); + isp1763_reg_write16(hcd->dev, hcd->regs.reset, temp); + command = 0; + do { + temp = isp1763_reg_read16(hcd->dev, hcd->regs.reset, temp); + mdelay(10); + command++; + if (command > 100) { + printk("not able to reset\n"); + break; + } + } while (temp & 0x02); + + /*read the command register */ + command = isp1763_reg_read16(hcd->dev, hcd->regs.command, command); + + command |= CMD_RESET; + /*write back and wait for, 250 msec */ + isp1763_reg_write16(hcd->dev, hcd->regs.command, command); + /*wait for maximum 250 msecs */ + mdelay(200); + printk("command %x\n", + isp1763_reg_read16(hcd->dev, hcd->regs.command, command)); + printk(KERN_NOTICE "-- %s: Exit \n", __FUNCTION__); + return 0; +} + +/*host controller initialize routine, + *called by phci_hcd_probe + * */ +static int +pehci_hcd_start(struct usb_hcd *usb_hcd) +{ + + int retval; + int count = 0; + phci_hcd *pehci_hcd = NULL; + u32 temp = 0; + u32 hwmodectrl = 0; + u32 ul_scratchval = 0; + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd); + + spin_lock_init(&pehci_hcd->lock); + atomic_set(&pehci_hcd->nuofsofs, 0); + atomic_set(&pehci_hcd->missedsofs, 0); + + /*Initialize host controller registers */ + pehci_hcd_init_reg(pehci_hcd); + + /*reset the host controller */ + retval = pehci_hcd_reset(usb_hcd); + if (retval) { + err("phci_1763_start: error failing with status %x\n", retval); + return retval; + } + + hwmodectrl = + isp1763_reg_read16(pehci_hcd->dev, + pehci_hcd->regs.hwmodecontrol, hwmodectrl); +#ifdef DATABUS_WIDTH_16 + printk(KERN_NOTICE "Mode Ctrl Value before 16width: %x\n", hwmodectrl); + hwmodectrl &= 0xFFEF; /*enable the 16 bit bus */ + hwmodectrl |= 0x0400; /*enable common int */ +#else + printk(KERN_NOTICE "Mode Ctrl Value before 8width : %x\n", hwmodectrl); + hwmodectrl |= 0x0010; /*enable the 8 bit bus */ + hwmodectrl |= 0x0400; /*enable common int */ +#endif + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.hwmodecontrol, + hwmodectrl); + + hwmodectrl = + isp1763_reg_read16(pehci_hcd->dev, + pehci_hcd->regs.hwmodecontrol, hwmodectrl); + hwmodectrl |=0x9; //lock interface and enable global interrupt. + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.hwmodecontrol, + hwmodectrl); + printk(KERN_NOTICE "Mode Ctrl Value after buswidth: %x\n", hwmodectrl); + + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.scratch, 0x3344); + + ul_scratchval = + isp1763_reg_read16(pehci_hcd->dev, pehci_hcd->regs.scratch, + ul_scratchval); + printk(KERN_NOTICE "Scratch Reg Value : %x\n", ul_scratchval); + if (ul_scratchval != 0x3344) { + printk(KERN_NOTICE "Scratch Reg Value Mismatch: %x\n", + ul_scratchval); + + } + + + /*initialize the host controller initial values */ + /*disable all the buffer */ + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.buffer_status, 0); + /*skip all the transfers */ + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.atltdskipmap, + NO_TRANSFER_ACTIVE); + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.inttdskipmap, + NO_TRANSFER_ACTIVE); + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.isotdskipmap, + NO_TRANSFER_ACTIVE); + /*clear done map */ + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.atltddonemap, + NO_TRANSFER_DONE); + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.inttddonemap, + NO_TRANSFER_DONE); + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.isotddonemap, + NO_TRANSFER_DONE); + +#ifdef HCD_PACKAGE + /*port1 as Host */ + isp1763_reg_write16(pehci_hcd->dev, OTG_CTRL_SET_REG, 0x0400); + isp1763_reg_write16(pehci_hcd->dev, OTG_CTRL_CLEAR_REG, 0x0080); + /*port2 as Host */ + isp1763_reg_write16(pehci_hcd->dev, OTG_CTRL_SET_REG, 0x0000); + isp1763_reg_write16(pehci_hcd->dev, OTG_CTRL_CLEAR_REG, 0x8000); + + #if 0 /* do not use bit 1&2 for pure host application */ + ul_scratchval = isp1763_reg_read32(pehci_hcd->dev, HC_POWER_DOWN_CONTROL_REG,0); + ul_scratchval |= 0x006; + isp1763_reg_write32(pehci_hcd->dev, HC_POWER_DOWN_CONTROL_REG,ul_scratchval); + #endif + +#elif defined(HCD_DCD_PACKAGE) + + /*port1 as device */ + isp1763_reg_write16(pehci_hcd->dev,OTG_CTRL_SET_REG, + OTG_CTRL_DMPULLDOWN |OTG_CTRL_DPPULLDOWN | + OTG_CTRL_SW_SEL_HC_DC |OTG_CTRL_OTG_DISABLE); /* pure Device Mode and OTG disabled */ + + isp1763_reg_write16(pehci_hcd->dev, OTG_CTRL_SET_REG, 0x0480); + /*port2 as host */ + isp1763_reg_write16(pehci_hcd->dev, OTG_CTRL_SET_REG, 0x0000); + isp1763_reg_write16(pehci_hcd->dev, OTG_CTRL_CLEAR_REG, 0x8000); + ul_scratchval = + isp1763_reg_read32(pehci_hcd->dev, HC_POWER_DOWN_CONTROL_REG, + 0); +#endif + + /*enable interrupts */ + pehci_hcd_enable_interrupts(pehci_hcd); + + /*put controller into operational mode */ + retval = pehci_hcd_start_controller(pehci_hcd); + if (retval) { + err("phci_1763_start: error failing with status %x\n", retval); + return retval; + } + + /*Init the phci qtd <-> ptd map buffers */ + pehci_hcd_init_map_buffers(pehci_hcd); + + /*set last maps, for iso its only 1, else 32 tds bitmap */ + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.atltdlastmap, + 0x8000); + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.inttdlastmap, 0x80); + isp1763_reg_write16(pehci_hcd->dev, pehci_hcd->regs.isotdlastmap, 0x01); + /*iso transfers are not active */ + pehci_hcd->next_uframe = -1; + pehci_hcd->periodic_sched = 0; + hwmodectrl = + isp1763_reg_read16(pehci_hcd->dev, + pehci_hcd->regs.hwmodecontrol, hwmodectrl); + + /*initialize the periodic list */ + for (count = 0; count < PTD_PERIODIC_SIZE; count++) { + pehci_hcd->periodic_list[count].framenumber = 0; + INIT_LIST_HEAD(&pehci_hcd->periodic_list[count].sitd_itd_head); + } + + + /*set the state of the host to ready, + * start processing interrupts + * */ + + usb_hcd->state = HC_STATE_RUNNING; + pehci_hcd->state = HC_STATE_RUNNING; + + + /*initialize root hub timer */ + init_timer(&pehci_hcd->rh_timer); + /*initialize watchdog */ + init_timer(&pehci_hcd->watchdog); + + temp = isp1763_reg_read32(pehci_hcd->dev, HC_POWER_DOWN_CONTROL_REG, + temp); + + temp = 0x3e81bA0; +#if 0 + temp |= 0x306; +#endif + isp1763_reg_write32(pehci_hcd->dev, HC_POWER_DOWN_CONTROL_REG, temp); + temp = isp1763_reg_read32(pehci_hcd->dev, HC_POWER_DOWN_CONTROL_REG, + temp); + printk(" Powerdown Reg Val: %x\n", temp); + + pehci_entry("-- %s: Exit\n", __FUNCTION__); + + return 0; +} + +static void +pehci_hcd_stop(struct usb_hcd *usb_hcd) +{ + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + + /* no more interrupts ... */ + if (usb_hcd->state == USB_STATE_RUNNING) { + mdelay(2); + } + if (in_interrupt()) { /* must not happen!! */ + pehci_info("stopped in_interrupt!\n"); + + return; + } + + /*power off our root hub */ + pehci_rh_control(usb_hcd, ClearPortFeature, USB_PORT_FEAT_POWER, + 1, NULL, 0); + + /*let the roothub power go off */ + mdelay(20); + pehci_entry("-- %s: Exit\n", __FUNCTION__); + + return; +} + + +/*submit urb , other than root hub*/ +static int +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +pehci_hcd_urb_enqueue(struct usb_hcd *usb_hcd, struct usb_host_endpoint *ep, + struct urb *urb, gfp_t mem_flags) +#else +pehci_hcd_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb, gfp_t mem_flags) +#endif +{ + + struct list_head qtd_list; + struct ehci_qh *qh = 0; + phci_hcd *pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd); + int status = 0; + int temp = 0, max = 0, num_tds = 0, mult = 0; + urb_priv_t *urb_priv = NULL; + unsigned long flags; + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + + + if (unlikely(atomic_read(&urb->reject))) + return -EINVAL; + + INIT_LIST_HEAD(&qtd_list); + urb->transfer_flags &= ~EHCI_STATE_UNLINK; + + + temp = usb_pipetype(urb->pipe); + max = usb_maxpacket(urb->dev, urb->pipe, !usb_pipein(urb->pipe)); + + + if (hcdpowerdown == 1) { + printk("Enqueue hcd power down\n"); + return -EINVAL; + } + + + /*pathch to get the otg device */ + if (!hubdev || + (urb->dev->parent==usb_hcd->self.root_hub && + hubdev!=urb->dev)) { + if(urb->dev->parent== usb_hcd->self.root_hub) { + hubdev = urb->dev; + } + } + + switch (temp) { + case PIPE_INTERRUPT: + /*only one td */ + num_tds = 1; + mult = 1 + ((max >> 11) & 0x03); + max &= 0x07ff; + max *= mult; + + if (urb->transfer_buffer_length > max) { + err("interrupt urb length is greater then %d\n", max); + return -EINVAL; + } + + if (hubdev && urb->dev->parent == usb_hcd->self.root_hub) { + huburb = urb; + } + + break; + + case PIPE_CONTROL: + /*calculate the number of tds, follow 1 pattern */ + if (No_Data_Phase && No_Status_Phase) { + printk("Only SetUP Phase\n"); + num_tds = (urb->transfer_buffer_length == 0) ? 1 : + ((urb->transfer_buffer_length - + 1) / HC_ATL_PL_SIZE + 1); + } else if (!No_Data_Phase && No_Status_Phase) { + printk("SetUP Phase and Data Phase\n"); + num_tds = (urb->transfer_buffer_length == 0) ? 2 : + ((urb->transfer_buffer_length - + 1) / HC_ATL_PL_SIZE + 3); + } else if (!No_Data_Phase && !No_Status_Phase) { + num_tds = (urb->transfer_buffer_length == 0) ? 2 : + ((urb->transfer_buffer_length - + 1) / HC_ATL_PL_SIZE + 3); + } + + break; + + case PIPE_BULK: + num_tds = + (urb->transfer_buffer_length - 1) / HC_ATL_PL_SIZE + 1; + if ((urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % max)) { + num_tds++; + } + + break; + +#ifdef CONFIG_ISO_SUPPORT + case PIPE_ISOCHRONOUS: + /* Don't need to do anything here */ + break; +#endif + default: + return -EINVAL; /*not supported isoc transfers */ + + + } + +#ifdef CONFIG_ISO_SUPPORT + if (temp != PIPE_ISOCHRONOUS) { +#endif + /*make number of tds required */ + urb_priv = kmalloc(sizeof(urb_priv_t) + + num_tds * sizeof(struct ehci_qtd), + mem_flags); + if (!urb_priv) { + err("memory allocation error\n"); + return -ENOMEM; + } + + memset(urb_priv, 0, sizeof(urb_priv_t) + + num_tds * sizeof(struct ehci_qtd)); + INIT_LIST_HEAD(&urb_priv->qtd_list); + urb_priv->qtd[0] = NULL; + urb_priv->length = num_tds; + { + int i = 0; + /*allocate number of tds here. better to do this in qtd_make routine */ + for (i = 0; i < num_tds; i++) { + urb_priv->qtd[i] = + phci_hcd_qtd_allocate(mem_flags); + if (!urb_priv->qtd[i]) { + phci_hcd_urb_free_priv(pehci_hcd, + urb_priv, NULL); + return -ENOMEM; + } + } + } + /*keep a copy of this */ + urb->hcpriv = urb_priv; +#ifdef CONFIG_ISO_SUPPORT + } +#endif + + switch (temp) { + case PIPE_INTERRUPT: + phci_hcd_make_qtd(pehci_hcd, &urb_priv->qtd_list, urb, &status); + if (status < 0) { + return status; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qh = phci_hcd_submit_interrupt(pehci_hcd, ep, &urb_priv->qtd_list, urb, + &status); +#else + qh = phci_hcd_submit_interrupt(pehci_hcd, &urb_priv->qtd_list, urb, + &status); +#endif + if (status < 0) + return status; + break; + + case PIPE_CONTROL: + case PIPE_BULK: + +#ifdef THREAD_BASED + spin_lock_irqsave (&pehci_hcd->lock, flags); +#endif + phci_hcd_make_qtd(pehci_hcd, &qtd_list, urb, &status); + if (status < 0) { + return status; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qh = phci_hcd_submit_async(pehci_hcd, ep, &qtd_list, urb, + &status); +#else + qh = phci_hcd_submit_async(pehci_hcd, &qtd_list, urb, &status); +#endif + +#ifdef THREAD_BASED + spin_unlock_irqrestore (&pehci_hcd->lock, flags); +#endif + + if (status < 0) { + return status; + } + break; +#ifdef CONFIG_ISO_SUPPORT + case PIPE_ISOCHRONOUS: + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_urb_enqueue]: URB Transfer buffer: 0x%08x\n", + (long) urb->transfer_buffer); + iso_dbg(ISO_DBG_DATA, + "[pehci_hcd_urb_enqueue]: URB Buffer Length: %d\n", + (long) urb->transfer_buffer_length); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + phcd_submit_iso(pehci_hcd, ep, urb, (unsigned long *) &status); +#else + spin_lock_irqsave(&pehci_hcd->lock, flags); + phcd_store_urb_pending(pehci_hcd, 0, urb, (int *) &status); + spin_unlock_irqrestore(&pehci_hcd->lock, flags); +#endif + + return status; + + break; +#endif + default: + return -ENODEV; + } /*end of switch */ + +#if (defined MSEC_INT_BASED) + return 0; +#elif (defined THREAD_BASED) +{ //send + st_UsbIt_Msg_Struc *stUsbItMsgSnd ; + unsigned long flags; + spin_lock_irqsave(&pehci_hcd->lock,flags); + + //local_irq_save(flags); /*disable interrupt*/ + stUsbItMsgSnd = (st_UsbIt_Msg_Struc *)kmalloc(sizeof(st_UsbIt_Msg_Struc), GFP_ATOMIC); + if (!stUsbItMsgSnd) + { + return -ENOMEM; + } + + memset(stUsbItMsgSnd, 0, sizeof(stUsbItMsgSnd)); + + stUsbItMsgSnd->usb_hcd = usb_hcd; + stUsbItMsgSnd->uIntStatus = NO_SOF_REQ_IN_REQ; + spin_lock(&enqueue_lock); + if(list_empty(&g_enqueueMessList.list)) + list_add_tail(&(stUsbItMsgSnd->list), &(g_enqueueMessList.list)); + spin_unlock(&enqueue_lock); + + pehci_print("\n------------- send mess : %d------------\n",stUsbItMsgSnd->uIntStatus); + + //local_irq_restore(flags); /*disable interrupt*/ + + spin_lock(&g_stUsbItThreadHandler.lock); + if ((g_stUsbItThreadHandler.phThreadTask != NULL) && (g_stUsbItThreadHandler.lThrdWakeUpNeeded == 0)) + { + pehci_print("\n------- wake up thread : %d-----\n",stUsbItMsgSnd->uIntStatus); + g_stUsbItThreadHandler.lThrdWakeUpNeeded = 1; + wake_up(&(g_stUsbItThreadHandler.ulThrdWaitQhead)); + } + spin_unlock(&g_stUsbItThreadHandler.lock); + + spin_unlock_irqrestore(&pehci_hcd->lock,flags); + } + pehci_entry("-- %s: Exit\n",__FUNCTION__); + return 0; +#else + /*submit tds but iso */ + if (temp != PIPE_ISOCHRONOUS) + pehci_hcd_td_ptd_submit_urb(pehci_hcd, qh, qh->type); +#endif + pehci_entry("-- %s: Exit\n", __FUNCTION__); + return 0; + +} + +/*---------------------------------------------* + io request handlers + *---------------------------------------------*/ + +/*unlink urb*/ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +static int +pehci_hcd_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb) +#else +static int +pehci_hcd_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status) +#endif +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + int status = 0; +#endif + int retval = 0; + td_ptd_map_buff_t *td_ptd_buf; + td_ptd_map_t *td_ptd_map; + struct ehci_qh *qh = 0; + u32 skipmap = 0; + u32 buffstatus = 0; + unsigned long flags; + struct ehci_qtd *qtd = 0; + struct usb_host_endpoint *ep; + + struct ehci_qtd *cancel_qtd = 0; /*added for stopping ptd*/ + struct urb *cancel_urb = 0; /*added for stopping ptd*/ + urb_priv_t *cancel_urb_priv = 0; /* added for stopping ptd */ + struct _isp1763_qha atlqha; + struct _isp1763_qha *qha; + struct isp1763_mem_addr *mem_addr = 0; + u32 ormask = 0; + struct list_head *qtd_list = 0; + urb_priv_t *urb_priv = (urb_priv_t *) urb->hcpriv; + phci_hcd *hcd = usb_hcd_to_pehci_hcd(usb_hcd); + pehci_entry("++ %s: Entered\n", __FUNCTION__); + + pehci_info("device %d\n", urb->dev->devnum); + + if(urb_priv==NULL){ + printk("*******urb_priv is NULL******* %s: Entered\n", __FUNCTION__); + return 0; + } + spin_lock_irqsave(&hcd->lock, flags); + + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + // status = 0; + qh = urb_priv->qh; + if(qh==NULL) + break; + + td_ptd_buf = &td_ptd_map_buff[TD_PTD_BUFF_TYPE_ATL]; + td_ptd_map = &td_ptd_buf->map_list[qh->qtd_ptd_index]; + + /*if its already been removed */ + if (td_ptd_map->state == TD_PTD_NEW) { + break; + } +/* patch added for stopping Full speed PTD */ +/* patch starts ere */ + if (urb->dev->speed != USB_SPEED_HIGH) { + + cancel_qtd = td_ptd_map->qtd; + if (!qh || !cancel_qtd) { + err("Never Error:QH and QTD must not be zero\n"); + } else { + cancel_urb = cancel_qtd->urb; + cancel_urb_priv = + (urb_priv_t *) cancel_urb->hcpriv; + mem_addr = &cancel_qtd->mem_addr; + qha = &atlqha; + memset(qha, 0, sizeof(struct _isp1763_qha)); + + skipmap = + isp1763_reg_read16(hcd->dev, + hcd->regs. + atltdskipmap, + skipmap); + skipmap |= td_ptd_map->ptd_bitmap; + isp1763_reg_write16(hcd->dev, + hcd->regs.atltdskipmap, + skipmap); + + /*read this ptd from the ram address,address is in the + td_ptd_map->ptd_header_addr */ + isp1763_mem_read(hcd->dev, + td_ptd_map->ptd_header_addr, 0, + (u32 *) (qha), PHCI_QHA_LENGTH, + 0); + if ((qha->td_info1 & QHA_VALID) + || (qha->td_info4 & QHA_ACTIVE)) { + + qha->td_info2 |= 0x00008000; + qha->td_info1 |= QHA_VALID; + qha->td_info4 |= QHA_ACTIVE; + skipmap &= ~td_ptd_map->ptd_bitmap; + ormask |= td_ptd_map->ptd_bitmap; + isp1763_reg_write16(hcd->dev, + hcd->regs. + atl_irq_mask_or, + ormask); + /* copy back into the header, payload is already + * present no need to write again */ + isp1763_mem_write(hcd->dev, + td_ptd_map-> + ptd_header_addr, 0, + (u32 *) (qha), + PHCI_QHA_LENGTH, 0); + /*unskip this td */ + isp1763_reg_write16(hcd->dev, + hcd->regs. + atltdskipmap, + skipmap); + udelay(100); + } + + isp1763_mem_read(hcd->dev, + td_ptd_map->ptd_header_addr, 0, + (u32 *) (qha), PHCI_QHA_LENGTH, + 0); + if (!(qha->td_info1 & QHA_VALID) + && !(qha->td_info4 & QHA_ACTIVE)) { + printk(KERN_NOTICE + "ptd has been retired \n"); + } + + } + } + +/* Patch Ends */ + /* These TDs are not pending anymore */ + td_ptd_buf->pending_ptd_bitmap &= ~td_ptd_map->ptd_bitmap; + + /*tell atl worker this urb is going to be removed */ + td_ptd_map->state = TD_PTD_REMOVE; + /* These TDs are not pending anymore */ + td_ptd_buf->pending_ptd_bitmap &= ~td_ptd_map->ptd_bitmap; + /*tell atl worker this urb is going to be removed */ + td_ptd_map->state = TD_PTD_REMOVE; + urb_priv->state |= DELETE_URB; + + /*read the skipmap, to see if this transfer has to be rescheduled */ + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.atltdskipmap, + skipmap); + pehci_check("remove skip map %x, ptd map %x\n", skipmap, + td_ptd_map->ptd_bitmap); + + buffstatus = + isp1763_reg_read16(hcd->dev, hcd->regs.buffer_status, + buffstatus); + + + isp1763_reg_write16(hcd->dev, hcd->regs.atltdskipmap, + skipmap | td_ptd_map->ptd_bitmap); + + while (!(skipmap & td_ptd_map->ptd_bitmap)) { + udelay(125); + + skipmap = isp1763_reg_read16(hcd->dev, + hcd->regs.atltdskipmap, + skipmap); + } + + /* if all transfers skipped, + * then disable the atl buffer, + * so that new transfer can come in + * need to see the side effects + * */ + if (skipmap == NO_TRANSFER_ACTIVE) { + /*disable the buffer */ + pehci_info("disable the atl buffer\n"); + buffstatus &= ~ATL_BUFFER; + isp1763_reg_write16(hcd->dev, hcd->regs.buffer_status, + buffstatus); + } + + qtd_list = &qh->qtd_list; + /*this should remove all pending transfers */ + pehci_check("num tds %d, urb length %d,device %d\n", + urb_priv->length, urb->transfer_buffer_length, + urb->dev->devnum); + + pehci_check("remove first qtd address %p\n", urb_priv->qtd[0]); + pehci_check("length of the urb %d, completed %d\n", + urb->transfer_buffer_length, urb->actual_length); + qtd = urb_priv->qtd[urb_priv->length - 1]; + pehci_check("qtd state is %x\n", qtd->state); + + + urb->status=status; + status = 0; +#ifdef USBNET +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + pehci_hcd_urb_delayed_complete(hcd, qh, urb, td_ptd_map, NULL); +#else + pehci_hcd_urb_delayed_complete(hcd, qh, urb, td_ptd_map); +#endif +#else +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map, NULL); +#else + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map); +#endif + +#endif + break; + + case PIPE_INTERRUPT: + pehci_check("phci_1763_urb_dequeue: INTR needs to be done\n"); + urb->status = status; //-ENOENT;//This will allow to suspend the system. in auto suspend mode + status = 0; + qh = urb_priv->qh; + if(qh==NULL) + break; + + td_ptd_buf = &td_ptd_map_buff[TD_PTD_BUFF_TYPE_INTL]; + td_ptd_map = &td_ptd_buf->map_list[qh->qtd_ptd_index]; + + /*urb is already been removed */ + if (td_ptd_map->state == TD_PTD_NEW) { + kfree(urb_priv); + break; + } + + /* These TDs are not pending anymore */ + td_ptd_buf->pending_ptd_bitmap &= ~td_ptd_map->ptd_bitmap; + + td_ptd_map->state = TD_PTD_REMOVE; + urb_priv->state |= DELETE_URB; + + /*read the skipmap, to see if this transfer has to be rescheduled */ + skipmap = + isp1763_reg_read16(hcd->dev, hcd->regs.inttdskipmap, + skipmap); + + isp1763_reg_write16(hcd->dev, hcd->regs.inttdskipmap, + skipmap | td_ptd_map->ptd_bitmap); + qtd_list = &qh->qtd_list; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map, NULL); +#else + pehci_hcd_urb_complete(hcd, qh, urb, td_ptd_map); +#endif + break; +#ifdef CONFIG_ISO_SUPPORT + case PIPE_ISOCHRONOUS: + pehci_info("urb dequeue %x %x\n", urb,urb->pipe); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + if(urb->dev->speed==USB_SPEED_HIGH){ + retval = usb_hcd_check_unlink_urb(usb_hcd, urb, status); + if (!retval) { + pehci_info("[pehci_hcd_urb_dequeue] usb_hcd_unlink_urb_from_ep with status = %d\n", status); + usb_hcd_unlink_urb_from_ep(usb_hcd, urb); + + + } + } +#endif + + + status = 0; + ep=urb->ep; + spin_unlock_irqrestore(&hcd->lock, flags); + mdelay(100); + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + if (urb->hcpriv!= periodic_ep[0]){ +#else + if (urb->ep != periodic_ep[0]){ +#endif + if(!list_empty(&ep->urb_list)){ + while(!list_empty(&ep->urb_list)){ + urb=container_of(ep->urb_list.next,struct urb,urb_list); + pehci_info("list is not empty %x %x\n",urb,urb->dev->state); + if(urb){ + retval = usb_hcd_check_unlink_urb(usb_hcd, urb,0); + if (!retval) { + pehci_info("[pehci_hcd_urb_dequeue] usb_hcd_unlink_urb_from_ep with status = %d\n", status); + usb_hcd_unlink_urb_from_ep(usb_hcd, urb); + } + urb->status=-ESHUTDOWN; + #if LINUX_VERSION_CODE status); + #endif + + } + } + }else{ + if(urb){ + pehci_info("list empty %x\n",urb->dev->state); + phcd_clean_urb_pending(hcd, urb); + retval = usb_hcd_check_unlink_urb(usb_hcd, urb,0); + if (!retval) { + pehci_info("[pehci_hcd_urb_dequeue] usb_hcd_unlink_urb_from_ep with status = %d\n", status); + usb_hcd_unlink_urb_from_ep(usb_hcd, urb); + } + urb->status=-ESHUTDOWN; + #if LINUX_VERSION_CODE status); + #endif + + } + + } + } +#endif + return 0; + /*nothing to do here, wait till all transfers are done in iso worker */ + break; + } + + spin_unlock_irqrestore(&hcd->lock, flags); + pehci_info("status %d\n", status); + pehci_entry("-- %s: Exit\n", __FUNCTION__); + return status; +} + +/* bulk qh holds the data toggle */ + +static void +pehci_hcd_endpoint_disable(struct usb_hcd *usb_hcd, + struct usb_host_endpoint *ep) +{ + phci_hcd *ehci = usb_hcd_to_pehci_hcd(usb_hcd); + struct urb *urb; + + unsigned long flags; + struct ehci_qh *qh; + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + +#ifdef CONFIG_ISO_SUPPORT + mdelay(100); //delay for ISO +#endif + spin_lock_irqsave(&ehci->lock, flags); + + qh = ep->hcpriv; + + if (!qh) { + goto done; + } else { +#ifdef CONFIG_ISO_SUPPORT + pehci_info("disable endpoint %x %x\n", ep->desc.bEndpointAddress,qh->type); + + + if (qh->type == TD_PTD_BUFF_TYPE_ISTL) { + + /*wait for urb to get complete*/ + pehci_info("disable %x \n", list_empty(&ep->urb_list)); + while (!list_empty(&ep->urb_list)) { + + urb = container_of(ep->urb_list.next, + struct urb, urb_list); + if (urb) { + phcd_clean_urb_pending(ehci, urb); + spin_unlock_irqrestore(&ehci->lock, + flags); + + urb->status = -ESHUTDOWN; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + usb_hcd_giveback_urb(usb_hcd, urb); +#else + usb_hcd_giveback_urb(usb_hcd, urb, + urb->status); +#endif + spin_lock_irqsave(&ehci->lock, flags); + + } + + } + } +#endif + /*i will complete whatever left on this endpoint */ + pehci_complete_device_removal(ehci, qh); +#ifdef CONFIG_ISO_SUPPORT + phcd_clean_periodic_ep(); +#endif + ep->hcpriv = NULL; + + goto done; + } + done: + + ep->hcpriv = NULL; + + spin_unlock_irqrestore(&ehci->lock, flags); + printk("disable endpoint exit\n"); + pehci_entry("-- %s: Exit\n", __FUNCTION__); + return; +} + +/*called by core, for current frame number*/ +static int +pehci_hcd_get_frame_number(struct usb_hcd *usb_hcd) +{ + u32 framenumber = 0; + phci_hcd *pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd); + framenumber = + isp1763_reg_read16(pehci_hcd->dev, pehci_hcd->regs.frameindex, + framenumber); + return framenumber; +} + +/*root hub status data, called by root hub timer + *return 0, if no change, else + * 1, incase of high speed device + */ +static int +pehci_rh_status_data(struct usb_hcd *usb_hcd, char *buf) +{ + + u32 temp = 0, status = 0; + u32 ports = 0, i, retval = 1; + unsigned long flags; + phci_hcd *hcd = usb_hcd_to_pehci_hcd(usb_hcd); + + if (hcdpowerdown == 1) + return 0; + + buf[0] = 0; + if(portchange==1){ + printk("Remotewakeup-enumerate again \n"); + buf[0] |= 2; + hcd->reset_done[0] = 0; + return 1; + } + /* init status to no-changes */ + buf[0] = 0; + /*number of ports */ + ports = 0x1; + spin_lock_irqsave(&hcd->lock, flags); + /*read the port status registers */ + for (i = 0; i < ports; i++) { + temp = isp1763_reg_read32(hcd->dev, hcd->regs.ports[i], temp); + if (temp & PORT_OWNER) { + /* dont report the port status change in case of CC HCD + * but clear the port status , if there is any*/ + if (temp & PORT_CSC) { + temp &= ~PORT_CSC; + isp1763_reg_write32(hcd->dev, + hcd->regs.ports[i], temp); + continue; + } + } + + if (!(temp & PORT_CONNECT)) { + hcd->reset_done[i] = 0; + } + if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0) { + if (i < 7) { + buf[0] |= 1 << (i + 1); + } else { + buf[1] |= 1 << (i - 7); + } + status = STS_PCD; + } + } + + spin_unlock_irqrestore(&hcd->lock, flags); + return status ? retval : 0; +} + +/*root hub control requests*/ +static int +pehci_rh_control(struct usb_hcd *usb_hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + u32 ports = 0; + u32 temp = 0, status; + unsigned long flags; + int retval = 0; + phci_hcd *hcd = usb_hcd_to_pehci_hcd(usb_hcd); + + ports = 0x11; + + printk("%s: request %x,wValuse:0x%x, wIndex:0x%x \n",__func__, typeReq,wValue,wIndex); + + spin_lock_irqsave(&hcd->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + pehci_print("ClearPortFeature:0x%x\n", ClearPortFeature); + if (!wIndex || wIndex > (ports & 0xf)) { + pehci_info + ("ClearPortFeature not valid port number %d, should be %d\n", + wIndex, (ports & 0xf)); + goto error; + } + wIndex--; + temp = isp1763_reg_read32(hcd->dev, hcd->regs.ports[wIndex], + temp); + if (temp & PORT_OWNER) { + printk("port is owned by the CC host\n"); + break; + } + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + pehci_print("enable the port\n"); + isp1763_reg_write32(hcd->dev, hcd->regs.ports[wIndex], + temp & ~PORT_PE); + + break; + case USB_PORT_FEAT_C_ENABLE: + printk("disable the port\n"); + isp1763_reg_write32(hcd->dev, hcd->regs.ports[wIndex], + temp | PORT_PEC); + break; + case USB_PORT_FEAT_SUSPEND: + case USB_PORT_FEAT_C_SUSPEND: + printk("clear feature suspend \n"); + break; + case USB_PORT_FEAT_POWER: + if (ports & 0x10) { /*port has has power control switches */ + isp1763_reg_write32(hcd->dev, + hcd->regs.ports[wIndex], + temp & ~PORT_POWER); + } + break; + case USB_PORT_FEAT_C_CONNECTION: + pehci_print("connect change, status is 0x%08x\n", temp); + isp1763_reg_write32(hcd->dev, hcd->regs.ports[wIndex], + temp | PORT_CSC); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + isp1763_reg_write32(hcd->dev, hcd->regs.ports[wIndex], + temp | PORT_OCC); + break; + default: + goto error; + + } + break; + + case GetHubDescriptor: + pehci_hub_descriptor(hcd, (struct usb_hub_descriptor *) buf); + break; + + case GetHubStatus: + pehci_print("GetHubStatus:0x%x\n", GetHubStatus); + /* no hub-wide feature/status flags */ + memset(buf, 0, 4); + break; + case GetPortStatus: + pehci_print("GetPortStatus:0x%x\n", GetPortStatus); + if (!wIndex || wIndex > (ports & 0xf)) { + pehci_info + ("GetPortStatus,not valid port number %d, should be %d\n", + wIndex, (ports & 0xf)); + goto error; + } + wIndex--; + status = 0; + temp = isp1763_reg_read32(hcd->dev, hcd->regs.ports[wIndex], + temp); + printk("root port status:0x%x\n", temp); + /*connect status chnage */ + if (temp & PORT_CSC) { + status |= 1 << USB_PORT_FEAT_C_CONNECTION; + pehci_print("feature CSC 0x%08x and status 0x%08x \n", + temp, status); + } + if(portchange){ + portchange=0; + status |= 1 << USB_PORT_FEAT_C_CONNECTION; + } + /*port enable change */ + if (temp & PORT_PEC) { + status |= 1 << USB_PORT_FEAT_C_ENABLE; + pehci_print("feature PEC 0x%08x and status 0x%08x \n", + temp, status); + } + /*port over-current */ + if (temp & PORT_OCC) { + status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; + pehci_print("feature OCC 0x%08x and status 0x%08x \n", + temp, status); + } + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) && jiffies > hcd->reset_done[wIndex]) { + status |= 1 << USB_PORT_FEAT_C_RESET; + pehci_print("feature reset 0x%08x and status 0x%08x\n", + temp, status); + printk(KERN_NOTICE + "feature reset 0x%08x and status 0x%08x\n", temp, + status); + /* force reset to complete */ + isp1763_reg_write32(hcd->dev, hcd->regs.ports[wIndex], + temp & ~PORT_RESET); + do { + mdelay(20); + temp = isp1763_reg_read32(hcd->dev, + hcd->regs. + ports[wIndex], temp); + } while (temp & PORT_RESET); + + /* see what we found out */ + printk(KERN_NOTICE "after portreset: %x\n", temp); + + temp = phci_check_reset_complete(hcd, wIndex, temp); + printk(KERN_NOTICE "after checkportreset: %x\n", temp); + } + + /* don't show wPortStatus if it's owned by a companion hc */ + + if (!(temp & PORT_OWNER)) { + + if (temp & PORT_CONNECT) { + status |= 1 << USB_PORT_FEAT_CONNECTION; + status |= 1 << USB_PORT_FEAT_HIGHSPEED; + } + if (temp & PORT_PE) { + status |= 1 << USB_PORT_FEAT_ENABLE; + } + if (temp & PORT_SUSPEND) { + status |= 1 << USB_PORT_FEAT_SUSPEND; + } + if (temp & PORT_OC) { + status |= 1 << USB_PORT_FEAT_OVER_CURRENT; + } + if (temp & PORT_RESET) { + status |= 1 << USB_PORT_FEAT_RESET; + } + if (temp & PORT_POWER) { + status |= 1 << USB_PORT_FEAT_POWER; + } + } + + /* This alignment is good, caller used kmalloc() */ + *((u32 *) buf) = cpu_to_le32(status); + break; + + case SetHubFeature: + pehci_print("SetHubFeature:0x%x\n", SetHubFeature); + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + pehci_print("SetPortFeature:%x\n", SetPortFeature); + if (!wIndex || wIndex > (ports & 0xf)) { + pehci_info + ("SetPortFeature not valid port number %d, should be %d\n", + wIndex, (ports & 0xf)); + goto error; + } + wIndex--; + temp = isp1763_reg_read32(hcd->dev, hcd->regs.ports[wIndex], + temp); + pehci_print("SetPortFeature:PortSc Val 0x%x\n", temp); + if (temp & PORT_OWNER) { + break; + } + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + /*enable the port */ + isp1763_reg_write32(hcd->dev, hcd->regs.ports[wIndex], + temp | PORT_PE); + break; + case USB_PORT_FEAT_SUSPEND: + + #if 0 /* Port suspend will be added in suspend function */ + isp1763_reg_write32(hcd->dev, hcd->regs.ports[wIndex], + temp | PORT_SUSPEND); + #endif + + break; + case USB_PORT_FEAT_POWER: + pehci_print("Set Port Power 0x%x and Ports %x\n", + USB_PORT_FEAT_POWER, ports); + if (ports & 0x10) { + printk(KERN_NOTICE + "PortSc Reg %x an Value %x\n", + hcd->regs.ports[wIndex], + (temp | PORT_POWER)); + + isp1763_reg_write32(hcd->dev, + hcd->regs.ports[wIndex], + temp | PORT_POWER); + } + break; + case USB_PORT_FEAT_RESET: + pehci_print("Set Port Reset 0x%x\n", + USB_PORT_FEAT_RESET); + if ((temp & (PORT_PE | PORT_CONNECT)) == PORT_CONNECT + && PORT_USB11(temp)) { + printk("error:port %d low speed --> companion\n", wIndex + 1); + temp |= PORT_OWNER; + } else { + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + hcd->reset_done[wIndex] = jiffies + + ((50 /* msec */ * HZ) / 1000); + } + isp1763_reg_write32(hcd->dev, hcd->regs.ports[wIndex], + temp); + break; + default: + goto error; + } + break; + default: + pehci_print("this request doesnt fit anywhere\n"); + error: + /* "stall" on error */ + pehci_info + ("unhandled root hub request: typereq 0x%08x, wValue %d, wIndex %d\n", + typeReq, wValue, wIndex); + retval = -EPIPE; + } + + pehci_info("rh_control:exit\n"); + spin_unlock_irqrestore(&hcd->lock, flags); + return retval; +} + + + +/*-------------------------------------------------------------------------*/ + +static const struct hc_driver pehci_driver = { + .description = hcd_name, + .product_desc = "ST-ERICSSON ISP1763", + .hcd_priv_size = sizeof(phci_hcd), +#ifdef LINUX_2620 + .irq = NULL, +#else + .irq = pehci_hcd_irq, +#endif + /* + * generic hardware linkage + */ + .flags = HCD_USB2 | HCD_MEMORY, + + /* + * basic lifecycle operations + */ + .reset = pehci_hcd_reset, + .start = pehci_hcd_start, + .bus_suspend = pehci_bus_suspend, + .bus_resume = pehci_bus_resume, + .stop = pehci_hcd_stop, + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = pehci_hcd_urb_enqueue, + .urb_dequeue = pehci_hcd_urb_dequeue, + .endpoint_disable = pehci_hcd_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = pehci_hcd_get_frame_number, + + /* + * root hub support + */ + .hub_status_data = pehci_rh_status_data, + .hub_control = pehci_rh_control, +}; + +/*probe the PCI host*/ + +#ifdef THREAD_BASED +int pehci_hcd_process_irq_it_handle(struct usb_hcd* usb_hcd_) +{ + int istatus; + + struct usb_hcd *usb_hcd; + char uIntStatus; + phci_hcd *pehci_hcd; + + struct list_head *pos, *lst_tmp; + st_UsbIt_Msg_Struc *mess; + unsigned long flags; + + g_stUsbItThreadHandler.phThreadTask = current; + siginitsetinv(&((g_stUsbItThreadHandler.phThreadTask)->blocked), sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM)); + pehci_info("pehci_hcd_process_irq_it_thread ID : %d\n", g_stUsbItThreadHandler.phThreadTask->pid); + + while (1) + { + if (signal_pending(g_stUsbItThreadHandler.phThreadTask)) + { + printk("thread handler: Thread received signal\n"); + break; + } + + spin_lock(&g_stUsbItThreadHandler.lock); + g_stUsbItThreadHandler.lThrdWakeUpNeeded = 0; + spin_unlock(&g_stUsbItThreadHandler.lock); + + /* Wait until a signal arrives or we are woken up or timeout (5second)*/ + istatus = wait_event_interruptible_timeout(g_stUsbItThreadHandler.ulThrdWaitQhead, (g_stUsbItThreadHandler.lThrdWakeUpNeeded== 1), msecs_to_jiffies(MSEC_INTERVAL_CHECKING)); + + local_irq_save(flags); /*disable interrupt*/ + spin_lock(&g_stUsbItThreadHandler.lock); + g_stUsbItThreadHandler.lThrdWakeUpNeeded = 1; + spin_unlock(&g_stUsbItThreadHandler.lock); + //receive mess + if (!list_empty(&g_messList.list)) //mess list not empty + { + + list_for_each_safe(pos, lst_tmp, &(g_messList.list)) + { + mess = list_entry(pos, st_UsbIt_Msg_Struc, list); + + usb_hcd = mess->usb_hcd; + uIntStatus = mess->uIntStatus; + //pehci_print("-------------receive mess : %d------------\n",uIntStatus); + pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd); + if((uIntStatus & NO_SOF_REQ_IN_TSK) || (uIntStatus & NO_SOF_REQ_IN_ISR) || (uIntStatus & NO_SOF_REQ_IN_REQ)) + pehci_interrupt_handler(pehci_hcd); + spin_lock(&g_stUsbItThreadHandler.lock); + list_del(pos); + kfree(mess); + spin_unlock(&g_stUsbItThreadHandler.lock); + } + } + else if(!list_empty(&g_enqueueMessList.list)) + { + mess = list_first_entry(&(g_enqueueMessList.list), st_UsbIt_Msg_Struc, list); + usb_hcd = mess->usb_hcd; + uIntStatus = mess->uIntStatus; + + pehci_print("-------------receive mess : %d------------\n",uIntStatus); + pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd); + if((uIntStatus & NO_SOF_REQ_IN_REQ)) + { + pehci_interrupt_handler(pehci_hcd); + } + + { + spin_lock(&enqueue_lock); + list_del((g_enqueueMessList.list).next); + kfree(mess); + spin_unlock(&enqueue_lock); + } + } + else if(istatus == 0) //timeout + { + pehci_hcd = NULL; + pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd_); + pehci_interrupt_handler(pehci_hcd); + + } + local_irq_restore(flags); /*enable interrupt*/ + } + + flush_signals(g_stUsbItThreadHandler.phThreadTask); + g_stUsbItThreadHandler.phThreadTask = NULL; + return 0; + +} + +int pehci_hcd_process_irq_in_thread(struct usb_hcd* usb_hcd_) +{ + + //status = msgq_create("usb_it_queue", 10, sizeof(st_UsbIt_Msg_Struc), &uUsbIt_MsgQueId); + INIT_LIST_HEAD(&g_messList.list); + INIT_LIST_HEAD(&g_enqueueMessList.list); + spin_lock_init(&enqueue_lock); + + memset(&g_stUsbItThreadHandler, 0, sizeof(st_UsbIt_Thread)); + init_waitqueue_head(&(g_stUsbItThreadHandler.ulThrdWaitQhead)); + g_stUsbItThreadHandler.lThrdWakeUpNeeded = 0; + spin_lock_init(&g_stUsbItThreadHandler.lock); + kernel_thread(pehci_hcd_process_irq_it_handle, usb_hcd_, 0); + + return 0; +} +#endif + + +/*probe the PCI host*/ +int +pehci_hcd_probe(struct isp1763_dev *isp1763_dev, isp1763_id * ids) +{ +#ifdef NON_PCI + struct platform_device *dev = isp1763_dev->dev; +#else /* PCI */ + struct pci_dev *dev = isp1763_dev->pcidev; +#endif + struct usb_hcd *usb_hcd; + phci_hcd *pehci_hcd; + int status = 0; + +#ifndef NON_PCI + u32 intcsr=0; +#endif + pehci_entry("++ %s: Entered\n", __FUNCTION__); + if (usb_disabled()) { + return -ENODEV; + } + + usb_hcd = usb_create_hcd(&pehci_driver,&dev->dev, "ISP1763"); + + if (usb_hcd == NULL) { + status = -ENOMEM; + goto clean; + } + + /* this is our host */ + pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd); + pehci_hcd->dev = isp1763_dev; + pehci_hcd->iobase = (u8 *) isp1763_dev->baseaddress; + pehci_hcd->iolength = isp1763_dev->length; + + + /* lets keep our host here */ + isp1763_dev->driver_data = usb_hcd; +#ifdef NON_PCI +//Do nothing +#else + /* Enable the interrupts from PLX to PCI */ + /* CONFIGURE PCI/PLX interrupt */ +#ifdef DATABUS_WIDTH_16 + wvalue1 = readw(pehci_hcd->plxiobase + 0x68); + wvalue2 = readw(pehci_hcd->plxiobase + 0x68 + 2); + intcsr |= wvalue2; + intcsr <<= 16; + intcsr |= wvalue1; + printk(KERN_NOTICE "Enable PCI Intr: %x \n", intcsr); + intcsr |= 0x900; + writew((u16) intcsr, pehci_hcd->plxiobase + 0x68); + writew((u16) (intcsr >> 16), pehci_hcd->plxiobase + 0x68 + 2); +#else + bvalue1 = readb(pehci_hcd->plxiobase + 0x68); + bvalue2 = readb(pehci_hcd->plxiobase + 0x68 + 1); + bvalue3 = readb(pehci_hcd->plxiobase + 0x68 + 2); + bvalue4 = readb(pehci_hcd->plxiobase + 0x68 + 3); + intcsr |= bvalue4; + intcsr <<= 8; + intcsr |= bvalue3; + intcsr <<= 8; + intcsr |= bvalue2; + intcsr <<= 8; + intcsr |= bvalue1; + writeb((u8) intcsr, pehci_hcd->plxiobase + 0x68); + writeb((u8) (intcsr >> 8), pehci_hcd->plxiobase + 0x68 + 1); + writeb((u8) (intcsr >> 16), pehci_hcd->plxiobase + 0x68 + 2); + writeb((u8) (intcsr >> 24), pehci_hcd->plxiobase + 0x68 + 3); +#endif +#endif + + No_Data_Phase = 0; + No_Status_Phase = 0; + usb_hcd->self.controller->dma_mask = 0; + usb_hcd->self.otg_port = 1; +#if 0 +#ifndef THREAD_BASED + status = isp1763_request_irq(pehci_hcd_irq, isp1763_dev, usb_hcd); +#endif +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + if (status == 0) { + status = usb_add_hcd(usb_hcd, isp1763_dev->irq, SA_SHIRQ); + } +#else /* Linux 2.6.28*/ + usb_hcd->self.uses_dma = 0; + if (status == 0){ + status = usb_add_hcd(usb_hcd, isp1763_dev->irq, + IRQF_SHARED | IRQF_DISABLED | IRQF_TRIGGER_LOW); + } +#endif + +#ifdef THREAD_BASED + g_pehci_hcd = pehci_hcd; +#endif + +#ifdef USBNET + // initialize clean up urb list + INIT_LIST_HEAD(&(pehci_hcd->cleanup_urb.urb_list)); +#endif + enable_irq_wake(isp1763_dev->irq); + wake_lock_init(&pehci_wake_lock, WAKE_LOCK_SUSPEND, + dev_name(&dev->dev)); + wake_lock(&pehci_wake_lock); + + pehci_entry("-- %s: Exit\n", __FUNCTION__); + isp1763_hcd=isp1763_dev; + return status; + + clean: + return status; + +} +/*--------------------------------------------------------------* + * + * Module details: pehci_hcd_powerup + * + * This function powerdown the chip completely, which make chip works in minimal power + * + * Input: struct isp1763_Dev * + * + * + * + * + * Called by: IOCTL function + * + * + --------------------------------------------------------------*/ +void +pehci_hcd_powerup(struct isp1763_dev *dev) +{ + printk("%s\n", __FUNCTION__); + hcdpowerdown = 0; + dev->driver->probe(dev,dev->driver->id); + + +} +void +pehci_hcd_powerdown(struct isp1763_dev *dev) +{ + struct usb_hcd *usb_hcd; + + phci_hcd *hcd = NULL; + u32 temp; + usb_hcd = (struct usb_hcd *) dev->driver_data; + if (!usb_hcd) { + return; + } + + printk("%s\n", __FUNCTION__); + hcd = usb_hcd_to_pehci_hcd(usb_hcd); + + temp = isp1763_reg_read16(dev, HC_USBCMD_REG, 0); + temp &= ~0x01; /* stop the controller first */ + isp1763_reg_write16(dev, HC_USBCMD_REG, temp); + printk("++ %s: Entered\n", __FUNCTION__); + +// isp1763_free_irq(dev,usb_hcd); + usb_remove_hcd(usb_hcd); + dev->driver_data = NULL; + + + temp = isp1763_reg_read16(dev, HC_INTENABLE_REG, temp); //0xD6 + temp &= ~0x400; /*disable otg interrupt*/ + isp1763_reg_write16(dev, HC_INTENABLE_REG, temp); //0xD6 + + isp1763_reg_write16(dev, HC_UNLOCK_DEVICE, 0xAA37); /*unlock the device 0x7c*/ + mdelay(1); + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + + + if ((temp & 0x1005) == 0x1005) { + isp1763_reg_write32(dev, HC_PORTSC1_REG, 0x1000); + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + mdelay(10); + isp1763_reg_write32(dev, HC_PORTSC1_REG, 0x1104); + mdelay(10); + isp1763_reg_write32(dev, HC_PORTSC1_REG, 0x1007); + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + mdelay(10); + isp1763_reg_write32(dev, HC_PORTSC1_REG, 0x1005); + + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + } + + printk("port status %x\n ", temp); + temp &= ~0x2; + temp &= ~0x40; /*force port resume*/ + temp |= 0x80; /*suspend*/ + + isp1763_reg_write32(dev, HC_PORTSC1_REG, temp); + printk("port status %x\n ", temp); + mdelay(200); + + temp = isp1763_reg_read16(dev, HC_HW_MODE_REG, 0); /*suspend the device first 0xc*/ + temp |= 0x2c; + isp1763_reg_write16(dev, HC_HW_MODE_REG, temp); //0xc + mdelay(20); + + temp = isp1763_reg_read16(dev, HC_HW_MODE_REG, 0); //0xc + temp = 0xc; + isp1763_reg_write16(dev, HC_HW_MODE_REG, temp); //0xc + + isp1763_reg_write32(dev, HC_POWER_DOWN_CONTROL_REG, 0xffff0800); + + wake_unlock(&pehci_wake_lock); + wake_lock_destroy(&pehci_wake_lock); + + hcdpowerdown = 1; + +} + +static int pehci_bus_suspend(struct usb_hcd *usb_hcd) +{ + u32 temp=0; + unsigned long flags; + phci_hcd *pehci_hcd = NULL; + struct isp1763_dev *dev = NULL; + + + if (!usb_hcd) { + return -EBUSY; + } + + printk("++ %s \n",__FUNCTION__); + pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd); + + dev = pehci_hcd->dev; + + spin_lock_irqsave(&pehci_hcd->lock, flags); + if(hcdpowerdown){ + spin_unlock_irqrestore(&pehci_hcd->lock, flags); + return 0; + } + + + isp1763_reg_write32(dev, HC_USBSTS_REG, 0x4); //0x90 + isp1763_reg_write32(dev, HC_INTERRUPT_REG_EHCI, 0x4); //0x94 + isp1763_reg_write16(dev, HC_INTERRUPT_REG, INTR_ENABLE_MASK); //0xd4 + + temp=isp1763_reg_read16(dev, HC_INTERRUPT_REG, 0); //0xd4 + + isp1763_reg_write16(dev,HC_INTENABLE_REG,INTR_ENABLE_MASK); + temp=isp1763_reg_read16(dev,HC_INTENABLE_REG,0); + + hcdpowerdown = 1; + + /* stop the controller first */ + temp = isp1763_reg_read16(dev, HC_USBCMD_REG, 0); + temp &= ~0x01; + isp1763_reg_write16(dev, HC_USBCMD_REG, temp); + + /* suspend root port which will suspend host controller of the ISP1763A */ + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + temp |= (PORT_SUSPEND);//0x80 + isp1763_reg_write32(dev, HC_PORTSC1_REG, temp); + + /* suspend device controller of the ISP1763a*/ + temp = isp1763_reg_read16(dev, HC_HW_MODE_REG, 0); + temp |= 0x20; + isp1763_reg_write16(dev, HC_HW_MODE_REG, temp); + mdelay(1); // make sure there will not be huge delay here max is 1 ms + temp &= ~0x20; + isp1763_reg_write16(dev, HC_HW_MODE_REG, temp); + /* put host controoler into low power mode */ + isp1763_reg_write32(dev, HC_POWER_DOWN_CONTROL_REG, POWER_DOWN_CTRL_SUSPEND_VALUE); + +// usb_hcd->state = HC_STATE_SUSPENDED; + + spin_unlock_irqrestore(&pehci_hcd->lock, flags); + + printk("-- %s \n",__FUNCTION__); + + wake_unlock(&pehci_wake_lock); + + return 0; + + +} + +static int pehci_bus_resume(struct usb_hcd *usb_hcd) +{ + u32 temp,i; + phci_hcd *pehci_hcd = NULL; + struct isp1763_dev *dev = NULL; + unsigned long flags; + u32 portsc1; + + printk("%s Enter \n",__func__); + + if (!usb_hcd) { + return -EBUSY; + } + + if(hcdpowerdown ==0){ + printk("%s already executed\n ",__func__); + return 0; + } + + pehci_hcd = usb_hcd_to_pehci_hcd(usb_hcd); + dev = pehci_hcd->dev; + spin_lock_irqsave(&pehci_hcd->lock, flags); + + for (temp = 0; temp < 100; temp++) + { + i = isp1763_reg_read32(dev, HC_CHIP_ID_REG, 0); + if(i==0x176320) + break; + mdelay(2); + } + printk("temp=%d, chipid:0x%x \n",temp,i); + mdelay(10); + isp1763_reg_write16(dev, HC_UNLOCK_DEVICE, 0xAA37); /*unlock the device 0x7c*/ + i = isp1763_reg_read32(dev, HC_POWER_DOWN_CONTROL_REG, 0); + printk("POWER DOWN CTRL REG value during suspend =0x%x\n", i); + for (temp = 0; temp < 100; temp++) { + mdelay(1); + isp1763_reg_write32(dev, HC_POWER_DOWN_CONTROL_REG, POWER_DOWN_CTRL_NORMAL_VALUE); + mdelay(1); + i = isp1763_reg_read32(dev, HC_POWER_DOWN_CONTROL_REG, 0); + if(i==POWER_DOWN_CTRL_NORMAL_VALUE) + break; + } + if (temp == 100) { + spin_unlock_irqrestore(&pehci_hcd->lock, flags); + pr_err("%s:isp1763a failed to resume\n", __func__); + return -1; + } + + wake_lock(&pehci_wake_lock); + + printk("%s: Powerdown Reg Val: 0x%08x -- %d\n", __func__, i, temp); + + isp1763_reg_write32(dev, HC_USBSTS_REG,0x0); //0x90 + isp1763_reg_write32(dev, HC_INTERRUPT_REG_EHCI, 0x0); //0x94 + isp1763_reg_write16(dev, HC_INTENABLE_REG,0); //0xD6 + + portsc1 = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + printk("%s PORTSC1: 0x%x\n", __func__, portsc1); + + temp = isp1763_reg_read16(dev, HC_USBCMD_REG, 0); + temp |= 0x01; /* Start the controller */ + isp1763_reg_write16(dev, HC_USBCMD_REG, temp); + mdelay(10); + + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + if (temp & PORT_SUSPEND) + pr_err("%s: HC_PORTSC1_REG: 0x%08x\n", __func__, temp); + temp |= PORT_SUSPEND; //0x80; + isp1763_reg_write32(dev, HC_PORTSC1_REG, temp); + mdelay(50); + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + temp |= PORT_RESUME; //0x40; + temp &= ~(PORT_SUSPEND); //0x80; /*suspend*/ + isp1763_reg_write32(dev, HC_PORTSC1_REG, temp); + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + temp &= ~(PORT_RESUME); //0x40; + isp1763_reg_write32(dev, HC_PORTSC1_REG, temp); + + temp = INTR_ENABLE_MASK; + isp1763_reg_write16(dev, HC_INTENABLE_REG, temp); //0xD6 + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + printk("%s resume port status: 0x%x\n", __func__, temp); + if(!(temp & 0x4)){ //port is disabled + isp1763_reg_write16(dev, HC_INTENABLE_REG, 0x1005); //0xD6 + mdelay(10); + } +// phci_resume_wakeup(dev); + + hcdpowerdown = 0; + if(hubdev){ + hubdev->hcd_priv = NULL; + hubdev->hcd_suspend = NULL; + } + + spin_unlock_irqrestore(&pehci_hcd->lock, flags); + printk("%s Leave\n",__func__); + + return 0; +} + +void +pehci_hcd_resume(struct isp1763_dev *dev) +{ + struct usb_hcd *usb_hcd; + u32 temp,i; + usb_hcd = (struct usb_hcd *) dev->driver_data; + if (!usb_hcd) { + return; + } + + if(hcdpowerdown ==0){ + return ; + } + + printk("%s \n",__FUNCTION__); + + for (temp = 0; temp < 10; temp++) + { + i = isp1763_reg_read32(dev, HC_CHIP_ID_REG, 0); + printk("temp=%d, chipid:0x%x \n",temp,i); + if(i==0x176320) + break; + mdelay(1); + } + + /* Start the controller */ + temp = 0x01; + isp1763_reg_write16(dev, HC_USBCMD_REG, temp); + + /* update power down control reg value */ + for (temp = 0; temp < 100; temp++) { + isp1763_reg_write32(dev, HC_POWER_DOWN_CONTROL_REG, POWER_DOWN_CTRL_NORMAL_VALUE); + i = isp1763_reg_read32(dev, HC_POWER_DOWN_CONTROL_REG, 0); + if(i==POWER_DOWN_CTRL_NORMAL_VALUE) + break; + } + + if (temp == 100) { + pr_err("%s:isp1763a failed to resume\n", __func__); + return; + } + + wake_lock(&pehci_wake_lock); + + isp1763_reg_write16(dev, HC_INTENABLE_REG,0); //0xD6 + isp1763_reg_write32(dev,HC_INTERRUPT_REG_EHCI,0x4); //0x94 + isp1763_reg_write32(dev,HC_INTERRUPT_REG,0xFFFF); //0x94 + /* clear suspend bit and resume bit */ + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + temp &= ~(PORT_SUSPEND); //0x80; /*suspend*/ + temp &= ~(PORT_RESUME); // 0x40; + isp1763_reg_write32(dev, HC_PORTSC1_REG, temp); + + isp1763_reg_write16(dev, HC_INTENABLE_REG, INTR_ENABLE_MASK); //0xD6 + /*this is just make sure port is resumed back */ + mdelay(1); + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + printk("after hcd resume :port status %x\n ", temp); + + hcdpowerdown = 0; + + phci_resume_wakeup(dev); + + if(hubdev){ + hubdev->hcd_priv=NULL; + hubdev->hcd_suspend=NULL; + } +// usb_hcd->state = HC_STATE_RUNNING; + +} + + +void +pehci_hcd_suspend(struct isp1763_dev *dev) +{ + struct usb_hcd *usb_hcd; + u32 temp; + usb_hcd = (struct usb_hcd *) dev->driver_data; + if (!usb_hcd) { + return; + } + printk("%s \n",__FUNCTION__); + if(hcdpowerdown){ + return ; + } + + temp = isp1763_reg_read16(dev, HC_USBCMD_REG, 0); + temp &= ~0x01; /* stop the controller first */ + isp1763_reg_write16(dev, HC_USBCMD_REG, temp); + + isp1763_reg_write32(dev, HC_USBSTS_REG, 0x4); //0x90 + isp1763_reg_write32(dev, HC_INTERRUPT_REG_EHCI, 0x4); //0x94 + isp1763_reg_write16(dev, HC_INTERRUPT_REG, INTR_ENABLE_MASK); //0xd4 + + temp=isp1763_reg_read16(dev, HC_INTERRUPT_REG, 0); //0xd4 + + printk("suspend :Interrupt Status %x\n",temp); + isp1763_reg_write16(dev,HC_INTENABLE_REG,INTR_ENABLE_MASK); + temp=isp1763_reg_read16(dev,HC_INTENABLE_REG,0); + printk("suspend :Interrupt Enable %x\n",temp); + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + + printk("suspend :port status %x\n ", temp); + temp &= ~0x2; + temp &= ~0x40; /*force port resume*/ + temp |= 0x80; /*suspend*/ +// temp |= 0x700000; /*WKCNNT_E,WKDSCNNT_E,WKOC_E*/ + isp1763_reg_write32(dev, HC_PORTSC1_REG, temp); + // mdelay(10); + temp = isp1763_reg_read32(dev, HC_PORTSC1_REG, 0); + printk("suspend :port status %x\n ", temp); + hcdpowerdown = 1; + + + temp = isp1763_reg_read16(dev,HC_HW_MODE_REG, 0); /*suspend the device first 0xc*/ + temp&=0xff7b; + isp1763_reg_write16(dev, HC_HW_MODE_REG, temp); //0xc + + + temp = isp1763_reg_read16(dev, HC_HW_MODE_REG, 0); /*suspend the device first 0xc*/ + temp |= 0x20; + isp1763_reg_write16(dev, HC_HW_MODE_REG, temp);//0xc + mdelay(2); + temp = isp1763_reg_read16(dev, HC_HW_MODE_REG, 0);//0xc + temp &= 0xffdf; + temp &= ~0x20; + isp1763_reg_write16(dev, HC_HW_MODE_REG, temp);//0xc + + isp1763_reg_write32(dev, HC_POWER_DOWN_CONTROL_REG, 0xffff0830); + + wake_unlock(&pehci_wake_lock); + +} + +void +pehci_hcd_remotewakeup(struct isp1763_dev *dev){ + if(hubdev){ + hubdev->hcd_priv=dev; + hubdev->hcd_suspend=(void *)pehci_hcd_suspend; + } + phci_remotewakeup(dev); +} + +/*remove the host controller*/ +static void +pehci_hcd_remove(struct isp1763_dev *isp1763_dev) +{ + + struct usb_hcd *usb_hcd; + +#ifdef NON_PCI +#else /* PCI */ +// struct pci_dev *dev = isp1763_dev->pcidev; +#endif + + phci_hcd *hcd = NULL; + u32 temp; + usb_hcd = (struct usb_hcd *) isp1763_dev->driver_data; + if (!usb_hcd) { + return; + } + hcd=usb_hcd_to_pehci_hcd(usb_hcd); + isp1763_reg_write32(hcd->dev,hcd->regs.hwmodecontrol,0); + isp1763_reg_write32(hcd->dev,hcd->regs.interruptenable,0); + hubdev=0; + huburb=0; + temp = isp1763_reg_read16(hcd->dev, HC_USBCMD_REG, 0); + temp &= ~0x01; /* stop the controller first */ + isp1763_reg_write16(hcd->dev, HC_USBCMD_REG, temp); +// isp1763_free_irq(isp1763_dev,usb_hcd); + usb_remove_hcd(usb_hcd); + + wake_unlock(&pehci_wake_lock); + wake_lock_destroy(&pehci_wake_lock); + + return ; +} + + +static isp1763_id ids = { + .idVendor = 0x04CC, /*st ericsson isp1763 vendor_id */ + .idProduct = 0x1A64, /*st ericsson isp1763 product_id */ + .driver_info = (unsigned long) &pehci_driver, +}; + +/* pci driver glue; this is a "new style" PCI driver module */ +static struct isp1763_driver pehci_hcd_pci_driver = { + .name = (char *) hcd_name, + .index = 0, + .id = &ids, + .probe = pehci_hcd_probe, + .remove = pehci_hcd_remove, + .suspend = pehci_hcd_suspend, + .resume = pehci_hcd_resume, + .remotewakeup=pehci_hcd_remotewakeup, + .powerup = pehci_hcd_powerup, + .powerdown = pehci_hcd_powerdown, +}; + +#ifdef HCD_PACKAGE +int +usb_hcddev_open(struct inode *inode, struct file *fp) +{ + + return 0; +} + +int +usb_hcddev_close(struct inode *inode, struct file *fp) +{ + + return 0; +} + +int +usb_hcddev_fasync(int fd, struct file *fp, int mode) +{ + + return fasync_helper(fd, fp, mode, &fasync_q); +} + +long +usb_hcddev_ioctl(struct file *fp, + unsigned int cmd, unsigned long arg) +{ + + switch (cmd) { + case HCD_IOC_POWERDOWN: /* SET HCD DEEP SUSPEND MODE */ + printk("HCD IOC POWERDOWN MODE\n"); + if(isp1763_hcd->driver->powerdown) + isp1763_hcd->driver->powerdown(isp1763_hcd); + + break; + + case HCD_IOC_POWERUP: /* Set HCD POWER UP */ + printk("HCD IOC POWERUP MODE\n"); + if(isp1763_hcd->driver->powerup) + isp1763_hcd->driver->powerup(isp1763_hcd); + + break; + case HCD_IOC_TESTSE0_NACK: + HostComplianceTest = HOST_COMPILANCE_TEST_ENABLE; + HostTest = HOST_COMP_TEST_SE0_NAK; + break; + case HCD_IOC_TEST_J: + HostComplianceTest = HOST_COMPILANCE_TEST_ENABLE; + HostTest = HOST_COMP_TEST_J; + break; + case HCD_IOC_TEST_K: + HostComplianceTest = HOST_COMPILANCE_TEST_ENABLE; + HostTest = HOST_COMP_TEST_K; + break; + + case HCD_IOC_TEST_TESTPACKET: + HostComplianceTest = HOST_COMPILANCE_TEST_ENABLE; + HostTest = HOST_COMP_TEST_PACKET; + break; + case HCD_IOC_TEST_FORCE_ENABLE: + HostComplianceTest = HOST_COMPILANCE_TEST_ENABLE; + HostTest = HOST_COMP_TEST_FORCE_ENABLE; + break; + case HCD_IOC_TEST_SUSPEND_RESUME: + HostComplianceTest = HOST_COMPILANCE_TEST_ENABLE; + HostTest = HOST_COMP_HS_HOST_PORT_SUSPEND_RESUME; + break; + case HCD_IOC_TEST_SINGLE_STEP_GET_DEV_DESC: + HostComplianceTest = HOST_COMPILANCE_TEST_ENABLE; + HostTest = HOST_COMP_SINGLE_STEP_GET_DEV_DESC; + break; + case HCD_IOC_TEST_SINGLE_STEP_SET_FEATURE: + HostComplianceTest = HOST_COMPILANCE_TEST_ENABLE; + HostTest = HOST_COMP_SINGLE_STEP_SET_FEATURE; + break; + case HCD_IOC_TEST_STOP: + HostComplianceTest = 0; + HostTest = 0; + break; + case HCD_IOC_SUSPEND_BUS: + printk("isp1763:SUSPEND bus\n"); + if(isp1763_hcd->driver->suspend) + isp1763_hcd->driver->suspend(isp1763_hcd); + break; + case HCD_IOC_RESUME_BUS: + printk("isp1763:RESUME bus\n"); + if(isp1763_hcd->driver->resume) + isp1763_hcd->driver->resume(isp1763_hcd); + break; + case HCD_IOC_REMOTEWAKEUP_BUS: + printk("isp1763:SUSPEND bus\n"); + if(isp1763_hcd->driver->remotewakeup) + isp1763_hcd->driver->remotewakeup(isp1763_hcd); + break; + default: + + break; + + } + return 0; +} + + +/* HCD file operations */ +static struct file_operations usb_hcddev_fops = { + owner:THIS_MODULE, + read:NULL, + write:NULL, + poll:NULL, + unlocked_ioctl:usb_hcddev_ioctl, + open:usb_hcddev_open, + release:usb_hcddev_close, + fasync:usb_hcddev_fasync, +}; + +#endif + + +static int __init +pehci_module_init(void) +{ + int result = 0; + phci_hcd_mem_init(); + + /*register driver */ + result = isp1763_register_driver(&pehci_hcd_pci_driver); + if (!result) { + info("Host Driver has been Registered"); + } else { + err("Host Driver has not been Registered with errors : %x", + result); + } + +#ifdef THREAD_BASED + pehci_hcd_process_irq_in_thread(&(g_pehci_hcd->usb_hcd)); + printk("kernel_thread() Enter\n"); +#endif + +#ifdef HCD_PACKAGE + printk("Register Char Driver for HCD\n"); + result = register_chrdev(USB_HCD_MAJOR, USB_HCD_MODULE_NAME, + &usb_hcddev_fops); + +#endif + return result; + +} + +static void __exit +pehci_module_cleanup(void) +{ +#ifdef THREAD_BASED + printk("module exit: Sending signal to stop thread\n"); + if (g_stUsbItThreadHandler.phThreadTask != NULL) + { + send_sig(SIGKILL, g_stUsbItThreadHandler.phThreadTask, 1); + mdelay(6); + } +#endif + +#ifdef HCD_PACKAGE + unregister_chrdev(USB_HCD_MAJOR, USB_HCD_MODULE_NAME); +#endif + isp1763_unregister_driver(&pehci_hcd_pci_driver); +} + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); +module_init(pehci_module_init); +module_exit(pehci_module_cleanup); diff --git a/drivers/usb/host/pehci/host/pehci.h b/drivers/usb/host/pehci/host/pehci.h new file mode 100644 index 00000000000..cc6a06bee63 --- /dev/null +++ b/drivers/usb/host/pehci/host/pehci.h @@ -0,0 +1,752 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux OTG Controller driver : host +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* Refer to file ~/drivers/usb/host/ehci-dbg.h for copyright owners (kernel version 2.6.9) +* Code is modified for ST-Ericsson product +* +* Author : wired support +* +*/ + +#ifndef __PEHCI_H__ +#define __PEHCI_H__ + + +#define DRIVER_AUTHOR "ST-ERICSSON " +#define DRIVER_DESC "ISP1763 'Enhanced' Host Controller (EHCI) Driver" + +/* bus related stuff */ +#define __ACTIVE 0x01 +#define __SLEEPY 0x02 +#define __SUSPEND 0x04 +#define __TRANSIENT 0x80 + +#define USB_STATE_HALT 0 +#define USB_STATE_RUNNING (__ACTIVE) +#define USB_STATE_READY (__ACTIVE|__SLEEPY) +#define USB_STATE_QUIESCING (__SUSPEND|__TRANSIENT|__ACTIVE) +#define USB_STATE_RESUMING (__SUSPEND|__TRANSIENT) +#define USB_STATE_SUSPENDED (__SUSPEND) + +/* System flags */ +#define HCD_MEMORY 0x0001 +#define HCD_USB2 0x0020 +#define HCD_USB11 0x0010 + +#define HCD_IS_RUNNING(state) ((state) & __ACTIVE) +#define HCD_IS_SUSPENDED(state) ((state) & __SUSPEND) + + +/*--------------------------------------------------- + * Host controller related + -----------------------------------------------------*/ +/* IRQ line for the ISP1763 */ +#define HCD_IRQ IRQ_GPIO(25) +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ +#define STS_PCD (1<<2) /* port change detect */ +/* NOTE: urb->transfer_flags expected to not use this bit !!! */ +#define EHCI_STATE_UNLINK 0x8000 /* urb being unlinked */ + +/* Bits definations for qha*/ +/* Bits PID*/ +#define SETUP_PID (2) +#define OUT_PID (0) +#define IN_PID (1) + +/* Bits MULTI*/ +#define MULTI(x) ((x)<< 29) +#define XFER_PER_UFRAME(x) (((x) >> 29) & 0x3) + +/*Active, EP type and speed bits */ +#define QHA_VALID (1<<0) +#define QHA_ACTIVE (1<<31) + +/*1763 error bit maps*/ +#define HC_MSOF_INT (1<< 0) +#define HC_MSEC_INT (1 << 1) +#define HC_EOT_INT (1 << 3) +#define HC_OPR_REG_INT (1<<4) +#define HC_CLK_RDY_INT (1<<6) +#define HC_INTL_INT (1 << 7) +#define HC_ATL_INT (1 << 8) +#define HC_ISO_INT (1 << 9) +#define HC_OTG_INT (1 << 10) + +/*PTD error codes*/ +#define PTD_STATUS_HALTED (1 << 30) +#define PTD_XACT_ERROR (1 << 28) +#define PTD_BABBLE (1 << 29) +#define PTD_ERROR (PTD_STATUS_HALTED | PTD_XACT_ERROR | PTD_BABBLE) +/*ep types*/ +#define EPTYPE_BULK (2 << 12) +#define EPTYPE_CONTROL (0 << 12) +#define EPTYPE_INT (3 << 12) +#define EPTYPE_ISO (1 << 12) + +#define PHCI_QHA_LENGTH 32 + +#define usb_inc_dev_use usb_get_dev +#define usb_dec_dev_use usb_put_dev +#define usb_free_dev usb_put_dev +/*1763 host controller periodic size*/ +#define PTD_PERIODIC_SIZE 16 +#define MAX_PERIODIC_SIZE 16 +#define PTD_FRAME_MASK 0x1f +/*periodic list*/ +struct _periodic_list { + int framenumber; + struct list_head sitd_itd_head; + char high_speed; /*1 - HS ; 0 - FS*/ + u16 ptdlocation; +}; +typedef struct _periodic_list periodic_list; + + +/*iso ptd*/ +struct _isp1763_isoptd { + u32 td_info1; + u32 td_info2; + u32 td_info3; + u32 td_info4; + u32 td_info5; + u32 td_info6; + u32 td_info7; + u32 td_info8; +} __attribute__ ((aligned(32))); + +typedef struct _isp1763_isoptd isp1763_isoptd; + +struct _isp1763_qhint { + u32 td_info1; + u32 td_info2; + u32 td_info3; + u32 td_info4; + u32 td_info5; +#define INT_UNDERRUN (1 << 2) +#define INT_BABBLE (1 << 1) +#define INT_EXACT (1 << 0) + u32 td_info6; + u32 td_info7; + u32 td_info8; +} __attribute__ ((aligned(32))); + +typedef struct _isp1763_qhint isp1763_qhint; + + +struct _isp1763_qha { + u32 td_info1; /* First 32 bit */ + u32 td_info2; /* Second 32 bit */ + u32 td_info3; /* third 32 bit */ + u32 td_info4; /* fourth 32 bit */ + u32 reserved[4]; +}; +typedef struct _isp1763_qha isp1763_qha, *pisp1763_qha; + + + + +/*this does not cover all interrupts in 1763 chip*/ +typedef struct _ehci_regs { + + /*standard ehci registers */ + u32 command; + u32 usbinterrupt; + u32 usbstatus; + u32 hcsparams; + u32 frameindex; + + /*isp1763 interrupt specific registers */ + u16 hwmodecontrol; + u16 interrupt; + u16 interruptenable; + u32 interruptthreshold; + u16 iso_irq_mask_or; + u16 int_irq_mask_or; + u16 atl_irq_mask_or; + u16 iso_irq_mask_and; + u16 int_irq_mask_and; + u16 atl_irq_mask_and; + u16 buffer_status; + + /*isp1763 initialization registers */ + u32 reset; + u32 configflag; + u32 ports[4]; + u32 pwrdwn_ctrl; + + /*isp1763 transfer specific registers */ + u16 isotddonemap; + u16 inttddonemap; + u16 atltddonemap; + u16 isotdskipmap; + u16 inttdskipmap; + u16 atltdskipmap; + u16 isotdlastmap; + u16 inttdlastmap; + u16 atltdlastmap; + u16 scratch; + +} ehci_regs, *pehci_regs; + +/*memory management structures*/ +#define MEM_KV +#ifdef MEM_KV +typedef struct isp1763_mem_addr { + u32 phy_addr; /* Physical address of the memory */ + u32 virt_addr; /* after ioremap() function call */ + u8 num_alloc; /* In case n*smaller size is allocated then for clearing purpose */ + u32 blk_size; /*block size */ + u8 blk_num; /* number of the block */ + u8 used; /*used/free */ +} isp1763_mem_addr_t; +#else +typedef struct isp1763_mem_addr { + void *phy_addr; /* Physical address of the memory */ + void *virt_addr; /* after ioremap() function call */ + u8 usage; + u32 blk_size; /*block size */ +} isp1763_mem_addr_t; + +#endif +/* type tag from {qh,itd,sitd,fstn}->hw_next */ +#define Q_NEXT_TYPE(dma) ((dma) & __constant_cpu_to_le32 (3 << 1)) + +/* values for that type tag */ +#define Q_TYPE_ITD __constant_cpu_to_le32 (0 << 1) +#define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1) +#define Q_TYPE_SITD __constant_cpu_to_le32 (2 << 1) +#define Q_TYPE_FSTN __constant_cpu_to_le32 (3 << 1) + +/*next queuehead in execution*/ +#define QH_NEXT(dma) cpu_to_le32((u32)dma) + +struct ehci_qh { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.6.1 */ + u32 hw_info1; /* see EHCI 3.6.2 */ + + u32 hw_info2; /* see EHCI 3.6.2 */ + u32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct ehci_qtd) */ + u32 hw_qtd_next; + u32 hw_alt_next; + u32 hw_token; + u32 hw_buf[5]; + u32 hw_buf_hi[5]; + + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + struct list_head qtd_list; /* sw qtd list */ + struct ehci_qtd *dummy; + struct ehci_qh *reclaim; /* next to reclaim */ + + atomic_t refcount; + wait_queue_head_t waitforcomplete; + unsigned stamp; + + u8 qh_state; + + /* periodic schedule info */ + u8 usecs; /* intr bandwidth */ + u8 gap_uf; /* uframes split/csplit gap */ + u8 c_usecs; /* ... split completion bw */ + unsigned short period; /* polling interval */ + unsigned short start; /* where polling starts */ + u8 datatoggle; /*data toggle */ + + /*handling the ping stuffs */ + u8 ping; /*ping bit */ + + /*qtd <-> ptd management */ + + u32 qtd_ptd_index; /* Td-PTD map index for this ptd */ + u32 type; /* endpoint type */ + + /*iso stuffs */ + struct usb_host_endpoint *ep; + int next_uframe; /*next uframe for this endpoint */ + struct list_head itd_list; /*list of tds to this endpoint */ + isp1763_mem_addr_t memory_addr; + struct _periodic_list periodic_list; + /*scheduling requirements for this endpoint */ + u32 ssplit; + u32 csplit; + u8 totalptds; // total number of PTDs needed for current URB + u8 actualptds; // scheduled PTDs until now for current URB +}; + +/* urb private part for the driver. */ +typedef struct { + struct ehci_qh *qh; + u16 length; /* number of tds associated with this request */ + u16 td_cnt; /* number of tds already serviced */ + int state; /* State machine state when URB is deleted */ + int timeout; /* timeout for bulk transfers */ + wait_queue_head_t wait; /* wait State machine state when URB is deleted */ + /*FIX solve the full speed dying */ + struct timer_list urb_timer; + struct list_head qtd_list; + struct ehci_qtd *qtd[0]; /* list pointer to all corresponding TDs associated with this request */ + +} urb_priv_t; + +/* + * EHCI Specification 0.95 Section 3.6 + * QH: describes control/bulk/interrupt endpoints + * See Fig 3-7 "Queue Head Structure Layout". + * + * These appear in both the async and (for interrupt) periodic schedules. + */ + + +/*Defination required for the ehci Queuehead */ +#define QH_HEAD 0x00008000 +#define QH_STATE_LINKED 1 /* HC sees this */ +#define QH_STATE_UNLINK 2 /* HC may still see this */ +#define QH_STATE_IDLE 3 /* HC doesn't see this */ +#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */ +#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ +#define QH_STATE_TAKE_NEXT 8 /*take the new transfer from */ +#define NO_FRAME ((unsigned short)~0) /* pick new start */ + + +#define EHCI_ITD_TRANLENGTH 0x0fff0000 /*transaction length */ +#define EHCI_ITD_PG 0x00007000 /*page select */ +#define EHCI_ITD_TRANOFFSET 0x00000fff /*transaction offset */ +#define EHCI_ITD_BUFFPTR 0xfffff000 /*buffer pointer */ + +struct ehci_sitd { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.3.1 */ + u32 hw_transaction[8]; /* see EHCI 3.3.2 */ +#define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ +#define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ +#define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ +#define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ + +#define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ + + u32 hw_bufp[7]; /* see EHCI 3.3.3 */ + u32 hw_bufp_hi[7]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t sitd_dma; /* for this itd */ + struct urb *urb; + struct list_head sitd_list; /* list of urb frames' itds */ + dma_addr_t buf_dma; /* frame's buffer address */ + + /* for now, only one hw_transaction per itd */ + u32 transaction; + u16 index; /* in urb->iso_frame_desc */ + u16 uframe; /* in periodic schedule */ + u16 usecs; + /*memory address */ + struct isp1763_mem_addr mem_addr; + int length; + u32 framenumber; + u32 ptdframe; + int sitd_index; + /*scheduling fields */ + u32 ssplit; + u32 csplit; + u32 start_frame; +}; + +struct ehci_itd { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.3.1 */ + u32 hw_transaction[8]; /* see EHCI 3.3.2 */ +#define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ +#define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ +#define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ +#define EHCI_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ + +#define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ + + u32 hw_bufp[7]; /* see EHCI 3.3.3 */ + u32 hw_bufp_hi[7]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t itd_dma; /* for this itd */ + struct urb *urb; + struct list_head itd_list; /* list of urb frames' itds */ + dma_addr_t buf_dma; /* frame's buffer address */ + u8 num_of_pkts; /*number of packets for this ITD */ + /* for now, only one hw_transaction per itd */ + u32 transaction; + u16 index; /* in urb->iso_frame_desc */ + u16 uframe; /* in periodic schedule */ + u16 usecs; + /*memory address */ + struct isp1763_mem_addr mem_addr; + int length; + u32 multi; + u32 framenumber; + u32 ptdframe; + int itd_index; + /*scheduling fields */ + u32 ssplit; + u32 csplit; +}; + +/* + * EHCI Specification 0.95 Section 3.5 + * QTD: describe data transfer components (buffer, direction, ...) + * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". + * + * These are associated only with "QH" (Queue Head) structures, + * used with control, bulk, and interrupt transfers. + */ +struct ehci_qtd { + /* first part defined by EHCI spec */ + u32 hw_next; /* see EHCI 3.5.1 */ + u32 hw_alt_next; /* see EHCI 3.5.2 */ + u32 hw_token; /* see EHCI 3.5.3 */ + + u32 hw_buf[5]; /* see EHCI 3.5.4 */ + u32 hw_buf_hi[5]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t qtd_dma; /* qtd address */ + struct list_head qtd_list; /* sw qtd list */ + struct urb *urb; /* qtd's urb */ + size_t length; /* length of buffer */ + u32 state; /*state of the qtd */ +#define QTD_STATE_NEW 0x100 +#define QTD_STATE_DONE 0x200 +#define QTD_STATE_SCHEDULED 0x400 +#define QTD_STATE_LAST 0x800 + struct isp1763_mem_addr mem_addr; +}; + +#define QTD_TOGGLE (1 << 31) /* data toggle */ +#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define QTD_IOC (1 << 15) /* interrupt on complete */ +#define QTD_CERR(tok) (((tok)>>10) & 0x3) +#define QTD_PID(tok) (((tok)>>8) & 0x3) +#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define QTD_STS_HALT (1 << 6) /* halted on error */ +#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ +#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ +#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define QTD_STS_STS (1 << 1) /* split transaction state */ +#define QTD_STS_PING (1 << 0) /* issue PING? */ + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define EHCI_LIST_END __constant_cpu_to_le32(1) /* "null pointer" to hw */ +#define QTD_NEXT(dma) cpu_to_le32((u32)dma) + +struct _phci_driver; +struct _isp1763_hcd; +#define EHCI_MAX_ROOT_PORTS 1 + +#include + +#define USBNET +#ifdef USBNET +struct isp1763_async_cleanup_urb { + struct list_head urb_list; + struct urb *urb; +}; +#endif + + +/*host controller*/ +typedef struct _phci_hcd { + + struct usb_hcd usb_hcd; + spinlock_t lock; + + /* async schedule support */ + struct ehci_qh *async; + struct ehci_qh *reclaim; + /* periodic schedule support */ + unsigned periodic_size; + int next_uframe; /* scan periodic, start here */ + int periodic_sched; /* periodic activity count */ + int periodic_more_urb; + struct usb_device *otgdev; /*otg deice, with address 2 */ + struct timer_list rh_timer; /* drives root hub */ + struct list_head dev_list; /* devices on this bus */ + struct list_head urb_list; /*iso testing */ + + /*msec break in interrupts */ + atomic_t nuofsofs; + atomic_t missedsofs; + + struct isp1763_dev *dev; + /*hw info */ + u8 *iobase; + u32 iolength; + u8 *plxiobase; + u32 plxiolength; + + int irq; /* irq allocated */ + int state; /*state of the host controller */ + unsigned long reset_done[EHCI_MAX_ROOT_PORTS]; + ehci_regs regs; + + struct _isp1763_qha qha; + struct _isp1763_qhint qhint; + struct _isp1763_isoptd isotd; + + struct tasklet_struct tasklet; + /*this timer is going to run every 20 msec */ + struct timer_list watchdog; + void (*worker_function) (struct _phci_hcd * hcd); + struct _periodic_list periodic_list[PTD_PERIODIC_SIZE]; +#ifdef USBNET + struct isp1763_async_cleanup_urb cleanup_urb; +#endif +} phci_hcd, *pphci_hcd; + +/*usb_device->hcpriv, points to this structure*/ +typedef struct hcd_dev { + struct list_head dev_list; + struct list_head urb_list; +} hcd_dev; + +#define usb_hcd_to_pehci_hcd(hcd) container_of(hcd, struct _phci_hcd, usb_hcd) + +/*td allocation*/ +#ifdef CONFIG_PHCI_MEM_SLAB + +#define qha_alloc(t,c) kmem_cache_alloc(c,ALLOC_FLAGS) +#define qha_free(c,x) kmem_cache_free(c,x) +static kmem_cache_t *qha_cache, *qh_cache, *qtd_cache; +static int +phci_hcd_mem_init(void) +{ + /* qha TDs accessed by controllers and host */ + qha_cache = kmem_cache_create("phci_ptd", sizeof(isp1763_qha), 0, + SLAB_HWCACHE_ALIGN, NULL, NULL); + if (!qha_cache) { + printk("no TD cache?"); + return -ENOMEM; + } + + /* qh TDs accessed by controllers and host */ + qh_cache = kmem_cache_create("phci_ptd", sizeof(isp1763_qha), 0, + SLAB_HWCACHE_ALIGN, NULL, NULL); + if (!qh_cache) { + printk("no TD cache?"); + return -ENOMEM; + } + + /* qtd accessed by controllers and host */ + qtd_cache = kmem_cache_create("phci_ptd", sizeof(isp1763_qha), 0, + SLAB_HWCACHE_ALIGN, NULL, NULL); + if (!qtd_cache) { + printk("no TD cache?"); + return -ENOMEM; + } + return 0; +} +static void +phci_mem_cleanup(void) +{ + if (qha_cache && kmem_cache_destroy(qha_cache)) + err("td_cache remained"); + qha_cache = 0; +} +#else + +#define qha_alloc(t,c) kmalloc(t,ALLOC_FLAGS) +#define qha_free(c,x) kfree(x) +#define qha_cache 0 + + +#ifdef CONFIG_ISO_SUPPORT +/*memory constants*/ +#define BLK_128_ 2 +#define BLK_256_ 3 +#define BLK_1024_ 1 +#define BLK_2048_ 3 +#define BLK_4096_ 3 //1 +#define BLK_8196_ 0 //1 +#define BLK_TOTAL (BLK_128_+BLK_256_ + BLK_1024_ +BLK_2048_+ BLK_4096_+BLK_8196_) + +#define BLK_SIZE_128 128 +#define BLK_SIZE_256 256 +#define BLK_SIZE_1024 1024 +#define BLK_SIZE_2048 2048 +#define BLK_SIZE_4096 4096 +#define BLK_SIZE_8192 8192 + +#define COMMON_MEMORY 1 + +#else +#define BLK_256_ 8 +#define BLK_1024_ 6 +#define BLK_4096_ 3 +#define BLK_TOTAL (BLK_256_ + BLK_1024_ + BLK_4096_) +#define BLK_SIZE_256 256 +#define BLK_SIZE_1024 1024 +#define BLK_SIZE_4096 4096 +#endif +static void phci_hcd_mem_init(void); +static inline void +phci_mem_cleanup(void) +{ + return; +} + +#endif + +#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ +#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ +#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ +/* 19:16 for port testing */ +/* 15:14 for using port indicator leds (if HCS_INDICATOR allows) */ +#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ +#define PORT_POWER (1<<12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x)&(3<<10))==(1<<10)) /* USB 1.1 device */ +/* 11:10 for detecting lowspeed devices (reset vs release ownership) */ +/* 9 reserved */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_OCC (1<<5) /* over current change */ + +#define PORT_OC (1<<4) /* over current active */ +#define PORT_PEC (1<<3) /* port enable change */ +#define PORT_PE (1<<2) /* port enable */ +#define PORT_CSC (1<<1) /* connect status change */ +#define PORT_CONNECT (1<<0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) +/*Legends, + * ATL control, bulk transfer + * INTL interrupt transfer + * ISTL iso transfer + * */ + +/*buffer(transfer) bitmaps*/ +#define ATL_BUFFER 0x1 +#define INT_BUFFER 0x2 +#define ISO_BUFFER 0x4 +#define BUFFER_MAP 0x7 + +/* buffer type for ST-ERICSSON HC */ +#define TD_PTD_BUFF_TYPE_ATL 0 /* ATL buffer */ +#define TD_PTD_BUFF_TYPE_INTL 1 /* INTL buffer */ +#define TD_PTD_BUFF_TYPE_ISTL 2 /* ISO buffer */ +#define TD_PTD_TOTAL_BUFF_TYPES (TD_PTD_BUFF_TYPE_ISTL +1) +/*maximum number of tds per transfer type*/ +#define TD_PTD_MAX_BUFF_TDS 16 + +/*invalid td index in the headers*/ +#define TD_PTD_INV_PTD_INDEX 0xFFFF +/*Host controller buffer defination*/ +#define INVALID_FRAME_NUMBER 0xFFFFFFFF +/*per td transfer size*/ +#define HC_ATL_PL_SIZE 4096 +#define HC_ISTL_PL_SIZE 1024 +#define HC_INTL_PL_SIZE 1024 + +/*TD_PTD_MAP states*/ +#define TD_PTD_NEW 0x0000 +#define TD_PTD_ACTIVE 0x0001 +#define TD_PTD_IDLE 0x0002 +#define TD_PTD_REMOVE 0x0004 +#define TD_PTD_RELOAD 0x0008 +#define TD_PTD_IN_SCHEDULE 0x0010 +#define TD_PTD_DONE 0x0020 + +#define PTD_RETRY(x) (((x) >> 23) & 0x3) +#define PTD_PID(x) (((x) >> 10) & (0x3)) +#define PTD_NEXTTOGGLE(x) (((x) >> 25) & (0x1)) +#define PTD_XFERRED_LENGTH(x) ((x) & 0x7fff) +#define PTD_XFERRED_NONHSLENGTH(x) ((x) & 0x7ff) +#define PTD_PING_STATE(x) (((x) >> 26) & (0x1)) + +/* urb state*/ +#define DELETE_URB 0x0008 +#define NO_TRANSFER_ACTIVE 0xFFFF +#define NO_TRANSFER_DONE 0x0000 +#define MAX_PTD_BUFFER_SIZE 4096 /*max ptd size */ + +/*information of the td in headers of host memory*/ +typedef struct td_ptd_map { + u32 state; /* ACTIVE, NEW, TO_BE_REMOVED */ + u8 datatoggle; /*to preserve the data toggle for ATL/ISTL transfers */ + u32 ptd_bitmap; /* Bitmap of this ptd in HC headers */ + u32 ptd_header_addr; /* headers address of this td */ + u32 ptd_data_addr; /*data address of this td to write in and read from */ + /*this is address is actual RAM address not the CPU address + * RAM address = (CPU ADDRESS-0x400) >> 3 + * */ + u32 ptd_ram_data_addr; + u8 lasttd; /*last td , complete the transfer */ + struct ehci_qh *qh; /* endpoint */ + struct ehci_qtd *qtd; /* qtds for this endpoint */ + struct ehci_itd *itd; /*itd pointer */ + struct ehci_sitd *sitd; /*itd pointer */ + /*iso specific only */ + u32 grouptdmap; /*if td need to complete with error, then process all the tds + in the groupmap */ +} td_ptd_map_t; + +/*buffer(ATL/ISTL/INTL) managemnet*/ +typedef struct td_ptd_map_buff { + u8 buffer_type; /* Buffer type: BUFF_TYPE_ATL/INTL/ISTL0/ISTL1 */ + u8 active_ptds; /* number of active td's in the buffer */ + u8 total_ptds; /* Total number of td's present in the buffer (active + tobe removed + skip) */ + u8 max_ptds; /* Maximum number of ptd's(32) this buffer can withstand */ + u16 active_ptd_bitmap; /* Active PTD's bitmap */ + u16 pending_ptd_bitmap; /* skip PTD's bitmap */ + td_ptd_map_t map_list[TD_PTD_MAX_BUFF_TDS]; /* td_ptd_map list */ +} td_ptd_map_buff_t; + + +#define USB_HCD_MAJOR 0 +#define USB_HCD_MODULE_NAME "isp1763hcd" +/* static char devpath[] = "/dev/isp1763hcd"; */ + +#define HCD_IOC_MAGIC 'h' + +#define HCD_IOC_POWERDOWN _IO(HCD_IOC_MAGIC, 1) +#define HCD_IOC_POWERUP _IO(HCD_IOC_MAGIC, 2) +#define HCD_IOC_TESTSE0_NACK _IO(HCD_IOC_MAGIC, 3) +#define HCD_IOC_TEST_J _IO(HCD_IOC_MAGIC,4) +#define HCD_IOC_TEST_K _IO(HCD_IOC_MAGIC,5) +#define HCD_IOC_TEST_TESTPACKET _IO(HCD_IOC_MAGIC,6) +#define HCD_IOC_TEST_FORCE_ENABLE _IO(HCD_IOC_MAGIC,7) +#define HCD_IOC_TEST_SUSPEND_RESUME _IO(HCD_IOC_MAGIC,8) +#define HCD_IOC_TEST_SINGLE_STEP_GET_DEV_DESC _IO(HCD_IOC_MAGIC,9) +#define HCD_IOC_TEST_SINGLE_STEP_SET_FEATURE _IO(HCD_IOC_MAGIC,10) +#define HCD_IOC_TEST_STOP _IO(HCD_IOC_MAGIC,11) +#define HCD_IOC_SUSPEND_BUS _IO(HCD_IOC_MAGIC,12) +#define HCD_IOC_RESUME_BUS _IO(HCD_IOC_MAGIC,13) +#define HCD_IOC_REMOTEWAKEUP_BUS _IO(HCD_IOC_MAGIC,14) + +#define HOST_COMPILANCE_TEST_ENABLE 1 +#define HOST_COMP_TEST_SE0_NAK 1 +#define HOST_COMP_TEST_J 2 +#define HOST_COMP_TEST_K 3 +#define HOST_COMP_TEST_PACKET 4 +#define HOST_COMP_TEST_FORCE_ENABLE 5 +#define HOST_COMP_HS_HOST_PORT_SUSPEND_RESUME 6 +#define HOST_COMP_SINGLE_STEP_GET_DEV_DESC 7 +#define HOST_COMP_SINGLE_STEP_SET_FEATURE 8 + +#endif diff --git a/drivers/usb/host/pehci/host/qtdptd.c b/drivers/usb/host/pehci/host/qtdptd.c new file mode 100644 index 00000000000..093800ee301 --- /dev/null +++ b/drivers/usb/host/pehci/host/qtdptd.c @@ -0,0 +1,1315 @@ +/* +* Copyright (C) ST-Ericsson AP Pte Ltd 2010 +* +* ISP1763 Linux OTG Controller driver : host +* +* This program is free software; you can redistribute it and/or modify it under the terms of +* the GNU General Public License as published by the Free Software Foundation; version +* 2 of the License. +* +* This program is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* This is a host controller driver file. QTD processing is handled here. +* +* Author : wired support +* +*/ + + +/* Td managenment routines */ + +#define QUEUE_HEAD_NOT_EMPTY 0x001 + + +/*free the location used by removed urb/endpoint*/ +static void +phci_hcd_release_td_ptd_index(struct ehci_qh *qh) +{ + td_ptd_map_buff_t *td_ptd_buff = &td_ptd_map_buff[qh->type]; + td_ptd_map_t *td_ptd_map = &td_ptd_buff->map_list[qh->qtd_ptd_index]; + pehci_entry("++ %s: Entered\n", __FUNCTION__); + /*hold the global lock here */ + td_ptd_map->state = TD_PTD_NEW; + qh->qh_state = QH_STATE_IDLE; + /* + set these values to NULL as schedule + is based on these values, + rather td_ptd_map state + */ + td_ptd_map->qh = NULL; + td_ptd_map->qtd = NULL; + + td_ptd_buff->active_ptd_bitmap &= ~td_ptd_map->ptd_bitmap; + + /* Only pending transfers on current QH must be cleared */ + td_ptd_buff->pending_ptd_bitmap &= ~td_ptd_map->ptd_bitmap; + + pehci_entry("-- %s: Exit\n", __FUNCTION__); + +} + +/*print ehciqtd*/ +static void +print_ehci_qtd(struct ehci_qtd *qtd) +{ + pehci_print("hwnext 0x%08x, altnext 0x%08x,token 0x%08x, length %d\n", + qtd->hw_next, qtd->hw_alt_next, + le32_to_cpu(qtd->hw_token), qtd->length); + + pehci_print("buf[0] 0x%08x\n", qtd->hw_buf[0]); + +} + +/*delete all qtds linked with this urb*/ +static void +phci_hcd_qtd_list_free(phci_hcd * ehci, + struct urb *urb, struct list_head *qtd_list) +{ + struct list_head *entry, *temp; + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + + list_for_each_safe(entry, temp, qtd_list) { + struct ehci_qtd *qtd; + qtd = list_entry(entry, struct ehci_qtd, qtd_list); + if(!list_empty(&qtd->qtd_list)) + list_del_init(&qtd->qtd_list); + qha_free(qha_cache, qtd); + } + + pehci_entry("-- %s: Exit \n", __FUNCTION__); +} + + +/* + * free all the qtds for this transfer, also + * free the Host memory to be reused + */ +static void +phci_hcd_urb_free_priv(phci_hcd * hcd, + urb_priv_t * urb_priv_to_remove, struct ehci_qh *qh) +{ + int i = 0; + struct ehci_qtd *qtd; + for (i = 0; i < urb_priv_to_remove->length; i++) { + if (urb_priv_to_remove->qtd[i]) { + qtd = urb_priv_to_remove->qtd[i]; + + if(!list_empty(&qtd->qtd_list)) + list_del_init(&qtd->qtd_list); + + /* This is required when the device is abruptly disconnected and the + * PTDs are not completely processed + */ + if (qtd->length) + phci_hcd_mem_free(&qtd->mem_addr); + + qha_free(qha_cache, qtd); + urb_priv_to_remove->qtd[i] = 0; + qtd = 0; + } + + } + + return; +} + + +/*allocate the qtd*/ +struct ehci_qtd * +phci_hcd_qtd_allocate(int mem_flags) +{ + + struct ehci_qtd *qtd = 0; + qtd = kmalloc(sizeof *qtd, mem_flags); + if (!qtd) + { + return 0; + } + + memset(qtd, 0, sizeof *qtd); + qtd->qtd_dma = cpu_to_le32(qtd); + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + qtd->state = QTD_STATE_NEW; + INIT_LIST_HEAD(&qtd->qtd_list); + return qtd; +} + +/* + * calculates host memory for current length transfer td, + * maximum td length is 4K(custom made) + * */ +static int +phci_hcd_qtd_fill(struct urb *urb, + struct ehci_qtd *qtd, + dma_addr_t buf, size_t len, int token, int *status) +{ + int count = 0; + + qtd->hw_buf[0] = (u32) buf; + /*max lenggth is HC_ATL_PL_SIZE */ + if (len > HC_ATL_PL_SIZE) { + count = HC_ATL_PL_SIZE; + } else { + count = len; + } + qtd->hw_token = cpu_to_le32((count << 16) | token); + qtd->length = count; + + pehci_print("%s:qtd %p, token %8x bytes %d dma %x\n", + __FUNCTION__, qtd, le32_to_cpu(qtd->hw_token), count, + qtd->hw_buf[0]); + + return count; +} + + +/* + * makes number of qtds required for + * interrupt/bulk/control transfer length + * and initilize qtds + * */ +struct list_head * +phci_hcd_make_qtd(phci_hcd * hcd, + struct list_head *head, struct urb *urb, int *status) +{ + + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf, map_buf; + int len, maxpacket; + int is_input; + u32 token; + int cnt = 0; + urb_priv_t *urb_priv = (urb_priv_t *) urb->hcpriv; + + pehci_entry("++ %s, Entered\n", __FUNCTION__); + + /*take the qtd from already allocated + structure from hcd_submit_urb + */ + qtd = urb_priv->qtd[cnt]; + if (unlikely(!qtd)) { + *status = -ENOMEM; + return 0; + } + + qtd_prev = 0; + list_add_tail(&qtd->qtd_list, head); + + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + + len = urb->transfer_buffer_length; + + is_input = usb_pipein(urb->pipe); + + if (usb_pipecontrol(urb->pipe)) { + /* SETUP pid */ + if (phci_hcd_qtd_fill(urb, qtd, cpu_to_le32(urb->setup_packet), + sizeof(struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), + status) < 0) { + goto cleanup; + } + + cnt++; /* increment the index */ + print_ehci_qtd(qtd); + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = urb_priv->qtd[cnt]; + if (unlikely(!qtd)) { + *status = -ENOMEM; + goto cleanup; + } + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + } + + /* + * data transfer stage: buffer setup + */ + len = urb->transfer_buffer_length; + if (likely(len > 0)) { + /*update the buffer address */ + buf = cpu_to_le32(urb->transfer_buffer); + } else { + buf = map_buf = cpu_to_le32(0); /*set-up stage has no data. */ + } + + /* So are we waiting for the ack only or there is a data stage with out. */ + if (!buf || usb_pipein(urb->pipe)) { + token |= (1 /* "in" */ << 8); + } + /* else it's already initted to "out" pid (0 << 8) */ + maxpacket = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)) & 0x07ff; + + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + + for (;;) { + int this_qtd_len; + this_qtd_len = + phci_hcd_qtd_fill(urb, qtd, buf, len, token, status); + if (this_qtd_len < 0) + goto cleanup; + print_ehci_qtd(qtd); + len -= this_qtd_len; + buf += this_qtd_len; + cnt++; + /* qh makes control packets use qtd toggle; maybe switch it */ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) { + token ^= QTD_TOGGLE; + } + + if (likely(len <= 0)) { + break; + } + qtd_prev = qtd; + qtd = urb_priv->qtd[cnt]; + if (unlikely(!qtd)) { + goto cleanup; + } + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + } + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (likely(buf != 0)) { + int one_more = 0; + if (usb_pipecontrol(urb->pipe)) { + one_more = 1; + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + + } else if (usb_pipebulk(urb->pipe) /* bulk data exactly terminated on zero lenth */ + &&(urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = urb_priv->qtd[cnt]; + if (unlikely(!qtd)) { + goto cleanup; + } + + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + phci_hcd_qtd_fill(urb, qtd, 0, 0, token, status); + print_ehci_qtd(qtd); + cnt++; + } + } + + /*this is our last td for current transfer */ + qtd->state |= QTD_STATE_LAST; + + /*number of tds */ + if (urb_priv->length != cnt) { + err("Never Error: number of tds allocated %d exceeding %d\n", + urb_priv->length, cnt); + } + /* by default, enable interrupt on urb completion */ + if (likely(!(urb->transfer_flags & URB_NO_INTERRUPT))) { + qtd->hw_token |= __constant_cpu_to_le32(QTD_IOC); + } + + pehci_entry("-- %s, Exit\n", __FUNCTION__); + return head; + + cleanup: + phci_hcd_qtd_list_free(hcd, urb, head); + return 0; +} + +/*allocates a queue head(endpoint*/ +struct ehci_qh * +phci_hcd_qh_alloc(phci_hcd * hcd) +{ + + struct ehci_qh *qh = kmalloc(sizeof(struct ehci_qh), GFP_ATOMIC); + if (!qh) + { + return qh; + } + + memset(qh, 0, sizeof *qh); + atomic_set(&qh->refcount, 1); + init_waitqueue_head(&qh->waitforcomplete); + qh->qh_dma = (u32) qh; + INIT_LIST_HEAD(&qh->qtd_list); + INIT_LIST_HEAD(&qh->itd_list); + qh->next_uframe = -1; + return qh; +} + +/* calculates header address for the tds*/ +static int +phci_hcd_fill_ptd_addresses(td_ptd_map_t * td_ptd_map, int index, int bufftype) +{ + int i = 0; + unsigned long tdlocation = 0; + /* + * the below payloadlocation and + * payloadsize are redundant + * */ + unsigned long payloadlocation = 0; + unsigned long payloadsize = 0; + pehci_entry("++ %s: enter\n", __FUNCTION__); + switch (bufftype) { + /*atl header starts at 0xc00 */ + case TD_PTD_BUFF_TYPE_ATL: + tdlocation = 0x0c00; + /*redundant */ + payloadsize = 0x1000; + payloadlocation = 0x1000; + break; + case TD_PTD_BUFF_TYPE_INTL: + /*interrupt header + * starts at 0x800 + * */ + tdlocation = 0x0800; + /*redundant */ + payloadlocation = 0x1000; + payloadsize = 0x1000; + break; + + case TD_PTD_BUFF_TYPE_ISTL: + /*iso header starts + * at 0x400*/ + + tdlocation = 0x0400; + /*redunndant */ + payloadlocation = 0x1000; + payloadsize = 0x1000; + + break; + } + + + i = index; + payloadlocation += (i) * payloadsize; /*each payload is of 4096 bytes */ + tdlocation += (i) * PHCI_QHA_LENGTH; /*each td is of 32 bytes */ + td_ptd_map->ptd_header_addr = tdlocation; + td_ptd_map->ptd_data_addr = payloadlocation; + td_ptd_map->ptd_ram_data_addr = ((payloadlocation - 0x0400) >> 3); + pehci_print + ("Index: %d, Header: 0x%08x, Payload: 0x%08x,Data start address: 0x%08x\n", + index, td_ptd_map->ptd_header_addr, td_ptd_map->ptd_data_addr, + td_ptd_map->ptd_ram_data_addr); + pehci_entry("-- %s: Exit", __FUNCTION__); + return payloadlocation; +} + + +/*--------------------------------------------------------------* + * calculate the header location for the current + * endpoint, if found returns a valid index + * else invalid + -----------------------------------------------------------*/ +static void +phci_hcd_get_qtd_ptd_index(struct ehci_qh *qh, + struct ehci_qtd *qtd, struct ehci_itd *itd) +{ + u8 buff_type = td_ptd_pipe_x_buff_type[qh->type]; + u8 qtd_ptd_index; /*, index; */ + /*this is the location of the ptd's skip map/done map, also + calculating the td header, payload, data start address + location */ + u8 bitmap = 0x1; + u8 max_ptds; + + td_ptd_map_buff_t *ptd_map_buff = &(td_ptd_map_buff[buff_type]); + pehci_entry("++ %s, Entered, buffer type %d\n", __FUNCTION__, + buff_type); + + /* ATL PTDs can wait */ + max_ptds = (buff_type == TD_PTD_BUFF_TYPE_ATL) + ? TD_PTD_MAX_BUFF_TDS : ptd_map_buff->max_ptds; + + for (qtd_ptd_index = 0; qtd_ptd_index < max_ptds; qtd_ptd_index++) { /* Find the first free slot */ + if (ptd_map_buff->map_list[qtd_ptd_index].state == TD_PTD_NEW) { + /* Found a free slot */ + if (qh->qtd_ptd_index == TD_PTD_INV_PTD_INDEX) { + qh->qtd_ptd_index = qtd_ptd_index; + } + ptd_map_buff->map_list[qtd_ptd_index].datatoggle = 0; + /*put the ptd_index into operational state */ + ptd_map_buff->map_list[qtd_ptd_index].state = + TD_PTD_ACTIVE; + ptd_map_buff->map_list[qtd_ptd_index].qtd = qtd; + /* No td transfer is in progress */ + ptd_map_buff->map_list[qtd_ptd_index].itd = itd; + /*initialize endpoint(queuehead) */ + ptd_map_buff->map_list[qtd_ptd_index].qh = qh; + ptd_map_buff->map_list[qtd_ptd_index].ptd_bitmap = + bitmap << qtd_ptd_index; + phci_hcd_fill_ptd_addresses(&ptd_map_buff-> + map_list[qtd_ptd_index], + qh->qtd_ptd_index, + buff_type); + ptd_map_buff->map_list[qtd_ptd_index].lasttd = 0; + ptd_map_buff->total_ptds++; /* update # of total td's */ + /*make the queuehead map, to process in the phci_schedule_ptds */ + ptd_map_buff->active_ptd_bitmap |= + (bitmap << qtd_ptd_index); + break; + } + } + pehci_entry("-- %s, Exit\n", __FUNCTION__); + return; + +} /* phci_get_td_ptd_index */ + + + +/* + * calculate the header location for the endpoint and + * all tds on this endpoint will use the same + * header location for all transfers on this endpoint. + * also puts the endpoint into the linked state + * */ +static void +phci_hcd_qh_link_async(phci_hcd * hcd, struct ehci_qh *qh, int *status) +{ + struct ehci_qtd *qtd = 0; + struct list_head *qtd_list = &qh->qtd_list; + +#ifdef MSEC_INT_BASED + td_ptd_map_buff_t *ptd_map_buff; + td_ptd_map_t *td_ptd_map; +#endif + + /* take the first td, in case we are not able to schedule the new td + and this is going for remove + */ + qtd = list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + + /* Assign a td-ptd index for this ed so that we can put ptd's in the HC buffers */ + + qh->qtd_ptd_index = TD_PTD_INV_PTD_INDEX; + phci_hcd_get_qtd_ptd_index(qh, qtd, NULL); /* Get a td-ptd index */ + if (qh->qtd_ptd_index == TD_PTD_INV_PTD_INDEX) { + err("can not find the location in our buffer\n"); + *status = -ENOSPC; + return; + } +#ifdef MSEC_INT_BASED + /*first transfers in sof interrupt goes into pending */ + ptd_map_buff = &(td_ptd_map_buff[qh->type]); + td_ptd_map = &ptd_map_buff->map_list[qh->qtd_ptd_index]; + ptd_map_buff->pending_ptd_bitmap |= td_ptd_map->ptd_bitmap; + +#endif + /* open the halt so that it acessed */ + qh->hw_token &= ~__constant_cpu_to_le32(QTD_STS_HALT); + qh->qh_state = QH_STATE_LINKED; + qh->qh_state |= QH_STATE_TAKE_NEXT; + pehci_entry("-- %s: Exit , qh %p\n", __FUNCTION__, qh); + + +} + +/*-------------------------------------------------------------------------*/ + +/* + * mainly used for setting up current td on current + * endpoint(queuehead), endpoint may be new or + * halted one + * */ + +static inline void +phci_hcd_qh_update(phci_hcd * ehci, struct ehci_qh *qh, struct ehci_qtd *qtd) +{ + /*make this current td */ + qh->hw_current = QTD_NEXT(qtd->qtd_dma); + qh->hw_qtd_next = QTD_NEXT(qtd->qtd_dma); + qh->hw_alt_next = EHCI_LIST_END; + /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ + wmb(); + qh->hw_token &= __constant_cpu_to_le32(QTD_TOGGLE | QTD_STS_PING); +} + +/* + * used for ATL, INT transfers + * function creates new endpoint, + * calculates bandwidth for interrupt transfers, + * and initialize the qh based on endpoint type/speed + * */ +struct ehci_qh * +phci_hcd_make_qh(phci_hcd * hcd, + struct urb *urb, struct list_head *qtd_list, int *status) +{ + struct ehci_qh *qh = 0; + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + int mult = 0; + int bustime = 0; + struct ehci_qtd *qtd = + list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + + qh = phci_hcd_qh_alloc(hcd); + if (!qh) { + *status = -ENOMEM; + return 0; + } + + /* + * init endpoint/device data for this QH + */ + info1 |= usb_pipeendpoint(urb->pipe) << 8; + info1 |= usb_pipedevice(urb->pipe) << 0; + + is_input = usb_pipein(urb->pipe); + type = usb_pipetype(urb->pipe); + maxp = usb_maxpacket(urb->dev, urb->pipe, !is_input); + mult = 1 + ((maxp >> 11) & 0x3); + + /*set this queueheads index to invalid */ + qh->qtd_ptd_index = TD_PTD_INV_PTD_INDEX; + + switch (type) { + case PIPE_CONTROL: + case PIPE_BULK: + qh->type = TD_PTD_BUFF_TYPE_ATL; + break; + + case PIPE_INTERRUPT: + qh->type = TD_PTD_BUFF_TYPE_INTL; + break; + case PIPE_ISOCHRONOUS: + qh->type = TD_PTD_BUFF_TYPE_ISTL; + break; + + } + + + + if (type == PIPE_INTERRUPT) { + /*for this interrupt transfer check how much bustime in usecs required */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + bustime = usb_check_bandwidth(urb->dev, urb); + + if (bustime < 0) { + *status = -ENOSPC; + goto done; + } + + usb_claim_bandwidth(urb->dev, urb, bustime, + usb_pipeisoc(urb->pipe)); +#else +#endif + qh->usecs = bustime; + + qh->start = NO_FRAME; + + if (urb->dev->speed == USB_SPEED_HIGH) { + qh->c_usecs = 0; + qh->gap_uf = 0; + /*after how many uframes this interrupt is to be executed */ + qh->period = urb->interval >> 3; + if (qh->period < 1) { + printk("intr period %d uframes,\n", + urb->interval); + } + /*restore the original urb->interval in qh->period */ + qh->period = urb->interval; + + } else { + /* gap is f(FS/LS transfer times) */ + qh->gap_uf = 1 + 7; /*usb_calc_bus_time (urb->dev->speed, + is_input, 0, maxp) / (125 * 1000); */ + + if (is_input) { /* SPLIT, gap, CSPLIT+DATA */ + + qh->c_usecs = qh->usecs + 1; /*HS_USECS (0); */ + qh->usecs = 10; /*HS_USECS (1); */ + } else { /* SPLIT+DATA, gap, CSPLIT */ + qh->usecs += 10; /*HS_USECS (1); */ + qh->c_usecs = 1; /*HS_USECS (0); */ + } + + + /*take the period ss/cs scheduling will be + handled by submit urb + */ + qh->period = urb->interval; + } + } + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= (1 << 12); /* EPS "low" */ + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + if (type != PIPE_INTERRUPT) { + info1 |= (EHCI_TUNE_RL_TT << 28); + } + if (type == PIPE_CONTROL) { + info1 |= (1 << 27); /* for TT */ + info1 |= 1 << 14; /* toggle from qtd */ + } + info1 |= maxp << 16; + + info2 |= (EHCI_TUNE_MULT_TT << 30); + info2 |= urb->dev->ttport << 23; + info2 |= urb->dev->tt->hub->devnum << 16; + break; + + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= (2 << 12); /* EPS "high" */ + if (type == PIPE_CONTROL) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + + info1 |= 1 << 14; /* toggle from qtd */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else if (type == PIPE_BULK) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 512 << 16; /* usb2 fixed maxpacket */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= (maxp & 0x7ff) /*max_packet (maxp) */ <<16; + info2 |= mult /*hb_mult (maxp) */ << 30; + } + break; + + default: + pehci_print("bogus dev %p speed %d", urb->dev, urb->dev->speed); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + done: +#else +#endif + qha_free(qha_cache, qh); + return 0; + } /*end of switch */ + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ + + /* init as halted, toggle clear, advance to dummy */ + qh->qh_state = QH_STATE_IDLE; + qh->hw_info1 = cpu_to_le32(info1); + qh->hw_info2 = cpu_to_le32(info2); + /*link the tds here */ + list_splice(qtd_list, &qh->qtd_list); + phci_hcd_qh_update(hcd, qh, qtd); + qh->hw_token = cpu_to_le32(QTD_STS_HALT); + if (!usb_pipecontrol(urb->pipe)) { + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), !is_input, + 1); + } + pehci_entry("-- %s: Exit, qh %p\n", __FUNCTION__, qh); + return qh; +} + + +/*-----------------------------------------------------------*/ +/* + * Hardware maintains data toggle (like OHCI) ... here we (re)initialize + * the hardware data toggle in the QH, and set the pseudo-toggle in udev + * so we can see if usb_clear_halt() was called. NOP for control, since + * we set up qh->hw_info1 to always use the QTD toggle bits. + */ +static inline void +phci_hcd_clear_toggle(struct usb_device *udev, int ep, int is_out, + struct ehci_qh *qh) +{ + pehci_print("clear toggle, dev %d ep 0x%x-%s\n", + udev->devnum, ep, is_out ? "out" : "in"); + qh->hw_token &= ~__constant_cpu_to_le32(QTD_TOGGLE); + usb_settoggle(udev, ep, is_out, 1); +} + +/*-------------------------------------------------------------------------*/ + +/* + * For control/bulk/interrupt, return QH with these TDs appended. + * Allocates and initializes the QH if necessary. + * Returns null if it can't allocate a QH it needs to. + * If the QH has TDs (urbs) already, that's great. + */ +struct ehci_qh * +phci_hcd_qh_append_tds(phci_hcd * hcd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + struct usb_host_endpoint *ep, +#else +#endif + struct urb *urb, struct list_head *qtd_list, + void **ptr, int *status) +{ + + int epnum; + + struct ehci_qh *qh = 0; + struct ehci_qtd *qtd = + list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + td_ptd_map_buff_t *ptd_map_buff; + td_ptd_map_t *td_ptd_map; + + + + pehci_entry("++ %s: Entered\n", __FUNCTION__); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + epnum = ep->desc.bEndpointAddress; +#else + epnum = urb->ep->desc.bEndpointAddress; +#endif + + qh = (struct ehci_qh *) *ptr; + if (likely(qh != 0)) { + u32 hw_next = QTD_NEXT(qtd->qtd_dma); + pehci_print("%Queue head already %p\n", qh); + + ptd_map_buff = &(td_ptd_map_buff[qh->type]); + td_ptd_map = &ptd_map_buff->map_list[qh->qtd_ptd_index]; + + /* maybe patch the qh used for set_address */ + if (unlikely + (epnum == 0 && le32_to_cpu(qh->hw_info1 & 0x7f) == 0)) { + qh->hw_info1 |= cpu_to_le32(usb_pipedevice(urb->pipe)); + } + + /* is an URB is queued to this qh already? */ + if (unlikely(!list_empty(&qh->qtd_list))) { + struct ehci_qtd *last_qtd; + /* update the last qtd's "next" pointer */ + last_qtd = list_entry(qh->qtd_list.prev, + struct ehci_qtd, qtd_list); + + /*queue head is not empty just add the + td at the end of it , and return from here + */ + last_qtd->hw_next = hw_next; + + /*set the status as positive */ + *status = (u32) QUEUE_HEAD_NOT_EMPTY; + + /* no URB queued */ + } else { + + // qh->qh_state = QH_STATE_IDLE; + + + /* usb_clear_halt() means qh data toggle gets reset */ + if (usb_pipebulk(urb->pipe) + && unlikely(!usb_gettoggle(urb->dev, (epnum & 0x0f), + !(epnum & 0x80)))) { + + phci_hcd_clear_toggle(urb->dev, + epnum & 0x0f, + !(epnum & 0x80), qh); + + /*reset our data toggle */ + + qh->datatoggle = 0; + qh->ping = 0; + + } + phci_hcd_qh_update(hcd, qh, qtd); + } + /*put everything in pedning, will be cleared during scheduling */ + ptd_map_buff->pending_ptd_bitmap |= td_ptd_map->ptd_bitmap; + list_splice(qtd_list, qh->qtd_list.prev); + } else { + qh = phci_hcd_make_qh(hcd, urb, qtd_list, status); + *ptr = qh; + } + pehci_entry("-- %s: Exit qh %p\n", __FUNCTION__, qh); + return qh; +} + +/*link qtds to endpoint(qh)*/ +struct ehci_qh * +phci_hcd_submit_async(phci_hcd * hcd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + struct usb_host_endpoint *ep, +#else +#endif + struct list_head *qtd_list, struct urb *urb, int *status) +{ + struct ehci_qtd *qtd; + struct hcd_dev *dev; + int epnum; + +#ifndef THREAD_BASED + unsigned long flags; +#endif + + + struct ehci_qh *qh = 0; + + urb_priv_t *urb_priv = urb->hcpriv; + + qtd = list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + dev = (struct hcd_dev *) urb->hcpriv; + epnum = usb_pipeendpoint(urb->pipe); + if (usb_pipein(urb->pipe) && !usb_pipecontrol(urb->pipe)) { + epnum |= 0x10; + } + + pehci_entry("++ %s, enter\n", __FUNCTION__); + + /* ehci_hcd->lock guards shared data against other CPUs: + * ehci_hcd: async, reclaim, periodic (and shadow), ... + * hcd_dev: ep[] + + * ehci_qh: qh_next, qtd_list + + * ehci_qtd: qtd_list + * + * Also, hold this lock when talking to HC registers or + * when updating hw_* fields in shared qh/qtd/... structures. + */ +#ifndef THREAD_BASED + spin_lock_irqsave(&hcd->lock, flags); +#endif + + spin_lock(&hcd_data_lock); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + usb_hcd_link_urb_to_ep(&hcd->usb_hcd, urb); +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + qh = phci_hcd_qh_append_tds(hcd, ep, urb, qtd_list, &ep->hcpriv, + status); +#else + qh = phci_hcd_qh_append_tds(hcd, urb, qtd_list, &urb->ep->hcpriv, + status); +#endif + if (!qh || *status < 0) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) + usb_hcd_unlink_urb_from_ep(&hcd->usb_hcd, urb); +#endif + goto cleanup; + } + /* Control/bulk operations through TTs don't need scheduling, + * the HC and TT handle it when the TT has a buffer ready. + */ + + /* now the quehead can not be in the unlink state */ + +// printk("qh->qh_state:0x%x \n",qh->qh_state); + if (qh->qh_state == QH_STATE_UNLINK) { + pehci_info("%s: free the urb,qh->state %x\n", __FUNCTION__, + qh->qh_state); + phci_hcd_qtd_list_free(hcd, urb, &qh->qtd_list); + spin_unlock(&hcd_data_lock); + +#ifndef THREAD_BASED + spin_unlock_irqrestore(&hcd->lock, flags); +#endif + *status = -ENODEV; + return 0; + } + + if (likely(qh != 0)) { + urb_priv->qh = qh; + if (likely(qh->qh_state == QH_STATE_IDLE)) + phci_hcd_qh_link_async(hcd, qh, status); + } + + cleanup: + spin_unlock(&hcd_data_lock); + +#ifndef THREAD_BASED + /* free it from lock systme can sleep now */ + spin_unlock_irqrestore(&hcd->lock, flags); +#endif + + /* could not get the QH terminate and clean. */ + if (unlikely(qh == 0) || *status < 0) { + phci_hcd_qtd_list_free(hcd, urb, qtd_list); + return qh; + } + return qh; +} + +/* + * initilaize the s-mask c-mask for + * interrupt transfers. + */ +static int +phci_hcd_qhint_schedule(phci_hcd * hcd, + struct ehci_qh *qh, + struct ehci_qtd *qtd, + struct _isp1763_qhint *qha, struct urb *urb) +{ + int i = 0; + u32 td_info3 = 0; + u32 td_info5 = 0; + u32 period = 0; + u32 usofmask = 1; + u32 usof = 0; + u32 ssplit = 0, csplit = 0xFF; + int maxpacket; + u32 numberofusofs = 0; + + /*and since whol msec frame is empty, i can schedule in any uframe */ + maxpacket = usb_maxpacket(urb->dev, urb->pipe, !usb_pipein(urb->pipe)); + maxpacket &= 0x7ff; + /*length of the data per uframe */ + maxpacket = XFER_PER_UFRAME(qha->td_info1) * maxpacket; + + /*caculate the number of uframes are required */ + numberofusofs = urb->transfer_buffer_length / maxpacket; + /*if something left */ + if (urb->transfer_buffer_length % maxpacket) { + numberofusofs += 1; + } + + for (i = 0; i < numberofusofs; i++) { + usofmask <<= i; + usof |= usofmask; + + } + + /* + for full/low speed devices, as we + have seperate location for all the endpoints + let the start split goto the first uframe, means 0 uframe + */ + if (urb->dev->speed != USB_SPEED_HIGH && usb_pipeint(urb->pipe)) { + /*set the complete splits */ + /*set all the bits and lets see whats happening */ + /*but this will be set based on the maximum packet size */ + ssplit = usof; + /* need to fix it */ + csplit = 0x1C; + qha->td_info6 = csplit; + period = qh->period; + if (period >= 32) { + period = qh->period / 2; + } + td_info3 = period; + goto done; + + } else { + if (qh->period >= 8) { + period = qh->period / 8; + } else { + period = qh->period; + } + } + /*our limitaion is maximum of 32 ie 31, 5 bits */ + if (period >= 32) { + period = 32; + /*devide by 2 */ + period >>= 1; + } + if (qh->period >= 8) { + /*millisecond period */ + td_info3 = (period << 3); + } else { + /*usof based tranmsfers */ + /*minimum 4 usofs */ + td_info3 = period; + usof = 0x11; + } + + done: + td_info5 = usof; + qha->td_info3 |= td_info3; + qha->td_info5 |= usof; + return numberofusofs; +} + +/*link interrupts qtds to endpoint*/ +struct ehci_qh * +phci_hcd_submit_interrupt(phci_hcd * hcd, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + struct usb_host_endpoint *ep, +#else +#endif + struct list_head *qtd_list, + struct urb *urb, int *status) +{ + struct ehci_qtd *qtd; + struct _hcd_dev *dev; + int epnum; + unsigned long flags; + struct ehci_qh *qh = 0; + urb_priv_t *urb_priv = (urb_priv_t *) urb->hcpriv; + + qtd = list_entry(qtd_list->next, struct ehci_qtd, qtd_list); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + dev = (struct hcd_dev *) urb->hcpriv; + epnum = ep->desc.bEndpointAddress; + + pehci_entry("++ %s, enter\n", __FUNCTION__); + + + /*check for more than one urb queued for this endpoint */ + qh = ep->hcpriv; +#else + dev = (struct _hcd_dev *) (urb->hcpriv); + epnum = urb->ep->desc.bEndpointAddress; + + pehci_entry("++ %s, enter\n", __FUNCTION__); + + + /*check for more than one urb queued for this endpoint */ + qh = (struct ehci_qh *) urb->ep->hcpriv; +#endif + + spin_lock_irqsave(&hcd->lock, flags); + if (unlikely(qh != 0)) { + if (!list_empty(&qh->qtd_list)) { + *status = -EBUSY; + goto done; + } else { + td_ptd_map_buff_t *ptd_map_buff; + td_ptd_map_t *td_ptd_map; + ptd_map_buff = &(td_ptd_map_buff[qh->type]); + td_ptd_map = &ptd_map_buff->map_list[qh->qtd_ptd_index]; + ptd_map_buff->pending_ptd_bitmap |= + td_ptd_map->ptd_bitmap; + /*NEW*/ td_ptd_map->qtd = qtd; + /* maybe reset hardware's data toggle in the qh */ + if (unlikely(!usb_gettoggle(urb->dev, epnum & 0x0f, + !(epnum & 0x80)))) { + + /*reset our data toggle */ + td_ptd_map->datatoggle = 0; + usb_settoggle(urb->dev, epnum & 0x0f, + !(epnum & 0x80), 1); + qh->datatoggle = 0; + } + /* trust the QH was set up as interrupt ... */ + list_splice(qtd_list, &qh->qtd_list); + } + } + + + if (!qh) { + qh = phci_hcd_make_qh(hcd, urb, qtd_list, status); + if (likely(qh == 0)) { + *status = -ENOMEM; + goto done; + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + ep->hcpriv = qh; +#else + urb->ep->hcpriv = qh; +#endif + } + + if (likely(qh != 0)) { + urb_priv->qh = qh; + if (likely(qh->qh_state == QH_STATE_IDLE)) { + phci_hcd_qh_link_async(hcd, qh, status); + } + } + + + done: + /* free it from lock systme can sleep now */ + spin_unlock_irqrestore(&hcd->lock, flags); + /* could not get the QH terminate and clean. */ + if (unlikely(qh == 0) || *status < 0) { + phci_hcd_qtd_list_free(hcd, urb, qtd_list); + return qh; + } + return qh; +} + + + + +/* + * converts original EHCI QTD into PTD(Proprietary transfer descriptor) + * we call PTD as qha also for atl transfers + * for ATL and INT transfers + */ +void * +phci_hcd_qha_from_qtd(phci_hcd * hcd, + struct ehci_qtd *qtd, + struct urb *urb, + void *ptd, u32 ptd_data_addr, struct ehci_qh *qh) +{ + u8 toggle = qh->datatoggle; + u32 token = 0; + u32 td_info1 = 0; + u32 td_info3 = 0; + u32 td_info4 = 0; + int maxpacket = 0; + u32 length = 0, temp = 0; + /*for non high speed devices */ + u32 portnum = 0; + u32 hubnum = 0; + u32 se = 0, rl = 0x0, nk = 0x0; + u8 datatoggle = 0; + struct isp1763_mem_addr *mem_addr = &qtd->mem_addr; + u32 data_addr = 0; + u32 multi = 0; + struct _isp1763_qha *qha = (isp1763_qha *) ptd; + pehci_entry("++ %s: Entered\n", __FUNCTION__); + + maxpacket = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)); + + multi = 1 + ((maxpacket >> 11) & 0x3); + + maxpacket &= 0x7ff; + + /************************first word*********************************/ + length = qtd->length; + td_info1 = QHA_VALID; + td_info1 |= (length << 3); + td_info1 |= (maxpacket << 18); + td_info1 |= (usb_pipeendpoint(urb->pipe) << 31); + td_info1 |= MULTI(multi); + /*set the first dword */ + qha->td_info1 = td_info1; + + pehci_print("%s: length %d, 1st word 0x%08x\n", __FUNCTION__, length, + qha->td_info1); + + /*******************second word***************************************/ + temp = qtd->hw_token; + + /*take the pid, thats of only interest to me from qtd, + */ + + temp = temp & 0x0300; + temp = temp >> 8; + /*take the endpoint and its 3 bits */ + token = (usb_pipeendpoint(urb->pipe) & 0xE) >> 1; + token |= usb_pipedevice(urb->pipe) << 3; + + if (urb->dev->speed != USB_SPEED_HIGH) { + pehci_print("device is full/low speed, %d\n", urb->dev->speed); + token |= 1 << 14; + portnum = urb->dev->ttport; + /*IMMED*/ hubnum = urb->dev->tt->hub->devnum; + token |= portnum << 18; + token |= hubnum << 25; + /*for non-high speed transfer + reload and nak counts are zero + */ + rl = 0x0; + nk = 0x0; + + } + + /*se should be 0x2 for only low speed devices */ + if (urb->dev->speed == USB_SPEED_LOW) { + se = 0x2; + } + + if (usb_pipeint(urb->pipe)) { + /* reload count and nakcount is + required for only async transfers + */ + rl = 0x0; + } + + /*set the se field, should be zero for all + but low speed devices + */ + token |= se << 16; + /*take the pid */ + token |= temp << 10; + + if (usb_pipebulk(urb->pipe)) { + token |= EPTYPE_BULK; + } else if (usb_pipeint(urb->pipe)) { + token |= EPTYPE_INT; + } else if (usb_pipeisoc(urb->pipe)) { + token |= EPTYPE_ISO; + } + + + qha->td_info2 = token; + + pehci_print("%s: second word 0x%08x, qtd token 0x%08x\n", + __FUNCTION__, qha->td_info2, temp); + + /***********************Third word*************************************/ + + /*calculate the data start address from mem_addr for qha */ + + data_addr = ((u32) (mem_addr->phy_addr) & 0xffff) - 0x400; + data_addr >>= 3; + pehci_print("data start address %x\n", data_addr); + /*use this field only if there + * is something to transfer + * */ + if (length) { + td_info3 = data_addr << 8; + } + /*RL Count, 16 */ + td_info3 |= (rl << 25); + qha->td_info3 = td_info3; + + pehci_print("%s: third word 0x%08x, tdinfo 0x%08x\n", + __FUNCTION__, qha->td_info3, td_info3); + + + /**************************fourt word*************************************/ + + if (usb_pipecontrol(urb->pipe)) { + datatoggle = qtd->hw_token >> 31; + } else { + /*take the data toggle from the previous completed transfer + or zero in case of fresh */ + datatoggle = toggle; + } + + td_info4 = QHA_ACTIVE; + /*dt */ + td_info4 |= datatoggle << 25; /*QHA_DATA_TOGGLE; */ + /*3 retry count for setup else forever */ + if (PTD_PID(qha->td_info2) == SETUP_PID) { + td_info4 |= (3 << 23); + } else { + td_info4 |= (0 << 23); + } + + /*nak count */ + td_info4 |= (nk << 19); + + td_info4 |= (qh->ping << 26); + qha->td_info4 = td_info4; +#ifdef PTD_DUMP_SCHEDULE + printk("SCHEDULE PTD DUMPE\n") ; + printk("SDW0: 0x%08x\n",qha->td_info1); + printk("SDW1: 0x%08x\n",qha->td_info2); + printk("SDW2: 0x%08x\n",qha->td_info3); + printk("SDW3: 0x%08x\n",qha->td_info4); +#endif + pehci_print("%s: fourt word 0x%08x\n", __FUNCTION__, qha->td_info4); + pehci_entry("-- %s: Exit, qha %p\n", __FUNCTION__, qha); + return qha; + +} diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 1bfcd02ebeb..cf5d4526419 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -218,6 +218,21 @@ config USB_TEST See for more information, including sample test device firmware and "how to use it". +config USB_EHSET_TEST_FIXTURE + tristate "USB EHSET Test Fixture Driver" + depends on USB && USB_EHCI_EHSET + default n + help + Say Y here if you want to use EHSET Test Fixture device for host + compliance testing. + + This driver initiates test modes on the downstream port to which the + test fixture is attached. + + See + for more information. + + config USB_ISIGHTFW tristate "iSight firmware loading support" depends on USB diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 796ce7ebccc..c8e777a4ba7 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_USB_LED) += usbled.o obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o obj-$(CONFIG_USB_RIO500) += rio500.o obj-$(CONFIG_USB_TEST) += usbtest.o +obj-$(CONFIG_USB_EHSET_TEST_FIXTURE) += ehset.o obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o obj-$(CONFIG_USB_USS720) += uss720.o obj-$(CONFIG_USB_SEVSEG) += usbsevseg.o diff --git a/drivers/usb/misc/ehset.c b/drivers/usb/misc/ehset.c new file mode 100644 index 00000000000..30879e01b8d --- /dev/null +++ b/drivers/usb/misc/ehset.c @@ -0,0 +1,147 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#include +#include +#include +#include +#include +#include +#include + +#define TEST_SE0_NAK_PID 0x0101 +#define TEST_J_PID 0x0102 +#define TEST_K_PID 0x0103 +#define TEST_PACKET_PID 0x0104 +#define TEST_HS_HOST_PORT_SUSPEND_RESUME 0x0106 +#define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107 +#define TEST_SINGLE_STEP_SET_FEATURE 0x0108 + +static int ehset_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int status = -1; + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_device *rh_udev = dev->bus->root_hub; + struct usb_device *hub_udev = dev->parent; + int port1 = dev->portnum; + int test_mode = le16_to_cpu(dev->descriptor.idProduct); + + switch (test_mode) { + case TEST_SE0_NAK_PID: + status = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, + (3 << 8) | port1, NULL, 0, 1000); + break; + case TEST_J_PID: + status = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, + (1 << 8) | port1, NULL, 0, 1000); + break; + case TEST_K_PID: + status = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, + (2 << 8) | port1, NULL, 0, 1000); + break; + case TEST_PACKET_PID: + status = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, + (4 << 8) | port1, NULL, 0, 1000); + break; + case TEST_HS_HOST_PORT_SUSPEND_RESUME: + /* Test: wait for 15secs -> suspend -> 15secs delay -> resume */ + msleep(15 * 1000); + status = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_SUSPEND, port1, NULL, 0, 1000); + if (status < 0) + break; + msleep(15 * 1000); + status = usb_control_msg(hub_udev, usb_sndctrlpipe(hub_udev, 0), + USB_REQ_CLEAR_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_SUSPEND, port1, NULL, 0, 1000); + break; + case TEST_SINGLE_STEP_GET_DEV_DESC: + /* Test: wait for 15secs -> GetDescriptor request */ + msleep(15 * 1000); + { + struct usb_device_descriptor *buf; + buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + status = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + USB_DT_DEVICE << 8, 0, + buf, USB_DT_DEVICE_SIZE, + USB_CTRL_GET_TIMEOUT); + kfree(buf); + } + break; + case TEST_SINGLE_STEP_SET_FEATURE: + /* GetDescriptor's SETUP request -> 15secs delay -> IN & STATUS + * Issue request to ehci root hub driver with portnum = 1 + */ + status = usb_control_msg(rh_udev, usb_sndctrlpipe(rh_udev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, + (6 << 8) | 1, NULL, 0, 60 * 1000); + + break; + default: + pr_err("%s: undefined test mode ( %X )\n", __func__, test_mode); + return -EINVAL; + } + + return (status < 0) ? status : 0; +} + +static void ehset_disconnect(struct usb_interface *intf) +{ +} + +static struct usb_device_id ehset_id_table[] = { + { USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) }, + { USB_DEVICE(0x1a0a, TEST_J_PID) }, + { USB_DEVICE(0x1a0a, TEST_K_PID) }, + { USB_DEVICE(0x1a0a, TEST_PACKET_PID) }, + { USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) }, + { USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) }, + { USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, ehset_id_table); + +static struct usb_driver ehset_driver = { + .name = "usb_ehset_test", + .probe = ehset_probe, + .disconnect = ehset_disconnect, + .id_table = ehset_id_table, +}; + +static int __init ehset_init(void) +{ + return usb_register(&ehset_driver); +} + +static void __exit ehset_exit(void) +{ + usb_deregister(&ehset_driver); +} + +module_init(ehset_init); +module_exit(ehset_exit); + +MODULE_DESCRIPTION("USB Driver for EHSET Test Fixture"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 13093481f91..9cc6cb0274d 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -14,7 +14,7 @@ config USB_MUSB_HDRC select TWL4030_USB if MACH_OMAP_3430SDP select TWL6030_USB if MACH_OMAP_4430SDP || MACH_OMAP4_PANDA select USB_OTG_UTILS - tristate 'Inventra Highspeed Dual Role Controller (TI, ADI, ...)' + bool 'Inventra Highspeed Dual Role Controller (TI, ADI, ...)' help Say Y here if your system has a dual role high speed USB controller based on the Mentor Graphics silicon IP. Then @@ -30,8 +30,8 @@ config USB_MUSB_HDRC If you do not know what this is, please say N. - To compile this driver as a module, choose M here; the - module will be called "musb-hdrc". +# To compile this driver as a module, choose M here; the +# module will be called "musb-hdrc". choice prompt "Platform Glue Layer" diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index cd777190d54..00811828468 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -74,6 +74,34 @@ config TWL4030_USB This transceiver supports high and full speed devices plus, in host mode, low speed. +config USB_MSM_OTG_72K + bool "OTG support for Legcay Qualcomm on-chip USB controller" + depends on ARCH_MSM + select USB_OTG_UTILS + default USB_MSM_72K + help + Enable this to support the USB OTG transceiver on MSM chips. It + handles PHY initialization, clock management, low power mode and + workarounds required after resetting the hardware. This driver is + required for even peripheral only or host only mode configuration. + Supports SRP and HNP when both gadget and Host are selected. + +config MSM_OTG_ENABLE_A_WAIT_BCON_TIMEOUT + bool "Enable A-device timeout for B-device connection" + depends on USB_MSM_OTG_72K + default n + help + OTG specification allows A-device to turn off VBUS if B-device + fails to signal connect event before TA_WAIT_BCON (1.1 - 30 sec). + SRP detection is enabled and hardware is put into low power mode + upon this timeout. + + If you say yes, VBUS will be turned off if B-device does not signal + connect in 30 sec. Otherwise VBUS is not turned off when Micro-A + cable is connected. But hardware is put into LPM. Say no if leakage + currents in your system are minimum. + + config TWL6030_USB tristate "TWL6030 USB Transceiver Driver" depends on TWL4030_CORE @@ -121,6 +149,15 @@ config USB_MSM_OTG This driver is not supported on boards like trout which has an external PHY. +config USB_MSM_ACA + bool "Support for Accessory Charger Adapter (ACA)" + depends on (USB_MSM_OTG || USB_MSM_OTG_72K) && ARCH_MSM + default n + help + Accesory Charger Adapter is a charger specified in USB Battery + Charging Specification(1.1). It enables OTG devices to charge + while operating as a host or peripheral at the same time. + config AB8500_USB tristate "AB8500 USB Transceiver Driver" depends on AB8500_CORE diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index d2c0a7b2bf0..2984ee15679 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_USB_LANGWELL_OTG) += langwell_otg.o obj-$(CONFIG_NOP_USB_XCEIV) += nop-usb-xceiv.o obj-$(CONFIG_USB_ULPI) += ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += ulpi_viewport.o +obj-$(CONFIG_USB_MSM_OTG_72K) += msm72k_otg.o obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o obj-$(CONFIG_AB8500_USB) += ab8500-usb.o fsl_usb2_otg-objs := fsl_otg.o otg_fsm.o diff --git a/drivers/usb/otg/msm72k_otg.c b/drivers/usb/otg/msm72k_otg.c new file mode 100644 index 00000000000..dddfa33e64d --- /dev/null +++ b/drivers/usb/otg/msm72k_otg.c @@ -0,0 +1,2957 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSM_USB_BASE (dev->regs) +#define USB_LINK_RESET_TIMEOUT (msecs_to_jiffies(10)) +#define DRIVER_NAME "msm_otg" +static void otg_reset(struct otg_transceiver *xceiv, int phy_reset); +static void msm_otg_set_vbus_state(int online); +static void msm_otg_set_id_state(int online); + +struct msm_otg *the_msm_otg; + +static int is_host(void) +{ + struct msm_otg *dev = the_msm_otg; + + if (dev->pmic_id_notif_supp) + return dev->pmic_id_status ? 0 : 1; + else if (dev->pdata->otg_mode == OTG_ID) + return (OTGSC_ID & readl(USB_OTGSC)) ? 0 : 1; + else + return !test_bit(ID, &dev->inputs); +} + +static int is_b_sess_vld(void) +{ + struct msm_otg *dev = the_msm_otg; + + if (dev->pdata->otg_mode == OTG_ID) + return (OTGSC_BSV & readl(USB_OTGSC)) ? 1 : 0; + else + return test_bit(B_SESS_VLD, &dev->inputs); +} + +static unsigned ulpi_read(struct msm_otg *dev, unsigned reg) +{ + unsigned ret, timeout = 100000; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + /* initiate read operation */ + writel(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while ((readl(USB_ULPI_VIEWPORT) & ULPI_RUN) && (--timeout)) + cpu_relax(); + + if (timeout == 0) { + pr_err("%s: timeout %08x\n", __func__, + readl(USB_ULPI_VIEWPORT)); + spin_unlock_irqrestore(&dev->lock, flags); + return 0xffffffff; + } + ret = ULPI_DATA_READ(readl(USB_ULPI_VIEWPORT)); + + spin_unlock_irqrestore(&dev->lock, flags); + + return ret; +} + +static int ulpi_write(struct msm_otg *dev, unsigned val, unsigned reg) +{ + unsigned timeout = 10000; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + /* initiate write operation */ + writel(ULPI_RUN | ULPI_WRITE | + ULPI_ADDR(reg) | ULPI_DATA(val), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while ((readl(USB_ULPI_VIEWPORT) & ULPI_RUN) && (--timeout)) + ; + + if (timeout == 0) { + pr_err("%s: timeout\n", __func__); + spin_unlock_irqrestore(&dev->lock, flags); + return -1; + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +static int usb_ulpi_write(struct otg_transceiver *xceiv, u32 val, u32 reg) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + + return ulpi_write(dev, val, reg); +} + +static int usb_ulpi_read(struct otg_transceiver *xceiv, u32 reg) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + + return ulpi_read(dev, reg); +} + +#ifdef CONFIG_USB_EHCI_MSM_72K +static void enable_idgnd(struct msm_otg *dev) +{ + /* Do nothing if instead of ID pin, USER controls mode switch */ + if (dev->pdata->otg_mode == OTG_USER_CONTROL) + return; + + ulpi_write(dev, (1<<4), 0x0E); + ulpi_write(dev, (1<<4), 0x11); + writel(readl(USB_OTGSC) | OTGSC_IDIE, USB_OTGSC); +} + +static void disable_idgnd(struct msm_otg *dev) +{ + /* Do nothing if instead of ID pin, USER controls mode switch */ + if (dev->pdata->otg_mode == OTG_USER_CONTROL) + return; + + ulpi_write(dev, (1<<4), 0x0F); + ulpi_write(dev, (1<<4), 0x12); + writel(readl(USB_OTGSC) & ~OTGSC_IDIE, USB_OTGSC); +} +#endif + +static void enable_idabc(struct msm_otg *dev) +{ +#ifdef CONFIG_USB_MSM_ACA + ulpi_write(dev, (1<<5), 0x0E); + ulpi_write(dev, (1<<5), 0x11); +#endif +} +static void disable_idabc(struct msm_otg *dev) +{ +#ifdef CONFIG_USB_MSM_ACA + ulpi_write(dev, (1<<5), 0x0F); + ulpi_write(dev, (1<<5), 0x12); +#endif +} + +static void enable_sess_valid(struct msm_otg *dev) +{ + /* Do nothing if instead of ID pin, USER controls mode switch */ + if (dev->pdata->otg_mode == OTG_USER_CONTROL) + return; + + ulpi_write(dev, (1<<2), 0x0E); + ulpi_write(dev, (1<<2), 0x11); + writel(readl(USB_OTGSC) | OTGSC_BSVIE, USB_OTGSC); +} + +static void disable_sess_valid(struct msm_otg *dev) +{ + /* Do nothing if instead of ID pin, USER controls mode switch */ + if (dev->pdata->otg_mode == OTG_USER_CONTROL) + return; + + ulpi_write(dev, (1<<2), 0x0F); + ulpi_write(dev, (1<<2), 0x12); + writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); +} +#ifdef CONFIG_USB_MSM_ACA +static void set_aca_id_inputs(struct msm_otg *dev) +{ + u8 phy_ints; + + phy_ints = ulpi_read(dev, 0x13); + if (phy_ints == -ETIMEDOUT) + return; + + pr_debug("phy_ints = %x\n", phy_ints); + clear_bit(ID_A, &dev->inputs); + clear_bit(ID_B, &dev->inputs); + clear_bit(ID_C, &dev->inputs); + if (phy_id_state_a(phy_ints)) { + pr_debug("ID_A set\n"); + set_bit(ID_A, &dev->inputs); + set_bit(A_BUS_REQ, &dev->inputs); + } else if (phy_id_state_b(phy_ints)) { + pr_debug("ID_B set\n"); + set_bit(ID_B, &dev->inputs); + } else if (phy_id_state_c(phy_ints)) { + pr_debug("ID_C set\n"); + set_bit(ID_C, &dev->inputs); + } + if (is_b_sess_vld()) + set_bit(B_SESS_VLD, &dev->inputs); + else + clear_bit(B_SESS_VLD, &dev->inputs); +} +#define get_aca_bmaxpower(dev) (dev->b_max_power) +#define set_aca_bmaxpower(dev, power) (dev->b_max_power = power) +#else +#define get_aca_bmaxpower(dev) 0 +#define set_aca_bmaxpower(dev, power) +#endif +static inline void set_pre_emphasis_level(struct msm_otg *dev) +{ + unsigned res = 0; + + if (!dev->pdata || dev->pdata->pemp_level == PRE_EMPHASIS_DEFAULT) + return; + + res = ulpi_read(dev, ULPI_CONFIG_REG3); + res &= ~(ULPI_PRE_EMPHASIS_MASK); + if (dev->pdata->pemp_level != PRE_EMPHASIS_DISABLE) + res |= dev->pdata->pemp_level; + ulpi_write(dev, res, ULPI_CONFIG_REG3); +} + +static inline void set_hsdrv_slope(struct msm_otg *dev) +{ + unsigned res = 0; + + if (!dev->pdata || dev->pdata->hsdrvslope == HS_DRV_SLOPE_DEFAULT) + return; + + res = ulpi_read(dev, ULPI_CONFIG_REG3); + res &= ~(ULPI_HSDRVSLOPE_MASK); + res |= (dev->pdata->hsdrvslope & ULPI_HSDRVSLOPE_MASK); + ulpi_write(dev, res, ULPI_CONFIG_REG3); +} + +static inline void set_cdr_auto_reset(struct msm_otg *dev) +{ + unsigned res = 0; + + if (!dev->pdata || dev->pdata->cdr_autoreset == CDR_AUTO_RESET_DEFAULT) + return; + + res = ulpi_read(dev, ULPI_DIGOUT_CTRL); + if (dev->pdata->cdr_autoreset == CDR_AUTO_RESET_ENABLE) + res &= ~ULPI_CDR_AUTORESET; + else + res |= ULPI_CDR_AUTORESET; + ulpi_write(dev, res, ULPI_DIGOUT_CTRL); +} + +static inline void set_se1_gating(struct msm_otg *dev) +{ + unsigned res = 0; + + if (!dev->pdata || dev->pdata->se1_gating == SE1_GATING_DEFAULT) + return; + + res = ulpi_read(dev, ULPI_DIGOUT_CTRL); + if (dev->pdata->se1_gating == SE1_GATING_ENABLE) + res &= ~ULPI_SE1_GATE; + else + res |= ULPI_SE1_GATE; + ulpi_write(dev, res, ULPI_DIGOUT_CTRL); +} +static inline void set_driver_amplitude(struct msm_otg *dev) +{ + unsigned res = 0; + + if (!dev->pdata || dev->pdata->drv_ampl == HS_DRV_AMPLITUDE_DEFAULT) + return; + + res = ulpi_read(dev, ULPI_CONFIG_REG2); + res &= ~ULPI_DRV_AMPL_MASK; + if (dev->pdata->drv_ampl != HS_DRV_AMPLITUDE_ZERO_PERCENT) + res |= dev->pdata->drv_ampl; + ulpi_write(dev, res, ULPI_CONFIG_REG2); +} + +static const char *state_string(enum usb_otg_state state) +{ + switch (state) { + case OTG_STATE_A_IDLE: return "a_idle"; + case OTG_STATE_A_WAIT_VRISE: return "a_wait_vrise"; + case OTG_STATE_A_WAIT_BCON: return "a_wait_bcon"; + case OTG_STATE_A_HOST: return "a_host"; + case OTG_STATE_A_SUSPEND: return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: return "a_peripheral"; + case OTG_STATE_A_WAIT_VFALL: return "a_wait_vfall"; + case OTG_STATE_A_VBUS_ERR: return "a_vbus_err"; + case OTG_STATE_B_IDLE: return "b_idle"; + case OTG_STATE_B_SRP_INIT: return "b_srp_init"; + case OTG_STATE_B_PERIPHERAL: return "b_peripheral"; + case OTG_STATE_B_WAIT_ACON: return "b_wait_acon"; + case OTG_STATE_B_HOST: return "b_host"; + default: return "UNDEFINED"; + } +} + +static const char *timer_string(int bit) +{ + switch (bit) { + case A_WAIT_VRISE: return "a_wait_vrise"; + case A_WAIT_VFALL: return "a_wait_vfall"; + case B_SRP_FAIL: return "b_srp_fail"; + case A_WAIT_BCON: return "a_wait_bcon"; + case A_AIDL_BDIS: return "a_aidl_bdis"; + case A_BIDL_ADIS: return "a_bidl_adis"; + case B_ASE0_BRST: return "b_ase0_brst"; + default: return "UNDEFINED"; + } +} + +/* Prevent idle power collapse(pc) while operating in peripheral mode */ +static void otg_pm_qos_update_latency(struct msm_otg *dev, int vote) +{ + struct msm_otg_platform_data *pdata = dev->pdata; + u32 swfi_latency = 0; + + if (pdata) + swfi_latency = pdata->swfi_latency + 1; + + if (vote) + pm_qos_update_request(&pdata->pm_qos_req_dma, + swfi_latency); + else + pm_qos_update_request(&pdata->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); +} + +/* If USB Core is running its protocol engine based on PCLK, + * PCLK must be running at >60Mhz for correct HSUSB operation and + * USB core cannot tolerate frequency changes on PCLK. For such + * USB cores, vote for maximum clk frequency on pclk source + */ +static void msm_otg_vote_for_pclk_source(struct msm_otg *dev, int vote) +{ + if (dev->pclk_src && pclk_requires_voting(&dev->otg)) { + + if (vote) + clk_enable(dev->pclk_src); + else + clk_disable(dev->pclk_src); + } +} + +/* Controller gives interrupt for every 1 mesc if 1MSIE is set in OTGSC. + * This interrupt can be used as a timer source and OTG timers can be + * implemented. But hrtimers on MSM hardware can give atleast 1/32 KHZ + * precision. This precision is more than enough for OTG timers. + */ +static enum hrtimer_restart msm_otg_timer_func(struct hrtimer *_timer) +{ + struct msm_otg *dev = container_of(_timer, struct msm_otg, timer); + + /* Phy lockup issues are observed when VBUS Valid interrupt is + * enabled. Hence set A_VBUS_VLD upon timer exipration. + */ + if (dev->active_tmout == A_WAIT_VRISE) + set_bit(A_VBUS_VLD, &dev->inputs); + else + set_bit(dev->active_tmout, &dev->tmouts); + + pr_debug("expired %s timer\n", timer_string(dev->active_tmout)); + queue_work(dev->wq, &dev->sm_work); + return HRTIMER_NORESTART; +} + +static void msm_otg_del_timer(struct msm_otg *dev) +{ + int bit = dev->active_tmout; + + pr_debug("deleting %s timer. remaining %lld msec \n", timer_string(bit), + div_s64(ktime_to_us(hrtimer_get_remaining(&dev->timer)), + 1000)); + hrtimer_cancel(&dev->timer); + clear_bit(bit, &dev->tmouts); +} + +static void msm_otg_start_timer(struct msm_otg *dev, int time, int bit) +{ + clear_bit(bit, &dev->tmouts); + dev->active_tmout = bit; + pr_debug("starting %s timer\n", timer_string(bit)); + hrtimer_start(&dev->timer, + ktime_set(time / 1000, (time % 1000) * 1000000), + HRTIMER_MODE_REL); +} + +/* No two otg timers run in parallel. So one hrtimer is sufficient */ +static void msm_otg_init_timer(struct msm_otg *dev) +{ + hrtimer_init(&dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + dev->timer.function = msm_otg_timer_func; +} + +static const char *event_string(enum usb_otg_event event) +{ + switch (event) { + case OTG_EVENT_DEV_CONN_TMOUT: + return "DEV_CONN_TMOUT"; + case OTG_EVENT_NO_RESP_FOR_HNP_ENABLE: + return "NO_RESP_FOR_HNP_ENABLE"; + case OTG_EVENT_HUB_NOT_SUPPORTED: + return "HUB_NOT_SUPPORTED"; + case OTG_EVENT_DEV_NOT_SUPPORTED: + return "DEV_NOT_SUPPORTED,"; + case OTG_EVENT_HNP_FAILED: + return "HNP_FAILED"; + case OTG_EVENT_NO_RESP_FOR_SRP: + return "NO_RESP_FOR_SRP"; + default: + return "UNDEFINED"; + } +} + +static int msm_otg_send_event(struct otg_transceiver *xceiv, + enum usb_otg_event event) +{ + char module_name[16]; + char udev_event[128]; + char *envp[] = { module_name, udev_event, NULL }; + int ret; + + pr_debug("sending %s event\n", event_string(event)); + + snprintf(module_name, 16, "MODULE=%s", DRIVER_NAME); + snprintf(udev_event, 128, "EVENT=%s", event_string(event)); + ret = kobject_uevent_env(&xceiv->dev->kobj, KOBJ_CHANGE, envp); + if (ret < 0) + pr_info("uevent sending failed with ret = %d\n", ret); + return ret; +} + +static int msm_otg_start_hnp(struct otg_transceiver *xceiv) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->otg.state; + spin_unlock_irqrestore(&dev->lock, flags); + + if (state != OTG_STATE_A_HOST) { + pr_err("HNP can not be initiated in %s state\n", + state_string(state)); + return -EINVAL; + } + + pr_debug("A-Host: HNP initiated\n"); + clear_bit(A_BUS_REQ, &dev->inputs); + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + return 0; +} + +static int msm_otg_start_srp(struct otg_transceiver *xceiv) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + u32 val; + int ret = 0; + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->otg.state; + spin_unlock_irqrestore(&dev->lock, flags); + + if (state != OTG_STATE_B_IDLE) { + pr_err("SRP can not be initiated in %s state\n", + state_string(state)); + ret = -EINVAL; + goto out; + } + + if ((jiffies - dev->b_last_se0_sess) < msecs_to_jiffies(TB_SRP_INIT)) { + pr_debug("initial conditions of SRP are not met. Try again" + "after some time\n"); + ret = -EAGAIN; + goto out; + } + + /* Harware auto assist data pulsing: Data pulse is given + * for 7msec; wait for vbus + */ + val = readl(USB_OTGSC); + writel((val & ~OTGSC_INTR_STS_MASK) | OTGSC_HADP, USB_OTGSC); + + /* VBUS plusing is obsoleted in OTG 2.0 supplement */ +out: + return ret; +} + +static int msm_otg_set_power(struct otg_transceiver *xceiv, unsigned mA) +{ + static enum chg_type curr_chg = USB_CHG_TYPE__INVALID; + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + struct msm_otg_platform_data *pdata = dev->pdata; + enum chg_type new_chg = atomic_read(&dev->chg_type); + unsigned charge = mA; + + /* Call chg_connected only if the charger has changed */ + if (new_chg != curr_chg && pdata->chg_connected) { + curr_chg = new_chg; + pdata->chg_connected(new_chg); + } + + /* Always use USB_IDCHG_MAX for charging in ID_B and ID_C */ + if (test_bit(ID_C, &dev->inputs) || + test_bit(ID_B, &dev->inputs)) + charge = USB_IDCHG_MAX; + + pr_debug("Charging with %dmA current\n", charge); + /* Call vbus_draw only if the charger is of known type and also + * ignore request to stop charging as a result of suspend interrupt + * when wall-charger is used. + */ + if (pdata->chg_vbus_draw && new_chg != USB_CHG_TYPE__INVALID && + (charge || new_chg != USB_CHG_TYPE__WALLCHARGER)) + pdata->chg_vbus_draw(charge); + + if (new_chg == USB_CHG_TYPE__WALLCHARGER) { + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } + + return 0; +} + +static int msm_otg_set_clk(struct otg_transceiver *xceiv, int on) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + + if (!dev || (dev != the_msm_otg)) + return -ENODEV; + + if (on) + /* enable clocks */ + clk_enable(dev->hs_clk); + else + clk_disable(dev->hs_clk); + + return 0; +} +static void msm_otg_start_peripheral(struct otg_transceiver *xceiv, int on) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + struct msm_otg_platform_data *pdata = dev->pdata; + + if (!xceiv->gadget) + return; + + if (on) { + if (pdata->setup_gpio) + pdata->setup_gpio(USB_SWITCH_PERIPHERAL); + /* vote for minimum dma_latency to prevent idle + * power collapse(pc) while running in peripheral mode. + */ + otg_pm_qos_update_latency(dev, 1); + + /* increment the clk reference count so that + * it would be still on when disabled from + * low power mode routine + */ + if (dev->pdata->pclk_required_during_lpm) + clk_enable(dev->hs_pclk); + + usb_gadget_vbus_connect(xceiv->gadget); + } else { + atomic_set(&dev->chg_type, USB_CHG_TYPE__INVALID); + usb_gadget_vbus_disconnect(xceiv->gadget); + + /* decrement the clk reference count so that + * it would be off when disabled from + * low power mode routine + */ + if (dev->pdata->pclk_required_during_lpm) + clk_disable(dev->hs_pclk); + + otg_pm_qos_update_latency(dev, 0); + if (pdata->setup_gpio) + pdata->setup_gpio(USB_SWITCH_DISABLE); + } +} + +static void msm_otg_start_host(struct otg_transceiver *xceiv, int on) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + struct msm_otg_platform_data *pdata = dev->pdata; + + if (!xceiv->host) + return; + + if (dev->start_host) { + /* Some targets, e.g. ST1.5, use GPIO to choose b/w connector */ + if (on && pdata->setup_gpio) + pdata->setup_gpio(USB_SWITCH_HOST); + + /* increment or decrement the clk reference count + * to avoid usb h/w lockup issues when low power + * mode is initiated and vbus is on. + */ + if (dev->pdata->pclk_required_during_lpm) { + if (on) + clk_enable(dev->hs_pclk); + else + clk_disable(dev->hs_pclk); + } + + dev->start_host(xceiv->host, on); + + if (!on && pdata->setup_gpio) + pdata->setup_gpio(USB_SWITCH_DISABLE); + } +} + +static int msm_otg_suspend(struct msm_otg *dev) +{ + unsigned long timeout; + bool host_bus_suspend; + unsigned ret; + enum chg_type chg_type = atomic_read(&dev->chg_type); + unsigned long flags; + + disable_irq(dev->irq); + if (atomic_read(&dev->in_lpm)) + goto out; +#ifdef CONFIG_USB_MSM_ACA + /* + * ACA interrupts are disabled before entering into LPM. + * If LPM is allowed in host mode with accessory charger + * connected or only accessory charger is connected, + * there is a chance that charger is removed and we will + * not know about it. + * + * REVISIT + * + * Allowing LPM in case of gadget bus suspend is tricky. + * Bus suspend can happen in two states. + * 1. ID_float: Allowing LPM has pros and cons. If LPM is allowed + * and accessory charger is connected, we miss ID_float --> ID_C + * transition where we could draw large amount of current + * compared to the suspend current. + * 2. ID_C: We can not allow LPM. If accessory charger is removed + * we should not draw more than what host could supply which will + * be less compared to accessory charger. + * + * For simplicity, LPM is not allowed in bus suspend. + */ +#ifndef CONFIG_USB_MSM_STANDARD_ACA + /* + * RID_A and IdGnd states are only possible with standard ACA. We can + * exit from low power mode with !BSV or IdGnd interrupt. Hence LPM + * is allowed. + */ + if ((test_bit(ID, &dev->inputs) && test_bit(B_SESS_VLD, &dev->inputs) && + chg_type != USB_CHG_TYPE__WALLCHARGER) || + test_bit(ID_A, &dev->inputs)) + goto out; +#endif + /* Disable ID_abc interrupts else it causes spurious interrupt */ + disable_idabc(dev); +#endif + ulpi_read(dev, 0x14);/* clear PHY interrupt latch register */ + + /* + * Turn on PHY comparators if, + * 1. USB wall charger is connected (bus suspend is not supported) + * 2. Host bus suspend + * 3. host is supported, but, id is not routed to pmic + * 4. peripheral is supported, but, vbus is not routed to pmic + */ + host_bus_suspend = dev->otg.host && is_host(); + if ((dev->otg.gadget && chg_type == USB_CHG_TYPE__WALLCHARGER) || + host_bus_suspend || + (dev->otg.host && !dev->pmic_id_notif_supp) || + (dev->otg.gadget && !dev->pmic_vbus_notif_supp)) { + ulpi_write(dev, 0x01, 0x30); + } + + ulpi_write(dev, 0x08, 0x09);/* turn off PLL on integrated phy */ + + timeout = jiffies + msecs_to_jiffies(500); + disable_phy_clk(); + while (!is_phy_clk_disabled()) { + if (time_after(jiffies, timeout)) { + pr_err("%s: Unable to suspend phy\n", __func__); + /* + * Start otg state machine in default state upon + * phy suspend failure*/ + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_UNDEFINED; + spin_unlock_irqrestore(&dev->lock, flags); + queue_work(dev->wq, &dev->sm_work); + goto out; + } + msleep(1); + /* check if there are any pending interrupts*/ + if (((readl(USB_OTGSC) & OTGSC_INTR_MASK) >> 8) & + readl(USB_OTGSC)) { + enable_idabc(dev); + goto out; + } + } + + writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL | ULPI_STP_CTRL, USB_USBCMD); + /* Ensure that above operation is completed before turning off clocks */ + mb(); + + if (dev->hs_pclk) + clk_disable(dev->hs_pclk); + if (dev->hs_cclk) + clk_disable(dev->hs_cclk); + /* usb phy no more require TCXO clock, hence vote for TCXO disable*/ + ret = msm_xo_mode_vote(dev->xo_handle, MSM_XO_MODE_OFF); + if (ret) + pr_err("%s failed to devote for" + "TCXO D1 buffer%d\n", __func__, ret); + + if (device_may_wakeup(dev->otg.dev)) { + enable_irq_wake(dev->irq); + if (dev->vbus_on_irq) + enable_irq_wake(dev->vbus_on_irq); + } + + msm_otg_vote_for_pclk_source(dev, 0); + + atomic_set(&dev->in_lpm, 1); + + if (!host_bus_suspend && dev->pmic_vbus_notif_supp) { + pr_debug("phy can power collapse: (%d)\n", + can_phy_power_collapse(dev)); + if (can_phy_power_collapse(dev) && dev->pdata->ldo_enable) { + pr_debug("disabling the regulators\n"); + dev->pdata->ldo_enable(0); + } + } + + /* phy can interrupts when vddcx is at 0.75, so irrespective + * of pmic notification support, configure vddcx @0.75 + */ + if (dev->pdata->config_vddcx) + dev->pdata->config_vddcx(0); + pr_info("%s: usb in low power mode\n", __func__); + +out: + enable_irq(dev->irq); + + return 0; +} + +static int msm_otg_resume(struct msm_otg *dev) +{ + unsigned temp; + unsigned ret; + + if (!atomic_read(&dev->in_lpm)) + return 0; + /* vote for vddcx, as PHY cannot tolerate vddcx below 1.0V */ + if (dev->pdata->config_vddcx) { + ret = dev->pdata->config_vddcx(1); + if (ret) { + pr_err("%s: unable to enable vddcx digital core:%d\n", + __func__, ret); + } + } + if (dev->pdata->ldo_set_voltage) + dev->pdata->ldo_set_voltage(3400); + + /* Vote for TCXO when waking up the phy */ + ret = msm_xo_mode_vote(dev->xo_handle, MSM_XO_MODE_ON); + if (ret) + pr_err("%s failed to vote for" + "TCXO D1 buffer%d\n", __func__, ret); + + msm_otg_vote_for_pclk_source(dev, 1); + + if (dev->hs_pclk) + clk_enable(dev->hs_pclk); + if (dev->hs_cclk) + clk_enable(dev->hs_cclk); + + temp = readl(USB_USBCMD); + temp &= ~ASYNC_INTR_CTRL; + temp &= ~ULPI_STP_CTRL; + writel(temp, USB_USBCMD); + + if (device_may_wakeup(dev->otg.dev)) { + disable_irq_wake(dev->irq); + if (dev->vbus_on_irq) + disable_irq_wake(dev->vbus_on_irq); + } + + atomic_set(&dev->in_lpm, 0); + + pr_info("%s: usb exited from low power mode\n", __func__); + + return 0; +} + +static void msm_otg_get_resume(struct msm_otg *dev) +{ +#ifdef CONFIG_PM_RUNTIME + pm_runtime_get_noresume(dev->otg.dev); + pm_runtime_resume(dev->otg.dev); +#else + msm_otg_resume(dev); +#endif +} + +static void msm_otg_put_suspend(struct msm_otg *dev) +{ +#ifdef CONFIG_PM_RUNTIME + pm_runtime_put_sync(dev->otg.dev); +#else + msm_otg_suspend(dev); +#endif +} + +static void msm_otg_resume_w(struct work_struct *w) +{ + struct msm_otg *dev = container_of(w, struct msm_otg, otg_resume_work); + unsigned long timeout; + + msm_otg_get_resume(dev); + + if (!is_phy_clk_disabled()) + goto phy_resumed; + + timeout = jiffies + usecs_to_jiffies(100); + enable_phy_clk(); + while (is_phy_clk_disabled() || !is_phy_active()) { + if (time_after(jiffies, timeout)) { + pr_err("%s: Unable to wakeup phy. is_phy_active: %x\n", + __func__, !!is_phy_active()); + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + break; + } + udelay(10); + } + +phy_resumed: + /* Enable Idabc interrupts as these were disabled before entering LPM */ + enable_idabc(dev); + + /* If resume signalling finishes before lpm exit, PCD is not set in + * USBSTS register. Drive resume signal to the downstream device now + * so that host driver can process the upcoming port change interrupt.*/ + if (is_host() || test_bit(ID_A, &dev->inputs)) { + writel(readl(USB_PORTSC) | PORTSC_FPR, USB_PORTSC); + msm_otg_start_host(&dev->otg, REQUEST_RESUME); + } + + /* Enable irq which was disabled before scheduling this work. + * But don't release wake_lock, as we got async interrupt and + * there will be some work pending for OTG state machine. + */ + enable_irq(dev->irq); +} + +static int msm_otg_set_suspend(struct otg_transceiver *xceiv, int suspend) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + enum usb_otg_state state; + unsigned long flags; + + if (!dev || (dev != the_msm_otg)) + return -ENODEV; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->otg.state; + spin_unlock_irqrestore(&dev->lock, flags); + + pr_debug("suspend request in state: %s\n", + state_string(state)); + + if (suspend) { + switch (state) { +#ifndef CONFIG_MSM_OTG_ENABLE_A_WAIT_BCON_TIMEOUT + case OTG_STATE_A_WAIT_BCON: + if (test_bit(ID_A, &dev->inputs)) + msm_otg_set_power(xceiv, USB_IDCHG_MIN - 100); + msm_otg_put_suspend(dev); + break; +#endif + case OTG_STATE_A_HOST: + clear_bit(A_BUS_REQ, &dev->inputs); + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + break; + case OTG_STATE_B_PERIPHERAL: + if (xceiv->gadget->b_hnp_enable) { + set_bit(A_BUS_SUSPEND, &dev->inputs); + set_bit(B_BUS_REQ, &dev->inputs); + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } + break; + case OTG_STATE_A_PERIPHERAL: + msm_otg_start_timer(dev, TA_BIDL_ADIS, + A_BIDL_ADIS); + break; + default: + break; + } + } else { + unsigned long timeout; + + switch (state) { + case OTG_STATE_A_PERIPHERAL: + /* A-peripheral observed activity on bus. + * clear A_BIDL_ADIS timer. + */ + msm_otg_del_timer(dev); + break; + case OTG_STATE_A_SUSPEND: + /* Remote wakeup or resume */ + set_bit(A_BUS_REQ, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_HOST; + spin_unlock_irqrestore(&dev->lock, flags); + if (test_bit(ID_A, &dev->inputs) && + (get_aca_bmaxpower(dev) < USB_IDCHG_MIN)) + msm_otg_set_power(xceiv, + USB_IDCHG_MIN - get_aca_bmaxpower(dev)); + break; + default: + break; + } + + if (suspend == atomic_read(&dev->in_lpm)) + return 0; + + disable_irq(dev->irq); + if (dev->pmic_vbus_notif_supp) + if (can_phy_power_collapse(dev) && + dev->pdata->ldo_enable) + dev->pdata->ldo_enable(1); + + msm_otg_get_resume(dev); + + if (!is_phy_clk_disabled()) + goto out; + + timeout = jiffies + usecs_to_jiffies(100); + enable_phy_clk(); + while (is_phy_clk_disabled() || !is_phy_active()) { + if (time_after(jiffies, timeout)) { + pr_err("%s: Unable to wakeup phy. " + "is_phy_active: %x\n", + __func__, !!is_phy_active()); + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + break; + } + udelay(10); + } +out: + enable_idabc(dev); + enable_irq(dev->irq); + + } + + return 0; +} + +static int msm_otg_set_peripheral(struct otg_transceiver *xceiv, + struct usb_gadget *gadget) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + + if (!dev || (dev != the_msm_otg)) + return -ENODEV; + + if (!gadget) { + msm_otg_start_peripheral(xceiv, 0); + dev->otg.gadget = 0; + disable_sess_valid(dev); + if (!dev->otg.host) + disable_idabc(dev); + return 0; + } + dev->otg.gadget = gadget; + pr_info("peripheral driver registered w/ tranceiver\n"); + + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + return 0; +} + +#ifdef CONFIG_USB_EHCI_MSM_72K +static int usbdev_notify(struct notifier_block *self, + unsigned long action, void *device) +{ + enum usb_otg_state state; + struct msm_otg *dev = container_of(self, struct msm_otg, usbdev_nb); + struct usb_device *udev = device; + int work = 1; + unsigned long flags; + + /* Interested in only devices directly connected + * to root hub directly. + */ + if (!udev->parent || udev->parent->parent) + goto out; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->otg.state; + spin_unlock_irqrestore(&dev->lock, flags); + + switch (state) { + case OTG_STATE_A_WAIT_BCON: + if (action == USB_DEVICE_ADD) { + pr_debug("B_CONN set\n"); + set_bit(B_CONN, &dev->inputs); + if (udev->actconfig) { + set_aca_bmaxpower(dev, + udev->actconfig->desc.bMaxPower * 2); + goto do_work; + } + if (udev->portnum == udev->bus->otg_port) + set_aca_bmaxpower(dev, USB_IB_UNCFG); + else + set_aca_bmaxpower(dev, 100); + } + break; + case OTG_STATE_A_HOST: + if (action == USB_DEVICE_REMOVE) { + pr_debug("B_CONN clear\n"); + clear_bit(B_CONN, &dev->inputs); + set_aca_bmaxpower(dev, 0); + } + break; + default: + work = 0; + break; + } +do_work: + if (work) { + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } +out: + return NOTIFY_OK; +} + +static int msm_otg_set_host(struct otg_transceiver *xceiv, struct usb_bus *host) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + + if (!dev || (dev != the_msm_otg)) + return -ENODEV; + + if (!dev->start_host) + return -ENODEV; + + if (!host) { + msm_otg_start_host(xceiv, REQUEST_STOP); + usb_unregister_notify(&dev->usbdev_nb); + dev->otg.host = 0; + dev->start_host = 0; + disable_idgnd(dev); + if (!dev->otg.gadget) + disable_idabc(dev); + return 0; + } +#ifdef CONFIG_USB_OTG + host->otg_port = 1; +#endif + dev->usbdev_nb.notifier_call = usbdev_notify; + usb_register_notify(&dev->usbdev_nb); + dev->otg.host = host; + pr_info("host driver registered w/ tranceiver\n"); + +#ifndef CONFIG_USB_MSM_72K + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); +#endif + return 0; +} +#endif + +void msm_otg_set_id_state(int id) +{ + struct msm_otg *dev = the_msm_otg; + unsigned long flags; + + if (id == dev->pmic_id_status) + return; + + if (id) { + set_bit(ID, &dev->inputs); + dev->pmic_id_status = 1; + } else { + clear_bit(ID, &dev->inputs); + set_bit(A_BUS_REQ, &dev->inputs); + dev->pmic_id_status = 0; + } + spin_lock_irqsave(&dev->lock, flags); + if (dev->otg.state != OTG_STATE_UNDEFINED) { + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +void msm_otg_set_vbus_state(int online) +{ + struct msm_otg *dev = the_msm_otg; + + if (!atomic_read(&dev->in_lpm) || !online) + return; + + wake_lock(&dev->wlock); + set_bit(B_SESS_VLD, &dev->inputs); + queue_work(dev->wq, &dev->sm_work); +} + +static irqreturn_t msm_otg_irq(int irq, void *data) +{ + struct msm_otg *dev = data; + u32 otgsc, sts, pc, sts_mask; + irqreturn_t ret = IRQ_HANDLED; + int work = 0; + enum usb_otg_state state; + unsigned long flags; + + if (atomic_read(&dev->in_lpm)) { + disable_irq_nosync(dev->irq); + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->otg_resume_work); + goto out; + } + + /* Return immediately if instead of ID pin, USER controls mode switch */ + if (dev->pdata->otg_mode == OTG_USER_CONTROL) + return IRQ_NONE; + + + otgsc = readl(USB_OTGSC); + sts = readl(USB_USBSTS); + + sts_mask = (otgsc & OTGSC_INTR_MASK) >> 8; + + if (!((otgsc & sts_mask) || (sts & STS_PCI))) { + ret = IRQ_NONE; + goto out; + } + + spin_lock_irqsave(&dev->lock, flags); + state = dev->otg.state; + spin_unlock_irqrestore(&dev->lock, flags); + + pr_debug("IRQ state: %s\n", state_string(state)); + pr_debug("otgsc = %x\n", otgsc); + + if ((otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) { + if (otgsc & OTGSC_ID) { + pr_debug("Id set\n"); + set_bit(ID, &dev->inputs); + } else { + pr_debug("Id clear\n"); + /* Assert a_bus_req to supply power on + * VBUS when Micro/Mini-A cable is connected + * with out user intervention. + */ + set_bit(A_BUS_REQ, &dev->inputs); + clear_bit(ID, &dev->inputs); + } + writel(otgsc, USB_OTGSC); + work = 1; + } else if (otgsc & OTGSC_BSVIS) { + writel(otgsc, USB_OTGSC); + /* BSV interrupt comes when operating as an A-device + * (VBUS on/off). + * But, handle BSV when charger is removed from ACA in ID_A + */ + if ((state >= OTG_STATE_A_IDLE) && + !test_bit(ID_A, &dev->inputs)) + goto out; + if (otgsc & OTGSC_BSV) { + pr_debug("BSV set\n"); + set_bit(B_SESS_VLD, &dev->inputs); + } else { + pr_debug("BSV clear\n"); + clear_bit(B_SESS_VLD, &dev->inputs); + } + work = 1; + } else if (otgsc & OTGSC_DPIS) { + pr_debug("DPIS detected\n"); + writel(otgsc, USB_OTGSC); + set_bit(A_SRP_DET, &dev->inputs); + set_bit(A_BUS_REQ, &dev->inputs); + work = 1; + } else if (sts & STS_PCI) { + pc = readl(USB_PORTSC); + pr_debug("portsc = %x\n", pc); + ret = IRQ_NONE; + /* HCD Acks PCI interrupt. We use this to switch + * between different OTG states. + */ + work = 1; + switch (state) { + case OTG_STATE_A_SUSPEND: + if (dev->otg.host->b_hnp_enable && (pc & PORTSC_CSC) && + !(pc & PORTSC_CCS)) { + pr_debug("B_CONN clear\n"); + clear_bit(B_CONN, &dev->inputs); + } + break; + case OTG_STATE_B_WAIT_ACON: + if ((pc & PORTSC_CSC) && (pc & PORTSC_CCS)) { + pr_debug("A_CONN set\n"); + set_bit(A_CONN, &dev->inputs); + /* Clear ASE0_BRST timer */ + msm_otg_del_timer(dev); + } + break; + case OTG_STATE_B_HOST: + if ((pc & PORTSC_CSC) && !(pc & PORTSC_CCS)) { + pr_debug("A_CONN clear\n"); + clear_bit(A_CONN, &dev->inputs); + } + break; + default: + work = 0; + break; + } + } + if (work) { +#ifdef CONFIG_USB_MSM_ACA + /* With ACA, ID can change bcoz of BSVIS as well, so update */ + if ((otgsc & OTGSC_IDIS) || (otgsc & OTGSC_BSVIS)) + set_aca_id_inputs(dev); +#endif + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } +out: + return ret; +} + +#define ULPI_VERIFY_MAX_LOOP_COUNT 5 +#define PHY_CALIB_RETRY_COUNT 10 +static void phy_clk_reset(struct msm_otg *dev) +{ + unsigned rc; + enum clk_reset_action assert = CLK_RESET_ASSERT; + + if (dev->pdata->phy_reset_sig_inverted) + assert = CLK_RESET_DEASSERT; + + rc = clk_reset(dev->phy_reset_clk, assert); + if (rc) { + pr_err("%s: phy clk assert failed\n", __func__); + return; + } + + msleep(1); + + rc = clk_reset(dev->phy_reset_clk, !assert); + if (rc) { + pr_err("%s: phy clk deassert failed\n", __func__); + return; + } + + msleep(1); +} + +static unsigned ulpi_read_with_reset(struct msm_otg *dev, unsigned reg) +{ + int temp; + unsigned res; + + for (temp = 0; temp < ULPI_VERIFY_MAX_LOOP_COUNT; temp++) { + res = ulpi_read(dev, reg); + if (res != 0xffffffff) + return res; + + phy_clk_reset(dev); + } + + pr_err("%s: ulpi read failed for %d times\n", + __func__, ULPI_VERIFY_MAX_LOOP_COUNT); + + return -1; +} + +static int ulpi_write_with_reset(struct msm_otg *dev, +unsigned val, unsigned reg) +{ + int temp, res; + + for (temp = 0; temp < ULPI_VERIFY_MAX_LOOP_COUNT; temp++) { + res = ulpi_write(dev, val, reg); + if (!res) + return 0; + phy_clk_reset(dev); + } + pr_err("%s: ulpi write failed for %d times\n", + __func__, ULPI_VERIFY_MAX_LOOP_COUNT); + + return -1; +} + +/* some of the older targets does not turn off the PLL + * if onclock bit is set and clocksuspendM bit is on, + * hence clear them too and initiate the suspend mode + * by clearing SupendM bit. + */ +static inline int turn_off_phy_pll(struct msm_otg *dev) +{ + unsigned res; + + res = ulpi_read_with_reset(dev, ULPI_CONFIG_REG1); + if (res == 0xffffffff) + return -ETIMEDOUT; + + res = ulpi_write_with_reset(dev, + res & ~(ULPI_ONCLOCK), ULPI_CONFIG_REG1); + if (res) + return -ETIMEDOUT; + + res = ulpi_write_with_reset(dev, + ULPI_CLOCK_SUSPENDM, ULPI_IFC_CTRL_CLR); + if (res) + return -ETIMEDOUT; + + /*Clear SuspendM bit to initiate suspend mode */ + res = ulpi_write_with_reset(dev, + ULPI_SUSPENDM, ULPI_FUNC_CTRL_CLR); + if (res) + return -ETIMEDOUT; + + return res; +} + +static inline int check_phy_caliberation(struct msm_otg *dev) +{ + unsigned res; + + res = ulpi_read_with_reset(dev, ULPI_DEBUG); + + if (res == 0xffffffff) + return -ETIMEDOUT; + + if (!(res & ULPI_CALIB_STS) && ULPI_CALIB_VAL(res)) + return 0; + + return -1; +} + +static int msm_otg_phy_caliberate(struct msm_otg *dev) +{ + int i = 0; + unsigned long res; + + do { + res = turn_off_phy_pll(dev); + if (res) + return -ETIMEDOUT; + + /* bring phy out of suspend */ + phy_clk_reset(dev); + + res = check_phy_caliberation(dev); + if (!res) + return res; + i++; + + } while (i < PHY_CALIB_RETRY_COUNT); + + return res; +} + +static int msm_otg_phy_reset(struct msm_otg *dev) +{ + unsigned rc; + unsigned temp; + unsigned long timeout; + + rc = clk_reset(dev->hs_clk, CLK_RESET_ASSERT); + if (rc) { + pr_err("%s: usb hs clk assert failed\n", __func__); + return -1; + } + + phy_clk_reset(dev); + + rc = clk_reset(dev->hs_clk, CLK_RESET_DEASSERT); + if (rc) { + pr_err("%s: usb hs clk deassert failed\n", __func__); + return -1; + } + /* Observing ulpi timeouts as part of PHY calibration. On resetting + * the HW link explicity by setting the RESET bit in the USBCMD + * register before PHY calibration fixes the ulpi timeout issue. + * This workaround is required for unicorn target + */ + writel_relaxed(USBCMD_RESET, USB_USBCMD); + timeout = jiffies + USB_LINK_RESET_TIMEOUT; + do { + if (time_after(jiffies, timeout)) { + pr_err("msm_otg: usb link reset timeout\n"); + break; + } + usleep_range(1000, 1200); + } while (readl_relaxed(USB_USBCMD) & USBCMD_RESET); + + /* select ULPI phy */ + temp = (readl(USB_PORTSC) & ~PORTSC_PTS); + writel(temp | PORTSC_PTS_ULPI, USB_PORTSC); + + if (atomic_read(&dev->chg_type) != + USB_CHG_TYPE__WALLCHARGER) { + rc = msm_otg_phy_caliberate(dev); + if (rc) + return rc; + } + + /* TBD: There are two link resets. One is below and other one + * is done immediately after this function. See if we can + * eliminate one of these. + */ + writel(USBCMD_RESET, USB_USBCMD); + timeout = jiffies + USB_LINK_RESET_TIMEOUT; + do { + if (time_after(jiffies, timeout)) { + pr_err("msm_otg: usb link reset timeout\n"); + break; + } + msleep(1); + } while (readl(USB_USBCMD) & USBCMD_RESET); + + if (readl(USB_USBCMD) & USBCMD_RESET) { + pr_err("%s: usb core reset failed\n", __func__); + return -1; + } + + return 0; +} + +static void otg_reset(struct otg_transceiver *xceiv, int phy_reset) +{ + struct msm_otg *dev = container_of(xceiv, struct msm_otg, otg); + unsigned long timeout; + u32 mode, work = 0; + + clk_enable(dev->hs_clk); + + if (!phy_reset) + goto reset_link; + + if (dev->pdata->phy_reset) + dev->pdata->phy_reset(dev->regs); + else + msm_otg_phy_reset(dev); + + /*disable all phy interrupts*/ + ulpi_write(dev, 0xFF, 0x0F); + ulpi_write(dev, 0xFF, 0x12); + msleep(100); + +reset_link: + writel(USBCMD_RESET, USB_USBCMD); + timeout = jiffies + USB_LINK_RESET_TIMEOUT; + do { + if (time_after(jiffies, timeout)) { + pr_err("msm_otg: usb link reset timeout\n"); + break; + } + msleep(1); + } while (readl(USB_USBCMD) & USBCMD_RESET); + + /* select ULPI phy */ + writel(0x80000000, USB_PORTSC); + + set_pre_emphasis_level(dev); + set_hsdrv_slope(dev); + set_cdr_auto_reset(dev); + set_driver_amplitude(dev); + set_se1_gating(dev); + + writel(0x0, USB_AHB_BURST); + writel(0x00, USB_AHB_MODE); + /* Ensure that RESET operation is completed before turning off clock */ + mb(); + + clk_disable(dev->hs_clk); + + if ((xceiv->gadget && xceiv->gadget->is_a_peripheral) || + test_bit(ID, &dev->inputs)) + mode = USBMODE_SDIS | USBMODE_DEVICE; + else + mode = USBMODE_SDIS | USBMODE_HOST; + writel(mode, USB_USBMODE); + + writel_relaxed((readl_relaxed(USB_OTGSC) | OTGSC_IDPU), USB_OTGSC); + if (dev->otg.gadget) { + enable_sess_valid(dev); + /* Due to the above 100ms delay, interrupts from PHY are + * sometimes missed during fast plug-in/plug-out of cable. + * Check for such cases here. + */ + if (is_b_sess_vld() && !test_bit(B_SESS_VLD, &dev->inputs)) { + pr_debug("%s: handle missing BSV event\n", __func__); + set_bit(B_SESS_VLD, &dev->inputs); + work = 1; + } else if (!is_b_sess_vld() && test_bit(B_SESS_VLD, + &dev->inputs)) { + pr_debug("%s: handle missing !BSV event\n", __func__); + clear_bit(B_SESS_VLD, &dev->inputs); + work = 1; + } + } + +#ifdef CONFIG_USB_EHCI_MSM_72K + if (dev->otg.host && !dev->pmic_id_notif_supp) { + enable_idgnd(dev); + /* Handle missing ID_GND interrupts during fast PIPO */ + if (is_host() && test_bit(ID, &dev->inputs)) { + pr_debug("%s: handle missing ID_GND event\n", __func__); + clear_bit(ID, &dev->inputs); + work = 1; + } else if (!is_host() && !test_bit(ID, &dev->inputs)) { + pr_debug("%s: handle missing !ID_GND event\n", + __func__); + set_bit(ID, &dev->inputs); + work = 1; + } + } else { + disable_idgnd(dev); + } +#endif + + enable_idabc(dev); + + if (work) { + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } +} + +static void msm_otg_sm_work(struct work_struct *w) +{ + struct msm_otg *dev = container_of(w, struct msm_otg, sm_work); + enum chg_type chg_type = atomic_read(&dev->chg_type); + int ret; + int work = 0; + enum usb_otg_state state; + unsigned long flags; + + if (atomic_read(&dev->in_lpm)) + msm_otg_set_suspend(&dev->otg, 0); + + spin_lock_irqsave(&dev->lock, flags); + state = dev->otg.state; + spin_unlock_irqrestore(&dev->lock, flags); + + switch (state) { + case OTG_STATE_UNDEFINED: + + /* + * We can come here when LPM fails with wall charger + * connected. Increment the PM usage counter to reflect + * the actual device state. Change the state to + * B_PERIPHERAL and schedule the work which takes care + * of resetting the PHY and putting the hardware in + * low power mode. + */ + if (atomic_read(&dev->chg_type) == + USB_CHG_TYPE__WALLCHARGER) { + msm_otg_get_resume(dev); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_PERIPHERAL; + spin_unlock_irqrestore(&dev->lock, flags); + work = 1; + break; + } + + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + +#ifdef CONFIG_USB_MSM_ACA + set_aca_id_inputs(dev); +#endif + if (dev->pdata->otg_mode == OTG_USER_CONTROL) { + if ((dev->pdata->usb_mode == USB_PERIPHERAL_MODE) || + !dev->otg.host) { + set_bit(ID, &dev->inputs); + set_bit(B_SESS_VLD, &dev->inputs); + } + } else { + if (!dev->otg.host || !is_host()) + set_bit(ID, &dev->inputs); + + if (dev->otg.gadget && is_b_sess_vld()) + set_bit(B_SESS_VLD, &dev->inputs); + } + spin_lock_irqsave(&dev->lock, flags); + if ((test_bit(ID, &dev->inputs)) && + !test_bit(ID_A, &dev->inputs)) { + dev->otg.state = OTG_STATE_B_IDLE; + } else { + set_bit(A_BUS_REQ, &dev->inputs); + dev->otg.state = OTG_STATE_A_IDLE; + } + spin_unlock_irqrestore(&dev->lock, flags); + + work = 1; + break; + case OTG_STATE_B_IDLE: + dev->otg.default_a = 0; + if (!test_bit(ID, &dev->inputs) || + test_bit(ID_A, &dev->inputs)) { + pr_debug("!id || id_A\n"); + clear_bit(B_BUS_REQ, &dev->inputs); + otg_reset(&dev->otg, 0); + + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_set_power(&dev->otg, 0); + work = 1; + } else if (test_bit(B_SESS_VLD, &dev->inputs) && + !test_bit(ID_B, &dev->inputs)) { + pr_debug("b_sess_vld\n"); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_PERIPHERAL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_set_power(&dev->otg, 0); + msm_otg_start_peripheral(&dev->otg, 1); + } else if (test_bit(B_BUS_REQ, &dev->inputs)) { + pr_debug("b_sess_end && b_bus_req\n"); + ret = msm_otg_start_srp(&dev->otg); + if (ret < 0) { + /* notify user space */ + clear_bit(B_BUS_REQ, &dev->inputs); + work = 1; + break; + } + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_SRP_INIT; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_timer(dev, TB_SRP_FAIL, B_SRP_FAIL); + break; + } else if (test_bit(ID_B, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->otg, USB_IDCHG_MAX); + } else { + msm_otg_set_power(&dev->otg, 0); + pr_debug("entering into lpm\n"); + msm_otg_put_suspend(dev); + + if (dev->pdata->ldo_set_voltage) + dev->pdata->ldo_set_voltage(3075); + } + break; + case OTG_STATE_B_SRP_INIT: + if (!test_bit(ID, &dev->inputs) || + test_bit(ID_A, &dev->inputs) || + test_bit(ID_C, &dev->inputs) || + (test_bit(B_SESS_VLD, &dev->inputs) && + !test_bit(ID_B, &dev->inputs))) { + pr_debug("!id || id_a/c || b_sess_vld+!id_b\n"); + msm_otg_del_timer(dev); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + work = 1; + } else if (test_bit(B_SRP_FAIL, &dev->tmouts)) { + pr_debug("b_srp_fail\n"); + /* notify user space */ + msm_otg_send_event(&dev->otg, + OTG_EVENT_NO_RESP_FOR_SRP); + clear_bit(B_BUS_REQ, &dev->inputs); + clear_bit(B_SRP_FAIL, &dev->tmouts); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + dev->b_last_se0_sess = jiffies; + work = 1; + } + break; + case OTG_STATE_B_PERIPHERAL: + if (!test_bit(ID, &dev->inputs) || + test_bit(ID_A, &dev->inputs) || + test_bit(ID_B, &dev->inputs) || + !test_bit(B_SESS_VLD, &dev->inputs)) { + pr_debug("!id || id_a/b || !b_sess_vld\n"); + clear_bit(B_BUS_REQ, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_peripheral(&dev->otg, 0); + dev->b_last_se0_sess = jiffies; + + /* Workaround: Reset phy after session */ + otg_reset(&dev->otg, 1); + work = 1; + } else if (test_bit(B_BUS_REQ, &dev->inputs) && + dev->otg.gadget->b_hnp_enable && + test_bit(A_BUS_SUSPEND, &dev->inputs)) { + pr_debug("b_bus_req && b_hnp_en && a_bus_suspend\n"); + msm_otg_start_timer(dev, TB_ASE0_BRST, B_ASE0_BRST); + msm_otg_start_peripheral(&dev->otg, 0); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_WAIT_ACON; + spin_unlock_irqrestore(&dev->lock, flags); + /* start HCD even before A-device enable + * pull-up to meet HNP timings. + */ + dev->otg.host->is_b_host = 1; + msm_otg_start_host(&dev->otg, REQUEST_START); + + } else if (test_bit(ID_C, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->otg, USB_IDCHG_MAX); + } else if (chg_type == USB_CHG_TYPE__WALLCHARGER) { +#ifdef CONFIG_USB_MSM_ACA + del_timer_sync(&dev->id_timer); +#endif + /* Workaround: Reset PHY in SE1 state */ + otg_reset(&dev->otg, 1); + pr_debug("entering into lpm with wall-charger\n"); + msm_otg_put_suspend(dev); + /* Allow idle power collapse */ + otg_pm_qos_update_latency(dev, 0); + } + break; + case OTG_STATE_B_WAIT_ACON: + if (!test_bit(ID, &dev->inputs) || + test_bit(ID_A, &dev->inputs) || + test_bit(ID_B, &dev->inputs) || + !test_bit(B_SESS_VLD, &dev->inputs)) { + pr_debug("!id || id_a/b || !b_sess_vld\n"); + msm_otg_del_timer(dev); + /* A-device is physically disconnected during + * HNP. Remove HCD. + */ + msm_otg_start_host(&dev->otg, REQUEST_STOP); + dev->otg.host->is_b_host = 0; + + clear_bit(B_BUS_REQ, &dev->inputs); + clear_bit(A_BUS_SUSPEND, &dev->inputs); + dev->b_last_se0_sess = jiffies; + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + + /* Workaround: Reset phy after session */ + otg_reset(&dev->otg, 1); + work = 1; + } else if (test_bit(A_CONN, &dev->inputs)) { + pr_debug("a_conn\n"); + clear_bit(A_BUS_SUSPEND, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_HOST; + spin_unlock_irqrestore(&dev->lock, flags); + if (test_bit(ID_C, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->otg, USB_IDCHG_MAX); + } + } else if (test_bit(B_ASE0_BRST, &dev->tmouts)) { + /* TODO: A-device may send reset after + * enabling HNP; a_bus_resume case is + * not handled for now. + */ + pr_debug("b_ase0_brst_tmout\n"); + msm_otg_send_event(&dev->otg, + OTG_EVENT_HNP_FAILED); + msm_otg_start_host(&dev->otg, REQUEST_STOP); + dev->otg.host->is_b_host = 0; + clear_bit(B_ASE0_BRST, &dev->tmouts); + clear_bit(A_BUS_SUSPEND, &dev->inputs); + clear_bit(B_BUS_REQ, &dev->inputs); + + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_PERIPHERAL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_peripheral(&dev->otg, 1); + } else if (test_bit(ID_C, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->otg, USB_IDCHG_MAX); + } + break; + case OTG_STATE_B_HOST: + /* B_BUS_REQ is not exposed to user space. So + * it must be A_CONN for now. + */ + if (!test_bit(B_BUS_REQ, &dev->inputs) || + !test_bit(A_CONN, &dev->inputs)) { + pr_debug("!b_bus_req || !a_conn\n"); + clear_bit(A_CONN, &dev->inputs); + clear_bit(B_BUS_REQ, &dev->inputs); + + msm_otg_start_host(&dev->otg, REQUEST_STOP); + dev->otg.host->is_b_host = 0; + + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + /* Workaround: Reset phy after session */ + otg_reset(&dev->otg, 1); + work = 1; + } else if (test_bit(ID_C, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->otg, USB_IDCHG_MAX); + } + break; + case OTG_STATE_A_IDLE: + dev->otg.default_a = 1; + if (test_bit(ID, &dev->inputs) && + !test_bit(ID_A, &dev->inputs)) { + pr_debug("id && !id_a\n"); + dev->otg.default_a = 0; + otg_reset(&dev->otg, 0); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_B_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_set_power(&dev->otg, 0); + work = 1; + } else if (!test_bit(A_BUS_DROP, &dev->inputs) && + (test_bit(A_SRP_DET, &dev->inputs) || + test_bit(A_BUS_REQ, &dev->inputs))) { + pr_debug("!a_bus_drop && (a_srp_det || a_bus_req)\n"); + + clear_bit(A_SRP_DET, &dev->inputs); + /* Disable SRP detection */ + writel((readl(USB_OTGSC) & ~OTGSC_INTR_STS_MASK) & + ~OTGSC_DPIE, USB_OTGSC); + + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_VRISE; + spin_unlock_irqrestore(&dev->lock, flags); + /* ACA: ID_A: Stop charging untill enumeration */ + if (test_bit(ID_A, &dev->inputs)) + msm_otg_set_power(&dev->otg, 0); + else + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 1); + msm_otg_start_timer(dev, TA_WAIT_VRISE, A_WAIT_VRISE); + /* no need to schedule work now */ + } else { + pr_debug("No session requested\n"); + + /* A-device is not providing power on VBUS. + * Enable SRP detection. + */ + writel((readl(USB_OTGSC) & ~OTGSC_INTR_STS_MASK) | + OTGSC_DPIE, USB_OTGSC); + msm_otg_put_suspend(dev); + + } + break; + case OTG_STATE_A_WAIT_VRISE: + if ((test_bit(ID, &dev->inputs) && + !test_bit(ID_A, &dev->inputs)) || + test_bit(A_BUS_DROP, &dev->inputs) || + test_bit(A_WAIT_VRISE, &dev->tmouts)) { + pr_debug("id || a_bus_drop || a_wait_vrise_tmout\n"); + clear_bit(A_BUS_REQ, &dev->inputs); + msm_otg_del_timer(dev); + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_VFALL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_timer(dev, TA_WAIT_VFALL, A_WAIT_VFALL); + } else if (test_bit(A_VBUS_VLD, &dev->inputs)) { + pr_debug("a_vbus_vld\n"); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_BCON; + spin_unlock_irqrestore(&dev->lock, flags); + if (TA_WAIT_BCON > 0) + msm_otg_start_timer(dev, TA_WAIT_BCON, + A_WAIT_BCON); + /* Start HCD to detect peripherals. */ + msm_otg_start_host(&dev->otg, REQUEST_START); + } + break; + case OTG_STATE_A_WAIT_BCON: + if ((test_bit(ID, &dev->inputs) && + !test_bit(ID_A, &dev->inputs)) || + test_bit(A_BUS_DROP, &dev->inputs) || + test_bit(A_WAIT_BCON, &dev->tmouts)) { + pr_debug("id_f/b/c || a_bus_drop ||" + "a_wait_bcon_tmout\n"); + if (test_bit(A_WAIT_BCON, &dev->tmouts)) + msm_otg_send_event(&dev->otg, + OTG_EVENT_DEV_CONN_TMOUT); + msm_otg_del_timer(dev); + clear_bit(A_BUS_REQ, &dev->inputs); + msm_otg_start_host(&dev->otg, REQUEST_STOP); + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + /* ACA: ID_A with NO accessory, just the A plug is + * attached to ACA: Use IDCHG_MAX for charging + */ + if (test_bit(ID_A, &dev->inputs)) + msm_otg_set_power(&dev->otg, USB_IDCHG_MAX); + else + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_VFALL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_timer(dev, TA_WAIT_VFALL, A_WAIT_VFALL); + } else if (test_bit(B_CONN, &dev->inputs)) { + pr_debug("b_conn\n"); + msm_otg_del_timer(dev); + /* HCD is added already. just move to + * A_HOST state. + */ + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_HOST; + spin_unlock_irqrestore(&dev->lock, flags); + if (test_bit(ID_A, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->otg, + USB_IDCHG_MIN - get_aca_bmaxpower(dev)); + } + } else if (!test_bit(A_VBUS_VLD, &dev->inputs)) { + pr_debug("!a_vbus_vld\n"); + msm_otg_del_timer(dev); + msm_otg_start_host(&dev->otg, REQUEST_STOP); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_VBUS_ERR; + spin_unlock_irqrestore(&dev->lock, flags); + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + } else if (test_bit(ID_A, &dev->inputs)) { + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + } else if (!test_bit(ID, &dev->inputs)) { + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 1); + } + break; + case OTG_STATE_A_HOST: + if ((test_bit(ID, &dev->inputs) && + !test_bit(ID_A, &dev->inputs)) || + test_bit(A_BUS_DROP, &dev->inputs)) { + pr_debug("id_f/b/c || a_bus_drop\n"); + clear_bit(B_CONN, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_VFALL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_host(&dev->otg, REQUEST_STOP); + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + if (!test_bit(ID_A, &dev->inputs)) + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + msm_otg_start_timer(dev, TA_WAIT_VFALL, A_WAIT_VFALL); + msm_otg_set_power(&dev->otg, 0); + } else if (!test_bit(A_VBUS_VLD, &dev->inputs)) { + pr_debug("!a_vbus_vld\n"); + clear_bit(B_CONN, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_VBUS_ERR; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_host(&dev->otg, REQUEST_STOP); + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + /* no work */ + } else if (!test_bit(A_BUS_REQ, &dev->inputs)) { + /* a_bus_req is de-asserted when root hub is + * suspended or HNP is in progress. + */ + pr_debug("!a_bus_req\n"); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_SUSPEND; + spin_unlock_irqrestore(&dev->lock, flags); + if (dev->otg.host->b_hnp_enable) { + msm_otg_start_timer(dev, TA_AIDL_BDIS, + A_AIDL_BDIS); + } else { + /* No HNP. Root hub suspended */ + msm_otg_put_suspend(dev); + } + if (test_bit(ID_A, &dev->inputs)) + msm_otg_set_power(&dev->otg, + USB_IDCHG_MIN - USB_IB_UNCFG); + } else if (!test_bit(B_CONN, &dev->inputs)) { + pr_debug("!b_conn\n"); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_BCON; + spin_unlock_irqrestore(&dev->lock, flags); + if (TA_WAIT_BCON > 0) + msm_otg_start_timer(dev, TA_WAIT_BCON, + A_WAIT_BCON); + } else if (test_bit(ID_A, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + msm_otg_set_power(&dev->otg, + USB_IDCHG_MIN - get_aca_bmaxpower(dev)); + } else if (!test_bit(ID, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__INVALID); + msm_otg_set_power(&dev->otg, 0); + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 1); + } + break; + case OTG_STATE_A_SUSPEND: + if ((test_bit(ID, &dev->inputs) && + !test_bit(ID_A, &dev->inputs)) || + test_bit(A_BUS_DROP, &dev->inputs) || + test_bit(A_AIDL_BDIS, &dev->tmouts)) { + pr_debug("id_f/b/c || a_bus_drop ||" + "a_aidl_bdis_tmout\n"); + if (test_bit(A_AIDL_BDIS, &dev->tmouts)) + msm_otg_send_event(&dev->otg, + OTG_EVENT_HNP_FAILED); + msm_otg_del_timer(dev); + clear_bit(B_CONN, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_VFALL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_host(&dev->otg, REQUEST_STOP); + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + if (!test_bit(ID_A, &dev->inputs)) + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + msm_otg_start_timer(dev, TA_WAIT_VFALL, A_WAIT_VFALL); + msm_otg_set_power(&dev->otg, 0); + } else if (!test_bit(A_VBUS_VLD, &dev->inputs)) { + pr_debug("!a_vbus_vld\n"); + msm_otg_del_timer(dev); + clear_bit(B_CONN, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_VBUS_ERR; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_host(&dev->otg, REQUEST_STOP); + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + } else if (!test_bit(B_CONN, &dev->inputs) && + dev->otg.host->b_hnp_enable) { + pr_debug("!b_conn && b_hnp_enable"); + /* Clear AIDL_BDIS timer */ + msm_otg_del_timer(dev); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_PERIPHERAL; + spin_unlock_irqrestore(&dev->lock, flags); + + msm_otg_start_host(&dev->otg, REQUEST_HNP_SUSPEND); + + /* We may come here even when B-dev is physically + * disconnected during HNP. We go back to host + * role if bus is idle for BIDL_ADIS time. + */ + dev->otg.gadget->is_a_peripheral = 1; + msm_otg_start_peripheral(&dev->otg, 1); + /* If ID_A: we can charge in a_peripheral as well */ + if (test_bit(ID_A, &dev->inputs)) { + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->otg, + USB_IDCHG_MIN - USB_IB_UNCFG); + } + } else if (!test_bit(B_CONN, &dev->inputs) && + !dev->otg.host->b_hnp_enable) { + pr_debug("!b_conn && !b_hnp_enable"); + /* bus request is dropped during suspend. + * acquire again for next device. + */ + set_bit(A_BUS_REQ, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_BCON; + spin_unlock_irqrestore(&dev->lock, flags); + if (TA_WAIT_BCON > 0) + msm_otg_start_timer(dev, TA_WAIT_BCON, + A_WAIT_BCON); + msm_otg_set_power(&dev->otg, 0); + } else if (test_bit(ID_A, &dev->inputs)) { + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->otg, + USB_IDCHG_MIN - USB_IB_UNCFG); + } else if (!test_bit(ID, &dev->inputs)) { + msm_otg_set_power(&dev->otg, 0); + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 1); + } + break; + case OTG_STATE_A_PERIPHERAL: + if ((test_bit(ID, &dev->inputs) && + !test_bit(ID_A, &dev->inputs)) || + test_bit(A_BUS_DROP, &dev->inputs)) { + pr_debug("id _f/b/c || a_bus_drop\n"); + /* Clear BIDL_ADIS timer */ + msm_otg_del_timer(dev); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_VFALL; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_peripheral(&dev->otg, 0); + dev->otg.gadget->is_a_peripheral = 0; + /* HCD was suspended before. Stop it now */ + msm_otg_start_host(&dev->otg, REQUEST_STOP); + + /* Reset both phy and link */ + otg_reset(&dev->otg, 1); + if (!test_bit(ID_A, &dev->inputs)) + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + msm_otg_start_timer(dev, TA_WAIT_VFALL, A_WAIT_VFALL); + msm_otg_set_power(&dev->otg, 0); + } else if (!test_bit(A_VBUS_VLD, &dev->inputs)) { + pr_debug("!a_vbus_vld\n"); + /* Clear BIDL_ADIS timer */ + msm_otg_del_timer(dev); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_VBUS_ERR; + spin_unlock_irqrestore(&dev->lock, flags); + msm_otg_start_peripheral(&dev->otg, 0); + dev->otg.gadget->is_a_peripheral = 0; + /* HCD was suspended before. Stop it now */ + msm_otg_start_host(&dev->otg, REQUEST_STOP); + } else if (test_bit(A_BIDL_ADIS, &dev->tmouts)) { + pr_debug("a_bidl_adis_tmout\n"); + msm_otg_start_peripheral(&dev->otg, 0); + dev->otg.gadget->is_a_peripheral = 0; + + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_BCON; + spin_unlock_irqrestore(&dev->lock, flags); + set_bit(A_BUS_REQ, &dev->inputs); + msm_otg_start_host(&dev->otg, REQUEST_HNP_RESUME); + if (TA_WAIT_BCON > 0) + msm_otg_start_timer(dev, TA_WAIT_BCON, + A_WAIT_BCON); + msm_otg_set_power(&dev->otg, 0); + } else if (test_bit(ID_A, &dev->inputs)) { + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + atomic_set(&dev->chg_type, USB_CHG_TYPE__SDP); + msm_otg_set_power(&dev->otg, + USB_IDCHG_MIN - USB_IB_UNCFG); + } else if (!test_bit(ID, &dev->inputs)) { + msm_otg_set_power(&dev->otg, 0); + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 1); + } + break; + case OTG_STATE_A_WAIT_VFALL: + if (test_bit(A_WAIT_VFALL, &dev->tmouts)) { + clear_bit(A_VBUS_VLD, &dev->inputs); + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_IDLE; + spin_unlock_irqrestore(&dev->lock, flags); + work = 1; + } + break; + case OTG_STATE_A_VBUS_ERR: + if ((test_bit(ID, &dev->inputs) && + !test_bit(ID_A, &dev->inputs)) || + test_bit(A_BUS_DROP, &dev->inputs) || + test_bit(A_CLR_ERR, &dev->inputs)) { + spin_lock_irqsave(&dev->lock, flags); + dev->otg.state = OTG_STATE_A_WAIT_VFALL; + spin_unlock_irqrestore(&dev->lock, flags); + if (!test_bit(ID_A, &dev->inputs)) + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + msm_otg_start_timer(dev, TA_WAIT_VFALL, A_WAIT_VFALL); + msm_otg_set_power(&dev->otg, 0); + } + break; + default: + pr_err("invalid OTG state\n"); + } + + if (work) + queue_work(dev->wq, &dev->sm_work); + +#ifdef CONFIG_USB_MSM_ACA + /* Start id_polling if (ID_FLOAT&BSV) || ID_A/B/C */ + if ((test_bit(ID, &dev->inputs) && + test_bit(B_SESS_VLD, &dev->inputs) && + chg_type != USB_CHG_TYPE__WALLCHARGER) || + test_bit(ID_A, &dev->inputs)) { + mod_timer(&dev->id_timer, jiffies + + msecs_to_jiffies(OTG_ID_POLL_MS)); + return; + } + del_timer(&dev->id_timer); +#endif + /* IRQ/sysfs may queue work. Check work_pending. otherwise + * we might endup releasing wakelock after it is acquired + * in IRQ/sysfs. + */ + if (!work_pending(&dev->sm_work) && !hrtimer_active(&dev->timer) && + !work_pending(&dev->otg_resume_work)) + wake_unlock(&dev->wlock); +} + +#ifdef CONFIG_USB_MSM_ACA +static void msm_otg_id_func(unsigned long _dev) +{ + struct msm_otg *dev = (struct msm_otg *) _dev; + u8 phy_ints; + +#ifdef CONFIG_USB_MSM_STANDARD_ACA + /* + * When standard ACA is attached RID_A and RID_GND states are only + * possible. RID_A-->RID_GND transition generates IdGnd interrupt + * from PHY. Hence polling is disabled. + */ + if (test_bit(ID_A, &dev->inputs)) + goto out; +#endif + + if (atomic_read(&dev->in_lpm)) + msm_otg_set_suspend(&dev->otg, 0); + + phy_ints = ulpi_read(dev, 0x13); + + /* + * ACA timer will be kicked again after the PHY + * state is recovered. + */ + if (phy_ints == -ETIMEDOUT) + return; + + + /* If id_gnd happened then stop and let isr take care of this */ + if (phy_id_state_gnd(phy_ints)) + goto out; + + if ((test_bit(ID_A, &dev->inputs) == phy_id_state_a(phy_ints)) && + (test_bit(ID_B, &dev->inputs) == phy_id_state_b(phy_ints)) && + (test_bit(ID_C, &dev->inputs) == phy_id_state_c(phy_ints))) { + mod_timer(&dev->id_timer, + jiffies + msecs_to_jiffies(OTG_ID_POLL_MS)); + goto out; + } else { + set_aca_id_inputs(dev); + } + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); +out: + /* OOPS: runing while !BSV, schedule work to initiate LPM */ + if (!is_b_sess_vld()) { + clear_bit(B_SESS_VLD, &dev->inputs); + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } + return; +} +#endif +#ifdef CONFIG_USB_OTG +static ssize_t +set_pwr_down(struct device *_dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_otg *dev = the_msm_otg; + int value; + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->otg.state; + spin_unlock_irqrestore(&dev->lock, flags); + + /* Applicable for only A-Device */ + if (state <= OTG_STATE_A_IDLE) + return -EINVAL; + + sscanf(buf, "%d", &value); + + if (test_bit(A_BUS_DROP, &dev->inputs) != !!value) { + change_bit(A_BUS_DROP, &dev->inputs); + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } + + return count; +} +static DEVICE_ATTR(pwr_down, S_IRUGO | S_IWUSR, NULL, set_pwr_down); + +static ssize_t +set_srp_req(struct device *_dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_otg *dev = the_msm_otg; + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->otg.state; + spin_unlock_irqrestore(&dev->lock, flags); + + if (state != OTG_STATE_B_IDLE) + return -EINVAL; + + set_bit(B_BUS_REQ, &dev->inputs); + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + + return count; +} +static DEVICE_ATTR(srp_req, S_IRUGO | S_IWUSR, NULL, set_srp_req); + +static ssize_t +set_clr_err(struct device *_dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_otg *dev = the_msm_otg; + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + state = dev->otg.state; + spin_unlock_irqrestore(&dev->lock, flags); + + if (state == OTG_STATE_A_VBUS_ERR) { + set_bit(A_CLR_ERR, &dev->inputs); + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } + + return count; +} +static DEVICE_ATTR(clr_err, S_IRUGO | S_IWUSR, NULL, set_clr_err); + +static struct attribute *msm_otg_attrs[] = { + &dev_attr_pwr_down.attr, + &dev_attr_srp_req.attr, + &dev_attr_clr_err.attr, + NULL, +}; + +static struct attribute_group msm_otg_attr_grp = { + .attrs = msm_otg_attrs, +}; +#endif + +#ifdef CONFIG_DEBUG_FS +static int otg_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +static ssize_t otg_mode_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct msm_otg *dev = file->private_data; + int ret = count; + int work = 0; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->pdata->otg_mode = OTG_USER_CONTROL; + if (!memcmp(buf, "none", count - 1)) { + clear_bit(B_SESS_VLD, &dev->inputs); + set_bit(ID, &dev->inputs); + work = 1; + } else if (!memcmp(buf, "peripheral", count - 1)) { + set_bit(B_SESS_VLD, &dev->inputs); + set_bit(ID, &dev->inputs); + work = 1; + } else if (!memcmp(buf, "host", count - 1)) { + clear_bit(B_SESS_VLD, &dev->inputs); + clear_bit(ID, &dev->inputs); + set_bit(A_BUS_REQ, &dev->inputs); + work = 1; + } else { + pr_info("%s: unknown mode specified\n", __func__); + ret = -EINVAL; + } + spin_unlock_irqrestore(&dev->lock, flags); + + if (work) { + wake_lock(&dev->wlock); + queue_work(dev->wq, &dev->sm_work); + } + + return ret; +} +const struct file_operations otgfs_fops = { + .open = otg_open, + .write = otg_mode_write, +}; + +#define OTG_INFO_SIZE 512 +static ssize_t otg_info_read(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf; + int temp = 0; + int ret; + struct msm_otg *dev = file->private_data; + + buf = kzalloc(sizeof(char) * OTG_INFO_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + temp += scnprintf(buf + temp, OTG_INFO_SIZE - temp, + "OTG State: %s\n" + "OTG Mode: %d\n" + "OTG Inputs: 0x%lx\n" + "Charger Type: %d\n" + "PMIC VBUS Support: %u\n" + "PMIC ID Support: %u\n" + "Core Clock: %u\n" + "USB In SPS: %d\n" + "pre_emphasis_level: 0x%x\n" + "cdr_auto_reset: 0x%x\n" + "hs_drv_amplitude: 0x%x\n" + "se1_gate_state: 0x%x\n" + "swfi_latency: 0x%x\n" + "PHY Powercollapse: 0x%x\n" + "PCLK Voting: 0x%x\n", + state_string(dev->otg.state), + dev->pdata->otg_mode, + dev->inputs, + atomic_read(&dev->chg_type), + dev->pmic_vbus_notif_supp, + dev->pmic_id_notif_supp, + dev->pdata->core_clk, + dev->pdata->usb_in_sps, + dev->pdata->pemp_level, + dev->pdata->cdr_autoreset, + dev->pdata->drv_ampl, + dev->pdata->se1_gating, + dev->pdata->swfi_latency, + dev->pdata->phy_can_powercollapse, + pclk_requires_voting(&dev->otg)); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +const struct file_operations otgfs_info_fops = { + .open = otg_open, + .read = otg_info_read, +}; + +struct dentry *otg_debug_root; +struct dentry *otg_debug_mode; +struct dentry *otg_debug_info; +#endif + +static int otg_debugfs_init(struct msm_otg *dev) +{ +#ifdef CONFIG_DEBUG_FS + otg_debug_root = debugfs_create_dir("otg", NULL); + if (!otg_debug_root) + return -ENOENT; + + otg_debug_mode = debugfs_create_file("mode", 0222, + otg_debug_root, dev, + &otgfs_fops); + if (!otg_debug_mode) + goto free_root; + + otg_debug_info = debugfs_create_file("info", 0444, + otg_debug_root, dev, + &otgfs_info_fops); + if (!otg_debug_info) + goto free_mode; + + return 0; + +free_mode: + debugfs_remove(otg_debug_mode); + otg_debug_mode = NULL; + +free_root: + debugfs_remove(otg_debug_root); + otg_debug_root = NULL; + return -ENOENT; +#endif + return 0; +} + +static void otg_debugfs_cleanup(void) +{ +#ifdef CONFIG_DEBUG_FS + debugfs_remove(otg_debug_info); + debugfs_remove(otg_debug_mode); + debugfs_remove(otg_debug_root); +#endif +} + +struct otg_io_access_ops msm_otg_io_ops = { + .read = usb_ulpi_read, + .write = usb_ulpi_write, +}; + +static int __init msm_otg_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res; + struct msm_otg *dev; + + dev = kzalloc(sizeof(struct msm_otg), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + the_msm_otg = dev; + dev->otg.dev = &pdev->dev; + dev->pdata = pdev->dev.platform_data; + + if (!dev->pdata) { + ret = -ENODEV; + goto free_dev; + } + +#ifdef CONFIG_USB_EHCI_MSM_72K + if (!dev->pdata->vbus_power) { + ret = -ENODEV; + goto free_dev; + } else + dev->pdata->vbus_power(USB_PHY_INTEGRATED, 0); + +#endif + + + if (dev->pdata->rpc_connect) { + ret = dev->pdata->rpc_connect(1); + pr_debug("%s: rpc_connect(%d)\n", __func__, ret); + if (ret) { + pr_err("%s: rpc connect failed\n", __func__); + ret = -ENODEV; + goto free_dev; + } + } + + dev->hs_clk = clk_get(&pdev->dev, "usb_hs_clk"); + if (IS_ERR(dev->hs_clk)) { + pr_err("%s: failed to get usb_hs_clk\n", __func__); + ret = PTR_ERR(dev->hs_clk); + goto rpc_fail; + } + clk_set_rate(dev->hs_clk, 60000000); + + /* pm qos request to prevent apps idle power collapse */ + pm_qos_add_request(&dev->pdata->pm_qos_req_dma, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + /* If USB Core is running its protocol engine based on PCLK, + * PCLK must be running at >60Mhz for correct HSUSB operation and + * USB core cannot tolerate frequency changes on PCLK. For such + * USB cores, vote for maximum clk frequency on pclk source + */ + if (dev->pdata->pclk_src_name) { + dev->pclk_src = clk_get(0, dev->pdata->pclk_src_name); + if (IS_ERR(dev->pclk_src)) + goto put_hs_clk; + clk_set_rate(dev->pclk_src, INT_MAX); + msm_otg_vote_for_pclk_source(dev, 1); + } + + if (!dev->pdata->pclk_is_hw_gated) { + dev->hs_pclk = clk_get(&pdev->dev, "usb_hs_pclk"); + if (IS_ERR(dev->hs_pclk)) { + pr_err("%s: failed to get usb_hs_pclk\n", __func__); + ret = PTR_ERR(dev->hs_pclk); + goto put_pclk_src; + } + clk_enable(dev->hs_pclk); + } + + if (dev->pdata->core_clk) { + dev->hs_cclk = clk_get(&pdev->dev, "usb_hs_core_clk"); + if (IS_ERR(dev->hs_cclk)) { + pr_err("%s: failed to get usb_hs_core_clk\n", __func__); + ret = PTR_ERR(dev->hs_cclk); + goto put_hs_pclk; + } + clk_enable(dev->hs_cclk); + } + + if (!dev->pdata->phy_reset) { + dev->phy_reset_clk = clk_get(&pdev->dev, "usb_phy_clk"); + if (IS_ERR(dev->phy_reset_clk)) { + pr_err("%s: failed to get usb_phy_clk\n", __func__); + ret = PTR_ERR(dev->phy_reset_clk); + goto put_hs_cclk; + } + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + pr_err("%s: failed to get platform resource mem\n", __func__); + ret = -ENODEV; + goto put_phy_clk; + } + + dev->regs = ioremap(res->start, resource_size(res)); + if (!dev->regs) { + pr_err("%s: ioremap failed\n", __func__); + ret = -ENOMEM; + goto put_phy_clk; + } + dev->irq = platform_get_irq(pdev, 0); + if (!dev->irq) { + pr_err("%s: platform_get_irq failed\n", __func__); + ret = -ENODEV; + goto free_regs; + } + dev->xo_handle = msm_xo_get(MSM_XO_TCXO_D1, "usb"); + if (IS_ERR(dev->xo_handle)) { + pr_err(" %s not able to get the handle" + "to vote for TCXO D1 buffer\n", __func__); + ret = PTR_ERR(dev->xo_handle); + goto free_regs; + } + + ret = msm_xo_mode_vote(dev->xo_handle, MSM_XO_MODE_ON); + if (ret) { + pr_err("%s failed to vote for TCXO" + "D1 buffer%d\n", __func__, ret); + goto free_xo_handle; + } + + + msm_otg_init_timer(dev); + INIT_WORK(&dev->sm_work, msm_otg_sm_work); + INIT_WORK(&dev->otg_resume_work, msm_otg_resume_w); + spin_lock_init(&dev->lock); + wake_lock_init(&dev->wlock, WAKE_LOCK_SUSPEND, "msm_otg"); + + dev->wq = alloc_workqueue("k_otg", WQ_NON_REENTRANT, 0); + if (!dev->wq) { + ret = -ENOMEM; + goto free_wlock; + } + + if (dev->pdata->init_gpio) { + ret = dev->pdata->init_gpio(1); + if (ret) { + pr_err("%s: gpio init failed with err:%d\n", + __func__, ret); + goto free_wq; + } + } + /* To reduce phy power consumption and to avoid external LDO + * on the board, PMIC comparators can be used to detect VBUS + * session change. + */ + if (dev->pdata->pmic_vbus_notif_init) { + ret = dev->pdata->pmic_vbus_notif_init + (&msm_otg_set_vbus_state, 1); + if (!ret) { + dev->pmic_vbus_notif_supp = 1; + } else if (ret != -ENOTSUPP) { + pr_err("%s: pmic_vbus_notif_init() failed, err:%d\n", + __func__, ret); + goto free_gpio; + } + } + + if (dev->pdata->pmic_id_notif_init) { + ret = dev->pdata->pmic_id_notif_init(&msm_otg_set_id_state, 1); + if (!ret) { + dev->pmic_id_notif_supp = 1; + } else if (ret != -ENOTSUPP) { + pr_err("%s: pmic_id_ notif_init failed err:%d", + __func__, ret); + goto free_pmic_vbus_notif; + } + } + + if (dev->pdata->pmic_vbus_irq) + dev->vbus_on_irq = dev->pdata->pmic_vbus_irq; + + /* vote for vddcx, as PHY cannot tolerate vddcx below 1.0V */ + if (dev->pdata->init_vddcx) { + ret = dev->pdata->init_vddcx(1); + if (ret) { + pr_err("%s: unable to enable vddcx digital core:%d\n", + __func__, ret); + goto free_pmic_id_notif; + } + } + + if (dev->pdata->ldo_init) { + ret = dev->pdata->ldo_init(1); + if (ret) { + pr_err("%s: ldo_init failed with err:%d\n", + __func__, ret); + goto free_config_vddcx; + } + } + + if (dev->pdata->ldo_enable) { + ret = dev->pdata->ldo_enable(1); + if (ret) { + pr_err("%s: ldo_enable failed with err:%d\n", + __func__, ret); + goto free_ldo_init; + } + } + + + /* ACk all pending interrupts and clear interrupt enable registers */ + writel((readl(USB_OTGSC) & ~OTGSC_INTR_MASK), USB_OTGSC); + writel(readl(USB_USBSTS), USB_USBSTS); + writel(0, USB_USBINTR); + /* Ensure that above STOREs are completed before enabling interrupts */ + mb(); + + ret = request_irq(dev->irq, msm_otg_irq, IRQF_SHARED, + "msm_otg", dev); + if (ret) { + pr_err("%s: request irq failed\n", __func__); + goto free_ldo_enable; + } + + dev->otg.set_peripheral = msm_otg_set_peripheral; +#ifdef CONFIG_USB_EHCI_MSM_72K + dev->otg.set_host = msm_otg_set_host; +#endif + dev->otg.set_suspend = msm_otg_set_suspend; + dev->otg.start_hnp = msm_otg_start_hnp; + dev->otg.send_event = msm_otg_send_event; + dev->otg.set_power = msm_otg_set_power; + dev->set_clk = msm_otg_set_clk; + dev->reset = otg_reset; + dev->otg.io_ops = &msm_otg_io_ops; + if (otg_set_transceiver(&dev->otg)) { + WARN_ON(1); + goto free_otg_irq; + } +#ifdef CONFIG_USB_MSM_ACA + /* Link doesnt support id_a/b/c interrupts, hence polling + * needs to be done to support ACA charger + */ + init_timer(&dev->id_timer); + dev->id_timer.function = msm_otg_id_func; + dev->id_timer.data = (unsigned long) dev; +#endif + + atomic_set(&dev->chg_type, USB_CHG_TYPE__INVALID); + if (dev->pdata->chg_init && dev->pdata->chg_init(1)) + pr_err("%s: chg_init failed\n", __func__); + + device_init_wakeup(&pdev->dev, 1); + + ret = pm_runtime_set_active(&pdev->dev); + if (ret < 0) + pr_err("%s: pm_runtime: Fail to set active\n", __func__); + + ret = 0; + pm_runtime_enable(&pdev->dev); + pm_runtime_get(&pdev->dev); + + + ret = otg_debugfs_init(dev); + if (ret) { + pr_err("%s: otg_debugfs_init failed\n", __func__); + goto chg_deinit; + } + +#ifdef CONFIG_USB_OTG + ret = sysfs_create_group(&pdev->dev.kobj, &msm_otg_attr_grp); + if (ret < 0) { + pr_err("%s: Failed to create the sysfs entry\n", __func__); + otg_debugfs_cleanup(); + goto chg_deinit; + } +#endif + + + return 0; + +chg_deinit: + if (dev->pdata->chg_init) + dev->pdata->chg_init(0); +free_otg_irq: + free_irq(dev->irq, dev); +free_ldo_enable: + if (dev->pdata->ldo_enable) + dev->pdata->ldo_enable(0); + if (dev->pdata->setup_gpio) + dev->pdata->setup_gpio(USB_SWITCH_DISABLE); +free_ldo_init: + if (dev->pdata->ldo_init) + dev->pdata->ldo_init(0); +free_config_vddcx: + if (dev->pdata->init_vddcx) + dev->pdata->init_vddcx(0); +free_pmic_id_notif: + if (dev->pdata->pmic_id_notif_init && dev->pmic_id_notif_supp) + dev->pdata->pmic_id_notif_init(&msm_otg_set_id_state, 0); +free_pmic_vbus_notif: + if (dev->pdata->pmic_vbus_notif_init && dev->pmic_vbus_notif_supp) + dev->pdata->pmic_vbus_notif_init(&msm_otg_set_vbus_state, 0); +free_gpio: + if (dev->pdata->init_gpio) + dev->pdata->init_gpio(0); +free_wq: + destroy_workqueue(dev->wq); +free_wlock: + wake_lock_destroy(&dev->wlock); +free_xo_handle: + msm_xo_put(dev->xo_handle); +free_regs: + iounmap(dev->regs); +put_phy_clk: + if (dev->phy_reset_clk) + clk_put(dev->phy_reset_clk); +put_hs_cclk: + if (dev->hs_cclk) { + clk_disable(dev->hs_cclk); + clk_put(dev->hs_cclk); + } +put_hs_pclk: + if (dev->hs_pclk) { + clk_disable(dev->hs_pclk); + clk_put(dev->hs_pclk); + } +put_pclk_src: + if (dev->pclk_src) { + msm_otg_vote_for_pclk_source(dev, 0); + clk_put(dev->pclk_src); + } +put_hs_clk: + if (dev->hs_clk) + clk_put(dev->hs_clk); +rpc_fail: + if (dev->pdata->rpc_connect) + dev->pdata->rpc_connect(0); +free_dev: + kfree(dev); + return ret; +} + +static int __exit msm_otg_remove(struct platform_device *pdev) +{ + struct msm_otg *dev = the_msm_otg; + + otg_debugfs_cleanup(); +#ifdef CONFIG_USB_OTG + sysfs_remove_group(&pdev->dev.kobj, &msm_otg_attr_grp); +#endif + destroy_workqueue(dev->wq); + wake_lock_destroy(&dev->wlock); + + if (dev->pdata->setup_gpio) + dev->pdata->setup_gpio(USB_SWITCH_DISABLE); + + if (dev->pdata->init_vddcx) + dev->pdata->init_vddcx(0); + if (dev->pdata->ldo_enable) + dev->pdata->ldo_enable(0); + + if (dev->pdata->ldo_init) + dev->pdata->ldo_init(0); + + if (dev->pmic_vbus_notif_supp) + dev->pdata->pmic_vbus_notif_init(&msm_otg_set_vbus_state, 0); + + if (dev->pmic_id_notif_supp) + dev->pdata->pmic_id_notif_init(&msm_otg_set_id_state, 0); + +#ifdef CONFIG_USB_MSM_ACA + del_timer_sync(&dev->id_timer); +#endif + if (dev->pdata->chg_init) + dev->pdata->chg_init(0); + free_irq(dev->irq, pdev); + iounmap(dev->regs); + if (dev->hs_cclk) { + clk_disable(dev->hs_cclk); + clk_put(dev->hs_cclk); + } + if (dev->hs_pclk) { + clk_disable(dev->hs_pclk); + clk_put(dev->hs_pclk); + } + if (dev->hs_clk) + clk_put(dev->hs_clk); + if (dev->phy_reset_clk) + clk_put(dev->phy_reset_clk); + if (dev->pdata->rpc_connect) + dev->pdata->rpc_connect(0); + msm_xo_put(dev->xo_handle); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + kfree(dev); + pm_qos_remove_request(&dev->pdata->pm_qos_req_dma); + clk_put(dev->pclk_src); + return 0; +} + +static int msm_otg_runtime_suspend(struct device *dev) +{ + struct msm_otg *otg = the_msm_otg; + + dev_dbg(dev, "pm_runtime: suspending...\n"); + msm_otg_suspend(otg); + return 0; +} + +static int msm_otg_runtime_resume(struct device *dev) +{ + struct msm_otg *otg = the_msm_otg; + + dev_dbg(dev, "pm_runtime: resuming...\n"); + msm_otg_resume(otg); + return 0; +} + +static int msm_otg_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: idling...\n"); + return 0; +} + +static struct dev_pm_ops msm_otg_dev_pm_ops = { + .runtime_suspend = msm_otg_runtime_suspend, + .runtime_resume = msm_otg_runtime_resume, + .runtime_idle = msm_otg_runtime_idle, +}; + +static struct platform_driver msm_otg_driver = { + .remove = __exit_p(msm_otg_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &msm_otg_dev_pm_ops, + }, +}; + +static int __init msm_otg_init(void) +{ + return platform_driver_probe(&msm_otg_driver, msm_otg_probe); +} + +static void __exit msm_otg_exit(void) +{ + platform_driver_unregister(&msm_otg_driver); +} + +module_init(msm_otg_init); +module_exit(msm_otg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM usb transceiver driver"); +MODULE_VERSION("1.00"); diff --git a/drivers/usb/otg/msm_otg.c b/drivers/usb/otg/msm_otg.c index b276f8fcdeb..ead2976571c 100644 --- a/drivers/usb/otg/msm_otg.c +++ b/drivers/usb/otg/msm_otg.c @@ -9,11 +9,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - * */ #include @@ -39,12 +34,34 @@ #include #include #include +#include #include #define MSM_USB_BASE (motg->regs) #define DRIVER_NAME "msm_otg" +#ifdef CONFIG_USB_MSM_ACA +static void msm_chg_enable_aca_det(struct msm_otg *motg); +static void msm_chg_enable_aca_intr(struct msm_otg *motg); +#else +static inline bool msm_chg_aca_detect(struct msm_otg *motg) +{ + return false; +} + +static inline void msm_chg_enable_aca_det(struct msm_otg *motg) +{ +} +static inline void msm_chg_enable_aca_intr(struct msm_otg *motg) +{ +} +static inline bool msm_chg_check_aca_intr(struct msm_otg *motg) +{ + return false; +} +#endif + #define ULPI_IO_TIMEOUT_USEC (10 * 1000) #define USB_PHY_3P3_VOL_MIN 3050000 /* uV */ @@ -60,6 +77,8 @@ #define USB_PHY_VDD_DIG_VOL_MIN 1000000 /* uV */ #define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */ +static struct msm_otg *the_msm_otg; + static struct regulator *hsusb_3p3; static struct regulator *hsusb_1p8; static struct regulator *hsusb_vddcx; @@ -87,18 +106,28 @@ static int msm_hsusb_init_vddcx(struct msm_otg *motg, int init) ret = regulator_enable(hsusb_vddcx); if (ret) { - dev_err(motg->otg.dev, "unable to enable hsusb vddcx\n"); + regulator_set_voltage(hsusb_vddcx, 0, + USB_PHY_VDD_DIG_VOL_MIN); regulator_put(hsusb_vddcx); + dev_err(motg->otg.dev, "unable to enable the hsusb vddcx\n"); + return ret; } + } else { - ret = regulator_set_voltage(hsusb_vddcx, 0, - USB_PHY_VDD_DIG_VOL_MAX); - if (ret) - dev_err(motg->otg.dev, "unable to set the voltage " - "for hsusb vddcx\n"); + ret = regulator_disable(hsusb_vddcx); - if (ret) + if (ret) { dev_err(motg->otg.dev, "unable to disable hsusb vddcx\n"); + return ret; + } + + ret = regulator_set_voltage(hsusb_vddcx, 0, + USB_PHY_VDD_DIG_VOL_MIN); + if (ret) { + dev_err(motg->otg.dev, "unable to set the voltage" + "for hsusb vddcx\n"); + return ret; + } regulator_put(hsusb_vddcx); } @@ -120,42 +149,32 @@ static int msm_hsusb_ldo_init(struct msm_otg *motg, int init) rc = regulator_set_voltage(hsusb_3p3, USB_PHY_3P3_VOL_MIN, USB_PHY_3P3_VOL_MAX); if (rc) { - dev_err(motg->otg.dev, "unable to set voltage level " - "for hsusb 3p3\n"); - goto put_3p3; - } - rc = regulator_enable(hsusb_3p3); - if (rc) { - dev_err(motg->otg.dev, "unable to enable the hsusb 3p3\n"); + dev_err(motg->otg.dev, "unable to set voltage level for" + "hsusb 3p3\n"); goto put_3p3; } hsusb_1p8 = regulator_get(motg->otg.dev, "HSUSB_1p8"); if (IS_ERR(hsusb_1p8)) { dev_err(motg->otg.dev, "unable to get hsusb 1p8\n"); rc = PTR_ERR(hsusb_1p8); - goto disable_3p3; + goto put_3p3_lpm; } rc = regulator_set_voltage(hsusb_1p8, USB_PHY_1P8_VOL_MIN, USB_PHY_1P8_VOL_MAX); if (rc) { - dev_err(motg->otg.dev, "unable to set voltage level " - "for hsusb 1p8\n"); - goto put_1p8; - } - rc = regulator_enable(hsusb_1p8); - if (rc) { - dev_err(motg->otg.dev, "unable to enable the hsusb 1p8\n"); + dev_err(motg->otg.dev, "unable to set voltage level for" + "hsusb 1p8\n"); goto put_1p8; } return 0; } - regulator_disable(hsusb_1p8); put_1p8: + regulator_set_voltage(hsusb_1p8, 0, USB_PHY_1P8_VOL_MAX); regulator_put(hsusb_1p8); -disable_3p3: - regulator_disable(hsusb_3p3); +put_3p3_lpm: + regulator_set_voltage(hsusb_3p3, 0, USB_PHY_3P3_VOL_MAX); put_3p3: regulator_put(hsusb_3p3); return rc; @@ -187,7 +206,7 @@ static int msm_hsusb_config_vddcx(int high) } #endif -static int msm_hsusb_ldo_set_mode(int on) +static int msm_hsusb_ldo_enable(struct msm_otg *motg, int on) { int ret = 0; @@ -205,29 +224,61 @@ static int msm_hsusb_ldo_set_mode(int on) ret = regulator_set_optimum_mode(hsusb_1p8, USB_PHY_1P8_HPM_LOAD); if (ret < 0) { - pr_err("%s: Unable to set HPM of the regulator " + pr_err("%s: Unable to set HPM of the regulator:" "HSUSB_1p8\n", __func__); return ret; } + + ret = regulator_enable(hsusb_1p8); + if (ret) { + dev_err(motg->otg.dev, "%s: unable to enable the hsusb 1p8\n", + __func__); + regulator_set_optimum_mode(hsusb_1p8, 0); + return ret; + } + ret = regulator_set_optimum_mode(hsusb_3p3, USB_PHY_3P3_HPM_LOAD); if (ret < 0) { - pr_err("%s: Unable to set HPM of the regulator " + pr_err("%s: Unable to set HPM of the regulator:" "HSUSB_3p3\n", __func__); - regulator_set_optimum_mode(hsusb_1p8, - USB_PHY_1P8_LPM_LOAD); + regulator_set_optimum_mode(hsusb_1p8, 0); + regulator_disable(hsusb_1p8); + return ret; + } + + ret = regulator_enable(hsusb_3p3); + if (ret) { + dev_err(motg->otg.dev, "%s: unable to enable the hsusb 3p3\n", + __func__); + regulator_set_optimum_mode(hsusb_3p3, 0); + regulator_set_optimum_mode(hsusb_1p8, 0); + regulator_disable(hsusb_1p8); return ret; } + } else { - ret = regulator_set_optimum_mode(hsusb_1p8, - USB_PHY_1P8_LPM_LOAD); + ret = regulator_disable(hsusb_1p8); + if (ret) { + dev_err(motg->otg.dev, "%s: unable to disable the hsusb 1p8\n", + __func__); + return ret; + } + + ret = regulator_set_optimum_mode(hsusb_1p8, 0); if (ret < 0) - pr_err("%s: Unable to set LPM of the regulator " + pr_err("%s: Unable to set LPM of the regulator:" "HSUSB_1p8\n", __func__); - ret = regulator_set_optimum_mode(hsusb_3p3, - USB_PHY_3P3_LPM_LOAD); + + ret = regulator_disable(hsusb_3p3); + if (ret) { + dev_err(motg->otg.dev, "%s: unable to disable the hsusb 3p3\n", + __func__); + return ret; + } + ret = regulator_set_optimum_mode(hsusb_3p3, 0); if (ret < 0) - pr_err("%s: Unable to set LPM of the regulator " + pr_err("%s: Unable to set LPM of the regulator:" "HSUSB_3p3\n", __func__); } @@ -399,6 +450,7 @@ static int msm_otg_reset(struct otg_transceiver *otg) u32 val = 0; u32 ulpi_val = 0; + clk_enable(motg->clk); ret = msm_otg_phy_reset(motg); if (ret) { dev_err(otg->dev, "phy_reset failed\n"); @@ -425,19 +477,24 @@ static int msm_otg_reset(struct otg_transceiver *otg) writel(0x0, USB_AHBBURST); writel(0x00, USB_AHBMODE); - if (pdata->otg_control == OTG_PHY_CONTROL) { - val = readl(USB_OTGSC); - if (pdata->mode == USB_OTG) { - ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID; - val |= OTGSC_IDIE | OTGSC_BSVIE; - } else if (pdata->mode == USB_PERIPHERAL) { - ulpi_val = ULPI_INT_SESS_VALID; - val |= OTGSC_BSVIE; - } - writel(val, USB_OTGSC); - ulpi_write(otg, ulpi_val, ULPI_USB_INT_EN_RISE); - ulpi_write(otg, ulpi_val, ULPI_USB_INT_EN_FALL); + /* Ensure that RESET operation is completed before turning off clock */ + mb(); + clk_disable(motg->clk); + + val = readl_relaxed(USB_OTGSC); + if (pdata->mode == USB_OTG) { + ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID; + val |= OTGSC_IDIE | OTGSC_BSVIE; + } else if (pdata->mode == USB_PERIPHERAL) { + ulpi_val = ULPI_INT_SESS_VALID; + val |= OTGSC_BSVIE; } + writel_relaxed(val, USB_OTGSC); + ulpi_write(otg, ulpi_val, ULPI_USB_INT_EN_RISE); + ulpi_write(otg, ulpi_val, ULPI_USB_INT_EN_FALL); + + msm_chg_enable_aca_det(motg); + msm_chg_enable_aca_intr(motg); return 0; } @@ -452,11 +509,14 @@ static int msm_otg_suspend(struct msm_otg *motg) struct usb_bus *bus = otg->host; struct msm_otg_platform_data *pdata = motg->pdata; int cnt = 0; + bool session_active; if (atomic_read(&motg->in_lpm)) return 0; disable_irq(motg->irq); + session_active = (otg->host && !test_bit(ID, &motg->inputs)) || + test_bit(B_SESS_VLD, &motg->inputs); /* * Chipidea 45-nm PHY suspend sequence: * @@ -481,6 +541,16 @@ static int msm_otg_suspend(struct msm_otg *motg) ulpi_write(otg, 0x08, 0x09); } + /* + * Turn off the OTG comparators, if depends on PMIC for + * VBUS and ID notifications. + */ + if ((motg->caps & ALLOW_PHY_COMP_DISABLE) && !session_active) { + ulpi_write(otg, OTG_COMP_DISABLE, + ULPI_SET(ULPI_PWR_CLK_MNG_REG)); + motg->lpm_flags |= PHY_OTG_COMP_DISABLED; + } + /* * PHY may take some time or even fail to enter into low power * mode (LPM). Hence poll for 500 msec and reset the PHY and link @@ -510,31 +580,40 @@ static int msm_otg_suspend(struct msm_otg *motg) */ writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL | ULPI_STP_CTRL, USB_USBCMD); - if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && - motg->pdata->otg_control == OTG_PMIC_CONTROL) - writel(readl(USB_PHY_CTRL) | PHY_RETEN, USB_PHY_CTRL); + if (motg->caps & ALLOW_PHY_RETENTION && !session_active) { + writel_relaxed(readl_relaxed(USB_PHY_CTRL) & ~PHY_RETEN, + USB_PHY_CTRL); + motg->lpm_flags |= PHY_RETENTIONED; + } + /* Ensure that above operation is completed before turning off clocks */ + mb(); clk_disable(motg->pclk); - clk_disable(motg->clk); if (motg->core_clk) clk_disable(motg->core_clk); if (!IS_ERR(motg->pclk_src)) clk_disable(motg->pclk_src); - if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && - motg->pdata->otg_control == OTG_PMIC_CONTROL) { - msm_hsusb_ldo_set_mode(0); - msm_hsusb_config_vddcx(0); + if (motg->caps & ALLOW_PHY_POWER_COLLAPSE && !session_active) { + msm_hsusb_ldo_enable(motg, 0); + motg->lpm_flags |= PHY_PWR_COLLAPSED; } - if (device_may_wakeup(otg->dev)) + if (motg->lpm_flags & PHY_RETENTIONED) + msm_hsusb_config_vddcx(0); + + if (device_may_wakeup(otg->dev)) { enable_irq_wake(motg->irq); + if (motg->pdata->pmic_id_irq) + enable_irq_wake(motg->pdata->pmic_id_irq); + } if (bus) clear_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); atomic_set(&motg->in_lpm, 1); enable_irq(motg->irq); + wake_unlock(&motg->wlock); dev_info(otg->dev, "USB in low power mode\n"); @@ -551,19 +630,24 @@ static int msm_otg_resume(struct msm_otg *motg) if (!atomic_read(&motg->in_lpm)) return 0; + wake_lock(&motg->wlock); if (!IS_ERR(motg->pclk_src)) clk_enable(motg->pclk_src); clk_enable(motg->pclk); - clk_enable(motg->clk); if (motg->core_clk) clk_enable(motg->core_clk); - if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && - motg->pdata->otg_control == OTG_PMIC_CONTROL) { - msm_hsusb_ldo_set_mode(1); + if (motg->lpm_flags & PHY_PWR_COLLAPSED) { + msm_hsusb_ldo_enable(motg, 1); + motg->lpm_flags &= ~PHY_PWR_COLLAPSED; + } + + if (motg->lpm_flags & PHY_RETENTIONED) { msm_hsusb_config_vddcx(1); - writel(readl(USB_PHY_CTRL) & ~PHY_RETEN, USB_PHY_CTRL); + writel_relaxed(readl_relaxed(USB_PHY_CTRL) | PHY_RETEN, + USB_PHY_CTRL); + motg->lpm_flags &= ~PHY_RETENTIONED; } temp = readl(USB_USBCMD); @@ -598,8 +682,17 @@ static int msm_otg_resume(struct msm_otg *motg) } skip_phy_resume: - if (device_may_wakeup(otg->dev)) + /* Turn on the OTG comparators on resume */ + if (motg->lpm_flags & PHY_OTG_COMP_DISABLED) { + ulpi_write(otg, OTG_COMP_DISABLE, + ULPI_CLR(ULPI_PWR_CLK_MNG_REG)); + motg->lpm_flags &= ~PHY_OTG_COMP_DISABLED; + } + if (device_may_wakeup(otg->dev)) { disable_irq_wake(motg->irq); + if (motg->pdata->pmic_id_irq) + disable_irq_wake(motg->pdata->pmic_id_irq); + } if (bus) set_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); @@ -622,8 +715,8 @@ static void msm_otg_notify_charger(struct msm_otg *motg, unsigned mA) if (motg->cur_power == mA) return; - /* TODO: Notify PMIC about available current */ dev_info(motg->otg.dev, "Avail curr from USB = %u\n", mA); + pm8921_charger_vbus_draw(mA); motg->cur_power = mA; } @@ -658,8 +751,6 @@ static void msm_otg_start_host(struct otg_transceiver *otg, int on) if (on) { dev_dbg(otg->dev, "host on\n"); - if (pdata->vbus_power) - pdata->vbus_power(1); /* * Some boards have a switch cotrolled by gpio * to enable/disable internal HUB. Enable internal @@ -667,22 +758,49 @@ static void msm_otg_start_host(struct otg_transceiver *otg, int on) */ if (pdata->setup_gpio) pdata->setup_gpio(OTG_STATE_A_HOST); -#ifdef CONFIG_USB usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); -#endif } else { dev_dbg(otg->dev, "host off\n"); -#ifdef CONFIG_USB usb_remove_hcd(hcd); -#endif if (pdata->setup_gpio) pdata->setup_gpio(OTG_STATE_UNDEFINED); - if (pdata->vbus_power) - pdata->vbus_power(0); } } +static int msm_otg_usbdev_notify(struct notifier_block *self, + unsigned long action, void *priv) +{ + struct msm_otg *motg = container_of(self, struct msm_otg, usbdev_nb); + struct usb_device *udev; + + switch (action) { + case USB_DEVICE_ADD: + case USB_DEVICE_CONFIG: + udev = priv; + /* + * Interested in devices connected directly to the root hub. + * ACA dock can supply IDEV_CHG irrespective devices connected + * on the accessory port. + */ + if (!udev->parent || udev->parent->parent || + motg->chg_type == USB_ACA_DOCK_CHARGER) + break; + if (udev->actconfig) + motg->mA_port = udev->actconfig->desc.bMaxPower * 2; + else + motg->mA_port = IUNIT; + + if (test_bit(ID_A, &motg->inputs)) + msm_otg_notify_charger(motg, IDEV_CHG_MIN - + motg->mA_port); + break; + default: + break; + } + return NOTIFY_OK; +} + static int msm_otg_set_host(struct otg_transceiver *otg, struct usb_bus *host) { struct msm_otg *motg = container_of(otg, struct msm_otg, otg); @@ -700,7 +818,10 @@ static int msm_otg_set_host(struct otg_transceiver *otg, struct usb_bus *host) if (!host) { if (otg->state == OTG_STATE_A_HOST) { pm_runtime_get_sync(otg->dev); + usb_unregister_notify(&motg->usbdev_nb); msm_otg_start_host(otg, 0); + if (motg->pdata->vbus_power) + motg->pdata->vbus_power(0); otg->host = NULL; otg->state = OTG_STATE_UNDEFINED; schedule_work(&motg->sm_work); @@ -714,6 +835,8 @@ static int msm_otg_set_host(struct otg_transceiver *otg, struct usb_bus *host) hcd = bus_to_hcd(host); hcd->power_budget = motg->pdata->power_budget; + motg->usbdev_nb.notifier_call = msm_otg_usbdev_notify; + usb_register_notify(&motg->usbdev_nb); otg->host = host; dev_dbg(otg->dev, "host driver registered w/ tranceiver\n"); @@ -798,6 +921,108 @@ static int msm_otg_set_peripheral(struct otg_transceiver *otg, return 0; } +#ifdef CONFIG_USB_MSM_ACA +static bool msm_chg_aca_detect(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + u32 int_sts; + bool ret = false; + + if (motg->pdata->phy_type == CI_45NM_INTEGRATED_PHY) + goto out; + + int_sts = ulpi_read(otg, 0x87); + switch (int_sts & 0x1C) { + case 0x08: + if (!test_and_set_bit(ID_A, &motg->inputs)) { + dev_dbg(otg->dev, "ID_A\n"); + motg->chg_type = USB_ACA_A_CHARGER; + motg->chg_state = USB_CHG_STATE_DETECTED; + clear_bit(ID_B, &motg->inputs); + clear_bit(ID_C, &motg->inputs); + ret = true; + } + break; + case 0x0C: + if (!test_and_set_bit(ID_B, &motg->inputs)) { + dev_dbg(otg->dev, "ID_B\n"); + motg->chg_type = USB_ACA_B_CHARGER; + motg->chg_state = USB_CHG_STATE_DETECTED; + clear_bit(ID_A, &motg->inputs); + clear_bit(ID_C, &motg->inputs); + ret = true; + } + break; + case 0x10: + if (!test_and_set_bit(ID_C, &motg->inputs)) { + dev_dbg(otg->dev, "ID_C\n"); + motg->chg_type = USB_ACA_C_CHARGER; + motg->chg_state = USB_CHG_STATE_DETECTED; + clear_bit(ID_A, &motg->inputs); + clear_bit(ID_B, &motg->inputs); + ret = true; + } + break; + default: + ret = test_and_clear_bit(ID_A, &motg->inputs) | + test_and_clear_bit(ID_B, &motg->inputs) | + test_and_clear_bit(ID_C, &motg->inputs); + if (ret) { + dev_dbg(otg->dev, "ID A/B/C is no more\n"); + motg->chg_type = USB_INVALID_CHARGER; + motg->chg_state = USB_CHG_STATE_UNDEFINED; + } + } +out: + return ret; +} + +static void msm_chg_enable_aca_det(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + + switch (motg->pdata->phy_type) { + case SNPS_28NM_INTEGRATED_PHY: + /* ACA ID pin resistance detection enable */ + ulpi_write(otg, 0x20, 0x85); + break; + default: + break; + } +} + +static void msm_chg_enable_aca_intr(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + + switch (motg->pdata->phy_type) { + case SNPS_28NM_INTEGRATED_PHY: + /* Enables ACA Detection interrupt (on any RID change) */ + ulpi_write(otg, 0x20, 0x94); + break; + default: + break; + } +} + +static bool msm_chg_check_aca_intr(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + bool ret = false; + + switch (motg->pdata->phy_type) { + case SNPS_28NM_INTEGRATED_PHY: + if (ulpi_read(otg, 0x91) & 1) { + dev_dbg(otg->dev, "RID change\n"); + ulpi_write(otg, 0x01, 0x92); + ret = msm_chg_aca_detect(motg); + } + default: + break; + } + return ret; +} +#endif static bool msm_chg_check_secondary_det(struct msm_otg *motg) { struct otg_transceiver *otg = &motg->otg; @@ -1039,7 +1264,7 @@ static void msm_chg_detect_work(struct work_struct *w) { struct msm_otg *motg = container_of(w, struct msm_otg, chg_work.work); struct otg_transceiver *otg = &motg->otg; - bool is_dcd, tmout, vout; + bool is_dcd, tmout, vout, is_aca; unsigned long delay; dev_dbg(otg->dev, "chg detection work\n"); @@ -1048,11 +1273,25 @@ static void msm_chg_detect_work(struct work_struct *w) pm_runtime_get_sync(otg->dev); msm_chg_block_on(motg); msm_chg_enable_dcd(motg); + msm_chg_enable_aca_det(motg); motg->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; motg->dcd_retries = 0; delay = MSM_CHG_DCD_POLL_TIME; break; case USB_CHG_STATE_WAIT_FOR_DCD: + is_aca = msm_chg_aca_detect(motg); + if (is_aca) { + /* + * ID_A can be ACA dock too. continue + * primary detection after DCD. + */ + if (test_bit(ID_A, &motg->inputs)) { + motg->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; + } else { + delay = 0; + break; + } + } is_dcd = msm_chg_check_dcd(motg); tmout = ++motg->dcd_retries == MSM_CHG_DCD_MAX_RETRIES; if (is_dcd || tmout) { @@ -1066,6 +1305,13 @@ static void msm_chg_detect_work(struct work_struct *w) break; case USB_CHG_STATE_DCD_DONE: vout = msm_chg_check_primary_det(motg); + is_aca = msm_chg_aca_detect(motg); + if (is_aca) { + if (vout && test_bit(ID_A, &motg->inputs)) + motg->chg_type = USB_ACA_DOCK_CHARGER; + delay = 0; + break; + } if (vout) { msm_chg_enable_secondary_det(motg); delay = MSM_CHG_SECONDARY_DET_TIME; @@ -1088,6 +1334,8 @@ static void msm_chg_detect_work(struct work_struct *w) motg->chg_state = USB_CHG_STATE_DETECTED; case USB_CHG_STATE_DETECTED: msm_chg_block_off(motg); + msm_chg_enable_aca_det(motg); + msm_chg_enable_aca_intr(motg); dev_dbg(otg->dev, "charger = %d\n", motg->chg_type); schedule_work(&motg->sm_work); return; @@ -1112,17 +1360,7 @@ static void msm_otg_init_sm(struct msm_otg *motg) switch (pdata->mode) { case USB_OTG: - if (pdata->otg_control == OTG_PHY_CONTROL) { - if (otgsc & OTGSC_ID) - set_bit(ID, &motg->inputs); - else - clear_bit(ID, &motg->inputs); - - if (otgsc & OTGSC_BSV) - set_bit(B_SESS_VLD, &motg->inputs); - else - clear_bit(B_SESS_VLD, &motg->inputs); - } else if (pdata->otg_control == OTG_USER_CONTROL) { + if (pdata->otg_control == OTG_USER_CONTROL) { if (pdata->default_mode == USB_HOST) { clear_bit(ID, &motg->inputs); } else if (pdata->default_mode == USB_PERIPHERAL) { @@ -1132,6 +1370,16 @@ static void msm_otg_init_sm(struct msm_otg *motg) set_bit(ID, &motg->inputs); clear_bit(B_SESS_VLD, &motg->inputs); } + } else { + if (otgsc & OTGSC_ID) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + + if (otgsc & OTGSC_BSV) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); } break; case USB_HOST: @@ -1163,9 +1411,16 @@ static void msm_otg_sm_work(struct work_struct *w) /* FALL THROUGH */ case OTG_STATE_B_IDLE: dev_dbg(otg->dev, "OTG_STATE_B_IDLE state\n"); - if (!test_bit(ID, &motg->inputs) && otg->host) { + if ((!test_bit(ID, &motg->inputs) || + test_bit(ID_A, &motg->inputs)) && otg->host) { /* disable BSV bit */ writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); + if (motg->chg_type == USB_ACA_DOCK_CHARGER) + msm_otg_notify_charger(motg, + IDEV_CHG_MAX); + else if (!test_bit(ID_A, &motg->inputs) && + motg->pdata->vbus_power) + motg->pdata->vbus_power(1); msm_otg_start_host(otg, 1); otg->state = OTG_STATE_A_HOST; } else if (test_bit(B_SESS_VLD, &motg->inputs)) { @@ -1176,10 +1431,12 @@ static void msm_otg_sm_work(struct work_struct *w) case USB_CHG_STATE_DETECTED: switch (motg->chg_type) { case USB_DCP_CHARGER: + case USB_ACA_B_CHARGER: msm_otg_notify_charger(motg, IDEV_CHG_MAX); break; case USB_CDP_CHARGER: + case USB_ACA_C_CHARGER: msm_otg_notify_charger(motg, IDEV_CHG_MAX); msm_otg_start_peripheral(otg, 1); @@ -1216,23 +1473,51 @@ static void msm_otg_sm_work(struct work_struct *w) case OTG_STATE_B_PERIPHERAL: dev_dbg(otg->dev, "OTG_STATE_B_PERIPHERAL state\n"); if (!test_bit(B_SESS_VLD, &motg->inputs) || - !test_bit(ID, &motg->inputs)) { + !test_bit(ID, &motg->inputs) || + !test_bit(ID_C, &motg->inputs)) { msm_otg_notify_charger(motg, 0); msm_otg_start_peripheral(otg, 0); - motg->chg_state = USB_CHG_STATE_UNDEFINED; - motg->chg_type = USB_INVALID_CHARGER; + if (!test_bit(ID_B, &motg->inputs) && + !test_bit(ID_A, &motg->inputs)) { + motg->chg_state = USB_CHG_STATE_UNDEFINED; + motg->chg_type = USB_INVALID_CHARGER; + } otg->state = OTG_STATE_B_IDLE; msm_otg_reset(otg); schedule_work(w); + } else if (test_bit(ID_C, &motg->inputs)) { + msm_otg_notify_charger(motg, IDEV_CHG_MAX); + pm_runtime_put_sync(otg->dev); } break; case OTG_STATE_A_HOST: dev_dbg(otg->dev, "OTG_STATE_A_HOST state\n"); - if (test_bit(ID, &motg->inputs)) { + if (test_bit(ID, &motg->inputs) && + !test_bit(ID_A, &motg->inputs)) { msm_otg_start_host(otg, 0); + writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); + if (motg->pdata->vbus_power) + motg->pdata->vbus_power(0); + motg->chg_state = USB_CHG_STATE_UNDEFINED; + motg->chg_type = USB_INVALID_CHARGER; otg->state = OTG_STATE_B_IDLE; msm_otg_reset(otg); schedule_work(w); + } else if (test_bit(ID_A, &motg->inputs)) { + writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); + if (motg->pdata->vbus_power) + motg->pdata->vbus_power(0); + msm_otg_notify_charger(motg, + IDEV_CHG_MIN - motg->mA_port); + pm_runtime_put_sync(otg->dev); + } else if (!test_bit(ID, &motg->inputs)) { + motg->chg_state = USB_CHG_STATE_UNDEFINED; + motg->chg_type = USB_INVALID_CHARGER; + msm_otg_notify_charger(motg, 0); + writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); + if (motg->pdata->vbus_power) + motg->pdata->vbus_power(1); + pm_runtime_put_sync(otg->dev); } break; default: @@ -1244,7 +1529,7 @@ static irqreturn_t msm_otg_irq(int irq, void *data) { struct msm_otg *motg = data; struct otg_transceiver *otg = &motg->otg; - u32 otgsc = 0; + u32 otgsc = 0, usbsts; if (atomic_read(&motg->in_lpm)) { disable_irq_nosync(irq); @@ -1253,6 +1538,16 @@ static irqreturn_t msm_otg_irq(int irq, void *data) return IRQ_HANDLED; } + usbsts = readl(USB_USBSTS); + if ((usbsts & PHY_ALT_INT)) { + writel(PHY_ALT_INT, USB_USBSTS); + if (msm_chg_check_aca_intr(motg)) { + pm_runtime_get_noresume(otg->dev); + schedule_work(&motg->sm_work); + } + return IRQ_HANDLED; + } + otgsc = readl(USB_OTGSC); if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS))) return IRQ_NONE; @@ -1263,6 +1558,7 @@ static irqreturn_t msm_otg_irq(int irq, void *data) else clear_bit(ID, &motg->inputs); dev_dbg(otg->dev, "ID set/clear\n"); + schedule_work(&motg->sm_work); pm_runtime_get_noresume(otg->dev); } else if ((otgsc & OTGSC_BSVIS) && (otgsc & OTGSC_BSVIE)) { if (otgsc & OTGSC_BSV) @@ -1270,11 +1566,36 @@ static irqreturn_t msm_otg_irq(int irq, void *data) else clear_bit(B_SESS_VLD, &motg->inputs); dev_dbg(otg->dev, "BSV set/clear\n"); + schedule_work(&motg->sm_work); pm_runtime_get_noresume(otg->dev); } writel(otgsc, USB_OTGSC); - schedule_work(&motg->sm_work); + return IRQ_HANDLED; +} + +static void msm_otg_set_vbus_state(int online) +{ + struct msm_otg *motg = the_msm_otg; + + /* We depend on PMIC for only VBUS ON interrupt */ + if (!atomic_read(&motg->in_lpm) || !online) + return; + + /* + * Let interrupt handler take care of resuming + * the hardware. + */ + msm_otg_irq(motg->irq, (void *) motg); +} + +static irqreturn_t msm_pmic_id_irq(int irq, void *data) +{ + struct msm_otg *motg = data; + + if (atomic_read(&motg->in_lpm) && !motg->async_int) + msm_otg_irq(motg->irq, motg); + return IRQ_HANDLED; } @@ -1428,6 +1749,7 @@ static int __init msm_otg_probe(struct platform_device *pdev) return -ENOMEM; } + the_msm_otg = motg; motg->pdata = pdev->dev.platform_data; otg = &motg->otg; otg->dev = &pdev->dev; @@ -1503,24 +1825,30 @@ static int __init msm_otg_probe(struct platform_device *pdev) goto free_regs; } - clk_enable(motg->clk); clk_enable(motg->pclk); ret = msm_hsusb_init_vddcx(motg, 1); if (ret) { - dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); + dev_err(&pdev->dev, "hsusb vddcx init failed\n"); goto free_regs; } + ret = msm_hsusb_config_vddcx(1); + if (ret) { + dev_err(&pdev->dev, "hsusb vddcx configuration failed\n"); + goto free_init_vddcx; + } + ret = msm_hsusb_ldo_init(motg, 1); if (ret) { dev_err(&pdev->dev, "hsusb vreg configuration failed\n"); - goto vddcx_exit; + goto free_init_vddcx; } - ret = msm_hsusb_ldo_set_mode(1); + + ret = msm_hsusb_ldo_enable(motg, 1); if (ret) { dev_err(&pdev->dev, "hsusb vreg enable failed\n"); - goto ldo_exit; + goto free_ldo_init; } if (motg->core_clk) @@ -1528,14 +1856,17 @@ static int __init msm_otg_probe(struct platform_device *pdev) writel(0, USB_USBINTR); writel(0, USB_OTGSC); + /* Ensure that above STOREs are completed before enabling interrupts */ + mb(); + wake_lock_init(&motg->wlock, WAKE_LOCK_SUSPEND, "msm_otg"); INIT_WORK(&motg->sm_work, msm_otg_sm_work); INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work); ret = request_irq(motg->irq, msm_otg_irq, IRQF_SHARED, "msm_otg", motg); if (ret) { dev_err(&pdev->dev, "request irq failed\n"); - goto disable_clks; + goto destroy_wlock; } otg->init = msm_otg_reset; @@ -1551,8 +1882,27 @@ static int __init msm_otg_probe(struct platform_device *pdev) goto free_irq; } + if (motg->pdata->otg_control == OTG_PMIC_CONTROL) { + if (motg->pdata->pmic_id_irq) { + ret = request_irq(motg->pdata->pmic_id_irq, + msm_pmic_id_irq, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "msm_otg", motg); + if (ret) { + dev_err(&pdev->dev, "request irq failed for PMIC ID\n"); + goto remove_otg; + } + } else { + ret = -ENODEV; + dev_err(&pdev->dev, "PMIC IRQ for ID notifications doesn't exist\n"); + goto remove_otg; + } + } + platform_set_drvdata(pdev, motg); device_init_wakeup(&pdev->dev, 1); + motg->mA_port = IUNIT; if (motg->pdata->mode == USB_OTG && motg->pdata->otg_control == OTG_USER_CONTROL) { @@ -1562,25 +1912,39 @@ static int __init msm_otg_probe(struct platform_device *pdev) "not available\n"); } + if (motg->pdata->otg_control == OTG_PMIC_CONTROL) + pm8921_charger_register_vbus_sn(&msm_otg_set_vbus_state); + + if (motg->pdata->phy_type == SNPS_28NM_INTEGRATED_PHY && + motg->pdata->otg_control == OTG_PMIC_CONTROL && + motg->pdata->pmic_id_irq) + motg->caps = ALLOW_PHY_POWER_COLLAPSE | + ALLOW_PHY_RETENTION | + ALLOW_PHY_COMP_DISABLE; + + wake_lock(&motg->wlock); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); return 0; + +remove_otg: + otg_set_transceiver(NULL); free_irq: free_irq(motg->irq, motg); -disable_clks: +destroy_wlock: + wake_lock_destroy(&motg->wlock); clk_disable(motg->pclk); - clk_disable(motg->clk); -ldo_exit: + msm_hsusb_ldo_enable(motg, 0); +free_ldo_init: msm_hsusb_ldo_init(motg, 0); -vddcx_exit: +free_init_vddcx: msm_hsusb_init_vddcx(motg, 0); free_regs: iounmap(motg->regs); put_core_clk: if (motg->core_clk) clk_put(motg->core_clk); - clk_put(motg->pclk); put_pclk_src: if (!IS_ERR(motg->pclk_src)) { clk_disable(motg->pclk_src); @@ -1604,6 +1968,8 @@ static int __devexit msm_otg_remove(struct platform_device *pdev) if (otg->host || otg->gadget) return -EBUSY; + if (motg->pdata->otg_control == OTG_PMIC_CONTROL) + pm8921_charger_unregister_vbus_sn(0); msm_otg_debugfs_cleanup(); cancel_delayed_work_sync(&motg->chg_work); cancel_work_sync(&motg->sm_work); @@ -1612,7 +1978,10 @@ static int __devexit msm_otg_remove(struct platform_device *pdev) device_init_wakeup(&pdev->dev, 0); pm_runtime_disable(&pdev->dev); + wake_lock_destroy(&motg->wlock); + if (motg->pdata->pmic_id_irq) + free_irq(motg->pdata->pmic_id_irq, motg); otg_set_transceiver(NULL); free_irq(motg->irq, motg); @@ -1633,14 +2002,15 @@ static int __devexit msm_otg_remove(struct platform_device *pdev) dev_err(otg->dev, "Unable to suspend PHY\n"); clk_disable(motg->pclk); - clk_disable(motg->clk); if (motg->core_clk) clk_disable(motg->core_clk); if (!IS_ERR(motg->pclk_src)) { clk_disable(motg->pclk_src); clk_put(motg->pclk_src); } + msm_hsusb_ldo_enable(motg, 0); msm_hsusb_ldo_init(motg, 0); + msm_hsusb_init_vddcx(motg, 0); iounmap(motg->regs); pm_runtime_set_suspended(&pdev->dev); diff --git a/drivers/usb/otg/otg.c b/drivers/usb/otg/otg.c index fb7adeff9ff..40a34ecd50d 100644 --- a/drivers/usb/otg/otg.c +++ b/drivers/usb/otg/otg.c @@ -99,3 +99,18 @@ const char *otg_state_string(enum usb_otg_state state) } } EXPORT_SYMBOL(otg_state_string); + +int otg_send_event(enum usb_otg_event event) +{ + struct otg_transceiver *otg = otg_get_transceiver(); + int ret = -ENOTSUPP; + + if (otg && otg->send_event) + ret = otg->send_event(otg, event); + + if (otg) + otg_put_transceiver(otg); + + return ret; +} +EXPORT_SYMBOL(otg_send_event); \ No newline at end of file diff --git a/drivers/usb/otg/otg_id.c b/drivers/usb/otg/otg_id.c index ce22b462130..64e1bd42050 100644 --- a/drivers/usb/otg/otg_id.c +++ b/drivers/usb/otg/otg_id.c @@ -42,7 +42,7 @@ static void otg_id_cancel(void) static void __otg_id_notify(void) { - int ret; + int ret = 0; struct otg_id_notifier_block *otg_id_nb; bool proxy_wait = false; if (plist_head_empty(&otg_id_plist)) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 4c85a4b15f9..a564a032499 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -25,6 +25,8 @@ source "drivers/gpu/stub/Kconfig" source "drivers/gpu/ion/Kconfig" +source "drivers/gpu/msm/Kconfig" + config VGASTATE tristate default n @@ -2323,13 +2325,6 @@ config FB_PRE_INIT_FB Select this option if display contents should be inherited as set by the bootloader. -config FB_MSM - tristate "MSM Framebuffer support" - depends on FB && ARCH_MSM - select FB_CFB_FILLRECT - select FB_CFB_COPYAREA - select FB_CFB_IMAGEBLIT - config FB_MX3 tristate "MX3 Framebuffer support" depends on FB && MX3_IPU @@ -2385,6 +2380,8 @@ config FB_PUV3_UNIGFX Choose this option if you want to use the Unigfx device as a framebuffer device. Without the support of PCI & AGP. +source "drivers/video/msm/Kconfig" + source "drivers/video/omap/Kconfig" source "drivers/video/omap2/Kconfig" diff --git a/drivers/video/fbmem.c b/drivers/video/fbmem.c index 5aac00eb183..a02406420d3 100644 --- a/drivers/video/fbmem.c +++ b/drivers/video/fbmem.c @@ -1169,14 +1169,11 @@ static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, unlock_fb_info(info); break; default: - if (!lock_fb_info(info)) - return -ENODEV; fb = info->fbops; if (fb->fb_ioctl) ret = fb->fb_ioctl(info, cmd, arg); else ret = -ENOTTY; - unlock_fb_info(info); } return ret; } diff --git a/drivers/video/msm/Kconfig b/drivers/video/msm/Kconfig new file mode 100644 index 00000000000..677863d3674 --- /dev/null +++ b/drivers/video/msm/Kconfig @@ -0,0 +1,661 @@ + +source "drivers/video/msm/vidc/Kconfig" + +config FB_MSM + tristate "MSM Framebuffer support" + depends on FB && ARCH_MSM + select FB_BACKLIGHT if FB_MSM_BACKLIGHT + select NEW_LEDS + select LEDS_CLASS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Support for MSM Framebuffer. + +if FB_MSM + +config FB_MSM_BACKLIGHT + bool "Support for backlight control" + default y + ---help--- + Say Y here if you want to control the backlight of your display. + +config FB_MSM_LOGO + bool "MSM Frame Buffer Logo" + default n + ---help--- + Show /initlogo.rle during boot. + +config FB_MSM_LCDC_HW + bool + default n + +config FB_MSM_TRIPLE_BUFFER + bool "Support for triple frame buffer" + default n + +choice + prompt "MDP HW version" + default FB_MSM_MDP22 + +config FB_MSM_MDP22 + bool "MDP HW ver2.2" + ---help--- + Support for MSM MDP HW revision 2.2 + Say Y here if this is msm7201 variant platform. + +config FB_MSM_MDP30 + select FB_MSM_LCDC_HW + bool "MDP HW ver3.0" + ---help--- + Support for MSM MDP HW revision 3.0 + Say Y here if this is msm7x25 variant platform. + +config FB_MSM_MDP303 + depends on FB_MSM_MDP30 + bool "MDP HW ver3.03" + default n + ---help--- + Support for MSM MDP HW revision 3.03. This is a new version of + MDP3.0 which has the required functionality to support the features + required for msm7x2xA platform. + Say Y here if this is msm7x2xA variant platform. + +config FB_MSM_MDP31 + select FB_MSM_LCDC_HW + bool "MDP HW ver3.1" + ---help--- + Support for MSM MDP HW revision 3.1 + Say Y here if this is msm8x50 variant platform. + +config FB_MSM_MDP40 + select FB_MSM_LCDC_HW + bool "MDP HW ver4.0" + ---help--- + Support for MSM MDP HW revision 4.0 + Say Y here if this is msm7x30 variant platform. +endchoice + +config FB_MSM_EBI2 + bool + default n + +config FB_MSM_MDDI + bool + default n + +config FB_MSM_MIPI_DSI + bool + default n + +config FB_MSM_LCDC + bool + default n + +config FB_MSM_OVERLAY + depends on FB_MSM_MDP40 && ANDROID_PMEM + bool "MDP4 overlay support" + default n + +config FB_MSM_DTV + depends on FB_MSM_OVERLAY + bool + default n + +config FB_MSM_EXTMDDI + bool + default n + +config FB_MSM_TVOUT + bool + default n + +config FB_MSM_MDDI_TOSHIBA_COMMON + bool + select FB_MSM_MDDI + default n + +config FB_MSM_MDDI_TOSHIBA_COMMON_VGA + bool + select FB_MSM_MDDI_TOSHIBA_COMMON + default n + +config FB_MSM_MDDI_ORISE + bool + select FB_MSM_MDDI + default n + +config FB_MSM_MDDI_QUICKVX + bool + select FB_MSM_MDDI + default n + +config FB_MSM_MDDI_AUTO_DETECT + bool + select FB_MSM_MDDI + default n + +config FB_MSM_LCDC_AUTO_DETECT + bool + select FB_MSM_LCDC + default n + +config FB_MSM_LCDC_PANEL + bool + select FB_MSM_LCDC + default n + +config FB_MSM_MIPI_DSI_TOSHIBA + bool + select FB_MSM_MIPI_DSI + default n + +config FB_MSM_MIPI_DSI_RENESAS + bool + select FB_MSM_MIPI_DSI + default n + +config FB_MSM_MIPI_DSI_SIMULATOR + bool + select FB_MSM_MIPI_DSI + default n + +config FB_MSM_MIPI_DSI_NOVATEK + bool + select FB_MSM_MIPI_DSI + default n + +config FB_MSM_LCDC_ST15_WXGA + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_ST15_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC ST1.5 Panel" + select FB_MSM_LCDC_ST15_WXGA + ---help--- + Support for ST1.5 WXGA (1366x768) panel + +config FB_MSM_LCDC_PRISM_WVGA + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_SAMSUNG_WSVGA + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_CHIMEI_WXGA + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_GORDON_VGA + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_TOSHIBA_WVGA_PT + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_TOSHIBA_FWVGA_PT + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_SHARP_WVGA_PT + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_AUO_WVGA + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_SAMSUNG_OLED_PT + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_LCDC_WXGA + bool + select FB_MSM_LCDC_PANEL + default n + +config FB_MSM_MIPI_TOSHIBA_VIDEO_WVGA_PT + bool + select FB_MSM_MIPI_DSI_TOSHIBA + default n + +config FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT + bool + select FB_MSM_MIPI_DSI_TOSHIBA + default n + +config FB_MSM_MIPI_NOVATEK_VIDEO_QHD_PT + bool + select FB_MSM_MIPI_DSI_NOVATEK + default n + +config FB_MSM_MIPI_NOVATEK_CMD_QHD_PT + bool + select FB_MSM_MIPI_DSI_NOVATEK + default n + +config FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT + bool + select FB_MSM_MIPI_DSI_RENESAS + default n + +config FB_MSM_MIPI_RENESAS_CMD_FWVGA_PT + bool + select FB_MSM_MIPI_DSI_RENESAS + default n + +config FB_MSM_MIPI_SIMULATOR_VIDEO + bool + select FB_MSM_MIPI_DSI_SIMULATOR + default n + + +config FB_MSM_OVERLAY_WRITEBACK + depends on FB_MSM_OVERLAY + bool "MDP overlay write back mode enable" + ---help--- + Support for MDP4 OVERLAY write back mode + +choice + prompt "LCD Panel" + default FB_MSM_MDDI_AUTO_DETECT + +config FB_MSM_LCDC_PRISM_WVGA_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC Prism WVGA Panel" + select FB_MSM_LCDC_PRISM_WVGA + ---help--- + Support for LCDC Prism WVGA (800x480) panel + +config FB_MSM_LCDC_SAMSUNG_WSVGA_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC Samsung WSVGA Panel" + select FB_MSM_LCDC_SAMSUNG_WSVGA + ---help--- + Support for LCDC Samsung WSVGA (1024x600) panel + +config FB_MSM_LCDC_CHIMEI_WXGA_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC Chimei WXGA Panel" + select FB_MSM_LCDC_CHIMEI_WXGA + ---help--- + Support for LCDC Chimei WXGA (1366x768) panel + +config FB_MSM_LCDC_GORDON_VGA_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC Gordon VGA Panel" + select FB_MSM_LCDC_GORDON_VGA + ---help--- + Support for LCDC Gordon VGA (480x640) panel + +config FB_MSM_LCDC_TOSHIBA_WVGA_PT_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC Toshiba WVGA PT Panel" + select FB_MSM_LCDC_TOSHIBA_WVGA_PT + ---help--- + Support for LCDC Toshiba WVGA PT (480x800) panel + +config FB_MSM_LCDC_TOSHIBA_FWVGA_PT_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC Toshiba FWVGA PT Panel" + select FB_MSM_LCDC_TOSHIBA_FWVGA_PT + ---help--- + Support for LCDC Toshiba FWVGA PT (480x864) panel. This + configuration has to be selected to support the Toshiba + FWVGA (480x864) portrait panel. + . + . + +config FB_MSM_LCDC_SHARP_WVGA_PT_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC Sharp WVGA PT Panel" + select FB_MSM_LCDC_SHARP_WVGA_PT + ---help--- + Support for LCDC Sharp WVGA PT (480x800) panel + +config FB_MSM_LCDC_AUO_WVGA_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC AUO WVGA Panel" + select FB_MSM_LCDC_AUO_WVGA + ---help--- + Support for LCDC AUO WVGA(480x800) panel + . + . + . +config FB_MSM_LCDC_SAMSUNG_OLED_PT_PANEL + depends on FB_MSM_LCDC_HW + bool "LCDC Samsung OLED PT Panel" + select FB_MSM_LCDC_SAMSUNG_OLED_PT + ---help--- + Support for LCDC Samsung OLED PT (480x800) panel + . + . + . + +config FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM + depends on FB_MSM_LCDC_HW + bool "MDDI Panel Auto Detect + LCDC Prism WVGA" + select FB_MSM_MDDI_AUTO_DETECT + select FB_MSM_LCDC_PRISM_WVGA + select FB_MSM_LCDC_GORDON_VGA + select FB_MSM_LCDC_WXGA + select FB_MSM_LCDC_TOSHIBA_WVGA_PT + select FB_MSM_LCDC_TOSHIBA_FWVGA_PT + select FB_MSM_LCDC_SHARP_WVGA_PT + select FB_MSM_LCDC_ST15_WXGA + ---help--- + Support for MDDI panel auto detect. + If it can't find any MDDI panel, it will load an LCDC panel. + +config FB_MSM_MIPI_PANEL_DETECT + depends on FB_MSM_LCDC_HW + bool "MIPI Panel Detect + LCDC Panel Auto Detect" + select FB_MSM_MIPI_TOSHIBA_VIDEO_WVGA_PT + select FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT + select FB_MSM_LCDC_AUTO_DETECT + select FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT + select FB_MSM_MIPI_SIMULATOR_VIDEO + select FB_MSM_LCDC_SAMSUNG_WSVGA + select FB_MSM_LCDC_AUO_WVGA + select FB_MSM_LCDC_SAMSUNG_OLED_PT + +config FB_MSM_MDDI_PANEL_AUTO_DETECT + bool "MDDI Panel Auto Detect" + select FB_MSM_MDDI_AUTO_DETECT + ---help--- + Support for MDDI panel auto detect + +config FB_MSM_LCDC_PANEL_AUTO_DETECT + bool "LCDC Panel Auto Detect" + select FB_MSM_LCDC_AUTO_DETECT + select FB_MSM_LCDC_SAMSUNG_WSVGA + select FB_MSM_LCDC_AUO_WVGA + select FB_MSM_LCDC_SAMSUNG_OLED_PT + ---help--- + Support for LCDC panel auto detect + . + . + . + +config FB_MSM_MDDI_PRISM_WVGA + bool "MDDI Prism WVGA Panel" + select FB_MSM_MDDI + ---help--- + Support for MDDI Prism WVGA (800x480) panel + +config FB_MSM_MDDI_TOSHIBA_WVGA_PORTRAIT + bool "MDDI Toshiba WVGA Portrait Panel" + select FB_MSM_MDDI_TOSHIBA_COMMON + ---help--- + Support for MDDI Toshiba WVGA (480x800) panel + +config FB_MSM_MDDI_TOSHIBA_VGA + bool "MDDI Toshiba VGA Panel" + select FB_MSM_MDDI_TOSHIBA_COMMON_VGA + ---help--- + Support for MDDI Toshiba VGA (480x640) and QCIF (176x220) panel + +config FB_MSM_MDDI_TOSHIBA_WVGA + bool "MDDI Toshiba WVGA panel" + select FB_MSM_MDDI_TOSHIBA_COMMON + ---help--- + Support for MDDI Toshiba (800x480) WVGA panel + +config FB_MSM_MDDI_SHARP_QVGA_128x128 + bool "MDDI Sharp QVGA Dual Panel" + select FB_MSM_MDDI + ---help--- + Support for MDDI Sharp QVGA (240x320) and 128x128 dual panel + +config FB_MSM_MIPI_TOSHIBA_VIDEO_WVGA_PT_PANEL + bool "MIPI Toshiba WVGA PT Panel" + select FB_MSM_MIPI_TOSHIBA_VIDEO_WVGA_PT + +config FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT_PANEL + bool "MIPI Toshiba WSVGA PT Panel" + select FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT + +config FB_MSM_MIPI_NOVATEK_VIDEO_QHD_PT_PANEL + bool "MIPI NOVATEK VIDEO QHD PT Panel" + select FB_MSM_MIPI_NOVATEK_VIDEO_QHD_PT + +config FB_MSM_MIPI_NOVATEK_CMD_QHD_PT_PANEL + bool "MIPI NOVATEK CMD QHD PT Panel" + select FB_MSM_MIPI_NOVATEK_CMD_QHD_PT + +config FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT_PANEL + bool "MIPI Renesas Video FWVGA PT Panel" + select FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT + +config FB_MSM_MIPI_RENESAS_CMD_FWVGA_PT_PANEL + bool "MIPI Renesas Command FWVGA PT Panel" + select FB_MSM_MIPI_RENESAS_CMD_FWVGA_PT + +config FB_MSM_MIPI_SIMULATOR_VIDEO_PANEL + bool "MIPI Simulator Video Panel" + select FB_MSM_MIPI_SIMULATOR_VIDEO + +config FB_MSM_EBI2_TMD_QVGA_EPSON_QCIF + bool "EBI2 TMD QVGA Epson QCIF Dual Panel" + select FB_MSM_EBI2 + ---help--- + Support for EBI2 TMD QVGA (240x320) and Epson QCIF (176x220) panel + +config FB_MSM_PANEL_NONE + bool "NONE" + ---help--- + This will disable LCD panel +endchoice + +choice + prompt "Secondary LCD Panel" + depends on FB_MSM_MDP31 + default FB_MSM_SECONDARY_PANEL_NONE + +config FB_MSM_LCDC_EXTERNAL_WXGA + depends on FB_MSM_MDP31 + bool "External WXGA on LCDC" + select FB_MSM_LCDC_PANEL + ---help--- + Support for external WXGA display (1280x720) + +config FB_MSM_HDMI_SII_EXTERNAL_720P + depends on FB_MSM_MDP31 + bool "External SiI9022 HDMI 720P" + select FB_MSM_LCDC_PANEL + ---help--- + Support for external HDMI 720p display (1280x720p) + Using SiI9022 chipset + +config FB_MSM_SECONDARY_PANEL_NONE + bool "NONE" + ---help--- + No secondary panel +endchoice + +config FB_MSM_LCDC_DSUB + depends on FB_MSM_LCDC_SAMSUNG_WSVGA && FB_MSM_MDP40 && FB_MSM_LCDC_HW + bool "External DSUB support" + default n + ---help--- + Support for external DSUB (VGA) display up to 1440x900. The DSUB + display shares the same video bus as the primary LCDC attached display. + Typically only one of the two displays can be used at one time. + +config FB_MSM_EXT_INTERFACE_COMMON + bool + default n + +config FB_MSM_HDMI_COMMON + bool + default n + +config FB_MSM_HDMI_3D + bool + default n + +config FB_MSM_HDMI_ADV7520_PANEL + depends on FB_MSM_MDP40 && FB_MSM_OVERLAY + bool "LCDC HDMI ADV7520 720p Panel" + select FB_MSM_DTV + select FB_MSM_EXT_INTERFACE_COMMON + select FB_MSM_HDMI_COMMON + default n + ---help--- + Support for LCDC 720p HDMI panel attached to ADV7520 + +config FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT + depends on FB_MSM_HDMI_ADV7520_PANEL + bool "Use HDCP mode" + default y + ---help--- + Support for HDCP mode for ADV7520 HDMI 720p Panel + Choose to enable HDCP + + +config FB_MSM_HDMI_MSM_PANEL + depends on FB_MSM_MDP40 + bool "MSM HDMI 1080p Panel" + select FB_MSM_DTV + select FB_MSM_EXT_INTERFACE_COMMON + select FB_MSM_HDMI_COMMON + select FB_MSM_HDMI_3D + default n + ---help--- + Support for 480p/720p/1080i/1080p output through MSM HDMI + +config FB_MSM_HDMI_MSM_PANEL_DVI_SUPPORT + depends on FB_MSM_HDMI_MSM_PANEL + bool "Use DVI mode" + default n + ---help--- + Support for DVI mode for MSM HDMI 1080p Panel + +config FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + depends on FB_MSM_HDMI_MSM_PANEL + bool "Use HDCP mode" + default y + ---help--- + Support for HDCP mode for MSM HDMI 1080p Panel + Choose to enable HDCP + . + . + +choice + depends on (FB_MSM_MDP22 || FB_MSM_MDP31 || FB_MSM_MDP40) + prompt "TVOut Region" + default FB_MSM_TVOUT_NONE + +config FB_MSM_TVOUT_NTSC_M + bool "NTSC M" + select FB_MSM_TVOUT + select FB_MSM_EXT_INTERFACE_COMMON + ---help--- + Support for NTSC M region (North American and Korea) + . + . + . + +config FB_MSM_TVOUT_NTSC_J + bool "NTSC J" + select FB_MSM_TVOUT + select FB_MSM_EXT_INTERFACE_COMMON + ---help--- + Support for NTSC J region (Japan) + . + . + . + +config FB_MSM_TVOUT_PAL_BDGHIN + bool "PAL BDGHIN" + select FB_MSM_TVOUT + select FB_MSM_EXT_INTERFACE_COMMON + ---help--- + Support for PAL BDGHIN region (Non-argentina PAL-N) + . + . + . + +config FB_MSM_TVOUT_PAL_M + bool "PAL M" + select FB_MSM_TVOUT + select FB_MSM_EXT_INTERFACE_COMMON + ---help--- + Support for PAL M region + . + . + . + +config FB_MSM_TVOUT_PAL_N + bool "PAL N" + select FB_MSM_TVOUT + select FB_MSM_EXT_INTERFACE_COMMON + ---help--- + Support for PAL N region (Argentina PAL-N) + . + . + . + +config FB_MSM_TVOUT_NONE + bool "NONE" + ---help--- + This will disable TV Out functionality. +endchoice + +config FB_MSM_TVOUT_SVIDEO + bool "TVOut on S-video" + depends on FB_MSM_TVOUT + default n + ---help--- + Selects whether the TVOut signal uses S-video. + Choose n for composite output. + +choice + depends on FB_MSM_MDP22 + prompt "External MDDI" + default FB_MSM_EXTMDDI_SVGA + +config FB_MSM_EXTMDDI_SVGA + bool "External MDDI SVGA" + select FB_MSM_MDDI + select FB_MSM_EXTMDDI + ---help--- + Support for MSM SVGA (800x600) external MDDI panel + +config FB_MSM_EXTMDDI_NONE + bool "NONE" + ---help--- + This will disable External MDDI functionality. +endchoice + +choice + prompt "Default framebuffer color depth" + depends on FB_MSM_MDP40 || FB_MSM_MDP31 + default FB_MSM_DEFAULT_DEPTH_RGBA8888 + +config FB_MSM_DEFAULT_DEPTH_RGB565 + bool "16 bits per pixel (RGB565)" + +config FB_MSM_DEFAULT_DEPTH_ARGB8888 + bool "32 bits per pixel (ARGB8888)" + +config FB_MSM_DEFAULT_DEPTH_RGBA8888 + bool "32 bits per pixel (RGBA8888)" + +endchoice + +endif diff --git a/drivers/video/msm/Makefile b/drivers/video/msm/Makefile index 802d6ae523f..280e528cef4 100644 --- a/drivers/video/msm/Makefile +++ b/drivers/video/msm/Makefile @@ -1,19 +1,150 @@ - -# core framebuffer -# obj-y := msm_fb.o -# MDP DMA/PPP engine -# -obj-y += mdp.o mdp_scale_tables.o mdp_ppp.o +obj-$(CONFIG_FB_MSM_LOGO) += logo.o +obj-$(CONFIG_FB_BACKLIGHT) += msm_fb_bl.o + +# MDP +obj-y += mdp.o + +obj-$(CONFIG_DEBUG_FS) += mdp_debugfs.o + +ifeq ($(CONFIG_FB_MSM_MDP40),y) +obj-y += mdp4_util.o +else +obj-y += mdp_hw_init.o +obj-y += mdp_ppp.o +ifeq ($(CONFIG_FB_MSM_MDP31),y) +obj-y += mdp_ppp_v31.o +else +obj-y += mdp_ppp_v20.o +endif +endif + +ifeq ($(CONFIG_FB_MSM_OVERLAY),y) +obj-y += mdp4_overlay.o +obj-y += mdp4_overlay_lcdc.o +ifeq ($(CONFIG_FB_MSM_MIPI_DSI),y) +obj-y += mdp4_overlay_dsi_video.o +obj-y += mdp4_overlay_dsi_cmd.o +else +obj-y += mdp4_overlay_mddi.o +endif +else +obj-y += mdp_dma_lcdc.o +endif + +obj-$(CONFIG_FB_MSM_MDP303) += mdp_dma_dsi_video.o + +ifeq ($(CONFIG_FB_MSM_DTV),y) +obj-y += mdp4_dtv.o +obj-y += mdp4_overlay_dtv.o +endif + +obj-y += mdp_dma.o +obj-y += mdp_dma_s.o +obj-y += mdp_vsync.o +obj-y += mdp_cursor.o +obj-y += mdp_dma_tv.o +obj-$(CONFIG_ARCH_MSM7X27A) += msm_dss_io_7x27a.o +obj-$(CONFIG_ARCH_MSM8X60) += msm_dss_io_8x60.o +obj-$(CONFIG_ARCH_MSM8960) += msm_dss_io_8960.o + +# EBI2 +obj-$(CONFIG_FB_MSM_EBI2) += ebi2_lcd.o + +# LCDC +obj-$(CONFIG_FB_MSM_LCDC) += lcdc.o + +# MDDI +msm_mddi-objs := mddi.o mddihost.o mddihosti.o +obj-$(CONFIG_FB_MSM_MDDI) += msm_mddi.o + +# External MDDI +msm_mddi_ext-objs := mddihost_e.o mddi_ext.o +obj-$(CONFIG_FB_MSM_EXTMDDI) += msm_mddi_ext.o + +# MIPI gereric +msm_mipi-objs := mipi_dsi.o mipi_dsi_host.o +obj-$(CONFIG_FB_MSM_MIPI_DSI) += msm_mipi.o + +# MIPI manufacture +obj-$(CONFIG_FB_MSM_MIPI_DSI_TOSHIBA) += mipi_toshiba.o +obj-$(CONFIG_FB_MSM_MIPI_DSI_NOVATEK) += mipi_novatek.o +obj-$(CONFIG_FB_MSM_MIPI_DSI_RENESAS) += mipi_renesas.o +obj-$(CONFIG_FB_MSM_MIPI_DSI_SIMULATOR) += mipi_simulator.o + +# TVEnc +obj-$(CONFIG_FB_MSM_TVOUT) += tvenc.o +ifeq ($(CONFIG_FB_MSM_OVERLAY),y) +obj-$(CONFIG_FB_MSM_TVOUT) += mdp4_overlay_atv.o +endif + +# MSM FB Panel +obj-y += msm_fb_panel.o +obj-$(CONFIG_FB_MSM_EBI2_TMD_QVGA_EPSON_QCIF) += ebi2_tmd20.o +obj-$(CONFIG_FB_MSM_EBI2_TMD_QVGA_EPSON_QCIF) += ebi2_l2f.o + +ifeq ($(CONFIG_FB_MSM_MDDI_AUTO_DETECT),y) +obj-y += mddi_prism.o +obj-y += mddi_toshiba.o +obj-y += mddi_toshiba_vga.o +obj-y += mddi_toshiba_wvga_pt.o +obj-y += mddi_toshiba_wvga.o +obj-y += mddi_sharp.o +obj-y += mddi_orise.o +obj-y += mddi_quickvx.o +else +obj-$(CONFIG_FB_MSM_MDDI_PRISM_WVGA) += mddi_prism.o +obj-$(CONFIG_FB_MSM_MDDI_TOSHIBA_COMMON) += mddi_toshiba.o +obj-$(CONFIG_FB_MSM_MDDI_TOSHIBA_COMMON_VGA) += mddi_toshiba_vga.o +obj-$(CONFIG_FB_MSM_MDDI_TOSHIBA_WVGA_PORTRAIT) += mddi_toshiba_wvga_pt.o +obj-$(CONFIG_FB_MSM_MDDI_TOSHIBA_WVGA) += mddi_toshiba_wvga.o +obj-$(CONFIG_FB_MSM_MDDI_SHARP_QVGA_128x128) += mddi_sharp.o +obj-$(CONFIG_FB_MSM_MDDI_ORISE) += mddi_orise.o +obj-$(CONFIG_FB_MSM_MDDI_QUICKVX) += mddi_quickvx.o +endif + +ifeq ($(CONFIG_FB_MSM_MIPI_PANEL_DETECT),y) +obj-y += mipi_toshiba_video_wvga_pt.o mipi_toshiba_video_wsvga_pt.o +obj-y += mipi_novatek_video_qhd_pt.o mipi_novatek_cmd_qhd_pt.o +obj-y += mipi_renesas_video_fwvga_pt.o mipi_renesas_cmd_fwvga_pt.o +else +obj-$(CONFIG_FB_MSM_MIPI_TOSHIBA_VIDEO_WVGA_PT) += mipi_toshiba_video_wvga_pt.o +obj-$(CONFIG_FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT) += mipi_toshiba_video_wsvga_pt.o +obj-$(CONFIG_FB_MSM_MIPI_NOVATEK_VIDEO_QHD_PT) += mipi_novatek_video_qhd_pt.o +obj-$(CONFIG_FB_MSM_MIPI_NOVATEK_CMD_QHD_PT) += mipi_novatek_cmd_qhd_pt.o +obj-$(CONFIG_FB_MSM_MIPI_RENESAS_VIDEO_FWVGA_PT) += mipi_renesas_video_fwvga_pt.o +obj-$(CONFIG_FB_MSM_MIPI_RENESAS_CMD_FWVGA_PT) += mipi_renesas_cmd_fwvga_pt.o +obj-$(CONFIG_FB_MSM_MIPI_SIMULATOR_VIDEO) += mipi_simulator_video.o +endif + + + + +obj-$(CONFIG_FB_MSM_LCDC_PANEL) += lcdc_panel.o +obj-$(CONFIG_FB_MSM_LCDC_PRISM_WVGA) += lcdc_prism.o +obj-$(CONFIG_FB_MSM_LCDC_SAMSUNG_WSVGA) += lcdc_samsung_wsvga.o +obj-$(CONFIG_FB_MSM_LCDC_CHIMEI_WXGA) += lcdc_chimei_wxga.o +obj-$(CONFIG_FB_MSM_LCDC_EXTERNAL_WXGA) += lcdc_external.o +obj-$(CONFIG_FB_MSM_HDMI_SII_EXTERNAL_720P) += hdmi_sii9022.o +obj-$(CONFIG_FB_MSM_LCDC_GORDON_VGA) += lcdc_gordon.o +obj-$(CONFIG_FB_MSM_LCDC_WXGA) += lcdc_wxga.o +obj-$(CONFIG_FB_MSM_LCDC_TOSHIBA_WVGA_PT) += lcdc_toshiba_wvga_pt.o +obj-$(CONFIG_FB_MSM_LCDC_TOSHIBA_FWVGA_PT) += lcdc_toshiba_fwvga_pt.o +obj-$(CONFIG_FB_MSM_LCDC_SHARP_WVGA_PT) += lcdc_sharp_wvga_pt.o +obj-$(CONFIG_FB_MSM_LCDC_AUO_WVGA) += lcdc_auo_wvga.o +obj-$(CONFIG_FB_MSM_LCDC_SAMSUNG_OLED_PT) += lcdc_samsung_oled_pt.o +obj-$(CONFIG_FB_MSM_HDMI_ADV7520_PANEL) += adv7520.o +obj-$(CONFIG_FB_MSM_LCDC_ST15_WXGA) += lcdc_st15.o +obj-$(CONFIG_FB_MSM_HDMI_MSM_PANEL) += hdmi_msm.o +obj-$(CONFIG_FB_MSM_EXT_INTERFACE_COMMON) += external_common.o + +obj-$(CONFIG_FB_MSM_TVOUT) += tvout_msm.o -# MDDI interface -# -obj-y += mddi.o +obj-$(CONFIG_FB_MSM_EXTMDDI_SVGA) += mddi_ext_lcd.o -# MDDI client/panel drivers -# -obj-y += mddi_client_dummy.o -obj-y += mddi_client_toshiba.o -obj-y += mddi_client_nt35399.o +obj-$(CONFIG_MSM_VIDC_1080P) += vidc/ +obj-$(CONFIG_MSM_VIDC_720P) += vidc/ +clean: + rm *.o .*cmd diff --git a/drivers/video/msm/adv7520.c b/drivers/video/msm/adv7520.c new file mode 100644 index 00000000000..0900f23ba0e --- /dev/null +++ b/drivers/video/msm/adv7520.c @@ -0,0 +1,1005 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_fb.h" + +#define DEBUG +#define DEV_DBG_PREFIX "HDMI: " + +#include "external_common.h" + +/* #define PORT_DEBUG */ +/* #define TESTING_FORCE_480p */ + +#define HPD_DUTY_CYCLE 4 /*secs*/ + +static struct external_common_state_type hdmi_common; + +static struct i2c_client *hclient; + +static bool chip_power_on = FALSE; /* For chip power on/off */ +static bool enable_5v_on = FALSE; +static bool hpd_power_on = FALSE; +static atomic_t comm_power_on; /* For dtv power on/off (I2C) */ +static int suspend_count; + +static u8 reg[256]; /* HDMI panel registers */ + +struct hdmi_data { + struct msm_hdmi_platform_data *pd; + struct work_struct isr_work; +}; +static struct hdmi_data *dd; +static struct work_struct hpd_timer_work; + +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT +static struct work_struct hdcp_handle_work; +static int hdcp_activating; +static DEFINE_MUTEX(hdcp_state_mutex); +static int has_hdcp_hw_support = true; +#endif + +static struct timer_list hpd_timer; +static struct timer_list hpd_duty_timer; +static struct work_struct hpd_duty_work; +static unsigned int monitor_sense; +static boolean hpd_cable_chg_detected; + +struct wake_lock wlock; + +/* Change HDMI state */ +static void change_hdmi_state(int online) +{ + if (!external_common_state) + return; + + mutex_lock(&external_common_state_hpd_mutex); + external_common_state->hpd_state = online; + mutex_unlock(&external_common_state_hpd_mutex); + + if (!external_common_state->uevent_kobj) + return; + + if (online) + kobject_uevent(external_common_state->uevent_kobj, + KOBJ_ONLINE); + else + kobject_uevent(external_common_state->uevent_kobj, + KOBJ_OFFLINE); + DEV_INFO("adv7520_uevent: %d [suspend# %d]\n", online, suspend_count); +} + + +/* + * Read a value from a register on ADV7520 device + * If sucessfull returns value read , otherwise error. + */ +static u8 adv7520_read_reg(struct i2c_client *client, u8 reg) +{ + int err; + struct i2c_msg msg[2]; + u8 reg_buf[] = { reg }; + u8 data_buf[] = { 0 }; + + if (!client->adapter) + return -ENODEV; + if (!atomic_read(&comm_power_on)) { + DEV_WARN("%s: WARN: missing GPIO power\n", __func__); + return -ENODEV; + } + + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = reg_buf; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = 1; + msg[1].buf = data_buf; + + err = i2c_transfer(client->adapter, msg, 2); + + if (err < 0) { + DEV_INFO("%s: I2C err: %d\n", __func__, err); + return err; + } + +#ifdef PORT_DEBUG + DEV_INFO("HDMI[%02x] [R] %02x\n", reg, data); +#endif + return *data_buf; +} + +/* + * Write a value to a register on adv7520 device. + * Returns zero if successful, or non-zero otherwise. + */ +static int adv7520_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + int err; + struct i2c_msg msg[1]; + unsigned char data[2]; + + if (!client->adapter) + return -ENODEV; + if (!atomic_read(&comm_power_on)) { + DEV_WARN("%s: WARN: missing GPIO power\n", __func__); + return -ENODEV; + } + + msg->addr = client->addr; + msg->flags = 0; + msg->len = 2; + msg->buf = data; + data[0] = reg; + data[1] = val; + + err = i2c_transfer(client->adapter, msg, 1); + if (err >= 0) + return 0; +#ifdef PORT_DEBUG + DEV_INFO("HDMI[%02x] [W] %02x [%d]\n", reg, val, err); +#endif + return err; +} + +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT +static void adv7520_close_hdcp_link(void) +{ + if (!external_common_state->hdcp_active && !hdcp_activating) + return; + + DEV_INFO("HDCP: Close link\n"); + + reg[0xD5] = adv7520_read_reg(hclient, 0xD5); + reg[0xD5] &= 0xFE; + adv7520_write_reg(hclient, 0xD5, (u8)reg[0xD5]); + + reg[0x16] = adv7520_read_reg(hclient, 0x16); + reg[0x16] &= 0xFE; + adv7520_write_reg(hclient, 0x16, (u8)reg[0x16]); + + /* UnMute Audio */ + adv7520_write_reg(hclient, 0x0C, (u8)0x84); + + external_common_state->hdcp_active = FALSE; + mutex_lock(&hdcp_state_mutex); + hdcp_activating = FALSE; + mutex_unlock(&hdcp_state_mutex); +} + +static void adv7520_comm_power(int on, int show); +static void adv7520_hdcp_enable(struct work_struct *work) +{ + DEV_INFO("HDCP: Start reg[0xaf]=%02x (mute audio)\n", reg[0xaf]); + + adv7520_comm_power(1, 1); + + /* Mute Audio */ + adv7520_write_reg(hclient, 0x0C, (u8)0xC3); + + msleep(200); + /* Wait for BKSV ready interrupt */ + /* Read BKSV's keys from HDTV */ + reg[0xBF] = adv7520_read_reg(hclient, 0xBF); + reg[0xC0] = adv7520_read_reg(hclient, 0xC0); + reg[0xC1] = adv7520_read_reg(hclient, 0xC1); + reg[0xC2] = adv7520_read_reg(hclient, 0xC2); + reg[0xc3] = adv7520_read_reg(hclient, 0xC3); + + DEV_DBG("HDCP: BKSV={%02x,%02x,%02x,%02x,%02x}\n", reg[0xbf], reg[0xc0], + reg[0xc1], reg[0xc2], reg[0xc3]); + + /* Is SINK repeater */ + reg[0xBE] = adv7520_read_reg(hclient, 0xBE); + if (~(reg[0xBE] & 0x40)) { + ; /* compare with revocation list */ + /* Check 20 1's and 20 zero's */ + } else { + /* Don't implement HDCP if sink as a repeater */ + adv7520_write_reg(hclient, 0x0C, (u8)0x84); + mutex_lock(&hdcp_state_mutex); + hdcp_activating = FALSE; + mutex_unlock(&hdcp_state_mutex); + DEV_WARN("HDCP: Sink Repeater (%02x), (unmute audio)\n", + reg[0xbe]); + + adv7520_comm_power(0, 1); + return; + } + + msleep(200); + reg[0xB8] = adv7520_read_reg(hclient, 0xB8); + DEV_INFO("HDCP: Status reg[0xB8] is %02x\n", reg[0xb8]); + if (reg[0xb8] & 0x40) { + /* UnMute Audio */ + adv7520_write_reg(hclient, 0x0C, (u8)0x84); + DEV_INFO("HDCP: A/V content Encrypted (unmute audio)\n"); + external_common_state->hdcp_active = TRUE; + } + adv7520_comm_power(0, 1); + + mutex_lock(&hdcp_state_mutex); + hdcp_activating = FALSE; + mutex_unlock(&hdcp_state_mutex); +} +#endif + +static int adv7520_read_edid_block(int block, uint8 *edid_buf) +{ + u8 r = 0; + int ret; + struct i2c_msg msg[] = { + { .addr = reg[0x43] >> 1, + .flags = 0, + .len = 1, + .buf = &r }, + { .addr = reg[0x43] >> 1, + .flags = I2C_M_RD, + .len = 0x100, + .buf = edid_buf } }; + + if (block > 0) + return 0; + ret = i2c_transfer(hclient->adapter, msg, 2); + DEV_DBG("EDID block: addr=%02x, ret=%d\n", reg[0x43] >> 1, ret); + return (ret < 2) ? -ENODEV : 0; +} + +static void adv7520_read_edid(void) +{ + external_common_state->read_edid_block = adv7520_read_edid_block; + if (hdmi_common_read_edid()) { + u8 timeout; + DEV_INFO("%s: retry\n", __func__); + adv7520_write_reg(hclient, 0xc9, 0x13); + msleep(500); + timeout = (adv7520_read_reg(hclient, 0x96) & (1 << 2)); + if (timeout) { + hdmi_common_read_edid(); + } + } +} + +static void adv7520_chip_on(void) +{ + if (!chip_power_on) { + /* Get the current register holding the power bit. */ + unsigned long reg0xaf = adv7520_read_reg(hclient, 0xaf); + + dd->pd->core_power(1, 1); + + /* Set the HDMI select bit. */ + set_bit(1, ®0xaf); + DEV_INFO("%s: turn on chip power\n", __func__); + adv7520_write_reg(hclient, 0x41, 0x10); + adv7520_write_reg(hclient, 0xaf, (u8)reg0xaf); + chip_power_on = TRUE; + } else + DEV_INFO("%s: chip already has power\n", __func__); +} + +static void adv7520_chip_off(void) +{ + if (chip_power_on) { +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT + if (has_hdcp_hw_support) + adv7520_close_hdcp_link(); +#endif + + DEV_INFO("%s: turn off chip power\n", __func__); + adv7520_write_reg(hclient, 0x41, 0x50); + dd->pd->core_power(0, 1); + chip_power_on = FALSE; + } else + DEV_INFO("%s: chip is already off\n", __func__); + + monitor_sense = 0; + hpd_cable_chg_detected = FALSE; + + if (enable_5v_on) { + dd->pd->enable_5v(0); + enable_5v_on = FALSE; + } +} + +/* Power ON/OFF ADV7520 chip */ +static void adv7520_isr_w(struct work_struct *work); +static void adv7520_comm_power(int on, int show) +{ + if (!on) + atomic_dec(&comm_power_on); + dd->pd->comm_power(on, 0/*show*/); + if (on) + atomic_inc(&comm_power_on); +} + +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT +static void adv7520_start_hdcp(void); +#endif +static int adv7520_power_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); + + external_common_state->dev = &pdev->dev; + if (mfd != NULL) { + DEV_INFO("adv7520_power: ON (%dx%d %d)\n", + mfd->var_xres, mfd->var_yres, mfd->var_pixclock); + hdmi_common_get_video_format_from_drv_data(mfd); + } + + adv7520_comm_power(1, 1); + /* Check if HPD is signaled */ + if (adv7520_read_reg(hclient, 0x42) & (1 << 6)) { + DEV_INFO("power_on: cable detected\n"); + monitor_sense = adv7520_read_reg(hclient, 0xC6); +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT + if (has_hdcp_hw_support) { + if (!hdcp_activating) + adv7520_start_hdcp(); + } +#endif + } else + DEV_INFO("power_on: cable NOT detected\n"); + adv7520_comm_power(0, 1); + wake_lock(&wlock); + + return 0; +} + +static int adv7520_power_off(struct platform_device *pdev) +{ + DEV_INFO("power_off\n"); + adv7520_comm_power(1, 1); + adv7520_chip_off(); + wake_unlock(&wlock); + adv7520_comm_power(0, 1); + + return 0; +} + + +/* AV7520 chip specific initialization */ +static void adv7520_chip_init(void) +{ + /* Initialize the variables used to read/write the ADV7520 chip. */ + memset(®, 0xff, sizeof(reg)); + + /* Get the values from the "Fixed Registers That Must Be Set". */ + reg[0x98] = adv7520_read_reg(hclient, 0x98); + reg[0x9c] = adv7520_read_reg(hclient, 0x9c); + reg[0x9d] = adv7520_read_reg(hclient, 0x9d); + reg[0xa2] = adv7520_read_reg(hclient, 0xa2); + reg[0xa3] = adv7520_read_reg(hclient, 0xa3); + reg[0xde] = adv7520_read_reg(hclient, 0xde); + + /* Get the "HDMI/DVI Selection" register. */ + reg[0xaf] = adv7520_read_reg(hclient, 0xaf); + + /* Read Packet Memory I2C Address */ + reg[0x45] = adv7520_read_reg(hclient, 0x45); + + /* Hard coded values provided by ADV7520 data sheet. */ + reg[0x98] = 0x03; + reg[0x9c] = 0x38; + reg[0x9d] = 0x61; + reg[0xa2] = 0x94; + reg[0xa3] = 0x94; + reg[0xde] = 0x88; + + /* Set the HDMI select bit. */ + reg[0xaf] |= 0x16; + + /* Set the audio related registers. */ + reg[0x01] = 0x00; + reg[0x02] = 0x2d; + reg[0x03] = 0x80; + reg[0x0a] = 0x4d; + reg[0x0b] = 0x0e; + reg[0x0c] = 0x84; + reg[0x0d] = 0x10; + reg[0x12] = 0x00; + reg[0x14] = 0x00; + reg[0x15] = 0x20; + reg[0x44] = 0x79; + reg[0x73] = 0x01; + reg[0x76] = 0x00; + + /* Set 720p display related registers */ + reg[0x16] = 0x00; + + reg[0x18] = 0x46; + reg[0x55] = 0x00; + reg[0x3c] = 0x04; + + /* Set Interrupt Mask register for HPD/HDCP */ + reg[0x94] = 0xC0; +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT + if (has_hdcp_hw_support) + reg[0x95] = 0xC0; + else + reg[0x95] = 0x00; +#else + reg[0x95] = 0x00; +#endif + adv7520_write_reg(hclient, 0x94, reg[0x94]); + adv7520_write_reg(hclient, 0x95, reg[0x95]); + + /* Set Packet Memory I2C Address */ + reg[0x45] = 0x74; + + /* Set the values from the "Fixed Registers That Must Be Set". */ + adv7520_write_reg(hclient, 0x98, reg[0x98]); + adv7520_write_reg(hclient, 0x9c, reg[0x9c]); + adv7520_write_reg(hclient, 0x9d, reg[0x9d]); + adv7520_write_reg(hclient, 0xa2, reg[0xa2]); + adv7520_write_reg(hclient, 0xa3, reg[0xa3]); + adv7520_write_reg(hclient, 0xde, reg[0xde]); + + /* Set the "HDMI/DVI Selection" register. */ + adv7520_write_reg(hclient, 0xaf, reg[0xaf]); + + /* Set EDID Monitor address */ + reg[0x43] = 0x7E; + adv7520_write_reg(hclient, 0x43, reg[0x43]); + + /* Enable the i2s audio input. */ + adv7520_write_reg(hclient, 0x01, reg[0x01]); + adv7520_write_reg(hclient, 0x02, reg[0x02]); + adv7520_write_reg(hclient, 0x03, reg[0x03]); + adv7520_write_reg(hclient, 0x0a, reg[0x0a]); + adv7520_write_reg(hclient, 0x0b, reg[0x0b]); + adv7520_write_reg(hclient, 0x0c, reg[0x0c]); + adv7520_write_reg(hclient, 0x0d, reg[0x0d]); + adv7520_write_reg(hclient, 0x12, reg[0x12]); + adv7520_write_reg(hclient, 0x14, reg[0x14]); + adv7520_write_reg(hclient, 0x15, reg[0x15]); + adv7520_write_reg(hclient, 0x44, reg[0x44]); + adv7520_write_reg(hclient, 0x73, reg[0x73]); + adv7520_write_reg(hclient, 0x76, reg[0x76]); + + /* Enable 720p display */ + adv7520_write_reg(hclient, 0x16, reg[0x16]); + adv7520_write_reg(hclient, 0x18, reg[0x18]); + adv7520_write_reg(hclient, 0x55, reg[0x55]); + adv7520_write_reg(hclient, 0x3c, reg[0x3c]); + + /* Set Packet Memory address to avoid conflict + with Bosch Accelerometer */ + adv7520_write_reg(hclient, 0x45, reg[0x45]); + + /* Ensure chip is in low-power state */ + adv7520_write_reg(hclient, 0x41, 0x50); +} + +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT +static void adv7520_start_hdcp(void) +{ + mutex_lock(&hdcp_state_mutex); + if (hdcp_activating) { + DEV_WARN("adv7520_timer: HDCP already" + " activating, skipping\n"); + mutex_unlock(&hdcp_state_mutex); + return; + } + hdcp_activating = TRUE; + mutex_unlock(&hdcp_state_mutex); + + del_timer(&hpd_duty_timer); + + adv7520_comm_power(1, 1); + + if (!enable_5v_on) { + dd->pd->enable_5v(1); + enable_5v_on = TRUE; + adv7520_chip_on(); + } + + /* request for HDCP */ + reg[0xaf] = adv7520_read_reg(hclient, 0xaf); + reg[0xaf] |= 0x90; + adv7520_write_reg(hclient, 0xaf, reg[0xaf]); + reg[0xaf] = adv7520_read_reg(hclient, 0xaf); + + reg[0xba] = adv7520_read_reg(hclient, 0xba); + reg[0xba] |= 0x10; + adv7520_write_reg(hclient, 0xba, reg[0xba]); + reg[0xba] = adv7520_read_reg(hclient, 0xba); + adv7520_comm_power(0, 1); + + DEV_INFO("HDCP: reg[0xaf]=0x%02x, reg[0xba]=0x%02x, waiting for BKSV\n", + reg[0xaf], reg[0xba]); + + /* will check for HDCP Error or BKSV ready */ + mod_timer(&hpd_duty_timer, jiffies + HZ/2); +} +#endif + +static void adv7520_hpd_timer_w(struct work_struct *work) +{ + if (!external_common_state->hpd_feature_on) { + DEV_INFO("adv7520_timer: skipping, feature off\n"); + return; + } + + if ((monitor_sense & 0x4) && !external_common_state->hpd_state) { + int timeout; + DEV_DBG("adv7520_timer: Cable Detected\n"); + adv7520_comm_power(1, 1); + adv7520_chip_on(); + + if (hpd_cable_chg_detected) { + hpd_cable_chg_detected = FALSE; + /* Ensure 5V to read EDID */ + if (!enable_5v_on) { + dd->pd->enable_5v(1); + enable_5v_on = TRUE; + } + msleep(500); + timeout = (adv7520_read_reg(hclient, 0x96) & (1 << 2)); + if (timeout) { + DEV_DBG("adv7520_timer: EDID-Ready..\n"); + adv7520_read_edid(); + } else + DEV_DBG("adv7520_timer: EDID TIMEOUT (C9=%02x)" + "\n", adv7520_read_reg(hclient, 0xC9)); + } +#ifdef TESTING_FORCE_480p + external_common_state->disp_mode_list.num_of_elements = 1; + external_common_state->disp_mode_list.disp_mode_list[0] = + HDMI_VFRMT_720x480p60_16_9; +#endif + adv7520_comm_power(0, 1); +#ifndef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT + /* HDMI_5V_EN not needed anymore */ + if (enable_5v_on) { + DEV_DBG("adv7520_timer: EDID done, no HDCP, 5V not " + "needed anymore\n"); + dd->pd->enable_5v(0); + enable_5v_on = FALSE; + } +#endif + change_hdmi_state(1); + } else if (external_common_state->hpd_state) { + adv7520_comm_power(1, 1); + adv7520_chip_off(); + adv7520_comm_power(0, 1); + DEV_DBG("adv7520_timer: Cable Removed\n"); + change_hdmi_state(0); + } +} + +static void adv7520_hpd_timer_f(unsigned long data) +{ + schedule_work(&hpd_timer_work); +} + +static void adv7520_isr_w(struct work_struct *work) +{ + static int state_count; + static u8 last_reg0x96; + u8 reg0xc8; + u8 reg0x96; +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT + static u8 last_reg0x97; + u8 reg0x97 = 0; +#endif + if (!external_common_state->hpd_feature_on) { + DEV_DBG("adv7520_irq: skipping, hpd off\n"); + return; + } + + adv7520_comm_power(1, 1); + reg0x96 = adv7520_read_reg(hclient, 0x96); +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT + if (has_hdcp_hw_support) { + reg0x97 = adv7520_read_reg(hclient, 0x97); + /* Clearing the Interrupts */ + adv7520_write_reg(hclient, 0x97, reg0x97); + } +#endif + /* Clearing the Interrupts */ + adv7520_write_reg(hclient, 0x96, reg0x96); + + if ((reg0x96 == 0xC0) || (reg0x96 & 0x40)) { +#ifdef DEBUG + unsigned int hpd_state = adv7520_read_reg(hclient, 0x42); +#endif + monitor_sense = adv7520_read_reg(hclient, 0xC6); + DEV_DBG("adv7520_irq: reg[0x42]=%02x && reg[0xC6]=%02x\n", + hpd_state, monitor_sense); + + if (!enable_5v_on) { + dd->pd->enable_5v(1); + enable_5v_on = TRUE; + } + if (!hpd_power_on) { + dd->pd->core_power(1, 1); + hpd_power_on = TRUE; + } + + /* Timer for catching interrupt debouning */ + DEV_DBG("adv7520_irq: Timer in .5sec\n"); + hpd_cable_chg_detected = TRUE; + mod_timer(&hpd_timer, jiffies + HZ/2); + } +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT + if (has_hdcp_hw_support) { + if (hdcp_activating) { + /* HDCP controller error Interrupt */ + if (reg0x97 & 0x80) { + DEV_ERR("adv7520_irq: HDCP_ERROR\n"); + state_count = 0; + adv7520_close_hdcp_link(); + /* BKSV Ready interrupts */ + } else if (reg0x97 & 0x40) { + DEV_INFO("adv7520_irq: BKSV keys ready, Begin" + " HDCP encryption\n"); + state_count = 0; + schedule_work(&hdcp_handle_work); + } else if (++state_count > 2 && (monitor_sense & 0x4)) { + DEV_INFO("adv7520_irq: Still waiting for BKSV," + "restart HDCP\n"); + hdcp_activating = FALSE; + state_count = 0; + adv7520_chip_off(); + adv7520_start_hdcp(); + } + reg0xc8 = adv7520_read_reg(hclient, 0xc8); + DEV_INFO("adv7520_irq: DDC controller reg[0xC8]=0x%02x," + "state_count=%d, monitor_sense=%x\n", + reg0xc8, state_count, monitor_sense); + } else if (!external_common_state->hdcp_active + && (monitor_sense & 0x4)) { + DEV_INFO("adv7520_irq: start HDCP with" + " monitor sense\n"); + state_count = 0; + adv7520_start_hdcp(); + } else + state_count = 0; + if (last_reg0x97 != reg0x97 || last_reg0x96 != reg0x96) + DEV_DBG("adv7520_irq: reg[0x96]=%02x " + "reg[0x97]=%02x: HDCP: %d\n", reg0x96, reg0x97, + external_common_state->hdcp_active); + last_reg0x97 = reg0x97; + } else { + if (last_reg0x96 != reg0x96) + DEV_DBG("adv7520_irq: reg[0x96]=%02x\n", reg0x96); + } +#else + if (last_reg0x96 != reg0x96) + DEV_DBG("adv7520_irq: reg[0x96]=%02x\n", reg0x96); +#endif + last_reg0x96 = reg0x96; + adv7520_comm_power(0, 1); +} + +static void adv7520_hpd_duty_work(struct work_struct *work) +{ + if (!external_common_state->hpd_feature_on) { + DEV_WARN("%s: hpd feature is off, skipping\n", __func__); + return; + } + + dd->pd->core_power(1, 0); + msleep(10); + adv7520_isr_w(NULL); + dd->pd->core_power(0, 0); +} + +static void adv7520_hpd_duty_timer_f(unsigned long data) +{ + if (!external_common_state->hpd_feature_on) { + DEV_WARN("%s: hpd feature is off, skipping\n", __func__); + return; + } + + mod_timer(&hpd_duty_timer, jiffies + HPD_DUTY_CYCLE*HZ); + schedule_work(&hpd_duty_work); +} + +static const struct i2c_device_id adv7520_id[] = { + { ADV7520_DRV_NAME , 0}, + {} +}; + +static struct msm_fb_panel_data hdmi_panel_data = { + .on = adv7520_power_on, + .off = adv7520_power_off, +}; + +static struct platform_device hdmi_device = { + .name = ADV7520_DRV_NAME , + .id = 2, + .dev = { + .platform_data = &hdmi_panel_data, + } +}; + +static void adv7520_ensure_init(void) +{ + static boolean init_done; + if (!init_done) { + int rc = dd->pd->init_irq(); + if (rc) { + DEV_ERR("adv7520_init: init_irq: %d\n", rc); + return; + } + + init_done = TRUE; + } + DEV_INFO("adv7520_init: chip init\n"); + adv7520_comm_power(1, 1); + adv7520_chip_init(); + adv7520_comm_power(0, 1); +} + +static int adv7520_hpd_feature(int on) +{ + int rc = 0; + + if (!on) { + if (enable_5v_on) { + dd->pd->enable_5v(0); + enable_5v_on = FALSE; + } + if (hpd_power_on) { + dd->pd->core_power(0, 1); + hpd_power_on = FALSE; + } + + DEV_DBG("adv7520_hpd: %d: stop duty timer\n", on); + del_timer(&hpd_timer); + del_timer(&hpd_duty_timer); + external_common_state->hpd_state = 0; + } + + if (on) { + dd->pd->core_power(1, 0); + adv7520_ensure_init(); + + adv7520_comm_power(1, 1); + monitor_sense = adv7520_read_reg(hclient, 0xC6); + DEV_DBG("adv7520_irq: reg[0xC6]=%02x\n", monitor_sense); + adv7520_comm_power(0, 1); + dd->pd->core_power(0, 0); + + if (monitor_sense & 0x4) { + if (!enable_5v_on) { + dd->pd->enable_5v(1); + enable_5v_on = TRUE; + } + if (!hpd_power_on) { + dd->pd->core_power(1, 1); + hpd_power_on = TRUE; + } + + hpd_cable_chg_detected = TRUE; + mod_timer(&hpd_timer, jiffies + HZ/2); + } + + DEV_DBG("adv7520_hpd: %d start duty timer\n", on); + mod_timer(&hpd_duty_timer, jiffies + HZ/100); + } + + DEV_INFO("adv7520_hpd: %d\n", on); + return rc; +} + +static int __devinit + adv7520_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int rc; + struct platform_device *fb_dev; + + dd = kzalloc(sizeof *dd, GFP_KERNEL); + if (!dd) { + rc = -ENOMEM; + goto probe_exit; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + external_common_state->dev = &client->dev; + + /* Init real i2c_client */ + hclient = client; + + i2c_set_clientdata(client, dd); + dd->pd = client->dev.platform_data; + if (!dd->pd) { + rc = -ENODEV; + goto probe_free; + } + + INIT_WORK(&dd->isr_work, adv7520_isr_w); + INIT_WORK(&hpd_timer_work, adv7520_hpd_timer_w); +#ifdef CONFIG_FB_MSM_HDMI_ADV7520_PANEL_HDCP_SUPPORT + if (dd->pd->check_hdcp_hw_support) + has_hdcp_hw_support = dd->pd->check_hdcp_hw_support(); + + if (has_hdcp_hw_support) + INIT_WORK(&hdcp_handle_work, adv7520_hdcp_enable); + else + DEV_INFO("%s: no hdcp hw support.\n", __func__); +#endif + + init_timer(&hpd_timer); + hpd_timer.function = adv7520_hpd_timer_f; + hpd_timer.data = (unsigned long)NULL; + hpd_timer.expires = 0xffffffff; + add_timer(&hpd_timer); + + external_common_state->hpd_feature = adv7520_hpd_feature; + DEV_INFO("adv7520_probe: HPD detection on request\n"); + init_timer(&hpd_duty_timer); + hpd_duty_timer.function = adv7520_hpd_duty_timer_f; + hpd_duty_timer.data = (unsigned long)NULL; + hpd_duty_timer.expires = 0xffffffff; + add_timer(&hpd_duty_timer); + INIT_WORK(&hpd_duty_work, adv7520_hpd_duty_work); + DEV_INFO("adv7520_probe: HPD detection ON (duty)\n"); + + fb_dev = msm_fb_add_device(&hdmi_device); + + if (fb_dev) { + rc = external_common_state_create(fb_dev); + if (rc) + goto probe_free; + } else + DEV_ERR("adv7520_probe: failed to add fb device\n"); + + return 0; + +probe_free: + kfree(dd); + dd = NULL; +probe_exit: + return rc; + +} + +static int __devexit adv7520_remove(struct i2c_client *client) +{ + if (!client->adapter) { + DEV_ERR("%s: No HDMI Device\n", __func__); + return -ENODEV; + } + wake_lock_destroy(&wlock); + kfree(dd); + dd = NULL; + return 0; +} + +#ifdef CONFIG_SUSPEND +static int adv7520_i2c_suspend(struct device *dev) +{ + DEV_INFO("%s\n", __func__); + + ++suspend_count; + + if (external_common_state->hpd_feature_on) { + DEV_DBG("%s: stop duty timer\n", __func__); + del_timer(&hpd_duty_timer); + del_timer(&hpd_timer); + } + + /* Turn off LDO8 and go into low-power state */ + if (chip_power_on) { + DEV_DBG("%s: turn off power\n", __func__); + adv7520_comm_power(1, 1); + adv7520_write_reg(hclient, 0x41, 0x50); + adv7520_comm_power(0, 1); + dd->pd->core_power(0, 1); + } + + return 0; +} + +static int adv7520_i2c_resume(struct device *dev) +{ + DEV_INFO("%s\n", __func__); + + /* Turn on LDO8 and go into normal-power state */ + if (chip_power_on) { + DEV_DBG("%s: turn on power\n", __func__); + dd->pd->core_power(1, 1); + adv7520_comm_power(1, 1); + adv7520_write_reg(hclient, 0x41, 0x10); + adv7520_comm_power(0, 1); + } + + if (external_common_state->hpd_feature_on) { + DEV_DBG("%s: start duty timer\n", __func__); + mod_timer(&hpd_duty_timer, jiffies + HPD_DUTY_CYCLE*HZ); + } + + return 0; +} +#else +#define adv7520_i2c_suspend NULL +#define adv7520_i2c_resume NULL +#endif + +static const struct dev_pm_ops adv7520_device_pm_ops = { + .suspend = adv7520_i2c_suspend, + .resume = adv7520_i2c_resume, +}; + +static struct i2c_driver hdmi_i2c_driver = { + .driver = { + .name = ADV7520_DRV_NAME, + .owner = THIS_MODULE, + .pm = &adv7520_device_pm_ops, + }, + .probe = adv7520_probe, + .id_table = adv7520_id, + .remove = __devexit_p(adv7520_remove), +}; + +static int __init adv7520_init(void) +{ + int rc; + + pr_info("%s\n", __func__); + external_common_state = &hdmi_common; + external_common_state->video_resolution = HDMI_VFRMT_1280x720p60_16_9; + HDMI_SETUP_LUT(640x480p60_4_3); /* 25.20MHz */ + HDMI_SETUP_LUT(720x480p60_16_9); /* 27.03MHz */ + HDMI_SETUP_LUT(1280x720p60_16_9); /* 74.25MHz */ + + HDMI_SETUP_LUT(720x576p50_16_9); /* 27.00MHz */ + HDMI_SETUP_LUT(1280x720p50_16_9); /* 74.25MHz */ + + hdmi_common_init_panel_info(&hdmi_panel_data.panel_info); + + rc = i2c_add_driver(&hdmi_i2c_driver); + if (rc) { + pr_err("hdmi_init FAILED: i2c_add_driver rc=%d\n", rc); + goto init_exit; + } + + if (machine_is_msm7x30_surf() || machine_is_msm8x55_surf()) { + short *hdtv_mux = (short *)ioremap(0x8e000170 , 0x100); + *hdtv_mux++ = 0x020b; + *hdtv_mux = 0x8000; + iounmap(hdtv_mux); + } + wake_lock_init(&wlock, WAKE_LOCK_IDLE, "hdmi_active"); + + return 0; + +init_exit: + return rc; +} + +static void __exit adv7520_exit(void) +{ + i2c_del_driver(&hdmi_i2c_driver); +} + +module_init(adv7520_init); +module_exit(adv7520_exit); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); +MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); +MODULE_DESCRIPTION("ADV7520 HDMI driver"); diff --git a/drivers/video/msm/ebi2_l2f.c b/drivers/video/msm/ebi2_l2f.c new file mode 100644 index 00000000000..767b802cf6a --- /dev/null +++ b/drivers/video/msm/ebi2_l2f.c @@ -0,0 +1,566 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" + +#include +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" + +#include + +#include +#include + +#include +#include + +/* The following are for MSM5100 on Gator +*/ +#ifdef FEATURE_PM1000 +#include "pm1000.h" +#endif /* FEATURE_PM1000 */ +/* The following are for MSM6050 on Bambi +*/ +#ifdef FEATURE_PMIC_LCDKBD_LED_DRIVER +#include "pm.h" +#endif /* FEATURE_PMIC_LCDKBD_LED_DRIVER */ + +#ifdef DISP_DEVICE_18BPP +#undef DISP_DEVICE_18BPP +#define DISP_DEVICE_16BPP +#endif + +#define QCIF_WIDTH 176 +#define QCIF_HEIGHT 220 + +static void *DISP_CMD_PORT; +static void *DISP_DATA_PORT; + +#define DISP_CMD_DISON 0xaf +#define DISP_CMD_DISOFF 0xae +#define DISP_CMD_DISNOR 0xa6 +#define DISP_CMD_DISINV 0xa7 +#define DISP_CMD_DISCTL 0xca +#define DISP_CMD_GCP64 0xcb +#define DISP_CMD_GCP16 0xcc +#define DISP_CMD_GSSET 0xcd +#define DISP_GS_2 0x02 +#define DISP_GS_16 0x01 +#define DISP_GS_64 0x00 +#define DISP_CMD_SLPIN 0x95 +#define DISP_CMD_SLPOUT 0x94 +#define DISP_CMD_SD_PSET 0x75 +#define DISP_CMD_MD_PSET 0x76 +#define DISP_CMD_SD_CSET 0x15 +#define DISP_CMD_MD_CSET 0x16 +#define DISP_CMD_DATCTL 0xbc +#define DISP_DATCTL_666 0x08 +#define DISP_DATCTL_565 0x28 +#define DISP_DATCTL_444 0x38 +#define DISP_CMD_RAMWR 0x5c +#define DISP_CMD_RAMRD 0x5d +#define DISP_CMD_PTLIN 0xa8 +#define DISP_CMD_PTLOUT 0xa9 +#define DISP_CMD_ASCSET 0xaa +#define DISP_CMD_SCSTART 0xab +#define DISP_CMD_VOLCTL 0xc6 +#define DISP_VOLCTL_TONE 0x80 +#define DISP_CMD_NOp 0x25 +#define DISP_CMD_OSSEL 0xd0 +#define DISP_CMD_3500KSET 0xd1 +#define DISP_CMD_3500KEND 0xd2 +#define DISP_CMD_14MSET 0xd3 +#define DISP_CMD_14MEND 0xd4 + +#define DISP_CMD_OUT(cmd) outpw(DISP_CMD_PORT, cmd); + +#define DISP_DATA_OUT(data) outpw(DISP_DATA_PORT, data); + +#define DISP_DATA_IN() inpw(DISP_DATA_PORT); + +/* Epson device column number starts at 2 +*/ +#define DISP_SET_RECT(ulhc_row, lrhc_row, ulhc_col, lrhc_col) \ + DISP_CMD_OUT(DISP_CMD_SD_PSET) \ + DISP_DATA_OUT((ulhc_row) & 0xFF) \ + DISP_DATA_OUT((ulhc_row) >> 8) \ + DISP_DATA_OUT((lrhc_row) & 0xFF) \ + DISP_DATA_OUT((lrhc_row) >> 8) \ + DISP_CMD_OUT(DISP_CMD_SD_CSET) \ + DISP_DATA_OUT(((ulhc_col)+2) & 0xFF) \ + DISP_DATA_OUT(((ulhc_col)+2) >> 8) \ + DISP_DATA_OUT(((lrhc_col)+2) & 0xFF) \ + DISP_DATA_OUT(((lrhc_col)+2) >> 8) + +#define DISP_MIN_CONTRAST 0 +#define DISP_MAX_CONTRAST 127 +#define DISP_DEFAULT_CONTRAST 80 + +#define DISP_MIN_BACKLIGHT 0 +#define DISP_MAX_BACKLIGHT 15 +#define DISP_DEFAULT_BACKLIGHT 2 + +#define WAIT_SEC(sec) mdelay((sec)/1000) + +static word disp_area_start_row; +static word disp_area_end_row; +static byte disp_contrast = DISP_DEFAULT_CONTRAST; +static boolean disp_powered_up; +static boolean disp_initialized = FALSE; +/* For some reason the contrast set at init time is not good. Need to do + * it again + */ +static boolean display_on = FALSE; +static void epsonQcif_disp_init(struct platform_device *pdev); +static void epsonQcif_disp_set_contrast(word contrast); +static void epsonQcif_disp_set_display_area(word start_row, word end_row); +static int epsonQcif_disp_off(struct platform_device *pdev); +static int epsonQcif_disp_on(struct platform_device *pdev); +static void epsonQcif_disp_set_rect(int x, int y, int xres, int yres); + +volatile word databack; +static void epsonQcif_disp_init(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + int i; + + if (disp_initialized) + return; + + mfd = platform_get_drvdata(pdev); + + DISP_CMD_PORT = mfd->cmd_port; + DISP_DATA_PORT = mfd->data_port; + + /* Sleep in */ + DISP_CMD_OUT(DISP_CMD_SLPIN); + + /* Display off */ + DISP_CMD_OUT(DISP_CMD_DISOFF); + + /* Display normal */ + DISP_CMD_OUT(DISP_CMD_DISNOR); + + /* Set data mode */ + DISP_CMD_OUT(DISP_CMD_DATCTL); + DISP_DATA_OUT(DISP_DATCTL_565); + + /* Set display timing */ + DISP_CMD_OUT(DISP_CMD_DISCTL); + DISP_DATA_OUT(0x1c); /* p1 */ + DISP_DATA_OUT(0x02); /* p1 */ + DISP_DATA_OUT(0x82); /* p2 */ + DISP_DATA_OUT(0x00); /* p3 */ + DISP_DATA_OUT(0x00); /* p4 */ + DISP_DATA_OUT(0xe0); /* p5 */ + DISP_DATA_OUT(0x00); /* p5 */ + DISP_DATA_OUT(0xdc); /* p6 */ + DISP_DATA_OUT(0x00); /* p6 */ + DISP_DATA_OUT(0x02); /* p7 */ + DISP_DATA_OUT(0x00); /* p8 */ + + /* Set 64 gray scale level */ + DISP_CMD_OUT(DISP_CMD_GCP64); + DISP_DATA_OUT(0x08); /* p01 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x2a); /* p02 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x4e); /* p03 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x6b); /* p04 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x88); /* p05 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0xa3); /* p06 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0xba); /* p07 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0xd1); /* p08 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0xe5); /* p09 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0xf3); /* p10 */ + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x03); /* p11 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x13); /* p12 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x22); /* p13 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x2f); /* p14 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x3b); /* p15 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x46); /* p16 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x51); /* p17 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x5b); /* p18 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x64); /* p19 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x6c); /* p20 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x74); /* p21 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x7c); /* p22 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x83); /* p23 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x8a); /* p24 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x91); /* p25 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x98); /* p26 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x9f); /* p27 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xa6); /* p28 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xac); /* p29 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xb2); /* p30 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xb7); /* p31 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xbc); /* p32 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xc1); /* p33 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xc6); /* p34 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xcb); /* p35 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xd0); /* p36 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xd4); /* p37 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xd8); /* p38 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xdc); /* p39 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xe0); /* p40 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xe4); /* p41 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xe8); /* p42 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xec); /* p43 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xf0); /* p44 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xf4); /* p45 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xf8); /* p46 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xfb); /* p47 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xfe); /* p48 */ + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0x01); /* p49 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x03); /* p50 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x05); /* p51 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x07); /* p52 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x09); /* p53 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x0b); /* p54 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x0d); /* p55 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x0f); /* p56 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x11); /* p57 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x13); /* p58 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x15); /* p59 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x17); /* p60 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x19); /* p61 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x1b); /* p62 */ + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x1c); /* p63 */ + DISP_DATA_OUT(0x02); + + /* Set 16 gray scale level */ + DISP_CMD_OUT(DISP_CMD_GCP16); + DISP_DATA_OUT(0x1a); /* p01 */ + DISP_DATA_OUT(0x32); /* p02 */ + DISP_DATA_OUT(0x42); /* p03 */ + DISP_DATA_OUT(0x4c); /* p04 */ + DISP_DATA_OUT(0x58); /* p05 */ + DISP_DATA_OUT(0x5f); /* p06 */ + DISP_DATA_OUT(0x66); /* p07 */ + DISP_DATA_OUT(0x6b); /* p08 */ + DISP_DATA_OUT(0x70); /* p09 */ + DISP_DATA_OUT(0x74); /* p10 */ + DISP_DATA_OUT(0x78); /* p11 */ + DISP_DATA_OUT(0x7b); /* p12 */ + DISP_DATA_OUT(0x7e); /* p13 */ + DISP_DATA_OUT(0x80); /* p14 */ + DISP_DATA_OUT(0x82); /* p15 */ + + /* Set DSP column */ + DISP_CMD_OUT(DISP_CMD_MD_CSET); + DISP_DATA_OUT(0xff); + DISP_DATA_OUT(0x03); + DISP_DATA_OUT(0xff); + DISP_DATA_OUT(0x03); + + /* Set DSP page */ + DISP_CMD_OUT(DISP_CMD_MD_PSET); + DISP_DATA_OUT(0xff); + DISP_DATA_OUT(0x01); + DISP_DATA_OUT(0xff); + DISP_DATA_OUT(0x01); + + /* Set ARM column */ + DISP_CMD_OUT(DISP_CMD_SD_CSET); + DISP_DATA_OUT(0x02); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT((QCIF_WIDTH + 1) & 0xFF); + DISP_DATA_OUT((QCIF_WIDTH + 1) >> 8); + + /* Set ARM page */ + DISP_CMD_OUT(DISP_CMD_SD_PSET); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT(0x00); + DISP_DATA_OUT((QCIF_HEIGHT - 1) & 0xFF); + DISP_DATA_OUT((QCIF_HEIGHT - 1) >> 8); + + /* Set 64 gray scales */ + DISP_CMD_OUT(DISP_CMD_GSSET); + DISP_DATA_OUT(DISP_GS_64); + + DISP_CMD_OUT(DISP_CMD_OSSEL); + DISP_DATA_OUT(0); + + /* Sleep out */ + DISP_CMD_OUT(DISP_CMD_SLPOUT); + + WAIT_SEC(40000); + + /* Initialize power IC */ + DISP_CMD_OUT(DISP_CMD_VOLCTL); + DISP_DATA_OUT(DISP_VOLCTL_TONE); + + WAIT_SEC(40000); + + /* Set electronic volume, d'xx */ + DISP_CMD_OUT(DISP_CMD_VOLCTL); + DISP_DATA_OUT(DISP_DEFAULT_CONTRAST); /* value from 0 to 127 */ + + /* Initialize display data */ + DISP_SET_RECT(0, (QCIF_HEIGHT - 1), 0, (QCIF_WIDTH - 1)); + DISP_CMD_OUT(DISP_CMD_RAMWR); + for (i = 0; i < QCIF_HEIGHT * QCIF_WIDTH; i++) + DISP_DATA_OUT(0xffff); + + DISP_CMD_OUT(DISP_CMD_RAMRD); + databack = DISP_DATA_IN(); + databack = DISP_DATA_IN(); + databack = DISP_DATA_IN(); + databack = DISP_DATA_IN(); + + WAIT_SEC(80000); + + DISP_CMD_OUT(DISP_CMD_DISON); + + disp_area_start_row = 0; + disp_area_end_row = QCIF_HEIGHT - 1; + disp_powered_up = TRUE; + disp_initialized = TRUE; + epsonQcif_disp_set_display_area(0, QCIF_HEIGHT - 1); + display_on = TRUE; +} + +static void epsonQcif_disp_set_rect(int x, int y, int xres, int yres) +{ + if (!disp_initialized) + return; + + DISP_SET_RECT(y, y + yres - 1, x, x + xres - 1); + DISP_CMD_OUT(DISP_CMD_RAMWR); +} + +static void epsonQcif_disp_set_display_area(word start_row, word end_row) +{ + if (!disp_initialized) + return; + + if ((start_row == disp_area_start_row) + && (end_row == disp_area_end_row)) + return; + disp_area_start_row = start_row; + disp_area_end_row = end_row; + + /* Range checking + */ + if (end_row >= QCIF_HEIGHT) + end_row = QCIF_HEIGHT - 1; + if (start_row > end_row) + start_row = end_row; + + /* When display is not the full screen, gray scale is set to + ** 2; otherwise it is set to 64. + */ + if ((start_row == 0) && (end_row == (QCIF_HEIGHT - 1))) { + /* The whole screen */ + DISP_CMD_OUT(DISP_CMD_PTLOUT); + WAIT_SEC(10000); + DISP_CMD_OUT(DISP_CMD_DISOFF); + WAIT_SEC(100000); + DISP_CMD_OUT(DISP_CMD_GSSET); + DISP_DATA_OUT(DISP_GS_64); + WAIT_SEC(100000); + DISP_CMD_OUT(DISP_CMD_DISON); + } else { + /* partial screen */ + DISP_CMD_OUT(DISP_CMD_PTLIN); + DISP_DATA_OUT(start_row); + DISP_DATA_OUT(start_row >> 8); + DISP_DATA_OUT(end_row); + DISP_DATA_OUT(end_row >> 8); + DISP_CMD_OUT(DISP_CMD_GSSET); + DISP_DATA_OUT(DISP_GS_2); + } +} + +static int epsonQcif_disp_off(struct platform_device *pdev) +{ + if (!disp_initialized) + epsonQcif_disp_init(pdev); + + if (display_on) { + DISP_CMD_OUT(DISP_CMD_DISOFF); + DISP_CMD_OUT(DISP_CMD_SLPIN); + display_on = FALSE; + } + + return 0; +} + +static int epsonQcif_disp_on(struct platform_device *pdev) +{ + if (!disp_initialized) + epsonQcif_disp_init(pdev); + + if (!display_on) { + DISP_CMD_OUT(DISP_CMD_SLPOUT); + WAIT_SEC(40000); + DISP_CMD_OUT(DISP_CMD_DISON); + epsonQcif_disp_set_contrast(disp_contrast); + display_on = TRUE; + } + + return 0; +} + +static void epsonQcif_disp_set_contrast(word contrast) +{ + if (!disp_initialized) + return; + + /* Initialize power IC, d'24 */ + DISP_CMD_OUT(DISP_CMD_VOLCTL); + DISP_DATA_OUT(DISP_VOLCTL_TONE); + + WAIT_SEC(40000); + + /* Set electronic volume, d'xx */ + DISP_CMD_OUT(DISP_CMD_VOLCTL); + if (contrast > 127) + contrast = 127; + DISP_DATA_OUT(contrast); /* value from 0 to 127 */ + disp_contrast = (byte) contrast; +} /* End disp_set_contrast */ + +static void epsonQcif_disp_clear_screen_area( + word start_row, word end_row, word start_column, word end_column) { + int32 i; + + /* Clear the display screen */ + DISP_SET_RECT(start_row, end_row, start_column, end_column); + DISP_CMD_OUT(DISP_CMD_RAMWR); + i = (end_row - start_row + 1) * (end_column - start_column + 1); + for (; i > 0; i--) + DISP_DATA_OUT(0xffff); +} + +static int __init epsonQcif_probe(struct platform_device *pdev) +{ + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = epsonQcif_probe, + .driver = { + .name = "ebi2_epson_qcif", + }, +}; + +static struct msm_fb_panel_data epsonQcif_panel_data = { + .on = epsonQcif_disp_on, + .off = epsonQcif_disp_off, + .set_rect = epsonQcif_disp_set_rect, +}; + +static struct platform_device this_device = { + .name = "ebi2_epson_qcif", + .id = 0, + .dev = { + .platform_data = &epsonQcif_panel_data, + } +}; + +static int __init epsonQcif_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + + ret = platform_driver_register(&this_driver); + if (!ret) { + pinfo = &epsonQcif_panel_data.panel_info; + pinfo->xres = QCIF_WIDTH; + pinfo->yres = QCIF_HEIGHT; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = EBI2_PANEL; + pinfo->pdest = DISPLAY_2; + pinfo->wait_cycle = 0x808000; + pinfo->bpp = 16; + pinfo->fb_num = 2; + pinfo->lcd.vsync_enable = FALSE; + + ret = platform_device_register(&this_device); + if (ret) + platform_driver_unregister(&this_driver); + } + + return ret; +} + +module_init(epsonQcif_init); diff --git a/drivers/video/msm/ebi2_lcd.c b/drivers/video/msm/ebi2_lcd.c new file mode 100644 index 00000000000..68590afd04a --- /dev/null +++ b/drivers/video/msm/ebi2_lcd.c @@ -0,0 +1,268 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb.h" + +static int ebi2_lcd_probe(struct platform_device *pdev); +static int ebi2_lcd_remove(struct platform_device *pdev); + +static int ebi2_lcd_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int ebi2_lcd_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static struct dev_pm_ops ebi2_lcd_dev_pm_ops = { + .runtime_suspend = ebi2_lcd_runtime_suspend, + .runtime_resume = ebi2_lcd_runtime_resume, +}; + +static struct platform_driver ebi2_lcd_driver = { + .probe = ebi2_lcd_probe, + .remove = ebi2_lcd_remove, + .suspend = NULL, + .suspend_late = NULL, + .resume_early = NULL, + .resume = NULL, + .shutdown = NULL, + .driver = { + .name = "ebi2_lcd", + .pm = &ebi2_lcd_dev_pm_ops, + }, +}; + +static void *ebi2_base; +static void *ebi2_lcd_cfg0; +static void *ebi2_lcd_cfg1; +static void __iomem *lcd01_base; +static void __iomem *lcd02_base; +static int ebi2_lcd_resource_initialized; + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; + +static int ebi2_lcd_probe(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct platform_device *mdp_dev = NULL; + struct msm_fb_panel_data *pdata = NULL; + int rc, i; + + if (pdev->id == 0) { + for (i = 0; i < pdev->num_resources; i++) { + if (!strncmp(pdev->resource[i].name, "base", 4)) { + ebi2_base = ioremap(pdev->resource[i].start, + pdev->resource[i].end - + pdev->resource[i].start + 1); + if (!ebi2_base) { + printk(KERN_ERR + "ebi2_base ioremap failed!\n"); + return -ENOMEM; + } + ebi2_lcd_cfg0 = (void *)(ebi2_base + 0x20); + ebi2_lcd_cfg1 = (void *)(ebi2_base + 0x24); + } else if (!strncmp(pdev->resource[i].name, + "lcd01", 5)) { + lcd01_base = ioremap(pdev->resource[i].start, + pdev->resource[i].end - + pdev->resource[i].start + 1); + if (!lcd01_base) { + printk(KERN_ERR + "lcd01_base ioremap failed!\n"); + return -ENOMEM; + } + } else if (!strncmp(pdev->resource[i].name, + "lcd02", 5)) { + lcd02_base = ioremap(pdev->resource[i].start, + pdev->resource[i].end - + pdev->resource[i].start + 1); + if (!lcd02_base) { + printk(KERN_ERR + "lcd02_base ioremap failed!\n"); + return -ENOMEM; + } + } + } + ebi2_lcd_resource_initialized = 1; + return 0; + } + + if (!ebi2_lcd_resource_initialized) + return -EPERM; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; + + if (ebi2_base == NULL) + return -ENOMEM; + + mdp_dev = platform_device_alloc("mdp", pdev->id); + if (!mdp_dev) + return -ENOMEM; + + /* link to the latest pdev */ + mfd->pdev = mdp_dev; + mfd->dest = DISPLAY_LCD; + + /* add panel data */ + if (platform_device_add_data + (mdp_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + printk(KERN_ERR "ebi2_lcd_probe: platform_device_add_data failed!\n"); + platform_device_put(mdp_dev); + return -ENOMEM; + } + + /* data chain */ + pdata = mdp_dev->dev.platform_data; + pdata->on = panel_next_on; + pdata->off = panel_next_off; + pdata->next = pdev; + + /* get/set panel specific fb info */ + mfd->panel_info = pdata->panel_info; + + if (mfd->panel_info.bpp == 24) + mfd->fb_imgType = MDP_RGB_888; + else + mfd->fb_imgType = MDP_RGB_565; + + /* config msm ebi2 lcd register */ + if (mfd->panel_info.pdest == DISPLAY_1) { + outp32(ebi2_base, + (inp32(ebi2_base) & (~(EBI2_PRIM_LCD_CLR))) | + EBI2_PRIM_LCD_SEL); + /* + * current design has one set of cfg0/1 register to control + * both EBI2 channels. so, we're using the PRIM channel to + * configure both. + */ + outp32(ebi2_lcd_cfg0, mfd->panel_info.wait_cycle); + if (mfd->panel_info.bpp == 18) + outp32(ebi2_lcd_cfg1, 0x01000000); + else + outp32(ebi2_lcd_cfg1, 0x0); + } else { +#ifdef DEBUG_EBI2_LCD + /* + * confliting with QCOM SURF FPGA CS. + * OEM should enable below for their CS mapping + */ + outp32(ebi2_base, (inp32(ebi2_base)&(~(EBI2_SECD_LCD_CLR))) + |EBI2_SECD_LCD_SEL); +#endif + } + + /* + * map cs (chip select) address + */ + if (mfd->panel_info.pdest == DISPLAY_1) { + mfd->cmd_port = lcd01_base; + mfd->data_port = + (void *)((uint32) mfd->cmd_port + EBI2_PRIM_LCD_RS_PIN); + mfd->data_port_phys = + (void *)(LCD_PRIM_BASE_PHYS + EBI2_PRIM_LCD_RS_PIN); + } else { + mfd->cmd_port = lcd01_base; + mfd->data_port = + (void *)((uint32) mfd->cmd_port + EBI2_SECD_LCD_RS_PIN); + mfd->data_port_phys = + (void *)(LCD_SECD_BASE_PHYS + EBI2_SECD_LCD_RS_PIN); + } + + /* + * set driver data + */ + platform_set_drvdata(mdp_dev, mfd); + + /* + * register in mdp driver + */ + rc = platform_device_add(mdp_dev); + if (rc) { + goto ebi2_lcd_probe_err; + } + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + + pdev_list[pdev_list_cnt++] = pdev; + return 0; + + ebi2_lcd_probe_err: + platform_device_put(mdp_dev); + return rc; +} + +static int ebi2_lcd_remove(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return 0; + + if (mfd->key != MFD_KEY) + return 0; + + iounmap(mfd->cmd_port); + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int ebi2_lcd_register_driver(void) +{ + return platform_driver_register(&ebi2_lcd_driver); +} + +static int __init ebi2_lcd_driver_init(void) +{ + return ebi2_lcd_register_driver(); +} + +module_init(ebi2_lcd_driver_init); diff --git a/drivers/video/msm/ebi2_tmd20.c b/drivers/video/msm/ebi2_tmd20.c new file mode 100644 index 00000000000..280373fcd50 --- /dev/null +++ b/drivers/video/msm/ebi2_tmd20.c @@ -0,0 +1,1120 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" + +#include +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" + +#include + +#include +#include + +#include +#include + +/* #define TMD20QVGA_LCD_18BPP */ +#define QVGA_WIDTH 240 +#define QVGA_HEIGHT 320 + +#ifdef TMD20QVGA_LCD_18BPP +#define DISP_QVGA_18BPP(x) ((((x)<<2) & 0x3FC00)|(( (x)<<1)& 0x1FE)) +#define DISP_REG(name) uint32 register_##name; +#define OUTPORT(x, y) outpdw(x, y) +#define INPORT(x) inpdw(x) +#else +#define DISP_QVGA_18BPP(x) (x) +#define DISP_REG(name) uint16 register_##name; +#define OUTPORT(x, y) outpw(x, y) +#define INPORT(x) intpw(x) +#endif + +static void *DISP_CMD_PORT; +static void *DISP_DATA_PORT; + +#define DISP_RNTI 0x10 + +#define DISP_CMD_OUT(cmd) OUTPORT(DISP_CMD_PORT, DISP_QVGA_18BPP(cmd)) +#define DISP_DATA_OUT(data) OUTPORT(DISP_DATA_PORT, data) +#define DISP_DATA_IN() INPORT(DISP_DATA_PORT) + +#if (defined(TMD20QVGA_LCD_18BPP)) +#define DISP_DATA_OUT_16TO18BPP(x) \ + DISP_DATA_OUT((((x)&0xf800)<<2|((x)&0x80000)>>3) \ + | (((x)&0x7e0)<<1) \ + | (((x)&0x1F)<<1|((x)&0x10)>>4)) +#else +#define DISP_DATA_OUT_16TO18BPP(x) \ + DISP_DATA_OUT(x) +#endif + +#define DISP_WRITE_OUT(addr, data) \ + register_##addr = DISP_QVGA_18BPP(data); \ + DISP_CMD_OUT(addr); \ + DISP_DATA_OUT(register_##addr); + +#define DISP_UPDATE_VALUE(addr, bitmask, data) \ + DISP_WRITE_OUT(##addr, (register_##addr & ~(bitmask)) | (data)); + +#define DISP_VAL_IF(bitvalue, bitmask) \ + ((bitvalue) ? (bitmask) : 0) + +/* QVGA = 256 x 320 */ +/* actual display is 240 x 320...offset by 0x10 */ +#define DISP_ROW_COL_TO_ADDR(row, col) ((row) * 0x100 + col) +#define DISP_SET_RECT(ulhc_row, lrhc_row, ulhc_col, lrhc_col) \ + { \ + DISP_WRITE_OUT(DISP_HORZ_RAM_ADDR_POS_1_ADDR, (ulhc_col) + tmd20qvga_panel_offset); \ + DISP_WRITE_OUT(DISP_HORZ_RAM_ADDR_POS_2_ADDR, (lrhc_col) + tmd20qvga_panel_offset); \ + DISP_WRITE_OUT(DISP_VERT_RAM_ADDR_POS_1_ADDR, (ulhc_row)); \ + DISP_WRITE_OUT(DISP_VERT_RAM_ADDR_POS_2_ADDR, (lrhc_row)); \ + DISP_WRITE_OUT(DISP_RAM_ADDR_SET_1_ADDR, (ulhc_col) + tmd20qvga_panel_offset); \ + DISP_WRITE_OUT(DISP_RAM_ADDR_SET_2_ADDR, (ulhc_row)); \ + } + +#define WAIT_MSEC(msec) mdelay(msec) + +/* + * TMD QVGA Address + */ +/* Display Control */ +#define DISP_START_OSCILLATION_ADDR 0x000 +DISP_REG(DISP_START_OSCILLATION_ADDR) +#define DISP_DRIVER_OUTPUT_CTL_ADDR 0x001 + DISP_REG(DISP_DRIVER_OUTPUT_CTL_ADDR) +#define DISP_LCD_DRIVING_SIG_ADDR 0x002 + DISP_REG(DISP_LCD_DRIVING_SIG_ADDR) +#define DISP_ENTRY_MODE_ADDR 0x003 + DISP_REG(DISP_ENTRY_MODE_ADDR) +#define DISP_DISPLAY_CTL_1_ADDR 0x007 + DISP_REG(DISP_DISPLAY_CTL_1_ADDR) +#define DISP_DISPLAY_CTL_2_ADDR 0x008 + DISP_REG(DISP_DISPLAY_CTL_2_ADDR) + +/* DISPLAY MODE 0x009 partial display not supported */ +#define DISP_POWER_SUPPLY_INTF_ADDR 0x00A + DISP_REG(DISP_POWER_SUPPLY_INTF_ADDR) + +/* DISPLAY MODE 0x00B xZoom feature is not supported */ +#define DISP_EXT_DISPLAY_CTL_1_ADDR 0x00C + DISP_REG(DISP_EXT_DISPLAY_CTL_1_ADDR) + +#define DISP_FRAME_CYCLE_CTL_ADDR 0x00D + DISP_REG(DISP_FRAME_CYCLE_CTL_ADDR) + +#define DISP_EXT_DISPLAY_CTL_2_ADDR 0x00E + DISP_REG(DISP_EXT_DISPLAY_CTL_2_ADDR) + +#define DISP_EXT_DISPLAY_CTL_3_ADDR 0x00F + DISP_REG(DISP_EXT_DISPLAY_CTL_3_ADDR) + +#define DISP_LTPS_CTL_1_ADDR 0x012 + DISP_REG(DISP_LTPS_CTL_1_ADDR) +#define DISP_LTPS_CTL_2_ADDR 0x013 + DISP_REG(DISP_LTPS_CTL_2_ADDR) +#define DISP_LTPS_CTL_3_ADDR 0x014 + DISP_REG(DISP_LTPS_CTL_3_ADDR) +#define DISP_LTPS_CTL_4_ADDR 0x018 + DISP_REG(DISP_LTPS_CTL_4_ADDR) +#define DISP_LTPS_CTL_5_ADDR 0x019 + DISP_REG(DISP_LTPS_CTL_5_ADDR) +#define DISP_LTPS_CTL_6_ADDR 0x01A + DISP_REG(DISP_LTPS_CTL_6_ADDR) +#define DISP_AMP_SETTING_ADDR 0x01C + DISP_REG(DISP_AMP_SETTING_ADDR) +#define DISP_MODE_SETTING_ADDR 0x01D + DISP_REG(DISP_MODE_SETTING_ADDR) +#define DISP_POFF_LN_SETTING_ADDR 0x01E + DISP_REG(DISP_POFF_LN_SETTING_ADDR) +/* Power Contol */ +#define DISP_POWER_CTL_1_ADDR 0x100 + DISP_REG(DISP_POWER_CTL_1_ADDR) +#define DISP_POWER_CTL_2_ADDR 0x101 + DISP_REG(DISP_POWER_CTL_2_ADDR) +#define DISP_POWER_CTL_3_ADDR 0x102 + DISP_REG(DISP_POWER_CTL_3_ADDR) +#define DISP_POWER_CTL_4_ADDR 0x103 + DISP_REG(DISP_POWER_CTL_4_ADDR) +#define DISP_POWER_CTL_5_ADDR 0x104 + DISP_REG(DISP_POWER_CTL_5_ADDR) +#define DISP_POWER_CTL_6_ADDR 0x105 + DISP_REG(DISP_POWER_CTL_6_ADDR) +#define DISP_POWER_CTL_7_ADDR 0x106 + DISP_REG(DISP_POWER_CTL_7_ADDR) +/* RAM Access */ +#define DISP_RAM_ADDR_SET_1_ADDR 0x200 + DISP_REG(DISP_RAM_ADDR_SET_1_ADDR) +#define DISP_RAM_ADDR_SET_2_ADDR 0x201 + DISP_REG(DISP_RAM_ADDR_SET_2_ADDR) +#define DISP_CMD_RAMRD DISP_CMD_RAMWR +#define DISP_CMD_RAMWR 0x202 + DISP_REG(DISP_CMD_RAMWR) +#define DISP_RAM_DATA_MASK_1_ADDR 0x203 + DISP_REG(DISP_RAM_DATA_MASK_1_ADDR) +#define DISP_RAM_DATA_MASK_2_ADDR 0x204 + DISP_REG(DISP_RAM_DATA_MASK_2_ADDR) +/* Gamma Control, Contrast, Gray Scale Setting */ +#define DISP_GAMMA_CONTROL_1_ADDR 0x300 + DISP_REG(DISP_GAMMA_CONTROL_1_ADDR) +#define DISP_GAMMA_CONTROL_2_ADDR 0x301 + DISP_REG(DISP_GAMMA_CONTROL_2_ADDR) +#define DISP_GAMMA_CONTROL_3_ADDR 0x302 + DISP_REG(DISP_GAMMA_CONTROL_3_ADDR) +#define DISP_GAMMA_CONTROL_4_ADDR 0x303 + DISP_REG(DISP_GAMMA_CONTROL_4_ADDR) +#define DISP_GAMMA_CONTROL_5_ADDR 0x304 + DISP_REG(DISP_GAMMA_CONTROL_5_ADDR) +/* Coordinate Control */ +#define DISP_VERT_SCROLL_CTL_1_ADDR 0x400 + DISP_REG(DISP_VERT_SCROLL_CTL_1_ADDR) +#define DISP_VERT_SCROLL_CTL_2_ADDR 0x401 + DISP_REG(DISP_VERT_SCROLL_CTL_2_ADDR) +#define DISP_SCREEN_1_DRV_POS_1_ADDR 0x402 + DISP_REG(DISP_SCREEN_1_DRV_POS_1_ADDR) +#define DISP_SCREEN_1_DRV_POS_2_ADDR 0x403 + DISP_REG(DISP_SCREEN_1_DRV_POS_2_ADDR) +#define DISP_SCREEN_2_DRV_POS_1_ADDR 0x404 + DISP_REG(DISP_SCREEN_2_DRV_POS_1_ADDR) +#define DISP_SCREEN_2_DRV_POS_2_ADDR 0x405 + DISP_REG(DISP_SCREEN_2_DRV_POS_2_ADDR) +#define DISP_HORZ_RAM_ADDR_POS_1_ADDR 0x406 + DISP_REG(DISP_HORZ_RAM_ADDR_POS_1_ADDR) +#define DISP_HORZ_RAM_ADDR_POS_2_ADDR 0x407 + DISP_REG(DISP_HORZ_RAM_ADDR_POS_2_ADDR) +#define DISP_VERT_RAM_ADDR_POS_1_ADDR 0x408 + DISP_REG(DISP_VERT_RAM_ADDR_POS_1_ADDR) +#define DISP_VERT_RAM_ADDR_POS_2_ADDR 0x409 + DISP_REG(DISP_VERT_RAM_ADDR_POS_2_ADDR) +#define DISP_TMD_700_ADDR 0x700 /* 0x700 */ + DISP_REG(DISP_TMD_700_ADDR) +#define DISP_TMD_015_ADDR 0x015 /* 0x700 */ + DISP_REG(DISP_TMD_015_ADDR) +#define DISP_TMD_305_ADDR 0x305 /* 0x700 */ + DISP_REG(DISP_TMD_305_ADDR) + +/* + * TMD QVGA Bit Definations + */ + +#define DISP_BIT_IB15 0x8000 +#define DISP_BIT_IB14 0x4000 +#define DISP_BIT_IB13 0x2000 +#define DISP_BIT_IB12 0x1000 +#define DISP_BIT_IB11 0x0800 +#define DISP_BIT_IB10 0x0400 +#define DISP_BIT_IB09 0x0200 +#define DISP_BIT_IB08 0x0100 +#define DISP_BIT_IB07 0x0080 +#define DISP_BIT_IB06 0x0040 +#define DISP_BIT_IB05 0x0020 +#define DISP_BIT_IB04 0x0010 +#define DISP_BIT_IB03 0x0008 +#define DISP_BIT_IB02 0x0004 +#define DISP_BIT_IB01 0x0002 +#define DISP_BIT_IB00 0x0001 +/* + * Display Control + * DISP_START_OSCILLATION_ADDR Start Oscillation + * DISP_DRIVER_OUTPUT_CTL_ADDR Driver Output Control + */ +#define DISP_BITMASK_SS DISP_BIT_IB08 +#define DISP_BITMASK_NL5 DISP_BIT_IB05 +#define DISP_BITMASK_NL4 DISP_BIT_IB04 +#define DISP_BITMASK_NL3 DISP_BIT_IB03 +#define DISP_BITMASK_NL2 DISP_BIT_IB02 +#define DISP_BITMASK_NL1 DISP_BIT_IB01 +#define DISP_BITMASK_NL0 DISP_BIT_IB00 +/* DISP_LCD_DRIVING_SIG_ADDR LCD Driving Signal Setting */ +#define DISP_BITMASK_BC DISP_BIT_IB09 +/* DISP_ENTRY_MODE_ADDR Entry Mode */ +#define DISP_BITMASK_TRI DISP_BIT_IB15 +#define DISP_BITMASK_DFM1 DISP_BIT_IB14 +#define DISP_BITMASK_DFM0 DISP_BIT_IB13 +#define DISP_BITMASK_BGR DISP_BIT_IB12 +#define DISP_BITMASK_HWM0 DISP_BIT_IB08 +#define DISP_BITMASK_ID1 DISP_BIT_IB05 +#define DISP_BITMASK_ID0 DISP_BIT_IB04 +#define DISP_BITMASK_AM DISP_BIT_IB03 +/* DISP_DISPLAY_CTL_1_ADDR Display Control (1) */ +#define DISP_BITMASK_COL1 DISP_BIT_IB15 +#define DISP_BITMASK_COL0 DISP_BIT_IB14 +#define DISP_BITMASK_VLE2 DISP_BIT_IB10 +#define DISP_BITMASK_VLE1 DISP_BIT_IB09 +#define DISP_BITMASK_SPT DISP_BIT_IB08 +#define DISP_BITMASK_PT1 DISP_BIT_IB07 +#define DISP_BITMASK_PT0 DISP_BIT_IB06 +#define DISP_BITMASK_REV DISP_BIT_IB02 +/* DISP_DISPLAY_CTL_2_ADDR Display Control (2) */ +#define DISP_BITMASK_FP3 DISP_BIT_IB11 +#define DISP_BITMASK_FP2 DISP_BIT_IB10 +#define DISP_BITMASK_FP1 DISP_BIT_IB09 +#define DISP_BITMASK_FP0 DISP_BIT_IB08 +#define DISP_BITMASK_BP3 DISP_BIT_IB03 +#define DISP_BITMASK_BP2 DISP_BIT_IB02 +#define DISP_BITMASK_BP1 DISP_BIT_IB01 +#define DISP_BITMASK_BP0 DISP_BIT_IB00 +/* DISP_POWER_SUPPLY_INTF_ADDR Power Supply IC Interface Control */ +#define DISP_BITMASK_CSE DISP_BIT_IB12 +#define DISP_BITMASK_TE DISP_BIT_IB08 +#define DISP_BITMASK_IX3 DISP_BIT_IB03 +#define DISP_BITMASK_IX2 DISP_BIT_IB02 +#define DISP_BITMASK_IX1 DISP_BIT_IB01 +#define DISP_BITMASK_IX0 DISP_BIT_IB00 +/* DISP_EXT_DISPLAY_CTL_1_ADDR External Display Interface Control (1) */ +#define DISP_BITMASK_RM DISP_BIT_IB08 +#define DISP_BITMASK_DM1 DISP_BIT_IB05 +#define DISP_BITMASK_DM0 DISP_BIT_IB04 +#define DISP_BITMASK_RIM1 DISP_BIT_IB01 +#define DISP_BITMASK_RIM0 DISP_BIT_IB00 +/* DISP_FRAME_CYCLE_CTL_ADDR Frame Frequency Adjustment Control */ +#define DISP_BITMASK_DIVI1 DISP_BIT_IB09 +#define DISP_BITMASK_DIVI0 DISP_BIT_IB08 +#define DISP_BITMASK_RTNI4 DISP_BIT_IB04 +#define DISP_BITMASK_RTNI3 DISP_BIT_IB03 +#define DISP_BITMASK_RTNI2 DISP_BIT_IB02 +#define DISP_BITMASK_RTNI1 DISP_BIT_IB01 +#define DISP_BITMASK_RTNI0 DISP_BIT_IB00 +/* DISP_EXT_DISPLAY_CTL_2_ADDR External Display Interface Control (2) */ +#define DISP_BITMASK_DIVE1 DISP_BIT_IB09 +#define DISP_BITMASK_DIVE0 DISP_BIT_IB08 +#define DISP_BITMASK_RTNE7 DISP_BIT_IB07 +#define DISP_BITMASK_RTNE6 DISP_BIT_IB06 +#define DISP_BITMASK_RTNE5 DISP_BIT_IB05 +#define DISP_BITMASK_RTNE4 DISP_BIT_IB04 +#define DISP_BITMASK_RTNE3 DISP_BIT_IB03 +#define DISP_BITMASK_RTNE2 DISP_BIT_IB02 +#define DISP_BITMASK_RTNE1 DISP_BIT_IB01 +#define DISP_BITMASK_RTNE0 DISP_BIT_IB00 +/* DISP_EXT_DISPLAY_CTL_3_ADDR External Display Interface Control (3) */ +#define DISP_BITMASK_VSPL DISP_BIT_IB04 +#define DISP_BITMASK_HSPL DISP_BIT_IB03 +#define DISP_BITMASK_VPL DISP_BIT_IB02 +#define DISP_BITMASK_EPL DISP_BIT_IB01 +#define DISP_BITMASK_DPL DISP_BIT_IB00 +/* DISP_LTPS_CTL_1_ADDR LTPS Interface Control (1) */ +#define DISP_BITMASK_CLWI3 DISP_BIT_IB11 +#define DISP_BITMASK_CLWI2 DISP_BIT_IB10 +#define DISP_BITMASK_CLWI1 DISP_BIT_IB09 +#define DISP_BITMASK_CLWI0 DISP_BIT_IB08 +#define DISP_BITMASK_CLTI1 DISP_BIT_IB01 +#define DISP_BITMASK_CLTI0 DISP_BIT_IB00 +/* DISP_LTPS_CTL_2_ADDR LTPS Interface Control (2) */ +#define DISP_BITMASK_OEVBI1 DISP_BIT_IB09 +#define DISP_BITMASK_OEVBI0 DISP_BIT_IB08 +#define DISP_BITMASK_OEVFI1 DISP_BIT_IB01 +#define DISP_BITMASK_OEVFI0 DISP_BIT_IB00 +/* DISP_LTPS_CTL_3_ADDR LTPS Interface Control (3) */ +#define DISP_BITMASK_SHI1 DISP_BIT_IB01 +#define DISP_BITMASK_SHI0 DISP_BIT_IB00 +/* DISP_LTPS_CTL_4_ADDR LTPS Interface Control (4) */ +#define DISP_BITMASK_CLWE5 DISP_BIT_IB13 +#define DISP_BITMASK_CLWE4 DISP_BIT_IB12 +#define DISP_BITMASK_CLWE3 DISP_BIT_IB11 +#define DISP_BITMASK_CLWE2 DISP_BIT_IB10 +#define DISP_BITMASK_CLWE1 DISP_BIT_IB09 +#define DISP_BITMASK_CLWE0 DISP_BIT_IB08 +#define DISP_BITMASK_CLTE3 DISP_BIT_IB03 +#define DISP_BITMASK_CLTE2 DISP_BIT_IB02 +#define DISP_BITMASK_CLTE1 DISP_BIT_IB01 +#define DISP_BITMASK_CLTE0 DISP_BIT_IB00 +/* DISP_LTPS_CTL_5_ADDR LTPS Interface Control (5) */ +#define DISP_BITMASK_OEVBE3 DISP_BIT_IB11 +#define DISP_BITMASK_OEVBE2 DISP_BIT_IB10 +#define DISP_BITMASK_OEVBE1 DISP_BIT_IB09 +#define DISP_BITMASK_OEVBE0 DISP_BIT_IB08 +#define DISP_BITMASK_OEVFE3 DISP_BIT_IB03 +#define DISP_BITMASK_OEVFE2 DISP_BIT_IB02 +#define DISP_BITMASK_OEVFE1 DISP_BIT_IB01 +#define DISP_BITMASK_OEVFE0 DISP_BIT_IB00 +/* DISP_LTPS_CTL_6_ADDR LTPS Interface Control (6) */ +#define DISP_BITMASK_SHE3 DISP_BIT_IB03 +#define DISP_BITMASK_SHE2 DISP_BIT_IB02 +#define DISP_BITMASK_SHE1 DISP_BIT_IB01 +#define DISP_BITMASK_SHE0 DISP_BIT_IB00 +/* DISP_AMP_SETTING_ADDR Amplify Setting */ +#define DISP_BITMASK_ABSW1 DISP_BIT_IB01 +#define DISP_BITMASK_ABSW0 DISP_BIT_IB00 +/* DISP_MODE_SETTING_ADDR Mode Setting */ +#define DISP_BITMASK_DSTB DISP_BIT_IB02 +#define DISP_BITMASK_STB DISP_BIT_IB00 +/* DISP_POFF_LN_SETTING_ADDR Power Off Line Setting */ +#define DISP_BITMASK_POFH3 DISP_BIT_IB03 +#define DISP_BITMASK_POFH2 DISP_BIT_IB02 +#define DISP_BITMASK_POFH1 DISP_BIT_IB01 +#define DISP_BITMASK_POFH0 DISP_BIT_IB00 + +/* Power Contol */ +/* DISP_POWER_CTL_1_ADDR Power Control (1) */ +#define DISP_BITMASK_PO DISP_BIT_IB11 +#define DISP_BITMASK_VCD DISP_BIT_IB09 +#define DISP_BITMASK_VSC DISP_BIT_IB08 +#define DISP_BITMASK_CON DISP_BIT_IB07 +#define DISP_BITMASK_ASW1 DISP_BIT_IB06 +#define DISP_BITMASK_ASW0 DISP_BIT_IB05 +#define DISP_BITMASK_OEV DISP_BIT_IB04 +#define DISP_BITMASK_OEVE DISP_BIT_IB03 +#define DISP_BITMASK_FR DISP_BIT_IB02 +#define DISP_BITMASK_D1 DISP_BIT_IB01 +#define DISP_BITMASK_D0 DISP_BIT_IB00 +/* DISP_POWER_CTL_2_ADDR Power Control (2) */ +#define DISP_BITMASK_DC4 DISP_BIT_IB15 +#define DISP_BITMASK_DC3 DISP_BIT_IB14 +#define DISP_BITMASK_SAP2 DISP_BIT_IB13 +#define DISP_BITMASK_SAP1 DISP_BIT_IB12 +#define DISP_BITMASK_SAP0 DISP_BIT_IB11 +#define DISP_BITMASK_BT2 DISP_BIT_IB10 +#define DISP_BITMASK_BT1 DISP_BIT_IB09 +#define DISP_BITMASK_BT0 DISP_BIT_IB08 +#define DISP_BITMASK_DC2 DISP_BIT_IB07 +#define DISP_BITMASK_DC1 DISP_BIT_IB06 +#define DISP_BITMASK_DC0 DISP_BIT_IB05 +#define DISP_BITMASK_AP2 DISP_BIT_IB04 +#define DISP_BITMASK_AP1 DISP_BIT_IB03 +#define DISP_BITMASK_AP0 DISP_BIT_IB02 +/* DISP_POWER_CTL_3_ADDR Power Control (3) */ +#define DISP_BITMASK_VGL4 DISP_BIT_IB10 +#define DISP_BITMASK_VGL3 DISP_BIT_IB09 +#define DISP_BITMASK_VGL2 DISP_BIT_IB08 +#define DISP_BITMASK_VGL1 DISP_BIT_IB07 +#define DISP_BITMASK_VGL0 DISP_BIT_IB06 +#define DISP_BITMASK_VGH4 DISP_BIT_IB04 +#define DISP_BITMASK_VGH3 DISP_BIT_IB03 +#define DISP_BITMASK_VGH2 DISP_BIT_IB02 +#define DISP_BITMASK_VGH1 DISP_BIT_IB01 +#define DISP_BITMASK_VGH0 DISP_BIT_IB00 +/* DISP_POWER_CTL_4_ADDR Power Control (4) */ +#define DISP_BITMASK_VC2 DISP_BIT_IB02 +#define DISP_BITMASK_VC1 DISP_BIT_IB01 +#define DISP_BITMASK_VC0 DISP_BIT_IB00 +/* DISP_POWER_CTL_5_ADDR Power Control (5) */ +#define DISP_BITMASK_VRL3 DISP_BIT_IB11 +#define DISP_BITMASK_VRL2 DISP_BIT_IB10 +#define DISP_BITMASK_VRL1 DISP_BIT_IB09 +#define DISP_BITMASK_VRL0 DISP_BIT_IB08 +#define DISP_BITMASK_PON DISP_BIT_IB04 +#define DISP_BITMASK_VRH3 DISP_BIT_IB03 +#define DISP_BITMASK_VRH2 DISP_BIT_IB02 +#define DISP_BITMASK_VRH1 DISP_BIT_IB01 +#define DISP_BITMASK_VRH0 DISP_BIT_IB00 +/* DISP_POWER_CTL_6_ADDR Power Control (6) */ +#define DISP_BITMASK_VCOMG DISP_BIT_IB13 +#define DISP_BITMASK_VDV4 DISP_BIT_IB12 +#define DISP_BITMASK_VDV3 DISP_BIT_IB11 +#define DISP_BITMASK_VDV2 DISP_BIT_IB10 +#define DISP_BITMASK_VDV1 DISP_BIT_IB09 +#define DISP_BITMASK_VDV0 DISP_BIT_IB08 +#define DISP_BITMASK_VCM4 DISP_BIT_IB04 +#define DISP_BITMASK_VCM3 DISP_BIT_IB03 +#define DISP_BITMASK_VCM2 DISP_BIT_IB02 +#define DISP_BITMASK_VCM1 DISP_BIT_IB01 +#define DISP_BITMASK_VCM0 DISP_BIT_IB00 +/* RAM Access */ +/* DISP_RAM_ADDR_SET_1_ADDR RAM Address Set (1) */ +#define DISP_BITMASK_AD7 DISP_BIT_IB07 +#define DISP_BITMASK_AD6 DISP_BIT_IB06 +#define DISP_BITMASK_AD5 DISP_BIT_IB05 +#define DISP_BITMASK_AD4 DISP_BIT_IB04 +#define DISP_BITMASK_AD3 DISP_BIT_IB03 +#define DISP_BITMASK_AD2 DISP_BIT_IB02 +#define DISP_BITMASK_AD1 DISP_BIT_IB01 +#define DISP_BITMASK_AD0 DISP_BIT_IB00 +/* DISP_RAM_ADDR_SET_2_ADDR RAM Address Set (2) */ +#define DISP_BITMASK_AD16 DISP_BIT_IB08 +#define DISP_BITMASK_AD15 DISP_BIT_IB07 +#define DISP_BITMASK_AD14 DISP_BIT_IB06 +#define DISP_BITMASK_AD13 DISP_BIT_IB05 +#define DISP_BITMASK_AD12 DISP_BIT_IB04 +#define DISP_BITMASK_AD11 DISP_BIT_IB03 +#define DISP_BITMASK_AD10 DISP_BIT_IB02 +#define DISP_BITMASK_AD9 DISP_BIT_IB01 +#define DISP_BITMASK_AD8 DISP_BIT_IB00 +/* + * DISP_CMD_RAMWR RAM Data Read/Write + * Use Data Bit Configuration + */ +/* DISP_RAM_DATA_MASK_1_ADDR RAM Write Data Mask (1) */ +#define DISP_BITMASK_WM11 DISP_BIT_IB13 +#define DISP_BITMASK_WM10 DISP_BIT_IB12 +#define DISP_BITMASK_WM9 DISP_BIT_IB11 +#define DISP_BITMASK_WM8 DISP_BIT_IB10 +#define DISP_BITMASK_WM7 DISP_BIT_IB09 +#define DISP_BITMASK_WM6 DISP_BIT_IB08 +#define DISP_BITMASK_WM5 DISP_BIT_IB05 +#define DISP_BITMASK_WM4 DISP_BIT_IB04 +#define DISP_BITMASK_WM3 DISP_BIT_IB03 +#define DISP_BITMASK_WM2 DISP_BIT_IB02 +#define DISP_BITMASK_WM1 DISP_BIT_IB01 +#define DISP_BITMASK_WM0 DISP_BIT_IB00 +/* DISP_RAM_DATA_MASK_2_ADDR RAM Write Data Mask (2) */ +#define DISP_BITMASK_WM17 DISP_BIT_IB05 +#define DISP_BITMASK_WM16 DISP_BIT_IB04 +#define DISP_BITMASK_WM15 DISP_BIT_IB03 +#define DISP_BITMASK_WM14 DISP_BIT_IB02 +#define DISP_BITMASK_WM13 DISP_BIT_IB01 +#define DISP_BITMASK_WM12 DISP_BIT_IB00 +/*Gamma Control */ +/* DISP_GAMMA_CONTROL_1_ADDR Gamma Control (1) */ +#define DISP_BITMASK_PKP12 DISP_BIT_IB10 +#define DISP_BITMASK_PKP11 DISP_BIT_IB08 +#define DISP_BITMASK_PKP10 DISP_BIT_IB09 +#define DISP_BITMASK_PKP02 DISP_BIT_IB02 +#define DISP_BITMASK_PKP01 DISP_BIT_IB01 +#define DISP_BITMASK_PKP00 DISP_BIT_IB00 +/* DISP_GAMMA_CONTROL_2_ADDR Gamma Control (2) */ +#define DISP_BITMASK_PKP32 DISP_BIT_IB10 +#define DISP_BITMASK_PKP31 DISP_BIT_IB09 +#define DISP_BITMASK_PKP30 DISP_BIT_IB08 +#define DISP_BITMASK_PKP22 DISP_BIT_IB02 +#define DISP_BITMASK_PKP21 DISP_BIT_IB01 +#define DISP_BITMASK_PKP20 DISP_BIT_IB00 +/* DISP_GAMMA_CONTROL_3_ADDR Gamma Control (3) */ +#define DISP_BITMASK_PKP52 DISP_BIT_IB10 +#define DISP_BITMASK_PKP51 DISP_BIT_IB09 +#define DISP_BITMASK_PKP50 DISP_BIT_IB08 +#define DISP_BITMASK_PKP42 DISP_BIT_IB02 +#define DISP_BITMASK_PKP41 DISP_BIT_IB01 +#define DISP_BITMASK_PKP40 DISP_BIT_IB00 +/* DISP_GAMMA_CONTROL_4_ADDR Gamma Control (4) */ +#define DISP_BITMASK_PRP12 DISP_BIT_IB10 +#define DISP_BITMASK_PRP11 DISP_BIT_IB08 +#define DISP_BITMASK_PRP10 DISP_BIT_IB09 +#define DISP_BITMASK_PRP02 DISP_BIT_IB02 +#define DISP_BITMASK_PRP01 DISP_BIT_IB01 +#define DISP_BITMASK_PRP00 DISP_BIT_IB00 +/* DISP_GAMMA_CONTROL_5_ADDR Gamma Control (5) */ +#define DISP_BITMASK_VRP14 DISP_BIT_IB12 +#define DISP_BITMASK_VRP13 DISP_BIT_IB11 +#define DISP_BITMASK_VRP12 DISP_BIT_IB10 +#define DISP_BITMASK_VRP11 DISP_BIT_IB08 +#define DISP_BITMASK_VRP10 DISP_BIT_IB09 +#define DISP_BITMASK_VRP03 DISP_BIT_IB03 +#define DISP_BITMASK_VRP02 DISP_BIT_IB02 +#define DISP_BITMASK_VRP01 DISP_BIT_IB01 +#define DISP_BITMASK_VRP00 DISP_BIT_IB00 +/* DISP_GAMMA_CONTROL_6_ADDR Gamma Control (6) */ +#define DISP_BITMASK_PKN12 DISP_BIT_IB10 +#define DISP_BITMASK_PKN11 DISP_BIT_IB08 +#define DISP_BITMASK_PKN10 DISP_BIT_IB09 +#define DISP_BITMASK_PKN02 DISP_BIT_IB02 +#define DISP_BITMASK_PKN01 DISP_BIT_IB01 +#define DISP_BITMASK_PKN00 DISP_BIT_IB00 +/* DISP_GAMMA_CONTROL_7_ADDR Gamma Control (7) */ +#define DISP_BITMASK_PKN32 DISP_BIT_IB10 +#define DISP_BITMASK_PKN31 DISP_BIT_IB08 +#define DISP_BITMASK_PKN30 DISP_BIT_IB09 +#define DISP_BITMASK_PKN22 DISP_BIT_IB02 +#define DISP_BITMASK_PKN21 DISP_BIT_IB01 +#define DISP_BITMASK_PKN20 DISP_BIT_IB00 +/* DISP_GAMMA_CONTROL_8_ADDR Gamma Control (8) */ +#define DISP_BITMASK_PKN52 DISP_BIT_IB10 +#define DISP_BITMASK_PKN51 DISP_BIT_IB08 +#define DISP_BITMASK_PKN50 DISP_BIT_IB09 +#define DISP_BITMASK_PKN42 DISP_BIT_IB02 +#define DISP_BITMASK_PKN41 DISP_BIT_IB01 +#define DISP_BITMASK_PKN40 DISP_BIT_IB00 +/* DISP_GAMMA_CONTROL_9_ADDR Gamma Control (9) */ +#define DISP_BITMASK_PRN12 DISP_BIT_IB10 +#define DISP_BITMASK_PRN11 DISP_BIT_IB08 +#define DISP_BITMASK_PRN10 DISP_BIT_IB09 +#define DISP_BITMASK_PRN02 DISP_BIT_IB02 +#define DISP_BITMASK_PRN01 DISP_BIT_IB01 +#define DISP_BITMASK_PRN00 DISP_BIT_IB00 +/* DISP_GAMMA_CONTROL_10_ADDR Gamma Control (10) */ +#define DISP_BITMASK_VRN14 DISP_BIT_IB12 +#define DISP_BITMASK_VRN13 DISP_BIT_IB11 +#define DISP_BITMASK_VRN12 DISP_BIT_IB10 +#define DISP_BITMASK_VRN11 DISP_BIT_IB08 +#define DISP_BITMASK_VRN10 DISP_BIT_IB09 +#define DISP_BITMASK_VRN03 DISP_BIT_IB03 +#define DISP_BITMASK_VRN02 DISP_BIT_IB02 +#define DISP_BITMASK_VRN01 DISP_BIT_IB01 +#define DISP_BITMASK_VRN00 DISP_BIT_IB00 +/* Coordinate Control */ +/* DISP_VERT_SCROLL_CTL_1_ADDR Vertical Scroll Control (1) */ +#define DISP_BITMASK_VL18 DISP_BIT_IB08 +#define DISP_BITMASK_VL17 DISP_BIT_IB07 +#define DISP_BITMASK_VL16 DISP_BIT_IB06 +#define DISP_BITMASK_VL15 DISP_BIT_IB05 +#define DISP_BITMASK_VL14 DISP_BIT_IB04 +#define DISP_BITMASK_VL13 DISP_BIT_IB03 +#define DISP_BITMASK_VL12 DISP_BIT_IB02 +#define DISP_BITMASK_VL11 DISP_BIT_IB01 +#define DISP_BITMASK_VL10 DISP_BIT_IB00 +/* DISP_VERT_SCROLL_CTL_2_ADDR Vertical Scroll Control (2) */ +#define DISP_BITMASK_VL28 DISP_BIT_IB08 +#define DISP_BITMASK_VL27 DISP_BIT_IB07 +#define DISP_BITMASK_VL26 DISP_BIT_IB06 +#define DISP_BITMASK_VL25 DISP_BIT_IB05 +#define DISP_BITMASK_VL24 DISP_BIT_IB04 +#define DISP_BITMASK_VL23 DISP_BIT_IB03 +#define DISP_BITMASK_VL22 DISP_BIT_IB02 +#define DISP_BITMASK_VL21 DISP_BIT_IB01 +#define DISP_BITMASK_VL20 DISP_BIT_IB00 +/* DISP_SCREEN_1_DRV_POS_1_ADDR First Screen Driving Position (1) */ +#define DISP_BITMASK_SS18 DISP_BIT_IB08 +#define DISP_BITMASK_SS17 DISP_BIT_IB07 +#define DISP_BITMASK_SS16 DISP_BIT_IB06 +#define DISP_BITMASK_SS15 DISP_BIT_IB05 +#define DISP_BITMASK_SS14 DISP_BIT_IB04 +#define DISP_BITMASK_SS13 DISP_BIT_IB03 +#define DISP_BITMASK_SS12 DISP_BIT_IB02 +#define DISP_BITMASK_SS11 DISP_BIT_IB01 +#define DISP_BITMASK_SS10 DISP_BIT_IB00 +/* DISP_SCREEN_1_DRV_POS_2_ADDR First Screen Driving Position (2) */ +#define DISP_BITMASK_SE18 DISP_BIT_IB08 +#define DISP_BITMASK_SE17 DISP_BIT_IB07 +#define DISP_BITMASK_SE16 DISP_BIT_IB06 +#define DISP_BITMASK_SE15 DISP_BIT_IB05 +#define DISP_BITMASK_SE14 DISP_BIT_IB04 +#define DISP_BITMASK_SE13 DISP_BIT_IB03 +#define DISP_BITMASK_SE12 DISP_BIT_IB02 +#define DISP_BITMASK_SE11 DISP_BIT_IB01 +#define DISP_BITMASK_SE10 DISP_BIT_IB00 +/* DISP_SCREEN_2_DRV_POS_1_ADDR Second Screen Driving Position (1) */ +#define DISP_BITMASK_SS28 DISP_BIT_IB08 +#define DISP_BITMASK_SS27 DISP_BIT_IB07 +#define DISP_BITMASK_SS26 DISP_BIT_IB06 +#define DISP_BITMASK_SS25 DISP_BIT_IB05 +#define DISP_BITMASK_SS24 DISP_BIT_IB04 +#define DISP_BITMASK_SS23 DISP_BIT_IB03 +#define DISP_BITMASK_SS22 DISP_BIT_IB02 +#define DISP_BITMASK_SS21 DISP_BIT_IB01 +#define DISP_BITMASK_SS20 DISP_BIT_IB00 +/* DISP_SCREEN_3_DRV_POS_2_ADDR Second Screen Driving Position (2) */ +#define DISP_BITMASK_SE28 DISP_BIT_IB08 +#define DISP_BITMASK_SE27 DISP_BIT_IB07 +#define DISP_BITMASK_SE26 DISP_BIT_IB06 +#define DISP_BITMASK_SE25 DISP_BIT_IB05 +#define DISP_BITMASK_SE24 DISP_BIT_IB04 +#define DISP_BITMASK_SE23 DISP_BIT_IB03 +#define DISP_BITMASK_SE22 DISP_BIT_IB02 +#define DISP_BITMASK_SE21 DISP_BIT_IB01 +#define DISP_BITMASK_SE20 DISP_BIT_IB00 +/* DISP_HORZ_RAM_ADDR_POS_1_ADDR Horizontal RAM Address Position (1) */ +#define DISP_BITMASK_HSA7 DISP_BIT_IB07 +#define DISP_BITMASK_HSA6 DISP_BIT_IB06 +#define DISP_BITMASK_HSA5 DISP_BIT_IB05 +#define DISP_BITMASK_HSA4 DISP_BIT_IB04 +#define DISP_BITMASK_HSA3 DISP_BIT_IB03 +#define DISP_BITMASK_HSA2 DISP_BIT_IB02 +#define DISP_BITMASK_HSA1 DISP_BIT_IB01 +#define DISP_BITMASK_HSA0 DISP_BIT_IB00 +/* DISP_HORZ_RAM_ADDR_POS_2_ADDR Horizontal RAM Address Position (2) */ +#define DISP_BITMASK_HEA7 DISP_BIT_IB07 +#define DISP_BITMASK_HEA6 DISP_BIT_IB06 +#define DISP_BITMASK_HEA5 DISP_BIT_IB05 +#define DISP_BITMASK_HEA4 DISP_BIT_IB04 +#define DISP_BITMASK_HEA3 DISP_BIT_IB03 +#define DISP_BITMASK_HEA2 DISP_BIT_IB02 +#define DISP_BITMASK_HEA1 DISP_BIT_IB01 +#define DISP_BITMASK_HEA0 DISP_BIT_IB00 +/* DISP_VERT_RAM_ADDR_POS_1_ADDR Vertical RAM Address Position (1) */ +#define DISP_BITMASK_VSA8 DISP_BIT_IB08 +#define DISP_BITMASK_VSA7 DISP_BIT_IB07 +#define DISP_BITMASK_VSA6 DISP_BIT_IB06 +#define DISP_BITMASK_VSA5 DISP_BIT_IB05 +#define DISP_BITMASK_VSA4 DISP_BIT_IB04 +#define DISP_BITMASK_VSA3 DISP_BIT_IB03 +#define DISP_BITMASK_VSA2 DISP_BIT_IB02 +#define DISP_BITMASK_VSA1 DISP_BIT_IB01 +#define DISP_BITMASK_VSA0 DISP_BIT_IB00 +/* DISP_VERT_RAM_ADDR_POS_2_ADDR Vertical RAM Address Position (2) */ +#define DISP_BITMASK_VEA8 DISP_BIT_IB08 +#define DISP_BITMASK_VEA7 DISP_BIT_IB07 +#define DISP_BITMASK_VEA6 DISP_BIT_IB06 +#define DISP_BITMASK_VEA5 DISP_BIT_IB05 +#define DISP_BITMASK_VEA4 DISP_BIT_IB04 +#define DISP_BITMASK_VEA3 DISP_BIT_IB03 +#define DISP_BITMASK_VEA2 DISP_BIT_IB02 +#define DISP_BITMASK_VEA1 DISP_BIT_IB01 +#define DISP_BITMASK_VEA0 DISP_BIT_IB00 +static word disp_area_start_row; +static word disp_area_end_row; +static boolean disp_initialized = FALSE; +/* For some reason the contrast set at init time is not good. Need to do +* it again +*/ +static boolean display_on = FALSE; + +static uint32 tmd20qvga_lcd_rev; +uint16 tmd20qvga_panel_offset; + +#ifdef DISP_DEVICE_8BPP +static word convert_8_to_16_tbl[256] = { + 0x0000, 0x2000, 0x4000, 0x6000, 0x8000, 0xA000, 0xC000, 0xE000, + 0x0100, 0x2100, 0x4100, 0x6100, 0x8100, 0xA100, 0xC100, 0xE100, + 0x0200, 0x2200, 0x4200, 0x6200, 0x8200, 0xA200, 0xC200, 0xE200, + 0x0300, 0x2300, 0x4300, 0x6300, 0x8300, 0xA300, 0xC300, 0xE300, + 0x0400, 0x2400, 0x4400, 0x6400, 0x8400, 0xA400, 0xC400, 0xE400, + 0x0500, 0x2500, 0x4500, 0x6500, 0x8500, 0xA500, 0xC500, 0xE500, + 0x0600, 0x2600, 0x4600, 0x6600, 0x8600, 0xA600, 0xC600, 0xE600, + 0x0700, 0x2700, 0x4700, 0x6700, 0x8700, 0xA700, 0xC700, 0xE700, + 0x0008, 0x2008, 0x4008, 0x6008, 0x8008, 0xA008, 0xC008, 0xE008, + 0x0108, 0x2108, 0x4108, 0x6108, 0x8108, 0xA108, 0xC108, 0xE108, + 0x0208, 0x2208, 0x4208, 0x6208, 0x8208, 0xA208, 0xC208, 0xE208, + 0x0308, 0x2308, 0x4308, 0x6308, 0x8308, 0xA308, 0xC308, 0xE308, + 0x0408, 0x2408, 0x4408, 0x6408, 0x8408, 0xA408, 0xC408, 0xE408, + 0x0508, 0x2508, 0x4508, 0x6508, 0x8508, 0xA508, 0xC508, 0xE508, + 0x0608, 0x2608, 0x4608, 0x6608, 0x8608, 0xA608, 0xC608, 0xE608, + 0x0708, 0x2708, 0x4708, 0x6708, 0x8708, 0xA708, 0xC708, 0xE708, + 0x0010, 0x2010, 0x4010, 0x6010, 0x8010, 0xA010, 0xC010, 0xE010, + 0x0110, 0x2110, 0x4110, 0x6110, 0x8110, 0xA110, 0xC110, 0xE110, + 0x0210, 0x2210, 0x4210, 0x6210, 0x8210, 0xA210, 0xC210, 0xE210, + 0x0310, 0x2310, 0x4310, 0x6310, 0x8310, 0xA310, 0xC310, 0xE310, + 0x0410, 0x2410, 0x4410, 0x6410, 0x8410, 0xA410, 0xC410, 0xE410, + 0x0510, 0x2510, 0x4510, 0x6510, 0x8510, 0xA510, 0xC510, 0xE510, + 0x0610, 0x2610, 0x4610, 0x6610, 0x8610, 0xA610, 0xC610, 0xE610, + 0x0710, 0x2710, 0x4710, 0x6710, 0x8710, 0xA710, 0xC710, 0xE710, + 0x0018, 0x2018, 0x4018, 0x6018, 0x8018, 0xA018, 0xC018, 0xE018, + 0x0118, 0x2118, 0x4118, 0x6118, 0x8118, 0xA118, 0xC118, 0xE118, + 0x0218, 0x2218, 0x4218, 0x6218, 0x8218, 0xA218, 0xC218, 0xE218, + 0x0318, 0x2318, 0x4318, 0x6318, 0x8318, 0xA318, 0xC318, 0xE318, + 0x0418, 0x2418, 0x4418, 0x6418, 0x8418, 0xA418, 0xC418, 0xE418, + 0x0518, 0x2518, 0x4518, 0x6518, 0x8518, 0xA518, 0xC518, 0xE518, + 0x0618, 0x2618, 0x4618, 0x6618, 0x8618, 0xA618, 0xC618, 0xE618, + 0x0718, 0x2718, 0x4718, 0x6718, 0x8718, 0xA718, 0xC718, 0xE718 +}; +#endif /* DISP_DEVICE_8BPP */ + +static void tmd20qvga_disp_set_rect(int x, int y, int xres, int yres); +static void tmd20qvga_disp_init(struct platform_device *pdev); +static void tmd20qvga_disp_set_contrast(void); +static void tmd20qvga_disp_set_display_area(word start_row, word end_row); +static int tmd20qvga_disp_off(struct platform_device *pdev); +static int tmd20qvga_disp_on(struct platform_device *pdev); +static void tmd20qvga_set_revId(int); + +/* future use */ +void tmd20qvga_disp_clear_screen_area(word start_row, word end_row, + word start_column, word end_column); + +static void tmd20qvga_set_revId(int id) +{ + + tmd20qvga_lcd_rev = id; + + if (tmd20qvga_lcd_rev == 1) + tmd20qvga_panel_offset = 0x10; + else + tmd20qvga_panel_offset = 0; +} + +static void tmd20qvga_disp_init(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + if (disp_initialized) + return; + + mfd = platform_get_drvdata(pdev); + + DISP_CMD_PORT = mfd->cmd_port; + DISP_DATA_PORT = mfd->data_port; + +#ifdef TMD20QVGA_LCD_18BPP + tmd20qvga_set_revId(2); +#else + tmd20qvga_set_revId(1); +#endif + + disp_initialized = TRUE; + tmd20qvga_disp_set_contrast(); + tmd20qvga_disp_set_display_area(0, QVGA_HEIGHT - 1); +} + +static void tmd20qvga_disp_set_rect(int x, int y, int xres, int yres) +{ + if (!disp_initialized) + return; + + DISP_SET_RECT(y, y + yres - 1, x, x + xres - 1); + + DISP_CMD_OUT(DISP_CMD_RAMWR); +} + +static void tmd20qvga_disp_set_display_area(word start_row, word end_row) +{ + word start_driving = start_row; + word end_driving = end_row; + + if (!disp_initialized) + return; + + /* Range checking + */ + if (end_driving >= QVGA_HEIGHT) + end_driving = QVGA_HEIGHT - 1; + if (start_driving > end_driving) { + /* Probably Backwards Switch */ + start_driving = end_driving; + end_driving = start_row; /* Has not changed */ + if (end_driving >= QVGA_HEIGHT) + end_driving = QVGA_HEIGHT - 1; + } + + if ((start_driving == disp_area_start_row) + && (end_driving == disp_area_end_row)) + return; + + disp_area_start_row = start_driving; + disp_area_end_row = end_driving; + + DISP_WRITE_OUT(DISP_SCREEN_1_DRV_POS_1_ADDR, + DISP_VAL_IF(start_driving & 0x100, + DISP_BITMASK_SS18) | + DISP_VAL_IF(start_driving & 0x080, + DISP_BITMASK_SS17) | + DISP_VAL_IF(start_driving & 0x040, + DISP_BITMASK_SS16) | + DISP_VAL_IF(start_driving & 0x020, + DISP_BITMASK_SS15) | + DISP_VAL_IF(start_driving & 0x010, + DISP_BITMASK_SS14) | + DISP_VAL_IF(start_driving & 0x008, + DISP_BITMASK_SS13) | + DISP_VAL_IF(start_driving & 0x004, + DISP_BITMASK_SS12) | + DISP_VAL_IF(start_driving & 0x002, + DISP_BITMASK_SS11) | + DISP_VAL_IF(start_driving & 0x001, DISP_BITMASK_SS10)); + + DISP_WRITE_OUT(DISP_SCREEN_1_DRV_POS_2_ADDR, + DISP_VAL_IF(end_driving & 0x100, DISP_BITMASK_SE18) | + DISP_VAL_IF(end_driving & 0x080, DISP_BITMASK_SE17) | + DISP_VAL_IF(end_driving & 0x040, DISP_BITMASK_SE16) | + DISP_VAL_IF(end_driving & 0x020, DISP_BITMASK_SE15) | + DISP_VAL_IF(end_driving & 0x010, DISP_BITMASK_SE14) | + DISP_VAL_IF(end_driving & 0x008, DISP_BITMASK_SE13) | + DISP_VAL_IF(end_driving & 0x004, DISP_BITMASK_SE12) | + DISP_VAL_IF(end_driving & 0x002, DISP_BITMASK_SE11) | + DISP_VAL_IF(end_driving & 0x001, DISP_BITMASK_SE10)); +} + +static int tmd20qvga_disp_off(struct platform_device *pdev) +{ + if (!disp_initialized) + tmd20qvga_disp_init(pdev); + + if (display_on) { + if (tmd20qvga_lcd_rev == 2) { + DISP_WRITE_OUT(DISP_POFF_LN_SETTING_ADDR, 0x000A); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0xFFEE); + WAIT_MSEC(40); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0xF812); + WAIT_MSEC(40); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0xE811); + WAIT_MSEC(40); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0xC011); + WAIT_MSEC(40); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0x4011); + WAIT_MSEC(20); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0x0010); + + } else { + DISP_WRITE_OUT(DISP_POFF_LN_SETTING_ADDR, 0x000F); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0x0BFE); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0100); + WAIT_MSEC(40); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0x0BED); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0100); + WAIT_MSEC(40); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0x00CD); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0100); + WAIT_MSEC(20); + DISP_WRITE_OUT(DISP_START_OSCILLATION_ADDR, 0x0); + } + + DISP_WRITE_OUT(DISP_MODE_SETTING_ADDR, 0x0004); + DISP_WRITE_OUT(DISP_MODE_SETTING_ADDR, 0x0000); + + display_on = FALSE; + } + + return 0; +} + +static int tmd20qvga_disp_on(struct platform_device *pdev) +{ + if (!disp_initialized) + tmd20qvga_disp_init(pdev); + + if (!display_on) { + /* Deep Stand-by -> Stand-by */ + DISP_CMD_OUT(DISP_START_OSCILLATION_ADDR); + WAIT_MSEC(1); + DISP_CMD_OUT(DISP_START_OSCILLATION_ADDR); + WAIT_MSEC(1); + DISP_CMD_OUT(DISP_START_OSCILLATION_ADDR); + WAIT_MSEC(1); + + /* OFF -> Deep Stan-By -> Stand-by */ + /* let's change the state from "Stand-by" to "Sleep" */ + DISP_WRITE_OUT(DISP_MODE_SETTING_ADDR, 0x0005); + WAIT_MSEC(1); + + /* Sleep -> Displaying */ + DISP_WRITE_OUT(DISP_START_OSCILLATION_ADDR, 0x0001); + DISP_WRITE_OUT(DISP_DRIVER_OUTPUT_CTL_ADDR, 0x0127); + DISP_WRITE_OUT(DISP_LCD_DRIVING_SIG_ADDR, 0x200); + /* fast write mode */ + DISP_WRITE_OUT(DISP_ENTRY_MODE_ADDR, 0x0130); + if (tmd20qvga_lcd_rev == 2) + DISP_WRITE_OUT(DISP_TMD_700_ADDR, 0x0003); + /* back porch = 14 + front porch = 2 --> 16 lines */ + if (tmd20qvga_lcd_rev == 2) { +#ifdef TMD20QVGA_LCD_18BPP + /* 256k color */ + DISP_WRITE_OUT(DISP_DISPLAY_CTL_1_ADDR, 0x0000); +#else + /* 65k color */ + DISP_WRITE_OUT(DISP_DISPLAY_CTL_1_ADDR, 0x4000); +#endif + DISP_WRITE_OUT(DISP_DISPLAY_CTL_2_ADDR, 0x0302); + } else { +#ifdef TMD20QVGA_LCD_18BPP + /* 256k color */ + DISP_WRITE_OUT(DISP_DISPLAY_CTL_1_ADDR, 0x0004); +#else + /* 65k color */ + DISP_WRITE_OUT(DISP_DISPLAY_CTL_1_ADDR, 0x4004); +#endif + DISP_WRITE_OUT(DISP_DISPLAY_CTL_2_ADDR, 0x020E); + } + /* 16 bit one transfer */ + if (tmd20qvga_lcd_rev == 2) { + DISP_WRITE_OUT(DISP_EXT_DISPLAY_CTL_1_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_FRAME_CYCLE_CTL_ADDR, 0x0010); + DISP_WRITE_OUT(DISP_LTPS_CTL_1_ADDR, 0x0302); + DISP_WRITE_OUT(DISP_LTPS_CTL_2_ADDR, 0x0102); + DISP_WRITE_OUT(DISP_LTPS_CTL_3_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_TMD_015_ADDR, 0x2000); + + DISP_WRITE_OUT(DISP_AMP_SETTING_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_1_ADDR, 0x0403); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_2_ADDR, 0x0304); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_3_ADDR, 0x0403); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_4_ADDR, 0x0303); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_5_ADDR, 0x0101); + DISP_WRITE_OUT(DISP_TMD_305_ADDR, 0); + + DISP_WRITE_OUT(DISP_SCREEN_1_DRV_POS_1_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_SCREEN_1_DRV_POS_2_ADDR, 0x013F); + + DISP_WRITE_OUT(DISP_POWER_CTL_3_ADDR, 0x077D); + + DISP_WRITE_OUT(DISP_POWER_CTL_4_ADDR, 0x0005); + DISP_WRITE_OUT(DISP_POWER_CTL_5_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_POWER_CTL_6_ADDR, 0x0015); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0xC010); + WAIT_MSEC(1); + + DISP_WRITE_OUT(DISP_POWER_CTL_2_ADDR, 0x0001); + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0xFFFE); + WAIT_MSEC(60); + } else { + DISP_WRITE_OUT(DISP_EXT_DISPLAY_CTL_1_ADDR, 0x0001); + DISP_WRITE_OUT(DISP_FRAME_CYCLE_CTL_ADDR, 0x0010); + DISP_WRITE_OUT(DISP_LTPS_CTL_1_ADDR, 0x0301); + DISP_WRITE_OUT(DISP_LTPS_CTL_2_ADDR, 0x0001); + DISP_WRITE_OUT(DISP_LTPS_CTL_3_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_AMP_SETTING_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_1_ADDR, 0x0507); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_2_ADDR, 0x0405); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_3_ADDR, 0x0607); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_4_ADDR, 0x0502); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_5_ADDR, 0x0301); + DISP_WRITE_OUT(DISP_SCREEN_1_DRV_POS_1_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_SCREEN_1_DRV_POS_2_ADDR, 0x013F); + DISP_WRITE_OUT(DISP_POWER_CTL_3_ADDR, 0x0795); + + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0102); + WAIT_MSEC(1); + + DISP_WRITE_OUT(DISP_POWER_CTL_4_ADDR, 0x0450); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0103); + WAIT_MSEC(1); + + DISP_WRITE_OUT(DISP_POWER_CTL_5_ADDR, 0x0008); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0104); + WAIT_MSEC(1); + + DISP_WRITE_OUT(DISP_POWER_CTL_6_ADDR, 0x0C00); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0105); + WAIT_MSEC(1); + + DISP_WRITE_OUT(DISP_POWER_CTL_7_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0106); + WAIT_MSEC(1); + + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0x0801); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0100); + WAIT_MSEC(1); + + DISP_WRITE_OUT(DISP_POWER_CTL_2_ADDR, 0x001F); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0101); + WAIT_MSEC(60); + + DISP_WRITE_OUT(DISP_POWER_CTL_2_ADDR, 0x009F); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0101); + WAIT_MSEC(10); + + DISP_WRITE_OUT(DISP_HORZ_RAM_ADDR_POS_1_ADDR, 0x0010); + DISP_WRITE_OUT(DISP_HORZ_RAM_ADDR_POS_2_ADDR, 0x00FF); + DISP_WRITE_OUT(DISP_VERT_RAM_ADDR_POS_1_ADDR, 0x0000); + DISP_WRITE_OUT(DISP_VERT_RAM_ADDR_POS_2_ADDR, 0x013F); + /* RAM starts at address 0x10 */ + DISP_WRITE_OUT(DISP_RAM_ADDR_SET_1_ADDR, 0x0010); + DISP_WRITE_OUT(DISP_RAM_ADDR_SET_2_ADDR, 0x0000); + + /* lcd controller uses internal clock, not ext. vsync */ + DISP_CMD_OUT(DISP_CMD_RAMWR); + + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0x0881); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0100); + WAIT_MSEC(40); + + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0x0BE1); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0100); + WAIT_MSEC(40); + + DISP_WRITE_OUT(DISP_POWER_CTL_1_ADDR, 0x0BFF); + DISP_WRITE_OUT(DISP_POWER_SUPPLY_INTF_ADDR, 0x0100); + } + display_on = TRUE; + } + + return 0; +} + +static void tmd20qvga_disp_set_contrast(void) +{ +#if (defined(TMD20QVGA_LCD_18BPP)) + + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_1_ADDR, 0x0403); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_2_ADDR, 0x0302); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_3_ADDR, 0x0403); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_4_ADDR, 0x0303); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_5_ADDR, 0x0F07); + +#else + int newcontrast = 0x46; + + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_1_ADDR, 0x0403); + + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_2_ADDR, + DISP_VAL_IF(newcontrast & 0x0001, DISP_BITMASK_PKP20) | + DISP_VAL_IF(newcontrast & 0x0002, DISP_BITMASK_PKP21) | + DISP_VAL_IF(newcontrast & 0x0004, DISP_BITMASK_PKP22) | + DISP_VAL_IF(newcontrast & 0x0010, DISP_BITMASK_PKP30) | + DISP_VAL_IF(newcontrast & 0x0020, DISP_BITMASK_PKP31) | + DISP_VAL_IF(newcontrast & 0x0040, DISP_BITMASK_PKP32)); + + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_3_ADDR, + DISP_VAL_IF(newcontrast & 0x0010, DISP_BITMASK_PKP40) | + DISP_VAL_IF(newcontrast & 0x0020, DISP_BITMASK_PKP41) | + DISP_VAL_IF(newcontrast & 0x0040, DISP_BITMASK_PKP42) | + DISP_VAL_IF(newcontrast & 0x0001, DISP_BITMASK_PKP50) | + DISP_VAL_IF(newcontrast & 0x0002, DISP_BITMASK_PKP51) | + DISP_VAL_IF(newcontrast & 0x0004, DISP_BITMASK_PKP52)); + + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_4_ADDR, 0x0303); + DISP_WRITE_OUT(DISP_GAMMA_CONTROL_5_ADDR, 0x0F07); + +#endif /* defined(TMD20QVGA_LCD_18BPP) */ + +} /* End disp_set_contrast */ + +void tmd20qvga_disp_clear_screen_area + (word start_row, word end_row, word start_column, word end_column) { + int32 i; + + /* Clear the display screen */ + DISP_SET_RECT(start_row, end_row, start_column, end_column); + DISP_CMD_OUT(DISP_CMD_RAMWR); + i = (end_row - start_row + 1) * (end_column - start_column + 1); + for (; i > 0; i--) + DISP_DATA_OUT_16TO18BPP(0x0); +} + +static int __init tmd20qvga_probe(struct platform_device *pdev) +{ + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = tmd20qvga_probe, + .driver = { + .name = "ebi2_tmd_qvga", + }, +}; + +static struct msm_fb_panel_data tmd20qvga_panel_data = { + .on = tmd20qvga_disp_on, + .off = tmd20qvga_disp_off, + .set_rect = tmd20qvga_disp_set_rect, +}; + +static struct platform_device this_device = { + .name = "ebi2_tmd_qvga", + .id = 0, + .dev = { + .platform_data = &tmd20qvga_panel_data, + } +}; + +static int __init tmd20qvga_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + + ret = platform_driver_register(&this_driver); + if (!ret) { + pinfo = &tmd20qvga_panel_data.panel_info; + pinfo->xres = 240; + pinfo->yres = 320; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = EBI2_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0x808000; +#ifdef TMD20QVGA_LCD_18BPP + pinfo->bpp = 18; +#else + pinfo->bpp = 16; +#endif + pinfo->fb_num = 2; + pinfo->lcd.vsync_enable = TRUE; + pinfo->lcd.refx100 = 6000; + pinfo->lcd.v_back_porch = 16; + pinfo->lcd.v_front_porch = 4; + pinfo->lcd.v_pulse_width = 0; + pinfo->lcd.hw_vsync_mode = FALSE; + pinfo->lcd.vsync_notifier_period = 0; + + ret = platform_device_register(&this_device); + if (ret) + platform_driver_unregister(&this_driver); + } + + return ret; +} + +module_init(tmd20qvga_init); + diff --git a/drivers/video/msm/external_common.c b/drivers/video/msm/external_common.c new file mode 100644 index 00000000000..694450ac4c9 --- /dev/null +++ b/drivers/video/msm/external_common.c @@ -0,0 +1,1229 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#define DEBUG +#define DEV_DBG_PREFIX "EXT_COMMON: " + +#include "msm_fb.h" +#include "external_common.h" + +struct external_common_state_type *external_common_state; +EXPORT_SYMBOL(external_common_state); +DEFINE_MUTEX(external_common_state_hpd_mutex); +EXPORT_SYMBOL(external_common_state_hpd_mutex); + +static int atoi(const char *name) +{ + int val = 0; + + for (;; name++) { + switch (*name) { + case '0' ... '9': + val = 10*val+(*name-'0'); + break; + default: + return val; + } + } +} + +const char *video_format_2string(uint32 format) +{ + switch (format) { + default: +#ifdef CONFIG_FB_MSM_HDMI_COMMON + case HDMI_VFRMT_640x480p60_4_3: return " 640x 480 p60 4/3"; + case HDMI_VFRMT_720x480p60_4_3: return " 720x 480 p60 4/3"; + case HDMI_VFRMT_720x480p60_16_9: return " 720x 480 p60 16/9"; + case HDMI_VFRMT_1280x720p60_16_9: return "1280x 720 p60 16/9"; + case HDMI_VFRMT_1920x1080i60_16_9: return "1920x1080 i60 16/9"; + case HDMI_VFRMT_1440x480i60_4_3: return "1440x 480 i60 4/3"; + case HDMI_VFRMT_1440x480i60_16_9: return "1440x 480 i60 16/9"; + case HDMI_VFRMT_1440x240p60_4_3: return "1440x 240 p60 4/3"; + case HDMI_VFRMT_1440x240p60_16_9: return "1440x 240 p60 16/9"; + case HDMI_VFRMT_2880x480i60_4_3: return "2880x 480 i60 4/3"; + case HDMI_VFRMT_2880x480i60_16_9: return "2880x 480 i60 16/9"; + case HDMI_VFRMT_2880x240p60_4_3: return "2880x 240 p60 4/3"; + case HDMI_VFRMT_2880x240p60_16_9: return "2880x 240 p60 16/9"; + case HDMI_VFRMT_1440x480p60_4_3: return "1440x 480 p60 4/3"; + case HDMI_VFRMT_1440x480p60_16_9: return "1440x 480 p60 16/9"; + case HDMI_VFRMT_1920x1080p60_16_9: return "1920x1080 p60 16/9"; + case HDMI_VFRMT_720x576p50_4_3: return " 720x 576 p50 4/3"; + case HDMI_VFRMT_720x576p50_16_9: return " 720x 576 p50 16/9"; + case HDMI_VFRMT_1280x720p50_16_9: return "1280x 720 p50 16/9"; + case HDMI_VFRMT_1920x1080i50_16_9: return "1920x1080 i50 16/9"; + case HDMI_VFRMT_1440x576i50_4_3: return "1440x 576 i50 4/3"; + case HDMI_VFRMT_1440x576i50_16_9: return "1440x 576 i50 16/9"; + case HDMI_VFRMT_1440x288p50_4_3: return "1440x 288 p50 4/3"; + case HDMI_VFRMT_1440x288p50_16_9: return "1440x 288 p50 16/9"; + case HDMI_VFRMT_2880x576i50_4_3: return "2880x 576 i50 4/3"; + case HDMI_VFRMT_2880x576i50_16_9: return "2880x 576 i50 16/9"; + case HDMI_VFRMT_2880x288p50_4_3: return "2880x 288 p50 4/3"; + case HDMI_VFRMT_2880x288p50_16_9: return "2880x 288 p50 16/9"; + case HDMI_VFRMT_1440x576p50_4_3: return "1440x 576 p50 4/3"; + case HDMI_VFRMT_1440x576p50_16_9: return "1440x 576 p50 16/9"; + case HDMI_VFRMT_1920x1080p50_16_9: return "1920x1080 p50 16/9"; + case HDMI_VFRMT_1920x1080p24_16_9: return "1920x1080 p24 16/9"; + case HDMI_VFRMT_1920x1080p25_16_9: return "1920x1080 p25 16/9"; + case HDMI_VFRMT_1920x1080p30_16_9: return "1920x1080 p30 16/9"; + case HDMI_VFRMT_2880x480p60_4_3: return "2880x 480 p60 4/3"; + case HDMI_VFRMT_2880x480p60_16_9: return "2880x 480 p60 16/9"; + case HDMI_VFRMT_2880x576p50_4_3: return "2880x 576 p50 4/3"; + case HDMI_VFRMT_2880x576p50_16_9: return "2880x 576 p50 16/9"; + case HDMI_VFRMT_1920x1250i50_16_9: return "1920x1250 i50 16/9"; + case HDMI_VFRMT_1920x1080i100_16_9:return "1920x1080 i100 16/9"; + case HDMI_VFRMT_1280x720p100_16_9: return "1280x 720 p100 16/9"; + case HDMI_VFRMT_720x576p100_4_3: return " 720x 576 p100 4/3"; + case HDMI_VFRMT_720x576p100_16_9: return " 720x 576 p100 16/9"; + case HDMI_VFRMT_1440x576i100_4_3: return "1440x 576 i100 4/3"; + case HDMI_VFRMT_1440x576i100_16_9: return "1440x 576 i100 16/9"; + case HDMI_VFRMT_1920x1080i120_16_9:return "1920x1080 i120 16/9"; + case HDMI_VFRMT_1280x720p120_16_9: return "1280x 720 p120 16/9"; + case HDMI_VFRMT_720x480p120_4_3: return " 720x 480 p120 4/3"; + case HDMI_VFRMT_720x480p120_16_9: return " 720x 480 p120 16/9"; + case HDMI_VFRMT_1440x480i120_4_3: return "1440x 480 i120 4/3"; + case HDMI_VFRMT_1440x480i120_16_9: return "1440x 480 i120 16/9"; + case HDMI_VFRMT_720x576p200_4_3: return " 720x 576 p200 4/3"; + case HDMI_VFRMT_720x576p200_16_9: return " 720x 576 p200 16/9"; + case HDMI_VFRMT_1440x576i200_4_3: return "1440x 576 i200 4/3"; + case HDMI_VFRMT_1440x576i200_16_9: return "1440x 576 i200 16/9"; + case HDMI_VFRMT_720x480p240_4_3: return " 720x 480 p240 4/3"; + case HDMI_VFRMT_720x480p240_16_9: return " 720x 480 p240 16/9"; + case HDMI_VFRMT_1440x480i240_4_3: return "1440x 480 i240 4/3"; + case HDMI_VFRMT_1440x480i240_16_9: return "1440x 480 i240 16/9"; +#elif defined(CONFIG_FB_MSM_TVOUT) + case TVOUT_VFRMT_NTSC_M_720x480i: return "NTSC_M_720x480i"; + case TVOUT_VFRMT_NTSC_J_720x480i: return "NTSC_J_720x480i"; + case TVOUT_VFRMT_PAL_BDGHIN_720x576i: return "PAL_BDGHIN_720x576i"; + case TVOUT_VFRMT_PAL_M_720x480i: return "PAL_M_720x480i"; + case TVOUT_VFRMT_PAL_N_720x480i: return "PAL_N_720x480i"; +#endif + + } +} +EXPORT_SYMBOL(video_format_2string); + +static ssize_t external_common_rda_video_mode_str(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%s\n", + video_format_2string(external_common_state->video_resolution)); + DEV_DBG("%s: '%s'\n", __func__, + video_format_2string(external_common_state->video_resolution)); + return ret; +} + +#ifdef CONFIG_FB_MSM_HDMI_COMMON +struct hdmi_disp_mode_timing_type + hdmi_common_supported_video_mode_lut[HDMI_VFRMT_MAX] = { + HDMI_SETTINGS_640x480p60_4_3, + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x480p60_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x480p60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1280x720p60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1080i60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x480i60_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x480i60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x240p60_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x240p60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x480i60_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x480i60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x240p60_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x240p60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x480p60_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x480p60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1080p60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x576p50_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x576p50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1280x720p50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1080i50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x576i50_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x576i50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x288p50_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x288p50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x576i50_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x576i50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x288p50_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x288p50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x576p50_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x576p50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1080p50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1080p24_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1080p25_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1080p30_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x480p60_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x480p60_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x576p50_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_2880x576p50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1250i50_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1080i100_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1280x720p100_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x576p100_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x576p100_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x576i100_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x576i100_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1920x1080i120_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1280x720p120_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x480p120_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x480p120_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x480i120_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x480i120_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x576p200_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x576p200_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x576i200_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x576i200_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x480p240_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_720x480p240_16_9), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x480i240_4_3), + VFRMT_NOT_SUPPORTED(HDMI_VFRMT_1440x480i240_16_9), +}; +EXPORT_SYMBOL(hdmi_common_supported_video_mode_lut); + +static ssize_t hdmi_common_rda_edid_modes(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = 0; + int i; + + buf[0] = 0; + if (external_common_state->disp_mode_list.num_of_elements) { + uint32 *video_mode = external_common_state->disp_mode_list + .disp_mode_list; + for (i = 0; i < external_common_state->disp_mode_list + .num_of_elements; ++i) { + if (ret > 0) + ret += snprintf(buf+ret, PAGE_SIZE-ret, ",%d", + *video_mode++ + 1); + else + ret += snprintf(buf+ret, PAGE_SIZE-ret, "%d", + *video_mode++ + 1); + } + } else + ret += snprintf(buf+ret, PAGE_SIZE-ret, "%d", + external_common_state->video_resolution+1); + + DEV_DBG("%s: '%s'\n", __func__, buf); + ret += snprintf(buf+ret, PAGE_SIZE-ret, "\n"); + return ret; +} + +static ssize_t hdmi_common_rda_hdcp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%d\n", + external_common_state->hdcp_active); + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->hdcp_active); + return ret; +} + +static ssize_t hdmi_common_rda_hpd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + if (external_common_state->hpd_feature) { + ret = snprintf(buf, PAGE_SIZE, "%d\n", + external_common_state->hpd_feature_on); + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->hpd_feature_on); + } else { + ret = snprintf(buf, PAGE_SIZE, "-1\n"); + DEV_DBG("%s: 'not supported'\n", __func__); + } + return ret; +} + +static ssize_t hdmi_common_wta_hpd(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret = strnlen(buf, PAGE_SIZE); + int hpd = atoi(buf); + + if (external_common_state->hpd_feature) { + if (hpd == 0 && external_common_state->hpd_feature_on) { + external_common_state->hpd_feature(0); + external_common_state->hpd_feature_on = 0; + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->hpd_feature_on); + } else if (hpd == 1 && !external_common_state->hpd_feature_on) { + external_common_state->hpd_feature(1); + external_common_state->hpd_feature_on = 1; + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->hpd_feature_on); + } else { + DEV_DBG("%s: '%d' (unchanged)\n", __func__, + external_common_state->hpd_feature_on); + } + } else { + DEV_DBG("%s: 'not supported'\n", __func__); + } + + return ret; +} + +static ssize_t hdmi_common_rda_3d_present(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%d\n", + external_common_state->present_3d); + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->present_3d); + return ret; +} + +static ssize_t hdmi_common_rda_hdcp_present(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%d\n", + external_common_state->present_hdcp); + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->present_hdcp); + return ret; +} +#endif + +#ifdef CONFIG_FB_MSM_HDMI_3D +static ssize_t hdmi_3d_rda_format_3d(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%d\n", + external_common_state->format_3d); + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->format_3d); + return ret; +} + +static ssize_t hdmi_3d_wta_format_3d(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret = strnlen(buf, PAGE_SIZE); + int format_3d = atoi(buf); + + if (format_3d >= 0 && format_3d <= 2) { + if (format_3d != external_common_state->format_3d) { + external_common_state->format_3d = format_3d; + if (external_common_state->switch_3d) + external_common_state->switch_3d(format_3d); + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->format_3d); + } else { + DEV_DBG("%s: '%d' (unchanged)\n", __func__, + external_common_state->format_3d); + } + } else { + DEV_DBG("%s: '%d' (unknown)\n", __func__, format_3d); + } + + return ret; +} +#endif + +static ssize_t external_common_rda_video_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "%d\n", + external_common_state->video_resolution+1); + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->video_resolution+1); + return ret; +} + +static ssize_t external_common_wta_video_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret = strnlen(buf, PAGE_SIZE); + uint32 video_mode; +#ifdef CONFIG_FB_MSM_HDMI_COMMON + const struct hdmi_disp_mode_timing_type *disp_mode; +#endif + mutex_lock(&external_common_state_hpd_mutex); + if (!external_common_state->hpd_state) { + mutex_unlock(&external_common_state_hpd_mutex); + DEV_INFO("%s: FAILED: display off or cable disconnected\n", + __func__); + return ret; + } + mutex_unlock(&external_common_state_hpd_mutex); + + video_mode = atoi(buf)-1; + kobject_uevent(external_common_state->uevent_kobj, KOBJ_OFFLINE); +#ifdef CONFIG_FB_MSM_HDMI_COMMON + disp_mode = hdmi_common_get_supported_mode(video_mode); + if (!disp_mode) { + DEV_INFO("%s: FAILED: mode not supported (%d)\n", + __func__, video_mode); + return ret; + } + external_common_state->disp_mode_list.num_of_elements = 1; + external_common_state->disp_mode_list.disp_mode_list[0] = video_mode; +#elif defined(CONFIG_FB_MSM_TVOUT) + external_common_state->video_resolution = video_mode; +#endif + DEV_DBG("%s: 'mode=%d %s' successful (sending OFF/ONLINE)\n", __func__, + video_mode, video_format_2string(video_mode)); + kobject_uevent(external_common_state->uevent_kobj, KOBJ_ONLINE); + return ret; +} + +static ssize_t external_common_rda_connected(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret; + mutex_lock(&external_common_state_hpd_mutex); + ret = snprintf(buf, PAGE_SIZE, "%d\n", + external_common_state->hpd_state); + DEV_DBG("%s: '%d'\n", __func__, + external_common_state->hpd_state); + mutex_unlock(&external_common_state_hpd_mutex); + return ret; +} + +static DEVICE_ATTR(video_mode, S_IRUGO | S_IWUGO, + external_common_rda_video_mode, external_common_wta_video_mode); +static DEVICE_ATTR(video_mode_str, S_IRUGO, external_common_rda_video_mode_str, + NULL); +static DEVICE_ATTR(connected, S_IRUGO, external_common_rda_connected, NULL); +#ifdef CONFIG_FB_MSM_HDMI_COMMON +static DEVICE_ATTR(edid_modes, S_IRUGO, hdmi_common_rda_edid_modes, NULL); +static DEVICE_ATTR(hpd, S_IRUGO | S_IWUGO, hdmi_common_rda_hpd, + hdmi_common_wta_hpd); +static DEVICE_ATTR(hdcp, S_IRUGO, hdmi_common_rda_hdcp, NULL); +static DEVICE_ATTR(3d_present, S_IRUGO, hdmi_common_rda_3d_present, NULL); +static DEVICE_ATTR(hdcp_present, S_IRUGO, hdmi_common_rda_hdcp_present, NULL); +#endif +#ifdef CONFIG_FB_MSM_HDMI_3D +static DEVICE_ATTR(format_3d, S_IRUGO | S_IWUGO, hdmi_3d_rda_format_3d, + hdmi_3d_wta_format_3d); +#endif + +static struct attribute *external_common_fs_attrs[] = { + &dev_attr_video_mode.attr, + &dev_attr_video_mode_str.attr, + &dev_attr_connected.attr, +#ifdef CONFIG_FB_MSM_HDMI_COMMON + &dev_attr_edid_modes.attr, + &dev_attr_hdcp.attr, + &dev_attr_hpd.attr, + &dev_attr_3d_present.attr, + &dev_attr_hdcp_present.attr, +#endif +#ifdef CONFIG_FB_MSM_HDMI_3D + &dev_attr_format_3d.attr, +#endif + NULL, +}; +static struct attribute_group external_common_fs_attr_group = { + .attrs = external_common_fs_attrs, +}; + +/* create external interface kobject and initialize */ +int external_common_state_create(struct platform_device *pdev) +{ + int rc; + struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); + if (!mfd) { + DEV_ERR("%s: mfd not found\n", __func__); + return -ENODEV; + } + if (!mfd->fbi) { + DEV_ERR("%s: mfd->fbi not found\n", __func__); + return -ENODEV; + } + if (!mfd->fbi->dev) { + DEV_ERR("%s: mfd->fbi->dev not found\n", __func__); + return -ENODEV; + } + rc = sysfs_create_group(&mfd->fbi->dev->kobj, + &external_common_fs_attr_group); + if (rc) { + DEV_ERR("%s: sysfs group creation failed, rc=%d\n", __func__, + rc); + return rc; + } + external_common_state->uevent_kobj = &mfd->fbi->dev->kobj; + DEV_ERR("%s: sysfs group %p\n", __func__, + external_common_state->uevent_kobj); + + kobject_uevent(external_common_state->uevent_kobj, KOBJ_ADD); + DEV_DBG("%s: kobject_uevent(KOBJ_ADD)\n", __func__); + return 0; +} +EXPORT_SYMBOL(external_common_state_create); + +void external_common_state_remove(void) +{ + if (external_common_state->uevent_kobj) + sysfs_remove_group(external_common_state->uevent_kobj, + &external_common_fs_attr_group); + external_common_state->uevent_kobj = NULL; +} +EXPORT_SYMBOL(external_common_state_remove); + +#ifdef CONFIG_FB_MSM_HDMI_COMMON +/* The Logic ID for HDMI TX Core. Currently only support 1 HDMI TX Core. */ +struct hdmi_edid_video_mode_property_type { + uint32 video_code; + uint32 active_h; + uint32 active_v; + boolean interlaced; + uint32 total_h; + uint32 total_blank_h; + uint32 total_v; + uint32 total_blank_v; + /* Must divide by 1000 to get the frequency */ + uint32 freq_h; + /* Must divide by 1000 to get the frequency */ + uint32 freq_v; + /* Must divide by 1000 to get the frequency */ + uint32 pixel_freq; + /* Must divide by 1000 to get the frequency */ + uint32 refresh_rate; + boolean aspect_ratio_4_3; +}; + +/* LUT is sorted from lowest Active H to highest Active H - ease searching */ +static struct hdmi_edid_video_mode_property_type + hdmi_edid_disp_mode_lut[] = { + + /* All 640 H Active */ + {HDMI_VFRMT_640x480p60_4_3, 640, 480, FALSE, 800, 160, 525, 45, + 31465, 59940, 25175, 59940, TRUE}, + {HDMI_VFRMT_640x480p60_4_3, 640, 480, FALSE, 800, 160, 525, 45, + 31500, 60000, 25200, 60000, TRUE}, + + /* All 720 H Active */ + {HDMI_VFRMT_720x576p50_4_3, 720, 576, FALSE, 864, 144, 625, 49, + 31250, 50000, 27000, 50000, TRUE}, + {HDMI_VFRMT_720x480p60_4_3, 720, 480, FALSE, 858, 138, 525, 45, + 31465, 59940, 27000, 59940, TRUE}, + {HDMI_VFRMT_720x480p60_4_3, 720, 480, FALSE, 858, 138, 525, 45, + 31500, 60000, 27030, 60000, TRUE}, + {HDMI_VFRMT_720x576p100_4_3, 720, 576, FALSE, 864, 144, 625, 49, + 62500, 100000, 54000, 100000, TRUE}, + {HDMI_VFRMT_720x480p120_4_3, 720, 480, FALSE, 858, 138, 525, 45, + 62937, 119880, 54000, 119880, TRUE}, + {HDMI_VFRMT_720x480p120_4_3, 720, 480, FALSE, 858, 138, 525, 45, + 63000, 120000, 54054, 120000, TRUE}, + {HDMI_VFRMT_720x576p200_4_3, 720, 576, FALSE, 864, 144, 625, 49, + 125000, 200000, 108000, 200000, TRUE}, + {HDMI_VFRMT_720x480p240_4_3, 720, 480, FALSE, 858, 138, 525, 45, + 125874, 239760, 108000, 239000, TRUE}, + {HDMI_VFRMT_720x480p240_4_3, 720, 480, FALSE, 858, 138, 525, 45, + 126000, 240000, 108108, 240000, TRUE}, + + /* All 1280 H Active */ + {HDMI_VFRMT_1280x720p50_16_9, 1280, 720, FALSE, 1980, 700, 750, 30, + 37500, 50000, 74250, 50000, FALSE}, + {HDMI_VFRMT_1280x720p60_16_9, 1280, 720, FALSE, 1650, 370, 750, 30, + 44955, 59940, 74176, 59940, FALSE}, + {HDMI_VFRMT_1280x720p60_16_9, 1280, 720, FALSE, 1650, 370, 750, 30, + 45000, 60000, 74250, 60000, FALSE}, + {HDMI_VFRMT_1280x720p100_16_9, 1280, 720, FALSE, 1980, 700, 750, 30, + 75000, 100000, 148500, 100000, FALSE}, + {HDMI_VFRMT_1280x720p120_16_9, 1280, 720, FALSE, 1650, 370, 750, 30, + 89909, 119880, 148352, 119880, FALSE}, + {HDMI_VFRMT_1280x720p120_16_9, 1280, 720, FALSE, 1650, 370, 750, 30, + 90000, 120000, 148500, 120000, FALSE}, + + /* All 1440 H Active */ + {HDMI_VFRMT_1440x576i50_4_3, 1440, 576, TRUE, 1728, 288, 625, 24, + 15625, 50000, 27000, 50000, TRUE}, + {HDMI_VFRMT_720x288p50_4_3, 1440, 288, FALSE, 1728, 288, 312, 24, + 15625, 50080, 27000, 50000, TRUE}, + {HDMI_VFRMT_720x288p50_4_3, 1440, 288, FALSE, 1728, 288, 313, 25, + 15625, 49920, 27000, 50000, TRUE}, + {HDMI_VFRMT_720x288p50_4_3, 1440, 288, FALSE, 1728, 288, 314, 26, + 15625, 49761, 27000, 50000, TRUE}, + {HDMI_VFRMT_1440x576p50_4_3, 1440, 576, FALSE, 1728, 288, 625, 49, + 31250, 50000, 54000, 50000, TRUE}, + {HDMI_VFRMT_1440x480i60_4_3, 1440, 480, TRUE, 1716, 276, 525, 22, + 15734, 59940, 27000, 59940, TRUE}, + {HDMI_VFRMT_1440x240p60_4_3, 1440, 240, FALSE, 1716, 276, 262, 22, + 15734, 60054, 27000, 59940, TRUE}, + {HDMI_VFRMT_1440x240p60_4_3, 1440, 240, FALSE, 1716, 276, 263, 23, + 15734, 59826, 27000, 59940, TRUE}, + {HDMI_VFRMT_1440x480p60_4_3, 1440, 480, FALSE, 1716, 276, 525, 45, + 31469, 59940, 54000, 59940, TRUE}, + {HDMI_VFRMT_1440x480i60_4_3, 1440, 480, TRUE, 1716, 276, 525, 22, + 15750, 60000, 27027, 60000, TRUE}, + {HDMI_VFRMT_1440x240p60_4_3, 1440, 240, FALSE, 1716, 276, 262, 22, + 15750, 60115, 27027, 60000, TRUE}, + {HDMI_VFRMT_1440x240p60_4_3, 1440, 240, FALSE, 1716, 276, 263, 23, + 15750, 59886, 27027, 60000, TRUE}, + {HDMI_VFRMT_1440x480p60_4_3, 1440, 480, FALSE, 1716, 276, 525, 45, + 31500, 60000, 54054, 60000, TRUE}, + {HDMI_VFRMT_1440x576i100_4_3, 1440, 576, TRUE, 1728, 288, 625, 24, + 31250, 100000, 54000, 100000, TRUE}, + {HDMI_VFRMT_1440x480i120_4_3, 1440, 480, TRUE, 1716, 276, 525, 22, + 31469, 119880, 54000, 119880, TRUE}, + {HDMI_VFRMT_1440x480i120_4_3, 1440, 480, TRUE, 1716, 276, 525, 22, + 31500, 120000, 54054, 120000, TRUE}, + {HDMI_VFRMT_1440x576i200_4_3, 1440, 576, TRUE, 1728, 288, 625, 24, + 62500, 200000, 108000, 200000, TRUE}, + {HDMI_VFRMT_1440x480i240_4_3, 1440, 480, TRUE, 1716, 276, 525, 22, + 62937, 239760, 108000, 239000, TRUE}, + {HDMI_VFRMT_1440x480i240_4_3, 1440, 480, TRUE, 1716, 276, 525, 22, + 63000, 240000, 108108, 240000, TRUE}, + + /* All 1920 H Active */ + {HDMI_VFRMT_1920x1080p60_16_9, 1920, 1080, FALSE, 2200, 280, 1125, + 45, 67433, 59940, 148352, 59940, FALSE}, + {HDMI_VFRMT_1920x1080p60_16_9, 1920, 1080, TRUE, 2200, 280, 1125, + 45, 67500, 60000, 148500, 60000, FALSE}, + {HDMI_VFRMT_1920x1080p50_16_9, 1920, 1080, FALSE, 2640, 720, 1125, + 45, 56250, 50000, 148500, 50000, FALSE}, + {HDMI_VFRMT_1920x1080p24_16_9, 1920, 1080, FALSE, 2750, 830, 1125, + 45, 26973, 23976, 74176, 24000, FALSE}, + {HDMI_VFRMT_1920x1080p24_16_9, 1920, 1080, FALSE, 2750, 830, 1125, + 45, 27000, 24000, 74250, 24000, FALSE}, + {HDMI_VFRMT_1920x1080p25_16_9, 1920, 1080, FALSE, 2640, 720, 1125, + 45, 28125, 25000, 74250, 25000, FALSE}, + {HDMI_VFRMT_1920x1080p30_16_9, 1920, 1080, FALSE, 2200, 280, 1125, + 45, 33716, 29970, 74176, 30000, FALSE}, + {HDMI_VFRMT_1920x1080p30_16_9, 1920, 1080, FALSE, 2200, 280, 1125, + 45, 33750, 30000, 74250, 30000, FALSE}, + {HDMI_VFRMT_1920x1080i50_16_9, 1920, 1080, TRUE, 2304, 384, 1250, + 85, 31250, 50000, 72000, 50000, FALSE}, + {HDMI_VFRMT_1920x1080i60_16_9, 1920, 1080, TRUE, 2200, 280, 1125, + 22, 33716, 59940, 74176, 59940, FALSE}, + {HDMI_VFRMT_1920x1080i60_16_9, 1920, 1080, TRUE, 2200, 280, 1125, + 22, 33750, 60000, 74250, 60000, FALSE}, + {HDMI_VFRMT_1920x1080i100_16_9, 1920, 1080, TRUE, 2640, 720, 1125, + 22, 56250, 100000, 148500, 100000, FALSE}, + {HDMI_VFRMT_1920x1080i120_16_9, 1920, 1080, TRUE, 2200, 280, 1125, + 22, 67432, 119880, 148352, 119980, FALSE}, + {HDMI_VFRMT_1920x1080i120_16_9, 1920, 1080, TRUE, 2200, 280, 1125, + 22, 67500, 120000, 148500, 120000, FALSE}, + + /* All 2880 H Active */ + {HDMI_VFRMT_2880x576i50_4_3, 2880, 576, TRUE, 3456, 576, 625, 24, + 15625, 50000, 54000, 50000, TRUE}, + {HDMI_VFRMT_2880x288p50_4_3, 2880, 576, FALSE, 3456, 576, 312, 24, + 15625, 50080, 54000, 50000, TRUE}, + {HDMI_VFRMT_2880x288p50_4_3, 2880, 576, FALSE, 3456, 576, 313, 25, + 15625, 49920, 54000, 50000, TRUE}, + {HDMI_VFRMT_2880x288p50_4_3, 2880, 576, FALSE, 3456, 576, 314, 26, + 15625, 49761, 54000, 50000, TRUE}, + {HDMI_VFRMT_2880x576p50_4_3, 2880, 576, FALSE, 3456, 576, 625, 49, + 31250, 50000, 108000, 50000, TRUE}, + {HDMI_VFRMT_2880x480i60_4_3, 2880, 480, TRUE, 3432, 552, 525, 22, + 15734, 59940, 54000, 59940, TRUE}, + {HDMI_VFRMT_2880x240p60_4_3, 2880, 480, FALSE, 3432, 552, 262, 22, + 15734, 60054, 54000, 59940, TRUE}, + {HDMI_VFRMT_2880x240p60_4_3, 2880, 480, FALSE, 3432, 552, 263, 23, + 15734, 59940, 54000, 59940, TRUE}, + {HDMI_VFRMT_2880x480p60_4_3, 2880, 480, FALSE, 3432, 552, 525, 45, + 31469, 59940, 108000, 59940, TRUE}, + {HDMI_VFRMT_2880x480i60_4_3, 2880, 480, TRUE, 3432, 552, 525, 22, + 15750, 60000, 54054, 60000, TRUE}, + {HDMI_VFRMT_2880x240p60_4_3, 2880, 240, FALSE, 3432, 552, 262, 22, + 15750, 60115, 54054, 60000, TRUE}, + {HDMI_VFRMT_2880x240p60_4_3, 2880, 240, FALSE, 3432, 552, 262, 23, + 15750, 59886, 54054, 60000, TRUE}, + {HDMI_VFRMT_2880x480p60_4_3, 2880, 480, FALSE, 3432, 552, 525, 45, + 31500, 60000, 108108, 60000, TRUE}, +}; + +static const uint8 *hdmi_edid_find_block(const uint8 *in_buf, uint8 type, + uint8 *len) +{ + /* the start of data block collection, start of Video Data Block */ + uint32 offset = 4; + + *len = 0; + if (in_buf[2] == 4) { /* no non-DTD data present */ + DEV_WARN("EDID: no non-DTD data present\n"); + return NULL; + } + while (offset < 0x80) { + uint8 block_len = in_buf[offset] & 0x1F; + if ((in_buf[offset] >> 5) == type) { + *len = block_len; + DEV_DBG("EDID: block=%d found @ %d with length=%d\n", + type, offset, block_len); + return in_buf+offset; + } + offset += 1 + block_len; + } + DEV_WARN("EDID: block=%d not found in EDID block\n", type); + return NULL; +} + +static void hdmi_edid_extract_vendor_id(const uint8 *in_buf, + char *vendor_id) +{ + uint32 id_codes = ((uint32)in_buf[8] << 8) + in_buf[9]; + + vendor_id[0] = 'A' - 1 + ((id_codes >> 10) & 0x1F); + vendor_id[1] = 'A' - 1 + ((id_codes >> 5) & 0x1F); + vendor_id[2] = 'A' - 1 + (id_codes & 0x1F); + vendor_id[3] = 0; +} + +static uint32 hdmi_edid_extract_ieee_reg_id(const uint8 *in_buf) +{ + uint8 len; + const uint8 *vsd = hdmi_edid_find_block(in_buf, 3, &len); + + if (vsd == NULL) + return 0; + + DEV_DBG("EDID: VSD PhyAddr=%04x, MaxTMDS=%dMHz\n", + ((uint32)vsd[6] << 8) + (uint32)vsd[5], (uint32)vsd[7] * 5); + return ((uint32)vsd[3] << 16) + ((uint32)vsd[2] << 8) + (uint32)vsd[1]; +} + +static void hdmi_edid_extract_3d_present(const uint8 *in_buf) +{ + uint8 len, offset; + const uint8 *vsd = hdmi_edid_find_block(in_buf, 3, &len); + + external_common_state->present_3d = 0; + if (vsd == NULL || len < 9) { + DEV_DBG("EDID[3D]: block-id 3 not found or not long enough\n"); + return; + } + + offset = !(vsd[8] & BIT(7)) ? 9 : 13; + DEV_DBG("EDID: 3D present @ %d = %02x\n", offset, vsd[offset]); + if (vsd[offset] >> 7) { /* 3D format indication present */ + DEV_INFO("EDID: 3D present, 3D-len=%d\n", vsd[offset+1] & 0x1F); + external_common_state->present_3d = 1; + } +} + + +static void hdmi_edid_extract_latency_fields(const uint8 *in_buf) +{ + uint8 len; + const uint8 *vsd = hdmi_edid_find_block(in_buf, 3, &len); + + if (vsd == NULL || len < 12 || !(vsd[8] & BIT(7))) { + external_common_state->video_latency = (uint16)-1; + external_common_state->audio_latency = (uint16)-1; + DEV_DBG("EDID: No audio/video latency present\n"); + } else { + external_common_state->video_latency = vsd[9]; + external_common_state->audio_latency = vsd[10]; + DEV_DBG("EDID: video-latency=%04x, audio-latency=%04x\n", + external_common_state->video_latency, + external_common_state->audio_latency); + } +} + +static void hdmi_edid_extract_speaker_allocation_data(const uint8 *in_buf) +{ + uint8 len; + const uint8 *sad = hdmi_edid_find_block(in_buf, 4, &len); + + if (sad == NULL) + return; + + external_common_state->speaker_allocation_block = sad[1]; + DEV_DBG("EDID: speaker allocation data=%s%s%s%s%s%s%s\n", + (sad[1] & BIT(0)) ? "FL/FR," : "", + (sad[1] & BIT(1)) ? "LFE," : "", + (sad[1] & BIT(2)) ? "FC," : "", + (sad[1] & BIT(3)) ? "RL/RR," : "", + (sad[1] & BIT(4)) ? "RC," : "", + (sad[1] & BIT(5)) ? "FLC/FRC," : "", + (sad[1] & BIT(6)) ? "LFE," : ""); +} + +static void hdmi_edid_extract_audio_data_blocks(const uint8 *in_buf) +{ + uint8 len; + const uint8 *sad = hdmi_edid_find_block(in_buf, 1, &len); + uint32 *adb = external_common_state->audio_data_blocks; + + if (sad == NULL) + return; + + external_common_state->audio_data_block_cnt = 0; + while (len >= 3 && external_common_state->audio_data_block_cnt < 16) { + DEV_DBG("EDID: Audio Data Block=\n", + (sad[1] & 0x7)+1, sad[1] >> 3, sad[2], sad[3]); + *adb++ = (uint32)sad[1] + ((uint32)sad[2] << 8) + + ((uint32)sad[2] << 16); + ++external_common_state->audio_data_block_cnt; + len -= 3; + sad += 3; + } +} + + +static void hdmi_edid_detail_desc(const uint8 *data_buf, uint32 *disp_mode) +{ + boolean aspect_ratio_4_3 = FALSE; + boolean interlaced = FALSE; + uint32 active_h = 0; + uint32 active_v = 0; + uint32 blank_h = 0; + uint32 blank_v = 0; + uint32 ndx = 0; + uint32 max_num_of_elements = 0; + uint32 img_size_h = 0; + uint32 img_size_v = 0; + + /* See VESA Spec */ + /* EDID_TIMING_DESC_UPPER_H_NIBBLE[0x4]: Relative Offset to the EDID + * detailed timing descriptors - Upper 4 bit for each H active/blank + * field */ + /* EDID_TIMING_DESC_H_ACTIVE[0x2]: Relative Offset to the EDID detailed + * timing descriptors - H active */ + active_h = ((((uint32)data_buf[0x4] >> 0x4) & 0xF) << 8) + | data_buf[0x2]; + + /* EDID_TIMING_DESC_H_BLANK[0x3]: Relative Offset to the EDID detailed + * timing descriptors - H blank */ + blank_h = (((uint32)data_buf[0x4] & 0xF) << 8) + | data_buf[0x3]; + + /* EDID_TIMING_DESC_UPPER_V_NIBBLE[0x7]: Relative Offset to the EDID + * detailed timing descriptors - Upper 4 bit for each V active/blank + * field */ + /* EDID_TIMING_DESC_V_ACTIVE[0x5]: Relative Offset to the EDID detailed + * timing descriptors - V active */ + active_v = ((((uint32)data_buf[0x7] >> 0x4) & 0xF) << 8) + | data_buf[0x5]; + + /* EDID_TIMING_DESC_V_BLANK[0x6]: Relative Offset to the EDID detailed + * timing descriptors - V blank */ + blank_v = (((uint32)data_buf[0x7] & 0xF) << 8) + | data_buf[0x6]; + + /* EDID_TIMING_DESC_IMAGE_SIZE_UPPER_NIBBLE[0xE]: Relative Offset to the + * EDID detailed timing descriptors - Image Size upper nibble + * V and H */ + /* EDID_TIMING_DESC_H_IMAGE_SIZE[0xC]: Relative Offset to the EDID + * detailed timing descriptors - H image size */ + /* EDID_TIMING_DESC_V_IMAGE_SIZE[0xD]: Relative Offset to the EDID + * detailed timing descriptors - V image size */ + img_size_h = ((((uint32)data_buf[0xE] >> 0x4) & 0xF) << 8) + | data_buf[0xC]; + img_size_v = (((uint32)data_buf[0xE] & 0xF) << 8) + | data_buf[0xD]; + + aspect_ratio_4_3 = (img_size_h * 3 == img_size_v * 4); + + max_num_of_elements = sizeof(hdmi_edid_disp_mode_lut) + / sizeof(*hdmi_edid_disp_mode_lut); + + /* Break table in half and search using H Active */ + ndx = active_h < hdmi_edid_disp_mode_lut[max_num_of_elements / 2] + .active_h ? 0 : max_num_of_elements / 2; + + /* EDID_TIMING_DESC_INTERLACE[0xD:8]: Relative Offset to the EDID + * detailed timing descriptors - Interlace flag */ + interlaced = (data_buf[0xD] & 0x80) >> 7; + + DEV_DBG("%s: A[%ux%u] B[%ux%u] V[%ux%u] %s\n", __func__, + active_h, active_v, blank_h, blank_v, img_size_h, img_size_v, + interlaced ? "i" : "p"); + + *disp_mode = HDMI_VFRMT_FORCE_32BIT; + while (ndx < max_num_of_elements) { + const struct hdmi_edid_video_mode_property_type *edid = + hdmi_edid_disp_mode_lut+ndx; + + if ((interlaced == edid->interlaced) && + (active_h == edid->active_h) && + (blank_h == edid->total_blank_h) && + (blank_v == edid->total_blank_v) && + ((active_v == edid->active_v) || + (active_v == (edid->active_v + 1))) + ) { + if (edid->aspect_ratio_4_3 && !aspect_ratio_4_3) + /* Aspect ratio 16:9 */ + *disp_mode = edid->video_code + 1; + else + /* Aspect ratio 4:3 */ + *disp_mode = edid->video_code; + + DEV_DBG("%s: mode found:%d\n", __func__, *disp_mode); + break; + } + ++ndx; + } + if (ndx == max_num_of_elements) + DEV_INFO("%s: *no mode* found\n", __func__); +} + +static void add_supported_video_format( + struct hdmi_disp_mode_list_type *disp_mode_list, + uint32 video_format) +{ + const struct hdmi_disp_mode_timing_type *timing = + hdmi_common_get_supported_mode(video_format); + boolean supported = timing != NULL; + + if (video_format >= HDMI_VFRMT_MAX) + return; + + DEV_DBG("EDID: format: %d [%s], %s\n", + video_format, video_format_2string(video_format), + supported ? "Supported" : "Not-Supported"); + if (supported) + disp_mode_list->disp_mode_list[ + disp_mode_list->num_of_elements++] = video_format; +} + +static void hdmi_edid_get_display_mode(const uint8 *data_buf, + struct hdmi_disp_mode_list_type *disp_mode_list, + uint32 num_og_cea_blocks) +{ + uint8 i = 0; + uint32 video_format = HDMI_VFRMT_640x480p60_4_3; + boolean has480p = FALSE; + uint8 len; + const uint8 *svd = num_og_cea_blocks ? + hdmi_edid_find_block(data_buf+0x80, 2, &len) : NULL; + + disp_mode_list->num_of_elements = 0; + if (svd != NULL) { + ++svd; + for (i = 0; i < len; ++i, ++svd) { + /* Subtract 1 because it is zero based in the driver, + * while the Video identification code is 1 based in the + * CEA_861D spec */ + video_format = (*svd & 0x7F) - 1; + add_supported_video_format(disp_mode_list, + video_format); + if (video_format == HDMI_VFRMT_640x480p60_4_3) + has480p = TRUE; + } + } else if (!num_og_cea_blocks) { + /* Detailed timing descriptors */ + uint32 desc_offset = 0; + /* Maximum 4 timing descriptor in block 0 - No CEA + * extension in this case */ + /* EDID_FIRST_TIMING_DESC[0x36] - 1st detailed timing + * descriptor */ + /* EDID_DETAIL_TIMING_DESC_BLCK_SZ[0x12] - Each detailed timing + * descriptor has block size of 18 */ + while (4 > i && 0 != data_buf[0x36+desc_offset]) { + hdmi_edid_detail_desc(data_buf+0x36+desc_offset, + &video_format); + add_supported_video_format(disp_mode_list, + video_format); + if (video_format == HDMI_VFRMT_640x480p60_4_3) + has480p = TRUE; + desc_offset += 0x12; + ++i; + } + } else if (1 == num_og_cea_blocks) { + uint32 desc_offset = 0; + /* Parse block 1 - CEA extension byte offset of first + * detailed timing generation - offset is relevant to + * the offset of block 1 */ + + /* EDID_CEA_EXTENSION_FIRST_DESC[0x82]: Offset to CEA + * extension first timing desc - indicate the offset of + * the first detailed timing descriptor */ + /* EDID_BLOCK_SIZE = 0x80 Each page size in the EDID ROM */ + desc_offset = data_buf[0x82]; + while (0 != data_buf[0x80 + desc_offset]) { + hdmi_edid_detail_desc(data_buf+0x36+desc_offset, + &video_format); + add_supported_video_format(disp_mode_list, + video_format); + if (video_format == HDMI_VFRMT_640x480p60_4_3) + has480p = TRUE; + desc_offset += 0x12; + ++i; + } + } + + if (!has480p) + /* Need to add default 640 by 480 timings, in case not described + * in the EDID structure. + * All DTV sink devices should support this mode */ + add_supported_video_format(disp_mode_list, + HDMI_VFRMT_640x480p60_4_3); +} + +static int hdmi_common_read_edid_block(int block, uint8 *edid_buf) +{ + uint32 ndx, check_sum; + int status = external_common_state->read_edid_block(block, edid_buf); + if (status || block > 0) + goto error; + + /* Calculate checksum */ + check_sum = 0; + for (ndx = 0; ndx < 0x80; ++ndx) + check_sum += edid_buf[ndx]; + + if (check_sum & 0xFF) { +#ifdef DEBUG + const u8 *b = edid_buf; +#endif + DEV_ERR("%s: failed CHECKSUM (read:%x, expected:%x)\n", + __func__, (uint8)edid_buf[0x7F], (uint8)check_sum); + +#ifdef DEBUG + for (ndx = 0; ndx < 0x100; ndx += 16) + DEV_DBG("EDID[%02x-%02x] %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x\n", ndx, ndx+15, + b[ndx+0], b[ndx+1], b[ndx+2], b[ndx+3], + b[ndx+4], b[ndx+5], b[ndx+6], b[ndx+7], + b[ndx+8], b[ndx+9], b[ndx+10], b[ndx+11], + b[ndx+12], b[ndx+13], b[ndx+14], b[ndx+15]); +#endif + status = -EPROTO; + goto error; + } + +error: + return status; +} + +static boolean check_edid_header(const uint8 *edid_buf) +{ + return (edid_buf[0] == 0x00) && (edid_buf[1] == 0xff) + && (edid_buf[2] == 0xff) && (edid_buf[3] == 0xff) + && (edid_buf[4] == 0xff) && (edid_buf[5] == 0xff) + && (edid_buf[6] == 0xff) && (edid_buf[7] == 0x00); +} + +int hdmi_common_read_edid(void) +{ + int status = 0; + uint32 cea_extension_ver = 0; + uint32 num_og_cea_blocks = 0; + uint32 ieee_reg_id = 0; + uint32 i = 1; + char vendor_id[5]; + /* EDID_BLOCK_SIZE[0x80] Each page size in the EDID ROM */ + uint8 edid_buf[0x80 * 4]; + + external_common_state->present_3d = 0; + memset(&external_common_state->disp_mode_list, 0, + sizeof(external_common_state->disp_mode_list)); + memset(edid_buf, 0, sizeof(edid_buf)); + + status = hdmi_common_read_edid_block(0, edid_buf); + if (status || !check_edid_header(edid_buf)) { + if (!status) + status = -EPROTO; + DEV_ERR("%s: edid read block(0) failed: %d " + "[%02x%02x%02x%02x%02x%02x%02x%02x]\n", __func__, + status, + edid_buf[0], edid_buf[1], edid_buf[2], edid_buf[3], + edid_buf[4], edid_buf[5], edid_buf[6], edid_buf[7]); + goto error; + } + hdmi_edid_extract_vendor_id(edid_buf, vendor_id); + + /* EDID_CEA_EXTENSION_FLAG[0x7E] - CEC extension byte */ + num_og_cea_blocks = edid_buf[0x7E]; + + DEV_DBG("[JSR] (%s): No. of CEA blocks is [%u]\n", __func__, + num_og_cea_blocks); + /* Find out any CEA extension blocks following block 0 */ + switch (num_og_cea_blocks) { + case 0: /* No CEA extension */ + external_common_state->hdmi_sink = false; + DEV_DBG("HDMI DVI mode: %s\n", + external_common_state->hdmi_sink ? "no" : "yes"); + break; + case 1: /* Read block 1 */ + status = hdmi_common_read_edid_block(1, edid_buf+0x80); + if (status) { + DEV_ERR("%s: ddc read block(1) failed: %d\n", __func__, + status); + goto error; + } + if (edid_buf[0x80] != 2) + num_og_cea_blocks = 0; + if (num_og_cea_blocks) { + ieee_reg_id = + hdmi_edid_extract_ieee_reg_id(edid_buf+0x80); + if (ieee_reg_id == 0x0c03) + external_common_state->hdmi_sink = TRUE ; + else + external_common_state->hdmi_sink = FALSE ; + hdmi_edid_extract_latency_fields(edid_buf+0x80); + hdmi_edid_extract_speaker_allocation_data( + edid_buf+0x80); + hdmi_edid_extract_audio_data_blocks(edid_buf+0x80); + hdmi_edid_extract_3d_present(edid_buf+0x80); + } + break; + case 2: + case 3: + case 4: + for (i = 1; i <= num_og_cea_blocks; i++) { + if (!(i % 2)) { + status = hdmi_common_read_edid_block(i, + edid_buf+0x00); + if (status) { + DEV_ERR("%s: ddc read block(%d)" + "failed: %d\n", __func__, i, + status); + goto error; + } + } else { + status = hdmi_common_read_edid_block(i, + edid_buf+0x80); + if (status) { + DEV_ERR("%s: ddc read block(%d)" + "failed:%d\n", __func__, i, + status); + goto error; + } + } + } + break; + default: + DEV_ERR("%s: ddc read failed, not supported multi-blocks: %d\n", + __func__, num_og_cea_blocks); + status = -EPROTO; + goto error; + } + + if (num_og_cea_blocks) { + /* EDID_CEA_EXTENSION_VERSION[0x81]: Offset to CEA extension + * version number - v1,v2,v3 (v1 is seldom, v2 is obsolete, + * v3 most common) */ + cea_extension_ver = edid_buf[0x81]; + } + + /* EDID_VERSION[0x12] - EDID Version */ + /* EDID_REVISION[0x13] - EDID Revision */ + DEV_INFO("EDID (V=%d.%d, #CEABlocks=%d[V%d], ID=%s, IEEE=%04x, " + "EDID-Ext=0x%02x)\n", edid_buf[0x12], edid_buf[0x13], + num_og_cea_blocks, cea_extension_ver, vendor_id, ieee_reg_id, + edid_buf[0x80]); + + hdmi_edid_get_display_mode(edid_buf, + &external_common_state->disp_mode_list, num_og_cea_blocks); + + return 0; + +error: + external_common_state->disp_mode_list.num_of_elements = 1; + external_common_state->disp_mode_list.disp_mode_list[0] = + external_common_state->video_resolution; + return status; +} +EXPORT_SYMBOL(hdmi_common_read_edid); + +bool hdmi_common_get_video_format_from_drv_data(struct msm_fb_data_type *mfd) +{ + uint32 format; + struct fb_var_screeninfo *var = &mfd->fbi->var; + bool changed = TRUE; + + if (var->reserved[3]) { + format = var->reserved[3]-1; + } else { + DEV_DBG("detecting resolution from %dx%d use var->reserved[3]" + " to specify mode", mfd->var_xres, mfd->var_yres); + switch (mfd->var_xres) { + default: + case 640: + format = HDMI_VFRMT_640x480p60_4_3; + break; + case 720: + format = (mfd->var_yres == 480) + ? HDMI_VFRMT_720x480p60_16_9 + : HDMI_VFRMT_720x576p50_16_9; + break; + case 1280: + format = HDMI_VFRMT_1280x720p60_16_9; + break; + case 1440: + format = (mfd->var_yres == 480) + ? HDMI_VFRMT_1440x480i60_16_9 + : HDMI_VFRMT_1440x576i50_16_9; + break; + case 1920: + format = HDMI_VFRMT_1920x1080p60_16_9; + break; + } + } + + changed = external_common_state->video_resolution != format; + if (external_common_state->video_resolution != format) + DEV_DBG("switching %s => %s", video_format_2string( + external_common_state->video_resolution), + video_format_2string(format)); + else + DEV_DBG("resolution %s", video_format_2string( + external_common_state->video_resolution)); + external_common_state->video_resolution = format; + return changed; +} +EXPORT_SYMBOL(hdmi_common_get_video_format_from_drv_data); + +const struct hdmi_disp_mode_timing_type *hdmi_common_get_mode(uint32 mode) +{ + if (mode >= HDMI_VFRMT_MAX) + return NULL; + + return &hdmi_common_supported_video_mode_lut[mode]; +} +EXPORT_SYMBOL(hdmi_common_get_mode); + +const struct hdmi_disp_mode_timing_type *hdmi_common_get_supported_mode( + uint32 mode) +{ + const struct hdmi_disp_mode_timing_type *ret + = hdmi_common_get_mode(mode); + + if (ret == NULL || !ret->supported) + return NULL; + return ret; +} +EXPORT_SYMBOL(hdmi_common_get_supported_mode); + +void hdmi_common_init_panel_info(struct msm_panel_info *pinfo) +{ + const struct hdmi_disp_mode_timing_type *timing = + hdmi_common_get_supported_mode( + external_common_state->video_resolution); + + if (timing == NULL) + return; + + pinfo->xres = timing->active_h; + pinfo->yres = timing->active_v; + pinfo->clk_rate = timing->pixel_freq*1000; + + pinfo->lcdc.h_back_porch = timing->back_porch_h; + pinfo->lcdc.h_front_porch = timing->front_porch_h; + pinfo->lcdc.h_pulse_width = timing->pulse_width_h; + pinfo->lcdc.v_back_porch = timing->back_porch_v; + pinfo->lcdc.v_front_porch = timing->front_porch_v; + pinfo->lcdc.v_pulse_width = timing->pulse_width_v; + + pinfo->type = DTV_PANEL; + pinfo->pdest = DISPLAY_2; + pinfo->wait_cycle = 0; + pinfo->bpp = 24; + pinfo->fb_num = 1; + + /* blk */ + pinfo->lcdc.border_clr = 0; + /* blue */ + pinfo->lcdc.underflow_clr = 0xff; + pinfo->lcdc.hsync_skew = 0; +} +EXPORT_SYMBOL(hdmi_common_init_panel_info); +#endif diff --git a/drivers/video/msm/external_common.h b/drivers/video/msm/external_common.h new file mode 100644 index 00000000000..30a8f48524a --- /dev/null +++ b/drivers/video/msm/external_common.h @@ -0,0 +1,251 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __EXTERNAL_COMMON_H__ +#define __EXTERNAL_COMMON_H__ + +#ifdef DEBUG +#ifndef DEV_DBG_PREFIX +#define DEV_DBG_PREFIX "EXT_INTERFACE: " +#endif +#define DEV_DBG(args...) pr_debug(DEV_DBG_PREFIX args) +#else +#define DEV_DBG(args...) (void)0 +#endif /* DEBUG */ +#define DEV_INFO(args...) dev_info(external_common_state->dev, args) +#define DEV_WARN(args...) dev_warn(external_common_state->dev, args) +#define DEV_ERR(args...) dev_err(external_common_state->dev, args) + +#ifdef CONFIG_FB_MSM_TVOUT +#define TVOUT_VFRMT_NTSC_M_720x480i 0 +#define TVOUT_VFRMT_NTSC_J_720x480i 1 +#define TVOUT_VFRMT_PAL_BDGHIN_720x576i 2 +#define TVOUT_VFRMT_PAL_M_720x480i 3 +#define TVOUT_VFRMT_PAL_N_720x480i 4 +#elif defined(CONFIG_FB_MSM_HDMI_COMMON) +/* all video formats defined by EIA CEA 861D */ +#define HDMI_VFRMT_640x480p60_4_3 0 +#define HDMI_VFRMT_720x480p60_4_3 1 +#define HDMI_VFRMT_720x480p60_16_9 2 +#define HDMI_VFRMT_1280x720p60_16_9 3 +#define HDMI_VFRMT_1920x1080i60_16_9 4 +#define HDMI_VFRMT_720x480i60_4_3 5 +#define HDMI_VFRMT_1440x480i60_4_3 HDMI_VFRMT_720x480i60_4_3 +#define HDMI_VFRMT_720x480i60_16_9 6 +#define HDMI_VFRMT_1440x480i60_16_9 HDMI_VFRMT_720x480i60_16_9 +#define HDMI_VFRMT_720x240p60_4_3 7 +#define HDMI_VFRMT_1440x240p60_4_3 HDMI_VFRMT_720x240p60_4_3 +#define HDMI_VFRMT_720x240p60_16_9 8 +#define HDMI_VFRMT_1440x240p60_16_9 HDMI_VFRMT_720x240p60_16_9 +#define HDMI_VFRMT_2880x480i60_4_3 9 +#define HDMI_VFRMT_2880x480i60_16_9 10 +#define HDMI_VFRMT_2880x240p60_4_3 11 +#define HDMI_VFRMT_2880x240p60_16_9 12 +#define HDMI_VFRMT_1440x480p60_4_3 13 +#define HDMI_VFRMT_1440x480p60_16_9 14 +#define HDMI_VFRMT_1920x1080p60_16_9 15 +#define HDMI_VFRMT_720x576p50_4_3 16 +#define HDMI_VFRMT_720x576p50_16_9 17 +#define HDMI_VFRMT_1280x720p50_16_9 18 +#define HDMI_VFRMT_1920x1080i50_16_9 19 +#define HDMI_VFRMT_720x576i50_4_3 20 +#define HDMI_VFRMT_1440x576i50_4_3 HDMI_VFRMT_720x576i50_4_3 +#define HDMI_VFRMT_720x576i50_16_9 21 +#define HDMI_VFRMT_1440x576i50_16_9 HDMI_VFRMT_720x576i50_16_9 +#define HDMI_VFRMT_720x288p50_4_3 22 +#define HDMI_VFRMT_1440x288p50_4_3 HDMI_VFRMT_720x288p50_4_3 +#define HDMI_VFRMT_720x288p50_16_9 23 +#define HDMI_VFRMT_1440x288p50_16_9 HDMI_VFRMT_720x288p50_16_9 +#define HDMI_VFRMT_2880x576i50_4_3 24 +#define HDMI_VFRMT_2880x576i50_16_9 25 +#define HDMI_VFRMT_2880x288p50_4_3 26 +#define HDMI_VFRMT_2880x288p50_16_9 27 +#define HDMI_VFRMT_1440x576p50_4_3 28 +#define HDMI_VFRMT_1440x576p50_16_9 29 +#define HDMI_VFRMT_1920x1080p50_16_9 30 +#define HDMI_VFRMT_1920x1080p24_16_9 31 +#define HDMI_VFRMT_1920x1080p25_16_9 32 +#define HDMI_VFRMT_1920x1080p30_16_9 33 +#define HDMI_VFRMT_2880x480p60_4_3 34 +#define HDMI_VFRMT_2880x480p60_16_9 35 +#define HDMI_VFRMT_2880x576p50_4_3 36 +#define HDMI_VFRMT_2880x576p50_16_9 37 +#define HDMI_VFRMT_1920x1250i50_16_9 38 +#define HDMI_VFRMT_1920x1080i100_16_9 39 +#define HDMI_VFRMT_1280x720p100_16_9 40 +#define HDMI_VFRMT_720x576p100_4_3 41 +#define HDMI_VFRMT_720x576p100_16_9 42 +#define HDMI_VFRMT_720x576i100_4_3 43 +#define HDMI_VFRMT_1440x576i100_4_3 HDMI_VFRMT_720x576i100_4_3 +#define HDMI_VFRMT_720x576i100_16_9 44 +#define HDMI_VFRMT_1440x576i100_16_9 HDMI_VFRMT_720x576i100_16_9 +#define HDMI_VFRMT_1920x1080i120_16_9 45 +#define HDMI_VFRMT_1280x720p120_16_9 46 +#define HDMI_VFRMT_720x480p120_4_3 47 +#define HDMI_VFRMT_720x480p120_16_9 48 +#define HDMI_VFRMT_720x480i120_4_3 49 +#define HDMI_VFRMT_1440x480i120_4_3 HDMI_VFRMT_720x480i120_4_3 +#define HDMI_VFRMT_720x480i120_16_9 50 +#define HDMI_VFRMT_1440x480i120_16_9 HDMI_VFRMT_720x480i120_16_9 +#define HDMI_VFRMT_720x576p200_4_3 51 +#define HDMI_VFRMT_720x576p200_16_9 52 +#define HDMI_VFRMT_720x576i200_4_3 53 +#define HDMI_VFRMT_1440x576i200_4_3 HDMI_VFRMT_720x576i200_4_3 +#define HDMI_VFRMT_720x576i200_16_9 54 +#define HDMI_VFRMT_1440x576i200_16_9 HDMI_VFRMT_720x576i200_16_9 +#define HDMI_VFRMT_720x480p240_4_3 55 +#define HDMI_VFRMT_720x480p240_16_9 56 +#define HDMI_VFRMT_720x480i240_4_3 57 +#define HDMI_VFRMT_1440x480i240_4_3 HDMI_VFRMT_720x480i240_4_3 +#define HDMI_VFRMT_720x480i240_16_9 58 +#define HDMI_VFRMT_1440x480i240_16_9 HDMI_VFRMT_720x480i240_16_9 +#define HDMI_VFRMT_MAX 59 +#define HDMI_VFRMT_FORCE_32BIT 0x7FFFFFFF + +struct hdmi_disp_mode_timing_type { + uint32 video_format; + uint32 active_h; + uint32 front_porch_h; + uint32 pulse_width_h; + uint32 back_porch_h; + boolean active_low_h; + uint32 active_v; + uint32 front_porch_v; + uint32 pulse_width_v; + uint32 back_porch_v; + boolean active_low_v; + /* Must divide by 1000 to get the actual frequency in MHZ */ + uint32 pixel_freq; + /* Must divide by 1000 to get the actual frequency in HZ */ + uint32 refresh_rate; + boolean interlaced; + boolean supported; +}; + +#define HDMI_SETTINGS_640x480p60_4_3 \ + {HDMI_VFRMT_640x480p60_4_3, 640, 16, 96, 48, TRUE, \ + 480, 10, 2, 33, TRUE, 25200, 60000, FALSE, TRUE} +#define HDMI_SETTINGS_720x480p60_4_3 \ + {HDMI_VFRMT_720x480p60_4_3, 720, 16, 62, 60, TRUE, \ + 480, 9, 6, 30, TRUE, 27030, 60000, FALSE, TRUE} +#define HDMI_SETTINGS_720x480p60_16_9 \ + {HDMI_VFRMT_720x480p60_16_9, 720, 16, 62, 60, TRUE, \ + 480, 9, 6, 30, TRUE, 27030, 60000, FALSE, TRUE} +#define HDMI_SETTINGS_1280x720p60_16_9 \ + {HDMI_VFRMT_1280x720p60_16_9, 1280, 110, 40, 220, FALSE, \ + 720, 5, 5, 20, FALSE, 74250, 60000, FALSE, TRUE} +#define HDMI_SETTINGS_1920x1080i60_16_9 \ + {HDMI_VFRMT_1920x1080i60_16_9, 1920, 88, 44, 148, FALSE, \ + 540, 2, 5, 5, FALSE, 74250, 60000, FALSE, TRUE} +#define HDMI_SETTINGS_1440x480i60_4_3 \ + {HDMI_VFRMT_1440x480i60_4_3, 1440, 38, 124, 114, TRUE, \ + 240, 4, 3, 15, TRUE, 27000, 60000, TRUE, TRUE} +#define HDMI_SETTINGS_1440x480i60_16_9 \ + {HDMI_VFRMT_1440x480i60_16_9, 1440, 38, 124, 114, TRUE, \ + 240, 4, 3, 15, TRUE, 27000, 60000, TRUE, TRUE} +#define HDMI_SETTINGS_1920x1080p60_16_9 \ + {HDMI_VFRMT_1920x1080p60_16_9, 1920, 88, 44, 148, FALSE, \ + 1080, 4, 5, 36, FALSE, 148500, 60000, FALSE, TRUE} +#define HDMI_SETTINGS_720x576p50_4_3 \ + {HDMI_VFRMT_720x576p50_4_3, 720, 12, 64, 68, TRUE, \ + 576, 5, 5, 39, TRUE, 27000, 50000, FALSE, TRUE} +#define HDMI_SETTINGS_720x576p50_16_9 \ + {HDMI_VFRMT_720x576p50_16_9, 720, 12, 64, 68, TRUE, \ + 576, 5, 5, 39, TRUE, 27000, 50000, FALSE, TRUE} +#define HDMI_SETTINGS_1280x720p50_16_9 \ + {HDMI_VFRMT_1280x720p50_16_9, 1280, 440, 40, 220, FALSE, \ + 720, 5, 5, 20, FALSE, 74250, 50000, FALSE, TRUE} +#define HDMI_SETTINGS_1440x576i50_4_3 \ + {HDMI_VFRMT_1440x576i50_4_3, 1440, 24, 126, 138, TRUE, \ + 288, 2, 3, 19, TRUE, 27000, 50000, TRUE, TRUE} +#define HDMI_SETTINGS_1440x576i50_16_9 \ + {HDMI_VFRMT_1440x576i50_16_9, 1440, 24, 126, 138, TRUE, \ + 288, 2, 3, 19, TRUE, 27000, 50000, TRUE, TRUE} +#define HDMI_SETTINGS_1920x1080p50_16_9 \ + {HDMI_VFRMT_1920x1080p50_16_9, 1920, 528, 44, 148, FALSE, \ + 1080, 4, 5, 36, FALSE, 148500, 50000, FALSE, TRUE} +#define HDMI_SETTINGS_1920x1080p24_16_9 \ + {HDMI_VFRMT_1920x1080p24_16_9, 1920, 638, 44, 148, FALSE, \ + 1080, 4, 5, 36, FALSE, 74250, 24000, FALSE, TRUE} +#define HDMI_SETTINGS_1920x1080p25_16_9 \ + {HDMI_VFRMT_1920x1080p25_16_9, 1920, 528, 44, 148, FALSE, \ + 1080, 4, 5, 36, FALSE, 74250, 25000, FALSE, TRUE} +#define HDMI_SETTINGS_1920x1080p30_16_9 \ + {HDMI_VFRMT_1920x1080p30_16_9, 1920, 88, 44, 148, FALSE, \ + 1080, 4, 5, 36, FALSE, 74250, 30000, FALSE, TRUE} + +/* A lookup table for all the supported display modes by the HDMI + * hardware and driver. Use HDMI_SETUP_LUT in the module init to + * setup the LUT with the supported modes. */ +extern struct hdmi_disp_mode_timing_type + hdmi_common_supported_video_mode_lut[HDMI_VFRMT_MAX]; + +/* Structure that encapsulates all the supported display modes by the HDMI sink + * device */ +struct hdmi_disp_mode_list_type { + uint32 disp_mode_list[HDMI_VFRMT_MAX]; + uint32 num_of_elements; +}; +#endif + +struct external_common_state_type { + boolean hpd_state; + struct kobject *uevent_kobj; + uint32 video_resolution; + struct device *dev; +#ifdef CONFIG_FB_MSM_HDMI_3D + boolean format_3d; + void (*switch_3d)(boolean on); +#endif +#ifdef CONFIG_FB_MSM_HDMI_COMMON + boolean hdcp_active; + boolean hpd_feature_on; + boolean hdmi_sink; + struct hdmi_disp_mode_list_type disp_mode_list; + uint8 speaker_allocation_block; + uint16 video_latency, audio_latency; + uint8 audio_data_block_cnt; + boolean present_3d; + boolean present_hdcp; + uint32 audio_data_blocks[16]; + int (*read_edid_block)(int block, uint8 *edid_buf); + int (*hpd_feature)(int on); +#endif +}; + +/* The external interface driver needs to initialize the common state. */ +extern struct external_common_state_type *external_common_state; +extern struct mutex external_common_state_hpd_mutex; + +#ifdef CONFIG_FB_MSM_HDMI_COMMON +#define VFRMT_NOT_SUPPORTED(VFRMT) \ + {VFRMT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, FALSE} +#define HDMI_SETUP_LUT(MODE) do { \ + struct hdmi_disp_mode_timing_type mode \ + = HDMI_SETTINGS_ ## MODE; \ + hdmi_common_supported_video_mode_lut[mode.video_format] \ + = mode; \ + } while (0) + +int hdmi_common_read_edid(void); +const char *video_format_2string(uint32 format); +bool hdmi_common_get_video_format_from_drv_data(struct msm_fb_data_type *mfd); +const struct hdmi_disp_mode_timing_type *hdmi_common_get_mode(uint32 mode); +const struct hdmi_disp_mode_timing_type *hdmi_common_get_supported_mode( + uint32 mode); +void hdmi_common_init_panel_info(struct msm_panel_info *pinfo); +#endif + +int external_common_state_create(struct platform_device *pdev); +void external_common_state_remove(void); + +#endif /* __EXTERNAL_COMMON_H__ */ diff --git a/drivers/video/msm/hdmi_msm.c b/drivers/video/msm/hdmi_msm.c new file mode 100644 index 00000000000..53dc91197b5 --- /dev/null +++ b/drivers/video/msm/hdmi_msm.c @@ -0,0 +1,3731 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* #define DEBUG */ +#define DEV_DBG_PREFIX "HDMI: " +/* #define REG_DUMP */ + +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb.h" +#include "hdmi_msm.h" + +/* Supported HDMI Audio channels */ +#define MSM_HDMI_AUDIO_CHANNEL_2 0 +#define MSM_HDMI_AUDIO_CHANNEL_4 1 +#define MSM_HDMI_AUDIO_CHANNEL_6 2 +#define MSM_HDMI_AUDIO_CHANNEL_8 3 +#define MSM_HDMI_AUDIO_CHANNEL_MAX 4 +#define MSM_HDMI_AUDIO_CHANNEL_FORCE_32BIT 0x7FFFFFFF + +/* Supported HDMI Audio sample rates */ +#define MSM_HDMI_SAMPLE_RATE_32KHZ 0 +#define MSM_HDMI_SAMPLE_RATE_44_1KHZ 1 +#define MSM_HDMI_SAMPLE_RATE_48KHZ 2 +#define MSM_HDMI_SAMPLE_RATE_88_2KHZ 3 +#define MSM_HDMI_SAMPLE_RATE_96KHZ 4 +#define MSM_HDMI_SAMPLE_RATE_176_4KHZ 5 +#define MSM_HDMI_SAMPLE_RATE_192KHZ 6 +#define MSM_HDMI_SAMPLE_RATE_MAX 7 +#define MSM_HDMI_SAMPLE_RATE_FORCE_32BIT 0x7FFFFFFF + +struct workqueue_struct *hdmi_work_queue; +struct hdmi_msm_state_type *hdmi_msm_state; + +static DEFINE_MUTEX(hdmi_msm_state_mutex); +static DEFINE_MUTEX(hdcp_auth_state_mutex); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT +static void hdmi_msm_hdcp_enable(void); +#else +static inline void hdmi_msm_hdcp_enable(void) {} +#endif + +uint32 hdmi_msm_get_io_base(void) +{ + return (uint32)MSM_HDMI_BASE; +} +EXPORT_SYMBOL(hdmi_msm_get_io_base); + +/* Table indicating the video format supported by the HDMI TX Core v1.0 */ +/* Valid Pixel-Clock rates: 25.2MHz, 27MHz, 27.03MHz, 74.25MHz, 148.5MHz */ +static void hdmi_msm_setup_video_mode_lut(void) +{ + HDMI_SETUP_LUT(640x480p60_4_3); + HDMI_SETUP_LUT(720x480p60_4_3); + HDMI_SETUP_LUT(720x480p60_16_9); + HDMI_SETUP_LUT(1280x720p60_16_9); + HDMI_SETUP_LUT(1920x1080i60_16_9); + HDMI_SETUP_LUT(1440x480i60_4_3); + HDMI_SETUP_LUT(1440x480i60_16_9); + HDMI_SETUP_LUT(1920x1080p60_16_9); + HDMI_SETUP_LUT(720x576p50_4_3); + HDMI_SETUP_LUT(720x576p50_16_9); + HDMI_SETUP_LUT(1280x720p50_16_9); + HDMI_SETUP_LUT(1440x576i50_4_3); + HDMI_SETUP_LUT(1440x576i50_16_9); + HDMI_SETUP_LUT(1920x1080p50_16_9); + HDMI_SETUP_LUT(1920x1080p24_16_9); + HDMI_SETUP_LUT(1920x1080p25_16_9); + HDMI_SETUP_LUT(1920x1080p30_16_9); +} + +#ifdef PORT_DEBUG +const char *hdmi_msm_name(uint32 offset) +{ + switch (offset) { + case 0x0000: return "CTRL"; + case 0x0020: return "AUDIO_PKT_CTRL1"; + case 0x0024: return "ACR_PKT_CTRL"; + case 0x0028: return "VBI_PKT_CTRL"; + case 0x002C: return "INFOFRAME_CTRL0"; +#ifdef CONFIG_FB_MSM_HDMI_3D + case 0x0034: return "GEN_PKT_CTRL"; +#endif + case 0x003C: return "ACP"; + case 0x0040: return "GC"; + case 0x0044: return "AUDIO_PKT_CTRL2"; + case 0x0048: return "ISRC1_0"; + case 0x004C: return "ISRC1_1"; + case 0x0050: return "ISRC1_2"; + case 0x0054: return "ISRC1_3"; + case 0x0058: return "ISRC1_4"; + case 0x005C: return "ISRC2_0"; + case 0x0060: return "ISRC2_1"; + case 0x0064: return "ISRC2_2"; + case 0x0068: return "ISRC2_3"; + case 0x006C: return "AVI_INFO0"; + case 0x0070: return "AVI_INFO1"; + case 0x0074: return "AVI_INFO2"; + case 0x0078: return "AVI_INFO3"; +#ifdef CONFIG_FB_MSM_HDMI_3D + case 0x0084: return "GENERIC0_HDR"; + case 0x0088: return "GENERIC0_0"; + case 0x008C: return "GENERIC0_1"; +#endif + case 0x00C4: return "ACR_32_0"; + case 0x00C8: return "ACR_32_1"; + case 0x00CC: return "ACR_44_0"; + case 0x00D0: return "ACR_44_1"; + case 0x00D4: return "ACR_48_0"; + case 0x00D8: return "ACR_48_1"; + case 0x00E4: return "AUDIO_INFO0"; + case 0x00E8: return "AUDIO_INFO1"; +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + case 0x0110: return "HDCP_CTRL"; + case 0x0114: return "HDCP_DEBUG_CTRL"; + case 0x0118: return "HDCP_INT_CTRL"; + case 0x011C: return "HDCP_LINK0_STATUS"; + case 0x012C: return "HDCP_ENTROPY_CTRL0"; + case 0x0130: return "HDCP_RESET"; + case 0x0134: return "HDCP_RCVPORT_DATA0"; + case 0x0138: return "HDCP_RCVPORT_DATA1"; + case 0x013C: return "HDCP_RCVPORT_DATA2"; + case 0x0144: return "HDCP_RCVPORT_DATA3"; + case 0x0148: return "HDCP_RCVPORT_DATA4"; + case 0x014C: return "HDCP_RCVPORT_DATA5"; + case 0x0150: return "HDCP_RCVPORT_DATA6"; + case 0x0168: return "HDCP_RCVPORT_DATA12"; +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + case 0x01D0: return "AUDIO_CFG"; + case 0x0208: return "USEC_REFTIMER"; + case 0x020C: return "DDC_CTRL"; + case 0x0214: return "DDC_INT_CTRL"; + case 0x0218: return "DDC_SW_STATUS"; + case 0x021C: return "DDC_HW_STATUS"; + case 0x0220: return "DDC_SPEED"; + case 0x0224: return "DDC_SETUP"; + case 0x0228: return "DDC_TRANS0"; + case 0x022C: return "DDC_TRANS1"; + case 0x0238: return "DDC_DATA"; + case 0x0250: return "HPD_INT_STATUS"; + case 0x0254: return "HPD_INT_CTRL"; + case 0x0258: return "HPD_CTRL"; +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + case 0x025C: return "HDCP_ENTROPY_CTRL1"; +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + case 0x027C: return "DDC_REF"; +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + case 0x0284: return "HDCP_SW_UPPER_AKSV"; + case 0x0288: return "HDCP_SW_LOWER_AKSV"; +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + case 0x02B4: return "ACTIVE_H"; + case 0x02B8: return "ACTIVE_V"; + case 0x02BC: return "ACTIVE_V_F2"; + case 0x02C0: return "TOTAL"; + case 0x02C4: return "V_TOTAL_F2"; + case 0x02C8: return "FRAME_CTRL"; + case 0x02CC: return "AUD_INT"; + case 0x0300: return "PHY_REG0"; + case 0x0304: return "PHY_REG1"; + case 0x0308: return "PHY_REG2"; + case 0x030C: return "PHY_REG3"; + case 0x0310: return "PHY_REG4"; + case 0x0314: return "PHY_REG5"; + case 0x0318: return "PHY_REG6"; + case 0x031C: return "PHY_REG7"; + case 0x0320: return "PHY_REG8"; + case 0x0324: return "PHY_REG9"; + case 0x0328: return "PHY_REG10"; + case 0x032C: return "PHY_REG11"; + case 0x0330: return "PHY_REG12"; + default: return "???"; + } +} + +void hdmi_outp(uint32 offset, uint32 value) +{ + uint32 in_val; + + outpdw(MSM_HDMI_BASE+offset, value); + in_val = inpdw(MSM_HDMI_BASE+offset); + DEV_DBG("HDMI[%04x] => %08x [%08x] %s\n", + offset, value, in_val, hdmi_msm_name(offset)); +} + +uint32 hdmi_inp(uint32 offset) +{ + uint32 value = inpdw(MSM_HDMI_BASE+offset); + DEV_DBG("HDMI[%04x] <= %08x %s\n", + offset, value, hdmi_msm_name(offset)); + return value; +} +#endif /* DEBUG */ + +static void hdmi_msm_turn_on(void); +static int hdmi_msm_audio_off(void); +static int hdmi_msm_read_edid(void); +static void hdmi_msm_hpd_off(void); + +static void hdmi_msm_hpd_state_work(struct work_struct *work) +{ + boolean hpd_state; + char *envp[2]; + + if (!hdmi_msm_state || !hdmi_msm_state->hpd_initialized || + !MSM_HDMI_BASE) { + DEV_DBG("%s: ignored, probe failed\n", __func__); + return; + } +#ifdef CONFIG_SUSPEND + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->pm_suspended) { + mutex_unlock(&hdmi_msm_state_mutex); + DEV_WARN("%s: ignored, pm_suspended\n", __func__); + return; + } + mutex_unlock(&hdmi_msm_state_mutex); +#endif + + /* HPD_INT_STATUS[0x0250] */ + hpd_state = (HDMI_INP(0x0250) & 0x2) >> 1; + mutex_lock(&external_common_state_hpd_mutex); + mutex_lock(&hdmi_msm_state_mutex); + if ((external_common_state->hpd_state != hpd_state) || (hdmi_msm_state-> + hpd_prev_state != external_common_state->hpd_state)) { + external_common_state->hpd_state = hpd_state; + hdmi_msm_state->hpd_prev_state = + external_common_state->hpd_state; + DEV_DBG("%s: state not stable yet, wait again (%d|%d|%d)\n", + __func__, hdmi_msm_state->hpd_prev_state, + external_common_state->hpd_state, hpd_state); + mutex_unlock(&external_common_state_hpd_mutex); + hdmi_msm_state->hpd_stable = 0; + mutex_unlock(&hdmi_msm_state_mutex); + mod_timer(&hdmi_msm_state->hpd_state_timer, jiffies + HZ/2); + return; + } + mutex_unlock(&external_common_state_hpd_mutex); + + if (hdmi_msm_state->hpd_stable++) { + mutex_unlock(&hdmi_msm_state_mutex); + DEV_DBG("%s: no more timer, depending for IRQ now\n", + __func__); + return; + } + + hdmi_msm_state->hpd_stable = 1; + DEV_INFO("HDMI HPD: event detected\n"); + + if (!hdmi_msm_state->hpd_cable_chg_detected) { + mutex_unlock(&hdmi_msm_state_mutex); + if (hpd_state) { + if (!external_common_state-> + disp_mode_list.num_of_elements) + hdmi_msm_read_edid(); + hdmi_msm_turn_on(); + } + } else { + hdmi_msm_state->hpd_cable_chg_detected = FALSE; + mutex_unlock(&hdmi_msm_state_mutex); + if (hpd_state) { + hdmi_msm_read_edid(); +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + hdmi_msm_state->reauth = FALSE ; +#endif + /* Build EDID table */ + envp[0] = "HDCP_STATE=FAIL"; + envp[1] = NULL; + DEV_INFO("HDMI HPD: QDSP OFF\n"); + kobject_uevent_env(external_common_state->uevent_kobj, + KOBJ_CHANGE, envp); + hdmi_msm_turn_on(); + DEV_INFO("HDMI HPD: sense CONNECTED: send ONLINE\n"); + kobject_uevent(external_common_state->uevent_kobj, + KOBJ_ONLINE); + hdmi_msm_hdcp_enable(); + } else { + DEV_INFO("HDMI HPD: sense DISCONNECTED: send OFFLINE\n" + ); + kobject_uevent(external_common_state->uevent_kobj, + KOBJ_OFFLINE); + } + } + + /* HPD_INT_CTRL[0x0254] + * 31:10 Reserved + * 9 RCV_PLUGIN_DET_MASK receiver plug in interrupt mask. + * When programmed to 1, + * RCV_PLUGIN_DET_INT will toggle + * the interrupt line + * 8:6 Reserved + * 5 RX_INT_EN Panel RX interrupt enable + * 0: Disable + * 1: Enable + * 4 RX_INT_ACK WRITE ONLY. Panel RX interrupt + * ack + * 3 Reserved + * 2 INT_EN Panel interrupt control + * 0: Disable + * 1: Enable + * 1 INT_POLARITY Panel interrupt polarity + * 0: generate interrupt on disconnect + * 1: generate interrupt on connect + * 0 INT_ACK WRITE ONLY. Panel interrupt ack */ + /* Set IRQ for HPD */ + HDMI_OUTP(0x0254, 4 | (hpd_state ? 0 : 2)); +} + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT +static void hdcp_deauthenticate(void); +static void hdmi_msm_hdcp_reauth_work(struct work_struct *work) +{ +#ifdef CONFIG_SUSPEND + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->pm_suspended) { + mutex_unlock(&hdmi_msm_state_mutex); + DEV_WARN("HDCP: deauthenticating skipped, pm_suspended\n"); + return; + } + mutex_unlock(&hdmi_msm_state_mutex); +#endif + + /* Don't process recursive actions */ + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->hdcp_activating) { + mutex_unlock(&hdmi_msm_state_mutex); + return; + } + mutex_unlock(&hdmi_msm_state_mutex); + + /* + * Reauth=>deauth, hdcp_auth + * hdcp_auth=>turn_on() which calls + * HDMI Core reset without informing the Audio QDSP + * this can do bad things to video playback on the HDTV + * Therefore, as surprising as it may sound do reauth + * only if the device is HDCP-capable + */ + if (external_common_state->present_hdcp) { + hdcp_deauthenticate(); + mod_timer(&hdmi_msm_state->hdcp_timer, jiffies + HZ/2); + } +} + +static void hdmi_msm_hdcp_work(struct work_struct *work) +{ +#ifdef CONFIG_SUSPEND + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->pm_suspended) { + mutex_unlock(&hdmi_msm_state_mutex); + DEV_WARN("HDCP: Re-enable skipped, pm_suspended\n"); + return; + } + mutex_unlock(&hdmi_msm_state_mutex); +#endif + + /* Only re-enable if cable still connected */ + mutex_lock(&external_common_state_hpd_mutex); + if (external_common_state->hpd_state && + !(hdmi_msm_state->full_auth_done)) { + mutex_unlock(&external_common_state_hpd_mutex); + hdmi_msm_state->reauth = TRUE; + hdmi_msm_turn_on(); + } else + mutex_unlock(&external_common_state_hpd_mutex); +} +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + +static irqreturn_t hdmi_msm_isr(int irq, void *dev_id) +{ + uint32 hpd_int_status; + uint32 hpd_int_ctrl; + uint32 ddc_int_ctrl; + uint32 audio_int_val; +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + uint32 hdcp_int_val; + char *envp[2]; +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + static uint32 fifo_urun_int_occurred; + static uint32 sample_drop_int_occurred; + const uint32 occurrence_limit = 5; + + if (!hdmi_msm_state || !hdmi_msm_state->hpd_initialized || + !MSM_HDMI_BASE) { + DEV_DBG("ISR ignored, probe failed\n"); + return IRQ_HANDLED; + } +#ifdef CONFIG_SUSPEND + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->pm_suspended) { + mutex_unlock(&hdmi_msm_state_mutex); + DEV_WARN("ISR ignored, pm_suspended\n"); + return IRQ_HANDLED; + } + mutex_unlock(&hdmi_msm_state_mutex); +#endif + + /* Process HPD Interrupt */ + /* HDMI_HPD_INT_STATUS[0x0250] */ + hpd_int_status = HDMI_INP_ND(0x0250); + /* HDMI_HPD_INT_CTRL[0x0254] */ + hpd_int_ctrl = HDMI_INP_ND(0x0254); + if ((hpd_int_ctrl & (1 << 2)) && (hpd_int_status & (1 << 0))) { + boolean cable_detected = (hpd_int_status & 2) >> 1; + + /* HDMI_HPD_INT_CTRL[0x0254] */ + /* Clear all interrupts, timer will turn IRQ back on */ + HDMI_OUTP(0x0254, 1 << 0); + + DEV_DBG("%s: HPD IRQ, Ctrl=%04x, State=%04x\n", __func__, + hpd_int_ctrl, hpd_int_status); + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->hpd_cable_chg_detected = TRUE; + + /* ensure 2 readouts */ + hdmi_msm_state->hpd_prev_state = cable_detected ? 0 : 1; + external_common_state->hpd_state = cable_detected ? 1 : 0; + hdmi_msm_state->hpd_stable = 0; + mod_timer(&hdmi_msm_state->hpd_state_timer, jiffies + HZ/2); + mutex_unlock(&hdmi_msm_state_mutex); + /* + * HDCP Compliance 1A-01: + * The Quantum Data Box 882 triggers two consecutive + * HPD events very close to each other as a part of this + * test which can trigger two parallel HDCP auth threads + * if HDCP authentication is going on and we get ISR + * then stop the authentication , rather than + * reauthenticating it again + */ + if (!(hdmi_msm_state->full_auth_done)) { + DEV_DBG("%s getting hpd while authenticating\n",\ + __func__); + mutex_lock(&hdcp_auth_state_mutex); + hdmi_msm_state->hpd_during_auth = TRUE; + mutex_unlock(&hdcp_auth_state_mutex); + } + return IRQ_HANDLED; + } + + /* Process DDC Interrupts */ + /* HDMI_DDC_INT_CTRL[0x0214] */ + ddc_int_ctrl = HDMI_INP_ND(0x0214); + if ((ddc_int_ctrl & (1 << 2)) && (ddc_int_ctrl & (1 << 0))) { + /* SW_DONE INT occured, clr it */ + HDMI_OUTP_ND(0x0214, ddc_int_ctrl | (1 << 1)); + complete(&hdmi_msm_state->ddc_sw_done); + return IRQ_HANDLED; + } + + /* FIFO Underrun Int is enabled */ + /* HDMI_AUD_INT[0x02CC] + * [3] AUD_SAM_DROP_MASK [R/W] + * [2] AUD_SAM_DROP_ACK [W], AUD_SAM_DROP_INT [R] + * [1] AUD_FIFO_URUN_MASK [R/W] + * [0] AUD_FIFO_URUN_ACK [W], AUD_FIFO_URUN_INT [R] */ + audio_int_val = HDMI_INP_ND(0x02CC); + if ((audio_int_val & (1 << 1)) && (audio_int_val & (1 << 0))) { + /* FIFO Underrun occured, clr it */ + HDMI_OUTP(0x02CC, audio_int_val | (1 << 0)); + + ++fifo_urun_int_occurred; + DEV_INFO("HDMI AUD_FIFO_URUN: %d\n", fifo_urun_int_occurred); + + if (fifo_urun_int_occurred >= occurrence_limit) { + HDMI_OUTP(0x02CC, HDMI_INP(0x02CC) & ~(1 << 1)); + DEV_INFO("HDMI AUD_FIFO_URUN: INT has been disabled " + "by the ISR after %d occurences...\n", + fifo_urun_int_occurred); + } + return IRQ_HANDLED; + } + + /* Audio Sample Drop int is enabled */ + if ((audio_int_val & (1 << 3)) && (audio_int_val & (1 << 2))) { + /* Audio Sample Drop occured, clr it */ + HDMI_OUTP(0x02CC, audio_int_val | (1 << 2)); + DEV_DBG("%s: AUD_SAM_DROP", __func__); + + ++sample_drop_int_occurred; + if (sample_drop_int_occurred >= occurrence_limit) { + HDMI_OUTP(0x02CC, HDMI_INP(0x02CC) & ~(1 << 3)); + DEV_INFO("HDMI AUD_SAM_DROP: INT has been disabled " + "by the ISR after %d occurences...\n", + sample_drop_int_occurred); + } + return IRQ_HANDLED; + } + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + /* HDCP_INT_CTRL[0x0118] + * [0] AUTH_SUCCESS_INT [R] HDCP Authentication Success + * interrupt status + * [1] AUTH_SUCCESS_ACK [W] Acknowledge bit for HDCP + * Authentication Success bit - write 1 to clear + * [2] AUTH_SUCCESS_MASK [R/W] Mask bit for HDCP Authentication + * Success interrupt - set to 1 to enable interrupt */ + hdcp_int_val = HDMI_INP_ND(0x0118); + if ((hdcp_int_val & (1 << 2)) && (hdcp_int_val & (1 << 0))) { + /* AUTH_SUCCESS_INT */ + HDMI_OUTP(0x0118, (hdcp_int_val | (1 << 1)) & ~(1 << 0)); + DEV_INFO("HDCP: AUTH_SUCCESS_INT received\n"); + complete_all(&hdmi_msm_state->hdcp_success_done); + return IRQ_HANDLED; + } + /* [4] AUTH_FAIL_INT [R] HDCP Authentication Lost + * interrupt Status + * [5] AUTH_FAIL_ACK [W] Acknowledge bit for HDCP + * Authentication Lost bit - write 1 to clear + * [6] AUTH_FAIL_MASK [R/W] Mask bit fo HDCP Authentication + * Lost interrupt set to 1 to enable interrupt + * [7] AUTH_FAIL_INFO_ACK [W] Acknowledge bit for HDCP + * Authentication Failure Info field - write 1 to clear */ + if ((hdcp_int_val & (1 << 6)) && (hdcp_int_val & (1 << 4))) { + /* AUTH_FAIL_INT */ + /* Clear and Disable */ + HDMI_OUTP(0x0118, (hdcp_int_val | (1 << 5)) + & ~((1 << 6) | (1 << 4))); + DEV_INFO("HDCP: AUTH_FAIL_INT received, LINK0_STATUS=0x%08x\n", + HDMI_INP_ND(0x011C)); + if (hdmi_msm_state->full_auth_done) { + envp[0] = "HDCP_STATE=FAIL"; + envp[1] = NULL; + DEV_INFO("HDMI HPD:QDSP OFF\n"); + kobject_uevent_env(external_common_state->uevent_kobj, + KOBJ_CHANGE, envp); + mutex_lock(&hdcp_auth_state_mutex); + hdmi_msm_state->full_auth_done = FALSE; + mutex_unlock(&hdcp_auth_state_mutex); + /* Calling reauth only when authentication + * is sucessful or else we always go into + * the reauth loop + */ + queue_work(hdmi_work_queue, + &hdmi_msm_state->hdcp_reauth_work); + } + mutex_lock(&hdcp_auth_state_mutex); + /* This flag prevents other threads from re-authenticating + * after we've just authenticated (i.e., finished part3) + */ + hdmi_msm_state->full_auth_done = FALSE; + + mutex_unlock(&hdcp_auth_state_mutex); + DEV_DBG("calling reauthenticate from %s HDCP FAIL INT ", + __func__); + + return IRQ_HANDLED; + } + /* [8] DDC_XFER_REQ_INT [R] HDCP DDC Transfer Request + * interrupt status + * [9] DDC_XFER_REQ_ACK [W] Acknowledge bit for HDCP DDC + * Transfer Request bit - write 1 to clear + * [10] DDC_XFER_REQ_MASK [R/W] Mask bit for HDCP DDC Transfer + * Request interrupt - set to 1 to enable interrupt */ + if ((hdcp_int_val & (1 << 10)) && (hdcp_int_val & (1 << 8))) { + /* DDC_XFER_REQ_INT */ + HDMI_OUTP_ND(0x0118, (hdcp_int_val | (1 << 9)) & ~(1 << 8)); + if (!(hdcp_int_val & (1 << 12))) + return IRQ_HANDLED; + } + /* [12] DDC_XFER_DONE_INT [R] HDCP DDC Transfer done interrupt + * status + * [13] DDC_XFER_DONE_ACK [W] Acknowledge bit for HDCP DDC + * Transfer done bit - write 1 to clear + * [14] DDC_XFER_DONE_MASK [R/W] Mask bit for HDCP DDC Transfer + * done interrupt - set to 1 to enable interrupt */ + if ((hdcp_int_val & (1 << 14)) && (hdcp_int_val & (1 << 12))) { + /* DDC_XFER_DONE_INT */ + HDMI_OUTP_ND(0x0118, (hdcp_int_val | (1 << 13)) & ~(1 << 12)); + DEV_INFO("HDCP: DDC_XFER_DONE received\n"); + return IRQ_HANDLED; + } +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + + DEV_DBG("%s: HPD, ddc_int_ctrl=%04x, " + "aud_int=%04x, cec_int=%04x\n", __func__, hpd_int_ctrl, + hpd_int_status, ddc_int_ctrl, audio_int_val, + HDMI_INP_ND(0x029C)); + + return IRQ_HANDLED; +} + +static int check_hdmi_features(void) +{ + /* RAW_FEAT_CONFIG_ROW0_LSB */ + uint32 val = inpdw(QFPROM_BASE + 0x0238); + /* HDMI_DISABLE */ + boolean hdmi_disabled = (val & 0x00200000) >> 21; + /* HDCP_DISABLE */ + boolean hdcp_disabled = (val & 0x00400000) >> 22; + + DEV_DBG("Features \n", val, + hdmi_disabled ? "OFF" : "ON", hdcp_disabled ? "OFF" : "ON"); + if (hdmi_disabled) { + DEV_ERR("ERROR: HDMI disabled\n"); + return -ENODEV; + } + + if (hdcp_disabled) + DEV_WARN("WARNING: HDCP disabled\n"); + + return 0; +} + +static boolean hdmi_msm_has_hdcp(void) +{ + /* RAW_FEAT_CONFIG_ROW0_LSB, HDCP_DISABLE */ + return (inpdw(QFPROM_BASE + 0x0238) & 0x00400000) ? FALSE : TRUE; +} + +static boolean hdmi_msm_is_power_on(void) +{ + /* HDMI_CTRL, ENABLE */ + return (HDMI_INP_ND(0x0000) & 0x00000001) ? TRUE : FALSE; +} + +/* 1.2.1.2.1 DVI Operation + * HDMI compliance requires the HDMI core to support DVI as well. The + * HDMI core also supports DVI. In DVI operation there are no preambles + * and guardbands transmitted. THe TMDS encoding of video data remains + * the same as HDMI. There are no VBI or audio packets transmitted. In + * order to enable DVI mode in HDMI core, HDMI_DVI_SEL field of + * HDMI_CTRL register needs to be programmed to 0. */ +static boolean hdmi_msm_is_dvi_mode(void) +{ + /* HDMI_CTRL, HDMI_DVI_SEL */ + return (HDMI_INP_ND(0x0000) & 0x00000002) ? FALSE : TRUE; +} + +static void hdmi_msm_set_mode(boolean power_on) +{ + uint32 reg_val = 0; + if (power_on) { + /* ENABLE */ + reg_val |= 0x00000001; /* Enable the block */ + if (external_common_state->hdmi_sink == 0) { + /* HDMI_DVI_SEL */ + reg_val |= 0x00000002; + /* HDMI_CTRL */ + HDMI_OUTP(0x0000, reg_val); + /* HDMI_DVI_SEL */ + reg_val &= ~0x00000002; + } else + reg_val |= 0x00000002; + } else + reg_val = 0x00000002; + + /* HDMI_CTRL */ + HDMI_OUTP(0x0000, reg_val); + DEV_DBG("HDMI Core: %s\n", power_on ? "Enable" : "Disable"); +} + +static void msm_hdmi_init_ddc(void) +{ + /* 0x0220 HDMI_DDC_SPEED + [31:16] PRESCALE prescale = (m * xtal_frequency) / + (desired_i2c_speed), where m is multiply + factor, default: m = 1 + [1:0] THRESHOLD Select threshold to use to determine whether value + sampled on SDA is a 1 or 0. Specified in terms of the ratio + between the number of sampled ones and the total number of times + SDA is sampled. + * 0x0: >0 + * 0x1: 1/4 of total samples + * 0x2: 1/2 of total samples + * 0x3: 3/4 of total samples */ + /* Configure the Pre-Scale multiplier + * Configure the Threshold */ + HDMI_OUTP_ND(0x0220, (10 << 16) | (2 << 0)); + + /* 0x0224 HDMI_DDC_SETUP */ + HDMI_OUTP_ND(0x0224, 0); + + /* 0x027C HDMI_DDC_REF + [6] REFTIMER_ENABLE Enable the timer + * 0: Disable + * 1: Enable + [15:0] REFTIMER Value to set the register in order to generate + DDC strobe. This register counts on HDCP application clock */ + /* Enable reference timer + * 27 micro-seconds */ + HDMI_OUTP_ND(0x027C, (1 << 16) | (27 << 0)); +} + +static int hdmi_msm_ddc_clear_irq(const char *what) +{ + const uint32 time_out = 0xFFFF; + uint32 time_out_count, reg_val; + + /* clear pending and enable interrupt */ + time_out_count = time_out; + do { + --time_out_count; + /* HDMI_DDC_INT_CTRL[0x0214] + [2] SW_DONE_MK Mask bit for SW_DONE_INT. Set to 1 to enable + interrupt. + [1] SW_DONE_ACK WRITE ONLY. Acknowledge bit for SW_DONE_INT. + Write 1 to clear interrupt. + [0] SW_DONE_INT READ ONLY. SW_DONE interrupt status */ + /* Clear and Enable DDC interrupt */ + /* Write */ + HDMI_OUTP_ND(0x0214, (1 << 2) | (1 << 1)); + /* Read back */ + reg_val = HDMI_INP_ND(0x0214); + } while ((reg_val & 0x1) && time_out_count); + if (!time_out_count) { + DEV_ERR("%s[%s]: timedout\n", __func__, what); + return -ETIMEDOUT; + } + + return 0; +} + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT +static int hdmi_msm_ddc_write(uint32 dev_addr, uint32 offset, + const uint8 *data_buf, uint32 data_len, const char *what) +{ + uint32 reg_val, ndx; + int status = 0, retry = 10; + uint32 time_out_count; + + if (NULL == data_buf) { + status = -EINVAL; + DEV_ERR("%s[%s]: invalid input paramter\n", __func__, what); + goto error; + } + +again: + status = hdmi_msm_ddc_clear_irq(what); + if (status) + goto error; + + /* Ensure Device Address has LSB set to 0 to indicate Slave addr read */ + dev_addr &= 0xFE; + + /* 0x0238 HDMI_DDC_DATA + [31] INDEX_WRITE WRITE ONLY. To write index field, set this bit to + 1 while writing HDMI_DDC_DATA. + [23:16] INDEX Use to set index into DDC buffer for next read or + current write, or to read index of current read or next write. + Writable only when INDEX_WRITE=1. + [15:8] DATA Use to fill or read the DDC buffer + [0] DATA_RW Select whether buffer access will be a read or write. + For writes, address auto-increments on write to HDMI_DDC_DATA. + For reads, address autoincrements on reads to HDMI_DDC_DATA. + * 0: Write + * 1: Read */ + + /* 1. Write to HDMI_I2C_DATA with the following fields set in order to + * handle portion #1 + * DATA_RW = 0x1 (write) + * DATA = linkAddress (primary link address and writing) + * INDEX = 0x0 (initial offset into buffer) + * INDEX_WRITE = 0x1 (setting initial offset) */ + HDMI_OUTP_ND(0x0238, (0x1UL << 31) | (dev_addr << 8)); + + /* 2. Write to HDMI_I2C_DATA with the following fields set in order to + * handle portion #2 + * DATA_RW = 0x0 (write) + * DATA = offsetAddress + * INDEX = 0x0 + * INDEX_WRITE = 0x0 (auto-increment by hardware) */ + HDMI_OUTP_ND(0x0238, offset << 8); + + /* 3. Write to HDMI_I2C_DATA with the following fields set in order to + * handle portion #3 + * DATA_RW = 0x0 (write) + * DATA = data_buf[ndx] + * INDEX = 0x0 + * INDEX_WRITE = 0x0 (auto-increment by hardware) */ + for (ndx = 0; ndx < data_len; ++ndx) + HDMI_OUTP_ND(0x0238, ((uint32)data_buf[ndx]) << 8); + + /* Data setup is complete, now setup the transaction characteristics */ + + /* 0x0228 HDMI_DDC_TRANS0 + [23:16] CNT0 Byte count for first transaction (excluding the first + byte, which is usually the address). + [13] STOP0 Determines whether a stop bit will be sent after the first + transaction + * 0: NO STOP + * 1: STOP + [12] START0 Determines whether a start bit will be sent before the + first transaction + * 0: NO START + * 1: START + [8] STOP_ON_NACK0 Determines whether the current transfer will stop + if a NACK is received during the first transaction (current + transaction always stops). + * 0: STOP CURRENT TRANSACTION, GO TO NEXT TRANSACTION + * 1: STOP ALL TRANSACTIONS, SEND STOP BIT + [0] RW0 Read/write indicator for first transaction - set to 0 for + write, 1 for read. This bit only controls HDMI_DDC behaviour - + the R/W bit in the transaction is programmed into the DDC buffer + as the LSB of the address byte. + * 0: WRITE + * 1: READ */ + + /* 4. Write to HDMI_I2C_TRANSACTION0 with the following fields set in + order to handle characteristics of portion #1 and portion #2 + * RW0 = 0x0 (write) + * START0 = 0x1 (insert START bit) + * STOP0 = 0x0 (do NOT insert STOP bit) + * CNT0 = 0x1 (single byte transaction excluding address) */ + HDMI_OUTP_ND(0x0228, (1 << 12) | (1 << 16)); + + /* 0x022C HDMI_DDC_TRANS1 + [23:16] CNT1 Byte count for second transaction (excluding the first + byte, which is usually the address). + [13] STOP1 Determines whether a stop bit will be sent after the second + transaction + * 0: NO STOP + * 1: STOP + [12] START1 Determines whether a start bit will be sent before the + second transaction + * 0: NO START + * 1: START + [8] STOP_ON_NACK1 Determines whether the current transfer will stop if + a NACK is received during the second transaction (current + transaction always stops). + * 0: STOP CURRENT TRANSACTION, GO TO NEXT TRANSACTION + * 1: STOP ALL TRANSACTIONS, SEND STOP BIT + [0] RW1 Read/write indicator for second transaction - set to 0 for + write, 1 for read. This bit only controls HDMI_DDC behaviour - + the R/W bit in the transaction is programmed into the DDC buffer + as the LSB of the address byte. + * 0: WRITE + * 1: READ */ + + /* 5. Write to HDMI_I2C_TRANSACTION1 with the following fields set in + order to handle characteristics of portion #3 + * RW1 = 0x1 (read) + * START1 = 0x1 (insert START bit) + * STOP1 = 0x1 (insert STOP bit) + * CNT1 = data_len (0xN (write N bytes of data)) + * Byte count for second transition (excluding the first + * Byte which is usually the address) */ + HDMI_OUTP_ND(0x022C, (1 << 13) | ((data_len-1) << 16)); + + /* Trigger the I2C transfer */ + /* 0x020C HDMI_DDC_CTRL + [21:20] TRANSACTION_CNT + Number of transactions to be done in current transfer. + * 0x0: transaction0 only + * 0x1: transaction0, transaction1 + * 0x2: transaction0, transaction1, transaction2 + * 0x3: transaction0, transaction1, transaction2, transaction3 + [3] SW_STATUS_RESET + Write 1 to reset HDMI_DDC_SW_STATUS flags, will reset SW_DONE, + ABORTED, TIMEOUT, SW_INTERRUPTED, BUFFER_OVERFLOW, + STOPPED_ON_NACK, NACK0, NACK1, NACK2, NACK3 + [2] SEND_RESET Set to 1 to send reset sequence (9 clocks with no + data) at start of transfer. This sequence is sent after GO is + written to 1, before the first transaction only. + [1] SOFT_RESET Write 1 to reset DDC controller + [0] GO WRITE ONLY. Write 1 to start DDC transfer. */ + + /* 6. Write to HDMI_I2C_CONTROL to kick off the hardware. + * Note that NOTHING has been transmitted on the DDC lines up to this + * point. + * TRANSACTION_CNT = 0x1 (execute transaction0 followed by + * transaction1) + * GO = 0x1 (kicks off hardware) */ + INIT_COMPLETION(hdmi_msm_state->ddc_sw_done); + HDMI_OUTP_ND(0x020C, (1 << 0) | (1 << 20)); + + time_out_count = wait_for_completion_interruptible_timeout( + &hdmi_msm_state->ddc_sw_done, HZ/2); + HDMI_OUTP_ND(0x0214, 0x2); + if (!time_out_count) { + if (retry-- > 0) { + DEV_INFO("%s[%s]: failed timout, retry=%d\n", __func__, + what, retry); + goto again; + } + status = -ETIMEDOUT; + DEV_ERR("%s[%s]: timedout, DDC SW Status=%08x, HW " + "Status=%08x, Int Ctrl=%08x\n", __func__, what, + HDMI_INP_ND(0x0218), HDMI_INP_ND(0x021C), + HDMI_INP_ND(0x0214)); + goto error; + } + + /* Read DDC status */ + reg_val = HDMI_INP_ND(0x0218); + reg_val &= 0x00001000 | 0x00002000 | 0x00004000 | 0x00008000; + + /* Check if any NACK occurred */ + if (reg_val) { + if (retry > 1) + HDMI_OUTP_ND(0x020C, BIT(3)); /* SW_STATUS_RESET */ + else + HDMI_OUTP_ND(0x020C, BIT(1)); /* SOFT_RESET */ + if (retry-- > 0) { + DEV_DBG("%s[%s]: failed NACK=%08x, retry=%d\n", + __func__, what, reg_val, retry); + msleep(100); + goto again; + } + status = -EIO; + DEV_ERR("%s[%s]: failed NACK: %08x\n", __func__, what, reg_val); + goto error; + } + + DEV_DBG("%s[%s] success\n", __func__, what); + +error: + return status; +} +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + +static int hdmi_msm_ddc_read_retry(uint32 dev_addr, uint32 offset, + uint8 *data_buf, uint32 data_len, uint32 request_len, int retry, + const char *what) +{ + uint32 reg_val, ndx; + int status = 0; + uint32 time_out_count; + int log_retry_fail = retry != 1; + + if (NULL == data_buf) { + status = -EINVAL; + DEV_ERR("%s: invalid input paramter\n", __func__); + goto error; + } + +again: + status = hdmi_msm_ddc_clear_irq(what); + if (status) + goto error; + + /* Ensure Device Address has LSB set to 0 to indicate Slave addr read */ + dev_addr &= 0xFE; + + /* 0x0238 HDMI_DDC_DATA + [31] INDEX_WRITE WRITE ONLY. To write index field, set this bit to + 1 while writing HDMI_DDC_DATA. + [23:16] INDEX Use to set index into DDC buffer for next read or + current write, or to read index of current read or next write. + Writable only when INDEX_WRITE=1. + [15:8] DATA Use to fill or read the DDC buffer + [0] DATA_RW Select whether buffer access will be a read or write. + For writes, address auto-increments on write to HDMI_DDC_DATA. + For reads, address autoincrements on reads to HDMI_DDC_DATA. + * 0: Write + * 1: Read */ + + /* 1. Write to HDMI_I2C_DATA with the following fields set in order to + * handle portion #1 + * DATA_RW = 0x0 (write) + * DATA = linkAddress (primary link address and writing) + * INDEX = 0x0 (initial offset into buffer) + * INDEX_WRITE = 0x1 (setting initial offset) */ + HDMI_OUTP_ND(0x0238, (0x1UL << 31) | (dev_addr << 8)); + + /* 2. Write to HDMI_I2C_DATA with the following fields set in order to + * handle portion #2 + * DATA_RW = 0x0 (write) + * DATA = offsetAddress + * INDEX = 0x0 + * INDEX_WRITE = 0x0 (auto-increment by hardware) */ + HDMI_OUTP_ND(0x0238, offset << 8); + + /* 3. Write to HDMI_I2C_DATA with the following fields set in order to + * handle portion #3 + * DATA_RW = 0x0 (write) + * DATA = linkAddress + 1 (primary link address 0x74 and reading) + * INDEX = 0x0 + * INDEX_WRITE = 0x0 (auto-increment by hardware) */ + HDMI_OUTP_ND(0x0238, (dev_addr | 1) << 8); + + /* Data setup is complete, now setup the transaction characteristics */ + + /* 0x0228 HDMI_DDC_TRANS0 + [23:16] CNT0 Byte count for first transaction (excluding the first + byte, which is usually the address). + [13] STOP0 Determines whether a stop bit will be sent after the first + transaction + * 0: NO STOP + * 1: STOP + [12] START0 Determines whether a start bit will be sent before the + first transaction + * 0: NO START + * 1: START + [8] STOP_ON_NACK0 Determines whether the current transfer will stop + if a NACK is received during the first transaction (current + transaction always stops). + * 0: STOP CURRENT TRANSACTION, GO TO NEXT TRANSACTION + * 1: STOP ALL TRANSACTIONS, SEND STOP BIT + [0] RW0 Read/write indicator for first transaction - set to 0 for + write, 1 for read. This bit only controls HDMI_DDC behaviour - + the R/W bit in the transaction is programmed into the DDC buffer + as the LSB of the address byte. + * 0: WRITE + * 1: READ */ + + /* 4. Write to HDMI_I2C_TRANSACTION0 with the following fields set in + order to handle characteristics of portion #1 and portion #2 + * RW0 = 0x0 (write) + * START0 = 0x1 (insert START bit) + * STOP0 = 0x0 (do NOT insert STOP bit) + * CNT0 = 0x1 (single byte transaction excluding address) */ + HDMI_OUTP_ND(0x0228, (1 << 12) | (1 << 16)); + + /* 0x022C HDMI_DDC_TRANS1 + [23:16] CNT1 Byte count for second transaction (excluding the first + byte, which is usually the address). + [13] STOP1 Determines whether a stop bit will be sent after the second + transaction + * 0: NO STOP + * 1: STOP + [12] START1 Determines whether a start bit will be sent before the + second transaction + * 0: NO START + * 1: START + [8] STOP_ON_NACK1 Determines whether the current transfer will stop if + a NACK is received during the second transaction (current + transaction always stops). + * 0: STOP CURRENT TRANSACTION, GO TO NEXT TRANSACTION + * 1: STOP ALL TRANSACTIONS, SEND STOP BIT + [0] RW1 Read/write indicator for second transaction - set to 0 for + write, 1 for read. This bit only controls HDMI_DDC behaviour - + the R/W bit in the transaction is programmed into the DDC buffer + as the LSB of the address byte. + * 0: WRITE + * 1: READ */ + + /* 5. Write to HDMI_I2C_TRANSACTION1 with the following fields set in + order to handle characteristics of portion #3 + * RW1 = 0x1 (read) + * START1 = 0x1 (insert START bit) + * STOP1 = 0x1 (insert STOP bit) + * CNT1 = data_len (it's 128 (0x80) for a blk read) */ + HDMI_OUTP_ND(0x022C, 1 | (1 << 12) | (1 << 13) | (request_len << 16)); + + /* Trigger the I2C transfer */ + /* 0x020C HDMI_DDC_CTRL + [21:20] TRANSACTION_CNT + Number of transactions to be done in current transfer. + * 0x0: transaction0 only + * 0x1: transaction0, transaction1 + * 0x2: transaction0, transaction1, transaction2 + * 0x3: transaction0, transaction1, transaction2, transaction3 + [3] SW_STATUS_RESET + Write 1 to reset HDMI_DDC_SW_STATUS flags, will reset SW_DONE, + ABORTED, TIMEOUT, SW_INTERRUPTED, BUFFER_OVERFLOW, + STOPPED_ON_NACK, NACK0, NACK1, NACK2, NACK3 + [2] SEND_RESET Set to 1 to send reset sequence (9 clocks with no + data) at start of transfer. This sequence is sent after GO is + written to 1, before the first transaction only. + [1] SOFT_RESET Write 1 to reset DDC controller + [0] GO WRITE ONLY. Write 1 to start DDC transfer. */ + + /* 6. Write to HDMI_I2C_CONTROL to kick off the hardware. + * Note that NOTHING has been transmitted on the DDC lines up to this + * point. + * TRANSACTION_CNT = 0x1 (execute transaction0 followed by + * transaction1) + * SEND_RESET = Set to 1 to send reset sequence + * GO = 0x1 (kicks off hardware) */ + INIT_COMPLETION(hdmi_msm_state->ddc_sw_done); + HDMI_OUTP_ND(0x020C, (1 << 0) | (1 << 20)); + + time_out_count = wait_for_completion_interruptible_timeout( + &hdmi_msm_state->ddc_sw_done, HZ/2); + HDMI_OUTP_ND(0x0214, 0x2); + if (!time_out_count) { + if (retry-- > 0) { + DEV_INFO("%s: failed timout, retry=%d\n", __func__, + retry); + goto again; + } + status = -ETIMEDOUT; + DEV_ERR("%s: timedout(7), DDC SW Status=%08x, HW " + "Status=%08x, Int Ctrl=%08x\n", __func__, + HDMI_INP(0x0218), HDMI_INP(0x021C), HDMI_INP(0x0214)); + goto error; + } + + /* Read DDC status */ + reg_val = HDMI_INP_ND(0x0218); + reg_val &= 0x00001000 | 0x00002000 | 0x00004000 | 0x00008000; + + /* Check if any NACK occurred */ + if (reg_val) { + HDMI_OUTP_ND(0x020C, BIT(3)); /* SW_STATUS_RESET */ + if (retry == 1) + HDMI_OUTP_ND(0x020C, BIT(1)); /* SOFT_RESET */ + if (retry-- > 0) { + DEV_DBG("%s(%s): failed NACK=0x%08x, retry=%d, " + "dev-addr=0x%02x, offset=0x%02x, " + "length=%d\n", __func__, what, + reg_val, retry, dev_addr, + offset, data_len); + goto again; + } + status = -EIO; + if (log_retry_fail) + DEV_ERR("%s(%s): failed NACK=0x%08x, dev-addr=0x%02x, " + "offset=0x%02x, length=%d\n", __func__, what, + reg_val, dev_addr, offset, data_len); + goto error; + } + + /* 0x0238 HDMI_DDC_DATA + [31] INDEX_WRITE WRITE ONLY. To write index field, set this bit to 1 + while writing HDMI_DDC_DATA. + [23:16] INDEX Use to set index into DDC buffer for next read or + current write, or to read index of current read or next write. + Writable only when INDEX_WRITE=1. + [15:8] DATA Use to fill or read the DDC buffer + [0] DATA_RW Select whether buffer access will be a read or write. + For writes, address auto-increments on write to HDMI_DDC_DATA. + For reads, address autoincrements on reads to HDMI_DDC_DATA. + * 0: Write + * 1: Read */ + + /* 8. ALL data is now available and waiting in the DDC buffer. + * Read HDMI_I2C_DATA with the following fields set + * RW = 0x1 (read) + * DATA = BCAPS (this is field where data is pulled from) + * INDEX = 0x3 (where the data has been placed in buffer by hardware) + * INDEX_WRITE = 0x1 (explicitly define offset) */ + /* Write this data to DDC buffer */ + HDMI_OUTP_ND(0x0238, 0x1 | (3 << 16) | (1 << 31)); + + /* Discard first byte */ + HDMI_INP_ND(0x0238); + for (ndx = 0; ndx < data_len; ++ndx) { + reg_val = HDMI_INP_ND(0x0238); + data_buf[ndx] = (uint8) ((reg_val & 0x0000FF00) >> 8); + } + + DEV_DBG("%s[%s] success\n", __func__, what); + +error: + return status; +} + +static int hdmi_msm_ddc_read_edid_seg(uint32 dev_addr, uint32 offset, + uint8 *data_buf, uint32 data_len, uint32 request_len, int retry, + const char *what) +{ + uint32 reg_val, ndx; + int status = 0; + uint32 time_out_count; + int log_retry_fail = retry != 1; + int seg_addr = 0x60, seg_num = 0x01; + + if (NULL == data_buf) { + status = -EINVAL; + DEV_ERR("%s: invalid input paramter\n", __func__); + goto error; + } + +again: + status = hdmi_msm_ddc_clear_irq(what); + if (status) + goto error; + + /* Ensure Device Address has LSB set to 0 to indicate Slave addr read */ + dev_addr &= 0xFE; + + /* 0x0238 HDMI_DDC_DATA + [31] INDEX_WRITE WRITE ONLY. To write index field, set this bit to + 1 while writing HDMI_DDC_DATA. + [23:16] INDEX Use to set index into DDC buffer for next read or + current write, or to read index of current read or next write. + Writable only when INDEX_WRITE=1. + [15:8] DATA Use to fill or read the DDC buffer + [0] DATA_RW Select whether buffer access will be a read or write. + For writes, address auto-increments on write to HDMI_DDC_DATA. + For reads, address autoincrements on reads to HDMI_DDC_DATA. + * 0: Write + * 1: Read */ + + /* 1. Write to HDMI_I2C_DATA with the following fields set in order to + * handle portion #1 + * DATA_RW = 0x0 (write) + * DATA = linkAddress (primary link address and writing) + * INDEX = 0x0 (initial offset into buffer) + * INDEX_WRITE = 0x1 (setting initial offset) */ + HDMI_OUTP_ND(0x0238, (0x1UL << 31) | (seg_addr << 8)); + + /* 2. Write to HDMI_I2C_DATA with the following fields set in order to + * handle portion #2 + * DATA_RW = 0x0 (write) + * DATA = offsetAddress + * INDEX = 0x0 + * INDEX_WRITE = 0x0 (auto-increment by hardware) */ + HDMI_OUTP_ND(0x0238, seg_num << 8); + + /* 3. Write to HDMI_I2C_DATA with the following fields set in order to + * handle portion #3 + * DATA_RW = 0x0 (write) + * DATA = linkAddress + 1 (primary link address 0x74 and reading) + * INDEX = 0x0 + * INDEX_WRITE = 0x0 (auto-increment by hardware) */ + HDMI_OUTP_ND(0x0238, dev_addr << 8); + HDMI_OUTP_ND(0x0238, offset << 8); + HDMI_OUTP_ND(0x0238, (dev_addr | 1) << 8); + + /* Data setup is complete, now setup the transaction characteristics */ + + /* 0x0228 HDMI_DDC_TRANS0 + [23:16] CNT0 Byte count for first transaction (excluding the first + byte, which is usually the address). + [13] STOP0 Determines whether a stop bit will be sent after the first + transaction + * 0: NO STOP + * 1: STOP + [12] START0 Determines whether a start bit will be sent before the + first transaction + * 0: NO START + * 1: START + [8] STOP_ON_NACK0 Determines whether the current transfer will stop + if a NACK is received during the first transaction (current + transaction always stops). + * 0: STOP CURRENT TRANSACTION, GO TO NEXT TRANSACTION + * 1: STOP ALL TRANSACTIONS, SEND STOP BIT + [0] RW0 Read/write indicator for first transaction - set to 0 for + write, 1 for read. This bit only controls HDMI_DDC behaviour - + the R/W bit in the transaction is programmed into the DDC buffer + as the LSB of the address byte. + * 0: WRITE + * 1: READ */ + + /* 4. Write to HDMI_I2C_TRANSACTION0 with the following fields set in + order to handle characteristics of portion #1 and portion #2 + * RW0 = 0x0 (write) + * START0 = 0x1 (insert START bit) + * STOP0 = 0x0 (do NOT insert STOP bit) + * CNT0 = 0x1 (single byte transaction excluding address) */ + HDMI_OUTP_ND(0x0228, (1 << 12) | (1 << 16)); + + /* 0x022C HDMI_DDC_TRANS1 + [23:16] CNT1 Byte count for second transaction (excluding the first + byte, which is usually the address). + [13] STOP1 Determines whether a stop bit will be sent after the second + transaction + * 0: NO STOP + * 1: STOP + [12] START1 Determines whether a start bit will be sent before the + second transaction + * 0: NO START + * 1: START + [8] STOP_ON_NACK1 Determines whether the current transfer will stop if + a NACK is received during the second transaction (current + transaction always stops). + * 0: STOP CURRENT TRANSACTION, GO TO NEXT TRANSACTION + * 1: STOP ALL TRANSACTIONS, SEND STOP BIT + [0] RW1 Read/write indicator for second transaction - set to 0 for + write, 1 for read. This bit only controls HDMI_DDC behaviour - + the R/W bit in the transaction is programmed into the DDC buffer + as the LSB of the address byte. + * 0: WRITE + * 1: READ */ + + /* 5. Write to HDMI_I2C_TRANSACTION1 with the following fields set in + order to handle characteristics of portion #3 + * RW1 = 0x1 (read) + * START1 = 0x1 (insert START bit) + * STOP1 = 0x1 (insert STOP bit) + * CNT1 = data_len (it's 128 (0x80) for a blk read) */ + HDMI_OUTP_ND(0x022C, (1 << 12) | (1 << 16)); + + /* 0x022C HDMI_DDC_TRANS2 + [23:16] CNT1 Byte count for second transaction (excluding the first + byte, which is usually the address). + [13] STOP1 Determines whether a stop bit will be sent after the second + transaction + * 0: NO STOP + * 1: STOP + [12] START1 Determines whether a start bit will be sent before the + second transaction + * 0: NO START + * 1: START + [8] STOP_ON_NACK1 Determines whether the current transfer will stop if + a NACK is received during the second transaction (current + transaction always stops). + * 0: STOP CURRENT TRANSACTION, GO TO NEXT TRANSACTION + * 1: STOP ALL TRANSACTIONS, SEND STOP BIT + [0] RW1 Read/write indicator for second transaction - set to 0 for + write, 1 for read. This bit only controls HDMI_DDC behaviour - + the R/W bit in the transaction is programmed into the DDC buffer + as the LSB of the address byte. + * 0: WRITE + * 1: READ */ + + /* 5. Write to HDMI_I2C_TRANSACTION1 with the following fields set in + order to handle characteristics of portion #3 + * RW1 = 0x1 (read) + * START1 = 0x1 (insert START bit) + * STOP1 = 0x1 (insert STOP bit) + * CNT1 = data_len (it's 128 (0x80) for a blk read) */ + HDMI_OUTP_ND(0x0230, 1 | (1 << 12) | (1 << 13) | (request_len << 16)); + + /* Trigger the I2C transfer */ + /* 0x020C HDMI_DDC_CTRL + [21:20] TRANSACTION_CNT + Number of transactions to be done in current transfer. + * 0x0: transaction0 only + * 0x1: transaction0, transaction1 + * 0x2: transaction0, transaction1, transaction2 + * 0x3: transaction0, transaction1, transaction2, transaction3 + [3] SW_STATUS_RESET + Write 1 to reset HDMI_DDC_SW_STATUS flags, will reset SW_DONE, + ABORTED, TIMEOUT, SW_INTERRUPTED, BUFFER_OVERFLOW, + STOPPED_ON_NACK, NACK0, NACK1, NACK2, NACK3 + [2] SEND_RESET Set to 1 to send reset sequence (9 clocks with no + data) at start of transfer. This sequence is sent after GO is + written to 1, before the first transaction only. + [1] SOFT_RESET Write 1 to reset DDC controller + [0] GO WRITE ONLY. Write 1 to start DDC transfer. */ + + /* 6. Write to HDMI_I2C_CONTROL to kick off the hardware. + * Note that NOTHING has been transmitted on the DDC lines up to this + * point. + * TRANSACTION_CNT = 0x2 (execute transaction0 followed by + * transaction1) + * GO = 0x1 (kicks off hardware) */ + INIT_COMPLETION(hdmi_msm_state->ddc_sw_done); + HDMI_OUTP_ND(0x020C, (1 << 0) | (2 << 20)); + + time_out_count = wait_for_completion_interruptible_timeout( + &hdmi_msm_state->ddc_sw_done, HZ/2); + HDMI_OUTP_ND(0x0214, 0x2); + if (!time_out_count) { + if (retry-- > 0) { + DEV_INFO("%s: failed timout, retry=%d\n", __func__, + retry); + goto again; + } + status = -ETIMEDOUT; + DEV_ERR("%s: timedout(7), DDC SW Status=%08x, HW " + "Status=%08x, Int Ctrl=%08x\n", __func__, + HDMI_INP(0x0218), HDMI_INP(0x021C), HDMI_INP(0x0214)); + goto error; + } + + /* Read DDC status */ + reg_val = HDMI_INP_ND(0x0218); + reg_val &= 0x00001000 | 0x00002000 | 0x00004000 | 0x00008000; + + /* Check if any NACK occurred */ + if (reg_val) { + HDMI_OUTP_ND(0x020C, BIT(3)); /* SW_STATUS_RESET */ + if (retry == 1) + HDMI_OUTP_ND(0x020C, BIT(1)); /* SOFT_RESET */ + if (retry-- > 0) { + DEV_DBG("%s(%s): failed NACK=0x%08x, retry=%d, " + "dev-addr=0x%02x, offset=0x%02x, " + "length=%d\n", __func__, what, + reg_val, retry, dev_addr, + offset, data_len); + goto again; + } + status = -EIO; + if (log_retry_fail) + DEV_ERR("%s(%s): failed NACK=0x%08x, dev-addr=0x%02x, " + "offset=0x%02x, length=%d\n", __func__, what, + reg_val, dev_addr, offset, data_len); + goto error; + } + + /* 0x0238 HDMI_DDC_DATA + [31] INDEX_WRITE WRITE ONLY. To write index field, set this bit to 1 + while writing HDMI_DDC_DATA. + [23:16] INDEX Use to set index into DDC buffer for next read or + current write, or to read index of current read or next write. + Writable only when INDEX_WRITE=1. + [15:8] DATA Use to fill or read the DDC buffer + [0] DATA_RW Select whether buffer access will be a read or write. + For writes, address auto-increments on write to HDMI_DDC_DATA. + For reads, address autoincrements on reads to HDMI_DDC_DATA. + * 0: Write + * 1: Read */ + + /* 8. ALL data is now available and waiting in the DDC buffer. + * Read HDMI_I2C_DATA with the following fields set + * RW = 0x1 (read) + * DATA = BCAPS (this is field where data is pulled from) + * INDEX = 0x3 (where the data has been placed in buffer by hardware) + * INDEX_WRITE = 0x1 (explicitly define offset) */ + /* Write this data to DDC buffer */ + HDMI_OUTP_ND(0x0238, 0x1 | (3 << 16) | (1 << 31)); + + /* Discard first byte */ + HDMI_INP_ND(0x0238); + for (ndx = 0; ndx < data_len; ++ndx) { + reg_val = HDMI_INP_ND(0x0238); + data_buf[ndx] = (uint8) ((reg_val & 0x0000FF00) >> 8); + } + + DEV_DBG("%s[%s] success\n", __func__, what); + +error: + return status; +} + + +static int hdmi_msm_ddc_read(uint32 dev_addr, uint32 offset, uint8 *data_buf, + uint32 data_len, int retry, const char *what, boolean no_align) +{ + int ret = hdmi_msm_ddc_read_retry(dev_addr, offset, data_buf, data_len, + data_len, retry, what); + if (!ret) + return 0; + if (no_align) { + return hdmi_msm_ddc_read_retry(dev_addr, offset, data_buf, + data_len, data_len, retry, what); + } else { + return hdmi_msm_ddc_read_retry(dev_addr, offset, data_buf, + data_len, 32 * ((data_len + 31) / 32), retry, what); + } +} + + +static int hdmi_msm_read_edid_block(int block, uint8 *edid_buf) +{ + int i, rc = 0; + int block_size = 0x80; + + do { + DEV_DBG("EDID: reading block(%d) with block-size=%d\n", + block, block_size); + for (i = 0; i < 0x80; i += block_size) { + /*Read EDID twice with 32bit alighnment too */ + if (block < 2) { + rc = hdmi_msm_ddc_read(0xA0, block*0x80 + i, + edid_buf+i, block_size, 1, + "EDID", FALSE); + } else { + rc = hdmi_msm_ddc_read_edid_seg(0xA0, + block*0x80 + i, edid_buf+i, block_size, + block_size, 1, "EDID"); + } + if (rc) + break; + } + + block_size /= 2; + } while (rc && (block_size >= 16)); + + return rc; +} + +static int hdmi_msm_read_edid(void) +{ + int status; + + msm_hdmi_init_ddc(); + /* Looks like we need to turn on HDMI engine before any + * DDC transaction */ + if (!hdmi_msm_is_power_on()) { + DEV_ERR("%s: failed: HDMI power is off", __func__); + status = -ENXIO; + goto error; + } + + external_common_state->read_edid_block = hdmi_msm_read_edid_block; + status = hdmi_common_read_edid(); + if (!status) + DEV_DBG("EDID: successfully read\n"); + +error: + return status; +} + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT +static void hdcp_auth_info(uint32 auth_info) +{ + switch (auth_info) { + case 0: + DEV_INFO("%s: None", __func__); + break; + case 1: + DEV_INFO("%s: Software Disabled Authentication", __func__); + break; + case 2: + DEV_INFO("%s: An Written", __func__); + break; + case 3: + DEV_INFO("%s: Invalid Aksv", __func__); + break; + case 4: + DEV_INFO("%s: Invalid Bksv", __func__); + break; + case 5: + DEV_INFO("%s: RI Mismatch (including RO)", __func__); + break; + case 6: + DEV_INFO("%s: consecutive Pj Mismatches", __func__); + break; + case 7: + DEV_INFO("%s: HPD Disconnect", __func__); + break; + case 8: + default: + DEV_INFO("%s: Reserved", __func__); + break; + } +} + +static void hdcp_key_state(uint32 key_state) +{ + switch (key_state) { + case 0: + DEV_WARN("%s: No HDCP Keys", __func__); + break; + case 1: + DEV_WARN("%s: Not Checked", __func__); + break; + case 2: + DEV_DBG("%s: Checking", __func__); + break; + case 3: + DEV_DBG("%s: HDCP Keys Valid", __func__); + break; + case 4: + DEV_WARN("%s: AKSV not valid", __func__); + break; + case 5: + DEV_WARN("%s: Checksum Mismatch", __func__); + break; + case 6: + DEV_DBG("%s: Production AKSV" + "with ENABLE_USER_DEFINED_AN=1", __func__); + break; + case 7: + default: + DEV_INFO("%s: Reserved", __func__); + break; + } +} + +static int hdmi_msm_count_one(uint8 *array, uint8 len) +{ + int i, j, count = 0; + for (i = 0; i < len; i++) + for (j = 0; j < 8; j++) + count += (((array[i] >> j) & 0x1) ? 1 : 0); + return count; +} + +static void hdcp_deauthenticate(void) +{ + int hdcp_link_status = HDMI_INP(0x011C); + + external_common_state->hdcp_active = FALSE; + /* 0x0130 HDCP_RESET + [0] LINK0_DEAUTHENTICATE */ + HDMI_OUTP(0x0130, 0x1); + + /* 0x0110 HDCP_CTRL + [8] ENCRYPTION_ENABLE + [0] ENABLE */ + /* encryption_enable = 0 | hdcp block enable = 1 */ + HDMI_OUTP(0x0110, 0x0); + + if (hdcp_link_status & 0x00000004) + hdcp_auth_info((hdcp_link_status & 0x000000F0) >> 4); +} + +static int hdcp_authentication_part1(void) +{ + int ret = 0; + boolean is_match; + boolean is_part1_done = FALSE; + uint32 timeout_count; + uint8 bcaps; + uint8 aksv[5]; + uint32 qfprom_aksv_0, qfprom_aksv_1, link0_aksv_0, link0_aksv_1; + uint8 bksv[5]; + uint32 link0_bksv_0, link0_bksv_1; + uint8 an[8]; + uint32 link0_an_0, link0_an_1; + uint32 hpd_int_status, hpd_int_ctrl; + + + static uint8 buf[0xFF]; + memset(buf, 0, sizeof(buf)); + + if (!is_part1_done) { + is_part1_done = TRUE; + + /* Fetch aksv from QFprom, this info should be public. */ + qfprom_aksv_0 = inpdw(QFPROM_BASE + 0x000060D8); + qfprom_aksv_1 = inpdw(QFPROM_BASE + 0x000060DC); + + /* copy an and aksv to byte arrays for transmission */ + aksv[0] = qfprom_aksv_0 & 0xFF; + aksv[1] = (qfprom_aksv_0 >> 8) & 0xFF; + aksv[2] = (qfprom_aksv_0 >> 16) & 0xFF; + aksv[3] = (qfprom_aksv_0 >> 24) & 0xFF; + aksv[4] = qfprom_aksv_1 & 0xFF; + /* check there are 20 ones in AKSV */ + if (hdmi_msm_count_one(aksv, 5) != 20) { + DEV_ERR("HDCP: AKSV read from QFPROM doesn't have\ + 20 1's and 20 0's, FAIL (AKSV=%02x%08x)\n", + qfprom_aksv_1, qfprom_aksv_0); + ret = -EINVAL; + goto error; + } + DEV_DBG("HDCP: AKSV=%02x%08x\n", qfprom_aksv_1, qfprom_aksv_0); + + /* 0x0288 HDCP_SW_LOWER_AKSV + [31:0] LOWER_AKSV */ + /* 0x0284 HDCP_SW_UPPER_AKSV + [7:0] UPPER_AKSV */ + + /* This is the lower 32 bits of the SW + * injected AKSV value(AKSV[31:0]) read + * from the EFUSE. It is needed for HDCP + * authentication and must be written + * before enabling HDCP. */ + HDMI_OUTP(0x0288, qfprom_aksv_0); + HDMI_OUTP(0x0284, qfprom_aksv_1); + + msm_hdmi_init_ddc(); + + /* Read Bksv 5 bytes at 0x00 in HDCP port */ + ret = hdmi_msm_ddc_read(0x74, 0x00, bksv, 5, 5, "Bksv", TRUE); + if (ret) { + DEV_ERR("%s(%d): Read BKSV failed", __func__, __LINE__); + goto error; + } + /* check there are 20 ones in BKSV */ + if (hdmi_msm_count_one(bksv, 5) != 20) { + DEV_ERR("HDCP: BKSV read from Sink doesn't have\ + 20 1's and 20 0's, FAIL (BKSV=\ + %02x%02x%02x%02x%02x)\n", + bksv[4], bksv[3], bksv[2], bksv[1], bksv[0]); + ret = -EINVAL; + goto error; + } + + link0_bksv_0 = bksv[3]; + link0_bksv_0 = (link0_bksv_0 << 8) | bksv[2]; + link0_bksv_0 = (link0_bksv_0 << 8) | bksv[1]; + link0_bksv_0 = (link0_bksv_0 << 8) | bksv[0]; + link0_bksv_1 = bksv[4]; + DEV_DBG("HDCP: BKSV=%02x%08x\n", link0_bksv_1, link0_bksv_0); + + /* read Bcaps at 0x40 in HDCP Port */ + ret = hdmi_msm_ddc_read(0x74, 0x40, &bcaps, 1, 5, "Bcaps", + TRUE); + if (ret) { + DEV_ERR("%s(%d): Read Bcaps failed", __func__, + __LINE__); + goto error; + } + DEV_DBG("HDCP: Bcaps=%02x\n", bcaps); + + /* HDCP setup prior to HDCP enabled */ + + /* 0x0148 HDCP_RCVPORT_DATA4 + [15:8] LINK0_AINFO + [7:0] LINK0_AKSV_1 */ + /* LINK0_AINFO = 0x2 FEATURE 1.1 on. + * = 0x0 FEATURE 1.1 off*/ + HDMI_OUTP(0x0148, 0x2 << 8); + + /* 0x012C HDCP_ENTROPY_CTRL0 + [31:0] BITS_OF_INFLUENCE_0 */ + /* 0x025C HDCP_ENTROPY_CTRL1 + [31:0] BITS_OF_INFLUENCE_1 */ + HDMI_OUTP(0x012C, 0xB1FFB0FF); + HDMI_OUTP(0x025C, 0xF00DFACE); + + /* 0x0114 HDCP_DEBUG_CTRL + [2] DEBUG_RNG_CIPHER + else default 0 */ + HDMI_OUTP(0x0114, HDMI_INP(0x0114) & 0xFFFFFFFB); + + /* 0x0110 HDCP_CTRL + [8] ENCRYPTION_ENABLE + [0] ENABLE */ + /* encryption_enable | enable */ + HDMI_OUTP(0x0110, (1 << 8) | (1 << 0)); + + /* 0x0118 HDCP_INT_CTRL + * [2] AUTH_SUCCESS_MASK [R/W] Mask bit for\ + * HDCP Authentication + * Success interrupt - set to 1 to enable interrupt + * + * [6] AUTH_FAIL_MASK [R/W] Mask bit for HDCP + * Authentication + * Lost interrupt set to 1 to enable interrupt + * + * [7] AUTH_FAIL_INFO_ACK [W] Acknwledge bit for HDCP + * Auth Failure Info field - write 1 to clear + * + * [10] DDC_XFER_REQ_MASK [R/W] Mask bit for HDCP\ + * DDC Transfer + * Request interrupt - set to 1 to enable interrupt + * + * [14] DDC_XFER_DONE_MASK [R/W] Mask bit for HDCP\ + * DDC Transfer + * done interrupt - set to 1 to enable interrupt */ + /* enable all HDCP ints */ + HDMI_OUTP(0x0118, (1 << 2) | (1 << 6) | (1 << 7)); + + /* 0x011C HDCP_LINK0_STATUS + [8] AN_0_READY + [9] AN_1_READY */ + /* wait for an0 and an1 ready bits to be set in LINK0_STATUS */ + timeout_count = 100; + while (((HDMI_INP_ND(0x011C) & (0x3 << 8)) != (0x3 << 8)) + && timeout_count--) + msleep(20); + if (!timeout_count) { + ret = -ETIMEDOUT; + DEV_ERR("%s(%d): timedout, An0=%d, An1=%d\n", + __func__, __LINE__, + (HDMI_INP_ND(0x011C) & BIT(8)) >> 8, + (HDMI_INP_ND(0x011C) & BIT(9)) >> 9); + goto error; + } + + /* 0x0168 HDCP_RCVPORT_DATA12 + [23:8] BSTATUS + [7:0] BCAPS */ + HDMI_OUTP(0x0168, bcaps); + + /* 0x014C HDCP_RCVPORT_DATA5 + [31:0] LINK0_AN_0 */ + /* read an0 calculation */ + link0_an_0 = HDMI_INP(0x014C); + + /* 0x0150 HDCP_RCVPORT_DATA6 + [31:0] LINK0_AN_1 */ + /* read an1 calculation */ + link0_an_1 = HDMI_INP(0x0150); + + /* three bits 28..30 */ + hdcp_key_state((HDMI_INP(0x011C) >> 28) & 0x7); + + /* 0x0144 HDCP_RCVPORT_DATA3 + [31:0] LINK0_AKSV_0 public key + 0x0148 HDCP_RCVPORT_DATA4 + [15:8] LINK0_AINFO + [7:0] LINK0_AKSV_1 public key */ + link0_aksv_0 = HDMI_INP(0x0144); + link0_aksv_1 = HDMI_INP(0x0148); + + /* copy an and aksv to byte arrays for transmission */ + aksv[0] = link0_aksv_0 & 0xFF; + aksv[1] = (link0_aksv_0 >> 8) & 0xFF; + aksv[2] = (link0_aksv_0 >> 16) & 0xFF; + aksv[3] = (link0_aksv_0 >> 24) & 0xFF; + aksv[4] = link0_aksv_1 & 0xFF; + + an[0] = link0_an_0 & 0xFF; + an[1] = (link0_an_0 >> 8) & 0xFF; + an[2] = (link0_an_0 >> 16) & 0xFF; + an[3] = (link0_an_0 >> 24) & 0xFF; + an[4] = link0_an_1 & 0xFF; + an[5] = (link0_an_1 >> 8) & 0xFF; + an[6] = (link0_an_1 >> 16) & 0xFF; + an[7] = (link0_an_1 >> 24) & 0xFF; + + /* Write An 8 bytes to offset 0x18 */ + ret = hdmi_msm_ddc_write(0x74, 0x18, an, 8, "An"); + if (ret) { + DEV_ERR("%s(%d): Write An failed", __func__, __LINE__); + goto error; + } + + /* Write Aksv 5 bytes to offset 0x10 */ + ret = hdmi_msm_ddc_write(0x74, 0x10, aksv, 5, "Aksv"); + if (ret) { + DEV_ERR("%s(%d): Write Aksv failed", __func__, + __LINE__); + goto error; + } + DEV_DBG("HDCP: Link0-AKSV=%02x%08x\n", + link0_aksv_1 & 0xFF, link0_aksv_0); + + /* 0x0134 HDCP_RCVPORT_DATA0 + [31:0] LINK0_BKSV_0 */ + HDMI_OUTP(0x0134, link0_bksv_0); + /* 0x0138 HDCP_RCVPORT_DATA1 + [31:0] LINK0_BKSV_1 */ + HDMI_OUTP(0x0138, link0_bksv_1); + DEV_DBG("HDCP: Link0-BKSV=%02x%08x\n", link0_bksv_1, + link0_bksv_0); + + /* HDMI_HPD_INT_STATUS[0x0250] */ + hpd_int_status = HDMI_INP_ND(0x0250); + /* HDMI_HPD_INT_CTRL[0x0254] */ + hpd_int_ctrl = HDMI_INP_ND(0x0254); + DEV_DBG("[SR-DEUG]: HPD_INTR_CTRL=[%u] HPD_INTR_STATUS=[%u]\ + before reading R0'\n", hpd_int_ctrl, hpd_int_status); + + /* + * HDCP Compliace Test case 1B-01: + * Wait here until all the ksv bytes have been + * read from the KSV FIFO register. + */ + msleep(125); + + /* Reading R0' 2 bytes at offset 0x08 */ + ret = hdmi_msm_ddc_read(0x74, 0x08, buf, 2, 5, "RO'", TRUE); + if (ret) { + DEV_ERR("%s(%d): Read RO's failed", __func__, + __LINE__); + goto error; + } + + /* 0x013C HDCP_RCVPORT_DATA2_0 + [15:0] LINK0_RI */ + HDMI_OUTP(0x013C, (((uint32)buf[1]) << 8) | buf[0]); + DEV_DBG("HDCP: R0'=%02x%02x\n", buf[1], buf[0]); + + INIT_COMPLETION(hdmi_msm_state->hdcp_success_done); + timeout_count = wait_for_completion_interruptible_timeout( + &hdmi_msm_state->hdcp_success_done, HZ*2); + + if (!timeout_count) { + ret = -ETIMEDOUT; + is_match = HDMI_INP(0x011C) & BIT(12); + DEV_ERR("%s(%d): timedout, Link0=<%s>\n", __func__, + __LINE__, + is_match ? "RI_MATCH" : "No RI Match INTR in time"); + if (!is_match) + goto error; + } + + /* 0x011C HDCP_LINK0_STATUS + [12] RI_MATCHES [0] MISMATCH, [1] MATCH + [0] AUTH_SUCCESS */ + /* Checking for RI, R0 Match */ + /* RI_MATCHES */ + if ((HDMI_INP(0x011C) & BIT(12)) != BIT(12)) { + ret = -EINVAL; + DEV_ERR("%s: HDCP_LINK0_STATUS[RI_MATCHES]: MISMATCH\n", + __func__); + goto error; + } + + DEV_INFO("HDCP: authentication part I, successful\n"); + is_part1_done = FALSE; + return 0; +error: + DEV_ERR("[%s]: HDCP Reauthentication\n", __func__); + is_part1_done = FALSE; + return ret; + } else { + return 1; + } +} + +static int hdmi_msm_transfer_v_h(void) +{ + /* Read V'.HO 4 Byte at offset 0x20 */ + char what[20]; + int ret; + uint8 buf[4]; + + snprintf(what, sizeof(what), "V' H0"); + ret = hdmi_msm_ddc_read(0x74, 0x20, buf, 4, 5, what, TRUE); + if (ret) { + DEV_ERR("%s: Read %s failed", __func__, what); + return ret; + } + DEV_DBG("buf[0]= %x , buf[1] = %x , buf[2] = %x , buf[3] = %x\n ", + buf[0] , buf[1] , buf[2] , buf[3]); + + /* 0x0154 HDCP_RCVPORT_DATA7 + [31:0] V_HO */ + HDMI_OUTP(0x0154 , + (buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0])); + + snprintf(what, sizeof(what), "V' H1"); + ret = hdmi_msm_ddc_read(0x74, 0x24, buf, 4, 5, what, TRUE); + if (ret) { + DEV_ERR("%s: Read %s failed", __func__, what); + return ret; + } + DEV_DBG("buf[0]= %x , buf[1] = %x , buf[2] = %x , buf[3] = %x\n ", + buf[0] , buf[1] , buf[2] , buf[3]); + + /* 0x0158 HDCP_RCVPORT_ DATA8 + [31:0] V_H1 */ + HDMI_OUTP(0x0158, + (buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0])); + + + snprintf(what, sizeof(what), "V' H2"); + ret = hdmi_msm_ddc_read(0x74, 0x28, buf, 4, 5, what, TRUE); + if (ret) { + DEV_ERR("%s: Read %s failed", __func__, what); + return ret; + } + DEV_DBG("buf[0]= %x , buf[1] = %x , buf[2] = %x , buf[3] = %x\n ", + buf[0] , buf[1] , buf[2] , buf[3]); + + /* 0x015c HDCP_RCVPORT_DATA9 + [31:0] V_H2 */ + HDMI_OUTP(0x015c , + (buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0])); + + snprintf(what, sizeof(what), "V' H3"); + ret = hdmi_msm_ddc_read(0x74, 0x2c, buf, 4, 5, what, TRUE); + if (ret) { + DEV_ERR("%s: Read %s failed", __func__, what); + return ret; + } + DEV_DBG("buf[0]= %x , buf[1] = %x , buf[2] = %x , buf[3] = %x\n ", + buf[0] , buf[1] , buf[2] , buf[3]); + + /* 0x0160 HDCP_RCVPORT_DATA10 + [31:0] V_H3 */ + HDMI_OUTP(0x0160, + (buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0])); + + snprintf(what, sizeof(what), "V' H4"); + ret = hdmi_msm_ddc_read(0x74, 0x30, buf, 4, 5, what, TRUE); + if (ret) { + DEV_ERR("%s: Read %s failed", __func__, what); + return ret; + } + DEV_DBG("buf[0]= %x , buf[1] = %x , buf[2] = %x , buf[3] = %x\n ", + buf[0] , buf[1] , buf[2] , buf[3]); + /* 0x0164 HDCP_RCVPORT_DATA11 + [31:0] V_H4 */ + HDMI_OUTP(0x0164, + (buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0])); + + return 0; +} + +static int hdcp_authentication_part2(void) +{ + int ret = 0; + uint32 timeout_count; + int i = 0; + int cnt = 0; + uint bstatus; + uint8 bcaps; + uint32 down_stream_devices; + uint32 ksv_bytes; + + static uint8 buf[0xFF]; + static uint8 kvs_fifo[5 * 127]; + + boolean max_devs_exceeded = 0; + boolean max_cascade_exceeded = 0; + + boolean ksv_done = FALSE; + + memset(buf, 0, sizeof(buf)); + memset(kvs_fifo, 0, sizeof(kvs_fifo)); + + /* wait until READY bit is set in bcaps */ + timeout_count = 50; + do { + timeout_count--; + /* read bcaps 1 Byte at offset 0x40 */ + ret = hdmi_msm_ddc_read(0x74, 0x40, &bcaps, 1, 1, + "Bcaps", FALSE); + if (ret) { + DEV_ERR("%s(%d): Read Bcaps failed", __func__, + __LINE__); + goto error; + } + msleep(100); + } while ((0 == (bcaps & 0x20)) && timeout_count); /* READY (Bit 5) */ + if (!timeout_count) { + ret = -ETIMEDOUT; + DEV_ERR("%s:timedout(1)", __func__); + goto error; + } + + /* read bstatus 2 bytes at offset 0x41 */ + + ret = hdmi_msm_ddc_read(0x74, 0x41, buf, 2, 5, "Bstatus", FALSE); + if (ret) { + DEV_ERR("%s(%d): Read Bstatus failed", __func__, __LINE__); + goto error; + } + bstatus = buf[1]; + bstatus = (bstatus << 8) | buf[0]; + /* 0x0168 DCP_RCVPORT_DATA12 + [7:0] BCAPS + [23:8 BSTATUS */ + HDMI_OUTP(0x0168, bcaps | (bstatus << 8)); + /* BSTATUS [6:0] DEVICE_COUNT Number of HDMI device attached to repeater + * - see HDCP spec */ + down_stream_devices = bstatus & 0x7F; + + if (down_stream_devices == 0x0) { + /* There isn't any devices attaced to the Repeater */ + DEV_ERR("%s: there isn't any devices attached to the " + "Repeater\n", __func__); + ret = -EINVAL; + goto error; + } + + /* + * HDCP Compliance 1B-05: + * Check if no. of devices connected to repeater + * exceed max_devices_connected from bit 7 of Bstatus. + */ + max_devs_exceeded = (bstatus & 0x80) >> 7; + if (max_devs_exceeded == 0x01) { + DEV_ERR("%s: Number of devs connected to repeater " + "exceeds max_devs\n", __func__); + ret = -EINVAL; + goto hdcp_error; + } + + /* + * HDCP Compliance 1B-06: + * Check if no. of cascade connected to repeater + * exceed max_cascade_connected from bit 11 of Bstatus. + */ + max_cascade_exceeded = (bstatus & 0x800) >> 11; + if (max_cascade_exceeded == 0x01) { + DEV_ERR("%s: Number of cascade connected to repeater " + "exceeds max_cascade\n", __func__); + ret = -EINVAL; + goto hdcp_error; + } + + /* Read KSV FIFO over DDC + * Key Slection vector FIFO + * Used to pull downstream KSVs from HDCP Repeaters. + * All bytes (DEVICE_COUNT * 5) must be read in a single, + * auto incrementing access. + * All bytes read as 0x00 for HDCP Receivers that are not + * HDCP Repeaters (REPEATER == 0). */ + ksv_bytes = 5 * down_stream_devices; + /* Reading KSV FIFO / KSV FIFO */ + ksv_done = FALSE; + + ret = hdmi_msm_ddc_read(0x74, 0x43, kvs_fifo, ksv_bytes, 5, + "KSV FIFO", TRUE); + do { + if (ret) { + DEV_ERR("%s(%d): Read KSV FIFO failed", + __func__, __LINE__); + /* + * HDCP Compliace Test case 1B-01: + * Wait here until all the ksv bytes have been + * read from the KSV FIFO register. + */ + msleep(25); + } else { + ksv_done = TRUE; + } + cnt++; + } while (!ksv_done && cnt != 20); + + if (ksv_done == FALSE) + goto error; + + ret = hdmi_msm_transfer_v_h(); + if (ret) + goto error; + + /* Next: Write KSV FIFO to HDCP_SHA_DATA. + * This is done 1 byte at time starting with the LSB. + * On the very last byte write, + * the HDCP_SHA_DATA_DONE bit[0] + */ + + /* 0x023C HDCP_SHA_CTRL + [0] RESET [0] Enable, [1] Reset + [4] SELECT [0] DIGA_HDCP, [1] DIGB_HDCP */ + /* reset SHA engine */ + HDMI_OUTP(0x023C, 1); + /* enable SHA engine, SEL=DIGA_HDCP */ + HDMI_OUTP(0x023C, 0); + + for (i = 0; i < ksv_bytes - 1; i++) { + /* Write KSV byte and do not set DONE bit[0] */ + HDMI_OUTP_ND(0x0244, kvs_fifo[i] << 16); + } + /* Write l to DONE bit[0] */ + HDMI_OUTP_ND(0x0244, (kvs_fifo[ksv_bytes - 1] << 16) | 0x1); + + /* 0x0240 HDCP_SHA_STATUS + [4] COMP_DONE */ + /* Now wait for HDCP_SHA_COMP_DONE */ + timeout_count = 100; + while ((0x10 != (HDMI_INP_ND(0x0240) & 0x10)) && timeout_count--) + msleep(20); + if (!timeout_count) { + ret = -ETIMEDOUT; + DEV_ERR("%s(%d): timedout", __func__, __LINE__); + goto error; + } + + /* 0x011C HDCP_LINK0_STATUS + [20] V_MATCHES */ + timeout_count = 100; + while (((HDMI_INP_ND(0x011C) & (1 << 20)) != (1 << 20)) + && timeout_count--) + msleep(20); + if (!timeout_count) { + ret = -ETIMEDOUT; + DEV_ERR("%s(%d): timedout", __func__, __LINE__); + goto error; + } + + DEV_INFO("HDCP: authentication part II, successful\n"); + +hdcp_error: +error: + return ret; +} + +static int hdcp_authentication_part3(uint32 found_repeater) +{ + int ret = 0; + int poll = 3000; + while (poll) { + /* 0x011C HDCP_LINK0_STATUS + [30:28] KEYS_STATE = 3 = "Valid" + [24] RO_COMPUTATION_DONE [0] Not Done, [1] Done + [20] V_MATCHES [0] Mismtach, [1] Match + [12] RI_MATCHES [0] Mismatch, [1] Match + [0] AUTH_SUCCESS */ + if (HDMI_INP_ND(0x011C) != (0x31001001 | + (found_repeater << 20))) { + DEV_ERR("HDCP: autentication part III, FAILED, " + "Link Status=%08x\n", HDMI_INP(0x011C)); + ret = -EINVAL; + goto error; + } + poll--; + } + + DEV_INFO("HDCP: authentication part III, successful\n"); + +error: + return ret; +} + +static void hdmi_msm_hdcp_enable(void) +{ + int ret = 0; + uint8 bcaps; + uint32 found_repeater = 0x0; + char *envp[2]; + + if (!hdmi_msm_has_hdcp()) + return; + + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->hdcp_activating = TRUE; + mutex_unlock(&hdmi_msm_state_mutex); + + fill_black_screen(); + + mutex_lock(&hdcp_auth_state_mutex); + /* + * Initialize this to zero here to make + * sure HPD has not happened yet + */ + hdmi_msm_state->hpd_during_auth = FALSE; + /* This flag prevents other threads from re-authenticating + * after we've just authenticated (i.e., finished part3) + * We probably need to protect this in a mutex lock */ + hdmi_msm_state->full_auth_done = FALSE; + mutex_unlock(&hdcp_auth_state_mutex); + + /* PART I Authentication*/ + ret = hdcp_authentication_part1(); + if (ret) + goto error; + + /* PART II Authentication*/ + /* read Bcaps at 0x40 in HDCP Port */ + ret = hdmi_msm_ddc_read(0x74, 0x40, &bcaps, 1, 5, "Bcaps", FALSE); + if (ret) { + DEV_ERR("%s(%d): Read Bcaps failed\n", __func__, __LINE__); + goto error; + } + DEV_DBG("HDCP: Bcaps=0x%02x (%s)\n", bcaps, + (bcaps & BIT(6)) ? "repeater" : "no repeater"); + + /* if REPEATER (Bit 6), perform Part2 Authentication */ + if (bcaps & BIT(6)) { + found_repeater = 0x1; + ret = hdcp_authentication_part2(); + if (ret) + goto error; + } else + DEV_INFO("HDCP: authentication part II skipped, no repeater\n"); + + /* PART III Authentication*/ + ret = hdcp_authentication_part3(found_repeater); + if (ret) + goto error; + + unfill_black_screen(); + + external_common_state->hdcp_active = TRUE; + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->hdcp_activating = FALSE; + mutex_unlock(&hdmi_msm_state_mutex); + + mutex_lock(&hdcp_auth_state_mutex); + /* + * This flag prevents other threads from re-authenticating + * after we've just authenticated (i.e., finished part3) + */ + hdmi_msm_state->full_auth_done = TRUE; + mutex_unlock(&hdcp_auth_state_mutex); + + if (!hdmi_msm_is_dvi_mode()) { + DEV_INFO("HDMI HPD: sense : send HDCP_PASS\n"); + envp[0] = "HDCP_STATE=PASS"; + envp[1] = NULL; + kobject_uevent_env(external_common_state->uevent_kobj, + KOBJ_CHANGE, envp); + } + return; + +error: + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->hdcp_activating = FALSE; + mutex_unlock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->hpd_during_auth) { + DEV_WARN("Calling Deauthentication: HPD occured during\ + authentication from [%s]\n", __func__); + hdcp_deauthenticate(); + mutex_lock(&hdcp_auth_state_mutex); + hdmi_msm_state->hpd_during_auth = FALSE; + mutex_unlock(&hdcp_auth_state_mutex); + } else { + DEV_WARN("[DEV_DBG]: Calling reauth from [%s]\n", __func__); + if (hdmi_msm_state->panel_power_on) + queue_work(hdmi_work_queue, + &hdmi_msm_state->hdcp_reauth_work); + } +} +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + +static void hdmi_msm_video_setup(int video_format) +{ + uint32 total_v = 0; + uint32 total_h = 0; + uint32 start_h = 0; + uint32 end_h = 0; + uint32 start_v = 0; + uint32 end_v = 0; + const struct hdmi_disp_mode_timing_type *timing = + hdmi_common_get_supported_mode(video_format); + + /* timing register setup */ + if (timing == NULL) { + DEV_ERR("video format not supported: %d\n", video_format); + return; + } + + /* Hsync Total and Vsync Total */ + total_h = timing->active_h + timing->front_porch_h + + timing->back_porch_h + timing->pulse_width_h - 1; + total_v = timing->active_v + timing->front_porch_v + + timing->back_porch_v + timing->pulse_width_v - 1; + /* 0x02C0 HDMI_TOTAL + [27:16] V_TOTAL Vertical Total + [11:0] H_TOTAL Horizontal Total */ + HDMI_OUTP(0x02C0, ((total_v << 16) & 0x0FFF0000) + | ((total_h << 0) & 0x00000FFF)); + + /* Hsync Start and Hsync End */ + start_h = timing->back_porch_h + timing->pulse_width_h; + end_h = (total_h + 1) - timing->front_porch_h; + /* 0x02B4 HDMI_ACTIVE_H + [27:16] END Horizontal end + [11:0] START Horizontal start */ + HDMI_OUTP(0x02B4, ((end_h << 16) & 0x0FFF0000) + | ((start_h << 0) & 0x00000FFF)); + + start_v = timing->back_porch_v + timing->pulse_width_v - 1; + end_v = total_v - timing->front_porch_v; + /* 0x02B8 HDMI_ACTIVE_V + [27:16] END Vertical end + [11:0] START Vertical start */ + HDMI_OUTP(0x02B8, ((end_v << 16) & 0x0FFF0000) + | ((start_v << 0) & 0x00000FFF)); + + if (timing->interlaced) { + /* 0x02C4 HDMI_V_TOTAL_F2 + [11:0] V_TOTAL_F2 Vertical total for field2 */ + HDMI_OUTP(0x02C4, ((total_v + 1) << 0) & 0x00000FFF); + + /* 0x02BC HDMI_ACTIVE_V_F2 + [27:16] END_F2 Vertical end for field2 + [11:0] START_F2 Vertical start for Field2 */ + HDMI_OUTP(0x02BC, + (((start_v + 1) << 0) & 0x00000FFF) + | (((end_v + 1) << 16) & 0x0FFF0000)); + } else { + /* HDMI_V_TOTAL_F2 */ + HDMI_OUTP(0x02C4, 0); + /* HDMI_ACTIVE_V_F2 */ + HDMI_OUTP(0x02BC, 0); + } + + hdmi_frame_ctrl_cfg(timing); +} + +struct hdmi_msm_audio_acr { + uint32 n; /* N parameter for clock regeneration */ + uint32 cts; /* CTS parameter for clock regeneration */ +}; + +struct hdmi_msm_audio_arcs { + uint32 pclk; + struct hdmi_msm_audio_acr lut[MSM_HDMI_SAMPLE_RATE_MAX]; +}; + +#define HDMI_MSM_AUDIO_ARCS(pclk, ...) { pclk, __VA_ARGS__ } + +/* Audio constants lookup table for hdmi_msm_audio_acr_setup */ +/* Valid Pixel-Clock rates: 25.2MHz, 27MHz, 27.03MHz, 74.25MHz, 148.5MHz */ +static const struct hdmi_msm_audio_arcs hdmi_msm_audio_acr_lut[] = { + /* 25.200MHz */ + HDMI_MSM_AUDIO_ARCS(25200, { + {4096, 25200}, {6272, 28000}, {6144, 25200}, {12544, 28000}, + {12288, 25200}, {25088, 28000}, {24576, 25200} }), + /* 27.000MHz */ + HDMI_MSM_AUDIO_ARCS(27000, { + {4096, 27000}, {6272, 30000}, {6144, 27000}, {12544, 30000}, + {12288, 27000}, {25088, 30000}, {24576, 27000} }), + /* 27.030MHz */ + HDMI_MSM_AUDIO_ARCS(27030, { + {4096, 27030}, {6272, 30030}, {6144, 27030}, {12544, 30030}, + {12288, 27030}, {25088, 30030}, {24576, 27030} }), + /* 74.250MHz */ + HDMI_MSM_AUDIO_ARCS(74250, { + {4096, 74250}, {6272, 82500}, {6144, 74250}, {12544, 82500}, + {12288, 74250}, {25088, 82500}, {24576, 74250} }), + /* 148.500MHz */ + HDMI_MSM_AUDIO_ARCS(148500, { + {4096, 148500}, {6272, 165000}, {6144, 148500}, {12544, 165000}, + {12288, 148500}, {25088, 165000}, {24576, 148500} }), +}; + +static void hdmi_msm_audio_acr_setup(boolean enabled, int video_format, + int audio_sample_rate, int num_of_channels) +{ + /* Read first before writing */ + /* HDMI_ACR_PKT_CTRL[0x0024] */ + uint32 acr_pck_ctrl_reg = HDMI_INP(0x0024); + + if (enabled) { + const struct hdmi_disp_mode_timing_type *timing = + hdmi_common_get_supported_mode(video_format); + const struct hdmi_msm_audio_arcs *audio_arc = + &hdmi_msm_audio_acr_lut[0]; + const int lut_size = sizeof(hdmi_msm_audio_acr_lut) + /sizeof(*hdmi_msm_audio_acr_lut); + uint32 i, n, cts, layout, multiplier, aud_pck_ctrl_2_reg; + + if (timing == NULL) { + DEV_WARN("%s: video format %d not supported\n", + __func__, video_format); + return; + } + + for (i = 0; i < lut_size; + audio_arc = &hdmi_msm_audio_acr_lut[++i]) { + if (audio_arc->pclk == timing->pixel_freq) + break; + } + if (i >= lut_size) { + DEV_WARN("%s: pixel clock %d not supported\n", __func__, + timing->pixel_freq); + return; + } + + n = audio_arc->lut[audio_sample_rate].n; + cts = audio_arc->lut[audio_sample_rate].cts; + layout = (MSM_HDMI_AUDIO_CHANNEL_2 == num_of_channels) ? 0 : 1; + + if ((MSM_HDMI_SAMPLE_RATE_192KHZ == audio_sample_rate) || + (MSM_HDMI_SAMPLE_RATE_176_4KHZ == audio_sample_rate)) { + multiplier = 4; + n >>= 2; /* divide N by 4 and use multiplier */ + } else if ((MSM_HDMI_SAMPLE_RATE_96KHZ == audio_sample_rate) || + (MSM_HDMI_SAMPLE_RATE_88_2KHZ == audio_sample_rate)) { + multiplier = 2; + n >>= 1; /* divide N by 2 and use multiplier */ + } else { + multiplier = 1; + } + DEV_DBG("%s: n=%u, cts=%u, layout=%u\n", __func__, n, cts, + layout); + + /* AUDIO_PRIORITY | SOURCE */ + acr_pck_ctrl_reg |= 0x80000100; + /* N_MULTIPLE(multiplier) */ + acr_pck_ctrl_reg |= (multiplier & 7) << 16; + + if ((MSM_HDMI_SAMPLE_RATE_48KHZ == audio_sample_rate) || + (MSM_HDMI_SAMPLE_RATE_96KHZ == audio_sample_rate) || + (MSM_HDMI_SAMPLE_RATE_192KHZ == audio_sample_rate)) { + /* SELECT(3) */ + acr_pck_ctrl_reg |= 3 << 4; + /* CTS_48 */ + cts <<= 12; + + /* CTS: need to determine how many fractional bits */ + /* HDMI_ACR_48_0 */ + HDMI_OUTP(0x00D4, cts); + /* N */ + /* HDMI_ACR_48_1 */ + HDMI_OUTP(0x00D8, n); + } else if ((MSM_HDMI_SAMPLE_RATE_44_1KHZ == audio_sample_rate) + || (MSM_HDMI_SAMPLE_RATE_88_2KHZ == + audio_sample_rate) + || (MSM_HDMI_SAMPLE_RATE_176_4KHZ == + audio_sample_rate)) { + /* SELECT(2) */ + acr_pck_ctrl_reg |= 2 << 4; + /* CTS_44 */ + cts <<= 12; + + /* CTS: need to determine how many fractional bits */ + /* HDMI_ACR_44_0 */ + HDMI_OUTP(0x00CC, cts); + /* N */ + /* HDMI_ACR_44_1 */ + HDMI_OUTP(0x00D0, n); + } else { /* default to 32k */ + /* SELECT(1) */ + acr_pck_ctrl_reg |= 1 << 4; + /* CTS_32 */ + cts <<= 12; + + /* CTS: need to determine how many fractional bits */ + /* HDMI_ACR_32_0 */ + HDMI_OUTP(0x00C4, cts); + /* N */ + /* HDMI_ACR_32_1 */ + HDMI_OUTP(0x00C8, n); + } + /* Payload layout depends on number of audio channels */ + /* LAYOUT_SEL(layout) */ + aud_pck_ctrl_2_reg = 1 | (layout << 1); + /* override | layout */ + /* HDMI_AUDIO_PKT_CTRL2[0x00044] */ + HDMI_OUTP(0x00044, aud_pck_ctrl_2_reg); + + /* SEND | CONT */ + acr_pck_ctrl_reg |= 0x00000003; + } else { + /* ~(SEND | CONT) */ + acr_pck_ctrl_reg &= ~0x00000003; + } + /* HDMI_ACR_PKT_CTRL[0x0024] */ + HDMI_OUTP(0x0024, acr_pck_ctrl_reg); +} + +static void hdmi_msm_outpdw_chk(uint32 offset, uint32 data) +{ + uint32 check, i = 0; + +#ifdef DEBUG + HDMI_OUTP(offset, data); +#endif + do { + outpdw(MSM_HDMI_BASE+offset, data); + check = inpdw(MSM_HDMI_BASE+offset); + } while (check != data && i++ < 10); + + if (check != data) + DEV_ERR("%s: failed addr=%08x, data=%x, check=%x", + __func__, offset, data, check); +} + +static void hdmi_msm_rmw32or(uint32 offset, uint32 data) +{ + uint32 reg_data; + reg_data = inpdw(MSM_HDMI_BASE+offset); + reg_data = inpdw(MSM_HDMI_BASE+offset); + hdmi_msm_outpdw_chk(offset, reg_data | data); +} + + +#define HDMI_AUDIO_CFG 0x01D0 +#define HDMI_AUDIO_ENGINE_ENABLE 1 +#define HDMI_AUDIO_FIFO_MASK 0x000000F0 +#define HDMI_AUDIO_FIFO_WATERMARK_SHIFT 4 +#define HDMI_AUDIO_FIFO_MAX_WATER_MARK 8 + + +int hdmi_audio_enable(bool on , u32 fifo_water_mark) +{ + u32 hdmi_audio_config; + + hdmi_audio_config = HDMI_INP(HDMI_AUDIO_CFG); + + if (on) { + + if (fifo_water_mark > HDMI_AUDIO_FIFO_MAX_WATER_MARK) { + pr_err("%s : HDMI audio fifo water mark can not be more" + " than %u\n", __func__, + HDMI_AUDIO_FIFO_MAX_WATER_MARK); + return -EINVAL; + } + + /* + * Enable HDMI Audio engine. + * MUST be enabled after Audio DMA is enabled. + */ + hdmi_audio_config &= ~(HDMI_AUDIO_FIFO_MASK); + + hdmi_audio_config |= (HDMI_AUDIO_ENGINE_ENABLE | + (fifo_water_mark << HDMI_AUDIO_FIFO_WATERMARK_SHIFT)); + + } else + hdmi_audio_config &= ~(HDMI_AUDIO_ENGINE_ENABLE); + + HDMI_OUTP(HDMI_AUDIO_CFG, hdmi_audio_config); + + return 0; +} +EXPORT_SYMBOL(hdmi_audio_enable); + +static void hdmi_msm_audio_info_setup(boolean enabled, int num_of_channels, + int level_shift, boolean down_mix) +{ + uint32 channel_allocation = 0; /* Default to FR,FL */ + uint32 channel_count = 1; /* Default to 2 channels + -> See Table 17 in CEA-D spec */ + uint32 check_sum, audio_info_0_reg, audio_info_1_reg; + uint32 audio_info_ctrl_reg; + + /* Please see table 20 Audio InfoFrame in HDMI spec + FL = front left + FC = front Center + FR = front right + FLC = front left center + FRC = front right center + RL = rear left + RC = rear center + RR = rear right + RLC = rear left center + RRC = rear right center + LFE = low frequency effect + */ + + /* Read first then write because it is bundled with other controls */ + /* HDMI_INFOFRAME_CTRL0[0x002C] */ + audio_info_ctrl_reg = HDMI_INP(0x002C); + + if (enabled) { + switch (num_of_channels) { + case MSM_HDMI_AUDIO_CHANNEL_2: + break; + case MSM_HDMI_AUDIO_CHANNEL_4: + channel_count = 3; + /* FC,LFE,FR,FL */ + channel_allocation = 0x3; + break; + case MSM_HDMI_AUDIO_CHANNEL_6: + channel_count = 5; + /* RR,RL,FC,LFE,FR,FL */ + channel_allocation = 0xB; + break; + case MSM_HDMI_AUDIO_CHANNEL_8: + channel_count = 7; + /* FRC,FLC,RR,RL,FC,LFE,FR,FL */ + channel_allocation = 0x1f; + break; + default: + break; + } + + /* Program the Channel-Speaker allocation */ + audio_info_1_reg = 0; + /* CA(channel_allocation) */ + audio_info_1_reg |= channel_allocation & 0xff; + /* Program the Level shifter */ + /* LSV(level_shift) */ + audio_info_1_reg |= (level_shift << 11) & 0x00007800; + /* Program the Down-mix Inhibit Flag */ + /* DM_INH(down_mix) */ + audio_info_1_reg |= (down_mix << 15) & 0x00008000; + + /* HDMI_AUDIO_INFO1[0x00E8] */ + HDMI_OUTP(0x00E8, audio_info_1_reg); + + /* Calculate CheckSum + Sum of all the bytes in the Audio Info Packet bytes + (See table 8.4 in HDMI spec) */ + check_sum = 0; + /* HDMI_AUDIO_INFO_FRAME_PACKET_HEADER_TYPE[0x84] */ + check_sum += 0x84; + /* HDMI_AUDIO_INFO_FRAME_PACKET_HEADER_VERSION[0x01] */ + check_sum += 1; + /* HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH[0x0A] */ + check_sum += 0x0A; + check_sum += channel_count; + check_sum += channel_allocation; + /* See Table 8.5 in HDMI spec */ + check_sum += (level_shift & 0xF) << 3 | (down_mix & 0x1) << 7; + check_sum &= 0xFF; + check_sum = (uint8) (256 - check_sum); + + audio_info_0_reg = 0; + /* CHECKSUM(check_sum) */ + audio_info_0_reg |= check_sum & 0xff; + /* CC(channel_count) */ + audio_info_0_reg |= (channel_count << 8) & 0x00000700; + + /* HDMI_AUDIO_INFO0[0x00E4] */ + HDMI_OUTP(0x00E4, audio_info_0_reg); + + /* Set these flags */ + /* AUDIO_INFO_UPDATE | AUDIO_INFO_SOURCE | AUDIO_INFO_CONT + | AUDIO_INFO_SEND */ + audio_info_ctrl_reg |= 0x000000F0; + } else { + /* Clear these flags */ + /* ~(AUDIO_INFO_UPDATE | AUDIO_INFO_SOURCE | AUDIO_INFO_CONT + | AUDIO_INFO_SEND) */ + audio_info_ctrl_reg &= ~0x000000F0; + } + /* HDMI_INFOFRAME_CTRL0[0x002C] */ + HDMI_OUTP(0x002C, audio_info_ctrl_reg); +} + +static void hdmi_msm_audio_ctrl_setup(boolean enabled, int delay) +{ + uint32 audio_pkt_ctrl_reg = 0; + + /* Enable Packet Transmission */ + audio_pkt_ctrl_reg |= enabled ? 0x00000001 : 0; + audio_pkt_ctrl_reg |= (delay << 4); + + /* HDMI_AUDIO_PKT_CTRL1[0x0020] */ + HDMI_OUTP(0x0020, audio_pkt_ctrl_reg); +} + +static void hdmi_msm_en_gc_packet(boolean av_mute_is_requested) +{ + /* HDMI_GC[0x0040] */ + HDMI_OUTP(0x0040, av_mute_is_requested ? 1 : 0); + + /* GC packet enable (every frame) */ + /* HDMI_VBI_PKT_CTRL[0x0028] */ + hdmi_msm_rmw32or(0x0028, 3 << 4); +} + +static void hdmi_msm_en_isrc_packet(boolean isrc_is_continued) +{ + static const char isrc_psuedo_data[] = + "ISRC1:0123456789isrc2=ABCDEFGHIJ"; + const uint32 * isrc_data = (const uint32 *) isrc_psuedo_data; + + /* ISRC_STATUS =0b010 | ISRC_CONTINUE | ISRC_VALID */ + /* HDMI_ISRC1_0[0x00048] */ + HDMI_OUTP(0x00048, 2 | (isrc_is_continued ? 1 : 0) << 6 | 0 << 7); + + /* HDMI_ISRC1_1[0x004C] */ + HDMI_OUTP(0x004C, *isrc_data++); + /* HDMI_ISRC1_2[0x0050] */ + HDMI_OUTP(0x0050, *isrc_data++); + /* HDMI_ISRC1_3[0x0054] */ + HDMI_OUTP(0x0054, *isrc_data++); + /* HDMI_ISRC1_4[0x0058] */ + HDMI_OUTP(0x0058, *isrc_data++); + + /* HDMI_ISRC2_0[0x005C] */ + HDMI_OUTP(0x005C, *isrc_data++); + /* HDMI_ISRC2_1[0x0060] */ + HDMI_OUTP(0x0060, *isrc_data++); + /* HDMI_ISRC2_2[0x0064] */ + HDMI_OUTP(0x0064, *isrc_data++); + /* HDMI_ISRC2_3[0x0068] */ + HDMI_OUTP(0x0068, *isrc_data); + + /* HDMI_VBI_PKT_CTRL[0x0028] */ + /* ISRC Send + Continuous */ + hdmi_msm_rmw32or(0x0028, 3 << 8); +} + +static void hdmi_msm_en_acp_packet(uint32 byte1) +{ + /* HDMI_ACP[0x003C] */ + HDMI_OUTP(0x003C, 2 | 1 << 8 | byte1 << 16); + + /* HDMI_VBI_PKT_CTRL[0x0028] */ + /* ACP send, s/w source */ + hdmi_msm_rmw32or(0x0028, 3 << 12); +} + +static void hdmi_msm_audio_setup(void) +{ + const int channels = MSM_HDMI_AUDIO_CHANNEL_2; + + /* (0) for clr_avmute, (1) for set_avmute */ + hdmi_msm_en_gc_packet(0); + /* (0) for isrc1 only, (1) for isrc1 and isrc2 */ + hdmi_msm_en_isrc_packet(1); + /* arbitrary bit pattern for byte1 */ + hdmi_msm_en_acp_packet(0x5a); + + hdmi_msm_audio_acr_setup(TRUE, + external_common_state->video_resolution, + MSM_HDMI_SAMPLE_RATE_48KHZ, channels); + hdmi_msm_audio_info_setup(TRUE, channels, 0, FALSE); + hdmi_msm_audio_ctrl_setup(TRUE, 1); + + /* Turn on Audio FIFO and SAM DROP ISR */ + HDMI_OUTP(0x02CC, HDMI_INP(0x02CC) | BIT(1) | BIT(3)); + DEV_INFO("HDMI Audio: Enabled\n"); +} + +static int hdmi_msm_audio_off(void) +{ + uint32 audio_pkt_ctrl, audio_cfg; + /* Number of wait iterations */ + int i = 10; + audio_pkt_ctrl = HDMI_INP_ND(0x0020); + audio_cfg = HDMI_INP_ND(0x01D0); + + /* Checking BIT[0] of AUDIO PACKET CONTROL and */ + /* AUDIO CONFIGURATION register */ + while (((audio_pkt_ctrl & 0x00000001) || (audio_cfg & 0x00000001)) + && (i--)) { + audio_pkt_ctrl = HDMI_INP_ND(0x0020); + audio_cfg = HDMI_INP_ND(0x01D0); + DEV_DBG("%d times :: HDMI AUDIO PACKET is %08x and " + "AUDIO CFG is %08x", i, audio_pkt_ctrl, audio_cfg); + msleep(100); + if (!i) { + DEV_ERR("%s:failed to set BIT[0] AUDIO PACKET" + "CONTROL or AUDIO CONFIGURATION REGISTER\n", + __func__); + return -ETIMEDOUT; + } + } + hdmi_msm_audio_info_setup(FALSE, 0, 0, FALSE); + hdmi_msm_audio_ctrl_setup(FALSE, 0); + hdmi_msm_audio_acr_setup(FALSE, 0, 0, 0); + DEV_INFO("HDMI Audio: Disabled\n"); + return 0; +} + + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT +static uint8 hdmi_msm_avi_iframe_lut[][14] = { +/* 480p60 480i60 576p50 576i50 720p60 720p50 1080p60 1080i60 1080p50 + 1080i50 1080p24 1080p30 1080p25 640x480p */ + {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10}, + {0x18, 0x18, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x18}, + {0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x88}, + {0x02, 0x06, 0x11, 0x15, 0x04, 0x13, 0x10, 0x05, 0x1F, + 0x14, 0x20, 0x22, 0x21, 0x01}, + {0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xE1, 0xE1, 0x41, 0x41, 0xD1, 0xd1, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0xe1}, + {0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x01}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xD1, 0xD1, 0xD1, 0xD1, 0x01, 0x01, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81}, + {0x02, 0x02, 0x02, 0x02, 0x05, 0x05, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x02} +}; + +static void hdmi_msm_avi_info_frame(void) +{ + /* two header + length + 13 data */ + uint8 aviInfoFrame[16]; + uint8 checksum; + uint32 sum; + uint32 regVal; + int i; + int mode = 0; + + switch (external_common_state->video_resolution) { + case HDMI_VFRMT_720x480p60_16_9: + mode = 0; + break; + case HDMI_VFRMT_720x480i60_16_9: + mode = 1; + break; + case HDMI_VFRMT_720x576p50_16_9: + mode = 2; + break; + case HDMI_VFRMT_720x576i50_16_9: + mode = 3; + break; + case HDMI_VFRMT_1280x720p60_16_9: + mode = 4; + break; + case HDMI_VFRMT_1280x720p50_16_9: + mode = 5; + break; + case HDMI_VFRMT_1920x1080p60_16_9: + mode = 6; + break; + case HDMI_VFRMT_1920x1080i60_16_9: + mode = 7; + break; + case HDMI_VFRMT_1920x1080p50_16_9: + mode = 8; + break; + case HDMI_VFRMT_1920x1080i50_16_9: + mode = 9; + break; + case HDMI_VFRMT_1920x1080p24_16_9: + mode = 10; + break; + case HDMI_VFRMT_1920x1080p30_16_9: + mode = 11; + break; + case HDMI_VFRMT_1920x1080p25_16_9: + mode = 12; + break; + case HDMI_VFRMT_640x480p60_4_3: + mode = 13; + break; + default: + DEV_INFO("%s: mode %d not supported\n", __func__, + external_common_state->video_resolution); + return; + } + + /* InfoFrame Type = 82 */ + aviInfoFrame[0] = 0x82; + /* Version = 2 */ + aviInfoFrame[1] = 2; + /* Length of AVI InfoFrame = 13 */ + aviInfoFrame[2] = 13; + + /* Data Byte 01: 0 Y1 Y0 A0 B1 B0 S1 S0 */ + aviInfoFrame[3] = hdmi_msm_avi_iframe_lut[0][mode]; + /* Data Byte 02: C1 C0 M1 M0 R3 R2 R1 R0 */ + aviInfoFrame[4] = hdmi_msm_avi_iframe_lut[1][mode]; + /* Data Byte 03: ITC EC2 EC1 EC0 Q1 Q0 SC1 SC0 */ + aviInfoFrame[5] = hdmi_msm_avi_iframe_lut[2][mode]; + /* Data Byte 04: 0 VIC6 VIC5 VIC4 VIC3 VIC2 VIC1 VIC0 */ + aviInfoFrame[6] = hdmi_msm_avi_iframe_lut[3][mode]; + /* Data Byte 05: 0 0 0 0 PR3 PR2 PR1 PR0 */ + aviInfoFrame[7] = hdmi_msm_avi_iframe_lut[4][mode]; + /* Data Byte 06: LSB Line No of End of Top Bar */ + aviInfoFrame[8] = hdmi_msm_avi_iframe_lut[5][mode]; + /* Data Byte 07: MSB Line No of End of Top Bar */ + aviInfoFrame[9] = hdmi_msm_avi_iframe_lut[6][mode]; + /* Data Byte 08: LSB Line No of Start of Bottom Bar */ + aviInfoFrame[10] = hdmi_msm_avi_iframe_lut[7][mode]; + /* Data Byte 09: MSB Line No of Start of Bottom Bar */ + aviInfoFrame[11] = hdmi_msm_avi_iframe_lut[8][mode]; + /* Data Byte 10: LSB Pixel Number of End of Left Bar */ + aviInfoFrame[12] = hdmi_msm_avi_iframe_lut[9][mode]; + /* Data Byte 11: MSB Pixel Number of End of Left Bar */ + aviInfoFrame[13] = hdmi_msm_avi_iframe_lut[10][mode]; + /* Data Byte 12: LSB Pixel Number of Start of Right Bar */ + aviInfoFrame[14] = hdmi_msm_avi_iframe_lut[11][mode]; + /* Data Byte 13: MSB Pixel Number of Start of Right Bar */ + aviInfoFrame[15] = hdmi_msm_avi_iframe_lut[12][mode]; + + sum = 0; + for (i = 0; i < 16; i++) + sum += aviInfoFrame[i]; + sum &= 0xFF; + sum = 256 - sum; + checksum = (uint8) sum; + + regVal = aviInfoFrame[5]; + regVal = regVal << 8 | aviInfoFrame[4]; + regVal = regVal << 8 | aviInfoFrame[3]; + regVal = regVal << 8 | checksum; + HDMI_OUTP(0x006C, regVal); + + regVal = aviInfoFrame[9]; + regVal = regVal << 8 | aviInfoFrame[8]; + regVal = regVal << 8 | aviInfoFrame[7]; + regVal = regVal << 8 | aviInfoFrame[6]; + HDMI_OUTP(0x0070, regVal); + + regVal = aviInfoFrame[13]; + regVal = regVal << 8 | aviInfoFrame[12]; + regVal = regVal << 8 | aviInfoFrame[11]; + regVal = regVal << 8 | aviInfoFrame[10]; + HDMI_OUTP(0x0074, regVal); + + regVal = aviInfoFrame[1]; + regVal = regVal << 16 | aviInfoFrame[15]; + regVal = regVal << 8 | aviInfoFrame[14]; + HDMI_OUTP(0x0078, regVal); + + /* INFOFRAME_CTRL0[0x002C] */ + /* 0x3 for AVI InfFrame enable (every frame) */ + HDMI_OUTP(0x002C, HDMI_INP(0x002C) | 0x00000003L); +} +#endif + +#ifdef CONFIG_FB_MSM_HDMI_3D +static void hdmi_msm_vendor_infoframe_packetsetup(void) +{ + uint32 packet_header = 0; + uint32 check_sum = 0; + uint32 packet_payload = 0; + + if (!external_common_state->format_3d) { + HDMI_OUTP(0x0034, 0); + return; + } + + /* 0x0084 GENERIC0_HDR + * HB0 7:0 NUM + * HB1 15:8 NUM + * HB2 23:16 NUM */ + /* Setup Packet header and payload */ + /* 0x81 VS_INFO_FRAME_ID + 0x01 VS_INFO_FRAME_VERSION + 0x1B VS_INFO_FRAME_PAYLOAD_LENGTH */ + packet_header = 0x81 | (0x01 << 8) | (0x1B << 16); + HDMI_OUTP(0x0084, packet_header); + + check_sum = packet_header & 0xff; + check_sum += (packet_header >> 8) & 0xff; + check_sum += (packet_header >> 16) & 0xff; + + /* 0x008C GENERIC0_1 + * BYTE4 7:0 NUM + * BYTE5 15:8 NUM + * BYTE6 23:16 NUM + * BYTE7 31:24 NUM */ + /* 0x02 VS_INFO_FRAME_3D_PRESENT */ + packet_payload = 0x02 << 5; + switch (external_common_state->format_3d) { + case 1: + /* 0b1000 VIDEO_3D_FORMAT_SIDE_BY_SIDE_HALF */ + packet_payload |= (0x08 << 8) << 4; + break; + case 2: + /* 0b0110 VIDEO_3D_FORMAT_TOP_AND_BOTTOM_HALF */ + packet_payload |= (0x06 << 8) << 4; + break; + } + HDMI_OUTP(0x008C, packet_payload); + + check_sum += packet_payload & 0xff; + check_sum += (packet_payload >> 8) & 0xff; + + #define IEEE_REGISTRATION_ID 0xC03 + /* Next 3 bytes are IEEE Registration Identifcation */ + /* 0x0088 GENERIC0_0 + * BYTE0 7:0 NUM (checksum) + * BYTE1 15:8 NUM + * BYTE2 23:16 NUM + * BYTE3 31:24 NUM */ + check_sum += IEEE_REGISTRATION_ID & 0xff; + check_sum += (IEEE_REGISTRATION_ID >> 8) & 0xff; + check_sum += (IEEE_REGISTRATION_ID >> 16) & 0xff; + + HDMI_OUTP(0x0088, (0x100 - (0xff & check_sum)) + | ((IEEE_REGISTRATION_ID & 0xff) << 8) + | (((IEEE_REGISTRATION_ID >> 8) & 0xff) << 16) + | (((IEEE_REGISTRATION_ID >> 16) & 0xff) << 24)); + + /* 0x0034 GEN_PKT_CTRL + * GENERIC0_SEND 0 0 = Disable Generic0 Packet Transmission + * 1 = Enable Generic0 Packet Transmission + * GENERIC0_CONT 1 0 = Send Generic0 Packet on next frame only + * 1 = Send Generic0 Packet on every frame + * GENERIC0_UPDATE 2 NUM + * GENERIC1_SEND 4 0 = Disable Generic1 Packet Transmission + * 1 = Enable Generic1 Packet Transmission + * GENERIC1_CONT 5 0 = Send Generic1 Packet on next frame only + * 1 = Send Generic1 Packet on every frame + * GENERIC0_LINE 21:16 NUM + * GENERIC1_LINE 29:24 NUM + */ + /* GENERIC0_LINE | GENERIC0_UPDATE | GENERIC0_CONT | GENERIC0_SEND + * Setup HDMI TX generic packet control + * Enable this packet to transmit every frame + * Enable this packet to transmit every frame + * Enable HDMI TX engine to transmit Generic packet 0 */ + HDMI_OUTP(0x0034, (1 << 16) | (1 << 2) | BIT(1) | BIT(0)); +} + +static void hdmi_msm_switch_3d(boolean on) +{ + mutex_lock(&external_common_state_hpd_mutex); + if (external_common_state->hpd_state) + hdmi_msm_vendor_infoframe_packetsetup(); + mutex_unlock(&external_common_state_hpd_mutex); +} +#endif + +static int hdmi_msm_clk(int on) +{ + int rc; + + DEV_DBG("HDMI Clk: %s\n", on ? "Enable" : "Disable"); + if (on) { + rc = clk_enable(hdmi_msm_state->hdmi_app_clk); + if (rc) { + DEV_ERR("'hdmi_app_clk' clock enable failed, rc=%d\n", + rc); + return rc; + } + + rc = clk_enable(hdmi_msm_state->hdmi_m_pclk); + if (rc) { + DEV_ERR("'hdmi_m_pclk' clock enable failed, rc=%d\n", + rc); + return rc; + } + + rc = clk_enable(hdmi_msm_state->hdmi_s_pclk); + if (rc) { + DEV_ERR("'hdmi_s_pclk' clock enable failed, rc=%d\n", + rc); + return rc; + } + } else { + clk_disable(hdmi_msm_state->hdmi_app_clk); + clk_disable(hdmi_msm_state->hdmi_m_pclk); + clk_disable(hdmi_msm_state->hdmi_s_pclk); + } + + return 0; +} + +static void hdmi_msm_reset_core(void) +{ + hdmi_msm_set_mode(FALSE); + hdmi_msm_clk(0); + udelay(5); + hdmi_msm_clk(1); + + clk_reset(hdmi_msm_state->hdmi_app_clk, CLK_RESET_ASSERT); + clk_reset(hdmi_msm_state->hdmi_m_pclk, CLK_RESET_ASSERT); + clk_reset(hdmi_msm_state->hdmi_s_pclk, CLK_RESET_ASSERT); + udelay(20); + clk_reset(hdmi_msm_state->hdmi_app_clk, CLK_RESET_DEASSERT); + clk_reset(hdmi_msm_state->hdmi_m_pclk, CLK_RESET_DEASSERT); + clk_reset(hdmi_msm_state->hdmi_s_pclk, CLK_RESET_DEASSERT); +} + +static void hdmi_msm_turn_on(void) +{ + uint32 hpd_ctrl; + + hdmi_msm_reset_core(); + hdmi_msm_init_phy(external_common_state->video_resolution); + /* HDMI_USEC_REFTIMER[0x0208] */ + HDMI_OUTP(0x0208, 0x0001001B); + + hdmi_msm_video_setup(external_common_state->video_resolution); + if (!hdmi_msm_is_dvi_mode()) + hdmi_msm_audio_setup(); +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + hdmi_msm_avi_info_frame(); +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ +#ifdef CONFIG_FB_MSM_HDMI_3D + hdmi_msm_vendor_infoframe_packetsetup(); +#endif + + /* set timeout to 4.1ms (max) for hardware debounce */ + hpd_ctrl = (HDMI_INP(0x0258) & ~0xFFF) | 0xFFF; + + /* Toggle HPD circuit to trigger HPD sense */ + HDMI_OUTP(0x0258, ~(1 << 28) & hpd_ctrl); + HDMI_OUTP(0x0258, (1 << 28) | hpd_ctrl); + + hdmi_msm_set_mode(TRUE); + + /* Setup HPD IRQ */ + HDMI_OUTP(0x0254, 4 | (external_common_state->hpd_state ? 0 : 2)); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + if (hdmi_msm_state->reauth) { + hdmi_msm_hdcp_enable(); + hdmi_msm_state->reauth = FALSE ; + } +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + DEV_INFO("HDMI Core: Initialized\n"); +} + +static void hdmi_msm_hpd_state_timer(unsigned long data) +{ + queue_work(hdmi_work_queue, &hdmi_msm_state->hpd_state_work); +} + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT +static void hdmi_msm_hdcp_timer(unsigned long data) +{ + queue_work(hdmi_work_queue, &hdmi_msm_state->hdcp_work); +} +#endif + +static void hdmi_msm_hpd_read_work(struct work_struct *work) +{ + uint32 hpd_ctrl; + + clk_enable(hdmi_msm_state->hdmi_app_clk); + hdmi_msm_state->pd->core_power(1, 1); + hdmi_msm_state->pd->enable_5v(1); + hdmi_msm_set_mode(FALSE); + hdmi_msm_init_phy(external_common_state->video_resolution); + /* HDMI_USEC_REFTIMER[0x0208] */ + HDMI_OUTP(0x0208, 0x0001001B); + hpd_ctrl = (HDMI_INP(0x0258) & ~0xFFF) | 0xFFF; + + /* Toggle HPD circuit to trigger HPD sense */ + HDMI_OUTP(0x0258, ~(1 << 28) & hpd_ctrl); + HDMI_OUTP(0x0258, (1 << 28) | hpd_ctrl); + + hdmi_msm_set_mode(TRUE); + msleep(1000); + external_common_state->hpd_state = (HDMI_INP(0x0250) & 0x2) >> 1; + if (external_common_state->hpd_state) { + hdmi_msm_read_edid(); + DEV_DBG("%s: sense CONNECTED: send ONLINE\n", __func__); + kobject_uevent(external_common_state->uevent_kobj, + KOBJ_ONLINE); + } + hdmi_msm_hpd_off(); + hdmi_msm_set_mode(FALSE); + hdmi_msm_state->pd->core_power(0, 1); + hdmi_msm_state->pd->enable_5v(0); + clk_disable(hdmi_msm_state->hdmi_app_clk); +} + +static void hdmi_msm_hpd_off(void) +{ + DEV_DBG("%s: (timer, clk, 5V, core, IRQ off)\n", __func__); + del_timer(&hdmi_msm_state->hpd_state_timer); + disable_irq(hdmi_msm_state->irq); + + hdmi_msm_set_mode(FALSE); + HDMI_OUTP_ND(0x0308, 0x7F); /*0b01111111*/ + hdmi_msm_state->hpd_initialized = FALSE; + hdmi_msm_state->pd->enable_5v(0); + hdmi_msm_state->pd->core_power(0, 1); + hdmi_msm_clk(0); + hdmi_msm_state->hpd_initialized = FALSE; +} + +static void hdmi_msm_dump_regs(const char *prefex) +{ +#ifdef REG_DUMP + print_hex_dump(KERN_INFO, prefix, DUMP_PREFIX_OFFSET, 32, 4, + (void *)MSM_HDMI_BASE, 0x0334, false); +#endif +} + +static int hdmi_msm_hpd_on(bool trigger_handler) +{ + static int phy_reset_done; + + hdmi_msm_clk(1); + hdmi_msm_state->pd->core_power(1, 1); + hdmi_msm_state->pd->enable_5v(1); + hdmi_msm_dump_regs("HDMI-INIT: "); + hdmi_msm_set_mode(FALSE); + + if (!phy_reset_done) { + hdmi_phy_reset(); + phy_reset_done = 1; + } + + hdmi_msm_init_phy(external_common_state->video_resolution); + /* HDMI_USEC_REFTIMER[0x0208] */ + HDMI_OUTP(0x0208, 0x0001001B); + + /* Check HPD State */ + if (!hdmi_msm_state->hpd_initialized) { + uint32 hpd_ctrl; + enable_irq(hdmi_msm_state->irq); + + /* set timeout to 4.1ms (max) for hardware debounce */ + hpd_ctrl = (HDMI_INP(0x0258) & ~0xFFF) | 0xFFF; + + /* Toggle HPD circuit to trigger HPD sense */ + HDMI_OUTP(0x0258, ~(1 << 28) & hpd_ctrl); + HDMI_OUTP(0x0258, (1 << 28) | hpd_ctrl); + + DEV_DBG("%s: (clk, 5V, core, IRQ on) \n", __func__, + trigger_handler ? "true" : "false"); + + if (trigger_handler) { + /* Set HPD state machine: ensure at least 2 readouts */ + mutex_lock(&hdmi_msm_state_mutex); + hdmi_msm_state->hpd_stable = 0; + hdmi_msm_state->hpd_prev_state = TRUE; + mutex_lock(&external_common_state_hpd_mutex); + external_common_state->hpd_state = FALSE; + mutex_unlock(&external_common_state_hpd_mutex); + hdmi_msm_state->hpd_cable_chg_detected = TRUE; + mutex_unlock(&hdmi_msm_state_mutex); + mod_timer(&hdmi_msm_state->hpd_state_timer, + jiffies + HZ/2); + } + + hdmi_msm_state->hpd_initialized = TRUE; + } + hdmi_msm_set_mode(TRUE); + + return 0; +} + +static int hdmi_msm_power_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); + bool changed; + + if (!hdmi_msm_state || !hdmi_msm_state->hdmi_app_clk || !MSM_HDMI_BASE) + return -ENODEV; +#ifdef CONFIG_SUSPEND + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->pm_suspended) { + mutex_unlock(&hdmi_msm_state_mutex); + DEV_WARN("%s: ignored, pm_suspended\n", __func__); + return -ENODEV; + } + mutex_unlock(&hdmi_msm_state_mutex); +#endif + + DEV_INFO("power: ON (%dx%d %d)\n", mfd->var_xres, mfd->var_yres, + mfd->var_pixclock); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->hdcp_activating) { + hdmi_msm_state->panel_power_on = TRUE; + DEV_INFO("HDCP: activating, returning\n"); + } + mutex_unlock(&hdmi_msm_state_mutex); +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + + changed = hdmi_common_get_video_format_from_drv_data(mfd); + if (!external_common_state->hpd_feature_on) { + int rc = hdmi_msm_hpd_on(true); + DEV_INFO("HPD: panel power without 'hpd' feature on\n"); + if (rc) { + DEV_WARN("HPD: activation failed: rc=%d\n", rc); + return rc; + } + } + hdmi_msm_audio_info_setup(TRUE, 0, 0, FALSE); + + mutex_lock(&external_common_state_hpd_mutex); + hdmi_msm_state->panel_power_on = TRUE; + if ((external_common_state->hpd_state && !hdmi_msm_is_power_on()) + || changed) { + mutex_unlock(&external_common_state_hpd_mutex); + hdmi_msm_turn_on(); + } else + mutex_unlock(&external_common_state_hpd_mutex); + + hdmi_msm_dump_regs("HDMI-ON: "); + + DEV_INFO("power=%s DVI= %s\n", + hdmi_msm_is_power_on() ? "ON" : "OFF" , + hdmi_msm_is_dvi_mode() ? "ON" : "OFF"); + return 0; +} + +/* Note that power-off will also be called when the cable-remove event is + * processed on the user-space and as a result the framebuffer is powered + * down. However, we are still required to be able to detect a cable-insert + * event; so for now leave the HDMI engine running; so that the HPD IRQ is + * still being processed. + */ +static int hdmi_msm_power_off(struct platform_device *pdev) +{ + if (!hdmi_msm_state->hdmi_app_clk) + return -ENODEV; +#ifdef CONFIG_SUSPEND + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->pm_suspended) { + mutex_unlock(&hdmi_msm_state_mutex); + DEV_WARN("%s: ignored, pm_suspended\n", __func__); + return -ENODEV; + } + mutex_unlock(&hdmi_msm_state_mutex); +#endif + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->hdcp_activating) { + hdmi_msm_state->panel_power_on = FALSE; + mutex_unlock(&hdmi_msm_state_mutex); + DEV_INFO("HDCP: activating, returning\n"); + return 0; + } + mutex_unlock(&hdmi_msm_state_mutex); +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + + DEV_INFO("power: OFF (audio off, Reset Core)\n"); + hdmi_msm_audio_off(); +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + hdcp_deauthenticate(); +#endif + hdmi_msm_hpd_off(); + hdmi_msm_powerdown_phy(); + hdmi_msm_dump_regs("HDMI-OFF: "); + hdmi_msm_hpd_on(false); + + mutex_lock(&external_common_state_hpd_mutex); + if (!external_common_state->hpd_feature_on) + hdmi_msm_hpd_off(); + mutex_unlock(&external_common_state_hpd_mutex); + + hdmi_msm_state->panel_power_on = FALSE; + return 0; +} + +static int __devinit hdmi_msm_probe(struct platform_device *pdev) +{ + int rc; + struct platform_device *fb_dev; + + if (!hdmi_msm_state) { + pr_err("%s: hdmi_msm_state is NULL\n", __func__); + return -ENOMEM; + } + + external_common_state->dev = &pdev->dev; + DEV_DBG("probe\n"); + if (pdev->id == 0) { + struct resource *res; + + #define GET_RES(name, mode) do { \ + res = platform_get_resource_byname(pdev, mode, name); \ + if (!res) { \ + DEV_ERR("'" name "' resource not found\n"); \ + rc = -ENODEV; \ + goto error; \ + } \ + } while (0) + + #define IO_REMAP(var, name) do { \ + GET_RES(name, IORESOURCE_MEM); \ + var = ioremap(res->start, resource_size(res)); \ + if (!var) { \ + DEV_ERR("'" name "' ioremap failed\n"); \ + rc = -ENOMEM; \ + goto error; \ + } \ + } while (0) + + #define GET_IRQ(var, name) do { \ + GET_RES(name, IORESOURCE_IRQ); \ + var = res->start; \ + } while (0) + + IO_REMAP(hdmi_msm_state->qfprom_io, "hdmi_msm_qfprom_addr"); + hdmi_msm_state->hdmi_io = MSM_HDMI_BASE; + GET_IRQ(hdmi_msm_state->irq, "hdmi_msm_irq"); + + hdmi_msm_state->pd = pdev->dev.platform_data; + + #undef GET_RES + #undef IO_REMAP + #undef GET_IRQ + return 0; + } + + hdmi_msm_state->hdmi_app_clk = clk_get(NULL, "hdmi_app_clk"); + if (IS_ERR(hdmi_msm_state->hdmi_app_clk)) { + DEV_ERR("'hdmi_app_clk' clk not found\n"); + rc = IS_ERR(hdmi_msm_state->hdmi_app_clk); + goto error; + } + + hdmi_msm_state->hdmi_m_pclk = clk_get(NULL, "hdmi_m_pclk"); + if (IS_ERR(hdmi_msm_state->hdmi_m_pclk)) { + DEV_ERR("'hdmi_m_pclk' clk not found\n"); + rc = IS_ERR(hdmi_msm_state->hdmi_m_pclk); + goto error; + } + + hdmi_msm_state->hdmi_s_pclk = clk_get(NULL, "hdmi_s_pclk"); + if (IS_ERR(hdmi_msm_state->hdmi_s_pclk)) { + DEV_ERR("'hdmi_s_pclk' clk not found\n"); + rc = IS_ERR(hdmi_msm_state->hdmi_s_pclk); + goto error; + } + + rc = check_hdmi_features(); + if (rc) { + DEV_ERR("Init FAILED: check_hdmi_features rc=%d\n", rc); + goto error; + } + + if (!hdmi_msm_state->pd->core_power) { + DEV_ERR("Init FAILED: core_power function missing\n"); + rc = -ENODEV; + goto error; + } + if (!hdmi_msm_state->pd->enable_5v) { + DEV_ERR("Init FAILED: enable_5v function missing\n"); + rc = -ENODEV; + goto error; + } + + rc = request_threaded_irq(hdmi_msm_state->irq, NULL, &hdmi_msm_isr, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "hdmi_msm_isr", NULL); + if (rc) { + DEV_ERR("Init FAILED: IRQ request, rc=%d\n", rc); + goto error; + } + disable_irq(hdmi_msm_state->irq); + + init_timer(&hdmi_msm_state->hpd_state_timer); + hdmi_msm_state->hpd_state_timer.function = + hdmi_msm_hpd_state_timer; + hdmi_msm_state->hpd_state_timer.data = (uint32)NULL; + + hdmi_msm_state->hpd_state_timer.expires = 0xffffffffL; + add_timer(&hdmi_msm_state->hpd_state_timer); + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + init_timer(&hdmi_msm_state->hdcp_timer); + hdmi_msm_state->hdcp_timer.function = + hdmi_msm_hdcp_timer; + hdmi_msm_state->hdcp_timer.data = (uint32)NULL; + + hdmi_msm_state->hdcp_timer.expires = 0xffffffffL; + add_timer(&hdmi_msm_state->hdcp_timer); +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + + fb_dev = msm_fb_add_device(pdev); + if (fb_dev) { + rc = external_common_state_create(fb_dev); + if (rc) { + DEV_ERR("Init FAILED: hdmi_msm_state_create, rc=%d\n", + rc); + goto error; + } + } else + DEV_ERR("Init FAILED: failed to add fb device\n"); + + DEV_INFO("HDMI HPD: ON\n"); + + rc = hdmi_msm_hpd_on(true); + if (rc) + goto error; + + if (hdmi_msm_has_hdcp()) + external_common_state->present_hdcp = TRUE; + else { + external_common_state->present_hdcp = FALSE; +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + /* + * If the device is not hdcp capable do + * not start hdcp timer. + */ + del_timer(&hdmi_msm_state->hdcp_timer); +#endif + } + + queue_work(hdmi_work_queue, &hdmi_msm_state->hpd_read_work); + return 0; + +error: + if (hdmi_msm_state->qfprom_io) + iounmap(hdmi_msm_state->qfprom_io); + hdmi_msm_state->qfprom_io = NULL; + + if (hdmi_msm_state->hdmi_io) + iounmap(hdmi_msm_state->hdmi_io); + hdmi_msm_state->hdmi_io = NULL; + + external_common_state_remove(); + + if (hdmi_msm_state->hdmi_app_clk) + clk_put(hdmi_msm_state->hdmi_app_clk); + if (hdmi_msm_state->hdmi_m_pclk) + clk_put(hdmi_msm_state->hdmi_m_pclk); + if (hdmi_msm_state->hdmi_s_pclk) + clk_put(hdmi_msm_state->hdmi_s_pclk); + + hdmi_msm_state->hdmi_app_clk = NULL; + hdmi_msm_state->hdmi_m_pclk = NULL; + hdmi_msm_state->hdmi_s_pclk = NULL; + + return rc; +} + +static int __devexit hdmi_msm_remove(struct platform_device *pdev) +{ + DEV_INFO("HDMI device: remove\n"); + + DEV_INFO("HDMI HPD: OFF\n"); + hdmi_msm_hpd_off(); + free_irq(hdmi_msm_state->irq, NULL); + + if (hdmi_msm_state->qfprom_io) + iounmap(hdmi_msm_state->qfprom_io); + hdmi_msm_state->qfprom_io = NULL; + + if (hdmi_msm_state->hdmi_io) + iounmap(hdmi_msm_state->hdmi_io); + hdmi_msm_state->hdmi_io = NULL; + + external_common_state_remove(); + + if (hdmi_msm_state->hdmi_app_clk) + clk_put(hdmi_msm_state->hdmi_app_clk); + if (hdmi_msm_state->hdmi_m_pclk) + clk_put(hdmi_msm_state->hdmi_m_pclk); + if (hdmi_msm_state->hdmi_s_pclk) + clk_put(hdmi_msm_state->hdmi_s_pclk); + + hdmi_msm_state->hdmi_app_clk = NULL; + hdmi_msm_state->hdmi_m_pclk = NULL; + hdmi_msm_state->hdmi_s_pclk = NULL; + + kfree(hdmi_msm_state); + hdmi_msm_state = NULL; + + return 0; +} + +static int hdmi_msm_hpd_feature(int on) +{ + int rc = 0; + + DEV_INFO("%s: %d\n", __func__, on); + if (on) + rc = hdmi_msm_hpd_on(true); + else + hdmi_msm_hpd_off(); + + return rc; +} + + +#ifdef CONFIG_SUSPEND +static int hdmi_msm_device_pm_suspend(struct device *dev) +{ + mutex_lock(&hdmi_msm_state_mutex); + if (hdmi_msm_state->pm_suspended) { + mutex_unlock(&hdmi_msm_state_mutex); + return 0; + } + + DEV_DBG("pm_suspend\n"); + + del_timer(&hdmi_msm_state->hpd_state_timer); +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + del_timer(&hdmi_msm_state->hdcp_timer); +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + + disable_irq(hdmi_msm_state->irq); + clk_disable(hdmi_msm_state->hdmi_app_clk); + clk_disable(hdmi_msm_state->hdmi_m_pclk); + clk_disable(hdmi_msm_state->hdmi_s_pclk); + + hdmi_msm_state->pm_suspended = TRUE; + mutex_unlock(&hdmi_msm_state_mutex); + + hdmi_msm_powerdown_phy(); + hdmi_msm_state->pd->enable_5v(0); + hdmi_msm_state->pd->core_power(0, 1); + return 0; +} + +static int hdmi_msm_device_pm_resume(struct device *dev) +{ + mutex_lock(&hdmi_msm_state_mutex); + if (!hdmi_msm_state->pm_suspended) { + mutex_unlock(&hdmi_msm_state_mutex); + return 0; + } + + DEV_DBG("pm_resume\n"); + + hdmi_msm_state->pd->core_power(1, 1); + hdmi_msm_state->pd->enable_5v(1); + clk_enable(hdmi_msm_state->hdmi_app_clk); + clk_enable(hdmi_msm_state->hdmi_m_pclk); + clk_enable(hdmi_msm_state->hdmi_s_pclk); + + hdmi_msm_state->pm_suspended = FALSE; + mutex_unlock(&hdmi_msm_state_mutex); + enable_irq(hdmi_msm_state->irq); + return 0; +} +#else +#define hdmi_msm_device_pm_suspend NULL +#define hdmi_msm_device_pm_resume NULL +#endif + +static const struct dev_pm_ops hdmi_msm_device_pm_ops = { + .suspend = hdmi_msm_device_pm_suspend, + .resume = hdmi_msm_device_pm_resume, +}; + +static struct platform_driver this_driver = { + .probe = hdmi_msm_probe, + .remove = hdmi_msm_remove, + .driver.name = "hdmi_msm", + .driver.pm = &hdmi_msm_device_pm_ops, +}; + +static struct msm_fb_panel_data hdmi_msm_panel_data = { + .on = hdmi_msm_power_on, + .off = hdmi_msm_power_off, +}; + +static struct platform_device this_device = { + .name = "hdmi_msm", + .id = 1, + .dev.platform_data = &hdmi_msm_panel_data, +}; + +static int __init hdmi_msm_init(void) +{ + int rc; + + hdmi_msm_setup_video_mode_lut(); + hdmi_msm_state = kzalloc(sizeof(*hdmi_msm_state), GFP_KERNEL); + if (!hdmi_msm_state) { + pr_err("hdmi_msm_init FAILED: out of memory\n"); + rc = -ENOMEM; + goto init_exit; + } + + external_common_state = &hdmi_msm_state->common; + external_common_state->video_resolution = HDMI_VFRMT_1920x1080p60_16_9; +#ifdef CONFIG_FB_MSM_HDMI_3D + external_common_state->switch_3d = hdmi_msm_switch_3d; +#endif + + /* + * Create your work queue + * allocs and returns ptr + */ + hdmi_work_queue = create_workqueue("hdmi_hdcp"); + external_common_state->hpd_feature = hdmi_msm_hpd_feature; + + rc = platform_driver_register(&this_driver); + if (rc) { + pr_err("hdmi_msm_init FAILED: platform_driver_register rc=%d\n", + rc); + goto init_exit; + } + + hdmi_common_init_panel_info(&hdmi_msm_panel_data.panel_info); + init_completion(&hdmi_msm_state->ddc_sw_done); + INIT_WORK(&hdmi_msm_state->hpd_state_work, hdmi_msm_hpd_state_work); + INIT_WORK(&hdmi_msm_state->hpd_read_work, hdmi_msm_hpd_read_work); +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + init_completion(&hdmi_msm_state->hdcp_success_done); + INIT_WORK(&hdmi_msm_state->hdcp_reauth_work, hdmi_msm_hdcp_reauth_work); + INIT_WORK(&hdmi_msm_state->hdcp_work, hdmi_msm_hdcp_work); +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + + rc = platform_device_register(&this_device); + if (rc) { + pr_err("hdmi_msm_init FAILED: platform_device_register rc=%d\n", + rc); + platform_driver_unregister(&this_driver); + goto init_exit; + } + + pr_debug("%s: success:" +#ifdef DEBUG + " DEBUG" +#else + " RELEASE" +#endif + " AUDIO EDID HPD HDCP" +#ifndef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + ":0" +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + " DVI" +#ifndef CONFIG_FB_MSM_HDMI_MSM_PANEL_DVI_SUPPORT + ":0" +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_DVI_SUPPORT */ + "\n", __func__); + + return 0; + +init_exit: + kfree(hdmi_msm_state); + hdmi_msm_state = NULL; + + return rc; +} + +static void __exit hdmi_msm_exit(void) +{ + platform_device_unregister(&this_device); + platform_driver_unregister(&this_driver); +} + +module_init(hdmi_msm_init); +module_exit(hdmi_msm_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.3"); +MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); +MODULE_DESCRIPTION("HDMI MSM TX driver"); diff --git a/drivers/video/msm/hdmi_msm.h b/drivers/video/msm/hdmi_msm.h new file mode 100644 index 00000000000..41c756fa8f5 --- /dev/null +++ b/drivers/video/msm/hdmi_msm.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __HDMI_MSM_H__ +#define __HDMI_MSM_H__ + +#include +#include "external_common.h" +/* #define PORT_DEBUG */ + +#ifdef PORT_DEBUG +const char *hdmi_msm_name(uint32 offset); +void hdmi_outp(uint32 offset, uint32 value); +uint32 hdmi_inp(uint32 offset); + +#define HDMI_OUTP_ND(offset, value) outpdw(MSM_HDMI_BASE+(offset), (value)) +#define HDMI_OUTP(offset, value) hdmi_outp((offset), (value)) +#define HDMI_INP_ND(offset) inpdw(MSM_HDMI_BASE+(offset)) +#define HDMI_INP(offset) hdmi_inp((offset)) +#else +#define HDMI_OUTP_ND(offset, value) outpdw(MSM_HDMI_BASE+(offset), (value)) +#define HDMI_OUTP(offset, value) outpdw(MSM_HDMI_BASE+(offset), (value)) +#define HDMI_INP_ND(offset) inpdw(MSM_HDMI_BASE+(offset)) +#define HDMI_INP(offset) inpdw(MSM_HDMI_BASE+(offset)) +#endif + +#define QFPROM_BASE ((uint32)hdmi_msm_state->qfprom_io) + +struct hdmi_msm_state_type { + boolean panel_power_on; + boolean hpd_initialized; +#ifdef CONFIG_SUSPEND + boolean pm_suspended; +#endif + int hpd_stable; + boolean hpd_prev_state; + boolean hpd_cable_chg_detected; + boolean full_auth_done; + boolean hpd_during_auth; + struct work_struct hpd_state_work, hpd_read_work; + struct timer_list hpd_state_timer; + struct completion ddc_sw_done; + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT + boolean hdcp_activating; + boolean reauth ; + struct work_struct hdcp_reauth_work, hdcp_work; + struct completion hdcp_success_done; + struct timer_list hdcp_timer; +#endif /* CONFIG_FB_MSM_HDMI_MSM_PANEL_HDCP_SUPPORT */ + + int irq; + struct msm_hdmi_platform_data *pd; + struct clk *hdmi_app_clk; + struct clk *hdmi_m_pclk; + struct clk *hdmi_s_pclk; + void __iomem *qfprom_io; + void __iomem *hdmi_io; + + struct external_common_state_type common; +}; + +extern struct hdmi_msm_state_type *hdmi_msm_state; + +uint32 hdmi_msm_get_io_base(void); + +#ifdef CONFIG_FB_MSM_HDMI_COMMON +void hdmi_phy_reset(void); +void hdmi_msm_init_phy(int video_format); +void hdmi_msm_powerdown_phy(void); +void hdmi_frame_ctrl_cfg(const struct hdmi_disp_mode_timing_type *timing); +void hdmi_msm_phy_status_poll(void); +#endif + +#endif /* __HDMI_MSM_H__ */ diff --git a/drivers/video/msm/hdmi_sii9022.c b/drivers/video/msm/hdmi_sii9022.c new file mode 100644 index 00000000000..3d274888e88 --- /dev/null +++ b/drivers/video/msm/hdmi_sii9022.c @@ -0,0 +1,245 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include "msm_fb.h" + +#define DEVICE_NAME "sii9022" +#define SII9022_DEVICE_ID 0xB0 + +struct sii9022_i2c_addr_data{ + u8 addr; + u8 data; +}; + +/* video mode data */ +static u8 video_mode_data[] = { + 0x00, + 0xF9, 0x1C, 0x70, 0x17, 0x72, 0x06, 0xEE, 0x02, +}; + +static u8 avi_io_format[] = { + 0x09, + 0x00, 0x00, +}; + +/* power state */ +static struct sii9022_i2c_addr_data regset0[] = { + { 0x60, 0x04 }, + { 0x63, 0x00 }, + { 0x1E, 0x00 }, +}; + +static u8 video_infoframe[] = { + 0x0C, + 0xF0, 0x00, 0x68, 0x00, 0x04, 0x00, 0x19, 0x00, + 0xE9, 0x02, 0x04, 0x01, 0x04, 0x06, +}; + +/* configure audio */ +static struct sii9022_i2c_addr_data regset1[] = { + { 0x26, 0x90 }, + { 0x20, 0x90 }, + { 0x1F, 0x80 }, + { 0x26, 0x80 }, + { 0x24, 0x02 }, + { 0x25, 0x0B }, + { 0xBC, 0x02 }, + { 0xBD, 0x24 }, + { 0xBE, 0x02 }, +}; + +/* enable audio */ +static u8 misc_infoframe[] = { + 0xBF, + 0xC2, 0x84, 0x01, 0x0A, 0x6F, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +/* set HDMI, active */ +static struct sii9022_i2c_addr_data regset2[] = { + { 0x1A, 0x01 }, + { 0x3D, 0x00 }, +}; + +static int send_i2c_data(struct i2c_client *client, + struct sii9022_i2c_addr_data *regset, + int size) +{ + int i; + int rc = 0; + + for (i = 0; i < size; i++) { + rc = i2c_smbus_write_byte_data( + client, + regset[i].addr, regset[i].data); + if (rc) + break; + } + return rc; +} + +static int hdmi_sii_enable(struct i2c_client *client) +{ + int rc; + int retries = 10; + int count; + + rc = i2c_smbus_write_byte_data(client, 0xC7, 0x00); + if (rc) + goto enable_exit; + + do { + msleep(1); + rc = i2c_smbus_read_byte_data(client, 0x1B); + } while ((rc != SII9022_DEVICE_ID) && retries--); + + if (rc != SII9022_DEVICE_ID) + return -ENODEV; + + rc = i2c_smbus_write_byte_data(client, 0x1A, 0x11); + if (rc) + goto enable_exit; + + count = ARRAY_SIZE(video_mode_data); + rc = i2c_master_send(client, video_mode_data, count); + if (rc != count) { + rc = -EIO; + goto enable_exit; + } + + rc = i2c_smbus_write_byte_data(client, 0x08, 0x20); + if (rc) + goto enable_exit; + count = ARRAY_SIZE(avi_io_format); + rc = i2c_master_send(client, avi_io_format, count); + if (rc != count) { + rc = -EIO; + goto enable_exit; + } + + rc = send_i2c_data(client, regset0, ARRAY_SIZE(regset0)); + if (rc) + goto enable_exit; + + count = ARRAY_SIZE(video_infoframe); + rc = i2c_master_send(client, video_infoframe, count); + if (rc != count) { + rc = -EIO; + goto enable_exit; + } + + rc = send_i2c_data(client, regset1, ARRAY_SIZE(regset1)); + if (rc) + goto enable_exit; + + count = ARRAY_SIZE(misc_infoframe); + rc = i2c_master_send(client, misc_infoframe, count); + if (rc != count) { + rc = -EIO; + goto enable_exit; + } + + rc = send_i2c_data(client, regset2, ARRAY_SIZE(regset2)); + if (rc) + goto enable_exit; + + return 0; +enable_exit: + printk(KERN_ERR "%s: exited rc=%d\n", __func__, rc); + return rc; +} + +static const struct i2c_device_id hmdi_sii_id[] = { + { DEVICE_NAME, 0 }, + { } +}; + +static int hdmi_sii_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -ENODEV; + rc = hdmi_sii_enable(client); + return rc; +} + + +static struct i2c_driver hdmi_sii_i2c_driver = { + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, + }, + .probe = hdmi_sii_probe, + .remove = __exit_p(hdmi_sii_remove), + .id_table = hmdi_sii_id, +}; + +static int __init hdmi_sii_init(void) +{ + int ret; + struct msm_panel_info pinfo; + + if (msm_fb_detect_client("hdmi_sii9022")) + return 0; + + pinfo.xres = 1280; + pinfo.yres = 720; + MSM_FB_SINGLE_MODE_PANEL(&pinfo); + pinfo.type = HDMI_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 18; + pinfo.fb_num = 2; + pinfo.clk_rate = 74250000; + + pinfo.lcdc.h_back_porch = 124; + pinfo.lcdc.h_front_porch = 110; + pinfo.lcdc.h_pulse_width = 136; + pinfo.lcdc.v_back_porch = 19; + pinfo.lcdc.v_front_porch = 5; + pinfo.lcdc.v_pulse_width = 6; + pinfo.lcdc.border_clr = 0; + pinfo.lcdc.underflow_clr = 0xff; + pinfo.lcdc.hsync_skew = 0; + + ret = lcdc_device_register(&pinfo); + if (ret) { + printk(KERN_ERR "%s: failed to register device\n", __func__); + goto init_exit; + } + + ret = i2c_add_driver(&hdmi_sii_i2c_driver); + if (ret) + printk(KERN_ERR "%s: failed to add i2c driver\n", __func__); + +init_exit: + return ret; +} + +static void __exit hdmi_sii_exit(void) +{ + i2c_del_driver(&hdmi_sii_i2c_driver); +} + +module_init(hdmi_sii_init); +module_exit(hdmi_sii_exit); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); +MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); +MODULE_DESCRIPTION("SiI9022 HDMI driver"); +MODULE_ALIAS("platform:hdmi-sii9022"); diff --git a/drivers/video/msm/lcdc.c b/drivers/video/msm/lcdc.c new file mode 100644 index 00000000000..e3f190727dd --- /dev/null +++ b/drivers/video/msm/lcdc.c @@ -0,0 +1,294 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb.h" + +static int lcdc_probe(struct platform_device *pdev); +static int lcdc_remove(struct platform_device *pdev); + +static int lcdc_off(struct platform_device *pdev); +static int lcdc_on(struct platform_device *pdev); + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; + +static struct clk *pixel_mdp_clk; /* drives the lcdc block in mdp */ +static struct clk *pixel_lcdc_clk; /* drives the lcdc interface */ + +static struct platform_driver lcdc_driver = { + .probe = lcdc_probe, + .remove = lcdc_remove, + .suspend = NULL, + .resume = NULL, + .shutdown = NULL, + .driver = { + .name = "lcdc", + }, +}; + +static struct lcdc_platform_data *lcdc_pdata; + +static int lcdc_off(struct platform_device *pdev) +{ + int ret = 0; + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + ret = panel_next_off(pdev); + + clk_disable(pixel_mdp_clk); + clk_disable(pixel_lcdc_clk); + + if (lcdc_pdata && lcdc_pdata->lcdc_power_save) + lcdc_pdata->lcdc_power_save(0); + + if (lcdc_pdata && lcdc_pdata->lcdc_gpio_config) + ret = lcdc_pdata->lcdc_gpio_config(0); + +#ifndef CONFIG_MSM_BUS_SCALING + if (mdp_rev != MDP_REV_303) { + if (mfd->ebi1_clk) + clk_disable(mfd->ebi1_clk); + } +#else + mdp_bus_scale_update_request(0); +#endif + + return ret; +} + +static int lcdc_on(struct platform_device *pdev) +{ + int ret = 0; + struct msm_fb_data_type *mfd; + unsigned long panel_pixclock_freq = 0; +#ifndef CONFIG_MSM_BUS_SCALING + unsigned long pm_qos_rate; +#endif + mfd = platform_get_drvdata(pdev); + + if (lcdc_pdata && lcdc_pdata->lcdc_get_clk) + panel_pixclock_freq = lcdc_pdata->lcdc_get_clk(); + + if (!panel_pixclock_freq) + panel_pixclock_freq = mfd->fbi->var.pixclock; +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(2); +#else +#ifdef CONFIG_MSM_NPA_SYSTEM_BUS + pm_qos_rate = MSM_AXI_FLOW_MDP_LCDC_WVGA_2BPP; +#else + if (panel_pixclock_freq > 65000000) + /* pm_qos_rate should be in Khz */ + pm_qos_rate = panel_pixclock_freq / 1000 ; + else + pm_qos_rate = 65000; +#endif + + if (mdp_rev != MDP_REV_303) { + if (mfd->ebi1_clk) { + clk_set_rate(mfd->ebi1_clk, pm_qos_rate * 1000); + clk_enable(mfd->ebi1_clk); + } + } +#endif + mfd = platform_get_drvdata(pdev); + + mfd->fbi->var.pixclock = clk_round_rate(pixel_mdp_clk, + mfd->fbi->var.pixclock); + ret = clk_set_rate(pixel_mdp_clk, mfd->fbi->var.pixclock); + if (ret) { + pr_err("%s: Can't set MDP LCDC pixel clock to rate %u\n", + __func__, mfd->fbi->var.pixclock); + goto out; + } + + clk_enable(pixel_mdp_clk); + clk_enable(pixel_lcdc_clk); + + if (lcdc_pdata && lcdc_pdata->lcdc_power_save) + lcdc_pdata->lcdc_power_save(1); + if (lcdc_pdata && lcdc_pdata->lcdc_gpio_config) + ret = lcdc_pdata->lcdc_gpio_config(1); + + ret = panel_next_on(pdev); + +out: + return ret; +} + +static int lcdc_probe(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct fb_info *fbi; + struct platform_device *mdp_dev = NULL; + struct msm_fb_panel_data *pdata = NULL; + int rc; + + if (pdev->id == 0) { + lcdc_pdata = pdev->dev.platform_data; + return 0; + } + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; + + mdp_dev = platform_device_alloc("mdp", pdev->id); + if (!mdp_dev) + return -ENOMEM; + + /* + * link to the latest pdev + */ + mfd->pdev = mdp_dev; + mfd->dest = DISPLAY_LCDC; + + /* + * alloc panel device data + */ + if (platform_device_add_data + (mdp_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + printk(KERN_ERR "lcdc_probe: platform_device_add_data failed!\n"); + platform_device_put(mdp_dev); + return -ENOMEM; + } + /* + * data chain + */ + pdata = (struct msm_fb_panel_data *)mdp_dev->dev.platform_data; + pdata->on = lcdc_on; + pdata->off = lcdc_off; + pdata->next = pdev; + + /* + * get/set panel specific fb info + */ + mfd->panel_info = pdata->panel_info; + + if (mfd->index == 0) + mfd->fb_imgType = MSMFB_DEFAULT_TYPE; + else + mfd->fb_imgType = MDP_RGB_565; + + fbi = mfd->fbi; + fbi->var.pixclock = clk_round_rate(pixel_mdp_clk, + mfd->panel_info.clk_rate); + fbi->var.left_margin = mfd->panel_info.lcdc.h_back_porch; + fbi->var.right_margin = mfd->panel_info.lcdc.h_front_porch; + fbi->var.upper_margin = mfd->panel_info.lcdc.v_back_porch; + fbi->var.lower_margin = mfd->panel_info.lcdc.v_front_porch; + fbi->var.hsync_len = mfd->panel_info.lcdc.h_pulse_width; + fbi->var.vsync_len = mfd->panel_info.lcdc.v_pulse_width; + +#ifndef CONFIG_MSM_BUS_SCALING + mfd->ebi1_clk = clk_get(NULL, "ebi1_lcdc_clk"); + if (IS_ERR(mfd->ebi1_clk)) + return PTR_ERR(mfd->ebi1_clk); +#endif + /* + * set driver data + */ + platform_set_drvdata(mdp_dev, mfd); + /* + * register in mdp driver + */ + rc = platform_device_add(mdp_dev); + if (rc) + goto lcdc_probe_err; + + pdev_list[pdev_list_cnt++] = pdev; + + return 0; + +lcdc_probe_err: + platform_device_put(mdp_dev); + return rc; +} + +static int lcdc_remove(struct platform_device *pdev) +{ +#ifndef CONFIG_MSM_BUS_SCALING + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + + clk_put(mfd->ebi1_clk); +#endif + return 0; +} + +static int lcdc_register_driver(void) +{ + return platform_driver_register(&lcdc_driver); +} + +static int __init lcdc_driver_init(void) +{ + + pixel_mdp_clk = clk_get(NULL, "pixel_mdp_clk"); + if (IS_ERR(pixel_mdp_clk)) + pixel_mdp_clk = NULL; + + if (pixel_mdp_clk) { + pixel_lcdc_clk = clk_get(NULL, "pixel_lcdc_clk"); + if (IS_ERR(pixel_lcdc_clk)) { + printk(KERN_ERR "Couldnt find pixel_lcdc_clk\n"); + return -EINVAL; + } + } else { + pixel_mdp_clk = clk_get(NULL, "mdp_lcdc_pclk_clk"); + if (IS_ERR(pixel_mdp_clk)) { + printk(KERN_ERR "Couldnt find mdp_lcdc_pclk_clk\n"); + return -EINVAL; + } + + pixel_lcdc_clk = clk_get(NULL, "mdp_lcdc_pad_pclk_clk"); + if (IS_ERR(pixel_lcdc_clk)) { + printk(KERN_ERR "Couldnt find mdp_lcdc_pad_pclk_clk\n"); + return -EINVAL; + } + } + + return lcdc_register_driver(); +} + +module_init(lcdc_driver_init); diff --git a/drivers/video/msm/lcdc_auo_wvga.c b/drivers/video/msm/lcdc_auo_wvga.c new file mode 100644 index 00000000000..b1c3af022de --- /dev/null +++ b/drivers/video/msm/lcdc_auo_wvga.c @@ -0,0 +1,413 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#ifdef CONFIG_SPI_QUP +#include +#else +#include +#endif +#include "msm_fb.h" + +#define MAX_BACKLIGHT_LEVEL 15 +#define PANEL_CMD_BACKLIGHT_LEVEL 0x6A18 +#define PANEL_CMD_FORMAT 0x3A00 +#define PANEL_CMD_RGBCTRL 0x3B00 +#define PANEL_CMD_BCTRL 0x5300 +#define PANEL_CMD_PWM_EN 0x6A17 + +#define PANEL_CMD_SLEEP_OUT 0x1100 +#define PANEL_CMD_DISP_ON 0x2900 +#define PANEL_CMD_DISP_OFF 0x2800 +#define PANEL_CMD_SLEEP_IN 0x1000 + +#define LCDC_AUO_PANEL_NAME "lcdc_auo_wvga" + +#ifdef CONFIG_SPI_QUP +#define LCDC_AUO_SPI_DEVICE_NAME "lcdc_auo_nt35582" +static struct spi_device *lcdc_spi_client; +#else +static int spi_cs; +static int spi_sclk; +static int spi_mosi; +#endif + +struct auo_state_type { + boolean display_on; + int bl_level; +}; + + +static struct auo_state_type auo_state = { .bl_level = 10 }; +static struct msm_panel_common_pdata *lcdc_auo_pdata; + +#ifndef CONFIG_SPI_QUP +static void auo_spi_write_byte(u8 data) +{ + uint32 bit; + int bnum; + + bnum = 8; /* 8 data bits */ + bit = 0x80; + while (bnum--) { + gpio_set_value(spi_sclk, 0); /* clk low */ + gpio_set_value(spi_mosi, (data & bit) ? 1 : 0); + udelay(1); + gpio_set_value(spi_sclk, 1); /* clk high */ + udelay(1); + bit >>= 1; + } + gpio_set_value(spi_mosi, 0); +} + +static void auo_spi_read_byte(u16 cmd_16, u8 *data) +{ + int bnum; + u8 cmd_hi = (u8)(cmd_16 >> 8); + u8 cmd_low = (u8)(cmd_16); + + /* Chip Select - low */ + gpio_set_value(spi_cs, 0); + udelay(2); + + /* command byte first */ + auo_spi_write_byte(0x20); + udelay(2); + auo_spi_write_byte(cmd_hi); + udelay(2); + auo_spi_write_byte(0x00); + udelay(2); + auo_spi_write_byte(cmd_low); + udelay(2); + auo_spi_write_byte(0xc0); + udelay(2); + + gpio_direction_input(spi_mosi); + + /* followed by data bytes */ + bnum = 1 * 8; /* number of bits */ + *data = 0; + while (bnum) { + gpio_set_value(spi_sclk, 0); /* clk low */ + udelay(1); + *data <<= 1; + *data |= gpio_get_value(spi_mosi) ? 1 : 0; + gpio_set_value(spi_sclk, 1); /* clk high */ + udelay(1); + --bnum; + if ((bnum % 8) == 0) + ++data; + } + + gpio_direction_output(spi_mosi, 0); + + /* Chip Select - high */ + udelay(2); + gpio_set_value(spi_cs, 1); +} +#endif + +static int auo_serigo(u8 *input_data, int input_len) +{ +#ifdef CONFIG_SPI_QUP + int rc; + struct spi_message m; + struct spi_transfer t; + + if (!lcdc_spi_client) { + pr_err("%s lcdc_spi_client is NULL\n", __func__); + return -EINVAL; + } + + memset(&t, 0, sizeof t); + + t.tx_buf = input_data; + t.len = input_len; + t.bits_per_word = 16; + + spi_setup(lcdc_spi_client); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + rc = spi_sync(lcdc_spi_client, &m); + + return rc; +#else + int i; + + /* Chip Select - low */ + gpio_set_value(spi_cs, 0); + udelay(2); + + for (i = 0; i < input_len; ++i) { + auo_spi_write_byte(input_data[i]); + udelay(2); + } + + /* Chip Select - high */ + gpio_set_value(spi_cs, 1); + + return 0; +#endif +} + +#ifndef CONFIG_SPI_QUP +static void auo_spi_init(void) +{ + spi_sclk = *(lcdc_auo_pdata->gpio_num); + spi_cs = *(lcdc_auo_pdata->gpio_num + 1); + spi_mosi = *(lcdc_auo_pdata->gpio_num + 2); + + /* Set the output so that we don't disturb the slave device */ + gpio_set_value(spi_sclk, 1); + gpio_set_value(spi_mosi, 0); + + /* Set the Chip Select deasserted (active low) */ + gpio_set_value(spi_cs, 1); +} +#endif + +static struct work_struct disp_on_delayed_work; +static void auo_write_cmd(u16 cmd) +{ + u8 local_data[4]; + + local_data[0] = 0x20; + local_data[1] = (u8)(cmd >> 8); + local_data[2] = 0; + local_data[3] = (u8)cmd; + auo_serigo(local_data, 4); +} +static void auo_write_cmd_1param(u16 cmd, u8 para1) +{ + u8 local_data[6]; + + local_data[0] = 0x20; + local_data[1] = (u8)(cmd >> 8); + local_data[2] = 0; + local_data[3] = (u8)cmd; + local_data[4] = 0x40; + local_data[5] = para1; + auo_serigo(local_data, 6); +} +static void lcdc_auo_set_backlight(struct msm_fb_data_type *mfd) +{ + int bl_level; + + bl_level = mfd->bl_level; + if (auo_state.display_on) { + auo_write_cmd_1param(PANEL_CMD_BACKLIGHT_LEVEL, + bl_level * 255 / MAX_BACKLIGHT_LEVEL); + auo_state.bl_level = bl_level; + } + +} +static void auo_disp_on_delayed_work(struct work_struct *work_ptr) +{ + /* 0x1100: Sleep Out */ + auo_write_cmd(PANEL_CMD_SLEEP_OUT); + + msleep(180); + + /* SET_PIXEL_FORMAT: Set how many bits per pixel are used (3A00h)*/ + auo_write_cmd_1param(PANEL_CMD_FORMAT, 0x66); /* 18 bits */ + + /* RGBCTRL: RGB Interface Signal Control (3B00h) */ + auo_write_cmd_1param(PANEL_CMD_RGBCTRL, 0x2B); + + /* Display ON command */ + auo_write_cmd(PANEL_CMD_DISP_ON); + msleep(20); + + /*Backlight on */ + auo_write_cmd_1param(PANEL_CMD_BCTRL, 0x24); /*BCTRL, BL */ + auo_write_cmd_1param(PANEL_CMD_PWM_EN, 0x01); /*Enable PWM Level */ + + msleep(20); +} + +static void auo_disp_on(void) +{ + if (!auo_state.display_on) { + INIT_WORK(&disp_on_delayed_work, auo_disp_on_delayed_work); +#ifdef CONFIG_SPI_QUP + if (lcdc_spi_client) +#endif + schedule_work(&disp_on_delayed_work); + auo_state.display_on = TRUE; + } +} + +static int lcdc_auo_panel_on(struct platform_device *pdev) +{ + pr_info("%s\n", __func__); + if (!auo_state.display_on) { +#ifndef CONFIG_SPI_QUP + lcdc_auo_pdata->panel_config_gpio(1); + auo_spi_init(); +#endif + auo_disp_on(); + } + return 0; +} + +static int lcdc_auo_panel_off(struct platform_device *pdev) +{ + pr_info("%s\n", __func__); + if (auo_state.display_on) { + /* 0x2800: Display Off */ + auo_write_cmd(PANEL_CMD_DISP_OFF); + msleep(120); + /* 0x1000: Sleep In */ + auo_write_cmd(PANEL_CMD_SLEEP_IN); + msleep(120); + + auo_state.display_on = FALSE; + } + return 0; +} + +static int auo_probe(struct platform_device *pdev) +{ + pr_info("%s: id=%d\n", __func__, pdev->id); + if (pdev->id == 0) { + lcdc_auo_pdata = pdev->dev.platform_data; + return 0; + } + + msm_fb_add_device(pdev); + + return 0; +} + +#ifdef CONFIG_SPI_QUP +static int __devinit lcdc_auo_spi_probe(struct spi_device *spi) +{ + pr_info("%s\n", __func__); + lcdc_spi_client = spi; + lcdc_spi_client->bits_per_word = 32; + if (auo_state.display_on) + schedule_work(&disp_on_delayed_work); + return 0; +} +static int __devexit lcdc_auo_spi_remove(struct spi_device *spi) +{ + lcdc_spi_client = NULL; + return 0; +} +static struct spi_driver lcdc_auo_spi_driver = { + .driver.name = LCDC_AUO_SPI_DEVICE_NAME, + .driver.owner = THIS_MODULE, + .probe = lcdc_auo_spi_probe, + .remove = __devexit_p(lcdc_auo_spi_remove), +}; +#endif + +static struct platform_driver this_driver = { + .probe = auo_probe, + .driver.name = LCDC_AUO_PANEL_NAME, +}; + +static struct msm_fb_panel_data auo_panel_data = { + .on = lcdc_auo_panel_on, + .off = lcdc_auo_panel_off, + .set_backlight = lcdc_auo_set_backlight, +}; + +static struct platform_device this_device = { + .name = LCDC_AUO_PANEL_NAME, + .id = 1, + .dev.platform_data = &auo_panel_data, +}; + +static int __init lcdc_auo_panel_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_LCDC_AUTO_DETECT + if (msm_fb_detect_client(LCDC_AUO_PANEL_NAME)) { + pr_err("%s: detect failed\n", __func__); + return 0; + } +#endif + + ret = platform_driver_register(&this_driver); + if (ret) { + pr_err("%s: driver register failed, rc=%d\n", __func__, ret); + return ret; + } + + pinfo = &auo_panel_data.panel_info; + pinfo->xres = 480; + pinfo->yres = 800; + pinfo->type = LCDC_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->fb_num = 2; + pinfo->clk_rate = 25600000; + pinfo->bl_max = MAX_BACKLIGHT_LEVEL; + pinfo->bl_min = 1; + + pinfo->lcdc.h_back_porch = 16-2; /* HBP-HLW */ + pinfo->lcdc.h_front_porch = 16; + pinfo->lcdc.h_pulse_width = 2; + + pinfo->lcdc.v_back_porch = 3-2; /* VBP-VLW */ + pinfo->lcdc.v_front_porch = 28; + pinfo->lcdc.v_pulse_width = 2; + + pinfo->lcdc.border_clr = 0; + pinfo->lcdc.underflow_clr = 0xff; + pinfo->lcdc.hsync_skew = 0; + + ret = platform_device_register(&this_device); + if (ret) { + pr_err("%s: device register failed, rc=%d\n", __func__, ret); + goto fail_driver; + } +#ifdef CONFIG_SPI_QUP + ret = spi_register_driver(&lcdc_auo_spi_driver); + + if (ret) { + pr_err("%s: spi register failed: rc=%d\n", __func__, ret); + goto fail_device; + } + pr_info("%s: SUCCESS (SPI)\n", __func__); +#else + pr_info("%s: SUCCESS (BitBang)\n", __func__); +#endif + return ret; + +#ifdef CONFIG_SPI_QUP +fail_device: + platform_device_unregister(&this_device); +#endif +fail_driver: + platform_driver_unregister(&this_driver); + + return ret; +} + +module_init(lcdc_auo_panel_init); +static void __exit lcdc_auo_panel_exit(void) +{ + pr_info("%s\n", __func__); + platform_device_unregister(&this_device); + platform_driver_unregister(&this_driver); +#ifdef CONFIG_SPI_QUP + spi_unregister_driver(&lcdc_auo_spi_driver); +#endif +} +module_exit(lcdc_auo_panel_exit); diff --git a/drivers/video/msm/lcdc_chimei_wxga.c b/drivers/video/msm/lcdc_chimei_wxga.c new file mode 100644 index 00000000000..b5cce2c3fef --- /dev/null +++ b/drivers/video/msm/lcdc_chimei_wxga.c @@ -0,0 +1,233 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#ifdef CONFIG_PMIC8058_PWM +#include +#include +#endif +#include +#include "msm_fb.h" + + + + +static struct pwm_device *bl_pwm; + +#define PWM_FREQ_HZ 210 +#define PWM_PERIOD_USEC (USEC_PER_SEC / PWM_FREQ_HZ) +#define PWM_DUTY_LEVEL (PWM_PERIOD_USEC / PWM_LEVEL) +#define PWM_LEVEL 15 + +static struct msm_panel_common_pdata *cm_pdata; +static struct platform_device *cm_fbpdev; +static int led_pwm; /* pm8058 gpio 24, channel 0 */ +static int led_en; /* pm8058 gpio 1 */ +static int lvds_pwr_down; /* msm gpio 30 */ +static int chimei_bl_level = 1; + + +static void lcdc_chimei_set_backlight(int level) +{ + int ret; + + if (bl_pwm) { + ret = pwm_config(bl_pwm, PWM_DUTY_LEVEL * level, + PWM_PERIOD_USEC); + if (ret) { + pr_err("%s: pwm_config on pwm failed %d\n", + __func__, ret); + return; + } + + ret = pwm_enable(bl_pwm); + if (ret) { + pr_err("%s: pwm_enable on pwm failed %d\n", + __func__, ret); + return; + } + } + + chimei_bl_level = level; +} + +static int lcdc_chimei_panel_on(struct platform_device *pdev) +{ + int ret; + + /* panel powered on here */ + + ret = gpio_request(lvds_pwr_down, "lvds_pwr_down"); + if (ret == 0) { + /* output, pull high to enable */ + gpio_direction_output(lvds_pwr_down, 1); + } else { + pr_err("%s: lvds_pwr_down=%d, gpio_request failed\n", + __func__, lvds_pwr_down); + } + + msleep(200); + /* power on led pwm power >= 200 ms */ + + if (chimei_bl_level == 0) + chimei_bl_level = 1; + lcdc_chimei_set_backlight(chimei_bl_level); + + msleep(10); + + ret = gpio_request(led_en, "led_en"); + if (ret == 0) { + /* output, pull high */ + gpio_direction_output(led_en, 1); + } else { + pr_err("%s: led_en=%d, gpio_request failed\n", + __func__, led_en); + } + return ret; +} + +static int lcdc_chimei_panel_off(struct platform_device *pdev) +{ + /* pull low to disable */ + gpio_set_value_cansleep(led_en, 0); + gpio_free(led_en); + + msleep(10); + + lcdc_chimei_set_backlight(0); + + msleep(200); + /* power off led pwm power >= 200 ms */ + + /* pull low to shut down lvds */ + gpio_set_value_cansleep(lvds_pwr_down, 0); + gpio_free(lvds_pwr_down); + + /* panel power off here */ + + return 0; +} + +static void lcdc_chimei_panel_backlight(struct msm_fb_data_type *mfd) +{ + lcdc_chimei_set_backlight(mfd->bl_level); +} + +static int __devinit chimei_probe(struct platform_device *pdev) +{ + int rc = 0; + + if (pdev->id == 0) { + cm_pdata = pdev->dev.platform_data; + if (cm_pdata == NULL) { + pr_err("%s: no PWM gpio specified\n", __func__); + return 0; + } + led_pwm = cm_pdata->gpio_num[0]; + led_en = cm_pdata->gpio_num[1]; + lvds_pwr_down = cm_pdata->gpio_num[2]; + pr_info("%s: led_pwm=%d led_en=%d lvds_pwr_down=%d\n", + __func__, led_pwm, led_en, lvds_pwr_down); + return 0; + } + + if (cm_pdata == NULL) + return -ENODEV; + + bl_pwm = pwm_request(led_pwm, "backlight"); + if (bl_pwm == NULL || IS_ERR(bl_pwm)) { + pr_err("%s pwm_request() failed\n", __func__); + bl_pwm = NULL; + } + + cm_fbpdev = msm_fb_add_device(pdev); + if (!cm_fbpdev) { + dev_err(&pdev->dev, "failed to add msm_fb device\n"); + rc = -ENODEV; + goto probe_exit; + } + +probe_exit: + return rc; +} + +static struct platform_driver this_driver = { + .probe = chimei_probe, + .driver = { + .name = "lcdc_chimei_lvds_wxga", + }, +}; + +static struct msm_fb_panel_data chimei_panel_data = { + .on = lcdc_chimei_panel_on, + .off = lcdc_chimei_panel_off, + .set_backlight = lcdc_chimei_panel_backlight, +}; + +static struct platform_device this_device = { + .name = "lcdc_chimei_lvds_wxga", + .id = 1, + .dev = { + .platform_data = &chimei_panel_data, + } +}; + +static int __init lcdc_chimei_lvds_panel_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_MIPI_PANEL_DETECT + if (msm_fb_detect_client("lcdc_chimei_lvds_wxga")) + return 0; +#endif + + ret = platform_driver_register(&this_driver); + if (ret) + return ret; + + pinfo = &chimei_panel_data.panel_info; + pinfo->xres = 1366; + pinfo->yres = 768; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = LCDC_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->fb_num = 2; + pinfo->clk_rate = 69300000; + pinfo->bl_max = PWM_LEVEL; + pinfo->bl_min = 1; + + /* + * this panel is operated by de, + * vsycn and hsync are ignored + */ + pinfo->lcdc.h_back_porch = 108; + pinfo->lcdc.h_front_porch = 0; + pinfo->lcdc.h_pulse_width = 1; + pinfo->lcdc.v_back_porch = 0; + pinfo->lcdc.v_front_porch = 16; + pinfo->lcdc.v_pulse_width = 1; + pinfo->lcdc.border_clr = 0; + pinfo->lcdc.underflow_clr = 0xff; + pinfo->lcdc.hsync_skew = 0; + + ret = platform_device_register(&this_device); + if (ret) + platform_driver_unregister(&this_driver); + + return ret; +} + +module_init(lcdc_chimei_lvds_panel_init); diff --git a/drivers/video/msm/lcdc_external.c b/drivers/video/msm/lcdc_external.c new file mode 100644 index 00000000000..ca82def6aef --- /dev/null +++ b/drivers/video/msm/lcdc_external.c @@ -0,0 +1,51 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" + +static int __init lcdc_external_init(void) +{ + int ret; + struct msm_panel_info pinfo; + + if (msm_fb_detect_client("lcdc_external")) + return 0; + + pinfo.xres = 1280; + pinfo.yres = 720; + MSM_FB_SINGLE_MODE_PANEL(&pinfo); + pinfo.type = LCDC_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.fb_num = 2; + pinfo.clk_rate = 74250000; + + pinfo.lcdc.h_back_porch = 124; + pinfo.lcdc.h_front_porch = 110; + pinfo.lcdc.h_pulse_width = 136; + pinfo.lcdc.v_back_porch = 19; + pinfo.lcdc.v_front_porch = 5; + pinfo.lcdc.v_pulse_width = 6; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + + ret = lcdc_device_register(&pinfo); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(lcdc_external_init); diff --git a/drivers/video/msm/lcdc_gordon.c b/drivers/video/msm/lcdc_gordon.c new file mode 100644 index 00000000000..f9532b480fe --- /dev/null +++ b/drivers/video/msm/lcdc_gordon.c @@ -0,0 +1,443 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include "msm_fb.h" + +/* registers */ +#define GORDON_REG_NOP 0x00 +#define GORDON_REG_IMGCTL1 0x10 +#define GORDON_REG_IMGCTL2 0x11 +#define GORDON_REG_IMGSET1 0x12 +#define GORDON_REG_IMGSET2 0x13 +#define GORDON_REG_IVBP1 0x14 +#define GORDON_REG_IHBP1 0x15 +#define GORDON_REG_IVNUM1 0x16 +#define GORDON_REG_IHNUM1 0x17 +#define GORDON_REG_IVBP2 0x18 +#define GORDON_REG_IHBP2 0x19 +#define GORDON_REG_IVNUM2 0x1A +#define GORDON_REG_IHNUM2 0x1B +#define GORDON_REG_LCDIFCTL1 0x30 +#define GORDON_REG_VALTRAN 0x31 +#define GORDON_REG_AVCTL 0x33 +#define GORDON_REG_LCDIFCTL2 0x34 +#define GORDON_REG_LCDIFCTL3 0x35 +#define GORDON_REG_LCDIFSET1 0x36 +#define GORDON_REG_PCCTL 0x3C +#define GORDON_REG_TPARAM1 0x40 +#define GORDON_REG_TLCDIF1 0x41 +#define GORDON_REG_TSSPB_ST1 0x42 +#define GORDON_REG_TSSPB_ED1 0x43 +#define GORDON_REG_TSCK_ST1 0x44 +#define GORDON_REG_TSCK_WD1 0x45 +#define GORDON_REG_TGSPB_VST1 0x46 +#define GORDON_REG_TGSPB_VED1 0x47 +#define GORDON_REG_TGSPB_CH1 0x48 +#define GORDON_REG_TGCK_ST1 0x49 +#define GORDON_REG_TGCK_ED1 0x4A +#define GORDON_REG_TPCTL_ST1 0x4B +#define GORDON_REG_TPCTL_ED1 0x4C +#define GORDON_REG_TPCHG_ED1 0x4D +#define GORDON_REG_TCOM_CH1 0x4E +#define GORDON_REG_THBP1 0x4F +#define GORDON_REG_TPHCTL1 0x50 +#define GORDON_REG_EVPH1 0x51 +#define GORDON_REG_EVPL1 0x52 +#define GORDON_REG_EVNH1 0x53 +#define GORDON_REG_EVNL1 0x54 +#define GORDON_REG_TBIAS1 0x55 +#define GORDON_REG_TPARAM2 0x56 +#define GORDON_REG_TLCDIF2 0x57 +#define GORDON_REG_TSSPB_ST2 0x58 +#define GORDON_REG_TSSPB_ED2 0x59 +#define GORDON_REG_TSCK_ST2 0x5A +#define GORDON_REG_TSCK_WD2 0x5B +#define GORDON_REG_TGSPB_VST2 0x5C +#define GORDON_REG_TGSPB_VED2 0x5D +#define GORDON_REG_TGSPB_CH2 0x5E +#define GORDON_REG_TGCK_ST2 0x5F +#define GORDON_REG_TGCK_ED2 0x60 +#define GORDON_REG_TPCTL_ST2 0x61 +#define GORDON_REG_TPCTL_ED2 0x62 +#define GORDON_REG_TPCHG_ED2 0x63 +#define GORDON_REG_TCOM_CH2 0x64 +#define GORDON_REG_THBP2 0x65 +#define GORDON_REG_TPHCTL2 0x66 +#define GORDON_REG_POWCTL 0x80 + +static int lcdc_gordon_panel_off(struct platform_device *pdev); + +static int spi_cs; +static int spi_sclk; +static int spi_sdo; +static int spi_sdi; +static int spi_dac; +static unsigned char bit_shift[8] = { (1 << 7), /* MSB */ + (1 << 6), + (1 << 5), + (1 << 4), + (1 << 3), + (1 << 2), + (1 << 1), + (1 << 0) /* LSB */ +}; + +struct gordon_state_type{ + boolean disp_initialized; + boolean display_on; + boolean disp_powered_up; +}; + +static struct gordon_state_type gordon_state = { 0 }; +static struct msm_panel_common_pdata *lcdc_gordon_pdata; + +static void serigo(uint16 reg, uint8 data) +{ + unsigned int tx_val = ((0x00FF & reg) << 8) | data; + unsigned char i, val = 0; + + /* Enable the Chip Select */ + gpio_set_value(spi_cs, 1); + udelay(33); + + /* Transmit it in two parts, Higher Byte first, then Lower Byte */ + val = (unsigned char)((tx_val & 0xFF00) >> 8); + + /* Clock should be Low before entering ! */ + for (i = 0; i < 8; i++) { + /* #1: Drive the Data (High or Low) */ + if (val & bit_shift[i]) + gpio_set_value(spi_sdi, 1); + else + gpio_set_value(spi_sdi, 0); + + /* #2: Drive the Clk High and then Low */ + udelay(33); + gpio_set_value(spi_sclk, 1); + udelay(33); + gpio_set_value(spi_sclk, 0); + } + + /* Idle state of SDO (MOSI) is Low */ + gpio_set_value(spi_sdi, 0); + /* ..then Lower Byte */ + val = (uint8) (tx_val & 0x00FF); + /* Before we enter here the Clock should be Low ! */ + + for (i = 0; i < 8; i++) { + /* #1: Drive the Data (High or Low) */ + if (val & bit_shift[i]) + gpio_set_value(spi_sdi, 1); + else + gpio_set_value(spi_sdi, 0); + + /* #2: Drive the Clk High and then Low */ + udelay(33); + + gpio_set_value(spi_sclk, 1); + udelay(33); + gpio_set_value(spi_sclk, 0); + } + + /* Idle state of SDO (MOSI) is Low */ + gpio_set_value(spi_sdi, 0); + + /* Now Disable the Chip Select */ + udelay(33); + gpio_set_value(spi_cs, 0); +} + +static void spi_init(void) +{ + /* Setting the Default GPIO's */ + spi_sclk = *(lcdc_gordon_pdata->gpio_num); + spi_cs = *(lcdc_gordon_pdata->gpio_num + 1); + spi_sdi = *(lcdc_gordon_pdata->gpio_num + 2); + spi_sdo = *(lcdc_gordon_pdata->gpio_num + 3); + + /* Set the output so that we dont disturb the slave device */ + gpio_set_value(spi_sclk, 0); + gpio_set_value(spi_sdi, 0); + + /* Set the Chip Select De-asserted */ + gpio_set_value(spi_cs, 0); + +} + +static void gordon_disp_powerup(void) +{ + if (!gordon_state.disp_powered_up && !gordon_state.display_on) { + /* Reset the hardware first */ + /* Include DAC power up implementation here */ + gordon_state.disp_powered_up = TRUE; + } +} + +static void gordon_init(void) +{ + /* Image interface settings */ + serigo(GORDON_REG_IMGCTL2, 0x00); + serigo(GORDON_REG_IMGSET1, 0x00); + + /* Exchange the RGB signal for J510(Softbank mobile) */ + serigo(GORDON_REG_IMGSET2, 0x12); + serigo(GORDON_REG_LCDIFSET1, 0x00); + + /* Pre-charge settings */ + serigo(GORDON_REG_PCCTL, 0x09); + serigo(GORDON_REG_LCDIFCTL2, 0x7B); + + mdelay(1); +} + +static void gordon_disp_on(void) +{ + if (gordon_state.disp_powered_up && !gordon_state.display_on) { + gordon_init(); + mdelay(20); + /* gordon_dispmode setting */ + serigo(GORDON_REG_TPARAM1, 0x30); + serigo(GORDON_REG_TLCDIF1, 0x00); + serigo(GORDON_REG_TSSPB_ST1, 0x8B); + serigo(GORDON_REG_TSSPB_ED1, 0x93); + serigo(GORDON_REG_TSCK_ST1, 0x88); + serigo(GORDON_REG_TSCK_WD1, 0x00); + serigo(GORDON_REG_TGSPB_VST1, 0x01); + serigo(GORDON_REG_TGSPB_VED1, 0x02); + serigo(GORDON_REG_TGSPB_CH1, 0x5E); + serigo(GORDON_REG_TGCK_ST1, 0x80); + serigo(GORDON_REG_TGCK_ED1, 0x3C); + serigo(GORDON_REG_TPCTL_ST1, 0x50); + serigo(GORDON_REG_TPCTL_ED1, 0x74); + serigo(GORDON_REG_TPCHG_ED1, 0x78); + serigo(GORDON_REG_TCOM_CH1, 0x50); + serigo(GORDON_REG_THBP1, 0x84); + serigo(GORDON_REG_TPHCTL1, 0x00); + serigo(GORDON_REG_EVPH1, 0x70); + serigo(GORDON_REG_EVPL1, 0x64); + serigo(GORDON_REG_EVNH1, 0x56); + serigo(GORDON_REG_EVNL1, 0x48); + serigo(GORDON_REG_TBIAS1, 0x88); + + /* QVGA settings */ + serigo(GORDON_REG_TPARAM2, 0x28); + serigo(GORDON_REG_TLCDIF2, 0x14); + serigo(GORDON_REG_TSSPB_ST2, 0x49); + serigo(GORDON_REG_TSSPB_ED2, 0x4B); + serigo(GORDON_REG_TSCK_ST2, 0x4A); + serigo(GORDON_REG_TSCK_WD2, 0x02); + serigo(GORDON_REG_TGSPB_VST2, 0x02); + serigo(GORDON_REG_TGSPB_VED2, 0x03); + serigo(GORDON_REG_TGSPB_CH2, 0x2F); + serigo(GORDON_REG_TGCK_ST2, 0x40); + serigo(GORDON_REG_TGCK_ED2, 0x1E); + serigo(GORDON_REG_TPCTL_ST2, 0x2C); + serigo(GORDON_REG_TPCTL_ED2, 0x3A); + serigo(GORDON_REG_TPCHG_ED2, 0x3C); + serigo(GORDON_REG_TCOM_CH2, 0x28); + serigo(GORDON_REG_THBP2, 0x4D); + serigo(GORDON_REG_TPHCTL2, 0x1A); + + /* VGA settings */ + serigo(GORDON_REG_IVBP1, 0x02); + serigo(GORDON_REG_IHBP1, 0x90); + serigo(GORDON_REG_IVNUM1, 0xA0); + serigo(GORDON_REG_IHNUM1, 0x78); + + /* QVGA settings */ + serigo(GORDON_REG_IVBP2, 0x02); + serigo(GORDON_REG_IHBP2, 0x48); + serigo(GORDON_REG_IVNUM2, 0x50); + serigo(GORDON_REG_IHNUM2, 0x3C); + + /* Gordon Charge pump settings and ON */ + serigo(GORDON_REG_POWCTL, 0x03); + mdelay(15); + serigo(GORDON_REG_POWCTL, 0x07); + mdelay(15); + + serigo(GORDON_REG_POWCTL, 0x0F); + mdelay(15); + + serigo(GORDON_REG_AVCTL, 0x03); + mdelay(15); + + serigo(GORDON_REG_POWCTL, 0x1F); + mdelay(15); + + serigo(GORDON_REG_POWCTL, 0x5F); + mdelay(15); + + serigo(GORDON_REG_POWCTL, 0x7F); + mdelay(15); + + serigo(GORDON_REG_LCDIFCTL1, 0x02); + mdelay(15); + + serigo(GORDON_REG_IMGCTL1, 0x00); + mdelay(15); + + serigo(GORDON_REG_LCDIFCTL3, 0x00); + mdelay(15); + + serigo(GORDON_REG_VALTRAN, 0x01); + mdelay(15); + + serigo(GORDON_REG_LCDIFCTL1, 0x03); + mdelay(1); + gordon_state.display_on = TRUE; + } +} + +static int lcdc_gordon_panel_on(struct platform_device *pdev) +{ + if (!gordon_state.disp_initialized) { + /* Configure reset GPIO that drives DAC */ + lcdc_gordon_pdata->panel_config_gpio(1); + spi_dac = *(lcdc_gordon_pdata->gpio_num + 4); + gpio_set_value(spi_dac, 0); + udelay(15); + gpio_set_value(spi_dac, 1); + spi_init(); /* LCD needs SPI */ + gordon_disp_powerup(); + gordon_disp_on(); + gordon_state.disp_initialized = TRUE; + } + return 0; +} + +static int lcdc_gordon_panel_off(struct platform_device *pdev) +{ + if (gordon_state.disp_powered_up && gordon_state.display_on) { + serigo(GORDON_REG_LCDIFCTL2, 0x7B); + serigo(GORDON_REG_VALTRAN, 0x01); + serigo(GORDON_REG_LCDIFCTL1, 0x02); + serigo(GORDON_REG_LCDIFCTL3, 0x01); + mdelay(20); + serigo(GORDON_REG_VALTRAN, 0x01); + serigo(GORDON_REG_IMGCTL1, 0x01); + serigo(GORDON_REG_LCDIFCTL1, 0x00); + mdelay(20); + + serigo(GORDON_REG_POWCTL, 0x1F); + mdelay(40); + + serigo(GORDON_REG_POWCTL, 0x07); + mdelay(40); + + serigo(GORDON_REG_POWCTL, 0x03); + mdelay(40); + + serigo(GORDON_REG_POWCTL, 0x00); + mdelay(40); + lcdc_gordon_pdata->panel_config_gpio(0); + gordon_state.display_on = FALSE; + gordon_state.disp_initialized = FALSE; + } + return 0; +} + +static void lcdc_gordon_set_backlight(struct msm_fb_data_type *mfd) +{ + int bl_level = mfd->bl_level; + + if (bl_level <= 1) { + /* keep back light OFF */ + serigo(GORDON_REG_LCDIFCTL2, 0x0B); + udelay(15); + serigo(GORDON_REG_VALTRAN, 0x01); + } else { + /* keep back light ON */ + serigo(GORDON_REG_LCDIFCTL2, 0x7B); + udelay(15); + serigo(GORDON_REG_VALTRAN, 0x01); + } +} + +static int __devinit gordon_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + lcdc_gordon_pdata = pdev->dev.platform_data; + return 0; + } + msm_fb_add_device(pdev); + return 0; +} + +static struct platform_driver this_driver = { + .probe = gordon_probe, + .driver = { + .name = "lcdc_gordon_vga", + }, +}; + +static struct msm_fb_panel_data gordon_panel_data = { + .on = lcdc_gordon_panel_on, + .off = lcdc_gordon_panel_off, + .set_backlight = lcdc_gordon_set_backlight, +}; + +static struct platform_device this_device = { + .name = "lcdc_gordon_vga", + .id = 1, + .dev = { + .platform_data = &gordon_panel_data, + } +}; + +static int __init lcdc_gordon_panel_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM + if (msm_fb_detect_client("lcdc_gordon_vga")) + return 0; +#endif + ret = platform_driver_register(&this_driver); + if (ret) + return ret; + + pinfo = &gordon_panel_data.panel_info; + pinfo->xres = 480; + pinfo->yres = 640; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = LCDC_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 24; + pinfo->fb_num = 2; + pinfo->clk_rate = 24500000; + pinfo->bl_max = 4; + pinfo->bl_min = 1; + + pinfo->lcdc.h_back_porch = 84; + pinfo->lcdc.h_front_porch = 33; + pinfo->lcdc.h_pulse_width = 60; + pinfo->lcdc.v_back_porch = 0; + pinfo->lcdc.v_front_porch = 2; + pinfo->lcdc.v_pulse_width = 2; + pinfo->lcdc.border_clr = 0; /* blk */ + pinfo->lcdc.underflow_clr = 0xff; /* blue */ + pinfo->lcdc.hsync_skew = 0; + + ret = platform_device_register(&this_device); + if (ret) + platform_driver_unregister(&this_driver); + + return ret; +} + +module_init(lcdc_gordon_panel_init); diff --git a/drivers/video/msm/lcdc_panel.c b/drivers/video/msm/lcdc_panel.c new file mode 100644 index 00000000000..57053255579 --- /dev/null +++ b/drivers/video/msm/lcdc_panel.c @@ -0,0 +1,84 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" + +static int lcdc_panel_on(struct platform_device *pdev) +{ + return 0; +} + +static int lcdc_panel_off(struct platform_device *pdev) +{ + return 0; +} + +static int __devinit lcdc_panel_probe(struct platform_device *pdev) +{ + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = lcdc_panel_probe, + .driver = { + .name = "lcdc_panel", + }, +}; + +static struct msm_fb_panel_data lcdc_panel_data = { + .on = lcdc_panel_on, + .off = lcdc_panel_off, +}; + +static int lcdc_dev_id; + +int lcdc_device_register(struct msm_panel_info *pinfo) +{ + struct platform_device *pdev = NULL; + int ret; + + pdev = platform_device_alloc("lcdc_panel", ++lcdc_dev_id); + if (!pdev) + return -ENOMEM; + + lcdc_panel_data.panel_info = *pinfo; + ret = platform_device_add_data(pdev, &lcdc_panel_data, + sizeof(lcdc_panel_data)); + if (ret) { + printk(KERN_ERR + "%s: platform_device_add_data failed!\n", __func__); + goto err_device_put; + } + + ret = platform_device_add(pdev); + if (ret) { + printk(KERN_ERR + "%s: platform_device_register failed!\n", __func__); + goto err_device_put; + } + + return 0; + +err_device_put: + platform_device_put(pdev); + return ret; +} + +static int __init lcdc_panel_init(void) +{ + return platform_driver_register(&this_driver); +} + +module_init(lcdc_panel_init); diff --git a/drivers/video/msm/lcdc_prism.c b/drivers/video/msm/lcdc_prism.c new file mode 100644 index 00000000000..d127f63b912 --- /dev/null +++ b/drivers/video/msm/lcdc_prism.c @@ -0,0 +1,61 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" + +#ifdef CONFIG_FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM +#include "mddihosti.h" +#endif + +static int __init lcdc_prism_init(void) +{ + int ret; + struct msm_panel_info pinfo; + +#ifdef CONFIG_FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM + ret = msm_fb_detect_client("lcdc_prism_wvga"); + if (ret == -ENODEV) + return 0; + + if (ret && (mddi_get_client_id() != 0)) + return 0; +#endif + + pinfo.xres = 800; + pinfo.yres = 480; + MSM_FB_SINGLE_MODE_PANEL(&pinfo); + pinfo.type = LCDC_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.fb_num = 2; + pinfo.clk_rate = 30720000; + + pinfo.lcdc.h_back_porch = 21; + pinfo.lcdc.h_front_porch = 81; + pinfo.lcdc.h_pulse_width = 60; + pinfo.lcdc.v_back_porch = 18; + pinfo.lcdc.v_front_porch = 27; + pinfo.lcdc.v_pulse_width = 2; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + + ret = lcdc_device_register(&pinfo); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(lcdc_prism_init); diff --git a/drivers/video/msm/lcdc_samsung_oled_pt.c b/drivers/video/msm/lcdc_samsung_oled_pt.c new file mode 100644 index 00000000000..dccc99728b7 --- /dev/null +++ b/drivers/video/msm/lcdc_samsung_oled_pt.c @@ -0,0 +1,590 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#ifdef CONFIG_SPI_QUP +#include +#else +#include +#endif +#include "msm_fb.h" + +#define DEBUG +/* #define SYSFS_DEBUG_CMD */ + +#ifdef CONFIG_SPI_QUP +#define LCDC_SAMSUNG_SPI_DEVICE_NAME "lcdc_samsung_ams367pe02" +static struct spi_device *lcdc_spi_client; +#else +static int spi_cs; +static int spi_sclk; +static int spi_mosi; +#endif + +struct samsung_state_type { + boolean disp_initialized; + boolean display_on; + boolean disp_powered_up; + int brightness; +}; + +struct samsung_spi_data { + u8 addr; + u8 len; + u8 data[22]; +}; + +static struct samsung_spi_data panel_sequence[] = { + { .addr = 0xf8, .len = 14, .data = { 0x01, 0x27, 0x27, 0x07, 0x07, + 0x54, 0x9f, 0x63, 0x86, 0x1a, 0x33, 0x0d, 0x00, 0x00 } }, +}; +static struct samsung_spi_data display_sequence[] = { + { .addr = 0xf2, .len = 5, .data = { 0x02, 0x03, 0x1c, 0x10, 0x10 } }, + { .addr = 0xf7, .len = 3, .data = { 0x00, 0x00, 0x30 } }, +}; + +/* lum=300 cd/m2 */ +static struct samsung_spi_data gamma_sequence_300[] = { + { .addr = 0xfa, .len = 22, .data = { 0x02, 0x18, 0x08, 0x24, 0x7d, 0x77, + 0x5b, 0xbe, 0xc1, 0xb1, 0xb3, 0xb7, 0xa6, 0xc3, 0xc5, 0xb9, 0x00, 0xb3, + 0x00, 0xaf, 0x00, 0xe8 } }, + { .addr = 0xFA, .len = 1, .data = { 0x03 } }, +}; +/* lum = 180 cd/m2*/ +static struct samsung_spi_data gamma_sequence_180[] = { + { .addr = 0xfa, .len = 22, .data = { 0x02, 0x18, 0x08, 0x24, 0x83, 0x78, + 0x60, 0xc5, 0xc6, 0xb8, 0xba, 0xbe, 0xad, 0xcb, 0xcd, 0xc2, 0x00, 0x92, + 0x00, 0x8e, 0x00, 0xbc } }, + { .addr = 0xFA, .len = 1, .data = { 0x03 } }, +}; +/* lum = 80 cd/m2*/ +static struct samsung_spi_data gamma_sequence_80[] = { + { .addr = 0xfa, .len = 22, .data = { 0x02, 0x18, 0x08, 0x24, 0x94, 0x73, + 0x6c, 0xcb, 0xca, 0xbe, 0xc4, 0xc7, 0xb8, 0xd3, 0xd5, 0xcb, 0x00, 0x6d, + 0x00, 0x69, 0x00, 0x8b } }, + { .addr = 0xFA, .len = 1, .data = { 0x03 } }, +}; + +static struct samsung_spi_data etc_sequence[] = { + { .addr = 0xF6, .len = 3, .data = { 0x00, 0x8e, 0x07 } }, + { .addr = 0xB3, .len = 1, .data = { 0x0C } }, +}; + +static struct samsung_state_type samsung_state = { .brightness = 180 }; +static struct msm_panel_common_pdata *lcdc_samsung_pdata; + +#ifndef CONFIG_SPI_QUP +static void samsung_spi_write_byte(boolean dc, u8 data) +{ + uint32 bit; + int bnum; + + gpio_set_value(spi_sclk, 0); + gpio_set_value(spi_mosi, dc ? 1 : 0); + udelay(1); /* at least 20 ns */ + gpio_set_value(spi_sclk, 1); /* clk high */ + udelay(1); /* at least 20 ns */ + + bnum = 8; /* 8 data bits */ + bit = 0x80; + while (bnum--) { + gpio_set_value(spi_sclk, 0); /* clk low */ + gpio_set_value(spi_mosi, (data & bit) ? 1 : 0); + udelay(1); + gpio_set_value(spi_sclk, 1); /* clk high */ + udelay(1); + bit >>= 1; + } + gpio_set_value(spi_mosi, 0); + +} + +static void samsung_spi_read_bytes(u8 cmd, u8 *data, int num) +{ + int bnum; + + /* Chip Select - low */ + gpio_set_value(spi_cs, 0); + udelay(2); + + /* command byte first */ + samsung_spi_write_byte(0, cmd); + udelay(2); + + gpio_direction_input(spi_mosi); + + if (num > 1) { + /* extra dummy clock */ + gpio_set_value(spi_sclk, 0); + udelay(1); + gpio_set_value(spi_sclk, 1); + udelay(1); + } + + /* followed by data bytes */ + bnum = num * 8; /* number of bits */ + *data = 0; + while (bnum) { + gpio_set_value(spi_sclk, 0); /* clk low */ + udelay(1); + *data <<= 1; + *data |= gpio_get_value(spi_mosi) ? 1 : 0; + gpio_set_value(spi_sclk, 1); /* clk high */ + udelay(1); + --bnum; + if ((bnum % 8) == 0) + ++data; + } + + gpio_direction_output(spi_mosi, 0); + + /* Chip Select - high */ + udelay(2); + gpio_set_value(spi_cs, 1); +} +#endif + +#ifdef DEBUG +static const char *byte_to_binary(const u8 *buf, int len) +{ + static char b[32*8+1]; + char *p = b; + int i, z; + + for (i = 0; i < len; ++i) { + u8 val = *buf++; + for (z = 1 << 7; z > 0; z >>= 1) + *p++ = (val & z) ? '1' : '0'; + } + *p = 0; + + return b; +} +#endif + +#define BIT_OFFSET (bit_size % 8) +#define ADD_BIT(val) do { \ + tx_buf[bit_size / 8] |= \ + (u8)((val ? 1 : 0) << (7 - BIT_OFFSET)); \ + ++bit_size; \ + } while (0) + +#define ADD_BYTE(data) do { \ + tx_buf[bit_size / 8] |= (u8)(data >> BIT_OFFSET); \ + bit_size += 8; \ + if (BIT_OFFSET != 0) \ + tx_buf[bit_size / 8] |= (u8)(data << (8 - BIT_OFFSET));\ + } while (0) + +static int samsung_serigo(struct samsung_spi_data data) +{ +#ifdef CONFIG_SPI_QUP + char tx_buf[32]; + int bit_size = 0, i, rc; + struct spi_message m; + struct spi_transfer t; + + if (!lcdc_spi_client) { + pr_err("%s lcdc_spi_client is NULL\n", __func__); + return -EINVAL; + } + + memset(&t, 0, sizeof t); + memset(tx_buf, 0, sizeof tx_buf); + t.tx_buf = tx_buf; + spi_setup(lcdc_spi_client); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + ADD_BIT(FALSE); + ADD_BYTE(data.addr); + for (i = 0; i < data.len; ++i) { + ADD_BIT(TRUE); + ADD_BYTE(data.data[i]); + } + + /* add padding bits so we round to next byte */ + t.len = (bit_size+7) / 8; + if (t.len <= 4) + t.bits_per_word = bit_size; + + rc = spi_sync(lcdc_spi_client, &m); +#ifdef DEBUG + pr_info("%s: addr=0x%02x, #args=%d[%d] [%s], rc=%d\n", + __func__, data.addr, t.len, t.bits_per_word, + byte_to_binary(tx_buf, t.len), rc); +#endif + return rc; +#else + int i; + + /* Chip Select - low */ + gpio_set_value(spi_cs, 0); + udelay(2); + + samsung_spi_write_byte(FALSE, data.addr); + udelay(2); + + for (i = 0; i < data.len; ++i) { + samsung_spi_write_byte(TRUE, data.data[i]); + udelay(2); + } + + /* Chip Select - high */ + gpio_set_value(spi_cs, 1); +#ifdef DEBUG + pr_info("%s: cmd=0x%02x, #args=%d\n", __func__, data.addr, data.len); +#endif + return 0; +#endif +} + +static int samsung_write_cmd(u8 cmd) +{ +#ifdef CONFIG_SPI_QUP + char tx_buf[2]; + int bit_size = 0, rc; + struct spi_message m; + struct spi_transfer t; + + if (!lcdc_spi_client) { + pr_err("%s lcdc_spi_client is NULL\n", __func__); + return -EINVAL; + } + + memset(&t, 0, sizeof t); + memset(tx_buf, 0, sizeof tx_buf); + t.tx_buf = tx_buf; + spi_setup(lcdc_spi_client); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + ADD_BIT(FALSE); + ADD_BYTE(cmd); + + t.len = 2; + t.bits_per_word = 9; + + rc = spi_sync(lcdc_spi_client, &m); +#ifdef DEBUG + pr_info("%s: addr=0x%02x, #args=%d[%d] [%s], rc=%d\n", + __func__, cmd, t.len, t.bits_per_word, + byte_to_binary(tx_buf, t.len), rc); +#endif + return rc; +#else + /* Chip Select - low */ + gpio_set_value(spi_cs, 0); + udelay(2); + + samsung_spi_write_byte(FALSE, cmd); + + /* Chip Select - high */ + udelay(2); + gpio_set_value(spi_cs, 1); +#ifdef DEBUG + pr_info("%s: cmd=0x%02x\n", __func__, cmd); +#endif + return 0; +#endif +} + +static int samsung_serigo_list(struct samsung_spi_data *data, int count) +{ + int i, rc; + for (i = 0; i < count; ++i, ++data) { + rc = samsung_serigo(*data); + if (rc) + return rc; + msleep(10); + } + return 0; +} + +#ifndef CONFIG_SPI_QUP +static void samsung_spi_init(void) +{ + spi_sclk = *(lcdc_samsung_pdata->gpio_num); + spi_cs = *(lcdc_samsung_pdata->gpio_num + 1); + spi_mosi = *(lcdc_samsung_pdata->gpio_num + 2); + + /* Set the output so that we don't disturb the slave device */ + gpio_set_value(spi_sclk, 1); + gpio_set_value(spi_mosi, 0); + + /* Set the Chip Select deasserted (active low) */ + gpio_set_value(spi_cs, 1); +} +#endif + +static void samsung_disp_powerup(void) +{ + if (!samsung_state.disp_powered_up && !samsung_state.display_on) + samsung_state.disp_powered_up = TRUE; +} + +static struct work_struct disp_on_delayed_work; +static void samsung_disp_on_delayed_work(struct work_struct *work_ptr) +{ + /* 0x01: Software Reset */ + samsung_write_cmd(0x01); + msleep(120); + + msleep(300); + samsung_serigo_list(panel_sequence, + sizeof(panel_sequence)/sizeof(*panel_sequence)); + samsung_serigo_list(display_sequence, + sizeof(display_sequence)/sizeof(*display_sequence)); + + switch (samsung_state.brightness) { + case 300: + samsung_serigo_list(gamma_sequence_300, + sizeof(gamma_sequence_300)/sizeof(*gamma_sequence_300)); + break; + case 180: + default: + samsung_serigo_list(gamma_sequence_180, + sizeof(gamma_sequence_180)/sizeof(*gamma_sequence_180)); + break; + case 80: + samsung_serigo_list(gamma_sequence_80, + sizeof(gamma_sequence_80)/sizeof(*gamma_sequence_80)); + break; + } + + samsung_serigo_list(etc_sequence, + sizeof(etc_sequence)/sizeof(*etc_sequence)); + + /* 0x11: Sleep Out */ + samsung_write_cmd(0x11); + msleep(120); + /* 0x13: Normal Mode On */ + samsung_write_cmd(0x13); + +#ifndef CONFIG_SPI_QUP + { + u8 data; + + msleep(120); + /* 0x0A: Read Display Power Mode */ + samsung_spi_read_bytes(0x0A, &data, 1); + pr_info("%s: power=[%s]\n", __func__, + byte_to_binary(&data, 1)); + + msleep(120); + /* 0x0C: Read Display Pixel Format */ + samsung_spi_read_bytes(0x0C, &data, 1); + pr_info("%s: pixel-format=[%s]\n", __func__, + byte_to_binary(&data, 1)); + } +#endif + msleep(120); + /* 0x29: Display On */ + samsung_write_cmd(0x29); +} + +static void samsung_disp_on(void) +{ + if (samsung_state.disp_powered_up && !samsung_state.display_on) { + INIT_WORK(&disp_on_delayed_work, samsung_disp_on_delayed_work); + schedule_work(&disp_on_delayed_work); + + samsung_state.display_on = TRUE; + } +} + +static int lcdc_samsung_panel_on(struct platform_device *pdev) +{ + pr_info("%s\n", __func__); + if (!samsung_state.disp_initialized) { +#ifndef CONFIG_SPI_QUP + lcdc_samsung_pdata->panel_config_gpio(1); + samsung_spi_init(); +#endif + samsung_disp_powerup(); + samsung_disp_on(); + samsung_state.disp_initialized = TRUE; + } + return 0; +} + +static int lcdc_samsung_panel_off(struct platform_device *pdev) +{ + pr_info("%s\n", __func__); + if (samsung_state.disp_powered_up && samsung_state.display_on) { + /* 0x10: Sleep In */ + samsung_write_cmd(0x10); + msleep(120); + + samsung_state.display_on = FALSE; + samsung_state.disp_initialized = FALSE; + } + return 0; +} + +#ifdef SYSFS_DEBUG_CMD +static ssize_t samsung_rda_cmd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t ret = snprintf(buf, PAGE_SIZE, "n/a\n"); + pr_info("%s: 'n/a'\n", __func__); + return ret; +} + +static ssize_t samsung_wta_cmd(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + ssize_t ret = strnlen(buf, PAGE_SIZE); + uint32 cmd; + + sscanf(buf, "%x", &cmd); + samsung_write_cmd((u8)cmd); + + return ret; +} + +static DEVICE_ATTR(cmd, S_IRUGO | S_IWUGO, samsung_rda_cmd, samsung_wta_cmd); +static struct attribute *fs_attrs[] = { + &dev_attr_cmd.attr, + NULL, +}; +static struct attribute_group fs_attr_group = { + .attrs = fs_attrs, +}; +#endif + +static struct msm_fb_panel_data samsung_panel_data = { + .on = lcdc_samsung_panel_on, + .off = lcdc_samsung_panel_off, +}; + +static int __devinit samsung_probe(struct platform_device *pdev) +{ + struct msm_panel_info *pinfo; +#ifdef SYSFS_DEBUG_CMD + struct platform_device *fb_dev; + struct msm_fb_data_type *mfd; + int rc; +#endif + + pr_info("%s: id=%d\n", __func__, pdev->id); + lcdc_samsung_pdata = pdev->dev.platform_data; + + pinfo = &samsung_panel_data.panel_info; + pinfo->xres = 480; + pinfo->yres = 800; + pinfo->type = LCDC_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 24; + pinfo->fb_num = 2; + pinfo->clk_rate = 25600000; /* Max 27.77MHz */ + pinfo->bl_max = 15; + pinfo->bl_min = 1; + + /* AMS367PE02 Operation Manual, Page 7 */ + pinfo->lcdc.h_back_porch = 16-2; /* HBP-HLW */ + pinfo->lcdc.h_front_porch = 16; + pinfo->lcdc.h_pulse_width = 2; + /* AMS367PE02 Operation Manual, Page 6 */ + pinfo->lcdc.v_back_porch = 3-2; /* VBP-VLW */ + pinfo->lcdc.v_front_porch = 28; + pinfo->lcdc.v_pulse_width = 2; + + pinfo->lcdc.border_clr = 0; + pinfo->lcdc.underflow_clr = 0xff; + pinfo->lcdc.hsync_skew = 0; + pdev->dev.platform_data = &samsung_panel_data; + +#ifndef SYSFS_DEBUG_CMD + msm_fb_add_device(pdev); +#else + fb_dev = msm_fb_add_device(pdev); + mfd = platform_get_drvdata(fb_dev); + rc = sysfs_create_group(&mfd->fbi->dev->kobj, &fs_attr_group); + if (rc) { + pr_err("%s: sysfs group creation failed, rc=%d\n", __func__, + rc); + return rc; + } +#endif + return 0; +} + +#ifdef CONFIG_SPI_QUP +static int __devinit lcdc_samsung_spi_probe(struct spi_device *spi) +{ + pr_info("%s\n", __func__); + lcdc_spi_client = spi; + lcdc_spi_client->bits_per_word = 32; + return 0; +} +static int __devexit lcdc_samsung_spi_remove(struct spi_device *spi) +{ + lcdc_spi_client = NULL; + return 0; +} +static struct spi_driver lcdc_samsung_spi_driver = { + .driver.name = LCDC_SAMSUNG_SPI_DEVICE_NAME, + .driver.owner = THIS_MODULE, + .probe = lcdc_samsung_spi_probe, + .remove = __devexit_p(lcdc_samsung_spi_remove), +}; +#endif + +static struct platform_driver this_driver = { + .probe = samsung_probe, + .driver.name = "lcdc_samsung_oled", +}; + +static int __init lcdc_samsung_panel_init(void) +{ + int ret; + +#ifdef CONFIG_FB_MSM_LCDC_AUTO_DETECT + if (msm_fb_detect_client("lcdc_samsung_oled")) { + pr_err("%s: detect failed\n", __func__); + return 0; + } +#endif + + ret = platform_driver_register(&this_driver); + if (ret) { + pr_err("%s: driver register failed, rc=%d\n", __func__, ret); + return ret; + } + +#ifdef CONFIG_SPI_QUP + ret = spi_register_driver(&lcdc_samsung_spi_driver); + + if (ret) { + pr_err("%s: spi register failed: rc=%d\n", __func__, ret); + platform_driver_unregister(&this_driver); + } else + pr_info("%s: SUCCESS (SPI)\n", __func__); +#else + pr_info("%s: SUCCESS (BitBang)\n", __func__); +#endif + return ret; +} + +module_init(lcdc_samsung_panel_init); +static void __exit lcdc_samsung_panel_exit(void) +{ + pr_info("%s\n", __func__); +#ifdef CONFIG_SPI_QUP + spi_unregister_driver(&lcdc_samsung_spi_driver); +#endif + platform_driver_unregister(&this_driver); +} +module_exit(lcdc_samsung_panel_exit); diff --git a/drivers/video/msm/lcdc_samsung_wsvga.c b/drivers/video/msm/lcdc_samsung_wsvga.c new file mode 100644 index 00000000000..b4bf8cf4ace --- /dev/null +++ b/drivers/video/msm/lcdc_samsung_wsvga.c @@ -0,0 +1,272 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#ifdef CONFIG_PMIC8058_PWM +#include +#include +#endif +#include +#include "msm_fb.h" + + + +#ifdef CONFIG_PMIC8058_PWM +static struct pwm_device *bl_pwm0; +static struct pwm_device *bl_pwm1; + +/* for samsung panel 300hz was the minimum freq where flickering wasnt + * observed as the screen was dimmed + */ + +#define PWM_FREQ_HZ 300 +#define PWM_PERIOD_USEC (USEC_PER_SEC / PWM_FREQ_HZ) +#define PWM_LEVEL 100 +#define PWM_DUTY_LEVEL (PWM_PERIOD_USEC / PWM_LEVEL) +#endif + +struct lcdc_samsung_data { + struct msm_panel_common_pdata *pdata; +#ifdef CONFIG_FB_MSM_LCDC_DSUB + int vga_enabled; +#endif + struct platform_device *fbpdev; +}; + +static struct lcdc_samsung_data *dd; + + +static void lcdc_samsung_panel_set_backlight(struct msm_fb_data_type *mfd) +{ + int bl_level; + int ret; + + bl_level = mfd->bl_level; + +#ifdef CONFIG_PMIC8058_PWM + if (bl_pwm0) { + ret = pwm_config(bl_pwm0, PWM_DUTY_LEVEL * bl_level, + PWM_PERIOD_USEC); + if (ret) + printk(KERN_ERR "pwm_config on pwm 0 failed %d\n", ret); + } + + if (bl_pwm1) { + ret = pwm_config(bl_pwm1, + PWM_PERIOD_USEC - (PWM_DUTY_LEVEL * bl_level), + PWM_PERIOD_USEC); + if (ret) + printk(KERN_ERR "pwm_config on pwm 1 failed %d\n", ret); + } + + if (bl_pwm0) { + ret = pwm_enable(bl_pwm0); + if (ret) + printk(KERN_ERR "pwm_enable on pwm 0 failed %d\n", ret); + } + + if (bl_pwm1) { + ret = pwm_enable(bl_pwm1); + if (ret) + printk(KERN_ERR "pwm_enable on pwm 1 failed %d\n", ret); + } +#endif + +} + +#ifdef CONFIG_FB_MSM_LCDC_DSUB +static ssize_t show_vga_enable(struct device *device, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", dd->vga_enabled); +} + +static ssize_t store_vga_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long enable; + int rc; + + rc = strict_strtoul(buf, 10, &enable); + if (rc) + return -EINVAL; + + if (dd->pdata && dd->pdata->vga_switch) + rc = dd->pdata->vga_switch(enable); + else + rc = -ENODEV; + if (!rc) { + dd->vga_enabled = enable; + rc = count; + } + return rc; +} + +static DEVICE_ATTR(vga_enable, S_IRUGO|S_IWUSR, show_vga_enable, + store_vga_enable); +static struct attribute *attrs[] = { + &dev_attr_vga_enable.attr, + NULL, +}; +static struct attribute_group attr_group = { + .attrs = attrs, +}; +#endif + +static int __devinit samsung_probe(struct platform_device *pdev) +{ + int rc = 0; +#ifdef CONFIG_FB_MSM_LCDC_DSUB + struct msm_fb_data_type *mfd; +#endif + + if (pdev->id == 0) { + dd = kzalloc(sizeof *dd, GFP_KERNEL); + if (!dd) + return -ENOMEM; +#ifdef CONFIG_FB_MSM_LCDC_DSUB + dd->vga_enabled = 0; +#endif + dd->pdata = pdev->dev.platform_data; + return 0; + } else if (!dd) + return -ENODEV; + +#ifdef CONFIG_PMIC8058_PWM + bl_pwm0 = pwm_request(dd->pdata->gpio_num[0], "backlight1"); + if (bl_pwm0 == NULL || IS_ERR(bl_pwm0)) { + pr_err("%s pwm_request() failed\n", __func__); + bl_pwm0 = NULL; + } + + bl_pwm1 = pwm_request(dd->pdata->gpio_num[1], "backlight2"); + if (bl_pwm1 == NULL || IS_ERR(bl_pwm1)) { + pr_err("%s pwm_request() failed\n", __func__); + bl_pwm1 = NULL; + } + + pr_debug("samsung_probe: bl_pwm0=%p LPG_chan0=%d " + "bl_pwm1=%p LPG_chan1=%d\n", + bl_pwm0, (int)dd->pdata->gpio_num[0], + bl_pwm1, (int)dd->pdata->gpio_num[1] + ); +#endif + + + dd->fbpdev = msm_fb_add_device(pdev); + if (!dd->fbpdev) { + dev_err(&pdev->dev, "failed to add msm_fb device\n"); + rc = -ENODEV; + goto probe_exit; + } + +#ifdef CONFIG_FB_MSM_LCDC_DSUB + mfd = platform_get_drvdata(dd->fbpdev); + if (mfd && mfd->fbi && mfd->fbi->dev) { + rc = sysfs_create_group(&mfd->fbi->dev->kobj, &attr_group); + if (rc) + dev_err(&pdev->dev, "failed to create sysfs group\n"); + } else { + dev_err(&pdev->dev, "no dev to create sysfs group\n"); + rc = -ENODEV; + } +#endif + +probe_exit: + return rc; +} + +#ifdef CONFIG_FB_MSM_LCDC_DSUB +static int __devexit samsung_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&dd->fbpdev->dev.kobj, &attr_group); + return 0; +} +#endif + +static struct platform_driver this_driver = { + .probe = samsung_probe, +#ifdef CONFIG_FB_MSM_LCDC_DSUB + .remove = samsung_remove, +#endif + .driver = { + .name = "lcdc_samsung_wsvga", + }, +}; + +static struct msm_fb_panel_data samsung_panel_data = { + .set_backlight = lcdc_samsung_panel_set_backlight, +}; + +static struct platform_device this_device = { + .name = "lcdc_samsung_wsvga", + .id = 1, + .dev = { + .platform_data = &samsung_panel_data, + } +}; + +static int __init lcdc_samsung_panel_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_LCDC_AUTO_DETECT + if (msm_fb_detect_client("lcdc_samsung_wsvga")) + return 0; +#endif + + ret = platform_driver_register(&this_driver); + if (ret) + return ret; + + pinfo = &samsung_panel_data.panel_info; + pinfo->xres = 1024; + pinfo->yres = 600; +#ifdef CONFIG_FB_MSM_LCDC_DSUB + /* DSUB (VGA) is on the same bus, this allows us to allocate for the + * max resolution of the DSUB display */ + pinfo->mode2_xres = 1440; + pinfo->mode2_yres = 900; + pinfo->mode2_bpp = 16; +#else + MSM_FB_SINGLE_MODE_PANEL(pinfo); +#endif + pinfo->type = LCDC_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->fb_num = 2; + pinfo->clk_rate = 43192000; + pinfo->bl_max = PWM_LEVEL; + pinfo->bl_min = 1; + + pinfo->lcdc.h_back_porch = 80; + pinfo->lcdc.h_front_porch = 48; + pinfo->lcdc.h_pulse_width = 32; + pinfo->lcdc.v_back_porch = 4; + pinfo->lcdc.v_front_porch = 3; + pinfo->lcdc.v_pulse_width = 1; + pinfo->lcdc.border_clr = 0; + pinfo->lcdc.underflow_clr = 0xff; + pinfo->lcdc.hsync_skew = 0; + + ret = platform_device_register(&this_device); + if (ret) + platform_driver_unregister(&this_driver); + + return ret; +} + +module_init(lcdc_samsung_panel_init); diff --git a/drivers/video/msm/lcdc_sharp_wvga_pt.c b/drivers/video/msm/lcdc_sharp_wvga_pt.c new file mode 100644 index 00000000000..2ba2618b971 --- /dev/null +++ b/drivers/video/msm/lcdc_sharp_wvga_pt.c @@ -0,0 +1,414 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#ifdef CONFIG_PMIC8058_PWM +#include +#include +#endif +#ifdef CONFIG_SPI_QSD +#include +#endif +#include +#include "msm_fb.h" + +#ifdef CONFIG_SPI_QSD +#define LCDC_SHARP_SPI_DEVICE_NAME "lcdc_sharp_ls038y7dx01" +static struct spi_device *lcdc_spi_client; +#endif +static int lcdc_sharp_panel_off(struct platform_device *pdev); + +#define BL_MAX 16 + +#ifdef CONFIG_PMIC8058_PWM +static struct pwm_device *bl_pwm; + +#define PWM_PERIOD 1000 /* us, period of 1Khz */ +#define DUTY_LEVEL (PWM_PERIOD / BL_MAX) +#endif + +#ifndef CONFIG_SPI_QSD +static int spi_cs; +static int spi_sclk; +static int spi_mosi; +static int spi_miso; +static unsigned char bit_shift[8] = { (1 << 7), /* MSB */ + (1 << 6), + (1 << 5), + (1 << 4), + (1 << 3), + (1 << 2), + (1 << 1), + (1 << 0) /* LSB */ +}; +#endif + +struct sharp_state_type { + boolean disp_initialized; + boolean display_on; + boolean disp_powered_up; +}; + +struct sharp_spi_data { + u8 addr; + u8 data; +}; + +static struct sharp_spi_data init_sequence[] = { + { 15, 0x01 }, + { 5, 0x01 }, + { 7, 0x10 }, + { 9, 0x1E }, + { 10, 0x04 }, + { 17, 0xFF }, + { 21, 0x8A }, + { 22, 0x00 }, + { 23, 0x82 }, + { 24, 0x24 }, + { 25, 0x22 }, + { 26, 0x6D }, + { 27, 0xEB }, + { 28, 0xB9 }, + { 29, 0x3A }, + { 49, 0x1A }, + { 50, 0x16 }, + { 51, 0x05 }, + { 55, 0x7F }, + { 56, 0x15 }, + { 57, 0x7B }, + { 60, 0x05 }, + { 61, 0x0C }, + { 62, 0x80 }, + { 63, 0x00 }, + { 92, 0x90 }, + { 97, 0x01 }, + { 98, 0xFF }, + { 113, 0x11 }, + { 114, 0x02 }, + { 115, 0x08 }, + { 123, 0xAB }, + { 124, 0x04 }, + { 6, 0x02 }, + { 133, 0x00 }, + { 134, 0xFE }, + { 135, 0x22 }, + { 136, 0x0B }, + { 137, 0xFF }, + { 138, 0x0F }, + { 139, 0x00 }, + { 140, 0xFE }, + { 141, 0x22 }, + { 142, 0x0B }, + { 143, 0xFF }, + { 144, 0x0F }, + { 145, 0x00 }, + { 146, 0xFE }, + { 147, 0x22 }, + { 148, 0x0B }, + { 149, 0xFF }, + { 150, 0x0F }, + { 202, 0x30 }, + { 30, 0x01 }, + { 4, 0x01 }, + { 31, 0x41 }, +}; + +static struct sharp_state_type sharp_state = { 0 }; +static struct msm_panel_common_pdata *lcdc_sharp_pdata; + +#ifndef CONFIG_SPI_QSD +static void sharp_spi_write_byte(u8 val) +{ + int i; + + /* Clock should be Low before entering */ + for (i = 0; i < 8; i++) { + /* #1: Drive the Data (High or Low) */ + if (val & bit_shift[i]) + gpio_set_value(spi_mosi, 1); + else + gpio_set_value(spi_mosi, 0); + + /* #2: Drive the Clk High and then Low */ + gpio_set_value(spi_sclk, 1); + gpio_set_value(spi_sclk, 0); + } +} +#endif + +static int serigo(u8 reg, u8 data) +{ +#ifdef CONFIG_SPI_QSD + char tx_buf[2]; + int rc; + struct spi_message m; + struct spi_transfer t; + + if (!lcdc_spi_client) { + printk(KERN_ERR "%s lcdc_spi_client is NULL\n", __func__); + return -EINVAL; + } + + memset(&t, 0, sizeof t); + t.tx_buf = tx_buf; + spi_setup(lcdc_spi_client); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + tx_buf[0] = reg; + tx_buf[1] = data; + t.rx_buf = NULL; + t.len = 2; + rc = spi_sync(lcdc_spi_client, &m); + return rc; +#else + /* Enable the Chip Select - low */ + gpio_set_value(spi_cs, 0); + udelay(1); + + /* Transmit register address first, then data */ + sharp_spi_write_byte(reg); + + /* Idle state of MOSI is Low */ + gpio_set_value(spi_mosi, 0); + udelay(1); + sharp_spi_write_byte(data); + + gpio_set_value(spi_mosi, 0); + gpio_set_value(spi_cs, 1); + return 0; +#endif +} + +#ifndef CONFIG_SPI_QSD +static void sharp_spi_init(void) +{ + spi_sclk = *(lcdc_sharp_pdata->gpio_num); + spi_cs = *(lcdc_sharp_pdata->gpio_num + 1); + spi_mosi = *(lcdc_sharp_pdata->gpio_num + 2); + spi_miso = *(lcdc_sharp_pdata->gpio_num + 3); + + /* Set the output so that we don't disturb the slave device */ + gpio_set_value(spi_sclk, 0); + gpio_set_value(spi_mosi, 0); + + /* Set the Chip Select deasserted (active low) */ + gpio_set_value(spi_cs, 1); +} +#endif + +static void sharp_disp_powerup(void) +{ + if (!sharp_state.disp_powered_up && !sharp_state.display_on) + sharp_state.disp_powered_up = TRUE; +} + +static void sharp_disp_on(void) +{ + int i; + + if (sharp_state.disp_powered_up && !sharp_state.display_on) { + for (i = 0; i < ARRAY_SIZE(init_sequence); i++) { + serigo(init_sequence[i].addr, + init_sequence[i].data); + } + mdelay(10); + serigo(31, 0xC1); + mdelay(10); + serigo(31, 0xD9); + serigo(31, 0xDF); + + sharp_state.display_on = TRUE; + } +} + +static int lcdc_sharp_panel_on(struct platform_device *pdev) +{ + if (!sharp_state.disp_initialized) { +#ifndef CONFIG_SPI_QSD + lcdc_sharp_pdata->panel_config_gpio(1); + sharp_spi_init(); +#endif + sharp_disp_powerup(); + sharp_disp_on(); + sharp_state.disp_initialized = TRUE; + } + return 0; +} + +static int lcdc_sharp_panel_off(struct platform_device *pdev) +{ + if (sharp_state.disp_powered_up && sharp_state.display_on) { + serigo(4, 0x00); + mdelay(40); + serigo(31, 0xC1); + mdelay(40); + serigo(31, 0x00); + msleep(16); + sharp_state.display_on = FALSE; + sharp_state.disp_initialized = FALSE; + } + return 0; +} + +static void lcdc_sharp_panel_set_backlight(struct msm_fb_data_type *mfd) +{ + int bl_level; + + bl_level = mfd->bl_level; + +#ifdef CONFIG_PMIC8058_PWM + if (bl_pwm) { + pwm_config(bl_pwm, DUTY_LEVEL * bl_level, PWM_PERIOD); + pwm_enable(bl_pwm); + } +#endif +} + +static int __devinit sharp_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + lcdc_sharp_pdata = pdev->dev.platform_data; + return 0; + } + +#ifdef CONFIG_PMIC8058_PWM + bl_pwm = pwm_request(lcdc_sharp_pdata->gpio, "backlight"); + if (bl_pwm == NULL || IS_ERR(bl_pwm)) { + pr_err("%s pwm_request() failed\n", __func__); + bl_pwm = NULL; + } + + printk(KERN_INFO "sharp_probe: bl_pwm=%x LPG_chan=%d\n", + (int) bl_pwm, (int)lcdc_sharp_pdata->gpio); +#endif + + msm_fb_add_device(pdev); + + return 0; +} + +#ifdef CONFIG_SPI_QSD +static int __devinit lcdc_sharp_spi_probe(struct spi_device *spi) +{ + lcdc_spi_client = spi; + lcdc_spi_client->bits_per_word = 32; + return 0; +} +static int __devexit lcdc_sharp_spi_remove(struct spi_device *spi) +{ + lcdc_spi_client = NULL; + return 0; +} +static struct spi_driver lcdc_sharp_spi_driver = { + .driver = { + .name = LCDC_SHARP_SPI_DEVICE_NAME, + .owner = THIS_MODULE, + }, + .probe = lcdc_sharp_spi_probe, + .remove = __devexit_p(lcdc_sharp_spi_remove), +}; +#endif +static struct platform_driver this_driver = { + .probe = sharp_probe, + .driver = { + .name = "lcdc_sharp_wvga", + }, +}; + +static struct msm_fb_panel_data sharp_panel_data = { + .on = lcdc_sharp_panel_on, + .off = lcdc_sharp_panel_off, + .set_backlight = lcdc_sharp_panel_set_backlight, +}; + +static struct platform_device this_device = { + .name = "lcdc_sharp_wvga", + .id = 1, + .dev = { + .platform_data = &sharp_panel_data, + } +}; + +static int __init lcdc_sharp_panel_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + if (msm_fb_detect_client("lcdc_sharp_wvga_pt")) + return 0; +#endif + + ret = platform_driver_register(&this_driver); + if (ret) + return ret; + + pinfo = &sharp_panel_data.panel_info; + pinfo->xres = 480; + pinfo->yres = 800; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = LCDC_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->fb_num = 2; + pinfo->clk_rate = 24500000; + pinfo->bl_max = BL_MAX; + pinfo->bl_min = 1; + + pinfo->lcdc.h_back_porch = 20; + pinfo->lcdc.h_front_porch = 10; + pinfo->lcdc.h_pulse_width = 10; + pinfo->lcdc.v_back_porch = 2; + pinfo->lcdc.v_front_porch = 2; + pinfo->lcdc.v_pulse_width = 2; + pinfo->lcdc.border_clr = 0; + pinfo->lcdc.underflow_clr = 0xff; + pinfo->lcdc.hsync_skew = 0; + + ret = platform_device_register(&this_device); + if (ret) { + printk(KERN_ERR "%s not able to register the device\n", + __func__); + goto fail_driver; + } +#ifdef CONFIG_SPI_QSD + ret = spi_register_driver(&lcdc_sharp_spi_driver); + + if (ret) { + printk(KERN_ERR "%s not able to register spi\n", __func__); + goto fail_device; + } +#endif + return ret; +#ifdef CONFIG_SPI_QSD +fail_device: + platform_device_unregister(&this_device); +#endif +fail_driver: + platform_driver_unregister(&this_driver); + + return ret; +} + +module_init(lcdc_sharp_panel_init); +#ifdef CONFIG_SPI_QSD +static void __exit lcdc_sharp_panel_exit(void) +{ + spi_unregister_driver(&lcdc_sharp_spi_driver); +} +module_exit(lcdc_sharp_panel_exit); +#endif + diff --git a/drivers/video/msm/lcdc_st15.c b/drivers/video/msm/lcdc_st15.c new file mode 100644 index 00000000000..cdae3589b7d --- /dev/null +++ b/drivers/video/msm/lcdc_st15.c @@ -0,0 +1,413 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "msm_fb.h" + +#define DEVICE_NAME "sii9022" +#define SII9022_DEVICE_ID 0xB0 +#define SII9022_ISR 0x3D +#define SII9022_ISR_RXS_STATUS 0x08 + +static int lcdc_sii9022_panel_on(struct platform_device *pdev); +static int lcdc_sii9022_panel_off(struct platform_device *pdev); + +static struct i2c_client *sii9022_i2c_client; + +struct sii9022_data { + struct msm_hdmi_platform_data *pd; + struct platform_device *pdev; + struct work_struct work; + int x_res; + int y_res; + int sysfs_entry_created; + int hdmi_attached; +}; +static struct sii9022_data *dd; + +struct sii9022_i2c_addr_data{ + u8 addr; + u8 data; +}; + +/* video mode data */ +static u8 video_mode_data[] = { + 0x00, + 0xF9, 0x1C, 0x70, 0x17, 0x72, 0x06, 0xEE, 0x02, +}; + +static u8 avi_io_format[] = { + 0x09, + 0x00, 0x00, +}; + +/* power state */ +static struct sii9022_i2c_addr_data regset0[] = { + { 0x60, 0x04 }, + { 0x63, 0x00 }, + { 0x1E, 0x00 }, +}; + +static u8 video_infoframe[] = { + 0x0C, + 0xF0, 0x00, 0x68, 0x00, 0x04, 0x00, 0x19, 0x00, + 0xE9, 0x02, 0x04, 0x01, 0x04, 0x06, +}; + +/* configure audio */ +static struct sii9022_i2c_addr_data regset1[] = { + { 0x26, 0x90 }, + { 0x20, 0x90 }, + { 0x1F, 0x80 }, + { 0x26, 0x80 }, + { 0x24, 0x02 }, + { 0x25, 0x0B }, + { 0xBC, 0x02 }, + { 0xBD, 0x24 }, + { 0xBE, 0x02 }, +}; + +/* enable audio */ +static u8 misc_infoframe[] = { + 0xBF, + 0xC2, 0x84, 0x01, 0x0A, 0x6F, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +/* set HDMI, active */ +static struct sii9022_i2c_addr_data regset2[] = { + { 0x1A, 0x01 }, + { 0x3D, 0x00 }, + { 0x3C, 0x02 }, +}; + +static struct msm_fb_panel_data sii9022_panel_data = { + .on = lcdc_sii9022_panel_on, + .off = lcdc_sii9022_panel_off, +}; + +static struct platform_device sii9022_device = { + .name = DEVICE_NAME, + .id = 1, + .dev = { + .platform_data = &sii9022_panel_data, + } +}; + +static int send_i2c_data(struct i2c_client *client, + struct sii9022_i2c_addr_data *regset, + int size) +{ + int i; + int rc = 0; + + for (i = 0; i < size; i++) { + rc = i2c_smbus_write_byte_data( + client, + regset[i].addr, regset[i].data); + if (rc) + break; + } + return rc; +} + +static void sii9022_work_f(struct work_struct *work) +{ + int isr; + + isr = i2c_smbus_read_byte_data(sii9022_i2c_client, SII9022_ISR); + if (isr < 0) { + dev_err(&sii9022_i2c_client->dev, + "i2c read of isr failed rc = 0x%x\n", isr); + return; + } + if (isr == 0) + return; + + /* reset any set bits */ + i2c_smbus_write_byte_data(sii9022_i2c_client, SII9022_ISR, isr); + dd->hdmi_attached = isr & SII9022_ISR_RXS_STATUS; + if (dd->pd->cable_detect) + dd->pd->cable_detect(dd->hdmi_attached); + if (dd->hdmi_attached) { + dd->x_res = 1280; + dd->y_res = 720; + } else { + dd->x_res = sii9022_panel_data.panel_info.xres; + dd->y_res = sii9022_panel_data.panel_info.yres; + } +} + +static irqreturn_t sii9022_interrupt(int irq, void *dev_id) +{ + struct sii9022_data *dd = dev_id; + + schedule_work(&dd->work); + return IRQ_HANDLED; +} + +static int hdmi_sii_enable(struct i2c_client *client) +{ + int rc; + int retries = 10; + int count; + + rc = i2c_smbus_write_byte_data(client, 0xC7, 0x00); + if (rc) + goto enable_exit; + + do { + msleep(1); + rc = i2c_smbus_read_byte_data(client, 0x1B); + } while ((rc != SII9022_DEVICE_ID) && retries--); + + if (rc != SII9022_DEVICE_ID) + return -ENODEV; + + rc = i2c_smbus_write_byte_data(client, 0x1A, 0x11); + if (rc) + goto enable_exit; + + count = ARRAY_SIZE(video_mode_data); + rc = i2c_master_send(client, video_mode_data, count); + if (rc != count) { + rc = -EIO; + goto enable_exit; + } + + rc = i2c_smbus_write_byte_data(client, 0x08, 0x20); + if (rc) + goto enable_exit; + count = ARRAY_SIZE(avi_io_format); + rc = i2c_master_send(client, avi_io_format, count); + if (rc != count) { + rc = -EIO; + goto enable_exit; + } + + rc = send_i2c_data(client, regset0, ARRAY_SIZE(regset0)); + if (rc) + goto enable_exit; + + count = ARRAY_SIZE(video_infoframe); + rc = i2c_master_send(client, video_infoframe, count); + if (rc != count) { + rc = -EIO; + goto enable_exit; + } + + rc = send_i2c_data(client, regset1, ARRAY_SIZE(regset1)); + if (rc) + goto enable_exit; + + count = ARRAY_SIZE(misc_infoframe); + rc = i2c_master_send(client, misc_infoframe, count); + if (rc != count) { + rc = -EIO; + goto enable_exit; + } + + rc = send_i2c_data(client, regset2, ARRAY_SIZE(regset2)); + if (rc) + goto enable_exit; + + return 0; +enable_exit: + printk(KERN_ERR "%s: exited rc=%d\n", __func__, rc); + return rc; +} + +static ssize_t show_res(struct device *device, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%dx%d\n", dd->x_res, dd->y_res); +} + +static struct device_attribute device_attrs[] = { + __ATTR(screen_resolution, S_IRUGO|S_IWUSR, show_res, NULL), +}; + +static int lcdc_sii9022_panel_on(struct platform_device *pdev) +{ + int rc; + if (!dd->sysfs_entry_created) { + dd->pdev = pdev; + rc = device_create_file(&pdev->dev, &device_attrs[0]); + if (!rc) + dd->sysfs_entry_created = 1; + } + + rc = hdmi_sii_enable(sii9022_i2c_client); + if (rc) { + dd->hdmi_attached = 0; + dd->x_res = sii9022_panel_data.panel_info.xres; + dd->y_res = sii9022_panel_data.panel_info.yres; + } + if (dd->pd->irq) + enable_irq(dd->pd->irq); + /* Don't return the value from hdmi_sii_enable(). + * It may fail on some ST1.5s, but we must return 0 from this + * function in order for the on-board display to turn on. + */ + return 0; +} + +static int lcdc_sii9022_panel_off(struct platform_device *pdev) +{ + if (dd->pd->irq) + disable_irq(dd->pd->irq); + return 0; +} + +static const struct i2c_device_id hmdi_sii_id[] = { + { DEVICE_NAME, 0 }, + { } +}; + +static int hdmi_sii_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C)) + return -ENODEV; + + dd = kzalloc(sizeof *dd, GFP_KERNEL); + if (!dd) { + rc = -ENOMEM; + goto probe_exit; + } + sii9022_i2c_client = client; + i2c_set_clientdata(client, dd); + dd->pd = client->dev.platform_data; + if (!dd->pd) { + rc = -ENODEV; + goto probe_free; + } + if (dd->pd->irq) { + INIT_WORK(&dd->work, sii9022_work_f); + rc = request_irq(dd->pd->irq, + &sii9022_interrupt, + IRQF_TRIGGER_FALLING, + "sii9022_cable", dd); + if (rc) + goto probe_free; + disable_irq(dd->pd->irq); + } + msm_fb_add_device(&sii9022_device); + dd->x_res = sii9022_panel_data.panel_info.xres; + dd->y_res = sii9022_panel_data.panel_info.yres; + + return 0; + +probe_free: + i2c_set_clientdata(client, NULL); + kfree(dd); +probe_exit: + return rc; +} + +static int __devexit hdmi_sii_remove(struct i2c_client *client) +{ + int err = 0 ; + struct msm_hdmi_platform_data *pd; + + if (dd->sysfs_entry_created) + device_remove_file(&dd->pdev->dev, &device_attrs[0]); + pd = client->dev.platform_data; + if (pd && pd->irq) + free_irq(pd->irq, dd); + i2c_set_clientdata(client, NULL); + kfree(dd); + + return err ; +} + +#ifdef CONFIG_PM +static int sii9022_suspend(struct device *dev) +{ + if (dd && dd->pd && dd->pd->irq) + disable_irq(dd->pd->irq); + return 0; +} + +static int sii9022_resume(struct device *dev) +{ + if (dd && dd->pd && dd->pd->irq) + enable_irq(dd->pd->irq); + return 0; +} + +static struct dev_pm_ops sii9022_pm_ops = { + .suspend = sii9022_suspend, + .resume = sii9022_resume, +}; +#endif + +static struct i2c_driver hdmi_sii_i2c_driver = { + .driver = { + .name = DEVICE_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &sii9022_pm_ops, +#endif + }, + .probe = hdmi_sii_probe, + .remove = __exit_p(hdmi_sii_remove), + .id_table = hmdi_sii_id, +}; + +static int __init lcdc_st15_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + + if (msm_fb_detect_client("lcdc_st15")) + return 0; + + pinfo = &sii9022_panel_data.panel_info; + pinfo->xres = 1366; + pinfo->yres = 768; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = LCDC_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 24; + pinfo->fb_num = 2; + pinfo->clk_rate = 74250000; + + pinfo->lcdc.h_back_porch = 120; + pinfo->lcdc.h_front_porch = 20; + pinfo->lcdc.h_pulse_width = 40; + pinfo->lcdc.v_back_porch = 25; + pinfo->lcdc.v_front_porch = 1; + pinfo->lcdc.v_pulse_width = 7; + pinfo->lcdc.border_clr = 0; /* blk */ + pinfo->lcdc.underflow_clr = 0xff; /* blue */ + pinfo->lcdc.hsync_skew = 0; + + ret = i2c_add_driver(&hdmi_sii_i2c_driver); + if (ret) + printk(KERN_ERR "%s: failed to add i2c driver\n", __func__); + + return ret; +} + +static void __exit hdmi_sii_exit(void) +{ + i2c_del_driver(&hdmi_sii_i2c_driver); +} + +module_init(lcdc_st15_init); +module_exit(hdmi_sii_exit); diff --git a/drivers/video/msm/lcdc_toshiba_fwvga_pt.c b/drivers/video/msm/lcdc_toshiba_fwvga_pt.c new file mode 100644 index 00000000000..3e814714542 --- /dev/null +++ b/drivers/video/msm/lcdc_toshiba_fwvga_pt.c @@ -0,0 +1,471 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include "msm_fb.h" + +static int spi_cs0_N; +static int spi_sclk; +static int spi_mosi; +static int spi_miso; + +struct toshiba_state_type { + boolean disp_initialized; + boolean display_on; + boolean disp_powered_up; +}; + +static struct toshiba_state_type toshiba_state = { 0 }; +static struct msm_panel_common_pdata *lcdc_toshiba_pdata; + +static int toshiba_spi_write(char data1, char data2, int rs) +{ + uint32 bitdata = 0, bnum = 24, bmask = 0x800000; + + gpio_set_value_cansleep(spi_cs0_N, 0); /* cs* low */ + udelay(1); + + if (rs) + bitdata = (0x72 << 16); + else + bitdata = (0x70 << 16); + + bitdata |= ((data1 << 8) | data2); + + while (bnum) { + gpio_set_value_cansleep(spi_sclk, 0); /* clk low */ + udelay(1); + + if (bitdata & bmask) + gpio_set_value_cansleep(spi_mosi, 1); + else + gpio_set_value_cansleep(spi_mosi, 0); + + udelay(1); + gpio_set_value_cansleep(spi_sclk, 1); /* clk high */ + udelay(1); + bmask >>= 1; + bnum--; + } + + gpio_set_value_cansleep(spi_cs0_N, 1); /* cs* high */ + udelay(1); + return 0; +} + +static void spi_pin_assign(void) +{ + /* Setting the Default GPIO's */ + spi_mosi = *(lcdc_toshiba_pdata->gpio_num); + spi_miso = *(lcdc_toshiba_pdata->gpio_num + 1); + spi_sclk = *(lcdc_toshiba_pdata->gpio_num + 2); + spi_cs0_N = *(lcdc_toshiba_pdata->gpio_num + 3); +} + +static void toshiba_disp_powerup(void) +{ + if (!toshiba_state.disp_powered_up && !toshiba_state.display_on) { + /* Reset the hardware first */ + /* Include DAC power up implementation here */ + toshiba_state.disp_powered_up = TRUE; + } +} + +static void toshiba_disp_on(void) +{ + if (toshiba_state.disp_powered_up && !toshiba_state.display_on) { + toshiba_spi_write(0x01, 0x00, 0); + toshiba_spi_write(0x30, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x01, 0x01, 0); + toshiba_spi_write(0x40, 0x10, 1); + +#ifdef TOSHIBA_FWVGA_FULL_INIT + udelay(500); + toshiba_spi_write(0x01, 0x06, 0); + toshiba_spi_write(0x00, 0x00, 1); + msleep(20); + + toshiba_spi_write(0x00, 0x01, 0); + toshiba_spi_write(0x03, 0x10, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x02, 0); + toshiba_spi_write(0x01, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x03, 0); + toshiba_spi_write(0x00, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x07, 0); + toshiba_spi_write(0x00, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x08, 0); + toshiba_spi_write(0x00, 0x04, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x09, 0); + toshiba_spi_write(0x00, 0x0c, 1); +#endif + udelay(500); + toshiba_spi_write(0x00, 0x0c, 0); + toshiba_spi_write(0x40, 0x10, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x0e, 0); + toshiba_spi_write(0x00, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x20, 0); + toshiba_spi_write(0x01, 0x3f, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x22, 0); + toshiba_spi_write(0x76, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x23, 0); + toshiba_spi_write(0x1c, 0x0a, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x24, 0); + toshiba_spi_write(0x1c, 0x2c, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x25, 0); + toshiba_spi_write(0x1c, 0x4e, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x27, 0); + toshiba_spi_write(0x00, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x28, 0); + toshiba_spi_write(0x76, 0x0c, 1); + +#ifdef TOSHIBA_FWVGA_FULL_INIT + udelay(500); + toshiba_spi_write(0x03, 0x00, 0); + toshiba_spi_write(0x00, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x01, 0); + toshiba_spi_write(0x05, 0x02, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x02, 0); + toshiba_spi_write(0x07, 0x05, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x03, 0); + toshiba_spi_write(0x00, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x04, 0); + toshiba_spi_write(0x02, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x05, 0); + toshiba_spi_write(0x07, 0x07, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x06, 0); + toshiba_spi_write(0x10, 0x10, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x07, 0); + toshiba_spi_write(0x02, 0x02, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x08, 0); + toshiba_spi_write(0x07, 0x04, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x09, 0); + toshiba_spi_write(0x07, 0x07, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x0a, 0); + toshiba_spi_write(0x00, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x0b, 0); + toshiba_spi_write(0x00, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x0c, 0); + toshiba_spi_write(0x07, 0x07, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x0d, 0); + toshiba_spi_write(0x10, 0x10, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x10, 0); + toshiba_spi_write(0x01, 0x04, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x11, 0); + toshiba_spi_write(0x05, 0x03, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x12, 0); + toshiba_spi_write(0x03, 0x04, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x15, 0); + toshiba_spi_write(0x03, 0x04, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x16, 0); + toshiba_spi_write(0x03, 0x1c, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x17, 0); + toshiba_spi_write(0x02, 0x04, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x18, 0); + toshiba_spi_write(0x04, 0x02, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x19, 0); + toshiba_spi_write(0x03, 0x05, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x1c, 0); + toshiba_spi_write(0x07, 0x07, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x1d, 0); + toshiba_spi_write(0x02, 0x1f, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x20, 0); + toshiba_spi_write(0x05, 0x07, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x21, 0); + toshiba_spi_write(0x06, 0x04, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x22, 0); + toshiba_spi_write(0x04, 0x05, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x27, 0); + toshiba_spi_write(0x02, 0x03, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x28, 0); + toshiba_spi_write(0x03, 0x00, 1); + + udelay(500); + toshiba_spi_write(0x03, 0x29, 0); + toshiba_spi_write(0x00, 0x02, 1); + +#endif + udelay(500); + toshiba_spi_write(0x01, 0x00, 0); + toshiba_spi_write(0x36, 0x3c, 1); + udelay(500); + + toshiba_spi_write(0x01, 0x01, 0); + toshiba_spi_write(0x40, 0x03, 1); + + udelay(500); + toshiba_spi_write(0x01, 0x02, 0); + toshiba_spi_write(0x00, 0x01, 1); + + udelay(500); + toshiba_spi_write(0x01, 0x03, 0); + toshiba_spi_write(0x3c, 0x58, 1); + + udelay(500); + toshiba_spi_write(0x01, 0x0c, 0); + toshiba_spi_write(0x01, 0x35, 1); + + udelay(500); + toshiba_spi_write(0x01, 0x06, 0); + toshiba_spi_write(0x00, 0x02, 1); + + udelay(500); + toshiba_spi_write(0x00, 0x29, 0); + toshiba_spi_write(0x03, 0xbf, 1); + + udelay(500); + toshiba_spi_write(0x01, 0x06, 0); + toshiba_spi_write(0x00, 0x03, 1); + msleep(32); + + toshiba_spi_write(0x01, 0x01, 0); + toshiba_spi_write(0x40, 0x10, 1); + msleep(80); + + toshiba_state.display_on = TRUE; + } +} + +static int lcdc_toshiba_panel_on(struct platform_device *pdev) +{ + if (!toshiba_state.disp_initialized) { + /* Configure reset GPIO that drives DAC */ + if (lcdc_toshiba_pdata->panel_config_gpio) + lcdc_toshiba_pdata->panel_config_gpio(1); + toshiba_disp_powerup(); + toshiba_disp_on(); + toshiba_state.disp_initialized = TRUE; + } + return 0; +} + +static int lcdc_toshiba_panel_off(struct platform_device *pdev) +{ + if (toshiba_state.disp_powered_up && toshiba_state.display_on) { + toshiba_spi_write(0x01, 0x06, 1); + toshiba_spi_write(0x00, 0x02, 1); + msleep(80); + + toshiba_spi_write(0x01, 0x06, 1); + toshiba_spi_write(0x00, 0x00, 1); + + toshiba_spi_write(0x00, 0x29, 1); + toshiba_spi_write(0x00, 0x02, 1); + + toshiba_spi_write(0x01, 0x00, 1); + toshiba_spi_write(0x30, 0x00, 1); + + if (lcdc_toshiba_pdata->panel_config_gpio) + lcdc_toshiba_pdata->panel_config_gpio(0); + toshiba_state.display_on = FALSE; + toshiba_state.disp_initialized = FALSE; + } + + return 0; +} + +static void lcdc_toshiba_set_backlight(struct msm_fb_data_type *mfd) +{ + int ret; + int bl_level; + + bl_level = mfd->bl_level; + + if (lcdc_toshiba_pdata && lcdc_toshiba_pdata->pmic_backlight) + ret = lcdc_toshiba_pdata->pmic_backlight(bl_level); + else + pr_err("%s(): Backlight level set failed", __func__); + + return; +} + +static int __devinit toshiba_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + lcdc_toshiba_pdata = pdev->dev.platform_data; + spi_pin_assign(); + return 0; + } + msm_fb_add_device(pdev); + return 0; +} + +static struct platform_driver this_driver = { + .probe = toshiba_probe, + .driver = { + .name = "lcdc_toshiba_fwvga_pt", + }, +}; + +static struct msm_fb_panel_data toshiba_panel_data = { + .on = lcdc_toshiba_panel_on, + .off = lcdc_toshiba_panel_off, + .set_backlight = lcdc_toshiba_set_backlight, +}; + +static struct platform_device this_device = { + .name = "lcdc_toshiba_fwvga_pt", + .id = 1, + .dev = { + .platform_data = &toshiba_panel_data, + } +}; + +static int __init lcdc_toshiba_panel_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + + ret = msm_fb_detect_client("lcdc_toshiba_fwvga_pt"); + if (ret) + return 0; + + + ret = platform_driver_register(&this_driver); + if (ret) + return ret; + + pinfo = &toshiba_panel_data.panel_info; + pinfo->xres = 480; + pinfo->yres = 864; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = LCDC_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->fb_num = 2; + /* 30Mhz mdp_lcdc_pclk and mdp_lcdc_pad_pcl */ + pinfo->clk_rate = 30720000; + pinfo->bl_max = 100; + pinfo->bl_min = 1; + + if (cpu_is_msm7x25a() || cpu_is_msm7x25aa()) { + pinfo->yres = 320; + pinfo->lcdc.h_back_porch = 10; + pinfo->lcdc.h_front_porch = 21; + pinfo->lcdc.h_pulse_width = 5; + pinfo->lcdc.v_back_porch = 8; + pinfo->lcdc.v_front_porch = 540; + pinfo->lcdc.v_pulse_width = 42; + pinfo->lcdc.border_clr = 0; /* blk */ + pinfo->lcdc.underflow_clr = 0xff; /* blue */ + pinfo->lcdc.hsync_skew = 0; + } else { + pinfo->lcdc.h_back_porch = 8; + pinfo->lcdc.h_front_porch = 16; + pinfo->lcdc.h_pulse_width = 8; + pinfo->lcdc.v_back_porch = 2; + pinfo->lcdc.v_front_porch = 2; + pinfo->lcdc.v_pulse_width = 2; + pinfo->lcdc.border_clr = 0; /* blk */ + pinfo->lcdc.underflow_clr = 0xff; /* blue */ + pinfo->lcdc.hsync_skew = 0; + } + + ret = platform_device_register(&this_device); + if (ret) { + printk(KERN_ERR "%s not able to register the device\n", + __func__); + platform_driver_unregister(&this_driver); + } + return ret; +} + +device_initcall(lcdc_toshiba_panel_init); diff --git a/drivers/video/msm/lcdc_toshiba_wvga_pt.c b/drivers/video/msm/lcdc_toshiba_wvga_pt.c new file mode 100644 index 00000000000..f0aa8f55cff --- /dev/null +++ b/drivers/video/msm/lcdc_toshiba_wvga_pt.c @@ -0,0 +1,519 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#ifdef CONFIG_SPI_QSD +#include +#endif +#include +#include +#include "msm_fb.h" + +#ifdef CONFIG_FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM +#include "mddihosti.h" +#endif + +#ifdef CONFIG_SPI_QSD +#define LCDC_TOSHIBA_SPI_DEVICE_NAME "lcdc_toshiba_ltm030dd40" +static struct spi_device *lcdc_toshiba_spi_client; +#else +static int spi_cs; +static int spi_sclk; +static int spi_mosi; +static int spi_miso; +#endif +struct toshiba_state_type{ + boolean disp_initialized; + boolean display_on; + boolean disp_powered_up; +}; + +static struct toshiba_state_type toshiba_state = { 0 }; +static struct msm_panel_common_pdata *lcdc_toshiba_pdata; + +#ifndef CONFIG_SPI_QSD +static void toshiba_spi_write_byte(char dc, uint8 data) +{ + uint32 bit; + int bnum; + + gpio_set_value(spi_sclk, 0); /* clk low */ + /* dc: 0 for command, 1 for parameter */ + gpio_set_value(spi_mosi, dc); + udelay(1); /* at least 20 ns */ + gpio_set_value(spi_sclk, 1); /* clk high */ + udelay(1); /* at least 20 ns */ + bnum = 8; /* 8 data bits */ + bit = 0x80; + while (bnum) { + gpio_set_value(spi_sclk, 0); /* clk low */ + if (data & bit) + gpio_set_value(spi_mosi, 1); + else + gpio_set_value(spi_mosi, 0); + udelay(1); + gpio_set_value(spi_sclk, 1); /* clk high */ + udelay(1); + bit >>= 1; + bnum--; + } +} +#endif + +static int toshiba_spi_write(char cmd, uint32 data, int num) +{ + char *bp; +#ifdef CONFIG_SPI_QSD + char tx_buf[4]; + int rc, i; + struct spi_message m; + struct spi_transfer t; + uint32 final_data = 0; + + if (!lcdc_toshiba_spi_client) { + printk(KERN_ERR "%s lcdc_toshiba_spi_client is NULL\n", + __func__); + return -EINVAL; + } + + memset(&t, 0, sizeof t); + t.tx_buf = tx_buf; + spi_setup(lcdc_toshiba_spi_client); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + /* command byte first */ + final_data |= cmd << 23; + t.len = num + 2; + if (t.len < 4) + t.bits_per_word = 8 * t.len; + /* followed by parameter bytes */ + if (num) { + bp = (char *)&data;; + bp += (num - 1); + i = 1; + while (num) { + final_data |= 1 << (((4 - i) << 3) - i - 1); + final_data |= *bp << (((4 - i - 1) << 3) - i - 1); + num--; + bp--; + i++; + } + } + + bp = (char *)&final_data; + for (i = 0; i < t.len; i++) + tx_buf[i] = bp[3 - i]; + t.rx_buf = NULL; + rc = spi_sync(lcdc_toshiba_spi_client, &m); + if (rc) + printk(KERN_ERR "spi_sync _write failed %d\n", rc); + return rc; +#else + gpio_set_value(spi_cs, 1); /* cs high */ + + /* command byte first */ + toshiba_spi_write_byte(0, cmd); + + /* followed by parameter bytes */ + if (num) { + bp = (char *)&data;; + bp += (num - 1); + while (num) { + toshiba_spi_write_byte(1, *bp); + num--; + bp--; + } + } + + gpio_set_value(spi_cs, 0); /* cs low */ + udelay(1); + return 0; +#endif +} + +static int toshiba_spi_read_bytes(char cmd, uint32 *data, int num) +{ +#ifdef CONFIG_SPI_QSD + char tx_buf[5]; + char rx_buf[5]; + int rc; + struct spi_message m; + struct spi_transfer t; + + if (!lcdc_toshiba_spi_client) { + printk(KERN_ERR "%s lcdc_toshiba_spi_client is NULL\n", + __func__); + return -EINVAL; + } + + memset(&t, 0, sizeof t); + t.tx_buf = tx_buf; + t.rx_buf = rx_buf; + spi_setup(lcdc_toshiba_spi_client); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + /* command byte first */ + tx_buf[0] = 0 | ((cmd >> 1) & 0x7f); + tx_buf[1] = (cmd & 0x01) << 7; + tx_buf[2] = 0; + tx_buf[3] = 0; + tx_buf[4] = 0; + + t.len = 5; + + rc = spi_sync(lcdc_toshiba_spi_client, &m); + *data = 0; + *data = ((rx_buf[1] & 0x1f) << 19) | (rx_buf[2] << 11) | + (rx_buf[3] << 3) | ((rx_buf[4] & 0xe0) >> 5); + if (rc) + printk(KERN_ERR "spi_sync _read failed %d\n", rc); + return rc; +#else + uint32 dbit, bits; + int bnum; + + gpio_set_value(spi_cs, 1); /* cs high */ + + /* command byte first */ + toshiba_spi_write_byte(0, cmd); + + if (num > 1) { + /* extra dc bit */ + gpio_set_value(spi_sclk, 0); /* clk low */ + udelay(1); + dbit = gpio_get_value(spi_miso);/* dc bit */ + udelay(1); + gpio_set_value(spi_sclk, 1); /* clk high */ + } + + /* followed by data bytes */ + bnum = num * 8; /* number of bits */ + bits = 0; + while (bnum) { + bits <<= 1; + gpio_set_value(spi_sclk, 0); /* clk low */ + udelay(1); + dbit = gpio_get_value(spi_miso); + udelay(1); + gpio_set_value(spi_sclk, 1); /* clk high */ + bits |= dbit; + bnum--; + } + + *data = bits; + + udelay(1); + gpio_set_value(spi_cs, 0); /* cs low */ + udelay(1); + return 0; +#endif +} + +#ifndef CONFIG_SPI_QSD +static void spi_pin_assign(void) +{ + /* Setting the Default GPIO's */ + spi_sclk = *(lcdc_toshiba_pdata->gpio_num); + spi_cs = *(lcdc_toshiba_pdata->gpio_num + 1); + spi_mosi = *(lcdc_toshiba_pdata->gpio_num + 2); + spi_miso = *(lcdc_toshiba_pdata->gpio_num + 3); +} +#endif + +static void toshiba_disp_powerup(void) +{ + if (!toshiba_state.disp_powered_up && !toshiba_state.display_on) { + /* Reset the hardware first */ + /* Include DAC power up implementation here */ + toshiba_state.disp_powered_up = TRUE; + } +} + +static void toshiba_disp_on(void) +{ + uint32 data; + +#ifndef CONFIG_SPI_QSD + gpio_set_value(spi_cs, 0); /* low */ + gpio_set_value(spi_sclk, 1); /* high */ + gpio_set_value(spi_mosi, 0); + gpio_set_value(spi_miso, 0); +#endif + + if (toshiba_state.disp_powered_up && !toshiba_state.display_on) { + toshiba_spi_write(0, 0, 0); + mdelay(7); + toshiba_spi_write(0, 0, 0); + mdelay(7); + toshiba_spi_write(0, 0, 0); + mdelay(7); + toshiba_spi_write(0xba, 0x11, 1); + toshiba_spi_write(0x36, 0x00, 1); + mdelay(1); + toshiba_spi_write(0x3a, 0x60, 1); + toshiba_spi_write(0xb1, 0x5d, 1); + mdelay(1); + toshiba_spi_write(0xb2, 0x33, 1); + toshiba_spi_write(0xb3, 0x22, 1); + mdelay(1); + toshiba_spi_write(0xb4, 0x02, 1); + toshiba_spi_write(0xb5, 0x1e, 1); /* vcs -- adjust brightness */ + mdelay(1); + toshiba_spi_write(0xb6, 0x27, 1); + toshiba_spi_write(0xb7, 0x03, 1); + mdelay(1); + toshiba_spi_write(0xb9, 0x24, 1); + toshiba_spi_write(0xbd, 0xa1, 1); + mdelay(1); + toshiba_spi_write(0xbb, 0x00, 1); + toshiba_spi_write(0xbf, 0x01, 1); + mdelay(1); + toshiba_spi_write(0xbe, 0x00, 1); + toshiba_spi_write(0xc0, 0x11, 1); + mdelay(1); + toshiba_spi_write(0xc1, 0x11, 1); + toshiba_spi_write(0xc2, 0x11, 1); + mdelay(1); + toshiba_spi_write(0xc3, 0x3232, 2); + mdelay(1); + toshiba_spi_write(0xc4, 0x3232, 2); + mdelay(1); + toshiba_spi_write(0xc5, 0x3232, 2); + mdelay(1); + toshiba_spi_write(0xc6, 0x3232, 2); + mdelay(1); + toshiba_spi_write(0xc7, 0x6445, 2); + mdelay(1); + toshiba_spi_write(0xc8, 0x44, 1); + toshiba_spi_write(0xc9, 0x52, 1); + mdelay(1); + toshiba_spi_write(0xca, 0x00, 1); + mdelay(1); + toshiba_spi_write(0xec, 0x02a4, 2); /* 0x02a4 */ + mdelay(1); + toshiba_spi_write(0xcf, 0x01, 1); + mdelay(1); + toshiba_spi_write(0xd0, 0xc003, 2); /* c003 */ + mdelay(1); + toshiba_spi_write(0xd1, 0x01, 1); + mdelay(1); + toshiba_spi_write(0xd2, 0x0028, 2); + mdelay(1); + toshiba_spi_write(0xd3, 0x0028, 2); + mdelay(1); + toshiba_spi_write(0xd4, 0x26a4, 2); + mdelay(1); + toshiba_spi_write(0xd5, 0x20, 1); + mdelay(1); + toshiba_spi_write(0xef, 0x3200, 2); + mdelay(32); + toshiba_spi_write(0xbc, 0x80, 1); /* wvga pass through */ + toshiba_spi_write(0x3b, 0x00, 1); + mdelay(1); + toshiba_spi_write(0xb0, 0x16, 1); + mdelay(1); + toshiba_spi_write(0xb8, 0xfff5, 2); + mdelay(1); + toshiba_spi_write(0x11, 0, 0); + mdelay(5); + toshiba_spi_write(0x29, 0, 0); + mdelay(5); + toshiba_state.display_on = TRUE; + } + + data = 0; + toshiba_spi_read_bytes(0x04, &data, 3); + printk(KERN_INFO "toshiba_disp_on: id=%x\n", data); + +} + +static int lcdc_toshiba_panel_on(struct platform_device *pdev) +{ + if (!toshiba_state.disp_initialized) { + /* Configure reset GPIO that drives DAC */ + if (lcdc_toshiba_pdata->panel_config_gpio) + lcdc_toshiba_pdata->panel_config_gpio(1); + toshiba_disp_powerup(); + toshiba_disp_on(); + toshiba_state.disp_initialized = TRUE; + } + return 0; +} + +static int lcdc_toshiba_panel_off(struct platform_device *pdev) +{ + if (toshiba_state.disp_powered_up && toshiba_state.display_on) { + /* Main panel power off (Deep standby in) */ + + toshiba_spi_write(0x28, 0, 0); /* display off */ + mdelay(1); + toshiba_spi_write(0xb8, 0x8002, 2); /* output control */ + mdelay(1); + toshiba_spi_write(0x10, 0x00, 1); /* sleep mode in */ + mdelay(85); /* wait 85 msec */ + toshiba_spi_write(0xb0, 0x00, 1); /* deep standby in */ + mdelay(1); + if (lcdc_toshiba_pdata->panel_config_gpio) + lcdc_toshiba_pdata->panel_config_gpio(0); + toshiba_state.display_on = FALSE; + toshiba_state.disp_initialized = FALSE; + } + return 0; +} + +static void lcdc_toshiba_set_backlight(struct msm_fb_data_type *mfd) +{ + int bl_level; + int ret = -EPERM; + int i = 0; + + bl_level = mfd->bl_level; + + while (i++ < 3) { + ret = pmic_set_led_intensity(LED_LCD, bl_level); + if (ret == 0) + return; + msleep(10); + } + + printk(KERN_WARNING "%s: can't set lcd backlight!\n", + __func__); +} + +static int __devinit toshiba_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + lcdc_toshiba_pdata = pdev->dev.platform_data; +#ifndef CONFIG_SPI_QSD + spi_pin_assign(); +#endif + return 0; + } + msm_fb_add_device(pdev); + return 0; +} + +#ifdef CONFIG_SPI_QSD +static int __devinit lcdc_toshiba_spi_probe(struct spi_device *spi) +{ + lcdc_toshiba_spi_client = spi; + lcdc_toshiba_spi_client->bits_per_word = 32; + return 0; +} +static int __devexit lcdc_toshiba_spi_remove(struct spi_device *spi) +{ + lcdc_toshiba_spi_client = NULL; + return 0; +} + +static struct spi_driver lcdc_toshiba_spi_driver = { + .driver = { + .name = LCDC_TOSHIBA_SPI_DEVICE_NAME, + .owner = THIS_MODULE, + }, + .probe = lcdc_toshiba_spi_probe, + .remove = __devexit_p(lcdc_toshiba_spi_remove), +}; +#endif +static struct platform_driver this_driver = { + .probe = toshiba_probe, + .driver = { + .name = "lcdc_toshiba_wvga", + }, +}; + +static struct msm_fb_panel_data toshiba_panel_data = { + .on = lcdc_toshiba_panel_on, + .off = lcdc_toshiba_panel_off, + .set_backlight = lcdc_toshiba_set_backlight, +}; + +static struct platform_device this_device = { + .name = "lcdc_toshiba_wvga", + .id = 1, + .dev = { + .platform_data = &toshiba_panel_data, + } +}; + +static int __init lcdc_toshiba_panel_init(void) +{ + int ret; + struct msm_panel_info *pinfo; +#ifdef CONFIG_FB_MSM_TRY_MDDI_CATCH_LCDC_PRISM + if (mddi_get_client_id() != 0) + return 0; + + ret = msm_fb_detect_client("lcdc_toshiba_wvga_pt"); + if (ret) + return 0; + +#endif + + ret = platform_driver_register(&this_driver); + if (ret) + return ret; + + pinfo = &toshiba_panel_data.panel_info; + pinfo->xres = 480; + pinfo->yres = 800; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = LCDC_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->fb_num = 2; + /* 30Mhz mdp_lcdc_pclk and mdp_lcdc_pad_pcl */ + pinfo->clk_rate = 30720000; + pinfo->bl_max = 15; + pinfo->bl_min = 1; + + pinfo->lcdc.h_back_porch = 184; /* hsw = 8 + hbp=184 */ + pinfo->lcdc.h_front_porch = 4; + pinfo->lcdc.h_pulse_width = 8; + pinfo->lcdc.v_back_porch = 2; /* vsw=1 + vbp = 2 */ + pinfo->lcdc.v_front_porch = 3; + pinfo->lcdc.v_pulse_width = 1; + pinfo->lcdc.border_clr = 0; /* blk */ + pinfo->lcdc.underflow_clr = 0xff; /* blue */ + pinfo->lcdc.hsync_skew = 0; + + ret = platform_device_register(&this_device); + if (ret) { + printk(KERN_ERR "%s not able to register the device\n", + __func__); + goto fail_driver; + } +#ifdef CONFIG_SPI_QSD + ret = spi_register_driver(&lcdc_toshiba_spi_driver); + + if (ret) { + printk(KERN_ERR "%s not able to register spi\n", __func__); + goto fail_device; + } +#endif + return ret; + +#ifdef CONFIG_SPI_QSD +fail_device: + platform_device_unregister(&this_device); +#endif +fail_driver: + platform_driver_unregister(&this_driver); + return ret; +} + +device_initcall(lcdc_toshiba_panel_init); diff --git a/drivers/video/msm/lcdc_wxga.c b/drivers/video/msm/lcdc_wxga.c new file mode 100644 index 00000000000..32047047918 --- /dev/null +++ b/drivers/video/msm/lcdc_wxga.c @@ -0,0 +1,53 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" + +static int __init lcdc_wxga_init(void) +{ + int ret; + struct msm_panel_info pinfo; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + if (msm_fb_detect_client("lcdc_wxga")) + return 0; +#endif + + pinfo.xres = 1280; + pinfo.yres = 720; + MSM_FB_SINGLE_MODE_PANEL(&pinfo); + pinfo.type = LCDC_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.fb_num = 2; + pinfo.clk_rate = 74250000; + + pinfo.lcdc.h_back_porch = 124; + pinfo.lcdc.h_front_porch = 110; + pinfo.lcdc.h_pulse_width = 136; + pinfo.lcdc.v_back_porch = 19; + pinfo.lcdc.v_front_porch = 5; + pinfo.lcdc.v_pulse_width = 6; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + + ret = lcdc_device_register(&pinfo); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(lcdc_wxga_init); diff --git a/drivers/video/msm/logo.c b/drivers/video/msm/logo.c new file mode 100644 index 00000000000..c061e86faf1 --- /dev/null +++ b/drivers/video/msm/logo.c @@ -0,0 +1,97 @@ +/* drivers/video/msm/logo.c + * + * Show Logo in RLE 565 format + * + * Copyright (C) 2008 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#define fb_width(fb) ((fb)->var.xres) +#define fb_height(fb) ((fb)->var.yres) +#define fb_size(fb) ((fb)->var.xres * (fb)->var.yres * 2) + +static void memset16(void *_ptr, unsigned short val, unsigned count) +{ + unsigned short *ptr = _ptr; + count >>= 1; + while (count--) + *ptr++ = val; +} + +/* 565RLE image format: [count(2 bytes), rle(2 bytes)] */ +int load_565rle_image(char *filename) +{ + struct fb_info *info; + int fd, count, err = 0; + unsigned max; + unsigned short *data, *bits, *ptr; + + info = registered_fb[0]; + if (!info) { + printk(KERN_WARNING "%s: Can not access framebuffer\n", + __func__); + return -ENODEV; + } + + fd = sys_open(filename, O_RDONLY, 0); + if (fd < 0) { + printk(KERN_WARNING "%s: Can not open %s\n", + __func__, filename); + return -ENOENT; + } + count = sys_lseek(fd, (off_t)0, 2); + if (count <= 0) { + err = -EIO; + goto err_logo_close_file; + } + sys_lseek(fd, (off_t)0, 0); + data = kmalloc(count, GFP_KERNEL); + if (!data) { + printk(KERN_WARNING "%s: Can not alloc data\n", __func__); + err = -ENOMEM; + goto err_logo_close_file; + } + if (sys_read(fd, (char *)data, count) != count) { + err = -EIO; + goto err_logo_free_data; + } + + max = fb_width(info) * fb_height(info); + ptr = data; + bits = (unsigned short *)(info->screen_base); + while (count > 3) { + unsigned n = ptr[0]; + if (n > max) + break; + memset16(bits, ptr[1], n << 1); + bits += n; + max -= n; + ptr += 2; + count -= 4; + } + +err_logo_free_data: + kfree(data); +err_logo_close_file: + sys_close(fd); + return err; +} +EXPORT_SYMBOL(load_565rle_image); diff --git a/drivers/video/msm/mddi.c b/drivers/video/msm/mddi.c index 178b0720bd7..9b9ee946a28 100644 --- a/drivers/video/msm/mddi.c +++ b/drivers/video/msm/mddi.c @@ -2,7 +2,7 @@ * MSM MDDI Transport * * Copyright (C) 2007 Google Incorporated - * Copyright (C) 2007 QUALCOMM Incorporated + * Copyright (c) 2007-2011, Code Aurora Forum. 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 @@ -10,816 +10,578 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include -#include +#include +#include +#include #include -#include -#include -#include #include +#include +#include +#include + +#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include "mddi_hw.h" - -#define FLAG_DISABLE_HIBERNATION 0x0001 -#define FLAG_HAVE_CAPS 0x0002 -#define FLAG_HAS_VSYNC_IRQ 0x0004 -#define FLAG_HAVE_STATUS 0x0008 - -#define CMD_GET_CLIENT_CAP 0x0601 -#define CMD_GET_CLIENT_STATUS 0x0602 - -union mddi_rev { - unsigned char raw[MDDI_REV_BUFFER_SIZE]; - struct mddi_rev_packet hdr; - struct mddi_client_status status; - struct mddi_client_caps caps; - struct mddi_register_access reg; -}; +#include +#include +#include "msm_fb.h" +#include "mddihosti.h" +#include "mddihost.h" +#include +#include + +static int mddi_probe(struct platform_device *pdev); +static int mddi_remove(struct platform_device *pdev); + +static int mddi_off(struct platform_device *pdev); +static int mddi_on(struct platform_device *pdev); + +#ifdef CONFIG_PM +static int mddi_suspend(struct platform_device *pdev, pm_message_t state); +static int mddi_resume(struct platform_device *pdev); +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mddi_early_suspend(struct early_suspend *h); +static void mddi_early_resume(struct early_suspend *h); +#endif + +static void pmdh_clk_disable(void); +static void pmdh_clk_enable(void); +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; +static struct clk *mddi_clk; +static struct clk *mddi_pclk; +static struct mddi_platform_data *mddi_pdata; + +DEFINE_MUTEX(mddi_timer_lock); + +static int mddi_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} -struct reg_read_info { - struct completion done; - uint32_t reg; - uint32_t status; - uint32_t result; -}; +static int mddi_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} -struct mddi_info { - uint16_t flags; - uint16_t version; - char __iomem *base; - int irq; - struct clk *clk; - struct msm_mddi_client_data client_data; - - /* buffer for rev encap packets */ - void *rev_data; - dma_addr_t rev_addr; - struct mddi_llentry *reg_write_data; - dma_addr_t reg_write_addr; - struct mddi_llentry *reg_read_data; - dma_addr_t reg_read_addr; - size_t rev_data_curr; - - spinlock_t int_lock; - uint32_t int_enable; - uint32_t got_int; - wait_queue_head_t int_wait; - - struct mutex reg_write_lock; - struct mutex reg_read_lock; - struct reg_read_info *reg_read; - - struct mddi_client_caps caps; - struct mddi_client_status status; - - void (*power_client)(struct msm_mddi_client_data *, int); - - /* client device published to bind us to the - * appropriate mddi_client driver - */ - char client_name[20]; +static int mddi_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: idling...\n"); + return 0; +} - struct platform_device client_pdev; +static struct dev_pm_ops mddi_dev_pm_ops = { + .runtime_suspend = mddi_runtime_suspend, + .runtime_resume = mddi_runtime_resume, + .runtime_idle = mddi_runtime_idle, }; -static void mddi_init_rev_encap(struct mddi_info *mddi); - -#define mddi_readl(r) readl(mddi->base + (MDDI_##r)) -#define mddi_writel(v, r) writel((v), mddi->base + (MDDI_##r)) +static int pmdh_clk_status; +int irq_enabled; +unsigned char mddi_timer_shutdown_flag; -void mddi_activate_link(struct msm_mddi_client_data *cdata) -{ - struct mddi_info *mddi = container_of(cdata, struct mddi_info, - client_data); - - mddi_writel(MDDI_CMD_LINK_ACTIVE, CMD); -} +static struct platform_driver mddi_driver = { + .probe = mddi_probe, + .remove = mddi_remove, +#ifndef CONFIG_HAS_EARLYSUSPEND +#ifdef CONFIG_PM + .suspend = mddi_suspend, + .resume = mddi_resume, +#endif +#endif + .shutdown = NULL, + .driver = { + .name = "mddi", + .pm = &mddi_dev_pm_ops, + }, +}; -static void mddi_handle_link_list_done(struct mddi_info *mddi) -{ -} +extern int int_mddi_pri_flag; +DEFINE_MUTEX(pmdh_clk_lock); -static void mddi_reset_rev_encap_ptr(struct mddi_info *mddi) +int pmdh_clk_func(int value) { - printk(KERN_INFO "mddi: resetting rev ptr\n"); - mddi->rev_data_curr = 0; - mddi_writel(mddi->rev_addr, REV_PTR); - mddi_writel(mddi->rev_addr, REV_PTR); - mddi_writel(MDDI_CMD_FORCE_NEW_REV_PTR, CMD); + int ret = 0; + + switch (value) { + case 0: + pmdh_clk_disable(); + break; + case 1: + pmdh_clk_enable(); + break; + case 2: + default: + mutex_lock(&pmdh_clk_lock); + ret = pmdh_clk_status; + mutex_unlock(&pmdh_clk_lock); + break; + } + return ret; } -static void mddi_handle_rev_data(struct mddi_info *mddi, union mddi_rev *rev) +static void pmdh_clk_disable() { - int i; - struct reg_read_info *ri; - - if ((rev->hdr.length <= MDDI_REV_BUFFER_SIZE - 2) && - (rev->hdr.length >= sizeof(struct mddi_rev_packet) - 2)) { - - switch (rev->hdr.type) { - case TYPE_CLIENT_CAPS: - memcpy(&mddi->caps, &rev->caps, - sizeof(struct mddi_client_caps)); - mddi->flags |= FLAG_HAVE_CAPS; - wake_up(&mddi->int_wait); - break; - case TYPE_CLIENT_STATUS: - memcpy(&mddi->status, &rev->status, - sizeof(struct mddi_client_status)); - mddi->flags |= FLAG_HAVE_STATUS; - wake_up(&mddi->int_wait); - break; - case TYPE_REGISTER_ACCESS: - ri = mddi->reg_read; - if (ri == 0) { - printk(KERN_INFO "rev: got reg %x = %x without " - " pending read\n", - rev->reg.register_address, - rev->reg.register_data_list); - break; - } - if (ri->reg != rev->reg.register_address) { - printk(KERN_INFO "rev: got reg %x = %x for " - "wrong register, expected " - "%x\n", - rev->reg.register_address, - rev->reg.register_data_list, ri->reg); - break; - } - mddi->reg_read = NULL; - ri->status = 0; - ri->result = rev->reg.register_data_list; - complete(&ri->done); - break; - default: - printk(KERN_INFO "rev: unknown reverse packet: " - "len=%04x type=%04x CURR_REV_PTR=%x\n", - rev->hdr.length, rev->hdr.type, - mddi_readl(CURR_REV_PTR)); - for (i = 0; i < rev->hdr.length + 2; i++) { - if ((i % 16) == 0) - printk(KERN_INFO "\n"); - printk(KERN_INFO " %02x", rev->raw[i]); - } - printk(KERN_INFO "\n"); - mddi_reset_rev_encap_ptr(mddi); - } - } else { - printk(KERN_INFO "bad rev length, %d, CURR_REV_PTR %x\n", - rev->hdr.length, mddi_readl(CURR_REV_PTR)); - mddi_reset_rev_encap_ptr(mddi); + mutex_lock(&pmdh_clk_lock); + if (pmdh_clk_status == 0) { + mutex_unlock(&pmdh_clk_lock); + return; } -} -static void mddi_wait_interrupt(struct mddi_info *mddi, uint32_t intmask); + if (mddi_host_timer.function) { + mutex_lock(&mddi_timer_lock); + mddi_timer_shutdown_flag = 1; + mutex_unlock(&mddi_timer_lock); + del_timer_sync(&mddi_host_timer); + mutex_lock(&mddi_timer_lock); + mddi_timer_shutdown_flag = 0; + mutex_unlock(&mddi_timer_lock); + } + if (int_mddi_pri_flag && irq_enabled) { + disable_irq(INT_MDDI_PRI); + irq_enabled = 0; + } -static void mddi_handle_rev_data_avail(struct mddi_info *mddi) -{ - uint32_t rev_data_count; - uint32_t rev_crc_err_count; - struct reg_read_info *ri; - size_t prev_offset; - uint16_t length; - - union mddi_rev *crev = mddi->rev_data + mddi->rev_data_curr; - - /* clear the interrupt */ - mddi_writel(MDDI_INT_REV_DATA_AVAIL, INT); - rev_data_count = mddi_readl(REV_PKT_CNT); - rev_crc_err_count = mddi_readl(REV_CRC_ERR); - if (rev_data_count > 1) - printk(KERN_INFO "rev_data_count %d\n", rev_data_count); - - if (rev_crc_err_count) { - printk(KERN_INFO "rev_crc_err_count %d, INT %x\n", - rev_crc_err_count, mddi_readl(INT)); - ri = mddi->reg_read; - if (ri == 0) { - printk(KERN_INFO "rev: got crc error without pending " - "read\n"); - } else { - mddi->reg_read = NULL; - ri->status = -EIO; - ri->result = -1; - complete(&ri->done); - } + if (mddi_clk) { + clk_disable(mddi_clk); + pmdh_clk_status = 0; } + if (mddi_pclk) + clk_disable(mddi_pclk); + mutex_unlock(&pmdh_clk_lock); +} - if (rev_data_count == 0) +static void pmdh_clk_enable() +{ + mutex_lock(&pmdh_clk_lock); + if (pmdh_clk_status == 1) { + mutex_unlock(&pmdh_clk_lock); return; + } - prev_offset = mddi->rev_data_curr; - - length = *((uint8_t *)mddi->rev_data + mddi->rev_data_curr); - mddi->rev_data_curr++; - if (mddi->rev_data_curr == MDDI_REV_BUFFER_SIZE) - mddi->rev_data_curr = 0; - length += *((uint8_t *)mddi->rev_data + mddi->rev_data_curr) << 8; - mddi->rev_data_curr += 1 + length; - if (mddi->rev_data_curr >= MDDI_REV_BUFFER_SIZE) - mddi->rev_data_curr = - mddi->rev_data_curr % MDDI_REV_BUFFER_SIZE; - - if (length > MDDI_REV_BUFFER_SIZE - 2) { - printk(KERN_INFO "mddi: rev data length greater than buffer" - "size\n"); - mddi_reset_rev_encap_ptr(mddi); - return; + if (mddi_clk) { + clk_enable(mddi_clk); + pmdh_clk_status = 1; } + if (mddi_pclk) + clk_enable(mddi_pclk); - if (prev_offset + 2 + length >= MDDI_REV_BUFFER_SIZE) { - union mddi_rev tmprev; - size_t rem = MDDI_REV_BUFFER_SIZE - prev_offset; - memcpy(&tmprev.raw[0], mddi->rev_data + prev_offset, rem); - memcpy(&tmprev.raw[rem], mddi->rev_data, 2 + length - rem); - mddi_handle_rev_data(mddi, &tmprev); - } else { - mddi_handle_rev_data(mddi, crev); + if (int_mddi_pri_flag && !irq_enabled) { + enable_irq(INT_MDDI_PRI); + irq_enabled = 1; } - if (prev_offset < MDDI_REV_BUFFER_SIZE / 2 && - mddi->rev_data_curr >= MDDI_REV_BUFFER_SIZE / 2) { - mddi_writel(mddi->rev_addr, REV_PTR); + if (mddi_host_timer.function) + mddi_host_timer_service(0); + + mutex_unlock(&pmdh_clk_lock); +} + +static int mddi_off(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + boolean dma_pending, dma_update_flag; + int ret, i; + + mfd = platform_get_drvdata(pdev); + + for (i = 0; i < 6; i++) { + dma_update_flag = mfd->dma_update_flag; + dma_pending = mfd->dma->busy; + if (dma_update_flag && !dma_pending) + break; + msleep(5); } + + pmdh_clk_enable(); + ret = panel_next_off(pdev); + pmdh_clk_disable(); + + if (mddi_pdata && mddi_pdata->mddi_power_save) + mddi_pdata->mddi_power_save(0); +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(0); +#else + if (mfd->ebi1_clk) + clk_disable(mfd->ebi1_clk); +#endif + pm_runtime_put(&pdev->dev); + return ret; } -static irqreturn_t mddi_isr(int irq, void *data) +static int mddi_on(struct platform_device *pdev) { - struct msm_mddi_client_data *cdata = data; - struct mddi_info *mddi = container_of(cdata, struct mddi_info, - client_data); - uint32_t active, status; + int ret = 0; + u32 clk_rate; + struct msm_fb_data_type *mfd; +#ifdef ENABLE_FWD_LINK_SKEW_CALIBRATION + mddi_host_type host_idx = MDDI_HOST_PRIM; + u32 stat_reg; +#endif + + mfd = platform_get_drvdata(pdev); + pm_runtime_get(&pdev->dev); + if (mddi_pdata && mddi_pdata->mddi_power_save) + mddi_pdata->mddi_power_save(1); + + pmdh_clk_enable(); +#ifdef ENABLE_FWD_LINK_SKEW_CALIBRATION + if (mddi_client_type < 2) { + /* For skew calibration, clock should be less than 50MHz */ + if (!clk_set_min_rate(mddi_clk, 49000000)) { + stat_reg = mddi_host_reg_in(STAT); + printk(KERN_DEBUG "\n stat_reg = 0x%x", stat_reg); + mddi_host_reg_out(CMD, MDDI_CMD_HIBERNATE); + if (stat_reg & (0x1 << 4)) + mddi_host_reg_out(CMD, MDDI_CMD_LINK_ACTIVE); + + mddi_host_reg_out(CMD, MDDI_CMD_SEND_RTD); + mddi_send_fw_link_skew_cal(host_idx); + mddi_host_reg_out(CMD, MDDI_CMD_SEND_RTD); + mddi_host_reg_out(CMD, MDDI_CMD_HIBERNATE | 1); + } else { + printk(KERN_ERR "%s: clk_set_min_rate failed\n", + __func__); + } + } +#endif + + clk_rate = mfd->fbi->var.pixclock; + clk_rate = min(clk_rate, mfd->panel_info.clk_max); + + if (mddi_pdata && + mddi_pdata->mddi_sel_clk && + mddi_pdata->mddi_sel_clk(&clk_rate)) + printk(KERN_ERR + "%s: can't select mddi io clk targate rate = %d\n", + __func__, clk_rate); + + if (clk_set_min_rate(mddi_clk, clk_rate) < 0) + printk(KERN_ERR "%s: clk_set_min_rate failed\n", + __func__); + +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(2); +#else + if (mfd->ebi1_clk) + clk_enable(mfd->ebi1_clk); +#endif + ret = panel_next_on(pdev); - spin_lock(&mddi->int_lock); + return ret; +} - active = mddi_readl(INT); - status = mddi_readl(STAT); +static int mddi_resource_initialized; - mddi_writel(active, INT); +static int mddi_probe(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct platform_device *mdp_dev = NULL; + struct msm_fb_panel_data *pdata = NULL; + int rc; + resource_size_t size ; + u32 clk_rate; - /* ignore any interrupts we have disabled */ - active &= mddi->int_enable; + if ((pdev->id == 0) && (pdev->num_resources >= 0)) { + mddi_pdata = pdev->dev.platform_data; - mddi->got_int |= active; - wake_up(&mddi->int_wait); + size = resource_size(&pdev->resource[0]); + msm_pmdh_base = ioremap(pdev->resource[0].start, size); - if (active & MDDI_INT_PRI_LINK_LIST_DONE) { - mddi->int_enable &= (~MDDI_INT_PRI_LINK_LIST_DONE); - mddi_handle_link_list_done(mddi); - } - if (active & MDDI_INT_REV_DATA_AVAIL) - mddi_handle_rev_data_avail(mddi); + MSM_FB_INFO("primary mddi base phy_addr = 0x%x virt = 0x%x\n", + pdev->resource[0].start, (int) msm_pmdh_base); - if (active & ~MDDI_INT_NEED_CLEAR) - mddi->int_enable &= ~(active & ~MDDI_INT_NEED_CLEAR); + if (unlikely(!msm_pmdh_base)) + return -ENOMEM; - if (active & MDDI_INT_LINK_ACTIVE) { - mddi->int_enable &= (~MDDI_INT_LINK_ACTIVE); - mddi->int_enable |= MDDI_INT_IN_HIBERNATION; - } + if (mddi_pdata && mddi_pdata->mddi_power_save) + mddi_pdata->mddi_power_save(1); - if (active & MDDI_INT_IN_HIBERNATION) { - mddi->int_enable &= (~MDDI_INT_IN_HIBERNATION); - mddi->int_enable |= MDDI_INT_LINK_ACTIVE; + mddi_resource_initialized = 1; + return 0; } - mddi_writel(mddi->int_enable, INTEN); - spin_unlock(&mddi->int_lock); + if (!mddi_resource_initialized) + return -EPERM; - return IRQ_HANDLED; -} + mfd = platform_get_drvdata(pdev); -static long mddi_wait_interrupt_timeout(struct mddi_info *mddi, - uint32_t intmask, int timeout) -{ - unsigned long irq_flags; - - spin_lock_irqsave(&mddi->int_lock, irq_flags); - mddi->got_int &= ~intmask; - mddi->int_enable |= intmask; - mddi_writel(mddi->int_enable, INTEN); - spin_unlock_irqrestore(&mddi->int_lock, irq_flags); - return wait_event_timeout(mddi->int_wait, mddi->got_int & intmask, - timeout); -} + if (!mfd) + return -ENODEV; -static void mddi_wait_interrupt(struct mddi_info *mddi, uint32_t intmask) -{ - if (mddi_wait_interrupt_timeout(mddi, intmask, HZ/10) == 0) - printk(KERN_INFO "mddi_wait_interrupt %d, timeout " - "waiting for %x, INT = %x, STAT = %x gotint = %x\n", - current->pid, intmask, mddi_readl(INT), mddi_readl(STAT), - mddi->got_int); -} + if (mfd->key != MFD_KEY) + return -EINVAL; -static void mddi_init_rev_encap(struct mddi_info *mddi) -{ - memset(mddi->rev_data, 0xee, MDDI_REV_BUFFER_SIZE); - mddi_writel(mddi->rev_addr, REV_PTR); - mddi_writel(MDDI_CMD_FORCE_NEW_REV_PTR, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); -} + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; -void mddi_set_auto_hibernate(struct msm_mddi_client_data *cdata, int on) -{ - struct mddi_info *mddi = container_of(cdata, struct mddi_info, - client_data); - mddi_writel(MDDI_CMD_POWERDOWN, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_IN_HIBERNATION); - mddi_writel(MDDI_CMD_HIBERNATE | !!on, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); -} + mdp_dev = platform_device_alloc("mdp", pdev->id); + if (!mdp_dev) + return -ENOMEM; + /* + * link to the latest pdev + */ + mfd->pdev = mdp_dev; + mfd->dest = DISPLAY_LCD; -static uint16_t mddi_init_registers(struct mddi_info *mddi) -{ - mddi_writel(0x0001, VERSION); - mddi_writel(MDDI_HOST_BYTES_PER_SUBFRAME, BPS); - mddi_writel(0x0003, SPM); /* subframes per media */ - mddi_writel(0x0005, TA1_LEN); - mddi_writel(MDDI_HOST_TA2_LEN, TA2_LEN); - mddi_writel(0x0096, DRIVE_HI); - /* 0x32 normal, 0x50 for Toshiba display */ - mddi_writel(0x0050, DRIVE_LO); - mddi_writel(0x003C, DISP_WAKE); /* wakeup counter */ - mddi_writel(MDDI_HOST_REV_RATE_DIV, REV_RATE_DIV); - - mddi_writel(MDDI_REV_BUFFER_SIZE, REV_SIZE); - mddi_writel(MDDI_MAX_REV_PKT_SIZE, REV_ENCAP_SZ); - - /* disable periodic rev encap */ - mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - - if (mddi_readl(PAD_CTL) == 0) { - /* If we are turning on band gap, need to wait 5us before - * turning on the rest of the PAD */ - mddi_writel(0x08000, PAD_CTL); - udelay(5); + /* + * alloc panel device data + */ + if (platform_device_add_data + (mdp_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + printk(KERN_ERR "mddi_probe: platform_device_add_data failed!\n"); + platform_device_put(mdp_dev); + return -ENOMEM; } + /* + * data chain + */ + pdata = mdp_dev->dev.platform_data; + pdata->on = mddi_on; + pdata->off = mddi_off; + pdata->next = pdev; + pdata->clk_func = pmdh_clk_func; + /* + * get/set panel specific fb info + */ + mfd->panel_info = pdata->panel_info; + + if (mfd->index == 0) + mfd->fb_imgType = MSMFB_DEFAULT_TYPE; + else + mfd->fb_imgType = MDP_RGB_565; + + clk_rate = mfd->panel_info.clk_max; + if (mddi_pdata && + mddi_pdata->mddi_sel_clk && + mddi_pdata->mddi_sel_clk(&clk_rate)) + printk(KERN_ERR + "%s: can't select mddi io clk targate rate = %d\n", + __func__, clk_rate); + + if (clk_set_max_rate(mddi_clk, clk_rate) < 0) + printk(KERN_ERR "%s: clk_set_max_rate failed\n", __func__); + mfd->panel_info.clk_rate = mfd->panel_info.clk_min; + + if (!mddi_client_type) + mddi_client_type = mfd->panel_info.lcd.rev; + else if (!mfd->panel_info.lcd.rev) + printk(KERN_ERR + "%s: mddi client is trying to revert back to type 1 !!!\n", + __func__); + + /* + * set driver data + */ + platform_set_drvdata(mdp_dev, mfd); + rc = pm_runtime_set_active(&pdev->dev); + if (rc < 0) + printk(KERN_ERR "pm_runtime: fail to set active\n"); + + rc = 0; + pm_runtime_enable(&pdev->dev); +#ifndef CONFIG_MSM_BUS_SCALING + mfd->ebi1_clk = clk_get(NULL, "ebi1_mddi_clk"); + if (IS_ERR(mfd->ebi1_clk)) + return PTR_ERR(mfd->ebi1_clk); + clk_set_rate(mfd->ebi1_clk, 65000000); +#endif + /* + * register in mdp driver + */ + rc = platform_device_add(mdp_dev); + if (rc) + goto mddi_probe_err; - /* Recommendation from PAD hw team */ - mddi_writel(0xa850f, PAD_CTL); - - - /* Need an even number for counts */ - mddi_writel(0x60006, DRIVER_START_CNT); + pdev_list[pdev_list_cnt++] = pdev; - mddi_set_auto_hibernate(&mddi->client_data, 0); +#ifdef CONFIG_HAS_EARLYSUSPEND + mfd->mddi_early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + mfd->mddi_early_suspend.suspend = mddi_early_suspend; + mfd->mddi_early_suspend.resume = mddi_early_resume; + register_early_suspend(&mfd->mddi_early_suspend); +#endif - mddi_writel(MDDI_CMD_DISP_IGNORE, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); + return 0; - mddi_init_rev_encap(mddi); - return mddi_readl(CORE_VER) & 0xffff; +mddi_probe_err: + platform_device_put(mdp_dev); + return rc; } -static void mddi_suspend(struct msm_mddi_client_data *cdata) -{ - struct mddi_info *mddi = container_of(cdata, struct mddi_info, - client_data); - /* turn off the client */ - if (mddi->power_client) - mddi->power_client(&mddi->client_data, 0); - /* turn off the link */ - mddi_writel(MDDI_CMD_RESET, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - /* turn off the clock */ - clk_disable(mddi->clk); -} +static int mddi_pad_ctrl; +static int mddi_power_locked; -static void mddi_resume(struct msm_mddi_client_data *cdata) +int mddi_client_power(unsigned int client_id) { - struct mddi_info *mddi = container_of(cdata, struct mddi_info, - client_data); - mddi_set_auto_hibernate(&mddi->client_data, 0); - /* turn on the client */ - if (mddi->power_client) - mddi->power_client(&mddi->client_data, 1); - /* turn on the clock */ - clk_enable(mddi->clk); - /* set up the local registers */ - mddi->rev_data_curr = 0; - mddi_init_registers(mddi); - mddi_writel(mddi->int_enable, INTEN); - mddi_writel(MDDI_CMD_LINK_ACTIVE, CMD); - mddi_writel(MDDI_CMD_SEND_RTD, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - mddi_set_auto_hibernate(&mddi->client_data, 1); + int ret = 0; + if (mddi_pdata && mddi_pdata->mddi_client_power) + ret = mddi_pdata->mddi_client_power(client_id); + return ret; } -static int __init mddi_get_client_caps(struct mddi_info *mddi) +void mddi_disable(int lock) { - int i, j; - - /* clear any stale interrupts */ - mddi_writel(0xffffffff, INT); - - mddi->int_enable = MDDI_INT_LINK_ACTIVE | - MDDI_INT_IN_HIBERNATION | - MDDI_INT_PRI_LINK_LIST_DONE | - MDDI_INT_REV_DATA_AVAIL | - MDDI_INT_REV_OVERFLOW | - MDDI_INT_REV_OVERWRITE | - MDDI_INT_RTD_FAILURE; - mddi_writel(mddi->int_enable, INTEN); - - mddi_writel(MDDI_CMD_LINK_ACTIVE, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - - for (j = 0; j < 3; j++) { - /* the toshiba vga panel does not respond to get - * caps unless you SEND_RTD, but the first SEND_RTD - * will fail... - */ - for (i = 0; i < 4; i++) { - uint32_t stat; - - mddi_writel(MDDI_CMD_SEND_RTD, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - stat = mddi_readl(STAT); - printk(KERN_INFO "mddi cmd send rtd: int %x, stat %x, " - "rtd val %x\n", mddi_readl(INT), stat, - mddi_readl(RTD_VAL)); - if ((stat & MDDI_STAT_RTD_MEAS_FAIL) == 0) - break; - msleep(1); - } + mddi_host_type host_idx = MDDI_HOST_PRIM; - mddi_writel(CMD_GET_CLIENT_CAP, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - wait_event_timeout(mddi->int_wait, mddi->flags & FLAG_HAVE_CAPS, - HZ / 100); + if (mddi_power_locked) + return; - if (mddi->flags & FLAG_HAVE_CAPS) - break; - printk(KERN_INFO "mddi_init, timeout waiting for caps\n"); - } - return mddi->flags & FLAG_HAVE_CAPS; -} + if (lock) + mddi_power_locked = 1; + pmdh_clk_enable(); -/* link must be active when this is called */ -int mddi_check_status(struct mddi_info *mddi) -{ - int ret = -1, retry = 3; - mutex_lock(&mddi->reg_read_lock); - mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP | 1, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - - do { - mddi->flags &= ~FLAG_HAVE_STATUS; - mddi_writel(CMD_GET_CLIENT_STATUS, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - wait_event_timeout(mddi->int_wait, - mddi->flags & FLAG_HAVE_STATUS, - HZ / 100); - - if (mddi->flags & FLAG_HAVE_STATUS) { - if (mddi->status.crc_error_count) - printk(KERN_INFO "mddi status: crc_error " - "count: %d\n", - mddi->status.crc_error_count); - else - ret = 0; - break; - } else - printk(KERN_INFO "mddi status: failed to get client " - "status\n"); - mddi_writel(MDDI_CMD_SEND_RTD, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - } while (--retry); - - mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP | 0, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - mutex_unlock(&mddi->reg_read_lock); - return ret; + mddi_pad_ctrl = mddi_host_reg_in(PAD_CTL); + mddi_host_reg_out(PAD_CTL, 0x0); + + if (clk_set_min_rate(mddi_clk, 0) < 0) + printk(KERN_ERR "%s: clk_set_min_rate failed\n", __func__); + + pmdh_clk_disable(); + + if (mddi_pdata && mddi_pdata->mddi_power_save) + mddi_pdata->mddi_power_save(0); } +#ifdef CONFIG_PM +static int mddi_is_in_suspend; -void mddi_remote_write(struct msm_mddi_client_data *cdata, uint32_t val, - uint32_t reg) +static int mddi_suspend(struct platform_device *pdev, pm_message_t state) { - struct mddi_info *mddi = container_of(cdata, struct mddi_info, - client_data); - struct mddi_llentry *ll; - struct mddi_register_access *ra; + mddi_host_type host_idx = MDDI_HOST_PRIM; + if (mddi_is_in_suspend) + return 0; - mutex_lock(&mddi->reg_write_lock); + mddi_is_in_suspend = 1; - ll = mddi->reg_write_data; + if (mddi_power_locked) + return 0; - ra = &(ll->u.r); - ra->length = 14 + 4; - ra->type = TYPE_REGISTER_ACCESS; - ra->client_id = 0; - ra->read_write_info = MDDI_WRITE | 1; - ra->crc16 = 0; + pmdh_clk_enable(); - ra->register_address = reg; - ra->register_data_list = val; + mddi_pad_ctrl = mddi_host_reg_in(PAD_CTL); + mddi_host_reg_out(PAD_CTL, 0x0); - ll->flags = 1; - ll->header_count = 14; - ll->data_count = 4; - ll->data = mddi->reg_write_addr + offsetof(struct mddi_llentry, - u.r.register_data_list); - ll->next = 0; - ll->reserved = 0; + if (clk_set_min_rate(mddi_clk, 0) < 0) + printk(KERN_ERR "%s: clk_set_min_rate failed\n", __func__); - mddi_writel(mddi->reg_write_addr, PRI_PTR); + pmdh_clk_disable(); - mddi_wait_interrupt(mddi, MDDI_INT_PRI_LINK_LIST_DONE); - mutex_unlock(&mddi->reg_write_lock); + return 0; } -uint32_t mddi_remote_read(struct msm_mddi_client_data *cdata, uint32_t reg) +static int mddi_resume(struct platform_device *pdev) { - struct mddi_info *mddi = container_of(cdata, struct mddi_info, - client_data); - struct mddi_llentry *ll; - struct mddi_register_access *ra; - struct reg_read_info ri; - unsigned s; - int retry_count = 2; - unsigned long irq_flags; - - mutex_lock(&mddi->reg_read_lock); - - ll = mddi->reg_read_data; - - ra = &(ll->u.r); - ra->length = 14; - ra->type = TYPE_REGISTER_ACCESS; - ra->client_id = 0; - ra->read_write_info = MDDI_READ | 1; - ra->crc16 = 0; - - ra->register_address = reg; - - ll->flags = 0x11; - ll->header_count = 14; - ll->data_count = 0; - ll->data = 0; - ll->next = 0; - ll->reserved = 0; - - s = mddi_readl(STAT); - - ri.reg = reg; - ri.status = -1; - - do { - init_completion(&ri.done); - mddi->reg_read = &ri; - mddi_writel(mddi->reg_read_addr, PRI_PTR); - - mddi_wait_interrupt(mddi, MDDI_INT_PRI_LINK_LIST_DONE); - - /* Enable Periodic Reverse Encapsulation. */ - mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP | 1, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - if (wait_for_completion_timeout(&ri.done, HZ/10) == 0 && - !ri.done.done) { - printk(KERN_INFO "mddi_remote_read(%x) timeout " - "(%d %d %d)\n", - reg, ri.status, ri.result, ri.done.done); - spin_lock_irqsave(&mddi->int_lock, irq_flags); - mddi->reg_read = NULL; - spin_unlock_irqrestore(&mddi->int_lock, irq_flags); - ri.status = -1; - ri.result = -1; - } - if (ri.status == 0) - break; + mddi_host_type host_idx = MDDI_HOST_PRIM; - mddi_writel(MDDI_CMD_SEND_RTD, CMD); - mddi_writel(MDDI_CMD_LINK_ACTIVE, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - printk(KERN_INFO "mddi_remote_read: failed, sent " - "MDDI_CMD_SEND_RTD: int %x, stat %x, rtd val %x " - "curr_rev_ptr %x\n", mddi_readl(INT), mddi_readl(STAT), - mddi_readl(RTD_VAL), mddi_readl(CURR_REV_PTR)); - } while (retry_count-- > 0); - /* Disable Periodic Reverse Encapsulation. */ - mddi_writel(MDDI_CMD_PERIODIC_REV_ENCAP | 0, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - mddi->reg_read = NULL; - mutex_unlock(&mddi->reg_read_lock); - return ri.result; -} + if (!mddi_is_in_suspend) + return 0; -static struct mddi_info mddi_info[2]; + mddi_is_in_suspend = 0; -static int __init mddi_clk_setup(struct platform_device *pdev, - struct mddi_info *mddi, - unsigned long clk_rate) -{ - int ret; + if (mddi_power_locked) + return 0; - /* set up the clocks */ - mddi->clk = clk_get(&pdev->dev, "mddi_clk"); - if (IS_ERR(mddi->clk)) { - printk(KERN_INFO "mddi: failed to get clock\n"); - return PTR_ERR(mddi->clk); - } - ret = clk_enable(mddi->clk); - if (ret) - goto fail; - ret = clk_set_rate(mddi->clk, clk_rate); - if (ret) - goto fail; - return 0; + pmdh_clk_enable(); -fail: - clk_put(mddi->clk); - return ret; + mddi_host_reg_out(PAD_CTL, mddi_pad_ctrl); + + + return 0; } +#endif -static int __init mddi_rev_data_setup(struct mddi_info *mddi) +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mddi_early_suspend(struct early_suspend *h) { - void *dma; - dma_addr_t dma_addr; + pm_message_t state; + struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, + mddi_early_suspend); - /* set up dma buffer */ - dma = dma_alloc_coherent(NULL, 0x1000, &dma_addr, GFP_KERNEL); - if (dma == 0) - return -ENOMEM; - mddi->rev_data = dma; - mddi->rev_data_curr = 0; - mddi->rev_addr = dma_addr; - mddi->reg_write_data = dma + MDDI_REV_BUFFER_SIZE; - mddi->reg_write_addr = dma_addr + MDDI_REV_BUFFER_SIZE; - mddi->reg_read_data = mddi->reg_write_data + 1; - mddi->reg_read_addr = mddi->reg_write_addr + - sizeof(*mddi->reg_write_data); - return 0; + state.event = PM_EVENT_SUSPEND; + mddi_suspend(mfd->pdev, state); } -static int __devinit mddi_probe(struct platform_device *pdev) +static void mddi_early_resume(struct early_suspend *h) { - struct msm_mddi_platform_data *pdata = pdev->dev.platform_data; - struct mddi_info *mddi = &mddi_info[pdev->id]; - struct resource *resource; - int ret, i; + struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, + mddi_early_suspend); + mddi_resume(mfd->pdev); +} +#endif - resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!resource) { - printk(KERN_ERR "mddi: no associated mem resource!\n"); - return -ENOMEM; - } - mddi->base = ioremap(resource->start, resource_size(resource)); - if (!mddi->base) { - printk(KERN_ERR "mddi: failed to remap base!\n"); - ret = -EINVAL; - goto error_ioremap; - } - resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!resource) { - printk(KERN_ERR "mddi: no associated irq resource!\n"); - ret = -EINVAL; - goto error_get_irq_resource; +static int mddi_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (mddi_host_timer.function) { + mutex_lock(&mddi_timer_lock); + mddi_timer_shutdown_flag = 1; + mutex_unlock(&mddi_timer_lock); + del_timer_sync(&mddi_host_timer); + mutex_lock(&mddi_timer_lock); + mddi_timer_shutdown_flag = 0; + mutex_unlock(&mddi_timer_lock); } - mddi->irq = resource->start; - printk(KERN_INFO "mddi: init() base=0x%p irq=%d\n", mddi->base, - mddi->irq); - mddi->power_client = pdata->power_client; - mutex_init(&mddi->reg_write_lock); - mutex_init(&mddi->reg_read_lock); - spin_lock_init(&mddi->int_lock); - init_waitqueue_head(&mddi->int_wait); + iounmap(msm_pmdh_base); - ret = mddi_clk_setup(pdev, mddi, pdata->clk_rate); - if (ret) { - printk(KERN_ERR "mddi: failed to setup clock!\n"); - goto error_clk_setup; - } + return 0; +} - ret = mddi_rev_data_setup(mddi); - if (ret) { - printk(KERN_ERR "mddi: failed to setup rev data!\n"); - goto error_rev_data; - } +static int mddi_register_driver(void) +{ + return platform_driver_register(&mddi_driver); +} - mddi->int_enable = 0; - mddi_writel(mddi->int_enable, INTEN); - ret = request_irq(mddi->irq, mddi_isr, IRQF_DISABLED, "mddi", - &mddi->client_data); - if (ret) { - printk(KERN_ERR "mddi: failed to request enable irq!\n"); - goto error_request_irq; - } +static int __init mddi_driver_init(void) +{ + int ret; + pmdh_clk_status = 0; - /* turn on the mddi client bridge chip */ - if (mddi->power_client) - mddi->power_client(&mddi->client_data, 1); - - /* initialize the mddi registers */ - mddi_set_auto_hibernate(&mddi->client_data, 0); - mddi_writel(MDDI_CMD_RESET, CMD); - mddi_wait_interrupt(mddi, MDDI_INT_NO_CMD_PKTS_PEND); - mddi->version = mddi_init_registers(mddi); - if (mddi->version < 0x20) { - printk(KERN_ERR "mddi: unsupported version 0x%x\n", - mddi->version); - ret = -ENODEV; - goto error_mddi_version; + mddi_clk = clk_get(NULL, "mddi_clk"); + if (IS_ERR(mddi_clk)) { + printk(KERN_ERR "can't find mddi_clk\n"); + return PTR_ERR(mddi_clk); } + ret = clk_set_min_rate(mddi_clk, 49000000); + if (ret) + printk(KERN_ERR "Can't set mddi_clk min rate to 49000000\n"); - /* read the capabilities off the client */ - if (!mddi_get_client_caps(mddi)) { - printk(KERN_INFO "mddi: no client found\n"); - /* power down the panel */ - mddi_writel(MDDI_CMD_POWERDOWN, CMD); - printk(KERN_INFO "mddi powerdown: stat %x\n", mddi_readl(STAT)); - msleep(100); - printk(KERN_INFO "mddi powerdown: stat %x\n", mddi_readl(STAT)); - return 0; - } - mddi_set_auto_hibernate(&mddi->client_data, 1); - - if (mddi->caps.Mfr_Name == 0 && mddi->caps.Product_Code == 0) - pdata->fixup(&mddi->caps.Mfr_Name, &mddi->caps.Product_Code); - - mddi->client_pdev.id = 0; - for (i = 0; i < pdata->num_clients; i++) { - if (pdata->client_platform_data[i].product_id == - (mddi->caps.Mfr_Name << 16 | mddi->caps.Product_Code)) { - mddi->client_data.private_client_data = - pdata->client_platform_data[i].client_data; - mddi->client_pdev.name = - pdata->client_platform_data[i].name; - mddi->client_pdev.id = - pdata->client_platform_data[i].id; - /* XXX: possibly set clock */ - break; - } - } + printk(KERN_INFO "mddi_clk init rate is %lu\n", + clk_get_rate(mddi_clk)); + mddi_pclk = clk_get(NULL, "mddi_pclk"); + if (IS_ERR(mddi_pclk)) + mddi_pclk = NULL; + pmdh_clk_enable(); - if (i >= pdata->num_clients) - mddi->client_pdev.name = "mddi_c_dummy"; - printk(KERN_INFO "mddi: registering panel %s\n", - mddi->client_pdev.name); - - mddi->client_data.suspend = mddi_suspend; - mddi->client_data.resume = mddi_resume; - mddi->client_data.activate_link = mddi_activate_link; - mddi->client_data.remote_write = mddi_remote_write; - mddi->client_data.remote_read = mddi_remote_read; - mddi->client_data.auto_hibernate = mddi_set_auto_hibernate; - mddi->client_data.fb_resource = pdata->fb_resource; - if (pdev->id == 0) - mddi->client_data.interface_type = MSM_MDDI_PMDH_INTERFACE; - else if (pdev->id == 1) - mddi->client_data.interface_type = MSM_MDDI_EMDH_INTERFACE; - else { - printk(KERN_ERR "mddi: can not determine interface %d!\n", - pdev->id); - ret = -EINVAL; - goto error_mddi_interface; + ret = mddi_register_driver(); + if (ret) { + pmdh_clk_disable(); + clk_put(mddi_clk); + if (mddi_pclk) + clk_put(mddi_pclk); + printk(KERN_ERR "mddi_register_driver() failed!\n"); + return ret; } - mddi->client_pdev.dev.platform_data = &mddi->client_data; - printk(KERN_INFO "mddi: publish: %s\n", mddi->client_name); - platform_device_register(&mddi->client_pdev); - return 0; + mddi_init(); -error_mddi_interface: -error_mddi_version: - free_irq(mddi->irq, 0); -error_request_irq: - dma_free_coherent(NULL, 0x1000, mddi->rev_data, mddi->rev_addr); -error_rev_data: -error_clk_setup: -error_get_irq_resource: - iounmap(mddi->base); -error_ioremap: - - printk(KERN_INFO "mddi: mddi_init() failed (%d)\n", ret); return ret; } - -static struct platform_driver mddi_driver = { - .probe = mddi_probe, - .driver = { .name = "msm_mddi" }, -}; - -static int __init _mddi_init(void) -{ - return platform_driver_register(&mddi_driver); -} - -module_init(_mddi_init); +module_init(mddi_driver_init); diff --git a/drivers/video/msm/mddi_client_dummy.c b/drivers/video/msm/mddi_client_dummy.c index d2a091cebe2..ebbae87885b 100644 --- a/drivers/video/msm/mddi_client_dummy.c +++ b/drivers/video/msm/mddi_client_dummy.c @@ -15,7 +15,6 @@ * GNU General Public License for more details. */ -#include #include #include #include diff --git a/drivers/video/msm/mddi_client_nt35399.c b/drivers/video/msm/mddi_client_nt35399.c index f239f4a25e0..c9e9349451c 100644 --- a/drivers/video/msm/mddi_client_nt35399.c +++ b/drivers/video/msm/mddi_client_nt35399.c @@ -21,7 +21,6 @@ #include #include #include -#include #include static DECLARE_WAIT_QUEUE_HEAD(nt35399_vsync_wait); diff --git a/drivers/video/msm/mddi_client_toshiba.c b/drivers/video/msm/mddi_client_toshiba.c index f9bc932ac46..88687810909 100644 --- a/drivers/video/msm/mddi_client_toshiba.c +++ b/drivers/video/msm/mddi_client_toshiba.c @@ -21,7 +21,6 @@ #include #include #include -#include #include @@ -60,6 +59,7 @@ struct panel_info { struct msm_panel_data panel_data; struct msmfb_callback *toshiba_callback; int toshiba_got_int; + int irq; }; @@ -175,47 +175,6 @@ irqreturn_t toshiba_vsync_interrupt(int irq, void *data) return IRQ_HANDLED; } -static int setup_vsync(struct panel_info *panel, - int init) -{ - int ret; - int gpio = 97; - unsigned int irq; - - if (!init) { - ret = 0; - goto uninit; - } - ret = gpio_request(gpio, "vsync"); - if (ret) - goto err_request_gpio_failed; - - ret = gpio_direction_input(gpio); - if (ret) - goto err_gpio_direction_input_failed; - - ret = irq = gpio_to_irq(gpio); - if (ret < 0) - goto err_get_irq_num_failed; - - ret = request_irq(irq, toshiba_vsync_interrupt, IRQF_TRIGGER_RISING, - "vsync", panel); - if (ret) - goto err_request_irq_failed; - printk(KERN_INFO "vsync on gpio %d now %d\n", - gpio, gpio_get_value(gpio)); - return 0; - -uninit: - free_irq(gpio_to_irq(gpio), panel); -err_request_irq_failed: -err_get_irq_num_failed: -err_gpio_direction_input_failed: - gpio_free(gpio); -err_request_gpio_failed: - return ret; -} - static int mddi_toshiba_probe(struct platform_device *pdev) { int ret; @@ -232,10 +191,16 @@ static int mddi_toshiba_probe(struct platform_device *pdev) client_data->remote_write(client_data, GPIOSEL_VWAKEINT, GPIOSEL); client_data->remote_write(client_data, INTMASK_VWAKEOUT, INTMASK); - ret = setup_vsync(panel, 1); + ret = platform_get_irq_byname(pdev, "vsync"); + if (ret < 0) + goto err_plat_get_irq; + + panel->irq = ret; + ret = request_irq(panel->irq, toshiba_vsync_interrupt, + IRQF_TRIGGER_RISING, "vsync", panel); if (ret) { dev_err(&pdev->dev, "mddi_bridge_setup_vsync failed\n"); - return ret; + goto err_req_irq; } panel->client_data = client_data; @@ -258,13 +223,19 @@ static int mddi_toshiba_probe(struct platform_device *pdev) platform_device_register(&panel->pdev); return 0; + +err_req_irq: +err_plat_get_irq: + kfree(panel); + return ret; } static int mddi_toshiba_remove(struct platform_device *pdev) { struct panel_info *panel = platform_get_drvdata(pdev); - setup_vsync(panel, 0); + platform_set_drvdata(pdev, NULL); + free_irq(panel->irq, panel); kfree(panel); return 0; } diff --git a/drivers/video/msm/mddi_ext.c b/drivers/video/msm/mddi_ext.c new file mode 100644 index 00000000000..0ecd593c43a --- /dev/null +++ b/drivers/video/msm/mddi_ext.c @@ -0,0 +1,363 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb.h" +#include "mddihosti.h" + +static int mddi_ext_probe(struct platform_device *pdev); +static int mddi_ext_remove(struct platform_device *pdev); + +static int mddi_ext_off(struct platform_device *pdev); +static int mddi_ext_on(struct platform_device *pdev); + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; + +static int mddi_ext_suspend(struct platform_device *pdev, pm_message_t state); +static int mddi_ext_resume(struct platform_device *pdev); + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mddi_ext_early_suspend(struct early_suspend *h); +static void mddi_ext_early_resume(struct early_suspend *h); +#endif + +static int mddi_ext_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int mddi_ext_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static int mddi_ext_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: idling...\n"); + return 0; +} +static struct dev_pm_ops mddi_ext_dev_pm_ops = { + .runtime_suspend = mddi_ext_runtime_suspend, + .runtime_resume = mddi_ext_runtime_resume, + .runtime_idle = mddi_ext_runtime_idle, +}; + +static struct platform_driver mddi_ext_driver = { + .probe = mddi_ext_probe, + .remove = mddi_ext_remove, +#ifndef CONFIG_HAS_EARLYSUSPEND +#ifdef CONFIG_PM + .suspend = mddi_ext_suspend, + .resume = mddi_ext_resume, +#endif +#endif + .resume_early = NULL, + .resume = NULL, + .shutdown = NULL, + .driver = { + .name = "mddi_ext", + .pm = &mddi_ext_dev_pm_ops, + }, +}; + +static struct clk *mddi_ext_clk; +static struct clk *mddi_ext_pclk; +static struct mddi_platform_data *mddi_ext_pdata; + +extern int int_mddi_ext_flag; + +static int mddi_ext_off(struct platform_device *pdev) +{ + int ret = 0; + + ret = panel_next_off(pdev); + mddi_host_stop_ext_display(); + pm_runtime_put(&pdev->dev); + return ret; +} + +static int mddi_ext_on(struct platform_device *pdev) +{ + int ret = 0; + u32 clk_rate; + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + pm_runtime_get(&pdev->dev); + clk_rate = mfd->fbi->var.pixclock; + clk_rate = min(clk_rate, mfd->panel_info.clk_max); + + if (mddi_ext_pdata && + mddi_ext_pdata->mddi_sel_clk && + mddi_ext_pdata->mddi_sel_clk(&clk_rate)) + printk(KERN_ERR + "%s: can't select mddi io clk targate rate = %d\n", + __func__, clk_rate); + + if (clk_set_min_rate(mddi_ext_clk, clk_rate) < 0) + printk(KERN_ERR "%s: clk_set_min_rate failed\n", + __func__); + + mddi_host_start_ext_display(); + ret = panel_next_on(pdev); + + return ret; +} + +static int mddi_ext_resource_initialized; + +static int mddi_ext_probe(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct platform_device *mdp_dev = NULL; + struct msm_fb_panel_data *pdata = NULL; + int rc; + resource_size_t size ; + u32 clk_rate; + + if ((pdev->id == 0) && (pdev->num_resources >= 0)) { + mddi_ext_pdata = pdev->dev.platform_data; + + size = resource_size(&pdev->resource[0]); + msm_emdh_base = ioremap(pdev->resource[0].start, size); + + MSM_FB_INFO("external mddi base address = 0x%x\n", + pdev->resource[0].start); + + if (unlikely(!msm_emdh_base)) + return -ENOMEM; + + mddi_ext_resource_initialized = 1; + return 0; + } + + if (!mddi_ext_resource_initialized) + return -EPERM; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; + + mdp_dev = platform_device_alloc("mdp", pdev->id); + if (!mdp_dev) + return -ENOMEM; + + /* + * link to the latest pdev + */ + mfd->pdev = mdp_dev; + mfd->dest = DISPLAY_EXT_MDDI; + + /* + * alloc panel device data + */ + if (platform_device_add_data + (mdp_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + printk(KERN_ERR "mddi_ext_probe: platform_device_add_data failed!\n"); + platform_device_put(mdp_dev); + return -ENOMEM; + } + /* + * data chain + */ + pdata = mdp_dev->dev.platform_data; + pdata->on = mddi_ext_on; + pdata->off = mddi_ext_off; + pdata->next = pdev; + + /* + * get/set panel specific fb info + */ + mfd->panel_info = pdata->panel_info; + mfd->fb_imgType = MDP_RGB_565; + + clk_rate = mfd->panel_info.clk_max; + if (mddi_ext_pdata && + mddi_ext_pdata->mddi_sel_clk && + mddi_ext_pdata->mddi_sel_clk(&clk_rate)) + printk(KERN_ERR + "%s: can't select mddi io clk targate rate = %d\n", + __func__, clk_rate); + + if (clk_set_max_rate(mddi_ext_clk, clk_rate) < 0) + printk(KERN_ERR "%s: clk_set_max_rate failed\n", __func__); + mfd->panel_info.clk_rate = mfd->panel_info.clk_min; + + /* + * set driver data + */ + platform_set_drvdata(mdp_dev, mfd); + rc = pm_runtime_set_active(&pdev->dev); + if (rc < 0) + printk(KERN_ERR "pm_runtime: fail to set active\n"); + + rc = 0; + pm_runtime_enable(&pdev->dev); + /* + * register in mdp driver + */ + rc = platform_device_add(mdp_dev); + if (rc) + goto mddi_ext_probe_err; + + pdev_list[pdev_list_cnt++] = pdev; + +#ifdef CONFIG_HAS_EARLYSUSPEND + mfd->mddi_ext_early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + mfd->mddi_ext_early_suspend.suspend = mddi_ext_early_suspend; + mfd->mddi_ext_early_suspend.resume = mddi_ext_early_resume; + register_early_suspend(&mfd->mddi_ext_early_suspend); +#endif + + return 0; + +mddi_ext_probe_err: + platform_device_put(mdp_dev); + return rc; +} + +static int mddi_ext_is_in_suspend; + +static int mddi_ext_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (mddi_ext_is_in_suspend) + return 0; + + mddi_ext_is_in_suspend = 1; + + if (clk_set_min_rate(mddi_ext_clk, 0) < 0) + printk(KERN_ERR "%s: clk_set_min_rate failed\n", __func__); + + clk_disable(mddi_ext_clk); + if (mddi_ext_pclk) + clk_disable(mddi_ext_pclk); + + disable_irq(INT_MDDI_EXT); + + return 0; +} + +static int mddi_ext_resume(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + + if (!mddi_ext_is_in_suspend) + return 0; + + mddi_ext_is_in_suspend = 0; + enable_irq(INT_MDDI_EXT); + + clk_enable(mddi_ext_clk); + if (mddi_ext_pclk) + clk_enable(mddi_ext_pclk); + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mddi_ext_early_suspend(struct early_suspend *h) +{ + pm_message_t state; + struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, + mddi_ext_early_suspend); + + state.event = PM_EVENT_SUSPEND; + mddi_ext_suspend(mfd->pdev, state); +} + +static void mddi_ext_early_resume(struct early_suspend *h) +{ + struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, + mddi_ext_early_suspend); + mddi_ext_resume(mfd->pdev); +} +#endif + +static int mddi_ext_remove(struct platform_device *pdev) +{ + pm_runtim_disable(&pdev->dev); + iounmap(msm_emdh_base); + return 0; +} + +static int mddi_ext_register_driver(void) +{ + return platform_driver_register(&mddi_ext_driver); +} + +static int __init mddi_ext_driver_init(void) +{ + int ret; + + mddi_ext_clk = clk_get(NULL, "emdh_clk"); + if (IS_ERR(mddi_ext_clk)) { + printk(KERN_ERR "can't find emdh_clk\n"); + return PTR_ERR(mddi_ext_clk); + } + clk_enable(mddi_ext_clk); + + mddi_ext_pclk = clk_get(NULL, "emdh_pclk"); + if (IS_ERR(mddi_ext_pclk)) + mddi_ext_pclk = NULL; + else + clk_enable(mddi_ext_pclk); + + ret = mddi_ext_register_driver(); + if (ret) { + clk_disable(mddi_ext_clk); + clk_put(mddi_ext_clk); + if (mddi_ext_pclk) { + clk_disable(mddi_ext_pclk); + clk_put(mddi_ext_pclk); + } + printk(KERN_ERR "mddi_ext_register_driver() failed!\n"); + return ret; + } + mddi_init(); + + return ret; +} + +module_init(mddi_ext_driver_init); diff --git a/drivers/video/msm/mddi_ext_lcd.c b/drivers/video/msm/mddi_ext_lcd.c new file mode 100644 index 00000000000..da79513289f --- /dev/null +++ b/drivers/video/msm/mddi_ext_lcd.c @@ -0,0 +1,90 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" + +static int mddi_ext_lcd_on(struct platform_device *pdev); +static int mddi_ext_lcd_off(struct platform_device *pdev); + +static int mddi_ext_lcd_on(struct platform_device *pdev) +{ + return 0; +} + +static int mddi_ext_lcd_off(struct platform_device *pdev) +{ + return 0; +} + +static int __init mddi_ext_lcd_probe(struct platform_device *pdev) +{ + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = mddi_ext_lcd_probe, + .driver = { + .name = "extmddi_svga", + }, +}; + +static struct msm_fb_panel_data mddi_ext_lcd_panel_data = { + .panel_info.xres = 800, + .panel_info.yres = 600, + .panel_info.mode2_xres = 0; + .panel_info.mode2_yres = 0; + .panel_info.mode2_bpp = 0; + .panel_info.type = EXT_MDDI_PANEL, + .panel_info.pdest = DISPLAY_1, + .panel_info.wait_cycle = 0, + .panel_info.bpp = 18, + .panel_info.fb_num = 2, + .panel_info.clk_rate = 122880000, + .panel_info.clk_min = 120000000, + .panel_info.clk_max = 125000000, + .on = mddi_ext_lcd_on, + .off = mddi_ext_lcd_off, +}; + +static struct platform_device this_device = { + .name = "extmddi_svga", + .id = 0, + .dev = { + .platform_data = &mddi_ext_lcd_panel_data, + } +}; + +static int __init mddi_ext_lcd_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + + ret = platform_driver_register(&this_driver); + if (!ret) { + pinfo = &mddi_ext_lcd_panel_data.panel_info; + pinfo->lcd.vsync_enable = FALSE; + pinfo->mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; + + ret = platform_device_register(&this_device); + if (ret) + platform_driver_unregister(&this_driver); + } + + return ret; +} + +module_init(mddi_ext_lcd_init); diff --git a/drivers/video/msm/mddi_hw.h b/drivers/video/msm/mddi_hw.h index 45cc01fc1e7..47bb4494c5f 100644 --- a/drivers/video/msm/mddi_hw.h +++ b/drivers/video/msm/mddi_hw.h @@ -53,6 +53,9 @@ #define MDDI_MF_CNT 0x0084 #define MDDI_CURR_REV_PTR 0x0088 #define MDDI_CORE_VER 0x008c +#define MDDI_FIFO_ALLOC 0x0090 +#define MDDI_PAD_IO_CTL 0x00a0 +#define MDDI_PAD_CAL 0x00a4 #define MDDI_INT_PRI_PTR_READ 0x0001 #define MDDI_INT_SEC_PTR_READ 0x0002 @@ -125,8 +128,14 @@ /* MDP sends 256 pixel packets, so lower value hibernates more without * significantly increasing latency of waiting for next subframe */ #define MDDI_HOST_BYTES_PER_SUBFRAME 0x3C00 + +#if defined(CONFIG_MSM_MDP31) || defined(CONFIG_MSM_MDP40) +#define MDDI_HOST_TA2_LEN 0x001a +#define MDDI_HOST_REV_RATE_DIV 0x0004 +#else #define MDDI_HOST_TA2_LEN 0x000c #define MDDI_HOST_REV_RATE_DIV 0x0002 +#endif struct __attribute__((packed)) mddi_rev_packet { diff --git a/drivers/video/msm/mddi_orise.c b/drivers/video/msm/mddi_orise.c new file mode 100644 index 00000000000..dc913e6b8fa --- /dev/null +++ b/drivers/video/msm/mddi_orise.c @@ -0,0 +1,127 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" + +#define MDDI_ORISE_1_2 1 +#define write_client_reg(__X, __Y, __Z) {\ + mddi_queue_register_write(__X, __Y, TRUE, 0);\ +} + +static int mddi_orise_lcd_on(struct platform_device *pdev); +static int mddi_orise_lcd_off(struct platform_device *pdev); +static int __init mddi_orise_probe(struct platform_device *pdev); +static int __init mddi_orise_init(void); + +/* function used to turn on the display */ +static void mddi_orise_prim_lcd_init(void) +{ + write_client_reg(0x00110000, 0, TRUE); + mddi_wait(150); + write_client_reg(0x00290000, 0, TRUE); +} + +static struct platform_driver this_driver = { + .driver = { + .name = "mddi_orise", + }, +}; + +static struct msm_fb_panel_data mddi_orise_panel_data = { + .on = mddi_orise_lcd_on, + .off = mddi_orise_lcd_off, +}; + +static struct platform_device this_device = { + .name = "mddi_orise", + .id = MDDI_ORISE_1_2, + .dev = { + .platform_data = &mddi_orise_panel_data, + } +}; + +static int mddi_orise_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + mfd = platform_get_drvdata(pdev); + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mddi_orise_prim_lcd_init(); + + return 0; +} + +static int mddi_orise_lcd_off(struct platform_device *pdev) +{ + return 0; +} + +static int __init mddi_orise_probe(struct platform_device *pdev) +{ + msm_fb_add_device(pdev); + return 0; +} + +static int __init mddi_orise_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + u32 id; + ret = msm_fb_detect_client("mddi_orise"); + if (ret == -ENODEV) + return 0; + + if (ret) { + id = mddi_get_client_id(); + if (((id >> 16) != 0xbe8d) || ((id & 0xffff) != 0x8031)) + return 0; + } +#endif + ret = platform_driver_probe(&this_driver, mddi_orise_probe); + if (!ret) { + pinfo = &mddi_orise_panel_data.panel_info; + pinfo->xres = 480; + pinfo->yres = 800; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = MDDI_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->fb_num = 2; + pinfo->clk_rate = 192000000; + pinfo->clk_min = 192000000; + pinfo->clk_max = 192000000; + pinfo->lcd.rev = 2; + pinfo->lcd.vsync_enable = FALSE; + pinfo->lcd.refx100 = 6050; + pinfo->lcd.v_back_porch = 2; + pinfo->lcd.v_front_porch = 2; + pinfo->lcd.v_pulse_width = 105; + pinfo->lcd.hw_vsync_mode = TRUE; + pinfo->lcd.vsync_notifier_period = 0; + + ret = platform_device_register(&this_device); + if (ret) + platform_driver_unregister(&this_driver); + } + return ret; +} +module_init(mddi_orise_init); diff --git a/drivers/video/msm/mddi_prism.c b/drivers/video/msm/mddi_prism.c new file mode 100644 index 00000000000..5269b228cdc --- /dev/null +++ b/drivers/video/msm/mddi_prism.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" + +static int prism_lcd_on(struct platform_device *pdev); +static int prism_lcd_off(struct platform_device *pdev); + +static int prism_lcd_on(struct platform_device *pdev) +{ + /* Set the MDP pixel data attributes for Primary Display */ + mddi_host_write_pix_attr_reg(0x00C3); + + return 0; +} + +static int prism_lcd_off(struct platform_device *pdev) +{ + return 0; +} + +static int __devinit prism_probe(struct platform_device *pdev) +{ + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = prism_probe, + .driver = { + .name = "mddi_prism_wvga", + }, +}; + +static struct msm_fb_panel_data prism_panel_data = { + .on = prism_lcd_on, + .off = prism_lcd_off, +}; + +static struct platform_device this_device = { + .name = "mddi_prism_wvga", + .id = 0, + .dev = { + .platform_data = &prism_panel_data, + } +}; + +static int __init prism_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + u32 id; + + ret = msm_fb_detect_client("mddi_prism_wvga"); + if (ret == -ENODEV) + return 0; + + if (ret) { + id = mddi_get_client_id(); + + if (((id >> 16) != 0x4474) || ((id & 0xffff) == 0x8960)) + return 0; + } +#endif + ret = platform_driver_register(&this_driver); + if (!ret) { + pinfo = &prism_panel_data.panel_info; + pinfo->xres = 800; + pinfo->yres = 480; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = MDDI_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->fb_num = 2; + pinfo->clk_rate = 153600000; + pinfo->clk_min = 140000000; + pinfo->clk_max = 160000000; + pinfo->lcd.vsync_enable = TRUE; + pinfo->lcd.refx100 = 6050; + pinfo->lcd.v_back_porch = 23; + pinfo->lcd.v_front_porch = 20; + pinfo->lcd.v_pulse_width = 105; + pinfo->lcd.hw_vsync_mode = TRUE; + pinfo->lcd.vsync_notifier_period = 0; + + ret = platform_device_register(&this_device); + if (ret) + platform_driver_unregister(&this_driver); + } + + return ret; +} + +module_init(prism_init); diff --git a/drivers/video/msm/mddi_quickvx.c b/drivers/video/msm/mddi_quickvx.c new file mode 100644 index 00000000000..330e6796f5e --- /dev/null +++ b/drivers/video/msm/mddi_quickvx.c @@ -0,0 +1,718 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" + +/* WVGA Primary Display */ +#define MDDI_QUICKVX_1_2 1 +/* MDDI Manufacturer Code */ +#define QUICKVX_MDDI_MFR_CODE 0xc583 +/* MDDI Product Code */ +#define QUICKVX_MDDI_PRD_CODE 0x5800 + +/* Register Address Maps */ +/* MDDI Address Anti-fuse values for bits [31:22] */ +#define QUICKVX_ADDR_31_22_AF (0X000 << 22) + +/* MDDI Address Maps */ +/* VEE Block Address Base */ +#define QUICKVX_VEE_BASE (QUICKVX_ADDR_31_22_AF | 0x00000000) +/* SPI Block Address Base */ +#define QUICKVX_SPI_BASE (QUICKVX_ADDR_31_22_AF | 0x00010000) +/* Clock and Reset (CAR) Address Base */ +#define QUICKVX_CAR_BASE (QUICKVX_ADDR_31_22_AF | 0x00020000) +/* Register Control Block (RCB) Address Base */ +#define QUICKVX_RCB_BASE (QUICKVX_ADDR_31_22_AF | 0x00030000) +/* Cellular RAM Address Base */ +#define QUICKVX_CELLRAM_BASE (QUICKVX_ADDR_31_22_AF | 0x00100000) +/* FB through A2F Address Base */ +#define QUICKVX_FB_A2F_BASE (QUICKVX_ADDR_31_22_AF | 0x00200000) + + +/*************************************************** + * Common Registers in Register Control Block (RCB) Registers + ***************************************************/ + /* CellRAM Configuration RCR Register */ +#define QUICKVX_RCB_RCR_REG (QUICKVX_RCB_BASE | 0x00000000) +/* Image Effect Register */ +#define QUICKVX_RCB_IER_REG (QUICKVX_RCB_BASE | 0x00000004) +/* Row Number Register */ +#define QUICKVX_RCB_ROWNUM_REG (QUICKVX_RCB_BASE | 0x00000008) +/* TCON Timing0 Register */ +#define QUICKVX_RCB_TCON0_REG (QUICKVX_RCB_BASE | 0x0000000C) +/* TCON Timing1 Register */ +#define QUICKVX_RCB_TCON1_REG (QUICKVX_RCB_BASE | 0x00000010) +/* TCON Timing2 Register */ +#define QUICKVX_RCB_TCON2_REG (QUICKVX_RCB_BASE | 0x00000014) +/* PWM Control Register */ +#define QUICKVX_RCB_PWMC_REG (QUICKVX_RCB_BASE | 0x00000018) +/* PWM Width Register */ +#define QUICKVX_RCB_PWMW_REG (QUICKVX_RCB_BASE | 0x0000001C) +/* VEE Configuration Register */ +#define QUICKVX_RCB_VEECONF_REG (QUICKVX_RCB_BASE | 0x00000020) +/* CellRAM Configuration BCR Register */ +#define QUICKVX_RCB_CELLBCR_REG (QUICKVX_RCB_BASE | 0x00000024) +/* CellRAM Configuration Control Register */ +#define QUICKVX_RCB_CELLCC_REG (QUICKVX_RCB_BASE | 0x00000028) +/* Use Case Register */ +#define QUICKVX_RCB_USECASE_REG (QUICKVX_RCB_BASE | 0x00000100) +/* Video Parameter Register */ +#define QUICKVX_RCB_VPARM_REG (QUICKVX_RCB_BASE | 0x00000104) +/* MDDI Client Wake-up Register */ +#define QUICKVX_RCB_MCW_REG (QUICKVX_RCB_BASE | 0x00000108) +/* Burst Length Register */ +#define QUICKVX_RCB_BURSTLN_REG (QUICKVX_RCB_BASE | 0x0000010C) +/* Display Attributes Register */ +#define QUICKVX_RCB_DISPATTR_REG (QUICKVX_RCB_BASE | 0x00000110) +/* Error Status Register */ +#define QUICKVX_RCB_ERRSTAT_REG (QUICKVX_RCB_BASE | 0x00000114) +/* Error Mask Register */ +#define QUICKVX_RCB_ERRMSK_REG (QUICKVX_RCB_BASE | 0x00000118) +/* MDDI ASSP FIFO Overflow Address Register */ +#define QUICKVX_RCB_ASSPFOA_REG (QUICKVX_RCB_BASE | 0x0000011C) +/* MDDI Fabric FIFO Overflow Address Register */ +#define QUICKVX_RCB_FABFOA_REG (QUICKVX_RCB_BASE | 0x00000120) +/* Incoming RGB FIFO Overflow Address Register */ +#define QUICKVX_RCB_IRFOA_REG (QUICKVX_RCB_BASE | 0x00000124) +/* SPI Overflow Address Register */ +#define QUICKVX_RCB_SPIOA_REG (QUICKVX_RCB_BASE | 0x00000128) +/* Ping Buffer Address Register */ +#define QUICKVX_RCB_PINGBA_REG (QUICKVX_RCB_BASE | 0x0000012C) +/* Pong Buffer Address Register */ +#define QUICKVX_RCB_PONGBA_REG (QUICKVX_RCB_BASE | 0x00000130) +/* Configuration Done Register */ +#define QUICKVX_RCB_CONFDONE_REG (QUICKVX_RCB_BASE | 0x00000134) +/* FIFO Flush Register */ +#define QUICKVX_RCB_FFLUSH_REG (QUICKVX_RCB_BASE | 0x00000138) + + +/*************************************************** + * SPI Block Registers + ***************************************************/ +/* SPI Rx0 Register */ +#define QUICKVX_SPI_RX0_REG (QUICKVX_SPI_BASE | 0x00000000) +/* SPI Rx1 Register */ +#define QUICKVX_SPI_RX1_REG (QUICKVX_SPI_BASE | 0x00000004) +/* SPI Rx2 Register */ +#define QUICKVX_SPI_RX2_REG (QUICKVX_SPI_BASE | 0x00000008) +/* SPI Rx3 Register */ +#define QUICKVX_SPI_RX3_REG (QUICKVX_SPI_BASE | 0x0000000C) +/* SPI Rx4 Register */ +#define QUICKVX_SPI_RX4_REG (QUICKVX_SPI_BASE | 0x00000010) +/* SPI Rx5 Register */ +#define QUICKVX_SPI_RX5_REG (QUICKVX_SPI_BASE | 0x00000014) +/* SPI Rx6 Register */ +#define QUICKVX_SPI_RX6_REG (QUICKVX_SPI_BASE | 0x00000018) +/* SPI Rx7 Register */ +#define QUICKVX_SPI_RX7_REG (QUICKVX_SPI_BASE | 0x0000001C) +/* SPI Tx0 Register */ +#define QUICKVX_SPI_TX0_REG (QUICKVX_SPI_BASE | 0x00000020) +/* SPI Tx1 Register */ +#define QUICKVX_SPI_TX1_REG (QUICKVX_SPI_BASE | 0x00000024) +/* SPI Tx2 Register */ +#define QUICKVX_SPI_TX2_REG (QUICKVX_SPI_BASE | 0x00000028) +/* SPI Tx3 Register */ +#define QUICKVX_SPI_TX3_REG (QUICKVX_SPI_BASE | 0x0000002C) +/* SPI Tx4 Register */ +#define QUICKVX_SPI_TX4_REG (QUICKVX_SPI_BASE | 0x00000030) +/* SPI Tx5 Register */ +#define QUICKVX_SPI_TX5_REG (QUICKVX_SPI_BASE | 0x00000034) +/* SPI Tx6 Register */ +#define QUICKVX_SPI_TX6_REG (QUICKVX_SPI_BASE | 0x00000038) +/* SPI Tx7 Register */ +#define QUICKVX_SPI_TX7_REG (QUICKVX_SPI_BASE | 0x0000003C) +/* SPI Control Register */ +#define QUICKVX_SPI_CTRL_REG (QUICKVX_SPI_BASE | 0x00000040) +/* SPI Transfer Length Register */ +#define QUICKVX_SPI_TLEN_REG (QUICKVX_SPI_BASE | 0x00000044) + + +/*************************************************** + * Clock and Reset (CAR) Block Registers + ***************************************************/ +/* ASSP Global Clock Enable Register */ +#define QUICKVX_CAR_ASSP_GCE_REG (QUICKVX_CAR_BASE | 0x00000000) +/* VLP Control1 Register */ +#define QUICKVX_CAR_VLPCTRL1_REG (QUICKVX_CAR_BASE | 0x00000004) +/* VLP Control2 Register */ +#define QUICKVX_CAR_VLPCTRL2_REG (QUICKVX_CAR_BASE | 0x00000008) +/* Clock Selection Register */ +#define QUICKVX_CAR_CLKSEL_REG (QUICKVX_CAR_BASE | 0x0000000C) +/* PLL Control Register */ +#define QUICKVX_CAR_PLLCTRL_REG (QUICKVX_CAR_BASE | 0x00000010) +/* PLL Clock Ratio Register */ +#define QUICKVX_CAR_PLLCLKRATIO_REG (QUICKVX_CAR_BASE | 0x00000014) + + +/*************************************************** + * VEE Block Registers + ***************************************************/ +/* VEE Control Register */ +#define QUICKVX_VEE_VEECTRL_REG (QUICKVX_VEE_BASE | 0x00000000) +/* Strength Register */ +#define QUICKVX_VEE_STRENGTH_REG (QUICKVX_VEE_BASE | 0x0000000C) +/* Variance Register */ +#define QUICKVX_VEE_VARIANCE_REG (QUICKVX_VEE_BASE | 0x00000010) +/* Slope Register */ +#define QUICKVX_VEE_SLOPE_REG (QUICKVX_VEE_BASE | 0x00000014) +/* Sharpen Control0 Register */ +#define QUICKVX_VEE_SHRPCTRL0_REG (QUICKVX_VEE_BASE | 0x0000001C) +/* Sharpen Control1 Register */ +#define QUICKVX_VEE_SHRPCTRL1_REG (QUICKVX_VEE_BASE | 0x00000020) +/* Upper Horizontal Positon Register */ +#define QUICKVX_VEE_UHPOS_REG (QUICKVX_VEE_BASE | 0x00000024) +/* Lower Horizontal Positon Register */ +#define QUICKVX_VEE_LHPOS_REG (QUICKVX_VEE_BASE | 0x00000028) +/* Upper Vertical Positon Register */ +#define QUICKVX_VEE_UVPOS_REG (QUICKVX_VEE_BASE | 0x0000002C) +/* Lower Vertical Positon Register */ +#define QUICKVX_VEE_LVPOS_REG (QUICKVX_VEE_BASE | 0x00000030) +/* Upper Frame Width Register */ +#define QUICKVX_VEE_UFWDTH_REG (QUICKVX_VEE_BASE | 0x00000034) +/* Lower Frame Width Register */ +#define QUICKVX_VEE_LFWDTH_REG (QUICKVX_VEE_BASE | 0x00000038) +/* Upper Frame Height Register */ +#define QUICKVX_VEE_UFHGHT_REG (QUICKVX_VEE_BASE | 0x0000003C) +/* Lower Frame Height Register */ +#define QUICKVX_VEE_LFHGHT_REG (QUICKVX_VEE_BASE | 0x00000040) +/* Control0 Register */ +#define QUICKVX_VEE_CTRL0_REG (QUICKVX_VEE_BASE | 0x00000044) +/* Control1 Register */ +#define QUICKVX_VEE_CTRL1_REG (QUICKVX_VEE_BASE | 0x00000048) +/* Video Enhancement Enable Register */ +#define QUICKVX_VEE_VDOEEN_REG (QUICKVX_VEE_BASE | 0x0000004C) +/* Black Level Register */ +#define QUICKVX_VEE_BLCKLEV_REG (QUICKVX_VEE_BASE | 0x00000050) +/* White Level Register */ +#define QUICKVX_VEE_WHTLEV_REG (QUICKVX_VEE_BASE | 0x00000054) +/* Amplification Limits Register */ +#define QUICKVX_VEE_AMPLMTS_REG (QUICKVX_VEE_BASE | 0x00000060) +/* Dithering Mode Register */ +#define QUICKVX_VEE_DITHMOD_REG (QUICKVX_VEE_BASE | 0x00000064) +/* Upper Look-up Data Register */ +#define QUICKVX_VEE_ULUD_REG (QUICKVX_VEE_BASE | 0x00000080) +/* Lower Look-up Data Register */ +#define QUICKVX_VEE_LLUD_REG (QUICKVX_VEE_BASE | 0x00000084) +/* Look-up Address Register */ +#define QUICKVX_VEE_LUADDR_REG (QUICKVX_VEE_BASE | 0x00000088) +/* Look-up Write Enable Register */ +#define QUICKVX_VEE_LUWREN_REG (QUICKVX_VEE_BASE | 0x0000008C) +/* VEE ID Register */ +#define QUICKVX_VEE_VEEID_REG (QUICKVX_VEE_BASE | 0x000003FC) +/* M_11 Register */ +#define QUICKVX_VEE_M_11_REG (QUICKVX_VEE_BASE | 0x000000C0) +/* M_12 Register */ +#define QUICKVX_VEE_M_12_REG (QUICKVX_VEE_BASE | 0x000000C4) +/* M_13 Register */ +#define QUICKVX_VEE_M_13_REG (QUICKVX_VEE_BASE | 0x000000C8) +/* M_21 Register */ +#define QUICKVX_VEE_M_21_REG (QUICKVX_VEE_BASE | 0x000000CC) +/* M_22 Register */ +#define QUICKVX_VEE_M_22_REG (QUICKVX_VEE_BASE | 0x000000D0) +/* M_23 Register */ +#define QUICKVX_VEE_M_23_REG (QUICKVX_VEE_BASE | 0x000000D4) +/* M_31 Register */ +#define QUICKVX_VEE_M_31_REG (QUICKVX_VEE_BASE | 0x000000D8) +/* M_32 Register */ +#define QUICKVX_VEE_M_32_REG (QUICKVX_VEE_BASE | 0x000000DC) +/* M_33 Register */ +#define QUICKVX_VEE_M_33_REG (QUICKVX_VEE_BASE | 0x000000E0) +/* R Offset Register */ +#define QUICKVX_VEE_OFFSET_R_REG (QUICKVX_VEE_BASE | 0x000000E8) +/* G Offset Register */ +#define QUICKVX_VEE_OFFSET_G_REG (QUICKVX_VEE_BASE | 0x000000EC) +/* B Offset Register */ +#define QUICKVX_VEE_OFFSET_B_REG (QUICKVX_VEE_BASE | 0x000000F0) + +/* LCD Reset Register */ +#define QUICKVX_FB_A2F_LCD_RESET_REG (QUICKVX_FB_A2F_BASE | 0x00000000) + +/* Register bit defines */ +/* PLL Lock bit in the PLL Control Register */ +#define QUICKVX_PLL_LOCK_BIT (1 << 7) + +#define QL_SPI_CTRL_rSPISTart(x) (x) +#define QL_SPI_CTRL_rCPHA(x) (x << 1) +#define QL_SPI_CTRL_rCPOL(x) (x << 2) +#define QL_SPI_CTRL_rLSB(x) (x << 3) +#define QL_SPI_CTRL_rSLVSEL(x) (x << 4) +#define QL_SPI_CTRL_MASK_rTxDone (1 << 9) + +#define QL_SPI_LCD_DEV_ID 0x1c +#define QL_SPI_LCD_RS(x) (x << 1) +#define QL_SPI_LCD_RW(x) (x) +#define QL_SPI_LCD_INDEX_START_BYTE ((QL_SPI_LCD_DEV_ID << 2) | \ + QL_SPI_LCD_RS(0) | QL_SPI_LCD_RW(0)) +#define QL_SPI_LCD_CMD_START_BYTE ((QL_SPI_LCD_DEV_ID << 2) | \ + QL_SPI_LCD_RS(1) | QL_SPI_LCD_RW(0)) +#define QL_SPI_CTRL_LCD_START (QL_SPI_CTRL_rSPISTart(1) | \ + QL_SPI_CTRL_rCPHA(1) | QL_SPI_CTRL_rCPOL(1) | \ + QL_SPI_CTRL_rLSB(0) | QL_SPI_CTRL_rSLVSEL(0)) + +int ql_mddi_write(uint32 address, uint32 value) +{ + uint32 regval = 0; + int ret = 0; + + ret = mddi_queue_register_write(address, value, TRUE, 0); + + if (!ret) { + ret = mddi_queue_register_read(address, ®val, TRUE, 0); + if (regval != value) { + MDDI_MSG_DEBUG("\nMismatch: ql_mddi_write[0x%x]->0x%x " + "r0x%x\n", address, value, regval); + } else { + MDDI_MSG_DEBUG("\nMatch: ql_mddi_write[0x%x]->0x%x " + "r0x%x\n", address, value, regval); + } + } + + return ret; +} + +int ql_mddi_read(uint32 address, uint32 *regval) +{ + int ret = 0; + + ret = mddi_queue_register_read(address, regval, TRUE, 0); + MDDI_MSG_DEBUG("\nql_mddi_read[0x%x]=0x%x", address, *regval); + + return ret; +} + +int ql_send_spi_cmd_to_lcd(uint32 index, uint32 cmd) +{ + int retry, ret; + uint32 readval; + + MDDI_MSG_DEBUG("\n %s(): index 0x%x, cmd 0x%x", __func__, index, cmd); + /* do the index phase */ + /* send 24 bits in the index phase */ + ql_mddi_write(QUICKVX_SPI_TLEN_REG, 23); + + /* send 24 bits in the index phase, starting at bit 23 of TX0 reg */ + ql_mddi_write(QUICKVX_SPI_TX0_REG, + (QL_SPI_LCD_INDEX_START_BYTE << 16) | index); + + /* set start */ + ql_mddi_write(QUICKVX_SPI_CTRL_REG, QL_SPI_CTRL_LCD_START); + retry = 0; + + do { + ret = ql_mddi_read(QUICKVX_SPI_CTRL_REG, &readval); + + if (ret || ++retry > 5) { + MDDI_MSG_DEBUG("\n ql_send_spi_cmd_to_lcd: retry " + "timeout at index phase, ret = %d", ret); + return -EIO; + } + mddi_wait(1); + } while ((readval & QL_SPI_CTRL_MASK_rTxDone) == 0); + + /* do the command phase */ + /* send 24 bits in the cmd phase */ + ql_mddi_write(QUICKVX_SPI_TLEN_REG, 23); + + /* send 24 bits in the cmd phase, starting at bit 23 of TX0 reg. */ + ql_mddi_write(QUICKVX_SPI_TX0_REG, + (QL_SPI_LCD_CMD_START_BYTE << 16) | cmd); + + /* set start */ + ql_mddi_write(QUICKVX_SPI_CTRL_REG, QL_SPI_CTRL_LCD_START); + retry = 0; + + do { + ret = ql_mddi_read(QUICKVX_SPI_CTRL_REG, &readval); + + if (ret || ++retry > 5) { + MDDI_MSG_DEBUG("\n ql_send_spi_cmd_to_lcd: retry " + "timeout at cmd phase, ret = %d", ret); + return -EIO; + } + mddi_wait(1); + } while ((readval & QL_SPI_CTRL_MASK_rTxDone) == 0); + + return 0; +} + + +int ql_send_spi_data_from_lcd(uint32 index, uint32 *value) +{ + int retry, ret; + uint32 readval; + + MDDI_MSG_DEBUG("\n %s(): index 0x%x", __func__, index); + /* do the index phase */ + /* send 24 bits in the index phase */ + ql_mddi_write(QUICKVX_SPI_TLEN_REG, 23); + + /* send 24 bits in the index phase, starting at bit 23 of TX0 reg */ + ql_mddi_write(QUICKVX_SPI_TX0_REG, + (QL_SPI_LCD_INDEX_START_BYTE << 16) | index); + + /* set start */ + ql_mddi_write(QUICKVX_SPI_CTRL_REG, QL_SPI_CTRL_LCD_START); + retry = 0; + + do { + ret = ql_mddi_read(QUICKVX_SPI_CTRL_REG, &readval); + + if (ret || ++retry > 5) { + MDDI_MSG_DEBUG("\n ql_send_spi_cmd_to_lcd: retry " + "timeout at index phase, ret = %d", ret); + return -EIO; + } + mddi_wait(1); + } while ((readval & QL_SPI_CTRL_MASK_rTxDone) == 0); + + /* do the command phase */ + /* send 8 bits and read 24 bits in the cmd phase, so total 32 bits */ + ql_mddi_write(QUICKVX_SPI_TLEN_REG, 31); + + /* send 24 bits in the cmd phase, starting at bit 31 of TX0 reg */ + ql_mddi_write(QUICKVX_SPI_TX0_REG, + ((QL_SPI_LCD_CMD_START_BYTE << 16)) << 8); + + /* set start */ + ql_mddi_write(QUICKVX_SPI_CTRL_REG, QL_SPI_CTRL_LCD_START); + retry = 0; + + do { + ret = ql_mddi_read(QUICKVX_SPI_CTRL_REG, &readval); + + if (ret || ++retry > 5) { + MDDI_MSG_DEBUG("\n ql_send_spi_cmd_to_lcd: retry " + "timeout at cmd phase, ret = %d", ret); + return -EIO; + } + mddi_wait(1); + } while ((readval & QL_SPI_CTRL_MASK_rTxDone) == 0); + + /* value will appear at lower 16 bits */ + ret = ql_mddi_read(QUICKVX_SPI_RX0_REG, value); + + if (!ret) { + *value = *value & 0xffff; + MDDI_MSG_DEBUG("\n QUICKVX_SPI_RX0_REG value = 0x%x", *value); + } else + MDDI_MSG_DEBUG("\n Read QUICKVX_SPI_RX0_REG Failed"); + + return ret; +} + +/* Global Variables */ +static uint32 mddi_quickvx_rows_per_second; +static uint32 mddi_quickvx_usecs_per_refresh; +static uint32 mddi_quickvx_rows_per_refresh; + +void mddi_quickvx_configure_registers(void) +{ + MDDI_MSG_DEBUG("\n%s(): ", __func__); + ql_mddi_write(QUICKVX_CAR_CLKSEL_REG, 0x00007000); + + ql_mddi_write(QUICKVX_RCB_PWMW_REG, 0x0000FFFF); + + ql_mddi_write(QUICKVX_RCB_PWMC_REG, 0x00000001); + + ql_mddi_write(QUICKVX_RCB_CONFDONE_REG, 0x00000000); + + /* display is x width = 480, y width = 864 */ + ql_mddi_write(QUICKVX_RCB_TCON0_REG, 0x035f01df); + + /* VFP=2, VBP=4, HFP=16, HBP=16 */ + ql_mddi_write(QUICKVX_RCB_TCON1_REG, 0x01e301e1); + + /* VSW =2, HSW=8 */ + ql_mddi_write(QUICKVX_RCB_TCON2_REG, 0x000000e1); + + ql_mddi_write(QUICKVX_RCB_DISPATTR_REG, 0x00000000); + + ql_mddi_write(QUICKVX_RCB_USECASE_REG, 0x00000025); + + ql_mddi_write(QUICKVX_RCB_VPARM_REG, 0x00000888); + + ql_mddi_write(QUICKVX_RCB_VEECONF_REG, 0x00000001); + + ql_mddi_write(QUICKVX_RCB_IER_REG, 0x00000000); + + ql_mddi_write(QUICKVX_RCB_RCR_REG, 0x80000010); + + ql_mddi_write(QUICKVX_RCB_CELLBCR_REG, 0x8008746F); + + ql_mddi_write(QUICKVX_RCB_CELLCC_REG, 0x800000A3); + + ql_mddi_write(QUICKVX_RCB_CONFDONE_REG, 0x00000001); +} + +void mddi_quickvx_prim_lcd_init(void) +{ + uint32 value; + + MDDI_MSG_DEBUG("\n%s(): ", __func__); + ql_send_spi_data_from_lcd(0, &value); + + ql_send_spi_cmd_to_lcd(0x0100, 0x3000); /* power control1 */ + ql_send_spi_cmd_to_lcd(0x0101, 0x4010); /* power control2 */ + ql_send_spi_cmd_to_lcd(0x0106, 0x0000); /* auto seq setting */ + mddi_wait(3); + + ql_mddi_write(QUICKVX_FB_A2F_LCD_RESET_REG, 0x00000001); + mddi_wait(1); + ql_mddi_write(QUICKVX_FB_A2F_LCD_RESET_REG, 0x00000000); + mddi_wait(1); + ql_mddi_write(QUICKVX_FB_A2F_LCD_RESET_REG, 0x00000001); + mddi_wait(10); + + ql_send_spi_cmd_to_lcd(0x0001, 0x0310); /* driver out control */ + ql_send_spi_cmd_to_lcd(0x0002, 0x0100); /* lcd ac control */ + ql_send_spi_cmd_to_lcd(0x0003, 0x0000); /* entry mode */ + ql_send_spi_cmd_to_lcd(0x0007, 0x0000); /* disp cont1 */ + ql_send_spi_cmd_to_lcd(0x0008, 0x0004); /* disp cont2 */ + ql_send_spi_cmd_to_lcd(0x0009, 0x000C); /* disp cont3 */ + ql_send_spi_cmd_to_lcd(0x000C, 0x4010); /* disp if cont1 */ + ql_send_spi_cmd_to_lcd(0x000E, 0x0000); /* disp if cont2 */ + ql_send_spi_cmd_to_lcd(0x0020, 0x013F); /* panel if cont1 */ + ql_send_spi_cmd_to_lcd(0x0022, 0x7600); /* panel if cont3 */ + ql_send_spi_cmd_to_lcd(0x0023, 0x1C0A); /* panel if cont4 */ + ql_send_spi_cmd_to_lcd(0x0024, 0x1C2C); /* panel if cont5 */ + ql_send_spi_cmd_to_lcd(0x0025, 0x1C4E); /* panel if cont6 */ + ql_send_spi_cmd_to_lcd(0x0027, 0x0000); /* panel if cont8 */ + ql_send_spi_cmd_to_lcd(0x0028, 0x760C); /* panel if cont9 */ + ql_send_spi_cmd_to_lcd(0x0300, 0x0000); /* gamma adj0 */ + ql_send_spi_cmd_to_lcd(0x0301, 0x0502); /* gamma adj1 */ + ql_send_spi_cmd_to_lcd(0x0302, 0x0705); /* gamma adj2 */ + ql_send_spi_cmd_to_lcd(0x0303, 0x0000); /* gamma adj3 */ + ql_send_spi_cmd_to_lcd(0x0304, 0x0200); /* gamma adj4 */ + ql_send_spi_cmd_to_lcd(0x0305, 0x0707); /* gamma adj5 */ + ql_send_spi_cmd_to_lcd(0x0306, 0x1010); /* gamma adj6 */ + ql_send_spi_cmd_to_lcd(0x0307, 0x0202); /* gamma adj7 */ + ql_send_spi_cmd_to_lcd(0x0308, 0x0704); /* gamma adj8 */ + ql_send_spi_cmd_to_lcd(0x0309, 0x0707); /* gamma adj9 */ + ql_send_spi_cmd_to_lcd(0x030A, 0x0000); /* gamma adja */ + ql_send_spi_cmd_to_lcd(0x030B, 0x0000); /* gamma adjb */ + ql_send_spi_cmd_to_lcd(0x030C, 0x0707); /* gamma adjc */ + ql_send_spi_cmd_to_lcd(0x030D, 0x1010); /* gamma adjd */ + ql_send_spi_cmd_to_lcd(0x0310, 0x0104); /* gamma adj10 */ + ql_send_spi_cmd_to_lcd(0x0311, 0x0503); /* gamma adj11 */ + ql_send_spi_cmd_to_lcd(0x0312, 0x0304); /* gamma adj12 */ + ql_send_spi_cmd_to_lcd(0x0315, 0x0304); /* gamma adj15 */ + ql_send_spi_cmd_to_lcd(0x0316, 0x031C); /* gamma adj16 */ + ql_send_spi_cmd_to_lcd(0x0317, 0x0204); /* gamma adj17 */ + ql_send_spi_cmd_to_lcd(0x0318, 0x0402); /* gamma adj18 */ + ql_send_spi_cmd_to_lcd(0x0319, 0x0305); /* gamma adj19 */ + ql_send_spi_cmd_to_lcd(0x031C, 0x0707); /* gamma adj1c */ + ql_send_spi_cmd_to_lcd(0x031D, 0x021F); /* gamma adj1d */ + ql_send_spi_cmd_to_lcd(0x0320, 0x0507); /* gamma adj20 */ + ql_send_spi_cmd_to_lcd(0x0321, 0x0604); /* gamma adj21 */ + ql_send_spi_cmd_to_lcd(0x0322, 0x0405); /* gamma adj22 */ + ql_send_spi_cmd_to_lcd(0x0327, 0x0203); /* gamma adj27 */ + ql_send_spi_cmd_to_lcd(0x0328, 0x0300); /* gamma adj28 */ + ql_send_spi_cmd_to_lcd(0x0329, 0x0002); /* gamma adj29 */ + ql_send_spi_cmd_to_lcd(0x0100, 0x363C); /* power cont1 */ + mddi_wait(1); + ql_send_spi_cmd_to_lcd(0x0101, 0x4003); /* power cont2 */ + ql_send_spi_cmd_to_lcd(0x0102, 0x0001); /* power cont3 */ + ql_send_spi_cmd_to_lcd(0x0103, 0x3C58); /* power cont4 */ + ql_send_spi_cmd_to_lcd(0x010C, 0x0135); /* power cont6 */ + ql_send_spi_cmd_to_lcd(0x0106, 0x0002); /* auto seq */ + ql_send_spi_cmd_to_lcd(0x0029, 0x03BF); /* panel if cont10 */ + ql_send_spi_cmd_to_lcd(0x0106, 0x0003); /* auto seq */ + mddi_wait(5); + ql_send_spi_cmd_to_lcd(0x0101, 0x4010); /* power cont2 */ + mddi_wait(10); +} + +/* Function to Power On the Primary and Secondary LCD panels */ +static int mddi_quickvx_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + MDDI_MSG_DEBUG("\n%s(): ", __func__); + mfd = platform_get_drvdata(pdev); + + if (!mfd) { + MDDI_MSG_DEBUG("\n mddi_quickvx_lcd_on: Device not found!"); + return -ENODEV; + } + + if (mfd->key != MFD_KEY) { + MDDI_MSG_DEBUG("\n mddi_quickvx_lcd_on: Invalid MFD key!"); + return -EINVAL; + } + + mddi_host_client_cnt_reset(); + mddi_quickvx_configure_registers(); + mddi_quickvx_prim_lcd_init(); + + return 0; +} + + +/* Function to Power Off the Primary and Secondary LCD panels */ +static int mddi_quickvx_lcd_off(struct platform_device *pdev) +{ + MDDI_MSG_DEBUG("\n%s(): ", __func__); + mddi_wait(1); + ql_send_spi_cmd_to_lcd(0x0106, 0x0002); /* Auto Sequencer setting */ + mddi_wait(10); + ql_send_spi_cmd_to_lcd(0x0106, 0x0000); /* Auto Sequencer setting */ + ql_send_spi_cmd_to_lcd(0x0029, 0x0002); /* Panel IF control 10 */ + ql_send_spi_cmd_to_lcd(0x0100, 0x300D); /* Power Control 1 */ + mddi_wait(1); + + return 0; +} + +/* Function to set the Backlight brightness level */ +static void mddi_quickvx_lcd_set_backlight(struct msm_fb_data_type *mfd) +{ + int32 level, i = 0, ret; + + MDDI_MSG_DEBUG("%s(): ", __func__); + + level = mfd->bl_level; + MDDI_MSG_DEBUG("\n level = %d", level); + if (level < 0) { + MDDI_MSG_DEBUG("mddi_quickvx_lcd_set_backlight: " + "Invalid backlight level (%d)!\n", level); + return; + } + while (i++ < 3) { + ret = pmic_set_led_intensity(LED_LCD, level); + if (ret == 0) + return; + msleep(10); + } + + MDDI_MSG_DEBUG("%s: can't set lcd backlight!\n", + __func__); +} + +/* Driver Probe function */ +static int __devinit mddi_quickvx_lcd_probe(struct platform_device *pdev) +{ + MDDI_MSG_DEBUG("\n%s(): id is %d", __func__, pdev->id); + msm_fb_add_device(pdev); + return 0; +} + +/* Driver data structure */ +static struct platform_driver this_driver = { + .probe = mddi_quickvx_lcd_probe, + .driver = { + .name = "mddi_quickvx", + }, +}; + + +/* Primary LCD panel data structure */ +static struct msm_fb_panel_data mddi_quickvx_panel_data0 = { + .on = mddi_quickvx_lcd_on, + .off = mddi_quickvx_lcd_off, + .set_backlight = mddi_quickvx_lcd_set_backlight, +}; + + +/* Primary LCD panel device structure */ +static struct platform_device this_device0 = { + .name = "mddi_quickvx", + .id = MDDI_QUICKVX_1_2, + .dev = { + .platform_data = &mddi_quickvx_panel_data0, + } +}; + +/* Module init - driver main entry point */ +static int __init mddi_quickvx_lcd_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + u32 cid; + MDDI_MSG_DEBUG("\n%s(): ", __func__); + + ret = msm_fb_detect_client("mddi_quickvx"); + + if (ret == -ENODEV) { + /* Device not found */ + MDDI_MSG_DEBUG("\n mddi_quickvx_lcd_init: No device found!"); + return 0; + } + + if (ret) { + cid = mddi_get_client_id(); + + MDDI_MSG_DEBUG("\n cid = 0x%x", cid); + if (((cid >> 16) != QUICKVX_MDDI_MFR_CODE) || + ((cid & 0xFFFF) != QUICKVX_MDDI_PRD_CODE)) { + /* MDDI Client ID not matching */ + MDDI_MSG_DEBUG("\n mddi_quickvx_lcd_init: " + "Client ID missmatch!"); + + return 0; + } + MDDI_MSG_DEBUG("\n mddi_quickvx_lcd_init: " + "QuickVX LCD panel detected!"); + } + +#endif /* CONFIG_FB_MSM_MDDI_AUTO_DETECT */ + + mddi_quickvx_rows_per_refresh = 872; + mddi_quickvx_rows_per_second = 52364; + mddi_quickvx_usecs_per_refresh = 16574; + + ret = platform_driver_register(&this_driver); + + if (!ret) { + pinfo = &mddi_quickvx_panel_data0.panel_info; + pinfo->xres = 480; + pinfo->yres = 864; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = MDDI_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; + pinfo->wait_cycle = 0; + pinfo->bpp = 24; + pinfo->fb_num = 2; + + pinfo->clk_rate = 192000000; + pinfo->clk_min = 192000000; + pinfo->clk_max = 200000000; + pinfo->lcd.rev = 1; + pinfo->lcd.vsync_enable = TRUE; + pinfo->lcd.refx100 = (mddi_quickvx_rows_per_second \ + * 100)/mddi_quickvx_rows_per_refresh; + pinfo->lcd.v_back_porch = 4; + pinfo->lcd.v_front_porch = 2; + pinfo->lcd.v_pulse_width = 2; + pinfo->lcd.hw_vsync_mode = TRUE; + pinfo->lcd.vsync_notifier_period = (1 * HZ); + pinfo->bl_max = 10; + pinfo->bl_min = 0; + + ret = platform_device_register(&this_device0); + if (ret) { + platform_driver_unregister(&this_driver); + MDDI_MSG_DEBUG("mddi_quickvx_lcd_init: " + "Primary device registration failed!\n"); + } + } + + return ret; +} + +module_init(mddi_quickvx_lcd_init); + diff --git a/drivers/video/msm/mddi_sharp.c b/drivers/video/msm/mddi_sharp.c new file mode 100644 index 00000000000..b2a7188de55 --- /dev/null +++ b/drivers/video/msm/mddi_sharp.c @@ -0,0 +1,900 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" + +#define SHARP_QVGA_PRIM 1 +#define SHARP_128X128_SECD 2 + +extern uint32 mddi_host_core_version; +static boolean mddi_debug_prim_wait = FALSE; +static boolean mddi_sharp_vsync_wake = TRUE; +static boolean mddi_sharp_monitor_refresh_value = TRUE; +static boolean mddi_sharp_report_refresh_measurements = FALSE; +static uint32 mddi_sharp_rows_per_second = 13830; /* 5200000/376 */ +static uint32 mddi_sharp_rows_per_refresh = 338; +static uint32 mddi_sharp_usecs_per_refresh = 24440; /* (376+338)/5200000 */ +static boolean mddi_sharp_debug_60hz_refresh = FALSE; + +extern mddi_gpio_info_type mddi_gpio; +extern boolean mddi_vsync_detect_enabled; +static msm_fb_vsync_handler_type mddi_sharp_vsync_handler; +static void *mddi_sharp_vsync_handler_arg; +static uint16 mddi_sharp_vsync_attempts; + +static void mddi_sharp_prim_lcd_init(void); +static void mddi_sharp_sub_lcd_init(void); +static void mddi_sharp_lcd_set_backlight(struct msm_fb_data_type *mfd); +static void mddi_sharp_vsync_set_handler(msm_fb_vsync_handler_type handler, + void *); +static void mddi_sharp_lcd_vsync_detected(boolean detected); +static struct msm_panel_common_pdata *mddi_sharp_pdata; + +#define REG_SYSCTL 0x0000 +#define REG_INTR 0x0006 +#define REG_CLKCNF 0x000C +#define REG_CLKDIV1 0x000E +#define REG_CLKDIV2 0x0010 + +#define REG_GIOD 0x0040 +#define REG_GIOA 0x0042 + +#define REG_AGM 0x010A +#define REG_FLFT 0x0110 +#define REG_FRGT 0x0112 +#define REG_FTOP 0x0114 +#define REG_FBTM 0x0116 +#define REG_FSTRX 0x0118 +#define REG_FSTRY 0x011A +#define REG_VRAM 0x0202 +#define REG_SSDCTL 0x0330 +#define REG_SSD0 0x0332 +#define REG_PSTCTL1 0x0400 +#define REG_PSTCTL2 0x0402 +#define REG_PTGCTL 0x042A +#define REG_PTHP 0x042C +#define REG_PTHB 0x042E +#define REG_PTHW 0x0430 +#define REG_PTHF 0x0432 +#define REG_PTVP 0x0434 +#define REG_PTVB 0x0436 +#define REG_PTVW 0x0438 +#define REG_PTVF 0x043A +#define REG_VBLKS 0x0458 +#define REG_VBLKE 0x045A +#define REG_SUBCTL 0x0700 +#define REG_SUBTCMD 0x0702 +#define REG_SUBTCMDD 0x0704 +#define REG_REVBYTE 0x0A02 +#define REG_REVCNT 0x0A04 +#define REG_REVATTR 0x0A06 +#define REG_REVFMT 0x0A08 + +#define SHARP_SUB_UNKNOWN 0xffffffff +#define SHARP_SUB_HYNIX 1 +#define SHARP_SUB_ROHM 2 + +static uint32 sharp_subpanel_type = SHARP_SUB_UNKNOWN; + +static void sub_through_write(int sub_rs, uint32 sub_data) +{ + mddi_queue_register_write(REG_SUBTCMDD, sub_data, FALSE, 0); + + /* CS=1,RD=1,WE=1,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x000e | sub_rs, FALSE, 0); + + /* CS=0,RD=1,WE=1,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x0006 | sub_rs, FALSE, 0); + + /* CS=0,RD=1,WE=0,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x0004 | sub_rs, FALSE, 0); + + /* CS=0,RD=1,WE=1,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x0006 | sub_rs, FALSE, 0); + + /* CS=1,RD=1,WE=1,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x000e | sub_rs, TRUE, 0); +} + +static uint32 sub_through_read(int sub_rs) +{ + uint32 sub_data; + + /* CS=1,RD=1,WE=1,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x000e | sub_rs, FALSE, 0); + + /* CS=0,RD=1,WE=1,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x0006 | sub_rs, FALSE, 0); + + /* CS=0,RD=1,WE=0,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x0002 | sub_rs, TRUE, 0); + + mddi_queue_register_read(REG_SUBTCMDD, &sub_data, TRUE, 0); + + /* CS=0,RD=1,WE=1,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x0006 | sub_rs, FALSE, 0); + + /* CS=1,RD=1,WE=1,RS=sub_rs */ + mddi_queue_register_write(REG_SUBTCMD, 0x000e | sub_rs, TRUE, 0); + + return sub_data; +} + +static void serigo(uint32 ssd) +{ + uint32 ssdctl; + + mddi_queue_register_read(REG_SSDCTL, &ssdctl, TRUE, 0); + ssdctl = ((ssdctl & 0xE7) | 0x02); + + mddi_queue_register_write(REG_SSD0, ssd, FALSE, 0); + mddi_queue_register_write(REG_SSDCTL, ssdctl, TRUE, 0); + + do { + mddi_queue_register_read(REG_SSDCTL, &ssdctl, TRUE, 0); + } while ((ssdctl & 0x0002) != 0); + + if (mddi_debug_prim_wait) + mddi_wait(2); +} + +static void mddi_sharp_lcd_powerdown(void) +{ + serigo(0x0131); + serigo(0x0300); + mddi_wait(40); + serigo(0x0135); + mddi_wait(20); + serigo(0x2122); + mddi_wait(20); + serigo(0x0201); + mddi_wait(20); + serigo(0x2100); + mddi_wait(20); + serigo(0x2000); + mddi_wait(20); + + mddi_queue_register_write(REG_PSTCTL1, 0x1, TRUE, 0); + mddi_wait(100); + mddi_queue_register_write(REG_PSTCTL1, 0x0, TRUE, 0); + mddi_wait(2); + mddi_queue_register_write(REG_SYSCTL, 0x1, TRUE, 0); + mddi_wait(2); + mddi_queue_register_write(REG_CLKDIV1, 0x3, TRUE, 0); + mddi_wait(2); + mddi_queue_register_write(REG_SSDCTL, 0x0000, TRUE, 0); /* SSDRESET */ + mddi_queue_register_write(REG_SYSCTL, 0x0, TRUE, 0); + mddi_wait(2); +} + +static void mddi_sharp_lcd_set_backlight(struct msm_fb_data_type *mfd) +{ + uint32 regdata; + int32 level; + int max = mfd->panel_info.bl_max; + int min = mfd->panel_info.bl_min; + + if (mddi_sharp_pdata && mddi_sharp_pdata->backlight_level) { + level = mddi_sharp_pdata->backlight_level(mfd->bl_level, + max, + min); + + if (level < 0) + return; + + /* use Rodem GPIO(2:0) to give 8 levels of backlight (7-0) */ + /* Set lower 3 GPIOs as Outputs (set to 0) */ + mddi_queue_register_read(REG_GIOA, ®data, TRUE, 0); + mddi_queue_register_write(REG_GIOA, regdata & 0xfff8, TRUE, 0); + + /* Set lower 3 GPIOs as level */ + mddi_queue_register_read(REG_GIOD, ®data, TRUE, 0); + mddi_queue_register_write(REG_GIOD, + (regdata & 0xfff8) | (0x07 & level), TRUE, 0); + } +} + +static void mddi_sharp_prim_lcd_init(void) +{ + mddi_queue_register_write(REG_SYSCTL, 0x4000, TRUE, 0); + mddi_wait(1); + mddi_queue_register_write(REG_SYSCTL, 0x0000, TRUE, 0); + mddi_wait(5); + mddi_queue_register_write(REG_SYSCTL, 0x0001, FALSE, 0); + mddi_queue_register_write(REG_CLKDIV1, 0x000b, FALSE, 0); + + /* new reg write below */ + if (mddi_sharp_debug_60hz_refresh) + mddi_queue_register_write(REG_CLKCNF, 0x070d, FALSE, 0); + else + mddi_queue_register_write(REG_CLKCNF, 0x0708, FALSE, 0); + + mddi_queue_register_write(REG_SYSCTL, 0x0201, FALSE, 0); + mddi_queue_register_write(REG_PTGCTL, 0x0010, FALSE, 0); + mddi_queue_register_write(REG_PTHP, 4, FALSE, 0); + mddi_queue_register_write(REG_PTHB, 40, FALSE, 0); + mddi_queue_register_write(REG_PTHW, 240, FALSE, 0); + if (mddi_sharp_debug_60hz_refresh) + mddi_queue_register_write(REG_PTHF, 12, FALSE, 0); + else + mddi_queue_register_write(REG_PTHF, 92, FALSE, 0); + + mddi_wait(1); + + mddi_queue_register_write(REG_PTVP, 1, FALSE, 0); + mddi_queue_register_write(REG_PTVB, 2, FALSE, 0); + mddi_queue_register_write(REG_PTVW, 320, FALSE, 0); + mddi_queue_register_write(REG_PTVF, 15, FALSE, 0); + + mddi_wait(1); + + /* vram_color set REG_AGM???? */ + mddi_queue_register_write(REG_AGM, 0x0000, TRUE, 0); + + mddi_queue_register_write(REG_SSDCTL, 0x0000, FALSE, 0); + mddi_queue_register_write(REG_SSDCTL, 0x0001, TRUE, 0); + mddi_wait(1); + mddi_queue_register_write(REG_PSTCTL1, 0x0001, TRUE, 0); + mddi_wait(10); + + serigo(0x0701); + /* software reset */ + mddi_wait(1); + /* Wait over 50us */ + + serigo(0x0400); + /* DCLK~ACHSYNC~ACVSYNC polarity setting */ + serigo(0x2900); + /* EEPROM start read address setting */ + serigo(0x2606); + /* EEPROM start read register setting */ + mddi_wait(20); + /* Wait over 20ms */ + + serigo(0x0503); + /* Horizontal timing setting */ + serigo(0x062C); + /* Veritical timing setting */ + serigo(0x2001); + /* power initialize setting(VDC2) */ + mddi_wait(20); + /* Wait over 20ms */ + + serigo(0x2120); + /* Initialize power setting(CPS) */ + mddi_wait(20); + /* Wait over 20ms */ + + serigo(0x2130); + /* Initialize power setting(CPS) */ + mddi_wait(20); + /* Wait over 20ms */ + + serigo(0x2132); + /* Initialize power setting(CPS) */ + mddi_wait(10); + /* Wait over 10ms */ + + serigo(0x2133); + /* Initialize power setting(CPS) */ + mddi_wait(20); + /* Wait over 20ms */ + + serigo(0x0200); + /* Panel initialize release(INIT) */ + mddi_wait(1); + /* Wait over 1ms */ + + serigo(0x0131); + /* Panel setting(CPS) */ + mddi_wait(1); + /* Wait over 1ms */ + + mddi_queue_register_write(REG_PSTCTL1, 0x0003, TRUE, 0); + + /* if (FFA LCD is upside down) -> serigo(0x0100); */ + serigo(0x0130); + + /* Black mask release(display ON) */ + mddi_wait(1); + /* Wait over 1ms */ + + if (mddi_sharp_vsync_wake) { + mddi_queue_register_write(REG_VBLKS, 0x1001, TRUE, 0); + mddi_queue_register_write(REG_VBLKE, 0x1002, TRUE, 0); + } + + /* Set the MDP pixel data attributes for Primary Display */ + mddi_host_write_pix_attr_reg(0x00C3); + return; + +} + +void mddi_sharp_sub_lcd_init(void) +{ + + mddi_queue_register_write(REG_SYSCTL, 0x4000, FALSE, 0); + mddi_queue_register_write(REG_SYSCTL, 0x0000, TRUE, 0); + mddi_wait(100); + + mddi_queue_register_write(REG_SYSCTL, 0x0001, FALSE, 0); + mddi_queue_register_write(REG_CLKDIV1, 0x000b, FALSE, 0); + mddi_queue_register_write(REG_CLKCNF, 0x0708, FALSE, 0); + mddi_queue_register_write(REG_SYSCTL, 0x0201, FALSE, 0); + mddi_queue_register_write(REG_PTGCTL, 0x0010, FALSE, 0); + mddi_queue_register_write(REG_PTHP, 4, FALSE, 0); + mddi_queue_register_write(REG_PTHB, 40, FALSE, 0); + mddi_queue_register_write(REG_PTHW, 128, FALSE, 0); + mddi_queue_register_write(REG_PTHF, 92, FALSE, 0); + mddi_queue_register_write(REG_PTVP, 1, FALSE, 0); + mddi_queue_register_write(REG_PTVB, 2, FALSE, 0); + mddi_queue_register_write(REG_PTVW, 128, FALSE, 0); + mddi_queue_register_write(REG_PTVF, 15, FALSE, 0); + + /* Now the sub display..... */ + /* Reset High */ + mddi_queue_register_write(REG_SUBCTL, 0x0200, FALSE, 0); + /* CS=1,RD=1,WE=1,RS=1 */ + mddi_queue_register_write(REG_SUBTCMD, 0x000f, TRUE, 0); + mddi_wait(1); + /* Wait 5us */ + + if (sharp_subpanel_type == SHARP_SUB_UNKNOWN) { + uint32 data; + + sub_through_write(1, 0x05); + sub_through_write(1, 0x6A); + sub_through_write(1, 0x1D); + sub_through_write(1, 0x05); + data = sub_through_read(1); + if (data == 0x6A) { + sharp_subpanel_type = SHARP_SUB_HYNIX; + } else { + sub_through_write(0, 0x36); + sub_through_write(1, 0xA8); + sub_through_write(0, 0x09); + data = sub_through_read(1); + data = sub_through_read(1); + if (data == 0x54) { + sub_through_write(0, 0x36); + sub_through_write(1, 0x00); + sharp_subpanel_type = SHARP_SUB_ROHM; + } + } + } + + if (sharp_subpanel_type == SHARP_SUB_HYNIX) { + sub_through_write(1, 0x00); /* Display setting 1 */ + sub_through_write(1, 0x04); + sub_through_write(1, 0x01); + sub_through_write(1, 0x05); + sub_through_write(1, 0x0280); + sub_through_write(1, 0x0301); + sub_through_write(1, 0x0402); + sub_through_write(1, 0x0500); + sub_through_write(1, 0x0681); + sub_through_write(1, 0x077F); + sub_through_write(1, 0x08C0); + sub_through_write(1, 0x0905); + sub_through_write(1, 0x0A02); + sub_through_write(1, 0x0B00); + sub_through_write(1, 0x0C00); + sub_through_write(1, 0x0D00); + sub_through_write(1, 0x0E00); + sub_through_write(1, 0x0F00); + + sub_through_write(1, 0x100B); /* Display setting 2 */ + sub_through_write(1, 0x1103); + sub_through_write(1, 0x1237); + sub_through_write(1, 0x1300); + sub_through_write(1, 0x1400); + sub_through_write(1, 0x1500); + sub_through_write(1, 0x1605); + sub_through_write(1, 0x1700); + sub_through_write(1, 0x1800); + sub_through_write(1, 0x192E); + sub_through_write(1, 0x1A00); + sub_through_write(1, 0x1B00); + sub_through_write(1, 0x1C00); + + sub_through_write(1, 0x151A); /* Power setting */ + + sub_through_write(1, 0x2002); /* Gradation Palette setting */ + sub_through_write(1, 0x2107); + sub_through_write(1, 0x220C); + sub_through_write(1, 0x2310); + sub_through_write(1, 0x2414); + sub_through_write(1, 0x2518); + sub_through_write(1, 0x261C); + sub_through_write(1, 0x2720); + sub_through_write(1, 0x2824); + sub_through_write(1, 0x2928); + sub_through_write(1, 0x2A2B); + sub_through_write(1, 0x2B2E); + sub_through_write(1, 0x2C31); + sub_through_write(1, 0x2D34); + sub_through_write(1, 0x2E37); + sub_through_write(1, 0x2F3A); + sub_through_write(1, 0x303C); + sub_through_write(1, 0x313E); + sub_through_write(1, 0x323F); + sub_through_write(1, 0x3340); + sub_through_write(1, 0x3441); + sub_through_write(1, 0x3543); + sub_through_write(1, 0x3646); + sub_through_write(1, 0x3749); + sub_through_write(1, 0x384C); + sub_through_write(1, 0x394F); + sub_through_write(1, 0x3A52); + sub_through_write(1, 0x3B59); + sub_through_write(1, 0x3C60); + sub_through_write(1, 0x3D67); + sub_through_write(1, 0x3E6E); + sub_through_write(1, 0x3F7F); + sub_through_write(1, 0x4001); + sub_through_write(1, 0x4107); + sub_through_write(1, 0x420C); + sub_through_write(1, 0x4310); + sub_through_write(1, 0x4414); + sub_through_write(1, 0x4518); + sub_through_write(1, 0x461C); + sub_through_write(1, 0x4720); + sub_through_write(1, 0x4824); + sub_through_write(1, 0x4928); + sub_through_write(1, 0x4A2B); + sub_through_write(1, 0x4B2E); + sub_through_write(1, 0x4C31); + sub_through_write(1, 0x4D34); + sub_through_write(1, 0x4E37); + sub_through_write(1, 0x4F3A); + sub_through_write(1, 0x503C); + sub_through_write(1, 0x513E); + sub_through_write(1, 0x523F); + sub_through_write(1, 0x5340); + sub_through_write(1, 0x5441); + sub_through_write(1, 0x5543); + sub_through_write(1, 0x5646); + sub_through_write(1, 0x5749); + sub_through_write(1, 0x584C); + sub_through_write(1, 0x594F); + sub_through_write(1, 0x5A52); + sub_through_write(1, 0x5B59); + sub_through_write(1, 0x5C60); + sub_through_write(1, 0x5D67); + sub_through_write(1, 0x5E6E); + sub_through_write(1, 0x5F7E); + sub_through_write(1, 0x6000); + sub_through_write(1, 0x6107); + sub_through_write(1, 0x620C); + sub_through_write(1, 0x6310); + sub_through_write(1, 0x6414); + sub_through_write(1, 0x6518); + sub_through_write(1, 0x661C); + sub_through_write(1, 0x6720); + sub_through_write(1, 0x6824); + sub_through_write(1, 0x6928); + sub_through_write(1, 0x6A2B); + sub_through_write(1, 0x6B2E); + sub_through_write(1, 0x6C31); + sub_through_write(1, 0x6D34); + sub_through_write(1, 0x6E37); + sub_through_write(1, 0x6F3A); + sub_through_write(1, 0x703C); + sub_through_write(1, 0x713E); + sub_through_write(1, 0x723F); + sub_through_write(1, 0x7340); + sub_through_write(1, 0x7441); + sub_through_write(1, 0x7543); + sub_through_write(1, 0x7646); + sub_through_write(1, 0x7749); + sub_through_write(1, 0x784C); + sub_through_write(1, 0x794F); + sub_through_write(1, 0x7A52); + sub_through_write(1, 0x7B59); + sub_through_write(1, 0x7C60); + sub_through_write(1, 0x7D67); + sub_through_write(1, 0x7E6E); + sub_through_write(1, 0x7F7D); + + sub_through_write(1, 0x1851); /* Display on */ + + mddi_queue_register_write(REG_AGM, 0x0000, TRUE, 0); + + /* 1 pixel / 1 post clock */ + mddi_queue_register_write(REG_CLKDIV2, 0x3b00, FALSE, 0); + + /* SUB LCD select */ + mddi_queue_register_write(REG_PSTCTL2, 0x0080, FALSE, 0); + + /* RS=0,command initiate number=0,select master mode */ + mddi_queue_register_write(REG_SUBCTL, 0x0202, FALSE, 0); + + /* Sub LCD Data transform start */ + mddi_queue_register_write(REG_PSTCTL1, 0x0003, FALSE, 0); + + } else if (sharp_subpanel_type == SHARP_SUB_ROHM) { + + sub_through_write(0, 0x01); /* Display setting */ + sub_through_write(1, 0x00); + + mddi_wait(1); + /* Wait 100us <----- ******* Update 2005/01/24 */ + + sub_through_write(0, 0xB6); + sub_through_write(1, 0x0C); + sub_through_write(1, 0x4A); + sub_through_write(1, 0x20); + sub_through_write(0, 0x3A); + sub_through_write(1, 0x05); + sub_through_write(0, 0xB7); + sub_through_write(1, 0x01); + sub_through_write(0, 0xBA); + sub_through_write(1, 0x20); + sub_through_write(1, 0x02); + sub_through_write(0, 0x25); + sub_through_write(1, 0x4F); + sub_through_write(0, 0xBB); + sub_through_write(1, 0x00); + sub_through_write(0, 0x36); + sub_through_write(1, 0x00); + sub_through_write(0, 0xB1); + sub_through_write(1, 0x05); + sub_through_write(0, 0xBE); + sub_through_write(1, 0x80); + sub_through_write(0, 0x26); + sub_through_write(1, 0x01); + sub_through_write(0, 0x2A); + sub_through_write(1, 0x02); + sub_through_write(1, 0x81); + sub_through_write(0, 0x2B); + sub_through_write(1, 0x00); + sub_through_write(1, 0x7F); + + sub_through_write(0, 0x2C); + sub_through_write(0, 0x11); /* Sleep mode off */ + + mddi_wait(1); + /* Wait 100 ms <----- ******* Update 2005/01/24 */ + + sub_through_write(0, 0x29); /* Display on */ + sub_through_write(0, 0xB3); + sub_through_write(1, 0x20); + sub_through_write(1, 0xAA); + sub_through_write(1, 0xA0); + sub_through_write(1, 0x20); + sub_through_write(1, 0x30); + sub_through_write(1, 0xA6); + sub_through_write(1, 0xFF); + sub_through_write(1, 0x9A); + sub_through_write(1, 0x9F); + sub_through_write(1, 0xAF); + sub_through_write(1, 0xBC); + sub_through_write(1, 0xCF); + sub_through_write(1, 0xDF); + sub_through_write(1, 0x20); + sub_through_write(1, 0x9C); + sub_through_write(1, 0x8A); + + sub_through_write(0, 0x002C); /* Display on */ + + /* 1 pixel / 2 post clock */ + mddi_queue_register_write(REG_CLKDIV2, 0x7b00, FALSE, 0); + + /* SUB LCD select */ + mddi_queue_register_write(REG_PSTCTL2, 0x0080, FALSE, 0); + + /* RS=1,command initiate number=0,select master mode */ + mddi_queue_register_write(REG_SUBCTL, 0x0242, FALSE, 0); + + /* Sub LCD Data transform start */ + mddi_queue_register_write(REG_PSTCTL1, 0x0003, FALSE, 0); + + } + + /* Set the MDP pixel data attributes for Sub Display */ + mddi_host_write_pix_attr_reg(0x00C0); +} + +void mddi_sharp_lcd_vsync_detected(boolean detected) +{ + /* static timetick_type start_time = 0; */ + static struct timeval start_time; + static boolean first_time = TRUE; + /* uint32 mdp_cnt_val = 0; */ + /* timetick_type elapsed_us; */ + struct timeval now; + uint32 elapsed_us; + uint32 num_vsyncs; + + if ((detected) || (mddi_sharp_vsync_attempts > 5)) { + if ((detected) && (mddi_sharp_monitor_refresh_value)) { + /* if (start_time != 0) */ + if (!first_time) { + jiffies_to_timeval(jiffies, &now); + elapsed_us = + (now.tv_sec - start_time.tv_sec) * 1000000 + + now.tv_usec - start_time.tv_usec; + /* + * LCD is configured for a refresh every usecs, + * so to determine the number of vsyncs that + * have occurred since the last measurement add + * half that to the time difference and divide + * by the refresh rate. + */ + num_vsyncs = (elapsed_us + + (mddi_sharp_usecs_per_refresh >> + 1)) / + mddi_sharp_usecs_per_refresh; + /* + * LCD is configured for * hsyncs (rows) per + * refresh cycle. Calculate new rows_per_second + * value based upon these new measurements. + * MDP can update with this new value. + */ + mddi_sharp_rows_per_second = + (mddi_sharp_rows_per_refresh * 1000 * + num_vsyncs) / (elapsed_us / 1000); + } + /* start_time = timetick_get(); */ + first_time = FALSE; + jiffies_to_timeval(jiffies, &start_time); + if (mddi_sharp_report_refresh_measurements) { + /* mdp_cnt_val = MDP_LINE_COUNT; */ + } + } + /* if detected = TRUE, client initiated wakeup was detected */ + if (mddi_sharp_vsync_handler != NULL) { + (*mddi_sharp_vsync_handler) + (mddi_sharp_vsync_handler_arg); + mddi_sharp_vsync_handler = NULL; + } + mddi_vsync_detect_enabled = FALSE; + mddi_sharp_vsync_attempts = 0; + /* need to clear this vsync wakeup */ + if (!mddi_queue_register_write_int(REG_INTR, 0x0000)) { + MDDI_MSG_ERR("Vsync interrupt clear failed!\n"); + } + if (!detected) { + /* give up after 5 failed attempts but show error */ + MDDI_MSG_NOTICE("Vsync detection failed!\n"); + } else if ((mddi_sharp_monitor_refresh_value) && + (mddi_sharp_report_refresh_measurements)) { + MDDI_MSG_NOTICE(" Lines Per Second=%d!\n", + mddi_sharp_rows_per_second); + } + } else + /* if detected = FALSE, we woke up from hibernation, but did not + * detect client initiated wakeup. + */ + mddi_sharp_vsync_attempts++; +} + +/* ISR to be executed */ +void mddi_sharp_vsync_set_handler(msm_fb_vsync_handler_type handler, void *arg) +{ + boolean error = FALSE; + unsigned long flags; + + /* Disable interrupts */ + spin_lock_irqsave(&mddi_host_spin_lock, flags); + /* INTLOCK(); */ + + if (mddi_sharp_vsync_handler != NULL) + error = TRUE; + + /* Register the handler for this particular GROUP interrupt source */ + mddi_sharp_vsync_handler = handler; + mddi_sharp_vsync_handler_arg = arg; + + /* Restore interrupts */ + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + /* INTFREE(); */ + + if (error) + MDDI_MSG_ERR("MDDI: Previous Vsync handler never called\n"); + + /* Enable the vsync wakeup */ + mddi_queue_register_write(REG_INTR, 0x8100, FALSE, 0); + + mddi_sharp_vsync_attempts = 1; + mddi_vsync_detect_enabled = TRUE; +} /* mddi_sharp_vsync_set_handler */ + +static int mddi_sharp_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + mddi_host_client_cnt_reset(); + + if (mfd->panel.id == SHARP_QVGA_PRIM) + mddi_sharp_prim_lcd_init(); + else + mddi_sharp_sub_lcd_init(); + + return 0; +} + +static int mddi_sharp_lcd_off(struct platform_device *pdev) +{ + if (mddi_sharp_vsync_handler != NULL) { + (*mddi_sharp_vsync_handler) + (mddi_sharp_vsync_handler_arg); + mddi_sharp_vsync_handler = NULL; + printk(KERN_INFO "%s: clean up vsyn_handler=%x\n", __func__, + (int)mddi_sharp_vsync_handler); + } + + mddi_sharp_lcd_powerdown(); + return 0; +} + +static int __devinit mddi_sharp_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + mddi_sharp_pdata = pdev->dev.platform_data; + return 0; + } + + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = mddi_sharp_probe, + .driver = { + .name = "mddi_sharp_qvga", + }, +}; + +static struct msm_fb_panel_data mddi_sharp_panel_data0 = { + .on = mddi_sharp_lcd_on, + .off = mddi_sharp_lcd_off, + .set_backlight = mddi_sharp_lcd_set_backlight, + .set_vsync_notifier = mddi_sharp_vsync_set_handler, +}; + +static struct platform_device this_device_0 = { + .name = "mddi_sharp_qvga", + .id = SHARP_QVGA_PRIM, + .dev = { + .platform_data = &mddi_sharp_panel_data0, + } +}; + +static struct msm_fb_panel_data mddi_sharp_panel_data1 = { + .on = mddi_sharp_lcd_on, + .off = mddi_sharp_lcd_off, +}; + +static struct platform_device this_device_1 = { + .name = "mddi_sharp_qvga", + .id = SHARP_128X128_SECD, + .dev = { + .platform_data = &mddi_sharp_panel_data1, + } +}; + +static int __init mddi_sharp_init(void) +{ + int ret; + struct msm_panel_info *pinfo; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + u32 id; + + ret = msm_fb_detect_client("mddi_sharp_qvga"); + if (ret == -ENODEV) + return 0; + + if (ret) { + id = mddi_get_client_id(); + + if (((id >> 16) != 0x0) || ((id & 0xffff) != 0x8835)) + return 0; + } +#endif + if (mddi_host_core_version > 8) { + /* can use faster refresh with newer hw revisions */ + mddi_sharp_debug_60hz_refresh = TRUE; + + /* Timing variables for tracking vsync */ + /* dot_clock = 6.00MHz + * horizontal count = 296 + * vertical count = 338 + * refresh rate = 6000000/(296+338) = 60Hz + */ + mddi_sharp_rows_per_second = 20270; /* 6000000/296 */ + mddi_sharp_rows_per_refresh = 338; + mddi_sharp_usecs_per_refresh = 16674; /* (296+338)/6000000 */ + } else { + /* Timing variables for tracking vsync */ + /* dot_clock = 5.20MHz + * horizontal count = 376 + * vertical count = 338 + * refresh rate = 5200000/(376+338) = 41Hz + */ + mddi_sharp_rows_per_second = 13830; /* 5200000/376 */ + mddi_sharp_rows_per_refresh = 338; + mddi_sharp_usecs_per_refresh = 24440; /* (376+338)/5200000 */ + } + + ret = platform_driver_register(&this_driver); + if (!ret) { + pinfo = &mddi_sharp_panel_data0.panel_info; + pinfo->xres = 240; + pinfo->yres = 320; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = MDDI_PANEL; + pinfo->pdest = DISPLAY_1; + pinfo->mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->fb_num = 2; + pinfo->clk_rate = 122880000; + pinfo->clk_min = 120000000; + pinfo->clk_max = 125000000; + pinfo->lcd.vsync_enable = TRUE; + pinfo->lcd.refx100 = + (mddi_sharp_rows_per_second * 100) / + mddi_sharp_rows_per_refresh; + pinfo->lcd.v_back_porch = 12; + pinfo->lcd.v_front_porch = 6; + pinfo->lcd.v_pulse_width = 0; + pinfo->lcd.hw_vsync_mode = FALSE; + pinfo->lcd.vsync_notifier_period = (1 * HZ); + pinfo->bl_max = 7; + pinfo->bl_min = 1; + + ret = platform_device_register(&this_device_0); + if (ret) + platform_driver_unregister(&this_driver); + + pinfo = &mddi_sharp_panel_data1.panel_info; + pinfo->xres = 128; + pinfo->yres = 128; + MSM_FB_SINGLE_MODE_PANEL(pinfo); + pinfo->type = MDDI_PANEL; + pinfo->pdest = DISPLAY_2; + pinfo->mddi.vdopkt = 0x400; + pinfo->wait_cycle = 0; + pinfo->bpp = 18; + pinfo->clk_rate = 122880000; + pinfo->clk_min = 120000000; + pinfo->clk_max = 125000000; + pinfo->fb_num = 2; + + ret = platform_device_register(&this_device_1); + if (ret) { + platform_device_unregister(&this_device_0); + platform_driver_unregister(&this_driver); + } + } + + if (!ret) + mddi_lcd.vsync_detected = mddi_sharp_lcd_vsync_detected; + + return ret; +} + +module_init(mddi_sharp_init); diff --git a/drivers/video/msm/mddi_toshiba.c b/drivers/video/msm/mddi_toshiba.c new file mode 100644 index 00000000000..9727453218f --- /dev/null +++ b/drivers/video/msm/mddi_toshiba.c @@ -0,0 +1,1753 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" +#include "mddi_toshiba.h" + +#define TM_GET_DID(id) ((id) & 0xff) +#define TM_GET_PID(id) (((id) & 0xff00)>>8) + +#define MDDI_CLIENT_CORE_BASE 0x108000 +#define LCD_CONTROL_BLOCK_BASE 0x110000 +#define SPI_BLOCK_BASE 0x120000 +#define PWM_BLOCK_BASE 0x140000 +#define SYSTEM_BLOCK1_BASE 0x160000 + +#define TTBUSSEL (MDDI_CLIENT_CORE_BASE|0x18) +#define DPSET0 (MDDI_CLIENT_CORE_BASE|0x1C) +#define DPSET1 (MDDI_CLIENT_CORE_BASE|0x20) +#define DPSUS (MDDI_CLIENT_CORE_BASE|0x24) +#define DPRUN (MDDI_CLIENT_CORE_BASE|0x28) +#define SYSCKENA (MDDI_CLIENT_CORE_BASE|0x2C) + +#define BITMAP0 (MDDI_CLIENT_CORE_BASE|0x44) +#define BITMAP1 (MDDI_CLIENT_CORE_BASE|0x48) +#define BITMAP2 (MDDI_CLIENT_CORE_BASE|0x4C) +#define BITMAP3 (MDDI_CLIENT_CORE_BASE|0x50) +#define BITMAP4 (MDDI_CLIENT_CORE_BASE|0x54) + +#define SRST (LCD_CONTROL_BLOCK_BASE|0x00) +#define PORT_ENB (LCD_CONTROL_BLOCK_BASE|0x04) +#define START (LCD_CONTROL_BLOCK_BASE|0x08) +#define PORT (LCD_CONTROL_BLOCK_BASE|0x0C) + +#define INTFLG (LCD_CONTROL_BLOCK_BASE|0x18) +#define INTMSK (LCD_CONTROL_BLOCK_BASE|0x1C) +#define MPLFBUF (LCD_CONTROL_BLOCK_BASE|0x20) + +#define PXL (LCD_CONTROL_BLOCK_BASE|0x30) +#define HCYCLE (LCD_CONTROL_BLOCK_BASE|0x34) +#define HSW (LCD_CONTROL_BLOCK_BASE|0x38) +#define HDE_START (LCD_CONTROL_BLOCK_BASE|0x3C) +#define HDE_SIZE (LCD_CONTROL_BLOCK_BASE|0x40) +#define VCYCLE (LCD_CONTROL_BLOCK_BASE|0x44) +#define VSW (LCD_CONTROL_BLOCK_BASE|0x48) +#define VDE_START (LCD_CONTROL_BLOCK_BASE|0x4C) +#define VDE_SIZE (LCD_CONTROL_BLOCK_BASE|0x50) +#define WAKEUP (LCD_CONTROL_BLOCK_BASE|0x54) +#define REGENB (LCD_CONTROL_BLOCK_BASE|0x5C) +#define VSYNIF (LCD_CONTROL_BLOCK_BASE|0x60) +#define WRSTB (LCD_CONTROL_BLOCK_BASE|0x64) +#define RDSTB (LCD_CONTROL_BLOCK_BASE|0x68) +#define ASY_DATA (LCD_CONTROL_BLOCK_BASE|0x6C) +#define ASY_DATB (LCD_CONTROL_BLOCK_BASE|0x70) +#define ASY_DATC (LCD_CONTROL_BLOCK_BASE|0x74) +#define ASY_DATD (LCD_CONTROL_BLOCK_BASE|0x78) +#define ASY_DATE (LCD_CONTROL_BLOCK_BASE|0x7C) +#define ASY_DATF (LCD_CONTROL_BLOCK_BASE|0x80) +#define ASY_DATG (LCD_CONTROL_BLOCK_BASE|0x84) +#define ASY_DATH (LCD_CONTROL_BLOCK_BASE|0x88) +#define ASY_CMDSET (LCD_CONTROL_BLOCK_BASE|0x8C) +#define MONI (LCD_CONTROL_BLOCK_BASE|0xB0) +#define VPOS (LCD_CONTROL_BLOCK_BASE|0xC0) + +#define SSICTL (SPI_BLOCK_BASE|0x00) +#define SSITIME (SPI_BLOCK_BASE|0x04) +#define SSITX (SPI_BLOCK_BASE|0x08) +#define SSIINTS (SPI_BLOCK_BASE|0x14) + +#define TIMER0LOAD (PWM_BLOCK_BASE|0x00) +#define TIMER0CTRL (PWM_BLOCK_BASE|0x08) +#define PWM0OFF (PWM_BLOCK_BASE|0x1C) +#define TIMER1LOAD (PWM_BLOCK_BASE|0x20) +#define TIMER1CTRL (PWM_BLOCK_BASE|0x28) +#define PWM1OFF (PWM_BLOCK_BASE|0x3C) +#define TIMER2LOAD (PWM_BLOCK_BASE|0x40) +#define TIMER2CTRL (PWM_BLOCK_BASE|0x48) +#define PWM2OFF (PWM_BLOCK_BASE|0x5C) +#define PWMCR (PWM_BLOCK_BASE|0x68) + +#define GPIOIS (GPIO_BLOCK_BASE|0x08) +#define GPIOIEV (GPIO_BLOCK_BASE|0x10) +#define GPIOIC (GPIO_BLOCK_BASE|0x20) + +#define WKREQ (SYSTEM_BLOCK1_BASE|0x00) +#define CLKENB (SYSTEM_BLOCK1_BASE|0x04) +#define DRAMPWR (SYSTEM_BLOCK1_BASE|0x08) +#define INTMASK (SYSTEM_BLOCK1_BASE|0x0C) +#define CNT_DIS (SYSTEM_BLOCK1_BASE|0x10) + +typedef enum { + TOSHIBA_STATE_OFF, + TOSHIBA_STATE_PRIM_SEC_STANDBY, + TOSHIBA_STATE_PRIM_SEC_READY, + TOSHIBA_STATE_PRIM_NORMAL_MODE, + TOSHIBA_STATE_SEC_NORMAL_MODE +} mddi_toshiba_state_t; + +static uint32 mddi_toshiba_curr_vpos; +static boolean mddi_toshiba_monitor_refresh_value = FALSE; +static boolean mddi_toshiba_report_refresh_measurements = FALSE; + +boolean mddi_toshiba_61Hz_refresh = TRUE; + +/* Modifications to timing to increase refresh rate to > 60Hz. + * 20MHz dot clock. + * 646 total rows. + * 506 total columns. + * refresh rate = 61.19Hz + */ +static uint32 mddi_toshiba_rows_per_second = 39526; +static uint32 mddi_toshiba_usecs_per_refresh = 16344; +static uint32 mddi_toshiba_rows_per_refresh = 646; +extern boolean mddi_vsync_detect_enabled; + +static msm_fb_vsync_handler_type mddi_toshiba_vsync_handler; +static void *mddi_toshiba_vsync_handler_arg; +static uint16 mddi_toshiba_vsync_attempts; + +static mddi_toshiba_state_t toshiba_state = TOSHIBA_STATE_OFF; + +static struct msm_panel_common_pdata *mddi_toshiba_pdata; + +static int mddi_toshiba_lcd_on(struct platform_device *pdev); +static int mddi_toshiba_lcd_off(struct platform_device *pdev); + +static void mddi_toshiba_state_transition(mddi_toshiba_state_t a, + mddi_toshiba_state_t b) +{ + if (toshiba_state != a) { + MDDI_MSG_ERR("toshiba state trans. (%d->%d) found %d\n", a, b, + toshiba_state); + } + toshiba_state = b; +} + +#define GORDON_REG_IMGCTL1 0x10 /* Image interface control 1 */ +#define GORDON_REG_IMGCTL2 0x11 /* Image interface control 2 */ +#define GORDON_REG_IMGSET1 0x12 /* Image interface settings 1 */ +#define GORDON_REG_IMGSET2 0x13 /* Image interface settings 2 */ +#define GORDON_REG_IVBP1 0x14 /* DM0: Vert back porch */ +#define GORDON_REG_IHBP1 0x15 /* DM0: Horiz back porch */ +#define GORDON_REG_IVNUM1 0x16 /* DM0: Num of vert lines */ +#define GORDON_REG_IHNUM1 0x17 /* DM0: Num of pixels per line */ +#define GORDON_REG_IVBP2 0x18 /* DM1: Vert back porch */ +#define GORDON_REG_IHBP2 0x19 /* DM1: Horiz back porch */ +#define GORDON_REG_IVNUM2 0x1A /* DM1: Num of vert lines */ +#define GORDON_REG_IHNUM2 0x1B /* DM1: Num of pixels per line */ +#define GORDON_REG_LCDIFCTL1 0x30 /* LCD interface control 1 */ +#define GORDON_REG_VALTRAN 0x31 /* LCD IF ctl: VALTRAN sync flag */ +#define GORDON_REG_AVCTL 0x33 +#define GORDON_REG_LCDIFCTL2 0x34 /* LCD interface control 2 */ +#define GORDON_REG_LCDIFCTL3 0x35 /* LCD interface control 3 */ +#define GORDON_REG_LCDIFSET1 0x36 /* LCD interface settings 1 */ +#define GORDON_REG_PCCTL 0x3C +#define GORDON_REG_TPARAM1 0x40 +#define GORDON_REG_TLCDIF1 0x41 +#define GORDON_REG_TSSPB_ST1 0x42 +#define GORDON_REG_TSSPB_ED1 0x43 +#define GORDON_REG_TSCK_ST1 0x44 +#define GORDON_REG_TSCK_WD1 0x45 +#define GORDON_REG_TGSPB_VST1 0x46 +#define GORDON_REG_TGSPB_VED1 0x47 +#define GORDON_REG_TGSPB_CH1 0x48 +#define GORDON_REG_TGCK_ST1 0x49 +#define GORDON_REG_TGCK_ED1 0x4A +#define GORDON_REG_TPCTL_ST1 0x4B +#define GORDON_REG_TPCTL_ED1 0x4C +#define GORDON_REG_TPCHG_ED1 0x4D +#define GORDON_REG_TCOM_CH1 0x4E +#define GORDON_REG_THBP1 0x4F +#define GORDON_REG_TPHCTL1 0x50 +#define GORDON_REG_EVPH1 0x51 +#define GORDON_REG_EVPL1 0x52 +#define GORDON_REG_EVNH1 0x53 +#define GORDON_REG_EVNL1 0x54 +#define GORDON_REG_TBIAS1 0x55 +#define GORDON_REG_TPARAM2 0x56 +#define GORDON_REG_TLCDIF2 0x57 +#define GORDON_REG_TSSPB_ST2 0x58 +#define GORDON_REG_TSSPB_ED2 0x59 +#define GORDON_REG_TSCK_ST2 0x5A +#define GORDON_REG_TSCK_WD2 0x5B +#define GORDON_REG_TGSPB_VST2 0x5C +#define GORDON_REG_TGSPB_VED2 0x5D +#define GORDON_REG_TGSPB_CH2 0x5E +#define GORDON_REG_TGCK_ST2 0x5F +#define GORDON_REG_TGCK_ED2 0x60 +#define GORDON_REG_TPCTL_ST2 0x61 +#define GORDON_REG_TPCTL_ED2 0x62 +#define GORDON_REG_TPCHG_ED2 0x63 +#define GORDON_REG_TCOM_CH2 0x64 +#define GORDON_REG_THBP2 0x65 +#define GORDON_REG_TPHCTL2 0x66 +#define GORDON_REG_EVPH2 0x67 +#define GORDON_REG_EVPL2 0x68 +#define GORDON_REG_EVNH2 0x69 +#define GORDON_REG_EVNL2 0x6A +#define GORDON_REG_TBIAS2 0x6B +#define GORDON_REG_POWCTL 0x80 +#define GORDON_REG_POWOSC1 0x81 +#define GORDON_REG_POWOSC2 0x82 +#define GORDON_REG_POWSET 0x83 +#define GORDON_REG_POWTRM1 0x85 +#define GORDON_REG_POWTRM2 0x86 +#define GORDON_REG_POWTRM3 0x87 +#define GORDON_REG_POWTRMSEL 0x88 +#define GORDON_REG_POWHIZ 0x89 + +void serigo(uint16 reg, uint8 data) +{ + uint32 mddi_val = 0; + mddi_queue_register_read(SSIINTS, &mddi_val, TRUE, 0); + if (mddi_val & (1 << 8)) + mddi_wait(1); + /* No De-assert of CS and send 2 bytes */ + mddi_val = 0x90000 | ((0x00FF & reg) << 8) | data; + mddi_queue_register_write(SSITX, mddi_val, TRUE, 0); +} + +void gordon_init(void) +{ + /* Image interface settings ***/ + serigo(GORDON_REG_IMGCTL2, 0x00); + serigo(GORDON_REG_IMGSET1, 0x01); + + /* Exchange the RGB signal for J510(Softbank mobile) */ + serigo(GORDON_REG_IMGSET2, 0x12); + serigo(GORDON_REG_LCDIFSET1, 0x00); + mddi_wait(2); + + /* Pre-charge settings */ + serigo(GORDON_REG_PCCTL, 0x09); + serigo(GORDON_REG_LCDIFCTL2, 0x1B); + mddi_wait(1); +} + +void gordon_disp_on(void) +{ + /*gordon_dispmode setting */ + /*VGA settings */ + serigo(GORDON_REG_TPARAM1, 0x30); + serigo(GORDON_REG_TLCDIF1, 0x00); + serigo(GORDON_REG_TSSPB_ST1, 0x8B); + serigo(GORDON_REG_TSSPB_ED1, 0x93); + mddi_wait(2); + serigo(GORDON_REG_TSCK_ST1, 0x88); + serigo(GORDON_REG_TSCK_WD1, 0x00); + serigo(GORDON_REG_TGSPB_VST1, 0x01); + serigo(GORDON_REG_TGSPB_VED1, 0x02); + mddi_wait(2); + serigo(GORDON_REG_TGSPB_CH1, 0x5E); + serigo(GORDON_REG_TGCK_ST1, 0x80); + serigo(GORDON_REG_TGCK_ED1, 0x3C); + serigo(GORDON_REG_TPCTL_ST1, 0x50); + mddi_wait(2); + serigo(GORDON_REG_TPCTL_ED1, 0x74); + serigo(GORDON_REG_TPCHG_ED1, 0x78); + serigo(GORDON_REG_TCOM_CH1, 0x50); + serigo(GORDON_REG_THBP1, 0x84); + mddi_wait(2); + serigo(GORDON_REG_TPHCTL1, 0x00); + serigo(GORDON_REG_EVPH1, 0x70); + serigo(GORDON_REG_EVPL1, 0x64); + serigo(GORDON_REG_EVNH1, 0x56); + mddi_wait(2); + serigo(GORDON_REG_EVNL1, 0x48); + serigo(GORDON_REG_TBIAS1, 0x88); + mddi_wait(2); + serigo(GORDON_REG_TPARAM2, 0x28); + serigo(GORDON_REG_TLCDIF2, 0x14); + serigo(GORDON_REG_TSSPB_ST2, 0x49); + serigo(GORDON_REG_TSSPB_ED2, 0x4B); + mddi_wait(2); + serigo(GORDON_REG_TSCK_ST2, 0x4A); + serigo(GORDON_REG_TSCK_WD2, 0x02); + serigo(GORDON_REG_TGSPB_VST2, 0x02); + serigo(GORDON_REG_TGSPB_VED2, 0x03); + mddi_wait(2); + serigo(GORDON_REG_TGSPB_CH2, 0x2F); + serigo(GORDON_REG_TGCK_ST2, 0x40); + serigo(GORDON_REG_TGCK_ED2, 0x1E); + serigo(GORDON_REG_TPCTL_ST2, 0x2C); + mddi_wait(2); + serigo(GORDON_REG_TPCTL_ED2, 0x3A); + serigo(GORDON_REG_TPCHG_ED2, 0x3C); + serigo(GORDON_REG_TCOM_CH2, 0x28); + serigo(GORDON_REG_THBP2, 0x4D); + mddi_wait(2); + serigo(GORDON_REG_TPHCTL2, 0x1A); + mddi_wait(2); + serigo(GORDON_REG_IVBP1, 0x02); + serigo(GORDON_REG_IHBP1, 0x90); + serigo(GORDON_REG_IVNUM1, 0xA0); + serigo(GORDON_REG_IHNUM1, 0x78); + mddi_wait(2); + serigo(GORDON_REG_IVBP2, 0x02); + serigo(GORDON_REG_IHBP2, 0x48); + serigo(GORDON_REG_IVNUM2, 0x50); + serigo(GORDON_REG_IHNUM2, 0x3C); + mddi_wait(2); + serigo(GORDON_REG_POWCTL, 0x03); + mddi_wait(15); + serigo(GORDON_REG_POWCTL, 0x07); + mddi_wait(15); + serigo(GORDON_REG_POWCTL, 0x0F); + mddi_wait(15); + serigo(GORDON_REG_AVCTL, 0x03); + mddi_wait(15); + serigo(GORDON_REG_POWCTL, 0x1F); + mddi_wait(15); + serigo(GORDON_REG_POWCTL, 0x5F); + mddi_wait(15); + serigo(GORDON_REG_POWCTL, 0x7F); + mddi_wait(15); + serigo(GORDON_REG_LCDIFCTL1, 0x02); + mddi_wait(15); + serigo(GORDON_REG_IMGCTL1, 0x00); + mddi_wait(15); + serigo(GORDON_REG_LCDIFCTL3, 0x00); + mddi_wait(15); + serigo(GORDON_REG_VALTRAN, 0x01); + mddi_wait(15); + serigo(GORDON_REG_LCDIFCTL1, 0x03); + serigo(GORDON_REG_LCDIFCTL1, 0x03); + mddi_wait(1); +} + +void gordon_disp_off(void) +{ + serigo(GORDON_REG_LCDIFCTL2, 0x7B); + serigo(GORDON_REG_VALTRAN, 0x01); + serigo(GORDON_REG_LCDIFCTL1, 0x02); + serigo(GORDON_REG_LCDIFCTL3, 0x01); + mddi_wait(20); + serigo(GORDON_REG_VALTRAN, 0x01); + serigo(GORDON_REG_IMGCTL1, 0x01); + serigo(GORDON_REG_LCDIFCTL1, 0x00); + mddi_wait(20); + serigo(GORDON_REG_POWCTL, 0x1F); + mddi_wait(40); + serigo(GORDON_REG_POWCTL, 0x07); + mddi_wait(40); + serigo(GORDON_REG_POWCTL, 0x03); + mddi_wait(40); + serigo(GORDON_REG_POWCTL, 0x00); + mddi_wait(40); +} + +void gordon_disp_init(void) +{ + gordon_init(); + mddi_wait(20); + gordon_disp_on(); +} + +static void toshiba_common_initial_setup(struct msm_fb_data_type *mfd) +{ + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA_PT) { + write_client_reg(DPSET0 , 0x4bec0066, TRUE); + write_client_reg(DPSET1 , 0x00000113, TRUE); + write_client_reg(DPSUS , 0x00000000, TRUE); + write_client_reg(DPRUN , 0x00000001, TRUE); + mddi_wait(5); + write_client_reg(SYSCKENA , 0x00000001, TRUE); + write_client_reg(CLKENB , 0x0000a0e9, TRUE); + + write_client_reg(GPIODATA , 0x03FF0000, TRUE); + write_client_reg(GPIODIR , 0x0000024D, TRUE); + write_client_reg(GPIOSEL , 0x00000173, TRUE); + write_client_reg(GPIOPC , 0x03C300C0, TRUE); + write_client_reg(WKREQ , 0x00000000, TRUE); + write_client_reg(GPIOIS , 0x00000000, TRUE); + write_client_reg(GPIOIEV , 0x00000001, TRUE); + write_client_reg(GPIOIC , 0x000003FF, TRUE); + write_client_reg(GPIODATA , 0x00040004, TRUE); + + write_client_reg(GPIODATA , 0x00080008, TRUE); + write_client_reg(DRAMPWR , 0x00000001, TRUE); + write_client_reg(CLKENB , 0x0000a0eb, TRUE); + write_client_reg(PWMCR , 0x00000000, TRUE); + mddi_wait(1); + + write_client_reg(SSICTL , 0x00060399, TRUE); + write_client_reg(SSITIME , 0x00000100, TRUE); + write_client_reg(CNT_DIS , 0x00000002, TRUE); + write_client_reg(SSICTL , 0x0006039b, TRUE); + + write_client_reg(SSITX , 0x00000000, TRUE); + mddi_wait(7); + write_client_reg(SSITX , 0x00000000, TRUE); + mddi_wait(7); + write_client_reg(SSITX , 0x00000000, TRUE); + mddi_wait(7); + + write_client_reg(SSITX , 0x000800BA, TRUE); + write_client_reg(SSITX , 0x00000111, TRUE); + write_client_reg(SSITX , 0x00080036, TRUE); + write_client_reg(SSITX , 0x00000100, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x0008003A, TRUE); + write_client_reg(SSITX , 0x00000160, TRUE); + write_client_reg(SSITX , 0x000800B1, TRUE); + write_client_reg(SSITX , 0x0000015D, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800B2, TRUE); + write_client_reg(SSITX , 0x00000133, TRUE); + write_client_reg(SSITX , 0x000800B3, TRUE); + write_client_reg(SSITX , 0x00000122, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800B4, TRUE); + write_client_reg(SSITX , 0x00000102, TRUE); + write_client_reg(SSITX , 0x000800B5, TRUE); + write_client_reg(SSITX , 0x0000011E, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800B6, TRUE); + write_client_reg(SSITX , 0x00000127, TRUE); + write_client_reg(SSITX , 0x000800B7, TRUE); + write_client_reg(SSITX , 0x00000103, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800B9, TRUE); + write_client_reg(SSITX , 0x00000124, TRUE); + write_client_reg(SSITX , 0x000800BD, TRUE); + write_client_reg(SSITX , 0x000001A1, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800BB, TRUE); + write_client_reg(SSITX , 0x00000100, TRUE); + write_client_reg(SSITX , 0x000800BF, TRUE); + write_client_reg(SSITX , 0x00000101, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800BE, TRUE); + write_client_reg(SSITX , 0x00000100, TRUE); + write_client_reg(SSITX , 0x000800C0, TRUE); + write_client_reg(SSITX , 0x00000111, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800C1, TRUE); + write_client_reg(SSITX , 0x00000111, TRUE); + write_client_reg(SSITX , 0x000800C2, TRUE); + write_client_reg(SSITX , 0x00000111, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800C3, TRUE); + write_client_reg(SSITX , 0x00080132, TRUE); + write_client_reg(SSITX , 0x00000132, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800C4, TRUE); + write_client_reg(SSITX , 0x00080132, TRUE); + write_client_reg(SSITX , 0x00000132, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800C5, TRUE); + write_client_reg(SSITX , 0x00080132, TRUE); + write_client_reg(SSITX , 0x00000132, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800C6, TRUE); + write_client_reg(SSITX , 0x00080132, TRUE); + write_client_reg(SSITX , 0x00000132, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800C7, TRUE); + write_client_reg(SSITX , 0x00080164, TRUE); + write_client_reg(SSITX , 0x00000145, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800C8, TRUE); + write_client_reg(SSITX , 0x00000144, TRUE); + write_client_reg(SSITX , 0x000800C9, TRUE); + write_client_reg(SSITX , 0x00000152, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800CA, TRUE); + write_client_reg(SSITX , 0x00000100, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800EC, TRUE); + write_client_reg(SSITX , 0x00080101, TRUE); + write_client_reg(SSITX , 0x000001FC, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800CF, TRUE); + write_client_reg(SSITX , 0x00000101, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800D0, TRUE); + write_client_reg(SSITX , 0x00080110, TRUE); + write_client_reg(SSITX , 0x00000104, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800D1, TRUE); + write_client_reg(SSITX , 0x00000101, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800D2, TRUE); + write_client_reg(SSITX , 0x00080100, TRUE); + write_client_reg(SSITX , 0x00000128, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800D3, TRUE); + write_client_reg(SSITX , 0x00080100, TRUE); + write_client_reg(SSITX , 0x00000128, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800D4, TRUE); + write_client_reg(SSITX , 0x00080126, TRUE); + write_client_reg(SSITX , 0x000001A4, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800D5, TRUE); + write_client_reg(SSITX , 0x00000120, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800EF, TRUE); + write_client_reg(SSITX , 0x00080132, TRUE); + write_client_reg(SSITX , 0x00000100, TRUE); + mddi_wait(1); + + write_client_reg(BITMAP0 , 0x032001E0, TRUE); + write_client_reg(BITMAP1 , 0x032001E0, TRUE); + write_client_reg(BITMAP2 , 0x014000F0, TRUE); + write_client_reg(BITMAP3 , 0x014000F0, TRUE); + write_client_reg(BITMAP4 , 0x014000F0, TRUE); + write_client_reg(CLKENB , 0x0000A1EB, TRUE); + write_client_reg(PORT_ENB , 0x00000001, TRUE); + write_client_reg(PORT , 0x00000004, TRUE); + write_client_reg(PXL , 0x00000002, TRUE); + write_client_reg(MPLFBUF , 0x00000000, TRUE); + write_client_reg(HCYCLE , 0x000000FD, TRUE); + write_client_reg(HSW , 0x00000003, TRUE); + write_client_reg(HDE_START , 0x00000007, TRUE); + write_client_reg(HDE_SIZE , 0x000000EF, TRUE); + write_client_reg(VCYCLE , 0x00000325, TRUE); + write_client_reg(VSW , 0x00000001, TRUE); + write_client_reg(VDE_START , 0x00000003, TRUE); + write_client_reg(VDE_SIZE , 0x0000031F, TRUE); + write_client_reg(START , 0x00000001, TRUE); + mddi_wait(32); + write_client_reg(SSITX , 0x000800BC, TRUE); + write_client_reg(SSITX , 0x00000180, TRUE); + write_client_reg(SSITX , 0x0008003B, TRUE); + write_client_reg(SSITX , 0x00000100, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800B0, TRUE); + write_client_reg(SSITX , 0x00000116, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x000800B8, TRUE); + write_client_reg(SSITX , 0x000801FF, TRUE); + write_client_reg(SSITX , 0x000001F5, TRUE); + mddi_wait(1); + write_client_reg(SSITX , 0x00000011, TRUE); + mddi_wait(5); + write_client_reg(SSITX , 0x00000029, TRUE); + return; + } + + if (TM_GET_PID(mfd->panel.id) == LCD_SHARP_2P4_VGA) { + write_client_reg(DPSET0, 0x4BEC0066, TRUE); + write_client_reg(DPSET1, 0x00000113, TRUE); + write_client_reg(DPSUS, 0x00000000, TRUE); + write_client_reg(DPRUN, 0x00000001, TRUE); + mddi_wait(14); + write_client_reg(SYSCKENA, 0x00000001, TRUE); + write_client_reg(CLKENB, 0x000000EF, TRUE); + write_client_reg(GPIO_BLOCK_BASE, 0x03FF0000, TRUE); + write_client_reg(GPIODIR, 0x0000024D, TRUE); + write_client_reg(SYSTEM_BLOCK2_BASE, 0x00000173, TRUE); + write_client_reg(GPIOPC, 0x03C300C0, TRUE); + write_client_reg(SYSTEM_BLOCK1_BASE, 0x00000000, TRUE); + write_client_reg(GPIOIS, 0x00000000, TRUE); + write_client_reg(GPIOIEV, 0x00000001, TRUE); + write_client_reg(GPIOIC, 0x000003FF, TRUE); + write_client_reg(GPIO_BLOCK_BASE, 0x00060006, TRUE); + write_client_reg(GPIO_BLOCK_BASE, 0x00080008, TRUE); + write_client_reg(GPIO_BLOCK_BASE, 0x02000200, TRUE); + write_client_reg(DRAMPWR, 0x00000001, TRUE); + write_client_reg(TIMER0CTRL, 0x00000060, TRUE); + write_client_reg(PWM_BLOCK_BASE, 0x00001388, TRUE); + write_client_reg(PWM0OFF, 0x00001387, TRUE); + write_client_reg(TIMER1CTRL, 0x00000060, TRUE); + write_client_reg(TIMER1LOAD, 0x00001388, TRUE); + write_client_reg(PWM1OFF, 0x00001387, TRUE); + write_client_reg(TIMER0CTRL, 0x000000E0, TRUE); + write_client_reg(TIMER1CTRL, 0x000000E0, TRUE); + write_client_reg(PWMCR, 0x00000003, TRUE); + mddi_wait(1); + write_client_reg(SPI_BLOCK_BASE, 0x00063111, TRUE); + write_client_reg(SSITIME, 0x00000100, TRUE); + write_client_reg(SPI_BLOCK_BASE, 0x00063113, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x00000000, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x00000000, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x00000000, TRUE); + mddi_wait(1); + write_client_reg(CLKENB, 0x0000A1EF, TRUE); + write_client_reg(START, 0x00000000, TRUE); + write_client_reg(WRSTB, 0x0000003F, TRUE); + write_client_reg(RDSTB, 0x00000432, TRUE); + write_client_reg(PORT_ENB, 0x00000002, TRUE); + write_client_reg(VSYNIF, 0x00000000, TRUE); + write_client_reg(ASY_DATA, 0x80000000, TRUE); + write_client_reg(ASY_DATB, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(10); + write_client_reg(ASY_DATA, 0x80000000, TRUE); + write_client_reg(ASY_DATB, 0x80000000, TRUE); + write_client_reg(ASY_DATC, 0x80000000, TRUE); + write_client_reg(ASY_DATD, 0x80000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000009, TRUE); + write_client_reg(ASY_CMDSET, 0x00000008, TRUE); + write_client_reg(ASY_DATA, 0x80000007, TRUE); + write_client_reg(ASY_DATB, 0x00004005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(20); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + + write_client_reg(VSYNIF, 0x00000001, TRUE); + write_client_reg(PORT_ENB, 0x00000001, TRUE); + } else { + write_client_reg(DPSET0, 0x4BEC0066, TRUE); + write_client_reg(DPSET1, 0x00000113, TRUE); + write_client_reg(DPSUS, 0x00000000, TRUE); + write_client_reg(DPRUN, 0x00000001, TRUE); + mddi_wait(14); + write_client_reg(SYSCKENA, 0x00000001, TRUE); + write_client_reg(CLKENB, 0x000000EF, TRUE); + write_client_reg(GPIODATA, 0x03FF0000, TRUE); + write_client_reg(GPIODIR, 0x0000024D, TRUE); + write_client_reg(GPIOSEL, 0x00000173, TRUE); + write_client_reg(GPIOPC, 0x03C300C0, TRUE); + write_client_reg(WKREQ, 0x00000000, TRUE); + write_client_reg(GPIOIS, 0x00000000, TRUE); + write_client_reg(GPIOIEV, 0x00000001, TRUE); + write_client_reg(GPIOIC, 0x000003FF, TRUE); + write_client_reg(GPIODATA, 0x00060006, TRUE); + write_client_reg(GPIODATA, 0x00080008, TRUE); + write_client_reg(GPIODATA, 0x02000200, TRUE); + + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA) { + mddi_wait(400); + write_client_reg(DRAMPWR, 0x00000001, TRUE); + + write_client_reg(CNT_DIS, 0x00000002, TRUE); + write_client_reg(BITMAP0, 0x01E00320, TRUE); + write_client_reg(PORT_ENB, 0x00000001, TRUE); + write_client_reg(PORT, 0x00000004, TRUE); + write_client_reg(PXL, 0x0000003A, TRUE); + write_client_reg(MPLFBUF, 0x00000000, TRUE); + write_client_reg(HCYCLE, 0x00000253, TRUE); + write_client_reg(HSW, 0x00000003, TRUE); + write_client_reg(HDE_START, 0x00000017, TRUE); + write_client_reg(HDE_SIZE, 0x0000018F, TRUE); + write_client_reg(VCYCLE, 0x000001FF, TRUE); + write_client_reg(VSW, 0x00000001, TRUE); + write_client_reg(VDE_START, 0x00000003, TRUE); + write_client_reg(VDE_SIZE, 0x000001DF, TRUE); + write_client_reg(START, 0x00000001, TRUE); + mddi_wait(1); + write_client_reg(TIMER0CTRL, 0x00000060, TRUE); + write_client_reg(TIMER0LOAD, 0x00001388, TRUE); + write_client_reg(TIMER1CTRL, 0x00000060, TRUE); + write_client_reg(TIMER1LOAD, 0x00001388, TRUE); + write_client_reg(PWM1OFF, 0x00000087, TRUE); + } else { + write_client_reg(DRAMPWR, 0x00000001, TRUE); + write_client_reg(TIMER0CTRL, 0x00000060, TRUE); + write_client_reg(TIMER0LOAD, 0x00001388, TRUE); + write_client_reg(TIMER1CTRL, 0x00000060, TRUE); + write_client_reg(TIMER1LOAD, 0x00001388, TRUE); + write_client_reg(PWM1OFF, 0x00001387, TRUE); + } + + write_client_reg(TIMER0CTRL, 0x000000E0, TRUE); + write_client_reg(TIMER1CTRL, 0x000000E0, TRUE); + write_client_reg(PWMCR, 0x00000003, TRUE); + mddi_wait(1); + write_client_reg(SSICTL, 0x00000799, TRUE); + write_client_reg(SSITIME, 0x00000100, TRUE); + write_client_reg(SSICTL, 0x0000079b, TRUE); + write_client_reg(SSITX, 0x00000000, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x00000000, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x00000000, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x000800BA, TRUE); + write_client_reg(SSITX, 0x00000111, TRUE); + write_client_reg(SSITX, 0x00080036, TRUE); + write_client_reg(SSITX, 0x00000100, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800BB, TRUE); + write_client_reg(SSITX, 0x00000100, TRUE); + write_client_reg(SSITX, 0x0008003A, TRUE); + write_client_reg(SSITX, 0x00000160, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800BF, TRUE); + write_client_reg(SSITX, 0x00000100, TRUE); + write_client_reg(SSITX, 0x000800B1, TRUE); + write_client_reg(SSITX, 0x0000015D, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800B2, TRUE); + write_client_reg(SSITX, 0x00000133, TRUE); + write_client_reg(SSITX, 0x000800B3, TRUE); + write_client_reg(SSITX, 0x00000122, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800B4, TRUE); + write_client_reg(SSITX, 0x00000102, TRUE); + write_client_reg(SSITX, 0x000800B5, TRUE); + write_client_reg(SSITX, 0x0000011F, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800B6, TRUE); + write_client_reg(SSITX, 0x00000128, TRUE); + write_client_reg(SSITX, 0x000800B7, TRUE); + write_client_reg(SSITX, 0x00000103, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800B9, TRUE); + write_client_reg(SSITX, 0x00000120, TRUE); + write_client_reg(SSITX, 0x000800BD, TRUE); + write_client_reg(SSITX, 0x00000102, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800BE, TRUE); + write_client_reg(SSITX, 0x00000100, TRUE); + write_client_reg(SSITX, 0x000800C0, TRUE); + write_client_reg(SSITX, 0x00000111, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800C1, TRUE); + write_client_reg(SSITX, 0x00000111, TRUE); + write_client_reg(SSITX, 0x000800C2, TRUE); + write_client_reg(SSITX, 0x00000111, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800C3, TRUE); + write_client_reg(SSITX, 0x0008010A, TRUE); + write_client_reg(SSITX, 0x0000010A, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800C4, TRUE); + write_client_reg(SSITX, 0x00080160, TRUE); + write_client_reg(SSITX, 0x00000160, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800C5, TRUE); + write_client_reg(SSITX, 0x00080160, TRUE); + write_client_reg(SSITX, 0x00000160, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800C6, TRUE); + write_client_reg(SSITX, 0x00080160, TRUE); + write_client_reg(SSITX, 0x00000160, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800C7, TRUE); + write_client_reg(SSITX, 0x00080133, TRUE); + write_client_reg(SSITX, 0x00000143, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800C8, TRUE); + write_client_reg(SSITX, 0x00000144, TRUE); + write_client_reg(SSITX, 0x000800C9, TRUE); + write_client_reg(SSITX, 0x00000133, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800CA, TRUE); + write_client_reg(SSITX, 0x00000100, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800EC, TRUE); + write_client_reg(SSITX, 0x00080102, TRUE); + write_client_reg(SSITX, 0x00000118, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800CF, TRUE); + write_client_reg(SSITX, 0x00000101, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800D0, TRUE); + write_client_reg(SSITX, 0x00080110, TRUE); + write_client_reg(SSITX, 0x00000104, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800D1, TRUE); + write_client_reg(SSITX, 0x00000101, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800D2, TRUE); + write_client_reg(SSITX, 0x00080100, TRUE); + write_client_reg(SSITX, 0x0000013A, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800D3, TRUE); + write_client_reg(SSITX, 0x00080100, TRUE); + write_client_reg(SSITX, 0x0000013A, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800D4, TRUE); + write_client_reg(SSITX, 0x00080124, TRUE); + write_client_reg(SSITX, 0x0000016E, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x000800D5, TRUE); + write_client_reg(SSITX, 0x00000124, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800ED, TRUE); + write_client_reg(SSITX, 0x00080101, TRUE); + write_client_reg(SSITX, 0x0000010A, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800D6, TRUE); + write_client_reg(SSITX, 0x00000101, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800D7, TRUE); + write_client_reg(SSITX, 0x00080110, TRUE); + write_client_reg(SSITX, 0x0000010A, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800D8, TRUE); + write_client_reg(SSITX, 0x00000101, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800D9, TRUE); + write_client_reg(SSITX, 0x00080100, TRUE); + write_client_reg(SSITX, 0x00000114, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800DE, TRUE); + write_client_reg(SSITX, 0x00080100, TRUE); + write_client_reg(SSITX, 0x00000114, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800DF, TRUE); + write_client_reg(SSITX, 0x00080112, TRUE); + write_client_reg(SSITX, 0x0000013F, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800E0, TRUE); + write_client_reg(SSITX, 0x0000010B, TRUE); + write_client_reg(SSITX, 0x000800E2, TRUE); + write_client_reg(SSITX, 0x00000101, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800E3, TRUE); + write_client_reg(SSITX, 0x00000136, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800E4, TRUE); + write_client_reg(SSITX, 0x00080100, TRUE); + write_client_reg(SSITX, 0x00000103, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800E5, TRUE); + write_client_reg(SSITX, 0x00080102, TRUE); + write_client_reg(SSITX, 0x00000104, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800E6, TRUE); + write_client_reg(SSITX, 0x00000103, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800E7, TRUE); + write_client_reg(SSITX, 0x00080104, TRUE); + write_client_reg(SSITX, 0x0000010A, TRUE); + mddi_wait(2); + write_client_reg(SSITX, 0x000800E8, TRUE); + write_client_reg(SSITX, 0x00000104, TRUE); + write_client_reg(CLKENB, 0x000001EF, TRUE); + write_client_reg(START, 0x00000000, TRUE); + write_client_reg(WRSTB, 0x0000003F, TRUE); + write_client_reg(RDSTB, 0x00000432, TRUE); + write_client_reg(PORT_ENB, 0x00000002, TRUE); + write_client_reg(VSYNIF, 0x00000000, TRUE); + write_client_reg(ASY_DATA, 0x80000000, TRUE); + write_client_reg(ASY_DATB, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(10); + write_client_reg(ASY_DATA, 0x80000000, TRUE); + write_client_reg(ASY_DATB, 0x80000000, TRUE); + write_client_reg(ASY_DATC, 0x80000000, TRUE); + write_client_reg(ASY_DATD, 0x80000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000009, TRUE); + write_client_reg(ASY_CMDSET, 0x00000008, TRUE); + write_client_reg(ASY_DATA, 0x80000007, TRUE); + write_client_reg(ASY_DATB, 0x00004005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(20); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + write_client_reg(VSYNIF, 0x00000001, TRUE); + write_client_reg(PORT_ENB, 0x00000001, TRUE); + } + + mddi_toshiba_state_transition(TOSHIBA_STATE_PRIM_SEC_STANDBY, + TOSHIBA_STATE_PRIM_SEC_READY); +} + +static void toshiba_prim_start(struct msm_fb_data_type *mfd) +{ + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA_PT) + return; + + if (TM_GET_PID(mfd->panel.id) == LCD_SHARP_2P4_VGA) { + write_client_reg(BITMAP1, 0x01E000F0, TRUE); + write_client_reg(BITMAP2, 0x01E000F0, TRUE); + write_client_reg(BITMAP3, 0x01E000F0, TRUE); + write_client_reg(BITMAP4, 0x00DC00B0, TRUE); + write_client_reg(CLKENB, 0x000001EF, TRUE); + write_client_reg(PORT_ENB, 0x00000001, TRUE); + write_client_reg(PORT, 0x00000016, TRUE); + write_client_reg(PXL, 0x00000002, TRUE); + write_client_reg(MPLFBUF, 0x00000000, TRUE); + write_client_reg(HCYCLE, 0x00000185, TRUE); + write_client_reg(HSW, 0x00000018, TRUE); + write_client_reg(HDE_START, 0x0000004A, TRUE); + write_client_reg(HDE_SIZE, 0x000000EF, TRUE); + write_client_reg(VCYCLE, 0x0000028E, TRUE); + write_client_reg(VSW, 0x00000004, TRUE); + write_client_reg(VDE_START, 0x00000009, TRUE); + write_client_reg(VDE_SIZE, 0x0000027F, TRUE); + write_client_reg(START, 0x00000001, TRUE); + write_client_reg(SYSTEM_BLOCK1_BASE, 0x00000002, TRUE); + } else{ + + write_client_reg(VSYNIF, 0x00000001, TRUE); + write_client_reg(PORT_ENB, 0x00000001, TRUE); + write_client_reg(BITMAP1, 0x01E000F0, TRUE); + write_client_reg(BITMAP2, 0x01E000F0, TRUE); + write_client_reg(BITMAP3, 0x01E000F0, TRUE); + write_client_reg(BITMAP4, 0x00DC00B0, TRUE); + write_client_reg(CLKENB, 0x000001EF, TRUE); + write_client_reg(PORT_ENB, 0x00000001, TRUE); + write_client_reg(PORT, 0x00000004, TRUE); + write_client_reg(PXL, 0x00000002, TRUE); + write_client_reg(MPLFBUF, 0x00000000, TRUE); + + if (mddi_toshiba_61Hz_refresh) { + write_client_reg(HCYCLE, 0x000000FC, TRUE); + mddi_toshiba_rows_per_second = 39526; + mddi_toshiba_rows_per_refresh = 646; + mddi_toshiba_usecs_per_refresh = 16344; + } else { + write_client_reg(HCYCLE, 0x0000010b, TRUE); + mddi_toshiba_rows_per_second = 37313; + mddi_toshiba_rows_per_refresh = 646; + mddi_toshiba_usecs_per_refresh = 17313; + } + + write_client_reg(HSW, 0x00000003, TRUE); + write_client_reg(HDE_START, 0x00000007, TRUE); + write_client_reg(HDE_SIZE, 0x000000EF, TRUE); + write_client_reg(VCYCLE, 0x00000285, TRUE); + write_client_reg(VSW, 0x00000001, TRUE); + write_client_reg(VDE_START, 0x00000003, TRUE); + write_client_reg(VDE_SIZE, 0x0000027F, TRUE); + write_client_reg(START, 0x00000001, TRUE); + mddi_wait(10); + write_client_reg(SSITX, 0x000800BC, TRUE); + write_client_reg(SSITX, 0x00000180, TRUE); + write_client_reg(SSITX, 0x0008003B, TRUE); + write_client_reg(SSITX, 0x00000100, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x000800B0, TRUE); + write_client_reg(SSITX, 0x00000116, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x000800B8, TRUE); + write_client_reg(SSITX, 0x000801FF, TRUE); + write_client_reg(SSITX, 0x000001F5, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x00000011, TRUE); + write_client_reg(SSITX, 0x00000029, TRUE); + write_client_reg(WKREQ, 0x00000000, TRUE); + write_client_reg(WAKEUP, 0x00000000, TRUE); + write_client_reg(INTMSK, 0x00000001, TRUE); + } + + mddi_toshiba_state_transition(TOSHIBA_STATE_PRIM_SEC_READY, + TOSHIBA_STATE_PRIM_NORMAL_MODE); +} + +static void toshiba_sec_start(struct msm_fb_data_type *mfd) +{ + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA_PT) + return; + + write_client_reg(VSYNIF, 0x00000000, TRUE); + write_client_reg(PORT_ENB, 0x00000002, TRUE); + write_client_reg(CLKENB, 0x000011EF, TRUE); + write_client_reg(BITMAP0, 0x028001E0, TRUE); + write_client_reg(BITMAP1, 0x00000000, TRUE); + write_client_reg(BITMAP2, 0x00000000, TRUE); + write_client_reg(BITMAP3, 0x00000000, TRUE); + write_client_reg(BITMAP4, 0x00DC00B0, TRUE); + write_client_reg(PORT, 0x00000000, TRUE); + write_client_reg(PXL, 0x00000000, TRUE); + write_client_reg(MPLFBUF, 0x00000004, TRUE); + write_client_reg(HCYCLE, 0x0000006B, TRUE); + write_client_reg(HSW, 0x00000003, TRUE); + write_client_reg(HDE_START, 0x00000007, TRUE); + write_client_reg(HDE_SIZE, 0x00000057, TRUE); + write_client_reg(VCYCLE, 0x000000E6, TRUE); + write_client_reg(VSW, 0x00000001, TRUE); + write_client_reg(VDE_START, 0x00000003, TRUE); + write_client_reg(VDE_SIZE, 0x000000DB, TRUE); + write_client_reg(ASY_DATA, 0x80000001, TRUE); + write_client_reg(ASY_DATB, 0x0000011B, TRUE); + write_client_reg(ASY_DATC, 0x80000002, TRUE); + write_client_reg(ASY_DATD, 0x00000700, TRUE); + write_client_reg(ASY_DATE, 0x80000003, TRUE); + write_client_reg(ASY_DATF, 0x00000230, TRUE); + write_client_reg(ASY_DATG, 0x80000008, TRUE); + write_client_reg(ASY_DATH, 0x00000402, TRUE); + write_client_reg(ASY_CMDSET, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + write_client_reg(ASY_DATA, 0x80000009, TRUE); + write_client_reg(ASY_DATB, 0x00000000, TRUE); + write_client_reg(ASY_DATC, 0x8000000B, TRUE); + write_client_reg(ASY_DATD, 0x00000000, TRUE); + write_client_reg(ASY_DATE, 0x8000000C, TRUE); + write_client_reg(ASY_DATF, 0x00000000, TRUE); + write_client_reg(ASY_DATG, 0x8000000D, TRUE); + write_client_reg(ASY_DATH, 0x00000409, TRUE); + write_client_reg(ASY_CMDSET, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + write_client_reg(ASY_DATA, 0x8000000E, TRUE); + write_client_reg(ASY_DATB, 0x00000409, TRUE); + write_client_reg(ASY_DATC, 0x80000030, TRUE); + write_client_reg(ASY_DATD, 0x00000000, TRUE); + write_client_reg(ASY_DATE, 0x80000031, TRUE); + write_client_reg(ASY_DATF, 0x00000100, TRUE); + write_client_reg(ASY_DATG, 0x80000032, TRUE); + write_client_reg(ASY_DATH, 0x00000104, TRUE); + write_client_reg(ASY_CMDSET, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + write_client_reg(ASY_DATA, 0x80000033, TRUE); + write_client_reg(ASY_DATB, 0x00000400, TRUE); + write_client_reg(ASY_DATC, 0x80000034, TRUE); + write_client_reg(ASY_DATD, 0x00000306, TRUE); + write_client_reg(ASY_DATE, 0x80000035, TRUE); + write_client_reg(ASY_DATF, 0x00000706, TRUE); + write_client_reg(ASY_DATG, 0x80000036, TRUE); + write_client_reg(ASY_DATH, 0x00000707, TRUE); + write_client_reg(ASY_CMDSET, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + write_client_reg(ASY_DATA, 0x80000037, TRUE); + write_client_reg(ASY_DATB, 0x00000004, TRUE); + write_client_reg(ASY_DATC, 0x80000038, TRUE); + write_client_reg(ASY_DATD, 0x00000000, TRUE); + write_client_reg(ASY_DATE, 0x80000039, TRUE); + write_client_reg(ASY_DATF, 0x00000000, TRUE); + write_client_reg(ASY_DATG, 0x8000003A, TRUE); + write_client_reg(ASY_DATH, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + write_client_reg(ASY_DATA, 0x80000044, TRUE); + write_client_reg(ASY_DATB, 0x0000AF00, TRUE); + write_client_reg(ASY_DATC, 0x80000045, TRUE); + write_client_reg(ASY_DATD, 0x0000DB00, TRUE); + write_client_reg(ASY_DATE, 0x08000042, TRUE); + write_client_reg(ASY_DATF, 0x0000DB00, TRUE); + write_client_reg(ASY_DATG, 0x80000021, TRUE); + write_client_reg(ASY_DATH, 0x00000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + write_client_reg(PXL, 0x0000000C, TRUE); + write_client_reg(VSYNIF, 0x00000001, TRUE); + write_client_reg(ASY_DATA, 0x80000022, TRUE); + write_client_reg(ASY_CMDSET, 0x00000003, TRUE); + write_client_reg(START, 0x00000001, TRUE); + mddi_wait(60); + write_client_reg(PXL, 0x00000000, TRUE); + write_client_reg(VSYNIF, 0x00000000, TRUE); + write_client_reg(START, 0x00000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + write_client_reg(ASY_DATA, 0x80000050, TRUE); + write_client_reg(ASY_DATB, 0x00000000, TRUE); + write_client_reg(ASY_DATC, 0x80000051, TRUE); + write_client_reg(ASY_DATD, 0x00000E00, TRUE); + write_client_reg(ASY_DATE, 0x80000052, TRUE); + write_client_reg(ASY_DATF, 0x00000D01, TRUE); + write_client_reg(ASY_DATG, 0x80000053, TRUE); + write_client_reg(ASY_DATH, 0x00000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + write_client_reg(ASY_DATA, 0x80000058, TRUE); + write_client_reg(ASY_DATB, 0x00000000, TRUE); + write_client_reg(ASY_DATC, 0x8000005A, TRUE); + write_client_reg(ASY_DATD, 0x00000E01, TRUE); + write_client_reg(ASY_CMDSET, 0x00000009, TRUE); + write_client_reg(ASY_CMDSET, 0x00000008, TRUE); + write_client_reg(ASY_DATA, 0x80000011, TRUE); + write_client_reg(ASY_DATB, 0x00000812, TRUE); + write_client_reg(ASY_DATC, 0x80000012, TRUE); + write_client_reg(ASY_DATD, 0x00000003, TRUE); + write_client_reg(ASY_DATE, 0x80000013, TRUE); + write_client_reg(ASY_DATF, 0x00000909, TRUE); + write_client_reg(ASY_DATG, 0x80000010, TRUE); + write_client_reg(ASY_DATH, 0x00000040, TRUE); + write_client_reg(ASY_CMDSET, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + mddi_wait(40); + write_client_reg(ASY_DATA, 0x80000010, TRUE); + write_client_reg(ASY_DATB, 0x00000340, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(60); + write_client_reg(ASY_DATA, 0x80000010, TRUE); + write_client_reg(ASY_DATB, 0x00003340, TRUE); + write_client_reg(ASY_DATC, 0x80000007, TRUE); + write_client_reg(ASY_DATD, 0x00004007, TRUE); + write_client_reg(ASY_CMDSET, 0x00000009, TRUE); + write_client_reg(ASY_CMDSET, 0x00000008, TRUE); + mddi_wait(1); + write_client_reg(ASY_DATA, 0x80000007, TRUE); + write_client_reg(ASY_DATB, 0x00004017, TRUE); + write_client_reg(ASY_DATC, 0x8000005B, TRUE); + write_client_reg(ASY_DATD, 0x00000000, TRUE); + write_client_reg(ASY_DATE, 0x80000059, TRUE); + write_client_reg(ASY_DATF, 0x00000011, TRUE); + write_client_reg(ASY_CMDSET, 0x0000000D, TRUE); + write_client_reg(ASY_CMDSET, 0x0000000C, TRUE); + mddi_wait(20); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + /* LTPS I/F control */ + write_client_reg(ASY_DATB, 0x00000019, TRUE); + /* Direct cmd transfer enable */ + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + /* Direct cmd transfer disable */ + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(20); + /* Index setting of SUB LCDD */ + write_client_reg(ASY_DATA, 0x80000059, TRUE); + /* LTPS I/F control */ + write_client_reg(ASY_DATB, 0x00000079, TRUE); + /* Direct cmd transfer enable */ + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + /* Direct cmd transfer disable */ + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(20); + /* Index setting of SUB LCDD */ + write_client_reg(ASY_DATA, 0x80000059, TRUE); + /* LTPS I/F control */ + write_client_reg(ASY_DATB, 0x000003FD, TRUE); + /* Direct cmd transfer enable */ + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + /* Direct cmd transfer disable */ + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(20); + mddi_toshiba_state_transition(TOSHIBA_STATE_PRIM_SEC_READY, + TOSHIBA_STATE_SEC_NORMAL_MODE); +} + +static void toshiba_prim_lcd_off(struct msm_fb_data_type *mfd) +{ + if (TM_GET_PID(mfd->panel.id) == LCD_SHARP_2P4_VGA) { + gordon_disp_off(); + } else{ + + /* Main panel power off (Deep standby in) */ + write_client_reg(SSITX, 0x000800BC, TRUE); + write_client_reg(SSITX, 0x00000100, TRUE); + write_client_reg(SSITX, 0x00000028, TRUE); + mddi_wait(1); + write_client_reg(SSITX, 0x000800B8, TRUE); + write_client_reg(SSITX, 0x00000180, TRUE); + write_client_reg(SSITX, 0x00000102, TRUE); + write_client_reg(SSITX, 0x00000010, TRUE); + } + write_client_reg(PORT, 0x00000003, TRUE); + write_client_reg(REGENB, 0x00000001, TRUE); + mddi_wait(1); + write_client_reg(PXL, 0x00000000, TRUE); + write_client_reg(START, 0x00000000, TRUE); + write_client_reg(REGENB, 0x00000001, TRUE); + mddi_wait(3); + if (TM_GET_PID(mfd->panel.id) != LCD_SHARP_2P4_VGA) { + write_client_reg(SSITX, 0x000800B0, TRUE); + write_client_reg(SSITX, 0x00000100, TRUE); + } + mddi_toshiba_state_transition(TOSHIBA_STATE_PRIM_NORMAL_MODE, + TOSHIBA_STATE_PRIM_SEC_STANDBY); +} + +static void toshiba_sec_lcd_off(struct msm_fb_data_type *mfd) +{ + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA_PT) + return; + + write_client_reg(VSYNIF, 0x00000000, TRUE); + write_client_reg(PORT_ENB, 0x00000002, TRUE); + write_client_reg(ASY_DATA, 0x80000007, TRUE); + write_client_reg(ASY_DATB, 0x00004016, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000019, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x0000000B, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000002, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(4); + write_client_reg(ASY_DATA, 0x80000010, TRUE); + write_client_reg(ASY_DATB, 0x00000300, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(4); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000007, TRUE); + write_client_reg(ASY_DATB, 0x00004004, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(PORT, 0x00000000, TRUE); + write_client_reg(PXL, 0x00000000, TRUE); + write_client_reg(START, 0x00000000, TRUE); + write_client_reg(VSYNIF, 0x00000001, TRUE); + write_client_reg(PORT_ENB, 0x00000001, TRUE); + write_client_reg(REGENB, 0x00000001, TRUE); + mddi_toshiba_state_transition(TOSHIBA_STATE_SEC_NORMAL_MODE, + TOSHIBA_STATE_PRIM_SEC_STANDBY); +} + +static void toshiba_sec_cont_update_start(struct msm_fb_data_type *mfd) +{ + + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA_PT) + return; + + write_client_reg(VSYNIF, 0x00000000, TRUE); + write_client_reg(PORT_ENB, 0x00000002, TRUE); + write_client_reg(INTMASK, 0x00000001, TRUE); + write_client_reg(TTBUSSEL, 0x0000000B, TRUE); + write_client_reg(MONI, 0x00000008, TRUE); + write_client_reg(CLKENB, 0x000000EF, TRUE); + write_client_reg(CLKENB, 0x000010EF, TRUE); + write_client_reg(CLKENB, 0x000011EF, TRUE); + write_client_reg(BITMAP4, 0x00DC00B0, TRUE); + write_client_reg(HCYCLE, 0x0000006B, TRUE); + write_client_reg(HSW, 0x00000003, TRUE); + write_client_reg(HDE_START, 0x00000002, TRUE); + write_client_reg(HDE_SIZE, 0x00000057, TRUE); + write_client_reg(VCYCLE, 0x000000E6, TRUE); + write_client_reg(VSW, 0x00000001, TRUE); + write_client_reg(VDE_START, 0x00000003, TRUE); + write_client_reg(VDE_SIZE, 0x000000DB, TRUE); + write_client_reg(WRSTB, 0x00000015, TRUE); + write_client_reg(MPLFBUF, 0x00000004, TRUE); + write_client_reg(ASY_DATA, 0x80000021, TRUE); + write_client_reg(ASY_DATB, 0x00000000, TRUE); + write_client_reg(ASY_DATC, 0x80000022, TRUE); + write_client_reg(ASY_CMDSET, 0x00000007, TRUE); + write_client_reg(PXL, 0x00000089, TRUE); + write_client_reg(VSYNIF, 0x00000001, TRUE); + mddi_wait(2); +} + +static void toshiba_sec_cont_update_stop(struct msm_fb_data_type *mfd) +{ + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA_PT) + return; + + write_client_reg(PXL, 0x00000000, TRUE); + write_client_reg(VSYNIF, 0x00000000, TRUE); + write_client_reg(START, 0x00000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + mddi_wait(3); + write_client_reg(SRST, 0x00000002, TRUE); + mddi_wait(3); + write_client_reg(SRST, 0x00000003, TRUE); +} + +static void toshiba_sec_backlight_on(struct msm_fb_data_type *mfd) +{ + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA_PT) + return; + + write_client_reg(TIMER0CTRL, 0x00000060, TRUE); + write_client_reg(TIMER0LOAD, 0x00001388, TRUE); + write_client_reg(PWM0OFF, 0x00000001, TRUE); + write_client_reg(TIMER1CTRL, 0x00000060, TRUE); + write_client_reg(TIMER1LOAD, 0x00001388, TRUE); + write_client_reg(PWM1OFF, 0x00001387, TRUE); + write_client_reg(TIMER0CTRL, 0x000000E0, TRUE); + write_client_reg(TIMER1CTRL, 0x000000E0, TRUE); + write_client_reg(PWMCR, 0x00000003, TRUE); +} + +static void toshiba_sec_sleep_in(struct msm_fb_data_type *mfd) +{ + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA_PT) + return; + + write_client_reg(VSYNIF, 0x00000000, TRUE); + write_client_reg(PORT_ENB, 0x00000002, TRUE); + write_client_reg(ASY_DATA, 0x80000007, TRUE); + write_client_reg(ASY_DATB, 0x00004016, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000019, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x0000000B, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000002, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(4); + write_client_reg(ASY_DATA, 0x80000010, TRUE); + write_client_reg(ASY_DATB, 0x00000300, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(4); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000000, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000007, TRUE); + write_client_reg(ASY_DATB, 0x00004004, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(PORT, 0x00000000, TRUE); + write_client_reg(PXL, 0x00000000, TRUE); + write_client_reg(START, 0x00000000, TRUE); + write_client_reg(REGENB, 0x00000001, TRUE); + /* Sleep in sequence */ + write_client_reg(ASY_DATA, 0x80000010, TRUE); + write_client_reg(ASY_DATB, 0x00000302, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); +} + +static void toshiba_sec_sleep_out(struct msm_fb_data_type *mfd) +{ + if (TM_GET_PID(mfd->panel.id) == LCD_TOSHIBA_2P4_WVGA_PT) + return; + + write_client_reg(VSYNIF, 0x00000000, TRUE); + write_client_reg(PORT_ENB, 0x00000002, TRUE); + write_client_reg(ASY_DATA, 0x80000010, TRUE); + write_client_reg(ASY_DATB, 0x00000300, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + /* Display ON sequence */ + write_client_reg(ASY_DATA, 0x80000011, TRUE); + write_client_reg(ASY_DATB, 0x00000812, TRUE); + write_client_reg(ASY_DATC, 0x80000012, TRUE); + write_client_reg(ASY_DATD, 0x00000003, TRUE); + write_client_reg(ASY_DATE, 0x80000013, TRUE); + write_client_reg(ASY_DATF, 0x00000909, TRUE); + write_client_reg(ASY_DATG, 0x80000010, TRUE); + write_client_reg(ASY_DATH, 0x00000040, TRUE); + write_client_reg(ASY_CMDSET, 0x00000001, TRUE); + write_client_reg(ASY_CMDSET, 0x00000000, TRUE); + mddi_wait(4); + write_client_reg(ASY_DATA, 0x80000010, TRUE); + write_client_reg(ASY_DATB, 0x00000340, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(6); + write_client_reg(ASY_DATA, 0x80000010, TRUE); + write_client_reg(ASY_DATB, 0x00003340, TRUE); + write_client_reg(ASY_DATC, 0x80000007, TRUE); + write_client_reg(ASY_DATD, 0x00004007, TRUE); + write_client_reg(ASY_CMDSET, 0x00000009, TRUE); + write_client_reg(ASY_CMDSET, 0x00000008, TRUE); + mddi_wait(1); + write_client_reg(ASY_DATA, 0x80000007, TRUE); + write_client_reg(ASY_DATB, 0x00004017, TRUE); + write_client_reg(ASY_DATC, 0x8000005B, TRUE); + write_client_reg(ASY_DATD, 0x00000000, TRUE); + write_client_reg(ASY_DATE, 0x80000059, TRUE); + write_client_reg(ASY_DATF, 0x00000011, TRUE); + write_client_reg(ASY_CMDSET, 0x0000000D, TRUE); + write_client_reg(ASY_CMDSET, 0x0000000C, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000019, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x00000079, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); + write_client_reg(ASY_DATA, 0x80000059, TRUE); + write_client_reg(ASY_DATB, 0x000003FD, TRUE); + write_client_reg(ASY_CMDSET, 0x00000005, TRUE); + write_client_reg(ASY_CMDSET, 0x00000004, TRUE); + mddi_wait(2); +} + +static void mddi_toshiba_lcd_set_backlight(struct msm_fb_data_type *mfd) +{ + int32 level; + int ret = -EPERM; + int max = mfd->panel_info.bl_max; + int min = mfd->panel_info.bl_min; + int i = 0; + + if (mddi_toshiba_pdata && mddi_toshiba_pdata->pmic_backlight) { + while (i++ < 3) { + ret = mddi_toshiba_pdata->pmic_backlight(mfd->bl_level); + if (!ret) + return; + msleep(10); + } + printk(KERN_WARNING "%s: pmic_backlight Failed\n", __func__); + } + + + if (ret && mddi_toshiba_pdata && mddi_toshiba_pdata->backlight_level) { + level = mddi_toshiba_pdata->backlight_level(mfd->bl_level, + max, min); + + if (level < 0) + return; + + if (TM_GET_PID(mfd->panel.id) == LCD_SHARP_2P4_VGA) + write_client_reg(TIMER0LOAD, 0x00001388, TRUE); + } else { + if (!max) + level = 0; + else + level = (mfd->bl_level * 4999) / max; + } + + write_client_reg(PWM0OFF, level, TRUE); +} + +static void mddi_toshiba_vsync_set_handler(msm_fb_vsync_handler_type handler, /* ISR to be executed */ + void *arg) +{ + boolean error = FALSE; + unsigned long flags; + + /* Disable interrupts */ + spin_lock_irqsave(&mddi_host_spin_lock, flags); + /* INTLOCK(); */ + + if (mddi_toshiba_vsync_handler != NULL) { + error = TRUE; + } else { + /* Register the handler for this particular GROUP interrupt source */ + mddi_toshiba_vsync_handler = handler; + mddi_toshiba_vsync_handler_arg = arg; + } + + /* Restore interrupts */ + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + /* MDDI_INTFREE(); */ + if (error) { + MDDI_MSG_ERR("MDDI: Previous Vsync handler never called\n"); + } else { + /* Enable the vsync wakeup */ + mddi_queue_register_write(INTMSK, 0x0000, FALSE, 0); + + mddi_toshiba_vsync_attempts = 1; + mddi_vsync_detect_enabled = TRUE; + } +} /* mddi_toshiba_vsync_set_handler */ + +static void mddi_toshiba_lcd_vsync_detected(boolean detected) +{ + /* static timetick_type start_time = 0; */ + static struct timeval start_time; + static boolean first_time = TRUE; + /* uint32 mdp_cnt_val = 0; */ + /* timetick_type elapsed_us; */ + struct timeval now; + uint32 elapsed_us; + uint32 num_vsyncs; + + if ((detected) || (mddi_toshiba_vsync_attempts > 5)) { + if ((detected) && (mddi_toshiba_monitor_refresh_value)) { + /* if (start_time != 0) */ + if (!first_time) { + jiffies_to_timeval(jiffies, &now); + elapsed_us = + (now.tv_sec - start_time.tv_sec) * 1000000 + + now.tv_usec - start_time.tv_usec; + /* + * LCD is configured for a refresh every usecs, + * so to determine the number of vsyncs that + * have occurred since the last measurement + * add half that to the time difference and + * divide by the refresh rate. + */ + num_vsyncs = (elapsed_us + + (mddi_toshiba_usecs_per_refresh >> + 1)) / + mddi_toshiba_usecs_per_refresh; + /* + * LCD is configured for * hsyncs (rows) per + * refresh cycle. Calculate new rows_per_second + * value based upon these new measurements. + * MDP can update with this new value. + */ + mddi_toshiba_rows_per_second = + (mddi_toshiba_rows_per_refresh * 1000 * + num_vsyncs) / (elapsed_us / 1000); + } + /* start_time = timetick_get(); */ + first_time = FALSE; + jiffies_to_timeval(jiffies, &start_time); + if (mddi_toshiba_report_refresh_measurements) { + (void)mddi_queue_register_read_int(VPOS, + &mddi_toshiba_curr_vpos); + /* mdp_cnt_val = MDP_LINE_COUNT; */ + } + } + /* if detected = TRUE, client initiated wakeup was detected */ + if (mddi_toshiba_vsync_handler != NULL) { + (*mddi_toshiba_vsync_handler) + (mddi_toshiba_vsync_handler_arg); + mddi_toshiba_vsync_handler = NULL; + } + mddi_vsync_detect_enabled = FALSE; + mddi_toshiba_vsync_attempts = 0; + /* need to disable the interrupt wakeup */ + if (!mddi_queue_register_write_int(INTMSK, 0x0001)) + MDDI_MSG_ERR("Vsync interrupt disable failed!\n"); + if (!detected) { + /* give up after 5 failed attempts but show error */ + MDDI_MSG_NOTICE("Vsync detection failed!\n"); + } else if ((mddi_toshiba_monitor_refresh_value) && + (mddi_toshiba_report_refresh_measurements)) { + MDDI_MSG_NOTICE(" Last Line Counter=%d!\n", + mddi_toshiba_curr_vpos); + /* MDDI_MSG_NOTICE(" MDP Line Counter=%d!\n",mdp_cnt_val); */ + MDDI_MSG_NOTICE(" Lines Per Second=%d!\n", + mddi_toshiba_rows_per_second); + } + /* clear the interrupt */ + if (!mddi_queue_register_write_int(INTFLG, 0x0001)) + MDDI_MSG_ERR("Vsync interrupt clear failed!\n"); + } else { + /* if detected = FALSE, we woke up from hibernation, but did not + * detect client initiated wakeup. + */ + mddi_toshiba_vsync_attempts++; + } +} + +static void mddi_toshiba_prim_init(struct msm_fb_data_type *mfd) +{ + + switch (toshiba_state) { + case TOSHIBA_STATE_PRIM_SEC_READY: + break; + case TOSHIBA_STATE_OFF: + toshiba_state = TOSHIBA_STATE_PRIM_SEC_STANDBY; + toshiba_common_initial_setup(mfd); + break; + case TOSHIBA_STATE_PRIM_SEC_STANDBY: + toshiba_common_initial_setup(mfd); + break; + case TOSHIBA_STATE_SEC_NORMAL_MODE: + toshiba_sec_cont_update_stop(mfd); + toshiba_sec_sleep_in(mfd); + toshiba_sec_sleep_out(mfd); + toshiba_sec_lcd_off(mfd); + toshiba_common_initial_setup(mfd); + break; + default: + MDDI_MSG_ERR("mddi_toshiba_prim_init from state %d\n", + toshiba_state); + } + + toshiba_prim_start(mfd); + if (TM_GET_PID(mfd->panel.id) == LCD_SHARP_2P4_VGA) + gordon_disp_init(); + mddi_host_write_pix_attr_reg(0x00C3); +} + +static void mddi_toshiba_sec_init(struct msm_fb_data_type *mfd) +{ + + switch (toshiba_state) { + case TOSHIBA_STATE_PRIM_SEC_READY: + break; + case TOSHIBA_STATE_PRIM_SEC_STANDBY: + toshiba_common_initial_setup(mfd); + break; + case TOSHIBA_STATE_PRIM_NORMAL_MODE: + toshiba_prim_lcd_off(mfd); + toshiba_common_initial_setup(mfd); + break; + default: + MDDI_MSG_ERR("mddi_toshiba_sec_init from state %d\n", + toshiba_state); + } + + toshiba_sec_start(mfd); + toshiba_sec_backlight_on(mfd); + toshiba_sec_cont_update_start(mfd); + mddi_host_write_pix_attr_reg(0x0400); +} + +static void mddi_toshiba_lcd_powerdown(struct msm_fb_data_type *mfd) +{ + switch (toshiba_state) { + case TOSHIBA_STATE_PRIM_SEC_READY: + mddi_toshiba_prim_init(mfd); + mddi_toshiba_lcd_powerdown(mfd); + return; + case TOSHIBA_STATE_PRIM_SEC_STANDBY: + break; + case TOSHIBA_STATE_PRIM_NORMAL_MODE: + toshiba_prim_lcd_off(mfd); + break; + case TOSHIBA_STATE_SEC_NORMAL_MODE: + toshiba_sec_cont_update_stop(mfd); + toshiba_sec_sleep_in(mfd); + toshiba_sec_sleep_out(mfd); + toshiba_sec_lcd_off(mfd); + break; + default: + MDDI_MSG_ERR("mddi_toshiba_lcd_powerdown from state %d\n", + toshiba_state); + } +} + +static int mddi_sharpgordon_firsttime = 1; + +static int mddi_toshiba_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + mfd = platform_get_drvdata(pdev); + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mddi_host_client_cnt_reset(); + + if (TM_GET_DID(mfd->panel.id) == TOSHIBA_VGA_PRIM) + mddi_toshiba_prim_init(mfd); + else + mddi_toshiba_sec_init(mfd); + if (TM_GET_PID(mfd->panel.id) == LCD_SHARP_2P4_VGA) { + if (mddi_sharpgordon_firsttime) { + mddi_sharpgordon_firsttime = 0; + write_client_reg(REGENB, 0x00000001, TRUE); + } + } + return 0; +} + +static int mddi_toshiba_lcd_off(struct platform_device *pdev) +{ + if (mddi_toshiba_vsync_handler != NULL) { + (*mddi_toshiba_vsync_handler) + (mddi_toshiba_vsync_handler_arg); + mddi_toshiba_vsync_handler = NULL; + printk(KERN_INFO "%s: clean up vsyn_handler=%x\n", __func__, + (int)mddi_toshiba_vsync_handler); + } + + mddi_toshiba_lcd_powerdown(platform_get_drvdata(pdev)); + return 0; +} + +static int __devinit mddi_toshiba_lcd_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + mddi_toshiba_pdata = pdev->dev.platform_data; + return 0; + } + + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = mddi_toshiba_lcd_probe, + .driver = { + .name = "mddi_toshiba", + }, +}; + +static struct msm_fb_panel_data toshiba_panel_data = { + .on = mddi_toshiba_lcd_on, + .off = mddi_toshiba_lcd_off, +}; + +static int ch_used[3]; + +int mddi_toshiba_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel) +{ + struct platform_device *pdev = NULL; + int ret; + + if ((channel >= 3) || ch_used[channel]) + return -ENODEV; + + if ((channel != TOSHIBA_VGA_PRIM) && + mddi_toshiba_pdata && mddi_toshiba_pdata->panel_num) + if (mddi_toshiba_pdata->panel_num() < 2) + return -ENODEV; + + ch_used[channel] = TRUE; + + pdev = platform_device_alloc("mddi_toshiba", (panel << 8)|channel); + if (!pdev) + return -ENOMEM; + + if (channel == TOSHIBA_VGA_PRIM) { + toshiba_panel_data.set_backlight = + mddi_toshiba_lcd_set_backlight; + + if (pinfo->lcd.vsync_enable) { + toshiba_panel_data.set_vsync_notifier = + mddi_toshiba_vsync_set_handler; + mddi_lcd.vsync_detected = + mddi_toshiba_lcd_vsync_detected; + } + } else { + toshiba_panel_data.set_backlight = NULL; + toshiba_panel_data.set_vsync_notifier = NULL; + } + + toshiba_panel_data.panel_info = *pinfo; + + ret = platform_device_add_data(pdev, &toshiba_panel_data, + sizeof(toshiba_panel_data)); + if (ret) { + printk(KERN_ERR + "%s: platform_device_add_data failed!\n", __func__); + goto err_device_put; + } + + ret = platform_device_add(pdev); + if (ret) { + printk(KERN_ERR + "%s: platform_device_register failed!\n", __func__); + goto err_device_put; + } + + return 0; + +err_device_put: + platform_device_put(pdev); + return ret; +} + +static int __init mddi_toshiba_lcd_init(void) +{ + return platform_driver_register(&this_driver); +} + +module_init(mddi_toshiba_lcd_init); diff --git a/drivers/video/msm/mddi_toshiba.h b/drivers/video/msm/mddi_toshiba.h new file mode 100644 index 00000000000..646f5e9e156 --- /dev/null +++ b/drivers/video/msm/mddi_toshiba.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MDDI_TOSHIBA_H +#define MDDI_TOSHIBA_H + +#define TOSHIBA_VGA_PRIM 1 +#define TOSHIBA_VGA_SECD 2 + +#define LCD_TOSHIBA_2P4_VGA 0 +#define LCD_TOSHIBA_2P4_WVGA 1 +#define LCD_TOSHIBA_2P4_WVGA_PT 2 +#define LCD_SHARP_2P4_VGA 3 + +#define GPIO_BLOCK_BASE 0x150000 +#define SYSTEM_BLOCK2_BASE 0x170000 + +#define GPIODIR (GPIO_BLOCK_BASE|0x04) +#define GPIOSEL (SYSTEM_BLOCK2_BASE|0x00) +#define GPIOPC (GPIO_BLOCK_BASE|0x28) +#define GPIODATA (GPIO_BLOCK_BASE|0x00) + +#define write_client_reg(__X, __Y, __Z) {\ + mddi_queue_register_write(__X, __Y, TRUE, 0);\ +} + +#endif /* MDDI_TOSHIBA_H */ diff --git a/drivers/video/msm/mddi_toshiba_vga.c b/drivers/video/msm/mddi_toshiba_vga.c new file mode 100644 index 00000000000..794edffe036 --- /dev/null +++ b/drivers/video/msm/mddi_toshiba_vga.c @@ -0,0 +1,133 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" +#include "mddi_toshiba.h" + +static uint32 read_client_reg(uint32 addr) +{ + uint32 val; + mddi_queue_register_read(addr, &val, TRUE, 0); + return val; +} + +static uint32 toshiba_lcd_gpio_read(void) +{ + uint32 val; + + write_client_reg(GPIODIR, 0x0000000C, TRUE); + write_client_reg(GPIOSEL, 0x00000000, TRUE); + write_client_reg(GPIOSEL, 0x00000000, TRUE); + write_client_reg(GPIOPC, 0x03CF00C0, TRUE); + val = read_client_reg(GPIODATA) & 0x2C0; + + return val; +} + +static u32 mddi_toshiba_panel_detect(void) +{ + mddi_host_type host_idx = MDDI_HOST_PRIM; + uint32 lcd_gpio; + u32 mddi_toshiba_lcd = LCD_TOSHIBA_2P4_VGA; + + /* Toshiba display requires larger drive_lo value */ + mddi_host_reg_out(DRIVE_LO, 0x0050); + + lcd_gpio = toshiba_lcd_gpio_read(); + switch (lcd_gpio) { + case 0x0080: + mddi_toshiba_lcd = LCD_SHARP_2P4_VGA; + break; + + case 0x00C0: + default: + mddi_toshiba_lcd = LCD_TOSHIBA_2P4_VGA; + break; + } + + return mddi_toshiba_lcd; +} + +static int __init mddi_toshiba_vga_init(void) +{ + int ret; + struct msm_panel_info pinfo; + u32 panel; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + u32 id; + + ret = msm_fb_detect_client("mddi_toshiba_vga"); + if (ret == -ENODEV) + return 0; + + if (ret) { + id = mddi_get_client_id(); + if ((id >> 16) != 0xD263) + return 0; + } +#endif + + panel = mddi_toshiba_panel_detect(); + + pinfo.xres = 480; + pinfo.yres = 640; + pinfo.type = MDDI_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; + pinfo.wait_cycle = 0; + pinfo.bpp = 18; + pinfo.lcd.vsync_enable = TRUE; + pinfo.lcd.refx100 = 6118; + pinfo.lcd.v_back_porch = 6; + pinfo.lcd.v_front_porch = 0; + pinfo.lcd.v_pulse_width = 0; + pinfo.lcd.hw_vsync_mode = FALSE; + pinfo.lcd.vsync_notifier_period = (1 * HZ); + pinfo.bl_max = 99; + pinfo.bl_min = 1; + pinfo.clk_rate = 122880000; + pinfo.clk_min = 120000000; + pinfo.clk_max = 200000000; + pinfo.fb_num = 2; + + ret = mddi_toshiba_device_register(&pinfo, TOSHIBA_VGA_PRIM, panel); + if (ret) { + printk(KERN_ERR "%s: failed to register device!\n", __func__); + return ret; + } + + pinfo.xres = 176; + pinfo.yres = 220; + MSM_FB_SINGLE_MODE_PANEL(&pinfo); + pinfo.type = MDDI_PANEL; + pinfo.pdest = DISPLAY_2; + pinfo.mddi.vdopkt = 0x400; + pinfo.wait_cycle = 0; + pinfo.bpp = 18; + pinfo.clk_rate = 122880000; + pinfo.clk_min = 120000000; + pinfo.clk_max = 200000000; + pinfo.fb_num = 2; + + ret = mddi_toshiba_device_register(&pinfo, TOSHIBA_VGA_SECD, panel); + if (ret) + printk(KERN_WARNING + "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mddi_toshiba_vga_init); diff --git a/drivers/video/msm/mddi_toshiba_wvga.c b/drivers/video/msm/mddi_toshiba_wvga.c new file mode 100644 index 00000000000..ad4ce465b8e --- /dev/null +++ b/drivers/video/msm/mddi_toshiba_wvga.c @@ -0,0 +1,60 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddi_toshiba.h" + +static int __init mddi_toshiba_wvga_init(void) +{ + int ret; + struct msm_panel_info pinfo; + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + if (msm_fb_detect_client("mddi_toshiba_wvga")) + return 0; +#endif + + pinfo.xres = 800; + pinfo.yres = 480; + MSM_FB_SINGLE_MODE_PANEL(&pinfo); + pinfo.pdest = DISPLAY_2; + pinfo.type = MDDI_PANEL; + pinfo.mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; + pinfo.wait_cycle = 0; + pinfo.bpp = 18; + pinfo.lcd.vsync_enable = TRUE; + pinfo.lcd.refx100 = 6118; + pinfo.lcd.v_back_porch = 6; + pinfo.lcd.v_front_porch = 0; + pinfo.lcd.v_pulse_width = 0; + pinfo.lcd.hw_vsync_mode = FALSE; + pinfo.lcd.vsync_notifier_period = (1 * HZ); + pinfo.bl_max = 4; + pinfo.bl_min = 1; + pinfo.clk_rate = 192000000; + pinfo.clk_min = 190000000; + pinfo.clk_max = 200000000; + pinfo.fb_num = 2; + + ret = mddi_toshiba_device_register(&pinfo, TOSHIBA_VGA_PRIM, + LCD_TOSHIBA_2P4_WVGA); + if (ret) { + printk(KERN_ERR "%s: failed to register device!\n", __func__); + return ret; + } + + return ret; +} + +module_init(mddi_toshiba_wvga_init); diff --git a/drivers/video/msm/mddi_toshiba_wvga_pt.c b/drivers/video/msm/mddi_toshiba_wvga_pt.c new file mode 100644 index 00000000000..edf739d905e --- /dev/null +++ b/drivers/video/msm/mddi_toshiba_wvga_pt.c @@ -0,0 +1,68 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" +#include "mddi_toshiba.h" + +static struct msm_panel_info pinfo; + +static int __init mddi_toshiba_wvga_pt_init(void) +{ + int ret; +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + uint id; + + ret = msm_fb_detect_client("mddi_toshiba_wvga_pt"); + if (ret == -ENODEV) + return 0; + + if (ret) { + id = mddi_get_client_id(); + if (id != 0xd2638722) + return 0; + } +#endif + + pinfo.xres = 480; + pinfo.yres = 800; + MSM_FB_SINGLE_MODE_PANEL(&pinfo); + pinfo.type = MDDI_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.mddi.vdopkt = MDDI_DEFAULT_PRIM_PIX_ATTR; + pinfo.wait_cycle = 0; + pinfo.bpp = 18; + pinfo.lcd.vsync_enable = TRUE; + pinfo.lcd.refx100 = 6096; /* adjust refx100 to prevent tearing */ + pinfo.lcd.v_back_porch = 2; /* vsw=1 + vbp = 2 */ + pinfo.lcd.v_front_porch = 3; + pinfo.lcd.v_pulse_width = 1; + pinfo.lcd.hw_vsync_mode = FALSE; + pinfo.lcd.vsync_notifier_period = (1 * HZ); + pinfo.bl_max = 15; + pinfo.bl_min = 1; + pinfo.clk_rate = 222750000; + pinfo.clk_min = 200000000; + pinfo.clk_max = 240000000; + pinfo.fb_num = 2; + + ret = mddi_toshiba_device_register(&pinfo, TOSHIBA_VGA_PRIM, + LCD_TOSHIBA_2P4_WVGA_PT); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mddi_toshiba_wvga_pt_init); diff --git a/drivers/video/msm/mddihost.c b/drivers/video/msm/mddihost.c new file mode 100644 index 00000000000..c6acf9f2807 --- /dev/null +++ b/drivers/video/msm/mddihost.c @@ -0,0 +1,626 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" + +#include +#include + +struct semaphore mddi_host_mutex; + +struct clk *mddi_io_clk; +static boolean mddi_host_powered = FALSE; +static boolean mddi_host_initialized = FALSE; +extern uint32 *mddi_reg_read_value_ptr; + +mddi_lcd_func_type mddi_lcd; + +extern mddi_client_capability_type mddi_client_capability_pkt; + +#ifdef MDDI_HOST_WINDOW_WORKAROUND +/* Tables showing number of rows that would cause a packet length + * ending in 0x02, for each number of columns. These tables have + * been generated for MDDI packets that have 16 and 16 bits-per-pixel. + * This is a work-around for MDDI clients that declare a CRC error + * on MDDI packets where ((length & 0x00ff) == 0x02). + */ +static uint16 error_vals_16bpp[] = { +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 12, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 10, 0, 0, 0, 14, 0, 0, 0, 2, 0, 0, 4, 6, 12, 0, +0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, +0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 11, 4, 0, 12, 0, +0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, +0, 10, 0, 1, 0, 14, 0, 0, 0, 2, 0, 3, 4, 6, 12, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 12, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 10, 0, 0, 0, 14, 0, 0, 0, 2, 0, 0, 4, 6, 12, 0, +0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, +0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 11, 4, 0, 12, 0, +0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, +}; + +static uint16 error_vals_18bpp[] = { +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 14, +0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 9, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 7, +0, 0, 0, 0, 0, 0, 1, 0, 0, 16, 0, 0, 0, 0, 0, 6, +14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +7, 0, 0, 0, 0, 0, 0, 4, 0, 16, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, +0, 7, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 9, 0 +}; +#endif + +#ifdef FEATURE_MDDI_HITACHI +extern void mddi_hitachi_window_adjust(uint16 x1, + uint16 x2, uint16 y1, uint16 y2); +#endif + +extern void mddi_toshiba_lcd_init(void); + +#ifdef FEATURE_MDDI_S6D0142 +extern void mddi_s6d0142_lcd_init(void); +extern void mddi_s6d0142_window_adjust(uint16 x1, + uint16 x2, + uint16 y1, + uint16 y2, + mddi_llist_done_cb_type done_cb); +#endif + +void mddi_init(void) +{ + if (mddi_host_initialized) + return; + + mddi_host_initialized = TRUE; + + sema_init(&mddi_host_mutex, 1); + + if (!mddi_host_powered) { + down(&mddi_host_mutex); + mddi_host_init(MDDI_HOST_PRIM); + mddi_host_powered = TRUE; + up(&mddi_host_mutex); + mdelay(10); + } +} + +int mddi_host_register_read(uint32 reg_addr, + uint32 *reg_value_ptr, boolean wait, mddi_host_type host) { + mddi_linked_list_type *curr_llist_ptr; + mddi_register_access_packet_type *regacc_pkt_ptr; + uint16 curr_llist_idx; + int ret = 0; + + if (in_interrupt()) + MDDI_MSG_CRIT("Called from ISR context\n"); + + if (!mddi_host_powered) { + MDDI_MSG_ERR("MDDI powered down!\n"); + mddi_init(); + } + + down(&mddi_host_mutex); + + mddi_reg_read_value_ptr = reg_value_ptr; + curr_llist_idx = mddi_get_reg_read_llist_item(host, TRUE); + if (curr_llist_idx == UNASSIGNED_INDEX) { + up(&mddi_host_mutex); + + /* need to change this to some sort of wait */ + MDDI_MSG_ERR("Attempting to queue up more than 1 reg read\n"); + return -EINVAL; + } + + curr_llist_ptr = &llist_extern[host][curr_llist_idx]; + curr_llist_ptr->link_controller_flags = 0x11; + curr_llist_ptr->packet_header_count = 14; + curr_llist_ptr->packet_data_count = 0; + + curr_llist_ptr->next_packet_pointer = NULL; + curr_llist_ptr->packet_data_pointer = NULL; + curr_llist_ptr->reserved = 0; + + regacc_pkt_ptr = &curr_llist_ptr->packet_header.register_pkt; + + regacc_pkt_ptr->packet_length = curr_llist_ptr->packet_header_count; + regacc_pkt_ptr->packet_type = 146; /* register access packet */ + regacc_pkt_ptr->bClient_ID = 0; + regacc_pkt_ptr->read_write_info = 0x8001; + regacc_pkt_ptr->register_address = reg_addr; + + /* now adjust pointers */ + mddi_queue_forward_packets(curr_llist_idx, curr_llist_idx, wait, + NULL, host); + /* need to check if we can write the pointer or not */ + + up(&mddi_host_mutex); + + if (wait) { + int wait_ret; + + mddi_linked_list_notify_type *llist_notify_ptr; + llist_notify_ptr = &llist_extern_notify[host][curr_llist_idx]; + wait_ret = wait_for_completion_timeout( + &(llist_notify_ptr->done_comp), 5 * HZ); + + if (wait_ret <= 0) + ret = -EBUSY; + + if (wait_ret < 0) + printk(KERN_ERR "%s: failed to wait for completion!\n", + __func__); + else if (!wait_ret) + printk(KERN_ERR "%s: Timed out waiting!\n", __func__); + + if (!ret && (mddi_reg_read_value_ptr == reg_value_ptr) && + (*reg_value_ptr == -EBUSY)) { + printk(KERN_ERR "%s - failed to get data from client", + __func__); + mddi_reg_read_value_ptr = NULL; + ret = -EBUSY; + } + } + + MDDI_MSG_DEBUG("Reg Read value=0x%x\n", *reg_value_ptr); + + return ret; +} /* mddi_host_register_read */ + +int mddi_host_register_write(uint32 reg_addr, + uint32 reg_val, enum mddi_data_packet_size_type packet_size, + boolean wait, mddi_llist_done_cb_type done_cb, mddi_host_type host) { + mddi_linked_list_type *curr_llist_ptr; + mddi_linked_list_type *curr_llist_dma_ptr; + mddi_register_access_packet_type *regacc_pkt_ptr; + uint16 curr_llist_idx; + int ret = 0; + + if (in_interrupt()) + MDDI_MSG_CRIT("Called from ISR context\n"); + + if (!mddi_host_powered) { + MDDI_MSG_ERR("MDDI powered down!\n"); + mddi_init(); + } + + down(&mddi_host_mutex); + + curr_llist_idx = mddi_get_next_free_llist_item(host, TRUE); + curr_llist_ptr = &llist_extern[host][curr_llist_idx]; + curr_llist_dma_ptr = &llist_dma_extern[host][curr_llist_idx]; + + curr_llist_ptr->link_controller_flags = 1; + curr_llist_ptr->packet_header_count = 14; + curr_llist_ptr->packet_data_count = 4; + + curr_llist_ptr->next_packet_pointer = NULL; + curr_llist_ptr->reserved = 0; + + regacc_pkt_ptr = &curr_llist_ptr->packet_header.register_pkt; + + regacc_pkt_ptr->packet_length = curr_llist_ptr->packet_header_count + + (uint16)packet_size; + regacc_pkt_ptr->packet_type = 146; /* register access packet */ + regacc_pkt_ptr->bClient_ID = 0; + regacc_pkt_ptr->read_write_info = 0x0001; + regacc_pkt_ptr->register_address = reg_addr; + regacc_pkt_ptr->register_data_list[0] = reg_val; + + MDDI_MSG_DEBUG("Reg Access write reg=0x%x, value=0x%x\n", + regacc_pkt_ptr->register_address, + regacc_pkt_ptr->register_data_list[0]); + + regacc_pkt_ptr = &curr_llist_dma_ptr->packet_header.register_pkt; + curr_llist_ptr->packet_data_pointer = + (void *)(®acc_pkt_ptr->register_data_list[0]); + + /* now adjust pointers */ + mddi_queue_forward_packets(curr_llist_idx, curr_llist_idx, wait, + done_cb, host); + + up(&mddi_host_mutex); + + if (wait) { + int wait_ret; + + mddi_linked_list_notify_type *llist_notify_ptr; + llist_notify_ptr = &llist_extern_notify[host][curr_llist_idx]; + wait_ret = wait_for_completion_timeout( + &(llist_notify_ptr->done_comp), 5 * HZ); + + if (wait_ret <= 0) + ret = -EBUSY; + + if (wait_ret < 0) + printk(KERN_ERR "%s: failed to wait for completion!\n", + __func__); + else if (!wait_ret) + printk(KERN_ERR "%s: Timed out waiting!\n", __func__); + } + + return ret; +} /* mddi_host_register_write */ + +boolean mddi_host_register_read_int + (uint32 reg_addr, uint32 *reg_value_ptr, mddi_host_type host) { + mddi_linked_list_type *curr_llist_ptr; + mddi_register_access_packet_type *regacc_pkt_ptr; + uint16 curr_llist_idx; + + if (!in_interrupt()) + MDDI_MSG_CRIT("Called from TASK context\n"); + + if (!mddi_host_powered) { + MDDI_MSG_ERR("MDDI powered down!\n"); + return FALSE; + } + + if (down_trylock(&mddi_host_mutex) != 0) + return FALSE; + + mddi_reg_read_value_ptr = reg_value_ptr; + curr_llist_idx = mddi_get_reg_read_llist_item(host, FALSE); + if (curr_llist_idx == UNASSIGNED_INDEX) { + up(&mddi_host_mutex); + return FALSE; + } + + curr_llist_ptr = &llist_extern[host][curr_llist_idx]; + curr_llist_ptr->link_controller_flags = 0x11; + curr_llist_ptr->packet_header_count = 14; + curr_llist_ptr->packet_data_count = 0; + + curr_llist_ptr->next_packet_pointer = NULL; + curr_llist_ptr->packet_data_pointer = NULL; + curr_llist_ptr->reserved = 0; + + regacc_pkt_ptr = &curr_llist_ptr->packet_header.register_pkt; + + regacc_pkt_ptr->packet_length = curr_llist_ptr->packet_header_count; + regacc_pkt_ptr->packet_type = 146; /* register access packet */ + regacc_pkt_ptr->bClient_ID = 0; + regacc_pkt_ptr->read_write_info = 0x8001; + regacc_pkt_ptr->register_address = reg_addr; + + /* now adjust pointers */ + mddi_queue_forward_packets(curr_llist_idx, curr_llist_idx, FALSE, + NULL, host); + /* need to check if we can write the pointer or not */ + + up(&mddi_host_mutex); + + return TRUE; + +} /* mddi_host_register_read */ + +boolean mddi_host_register_write_int + (uint32 reg_addr, + uint32 reg_val, mddi_llist_done_cb_type done_cb, mddi_host_type host) { + mddi_linked_list_type *curr_llist_ptr; + mddi_linked_list_type *curr_llist_dma_ptr; + mddi_register_access_packet_type *regacc_pkt_ptr; + uint16 curr_llist_idx; + + if (!in_interrupt()) + MDDI_MSG_CRIT("Called from TASK context\n"); + + if (!mddi_host_powered) { + MDDI_MSG_ERR("MDDI powered down!\n"); + return FALSE; + } + + if (down_trylock(&mddi_host_mutex) != 0) + return FALSE; + + curr_llist_idx = mddi_get_next_free_llist_item(host, FALSE); + if (curr_llist_idx == UNASSIGNED_INDEX) { + up(&mddi_host_mutex); + return FALSE; + } + + curr_llist_ptr = &llist_extern[host][curr_llist_idx]; + curr_llist_dma_ptr = &llist_dma_extern[host][curr_llist_idx]; + + curr_llist_ptr->link_controller_flags = 1; + curr_llist_ptr->packet_header_count = 14; + curr_llist_ptr->packet_data_count = 4; + + curr_llist_ptr->next_packet_pointer = NULL; + curr_llist_ptr->reserved = 0; + + regacc_pkt_ptr = &curr_llist_ptr->packet_header.register_pkt; + + regacc_pkt_ptr->packet_length = curr_llist_ptr->packet_header_count + 4; + regacc_pkt_ptr->packet_type = 146; /* register access packet */ + regacc_pkt_ptr->bClient_ID = 0; + regacc_pkt_ptr->read_write_info = 0x0001; + regacc_pkt_ptr->register_address = reg_addr; + regacc_pkt_ptr->register_data_list[0] = reg_val; + + regacc_pkt_ptr = &curr_llist_dma_ptr->packet_header.register_pkt; + curr_llist_ptr->packet_data_pointer = + (void *)(&(regacc_pkt_ptr->register_data_list[0])); + + /* now adjust pointers */ + mddi_queue_forward_packets(curr_llist_idx, curr_llist_idx, FALSE, + done_cb, host); + up(&mddi_host_mutex); + + return TRUE; + +} /* mddi_host_register_write */ + +void mddi_wait(uint16 time_ms) +{ + mdelay(time_ms); +} + +void mddi_client_lcd_vsync_detected(boolean detected) +{ + if (mddi_lcd.vsync_detected) + (*mddi_lcd.vsync_detected) (detected); +} + +/* extended version of function includes done callback */ +void mddi_window_adjust_ext(struct msm_fb_data_type *mfd, + uint16 x1, + uint16 x2, + uint16 y1, + uint16 y2, mddi_llist_done_cb_type done_cb) +{ +#ifdef FEATURE_MDDI_HITACHI + if (mfd->panel.id == HITACHI) + mddi_hitachi_window_adjust(x1, x2, y1, y2); +#elif defined(FEATURE_MDDI_S6D0142) + if (mfd->panel.id == MDDI_LCD_S6D0142) + mddi_s6d0142_window_adjust(x1, x2, y1, y2, done_cb); +#else + /* Do nothing then... except avoid lint/compiler warnings */ + (void)x1; + (void)x2; + (void)y1; + (void)y2; + (void)done_cb; +#endif +} + +void mddi_window_adjust(struct msm_fb_data_type *mfd, + uint16 x1, uint16 x2, uint16 y1, uint16 y2) +{ + mddi_window_adjust_ext(mfd, x1, x2, y1, y2, NULL); +} + +#ifdef MDDI_HOST_WINDOW_WORKAROUND +uint16 mddi_assign_pkt_height(uint16 pkt_width, + uint16 pkt_height, uint16 bpp) +{ + uint16 new_pkt_height; + uint16 problem_height = 0; + + if (pkt_width <= 240) { + if (bpp == 16) + problem_height = error_vals_16bpp[pkt_width-1]; + else if (bpp == 18) + problem_height = error_vals_18bpp[pkt_width-1]; + else { + printk(KERN_ERR"Invalid bpp value"); + return -EINVAL; + } + } + if (problem_height == pkt_height) + new_pkt_height = problem_height - 1; + else + new_pkt_height = pkt_height; + + return new_pkt_height; +} +#endif + +#ifdef ENABLE_MDDI_MULTI_READ_WRITE +int mddi_host_register_multiwrite(uint32 reg_addr, + uint32 *value_list_ptr, + uint32 value_count, boolean wait, mddi_llist_done_cb_type done_cb, + mddi_host_type host) +{ + mddi_linked_list_type *curr_llist_ptr; + mddi_linked_list_type *curr_llist_dma_ptr; + mddi_register_access_packet_type *regacc_pkt_ptr; + uint16 curr_llist_idx; + int ret = 0; + + if (!value_list_ptr || !value_count || + value_count > MDDI_HOST_MAX_CLIENT_REG_IN_SAME_ADDR) { + MDDI_MSG_ERR("\n Invalid value_list or value_count"); + return -EINVAL; + } + + if (in_interrupt()) + MDDI_MSG_CRIT("Called from ISR context\n"); + + if (!mddi_host_powered) { + MDDI_MSG_ERR("MDDI powered down!\n"); + mddi_init(); + } + + down(&mddi_host_mutex); + + curr_llist_idx = mddi_get_next_free_llist_item(host, TRUE); + curr_llist_ptr = &llist_extern[host][curr_llist_idx]; + curr_llist_dma_ptr = &llist_dma_extern[host][curr_llist_idx]; + + curr_llist_ptr->link_controller_flags = 1; + curr_llist_ptr->packet_header_count = 14; + curr_llist_ptr->packet_data_count = + (uint16)(value_count * 4); + + curr_llist_ptr->next_packet_pointer = NULL; + curr_llist_ptr->reserved = 0; + + regacc_pkt_ptr = &curr_llist_ptr->packet_header.register_pkt; + + regacc_pkt_ptr->packet_length = curr_llist_ptr->packet_header_count + + curr_llist_ptr->packet_data_count; + regacc_pkt_ptr->packet_type = 146; /* register access packet */ + regacc_pkt_ptr->bClient_ID = 0; + regacc_pkt_ptr->read_write_info = value_count; + regacc_pkt_ptr->register_address = reg_addr; + memcpy((void *)®acc_pkt_ptr->register_data_list[0], value_list_ptr, + curr_llist_ptr->packet_data_count); + + regacc_pkt_ptr = &curr_llist_dma_ptr->packet_header.register_pkt; + curr_llist_ptr->packet_data_pointer = + (void *)(®acc_pkt_ptr->register_data_list[0]); + MDDI_MSG_DEBUG("MultiReg Access write reg=0x%x, value[0]=0x%x\n", + regacc_pkt_ptr->register_address, + regacc_pkt_ptr->register_data_list[0]); + + /* now adjust pointers */ + mddi_queue_forward_packets(curr_llist_idx, curr_llist_idx, wait, + done_cb, host); + + up(&mddi_host_mutex); + + if (wait) { + int wait_ret; + + mddi_linked_list_notify_type *llist_notify_ptr; + llist_notify_ptr = &llist_extern_notify[host][curr_llist_idx]; + wait_ret = wait_for_completion_timeout( + &(llist_notify_ptr->done_comp), 5 * HZ); + + if (wait_ret <= 0) + ret = -EBUSY; + + if (wait_ret < 0) + printk(KERN_ERR "%s: failed to wait for completion!\n", + __func__); + else if (!wait_ret) + printk(KERN_ERR "%s: Timed out waiting!\n", __func__); + } + + return ret; +} + +int mddi_host_register_multiread(uint32 reg_addr, + uint32 *value_list_ptr, uint32 value_count, + boolean wait, mddi_host_type host) { + mddi_linked_list_type *curr_llist_ptr; + mddi_register_access_packet_type *regacc_pkt_ptr; + uint16 curr_llist_idx; + int ret = 0; + + if (!value_list_ptr || !value_count || + value_count >= MDDI_HOST_MAX_CLIENT_REG_IN_SAME_ADDR) { + MDDI_MSG_ERR("\n Invalid value_list or value_count"); + return -EINVAL; + } + + if (in_interrupt()) + MDDI_MSG_CRIT("Called from ISR context\n"); + + if (!mddi_host_powered) { + MDDI_MSG_ERR("MDDI powered down!\n"); + mddi_init(); + } + + down(&mddi_host_mutex); + + mddi_reg_read_value_ptr = value_list_ptr; + curr_llist_idx = mddi_get_reg_read_llist_item(host, TRUE); + if (curr_llist_idx == UNASSIGNED_INDEX) { + up(&mddi_host_mutex); + + /* need to change this to some sort of wait */ + MDDI_MSG_ERR("Attempting to queue up more than 1 reg read\n"); + return -EINVAL; + } + + curr_llist_ptr = &llist_extern[host][curr_llist_idx]; + curr_llist_ptr->link_controller_flags = 0x11; + curr_llist_ptr->packet_header_count = 14; + curr_llist_ptr->packet_data_count = 0; + + curr_llist_ptr->next_packet_pointer = NULL; + curr_llist_ptr->packet_data_pointer = NULL; + curr_llist_ptr->reserved = 0; + + regacc_pkt_ptr = &curr_llist_ptr->packet_header.register_pkt; + + regacc_pkt_ptr->packet_length = curr_llist_ptr->packet_header_count; + regacc_pkt_ptr->packet_type = 146; /* register access packet */ + regacc_pkt_ptr->bClient_ID = 0; + regacc_pkt_ptr->read_write_info = 0x8000 | value_count; + regacc_pkt_ptr->register_address = reg_addr; + + /* now adjust pointers */ + mddi_queue_forward_packets(curr_llist_idx, curr_llist_idx, wait, + NULL, host); + /* need to check if we can write the pointer or not */ + + up(&mddi_host_mutex); + + if (wait) { + int wait_ret; + + mddi_linked_list_notify_type *llist_notify_ptr; + llist_notify_ptr = &llist_extern_notify[host][curr_llist_idx]; + wait_ret = wait_for_completion_timeout( + &(llist_notify_ptr->done_comp), 5 * HZ); + + if (wait_ret <= 0) + ret = -EBUSY; + + if (wait_ret < 0) + printk(KERN_ERR "%s: failed to wait for completion!\n", + __func__); + else if (!wait_ret) + printk(KERN_ERR "%s: Timed out waiting!\n", __func__); + + if (!ret && (mddi_reg_read_value_ptr == value_list_ptr) && + (*value_list_ptr == -EBUSY)) { + printk(KERN_ERR "%s - failed to get data from client", + __func__); + mddi_reg_read_value_ptr = NULL; + ret = -EBUSY; + } + } + + MDDI_MSG_DEBUG("MultiReg Read value[0]=0x%x\n", *value_list_ptr); + + return ret; +} +#endif diff --git a/drivers/video/msm/mddihost.h b/drivers/video/msm/mddihost.h new file mode 100644 index 00000000000..52bc67c8150 --- /dev/null +++ b/drivers/video/msm/mddihost.h @@ -0,0 +1,231 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MDDIHOST_H +#define MDDIHOST_H + +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "msm_fb_panel.h" + +#undef FEATURE_MDDI_MC4 +#undef FEATURE_MDDI_S6D0142 +#undef FEATURE_MDDI_HITACHI +#define FEATURE_MDDI_SHARP +#define FEATURE_MDDI_TOSHIBA +#undef FEATURE_MDDI_E751 +#define FEATURE_MDDI_CORONA +#define FEATURE_MDDI_PRISM + +#define T_MSM7500 + +typedef enum { + format_16bpp, + format_18bpp, + format_24bpp +} mddi_video_format; + +typedef enum { + MDDI_LCD_NONE = 0, + MDDI_LCD_MC4, + MDDI_LCD_S6D0142, + MDDI_LCD_SHARP, + MDDI_LCD_E751, + MDDI_LCD_CORONA, + MDDI_LCD_HITACHI, + MDDI_LCD_TOSHIBA, + MDDI_LCD_PRISM, + MDDI_LCD_TP2, + MDDI_NUM_LCD_TYPES, + MDDI_LCD_DEFAULT = MDDI_LCD_TOSHIBA +} mddi_lcd_type; + +typedef enum { + MDDI_HOST_PRIM = 0, + MDDI_HOST_EXT, + MDDI_NUM_HOST_CORES +} mddi_host_type; + +typedef enum { + MDDI_DRIVER_RESET, /* host core registers have not been written. */ + MDDI_DRIVER_DISABLED, /* registers written, interrupts disabled. */ + MDDI_DRIVER_ENABLED /* registers written, interrupts enabled. */ +} mddi_host_driver_state_type; + +typedef enum { + MDDI_GPIO_INT_0 = 0, + MDDI_GPIO_INT_1, + MDDI_GPIO_INT_2, + MDDI_GPIO_INT_3, + MDDI_GPIO_INT_4, + MDDI_GPIO_INT_5, + MDDI_GPIO_INT_6, + MDDI_GPIO_INT_7, + MDDI_GPIO_INT_8, + MDDI_GPIO_INT_9, + MDDI_GPIO_INT_10, + MDDI_GPIO_INT_11, + MDDI_GPIO_INT_12, + MDDI_GPIO_INT_13, + MDDI_GPIO_INT_14, + MDDI_GPIO_INT_15, + MDDI_GPIO_NUM_INTS +} mddi_gpio_int_type; + +enum mddi_data_packet_size_type { + MDDI_DATA_PACKET_4_BYTES = 4, + MDDI_DATA_PACKET_8_BYTES = 8, + MDDI_DATA_PACKET_12_BYTES = 12, + MDDI_DATA_PACKET_16_BYTES = 16, + MDDI_DATA_PACKET_24_BYTES = 24 +}; + +typedef struct { + uint32 addr; + uint32 value; +} mddi_reg_write_type; + +boolean mddi_vsync_set_handler(msm_fb_vsync_handler_type handler, void *arg); + +typedef void (*mddi_llist_done_cb_type) (void); + +typedef void (*mddi_rev_handler_type) (void *); + +boolean mddi_set_rev_handler(mddi_rev_handler_type handler, uint16 pkt_type); + +#define MDDI_DEFAULT_PRIM_PIX_ATTR 0xC3 +#define MDDI_DEFAULT_SECD_PIX_ATTR 0xC0 + +typedef int gpio_int_polarity_type; +typedef int gpio_int_handler_type; + +typedef struct { + void (*vsync_detected) (boolean); +} mddi_lcd_func_type; + +extern mddi_lcd_func_type mddi_lcd; +extern int irq_enabled; +extern unsigned char mddi_timer_shutdown_flag; +extern struct mutex mddi_timer_lock; + +void mddi_init(void); +void mddi_powerdown(void); + +void mddi_host_start_ext_display(void); +void mddi_host_stop_ext_display(void); + +extern spinlock_t mddi_host_spin_lock; +#ifdef T_MSM7500 +void mddi_reset(void); +#ifdef FEATURE_DUAL_PROC_MODEM_DISPLAY +void mddi_host_switch_proc_control(boolean on); +#endif +#endif +void mddi_host_exit_power_collapse(void); + +void mddi_queue_splash_screen + (void *buf_ptr, + boolean clear_area, + int16 src_width, + int16 src_starting_row, + int16 src_starting_column, + int16 num_of_rows, + int16 num_of_columns, int16 dst_starting_row, int16 dst_starting_column); + +void mddi_queue_image + (void *buf_ptr, + uint8 stereo_video, + boolean clear_area, + int16 src_width, + int16 src_starting_row, + int16 src_starting_column, + int16 num_of_rows, + int16 num_of_columns, int16 dst_starting_row, int16 dst_starting_column); + +int mddi_host_register_read + (uint32 reg_addr, + uint32 *reg_value_ptr, boolean wait, mddi_host_type host_idx); +int mddi_host_register_write + (uint32 reg_addr, uint32 reg_val, + enum mddi_data_packet_size_type packet_size, + boolean wait, mddi_llist_done_cb_type done_cb, mddi_host_type host); +boolean mddi_host_register_write_int + (uint32 reg_addr, + uint32 reg_val, mddi_llist_done_cb_type done_cb, mddi_host_type host); +boolean mddi_host_register_read_int + (uint32 reg_addr, uint32 *reg_value_ptr, mddi_host_type host_idx); +void mddi_queue_register_write_static + (uint32 reg_addr, + uint32 reg_val, boolean wait, mddi_llist_done_cb_type done_cb); +void mddi_queue_static_window_adjust + (const mddi_reg_write_type *reg_write, + uint16 num_writes, mddi_llist_done_cb_type done_cb); + +#ifdef ENABLE_MDDI_MULTI_READ_WRITE +int mddi_host_register_multiwrite(uint32 reg_addr, + uint32 *value_list_ptr, uint32 value_count, + boolean wait, mddi_llist_done_cb_type done_cb, + mddi_host_type host); +int mddi_host_register_multiread(uint32 reg_addr, + uint32 *value_list_ptr, uint32 value_count, + boolean wait, mddi_host_type host); +#endif + +#define mddi_queue_register_read(reg, val_ptr, wait, sig) \ + mddi_host_register_read(reg, val_ptr, wait, MDDI_HOST_PRIM) +#define mddi_queue_register_write(reg, val, wait, sig) \ + mddi_host_register_write(reg, val, MDDI_DATA_PACKET_4_BYTES,\ + wait, NULL, MDDI_HOST_PRIM) +#define mddi_queue_register_write_extn(reg, val, pkt_size, wait, sig) \ + mddi_host_register_write(reg, val, pkt_size, \ + wait, NULL, MDDI_HOST_PRIM) +#define mddi_queue_register_write_int(reg, val) \ + mddi_host_register_write_int(reg, val, NULL, MDDI_HOST_PRIM) +#define mddi_queue_register_read_int(reg, val_ptr) \ + mddi_host_register_read_int(reg, val_ptr, MDDI_HOST_PRIM) +#define mddi_queue_register_writes(reg_ptr, val, wait, sig) \ + mddi_host_register_writes(reg_ptr, val, wait, sig, MDDI_HOST_PRIM) + +void mddi_wait(uint16 time_ms); +void mddi_assign_max_pkt_dimensions(uint16 image_cols, + uint16 image_rows, + uint16 bpp, + uint16 *max_cols, uint16 * max_rows); +#ifdef MDDI_HOST_WINDOW_WORKAROUND +uint16 mddi_assign_pkt_height(uint16 pkt_width, uint16 pkt_height, uint16 bpp); +#endif +void mddi_queue_reverse_encapsulation(boolean wait); +int mddi_client_power(unsigned int client_id); +void mddi_disable(int lock); +void mddi_window_adjust(struct msm_fb_data_type *mfd, + uint16 x1, uint16 x2, uint16 y1, uint16 y2); +void mddi_send_fw_link_skew_cal(mddi_host_type host_idx); +int pmdh_clk_func(int enable); + +#endif /* MDDIHOST_H */ diff --git a/drivers/video/msm/mddihost_e.c b/drivers/video/msm/mddihost_e.c new file mode 100644 index 00000000000..d53aa6fa7a5 --- /dev/null +++ b/drivers/video/msm/mddihost_e.c @@ -0,0 +1,59 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb.h" +#include "mddihost.h" +#include "mddihosti.h" + +#include +#include + +extern struct semaphore mddi_host_mutex; +static boolean mddi_host_ext_powered = FALSE; + +void mddi_host_start_ext_display(void) +{ + down(&mddi_host_mutex); + + if (!mddi_host_ext_powered) { + mddi_host_init(MDDI_HOST_EXT); + + mddi_host_ext_powered = TRUE; + } + + up(&mddi_host_mutex); +} + +void mddi_host_stop_ext_display(void) +{ + down(&mddi_host_mutex); + + if (mddi_host_ext_powered) { + mddi_host_powerdown(MDDI_HOST_EXT); + + mddi_host_ext_powered = FALSE; + } + + up(&mddi_host_mutex); +} diff --git a/drivers/video/msm/mddihosti.c b/drivers/video/msm/mddihosti.c new file mode 100644 index 00000000000..4989d355ee9 --- /dev/null +++ b/drivers/video/msm/mddihosti.c @@ -0,0 +1,2304 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb_panel.h" +#include "mddihost.h" +#include "mddihosti.h" + +#define FEATURE_MDDI_UNDERRUN_RECOVERY +#ifndef FEATURE_MDDI_DISABLE_REVERSE +static void mddi_read_rev_packet(byte *data_ptr); +#endif + +struct timer_list mddi_host_timer; + +#define MDDI_DEFAULT_TIMER_LENGTH 5000 /* 5 seconds */ +uint32 mddi_rtd_frequency = 60000; /* send RTD every 60 seconds */ +uint32 mddi_client_status_frequency = 60000; /* get status pkt every 60 secs */ + +boolean mddi_vsync_detect_enabled = FALSE; +mddi_gpio_info_type mddi_gpio; + +uint32 mddi_host_core_version; +boolean mddi_debug_log_statistics = FALSE; +/* #define FEATURE_MDDI_HOST_ENABLE_EARLY_HIBERNATION */ +/* default to TRUE in case MDP does not vote */ +static boolean mddi_host_mdp_active_flag = TRUE; +static uint32 mddi_log_stats_counter; +uint32 mddi_log_stats_frequency = 4000; +int32 mddi_client_type; + +#define MDDI_DEFAULT_REV_PKT_SIZE 0x20 + +#ifndef FEATURE_MDDI_DISABLE_REVERSE +static boolean mddi_rev_ptr_workaround = TRUE; +static uint32 mddi_reg_read_retry; +static uint32 mddi_reg_read_retry_max = 20; +static boolean mddi_enable_reg_read_retry = TRUE; +static boolean mddi_enable_reg_read_retry_once = FALSE; + +#define MDDI_MAX_REV_PKT_SIZE 0x60 + +#define MDDI_CLIENT_CAPABILITY_REV_PKT_SIZE 0x60 + +#define MDDI_VIDEO_REV_PKT_SIZE 0x40 +#define MDDI_REV_BUFFER_SIZE MDDI_MAX_REV_PKT_SIZE +static byte rev_packet_data[MDDI_MAX_REV_PKT_SIZE]; +#endif /* FEATURE_MDDI_DISABLE_REVERSE */ +/* leave these variables so graphics will compile */ + +#define MDDI_MAX_REV_DATA_SIZE 128 +/*lint -d__align(x) */ +boolean mddi_debug_clear_rev_data = TRUE; + +uint32 *mddi_reg_read_value_ptr; + +mddi_client_capability_type mddi_client_capability_pkt; +static boolean mddi_client_capability_request = FALSE; + +#ifndef FEATURE_MDDI_DISABLE_REVERSE + +#define MAX_MDDI_REV_HANDLERS 2 +#define INVALID_PKT_TYPE 0xFFFF + +typedef struct { + mddi_rev_handler_type handler; /* ISR to be executed */ + uint16 pkt_type; +} mddi_rev_pkt_handler_type; +static mddi_rev_pkt_handler_type mddi_rev_pkt_handler[MAX_MDDI_REV_HANDLERS] = + { {NULL, INVALID_PKT_TYPE}, {NULL, INVALID_PKT_TYPE} }; + +static boolean mddi_rev_encap_user_request = FALSE; +static mddi_linked_list_notify_type mddi_rev_user; + +spinlock_t mddi_host_spin_lock; +extern uint32 mdp_in_processing; +#endif + +typedef enum { + MDDI_REV_IDLE +#ifndef FEATURE_MDDI_DISABLE_REVERSE + , MDDI_REV_REG_READ_ISSUED, + MDDI_REV_REG_READ_SENT, + MDDI_REV_ENCAP_ISSUED, + MDDI_REV_STATUS_REQ_ISSUED, + MDDI_REV_CLIENT_CAP_ISSUED +#endif +} mddi_rev_link_state_type; + +typedef enum { + MDDI_LINK_DISABLED, + MDDI_LINK_HIBERNATING, + MDDI_LINK_ACTIVATING, + MDDI_LINK_ACTIVE +} mddi_host_link_state_type; + +typedef struct { + uint32 count; + uint32 in_count; + uint32 disp_req_count; + uint32 state_change_count; + uint32 ll_done_count; + uint32 rev_avail_count; + uint32 error_count; + uint32 rev_encap_count; + uint32 llist_ptr_write_1; + uint32 llist_ptr_write_2; +} mddi_host_int_type; + +typedef struct { + uint32 fwd_crc_count; + uint32 rev_crc_count; + uint32 pri_underflow; + uint32 sec_underflow; + uint32 rev_overflow; + uint32 pri_overwrite; + uint32 sec_overwrite; + uint32 rev_overwrite; + uint32 dma_failure; + uint32 rtd_failure; + uint32 reg_read_failure; +#ifdef FEATURE_MDDI_UNDERRUN_RECOVERY + uint32 pri_underrun_detected; +#endif +} mddi_host_stat_type; + +typedef struct { + uint32 rtd_cnt; + uint32 rev_enc_cnt; + uint32 vid_cnt; + uint32 reg_acc_cnt; + uint32 cli_stat_cnt; + uint32 cli_cap_cnt; + uint32 reg_read_cnt; + uint32 link_active_cnt; + uint32 link_hibernate_cnt; + uint32 vsync_response_cnt; + uint32 fwd_crc_cnt; + uint32 rev_crc_cnt; +} mddi_log_params_struct_type; + +typedef struct { + uint32 rtd_value; + uint32 rtd_counter; + uint32 client_status_cnt; + boolean rev_ptr_written; + uint8 *rev_ptr_start; + uint8 *rev_ptr_curr; + uint32 mddi_rev_ptr_write_val; + dma_addr_t rev_data_dma_addr; + uint16 rev_pkt_size; + mddi_rev_link_state_type rev_state; + mddi_host_link_state_type link_state; + mddi_host_driver_state_type driver_state; + boolean disable_hibernation; + uint32 saved_int_reg; + uint32 saved_int_en; + mddi_linked_list_type *llist_ptr; + dma_addr_t llist_dma_addr; + mddi_linked_list_type *llist_dma_ptr; + uint32 *rev_data_buf; + struct completion mddi_llist_avail_comp; + boolean mddi_waiting_for_llist_avail; + mddi_host_int_type int_type; + mddi_host_stat_type stats; + mddi_log_params_struct_type log_parms; + mddi_llist_info_type llist_info; + mddi_linked_list_notify_type llist_notify[MDDI_MAX_NUM_LLIST_ITEMS]; +} mddi_host_cntl_type; + +static mddi_host_type mddi_curr_host = MDDI_HOST_PRIM; +static mddi_host_cntl_type mhctl[MDDI_NUM_HOST_CORES]; +mddi_linked_list_type *llist_extern[MDDI_NUM_HOST_CORES]; +mddi_linked_list_type *llist_dma_extern[MDDI_NUM_HOST_CORES]; +mddi_linked_list_notify_type *llist_extern_notify[MDDI_NUM_HOST_CORES]; +static mddi_log_params_struct_type prev_parms[MDDI_NUM_HOST_CORES]; + +extern uint32 mdp_total_vdopkts; + +static boolean mddi_host_io_clock_on = FALSE; +static boolean mddi_host_hclk_on = FALSE; + +int int_mddi_pri_flag = FALSE; +int int_mddi_ext_flag = FALSE; + +static void mddi_report_errors(uint32 int_reg) +{ + mddi_host_type host_idx = mddi_curr_host; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + if (int_reg & MDDI_INT_PRI_UNDERFLOW) { + pmhctl->stats.pri_underflow++; + MDDI_MSG_ERR("!!! MDDI Primary Underflow !!!\n"); + } + if (int_reg & MDDI_INT_SEC_UNDERFLOW) { + pmhctl->stats.sec_underflow++; + MDDI_MSG_ERR("!!! MDDI Secondary Underflow !!!\n"); + } +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (int_reg & MDDI_INT_REV_OVERFLOW) { + pmhctl->stats.rev_overflow++; + MDDI_MSG_ERR("!!! MDDI Reverse Overflow !!!\n"); + pmhctl->rev_ptr_curr = pmhctl->rev_ptr_start; + mddi_host_reg_out(REV_PTR, pmhctl->mddi_rev_ptr_write_val); + + } + if (int_reg & MDDI_INT_CRC_ERROR) + MDDI_MSG_ERR("!!! MDDI Reverse CRC Error !!!\n"); +#endif + if (int_reg & MDDI_INT_PRI_OVERWRITE) { + pmhctl->stats.pri_overwrite++; + MDDI_MSG_ERR("!!! MDDI Primary Overwrite !!!\n"); + } + if (int_reg & MDDI_INT_SEC_OVERWRITE) { + pmhctl->stats.sec_overwrite++; + MDDI_MSG_ERR("!!! MDDI Secondary Overwrite !!!\n"); + } +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (int_reg & MDDI_INT_REV_OVERWRITE) { + pmhctl->stats.rev_overwrite++; + /* This will show up normally and is not a problem */ + MDDI_MSG_DEBUG("MDDI Reverse Overwrite!\n"); + } + if (int_reg & MDDI_INT_RTD_FAILURE) { + mddi_host_reg_outm(INTEN, MDDI_INT_RTD_FAILURE, 0); + pmhctl->stats.rtd_failure++; + MDDI_MSG_ERR("!!! MDDI RTD Failure !!!\n"); + } +#endif + if (int_reg & MDDI_INT_DMA_FAILURE) { + pmhctl->stats.dma_failure++; + MDDI_MSG_ERR("!!! MDDI DMA Abort !!!\n"); + } +} + +static void mddi_host_enable_io_clock(void) +{ + if (!MDDI_HOST_IS_IO_CLOCK_ON) + MDDI_HOST_ENABLE_IO_CLOCK; +} + +static void mddi_host_enable_hclk(void) +{ + + if (!MDDI_HOST_IS_HCLK_ON) + MDDI_HOST_ENABLE_HCLK; +} + +static void mddi_host_disable_io_clock(void) +{ +#ifndef FEATURE_MDDI_HOST_IO_CLOCK_CONTROL_DISABLE + if (MDDI_HOST_IS_IO_CLOCK_ON) + MDDI_HOST_DISABLE_IO_CLOCK; +#endif +} + +static void mddi_host_disable_hclk(void) +{ +#ifndef FEATURE_MDDI_HOST_HCLK_CONTROL_DISABLE + if (MDDI_HOST_IS_HCLK_ON) + MDDI_HOST_DISABLE_HCLK; +#endif +} + +static void mddi_vote_to_sleep(mddi_host_type host_idx, boolean sleep) +{ + uint16 vote_mask; + + if (host_idx == MDDI_HOST_PRIM) + vote_mask = 0x01; + else + vote_mask = 0x02; +} + +static void mddi_report_state_change(uint32 int_reg) +{ + mddi_host_type host_idx = mddi_curr_host; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + if ((pmhctl->saved_int_reg & MDDI_INT_IN_HIBERNATION) && + (pmhctl->saved_int_reg & MDDI_INT_LINK_ACTIVE)) { + /* recover from condition where the io_clock was turned off by the + clock driver during a transition to hibernation. The io_clock + disable is to prevent MDP/MDDI underruns when changing ARM + clock speeds. In the process of halting the ARM, the hclk + divider needs to be set to 1. When it is set to 1, there is + a small time (usecs) when hclk is off or slow, and this can + cause an underrun. To prevent the underrun, clock driver turns + off the MDDI io_clock before making the change. */ + mddi_host_reg_out(CMD, MDDI_CMD_POWERUP); + } + + if (int_reg & MDDI_INT_LINK_ACTIVE) { + pmhctl->link_state = MDDI_LINK_ACTIVE; + pmhctl->log_parms.link_active_cnt++; + pmhctl->rtd_value = mddi_host_reg_in(RTD_VAL); + MDDI_MSG_DEBUG("!!! MDDI Active RTD:0x%x!!!\n", + pmhctl->rtd_value); + /* now interrupt on hibernation */ + mddi_host_reg_outm(INTEN, + (MDDI_INT_IN_HIBERNATION | + MDDI_INT_LINK_ACTIVE), + MDDI_INT_IN_HIBERNATION); + +#ifdef DEBUG_MDDIHOSTI + /* if gpio interrupt is enabled, start polling at fastest + * registered rate + */ + if (mddi_gpio.polling_enabled) { + timer_reg(&mddi_gpio_poll_timer, + mddi_gpio_poll_timer_cb, 0, mddi_gpio.polling_interval, 0); + } +#endif +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (mddi_rev_ptr_workaround) { + /* HW CR: need to reset reverse register stuff */ + pmhctl->rev_ptr_written = FALSE; + pmhctl->rev_ptr_curr = pmhctl->rev_ptr_start; + } +#endif + /* vote on sleep */ + mddi_vote_to_sleep(host_idx, FALSE); + + if (host_idx == MDDI_HOST_PRIM) { + if (mddi_vsync_detect_enabled) { + /* + * Indicate to client specific code that vsync + * was enabled, but we did not detect a client + * intiated wakeup. The client specific + * handler can either reassert vsync detection, + * or treat this as a valid vsync. + */ + mddi_client_lcd_vsync_detected(FALSE); + pmhctl->log_parms.vsync_response_cnt++; + } + } + } + if (int_reg & MDDI_INT_IN_HIBERNATION) { + pmhctl->link_state = MDDI_LINK_HIBERNATING; + pmhctl->log_parms.link_hibernate_cnt++; + MDDI_MSG_DEBUG("!!! MDDI Hibernating !!!\n"); + + if (mddi_client_type == 2) { + mddi_host_reg_out(PAD_CTL, 0x402a850f); + mddi_host_reg_out(PAD_CAL, 0x10220020); + mddi_host_reg_out(TA1_LEN, 0x0010); + mddi_host_reg_out(TA2_LEN, 0x0040); + } + /* now interrupt on link_active */ +#ifdef FEATURE_MDDI_DISABLE_REVERSE + mddi_host_reg_outm(INTEN, + (MDDI_INT_MDDI_IN | + MDDI_INT_IN_HIBERNATION | + MDDI_INT_LINK_ACTIVE), + MDDI_INT_LINK_ACTIVE); +#else + mddi_host_reg_outm(INTEN, + (MDDI_INT_MDDI_IN | + MDDI_INT_IN_HIBERNATION | + MDDI_INT_LINK_ACTIVE), + (MDDI_INT_MDDI_IN | MDDI_INT_LINK_ACTIVE)); + + pmhctl->rtd_counter = mddi_rtd_frequency; + + if (pmhctl->rev_state != MDDI_REV_IDLE) { + /* a rev_encap will not wake up the link, so we do that here */ + pmhctl->link_state = MDDI_LINK_ACTIVATING; + mddi_host_reg_out(CMD, MDDI_CMD_LINK_ACTIVE); + } +#endif + + if (pmhctl->disable_hibernation) { + mddi_host_reg_out(CMD, MDDI_CMD_HIBERNATE); + mddi_host_reg_out(CMD, MDDI_CMD_LINK_ACTIVE); + pmhctl->link_state = MDDI_LINK_ACTIVATING; + } +#ifdef FEATURE_MDDI_UNDERRUN_RECOVERY + if ((pmhctl->llist_info.transmitting_start_idx != + UNASSIGNED_INDEX) + && + ((pmhctl-> + saved_int_reg & (MDDI_INT_PRI_LINK_LIST_DONE | + MDDI_INT_PRI_PTR_READ)) == + MDDI_INT_PRI_PTR_READ)) { + mddi_linked_list_type *llist_dma; + llist_dma = pmhctl->llist_dma_ptr; + /* + * All indications are that we have not received a + * linked list done interrupt, due to an underrun + * condition. Recovery attempt is to send again. + */ + dma_coherent_pre_ops(); + /* Write to primary pointer register again */ + mddi_host_reg_out(PRI_PTR, + &llist_dma[pmhctl->llist_info. + transmitting_start_idx]); + pmhctl->stats.pri_underrun_detected++; + } +#endif + + /* vote on sleep */ + if (pmhctl->link_state == MDDI_LINK_HIBERNATING) { + mddi_vote_to_sleep(host_idx, TRUE); + } + +#ifdef DEBUG_MDDIHOSTI + /* need to stop polling timer */ + if (mddi_gpio.polling_enabled) { + (void) timer_clr(&mddi_gpio_poll_timer, T_NONE); + } +#endif + } +} + +void mddi_host_timer_service(unsigned long data) +{ +#ifndef FEATURE_MDDI_DISABLE_REVERSE + unsigned long flags; +#endif + mddi_host_type host_idx; + mddi_host_cntl_type *pmhctl; + + unsigned long time_ms = MDDI_DEFAULT_TIMER_LENGTH; + init_timer(&mddi_host_timer); + for (host_idx = MDDI_HOST_PRIM; host_idx < MDDI_NUM_HOST_CORES; + host_idx++) { + pmhctl = &(mhctl[host_idx]); + mddi_log_stats_counter += (uint32) time_ms; +#ifndef FEATURE_MDDI_DISABLE_REVERSE + pmhctl->rtd_counter += (uint32) time_ms; + pmhctl->client_status_cnt += (uint32) time_ms; + + if (host_idx == MDDI_HOST_PRIM) { + if (pmhctl->client_status_cnt >= + mddi_client_status_frequency) { + if ((pmhctl->link_state == + MDDI_LINK_HIBERNATING) + && (pmhctl->client_status_cnt > + mddi_client_status_frequency)) { + /* + * special case where we are hibernating + * and mddi_host_isr is not firing, so + * kick the link so that the status can + * be retrieved + */ + + /* need to wake up link before issuing + * rev encap command + */ + MDDI_MSG_INFO("wake up link!\n"); + spin_lock_irqsave(&mddi_host_spin_lock, + flags); + mddi_host_enable_hclk(); + mddi_host_enable_io_clock(); + pmhctl->link_state = + MDDI_LINK_ACTIVATING; + mddi_host_reg_out(CMD, + MDDI_CMD_LINK_ACTIVE); + spin_unlock_irqrestore + (&mddi_host_spin_lock, flags); + } else + if ((pmhctl->link_state == MDDI_LINK_ACTIVE) + && pmhctl->disable_hibernation) { + /* + * special case where we have disabled + * hibernation and mddi_host_isr + * is not firing, so enable interrupt + * for no pkts pending, which will + * generate an interrupt + */ + MDDI_MSG_INFO("kick isr!\n"); + spin_lock_irqsave(&mddi_host_spin_lock, + flags); + mddi_host_enable_hclk(); + mddi_host_reg_outm(INTEN, + MDDI_INT_NO_CMD_PKTS_PEND, + MDDI_INT_NO_CMD_PKTS_PEND); + spin_unlock_irqrestore + (&mddi_host_spin_lock, flags); + } + } + } +#endif /* #ifndef FEATURE_MDDI_DISABLE_REVERSE */ + } + + /* Check if logging is turned on */ + for (host_idx = MDDI_HOST_PRIM; host_idx < MDDI_NUM_HOST_CORES; + host_idx++) { + mddi_log_params_struct_type *prev_ptr = &(prev_parms[host_idx]); + pmhctl = &(mhctl[host_idx]); + + if (mddi_debug_log_statistics) { + + /* get video pkt count from MDP, since MDDI sw cannot know this */ + pmhctl->log_parms.vid_cnt = mdp_total_vdopkts; + + if (mddi_log_stats_counter >= mddi_log_stats_frequency) { + /* mddi_log_stats_counter = 0; */ + if (mddi_debug_log_statistics) { + MDDI_MSG_NOTICE + ("MDDI Statistics since last report:\n"); + MDDI_MSG_NOTICE(" Packets sent:\n"); + MDDI_MSG_NOTICE + (" %d RTD packet(s)\n", + pmhctl->log_parms.rtd_cnt - + prev_ptr->rtd_cnt); + if (prev_ptr->rtd_cnt != + pmhctl->log_parms.rtd_cnt) { + unsigned long flags; + spin_lock_irqsave + (&mddi_host_spin_lock, + flags); + mddi_host_enable_hclk(); + pmhctl->rtd_value = + mddi_host_reg_in(RTD_VAL); + spin_unlock_irqrestore + (&mddi_host_spin_lock, + flags); + MDDI_MSG_NOTICE + (" RTD value=%d\n", + pmhctl->rtd_value); + } + MDDI_MSG_NOTICE + (" %d VIDEO packets\n", + pmhctl->log_parms.vid_cnt - + prev_ptr->vid_cnt); + MDDI_MSG_NOTICE + (" %d Register Access packets\n", + pmhctl->log_parms.reg_acc_cnt - + prev_ptr->reg_acc_cnt); + MDDI_MSG_NOTICE + (" %d Reverse Encapsulation packet(s)\n", + pmhctl->log_parms.rev_enc_cnt - + prev_ptr->rev_enc_cnt); + if (prev_ptr->rev_enc_cnt != + pmhctl->log_parms.rev_enc_cnt) { + /* report # of reverse CRC errors */ + MDDI_MSG_NOTICE + (" %d reverse CRC errors detected\n", + pmhctl->log_parms. + rev_crc_cnt - + prev_ptr->rev_crc_cnt); + } + MDDI_MSG_NOTICE + (" Packets received:\n"); + MDDI_MSG_NOTICE + (" %d Client Status packets", + pmhctl->log_parms.cli_stat_cnt - + prev_ptr->cli_stat_cnt); + if (prev_ptr->cli_stat_cnt != + pmhctl->log_parms.cli_stat_cnt) { + MDDI_MSG_NOTICE + (" %d forward CRC errors reported\n", + pmhctl->log_parms. + fwd_crc_cnt - + prev_ptr->fwd_crc_cnt); + } + MDDI_MSG_NOTICE + (" %d Register Access Read packets\n", + pmhctl->log_parms.reg_read_cnt - + prev_ptr->reg_read_cnt); + + if (pmhctl->link_state == + MDDI_LINK_ACTIVE) { + MDDI_MSG_NOTICE + (" Current Link Status: Active\n"); + } else + if ((pmhctl->link_state == + MDDI_LINK_HIBERNATING) + || (pmhctl->link_state == + MDDI_LINK_ACTIVATING)) { + MDDI_MSG_NOTICE + (" Current Link Status: Hibernation\n"); + } else { + MDDI_MSG_NOTICE + (" Current Link Status: Inactive\n"); + } + MDDI_MSG_NOTICE + (" Active state entered %d times\n", + pmhctl->log_parms.link_active_cnt - + prev_ptr->link_active_cnt); + MDDI_MSG_NOTICE + (" Hibernation state entered %d times\n", + pmhctl->log_parms. + link_hibernate_cnt - + prev_ptr->link_hibernate_cnt); + } + } + prev_parms[host_idx] = pmhctl->log_parms; + } + } + if (mddi_log_stats_counter >= mddi_log_stats_frequency) + mddi_log_stats_counter = 0; + + mutex_lock(&mddi_timer_lock); + if (!mddi_timer_shutdown_flag) { + mddi_host_timer.function = mddi_host_timer_service; + mddi_host_timer.data = 0; + mddi_host_timer.expires = jiffies + ((time_ms * HZ) / 1000); + add_timer(&mddi_host_timer); + } + mutex_unlock(&mddi_timer_lock); + + return; +} /* mddi_host_timer_cb */ + +static void mddi_process_link_list_done(void) +{ + mddi_host_type host_idx = mddi_curr_host; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + /* normal forward linked list packet(s) were sent */ + if (pmhctl->llist_info.transmitting_start_idx == UNASSIGNED_INDEX) { + MDDI_MSG_ERR("**** getting LL done, but no list ****\n"); + } else { + uint16 idx; + +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (pmhctl->rev_state == MDDI_REV_REG_READ_ISSUED) { + /* special case where a register read packet was sent */ + pmhctl->rev_state = MDDI_REV_REG_READ_SENT; + if (pmhctl->llist_info.reg_read_idx == UNASSIGNED_INDEX) { + MDDI_MSG_ERR + ("**** getting LL done, but no list ****\n"); + } + } +#endif + for (idx = pmhctl->llist_info.transmitting_start_idx;;) { + uint16 next_idx = pmhctl->llist_notify[idx].next_idx; + /* with reg read we don't release the waiting tcb until after + * the reverse encapsulation has completed. + */ + if (idx != pmhctl->llist_info.reg_read_idx) { + /* notify task that may be waiting on this completion */ + if (pmhctl->llist_notify[idx].waiting) { + complete(& + (pmhctl->llist_notify[idx]. + done_comp)); + } + if (pmhctl->llist_notify[idx].done_cb != NULL) { + (*(pmhctl->llist_notify[idx].done_cb)) + (); + } + + pmhctl->llist_notify[idx].in_use = FALSE; + pmhctl->llist_notify[idx].waiting = FALSE; + pmhctl->llist_notify[idx].done_cb = NULL; + if (idx < MDDI_NUM_DYNAMIC_LLIST_ITEMS) { + /* static LLIST items are configured only once */ + pmhctl->llist_notify[idx].next_idx = + UNASSIGNED_INDEX; + } + /* + * currently, all linked list packets are + * register access, so we can increment the + * counter for that packet type here. + */ + pmhctl->log_parms.reg_acc_cnt++; + } + if (idx == pmhctl->llist_info.transmitting_end_idx) + break; + idx = next_idx; + if (idx == UNASSIGNED_INDEX) + MDDI_MSG_CRIT("MDDI linked list corruption!\n"); + } + + pmhctl->llist_info.transmitting_start_idx = UNASSIGNED_INDEX; + pmhctl->llist_info.transmitting_end_idx = UNASSIGNED_INDEX; + + if (pmhctl->mddi_waiting_for_llist_avail) { + if (! + (pmhctl-> + llist_notify[pmhctl->llist_info.next_free_idx]. + in_use)) { + pmhctl->mddi_waiting_for_llist_avail = FALSE; + complete(&(pmhctl->mddi_llist_avail_comp)); + } + } + } + + /* Turn off MDDI_INT_PRI_LINK_LIST_DONE interrupt */ + mddi_host_reg_outm(INTEN, MDDI_INT_PRI_LINK_LIST_DONE, 0); + +} + +static void mddi_queue_forward_linked_list(void) +{ + uint16 first_pkt_index; + mddi_linked_list_type *llist_dma; + mddi_host_type host_idx = mddi_curr_host; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + llist_dma = pmhctl->llist_dma_ptr; + + first_pkt_index = UNASSIGNED_INDEX; + + if (pmhctl->llist_info.transmitting_start_idx == UNASSIGNED_INDEX) { +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (pmhctl->llist_info.reg_read_waiting) { + if (pmhctl->rev_state == MDDI_REV_IDLE) { + /* + * we have a register read to send and + * can send it now + */ + pmhctl->rev_state = MDDI_REV_REG_READ_ISSUED; + mddi_reg_read_retry = 0; + first_pkt_index = + pmhctl->llist_info.waiting_start_idx; + pmhctl->llist_info.reg_read_waiting = FALSE; + } + } else +#endif + { + /* + * not register read to worry about, go ahead and write + * anything that may be on the waiting list. + */ + first_pkt_index = pmhctl->llist_info.waiting_start_idx; + } + } + + if (first_pkt_index != UNASSIGNED_INDEX) { + pmhctl->llist_info.transmitting_start_idx = + pmhctl->llist_info.waiting_start_idx; + pmhctl->llist_info.transmitting_end_idx = + pmhctl->llist_info.waiting_end_idx; + pmhctl->llist_info.waiting_start_idx = UNASSIGNED_INDEX; + pmhctl->llist_info.waiting_end_idx = UNASSIGNED_INDEX; + + /* write to the primary pointer register */ + MDDI_MSG_DEBUG("MDDI writing primary ptr with idx=%d\n", + first_pkt_index); + + pmhctl->int_type.llist_ptr_write_2++; + + dma_coherent_pre_ops(); + mddi_host_reg_out(PRI_PTR, &llist_dma[first_pkt_index]); + + /* enable interrupt when complete */ + mddi_host_reg_outm(INTEN, MDDI_INT_PRI_LINK_LIST_DONE, + MDDI_INT_PRI_LINK_LIST_DONE); + + } + +} + +#ifndef FEATURE_MDDI_DISABLE_REVERSE +static void mddi_read_rev_packet(byte *data_ptr) +{ + uint16 i, length; + mddi_host_type host_idx = mddi_curr_host; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + uint8 *rev_ptr_overflow = + (pmhctl->rev_ptr_start + MDDI_REV_BUFFER_SIZE); + + /* first determine the length and handle invalid lengths */ + length = *pmhctl->rev_ptr_curr++; + if (pmhctl->rev_ptr_curr >= rev_ptr_overflow) + pmhctl->rev_ptr_curr = pmhctl->rev_ptr_start; + length |= ((*pmhctl->rev_ptr_curr++) << 8); + if (pmhctl->rev_ptr_curr >= rev_ptr_overflow) + pmhctl->rev_ptr_curr = pmhctl->rev_ptr_start; + if (length > (pmhctl->rev_pkt_size - 2)) { + MDDI_MSG_ERR("Invalid rev pkt length %d\n", length); + /* rev_pkt_size should always be <= rev_ptr_size so limit to packet size */ + length = pmhctl->rev_pkt_size - 2; + } + + /* If the data pointer is NULL, just increment the pmhctl->rev_ptr_curr. + * Loop around if necessary. Don't bother reading the data. + */ + if (data_ptr == NULL) { + pmhctl->rev_ptr_curr += length; + if (pmhctl->rev_ptr_curr >= rev_ptr_overflow) + pmhctl->rev_ptr_curr -= MDDI_REV_BUFFER_SIZE; + return; + } + + data_ptr[0] = length & 0x0ff; + data_ptr[1] = length >> 8; + data_ptr += 2; + /* copy the data to data_ptr byte-at-a-time */ + for (i = 0; (i < length) && (pmhctl->rev_ptr_curr < rev_ptr_overflow); + i++) + *data_ptr++ = *pmhctl->rev_ptr_curr++; + if (pmhctl->rev_ptr_curr >= rev_ptr_overflow) + pmhctl->rev_ptr_curr = pmhctl->rev_ptr_start; + for (; (i < length) && (pmhctl->rev_ptr_curr < rev_ptr_overflow); i++) + *data_ptr++ = *pmhctl->rev_ptr_curr++; +} + +static void mddi_process_rev_packets(void) +{ + uint32 rev_packet_count; + word i; + uint32 crc_errors; + boolean mddi_reg_read_successful = FALSE; + mddi_host_type host_idx = mddi_curr_host; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + pmhctl->log_parms.rev_enc_cnt++; + if ((pmhctl->rev_state != MDDI_REV_ENCAP_ISSUED) && + (pmhctl->rev_state != MDDI_REV_STATUS_REQ_ISSUED) && + (pmhctl->rev_state != MDDI_REV_CLIENT_CAP_ISSUED)) { + MDDI_MSG_ERR("Wrong state %d for reverse int\n", + pmhctl->rev_state); + } + /* Turn off MDDI_INT_REV_AVAIL interrupt */ + mddi_host_reg_outm(INTEN, MDDI_INT_REV_DATA_AVAIL, 0); + + /* Clear rev data avail int */ + mddi_host_reg_out(INT, MDDI_INT_REV_DATA_AVAIL); + + /* Get Number of packets */ + rev_packet_count = mddi_host_reg_in(REV_PKT_CNT); + +#ifndef T_MSM7500 + /* Clear out rev packet counter */ + mddi_host_reg_out(REV_PKT_CNT, 0x0000); +#endif + +#if defined(CONFIG_FB_MSM_MDP31) || defined(CONFIG_FB_MSM_MDP40) + if ((pmhctl->rev_state == MDDI_REV_CLIENT_CAP_ISSUED) && + (rev_packet_count > 0) && + (mddi_host_core_version == 0x28 || + mddi_host_core_version == 0x30)) { + + uint32 int_reg; + uint32 max_count = 0; + + mddi_host_reg_out(REV_PTR, pmhctl->mddi_rev_ptr_write_val); + int_reg = mddi_host_reg_in(INT); + while ((int_reg & 0x100000) == 0) { + udelay(3); + int_reg = mddi_host_reg_in(INT); + if (++max_count > 100) + break; + } + } +#endif + + /* Get CRC error count */ + crc_errors = mddi_host_reg_in(REV_CRC_ERR); + if (crc_errors != 0) { + pmhctl->log_parms.rev_crc_cnt += crc_errors; + pmhctl->stats.rev_crc_count += crc_errors; + MDDI_MSG_ERR("!!! MDDI %d Reverse CRC Error(s) !!!\n", + crc_errors); +#ifndef T_MSM7500 + /* Clear CRC error count */ + mddi_host_reg_out(REV_CRC_ERR, 0x0000); +#endif + /* also issue an RTD to attempt recovery */ + pmhctl->rtd_counter = mddi_rtd_frequency; + } + + pmhctl->rtd_value = mddi_host_reg_in(RTD_VAL); + + MDDI_MSG_DEBUG("MDDI rev pkt cnt=%d, ptr=0x%x, RTD:0x%x\n", + rev_packet_count, + pmhctl->rev_ptr_curr - pmhctl->rev_ptr_start, + pmhctl->rtd_value); + + if (rev_packet_count >= 1) { + mddi_invalidate_cache_lines((uint32 *) pmhctl->rev_ptr_start, + MDDI_REV_BUFFER_SIZE); + } else { + MDDI_MSG_ERR("Reverse pkt sent, no data rxd\n"); + if (mddi_reg_read_value_ptr) + *mddi_reg_read_value_ptr = -EBUSY; + } + /* order the reads */ + dma_coherent_post_ops(); + for (i = 0; i < rev_packet_count; i++) { + mddi_rev_packet_type *rev_pkt_ptr; + + mddi_read_rev_packet(rev_packet_data); + + rev_pkt_ptr = (mddi_rev_packet_type *) rev_packet_data; + + if (rev_pkt_ptr->packet_length > pmhctl->rev_pkt_size) { + MDDI_MSG_ERR("!!!invalid packet size: %d\n", + rev_pkt_ptr->packet_length); + } + + MDDI_MSG_DEBUG("MDDI rev pkt 0x%x size 0x%x\n", + rev_pkt_ptr->packet_type, + rev_pkt_ptr->packet_length); + + /* Do whatever you want to do with the data based on the packet type */ + switch (rev_pkt_ptr->packet_type) { + case 66: /* Client Capability */ + { + mddi_client_capability_type + *client_capability_pkt_ptr; + + client_capability_pkt_ptr = + (mddi_client_capability_type *) + rev_packet_data; + MDDI_MSG_NOTICE + ("Client Capability: Week=%d, Year=%d\n", + client_capability_pkt_ptr-> + Week_of_Manufacture, + client_capability_pkt_ptr-> + Year_of_Manufacture); + memcpy((void *)&mddi_client_capability_pkt, + (void *)rev_packet_data, + sizeof(mddi_client_capability_type)); + pmhctl->log_parms.cli_cap_cnt++; + } + break; + + case 70: /* Display Status */ + { + mddi_client_status_type *client_status_pkt_ptr; + + client_status_pkt_ptr = + (mddi_client_status_type *) rev_packet_data; + if ((client_status_pkt_ptr->crc_error_count != + 0) + || (client_status_pkt_ptr-> + reverse_link_request != 0)) { + MDDI_MSG_ERR + ("Client Status: RevReq=%d, CrcErr=%d\n", + client_status_pkt_ptr-> + reverse_link_request, + client_status_pkt_ptr-> + crc_error_count); + } else { + MDDI_MSG_DEBUG + ("Client Status: RevReq=%d, CrcErr=%d\n", + client_status_pkt_ptr-> + reverse_link_request, + client_status_pkt_ptr-> + crc_error_count); + } + pmhctl->log_parms.fwd_crc_cnt += + client_status_pkt_ptr->crc_error_count; + pmhctl->stats.fwd_crc_count += + client_status_pkt_ptr->crc_error_count; + pmhctl->log_parms.cli_stat_cnt++; + } + break; + + case 146: /* register access packet */ + { + mddi_register_access_packet_type + * regacc_pkt_ptr; + uint32 data_count; + + regacc_pkt_ptr = + (mddi_register_access_packet_type *) + rev_packet_data; + + /* Bits[0:13] - read data count */ + data_count = regacc_pkt_ptr->read_write_info + & 0x3FFF; + MDDI_MSG_DEBUG("\n MDDI rev read: 0x%x", + regacc_pkt_ptr->read_write_info); + MDDI_MSG_DEBUG("Reg Acc parse reg=0x%x," + "value=0x%x\n", regacc_pkt_ptr-> + register_address, regacc_pkt_ptr-> + register_data_list[0]); + + /* Copy register value to location passed in */ + if (mddi_reg_read_value_ptr) { +#if defined(T_MSM6280) && !defined(T_MSM7200) + /* only least significant 16 bits are valid with 6280 */ + *mddi_reg_read_value_ptr = + regacc_pkt_ptr-> + register_data_list[0] & 0x0000ffff; + mddi_reg_read_successful = TRUE; + mddi_reg_read_value_ptr = NULL; +#else + if (data_count && data_count <= + MDDI_HOST_MAX_CLIENT_REG_IN_SAME_ADDR) { + memcpy(mddi_reg_read_value_ptr, + (void *)®acc_pkt_ptr-> + register_data_list[0], + data_count * 4); + mddi_reg_read_successful = TRUE; + mddi_reg_read_value_ptr = NULL; + } +#endif + } + +#ifdef DEBUG_MDDIHOSTI + if ((mddi_gpio.polling_enabled) && + (regacc_pkt_ptr->register_address == + mddi_gpio.polling_reg)) { + /* + * ToDo: need to call Linux GPIO call + * here... + */ + mddi_client_lcd_gpio_poll( + regacc_pkt_ptr->register_data_list[0]); + } +#endif + pmhctl->log_parms.reg_read_cnt++; + } + break; + + case INVALID_PKT_TYPE: /* 0xFFFF */ + MDDI_MSG_ERR("!!!INVALID_PKT_TYPE rcvd\n"); + break; + + default: /* any other packet */ + { + uint16 hdlr; + + for (hdlr = 0; hdlr < MAX_MDDI_REV_HANDLERS; + hdlr++) { + if (mddi_rev_pkt_handler[hdlr]. + handler == NULL) + continue; + if (mddi_rev_pkt_handler[hdlr]. + pkt_type == + rev_pkt_ptr->packet_type) { + (*(mddi_rev_pkt_handler[hdlr]. + handler)) (rev_pkt_ptr); + /* pmhctl->rev_state = MDDI_REV_IDLE; */ + break; + } + } + if (hdlr >= MAX_MDDI_REV_HANDLERS) + MDDI_MSG_ERR("MDDI unknown rev pkt\n"); + } + break; + } + } + if ((pmhctl->rev_ptr_curr + pmhctl->rev_pkt_size) >= + (pmhctl->rev_ptr_start + MDDI_REV_BUFFER_SIZE)) { + pmhctl->rev_ptr_written = FALSE; + } + + if (pmhctl->rev_state == MDDI_REV_ENCAP_ISSUED) { + pmhctl->rev_state = MDDI_REV_IDLE; + if (mddi_rev_user.waiting) { + mddi_rev_user.waiting = FALSE; + complete(&(mddi_rev_user.done_comp)); + } else if (pmhctl->llist_info.reg_read_idx == UNASSIGNED_INDEX) { + MDDI_MSG_ERR + ("Reverse Encap state, but no reg read in progress\n"); + } else { + if ((!mddi_reg_read_successful) && + (mddi_reg_read_retry < mddi_reg_read_retry_max) && + (mddi_enable_reg_read_retry)) { + /* + * There is a race condition that can happen + * where the reverse encapsulation message is + * sent out by the MDDI host before the register + * read packet is sent. As a work-around for + * that problem we issue the reverse + * encapsulation one more time before giving up. + */ + if (mddi_enable_reg_read_retry_once) + mddi_reg_read_retry = + mddi_reg_read_retry_max; + else + mddi_reg_read_retry++; + pmhctl->rev_state = MDDI_REV_REG_READ_SENT; + pmhctl->stats.reg_read_failure++; + } else { + uint16 reg_read_idx = + pmhctl->llist_info.reg_read_idx; + + mddi_reg_read_retry = 0; + if (pmhctl->llist_notify[reg_read_idx].waiting) { + complete(& + (pmhctl-> + llist_notify[reg_read_idx]. + done_comp)); + } + pmhctl->llist_info.reg_read_idx = + UNASSIGNED_INDEX; + if (pmhctl->llist_notify[reg_read_idx]. + done_cb != NULL) { + (* + (pmhctl->llist_notify[reg_read_idx]. + done_cb)) (); + } + pmhctl->llist_notify[reg_read_idx].next_idx = + UNASSIGNED_INDEX; + pmhctl->llist_notify[reg_read_idx].in_use = + FALSE; + pmhctl->llist_notify[reg_read_idx].waiting = + FALSE; + pmhctl->llist_notify[reg_read_idx].done_cb = + NULL; + if (!mddi_reg_read_successful) + pmhctl->stats.reg_read_failure++; + } + } + } else if (pmhctl->rev_state == MDDI_REV_CLIENT_CAP_ISSUED) { +#if defined(CONFIG_FB_MSM_MDP31) || defined(CONFIG_FB_MSM_MDP40) + if (mddi_host_core_version == 0x28 || + mddi_host_core_version == 0x30) { + mddi_host_reg_out(FIFO_ALLOC, 0x00); + pmhctl->rev_ptr_written = TRUE; + mddi_host_reg_out(REV_PTR, + pmhctl->mddi_rev_ptr_write_val); + pmhctl->rev_ptr_curr = pmhctl->rev_ptr_start; + mddi_host_reg_out(CMD, 0xC00); + } +#endif + + if (mddi_rev_user.waiting) { + mddi_rev_user.waiting = FALSE; + complete(&(mddi_rev_user.done_comp)); + } + pmhctl->rev_state = MDDI_REV_IDLE; + } else { + pmhctl->rev_state = MDDI_REV_IDLE; + } + + /* pmhctl->rev_state = MDDI_REV_IDLE; */ + + /* Re-enable interrupt */ + mddi_host_reg_outm(INTEN, MDDI_INT_REV_DATA_AVAIL, + MDDI_INT_REV_DATA_AVAIL); + +} + +static void mddi_issue_reverse_encapsulation(void) +{ + mddi_host_type host_idx = mddi_curr_host; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + /* Only issue a reverse encapsulation packet if: + * 1) another reverse is not in progress (MDDI_REV_IDLE). + * 2) a register read has been sent (MDDI_REV_REG_READ_SENT). + * 3) forward is not in progress, because of a hw bug in client that + * causes forward crc errors on packet immediately after rev encap. + */ + if (((pmhctl->rev_state == MDDI_REV_IDLE) || + (pmhctl->rev_state == MDDI_REV_REG_READ_SENT)) && + (pmhctl->llist_info.transmitting_start_idx == UNASSIGNED_INDEX) && + (!mdp_in_processing)) { + uint32 mddi_command = MDDI_CMD_SEND_REV_ENCAP; + + if ((pmhctl->rev_state == MDDI_REV_REG_READ_SENT) || + (mddi_rev_encap_user_request == TRUE)) { + mddi_host_enable_io_clock(); + if (pmhctl->link_state == MDDI_LINK_HIBERNATING) { + /* need to wake up link before issuing rev encap command */ + MDDI_MSG_DEBUG("wake up link!\n"); + pmhctl->link_state = MDDI_LINK_ACTIVATING; + mddi_host_reg_out(CMD, MDDI_CMD_LINK_ACTIVE); + } else { + if (pmhctl->rtd_counter >= mddi_rtd_frequency) { + MDDI_MSG_DEBUG + ("mddi sending RTD command!\n"); + mddi_host_reg_out(CMD, + MDDI_CMD_SEND_RTD); + pmhctl->rtd_counter = 0; + pmhctl->log_parms.rtd_cnt++; + } + if (pmhctl->rev_state != MDDI_REV_REG_READ_SENT) { + /* this is generic reverse request by user, so + * reset the waiting flag. */ + mddi_rev_encap_user_request = FALSE; + } + /* link is active so send reverse encap to get register read results */ + pmhctl->rev_state = MDDI_REV_ENCAP_ISSUED; + mddi_command = MDDI_CMD_SEND_REV_ENCAP; + MDDI_MSG_DEBUG("sending rev encap!\n"); + } + } else + if ((pmhctl->client_status_cnt >= + mddi_client_status_frequency) + || mddi_client_capability_request) { + mddi_host_enable_io_clock(); + if (pmhctl->link_state == MDDI_LINK_HIBERNATING) { + /* only wake up the link if it client status is overdue */ + if ((pmhctl->client_status_cnt >= + (mddi_client_status_frequency * 2)) + || mddi_client_capability_request) { + /* need to wake up link before issuing rev encap command */ + MDDI_MSG_DEBUG("wake up link!\n"); + pmhctl->link_state = + MDDI_LINK_ACTIVATING; + mddi_host_reg_out(CMD, + MDDI_CMD_LINK_ACTIVE); + } + } else { + if (pmhctl->rtd_counter >= mddi_rtd_frequency) { + MDDI_MSG_DEBUG + ("mddi sending RTD command!\n"); + mddi_host_reg_out(CMD, + MDDI_CMD_SEND_RTD); + pmhctl->rtd_counter = 0; + pmhctl->log_parms.rtd_cnt++; + } + /* periodically get client status */ + MDDI_MSG_DEBUG + ("mddi sending rev enc! (get status)\n"); + if (mddi_client_capability_request) { + pmhctl->rev_state = + MDDI_REV_CLIENT_CAP_ISSUED; + mddi_command = MDDI_CMD_GET_CLIENT_CAP; + mddi_client_capability_request = FALSE; + } else { + pmhctl->rev_state = + MDDI_REV_STATUS_REQ_ISSUED; + pmhctl->client_status_cnt = 0; + mddi_command = + MDDI_CMD_GET_CLIENT_STATUS; + } + } + } + if ((pmhctl->rev_state == MDDI_REV_ENCAP_ISSUED) || + (pmhctl->rev_state == MDDI_REV_STATUS_REQ_ISSUED) || + (pmhctl->rev_state == MDDI_REV_CLIENT_CAP_ISSUED)) { + pmhctl->int_type.rev_encap_count++; +#if defined(T_MSM6280) && !defined(T_MSM7200) + mddi_rev_pointer_written = TRUE; + mddi_host_reg_out(REV_PTR, mddi_rev_ptr_write_val); + mddi_rev_ptr_curr = mddi_rev_ptr_start; + /* force new rev ptr command */ + mddi_host_reg_out(CMD, 0xC00); +#else + if (!pmhctl->rev_ptr_written) { + MDDI_MSG_DEBUG("writing reverse pointer!\n"); + pmhctl->rev_ptr_written = TRUE; +#if defined(CONFIG_FB_MSM_MDP31) || defined(CONFIG_FB_MSM_MDP40) + if ((pmhctl->rev_state == + MDDI_REV_CLIENT_CAP_ISSUED) && + (mddi_host_core_version == 0x28 || + mddi_host_core_version == 0x30)) { + pmhctl->rev_ptr_written = FALSE; + mddi_host_reg_out(FIFO_ALLOC, 0x02); + } else + mddi_host_reg_out(REV_PTR, + pmhctl-> + mddi_rev_ptr_write_val); +#else + mddi_host_reg_out(REV_PTR, + pmhctl-> + mddi_rev_ptr_write_val); +#endif + } +#endif + if (mddi_debug_clear_rev_data) { + uint16 i; + for (i = 0; i < MDDI_MAX_REV_DATA_SIZE / 4; i++) + pmhctl->rev_data_buf[i] = 0xdddddddd; + /* clean cache */ + mddi_flush_cache_lines(pmhctl->rev_data_buf, + MDDI_MAX_REV_DATA_SIZE); + } + + /* send reverse encapsulation to get needed data */ + mddi_host_reg_out(CMD, mddi_command); + } + } + +} + +static void mddi_process_client_initiated_wakeup(void) +{ + mddi_host_type host_idx = mddi_curr_host; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + /* Disable MDDI_INT Interrupt, we detect client initiated wakeup one + * time for each entry into hibernation */ + mddi_host_reg_outm(INTEN, MDDI_INT_MDDI_IN, 0); + + if (host_idx == MDDI_HOST_PRIM) { + if (mddi_vsync_detect_enabled) { + mddi_host_enable_io_clock(); +#ifndef MDDI_HOST_DISP_LISTEN + /* issue command to bring up link */ + /* need to do this to clear the vsync condition */ + if (pmhctl->link_state == MDDI_LINK_HIBERNATING) { + pmhctl->link_state = MDDI_LINK_ACTIVATING; + mddi_host_reg_out(CMD, MDDI_CMD_LINK_ACTIVE); + } +#endif + /* + * Indicate to client specific code that vsync was + * enabled, and we did not detect a client initiated + * wakeup. The client specific handler can clear the + * condition if necessary to prevent subsequent + * client initiated wakeups. + */ + mddi_client_lcd_vsync_detected(TRUE); + pmhctl->log_parms.vsync_response_cnt++; + MDDI_MSG_NOTICE("MDDI_INT_IN condition\n"); + + } + } + + if (mddi_gpio.polling_enabled) { + mddi_host_enable_io_clock(); + /* check interrupt status now */ + (void)mddi_queue_register_read_int(mddi_gpio.polling_reg, + &mddi_gpio.polling_val); + } +} +#endif /* FEATURE_MDDI_DISABLE_REVERSE */ + +static void mddi_host_isr(void) +{ + uint32 int_reg, int_en; +#ifndef FEATURE_MDDI_DISABLE_REVERSE + uint32 status_reg; +#endif + mddi_host_type host_idx = mddi_curr_host; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + if (!MDDI_HOST_IS_HCLK_ON) { + MDDI_HOST_ENABLE_HCLK; + } + int_reg = mddi_host_reg_in(INT); + int_en = mddi_host_reg_in(INTEN); + pmhctl->saved_int_reg = int_reg; + pmhctl->saved_int_en = int_en; + int_reg = int_reg & int_en; + pmhctl->int_type.count++; + + +#ifndef FEATURE_MDDI_DISABLE_REVERSE + status_reg = mddi_host_reg_in(STAT); + + if ((int_reg & MDDI_INT_MDDI_IN) || + ((int_en & MDDI_INT_MDDI_IN) && + ((int_reg == 0) || (status_reg & MDDI_STAT_CLIENT_WAKEUP_REQ)))) { + /* + * The MDDI_IN condition will clear itself, and so it is + * possible that MDDI_IN was the reason for the isr firing, + * even though the interrupt register does not have the + * MDDI_IN bit set. To check if this was the case we need to + * look at the status register bit that signifies a client + * initiated wakeup. If the status register bit is set, as well + * as the MDDI_IN interrupt enabled, then we treat this as a + * client initiated wakeup. + */ + if (int_reg & MDDI_INT_MDDI_IN) + pmhctl->int_type.in_count++; + mddi_process_client_initiated_wakeup(); + } +#endif + + if (int_reg & MDDI_INT_LINK_STATE_CHANGES) { + pmhctl->int_type.state_change_count++; + mddi_report_state_change(int_reg); + } + + if (int_reg & MDDI_INT_PRI_LINK_LIST_DONE) { + pmhctl->int_type.ll_done_count++; + mddi_process_link_list_done(); + } +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (int_reg & MDDI_INT_REV_DATA_AVAIL) { + pmhctl->int_type.rev_avail_count++; + mddi_process_rev_packets(); + } +#endif + + if (int_reg & MDDI_INT_ERROR_CONDITIONS) { + pmhctl->int_type.error_count++; + mddi_report_errors(int_reg); + + mddi_host_reg_out(INT, int_reg & MDDI_INT_ERROR_CONDITIONS); + } +#ifndef FEATURE_MDDI_DISABLE_REVERSE + mddi_issue_reverse_encapsulation(); + + if ((pmhctl->rev_state != MDDI_REV_ENCAP_ISSUED) && + (pmhctl->rev_state != MDDI_REV_STATUS_REQ_ISSUED)) +#endif + /* don't want simultaneous reverse and forward with Eagle */ + mddi_queue_forward_linked_list(); + + if (int_reg & MDDI_INT_NO_CMD_PKTS_PEND) { + /* this interrupt is used to kick the isr when hibernation is disabled */ + mddi_host_reg_outm(INTEN, MDDI_INT_NO_CMD_PKTS_PEND, 0); + } + + if ((!mddi_host_mdp_active_flag) && + (!mddi_vsync_detect_enabled) && + (pmhctl->llist_info.transmitting_start_idx == UNASSIGNED_INDEX) && + (pmhctl->llist_info.waiting_start_idx == UNASSIGNED_INDEX) && + (pmhctl->rev_state == MDDI_REV_IDLE)) { + if (pmhctl->link_state == MDDI_LINK_HIBERNATING) { + mddi_host_disable_io_clock(); + mddi_host_disable_hclk(); + } +#ifdef FEATURE_MDDI_HOST_ENABLE_EARLY_HIBERNATION + else if ((pmhctl->link_state == MDDI_LINK_ACTIVE) && + (!pmhctl->disable_hibernation)) { + mddi_host_reg_out(CMD, MDDI_CMD_POWERDOWN); + } +#endif + } +} + +static void mddi_host_isr_primary(void) +{ + mddi_curr_host = MDDI_HOST_PRIM; + mddi_host_isr(); +} + +irqreturn_t mddi_pmdh_isr_proxy(int irq, void *ptr) +{ + mddi_host_isr_primary(); + return IRQ_HANDLED; +} + +static void mddi_host_isr_external(void) +{ + mddi_curr_host = MDDI_HOST_EXT; + mddi_host_isr(); + mddi_curr_host = MDDI_HOST_PRIM; +} + +irqreturn_t mddi_emdh_isr_proxy(int irq, void *ptr) +{ + mddi_host_isr_external(); + return IRQ_HANDLED; +} + +static void mddi_host_initialize_registers(mddi_host_type host_idx) +{ + uint32 pad_reg_val; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + if (pmhctl->driver_state == MDDI_DRIVER_ENABLED) + return; + + /* turn on HCLK to MDDI host core */ + mddi_host_enable_hclk(); + + /* MDDI Reset command */ + mddi_host_reg_out(CMD, MDDI_CMD_RESET); + + /* Version register (= 0x01) */ + mddi_host_reg_out(VERSION, 0x0001); + + /* Bytes per subframe register */ + mddi_host_reg_out(BPS, MDDI_HOST_BYTES_PER_SUBFRAME); + + /* Subframes per media frames register (= 0x03) */ + mddi_host_reg_out(SPM, 0x0003); + + /* Turn Around 1 register (= 0x05) */ + mddi_host_reg_out(TA1_LEN, 0x0005); + + /* Turn Around 2 register (= 0x0C) */ + mddi_host_reg_out(TA2_LEN, MDDI_HOST_TA2_LEN); + + /* Drive hi register (= 0x96) */ + mddi_host_reg_out(DRIVE_HI, 0x0096); + + /* Drive lo register (= 0x32) */ + mddi_host_reg_out(DRIVE_LO, 0x0032); + + /* Display wakeup count register (= 0x3c) */ + mddi_host_reg_out(DISP_WAKE, 0x003c); + + /* Reverse Rate Divisor register (= 0x2) */ + mddi_host_reg_out(REV_RATE_DIV, MDDI_HOST_REV_RATE_DIV); + +#ifndef FEATURE_MDDI_DISABLE_REVERSE + /* Reverse Pointer Size */ + mddi_host_reg_out(REV_SIZE, MDDI_REV_BUFFER_SIZE); + + /* Rev Encap Size */ + mddi_host_reg_out(REV_ENCAP_SZ, pmhctl->rev_pkt_size); +#endif + + /* Periodic Rev Encap */ + /* don't send periodically */ + mddi_host_reg_out(CMD, MDDI_CMD_PERIODIC_REV_ENCAP); + + pad_reg_val = mddi_host_reg_in(PAD_CTL); + if (pad_reg_val == 0) { + /* If we are turning on band gap, need to wait 5us before turning + * on the rest of the PAD */ + mddi_host_reg_out(PAD_CTL, 0x08000); + udelay(5); + } +#ifdef T_MSM7200 + /* Recommendation from PAD hw team */ + mddi_host_reg_out(PAD_CTL, 0xa850a); +#else + /* Recommendation from PAD hw team */ + mddi_host_reg_out(PAD_CTL, 0xa850f); +#endif + + pad_reg_val = 0x00220020; + +#if defined(CONFIG_FB_MSM_MDP31) || defined(CONFIG_FB_MSM_MDP40) + mddi_host_reg_out(PAD_IO_CTL, 0x00320000); + mddi_host_reg_out(PAD_CAL, pad_reg_val); +#endif + + mddi_host_core_version = mddi_host_reg_inm(CORE_VER, 0xffff); + +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (mddi_host_core_version >= 8) + mddi_rev_ptr_workaround = FALSE; + pmhctl->rev_ptr_curr = pmhctl->rev_ptr_start; +#endif + + if ((mddi_host_core_version > 8) && (mddi_host_core_version < 0x19)) + mddi_host_reg_out(TEST, 0x2); + + /* Need an even number for counts */ + mddi_host_reg_out(DRIVER_START_CNT, 0x60006); + +#ifndef T_MSM7500 + /* Setup defaults for MDP related register */ + mddi_host_reg_out(MDP_VID_FMT_DES, 0x5666); + mddi_host_reg_out(MDP_VID_PIX_ATTR, 0x00C3); + mddi_host_reg_out(MDP_VID_CLIENTID, 0); +#endif + + /* automatically hibernate after 1 empty subframe */ + if (pmhctl->disable_hibernation) + mddi_host_reg_out(CMD, MDDI_CMD_HIBERNATE); + else + mddi_host_reg_out(CMD, MDDI_CMD_HIBERNATE | 1); + + /* Bring up link if display (client) requests it */ +#ifdef MDDI_HOST_DISP_LISTEN + mddi_host_reg_out(CMD, MDDI_CMD_DISP_LISTEN); +#else + mddi_host_reg_out(CMD, MDDI_CMD_DISP_IGNORE); +#endif + +} + +void mddi_host_configure_interrupts(mddi_host_type host_idx, boolean enable) +{ + unsigned long flags; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + spin_lock_irqsave(&mddi_host_spin_lock, flags); + + /* turn on HCLK to MDDI host core if it has been disabled */ + mddi_host_enable_hclk(); + /* Clear MDDI Interrupt enable reg */ + mddi_host_reg_out(INTEN, 0); + + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + + if (enable) { + pmhctl->driver_state = MDDI_DRIVER_ENABLED; + + if (host_idx == MDDI_HOST_PRIM) { + if (request_irq + (INT_MDDI_PRI, mddi_pmdh_isr_proxy, IRQF_DISABLED, + "PMDH", 0) != 0) + printk(KERN_ERR + "a mddi: unable to request_irq\n"); + else { + int_mddi_pri_flag = TRUE; + irq_enabled = 1; + } + } else { + if (request_irq + (INT_MDDI_EXT, mddi_emdh_isr_proxy, IRQF_DISABLED, + "EMDH", 0) != 0) + printk(KERN_ERR + "b mddi: unable to request_irq\n"); + else + int_mddi_ext_flag = TRUE; + } + + /* Set MDDI Interrupt enable reg -- Enable Reverse data avail */ +#ifdef FEATURE_MDDI_DISABLE_REVERSE + mddi_host_reg_out(INTEN, + MDDI_INT_ERROR_CONDITIONS | + MDDI_INT_LINK_STATE_CHANGES); +#else + /* Reverse Pointer register */ + pmhctl->rev_ptr_written = FALSE; + + mddi_host_reg_out(INTEN, + MDDI_INT_REV_DATA_AVAIL | + MDDI_INT_ERROR_CONDITIONS | + MDDI_INT_LINK_STATE_CHANGES); + pmhctl->rtd_counter = mddi_rtd_frequency; + pmhctl->client_status_cnt = 0; +#endif + } else { + if (pmhctl->driver_state == MDDI_DRIVER_ENABLED) + pmhctl->driver_state = MDDI_DRIVER_DISABLED; + } + +} + +/* + * mddi_host_client_cnt_reset: + * reset client_status_cnt to 0 to make sure host does not + * send RTD cmd to client right after resume before mddi + * client be powered up. this fix "MDDI RTD Failure" problem + */ +void mddi_host_client_cnt_reset(void) +{ + unsigned long flags; + mddi_host_cntl_type *pmhctl; + + pmhctl = &(mhctl[MDDI_HOST_PRIM]); + spin_lock_irqsave(&mddi_host_spin_lock, flags); + pmhctl->client_status_cnt = 0; + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); +} + +static void mddi_host_powerup(mddi_host_type host_idx) +{ + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + if (pmhctl->link_state != MDDI_LINK_DISABLED) + return; + + /* enable IO_CLK and hclk to MDDI host core */ + mddi_host_enable_io_clock(); + + mddi_host_initialize_registers(host_idx); + mddi_host_configure_interrupts(host_idx, TRUE); + + pmhctl->link_state = MDDI_LINK_ACTIVATING; + + /* Link activate command */ + mddi_host_reg_out(CMD, MDDI_CMD_LINK_ACTIVE); + +#ifdef CLKRGM_MDDI_IO_CLOCK_IN_MHZ + MDDI_MSG_NOTICE("MDDI Host: Activating Link %d Mbps\n", + CLKRGM_MDDI_IO_CLOCK_IN_MHZ * 2); +#else + MDDI_MSG_NOTICE("MDDI Host: Activating Link\n"); +#endif + + /* Initialize the timer */ + if (host_idx == MDDI_HOST_PRIM) + mddi_host_timer_service(0); +} + +void mddi_send_fw_link_skew_cal(mddi_host_type host_idx) +{ + mddi_host_reg_out(CMD, MDDI_CMD_FW_LINK_SKEW_CAL); + MDDI_MSG_DEBUG("%s: Skew Calibration done!!\n", __func__); +} + + +void mddi_host_init(mddi_host_type host_idx) +/* Write out the MDDI configuration registers */ +{ + static boolean initialized = FALSE; + mddi_host_cntl_type *pmhctl; + + if (host_idx >= MDDI_NUM_HOST_CORES) { + MDDI_MSG_ERR("Invalid host core index\n"); + return; + } + + if (!initialized) { + uint16 idx; + mddi_host_type host; + + for (host = MDDI_HOST_PRIM; host < MDDI_NUM_HOST_CORES; host++) { + pmhctl = &(mhctl[host]); + initialized = TRUE; + + pmhctl->llist_ptr = + dma_alloc_coherent(NULL, MDDI_LLIST_POOL_SIZE, + &(pmhctl->llist_dma_addr), + GFP_KERNEL); + pmhctl->llist_dma_ptr = + (mddi_linked_list_type *) (void *)pmhctl-> + llist_dma_addr; +#ifdef FEATURE_MDDI_DISABLE_REVERSE + pmhctl->rev_data_buf = NULL; + if (pmhctl->llist_ptr == NULL) +#else + mddi_rev_user.waiting = FALSE; + init_completion(&(mddi_rev_user.done_comp)); + pmhctl->rev_data_buf = + dma_alloc_coherent(NULL, MDDI_MAX_REV_DATA_SIZE, + &(pmhctl->rev_data_dma_addr), + GFP_KERNEL); + if ((pmhctl->llist_ptr == NULL) + || (pmhctl->rev_data_buf == NULL)) +#endif + { + MDDI_MSG_CRIT + ("unable to alloc non-cached memory\n"); + } + llist_extern[host] = pmhctl->llist_ptr; + llist_dma_extern[host] = pmhctl->llist_dma_ptr; + llist_extern_notify[host] = pmhctl->llist_notify; + + for (idx = 0; idx < UNASSIGNED_INDEX; idx++) { + init_completion(& + (pmhctl->llist_notify[idx]. + done_comp)); + } + init_completion(&(pmhctl->mddi_llist_avail_comp)); + spin_lock_init(&mddi_host_spin_lock); + pmhctl->mddi_waiting_for_llist_avail = FALSE; + pmhctl->mddi_rev_ptr_write_val = + (uint32) (void *)(pmhctl->rev_data_dma_addr); + pmhctl->rev_ptr_start = (void *)pmhctl->rev_data_buf; + + pmhctl->rev_pkt_size = MDDI_DEFAULT_REV_PKT_SIZE; + pmhctl->rev_state = MDDI_REV_IDLE; +#ifdef IMAGE_MODEM_PROC + /* assume hibernation state is last state from APPS proc, so that + * we don't reinitialize the host core */ + pmhctl->link_state = MDDI_LINK_HIBERNATING; +#else + pmhctl->link_state = MDDI_LINK_DISABLED; +#endif + pmhctl->driver_state = MDDI_DRIVER_DISABLED; + pmhctl->disable_hibernation = FALSE; + + /* initialize llist variables */ + pmhctl->llist_info.transmitting_start_idx = + UNASSIGNED_INDEX; + pmhctl->llist_info.transmitting_end_idx = + UNASSIGNED_INDEX; + pmhctl->llist_info.waiting_start_idx = UNASSIGNED_INDEX; + pmhctl->llist_info.waiting_end_idx = UNASSIGNED_INDEX; + pmhctl->llist_info.reg_read_idx = UNASSIGNED_INDEX; + pmhctl->llist_info.next_free_idx = + MDDI_FIRST_DYNAMIC_LLIST_IDX; + pmhctl->llist_info.reg_read_waiting = FALSE; + + mddi_vsync_detect_enabled = FALSE; + mddi_gpio.polling_enabled = FALSE; + + pmhctl->int_type.count = 0; + pmhctl->int_type.in_count = 0; + pmhctl->int_type.disp_req_count = 0; + pmhctl->int_type.state_change_count = 0; + pmhctl->int_type.ll_done_count = 0; + pmhctl->int_type.rev_avail_count = 0; + pmhctl->int_type.error_count = 0; + pmhctl->int_type.rev_encap_count = 0; + pmhctl->int_type.llist_ptr_write_1 = 0; + pmhctl->int_type.llist_ptr_write_2 = 0; + + pmhctl->stats.fwd_crc_count = 0; + pmhctl->stats.rev_crc_count = 0; + pmhctl->stats.pri_underflow = 0; + pmhctl->stats.sec_underflow = 0; + pmhctl->stats.rev_overflow = 0; + pmhctl->stats.pri_overwrite = 0; + pmhctl->stats.sec_overwrite = 0; + pmhctl->stats.rev_overwrite = 0; + pmhctl->stats.dma_failure = 0; + pmhctl->stats.rtd_failure = 0; + pmhctl->stats.reg_read_failure = 0; +#ifdef FEATURE_MDDI_UNDERRUN_RECOVERY + pmhctl->stats.pri_underrun_detected = 0; +#endif + + pmhctl->log_parms.rtd_cnt = 0; + pmhctl->log_parms.rev_enc_cnt = 0; + pmhctl->log_parms.vid_cnt = 0; + pmhctl->log_parms.reg_acc_cnt = 0; + pmhctl->log_parms.cli_stat_cnt = 0; + pmhctl->log_parms.cli_cap_cnt = 0; + pmhctl->log_parms.reg_read_cnt = 0; + pmhctl->log_parms.link_active_cnt = 0; + pmhctl->log_parms.link_hibernate_cnt = 0; + pmhctl->log_parms.fwd_crc_cnt = 0; + pmhctl->log_parms.rev_crc_cnt = 0; + pmhctl->log_parms.vsync_response_cnt = 0; + + prev_parms[host_idx] = pmhctl->log_parms; + mddi_client_capability_pkt.packet_length = 0; + } + +#ifndef T_MSM7500 + /* tell clock driver we are user of this PLL */ + MDDI_HOST_ENABLE_IO_CLOCK; +#endif + } + + mddi_host_powerup(host_idx); + pmhctl = &(mhctl[host_idx]); +} + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT +static uint32 mddi_client_id; + +uint32 mddi_get_client_id(void) +{ + +#ifndef FEATURE_MDDI_DISABLE_REVERSE + mddi_host_type host_idx = MDDI_HOST_PRIM; + static boolean client_detection_try = FALSE; + mddi_host_cntl_type *pmhctl; + unsigned long flags; + uint16 saved_rev_pkt_size; + int ret; + + if (!client_detection_try) { + /* Toshiba display requires larger drive_lo value */ + mddi_host_reg_out(DRIVE_LO, 0x0050); + + pmhctl = &(mhctl[MDDI_HOST_PRIM]); + + saved_rev_pkt_size = pmhctl->rev_pkt_size; + + /* Increase Rev Encap Size */ + pmhctl->rev_pkt_size = MDDI_CLIENT_CAPABILITY_REV_PKT_SIZE; + mddi_host_reg_out(REV_ENCAP_SZ, pmhctl->rev_pkt_size); + + /* disable hibernation temporarily */ + if (!pmhctl->disable_hibernation) + mddi_host_reg_out(CMD, MDDI_CMD_HIBERNATE); + + mddi_rev_user.waiting = TRUE; + INIT_COMPLETION(mddi_rev_user.done_comp); + + spin_lock_irqsave(&mddi_host_spin_lock, flags); + + /* turn on clock(s), if they have been disabled */ + mddi_host_enable_hclk(); + mddi_host_enable_io_clock(); + + mddi_client_capability_request = TRUE; + + if (pmhctl->rev_state == MDDI_REV_IDLE) { + /* attempt to send the reverse encapsulation now */ + mddi_issue_reverse_encapsulation(); + } + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + + wait_for_completion_killable(&(mddi_rev_user.done_comp)); + + /* Set Rev Encap Size back to its original value */ + pmhctl->rev_pkt_size = saved_rev_pkt_size; + mddi_host_reg_out(REV_ENCAP_SZ, pmhctl->rev_pkt_size); + + /* reenable auto-hibernate */ + if (!pmhctl->disable_hibernation) + mddi_host_reg_out(CMD, MDDI_CMD_HIBERNATE | 1); + + mddi_host_reg_out(DRIVE_LO, 0x0032); + client_detection_try = TRUE; + + mddi_client_id = (mddi_client_capability_pkt.Mfr_Name<<16) | + mddi_client_capability_pkt.Product_Code; + + if (!mddi_client_id) + mddi_disable(1); + + ret = mddi_client_power(mddi_client_id); + if (ret < 0) + MDDI_MSG_ERR("mddi_client_power return %d", ret); + } + +#if 0 + switch (mddi_client_capability_pkt.Mfr_Name) { + case 0x4474: + if ((mddi_client_capability_pkt.Product_Code != 0x8960) && + (target == DISPLAY_1)) { + ret = PRISM_WVGA; + } + break; + + case 0xD263: + if (target == DISPLAY_1) + ret = TOSHIBA_VGA_PRIM; + else if (target == DISPLAY_2) + ret = TOSHIBA_QCIF_SECD; + break; + + case 0: + if (mddi_client_capability_pkt.Product_Code == 0x8835) { + if (target == DISPLAY_1) + ret = SHARP_QVGA_PRIM; + else if (target == DISPLAY_2) + ret = SHARP_128x128_SECD; + } + break; + + default: + break; + } + + if ((!client_detection_try) && (ret != TOSHIBA_VGA_PRIM) + && (ret != TOSHIBA_QCIF_SECD)) { + /* Not a Toshiba display, so change drive_lo back to default value */ + mddi_host_reg_out(DRIVE_LO, 0x0032); + } +#endif + +#endif + + return mddi_client_id; +} +#endif + +void mddi_host_powerdown(mddi_host_type host_idx) +{ + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + if (host_idx >= MDDI_NUM_HOST_CORES) { + MDDI_MSG_ERR("Invalid host core index\n"); + return; + } + + if (pmhctl->driver_state == MDDI_DRIVER_RESET) { + return; + } + + if (host_idx == MDDI_HOST_PRIM) { + /* disable timer */ + del_timer(&mddi_host_timer); + } + + mddi_host_configure_interrupts(host_idx, FALSE); + + /* turn on HCLK to MDDI host core if it has been disabled */ + mddi_host_enable_hclk(); + + /* MDDI Reset command */ + mddi_host_reg_out(CMD, MDDI_CMD_RESET); + + /* Pad Control Register */ + mddi_host_reg_out(PAD_CTL, 0x0); + + /* disable IO_CLK and hclk to MDDI host core */ + mddi_host_disable_io_clock(); + mddi_host_disable_hclk(); + + pmhctl->link_state = MDDI_LINK_DISABLED; + pmhctl->driver_state = MDDI_DRIVER_RESET; + + MDDI_MSG_NOTICE("MDDI Host: Disabling Link\n"); + +} + +uint16 mddi_get_next_free_llist_item(mddi_host_type host_idx, boolean wait) +{ + unsigned long flags; + uint16 ret_idx; + boolean forced_wait = FALSE; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + ret_idx = pmhctl->llist_info.next_free_idx; + + pmhctl->llist_info.next_free_idx++; + if (pmhctl->llist_info.next_free_idx >= MDDI_NUM_DYNAMIC_LLIST_ITEMS) + pmhctl->llist_info.next_free_idx = MDDI_FIRST_DYNAMIC_LLIST_IDX; + spin_lock_irqsave(&mddi_host_spin_lock, flags); + if (pmhctl->llist_notify[ret_idx].in_use) { + if (!wait) { + pmhctl->llist_info.next_free_idx = ret_idx; + ret_idx = UNASSIGNED_INDEX; + } else { + forced_wait = TRUE; + INIT_COMPLETION(pmhctl->mddi_llist_avail_comp); + } + } + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + + if (forced_wait) { + wait_for_completion_killable(& + (pmhctl-> + mddi_llist_avail_comp)); + MDDI_MSG_ERR("task waiting on mddi llist item\n"); + } + + if (ret_idx != UNASSIGNED_INDEX) { + pmhctl->llist_notify[ret_idx].waiting = FALSE; + pmhctl->llist_notify[ret_idx].done_cb = NULL; + pmhctl->llist_notify[ret_idx].in_use = TRUE; + pmhctl->llist_notify[ret_idx].next_idx = UNASSIGNED_INDEX; + } + + return ret_idx; +} + +uint16 mddi_get_reg_read_llist_item(mddi_host_type host_idx, boolean wait) +{ +#ifdef FEATURE_MDDI_DISABLE_REVERSE + MDDI_MSG_CRIT("No reverse link available\n"); + (void)wait; + return FALSE; +#else + unsigned long flags; + uint16 ret_idx; + boolean error = FALSE; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + spin_lock_irqsave(&mddi_host_spin_lock, flags); + if (pmhctl->llist_info.reg_read_idx != UNASSIGNED_INDEX) { + /* need to block here or is this an error condition? */ + error = TRUE; + ret_idx = UNASSIGNED_INDEX; + } + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + + if (!error) { + ret_idx = pmhctl->llist_info.reg_read_idx = + mddi_get_next_free_llist_item(host_idx, wait); + /* clear the reg_read_waiting flag */ + pmhctl->llist_info.reg_read_waiting = FALSE; + } + + if (error) + MDDI_MSG_ERR("***** Reg read still in progress! ****\n"); + return ret_idx; +#endif + +} + +void mddi_queue_forward_packets(uint16 first_llist_idx, + uint16 last_llist_idx, + boolean wait, + mddi_llist_done_cb_type llist_done_cb, + mddi_host_type host_idx) +{ + unsigned long flags; + mddi_linked_list_type *llist; + mddi_linked_list_type *llist_dma; + mddi_host_cntl_type *pmhctl = &(mhctl[host_idx]); + + if ((first_llist_idx >= UNASSIGNED_INDEX) || + (last_llist_idx >= UNASSIGNED_INDEX)) { + MDDI_MSG_ERR("MDDI queueing invalid linked list\n"); + return; + } + + if (pmhctl->link_state == MDDI_LINK_DISABLED) + MDDI_MSG_CRIT("MDDI host powered down!\n"); + + llist = pmhctl->llist_ptr; + llist_dma = pmhctl->llist_dma_ptr; + + /* clean cache so MDDI host can read data */ + memory_barrier(); + + pmhctl->llist_notify[last_llist_idx].waiting = wait; + if (wait) + INIT_COMPLETION(pmhctl->llist_notify[last_llist_idx].done_comp); + pmhctl->llist_notify[last_llist_idx].done_cb = llist_done_cb; + + spin_lock_irqsave(&mddi_host_spin_lock, flags); + + if ((pmhctl->llist_info.transmitting_start_idx == UNASSIGNED_INDEX) && + (pmhctl->llist_info.waiting_start_idx == UNASSIGNED_INDEX) && + (pmhctl->rev_state == MDDI_REV_IDLE)) { + /* no packets are currently transmitting */ +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (first_llist_idx == pmhctl->llist_info.reg_read_idx) { + /* This is the special case where the packet is a register read. */ + pmhctl->rev_state = MDDI_REV_REG_READ_ISSUED; + mddi_reg_read_retry = 0; + /* mddi_rev_reg_read_attempt = 1; */ + } +#endif + /* assign transmitting index values */ + pmhctl->llist_info.transmitting_start_idx = first_llist_idx; + pmhctl->llist_info.transmitting_end_idx = last_llist_idx; + + /* turn on clock(s), if they have been disabled */ + mddi_host_enable_hclk(); + mddi_host_enable_io_clock(); + pmhctl->int_type.llist_ptr_write_1++; + /* Write to primary pointer register */ + dma_coherent_pre_ops(); + mddi_host_reg_out(PRI_PTR, &llist_dma[first_llist_idx]); + + /* enable interrupt when complete */ + mddi_host_reg_outm(INTEN, MDDI_INT_PRI_LINK_LIST_DONE, + MDDI_INT_PRI_LINK_LIST_DONE); + + } else if (pmhctl->llist_info.waiting_start_idx == UNASSIGNED_INDEX) { +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (first_llist_idx == pmhctl->llist_info.reg_read_idx) { + /* + * we have a register read to send but need to wait + * for current reverse activity to end or there are + * packets currently transmitting + */ + /* mddi_rev_reg_read_attempt = 0; */ + pmhctl->llist_info.reg_read_waiting = TRUE; + } +#endif + + /* assign waiting index values */ + pmhctl->llist_info.waiting_start_idx = first_llist_idx; + pmhctl->llist_info.waiting_end_idx = last_llist_idx; + } else { + uint16 prev_end_idx = pmhctl->llist_info.waiting_end_idx; +#ifndef FEATURE_MDDI_DISABLE_REVERSE + if (first_llist_idx == pmhctl->llist_info.reg_read_idx) { + /* + * we have a register read to send but need to wait + * for current reverse activity to end or there are + * packets currently transmitting + */ + /* mddi_rev_reg_read_attempt = 0; */ + pmhctl->llist_info.reg_read_waiting = TRUE; + } +#endif + + llist = pmhctl->llist_ptr; + + /* clear end flag in previous last packet */ + llist[prev_end_idx].link_controller_flags = 0; + pmhctl->llist_notify[prev_end_idx].next_idx = first_llist_idx; + + /* set the next_packet_pointer of the previous last packet */ + llist[prev_end_idx].next_packet_pointer = + (void *)(&llist_dma[first_llist_idx]); + + /* clean cache so MDDI host can read data */ + memory_barrier(); + + /* assign new waiting last index value */ + pmhctl->llist_info.waiting_end_idx = last_llist_idx; + } + + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + +} + +void mddi_host_write_pix_attr_reg(uint32 value) +{ + (void)value; +} + +void mddi_queue_reverse_encapsulation(boolean wait) +{ +#ifdef FEATURE_MDDI_DISABLE_REVERSE + MDDI_MSG_CRIT("No reverse link available\n"); + (void)wait; +#else + unsigned long flags; + boolean error = FALSE; + mddi_host_type host_idx = MDDI_HOST_PRIM; + mddi_host_cntl_type *pmhctl = &(mhctl[MDDI_HOST_PRIM]); + + spin_lock_irqsave(&mddi_host_spin_lock, flags); + + /* turn on clock(s), if they have been disabled */ + mddi_host_enable_hclk(); + mddi_host_enable_io_clock(); + + if (wait) { + if (!mddi_rev_user.waiting) { + mddi_rev_user.waiting = TRUE; + INIT_COMPLETION(mddi_rev_user.done_comp); + } else + error = TRUE; + } + mddi_rev_encap_user_request = TRUE; + + if (pmhctl->rev_state == MDDI_REV_IDLE) { + /* attempt to send the reverse encapsulation now */ + mddi_host_type orig_host_idx = mddi_curr_host; + mddi_curr_host = host_idx; + mddi_issue_reverse_encapsulation(); + mddi_curr_host = orig_host_idx; + } + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + + if (error) { + MDDI_MSG_ERR("Reverse Encap request already in progress\n"); + } else if (wait) + wait_for_completion_killable(&(mddi_rev_user.done_comp)); +#endif +} + +/* ISR to be executed */ +boolean mddi_set_rev_handler(mddi_rev_handler_type handler, uint16 pkt_type) +{ +#ifdef FEATURE_MDDI_DISABLE_REVERSE + MDDI_MSG_CRIT("No reverse link available\n"); + (void)handler; + (void)pkt_type; + return (FALSE); +#else + unsigned long flags; + uint16 hdlr; + boolean handler_set = FALSE; + boolean overwrite = FALSE; + mddi_host_type host_idx = MDDI_HOST_PRIM; + mddi_host_cntl_type *pmhctl = &(mhctl[MDDI_HOST_PRIM]); + + /* Disable interrupts */ + spin_lock_irqsave(&mddi_host_spin_lock, flags); + + for (hdlr = 0; hdlr < MAX_MDDI_REV_HANDLERS; hdlr++) { + if (mddi_rev_pkt_handler[hdlr].pkt_type == pkt_type) { + mddi_rev_pkt_handler[hdlr].handler = handler; + if (handler == NULL) { + /* clearing handler from table */ + mddi_rev_pkt_handler[hdlr].pkt_type = + INVALID_PKT_TYPE; + handler_set = TRUE; + if (pkt_type == 0x10) { /* video stream packet */ + /* ensure HCLK on to MDDI host core before register write */ + mddi_host_enable_hclk(); + /* No longer getting video, so reset rev encap size to default */ + pmhctl->rev_pkt_size = + MDDI_DEFAULT_REV_PKT_SIZE; + mddi_host_reg_out(REV_ENCAP_SZ, + pmhctl->rev_pkt_size); + } + } else { + /* already a handler for this packet */ + overwrite = TRUE; + } + break; + } + } + if ((hdlr >= MAX_MDDI_REV_HANDLERS) && (handler != NULL)) { + /* assigning new handler */ + for (hdlr = 0; hdlr < MAX_MDDI_REV_HANDLERS; hdlr++) { + if (mddi_rev_pkt_handler[hdlr].pkt_type == + INVALID_PKT_TYPE) { + if ((pkt_type == 0x10) && /* video stream packet */ + (pmhctl->rev_pkt_size < + MDDI_VIDEO_REV_PKT_SIZE)) { + /* ensure HCLK on to MDDI host core before register write */ + mddi_host_enable_hclk(); + /* Increase Rev Encap Size */ + pmhctl->rev_pkt_size = + MDDI_VIDEO_REV_PKT_SIZE; + mddi_host_reg_out(REV_ENCAP_SZ, + pmhctl->rev_pkt_size); + } + mddi_rev_pkt_handler[hdlr].handler = handler; + mddi_rev_pkt_handler[hdlr].pkt_type = pkt_type; + handler_set = TRUE; + break; + } + } + } + + /* Restore interrupts */ + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + + if (overwrite) + MDDI_MSG_ERR("Overwriting previous rev packet handler\n"); + + return handler_set; + +#endif +} /* mddi_set_rev_handler */ + +void mddi_host_disable_hibernation(boolean disable) +{ + mddi_host_type host_idx = MDDI_HOST_PRIM; + mddi_host_cntl_type *pmhctl = &(mhctl[MDDI_HOST_PRIM]); + + if (disable) { + pmhctl->disable_hibernation = TRUE; + /* hibernation will be turned off by isr next time it is entered */ + } else { + if (pmhctl->disable_hibernation) { + unsigned long flags; + spin_lock_irqsave(&mddi_host_spin_lock, flags); + if (!MDDI_HOST_IS_HCLK_ON) + MDDI_HOST_ENABLE_HCLK; + mddi_host_reg_out(CMD, MDDI_CMD_HIBERNATE | 1); + spin_unlock_irqrestore(&mddi_host_spin_lock, flags); + pmhctl->disable_hibernation = FALSE; + } + } +} + +void mddi_mhctl_remove(mddi_host_type host_idx) +{ + mddi_host_cntl_type *pmhctl; + + pmhctl = &(mhctl[host_idx]); + + dma_free_coherent(NULL, MDDI_LLIST_POOL_SIZE, (void *)pmhctl->llist_ptr, + pmhctl->llist_dma_addr); + + dma_free_coherent(NULL, MDDI_MAX_REV_DATA_SIZE, + (void *)pmhctl->rev_data_buf, + pmhctl->rev_data_dma_addr); +} diff --git a/drivers/video/msm/mddihosti.h b/drivers/video/msm/mddihosti.h new file mode 100644 index 00000000000..166d15c89db --- /dev/null +++ b/drivers/video/msm/mddihosti.h @@ -0,0 +1,552 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MDDIHOSTI_H +#define MDDIHOSTI_H + +#include "msm_fb.h" +#include "mddihost.h" +#include + +/* Register offsets in MDDI, applies to both msm_pmdh_base and + * (u32)msm_emdh_base. */ +#define MDDI_CMD 0x0000 +#define MDDI_VERSION 0x0004 +#define MDDI_PRI_PTR 0x0008 +#define MDDI_BPS 0x0010 +#define MDDI_SPM 0x0014 +#define MDDI_INT 0x0018 +#define MDDI_INTEN 0x001c +#define MDDI_REV_PTR 0x0020 +#define MDDI_REV_SIZE 0x0024 +#define MDDI_STAT 0x0028 +#define MDDI_REV_RATE_DIV 0x002c +#define MDDI_REV_CRC_ERR 0x0030 +#define MDDI_TA1_LEN 0x0034 +#define MDDI_TA2_LEN 0x0038 +#define MDDI_TEST 0x0040 +#define MDDI_REV_PKT_CNT 0x0044 +#define MDDI_DRIVE_HI 0x0048 +#define MDDI_DRIVE_LO 0x004c +#define MDDI_DISP_WAKE 0x0050 +#define MDDI_REV_ENCAP_SZ 0x0054 +#define MDDI_RTD_VAL 0x0058 +#define MDDI_PAD_CTL 0x0068 +#define MDDI_DRIVER_START_CNT 0x006c +#define MDDI_CORE_VER 0x008c +#define MDDI_FIFO_ALLOC 0x0090 +#define MDDI_PAD_IO_CTL 0x00a0 +#define MDDI_PAD_CAL 0x00a4 + +#ifdef ENABLE_MDDI_MULTI_READ_WRITE +#define MDDI_HOST_MAX_CLIENT_REG_IN_SAME_ADDR 128 +#else +#define MDDI_HOST_MAX_CLIENT_REG_IN_SAME_ADDR 1 +#endif + +extern int32 mddi_client_type; +extern u32 mddi_msg_level; + +/* No longer need to write to clear these registers */ +#define xxxx_mddi_host_reg_outm(reg, mask, val) \ +do { \ + if (host_idx == MDDI_HOST_PRIM) \ + mddi_host_reg_outm_pmdh(reg, mask, val); \ + else \ + mddi_host_reg_outm_emdh(reg, mask, val); \ +} while (0) + +#define mddi_host_reg_outm(reg, mask, val) \ +do { \ + unsigned long __addr; \ + if (host_idx == MDDI_HOST_PRIM) \ + __addr = (u32)msm_pmdh_base + MDDI_##reg; \ + else \ + __addr = (u32)msm_emdh_base + MDDI_##reg; \ + writel((readl(__addr) & ~(mask)) | ((val) & (mask)), __addr); \ +} while (0) + +#define xxxx_mddi_host_reg_out(reg, val) \ +do { \ + if (host_idx == MDDI_HOST_PRIM) \ + mddi_host_reg_out_pmdh(reg, val); \ + else \ + mddi_host_reg_out_emdh(reg, val); \ + } while (0) + +#define mddi_host_reg_out(reg, val) \ +do { \ + if (host_idx == MDDI_HOST_PRIM) \ + writel(val, (u32)msm_pmdh_base + MDDI_##reg); \ + else \ + writel(val, (u32)msm_emdh_base + MDDI_##reg); \ +} while (0) + +#define xxxx_mddi_host_reg_in(reg) \ + ((host_idx) ? \ + mddi_host_reg_in_emdh(reg) : mddi_host_reg_in_pmdh(reg)); + +#define mddi_host_reg_in(reg) \ +((host_idx) ? \ + readl((u32)msm_emdh_base + MDDI_##reg) : \ + readl((u32)msm_pmdh_base + MDDI_##reg)) \ + +#define xxxx_mddi_host_reg_inm(reg, mask) \ + ((host_idx) ? \ + mddi_host_reg_inm_emdh(reg, mask) : \ + mddi_host_reg_inm_pmdh(reg, mask);) + +#define mddi_host_reg_inm(reg, mask) \ +((host_idx) ? \ + readl((u32)msm_emdh_base + MDDI_##reg) & (mask) : \ + readl((u32)msm_pmdh_base + MDDI_##reg) & (mask)) \ + +/* Using non-cacheable pmem, so do nothing */ +#define mddi_invalidate_cache_lines(addr_start, num_bytes) +/* + * Using non-cacheable pmem, so do nothing with cache + * but, ensure write goes out to memory + */ +#define mddi_flush_cache_lines(addr_start, num_bytes) \ + (void) addr_start; \ + (void) num_bytes; \ + memory_barrier() + +/* Since this translates to Remote Procedure Calls to check on clock status +* just use a local variable to keep track of io_clock */ +#define MDDI_HOST_IS_IO_CLOCK_ON mddi_host_io_clock_on +#define MDDI_HOST_ENABLE_IO_CLOCK +#define MDDI_HOST_DISABLE_IO_CLOCK +#define MDDI_HOST_IS_HCLK_ON mddi_host_hclk_on +#define MDDI_HOST_ENABLE_HCLK +#define MDDI_HOST_DISABLE_HCLK +#define FEATURE_MDDI_HOST_IO_CLOCK_CONTROL_DISABLE +#define FEATURE_MDDI_HOST_HCLK_CONTROL_DISABLE + +#define TRAMP_MDDI_HOST_ISR TRAMP_MDDI_PRI_ISR +#define TRAMP_MDDI_HOST_EXT_ISR TRAMP_MDDI_EXT_ISR +#define MDP_LINE_COUNT_BMSK 0x3ff +#define MDP_SYNC_STATUS 0x000c +#define MDP_LINE_COUNT \ +(readl(msm_mdp_base + MDP_SYNC_STATUS) & MDP_LINE_COUNT_BMSK) + +/* MDP sends 256 pixel packets, so lower value hibernates more without +* significantly increasing latency of waiting for next subframe */ +#define MDDI_HOST_BYTES_PER_SUBFRAME 0x3C00 + +#if defined(CONFIG_FB_MSM_MDP31) || defined(CONFIG_FB_MSM_MDP40) +#define MDDI_HOST_TA2_LEN 0x001a +#define MDDI_HOST_REV_RATE_DIV 0x0004 +#else +#define MDDI_HOST_TA2_LEN 0x000c +#define MDDI_HOST_REV_RATE_DIV 0x0002 +#endif + +#define MDDI_MSG_EMERG(msg, ...) \ + if (mddi_msg_level > 0) \ + printk(KERN_EMERG msg, ## __VA_ARGS__); +#define MDDI_MSG_ALERT(msg, ...) \ + if (mddi_msg_level > 1) \ + printk(KERN_ALERT msg, ## __VA_ARGS__); +#define MDDI_MSG_CRIT(msg, ...) \ + if (mddi_msg_level > 2) \ + printk(KERN_CRIT msg, ## __VA_ARGS__); +#define MDDI_MSG_ERR(msg, ...) \ + if (mddi_msg_level > 3) \ + printk(KERN_ERR msg, ## __VA_ARGS__); +#define MDDI_MSG_WARNING(msg, ...) \ + if (mddi_msg_level > 4) \ + printk(KERN_WARNING msg, ## __VA_ARGS__); +#define MDDI_MSG_NOTICE(msg, ...) \ + if (mddi_msg_level > 5) \ + printk(KERN_NOTICE msg, ## __VA_ARGS__); +#define MDDI_MSG_INFO(msg, ...) \ + if (mddi_msg_level > 6) \ + printk(KERN_INFO msg, ## __VA_ARGS__); +#define MDDI_MSG_DEBUG(msg, ...) \ + if (mddi_msg_level > 7) \ + printk(KERN_DEBUG msg, ## __VA_ARGS__); + +#define GCC_PACKED __attribute__((packed)) +typedef struct GCC_PACKED { + uint16 packet_length; + /* total # of bytes in the packet not including + the packet_length field. */ + + uint16 packet_type; + /* A Packet Type of 70 identifies the packet as + a Client status Packet. */ + + uint16 bClient_ID; + /* This field is reserved for future use and shall + be set to zero. */ + +} mddi_rev_packet_type; + +typedef struct GCC_PACKED { + uint16 packet_length; + /* total # of bytes in the packet not including + the packet_length field. */ + + uint16 packet_type; + /* A Packet Type of 70 identifies the packet as + a Client status Packet. */ + + uint16 bClient_ID; + /* This field is reserved for future use and shall + be set to zero. */ + + uint16 reverse_link_request; + /* 16 bit unsigned integer with number of bytes client + needs in the * reverse encapsulation message + to transmit data. */ + + uint8 crc_error_count; + uint8 capability_change; + uint16 graphics_busy_flags; + + uint16 parameter_CRC; + /* 16-bit CRC of all the bytes in the packet + including Packet Length. */ + +} mddi_client_status_type; + +typedef struct GCC_PACKED { + uint16 packet_length; + /* total # of bytes in the packet not including + the packet_length field. */ + + uint16 packet_type; + /* A Packet Type of 66 identifies the packet as + a Client Capability Packet. */ + + uint16 bClient_ID; + /* This field is reserved for future use and + shall be set to zero. */ + + uint16 Protocol_Version; + uint16 Minimum_Protocol_Version; + uint16 Data_Rate_Capability; + uint8 Interface_Type_Capability; + uint8 Number_of_Alt_Displays; + uint16 PostCal_Data_Rate; + uint16 Bitmap_Width; + uint16 Bitmap_Height; + uint16 Display_Window_Width; + uint16 Display_Window_Height; + uint32 Color_Map_Size; + uint16 Color_Map_RGB_Width; + uint16 RGB_Capability; + uint8 Monochrome_Capability; + uint8 Reserved_1; + uint16 Y_Cb_Cr_Capability; + uint16 Bayer_Capability; + uint16 Alpha_Cursor_Image_Planes; + uint32 Client_Feature_Capability_Indicators; + uint8 Maximum_Video_Frame_Rate_Capability; + uint8 Minimum_Video_Frame_Rate_Capability; + uint16 Minimum_Sub_frame_Rate; + uint16 Audio_Buffer_Depth; + uint16 Audio_Channel_Capability; + uint16 Audio_Sample_Rate_Capability; + uint8 Audio_Sample_Resolution; + uint8 Mic_Audio_Sample_Resolution; + uint16 Mic_Sample_Rate_Capability; + uint8 Keyboard_Data_Format; + uint8 pointing_device_data_format; + uint16 content_protection_type; + uint16 Mfr_Name; + uint16 Product_Code; + uint16 Reserved_3; + uint32 Serial_Number; + uint8 Week_of_Manufacture; + uint8 Year_of_Manufacture; + + uint16 parameter_CRC; + /* 16-bit CRC of all the bytes in the packet including Packet Length. */ + +} mddi_client_capability_type; + +typedef struct GCC_PACKED { + uint16 packet_length; + /* total # of bytes in the packet not including the packet_length field. */ + + uint16 packet_type; + /* A Packet Type of 16 identifies the packet as a Video Stream Packet. */ + + uint16 bClient_ID; + /* This field is reserved for future use and shall be set to zero. */ + + uint16 video_data_format_descriptor; + /* format of each pixel in the Pixel Data in the present stream in the + * present packet. + * If bits [15:13] = 000 monochrome + * If bits [15:13] = 001 color pixels (palette). + * If bits [15:13] = 010 color pixels in raw RGB + * If bits [15:13] = 011 data in 4:2:2 Y Cb Cr format + * If bits [15:13] = 100 Bayer pixels + */ + + uint16 pixel_data_attributes; + /* interpreted as follows: + * Bits [1:0] = 11 pixel data is displayed to both eyes + * Bits [1:0] = 10 pixel data is routed to the left eye only. + * Bits [1:0] = 01 pixel data is routed to the right eye only. + * Bits [1:0] = 00 pixel data is routed to the alternate display. + * Bit 2 is 0 Pixel Data is in the standard progressive format. + * Bit 2 is 1 Pixel Data is in interlace format. + * Bit 3 is 0 Pixel Data is in the standard progressive format. + * Bit 3 is 1 Pixel Data is in alternate pixel format. + * Bit 4 is 0 Pixel Data is to or from the display frame buffer. + * Bit 4 is 1 Pixel Data is to or from the camera. + * Bit 5 is 0 pixel data contains the next consecutive row of pixels. + * Bit 5 is 1 X Left Edge, Y Top Edge, X Right Edge, Y Bottom Edge, + * X Start, and Y Start parameters are not defined and + * shall be ignored by the client. + * Bits [7:6] = 01 Pixel data is written to the offline image buffer. + * Bits [7:6] = 00 Pixel data is written to the buffer to refresh display. + * Bits [7:6] = 11 Pixel data is written to all image buffers. + * Bits [7:6] = 10 Invalid. Reserved for future use. + * Bits 8 through 11 alternate display number. + * Bits 12 through 14 are reserved for future use and shall be set to zero. + * Bit 15 is 1 the row of pixels is the last row of pixels in a frame. + */ + + uint16 x_left_edge; + uint16 y_top_edge; + /* X,Y coordinate of the top left edge of the screen window */ + + uint16 x_right_edge; + uint16 y_bottom_edge; + /* X,Y coordinate of the bottom right edge of the window being updated. */ + + uint16 x_start; + uint16 y_start; + /* (X Start, Y Start) is the first pixel in the Pixel Data field below. */ + + uint16 pixel_count; + /* number of pixels in the Pixel Data field below. */ + + uint16 parameter_CRC; + /* 16-bit CRC of all bytes from the Packet Length to the Pixel Count. */ + + uint16 reserved; + /* 16-bit variable to make structure align on 4 byte boundary */ + +} mddi_video_stream_packet_type; + +typedef struct GCC_PACKED { + uint16 packet_length; + /* total # of bytes in the packet not including the packet_length field. */ + + uint16 packet_type; + /* A Packet Type of 146 identifies the packet as a Register Access Packet. */ + + uint16 bClient_ID; + /* This field is reserved for future use and shall be set to zero. */ + + uint16 read_write_info; + /* Bits 13:0 a 14-bit unsigned integer that specifies the number of + * 32-bit Register Data List items to be transferred in the + * Register Data List field. + * Bits[15:14] = 00 Write to register(s); + * Bits[15:14] = 10 Read from register(s); + * Bits[15:14] = 11 Response to a Read. + * Bits[15:14] = 01 this value is reserved for future use. */ + + uint32 register_address; + /* the register address that is to be written to or read from. */ + + uint16 parameter_CRC; + /* 16-bit CRC of all bytes from the Packet Length to the Register Address. */ + + uint32 register_data_list[MDDI_HOST_MAX_CLIENT_REG_IN_SAME_ADDR]; + /* list of 4-byte register data values for/from client registers */ + /* For multi-read/write, 512(128 * 4) bytes of data available */ + +} mddi_register_access_packet_type; + +typedef union GCC_PACKED { + mddi_video_stream_packet_type video_pkt; + mddi_register_access_packet_type register_pkt; +#ifdef ENABLE_MDDI_MULTI_READ_WRITE + /* add 1008 byte pad to ensure 1024 byte llist struct, that can be + * manipulated easily with cache */ + uint32 alignment_pad[252]; /* 1008 bytes */ +#else + /* add 48 byte pad to ensure 64 byte llist struct, that can be + * manipulated easily with cache */ + uint32 alignment_pad[12]; /* 48 bytes */ +#endif +} mddi_packet_header_type; + +typedef struct GCC_PACKED mddi_host_llist_struct { + uint16 link_controller_flags; + uint16 packet_header_count; + uint16 packet_data_count; + void *packet_data_pointer; + struct mddi_host_llist_struct *next_packet_pointer; + uint16 reserved; + mddi_packet_header_type packet_header; +} mddi_linked_list_type; + +typedef struct { + struct completion done_comp; + mddi_llist_done_cb_type done_cb; + uint16 next_idx; + boolean waiting; + boolean in_use; +} mddi_linked_list_notify_type; + +#ifdef ENABLE_MDDI_MULTI_READ_WRITE +#define MDDI_LLIST_POOL_SIZE 0x10000 +#else +#define MDDI_LLIST_POOL_SIZE 0x1000 +#endif +#define MDDI_MAX_NUM_LLIST_ITEMS (MDDI_LLIST_POOL_SIZE / \ + sizeof(mddi_linked_list_type)) +#define UNASSIGNED_INDEX MDDI_MAX_NUM_LLIST_ITEMS +#define MDDI_FIRST_DYNAMIC_LLIST_IDX 0 + +/* Static llist items can be used for applications that frequently send + * the same set of packets using the linked list interface. */ +/* Here we configure for 6 static linked list items: + * The 1st is used for a the adaptive backlight setting. + * and the remaining 5 are used for sending window adjustments for + * MDDI clients that need windowing info sent separate from video + * packets. */ +#define MDDI_NUM_STATIC_ABL_ITEMS 1 +#define MDDI_NUM_STATIC_WINDOW_ITEMS 5 +#define MDDI_NUM_STATIC_LLIST_ITEMS (MDDI_NUM_STATIC_ABL_ITEMS + \ + MDDI_NUM_STATIC_WINDOW_ITEMS) +#define MDDI_NUM_DYNAMIC_LLIST_ITEMS (MDDI_MAX_NUM_LLIST_ITEMS - \ + MDDI_NUM_STATIC_LLIST_ITEMS) + +#define MDDI_FIRST_STATIC_LLIST_IDX MDDI_NUM_DYNAMIC_LLIST_ITEMS +#define MDDI_FIRST_STATIC_ABL_IDX MDDI_FIRST_STATIC_LLIST_IDX +#define MDDI_FIRST_STATIC_WINDOW_IDX (MDDI_FIRST_STATIC_LLIST_IDX + \ + MDDI_NUM_STATIC_ABL_ITEMS) + +/* GPIO registers */ +#define VSYNC_WAKEUP_REG 0x80 +#define GPIO_REG 0x81 +#define GPIO_OUTPUT_REG 0x82 +#define GPIO_INTERRUPT_REG 0x83 +#define GPIO_INTERRUPT_ENABLE_REG 0x84 +#define GPIO_POLARITY_REG 0x85 + +/* Interrupt Bits */ +#define MDDI_INT_PRI_PTR_READ 0x0001 +#define MDDI_INT_SEC_PTR_READ 0x0002 +#define MDDI_INT_REV_DATA_AVAIL 0x0004 +#define MDDI_INT_DISP_REQ 0x0008 +#define MDDI_INT_PRI_UNDERFLOW 0x0010 +#define MDDI_INT_SEC_UNDERFLOW 0x0020 +#define MDDI_INT_REV_OVERFLOW 0x0040 +#define MDDI_INT_CRC_ERROR 0x0080 +#define MDDI_INT_MDDI_IN 0x0100 +#define MDDI_INT_PRI_OVERWRITE 0x0200 +#define MDDI_INT_SEC_OVERWRITE 0x0400 +#define MDDI_INT_REV_OVERWRITE 0x0800 +#define MDDI_INT_DMA_FAILURE 0x1000 +#define MDDI_INT_LINK_ACTIVE 0x2000 +#define MDDI_INT_IN_HIBERNATION 0x4000 +#define MDDI_INT_PRI_LINK_LIST_DONE 0x8000 +#define MDDI_INT_SEC_LINK_LIST_DONE 0x10000 +#define MDDI_INT_NO_CMD_PKTS_PEND 0x20000 +#define MDDI_INT_RTD_FAILURE 0x40000 + +#define MDDI_INT_ERROR_CONDITIONS ( \ + MDDI_INT_PRI_UNDERFLOW | MDDI_INT_SEC_UNDERFLOW | \ + MDDI_INT_REV_OVERFLOW | MDDI_INT_CRC_ERROR | \ + MDDI_INT_PRI_OVERWRITE | MDDI_INT_SEC_OVERWRITE | \ + MDDI_INT_RTD_FAILURE | \ + MDDI_INT_REV_OVERWRITE | MDDI_INT_DMA_FAILURE) + +#define MDDI_INT_LINK_STATE_CHANGES ( \ + MDDI_INT_LINK_ACTIVE | MDDI_INT_IN_HIBERNATION) + +/* Status Bits */ +#define MDDI_STAT_LINK_ACTIVE 0x0001 +#define MDDI_STAT_NEW_REV_PTR 0x0002 +#define MDDI_STAT_NEW_PRI_PTR 0x0004 +#define MDDI_STAT_NEW_SEC_PTR 0x0008 +#define MDDI_STAT_IN_HIBERNATION 0x0010 +#define MDDI_STAT_PRI_LINK_LIST_DONE 0x0020 +#define MDDI_STAT_SEC_LINK_LIST_DONE 0x0040 +#define MDDI_STAT_PENDING_TIMING_PKT 0x0080 +#define MDDI_STAT_PENDING_REV_ENCAP 0x0100 +#define MDDI_STAT_PENDING_POWERDOWN 0x0200 +#define MDDI_STAT_RTD_MEAS_FAIL 0x0800 +#define MDDI_STAT_CLIENT_WAKEUP_REQ 0x1000 + +/* Command Bits */ +#define MDDI_CMD_POWERDOWN 0x0100 +#define MDDI_CMD_POWERUP 0x0200 +#define MDDI_CMD_HIBERNATE 0x0300 +#define MDDI_CMD_RESET 0x0400 +#define MDDI_CMD_DISP_IGNORE 0x0501 +#define MDDI_CMD_DISP_LISTEN 0x0500 +#define MDDI_CMD_SEND_REV_ENCAP 0x0600 +#define MDDI_CMD_GET_CLIENT_CAP 0x0601 +#define MDDI_CMD_GET_CLIENT_STATUS 0x0602 +#define MDDI_CMD_SEND_RTD 0x0700 +#define MDDI_CMD_LINK_ACTIVE 0x0900 +#define MDDI_CMD_PERIODIC_REV_ENCAP 0x0A00 +#define MDDI_CMD_FW_LINK_SKEW_CAL 0x0D00 + +extern void mddi_host_init(mddi_host_type host); +extern void mddi_host_powerdown(mddi_host_type host); +extern uint16 mddi_get_next_free_llist_item(mddi_host_type host, boolean wait); +extern uint16 mddi_get_reg_read_llist_item(mddi_host_type host, boolean wait); +extern void mddi_queue_forward_packets(uint16 first_llist_idx, + uint16 last_llist_idx, + boolean wait, + mddi_llist_done_cb_type llist_done_cb, + mddi_host_type host); + +extern void mddi_host_write_pix_attr_reg(uint32 value); +extern void mddi_client_lcd_gpio_poll(uint32 poll_reg_val); +extern void mddi_client_lcd_vsync_detected(boolean detected); +extern void mddi_host_disable_hibernation(boolean disable); + +extern mddi_linked_list_type *llist_extern[]; +extern mddi_linked_list_type *llist_dma_extern[]; +extern mddi_linked_list_notify_type *llist_extern_notify[]; +extern struct timer_list mddi_host_timer; + +typedef struct { + uint16 transmitting_start_idx; + uint16 transmitting_end_idx; + uint16 waiting_start_idx; + uint16 waiting_end_idx; + uint16 reg_read_idx; + uint16 next_free_idx; + boolean reg_read_waiting; +} mddi_llist_info_type; + +extern mddi_llist_info_type mddi_llist; + +#define MDDI_GPIO_DEFAULT_POLLING_INTERVAL 200 +typedef struct { + uint32 polling_reg; + uint32 polling_val; + uint32 polling_interval; + boolean polling_enabled; +} mddi_gpio_info_type; + +uint32 mddi_get_client_id(void); +void mddi_mhctl_remove(mddi_host_type host_idx); +void mddi_host_timer_service(unsigned long data); +void mddi_host_client_cnt_reset(void); +#endif /* MDDIHOSTI_H */ diff --git a/drivers/video/msm/mdp.c b/drivers/video/msm/mdp.c index c3636d55a3c..8df57ae653d 100644 --- a/drivers/video/msm/mdp.c +++ b/drivers/video/msm/mdp.c @@ -2,7 +2,7 @@ * * MSM MDP Interface (used by framebuffer core) * - * Copyright (C) 2007 QUALCOMM Incorporated + * Copyright (c) 2007-2011, Code Aurora Forum. All rights reserved. * Copyright (C) 2007 Google Incorporated * * This software is licensed under the terms of the GNU General Public @@ -15,507 +15,1659 @@ * GNU General Public License for more details. */ +#include #include -#include -#include +#include +#include +#include #include -#include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "mdp.h" +#include "msm_fb.h" +#ifdef CONFIG_FB_MSM_MDP40 +#include "mdp4.h" +#endif +#include "mipi_dsi.h" + +uint32 mdp4_extn_disp; + +static struct clk *mdp_clk; +static struct clk *mdp_pclk; +static struct clk *mdp_axi_clk; +static struct clk *mdp_lut_clk; +int mdp_rev; + +struct regulator *footswitch; + +struct completion mdp_ppp_comp; +struct semaphore mdp_ppp_mutex; +struct semaphore mdp_pipe_ctrl_mutex; + +unsigned long mdp_timer_duration = (HZ/20); /* 50 msecond */ + +boolean mdp_ppp_waiting = FALSE; +uint32 mdp_tv_underflow_cnt; +uint32 mdp_lcdc_underflow_cnt; + +boolean mdp_current_clk_on = FALSE; +boolean mdp_is_in_isr = FALSE; + +/* + * legacy mdp_in_processing is only for DMA2-MDDI + * this applies to DMA2 block only + */ +uint32 mdp_in_processing = FALSE; + +#ifdef CONFIG_FB_MSM_MDP40 +uint32 mdp_intr_mask = MDP4_ANY_INTR_MASK; +#else +uint32 mdp_intr_mask = MDP_ANY_INTR_MASK; +#endif + +MDP_BLOCK_TYPE mdp_debug[MDP_MAX_BLOCK]; + +atomic_t mdp_block_power_cnt[MDP_MAX_BLOCK]; + +spinlock_t mdp_spin_lock; +struct workqueue_struct *mdp_dma_wq; /*mdp dma wq */ +struct workqueue_struct *mdp_vsync_wq; /*mdp vsync wq */ + +static struct workqueue_struct *mdp_pipe_ctrl_wq; /* mdp mdp pipe ctrl wq */ +static struct delayed_work mdp_pipe_ctrl_worker; + +static boolean mdp_suspended = FALSE; +DEFINE_MUTEX(mdp_suspend_mutex); + +#ifdef CONFIG_FB_MSM_MDP40 +struct mdp_dma_data dma2_data; +struct mdp_dma_data dma_s_data; +struct mdp_dma_data dma_e_data; +ulong mdp4_display_intf; +#else +static struct mdp_dma_data dma2_data; +static struct mdp_dma_data dma_s_data; +#ifndef CONFIG_FB_MSM_MDP303 +static struct mdp_dma_data dma_e_data; +#endif +#endif +static struct mdp_dma_data dma3_data; + +extern ktime_t mdp_dma2_last_update_time; + +extern uint32 mdp_dma2_update_time_in_usec; +extern int mdp_lcd_rd_cnt_offset_slow; +extern int mdp_lcd_rd_cnt_offset_fast; +extern int mdp_usec_diff_threshold; + +#ifdef CONFIG_FB_MSM_LCDC +extern int first_pixel_start_x; +extern int first_pixel_start_y; +#endif + +#ifdef MSM_FB_ENABLE_DBGFS +struct dentry *mdp_dir; +#endif + +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) +static int mdp_suspend(struct platform_device *pdev, pm_message_t state); +#else +#define mdp_suspend NULL +#endif + +struct timeval mdp_dma2_timeval; +struct timeval mdp_ppp_timeval; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static struct early_suspend early_suspend; +#endif + +static u32 mdp_irq; + +static uint32 mdp_prim_panel_type = NO_PANEL; +#ifndef CONFIG_FB_MSM_MDP22 +DEFINE_MUTEX(mdp_lut_push_sem); +static int mdp_lut_i; +static int mdp_lut_hw_update(struct fb_cmap *cmap) +{ + int i; + u16 *c[3]; + u16 r, g, b; + + c[0] = cmap->green; + c[1] = cmap->blue; + c[2] = cmap->red; + + for (i = 0; i < cmap->len; i++) { + if (copy_from_user(&r, cmap->red++, sizeof(r)) || + copy_from_user(&g, cmap->green++, sizeof(g)) || + copy_from_user(&b, cmap->blue++, sizeof(b))) + return -EFAULT; + +#ifdef CONFIG_FB_MSM_MDP40 + MDP_OUTP(MDP_BASE + 0x94800 + +#else + MDP_OUTP(MDP_BASE + 0x93800 + +#endif + (0x400*mdp_lut_i) + cmap->start*4 + i*4, + ((g & 0xff) | + ((b & 0xff) << 8) | + ((r & 0xff) << 16))); + } + + return 0; +} -#include -#include -#include +static int mdp_lut_push; +static int mdp_lut_push_i; +static int mdp_lut_update_nonlcdc(struct fb_info *info, struct fb_cmap *cmap) +{ + int ret; -#include "mdp_hw.h" + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + ret = mdp_lut_hw_update(cmap); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); -struct class *mdp_class; + if (ret) + return ret; -#define MDP_CMD_DEBUG_ACCESS_BASE (0x10000) + mutex_lock(&mdp_lut_push_sem); + mdp_lut_push = 1; + mdp_lut_push_i = mdp_lut_i; + mutex_unlock(&mdp_lut_push_sem); -static uint16_t mdp_default_ccs[] = { - 0x254, 0x000, 0x331, 0x254, 0xF38, 0xE61, 0x254, 0x409, 0x000, - 0x010, 0x080, 0x080 -}; + mdp_lut_i = (mdp_lut_i + 1)%2; -static DECLARE_WAIT_QUEUE_HEAD(mdp_dma2_waitqueue); -static DECLARE_WAIT_QUEUE_HEAD(mdp_ppp_waitqueue); -static struct msmfb_callback *dma_callback; -static struct clk *clk; -static unsigned int mdp_irq_mask; -static DEFINE_SPINLOCK(mdp_lock); -DEFINE_MUTEX(mdp_mutex); + return 0; +} -static int enable_mdp_irq(struct mdp_info *mdp, uint32_t mask) +static int mdp_lut_update_lcdc(struct fb_info *info, struct fb_cmap *cmap) { - unsigned long irq_flags; - int ret = 0; + int ret; - BUG_ON(!mask); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + ret = mdp_lut_hw_update(cmap); - spin_lock_irqsave(&mdp_lock, irq_flags); - /* if the mask bits are already set return an error, this interrupt - * is already enabled */ - if (mdp_irq_mask & mask) { - printk(KERN_ERR "mdp irq already on already on %x %x\n", - mdp_irq_mask, mask); - ret = -1; - } - /* if the mdp irq is not already enabled enable it */ - if (!mdp_irq_mask) { - if (clk) - clk_enable(clk); - enable_irq(mdp->irq); - } - - /* update the irq mask to reflect the fact that the interrupt is - * enabled */ - mdp_irq_mask |= mask; - spin_unlock_irqrestore(&mdp_lock, irq_flags); - return ret; + if (ret) { + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return ret; + } + + MDP_OUTP(MDP_BASE + 0x90070, (mdp_lut_i << 10) | 0x17); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + mdp_lut_i = (mdp_lut_i + 1)%2; + + return 0; } -static int locked_disable_mdp_irq(struct mdp_info *mdp, uint32_t mask) +static void mdp_lut_enable(void) { - /* this interrupt is already disabled! */ - if (!(mdp_irq_mask & mask)) { - printk(KERN_ERR "mdp irq already off %x %x\n", - mdp_irq_mask, mask); - return -1; + if (mdp_lut_push) { + mutex_lock(&mdp_lut_push_sem); + mdp_lut_push = 0; + MDP_OUTP(MDP_BASE + 0x90070, + (mdp_lut_push_i << 10) | 0x17); + mutex_unlock(&mdp_lut_push_sem); } - /* update the irq mask to reflect the fact that the interrupt is - * disabled */ - mdp_irq_mask &= ~(mask); - /* if no one is waiting on the interrupt, disable it */ - if (!mdp_irq_mask) { - disable_irq_nosync(mdp->irq); - if (clk) - clk_disable(clk); +} + +#define MDP_HIST_MAX_BIN 32 +static __u32 mdp_hist_r[MDP_HIST_MAX_BIN]; +static __u32 mdp_hist_g[MDP_HIST_MAX_BIN]; +static __u32 mdp_hist_b[MDP_HIST_MAX_BIN]; + +#ifdef CONFIG_FB_MSM_MDP40 +struct mdp_histogram mdp_hist; +struct completion mdp_hist_comp; +boolean mdp_is_hist_start = FALSE; +#else +static struct mdp_histogram mdp_hist; +static struct completion mdp_hist_comp; +static boolean mdp_is_hist_start = FALSE; +#endif +static DEFINE_MUTEX(mdp_hist_mutex); + +int mdp_histogram_ctrl(boolean en) +{ + unsigned long flag; + boolean hist_start; + spin_lock_irqsave(&mdp_spin_lock, flag); + hist_start = mdp_is_hist_start; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + if (hist_start == TRUE) { + if (en == TRUE) { + mdp_enable_irq(MDP_HISTOGRAM_TERM); + mdp_hist.frame_cnt = 1; + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); +#ifdef CONFIG_FB_MSM_MDP40 + MDP_OUTP(MDP_BASE + 0x95010, 1); + MDP_OUTP(MDP_BASE + 0x9501c, INTR_HIST_DONE); + MDP_OUTP(MDP_BASE + 0x95004, 1); + MDP_OUTP(MDP_BASE + 0x95000, 1); +#else + MDP_OUTP(MDP_BASE + 0x94004, 1); + MDP_OUTP(MDP_BASE + 0x94000, 1); +#endif + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, + FALSE); + } else + mdp_disable_irq(MDP_HISTOGRAM_TERM); } return 0; } -static int disable_mdp_irq(struct mdp_info *mdp, uint32_t mask) +int mdp_start_histogram(struct fb_info *info) { - unsigned long irq_flags; - int ret; + unsigned long flag; - spin_lock_irqsave(&mdp_lock, irq_flags); - ret = locked_disable_mdp_irq(mdp, mask); - spin_unlock_irqrestore(&mdp_lock, irq_flags); + int ret = 0; + mutex_lock(&mdp_hist_mutex); + if (mdp_is_hist_start == TRUE) { + printk(KERN_ERR "%s histogram already started\n", __func__); + ret = -EPERM; + goto mdp_hist_start_err; + } + + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_is_hist_start = TRUE; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_HISTOGRAM_TERM); + mdp_hist.frame_cnt = 1; + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); +#ifdef CONFIG_FB_MSM_MDP40 + MDP_OUTP(MDP_BASE + 0x95004, 1); + MDP_OUTP(MDP_BASE + 0x95000, 1); +#else + MDP_OUTP(MDP_BASE + 0x94004, 1); + MDP_OUTP(MDP_BASE + 0x94000, 1); +#endif + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + +mdp_hist_start_err: + mutex_unlock(&mdp_hist_mutex); + return ret; + +} +int mdp_stop_histogram(struct fb_info *info) +{ + unsigned long flag; + int ret = 0; + mutex_lock(&mdp_hist_mutex); + if (!mdp_is_hist_start) { + printk(KERN_ERR "%s histogram already stopped\n", __func__); + ret = -EPERM; + goto mdp_hist_stop_err; + } + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_is_hist_start = FALSE; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + /* disable the irq for histogram since we handled it + when the control reaches here */ + mdp_disable_irq(MDP_HISTOGRAM_TERM); + +mdp_hist_stop_err: + mutex_unlock(&mdp_hist_mutex); return ret; } -static irqreturn_t mdp_isr(int irq, void *data) +static int mdp_do_histogram(struct fb_info *info, struct mdp_histogram *hist) { - uint32_t status; - unsigned long irq_flags; - struct mdp_info *mdp = data; + int ret = 0; - spin_lock_irqsave(&mdp_lock, irq_flags); + if (!hist->frame_cnt || (hist->bin_cnt == 0) || + (hist->bin_cnt > MDP_HIST_MAX_BIN)) + return -EINVAL; + mutex_lock(&mdp_hist_mutex); + if (!mdp_is_hist_start) { + printk(KERN_ERR "%s histogram not started\n", __func__); + mutex_unlock(&mdp_hist_mutex); + return -EPERM; + } + mutex_unlock(&mdp_hist_mutex); - status = mdp_readl(mdp, MDP_INTR_STATUS); - mdp_writel(mdp, status, MDP_INTR_CLEAR); + INIT_COMPLETION(mdp_hist_comp); - status &= mdp_irq_mask; - if (status & DL0_DMA2_TERM_DONE) { - if (dma_callback) { - dma_callback->func(dma_callback); - dma_callback = NULL; - } - wake_up(&mdp_dma2_waitqueue); + mdp_hist.bin_cnt = hist->bin_cnt; + mdp_hist.frame_cnt = hist->frame_cnt; + mdp_hist.r = (hist->r) ? mdp_hist_r : 0; + mdp_hist.g = (hist->g) ? mdp_hist_g : 0; + mdp_hist.b = (hist->b) ? mdp_hist_b : 0; + + wait_for_completion_killable(&mdp_hist_comp); + + if (hist->r) { + ret = copy_to_user(hist->r, mdp_hist.r, hist->bin_cnt*4); + if (ret) + goto hist_err; } + if (hist->g) { + ret = copy_to_user(hist->g, mdp_hist.g, hist->bin_cnt*4); + if (ret) + goto hist_err; + } + if (hist->b) { + ret = copy_to_user(hist->b, mdp_hist.b, hist->bin_cnt*4); + if (ret) + goto hist_err; + } + return 0; - if (status & DL0_ROI_DONE) - wake_up(&mdp_ppp_waitqueue); +hist_err: + printk(KERN_ERR "%s: invalid hist buffer\n", __func__); + return ret; +} +#endif - if (status) - locked_disable_mdp_irq(mdp, status); +/* Returns < 0 on error, 0 on timeout, or > 0 on successful wait */ - spin_unlock_irqrestore(&mdp_lock, irq_flags); - return IRQ_HANDLED; +int mdp_ppp_pipe_wait(void) +{ + int ret = 1; + + /* wait 5 seconds for the operation to complete before declaring + the MDP hung */ + + if (mdp_ppp_waiting == TRUE) { + ret = wait_for_completion_interruptible_timeout(&mdp_ppp_comp, + 5 * HZ); + + if (!ret) + printk(KERN_ERR "%s: Timed out waiting for the MDP.\n", + __func__); + } + + return ret; } -static uint32_t mdp_check_mask(uint32_t mask) +static DEFINE_SPINLOCK(mdp_lock); +static int mdp_irq_mask; +static int mdp_irq_enabled; + +/* + * mdp_enable_irq: can not be called from isr + */ +void mdp_enable_irq(uint32 term) { - uint32_t ret; unsigned long irq_flags; spin_lock_irqsave(&mdp_lock, irq_flags); - ret = mdp_irq_mask & mask; + if (mdp_irq_mask & term) { + printk(KERN_ERR "%s: MDP IRQ term-0x%x is already set, mask=%x irq=%d\n", + __func__, term, mdp_irq_mask, mdp_irq_enabled); + } else { + mdp_irq_mask |= term; + if (mdp_irq_mask && !mdp_irq_enabled) { + mdp_irq_enabled = 1; + enable_irq(mdp_irq); + } + } spin_unlock_irqrestore(&mdp_lock, irq_flags); - return ret; } -static int mdp_wait(struct mdp_info *mdp, uint32_t mask, wait_queue_head_t *wq) +/* + * mdp_disable_irq: can not be called from isr + */ +void mdp_disable_irq(uint32 term) { - int ret = 0; unsigned long irq_flags; - wait_event_timeout(*wq, !mdp_check_mask(mask), HZ); - spin_lock_irqsave(&mdp_lock, irq_flags); - if (mdp_irq_mask & mask) { - locked_disable_mdp_irq(mdp, mask); - printk(KERN_WARNING "timeout waiting for mdp to complete %x\n", - mask); - ret = -ETIMEDOUT; + if (!(mdp_irq_mask & term)) { + printk(KERN_ERR "%s: MDP IRQ term-0x%x is NOT set, mask=%x irq=%d\n", + __func__, term, mdp_irq_mask, mdp_irq_enabled); + } else { + mdp_irq_mask &= ~term; + if (!mdp_irq_mask && mdp_irq_enabled) { + mdp_irq_enabled = 0; + disable_irq(mdp_irq); + } } spin_unlock_irqrestore(&mdp_lock, irq_flags); - - return ret; } -void mdp_dma_wait(struct mdp_device *mdp_dev) +void mdp_disable_irq_nosync(uint32 term) { -#define MDP_MAX_TIMEOUTS 20 - static int timeout_count; - struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); - - if (mdp_wait(mdp, DL0_DMA2_TERM_DONE, &mdp_dma2_waitqueue) == -ETIMEDOUT) - timeout_count++; - else - timeout_count = 0; - - if (timeout_count > MDP_MAX_TIMEOUTS) { - printk(KERN_ERR "mdp: dma failed %d times, somethings wrong!\n", - MDP_MAX_TIMEOUTS); - BUG(); + spin_lock(&mdp_lock); + if (!(mdp_irq_mask & term)) { + printk(KERN_ERR "%s: MDP IRQ term-0x%x is NOT set, mask=%x irq=%d\n", + __func__, term, mdp_irq_mask, mdp_irq_enabled); + } else { + mdp_irq_mask &= ~term; + if (!mdp_irq_mask && mdp_irq_enabled) { + mdp_irq_enabled = 0; + disable_irq_nosync(mdp_irq); + } } + spin_unlock(&mdp_lock); } -static int mdp_ppp_wait(struct mdp_info *mdp) +void mdp_pipe_kickoff(uint32 term, struct msm_fb_data_type *mfd) { - return mdp_wait(mdp, DL0_ROI_DONE, &mdp_ppp_waitqueue); + /* complete all the writes before starting */ + wmb(); + + /* kick off PPP engine */ + if (term == MDP_PPP_TERM) { + if (mdp_debug[MDP_PPP_BLOCK]) + jiffies_to_timeval(jiffies, &mdp_ppp_timeval); + + /* let's turn on PPP block */ + mdp_pipe_ctrl(MDP_PPP_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + mdp_enable_irq(term); + INIT_COMPLETION(mdp_ppp_comp); + mdp_ppp_waiting = TRUE; + outpdw(MDP_BASE + 0x30, 0x1000); + wait_for_completion_killable(&mdp_ppp_comp); + mdp_disable_irq(term); + + if (mdp_debug[MDP_PPP_BLOCK]) { + struct timeval now; + + jiffies_to_timeval(jiffies, &now); + mdp_ppp_timeval.tv_usec = + now.tv_usec - mdp_ppp_timeval.tv_usec; + MSM_FB_DEBUG("MDP-PPP: %d\n", + (int)mdp_ppp_timeval.tv_usec); + } + } else if (term == MDP_DMA2_TERM) { + if (mdp_debug[MDP_DMA2_BLOCK]) { + MSM_FB_DEBUG("MDP-DMA2: %d\n", + (int)mdp_dma2_timeval.tv_usec); + jiffies_to_timeval(jiffies, &mdp_dma2_timeval); + } + /* DMA update timestamp */ + mdp_dma2_last_update_time = ktime_get_real(); + /* let's turn on DMA2 block */ +#if 0 + mdp_pipe_ctrl(MDP_DMA2_BLOCK, MDP_BLOCK_POWER_ON, FALSE); +#endif +#ifdef CONFIG_FB_MSM_MDP22 + outpdw(MDP_CMD_DEBUG_ACCESS_BASE + 0x0044, 0x0);/* start DMA */ +#else + mdp_lut_enable(); + +#ifdef CONFIG_FB_MSM_MDP40 + outpdw(MDP_BASE + 0x000c, 0x0); /* start DMA */ +#else + outpdw(MDP_BASE + 0x0044, 0x0); /* start DMA */ + +#ifdef CONFIG_FB_MSM_MDP303 + +#ifdef CONFIG_FB_MSM_MIPI_DSI + mipi_dsi_cmd_mdp_sw_trigger(); +#endif + +#endif + +#endif +#endif +#ifdef CONFIG_FB_MSM_MDP40 + } else if (term == MDP_DMA_S_TERM) { + mdp_pipe_ctrl(MDP_DMA_S_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + outpdw(MDP_BASE + 0x0010, 0x0); /* start DMA */ + } else if (term == MDP_DMA_E_TERM) { + mdp_pipe_ctrl(MDP_DMA_E_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + outpdw(MDP_BASE + 0x0014, 0x0); /* start DMA */ + } else if (term == MDP_OVERLAY0_TERM) { + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp_lut_enable(); + outpdw(MDP_BASE + 0x0004, 0); + } else if (term == MDP_OVERLAY1_TERM) { + mdp_pipe_ctrl(MDP_OVERLAY1_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp_lut_enable(); + outpdw(MDP_BASE + 0x0008, 0); + } +#else + } else if (term == MDP_DMA_S_TERM) { + mdp_pipe_ctrl(MDP_DMA_S_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + outpdw(MDP_BASE + 0x0048, 0x0); /* start DMA */ + } else if (term == MDP_DMA_E_TERM) { + mdp_pipe_ctrl(MDP_DMA_E_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + outpdw(MDP_BASE + 0x004C, 0x0); + } +#endif } +static int mdp_clk_rate; +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; -void mdp_dma_to_mddi(struct mdp_info *mdp, uint32_t addr, uint32_t stride, - uint32_t width, uint32_t height, uint32_t x, uint32_t y, - struct msmfb_callback *callback) +static void mdp_pipe_ctrl_workqueue_handler(struct work_struct *work) +{ + mdp_pipe_ctrl(MDP_MASTER_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} +void mdp_pipe_ctrl(MDP_BLOCK_TYPE block, MDP_BLOCK_POWER_STATE state, + boolean isr) { - uint32_t dma2_cfg; - uint16_t ld_param = 0; /* 0=PRIM, 1=SECD, 2=EXT */ + boolean mdp_all_blocks_off = TRUE; + int i; + unsigned long flag; + struct msm_fb_panel_data *pdata; + + /* + * It is assumed that if isr = TRUE then start = OFF + * if start = ON when isr = TRUE it could happen that the usercontext + * could turn off the clocks while the interrupt is updating the + * power to ON + */ + WARN_ON(isr == TRUE && state == MDP_BLOCK_POWER_ON); + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (MDP_BLOCK_POWER_ON == state) { + atomic_inc(&mdp_block_power_cnt[block]); + + if (MDP_DMA2_BLOCK == block) + mdp_in_processing = TRUE; + } else { + atomic_dec(&mdp_block_power_cnt[block]); + + if (atomic_read(&mdp_block_power_cnt[block]) < 0) { + /* + * Master has to serve a request to power off MDP always + * It also has a timer to power off. So, in case of + * timer expires first and DMA2 finishes later, + * master has to power off two times + * There shouldn't be multiple power-off request for + * other blocks + */ + if (block != MDP_MASTER_BLOCK) { + MSM_FB_INFO("mdp_block_power_cnt[block=%d] \ + multiple power-off request\n", block); + } + atomic_set(&mdp_block_power_cnt[block], 0); + } - if (enable_mdp_irq(mdp, DL0_DMA2_TERM_DONE)) { - printk(KERN_ERR "mdp_dma_to_mddi: busy\n"); - return; + if (MDP_DMA2_BLOCK == block) + mdp_in_processing = FALSE; } + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + /* + * If it's in isr, we send our request to workqueue. + * Otherwise, processing happens in the current context + */ + if (isr) { + if (mdp_current_clk_on) { + /* checking all blocks power state */ + for (i = 0; i < MDP_MAX_BLOCK; i++) { + if (atomic_read(&mdp_block_power_cnt[i]) > 0) { + mdp_all_blocks_off = FALSE; + break; + } + } + + if (mdp_all_blocks_off) { + /* send workqueue to turn off mdp power */ + queue_delayed_work(mdp_pipe_ctrl_wq, + &mdp_pipe_ctrl_worker, + mdp_timer_duration); + } + } + } else { + down(&mdp_pipe_ctrl_mutex); + /* checking all blocks power state */ + for (i = 0; i < MDP_MAX_BLOCK; i++) { + if (atomic_read(&mdp_block_power_cnt[i]) > 0) { + mdp_all_blocks_off = FALSE; + break; + } + } + + /* + * find out whether a delayable work item is currently + * pending + */ + + if (delayed_work_pending(&mdp_pipe_ctrl_worker)) { + /* + * try to cancel the current work if it fails to + * stop (which means del_timer can't delete it + * from the list, it's about to expire and run), + * we have to let it run. queue_delayed_work won't + * accept the next job which is same as + * queue_delayed_work(mdp_timer_duration = 0) + */ + cancel_delayed_work(&mdp_pipe_ctrl_worker); + } + + if ((mdp_all_blocks_off) && (mdp_current_clk_on)) { + mutex_lock(&mdp_suspend_mutex); + if (block == MDP_MASTER_BLOCK || mdp_suspended) { + mdp_current_clk_on = FALSE; + mb(); + /* turn off MDP clks */ + mdp_vsync_clk_disable(); + for (i = 0; i < pdev_list_cnt; i++) { + pdata = (struct msm_fb_panel_data *) + pdev_list[i]->dev.platform_data; + if (pdata && pdata->clk_func) + pdata->clk_func(0); + } + if (mdp_clk != NULL) { + mdp_clk_rate = clk_get_rate(mdp_clk); + clk_disable(mdp_clk); + if (mdp_hw_revision <= + MDP4_REVISION_V2_1 && + mdp_clk_rate > 122880000) { + clk_set_rate(mdp_clk, + 122880000); + } + MSM_FB_DEBUG("MDP CLK OFF\n"); + } + if (mdp_pclk != NULL) { + clk_disable(mdp_pclk); + MSM_FB_DEBUG("MDP PCLK OFF\n"); + } + if (mdp_axi_clk != NULL) + clk_disable(mdp_axi_clk); + if (mdp_lut_clk != NULL) + clk_disable(mdp_lut_clk); + } else { + /* send workqueue to turn off mdp power */ + queue_delayed_work(mdp_pipe_ctrl_wq, + &mdp_pipe_ctrl_worker, + mdp_timer_duration); + } + mutex_unlock(&mdp_suspend_mutex); + } else if ((!mdp_all_blocks_off) && (!mdp_current_clk_on)) { + mdp_current_clk_on = TRUE; + /* turn on MDP clks */ + for (i = 0; i < pdev_list_cnt; i++) { + pdata = (struct msm_fb_panel_data *) + pdev_list[i]->dev.platform_data; + if (pdata && pdata->clk_func) + pdata->clk_func(1); + } + if (mdp_clk != NULL) { + if (mdp_hw_revision <= + MDP4_REVISION_V2_1 && + mdp_clk_rate > 122880000) { + clk_set_rate(mdp_clk, + mdp_clk_rate); + } + clk_enable(mdp_clk); + MSM_FB_DEBUG("MDP CLK ON\n"); + } + if (mdp_pclk != NULL) { + clk_enable(mdp_pclk); + MSM_FB_DEBUG("MDP PCLK ON\n"); + } + if (mdp_axi_clk != NULL) + clk_enable(mdp_axi_clk); + if (mdp_lut_clk != NULL) + clk_enable(mdp_lut_clk); + mdp_vsync_clk_enable(); + } + up(&mdp_pipe_ctrl_mutex); + } +} - dma_callback = callback; +#ifndef CONFIG_FB_MSM_MDP40 +irqreturn_t mdp_isr(int irq, void *ptr) +{ + uint32 mdp_interrupt = 0; + struct mdp_dma_data *dma; + + mdp_is_in_isr = TRUE; + do { + mdp_interrupt = inp32(MDP_INTR_STATUS); + outp32(MDP_INTR_CLEAR, mdp_interrupt); - dma2_cfg = DMA_PACK_TIGHT | - DMA_PACK_ALIGN_LSB | - DMA_PACK_PATTERN_RGB | - DMA_OUT_SEL_AHB | - DMA_IBUF_NONCONTIGUOUS; + mdp_interrupt &= mdp_intr_mask; - dma2_cfg |= DMA_IBUF_FORMAT_RGB565; + if (mdp_interrupt & TV_ENC_UNDERRUN) { + mdp_interrupt &= ~(TV_ENC_UNDERRUN); + mdp_tv_underflow_cnt++; + } - dma2_cfg |= DMA_OUT_SEL_MDDI; + if (!mdp_interrupt) + break; - dma2_cfg |= DMA_MDDI_DMAOUT_LCD_SEL_PRIMARY; + /* DMA3 TV-Out Start */ + if (mdp_interrupt & TV_OUT_DMA3_START) { + /* let's disable TV out interrupt */ + mdp_intr_mask &= ~TV_OUT_DMA3_START; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); - dma2_cfg |= DMA_DITHER_EN; + dma = &dma3_data; + if (dma->waiting) { + dma->waiting = FALSE; + complete(&dma->comp); + } + } +#ifndef CONFIG_FB_MSM_MDP22 + if (mdp_interrupt & MDP_HIST_DONE) { + outp32(MDP_BASE + 0x94018, 0x3); + outp32(MDP_INTR_CLEAR, MDP_HIST_DONE); + if (mdp_hist.r) + memcpy(mdp_hist.r, MDP_BASE + 0x94100, + mdp_hist.bin_cnt*4); + if (mdp_hist.g) + memcpy(mdp_hist.g, MDP_BASE + 0x94200, + mdp_hist.bin_cnt*4); + if (mdp_hist.b) + memcpy(mdp_hist.b, MDP_BASE + 0x94300, + mdp_hist.bin_cnt*4); + complete(&mdp_hist_comp); + if (mdp_is_hist_start == TRUE) { + MDP_OUTP(MDP_BASE + 0x94004, + mdp_hist.frame_cnt); + MDP_OUTP(MDP_BASE + 0x94000, 1); + } + } - /* setup size, address, and stride */ - mdp_writel(mdp, (height << 16) | (width), - MDP_CMD_DEBUG_ACCESS_BASE + 0x0184); - mdp_writel(mdp, addr, MDP_CMD_DEBUG_ACCESS_BASE + 0x0188); - mdp_writel(mdp, stride, MDP_CMD_DEBUG_ACCESS_BASE + 0x018C); + /* LCDC UnderFlow */ + if (mdp_interrupt & LCDC_UNDERFLOW) { + mdp_lcdc_underflow_cnt++; + /*when underflow happens HW resets all the histogram + registers that were set before so restore them back + to normal.*/ + MDP_OUTP(MDP_BASE + 0x94010, 1); + MDP_OUTP(MDP_BASE + 0x9401c, 2); + if (mdp_is_hist_start == TRUE) { + MDP_OUTP(MDP_BASE + 0x94004, + mdp_hist.frame_cnt); + MDP_OUTP(MDP_BASE + 0x94000, 1); + } + } + /* LCDC Frame Start */ + if (mdp_interrupt & LCDC_FRAME_START) { + /* let's disable LCDC interrupt */ + mdp_intr_mask &= ~LCDC_FRAME_START; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + + dma = &dma2_data; + if (dma->waiting) { + dma->waiting = FALSE; + complete(&dma->comp); + } + } - /* 666 18BPP */ - dma2_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + /* DMA2 LCD-Out Complete */ + if (mdp_interrupt & MDP_DMA_S_DONE) { + dma = &dma_s_data; + dma->busy = FALSE; + mdp_pipe_ctrl(MDP_DMA_S_BLOCK, MDP_BLOCK_POWER_OFF, + TRUE); + complete(&dma->comp); + } + /* DMA_E LCD-Out Complete */ + if (mdp_interrupt & MDP_DMA_E_DONE) { + dma = &dma_s_data; + dma->busy = FALSE; + mdp_pipe_ctrl(MDP_DMA_E_BLOCK, MDP_BLOCK_POWER_OFF, + TRUE); + complete(&dma->comp); + } - /* set y & x offset and MDDI transaction parameters */ - mdp_writel(mdp, (y << 16) | (x), MDP_CMD_DEBUG_ACCESS_BASE + 0x0194); - mdp_writel(mdp, ld_param, MDP_CMD_DEBUG_ACCESS_BASE + 0x01a0); - mdp_writel(mdp, (MDDI_VDO_PACKET_DESC << 16) | MDDI_VDO_PACKET_PRIM, - MDP_CMD_DEBUG_ACCESS_BASE + 0x01a4); +#endif + + /* DMA2 LCD-Out Complete */ + if (mdp_interrupt & MDP_DMA_P_DONE) { + struct timeval now; + + mdp_dma2_last_update_time = ktime_sub(ktime_get_real(), + mdp_dma2_last_update_time); + if (mdp_debug[MDP_DMA2_BLOCK]) { + jiffies_to_timeval(jiffies, &now); + mdp_dma2_timeval.tv_usec = + now.tv_usec - mdp_dma2_timeval.tv_usec; + } +#ifndef CONFIG_FB_MSM_MDP303 + dma = &dma2_data; + dma->busy = FALSE; + mdp_pipe_ctrl(MDP_DMA2_BLOCK, MDP_BLOCK_POWER_OFF, + TRUE); + complete(&dma->comp); +#else + if (mdp_prim_panel_type == MIPI_CMD_PANEL) { + dma = &dma2_data; + dma->busy = FALSE; + mdp_pipe_ctrl(MDP_DMA2_BLOCK, + MDP_BLOCK_POWER_OFF, TRUE); + complete(&dma->comp); + } +#endif + } + /* PPP Complete */ + if (mdp_interrupt & MDP_PPP_DONE) { +#ifdef CONFIG_FB_MSM_MDP31 + MDP_OUTP(MDP_BASE + 0x00100, 0xFFFF); +#endif + mdp_pipe_ctrl(MDP_PPP_BLOCK, MDP_BLOCK_POWER_OFF, TRUE); + if (mdp_ppp_waiting) { + mdp_ppp_waiting = FALSE; + complete(&mdp_ppp_comp); + } + } + } while (1); - mdp_writel(mdp, dma2_cfg, MDP_CMD_DEBUG_ACCESS_BASE + 0x0180); + mdp_is_in_isr = FALSE; - /* start DMA2 */ - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0044); + return IRQ_HANDLED; } +#endif -void mdp_dma(struct mdp_device *mdp_dev, uint32_t addr, uint32_t stride, - uint32_t width, uint32_t height, uint32_t x, uint32_t y, - struct msmfb_callback *callback, int interface) +static void mdp_drv_init(void) { - struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + int i; - if (interface == MSM_MDDI_PMDH_INTERFACE) { - mdp_dma_to_mddi(mdp, addr, stride, width, height, x, y, - callback); + for (i = 0; i < MDP_MAX_BLOCK; i++) { + mdp_debug[i] = 0; } + + /* initialize spin lock and workqueue */ + spin_lock_init(&mdp_spin_lock); + mdp_dma_wq = create_singlethread_workqueue("mdp_dma_wq"); + mdp_vsync_wq = create_singlethread_workqueue("mdp_vsync_wq"); + mdp_pipe_ctrl_wq = create_singlethread_workqueue("mdp_pipe_ctrl_wq"); + INIT_DELAYED_WORK(&mdp_pipe_ctrl_worker, + mdp_pipe_ctrl_workqueue_handler); + + /* initialize semaphore */ + init_completion(&mdp_ppp_comp); + sema_init(&mdp_ppp_mutex, 1); + sema_init(&mdp_pipe_ctrl_mutex, 1); + + dma2_data.busy = FALSE; + dma2_data.dmap_busy = FALSE; + dma2_data.waiting = FALSE; + init_completion(&dma2_data.comp); + init_completion(&dma2_data.dmap_comp); + sema_init(&dma2_data.mutex, 1); + mutex_init(&dma2_data.ov_mutex); + + dma3_data.busy = FALSE; + dma3_data.waiting = FALSE; + init_completion(&dma3_data.comp); + sema_init(&dma3_data.mutex, 1); + + dma_s_data.busy = FALSE; + dma_s_data.waiting = FALSE; + init_completion(&dma_s_data.comp); + sema_init(&dma_s_data.mutex, 1); + +#ifndef CONFIG_FB_MSM_MDP303 + dma_e_data.busy = FALSE; + dma_e_data.waiting = FALSE; + init_completion(&dma_e_data.comp); + mutex_init(&dma_e_data.ov_mutex); +#endif + +#ifndef CONFIG_FB_MSM_MDP22 + init_completion(&mdp_hist_comp); +#endif + + /* initializing mdp power block counter to 0 */ + for (i = 0; i < MDP_MAX_BLOCK; i++) { + atomic_set(&mdp_block_power_cnt[i], 0); + } + +#ifdef MSM_FB_ENABLE_DBGFS + { + struct dentry *root; + char sub_name[] = "mdp"; + + root = msm_fb_get_debugfs_root(); + if (root != NULL) { + mdp_dir = debugfs_create_dir(sub_name, root); + + if (mdp_dir) { + msm_fb_debugfs_file_create(mdp_dir, + "dma2_update_time_in_usec", + (u32 *) &mdp_dma2_update_time_in_usec); + msm_fb_debugfs_file_create(mdp_dir, + "vs_rdcnt_slow", + (u32 *) &mdp_lcd_rd_cnt_offset_slow); + msm_fb_debugfs_file_create(mdp_dir, + "vs_rdcnt_fast", + (u32 *) &mdp_lcd_rd_cnt_offset_fast); + msm_fb_debugfs_file_create(mdp_dir, + "mdp_usec_diff_threshold", + (u32 *) &mdp_usec_diff_threshold); + msm_fb_debugfs_file_create(mdp_dir, + "mdp_current_clk_on", + (u32 *) &mdp_current_clk_on); +#ifdef CONFIG_FB_MSM_LCDC + msm_fb_debugfs_file_create(mdp_dir, + "lcdc_start_x", + (u32 *) &first_pixel_start_x); + msm_fb_debugfs_file_create(mdp_dir, + "lcdc_start_y", + (u32 *) &first_pixel_start_y); +#endif + } + } + } +#endif } -int get_img(struct mdp_img *img, struct fb_info *info, - unsigned long *start, unsigned long *len, - struct file **filep) +static int mdp_probe(struct platform_device *pdev); +static int mdp_remove(struct platform_device *pdev); + +static int mdp_runtime_suspend(struct device *dev) { - int put_needed, ret = 0; - struct file *file; + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} - file = fget_light(img->memory_id, &put_needed); - if (file == NULL) - return -1; +static int mdp_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} - if (MAJOR(file->f_dentry->d_inode->i_rdev) == FB_MAJOR) { - *start = info->fix.smem_start; - *len = info->fix.smem_len; - } else - ret = -1; - fput_light(file, put_needed); +static struct dev_pm_ops mdp_dev_pm_ops = { + .runtime_suspend = mdp_runtime_suspend, + .runtime_resume = mdp_runtime_resume, +}; + + +static struct platform_driver mdp_driver = { + .probe = mdp_probe, + .remove = mdp_remove, +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = mdp_suspend, + .resume = NULL, +#endif + .shutdown = NULL, + .driver = { + /* + * Driver name must match the device name added in + * platform.c. + */ + .name = "mdp", + .pm = &mdp_dev_pm_ops, + }, +}; + +static int mdp_off(struct platform_device *pdev) +{ + int ret = 0; + mdp_histogram_ctrl(FALSE); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + ret = panel_next_off(pdev); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); return ret; } -void put_img(struct file *src_file, struct file *dst_file) +static int mdp_on(struct platform_device *pdev) { + int ret = 0; +#ifdef CONFIG_FB_MSM_MDP40 + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + if (is_mdp4_hw_reset()) { + mdp4_hw_init(); + outpdw(MDP_BASE + 0x0038, mdp4_display_intf); + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +#endif + mdp_histogram_ctrl(TRUE); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + ret = panel_next_on(pdev); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return ret; } -int mdp_blit(struct mdp_device *mdp_dev, struct fb_info *fb, - struct mdp_blit_req *req) +static int mdp_resource_initialized; +static struct msm_panel_common_pdata *mdp_pdata; + +uint32 mdp_hw_revision; + +/* + * mdp_hw_revision: + * 0 == V1 + * 1 == V2 + * 2 == V2.1 + * + */ +void mdp_hw_version(void) { - int ret; - unsigned long src_start = 0, src_len = 0, dst_start = 0, dst_len = 0; - struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); - struct file *src_file = 0, *dst_file = 0; - - /* WORKAROUND FOR HARDWARE BUG IN BG TILE FETCH */ - if (unlikely(req->src_rect.h == 0 || - req->src_rect.w == 0)) { - printk(KERN_ERR "mpd_ppp: src img of zero size!\n"); - return -EINVAL; + char *cp; + uint32 *hp; + + if (mdp_pdata == NULL) + return; + + mdp_hw_revision = MDP4_REVISION_NONE; + if (mdp_pdata->hw_revision_addr == 0) + return; + + /* tlmmgpio2 shadow */ + cp = (char *)ioremap(mdp_pdata->hw_revision_addr, 0x16); + + if (cp == NULL) + return; + + hp = (uint32 *)cp; /* HW_REVISION_NUMBER */ + mdp_hw_revision = *hp; + iounmap(cp); + + mdp_hw_revision >>= 28; /* bit 31:28 */ + mdp_hw_revision &= 0x0f; + + MSM_FB_DEBUG("%s: mdp_hw_revision=%x\n", + __func__, mdp_hw_revision); +} + +#ifdef CONFIG_FB_MSM_MDP40 +static void configure_mdp_core_clk_table(uint32 min_clk_rate) +{ + uint8 count; + uint32 current_rate; + if (mdp_clk && mdp_pdata + && mdp_pdata->mdp_core_clk_table) { + if (clk_set_min_rate(mdp_clk, + min_clk_rate) < 0) + printk(KERN_ERR "%s: clk_set_min_rate failed\n", + __func__); + else { + count = 0; + current_rate = clk_get_rate(mdp_clk); + while (count < mdp_pdata->num_mdp_clk) { + if (mdp_pdata->mdp_core_clk_table[count] + < current_rate) { + mdp_pdata-> + mdp_core_clk_table[count] = + current_rate; + } + count++; + } + } } - if (unlikely(req->dst_rect.h == 0 || - req->dst_rect.w == 0)) - return -EINVAL; +} +#endif - /* do this first so that if this fails, the caller can always - * safely call put_img */ - if (unlikely(get_img(&req->src, fb, &src_start, &src_len, &src_file))) { - printk(KERN_ERR "mpd_ppp: could not retrieve src image from " - "memory\n"); +#ifdef CONFIG_MSM_BUS_SCALING +static uint32_t mdp_bus_scale_handle; +int mdp_bus_scale_update_request(uint32_t index) +{ + if (!mdp_pdata && (!mdp_pdata->mdp_bus_scale_table + || index > (mdp_pdata->mdp_bus_scale_table->num_usecases - 1))) { + printk(KERN_ERR "%s invalid table or index\n", __func__); return -EINVAL; } - - if (unlikely(get_img(&req->dst, fb, &dst_start, &dst_len, &dst_file))) { - printk(KERN_ERR "mpd_ppp: could not retrieve dst image from " - "memory\n"); + if (mdp_bus_scale_handle < 1) { + printk(KERN_ERR "%s invalid bus handle\n", __func__); return -EINVAL; } - mutex_lock(&mdp_mutex); - - /* transp_masking unimplemented */ - req->transp_mask = MDP_TRANSP_NOP; - if (unlikely((req->transp_mask != MDP_TRANSP_NOP || - req->alpha != MDP_ALPHA_NOP || - HAS_ALPHA(req->src.format)) && - (req->flags & MDP_ROT_90 && - req->dst_rect.w <= 16 && req->dst_rect.h >= 16))) { - int i; - unsigned int tiles = req->dst_rect.h / 16; - unsigned int remainder = req->dst_rect.h % 16; - req->src_rect.w = 16*req->src_rect.w / req->dst_rect.h; - req->dst_rect.h = 16; - for (i = 0; i < tiles; i++) { - enable_mdp_irq(mdp, DL0_ROI_DONE); - ret = mdp_ppp_blit(mdp, req, src_file, src_start, - src_len, dst_file, dst_start, - dst_len); - if (ret) - goto err_bad_blit; - ret = mdp_ppp_wait(mdp); - if (ret) - goto err_wait_failed; - req->dst_rect.y += 16; - req->src_rect.x += req->src_rect.w; - } - if (!remainder) - goto end; - req->src_rect.w = remainder*req->src_rect.w / req->dst_rect.h; - req->dst_rect.h = remainder; - } - enable_mdp_irq(mdp, DL0_ROI_DONE); - ret = mdp_ppp_blit(mdp, req, src_file, src_start, src_len, dst_file, - dst_start, - dst_len); - if (ret) - goto err_bad_blit; - ret = mdp_ppp_wait(mdp); - if (ret) - goto err_wait_failed; -end: - put_img(src_file, dst_file); - mutex_unlock(&mdp_mutex); - return 0; -err_bad_blit: - disable_mdp_irq(mdp, DL0_ROI_DONE); -err_wait_failed: - put_img(src_file, dst_file); - mutex_unlock(&mdp_mutex); + return msm_bus_scale_client_update_request(mdp_bus_scale_handle, + index); +} +#endif +DEFINE_MUTEX(mdp_clk_lock); +int mdp_set_core_clk(uint16 perf_level) +{ + int ret = -EINVAL; + if (mdp_clk && mdp_pdata + && mdp_pdata->mdp_core_clk_table) { + if (perf_level > mdp_pdata->num_mdp_clk) + printk(KERN_ERR "%s invalid perf level\n", __func__); + else { + mutex_lock(&mdp_clk_lock); + if (mdp4_extn_disp) + perf_level = 1; + ret = clk_set_rate(mdp_clk, + mdp_pdata-> + mdp_core_clk_table[mdp_pdata->num_mdp_clk + - perf_level]); + mutex_unlock(&mdp_clk_lock); + if (ret) { + printk(KERN_ERR "%s unable to set mdp_core_clk rate\n", + __func__); + } + } + } return ret; } -void mdp_set_grp_disp(struct mdp_device *mdp_dev, unsigned disp_id) +unsigned long mdp_get_core_clk(void) { - struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + unsigned long clk_rate = 0; + if (mdp_clk) { + mutex_lock(&mdp_clk_lock); + clk_rate = clk_get_rate(mdp_clk); + mutex_unlock(&mdp_clk_lock); + } - disp_id &= 0xf; - mdp_writel(mdp, disp_id, MDP_FULL_BYPASS_WORD43); + return clk_rate; } -int register_mdp_client(struct class_interface *cint) +unsigned long mdp_perf_level2clk_rate(uint32 perf_level) { - if (!mdp_class) { - pr_err("mdp: no mdp_class when registering mdp client\n"); - return -ENODEV; - } - cint->class = mdp_class; - return class_interface_register(cint); -} + unsigned long clk_rate = 0; + + if (mdp_pdata && mdp_pdata->mdp_core_clk_table) { + if (perf_level > mdp_pdata->num_mdp_clk) { + printk(KERN_ERR "%s invalid perf level\n", __func__); + clk_rate = mdp_get_core_clk(); + } else { + if (mdp4_extn_disp) + perf_level = 1; + clk_rate = mdp_pdata-> + mdp_core_clk_table[mdp_pdata->num_mdp_clk + - perf_level]; + } + } else + clk_rate = mdp_get_core_clk(); -#include "mdp_csc_table.h" -#include "mdp_scale_tables.h" + return clk_rate; +} -int mdp_probe(struct platform_device *pdev) +static int mdp_irq_clk_setup(void) { - struct resource *resource; int ret; - int n; - struct mdp_info *mdp; - resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!resource) { - pr_err("mdp: can not get mdp mem resource!\n"); - return -ENOMEM; +#ifdef CONFIG_FB_MSM_MDP40 + ret = request_irq(mdp_irq, mdp4_isr, IRQF_DISABLED, "MDP", 0); +#else + ret = request_irq(mdp_irq, mdp_isr, IRQF_DISABLED, "MDP", 0); +#endif + if (ret) { + printk(KERN_ERR "mdp request_irq() failed!\n"); + return ret; + } + disable_irq(mdp_irq); + + footswitch = regulator_get(NULL, "fs_mdp"); + if (IS_ERR(footswitch)) + footswitch = NULL; + else + regulator_enable(footswitch); + + mdp_clk = clk_get(NULL, "mdp_clk"); + if (IS_ERR(mdp_clk)) { + ret = PTR_ERR(mdp_clk); + printk(KERN_ERR "can't get mdp_clk error:%d!\n", ret); + free_irq(mdp_irq, 0); + return ret; + } + + mdp_pclk = clk_get(NULL, "mdp_pclk"); + if (IS_ERR(mdp_pclk)) + mdp_pclk = NULL; + + if (mdp_rev == MDP_REV_42) { + mdp_axi_clk = clk_get(NULL, "mdp_axi_clk"); + if (IS_ERR(mdp_axi_clk)) { + ret = PTR_ERR(mdp_axi_clk); + clk_put(mdp_clk); + pr_err("can't get mdp_axi_clk error:%d!\n", ret); + return ret; + } + + mdp_lut_clk = clk_get(NULL, "lut_mdp"); + if (IS_ERR(mdp_lut_clk)) { + ret = PTR_ERR(mdp_lut_clk); + pr_err("can't get mdp_clk error:%d!\n", ret); + clk_put(mdp_clk); + clk_put(mdp_axi_clk); + free_irq(mdp_irq, 0); + return ret; + } + } else { + mdp_axi_clk = NULL; + mdp_lut_clk = NULL; + } + +#ifdef CONFIG_FB_MSM_MDP40 + /* + * mdp_clk should greater than mdp_pclk always + */ + if (mdp_pdata && mdp_pdata->mdp_core_clk_rate) { + mutex_lock(&mdp_clk_lock); + clk_set_rate(mdp_clk, mdp_pdata->mdp_core_clk_rate); + if (mdp_lut_clk != NULL) + clk_set_rate(mdp_lut_clk, mdp_pdata->mdp_core_clk_rate); + mutex_unlock(&mdp_clk_lock); + } + MSM_FB_DEBUG("mdp_clk: mdp_clk=%d\n", (int)clk_get_rate(mdp_clk)); +#endif + return 0; +} + +static int mdp_probe(struct platform_device *pdev) +{ + struct platform_device *msm_fb_dev = NULL; + struct msm_fb_data_type *mfd; + struct msm_fb_panel_data *pdata = NULL; + int rc; + resource_size_t size ; +#ifdef CONFIG_FB_MSM_MDP40 + int intf, if_no; +#else + unsigned long flag; +#endif +#if defined(CONFIG_FB_MSM_MIPI_DSI) && defined(CONFIG_FB_MSM_MDP40) + struct mipi_panel_info *mipi; +#endif + + if ((pdev->id == 0) && (pdev->num_resources > 0)) { + mdp_pdata = pdev->dev.platform_data; + + size = resource_size(&pdev->resource[0]); + msm_mdp_base = ioremap(pdev->resource[0].start, size); + + MSM_FB_DEBUG("MDP HW Base phy_Address = 0x%x virt = 0x%x\n", + (int)pdev->resource[0].start, (int)msm_mdp_base); + + if (unlikely(!msm_mdp_base)) + return -ENOMEM; + + mdp_irq = platform_get_irq(pdev, 0); + if (mdp_irq < 0) { + pr_err("mdp: can not get mdp irq\n"); + return -ENOMEM; + } + + mdp_rev = mdp_pdata->mdp_rev; + rc = mdp_irq_clk_setup(); + + if (rc) + return rc; + + mdp_hw_version(); + + /* initializing mdp hw */ +#ifdef CONFIG_FB_MSM_MDP40 + mdp4_hw_init(); + mdp4_fetch_cfg(clk_get_rate(mdp_clk)); +#else + mdp_hw_init(); +#endif + +#ifdef CONFIG_FB_MSM_OVERLAY + mdp_hw_cursor_init(); +#endif + + mdp_resource_initialized = 1; + return 0; } - mdp = kzalloc(sizeof(struct mdp_info), GFP_KERNEL); - if (!mdp) + if (!mdp_resource_initialized) + return -EPERM; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; + + msm_fb_dev = platform_device_alloc("msm_fb", pdev->id); + if (!msm_fb_dev) return -ENOMEM; - mdp->irq = platform_get_irq(pdev, 0); - if (mdp->irq < 0) { - pr_err("mdp: can not get mdp irq\n"); - ret = mdp->irq; - goto error_get_irq; + /* link to the latest pdev */ + mfd->pdev = msm_fb_dev; + + /* add panel data */ + if (platform_device_add_data + (msm_fb_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + printk(KERN_ERR "mdp_probe: platform_device_add_data failed!\n"); + rc = -ENOMEM; + goto mdp_probe_err; } + /* data chain */ + pdata = msm_fb_dev->dev.platform_data; + pdata->on = mdp_on; + pdata->off = mdp_off; + pdata->next = pdev; + + mdp_prim_panel_type = mfd->panel.type; + switch (mfd->panel.type) { + case EXT_MDDI_PANEL: + case MDDI_PANEL: + case EBI2_PANEL: + INIT_WORK(&mfd->dma_update_worker, + mdp_lcd_update_workqueue_handler); + INIT_WORK(&mfd->vsync_resync_worker, + mdp_vsync_resync_workqueue_handler); + mfd->hw_refresh = FALSE; + + if (mfd->panel.type == EXT_MDDI_PANEL) { + /* 15 fps -> 66 msec */ + mfd->refresh_timer_duration = (66 * HZ / 1000); + } else { + /* 24 fps -> 42 msec */ + mfd->refresh_timer_duration = (42 * HZ / 1000); + } - mdp->base = ioremap(resource->start, - resource->end - resource->start); - if (mdp->base == 0) { - printk(KERN_ERR "msmfb: cannot allocate mdp regs!\n"); - ret = -ENOMEM; - goto error_ioremap; +#ifdef CONFIG_FB_MSM_MDP22 + mfd->dma_fnc = mdp_dma2_update; + mfd->dma = &dma2_data; +#else + if (mfd->panel_info.pdest == DISPLAY_1) { +#if defined(CONFIG_FB_MSM_OVERLAY) && defined(CONFIG_FB_MSM_MDDI) + mfd->dma_fnc = mdp4_mddi_overlay; + mfd->cursor_update = mdp4_mddi_overlay_cursor; +#else + mfd->dma_fnc = mdp_dma2_update; +#endif + mfd->dma = &dma2_data; + mfd->lut_update = mdp_lut_update_nonlcdc; + mfd->do_histogram = mdp_do_histogram; + } else { + mfd->dma_fnc = mdp_dma_s_update; + mfd->dma = &dma_s_data; + } +#endif + if (mdp_pdata) + mfd->vsync_gpio = mdp_pdata->gpio; + else + mfd->vsync_gpio = -1; + +#ifdef CONFIG_FB_MSM_MDP40 + if (mfd->panel.type == EBI2_PANEL) + intf = EBI2_INTF; + else + intf = MDDI_INTF; + + if (mfd->panel_info.pdest == DISPLAY_1) + if_no = PRIMARY_INTF_SEL; + else + if_no = SECONDARY_INTF_SEL; + + mdp4_display_intf_sel(if_no, intf); +#endif + mdp_config_vsync(mfd); + break; + +#ifdef CONFIG_FB_MSM_MIPI_DSI + case MIPI_VIDEO_PANEL: +#ifndef CONFIG_FB_MSM_MDP303 + pdata->on = mdp4_dsi_video_on; + pdata->off = mdp4_dsi_video_off; + mfd->hw_refresh = TRUE; + mfd->dma_fnc = mdp4_dsi_video_overlay; + if (mfd->panel_info.pdest == DISPLAY_1) { + if_no = PRIMARY_INTF_SEL; + mfd->dma = &dma2_data; + } else { + if_no = EXTERNAL_INTF_SEL; + mfd->dma = &dma_e_data; + } + mdp4_display_intf_sel(if_no, DSI_VIDEO_INTF); +#else + pdata->on = mdp_dsi_video_on; + pdata->off = mdp_dsi_video_off; + mfd->hw_refresh = TRUE; + mfd->dma_fnc = mdp_dsi_video_update; + mfd->do_histogram = mdp_do_histogram; + if (mfd->panel_info.pdest == DISPLAY_1) + mfd->dma = &dma2_data; + else { + printk(KERN_ERR "Invalid Selection of destination panel\n"); + rc = -ENODEV; + goto mdp_probe_err; + } + +#endif + break; + + case MIPI_CMD_PANEL: +#ifndef CONFIG_FB_MSM_MDP303 + mfd->dma_fnc = mdp4_dsi_cmd_overlay; +#ifdef CONFIG_FB_MSM_MDP40 + mipi = &mfd->panel_info.mipi; + configure_mdp_core_clk_table((mipi->dsi_pclk_rate) * 3 / 2); +#endif + if (mfd->panel_info.pdest == DISPLAY_1) { + if_no = PRIMARY_INTF_SEL; + mfd->dma = &dma2_data; + } else { + if_no = SECONDARY_INTF_SEL; + mfd->dma = &dma_s_data; + } + mdp4_display_intf_sel(if_no, DSI_CMD_INTF); +#else + mfd->dma_fnc = mdp_dma2_update; + mfd->do_histogram = mdp_do_histogram; + if (mfd->panel_info.pdest == DISPLAY_1) + mfd->dma = &dma2_data; + else { + printk(KERN_ERR "Invalid Selection of destination panel\n"); + rc = -ENODEV; + goto mdp_probe_err; + } +#endif + mdp_config_vsync(mfd); + break; +#endif + +#ifdef CONFIG_FB_MSM_DTV + case DTV_PANEL: + pdata->on = mdp4_dtv_on; + pdata->off = mdp4_dtv_off; + mfd->hw_refresh = TRUE; + mfd->cursor_update = mdp_hw_cursor_update; + mfd->dma_fnc = mdp4_dtv_overlay; + mfd->dma = &dma_e_data; + mdp4_display_intf_sel(EXTERNAL_INTF_SEL, DTV_INTF); + break; +#endif + case HDMI_PANEL: + case LCDC_PANEL: + pdata->on = mdp_lcdc_on; + pdata->off = mdp_lcdc_off; + mfd->hw_refresh = TRUE; +#if defined(CONFIG_FB_MSM_OVERLAY) && defined(CONFIG_FB_MSM_MDP40) + mfd->cursor_update = mdp_hw_cursor_sync_update; +#else + mfd->cursor_update = mdp_hw_cursor_update; +#endif +#ifndef CONFIG_FB_MSM_MDP22 + mfd->lut_update = mdp_lut_update_lcdc; + mfd->do_histogram = mdp_do_histogram; +#endif +#ifdef CONFIG_FB_MSM_OVERLAY + mfd->dma_fnc = mdp4_lcdc_overlay; +#else + mfd->dma_fnc = mdp_lcdc_update; +#endif + +#ifdef CONFIG_FB_MSM_MDP40 + configure_mdp_core_clk_table((mfd->panel_info.clk_rate) + * 23 / 20); + if (mfd->panel.type == HDMI_PANEL) { + mfd->dma = &dma_e_data; + mdp4_display_intf_sel(EXTERNAL_INTF_SEL, LCDC_RGB_INTF); + } else { + mfd->dma = &dma2_data; + mdp4_display_intf_sel(PRIMARY_INTF_SEL, LCDC_RGB_INTF); + } +#else + mfd->dma = &dma2_data; + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_intr_mask &= ~MDP_DMA_P_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); +#endif + break; + + case TV_PANEL: +#if defined(CONFIG_FB_MSM_OVERLAY) && defined(CONFIG_FB_MSM_TVOUT) + pdata->on = mdp4_atv_on; + pdata->off = mdp4_atv_off; + mfd->dma_fnc = mdp4_atv_overlay; + mfd->dma = &dma_e_data; + mdp4_display_intf_sel(EXTERNAL_INTF_SEL, TV_INTF); +#else + pdata->on = mdp_dma3_on; + pdata->off = mdp_dma3_off; + mfd->hw_refresh = TRUE; + mfd->dma_fnc = mdp_dma3_update; + mfd->dma = &dma3_data; +#endif + break; + + default: + printk(KERN_ERR "mdp_probe: unknown device type!\n"); + rc = -ENODEV; + goto mdp_probe_err; + } +#ifdef CONFIG_FB_MSM_MDP40 + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp4_display_intf = inpdw(MDP_BASE + 0x0038); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +#endif + +#ifdef CONFIG_MSM_BUS_SCALING + if (!mdp_bus_scale_handle && mdp_pdata && + mdp_pdata->mdp_bus_scale_table) { + mdp_bus_scale_handle = + msm_bus_scale_register_client( + mdp_pdata->mdp_bus_scale_table); + if (!mdp_bus_scale_handle) { + printk(KERN_ERR "%s not able to get bus scale\n", + __func__); + return -ENOMEM; + } } +#endif + /* set driver data */ + platform_set_drvdata(msm_fb_dev, mfd); + + rc = platform_device_add(msm_fb_dev); + if (rc) { + goto mdp_probe_err; + } + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + pdev_list[pdev_list_cnt++] = pdev; + mdp4_extn_disp = 0; + return 0; + + mdp_probe_err: + platform_device_put(msm_fb_dev); +#ifdef CONFIG_MSM_BUS_SCALING + if (mdp_pdata && mdp_pdata->mdp_bus_scale_table && + mdp_bus_scale_handle > 0) + msm_bus_scale_unregister_client(mdp_bus_scale_handle); +#endif + return rc; +} - mdp->mdp_dev.dma = mdp_dma; - mdp->mdp_dev.dma_wait = mdp_dma_wait; - mdp->mdp_dev.blit = mdp_blit; - mdp->mdp_dev.set_grp_disp = mdp_set_grp_disp; +#ifdef CONFIG_PM +static void mdp_suspend_sub(void) +{ + /* cancel pipe ctrl worker */ + cancel_delayed_work(&mdp_pipe_ctrl_worker); + + /* for workder can't be cancelled... */ + flush_workqueue(mdp_pipe_ctrl_wq); + + /* let's wait for PPP completion */ + while (atomic_read(&mdp_block_power_cnt[MDP_PPP_BLOCK]) > 0) + cpu_relax(); + + /* try to power down */ + mdp_pipe_ctrl(MDP_MASTER_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); - clk = clk_get(&pdev->dev, "mdp_clk"); - if (IS_ERR(clk)) { - printk(KERN_INFO "mdp: failed to get mdp clk"); - return PTR_ERR(clk); + mutex_lock(&mdp_suspend_mutex); + mdp_suspended = TRUE; + mutex_unlock(&mdp_suspend_mutex); +} +#endif + +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) +static int mdp_suspend(struct platform_device *pdev, pm_message_t state) +{ + if (pdev->id == 0) { + mdp_suspend_sub(); + if (mdp_current_clk_on) { + printk(KERN_WARNING"MDP suspend failed\n"); + return -EBUSY; + } } - ret = request_irq(mdp->irq, mdp_isr, IRQF_DISABLED, "msm_mdp", mdp); - if (ret) - goto error_request_irq; - disable_irq(mdp->irq); - mdp_irq_mask = 0; - - /* debug interface write access */ - mdp_writel(mdp, 1, 0x60); - - mdp_writel(mdp, MDP_ANY_INTR_MASK, MDP_INTR_ENABLE); - mdp_writel(mdp, 1, MDP_EBI2_PORTMAP_MODE); - - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01f8); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01fc); - - for (n = 0; n < ARRAY_SIZE(csc_table); n++) - mdp_writel(mdp, csc_table[n].val, csc_table[n].reg); - - /* clear up unused fg/main registers */ - /* comp.plane 2&3 ystride */ - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0120); - - /* unpacked pattern */ - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x012c); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0130); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0134); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0158); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x015c); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0160); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0170); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0174); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x017c); - - /* comp.plane 2 & 3 */ - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0114); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x0118); - - /* clear unused bg registers */ - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01c8); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01d0); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01dc); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01e0); - mdp_writel(mdp, 0, MDP_CMD_DEBUG_ACCESS_BASE + 0x01e4); - - for (n = 0; n < ARRAY_SIZE(mdp_upscale_table); n++) - mdp_writel(mdp, mdp_upscale_table[n].val, - mdp_upscale_table[n].reg); - - for (n = 0; n < 9; n++) - mdp_writel(mdp, mdp_default_ccs[n], 0x40440 + 4 * n); - mdp_writel(mdp, mdp_default_ccs[9], 0x40500 + 4 * 0); - mdp_writel(mdp, mdp_default_ccs[10], 0x40500 + 4 * 0); - mdp_writel(mdp, mdp_default_ccs[11], 0x40500 + 4 * 0); - - /* register mdp device */ - mdp->mdp_dev.dev.parent = &pdev->dev; - mdp->mdp_dev.dev.class = mdp_class; - dev_set_name(&mdp->mdp_dev.dev, "mdp%d", pdev->id); - - /* if you can remove the platform device you'd have to implement - * this: - mdp_dev.release = mdp_class; */ - - ret = device_register(&mdp->mdp_dev.dev); - if (ret) - goto error_device_register; return 0; +} +#endif -error_device_register: - free_irq(mdp->irq, mdp); -error_request_irq: - iounmap(mdp->base); -error_get_irq: -error_ioremap: - kfree(mdp); - return ret; +#ifdef CONFIG_HAS_EARLYSUSPEND +static void mdp_early_suspend(struct early_suspend *h) +{ + mdp_suspend_sub(); } -static struct platform_driver msm_mdp_driver = { - .probe = mdp_probe, - .driver = {.name = "msm_mdp"}, -}; +static void mdp_early_resume(struct early_suspend *h) +{ + mutex_lock(&mdp_suspend_mutex); + mdp_suspended = FALSE; + mutex_unlock(&mdp_suspend_mutex); +} +#endif + +static int mdp_remove(struct platform_device *pdev) +{ + if (footswitch != NULL) + regulator_put(footswitch); + iounmap(msm_mdp_base); + pm_runtime_disable(&pdev->dev); +#ifdef CONFIG_MSM_BUS_SCALING + if (mdp_pdata && mdp_pdata->mdp_bus_scale_table && + mdp_bus_scale_handle > 0) + msm_bus_scale_unregister_client(mdp_bus_scale_handle); +#endif + return 0; +} + +static int mdp_register_driver(void) +{ +#ifdef CONFIG_HAS_EARLYSUSPEND + early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB - 1; + early_suspend.suspend = mdp_early_suspend; + early_suspend.resume = mdp_early_resume; + register_early_suspend(&early_suspend); +#endif + + return platform_driver_register(&mdp_driver); +} -static int __init mdp_init(void) +static int __init mdp_driver_init(void) { - mdp_class = class_create(THIS_MODULE, "msm_mdp"); - if (IS_ERR(mdp_class)) { - printk(KERN_ERR "Error creating mdp class\n"); - return PTR_ERR(mdp_class); + int ret; + + mdp_drv_init(); + + ret = mdp_register_driver(); + if (ret) { + printk(KERN_ERR "mdp_register_driver() failed!\n"); + return ret; } - return platform_driver_register(&msm_mdp_driver); + +#if defined(CONFIG_DEBUG_FS) + mdp_debugfs_init(); +#endif + + return 0; + } -subsys_initcall(mdp_init); +module_init(mdp_driver_init); diff --git a/drivers/video/msm/mdp.h b/drivers/video/msm/mdp.h new file mode 100644 index 00000000000..165502cef2b --- /dev/null +++ b/drivers/video/msm/mdp.h @@ -0,0 +1,727 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MDP_H +#define MDP_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef CONFIG_MSM_BUS_SCALING +#include +#include +#endif + +#include + +#include +#include + +#include "msm_fb_panel.h" + +extern uint32 mdp_hw_revision; +extern ulong mdp4_display_intf; +extern spinlock_t mdp_spin_lock; +extern int mdp_rev; + +#define MDP4_REVISION_V1 0 +#define MDP4_REVISION_V2 1 +#define MDP4_REVISION_V2_1 2 +#define MDP4_REVISION_NONE 0xffffffff + +#ifdef BIT +#undef BIT +#endif + +#define BIT(x) (1<<(x)) + +#define MDPOP_NOP 0 +#define MDPOP_LR BIT(0) /* left to right flip */ +#define MDPOP_UD BIT(1) /* up and down flip */ +#define MDPOP_ROT90 BIT(2) /* rotate image to 90 degree */ +#define MDPOP_ROT180 (MDPOP_UD|MDPOP_LR) +#define MDPOP_ROT270 (MDPOP_ROT90|MDPOP_UD|MDPOP_LR) +#define MDPOP_ASCALE BIT(7) +#define MDPOP_ALPHAB BIT(8) /* enable alpha blending */ +#define MDPOP_TRANSP BIT(9) /* enable transparency */ +#define MDPOP_DITHER BIT(10) /* enable dither */ +#define MDPOP_SHARPENING BIT(11) /* enable sharpening */ +#define MDPOP_BLUR BIT(12) /* enable blur */ +#define MDPOP_FG_PM_ALPHA BIT(13) + +struct mdp_table_entry { + uint32_t reg; + uint32_t val; +}; + +extern struct mdp_ccs mdp_ccs_yuv2rgb ; +extern struct mdp_ccs mdp_ccs_rgb2yuv ; + +/* + * MDP Image Structure + */ +typedef struct mdpImg_ { + uint32 imgType; /* Image type */ + uint32 *bmy_addr; /* bitmap or y addr */ + uint32 *cbcr_addr; /* cbcr addr */ + uint32 width; /* image width */ + uint32 mdpOp; /* image opertion (rotation,flip up/down, alpha/tp) */ + uint32 tpVal; /* transparency color */ + uint32 alpha; /* alpha percentage 0%(0x0) ~ 100%(0x100) */ + int sp_value; /* sharpening strength */ +} MDPIMG; + +#define MDP_OUTP(addr, data) outpdw((addr), (data)) + +#define MDP_BASE msm_mdp_base + +typedef enum { + MDP_BC_SCALE_POINT2_POINT4, + MDP_BC_SCALE_POINT4_POINT6, + MDP_BC_SCALE_POINT6_POINT8, + MDP_BC_SCALE_POINT8_1, + MDP_BC_SCALE_UP, + MDP_PR_SCALE_POINT2_POINT4, + MDP_PR_SCALE_POINT4_POINT6, + MDP_PR_SCALE_POINT6_POINT8, + MDP_PR_SCALE_POINT8_1, + MDP_PR_SCALE_UP, + MDP_SCALE_BLUR, + MDP_INIT_SCALE +} MDP_SCALE_MODE; + +typedef enum { + MDP_BLOCK_POWER_OFF, + MDP_BLOCK_POWER_ON +} MDP_BLOCK_POWER_STATE; + +typedef enum { + MDP_CMD_BLOCK, + MDP_OVERLAY0_BLOCK, + MDP_MASTER_BLOCK, + MDP_PPP_BLOCK, + MDP_DMA2_BLOCK, + MDP_DMA3_BLOCK, + MDP_DMA_S_BLOCK, + MDP_DMA_E_BLOCK, + MDP_OVERLAY1_BLOCK, + MDP_MAX_BLOCK +} MDP_BLOCK_TYPE; + +/* Let's keep Q Factor power of 2 for optimization */ +#define MDP_SCALE_Q_FACTOR 512 + +#ifdef CONFIG_FB_MSM_MDP31 +#define MDP_MAX_X_SCALE_FACTOR (MDP_SCALE_Q_FACTOR*8) +#define MDP_MIN_X_SCALE_FACTOR (MDP_SCALE_Q_FACTOR/8) +#define MDP_MAX_Y_SCALE_FACTOR (MDP_SCALE_Q_FACTOR*8) +#define MDP_MIN_Y_SCALE_FACTOR (MDP_SCALE_Q_FACTOR/8) +#else +#define MDP_MAX_X_SCALE_FACTOR (MDP_SCALE_Q_FACTOR*4) +#define MDP_MIN_X_SCALE_FACTOR (MDP_SCALE_Q_FACTOR/4) +#define MDP_MAX_Y_SCALE_FACTOR (MDP_SCALE_Q_FACTOR*4) +#define MDP_MIN_Y_SCALE_FACTOR (MDP_SCALE_Q_FACTOR/4) +#endif + +/* SHIM Q Factor */ +#define PHI_Q_FACTOR 29 +#define PQF_PLUS_5 (PHI_Q_FACTOR + 5) /* due to 32 phases */ +#define PQF_PLUS_4 (PHI_Q_FACTOR + 4) +#define PQF_PLUS_2 (PHI_Q_FACTOR + 2) /* to get 4.0 */ +#define PQF_MINUS_2 (PHI_Q_FACTOR - 2) /* to get 0.25 */ +#define PQF_PLUS_5_PLUS_2 (PQF_PLUS_5 + 2) +#define PQF_PLUS_5_MINUS_2 (PQF_PLUS_5 - 2) + +#define MDP_CONVTP(tpVal) (((tpVal&0xF800)<<8)|((tpVal&0x7E0)<<5)|((tpVal&0x1F)<<3)) + +#define MDPOP_ROTATION (MDPOP_ROT90|MDPOP_LR|MDPOP_UD) +#define MDP_CHKBIT(val, bit) ((bit) == ((val) & (bit))) + +/* overlay interface API defines */ +typedef enum { + MORE_IBUF, + FINAL_IBUF, + COMPLETE_IBUF +} MDP_IBUF_STATE; + +struct mdp_dirty_region { + __u32 xoffset; /* source origin in the x-axis */ + __u32 yoffset; /* source origin in the y-axis */ + __u32 width; /* number of pixels in the x-axis */ + __u32 height; /* number of pixels in the y-axis */ +}; + +/* + * MDP extended data types + */ +typedef struct mdp_roi_s { + uint32 x; + uint32 y; + uint32 width; + uint32 height; + int32 lcd_x; + int32 lcd_y; + uint32 dst_width; + uint32 dst_height; +} MDP_ROI; + +typedef struct mdp_ibuf_s { + uint8 *buf; + uint32 bpp; + uint32 ibuf_type; + uint32 ibuf_width; + uint32 ibuf_height; + + MDP_ROI roi; + MDPIMG mdpImg; + + int32 dma_x; + int32 dma_y; + uint32 dma_w; + uint32 dma_h; + + uint32 vsync_enable; +} MDPIBUF; + +struct mdp_dma_data { + boolean busy; + boolean dmap_busy; + boolean waiting; + struct mutex ov_mutex; + struct semaphore mutex; + struct completion comp; + struct completion dmap_comp; +}; + +#define MDP_CMD_DEBUG_ACCESS_BASE (MDP_BASE+0x10000) + +#define MDP_DMA2_TERM 0x1 +#define MDP_DMA3_TERM 0x2 +#define MDP_PPP_TERM 0x4 +#define MDP_DMA_S_TERM 0x8 +#define MDP_DMA_E_TERM 0x10 +#ifdef CONFIG_FB_MSM_MDP40 +#define MDP_OVERLAY0_TERM 0x20 +#define MDP_OVERLAY1_TERM 0x40 +#endif +#define MDP_HISTOGRAM_TERM 0x80 + +#define ACTIVE_START_X_EN BIT(31) +#define ACTIVE_START_Y_EN BIT(31) +#define ACTIVE_HIGH 0 +#define ACTIVE_LOW 1 +#define MDP_DMA_S_DONE BIT(2) +#define MDP_DMA_E_DONE BIT(3) +#define LCDC_FRAME_START BIT(15) +#define LCDC_UNDERFLOW BIT(16) + +#ifdef CONFIG_FB_MSM_MDP22 +#define MDP_DMA_P_DONE BIT(2) +#else +#define MDP_DMA_P_DONE BIT(14) +#endif + +#define MDP_PPP_DONE BIT(0) +#define TV_OUT_DMA3_DONE BIT(6) +#define TV_ENC_UNDERRUN BIT(7) +#define TV_OUT_DMA3_START BIT(13) +#define MDP_HIST_DONE BIT(20) + +#ifdef CONFIG_FB_MSM_MDP22 +#define MDP_ANY_INTR_MASK (MDP_PPP_DONE| \ + MDP_DMA_P_DONE| \ + TV_ENC_UNDERRUN) +#else +#define MDP_ANY_INTR_MASK (MDP_PPP_DONE| \ + MDP_DMA_P_DONE| \ + MDP_DMA_S_DONE| \ + MDP_DMA_E_DONE| \ + LCDC_UNDERFLOW| \ + MDP_HIST_DONE| \ + TV_ENC_UNDERRUN) +#endif + +#define MDP_TOP_LUMA 16 +#define MDP_TOP_CHROMA 0 +#define MDP_BOTTOM_LUMA 19 +#define MDP_BOTTOM_CHROMA 3 +#define MDP_LEFT_LUMA 22 +#define MDP_LEFT_CHROMA 6 +#define MDP_RIGHT_LUMA 25 +#define MDP_RIGHT_CHROMA 9 + +#define CLR_G 0x0 +#define CLR_B 0x1 +#define CLR_R 0x2 +#define CLR_ALPHA 0x3 + +#define CLR_Y CLR_G +#define CLR_CB CLR_B +#define CLR_CR CLR_R + +/* from lsb to msb */ +#define MDP_GET_PACK_PATTERN(a,x,y,z,bit) (((a)<<(bit*3))|((x)<<(bit*2))|((y)<blt_base = (ulong) buf; + off = ALIGN(fbi->var.xres, 32) * fbi->var.yres * bpp * 2; + off += (1920 * 1080 * 2 * 1); /* hdmi */ + pipe->blt_base += off; + + pr_info("%s: base=%x offset=%x\n", + __func__, (int) pipe->blt_base, (int)off); + + return off; + +} +#else +static inline int mdp4_overlay_writeback_setup(struct fb_info *fbi, + struct mdp4_overlay_pipe *pipe, uint8 *buf, int bpp) +{ + return 0; +} +#endif + +void mdp4_sw_reset(unsigned long bits); +void mdp4_display_intf_sel(int output, unsigned long intf); +void mdp4_overlay_cfg(int layer, int blt_mode, int refresh, int direct_out); +void mdp4_ebi2_lcd_setup(int lcd, unsigned long base, int ystride); +void mdp4_mddi_setup(int which, unsigned long id); +unsigned long mdp4_display_status(void); +void mdp4_enable_clk_irq(void); +void mdp4_disable_clk_irq(void); +void mdp4_dma_p_update(struct msm_fb_data_type *mfd); +void mdp4_dma_s_update(struct msm_fb_data_type *mfd); +void mdp_pipe_ctrl(MDP_BLOCK_TYPE block, MDP_BLOCK_POWER_STATE state, + boolean isr); +void mdp4_pipe_kickoff(uint32 pipe, struct msm_fb_data_type *mfd); +int mdp4_lcdc_on(struct platform_device *pdev); +int mdp4_lcdc_off(struct platform_device *pdev); +void mdp4_lcdc_update(struct msm_fb_data_type *mfd); +void mdp4_intr_clear_set(ulong clear, ulong set); +void mdp4_dma_p_cfg(void); +unsigned is_mdp4_hw_reset(void); +void mdp4_hw_init(void); +void mdp4_isr_read(int); +void mdp4_clear_lcdc(void); +void mdp4_mixer_blend_init(int mixer_num); +void mdp4_vg_qseed_init(int vg_num); +void mdp4_vg_csc_mv_setup(int vp_num); +void mdp4_vg_csc_pre_bv_setup(int vp_num); +void mdp4_vg_csc_post_bv_setup(int vp_num); +void mdp4_vg_csc_pre_lv_setup(int vp_num); +void mdp4_vg_csc_post_lv_setup(int vp_num); +void mdp4_mixer1_csc_mv_setup(void); +void mdp4_mixer1_csc_pre_bv_setup(void); +void mdp4_mixer1_csc_post_bv_setup(void); +void mdp4_mixer1_csc_pre_lv_setup(void); +void mdp4_mixer1_csc_post_lv_setup(void); +irqreturn_t mdp4_isr(int irq, void *ptr); +void mdp4_overlay_format_to_pipe(uint32 format, struct mdp4_overlay_pipe *pipe); +uint32 mdp4_overlay_format(struct mdp4_overlay_pipe *pipe); +uint32 mdp4_overlay_unpack_pattern(struct mdp4_overlay_pipe *pipe); +uint32 mdp4_overlay_op_mode(struct mdp4_overlay_pipe *pipe); +void mdp4_lcdc_overlay(struct msm_fb_data_type *mfd); +void mdp4_overlay_dtv_ov_done_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_overlay_dtv_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_dtv_overlay(struct msm_fb_data_type *mfd); +int mdp4_dtv_on(struct platform_device *pdev); +int mdp4_dtv_off(struct platform_device *pdev); +void mdp4_atv_overlay(struct msm_fb_data_type *mfd); +int mdp4_atv_on(struct platform_device *pdev); +int mdp4_atv_off(struct platform_device *pdev); +void mdp4_dsi_video_fxn_register(cmd_fxn_t fxn); +void mdp4_dsi_video_overlay(struct msm_fb_data_type *mfd); +int mdp4_dsi_video_on(struct platform_device *pdev); +int mdp4_dsi_video_off(struct platform_device *pdev); +void mdp4_overlay0_done_dsi_video(void); +void mdp4_overlay0_done_dsi_cmd(struct mdp_dma_data *dma); +void mdp4_dsi_cmd_overlay(struct msm_fb_data_type *mfd); +void mdp4_overlay_dsi_state_set(int state); +void mdp4_overlay_rgb_setup(struct mdp4_overlay_pipe *pipe); +void mdp4_overlay_reg_flush(struct mdp4_overlay_pipe *pipe, int all); +void mdp4_mixer_blend_setup(struct mdp4_overlay_pipe *pipe); +struct mdp4_overlay_pipe *mdp4_overlay_stage_pipe(int mixer, int stage); +void mdp4_mixer_stage_up(struct mdp4_overlay_pipe *pipe); +void mdp4_mixer_stage_down(struct mdp4_overlay_pipe *pipe); +int mdp4_mixer_stage_can_run(struct mdp4_overlay_pipe *pipe); +void mdp4_overlayproc_cfg(struct mdp4_overlay_pipe *pipe); +void mdp4_mddi_overlay(struct msm_fb_data_type *mfd); +int mdp4_overlay_format2type(uint32 format); +int mdp4_overlay_format2pipe(struct mdp4_overlay_pipe *pipe); +int mdp4_overlay_get(struct fb_info *info, struct mdp_overlay *req); +int mdp4_overlay_set(struct fb_info *info, struct mdp_overlay *req); +int mdp4_overlay_unset(struct fb_info *info, int ndx); +int mdp4_overlay_play(struct fb_info *info, struct msmfb_overlay_data *req, + struct file **pp_src_file, struct file **pp_src_plane1_file, + struct file **pp_src_plane2_file); +struct mdp4_overlay_pipe *mdp4_overlay_pipe_alloc(int ptype, int mixer, + int req_share); +void mdp4_overlay_pipe_free(struct mdp4_overlay_pipe *pipe); +void mdp4_overlay_dmap_cfg(struct msm_fb_data_type *mfd, int lcdc); +void mdp4_overlay_dmap_xy(struct mdp4_overlay_pipe *pipe); +void mdp4_overlay_dmae_cfg(struct msm_fb_data_type *mfd, int atv); +void mdp4_overlay_dmae_xy(struct mdp4_overlay_pipe *pipe); +int mdp4_overlay_pipe_staged(int mixer); +void mdp4_lcdc_primary_vsyn(void); +void mdp4_overlay0_done_lcdc(void); +void mdp4_overlay0_done_mddi(struct mdp_dma_data *dma); +void mdp4_dma_s_done_mddi(void); +void mdp4_dma_p_done_mddi(void); +void mdp4_dma_p_done_dsi(struct mdp_dma_data *dma); +void mdp4_overlay1_done_dtv(void); +void mdp4_overlay1_done_atv(void); +void mdp4_primary_vsync_lcdc(void); +void mdp4_external_vsync_dtv(void); +void mdp4_mddi_overlay_restore(void); +void mdp4_overlay_lcdc_wait4vsync(struct msm_fb_data_type *mfd); +void mdp4_overlay_lcdc_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_mddi_overlay_dmas_restore(void); +void mdp4_mddi_dma_busy_wait(struct msm_fb_data_type *mfd); +void mdp4_mddi_overlay_kickoff(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_rgb_igc_lut_setup(int num); +void mdp4_vg_igc_lut_setup(int num); +void mdp4_mixer_gc_lut_setup(int mixer_num); +void mdp4_fetch_cfg(uint32 clk); +uint32 mdp4_rgb_igc_lut_cvt(uint32 ndx); +void mdp4_vg_qseed_init(int); +int mdp4_overlay_blt(struct fb_info *info, struct msmfb_overlay_blt *req); +int mdp4_overlay_blt_offset(struct fb_info *info, + struct msmfb_overlay_blt *req); + +int mdp4_dsi_overlay_blt_start(struct msm_fb_data_type *mfd); +int mdp4_dsi_overlay_blt_stop(struct msm_fb_data_type *mfd); + +#ifdef CONFIG_FB_MSM_MIPI_DSI +void mdp4_dsi_overlay_blt(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req); +int mdp4_dsi_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req); + +void mdp4_dsi_video_overlay_blt(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req); +int mdp4_dsi_video_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req); + +#ifdef CONFIG_FB_MSM_MDP40 +static inline void mdp3_dsi_cmd_dma_busy_wait(struct msm_fb_data_type *mfd) +{ + /* empty */ +} +#endif +#else +static inline void mdp4_dsi_overlay_blt( + struct msm_fb_data_type *mfd, struct msmfb_overlay_blt *req) +{ +} +static inline int mdp4_dsi_overlay_blt_offset( + struct msm_fb_data_type *mfd, struct msmfb_overlay_blt *req) +{ + return -ENODEV; +} +static inline void mdp4_dsi_video_overlay_blt( + struct msm_fb_data_type *mfd, struct msmfb_overlay_blt *req) +{ +} +static inline int mdp4_dsi_video_overlay_blt_offset( + struct msm_fb_data_type *mfd, struct msmfb_overlay_blt *req) +{ + return -ENODEV; +} +#endif + +void mdp4_lcdc_overlay_blt(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req); +int mdp4_lcdc_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req); + +int mdp4_mddi_overlay_blt_offset(int *off); +void mdp4_mddi_overlay_blt(ulong addr); +void mdp4_overlay_panel_mode(int mixer_num, uint32 mode); +int mdp4_overlay_mixer_play(int mixer_num); +uint32 mdp4_overlay_panel_list(void); +void mdp4_lcdc_overlay_kickoff(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); + +void mdp4_mddi_kickoff_video(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); + +void mdp4_mddi_read_ptr_intr(void); + +void mdp4_dsi_cmd_dma_busy_check(void); +void mdp4_dsi_cmd_dma_busy_wait(struct msm_fb_data_type *mfd); +void mdp4_dsi_blt_dmap_busy_wait(struct msm_fb_data_type *mfd); +void mdp4_dsi_cmd_kickoff_ui(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_dsi_cmd_kickoff_video(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_dsi_cmd_overlay_kickoff(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_dsi_cmd_overlay_restore(void); + +void mdp4_overlay_panel_3d(int mixer_num, uint32 panel_3d); +int mdp4_overlay_3d(struct fb_info *info, struct msmfb_overlay_3d *req); +void mdp4_dsi_cmd_3d(struct msm_fb_data_type *mfd, + struct msmfb_overlay_3d *r3d); + +void mdp_dmap_vsync_set(int enable); +int mdp_dmap_vsync_get(void); +void mdp_hw_cursor_done(void); +void mdp_hw_cursor_init(void); +int mdp4_mddi_overlay_cursor(struct fb_info *info, struct fb_cursor *cursor); +int mdp_ppp_blit(struct fb_info *info, struct mdp_blit_req *req); +void mdp4_overlay_resource_release(void); +void mdp4_overlay_dsi_video_wait4vsync(struct msm_fb_data_type *mfd); +void mdp4_overlay_dsi_video_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe); +void mdp4_primary_vsync_dsi_video(void); +uint32_t mdp4_ss_table_value(int8_t param, int8_t index); +void mdp4_overlay_status_write(enum mdp4_overlay_status type, bool val); +bool mdp4_overlay_status_read(enum mdp4_overlay_status type); +#endif /* MDP_H */ diff --git a/drivers/video/msm/mdp4_dtv.c b/drivers/video/msm/mdp4_dtv.c new file mode 100644 index 00000000000..b039da87be5 --- /dev/null +++ b/drivers/video/msm/mdp4_dtv.c @@ -0,0 +1,329 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb.h" +#include "mdp4.h" + +static int dtv_probe(struct platform_device *pdev); +static int dtv_remove(struct platform_device *pdev); + +static int dtv_off(struct platform_device *pdev); +static int dtv_on(struct platform_device *pdev); + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; + +static struct clk *tv_src_clk; +static struct clk *hdmi_clk; +static struct clk *mdp_tv_clk; + + +static int mdp4_dtv_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int mdp4_dtv_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops mdp4_dtv_dev_pm_ops = { + .runtime_suspend = mdp4_dtv_runtime_suspend, + .runtime_resume = mdp4_dtv_runtime_resume, +}; + +static struct platform_driver dtv_driver = { + .probe = dtv_probe, + .remove = dtv_remove, + .suspend = NULL, + .resume = NULL, + .shutdown = NULL, + .driver = { + .name = "dtv", + .pm = &mdp4_dtv_dev_pm_ops, + }, +}; + +static struct lcdc_platform_data *dtv_pdata; +#ifdef CONFIG_MSM_BUS_SCALING +static uint32_t dtv_bus_scale_handle; +#else +static struct clk *ebi1_clk; +#endif + +static int dtv_off(struct platform_device *pdev) +{ + int ret = 0; + + ret = panel_next_off(pdev); + + pr_info("%s\n", __func__); + + clk_disable(hdmi_clk); + if (mdp_tv_clk) + clk_disable(mdp_tv_clk); + + if (dtv_pdata && dtv_pdata->lcdc_power_save) + dtv_pdata->lcdc_power_save(0); + + if (dtv_pdata && dtv_pdata->lcdc_gpio_config) + ret = dtv_pdata->lcdc_gpio_config(0); +#ifdef CONFIG_MSM_BUS_SCALING + if (dtv_bus_scale_handle > 0) + msm_bus_scale_client_update_request(dtv_bus_scale_handle, + 0); +#else + if (ebi1_clk) + clk_disable(ebi1_clk); +#endif + mdp4_extn_disp = 0; + return ret; +} + +static int dtv_on(struct platform_device *pdev) +{ + int ret = 0; + struct msm_fb_data_type *mfd; + unsigned long panel_pixclock_freq , pm_qos_rate; + + mfd = platform_get_drvdata(pdev); + panel_pixclock_freq = mfd->fbi->var.pixclock; + +#ifdef CONFIG_MSM_NPA_SYSTEM_BUS + pm_qos_rate = MSM_AXI_FLOW_MDP_DTV_720P_2BPP; +#else + if (panel_pixclock_freq > 58000000) + /* pm_qos_rate should be in Khz */ + pm_qos_rate = panel_pixclock_freq / 1000 ; + else + pm_qos_rate = 58000; +#endif + mdp_set_core_clk(1); + mdp4_extn_disp = 1; +#ifdef CONFIG_MSM_BUS_SCALING + if (dtv_bus_scale_handle > 0) + msm_bus_scale_client_update_request(dtv_bus_scale_handle, + 1); +#else + if (ebi1_clk) { + clk_set_rate(ebi1_clk, pm_qos_rate * 1000); + clk_enable(ebi1_clk); + } +#endif + mfd = platform_get_drvdata(pdev); + + ret = clk_set_rate(tv_src_clk, mfd->fbi->var.pixclock); + if (ret) { + pr_info("%s: clk_set_rate(%d) failed\n", __func__, + mfd->fbi->var.pixclock); + if (mfd->fbi->var.pixclock == 27030000) + mfd->fbi->var.pixclock = 27000000; + ret = clk_set_rate(tv_src_clk, mfd->fbi->var.pixclock); + } + pr_info("%s: tv_src_clk=%dkHz, pm_qos_rate=%ldkHz, [%d]\n", __func__, + mfd->fbi->var.pixclock/1000, pm_qos_rate, ret); + + clk_enable(hdmi_clk); + clk_reset(hdmi_clk, CLK_RESET_ASSERT); + udelay(20); + clk_reset(hdmi_clk, CLK_RESET_DEASSERT); + + if (mdp_tv_clk) + clk_enable(mdp_tv_clk); + + if (dtv_pdata && dtv_pdata->lcdc_power_save) + dtv_pdata->lcdc_power_save(1); + if (dtv_pdata && dtv_pdata->lcdc_gpio_config) + ret = dtv_pdata->lcdc_gpio_config(1); + + ret = panel_next_on(pdev); + return ret; +} + +static int dtv_probe(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct fb_info *fbi; + struct platform_device *mdp_dev = NULL; + struct msm_fb_panel_data *pdata = NULL; + int rc; + + if (pdev->id == 0) { + dtv_pdata = pdev->dev.platform_data; + return 0; + } + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; + + mdp_dev = platform_device_alloc("mdp", pdev->id); + if (!mdp_dev) + return -ENOMEM; + + /* + * link to the latest pdev + */ + mfd->pdev = mdp_dev; + mfd->dest = DISPLAY_LCDC; + + /* + * alloc panel device data + */ + if (platform_device_add_data + (mdp_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + pr_err("dtv_probe: platform_device_add_data failed!\n"); + platform_device_put(mdp_dev); + return -ENOMEM; + } + /* + * data chain + */ + pdata = (struct msm_fb_panel_data *)mdp_dev->dev.platform_data; + pdata->on = dtv_on; + pdata->off = dtv_off; + pdata->next = pdev; + + /* + * get/set panel specific fb info + */ + mfd->panel_info = pdata->panel_info; + mfd->fb_imgType = MDP_RGB_565; + + fbi = mfd->fbi; + fbi->var.pixclock = mfd->panel_info.clk_rate; + fbi->var.left_margin = mfd->panel_info.lcdc.h_back_porch; + fbi->var.right_margin = mfd->panel_info.lcdc.h_front_porch; + fbi->var.upper_margin = mfd->panel_info.lcdc.v_back_porch; + fbi->var.lower_margin = mfd->panel_info.lcdc.v_front_porch; + fbi->var.hsync_len = mfd->panel_info.lcdc.h_pulse_width; + fbi->var.vsync_len = mfd->panel_info.lcdc.v_pulse_width; + +#ifdef CONFIG_MSM_BUS_SCALING + if (!dtv_bus_scale_handle && dtv_pdata && + dtv_pdata->bus_scale_table) { + dtv_bus_scale_handle = + msm_bus_scale_register_client( + dtv_pdata->bus_scale_table); + if (!dtv_bus_scale_handle) { + pr_err("%s not able to get bus scale\n", + __func__); + } + } +#else + ebi1_clk = clk_get(NULL, "ebi1_dtv_clk"); + if (IS_ERR(ebi1_clk)) { + ebi1_clk = NULL; + pr_warning("%s: Couldn't get ebi1 clock\n", __func__); + } +#endif + /* + * set driver data + */ + platform_set_drvdata(mdp_dev, mfd); + + /* + * register in mdp driver + */ + rc = platform_device_add(mdp_dev); + if (rc) + goto dtv_probe_err; + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + pdev_list[pdev_list_cnt++] = pdev; + return 0; + +dtv_probe_err: +#ifdef CONFIG_MSM_BUS_SCALING + if (dtv_pdata && dtv_pdata->bus_scale_table && + dtv_bus_scale_handle > 0) + msm_bus_scale_unregister_client(dtv_bus_scale_handle); +#endif + platform_device_put(mdp_dev); + return rc; +} + +static int dtv_remove(struct platform_device *pdev) +{ +#ifdef CONFIG_MSM_BUS_SCALING + if (dtv_pdata && dtv_pdata->bus_scale_table && + dtv_bus_scale_handle > 0) + msm_bus_scale_unregister_client(dtv_bus_scale_handle); +#else + if (ebi1_clk) + clk_put(ebi1_clk); +#endif + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int dtv_register_driver(void) +{ + return platform_driver_register(&dtv_driver); +} + +static int __init dtv_driver_init(void) +{ + tv_src_clk = clk_get(NULL, "tv_src_clk"); + if (IS_ERR(tv_src_clk)) { + pr_err("error: can't get tv_src_clk!\n"); + return IS_ERR(tv_src_clk); + } + + hdmi_clk = clk_get(NULL, "hdmi_clk"); + if (IS_ERR(hdmi_clk)) { + pr_err("error: can't get hdmi_clk!\n"); + return IS_ERR(hdmi_clk); + } + + mdp_tv_clk = clk_get(NULL, "mdp_tv_clk"); + if (IS_ERR(mdp_tv_clk)) + mdp_tv_clk = NULL; + + return dtv_register_driver(); +} + +module_init(dtv_driver_init); diff --git a/drivers/video/msm/mdp4_overlay.c b/drivers/video/msm/mdp4_overlay.c new file mode 100644 index 00000000000..cc3bd1b5a74 --- /dev/null +++ b/drivers/video/msm/mdp4_overlay.c @@ -0,0 +1,2290 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + +#define VERSION_KEY_MASK 0xFFFFFF00 + +struct mdp4_overlay_ctrl { + struct mdp4_pipe_desc ov_pipe[OVERLAY_PIPE_MAX];/* 4 */ + struct mdp4_overlay_pipe plist[MDP4_MAX_PIPE]; /* 4 + 2 */ + struct mdp4_overlay_pipe *stage[MDP4_MAX_MIXER][MDP4_MAX_STAGE + 2]; + uint32 panel_3d; + uint32 panel_mode; + uint32 mixer0_played; + uint32 mixer1_played; +} mdp4_overlay_db = { + .ov_pipe = { + { + .share = 0, /* RGB 1 */ + }, + { + .share = 0, /* RGB 2 */ + }, + { + .share = 1, /* VG 1 */ + }, + { + .share = 1, /* VG 2 */ + }, + }, + .plist = { + { + .pipe_type = OVERLAY_TYPE_RGB, + .pipe_num = OVERLAY_PIPE_RGB1, + .pipe_ndx = 1, + }, + { + .pipe_type = OVERLAY_TYPE_RGB, + .pipe_num = OVERLAY_PIPE_RGB2, + .pipe_ndx = 2, + }, + { + .pipe_type = OVERLAY_TYPE_RGB, /* shared */ + .pipe_num = OVERLAY_PIPE_VG1, + .pipe_ndx = 3, + }, + { + .pipe_type = OVERLAY_TYPE_RGB, /* shared */ + .pipe_num = OVERLAY_PIPE_VG2, + .pipe_ndx = 4, + }, + { + .pipe_type = OVERLAY_TYPE_VIDEO, /* shared */ + .pipe_num = OVERLAY_PIPE_VG1, + .pipe_ndx = 5, + }, + { + .pipe_type = OVERLAY_TYPE_VIDEO, /* shared */ + .pipe_num = OVERLAY_PIPE_VG2, + .pipe_ndx = 6, + }, + }, +}; + +static struct mdp4_overlay_ctrl *ctrl = &mdp4_overlay_db; +static uint32 perf_level; +static uint32 mdp4_del_res_rel; +/* static array with index 0 for unset status and 1 for set status */ +static bool overlay_status[MDP4_OVERLAY_TYPE_MAX]; + +void mdp4_overlay_status_write(enum mdp4_overlay_status type, bool val) +{ + overlay_status[type] = val; +} + +bool mdp4_overlay_status_read(enum mdp4_overlay_status type) +{ + return overlay_status[type]; +} + +int mdp4_overlay_mixer_play(int mixer_num) +{ + if (mixer_num == MDP4_MIXER1) + return ctrl->mixer1_played; + else + return ctrl->mixer0_played; +} + +void mdp4_overlay_panel_3d(int mixer_num, uint32 panel_3d) +{ + ctrl->panel_3d = panel_3d; +} + +void mdp4_overlay_panel_mode(int mixer_num, uint32 mode) +{ + ctrl->panel_mode |= mode; +} + +uint32 mdp4_overlay_panel_list(void) +{ + return ctrl->panel_mode; +} + +void mdp4_overlay_dmae_cfg(struct msm_fb_data_type *mfd, int atv) +{ + uint32 dmae_cfg_reg; + + if (atv) + dmae_cfg_reg = DMA_DEFLKR_EN; + else + dmae_cfg_reg = 0; + + if (mfd->fb_imgType == MDP_BGR_565) + dmae_cfg_reg |= DMA_PACK_PATTERN_BGR; + else + dmae_cfg_reg |= DMA_PACK_PATTERN_RGB; + + + if (mfd->panel_info.bpp == 18) { + dmae_cfg_reg |= DMA_DSTC0G_6BITS | /* 666 18BPP */ + DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + } else if (mfd->panel_info.bpp == 16) { + dmae_cfg_reg |= DMA_DSTC0G_6BITS | /* 565 16BPP */ + DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; + } else { + dmae_cfg_reg |= DMA_DSTC0G_8BITS | /* 888 16BPP */ + DMA_DSTC1B_8BITS | DMA_DSTC2R_8BITS; + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* dma2 config register */ + MDP_OUTP(MDP_BASE + 0xb0000, dmae_cfg_reg); + if (atv) { + MDP_OUTP(MDP_BASE + 0xb0070, 0xeb0010); + MDP_OUTP(MDP_BASE + 0xb0074, 0xf00010); + MDP_OUTP(MDP_BASE + 0xb0078, 0xf00010); + MDP_OUTP(MDP_BASE + 0xb3000, 0x80); + MDP_OUTP(MDP_BASE + 0xb3010, 0x1800040); + MDP_OUTP(MDP_BASE + 0xb3014, 0x1000080); + MDP_OUTP(MDP_BASE + 0xb4004, 0x67686970); + } else { + MDP_OUTP(MDP_BASE + 0xb0070, 0xff0000); + MDP_OUTP(MDP_BASE + 0xb0074, 0xff0000); + MDP_OUTP(MDP_BASE + 0xb0078, 0xff0000); + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void unfill_black_screen(void) +{ + uint32 temp_src_format; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + /* + * VG2 Constant Color + */ + temp_src_format = inpdw(MDP_BASE + 0x30050); + MDP_OUTP(MDP_BASE + 0x30050, temp_src_format&(~BIT(22))); + /* + * MDP_OVERLAY_REG_FLUSH + */ + MDP_OUTP(MDP_BASE + 0x18000, BIT(3)); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void fill_black_screen(void) +{ + /*Black color*/ + uint32 color = 0x00000000; + uint32 temp_src_format; + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + /* + * VG2 Constant Color + */ + MDP_OUTP(MDP_BASE + 0x31008, color); + /* + * MDP_VG2_SRC_FORMAT + */ + temp_src_format = inpdw(MDP_BASE + 0x30050); + MDP_OUTP(MDP_BASE + 0x30050, temp_src_format | BIT(22)); + /* + * MDP_OVERLAY_REG_FLUSH + */ + MDP_OUTP(MDP_BASE + 0x18000, BIT(3)); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_overlay_dmae_xy(struct mdp4_overlay_pipe *pipe) +{ + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* dma_p source */ + MDP_OUTP(MDP_BASE + 0xb0004, + (pipe->src_height << 16 | pipe->src_width)); + MDP_OUTP(MDP_BASE + 0xb0008, pipe->srcp0_addr); + MDP_OUTP(MDP_BASE + 0xb000c, pipe->srcp0_ystride); + + /* dma_p dest */ + MDP_OUTP(MDP_BASE + 0xb0010, (pipe->dst_y << 16 | pipe->dst_x)); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_overlay_dmap_cfg(struct msm_fb_data_type *mfd, int lcdc) +{ + uint32 dma2_cfg_reg; + + dma2_cfg_reg = DMA_DITHER_EN; +#ifdef BLT_RGB565 + /* RGB888 is 0 */ + dma2_cfg_reg |= DMA_BUF_FORMAT_RGB565; /* blt only */ +#endif + + if (mfd->fb_imgType == MDP_BGR_565) + dma2_cfg_reg |= DMA_PACK_PATTERN_BGR; + else + dma2_cfg_reg |= DMA_PACK_PATTERN_RGB; + + + if (mfd->panel_info.bpp == 18) { + dma2_cfg_reg |= DMA_DSTC0G_6BITS | /* 666 18BPP */ + DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + } else if (mfd->panel_info.bpp == 16) { + dma2_cfg_reg |= DMA_DSTC0G_6BITS | /* 565 16BPP */ + DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; + } else { + dma2_cfg_reg |= DMA_DSTC0G_8BITS | /* 888 16BPP */ + DMA_DSTC1B_8BITS | DMA_DSTC2R_8BITS; + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + +#ifndef CONFIG_FB_MSM_LCDC_CHIMEI_WXGA_PANEL + if (lcdc) + dma2_cfg_reg |= DMA_PACK_ALIGN_MSB; +#endif + + /* dma2 config register */ + MDP_OUTP(MDP_BASE + 0x90000, dma2_cfg_reg); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +/* + * mdp4_overlay_dmap_xy: called form baselayer only + */ +void mdp4_overlay_dmap_xy(struct mdp4_overlay_pipe *pipe) +{ + uint32 off, bpp; + + if (mdp_is_in_isr == FALSE) + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* dma_p source */ + MDP_OUTP(MDP_BASE + 0x90004, + (pipe->src_height << 16 | pipe->src_width)); + if (pipe->blt_addr) { +#ifdef BLT_RGB565 + bpp = 2; /* overlay ouput is RGB565 */ +#else + bpp = 3; /* overlay ouput is RGB888 */ +#endif + off = 0; + if (pipe->dmap_cnt & 0x01) + off = pipe->src_height * pipe->src_width * bpp; + MDP_OUTP(MDP_BASE + 0x90008, pipe->blt_addr + off); + /* RGB888, output of overlay blending */ + MDP_OUTP(MDP_BASE + 0x9000c, pipe->src_width * bpp); + } else { + MDP_OUTP(MDP_BASE + 0x90008, pipe->srcp0_addr); + MDP_OUTP(MDP_BASE + 0x9000c, pipe->srcp0_ystride); + } + + /* dma_p dest */ + MDP_OUTP(MDP_BASE + 0x90010, (pipe->dst_y << 16 | pipe->dst_x)); + + if (mdp_is_in_isr == FALSE) + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +#define MDP4_VG_PHASE_STEP_DEFAULT 0x20000000 +#define MDP4_VG_PHASE_STEP_SHIFT 29 + +static int mdp4_leading_0(uint32 num) +{ + uint32 bit = 0x80000000; + int i; + + for (i = 0; i < 32; i++) { + if (bit & num) + return i; + bit >>= 1; + } + + return i; +} + +static uint32 mdp4_scale_phase_step(int f_num, uint32 src, uint32 dst) +{ + uint32 val; + int n; + + n = mdp4_leading_0(src); + if (n > f_num) + n = f_num; + val = src << n; /* maximum to reduce lose of resolution */ + val /= dst; + if (n < f_num) { + n = f_num - n; + val <<= n; + } + + return val; +} + +static void mdp4_scale_setup(struct mdp4_overlay_pipe *pipe) +{ + int ptype; + + pipe->phasex_step = MDP4_VG_PHASE_STEP_DEFAULT; + pipe->phasey_step = MDP4_VG_PHASE_STEP_DEFAULT; + ptype = mdp4_overlay_format2type(pipe->src_format); + + if (pipe->dst_h && pipe->src_h != pipe->dst_h) { + if (pipe->dst_h > pipe->src_h * 8) /* too much */ + return; + pipe->op_mode |= MDP4_OP_SCALEY_EN; + + if (pipe->pipe_num >= OVERLAY_PIPE_VG1) { + if (pipe->dst_h <= (pipe->src_h / 4)) + pipe->op_mode |= MDP4_OP_SCALEY_MN_PHASE; + else + pipe->op_mode |= MDP4_OP_SCALEY_FIR; + } + + pipe->phasey_step = mdp4_scale_phase_step(29, + pipe->src_h, pipe->dst_h); + } + + if (pipe->dst_w && pipe->src_w != pipe->dst_w) { + if (pipe->dst_w > pipe->src_w * 8) /* too much */ + return; + pipe->op_mode |= MDP4_OP_SCALEX_EN; + + if (pipe->pipe_num >= OVERLAY_PIPE_VG1) { + if (pipe->dst_w <= (pipe->src_w / 4)) + pipe->op_mode |= MDP4_OP_SCALEX_MN_PHASE; + else + pipe->op_mode |= MDP4_OP_SCALEX_FIR; + } + + pipe->phasex_step = mdp4_scale_phase_step(29, + pipe->src_w, pipe->dst_w); + } +} + +void mdp4_overlay_rgb_setup(struct mdp4_overlay_pipe *pipe) +{ + char *rgb_base; + uint32 src_size, src_xy, dst_size, dst_xy; + uint32 format, pattern; + + rgb_base = MDP_BASE + MDP4_RGB_BASE; + rgb_base += (MDP4_RGB_OFF * pipe->pipe_num); + + src_size = ((pipe->src_h << 16) | pipe->src_w); + src_xy = ((pipe->src_y << 16) | pipe->src_x); + dst_size = ((pipe->dst_h << 16) | pipe->dst_w); + dst_xy = ((pipe->dst_y << 16) | pipe->dst_x); + + format = mdp4_overlay_format(pipe); + pattern = mdp4_overlay_unpack_pattern(pipe); + +#ifdef MDP4_IGC_LUT_ENABLE + pipe->op_mode |= MDP4_OP_IGC_LUT_EN; +#endif + + mdp4_scale_setup(pipe); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + outpdw(rgb_base + 0x0000, src_size); /* MDP_RGB_SRC_SIZE */ + outpdw(rgb_base + 0x0004, src_xy); /* MDP_RGB_SRC_XY */ + outpdw(rgb_base + 0x0008, dst_size); /* MDP_RGB_DST_SIZE */ + outpdw(rgb_base + 0x000c, dst_xy); /* MDP_RGB_DST_XY */ + + outpdw(rgb_base + 0x0010, pipe->srcp0_addr); + outpdw(rgb_base + 0x0040, pipe->srcp0_ystride); + + outpdw(rgb_base + 0x0050, format);/* MDP_RGB_SRC_FORMAT */ + outpdw(rgb_base + 0x0054, pattern);/* MDP_RGB_SRC_UNPACK_PATTERN */ + outpdw(rgb_base + 0x0058, pipe->op_mode);/* MDP_RGB_OP_MODE */ + outpdw(rgb_base + 0x005c, pipe->phasex_step); + outpdw(rgb_base + 0x0060, pipe->phasey_step); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + mdp4_stat.pipe[pipe->pipe_num]++; +} + +void mdp4_overlay_vg_setup(struct mdp4_overlay_pipe *pipe) +{ + char *vg_base; + uint32 frame_size, src_size, src_xy, dst_size, dst_xy; + uint32 format, pattern; + int pnum; + + pnum = pipe->pipe_num - OVERLAY_PIPE_VG1; /* start from 0 */ + vg_base = MDP_BASE + MDP4_VIDEO_BASE; + vg_base += (MDP4_VIDEO_OFF * pnum); + + frame_size = ((pipe->src_height << 16) | pipe->src_width); + src_size = ((pipe->src_h << 16) | pipe->src_w); + src_xy = ((pipe->src_y << 16) | pipe->src_x); + dst_size = ((pipe->dst_h << 16) | pipe->dst_w); + dst_xy = ((pipe->dst_y << 16) | pipe->dst_x); + + format = mdp4_overlay_format(pipe); + pattern = mdp4_overlay_unpack_pattern(pipe); + + /* not RGB use VG pipe, pure VG pipe */ + if (pipe->pipe_type != OVERLAY_TYPE_RGB) +#ifdef MDP4_IGC_LUT_ENABLE + pipe->op_mode |= (MDP4_OP_CSC_EN | MDP4_OP_SRC_DATA_YCBCR | + MDP4_OP_IGC_LUT_EN); +#else + pipe->op_mode |= (MDP4_OP_CSC_EN | MDP4_OP_SRC_DATA_YCBCR); +#endif + + mdp4_scale_setup(pipe); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + outpdw(vg_base + 0x0000, src_size); /* MDP_RGB_SRC_SIZE */ + outpdw(vg_base + 0x0004, src_xy); /* MDP_RGB_SRC_XY */ + outpdw(vg_base + 0x0008, dst_size); /* MDP_RGB_DST_SIZE */ + outpdw(vg_base + 0x000c, dst_xy); /* MDP_RGB_DST_XY */ + outpdw(vg_base + 0x0048, frame_size); /* TILE frame size */ + + /* luma component plane */ + outpdw(vg_base + 0x0010, pipe->srcp0_addr); + + /* chroma component plane or planar color 1 */ + outpdw(vg_base + 0x0014, pipe->srcp1_addr); + + /* planar color 2 */ + outpdw(vg_base + 0x0018, pipe->srcp2_addr); + + outpdw(vg_base + 0x0040, + pipe->srcp1_ystride << 16 | pipe->srcp0_ystride); + + outpdw(vg_base + 0x0044, + pipe->srcp3_ystride << 16 | pipe->srcp2_ystride); + + outpdw(vg_base + 0x0050, format); /* MDP_RGB_SRC_FORMAT */ + outpdw(vg_base + 0x0054, pattern); /* MDP_RGB_SRC_UNPACK_PATTERN */ + outpdw(vg_base + 0x0058, pipe->op_mode);/* MDP_RGB_OP_MODE */ + outpdw(vg_base + 0x005c, pipe->phasex_step); + outpdw(vg_base + 0x0060, pipe->phasey_step); + + if (pipe->op_mode & MDP4_OP_DITHER_EN) { + outpdw(vg_base + 0x0068, + pipe->r_bit << 4 | pipe->b_bit << 2 | pipe->g_bit); + } + + if (pipe->flags & MDP_SHARPENING) { + outpdw(vg_base + 0x8200, + mdp4_ss_table_value(pipe->req_data.dpp.sharp_strength, + 0)); + outpdw(vg_base + 0x8204, + mdp4_ss_table_value(pipe->req_data.dpp.sharp_strength, + 1)); + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + mdp4_stat.pipe[pipe->pipe_num]++; +} + +int mdp4_overlay_format2type(uint32 format) +{ + switch (format) { + case MDP_RGB_565: + case MDP_RGB_888: + case MDP_BGR_565: + case MDP_XRGB_8888: + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_BGRA_8888: + case MDP_RGBX_8888: + return OVERLAY_TYPE_RGB; + case MDP_YCRYCB_H2V1: + case MDP_Y_CRCB_H2V1: + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CBCR_H2V2: + case MDP_Y_CBCR_H2V2_TILE: + case MDP_Y_CRCB_H2V2_TILE: + case MDP_Y_CR_CB_H2V2: + case MDP_Y_CB_CR_H2V2: + case MDP_Y_CRCB_H1V1: + case MDP_Y_CBCR_H1V1: + return OVERLAY_TYPE_VIDEO; + default: + mdp4_stat.err_format++; + return -ERANGE; + } + +} + +#define C3_ALPHA 3 /* alpha */ +#define C2_R_Cr 2 /* R/Cr */ +#define C1_B_Cb 1 /* B/Cb */ +#define C0_G_Y 0 /* G/luma */ +#define YUV_444_MAX_WIDTH 1280 /* Max width for YUV 444*/ + +int mdp4_overlay_format2pipe(struct mdp4_overlay_pipe *pipe) +{ + switch (pipe->src_format) { + case MDP_RGB_565: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_INTERLEAVED; + pipe->a_bit = 0; + pipe->r_bit = 1; /* R, 5 bits */ + pipe->b_bit = 1; /* B, 5 bits */ + pipe->g_bit = 2; /* G, 6 bits */ + pipe->alpha_enable = 0; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 2; + pipe->element2 = C2_R_Cr; /* R */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->bpp = 2; /* 2 bpp */ + break; + case MDP_RGB_888: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_INTERLEAVED; + pipe->a_bit = 0; + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 0; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 2; + pipe->element2 = C2_R_Cr; /* R */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->bpp = 3; /* 3 bpp */ + break; + case MDP_BGR_565: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_INTERLEAVED; + pipe->a_bit = 0; + pipe->r_bit = 1; /* R, 5 bits */ + pipe->b_bit = 1; /* B, 5 bits */ + pipe->g_bit = 2; /* G, 6 bits */ + pipe->alpha_enable = 0; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 2; + pipe->element2 = C1_B_Cb; /* B */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C2_R_Cr; /* R */ + pipe->bpp = 2; /* 2 bpp */ + break; + case MDP_XRGB_8888: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_INTERLEAVED; + pipe->a_bit = 3; /* alpha, 4 bits */ + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 0; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 3; + pipe->element3 = C3_ALPHA; /* alpha */ + pipe->element2 = C2_R_Cr; /* R */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->bpp = 4; /* 4 bpp */ + break; + case MDP_ARGB_8888: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_INTERLEAVED; + pipe->a_bit = 3; /* alpha, 4 bits */ + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 1; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 3; + pipe->element3 = C3_ALPHA; /* alpha */ + pipe->element2 = C2_R_Cr; /* R */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->bpp = 4; /* 4 bpp */ + break; + case MDP_RGBA_8888: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_INTERLEAVED; + pipe->a_bit = 3; /* alpha, 4 bits */ + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 1; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 3; + pipe->element3 = C3_ALPHA; /* alpha */ + pipe->element2 = C1_B_Cb; /* B */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C2_R_Cr; /* R */ + pipe->bpp = 4; /* 4 bpp */ + break; + case MDP_RGBX_8888: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_INTERLEAVED; + pipe->a_bit = 3; + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 0; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 3; + pipe->element3 = C3_ALPHA; /* alpha */ + pipe->element2 = C1_B_Cb; /* B */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C2_R_Cr; /* R */ + pipe->bpp = 4; /* 4 bpp */ + break; + case MDP_BGRA_8888: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_INTERLEAVED; + pipe->a_bit = 3; /* alpha, 4 bits */ + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 1; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 3; + pipe->element3 = C3_ALPHA; /* alpha */ + pipe->element2 = C2_R_Cr; /* R */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->bpp = 4; /* 4 bpp */ + break; + case MDP_YCRYCB_H2V1: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_INTERLEAVED; + pipe->a_bit = 0; /* alpha, 4 bits */ + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 0; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 3; + pipe->element3 = C0_G_Y; /* G */ + pipe->element2 = C2_R_Cr; /* R */ + pipe->element1 = C0_G_Y; /* G */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->bpp = 2; /* 2 bpp */ + pipe->chroma_sample = MDP4_CHROMA_H2V1; + break; + case MDP_Y_CRCB_H2V1: + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H1V1: + case MDP_Y_CBCR_H1V1: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_PSEUDO_PLANAR; + pipe->a_bit = 0; + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 0; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 1; /* 2 */ + pipe->element3 = C0_G_Y; /* not used */ + pipe->element2 = C0_G_Y; /* not used */ + if (pipe->src_format == MDP_Y_CRCB_H2V1) { + pipe->element1 = C2_R_Cr; /* R */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->chroma_sample = MDP4_CHROMA_H2V1; + } else if (pipe->src_format == MDP_Y_CRCB_H1V1) { + pipe->element1 = C2_R_Cr; /* R */ + pipe->element0 = C1_B_Cb; /* B */ + if (pipe->src_width > YUV_444_MAX_WIDTH) + pipe->chroma_sample = MDP4_CHROMA_H1V2; + else + pipe->chroma_sample = MDP4_CHROMA_RGB; + } else if (pipe->src_format == MDP_Y_CBCR_H2V1) { + pipe->element1 = C1_B_Cb; /* B */ + pipe->element0 = C2_R_Cr; /* R */ + pipe->chroma_sample = MDP4_CHROMA_H2V1; + } else if (pipe->src_format == MDP_Y_CBCR_H1V1) { + pipe->element1 = C1_B_Cb; /* B */ + pipe->element0 = C2_R_Cr; /* R */ + if (pipe->src_width > YUV_444_MAX_WIDTH) + pipe->chroma_sample = MDP4_CHROMA_H1V2; + else + pipe->chroma_sample = MDP4_CHROMA_RGB; + } else if (pipe->src_format == MDP_Y_CRCB_H2V2) { + pipe->element1 = C2_R_Cr; /* R */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->chroma_sample = MDP4_CHROMA_420; + } else if (pipe->src_format == MDP_Y_CBCR_H2V2) { + pipe->element1 = C1_B_Cb; /* B */ + pipe->element0 = C2_R_Cr; /* R */ + pipe->chroma_sample = MDP4_CHROMA_420; + } + pipe->bpp = 2; /* 2 bpp */ + break; + case MDP_Y_CBCR_H2V2_TILE: + case MDP_Y_CRCB_H2V2_TILE: + pipe->frame_format = MDP4_FRAME_FORMAT_VIDEO_SUPERTILE; + pipe->fetch_plane = OVERLAY_PLANE_PSEUDO_PLANAR; + pipe->a_bit = 0; + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 0; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 1; /* 2 */ + pipe->element3 = C0_G_Y; /* not used */ + pipe->element2 = C0_G_Y; /* not used */ + if (pipe->src_format == MDP_Y_CRCB_H2V2_TILE) { + pipe->element1 = C2_R_Cr; /* R */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->chroma_sample = MDP4_CHROMA_420; + } else if (pipe->src_format == MDP_Y_CBCR_H2V2_TILE) { + pipe->element1 = C1_B_Cb; /* B */ + pipe->element0 = C2_R_Cr; /* R */ + pipe->chroma_sample = MDP4_CHROMA_420; + } + pipe->bpp = 2; /* 2 bpp */ + break; + case MDP_Y_CR_CB_H2V2: + case MDP_Y_CB_CR_H2V2: + pipe->frame_format = MDP4_FRAME_FORMAT_LINEAR; + pipe->fetch_plane = OVERLAY_PLANE_PLANAR; + pipe->a_bit = 0; + pipe->r_bit = 3; /* R, 8 bits */ + pipe->b_bit = 3; /* B, 8 bits */ + pipe->g_bit = 3; /* G, 8 bits */ + pipe->alpha_enable = 0; + pipe->unpack_tight = 1; + pipe->unpack_align_msb = 0; + pipe->unpack_count = 1; /* 2 */ + pipe->element3 = C0_G_Y; /* not used */ + pipe->element2 = C0_G_Y; /* not used */ + if (pipe->src_format == MDP_Y_CR_CB_H2V2) { + pipe->element1 = C2_R_Cr; /* R */ + pipe->element0 = C1_B_Cb; /* B */ + pipe->chroma_sample = MDP4_CHROMA_420; + } else if (pipe->src_format == MDP_Y_CB_CR_H2V2) { + pipe->element1 = C1_B_Cb; /* B */ + pipe->element0 = C2_R_Cr; /* R */ + pipe->chroma_sample = MDP4_CHROMA_420; + } + pipe->bpp = 2; /* 2 bpp */ + break; + default: + /* not likely */ + mdp4_stat.err_format++; + return -ERANGE; + } + + return 0; +} + +/* + * color_key_convert: output with 12 bits color key + */ +static uint32 color_key_convert(int start, int num, uint32 color) +{ + uint32 data; + + data = (color >> start) & ((1 << num) - 1); + + /* convert to 8 bits */ + if (num == 5) + data = ((data << 3) | (data >> 2)); + else if (num == 6) + data = ((data << 2) | (data >> 4)); + + /* convert 8 bits to 12 bits */ + data = (data << 4) | (data >> 4); + + return data; +} + +void transp_color_key(int format, uint32 transp, + uint32 *c0, uint32 *c1, uint32 *c2) +{ + int b_start, g_start, r_start; + int b_num, g_num, r_num; + + switch (format) { + case MDP_RGB_565: + b_start = 0; + g_start = 5; + r_start = 11; + r_num = 5; + g_num = 6; + b_num = 5; + break; + case MDP_RGB_888: + case MDP_XRGB_8888: + case MDP_ARGB_8888: + case MDP_BGRA_8888: + b_start = 0; + g_start = 8; + r_start = 16; + r_num = 8; + g_num = 8; + b_num = 8; + break; + case MDP_RGBA_8888: + case MDP_RGBX_8888: + b_start = 16; + g_start = 8; + r_start = 0; + r_num = 8; + g_num = 8; + b_num = 8; + break; + case MDP_BGR_565: + b_start = 11; + g_start = 5; + r_start = 0; + r_num = 5; + g_num = 6; + b_num = 5; + break; + case MDP_Y_CB_CR_H2V2: + case MDP_Y_CBCR_H2V2: + case MDP_Y_CBCR_H2V1: + b_start = 8; + g_start = 16; + r_start = 0; + r_num = 8; + g_num = 8; + b_num = 8; + break; + case MDP_Y_CR_CB_H2V2: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CRCB_H2V1: + case MDP_Y_CRCB_H1V1: + case MDP_Y_CBCR_H1V1: + b_start = 0; + g_start = 16; + r_start = 8; + r_num = 8; + g_num = 8; + b_num = 8; + break; + default: + b_start = 0; + g_start = 8; + r_start = 16; + r_num = 8; + g_num = 8; + b_num = 8; + break; + } + + *c0 = color_key_convert(g_start, g_num, transp); + *c1 = color_key_convert(b_start, b_num, transp); + *c2 = color_key_convert(r_start, r_num, transp); +} + +uint32 mdp4_overlay_format(struct mdp4_overlay_pipe *pipe) +{ + uint32 format; + + format = 0; + + if (pipe->solid_fill) + format |= MDP4_FORMAT_SOLID_FILL; + + if (pipe->unpack_align_msb) + format |= MDP4_FORMAT_UNPACK_ALIGN_MSB; + + if (pipe->unpack_tight) + format |= MDP4_FORMAT_UNPACK_TIGHT; + + if (pipe->alpha_enable) + format |= MDP4_FORMAT_ALPHA_ENABLE; + + if (pipe->flags & MDP_SOURCE_ROTATED_90) + format |= MDP4_FORMAT_90_ROTATED; + format |= (pipe->unpack_count << 13); + format |= ((pipe->bpp - 1) << 9); + format |= (pipe->a_bit << 6); + format |= (pipe->r_bit << 4); + format |= (pipe->b_bit << 2); + format |= pipe->g_bit; + + format |= (pipe->frame_format << 29); + + if (pipe->fetch_plane == OVERLAY_PLANE_PSEUDO_PLANAR || + pipe->fetch_plane == OVERLAY_PLANE_PLANAR) { + /* video/graphic */ + format |= (pipe->fetch_plane << 19); + format |= (pipe->chroma_site << 28); + format |= (pipe->chroma_sample << 26); + } + + return format; +} + +uint32 mdp4_overlay_unpack_pattern(struct mdp4_overlay_pipe *pipe) +{ + return (pipe->element3 << 24) | (pipe->element2 << 16) | + (pipe->element1 << 8) | pipe->element0; +} + +/* + * mdp4_overlayproc_cfg: only be called from base layer + */ +void mdp4_overlayproc_cfg(struct mdp4_overlay_pipe *pipe) +{ + uint32 data, intf; + char *overlay_base; + + intf = 0; + if (pipe->mixer_num == MDP4_MIXER1) { + overlay_base = MDP_BASE + MDP4_OVERLAYPROC1_BASE;/* 0x18000 */ + intf = inpdw(MDP_BASE + 0x0038); /* MDP_DISP_INTF_SEL */ + intf >>= 4; + intf &= 0x03; + } else + overlay_base = MDP_BASE + MDP4_OVERLAYPROC0_BASE;/* 0x10000 */ + + if (mdp_is_in_isr == FALSE) + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* + * BLT only siupport at primary display + */ + if (pipe->mixer_num == MDP4_MIXER0 && pipe->blt_addr) { + int off, bpp; +#ifdef BLT_RGB565 + bpp = 2; /* overlay ouput is RGB565 */ +#else + bpp = 3; /* overlay ouput is RGB888 */ +#endif + data = pipe->src_height; + data <<= 16; + data |= pipe->src_width; + outpdw(overlay_base + 0x0008, data); /* ROI, height + width */ + if (ctrl->panel_mode & MDP4_PANEL_LCDC || + ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) { + outpdw(overlay_base + 0x000c, pipe->blt_addr); + outpdw(overlay_base + 0x0010, pipe->src_width * bpp); + off = pipe->src_height * pipe->src_width * bpp; + outpdw(overlay_base + 0x001c, pipe->blt_addr + off); + /* LCDC - FRAME BUFFER + vsync rate */ + outpdw(overlay_base + 0x0004, 0x02); + } else { /* MDDI */ + off = 0; + if (pipe->ov_cnt & 0x01) + off = pipe->src_height * pipe->src_width * bpp; + + outpdw(overlay_base + 0x000c, pipe->blt_addr + off); + /* overlay ouput is RGB888 */ + outpdw(overlay_base + 0x0010, pipe->src_width * bpp); + outpdw(overlay_base + 0x001c, pipe->blt_addr + off); + /* MDDI - BLT + on demand */ + outpdw(overlay_base + 0x0004, 0x08); + } +#ifdef BLT_RGB565 + outpdw(overlay_base + 0x0014, 0x1); /* RGB565 */ +#else + outpdw(overlay_base + 0x0014, 0x0); /* RGB888 */ +#endif + } else { + data = pipe->src_height; + data <<= 16; + data |= pipe->src_width; + outpdw(overlay_base + 0x0008, data); /* ROI, height + width */ + outpdw(overlay_base + 0x000c, pipe->srcp0_addr); + outpdw(overlay_base + 0x0010, pipe->srcp0_ystride); + outpdw(overlay_base + 0x0004, 0x01); /* directout */ + } + + if (pipe->mixer_num == MDP4_MIXER1) { + if (intf == TV_INTF) { + outpdw(overlay_base + 0x0014, 0x02); /* yuv422 */ + /* overlay1 CSC config */ + outpdw(overlay_base + 0x0200, 0x05); /* rgb->yuv */ + } + } + +#ifdef MDP4_IGC_LUT_ENABLE + outpdw(overlay_base + 0x0014, 0x4); /* GC_LUT_EN, 888 */ +#endif + + if (mdp_is_in_isr == FALSE) + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +int mdp4_overlay_pipe_staged(int mixer) +{ + uint32 data, mask, i; + int p1, p2; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + data = inpdw(MDP_BASE + 0x10100); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + p1 = 0; + p2 = 0; + for (i = 0; i < 8; i++) { + mask = data & 0x0f; + if (mask) { + if (mask <= 4) + p1++; + else + p2++; + } + data >>= 4; + } + + if (mixer) + return p2; + else + return p1; +} + +void mdp4_mixer_stage_up(struct mdp4_overlay_pipe *pipe) +{ + uint32 data, mask, snum, stage, mixer, pnum; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + stage = pipe->mixer_stage; + mixer = pipe->mixer_num; + pnum = pipe->pipe_num; + + /* MDP_LAYERMIXER_IN_CFG, shard by both mixer 0 and 1 */ + data = inpdw(MDP_BASE + 0x10100); + + if (mixer == MDP4_MIXER1) + stage += 8; + + if (pipe->pipe_num >= OVERLAY_PIPE_VG1) {/* VG1 and VG2 */ + pnum -= OVERLAY_PIPE_VG1; /* start from 0 */ + snum = 0; + snum += (4 * pnum); + } else { + snum = 8; + snum += (4 * pnum); /* RGB1 and RGB2 */ + } + + mask = 0x0f; + mask <<= snum; + stage <<= snum; + data &= ~mask; /* clear old bits */ + + data |= stage; + + outpdw(MDP_BASE + 0x10100, data); /* MDP_LAYERMIXER_IN_CFG */ + + data = inpdw(MDP_BASE + 0x10100); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + ctrl->stage[pipe->mixer_num][pipe->mixer_stage] = pipe; /* keep it */ +} + +void mdp4_mixer_stage_down(struct mdp4_overlay_pipe *pipe) +{ + uint32 data, mask, snum, stage, mixer, pnum; + + stage = pipe->mixer_stage; + mixer = pipe->mixer_num; + pnum = pipe->pipe_num; + + if (pipe != ctrl->stage[mixer][stage]) /* not runing */ + return; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* MDP_LAYERMIXER_IN_CFG, shard by both mixer 0 and 1 */ + data = inpdw(MDP_BASE + 0x10100); + + if (mixer == MDP4_MIXER1) + stage += 8; + + if (pipe->pipe_num >= OVERLAY_PIPE_VG1) {/* VG1 and VG2 */ + pnum -= OVERLAY_PIPE_VG1; /* start from 0 */ + snum = 0; + snum += (4 * pnum); + } else { + snum = 8; + snum += (4 * pnum); /* RGB1 and RGB2 */ + } + + mask = 0x0f; + mask <<= snum; + data &= ~mask; /* clear old bits */ + + outpdw(MDP_BASE + 0x10100, data); /* MDP_LAYERMIXER_IN_CFG */ + + data = inpdw(MDP_BASE + 0x10100); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + ctrl->stage[pipe->mixer_num][pipe->mixer_stage] = NULL; /* clear it */ +} + +void mdp4_mixer_blend_setup(struct mdp4_overlay_pipe *pipe) +{ + struct mdp4_overlay_pipe *bg_pipe; + unsigned char *overlay_base, *rgb_base; + uint32 c0, c1, c2, blend_op, constant_color = 0, rgb_src_format; + int off; + + if (pipe->mixer_num) /* mixer number, /dev/fb0, /dev/fb1 */ + overlay_base = MDP_BASE + MDP4_OVERLAYPROC1_BASE;/* 0x18000 */ + else + overlay_base = MDP_BASE + MDP4_OVERLAYPROC0_BASE;/* 0x10000 */ + + /* stage 0 to stage 2 */ + off = 0x20 * (pipe->mixer_stage - MDP4_MIXER_STAGE0); + + bg_pipe = mdp4_overlay_stage_pipe(pipe->mixer_num, + MDP4_MIXER_STAGE_BASE); + if (bg_pipe == NULL) { + pr_err("%s: Error: no bg_pipe\n", __func__); + return; + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + blend_op = 0; + + if (pipe->is_fg) { + blend_op |= (MDP4_BLEND_FG_ALPHA_FG_CONST | + MDP4_BLEND_BG_ALPHA_BG_CONST); + outpdw(overlay_base + off + 0x108, pipe->alpha); + outpdw(overlay_base + off + 0x10c, 0xff - pipe->alpha); + if (pipe->alpha == 0xff) { + rgb_base = MDP_BASE + MDP4_RGB_BASE; + rgb_base += MDP4_RGB_OFF * bg_pipe->pipe_num; + rgb_src_format = inpdw(rgb_base + 0x50); + rgb_src_format |= MDP4_FORMAT_SOLID_FILL; + outpdw(rgb_base + 0x50, rgb_src_format); + outpdw(rgb_base + 0x1008, constant_color); + } + } else { + if (bg_pipe->alpha_enable && pipe->alpha_enable) { + /* both pipe have alpha */ + blend_op |= (MDP4_BLEND_FG_ALPHA_BG_PIXEL | + MDP4_BLEND_FG_INV_ALPHA | + MDP4_BLEND_BG_ALPHA_BG_PIXEL); + } else if (bg_pipe->alpha_enable && pipe->alpha_enable == 0) { + /* no alpha on both pipe */ + blend_op = (MDP4_BLEND_BG_ALPHA_BG_PIXEL | + MDP4_BLEND_FG_ALPHA_BG_PIXEL | + MDP4_BLEND_FG_INV_ALPHA); + } + } + + + if (pipe->transp != MDP_TRANSP_NOP) { + if (pipe->is_fg) { + transp_color_key(pipe->src_format, pipe->transp, + &c0, &c1, &c2); + /* Fg blocked */ + blend_op |= MDP4_BLEND_FG_TRANSP_EN; + /* lower limit */ + outpdw(overlay_base + off + 0x110, + (c1 << 16 | c0));/* low */ + outpdw(overlay_base + off + 0x114, c2);/* low */ + /* upper limit */ + outpdw(overlay_base + off + 0x118, + (c1 << 16 | c0)); + outpdw(overlay_base + off + 0x11c, c2); + } else { + transp_color_key(bg_pipe->src_format, + pipe->transp, &c0, &c1, &c2); + /* bg blocked */ + blend_op |= MDP4_BLEND_BG_TRANSP_EN; + /* lower limit */ + outpdw(overlay_base + 0x180, + (c1 << 16 | c0));/* low */ + outpdw(overlay_base + 0x184, c2);/* low */ + /* upper limit */ + outpdw(overlay_base + 0x188, + (c1 << 16 | c0));/* high */ + outpdw(overlay_base + 0x18c, c2);/* high */ + } + } + + outpdw(overlay_base + off + 0x104, blend_op); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_overlay_reg_flush(struct mdp4_overlay_pipe *pipe, int all) +{ + struct mdp4_overlay_pipe *bg_pipe; + uint32 bits = 0; + + if (pipe->mixer_num == MDP4_MIXER1) + bits |= 0x02; + else + bits |= 0x01; + + if (all) { + if (pipe->pipe_num <= OVERLAY_PIPE_RGB2) { + if (pipe->pipe_num == OVERLAY_PIPE_RGB2) + bits |= 0x20; + else + bits |= 0x10; + } else { + if (pipe->is_fg && pipe->alpha == 0xFF) { + bg_pipe = mdp4_overlay_stage_pipe( + pipe->mixer_num, + MDP4_MIXER_STAGE_BASE); + if (bg_pipe->pipe_num <= OVERLAY_PIPE_RGB2) { + if (bg_pipe->pipe_num == + OVERLAY_PIPE_RGB2) + bits |= 0x20; + else + bits |= 0x10; + } + } + if (pipe->pipe_num == OVERLAY_PIPE_VG2) + bits |= 0x08; + else + bits |= 0x04; + } + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + outpdw(MDP_BASE + 0x18000, bits); /* MDP_OVERLAY_REG_FLUSH */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +struct mdp4_overlay_pipe *mdp4_overlay_stage_pipe(int mixer, int stage) +{ + return ctrl->stage[mixer][stage]; +} + +struct mdp4_overlay_pipe *mdp4_overlay_ndx2pipe(int ndx) +{ + struct mdp4_overlay_pipe *pipe; + + if (ndx <= 0 || ndx > MDP4_MAX_PIPE) + return NULL; + + pipe = &ctrl->plist[ndx - 1]; /* ndx start from 1 */ + + if (pipe->pipe_used == 0) + return NULL; + + return pipe; +} + +struct mdp4_overlay_pipe *mdp4_overlay_pipe_alloc( + int ptype, int mixer, int req_share) +{ + int i, j, ndx, found; + struct mdp4_overlay_pipe *pipe, *opipe; + struct mdp4_pipe_desc *pd; + + found = 0; + pipe = &ctrl->plist[0]; + + for (i = 0; i < MDP4_MAX_PIPE; i++) { + if (pipe->pipe_type == ptype && pipe->pipe_used == 0) { + pd = &ctrl->ov_pipe[pipe->pipe_num]; + if (pd->share) { /* pipe can be shared */ + if (pd->ref_cnt == 0) { + /* not yet been used */ + found++; + break; + } + /* pipe occupied already */ + if (req_share && pd->ref_cnt < MDP4_MAX_SHARE) { + for (j = 0; j < MDP4_MAX_SHARE; j++) { + ndx = pd->ndx_list[j]; + if (ndx != 0) + break; + } + /* ndx satrt from 1 */ + opipe = &ctrl->plist[ndx - 1]; + /* + * occupied pipe willing to share and + * same mixer + */ + if (opipe->pipe_share && + opipe->mixer_num == mixer) { + found++; + break; + } + } + } else { /* not a shared pipe */ + if (req_share == 0 && pd->ref_cnt == 0) { + found++; + break; + } + } + } + pipe++; + } + + if (found) { + init_completion(&pipe->comp); + init_completion(&pipe->dmas_comp); + pr_info("%s: pipe=%x ndx=%d num=%d share=%d cnt=%d\n", + __func__, (int)pipe, pipe->pipe_ndx, pipe->pipe_num, + pd->share, pd->ref_cnt); + return pipe; + } + + pr_debug("%s: ptype=%d mixer=%d req_share=%d FAILED\n", + __func__, ptype, mixer, req_share); + + return NULL; +} + + +void mdp4_overlay_pipe_free(struct mdp4_overlay_pipe *pipe) +{ + int i; + uint32 ptype, num, ndx; + struct mdp4_pipe_desc *pd; + + pr_debug("%s: pipe=%x ndx=%d\n", __func__, + (int)pipe, pipe->pipe_ndx); + pd = &ctrl->ov_pipe[pipe->pipe_num]; + if (pd->ref_cnt) { + pd->ref_cnt--; + for (i = 0; i < MDP4_MAX_SHARE; i++) { + if (pd->ndx_list[i] == pipe->pipe_ndx) { + pd->ndx_list[i] = 0; + break; + } + } + } + + pd->player = NULL; + + ptype = pipe->pipe_type; + num = pipe->pipe_num; + ndx = pipe->pipe_ndx; + + memset(pipe, 0, sizeof(*pipe)); + + pipe->pipe_type = ptype; + pipe->pipe_num = num; + pipe->pipe_ndx = ndx; +} + +int mdp4_overlay_req_check(uint32 id, uint32 z_order, uint32 mixer) +{ + struct mdp4_overlay_pipe *pipe; + + pipe = ctrl->stage[mixer][z_order]; + + if (pipe == NULL) + return 0; + + if (pipe->pipe_ndx == id) /* same req, recycle */ + return 0; + + if (id == MSMFB_NEW_REQUEST) { /* new request */ + if (pipe->pipe_num >= OVERLAY_PIPE_VG1) /* share pipe */ + return 0; + } + + return -EPERM; +} + +static int mdp4_overlay_validate_downscale(struct mdp_overlay *req, + struct msm_fb_data_type *mfd, uint32 perf_level, uint32 pclk_rate) +{ + __u32 panel_clk_khz, mdp_clk_khz; + __u32 num_hsync_pix_clks, mdp_clks_per_hsync, src_wh; + __u32 hsync_period_ps, mdp_period_ps, total_hsync_period_ps; + unsigned long fill_rate_y_dir, fill_rate_x_dir; + unsigned long fillratex100, mdp_pixels_produced; + unsigned long mdp_clk_hz; + + pr_debug("%s: LCDC Mode Downscale validation with MDP Core" + " Clk rate\n", __func__); + pr_debug("src_w %u, src_h %u, dst_w %u, dst_h %u\n", + req->src_rect.w, req->src_rect.h, req->dst_rect.w, + req->dst_rect.h); + + + panel_clk_khz = pclk_rate/1000; + mdp_clk_hz = mdp_perf_level2clk_rate(perf_level); + + if (!mdp_clk_hz) { + pr_debug("mdp_perf_level2clk_rate returned 0," + "Downscale Validation incomplete\n"); + return 0; + } + + mdp_clk_khz = mdp_clk_hz/1000; + + num_hsync_pix_clks = mfd->panel_info.lcdc.h_back_porch + + mfd->panel_info.lcdc.h_front_porch + + mfd->panel_info.lcdc.h_pulse_width + + mfd->panel_info.xres; + + hsync_period_ps = 1000000000/panel_clk_khz; + mdp_period_ps = 1000000000/mdp_clk_khz; + + total_hsync_period_ps = num_hsync_pix_clks * hsync_period_ps; + mdp_clks_per_hsync = total_hsync_period_ps/mdp_period_ps; + + pr_debug("hsync_period_ps %u, mdp_period_ps %u," + "total_hsync_period_ps %u\n", hsync_period_ps, + mdp_period_ps, total_hsync_period_ps); + + src_wh = req->src_rect.w * req->src_rect.h; + if (src_wh % req->dst_rect.h) + fill_rate_y_dir = (src_wh / req->dst_rect.h) + 1; + else + fill_rate_y_dir = (src_wh / req->dst_rect.h); + + fill_rate_x_dir = (mfd->panel_info.xres - req->dst_rect.w) + + req->src_rect.w; + + if (fill_rate_y_dir >= fill_rate_x_dir) + fillratex100 = 100 * fill_rate_y_dir / mfd->panel_info.xres; + else + fillratex100 = 100 * fill_rate_x_dir / mfd->panel_info.xres; + + pr_debug("mdp_clks_per_hsync %u, fill_rate_y_dir %lu," + "fill_rate_x_dir %lu\n", mdp_clks_per_hsync, + fill_rate_y_dir, fill_rate_x_dir); + + mdp_pixels_produced = 100 * mdp_clks_per_hsync/fillratex100; + pr_debug("fillratex100 %lu, mdp_pixels_produced %lu\n", + fillratex100, mdp_pixels_produced); + if (mdp_pixels_produced <= mfd->panel_info.xres) { + pr_err("%s(): LCDC underflow detected during downscale\n", + __func__); + return -ERANGE; + } + + return 0; +} + +static int mdp4_overlay_req2pipe(struct mdp_overlay *req, int mixer, + struct mdp4_overlay_pipe **ppipe, + struct msm_fb_data_type *mfd) +{ + struct mdp4_overlay_pipe *pipe; + struct mdp4_pipe_desc *pd; + int ret, ptype, req_share; + int j; + + if (mfd == NULL) { + pr_err("%s: mfd == NULL, -ENODEV\n", __func__); + return -ENODEV; + } + + if (mixer >= MDP4_MAX_MIXER) { + pr_err("%s: mixer out of range!\n", __func__); + mdp4_stat.err_mixer++; + return -ERANGE; + } + + if (req->z_order < 0 || req->z_order > 2) { + pr_err("%s: z_order=%d out of range!\n", __func__, + req->z_order); + mdp4_stat.err_zorder++; + return -ERANGE; + } + + if (req->src_rect.h == 0 || req->src_rect.w == 0) { + pr_err("%s: src img of zero size!\n", __func__); + mdp4_stat.err_size++; + return -EINVAL; + } + + + if (req->dst_rect.h > (req->src_rect.h * 8)) { /* too much */ + mdp4_stat.err_scale++; + pr_err("%s: scale up, too much (h)!\n", __func__); + return -ERANGE; + } + + if (req->src_rect.h > (req->dst_rect.h * 8)) { /* too little */ + mdp4_stat.err_scale++; + pr_err("%s: scale down, too little (h)!\n", __func__); + return -ERANGE; + } + + if (req->dst_rect.w > (req->src_rect.w * 8)) { /* too much */ + mdp4_stat.err_scale++; + pr_err("%s: scale up, too much (w)!\n", __func__); + return -ERANGE; + } + + if (req->src_rect.w > (req->dst_rect.w * 8)) { /* too little */ + mdp4_stat.err_scale++; + pr_err("%s: scale down, too little (w)!\n", __func__); + return -ERANGE; + } + + if (mdp_hw_revision == MDP4_REVISION_V1) { + /* non integer down saceling ratio smaller than 1/4 + * is not supportted + */ + if (req->src_rect.h > (req->dst_rect.h * 4)) { + if (req->src_rect.h % req->dst_rect.h) { + mdp4_stat.err_scale++; + pr_err("%s: need integer (h)!\n", __func__); + return -ERANGE; + } + } + + if (req->src_rect.w > (req->dst_rect.w * 4)) { + if (req->src_rect.w % req->dst_rect.w) { + mdp4_stat.err_scale++; + pr_err("%s: need integer (w)!\n", __func__); + return -ERANGE; + } + } + } + + if (((req->src_rect.x + req->src_rect.w) > req->src.width) || + ((req->src_rect.y + req->src_rect.h) > req->src.height)) { + mdp4_stat.err_size++; + pr_err("%s invalid src rectangle\n", __func__); + return -ERANGE; + } + + if (ctrl->panel_3d != MDP4_3D_SIDE_BY_SIDE) { + int xres; + int yres; + + xres = mfd->panel_info.xres; + yres = mfd->panel_info.yres; + + if (((req->dst_rect.x + req->dst_rect.w) > xres) || + ((req->dst_rect.y + req->dst_rect.h) > yres)) { + mdp4_stat.err_size++; + pr_err("%s invalid dst rectangle\n", __func__); + return -ERANGE; + } + } + + ptype = mdp4_overlay_format2type(req->src.format); + if (ptype < 0) { + pr_err("%s: mdp4_overlay_format2type!\n", __func__); + return ptype; + } + + req_share = (req->flags & MDP_OV_PIPE_SHARE); + + if (req->id == MSMFB_NEW_REQUEST) /* new request */ + pipe = mdp4_overlay_pipe_alloc(ptype, mixer, req_share); + else + pipe = mdp4_overlay_ndx2pipe(req->id); + + if (pipe == NULL) { + pr_err("%s: pipe == NULL!\n", __func__); + return -ENOMEM; + } + + /* no down scale at rgb pipe */ + if (pipe->pipe_num <= OVERLAY_PIPE_RGB2) { + if ((req->src_rect.h > req->dst_rect.h) || + (req->src_rect.w > req->dst_rect.w)) { + pr_err("%s: h>h || w>w!\n", __func__); + return -ERANGE; + } + } + + pipe->src_format = req->src.format; + ret = mdp4_overlay_format2pipe(pipe); + if (ret < 0) { + pr_err("%s: mdp4_overlay_format2pipe!\n", __func__); + return ret; + } + + /* + * base layer == 1, reserved for frame buffer + * zorder 0 == stage 0 == 2 + * zorder 1 == stage 1 == 3 + * zorder 2 == stage 2 == 4 + */ + if (req->id == MSMFB_NEW_REQUEST) { /* new request */ + pd = &ctrl->ov_pipe[pipe->pipe_num]; + for (j = 0; j < MDP4_MAX_SHARE; j++) { + if (pd->ndx_list[j] == 0) { + pd->ndx_list[j] = pipe->pipe_ndx; + break; + } + } + pipe->pipe_share = req_share; + pd->ref_cnt++; + pipe->pipe_used++; + pipe->mixer_num = mixer; + pipe->mixer_stage = req->z_order + MDP4_MIXER_STAGE0; + pr_debug("%s: zorder=%d pipe ndx=%d num=%d\n", __func__, + req->z_order, pipe->pipe_ndx, pipe->pipe_num); + + } + + pipe->src_width = req->src.width & 0x07ff; /* source img width */ + pipe->src_height = req->src.height & 0x07ff; /* source img height */ + pipe->src_h = req->src_rect.h & 0x07ff; + pipe->src_w = req->src_rect.w & 0x07ff; + pipe->src_y = req->src_rect.y & 0x07ff; + pipe->src_x = req->src_rect.x & 0x07ff; + pipe->dst_h = req->dst_rect.h & 0x07ff; + pipe->dst_w = req->dst_rect.w & 0x07ff; + pipe->dst_y = req->dst_rect.y & 0x07ff; + pipe->dst_x = req->dst_rect.x & 0x07ff; + + pipe->op_mode = 0; + + if (req->flags & MDP_FLIP_LR) + pipe->op_mode |= MDP4_OP_FLIP_LR; + + if (req->flags & MDP_FLIP_UD) + pipe->op_mode |= MDP4_OP_FLIP_UD; + + if (req->flags & MDP_DITHER) + pipe->op_mode |= MDP4_OP_DITHER_EN; + + if (req->flags & MDP_DEINTERLACE) + pipe->op_mode |= MDP4_OP_DEINT_EN; + + if (req->flags & MDP_DEINTERLACE_ODD) + pipe->op_mode |= MDP4_OP_DEINT_ODD_REF; + + pipe->is_fg = req->is_fg;/* control alpha and color key */ + + pipe->alpha = req->alpha & 0x0ff; + + pipe->transp = req->transp_mask; + + *ppipe = pipe; + + return 0; +} + +static int get_img(struct msmfb_data *img, struct fb_info *info, + unsigned long *start, unsigned long *len, struct file **pp_file) +{ + int put_needed, ret = 0, fb_num; + struct file *file; +#ifdef CONFIG_ANDROID_PMEM + unsigned long vstart; +#endif + + if (img->flags & MDP_BLIT_SRC_GEM) { + *pp_file = NULL; + return kgsl_gem_obj_addr(img->memory_id, (int) img->priv, + start, len); + } + +#ifdef CONFIG_ANDROID_PMEM + if (!get_pmem_file(img->memory_id, start, &vstart, len, pp_file)) + return 0; +#endif + file = fget_light(img->memory_id, &put_needed); + if (file == NULL) + return -1; + + if (MAJOR(file->f_dentry->d_inode->i_rdev) == FB_MAJOR) { + fb_num = MINOR(file->f_dentry->d_inode->i_rdev); + if (get_fb_phys_info(start, len, fb_num)) + ret = -1; + else + *pp_file = file; + } else + ret = -1; + if (ret) + fput_light(file, put_needed); + return ret; +} + +int mdp4_overlay_3d(struct fb_info *info, struct msmfb_overlay_3d *req) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + int ret = -EPERM; + + if (mutex_lock_interruptible(&mfd->dma->ov_mutex)) + return -EINTR; + +#ifdef CONFIG_FB_MSM_MIPI_DSI + /* Only dsi_cmd panel support 3D */ + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) { + mdp4_dsi_cmd_3d(mfd, req); + ret = 0; + } +#endif + mutex_unlock(&mfd->dma->ov_mutex); + + return ret; +} + +#ifdef CONFIG_FB_MSM_OVERLAY_WRITEBACK +int mdp4_overlay_blt(struct fb_info *info, struct msmfb_overlay_blt *req) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + + if (mfd == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&mfd->dma->ov_mutex)) + return -EINTR; + + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) + mdp4_dsi_overlay_blt(mfd, req); + else if (ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) + mdp4_dsi_video_overlay_blt(mfd, req); + else if (ctrl->panel_mode & MDP4_PANEL_LCDC) + mdp4_lcdc_overlay_blt(mfd, req); + + mutex_unlock(&mfd->dma->ov_mutex); + + return 0; +} + +int mdp4_overlay_blt_offset(struct fb_info *info, struct msmfb_overlay_blt *req) +{ + int ret = 0; + + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + + if (mutex_lock_interruptible(&mfd->dma->ov_mutex)) + return -EINTR; + + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) + ret = mdp4_dsi_overlay_blt_offset(mfd, req); + else if (ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) + ret = mdp4_dsi_video_overlay_blt_offset(mfd, req); + else if (ctrl->panel_mode & MDP4_PANEL_LCDC) + ret = mdp4_lcdc_overlay_blt_offset(mfd, req); + + mutex_unlock(&mfd->dma->ov_mutex); + + return ret; +} +#endif + +int mdp4_overlay_get(struct fb_info *info, struct mdp_overlay *req) +{ + struct mdp4_overlay_pipe *pipe; + + pipe = mdp4_overlay_ndx2pipe(req->id); + if (pipe == NULL) + return -ENODEV; + + *req = pipe->req_data; + + return 0; +} + +#define OVERLAY_VGA_SIZE 0x04B000 +#define OVERLAY_720P_TILE_SIZE 0x0E6000 +#define OVERLAY_WSVGA_SIZE 0x98000 /* 1024x608, align 600 to 32bit */ +#define OVERLAY_PERF_LEVEL1 1 +#define OVERLAY_PERF_LEVEL2 2 +#define OVERLAY_PERF_LEVEL3 3 +#define OVERLAY_PERF_LEVEL4 4 + +#ifdef CONFIG_MSM_BUS_SCALING +#define OVERLAY_BUS_SCALE_TABLE_BASE 6 +#endif + +static int mdp4_overlay_is_rgb_type(int format) +{ + switch (format) { + case MDP_RGB_565: + case MDP_RGB_888: + case MDP_BGR_565: + case MDP_XRGB_8888: + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_BGRA_8888: + case MDP_RGBX_8888: + return 1; + default: + return 0; + } +} + +static uint32 mdp4_overlay_get_perf_level(struct mdp_overlay *req) +{ + int is_fg; + + if (req->is_fg && ((req->alpha & 0x0ff) == 0xff)) + is_fg = 1; + + if (req->flags & MDP_DEINTERLACE) + return OVERLAY_PERF_LEVEL1; + + if (mdp4_overlay_is_rgb_type(req->src.format) && is_fg && + ((req->src.width * req->src.height) <= OVERLAY_WSVGA_SIZE)) + return OVERLAY_PERF_LEVEL4; + else if (mdp4_overlay_is_rgb_type(req->src.format)) + return OVERLAY_PERF_LEVEL1; + + if (ctrl->ov_pipe[OVERLAY_PIPE_VG1].ref_cnt && + ctrl->ov_pipe[OVERLAY_PIPE_VG2].ref_cnt) + return OVERLAY_PERF_LEVEL1; + + if (req->src.width*req->src.height <= OVERLAY_VGA_SIZE) + return OVERLAY_PERF_LEVEL3; + else if (req->src.width*req->src.height <= OVERLAY_720P_TILE_SIZE) + return OVERLAY_PERF_LEVEL2; + else + return OVERLAY_PERF_LEVEL1; +} + +int mdp4_overlay_set(struct fb_info *info, struct mdp_overlay *req) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + int ret, mixer; + struct mdp4_overlay_pipe *pipe; + + if (mfd == NULL) { + pr_err("%s: mfd == NULL, -ENODEV\n", __func__); + return -ENODEV; + } + + if (!mfd->panel_power_on) /* suspended */ + return -EPERM; + + if (req->src.format == MDP_FB_FORMAT) + req->src.format = mfd->fb_imgType; + + if (mutex_lock_interruptible(&mfd->dma->ov_mutex)) { + pr_err("%s: mutex_lock_interruptible, -EINTR\n", __func__); + return -EINTR; + } + + perf_level = mdp4_overlay_get_perf_level(req); + + if ((mfd->panel_info.type == LCDC_PANEL) && + (req->src_rect.h > + req->dst_rect.h || req->src_rect.w > req->dst_rect.w)) { + if (mdp4_overlay_validate_downscale(req, mfd, + perf_level, mfd->panel_info.clk_rate)) { + mutex_unlock(&mfd->dma->ov_mutex); + return -ERANGE; + } + } + if ((mfd->panel_info.type == MIPI_VIDEO_PANEL) && + (req->src_rect.h > + req->dst_rect.h || req->src_rect.w > req->dst_rect.w)) { + if (mdp4_overlay_validate_downscale(req, mfd, + perf_level, (&mfd->panel_info.mipi)->dsi_pclk_rate)) { + mutex_unlock(&mfd->dma->ov_mutex); + return -ERANGE; + } + } + mixer = mfd->panel_info.pdest; /* DISPLAY_1 or DISPLAY_2 */ + + ret = mdp4_overlay_req2pipe(req, mixer, &pipe, mfd); + if (ret < 0) { + mutex_unlock(&mfd->dma->ov_mutex); + pr_err("%s: mdp4_overlay_req2pipe, ret=%d\n", __func__, ret); + return ret; + } + +#ifdef CONFIG_FB_MSM_MIPI_DSI + /* + * writeback (blt) mode to provide work around for + * dsi cmd mode interface hardware bug. + */ + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) { + if (mixer == MDP4_MIXER0 && req->dst_rect.x != 0) { + mdp4_dsi_blt_dmap_busy_wait(mfd); + mdp4_dsi_overlay_blt_start(mfd); + } + } +#endif + + /* return id back to user */ + req->id = pipe->pipe_ndx; /* pipe_ndx start from 1 */ + pipe->req_data = *req; /* keep original req */ + + pipe->flags = req->flags; + + if (pipe->flags & MDP_SHARPENING) { + bool test = ((pipe->req_data.dpp.sharp_strength > 0) && + ((req->src_rect.w > req->dst_rect.w) && + (req->src_rect.h > req->dst_rect.h))); + if (test) { + pr_warn("%s: No sharpening while downscaling.\n", + __func__); + pipe->flags &= ~MDP_SHARPENING; + } + } + + mdp4_stat.overlay_set[pipe->mixer_num]++; + + if (ctrl->panel_mode & MDP4_PANEL_MDDI) { + if (mdp_hw_revision == MDP4_REVISION_V2_1 && + pipe->mixer_num == MDP4_MIXER0) + mdp4_overlay_status_write(MDP4_OVERLAY_TYPE_SET, true); + } + + mdp4_del_res_rel = 0; + mutex_unlock(&mfd->dma->ov_mutex); + mdp_set_core_clk(perf_level); + +#ifdef CONFIG_MSM_BUS_SCALING + if (pipe->mixer_num == MDP4_MIXER0) { + mdp_bus_scale_update_request(OVERLAY_BUS_SCALE_TABLE_BASE + - perf_level); + } +#endif + + return 0; +} + +void mdp4_overlay_resource_release(void) +{ + if (mdp4_del_res_rel) { + mdp_set_core_clk(OVERLAY_PERF_LEVEL4); + mdp4_del_res_rel = 0; + } +} + +int mdp4_overlay_unset(struct fb_info *info, int ndx) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct mdp4_overlay_pipe *pipe; + uint32 flags; + + if (mfd == NULL) + return -ENODEV; + + if (mutex_lock_interruptible(&mfd->dma->ov_mutex)) + return -EINTR; + + pipe = mdp4_overlay_ndx2pipe(ndx); + + if (pipe == NULL) { + mutex_unlock(&mfd->dma->ov_mutex); + return -ENODEV; + } + + if (pipe->mixer_num == MDP4_MIXER1) + ctrl->mixer1_played = 0; + else { + /* mixer 0 */ + ctrl->mixer0_played = 0; +#ifdef CONFIG_FB_MSM_MIPI_DSI + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) { + if (mfd->panel_power_on) { + mdp4_dsi_blt_dmap_busy_wait(mfd); + } + } +#else + if (ctrl->panel_mode & MDP4_PANEL_MDDI) { + if (mfd->panel_power_on) + mdp4_mddi_dma_busy_wait(mfd); + } +#endif + } + + mdp4_mixer_stage_down(pipe); + + if (pipe->mixer_num == MDP4_MIXER0) { +#ifdef CONFIG_FB_MSM_MIPI_DSI + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) { + if (mfd->panel_power_on) + if (mdp4_dsi_overlay_blt_stop(mfd) == 0) + mdp4_dsi_cmd_overlay_restore(); + } else if (ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) { + flags = pipe->flags; + pipe->flags &= ~MDP_OV_PLAY_NOWAIT; + mdp4_overlay_dsi_video_vsync_push(mfd, pipe); + pipe->flags = flags; + } +#else + if (ctrl->panel_mode & MDP4_PANEL_MDDI) { + if (mdp_hw_revision == MDP4_REVISION_V2_1) + mdp4_overlay_status_write( + MDP4_OVERLAY_TYPE_UNSET, true); + if (mfd->panel_power_on) + mdp4_mddi_overlay_restore(); + } +#endif + else if (ctrl->panel_mode & MDP4_PANEL_LCDC) { + flags = pipe->flags; + pipe->flags &= ~MDP_OV_PLAY_NOWAIT; + mdp4_overlay_lcdc_vsync_push(mfd, pipe); + pipe->flags = flags; + } + } +#ifdef CONFIG_FB_MSM_DTV + else { /* mixer1, DTV, ATV */ + flags = pipe->flags; + pipe->flags &= ~MDP_OV_PLAY_NOWAIT; + mdp4_overlay_dtv_vsync_push(mfd, pipe); + pipe->flags = flags; + } +#endif + + mdp4_stat.overlay_unset[pipe->mixer_num]++; + + mdp4_overlay_pipe_free(pipe); + + if (!(ctrl->ov_pipe[OVERLAY_PIPE_VG1].ref_cnt + + ctrl->ov_pipe[OVERLAY_PIPE_VG2].ref_cnt)) + mdp4_del_res_rel = 1; + + mutex_unlock(&mfd->dma->ov_mutex); + +#ifdef CONFIG_MSM_BUS_SCALING + if (pipe->mixer_num == MDP4_MIXER0) + if (mfd->panel_power_on) + mdp_bus_scale_update_request(2); +#endif + return 0; +} + +struct tile_desc { + uint32 width; /* tile's width */ + uint32 height; /* tile's height */ + uint32 row_tile_w; /* tiles per row's width */ + uint32 row_tile_h; /* tiles per row's height */ +}; + +void tile_samsung(struct tile_desc *tp) +{ + /* + * each row of samsung tile consists of two tiles in height + * and two tiles in width which means width should align to + * 64 x 2 bytes and height should align to 32 x 2 bytes. + * video decoder generate two tiles in width and one tile + * in height which ends up height align to 32 X 1 bytes. + */ + tp->width = 64; /* 64 bytes */ + tp->row_tile_w = 2; /* 2 tiles per row's width */ + tp->height = 32; /* 32 bytes */ + tp->row_tile_h = 1; /* 1 tiles per row's height */ +} + +uint32 tile_mem_size(struct mdp4_overlay_pipe *pipe, struct tile_desc *tp) +{ + uint32 tile_w, tile_h; + uint32 row_num_w, row_num_h; + + + tile_w = tp->width * tp->row_tile_w; + tile_h = tp->height * tp->row_tile_h; + + row_num_w = (pipe->src_width + tile_w - 1) / tile_w; + row_num_h = (pipe->src_height + tile_h - 1) / tile_h; + return ((row_num_w * row_num_h * tile_w * tile_h) + 8191) & ~8191; +} + +int mdp4_overlay_play(struct fb_info *info, struct msmfb_overlay_data *req, + struct file **pp_src_file, struct file **pp_src_plane1_file, + struct file **pp_src_plane2_file) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct msmfb_data *img; + struct mdp4_overlay_pipe *pipe; + struct mdp4_pipe_desc *pd; + ulong start, addr; + ulong len = 0; + struct file *p_src_file = 0; + struct file *p_src_plane1_file = 0, *p_src_plane2_file = 0; + uint32_t overlay_version = 0; + + if (mfd == NULL) + return -ENODEV; + + if (!mfd->panel_power_on) /* suspended */ + return -EPERM; + + pipe = mdp4_overlay_ndx2pipe(req->id); + if (pipe == NULL) { + pr_err("%s: req_id=%d Error\n", __func__, req->id); + return -ENODEV; + } + + if (mutex_lock_interruptible(&mfd->dma->ov_mutex)) + return -EINTR; + + pd = &ctrl->ov_pipe[pipe->pipe_num]; + if (pd->player && pipe != pd->player) { + if (pipe->pipe_type == OVERLAY_TYPE_RGB) { + mutex_unlock(&mfd->dma->ov_mutex); + return 0; /* ignore it, kicked out already */ + } + } + + pd->player = pipe; /* keep */ + + img = &req->data; + get_img(img, info, &start, &len, &p_src_file); + if (len == 0) { + mutex_unlock(&mfd->dma->ov_mutex); + pr_err("%s: pmem Error\n", __func__); + return -1; + } + *pp_src_file = p_src_file; + + addr = start + img->offset; + pipe->srcp0_addr = addr; + pipe->srcp0_ystride = pipe->src_width * pipe->bpp; + + if ((req->version_key & VERSION_KEY_MASK) == 0xF9E8D700) + overlay_version = (req->version_key & ~VERSION_KEY_MASK); + + if (pipe->fetch_plane == OVERLAY_PLANE_PSEUDO_PLANAR) { + if (overlay_version > 0) { + img = &req->plane1_data; + get_img(img, info, &start, &len, &p_src_plane1_file); + if (len == 0) { + mutex_unlock(&mfd->dma->ov_mutex); + pr_err("%s: Error to get plane1\n", __func__); + return -EINVAL; + } + pipe->srcp1_addr = start + img->offset; + *pp_src_plane1_file = p_src_plane1_file; + } else if (pipe->frame_format == + MDP4_FRAME_FORMAT_VIDEO_SUPERTILE) { + struct tile_desc tile; + + tile_samsung(&tile); + pipe->srcp1_addr = addr + tile_mem_size(pipe, &tile); + } else { + pipe->srcp1_addr = addr + (pipe->src_width * + pipe->src_height); + } + pipe->srcp0_ystride = pipe->src_width; + if ((pipe->src_format == MDP_Y_CRCB_H1V1) || + (pipe->src_format == MDP_Y_CBCR_H1V1)) { + if (pipe->src_width > YUV_444_MAX_WIDTH) + pipe->srcp1_ystride = pipe->src_width << 2; + else + pipe->srcp1_ystride = pipe->src_width << 1; + } else + pipe->srcp1_ystride = pipe->src_width; + + } else if (pipe->fetch_plane == OVERLAY_PLANE_PLANAR) { + if (overlay_version > 0) { + img = &req->plane1_data; + get_img(img, info, &start, &len, &p_src_plane1_file); + if (len == 0) { + mutex_unlock(&mfd->dma->ov_mutex); + pr_err("%s: Error to get plane1\n", __func__); + return -EINVAL; + } + pipe->srcp1_addr = start + img->offset; + *pp_src_plane1_file = p_src_plane1_file; + + img = &req->plane2_data; + get_img(img, info, &start, &len, &p_src_plane2_file); + if (len == 0) { + mutex_unlock(&mfd->dma->ov_mutex); + pr_err("%s: Error to get plane2\n", __func__); + return -EINVAL; + } + pipe->srcp2_addr = start + img->offset; + *pp_src_plane2_file = p_src_plane2_file; + } else { + addr += (pipe->src_width * pipe->src_height); + pipe->srcp1_addr = addr; + addr += ((pipe->src_width / 2) * + (pipe->src_height / 2)); + pipe->srcp2_addr = addr; + } + pipe->srcp0_ystride = pipe->src_width; + pipe->srcp1_ystride = pipe->src_width / 2; + pipe->srcp2_ystride = pipe->src_width / 2; + } + + if (pipe->pipe_num >= OVERLAY_PIPE_VG1) + mdp4_overlay_vg_setup(pipe); /* video/graphic pipe */ + else { + if (pipe->flags & MDP_SHARPENING) { + pr_warn( + "%s: Sharpening/Smoothing not supported on RGB pipe\n", + __func__); + pipe->flags &= ~MDP_SHARPENING; + } + mdp4_overlay_rgb_setup(pipe); /* rgb pipe */ + } + + mdp4_mixer_blend_setup(pipe); + mdp4_mixer_stage_up(pipe); + + if (pipe->mixer_num == MDP4_MIXER1) { + ctrl->mixer1_played++; + /* enternal interface */ + if (ctrl->panel_mode & MDP4_PANEL_DTV) +#ifdef CONFIG_FB_MSM_DTV + mdp4_overlay_dtv_ov_done_push(mfd, pipe); +#else + mdp4_overlay_reg_flush(pipe, 1); +#endif + else if (ctrl->panel_mode & MDP4_PANEL_ATV) + mdp4_overlay_reg_flush(pipe, 1); + } else { + /* primary interface */ + ctrl->mixer0_played++; + if (ctrl->panel_mode & MDP4_PANEL_LCDC) + mdp4_overlay_lcdc_vsync_push(mfd, pipe); +#ifdef CONFIG_FB_MSM_MIPI_DSI + else if (ctrl->panel_mode & MDP4_PANEL_DSI_VIDEO) + mdp4_overlay_dsi_video_vsync_push(mfd, pipe); +#endif + else { + /* mddi & mipi dsi cmd mode */ + if (pipe->flags & MDP_OV_PLAY_NOWAIT) { + mdp4_stat.overlay_play[pipe->mixer_num]++; + mutex_unlock(&mfd->dma->ov_mutex); + return 0; + } +#ifdef CONFIG_FB_MSM_MIPI_DSI + if (ctrl->panel_mode & MDP4_PANEL_DSI_CMD) { + mdp4_dsi_cmd_dma_busy_wait(mfd); + mdp4_dsi_cmd_kickoff_video(mfd, pipe); + } +#else + if (ctrl->panel_mode & MDP4_PANEL_MDDI) { + mdp4_mddi_dma_busy_wait(mfd); + mdp4_mddi_kickoff_video(mfd, pipe); + } +#endif + } + } + + mdp4_stat.overlay_play[pipe->mixer_num]++; + + mutex_unlock(&mfd->dma->ov_mutex); + + return 0; +} diff --git a/drivers/video/msm/mdp4_overlay_atv.c b/drivers/video/msm/mdp4_overlay_atv.c new file mode 100644 index 00000000000..8dcdec043fa --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_atv.c @@ -0,0 +1,189 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + + +static struct mdp4_overlay_pipe *atv_pipe; + +int mdp4_atv_on(struct platform_device *pdev) +{ + uint8 *buf; + int bpp, ptype; + struct fb_info *fbi; + struct fb_var_screeninfo *var; + struct msm_fb_data_type *mfd; + struct mdp4_overlay_pipe *pipe; + int ret; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + fbi = mfd->fbi; + var = &fbi->var; + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + if (atv_pipe == NULL) { + ptype = mdp4_overlay_format2type(mfd->fb_imgType); + pipe = mdp4_overlay_pipe_alloc(ptype, MDP4_MIXER1, 0); + if (pipe == NULL) + return -EBUSY; + pipe->pipe_used++; + pipe->mixer_stage = MDP4_MIXER_STAGE_BASE; + pipe->mixer_num = MDP4_MIXER1; + pipe->src_format = mfd->fb_imgType; + mdp4_overlay_panel_mode(pipe->mixer_num, MDP4_PANEL_ATV); + mdp4_overlay_format2pipe(pipe); + + atv_pipe = pipe; /* keep it */ + } else { + pipe = atv_pipe; + } + + printk(KERN_INFO "mdp4_atv_overlay: pipe=%x ndx=%d\n", + (int)pipe, pipe->pipe_ndx); + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* Turn the next panel on, get correct resolution + before configuring overlay pipe */ + ret = panel_next_on(pdev); + + pr_info("%s: fbi->var.yres: %d | fbi->var.xres: %d", + __func__, fbi->var.yres, fbi->var.xres); + + /* MDP4 Config */ + pipe->src_height = fbi->var.yres; + pipe->src_width = fbi->var.xres; + pipe->src_h = fbi->var.yres; + pipe->src_w = fbi->var.xres; + pipe->src_y = 0; + pipe->src_x = 0; + pipe->srcp0_addr = (uint32) buf; + pipe->srcp0_ystride = fbi->fix.line_length; + + mdp4_overlay_dmae_xy(pipe); /* dma_e */ + mdp4_overlay_dmae_cfg(mfd, 1); + + mdp4_overlay_rgb_setup(pipe); + + mdp4_mixer_stage_up(pipe); + + mdp4_overlayproc_cfg(pipe); + + if (ret == 0) + mdp_pipe_ctrl(MDP_OVERLAY1_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return ret; +} + +int mdp4_atv_off(struct platform_device *pdev) +{ + int ret = 0; + + mdp_pipe_ctrl(MDP_OVERLAY1_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + ret = panel_next_off(pdev); + + /* delay to make sure the last frame finishes */ + msleep(100); + + /* dis-engage rgb2 from mixer1 */ + if (atv_pipe) + mdp4_mixer_stage_down(atv_pipe); + + return ret; +} + +/* + * mdp4_overlay1_done_atv: called from isr + */ +void mdp4_overlay1_done_atv() +{ + complete(&atv_pipe->comp); +} + +void mdp4_atv_overlay(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi = mfd->fbi; + uint8 *buf; + int bpp; + unsigned long flag; + struct mdp4_overlay_pipe *pipe; + + if (!mfd->panel_power_on) + return; + + /* no need to power on cmd block since it's lcdc mode */ + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + mutex_lock(&mfd->dma->ov_mutex); + + pipe = atv_pipe; + pipe->srcp0_addr = (uint32) buf; + mdp4_overlay_rgb_setup(pipe); + mdp4_overlay_reg_flush(pipe, 1); /* rgb2 and mixer1 */ + + printk(KERN_INFO "mdp4_atv_overlay: pipe=%x ndx=%d\n", + (int)pipe, pipe->pipe_ndx); + + /* enable irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_OVERLAY1_TERM); + INIT_COMPLETION(atv_pipe->comp); + mfd->dma->waiting = TRUE; + outp32(MDP_INTR_CLEAR, INTR_OVERLAY1_DONE); + mdp_intr_mask |= INTR_OVERLAY1_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion_killable(&atv_pipe->comp); + mdp_disable_irq(MDP_OVERLAY1_TERM); + + mdp4_stat.kickoff_atv++; + mdp4_overlay_resource_release(); + mutex_unlock(&mfd->dma->ov_mutex); +} diff --git a/drivers/video/msm/mdp4_overlay_dsi_cmd.c b/drivers/video/msm/mdp4_overlay_dsi_cmd.c new file mode 100644 index 00000000000..22d9d3b07c2 --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_dsi_cmd.c @@ -0,0 +1,672 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" +#include "mipi_dsi.h" + +static struct mdp4_overlay_pipe *dsi_pipe; +static struct msm_fb_data_type *dsi_mfd; +static int busy_wait_cnt; +static int dsi_state; +static unsigned long tout_expired; + +#define TOUT_PERIOD HZ /* 1 second */ +#define MS_100 (HZ/10) /* 100 ms */ + +static int vsync_start_y_adjust = 4; + +struct timer_list dsi_clock_timer; + +static int writeback_offset; + +void mdp4_overlay_dsi_state_set(int state) +{ + unsigned long flag; + + spin_lock_irqsave(&mdp_spin_lock, flag); + dsi_state = state; + spin_unlock_irqrestore(&mdp_spin_lock, flag); +} + +static void dsi_clock_tout(unsigned long data) +{ + if (mipi_dsi_clk_on) { + if (dsi_state == ST_DSI_PLAYING) { + mdp4_stat.dsi_clkoff++; + mipi_dsi_clk_disable(); + mdp4_overlay_dsi_state_set(ST_DSI_CLK_OFF); + } + } +} + +static __u32 msm_fb_line_length(__u32 fb_index, __u32 xres, int bpp) +{ + /* + * The adreno GPU hardware requires that the pitch be aligned to + * 32 pixels for color buffers, so for the cases where the GPU + * is writing directly to fb0, the framebuffer pitch + * also needs to be 32 pixel aligned + */ + + if (fb_index == 0) + return ALIGN(xres, 32) * bpp; + else + return xres * bpp; +} + +void mdp4_mipi_vsync_enable(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe, int which) +{ + uint32 start_y, data, tear_en; + + tear_en = (1 << which); + + if ((mfd->use_mdp_vsync) && (mfd->ibuf.vsync_enable) && + (mfd->panel_info.lcd.vsync_enable)) { + + if (vsync_start_y_adjust <= pipe->dst_y) + start_y = pipe->dst_y - vsync_start_y_adjust; + else + start_y = (mfd->total_lcd_lines - 1) - + (vsync_start_y_adjust - pipe->dst_y); + if (which == 0) + MDP_OUTP(MDP_BASE + 0x210, start_y); /* primary */ + else + MDP_OUTP(MDP_BASE + 0x214, start_y); /* secondary */ + + data = inpdw(MDP_BASE + 0x20c); + data |= tear_en; + MDP_OUTP(MDP_BASE + 0x20c, data); + } else { + data = inpdw(MDP_BASE + 0x20c); + data &= ~tear_en; + MDP_OUTP(MDP_BASE + 0x20c, data); + } +} + +void mdp4_overlay_update_dsi_cmd(struct msm_fb_data_type *mfd) +{ + MDPIBUF *iBuf = &mfd->ibuf; + struct fb_info *fbi; + uint8 *src; + int ptype; + struct mdp4_overlay_pipe *pipe; + int bpp; + int ret; + + if (mfd->key != MFD_KEY) + return; + + dsi_mfd = mfd; /* keep it */ + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + if (dsi_pipe == NULL) { + ptype = mdp4_overlay_format2type(mfd->fb_imgType); + if (ptype < 0) + printk(KERN_INFO "%s: format2type failed\n", __func__); + pipe = mdp4_overlay_pipe_alloc(ptype, MDP4_MIXER0, 0); + if (pipe == NULL) + printk(KERN_INFO "%s: pipe_alloc failed\n", __func__); + pipe->pipe_used++; + pipe->mixer_stage = MDP4_MIXER_STAGE_BASE; + pipe->mixer_num = MDP4_MIXER0; + pipe->src_format = mfd->fb_imgType; + mdp4_overlay_panel_mode(pipe->mixer_num, MDP4_PANEL_DSI_CMD); + ret = mdp4_overlay_format2pipe(pipe); + if (ret < 0) + printk(KERN_INFO "%s: format2type failed\n", __func__); + + init_timer(&dsi_clock_timer); + dsi_clock_timer.function = dsi_clock_tout; + dsi_clock_timer.data = (unsigned long) mfd;; + dsi_clock_timer.expires = 0xffffffff; + add_timer(&dsi_clock_timer); + tout_expired = jiffies; + + dsi_pipe = pipe; /* keep it */ + + fbi = mfd->fbi; + bpp = fbi->var.bits_per_pixel / 8; + src = (uint8 *) iBuf->buf; + writeback_offset = mdp4_overlay_writeback_setup( + fbi, pipe, src, bpp); + + /* + * configure dsi stream id + * dma_p = 0, dma_s = 1 + */ + MDP_OUTP(MDP_BASE + 0x000a0, 0x10); + /* enable dsi trigger on dma_p */ + MDP_OUTP(MDP_BASE + 0x000a4, 0x01); + } else { + pipe = dsi_pipe; + } + + /* whole screen for base layer */ + src = (uint8 *) iBuf->buf; + + + { + struct fb_info *fbi; + + fbi = mfd->fbi; + if (pipe->is_3d) { + bpp = fbi->var.bits_per_pixel / 8; + pipe->src_height = pipe->src_height_3d; + pipe->src_width = pipe->src_width_3d; + pipe->src_h = pipe->src_height_3d; + pipe->src_w = pipe->src_width_3d; + pipe->dst_h = pipe->src_height_3d; + pipe->dst_w = pipe->src_width_3d; + pipe->srcp0_ystride = msm_fb_line_length(0, + pipe->src_width, bpp); + } else { + /* 2D */ + pipe->src_height = fbi->var.yres; + pipe->src_width = fbi->var.xres; + pipe->src_h = fbi->var.yres; + pipe->src_w = fbi->var.xres; + pipe->dst_h = fbi->var.yres; + pipe->dst_w = fbi->var.xres; + pipe->srcp0_ystride = fbi->fix.line_length; + } + pipe->src_y = 0; + pipe->src_x = 0; + pipe->dst_y = 0; + pipe->dst_x = 0; + pipe->srcp0_addr = (uint32)src; + } + + + mdp4_overlay_rgb_setup(pipe); + + mdp4_mixer_stage_up(pipe); + + mdp4_overlayproc_cfg(pipe); + + mdp4_overlay_dmap_xy(pipe); + + mdp4_overlay_dmap_cfg(mfd, 0); + + mdp4_mipi_vsync_enable(mfd, pipe, 0); + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + wmb(); +} + +void mdp4_dsi_cmd_3d(struct msm_fb_data_type *mfd, struct msmfb_overlay_3d *r3d) +{ + struct fb_info *fbi; + struct mdp4_overlay_pipe *pipe; + int bpp; + uint8 *src = NULL; + + if (dsi_pipe == NULL) + return; + + dsi_pipe->is_3d = r3d->is_3d; + dsi_pipe->src_height_3d = r3d->height; + dsi_pipe->src_width_3d = r3d->width; + + pipe = dsi_pipe; + + if (pipe->is_3d) + mdp4_overlay_panel_3d(pipe->mixer_num, MDP4_3D_SIDE_BY_SIDE); + else + mdp4_overlay_panel_3d(pipe->mixer_num, MDP4_3D_NONE); + + if (mfd->panel_power_on) { + mdp4_dsi_cmd_dma_busy_wait(mfd); + mdp4_dsi_blt_dmap_busy_wait(mfd); + } + + fbi = mfd->fbi; + if (pipe->is_3d) { + bpp = fbi->var.bits_per_pixel / 8; + pipe->src_height = pipe->src_height_3d; + pipe->src_width = pipe->src_width_3d; + pipe->src_h = pipe->src_height_3d; + pipe->src_w = pipe->src_width_3d; + pipe->dst_h = pipe->src_height_3d; + pipe->dst_w = pipe->src_width_3d; + pipe->srcp0_ystride = msm_fb_line_length(0, + pipe->src_width, bpp); + } else { + /* 2D */ + pipe->src_height = fbi->var.yres; + pipe->src_width = fbi->var.xres; + pipe->src_h = fbi->var.yres; + pipe->src_w = fbi->var.xres; + pipe->dst_h = fbi->var.yres; + pipe->dst_w = fbi->var.xres; + pipe->srcp0_ystride = fbi->fix.line_length; + } + pipe->src_y = 0; + pipe->src_x = 0; + pipe->dst_y = 0; + pipe->dst_x = 0; + pipe->srcp0_addr = (uint32)src; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + mdp4_overlay_rgb_setup(pipe); + + mdp4_mixer_stage_up(pipe); + + mdp4_overlayproc_cfg(pipe); + + mdp4_overlay_dmap_xy(pipe); + + mdp4_overlay_dmap_cfg(mfd, 0); + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + + +#ifdef CONFIG_FB_MSM_OVERLAY_WRITEBACK +int mdp4_dsi_overlay_blt_start(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + + pr_debug("%s: blt_end=%d blt_addr=%x pid=%d\n", + __func__, dsi_pipe->blt_end, (int)dsi_pipe->blt_addr, current->pid); + + if (dsi_pipe->blt_addr == 0) { + mdp4_dsi_cmd_dma_busy_wait(mfd); + spin_lock_irqsave(&mdp_spin_lock, flag); + dsi_pipe->blt_end = 0; + dsi_pipe->blt_cnt = 0; + dsi_pipe->ov_cnt = 0; + dsi_pipe->dmap_cnt = 0; + dsi_pipe->blt_addr = dsi_pipe->blt_base; + mdp4_stat.writeback++; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + return 0; + } + + return -EBUSY; +} + +int mdp4_dsi_overlay_blt_stop(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + + + pr_debug("%s: blt_end=%d blt_addr=%x\n", + __func__, dsi_pipe->blt_end, (int)dsi_pipe->blt_addr); + + if ((dsi_pipe->blt_end == 0) && dsi_pipe->blt_addr) { + spin_lock_irqsave(&mdp_spin_lock, flag); + dsi_pipe->blt_end = 1; /* mark as end */ + spin_unlock_irqrestore(&mdp_spin_lock, flag); + return 0; + } + + return -EBUSY; +} + +int mdp4_dsi_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + req->offset = writeback_offset; + req->width = dsi_pipe->src_width; + req->height = dsi_pipe->src_height; + req->bpp = dsi_pipe->bpp; + + return sizeof(*req); +} + +void mdp4_dsi_overlay_blt(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + if (req->enable) + mdp4_dsi_overlay_blt_start(mfd); + else if (req->enable == 0) + mdp4_dsi_overlay_blt_stop(mfd); + +} +#else +int mdp4_dsi_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + return 0; +} +int mdp4_dsi_overlay_blt_start(struct msm_fb_data_type *mfd) +{ + return -EBUSY; +} +int mdp4_dsi_overlay_blt_stop(struct msm_fb_data_type *mfd) +{ + return -EBUSY; +} +#endif + +void mdp4_blt_xy_update(struct mdp4_overlay_pipe *pipe) +{ + uint32 off, addr, addr2; + int bpp; + char *overlay_base; + + + if (pipe->blt_addr == 0) + return; + + +#ifdef BLT_RGB565 + bpp = 2; /* overlay ouput is RGB565 */ +#else + bpp = 3; /* overlay ouput is RGB888 */ +#endif + off = 0; + if (pipe->dmap_cnt & 0x01) + off = pipe->src_height * pipe->src_width * bpp; + addr = pipe->blt_addr + off; + + /* dmap */ + MDP_OUTP(MDP_BASE + 0x90008, addr); + + off = 0; + if (pipe->ov_cnt & 0x01) + off = pipe->src_height * pipe->src_width * bpp; + addr2 = pipe->blt_addr + off; + /* overlay 0 */ + overlay_base = MDP_BASE + MDP4_OVERLAYPROC0_BASE;/* 0x10000 */ + outpdw(overlay_base + 0x000c, addr2); + outpdw(overlay_base + 0x001c, addr2); +} + + +/* + * mdp4_dmap_done_dsi: called from isr + * DAM_P_DONE only used when blt enabled + */ +void mdp4_dma_p_done_dsi(struct mdp_dma_data *dma) +{ + int diff; + + dsi_pipe->dmap_cnt++; + diff = dsi_pipe->ov_cnt - dsi_pipe->dmap_cnt; + pr_debug("%s: ov_cnt=%d dmap_cnt=%d\n", + __func__, dsi_pipe->ov_cnt, dsi_pipe->dmap_cnt); + + if (diff <= 0) { + spin_lock(&mdp_spin_lock); + dma->dmap_busy = FALSE; + complete(&dma->dmap_comp); + spin_unlock(&mdp_spin_lock); + if (dsi_pipe->blt_end) { + dsi_pipe->blt_end = 0; + dsi_pipe->blt_addr = 0; + pr_debug("%s: END, ov_cnt=%d dmap_cnt=%d\n", + __func__, dsi_pipe->ov_cnt, dsi_pipe->dmap_cnt); + mdp_intr_mask &= ~INTR_DMA_P_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + } + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_OFF, TRUE); + mdp_disable_irq_nosync(MDP_DMA2_TERM); /* disable intr */ + return; + } + + spin_lock(&mdp_spin_lock); + dma->busy = FALSE; + spin_unlock(&mdp_spin_lock); + complete(&dma->comp); + if (busy_wait_cnt) + busy_wait_cnt--; + + pr_debug("%s: kickoff dmap\n", __func__); + + mdp4_blt_xy_update(dsi_pipe); + /* kick off dmap */ + outpdw(MDP_BASE + 0x000c, 0x0); + /* trigger dsi cmd engine */ + mipi_dsi_cmd_mdp_sw_trigger(); + + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_OFF, TRUE); +} + + +/* + * mdp4_overlay0_done_dsi_cmd: called from isr + */ +void mdp4_overlay0_done_dsi_cmd(struct mdp_dma_data *dma) +{ + int diff; + + if (dsi_pipe->blt_addr == 0) { + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_OFF, TRUE); + spin_lock(&mdp_spin_lock); + dma->busy = FALSE; + spin_unlock(&mdp_spin_lock); + complete(&dma->comp); + if (busy_wait_cnt) + busy_wait_cnt--; + mdp_disable_irq_nosync(MDP_OVERLAY0_TERM); + return; + } + + /* blt enabled */ + if (dsi_pipe->blt_end == 0) + dsi_pipe->ov_cnt++; + + pr_debug("%s: ov_cnt=%d dmap_cnt=%d\n", + __func__, dsi_pipe->ov_cnt, dsi_pipe->dmap_cnt); + + if (dsi_pipe->blt_cnt == 0) { + /* first kickoff since blt enabled */ + mdp_intr_mask |= INTR_DMA_P_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + } + dsi_pipe->blt_cnt++; + + diff = dsi_pipe->ov_cnt - dsi_pipe->dmap_cnt; + if (diff >= 2) { + mdp_disable_irq_nosync(MDP_OVERLAY0_TERM); + return; + } + + spin_lock(&mdp_spin_lock); + dma->busy = FALSE; + dma->dmap_busy = TRUE; + spin_unlock(&mdp_spin_lock); + complete(&dma->comp); + if (busy_wait_cnt) + busy_wait_cnt--; + + pr_debug("%s: kickoff dmap\n", __func__); + + mdp4_blt_xy_update(dsi_pipe); + mdp_enable_irq(MDP_DMA2_TERM); /* enable intr */ + /* kick off dmap */ + outpdw(MDP_BASE + 0x000c, 0x0); + /* trigger dsi cmd engine */ + mipi_dsi_cmd_mdp_sw_trigger(); + mdp_disable_irq_nosync(MDP_OVERLAY0_TERM); +} + +void mdp4_dsi_cmd_overlay_restore(void) +{ + /* mutex holded by caller */ + if (dsi_mfd && dsi_pipe) { + mdp4_dsi_cmd_dma_busy_wait(dsi_mfd); + mdp4_overlay_update_dsi_cmd(dsi_mfd); + + if (dsi_pipe->blt_addr) + mdp4_dsi_blt_dmap_busy_wait(dsi_mfd); + mdp4_dsi_cmd_overlay_kickoff(dsi_mfd, dsi_pipe); + } +} + +void mdp4_dsi_blt_dmap_busy_wait(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + int need_wait = 0; + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (mfd->dma->dmap_busy == TRUE) { + INIT_COMPLETION(mfd->dma->dmap_comp); + need_wait++; + } + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + if (need_wait) { + /* wait until DMA finishes the current job */ + wait_for_completion(&mfd->dma->dmap_comp); + } +} + +/* + * mdp4_dsi_cmd_dma_busy_wait: check dsi link activity + * dsi link is a shared resource and it can only be used + * while it is in idle state. + * ov_mutex need to be acquired before call this function. + */ +void mdp4_dsi_cmd_dma_busy_wait(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + int need_wait = 0; + + + + if (dsi_clock_timer.function) { + if (time_after(jiffies, tout_expired)) { + tout_expired = jiffies + TOUT_PERIOD; + mod_timer(&dsi_clock_timer, tout_expired); + tout_expired -= MS_100; + } + } + + pr_debug("%s: start pid=%d dsi_clk_on=%d\n", + __func__, current->pid, mipi_dsi_clk_on); + + /* satrt dsi clock if necessary */ + if (mipi_dsi_clk_on == 0) { + local_bh_disable(); + mipi_dsi_clk_enable(); + local_bh_enable(); + } + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (mfd->dma->busy == TRUE) { + if (busy_wait_cnt == 0) + INIT_COMPLETION(mfd->dma->comp); + busy_wait_cnt++; + need_wait++; + } + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + if (need_wait) { + /* wait until DMA finishes the current job */ + pr_debug("%s: pending pid=%d dsi_clk_on=%d\n", + __func__, current->pid, mipi_dsi_clk_on); + wait_for_completion(&mfd->dma->comp); + } + pr_debug("%s: done pid=%d dsi_clk_on=%d\n", + __func__, current->pid, mipi_dsi_clk_on); +} + +void mdp4_dsi_cmd_kickoff_video(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + if (dsi_pipe->blt_addr && dsi_pipe->blt_cnt == 0) + mdp4_overlay_update_dsi_cmd(mfd); + + pr_debug("%s: pid=%d\n", __func__, current->pid); + + if (dsi_pipe->blt_addr) + mdp4_dsi_blt_dmap_busy_wait(dsi_mfd); + + mdp4_dsi_cmd_overlay_kickoff(mfd, pipe); +} + +void mdp4_dsi_cmd_kickoff_ui(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + + pr_debug("%s: pid=%d\n", __func__, current->pid); + mdp4_dsi_cmd_overlay_kickoff(mfd, pipe); +} + + +void mdp4_dsi_cmd_overlay_kickoff(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + unsigned long flag; + + + mdp4_overlay_dsi_state_set(ST_DSI_PLAYING); + + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_OVERLAY0_TERM); + mfd->dma->busy = TRUE; + if (dsi_pipe->blt_addr) + mfd->dma->dmap_busy = TRUE; + /* start OVERLAY pipe */ + spin_unlock_irqrestore(&mdp_spin_lock, flag); + mdp_pipe_kickoff(MDP_OVERLAY0_TERM, mfd); + + if (dsi_pipe->blt_addr == 0) { + /* trigger dsi cmd engine */ + mipi_dsi_cmd_mdp_sw_trigger(); + } +} + +void mdp4_dsi_cmd_overlay(struct msm_fb_data_type *mfd) +{ + mutex_lock(&mfd->dma->ov_mutex); + + if (mfd && mfd->panel_power_on) { + mdp4_dsi_cmd_dma_busy_wait(mfd); + + if (dsi_pipe && dsi_pipe->blt_addr) + mdp4_dsi_blt_dmap_busy_wait(mfd); + + mdp4_overlay_update_dsi_cmd(mfd); + + mdp4_dsi_cmd_kickoff_ui(mfd, dsi_pipe); + + + mdp4_stat.kickoff_dsi++; + + /* signal if pan function is waiting for the update completion */ + if (mfd->pan_waiting) { + mfd->pan_waiting = FALSE; + complete(&mfd->pan_comp); + } + } + mdp4_overlay_resource_release(); + mutex_unlock(&mfd->dma->ov_mutex); +} diff --git a/drivers/video/msm/mdp4_overlay_dsi_video.c b/drivers/video/msm/mdp4_overlay_dsi_video.c new file mode 100644 index 00000000000..88b81e784ba --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_dsi_video.c @@ -0,0 +1,404 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + +#define DSI_VIDEO_BASE 0xE0000 + +static int first_pixel_start_x; +static int first_pixel_start_y; + +static int writeback_offset; + +static struct mdp4_overlay_pipe *dsi_pipe; + +static cmd_fxn_t display_on; + +void mdp4_dsi_video_fxn_register(cmd_fxn_t fxn) +{ + display_on = fxn; +} + +int mdp4_dsi_video_on(struct platform_device *pdev) +{ + int dsi_width; + int dsi_height; + int dsi_bpp; + int dsi_border_clr; + int dsi_underflow_clr; + int dsi_hsync_skew; + + int hsync_period; + int hsync_ctrl; + int vsync_period; + int display_hctl; + int display_v_start; + int display_v_end; + int active_hctl; + int active_h_start; + int active_h_end; + int active_v_start; + int active_v_end; + int ctrl_polarity; + int h_back_porch; + int h_front_porch; + int v_back_porch; + int v_front_porch; + int hsync_pulse_width; + int vsync_pulse_width; + int hsync_polarity; + int vsync_polarity; + int data_en_polarity; + int hsync_start_x; + int hsync_end_x; + uint8 *buf; + int bpp, ptype; + struct fb_info *fbi; + struct fb_var_screeninfo *var; + struct msm_fb_data_type *mfd; + struct mdp4_overlay_pipe *pipe; + int ret; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + fbi = mfd->fbi; + var = &fbi->var; + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + if (dsi_pipe == NULL) { + ptype = mdp4_overlay_format2type(mfd->fb_imgType); + if (ptype < 0) + printk(KERN_INFO "%s: format2type failed\n", __func__); + pipe = mdp4_overlay_pipe_alloc(ptype, MDP4_MIXER0, 0); + if (pipe == NULL) { + printk(KERN_INFO "%s: pipe_alloc failed\n", __func__); + return -EBUSY; + } + pipe->pipe_used++; + pipe->mixer_stage = MDP4_MIXER_STAGE_BASE; + pipe->mixer_num = MDP4_MIXER0; + pipe->src_format = mfd->fb_imgType; + mdp4_overlay_panel_mode(pipe->mixer_num, MDP4_PANEL_DSI_VIDEO); + ret = mdp4_overlay_format2pipe(pipe); + if (ret < 0) + printk(KERN_INFO "%s: format2type failed\n", __func__); + + dsi_pipe = pipe; /* keep it */ + + writeback_offset = mdp4_overlay_writeback_setup( + fbi, pipe, buf, bpp); + } else { + pipe = dsi_pipe; + } + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + if (is_mdp4_hw_reset()) { + mdp4_hw_init(); + outpdw(MDP_BASE + 0x0038, mdp4_display_intf); + } + + pipe->src_height = fbi->var.yres; + pipe->src_width = fbi->var.xres; + pipe->src_h = fbi->var.yres; + pipe->src_w = fbi->var.xres; + pipe->src_y = 0; + pipe->src_x = 0; + pipe->srcp0_addr = (uint32) buf; + pipe->srcp0_ystride = fbi->fix.line_length; + pipe->bpp = bpp; + + pipe->dst_h = fbi->var.yres; + pipe->dst_w = fbi->var.xres; + + mdp4_overlay_dmap_xy(pipe); /* dma_p */ + mdp4_overlay_dmap_cfg(mfd, 1); + + mdp4_overlay_rgb_setup(pipe); + + mdp4_mixer_stage_up(pipe); + + mdp4_overlayproc_cfg(pipe); + + /* + * DSI timing setting + */ + h_back_porch = var->left_margin; + h_front_porch = var->right_margin; + v_back_porch = var->upper_margin; + v_front_porch = var->lower_margin; + hsync_pulse_width = var->hsync_len; + vsync_pulse_width = var->vsync_len; + dsi_border_clr = mfd->panel_info.lcdc.border_clr; + dsi_underflow_clr = mfd->panel_info.lcdc.underflow_clr; + dsi_hsync_skew = mfd->panel_info.lcdc.hsync_skew; + dsi_width = mfd->panel_info.xres + + mfd->panel_info.mipi.xres_pad; + dsi_height = mfd->panel_info.yres + + mfd->panel_info.mipi.yres_pad; + dsi_bpp = mfd->panel_info.bpp; + + hsync_period = hsync_pulse_width + h_back_porch + dsi_width + + h_front_porch; + hsync_ctrl = (hsync_period << 16) | hsync_pulse_width; + hsync_start_x = h_back_porch + hsync_pulse_width; + hsync_end_x = hsync_period - h_front_porch - 1; + display_hctl = (hsync_end_x << 16) | hsync_start_x; + + vsync_period = + (vsync_pulse_width + v_back_porch + dsi_height + v_front_porch); + display_v_start = ((vsync_pulse_width + v_back_porch) * hsync_period) + + dsi_hsync_skew; + display_v_end = + ((vsync_period - v_front_porch) * hsync_period) + dsi_hsync_skew - 1; + + if (dsi_width != var->xres) { + active_h_start = hsync_start_x + first_pixel_start_x; + active_h_end = active_h_start + var->xres - 1; + active_hctl = + ACTIVE_START_X_EN | (active_h_end << 16) | active_h_start; + } else { + active_hctl = 0; + } + + if (dsi_height != var->yres) { + active_v_start = + display_v_start + first_pixel_start_y * hsync_period; + active_v_end = active_v_start + (var->yres) * hsync_period - 1; + active_v_start |= ACTIVE_START_Y_EN; + } else { + active_v_start = 0; + active_v_end = 0; + } + + dsi_underflow_clr |= 0x80000000; /* enable recovery */ + hsync_polarity = 0; + vsync_polarity = 0; + data_en_polarity = 0; + + ctrl_polarity = + (data_en_polarity << 2) | (vsync_polarity << 1) | (hsync_polarity); + + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x4, hsync_ctrl); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x8, vsync_period * hsync_period); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0xc, + vsync_pulse_width * hsync_period); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x10, display_hctl); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x14, display_v_start); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x18, display_v_end); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x1c, active_hctl); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x20, active_v_start); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x24, active_v_end); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x28, dsi_border_clr); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x2c, dsi_underflow_clr); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x30, dsi_hsync_skew); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x38, ctrl_polarity); + mdp4_overlay_reg_flush(pipe, 1); + mdp_histogram_ctrl(TRUE); + + ret = panel_next_on(pdev); + if (ret == 0) { + /* enable DSI block */ + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE, 1); + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + if (display_on != NULL) { + msleep(50); + display_on(pdev); + } + } + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return ret; +} + +int mdp4_dsi_video_off(struct platform_device *pdev) +{ + int ret = 0; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE, 0); + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + mdp_histogram_ctrl(FALSE); + ret = panel_next_off(pdev); + +#ifdef MIPI_DSI_RGB_UNSTAGE + /* delay to make sure the last frame finishes */ + msleep(100); + + /* dis-engage rgb0 from mixer0 */ + if (dsi_pipe) + mdp4_mixer_stage_down(dsi_pipe); +#endif + + return ret; +} + +#ifdef CONFIG_FB_MSM_OVERLAY_WRITEBACK +int mdp4_dsi_video_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + req->offset = writeback_offset; + req->width = dsi_pipe->src_width; + req->height = dsi_pipe->src_height; + req->bpp = dsi_pipe->bpp; + + return sizeof(*req); +} + +void mdp4_dsi_video_overlay_blt(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + unsigned long flag; + int change = 0; + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (req->enable && dsi_pipe->blt_addr == 0) { + dsi_pipe->blt_addr = dsi_pipe->blt_base; + change++; + } else if (req->enable == 0 && dsi_pipe->blt_addr) { + dsi_pipe->blt_addr = 0; + change++; + } + pr_debug("%s: blt_addr=%x\n", __func__, (int)dsi_pipe->blt_addr); + dsi_pipe->blt_cnt = 0; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + if (!change) + return; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + /* + * it does not work by turnning dsi video timing enerator off + * and configure new changes and tune it back on like LCDC. + */ + mdp4_overlayproc_cfg(dsi_pipe); + mdp4_overlay_dmap_xy(dsi_pipe); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} +#else +int mdp4_dsi_video_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + return 0; +} +void mdp4_dsi_video_overlay_blt(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + return; +} +#endif + +void mdp4_overlay_dsi_video_wait4vsync(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + + /* enable irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_DMA2_TERM); /* enable intr */ + INIT_COMPLETION(dsi_pipe->comp); + mfd->dma->waiting = TRUE; + outp32(MDP_INTR_CLEAR, INTR_PRIMARY_VSYNC); + mdp_intr_mask |= INTR_PRIMARY_VSYNC; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion_killable(&dsi_pipe->comp); + mdp_disable_irq(MDP_DMA2_TERM); +} + +void mdp4_overlay_dsi_video_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + + mdp4_overlay_reg_flush(pipe, 1); + if (pipe->flags & MDP_OV_PLAY_NOWAIT) + return; + + mdp4_overlay_dsi_video_wait4vsync(mfd); +} + +/* + * mdp4_primary_vsync_dsi_video: called from isr + */ +void mdp4_primary_vsync_dsi_video(void) +{ + complete_all(&dsi_pipe->comp); +} + +/* + * mdp4_overlay1_done_dsi: called from isr + */ +void mdp4_overlay0_done_dsi_video() +{ + complete(&dsi_pipe->comp); +} + + +void mdp4_dsi_video_overlay(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi = mfd->fbi; + uint8 *buf; + int bpp; + struct mdp4_overlay_pipe *pipe; + + if (!mfd->panel_power_on) + return; + + /* no need to power on cmd block since it's dsi video mode */ + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + mutex_lock(&mfd->dma->ov_mutex); + + pipe = dsi_pipe; + pipe->srcp0_addr = (uint32) buf; + mdp4_overlay_rgb_setup(pipe); + mutex_unlock(&mfd->dma->ov_mutex); + mdp4_overlay_dsi_video_vsync_push(mfd, pipe); + mdp4_stat.kickoff_dsi++; + mdp4_overlay_resource_release(); +} diff --git a/drivers/video/msm/mdp4_overlay_dtv.c b/drivers/video/msm/mdp4_overlay_dtv.c new file mode 100644 index 00000000000..71b460c1a8e --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_dtv.c @@ -0,0 +1,404 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + +#define DTV_BASE 0xD0000 + +/*#define DEBUG*/ +#ifdef DEBUG +static void __mdp_outp(uint32 port, uint32 value) +{ + uint32 in_val; + + outpdw(port, value); + in_val = inpdw(port); + printk(KERN_INFO "MDP-DTV[%04x] => %08x [%08x]\n", + port-(uint32)(MDP_BASE + DTV_BASE), value, in_val); +} + +#undef MDP_OUTP +#define MDP_OUTP(port, value) __mdp_outp((uint32)(port), (value)) +#endif + +static int first_pixel_start_x; +static int first_pixel_start_y; + +static struct mdp4_overlay_pipe *dtv_pipe; + +int mdp4_dtv_on(struct platform_device *pdev) +{ + int dtv_width; + int dtv_height; + int dtv_bpp; + int dtv_border_clr; + int dtv_underflow_clr; + int dtv_hsync_skew; + + int hsync_period; + int hsync_ctrl; + int vsync_period; + int display_hctl; + int display_v_start; + int display_v_end; + int active_hctl; + int active_h_start; + int active_h_end; + int active_v_start; + int active_v_end; + int ctrl_polarity; + int h_back_porch; + int h_front_porch; + int v_back_porch; + int v_front_porch; + int hsync_pulse_width; + int vsync_pulse_width; + int hsync_polarity; + int vsync_polarity; + int data_en_polarity; + int hsync_start_x; + int hsync_end_x; + uint8 *buf; + int bpp, ptype; + uint32 format; + struct fb_info *fbi; + struct fb_var_screeninfo *var; + struct msm_fb_data_type *mfd; + struct mdp4_overlay_pipe *pipe; + int ret; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + fbi = mfd->fbi; + var = &fbi->var; + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + if (bpp == 2) + format = MDP_RGB_565; + else if (bpp == 3) + format = MDP_RGB_888; + else + format = MDP_ARGB_8888; + + if (dtv_pipe == NULL) { + ptype = mdp4_overlay_format2type(format); + if (ptype < 0) + printk(KERN_INFO "%s: format2type failed\n", __func__); + pipe = mdp4_overlay_pipe_alloc(ptype, MDP4_MIXER1, 0); + if (pipe == NULL) { + printk(KERN_INFO "%s: pipe_alloc failed\n", __func__); + return -EBUSY; + } + pipe->pipe_used++; + pipe->mixer_stage = MDP4_MIXER_STAGE_BASE; + pipe->mixer_num = MDP4_MIXER1; + pipe->src_format = format; + mdp4_overlay_panel_mode(pipe->mixer_num, MDP4_PANEL_DTV); + ret = mdp4_overlay_format2pipe(pipe); + if (ret < 0) + printk(KERN_INFO "%s: format2type failed\n", __func__); + + dtv_pipe = pipe; /* keep it */ + } else { + pipe = dtv_pipe; + } + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + pipe->src_height = fbi->var.yres; + pipe->src_width = fbi->var.xres; + pipe->src_h = fbi->var.yres; + pipe->src_w = fbi->var.xres; + pipe->src_y = 0; + pipe->src_x = 0; + pipe->srcp0_addr = (uint32) buf; + pipe->srcp0_ystride = fbi->fix.line_length; + + mdp4_overlay_dmae_xy(pipe); /* dma_e */ + mdp4_overlay_dmae_cfg(mfd, 0); + + mdp4_overlay_rgb_setup(pipe); + + mdp4_mixer_stage_up(pipe); + + mdp4_overlayproc_cfg(pipe); + + /* + * DTV timing setting + */ + h_back_porch = var->left_margin; + h_front_porch = var->right_margin; + v_back_porch = var->upper_margin; + v_front_porch = var->lower_margin; + hsync_pulse_width = var->hsync_len; + vsync_pulse_width = var->vsync_len; + dtv_border_clr = mfd->panel_info.lcdc.border_clr; + dtv_underflow_clr = mfd->panel_info.lcdc.underflow_clr; + dtv_hsync_skew = mfd->panel_info.lcdc.hsync_skew; + + pr_info("%s: \n", __func__, + var->reserved[3], var->xres, var->yres, + var->right_margin, var->hsync_len, var->left_margin, + var->lower_margin, var->vsync_len, var->upper_margin, + var->pixclock/1000/1000); + + dtv_width = var->xres; + dtv_height = var->yres; + dtv_bpp = mfd->panel_info.bpp; + + hsync_period = + hsync_pulse_width + h_back_porch + dtv_width + h_front_porch; + hsync_ctrl = (hsync_period << 16) | hsync_pulse_width; + hsync_start_x = hsync_pulse_width + h_back_porch; + hsync_end_x = hsync_period - h_front_porch - 1; + display_hctl = (hsync_end_x << 16) | hsync_start_x; + + vsync_period = + (vsync_pulse_width + v_back_porch + dtv_height + + v_front_porch) * hsync_period; + display_v_start = + (vsync_pulse_width + v_back_porch) * hsync_period + dtv_hsync_skew; + display_v_end = + vsync_period - (v_front_porch * hsync_period) + dtv_hsync_skew - 1; + + if (dtv_width != var->xres) { + active_h_start = hsync_start_x + first_pixel_start_x; + active_h_end = active_h_start + var->xres - 1; + active_hctl = + ACTIVE_START_X_EN | (active_h_end << 16) | active_h_start; + } else { + active_hctl = 0; + } + + if (dtv_height != var->yres) { + active_v_start = + display_v_start + first_pixel_start_y * hsync_period; + active_v_end = active_v_start + (var->yres) * hsync_period - 1; + active_v_start |= ACTIVE_START_Y_EN; + } else { + active_v_start = 0; + active_v_end = 0; + } + + dtv_underflow_clr |= 0x80000000; /* enable recovery */ + hsync_polarity = fbi->var.yres >= 720 ? 0 : 1; + vsync_polarity = fbi->var.yres >= 720 ? 0 : 1; + data_en_polarity = 0; + + ctrl_polarity = + (data_en_polarity << 2) | (vsync_polarity << 1) | (hsync_polarity); + + + MDP_OUTP(MDP_BASE + DTV_BASE + 0x4, hsync_ctrl); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x8, vsync_period); + MDP_OUTP(MDP_BASE + DTV_BASE + 0xc, vsync_pulse_width * hsync_period); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x18, display_hctl); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x1c, display_v_start); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x20, display_v_end); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x40, dtv_border_clr); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x44, dtv_underflow_clr); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x48, dtv_hsync_skew); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x50, ctrl_polarity); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x2c, active_hctl); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x30, active_v_start); + MDP_OUTP(MDP_BASE + DTV_BASE + 0x38, active_v_end); + + /* Test pattern 8 x 8 pixel */ + /* MDP_OUTP(MDP_BASE + DTV_BASE + 0x4C, 0x80000808); */ + + ret = panel_next_on(pdev); + if (ret == 0) { + /* enable DTV block */ + MDP_OUTP(MDP_BASE + DTV_BASE, 1); + mdp_pipe_ctrl(MDP_OVERLAY1_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + dev_info(&pdev->dev, "mdp4_overlay_dtv: on"); + } else { + dev_warn(&pdev->dev, "mdp4_overlay_dtv: panel_next_on failed"); + } + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return ret; +} + +int mdp4_dtv_off(struct platform_device *pdev) +{ + int ret = 0; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + DTV_BASE, 0); + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + mdp_pipe_ctrl(MDP_OVERLAY1_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + /* + * wait for vsync == 16.6 ms to make sure + * the last frame finishes + */ + msleep(20); + pr_info("%s\n", __func__); + + ret = panel_next_off(pdev); + + /* dis-engage rgb2 from mixer1 */ + if (dtv_pipe) + mdp4_mixer_stage_down(dtv_pipe); + + /* + * wait for another vsync == 16.6 ms to make sure + * rgb2 dis-engaged + */ + msleep(20); + + return ret; +} + +static void mdp4_overlay_dtv_wait4vsync(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + + /* enable irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_OVERLAY1_TERM); + INIT_COMPLETION(dtv_pipe->comp); + mfd->dma->waiting = TRUE; + outp32(MDP_INTR_CLEAR, INTR_EXTERNAL_VSYNC); + mdp_intr_mask |= INTR_EXTERNAL_VSYNC; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion_killable(&dtv_pipe->comp); + mdp_disable_irq(MDP_OVERLAY1_TERM); +} + +void mdp4_overlay_dtv_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + + mdp4_overlay_reg_flush(pipe, 1); + if (pipe->flags & MDP_OV_PLAY_NOWAIT) + return; + + mdp4_overlay_dtv_wait4vsync(mfd); +} + +static void mdp4_overlay_dtv_wait4_ov_done(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + + /* enable irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_OVERLAY1_TERM); + INIT_COMPLETION(dtv_pipe->comp); + mfd->dma->waiting = TRUE; + outp32(MDP_INTR_CLEAR, INTR_OVERLAY1_DONE); + mdp_intr_mask |= INTR_OVERLAY1_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion_killable(&dtv_pipe->comp); + mdp_disable_irq(MDP_OVERLAY1_TERM); +} + +void mdp4_overlay_dtv_ov_done_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + + mdp4_overlay_reg_flush(pipe, 1); + if (pipe->flags & MDP_OV_PLAY_NOWAIT) + return; + + mdp4_overlay_dtv_wait4_ov_done(mfd); +} + +void mdp4_external_vsync_dtv() +{ + complete(&dtv_pipe->comp); +} + +/* + * mdp4_overlay1_done_dtv: called from isr + */ +void mdp4_overlay1_done_dtv() +{ + complete(&dtv_pipe->comp); +} + +void mdp4_dtv_overlay(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi = mfd->fbi; + uint8 *buf; + int bpp; + unsigned long flag; + struct mdp4_overlay_pipe *pipe; + + if (!mfd->panel_power_on) + return; + + /* no need to power on cmd block since it's lcdc mode */ + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + mutex_lock(&mfd->dma->ov_mutex); + + pipe = dtv_pipe; + pipe->srcp0_addr = (uint32) buf; + mdp4_overlay_rgb_setup(pipe); + mdp4_overlay_reg_flush(pipe, 1); /* rgb2 and mixer1 */ + + /* enable irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_OVERLAY1_TERM); + INIT_COMPLETION(dtv_pipe->comp); + mfd->dma->waiting = TRUE; + outp32(MDP_INTR_CLEAR, INTR_OVERLAY1_DONE); + mdp_intr_mask |= INTR_OVERLAY1_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion_killable(&dtv_pipe->comp); + mdp_disable_irq(MDP_OVERLAY1_TERM); + + mdp4_stat.kickoff_dtv++; + mdp4_overlay_resource_release(); + mutex_unlock(&mfd->dma->ov_mutex); +} diff --git a/drivers/video/msm/mdp4_overlay_lcdc.c b/drivers/video/msm/mdp4_overlay_lcdc.c new file mode 100644 index 00000000000..ed44d5450c0 --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_lcdc.c @@ -0,0 +1,395 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + +#ifdef CONFIG_FB_MSM_MDP40 +#define LCDC_BASE 0xC0000 +#else +#define LCDC_BASE 0xE0000 +#endif + +int first_pixel_start_x; +int first_pixel_start_y; + +static int writeback_offset; + +static struct mdp4_overlay_pipe *lcdc_pipe; +static struct completion lcdc_comp; + +int mdp_lcdc_on(struct platform_device *pdev) +{ + int lcdc_width; + int lcdc_height; + int lcdc_bpp; + int lcdc_border_clr; + int lcdc_underflow_clr; + int lcdc_hsync_skew; + + int hsync_period; + int hsync_ctrl; + int vsync_period; + int display_hctl; + int display_v_start; + int display_v_end; + int active_hctl; + int active_h_start; + int active_h_end; + int active_v_start; + int active_v_end; + int ctrl_polarity; + int h_back_porch; + int h_front_porch; + int v_back_porch; + int v_front_porch; + int hsync_pulse_width; + int vsync_pulse_width; + int hsync_polarity; + int vsync_polarity; + int data_en_polarity; + int hsync_start_x; + int hsync_end_x; + uint8 *buf; + int bpp, ptype; + struct fb_info *fbi; + struct fb_var_screeninfo *var; + struct msm_fb_data_type *mfd; + struct mdp4_overlay_pipe *pipe; + int ret; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + fbi = mfd->fbi; + var = &fbi->var; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + if (is_mdp4_hw_reset()) { + mdp4_hw_init(); + outpdw(MDP_BASE + 0x0038, mdp4_display_intf); + } + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + if (lcdc_pipe == NULL) { + ptype = mdp4_overlay_format2type(mfd->fb_imgType); + if (ptype < 0) + printk(KERN_INFO "%s: format2type failed\n", __func__); + pipe = mdp4_overlay_pipe_alloc(ptype, MDP4_MIXER0, 0); + if (pipe == NULL) + printk(KERN_INFO "%s: pipe_alloc failed\n", __func__); + pipe->pipe_used++; + pipe->mixer_stage = MDP4_MIXER_STAGE_BASE; + pipe->mixer_num = MDP4_MIXER0; + pipe->src_format = mfd->fb_imgType; + mdp4_overlay_panel_mode(pipe->mixer_num, MDP4_PANEL_LCDC); + ret = mdp4_overlay_format2pipe(pipe); + if (ret < 0) + printk(KERN_INFO "%s: format2pipe failed\n", __func__); + lcdc_pipe = pipe; /* keep it */ + init_completion(&lcdc_comp); + + writeback_offset = mdp4_overlay_writeback_setup( + fbi, pipe, buf, bpp); + } else { + pipe = lcdc_pipe; + } + + pipe->src_height = fbi->var.yres; + pipe->src_width = fbi->var.xres; + pipe->src_h = fbi->var.yres; + pipe->src_w = fbi->var.xres; + pipe->src_y = 0; + pipe->src_x = 0; + pipe->srcp0_addr = (uint32) buf; + pipe->srcp0_ystride = fbi->fix.line_length; + pipe->bpp = bpp; + + mdp4_overlay_dmap_xy(pipe); + mdp4_overlay_dmap_cfg(mfd, 1); + + mdp4_overlay_rgb_setup(pipe); + + mdp4_mixer_stage_up(pipe); + + mdp4_overlayproc_cfg(pipe); + + /* + * LCDC timing setting + */ + h_back_porch = var->left_margin; + h_front_porch = var->right_margin; + v_back_porch = var->upper_margin; + v_front_porch = var->lower_margin; + hsync_pulse_width = var->hsync_len; + vsync_pulse_width = var->vsync_len; + lcdc_border_clr = mfd->panel_info.lcdc.border_clr; + lcdc_underflow_clr = mfd->panel_info.lcdc.underflow_clr; + lcdc_hsync_skew = mfd->panel_info.lcdc.hsync_skew; + + lcdc_width = var->xres; + lcdc_height = var->yres; + lcdc_bpp = mfd->panel_info.bpp; + + hsync_period = + hsync_pulse_width + h_back_porch + lcdc_width + h_front_porch; + hsync_ctrl = (hsync_period << 16) | hsync_pulse_width; + hsync_start_x = hsync_pulse_width + h_back_porch; + hsync_end_x = hsync_period - h_front_porch - 1; + display_hctl = (hsync_end_x << 16) | hsync_start_x; + + vsync_period = + (vsync_pulse_width + v_back_porch + lcdc_height + + v_front_porch) * hsync_period; + display_v_start = + (vsync_pulse_width + v_back_porch) * hsync_period + lcdc_hsync_skew; + display_v_end = + vsync_period - (v_front_porch * hsync_period) + lcdc_hsync_skew - 1; + + if (lcdc_width != var->xres) { + active_h_start = hsync_start_x + first_pixel_start_x; + active_h_end = active_h_start + var->xres - 1; + active_hctl = + ACTIVE_START_X_EN | (active_h_end << 16) | active_h_start; + } else { + active_hctl = 0; + } + + if (lcdc_height != var->yres) { + active_v_start = + display_v_start + first_pixel_start_y * hsync_period; + active_v_end = active_v_start + (var->yres) * hsync_period - 1; + active_v_start |= ACTIVE_START_Y_EN; + } else { + active_v_start = 0; + active_v_end = 0; + } + + +#ifdef CONFIG_FB_MSM_MDP40 + hsync_polarity = 1; + vsync_polarity = 1; + lcdc_underflow_clr |= 0x80000000; /* enable recovery */ +#else + hsync_polarity = 0; + vsync_polarity = 0; +#endif + data_en_polarity = 0; + + ctrl_polarity = + (data_en_polarity << 2) | (vsync_polarity << 1) | (hsync_polarity); + + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x4, hsync_ctrl); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x8, vsync_period); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0xc, vsync_pulse_width * hsync_period); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x10, display_hctl); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x14, display_v_start); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x18, display_v_end); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x28, lcdc_border_clr); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x2c, lcdc_underflow_clr); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x30, lcdc_hsync_skew); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x38, ctrl_polarity); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x1c, active_hctl); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x20, active_v_start); + MDP_OUTP(MDP_BASE + LCDC_BASE + 0x24, active_v_end); + + mdp4_overlay_reg_flush(pipe, 1); +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(2); +#endif + mdp_histogram_ctrl(TRUE); + + ret = panel_next_on(pdev); + if (ret == 0) { + /* enable LCDC block */ + MDP_OUTP(MDP_BASE + LCDC_BASE, 1); + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + } + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return ret; +} + +int mdp_lcdc_off(struct platform_device *pdev) +{ + int ret = 0; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + LCDC_BASE, 0); + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + mdp_histogram_ctrl(FALSE); + ret = panel_next_off(pdev); + + /* delay to make sure the last frame finishes */ + msleep(16); + +#ifdef LCDC_RGB_UNSTAGE + /* dis-engage rgb0 from mixer0 */ + if (lcdc_pipe) + mdp4_mixer_stage_down(lcdc_pipe); +#endif +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(0); +#endif + + return ret; +} + + +#ifdef CONFIG_FB_MSM_OVERLAY_WRITEBACK +int mdp4_lcdc_overlay_blt_offset(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + req->offset = writeback_offset; + req->width = lcdc_pipe->src_width; + req->height = lcdc_pipe->src_height; + req->bpp = lcdc_pipe->bpp; + + return sizeof(*req); +} + +void mdp4_lcdc_overlay_blt(struct msm_fb_data_type *mfd, + struct msmfb_overlay_blt *req) +{ + unsigned long flag; + int change = 0; + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (req->enable && lcdc_pipe->blt_addr == 0) { + lcdc_pipe->blt_addr = lcdc_pipe->blt_base; + change++; + } else if (req->enable == 0 && lcdc_pipe->blt_addr) { + lcdc_pipe->blt_addr = 0; + change++; + } + pr_debug("%s: blt_addr=%x\n", __func__, (int)lcdc_pipe->blt_addr); + lcdc_pipe->blt_cnt = 0; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + if (!change) + return; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + LCDC_BASE, 0); /* stop lcdc */ + msleep(50); + mdp4_overlayproc_cfg(lcdc_pipe); + mdp4_overlay_dmap_xy(lcdc_pipe); + MDP_OUTP(MDP_BASE + LCDC_BASE, 1); /* start lcdc */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} +#endif + +void mdp4_overlay_lcdc_wait4vsync(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + + /* enable irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_DMA2_TERM); /* enable intr */ + INIT_COMPLETION(lcdc_comp); + mfd->dma->waiting = TRUE; + outp32(MDP_INTR_CLEAR, INTR_PRIMARY_VSYNC); + mdp_intr_mask |= INTR_PRIMARY_VSYNC; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion_killable(&lcdc_comp); + mdp_disable_irq(MDP_DMA2_TERM); +} + +void mdp4_overlay_lcdc_vsync_push(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + + mdp4_overlay_reg_flush(pipe, 1); + if (pipe->flags & MDP_OV_PLAY_NOWAIT) + return; + + mdp4_overlay_lcdc_wait4vsync(mfd); +} + +/* + * mdp4_primary_vsync_lcdc: called from isr + */ +void mdp4_primary_vsync_lcdc(void) +{ + complete_all(&lcdc_comp); +} + +/* + * mdp4_overlay0_done_lcdc: called from isr + */ +void mdp4_overlay0_done_lcdc(void) +{ + complete_all(&lcdc_comp); +} + +void mdp4_lcdc_overlay(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi = mfd->fbi; + uint8 *buf; + int bpp; + struct mdp4_overlay_pipe *pipe; + + if (!mfd->panel_power_on) + return; + + /* no need to power on cmd block since it's lcdc mode */ + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + mutex_lock(&mfd->dma->ov_mutex); + + pipe = lcdc_pipe; + pipe->srcp0_addr = (uint32) buf; + mdp4_overlay_rgb_setup(pipe); + mutex_unlock(&mfd->dma->ov_mutex); + mdp4_overlay_lcdc_vsync_push(mfd, pipe); + mdp4_stat.kickoff_lcdc++; + mdp4_overlay_resource_release(); +} diff --git a/drivers/video/msm/mdp4_overlay_mddi.c b/drivers/video/msm/mdp4_overlay_mddi.c new file mode 100644 index 00000000000..2bf9fafacf5 --- /dev/null +++ b/drivers/video/msm/mdp4_overlay_mddi.c @@ -0,0 +1,612 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + +static struct mdp4_overlay_pipe *mddi_pipe; +static struct msm_fb_data_type *mddi_mfd; +static int busy_wait_cnt; + +static int vsync_start_y_adjust = 4; + +static int dmap_vsync_enable; + +void mdp_dmap_vsync_set(int enable) +{ + dmap_vsync_enable = enable; +} + +int mdp_dmap_vsync_get(void) +{ + return dmap_vsync_enable; +} + +void mdp4_mddi_vsync_enable(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe, int which) +{ + uint32 start_y, data, tear_en; + + tear_en = (1 << which); + + if ((mfd->use_mdp_vsync) && (mfd->ibuf.vsync_enable) && + (mfd->panel_info.lcd.vsync_enable)) { + + if (mdp_hw_revision < MDP4_REVISION_V2_1) { + /* need dmas dmap switch */ + if (which == 0 && dmap_vsync_enable == 0 && + mfd->panel_info.lcd.rev < 2) /* dma_p */ + return; + } + + if (vsync_start_y_adjust <= pipe->dst_y) + start_y = pipe->dst_y - vsync_start_y_adjust; + else + start_y = (mfd->total_lcd_lines - 1) - + (vsync_start_y_adjust - pipe->dst_y); + if (which == 0) + MDP_OUTP(MDP_BASE + 0x210, start_y); /* primary */ + else + MDP_OUTP(MDP_BASE + 0x214, start_y); /* secondary */ + + data = inpdw(MDP_BASE + 0x20c); + data |= tear_en; + MDP_OUTP(MDP_BASE + 0x20c, data); + } else { + data = inpdw(MDP_BASE + 0x20c); + data &= ~tear_en; + MDP_OUTP(MDP_BASE + 0x20c, data); + } +} + +#define WHOLESCREEN + +void mdp4_overlay_update_lcd(struct msm_fb_data_type *mfd) +{ + MDPIBUF *iBuf = &mfd->ibuf; + uint8 *src; + int ptype; + uint32 mddi_ld_param; + uint16 mddi_vdo_packet_reg; + struct mdp4_overlay_pipe *pipe; + int ret; + + if (mfd->key != MFD_KEY) + return; + + mddi_mfd = mfd; /* keep it */ + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + if (mddi_pipe == NULL) { + ptype = mdp4_overlay_format2type(mfd->fb_imgType); + if (ptype < 0) + printk(KERN_INFO "%s: format2type failed\n", __func__); + pipe = mdp4_overlay_pipe_alloc(ptype, MDP4_MIXER0, 0); + if (pipe == NULL) + printk(KERN_INFO "%s: pipe_alloc failed\n", __func__); + pipe->pipe_used++; + pipe->mixer_num = MDP4_MIXER0; + pipe->src_format = mfd->fb_imgType; + mdp4_overlay_panel_mode(pipe->mixer_num, MDP4_PANEL_MDDI); + ret = mdp4_overlay_format2pipe(pipe); + if (ret < 0) + printk(KERN_INFO "%s: format2type failed\n", __func__); + + mddi_pipe = pipe; /* keep it */ + mddi_pipe->blt_end = 1; /* mark as end */ + mddi_ld_param = 0; + mddi_vdo_packet_reg = mfd->panel_info.mddi.vdopkt; + + if (mdp_hw_revision == MDP4_REVISION_V2_1) { + uint32 data; + + data = inpdw(MDP_BASE + 0x0028); + data &= ~0x0300; /* bit 8, 9, MASTER4 */ + if (mfd->fbi->var.xres == 540) /* qHD, 540x960 */ + data |= 0x0200; + else + data |= 0x0100; + + MDP_OUTP(MDP_BASE + 0x00028, data); + } + + if (mfd->panel_info.type == MDDI_PANEL) { + if (mfd->panel_info.pdest == DISPLAY_1) + mddi_ld_param = 0; + else + mddi_ld_param = 1; + } else { + mddi_ld_param = 2; + } + + MDP_OUTP(MDP_BASE + 0x00090, mddi_ld_param); + + if (mfd->panel_info.bpp == 24) + MDP_OUTP(MDP_BASE + 0x00094, + (MDDI_VDO_PACKET_DESC_24 << 16) | mddi_vdo_packet_reg); + else if (mfd->panel_info.bpp == 16) + MDP_OUTP(MDP_BASE + 0x00094, + (MDDI_VDO_PACKET_DESC_16 << 16) | mddi_vdo_packet_reg); + else + MDP_OUTP(MDP_BASE + 0x00094, + (MDDI_VDO_PACKET_DESC << 16) | mddi_vdo_packet_reg); + + MDP_OUTP(MDP_BASE + 0x00098, 0x01); + } else { + pipe = mddi_pipe; + } + + /* 0 for dma_p, client_id = 0 */ + MDP_OUTP(MDP_BASE + 0x00090, 0); + + + src = (uint8 *) iBuf->buf; + +#ifdef WHOLESCREEN + + { + struct fb_info *fbi; + + fbi = mfd->fbi; + pipe->src_height = fbi->var.yres; + pipe->src_width = fbi->var.xres; + pipe->src_h = fbi->var.yres; + pipe->src_w = fbi->var.xres; + pipe->src_y = 0; + pipe->src_x = 0; + pipe->dst_h = fbi->var.yres; + pipe->dst_w = fbi->var.xres; + pipe->dst_y = 0; + pipe->dst_x = 0; + pipe->srcp0_addr = (uint32)src; + pipe->srcp0_ystride = fbi->fix.line_length; + } + +#else + if (mdp4_overlay_active(MDP4_MIXER0)) { + struct fb_info *fbi; + + fbi = mfd->fbi; + pipe->src_height = fbi->var.yres; + pipe->src_width = fbi->var.xres; + pipe->src_h = fbi->var.yres; + pipe->src_w = fbi->var.xres; + pipe->src_y = 0; + pipe->src_x = 0; + pipe->dst_h = fbi->var.yres; + pipe->dst_w = fbi->var.xres; + pipe->dst_y = 0; + pipe->dst_x = 0; + pipe->srcp0_addr = (uint32) src; + pipe->srcp0_ystride = fbi->fix.line_length; + } else { + /* starting input address */ + src += (iBuf->dma_x + iBuf->dma_y * iBuf->ibuf_width) + * iBuf->bpp; + + pipe->src_height = iBuf->dma_h; + pipe->src_width = iBuf->dma_w; + pipe->src_h = iBuf->dma_h; + pipe->src_w = iBuf->dma_w; + pipe->src_y = 0; + pipe->src_x = 0; + pipe->dst_h = iBuf->dma_h; + pipe->dst_w = iBuf->dma_w; + pipe->dst_y = iBuf->dma_y; + pipe->dst_x = iBuf->dma_x; + pipe->srcp0_addr = (uint32) src; + pipe->srcp0_ystride = iBuf->ibuf_width * iBuf->bpp; + } +#endif + + pipe->mixer_stage = MDP4_MIXER_STAGE_BASE; + + mdp4_overlay_rgb_setup(pipe); + + mdp4_mixer_stage_up(pipe); + + mdp4_overlayproc_cfg(pipe); + + mdp4_overlay_dmap_xy(pipe); + + mdp4_overlay_dmap_cfg(mfd, 0); + + mdp4_mddi_vsync_enable(mfd, pipe, 0); + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + +} + +int mdp4_mddi_overlay_blt_offset(int *off) +{ + if (mdp_hw_revision < MDP4_REVISION_V2_1) { /* need dmas dmap switch */ + if (mddi_pipe->blt_end || + (mdp4_overlay_mixer_play(mddi_pipe->mixer_num) == 0)) { + *off = -1; + return -EINVAL; + } + } else { + /* no dmas dmap switch */ + if (mddi_pipe->blt_end) { + *off = -1; + return -EINVAL; + } + } + + if (mddi_pipe->blt_cnt & 0x01) + *off = mddi_pipe->src_height * mddi_pipe->src_width * 3; + else + *off = 0; + + return 0; +} + +void mdp4_mddi_overlay_blt(ulong addr) +{ + unsigned long flag; + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (addr) { + mdp_pipe_ctrl(MDP_DMA2_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + mdp_intr_mask |= INTR_DMA_P_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + mdp_pipe_ctrl(MDP_DMA2_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + mddi_pipe->blt_cnt = 0; + mddi_pipe->blt_end = 0; + mddi_pipe->blt_addr = addr; + } else { + mddi_pipe->blt_end = 1; /* mark as end */ + } + spin_unlock_irqrestore(&mdp_spin_lock, flag); +} + +void mdp4_blt_xy_update(struct mdp4_overlay_pipe *pipe) +{ + uint32 off, addr; + int bpp; + char *overlay_base; + + + if (pipe->blt_addr == 0) + return; + + +#ifdef BLT_RGB565 + bpp = 2; /* overlay ouput is RGB565 */ +#else + bpp = 3; /* overlay ouput is RGB888 */ +#endif + off = 0; + if (pipe->dmap_cnt & 0x01) + off = pipe->src_height * pipe->src_width * bpp; + + addr = pipe->blt_addr + off; + + /* dmap */ + MDP_OUTP(MDP_BASE + 0x90008, addr); + + /* overlay 0 */ + overlay_base = MDP_BASE + MDP4_OVERLAYPROC0_BASE;/* 0x10000 */ + outpdw(overlay_base + 0x000c, addr); + outpdw(overlay_base + 0x001c, addr); +} + +/* + * mdp4_dmap_done_mddi: called from isr + */ +void mdp4_dma_p_done_mddi(void) +{ + if (mddi_pipe->blt_end) { + mddi_pipe->blt_addr = 0; + mdp_intr_mask &= ~INTR_DMA_P_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + mdp4_overlayproc_cfg(mddi_pipe); + mdp4_overlay_dmap_xy(mddi_pipe); + } + + /* + * single buffer, no need to increase + * mdd_pipe->dmap_cnt here + */ +} + +/* + * mdp4_overlay0_done_mddi: called from isr + */ +void mdp4_overlay0_done_mddi(struct mdp_dma_data *dma) +{ + mdp_disable_irq_nosync(MDP_OVERLAY0_TERM); + + dma->busy = FALSE; + complete(&dma->comp); + mdp_pipe_ctrl(MDP_OVERLAY0_BLOCK, + MDP_BLOCK_POWER_OFF, TRUE); + + if (busy_wait_cnt) + busy_wait_cnt--; + + pr_debug("%s: ISR-done\n", __func__); + + if (mddi_pipe->blt_addr) { + if (mddi_pipe->blt_cnt == 0) { + mdp4_overlayproc_cfg(mddi_pipe); + mdp4_overlay_dmap_xy(mddi_pipe); + mddi_pipe->ov_cnt = 0; + mddi_pipe->dmap_cnt = 0; + /* BLT start from next frame */ + } else { + mdp_pipe_ctrl(MDP_DMA2_BLOCK, MDP_BLOCK_POWER_ON, + FALSE); + mdp4_blt_xy_update(mddi_pipe); + outpdw(MDP_BASE + 0x000c, 0x0); /* start DMAP */ + } + mddi_pipe->blt_cnt++; + mddi_pipe->ov_cnt++; + } + + + +} + +void mdp4_mddi_overlay_restore(void) +{ + if (mddi_mfd == NULL) + return; + + pr_debug("%s: resotre, pid=%d\n", __func__, current->pid); + + if (mddi_mfd->panel_power_on == 0) + return; + if (mddi_mfd && mddi_pipe) { + mdp4_mddi_dma_busy_wait(mddi_mfd); + mdp4_overlay_update_lcd(mddi_mfd); + mdp4_mddi_overlay_kickoff(mddi_mfd, mddi_pipe); + mddi_mfd->dma_update_flag = 1; + } + if (mdp_hw_revision < MDP4_REVISION_V2_1) /* need dmas dmap switch */ + mdp4_mddi_overlay_dmas_restore(); +} + +/* + * mdp4_mddi_cmd_dma_busy_wait: check mddi link activity + * dsi link is a shared resource and it can only be used + * while it is in idle state. + * ov_mutex need to be acquired before call this function. + */ +void mdp4_mddi_dma_busy_wait(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + int need_wait = 0; + + pr_debug("%s: START, pid=%d\n", __func__, current->pid); + spin_lock_irqsave(&mdp_spin_lock, flag); + if (mfd->dma->busy == TRUE) { + if (busy_wait_cnt == 0) + INIT_COMPLETION(mfd->dma->comp); + busy_wait_cnt++; + need_wait++; + } + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + + if (need_wait) { + /* wait until DMA finishes the current job */ + pr_debug("%s: PENDING, pid=%d\n", __func__, current->pid); + wait_for_completion(&mfd->dma->comp); + } + pr_debug("%s: DONE, pid=%d\n", __func__, current->pid); +} + +void mdp4_mddi_kickoff_video(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + pr_debug("%s: pid=%d\n", __func__, current->pid); + mdp4_mddi_overlay_kickoff(mfd, pipe); +} + +void mdp4_mddi_kickoff_ui(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + pr_debug("%s: pid=%d\n", __func__, current->pid); + mdp4_mddi_overlay_kickoff(mfd, pipe); +} + + +void mdp4_mddi_overlay_kickoff(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + if (mdp_hw_revision == MDP4_REVISION_V2_1) { + if (mdp4_overlay_status_read(MDP4_OVERLAY_TYPE_UNSET)) { + uint32 data; + data = inpdw(MDP_BASE + 0x0028); + data &= ~0x0300; /* bit 8, 9, MASTER4 */ + if (mfd->fbi->var.xres == 540) /* qHD, 540x960 */ + data |= 0x0200; + else + data |= 0x0100; + MDP_OUTP(MDP_BASE + 0x00028, data); + mdp4_overlay_status_write(MDP4_OVERLAY_TYPE_UNSET, + false); + } + if (mdp4_overlay_status_read(MDP4_OVERLAY_TYPE_SET)) { + uint32 data; + data = inpdw(MDP_BASE + 0x0028); + data &= ~0x0300; /* bit 8, 9, MASTER4 */ + MDP_OUTP(MDP_BASE + 0x00028, data); + mdp4_overlay_status_write(MDP4_OVERLAY_TYPE_SET, false); + } + } + mdp_enable_irq(MDP_OVERLAY0_TERM); + mfd->dma->busy = TRUE; + /* start OVERLAY pipe */ + mdp_pipe_kickoff(MDP_OVERLAY0_TERM, mfd); +} + +void mdp4_dma_s_update_lcd(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + MDPIBUF *iBuf = &mfd->ibuf; + uint32 outBpp = iBuf->bpp; + uint16 mddi_vdo_packet_reg; + uint32 dma_s_cfg_reg; + + dma_s_cfg_reg = 0; + + if (mfd->fb_imgType == MDP_RGBA_8888) + dma_s_cfg_reg |= DMA_PACK_PATTERN_BGR; /* on purpose */ + else if (mfd->fb_imgType == MDP_BGR_565) + dma_s_cfg_reg |= DMA_PACK_PATTERN_BGR; + else + dma_s_cfg_reg |= DMA_PACK_PATTERN_RGB; + + if (outBpp == 4) + dma_s_cfg_reg |= (1 << 26); /* xRGB8888 */ + else if (outBpp == 2) + dma_s_cfg_reg |= DMA_IBUF_FORMAT_RGB565; + + dma_s_cfg_reg |= DMA_DITHER_EN; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + /* PIXELSIZE */ + MDP_OUTP(MDP_BASE + 0xa0004, (pipe->dst_h << 16 | pipe->dst_w)); + MDP_OUTP(MDP_BASE + 0xa0008, pipe->srcp0_addr); /* ibuf address */ + MDP_OUTP(MDP_BASE + 0xa000c, pipe->srcp0_ystride);/* ystride */ + + if (mfd->panel_info.bpp == 24) { + dma_s_cfg_reg |= DMA_DSTC0G_8BITS | /* 666 18BPP */ + DMA_DSTC1B_8BITS | DMA_DSTC2R_8BITS; + } else if (mfd->panel_info.bpp == 18) { + dma_s_cfg_reg |= DMA_DSTC0G_6BITS | /* 666 18BPP */ + DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + } else { + dma_s_cfg_reg |= DMA_DSTC0G_6BITS | /* 565 16BPP */ + DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; + } + + MDP_OUTP(MDP_BASE + 0xa0010, (pipe->dst_y << 16) | pipe->dst_x); + + /* 1 for dma_s, client_id = 0 */ + MDP_OUTP(MDP_BASE + 0x00090, 1); + + mddi_vdo_packet_reg = mfd->panel_info.mddi.vdopkt; + + if (mfd->panel_info.bpp == 24) + MDP_OUTP(MDP_BASE + 0x00094, + (MDDI_VDO_PACKET_DESC_24 << 16) | mddi_vdo_packet_reg); + else if (mfd->panel_info.bpp == 16) + MDP_OUTP(MDP_BASE + 0x00094, + (MDDI_VDO_PACKET_DESC_16 << 16) | mddi_vdo_packet_reg); + else + MDP_OUTP(MDP_BASE + 0x00094, + (MDDI_VDO_PACKET_DESC << 16) | mddi_vdo_packet_reg); + + MDP_OUTP(MDP_BASE + 0x00098, 0x01); + + MDP_OUTP(MDP_BASE + 0xa0000, dma_s_cfg_reg); + + mdp4_mddi_vsync_enable(mfd, pipe, 1); + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_mddi_dma_s_kickoff(struct msm_fb_data_type *mfd, + struct mdp4_overlay_pipe *pipe) +{ + mdp_enable_irq(MDP_DMA_S_TERM); + mfd->dma->busy = TRUE; + mfd->ibuf_flushed = TRUE; + /* start dma_s pipe */ + mdp_pipe_kickoff(MDP_DMA_S_TERM, mfd); + + /* wait until DMA finishes the current job */ + wait_for_completion(&mfd->dma->comp); + mdp_disable_irq(MDP_DMA_S_TERM); +} + +void mdp4_mddi_overlay_dmas_restore(void) +{ + /* mutex held by caller */ + if (mddi_mfd && mddi_pipe) { + mdp4_mddi_dma_busy_wait(mddi_mfd); + mdp4_dma_s_update_lcd(mddi_mfd, mddi_pipe); + mdp4_mddi_dma_s_kickoff(mddi_mfd, mddi_pipe); + mddi_mfd->dma_update_flag = 1; + } +} + +void mdp4_mddi_overlay(struct msm_fb_data_type *mfd) +{ + mutex_lock(&mfd->dma->ov_mutex); + + if (mfd && mfd->panel_power_on) { + mdp4_mddi_dma_busy_wait(mfd); + mdp4_overlay_update_lcd(mfd); + + if (mdp_hw_revision < MDP4_REVISION_V2_1) { + /* dmas dmap switch */ + if (mdp4_overlay_mixer_play(mddi_pipe->mixer_num) + == 0) { + mdp4_dma_s_update_lcd(mfd, mddi_pipe); + mdp4_mddi_dma_s_kickoff(mfd, mddi_pipe); + } else + mdp4_mddi_kickoff_ui(mfd, mddi_pipe); + } else /* no dams dmap switch */ + mdp4_mddi_kickoff_ui(mfd, mddi_pipe); + + mdp4_stat.kickoff_mddi++; + + /* signal if pan function is waiting for the update completion */ + if (mfd->pan_waiting) { + mfd->pan_waiting = FALSE; + complete(&mfd->pan_comp); + } + } + mdp4_overlay_resource_release(); + mutex_unlock(&mfd->dma->ov_mutex); +} + +int mdp4_mddi_overlay_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + struct msm_fb_data_type *mfd = info->par; + mutex_lock(&mfd->dma->ov_mutex); + if (mfd && mfd->panel_power_on) { + mdp4_mddi_dma_busy_wait(mfd); + mdp_hw_cursor_update(info, cursor); + } + mutex_unlock(&mfd->dma->ov_mutex); + return 0; +} diff --git a/drivers/video/msm/mdp4_util.c b/drivers/video/msm/mdp4_util.c new file mode 100644 index 00000000000..52e4a828a6e --- /dev/null +++ b/drivers/video/msm/mdp4_util.c @@ -0,0 +1,2101 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + +struct mdp4_statistic mdp4_stat; + +unsigned is_mdp4_hw_reset(void) +{ + unsigned hw_reset = 0; + + /* Only revisions > v2.1 may be reset or powered off/on at runtime */ + if (mdp_hw_revision > MDP4_REVISION_V2_1) { + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + hw_reset = !inpdw(MDP_BASE + 0x003c); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + } + + return hw_reset; +} + +void mdp4_sw_reset(ulong bits) +{ + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + bits &= 0x1f; /* 5 bits */ + outpdw(MDP_BASE + 0x001c, bits); /* MDP_SW_RESET */ + + while (inpdw(MDP_BASE + 0x001c) & bits) /* self clear when complete */ + ; + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + MSM_FB_DEBUG("mdp4_sw_reset: 0x%x\n", (int)bits); +} + +void mdp4_overlay_cfg(int overlayer, int blt_mode, int refresh, int direct_out) +{ + ulong bits = 0; + + if (blt_mode) + bits |= (1 << 3); + refresh &= 0x03; /* 2 bites */ + bits |= (refresh << 1); + direct_out &= 0x01; + bits |= direct_out; + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + + if (overlayer == MDP4_MIXER0) + outpdw(MDP_BASE + 0x10004, bits); /* MDP_OVERLAY0_CFG */ + else + outpdw(MDP_BASE + 0x18004, bits); /* MDP_OVERLAY1_CFG */ + + MSM_FB_DEBUG("mdp4_overlay_cfg: 0x%x\n", + (int)inpdw(MDP_BASE + 0x10004)); + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_display_intf_sel(int output, ulong intf) +{ + ulong bits, mask, data; + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + bits = inpdw(MDP_BASE + 0x0038); /* MDP_DISP_INTF_SEL */ + + if (intf == DSI_VIDEO_INTF) { + data = 0x40; /* bit 6 */ + intf = MDDI_LCDC_INTF; + if (output == SECONDARY_INTF_SEL) { + MSM_FB_INFO("%s: Illegal INTF selected, output=%d \ + intf=%d\n", __func__, output, (int)intf); + } + } else if (intf == DSI_CMD_INTF) { + data = 0x80; /* bit 7 */ + intf = MDDI_INTF; + if (output == EXTERNAL_INTF_SEL) { + MSM_FB_INFO("%s: Illegal INTF selected, output=%d \ + intf=%d\n", __func__, output, (int)intf); + } + } else + data = 0; + + mask = 0x03; /* 2 bits */ + intf &= 0x03; /* 2 bits */ + + switch (output) { + case EXTERNAL_INTF_SEL: + intf <<= 4; + mask <<= 4; + break; + case SECONDARY_INTF_SEL: + intf &= 0x02; /* only MDDI and EBI2 support */ + intf <<= 2; + mask <<= 2; + break; + default: + break; + } + + intf |= data; + mask |= data; + + bits &= ~mask; + bits |= intf; + + outpdw(MDP_BASE + 0x0038, bits); /* MDP_DISP_INTF_SEL */ + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + MSM_FB_DEBUG("mdp4_display_intf_sel: 0x%x\n", (int)inpdw(MDP_BASE + 0x0038)); +} + +unsigned long mdp4_display_status(void) +{ + ulong status; + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + status = inpdw(MDP_BASE + 0x0018) & 0x3ff; /* MDP_DISPLAY_STATUS */ + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return status; +} + +void mdp4_ebi2_lcd_setup(int lcd, ulong base, int ystride) +{ + /* always use memory map */ + ystride &= 0x01fff; /* 13 bits */ + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + if (lcd == EBI2_LCD0) { + outpdw(MDP_BASE + 0x0060, base);/* MDP_EBI2_LCD0 */ + outpdw(MDP_BASE + 0x0068, ystride);/* MDP_EBI2_LCD0_YSTRIDE */ + } else { + outpdw(MDP_BASE + 0x0064, base);/* MDP_EBI2_LCD1 */ + outpdw(MDP_BASE + 0x006c, ystride);/* MDP_EBI2_LCD1_YSTRIDE */ + } + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_mddi_setup(int mddi, unsigned long id) +{ + ulong bits; + + if (mddi == MDDI_EXTERNAL_SET) + bits = 0x02; + else if (mddi == MDDI_SECONDARY_SET) + bits = 0x01; + else + bits = 0; /* PRIMARY_SET */ + + id <<= 16; + + bits |= id; + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + outpdw(MDP_BASE + 0x0090, bits); /* MDP_MDDI_PARAM_WR_SEL */ + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +int mdp_ppp_blit(struct fb_info *info, struct mdp_blit_req *req) +{ + + /* not implemented yet */ + return -1; +} + +void mdp4_fetch_cfg(uint32 core_clk) +{ + + uint32 dmap_data, vg_data; + char *base; + int i; + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + if (core_clk >= 90000000) { /* 90 Mhz */ + dmap_data = 0x47; /* 16 bytes-burst x 8 req */ + vg_data = 0x47; /* 16 bytes-burs x 8 req */ + } else { + dmap_data = 0x27; /* 8 bytes-burst x 8 req */ + vg_data = 0x43; /* 16 bytes-burst x 4 req */ + } + + MSM_FB_DEBUG("mdp4_fetch_cfg: dmap=%x vg=%x\n", + dmap_data, vg_data); + + /* dma_p fetch config */ + outpdw(MDP_BASE + 0x91004, dmap_data); + /* dma_e fetch config */ + outpdw(MDP_BASE + 0xB1004, dmap_data); + + /* + * set up two vg pipes and two rgb pipes + */ + base = MDP_BASE + MDP4_VIDEO_BASE; + for (i = 0; i < 4; i++) { + outpdw(base + 0x1004, vg_data); + base += MDP4_VIDEO_OFF; + } + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_hw_init(void) +{ + ulong bits; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + +#ifdef MDP4_ERROR + /* + * Issue software reset on DMA_P will casue DMA_P dma engine stall + * on LCDC mode. However DMA_P does not stall at MDDI mode. + * This need further investigation. + */ + mdp4_sw_reset(0x17); +#endif + + mdp4_clear_lcdc(); + + mdp4_mixer_blend_init(0); + mdp4_mixer_blend_init(1); + mdp4_vg_qseed_init(0); + mdp4_vg_qseed_init(1); + + /* yuv2rgb */ + mdp4_vg_csc_mv_setup(0); + mdp4_vg_csc_mv_setup(1); + mdp4_vg_csc_pre_bv_setup(0); + mdp4_vg_csc_pre_bv_setup(1); + mdp4_vg_csc_post_bv_setup(0); + mdp4_vg_csc_post_bv_setup(1); + mdp4_vg_csc_pre_lv_setup(0); + mdp4_vg_csc_pre_lv_setup(1); + mdp4_vg_csc_post_lv_setup(0); + mdp4_vg_csc_post_lv_setup(1); + + /* rgb2yuv */ + mdp4_mixer1_csc_mv_setup(); + mdp4_mixer1_csc_pre_bv_setup(); + mdp4_mixer1_csc_post_bv_setup(); + mdp4_mixer1_csc_pre_lv_setup(); + mdp4_mixer1_csc_post_lv_setup(); + + mdp4_vg_igc_lut_setup(0); + mdp4_vg_igc_lut_setup(1); + + mdp4_rgb_igc_lut_setup(0); + mdp4_rgb_igc_lut_setup(1); + + outp32(MDP_EBI2_PORTMAP_MODE, 0x3); + + /* system interrupts */ + + bits = mdp_intr_mask; + outpdw(MDP_BASE + 0x0050, bits);/* enable specififed interrupts */ + + /* histogram */ + MDP_OUTP(MDP_BASE + 0x95010, 1); /* auto clear HIST */ + + /* enable histogram interrupts */ + outpdw(MDP_BASE + 0x9501c, INTR_HIST_DONE); + + /* For the max read pending cmd config below, if the MDP clock */ + /* is less than the AXI clock, then we must use 3 pending */ + /* pending requests. Otherwise, we should use 8 pending requests. */ + /* In the future we should do this detection automatically. */ + + /* max read pending cmd config */ + outpdw(MDP_BASE + 0x004c, 0x02222); /* 3 pending requests */ + +#ifndef CONFIG_FB_MSM_OVERLAY + /* both REFRESH_MODE and DIRECT_OUT are ignored at BLT mode */ + mdp4_overlay_cfg(MDP4_MIXER0, OVERLAY_MODE_BLT, 0, 0); + mdp4_overlay_cfg(MDP4_MIXER1, OVERLAY_MODE_BLT, 0, 0); +#endif + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + /* Mark hardware as initialized. Only revisions > v2.1 have a register + * for tracking core reset status. */ + if (mdp_hw_revision > MDP4_REVISION_V2_1) + outpdw(MDP_BASE + 0x003c, 1); +} + + +void mdp4_clear_lcdc(void) +{ + uint32 bits; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + bits = inpdw(MDP_BASE + 0xc0000); + if (bits & 0x01) { /* enabled already */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + return; + } + + outpdw(MDP_BASE + 0xc0004, 0); /* vsync ctrl out */ + outpdw(MDP_BASE + 0xc0008, 0); /* vsync period */ + outpdw(MDP_BASE + 0xc000c, 0); /* vsync pusle width */ + outpdw(MDP_BASE + 0xc0010, 0); /* lcdc display HCTL */ + outpdw(MDP_BASE + 0xc0014, 0); /* lcdc display v start */ + outpdw(MDP_BASE + 0xc0018, 0); /* lcdc display v end */ + outpdw(MDP_BASE + 0xc001c, 0); /* lcdc active hctl */ + outpdw(MDP_BASE + 0xc0020, 0); /* lcdc active v start */ + outpdw(MDP_BASE + 0xc0024, 0); /* lcdc active v end */ + outpdw(MDP_BASE + 0xc0028, 0); /* lcdc board color */ + outpdw(MDP_BASE + 0xc002c, 0); /* lcdc underflow ctrl */ + outpdw(MDP_BASE + 0xc0030, 0); /* lcdc hsync skew */ + outpdw(MDP_BASE + 0xc0034, 0); /* lcdc test ctl */ + outpdw(MDP_BASE + 0xc0038, 0); /* lcdc ctl polarity */ + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +irqreturn_t mdp4_isr(int irq, void *ptr) +{ + uint32 isr, mask, panel; + struct mdp_dma_data *dma; + + mdp_is_in_isr = TRUE; + + /* complete all the reads before reading the interrupt + * status register - eliminate effects of speculative + * reads by the cpu + */ + rmb(); + isr = inpdw(MDP_INTR_STATUS); + if (isr == 0) + goto out; + + mdp4_stat.intr_tot++; + mask = inpdw(MDP_INTR_ENABLE); + outpdw(MDP_INTR_CLEAR, isr); + + if (isr & INTR_PRIMARY_INTF_UDERRUN) { + mdp4_stat.intr_underrun_p++; + /* When underun occurs mdp clear the histogram registers + that are set before in hw_init so restore them back so + that histogram works.*/ + MDP_OUTP(MDP_BASE + 0x95010, 1); + outpdw(MDP_BASE + 0x9501c, INTR_HIST_DONE); + if (mdp_is_hist_start == TRUE) { + MDP_OUTP(MDP_BASE + 0x95004, + mdp_hist.frame_cnt); + MDP_OUTP(MDP_BASE + 0x95000, 1); + } + } + + if (isr & INTR_EXTERNAL_INTF_UDERRUN) + mdp4_stat.intr_underrun_e++; + + isr &= mask; + + if (isr == 0) + goto out; + + panel = mdp4_overlay_panel_list(); + if (isr & INTR_PRIMARY_VSYNC) { + dma = &dma2_data; + spin_lock(&mdp_spin_lock); + mdp_intr_mask &= ~INTR_PRIMARY_VSYNC; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + dma->waiting = FALSE; + spin_unlock(&mdp_spin_lock); + if (panel & MDP4_PANEL_LCDC) + mdp4_primary_vsync_lcdc(); +#ifdef CONFIG_FB_MSM_MIPI_DSI + else if (panel & MDP4_PANEL_DSI_VIDEO) + mdp4_primary_vsync_dsi_video(); +#endif + } +#ifdef CONFIG_FB_MSM_DTV + if (isr & INTR_EXTERNAL_VSYNC) { + dma = &dma_e_data; + spin_lock(&mdp_spin_lock); + mdp_intr_mask &= ~INTR_EXTERNAL_VSYNC; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + dma->waiting = FALSE; + spin_unlock(&mdp_spin_lock); + if (panel & MDP4_PANEL_DTV) + mdp4_external_vsync_dtv(); + } +#endif + if (isr & INTR_DMA_P_DONE) { + mdp4_stat.intr_dma_p++; + dma = &dma2_data; + if (panel & MDP4_PANEL_LCDC) { + /* disable LCDC interrupt */ + spin_lock(&mdp_spin_lock); + mdp_intr_mask &= ~INTR_DMA_P_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + dma->waiting = FALSE; + spin_unlock(&mdp_spin_lock); + } else { /* MDDI */ +#ifdef CONFIG_FB_MSM_OVERLAY +#ifdef CONFIG_FB_MSM_MIPI_DSI + mdp4_dma_p_done_dsi(dma); +#else + mdp4_dma_p_done_mddi(); + mdp_pipe_ctrl(MDP_DMA2_BLOCK, + MDP_BLOCK_POWER_OFF, TRUE); +#endif +#else + spin_lock(&mdp_spin_lock); + dma->busy = FALSE; + spin_unlock(&mdp_spin_lock); +#endif + } +#ifndef CONFIG_FB_MSM_MIPI_DSI + complete(&dma->comp); +#endif + } + if (isr & INTR_DMA_S_DONE) { + mdp4_stat.intr_dma_s++; +#if defined(CONFIG_FB_MSM_OVERLAY) && defined(CONFIG_FB_MSM_MDDI) + dma = &dma2_data; +#else + dma = &dma_s_data; +#endif + + dma->busy = FALSE; + mdp_pipe_ctrl(MDP_DMA_S_BLOCK, + MDP_BLOCK_POWER_OFF, TRUE); + complete(&dma->comp); + } + if (isr & INTR_DMA_E_DONE) { + mdp4_stat.intr_dma_e++; + dma = &dma_e_data; + spin_lock(&mdp_spin_lock); + mdp_intr_mask &= ~INTR_DMA_E_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + dma->busy = FALSE; + + if (dma->waiting) { + dma->waiting = FALSE; + complete(&dma->comp); + } + spin_unlock(&mdp_spin_lock); + } +#ifdef CONFIG_FB_MSM_OVERLAY + if (isr & INTR_OVERLAY0_DONE) { + mdp4_stat.intr_overlay0++; + dma = &dma2_data; + if (panel & (MDP4_PANEL_LCDC | MDP4_PANEL_DSI_VIDEO)) { + /* disable LCDC interrupt */ + spin_lock(&mdp_spin_lock); + mdp_intr_mask &= ~INTR_OVERLAY0_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + dma->waiting = FALSE; + spin_unlock(&mdp_spin_lock); + if (panel & MDP4_PANEL_LCDC) + mdp4_overlay0_done_lcdc(); +#ifdef CONFIG_FB_MSM_MIPI_DSI + else if (panel & MDP4_PANEL_DSI_VIDEO) + mdp4_overlay0_done_dsi_video(); +#endif + } else { /* MDDI, DSI_CMD */ +#ifdef CONFIG_FB_MSM_MIPI_DSI + if (panel & MDP4_PANEL_DSI_CMD) + mdp4_overlay0_done_dsi_cmd(dma); +#else + if (panel & MDP4_PANEL_MDDI) + mdp4_overlay0_done_mddi(dma); +#endif + } + mdp_hw_cursor_done(); + } + if (isr & INTR_OVERLAY1_DONE) { + mdp4_stat.intr_overlay1++; + /* disable DTV interrupt */ + dma = &dma_e_data; + spin_lock(&mdp_spin_lock); + mdp_intr_mask &= ~INTR_OVERLAY1_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + dma->waiting = FALSE; + spin_unlock(&mdp_spin_lock); +#if defined(CONFIG_FB_MSM_DTV) + if (panel & MDP4_PANEL_DTV) + mdp4_overlay1_done_dtv(); +#endif +#if defined(CONFIG_FB_MSM_TVOUT) + if (panel & MDP4_PANEL_ATV) + mdp4_overlay1_done_atv(); +#endif + } +#endif /* OVERLAY */ + if (isr & INTR_DMA_P_HISTOGRAM) { + isr = inpdw(MDP_DMA_P_HIST_INTR_STATUS); + mask = inpdw(MDP_DMA_P_HIST_INTR_ENABLE); + outpdw(MDP_DMA_P_HIST_INTR_CLEAR, isr); + isr &= mask; + if (isr & INTR_HIST_DONE) { + if (mdp_hist.r) + memcpy(mdp_hist.r, MDP_BASE + 0x95100, + mdp_hist.bin_cnt*4); + if (mdp_hist.g) + memcpy(mdp_hist.g, MDP_BASE + 0x95200, + mdp_hist.bin_cnt*4); + if (mdp_hist.b) + memcpy(mdp_hist.b, MDP_BASE + 0x95300, + mdp_hist.bin_cnt*4); + complete(&mdp_hist_comp); + if (mdp_is_hist_start == TRUE) { + MDP_OUTP(MDP_BASE + 0x95004, + mdp_hist.frame_cnt); + MDP_OUTP(MDP_BASE + 0x95000, 1); + } + } + } + +out: + mdp_is_in_isr = FALSE; + + return IRQ_HANDLED; +} + + +/* + * QSEED tables + */ + +static uint32 vg_qseed_table0[] = { + 0x5556aaff, 0x00000000, 0x00000000, 0x00000000 +}; + +static uint32 vg_qseed_table1[] = { + 0x76543210, 0xfedcba98 +}; + +static uint32 vg_qseed_table2[] = { + 0x02000000, 0x00000000, 0x01ff0ff9, 0x00000008, + 0x01fb0ff2, 0x00000013, 0x01f50fed, 0x0ffe0020, + 0x01ed0fe8, 0x0ffd002e, 0x01e30fe4, 0x0ffb003e, + 0x01d80fe1, 0x0ff9004e, 0x01cb0fde, 0x0ff70060, + 0x01bc0fdc, 0x0ff40074, 0x01ac0fdb, 0x0ff20087, + 0x019a0fdb, 0x0fef009c, 0x01870fdb, 0x0fed00b1, + 0x01740fdb, 0x0fea00c7, 0x01600fdc, 0x0fe700dd, + 0x014b0fdd, 0x0fe500f3, 0x01350fdf, 0x0fe30109, + 0x01200fe0, 0x0fe00120, 0x01090fe3, 0x0fdf0135, + 0x00f30fe5, 0x0fdd014b, 0x00dd0fe7, 0x0fdc0160, + 0x00c70fea, 0x0fdb0174, 0x00b10fed, 0x0fdb0187, + 0x009c0fef, 0x0fdb019a, 0x00870ff2, 0x0fdb01ac, + 0x00740ff4, 0x0fdc01bc, 0x00600ff7, 0x0fde01cb, + 0x004e0ff9, 0x0fe101d8, 0x003e0ffb, 0x0fe401e3, + 0x002e0ffd, 0x0fe801ed, 0x00200ffe, 0x0fed01f5, + 0x00130000, 0x0ff201fb, 0x00080000, 0x0ff901ff, + + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + + 0x02000000, 0x00000000, 0x01fc0ff9, 0x0ffe000d, + 0x01f60ff3, 0x0ffb001c, 0x01ef0fed, 0x0ff9002b, + 0x01e60fe8, 0x0ff6003c, 0x01dc0fe4, 0x0ff3004d, + 0x01d00fe0, 0x0ff1005f, 0x01c30fde, 0x0fee0071, + 0x01b50fdb, 0x0feb0085, 0x01a70fd9, 0x0fe80098, + 0x01960fd8, 0x0fe600ac, 0x01850fd7, 0x0fe300c1, + 0x01730fd7, 0x0fe100d5, 0x01610fd7, 0x0fdf00e9, + 0x014e0fd8, 0x0fdd00fd, 0x013b0fd8, 0x0fdb0112, + 0x01250fda, 0x0fda0127, 0x01120fdb, 0x0fd8013b, + 0x00fd0fdd, 0x0fd8014e, 0x00e90fdf, 0x0fd70161, + 0x00d50fe1, 0x0fd70173, 0x00c10fe3, 0x0fd70185, + 0x00ac0fe6, 0x0fd80196, 0x00980fe8, 0x0fd901a7, + 0x00850feb, 0x0fdb01b5, 0x00710fee, 0x0fde01c3, + 0x005f0ff1, 0x0fe001d0, 0x004d0ff3, 0x0fe401dc, + 0x003c0ff6, 0x0fe801e6, 0x002b0ff9, 0x0fed01ef, + 0x001c0ffb, 0x0ff301f6, 0x000d0ffe, 0x0ff901fc, + + 0x020f0034, 0x0f7a0043, 0x01e80023, 0x0fa8004d, + 0x01d30016, 0x0fbe0059, 0x01c6000a, 0x0fc90067, + 0x01bd0000, 0x0fce0075, 0x01b50ff7, 0x0fcf0085, + 0x01ae0fee, 0x0fcf0095, 0x01a70fe6, 0x0fcd00a6, + 0x019d0fe0, 0x0fcb00b8, 0x01940fd9, 0x0fc900ca, + 0x01890fd4, 0x0fc700dc, 0x017d0fcf, 0x0fc600ee, + 0x01700fcc, 0x0fc40100, 0x01620fc9, 0x0fc40111, + 0x01540fc6, 0x0fc30123, 0x01430fc5, 0x0fc40134, + 0x01340fc4, 0x0fc50143, 0x01230fc3, 0x0fc60154, + 0x01110fc4, 0x0fc90162, 0x01000fc4, 0x0fcc0170, + 0x00ee0fc6, 0x0fcf017d, 0x00dc0fc7, 0x0fd40189, + 0x00ca0fc9, 0x0fd90194, 0x00b80fcb, 0x0fe0019d, + 0x00a60fcd, 0x0fe601a7, 0x00950fcf, 0x0fee01ae, + 0x00850fcf, 0x0ff701b5, 0x00750fce, 0x000001bd, + 0x00670fc9, 0x000a01c6, 0x00590fbe, 0x001601d3, + 0x004d0fa8, 0x002301e8, 0x00430f7a, 0x0034020f, + + 0x015c005e, 0x0fde0068, 0x015c0054, 0x0fdd0073, + 0x015b004b, 0x0fdc007e, 0x015a0042, 0x0fdb0089, + 0x01590039, 0x0fda0094, 0x01560030, 0x0fda00a0, + 0x01530028, 0x0fda00ab, 0x014f0020, 0x0fda00b7, + 0x014a0019, 0x0fdb00c2, 0x01450011, 0x0fdc00ce, + 0x013e000b, 0x0fde00d9, 0x01390004, 0x0fdf00e4, + 0x01310ffe, 0x0fe200ef, 0x01290ff9, 0x0fe400fa, + 0x01200ff4, 0x0fe80104, 0x01180fef, 0x0feb010e, + 0x010e0feb, 0x0fef0118, 0x01040fe8, 0x0ff40120, + 0x00fa0fe4, 0x0ff90129, 0x00ef0fe2, 0x0ffe0131, + 0x00e40fdf, 0x00040139, 0x00d90fde, 0x000b013e, + 0x00ce0fdc, 0x00110145, 0x00c20fdb, 0x0019014a, + 0x00b70fda, 0x0020014f, 0x00ab0fda, 0x00280153, + 0x00a00fda, 0x00300156, 0x00940fda, 0x00390159, + 0x00890fdb, 0x0042015a, 0x007e0fdc, 0x004b015b, + 0x00730fdd, 0x0054015c, 0x00680fde, 0x005e015c, + + 0x01300068, 0x0ff80070, 0x01300060, 0x0ff80078, + 0x012f0059, 0x0ff80080, 0x012d0052, 0x0ff80089, + 0x012b004b, 0x0ff90091, 0x01290044, 0x0ff9009a, + 0x0126003d, 0x0ffa00a3, 0x01220037, 0x0ffb00ac, + 0x011f0031, 0x0ffc00b4, 0x011a002b, 0x0ffe00bd, + 0x01150026, 0x000000c5, 0x010f0021, 0x000200ce, + 0x010a001c, 0x000400d6, 0x01030018, 0x000600df, + 0x00fd0014, 0x000900e6, 0x00f60010, 0x000c00ee, + 0x00ee000c, 0x001000f6, 0x00e60009, 0x001400fd, + 0x00df0006, 0x00180103, 0x00d60004, 0x001c010a, + 0x00ce0002, 0x0021010f, 0x00c50000, 0x00260115, + 0x00bd0ffe, 0x002b011a, 0x00b40ffc, 0x0031011f, + 0x00ac0ffb, 0x00370122, 0x00a30ffa, 0x003d0126, + 0x009a0ff9, 0x00440129, 0x00910ff9, 0x004b012b, + 0x00890ff8, 0x0052012d, 0x00800ff8, 0x0059012f, + 0x00780ff8, 0x00600130, 0x00700ff8, 0x00680130, + + 0x01050079, 0x0003007f, 0x01040073, 0x00030086, + 0x0103006d, 0x0004008c, 0x01030066, 0x00050092, + 0x01010060, 0x00060099, 0x0100005a, 0x0007009f, + 0x00fe0054, 0x000900a5, 0x00fa004f, 0x000b00ac, + 0x00f80049, 0x000d00b2, 0x00f50044, 0x000f00b8, + 0x00f2003f, 0x001200bd, 0x00ef0039, 0x001500c3, + 0x00ea0035, 0x001800c9, 0x00e60030, 0x001c00ce, + 0x00e3002b, 0x001f00d3, 0x00dd0027, 0x002300d9, + 0x00d90023, 0x002700dd, 0x00d3001f, 0x002b00e3, + 0x00ce001c, 0x003000e6, 0x00c90018, 0x003500ea, + 0x00c30015, 0x003900ef, 0x00bd0012, 0x003f00f2, + 0x00b8000f, 0x004400f5, 0x00b2000d, 0x004900f8, + 0x00ac000b, 0x004f00fa, 0x00a50009, 0x005400fe, + 0x009f0007, 0x005a0100, 0x00990006, 0x00600101, + 0x00920005, 0x00660103, 0x008c0004, 0x006d0103, + 0x00860003, 0x00730104, 0x007f0003, 0x00790105, + + 0x00cf0088, 0x001d008c, 0x00ce0084, 0x0020008e, + 0x00cd0080, 0x00210092, 0x00cd007b, 0x00240094, + 0x00ca0077, 0x00270098, 0x00c90073, 0x0029009b, + 0x00c8006f, 0x002c009d, 0x00c6006b, 0x002f00a0, + 0x00c50067, 0x003200a2, 0x00c30062, 0x003600a5, + 0x00c0005f, 0x003900a8, 0x00c0005b, 0x003b00aa, + 0x00be0057, 0x003e00ad, 0x00ba0054, 0x004200b0, + 0x00b90050, 0x004500b2, 0x00b7004c, 0x004900b4, + 0x00b40049, 0x004c00b7, 0x00b20045, 0x005000b9, + 0x00b00042, 0x005400ba, 0x00ad003e, 0x005700be, + 0x00aa003b, 0x005b00c0, 0x00a80039, 0x005f00c0, + 0x00a50036, 0x006200c3, 0x00a20032, 0x006700c5, + 0x00a0002f, 0x006b00c6, 0x009d002c, 0x006f00c8, + 0x009b0029, 0x007300c9, 0x00980027, 0x007700ca, + 0x00940024, 0x007b00cd, 0x00920021, 0x008000cd, + 0x008e0020, 0x008400ce, 0x008c001d, 0x008800cf, + + 0x008e0083, 0x006b0084, 0x008d0083, 0x006c0084, + 0x008d0082, 0x006d0084, 0x008d0081, 0x006d0085, + 0x008d0080, 0x006e0085, 0x008c007f, 0x006f0086, + 0x008b007f, 0x00700086, 0x008b007e, 0x00710086, + 0x008b007d, 0x00720086, 0x008a007d, 0x00730086, + 0x008a007c, 0x00730087, 0x008a007b, 0x00740087, + 0x0089007b, 0x00750087, 0x008a0079, 0x00750088, + 0x008a0078, 0x00760088, 0x008a0077, 0x00770088, + 0x00880077, 0x0077008a, 0x00880076, 0x0078008a, + 0x00880075, 0x0079008a, 0x00870075, 0x007b0089, + 0x00870074, 0x007b008a, 0x00870073, 0x007c008a, + 0x00860073, 0x007d008a, 0x00860072, 0x007d008b, + 0x00860071, 0x007e008b, 0x00860070, 0x007f008b, + 0x0086006f, 0x007f008c, 0x0085006e, 0x0080008d, + 0x0085006d, 0x0081008d, 0x0084006d, 0x0082008d, + 0x0084006c, 0x0083008d, 0x0084006b, 0x0083008e, + + 0x023c0fe2, 0x00000fe2, 0x023a0fdb, 0x00000feb, + 0x02360fd3, 0x0fff0ff8, 0x022e0fcf, 0x0ffc0007, + 0x02250fca, 0x0ffa0017, 0x021a0fc6, 0x0ff70029, + 0x020c0fc4, 0x0ff4003c, 0x01fd0fc1, 0x0ff10051, + 0x01eb0fc0, 0x0fed0068, 0x01d80fc0, 0x0fe9007f, + 0x01c30fc1, 0x0fe50097, 0x01ac0fc2, 0x0fe200b0, + 0x01960fc3, 0x0fdd00ca, 0x017e0fc5, 0x0fd900e4, + 0x01650fc8, 0x0fd500fe, 0x014b0fcb, 0x0fd20118, + 0x01330fcd, 0x0fcd0133, 0x01180fd2, 0x0fcb014b, + 0x00fe0fd5, 0x0fc80165, 0x00e40fd9, 0x0fc5017e, + 0x00ca0fdd, 0x0fc30196, 0x00b00fe2, 0x0fc201ac, + 0x00970fe5, 0x0fc101c3, 0x007f0fe9, 0x0fc001d8, + 0x00680fed, 0x0fc001eb, 0x00510ff1, 0x0fc101fd, + 0x003c0ff4, 0x0fc4020c, 0x00290ff7, 0x0fc6021a, + 0x00170ffa, 0x0fca0225, 0x00070ffc, 0x0fcf022e, + 0x0ff80fff, 0x0fd30236, 0x0feb0000, 0x0fdb023a, + + 0x02780fc4, 0x00000fc4, 0x02770fbc, 0x0fff0fce, + 0x02710fb5, 0x0ffe0fdc, 0x02690fb0, 0x0ffa0fed, + 0x025f0fab, 0x0ff70fff, 0x02500fa8, 0x0ff30015, + 0x02410fa6, 0x0fef002a, 0x022f0fa4, 0x0feb0042, + 0x021a0fa4, 0x0fe5005d, 0x02040fa5, 0x0fe10076, + 0x01eb0fa7, 0x0fdb0093, 0x01d20fa9, 0x0fd600af, + 0x01b80fab, 0x0fd000cd, 0x019d0faf, 0x0fca00ea, + 0x01810fb2, 0x0fc50108, 0x01620fb7, 0x0fc10126, + 0x01440fbb, 0x0fbb0146, 0x01260fc1, 0x0fb70162, + 0x01080fc5, 0x0fb20181, 0x00ea0fca, 0x0faf019d, + 0x00cd0fd0, 0x0fab01b8, 0x00af0fd6, 0x0fa901d2, + 0x00930fdb, 0x0fa701eb, 0x00760fe1, 0x0fa50204, + 0x005d0fe5, 0x0fa4021a, 0x00420feb, 0x0fa4022f, + 0x002a0fef, 0x0fa60241, 0x00150ff3, 0x0fa80250, + 0x0fff0ff7, 0x0fab025f, 0x0fed0ffa, 0x0fb00269, + 0x0fdc0ffe, 0x0fb50271, 0x0fce0fff, 0x0fbc0277, + + 0x02a00fb0, 0x00000fb0, 0x029e0fa8, 0x0fff0fbb, + 0x02980fa1, 0x0ffd0fca, 0x028f0f9c, 0x0ff90fdc, + 0x02840f97, 0x0ff50ff0, 0x02740f94, 0x0ff10007, + 0x02640f92, 0x0fec001e, 0x02500f91, 0x0fe70038, + 0x023a0f91, 0x0fe00055, 0x02220f92, 0x0fdb0071, + 0x02080f95, 0x0fd4008f, 0x01ec0f98, 0x0fce00ae, + 0x01cf0f9b, 0x0fc700cf, 0x01b10f9f, 0x0fc100ef, + 0x01920fa4, 0x0fbb010f, 0x01710faa, 0x0fb50130, + 0x01520fae, 0x0fae0152, 0x01300fb5, 0x0faa0171, + 0x010f0fbb, 0x0fa40192, 0x00ef0fc1, 0x0f9f01b1, + 0x00cf0fc7, 0x0f9b01cf, 0x00ae0fce, 0x0f9801ec, + 0x008f0fd4, 0x0f950208, 0x00710fdb, 0x0f920222, + 0x00550fe0, 0x0f91023a, 0x00380fe7, 0x0f910250, + 0x001e0fec, 0x0f920264, 0x00070ff1, 0x0f940274, + 0x0ff00ff5, 0x0f970284, 0x0fdc0ff9, 0x0f9c028f, + 0x0fca0ffd, 0x0fa10298, 0x0fbb0fff, 0x0fa8029e, + + 0x02c80f9c, 0x00000f9c, 0x02c70f94, 0x0ffe0fa7, + 0x02c10f8c, 0x0ffc0fb7, 0x02b70f87, 0x0ff70fcb, + 0x02aa0f83, 0x0ff30fe0, 0x02990f80, 0x0fee0ff9, + 0x02870f7f, 0x0fe80012, 0x02720f7e, 0x0fe2002e, + 0x025a0f7e, 0x0fdb004d, 0x02400f80, 0x0fd5006b, + 0x02230f84, 0x0fcd008c, 0x02050f87, 0x0fc700ad, + 0x01e60f8b, 0x0fbf00d0, 0x01c60f90, 0x0fb700f3, + 0x01a30f96, 0x0fb00117, 0x01800f9c, 0x0faa013a, + 0x015d0fa2, 0x0fa2015f, 0x013a0faa, 0x0f9c0180, + 0x01170fb0, 0x0f9601a3, 0x00f30fb7, 0x0f9001c6, + 0x00d00fbf, 0x0f8b01e6, 0x00ad0fc7, 0x0f870205, + 0x008c0fcd, 0x0f840223, 0x006b0fd5, 0x0f800240, + 0x004d0fdb, 0x0f7e025a, 0x002e0fe2, 0x0f7e0272, + 0x00120fe8, 0x0f7f0287, 0x0ff90fee, 0x0f800299, + 0x0fe00ff3, 0x0f8302aa, 0x0fcb0ff7, 0x0f8702b7, + 0x0fb70ffc, 0x0f8c02c1, 0x0fa70ffe, 0x0f9402c7, + + 0x02f00f88, 0x00000f88, 0x02ee0f80, 0x0ffe0f94, + 0x02e70f78, 0x0ffc0fa5, 0x02dd0f73, 0x0ff60fba, + 0x02ce0f6f, 0x0ff20fd1, 0x02be0f6c, 0x0feb0feb, + 0x02aa0f6b, 0x0fe50006, 0x02940f6a, 0x0fde0024, + 0x02790f6c, 0x0fd60045, 0x025e0f6e, 0x0fcf0065, + 0x023f0f72, 0x0fc60089, 0x021d0f77, 0x0fbf00ad, + 0x01fd0f7b, 0x0fb600d2, 0x01da0f81, 0x0fad00f8, + 0x01b50f87, 0x0fa6011e, 0x018f0f8f, 0x0f9e0144, + 0x016b0f95, 0x0f95016b, 0x01440f9e, 0x0f8f018f, + 0x011e0fa6, 0x0f8701b5, 0x00f80fad, 0x0f8101da, + 0x00d20fb6, 0x0f7b01fd, 0x00ad0fbf, 0x0f77021d, + 0x00890fc6, 0x0f72023f, 0x00650fcf, 0x0f6e025e, + 0x00450fd6, 0x0f6c0279, 0x00240fde, 0x0f6a0294, + 0x00060fe5, 0x0f6b02aa, 0x0feb0feb, 0x0f6c02be, + 0x0fd10ff2, 0x0f6f02ce, 0x0fba0ff6, 0x0f7302dd, + 0x0fa50ffc, 0x0f7802e7, 0x0f940ffe, 0x0f8002ee, + + 0x03180f74, 0x00000f74, 0x03160f6b, 0x0ffe0f81, + 0x030e0f64, 0x0ffb0f93, 0x03030f5f, 0x0ff50fa9, + 0x02f40f5b, 0x0ff00fc1, 0x02e20f58, 0x0fe90fdd, + 0x02cd0f57, 0x0fe20ffa, 0x02b60f57, 0x0fda0019, + 0x02990f59, 0x0fd1003d, 0x027b0f5c, 0x0fc90060, + 0x02590f61, 0x0fc00086, 0x02370f66, 0x0fb700ac, + 0x02130f6b, 0x0fae00d4, 0x01ee0f72, 0x0fa400fc, + 0x01c70f79, 0x0f9b0125, 0x019f0f81, 0x0f93014d, + 0x01760f89, 0x0f890178, 0x014d0f93, 0x0f81019f, + 0x01250f9b, 0x0f7901c7, 0x00fc0fa4, 0x0f7201ee, + 0x00d40fae, 0x0f6b0213, 0x00ac0fb7, 0x0f660237, + 0x00860fc0, 0x0f610259, 0x00600fc9, 0x0f5c027b, + 0x003d0fd1, 0x0f590299, 0x00190fda, 0x0f5702b6, + 0x0ffa0fe2, 0x0f5702cd, 0x0fdd0fe9, 0x0f5802e2, + 0x0fc10ff0, 0x0f5b02f4, 0x0fa90ff5, 0x0f5f0303, + 0x0f930ffb, 0x0f64030e, 0x0f810ffe, 0x0f6b0316, + + 0x03400f60, 0x00000f60, 0x033e0f57, 0x0ffe0f6d, + 0x03370f4f, 0x0ffa0f80, 0x032a0f4b, 0x0ff30f98, + 0x031a0f46, 0x0fee0fb2, 0x03070f44, 0x0fe60fcf, + 0x02f10f44, 0x0fde0fed, 0x02d70f44, 0x0fd6000f, + 0x02b80f46, 0x0fcc0036, 0x02990f4a, 0x0fc3005a, + 0x02750f4f, 0x0fb90083, 0x02500f55, 0x0fb000ab, + 0x022a0f5b, 0x0fa500d6, 0x02020f63, 0x0f9a0101, + 0x01d80f6b, 0x0f91012c, 0x01ae0f74, 0x0f870157, + 0x01840f7c, 0x0f7c0184, 0x01570f87, 0x0f7401ae, + 0x012c0f91, 0x0f6b01d8, 0x01010f9a, 0x0f630202, + 0x00d60fa5, 0x0f5b022a, 0x00ab0fb0, 0x0f550250, + 0x00830fb9, 0x0f4f0275, 0x005a0fc3, 0x0f4a0299, + 0x00360fcc, 0x0f4602b8, 0x000f0fd6, 0x0f4402d7, + 0x0fed0fde, 0x0f4402f1, 0x0fcf0fe6, 0x0f440307, + 0x0fb20fee, 0x0f46031a, 0x0f980ff3, 0x0f4b032a, + 0x0f800ffa, 0x0f4f0337, 0x0f6d0ffe, 0x0f57033e, + + 0x02000000, 0x00000000, 0x01ff0ff9, 0x00000008, + 0x01fb0ff2, 0x00000013, 0x01f50fed, 0x0ffe0020, + 0x01ed0fe8, 0x0ffd002e, 0x01e30fe4, 0x0ffb003e, + 0x01d80fe1, 0x0ff9004e, 0x01cb0fde, 0x0ff70060, + 0x01bc0fdc, 0x0ff40074, 0x01ac0fdb, 0x0ff20087, + 0x019a0fdb, 0x0fef009c, 0x01870fdb, 0x0fed00b1, + 0x01740fdb, 0x0fea00c7, 0x01600fdc, 0x0fe700dd, + 0x014b0fdd, 0x0fe500f3, 0x01350fdf, 0x0fe30109, + 0x01200fe0, 0x0fe00120, 0x01090fe3, 0x0fdf0135, + 0x00f30fe5, 0x0fdd014b, 0x00dd0fe7, 0x0fdc0160, + 0x00c70fea, 0x0fdb0174, 0x00b10fed, 0x0fdb0187, + 0x009c0fef, 0x0fdb019a, 0x00870ff2, 0x0fdb01ac, + 0x00740ff4, 0x0fdc01bc, 0x00600ff7, 0x0fde01cb, + 0x004e0ff9, 0x0fe101d8, 0x003e0ffb, 0x0fe401e3, + 0x002e0ffd, 0x0fe801ed, 0x00200ffe, 0x0fed01f5, + 0x00130000, 0x0ff201fb, 0x00080000, 0x0ff901ff, + + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + 0x02000000, 0x00000000, 0x02000000, 0x00000000, + + 0x02000000, 0x00000000, 0x01fc0ff9, 0x0ffe000d, + 0x01f60ff3, 0x0ffb001c, 0x01ef0fed, 0x0ff9002b, + 0x01e60fe8, 0x0ff6003c, 0x01dc0fe4, 0x0ff3004d, + 0x01d00fe0, 0x0ff1005f, 0x01c30fde, 0x0fee0071, + 0x01b50fdb, 0x0feb0085, 0x01a70fd9, 0x0fe80098, + 0x01960fd8, 0x0fe600ac, 0x01850fd7, 0x0fe300c1, + 0x01730fd7, 0x0fe100d5, 0x01610fd7, 0x0fdf00e9, + 0x014e0fd8, 0x0fdd00fd, 0x013b0fd8, 0x0fdb0112, + 0x01250fda, 0x0fda0127, 0x01120fdb, 0x0fd8013b, + 0x00fd0fdd, 0x0fd8014e, 0x00e90fdf, 0x0fd70161, + 0x00d50fe1, 0x0fd70173, 0x00c10fe3, 0x0fd70185, + 0x00ac0fe6, 0x0fd80196, 0x00980fe8, 0x0fd901a7, + 0x00850feb, 0x0fdb01b5, 0x00710fee, 0x0fde01c3, + 0x005f0ff1, 0x0fe001d0, 0x004d0ff3, 0x0fe401dc, + 0x003c0ff6, 0x0fe801e6, 0x002b0ff9, 0x0fed01ef, + 0x001c0ffb, 0x0ff301f6, 0x000d0ffe, 0x0ff901fc, + + 0x020f0034, 0x0f7a0043, 0x01e80023, 0x0fa8004d, + 0x01d30016, 0x0fbe0059, 0x01c6000a, 0x0fc90067, + 0x01bd0000, 0x0fce0075, 0x01b50ff7, 0x0fcf0085, + 0x01ae0fee, 0x0fcf0095, 0x01a70fe6, 0x0fcd00a6, + 0x019d0fe0, 0x0fcb00b8, 0x01940fd9, 0x0fc900ca, + 0x01890fd4, 0x0fc700dc, 0x017d0fcf, 0x0fc600ee, + 0x01700fcc, 0x0fc40100, 0x01620fc9, 0x0fc40111, + 0x01540fc6, 0x0fc30123, 0x01430fc5, 0x0fc40134, + 0x01340fc4, 0x0fc50143, 0x01230fc3, 0x0fc60154, + 0x01110fc4, 0x0fc90162, 0x01000fc4, 0x0fcc0170, + 0x00ee0fc6, 0x0fcf017d, 0x00dc0fc7, 0x0fd40189, + 0x00ca0fc9, 0x0fd90194, 0x00b80fcb, 0x0fe0019d, + 0x00a60fcd, 0x0fe601a7, 0x00950fcf, 0x0fee01ae, + 0x00850fcf, 0x0ff701b5, 0x00750fce, 0x000001bd, + 0x00670fc9, 0x000a01c6, 0x00590fbe, 0x001601d3, + 0x004d0fa8, 0x002301e8, 0x00430f7a, 0x0034020f, + + 0x015c005e, 0x0fde0068, 0x015c0054, 0x0fdd0073, + 0x015b004b, 0x0fdc007e, 0x015a0042, 0x0fdb0089, + 0x01590039, 0x0fda0094, 0x01560030, 0x0fda00a0, + 0x01530028, 0x0fda00ab, 0x014f0020, 0x0fda00b7, + 0x014a0019, 0x0fdb00c2, 0x01450011, 0x0fdc00ce, + 0x013e000b, 0x0fde00d9, 0x01390004, 0x0fdf00e4, + 0x01310ffe, 0x0fe200ef, 0x01290ff9, 0x0fe400fa, + 0x01200ff4, 0x0fe80104, 0x01180fef, 0x0feb010e, + 0x010e0feb, 0x0fef0118, 0x01040fe8, 0x0ff40120, + 0x00fa0fe4, 0x0ff90129, 0x00ef0fe2, 0x0ffe0131, + 0x00e40fdf, 0x00040139, 0x00d90fde, 0x000b013e, + 0x00ce0fdc, 0x00110145, 0x00c20fdb, 0x0019014a, + 0x00b70fda, 0x0020014f, 0x00ab0fda, 0x00280153, + 0x00a00fda, 0x00300156, 0x00940fda, 0x00390159, + 0x00890fdb, 0x0042015a, 0x007e0fdc, 0x004b015b, + 0x00730fdd, 0x0054015c, 0x00680fde, 0x005e015c, + + 0x01300068, 0x0ff80070, 0x01300060, 0x0ff80078, + 0x012f0059, 0x0ff80080, 0x012d0052, 0x0ff80089, + 0x012b004b, 0x0ff90091, 0x01290044, 0x0ff9009a, + 0x0126003d, 0x0ffa00a3, 0x01220037, 0x0ffb00ac, + 0x011f0031, 0x0ffc00b4, 0x011a002b, 0x0ffe00bd, + 0x01150026, 0x000000c5, 0x010f0021, 0x000200ce, + 0x010a001c, 0x000400d6, 0x01030018, 0x000600df, + 0x00fd0014, 0x000900e6, 0x00f60010, 0x000c00ee, + 0x00ee000c, 0x001000f6, 0x00e60009, 0x001400fd, + 0x00df0006, 0x00180103, 0x00d60004, 0x001c010a, + 0x00ce0002, 0x0021010f, 0x00c50000, 0x00260115, + 0x00bd0ffe, 0x002b011a, 0x00b40ffc, 0x0031011f, + 0x00ac0ffb, 0x00370122, 0x00a30ffa, 0x003d0126, + 0x009a0ff9, 0x00440129, 0x00910ff9, 0x004b012b, + 0x00890ff8, 0x0052012d, 0x00800ff8, 0x0059012f, + 0x00780ff8, 0x00600130, 0x00700ff8, 0x00680130, + + 0x01050079, 0x0003007f, 0x01040073, 0x00030086, + 0x0103006d, 0x0004008c, 0x01030066, 0x00050092, + 0x01010060, 0x00060099, 0x0100005a, 0x0007009f, + 0x00fe0054, 0x000900a5, 0x00fa004f, 0x000b00ac, + 0x00f80049, 0x000d00b2, 0x00f50044, 0x000f00b8, + 0x00f2003f, 0x001200bd, 0x00ef0039, 0x001500c3, + 0x00ea0035, 0x001800c9, 0x00e60030, 0x001c00ce, + 0x00e3002b, 0x001f00d3, 0x00dd0027, 0x002300d9, + 0x00d90023, 0x002700dd, 0x00d3001f, 0x002b00e3, + 0x00ce001c, 0x003000e6, 0x00c90018, 0x003500ea, + 0x00c30015, 0x003900ef, 0x00bd0012, 0x003f00f2, + 0x00b8000f, 0x004400f5, 0x00b2000d, 0x004900f8, + 0x00ac000b, 0x004f00fa, 0x00a50009, 0x005400fe, + 0x009f0007, 0x005a0100, 0x00990006, 0x00600101, + 0x00920005, 0x00660103, 0x008c0004, 0x006d0103, + 0x00860003, 0x00730104, 0x007f0003, 0x00790105, + + 0x00cf0088, 0x001d008c, 0x00ce0084, 0x0020008e, + 0x00cd0080, 0x00210092, 0x00cd007b, 0x00240094, + 0x00ca0077, 0x00270098, 0x00c90073, 0x0029009b, + 0x00c8006f, 0x002c009d, 0x00c6006b, 0x002f00a0, + 0x00c50067, 0x003200a2, 0x00c30062, 0x003600a5, + 0x00c0005f, 0x003900a8, 0x00c0005b, 0x003b00aa, + 0x00be0057, 0x003e00ad, 0x00ba0054, 0x004200b0, + 0x00b90050, 0x004500b2, 0x00b7004c, 0x004900b4, + 0x00b40049, 0x004c00b7, 0x00b20045, 0x005000b9, + 0x00b00042, 0x005400ba, 0x00ad003e, 0x005700be, + 0x00aa003b, 0x005b00c0, 0x00a80039, 0x005f00c0, + 0x00a50036, 0x006200c3, 0x00a20032, 0x006700c5, + 0x00a0002f, 0x006b00c6, 0x009d002c, 0x006f00c8, + 0x009b0029, 0x007300c9, 0x00980027, 0x007700ca, + 0x00940024, 0x007b00cd, 0x00920021, 0x008000cd, + 0x008e0020, 0x008400ce, 0x008c001d, 0x008800cf, + + 0x008e0083, 0x006b0084, 0x008d0083, 0x006c0084, + 0x008d0082, 0x006d0084, 0x008d0081, 0x006d0085, + 0x008d0080, 0x006e0085, 0x008c007f, 0x006f0086, + 0x008b007f, 0x00700086, 0x008b007e, 0x00710086, + 0x008b007d, 0x00720086, 0x008a007d, 0x00730086, + 0x008a007c, 0x00730087, 0x008a007b, 0x00740087, + 0x0089007b, 0x00750087, 0x008a0079, 0x00750088, + 0x008a0078, 0x00760088, 0x008a0077, 0x00770088, + 0x00880077, 0x0077008a, 0x00880076, 0x0078008a, + 0x00880075, 0x0079008a, 0x00870075, 0x007b0089, + 0x00870074, 0x007b008a, 0x00870073, 0x007c008a, + 0x00860073, 0x007d008a, 0x00860072, 0x007d008b, + 0x00860071, 0x007e008b, 0x00860070, 0x007f008b, + 0x0086006f, 0x007f008c, 0x0085006e, 0x0080008d, + 0x0085006d, 0x0081008d, 0x0084006d, 0x0082008d, + 0x0084006c, 0x0083008d, 0x0084006b, 0x0083008e, + + 0x023c0fe2, 0x00000fe2, 0x023a0fdb, 0x00000feb, + 0x02360fd3, 0x0fff0ff8, 0x022e0fcf, 0x0ffc0007, + 0x02250fca, 0x0ffa0017, 0x021a0fc6, 0x0ff70029, + 0x020c0fc4, 0x0ff4003c, 0x01fd0fc1, 0x0ff10051, + 0x01eb0fc0, 0x0fed0068, 0x01d80fc0, 0x0fe9007f, + 0x01c30fc1, 0x0fe50097, 0x01ac0fc2, 0x0fe200b0, + 0x01960fc3, 0x0fdd00ca, 0x017e0fc5, 0x0fd900e4, + 0x01650fc8, 0x0fd500fe, 0x014b0fcb, 0x0fd20118, + 0x01330fcd, 0x0fcd0133, 0x01180fd2, 0x0fcb014b, + 0x00fe0fd5, 0x0fc80165, 0x00e40fd9, 0x0fc5017e, + 0x00ca0fdd, 0x0fc30196, 0x00b00fe2, 0x0fc201ac, + 0x00970fe5, 0x0fc101c3, 0x007f0fe9, 0x0fc001d8, + 0x00680fed, 0x0fc001eb, 0x00510ff1, 0x0fc101fd, + 0x003c0ff4, 0x0fc4020c, 0x00290ff7, 0x0fc6021a, + 0x00170ffa, 0x0fca0225, 0x00070ffc, 0x0fcf022e, + 0x0ff80fff, 0x0fd30236, 0x0feb0000, 0x0fdb023a, + + 0x02780fc4, 0x00000fc4, 0x02770fbc, 0x0fff0fce, + 0x02710fb5, 0x0ffe0fdc, 0x02690fb0, 0x0ffa0fed, + 0x025f0fab, 0x0ff70fff, 0x02500fa8, 0x0ff30015, + 0x02410fa6, 0x0fef002a, 0x022f0fa4, 0x0feb0042, + 0x021a0fa4, 0x0fe5005d, 0x02040fa5, 0x0fe10076, + 0x01eb0fa7, 0x0fdb0093, 0x01d20fa9, 0x0fd600af, + 0x01b80fab, 0x0fd000cd, 0x019d0faf, 0x0fca00ea, + 0x01810fb2, 0x0fc50108, 0x01620fb7, 0x0fc10126, + 0x01440fbb, 0x0fbb0146, 0x01260fc1, 0x0fb70162, + 0x01080fc5, 0x0fb20181, 0x00ea0fca, 0x0faf019d, + 0x00cd0fd0, 0x0fab01b8, 0x00af0fd6, 0x0fa901d2, + 0x00930fdb, 0x0fa701eb, 0x00760fe1, 0x0fa50204, + 0x005d0fe5, 0x0fa4021a, 0x00420feb, 0x0fa4022f, + 0x002a0fef, 0x0fa60241, 0x00150ff3, 0x0fa80250, + 0x0fff0ff7, 0x0fab025f, 0x0fed0ffa, 0x0fb00269, + 0x0fdc0ffe, 0x0fb50271, 0x0fce0fff, 0x0fbc0277, + + 0x02a00fb0, 0x00000fb0, 0x029e0fa8, 0x0fff0fbb, + 0x02980fa1, 0x0ffd0fca, 0x028f0f9c, 0x0ff90fdc, + 0x02840f97, 0x0ff50ff0, 0x02740f94, 0x0ff10007, + 0x02640f92, 0x0fec001e, 0x02500f91, 0x0fe70038, + 0x023a0f91, 0x0fe00055, 0x02220f92, 0x0fdb0071, + 0x02080f95, 0x0fd4008f, 0x01ec0f98, 0x0fce00ae, + 0x01cf0f9b, 0x0fc700cf, 0x01b10f9f, 0x0fc100ef, + 0x01920fa4, 0x0fbb010f, 0x01710faa, 0x0fb50130, + 0x01520fae, 0x0fae0152, 0x01300fb5, 0x0faa0171, + 0x010f0fbb, 0x0fa40192, 0x00ef0fc1, 0x0f9f01b1, + 0x00cf0fc7, 0x0f9b01cf, 0x00ae0fce, 0x0f9801ec, + 0x008f0fd4, 0x0f950208, 0x00710fdb, 0x0f920222, + 0x00550fe0, 0x0f91023a, 0x00380fe7, 0x0f910250, + 0x001e0fec, 0x0f920264, 0x00070ff1, 0x0f940274, + 0x0ff00ff5, 0x0f970284, 0x0fdc0ff9, 0x0f9c028f, + 0x0fca0ffd, 0x0fa10298, 0x0fbb0fff, 0x0fa8029e, + + 0x02c80f9c, 0x00000f9c, 0x02c70f94, 0x0ffe0fa7, + 0x02c10f8c, 0x0ffc0fb7, 0x02b70f87, 0x0ff70fcb, + 0x02aa0f83, 0x0ff30fe0, 0x02990f80, 0x0fee0ff9, + 0x02870f7f, 0x0fe80012, 0x02720f7e, 0x0fe2002e, + 0x025a0f7e, 0x0fdb004d, 0x02400f80, 0x0fd5006b, + 0x02230f84, 0x0fcd008c, 0x02050f87, 0x0fc700ad, + 0x01e60f8b, 0x0fbf00d0, 0x01c60f90, 0x0fb700f3, + 0x01a30f96, 0x0fb00117, 0x01800f9c, 0x0faa013a, + 0x015d0fa2, 0x0fa2015f, 0x013a0faa, 0x0f9c0180, + 0x01170fb0, 0x0f9601a3, 0x00f30fb7, 0x0f9001c6, + 0x00d00fbf, 0x0f8b01e6, 0x00ad0fc7, 0x0f870205, + 0x008c0fcd, 0x0f840223, 0x006b0fd5, 0x0f800240, + 0x004d0fdb, 0x0f7e025a, 0x002e0fe2, 0x0f7e0272, + 0x00120fe8, 0x0f7f0287, 0x0ff90fee, 0x0f800299, + 0x0fe00ff3, 0x0f8302aa, 0x0fcb0ff7, 0x0f8702b7, + 0x0fb70ffc, 0x0f8c02c1, 0x0fa70ffe, 0x0f9402c7, + + 0x02f00f88, 0x00000f88, 0x02ee0f80, 0x0ffe0f94, + 0x02e70f78, 0x0ffc0fa5, 0x02dd0f73, 0x0ff60fba, + 0x02ce0f6f, 0x0ff20fd1, 0x02be0f6c, 0x0feb0feb, + 0x02aa0f6b, 0x0fe50006, 0x02940f6a, 0x0fde0024, + 0x02790f6c, 0x0fd60045, 0x025e0f6e, 0x0fcf0065, + 0x023f0f72, 0x0fc60089, 0x021d0f77, 0x0fbf00ad, + 0x01fd0f7b, 0x0fb600d2, 0x01da0f81, 0x0fad00f8, + 0x01b50f87, 0x0fa6011e, 0x018f0f8f, 0x0f9e0144, + 0x016b0f95, 0x0f95016b, 0x01440f9e, 0x0f8f018f, + 0x011e0fa6, 0x0f8701b5, 0x00f80fad, 0x0f8101da, + 0x00d20fb6, 0x0f7b01fd, 0x00ad0fbf, 0x0f77021d, + 0x00890fc6, 0x0f72023f, 0x00650fcf, 0x0f6e025e, + 0x00450fd6, 0x0f6c0279, 0x00240fde, 0x0f6a0294, + 0x00060fe5, 0x0f6b02aa, 0x0feb0feb, 0x0f6c02be, + 0x0fd10ff2, 0x0f6f02ce, 0x0fba0ff6, 0x0f7302dd, + 0x0fa50ffc, 0x0f7802e7, 0x0f940ffe, 0x0f8002ee, + + 0x03180f74, 0x00000f74, 0x03160f6b, 0x0ffe0f81, + 0x030e0f64, 0x0ffb0f93, 0x03030f5f, 0x0ff50fa9, + 0x02f40f5b, 0x0ff00fc1, 0x02e20f58, 0x0fe90fdd, + 0x02cd0f57, 0x0fe20ffa, 0x02b60f57, 0x0fda0019, + 0x02990f59, 0x0fd1003d, 0x027b0f5c, 0x0fc90060, + 0x02590f61, 0x0fc00086, 0x02370f66, 0x0fb700ac, + 0x02130f6b, 0x0fae00d4, 0x01ee0f72, 0x0fa400fc, + 0x01c70f79, 0x0f9b0125, 0x019f0f81, 0x0f93014d, + 0x01760f89, 0x0f890178, 0x014d0f93, 0x0f81019f, + 0x01250f9b, 0x0f7901c7, 0x00fc0fa4, 0x0f7201ee, + 0x00d40fae, 0x0f6b0213, 0x00ac0fb7, 0x0f660237, + 0x00860fc0, 0x0f610259, 0x00600fc9, 0x0f5c027b, + 0x003d0fd1, 0x0f590299, 0x00190fda, 0x0f5702b6, + 0x0ffa0fe2, 0x0f5702cd, 0x0fdd0fe9, 0x0f5802e2, + 0x0fc10ff0, 0x0f5b02f4, 0x0fa90ff5, 0x0f5f0303, + 0x0f930ffb, 0x0f64030e, 0x0f810ffe, 0x0f6b0316, + + 0x03400f60, 0x00000f60, 0x033e0f57, 0x0ffe0f6d, + 0x03370f4f, 0x0ffa0f80, 0x032a0f4b, 0x0ff30f98, + 0x031a0f46, 0x0fee0fb2, 0x03070f44, 0x0fe60fcf, + 0x02f10f44, 0x0fde0fed, 0x02d70f44, 0x0fd6000f, + 0x02b80f46, 0x0fcc0036, 0x02990f4a, 0x0fc3005a, + 0x02750f4f, 0x0fb90083, 0x02500f55, 0x0fb000ab, + 0x022a0f5b, 0x0fa500d6, 0x02020f63, 0x0f9a0101, + 0x01d80f6b, 0x0f91012c, 0x01ae0f74, 0x0f870157, + 0x01840f7c, 0x0f7c0184, 0x01570f87, 0x0f7401ae, + 0x012c0f91, 0x0f6b01d8, 0x01010f9a, 0x0f630202, + 0x00d60fa5, 0x0f5b022a, 0x00ab0fb0, 0x0f550250, + 0x00830fb9, 0x0f4f0275, 0x005a0fc3, 0x0f4a0299, + 0x00360fcc, 0x0f4602b8, 0x000f0fd6, 0x0f4402d7, + 0x0fed0fde, 0x0f4402f1, 0x0fcf0fe6, 0x0f440307, + 0x0fb20fee, 0x0f46031a, 0x0f980ff3, 0x0f4b032a, + 0x0f800ffa, 0x0f4f0337, 0x0f6d0ffe, 0x0f57033e +}; + + +#define MDP4_QSEED_TABLE0_OFF 0x8100 +#define MDP4_QSEED_TABLE1_OFF 0x8200 +#define MDP4_QSEED_TABLE2_OFF 0x9000 + +void mdp4_vg_qseed_init(int vp_num) +{ + uint32 *off; + int i, voff; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + voff = MDP4_VIDEO_OFF * vp_num; + off = (uint32 *)(MDP_BASE + MDP4_VIDEO_BASE + voff + + MDP4_QSEED_TABLE0_OFF); + for (i = 0; i < (sizeof(vg_qseed_table0) / sizeof(uint32)); i++) { + outpdw(off, vg_qseed_table0[i]); + off++; + /* This code is added to workaround the 1K Boundary AXI + Interleave operations from Scorpion that can potentially + corrupt the QSEED table. The idea is to complete the prevous + to the buffer before making the next write when address is + 1KB aligned to ensure the write has been committed prior to + next instruction write that can go out from the secondary AXI + port.This happens also because of the expected write sequence + from QSEED table, where LSP has to be written first then the + MSP to trigger both to write out to SRAM, if this has not been + the expectation, then corruption wouldn't have happened.*/ + + if (!((uint32)off & 0x3FF)) + wmb(); + } + + off = (uint32 *)(MDP_BASE + MDP4_VIDEO_BASE + voff + + MDP4_QSEED_TABLE1_OFF); + for (i = 0; i < (sizeof(vg_qseed_table1) / sizeof(uint32)); i++) { + outpdw(off, vg_qseed_table1[i]); + off++; + if (!((uint32)off & 0x3FF)) + wmb(); + } + + off = (uint32 *)(MDP_BASE + MDP4_VIDEO_BASE + voff + + MDP4_QSEED_TABLE2_OFF); + for (i = 0; i < (sizeof(vg_qseed_table2) / sizeof(uint32)); i++) { + outpdw(off, vg_qseed_table2[i]); + off++; + if (!((uint32)off & 0x3FF)) + wmb(); + } + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + +} + +void mdp4_mixer_blend_init(mixer_num) +{ + unsigned char *overlay_base; + int off; + + if (mixer_num) /* mixer number, /dev/fb0, /dev/fb1 */ + overlay_base = MDP_BASE + MDP4_OVERLAYPROC1_BASE;/* 0x18000 */ + else + overlay_base = MDP_BASE + MDP4_OVERLAYPROC0_BASE;/* 0x10000 */ + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* stage 0 to stage 2 */ + off = 0; + outpdw(overlay_base + off + 0x104, 0x010); + outpdw(overlay_base + off + 0x108, 0xff);/* FG */ + outpdw(overlay_base + off + 0x10c, 0x00);/* BG */ + + off += 0x20; + outpdw(overlay_base + off + 0x104, 0x010); + outpdw(overlay_base + off + 0x108, 0xff);/* FG */ + outpdw(overlay_base + off + 0x10c, 0x00);/* BG */ + + off += 0x20; + outpdw(overlay_base + off + 0x104, 0x010); + outpdw(overlay_base + off + 0x108, 0xff);/* FG */ + outpdw(overlay_base + off + 0x10c, 0x00);/* BG */ + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + + +static uint32 csc_matrix_tab[9] = { + 0x0254, 0x0000, 0x0331, + 0x0254, 0xff37, 0xfe60, + 0x0254, 0x0409, 0x0000 +}; + +static uint32 csc_pre_bv_tab[3] = {0xfff0, 0xff80, 0xff80 }; +static uint32 csc_post_bv_tab[3] = {0, 0, 0 }; + +static uint32 csc_pre_lv_tab[6] = {0, 0xff, 0, 0xff, 0, 0xff }; +static uint32 csc_post_lv_tab[6] = {0, 0xff, 0, 0xff, 0, 0xff }; + +#define MDP4_CSC_MV_OFF 0x4400 +#define MDP4_CSC_PRE_BV_OFF 0x4500 +#define MDP4_CSC_POST_BV_OFF 0x4580 +#define MDP4_CSC_PRE_LV_OFF 0x4600 +#define MDP4_CSC_POST_LV_OFF 0x4680 + +void mdp4_vg_csc_mv_setup(int vp_num) +{ + uint32 *off; + int i, voff; + + voff = MDP4_VIDEO_OFF * vp_num; + off = (uint32 *)(MDP_BASE + MDP4_VIDEO_BASE + voff + + MDP4_CSC_MV_OFF); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 9; i++) { + outpdw(off, csc_matrix_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_vg_csc_pre_bv_setup(int vp_num) +{ + uint32 *off; + int i, voff; + + voff = MDP4_VIDEO_OFF * vp_num; + off = (uint32 *)(MDP_BASE + MDP4_VIDEO_BASE + voff + + MDP4_CSC_PRE_BV_OFF); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 3; i++) { + outpdw(off, csc_pre_bv_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_vg_csc_post_bv_setup(int vp_num) +{ + uint32 *off; + int i, voff; + + voff = MDP4_VIDEO_OFF * vp_num; + off = (uint32 *)(MDP_BASE + MDP4_VIDEO_BASE + voff + + MDP4_CSC_POST_BV_OFF); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 3; i++) { + outpdw(off, csc_post_bv_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_vg_csc_pre_lv_setup(int vp_num) +{ + uint32 *off; + int i, voff; + + voff = MDP4_VIDEO_OFF * vp_num; + off = (uint32 *)(MDP_BASE + MDP4_VIDEO_BASE + voff + + MDP4_CSC_PRE_LV_OFF); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 6; i++) { + outpdw(off, csc_pre_lv_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_vg_csc_post_lv_setup(int vp_num) +{ + uint32 *off; + int i, voff; + + voff = MDP4_VIDEO_OFF * vp_num; + off = (uint32 *)(MDP_BASE + MDP4_VIDEO_BASE + voff + + MDP4_CSC_POST_LV_OFF); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 6; i++) { + outpdw(off, csc_post_lv_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +static uint32 csc_rgb2yuv_matrix_tab[9] = { + 0x0083, 0x0102, 0x0032, + 0x1fb5, 0x1f6c, 0x00e1, + 0x00e1, 0x1f45, 0x1fdc +}; + +static uint32 csc_rgb2yuv_pre_bv_tab[3] = {0, 0, 0}; + +static uint32 csc_rgb2yuv_post_bv_tab[3] = {0x0010, 0x0080, 0x0080}; + +static uint32 csc_rgb2yuv_pre_lv_tab[6] = { + 0x00, 0xff, 0x00, + 0xff, 0x00, 0xff +}; + +static uint32 csc_rgb2yuv_post_lv_tab[6] = { + 0x0010, 0x00eb, 0x0010, + 0x00f0, 0x0010, 0x00f0 +}; + +void mdp4_mixer1_csc_mv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2400); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 9; i++) { + outpdw(off, csc_rgb2yuv_matrix_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_mixer1_csc_pre_bv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2500); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 3; i++) { + outpdw(off, csc_rgb2yuv_pre_bv_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_mixer1_csc_post_bv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2580); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 3; i++) { + outpdw(off, csc_rgb2yuv_post_bv_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_mixer1_csc_pre_lv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2600); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 6; i++) { + outpdw(off, csc_rgb2yuv_pre_lv_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +void mdp4_mixer1_csc_post_lv_setup(void) +{ + uint32 *off; + int i; + + off = (uint32 *)(MDP_BASE + MDP4_OVERLAYPROC1_BASE + 0x2680); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + for (i = 0; i < 6; i++) { + outpdw(off, csc_rgb2yuv_post_lv_tab[i]); + off++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + + +char gc_lut[] = { + 0x0, 0x1, 0x2, 0x2, 0x3, 0x4, 0x5, 0x6, + 0x6, 0x7, 0x8, 0x9, 0xA, 0xA, 0xB, 0xC, + 0xD, 0xD, 0xE, 0xF, 0xF, 0x10, 0x10, 0x11, + 0x12, 0x12, 0x13, 0x13, 0x14, 0x14, 0x15, 0x15, + 0x16, 0x16, 0x17, 0x17, 0x17, 0x18, 0x18, 0x19, + 0x19, 0x19, 0x1A, 0x1A, 0x1B, 0x1B, 0x1B, 0x1C, + 0x1C, 0x1D, 0x1D, 0x1D, 0x1E, 0x1E, 0x1E, 0x1F, + 0x1F, 0x1F, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, + 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, 0x24, + 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, + 0x26, 0x26, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, + 0x28, 0x29, 0x29, 0x29, 0x29, 0x2A, 0x2A, 0x2A, + 0x2A, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2C, 0x2C, + 0x2C, 0x2C, 0x2D, 0x2D, 0x2D, 0x2D, 0x2E, 0x2E, + 0x2E, 0x2E, 0x2E, 0x2F, 0x2F, 0x2F, 0x2F, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x3A, 0x3A, 0x3A, 0x3A, 0x3A, + 0x3A, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3B, 0x3C, + 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3D, 0x3D, 0x3D, + 0x3D, 0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, + 0x3E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, + 0x42, 0x42, 0x42, 0x43, 0x43, 0x43, 0x43, 0x43, + 0x43, 0x43, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, + 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x47, + 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x48, 0x48, + 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, + 0x49, 0x49, 0x49, 0x49, 0x49, 0x4A, 0x4A, 0x4A, + 0x4A, 0x4A, 0x4A, 0x4A, 0x4A, 0x4B, 0x4B, 0x4B, + 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4C, 0x4C, 0x4C, + 0x4C, 0x4C, 0x4C, 0x4C, 0x4D, 0x4D, 0x4D, 0x4D, + 0x4D, 0x4D, 0x4D, 0x4D, 0x4E, 0x4E, 0x4E, 0x4E, + 0x4E, 0x4E, 0x4E, 0x4E, 0x4E, 0x4F, 0x4F, 0x4F, + 0x4F, 0x4F, 0x4F, 0x4F, 0x4F, 0x50, 0x50, 0x50, + 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51, 0x51, + 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, + 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x53, 0x53, + 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, + 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, + 0x56, 0x56, 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, + 0x57, 0x57, 0x57, 0x58, 0x58, 0x58, 0x58, 0x58, + 0x58, 0x58, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, + 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x5A, 0x5A, + 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, + 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, 0x5B, + 0x5B, 0x5B, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, + 0x5C, 0x5C, 0x5C, 0x5C, 0x5D, 0x5D, 0x5D, 0x5D, + 0x5D, 0x5D, 0x5D, 0x5D, 0x5D, 0x5D, 0x5E, 0x5E, + 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, 0x5E, + 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, 0x5F, + 0x5F, 0x5F, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, + 0x60, 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x62, + 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, + 0x62, 0x62, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, + 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, + 0x67, 0x67, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, + 0x68, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x6A, 0x6A, 0x6A, 0x6A, 0x6A, 0x6A, 0x6A, + 0x6A, 0x6A, 0x6A, 0x6A, 0x6A, 0x6B, 0x6B, 0x6B, + 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, + 0x6B, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, + 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6D, 0x6D, 0x6D, + 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, 0x6D, + 0x6D, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E, + 0x6E, 0x6E, 0x6E, 0x6E, 0x6E, 0x6F, 0x6F, 0x6F, + 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, + 0x6F, 0x6F, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, + 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x71, 0x71, + 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, 0x71, + 0x71, 0x71, 0x71, 0x72, 0x72, 0x72, 0x72, 0x72, + 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, + 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, + 0x73, 0x73, 0x73, 0x73, 0x73, 0x74, 0x74, 0x74, + 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, 0x74, + 0x74, 0x74, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, 0x75, + 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, 0x76, + 0x76, 0x76, 0x76, 0x76, 0x76, 0x77, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, + 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, + 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x7A, 0x7A, + 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, + 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7B, 0x7B, 0x7B, + 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, + 0x7B, 0x7B, 0x7B, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, + 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, + 0x7C, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, + 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, + 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, + 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7F, 0x7F, + 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, 0x8A, + 0x8A, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, 0x8B, + 0x8B, 0x8B, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, + 0x8C, 0x8C, 0x8C, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, + 0x8D, 0x8D, 0x8D, 0x8D, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, + 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, + 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, + 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, + 0x9C, 0x9C, 0x9C, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, + 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, + 0x9E, 0x9E, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, + 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, + 0xA0, 0xA0, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, + 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA1, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, 0xA2, + 0xA2, 0xA2, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, + 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, + 0xA4, 0xA4, 0xA4, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, + 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, + 0xA7, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, + 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, 0xA9, + 0xA9, 0xA9, 0xA9, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, + 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, + 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, 0xAD, + 0xAD, 0xAD, 0xAD, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, + 0xAE, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, + 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xB0, + 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, + 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, + 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB1, 0xB1, + 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, + 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, + 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB2, 0xB2, 0xB2, + 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, + 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, + 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, + 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, + 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, + 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, + 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, + 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, + 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, + 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, + 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, + 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, + 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, + 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, + 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB8, + 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, + 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, + 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB9, + 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, + 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, + 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xBA, + 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, + 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, + 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBA, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, + 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, + 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, 0xBC, + 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, + 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, + 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, + 0xBD, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, + 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, + 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, + 0xBE, 0xBE, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, 0xBF, + 0xBF, 0xBF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, + 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, + 0xC0, 0xC0, 0xC0, 0xC0, 0xC1, 0xC1, 0xC1, 0xC1, + 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, + 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, + 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC2, 0xC2, 0xC2, + 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, + 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, + 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC3, 0xC3, + 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, + 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, + 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, + 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, + 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, + 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, 0xC4, + 0xC4, 0xC4, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, + 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, + 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, 0xC5, + 0xC5, 0xC5, 0xC5, 0xC5, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC7, 0xC7, + 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, + 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, + 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, + 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, + 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, + 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, + 0xC8, 0xC8, 0xC8, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, + 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, + 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, + 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xC9, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA, + 0xCA, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, + 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, + 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, 0xCB, + 0xCB, 0xCB, 0xCB, 0xCB, 0xCC, 0xCC, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCD, + 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, + 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, + 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, + 0xCD, 0xCD, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, + 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, + 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, + 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCE, 0xCF, 0xCF, + 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, + 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, + 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, + 0xCF, 0xCF, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, + 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, + 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, + 0xD0, 0xD0, 0xD0, 0xD0, 0xD0, 0xD1, 0xD1, 0xD1, + 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, + 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, + 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, 0xD1, + 0xD1, 0xD1, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, + 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, + 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, + 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD2, 0xD3, 0xD3, + 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, + 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, + 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, 0xD3, + 0xD3, 0xD3, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, + 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, + 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, + 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD4, 0xD5, + 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, + 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, + 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, 0xD5, + 0xD5, 0xD5, 0xD5, 0xD5, 0xD6, 0xD6, 0xD6, 0xD6, + 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, + 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, + 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, 0xD6, + 0xD6, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, + 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, + 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, + 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD7, 0xD8, 0xD8, + 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, + 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, + 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, + 0xD8, 0xD8, 0xD8, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, + 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, + 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, + 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, + 0xD9, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, + 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, + 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, + 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xDB, 0xDC, 0xDC, 0xDC, 0xDC, + 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, + 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, + 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC, + 0xDC, 0xDC, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, + 0xDD, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, + 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, + 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, + 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE, 0xDF, + 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, + 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, + 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, + 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xE0, 0xE0, + 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, + 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, + 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, + 0xE0, 0xE0, 0xE0, 0xE0, 0xE1, 0xE1, 0xE1, 0xE1, + 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, + 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, + 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE1, + 0xE1, 0xE1, 0xE1, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, + 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, + 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, + 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2, + 0xE2, 0xE2, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, + 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, + 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, + 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, + 0xE3, 0xE3, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, + 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, + 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, + 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, 0xE4, + 0xE4, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, + 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, + 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, + 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, 0xE5, + 0xE5, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, + 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, + 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, + 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, + 0xE6, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, + 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, + 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, + 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, + 0xE7, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, + 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, + 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, + 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, + 0xE8, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, + 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, + 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, + 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, 0xE9, + 0xE9, 0xE9, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, + 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, + 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, + 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, + 0xEA, 0xEA, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, + 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, + 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, + 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, + 0xEB, 0xEB, 0xEB, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, + 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, + 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, + 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, 0xEC, + 0xEC, 0xEC, 0xEC, 0xEC, 0xED, 0xED, 0xED, 0xED, + 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, + 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, + 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, 0xED, + 0xED, 0xED, 0xED, 0xED, 0xED, 0xEE, 0xEE, 0xEE, + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEF, 0xEF, + 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, + 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, + 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, + 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, 0xEF, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, + 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, + 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, + 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, + 0xF1, 0xF1, 0xF1, 0xF1, 0xF2, 0xF2, 0xF2, 0xF2, + 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, + 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, + 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, + 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF3, 0xF3, + 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, + 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, + 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, + 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, + 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, + 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, + 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, + 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, 0xF4, + 0xF4, 0xF4, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, + 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, + 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, + 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, + 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF6, 0xF6, 0xF6, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, + 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, 0xF6, + 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, + 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, + 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, + 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, + 0xF7, 0xF7, 0xF7, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, + 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, + 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, + 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, + 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF9, 0xF9, + 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, + 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, + 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, + 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, + 0xF9, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, + 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, + 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, + 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, + 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFB, 0xFB, 0xFB, + 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, + 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, + 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, + 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, + 0xFB, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, + 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, + 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, + 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, + 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFD, 0xFD, 0xFD, + 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, + 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, + 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, + 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, + 0xFD, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + +void mdp4_mixer_gc_lut_setup(int mixer_num) +{ + unsigned char *base; + uint32 data; + char val; + int i, off; + + if (mixer_num) /* mixer number, /dev/fb0, /dev/fb1 */ + base = MDP_BASE + MDP4_OVERLAYPROC1_BASE;/* 0x18000 */ + else + base = MDP_BASE + MDP4_OVERLAYPROC0_BASE;/* 0x10000 */ + + base += 0x4000; /* GC_LUT offset */ + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + off = 0; + for (i = 0; i < 4096; i++) { + val = gc_lut[i]; + data = (val << 16 | val << 8 | val); /* R, B, and G are same */ + outpdw(base + off, data); + off += 4; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +uint32 igc_video_lut[] = { /* non linear */ + 0x0, 0x1, 0x2, 0x4, 0x5, 0x6, 0x7, 0x9, + 0xA, 0xB, 0xC, 0xE, 0xF, 0x10, 0x12, 0x14, + 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F, 0x21, 0x23, + 0x25, 0x28, 0x2A, 0x2D, 0x30, 0x32, 0x35, 0x38, + 0x3B, 0x3E, 0x42, 0x45, 0x48, 0x4C, 0x4F, 0x53, + 0x57, 0x5B, 0x5F, 0x63, 0x67, 0x6B, 0x70, 0x74, + 0x79, 0x7E, 0x83, 0x88, 0x8D, 0x92, 0x97, 0x9C, + 0xA2, 0xA8, 0xAD, 0xB3, 0xB9, 0xBF, 0xC5, 0xCC, + 0xD2, 0xD8, 0xDF, 0xE6, 0xED, 0xF4, 0xFB, 0x102, + 0x109, 0x111, 0x118, 0x120, 0x128, 0x130, 0x138, 0x140, + 0x149, 0x151, 0x15A, 0x162, 0x16B, 0x174, 0x17D, 0x186, + 0x190, 0x199, 0x1A3, 0x1AC, 0x1B6, 0x1C0, 0x1CA, 0x1D5, + 0x1DF, 0x1EA, 0x1F4, 0x1FF, 0x20A, 0x215, 0x220, 0x22B, + 0x237, 0x242, 0x24E, 0x25A, 0x266, 0x272, 0x27F, 0x28B, + 0x298, 0x2A4, 0x2B1, 0x2BE, 0x2CB, 0x2D8, 0x2E6, 0x2F3, + 0x301, 0x30F, 0x31D, 0x32B, 0x339, 0x348, 0x356, 0x365, + 0x374, 0x383, 0x392, 0x3A1, 0x3B1, 0x3C0, 0x3D0, 0x3E0, + 0x3F0, 0x400, 0x411, 0x421, 0x432, 0x443, 0x454, 0x465, + 0x476, 0x487, 0x499, 0x4AB, 0x4BD, 0x4CF, 0x4E1, 0x4F3, + 0x506, 0x518, 0x52B, 0x53E, 0x551, 0x565, 0x578, 0x58C, + 0x5A0, 0x5B3, 0x5C8, 0x5DC, 0x5F0, 0x605, 0x61A, 0x62E, + 0x643, 0x659, 0x66E, 0x684, 0x699, 0x6AF, 0x6C5, 0x6DB, + 0x6F2, 0x708, 0x71F, 0x736, 0x74D, 0x764, 0x77C, 0x793, + 0x7AB, 0x7C3, 0x7DB, 0x7F3, 0x80B, 0x824, 0x83D, 0x855, + 0x86F, 0x888, 0x8A1, 0x8BB, 0x8D4, 0x8EE, 0x908, 0x923, + 0x93D, 0x958, 0x973, 0x98E, 0x9A9, 0x9C4, 0x9DF, 0x9FB, + 0xA17, 0xA33, 0xA4F, 0xA6C, 0xA88, 0xAA5, 0xAC2, 0xADF, + 0xAFC, 0xB19, 0xB37, 0xB55, 0xB73, 0xB91, 0xBAF, 0xBCE, + 0xBEC, 0xC0B, 0xC2A, 0xC4A, 0xC69, 0xC89, 0xCA8, 0xCC8, + 0xCE8, 0xD09, 0xD29, 0xD4A, 0xD6B, 0xD8C, 0xDAD, 0xDCF, + 0xDF0, 0xE12, 0xE34, 0xE56, 0xE79, 0xE9B, 0xEBE, 0xEE1, + 0xF04, 0xF27, 0xF4B, 0xF6E, 0xF92, 0xFB6, 0xFDB, 0xFFF, +}; + +void mdp4_vg_igc_lut_setup(int vp_num) +{ + unsigned char *base; + int i, voff, off; + uint32 data, val; + + voff = MDP4_VIDEO_OFF * vp_num; + base = MDP_BASE + MDP4_VIDEO_BASE + voff + 0x5000; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + off = 0; + for (i = 0; i < 256; i++) { + val = igc_video_lut[i]; + data = (val << 16 | val); /* color 0 and 1 */ + outpdw(base + off, data); + outpdw(base + off + 0x800, val); /* color 2 */ + off += 4; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +uint32 igc_rgb_lut[] = { /* linear */ + 0x0, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, + 0x80, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1, + 0x101, 0x111, 0x121, 0x131, 0x141, 0x151, 0x161, 0x171, + 0x181, 0x191, 0x1A2, 0x1B2, 0x1C2, 0x1D2, 0x1E2, 0x1F2, + 0x202, 0x212, 0x222, 0x232, 0x242, 0x252, 0x262, 0x272, + 0x282, 0x292, 0x2A2, 0x2B3, 0x2C3, 0x2D3, 0x2E3, 0x2F3, + 0x303, 0x313, 0x323, 0x333, 0x343, 0x353, 0x363, 0x373, + 0x383, 0x393, 0x3A3, 0x3B3, 0x3C4, 0x3D4, 0x3E4, 0x3F4, + 0x404, 0x414, 0x424, 0x434, 0x444, 0x454, 0x464, 0x474, + 0x484, 0x494, 0x4A4, 0x4B4, 0x4C4, 0x4D5, 0x4E5, 0x4F5, + 0x505, 0x515, 0x525, 0x535, 0x545, 0x555, 0x565, 0x575, + 0x585, 0x595, 0x5A5, 0x5B5, 0x5C5, 0x5D5, 0x5E6, 0x5F6, + 0x606, 0x616, 0x626, 0x636, 0x646, 0x656, 0x666, 0x676, + 0x686, 0x696, 0x6A6, 0x6B6, 0x6C6, 0x6D6, 0x6E6, 0x6F7, + 0x707, 0x717, 0x727, 0x737, 0x747, 0x757, 0x767, 0x777, + 0x787, 0x797, 0x7A7, 0x7B7, 0x7C7, 0x7D7, 0x7E7, 0x7F7, + 0x808, 0x818, 0x828, 0x838, 0x848, 0x858, 0x868, 0x878, + 0x888, 0x898, 0x8A8, 0x8B8, 0x8C8, 0x8D8, 0x8E8, 0x8F8, + 0x908, 0x919, 0x929, 0x939, 0x949, 0x959, 0x969, 0x979, + 0x989, 0x999, 0x9A9, 0x9B9, 0x9C9, 0x9D9, 0x9E9, 0x9F9, + 0xA09, 0xA19, 0xA2A, 0xA3A, 0xA4A, 0xA5A, 0xA6A, 0xA7A, + 0xA8A, 0xA9A, 0xAAA, 0xABA, 0xACA, 0xADA, 0xAEA, 0xAFA, + 0xB0A, 0xB1A, 0xB2A, 0xB3B, 0xB4B, 0xB5B, 0xB6B, 0xB7B, + 0xB8B, 0xB9B, 0xBAB, 0xBBB, 0xBCB, 0xBDB, 0xBEB, 0xBFB, + 0xC0B, 0xC1B, 0xC2B, 0xC3B, 0xC4C, 0xC5C, 0xC6C, 0xC7C, + 0xC8C, 0xC9C, 0xCAC, 0xCBC, 0xCCC, 0xCDC, 0xCEC, 0xCFC, + 0xD0C, 0xD1C, 0xD2C, 0xD3C, 0xD4C, 0xD5D, 0xD6D, 0xD7D, + 0xD8D, 0xD9D, 0xDAD, 0xDBD, 0xDCD, 0xDDD, 0xDED, 0xDFD, + 0xE0D, 0xE1D, 0xE2D, 0xE3D, 0xE4D, 0xE5D, 0xE6E, 0xE7E, + 0xE8E, 0xE9E, 0xEAE, 0xEBE, 0xECE, 0xEDE, 0xEEE, 0xEFE, + 0xF0E, 0xF1E, 0xF2E, 0xF3E, 0xF4E, 0xF5E, 0xF6E, 0xF7F, + 0xF8F, 0xF9F, 0xFAF, 0xFBF, 0xFCF, 0xFDF, 0xFEF, 0xFFF, +}; + +void mdp4_rgb_igc_lut_setup(int num) +{ + unsigned char *base; + int i, voff, off; + uint32 data, val; + + voff = MDP4_RGB_OFF * num; + base = MDP_BASE + MDP4_RGB_BASE + voff + 0x5000; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + off = 0; + for (i = 0; i < 256; i++) { + val = igc_rgb_lut[i]; + data = (val << 16 | val); /* color 0 and 1 */ + outpdw(base + off, data); + outpdw(base + off + 0x800, val); /* color 2 */ + off += 4; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +uint32 mdp4_rgb_igc_lut_cvt(uint32 ndx) +{ + return igc_rgb_lut[ndx & 0x0ff]; +} + +uint32_t mdp4_ss_table_value(int8_t value, int8_t index) +{ + uint32_t out = 0x0; + int8_t level = -1; + uint32_t mask = 0xffffffff; + + if (value < 0) { + if (value == -128) + value = 127; + else + value = -value; + out = 0x11111111; + } else { + out = 0x88888888; + mask = 0x0fffffff; + } + + if (value == 0) + level = 0; + else { + while (value > 0 && level < 7) { + level++; + value -= 16; + } + } + + if (level == 0) { + if (index == 0) + out = 0x0; + else + out = 0x20000000; + } else { + out += (0x11111111 * level); + if (index == 1) + out &= mask; + } + + return out; +} + diff --git a/drivers/video/msm/mdp_csc_table.h b/drivers/video/msm/mdp_csc_table.h index d1cde30ead5..a0f72c0ebd4 100644 --- a/drivers/video/msm/mdp_csc_table.h +++ b/drivers/video/msm/mdp_csc_table.h @@ -1,4 +1,4 @@ -/* drivers/video/msm_fb/mdp_csc_table.h +/* drivers/video/msm/mdp_csc_table.h * * Copyright (C) 2007 QUALCOMM Incorporated * Copyright (C) 2007 Google Incorporated @@ -16,57 +16,116 @@ static struct { uint32_t reg; uint32_t val; -} csc_table[] = { - { 0x40400, 0x83 }, - { 0x40404, 0x102 }, - { 0x40408, 0x32 }, - { 0x4040c, 0xffffffb5 }, - { 0x40410, 0xffffff6c }, - { 0x40414, 0xe1 }, - { 0x40418, 0xe1 }, - { 0x4041c, 0xffffff45 }, - { 0x40420, 0xffffffdc }, - { 0x40440, 0x254 }, - { 0x40444, 0x0 }, - { 0x40448, 0x331 }, - { 0x4044c, 0x254 }, - { 0x40450, 0xffffff38 }, - { 0x40454, 0xfffffe61 }, - { 0x40458, 0x254 }, - { 0x4045c, 0x409 }, - { 0x40460, 0x0 }, - { 0x40480, 0x5d }, - { 0x40484, 0x13a }, - { 0x40488, 0x20 }, - { 0x4048c, 0xffffffcd }, - { 0x40490, 0xffffff54 }, - { 0x40494, 0xe1 }, - { 0x40498, 0xe1 }, - { 0x4049c, 0xffffff35 }, - { 0x404a0, 0xffffffec }, - { 0x404c0, 0x254 }, - { 0x404c4, 0x0 }, - { 0x404c8, 0x396 }, - { 0x404cc, 0x254 }, - { 0x404d0, 0xffffff94 }, - { 0x404d4, 0xfffffef0 }, - { 0x404d8, 0x254 }, - { 0x404dc, 0x43a }, - { 0x404e0, 0x0 }, - { 0x40500, 0x10 }, - { 0x40504, 0x80 }, - { 0x40508, 0x80 }, - { 0x40540, 0x10 }, - { 0x40544, 0x80 }, - { 0x40548, 0x80 }, - { 0x40580, 0x10 }, - { 0x40584, 0xeb }, - { 0x40588, 0x10 }, - { 0x4058c, 0xf0 }, - { 0x405c0, 0x10 }, - { 0x405c4, 0xeb }, - { 0x405c8, 0x10 }, - { 0x405cc, 0xf0 }, +} csc_matrix_config_table[] = { + /* RGB -> YUV primary forward matrix (set1). */ + { MDP_CSC_PFMVn(0), 0x83 }, + { MDP_CSC_PFMVn(1), 0x102 }, + { MDP_CSC_PFMVn(2), 0x32 }, + { MDP_CSC_PFMVn(3), 0xffffffb5 }, + { MDP_CSC_PFMVn(4), 0xffffff6c }, + { MDP_CSC_PFMVn(5), 0xe1 }, + { MDP_CSC_PFMVn(6), 0xe1 }, + { MDP_CSC_PFMVn(7), 0xffffff45 }, + { MDP_CSC_PFMVn(8), 0xffffffdc }, + + /* YUV -> RGB primary reverse matrix (set2) */ + { MDP_CSC_PRMVn(0), 0x254 }, + { MDP_CSC_PRMVn(1), 0x0 }, + { MDP_CSC_PRMVn(2), 0x331 }, + { MDP_CSC_PRMVn(3), 0x254 }, + { MDP_CSC_PRMVn(4), 0xffffff38 }, + { MDP_CSC_PRMVn(5), 0xfffffe61 }, + { MDP_CSC_PRMVn(6), 0x254 }, + { MDP_CSC_PRMVn(7), 0x409 }, + { MDP_CSC_PRMVn(8), 0x0 }, + +#ifndef CONFIG_MSM_MDP31 + /* For MDP 2.2/3.0 */ + + /* primary limit vector */ + { MDP_CSC_PLVn(0), 0x10 }, + { MDP_CSC_PLVn(1), 0xeb }, + { MDP_CSC_PLVn(2), 0x10 }, + { MDP_CSC_PLVn(3), 0xf0 }, + + /* primary bias vector */ + { MDP_CSC_PBVn(0), 0x10 }, + { MDP_CSC_PBVn(1), 0x80 }, + { MDP_CSC_PBVn(2), 0x80 }, + +#else /* CONFIG_MSM_MDP31 */ + + /* limit vectors configuration */ + /* rgb -> yuv (set1) pre-limit vector */ + { MDP_PPP_CSC_PRE_LV1n(0), 0x10 }, + { MDP_PPP_CSC_PRE_LV1n(1), 0xeb }, + { MDP_PPP_CSC_PRE_LV1n(2), 0x10 }, + { MDP_PPP_CSC_PRE_LV1n(3), 0xf0 }, + { MDP_PPP_CSC_PRE_LV1n(4), 0x10 }, + { MDP_PPP_CSC_PRE_LV1n(5), 0xf0 }, + + /* rgb -> yuv (set1) post-limit vector */ + { MDP_PPP_CSC_POST_LV1n(0), 0x0 }, + { MDP_PPP_CSC_POST_LV1n(1), 0xff }, + { MDP_PPP_CSC_POST_LV1n(2), 0x0 }, + { MDP_PPP_CSC_POST_LV1n(3), 0xff }, + { MDP_PPP_CSC_POST_LV1n(4), 0x0 }, + { MDP_PPP_CSC_POST_LV1n(5), 0xff }, + + /* yuv -> rgb (set2) pre-limit vector */ + { MDP_PPP_CSC_PRE_LV2n(0), 0x0 }, + { MDP_PPP_CSC_PRE_LV2n(1), 0xff }, + { MDP_PPP_CSC_PRE_LV2n(2), 0x0 }, + { MDP_PPP_CSC_PRE_LV2n(3), 0xff }, + { MDP_PPP_CSC_PRE_LV2n(4), 0x0 }, + { MDP_PPP_CSC_PRE_LV2n(5), 0xff }, + + /* yuv -> rgb (set2) post-limit vector */ + { MDP_PPP_CSC_POST_LV2n(0), 0x10 }, + { MDP_PPP_CSC_POST_LV2n(1), 0xeb }, + { MDP_PPP_CSC_POST_LV2n(2), 0x10 }, + { MDP_PPP_CSC_POST_LV2n(3), 0xf0 }, + { MDP_PPP_CSC_POST_LV2n(4), 0x10 }, + { MDP_PPP_CSC_POST_LV2n(5), 0xf0 }, + + /* bias vectors configuration */ + + /* XXX: why is set2 used for rgb->yuv, but set1 */ + /* used for yuv -> rgb??!? Seems to be the reverse of the + * other vectors. */ + + /* RGB -> YUV pre-bias vector... */ + { MDP_PPP_CSC_PRE_BV2n(0), 0 }, + { MDP_PPP_CSC_PRE_BV2n(1), 0 }, + { MDP_PPP_CSC_PRE_BV2n(2), 0 }, + + /* RGB -> YUV post-bias vector */ + { MDP_PPP_CSC_POST_BV2n(0), 0x10 }, + { MDP_PPP_CSC_POST_BV2n(1), 0x80 }, + { MDP_PPP_CSC_POST_BV2n(2), 0x80 }, + + /* YUV -> RGB pre-bias vector... */ + { MDP_PPP_CSC_PRE_BV1n(0), 0x1f0 }, + { MDP_PPP_CSC_PRE_BV1n(1), 0x180 }, + { MDP_PPP_CSC_PRE_BV1n(2), 0x180 }, + + /* YUV -> RGB post-bias vector */ + { MDP_PPP_CSC_POST_BV1n(0), 0 }, + { MDP_PPP_CSC_POST_BV1n(1), 0 }, + { MDP_PPP_CSC_POST_BV1n(2), 0 }, + + /* luma filter coefficients */ + { MDP_PPP_DEINT_COEFFn(0), 0x3e0 }, + { MDP_PPP_DEINT_COEFFn(1), 0x360 }, + { MDP_PPP_DEINT_COEFFn(2), 0x120 }, + { MDP_PPP_DEINT_COEFFn(3), 0x140 }, +#endif +}; + +static struct { + uint32_t reg; + uint32_t val; +} csc_color_lut[] = { { 0x40800, 0x0 }, { 0x40804, 0x151515 }, { 0x40808, 0x1d1d1d }, diff --git a/drivers/video/msm/mdp_cursor.c b/drivers/video/msm/mdp_cursor.c new file mode 100644 index 00000000000..f8c08e36066 --- /dev/null +++ b/drivers/video/msm/mdp_cursor.c @@ -0,0 +1,264 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "mdp.h" +#include "msm_fb.h" + +static int cursor_enabled; + +#include "mdp4.h" + +#if defined(CONFIG_FB_MSM_OVERLAY) && defined(CONFIG_FB_MSM_MDP40) +static struct workqueue_struct *mdp_cursor_ctrl_wq; +static struct work_struct mdp_cursor_ctrl_worker; + +/* cursor configuration */ +static void *cursor_buf_phys; +static __u32 width, height, bg_color; +static int calpha_en, transp_en, alpha; +static int sync_disabled = -1; + +void mdp_cursor_ctrl_workqueue_handler(struct work_struct *work) +{ + unsigned long flag; + + /* disable vsync */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_disable_irq(MDP_OVERLAY0_TERM); + spin_unlock_irqrestore(&mdp_spin_lock, flag); +} + +void mdp_hw_cursor_init(void) +{ + mdp_cursor_ctrl_wq = + create_singlethread_workqueue("mdp_cursor_ctrl_wq"); + INIT_WORK(&mdp_cursor_ctrl_worker, mdp_cursor_ctrl_workqueue_handler); +} + +void mdp_hw_cursor_done(void) +{ + /* Cursor configuration: + * + * This is done in DMA_P_DONE ISR because the following registers are + * not double buffered in hardware: + * + * MDP_DMA_P_CURSOR_SIZE, address = 0x90044 + * MDP_DMA_P_CURSOR_BLEND_CONFIG, address = 0x90060 + * MDP_DMA_P_CURSOR_BLEND_PARAM, address = 0x90064 + * MDP_DMA_P_CURSOR_BLEND_TRANS_LOW, address = 0x90068 + * MDP_DMA_P_CURSOR_BLEND_TRANS_HIG, address = 0x9006C + * + * Moving this code out of the ISR will cause the MDP to underrun! + */ + spin_lock(&mdp_spin_lock); + if (sync_disabled) { + spin_unlock(&mdp_spin_lock); + return; + } + + MDP_OUTP(MDP_BASE + 0x90044, (height << 16) | width); + MDP_OUTP(MDP_BASE + 0x90048, cursor_buf_phys); + + MDP_OUTP(MDP_BASE + 0x90060, + (transp_en << 3) | (calpha_en << 1) | + (inp32(MDP_BASE + 0x90060) & 0x1)); + + MDP_OUTP(MDP_BASE + 0x90064, (alpha << 24)); + MDP_OUTP(MDP_BASE + 0x90068, (0xffffff & bg_color)); + MDP_OUTP(MDP_BASE + 0x9006C, (0xffffff & bg_color)); + + /* enable/disable the cursor as per the last request */ + if (cursor_enabled && !(inp32(MDP_BASE + 0x90060) & (0x1))) + MDP_OUTP(MDP_BASE + 0x90060, inp32(MDP_BASE + 0x90060) | 0x1); + else if (!cursor_enabled && (inp32(MDP_BASE + 0x90060) & (0x1))) + MDP_OUTP(MDP_BASE + 0x90060, + inp32(MDP_BASE + 0x90060) & (~0x1)); + + /* enqueue the task to disable MDP interrupts */ + queue_work(mdp_cursor_ctrl_wq, &mdp_cursor_ctrl_worker); + + /* update done */ + sync_disabled = 1; + spin_unlock(&mdp_spin_lock); +} + +static void mdp_hw_cursor_enable_vsync(void) +{ + /* if the cursor registers were updated (once or more) since the + * last vsync, enable the vsync interrupt (if not already enabled) + * for the next update + */ + if (sync_disabled) { + + /* cancel pending task to disable MDP interrupts */ + if (work_pending(&mdp_cursor_ctrl_worker)) + cancel_work_sync(&mdp_cursor_ctrl_worker); + else + /* enable irq */ + mdp_enable_irq(MDP_OVERLAY0_TERM); + + sync_disabled = 0; + + /* enable vsync intr */ + outp32(MDP_INTR_CLEAR, INTR_OVERLAY0_DONE); + mdp_intr_mask |= INTR_OVERLAY0_DONE; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + } +} + +int mdp_hw_cursor_sync_update(struct fb_info *info, struct fb_cursor *cursor) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct fb_image *img = &cursor->image; + unsigned long flag; + int sync_needed = 0, ret = 0; + + if ((img->width > MDP_CURSOR_WIDTH) || + (img->height > MDP_CURSOR_HEIGHT) || + (img->depth != 32)) + return -EINVAL; + + if (cursor->set & FB_CUR_SETPOS) + MDP_OUTP(MDP_BASE + 0x9004c, (img->dy << 16) | img->dx); + + if (cursor->set & FB_CUR_SETIMAGE) { + ret = copy_from_user(mfd->cursor_buf, img->data, + img->width*img->height*4); + if (ret) + return ret; + + spin_lock_irqsave(&mdp_spin_lock, flag); + if (img->bg_color == 0xffffffff) + transp_en = 0; + else + transp_en = 1; + + alpha = (img->fg_color & 0xff000000) >> 24; + + if (alpha) + calpha_en = 0x2; /* xrgb */ + else + calpha_en = 0x1; /* argb */ + + /* cursor parameters */ + height = img->height; + width = img->width; + bg_color = img->bg_color; + cursor_buf_phys = mfd->cursor_buf_phys; + + sync_needed = 1; + } else + spin_lock_irqsave(&mdp_spin_lock, flag); + + if ((cursor->enable) && (!cursor_enabled)) { + cursor_enabled = 1; + sync_needed = 1; + } else if ((!cursor->enable) && (cursor_enabled)) { + cursor_enabled = 0; + sync_needed = 1; + } + + /* if sync cursor update is needed, enable vsync */ + if (sync_needed) + mdp_hw_cursor_enable_vsync(); + + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + return 0; +} +#endif /* CONFIG_FB_MSM_OVERLAY && CONFIG_FB_MSM_MDP40 */ + +int mdp_hw_cursor_update(struct fb_info *info, struct fb_cursor *cursor) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct fb_image *img = &cursor->image; + int calpha_en, transp_en; + int alpha; + int ret = 0; + + if ((img->width > MDP_CURSOR_WIDTH) || + (img->height > MDP_CURSOR_HEIGHT) || + (img->depth != 32)) + return -EINVAL; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + if (cursor->set & FB_CUR_SETPOS) + MDP_OUTP(MDP_BASE + 0x9004c, (img->dy << 16) | img->dx); + + if (cursor->set & FB_CUR_SETIMAGE) { + ret = copy_from_user(mfd->cursor_buf, img->data, + img->width*img->height*4); + if (ret) + return ret; + + if (img->bg_color == 0xffffffff) + transp_en = 0; + else + transp_en = 1; + + alpha = (img->fg_color & 0xff000000) >> 24; + + if (alpha) + calpha_en = 0x2; /* xrgb */ + else + calpha_en = 0x1; /* argb */ + + MDP_OUTP(MDP_BASE + 0x90044, (img->height << 16) | img->width); + MDP_OUTP(MDP_BASE + 0x90048, mfd->cursor_buf_phys); + /* order the writes the cursor_buf before updating the + * hardware */ + dma_coherent_pre_ops(); + MDP_OUTP(MDP_BASE + 0x90060, + (transp_en << 3) | (calpha_en << 1) | + (inp32(MDP_BASE + 0x90060) & 0x1)); +#ifdef CONFIG_FB_MSM_MDP40 + MDP_OUTP(MDP_BASE + 0x90064, (alpha << 24)); + MDP_OUTP(MDP_BASE + 0x90068, (0xffffff & img->bg_color)); + MDP_OUTP(MDP_BASE + 0x9006C, (0xffffff & img->bg_color)); +#else + MDP_OUTP(MDP_BASE + 0x90064, + (alpha << 24) | (0xffffff & img->bg_color)); + MDP_OUTP(MDP_BASE + 0x90068, 0); +#endif + } + + if ((cursor->enable) && (!cursor_enabled)) { + cursor_enabled = 1; + MDP_OUTP(MDP_BASE + 0x90060, inp32(MDP_BASE + 0x90060) | 0x1); + } else if ((!cursor->enable) && (cursor_enabled)) { + cursor_enabled = 0; + MDP_OUTP(MDP_BASE + 0x90060, + inp32(MDP_BASE + 0x90060) & (~0x1)); + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return 0; +} diff --git a/drivers/video/msm/mdp_debugfs.c b/drivers/video/msm/mdp_debugfs.c new file mode 100644 index 00000000000..b89e8c77b30 --- /dev/null +++ b/drivers/video/msm/mdp_debugfs.c @@ -0,0 +1,1289 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdp.h" +#include "msm_fb.h" +#ifdef CONFIG_FB_MSM_MDP40 +#include "mdp4.h" +#endif +#include "mddihosti.h" +#include "tvenc.h" +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +#include "hdmi_msm.h" +#endif + +#define MDP_DEBUG_BUF 2048 + +static uint32 mdp_offset; +static uint32 mdp_count; + +static char debug_buf[MDP_DEBUG_BUF]; + +/* + * MDP4 + * + */ + +static int mdp_offset_open(struct inode *inode, struct file *file) +{ + /* non-seekable */ + file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); + return 0; +} + +static int mdp_offset_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t mdp_offset_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + uint32 off, cnt; + + if (count >= sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + debug_buf[count] = 0; /* end of string */ + + sscanf(debug_buf, "%x %d", &off, &cnt); + + if (cnt <= 0) + cnt = 1; + + mdp_offset = off; + mdp_count = cnt; + + printk(KERN_INFO "%s: offset=%x cnt=%d\n", __func__, + mdp_offset, mdp_count); + + return count; +} + +static ssize_t mdp_offset_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int len = 0; + + + if (*ppos) + return 0; /* the end */ + + len = snprintf(debug_buf, sizeof(debug_buf), "0x%08x %d\n", + mdp_offset, mdp_count); + if (len < 0) + return 0; + + if (copy_to_user(buff, debug_buf, len)) + return -EFAULT; + + *ppos += len; /* increase offset */ + + return len; +} + +static const struct file_operations mdp_off_fops = { + .open = mdp_offset_open, + .release = mdp_offset_release, + .read = mdp_offset_read, + .write = mdp_offset_write, +}; + +static int mdp_reg_open(struct inode *inode, struct file *file) +{ + /* non-seekable */ + file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); + return 0; +} + +static int mdp_reg_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t mdp_reg_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + uint32 off, data; + int cnt; + + if (count >= sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + debug_buf[count] = 0; /* end of string */ + + cnt = sscanf(debug_buf, "%x %x", &off, &data); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + outpdw(MDP_BASE + off, data); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + printk(KERN_INFO "%s: addr=%x data=%x\n", __func__, off, data); + + return count; +} + +static ssize_t mdp_reg_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int len = 0; + uint32 data; + int i, j, off, dlen, num; + char *bp, *cp; + int tot = 0; + + + if (*ppos) + return 0; /* the end */ + + j = 0; + num = 0; + bp = debug_buf; + cp = MDP_BASE + mdp_offset; + dlen = sizeof(debug_buf); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + while (j++ < 8) { + len = snprintf(bp, dlen, "0x%08x: ", (int)cp); + tot += len; + bp += len; + dlen -= len; + off = 0; + i = 0; + while (i++ < 4) { + data = inpdw(cp + off); + len = snprintf(bp, dlen, "%08x ", data); + tot += len; + bp += len; + dlen -= len; + off += 4; + num++; + if (num >= mdp_count) + break; + } + *bp++ = '\n'; + --dlen; + tot++; + cp += off; + if (num >= mdp_count) + break; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + *bp = 0; + tot++; + + if (copy_to_user(buff, debug_buf, tot)) + return -EFAULT; + + *ppos += tot; /* increase offset */ + + return tot; +} + + +static const struct file_operations mdp_reg_fops = { + .open = mdp_reg_open, + .release = mdp_reg_release, + .read = mdp_reg_read, + .write = mdp_reg_write, +}; + +#ifdef CONFIG_FB_MSM_MDP40 +static int mdp_stat_open(struct inode *inode, struct file *file) +{ + /* non-seekable */ + file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); + return 0; +} + +static int mdp_stat_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t mdp_stat_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + unsigned long flag; + + if (count > sizeof(debug_buf)) + return -EFAULT; + + spin_lock_irqsave(&mdp_spin_lock, flag); + memset((char *)&mdp4_stat, 0 , sizeof(mdp4_stat)); /* reset */ + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + return count; +} + +static ssize_t mdp_stat_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int len = 0; + int tot = 0; + int dlen; + char *bp; + unsigned long flag; + + + if (*ppos) + return 0; /* the end */ + + bp = debug_buf; + dlen = sizeof(debug_buf); + + spin_lock_irqsave(&mdp_spin_lock, flag); + len = snprintf(bp, dlen, "intr_total: %08lu\n", + mdp4_stat.intr_tot); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "intr_dma_p: %08lu\n", + mdp4_stat.intr_dma_p); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "intr_dma_s: %08lu\n", + mdp4_stat.intr_dma_s); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "intr_dma_e: %08lu\n", + mdp4_stat.intr_dma_e); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "intr_overlay0: %08lu\n", + mdp4_stat.intr_overlay0); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "intr_overlay1: %08lu\n", + mdp4_stat.intr_overlay1); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "unerrun_primary: %08lu\n", + mdp4_stat.intr_underrun_p); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "unerrun_external: %08lu\n\n", + mdp4_stat.intr_underrun_e); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "intr_dsi : %08lu\n\n", + mdp4_stat.intr_dsi); + + bp += len; + dlen -= len; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + len = snprintf(bp, dlen, "kickoff_mddi: %08lu\n", + mdp4_stat.kickoff_mddi); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "kickoff_lcdc: %08lu\n", + mdp4_stat.kickoff_lcdc); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "kickoff_dtv: %08lu\n", + mdp4_stat.kickoff_dtv); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "kickoff_atv: %08lu\n", + mdp4_stat.kickoff_atv); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "kickoff_dsi: %08lu\n\n", + mdp4_stat.kickoff_dsi); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "writeback: %08lu\n", + mdp4_stat.writeback); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "overlay0_set: %08lu\n", + mdp4_stat.overlay_set[0]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "overlay0_unset: %08lu\n", + mdp4_stat.overlay_unset[0]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "overlay0_play: %08lu\n", + mdp4_stat.overlay_play[0]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "overlay1_set: %08lu\n", + mdp4_stat.overlay_set[1]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "overlay1_unset: %08lu\n", + mdp4_stat.overlay_unset[1]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "overlay1_play: %08lu\n\n", + mdp4_stat.overlay_play[1]); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "pipe_rgb1: %08lu\n", mdp4_stat.pipe[0]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "pipe_rgb2: %08lu\n", mdp4_stat.pipe[1]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "pipe_vg1: %08lu\n", mdp4_stat.pipe[2]); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "pipe_vg2: %08lu\n\n", mdp4_stat.pipe[3]); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "dsi_clkoff: %08lu\n\n", mdp4_stat.dsi_clkoff); + + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "err_mixer: %08lu\n", mdp4_stat.err_mixer); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "err_size: %08lu\n", mdp4_stat.err_size); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "err_scale: %08lu\n", mdp4_stat.err_scale); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "err_format: %08lu\n", mdp4_stat.err_format); + bp += len; + dlen -= len; + + tot = (uint32)bp - (uint32)debug_buf; + *bp = 0; + tot++; + + if (tot < 0) + return 0; + if (copy_to_user(buff, debug_buf, tot)) + return -EFAULT; + + *ppos += tot; /* increase offset */ + + return tot; +} + +static const struct file_operations mdp_stat_fops = { + .open = mdp_stat_open, + .release = mdp_stat_release, + .read = mdp_stat_read, + .write = mdp_stat_write, +}; +#endif + +/* + * MDDI + * + */ + +struct mddi_reg { + char *name; + int off; +}; + +static struct mddi_reg mddi_regs_list[] = { + {"MDDI_CMD", MDDI_CMD}, /* 0x0000 */ + {"MDDI_VERSION", MDDI_VERSION}, /* 0x0004 */ + {"MDDI_PRI_PTR", MDDI_PRI_PTR}, /* 0x0008 */ + {"MDDI_BPS", MDDI_BPS}, /* 0x0010 */ + {"MDDI_SPM", MDDI_SPM}, /* 0x0014 */ + {"MDDI_INT", MDDI_INT}, /* 0x0018 */ + {"MDDI_INTEN", MDDI_INTEN}, /* 0x001c */ + {"MDDI_REV_PTR", MDDI_REV_PTR}, /* 0x0020 */ + {"MDDI_ REV_SIZE", MDDI_REV_SIZE},/* 0x0024 */ + {"MDDI_STAT", MDDI_STAT}, /* 0x0028 */ + {"MDDI_REV_RATE_DIV", MDDI_REV_RATE_DIV}, /* 0x002c */ + {"MDDI_REV_CRC_ERR", MDDI_REV_CRC_ERR}, /* 0x0030 */ + {"MDDI_TA1_LEN", MDDI_TA1_LEN}, /* 0x0034 */ + {"MDDI_TA2_LEN", MDDI_TA2_LEN}, /* 0x0038 */ + {"MDDI_TEST", MDDI_TEST}, /* 0x0040 */ + {"MDDI_REV_PKT_CNT", MDDI_REV_PKT_CNT}, /* 0x0044 */ + {"MDDI_DRIVE_HI", MDDI_DRIVE_HI},/* 0x0048 */ + {"MDDI_DRIVE_LO", MDDI_DRIVE_LO}, /* 0x004c */ + {"MDDI_DISP_WAKE", MDDI_DISP_WAKE},/* 0x0050 */ + {"MDDI_REV_ENCAP_SZ", MDDI_REV_ENCAP_SZ}, /* 0x0054 */ + {"MDDI_RTD_VAL", MDDI_RTD_VAL}, /* 0x0058 */ + {"MDDI_PAD_CTL", MDDI_PAD_CTL}, /* 0x0068 */ + {"MDDI_DRIVER_START_CNT", MDDI_DRIVER_START_CNT}, /* 0x006c */ + {"MDDI_CORE_VER", MDDI_CORE_VER}, /* 0x008c */ + {"MDDI_FIFO_ALLOC", MDDI_FIFO_ALLOC}, /* 0x0090 */ + {"MDDI_PAD_IO_CTL", MDDI_PAD_IO_CTL}, /* 0x00a0 */ + {"MDDI_PAD_CAL", MDDI_PAD_CAL}, /* 0x00a4 */ + {0, 0} +}; + +static int mddi_reg_open(struct inode *inode, struct file *file) +{ + /* non-seekable */ + file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); + return 0; +} + +static int mddi_reg_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static void mddi_reg_write(int ndx, uint32 off, uint32 data) +{ + char *base; + + if (ndx) + base = (char *)msm_emdh_base; + else + base = (char *)msm_pmdh_base; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + writel(data, base + off); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + printk(KERN_INFO "%s: addr=%x data=%x\n", + __func__, (int)(base+off), (int)data); +} + +static int mddi_reg_read(int ndx) +{ + struct mddi_reg *reg; + unsigned char *base; + int data; + char *bp; + int len = 0; + int tot = 0; + int dlen; + + if (ndx) + base = msm_emdh_base; + else + base = msm_pmdh_base; + + reg = mddi_regs_list; + bp = debug_buf; + dlen = sizeof(debug_buf); + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + while (reg->name) { + data = readl((u32)base + reg->off); + len = snprintf(bp, dlen, "%s:0x%08x\t\t= 0x%08x\n", + reg->name, reg->off, data); + tot += len; + bp += len; + dlen -= len; + reg++; + } + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + *bp = 0; + tot++; + + return tot; +} + +static ssize_t pmdh_reg_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + uint32 off, data; + int cnt; + + if (count >= sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + debug_buf[count] = 0; /* end of string */ + + cnt = sscanf(debug_buf, "%x %x", &off, &data); + + mddi_reg_write(0, off, data); + + return count; +} + +static ssize_t pmdh_reg_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int tot = 0; + + if (*ppos) + return 0; /* the end */ + + tot = mddi_reg_read(0); /* pmdh */ + + if (tot < 0) + return 0; + if (copy_to_user(buff, debug_buf, tot)) + return -EFAULT; + + *ppos += tot; /* increase offset */ + + return tot; +} + + +static const struct file_operations pmdh_fops = { + .open = mddi_reg_open, + .release = mddi_reg_release, + .read = pmdh_reg_read, + .write = pmdh_reg_write, +}; + + + +#if defined(CONFIG_FB_MSM_OVERLAY) && defined(CONFIG_FB_MSM_MDDI) +static int vsync_reg_open(struct inode *inode, struct file *file) +{ + /* non-seekable */ + file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); + return 0; +} + +static int vsync_reg_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t vsync_reg_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + uint32 enable; + int cnt; + + if (count >= sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + debug_buf[count] = 0; /* end of string */ + + cnt = sscanf(debug_buf, "%x", &enable); + + mdp_dmap_vsync_set(enable); + + return count; +} + +static ssize_t vsync_reg_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + char *bp; + int len = 0; + int tot = 0; + int dlen; + + if (*ppos) + return 0; /* the end */ + + bp = debug_buf; + dlen = sizeof(debug_buf); + len = snprintf(bp, dlen, "%x\n", mdp_dmap_vsync_get()); + tot += len; + bp += len; + *bp = 0; + tot++; + + if (copy_to_user(buff, debug_buf, tot)) + return -EFAULT; + + *ppos += tot; /* increase offset */ + + return tot; +} + + +static const struct file_operations vsync_fops = { + .open = vsync_reg_open, + .release = vsync_reg_release, + .read = vsync_reg_read, + .write = vsync_reg_write, +}; +#endif + +static ssize_t emdh_reg_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + uint32 off, data; + int cnt; + + if (count >= sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + debug_buf[count] = 0; /* end of string */ + + cnt = sscanf(debug_buf, "%x %x", &off, &data); + + mddi_reg_write(1, off, data); + + return count; +} + +static ssize_t emdh_reg_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int tot = 0; + + if (*ppos) + return 0; /* the end */ + + tot = mddi_reg_read(1); /* emdh */ + + if (tot < 0) + return 0; + if (copy_to_user(buff, debug_buf, tot)) + return -EFAULT; + + *ppos += tot; /* increase offset */ + + return tot; +} + +static const struct file_operations emdh_fops = { + .open = mddi_reg_open, + .release = mddi_reg_release, + .read = emdh_reg_read, + .write = emdh_reg_write, +}; + + +uint32 dbg_offset; +uint32 dbg_count; +char *dbg_base; + + +static int dbg_open(struct inode *inode, struct file *file) +{ + /* non-seekable */ + file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); + return 0; +} + +static int dbg_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t dbg_base_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + return count; +} + +static ssize_t dbg_base_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int len = 0; + int tot = 0; + int dlen; + char *bp; + + + if (*ppos) + return 0; /* the end */ + + + bp = debug_buf; + dlen = sizeof(debug_buf); + + len = snprintf(bp, dlen, "mdp_base : %08x\n", + (int)msm_mdp_base); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "mddi_base : %08x\n", + (int)msm_pmdh_base); + bp += len; + dlen -= len; + len = snprintf(bp, dlen, "emdh_base : %08x\n", + (int)msm_emdh_base); + bp += len; + dlen -= len; +#ifdef CONFIG_FB_MSM_TVOUT + len = snprintf(bp, dlen, "tvenv_base: %08x\n", + (int)tvenc_base); + bp += len; + dlen -= len; +#endif + +#ifdef CONFIG_FB_MSM_MIPI_DSI + len = snprintf(bp, dlen, "mipi_dsi_base: %08x\n", + (int)mipi_dsi_base); + bp += len; + dlen -= len; +#endif + + tot = (uint32)bp - (uint32)debug_buf; + *bp = 0; + tot++; + + if (tot < 0) + return 0; + if (copy_to_user(buff, debug_buf, tot)) + return -EFAULT; + + *ppos += tot; /* increase offset */ + + return tot; +} + +static const struct file_operations dbg_base_fops = { + .open = dbg_open, + .release = dbg_release, + .read = dbg_base_read, + .write = dbg_base_write, +}; + +static ssize_t dbg_offset_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + uint32 off, cnt, num, base; + + if (count >= sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + debug_buf[count] = 0; /* end of string */ + + cnt = sscanf(debug_buf, "%x %d %x", &off, &num, &base); + + if (cnt < 0) + cnt = 0; + + if (cnt >= 1) + dbg_offset = off; + if (cnt >= 2) + dbg_count = num; + if (cnt >= 3) + dbg_base = (char *)base; + + printk(KERN_INFO "%s: offset=%x cnt=%d base=%x\n", __func__, + dbg_offset, dbg_count, (int)dbg_base); + + return count; +} + +static ssize_t dbg_offset_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int len = 0; + + + if (*ppos) + return 0; /* the end */ + + len = snprintf(debug_buf, sizeof(debug_buf), "0x%08x %d 0x%08x\n", + dbg_offset, dbg_count, (int)dbg_base); + if (len < 0) + return 0; + + if (copy_to_user(buff, debug_buf, len)) + return -EFAULT; + + *ppos += len; /* increase offset */ + + return len; +} + +static const struct file_operations dbg_off_fops = { + .open = dbg_open, + .release = dbg_release, + .read = dbg_offset_read, + .write = dbg_offset_write, +}; + + +static ssize_t dbg_reg_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + uint32 off, data; + int cnt; + + if (count >= sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + debug_buf[count] = 0; /* end of string */ + + cnt = sscanf(debug_buf, "%x %x", &off, &data); + + writel(data, dbg_base + off); + + printk(KERN_INFO "%s: addr=%x data=%x\n", + __func__, (int)(dbg_base+off), (int)data); + + return count; +} + +static ssize_t dbg_reg_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int len = 0; + uint32 data; + int i, j, off, dlen, num; + char *bp, *cp; + int tot = 0; + + + if (*ppos) + return 0; /* the end */ + + if (dbg_base == 0) + return 0; /* nothing to read */ + + j = 0; + num = 0; + bp = debug_buf; + cp = (char *)(dbg_base + dbg_offset); + dlen = sizeof(debug_buf); + while (j++ < 16) { + len = snprintf(bp, dlen, "0x%08x: ", (int)cp); + tot += len; + bp += len; + dlen -= len; + off = 0; + i = 0; + while (i++ < 4) { + data = readl(cp + off); + len = snprintf(bp, dlen, "%08x ", data); + tot += len; + bp += len; + dlen -= len; + off += 4; + num++; + if (num >= dbg_count) + break; + } + data = readl((u32)cp + off); + *bp++ = '\n'; + --dlen; + tot++; + cp += off; + if (num >= dbg_count) + break; + } + *bp = 0; + tot++; + + if (copy_to_user(buff, debug_buf, tot)) + return -EFAULT; + + *ppos += tot; /* increase offset */ + + return tot; +} + + +static const struct file_operations dbg_reg_fops = { + .open = dbg_open, + .release = dbg_release, + .read = dbg_reg_read, + .write = dbg_reg_write, +}; + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL +static uint32 hdmi_offset; +static uint32 hdmi_count; + +static int hdmi_open(struct inode *inode, struct file *file) +{ + /* non-seekable */ + file->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE); + return 0; +} + +static int hdmi_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t hdmi_offset_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + uint32 off, cnt, num; + + if (count >= sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + debug_buf[count] = 0; /* end of string */ + + cnt = sscanf(debug_buf, "%x %d", &off, &num); + + if (cnt < 0) + cnt = 0; + + if (cnt >= 1) + hdmi_offset = off; + if (cnt >= 2) + hdmi_count = num; + + printk(KERN_INFO "%s: offset=%x cnt=%d\n", __func__, + hdmi_offset, hdmi_count); + + return count; +} + +static ssize_t hdmi_offset_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int len = 0; + + + if (*ppos) + return 0; /* the end */ + + len = snprintf(debug_buf, sizeof(debug_buf), "0x%08x %d\n", + hdmi_offset, hdmi_count); + if (len < 0) + return 0; + + if (copy_to_user(buff, debug_buf, len)) + return -EFAULT; + + *ppos += len; /* increase offset */ + + return len; +} + +static const struct file_operations hdmi_off_fops = { + .open = hdmi_open, + .release = hdmi_release, + .read = hdmi_offset_read, + .write = hdmi_offset_write, +}; + + +static ssize_t hdmi_reg_write( + struct file *file, + const char __user *buff, + size_t count, + loff_t *ppos) +{ + uint32 off, data, base; + int cnt; + + if (count >= sizeof(debug_buf)) + return -EFAULT; + + if (copy_from_user(debug_buf, buff, count)) + return -EFAULT; + + base = hdmi_msm_get_io_base(); + if (base == 0) + return -EFAULT; + + debug_buf[count] = 0; /* end of string */ + + cnt = sscanf(debug_buf, "%x %x", &off, &data); + + writel(data, base + off); + + printk(KERN_INFO "%s: addr=%x data=%x\n", + __func__, (int)(base+off), (int)data); + + return count; +} + +static ssize_t hdmi_reg_read( + struct file *file, + char __user *buff, + size_t count, + loff_t *ppos) +{ + int len = 0; + uint32 data; + int i, j, off, dlen, num; + char *bp, *cp; + int tot = 0; + + + if (*ppos) + return 0; /* the end */ + + if (hdmi_msm_get_io_base() == 0) + return 0; /* nothing to read */ + + j = 0; + num = 0; + bp = debug_buf; + cp = (char *)(hdmi_msm_get_io_base() + hdmi_offset); + dlen = sizeof(debug_buf); + while (j++ < 16) { + len = snprintf(bp, dlen, "0x%08x: ", (int)cp); + tot += len; + bp += len; + dlen -= len; + off = 0; + i = 0; + while (i++ < 4) { + data = readl(cp + off); + len = snprintf(bp, dlen, "%08x ", data); + tot += len; + bp += len; + dlen -= len; + off += 4; + num++; + if (num >= hdmi_count) + break; + } + data = readl((u32)cp + off); + *bp++ = '\n'; + --dlen; + tot++; + cp += off; + if (num >= hdmi_count) + break; + } + *bp = 0; + tot++; + + if (copy_to_user(buff, debug_buf, tot)) + return -EFAULT; + + *ppos += tot; /* increase offset */ + + return tot; +} + + +static const struct file_operations hdmi_reg_fops = { + .open = hdmi_open, + .release = hdmi_release, + .read = hdmi_reg_read, + .write = hdmi_reg_write, +}; +#endif + +/* + * debugfs + * + */ + +int mdp_debugfs_init(void) +{ + struct dentry *dent = debugfs_create_dir("mdp", NULL); + + if (IS_ERR(dent)) { + printk(KERN_ERR "%s(%d): debugfs_create_dir fail, error %ld\n", + __FILE__, __LINE__, PTR_ERR(dent)); + return -1; + } + + if (debugfs_create_file("off", 0644, dent, 0, &mdp_off_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: index fail\n", + __FILE__, __LINE__); + return -1; + } + + if (debugfs_create_file("reg", 0644, dent, 0, &mdp_reg_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: debug fail\n", + __FILE__, __LINE__); + return -1; + } + +#ifdef CONFIG_FB_MSM_MDP40 + if (debugfs_create_file("stat", 0644, dent, 0, &mdp_stat_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: debug fail\n", + __FILE__, __LINE__); + return -1; + } +#endif + + dent = debugfs_create_dir("mddi", NULL); + + if (IS_ERR(dent)) { + printk(KERN_ERR "%s(%d): debugfs_create_dir fail, error %ld\n", + __FILE__, __LINE__, PTR_ERR(dent)); + return -1; + } + + if (debugfs_create_file("reg", 0644, dent, 0, &pmdh_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: debug fail\n", + __FILE__, __LINE__); + return -1; + } + +#if defined(CONFIG_FB_MSM_OVERLAY) && defined(CONFIG_FB_MSM_MDDI) + if (debugfs_create_file("vsync", 0644, dent, 0, &vsync_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: debug fail\n", + __FILE__, __LINE__); + return -1; + } +#endif + + dent = debugfs_create_dir("emdh", NULL); + + if (IS_ERR(dent)) { + printk(KERN_ERR "%s(%d): debugfs_create_dir fail, error %ld\n", + __FILE__, __LINE__, PTR_ERR(dent)); + return -1; + } + + if (debugfs_create_file("reg", 0644, dent, 0, &emdh_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: debug fail\n", + __FILE__, __LINE__); + return -1; + } + + dent = debugfs_create_dir("mdp-dbg", NULL); + + if (IS_ERR(dent)) { + printk(KERN_ERR "%s(%d): debugfs_create_dir fail, error %ld\n", + __FILE__, __LINE__, PTR_ERR(dent)); + return -1; + } + + if (debugfs_create_file("base", 0644, dent, 0, &dbg_base_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: index fail\n", + __FILE__, __LINE__); + return -1; + } + + if (debugfs_create_file("off", 0644, dent, 0, &dbg_off_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: index fail\n", + __FILE__, __LINE__); + return -1; + } + + if (debugfs_create_file("reg", 0644, dent, 0, &dbg_reg_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: debug fail\n", + __FILE__, __LINE__); + return -1; + } + +#ifdef CONFIG_FB_MSM_HDMI_MSM_PANEL + dent = debugfs_create_dir("hdmi", NULL); + + if (IS_ERR(dent)) { + printk(KERN_ERR "%s(%d): debugfs_create_dir fail, error %ld\n", + __FILE__, __LINE__, PTR_ERR(dent)); + return PTR_ERR(dent); + } + + if (debugfs_create_file("off", 0644, dent, 0, &hdmi_off_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: 'off' fail\n", + __FILE__, __LINE__); + return -ENOENT; + } + + if (debugfs_create_file("reg", 0644, dent, 0, &hdmi_reg_fops) + == NULL) { + printk(KERN_ERR "%s(%d): debugfs_create_file: 'reg' fail\n", + __FILE__, __LINE__); + return -ENOENT; + } +#endif + + return 0; +} diff --git a/drivers/video/msm/mdp_dma.c b/drivers/video/msm/mdp_dma.c new file mode 100644 index 00000000000..a78c0db0a29 --- /dev/null +++ b/drivers/video/msm/mdp_dma.c @@ -0,0 +1,602 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "mdp.h" +#include "msm_fb.h" +#include "mddihost.h" + +static uint32 mdp_last_dma2_update_width; +static uint32 mdp_last_dma2_update_height; +static uint32 mdp_curr_dma2_update_width; +static uint32 mdp_curr_dma2_update_height; + +ktime_t mdp_dma2_last_update_time = { 0 }; + +int mdp_lcd_rd_cnt_offset_slow = 20; +int mdp_lcd_rd_cnt_offset_fast = 20; +int mdp_vsync_usec_wait_line_too_short = 5; +uint32 mdp_dma2_update_time_in_usec; +uint32 mdp_total_vdopkts; + +extern u32 msm_fb_debug_enabled; +extern struct workqueue_struct *mdp_dma_wq; + +int vsync_start_y_adjust = 4; + +static void mdp_dma2_update_lcd(struct msm_fb_data_type *mfd) +{ + MDPIBUF *iBuf = &mfd->ibuf; + int mddi_dest = FALSE; + int cmd_mode = FALSE; + uint32 outBpp = iBuf->bpp; + uint32 dma2_cfg_reg; + uint8 *src; + uint32 mddi_ld_param; + uint16 mddi_vdo_packet_reg; +#ifndef CONFIG_FB_MSM_MDP303 + struct msm_fb_panel_data *pdata = + (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; +#endif + uint32 ystride = mfd->fbi->fix.line_length; + uint32 mddi_pkt_desc; + + dma2_cfg_reg = DMA_PACK_ALIGN_LSB | + DMA_OUT_SEL_AHB | DMA_IBUF_NONCONTIGUOUS; + +#ifdef CONFIG_FB_MSM_MDP22 + dma2_cfg_reg |= DMA_PACK_TIGHT; +#endif + +#ifdef CONFIG_FB_MSM_MDP30 + /* + * Software workaround: On 7x25/7x27, the MDP will not + * respond if dma_w is 1 pixel. Set the update width to + * 2 pixels and adjust the x offset if needed. + */ + if (iBuf->dma_w == 1) { + iBuf->dma_w = 2; + if (iBuf->dma_x == (iBuf->ibuf_width - 2)) + iBuf->dma_x--; + } +#endif + + if (mfd->fb_imgType == MDP_BGR_565) + dma2_cfg_reg |= DMA_PACK_PATTERN_BGR; + else if (mfd->fb_imgType == MDP_RGBA_8888) + dma2_cfg_reg |= DMA_PACK_PATTERN_BGR; + else + dma2_cfg_reg |= DMA_PACK_PATTERN_RGB; + + if (outBpp == 4) { + dma2_cfg_reg |= DMA_IBUF_C3ALPHA_EN; + dma2_cfg_reg |= DMA_IBUF_FORMAT_xRGB8888_OR_ARGB8888; + } + + if (outBpp == 2) + dma2_cfg_reg |= DMA_IBUF_FORMAT_RGB565; + + mddi_ld_param = 0; + mddi_vdo_packet_reg = mfd->panel_info.mddi.vdopkt; + + if ((mfd->panel_info.type == MDDI_PANEL) || + (mfd->panel_info.type == EXT_MDDI_PANEL)) { + dma2_cfg_reg |= DMA_OUT_SEL_MDDI; + mddi_dest = TRUE; + + if (mfd->panel_info.type == MDDI_PANEL) { + mdp_total_vdopkts++; + if (mfd->panel_info.pdest == DISPLAY_1) { + dma2_cfg_reg |= DMA_MDDI_DMAOUT_LCD_SEL_PRIMARY; + mddi_ld_param = 0; +#ifdef MDDI_HOST_WINDOW_WORKAROUND + mddi_window_adjust(mfd, iBuf->dma_x, + iBuf->dma_w - 1, iBuf->dma_y, + iBuf->dma_h - 1); +#endif + } else { + dma2_cfg_reg |= + DMA_MDDI_DMAOUT_LCD_SEL_SECONDARY; + mddi_ld_param = 1; +#ifdef MDDI_HOST_WINDOW_WORKAROUND + mddi_window_adjust(mfd, iBuf->dma_x, + iBuf->dma_w - 1, iBuf->dma_y, + iBuf->dma_h - 1); +#endif + } + } else { + dma2_cfg_reg |= DMA_MDDI_DMAOUT_LCD_SEL_EXTERNAL; + mddi_ld_param = 2; + } +#ifdef CONFIG_FB_MSM_MDP303 + } else if (mfd->panel_info.type == MIPI_CMD_PANEL) { + cmd_mode = TRUE; + dma2_cfg_reg |= DMA_OUT_SEL_DSI_CMD; +#endif + } else { + if (mfd->panel_info.pdest == DISPLAY_1) { + dma2_cfg_reg |= DMA_AHBM_LCD_SEL_PRIMARY; + outp32(MDP_EBI2_LCD0, mfd->data_port_phys); + } else { + dma2_cfg_reg |= DMA_AHBM_LCD_SEL_SECONDARY; + outp32(MDP_EBI2_LCD1, mfd->data_port_phys); + } + } + + src = (uint8 *) iBuf->buf; + /* starting input address */ + src += iBuf->dma_x * outBpp + iBuf->dma_y * ystride; + + mdp_curr_dma2_update_width = iBuf->dma_w; + mdp_curr_dma2_update_height = iBuf->dma_h; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + +#ifdef CONFIG_FB_MSM_MDP22 + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0184, + (iBuf->dma_h << 16 | iBuf->dma_w)); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0188, src); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x018C, ystride); +#else + if (cmd_mode) + MDP_OUTP(MDP_BASE + 0x90004, + (mfd->panel_info.yres << 16 | mfd->panel_info.xres)); + else + MDP_OUTP(MDP_BASE + 0x90004, (iBuf->dma_h << 16 | iBuf->dma_w)); + + MDP_OUTP(MDP_BASE + 0x90008, src); + MDP_OUTP(MDP_BASE + 0x9000c, ystride); +#endif + + if (mfd->panel_info.bpp == 18) { + mddi_pkt_desc = MDDI_VDO_PACKET_DESC; + dma2_cfg_reg |= DMA_DSTC0G_6BITS | /* 666 18BPP */ + DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + } else if (mfd->panel_info.bpp == 24) { + mddi_pkt_desc = MDDI_VDO_PACKET_DESC_24; + dma2_cfg_reg |= DMA_DSTC0G_8BITS | /* 888 24BPP */ + DMA_DSTC1B_8BITS | DMA_DSTC2R_8BITS; + } else { + mddi_pkt_desc = MDDI_VDO_PACKET_DESC_16; + dma2_cfg_reg |= DMA_DSTC0G_6BITS | /* 565 16BPP */ + DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; + } + +#ifndef CONFIG_FB_MSM_MDP303 + + if (mddi_dest) { +#ifdef CONFIG_FB_MSM_MDP22 + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0194, + (iBuf->dma_y << 16) | iBuf->dma_x); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01a0, mddi_ld_param); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01a4, + (mddi_pkt_desc << 16) | mddi_vdo_packet_reg); +#else + MDP_OUTP(MDP_BASE + 0x90010, (iBuf->dma_y << 16) | iBuf->dma_x); + MDP_OUTP(MDP_BASE + 0x00090, mddi_ld_param); + MDP_OUTP(MDP_BASE + 0x00094, + (mddi_pkt_desc << 16) | mddi_vdo_packet_reg); +#endif + } else { + /* setting EBI2 LCDC write window */ + pdata->set_rect(iBuf->dma_x, iBuf->dma_y, iBuf->dma_w, + iBuf->dma_h); + } +#else + if (mfd->panel_info.type == MIPI_CMD_PANEL) { + /* dma_p = 0, dma_s = 1 */ + MDP_OUTP(MDP_BASE + 0xF1000, 0x10); + /* enable dsi trigger on dma_p */ + MDP_OUTP(MDP_BASE + 0xF1004, 0x01); + } +#endif + + /* dma2 config register */ +#ifdef MDP_HW_VSYNC + MDP_OUTP(MDP_BASE + 0x90000, dma2_cfg_reg); + + if ((mfd->use_mdp_vsync) && + (mfd->ibuf.vsync_enable) && (mfd->panel_info.lcd.vsync_enable)) { + uint32 start_y; + + if (vsync_start_y_adjust <= iBuf->dma_y) + start_y = iBuf->dma_y - vsync_start_y_adjust; + else + start_y = + (mfd->total_lcd_lines - 1) - (vsync_start_y_adjust - + iBuf->dma_y); + + /* + * MDP VSYNC clock must be On by now so, we don't have to + * re-enable it + */ + MDP_OUTP(MDP_BASE + 0x210, start_y); + MDP_OUTP(MDP_BASE + 0x20c, 1); /* enable prim vsync */ + } else { + MDP_OUTP(MDP_BASE + 0x20c, 0); /* disable prim vsync */ + } +#else +#ifdef CONFIG_FB_MSM_MDP22 + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0180, dma2_cfg_reg); +#else + MDP_OUTP(MDP_BASE + 0x90000, dma2_cfg_reg); +#endif +#endif /* MDP_HW_VSYNC */ + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} + +static ktime_t vt = { 0 }; +int mdp_usec_diff_threshold = 100; +int mdp_expected_usec_wait; + +enum hrtimer_restart mdp_dma2_vsync_hrtimer_handler(struct hrtimer *ht) +{ + struct msm_fb_data_type *mfd = NULL; + + mfd = container_of(ht, struct msm_fb_data_type, dma_hrtimer); + + mdp_pipe_kickoff(MDP_DMA2_TERM, mfd); + + if (msm_fb_debug_enabled) { + ktime_t t; + int usec_diff; + int actual_wait; + + t = ktime_get_real(); + + actual_wait = ktime_to_us(ktime_sub(t, vt)); + usec_diff = actual_wait - mdp_expected_usec_wait; + + if ((mdp_usec_diff_threshold < usec_diff) || (usec_diff < 0)) + MSM_FB_DEBUG + ("HRT Diff = %d usec Exp=%d usec Act=%d usec\n", + usec_diff, mdp_expected_usec_wait, actual_wait); + } + + return HRTIMER_NORESTART; +} + + +#ifdef CONFIG_FB_MSM_MDP303 +static int busy_wait_cnt; + +void mdp3_dsi_cmd_dma_busy_wait(struct msm_fb_data_type *mfd) +{ + unsigned long flag; + int need_wait = 0; + +#ifdef DSI_CLK_CTRL + mod_timer(&dsi_clock_timer, jiffies + HZ); /* one second */ +#endif + + spin_lock_irqsave(&mdp_spin_lock, flag); +#ifdef DSI_CLK_CTRL + if (mipi_dsi_clk_on == 0) + mipi_dsi_clk_enable(); +#endif + + if (mfd->dma->busy == TRUE) { + if (busy_wait_cnt == 0) + INIT_COMPLETION(mfd->dma->comp); + busy_wait_cnt++; + need_wait++; + } + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + if (need_wait) { + /* wait until DMA finishes the current job */ + wait_for_completion(&mfd->dma->comp); + } +} +#endif + +static void mdp_dma_schedule(struct msm_fb_data_type *mfd, uint32 term) +{ + /* + * dma2 configure VSYNC block + * vsync supported on Primary LCD only for now + */ + int32 mdp_lcd_rd_cnt; + uint32 usec_wait_time; + uint32 start_y; + + /* + * ToDo: if we can move HRT timer callback to workqueue, we can + * move DMA2 power on under mdp_pipe_kickoff(). + * This will save a power for hrt time wait. + * However if the latency for context switch (hrt irq -> workqueue) + * is too big, we will miss the vsync timing. + */ + if (term == MDP_DMA2_TERM) + mdp_pipe_ctrl(MDP_DMA2_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + mdp_dma2_update_time_in_usec = ktime_to_us(mdp_dma2_last_update_time); + + if ((!mfd->ibuf.vsync_enable) || (!mfd->panel_info.lcd.vsync_enable) + || (mfd->use_mdp_vsync)) { + mdp_pipe_kickoff(term, mfd); + return; + } + /* SW vsync logic starts here */ + + /* get current rd counter */ + mdp_lcd_rd_cnt = mdp_get_lcd_line_counter(mfd); + if (mdp_dma2_update_time_in_usec != 0) { + uint32 num, den; + + /* + * roi width boundary calculation to know the size of pixel + * width that MDP can send faster or slower than LCD read + * pointer + */ + + num = mdp_last_dma2_update_width * mdp_last_dma2_update_height; + den = + (((mfd->panel_info.lcd.refx100 * mfd->total_lcd_lines) / + 1000) * (mdp_dma2_update_time_in_usec / 100)) / 1000; + + if (den == 0) + mfd->vsync_width_boundary[mdp_last_dma2_update_width] = + mfd->panel_info.xres + 1; + else + mfd->vsync_width_boundary[mdp_last_dma2_update_width] = + (int)(num / den); + } + + if (mfd->vsync_width_boundary[mdp_last_dma2_update_width] > + mdp_curr_dma2_update_width) { + /* MDP wrp is faster than LCD rdp */ + mdp_lcd_rd_cnt += mdp_lcd_rd_cnt_offset_fast; + } else { + /* MDP wrp is slower than LCD rdp */ + mdp_lcd_rd_cnt -= mdp_lcd_rd_cnt_offset_slow; + } + + if (mdp_lcd_rd_cnt < 0) + mdp_lcd_rd_cnt = mfd->total_lcd_lines + mdp_lcd_rd_cnt; + else if (mdp_lcd_rd_cnt > mfd->total_lcd_lines) + mdp_lcd_rd_cnt = mdp_lcd_rd_cnt - mfd->total_lcd_lines - 1; + + /* get wrt pointer position */ + start_y = mfd->ibuf.dma_y; + + /* measure line difference between start_y and rd counter */ + if (start_y > mdp_lcd_rd_cnt) { + /* + * *100 for lcd_ref_hzx100 was already multiplied by 100 + * *1000000 is for usec conversion + */ + + if ((start_y - mdp_lcd_rd_cnt) <= + mdp_vsync_usec_wait_line_too_short) + usec_wait_time = 0; + else + usec_wait_time = + ((start_y - + mdp_lcd_rd_cnt) * 1000000) / + ((mfd->total_lcd_lines * + mfd->panel_info.lcd.refx100) / 100); + } else { + if ((start_y + (mfd->total_lcd_lines - mdp_lcd_rd_cnt)) <= + mdp_vsync_usec_wait_line_too_short) + usec_wait_time = 0; + else + usec_wait_time = + ((start_y + + (mfd->total_lcd_lines - + mdp_lcd_rd_cnt)) * 1000000) / + ((mfd->total_lcd_lines * + mfd->panel_info.lcd.refx100) / 100); + } + + mdp_last_dma2_update_width = mdp_curr_dma2_update_width; + mdp_last_dma2_update_height = mdp_curr_dma2_update_height; + + if (usec_wait_time == 0) { + mdp_pipe_kickoff(term, mfd); + } else { + ktime_t wait_time; + + wait_time = ns_to_ktime(usec_wait_time * 1000); + + if (msm_fb_debug_enabled) { + vt = ktime_get_real(); + mdp_expected_usec_wait = usec_wait_time; + } + hrtimer_start(&mfd->dma_hrtimer, wait_time, HRTIMER_MODE_REL); + } +} + +#ifdef MDDI_HOST_WINDOW_WORKAROUND +static void mdp_dma2_update_sub(struct msm_fb_data_type *mfd); +void mdp_dma2_update(struct msm_fb_data_type *mfd) +{ + MDPIBUF *iBuf; + uint32 upper_height; + + if (mfd->panel.type == EXT_MDDI_PANEL) { + mdp_dma2_update_sub(mfd); + return; + } + + iBuf = &mfd->ibuf; + + upper_height = + (uint32) mddi_assign_pkt_height((uint16) iBuf->dma_w, + (uint16) iBuf->dma_h, 18); + + if (upper_height >= iBuf->dma_h) { + mdp_dma2_update_sub(mfd); + } else { + uint32 lower_height; + + /* sending the upper region first */ + lower_height = iBuf->dma_h - upper_height; + iBuf->dma_h = upper_height; + mdp_dma2_update_sub(mfd); + + /* sending the lower region second */ + iBuf->dma_h = lower_height; + iBuf->dma_y += lower_height; + iBuf->vsync_enable = FALSE; + mdp_dma2_update_sub(mfd); + } +} + +static void mdp_dma2_update_sub(struct msm_fb_data_type *mfd) +#else +void mdp_dma2_update(struct msm_fb_data_type *mfd) +#endif +{ + down(&mfd->dma->mutex); + if ((mfd) && (!mfd->dma->busy) && (mfd->panel_power_on)) { + down(&mfd->sem); + mfd->ibuf_flushed = TRUE; + mdp_dma2_update_lcd(mfd); + + mdp_enable_irq(MDP_DMA2_TERM); + mfd->dma->busy = TRUE; + INIT_COMPLETION(mfd->dma->comp); + + /* schedule DMA to start */ + mdp_dma_schedule(mfd, MDP_DMA2_TERM); + up(&mfd->sem); + + /* wait until DMA finishes the current job */ + wait_for_completion_killable(&mfd->dma->comp); + mdp_disable_irq(MDP_DMA2_TERM); + + /* signal if pan function is waiting for the update completion */ + if (mfd->pan_waiting) { + mfd->pan_waiting = FALSE; + complete(&mfd->pan_comp); + } + } + up(&mfd->dma->mutex); +} + +void mdp_lcd_update_workqueue_handler(struct work_struct *work) +{ + struct msm_fb_data_type *mfd = NULL; + + mfd = container_of(work, struct msm_fb_data_type, dma_update_worker); + if (mfd) + mfd->dma_fnc(mfd); +} + +void mdp_set_dma_pan_info(struct fb_info *info, struct mdp_dirty_region *dirty, + boolean sync) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + MDPIBUF *iBuf; + int bpp = info->var.bits_per_pixel / 8; + + down(&mfd->sem); + iBuf = &mfd->ibuf; + iBuf->buf = (uint8 *) info->fix.smem_start; + iBuf->buf += info->var.xoffset * bpp + + info->var.yoffset * info->fix.line_length; + + iBuf->ibuf_width = info->var.xres_virtual; + iBuf->bpp = bpp; + + iBuf->vsync_enable = sync; + + if (dirty) { + /* + * ToDo: dirty region check inside var.xoffset+xres + * <-> var.yoffset+yres + */ + iBuf->dma_x = dirty->xoffset % info->var.xres; + iBuf->dma_y = dirty->yoffset % info->var.yres; + iBuf->dma_w = dirty->width; + iBuf->dma_h = dirty->height; + } else { + iBuf->dma_x = 0; + iBuf->dma_y = 0; + iBuf->dma_w = info->var.xres; + iBuf->dma_h = info->var.yres; + } + mfd->ibuf_flushed = FALSE; + up(&mfd->sem); +} + +void mdp_dma_pan_update(struct fb_info *info) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + MDPIBUF *iBuf; + + iBuf = &mfd->ibuf; + + if (mfd->sw_currently_refreshing) { + /* we need to wait for the pending update */ + mfd->pan_waiting = TRUE; + if (!mfd->ibuf_flushed) { + wait_for_completion_killable(&mfd->pan_comp); + } + /* waiting for this update to complete */ + mfd->pan_waiting = TRUE; + wait_for_completion_killable(&mfd->pan_comp); + } else + mfd->dma_fnc(mfd); +} + +void mdp_refresh_screen(unsigned long data) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)data; + + if ((mfd->sw_currently_refreshing) && (mfd->sw_refreshing_enable)) { + init_timer(&mfd->refresh_timer); + mfd->refresh_timer.function = mdp_refresh_screen; + mfd->refresh_timer.data = data; + + if (mfd->dma->busy) + /* come back in 1 msec */ + mfd->refresh_timer.expires = jiffies + (HZ / 1000); + else + mfd->refresh_timer.expires = + jiffies + mfd->refresh_timer_duration; + + add_timer(&mfd->refresh_timer); + + if (!mfd->dma->busy) { + if (!queue_work(mdp_dma_wq, &mfd->dma_update_worker)) { + MSM_FB_DEBUG("mdp_dma: can't queue_work! -> \ + MDP/MDDI/LCD clock speed needs to be increased\n"); + } + } + } else { + if (!mfd->hw_refresh) + complete(&mfd->refresher_comp); + } +} diff --git a/drivers/video/msm/mdp_dma_dsi_video.c b/drivers/video/msm/mdp_dma_dsi_video.c new file mode 100644 index 00000000000..505eb74f809 --- /dev/null +++ b/drivers/video/msm/mdp_dma_dsi_video.c @@ -0,0 +1,270 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + +#define DSI_VIDEO_BASE 0xF0000 +#define DMA_P_BASE 0x90000 + +static int first_pixel_start_x; +static int first_pixel_start_y; + +int mdp_dsi_video_on(struct platform_device *pdev) +{ + int dsi_width; + int dsi_height; + int dsi_bpp; + int dsi_border_clr; + int dsi_underflow_clr; + int dsi_hsync_skew; + + int hsync_period; + int hsync_ctrl; + int vsync_period; + int display_hctl; + int display_v_start; + int display_v_end; + int active_hctl; + int active_h_start; + int active_h_end; + int active_v_start; + int active_v_end; + int ctrl_polarity; + int h_back_porch; + int h_front_porch; + int v_back_porch; + int v_front_porch; + int hsync_pulse_width; + int vsync_pulse_width; + int hsync_polarity; + int vsync_polarity; + int data_en_polarity; + int hsync_start_x; + int hsync_end_x; + uint8 *buf; + uint32 dma2_cfg_reg; + + int bpp; + struct fb_info *fbi; + struct fb_var_screeninfo *var; + struct msm_fb_data_type *mfd; + int ret; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + fbi = mfd->fbi; + var = &fbi->var; + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + dma2_cfg_reg = DMA_PACK_ALIGN_LSB | DMA_OUT_SEL_DSI_VIDEO; + + if (mfd->fb_imgType == MDP_BGR_565) + dma2_cfg_reg |= DMA_PACK_PATTERN_BGR; + else if (mfd->fb_imgType == MDP_RGBA_8888) + dma2_cfg_reg |= DMA_PACK_PATTERN_BGR; + else + dma2_cfg_reg |= DMA_PACK_PATTERN_RGB; + + if (bpp == 2) + dma2_cfg_reg |= DMA_IBUF_FORMAT_RGB565; + else if (bpp == 3) + dma2_cfg_reg |= DMA_IBUF_FORMAT_RGB888; + else + dma2_cfg_reg |= DMA_IBUF_FORMAT_xRGB8888_OR_ARGB8888; + + switch (mfd->panel_info.bpp) { + case 24: + dma2_cfg_reg |= DMA_DSTC0G_8BITS | + DMA_DSTC1B_8BITS | DMA_DSTC2R_8BITS; + break; + case 18: + dma2_cfg_reg |= DMA_DSTC0G_6BITS | + DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + break; + case 16: + dma2_cfg_reg |= DMA_DSTC0G_6BITS | + DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; + break; + default: + printk(KERN_ERR "mdp lcdc can't support format %d bpp!\n", + mfd->panel_info.bpp); + return -ENODEV; + } + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* starting address */ + MDP_OUTP(MDP_BASE + DMA_P_BASE + 0x8, (uint32) buf); + + /* active window width and height */ + MDP_OUTP(MDP_BASE + DMA_P_BASE + 0x4, ((fbi->var.yres) << 16) | + (fbi->var.xres)); + + /* buffer ystride */ + MDP_OUTP(MDP_BASE + DMA_P_BASE + 0xc, fbi->fix.line_length); + + /* x/y coordinate = always 0 for lcdc */ + MDP_OUTP(MDP_BASE + DMA_P_BASE + 0x10, 0); + + /* dma config */ + MDP_OUTP(MDP_BASE + DMA_P_BASE, dma2_cfg_reg); + + /* + * DSI timing setting + */ + h_back_porch = var->left_margin; + h_front_porch = var->right_margin; + v_back_porch = var->upper_margin; + v_front_porch = var->lower_margin; + hsync_pulse_width = var->hsync_len; + vsync_pulse_width = var->vsync_len; + dsi_border_clr = mfd->panel_info.lcdc.border_clr; + dsi_underflow_clr = mfd->panel_info.lcdc.underflow_clr; + dsi_hsync_skew = mfd->panel_info.lcdc.hsync_skew; + dsi_width = mfd->panel_info.xres; + dsi_height = mfd->panel_info.yres; + dsi_bpp = mfd->panel_info.bpp; + hsync_period = h_back_porch + dsi_width + h_front_porch + 1; + hsync_ctrl = (hsync_period << 16) | hsync_pulse_width; + hsync_start_x = h_back_porch; + hsync_end_x = dsi_width + h_back_porch - 1; + display_hctl = (hsync_end_x << 16) | hsync_start_x; + + vsync_period = + (v_back_porch + dsi_height + v_front_porch + 1) * hsync_period; + display_v_start = v_back_porch * hsync_period + dsi_hsync_skew; + display_v_end = (dsi_height + v_back_porch) * hsync_period; + + active_h_start = hsync_start_x + first_pixel_start_x; + active_h_end = active_h_start + var->xres - 1; + active_hctl = ACTIVE_START_X_EN | + (active_h_end << 16) | active_h_start; + + active_v_start = display_v_start + + first_pixel_start_y * hsync_period; + active_v_end = active_v_start + (var->yres) * hsync_period - 1; + active_v_start |= ACTIVE_START_Y_EN; + + dsi_underflow_clr |= 0x80000000; /* enable recovery */ + hsync_polarity = 0; + vsync_polarity = 0; + data_en_polarity = 0; + + ctrl_polarity = (data_en_polarity << 2) | + (vsync_polarity << 1) | (hsync_polarity); + + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x4, hsync_ctrl); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x8, vsync_period); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0xc, vsync_pulse_width); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x10, display_hctl); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x14, display_v_start); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x18, display_v_end); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x1c, active_hctl); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x20, active_v_start); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x24, active_v_end); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x28, dsi_border_clr); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x2c, dsi_underflow_clr); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x30, dsi_hsync_skew); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE + 0x38, ctrl_polarity); + + ret = panel_next_on(pdev); + if (ret == 0) { + /* enable DSI block */ + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE, 1); + /*Turning on DMA_P block*/ + mdp_pipe_ctrl(MDP_DMA2_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + } + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return ret; +} + +int mdp_dsi_video_off(struct platform_device *pdev) +{ + int ret = 0; + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + DSI_VIDEO_BASE, 0); + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + /*Turning off DMA_P block*/ + mdp_pipe_ctrl(MDP_DMA2_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + ret = panel_next_off(pdev); + /* delay to make sure the last frame finishes */ + msleep(20); + + return ret; +} + +void mdp_dsi_video_update(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi = mfd->fbi; + uint8 *buf; + int bpp; + unsigned long flag; + int irq_block = MDP_DMA2_TERM; + + if (!mfd->panel_power_on) + return; + + down(&mfd->dma->mutex); + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + /* no need to power on cmd block since it's dsi mode */ + /* starting address */ + MDP_OUTP(MDP_BASE + DMA_P_BASE + 0x8, (uint32) buf); + /* enable irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(irq_block); + INIT_COMPLETION(mfd->dma->comp); + mfd->dma->waiting = TRUE; + + outp32(MDP_INTR_CLEAR, LCDC_FRAME_START); + mdp_intr_mask |= LCDC_FRAME_START; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion_killable(&mfd->dma->comp); + mdp_disable_irq(irq_block); + up(&mfd->dma->mutex); +} diff --git a/drivers/video/msm/mdp_dma_lcdc.c b/drivers/video/msm/mdp_dma_lcdc.c new file mode 100644 index 00000000000..9ce7e13b14c --- /dev/null +++ b/drivers/video/msm/mdp_dma_lcdc.c @@ -0,0 +1,368 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "mdp.h" +#include "msm_fb.h" +#include "mdp4.h" + +#ifdef CONFIG_FB_MSM_MDP40 +#define LCDC_BASE 0xC0000 +#define DTV_BASE 0xD0000 +#define DMA_E_BASE 0xB0000 +#else +#define LCDC_BASE 0xE0000 +#endif + +#define DMA_P_BASE 0x90000 + +extern spinlock_t mdp_spin_lock; +#ifndef CONFIG_FB_MSM_MDP40 +extern uint32 mdp_intr_mask; +#endif + +int first_pixel_start_x; +int first_pixel_start_y; + +int mdp_lcdc_on(struct platform_device *pdev) +{ + int lcdc_width; + int lcdc_height; + int lcdc_bpp; + int lcdc_border_clr; + int lcdc_underflow_clr; + int lcdc_hsync_skew; + + int hsync_period; + int hsync_ctrl; + int vsync_period; + int display_hctl; + int display_v_start; + int display_v_end; + int active_hctl; + int active_h_start; + int active_h_end; + int active_v_start; + int active_v_end; + int ctrl_polarity; + int h_back_porch; + int h_front_porch; + int v_back_porch; + int v_front_porch; + int hsync_pulse_width; + int vsync_pulse_width; + int hsync_polarity; + int vsync_polarity; + int data_en_polarity; + int hsync_start_x; + int hsync_end_x; + uint8 *buf; + int bpp; + uint32 dma2_cfg_reg; + struct fb_info *fbi; + struct fb_var_screeninfo *var; + struct msm_fb_data_type *mfd; + uint32 dma_base; + uint32 timer_base = LCDC_BASE; + uint32 block = MDP_DMA2_BLOCK; + int ret; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + fbi = mfd->fbi; + var = &fbi->var; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + fbi->var.yoffset * fbi->fix.line_length; + + dma2_cfg_reg = DMA_PACK_ALIGN_LSB | DMA_OUT_SEL_LCDC; + + if (mfd->fb_imgType == MDP_BGR_565) + dma2_cfg_reg |= DMA_PACK_PATTERN_BGR; + else if (mfd->fb_imgType == MDP_RGBA_8888) + dma2_cfg_reg |= DMA_PACK_PATTERN_BGR; + else + dma2_cfg_reg |= DMA_PACK_PATTERN_RGB; + + if (bpp == 2) + dma2_cfg_reg |= DMA_IBUF_FORMAT_RGB565; + else if (bpp == 3) + dma2_cfg_reg |= DMA_IBUF_FORMAT_RGB888; + else + dma2_cfg_reg |= DMA_IBUF_FORMAT_xRGB8888_OR_ARGB8888; + + switch (mfd->panel_info.bpp) { + case 24: + dma2_cfg_reg |= DMA_DSTC0G_8BITS | + DMA_DSTC1B_8BITS | DMA_DSTC2R_8BITS; + break; + + case 18: + dma2_cfg_reg |= DMA_DSTC0G_6BITS | + DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + break; + + case 16: + dma2_cfg_reg |= DMA_DSTC0G_6BITS | + DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; + break; + + default: + printk(KERN_ERR "mdp lcdc can't support format %d bpp!\n", + mfd->panel_info.bpp); + return -ENODEV; + } + + /* DMA register config */ + + dma_base = DMA_P_BASE; + +#ifdef CONFIG_FB_MSM_MDP40 + if (mfd->panel.type == HDMI_PANEL) + dma_base = DMA_E_BASE; +#endif + + /* starting address */ + MDP_OUTP(MDP_BASE + dma_base + 0x8, (uint32) buf); + /* active window width and height */ + MDP_OUTP(MDP_BASE + dma_base + 0x4, ((fbi->var.yres) << 16) | + (fbi->var.xres)); + /* buffer ystride */ + MDP_OUTP(MDP_BASE + dma_base + 0xc, fbi->fix.line_length); + /* x/y coordinate = always 0 for lcdc */ + MDP_OUTP(MDP_BASE + dma_base + 0x10, 0); + /* dma config */ + MDP_OUTP(MDP_BASE + dma_base, dma2_cfg_reg); + + /* + * LCDC timing setting + */ + h_back_porch = var->left_margin; + h_front_porch = var->right_margin; + v_back_porch = var->upper_margin; + v_front_porch = var->lower_margin; + hsync_pulse_width = var->hsync_len; + vsync_pulse_width = var->vsync_len; + lcdc_border_clr = mfd->panel_info.lcdc.border_clr; + lcdc_underflow_clr = mfd->panel_info.lcdc.underflow_clr; + lcdc_hsync_skew = mfd->panel_info.lcdc.hsync_skew; + + lcdc_width = mfd->panel_info.xres; + lcdc_height = mfd->panel_info.yres; + lcdc_bpp = mfd->panel_info.bpp; + + hsync_period = + hsync_pulse_width + h_back_porch + lcdc_width + h_front_porch; + hsync_ctrl = (hsync_period << 16) | hsync_pulse_width; + hsync_start_x = hsync_pulse_width + h_back_porch; + hsync_end_x = hsync_period - h_front_porch - 1; + display_hctl = (hsync_end_x << 16) | hsync_start_x; + + vsync_period = + (vsync_pulse_width + v_back_porch + lcdc_height + + v_front_porch) * hsync_period; + display_v_start = + (vsync_pulse_width + v_back_porch) * hsync_period + lcdc_hsync_skew; + display_v_end = + vsync_period - (v_front_porch * hsync_period) + lcdc_hsync_skew - 1; + + if (lcdc_width != var->xres) { + active_h_start = hsync_start_x + first_pixel_start_x; + active_h_end = active_h_start + var->xres - 1; + active_hctl = + ACTIVE_START_X_EN | (active_h_end << 16) | active_h_start; + } else { + active_hctl = 0; + } + + if (lcdc_height != var->yres) { + active_v_start = + display_v_start + first_pixel_start_y * hsync_period; + active_v_end = active_v_start + (var->yres) * hsync_period - 1; + active_v_start |= ACTIVE_START_Y_EN; + } else { + active_v_start = 0; + active_v_end = 0; + } + + +#ifdef CONFIG_FB_MSM_MDP40 + if (mfd->panel.type == HDMI_PANEL) { + block = MDP_DMA_E_BLOCK; + timer_base = DTV_BASE; + hsync_polarity = 0; + vsync_polarity = 0; + } else { + hsync_polarity = 1; + vsync_polarity = 1; + } + + lcdc_underflow_clr |= 0x80000000; /* enable recovery */ +#else + hsync_polarity = 0; + vsync_polarity = 0; +#endif + data_en_polarity = 0; + + ctrl_polarity = + (data_en_polarity << 2) | (vsync_polarity << 1) | (hsync_polarity); + + MDP_OUTP(MDP_BASE + timer_base + 0x4, hsync_ctrl); + MDP_OUTP(MDP_BASE + timer_base + 0x8, vsync_period); + MDP_OUTP(MDP_BASE + timer_base + 0xc, vsync_pulse_width * hsync_period); + if (timer_base == LCDC_BASE) { + MDP_OUTP(MDP_BASE + timer_base + 0x10, display_hctl); + MDP_OUTP(MDP_BASE + timer_base + 0x14, display_v_start); + MDP_OUTP(MDP_BASE + timer_base + 0x18, display_v_end); + MDP_OUTP(MDP_BASE + timer_base + 0x28, lcdc_border_clr); + MDP_OUTP(MDP_BASE + timer_base + 0x2c, lcdc_underflow_clr); + MDP_OUTP(MDP_BASE + timer_base + 0x30, lcdc_hsync_skew); + MDP_OUTP(MDP_BASE + timer_base + 0x38, ctrl_polarity); + MDP_OUTP(MDP_BASE + timer_base + 0x1c, active_hctl); + MDP_OUTP(MDP_BASE + timer_base + 0x20, active_v_start); + MDP_OUTP(MDP_BASE + timer_base + 0x24, active_v_end); + } else { + MDP_OUTP(MDP_BASE + timer_base + 0x18, display_hctl); + MDP_OUTP(MDP_BASE + timer_base + 0x1c, display_v_start); + MDP_OUTP(MDP_BASE + timer_base + 0x20, display_v_end); + MDP_OUTP(MDP_BASE + timer_base + 0x40, lcdc_border_clr); + MDP_OUTP(MDP_BASE + timer_base + 0x44, lcdc_underflow_clr); + MDP_OUTP(MDP_BASE + timer_base + 0x48, lcdc_hsync_skew); + MDP_OUTP(MDP_BASE + timer_base + 0x50, ctrl_polarity); + MDP_OUTP(MDP_BASE + timer_base + 0x2c, active_hctl); + MDP_OUTP(MDP_BASE + timer_base + 0x30, active_v_start); + MDP_OUTP(MDP_BASE + timer_base + 0x38, active_v_end); + } + + ret = panel_next_on(pdev); + if (ret == 0) { + /* enable LCDC block */ + MDP_OUTP(MDP_BASE + timer_base, 1); + mdp_pipe_ctrl(block, MDP_BLOCK_POWER_ON, FALSE); + } + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + return ret; +} + +int mdp_lcdc_off(struct platform_device *pdev) +{ + int ret = 0; + struct msm_fb_data_type *mfd; + uint32 timer_base = LCDC_BASE; + uint32 block = MDP_DMA2_BLOCK; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + +#ifdef CONFIG_FB_MSM_MDP40 + if (mfd->panel.type == HDMI_PANEL) { + block = MDP_DMA_E_BLOCK; + timer_base = DTV_BASE; + } +#endif + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + timer_base, 0); + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + mdp_pipe_ctrl(block, MDP_BLOCK_POWER_OFF, FALSE); + + ret = panel_next_off(pdev); + + /* delay to make sure the last frame finishes */ + msleep(16); + + return ret; +} + +void mdp_lcdc_update(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi = mfd->fbi; + uint8 *buf; + int bpp; + unsigned long flag; + uint32 dma_base; + int irq_block = MDP_DMA2_TERM; +#ifdef CONFIG_FB_MSM_MDP40 + int intr = INTR_DMA_P_DONE; +#endif + + if (!mfd->panel_power_on) + return; + + /* no need to power on cmd block since it's lcdc mode */ + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + dma_base = DMA_P_BASE; + +#ifdef CONFIG_FB_MSM_MDP40 + if (mfd->panel.type == HDMI_PANEL) { + intr = INTR_DMA_E_DONE; + irq_block = MDP_DMA_E_TERM; + dma_base = DMA_E_BASE; + } +#endif + + /* starting address */ + MDP_OUTP(MDP_BASE + dma_base + 0x8, (uint32) buf); + + /* enable LCDC irq */ + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(irq_block); + INIT_COMPLETION(mfd->dma->comp); + mfd->dma->waiting = TRUE; +#ifdef CONFIG_FB_MSM_MDP40 + outp32(MDP_INTR_CLEAR, intr); + mdp_intr_mask |= intr; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); +#else + outp32(MDP_INTR_CLEAR, LCDC_FRAME_START); + mdp_intr_mask |= LCDC_FRAME_START; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); +#endif + spin_unlock_irqrestore(&mdp_spin_lock, flag); + wait_for_completion_killable(&mfd->dma->comp); + mdp_disable_irq(irq_block); +} diff --git a/drivers/video/msm/mdp_dma_s.c b/drivers/video/msm/mdp_dma_s.c new file mode 100644 index 00000000000..22d79be61a7 --- /dev/null +++ b/drivers/video/msm/mdp_dma_s.c @@ -0,0 +1,166 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "mdp.h" +#include "msm_fb.h" + +static void mdp_dma_s_update_lcd(struct msm_fb_data_type *mfd) +{ + MDPIBUF *iBuf = &mfd->ibuf; + int mddi_dest = FALSE; + uint32 outBpp = iBuf->bpp; + uint32 dma_s_cfg_reg; + uint8 *src; + struct msm_fb_panel_data *pdata = + (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; + + dma_s_cfg_reg = DMA_PACK_TIGHT | DMA_PACK_ALIGN_LSB | + DMA_OUT_SEL_AHB | DMA_IBUF_NONCONTIGUOUS; + + if (mfd->fb_imgType == MDP_BGR_565) + dma_s_cfg_reg |= DMA_PACK_PATTERN_BGR; + else + dma_s_cfg_reg |= DMA_PACK_PATTERN_RGB; + + if (outBpp == 4) + dma_s_cfg_reg |= DMA_IBUF_C3ALPHA_EN; + + if (outBpp == 2) + dma_s_cfg_reg |= DMA_IBUF_FORMAT_RGB565; + + if (mfd->panel_info.pdest != DISPLAY_2) { + printk(KERN_ERR "error: non-secondary type through dma_s!\n"); + return; + } + + if (mfd->panel_info.type == MDDI_PANEL || + mfd->panel_info.type == EXT_MDDI_PANEL) { + dma_s_cfg_reg |= DMA_OUT_SEL_MDDI; + mddi_dest = TRUE; + } else { + dma_s_cfg_reg |= DMA_AHBM_LCD_SEL_SECONDARY; + outp32(MDP_EBI2_LCD1, mfd->data_port_phys); + } + + src = (uint8 *) iBuf->buf; + /* starting input address */ + src += (iBuf->dma_x + iBuf->dma_y * iBuf->ibuf_width) * outBpp; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + /* PIXELSIZE */ + if (mfd->panel_info.type == MDDI_PANEL) { + MDP_OUTP(MDP_BASE + 0xa0004, + (iBuf->dma_h << 16 | iBuf->dma_w)); + MDP_OUTP(MDP_BASE + 0xa0008, src); /* ibuf address */ + MDP_OUTP(MDP_BASE + 0xa000c, + iBuf->ibuf_width * outBpp);/* ystride */ + } else { + MDP_OUTP(MDP_BASE + 0xb0004, + (iBuf->dma_h << 16 | iBuf->dma_w)); + MDP_OUTP(MDP_BASE + 0xb0008, src); /* ibuf address */ + MDP_OUTP(MDP_BASE + 0xb000c, + iBuf->ibuf_width * outBpp);/* ystride */ + } + + if (mfd->panel_info.bpp == 18) { + dma_s_cfg_reg |= DMA_DSTC0G_6BITS | /* 666 18BPP */ + DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + } else { + dma_s_cfg_reg |= DMA_DSTC0G_6BITS | /* 565 16BPP */ + DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; + } + + if (mddi_dest) { + if (mfd->panel_info.type == MDDI_PANEL) { + MDP_OUTP(MDP_BASE + 0xa0010, + (iBuf->dma_y << 16) | iBuf->dma_x); + MDP_OUTP(MDP_BASE + 0x00090, 1); + } else { + MDP_OUTP(MDP_BASE + 0xb0010, + (iBuf->dma_y << 16) | iBuf->dma_x); + MDP_OUTP(MDP_BASE + 0x00090, 2); + } + MDP_OUTP(MDP_BASE + 0x00094, + (MDDI_VDO_PACKET_DESC << 16) | + mfd->panel_info.mddi.vdopkt); + } else { + /* setting LCDC write window */ + pdata->set_rect(iBuf->dma_x, iBuf->dma_y, iBuf->dma_w, + iBuf->dma_h); + } + + if (mfd->panel_info.type == MDDI_PANEL) + MDP_OUTP(MDP_BASE + 0xa0000, dma_s_cfg_reg); + else + MDP_OUTP(MDP_BASE + 0xb0000, dma_s_cfg_reg); + + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + if (mfd->panel_info.type == MDDI_PANEL) + mdp_pipe_kickoff(MDP_DMA_S_TERM, mfd); + else + mdp_pipe_kickoff(MDP_DMA_E_TERM, mfd); + +} + +void mdp_dma_s_update(struct msm_fb_data_type *mfd) +{ + down(&mfd->dma->mutex); + if ((mfd) && (!mfd->dma->busy) && (mfd->panel_power_on)) { + down(&mfd->sem); + mdp_enable_irq(MDP_DMA_S_TERM); + if (mfd->panel_info.type == MDDI_PANEL) + mdp_enable_irq(MDP_DMA_S_TERM); + else + mdp_enable_irq(MDP_DMA_E_TERM); + mfd->dma->busy = TRUE; + INIT_COMPLETION(mfd->dma->comp); + mfd->ibuf_flushed = TRUE; + mdp_dma_s_update_lcd(mfd); + up(&mfd->sem); + + /* wait until DMA finishes the current job */ + wait_for_completion_killable(&mfd->dma->comp); + if (mfd->panel_info.type == MDDI_PANEL) + mdp_disable_irq(MDP_DMA_S_TERM); + else + mdp_disable_irq(MDP_DMA_E_TERM); + + /* signal if pan function is waiting for the update completion */ + if (mfd->pan_waiting) { + mfd->pan_waiting = FALSE; + complete(&mfd->pan_comp); + } + } + up(&mfd->dma->mutex); +} diff --git a/drivers/video/msm/mdp_dma_tv.c b/drivers/video/msm/mdp_dma_tv.c new file mode 100644 index 00000000000..66d9422fc12 --- /dev/null +++ b/drivers/video/msm/mdp_dma_tv.c @@ -0,0 +1,138 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "mdp.h" +#include "msm_fb.h" + +extern spinlock_t mdp_spin_lock; +extern uint32 mdp_intr_mask; + +int mdp_dma3_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct fb_info *fbi; + uint8 *buf; + int bpp; + int ret = 0; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + fbi = mfd->fbi; + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + + /* starting address[31..8] of Video frame buffer is CS0 */ + MDP_OUTP(MDP_BASE + 0xC0008, (uint32) buf >> 3); + + mdp_pipe_ctrl(MDP_DMA3_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + MDP_OUTP(MDP_BASE + 0xC0004, 0x4c60674); /* flicker filter enabled */ + MDP_OUTP(MDP_BASE + 0xC0010, 0x20); /* sobel treshold */ + + MDP_OUTP(MDP_BASE + 0xC0018, 0xeb0010); /* Y Max, Y min */ + MDP_OUTP(MDP_BASE + 0xC001C, 0xf00010); /* Cb Max, Cb min */ + MDP_OUTP(MDP_BASE + 0xC0020, 0xf00010); /* Cb Max, Cb min */ + + MDP_OUTP(MDP_BASE + 0xC000C, 0x67686970); /* add a few chars for CC */ + MDP_OUTP(MDP_BASE + 0xC0000, 0x1); /* MDP tv out enable */ + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + ret = panel_next_on(pdev); + + return ret; +} + +int mdp_dma3_off(struct platform_device *pdev) +{ + int ret = 0; + + ret = panel_next_off(pdev); + if (ret) + return ret; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + MDP_OUTP(MDP_BASE + 0xC0000, 0x0); + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + mdp_pipe_ctrl(MDP_DMA3_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + /* delay to make sure the last frame finishes */ + msleep(16); + + return ret; +} + +void mdp_dma3_update(struct msm_fb_data_type *mfd) +{ + struct fb_info *fbi = mfd->fbi; + uint8 *buf; + int bpp; + unsigned long flag; + + if (!mfd->panel_power_on) + return; + + /* no need to power on cmd block since dma3 is running */ + bpp = fbi->var.bits_per_pixel / 8; + buf = (uint8 *) fbi->fix.smem_start; + buf += fbi->var.xoffset * bpp + + fbi->var.yoffset * fbi->fix.line_length; + MDP_OUTP(MDP_BASE + 0xC0008, (uint32) buf >> 3); + + spin_lock_irqsave(&mdp_spin_lock, flag); + mdp_enable_irq(MDP_DMA3_TERM); + INIT_COMPLETION(mfd->dma->comp); + mfd->dma->waiting = TRUE; + + outp32(MDP_INTR_CLEAR, TV_OUT_DMA3_START); + mdp_intr_mask |= TV_OUT_DMA3_START; + outp32(MDP_INTR_ENABLE, mdp_intr_mask); + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + wait_for_completion_killable(&mfd->dma->comp); + mdp_disable_irq(MDP_DMA3_TERM); +} diff --git a/drivers/video/msm/mdp_hw.h b/drivers/video/msm/mdp_hw.h index d80477415ca..f35a757d473 100644 --- a/drivers/video/msm/mdp_hw.h +++ b/drivers/video/msm/mdp_hw.h @@ -15,20 +15,61 @@ #ifndef _MDP_HW_H_ #define _MDP_HW_H_ +#include +#include #include #include +typedef void (*mdp_dma_start_func_t)(void *private_data, uint32_t addr, + uint32_t stride, uint32_t width, + uint32_t height, uint32_t x, uint32_t y); + +struct mdp_out_interface { + uint32_t registered:1; + void *priv; + + /* If the interface client wants to get DMA_DONE events */ + uint32_t dma_mask; + mdp_dma_start_func_t dma_start; + + struct msmfb_callback *dma_cb; + wait_queue_head_t dma_waitqueue; + + /* If the interface client wants to be notified of non-DMA irqs, + * e.g. LCDC/TV-out frame start */ + uint32_t irq_mask; + struct msmfb_callback *irq_cb; +}; + struct mdp_info { + spinlock_t lock; struct mdp_device mdp_dev; char * __iomem base; int irq; + struct clk *clk; + struct clk *ebi1_clk; + struct mdp_out_interface out_if[MSM_MDP_NUM_INTERFACES]; + int format; + int pack_pattern; + bool dma_config_dirty; }; + +extern int mdp_out_if_register(struct mdp_device *mdp_dev, int interface, + void *private_data, uint32_t dma_mask, + mdp_dma_start_func_t dma_start); + +extern int mdp_out_if_req_irq(struct mdp_device *mdp_dev, int interface, + uint32_t mask, struct msmfb_callback *cb); + struct mdp_blit_req; struct mdp_device; int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, struct file *src_file, unsigned long src_start, unsigned long src_len, struct file *dst_file, unsigned long dst_start, unsigned long dst_len); + +void mdp_ppp_dump_debug(const struct mdp_info *mdp); + #define mdp_writel(mdp, value, offset) writel(value, mdp->base + offset) #define mdp_readl(mdp, offset) readl(mdp->base + offset) @@ -48,10 +89,18 @@ int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, #define MDP_DISPLAY_STATUS (0x00038) #define MDP_EBI2_LCD0 (0x0003c) #define MDP_EBI2_LCD1 (0x00040) +#define MDP_EBI2_PORTMAP_MODE (0x0005c) + +#ifndef CONFIG_MSM_MDP31 #define MDP_DISPLAY0_ADDR (0x00054) #define MDP_DISPLAY1_ADDR (0x00058) -#define MDP_EBI2_PORTMAP_MODE (0x0005c) -#define MDP_MODE (0x00060) +#define MDP_PPP_CMD_MODE (0x00060) +#else +#define MDP_DISPLAY0_ADDR (0x10000) +#define MDP_DISPLAY1_ADDR (0x10004) +#define MDP_PPP_CMD_MODE (0x10060) +#endif + #define MDP_TV_OUT_STATUS (0x00064) #define MDP_HW_VERSION (0x00070) #define MDP_SW_RESET (0x00074) @@ -61,6 +110,8 @@ int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, #define MDP_SECONDARY_VSYNC_OUT_CTRL (0x00084) #define MDP_EXTERNAL_VSYNC_OUT_CTRL (0x00088) #define MDP_VSYNC_CTRL (0x0008c) +#define MDP_MDDI_PARAM_WR_SEL (0x00090) +#define MDP_MDDI_PARAM (0x00094) #define MDP_CGC_EN (0x00100) #define MDP_CMD_STATUS (0x10008) #define MDP_PROFILE_EN (0x10010) @@ -107,6 +158,7 @@ int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, #define MDP_FULL_BYPASS_WORD35 (0x1018c) #define MDP_FULL_BYPASS_WORD37 (0x10194) #define MDP_FULL_BYPASS_WORD39 (0x1019c) +#define MDP_PPP_OUT_XY (0x1019c) #define MDP_FULL_BYPASS_WORD40 (0x101a0) #define MDP_FULL_BYPASS_WORD41 (0x101a4) #define MDP_FULL_BYPASS_WORD43 (0x101ac) @@ -129,11 +181,27 @@ int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, #define MDP_FULL_BYPASS_WORD61 (0x101f4) #define MDP_FULL_BYPASS_WORD62 (0x101f8) #define MDP_FULL_BYPASS_WORD63 (0x101fc) + +#ifdef CONFIG_MSM_MDP31 +#define MDP_PPP_SRC_XY (0x10200) +#define MDP_PPP_BG_XY (0x10204) +#define MDP_PPP_SRC_IMAGE_SIZE (0x10208) +#define MDP_PPP_BG_IMAGE_SIZE (0x1020c) +#define MDP_PPP_SCALE_CONFIG (0x10230) +#define MDP_PPP_CSC_CONFIG (0x10240) +#define MDP_PPP_BLEND_BG_ALPHA_SEL (0x70010) +#endif + #define MDP_TFETCH_TEST_MODE (0x20004) #define MDP_TFETCH_STATUS (0x20008) #define MDP_TFETCH_TILE_COUNT (0x20010) #define MDP_TFETCH_FETCH_COUNT (0x20014) #define MDP_TFETCH_CONSTANT_COLOR (0x20040) +#define MDP_BGTFETCH_TEST_MODE (0x28004) +#define MDP_BGTFETCH_STATUS (0x28008) +#define MDP_BGTFETCH_TILE_COUNT (0x28010) +#define MDP_BGTFETCH_FETCH_COUNT (0x28014) +#define MDP_BGTFETCH_CONSTANT_COLOR (0x28040) #define MDP_CSC_BYPASS (0x40004) #define MDP_SCALE_COEFF_LSB (0x5fffc) #define MDP_TV_OUT_CTL (0xc0000) @@ -158,55 +226,49 @@ int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, #define MDP_TEST_MISR_CURR_VAL_DCLK (0xd020c) #define MDP_TEST_CAPTURED_DCLK (0xd0210) #define MDP_TEST_MISR_CAPT_VAL_DCLK (0xd0214) -#define MDP_LCDC_CTL (0xe0000) + +#define MDP_DMA_P_START (0x00044) +#define MDP_DMA_P_CONFIG (0x90000) +#define MDP_DMA_P_SIZE (0x90004) +#define MDP_DMA_P_IBUF_ADDR (0x90008) +#define MDP_DMA_P_IBUF_Y_STRIDE (0x9000c) +#define MDP_DMA_P_OUT_XY (0x90010) +#define MDP_DMA_P_COLOR_CORRECT_CONFIG (0x90070) + +#define MDP_LCDC_EN (0xe0000) #define MDP_LCDC_HSYNC_CTL (0xe0004) -#define MDP_LCDC_VSYNC_CTL (0xe0008) -#define MDP_LCDC_ACTIVE_HCTL (0xe000c) -#define MDP_LCDC_ACTIVE_VCTL (0xe0010) -#define MDP_LCDC_BORDER_CLR (0xe0014) -#define MDP_LCDC_H_BLANK (0xe0018) -#define MDP_LCDC_V_BLANK (0xe001c) -#define MDP_LCDC_UNDERFLOW_CLR (0xe0020) -#define MDP_LCDC_HSYNC_SKEW (0xe0024) -#define MDP_LCDC_TEST_CTL (0xe0028) -#define MDP_LCDC_LINE_IRQ (0xe002c) -#define MDP_LCDC_CTL_POLARITY (0xe0030) -#define MDP_LCDC_DMA_CONFIG (0xe1000) -#define MDP_LCDC_DMA_SIZE (0xe1004) -#define MDP_LCDC_DMA_IBUF_ADDR (0xe1008) -#define MDP_LCDC_DMA_IBUF_Y_STRIDE (0xe100c) - - -#define MDP_DMA2_TERM 0x1 -#define MDP_DMA3_TERM 0x2 -#define MDP_PPP_TERM 0x3 +#define MDP_LCDC_VSYNC_PERIOD (0xe0008) +#define MDP_LCDC_VSYNC_PULSE_WIDTH (0xe000c) +#define MDP_LCDC_DISPLAY_HCTL (0xe0010) +#define MDP_LCDC_DISPLAY_V_START (0xe0014) +#define MDP_LCDC_DISPLAY_V_END (0xe0018) +#define MDP_LCDC_ACTIVE_HCTL (0xe001c) +#define MDP_LCDC_ACTIVE_V_START (0xe0020) +#define MDP_LCDC_ACTIVE_V_END (0xe0024) +#define MDP_LCDC_BORDER_CLR (0xe0028) +#define MDP_LCDC_UNDERFLOW_CTL (0xe002c) +#define MDP_LCDC_HSYNC_SKEW (0xe0030) +#define MDP_LCDC_TEST_CTL (0xe0034) +#define MDP_LCDC_CTL_POLARITY (0xe0038) + +#define MDP_PPP_SCALE_STATUS (0x50000) +#define MDP_PPP_BLEND_STATUS (0x70000) + +/* MDP_SW_RESET */ +#define MDP_PPP_SW_RESET (1<<4) /* MDP_INTR_ENABLE */ -#define DL0_ROI_DONE (1<<0) -#define DL1_ROI_DONE (1<<1) -#define DL0_DMA2_TERM_DONE (1<<2) -#define DL1_DMA2_TERM_DONE (1<<3) -#define DL0_PPP_TERM_DONE (1<<4) -#define DL1_PPP_TERM_DONE (1<<5) -#define TV_OUT_DMA3_DONE (1<<6) -#define TV_ENC_UNDERRUN (1<<7) -#define DL0_FETCH_DONE (1<<11) -#define DL1_FETCH_DONE (1<<12) - -#define MDP_PPP_BUSY_STATUS (DL0_ROI_DONE| \ - DL1_ROI_DONE| \ - DL0_PPP_TERM_DONE| \ - DL1_PPP_TERM_DONE) - -#define MDP_ANY_INTR_MASK (DL0_ROI_DONE| \ - DL1_ROI_DONE| \ - DL0_DMA2_TERM_DONE| \ - DL1_DMA2_TERM_DONE| \ - DL0_PPP_TERM_DONE| \ - DL1_PPP_TERM_DONE| \ - DL0_FETCH_DONE| \ - DL1_FETCH_DONE| \ - TV_ENC_UNDERRUN) +#define DL0_ROI_DONE (1<<0) +#define TV_OUT_DMA3_DONE (1<<6) +#define TV_ENC_UNDERRUN (1<<7) + +#ifdef CONFIG_MSM_MDP22 +#define MDP_DMA_P_DONE (1 << 2) +#else /* CONFIG_MSM_MDP31 */ +#define MDP_DMA_P_DONE (1 << 14) +#define MDP_LCDC_UNDERFLOW (1 << 16) +#define MDP_LCDC_FRAME_START (1 << 15) +#endif #define MDP_TOP_LUMA 16 #define MDP_TOP_CHROMA 0 @@ -316,7 +378,12 @@ int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, #define PPP_OP_SCALE_X_ON (1<<0) #define PPP_OP_SCALE_Y_ON (1<<1) +#ifndef CONFIG_MSM_MDP31 #define PPP_OP_CONVERT_RGB2YCBCR 0 +#else +#define PPP_OP_CONVERT_RGB2YCBCR (1<<30) +#endif + #define PPP_OP_CONVERT_YCBCR2RGB (1<<2) #define PPP_OP_CONVERT_ON (1<<3) @@ -372,6 +439,13 @@ int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, #define PPP_OP_BG_CHROMA_SITE_COSITE 0 #define PPP_OP_BG_CHROMA_SITE_OFFSITE (1<<27) +#define PPP_BLEND_BG_USE_ALPHA_SEL (1 << 0) +#define PPP_BLEND_BG_ALPHA_REVERSE (1 << 3) +#define PPP_BLEND_BG_SRCPIXEL_ALPHA (0 << 1) +#define PPP_BLEND_BG_DSTPIXEL_ALPHA (1 << 1) +#define PPP_BLEND_BG_CONSTANT_ALPHA (2 << 1) +#define PPP_BLEND_BG_CONST_ALPHA_VAL(x) ((x) << 24) + /* MDP_PPP_DESTINATION_CONFIG / MDP_FULL_BYPASS_WORD20 */ #define PPP_DST_C0G_8BIT ((1<<0)|(1<<1)) #define PPP_DST_C1B_8BIT ((1<<3)|(1<<2)) @@ -589,20 +663,71 @@ int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, #define PPP_ADDR_BG_CFG MDP_FULL_BYPASS_WORD53 #define PPP_ADDR_BG_PACK_PATTERN MDP_FULL_BYPASS_WORD54 +/* color conversion matrix configuration registers */ +/* pfmv is mv1, prmv is mv2 */ +#define MDP_CSC_PFMVn(n) (0x40400 + (4 * (n))) +#define MDP_CSC_PRMVn(n) (0x40440 + (4 * (n))) + +#ifdef CONFIG_MSM_MDP31 +#define MDP_PPP_CSC_PRE_BV1n(n) (0x40500 + (4 * (n))) +#define MDP_PPP_CSC_PRE_BV2n(n) (0x40540 + (4 * (n))) +#define MDP_PPP_CSC_POST_BV1n(n) (0x40580 + (4 * (n))) +#define MDP_PPP_CSC_POST_BV2n(n) (0x405c0 + (4 * (n))) + +#define MDP_PPP_CSC_PRE_LV1n(n) (0x40600 + (4 * (n))) +#define MDP_PPP_CSC_PRE_LV2n(n) (0x40640 + (4 * (n))) +#define MDP_PPP_CSC_POST_LV1n(n) (0x40680 + (4 * (n))) +#define MDP_PPP_CSC_POST_LV2n(n) (0x406c0 + (4 * (n))) + +#define MDP_PPP_SCALE_COEFF_D0_SET (0) +#define MDP_PPP_SCALE_COEFF_D1_SET (1) +#define MDP_PPP_SCALE_COEFF_D2_SET (2) +#define MDP_PPP_SCALE_COEFF_U1_SET (3) +#define MDP_PPP_SCALE_COEFF_LSBn(n) (0x50400 + (8 * (n))) +#define MDP_PPP_SCALE_COEFF_MSBn(n) (0x50404 + (8 * (n))) + +#define MDP_PPP_DEINT_COEFFn(n) (0x30010 + (4 * (n))) + +#define MDP_PPP_SCALER_FIR (0) +#define MDP_PPP_SCALER_MN (1) + +#else /* !defined(CONFIG_MSM_MDP31) */ + +#define MDP_CSC_PBVn(n) (0x40500 + (4 * (n))) +#define MDP_CSC_SBVn(n) (0x40540 + (4 * (n))) +#define MDP_CSC_PLVn(n) (0x40580 + (4 * (n))) +#define MDP_CSC_SLVn(n) (0x405c0 + (4 * (n))) + +#endif + + /* MDP_DMA_CONFIG / MDP_FULL_BYPASS_WORD32 */ -#define DMA_DSTC0G_6BITS (1<<1) -#define DMA_DSTC1B_6BITS (1<<3) -#define DMA_DSTC2R_6BITS (1<<5) #define DMA_DSTC0G_5BITS (1<<0) #define DMA_DSTC1B_5BITS (1<<2) #define DMA_DSTC2R_5BITS (1<<4) +#define DMA_DSTC0G_6BITS (2<<0) +#define DMA_DSTC1B_6BITS (2<<2) +#define DMA_DSTC2R_6BITS (2<<4) + +#define DMA_DSTC0G_8BITS (3<<0) +#define DMA_DSTC1B_8BITS (3<<2) +#define DMA_DSTC2R_8BITS (3<<4) + +#define DMA_DST_BITS_MASK 0x3F + #define DMA_PACK_TIGHT (1<<6) #define DMA_PACK_LOOSE 0 #define DMA_PACK_ALIGN_LSB 0 #define DMA_PACK_ALIGN_MSB (1<<7) +#define DMA_PACK_PATTERN_MASK (0x3f<<8) #define DMA_PACK_PATTERN_RGB \ (MDP_GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 2)<<8) +#define DMA_PACK_PATTERN_BGR \ + (MDP_GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 2)<<8) + + +#ifdef CONFIG_MSM_MDP22 #define DMA_OUT_SEL_AHB 0 #define DMA_OUT_SEL_MDDI (1<<14) @@ -610,16 +735,32 @@ int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, #define DMA_AHBM_LCD_SEL_SECONDARY (1<<15) #define DMA_IBUF_C3ALPHA_EN (1<<16) #define DMA_DITHER_EN (1<<17) - #define DMA_MDDI_DMAOUT_LCD_SEL_PRIMARY 0 #define DMA_MDDI_DMAOUT_LCD_SEL_SECONDARY (1<<18) #define DMA_MDDI_DMAOUT_LCD_SEL_EXTERNAL (1<<19) - #define DMA_IBUF_FORMAT_RGB565 (1<<20) #define DMA_IBUF_FORMAT_RGB888_OR_ARGB8888 0 - +#define DMA_IBUF_FORMAT_MASK (1 << 20) #define DMA_IBUF_NONCONTIGUOUS (1<<21) +#else /* CONFIG_MSM_MDP31 */ + +#define DMA_OUT_SEL_AHB (0 << 19) +#define DMA_OUT_SEL_MDDI (1 << 19) +#define DMA_OUT_SEL_LCDC (2 << 19) +#define DMA_OUT_SEL_LCDC_MDDI (3 << 19) +#define DMA_DITHER_EN (1 << 24) +#define DMA_IBUF_FORMAT_RGB888 (0 << 25) +#define DMA_IBUF_FORMAT_RGB565 (1 << 25) +#define DMA_IBUF_FORMAT_XRGB8888 (2 << 25) +#define DMA_IBUF_FORMAT_MASK (3 << 25) +#define DMA_IBUF_NONCONTIGUOUS (0) + +#define DMA_MDDI_DMAOUT_LCD_SEL_PRIMARY (0) +#define DMA_MDDI_DMAOUT_LCD_SEL_SECONDARY (0) +#define DMA_MDDI_DMAOUT_LCD_SEL_EXTERNAL (0) +#endif + /* MDDI REGISTER ? */ #define MDDI_VDO_PACKET_DESC 0x5666 #define MDDI_VDO_PACKET_PRIM 0xC3 diff --git a/drivers/video/msm/mdp_hw40.c b/drivers/video/msm/mdp_hw40.c new file mode 100644 index 00000000000..d36125e2b2a --- /dev/null +++ b/drivers/video/msm/mdp_hw40.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010 Google, Inc. + * Author: Dima Zavin + * + * Based on code from Code Aurora Forum. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include + +#include "mdp_hw.h" + +static void mdp_dma_to_mddi(void *priv, uint32_t addr, uint32_t stride, + uint32_t width, uint32_t height, uint32_t x, + uint32_t y) +{ + struct mdp_info *mdp = priv; + uint32_t dma2_cfg; + uint16_t ld_param = 0; /* 0=PRIM, 1=SECD, 2=EXT */ + + dma2_cfg = DMA_PACK_TIGHT | + DMA_PACK_ALIGN_LSB; + + dma2_cfg |= mdp->dma_format; + dma2_cfg |= mdp->dma_pack_pattern; + dma2_cfg |= DMA_DITHER_EN; + + /* 666 18BPP */ + dma2_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + + /* setup size, address, and stride */ + mdp_writel(mdp, (height << 16) | (width), MDP_DMA_P_SIZE); + mdp_writel(mdp, addr, MDP_DMA_P_IBUF_ADDR); + mdp_writel(mdp, stride, MDP_DMA_P_IBUF_Y_STRIDE); + + /* set y & x offset and MDDI transaction parameters */ + mdp_writel(mdp, (y << 16) | (x), MDP_DMA_P_OUT_XY); + mdp_writel(mdp, ld_param, MDP_MDDI_PARAM_WR_SEL); + mdp_writel(mdp, (MDDI_VDO_PACKET_DESC << 16) | MDDI_VDO_PACKET_PRIM, + MDP_MDDI_PARAM); + + mdp_writel(mdp, 0x1, MDP_MDDI_DATA_XFR); + mdp_writel(mdp, dma2_cfg, MDP_DMA_P_CONFIG); + mdp_writel(mdp, 0, MDP_DMA_P_START); +} + +int mdp_hw_init(struct mdp_info *mdp) +{ + int ret; + + ret = mdp_out_if_register(&mdp->mdp_dev, MSM_MDDI_PMDH_INTERFACE, mdp, + MDP_DMA_P_DONE, mdp_dma_to_mddi); + if (ret) + return ret; + + mdp_writel(mdp, 0, MDP_INTR_ENABLE); + mdp_writel(mdp, 0, MDP_DMA_P_HIST_INTR_ENABLE); + + /* XXX: why set this? QCT says it should be > mdp_pclk, + * but they never set the clkrate of pclk */ + mdp_set_core_clk(4); + pr_info("%s: mdp_clk=%lu\n", __func__, clk_get_rate(mdp->clk)); + + /* TODO: Configure the VG/RGB pipes fetch data */ + + /* this should work for any mdp_clk freq. + * TODO: use different value for mdp_clk freqs >= 90Mhz */ + mdp_writel(mdp, 0x27, MDP_DMA_P_FETCH_CFG); /* 8 bytes-burst x 8 req */ + + mdp_writel(mdp, 0x3, MDP_EBI2_PORTMAP_MODE); + + /* 3 pending requests */ + mdp_writel(mdp, 0x02222, MDP_MAX_RD_PENDING_CMD_CONFIG); + + /* no overlay processing, sw controls everything */ + mdp_writel(mdp, 0, MDP_LAYERMIXER_IN_CFG); + mdp_writel(mdp, 1 << 3, MDP_OVERLAYPROC0_CFG); + mdp_writel(mdp, 1 << 3, MDP_OVERLAYPROC1_CFG); + + /* XXX: HACK! hardcode to do mddi on primary */ + mdp_writel(mdp, 0x2, MDP_DISP_INTF_SEL); + return 0; +} + diff --git a/drivers/video/msm/mdp_hw_init.c b/drivers/video/msm/mdp_hw_init.c new file mode 100644 index 00000000000..8f8b4d3103a --- /dev/null +++ b/drivers/video/msm/mdp_hw_init.c @@ -0,0 +1,716 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "mdp.h" + +/* mdp primary csc limit vector */ +uint32 mdp_plv[] = { 0x10, 0xeb, 0x10, 0xf0 }; + +/* Color Coefficient matrix for YUV -> RGB */ +struct mdp_ccs mdp_ccs_yuv2rgb = { + MDP_CCS_YUV2RGB, + { + 0x254, + 0x000, + 0x331, + 0x254, + 0xff38, + 0xfe61, + 0x254, + 0x409, + 0x000, + }, + { +#ifdef CONFIG_FB_MSM_MDP31 + 0x1f0, + 0x180, + 0x180 +#else + 0x10, + 0x80, + 0x80 +#endif + } +}; + +/* Color Coefficient matrix for RGB -> YUV */ +struct mdp_ccs mdp_ccs_rgb2yuv = { + MDP_CCS_RGB2YUV, + { + 0x83, + 0x102, + 0x32, + 0xffb5, + 0xff6c, + 0xe1, + 0xe1, + 0xff45, + 0xffdc, + }, +#ifdef CONFIG_FB_MSM_MDP31 + { + 0x10, + 0x80, + 0x80 + } +#endif +}; + +static void mdp_load_lut_param(void) +{ + outpdw(MDP_BASE + 0x40800, 0x0); + outpdw(MDP_BASE + 0x40804, 0x151515); + outpdw(MDP_BASE + 0x40808, 0x1d1d1d); + outpdw(MDP_BASE + 0x4080c, 0x232323); + outpdw(MDP_BASE + 0x40810, 0x272727); + outpdw(MDP_BASE + 0x40814, 0x2b2b2b); + outpdw(MDP_BASE + 0x40818, 0x2f2f2f); + outpdw(MDP_BASE + 0x4081c, 0x333333); + outpdw(MDP_BASE + 0x40820, 0x363636); + outpdw(MDP_BASE + 0x40824, 0x393939); + outpdw(MDP_BASE + 0x40828, 0x3b3b3b); + outpdw(MDP_BASE + 0x4082c, 0x3e3e3e); + outpdw(MDP_BASE + 0x40830, 0x404040); + outpdw(MDP_BASE + 0x40834, 0x434343); + outpdw(MDP_BASE + 0x40838, 0x454545); + outpdw(MDP_BASE + 0x4083c, 0x474747); + outpdw(MDP_BASE + 0x40840, 0x494949); + outpdw(MDP_BASE + 0x40844, 0x4b4b4b); + outpdw(MDP_BASE + 0x40848, 0x4d4d4d); + outpdw(MDP_BASE + 0x4084c, 0x4f4f4f); + outpdw(MDP_BASE + 0x40850, 0x515151); + outpdw(MDP_BASE + 0x40854, 0x535353); + outpdw(MDP_BASE + 0x40858, 0x555555); + outpdw(MDP_BASE + 0x4085c, 0x565656); + outpdw(MDP_BASE + 0x40860, 0x585858); + outpdw(MDP_BASE + 0x40864, 0x5a5a5a); + outpdw(MDP_BASE + 0x40868, 0x5b5b5b); + outpdw(MDP_BASE + 0x4086c, 0x5d5d5d); + outpdw(MDP_BASE + 0x40870, 0x5e5e5e); + outpdw(MDP_BASE + 0x40874, 0x606060); + outpdw(MDP_BASE + 0x40878, 0x616161); + outpdw(MDP_BASE + 0x4087c, 0x636363); + outpdw(MDP_BASE + 0x40880, 0x646464); + outpdw(MDP_BASE + 0x40884, 0x666666); + outpdw(MDP_BASE + 0x40888, 0x676767); + outpdw(MDP_BASE + 0x4088c, 0x686868); + outpdw(MDP_BASE + 0x40890, 0x6a6a6a); + outpdw(MDP_BASE + 0x40894, 0x6b6b6b); + outpdw(MDP_BASE + 0x40898, 0x6c6c6c); + outpdw(MDP_BASE + 0x4089c, 0x6e6e6e); + outpdw(MDP_BASE + 0x408a0, 0x6f6f6f); + outpdw(MDP_BASE + 0x408a4, 0x707070); + outpdw(MDP_BASE + 0x408a8, 0x717171); + outpdw(MDP_BASE + 0x408ac, 0x727272); + outpdw(MDP_BASE + 0x408b0, 0x747474); + outpdw(MDP_BASE + 0x408b4, 0x757575); + outpdw(MDP_BASE + 0x408b8, 0x767676); + outpdw(MDP_BASE + 0x408bc, 0x777777); + outpdw(MDP_BASE + 0x408c0, 0x787878); + outpdw(MDP_BASE + 0x408c4, 0x797979); + outpdw(MDP_BASE + 0x408c8, 0x7a7a7a); + outpdw(MDP_BASE + 0x408cc, 0x7c7c7c); + outpdw(MDP_BASE + 0x408d0, 0x7d7d7d); + outpdw(MDP_BASE + 0x408d4, 0x7e7e7e); + outpdw(MDP_BASE + 0x408d8, 0x7f7f7f); + outpdw(MDP_BASE + 0x408dc, 0x808080); + outpdw(MDP_BASE + 0x408e0, 0x818181); + outpdw(MDP_BASE + 0x408e4, 0x828282); + outpdw(MDP_BASE + 0x408e8, 0x838383); + outpdw(MDP_BASE + 0x408ec, 0x848484); + outpdw(MDP_BASE + 0x408f0, 0x858585); + outpdw(MDP_BASE + 0x408f4, 0x868686); + outpdw(MDP_BASE + 0x408f8, 0x878787); + outpdw(MDP_BASE + 0x408fc, 0x888888); + outpdw(MDP_BASE + 0x40900, 0x898989); + outpdw(MDP_BASE + 0x40904, 0x8a8a8a); + outpdw(MDP_BASE + 0x40908, 0x8b8b8b); + outpdw(MDP_BASE + 0x4090c, 0x8c8c8c); + outpdw(MDP_BASE + 0x40910, 0x8d8d8d); + outpdw(MDP_BASE + 0x40914, 0x8e8e8e); + outpdw(MDP_BASE + 0x40918, 0x8f8f8f); + outpdw(MDP_BASE + 0x4091c, 0x8f8f8f); + outpdw(MDP_BASE + 0x40920, 0x909090); + outpdw(MDP_BASE + 0x40924, 0x919191); + outpdw(MDP_BASE + 0x40928, 0x929292); + outpdw(MDP_BASE + 0x4092c, 0x939393); + outpdw(MDP_BASE + 0x40930, 0x949494); + outpdw(MDP_BASE + 0x40934, 0x959595); + outpdw(MDP_BASE + 0x40938, 0x969696); + outpdw(MDP_BASE + 0x4093c, 0x969696); + outpdw(MDP_BASE + 0x40940, 0x979797); + outpdw(MDP_BASE + 0x40944, 0x989898); + outpdw(MDP_BASE + 0x40948, 0x999999); + outpdw(MDP_BASE + 0x4094c, 0x9a9a9a); + outpdw(MDP_BASE + 0x40950, 0x9b9b9b); + outpdw(MDP_BASE + 0x40954, 0x9c9c9c); + outpdw(MDP_BASE + 0x40958, 0x9c9c9c); + outpdw(MDP_BASE + 0x4095c, 0x9d9d9d); + outpdw(MDP_BASE + 0x40960, 0x9e9e9e); + outpdw(MDP_BASE + 0x40964, 0x9f9f9f); + outpdw(MDP_BASE + 0x40968, 0xa0a0a0); + outpdw(MDP_BASE + 0x4096c, 0xa0a0a0); + outpdw(MDP_BASE + 0x40970, 0xa1a1a1); + outpdw(MDP_BASE + 0x40974, 0xa2a2a2); + outpdw(MDP_BASE + 0x40978, 0xa3a3a3); + outpdw(MDP_BASE + 0x4097c, 0xa4a4a4); + outpdw(MDP_BASE + 0x40980, 0xa4a4a4); + outpdw(MDP_BASE + 0x40984, 0xa5a5a5); + outpdw(MDP_BASE + 0x40988, 0xa6a6a6); + outpdw(MDP_BASE + 0x4098c, 0xa7a7a7); + outpdw(MDP_BASE + 0x40990, 0xa7a7a7); + outpdw(MDP_BASE + 0x40994, 0xa8a8a8); + outpdw(MDP_BASE + 0x40998, 0xa9a9a9); + outpdw(MDP_BASE + 0x4099c, 0xaaaaaa); + outpdw(MDP_BASE + 0x409a0, 0xaaaaaa); + outpdw(MDP_BASE + 0x409a4, 0xababab); + outpdw(MDP_BASE + 0x409a8, 0xacacac); + outpdw(MDP_BASE + 0x409ac, 0xadadad); + outpdw(MDP_BASE + 0x409b0, 0xadadad); + outpdw(MDP_BASE + 0x409b4, 0xaeaeae); + outpdw(MDP_BASE + 0x409b8, 0xafafaf); + outpdw(MDP_BASE + 0x409bc, 0xafafaf); + outpdw(MDP_BASE + 0x409c0, 0xb0b0b0); + outpdw(MDP_BASE + 0x409c4, 0xb1b1b1); + outpdw(MDP_BASE + 0x409c8, 0xb2b2b2); + outpdw(MDP_BASE + 0x409cc, 0xb2b2b2); + outpdw(MDP_BASE + 0x409d0, 0xb3b3b3); + outpdw(MDP_BASE + 0x409d4, 0xb4b4b4); + outpdw(MDP_BASE + 0x409d8, 0xb4b4b4); + outpdw(MDP_BASE + 0x409dc, 0xb5b5b5); + outpdw(MDP_BASE + 0x409e0, 0xb6b6b6); + outpdw(MDP_BASE + 0x409e4, 0xb6b6b6); + outpdw(MDP_BASE + 0x409e8, 0xb7b7b7); + outpdw(MDP_BASE + 0x409ec, 0xb8b8b8); + outpdw(MDP_BASE + 0x409f0, 0xb8b8b8); + outpdw(MDP_BASE + 0x409f4, 0xb9b9b9); + outpdw(MDP_BASE + 0x409f8, 0xbababa); + outpdw(MDP_BASE + 0x409fc, 0xbababa); + outpdw(MDP_BASE + 0x40a00, 0xbbbbbb); + outpdw(MDP_BASE + 0x40a04, 0xbcbcbc); + outpdw(MDP_BASE + 0x40a08, 0xbcbcbc); + outpdw(MDP_BASE + 0x40a0c, 0xbdbdbd); + outpdw(MDP_BASE + 0x40a10, 0xbebebe); + outpdw(MDP_BASE + 0x40a14, 0xbebebe); + outpdw(MDP_BASE + 0x40a18, 0xbfbfbf); + outpdw(MDP_BASE + 0x40a1c, 0xc0c0c0); + outpdw(MDP_BASE + 0x40a20, 0xc0c0c0); + outpdw(MDP_BASE + 0x40a24, 0xc1c1c1); + outpdw(MDP_BASE + 0x40a28, 0xc1c1c1); + outpdw(MDP_BASE + 0x40a2c, 0xc2c2c2); + outpdw(MDP_BASE + 0x40a30, 0xc3c3c3); + outpdw(MDP_BASE + 0x40a34, 0xc3c3c3); + outpdw(MDP_BASE + 0x40a38, 0xc4c4c4); + outpdw(MDP_BASE + 0x40a3c, 0xc5c5c5); + outpdw(MDP_BASE + 0x40a40, 0xc5c5c5); + outpdw(MDP_BASE + 0x40a44, 0xc6c6c6); + outpdw(MDP_BASE + 0x40a48, 0xc6c6c6); + outpdw(MDP_BASE + 0x40a4c, 0xc7c7c7); + outpdw(MDP_BASE + 0x40a50, 0xc8c8c8); + outpdw(MDP_BASE + 0x40a54, 0xc8c8c8); + outpdw(MDP_BASE + 0x40a58, 0xc9c9c9); + outpdw(MDP_BASE + 0x40a5c, 0xc9c9c9); + outpdw(MDP_BASE + 0x40a60, 0xcacaca); + outpdw(MDP_BASE + 0x40a64, 0xcbcbcb); + outpdw(MDP_BASE + 0x40a68, 0xcbcbcb); + outpdw(MDP_BASE + 0x40a6c, 0xcccccc); + outpdw(MDP_BASE + 0x40a70, 0xcccccc); + outpdw(MDP_BASE + 0x40a74, 0xcdcdcd); + outpdw(MDP_BASE + 0x40a78, 0xcecece); + outpdw(MDP_BASE + 0x40a7c, 0xcecece); + outpdw(MDP_BASE + 0x40a80, 0xcfcfcf); + outpdw(MDP_BASE + 0x40a84, 0xcfcfcf); + outpdw(MDP_BASE + 0x40a88, 0xd0d0d0); + outpdw(MDP_BASE + 0x40a8c, 0xd0d0d0); + outpdw(MDP_BASE + 0x40a90, 0xd1d1d1); + outpdw(MDP_BASE + 0x40a94, 0xd2d2d2); + outpdw(MDP_BASE + 0x40a98, 0xd2d2d2); + outpdw(MDP_BASE + 0x40a9c, 0xd3d3d3); + outpdw(MDP_BASE + 0x40aa0, 0xd3d3d3); + outpdw(MDP_BASE + 0x40aa4, 0xd4d4d4); + outpdw(MDP_BASE + 0x40aa8, 0xd4d4d4); + outpdw(MDP_BASE + 0x40aac, 0xd5d5d5); + outpdw(MDP_BASE + 0x40ab0, 0xd6d6d6); + outpdw(MDP_BASE + 0x40ab4, 0xd6d6d6); + outpdw(MDP_BASE + 0x40ab8, 0xd7d7d7); + outpdw(MDP_BASE + 0x40abc, 0xd7d7d7); + outpdw(MDP_BASE + 0x40ac0, 0xd8d8d8); + outpdw(MDP_BASE + 0x40ac4, 0xd8d8d8); + outpdw(MDP_BASE + 0x40ac8, 0xd9d9d9); + outpdw(MDP_BASE + 0x40acc, 0xd9d9d9); + outpdw(MDP_BASE + 0x40ad0, 0xdadada); + outpdw(MDP_BASE + 0x40ad4, 0xdbdbdb); + outpdw(MDP_BASE + 0x40ad8, 0xdbdbdb); + outpdw(MDP_BASE + 0x40adc, 0xdcdcdc); + outpdw(MDP_BASE + 0x40ae0, 0xdcdcdc); + outpdw(MDP_BASE + 0x40ae4, 0xdddddd); + outpdw(MDP_BASE + 0x40ae8, 0xdddddd); + outpdw(MDP_BASE + 0x40aec, 0xdedede); + outpdw(MDP_BASE + 0x40af0, 0xdedede); + outpdw(MDP_BASE + 0x40af4, 0xdfdfdf); + outpdw(MDP_BASE + 0x40af8, 0xdfdfdf); + outpdw(MDP_BASE + 0x40afc, 0xe0e0e0); + outpdw(MDP_BASE + 0x40b00, 0xe0e0e0); + outpdw(MDP_BASE + 0x40b04, 0xe1e1e1); + outpdw(MDP_BASE + 0x40b08, 0xe1e1e1); + outpdw(MDP_BASE + 0x40b0c, 0xe2e2e2); + outpdw(MDP_BASE + 0x40b10, 0xe3e3e3); + outpdw(MDP_BASE + 0x40b14, 0xe3e3e3); + outpdw(MDP_BASE + 0x40b18, 0xe4e4e4); + outpdw(MDP_BASE + 0x40b1c, 0xe4e4e4); + outpdw(MDP_BASE + 0x40b20, 0xe5e5e5); + outpdw(MDP_BASE + 0x40b24, 0xe5e5e5); + outpdw(MDP_BASE + 0x40b28, 0xe6e6e6); + outpdw(MDP_BASE + 0x40b2c, 0xe6e6e6); + outpdw(MDP_BASE + 0x40b30, 0xe7e7e7); + outpdw(MDP_BASE + 0x40b34, 0xe7e7e7); + outpdw(MDP_BASE + 0x40b38, 0xe8e8e8); + outpdw(MDP_BASE + 0x40b3c, 0xe8e8e8); + outpdw(MDP_BASE + 0x40b40, 0xe9e9e9); + outpdw(MDP_BASE + 0x40b44, 0xe9e9e9); + outpdw(MDP_BASE + 0x40b48, 0xeaeaea); + outpdw(MDP_BASE + 0x40b4c, 0xeaeaea); + outpdw(MDP_BASE + 0x40b50, 0xebebeb); + outpdw(MDP_BASE + 0x40b54, 0xebebeb); + outpdw(MDP_BASE + 0x40b58, 0xececec); + outpdw(MDP_BASE + 0x40b5c, 0xececec); + outpdw(MDP_BASE + 0x40b60, 0xededed); + outpdw(MDP_BASE + 0x40b64, 0xededed); + outpdw(MDP_BASE + 0x40b68, 0xeeeeee); + outpdw(MDP_BASE + 0x40b6c, 0xeeeeee); + outpdw(MDP_BASE + 0x40b70, 0xefefef); + outpdw(MDP_BASE + 0x40b74, 0xefefef); + outpdw(MDP_BASE + 0x40b78, 0xf0f0f0); + outpdw(MDP_BASE + 0x40b7c, 0xf0f0f0); + outpdw(MDP_BASE + 0x40b80, 0xf1f1f1); + outpdw(MDP_BASE + 0x40b84, 0xf1f1f1); + outpdw(MDP_BASE + 0x40b88, 0xf2f2f2); + outpdw(MDP_BASE + 0x40b8c, 0xf2f2f2); + outpdw(MDP_BASE + 0x40b90, 0xf2f2f2); + outpdw(MDP_BASE + 0x40b94, 0xf3f3f3); + outpdw(MDP_BASE + 0x40b98, 0xf3f3f3); + outpdw(MDP_BASE + 0x40b9c, 0xf4f4f4); + outpdw(MDP_BASE + 0x40ba0, 0xf4f4f4); + outpdw(MDP_BASE + 0x40ba4, 0xf5f5f5); + outpdw(MDP_BASE + 0x40ba8, 0xf5f5f5); + outpdw(MDP_BASE + 0x40bac, 0xf6f6f6); + outpdw(MDP_BASE + 0x40bb0, 0xf6f6f6); + outpdw(MDP_BASE + 0x40bb4, 0xf7f7f7); + outpdw(MDP_BASE + 0x40bb8, 0xf7f7f7); + outpdw(MDP_BASE + 0x40bbc, 0xf8f8f8); + outpdw(MDP_BASE + 0x40bc0, 0xf8f8f8); + outpdw(MDP_BASE + 0x40bc4, 0xf9f9f9); + outpdw(MDP_BASE + 0x40bc8, 0xf9f9f9); + outpdw(MDP_BASE + 0x40bcc, 0xfafafa); + outpdw(MDP_BASE + 0x40bd0, 0xfafafa); + outpdw(MDP_BASE + 0x40bd4, 0xfafafa); + outpdw(MDP_BASE + 0x40bd8, 0xfbfbfb); + outpdw(MDP_BASE + 0x40bdc, 0xfbfbfb); + outpdw(MDP_BASE + 0x40be0, 0xfcfcfc); + outpdw(MDP_BASE + 0x40be4, 0xfcfcfc); + outpdw(MDP_BASE + 0x40be8, 0xfdfdfd); + outpdw(MDP_BASE + 0x40bec, 0xfdfdfd); + outpdw(MDP_BASE + 0x40bf0, 0xfefefe); + outpdw(MDP_BASE + 0x40bf4, 0xfefefe); + outpdw(MDP_BASE + 0x40bf8, 0xffffff); + outpdw(MDP_BASE + 0x40bfc, 0xffffff); + outpdw(MDP_BASE + 0x40c00, 0x0); + outpdw(MDP_BASE + 0x40c04, 0x0); + outpdw(MDP_BASE + 0x40c08, 0x0); + outpdw(MDP_BASE + 0x40c0c, 0x0); + outpdw(MDP_BASE + 0x40c10, 0x0); + outpdw(MDP_BASE + 0x40c14, 0x0); + outpdw(MDP_BASE + 0x40c18, 0x0); + outpdw(MDP_BASE + 0x40c1c, 0x0); + outpdw(MDP_BASE + 0x40c20, 0x0); + outpdw(MDP_BASE + 0x40c24, 0x0); + outpdw(MDP_BASE + 0x40c28, 0x0); + outpdw(MDP_BASE + 0x40c2c, 0x0); + outpdw(MDP_BASE + 0x40c30, 0x0); + outpdw(MDP_BASE + 0x40c34, 0x0); + outpdw(MDP_BASE + 0x40c38, 0x0); + outpdw(MDP_BASE + 0x40c3c, 0x0); + outpdw(MDP_BASE + 0x40c40, 0x10101); + outpdw(MDP_BASE + 0x40c44, 0x10101); + outpdw(MDP_BASE + 0x40c48, 0x10101); + outpdw(MDP_BASE + 0x40c4c, 0x10101); + outpdw(MDP_BASE + 0x40c50, 0x10101); + outpdw(MDP_BASE + 0x40c54, 0x10101); + outpdw(MDP_BASE + 0x40c58, 0x10101); + outpdw(MDP_BASE + 0x40c5c, 0x10101); + outpdw(MDP_BASE + 0x40c60, 0x10101); + outpdw(MDP_BASE + 0x40c64, 0x10101); + outpdw(MDP_BASE + 0x40c68, 0x20202); + outpdw(MDP_BASE + 0x40c6c, 0x20202); + outpdw(MDP_BASE + 0x40c70, 0x20202); + outpdw(MDP_BASE + 0x40c74, 0x20202); + outpdw(MDP_BASE + 0x40c78, 0x20202); + outpdw(MDP_BASE + 0x40c7c, 0x20202); + outpdw(MDP_BASE + 0x40c80, 0x30303); + outpdw(MDP_BASE + 0x40c84, 0x30303); + outpdw(MDP_BASE + 0x40c88, 0x30303); + outpdw(MDP_BASE + 0x40c8c, 0x30303); + outpdw(MDP_BASE + 0x40c90, 0x30303); + outpdw(MDP_BASE + 0x40c94, 0x40404); + outpdw(MDP_BASE + 0x40c98, 0x40404); + outpdw(MDP_BASE + 0x40c9c, 0x40404); + outpdw(MDP_BASE + 0x40ca0, 0x40404); + outpdw(MDP_BASE + 0x40ca4, 0x40404); + outpdw(MDP_BASE + 0x40ca8, 0x50505); + outpdw(MDP_BASE + 0x40cac, 0x50505); + outpdw(MDP_BASE + 0x40cb0, 0x50505); + outpdw(MDP_BASE + 0x40cb4, 0x50505); + outpdw(MDP_BASE + 0x40cb8, 0x60606); + outpdw(MDP_BASE + 0x40cbc, 0x60606); + outpdw(MDP_BASE + 0x40cc0, 0x60606); + outpdw(MDP_BASE + 0x40cc4, 0x70707); + outpdw(MDP_BASE + 0x40cc8, 0x70707); + outpdw(MDP_BASE + 0x40ccc, 0x70707); + outpdw(MDP_BASE + 0x40cd0, 0x70707); + outpdw(MDP_BASE + 0x40cd4, 0x80808); + outpdw(MDP_BASE + 0x40cd8, 0x80808); + outpdw(MDP_BASE + 0x40cdc, 0x80808); + outpdw(MDP_BASE + 0x40ce0, 0x90909); + outpdw(MDP_BASE + 0x40ce4, 0x90909); + outpdw(MDP_BASE + 0x40ce8, 0xa0a0a); + outpdw(MDP_BASE + 0x40cec, 0xa0a0a); + outpdw(MDP_BASE + 0x40cf0, 0xa0a0a); + outpdw(MDP_BASE + 0x40cf4, 0xb0b0b); + outpdw(MDP_BASE + 0x40cf8, 0xb0b0b); + outpdw(MDP_BASE + 0x40cfc, 0xb0b0b); + outpdw(MDP_BASE + 0x40d00, 0xc0c0c); + outpdw(MDP_BASE + 0x40d04, 0xc0c0c); + outpdw(MDP_BASE + 0x40d08, 0xd0d0d); + outpdw(MDP_BASE + 0x40d0c, 0xd0d0d); + outpdw(MDP_BASE + 0x40d10, 0xe0e0e); + outpdw(MDP_BASE + 0x40d14, 0xe0e0e); + outpdw(MDP_BASE + 0x40d18, 0xe0e0e); + outpdw(MDP_BASE + 0x40d1c, 0xf0f0f); + outpdw(MDP_BASE + 0x40d20, 0xf0f0f); + outpdw(MDP_BASE + 0x40d24, 0x101010); + outpdw(MDP_BASE + 0x40d28, 0x101010); + outpdw(MDP_BASE + 0x40d2c, 0x111111); + outpdw(MDP_BASE + 0x40d30, 0x111111); + outpdw(MDP_BASE + 0x40d34, 0x121212); + outpdw(MDP_BASE + 0x40d38, 0x121212); + outpdw(MDP_BASE + 0x40d3c, 0x131313); + outpdw(MDP_BASE + 0x40d40, 0x131313); + outpdw(MDP_BASE + 0x40d44, 0x141414); + outpdw(MDP_BASE + 0x40d48, 0x151515); + outpdw(MDP_BASE + 0x40d4c, 0x151515); + outpdw(MDP_BASE + 0x40d50, 0x161616); + outpdw(MDP_BASE + 0x40d54, 0x161616); + outpdw(MDP_BASE + 0x40d58, 0x171717); + outpdw(MDP_BASE + 0x40d5c, 0x171717); + outpdw(MDP_BASE + 0x40d60, 0x181818); + outpdw(MDP_BASE + 0x40d64, 0x191919); + outpdw(MDP_BASE + 0x40d68, 0x191919); + outpdw(MDP_BASE + 0x40d6c, 0x1a1a1a); + outpdw(MDP_BASE + 0x40d70, 0x1b1b1b); + outpdw(MDP_BASE + 0x40d74, 0x1b1b1b); + outpdw(MDP_BASE + 0x40d78, 0x1c1c1c); + outpdw(MDP_BASE + 0x40d7c, 0x1c1c1c); + outpdw(MDP_BASE + 0x40d80, 0x1d1d1d); + outpdw(MDP_BASE + 0x40d84, 0x1e1e1e); + outpdw(MDP_BASE + 0x40d88, 0x1f1f1f); + outpdw(MDP_BASE + 0x40d8c, 0x1f1f1f); + outpdw(MDP_BASE + 0x40d90, 0x202020); + outpdw(MDP_BASE + 0x40d94, 0x212121); + outpdw(MDP_BASE + 0x40d98, 0x212121); + outpdw(MDP_BASE + 0x40d9c, 0x222222); + outpdw(MDP_BASE + 0x40da0, 0x232323); + outpdw(MDP_BASE + 0x40da4, 0x242424); + outpdw(MDP_BASE + 0x40da8, 0x242424); + outpdw(MDP_BASE + 0x40dac, 0x252525); + outpdw(MDP_BASE + 0x40db0, 0x262626); + outpdw(MDP_BASE + 0x40db4, 0x272727); + outpdw(MDP_BASE + 0x40db8, 0x272727); + outpdw(MDP_BASE + 0x40dbc, 0x282828); + outpdw(MDP_BASE + 0x40dc0, 0x292929); + outpdw(MDP_BASE + 0x40dc4, 0x2a2a2a); + outpdw(MDP_BASE + 0x40dc8, 0x2b2b2b); + outpdw(MDP_BASE + 0x40dcc, 0x2c2c2c); + outpdw(MDP_BASE + 0x40dd0, 0x2c2c2c); + outpdw(MDP_BASE + 0x40dd4, 0x2d2d2d); + outpdw(MDP_BASE + 0x40dd8, 0x2e2e2e); + outpdw(MDP_BASE + 0x40ddc, 0x2f2f2f); + outpdw(MDP_BASE + 0x40de0, 0x303030); + outpdw(MDP_BASE + 0x40de4, 0x313131); + outpdw(MDP_BASE + 0x40de8, 0x323232); + outpdw(MDP_BASE + 0x40dec, 0x333333); + outpdw(MDP_BASE + 0x40df0, 0x333333); + outpdw(MDP_BASE + 0x40df4, 0x343434); + outpdw(MDP_BASE + 0x40df8, 0x353535); + outpdw(MDP_BASE + 0x40dfc, 0x363636); + outpdw(MDP_BASE + 0x40e00, 0x373737); + outpdw(MDP_BASE + 0x40e04, 0x383838); + outpdw(MDP_BASE + 0x40e08, 0x393939); + outpdw(MDP_BASE + 0x40e0c, 0x3a3a3a); + outpdw(MDP_BASE + 0x40e10, 0x3b3b3b); + outpdw(MDP_BASE + 0x40e14, 0x3c3c3c); + outpdw(MDP_BASE + 0x40e18, 0x3d3d3d); + outpdw(MDP_BASE + 0x40e1c, 0x3e3e3e); + outpdw(MDP_BASE + 0x40e20, 0x3f3f3f); + outpdw(MDP_BASE + 0x40e24, 0x404040); + outpdw(MDP_BASE + 0x40e28, 0x414141); + outpdw(MDP_BASE + 0x40e2c, 0x424242); + outpdw(MDP_BASE + 0x40e30, 0x434343); + outpdw(MDP_BASE + 0x40e34, 0x444444); + outpdw(MDP_BASE + 0x40e38, 0x464646); + outpdw(MDP_BASE + 0x40e3c, 0x474747); + outpdw(MDP_BASE + 0x40e40, 0x484848); + outpdw(MDP_BASE + 0x40e44, 0x494949); + outpdw(MDP_BASE + 0x40e48, 0x4a4a4a); + outpdw(MDP_BASE + 0x40e4c, 0x4b4b4b); + outpdw(MDP_BASE + 0x40e50, 0x4c4c4c); + outpdw(MDP_BASE + 0x40e54, 0x4d4d4d); + outpdw(MDP_BASE + 0x40e58, 0x4f4f4f); + outpdw(MDP_BASE + 0x40e5c, 0x505050); + outpdw(MDP_BASE + 0x40e60, 0x515151); + outpdw(MDP_BASE + 0x40e64, 0x525252); + outpdw(MDP_BASE + 0x40e68, 0x535353); + outpdw(MDP_BASE + 0x40e6c, 0x545454); + outpdw(MDP_BASE + 0x40e70, 0x565656); + outpdw(MDP_BASE + 0x40e74, 0x575757); + outpdw(MDP_BASE + 0x40e78, 0x585858); + outpdw(MDP_BASE + 0x40e7c, 0x595959); + outpdw(MDP_BASE + 0x40e80, 0x5b5b5b); + outpdw(MDP_BASE + 0x40e84, 0x5c5c5c); + outpdw(MDP_BASE + 0x40e88, 0x5d5d5d); + outpdw(MDP_BASE + 0x40e8c, 0x5e5e5e); + outpdw(MDP_BASE + 0x40e90, 0x606060); + outpdw(MDP_BASE + 0x40e94, 0x616161); + outpdw(MDP_BASE + 0x40e98, 0x626262); + outpdw(MDP_BASE + 0x40e9c, 0x646464); + outpdw(MDP_BASE + 0x40ea0, 0x656565); + outpdw(MDP_BASE + 0x40ea4, 0x666666); + outpdw(MDP_BASE + 0x40ea8, 0x686868); + outpdw(MDP_BASE + 0x40eac, 0x696969); + outpdw(MDP_BASE + 0x40eb0, 0x6a6a6a); + outpdw(MDP_BASE + 0x40eb4, 0x6c6c6c); + outpdw(MDP_BASE + 0x40eb8, 0x6d6d6d); + outpdw(MDP_BASE + 0x40ebc, 0x6f6f6f); + outpdw(MDP_BASE + 0x40ec0, 0x707070); + outpdw(MDP_BASE + 0x40ec4, 0x717171); + outpdw(MDP_BASE + 0x40ec8, 0x737373); + outpdw(MDP_BASE + 0x40ecc, 0x747474); + outpdw(MDP_BASE + 0x40ed0, 0x767676); + outpdw(MDP_BASE + 0x40ed4, 0x777777); + outpdw(MDP_BASE + 0x40ed8, 0x797979); + outpdw(MDP_BASE + 0x40edc, 0x7a7a7a); + outpdw(MDP_BASE + 0x40ee0, 0x7c7c7c); + outpdw(MDP_BASE + 0x40ee4, 0x7d7d7d); + outpdw(MDP_BASE + 0x40ee8, 0x7f7f7f); + outpdw(MDP_BASE + 0x40eec, 0x808080); + outpdw(MDP_BASE + 0x40ef0, 0x828282); + outpdw(MDP_BASE + 0x40ef4, 0x838383); + outpdw(MDP_BASE + 0x40ef8, 0x858585); + outpdw(MDP_BASE + 0x40efc, 0x868686); + outpdw(MDP_BASE + 0x40f00, 0x888888); + outpdw(MDP_BASE + 0x40f04, 0x898989); + outpdw(MDP_BASE + 0x40f08, 0x8b8b8b); + outpdw(MDP_BASE + 0x40f0c, 0x8d8d8d); + outpdw(MDP_BASE + 0x40f10, 0x8e8e8e); + outpdw(MDP_BASE + 0x40f14, 0x909090); + outpdw(MDP_BASE + 0x40f18, 0x919191); + outpdw(MDP_BASE + 0x40f1c, 0x939393); + outpdw(MDP_BASE + 0x40f20, 0x959595); + outpdw(MDP_BASE + 0x40f24, 0x969696); + outpdw(MDP_BASE + 0x40f28, 0x989898); + outpdw(MDP_BASE + 0x40f2c, 0x9a9a9a); + outpdw(MDP_BASE + 0x40f30, 0x9b9b9b); + outpdw(MDP_BASE + 0x40f34, 0x9d9d9d); + outpdw(MDP_BASE + 0x40f38, 0x9f9f9f); + outpdw(MDP_BASE + 0x40f3c, 0xa1a1a1); + outpdw(MDP_BASE + 0x40f40, 0xa2a2a2); + outpdw(MDP_BASE + 0x40f44, 0xa4a4a4); + outpdw(MDP_BASE + 0x40f48, 0xa6a6a6); + outpdw(MDP_BASE + 0x40f4c, 0xa7a7a7); + outpdw(MDP_BASE + 0x40f50, 0xa9a9a9); + outpdw(MDP_BASE + 0x40f54, 0xababab); + outpdw(MDP_BASE + 0x40f58, 0xadadad); + outpdw(MDP_BASE + 0x40f5c, 0xafafaf); + outpdw(MDP_BASE + 0x40f60, 0xb0b0b0); + outpdw(MDP_BASE + 0x40f64, 0xb2b2b2); + outpdw(MDP_BASE + 0x40f68, 0xb4b4b4); + outpdw(MDP_BASE + 0x40f6c, 0xb6b6b6); + outpdw(MDP_BASE + 0x40f70, 0xb8b8b8); + outpdw(MDP_BASE + 0x40f74, 0xbababa); + outpdw(MDP_BASE + 0x40f78, 0xbbbbbb); + outpdw(MDP_BASE + 0x40f7c, 0xbdbdbd); + outpdw(MDP_BASE + 0x40f80, 0xbfbfbf); + outpdw(MDP_BASE + 0x40f84, 0xc1c1c1); + outpdw(MDP_BASE + 0x40f88, 0xc3c3c3); + outpdw(MDP_BASE + 0x40f8c, 0xc5c5c5); + outpdw(MDP_BASE + 0x40f90, 0xc7c7c7); + outpdw(MDP_BASE + 0x40f94, 0xc9c9c9); + outpdw(MDP_BASE + 0x40f98, 0xcbcbcb); + outpdw(MDP_BASE + 0x40f9c, 0xcdcdcd); + outpdw(MDP_BASE + 0x40fa0, 0xcfcfcf); + outpdw(MDP_BASE + 0x40fa4, 0xd1d1d1); + outpdw(MDP_BASE + 0x40fa8, 0xd3d3d3); + outpdw(MDP_BASE + 0x40fac, 0xd5d5d5); + outpdw(MDP_BASE + 0x40fb0, 0xd7d7d7); + outpdw(MDP_BASE + 0x40fb4, 0xd9d9d9); + outpdw(MDP_BASE + 0x40fb8, 0xdbdbdb); + outpdw(MDP_BASE + 0x40fbc, 0xdddddd); + outpdw(MDP_BASE + 0x40fc0, 0xdfdfdf); + outpdw(MDP_BASE + 0x40fc4, 0xe1e1e1); + outpdw(MDP_BASE + 0x40fc8, 0xe3e3e3); + outpdw(MDP_BASE + 0x40fcc, 0xe5e5e5); + outpdw(MDP_BASE + 0x40fd0, 0xe7e7e7); + outpdw(MDP_BASE + 0x40fd4, 0xe9e9e9); + outpdw(MDP_BASE + 0x40fd8, 0xebebeb); + outpdw(MDP_BASE + 0x40fdc, 0xeeeeee); + outpdw(MDP_BASE + 0x40fe0, 0xf0f0f0); + outpdw(MDP_BASE + 0x40fe4, 0xf2f2f2); + outpdw(MDP_BASE + 0x40fe8, 0xf4f4f4); + outpdw(MDP_BASE + 0x40fec, 0xf6f6f6); + outpdw(MDP_BASE + 0x40ff0, 0xf8f8f8); + outpdw(MDP_BASE + 0x40ff4, 0xfbfbfb); + outpdw(MDP_BASE + 0x40ff8, 0xfdfdfd); + outpdw(MDP_BASE + 0x40ffc, 0xffffff); +} + +#define IRQ_EN_1__MDP_IRQ___M 0x00000800 + +void mdp_hw_init(void) +{ + int i; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* debug interface write access */ + outpdw(MDP_BASE + 0x60, 1); + + outp32(MDP_INTR_ENABLE, MDP_ANY_INTR_MASK); + outp32(MDP_EBI2_PORTMAP_MODE, 0x3); + outpdw(MDP_CMD_DEBUG_ACCESS_BASE + 0x01f8, 0x0); + outpdw(MDP_CMD_DEBUG_ACCESS_BASE + 0x01fc, 0x0); + outpdw(MDP_BASE + 0x60, 0x1); + mdp_load_lut_param(); + + /* + * clear up unused fg/main registers + */ + /* comp.plane 2&3 ystride */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0120, 0x0); + /* unpacked pattern */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x012c, 0x0); + /* unpacked pattern */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0130, 0x0); + /* unpacked pattern */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0134, 0x0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0158, 0x0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x15c, 0x0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0160, 0x0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0170, 0x0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0174, 0x0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x017c, 0x0); + + /* comp.plane 2 */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0114, 0x0); + /* comp.plane 3 */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0118, 0x0); + + /* clear up unused bg registers */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01c8, 0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01d0, 0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01dc, 0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01e0, 0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01e4, 0); + +#ifndef CONFIG_FB_MSM_MDP22 + MDP_OUTP(MDP_BASE + 0xE0000, 0); + MDP_OUTP(MDP_BASE + 0x100, 0xffffffff); + MDP_OUTP(MDP_BASE + 0x90070, 0); + MDP_OUTP(MDP_BASE + 0x94010, 1); + MDP_OUTP(MDP_BASE + 0x9401c, 2); +#endif + + /* + * limit vector + * pre gets applied before color matrix conversion + * post is after ccs + */ + writel(mdp_plv[0], MDP_CSC_PRE_LV1n(0)); + writel(mdp_plv[1], MDP_CSC_PRE_LV1n(1)); + writel(mdp_plv[2], MDP_CSC_PRE_LV1n(2)); + writel(mdp_plv[3], MDP_CSC_PRE_LV1n(3)); + +#ifdef CONFIG_FB_MSM_MDP31 + writel(mdp_plv[2], MDP_CSC_PRE_LV1n(4)); + writel(mdp_plv[3], MDP_CSC_PRE_LV1n(5)); + + writel(0, MDP_CSC_POST_LV1n(0)); + writel(0xff, MDP_CSC_POST_LV1n(1)); + writel(0, MDP_CSC_POST_LV1n(2)); + writel(0xff, MDP_CSC_POST_LV1n(3)); + writel(0, MDP_CSC_POST_LV1n(4)); + writel(0xff, MDP_CSC_POST_LV1n(5)); + + writel(0, MDP_CSC_PRE_LV2n(0)); + writel(0xff, MDP_CSC_PRE_LV2n(1)); + writel(0, MDP_CSC_PRE_LV2n(2)); + writel(0xff, MDP_CSC_PRE_LV2n(3)); + writel(0, MDP_CSC_PRE_LV2n(4)); + writel(0xff, MDP_CSC_PRE_LV2n(5)); + + writel(mdp_plv[0], MDP_CSC_POST_LV2n(0)); + writel(mdp_plv[1], MDP_CSC_POST_LV2n(1)); + writel(mdp_plv[2], MDP_CSC_POST_LV2n(2)); + writel(mdp_plv[3], MDP_CSC_POST_LV2n(3)); + writel(mdp_plv[2], MDP_CSC_POST_LV2n(4)); + writel(mdp_plv[3], MDP_CSC_POST_LV2n(5)); +#endif + + /* primary forward matrix */ + for (i = 0; i < MDP_CCS_SIZE; i++) + writel(mdp_ccs_rgb2yuv.ccs[i], MDP_CSC_PFMVn(i)); + +#ifdef CONFIG_FB_MSM_MDP31 + for (i = 0; i < MDP_BV_SIZE; i++) + writel(mdp_ccs_rgb2yuv.bv[i], MDP_CSC_POST_BV2n(i)); + + writel(0, MDP_CSC_PRE_BV2n(0)); + writel(0, MDP_CSC_PRE_BV2n(1)); + writel(0, MDP_CSC_PRE_BV2n(2)); +#endif + /* primary reverse matrix */ + for (i = 0; i < MDP_CCS_SIZE; i++) + writel(mdp_ccs_yuv2rgb.ccs[i], MDP_CSC_PRMVn(i)); + + for (i = 0; i < MDP_BV_SIZE; i++) + writel(mdp_ccs_yuv2rgb.bv[i], MDP_CSC_PRE_BV1n(i)); + +#ifdef CONFIG_FB_MSM_MDP31 + writel(0, MDP_CSC_POST_BV1n(0)); + writel(0, MDP_CSC_POST_BV1n(1)); + writel(0, MDP_CSC_POST_BV1n(2)); + + outpdw(MDP_BASE + 0x30010, 0x03e0); + outpdw(MDP_BASE + 0x30014, 0x0360); + outpdw(MDP_BASE + 0x30018, 0x0120); + outpdw(MDP_BASE + 0x3001c, 0x0140); +#endif + mdp_init_scale_table(); + +#ifndef CONFIG_FB_MSM_MDP31 + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0104, + ((16 << 6) << 16) | (16) << 6); +#endif + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); +} diff --git a/drivers/video/msm/mdp_lcdc.c b/drivers/video/msm/mdp_lcdc.c new file mode 100644 index 00000000000..be8d39de962 --- /dev/null +++ b/drivers/video/msm/mdp_lcdc.c @@ -0,0 +1,432 @@ +/* drivers/video/msm/mdp_lcdc.c + * + * Copyright (c) 2009 Google Inc. + * Copyright (c) 2009 Code Aurora Forum. 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. + * + * Author: Dima Zavin + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "mdp_hw.h" + +struct mdp_lcdc_info { + struct mdp_info *mdp; + struct clk *mdp_clk; + struct clk *pclk; + struct clk *pad_pclk; + struct msm_panel_data fb_panel_data; + struct platform_device fb_pdev; + struct msm_lcdc_platform_data *pdata; + uint32_t fb_start; + + struct msmfb_callback frame_start_cb; + wait_queue_head_t vsync_waitq; + int got_vsync; + + struct { + uint32_t clk_rate; + uint32_t hsync_ctl; + uint32_t vsync_period; + uint32_t vsync_pulse_width; + uint32_t display_hctl; + uint32_t display_vstart; + uint32_t display_vend; + uint32_t hsync_skew; + uint32_t polarity; + } parms; +}; + +static struct mdp_device *mdp_dev; + +#define panel_to_lcdc(p) container_of((p), struct mdp_lcdc_info, fb_panel_data) + +static int lcdc_unblank(struct msm_panel_data *fb_panel) +{ + struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); + struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops; + + pr_info("%s: ()\n", __func__); + panel_ops->unblank(panel_ops); + + return 0; +} + +static int lcdc_blank(struct msm_panel_data *fb_panel) +{ + struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); + struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops; + + pr_info("%s: ()\n", __func__); + panel_ops->blank(panel_ops); + + return 0; +} + +static int lcdc_suspend(struct msm_panel_data *fb_panel) +{ + struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); + + pr_info("%s: suspending\n", __func__); + + mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN); + clk_disable(lcdc->pad_pclk); + clk_disable(lcdc->pclk); + clk_disable(lcdc->mdp_clk); + + return 0; +} + +static int lcdc_resume(struct msm_panel_data *fb_panel) +{ + struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); + + pr_info("%s: resuming\n", __func__); + + clk_enable(lcdc->mdp_clk); + clk_enable(lcdc->pclk); + clk_enable(lcdc->pad_pclk); + mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN); + + return 0; +} + +static int lcdc_hw_init(struct mdp_lcdc_info *lcdc) +{ + struct msm_panel_data *fb_panel = &lcdc->fb_panel_data; + uint32_t dma_cfg; + + clk_enable(lcdc->mdp_clk); + clk_enable(lcdc->pclk); + clk_enable(lcdc->pad_pclk); + + clk_set_rate(lcdc->pclk, lcdc->parms.clk_rate); + clk_set_rate(lcdc->pad_pclk, lcdc->parms.clk_rate); + + /* write the lcdc params */ + mdp_writel(lcdc->mdp, lcdc->parms.hsync_ctl, MDP_LCDC_HSYNC_CTL); + mdp_writel(lcdc->mdp, lcdc->parms.vsync_period, MDP_LCDC_VSYNC_PERIOD); + mdp_writel(lcdc->mdp, lcdc->parms.vsync_pulse_width, + MDP_LCDC_VSYNC_PULSE_WIDTH); + mdp_writel(lcdc->mdp, lcdc->parms.display_hctl, MDP_LCDC_DISPLAY_HCTL); + mdp_writel(lcdc->mdp, lcdc->parms.display_vstart, + MDP_LCDC_DISPLAY_V_START); + mdp_writel(lcdc->mdp, lcdc->parms.display_vend, MDP_LCDC_DISPLAY_V_END); + mdp_writel(lcdc->mdp, lcdc->parms.hsync_skew, MDP_LCDC_HSYNC_SKEW); + + mdp_writel(lcdc->mdp, 0, MDP_LCDC_BORDER_CLR); + mdp_writel(lcdc->mdp, 0xff, MDP_LCDC_UNDERFLOW_CTL); + mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_HCTL); + mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_V_START); + mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_V_END); + mdp_writel(lcdc->mdp, lcdc->parms.polarity, MDP_LCDC_CTL_POLARITY); + + /* config the dma_p block that drives the lcdc data */ + mdp_writel(lcdc->mdp, lcdc->fb_start, MDP_DMA_P_IBUF_ADDR); + mdp_writel(lcdc->mdp, (((fb_panel->fb_data->yres & 0x7ff) << 16) | + (fb_panel->fb_data->xres & 0x7ff)), + MDP_DMA_P_SIZE); + + mdp_writel(lcdc->mdp, 0, MDP_DMA_P_OUT_XY); + + dma_cfg = mdp_readl(lcdc->mdp, MDP_DMA_P_CONFIG); + dma_cfg |= (DMA_PACK_ALIGN_LSB | + DMA_PACK_PATTERN_RGB | + DMA_DITHER_EN); + dma_cfg |= DMA_OUT_SEL_LCDC; + dma_cfg &= ~DMA_DST_BITS_MASK; + + if (fb_panel->fb_data->output_format == MSM_MDP_OUT_IF_FMT_RGB666) + dma_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; + else + dma_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; + + mdp_writel(lcdc->mdp, dma_cfg, MDP_DMA_P_CONFIG); + + /* enable the lcdc timing generation */ + mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN); + + return 0; +} + +static void lcdc_wait_vsync(struct msm_panel_data *panel) +{ + struct mdp_lcdc_info *lcdc = panel_to_lcdc(panel); + int ret; + + ret = wait_event_timeout(lcdc->vsync_waitq, lcdc->got_vsync, HZ / 2); + if (!ret && !lcdc->got_vsync) + pr_err("%s: timeout waiting for VSYNC\n", __func__); + lcdc->got_vsync = 0; +} + +static void lcdc_request_vsync(struct msm_panel_data *fb_panel, + struct msmfb_callback *vsync_cb) +{ + struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); + + /* the vsync callback will start the dma */ + vsync_cb->func(vsync_cb); + lcdc->got_vsync = 0; + mdp_out_if_req_irq(mdp_dev, MSM_LCDC_INTERFACE, MDP_LCDC_FRAME_START, + &lcdc->frame_start_cb); + lcdc_wait_vsync(fb_panel); +} + +static void lcdc_clear_vsync(struct msm_panel_data *fb_panel) +{ + struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); + lcdc->got_vsync = 0; + mdp_out_if_req_irq(mdp_dev, MSM_LCDC_INTERFACE, 0, NULL); +} + +/* called in irq context with mdp lock held, when mdp gets the + * MDP_LCDC_FRAME_START interrupt */ +static void lcdc_frame_start(struct msmfb_callback *cb) +{ + struct mdp_lcdc_info *lcdc; + + lcdc = container_of(cb, struct mdp_lcdc_info, frame_start_cb); + + lcdc->got_vsync = 1; + wake_up(&lcdc->vsync_waitq); +} + +static void lcdc_dma_start(void *priv, uint32_t addr, uint32_t stride, + uint32_t width, uint32_t height, uint32_t x, + uint32_t y) +{ + struct mdp_lcdc_info *lcdc = priv; + + struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + if (mdp->dma_config_dirty) + { + mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN); + mdelay(20); + mdp_dev->configure_dma(mdp_dev); + mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN); + } + mdp_writel(lcdc->mdp, stride, MDP_DMA_P_IBUF_Y_STRIDE); + mdp_writel(lcdc->mdp, addr, MDP_DMA_P_IBUF_ADDR); +} + +static void precompute_timing_parms(struct mdp_lcdc_info *lcdc) +{ + struct msm_lcdc_timing *timing = lcdc->pdata->timing; + struct msm_fb_data *fb_data = lcdc->pdata->fb_data; + unsigned int hsync_period; + unsigned int hsync_start_x; + unsigned int hsync_end_x; + unsigned int vsync_period; + unsigned int display_vstart; + unsigned int display_vend; + + hsync_period = (timing->hsync_back_porch + + fb_data->xres + timing->hsync_front_porch); + hsync_start_x = timing->hsync_back_porch; + hsync_end_x = hsync_start_x + fb_data->xres - 1; + + vsync_period = (timing->vsync_back_porch + + fb_data->yres + timing->vsync_front_porch); + vsync_period *= hsync_period; + + display_vstart = timing->vsync_back_porch; + display_vstart *= hsync_period; + display_vstart += timing->hsync_skew; + + display_vend = (timing->vsync_back_porch + fb_data->yres) * + hsync_period; + display_vend += timing->hsync_skew - 1; + + /* register values we pre-compute at init time from the timing + * information in the panel info */ + lcdc->parms.hsync_ctl = (((hsync_period & 0xfff) << 16) | + (timing->hsync_pulse_width & 0xfff)); + lcdc->parms.vsync_period = vsync_period & 0xffffff; + lcdc->parms.vsync_pulse_width = (timing->vsync_pulse_width * + hsync_period) & 0xffffff; + + lcdc->parms.display_hctl = (((hsync_end_x & 0xfff) << 16) | + (hsync_start_x & 0xfff)); + lcdc->parms.display_vstart = display_vstart & 0xffffff; + lcdc->parms.display_vend = display_vend & 0xffffff; + lcdc->parms.hsync_skew = timing->hsync_skew & 0xfff; + lcdc->parms.polarity = ((timing->hsync_act_low << 0) | + (timing->vsync_act_low << 1) | + (timing->den_act_low << 2)); + lcdc->parms.clk_rate = timing->clk_rate; +} + +static int mdp_lcdc_probe(struct platform_device *pdev) +{ + struct msm_lcdc_platform_data *pdata = pdev->dev.platform_data; + struct mdp_lcdc_info *lcdc; + int ret = 0; + + if (!pdata) { + pr_err("%s: no LCDC platform data found\n", __func__); + return -EINVAL; + } + + lcdc = kzalloc(sizeof(struct mdp_lcdc_info), GFP_KERNEL); + if (!lcdc) + return -ENOMEM; + + /* We don't actually own the clocks, the mdp does. */ + lcdc->mdp_clk = clk_get(mdp_dev->dev.parent, "mdp_clk"); + if (IS_ERR(lcdc->mdp_clk)) { + pr_err("%s: failed to get mdp_clk\n", __func__); + ret = PTR_ERR(lcdc->mdp_clk); + goto err_get_mdp_clk; + } + + lcdc->pclk = clk_get(mdp_dev->dev.parent, "lcdc_pclk_clk"); + if (IS_ERR(lcdc->pclk)) { + pr_err("%s: failed to get lcdc_pclk\n", __func__); + ret = PTR_ERR(lcdc->pclk); + goto err_get_pclk; + } + + lcdc->pad_pclk = clk_get(mdp_dev->dev.parent, "lcdc_pad_pclk_clk"); + if (IS_ERR(lcdc->pad_pclk)) { + pr_err("%s: failed to get lcdc_pad_pclk\n", __func__); + ret = PTR_ERR(lcdc->pad_pclk); + goto err_get_pad_pclk; + } + + init_waitqueue_head(&lcdc->vsync_waitq); + lcdc->pdata = pdata; + lcdc->frame_start_cb.func = lcdc_frame_start; + + platform_set_drvdata(pdev, lcdc); + + mdp_out_if_register(mdp_dev, MSM_LCDC_INTERFACE, lcdc, MDP_DMA_P_DONE, + lcdc_dma_start); + + precompute_timing_parms(lcdc); + + lcdc->fb_start = pdata->fb_resource->start; + lcdc->mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); + + lcdc->fb_panel_data.suspend = lcdc_suspend; + lcdc->fb_panel_data.resume = lcdc_resume; + lcdc->fb_panel_data.wait_vsync = lcdc_wait_vsync; + lcdc->fb_panel_data.request_vsync = lcdc_request_vsync; + lcdc->fb_panel_data.clear_vsync = lcdc_clear_vsync; + lcdc->fb_panel_data.blank = lcdc_blank; + lcdc->fb_panel_data.unblank = lcdc_unblank; + lcdc->fb_panel_data.fb_data = pdata->fb_data; + lcdc->fb_panel_data.interface_type = MSM_LCDC_INTERFACE; + + ret = lcdc_hw_init(lcdc); + if (ret) { + pr_err("%s: Cannot initialize the mdp_lcdc\n", __func__); + goto err_hw_init; + } + + lcdc->fb_pdev.name = "msm_panel"; + lcdc->fb_pdev.id = pdata->fb_id; + lcdc->fb_pdev.resource = pdata->fb_resource; + lcdc->fb_pdev.num_resources = 1; + lcdc->fb_pdev.dev.platform_data = &lcdc->fb_panel_data; + + if (pdata->panel_ops->init) + pdata->panel_ops->init(pdata->panel_ops); + + ret = platform_device_register(&lcdc->fb_pdev); + if (ret) { + pr_err("%s: Cannot register msm_panel pdev\n", __func__); + goto err_plat_dev_reg; + } + + pr_info("%s: initialized\n", __func__); + + return 0; + +err_plat_dev_reg: +err_hw_init: + platform_set_drvdata(pdev, NULL); + clk_put(lcdc->pad_pclk); +err_get_pad_pclk: + clk_put(lcdc->pclk); +err_get_pclk: + clk_put(lcdc->mdp_clk); +err_get_mdp_clk: + kfree(lcdc); + return ret; +} + +static int mdp_lcdc_remove(struct platform_device *pdev) +{ + struct mdp_lcdc_info *lcdc = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + + clk_put(lcdc->pclk); + clk_put(lcdc->pad_pclk); + kfree(lcdc); + + return 0; +} + +static struct platform_driver mdp_lcdc_driver = { + .probe = mdp_lcdc_probe, + .remove = mdp_lcdc_remove, + .driver = { + .name = "msm_mdp_lcdc", + .owner = THIS_MODULE, + }, +}; + +static int mdp_lcdc_add_mdp_device(struct device *dev, + struct class_interface *class_intf) +{ + /* might need locking if mulitple mdp devices */ + if (mdp_dev) + return 0; + mdp_dev = container_of(dev, struct mdp_device, dev); + return platform_driver_register(&mdp_lcdc_driver); +} + +static void mdp_lcdc_remove_mdp_device(struct device *dev, + struct class_interface *class_intf) +{ + /* might need locking if mulitple mdp devices */ + if (dev != &mdp_dev->dev) + return; + platform_driver_unregister(&mdp_lcdc_driver); + mdp_dev = NULL; +} + +static struct class_interface mdp_lcdc_interface = { + .add_dev = &mdp_lcdc_add_mdp_device, + .remove_dev = &mdp_lcdc_remove_mdp_device, +}; + +static int __init mdp_lcdc_init(void) +{ + return register_mdp_client(&mdp_lcdc_interface); +} + +module_init(mdp_lcdc_init); diff --git a/drivers/video/msm/mdp_ppp.c b/drivers/video/msm/mdp_ppp.c index 2b6564e8bfe..19dfe821141 100644 --- a/drivers/video/msm/mdp_ppp.c +++ b/drivers/video/msm/mdp_ppp.c @@ -1,7 +1,7 @@ -/* drivers/video/msm/mdp_ppp.c +/* drivers/video/msm/src/drv/mdp/mdp_ppp.c * - * Copyright (C) 2007 QUALCOMM Incorporated * Copyright (C) 2007 Google Incorporated + * Copyright (c) 2008-2009, Code Aurora Forum. 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 @@ -12,55 +12,35 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ + +#include +#include +#include +#include +#include +#include #include -#include -#include #include -#include - -#include "mdp_hw.h" -#include "mdp_scale_tables.h" - -#define DLOG(x...) do {} while (0) - -#define MDP_DOWNSCALE_BLUR (MDP_DOWNSCALE_MAX + 1) -static int downscale_y_table = MDP_DOWNSCALE_MAX; -static int downscale_x_table = MDP_DOWNSCALE_MAX; - -struct mdp_regs { - uint32_t src0; - uint32_t src1; - uint32_t dst0; - uint32_t dst1; - uint32_t src_cfg; - uint32_t dst_cfg; - uint32_t src_pack; - uint32_t dst_pack; - uint32_t src_rect; - uint32_t dst_rect; - uint32_t src_ystride; - uint32_t dst_ystride; - uint32_t op; - uint32_t src_bpp; - uint32_t dst_bpp; - uint32_t edge; - uint32_t phasex_init; - uint32_t phasey_init; - uint32_t phasex_step; - uint32_t phasey_step; -}; +#include +#include +#include -static uint32_t pack_pattern[] = { - PPP_ARRAY0(PACK_PATTERN) -}; +#include "linux/proc_fs.h" -static uint32_t src_img_cfg[] = { - PPP_ARRAY1(CFG, SRC) -}; +#include +#include -static uint32_t dst_img_cfg[] = { - PPP_ARRAY1(CFG, DST) -}; +#include +#include +#include +#include + +#include "mdp.h" +#include "msm_fb.h" + +#define MDP_IS_IMGTYPE_BAD(x) (((x) >= MDP_IMGTYPE_LIMIT) && \ + (((x) < MDP_IMGTYPE2_START) || \ + ((x) >= MDP_IMGTYPE_LIMIT2))) static uint32_t bytes_per_pixel[] = { [MDP_RGB_565] = 2, @@ -74,457 +54,499 @@ static uint32_t bytes_per_pixel[] = { [MDP_Y_CBCR_H2V2] = 1, [MDP_Y_CRCB_H2V1] = 1, [MDP_Y_CRCB_H2V2] = 1, - [MDP_YCRYCB_H2V1] = 2 -}; - -static uint32_t dst_op_chroma[] = { - PPP_ARRAY1(CHROMA_SAMP, DST) -}; - -static uint32_t src_op_chroma[] = { - PPP_ARRAY1(CHROMA_SAMP, SRC) + [MDP_YCRYCB_H2V1] = 2, + [MDP_BGR_565] = 2 }; -static uint32_t bg_op_chroma[] = { - PPP_ARRAY1(CHROMA_SAMP, BG) -}; - -static void rotate_dst_addr_x(struct mdp_blit_req *req, struct mdp_regs *regs) -{ - regs->dst0 += (req->dst_rect.w - - min((uint32_t)16, req->dst_rect.w)) * regs->dst_bpp; - regs->dst1 += (req->dst_rect.w - - min((uint32_t)16, req->dst_rect.w)) * regs->dst_bpp; -} +extern uint32 mdp_plv[]; +extern struct semaphore mdp_ppp_mutex; -static void rotate_dst_addr_y(struct mdp_blit_req *req, struct mdp_regs *regs) +int mdp_get_bytes_per_pixel(uint32_t format, + struct msm_fb_data_type *mfd) { - regs->dst0 += (req->dst_rect.h - - min((uint32_t)16, req->dst_rect.h)) * - regs->dst_ystride; - regs->dst1 += (req->dst_rect.h - - min((uint32_t)16, req->dst_rect.h)) * - regs->dst_ystride; + int bpp = -EINVAL; + if (format == MDP_FB_FORMAT) + format = mfd->fb_imgType; + if (format < ARRAY_SIZE(bytes_per_pixel)) + bpp = bytes_per_pixel[format]; + + if (bpp <= 0) + printk(KERN_ERR "%s incorrect format %d\n", __func__, format); + return bpp; } -static void blit_rotate(struct mdp_blit_req *req, - struct mdp_regs *regs) +static uint32 mdp_conv_matx_rgb2yuv(uint32 input_pixel, + uint16 *matrix_and_bias_vector, + uint32 *clamp_vector, + uint32 *look_up_table) { - if (req->flags == MDP_ROT_NOP) - return; + uint8 input_C2, input_C0, input_C1; + uint32 output; + int32 comp_C2, comp_C1, comp_C0, temp; + int32 temp1, temp2, temp3; + int32 matrix[9]; + int32 bias_vector[3]; + int32 Y_low_limit, Y_high_limit, C_low_limit, C_high_limit; + int32 i; + uint32 _is_lookup_table_enabled; + + input_C2 = (input_pixel >> 16) & 0xFF; + input_C1 = (input_pixel >> 8) & 0xFF; + input_C0 = (input_pixel >> 0) & 0xFF; + + comp_C0 = input_C0; + comp_C1 = input_C1; + comp_C2 = input_C2; + + for (i = 0; i < 9; i++) + matrix[i] = + ((int32) (((int32) matrix_and_bias_vector[i]) << 20)) >> 20; + + bias_vector[0] = (int32) (matrix_and_bias_vector[9] & 0xFF); + bias_vector[1] = (int32) (matrix_and_bias_vector[10] & 0xFF); + bias_vector[2] = (int32) (matrix_and_bias_vector[11] & 0xFF); + + Y_low_limit = (int32) clamp_vector[0]; + Y_high_limit = (int32) clamp_vector[1]; + C_low_limit = (int32) clamp_vector[2]; + C_high_limit = (int32) clamp_vector[3]; + + if (look_up_table == 0) /* check for NULL point */ + _is_lookup_table_enabled = 0; + else + _is_lookup_table_enabled = 1; - regs->op |= PPP_OP_ROT_ON; - if ((req->flags & MDP_ROT_90 || req->flags & MDP_FLIP_LR) && - !(req->flags & MDP_ROT_90 && req->flags & MDP_FLIP_LR)) - rotate_dst_addr_x(req, regs); - if (req->flags & MDP_ROT_90) - regs->op |= PPP_OP_ROT_90; - if (req->flags & MDP_FLIP_UD) { - regs->op |= PPP_OP_FLIP_UD; - rotate_dst_addr_y(req, regs); + if (_is_lookup_table_enabled == 1) { + comp_C2 = (look_up_table[comp_C2] >> 16) & 0xFF; + comp_C1 = (look_up_table[comp_C1] >> 8) & 0xFF; + comp_C0 = (look_up_table[comp_C0] >> 0) & 0xFF; } - if (req->flags & MDP_FLIP_LR) - regs->op |= PPP_OP_FLIP_LR; -} + /* + * Color Conversion + * reorder input colors + */ + temp = comp_C2; + comp_C2 = comp_C1; + comp_C1 = comp_C0; + comp_C0 = temp; -static void blit_convert(struct mdp_blit_req *req, struct mdp_regs *regs) -{ - if (req->src.format == req->dst.format) - return; - if (IS_RGB(req->src.format) && IS_YCRCB(req->dst.format)) { - regs->op |= PPP_OP_CONVERT_RGB2YCBCR | PPP_OP_CONVERT_ON; - } else if (IS_YCRCB(req->src.format) && IS_RGB(req->dst.format)) { - regs->op |= PPP_OP_CONVERT_YCBCR2RGB | PPP_OP_CONVERT_ON; - if (req->dst.format == MDP_RGB_565) - regs->op |= PPP_OP_CONVERT_MATRIX_SECONDARY; - } -} + /* matrix multiplication */ + temp1 = comp_C0 * matrix[0] + comp_C1 * matrix[1] + comp_C2 * matrix[2]; + temp2 = comp_C0 * matrix[3] + comp_C1 * matrix[4] + comp_C2 * matrix[5]; + temp3 = comp_C0 * matrix[6] + comp_C1 * matrix[7] + comp_C2 * matrix[8]; -#define GET_BIT_RANGE(value, high, low) \ - (((1 << (high - low + 1)) - 1) & (value >> low)) -static uint32_t transp_convert(struct mdp_blit_req *req) -{ - uint32_t transp = 0; - if (req->src.format == MDP_RGB_565) { - /* pad each value to 8 bits by copying the high bits into the - * low end, convert RGB to RBG by switching low 2 components */ - transp |= ((GET_BIT_RANGE(req->transp_mask, 15, 11) << 3) | - (GET_BIT_RANGE(req->transp_mask, 15, 13))) << 16; - - transp |= ((GET_BIT_RANGE(req->transp_mask, 4, 0) << 3) | - (GET_BIT_RANGE(req->transp_mask, 4, 2))) << 8; - - transp |= (GET_BIT_RANGE(req->transp_mask, 10, 5) << 2) | - (GET_BIT_RANGE(req->transp_mask, 10, 9)); - } else { - /* convert RGB to RBG */ - transp |= (GET_BIT_RANGE(req->transp_mask, 15, 8)) | - (GET_BIT_RANGE(req->transp_mask, 23, 16) << 16) | - (GET_BIT_RANGE(req->transp_mask, 7, 0) << 8); - } - return transp; -} -#undef GET_BIT_RANGE + comp_C0 = temp1 + 0x100; + comp_C1 = temp2 + 0x100; + comp_C2 = temp3 + 0x100; -static void blit_blend(struct mdp_blit_req *req, struct mdp_regs *regs) -{ - /* TRANSP BLEND */ - if (req->transp_mask != MDP_TRANSP_NOP) { - req->transp_mask = transp_convert(req); - if (req->alpha != MDP_ALPHA_NOP) { - /* use blended transparancy mode - * pixel = (src == transp) ? dst : blend - * blend is combo of blend_eq_sel and - * blend_alpha_sel */ - regs->op |= PPP_OP_ROT_ON | PPP_OP_BLEND_ON | - PPP_OP_BLEND_ALPHA_BLEND_NORMAL | - PPP_OP_BLEND_CONSTANT_ALPHA | - PPP_BLEND_ALPHA_TRANSP; - } else { - /* simple transparancy mode - * pixel = (src == transp) ? dst : src */ - regs->op |= PPP_OP_ROT_ON | PPP_OP_BLEND_ON | - PPP_OP_BLEND_SRCPIXEL_TRANSP; - } - } + /* take interger part */ + comp_C0 >>= 9; + comp_C1 >>= 9; + comp_C2 >>= 9; - req->alpha &= 0xff; - /* ALPHA BLEND */ - if (HAS_ALPHA(req->src.format)) { - regs->op |= PPP_OP_ROT_ON | PPP_OP_BLEND_ON | - PPP_OP_BLEND_SRCPIXEL_ALPHA; - } else if (req->alpha < MDP_ALPHA_NOP) { - /* just blend by alpha */ - regs->op |= PPP_OP_ROT_ON | PPP_OP_BLEND_ON | - PPP_OP_BLEND_ALPHA_BLEND_NORMAL | - PPP_OP_BLEND_CONSTANT_ALPHA; - } - - regs->op |= bg_op_chroma[req->dst.format]; -} + /* post bias (+) */ + comp_C0 += bias_vector[0]; + comp_C1 += bias_vector[1]; + comp_C2 += bias_vector[2]; -#define ONE_HALF (1LL << 32) -#define ONE (1LL << 33) -#define TWO (2LL << 33) -#define THREE (3LL << 33) -#define FRAC_MASK (ONE - 1) -#define INT_MASK (~FRAC_MASK) + /* limit pixel to 8-bit */ + if (comp_C0 < 0) + comp_C0 = 0; -static int scale_params(uint32_t dim_in, uint32_t dim_out, uint32_t origin, - uint32_t *phase_init, uint32_t *phase_step) -{ - /* to improve precicsion calculations are done in U31.33 and converted - * to U3.29 at the end */ - int64_t k1, k2, k3, k4, tmp; - uint64_t n, d, os, os_p, od, od_p, oreq; - unsigned rpa = 0; - int64_t ip64, delta; - - if (dim_out % 3 == 0) - rpa = !(dim_in % (dim_out / 3)); - - n = ((uint64_t)dim_out) << 34; - d = dim_in; - if (!d) - return -1; - do_div(n, d); - k3 = (n + 1) >> 1; - if ((k3 >> 4) < (1LL << 27) || (k3 >> 4) > (1LL << 31)) { - DLOG("crap bad scale\n"); - return -1; - } - n = ((uint64_t)dim_in) << 34; - d = (uint64_t)dim_out; - if (!d) - return -1; - do_div(n, d); - k1 = (n + 1) >> 1; - k2 = (k1 - ONE) >> 1; - - *phase_init = (int)(k2 >> 4); - k4 = (k3 - ONE) >> 1; - - if (rpa) { - os = ((uint64_t)origin << 33) - ONE_HALF; - tmp = (dim_out * os) + ONE_HALF; - if (!dim_in) - return -1; - do_div(tmp, dim_in); - od = tmp - ONE_HALF; - } else { - os = ((uint64_t)origin << 1) - 1; - od = (((k3 * os) >> 1) + k4); - } + if (comp_C0 > 255) + comp_C0 = 255; - od_p = od & INT_MASK; - if (od_p != od) - od_p += ONE; + if (comp_C1 < 0) + comp_C1 = 0; - if (rpa) { - tmp = (dim_in * od_p) + ONE_HALF; - if (!dim_in) - return -1; - do_div(tmp, dim_in); - os_p = tmp - ONE_HALF; - } else { - os_p = ((k1 * (od_p >> 33)) + k2); - } + if (comp_C1 > 255) + comp_C1 = 255; - oreq = (os_p & INT_MASK) - ONE; + if (comp_C2 < 0) + comp_C2 = 0; - ip64 = os_p - oreq; - delta = ((int64_t)(origin) << 33) - oreq; - ip64 -= delta; - /* limit to valid range before the left shift */ - delta = (ip64 & (1LL << 63)) ? 4 : -4; - delta <<= 33; - while (abs((int)(ip64 >> 33)) > 4) - ip64 += delta; - *phase_init = (int)(ip64 >> 4); - *phase_step = (uint32_t)(k1 >> 4); - return 0; -} + if (comp_C2 > 255) + comp_C2 = 255; -static void load_scale_table(const struct mdp_info *mdp, - struct mdp_table_entry *table, int len) -{ - int i; - for (i = 0; i < len; i++) - mdp_writel(mdp, table[i].val, table[i].reg); -} + /* clamp */ + if (comp_C0 < Y_low_limit) + comp_C0 = Y_low_limit; -enum { -IMG_LEFT, -IMG_RIGHT, -IMG_TOP, -IMG_BOTTOM, -}; + if (comp_C0 > Y_high_limit) + comp_C0 = Y_high_limit; -static void get_edge_info(uint32_t src, uint32_t src_coord, uint32_t dst, - uint32_t *interp1, uint32_t *interp2, - uint32_t *repeat1, uint32_t *repeat2) { - if (src > 3 * dst) { - *interp1 = 0; - *interp2 = src - 1; - *repeat1 = 0; - *repeat2 = 0; - } else if (src == 3 * dst) { - *interp1 = 0; - *interp2 = src; - *repeat1 = 0; - *repeat2 = 1; - } else if (src > dst && src < 3 * dst) { - *interp1 = -1; - *interp2 = src; - *repeat1 = 1; - *repeat2 = 1; - } else if (src == dst) { - *interp1 = -1; - *interp2 = src + 1; - *repeat1 = 1; - *repeat2 = 2; - } else { - *interp1 = -2; - *interp2 = src + 1; - *repeat1 = 2; - *repeat2 = 2; - } - *interp1 += src_coord; - *interp2 += src_coord; -} + if (comp_C1 < C_low_limit) + comp_C1 = C_low_limit; -static int get_edge_cond(struct mdp_blit_req *req, struct mdp_regs *regs) -{ - int32_t luma_interp[4]; - int32_t luma_repeat[4]; - int32_t chroma_interp[4]; - int32_t chroma_bound[4]; - int32_t chroma_repeat[4]; - uint32_t dst_w, dst_h; - - memset(&luma_interp, 0, sizeof(int32_t) * 4); - memset(&luma_repeat, 0, sizeof(int32_t) * 4); - memset(&chroma_interp, 0, sizeof(int32_t) * 4); - memset(&chroma_bound, 0, sizeof(int32_t) * 4); - memset(&chroma_repeat, 0, sizeof(int32_t) * 4); - regs->edge = 0; + if (comp_C1 > C_high_limit) + comp_C1 = C_high_limit; - if (req->flags & MDP_ROT_90) { - dst_w = req->dst_rect.h; - dst_h = req->dst_rect.w; - } else { - dst_w = req->dst_rect.w; - dst_h = req->dst_rect.h; - } + if (comp_C2 < C_low_limit) + comp_C2 = C_low_limit; - if (regs->op & (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON)) { - get_edge_info(req->src_rect.h, req->src_rect.y, dst_h, - &luma_interp[IMG_TOP], &luma_interp[IMG_BOTTOM], - &luma_repeat[IMG_TOP], &luma_repeat[IMG_BOTTOM]); - get_edge_info(req->src_rect.w, req->src_rect.x, dst_w, - &luma_interp[IMG_LEFT], &luma_interp[IMG_RIGHT], - &luma_repeat[IMG_LEFT], &luma_repeat[IMG_RIGHT]); - } else { - luma_interp[IMG_LEFT] = req->src_rect.x; - luma_interp[IMG_RIGHT] = req->src_rect.x + req->src_rect.w - 1; - luma_interp[IMG_TOP] = req->src_rect.y; - luma_interp[IMG_BOTTOM] = req->src_rect.y + req->src_rect.h - 1; - luma_repeat[IMG_LEFT] = 0; - luma_repeat[IMG_TOP] = 0; - luma_repeat[IMG_RIGHT] = 0; - luma_repeat[IMG_BOTTOM] = 0; - } - - chroma_interp[IMG_LEFT] = luma_interp[IMG_LEFT]; - chroma_interp[IMG_RIGHT] = luma_interp[IMG_RIGHT]; - chroma_interp[IMG_TOP] = luma_interp[IMG_TOP]; - chroma_interp[IMG_BOTTOM] = luma_interp[IMG_BOTTOM]; - - chroma_bound[IMG_LEFT] = req->src_rect.x; - chroma_bound[IMG_RIGHT] = req->src_rect.x + req->src_rect.w - 1; - chroma_bound[IMG_TOP] = req->src_rect.y; - chroma_bound[IMG_BOTTOM] = req->src_rect.y + req->src_rect.h - 1; - - if (IS_YCRCB(req->src.format)) { - chroma_interp[IMG_LEFT] = chroma_interp[IMG_LEFT] >> 1; - chroma_interp[IMG_RIGHT] = (chroma_interp[IMG_RIGHT] + 1) >> 1; - - chroma_bound[IMG_LEFT] = chroma_bound[IMG_LEFT] >> 1; - chroma_bound[IMG_RIGHT] = chroma_bound[IMG_RIGHT] >> 1; - } - - if (req->src.format == MDP_Y_CBCR_H2V2 || - req->src.format == MDP_Y_CRCB_H2V2) { - chroma_interp[IMG_TOP] = (chroma_interp[IMG_TOP] - 1) >> 1; - chroma_interp[IMG_BOTTOM] = (chroma_interp[IMG_BOTTOM] + 1) - >> 1; - chroma_bound[IMG_TOP] = (chroma_bound[IMG_TOP] + 1) >> 1; - chroma_bound[IMG_BOTTOM] = chroma_bound[IMG_BOTTOM] >> 1; - } - - chroma_repeat[IMG_LEFT] = chroma_bound[IMG_LEFT] - - chroma_interp[IMG_LEFT]; - chroma_repeat[IMG_RIGHT] = chroma_interp[IMG_RIGHT] - - chroma_bound[IMG_RIGHT]; - chroma_repeat[IMG_TOP] = chroma_bound[IMG_TOP] - - chroma_interp[IMG_TOP]; - chroma_repeat[IMG_BOTTOM] = chroma_interp[IMG_BOTTOM] - - chroma_bound[IMG_BOTTOM]; - - if (chroma_repeat[IMG_LEFT] < 0 || chroma_repeat[IMG_LEFT] > 3 || - chroma_repeat[IMG_RIGHT] < 0 || chroma_repeat[IMG_RIGHT] > 3 || - chroma_repeat[IMG_TOP] < 0 || chroma_repeat[IMG_TOP] > 3 || - chroma_repeat[IMG_BOTTOM] < 0 || chroma_repeat[IMG_BOTTOM] > 3 || - luma_repeat[IMG_LEFT] < 0 || luma_repeat[IMG_LEFT] > 3 || - luma_repeat[IMG_RIGHT] < 0 || luma_repeat[IMG_RIGHT] > 3 || - luma_repeat[IMG_TOP] < 0 || luma_repeat[IMG_TOP] > 3 || - luma_repeat[IMG_BOTTOM] < 0 || luma_repeat[IMG_BOTTOM] > 3) - return -1; + if (comp_C2 > C_high_limit) + comp_C2 = C_high_limit; - regs->edge |= (chroma_repeat[IMG_LEFT] & 3) << MDP_LEFT_CHROMA; - regs->edge |= (chroma_repeat[IMG_RIGHT] & 3) << MDP_RIGHT_CHROMA; - regs->edge |= (chroma_repeat[IMG_TOP] & 3) << MDP_TOP_CHROMA; - regs->edge |= (chroma_repeat[IMG_BOTTOM] & 3) << MDP_BOTTOM_CHROMA; - regs->edge |= (luma_repeat[IMG_LEFT] & 3) << MDP_LEFT_LUMA; - regs->edge |= (luma_repeat[IMG_RIGHT] & 3) << MDP_RIGHT_LUMA; - regs->edge |= (luma_repeat[IMG_TOP] & 3) << MDP_TOP_LUMA; - regs->edge |= (luma_repeat[IMG_BOTTOM] & 3) << MDP_BOTTOM_LUMA; - return 0; + output = (comp_C2 << 16) | (comp_C1 << 8) | comp_C0; + return output; } -static int blit_scale(const struct mdp_info *mdp, struct mdp_blit_req *req, - struct mdp_regs *regs) +uint32 mdp_conv_matx_yuv2rgb(uint32 input_pixel, + uint16 *matrix_and_bias_vector, + uint32 *clamp_vector, uint32 *look_up_table) { - uint32_t phase_init_x, phase_init_y, phase_step_x, phase_step_y; - uint32_t scale_factor_x, scale_factor_y; - uint32_t downscale; - uint32_t dst_w, dst_h; + uint8 input_C2, input_C0, input_C1; + uint32 output; + int32 comp_C2, comp_C1, comp_C0, temp; + int32 temp1, temp2, temp3; + int32 matrix[9]; + int32 bias_vector[3]; + int32 Y_low_limit, Y_high_limit, C_low_limit, C_high_limit; + int32 i; + uint32 _is_lookup_table_enabled; + + input_C2 = (input_pixel >> 16) & 0xFF; + input_C1 = (input_pixel >> 8) & 0xFF; + input_C0 = (input_pixel >> 0) & 0xFF; + + comp_C0 = input_C0; + comp_C1 = input_C1; + comp_C2 = input_C2; + + for (i = 0; i < 9; i++) + matrix[i] = + ((int32) (((int32) matrix_and_bias_vector[i]) << 20)) >> 20; + + bias_vector[0] = (int32) (matrix_and_bias_vector[9] & 0xFF); + bias_vector[1] = (int32) (matrix_and_bias_vector[10] & 0xFF); + bias_vector[2] = (int32) (matrix_and_bias_vector[11] & 0xFF); + + Y_low_limit = (int32) clamp_vector[0]; + Y_high_limit = (int32) clamp_vector[1]; + C_low_limit = (int32) clamp_vector[2]; + C_high_limit = (int32) clamp_vector[3]; + + if (look_up_table == 0) /* check for NULL point */ + _is_lookup_table_enabled = 0; + else + _is_lookup_table_enabled = 1; - if (req->flags & MDP_ROT_90) { - dst_w = req->dst_rect.h; - dst_h = req->dst_rect.w; - } else { - dst_w = req->dst_rect.w; - dst_h = req->dst_rect.h; - } - if ((req->src_rect.w == dst_w) && (req->src_rect.h == dst_h) && - !(req->flags & MDP_BLUR)) { - regs->phasex_init = 0; - regs->phasey_init = 0; - regs->phasex_step = 0; - regs->phasey_step = 0; - return 0; + /* clamp */ + if (comp_C0 < Y_low_limit) + comp_C0 = Y_low_limit; + + if (comp_C0 > Y_high_limit) + comp_C0 = Y_high_limit; + + if (comp_C1 < C_low_limit) + comp_C1 = C_low_limit; + + if (comp_C1 > C_high_limit) + comp_C1 = C_high_limit; + + if (comp_C2 < C_low_limit) + comp_C2 = C_low_limit; + + if (comp_C2 > C_high_limit) + comp_C2 = C_high_limit; + + /* + * Color Conversion + * pre bias (-) + */ + comp_C0 -= bias_vector[0]; + comp_C1 -= bias_vector[1]; + comp_C2 -= bias_vector[2]; + + /* matrix multiplication */ + temp1 = comp_C0 * matrix[0] + comp_C1 * matrix[1] + comp_C2 * matrix[2]; + temp2 = comp_C0 * matrix[3] + comp_C1 * matrix[4] + comp_C2 * matrix[5]; + temp3 = comp_C0 * matrix[6] + comp_C1 * matrix[7] + comp_C2 * matrix[8]; + + comp_C0 = temp1 + 0x100; + comp_C1 = temp2 + 0x100; + comp_C2 = temp3 + 0x100; + + /* take interger part */ + comp_C0 >>= 9; + comp_C1 >>= 9; + comp_C2 >>= 9; + + /* reorder output colors */ + temp = comp_C0; + comp_C0 = comp_C1; + comp_C1 = comp_C2; + comp_C2 = temp; + + /* limit pixel to 8-bit */ + if (comp_C0 < 0) + comp_C0 = 0; + + if (comp_C0 > 255) + comp_C0 = 255; + + if (comp_C1 < 0) + comp_C1 = 0; + + if (comp_C1 > 255) + comp_C1 = 255; + + if (comp_C2 < 0) + comp_C2 = 0; + + if (comp_C2 > 255) + comp_C2 = 255; + + /* Look-up table */ + if (_is_lookup_table_enabled == 1) { + comp_C2 = (look_up_table[comp_C2] >> 16) & 0xFF; + comp_C1 = (look_up_table[comp_C1] >> 8) & 0xFF; + comp_C0 = (look_up_table[comp_C0] >> 0) & 0xFF; } - if (scale_params(req->src_rect.w, dst_w, 1, &phase_init_x, - &phase_step_x) || - scale_params(req->src_rect.h, dst_h, 1, &phase_init_y, - &phase_step_y)) - return -1; + output = (comp_C2 << 16) | (comp_C1 << 8) | comp_C0; + return output; +} - scale_factor_x = (dst_w * 10) / req->src_rect.w; - scale_factor_y = (dst_h * 10) / req->src_rect.h; +static uint32 mdp_calc_tpval(MDPIMG *mdpImg) +{ + uint32 tpVal; + uint8 plane_tp; + + tpVal = 0; + if ((mdpImg->imgType == MDP_RGB_565) + || (mdpImg->imgType == MDP_BGR_565)) { + /* + * transparent color conversion into 24 bpp + * + * C2R_8BIT + * left shift the entire bit and or it with the upper most bits + */ + plane_tp = (uint8) ((mdpImg->tpVal & 0xF800) >> 11); + tpVal |= ((plane_tp << 3) | ((plane_tp & 0x1C) >> 2)) << 16; + + /* C1B_8BIT */ + plane_tp = (uint8) (mdpImg->tpVal & 0x1F); + tpVal |= ((plane_tp << 3) | ((plane_tp & 0x1C) >> 2)) << 8; + + /* C0G_8BIT */ + plane_tp = (uint8) ((mdpImg->tpVal & 0x7E0) >> 5); + tpVal |= ((plane_tp << 2) | ((plane_tp & 0x30) >> 4)); + } else { + /* 24bit RGB to RBG conversion */ - if (scale_factor_x > 8) - downscale = MDP_DOWNSCALE_PT8TO1; - else if (scale_factor_x > 6) - downscale = MDP_DOWNSCALE_PT6TOPT8; - else if (scale_factor_x > 4) - downscale = MDP_DOWNSCALE_PT4TOPT6; - else - downscale = MDP_DOWNSCALE_PT2TOPT4; - if (downscale != downscale_x_table) { - load_scale_table(mdp, mdp_downscale_x_table[downscale], 64); - downscale_x_table = downscale; - } - - if (scale_factor_y > 8) - downscale = MDP_DOWNSCALE_PT8TO1; - else if (scale_factor_y > 6) - downscale = MDP_DOWNSCALE_PT6TOPT8; - else if (scale_factor_y > 4) - downscale = MDP_DOWNSCALE_PT4TOPT6; - else - downscale = MDP_DOWNSCALE_PT2TOPT4; - if (downscale != downscale_y_table) { - load_scale_table(mdp, mdp_downscale_y_table[downscale], 64); - downscale_y_table = downscale; + tpVal = (mdpImg->tpVal & 0xFF00) >> 8; + tpVal |= (mdpImg->tpVal & 0xFF) << 8; + tpVal |= (mdpImg->tpVal & 0xFF0000); } - regs->phasex_init = phase_init_x; - regs->phasey_init = phase_init_y; - regs->phasex_step = phase_step_x; - regs->phasey_step = phase_step_y; - regs->op |= (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON); - return 0; + return tpVal; +} +static uint8 *mdp_get_chroma_addr(MDPIBUF *iBuf) +{ + uint8 *dest1; + + dest1 = NULL; + switch (iBuf->ibuf_type) { + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + dest1 = (uint8 *) iBuf->buf; + dest1 += iBuf->ibuf_width * iBuf->ibuf_height * iBuf->bpp; + break; + + default: + break; + } + + return dest1; } -static void blit_blur(const struct mdp_info *mdp, struct mdp_blit_req *req, - struct mdp_regs *regs) +static void mdp_ppp_setbg(MDPIBUF *iBuf) { - if (!(req->flags & MDP_BLUR)) + uint8 *bg0_addr; + uint8 *bg1_addr; + uint32 bg0_ystride, bg1_ystride; + uint32 ppp_src_cfg_reg, unpack_pattern; + int v_slice, h_slice; + + v_slice = h_slice = 1; + bg0_addr = (uint8 *) iBuf->buf; + bg1_addr = mdp_get_chroma_addr(iBuf); + + bg0_ystride = iBuf->ibuf_width * iBuf->bpp; + bg1_ystride = iBuf->ibuf_width * iBuf->bpp; + + switch (iBuf->ibuf_type) { + case MDP_BGR_565: + case MDP_RGB_565: + /* 888 = 3bytes + * RGB = 3Components + * RGB interleaved + */ + ppp_src_cfg_reg = PPP_SRC_C2R_5BITS | PPP_SRC_C0G_6BITS | + PPP_SRC_C1B_5BITS | PPP_SRC_BPP_INTERLVD_2BYTES | + PPP_SRC_INTERLVD_3COMPONENTS | PPP_SRC_UNPACK_TIGHT | + PPP_SRC_UNPACK_ALIGN_LSB | + PPP_SRC_FETCH_PLANES_INTERLVD; + + if (iBuf->ibuf_type == MDP_RGB_565) + unpack_pattern = + MDP_GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8); + else + unpack_pattern = + MDP_GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 8); + break; + + case MDP_RGB_888: + /* + * 888 = 3bytes + * RGB = 3Components + * RGB interleaved + */ + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | PPP_SRC_BPP_INTERLVD_3BYTES | + PPP_SRC_INTERLVD_3COMPONENTS | PPP_SRC_UNPACK_TIGHT | + PPP_SRC_UNPACK_ALIGN_LSB | PPP_SRC_FETCH_PLANES_INTERLVD; + + unpack_pattern = + MDP_GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8); + break; + + case MDP_BGRA_8888: + case MDP_RGBA_8888: + case MDP_ARGB_8888: + case MDP_XRGB_8888: + case MDP_RGBX_8888: + /* + * 8888 = 4bytes + * ARGB = 4Components + * ARGB interleaved + */ + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | PPP_SRC_C3A_8BITS | PPP_SRC_C3_ALPHA_EN | + PPP_SRC_BPP_INTERLVD_4BYTES | PPP_SRC_INTERLVD_4COMPONENTS | + PPP_SRC_UNPACK_TIGHT | PPP_SRC_UNPACK_ALIGN_LSB | + PPP_SRC_FETCH_PLANES_INTERLVD; + + if (iBuf->ibuf_type == MDP_BGRA_8888) + unpack_pattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, + 8); + else if (iBuf->ibuf_type == MDP_RGBA_8888 || + iBuf->ibuf_type == MDP_RGBX_8888) + unpack_pattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G, CLR_R, + 8); + else if (iBuf->ibuf_type == MDP_XRGB_8888) + unpack_pattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, + 8); + else + unpack_pattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, + 8); + break; + + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | + PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | + PPP_SRC_C3A_8BITS | + PPP_SRC_BPP_INTERLVD_2BYTES | + PPP_SRC_INTERLVD_2COMPONENTS | + PPP_SRC_UNPACK_TIGHT | + PPP_SRC_UNPACK_ALIGN_LSB | PPP_SRC_FETCH_PLANES_PSEUDOPLNR; + + if (iBuf->ibuf_type == MDP_Y_CBCR_H2V1) + unpack_pattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8); + else + unpack_pattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8); + v_slice = h_slice = 2; + break; + + case MDP_YCRYCB_H2V1: + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | + PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | + PPP_SRC_C3A_8BITS | + PPP_SRC_BPP_INTERLVD_2BYTES | + PPP_SRC_INTERLVD_4COMPONENTS | + PPP_SRC_UNPACK_TIGHT | PPP_SRC_UNPACK_ALIGN_LSB; + + unpack_pattern = + MDP_GET_PACK_PATTERN(CLR_Y, CLR_CR, CLR_Y, CLR_CB, 8); + h_slice = 2; + break; + + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | + PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | + PPP_SRC_C3A_8BITS | + PPP_SRC_BPP_INTERLVD_2BYTES | + PPP_SRC_INTERLVD_2COMPONENTS | + PPP_SRC_UNPACK_TIGHT | + PPP_SRC_UNPACK_ALIGN_LSB | PPP_SRC_FETCH_PLANES_PSEUDOPLNR; + + if (iBuf->ibuf_type == MDP_Y_CBCR_H2V1) + unpack_pattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8); + else + unpack_pattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8); + h_slice = 2; + break; + + default: return; - - if (!(downscale_x_table == MDP_DOWNSCALE_BLUR && - downscale_y_table == MDP_DOWNSCALE_BLUR)) { - load_scale_table(mdp, mdp_gaussian_blur_table, 128); - downscale_x_table = MDP_DOWNSCALE_BLUR; - downscale_y_table = MDP_DOWNSCALE_BLUR; } - regs->op |= (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON); + /* starting input address adjustment */ + mdp_adjust_start_addr(&bg0_addr, &bg1_addr, v_slice, h_slice, + iBuf->roi.lcd_x, iBuf->roi.lcd_y, + iBuf->ibuf_width, iBuf->ibuf_height, iBuf->bpp, + iBuf, 1); + + /* + * 0x01c0: background plane 0 addr + * 0x01c4: background plane 1 addr + * 0x01c8: background plane 2 addr + * 0x01cc: bg y stride for plane 0 and 1 + * 0x01d0: bg y stride for plane 2 + * 0x01d4: bg src PPP config + * 0x01d8: unpack pattern + */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01c0, bg0_addr); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01c4, bg1_addr); + + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01cc, + (bg1_ystride << 16) | bg0_ystride); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01d4, ppp_src_cfg_reg); + + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01d8, unpack_pattern); } +#define IS_PSEUDOPLNR(img) ((img == MDP_Y_CRCB_H2V2) | \ + (img == MDP_Y_CBCR_H2V2) | \ + (img == MDP_Y_CRCB_H2V1) | \ + (img == MDP_Y_CBCR_H2V1)) #define IMG_LEN(rect_h, w, rect_w, bpp) (((rect_h) * w) * bpp) #define Y_TO_CRCB_RATIO(format) \ ((format == MDP_Y_CBCR_H2V2 || format == MDP_Y_CRCB_H2V2) ? 2 :\ - (format == MDP_Y_CBCR_H2V1 || format == MDP_Y_CRCB_H2V1) ? 1 : 1) + (format == MDP_Y_CBCR_H2V1 || format == MDP_Y_CRCB_H2V1) ? 1 : 1) +#ifdef CONFIG_ANDROID_PMEM static void get_len(struct mdp_img *img, struct mdp_rect *rect, uint32_t bpp, - uint32_t *len0, uint32_t *len1) + uint32_t *len0, uint32_t *len1) { *len0 = IMG_LEN(rect->h, img->width, rect->w, bpp); if (IS_PSEUDOPLNR(img->format)) @@ -533,199 +555,1016 @@ static void get_len(struct mdp_img *img, struct mdp_rect *rect, uint32_t bpp, *len1 = 0; } -static int valid_src_dst(unsigned long src_start, unsigned long src_len, - unsigned long dst_start, unsigned long dst_len, - struct mdp_blit_req *req, struct mdp_regs *regs) +static void flush_imgs(struct mdp_blit_req *req, int src_bpp, int dst_bpp, + struct file *p_src_file, struct file *p_dst_file) { - unsigned long src_min_ok = src_start; - unsigned long src_max_ok = src_start + src_len; - unsigned long dst_min_ok = dst_start; - unsigned long dst_max_ok = dst_start + dst_len; - uint32_t src0_len, src1_len, dst0_len, dst1_len; - get_len(&req->src, &req->src_rect, regs->src_bpp, &src0_len, - &src1_len); - get_len(&req->dst, &req->dst_rect, regs->dst_bpp, &dst0_len, - &dst1_len); - - if (regs->src0 < src_min_ok || regs->src0 > src_max_ok || - regs->src0 + src0_len > src_max_ok) { - DLOG("invalid_src %x %x %lx %lx\n", regs->src0, - src0_len, src_min_ok, src_max_ok); - return 0; + uint32_t src0_len, src1_len; + + if (!(req->flags & MDP_BLIT_NON_CACHED)) { + /* flush src images to memory before dma to mdp */ + get_len(&req->src, &req->src_rect, src_bpp, + &src0_len, &src1_len); + + flush_pmem_file(p_src_file, + req->src.offset, src0_len); + + if (IS_PSEUDOPLNR(req->src.format)) + flush_pmem_file(p_src_file, + req->src.offset + src0_len, src1_len); + } + +} +#else +static void flush_imgs(struct mdp_blit_req *req, int src_bpp, int dst_bpp, + struct file *p_src_file, struct file *p_dst_file) { } +#endif + +static void mdp_start_ppp(struct msm_fb_data_type *mfd, MDPIBUF *iBuf, +struct mdp_blit_req *req, struct file *p_src_file, struct file *p_dst_file) +{ + uint8 *src0, *src1; + uint8 *dest0, *dest1; + uint16 inpBpp; + uint32 dest0_ystride; + uint32 src_width; + uint32 src_height; + uint32 src0_ystride; + uint32 dst_roi_width; + uint32 dst_roi_height; + uint32 ppp_src_cfg_reg, ppp_operation_reg, ppp_dst_cfg_reg; + uint32 alpha, tpVal; + uint32 packPattern; + uint32 dst_packPattern; + boolean inputRGB, outputRGB, pseudoplanr_output; + int sv_slice, sh_slice; + int dv_slice, dh_slice; + boolean perPixelAlpha = FALSE; + boolean ppp_lookUp_enable = FALSE; + + sv_slice = sh_slice = dv_slice = dh_slice = 1; + alpha = tpVal = 0; + src_width = iBuf->mdpImg.width; + src_height = iBuf->roi.y + iBuf->roi.height; + src1 = NULL; + dest1 = NULL; + + inputRGB = outputRGB = TRUE; + pseudoplanr_output = FALSE; + ppp_operation_reg = 0; + ppp_dst_cfg_reg = 0; + ppp_src_cfg_reg = 0; + + /* Wait for the pipe to clear */ + do { } while (mdp_ppp_pipe_wait() <= 0); + + /* + * destination config + */ + switch (iBuf->ibuf_type) { + case MDP_RGB_888: + dst_packPattern = + MDP_GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8); + ppp_dst_cfg_reg = + PPP_DST_C0G_8BIT | PPP_DST_C1B_8BIT | PPP_DST_C2R_8BIT | + PPP_DST_PACKET_CNT_INTERLVD_3ELEM | PPP_DST_PACK_TIGHT | + PPP_DST_PACK_ALIGN_LSB | PPP_DST_OUT_SEL_AXI | + PPP_DST_BPP_3BYTES | PPP_DST_PLANE_INTERLVD; + break; + + case MDP_BGRA_8888: + case MDP_XRGB_8888: + case MDP_ARGB_8888: + case MDP_RGBA_8888: + case MDP_RGBX_8888: + if (iBuf->ibuf_type == MDP_BGRA_8888) + dst_packPattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, + 8); + else if (iBuf->ibuf_type == MDP_RGBA_8888 || + iBuf->ibuf_type == MDP_RGBX_8888) + dst_packPattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G, CLR_R, + 8); + else if (iBuf->ibuf_type == MDP_XRGB_8888) + dst_packPattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, + 8); + else + dst_packPattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, + 8); + + ppp_dst_cfg_reg = PPP_DST_C0G_8BIT | + PPP_DST_C1B_8BIT | + PPP_DST_C2R_8BIT | + PPP_DST_C3A_8BIT | + PPP_DST_C3ALPHA_EN | + PPP_DST_PACKET_CNT_INTERLVD_4ELEM | + PPP_DST_PACK_TIGHT | + PPP_DST_PACK_ALIGN_LSB | + PPP_DST_OUT_SEL_AXI | + PPP_DST_BPP_4BYTES | PPP_DST_PLANE_INTERLVD; + break; + + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + if (iBuf->ibuf_type == MDP_Y_CBCR_H2V2) + dst_packPattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8); + else + dst_packPattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8); + + ppp_dst_cfg_reg = PPP_DST_C2R_8BIT | + PPP_DST_C0G_8BIT | + PPP_DST_C1B_8BIT | + PPP_DST_C3A_8BIT | + PPP_DST_PACKET_CNT_INTERLVD_2ELEM | + PPP_DST_PACK_TIGHT | + PPP_DST_PACK_ALIGN_LSB | + PPP_DST_OUT_SEL_AXI | PPP_DST_BPP_2BYTES; + + ppp_operation_reg |= PPP_OP_DST_CHROMA_420; + outputRGB = FALSE; + pseudoplanr_output = TRUE; + /* + * vertically (y direction) and horizontally (x direction) + * sample reduction by 2 + */ + + /* + * H2V2(YUV420) Cosite + * + * Y Y Y Y + * CbCr CbCr + * Y Y Y Y + * Y Y Y Y + * CbCr CbCr + * Y Y Y Y + */ + dv_slice = dh_slice = 2; + + /* (x,y) and (width,height) must be even numbern */ + iBuf->roi.lcd_x = (iBuf->roi.lcd_x / 2) * 2; + iBuf->roi.dst_width = (iBuf->roi.dst_width / 2) * 2; + iBuf->roi.x = (iBuf->roi.x / 2) * 2; + iBuf->roi.width = (iBuf->roi.width / 2) * 2; + + iBuf->roi.lcd_y = (iBuf->roi.lcd_y / 2) * 2; + iBuf->roi.dst_height = (iBuf->roi.dst_height / 2) * 2; + iBuf->roi.y = (iBuf->roi.y / 2) * 2; + iBuf->roi.height = (iBuf->roi.height / 2) * 2; + break; + + case MDP_YCRYCB_H2V1: + dst_packPattern = + MDP_GET_PACK_PATTERN(CLR_Y, CLR_CR, CLR_Y, CLR_CB, 8); + ppp_dst_cfg_reg = + PPP_DST_C2R_8BIT | PPP_DST_C0G_8BIT | PPP_DST_C1B_8BIT | + PPP_DST_C3A_8BIT | PPP_DST_PACKET_CNT_INTERLVD_4ELEM | + PPP_DST_PACK_TIGHT | PPP_DST_PACK_ALIGN_LSB | + PPP_DST_OUT_SEL_AXI | PPP_DST_BPP_2BYTES | + PPP_DST_PLANE_INTERLVD; + + ppp_operation_reg |= PPP_OP_DST_CHROMA_H2V1; + outputRGB = FALSE; + /* + * horizontally (x direction) sample reduction by 2 + * + * H2V1(YUV422) Cosite + * + * YCbCr Y YCbCr Y + * YCbCr Y YCbCr Y + * YCbCr Y YCbCr Y + * YCbCr Y YCbCr Y + */ + dh_slice = 2; + + /* + * if it's TV-Out/MDP_YCRYCB_H2V1, let's go through the + * preloaded gamma setting of 2.2 when the content is + * non-linear ppp_lookUp_enable = TRUE; + */ + + /* x and width must be even number */ + iBuf->roi.lcd_x = (iBuf->roi.lcd_x / 2) * 2; + iBuf->roi.dst_width = (iBuf->roi.dst_width / 2) * 2; + iBuf->roi.x = (iBuf->roi.x / 2) * 2; + iBuf->roi.width = (iBuf->roi.width / 2) * 2; + break; + + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + if (iBuf->ibuf_type == MDP_Y_CBCR_H2V1) + dst_packPattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8); + else + dst_packPattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8); + + ppp_dst_cfg_reg = PPP_DST_C2R_8BIT | + PPP_DST_C0G_8BIT | + PPP_DST_C1B_8BIT | + PPP_DST_C3A_8BIT | + PPP_DST_PACKET_CNT_INTERLVD_2ELEM | + PPP_DST_PACK_TIGHT | + PPP_DST_PACK_ALIGN_LSB | + PPP_DST_OUT_SEL_AXI | PPP_DST_BPP_2BYTES; + + ppp_operation_reg |= PPP_OP_DST_CHROMA_H2V1; + outputRGB = FALSE; + pseudoplanr_output = TRUE; + /* horizontally (x direction) sample reduction by 2 */ + dh_slice = 2; + + /* x and width must be even number */ + iBuf->roi.lcd_x = (iBuf->roi.lcd_x / 2) * 2; + iBuf->roi.dst_width = (iBuf->roi.dst_width / 2) * 2; + iBuf->roi.x = (iBuf->roi.x / 2) * 2; + iBuf->roi.width = (iBuf->roi.width / 2) * 2; + break; + + case MDP_BGR_565: + case MDP_RGB_565: + default: + if (iBuf->ibuf_type == MDP_RGB_565) + dst_packPattern = + MDP_GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8); + else + dst_packPattern = + MDP_GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 8); + + ppp_dst_cfg_reg = PPP_DST_C0G_6BIT | + PPP_DST_C1B_5BIT | + PPP_DST_C2R_5BIT | + PPP_DST_PACKET_CNT_INTERLVD_3ELEM | + PPP_DST_PACK_TIGHT | + PPP_DST_PACK_ALIGN_LSB | + PPP_DST_OUT_SEL_AXI | + PPP_DST_BPP_2BYTES | PPP_DST_PLANE_INTERLVD; + break; } - if (regs->src_cfg & PPP_SRC_PLANE_PSEUDOPLNR) { - if (regs->src1 < src_min_ok || regs->src1 > src_max_ok || - regs->src1 + src1_len > src_max_ok) { - DLOG("invalid_src1"); - return 0; + + /* source config */ + switch (iBuf->mdpImg.imgType) { + case MDP_RGB_888: + inpBpp = 3; + /* + * 565 = 2bytes + * RGB = 3Components + * RGB interleaved + */ + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | PPP_SRC_BPP_INTERLVD_3BYTES | + PPP_SRC_INTERLVD_3COMPONENTS | PPP_SRC_UNPACK_TIGHT | + PPP_SRC_UNPACK_ALIGN_LSB | + PPP_SRC_FETCH_PLANES_INTERLVD; + + packPattern = MDP_GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8); + + ppp_operation_reg |= PPP_OP_COLOR_SPACE_RGB | + PPP_OP_SRC_CHROMA_RGB | PPP_OP_DST_CHROMA_RGB; + break; + + case MDP_BGRA_8888: + case MDP_RGBA_8888: + case MDP_ARGB_8888: + perPixelAlpha = TRUE; + case MDP_XRGB_8888: + case MDP_RGBX_8888: + inpBpp = 4; + /* + * 8888 = 4bytes + * ARGB = 4Components + * ARGB interleaved + */ + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | PPP_SRC_C3A_8BITS | + PPP_SRC_C3_ALPHA_EN | PPP_SRC_BPP_INTERLVD_4BYTES | + PPP_SRC_INTERLVD_4COMPONENTS | PPP_SRC_UNPACK_TIGHT | + PPP_SRC_UNPACK_ALIGN_LSB | + PPP_SRC_FETCH_PLANES_INTERLVD; + + if (iBuf->mdpImg.imgType == MDP_BGRA_8888) + packPattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, + 8); + else if (iBuf->mdpImg.imgType == MDP_RGBA_8888 || + iBuf->mdpImg.imgType == MDP_RGBX_8888) + packPattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_B, CLR_G, CLR_R, + 8); + else if (iBuf->ibuf_type == MDP_XRGB_8888) + packPattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, + 8); + else + packPattern = + MDP_GET_PACK_PATTERN(CLR_ALPHA, CLR_R, CLR_G, CLR_B, + 8); + + ppp_operation_reg |= PPP_OP_COLOR_SPACE_RGB | + PPP_OP_SRC_CHROMA_RGB | PPP_OP_DST_CHROMA_RGB; + break; + + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + inpBpp = 1; + src1 = (uint8 *) iBuf->mdpImg.cbcr_addr; + + /* + * CbCr = 2bytes + * CbCr = 2Components + * Y+CbCr + */ + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | PPP_SRC_BPP_INTERLVD_2BYTES | + PPP_SRC_INTERLVD_2COMPONENTS | PPP_SRC_UNPACK_TIGHT | + PPP_SRC_UNPACK_ALIGN_LSB | + PPP_SRC_FETCH_PLANES_PSEUDOPLNR; + + if (iBuf->mdpImg.imgType == MDP_Y_CRCB_H2V2) + packPattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8); + else + packPattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8); + + ppp_operation_reg |= PPP_OP_COLOR_SPACE_YCBCR | + PPP_OP_SRC_CHROMA_420 | + PPP_OP_SRC_CHROMA_COSITE | + PPP_OP_DST_CHROMA_RGB | PPP_OP_DST_CHROMA_COSITE; + + inputRGB = FALSE; + sh_slice = sv_slice = 2; + break; + + case MDP_YCRYCB_H2V1: + inpBpp = 2; + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | + PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | + PPP_SRC_C3A_8BITS | + PPP_SRC_BPP_INTERLVD_2BYTES | + PPP_SRC_INTERLVD_4COMPONENTS | + PPP_SRC_UNPACK_TIGHT | PPP_SRC_UNPACK_ALIGN_LSB; + + packPattern = + MDP_GET_PACK_PATTERN(CLR_Y, CLR_CR, CLR_Y, CLR_CB, 8); + + ppp_operation_reg |= PPP_OP_SRC_CHROMA_H2V1 | + PPP_OP_SRC_CHROMA_COSITE | PPP_OP_DST_CHROMA_COSITE; + + /* + * if it's TV-Out/MDP_YCRYCB_H2V1, let's go through the + * preloaded inverse gamma setting of 2.2 since they're + * symetric when the content is non-linear + * ppp_lookUp_enable = TRUE; + */ + + /* x and width must be even number */ + iBuf->roi.lcd_x = (iBuf->roi.lcd_x / 2) * 2; + iBuf->roi.dst_width = (iBuf->roi.dst_width / 2) * 2; + iBuf->roi.x = (iBuf->roi.x / 2) * 2; + iBuf->roi.width = (iBuf->roi.width / 2) * 2; + + inputRGB = FALSE; + sh_slice = 2; + break; + + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + inpBpp = 1; + src1 = (uint8 *) iBuf->mdpImg.cbcr_addr; + + ppp_src_cfg_reg = PPP_SRC_C2R_8BITS | + PPP_SRC_C0G_8BITS | + PPP_SRC_C1B_8BITS | + PPP_SRC_C3A_8BITS | + PPP_SRC_BPP_INTERLVD_2BYTES | + PPP_SRC_INTERLVD_2COMPONENTS | + PPP_SRC_UNPACK_TIGHT | + PPP_SRC_UNPACK_ALIGN_LSB | PPP_SRC_FETCH_PLANES_PSEUDOPLNR; + + if (iBuf->mdpImg.imgType == MDP_Y_CBCR_H2V1) + packPattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CB, CLR_CR, 8); + else + packPattern = + MDP_GET_PACK_PATTERN(0, 0, CLR_CR, CLR_CB, 8); + + ppp_operation_reg |= PPP_OP_SRC_CHROMA_H2V1 | + PPP_OP_SRC_CHROMA_COSITE | PPP_OP_DST_CHROMA_COSITE; + inputRGB = FALSE; + sh_slice = 2; + break; + + case MDP_BGR_565: + case MDP_RGB_565: + default: + inpBpp = 2; + /* + * 565 = 2bytes + * RGB = 3Components + * RGB interleaved + */ + ppp_src_cfg_reg = PPP_SRC_C2R_5BITS | PPP_SRC_C0G_6BITS | + PPP_SRC_C1B_5BITS | PPP_SRC_BPP_INTERLVD_2BYTES | + PPP_SRC_INTERLVD_3COMPONENTS | PPP_SRC_UNPACK_TIGHT | + PPP_SRC_UNPACK_ALIGN_LSB | + PPP_SRC_FETCH_PLANES_INTERLVD; + + if (iBuf->mdpImg.imgType == MDP_RGB_565) + packPattern = + MDP_GET_PACK_PATTERN(0, CLR_R, CLR_G, CLR_B, 8); + else + packPattern = + MDP_GET_PACK_PATTERN(0, CLR_B, CLR_G, CLR_R, 8); + + ppp_operation_reg |= PPP_OP_COLOR_SPACE_RGB | + PPP_OP_SRC_CHROMA_RGB | PPP_OP_DST_CHROMA_RGB; + break; + + } + + if (pseudoplanr_output) + ppp_dst_cfg_reg |= PPP_DST_PLANE_PSEUDOPLN; + + /* YCbCr to RGB color conversion flag */ + if ((!inputRGB) && (outputRGB)) { + ppp_operation_reg |= PPP_OP_CONVERT_YCBCR2RGB | + PPP_OP_CONVERT_ON; + + /* + * primary/secondary is sort of misleading term...but + * in mdp2.2/3.0 we only use primary matrix (forward/rev) + * in mdp3.1 we use set1(prim) and set2(secd) + */ +#ifdef CONFIG_FB_MSM_MDP31 + ppp_operation_reg |= PPP_OP_CONVERT_MATRIX_SECONDARY | + PPP_OP_DST_RGB; + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0240, 0); +#endif + + if (ppp_lookUp_enable) { + ppp_operation_reg |= PPP_OP_LUT_C0_ON | + PPP_OP_LUT_C1_ON | PPP_OP_LUT_C2_ON; } } - if (regs->dst0 < dst_min_ok || regs->dst0 > dst_max_ok || - regs->dst0 + dst0_len > dst_max_ok) { - DLOG("invalid_dst"); - return 0; + /* RGB to YCbCr color conversion flag */ + if ((inputRGB) && (!outputRGB)) { + ppp_operation_reg |= PPP_OP_CONVERT_RGB2YCBCR | + PPP_OP_CONVERT_ON; + +#ifdef CONFIG_FB_MSM_MDP31 + ppp_operation_reg |= PPP_OP_CONVERT_MATRIX_PRIMARY | + PPP_OP_DST_YCBCR; + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0240, 0x1e); +#endif + + if (ppp_lookUp_enable) { + ppp_operation_reg |= PPP_OP_LUT_C0_ON | + PPP_OP_LUT_C1_ON | PPP_OP_LUT_C2_ON; + } + } + /* YCbCr to YCbCr color conversion flag */ + if ((!inputRGB) && (!outputRGB)) { + if ((ppp_lookUp_enable) && + (iBuf->mdpImg.imgType != iBuf->ibuf_type)) { + ppp_operation_reg |= PPP_OP_LUT_C0_ON; + } + } + + ppp_src_cfg_reg |= (iBuf->roi.x % 2) ? PPP_SRC_BPP_ROI_ODD_X : 0; + ppp_src_cfg_reg |= (iBuf->roi.y % 2) ? PPP_SRC_BPP_ROI_ODD_Y : 0; + + if (req->flags & MDP_DEINTERLACE) + ppp_operation_reg |= PPP_OP_DEINT_EN; + + /* Dither at DMA side only since iBuf format is RGB888 */ + if (iBuf->mdpImg.mdpOp & MDPOP_DITHER) + ppp_operation_reg |= PPP_OP_DITHER_EN; + + if (iBuf->mdpImg.mdpOp & MDPOP_ROTATION) { + ppp_operation_reg |= PPP_OP_ROT_ON; + + if (iBuf->mdpImg.mdpOp & MDPOP_ROT90) { + ppp_operation_reg |= PPP_OP_ROT_90; + } + if (iBuf->mdpImg.mdpOp & MDPOP_LR) { + ppp_operation_reg |= PPP_OP_FLIP_LR; + } + if (iBuf->mdpImg.mdpOp & MDPOP_UD) { + ppp_operation_reg |= PPP_OP_FLIP_UD; + } } - if (regs->dst_cfg & PPP_SRC_PLANE_PSEUDOPLNR) { - if (regs->dst1 < dst_min_ok || regs->dst1 > dst_max_ok || - regs->dst1 + dst1_len > dst_max_ok) { - DLOG("invalid_dst1"); - return 0; + + src0_ystride = src_width * inpBpp; + dest0_ystride = iBuf->ibuf_width * iBuf->bpp; + + /* no need to care about rotation since it's the real-XY. */ + dst_roi_width = iBuf->roi.dst_width; + dst_roi_height = iBuf->roi.dst_height; + + src0 = (uint8 *) iBuf->mdpImg.bmy_addr; + dest0 = (uint8 *) iBuf->buf; + + /* Jumping from Y-Plane to Chroma Plane */ + dest1 = mdp_get_chroma_addr(iBuf); + + /* first pixel addr calculation */ + mdp_adjust_start_addr(&src0, &src1, sv_slice, sh_slice, iBuf->roi.x, + iBuf->roi.y, src_width, src_height, inpBpp, iBuf, + 0); + mdp_adjust_start_addr(&dest0, &dest1, dv_slice, dh_slice, + iBuf->roi.lcd_x, iBuf->roi.lcd_y, + iBuf->ibuf_width, iBuf->ibuf_height, iBuf->bpp, + iBuf, 2); + + /* set scale operation */ + mdp_set_scale(iBuf, dst_roi_width, dst_roi_height, + inputRGB, outputRGB, &ppp_operation_reg); + + /* + * setting background source for blending + */ + mdp_set_blend_attr(iBuf, &alpha, &tpVal, perPixelAlpha, + &ppp_operation_reg); + + if (ppp_operation_reg & PPP_OP_BLEND_ON) { + mdp_ppp_setbg(iBuf); + + if (iBuf->ibuf_type == MDP_YCRYCB_H2V1) { + ppp_operation_reg |= PPP_OP_BG_CHROMA_H2V1; + + if (iBuf->mdpImg.mdpOp & MDPOP_TRANSP) { + tpVal = mdp_conv_matx_rgb2yuv(tpVal, + (uint16 *) & + mdp_ccs_rgb2yuv, + &mdp_plv[0], NULL); + } } } - return 1; + + /* + * 0x0004: enable dbg bus + * 0x0100: "don't care" Edge Condit until scaling is on + * 0x0104: xrc tile x&y size u7.6 format = 7bit.6bit + * 0x0108: src pixel size + * 0x010c: component plane 0 starting address + * 0x011c: component plane 0 ystride + * 0x0124: PPP source config register + * 0x0128: unpacked pattern from lsb to msb (eg. RGB->BGR) + */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0108, (iBuf->roi.height << 16 | + iBuf->roi.width)); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x010c, src0); /* comp.plane 0 */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0110, src1); /* comp.plane 1 */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x011c, + (src0_ystride << 16 | src0_ystride)); + + /* setup for rgb 565 */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0124, ppp_src_cfg_reg); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0128, packPattern); + /* + * 0x0138: PPP destination operation register + * 0x014c: constant_alpha|transparent_color + * 0x0150: PPP destination config register + * 0x0154: PPP packing pattern + */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0138, ppp_operation_reg); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x014c, alpha << 24 | (tpVal & + 0xffffff)); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0150, ppp_dst_cfg_reg); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0154, dst_packPattern); + + /* + * 0x0164: ROI height and width + * 0x0168: Component Plane 0 starting addr + * 0x016c: Component Plane 1 starting addr + * 0x0178: Component Plane 1/0 y stride + */ + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0164, + (dst_roi_height << 16 | dst_roi_width)); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0168, dest0); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x016c, dest1); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0178, + (dest0_ystride << 16 | dest0_ystride)); + + flush_imgs(req, inpBpp, iBuf->bpp, p_src_file, p_dst_file); +#ifdef CONFIG_FB_MSM_MDP31 + MDP_OUTP(MDP_BASE + 0x00100, 0xFF00); +#endif + mdp_pipe_kickoff(MDP_PPP_TERM, mfd); } +static int mdp_ppp_verify_req(struct mdp_blit_req *req) +{ + u32 src_width, src_height, dst_width, dst_height; + + if (req == NULL) { + printk(KERN_ERR "\n%s(): Error in Line %u", __func__, + __LINE__); + return -1; + } + + if (MDP_IS_IMGTYPE_BAD(req->src.format) || + MDP_IS_IMGTYPE_BAD(req->dst.format)) { + printk(KERN_ERR "\n%s(): Error in Line %u", __func__, + __LINE__); + return -1; + } + + if ((req->src.width == 0) || (req->src.height == 0) || + (req->src_rect.w == 0) || (req->src_rect.h == 0) || + (req->dst.width == 0) || (req->dst.height == 0) || + (req->dst_rect.w == 0) || (req->dst_rect.h == 0)) { + printk(KERN_ERR "\n%s(): Error in Line %u", __func__, + __LINE__); + + return -1; + } + + if (((req->src_rect.x + req->src_rect.w) > req->src.width) || + ((req->src_rect.y + req->src_rect.h) > req->src.height)) { + printk(KERN_ERR "\n%s(): Error in Line %u", __func__, + __LINE__); + return -1; + } + + if (((req->dst_rect.x + req->dst_rect.w) > req->dst.width) || + ((req->dst_rect.y + req->dst_rect.h) > req->dst.height)) { + printk(KERN_ERR "\n%s(): Error in Line %u", __func__, + __LINE__); + return -1; + } + + /* + * scaling range check + */ + src_width = req->src_rect.w; + src_height = req->src_rect.h; + + if (req->flags & MDP_ROT_90) { + dst_width = req->dst_rect.h; + dst_height = req->dst_rect.w; + } else { + dst_width = req->dst_rect.w; + dst_height = req->dst_rect.h; + } + + switch (req->dst.format) { + case MDP_Y_CRCB_H2V2: + case MDP_Y_CBCR_H2V2: + src_width = (src_width / 2) * 2; + src_height = (src_height / 2) * 2; + dst_width = (src_width / 2) * 2; + dst_height = (src_height / 2) * 2; + break; + + case MDP_Y_CRCB_H2V1: + case MDP_Y_CBCR_H2V1: + case MDP_YCRYCB_H2V1: + src_width = (src_width / 2) * 2; + dst_width = (src_width / 2) * 2; + break; + + default: + break; + } + + if (((MDP_SCALE_Q_FACTOR * dst_width) / src_width > + MDP_MAX_X_SCALE_FACTOR) + || ((MDP_SCALE_Q_FACTOR * dst_width) / src_width < + MDP_MIN_X_SCALE_FACTOR)) { + printk(KERN_ERR "\n%s(): Error in Line %u", __func__, + __LINE__); + return -1; + } + + if (((MDP_SCALE_Q_FACTOR * dst_height) / src_height > + MDP_MAX_Y_SCALE_FACTOR) + || ((MDP_SCALE_Q_FACTOR * dst_height) / src_height < + MDP_MIN_Y_SCALE_FACTOR)) { + printk(KERN_ERR "\n%s(): Error in Line %u", __func__, + __LINE__); + return -1; + } + return 0; +} -static void flush_imgs(struct mdp_blit_req *req, struct mdp_regs *regs, - struct file *src_file, struct file *dst_file) +int get_gem_img(struct mdp_img *img, unsigned long *start, unsigned long *len) { + /* Set len to zero to appropriately error out if + kgsl_gem_obj_addr fails */ + + *len = 0; + return kgsl_gem_obj_addr(img->memory_id, (int) img->priv, start, len); } -static void get_chroma_addr(struct mdp_img *img, struct mdp_rect *rect, - uint32_t base, uint32_t bpp, uint32_t cfg, - uint32_t *addr, uint32_t *ystride) +int get_img(struct mdp_img *img, struct fb_info *info, unsigned long *start, + unsigned long *len, struct file **pp_file) { - uint32_t compress_v = Y_TO_CRCB_RATIO(img->format); - uint32_t compress_h = 2; - uint32_t offset; - - if (IS_PSEUDOPLNR(img->format)) { - offset = (rect->x / compress_h) * compress_h; - offset += rect->y == 0 ? 0 : - ((rect->y + 1) / compress_v) * img->width; - *addr = base + (img->width * img->height * bpp); - *addr += offset * bpp; - *ystride |= *ystride << 16; + int put_needed, ret = 0; + struct file *file; +#ifdef CONFIG_ANDROID_PMEM + unsigned long vstart; +#endif + +#ifdef CONFIG_ANDROID_PMEM + if (!get_pmem_file(img->memory_id, start, &vstart, len, pp_file)) + return 0; +#endif + file = fget_light(img->memory_id, &put_needed); + if (file == NULL) + return -1; + + if (MAJOR(file->f_dentry->d_inode->i_rdev) == FB_MAJOR) { + *start = info->fix.smem_start; + *len = info->fix.smem_len; + *pp_file = file; } else { - *addr = 0; + ret = -1; + fput_light(file, put_needed); } + return ret; } -static int send_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, - struct mdp_regs *regs, struct file *src_file, - struct file *dst_file) + +void put_img(struct file *p_src_file) { - mdp_writel(mdp, 1, 0x060); - mdp_writel(mdp, regs->src_rect, PPP_ADDR_SRC_ROI); - mdp_writel(mdp, regs->src0, PPP_ADDR_SRC0); - mdp_writel(mdp, regs->src1, PPP_ADDR_SRC1); - mdp_writel(mdp, regs->src_ystride, PPP_ADDR_SRC_YSTRIDE); - mdp_writel(mdp, regs->src_cfg, PPP_ADDR_SRC_CFG); - mdp_writel(mdp, regs->src_pack, PPP_ADDR_SRC_PACK_PATTERN); - - mdp_writel(mdp, regs->op, PPP_ADDR_OPERATION); - mdp_writel(mdp, regs->phasex_init, PPP_ADDR_PHASEX_INIT); - mdp_writel(mdp, regs->phasey_init, PPP_ADDR_PHASEY_INIT); - mdp_writel(mdp, regs->phasex_step, PPP_ADDR_PHASEX_STEP); - mdp_writel(mdp, regs->phasey_step, PPP_ADDR_PHASEY_STEP); - - mdp_writel(mdp, (req->alpha << 24) | (req->transp_mask & 0xffffff), - PPP_ADDR_ALPHA_TRANSP); - - mdp_writel(mdp, regs->dst_cfg, PPP_ADDR_DST_CFG); - mdp_writel(mdp, regs->dst_pack, PPP_ADDR_DST_PACK_PATTERN); - mdp_writel(mdp, regs->dst_rect, PPP_ADDR_DST_ROI); - mdp_writel(mdp, regs->dst0, PPP_ADDR_DST0); - mdp_writel(mdp, regs->dst1, PPP_ADDR_DST1); - mdp_writel(mdp, regs->dst_ystride, PPP_ADDR_DST_YSTRIDE); - - mdp_writel(mdp, regs->edge, PPP_ADDR_EDGE); - if (regs->op & PPP_OP_BLEND_ON) { - mdp_writel(mdp, regs->dst0, PPP_ADDR_BG0); - mdp_writel(mdp, regs->dst1, PPP_ADDR_BG1); - mdp_writel(mdp, regs->dst_ystride, PPP_ADDR_BG_YSTRIDE); - mdp_writel(mdp, src_img_cfg[req->dst.format], PPP_ADDR_BG_CFG); - mdp_writel(mdp, pack_pattern[req->dst.format], - PPP_ADDR_BG_PACK_PATTERN); - } - flush_imgs(req, regs, src_file, dst_file); - mdp_writel(mdp, 0x1000, MDP_DISPLAY0_START); - return 0; +#ifdef CONFIG_ANDROID_PMEM + if (p_src_file) + put_pmem_file(p_src_file); +#endif } -int mdp_ppp_blit(const struct mdp_info *mdp, struct mdp_blit_req *req, - struct file *src_file, unsigned long src_start, unsigned long src_len, - struct file *dst_file, unsigned long dst_start, unsigned long dst_len) -{ - struct mdp_regs regs = {0}; - if (unlikely(req->src.format >= MDP_IMGTYPE_LIMIT || - req->dst.format >= MDP_IMGTYPE_LIMIT)) { - printk(KERN_ERR "mpd_ppp: img is of wrong format\n"); - return -EINVAL; +int mdp_ppp_blit(struct fb_info *info, struct mdp_blit_req *req) +{ + unsigned long src_start, dst_start; + unsigned long src_len = 0; + unsigned long dst_len = 0; + MDPIBUF iBuf; + u32 dst_width, dst_height; + struct file *p_src_file = 0 , *p_dst_file = 0; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + + if (req->dst.format == MDP_FB_FORMAT) + req->dst.format = mfd->fb_imgType; + if (req->src.format == MDP_FB_FORMAT) + req->src.format = mfd->fb_imgType; + if (req->flags & MDP_BLIT_SRC_GEM) + get_gem_img(&req->src, &src_start, &src_len); + else + get_img(&req->src, info, &src_start, &src_len, &p_src_file); + if (src_len == 0) { + printk(KERN_ERR "mdp_ppp: could not retrieve image from " + "memory\n"); + return -1; + } + if (req->flags & MDP_BLIT_DST_GEM) + get_gem_img(&req->dst, &dst_start, &dst_len); + else + get_img(&req->dst, info, &dst_start, &dst_len, &p_dst_file); + if (dst_len == 0) { + put_img(p_src_file); + printk(KERN_ERR "mdp_ppp: could not retrieve image from " + "memory\n"); + return -1; + } + if (mdp_ppp_verify_req(req)) { + printk(KERN_ERR "mdp_ppp: invalid image!\n"); + put_img(p_src_file); + put_img(p_dst_file); + return -1; } - if (unlikely(req->src_rect.x > req->src.width || - req->src_rect.y > req->src.height || - req->dst_rect.x > req->dst.width || - req->dst_rect.y > req->dst.height)) { - printk(KERN_ERR "mpd_ppp: img rect is outside of img!\n"); - return -EINVAL; + iBuf.ibuf_width = req->dst.width; + iBuf.ibuf_height = req->dst.height; + iBuf.bpp = bytes_per_pixel[req->dst.format]; + + iBuf.ibuf_type = req->dst.format; + iBuf.buf = (uint8 *) dst_start; + iBuf.buf += req->dst.offset; + + iBuf.roi.lcd_x = req->dst_rect.x; + iBuf.roi.lcd_y = req->dst_rect.y; + iBuf.roi.dst_width = req->dst_rect.w; + iBuf.roi.dst_height = req->dst_rect.h; + + iBuf.roi.x = req->src_rect.x; + iBuf.roi.width = req->src_rect.w; + iBuf.roi.y = req->src_rect.y; + iBuf.roi.height = req->src_rect.h; + + iBuf.mdpImg.width = req->src.width; + iBuf.mdpImg.imgType = req->src.format; + + iBuf.mdpImg.bmy_addr = (uint32 *) (src_start + req->src.offset); + iBuf.mdpImg.cbcr_addr = + (uint32 *) ((uint32) iBuf.mdpImg.bmy_addr + + req->src.width * req->src.height); + + iBuf.mdpImg.mdpOp = MDPOP_NOP; + + /* blending check */ + if (req->transp_mask != MDP_TRANSP_NOP) { + iBuf.mdpImg.mdpOp |= MDPOP_TRANSP; + iBuf.mdpImg.tpVal = req->transp_mask; + iBuf.mdpImg.tpVal = mdp_calc_tpval(&iBuf.mdpImg); + } else { + iBuf.mdpImg.tpVal = 0; } - /* set the src image configuration */ - regs.src_cfg = src_img_cfg[req->src.format]; - regs.src_cfg |= (req->src_rect.x & 0x1) ? PPP_SRC_BPP_ROI_ODD_X : 0; - regs.src_cfg |= (req->src_rect.y & 0x1) ? PPP_SRC_BPP_ROI_ODD_Y : 0; - regs.src_rect = (req->src_rect.h << 16) | req->src_rect.w; - regs.src_pack = pack_pattern[req->src.format]; - - /* set the dest image configuration */ - regs.dst_cfg = dst_img_cfg[req->dst.format] | PPP_DST_OUT_SEL_AXI; - regs.dst_rect = (req->dst_rect.h << 16) | req->dst_rect.w; - regs.dst_pack = pack_pattern[req->dst.format]; - - /* set src, bpp, start pixel and ystride */ - regs.src_bpp = bytes_per_pixel[req->src.format]; - regs.src0 = src_start + req->src.offset; - regs.src_ystride = req->src.width * regs.src_bpp; - get_chroma_addr(&req->src, &req->src_rect, regs.src0, regs.src_bpp, - regs.src_cfg, ®s.src1, ®s.src_ystride); - regs.src0 += (req->src_rect.x + (req->src_rect.y * req->src.width)) * - regs.src_bpp; - - /* set dst, bpp, start pixel and ystride */ - regs.dst_bpp = bytes_per_pixel[req->dst.format]; - regs.dst0 = dst_start + req->dst.offset; - regs.dst_ystride = req->dst.width * regs.dst_bpp; - get_chroma_addr(&req->dst, &req->dst_rect, regs.dst0, regs.dst_bpp, - regs.dst_cfg, ®s.dst1, ®s.dst_ystride); - regs.dst0 += (req->dst_rect.x + (req->dst_rect.y * req->dst.width)) * - regs.dst_bpp; - - if (!valid_src_dst(src_start, src_len, dst_start, dst_len, req, - ®s)) { - printk(KERN_ERR "mpd_ppp: final src or dst location is " - "invalid, are you trying to make an image too large " - "or to place it outside the screen?\n"); - return -EINVAL; + req->alpha &= 0xff; + if (req->alpha < MDP_ALPHA_NOP) { + iBuf.mdpImg.mdpOp |= MDPOP_ALPHAB; + iBuf.mdpImg.alpha = req->alpha; + } else { + iBuf.mdpImg.alpha = 0xff; } - /* set up operation register */ - regs.op = 0; - blit_rotate(req, ®s); - blit_convert(req, ®s); + /* rotation check */ + if (req->flags & MDP_FLIP_LR) + iBuf.mdpImg.mdpOp |= MDPOP_LR; + if (req->flags & MDP_FLIP_UD) + iBuf.mdpImg.mdpOp |= MDPOP_UD; + if (req->flags & MDP_ROT_90) + iBuf.mdpImg.mdpOp |= MDPOP_ROT90; if (req->flags & MDP_DITHER) - regs.op |= PPP_OP_DITHER_EN; - blit_blend(req, ®s); - if (blit_scale(mdp, req, ®s)) { - printk(KERN_ERR "mpd_ppp: error computing scale for img.\n"); + iBuf.mdpImg.mdpOp |= MDPOP_DITHER; + + if (req->flags & MDP_BLEND_FG_PREMULT) { +#ifdef CONFIG_FB_MSM_MDP31 + iBuf.mdpImg.mdpOp |= MDPOP_FG_PM_ALPHA; +#else + put_img(p_src_file); + put_img(p_dst_file); return -EINVAL; +#endif } - blit_blur(mdp, req, ®s); - regs.op |= dst_op_chroma[req->dst.format] | - src_op_chroma[req->src.format]; - /* if the image is YCRYCB, the x and w must be even */ - if (unlikely(req->src.format == MDP_YCRYCB_H2V1)) { - req->src_rect.x = req->src_rect.x & (~0x1); - req->src_rect.w = req->src_rect.w & (~0x1); - req->dst_rect.x = req->dst_rect.x & (~0x1); - req->dst_rect.w = req->dst_rect.w & (~0x1); + if (req->flags & MDP_DEINTERLACE) { +#ifdef CONFIG_FB_MSM_MDP31 + if ((req->src.format != MDP_Y_CBCR_H2V2) && + (req->src.format != MDP_Y_CRCB_H2V2)) { +#endif + put_img(p_src_file); + put_img(p_dst_file); + return -EINVAL; +#ifdef CONFIG_FB_MSM_MDP31 + } +#endif } - if (get_edge_cond(req, ®s)) + + /* scale check */ + if (req->flags & MDP_ROT_90) { + dst_width = req->dst_rect.h; + dst_height = req->dst_rect.w; + } else { + dst_width = req->dst_rect.w; + dst_height = req->dst_rect.h; + } + + if ((iBuf.roi.width != dst_width) || (iBuf.roi.height != dst_height)) + iBuf.mdpImg.mdpOp |= MDPOP_ASCALE; + + if (req->flags & MDP_BLUR) { +#ifdef CONFIG_FB_MSM_MDP31 + if (req->flags & MDP_SHARPENING) + printk(KERN_WARNING + "mdp: MDP_SHARPENING is set with MDP_BLUR!\n"); + req->flags |= MDP_SHARPENING; + req->sharpening_strength = -127; +#else + iBuf.mdpImg.mdpOp |= MDPOP_ASCALE | MDPOP_BLUR; + +#endif + } + + if (req->flags & MDP_SHARPENING) { +#ifdef CONFIG_FB_MSM_MDP31 + if ((req->sharpening_strength > 127) || + (req->sharpening_strength < -127)) { + printk(KERN_ERR + "%s: sharpening strength out of range\n", + __func__); + put_img(p_src_file); + put_img(p_dst_file); + return -EINVAL; + } + + iBuf.mdpImg.mdpOp |= MDPOP_ASCALE | MDPOP_SHARPENING; + iBuf.mdpImg.sp_value = req->sharpening_strength & 0xff; +#else + put_img(p_src_file); + put_img(p_dst_file); return -EINVAL; +#endif + } + + down(&mdp_ppp_mutex); + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + +#ifndef CONFIG_FB_MSM_MDP22 + mdp_start_ppp(mfd, &iBuf, req, p_src_file, p_dst_file); +#else + /* bg tile fetching HW workaround */ + if (((iBuf.mdpImg.mdpOp & (MDPOP_TRANSP | MDPOP_ALPHAB)) || + (req->src.format == MDP_ARGB_8888) || + (req->src.format == MDP_BGRA_8888) || + (req->src.format == MDP_RGBA_8888)) && + (iBuf.mdpImg.mdpOp & MDPOP_ROT90) && (req->dst_rect.w <= 16)) { + int dst_h, src_w, i; + uint32 mdpOp = iBuf.mdpImg.mdpOp; + + src_w = req->src_rect.w; + dst_h = iBuf.roi.dst_height; + + for (i = 0; i < (req->dst_rect.h / 16); i++) { + /* this tile size */ + iBuf.roi.dst_height = 16; + iBuf.roi.width = + (16 * req->src_rect.w) / req->dst_rect.h; + + /* if it's out of scale range... */ + if (((MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) / + iBuf.roi.width) > MDP_MAX_X_SCALE_FACTOR) + iBuf.roi.width = + (MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) / + MDP_MAX_X_SCALE_FACTOR; + else if (((MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) / + iBuf.roi.width) < MDP_MIN_X_SCALE_FACTOR) + iBuf.roi.width = + (MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) / + MDP_MIN_X_SCALE_FACTOR; + + mdp_start_ppp(mfd, &iBuf, req, p_src_file, p_dst_file); + + /* next tile location */ + iBuf.roi.lcd_y += 16; + iBuf.roi.x += iBuf.roi.width; + + /* this is for a remainder update */ + dst_h -= 16; + src_w -= iBuf.roi.width; + /* restore mdpOp since MDPOP_ASCALE have been cleared */ + iBuf.mdpImg.mdpOp = mdpOp; + } + + if ((dst_h < 0) || (src_w < 0)) + printk + ("msm_fb: mdp_blt_ex() unexpected result! line:%d\n", + __LINE__); + + /* remainder update */ + if ((dst_h > 0) && (src_w > 0)) { + u32 tmp_v; + + iBuf.roi.dst_height = dst_h; + iBuf.roi.width = src_w; + + if (((MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) / + iBuf.roi.width) > MDP_MAX_X_SCALE_FACTOR) { + tmp_v = + (MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) / + MDP_MAX_X_SCALE_FACTOR + + (MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) % + MDP_MAX_X_SCALE_FACTOR ? 1 : 0; + + /* move x location as roi width gets bigger */ + iBuf.roi.x -= tmp_v - iBuf.roi.width; + iBuf.roi.width = tmp_v; + } else + if (((MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) / + iBuf.roi.width) < MDP_MIN_X_SCALE_FACTOR) { + tmp_v = + (MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) / + MDP_MIN_X_SCALE_FACTOR + + (MDP_SCALE_Q_FACTOR * iBuf.roi.dst_height) % + MDP_MIN_X_SCALE_FACTOR ? 1 : 0; + + /* + * we don't move x location for continuity of + * source image + */ + iBuf.roi.width = tmp_v; + } + + mdp_start_ppp(mfd, &iBuf, req, p_src_file, p_dst_file); + } + } else { + mdp_start_ppp(mfd, &iBuf, req, p_src_file, p_dst_file); + } +#endif + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + up(&mdp_ppp_mutex); - send_blit(mdp, req, ®s, src_file, dst_file); + put_img(p_src_file); + put_img(p_dst_file); return 0; } diff --git a/drivers/video/msm/mdp_ppp.h b/drivers/video/msm/mdp_ppp.h new file mode 100644 index 00000000000..e04564347bd --- /dev/null +++ b/drivers/video/msm/mdp_ppp.h @@ -0,0 +1,82 @@ +/* drivers/video/msm/mdp_ppp.h + * + * Copyright (C) 2009 Google Incorporated + * + * 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 _VIDEO_MSM_MDP_PPP_H_ +#define _VIDEO_MSM_MDP_PPP_H_ + +#include + +struct ppp_regs { + uint32_t src0; + uint32_t src1; + uint32_t dst0; + uint32_t dst1; + uint32_t src_cfg; + uint32_t dst_cfg; + uint32_t src_pack; + uint32_t dst_pack; + uint32_t src_rect; + uint32_t dst_rect; + uint32_t src_ystride; + uint32_t dst_ystride; + uint32_t op; + uint32_t src_bpp; + uint32_t dst_bpp; + uint32_t edge; + uint32_t phasex_init; + uint32_t phasey_init; + uint32_t phasex_step; + uint32_t phasey_step; + + uint32_t bg0; + uint32_t bg1; + uint32_t bg_cfg; + uint32_t bg_bpp; + uint32_t bg_pack; + uint32_t bg_ystride; + +#ifdef CONFIG_MSM_MDP31 + uint32_t src_xy; + uint32_t src_img_sz; + uint32_t dst_xy; + uint32_t bg_xy; + uint32_t bg_img_sz; + uint32_t bg_alpha_sel; + + uint32_t scale_cfg; + uint32_t csc_cfg; +#endif +}; + +struct mdp_info; +struct mdp_rect; +struct mdp_blit_req; + +void mdp_ppp_init_scale(const struct mdp_info *mdp); +int mdp_ppp_cfg_scale(const struct mdp_info *mdp, struct ppp_regs *regs, + struct mdp_rect *src_rect, struct mdp_rect *dst_rect, + uint32_t src_format, uint32_t dst_format); +int mdp_ppp_load_blur(const struct mdp_info *mdp); + +#ifndef CONFIG_MSM_MDP31 +int mdp_ppp_cfg_edge_cond(struct mdp_blit_req *req, struct ppp_regs *regs); +#else +static inline int mdp_ppp_cfg_edge_cond(struct mdp_blit_req *req, + struct ppp_regs *regs) +{ + return 0; +} +#endif + +#endif /* _VIDEO_MSM_MDP_PPP_H_ */ diff --git a/drivers/video/msm/mdp_ppp22.c b/drivers/video/msm/mdp_ppp22.c new file mode 100644 index 00000000000..9016f0ab952 --- /dev/null +++ b/drivers/video/msm/mdp_ppp22.c @@ -0,0 +1,1091 @@ +/* drivers/video/msm/mdp_ppp22.c + * + * Copyright (C) 2007 Code Aurora Forum. All rights reserved. + * Copyright (C) 2007 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include "mdp_hw.h" +#include "mdp_ppp.h" + +struct mdp_table_entry { + uint32_t reg; + uint32_t val; +}; + +enum { + MDP_DOWNSCALE_PT2TOPT4, + MDP_DOWNSCALE_PT4TOPT6, + MDP_DOWNSCALE_PT6TOPT8, + MDP_DOWNSCALE_PT8TO1, + MDP_DOWNSCALE_MAX, + + /* not technically in the downscale table list */ + MDP_DOWNSCALE_BLUR, +}; + +static int downscale_x_table; +static int downscale_y_table; + +static struct mdp_table_entry mdp_upscale_table[] = { + { 0x5fffc, 0x0 }, + { 0x50200, 0x7fc00000 }, + { 0x5fffc, 0xff80000d }, + { 0x50204, 0x7ec003f9 }, + { 0x5fffc, 0xfec0001c }, + { 0x50208, 0x7d4003f3 }, + { 0x5fffc, 0xfe40002b }, + { 0x5020c, 0x7b8003ed }, + { 0x5fffc, 0xfd80003c }, + { 0x50210, 0x794003e8 }, + { 0x5fffc, 0xfcc0004d }, + { 0x50214, 0x76c003e4 }, + { 0x5fffc, 0xfc40005f }, + { 0x50218, 0x73c003e0 }, + { 0x5fffc, 0xfb800071 }, + { 0x5021c, 0x708003de }, + { 0x5fffc, 0xfac00085 }, + { 0x50220, 0x6d0003db }, + { 0x5fffc, 0xfa000098 }, + { 0x50224, 0x698003d9 }, + { 0x5fffc, 0xf98000ac }, + { 0x50228, 0x654003d8 }, + { 0x5fffc, 0xf8c000c1 }, + { 0x5022c, 0x610003d7 }, + { 0x5fffc, 0xf84000d5 }, + { 0x50230, 0x5c8003d7 }, + { 0x5fffc, 0xf7c000e9 }, + { 0x50234, 0x580003d7 }, + { 0x5fffc, 0xf74000fd }, + { 0x50238, 0x534003d8 }, + { 0x5fffc, 0xf6c00112 }, + { 0x5023c, 0x4e8003d8 }, + { 0x5fffc, 0xf6800126 }, + { 0x50240, 0x494003da }, + { 0x5fffc, 0xf600013a }, + { 0x50244, 0x448003db }, + { 0x5fffc, 0xf600014d }, + { 0x50248, 0x3f4003dd }, + { 0x5fffc, 0xf5c00160 }, + { 0x5024c, 0x3a4003df }, + { 0x5fffc, 0xf5c00172 }, + { 0x50250, 0x354003e1 }, + { 0x5fffc, 0xf5c00184 }, + { 0x50254, 0x304003e3 }, + { 0x5fffc, 0xf6000195 }, + { 0x50258, 0x2b0003e6 }, + { 0x5fffc, 0xf64001a6 }, + { 0x5025c, 0x260003e8 }, + { 0x5fffc, 0xf6c001b4 }, + { 0x50260, 0x214003eb }, + { 0x5fffc, 0xf78001c2 }, + { 0x50264, 0x1c4003ee }, + { 0x5fffc, 0xf80001cf }, + { 0x50268, 0x17c003f1 }, + { 0x5fffc, 0xf90001db }, + { 0x5026c, 0x134003f3 }, + { 0x5fffc, 0xfa0001e5 }, + { 0x50270, 0xf0003f6 }, + { 0x5fffc, 0xfb4001ee }, + { 0x50274, 0xac003f9 }, + { 0x5fffc, 0xfcc001f5 }, + { 0x50278, 0x70003fb }, + { 0x5fffc, 0xfe4001fb }, + { 0x5027c, 0x34003fe }, +}; + +static struct mdp_table_entry mdp_downscale_x_table_PT2TOPT4[] = { + { 0x5fffc, 0x740008c }, + { 0x50280, 0x33800088 }, + { 0x5fffc, 0x800008e }, + { 0x50284, 0x33400084 }, + { 0x5fffc, 0x8400092 }, + { 0x50288, 0x33000080 }, + { 0x5fffc, 0x9000094 }, + { 0x5028c, 0x3300007b }, + { 0x5fffc, 0x9c00098 }, + { 0x50290, 0x32400077 }, + { 0x5fffc, 0xa40009b }, + { 0x50294, 0x32000073 }, + { 0x5fffc, 0xb00009d }, + { 0x50298, 0x31c0006f }, + { 0x5fffc, 0xbc000a0 }, + { 0x5029c, 0x3140006b }, + { 0x5fffc, 0xc8000a2 }, + { 0x502a0, 0x31000067 }, + { 0x5fffc, 0xd8000a5 }, + { 0x502a4, 0x30800062 }, + { 0x5fffc, 0xe4000a8 }, + { 0x502a8, 0x2fc0005f }, + { 0x5fffc, 0xec000aa }, + { 0x502ac, 0x2fc0005b }, + { 0x5fffc, 0xf8000ad }, + { 0x502b0, 0x2f400057 }, + { 0x5fffc, 0x108000b0 }, + { 0x502b4, 0x2e400054 }, + { 0x5fffc, 0x114000b2 }, + { 0x502b8, 0x2e000050 }, + { 0x5fffc, 0x124000b4 }, + { 0x502bc, 0x2d80004c }, + { 0x5fffc, 0x130000b6 }, + { 0x502c0, 0x2d000049 }, + { 0x5fffc, 0x140000b8 }, + { 0x502c4, 0x2c800045 }, + { 0x5fffc, 0x150000b9 }, + { 0x502c8, 0x2c000042 }, + { 0x5fffc, 0x15c000bd }, + { 0x502cc, 0x2b40003e }, + { 0x5fffc, 0x16c000bf }, + { 0x502d0, 0x2a80003b }, + { 0x5fffc, 0x17c000bf }, + { 0x502d4, 0x2a000039 }, + { 0x5fffc, 0x188000c2 }, + { 0x502d8, 0x29400036 }, + { 0x5fffc, 0x19c000c4 }, + { 0x502dc, 0x28800032 }, + { 0x5fffc, 0x1ac000c5 }, + { 0x502e0, 0x2800002f }, + { 0x5fffc, 0x1bc000c7 }, + { 0x502e4, 0x2740002c }, + { 0x5fffc, 0x1cc000c8 }, + { 0x502e8, 0x26c00029 }, + { 0x5fffc, 0x1dc000c9 }, + { 0x502ec, 0x26000027 }, + { 0x5fffc, 0x1ec000cc }, + { 0x502f0, 0x25000024 }, + { 0x5fffc, 0x200000cc }, + { 0x502f4, 0x24800021 }, + { 0x5fffc, 0x210000cd }, + { 0x502f8, 0x23800020 }, + { 0x5fffc, 0x220000ce }, + { 0x502fc, 0x2300001d }, +}; + +static struct mdp_table_entry mdp_downscale_x_table_PT4TOPT6[] = { + { 0x5fffc, 0x740008c }, + { 0x50280, 0x33800088 }, + { 0x5fffc, 0x800008e }, + { 0x50284, 0x33400084 }, + { 0x5fffc, 0x8400092 }, + { 0x50288, 0x33000080 }, + { 0x5fffc, 0x9000094 }, + { 0x5028c, 0x3300007b }, + { 0x5fffc, 0x9c00098 }, + { 0x50290, 0x32400077 }, + { 0x5fffc, 0xa40009b }, + { 0x50294, 0x32000073 }, + { 0x5fffc, 0xb00009d }, + { 0x50298, 0x31c0006f }, + { 0x5fffc, 0xbc000a0 }, + { 0x5029c, 0x3140006b }, + { 0x5fffc, 0xc8000a2 }, + { 0x502a0, 0x31000067 }, + { 0x5fffc, 0xd8000a5 }, + { 0x502a4, 0x30800062 }, + { 0x5fffc, 0xe4000a8 }, + { 0x502a8, 0x2fc0005f }, + { 0x5fffc, 0xec000aa }, + { 0x502ac, 0x2fc0005b }, + { 0x5fffc, 0xf8000ad }, + { 0x502b0, 0x2f400057 }, + { 0x5fffc, 0x108000b0 }, + { 0x502b4, 0x2e400054 }, + { 0x5fffc, 0x114000b2 }, + { 0x502b8, 0x2e000050 }, + { 0x5fffc, 0x124000b4 }, + { 0x502bc, 0x2d80004c }, + { 0x5fffc, 0x130000b6 }, + { 0x502c0, 0x2d000049 }, + { 0x5fffc, 0x140000b8 }, + { 0x502c4, 0x2c800045 }, + { 0x5fffc, 0x150000b9 }, + { 0x502c8, 0x2c000042 }, + { 0x5fffc, 0x15c000bd }, + { 0x502cc, 0x2b40003e }, + { 0x5fffc, 0x16c000bf }, + { 0x502d0, 0x2a80003b }, + { 0x5fffc, 0x17c000bf }, + { 0x502d4, 0x2a000039 }, + { 0x5fffc, 0x188000c2 }, + { 0x502d8, 0x29400036 }, + { 0x5fffc, 0x19c000c4 }, + { 0x502dc, 0x28800032 }, + { 0x5fffc, 0x1ac000c5 }, + { 0x502e0, 0x2800002f }, + { 0x5fffc, 0x1bc000c7 }, + { 0x502e4, 0x2740002c }, + { 0x5fffc, 0x1cc000c8 }, + { 0x502e8, 0x26c00029 }, + { 0x5fffc, 0x1dc000c9 }, + { 0x502ec, 0x26000027 }, + { 0x5fffc, 0x1ec000cc }, + { 0x502f0, 0x25000024 }, + { 0x5fffc, 0x200000cc }, + { 0x502f4, 0x24800021 }, + { 0x5fffc, 0x210000cd }, + { 0x502f8, 0x23800020 }, + { 0x5fffc, 0x220000ce }, + { 0x502fc, 0x2300001d }, +}; + +static struct mdp_table_entry mdp_downscale_x_table_PT6TOPT8[] = { + { 0x5fffc, 0xfe000070 }, + { 0x50280, 0x4bc00068 }, + { 0x5fffc, 0xfe000078 }, + { 0x50284, 0x4bc00060 }, + { 0x5fffc, 0xfe000080 }, + { 0x50288, 0x4b800059 }, + { 0x5fffc, 0xfe000089 }, + { 0x5028c, 0x4b000052 }, + { 0x5fffc, 0xfe400091 }, + { 0x50290, 0x4a80004b }, + { 0x5fffc, 0xfe40009a }, + { 0x50294, 0x4a000044 }, + { 0x5fffc, 0xfe8000a3 }, + { 0x50298, 0x4940003d }, + { 0x5fffc, 0xfec000ac }, + { 0x5029c, 0x48400037 }, + { 0x5fffc, 0xff0000b4 }, + { 0x502a0, 0x47800031 }, + { 0x5fffc, 0xff8000bd }, + { 0x502a4, 0x4640002b }, + { 0x5fffc, 0xc5 }, + { 0x502a8, 0x45000026 }, + { 0x5fffc, 0x8000ce }, + { 0x502ac, 0x43800021 }, + { 0x5fffc, 0x10000d6 }, + { 0x502b0, 0x4240001c }, + { 0x5fffc, 0x18000df }, + { 0x502b4, 0x40800018 }, + { 0x5fffc, 0x24000e6 }, + { 0x502b8, 0x3f000014 }, + { 0x5fffc, 0x30000ee }, + { 0x502bc, 0x3d400010 }, + { 0x5fffc, 0x40000f5 }, + { 0x502c0, 0x3b80000c }, + { 0x5fffc, 0x50000fc }, + { 0x502c4, 0x39800009 }, + { 0x5fffc, 0x6000102 }, + { 0x502c8, 0x37c00006 }, + { 0x5fffc, 0x7000109 }, + { 0x502cc, 0x35800004 }, + { 0x5fffc, 0x840010e }, + { 0x502d0, 0x33800002 }, + { 0x5fffc, 0x9800114 }, + { 0x502d4, 0x31400000 }, + { 0x5fffc, 0xac00119 }, + { 0x502d8, 0x2f4003fe }, + { 0x5fffc, 0xc40011e }, + { 0x502dc, 0x2d0003fc }, + { 0x5fffc, 0xdc00121 }, + { 0x502e0, 0x2b0003fb }, + { 0x5fffc, 0xf400125 }, + { 0x502e4, 0x28c003fa }, + { 0x5fffc, 0x11000128 }, + { 0x502e8, 0x268003f9 }, + { 0x5fffc, 0x12c0012a }, + { 0x502ec, 0x244003f9 }, + { 0x5fffc, 0x1480012c }, + { 0x502f0, 0x224003f8 }, + { 0x5fffc, 0x1640012e }, + { 0x502f4, 0x200003f8 }, + { 0x5fffc, 0x1800012f }, + { 0x502f8, 0x1e0003f8 }, + { 0x5fffc, 0x1a00012f }, + { 0x502fc, 0x1c0003f8 }, +}; + +static struct mdp_table_entry mdp_downscale_x_table_PT8TO1[] = { + { 0x5fffc, 0x0 }, + { 0x50280, 0x7fc00000 }, + { 0x5fffc, 0xff80000d }, + { 0x50284, 0x7ec003f9 }, + { 0x5fffc, 0xfec0001c }, + { 0x50288, 0x7d4003f3 }, + { 0x5fffc, 0xfe40002b }, + { 0x5028c, 0x7b8003ed }, + { 0x5fffc, 0xfd80003c }, + { 0x50290, 0x794003e8 }, + { 0x5fffc, 0xfcc0004d }, + { 0x50294, 0x76c003e4 }, + { 0x5fffc, 0xfc40005f }, + { 0x50298, 0x73c003e0 }, + { 0x5fffc, 0xfb800071 }, + { 0x5029c, 0x708003de }, + { 0x5fffc, 0xfac00085 }, + { 0x502a0, 0x6d0003db }, + { 0x5fffc, 0xfa000098 }, + { 0x502a4, 0x698003d9 }, + { 0x5fffc, 0xf98000ac }, + { 0x502a8, 0x654003d8 }, + { 0x5fffc, 0xf8c000c1 }, + { 0x502ac, 0x610003d7 }, + { 0x5fffc, 0xf84000d5 }, + { 0x502b0, 0x5c8003d7 }, + { 0x5fffc, 0xf7c000e9 }, + { 0x502b4, 0x580003d7 }, + { 0x5fffc, 0xf74000fd }, + { 0x502b8, 0x534003d8 }, + { 0x5fffc, 0xf6c00112 }, + { 0x502bc, 0x4e8003d8 }, + { 0x5fffc, 0xf6800126 }, + { 0x502c0, 0x494003da }, + { 0x5fffc, 0xf600013a }, + { 0x502c4, 0x448003db }, + { 0x5fffc, 0xf600014d }, + { 0x502c8, 0x3f4003dd }, + { 0x5fffc, 0xf5c00160 }, + { 0x502cc, 0x3a4003df }, + { 0x5fffc, 0xf5c00172 }, + { 0x502d0, 0x354003e1 }, + { 0x5fffc, 0xf5c00184 }, + { 0x502d4, 0x304003e3 }, + { 0x5fffc, 0xf6000195 }, + { 0x502d8, 0x2b0003e6 }, + { 0x5fffc, 0xf64001a6 }, + { 0x502dc, 0x260003e8 }, + { 0x5fffc, 0xf6c001b4 }, + { 0x502e0, 0x214003eb }, + { 0x5fffc, 0xf78001c2 }, + { 0x502e4, 0x1c4003ee }, + { 0x5fffc, 0xf80001cf }, + { 0x502e8, 0x17c003f1 }, + { 0x5fffc, 0xf90001db }, + { 0x502ec, 0x134003f3 }, + { 0x5fffc, 0xfa0001e5 }, + { 0x502f0, 0xf0003f6 }, + { 0x5fffc, 0xfb4001ee }, + { 0x502f4, 0xac003f9 }, + { 0x5fffc, 0xfcc001f5 }, + { 0x502f8, 0x70003fb }, + { 0x5fffc, 0xfe4001fb }, + { 0x502fc, 0x34003fe }, +}; + +struct mdp_table_entry *mdp_downscale_x_table[MDP_DOWNSCALE_MAX] = { + [MDP_DOWNSCALE_PT2TOPT4] = mdp_downscale_x_table_PT2TOPT4, + [MDP_DOWNSCALE_PT4TOPT6] = mdp_downscale_x_table_PT4TOPT6, + [MDP_DOWNSCALE_PT6TOPT8] = mdp_downscale_x_table_PT6TOPT8, + [MDP_DOWNSCALE_PT8TO1] = mdp_downscale_x_table_PT8TO1, +}; + +static struct mdp_table_entry mdp_downscale_y_table_PT2TOPT4[] = { + { 0x5fffc, 0x740008c }, + { 0x50300, 0x33800088 }, + { 0x5fffc, 0x800008e }, + { 0x50304, 0x33400084 }, + { 0x5fffc, 0x8400092 }, + { 0x50308, 0x33000080 }, + { 0x5fffc, 0x9000094 }, + { 0x5030c, 0x3300007b }, + { 0x5fffc, 0x9c00098 }, + { 0x50310, 0x32400077 }, + { 0x5fffc, 0xa40009b }, + { 0x50314, 0x32000073 }, + { 0x5fffc, 0xb00009d }, + { 0x50318, 0x31c0006f }, + { 0x5fffc, 0xbc000a0 }, + { 0x5031c, 0x3140006b }, + { 0x5fffc, 0xc8000a2 }, + { 0x50320, 0x31000067 }, + { 0x5fffc, 0xd8000a5 }, + { 0x50324, 0x30800062 }, + { 0x5fffc, 0xe4000a8 }, + { 0x50328, 0x2fc0005f }, + { 0x5fffc, 0xec000aa }, + { 0x5032c, 0x2fc0005b }, + { 0x5fffc, 0xf8000ad }, + { 0x50330, 0x2f400057 }, + { 0x5fffc, 0x108000b0 }, + { 0x50334, 0x2e400054 }, + { 0x5fffc, 0x114000b2 }, + { 0x50338, 0x2e000050 }, + { 0x5fffc, 0x124000b4 }, + { 0x5033c, 0x2d80004c }, + { 0x5fffc, 0x130000b6 }, + { 0x50340, 0x2d000049 }, + { 0x5fffc, 0x140000b8 }, + { 0x50344, 0x2c800045 }, + { 0x5fffc, 0x150000b9 }, + { 0x50348, 0x2c000042 }, + { 0x5fffc, 0x15c000bd }, + { 0x5034c, 0x2b40003e }, + { 0x5fffc, 0x16c000bf }, + { 0x50350, 0x2a80003b }, + { 0x5fffc, 0x17c000bf }, + { 0x50354, 0x2a000039 }, + { 0x5fffc, 0x188000c2 }, + { 0x50358, 0x29400036 }, + { 0x5fffc, 0x19c000c4 }, + { 0x5035c, 0x28800032 }, + { 0x5fffc, 0x1ac000c5 }, + { 0x50360, 0x2800002f }, + { 0x5fffc, 0x1bc000c7 }, + { 0x50364, 0x2740002c }, + { 0x5fffc, 0x1cc000c8 }, + { 0x50368, 0x26c00029 }, + { 0x5fffc, 0x1dc000c9 }, + { 0x5036c, 0x26000027 }, + { 0x5fffc, 0x1ec000cc }, + { 0x50370, 0x25000024 }, + { 0x5fffc, 0x200000cc }, + { 0x50374, 0x24800021 }, + { 0x5fffc, 0x210000cd }, + { 0x50378, 0x23800020 }, + { 0x5fffc, 0x220000ce }, + { 0x5037c, 0x2300001d }, +}; + +static struct mdp_table_entry mdp_downscale_y_table_PT4TOPT6[] = { + { 0x5fffc, 0x740008c }, + { 0x50300, 0x33800088 }, + { 0x5fffc, 0x800008e }, + { 0x50304, 0x33400084 }, + { 0x5fffc, 0x8400092 }, + { 0x50308, 0x33000080 }, + { 0x5fffc, 0x9000094 }, + { 0x5030c, 0x3300007b }, + { 0x5fffc, 0x9c00098 }, + { 0x50310, 0x32400077 }, + { 0x5fffc, 0xa40009b }, + { 0x50314, 0x32000073 }, + { 0x5fffc, 0xb00009d }, + { 0x50318, 0x31c0006f }, + { 0x5fffc, 0xbc000a0 }, + { 0x5031c, 0x3140006b }, + { 0x5fffc, 0xc8000a2 }, + { 0x50320, 0x31000067 }, + { 0x5fffc, 0xd8000a5 }, + { 0x50324, 0x30800062 }, + { 0x5fffc, 0xe4000a8 }, + { 0x50328, 0x2fc0005f }, + { 0x5fffc, 0xec000aa }, + { 0x5032c, 0x2fc0005b }, + { 0x5fffc, 0xf8000ad }, + { 0x50330, 0x2f400057 }, + { 0x5fffc, 0x108000b0 }, + { 0x50334, 0x2e400054 }, + { 0x5fffc, 0x114000b2 }, + { 0x50338, 0x2e000050 }, + { 0x5fffc, 0x124000b4 }, + { 0x5033c, 0x2d80004c }, + { 0x5fffc, 0x130000b6 }, + { 0x50340, 0x2d000049 }, + { 0x5fffc, 0x140000b8 }, + { 0x50344, 0x2c800045 }, + { 0x5fffc, 0x150000b9 }, + { 0x50348, 0x2c000042 }, + { 0x5fffc, 0x15c000bd }, + { 0x5034c, 0x2b40003e }, + { 0x5fffc, 0x16c000bf }, + { 0x50350, 0x2a80003b }, + { 0x5fffc, 0x17c000bf }, + { 0x50354, 0x2a000039 }, + { 0x5fffc, 0x188000c2 }, + { 0x50358, 0x29400036 }, + { 0x5fffc, 0x19c000c4 }, + { 0x5035c, 0x28800032 }, + { 0x5fffc, 0x1ac000c5 }, + { 0x50360, 0x2800002f }, + { 0x5fffc, 0x1bc000c7 }, + { 0x50364, 0x2740002c }, + { 0x5fffc, 0x1cc000c8 }, + { 0x50368, 0x26c00029 }, + { 0x5fffc, 0x1dc000c9 }, + { 0x5036c, 0x26000027 }, + { 0x5fffc, 0x1ec000cc }, + { 0x50370, 0x25000024 }, + { 0x5fffc, 0x200000cc }, + { 0x50374, 0x24800021 }, + { 0x5fffc, 0x210000cd }, + { 0x50378, 0x23800020 }, + { 0x5fffc, 0x220000ce }, + { 0x5037c, 0x2300001d }, +}; + +static struct mdp_table_entry mdp_downscale_y_table_PT6TOPT8[] = { + { 0x5fffc, 0xfe000070 }, + { 0x50300, 0x4bc00068 }, + { 0x5fffc, 0xfe000078 }, + { 0x50304, 0x4bc00060 }, + { 0x5fffc, 0xfe000080 }, + { 0x50308, 0x4b800059 }, + { 0x5fffc, 0xfe000089 }, + { 0x5030c, 0x4b000052 }, + { 0x5fffc, 0xfe400091 }, + { 0x50310, 0x4a80004b }, + { 0x5fffc, 0xfe40009a }, + { 0x50314, 0x4a000044 }, + { 0x5fffc, 0xfe8000a3 }, + { 0x50318, 0x4940003d }, + { 0x5fffc, 0xfec000ac }, + { 0x5031c, 0x48400037 }, + { 0x5fffc, 0xff0000b4 }, + { 0x50320, 0x47800031 }, + { 0x5fffc, 0xff8000bd }, + { 0x50324, 0x4640002b }, + { 0x5fffc, 0xc5 }, + { 0x50328, 0x45000026 }, + { 0x5fffc, 0x8000ce }, + { 0x5032c, 0x43800021 }, + { 0x5fffc, 0x10000d6 }, + { 0x50330, 0x4240001c }, + { 0x5fffc, 0x18000df }, + { 0x50334, 0x40800018 }, + { 0x5fffc, 0x24000e6 }, + { 0x50338, 0x3f000014 }, + { 0x5fffc, 0x30000ee }, + { 0x5033c, 0x3d400010 }, + { 0x5fffc, 0x40000f5 }, + { 0x50340, 0x3b80000c }, + { 0x5fffc, 0x50000fc }, + { 0x50344, 0x39800009 }, + { 0x5fffc, 0x6000102 }, + { 0x50348, 0x37c00006 }, + { 0x5fffc, 0x7000109 }, + { 0x5034c, 0x35800004 }, + { 0x5fffc, 0x840010e }, + { 0x50350, 0x33800002 }, + { 0x5fffc, 0x9800114 }, + { 0x50354, 0x31400000 }, + { 0x5fffc, 0xac00119 }, + { 0x50358, 0x2f4003fe }, + { 0x5fffc, 0xc40011e }, + { 0x5035c, 0x2d0003fc }, + { 0x5fffc, 0xdc00121 }, + { 0x50360, 0x2b0003fb }, + { 0x5fffc, 0xf400125 }, + { 0x50364, 0x28c003fa }, + { 0x5fffc, 0x11000128 }, + { 0x50368, 0x268003f9 }, + { 0x5fffc, 0x12c0012a }, + { 0x5036c, 0x244003f9 }, + { 0x5fffc, 0x1480012c }, + { 0x50370, 0x224003f8 }, + { 0x5fffc, 0x1640012e }, + { 0x50374, 0x200003f8 }, + { 0x5fffc, 0x1800012f }, + { 0x50378, 0x1e0003f8 }, + { 0x5fffc, 0x1a00012f }, + { 0x5037c, 0x1c0003f8 }, +}; + +static struct mdp_table_entry mdp_downscale_y_table_PT8TO1[] = { + { 0x5fffc, 0x0 }, + { 0x50300, 0x7fc00000 }, + { 0x5fffc, 0xff80000d }, + { 0x50304, 0x7ec003f9 }, + { 0x5fffc, 0xfec0001c }, + { 0x50308, 0x7d4003f3 }, + { 0x5fffc, 0xfe40002b }, + { 0x5030c, 0x7b8003ed }, + { 0x5fffc, 0xfd80003c }, + { 0x50310, 0x794003e8 }, + { 0x5fffc, 0xfcc0004d }, + { 0x50314, 0x76c003e4 }, + { 0x5fffc, 0xfc40005f }, + { 0x50318, 0x73c003e0 }, + { 0x5fffc, 0xfb800071 }, + { 0x5031c, 0x708003de }, + { 0x5fffc, 0xfac00085 }, + { 0x50320, 0x6d0003db }, + { 0x5fffc, 0xfa000098 }, + { 0x50324, 0x698003d9 }, + { 0x5fffc, 0xf98000ac }, + { 0x50328, 0x654003d8 }, + { 0x5fffc, 0xf8c000c1 }, + { 0x5032c, 0x610003d7 }, + { 0x5fffc, 0xf84000d5 }, + { 0x50330, 0x5c8003d7 }, + { 0x5fffc, 0xf7c000e9 }, + { 0x50334, 0x580003d7 }, + { 0x5fffc, 0xf74000fd }, + { 0x50338, 0x534003d8 }, + { 0x5fffc, 0xf6c00112 }, + { 0x5033c, 0x4e8003d8 }, + { 0x5fffc, 0xf6800126 }, + { 0x50340, 0x494003da }, + { 0x5fffc, 0xf600013a }, + { 0x50344, 0x448003db }, + { 0x5fffc, 0xf600014d }, + { 0x50348, 0x3f4003dd }, + { 0x5fffc, 0xf5c00160 }, + { 0x5034c, 0x3a4003df }, + { 0x5fffc, 0xf5c00172 }, + { 0x50350, 0x354003e1 }, + { 0x5fffc, 0xf5c00184 }, + { 0x50354, 0x304003e3 }, + { 0x5fffc, 0xf6000195 }, + { 0x50358, 0x2b0003e6 }, + { 0x5fffc, 0xf64001a6 }, + { 0x5035c, 0x260003e8 }, + { 0x5fffc, 0xf6c001b4 }, + { 0x50360, 0x214003eb }, + { 0x5fffc, 0xf78001c2 }, + { 0x50364, 0x1c4003ee }, + { 0x5fffc, 0xf80001cf }, + { 0x50368, 0x17c003f1 }, + { 0x5fffc, 0xf90001db }, + { 0x5036c, 0x134003f3 }, + { 0x5fffc, 0xfa0001e5 }, + { 0x50370, 0xf0003f6 }, + { 0x5fffc, 0xfb4001ee }, + { 0x50374, 0xac003f9 }, + { 0x5fffc, 0xfcc001f5 }, + { 0x50378, 0x70003fb }, + { 0x5fffc, 0xfe4001fb }, + { 0x5037c, 0x34003fe }, +}; + +struct mdp_table_entry *mdp_downscale_y_table[MDP_DOWNSCALE_MAX] = { + [MDP_DOWNSCALE_PT2TOPT4] = mdp_downscale_y_table_PT2TOPT4, + [MDP_DOWNSCALE_PT4TOPT6] = mdp_downscale_y_table_PT4TOPT6, + [MDP_DOWNSCALE_PT6TOPT8] = mdp_downscale_y_table_PT6TOPT8, + [MDP_DOWNSCALE_PT8TO1] = mdp_downscale_y_table_PT8TO1, +}; + +struct mdp_table_entry mdp_gaussian_blur_table[] = { + /* max variance */ + { 0x5fffc, 0x20000080 }, + { 0x50280, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50284, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50288, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5028c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50290, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50294, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50298, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5029c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502a0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502a4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502a8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502ac, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502b0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502b4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502b8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502bc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502c0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502c4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502c8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502cc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502d0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502d4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502d8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502dc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502e0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502e4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502e8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502ec, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502f0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502f4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502f8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502fc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50300, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50304, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50308, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5030c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50310, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50314, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50318, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5031c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50320, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50324, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50328, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5032c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50330, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50334, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50338, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5033c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50340, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50344, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50348, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5034c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50350, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50354, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50358, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5035c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50360, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50364, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50368, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5036c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50370, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50374, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50378, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5037c, 0x20000080 }, +}; + +static void load_table(const struct mdp_info *mdp, + struct mdp_table_entry *table, int len) +{ + int i; + for (i = 0; i < len; i++) + mdp_writel(mdp, table[i].val, table[i].reg); +} + +enum { + IMG_LEFT, + IMG_RIGHT, + IMG_TOP, + IMG_BOTTOM, +}; + +static void get_edge_info(uint32_t src, uint32_t src_coord, uint32_t dst, + uint32_t *interp1, uint32_t *interp2, + uint32_t *repeat1, uint32_t *repeat2) { + if (src > 3 * dst) { + *interp1 = 0; + *interp2 = src - 1; + *repeat1 = 0; + *repeat2 = 0; + } else if (src == 3 * dst) { + *interp1 = 0; + *interp2 = src; + *repeat1 = 0; + *repeat2 = 1; + } else if (src > dst && src < 3 * dst) { + *interp1 = -1; + *interp2 = src; + *repeat1 = 1; + *repeat2 = 1; + } else if (src == dst) { + *interp1 = -1; + *interp2 = src + 1; + *repeat1 = 1; + *repeat2 = 2; + } else { + *interp1 = -2; + *interp2 = src + 1; + *repeat1 = 2; + *repeat2 = 2; + } + *interp1 += src_coord; + *interp2 += src_coord; +} + +int mdp_ppp_cfg_edge_cond(struct mdp_blit_req *req, struct ppp_regs *regs) +{ + int32_t luma_interp[4]; + int32_t luma_repeat[4]; + int32_t chroma_interp[4]; + int32_t chroma_bound[4]; + int32_t chroma_repeat[4]; + uint32_t dst_w, dst_h; + + memset(&luma_interp, 0, sizeof(int32_t) * 4); + memset(&luma_repeat, 0, sizeof(int32_t) * 4); + memset(&chroma_interp, 0, sizeof(int32_t) * 4); + memset(&chroma_bound, 0, sizeof(int32_t) * 4); + memset(&chroma_repeat, 0, sizeof(int32_t) * 4); + regs->edge = 0; + + if (req->flags & MDP_ROT_90) { + dst_w = req->dst_rect.h; + dst_h = req->dst_rect.w; + } else { + dst_w = req->dst_rect.w; + dst_h = req->dst_rect.h; + } + + if (regs->op & (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON)) { + get_edge_info(req->src_rect.h, req->src_rect.y, dst_h, + &luma_interp[IMG_TOP], &luma_interp[IMG_BOTTOM], + &luma_repeat[IMG_TOP], &luma_repeat[IMG_BOTTOM]); + get_edge_info(req->src_rect.w, req->src_rect.x, dst_w, + &luma_interp[IMG_LEFT], &luma_interp[IMG_RIGHT], + &luma_repeat[IMG_LEFT], &luma_repeat[IMG_RIGHT]); + } else { + luma_interp[IMG_LEFT] = req->src_rect.x; + luma_interp[IMG_RIGHT] = req->src_rect.x + req->src_rect.w - 1; + luma_interp[IMG_TOP] = req->src_rect.y; + luma_interp[IMG_BOTTOM] = req->src_rect.y + req->src_rect.h - 1; + luma_repeat[IMG_LEFT] = 0; + luma_repeat[IMG_TOP] = 0; + luma_repeat[IMG_RIGHT] = 0; + luma_repeat[IMG_BOTTOM] = 0; + } + + chroma_interp[IMG_LEFT] = luma_interp[IMG_LEFT]; + chroma_interp[IMG_RIGHT] = luma_interp[IMG_RIGHT]; + chroma_interp[IMG_TOP] = luma_interp[IMG_TOP]; + chroma_interp[IMG_BOTTOM] = luma_interp[IMG_BOTTOM]; + + chroma_bound[IMG_LEFT] = req->src_rect.x; + chroma_bound[IMG_RIGHT] = req->src_rect.x + req->src_rect.w - 1; + chroma_bound[IMG_TOP] = req->src_rect.y; + chroma_bound[IMG_BOTTOM] = req->src_rect.y + req->src_rect.h - 1; + + if (IS_YCRCB(req->src.format)) { + chroma_interp[IMG_LEFT] = chroma_interp[IMG_LEFT] >> 1; + chroma_interp[IMG_RIGHT] = (chroma_interp[IMG_RIGHT] + 1) >> 1; + + chroma_bound[IMG_LEFT] = chroma_bound[IMG_LEFT] >> 1; + chroma_bound[IMG_RIGHT] = chroma_bound[IMG_RIGHT] >> 1; + } + + if (req->src.format == MDP_Y_CBCR_H2V2 || + req->src.format == MDP_Y_CRCB_H2V2) { + chroma_interp[IMG_TOP] = (chroma_interp[IMG_TOP] - 1) >> 1; + chroma_interp[IMG_BOTTOM] = (chroma_interp[IMG_BOTTOM] + 1) + >> 1; + chroma_bound[IMG_TOP] = (chroma_bound[IMG_TOP] + 1) >> 1; + chroma_bound[IMG_BOTTOM] = chroma_bound[IMG_BOTTOM] >> 1; + } + + chroma_repeat[IMG_LEFT] = chroma_bound[IMG_LEFT] - + chroma_interp[IMG_LEFT]; + chroma_repeat[IMG_RIGHT] = chroma_interp[IMG_RIGHT] - + chroma_bound[IMG_RIGHT]; + chroma_repeat[IMG_TOP] = chroma_bound[IMG_TOP] - + chroma_interp[IMG_TOP]; + chroma_repeat[IMG_BOTTOM] = chroma_interp[IMG_BOTTOM] - + chroma_bound[IMG_BOTTOM]; + + if (chroma_repeat[IMG_LEFT] < 0 || chroma_repeat[IMG_LEFT] > 3 || + chroma_repeat[IMG_RIGHT] < 0 || chroma_repeat[IMG_RIGHT] > 3 || + chroma_repeat[IMG_TOP] < 0 || chroma_repeat[IMG_TOP] > 3 || + chroma_repeat[IMG_BOTTOM] < 0 || chroma_repeat[IMG_BOTTOM] > 3 || + luma_repeat[IMG_LEFT] < 0 || luma_repeat[IMG_LEFT] > 3 || + luma_repeat[IMG_RIGHT] < 0 || luma_repeat[IMG_RIGHT] > 3 || + luma_repeat[IMG_TOP] < 0 || luma_repeat[IMG_TOP] > 3 || + luma_repeat[IMG_BOTTOM] < 0 || luma_repeat[IMG_BOTTOM] > 3) + return -1; + + regs->edge |= (chroma_repeat[IMG_LEFT] & 3) << MDP_LEFT_CHROMA; + regs->edge |= (chroma_repeat[IMG_RIGHT] & 3) << MDP_RIGHT_CHROMA; + regs->edge |= (chroma_repeat[IMG_TOP] & 3) << MDP_TOP_CHROMA; + regs->edge |= (chroma_repeat[IMG_BOTTOM] & 3) << MDP_BOTTOM_CHROMA; + regs->edge |= (luma_repeat[IMG_LEFT] & 3) << MDP_LEFT_LUMA; + regs->edge |= (luma_repeat[IMG_RIGHT] & 3) << MDP_RIGHT_LUMA; + regs->edge |= (luma_repeat[IMG_TOP] & 3) << MDP_TOP_LUMA; + regs->edge |= (luma_repeat[IMG_BOTTOM] & 3) << MDP_BOTTOM_LUMA; + return 0; +} + +#define ONE_HALF (1LL << 32) +#define ONE (1LL << 33) +#define TWO (2LL << 33) +#define THREE (3LL << 33) +#define FRAC_MASK (ONE - 1) +#define INT_MASK (~FRAC_MASK) + +static int scale_params(uint32_t dim_in, uint32_t dim_out, uint32_t origin, + uint32_t *phase_init, uint32_t *phase_step) +{ + /* to improve precicsion calculations are done in U31.33 and converted + * to U3.29 at the end */ + int64_t k1, k2, k3, k4, tmp; + uint64_t n, d, os, os_p, od, od_p, oreq; + unsigned rpa = 0; + int64_t ip64, delta; + + if (dim_out % 3 == 0) + rpa = !(dim_in % (dim_out / 3)); + + n = ((uint64_t)dim_out) << 34; + d = dim_in; + if (!d) + return -1; + do_div(n, d); + k3 = (n + 1) >> 1; + if ((k3 >> 4) < (1LL << 27) || (k3 >> 4) > (1LL << 31)) + return -1; + + n = ((uint64_t)dim_in) << 34; + d = (uint64_t)dim_out; + if (!d) + return -1; + do_div(n, d); + k1 = (n + 1) >> 1; + k2 = (k1 - ONE) >> 1; + + *phase_init = (int)(k2 >> 4); + k4 = (k3 - ONE) >> 1; + + if (rpa) { + os = ((uint64_t)origin << 33) - ONE_HALF; + tmp = (dim_out * os) + ONE_HALF; + if (!dim_in) + return -1; + do_div(tmp, dim_in); + od = tmp - ONE_HALF; + } else { + os = ((uint64_t)origin << 1) - 1; + od = (((k3 * os) >> 1) + k4); + } + + od_p = od & INT_MASK; + if (od_p != od) + od_p += ONE; + + if (rpa) { + tmp = (dim_in * od_p) + ONE_HALF; + if (!dim_in) + return -1; + do_div(tmp, dim_in); + os_p = tmp - ONE_HALF; + } else { + os_p = ((k1 * (od_p >> 33)) + k2); + } + + oreq = (os_p & INT_MASK) - ONE; + + ip64 = os_p - oreq; + delta = ((int64_t)(origin) << 33) - oreq; + ip64 -= delta; + /* limit to valid range before the left shift */ + delta = (ip64 & (1LL << 63)) ? 4 : -4; + delta <<= 33; + while (abs((int)(ip64 >> 33)) > 4) + ip64 += delta; + *phase_init = (int)(ip64 >> 4); + *phase_step = (uint32_t)(k1 >> 4); + return 0; +} + +int mdp_ppp_cfg_scale(const struct mdp_info *mdp, struct ppp_regs *regs, + struct mdp_rect *src_rect, struct mdp_rect *dst_rect, + uint32_t src_format, uint32_t dst_format) +{ + int downscale; + uint32_t phase_init_x, phase_init_y, phase_step_x, phase_step_y; + uint32_t scale_factor_x, scale_factor_y; + + if (scale_params(src_rect->w, dst_rect->w, 1, &phase_init_x, + &phase_step_x) || + scale_params(src_rect->h, dst_rect->h, 1, &phase_init_y, + &phase_step_y)) + return -1; + + regs->phasex_init = phase_init_x; + regs->phasey_init = phase_init_y; + regs->phasex_step = phase_step_x; + regs->phasey_step = phase_step_y; + + scale_factor_x = (dst_rect->w * 10) / src_rect->w; + scale_factor_y = (dst_rect->h * 10) / src_rect->h; + + if (scale_factor_x > 8) + downscale = MDP_DOWNSCALE_PT8TO1; + else if (scale_factor_x > 6) + downscale = MDP_DOWNSCALE_PT6TOPT8; + else if (scale_factor_x > 4) + downscale = MDP_DOWNSCALE_PT4TOPT6; + else + downscale = MDP_DOWNSCALE_PT2TOPT4; + + if (downscale != downscale_x_table) { + load_table(mdp, mdp_downscale_x_table[downscale], 64); + downscale_x_table = downscale; + } + + if (scale_factor_y > 8) + downscale = MDP_DOWNSCALE_PT8TO1; + else if (scale_factor_y > 6) + downscale = MDP_DOWNSCALE_PT6TOPT8; + else if (scale_factor_y > 4) + downscale = MDP_DOWNSCALE_PT4TOPT6; + else + downscale = MDP_DOWNSCALE_PT2TOPT4; + + if (downscale != downscale_y_table) { + load_table(mdp, mdp_downscale_y_table[downscale], 64); + downscale_y_table = downscale; + } + + return 0; +} + + +int mdp_ppp_load_blur(const struct mdp_info *mdp) +{ + if (!(downscale_x_table == MDP_DOWNSCALE_BLUR && + downscale_y_table == MDP_DOWNSCALE_BLUR)) { + load_table(mdp, mdp_gaussian_blur_table, 128); + downscale_x_table = MDP_DOWNSCALE_BLUR; + downscale_y_table = MDP_DOWNSCALE_BLUR; + } + + return 0; +} + +void mdp_ppp_init_scale(const struct mdp_info *mdp) +{ + downscale_x_table = MDP_DOWNSCALE_MAX; + downscale_y_table = MDP_DOWNSCALE_MAX; + + load_table(mdp, mdp_upscale_table, ARRAY_SIZE(mdp_upscale_table)); +} diff --git a/drivers/video/msm/mdp_ppp31.c b/drivers/video/msm/mdp_ppp31.c new file mode 100644 index 00000000000..91764fe27a4 --- /dev/null +++ b/drivers/video/msm/mdp_ppp31.c @@ -0,0 +1,332 @@ +/* drivers/video/msm/mdp_ppp31.c + * + * Copyright (C) 2009 Code Aurora Forum. All rights reserved. + * Copyright (C) 2009 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include "mdp_hw.h" +#include "mdp_ppp.h" + +#define NUM_COEFFS 32 + +struct mdp_scale_coeffs { + uint16_t c[4][NUM_COEFFS]; +}; + +struct mdp_scale_tbl_info { + uint16_t offset; + uint32_t set:2; + int use_pr; + struct mdp_scale_coeffs coeffs; +}; + +enum { + MDP_SCALE_PT2TOPT4, + MDP_SCALE_PT4TOPT6, + MDP_SCALE_PT6TOPT8, + MDP_SCALE_PT8TO8, + MDP_SCALE_MAX, +}; + +static struct mdp_scale_coeffs mdp_scale_pr_coeffs = { + .c = { + [0] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + [1] = { + 511, 511, 511, 511, 511, 511, 511, 511, + 511, 511, 511, 511, 511, 511, 511, 511, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + [2] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 511, 511, 511, 511, 511, 511, 511, 511, + 511, 511, 511, 511, 511, 511, 511, 511, + }, + [3] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + }, +}; + +static struct mdp_scale_tbl_info mdp_scale_tbl[MDP_SCALE_MAX] = { + [ MDP_SCALE_PT2TOPT4 ] = { + .offset = 0, + .set = MDP_PPP_SCALE_COEFF_D0_SET, + .use_pr = -1, + .coeffs.c = { + [0] = { + 131, 131, 130, 129, 128, 127, 127, 126, + 125, 125, 124, 123, 123, 121, 120, 119, + 119, 118, 117, 117, 116, 115, 115, 114, + 113, 112, 111, 110, 109, 109, 108, 107, + }, + [1] = { + 141, 140, 140, 140, 140, 139, 138, 138, + 138, 137, 137, 137, 136, 137, 137, 137, + 136, 136, 136, 135, 135, 135, 134, 134, + 134, 134, 134, 133, 133, 132, 132, 132, + }, + [2] = { + 132, 132, 132, 133, 133, 134, 134, 134, + 134, 134, 135, 135, 135, 136, 136, 136, + 137, 137, 137, 136, 137, 137, 137, 138, + 138, 138, 139, 140, 140, 140, 140, 141, + }, + [3] = { + 107, 108, 109, 109, 110, 111, 112, 113, + 114, 115, 115, 116, 117, 117, 118, 119, + 119, 120, 121, 123, 123, 124, 125, 125, + 126, 127, 127, 128, 129, 130, 131, 131, + } + }, + }, + [ MDP_SCALE_PT4TOPT6 ] = { + .offset = 32, + .set = MDP_PPP_SCALE_COEFF_D1_SET, + .use_pr = -1, + .coeffs.c = { + [0] = { + 136, 132, 128, 123, 119, 115, 111, 107, + 103, 98, 95, 91, 87, 84, 80, 76, + 73, 69, 66, 62, 59, 57, 54, 50, + 47, 44, 41, 39, 36, 33, 32, 29, + }, + [1] = { + 206, 205, 204, 204, 201, 200, 199, 197, + 196, 194, 191, 191, 189, 185, 184, 182, + 180, 178, 176, 173, 170, 168, 165, 162, + 160, 157, 155, 152, 148, 146, 142, 140, + }, + [2] = { + 140, 142, 146, 148, 152, 155, 157, 160, + 162, 165, 168, 170, 173, 176, 178, 180, + 182, 184, 185, 189, 191, 191, 194, 196, + 197, 199, 200, 201, 204, 204, 205, 206, + }, + [3] = { + 29, 32, 33, 36, 39, 41, 44, 47, + 50, 54, 57, 59, 62, 66, 69, 73, + 76, 80, 84, 87, 91, 95, 98, 103, + 107, 111, 115, 119, 123, 128, 132, 136, + }, + }, + }, + [ MDP_SCALE_PT6TOPT8 ] = { + .offset = 64, + .set = MDP_PPP_SCALE_COEFF_D2_SET, + .use_pr = -1, + .coeffs.c = { + [0] = { + 104, 96, 89, 82, 75, 68, 61, 55, + 49, 43, 38, 33, 28, 24, 20, 16, + 12, 9, 6, 4, 2, 0, -2, -4, + -5, -6, -7, -7, -8, -8, -8, -8, + }, + [1] = { + 303, 303, 302, 300, 298, 296, 293, 289, + 286, 281, 276, 270, 265, 258, 252, 245, + 238, 230, 223, 214, 206, 197, 189, 180, + 172, 163, 154, 145, 137, 128, 120, 112, + }, + [2] = { + 112, 120, 128, 137, 145, 154, 163, 172, + 180, 189, 197, 206, 214, 223, 230, 238, + 245, 252, 258, 265, 270, 276, 281, 286, + 289, 293, 296, 298, 300, 302, 303, 303, + }, + [3] = { + -8, -8, -8, -8, -7, -7, -6, -5, + -4, -2, 0, 2, 4, 6, 9, 12, + 16, 20, 24, 28, 33, 38, 43, 49, + 55, 61, 68, 75, 82, 89, 96, 104, + }, + }, + }, + [ MDP_SCALE_PT8TO8 ] = { + .offset = 96, + .set = MDP_PPP_SCALE_COEFF_U1_SET, + .use_pr = -1, + .coeffs.c = { + [0] = { + 0, -7, -13, -19, -24, -28, -32, -34, + -37, -39, -40, -41, -41, -41, -40, -40, + -38, -37, -35, -33, -31, -29, -26, -24, + -21, -18, -15, -13, -10, -7, -5, -2, + }, + [1] = { + 511, 507, 501, 494, 485, 475, 463, 450, + 436, 422, 405, 388, 370, 352, 333, 314, + 293, 274, 253, 233, 213, 193, 172, 152, + 133, 113, 95, 77, 60, 43, 28, 13, + }, + [2] = { + 0, 13, 28, 43, 60, 77, 95, 113, + 133, 152, 172, 193, 213, 233, 253, 274, + 294, 314, 333, 352, 370, 388, 405, 422, + 436, 450, 463, 475, 485, 494, 501, 507, + }, + [3] = { + 0, -2, -5, -7, -10, -13, -15, -18, + -21, -24, -26, -29, -31, -33, -35, -37, + -38, -40, -40, -41, -41, -41, -40, -39, + -37, -34, -32, -28, -24, -19, -13, -7, + }, + }, + }, +}; + +static void load_table(const struct mdp_info *mdp, int scale, int use_pr) +{ + int i; + uint32_t val; + struct mdp_scale_coeffs *coeffs; + struct mdp_scale_tbl_info *tbl = &mdp_scale_tbl[scale]; + + if (use_pr == tbl->use_pr) + return; + + tbl->use_pr = use_pr; + if (!use_pr) + coeffs = &tbl->coeffs; + else + coeffs = &mdp_scale_pr_coeffs; + + for (i = 0; i < NUM_COEFFS; ++i) { + val = ((coeffs->c[1][i] & 0x3ff) << 16) | + (coeffs->c[0][i] & 0x3ff); + mdp_writel(mdp, val, MDP_PPP_SCALE_COEFF_LSBn(tbl->offset + i)); + + val = ((coeffs->c[3][i] & 0x3ff) << 16) | + (coeffs->c[2][i] & 0x3ff); + mdp_writel(mdp, val, MDP_PPP_SCALE_COEFF_MSBn(tbl->offset + i)); + } +} + +#define SCALER_PHASE_BITS 29 +static void scale_params(uint32_t dim_in, uint32_t dim_out, uint32_t scaler, + uint32_t *phase_init, uint32_t *phase_step) +{ + uint64_t src = dim_in; + uint64_t dst = dim_out; + uint64_t numer; + uint64_t denom; + + *phase_init = 0; + + if (dst == 1) { + /* if destination is 1 pixel wide, the value of phase_step + * is unimportant. */ + *phase_step = (uint32_t) (src << SCALER_PHASE_BITS); + if (scaler == MDP_PPP_SCALER_FIR) + *phase_init = + (uint32_t) ((src - 1) << SCALER_PHASE_BITS); + return; + } + + if (scaler == MDP_PPP_SCALER_FIR) { + numer = (src - 1) << SCALER_PHASE_BITS; + denom = dst - 1; + /* we want to round up the result*/ + numer += denom - 1; + } else { + numer = src << SCALER_PHASE_BITS; + denom = dst; + } + + do_div(numer, denom); + *phase_step = (uint32_t) numer; +} + +static int scale_idx(int factor) +{ + int idx; + + if (factor > 80) + idx = MDP_SCALE_PT8TO8; + else if (factor > 60) + idx = MDP_SCALE_PT6TOPT8; + else if (factor > 40) + idx = MDP_SCALE_PT4TOPT6; + else + idx = MDP_SCALE_PT2TOPT4; + + return idx; +} + +int mdp_ppp_cfg_scale(const struct mdp_info *mdp, struct ppp_regs *regs, + struct mdp_rect *src_rect, struct mdp_rect *dst_rect, + uint32_t src_format, uint32_t dst_format) +{ + uint32_t x_fac; + uint32_t y_fac; + uint32_t scaler_x = MDP_PPP_SCALER_FIR; + uint32_t scaler_y = MDP_PPP_SCALER_FIR; + // Don't use pixel repeat mode, it looks bad + int use_pr = 0; + int x_idx; + int y_idx; + + if (unlikely(src_rect->w > 2048 || src_rect->h > 2048)) + return -ENOTSUPP; + + x_fac = (dst_rect->w * 100) / src_rect->w; + y_fac = (dst_rect->h * 100) / src_rect->h; + + /* if down-scaling by a factor smaller than 1/4, use M/N */ + scaler_x = x_fac <= 25 ? MDP_PPP_SCALER_MN : MDP_PPP_SCALER_FIR; + scaler_y = y_fac <= 25 ? MDP_PPP_SCALER_MN : MDP_PPP_SCALER_FIR; + scale_params(src_rect->w, dst_rect->w, scaler_x, ®s->phasex_init, + ®s->phasex_step); + scale_params(src_rect->h, dst_rect->h, scaler_y, ®s->phasey_init, + ®s->phasey_step); + + x_idx = scale_idx(x_fac); + y_idx = scale_idx(y_fac); + load_table(mdp, x_idx, use_pr); + load_table(mdp, y_idx, use_pr); + + regs->scale_cfg = 0; + // Enable SVI when source or destination is YUV + if (!IS_RGB(src_format) && !IS_RGB(dst_format)) + regs->scale_cfg |= (1 << 6); + regs->scale_cfg |= (mdp_scale_tbl[x_idx].set << 2) | + (mdp_scale_tbl[x_idx].set << 4); + regs->scale_cfg |= (scaler_x << 0) | (scaler_y << 1); + + return 0; +} + +int mdp_ppp_load_blur(const struct mdp_info *mdp) +{ + return -ENOTSUPP; +} + +void mdp_ppp_init_scale(const struct mdp_info *mdp) +{ + int scale; + for (scale = 0; scale < MDP_SCALE_MAX; ++scale) + load_table(mdp, scale, 0); +} diff --git a/drivers/video/msm/mdp_ppp_v20.c b/drivers/video/msm/mdp_ppp_v20.c new file mode 100644 index 00000000000..8828a8f0cd0 --- /dev/null +++ b/drivers/video/msm/mdp_ppp_v20.c @@ -0,0 +1,2483 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" + +#include +#include + +#include +#include +#include +#include + +#include "mdp.h" +#include "msm_fb.h" + +static MDP_SCALE_MODE mdp_curr_up_scale_xy; +static MDP_SCALE_MODE mdp_curr_down_scale_x; +static MDP_SCALE_MODE mdp_curr_down_scale_y; + +static long long mdp_do_div(long long num, long long den) +{ + do_div(num, den); + return num; +} + +struct mdp_table_entry mdp_gaussian_blur_table[] = { + /* max variance */ + { 0x5fffc, 0x20000080 }, + { 0x50280, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50284, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50288, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5028c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50290, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50294, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50298, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5029c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502a0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502a4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502a8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502ac, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502b0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502b4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502b8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502bc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502c0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502c4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502c8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502cc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502d0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502d4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502d8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502dc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502e0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502e4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502e8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502ec, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502f0, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502f4, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502f8, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x502fc, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50300, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50304, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50308, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5030c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50310, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50314, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50318, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5031c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50320, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50324, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50328, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5032c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50330, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50334, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50338, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5033c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50340, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50344, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50348, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5034c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50350, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50354, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50358, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5035c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50360, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50364, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50368, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5036c, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50370, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50374, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x50378, 0x20000080 }, + { 0x5fffc, 0x20000080 }, + { 0x5037c, 0x20000080 }, +}; + +static void load_scale_table( + struct mdp_table_entry *table, int len) +{ + int i; + for (i = 0; i < len; i++) + MDP_OUTP(MDP_BASE + table[i].reg, table[i].val); +} + +static void mdp_load_pr_upscale_table(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50200, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50204, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50208, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5020c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50210, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50214, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50218, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5021c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50220, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50224, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50228, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5022c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50230, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50234, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50238, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5023c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50240, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50244, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50248, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5024c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50250, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50254, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50258, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5025c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50260, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50264, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50268, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5026c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50270, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50274, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50278, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5027c, 0x0); +} + +static void mdp_load_pr_downscale_table_x_point2TOpoint4(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50280, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50284, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50288, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5028c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50290, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50294, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50298, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5029c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a0, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a4, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a8, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502ac, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b0, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b4, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b8, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502bc, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502cc, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502dc, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502ec, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502fc, 0x0); +} + +static void mdp_load_pr_downscale_table_y_point2TOpoint4(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50300, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50304, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50308, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5030c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50310, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50314, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50318, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5031c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50320, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50324, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50328, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5032c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50330, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50334, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50338, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5033c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50340, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50344, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50348, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5034c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50350, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50354, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50358, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5035c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50360, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50364, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50368, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5036c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50370, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50374, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50378, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5037c, 0x0); +} + +static void mdp_load_pr_downscale_table_x_point4TOpoint6(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50280, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50284, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50288, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5028c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50290, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50294, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50298, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5029c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a0, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a4, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a8, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502ac, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b0, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b4, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b8, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502bc, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502cc, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502dc, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502ec, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502fc, 0x0); +} + +static void mdp_load_pr_downscale_table_y_point4TOpoint6(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50300, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50304, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50308, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5030c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50310, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50314, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50318, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5031c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50320, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50324, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50328, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5032c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50330, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50334, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50338, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5033c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50340, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50344, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50348, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5034c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50350, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50354, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50358, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5035c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50360, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50364, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50368, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5036c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50370, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50374, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50378, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5037c, 0x0); +} + +static void mdp_load_pr_downscale_table_x_point6TOpoint8(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50280, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50284, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50288, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5028c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50290, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50294, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50298, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5029c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a0, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a4, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a8, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502ac, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b0, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b4, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b8, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502bc, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502cc, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502dc, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502ec, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502fc, 0x0); +} + +static void mdp_load_pr_downscale_table_y_point6TOpoint8(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50300, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50304, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50308, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5030c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50310, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50314, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50318, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5031c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50320, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50324, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50328, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5032c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50330, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50334, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50338, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5033c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50340, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50344, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50348, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5034c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50350, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50354, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50358, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5035c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50360, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50364, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50368, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5036c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50370, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50374, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50378, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5037c, 0x0); +} + +static void mdp_load_pr_downscale_table_x_point8TO1(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50280, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50284, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50288, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5028c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50290, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50294, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50298, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5029c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a0, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a4, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502a8, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502ac, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b0, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b4, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502b8, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x502bc, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502c8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502cc, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502d8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502dc, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502e8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502ec, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f0, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f4, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502f8, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x502fc, 0x0); +} + +static void mdp_load_pr_downscale_table_y_point8TO1(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50300, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50304, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50308, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5030c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50310, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50314, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50318, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5031c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50320, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50324, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50328, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5032c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50330, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50334, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50338, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x5033c, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50340, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50344, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50348, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5034c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50350, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50354, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50358, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5035c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50360, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50364, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50368, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5036c, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50370, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50374, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x50378, 0x0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ff); + MDP_OUTP(MDP_BASE + 0x5037c, 0x0); +} + +static void mdp_load_bc_upscale_table(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50200, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xff80000d); + MDP_OUTP(MDP_BASE + 0x50204, 0x7ec003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfec0001c); + MDP_OUTP(MDP_BASE + 0x50208, 0x7d4003f3); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe40002b); + MDP_OUTP(MDP_BASE + 0x5020c, 0x7b8003ed); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfd80003c); + MDP_OUTP(MDP_BASE + 0x50210, 0x794003e8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfcc0004d); + MDP_OUTP(MDP_BASE + 0x50214, 0x76c003e4); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfc40005f); + MDP_OUTP(MDP_BASE + 0x50218, 0x73c003e0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfb800071); + MDP_OUTP(MDP_BASE + 0x5021c, 0x708003de); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfac00085); + MDP_OUTP(MDP_BASE + 0x50220, 0x6d0003db); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfa000098); + MDP_OUTP(MDP_BASE + 0x50224, 0x698003d9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf98000ac); + MDP_OUTP(MDP_BASE + 0x50228, 0x654003d8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf8c000c1); + MDP_OUTP(MDP_BASE + 0x5022c, 0x610003d7); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf84000d5); + MDP_OUTP(MDP_BASE + 0x50230, 0x5c8003d7); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf7c000e9); + MDP_OUTP(MDP_BASE + 0x50234, 0x580003d7); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf74000fd); + MDP_OUTP(MDP_BASE + 0x50238, 0x534003d8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6c00112); + MDP_OUTP(MDP_BASE + 0x5023c, 0x4e8003d8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6800126); + MDP_OUTP(MDP_BASE + 0x50240, 0x494003da); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf600013a); + MDP_OUTP(MDP_BASE + 0x50244, 0x448003db); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf600014d); + MDP_OUTP(MDP_BASE + 0x50248, 0x3f4003dd); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf5c00160); + MDP_OUTP(MDP_BASE + 0x5024c, 0x3a4003df); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf5c00172); + MDP_OUTP(MDP_BASE + 0x50250, 0x354003e1); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf5c00184); + MDP_OUTP(MDP_BASE + 0x50254, 0x304003e3); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6000195); + MDP_OUTP(MDP_BASE + 0x50258, 0x2b0003e6); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf64001a6); + MDP_OUTP(MDP_BASE + 0x5025c, 0x260003e8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6c001b4); + MDP_OUTP(MDP_BASE + 0x50260, 0x214003eb); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf78001c2); + MDP_OUTP(MDP_BASE + 0x50264, 0x1c4003ee); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf80001cf); + MDP_OUTP(MDP_BASE + 0x50268, 0x17c003f1); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf90001db); + MDP_OUTP(MDP_BASE + 0x5026c, 0x134003f3); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfa0001e5); + MDP_OUTP(MDP_BASE + 0x50270, 0xf0003f6); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfb4001ee); + MDP_OUTP(MDP_BASE + 0x50274, 0xac003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfcc001f5); + MDP_OUTP(MDP_BASE + 0x50278, 0x70003fb); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe4001fb); + MDP_OUTP(MDP_BASE + 0x5027c, 0x34003fe); +} + +static void mdp_load_bc_downscale_table_x_point2TOpoint4(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ac00084); + MDP_OUTP(MDP_BASE + 0x50280, 0x23400083); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1b000084); + MDP_OUTP(MDP_BASE + 0x50284, 0x23000083); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1b400084); + MDP_OUTP(MDP_BASE + 0x50288, 0x23000082); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1b400085); + MDP_OUTP(MDP_BASE + 0x5028c, 0x23000081); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1b800085); + MDP_OUTP(MDP_BASE + 0x50290, 0x23000080); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1bc00086); + MDP_OUTP(MDP_BASE + 0x50294, 0x22c0007f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1c000086); + MDP_OUTP(MDP_BASE + 0x50298, 0x2280007f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1c400086); + MDP_OUTP(MDP_BASE + 0x5029c, 0x2280007e); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1c800086); + MDP_OUTP(MDP_BASE + 0x502a0, 0x2280007d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1cc00086); + MDP_OUTP(MDP_BASE + 0x502a4, 0x2240007d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1cc00087); + MDP_OUTP(MDP_BASE + 0x502a8, 0x2240007c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1d000087); + MDP_OUTP(MDP_BASE + 0x502ac, 0x2240007b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1d400087); + MDP_OUTP(MDP_BASE + 0x502b0, 0x2200007b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1d400088); + MDP_OUTP(MDP_BASE + 0x502b4, 0x22400079); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1d800088); + MDP_OUTP(MDP_BASE + 0x502b8, 0x22400078); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1dc00088); + MDP_OUTP(MDP_BASE + 0x502bc, 0x22400077); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1dc00089); + MDP_OUTP(MDP_BASE + 0x502c0, 0x22000077); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1e000089); + MDP_OUTP(MDP_BASE + 0x502c4, 0x22000076); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1e400089); + MDP_OUTP(MDP_BASE + 0x502c8, 0x22000075); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ec00088); + MDP_OUTP(MDP_BASE + 0x502cc, 0x21c00075); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ec00089); + MDP_OUTP(MDP_BASE + 0x502d0, 0x21c00074); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1f000089); + MDP_OUTP(MDP_BASE + 0x502d4, 0x21c00073); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1f400089); + MDP_OUTP(MDP_BASE + 0x502d8, 0x21800073); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1f40008a); + MDP_OUTP(MDP_BASE + 0x502dc, 0x21800072); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1f80008a); + MDP_OUTP(MDP_BASE + 0x502e0, 0x21800071); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1fc0008a); + MDP_OUTP(MDP_BASE + 0x502e4, 0x21800070); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1fc0008b); + MDP_OUTP(MDP_BASE + 0x502e8, 0x2180006f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x2000008c); + MDP_OUTP(MDP_BASE + 0x502ec, 0x2140006e); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x2040008c); + MDP_OUTP(MDP_BASE + 0x502f0, 0x2140006d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x2080008c); + MDP_OUTP(MDP_BASE + 0x502f4, 0x2100006d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x20c0008c); + MDP_OUTP(MDP_BASE + 0x502f8, 0x2100006c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x20c0008d); + MDP_OUTP(MDP_BASE + 0x502fc, 0x2100006b); +} + +static void mdp_load_bc_downscale_table_y_point2TOpoint4(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ac00084); + MDP_OUTP(MDP_BASE + 0x50300, 0x23400083); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1b000084); + MDP_OUTP(MDP_BASE + 0x50304, 0x23000083); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1b400084); + MDP_OUTP(MDP_BASE + 0x50308, 0x23000082); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1b400085); + MDP_OUTP(MDP_BASE + 0x5030c, 0x23000081); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1b800085); + MDP_OUTP(MDP_BASE + 0x50310, 0x23000080); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1bc00086); + MDP_OUTP(MDP_BASE + 0x50314, 0x22c0007f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1c000086); + MDP_OUTP(MDP_BASE + 0x50318, 0x2280007f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1c400086); + MDP_OUTP(MDP_BASE + 0x5031c, 0x2280007e); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1c800086); + MDP_OUTP(MDP_BASE + 0x50320, 0x2280007d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1cc00086); + MDP_OUTP(MDP_BASE + 0x50324, 0x2240007d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1cc00087); + MDP_OUTP(MDP_BASE + 0x50328, 0x2240007c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1d000087); + MDP_OUTP(MDP_BASE + 0x5032c, 0x2240007b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1d400087); + MDP_OUTP(MDP_BASE + 0x50330, 0x2200007b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1d400088); + MDP_OUTP(MDP_BASE + 0x50334, 0x22400079); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1d800088); + MDP_OUTP(MDP_BASE + 0x50338, 0x22400078); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1dc00088); + MDP_OUTP(MDP_BASE + 0x5033c, 0x22400077); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1dc00089); + MDP_OUTP(MDP_BASE + 0x50340, 0x22000077); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1e000089); + MDP_OUTP(MDP_BASE + 0x50344, 0x22000076); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1e400089); + MDP_OUTP(MDP_BASE + 0x50348, 0x22000075); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ec00088); + MDP_OUTP(MDP_BASE + 0x5034c, 0x21c00075); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ec00089); + MDP_OUTP(MDP_BASE + 0x50350, 0x21c00074); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1f000089); + MDP_OUTP(MDP_BASE + 0x50354, 0x21c00073); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1f400089); + MDP_OUTP(MDP_BASE + 0x50358, 0x21800073); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1f40008a); + MDP_OUTP(MDP_BASE + 0x5035c, 0x21800072); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1f80008a); + MDP_OUTP(MDP_BASE + 0x50360, 0x21800071); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1fc0008a); + MDP_OUTP(MDP_BASE + 0x50364, 0x21800070); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1fc0008b); + MDP_OUTP(MDP_BASE + 0x50368, 0x2180006f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x2000008c); + MDP_OUTP(MDP_BASE + 0x5036c, 0x2140006e); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x2040008c); + MDP_OUTP(MDP_BASE + 0x50370, 0x2140006d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x2080008c); + MDP_OUTP(MDP_BASE + 0x50374, 0x2100006d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x20c0008c); + MDP_OUTP(MDP_BASE + 0x50378, 0x2100006c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x20c0008d); + MDP_OUTP(MDP_BASE + 0x5037c, 0x2100006b); +} + +static void mdp_load_bc_downscale_table_x_point4TOpoint6(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x740008c); + MDP_OUTP(MDP_BASE + 0x50280, 0x33800088); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x800008e); + MDP_OUTP(MDP_BASE + 0x50284, 0x33400084); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x8400092); + MDP_OUTP(MDP_BASE + 0x50288, 0x33000080); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x9000094); + MDP_OUTP(MDP_BASE + 0x5028c, 0x3300007b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x9c00098); + MDP_OUTP(MDP_BASE + 0x50290, 0x32400077); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xa40009b); + MDP_OUTP(MDP_BASE + 0x50294, 0x32000073); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xb00009d); + MDP_OUTP(MDP_BASE + 0x50298, 0x31c0006f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xbc000a0); + MDP_OUTP(MDP_BASE + 0x5029c, 0x3140006b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xc8000a2); + MDP_OUTP(MDP_BASE + 0x502a0, 0x31000067); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xd8000a5); + MDP_OUTP(MDP_BASE + 0x502a4, 0x30800062); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xe4000a8); + MDP_OUTP(MDP_BASE + 0x502a8, 0x2fc0005f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xec000aa); + MDP_OUTP(MDP_BASE + 0x502ac, 0x2fc0005b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf8000ad); + MDP_OUTP(MDP_BASE + 0x502b0, 0x2f400057); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x108000b0); + MDP_OUTP(MDP_BASE + 0x502b4, 0x2e400054); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x114000b2); + MDP_OUTP(MDP_BASE + 0x502b8, 0x2e000050); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x124000b4); + MDP_OUTP(MDP_BASE + 0x502bc, 0x2d80004c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x130000b6); + MDP_OUTP(MDP_BASE + 0x502c0, 0x2d000049); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x140000b8); + MDP_OUTP(MDP_BASE + 0x502c4, 0x2c800045); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x150000b9); + MDP_OUTP(MDP_BASE + 0x502c8, 0x2c000042); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x15c000bd); + MDP_OUTP(MDP_BASE + 0x502cc, 0x2b40003e); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x16c000bf); + MDP_OUTP(MDP_BASE + 0x502d0, 0x2a80003b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x17c000bf); + MDP_OUTP(MDP_BASE + 0x502d4, 0x2a000039); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x188000c2); + MDP_OUTP(MDP_BASE + 0x502d8, 0x29400036); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x19c000c4); + MDP_OUTP(MDP_BASE + 0x502dc, 0x28800032); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ac000c5); + MDP_OUTP(MDP_BASE + 0x502e0, 0x2800002f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1bc000c7); + MDP_OUTP(MDP_BASE + 0x502e4, 0x2740002c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1cc000c8); + MDP_OUTP(MDP_BASE + 0x502e8, 0x26c00029); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1dc000c9); + MDP_OUTP(MDP_BASE + 0x502ec, 0x26000027); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ec000cc); + MDP_OUTP(MDP_BASE + 0x502f0, 0x25000024); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x200000cc); + MDP_OUTP(MDP_BASE + 0x502f4, 0x24800021); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x210000cd); + MDP_OUTP(MDP_BASE + 0x502f8, 0x23800020); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x220000ce); + MDP_OUTP(MDP_BASE + 0x502fc, 0x2300001d); +} + +static void mdp_load_bc_downscale_table_y_point4TOpoint6(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x740008c); + MDP_OUTP(MDP_BASE + 0x50300, 0x33800088); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x800008e); + MDP_OUTP(MDP_BASE + 0x50304, 0x33400084); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x8400092); + MDP_OUTP(MDP_BASE + 0x50308, 0x33000080); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x9000094); + MDP_OUTP(MDP_BASE + 0x5030c, 0x3300007b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x9c00098); + MDP_OUTP(MDP_BASE + 0x50310, 0x32400077); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xa40009b); + MDP_OUTP(MDP_BASE + 0x50314, 0x32000073); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xb00009d); + MDP_OUTP(MDP_BASE + 0x50318, 0x31c0006f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xbc000a0); + MDP_OUTP(MDP_BASE + 0x5031c, 0x3140006b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xc8000a2); + MDP_OUTP(MDP_BASE + 0x50320, 0x31000067); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xd8000a5); + MDP_OUTP(MDP_BASE + 0x50324, 0x30800062); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xe4000a8); + MDP_OUTP(MDP_BASE + 0x50328, 0x2fc0005f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xec000aa); + MDP_OUTP(MDP_BASE + 0x5032c, 0x2fc0005b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf8000ad); + MDP_OUTP(MDP_BASE + 0x50330, 0x2f400057); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x108000b0); + MDP_OUTP(MDP_BASE + 0x50334, 0x2e400054); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x114000b2); + MDP_OUTP(MDP_BASE + 0x50338, 0x2e000050); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x124000b4); + MDP_OUTP(MDP_BASE + 0x5033c, 0x2d80004c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x130000b6); + MDP_OUTP(MDP_BASE + 0x50340, 0x2d000049); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x140000b8); + MDP_OUTP(MDP_BASE + 0x50344, 0x2c800045); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x150000b9); + MDP_OUTP(MDP_BASE + 0x50348, 0x2c000042); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x15c000bd); + MDP_OUTP(MDP_BASE + 0x5034c, 0x2b40003e); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x16c000bf); + MDP_OUTP(MDP_BASE + 0x50350, 0x2a80003b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x17c000bf); + MDP_OUTP(MDP_BASE + 0x50354, 0x2a000039); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x188000c2); + MDP_OUTP(MDP_BASE + 0x50358, 0x29400036); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x19c000c4); + MDP_OUTP(MDP_BASE + 0x5035c, 0x28800032); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ac000c5); + MDP_OUTP(MDP_BASE + 0x50360, 0x2800002f); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1bc000c7); + MDP_OUTP(MDP_BASE + 0x50364, 0x2740002c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1cc000c8); + MDP_OUTP(MDP_BASE + 0x50368, 0x26c00029); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1dc000c9); + MDP_OUTP(MDP_BASE + 0x5036c, 0x26000027); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1ec000cc); + MDP_OUTP(MDP_BASE + 0x50370, 0x25000024); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x200000cc); + MDP_OUTP(MDP_BASE + 0x50374, 0x24800021); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x210000cd); + MDP_OUTP(MDP_BASE + 0x50378, 0x23800020); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x220000ce); + MDP_OUTP(MDP_BASE + 0x5037c, 0x2300001d); +} + +static void mdp_load_bc_downscale_table_x_point6TOpoint8(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe000070); + MDP_OUTP(MDP_BASE + 0x50280, 0x4bc00068); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe000078); + MDP_OUTP(MDP_BASE + 0x50284, 0x4bc00060); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe000080); + MDP_OUTP(MDP_BASE + 0x50288, 0x4b800059); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe000089); + MDP_OUTP(MDP_BASE + 0x5028c, 0x4b000052); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe400091); + MDP_OUTP(MDP_BASE + 0x50290, 0x4a80004b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe40009a); + MDP_OUTP(MDP_BASE + 0x50294, 0x4a000044); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe8000a3); + MDP_OUTP(MDP_BASE + 0x50298, 0x4940003d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfec000ac); + MDP_OUTP(MDP_BASE + 0x5029c, 0x48400037); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xff0000b4); + MDP_OUTP(MDP_BASE + 0x502a0, 0x47800031); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xff8000bd); + MDP_OUTP(MDP_BASE + 0x502a4, 0x4640002b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xc5); + MDP_OUTP(MDP_BASE + 0x502a8, 0x45000026); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x8000ce); + MDP_OUTP(MDP_BASE + 0x502ac, 0x43800021); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x10000d6); + MDP_OUTP(MDP_BASE + 0x502b0, 0x4240001c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x18000df); + MDP_OUTP(MDP_BASE + 0x502b4, 0x40800018); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x24000e6); + MDP_OUTP(MDP_BASE + 0x502b8, 0x3f000014); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x30000ee); + MDP_OUTP(MDP_BASE + 0x502bc, 0x3d400010); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x40000f5); + MDP_OUTP(MDP_BASE + 0x502c0, 0x3b80000c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x50000fc); + MDP_OUTP(MDP_BASE + 0x502c4, 0x39800009); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x6000102); + MDP_OUTP(MDP_BASE + 0x502c8, 0x37c00006); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x7000109); + MDP_OUTP(MDP_BASE + 0x502cc, 0x35800004); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x840010e); + MDP_OUTP(MDP_BASE + 0x502d0, 0x33800002); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x9800114); + MDP_OUTP(MDP_BASE + 0x502d4, 0x31400000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xac00119); + MDP_OUTP(MDP_BASE + 0x502d8, 0x2f4003fe); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xc40011e); + MDP_OUTP(MDP_BASE + 0x502dc, 0x2d0003fc); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xdc00121); + MDP_OUTP(MDP_BASE + 0x502e0, 0x2b0003fb); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf400125); + MDP_OUTP(MDP_BASE + 0x502e4, 0x28c003fa); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x11000128); + MDP_OUTP(MDP_BASE + 0x502e8, 0x268003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x12c0012a); + MDP_OUTP(MDP_BASE + 0x502ec, 0x244003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1480012c); + MDP_OUTP(MDP_BASE + 0x502f0, 0x224003f8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1640012e); + MDP_OUTP(MDP_BASE + 0x502f4, 0x200003f8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1800012f); + MDP_OUTP(MDP_BASE + 0x502f8, 0x1e0003f8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1a00012f); + MDP_OUTP(MDP_BASE + 0x502fc, 0x1c0003f8); +} + +static void mdp_load_bc_downscale_table_y_point6TOpoint8(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe000070); + MDP_OUTP(MDP_BASE + 0x50300, 0x4bc00068); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe000078); + MDP_OUTP(MDP_BASE + 0x50304, 0x4bc00060); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe000080); + MDP_OUTP(MDP_BASE + 0x50308, 0x4b800059); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe000089); + MDP_OUTP(MDP_BASE + 0x5030c, 0x4b000052); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe400091); + MDP_OUTP(MDP_BASE + 0x50310, 0x4a80004b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe40009a); + MDP_OUTP(MDP_BASE + 0x50314, 0x4a000044); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe8000a3); + MDP_OUTP(MDP_BASE + 0x50318, 0x4940003d); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfec000ac); + MDP_OUTP(MDP_BASE + 0x5031c, 0x48400037); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xff0000b4); + MDP_OUTP(MDP_BASE + 0x50320, 0x47800031); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xff8000bd); + MDP_OUTP(MDP_BASE + 0x50324, 0x4640002b); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xc5); + MDP_OUTP(MDP_BASE + 0x50328, 0x45000026); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x8000ce); + MDP_OUTP(MDP_BASE + 0x5032c, 0x43800021); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x10000d6); + MDP_OUTP(MDP_BASE + 0x50330, 0x4240001c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x18000df); + MDP_OUTP(MDP_BASE + 0x50334, 0x40800018); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x24000e6); + MDP_OUTP(MDP_BASE + 0x50338, 0x3f000014); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x30000ee); + MDP_OUTP(MDP_BASE + 0x5033c, 0x3d400010); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x40000f5); + MDP_OUTP(MDP_BASE + 0x50340, 0x3b80000c); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x50000fc); + MDP_OUTP(MDP_BASE + 0x50344, 0x39800009); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x6000102); + MDP_OUTP(MDP_BASE + 0x50348, 0x37c00006); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x7000109); + MDP_OUTP(MDP_BASE + 0x5034c, 0x35800004); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x840010e); + MDP_OUTP(MDP_BASE + 0x50350, 0x33800002); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x9800114); + MDP_OUTP(MDP_BASE + 0x50354, 0x31400000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xac00119); + MDP_OUTP(MDP_BASE + 0x50358, 0x2f4003fe); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xc40011e); + MDP_OUTP(MDP_BASE + 0x5035c, 0x2d0003fc); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xdc00121); + MDP_OUTP(MDP_BASE + 0x50360, 0x2b0003fb); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf400125); + MDP_OUTP(MDP_BASE + 0x50364, 0x28c003fa); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x11000128); + MDP_OUTP(MDP_BASE + 0x50368, 0x268003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x12c0012a); + MDP_OUTP(MDP_BASE + 0x5036c, 0x244003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1480012c); + MDP_OUTP(MDP_BASE + 0x50370, 0x224003f8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1640012e); + MDP_OUTP(MDP_BASE + 0x50374, 0x200003f8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1800012f); + MDP_OUTP(MDP_BASE + 0x50378, 0x1e0003f8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0x1a00012f); + MDP_OUTP(MDP_BASE + 0x5037c, 0x1c0003f8); +} + +static void mdp_load_bc_downscale_table_x_point8TO1(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50280, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xff80000d); + MDP_OUTP(MDP_BASE + 0x50284, 0x7ec003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfec0001c); + MDP_OUTP(MDP_BASE + 0x50288, 0x7d4003f3); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe40002b); + MDP_OUTP(MDP_BASE + 0x5028c, 0x7b8003ed); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfd80003c); + MDP_OUTP(MDP_BASE + 0x50290, 0x794003e8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfcc0004d); + MDP_OUTP(MDP_BASE + 0x50294, 0x76c003e4); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfc40005f); + MDP_OUTP(MDP_BASE + 0x50298, 0x73c003e0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfb800071); + MDP_OUTP(MDP_BASE + 0x5029c, 0x708003de); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfac00085); + MDP_OUTP(MDP_BASE + 0x502a0, 0x6d0003db); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfa000098); + MDP_OUTP(MDP_BASE + 0x502a4, 0x698003d9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf98000ac); + MDP_OUTP(MDP_BASE + 0x502a8, 0x654003d8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf8c000c1); + MDP_OUTP(MDP_BASE + 0x502ac, 0x610003d7); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf84000d5); + MDP_OUTP(MDP_BASE + 0x502b0, 0x5c8003d7); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf7c000e9); + MDP_OUTP(MDP_BASE + 0x502b4, 0x580003d7); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf74000fd); + MDP_OUTP(MDP_BASE + 0x502b8, 0x534003d8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6c00112); + MDP_OUTP(MDP_BASE + 0x502bc, 0x4e8003d8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6800126); + MDP_OUTP(MDP_BASE + 0x502c0, 0x494003da); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf600013a); + MDP_OUTP(MDP_BASE + 0x502c4, 0x448003db); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf600014d); + MDP_OUTP(MDP_BASE + 0x502c8, 0x3f4003dd); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf5c00160); + MDP_OUTP(MDP_BASE + 0x502cc, 0x3a4003df); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf5c00172); + MDP_OUTP(MDP_BASE + 0x502d0, 0x354003e1); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf5c00184); + MDP_OUTP(MDP_BASE + 0x502d4, 0x304003e3); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6000195); + MDP_OUTP(MDP_BASE + 0x502d8, 0x2b0003e6); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf64001a6); + MDP_OUTP(MDP_BASE + 0x502dc, 0x260003e8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6c001b4); + MDP_OUTP(MDP_BASE + 0x502e0, 0x214003eb); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf78001c2); + MDP_OUTP(MDP_BASE + 0x502e4, 0x1c4003ee); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf80001cf); + MDP_OUTP(MDP_BASE + 0x502e8, 0x17c003f1); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf90001db); + MDP_OUTP(MDP_BASE + 0x502ec, 0x134003f3); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfa0001e5); + MDP_OUTP(MDP_BASE + 0x502f0, 0xf0003f6); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfb4001ee); + MDP_OUTP(MDP_BASE + 0x502f4, 0xac003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfcc001f5); + MDP_OUTP(MDP_BASE + 0x502f8, 0x70003fb); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe4001fb); + MDP_OUTP(MDP_BASE + 0x502fc, 0x34003fe); +} + +static void mdp_load_bc_downscale_table_y_point8TO1(void) +{ + MDP_OUTP(MDP_BASE + 0x5fffc, 0x0); + MDP_OUTP(MDP_BASE + 0x50300, 0x7fc00000); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xff80000d); + MDP_OUTP(MDP_BASE + 0x50304, 0x7ec003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfec0001c); + MDP_OUTP(MDP_BASE + 0x50308, 0x7d4003f3); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe40002b); + MDP_OUTP(MDP_BASE + 0x5030c, 0x7b8003ed); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfd80003c); + MDP_OUTP(MDP_BASE + 0x50310, 0x794003e8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfcc0004d); + MDP_OUTP(MDP_BASE + 0x50314, 0x76c003e4); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfc40005f); + MDP_OUTP(MDP_BASE + 0x50318, 0x73c003e0); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfb800071); + MDP_OUTP(MDP_BASE + 0x5031c, 0x708003de); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfac00085); + MDP_OUTP(MDP_BASE + 0x50320, 0x6d0003db); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfa000098); + MDP_OUTP(MDP_BASE + 0x50324, 0x698003d9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf98000ac); + MDP_OUTP(MDP_BASE + 0x50328, 0x654003d8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf8c000c1); + MDP_OUTP(MDP_BASE + 0x5032c, 0x610003d7); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf84000d5); + MDP_OUTP(MDP_BASE + 0x50330, 0x5c8003d7); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf7c000e9); + MDP_OUTP(MDP_BASE + 0x50334, 0x580003d7); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf74000fd); + MDP_OUTP(MDP_BASE + 0x50338, 0x534003d8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6c00112); + MDP_OUTP(MDP_BASE + 0x5033c, 0x4e8003d8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6800126); + MDP_OUTP(MDP_BASE + 0x50340, 0x494003da); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf600013a); + MDP_OUTP(MDP_BASE + 0x50344, 0x448003db); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf600014d); + MDP_OUTP(MDP_BASE + 0x50348, 0x3f4003dd); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf5c00160); + MDP_OUTP(MDP_BASE + 0x5034c, 0x3a4003df); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf5c00172); + MDP_OUTP(MDP_BASE + 0x50350, 0x354003e1); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf5c00184); + MDP_OUTP(MDP_BASE + 0x50354, 0x304003e3); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6000195); + MDP_OUTP(MDP_BASE + 0x50358, 0x2b0003e6); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf64001a6); + MDP_OUTP(MDP_BASE + 0x5035c, 0x260003e8); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf6c001b4); + MDP_OUTP(MDP_BASE + 0x50360, 0x214003eb); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf78001c2); + MDP_OUTP(MDP_BASE + 0x50364, 0x1c4003ee); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf80001cf); + MDP_OUTP(MDP_BASE + 0x50368, 0x17c003f1); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xf90001db); + MDP_OUTP(MDP_BASE + 0x5036c, 0x134003f3); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfa0001e5); + MDP_OUTP(MDP_BASE + 0x50370, 0xf0003f6); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfb4001ee); + MDP_OUTP(MDP_BASE + 0x50374, 0xac003f9); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfcc001f5); + MDP_OUTP(MDP_BASE + 0x50378, 0x70003fb); + MDP_OUTP(MDP_BASE + 0x5fffc, 0xfe4001fb); + MDP_OUTP(MDP_BASE + 0x5037c, 0x34003fe); +} + +static int mdp_get_edge_cond(MDPIBUF *iBuf, uint32 *dup, uint32 *dup2) +{ + uint32 reg; + uint32 dst_roi_width; /* Dimensions of DST ROI. */ + uint32 dst_roi_height; /* Used to calculate scaling ratios. */ + + /* + * positions of the luma pixel(relative to the image ) required for + * scaling the ROI + */ + int32 luma_interp_point_left = 0; /* left-most luma pixel needed */ + int32 luma_interp_point_right = 0; /* right-most luma pixel needed */ + int32 luma_interp_point_top = 0; /* top-most luma pixel needed */ + int32 luma_interp_point_bottom = 0; /* bottom-most luma pixel needed */ + + /* + * positions of the chroma pixel(relative to the image ) required for + * interpolating a chroma value at all required luma positions + */ + /* left-most chroma pixel needed */ + int32 chroma_interp_point_left = 0; + /* right-most chroma pixel needed */ + int32 chroma_interp_point_right = 0; + /* top-most chroma pixel needed */ + int32 chroma_interp_point_top = 0; + /* bottom-most chroma pixel needed */ + int32 chroma_interp_point_bottom = 0; + + /* + * a rectangular region within the chroma plane of the "image". + * Chroma pixels falling inside of this rectangle belongs to the ROI + */ + int32 chroma_bound_left = 0; + int32 chroma_bound_right = 0; + int32 chroma_bound_top = 0; + int32 chroma_bound_bottom = 0; + + /* + * number of chroma pixels to replicate on the left, right, + * top and bottom edge of the ROI. + */ + int32 chroma_repeat_left = 0; + int32 chroma_repeat_right = 0; + int32 chroma_repeat_top = 0; + int32 chroma_repeat_bottom = 0; + + /* + * number of luma pixels to replicate on the left, right, + * top and bottom edge of the ROI. + */ + int32 luma_repeat_left = 0; + int32 luma_repeat_right = 0; + int32 luma_repeat_top = 0; + int32 luma_repeat_bottom = 0; + + boolean chroma_edge_enable; + + uint32 _is_scale_enabled = 0; + uint32 _is_yuv_offsite_vertical = 0; + + /* fg edge duplicate */ + reg = 0x0; + + if (iBuf->mdpImg.mdpOp & MDPOP_ASCALE) { /* if scaling enabled */ + + _is_scale_enabled = 1; + + /* + * if rotation mode involves a 90 deg rotation, flip + * dst_roi_width with dst_roi_height. + * Scaling ratios is based on source ROI dimensions, and + * dst ROI dimensions before rotation. + */ + if (iBuf->mdpImg.mdpOp & MDPOP_ROT90) { + dst_roi_width = iBuf->roi.dst_height; + dst_roi_height = iBuf->roi.dst_width; + } else { + dst_roi_width = iBuf->roi.dst_width; + dst_roi_height = iBuf->roi.dst_height; + } + + /* + * Find out the luma pixels needed for scaling in the + * x direction (LEFT and RIGHT). Locations of pixels are + * relative to the ROI. Upper-left corner of ROI corresponds + * to coordinates (0,0). Also set the number of luma pixel + * to repeat. + */ + if (iBuf->roi.width > 3 * dst_roi_width) { + /* scale factor < 1/3 */ + luma_interp_point_left = 0; + luma_interp_point_right = (iBuf->roi.width - 1); + luma_repeat_left = 0; + luma_repeat_right = 0; + } else if (iBuf->roi.width == 3 * dst_roi_width) { + /* scale factor == 1/3 */ + luma_interp_point_left = 0; + luma_interp_point_right = (iBuf->roi.width - 1) + 1; + luma_repeat_left = 0; + luma_repeat_right = 1; + } else if ((iBuf->roi.width > dst_roi_width) && + (iBuf->roi.width < 3 * dst_roi_width)) { + /* 1/3 < scale factor < 1 */ + luma_interp_point_left = -1; + luma_interp_point_right = (iBuf->roi.width - 1) + 1; + luma_repeat_left = 1; + luma_repeat_right = 1; + } + + else if (iBuf->roi.width == dst_roi_width) { + /* scale factor == 1 */ + luma_interp_point_left = -1; + luma_interp_point_right = (iBuf->roi.width - 1) + 2; + luma_repeat_left = 1; + luma_repeat_right = 2; + } else { /* (iBuf->roi.width < dst_roi_width) */ + /* scale factor > 1 */ + luma_interp_point_left = -2; + luma_interp_point_right = (iBuf->roi.width - 1) + 2; + luma_repeat_left = 2; + luma_repeat_right = 2; + } + + /* + * Find out the number of pixels needed for scaling in the + * y direction (TOP and BOTTOM). Locations of pixels are + * relative to the ROI. Upper-left corner of ROI corresponds + * to coordinates (0,0). Also set the number of luma pixel + * to repeat. + */ + if (iBuf->roi.height > 3 * dst_roi_height) { + /* scale factor < 1/3 */ + luma_interp_point_top = 0; + luma_interp_point_bottom = (iBuf->roi.height - 1); + luma_repeat_top = 0; + luma_repeat_bottom = 0; + } else if (iBuf->roi.height == 3 * dst_roi_height) { + /* scale factor == 1/3 */ + luma_interp_point_top = 0; + luma_interp_point_bottom = (iBuf->roi.height - 1) + 1; + luma_repeat_top = 0; + luma_repeat_bottom = 1; + } else if ((iBuf->roi.height > dst_roi_height) && + (iBuf->roi.height < 3 * dst_roi_height)) { + /* 1/3 < scale factor < 1 */ + luma_interp_point_top = -1; + luma_interp_point_bottom = (iBuf->roi.height - 1) + 1; + luma_repeat_top = 1; + luma_repeat_bottom = 1; + } else if (iBuf->roi.height == dst_roi_height) { + /* scale factor == 1 */ + luma_interp_point_top = -1; + luma_interp_point_bottom = (iBuf->roi.height - 1) + 2; + luma_repeat_top = 1; + luma_repeat_bottom = 2; + } else { /* (iBuf->roi.height < dst_roi_height) */ + /* scale factor > 1 */ + luma_interp_point_top = -2; + luma_interp_point_bottom = (iBuf->roi.height - 1) + 2; + luma_repeat_top = 2; + luma_repeat_bottom = 2; + } + } /* if (iBuf->scale.scale_flag) */ + else { /* scaling disabled */ + /* + * Since no scaling needed, Tile Fetch does not require any + * more luma pixel than what the ROI contains. + */ + luma_interp_point_left = (int32) 0; + luma_interp_point_right = (int32) (iBuf->roi.width - 1); + luma_interp_point_top = (int32) 0; + luma_interp_point_bottom = (int32) (iBuf->roi.height - 1); + + luma_repeat_left = 0; + luma_repeat_right = 0; + luma_repeat_top = 0; + luma_repeat_bottom = 0; + } + + /* After adding the ROI offsets, we have locations of + * luma_interp_points relative to the image. + */ + luma_interp_point_left += (int32) (iBuf->roi.x); + luma_interp_point_right += (int32) (iBuf->roi.x); + luma_interp_point_top += (int32) (iBuf->roi.y); + luma_interp_point_bottom += (int32) (iBuf->roi.y); + + /* + * After adding the ROI offsets, we have locations of + * chroma_interp_points relative to the image. + */ + chroma_interp_point_left = luma_interp_point_left; + chroma_interp_point_right = luma_interp_point_right; + chroma_interp_point_top = luma_interp_point_top; + chroma_interp_point_bottom = luma_interp_point_bottom; + + chroma_edge_enable = TRUE; + /* find out which chroma pixels are needed for chroma upsampling. */ + switch (iBuf->mdpImg.imgType) { + /* + * cosite in horizontal axis + * fully sampled in vertical axis + */ + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + case MDP_YCRYCB_H2V1: + /* floor( luma_interp_point_left / 2 ); */ + chroma_interp_point_left = luma_interp_point_left >> 1; + /* floor( ( luma_interp_point_right + 1 ) / 2 ); */ + chroma_interp_point_right = (luma_interp_point_right + 1) >> 1; + + chroma_interp_point_top = luma_interp_point_top; + chroma_interp_point_bottom = luma_interp_point_bottom; + break; + + /* + * cosite in horizontal axis + * offsite in vertical axis + */ + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + /* floor( luma_interp_point_left / 2) */ + chroma_interp_point_left = luma_interp_point_left >> 1; + + /* floor( ( luma_interp_point_right + 1 )/ 2 ) */ + chroma_interp_point_right = (luma_interp_point_right + 1) >> 1; + + /* floor( (luma_interp_point_top - 1 ) / 2 ) */ + chroma_interp_point_top = (luma_interp_point_top - 1) >> 1; + + /* floor( ( luma_interp_point_bottom + 1 ) / 2 ) */ + chroma_interp_point_bottom = + (luma_interp_point_bottom + 1) >> 1; + + _is_yuv_offsite_vertical = 1; + break; + + default: + chroma_edge_enable = FALSE; + chroma_interp_point_left = luma_interp_point_left; + chroma_interp_point_right = luma_interp_point_right; + chroma_interp_point_top = luma_interp_point_top; + chroma_interp_point_bottom = luma_interp_point_bottom; + + break; + } + + /* only if the image type is in YUV domain, we calculate chroma edge */ + if (chroma_edge_enable) { + /* Defines which chroma pixels belongs to the roi */ + switch (iBuf->mdpImg.imgType) { + /* + * Cosite in horizontal direction, and fully sampled + * in vertical direction. + */ + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + case MDP_YCRYCB_H2V1: + /* + * width of chroma ROI is 1/2 of size of luma ROI + * height of chroma ROI same as size of luma ROI + */ + chroma_bound_left = iBuf->roi.x / 2; + + /* there are half as many chroma pixel as luma pixels */ + chroma_bound_right = + (iBuf->roi.width + iBuf->roi.x - 1) / 2; + chroma_bound_top = iBuf->roi.y; + chroma_bound_bottom = + (iBuf->roi.height + iBuf->roi.y - 1); + break; + + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + /* + * cosite in horizontal dir, and offsite in vertical dir + * width of chroma ROI is 1/2 of size of luma ROI + * height of chroma ROI is 1/2 of size of luma ROI + */ + + chroma_bound_left = iBuf->roi.x / 2; + chroma_bound_right = + (iBuf->roi.width + iBuf->roi.x - 1) / 2; + chroma_bound_top = iBuf->roi.y / 2; + chroma_bound_bottom = + (iBuf->roi.height + iBuf->roi.y - 1) / 2; + break; + + default: + /* + * If no valid chroma sub-sampling format specified, + * assume 4:4:4 ( i.e. fully sampled). Set ROI + * boundaries for chroma same as ROI boundaries for + * luma. + */ + chroma_bound_left = iBuf->roi.x; + chroma_bound_right = iBuf->roi.width + iBuf->roi.x - 1; + chroma_bound_top = iBuf->roi.y; + chroma_bound_bottom = + (iBuf->roi.height + iBuf->roi.y - 1); + break; + } + + /* + * Knowing which chroma pixels are needed, and which chroma + * pixels belong to the ROI (i.e. available for fetching ), + * calculate how many chroma pixels Tile Fetch needs to + * duplicate. If any required chroma pixels falls outside + * of the ROI, Tile Fetch must obtain them by replicating + * pixels. + */ + if (chroma_bound_left > chroma_interp_point_left) + chroma_repeat_left = + chroma_bound_left - chroma_interp_point_left; + else + chroma_repeat_left = 0; + + if (chroma_interp_point_right > chroma_bound_right) + chroma_repeat_right = + chroma_interp_point_right - chroma_bound_right; + else + chroma_repeat_right = 0; + + if (chroma_bound_top > chroma_interp_point_top) + chroma_repeat_top = + chroma_bound_top - chroma_interp_point_top; + else + chroma_repeat_top = 0; + + if (chroma_interp_point_bottom > chroma_bound_bottom) + chroma_repeat_bottom = + chroma_interp_point_bottom - chroma_bound_bottom; + else + chroma_repeat_bottom = 0; + + if (_is_scale_enabled && (iBuf->roi.height == 1) + && _is_yuv_offsite_vertical) { + chroma_repeat_bottom = 3; + chroma_repeat_top = 0; + } + } + /* make sure chroma repeats are non-negative */ + if ((chroma_repeat_left < 0) || (chroma_repeat_right < 0) || + (chroma_repeat_top < 0) || (chroma_repeat_bottom < 0)) + return -1; + + /* make sure chroma repeats are no larger than 3 pixels */ + if ((chroma_repeat_left > 3) || (chroma_repeat_right > 3) || + (chroma_repeat_top > 3) || (chroma_repeat_bottom > 3)) + return -1; + + /* make sure luma repeats are non-negative */ + if ((luma_repeat_left < 0) || (luma_repeat_right < 0) || + (luma_repeat_top < 0) || (luma_repeat_bottom < 0)) + return -1; + + /* make sure luma repeats are no larger than 3 pixels */ + if ((luma_repeat_left > 3) || (luma_repeat_right > 3) || + (luma_repeat_top > 3) || (luma_repeat_bottom > 3)) + return -1; + + /* write chroma_repeat_left to register */ + reg |= (chroma_repeat_left & 3) << MDP_LEFT_CHROMA; + + /* write chroma_repeat_right to register */ + reg |= (chroma_repeat_right & 3) << MDP_RIGHT_CHROMA; + + /* write chroma_repeat_top to register */ + reg |= (chroma_repeat_top & 3) << MDP_TOP_CHROMA; + + /* write chroma_repeat_bottom to register */ + reg |= (chroma_repeat_bottom & 3) << MDP_BOTTOM_CHROMA; + + /* write luma_repeat_left to register */ + reg |= (luma_repeat_left & 3) << MDP_LEFT_LUMA; + + /* write luma_repeat_right to register */ + reg |= (luma_repeat_right & 3) << MDP_RIGHT_LUMA; + + /* write luma_repeat_top to register */ + reg |= (luma_repeat_top & 3) << MDP_TOP_LUMA; + + /* write luma_repeat_bottom to register */ + reg |= (luma_repeat_bottom & 3) << MDP_BOTTOM_LUMA; + + /* done with reg */ + *dup = reg; + + /* bg edge duplicate */ + reg = 0x0; + + switch (iBuf->ibuf_type) { + case MDP_Y_CBCR_H2V2: + case MDP_Y_CRCB_H2V2: + /* + * Edge condition for MDP_Y_CRCB/CBCR_H2V2 cosite only. + * For 420 cosite, 1 chroma replicated on all sides except + * left, so reg 101b8 should be 0x0209. For 420 offsite, + * 1 chroma replicated all sides. + */ + if (iBuf->roi.lcd_y == 0) { + reg |= BIT(MDP_TOP_CHROMA); + } + + if ((iBuf->roi.lcd_y + iBuf->roi.dst_height) == + iBuf->ibuf_height) { + reg |= BIT(MDP_BOTTOM_CHROMA); + } + + if (((iBuf->roi.lcd_x + iBuf->roi.dst_width) == + iBuf->ibuf_width) && ((iBuf->roi.dst_width % 2) == 0)) { + reg |= BIT(MDP_RIGHT_CHROMA); + } + + break; + + case MDP_Y_CBCR_H2V1: + case MDP_Y_CRCB_H2V1: + case MDP_YCRYCB_H2V1: + if (((iBuf->roi.lcd_x + iBuf->roi.dst_width) == + iBuf->ibuf_width) && ((iBuf->roi.dst_width % 2) == 0)) { + reg |= BIT(MDP_RIGHT_CHROMA); + } + break; + default: + break; + } + + *dup2 = reg; + + return 0; +} + +#define ADJUST_IP /* for 1/3 scale factor fix */ + +static int mdp_calc_scale_params( +/* ROI origin coordinate for the dimension */ + uint32 org, +/* src ROI dimension */ + uint32 dim_in, +/* scaled ROI dimension*/ + uint32 dim_out, +/* is this ROI width dimension? */ + boolean is_W, +/* initial phase location address */ + int32 *phase_init_ptr, +/* phase increment location address */ + uint32 *phase_step_ptr, +/* ROI start over-fetch location address */ + uint32 *num_repl_beg_ptr, +/* ROI end over-fetch location address */ + uint32 *num_repl_end_ptr) +{ + boolean rpa_on = FALSE; + int init_phase = 0; + uint32 beg_of = 0; + uint32 end_of = 0; + uint64 numer = 0; + uint64 denom = 0; + /*uint64 inverter = 1; */ + int64 point5 = 1; + int64 one = 1; + int64 k1, k2, k3, k4; /* linear equation coefficients */ + uint64 int_mask; + uint64 fract_mask; + uint64 Os; + int64 Osprime; + int64 Od; + int64 Odprime; + int64 Oreq; + uint64 Es; + uint64 Ed; + uint64 Ereq; +#ifdef ADJUST_IP + int64 IP64; + int64 delta; +#endif + uint32 mult; + + /* + * The phase accumulator should really be rational for all cases in a + * general purpose polyphase scaler for a tiled architecture with + * non-zero * origin capability because there is no way to represent + * certain scale factors in fixed point regardless of precision. + * The error incurred in attempting to use fixed point is most + * eggregious for SF where 1/SF is an integral multiple of 1/3. + * + * However, since the MDP2 has already been committed to HW, we + * only use the rational phase accumulator (RPA) when 1/SF is an + * integral multiple of 1/3. This will help minimize regressions in + * matching the HW to the C-Sim. + */ + /* + * Set the RPA flag for this dimension. + * + * In order for 1/SF (dim_in/dim_out) to be an integral multiple of + * 1/3, dim_out must be an integral multiple of 3. + */ + if (!(dim_out % 3)) { + mult = dim_out / 3; + rpa_on = (!(dim_in % mult)); + } + + numer = dim_out; + denom = dim_in; + + /* + * convert to U30.34 before division + * + * The K vectors carry 4 extra bits of precision + * and are rounded. + * + * We initially go 5 bits over then round by adding + * 1 and right shifting by 1 + * so final result is U31.33 + */ + numer <<= PQF_PLUS_5; + + /* now calculate the scale factor (aka k3) */ + k3 = ((mdp_do_div(numer, denom) + 1) >> 1); + + /* check scale factor for legal range [0.25 - 4.0] */ + if (((k3 >> 4) < (1LL << PQF_MINUS_2)) || + ((k3 >> 4) > (1LL << PQF_PLUS_2))) { + return -1; + } + + /* calculate inverse scale factor (aka k1) for phase init */ + numer = dim_in; + denom = dim_out; + numer <<= PQF_PLUS_5; + k1 = ((mdp_do_div(numer, denom) + 1) >> 1); + + /* + * calculate initial phase and ROI overfetch + */ + /* convert point5 & one to S39.24 (will always be positive) */ + point5 <<= (PQF_PLUS_4 - 1); + one <<= PQF_PLUS_4; + k2 = ((k1 - one) >> 1); + init_phase = (int)(k2 >> 4); + k4 = ((k3 - one) >> 1); + if (k3 == one) { + /* the simple case; SF = 1.0 */ + beg_of = 1; + end_of = 2; + } else { + /* calculate the masks */ + fract_mask = one - 1; + int_mask = ~fract_mask; + + if (!rpa_on) { + /* + * FIXED POINT IMPLEMENTATION + */ + if (!org) { + /* A fairly simple case; ROI origin = 0 */ + if (k1 < one) { + /* upscaling */ + beg_of = end_of = 2; + } + /* 0.33 <= SF < 1.0 */ + else if (k1 < (3LL << PQF_PLUS_4)) + beg_of = end_of = 1; + /* 0.33 == SF */ + else if (k1 == (3LL << PQF_PLUS_4)) { + beg_of = 0; + end_of = 1; + } + /* 0.25 <= SF < 0.33 */ + else + beg_of = end_of = 0; + } else { + /* + * The complicated case; ROI origin != 0 + * init_phase needs to be adjusted + * OF is also position dependent + */ + + /* map (org - .5) into destination space */ + Os = ((uint64) org << 1) - 1; + Od = ((k3 * Os) >> 1) + k4; + + /* take the ceiling */ + Odprime = (Od & int_mask); + if (Odprime != Od) + Odprime += one; + + /* now map that back to source space */ + Osprime = (k1 * (Odprime >> PQF_PLUS_4)) + k2; + + /* then floor & decrement to calculate the required + starting coordinate */ + Oreq = (Osprime & int_mask) - one; + + /* calculate end coord in destination space then map to + source space */ + Ed = Odprime + + ((uint64) dim_out << PQF_PLUS_4) - one; + Es = (k1 * (Ed >> PQF_PLUS_4)) + k2; + + /* now floor & increment by 2 to calculate the required + ending coordinate */ + Ereq = (Es & int_mask) + (one << 1); + + /* calculate initial phase */ +#ifdef ADJUST_IP + + IP64 = Osprime - Oreq; + delta = ((int64) (org) << PQF_PLUS_4) - Oreq; + IP64 -= delta; + + /* limit to valid range before the left shift */ + delta = (IP64 & (1LL << 63)) ? 4 : -4; + delta <<= PQF_PLUS_4; + while (abs((int)(IP64 >> PQF_PLUS_4)) > 4) + IP64 += delta; + + /* right shift to account for extra bits of precision */ + init_phase = (int)(IP64 >> 4); + +#else /* ADJUST_IP */ + + /* just calculate the real initial phase */ + init_phase = (int)((Osprime - Oreq) >> 4); + +#endif /* ADJUST_IP */ + + /* calculate the overfetch */ + beg_of = org - (uint32) (Oreq >> PQF_PLUS_4); + end_of = + (uint32) (Ereq >> PQF_PLUS_4) - (org + + dim_in - + 1); + } + } else { + /* + * RPA IMPLEMENTATION + * + * init_phase needs to be calculated in all RPA_on cases + * because it's a numerator, not a fixed point value. + */ + + /* map (org - .5) into destination space */ + Os = ((uint64) org << PQF_PLUS_4) - point5; + Od = mdp_do_div((dim_out * (Os + point5)), + dim_in) - point5; + + /* take the ceiling */ + Odprime = (Od & int_mask); + if (Odprime != Od) + Odprime += one; + + /* now map that back to source space */ + Osprime = + mdp_do_div((dim_in * (Odprime + point5)), + dim_out) - point5; + + /* then floor & decrement to calculate the required + starting coordinate */ + Oreq = (Osprime & int_mask) - one; + + /* calculate end coord in destination space then map to + source space */ + Ed = Odprime + ((uint64) dim_out << PQF_PLUS_4) - one; + Es = mdp_do_div((dim_in * (Ed + point5)), + dim_out) - point5; + + /* now floor & increment by 2 to calculate the required + ending coordinate */ + Ereq = (Es & int_mask) + (one << 1); + + /* calculate initial phase */ + +#ifdef ADJUST_IP + + IP64 = Osprime - Oreq; + delta = ((int64) (org) << PQF_PLUS_4) - Oreq; + IP64 -= delta; + + /* limit to valid range before the left shift */ + delta = (IP64 & (1LL << 63)) ? 4 : -4; + delta <<= PQF_PLUS_4; + while (abs((int)(IP64 >> PQF_PLUS_4)) > 4) + IP64 += delta; + + /* right shift to account for extra bits of precision */ + init_phase = (int)(IP64 >> 4); + +#else /* ADJUST_IP */ + + /* just calculate the real initial phase */ + init_phase = (int)((Osprime - Oreq) >> 4); + +#endif /* ADJUST_IP */ + + /* calculate the overfetch */ + beg_of = org - (uint32) (Oreq >> PQF_PLUS_4); + end_of = + (uint32) (Ereq >> PQF_PLUS_4) - (org + dim_in - 1); + } + } + + /* return the scale parameters */ + *phase_init_ptr = init_phase; + *phase_step_ptr = (uint32) (k1 >> 4); + *num_repl_beg_ptr = beg_of; + *num_repl_end_ptr = end_of; + + return 0; +} + +static uint8 *mdp_adjust_rot_addr(MDPIBUF *iBuf, uint8 *addr, uint32 uv) +{ + uint32 dest_ystride = iBuf->ibuf_width * iBuf->bpp; + uint32 h_slice = 1; + + if (uv && ((iBuf->ibuf_type == MDP_Y_CBCR_H2V2) || + (iBuf->ibuf_type == MDP_Y_CRCB_H2V2))) + h_slice = 2; + + if (MDP_CHKBIT(iBuf->mdpImg.mdpOp, MDPOP_ROT90) ^ + MDP_CHKBIT(iBuf->mdpImg.mdpOp, MDPOP_LR)) { + addr = + addr + (iBuf->roi.dst_width - + MIN(16, iBuf->roi.dst_width)) * iBuf->bpp; + } + if (MDP_CHKBIT(iBuf->mdpImg.mdpOp, MDPOP_UD)) { + addr = + addr + ((iBuf->roi.dst_height - + MIN(16, iBuf->roi.dst_height))/h_slice) * dest_ystride; + } + + return addr; +} + +void mdp_set_scale(MDPIBUF *iBuf, + uint32 dst_roi_width, + uint32 dst_roi_height, + boolean inputRGB, boolean outputRGB, uint32 *pppop_reg_ptr) +{ + uint32 dst_roi_width_scale; + uint32 dst_roi_height_scale; + boolean use_pr; + uint32 phasex_step = 0; + uint32 phasey_step = 0; + int32 phasex_init = 0; + int32 phasey_init = 0; + uint32 lines_dup = 0; + uint32 lines_dup_bg = 0; + uint32 dummy; + uint32 mdp_blur = 0; + + if (iBuf->mdpImg.mdpOp & MDPOP_ASCALE) { + if (iBuf->mdpImg.mdpOp & MDPOP_ROT90) { + dst_roi_width_scale = dst_roi_height; + dst_roi_height_scale = dst_roi_width; + } else { + dst_roi_width_scale = dst_roi_width; + dst_roi_height_scale = dst_roi_height; + } + + mdp_blur = iBuf->mdpImg.mdpOp & MDPOP_BLUR; + + if ((dst_roi_width_scale != iBuf->roi.width) || + (dst_roi_height_scale != iBuf->roi.height) || + mdp_blur) { + *pppop_reg_ptr |= + (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON); + + /* let's use SHIM logic to calculate the partial ROI scaling */ +#if 0 + phasex_step = + (uint32) mdp_do_div(0x20000000 * iBuf->roi.width, + dst_roi_width_scale); + phasey_step = + (uint32) mdp_do_div(0x20000000 * iBuf->roi.height, + dst_roi_height_scale); + +/* + phasex_step= ((long long) iBuf->roi.width * 0x20000000)/dst_roi_width_scale; + phasey_step= ((long long)iBuf->roi.height * 0x20000000)/dst_roi_height_scale; +*/ + + phasex_init = + (((long long)phasex_step - 0x20000000) >> 1); + phasey_init = + (((long long)phasey_step - 0x20000000) >> 1); + +#else + mdp_calc_scale_params(iBuf->roi.x, iBuf->roi.width, + dst_roi_width_scale, 1, + &phasex_init, &phasex_step, + &dummy, &dummy); + mdp_calc_scale_params(iBuf->roi.y, iBuf->roi.height, + dst_roi_height_scale, 0, + &phasey_init, &phasey_step, + &dummy, &dummy); +#endif + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x013c, + phasex_init); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0140, + phasey_init); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0144, + phasex_step); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0148, + phasey_step); + + /* disable the pixel repeat option for scaling */ + use_pr = false; + + if ((dst_roi_width_scale > iBuf->roi.width) || + (dst_roi_height_scale > iBuf->roi.height)) { + if ((use_pr) + && (mdp_curr_up_scale_xy != + MDP_PR_SCALE_UP)) { + mdp_load_pr_upscale_table(); + mdp_curr_up_scale_xy = MDP_PR_SCALE_UP; + } else if ((!use_pr) + && (mdp_curr_up_scale_xy != + MDP_BC_SCALE_UP)) { + mdp_load_bc_upscale_table(); + mdp_curr_up_scale_xy = MDP_BC_SCALE_UP; + } + } + + if (mdp_blur) { + load_scale_table(mdp_gaussian_blur_table, + ARRAY_SIZE(mdp_gaussian_blur_table)); + mdp_curr_down_scale_x = MDP_SCALE_BLUR; + mdp_curr_down_scale_y = MDP_SCALE_BLUR; + } + + /* 0.2 < x <= 1 scaling factor */ + if ((dst_roi_width_scale <= iBuf->roi.width) && + !mdp_blur) { + if (((dst_roi_width_scale * 10) / + iBuf->roi.width) > 8) { + if ((use_pr) + && (mdp_curr_down_scale_x != + MDP_PR_SCALE_POINT8_1)) { + mdp_load_pr_downscale_table_x_point8TO1 + (); + mdp_curr_down_scale_x = + MDP_PR_SCALE_POINT8_1; + } else if ((!use_pr) + && (mdp_curr_down_scale_x != + MDP_BC_SCALE_POINT8_1)) { + mdp_load_bc_downscale_table_x_point8TO1 + (); + mdp_curr_down_scale_x = + MDP_BC_SCALE_POINT8_1; + } + } else + if (((dst_roi_width_scale * 10) / + iBuf->roi.width) > 6) { + if ((use_pr) + && (mdp_curr_down_scale_x != + MDP_PR_SCALE_POINT6_POINT8)) { + mdp_load_pr_downscale_table_x_point6TOpoint8 + (); + mdp_curr_down_scale_x = + MDP_PR_SCALE_POINT6_POINT8; + } else if ((!use_pr) + && (mdp_curr_down_scale_x != + MDP_BC_SCALE_POINT6_POINT8)) + { + mdp_load_bc_downscale_table_x_point6TOpoint8 + (); + mdp_curr_down_scale_x = + MDP_BC_SCALE_POINT6_POINT8; + } + } else + if (((dst_roi_width_scale * 10) / + iBuf->roi.width) > 4) { + if ((use_pr) + && (mdp_curr_down_scale_x != + MDP_PR_SCALE_POINT4_POINT6)) { + mdp_load_pr_downscale_table_x_point4TOpoint6 + (); + mdp_curr_down_scale_x = + MDP_PR_SCALE_POINT4_POINT6; + } else if ((!use_pr) + && (mdp_curr_down_scale_x != + MDP_BC_SCALE_POINT4_POINT6)) + { + mdp_load_bc_downscale_table_x_point4TOpoint6 + (); + mdp_curr_down_scale_x = + MDP_BC_SCALE_POINT4_POINT6; + } + } else { + if ((use_pr) + && (mdp_curr_down_scale_x != + MDP_PR_SCALE_POINT2_POINT4)) { + mdp_load_pr_downscale_table_x_point2TOpoint4 + (); + mdp_curr_down_scale_x = + MDP_PR_SCALE_POINT2_POINT4; + } else if ((!use_pr) + && (mdp_curr_down_scale_x != + MDP_BC_SCALE_POINT2_POINT4)) + { + mdp_load_bc_downscale_table_x_point2TOpoint4 + (); + mdp_curr_down_scale_x = + MDP_BC_SCALE_POINT2_POINT4; + } + } + } + /* 0.2 < y <= 1 scaling factor */ + if ((dst_roi_height_scale <= iBuf->roi.height) && + !mdp_blur) { + if (((dst_roi_height_scale * 10) / + iBuf->roi.height) > 8) { + if ((use_pr) + && (mdp_curr_down_scale_y != + MDP_PR_SCALE_POINT8_1)) { + mdp_load_pr_downscale_table_y_point8TO1 + (); + mdp_curr_down_scale_y = + MDP_PR_SCALE_POINT8_1; + } else if ((!use_pr) + && (mdp_curr_down_scale_y != + MDP_BC_SCALE_POINT8_1)) { + mdp_load_bc_downscale_table_y_point8TO1 + (); + mdp_curr_down_scale_y = + MDP_BC_SCALE_POINT8_1; + } + } else + if (((dst_roi_height_scale * 10) / + iBuf->roi.height) > 6) { + if ((use_pr) + && (mdp_curr_down_scale_y != + MDP_PR_SCALE_POINT6_POINT8)) { + mdp_load_pr_downscale_table_y_point6TOpoint8 + (); + mdp_curr_down_scale_y = + MDP_PR_SCALE_POINT6_POINT8; + } else if ((!use_pr) + && (mdp_curr_down_scale_y != + MDP_BC_SCALE_POINT6_POINT8)) + { + mdp_load_bc_downscale_table_y_point6TOpoint8 + (); + mdp_curr_down_scale_y = + MDP_BC_SCALE_POINT6_POINT8; + } + } else + if (((dst_roi_height_scale * 10) / + iBuf->roi.height) > 4) { + if ((use_pr) + && (mdp_curr_down_scale_y != + MDP_PR_SCALE_POINT4_POINT6)) { + mdp_load_pr_downscale_table_y_point4TOpoint6 + (); + mdp_curr_down_scale_y = + MDP_PR_SCALE_POINT4_POINT6; + } else if ((!use_pr) + && (mdp_curr_down_scale_y != + MDP_BC_SCALE_POINT4_POINT6)) + { + mdp_load_bc_downscale_table_y_point4TOpoint6 + (); + mdp_curr_down_scale_y = + MDP_BC_SCALE_POINT4_POINT6; + } + } else { + if ((use_pr) + && (mdp_curr_down_scale_y != + MDP_PR_SCALE_POINT2_POINT4)) { + mdp_load_pr_downscale_table_y_point2TOpoint4 + (); + mdp_curr_down_scale_y = + MDP_PR_SCALE_POINT2_POINT4; + } else if ((!use_pr) + && (mdp_curr_down_scale_y != + MDP_BC_SCALE_POINT2_POINT4)) + { + mdp_load_bc_downscale_table_y_point2TOpoint4 + (); + mdp_curr_down_scale_y = + MDP_BC_SCALE_POINT2_POINT4; + } + } + } + } else { + iBuf->mdpImg.mdpOp &= ~(MDPOP_ASCALE); + } + } + /* setting edge condition here after scaling check */ + if (mdp_get_edge_cond(iBuf, &lines_dup, &lines_dup_bg)) + printk(KERN_ERR "msm_fb: mdp_get_edge_cond() error!\n"); + + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01b8, lines_dup); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x01bc, lines_dup_bg); +} + +void mdp_init_scale_table(void) +{ + mdp_curr_up_scale_xy = MDP_INIT_SCALE; + mdp_curr_down_scale_x = MDP_INIT_SCALE; + mdp_curr_down_scale_y = MDP_INIT_SCALE; +} + +void mdp_adjust_start_addr(uint8 **src0, + uint8 **src1, + int v_slice, + int h_slice, + int x, + int y, + uint32 width, + uint32 height, int bpp, MDPIBUF *iBuf, int layer) +{ + *src0 += (x + y * width) * bpp; + + /* if it's dest/bg buffer, we need to adjust it for rotation */ + if (layer != 0) + *src0 = mdp_adjust_rot_addr(iBuf, *src0, 0); + + if (*src1) { + /* + * MDP_Y_CBCR_H2V2/MDP_Y_CRCB_H2V2 cosite for now + * we need to shift x direction same as y dir for offsite + */ + *src1 += + ((x / h_slice) * h_slice + + ((y == 0) ? 0 : ((y + 1) / v_slice - 1) * width)) * bpp; + + /* if it's dest/bg buffer, we need to adjust it for rotation */ + if (layer != 0) + *src1 = mdp_adjust_rot_addr(iBuf, *src1, 1); + } +} + +void mdp_set_blend_attr(MDPIBUF *iBuf, + uint32 *alpha, + uint32 *tpVal, + uint32 perPixelAlpha, uint32 *pppop_reg_ptr) +{ + if (perPixelAlpha) { + *pppop_reg_ptr |= PPP_OP_ROT_ON | + PPP_OP_BLEND_ON | PPP_OP_BLEND_SRCPIXEL_ALPHA; + } else { + if ((iBuf->mdpImg.mdpOp & MDPOP_ALPHAB) + && (iBuf->mdpImg.alpha == 0xff)) { + iBuf->mdpImg.mdpOp &= ~(MDPOP_ALPHAB); + } + + if ((iBuf->mdpImg.mdpOp & MDPOP_ALPHAB) + && (iBuf->mdpImg.mdpOp & MDPOP_TRANSP)) { + *pppop_reg_ptr |= + PPP_OP_ROT_ON | PPP_OP_BLEND_ON | + PPP_OP_BLEND_CONSTANT_ALPHA | + PPP_OP_BLEND_ALPHA_BLEND_NORMAL | + PPP_BLEND_CALPHA_TRNASP; + + *alpha = iBuf->mdpImg.alpha; + *tpVal = iBuf->mdpImg.tpVal; + } else { + if (iBuf->mdpImg.mdpOp & MDPOP_TRANSP) { + *pppop_reg_ptr |= PPP_OP_ROT_ON | + PPP_OP_BLEND_ON | + PPP_OP_BLEND_SRCPIXEL_TRANSP; + *tpVal = iBuf->mdpImg.tpVal; + } else if (iBuf->mdpImg.mdpOp & MDPOP_ALPHAB) { + *pppop_reg_ptr |= PPP_OP_ROT_ON | + PPP_OP_BLEND_ON | + PPP_OP_BLEND_ALPHA_BLEND_NORMAL | + PPP_OP_BLEND_CONSTANT_ALPHA; + *alpha = iBuf->mdpImg.alpha; + } + } + } +} diff --git a/drivers/video/msm/mdp_ppp_v31.c b/drivers/video/msm/mdp_ppp_v31.c new file mode 100644 index 00000000000..ee6af534877 --- /dev/null +++ b/drivers/video/msm/mdp_ppp_v31.c @@ -0,0 +1,844 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" + +#include +#include + +#include +#include +#include +#include + +#include "mdp.h" +#include "msm_fb.h" + +#define MDP_SCALE_COEFF_NUM 32 +#define MDP_SCALE_0P2_TO_0P4_INDEX 0 +#define MDP_SCALE_0P4_TO_0P6_INDEX 32 +#define MDP_SCALE_0P6_TO_0P8_INDEX 64 +#define MDP_SCALE_0P8_TO_8P0_INDEX 96 +#define MDP_SCALE_COEFF_MASK 0x3ff + +#define MDP_SCALE_PR 0 +#define MDP_SCALE_FIR 1 + +static uint32 mdp_scale_0p8_to_8p0_mode; +static uint32 mdp_scale_0p6_to_0p8_mode; +static uint32 mdp_scale_0p4_to_0p6_mode; +static uint32 mdp_scale_0p2_to_0p4_mode; + +/* -------- All scaling range, "pixel repeat" -------- */ +static int16 mdp_scale_pixel_repeat_C0[MDP_SCALE_COEFF_NUM] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static int16 mdp_scale_pixel_repeat_C1[MDP_SCALE_COEFF_NUM] = { + 511, 511, 511, 511, 511, 511, 511, 511, + 511, 511, 511, 511, 511, 511, 511, 511, + 511, 511, 511, 511, 511, 511, 511, 511, + 511, 511, 511, 511, 511, 511, 511, 511 +}; + +static int16 mdp_scale_pixel_repeat_C2[MDP_SCALE_COEFF_NUM] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static int16 mdp_scale_pixel_repeat_C3[MDP_SCALE_COEFF_NUM] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* --------------------------- FIR ------------------------------------- */ +/* -------- Downscale, ranging from 0.8x to 8.0x of original size -------- */ + +static int16 mdp_scale_0p8_to_8p0_C0[MDP_SCALE_COEFF_NUM] = { + 0, -7, -13, -19, -24, -28, -32, -34, -37, -39, + -40, -41, -41, -41, -40, -40, -38, -37, -35, -33, + -31, -29, -26, -24, -21, -18, -15, -13, -10, -7, + -5, -2 +}; + +static int16 mdp_scale_0p8_to_8p0_C1[MDP_SCALE_COEFF_NUM] = { + 511, 507, 501, 494, 485, 475, 463, 450, 436, 422, + 405, 388, 370, 352, 333, 314, 293, 274, 253, 233, + 213, 193, 172, 152, 133, 113, 95, 77, 60, 43, + 28, 13 +}; + +static int16 mdp_scale_0p8_to_8p0_C2[MDP_SCALE_COEFF_NUM] = { + 0, 13, 28, 43, 60, 77, 95, 113, 133, 152, + 172, 193, 213, 233, 253, 274, 294, 314, 333, 352, + 370, 388, 405, 422, 436, 450, 463, 475, 485, 494, + 501, 507, +}; + +static int16 mdp_scale_0p8_to_8p0_C3[MDP_SCALE_COEFF_NUM] = { + 0, -2, -5, -7, -10, -13, -15, -18, -21, -24, + -26, -29, -31, -33, -35, -37, -38, -40, -40, -41, + -41, -41, -40, -39, -37, -34, -32, -28, -24, -19, + -13, -7 +}; + +/* -------- Downscale, ranging from 0.6x to 0.8x of original size -------- */ + +static int16 mdp_scale_0p6_to_0p8_C0[MDP_SCALE_COEFF_NUM] = { + 104, 96, 89, 82, 75, 68, 61, 55, 49, 43, + 38, 33, 28, 24, 20, 16, 12, 9, 6, 4, + 2, 0, -2, -4, -5, -6, -7, -7, -8, -8, + -8, -8 +}; + +static int16 mdp_scale_0p6_to_0p8_C1[MDP_SCALE_COEFF_NUM] = { + 303, 303, 302, 300, 298, 296, 293, 289, 286, 281, + 276, 270, 265, 258, 252, 245, 238, 230, 223, 214, + 206, 197, 189, 180, 172, 163, 154, 145, 137, 128, + 120, 112 +}; + +static int16 mdp_scale_0p6_to_0p8_C2[MDP_SCALE_COEFF_NUM] = { + 112, 120, 128, 137, 145, 154, 163, 172, 180, 189, + 197, 206, 214, 223, 230, 238, 245, 252, 258, 265, + 270, 276, 281, 286, 289, 293, 296, 298, 300, 302, + 303, 303 +}; + +static int16 mdp_scale_0p6_to_0p8_C3[MDP_SCALE_COEFF_NUM] = { + -8, -8, -8, -8, -7, -7, -6, -5, -4, -2, + 0, 2, 4, 6, 9, 12, 16, 20, 24, 28, + 33, 38, 43, 49, 55, 61, 68, 75, 82, 89, + 96, 104 +}; + +/* -------- Downscale, ranging from 0.4x to 0.6x of original size -------- */ + +static int16 mdp_scale_0p4_to_0p6_C0[MDP_SCALE_COEFF_NUM] = { + 136, 132, 128, 123, 119, 115, 111, 107, 103, 98, + 95, 91, 87, 84, 80, 76, 73, 69, 66, 62, + 59, 57, 54, 50, 47, 44, 41, 39, 36, 33, + 32, 29 +}; + +static int16 mdp_scale_0p4_to_0p6_C1[MDP_SCALE_COEFF_NUM] = { + 206, 205, 204, 204, 201, 200, 199, 197, 196, 194, + 191, 191, 189, 185, 184, 182, 180, 178, 176, 173, + 170, 168, 165, 162, 160, 157, 155, 152, 148, 146, + 142, 140 +}; + +static int16 mdp_scale_0p4_to_0p6_C2[MDP_SCALE_COEFF_NUM] = { + 140, 142, 146, 148, 152, 155, 157, 160, 162, 165, + 168, 170, 173, 176, 178, 180, 182, 184, 185, 189, + 191, 191, 194, 196, 197, 199, 200, 201, 204, 204, + 205, 206 +}; + +static int16 mdp_scale_0p4_to_0p6_C3[MDP_SCALE_COEFF_NUM] = { + 29, 32, 33, 36, 39, 41, 44, 47, 50, 54, + 57, 59, 62, 66, 69, 73, 76, 80, 84, 87, + 91, 95, 98, 103, 107, 111, 115, 119, 123, 128, + 132, 136 +}; + +/* -------- Downscale, ranging from 0.2x to 0.4x of original size -------- */ + +static int16 mdp_scale_0p2_to_0p4_C0[MDP_SCALE_COEFF_NUM] = { + 131, 131, 130, 129, 128, 127, 127, 126, 125, 125, + 124, 123, 123, 121, 120, 119, 119, 118, 117, 117, + 116, 115, 115, 114, 113, 112, 111, 110, 109, 109, + 108, 107 +}; + +static int16 mdp_scale_0p2_to_0p4_C1[MDP_SCALE_COEFF_NUM] = { + 141, 140, 140, 140, 140, 139, 138, 138, 138, 137, + 137, 137, 136, 137, 137, 137, 136, 136, 136, 135, + 135, 135, 134, 134, 134, 134, 134, 133, 133, 132, + 132, 132 +}; + +static int16 mdp_scale_0p2_to_0p4_C2[MDP_SCALE_COEFF_NUM] = { + 132, 132, 132, 133, 133, 134, 134, 134, 134, 134, + 135, 135, 135, 136, 136, 136, 137, 137, 137, 136, + 137, 137, 137, 138, 138, 138, 139, 140, 140, 140, + 140, 141 +}; + +static int16 mdp_scale_0p2_to_0p4_C3[MDP_SCALE_COEFF_NUM] = { + 107, 108, 109, 109, 110, 111, 112, 113, 114, 115, + 115, 116, 117, 117, 118, 119, 119, 120, 121, 123, + 123, 124, 125, 125, 126, 127, 127, 128, 129, 130, + 131, 131 +}; + +static void mdp_update_scale_table(int index, int16 *c0, int16 *c1, + int16 *c2, int16 *c3) +{ + int i, val; + + for (i = 0; i < MDP_SCALE_COEFF_NUM; i++) { + val = + ((MDP_SCALE_COEFF_MASK & c1[i]) << 16) | + (MDP_SCALE_COEFF_MASK & c0[i]); + writel(val, MDP_PPP_SCALE_COEFF_LSBn(index)); + val = + ((MDP_SCALE_COEFF_MASK & c3[i]) << 16) | + (MDP_SCALE_COEFF_MASK & c2[i]); + writel(val, MDP_PPP_SCALE_COEFF_MSBn(index)); + index++; + } +} + +void mdp_init_scale_table(void) +{ + mdp_scale_0p2_to_0p4_mode = MDP_SCALE_FIR; + mdp_update_scale_table(MDP_SCALE_0P2_TO_0P4_INDEX, + mdp_scale_0p2_to_0p4_C0, + mdp_scale_0p2_to_0p4_C1, + mdp_scale_0p2_to_0p4_C2, + mdp_scale_0p2_to_0p4_C3); + + mdp_scale_0p4_to_0p6_mode = MDP_SCALE_FIR; + mdp_update_scale_table(MDP_SCALE_0P4_TO_0P6_INDEX, + mdp_scale_0p4_to_0p6_C0, + mdp_scale_0p4_to_0p6_C1, + mdp_scale_0p4_to_0p6_C2, + mdp_scale_0p4_to_0p6_C3); + + mdp_scale_0p6_to_0p8_mode = MDP_SCALE_FIR; + mdp_update_scale_table(MDP_SCALE_0P6_TO_0P8_INDEX, + mdp_scale_0p6_to_0p8_C0, + mdp_scale_0p6_to_0p8_C1, + mdp_scale_0p6_to_0p8_C2, + mdp_scale_0p6_to_0p8_C3); + + mdp_scale_0p8_to_8p0_mode = MDP_SCALE_FIR; + mdp_update_scale_table(MDP_SCALE_0P8_TO_8P0_INDEX, + mdp_scale_0p8_to_8p0_C0, + mdp_scale_0p8_to_8p0_C1, + mdp_scale_0p8_to_8p0_C2, + mdp_scale_0p8_to_8p0_C3); +} + +static long long mdp_do_div(long long num, long long den) +{ + do_div(num, den); + return num; +} + +#define SCALER_PHASE_BITS 29 +#define HAL_MDP_PHASE_STEP_2P50 0x50000000 +#define HAL_MDP_PHASE_STEP_1P66 0x35555555 +#define HAL_MDP_PHASE_STEP_1P25 0x28000000 + +struct phase_val { + int phase_init_x; + int phase_init_y; + int phase_step_x; + int phase_step_y; +}; + +static void mdp_calc_scaleInitPhase_3p1(uint32 in_w, + uint32 in_h, + uint32 out_w, + uint32 out_h, + boolean is_rotate, + boolean is_pp_x, + boolean is_pp_y, struct phase_val *pval) +{ + uint64 dst_ROI_width; + uint64 dst_ROI_height; + uint64 src_ROI_width; + uint64 src_ROI_height; + + /* + * phase_step_x, phase_step_y, phase_init_x and phase_init_y + * are represented in fixed-point, unsigned 3.29 format + */ + uint32 phase_step_x = 0; + uint32 phase_step_y = 0; + uint32 phase_init_x = 0; + uint32 phase_init_y = 0; + uint32 yscale_filter_sel, xscale_filter_sel; + uint32 scale_unit_sel_x, scale_unit_sel_y; + + uint64 numerator, denominator; + uint64 temp_dim; + + src_ROI_width = in_w; + src_ROI_height = in_h; + dst_ROI_width = out_w; + dst_ROI_height = out_h; + + /* if there is a 90 degree rotation */ + if (is_rotate) { + /* decide whether to use FIR or M/N for scaling */ + + /* if down-scaling by a factor smaller than 1/4 */ + if ((dst_ROI_height == 1 && src_ROI_width < 4) || + (src_ROI_width < 4 * dst_ROI_height - 3)) + scale_unit_sel_x = 0;/* use FIR scalar */ + else + scale_unit_sel_x = 1;/* use M/N scalar */ + + /* if down-scaling by a factor smaller than 1/4 */ + if ((dst_ROI_width == 1 && src_ROI_height < 4) || + (src_ROI_height < 4 * dst_ROI_width - 3)) + scale_unit_sel_y = 0;/* use FIR scalar */ + else + scale_unit_sel_y = 1;/* use M/N scalar */ + } else { + /* decide whether to use FIR or M/N for scaling */ + if ((dst_ROI_width == 1 && src_ROI_width < 4) || + (src_ROI_width < 4 * dst_ROI_width - 3)) + scale_unit_sel_x = 0;/* use FIR scalar */ + else + scale_unit_sel_x = 1;/* use M/N scalar */ + + if ((dst_ROI_height == 1 && src_ROI_height < 4) || + (src_ROI_height < 4 * dst_ROI_height - 3)) + scale_unit_sel_y = 0;/* use FIR scalar */ + else + scale_unit_sel_y = 1;/* use M/N scalar */ + } + + /* if there is a 90 degree rotation */ + if (is_rotate) { + /* swap the width and height of dst ROI */ + temp_dim = dst_ROI_width; + dst_ROI_width = dst_ROI_height; + dst_ROI_height = temp_dim; + } + + /* calculate phase step for the x direction */ + + /* if destination is only 1 pixel wide, the value of phase_step_x + is unimportant. Assigning phase_step_x to src ROI width + as an arbitrary value. */ + if (dst_ROI_width == 1) + phase_step_x = (uint32) ((src_ROI_width) << SCALER_PHASE_BITS); + + /* if using FIR scalar */ + else if (scale_unit_sel_x == 0) { + + /* Calculate the quotient ( src_ROI_width - 1 ) / ( dst_ROI_width - 1) + with u3.29 precision. Quotient is rounded up to the larger + 29th decimal point. */ + numerator = (src_ROI_width - 1) << SCALER_PHASE_BITS; + denominator = (dst_ROI_width - 1); /* never equals to 0 because of the "( dst_ROI_width == 1 ) case" */ + phase_step_x = (uint32) mdp_do_div((numerator + denominator - 1), denominator); /* divide and round up to the larger 29th decimal point. */ + + } + + /* if M/N scalar */ + else if (scale_unit_sel_x == 1) { + /* Calculate the quotient ( src_ROI_width ) / ( dst_ROI_width) + with u3.29 precision. Quotient is rounded down to the + smaller 29th decimal point. */ + numerator = (src_ROI_width) << SCALER_PHASE_BITS; + denominator = (dst_ROI_width); + phase_step_x = (uint32) mdp_do_div(numerator, denominator); + } + /* calculate phase step for the y direction */ + + /* if destination is only 1 pixel wide, the value of + phase_step_x is unimportant. Assigning phase_step_x + to src ROI width as an arbitrary value. */ + if (dst_ROI_height == 1) + phase_step_y = (uint32) ((src_ROI_height) << SCALER_PHASE_BITS); + + /* if FIR scalar */ + else if (scale_unit_sel_y == 0) { + /* Calculate the quotient ( src_ROI_height - 1 ) / ( dst_ROI_height - 1) + with u3.29 precision. Quotient is rounded up to the larger + 29th decimal point. */ + numerator = (src_ROI_height - 1) << SCALER_PHASE_BITS; + denominator = (dst_ROI_height - 1); /* never equals to 0 because of the "( dst_ROI_height == 1 )" case */ + phase_step_y = (uint32) mdp_do_div((numerator + denominator - 1), denominator); /* Quotient is rounded up to the larger 29th decimal point. */ + + } + + /* if M/N scalar */ + else if (scale_unit_sel_y == 1) { + /* Calculate the quotient ( src_ROI_height ) / ( dst_ROI_height) + with u3.29 precision. Quotient is rounded down to the smaller + 29th decimal point. */ + numerator = (src_ROI_height) << SCALER_PHASE_BITS; + denominator = (dst_ROI_height); + phase_step_y = (uint32) mdp_do_div(numerator, denominator); + } + + /* decide which set of FIR coefficients to use */ + if (phase_step_x > HAL_MDP_PHASE_STEP_2P50) + xscale_filter_sel = 0; + else if (phase_step_x > HAL_MDP_PHASE_STEP_1P66) + xscale_filter_sel = 1; + else if (phase_step_x > HAL_MDP_PHASE_STEP_1P25) + xscale_filter_sel = 2; + else + xscale_filter_sel = 3; + + if (phase_step_y > HAL_MDP_PHASE_STEP_2P50) + yscale_filter_sel = 0; + else if (phase_step_y > HAL_MDP_PHASE_STEP_1P66) + yscale_filter_sel = 1; + else if (phase_step_y > HAL_MDP_PHASE_STEP_1P25) + yscale_filter_sel = 2; + else + yscale_filter_sel = 3; + + /* calculate phase init for the x direction */ + + /* if using FIR scalar */ + if (scale_unit_sel_x == 0) { + if (dst_ROI_width == 1) + phase_init_x = + (uint32) ((src_ROI_width - 1) << SCALER_PHASE_BITS); + else + phase_init_x = 0; + + } + /* M over N scalar */ + else if (scale_unit_sel_x == 1) + phase_init_x = 0; + + /* calculate phase init for the y direction + if using FIR scalar */ + if (scale_unit_sel_y == 0) { + if (dst_ROI_height == 1) + phase_init_y = + (uint32) ((src_ROI_height - + 1) << SCALER_PHASE_BITS); + else + phase_init_y = 0; + + } + /* M over N scalar */ + else if (scale_unit_sel_y == 1) + phase_init_y = 0; + + /* write registers */ + pval->phase_step_x = (uint32) phase_step_x; + pval->phase_step_y = (uint32) phase_step_y; + pval->phase_init_x = (uint32) phase_init_x; + pval->phase_init_y = (uint32) phase_init_y; + + return; +} + +void mdp_set_scale(MDPIBUF *iBuf, + uint32 dst_roi_width, + uint32 dst_roi_height, + boolean inputRGB, boolean outputRGB, uint32 *pppop_reg_ptr) +{ + uint32 dst_roi_width_scale; + uint32 dst_roi_height_scale; + struct phase_val pval; + boolean use_pr; + uint32 ppp_scale_config = 0; + + if (!inputRGB) + ppp_scale_config |= BIT(6); + + if (iBuf->mdpImg.mdpOp & MDPOP_ASCALE) { + if (iBuf->mdpImg.mdpOp & MDPOP_ROT90) { + dst_roi_width_scale = dst_roi_height; + dst_roi_height_scale = dst_roi_width; + } else { + dst_roi_width_scale = dst_roi_width; + dst_roi_height_scale = dst_roi_height; + } + + if ((dst_roi_width_scale != iBuf->roi.width) || + (dst_roi_height_scale != iBuf->roi.height) || + (iBuf->mdpImg.mdpOp & MDPOP_SHARPENING)) { + *pppop_reg_ptr |= + (PPP_OP_SCALE_Y_ON | PPP_OP_SCALE_X_ON); + + mdp_calc_scaleInitPhase_3p1(iBuf->roi.width, + iBuf->roi.height, + dst_roi_width, + dst_roi_height, + iBuf->mdpImg. + mdpOp & MDPOP_ROT90, 1, 1, + &pval); + + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x013c, + pval.phase_init_x); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0140, + pval.phase_init_y); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0144, + pval.phase_step_x); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0148, + pval.phase_step_y); + + /* disable the pixel repeat option for scaling */ + use_pr = false; + + /* x-direction */ + if ((dst_roi_width_scale == iBuf->roi.width) && + !(iBuf->mdpImg.mdpOp & MDPOP_SHARPENING)) { + *pppop_reg_ptr &= ~PPP_OP_SCALE_X_ON; + } else + if (((dst_roi_width_scale * 10) / iBuf->roi.width) > + 8) { + if ((use_pr) + && (mdp_scale_0p8_to_8p0_mode != + MDP_SCALE_PR)) { + mdp_scale_0p8_to_8p0_mode = + MDP_SCALE_PR; + mdp_update_scale_table + (MDP_SCALE_0P8_TO_8P0_INDEX, + mdp_scale_pixel_repeat_C0, + mdp_scale_pixel_repeat_C1, + mdp_scale_pixel_repeat_C2, + mdp_scale_pixel_repeat_C3); + } else if ((!use_pr) + && (mdp_scale_0p8_to_8p0_mode != + MDP_SCALE_FIR)) { + mdp_scale_0p8_to_8p0_mode = + MDP_SCALE_FIR; + mdp_update_scale_table + (MDP_SCALE_0P8_TO_8P0_INDEX, + mdp_scale_0p8_to_8p0_C0, + mdp_scale_0p8_to_8p0_C1, + mdp_scale_0p8_to_8p0_C2, + mdp_scale_0p8_to_8p0_C3); + } + ppp_scale_config |= (SCALE_U1_SET << 2); + } else + if (((dst_roi_width_scale * 10) / iBuf->roi.width) > + 6) { + if ((use_pr) + && (mdp_scale_0p6_to_0p8_mode != + MDP_SCALE_PR)) { + mdp_scale_0p6_to_0p8_mode = + MDP_SCALE_PR; + mdp_update_scale_table + (MDP_SCALE_0P6_TO_0P8_INDEX, + mdp_scale_pixel_repeat_C0, + mdp_scale_pixel_repeat_C1, + mdp_scale_pixel_repeat_C2, + mdp_scale_pixel_repeat_C3); + } else if ((!use_pr) + && (mdp_scale_0p6_to_0p8_mode != + MDP_SCALE_FIR)) { + mdp_scale_0p6_to_0p8_mode = + MDP_SCALE_FIR; + mdp_update_scale_table + (MDP_SCALE_0P6_TO_0P8_INDEX, + mdp_scale_0p6_to_0p8_C0, + mdp_scale_0p6_to_0p8_C1, + mdp_scale_0p6_to_0p8_C2, + mdp_scale_0p6_to_0p8_C3); + } + ppp_scale_config |= (SCALE_D2_SET << 2); + } else + if (((dst_roi_width_scale * 10) / iBuf->roi.width) > + 4) { + if ((use_pr) + && (mdp_scale_0p4_to_0p6_mode != + MDP_SCALE_PR)) { + mdp_scale_0p4_to_0p6_mode = + MDP_SCALE_PR; + mdp_update_scale_table + (MDP_SCALE_0P4_TO_0P6_INDEX, + mdp_scale_pixel_repeat_C0, + mdp_scale_pixel_repeat_C1, + mdp_scale_pixel_repeat_C2, + mdp_scale_pixel_repeat_C3); + } else if ((!use_pr) + && (mdp_scale_0p4_to_0p6_mode != + MDP_SCALE_FIR)) { + mdp_scale_0p4_to_0p6_mode = + MDP_SCALE_FIR; + mdp_update_scale_table + (MDP_SCALE_0P4_TO_0P6_INDEX, + mdp_scale_0p4_to_0p6_C0, + mdp_scale_0p4_to_0p6_C1, + mdp_scale_0p4_to_0p6_C2, + mdp_scale_0p4_to_0p6_C3); + } + ppp_scale_config |= (SCALE_D1_SET << 2); + } else + if ((dst_roi_width_scale == 1 && iBuf->roi.width < 4) || + (iBuf->roi.width < 4 * dst_roi_width_scale - 3)) { + if ((use_pr) + && (mdp_scale_0p2_to_0p4_mode != + MDP_SCALE_PR)) { + mdp_scale_0p2_to_0p4_mode = + MDP_SCALE_PR; + mdp_update_scale_table + (MDP_SCALE_0P2_TO_0P4_INDEX, + mdp_scale_pixel_repeat_C0, + mdp_scale_pixel_repeat_C1, + mdp_scale_pixel_repeat_C2, + mdp_scale_pixel_repeat_C3); + } else if ((!use_pr) + && (mdp_scale_0p2_to_0p4_mode != + MDP_SCALE_FIR)) { + mdp_scale_0p2_to_0p4_mode = + MDP_SCALE_FIR; + mdp_update_scale_table + (MDP_SCALE_0P2_TO_0P4_INDEX, + mdp_scale_0p2_to_0p4_C0, + mdp_scale_0p2_to_0p4_C1, + mdp_scale_0p2_to_0p4_C2, + mdp_scale_0p2_to_0p4_C3); + } + ppp_scale_config |= (SCALE_D0_SET << 2); + } else + ppp_scale_config |= BIT(0); + + /* y-direction */ + if ((dst_roi_height_scale == iBuf->roi.height) && + !(iBuf->mdpImg.mdpOp & MDPOP_SHARPENING)) { + *pppop_reg_ptr &= ~PPP_OP_SCALE_Y_ON; + } else if (((dst_roi_height_scale * 10) / + iBuf->roi.height) > 8) { + if ((use_pr) + && (mdp_scale_0p8_to_8p0_mode != + MDP_SCALE_PR)) { + mdp_scale_0p8_to_8p0_mode = + MDP_SCALE_PR; + mdp_update_scale_table + (MDP_SCALE_0P8_TO_8P0_INDEX, + mdp_scale_pixel_repeat_C0, + mdp_scale_pixel_repeat_C1, + mdp_scale_pixel_repeat_C2, + mdp_scale_pixel_repeat_C3); + } else if ((!use_pr) + && (mdp_scale_0p8_to_8p0_mode != + MDP_SCALE_FIR)) { + mdp_scale_0p8_to_8p0_mode = + MDP_SCALE_FIR; + mdp_update_scale_table + (MDP_SCALE_0P8_TO_8P0_INDEX, + mdp_scale_0p8_to_8p0_C0, + mdp_scale_0p8_to_8p0_C1, + mdp_scale_0p8_to_8p0_C2, + mdp_scale_0p8_to_8p0_C3); + } + ppp_scale_config |= (SCALE_U1_SET << 4); + } else + if (((dst_roi_height_scale * 10) / + iBuf->roi.height) > 6) { + if ((use_pr) + && (mdp_scale_0p6_to_0p8_mode != + MDP_SCALE_PR)) { + mdp_scale_0p6_to_0p8_mode = + MDP_SCALE_PR; + mdp_update_scale_table + (MDP_SCALE_0P6_TO_0P8_INDEX, + mdp_scale_pixel_repeat_C0, + mdp_scale_pixel_repeat_C1, + mdp_scale_pixel_repeat_C2, + mdp_scale_pixel_repeat_C3); + } else if ((!use_pr) + && (mdp_scale_0p6_to_0p8_mode != + MDP_SCALE_FIR)) { + mdp_scale_0p6_to_0p8_mode = + MDP_SCALE_FIR; + mdp_update_scale_table + (MDP_SCALE_0P6_TO_0P8_INDEX, + mdp_scale_0p6_to_0p8_C0, + mdp_scale_0p6_to_0p8_C1, + mdp_scale_0p6_to_0p8_C2, + mdp_scale_0p6_to_0p8_C3); + } + ppp_scale_config |= (SCALE_D2_SET << 4); + } else + if (((dst_roi_height_scale * 10) / + iBuf->roi.height) > 4) { + if ((use_pr) + && (mdp_scale_0p4_to_0p6_mode != + MDP_SCALE_PR)) { + mdp_scale_0p4_to_0p6_mode = + MDP_SCALE_PR; + mdp_update_scale_table + (MDP_SCALE_0P4_TO_0P6_INDEX, + mdp_scale_pixel_repeat_C0, + mdp_scale_pixel_repeat_C1, + mdp_scale_pixel_repeat_C2, + mdp_scale_pixel_repeat_C3); + } else if ((!use_pr) + && (mdp_scale_0p4_to_0p6_mode != + MDP_SCALE_FIR)) { + mdp_scale_0p4_to_0p6_mode = + MDP_SCALE_FIR; + mdp_update_scale_table + (MDP_SCALE_0P4_TO_0P6_INDEX, + mdp_scale_0p4_to_0p6_C0, + mdp_scale_0p4_to_0p6_C1, + mdp_scale_0p4_to_0p6_C2, + mdp_scale_0p4_to_0p6_C3); + } + ppp_scale_config |= (SCALE_D1_SET << 4); + } else if ((dst_roi_height_scale == 1 && + iBuf->roi.height < 4) || + (iBuf->roi.height < 4 * dst_roi_height_scale - 3)) { + if ((use_pr) + && (mdp_scale_0p2_to_0p4_mode != + MDP_SCALE_PR)) { + mdp_scale_0p2_to_0p4_mode = + MDP_SCALE_PR; + mdp_update_scale_table + (MDP_SCALE_0P2_TO_0P4_INDEX, + mdp_scale_pixel_repeat_C0, + mdp_scale_pixel_repeat_C1, + mdp_scale_pixel_repeat_C2, + mdp_scale_pixel_repeat_C3); + } else if ((!use_pr) + && (mdp_scale_0p2_to_0p4_mode != + MDP_SCALE_FIR)) { + mdp_scale_0p2_to_0p4_mode = + MDP_SCALE_FIR; + mdp_update_scale_table + (MDP_SCALE_0P2_TO_0P4_INDEX, + mdp_scale_0p2_to_0p4_C0, + mdp_scale_0p2_to_0p4_C1, + mdp_scale_0p2_to_0p4_C2, + mdp_scale_0p2_to_0p4_C3); + } + ppp_scale_config |= (SCALE_D0_SET << 4); + } else + ppp_scale_config |= BIT(1); + + if (iBuf->mdpImg.mdpOp & MDPOP_SHARPENING) { + ppp_scale_config |= BIT(7); + MDP_OUTP(MDP_BASE + 0x50020, + iBuf->mdpImg.sp_value); + } + + MDP_OUTP(MDP_BASE + 0x10230, ppp_scale_config); + } else { + iBuf->mdpImg.mdpOp &= ~(MDPOP_ASCALE); + } + } +} + +void mdp_adjust_start_addr(uint8 **src0, + uint8 **src1, + int v_slice, + int h_slice, + int x, + int y, + uint32 width, + uint32 height, int bpp, MDPIBUF *iBuf, int layer) +{ + switch (layer) { + case 0: + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0200, (y << 16) | (x)); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0208, + (height << 16) | (width)); + break; + + case 1: + /* MDP 3.1 HW bug workaround */ + if (iBuf->ibuf_type == MDP_YCRYCB_H2V1) { + *src0 += (x + y * width) * bpp; + x = y = 0; + width = iBuf->roi.dst_width; + height = iBuf->roi.dst_height; + } + + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x0204, (y << 16) | (x)); + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x020c, + (height << 16) | (width)); + break; + + case 2: + MDP_OUTP(MDP_CMD_DEBUG_ACCESS_BASE + 0x019c, (y << 16) | (x)); + break; + } +} + +void mdp_set_blend_attr(MDPIBUF *iBuf, + uint32 *alpha, + uint32 *tpVal, + uint32 perPixelAlpha, uint32 *pppop_reg_ptr) +{ + int bg_alpha; + + *alpha = iBuf->mdpImg.alpha; + *tpVal = iBuf->mdpImg.tpVal; + + if (iBuf->mdpImg.mdpOp & MDPOP_FG_PM_ALPHA) { + if (perPixelAlpha) { + *pppop_reg_ptr |= PPP_OP_ROT_ON | + PPP_OP_BLEND_ON | PPP_OP_BLEND_CONSTANT_ALPHA; + } + else { + if ((iBuf->mdpImg.mdpOp & MDPOP_ALPHAB) + && (iBuf->mdpImg.alpha == 0xff)) { + iBuf->mdpImg.mdpOp &= ~(MDPOP_ALPHAB); + } + + if ((iBuf->mdpImg.mdpOp & MDPOP_ALPHAB) + || (iBuf->mdpImg.mdpOp & MDPOP_TRANSP)) { + *pppop_reg_ptr |= + PPP_OP_ROT_ON | PPP_OP_BLEND_ON | + PPP_OP_BLEND_CONSTANT_ALPHA | + PPP_OP_BLEND_ALPHA_BLEND_NORMAL; + } + } + + bg_alpha = PPP_BLEND_BG_USE_ALPHA_SEL | + PPP_BLEND_BG_ALPHA_REVERSE; + + if (perPixelAlpha) + bg_alpha |= PPP_BLEND_BG_SRCPIXEL_ALPHA; + else { + bg_alpha |= PPP_BLEND_BG_CONSTANT_ALPHA; + bg_alpha |= iBuf->mdpImg.alpha << 24; + } + outpdw(MDP_BASE + 0x70010, bg_alpha); + + if (iBuf->mdpImg.mdpOp & MDPOP_TRANSP) + *pppop_reg_ptr |= PPP_BLEND_CALPHA_TRNASP; + } else if (perPixelAlpha) { + *pppop_reg_ptr |= PPP_OP_ROT_ON | + PPP_OP_BLEND_ON | PPP_OP_BLEND_SRCPIXEL_ALPHA; + } else { + if ((iBuf->mdpImg.mdpOp & MDPOP_ALPHAB) + && (iBuf->mdpImg.alpha == 0xff)) { + iBuf->mdpImg.mdpOp &= ~(MDPOP_ALPHAB); + } + + if ((iBuf->mdpImg.mdpOp & MDPOP_ALPHAB) + || (iBuf->mdpImg.mdpOp & MDPOP_TRANSP)) { + *pppop_reg_ptr |= + PPP_OP_ROT_ON | PPP_OP_BLEND_ON | + PPP_OP_BLEND_CONSTANT_ALPHA | + PPP_OP_BLEND_ALPHA_BLEND_NORMAL; + } + + if (iBuf->mdpImg.mdpOp & MDPOP_TRANSP) + *pppop_reg_ptr |= PPP_BLEND_CALPHA_TRNASP; + } +} diff --git a/drivers/video/msm/mdp_vsync.c b/drivers/video/msm/mdp_vsync.c new file mode 100644 index 00000000000..4108c891f08 --- /dev/null +++ b/drivers/video/msm/mdp_vsync.c @@ -0,0 +1,488 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "mdp.h" +#include "msm_fb.h" +#include "mddihost.h" + +#ifdef CONFIG_FB_MSM_MDP40 +#include "mdp4.h" + +#define MDP_SYNC_CFG_0 0x100 +#define MDP_SYNC_STATUS_0 0x10c +#define MDP_SYNC_CFG_1 0x104 +#define MDP_SYNC_STATUS_1 0x110 +#define MDP_PRIM_VSYNC_OUT_CTRL 0x118 +#define MDP_SEC_VSYNC_OUT_CTRL 0x11C +#define MDP_VSYNC_SEL 0x124 +#define MDP_PRIM_VSYNC_INIT_VAL 0x128 +#define MDP_SEC_VSYNC_INIT_VAL 0x12C +#else +#define MDP_SYNC_CFG_0 0x300 +#define MDP_SYNC_STATUS_0 0x30c +#define MDP_PRIM_VSYNC_OUT_CTRL 0x318 +#define MDP_PRIM_VSYNC_INIT_VAL 0x328 +#endif + +extern mddi_lcd_type mddi_lcd_idx; +extern spinlock_t mdp_spin_lock; +extern struct workqueue_struct *mdp_vsync_wq; +extern int lcdc_mode; +extern int vsync_mode; + +#ifdef MDP_HW_VSYNC +int vsync_above_th = 4; +int vsync_start_th = 1; +int vsync_load_cnt; +int vsync_clk_status; +DEFINE_MUTEX(vsync_clk_lock); +DEFINE_MUTEX(vsync_timer_lock); + +static struct clk *mdp_vsync_clk; +static struct msm_fb_data_type *vsync_mfd; +static unsigned char timer_shutdown_flag; + +void mdp_hw_vsync_clk_enable(struct msm_fb_data_type *mfd) +{ + if (vsync_clk_status == 1) + return; + mutex_lock(&vsync_clk_lock); + if (mfd->use_mdp_vsync) { + clk_enable(mdp_vsync_clk); + vsync_clk_status = 1; + } + mutex_unlock(&vsync_clk_lock); +} + +void mdp_hw_vsync_clk_disable(struct msm_fb_data_type *mfd) +{ + if (vsync_clk_status == 0) + return; + mutex_lock(&vsync_clk_lock); + if (mfd->use_mdp_vsync) { + clk_disable(mdp_vsync_clk); + vsync_clk_status = 0; + } + mutex_unlock(&vsync_clk_lock); +} + +static void mdp_set_vsync(unsigned long data); +void mdp_vsync_clk_enable(void) +{ + if (vsync_mfd) { + mdp_hw_vsync_clk_enable(vsync_mfd); + if (!vsync_mfd->vsync_resync_timer.function) { + mdp_set_vsync((unsigned long) vsync_mfd); + } + } +} + +void mdp_vsync_clk_disable(void) +{ + if (vsync_mfd) { + if (vsync_mfd->vsync_resync_timer.function) { + mutex_lock(&vsync_timer_lock); + timer_shutdown_flag = 1; + mutex_unlock(&vsync_timer_lock); + del_timer_sync(&vsync_mfd->vsync_resync_timer); + mutex_lock(&vsync_timer_lock); + timer_shutdown_flag = 0; + mutex_unlock(&vsync_timer_lock); + vsync_mfd->vsync_resync_timer.function = NULL; + } + + mdp_hw_vsync_clk_disable(vsync_mfd); + } +} +#endif + +static void mdp_set_vsync(unsigned long data) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)data; + struct msm_fb_panel_data *pdata = NULL; + + pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; + + vsync_mfd = mfd; + init_timer(&mfd->vsync_resync_timer); + + if ((pdata) && (pdata->set_vsync_notifier == NULL)) + return; + + if ((mfd->panel_info.lcd.vsync_enable) && (mfd->panel_power_on) + && (!mfd->vsync_handler_pending)) { + mfd->vsync_handler_pending = TRUE; + if (!queue_work(mdp_vsync_wq, &mfd->vsync_resync_worker)) { + MSM_FB_INFO + ("mdp_set_vsync: can't queue_work! -> needs to increase vsync_resync_timer_duration\n"); + } + } else { + MSM_FB_DEBUG + ("mdp_set_vsync failed! EN:%d PWR:%d PENDING:%d\n", + mfd->panel_info.lcd.vsync_enable, mfd->panel_power_on, + mfd->vsync_handler_pending); + } + + mutex_lock(&vsync_timer_lock); + if (!timer_shutdown_flag) { + mfd->vsync_resync_timer.function = mdp_set_vsync; + mfd->vsync_resync_timer.data = data; + mfd->vsync_resync_timer.expires = + jiffies + mfd->panel_info.lcd.vsync_notifier_period; + add_timer(&mfd->vsync_resync_timer); + } + mutex_unlock(&vsync_timer_lock); +} + +static void mdp_vsync_handler(void *data) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)data; + + if (vsync_clk_status == 0) { + pr_debug("Warning: vsync clk is disabled\n"); + mfd->vsync_handler_pending = FALSE; + return; + } + + if (mfd->use_mdp_vsync) { +#ifdef MDP_HW_VSYNC + if (mfd->panel_power_on) { + MDP_OUTP(MDP_BASE + MDP_SYNC_STATUS_0, vsync_load_cnt); + +#ifdef CONFIG_FB_MSM_MDP40 + if (mdp_hw_revision < MDP4_REVISION_V2_1) + MDP_OUTP(MDP_BASE + MDP_SYNC_STATUS_1, + vsync_load_cnt); +#endif + } + +#endif + } else { + mfd->last_vsync_timetick = ktime_get_real(); + } + + mfd->vsync_handler_pending = FALSE; +} + +irqreturn_t mdp_hw_vsync_handler_proxy(int irq, void *data) +{ + /* + * ToDo: tried enabling/disabling GPIO MDP HW VSYNC interrupt + * but getting inaccurate timing in mdp_vsync_handler() + * disable_irq(MDP_HW_VSYNC_IRQ); + */ + mdp_vsync_handler(data); + + return IRQ_HANDLED; +} + +#ifdef MDP_HW_VSYNC +static void mdp_set_sync_cfg_0(struct msm_fb_data_type *mfd, int vsync_cnt) +{ + unsigned long cfg; + + cfg = mfd->total_lcd_lines - 1; + cfg <<= MDP_SYNCFG_HGT_LOC; + if (mfd->panel_info.lcd.hw_vsync_mode) + cfg |= MDP_SYNCFG_VSYNC_EXT_EN; + cfg |= (MDP_SYNCFG_VSYNC_INT_EN | vsync_cnt); + + MDP_OUTP(MDP_BASE + MDP_SYNC_CFG_0, cfg); +} + +#ifdef CONFIG_FB_MSM_MDP40 +static void mdp_set_sync_cfg_1(struct msm_fb_data_type *mfd, int vsync_cnt) +{ + unsigned long cfg; + + cfg = mfd->total_lcd_lines - 1; + cfg <<= MDP_SYNCFG_HGT_LOC; + if (mfd->panel_info.lcd.hw_vsync_mode) + cfg |= MDP_SYNCFG_VSYNC_EXT_EN; + cfg |= (MDP_SYNCFG_VSYNC_INT_EN | vsync_cnt); + + MDP_OUTP(MDP_BASE + MDP_SYNC_CFG_1, cfg); +} +#endif +#endif + +void mdp_config_vsync(struct msm_fb_data_type *mfd) +{ + /* vsync on primary lcd only for now */ + if ((mfd->dest != DISPLAY_LCD) || (mfd->panel_info.pdest != DISPLAY_1) + || (!vsync_mode)) { + goto err_handle; + } + + vsync_clk_status = 0; + if (mfd->panel_info.lcd.vsync_enable) { + mfd->total_porch_lines = mfd->panel_info.lcd.v_back_porch + + mfd->panel_info.lcd.v_front_porch + + mfd->panel_info.lcd.v_pulse_width; + mfd->total_lcd_lines = + mfd->panel_info.yres + mfd->total_porch_lines; + mfd->lcd_ref_usec_time = + 100000000 / mfd->panel_info.lcd.refx100; + mfd->vsync_handler_pending = FALSE; + + mfd->last_vsync_timetick.tv64 = 0; + +#ifdef MDP_HW_VSYNC + if (mdp_vsync_clk == NULL) + mdp_vsync_clk = clk_get(NULL, "mdp_vsync_clk"); + + if (IS_ERR(mdp_vsync_clk)) { + printk(KERN_ERR "error: can't get mdp_vsync_clk!\n"); + mfd->use_mdp_vsync = 0; + } else + mfd->use_mdp_vsync = 1; + + if (mfd->use_mdp_vsync) { + uint32 vsync_cnt_cfg, vsync_cnt_cfg_dem; + uint32 mdp_vsync_clk_speed_hz; + + mdp_vsync_clk_speed_hz = clk_get_rate(mdp_vsync_clk); + + if (mdp_vsync_clk_speed_hz == 0) { + mfd->use_mdp_vsync = 0; + } else { + /* + * Do this calculation in 2 steps for + * rounding uint32 properly. + */ + vsync_cnt_cfg_dem = + (mfd->panel_info.lcd.refx100 * + mfd->total_lcd_lines) / 100; + vsync_cnt_cfg = + (mdp_vsync_clk_speed_hz) / + vsync_cnt_cfg_dem; + + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, + FALSE); + mdp_hw_vsync_clk_enable(mfd); + + mdp_set_sync_cfg_0(mfd, vsync_cnt_cfg); + + +#ifdef CONFIG_FB_MSM_MDP40 + if (mdp_hw_revision < MDP4_REVISION_V2_1) + mdp_set_sync_cfg_1(mfd, vsync_cnt_cfg); +#endif + + /* + * load the last line + 1 to be in the + * safety zone + */ + vsync_load_cnt = mfd->panel_info.yres; + + /* line counter init value at the next pulse */ + MDP_OUTP(MDP_BASE + MDP_PRIM_VSYNC_INIT_VAL, + vsync_load_cnt); +#ifdef CONFIG_FB_MSM_MDP40 + if (mdp_hw_revision < MDP4_REVISION_V2_1) { + MDP_OUTP(MDP_BASE + + MDP_SEC_VSYNC_INIT_VAL, vsync_load_cnt); + } +#endif + + /* + * external vsync source pulse width and + * polarity flip + */ + MDP_OUTP(MDP_BASE + MDP_PRIM_VSYNC_OUT_CTRL, + BIT(0)); +#ifdef CONFIG_FB_MSM_MDP40 + if (mdp_hw_revision < MDP4_REVISION_V2_1) { + MDP_OUTP(MDP_BASE + + MDP_SEC_VSYNC_OUT_CTRL, BIT(0)); + MDP_OUTP(MDP_BASE + + MDP_VSYNC_SEL, 0x20); + } +#endif + + /* threshold */ + MDP_OUTP(MDP_BASE + 0x200, + (vsync_above_th << 16) | + (vsync_start_th)); + + mdp_hw_vsync_clk_disable(mfd); + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, + MDP_BLOCK_POWER_OFF, FALSE); + } + } +#else + mfd->use_mdp_vsync = 0; + hrtimer_init(&mfd->dma_hrtimer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + mfd->dma_hrtimer.function = mdp_dma2_vsync_hrtimer_handler; + mfd->vsync_width_boundary = vmalloc(mfd->panel_info.xres * 4); +#endif + +#ifdef CONFIG_FB_MSM_MDDI + mfd->channel_irq = 0; + if (mfd->panel_info.lcd.hw_vsync_mode) { + u32 vsync_gpio = mfd->vsync_gpio; + u32 ret; + + if (vsync_gpio == -1) { + MSM_FB_INFO("vsync_gpio not defined!\n"); + goto err_handle; + } + + ret = gpio_tlmm_config(GPIO_CFG + (vsync_gpio, + (mfd->use_mdp_vsync) ? 1 : 0, + GPIO_CFG_INPUT, + GPIO_CFG_PULL_DOWN, + GPIO_CFG_2MA), + GPIO_CFG_ENABLE); + if (ret) + goto err_handle; + + /* + * if use_mdp_vsync, then no interrupt need since + * mdp_vsync is feed directly to mdp to reset the + * write pointer counter. therefore no irq_handler + * need to reset write pointer counter. + */ + if (!mfd->use_mdp_vsync) { + mfd->channel_irq = MSM_GPIO_TO_INT(vsync_gpio); + if (request_irq + (mfd->channel_irq, + &mdp_hw_vsync_handler_proxy, + IRQF_TRIGGER_FALLING, "VSYNC_GPIO", + (void *)mfd)) { + MSM_FB_INFO + ("irq=%d failed! vsync_gpio=%d\n", + mfd->channel_irq, + vsync_gpio); + goto err_handle; + } + } + } +#endif + mdp_hw_vsync_clk_enable(mfd); + mdp_set_vsync((unsigned long)mfd); + } + + return; + +err_handle: + if (mfd->vsync_width_boundary) + vfree(mfd->vsync_width_boundary); + mfd->panel_info.lcd.vsync_enable = FALSE; + printk(KERN_ERR "%s: failed!\n", __func__); +} + +void mdp_vsync_resync_workqueue_handler(struct work_struct *work) +{ + struct msm_fb_data_type *mfd = NULL; + int vsync_fnc_enabled = FALSE; + struct msm_fb_panel_data *pdata = NULL; + + mfd = container_of(work, struct msm_fb_data_type, vsync_resync_worker); + + if (mfd) { + if (mfd->panel_power_on) { + pdata = + (struct msm_fb_panel_data *)mfd->pdev->dev. + platform_data; + + if (pdata->set_vsync_notifier != NULL) { + if (pdata->clk_func && !pdata->clk_func(2)) { + mfd->vsync_handler_pending = FALSE; + return; + } + + pdata->set_vsync_notifier( + mdp_vsync_handler, + (void *)mfd); + vsync_fnc_enabled = TRUE; + } + } + } + + if ((mfd) && (!vsync_fnc_enabled)) + mfd->vsync_handler_pending = FALSE; +} + +boolean mdp_hw_vsync_set_handler(msm_fb_vsync_handler_type handler, void *data) +{ + /* + * ToDo: tried enabling/disabling GPIO MDP HW VSYNC interrupt + * but getting inaccurate timing in mdp_vsync_handler() + * enable_irq(MDP_HW_VSYNC_IRQ); + */ + + return TRUE; +} + +uint32 mdp_get_lcd_line_counter(struct msm_fb_data_type *mfd) +{ + uint32 elapsed_usec_time; + uint32 lcd_line; + ktime_t last_vsync_timetick_local; + ktime_t curr_time; + unsigned long flag; + + if ((!mfd->panel_info.lcd.vsync_enable) || (!vsync_mode)) + return 0; + + spin_lock_irqsave(&mdp_spin_lock, flag); + last_vsync_timetick_local = mfd->last_vsync_timetick; + spin_unlock_irqrestore(&mdp_spin_lock, flag); + + curr_time = ktime_get_real(); + elapsed_usec_time = ktime_to_us(ktime_sub(curr_time, + last_vsync_timetick_local)); + + elapsed_usec_time = elapsed_usec_time % mfd->lcd_ref_usec_time; + + /* lcd line calculation referencing to line counter = 0 */ + lcd_line = + (elapsed_usec_time * mfd->total_lcd_lines) / mfd->lcd_ref_usec_time; + + /* lcd line adjusment referencing to the actual line counter at vsync */ + lcd_line = + (mfd->total_lcd_lines - mfd->panel_info.lcd.v_back_porch + + lcd_line) % (mfd->total_lcd_lines + 1); + + if (lcd_line > mfd->total_lcd_lines) { + MSM_FB_INFO + ("mdp_get_lcd_line_counter: mdp_lcd_rd_cnt >= mfd->total_lcd_lines error!\n"); + } + + return lcd_line; +} diff --git a/drivers/video/msm/mipi_dsi.c b/drivers/video/msm/mipi_dsi.c new file mode 100644 index 00000000000..61bb3458b77 --- /dev/null +++ b/drivers/video/msm/mipi_dsi.c @@ -0,0 +1,548 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mdp.h" +#include "mdp4.h" + +u32 dsi_irq; + +static int mipi_dsi_probe(struct platform_device *pdev); +static int mipi_dsi_remove(struct platform_device *pdev); + +static int mipi_dsi_off(struct platform_device *pdev); +static int mipi_dsi_on(struct platform_device *pdev); + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; +static struct mipi_dsi_platform_data *mipi_dsi_pdata; + +static int vsync_gpio = -1; + +static struct platform_driver mipi_dsi_driver = { + .probe = mipi_dsi_probe, + .remove = mipi_dsi_remove, + .shutdown = NULL, + .driver = { + .name = "mipi_dsi", + }, +}; + +struct device dsi_dev; + +static int mipi_dsi_off(struct platform_device *pdev) +{ + int ret = 0; + struct msm_fb_data_type *mfd; + struct msm_panel_info *pinfo; + + mfd = platform_get_drvdata(pdev); + pinfo = &mfd->panel_info; + + if (mdp_rev >= MDP_REV_41) + mutex_lock(&mfd->dma->ov_mutex); + else + down(&mfd->dma->mutex); + + mdp4_overlay_dsi_state_set(ST_DSI_SUSPEND); + + /* + * Description: dsi clock is need to perform shutdown. + * mdp4_dsi_cmd_dma_busy_wait() will enable dsi clock if disabled. + * also, wait until dma (overlay and dmap) finish. + */ + if (mfd->panel_info.type == MIPI_CMD_PANEL) { + if (mdp_rev >= MDP_REV_41) { + mdp4_dsi_cmd_dma_busy_wait(mfd); + mdp4_dsi_blt_dmap_busy_wait(mfd); + } else { + mdp3_dsi_cmd_dma_busy_wait(mfd); + } + } + + /* + * Desctiption: change to DSI_CMD_MODE since it needed to + * tx DCS dsiplay off comamnd to panel + */ + mipi_dsi_op_mode_config(DSI_CMD_MODE); + + if (mfd->panel_info.type == MIPI_CMD_PANEL) { + if (pinfo->lcd.vsync_enable) { + if (pinfo->lcd.hw_vsync_mode && vsync_gpio > 0) + gpio_free(vsync_gpio); + + mipi_dsi_set_tear_off(mfd); + } + } + + ret = panel_next_off(pdev); + +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(0); +#endif + /* disbale dsi engine */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0000, 0); + + mipi_dsi_phy_ctrl(0); + + local_bh_disable(); + mipi_dsi_clk_disable(); + local_bh_enable(); + + if (mipi_dsi_pdata && mipi_dsi_pdata->dsi_power_save) + mipi_dsi_pdata->dsi_power_save(0); + + if (mdp_rev >= MDP_REV_41) + mutex_unlock(&mfd->dma->ov_mutex); + else + up(&mfd->dma->mutex); + + pr_debug("%s:\n", __func__); + + return ret; +} + +static int mipi_dsi_on(struct platform_device *pdev) +{ + int ret = 0; + u32 clk_rate; + struct msm_fb_data_type *mfd; + struct fb_info *fbi; + struct fb_var_screeninfo *var; + struct msm_panel_info *pinfo; + struct mipi_panel_info *mipi; + u32 hbp, hfp, vbp, vfp, hspw, vspw, width, height; + u32 ystride, bpp, data; + u32 dummy_xres, dummy_yres; + int target_type = 0; + + mfd = platform_get_drvdata(pdev); + fbi = mfd->fbi; + var = &fbi->var; + pinfo = &mfd->panel_info; + + if (mipi_dsi_pdata && mipi_dsi_pdata->dsi_power_save) + mipi_dsi_pdata->dsi_power_save(1); + + clk_rate = mfd->fbi->var.pixclock; + clk_rate = min(clk_rate, mfd->panel_info.clk_max); + + local_bh_disable(); + mipi_dsi_clk_enable(); + local_bh_enable(); + +#ifndef CONFIG_FB_MSM_MDP303 + mdp4_overlay_dsi_state_set(ST_DSI_RESUME); +#endif + + MIPI_OUTP(MIPI_DSI_BASE + 0x114, 1); + MIPI_OUTP(MIPI_DSI_BASE + 0x114, 0); + + hbp = var->left_margin; + hfp = var->right_margin; + vbp = var->upper_margin; + vfp = var->lower_margin; + hspw = var->hsync_len; + vspw = var->vsync_len; + width = mfd->panel_info.xres; + height = mfd->panel_info.yres; + + mipi_dsi_phy_ctrl(1); + + if (mdp_rev == MDP_REV_42 && mipi_dsi_pdata) + target_type = mipi_dsi_pdata->target_type; + + mipi_dsi_phy_init(0, &(mfd->panel_info), target_type); + + mipi = &mfd->panel_info.mipi; + if (mfd->panel_info.type == MIPI_VIDEO_PANEL) { + dummy_xres = mfd->panel_info.mipi.xres_pad; + dummy_yres = mfd->panel_info.mipi.yres_pad; + + if (mdp_rev >= MDP_REV_41) { + MIPI_OUTP(MIPI_DSI_BASE + 0x20, + ((hspw + hbp + width + dummy_xres) << 16 | + (hspw + hbp))); + MIPI_OUTP(MIPI_DSI_BASE + 0x24, + ((vspw + vbp + height + dummy_yres) << 16 | + (vspw + vbp))); + MIPI_OUTP(MIPI_DSI_BASE + 0x28, + (vspw + vbp + height + dummy_yres + + vfp - 1) << 16 | (hspw + hbp + + width + dummy_xres + hfp - 1)); + } else { + /* DSI_LAN_SWAP_CTRL */ + MIPI_OUTP(MIPI_DSI_BASE + 0x00ac, mipi->dlane_swap); + + MIPI_OUTP(MIPI_DSI_BASE + 0x20, + ((hbp + width + dummy_xres) << 16 | (hbp))); + MIPI_OUTP(MIPI_DSI_BASE + 0x24, + ((vbp + height + dummy_yres) << 16 | (vbp))); + MIPI_OUTP(MIPI_DSI_BASE + 0x28, + (vbp + height + dummy_yres + vfp) << 16 | + (hbp + width + dummy_xres + hfp)); + } + + MIPI_OUTP(MIPI_DSI_BASE + 0x2c, (hspw << 16)); + MIPI_OUTP(MIPI_DSI_BASE + 0x30, 0); + MIPI_OUTP(MIPI_DSI_BASE + 0x34, (vspw << 16)); + + } else { /* command mode */ + if (mipi->dst_format == DSI_CMD_DST_FORMAT_RGB888) + bpp = 3; + else if (mipi->dst_format == DSI_CMD_DST_FORMAT_RGB666) + bpp = 3; + else if (mipi->dst_format == DSI_CMD_DST_FORMAT_RGB565) + bpp = 2; + else + bpp = 3; /* Default format set to RGB888 */ + + ystride = width * bpp + 1; + + /* DSI_COMMAND_MODE_MDP_STREAM_CTRL */ + data = (ystride << 16) | (mipi->vc << 8) | DTYPE_DCS_LWRITE; + MIPI_OUTP(MIPI_DSI_BASE + 0x5c, data); + MIPI_OUTP(MIPI_DSI_BASE + 0x54, data); + + /* DSI_COMMAND_MODE_MDP_STREAM_TOTAL */ + data = height << 16 | width; + MIPI_OUTP(MIPI_DSI_BASE + 0x60, data); + MIPI_OUTP(MIPI_DSI_BASE + 0x58, data); + } + + mipi_dsi_host_init(mipi); + mipi_dsi_cmd_bta_sw_trigger(); /* clean up ack_err_status */ + + ret = panel_next_on(pdev); + + mipi_dsi_op_mode_config(mipi->mode); + + if (mfd->panel_info.type == MIPI_CMD_PANEL) { + if (pinfo->lcd.vsync_enable) { + if (pinfo->lcd.hw_vsync_mode && vsync_gpio > 0) { + if (gpio_request(vsync_gpio, "MDP_VSYNC") == 0) + gpio_direction_input(vsync_gpio); + else + pr_err("%s: unable to request gpio=%d\n", + __func__, vsync_gpio); + } + mipi_dsi_set_tear_on(mfd); + } + } + +#ifdef CONFIG_MSM_BUS_SCALING + mdp_bus_scale_update_request(2); +#endif + return ret; +} + + +static int mipi_dsi_resource_initialized; + +static int mipi_dsi_probe(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct fb_info *fbi; + struct msm_panel_info *pinfo; + struct mipi_panel_info *mipi; + struct platform_device *mdp_dev = NULL; + struct msm_fb_panel_data *pdata = NULL; + int rc; + uint8 lanes = 0, bpp; + uint32 h_period, v_period, dsi_pclk_rate; + + resource_size_t size ; + + if ((pdev->id == 1) && (pdev->num_resources >= 0)) { + mipi_dsi_pdata = pdev->dev.platform_data; + + size = resource_size(&pdev->resource[0]); + mipi_dsi_base = ioremap(pdev->resource[0].start, size); + + MSM_FB_INFO("mipi_dsi base phy_addr = 0x%x virt = 0x%x\n", + pdev->resource[0].start, (int) mipi_dsi_base); + + if (!mipi_dsi_base) + return -ENOMEM; + + if (mdp_rev >= MDP_REV_41) { + mmss_sfpb_base = ioremap(MMSS_SFPB_BASE_PHY, 0x100); + MSM_FB_INFO("mmss_sfpb base phy_addr = 0x%x," + "virt = 0x%x\n", MMSS_SFPB_BASE_PHY, + (int) mmss_sfpb_base); + + if (!mmss_sfpb_base) + return -ENOMEM; + } + + dsi_irq = platform_get_irq(pdev, 0); + if (dsi_irq < 0) { + pr_err("mipi_dsi: can not get mdp irq\n"); + return -ENOMEM; + } + + rc = request_irq(dsi_irq, mipi_dsi_isr, IRQF_DISABLED, + "MIPI_DSI", 0); + if (rc) { + pr_err("mipi_dsi_host request_irq() failed!\n"); + return rc; + } + + disable_irq(dsi_irq); + + if (mdp_rev == MDP_REV_42 && mipi_dsi_pdata && + mipi_dsi_pdata->target_type == 1) { + /* Target type is 1 for device with (De)serializer + * 0x4f00000 is the base for TV Encoder. + * Unused Offset 0x1000 is used for + * (de)serializer on emulation platform + */ + periph_base = ioremap(MMSS_SERDES_BASE_PHY, 0x100); + + if (periph_base) { + pr_debug("periph_base %p\n", periph_base); + writel(0x4, periph_base + 0x28); + writel(0xc, periph_base + 0x28); + } else { + pr_err("periph_base is NULL\n"); + free_irq(dsi_irq, 0); + return -ENOMEM; + } + } + + if (mipi_dsi_pdata) { + vsync_gpio = mipi_dsi_pdata->vsync_gpio; + pr_debug("%s: vsync_gpio=%d\n", __func__, vsync_gpio); + + if (mdp_rev == MDP_REV_303 && + mipi_dsi_pdata->dsi_client_reset) { + if (mipi_dsi_pdata->dsi_client_reset()) + pr_err("%s: DSI Client Reset failed!\n", + __func__); + else + pr_debug("%s: DSI Client Reset success\n", + __func__); + } + } + + mipi_dsi_resource_initialized = 1; + + return 0; + } + + mipi_dsi_clk_init(&pdev->dev); + + if (!mipi_dsi_resource_initialized) + return -EPERM; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; + + + mdp_dev = platform_device_alloc("mdp", pdev->id); + if (!mdp_dev) + return -ENOMEM; + + /* + * link to the latest pdev + */ + mfd->pdev = mdp_dev; + mfd->dest = DISPLAY_LCD; + + /* + * alloc panel device data + */ + if (platform_device_add_data + (mdp_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + pr_err("mipi_dsi_probe: platform_device_add_data failed!\n"); + platform_device_put(mdp_dev); + return -ENOMEM; + } + /* + * data chain + */ + pdata = mdp_dev->dev.platform_data; + pdata->on = mipi_dsi_on; + pdata->off = mipi_dsi_off; + pdata->next = pdev; + + /* + * get/set panel specific fb info + */ + mfd->panel_info = pdata->panel_info; + pinfo = &mfd->panel_info; + + if (mdp_rev == MDP_REV_303 && + mipi_dsi_pdata->get_lane_config) { + if (mipi_dsi_pdata->get_lane_config() != 2) { + pr_info("Changing to DSI Single Mode Configuration\n"); +#ifdef CONFIG_FB_MSM_MDP303 + update_lane_config(pinfo); +#endif + } + } + + if (mfd->index == 0) + mfd->fb_imgType = MSMFB_DEFAULT_TYPE; + else + mfd->fb_imgType = MDP_RGB_565; + + fbi = mfd->fbi; + fbi->var.pixclock = mfd->panel_info.clk_rate; + fbi->var.left_margin = mfd->panel_info.lcdc.h_back_porch; + fbi->var.right_margin = mfd->panel_info.lcdc.h_front_porch; + fbi->var.upper_margin = mfd->panel_info.lcdc.v_back_porch; + fbi->var.lower_margin = mfd->panel_info.lcdc.v_front_porch; + fbi->var.hsync_len = mfd->panel_info.lcdc.h_pulse_width; + fbi->var.vsync_len = mfd->panel_info.lcdc.v_pulse_width; + + h_period = ((mfd->panel_info.lcdc.h_pulse_width) + + (mfd->panel_info.lcdc.h_back_porch) + + (mfd->panel_info.xres) + + (mfd->panel_info.lcdc.h_front_porch)); + + v_period = ((mfd->panel_info.lcdc.v_pulse_width) + + (mfd->panel_info.lcdc.v_back_porch) + + (mfd->panel_info.yres) + + (mfd->panel_info.lcdc.v_front_porch)); + + mipi = &mfd->panel_info.mipi; + + if (mipi->data_lane3) + lanes += 1; + if (mipi->data_lane2) + lanes += 1; + if (mipi->data_lane1) + lanes += 1; + if (mipi->data_lane0) + lanes += 1; + + if ((mipi->dst_format == DSI_CMD_DST_FORMAT_RGB888) + || (mipi->dst_format == DSI_VIDEO_DST_FORMAT_RGB888) + || (mipi->dst_format == DSI_VIDEO_DST_FORMAT_RGB666_LOOSE)) + bpp = 3; + else if ((mipi->dst_format == DSI_CMD_DST_FORMAT_RGB565) + || (mipi->dst_format == DSI_VIDEO_DST_FORMAT_RGB565)) + bpp = 2; + else + bpp = 3; /* Default format set to RGB888 */ + + if (mfd->panel_info.type == MIPI_VIDEO_PANEL && + !mfd->panel_info.clk_rate) { + h_period += mfd->panel_info.mipi.xres_pad; + v_period += mfd->panel_info.mipi.yres_pad; + + if (lanes > 0) { + mfd->panel_info.clk_rate = + ((h_period * v_period * (mipi->frame_rate) * bpp * 8) + / lanes); + } else { + pr_err("%s: forcing mipi_dsi lanes to 1\n", __func__); + mfd->panel_info.clk_rate = + (h_period * v_period + * (mipi->frame_rate) * bpp * 8); + } + } + pll_divider_config.clk_rate = mfd->panel_info.clk_rate; + + rc = mipi_dsi_clk_div_config(bpp, lanes, &dsi_pclk_rate); + if (rc) + goto mipi_dsi_probe_err; + + if ((dsi_pclk_rate < 3300000) || (dsi_pclk_rate > 103300000)) + dsi_pclk_rate = 35000000; + mipi->dsi_pclk_rate = dsi_pclk_rate; + + /* + * set driver data + */ + platform_set_drvdata(mdp_dev, mfd); + + /* + * register in mdp driver + */ + rc = platform_device_add(mdp_dev); + if (rc) + goto mipi_dsi_probe_err; + + pdev_list[pdev_list_cnt++] = pdev; + +return 0; + +mipi_dsi_probe_err: + platform_device_put(mdp_dev); + return rc; +} + +static int mipi_dsi_remove(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + iounmap(mipi_dsi_base); + return 0; +} + +static int mipi_dsi_register_driver(void) +{ + return platform_driver_register(&mipi_dsi_driver); +} + +static int __init mipi_dsi_driver_init(void) +{ + int ret; + + mipi_dsi_init(); + + ret = mipi_dsi_register_driver(); + + device_initialize(&dsi_dev); + + if (ret) { + pr_err("mipi_dsi_register_driver() failed!\n"); + return ret; + } + + return ret; +} + +module_init(mipi_dsi_driver_init); diff --git a/drivers/video/msm/mipi_dsi.h b/drivers/video/msm/mipi_dsi.h new file mode 100644 index 00000000000..2ed059627f1 --- /dev/null +++ b/drivers/video/msm/mipi_dsi.h @@ -0,0 +1,285 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MIPI_DSI_H +#define MIPI_DSI_H + +#include +#include + +#ifdef BIT +#undef BIT +#endif + +#define BIT(x) (1<<(x)) + +#define MMSS_CC_BASE_PHY 0x04000000 /* mmss clcok control */ +#define MMSS_SFPB_BASE_PHY 0x05700000 /* mmss SFPB CFG */ +#define MMSS_SERDES_BASE_PHY 0x04f01000 /* mmss (De)Serializer CFG */ + +#define MIPI_DSI_BASE mipi_dsi_base + +#define MIPI_OUTP(addr, data) writel((data), (addr)) +#define MIPI_INP(addr) readl(addr) + +#ifdef CONFIG_MSM_SECURE_IO +#define MIPI_OUTP_SECURE(addr, data) secure_writel((data), (addr)) +#define MIPI_INP_SECURE(addr) secure_readl(addr) +#else +#define MIPI_OUTP_SECURE(addr, data) writel((data), (addr)) +#define MIPI_INP_SECURE(addr) readl(addr) +#endif + +#define MIPI_DSI_PRIM 1 +#define MIPI_DSI_SECD 2 + +#define MIPI_DSI_PANEL_VGA 0 +#define MIPI_DSI_PANEL_WVGA 1 +#define MIPI_DSI_PANEL_WVGA_PT 2 +#define MIPI_DSI_PANEL_FWVGA_PT 3 +#define DSI_PANEL_MAX 3 + +enum { /* mipi dsi panel */ + DSI_VIDEO_MODE, + DSI_CMD_MODE, +}; + +enum { + ST_DSI_CLK_OFF, + ST_DSI_SUSPEND, + ST_DSI_RESUME, + ST_DSI_PLAYING, + ST_DSI_NUM +}; + +enum { + EV_DSI_UPDATE, + EV_DSI_DONE, + EV_DSI_TOUT, + EV_DSI_NUM +}; + +enum { + LANDSCAPE = 1, + PORTRAIT = 2, +}; + +#define DSI_NON_BURST_SYNCH_PULSE 0 +#define DSI_NON_BURST_SYNCH_EVENT 1 +#define DSI_BURST_MODE 2 + + +#define DSI_RGB_SWAP_RGB 0 +#define DSI_RGB_SWAP_RBG 1 +#define DSI_RGB_SWAP_BGR 2 +#define DSI_RGB_SWAP_BRG 3 +#define DSI_RGB_SWAP_GRB 4 +#define DSI_RGB_SWAP_GBR 5 + +#define DSI_VIDEO_DST_FORMAT_RGB565 0 +#define DSI_VIDEO_DST_FORMAT_RGB666 1 +#define DSI_VIDEO_DST_FORMAT_RGB666_LOOSE 2 +#define DSI_VIDEO_DST_FORMAT_RGB888 3 + +#define DSI_CMD_DST_FORMAT_RGB111 0 +#define DSI_CMD_DST_FORMAT_RGB332 3 +#define DSI_CMD_DST_FORMAT_RGB444 4 +#define DSI_CMD_DST_FORMAT_RGB565 6 +#define DSI_CMD_DST_FORMAT_RGB666 7 +#define DSI_CMD_DST_FORMAT_RGB888 8 + +#define DSI_INTR_ERROR_MASK BIT(25) +#define DSI_INTR_ERROR BIT(24) +#define DSI_INTR_VIDEO_DONE_MASK BIT(17) +#define DSI_INTR_VIDEO_DONE BIT(16) +#define DSI_INTR_CMD_MDP_DONE_MASK BIT(9) +#define DSI_INTR_CMD_MDP_DONE BIT(8) +#define DSI_INTR_CMD_DMA_DONE_MASK BIT(1) +#define DSI_INTR_CMD_DMA_DONE BIT(0) + +#define DSI_CMD_TRIGGER_NONE 0x0 /* mdp trigger */ +#define DSI_CMD_TRIGGER_TE 0x02 +#define DSI_CMD_TRIGGER_SW 0x04 +#define DSI_CMD_TRIGGER_SW_SEOF 0x05 /* cmd dma only */ +#define DSI_CMD_TRIGGER_SW_TE 0x06 + +extern struct device dsi_dev; +extern int mipi_dsi_clk_on; +extern u32 dsi_irq; + +extern void __iomem *periph_base; +extern char *mmss_cc_base; /* mutimedia sub system clock control */ +extern char *mmss_sfpb_base; /* mutimedia sub system sfpb */ + +struct dsiphy_pll_divider_config { + u32 clk_rate; + u32 fb_divider; + u32 ref_divider_ratio; + u32 bit_clk_divider; /* oCLK1 */ + u32 byte_clk_divider; /* oCLK2 */ + u32 dsi_clk_divider; /* oCLK3 */ +}; + +extern struct dsiphy_pll_divider_config pll_divider_config; + +struct dsi_clk_mnd_table { + uint8 lanes; + uint8 bpp; + uint8 dsiclk_div; + uint8 dsiclk_m; + uint8 dsiclk_n; + uint8 dsiclk_d; + uint8 pclk_m; + uint8 pclk_n; + uint8 pclk_d; +}; + +static const struct dsi_clk_mnd_table mnd_table[] = { + { 1, 2, 8, 1, 1, 0, 1, 2, 1}, + { 1, 3, 8, 1, 1, 0, 1, 3, 2}, + { 2, 2, 4, 1, 1, 0, 1, 2, 1}, + { 2, 3, 4, 1, 1, 0, 1, 3, 2}, + { 3, 2, 1, 3, 8, 4, 3, 16, 8}, + { 3, 3, 1, 3, 8, 4, 1, 8, 4}, + { 4, 2, 2, 1, 1, 0, 1, 2, 1}, + { 4, 3, 2, 1, 1, 0, 1, 3, 2}, +}; + +struct dsi_clk_desc { + uint32 src; + uint32 m; + uint32 n; + uint32 d; + uint32 mnd_mode; + uint32 pre_div_func; +}; + +#define DSI_HOST_HDR_SIZE 4 +#define DSI_HDR_LAST BIT(31) +#define DSI_HDR_LONG_PKT BIT(30) +#define DSI_HDR_BTA BIT(29) +#define DSI_HDR_VC(vc) (((vc) & 0x03) << 22) +#define DSI_HDR_DTYPE(dtype) (((dtype) & 0x03f) << 16) +#define DSI_HDR_DATA2(data) (((data) & 0x0ff) << 8) +#define DSI_HDR_DATA1(data) ((data) & 0x0ff) +#define DSI_HDR_WC(wc) ((wc) & 0x0ffff) + +#define DSI_BUF_SIZE 1024 +#define MIPI_DSI_MRPS 0x04 /* Maximum Return Packet Size */ + +#define MIPI_DSI_LEN 8 /* 4 x 4 - 6 - 2, bytes dcs header+crc-align */ + +struct dsi_buf { + uint32 *hdr; /* dsi host header */ + char *start; /* buffer start addr */ + char *end; /* buffer end addr */ + int size; /* size of buffer */ + char *data; /* buffer */ + int len; /* data length */ + dma_addr_t dmap; /* mapped dma addr */ +}; + +/* dcs read/write */ +#define DTYPE_DCS_WRITE 0x05 /* short write, 0 parameter */ +#define DTYPE_DCS_WRITE1 0x15 /* short write, 1 parameter */ +#define DTYPE_DCS_READ 0x06 /* read */ +#define DTYPE_DCS_LWRITE 0x39 /* long write */ + +/* generic read/write */ +#define DTYPE_GEN_WRITE 0x03 /* short write, 0 parameter */ +#define DTYPE_GEN_WRITE1 0x13 /* short write, 1 parameter */ +#define DTYPE_GEN_WRITE2 0x23 /* short write, 2 parameter */ +#define DTYPE_GEN_LWRITE 0x29 /* long write */ +#define DTYPE_GEN_READ 0x04 /* long read, 0 parameter */ +#define DTYPE_GEN_READ1 0x14 /* long read, 1 parameter */ +#define DTYPE_GEN_READ2 0x24 /* long read, 2 parameter */ + +#define DTYPE_TEAR_ON 0x35 /* set tear on */ +#define DTYPE_MAX_PKTSIZE 0x37 /* set max packet size */ +#define DTYPE_NULL_PKT 0x09 /* null packet, no data */ +#define DTYPE_BLANK_PKT 0x19 /* blankiing packet, no data */ + +#define DTYPE_CM_ON 0x02 /* color mode off */ +#define DTYPE_CM_OFF 0x12 /* color mode on */ +#define DTYPE_PERIPHERAL_OFF 0x22 +#define DTYPE_PERIPHERAL_ON 0x32 + + +struct dsi_cmd_desc { + int dtype; + int last; + int vc; + int ack; /* ask ACK from peripheral */ + int wait; + int dlen; + char *payload; +}; + + +typedef void (*kickoff_act)(void *); + +struct dsi_kickoff_action { + struct list_head act_entry; + kickoff_act action; + void *data; +}; + + +char *mipi_dsi_buf_reserve_hdr(struct dsi_buf *dp, int hlen); +char *mipi_dsi_buf_init(struct dsi_buf *dp); +void mipi_dsi_init(void); +int mipi_dsi_buf_alloc(struct dsi_buf *, int size); +int mipi_dsi_cmd_dma_add(struct dsi_buf *dp, struct dsi_cmd_desc *cm); +int mipi_dsi_cmds_tx(struct msm_fb_data_type *mfd, + struct dsi_buf *dp, struct dsi_cmd_desc *cmds, int cnt); + +int mipi_dsi_cmd_dma_tx(struct dsi_buf *dp); +int mipi_dsi_cmd_reg_tx(uint32 data); +int mipi_dsi_cmds_rx(struct msm_fb_data_type *mfd, + struct dsi_buf *tp, struct dsi_buf *rp, + struct dsi_cmd_desc *cmds, int len); +int mipi_dsi_cmd_dma_rx(struct dsi_buf *tp, int rlen); +void mipi_dsi_host_init(struct mipi_panel_info *pinfo); +void mipi_dsi_op_mode_config(int mode); +void mipi_dsi_cmd_mode_ctrl(int enable); +void mdp4_dsi_cmd_trigger(void); +void mipi_dsi_cmd_mdp_sw_trigger(void); +void mipi_dsi_cmd_bta_sw_trigger(void); +void mipi_dsi_ack_err_status(void); +void mipi_dsi_set_tear_on(struct msm_fb_data_type *mfd); +void mipi_dsi_set_tear_off(struct msm_fb_data_type *mfd); +void mipi_dsi_clk_enable(void); +void mipi_dsi_clk_disable(void); +void mipi_dsi_pre_kickoff_action(void); +void mipi_dsi_post_kickoff_action(void); +void mipi_dsi_pre_kickoff_add(struct dsi_kickoff_action *act); +void mipi_dsi_post_kickoff_add(struct dsi_kickoff_action *act); +void mipi_dsi_pre_kickoff_del(struct dsi_kickoff_action *act); +void mipi_dsi_post_kickoff_del(struct dsi_kickoff_action *act); + +irqreturn_t mipi_dsi_isr(int irq, void *ptr); + +void mipi_set_tx_power_mode(int mode); +void mipi_dsi_phy_ctrl(int on); +void mipi_dsi_phy_init(int panel_ndx, struct msm_panel_info const *panel_info, + int target_type); +int mipi_dsi_clk_div_config(uint8 bpp, uint8 lanes, + uint32 *expected_dsi_pclk); +void mipi_dsi_clk_init(struct device *dev); +void mipi_dsi_clk_deinit(struct device *dev); + +#ifdef CONFIG_FB_MSM_MDP303 +void update_lane_config(struct msm_panel_info *pinfo); +#endif + +#endif /* MIPI_DSI_H */ diff --git a/drivers/video/msm/mipi_dsi_host.c b/drivers/video/msm/mipi_dsi_host.c new file mode 100644 index 00000000000..6607e4cba10 --- /dev/null +++ b/drivers/video/msm/mipi_dsi_host.c @@ -0,0 +1,1248 @@ + +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mdp.h" +#include "mdp4.h" + +static struct completion dsi_dma_comp; +static struct dsi_buf dsi_tx_buf; +static int dsi_irq_enabled; +static spinlock_t dsi_lock; + +static struct list_head pre_kickoff_list; +static struct list_head post_kickoff_list; + +void mipi_dsi_init(void) +{ + init_completion(&dsi_dma_comp); + mipi_dsi_buf_alloc(&dsi_tx_buf, DSI_BUF_SIZE); + spin_lock_init(&dsi_lock); + + INIT_LIST_HEAD(&pre_kickoff_list); + INIT_LIST_HEAD(&post_kickoff_list); +} + +void mipi_dsi_enable_irq(void) +{ + unsigned long flags; + + spin_lock_irqsave(&dsi_lock, flags); + if (dsi_irq_enabled) { + pr_debug("%s: IRQ aleady enabled\n", __func__); + spin_unlock_irqrestore(&dsi_lock, flags); + return; + } + dsi_irq_enabled = 1; + enable_irq(dsi_irq); + spin_unlock_irqrestore(&dsi_lock, flags); +} + +void mipi_dsi_disable_irq(void) +{ + unsigned long flags; + + spin_lock_irqsave(&dsi_lock, flags); + if (dsi_irq_enabled == 0) { + pr_debug("%s: IRQ already disabled\n", __func__); + spin_unlock_irqrestore(&dsi_lock, flags); + return; + } + + dsi_irq_enabled = 0; + disable_irq(dsi_irq); + spin_unlock_irqrestore(&dsi_lock, flags); +} + +/* + * mipi_dsi_disale_irq_nosync() should be called + * from interrupt context + */ + void mipi_dsi_disable_irq_nosync(void) +{ + spin_lock(&dsi_lock); + if (dsi_irq_enabled == 0) { + pr_debug("%s: IRQ cannot be disabled\n", __func__); + return; + } + + dsi_irq_enabled = 0; + disable_irq_nosync(dsi_irq); + spin_unlock(&dsi_lock); +} + +static void mipi_dsi_action(struct list_head *act_list) +{ + struct list_head *lp; + struct dsi_kickoff_action *act; + + list_for_each(lp, act_list) { + act = list_entry(lp, struct dsi_kickoff_action, act_entry); + if (act && act->action) + act->action(act->data); + } +} + +void mipi_dsi_pre_kickoff_action(void) +{ + mipi_dsi_action(&pre_kickoff_list); +} + +void mipi_dsi_post_kickoff_action(void) +{ + mipi_dsi_action(&post_kickoff_list); +} + +/* + * mipi_dsi_pre_kickoff_add: + * ov_mutex need to be acquired before call this function. + */ +void mipi_dsi_pre_kickoff_add(struct dsi_kickoff_action *act) +{ + if (act) + list_add_tail(&act->act_entry, &pre_kickoff_list); +} + +/* + * mipi_dsi_pre_kickoff_add: + * ov_mutex need to be acquired before call this function. + */ +void mipi_dsi_post_kickoff_add(struct dsi_kickoff_action *act) +{ + if (act) + list_add_tail(&act->act_entry, &post_kickoff_list); +} + +/* + * mipi_dsi_pre_kickoff_add: + * ov_mutex need to be acquired before call this function. + */ +void mipi_dsi_pre_kickoff_del(struct dsi_kickoff_action *act) +{ + if (!list_empty(&pre_kickoff_list) && act) + list_del(&act->act_entry); +} + +/* + * mipi_dsi_pre_kickoff_add: + * ov_mutex need to be acquired before call this function. + */ +void mipi_dsi_post_kickoff_del(struct dsi_kickoff_action *act) +{ + if (!list_empty(&post_kickoff_list) && act) + list_del(&act->act_entry); +} + +/* + * mipi dsi buf mechanism + */ +char *mipi_dsi_buf_reserve(struct dsi_buf *dp, int len) +{ + dp->data += len; + return dp->data; +} + +char *mipi_dsi_buf_unreserve(struct dsi_buf *dp, int len) +{ + dp->data -= len; + return dp->data; +} + +char *mipi_dsi_buf_push(struct dsi_buf *dp, int len) +{ + dp->data -= len; + dp->len += len; + return dp->data; +} + +char *mipi_dsi_buf_reserve_hdr(struct dsi_buf *dp, int hlen) +{ + dp->hdr = (uint32 *)dp->data; + return mipi_dsi_buf_reserve(dp, hlen); +} + +char *mipi_dsi_buf_init(struct dsi_buf *dp) +{ + int off; + + dp->data = dp->start; + off = (int)dp->data; + /* 8 byte align */ + off &= 0x07; + if (off) + off = 8 - off; + dp->data += off; + dp->len = 0; + return dp->data; +} + +int mipi_dsi_buf_alloc(struct dsi_buf *dp, int size) +{ + + dp->start = kmalloc(size, GFP_KERNEL); + if (dp->start == NULL) { + pr_err("%s:%u\n", __func__, __LINE__); + return -ENOMEM; + } + + dp->end = dp->start + size; + dp->size = size; + + if ((int)dp->start & 0x07) + pr_err("%s: buf NOT 8 bytes aligned\n", __func__); + + dp->data = dp->start; + dp->len = 0; + return size; +} + +/* + * mipi dsi gerneric long write + */ +static int mipi_dsi_generic_lwrite(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + char *bp; + uint32 *hp; + int i, len; + + bp = mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + + /* fill up payload */ + if (cm->payload) { + len = cm->dlen; + len += 3; + len &= ~0x03; /* multipled by 4 */ + for (i = 0; i < cm->dlen; i++) + *bp++ = cm->payload[i]; + + /* append 0xff to the end */ + for (; i < len; i++) + *bp++ = 0xff; + + dp->len += len; + } + + /* fill up header */ + hp = dp->hdr; + *hp = 0; + *hp = DSI_HDR_WC(cm->dlen); + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_LONG_PKT; + *hp |= DSI_HDR_DTYPE(DTYPE_GEN_LWRITE); + if (cm->last) + *hp |= DSI_HDR_LAST; + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; +} + +/* + * mipi dsi gerneric short write with 0, 1 2 parameters + */ +static int mipi_dsi_generic_swrite(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + int len; + + if (cm->dlen && cm->payload == 0) { + pr_err("%s: NO payload error\n", __func__); + return 0; + } + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + if (cm->last) + *hp |= DSI_HDR_LAST; + + + len = (cm->dlen > 2) ? 2 : cm->dlen; + + if (len == 1) { + *hp |= DSI_HDR_DTYPE(DTYPE_GEN_WRITE1); + *hp |= DSI_HDR_DATA1(cm->payload[0]); + *hp |= DSI_HDR_DATA2(0); + } else if (len == 2) { + *hp |= DSI_HDR_DTYPE(DTYPE_GEN_WRITE2); + *hp |= DSI_HDR_DATA1(cm->payload[0]); + *hp |= DSI_HDR_DATA2(cm->payload[1]); + } else { + *hp |= DSI_HDR_DTYPE(DTYPE_GEN_WRITE); + *hp |= DSI_HDR_DATA1(0); + *hp |= DSI_HDR_DATA2(0); + } + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; /* 4 bytes */ +} + +/* + * mipi dsi gerneric read with 0, 1 2 parameters + */ +static int mipi_dsi_generic_read(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + int len; + + if (cm->dlen && cm->payload == 0) { + pr_err("%s: NO payload error\n", __func__); + return 0; + } + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_BTA; + if (cm->last) + *hp |= DSI_HDR_LAST; + + len = (cm->dlen > 2) ? 2 : cm->dlen; + + if (len == 1) { + *hp |= DSI_HDR_DTYPE(DTYPE_GEN_READ1); + *hp |= DSI_HDR_DATA1(cm->payload[0]); + *hp |= DSI_HDR_DATA2(0); + } else if (len == 2) { + *hp |= DSI_HDR_DTYPE(DTYPE_GEN_READ2); + *hp |= DSI_HDR_DATA1(cm->payload[0]); + *hp |= DSI_HDR_DATA2(cm->payload[1]); + } else { + *hp |= DSI_HDR_DTYPE(DTYPE_GEN_READ); + *hp |= DSI_HDR_DATA1(0); + *hp |= DSI_HDR_DATA2(0); + } + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + return dp->len; /* 4 bytes */ +} + +/* + * mipi dsi dcs long write + */ +static int mipi_dsi_dcs_lwrite(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + char *bp; + uint32 *hp; + int i, len; + + bp = mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + + /* + * fill up payload + * dcs command byte (first byte) followed by payload + */ + if (cm->payload) { + len = cm->dlen; + len += 3; + len &= ~0x03; /* multipled by 4 */ + for (i = 0; i < cm->dlen; i++) + *bp++ = cm->payload[i]; + + /* append 0xff to the end */ + for (; i < len; i++) + *bp++ = 0xff; + + dp->len += len; + } + + /* fill up header */ + hp = dp->hdr; + *hp = 0; + *hp = DSI_HDR_WC(cm->dlen); + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_LONG_PKT; + *hp |= DSI_HDR_DTYPE(DTYPE_DCS_LWRITE); + if (cm->last) + *hp |= DSI_HDR_LAST; + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; +} + +/* + * mipi dsi dcs short write with 0 parameters + */ +static int mipi_dsi_dcs_swrite(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + int len; + + if (cm->payload == 0) { + pr_err("%s: NO payload error\n", __func__); + return -EINVAL; + } + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + if (cm->ack) /* ask ACK trigger msg from peripeheral */ + *hp |= DSI_HDR_BTA; + if (cm->last) + *hp |= DSI_HDR_LAST; + + len = (cm->dlen > 1) ? 1 : cm->dlen; + + *hp |= DSI_HDR_DTYPE(DTYPE_DCS_WRITE); + *hp |= DSI_HDR_DATA1(cm->payload[0]); /* dcs command byte */ + *hp |= DSI_HDR_DATA2(0); + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + return dp->len; +} + +/* + * mipi dsi dcs short write with 1 parameters + */ +static int mipi_dsi_dcs_swrite1(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + + if (cm->dlen < 2 || cm->payload == 0) { + pr_err("%s: NO payload error\n", __func__); + return -EINVAL; + } + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + if (cm->ack) /* ask ACK trigger msg from peripeheral */ + *hp |= DSI_HDR_BTA; + if (cm->last) + *hp |= DSI_HDR_LAST; + + *hp |= DSI_HDR_DTYPE(DTYPE_DCS_WRITE1); + *hp |= DSI_HDR_DATA1(cm->payload[0]); /* dcs comamnd byte */ + *hp |= DSI_HDR_DATA2(cm->payload[1]); /* parameter */ + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; +} +/* + * mipi dsi dcs read with 0 parameters + */ + +static int mipi_dsi_dcs_read(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + + if (cm->payload == 0) { + pr_err("%s: NO payload error\n", __func__); + return -EINVAL; + } + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_BTA; + *hp |= DSI_HDR_DTYPE(DTYPE_DCS_READ); + if (cm->last) + *hp |= DSI_HDR_LAST; + + *hp |= DSI_HDR_DATA1(cm->payload[0]); /* dcs command byte */ + *hp |= DSI_HDR_DATA2(0); + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; /* 4 bytes */ +} + +static int mipi_dsi_cm_on(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_DTYPE(DTYPE_CM_ON); + if (cm->last) + *hp |= DSI_HDR_LAST; + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; /* 4 bytes */ +} + +static int mipi_dsi_cm_off(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_DTYPE(DTYPE_CM_OFF); + if (cm->last) + *hp |= DSI_HDR_LAST; + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; /* 4 bytes */ +} + +static int mipi_dsi_peripheral_on(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_DTYPE(DTYPE_PERIPHERAL_ON); + if (cm->last) + *hp |= DSI_HDR_LAST; + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; /* 4 bytes */ +} + +static int mipi_dsi_peripheral_off(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_DTYPE(DTYPE_PERIPHERAL_OFF); + if (cm->last) + *hp |= DSI_HDR_LAST; + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; /* 4 bytes */ +} + +static int mipi_dsi_set_max_pktsize(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + + if (cm->payload == 0) { + pr_err("%s: NO payload error\n", __func__); + return 0; + } + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_DTYPE(DTYPE_MAX_PKTSIZE); + if (cm->last) + *hp |= DSI_HDR_LAST; + + *hp |= DSI_HDR_DATA1(cm->payload[0]); + *hp |= DSI_HDR_DATA2(cm->payload[1]); + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; /* 4 bytes */ +} + +static int mipi_dsi_null_pkt(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp = DSI_HDR_WC(cm->dlen); + *hp |= DSI_HDR_LONG_PKT; + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_DTYPE(DTYPE_NULL_PKT); + if (cm->last) + *hp |= DSI_HDR_LAST; + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; /* 4 bytes */ +} + +static int mipi_dsi_blank_pkt(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + uint32 *hp; + + mipi_dsi_buf_reserve_hdr(dp, DSI_HOST_HDR_SIZE); + hp = dp->hdr; + *hp = 0; + *hp = DSI_HDR_WC(cm->dlen); + *hp |= DSI_HDR_LONG_PKT; + *hp |= DSI_HDR_VC(cm->vc); + *hp |= DSI_HDR_DTYPE(DTYPE_BLANK_PKT); + if (cm->last) + *hp |= DSI_HDR_LAST; + + mipi_dsi_buf_push(dp, DSI_HOST_HDR_SIZE); + + return dp->len; /* 4 bytes */ +} + +/* + * prepare cmd buffer to be txed + */ +int mipi_dsi_cmd_dma_add(struct dsi_buf *dp, struct dsi_cmd_desc *cm) +{ + int len = 0; + + switch (cm->dtype) { + case DTYPE_GEN_WRITE: + case DTYPE_GEN_WRITE1: + case DTYPE_GEN_WRITE2: + len = mipi_dsi_generic_swrite(dp, cm); + break; + case DTYPE_GEN_LWRITE: + len = mipi_dsi_generic_lwrite(dp, cm); + break; + case DTYPE_GEN_READ: + case DTYPE_GEN_READ1: + case DTYPE_GEN_READ2: + len = mipi_dsi_generic_read(dp, cm); + break; + case DTYPE_DCS_LWRITE: + len = mipi_dsi_dcs_lwrite(dp, cm); + break; + case DTYPE_DCS_WRITE: + len = mipi_dsi_dcs_swrite(dp, cm); + break; + case DTYPE_DCS_WRITE1: + len = mipi_dsi_dcs_swrite1(dp, cm); + break; + case DTYPE_DCS_READ: + len = mipi_dsi_dcs_read(dp, cm); + break; + case DTYPE_MAX_PKTSIZE: + len = mipi_dsi_set_max_pktsize(dp, cm); + break; + case DTYPE_NULL_PKT: + len = mipi_dsi_null_pkt(dp, cm); + break; + case DTYPE_BLANK_PKT: + len = mipi_dsi_blank_pkt(dp, cm); + break; + case DTYPE_CM_ON: + len = mipi_dsi_cm_on(dp, cm); + break; + case DTYPE_CM_OFF: + len = mipi_dsi_cm_off(dp, cm); + break; + case DTYPE_PERIPHERAL_ON: + len = mipi_dsi_peripheral_on(dp, cm); + break; + case DTYPE_PERIPHERAL_OFF: + len = mipi_dsi_peripheral_off(dp, cm); + break; + default: + pr_debug("%s: dtype=%x NOT supported\n", + __func__, cm->dtype); + break; + + } + + return len; +} + +void mipi_dsi_host_init(struct mipi_panel_info *pinfo) +{ + uint32 dsi_ctrl, intr_ctrl; + uint32 data; + + if (pinfo->mode == DSI_VIDEO_MODE) { + data = 0; + if (pinfo->pulse_mode_hsa_he) + data |= BIT(28); + if (pinfo->hfp_power_stop) + data |= BIT(24); + if (pinfo->hbp_power_stop) + data |= BIT(20); + if (pinfo->hsa_power_stop) + data |= BIT(16); + if (pinfo->eof_bllp_power_stop) + data |= BIT(15); + if (pinfo->bllp_power_stop) + data |= BIT(12); + data |= ((pinfo->traffic_mode & 0x03) << 8); + data |= ((pinfo->dst_format & 0x03) << 4); /* 2 bits */ + data |= (pinfo->vc & 0x03); + MIPI_OUTP(MIPI_DSI_BASE + 0x000c, data); + + data = 0; + data |= ((pinfo->rgb_swap & 0x07) << 12); + if (pinfo->b_sel) + data |= BIT(8); + if (pinfo->g_sel) + data |= BIT(4); + if (pinfo->r_sel) + data |= BIT(0); + MIPI_OUTP(MIPI_DSI_BASE + 0x001c, data); + } else if (pinfo->mode == DSI_CMD_MODE) { + data = 0; + data |= ((pinfo->interleave_max & 0x0f) << 20); + data |= ((pinfo->rgb_swap & 0x07) << 16); + if (pinfo->b_sel) + data |= BIT(12); + if (pinfo->g_sel) + data |= BIT(8); + if (pinfo->r_sel) + data |= BIT(4); + data |= (pinfo->dst_format & 0x0f); /* 4 bits */ + MIPI_OUTP(MIPI_DSI_BASE + 0x003c, data); + + /* DSI_COMMAND_MODE_MDP_DCS_CMD_CTRL */ + data = pinfo->wr_mem_continue & 0x0ff; + data <<= 8; + data |= (pinfo->wr_mem_start & 0x0ff); + if (pinfo->insert_dcs_cmd) + data |= BIT(16); + MIPI_OUTP(MIPI_DSI_BASE + 0x0040, data); + } else + pr_err("%s: Unknown DSI mode=%d\n", __func__, pinfo->mode); + + dsi_ctrl = BIT(8) | BIT(2); /* clock enable & cmd mode */ + intr_ctrl = 0; + intr_ctrl = (DSI_INTR_CMD_DMA_DONE_MASK | DSI_INTR_CMD_MDP_DONE_MASK); + + if (pinfo->crc_check) + dsi_ctrl |= BIT(24); + if (pinfo->ecc_check) + dsi_ctrl |= BIT(20); + if (pinfo->data_lane3) + dsi_ctrl |= BIT(7); + if (pinfo->data_lane2) + dsi_ctrl |= BIT(6); + if (pinfo->data_lane1) + dsi_ctrl |= BIT(5); + if (pinfo->data_lane0) + dsi_ctrl |= BIT(4); + + /* from frame buffer, low power mode */ + /* DSI_COMMAND_MODE_DMA_CTRL */ + MIPI_OUTP(MIPI_DSI_BASE + 0x38, 0x14000000); + + data = 0; + if (pinfo->te_sel) + data |= BIT(31); + data |= pinfo->mdp_trigger << 4;/* cmd mdp trigger */ + data |= pinfo->dma_trigger; /* cmd dma trigger */ + data |= (pinfo->stream & 0x01) << 8; + MIPI_OUTP(MIPI_DSI_BASE + 0x0080, data); /* DSI_TRIG_CTRL */ + + /* DSI_LAN_SWAP_CTRL */ + MIPI_OUTP(MIPI_DSI_BASE + 0x00ac, pinfo->dlane_swap); + + /* clock out ctrl */ + data = pinfo->t_clk_post & 0x3f; /* 6 bits */ + data <<= 8; + data |= pinfo->t_clk_pre & 0x3f; /* 6 bits */ + MIPI_OUTP(MIPI_DSI_BASE + 0xc0, data); /* DSI_CLKOUT_TIMING_CTRL */ + + data = 0; + if (pinfo->rx_eot_ignore) + data |= BIT(4); + if (pinfo->tx_eot_append) + data |= BIT(0); + MIPI_OUTP(MIPI_DSI_BASE + 0x00c8, data); /* DSI_EOT_PACKET_CTRL */ + + + /* allow only ack-err-status to generate interrupt */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0108, 0x13ff3fe0); /* DSI_ERR_INT_MASK0 */ + + intr_ctrl |= DSI_INTR_ERROR_MASK; + MIPI_OUTP(MIPI_DSI_BASE + 0x010c, intr_ctrl); /* DSI_INTL_CTRL */ + + /* turn esc, byte, dsi, pclk, sclk, hclk on */ + if (mdp_rev >= MDP_REV_41) + MIPI_OUTP(MIPI_DSI_BASE + 0x118, 0x23f); /* DSI_CLK_CTRL */ + else + MIPI_OUTP(MIPI_DSI_BASE + 0x118, 0x33f); /* DSI_CLK_CTRL */ + + dsi_ctrl |= BIT(0); /* enable dsi */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0000, dsi_ctrl); + + wmb(); +} + +void mipi_set_tx_power_mode(int mode) +{ + uint32 data = MIPI_INP(MIPI_DSI_BASE + 0x38); + + if (mode == 0) + data &= ~BIT(26); + else + data |= BIT(26); + + MIPI_OUTP(MIPI_DSI_BASE + 0x38, data); +} + +void mipi_dsi_op_mode_config(int mode) +{ + + uint32 dsi_ctrl, intr_ctrl; + + dsi_ctrl = MIPI_INP(MIPI_DSI_BASE + 0x0000); + dsi_ctrl &= ~0x07; + if (mode == DSI_VIDEO_MODE) { + dsi_ctrl |= 0x03; + intr_ctrl = DSI_INTR_CMD_DMA_DONE_MASK; + } else { /* command mode */ + dsi_ctrl |= 0x05; + intr_ctrl = DSI_INTR_CMD_DMA_DONE_MASK | DSI_INTR_ERROR_MASK | + DSI_INTR_CMD_MDP_DONE_MASK; + } + + pr_debug("%s: dsi_ctrl=%x intr=%x\n", __func__, dsi_ctrl, intr_ctrl); + + MIPI_OUTP(MIPI_DSI_BASE + 0x010c, intr_ctrl); /* DSI_INTL_CTRL */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0000, dsi_ctrl); + wmb(); +} + +void mipi_dsi_cmd_mdp_sw_trigger(void) +{ + mipi_dsi_pre_kickoff_action(); + mipi_dsi_enable_irq(); + MIPI_OUTP(MIPI_DSI_BASE + 0x090, 0x01); /* trigger */ + wmb(); +} + + +void mipi_dsi_cmd_bta_sw_trigger(void) +{ + uint32 data; + int cnt = 0; + + MIPI_OUTP(MIPI_DSI_BASE + 0x094, 0x01); /* trigger */ + wmb(); + + while (cnt < 10000) { + data = MIPI_INP(MIPI_DSI_BASE + 0x0004);/* DSI_STATUS */ + if ((data & 0x0010) == 0) + break; + cnt++; + } + + mipi_dsi_ack_err_status(); + + pr_debug("%s: BTA done, cnt=%d\n", __func__, cnt); +} + +static char set_tear_on[2] = {0x35, 0x00}; +static struct dsi_cmd_desc dsi_tear_on_cmd = { + DTYPE_DCS_WRITE1, 1, 0, 0, 0, sizeof(set_tear_on), set_tear_on}; + +static char set_tear_off[2] = {0x34, 0x00}; +static struct dsi_cmd_desc dsi_tear_off_cmd = { + DTYPE_DCS_WRITE, 1, 0, 0, 0, sizeof(set_tear_off), set_tear_off}; + +void mipi_dsi_set_tear_on(struct msm_fb_data_type *mfd) +{ + mipi_dsi_buf_init(&dsi_tx_buf); + mipi_dsi_cmds_tx(mfd, &dsi_tx_buf, &dsi_tear_on_cmd, 1); +} + +void mipi_dsi_set_tear_off(struct msm_fb_data_type *mfd) +{ + mipi_dsi_buf_init(&dsi_tx_buf); + mipi_dsi_cmds_tx(mfd, &dsi_tx_buf, &dsi_tear_off_cmd, 1); +} + +int mipi_dsi_cmd_reg_tx(uint32 data) +{ +#ifdef DSI_HOST_DEBUG + int i; + char *bp; + + bp = (char *)&data; + pr_debug("%s: ", __func__); + for (i = 0; i < 4; i++) + pr_debug("%x ", *bp++); + + pr_debug("\n"); +#endif + + MIPI_OUTP(MIPI_DSI_BASE + 0x0080, 0x04);/* sw trigger */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0, 0x135); + + wmb(); + + MIPI_OUTP(MIPI_DSI_BASE + 0x038, data); + wmb(); + MIPI_OUTP(MIPI_DSI_BASE + 0x08c, 0x01); /* trigger */ + wmb(); + + udelay(300); + + return 4; +} + +/* + * mipi_dsi_cmds_tx: + * ov_mutex need to be acquired before call this function. + */ +int mipi_dsi_cmds_tx(struct msm_fb_data_type *mfd, + struct dsi_buf *tp, struct dsi_cmd_desc *cmds, int cnt) +{ + struct dsi_cmd_desc *cm; + uint32 dsi_ctrl, ctrl; + int i, video_mode; + + /* turn on cmd mode + * for video mode, do not send cmds more than + * one pixel line, since it only transmit it + * during BLLP. + */ + dsi_ctrl = MIPI_INP(MIPI_DSI_BASE + 0x0000); + video_mode = dsi_ctrl & 0x02; /* VIDEO_MODE_EN */ + if (video_mode) { + ctrl = dsi_ctrl | 0x04; /* CMD_MODE_EN */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0000, ctrl); + } else { /* cmd mode */ + /* + * during boot up, cmd mode is configured + * even it is video mode panel. + */ + /* make sure mdp dma is not txing pixel data */ + if (mfd->panel_info.type == MIPI_CMD_PANEL) { +#ifndef CONFIG_FB_MSM_MDP303 + mdp4_dsi_cmd_dma_busy_wait(mfd); +#else + mdp3_dsi_cmd_dma_busy_wait(mfd); +#endif + } + } + + mipi_dsi_enable_irq(); + cm = cmds; + mipi_dsi_buf_init(tp); + for (i = 0; i < cnt; i++) { + mipi_dsi_buf_init(tp); + mipi_dsi_cmd_dma_add(tp, cm); + mipi_dsi_cmd_dma_tx(tp); + if (cm->wait) + msleep(cm->wait); + cm++; + } + mipi_dsi_disable_irq(); + + if (video_mode) + MIPI_OUTP(MIPI_DSI_BASE + 0x0000, dsi_ctrl); /* restore */ + + return cnt; +} + +/* MIPI_DSI_MRPS, Maximum Return Packet Size */ +static char max_pktsize[2] = {0x00, 0x00}; /* LSB tx first, 10 bytes */ + +static struct dsi_cmd_desc pkt_size_cmd[] = { + {DTYPE_MAX_PKTSIZE, 1, 0, 0, 0, + sizeof(max_pktsize), max_pktsize} +}; + +/* + * DSI panel reply with MAX_RETURN_PACKET_SIZE bytes of data + * plus DCS header, ECC and CRC for DCS long read response + * mipi_dsi_controller only have 4x32 bits register ( 16 bytes) to + * hold data per transaction. + * MIPI_DSI_LEN equal to 8 + * len should be either 4 or 8 + * any return data more than MIPI_DSI_LEN need to be break down + * to multiple transactions. + * + * ov_mutex need to be acquired before call this function. + */ +int mipi_dsi_cmds_rx(struct msm_fb_data_type *mfd, + struct dsi_buf *tp, struct dsi_buf *rp, + struct dsi_cmd_desc *cmds, int len) +{ + int cnt, res; + static int pkt_size; + + if (len <= 2) + cnt = 4; /* short read */ + else { + if (len > MIPI_DSI_LEN) + len = MIPI_DSI_LEN; /* 8 bytes at most */ + + res = len & 0x03; + len += (4 - res); /* 4 bytes align */ + /* + * add extra 2 bytes to len to have overall + * packet size is multipe by 4. This also make + * sure 4 bytes dcs headerlocates within a + * 32 bits register after shift in. + * after all, len should be either 6 or 10. + */ + len += 2; + cnt = len + 6; /* 4 bytes header + 2 bytes crc */ + } + + + if (mfd->panel_info.type == MIPI_CMD_PANEL) { + /* make sure mdp dma is not txing pixel data */ +#ifndef CONFIG_FB_MSM_MDP303 + mdp4_dsi_cmd_dma_busy_wait(mfd); +#else + mdp3_dsi_cmd_dma_busy_wait(mfd); +#endif + } + + mipi_dsi_enable_irq(); + if (pkt_size != len) { + /* set new max pkt size */ + pkt_size = len; + max_pktsize[0] = pkt_size; + mipi_dsi_buf_init(tp); + mipi_dsi_cmd_dma_add(tp, pkt_size_cmd); + mipi_dsi_cmd_dma_tx(tp); + } + + mipi_dsi_buf_init(tp); + mipi_dsi_cmd_dma_add(tp, cmds); + + /* transmit read comamnd to client */ + mipi_dsi_cmd_dma_tx(tp); + /* + * once cmd_dma_done interrupt received, + * return data from client is ready and stored + * at RDBK_DATA register already + */ + mipi_dsi_cmd_dma_rx(rp, cnt); + + mipi_dsi_disable_irq(); + + /* strip off dcs header & crc */ + if (cnt > 4) { /* long response */ + rp->data += 4; /* skip dcs header */ + rp->len -= 6; /* deduct 4 bytes header + 2 bytes crc */ + rp->len -= 2; /* extra 2 bytes added */ + } else { + rp->data += 1; /* skip dcs short header */ + rp->len -= 2; /* deduct 1 byte header + 1 byte ecc */ + } + + return rp->len; +} + +int mipi_dsi_cmd_dma_tx(struct dsi_buf *tp) +{ + int len; + +#ifdef DSI_HOST_DEBUG + int i; + char *bp; + + bp = tp->data; + + pr_debug("%s: ", __func__); + for (i = 0; i < tp->len; i++) + pr_debug("%x ", *bp++); + + pr_debug("\n"); +#endif + + len = tp->len; + len += 3; + len &= ~0x03; /* multipled by 4 */ + + tp->dmap = dma_map_single(&dsi_dev, tp->data, len, DMA_TO_DEVICE); + if (dma_mapping_error(&dsi_dev, tp->dmap)) + pr_err("%s: dmap mapp failed\n", __func__); + + INIT_COMPLETION(dsi_dma_comp); + + MIPI_OUTP(MIPI_DSI_BASE + 0x044, tp->dmap); + MIPI_OUTP(MIPI_DSI_BASE + 0x048, len); + wmb(); + MIPI_OUTP(MIPI_DSI_BASE + 0x08c, 0x01); /* trigger */ + wmb(); + + wait_for_completion(&dsi_dma_comp); + + dma_unmap_single(&dsi_dev, tp->dmap, len, DMA_TO_DEVICE); + tp->dmap = 0; + return tp->len; +} + +int mipi_dsi_cmd_dma_rx(struct dsi_buf *rp, int rlen) +{ + uint32 *lp, data; + int i, off, cnt; + + lp = (uint32 *)rp->data; + cnt = rlen; + cnt += 3; + cnt >>= 2; + + if (cnt > 4) + cnt = 4; /* 4 x 32 bits registers only */ + + off = 0x068; /* DSI_RDBK_DATA0 */ + off += ((cnt - 1) * 4); + + + for (i = 0; i < cnt; i++) { + data = (uint32)MIPI_INP(MIPI_DSI_BASE + off); + *lp++ = ntohl(data); /* to network byte order */ + off -= 4; + rp->len += sizeof(*lp); + } + + return rlen; +} + +void mipi_dsi_irq_set(uint32 mask, uint32 irq) +{ + uint32 data; + + data = MIPI_INP(MIPI_DSI_BASE + 0x010c);/* DSI_INTR_CTRL */ + data &= ~mask; + data |= irq; + MIPI_OUTP(MIPI_DSI_BASE + 0x010c, data); +} + + +void mipi_dsi_ack_err_status(void) +{ + uint32 status; + + status = MIPI_INP(MIPI_DSI_BASE + 0x0064);/* DSI_ACK_ERR_STATUS */ + + if (status) { + MIPI_OUTP(MIPI_DSI_BASE + 0x0064, status); + pr_debug("%s: status=%x\n", __func__, status); + } +} + +void mipi_dsi_timeout_status(void) +{ + uint32 status; + + status = MIPI_INP(MIPI_DSI_BASE + 0x00bc);/* DSI_TIMEOUT_STATUS */ + if (status & 0x0111) { + MIPI_OUTP(MIPI_DSI_BASE + 0x00bc, status); + pr_debug("%s: status=%x\n", __func__, status); + } +} + +void mipi_dsi_dln0_phy_err(void) +{ + uint32 status; + + status = MIPI_INP(MIPI_DSI_BASE + 0x00b0);/* DSI_DLN0_PHY_ERR */ + + if (status & 0x011111) { + MIPI_OUTP(MIPI_DSI_BASE + 0x00b0, status); + pr_debug("%s: status=%x\n", __func__, status); + } +} + +void mipi_dsi_fifo_status(void) +{ + uint32 status; + + status = MIPI_INP(MIPI_DSI_BASE + 0x0008);/* DSI_FIFO_STATUS */ + + if (status & 0x44444489) { + MIPI_OUTP(MIPI_DSI_BASE + 0x0008, status); + pr_debug("%s: status=%x\n", __func__, status); + } +} + +void mipi_dsi_status(void) +{ + uint32 status; + + status = MIPI_INP(MIPI_DSI_BASE + 0x0004);/* DSI_STATUS */ + + if (status & 0x80000000) { + MIPI_OUTP(MIPI_DSI_BASE + 0x0004, status); + pr_debug("%s: status=%x\n", __func__, status); + } +} + +void mipi_dsi_error(void) +{ + /* DSI_ERR_INT_MASK0 */ + mipi_dsi_ack_err_status(); /* mask0, 0x01f */ + mipi_dsi_timeout_status(); /* mask0, 0x0e0 */ + mipi_dsi_fifo_status(); /* mask0, 0x133d00 */ + mipi_dsi_status(); /* mask0, 0xc0100 */ + mipi_dsi_dln0_phy_err(); /* mask0, 0x3e00000 */ +} + + +irqreturn_t mipi_dsi_isr(int irq, void *ptr) +{ + uint32 isr; + + isr = MIPI_INP(MIPI_DSI_BASE + 0x010c);/* DSI_INTR_CTRL */ + MIPI_OUTP(MIPI_DSI_BASE + 0x010c, isr); + +#ifdef CONFIG_FB_MSM_MDP40 + mdp4_stat.intr_dsi++; +#endif + + if (isr & DSI_INTR_ERROR) { + mipi_dsi_error(); + } + + if (isr & DSI_INTR_VIDEO_DONE) { + /* + * do something here + */ + } + + if (isr & DSI_INTR_CMD_DMA_DONE) { + complete(&dsi_dma_comp); + } + + if (isr & DSI_INTR_CMD_MDP_DONE) { + mipi_dsi_disable_irq_nosync(); + mipi_dsi_post_kickoff_action(); + } + + + return IRQ_HANDLED; +} diff --git a/drivers/video/msm/mipi_novatek.c b/drivers/video/msm/mipi_novatek.c new file mode 100644 index 00000000000..27108f24e17 --- /dev/null +++ b/drivers/video/msm/mipi_novatek.c @@ -0,0 +1,463 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_novatek.h" +#include "mdp4.h" + + +static struct mipi_dsi_novatek_platform_data *mipi_novatek_pdata; + +static struct dsi_buf novatek_tx_buf; +static struct dsi_buf novatek_rx_buf; + + +/* novatek blue panel */ + +#ifdef NOVETAK_COMMANDS_UNUSED +static char display_config_cmd_mode1[] = { + /* TYPE_DCS_LWRITE */ + 0x2A, 0x00, 0x00, 0x01, + 0x3F, 0xFF, 0xFF, 0xFF +}; + +static char display_config_cmd_mode2[] = { + /* DTYPE_DCS_LWRITE */ + 0x2B, 0x00, 0x00, 0x01, + 0xDF, 0xFF, 0xFF, 0xFF +}; + +static char display_config_cmd_mode3_666[] = { + /* DTYPE_DCS_WRITE1 */ + 0x3A, 0x66, 0x15, 0x80 /* 666 Packed (18-bits) */ +}; + +static char display_config_cmd_mode3_565[] = { + /* DTYPE_DCS_WRITE1 */ + 0x3A, 0x55, 0x15, 0x80 /* 565 mode */ +}; + +static char display_config_321[] = { + /* DTYPE_DCS_WRITE1 */ + 0x66, 0x2e, 0x15, 0x00 /* Reg 0x66 : 2E */ +}; + +static char display_config_323[] = { + /* DTYPE_DCS_WRITE */ + 0x13, 0x00, 0x05, 0x00 /* Reg 0x13 < Set for Normal Mode> */ +}; + +static char display_config_2lan[] = { + /* DTYPE_DCS_WRITE */ + 0x61, 0x01, 0x02, 0xff /* Reg 0x61 : 01,02 < Set for 2 Data Lane > */ +}; + +static char display_config_exit_sleep[] = { + /* DTYPE_DCS_WRITE */ + 0x11, 0x00, 0x05, 0x80 /* Reg 0x11 < exit sleep mode> */ +}; + +static char display_config_TE_ON[] = { + /* DTYPE_DCS_WRITE1 */ + 0x35, 0x00, 0x15, 0x80 +}; + +static char display_config_39H[] = { + /* DTYPE_DCS_WRITE */ + 0x39, 0x00, 0x05, 0x80 +}; + +static char display_config_set_tear_scanline[] = { + /* DTYPE_DCS_LWRITE */ + 0x44, 0x00, 0x00, 0xff +}; + +static char display_config_set_twolane[] = { + /* DTYPE_DCS_WRITE1 */ + 0xae, 0x03, 0x15, 0x80 +}; + +static char display_config_set_threelane[] = { + /* DTYPE_DCS_WRITE1 */ + 0xae, 0x05, 0x15, 0x80 +}; + +#else + +static char sw_reset[2] = {0x01, 0x00}; /* DTYPE_DCS_WRITE */ +static char enter_sleep[2] = {0x10, 0x00}; /* DTYPE_DCS_WRITE */ +static char exit_sleep[2] = {0x11, 0x00}; /* DTYPE_DCS_WRITE */ +static char display_off[2] = {0x28, 0x00}; /* DTYPE_DCS_WRITE */ +static char display_on[2] = {0x29, 0x00}; /* DTYPE_DCS_WRITE */ + + + +static char rgb_888[2] = {0x3A, 0x77}; /* DTYPE_DCS_WRITE1 */ + +#if defined(NOVATEK_TWO_LANE) +static char set_num_of_lanes[2] = {0xae, 0x03}; /* DTYPE_DCS_WRITE1 */ +#else /* 1 lane */ +static char set_num_of_lanes[2] = {0xae, 0x01}; /* DTYPE_DCS_WRITE1 */ +#endif +/* commands by Novatke */ +static char novatek_f4[2] = {0xf4, 0x55}; /* DTYPE_DCS_WRITE1 */ +static char novatek_8c[16] = { /* DTYPE_DCS_LWRITE */ + 0x8C, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x08, 0x00, 0x30, 0xC0, 0xB7, 0x37}; +static char novatek_ff[2] = {0xff, 0x55 }; /* DTYPE_DCS_WRITE1 */ + +static char set_width[5] = { /* DTYPE_DCS_LWRITE */ + 0x2A, 0x00, 0x00, 0x02, 0x1B}; /* 540 - 1 */ +static char set_height[5] = { /* DTYPE_DCS_LWRITE */ + 0x2B, 0x00, 0x00, 0x03, 0xBF}; /* 960 - 1 */ +#endif + +static char led_pwm1[2] = {0x51, 0x0}; /* DTYPE_DCS_WRITE1 */ +static char led_pwm2[2] = {0x53, 0x24}; /* DTYPE_DCS_WRITE1 */ +static char led_pwm3[2] = {0x55, 0x00}; /* DTYPE_DCS_WRITE1 */ + +static struct dsi_cmd_desc novatek_cmd_backlight_cmds[] = { + {DTYPE_DCS_LWRITE, 1, 0, 0, 1, sizeof(led_pwm1), led_pwm1}, +}; + +static struct dsi_cmd_desc novatek_video_on_cmds[] = { + {DTYPE_DCS_WRITE, 1, 0, 0, 50, + sizeof(sw_reset), sw_reset}, + {DTYPE_DCS_WRITE, 1, 0, 0, 10, + sizeof(exit_sleep), exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, 10, + sizeof(display_on), display_on}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 10, + sizeof(set_num_of_lanes), set_num_of_lanes}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 10, + sizeof(rgb_888), rgb_888}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 10, + sizeof(led_pwm2), led_pwm2}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 10, + sizeof(led_pwm3), led_pwm3}, +}; + +static struct dsi_cmd_desc novatek_cmd_on_cmds[] = { + {DTYPE_DCS_WRITE, 1, 0, 0, 50, + sizeof(sw_reset), sw_reset}, + {DTYPE_DCS_WRITE, 1, 0, 0, 10, + sizeof(exit_sleep), exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, 10, + sizeof(display_on), display_on}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 50, + sizeof(novatek_f4), novatek_f4}, + {DTYPE_DCS_LWRITE, 1, 0, 0, 50, + sizeof(novatek_8c), novatek_8c}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 50, + sizeof(novatek_ff), novatek_ff}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 10, + sizeof(set_num_of_lanes), set_num_of_lanes}, + {DTYPE_DCS_LWRITE, 1, 0, 0, 50, + sizeof(set_width), set_width}, + {DTYPE_DCS_LWRITE, 1, 0, 0, 50, + sizeof(set_height), set_height}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 10, + sizeof(rgb_888), rgb_888}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 1, + sizeof(led_pwm2), led_pwm2}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 1, + sizeof(led_pwm3), led_pwm3}, +}; + +static struct dsi_cmd_desc novatek_display_off_cmds[] = { + {DTYPE_DCS_WRITE, 1, 0, 0, 10, + sizeof(display_off), display_off}, + {DTYPE_DCS_WRITE, 1, 0, 0, 120, + sizeof(enter_sleep), enter_sleep} +}; + +static char manufacture_id[2] = {0x04, 0x00}; /* DTYPE_DCS_READ */ + +static struct dsi_cmd_desc novatek_manufacture_id_cmd = { + DTYPE_DCS_READ, 1, 0, 1, 5, sizeof(manufacture_id), manufacture_id}; + +static uint32 mipi_novatek_manufacture_id(struct msm_fb_data_type *mfd) +{ + struct dsi_buf *rp, *tp; + struct dsi_cmd_desc *cmd; + uint32 *lp; + + tp = &novatek_tx_buf; + rp = &novatek_rx_buf; + mipi_dsi_buf_init(rp); + mipi_dsi_buf_init(tp); + + cmd = &novatek_manufacture_id_cmd; + mipi_dsi_cmds_rx(mfd, tp, rp, cmd, 3); + lp = (uint32 *)rp->data; + pr_info("%s: manufacture_id=%x", __func__, *lp); + return *lp; +} + +static int fpga_addr; +static bool support_3d; + +static void mipi_novatek_3d_init(int addr) +{ + fpga_addr = addr; +} + +static void mipi_dsi_enable_3d_barrier(int mode) +{ + void __iomem *fpga_ptr; + uint32_t ptr_value = 0; + + if (!fpga_addr && support_3d) { + pr_err("%s: fpga_addr not set. Failed to enable 3D barrier\n", + __func__); + return; + } + + fpga_ptr = ioremap_nocache(fpga_addr, sizeof(uint32_t)); + if (!fpga_ptr) { + pr_err("%s: FPGA ioremap failed. Failed to enable 3D barrier\n", + __func__); + return; + } + + ptr_value = readl_relaxed(fpga_ptr); + if (mode == LANDSCAPE) + writel_relaxed(((0xFFFF0000 & ptr_value) | 1), fpga_ptr); + else if (mode == PORTRAIT) + writel_relaxed(((0xFFFF0000 & ptr_value) | 3), fpga_ptr); + else + writel_relaxed((0xFFFF0000 & ptr_value), fpga_ptr); + + mb(); + iounmap(fpga_ptr); +} + +static int mipi_novatek_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct mipi_panel_info *mipi; + struct msm_panel_info *pinfo; + + mfd = platform_get_drvdata(pdev); + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + pinfo = &mfd->panel_info; + if (pinfo->is_3d_panel) + support_3d = TRUE; + + mipi = &mfd->panel_info.mipi; + + if (mipi->mode == DSI_VIDEO_MODE) { + mipi_dsi_cmds_tx(mfd, &novatek_tx_buf, novatek_video_on_cmds, + ARRAY_SIZE(novatek_video_on_cmds)); + } else { + mipi_dsi_cmds_tx(mfd, &novatek_tx_buf, novatek_cmd_on_cmds, + ARRAY_SIZE(novatek_cmd_on_cmds)); + + mipi_dsi_cmd_bta_sw_trigger(); /* clean up ack_err_status */ + + mipi_novatek_manufacture_id(mfd); + } + + return 0; +} + +static int mipi_novatek_lcd_off(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi_dsi_cmds_tx(mfd, &novatek_tx_buf, novatek_display_off_cmds, + ARRAY_SIZE(novatek_display_off_cmds)); + + return 0; +} + + + +static void mipi_novatek_set_backlight(struct msm_fb_data_type *mfd) +{ + struct mipi_panel_info *mipi; + static int bl_level_old; + + mipi = &mfd->panel_info.mipi; + if (bl_level_old == mfd->bl_level) + return; + + mutex_lock(&mfd->dma->ov_mutex); + /* mdp4_dsi_cmd_busy_wait: will turn on dsi clock also */ + mdp4_dsi_cmd_dma_busy_wait(mfd); + mdp4_dsi_blt_dmap_busy_wait(mfd); + + led_pwm1[1] = (unsigned char)(mfd->bl_level); + mipi_dsi_cmds_tx(mfd, &novatek_tx_buf, novatek_cmd_backlight_cmds, + ARRAY_SIZE(novatek_cmd_backlight_cmds)); + bl_level_old = mfd->bl_level; + mutex_unlock(&mfd->dma->ov_mutex); + return; +} + +static int mipi_dsi_3d_barrier_sysfs_register(struct device *dev); +static int barrier_mode; + +static int __devinit mipi_novatek_lcd_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + mipi_novatek_pdata = pdev->dev.platform_data; + + if (mipi_novatek_pdata && mipi_novatek_pdata->fpga_3d_config_addr) + mipi_novatek_3d_init(mipi_novatek_pdata->fpga_3d_config_addr); + + /* create sysfs to control 3D barrier for the Sharp panel */ + if (mipi_dsi_3d_barrier_sysfs_register(&pdev->dev)) { + pr_err("%s: Failed to register 3d Barrier sysfs\n", + __func__); + return -ENODEV; + } + barrier_mode = 0; + + return 0; + } + + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = mipi_novatek_lcd_probe, + .driver = { + .name = "mipi_novatek", + }, +}; + +static struct msm_fb_panel_data novatek_panel_data = { + .on = mipi_novatek_lcd_on, + .off = mipi_novatek_lcd_off, + .set_backlight = mipi_novatek_set_backlight, +}; + +static ssize_t mipi_dsi_3d_barrier_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return snprintf((char *)buf, sizeof(buf), "%u\n", barrier_mode); +} + +static ssize_t mipi_dsi_3d_barrier_write(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + int ret = -1; + u32 data = 0; + + if (sscanf((char *)buf, "%u", &data) != 1) { + dev_err(dev, "%s\n", __func__); + ret = -EINVAL; + } else { + barrier_mode = data; + if (data == 1) + mipi_dsi_enable_3d_barrier(LANDSCAPE); + else if (data == 2) + mipi_dsi_enable_3d_barrier(PORTRAIT); + else + mipi_dsi_enable_3d_barrier(0); + } + + return count; +} + +static struct device_attribute mipi_dsi_3d_barrier_attributes[] = { + __ATTR(enable_3d_barrier, 0666, mipi_dsi_3d_barrier_read, + mipi_dsi_3d_barrier_write), +}; + +static int mipi_dsi_3d_barrier_sysfs_register(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mipi_dsi_3d_barrier_attributes); i++) + if (device_create_file(dev, mipi_dsi_3d_barrier_attributes + i)) + goto error; + + return 0; + +error: + for (; i >= 0 ; i--) + device_remove_file(dev, mipi_dsi_3d_barrier_attributes + i); + pr_err("%s: Unable to create interface\n", __func__); + + return -ENODEV; +} + +static int ch_used[3]; + +int mipi_novatek_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel) +{ + struct platform_device *pdev = NULL; + int ret; + + if ((channel >= 3) || ch_used[channel]) + return -ENODEV; + + ch_used[channel] = TRUE; + + pdev = platform_device_alloc("mipi_novatek", (panel << 8)|channel); + if (!pdev) + return -ENOMEM; + + novatek_panel_data.panel_info = *pinfo; + + ret = platform_device_add_data(pdev, &novatek_panel_data, + sizeof(novatek_panel_data)); + if (ret) { + printk(KERN_ERR + "%s: platform_device_add_data failed!\n", __func__); + goto err_device_put; + } + + ret = platform_device_add(pdev); + if (ret) { + printk(KERN_ERR + "%s: platform_device_register failed!\n", __func__); + goto err_device_put; + } + + return 0; + +err_device_put: + platform_device_put(pdev); + return ret; +} + +static int __init mipi_novatek_lcd_init(void) +{ + mipi_dsi_buf_alloc(&novatek_tx_buf, DSI_BUF_SIZE); + mipi_dsi_buf_alloc(&novatek_rx_buf, DSI_BUF_SIZE); + + return platform_driver_register(&this_driver); +} + +module_init(mipi_novatek_lcd_init); diff --git a/drivers/video/msm/mipi_novatek.h b/drivers/video/msm/mipi_novatek.h new file mode 100644 index 00000000000..f84de9aa7b8 --- /dev/null +++ b/drivers/video/msm/mipi_novatek.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MIPI_NOVATEK_BLUE_H +#define MIPI_NOVATEK_BLUE_H + +#define NOVATEK_TWO_LANE + +int mipi_novatek_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#endif /* MIPI_NOVATEK_BLUE_H */ diff --git a/drivers/video/msm/mipi_novatek_cmd_qhd_pt.c b/drivers/video/msm/mipi_novatek_cmd_qhd_pt.c new file mode 100644 index 00000000000..b3bd6c8bc3c --- /dev/null +++ b/drivers/video/msm/mipi_novatek_cmd_qhd_pt.c @@ -0,0 +1,99 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_novatek.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_cmd_mode_phy_db = { +/* DSI_BIT_CLK at 500MHz, 2 lane, RGB888 */ + {0x03, 0x01, 0x01, 0x00}, /* regulator */ + /* timing */ + {0xB4, 0x8D, 0x1D, 0x00, 0x20, 0x94, 0x20, + 0x8F, 0x20, 0x03, 0x04}, + {0x7f, 0x00, 0x00, 0x00}, /* phy ctrl */ + {0xee, 0x02, 0x86, 0x00}, /* strength */ + /* pll control */ + {0x40, 0xf9, 0xb0, 0xda, 0x00, 0x50, 0x48, 0x63, +#if defined(NOVATEK_TWO_LANE) + 0x30, 0x07, 0x03, +#else /* default set to 1 lane */ + 0x30, 0x07, 0x07, +#endif + 0x05, 0x14, 0x03, 0x0, 0x0, 0x54, 0x06, 0x10, 0x04, 0x0}, +}; + +static int __init mipi_cmd_novatek_blue_qhd_pt_init(void) +{ + int ret; + +#ifdef CONFIG_FB_MSM_MIPI_PANEL_DETECT + if (msm_fb_detect_client("mipi_cmd_novatek_qhd")) + return 0; +#endif + + pinfo.xres = 540; + pinfo.yres = 960; + pinfo.type = MIPI_CMD_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.lcdc.h_back_porch = 50; + pinfo.lcdc.h_front_porch = 50; + pinfo.lcdc.h_pulse_width = 20; + pinfo.lcdc.v_back_porch = 11; + pinfo.lcdc.v_front_porch = 10; + pinfo.lcdc.v_pulse_width = 5; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = 255; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + pinfo.clk_rate = 454000000; + pinfo.is_3d_panel = FB_TYPE_3D_PANEL; + pinfo.lcd.vsync_enable = TRUE; + pinfo.lcd.hw_vsync_mode = TRUE; + pinfo.lcd.refx100 = 6000; /* adjust refx100 to prevent tearing */ + + pinfo.mipi.mode = DSI_CMD_MODE; + pinfo.mipi.dst_format = DSI_CMD_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + pinfo.mipi.rgb_swap = DSI_RGB_SWAP_BGR; + pinfo.mipi.data_lane0 = TRUE; +#if defined(NOVATEK_TWO_LANE) + pinfo.mipi.data_lane1 = TRUE; +#endif + pinfo.mipi.t_clk_post = 0x22; + pinfo.mipi.t_clk_pre = 0x3f; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.te_sel = 1; /* TE from vsycn gpio */ + pinfo.mipi.interleave_max = 1; + pinfo.mipi.insert_dcs_cmd = TRUE; + pinfo.mipi.wr_mem_continue = 0x3c; + pinfo.mipi.wr_mem_start = 0x2c; + pinfo.mipi.dsi_phy_db = &dsi_cmd_mode_phy_db; + + ret = mipi_novatek_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_WVGA_PT); + if (ret) + pr_err("%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_cmd_novatek_blue_qhd_pt_init); diff --git a/drivers/video/msm/mipi_novatek_video_qhd_pt.c b/drivers/video/msm/mipi_novatek_video_qhd_pt.c new file mode 100644 index 00000000000..635b66e68db --- /dev/null +++ b/drivers/video/msm/mipi_novatek_video_qhd_pt.c @@ -0,0 +1,98 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_novatek.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { +/* DSI_BIT_CLK at 500MHz, 2 lane, RGB888 */ + {0x03, 0x01, 0x01, 0x00}, /* regulator */ + /* timing */ + {0x82, 0x31, 0x13, 0x0, 0x42, 0x4D, 0x18, + 0x35, 0x21, 0x03, 0x04}, + {0x7f, 0x00, 0x00, 0x00}, /* phy ctrl */ + {0xee, 0x02, 0x86, 0x00}, /* strength */ + /* pll control */ + {0x40, 0xf9, 0xb0, 0xda, 0x00, 0x50, 0x48, 0x63, +#if defined(NOVATEK_TWO_LANE) + 0x30, 0x07, 0x03, +#else /* default set to 1 lane */ + 0x30, 0x07, 0x07, +#endif + 0x05, 0x14, 0x03, 0x0, 0x0, 0x54, 0x06, 0x10, 0x04, 0x0}, +}; + +static int __init mipi_video_novatek_qhd_pt_init(void) +{ + int ret; + +#ifdef CONFIG_FB_MSM_MIPI_PANEL_DETECT + if (msm_fb_detect_client("mipi_video_novatek_qhd")) + return 0; +#endif + + pinfo.xres = 540; + pinfo.yres = 960; + pinfo.type = MIPI_VIDEO_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.lcdc.h_back_porch = 80; + pinfo.lcdc.h_front_porch = 24; + pinfo.lcdc.h_pulse_width = 8; + pinfo.lcdc.v_back_porch = 16; + pinfo.lcdc.v_front_porch = 8; + pinfo.lcdc.v_pulse_width = 1; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = 15; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + + pinfo.mipi.mode = DSI_VIDEO_MODE; + pinfo.mipi.pulse_mode_hsa_he = TRUE; + pinfo.mipi.hfp_power_stop = FALSE; + pinfo.mipi.hbp_power_stop = FALSE; + pinfo.mipi.hsa_power_stop = FALSE; + pinfo.mipi.eof_bllp_power_stop = TRUE; + pinfo.mipi.bllp_power_stop = TRUE; + pinfo.mipi.traffic_mode = DSI_NON_BURST_SYNCH_PULSE; + pinfo.mipi.dst_format = DSI_VIDEO_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + pinfo.mipi.rgb_swap = DSI_RGB_SWAP_BGR; + pinfo.mipi.data_lane0 = TRUE; +#if defined(NOVATEK_TWO_LANE) + pinfo.mipi.data_lane1 = TRUE; +#endif + pinfo.mipi.tx_eot_append = TRUE; + pinfo.mipi.t_clk_post = 0x04; + pinfo.mipi.t_clk_pre = 0x1c; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.frame_rate = 60; + pinfo.mipi.dsi_phy_db = &dsi_video_mode_phy_db; + + ret = mipi_novatek_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_WVGA_PT); + if (ret) + pr_err("%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_video_novatek_qhd_pt_init); diff --git a/drivers/video/msm/mipi_renesas.c b/drivers/video/msm/mipi_renesas.c new file mode 100644 index 00000000000..652ca29465a --- /dev/null +++ b/drivers/video/msm/mipi_renesas.c @@ -0,0 +1,1231 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_renesas.h" + +#define RENESAS_CMD_DELAY 0 /* 50 */ +#define RENESAS_SLEEP_OFF_DELAY 50 +static struct msm_panel_common_pdata *mipi_renesas_pdata; + +static struct dsi_buf renesas_tx_buf; +static struct dsi_buf renesas_rx_buf; + +static char config_sleep_out[2] = {0x11, 0x00}; +static char config_CMD_MODE[2] = {0x40, 0x01}; +static char config_WRTXHT[7] = {0x92, 0x16, 0x08, 0x08, 0x00, 0x01, 0xe0}; +static char config_WRTXVT[7] = {0x8b, 0x02, 0x02, 0x02, 0x00, 0x03, 0x60}; +static char config_PLL2NR[2] = {0xa0, 0x24}; +static char config_PLL2NF1[2] = {0xa2, 0xd0}; +static char config_PLL2NF2[2] = {0xa4, 0x00}; +static char config_PLL2BWADJ1[2] = {0xa6, 0xd0}; +static char config_PLL2BWADJ2[2] = {0xa8, 0x00}; +static char config_PLL2CTL[2] = {0xaa, 0x00}; +static char config_DBICBR[2] = {0x48, 0x03}; +static char config_DBICTYPE[2] = {0x49, 0x00}; +static char config_DBICSET1[2] = {0x4a, 0x1c}; +static char config_DBICADD[2] = {0x4b, 0x00}; +static char config_DBICCTL[2] = {0x4e, 0x01}; +/* static char config_COLMOD_565[2] = {0x3a, 0x05}; */ +/* static char config_COLMOD_666PACK[2] = {0x3a, 0x06}; */ +static char config_COLMOD_888[2] = {0x3a, 0x07}; +static char config_MADCTL[2] = {0x36, 0x00}; +static char config_DBIOC[2] = {0x82, 0x40}; +static char config_CASET[7] = {0x2a, 0x00, 0x00, 0x00, 0x00, 0x01, 0xdf }; +static char config_PASET[7] = {0x2b, 0x00, 0x00, 0x00, 0x00, 0x03, 0x5f }; +static char config_TXON[2] = {0x81, 0x00}; +static char config_BLSET_TM[2] = {0xff, 0x6c}; + +static char config_AGCPSCTL_TM[2] = {0x56, 0x08}; + +static char config_DBICADD70[2] = {0x4b, 0x70}; +static char config_DBICSET_15[2] = {0x4a, 0x15}; +static char config_DBICADD72[2] = {0x4b, 0x72}; + +static char config_Power_Ctrl_2a_cmd[3] = {0x4c, 0x40, 0x10}; +static char config_Auto_Sequencer_Setting_a_cmd[3] = {0x4c, 0x00, 0x00}; +static char Driver_Output_Ctrl_indx[3] = {0x4c, 0x00, 0x01}; +static char Driver_Output_Ctrl_cmd[3] = {0x4c, 0x03, 0x10}; +static char config_LCD_drive_AC_Ctrl_indx[3] = {0x4c, 0x00, 0x02}; +static char config_LCD_drive_AC_Ctrl_cmd[3] = {0x4c, 0x01, 0x00}; +static char config_Entry_Mode_indx[3] = {0x4c, 0x00, 0x03}; +static char config_Entry_Mode_cmd[3] = {0x4c, 0x00, 0x00}; +static char config_Display_Ctrl_1_indx[3] = {0x4c, 0x00, 0x07}; +static char config_Display_Ctrl_1_cmd[3] = {0x4c, 0x00, 0x00}; +static char config_Display_Ctrl_2_indx[3] = {0x4c, 0x00, 0x08}; +static char config_Display_Ctrl_2_cmd[3] = {0x4c, 0x00, 0x04}; +static char config_Display_Ctrl_3_indx[3] = {0x4c, 0x00, 0x09}; +static char config_Display_Ctrl_3_cmd[3] = {0x4c, 0x00, 0x0c}; +static char config_Display_IF_Ctrl_1_indx[3] = {0x4c, 0x00, 0x0c}; +static char config_Display_IF_Ctrl_1_cmd[3] = {0x4c, 0x40, 0x10}; +static char config_Display_IF_Ctrl_2_indx[3] = {0x4c, 0x00, 0x0e}; +static char config_Display_IF_Ctrl_2_cmd[3] = {0x4c, 0x00, 0x00}; + +static char config_Panel_IF_Ctrl_1_indx[3] = {0x4c, 0x00, 0x20}; +static char config_Panel_IF_Ctrl_1_cmd[3] = {0x4c, 0x01, 0x3f}; +static char config_Panel_IF_Ctrl_3_indx[3] = {0x4c, 0x00, 0x22}; +static char config_Panel_IF_Ctrl_3_cmd[3] = {0x4c, 0x76, 0x00}; +static char config_Panel_IF_Ctrl_4_indx[3] = {0x4c, 0x00, 0x23}; +static char config_Panel_IF_Ctrl_4_cmd[3] = {0x4c, 0x1c, 0x0a}; +static char config_Panel_IF_Ctrl_5_indx[3] = {0x4c, 0x00, 0x24}; +static char config_Panel_IF_Ctrl_5_cmd[3] = {0x4c, 0x1c, 0x2c}; +static char config_Panel_IF_Ctrl_6_indx[3] = {0x4c, 0x00, 0x25}; +static char config_Panel_IF_Ctrl_6_cmd[3] = {0x4c, 0x1c, 0x4e}; +static char config_Panel_IF_Ctrl_8_indx[3] = {0x4c, 0x00, 0x27}; +static char config_Panel_IF_Ctrl_8_cmd[3] = {0x4c, 0x00, 0x00}; +static char config_Panel_IF_Ctrl_9_indx[3] = {0x4c, 0x00, 0x28}; +static char config_Panel_IF_Ctrl_9_cmd[3] = {0x4c, 0x76, 0x0c}; + + +static char config_gam_adjust_00_indx[3] = {0x4c, 0x03, 0x00}; +static char config_gam_adjust_00_cmd[3] = {0x4c, 0x00, 0x00}; +static char config_gam_adjust_01_indx[3] = {0x4c, 0x03, 0x01}; +static char config_gam_adjust_01_cmd[3] = {0x4c, 0x05, 0x02}; +static char config_gam_adjust_02_indx[3] = {0x4c, 0x03, 0x02}; +static char config_gam_adjust_02_cmd[3] = {0x4c, 0x07, 0x05}; +static char config_gam_adjust_03_indx[3] = {0x4c, 0x03, 0x03}; +static char config_gam_adjust_03_cmd[3] = {0x4c, 0x00, 0x00}; +static char config_gam_adjust_04_indx[3] = {0x4c, 0x03, 0x04}; +static char config_gam_adjust_04_cmd[3] = {0x4c, 0x02, 0x00}; +static char config_gam_adjust_05_indx[3] = {0x4c, 0x03, 0x05}; +static char config_gam_adjust_05_cmd[3] = {0x4c, 0x07, 0x07}; +static char config_gam_adjust_06_indx[3] = {0x4c, 0x03, 0x06}; +static char config_gam_adjust_06_cmd[3] = {0x4c, 0x10, 0x10}; +static char config_gam_adjust_07_indx[3] = {0x4c, 0x03, 0x07}; +static char config_gam_adjust_07_cmd[3] = {0x4c, 0x02, 0x02}; +static char config_gam_adjust_08_indx[3] = {0x4c, 0x03, 0x08}; +static char config_gam_adjust_08_cmd[3] = {0x4c, 0x07, 0x04}; +static char config_gam_adjust_09_indx[3] = {0x4c, 0x03, 0x09}; +static char config_gam_adjust_09_cmd[3] = {0x4c, 0x07, 0x07}; +static char config_gam_adjust_0A_indx[3] = {0x4c, 0x03, 0x0a}; +static char config_gam_adjust_0A_cmd[3] = {0x4c, 0x00, 0x00}; +static char config_gam_adjust_0B_indx[3] = {0x4c, 0x03, 0x0b}; +static char config_gam_adjust_0B_cmd[3] = {0x4c, 0x00, 0x00}; +static char config_gam_adjust_0C_indx[3] = {0x4c, 0x03, 0x0c}; +static char config_gam_adjust_0C_cmd[3] = {0x4c, 0x07, 0x07}; +static char config_gam_adjust_0D_indx[3] = {0x4c, 0x03, 0x0d}; +static char config_gam_adjust_0D_cmd[3] = {0x4c, 0x10, 0x10}; +static char config_gam_adjust_10_indx[3] = {0x4c, 0x03, 0x10}; +static char config_gam_adjust_10_cmd[3] = {0x4c, 0x01, 0x04}; +static char config_gam_adjust_11_indx[3] = {0x4c, 0x03, 0x11}; +static char config_gam_adjust_11_cmd[3] = {0x4c, 0x05, 0x03}; +static char config_gam_adjust_12_indx[3] = {0x4c, 0x03, 0x12}; +static char config_gam_adjust_12_cmd[3] = {0x4c, 0x03, 0x04}; +static char config_gam_adjust_15_indx[3] = {0x4c, 0x03, 0x15}; +static char config_gam_adjust_15_cmd[3] = {0x4c, 0x03, 0x04}; +static char config_gam_adjust_16_indx[3] = {0x4c, 0x03, 0x16}; +static char config_gam_adjust_16_cmd[3] = {0x4c, 0x03, 0x1c}; +static char config_gam_adjust_17_indx[3] = {0x4c, 0x03, 0x17}; +static char config_gam_adjust_17_cmd[3] = {0x4c, 0x02, 0x04}; +static char config_gam_adjust_18_indx[3] = {0x4c, 0x03, 0x18}; +static char config_gam_adjust_18_cmd[3] = {0x4c, 0x04, 0x02}; +static char config_gam_adjust_19_indx[3] = {0x4c, 0x03, 0x19}; +static char config_gam_adjust_19_cmd[3] = {0x4c, 0x03, 0x05}; +static char config_gam_adjust_1C_indx[3] = {0x4c, 0x03, 0x1c}; +static char config_gam_adjust_1C_cmd[3] = {0x4c, 0x07, 0x07}; +static char config_gam_adjust_1D_indx[3] = {0x4c, 0x03, 0x1D}; +static char config_gam_adjust_1D_cmd[3] = {0x4c, 0x02, 0x1f}; +static char config_gam_adjust_20_indx[3] = {0x4c, 0x03, 0x20}; +static char config_gam_adjust_20_cmd[3] = {0x4c, 0x05, 0x07}; +static char config_gam_adjust_21_indx[3] = {0x4c, 0x03, 0x21}; +static char config_gam_adjust_21_cmd[3] = {0x4c, 0x06, 0x04}; +static char config_gam_adjust_22_indx[3] = {0x4c, 0x03, 0x22}; +static char config_gam_adjust_22_cmd[3] = {0x4c, 0x04, 0x05}; +static char config_gam_adjust_27_indx[3] = {0x4c, 0x03, 0x27}; +static char config_gam_adjust_27_cmd[3] = {0x4c, 0x02, 0x03}; +static char config_gam_adjust_28_indx[3] = {0x4c, 0x03, 0x28}; +static char config_gam_adjust_28_cmd[3] = {0x4c, 0x03, 0x00}; +static char config_gam_adjust_29_indx[3] = {0x4c, 0x03, 0x29}; +static char config_gam_adjust_29_cmd[3] = {0x4c, 0x00, 0x02}; + +static char config_Power_Ctrl_1_indx[3] = {0x4c, 0x01, 0x00}; +static char config_Power_Ctrl_1b_cmd[3] = {0x4c, 0x36, 0x3c}; +static char config_Power_Ctrl_2_indx[3] = {0x4c, 0x01, 0x01}; +static char config_Power_Ctrl_2b_cmd[3] = {0x4c, 0x40, 0x03}; +static char config_Power_Ctrl_3_indx[3] = {0x4c, 0x01, 0x02}; +static char config_Power_Ctrl_3a_cmd[3] = {0x4c, 0x00, 0x01}; +static char config_Power_Ctrl_4_indx[3] = {0x4c, 0x01, 0x03}; +static char config_Power_Ctrl_4a_cmd[3] = {0x4c, 0x3c, 0x58}; +static char config_Power_Ctrl_6_indx[3] = {0x4c, 0x01, 0x0c}; +static char config_Power_Ctrl_6a_cmd[3] = {0x4c, 0x01, 0x35}; + +static char config_Auto_Sequencer_Setting_b_cmd[3] = {0x4c, 0x00, 0x02}; + +static char config_Panel_IF_Ctrl_10_indx[3] = {0x4c, 0x00, 0x29}; +static char config_Panel_IF_Ctrl_10a_cmd[3] = {0x4c, 0x03, 0xbf}; +static char config_Auto_Sequencer_Setting_indx[3] = {0x4c, 0x01, 0x06}; +static char config_Auto_Sequencer_Setting_c_cmd[3] = {0x4c, 0x00, 0x03}; +static char config_Power_Ctrl_2c_cmd[3] = {0x4c, 0x40, 0x10}; + +static char config_VIDEO[2] = {0x40, 0x00}; + +static char config_Panel_IF_Ctrl_10_indx_off[3] = {0x4C, 0x00, 0x29}; + +static char config_Panel_IF_Ctrl_10b_cmd_off[3] = {0x4C, 0x00, 0x02}; + +static char config_Power_Ctrl_1a_cmd[3] = {0x4C, 0x30, 0x00}; + +static struct dsi_cmd_desc renesas_sleep_off_cmds[] = { + {DTYPE_DCS_WRITE, 1, 0, 0, RENESAS_SLEEP_OFF_DELAY, + sizeof(config_sleep_out), config_sleep_out } +}; + +static struct dsi_cmd_desc renesas_display_off_cmds[] = { + /* Choosing Command Mode */ + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_CMD_MODE), config_CMD_MODE }, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_indx), + config_Auto_Sequencer_Setting_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_b_cmd), + config_Auto_Sequencer_Setting_b_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY * 2, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + /* After waiting >= 5 frames, turn OFF RGB signals + This is done by on DSI/MDP (depends on Vid/Cmd Mode. */ + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_indx), + config_Auto_Sequencer_Setting_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_a_cmd), + config_Auto_Sequencer_Setting_a_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_10_indx_off), + config_Panel_IF_Ctrl_10_indx_off}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_10b_cmd_off), + config_Panel_IF_Ctrl_10b_cmd_off}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_1_indx), + config_Power_Ctrl_1_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_1a_cmd), + config_Power_Ctrl_1a_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15} +}; + +static struct dsi_cmd_desc renesas_display_on_cmds[] = { + /* Choosing Command Mode */ + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_CMD_MODE), config_CMD_MODE }, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_WRTXHT), config_WRTXHT }, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_WRTXVT), config_WRTXVT }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_PLL2NR), config_PLL2NR }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_PLL2NF1), config_PLL2NF1 }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_PLL2NF2), config_PLL2NF2 }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_PLL2BWADJ1), config_PLL2BWADJ1}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_PLL2BWADJ2), config_PLL2BWADJ2}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_PLL2CTL), config_PLL2CTL}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICBR), config_DBICBR}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICTYPE), config_DBICTYPE}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET1), config_DBICSET1}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD), config_DBICADD}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICCTL), config_DBICCTL}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_COLMOD_888), config_COLMOD_888}, + /* Choose config_COLMOD_565 or config_COLMOD_666PACK for other modes */ + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_MADCTL), config_MADCTL}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBIOC), config_DBIOC}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_CASET), config_CASET}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_PASET), config_PASET}, + {DTYPE_DCS_WRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_TXON), config_TXON}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_BLSET_TM), config_BLSET_TM}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_AGCPSCTL_TM), config_AGCPSCTL_TM}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_1_indx), config_Power_Ctrl_1_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_1a_cmd), config_Power_Ctrl_1a_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_2_indx), config_Power_Ctrl_2_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_2a_cmd), config_Power_Ctrl_2a_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_indx), + config_Auto_Sequencer_Setting_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_a_cmd), + config_Auto_Sequencer_Setting_a_cmd }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(Driver_Output_Ctrl_indx), Driver_Output_Ctrl_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(Driver_Output_Ctrl_cmd), + Driver_Output_Ctrl_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_LCD_drive_AC_Ctrl_indx), + config_LCD_drive_AC_Ctrl_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_LCD_drive_AC_Ctrl_cmd), + config_LCD_drive_AC_Ctrl_cmd }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Entry_Mode_indx), + config_Entry_Mode_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Entry_Mode_cmd), + config_Entry_Mode_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_Ctrl_1_indx), + config_Display_Ctrl_1_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_Ctrl_1_cmd), + config_Display_Ctrl_1_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_Ctrl_2_indx), + config_Display_Ctrl_2_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_Ctrl_2_cmd), + config_Display_Ctrl_2_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_Ctrl_3_indx), + config_Display_Ctrl_3_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_Ctrl_3_cmd), + config_Display_Ctrl_3_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_IF_Ctrl_1_indx), + config_Display_IF_Ctrl_1_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_IF_Ctrl_1_cmd), + config_Display_IF_Ctrl_1_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_IF_Ctrl_2_indx), + config_Display_IF_Ctrl_2_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Display_IF_Ctrl_2_cmd), + config_Display_IF_Ctrl_2_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_1_indx), + config_Panel_IF_Ctrl_1_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_1_cmd), + config_Panel_IF_Ctrl_1_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_3_indx), + config_Panel_IF_Ctrl_3_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_3_cmd), + config_Panel_IF_Ctrl_3_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_4_indx), + config_Panel_IF_Ctrl_4_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_4_cmd), + config_Panel_IF_Ctrl_4_cmd }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_5_indx), + config_Panel_IF_Ctrl_5_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_5_cmd), + config_Panel_IF_Ctrl_5_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_6_indx), + config_Panel_IF_Ctrl_6_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_6_cmd), + config_Panel_IF_Ctrl_6_cmd }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_8_indx), + config_Panel_IF_Ctrl_8_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_8_cmd), + config_Panel_IF_Ctrl_8_cmd }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_9_indx), + config_Panel_IF_Ctrl_9_indx }, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_9_cmd), + config_Panel_IF_Ctrl_9_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_00_indx), + config_gam_adjust_00_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_00_cmd), + config_gam_adjust_00_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_01_indx), + config_gam_adjust_01_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_01_cmd), + config_gam_adjust_01_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_02_indx), + config_gam_adjust_02_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_02_cmd), + config_gam_adjust_02_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_03_indx), + config_gam_adjust_03_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_03_cmd), + config_gam_adjust_03_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_04_indx), config_gam_adjust_04_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_04_cmd), config_gam_adjust_04_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_05_indx), config_gam_adjust_05_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_05_cmd), config_gam_adjust_05_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_06_indx), config_gam_adjust_06_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_06_cmd), config_gam_adjust_06_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_07_indx), config_gam_adjust_07_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_07_cmd), config_gam_adjust_07_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_08_indx), config_gam_adjust_08_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_08_cmd), config_gam_adjust_08_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_09_indx), config_gam_adjust_09_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_09_cmd), config_gam_adjust_09_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_0A_indx), config_gam_adjust_0A_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_0A_cmd), config_gam_adjust_0A_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_0B_indx), config_gam_adjust_0B_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_0B_cmd), config_gam_adjust_0B_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_0C_indx), config_gam_adjust_0C_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_0C_cmd), config_gam_adjust_0C_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_0D_indx), config_gam_adjust_0D_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_0D_cmd), config_gam_adjust_0D_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_10_indx), config_gam_adjust_10_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_10_cmd), config_gam_adjust_10_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_11_indx), config_gam_adjust_11_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_11_cmd), config_gam_adjust_11_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_12_indx), config_gam_adjust_12_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_12_cmd), config_gam_adjust_12_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_15_indx), config_gam_adjust_15_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_15_cmd), config_gam_adjust_15_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_16_indx), config_gam_adjust_16_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_16_cmd), config_gam_adjust_16_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_17_indx), config_gam_adjust_17_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_17_cmd), config_gam_adjust_17_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_18_indx), config_gam_adjust_18_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_18_cmd), config_gam_adjust_18_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_19_indx), config_gam_adjust_19_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_19_cmd), config_gam_adjust_19_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_1C_indx), config_gam_adjust_1C_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_1C_cmd), config_gam_adjust_1C_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_1D_indx), config_gam_adjust_1D_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_1D_cmd), config_gam_adjust_1D_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_20_indx), config_gam_adjust_20_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_20_cmd), config_gam_adjust_20_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_21_indx), config_gam_adjust_21_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_21_cmd), config_gam_adjust_21_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_22_indx), config_gam_adjust_22_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_22_cmd), config_gam_adjust_22_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_27_indx), config_gam_adjust_27_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_27_cmd), config_gam_adjust_27_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_28_indx), config_gam_adjust_28_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_28_cmd), config_gam_adjust_28_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_29_indx), config_gam_adjust_29_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_gam_adjust_29_cmd), config_gam_adjust_29_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_1_indx), config_Power_Ctrl_1_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_1b_cmd), config_Power_Ctrl_1b_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_2_indx), config_Power_Ctrl_2_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_2b_cmd), config_Power_Ctrl_2b_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_3_indx), config_Power_Ctrl_3_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_3a_cmd), config_Power_Ctrl_3a_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_4_indx), config_Power_Ctrl_4_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_4a_cmd), config_Power_Ctrl_4a_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_6_indx), config_Power_Ctrl_6_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_6a_cmd), config_Power_Ctrl_6a_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_indx), + config_Auto_Sequencer_Setting_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_b_cmd), + config_Auto_Sequencer_Setting_b_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_10_indx), + config_Panel_IF_Ctrl_10_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Panel_IF_Ctrl_10a_cmd), + config_Panel_IF_Ctrl_10a_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_indx), + config_Auto_Sequencer_Setting_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Auto_Sequencer_Setting_c_cmd), + config_Auto_Sequencer_Setting_c_cmd}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD70), config_DBICADD70}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_2_indx), + config_Power_Ctrl_2_indx}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICSET_15), config_DBICSET_15}, + {DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_DBICADD72), config_DBICADD72}, + {DTYPE_DCS_LWRITE, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_Power_Ctrl_2c_cmd), + config_Power_Ctrl_2c_cmd}, + + {DTYPE_DCS_WRITE1, 1, 0, 0, 0/* RENESAS_CMD_DELAY */, + sizeof(config_DBICSET_15), config_DBICSET_15}, + +}; + +static struct dsi_cmd_desc renesas_video_on_cmds[] = { +{DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_VIDEO), config_VIDEO} +}; + +static struct dsi_cmd_desc renesas_cmd_on_cmds[] = { +{DTYPE_DCS_WRITE1, 1, 0, 0, RENESAS_CMD_DELAY, + sizeof(config_CMD_MODE), config_CMD_MODE}, +}; + +static int mipi_renesas_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct mipi_panel_info *mipi; + + mfd = platform_get_drvdata(pdev); + mipi = &mfd->panel_info.mipi; + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi_dsi_cmds_tx(mfd, &renesas_tx_buf, renesas_sleep_off_cmds, + ARRAY_SIZE(renesas_sleep_off_cmds)); + + mipi_set_tx_power_mode(1); + mipi_dsi_cmds_tx(mfd, &renesas_tx_buf, renesas_display_on_cmds, + ARRAY_SIZE(renesas_display_on_cmds)); + + if (mipi->mode == DSI_VIDEO_MODE) + mipi_dsi_cmds_tx(mfd, &renesas_tx_buf, renesas_video_on_cmds, + ARRAY_SIZE(renesas_video_on_cmds)); + else + mipi_dsi_cmds_tx(mfd, &renesas_tx_buf, renesas_cmd_on_cmds, + ARRAY_SIZE(renesas_cmd_on_cmds)); + mipi_set_tx_power_mode(0); + + return 0; +} + +static int mipi_renesas_lcd_off(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi_dsi_cmds_tx(mfd, &renesas_tx_buf, renesas_display_off_cmds, + ARRAY_SIZE(renesas_display_off_cmds)); + + return 0; +} + +static int __devinit mipi_renesas_lcd_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + mipi_renesas_pdata = pdev->dev.platform_data; + return 0; + } + + msm_fb_add_device(pdev); + + return 0; +} + +static void mipi_renesas_set_backlight(struct msm_fb_data_type *mfd) +{ + int ret = -EPERM; + int bl_level; + + bl_level = mfd->bl_level; + + if (mipi_renesas_pdata && mipi_renesas_pdata->pmic_backlight) + ret = mipi_renesas_pdata->pmic_backlight(bl_level); + else + pr_err("%s(): Backlight level set failed", __func__); +} + +static struct platform_driver this_driver = { + .probe = mipi_renesas_lcd_probe, + .driver = { + .name = "mipi_renesas", + }, +}; + +static struct msm_fb_panel_data renesas_panel_data = { + .on = mipi_renesas_lcd_on, + .off = mipi_renesas_lcd_off, + .set_backlight = mipi_renesas_set_backlight, +}; + +static int ch_used[3]; + +int mipi_renesas_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel) +{ + struct platform_device *pdev = NULL; + int ret; + if ((channel >= 3) || ch_used[channel]) + return -ENODEV; + + ch_used[channel] = TRUE; + + pdev = platform_device_alloc("mipi_renesas", (panel << 8)|channel); + if (!pdev) + return -ENOMEM; + + renesas_panel_data.panel_info = *pinfo; + + ret = platform_device_add_data(pdev, &renesas_panel_data, + sizeof(renesas_panel_data)); + if (ret) { + pr_err("%s: platform_device_add_data failed!\n", __func__); + goto err_device_put; + } + + ret = platform_device_add(pdev); + if (ret) { + pr_err("%s: platform_device_register failed!\n", __func__); + goto err_device_put; + } + + return 0; + +err_device_put: + platform_device_put(pdev); + return ret; +} + +static int __init mipi_renesas_lcd_init(void) +{ + mipi_dsi_buf_alloc(&renesas_tx_buf, DSI_BUF_SIZE); + mipi_dsi_buf_alloc(&renesas_rx_buf, DSI_BUF_SIZE); + + return platform_driver_register(&this_driver); +} + +module_init(mipi_renesas_lcd_init); diff --git a/drivers/video/msm/mipi_renesas.h b/drivers/video/msm/mipi_renesas.h new file mode 100644 index 00000000000..59ccfd04fcf --- /dev/null +++ b/drivers/video/msm/mipi_renesas.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MIPI_RENESAS_H +#define MIPI_RENESAS_H + +#define RENESAS_FWVGA_TWO_LANE + +int mipi_renesas_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#endif /* MIPI_RENESAS_H */ diff --git a/drivers/video/msm/mipi_renesas_cmd_fwvga_pt.c b/drivers/video/msm/mipi_renesas_cmd_fwvga_pt.c new file mode 100644 index 00000000000..ff573a30c61 --- /dev/null +++ b/drivers/video/msm/mipi_renesas_cmd_fwvga_pt.c @@ -0,0 +1,162 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_renesas.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_cmd_mode_phy_db = { +#ifdef CONFIG_FB_MSM_MDP303 + /* DSI Bit Clock at 500 MHz, 2 lane, RGB888 */ + /* regulator */ + {0x03, 0x01, 0x01, 0x00}, + /* timing */ + {0xb9, 0x8e, 0x1f, 0x00, 0x98, 0x9c, 0x22, 0x90, + 0x18, 0x03, 0x04}, + /* phy ctrl */ + {0x7f, 0x00, 0x00, 0x00}, + /* strength */ + {0xbb, 0x02, 0x06, 0x00}, + /* pll control */ + {0x01, 0xec, 0x31, 0xd2, 0x00, 0x40, 0x37, 0x62, + 0x01, 0x0f, 0x07, + 0x05, 0x14, 0x03, 0x0, 0x0, 0x0, 0x20, 0x0, 0x02, 0x0}, +#else + /* DSI_BIT_CLK at 400MHz, 1 lane, RGB888 */ + {0x03, 0x01, 0x01, 0x00}, /* regulator */ + /* timing */ + {0x22, 0x0c, 0x7, 0x00, 0x10, 0x20, 0x10, + 0xd, 0x8, 0x2, 0x3}, + /* phy ctrl */ + {0x7f, 0x00, 0x00, 0x00}, + /* strength */ + {0xee, 0x00, 0x6, 0x00}, + /* pll control */ + {0x40, 0x2f, 0xb1, 0xda, 0x00, 0x50, 0x48, 0x63, +#if defined(RENESAS_FWVGA_TWO_LANE) + 0x33, 0x1f, 0x07, +#else /* default set to 1 lane */ + 0x30, 0x07, 0x07, +#endif + 0x05, 0x14, 0x03, 0x0, 0x0, 0x54, 0x06, 0x10, 0x04, 0x0}, +#endif +}; + +static int __init mipi_cmd_renesas_fwvga_pt_init(void) +{ + int ret; + +#ifdef CONFIG_FB_MSM_MIPI_PANEL_DETECT + if (msm_fb_detect_client("mipi_cmd_renesas_fwvga")) + return 0; +#endif + + pinfo.xres = 480; + pinfo.yres = 864; + pinfo.type = MIPI_CMD_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; +#ifdef CONFIG_FB_MSM_MDP303 + pinfo.lcdc.h_back_porch = 100; + pinfo.lcdc.h_front_porch = 100; + pinfo.lcdc.h_pulse_width = 8; + pinfo.lcdc.v_back_porch = 20; + pinfo.lcdc.v_front_porch = 20; + pinfo.lcdc.v_pulse_width = 1; +#else + pinfo.lcdc.h_front_porch = 50; +#if defined(RENESAS_FWVGA_TWO_LANE) + pinfo.lcdc.h_back_porch = 400; + pinfo.lcdc.h_pulse_width = 5; + pinfo.lcdc.v_back_porch = 75; + pinfo.lcdc.v_front_porch = 5; + pinfo.lcdc.v_pulse_width = 1; +#else + pinfo.lcdc.h_back_porch = 50; + pinfo.lcdc.h_pulse_width = 20; + pinfo.lcdc.v_back_porch = 10; + pinfo.lcdc.v_front_porch = 10; + pinfo.lcdc.v_pulse_width = 5; +#endif + +#endif /* CONFIG_FB_MSM_MDP303 */ + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = 100; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + +#ifdef CONFIG_FB_MSM_MDP303 + pinfo.clk_rate = 499000000; +#else + pinfo.clk_rate = 152000000; +#endif + +#ifdef USE_HW_VSYNC + pinfo.lcd.vsync_enable = TRUE; + pinfo.lcd.hw_vsync_mode = TRUE; +#endif + pinfo.lcd.refx100 = 6000; /* adjust refx100 to prevent tearing */ + + pinfo.mipi.mode = DSI_CMD_MODE; + pinfo.mipi.dst_format = DSI_CMD_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + pinfo.mipi.rgb_swap = DSI_RGB_SWAP_RGB; + pinfo.mipi.data_lane0 = TRUE; +#ifdef CONFIG_FB_MSM_MDP303 + pinfo.mipi.data_lane1 = TRUE; + pinfo.mipi.t_clk_post = 0x20; + pinfo.mipi.t_clk_pre = 0x2F; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.te_sel = 0; /* TE from vsync gpio */ + pinfo.mipi.interleave_max = 1; + pinfo.mipi.insert_dcs_cmd = TRUE; + pinfo.mipi.wr_mem_continue = 0x3c; + pinfo.mipi.wr_mem_start = 0x2c; + pinfo.mipi.dsi_phy_db = &dsi_cmd_mode_phy_db; + pinfo.mipi.tx_eot_append = 0x01; + pinfo.mipi.rx_eot_ignore = 0; + pinfo.mipi.dlane_swap = 0x01; +#else +#if defined(RENESAS_FWVGA_TWO_LANE) + pinfo.mipi.data_lane1 = TRUE; +#else + pinfo.mipi.data_lane1 = FALSE; +#endif + pinfo.mipi.t_clk_post = 0x18; + pinfo.mipi.t_clk_pre = 0x14; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.te_sel = 1; /* TE from vsycn gpio */ + pinfo.mipi.interleave_max = 1; + pinfo.mipi.insert_dcs_cmd = TRUE; + pinfo.mipi.wr_mem_continue = 0x3c; + pinfo.mipi.wr_mem_start = 0x2c; + pinfo.mipi.dsi_phy_db = &dsi_cmd_mode_phy_db; +#endif /* CONFIG_FB_MSM_MDP303 */ + + ret = mipi_renesas_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_FWVGA_PT); + if (ret) + pr_err("%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_cmd_renesas_fwvga_pt_init); diff --git a/drivers/video/msm/mipi_renesas_video_fwvga_pt.c b/drivers/video/msm/mipi_renesas_video_fwvga_pt.c new file mode 100644 index 00000000000..0e490118ac9 --- /dev/null +++ b/drivers/video/msm/mipi_renesas_video_fwvga_pt.c @@ -0,0 +1,165 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_renesas.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { +#ifdef CONFIG_FB_MSM_MDP303 + /* DSI Bit Clock at 500 MHz, 2 lane, RGB888 */ + /* regulator */ + {0x03, 0x01, 0x01, 0x00}, + /* timing */ + {0xb9, 0x8e, 0x1f, 0x00, 0x98, 0x9c, 0x22, 0x90, + 0x18, 0x03, 0x04}, + /* phy ctrl */ + {0x7f, 0x00, 0x00, 0x00}, + /* strength */ + {0xbb, 0x02, 0x06, 0x00}, + /* pll control */ + {0x00, 0xec, 0x31, 0xd2, 0x00, 0x40, 0x37, 0x62, + 0x01, 0x0f, 0x07, + 0x05, 0x14, 0x03, 0x0, 0x0, 0x0, 0x20, 0x0, 0x02, 0x0}, +#else + /* DSI_BIT_CLK at 400MHz, 1 lane, RGB888 */ + /* regulator */ + {0x03, 0x01, 0x01, 0x00}, + /* timing */ + {0xaa, 0x3b, 0x1b, 0x00, 0x52, 0x58, 0x20, 0x3f, + 0x2e, 0x03, 0x04}, + /* phy ctrl */ + {0x7f, 0x00, 0x00, 0x00}, + /* strength */ + {0xee, 0x00, 0x86, 0x00}, + /* pll control */ + {0x40, 0xc7, 0xb0, 0xda, 0x00, 0x50, 0x48, 0x63, +#if defined(RENESAS_FWVGA_TWO_LANE) + 0x30, 0x07, 0x03, +#else + /* default set to 1 lane */ + 0x30, 0x07, 0x07, +#endif + 0x05, 0x14, 0x03, 0x0, 0x0, 0x54, 0x06, 0x10, 0x04, 0x0}, +#endif +}; + +static int __init mipi_video_renesas_fwvga_pt_init(void) +{ + int ret; + +#ifdef CONFIG_FB_MSM_MIPI_PANEL_DETECT + if (msm_fb_detect_client("mipi_video_renesas_fwvga")) + return 0; +#endif + + pinfo.xres = 480; + pinfo.yres = 864; + pinfo.type = MIPI_VIDEO_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; +#ifdef CONFIG_FB_MSM_MDP303 + pinfo.lcdc.h_back_porch = 100; + pinfo.lcdc.h_front_porch = 100; + pinfo.lcdc.h_pulse_width = 8; + pinfo.lcdc.v_back_porch = 20; + pinfo.lcdc.v_front_porch = 20; + pinfo.lcdc.v_pulse_width = 1; + pinfo.clk_rate = 499000000; +#else + +#if defined(RENESAS_FWVGA_TWO_LANE) + pinfo.lcdc.h_back_porch = 400; +#else + pinfo.lcdc.h_back_porch = 50; +#endif + pinfo.lcdc.h_front_porch = 50; + +#if defined(RENESAS_FWVGA_TWO_LANE) + pinfo.lcdc.h_pulse_width = 5; +#else + pinfo.lcdc.h_pulse_width = 20; +#endif + +#if defined(RENESAS_FWVGA_TWO_LANE) + pinfo.lcdc.v_back_porch = 75; + pinfo.lcdc.v_front_porch = 5; + pinfo.lcdc.v_pulse_width = 1; +#else + pinfo.lcdc.v_back_porch = 10; + pinfo.lcdc.v_front_porch = 10; + pinfo.lcdc.v_pulse_width = 5; +#endif + +#endif + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = 100; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + + pinfo.mipi.mode = DSI_VIDEO_MODE; + pinfo.mipi.pulse_mode_hsa_he = TRUE; + pinfo.mipi.hfp_power_stop = TRUE; + pinfo.mipi.hbp_power_stop = TRUE; + pinfo.mipi.hsa_power_stop = TRUE; + pinfo.mipi.eof_bllp_power_stop = TRUE; + pinfo.mipi.bllp_power_stop = TRUE; +#ifdef CONFIG_FB_MSM_MDP303 + pinfo.mipi.traffic_mode = DSI_BURST_MODE; + pinfo.mipi.dst_format = DSI_VIDEO_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + pinfo.mipi.rgb_swap = DSI_RGB_SWAP_RGB; + pinfo.mipi.data_lane0 = TRUE; + pinfo.mipi.data_lane1 = TRUE; + pinfo.mipi.t_clk_post = 0x20; + pinfo.mipi.t_clk_pre = 0x2F; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = DSI_CMD_TRIGGER_NONE; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.frame_rate = 60; + pinfo.mipi.dsi_phy_db = &dsi_video_mode_phy_db; + pinfo.mipi.dlane_swap = 0x01; + pinfo.mipi.tx_eot_append = 0x01; +#else + pinfo.mipi.traffic_mode = DSI_NON_BURST_SYNCH_PULSE; + pinfo.mipi.dst_format = DSI_VIDEO_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + pinfo.mipi.rgb_swap = DSI_RGB_SWAP_BGR; + pinfo.mipi.data_lane0 = TRUE; +#if defined(RENESAS_FWVGA_TWO_LANE) + pinfo.mipi.data_lane1 = TRUE; +#else + pinfo.mipi.data_lane1 = FALSE; +#endif + pinfo.mipi.t_clk_post = 0x03; + pinfo.mipi.t_clk_pre = 0x24; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.frame_rate = 60; + pinfo.mipi.dsi_phy_db = &dsi_video_mode_phy_db; +#endif + + ret = mipi_renesas_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_FWVGA_PT); + if (ret) + pr_err("%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_video_renesas_fwvga_pt_init); diff --git a/drivers/video/msm/mipi_simulator.c b/drivers/video/msm/mipi_simulator.c new file mode 100644 index 00000000000..da697b55502 --- /dev/null +++ b/drivers/video/msm/mipi_simulator.c @@ -0,0 +1,162 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_simulator.h" + +static struct dsi_buf simulator_tx_buf; +static struct dsi_buf simulator_rx_buf; +static struct msm_panel_common_pdata *mipi_simulator_pdata; + +static char display_on[2] = {0x00, 0x00}; +static char display_off[2] = {0x00, 0x00}; + +static struct dsi_cmd_desc display_on_cmds[] = { + {DTYPE_PERIPHERAL_ON, 1, 0, 0, 0, sizeof(display_on), + display_on} +}; +static struct dsi_cmd_desc display_off_cmds[] = { + {DTYPE_PERIPHERAL_OFF, 1, 0, 0, 0, sizeof(display_off), + display_off} +}; + +static int mipi_simulator_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct mipi_panel_info *mipi; + + mfd = platform_get_drvdata(pdev); + mipi = &mfd->panel_info.mipi; + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + pr_debug("%s:%d, debug info (mode) : %d", __func__, __LINE__, + mipi->mode); + + if (mipi->mode == DSI_VIDEO_MODE) { + mipi_dsi_cmds_tx(mfd, &simulator_tx_buf, display_on_cmds, + ARRAY_SIZE(display_on_cmds)); + } else { + pr_err("%s:%d, CMD MODE NOT SUPPORTED", __func__, __LINE__); + return -EINVAL; + } + + return 0; +} + +static int mipi_simulator_lcd_off(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct mipi_panel_info *mipi; + + mfd = platform_get_drvdata(pdev); + mipi = &mfd->panel_info.mipi; + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + pr_debug("%s:%d, debug info", __func__, __LINE__); + + if (mipi->mode == DSI_VIDEO_MODE) { + mipi_dsi_cmds_tx(mfd, &simulator_tx_buf, display_off_cmds, + ARRAY_SIZE(display_off_cmds)); + } else { + pr_debug("%s:%d, DONT REACH HERE", __func__, __LINE__); + return -EINVAL; + } + + return 0; +} + +static int __devinit mipi_simulator_lcd_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + mipi_simulator_pdata = pdev->dev.platform_data; + return 0; + } + pr_debug("%s:%d, debug info", __func__, __LINE__); + + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = mipi_simulator_lcd_probe, + .driver = { + .name = "mipi_simulator", + }, +}; + +static struct msm_fb_panel_data simulator_panel_data = { + .on = mipi_simulator_lcd_on, + .off = mipi_simulator_lcd_off, +}; + +static int ch_used[3]; + +int mipi_simulator_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel) +{ + struct platform_device *pdev = NULL; + int ret; + + if ((channel >= 3) || ch_used[channel]) + return -ENODEV; + + ch_used[channel] = TRUE; + + pr_debug("%s:%d, debug info", __func__, __LINE__); + + pdev = platform_device_alloc("mipi_simulator", (panel << 8)|channel); + if (!pdev) + return -ENOMEM; + + simulator_panel_data.panel_info = *pinfo; + + ret = platform_device_add_data(pdev, &simulator_panel_data, + sizeof(simulator_panel_data)); + if (ret) { + pr_err(KERN_ERR + "%s: platform_device_add_data failed!\n", __func__); + goto err_device_put; + } + + ret = platform_device_add(pdev); + if (ret) { + pr_err(KERN_ERR + "%s: platform_device_register failed!\n", __func__); + goto err_device_put; + } + + return 0; + +err_device_put: + platform_device_put(pdev); + return ret; +} + +static int __init mipi_simulator_lcd_init(void) +{ + mipi_dsi_buf_alloc(&simulator_tx_buf, DSI_BUF_SIZE); + mipi_dsi_buf_alloc(&simulator_rx_buf, DSI_BUF_SIZE); + + return platform_driver_register(&this_driver); +} + +module_init(mipi_simulator_lcd_init); diff --git a/drivers/video/msm/mipi_simulator.h b/drivers/video/msm/mipi_simulator.h new file mode 100644 index 00000000000..274ce8f1352 --- /dev/null +++ b/drivers/video/msm/mipi_simulator.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MIPI_SIMULATOR_H +#define MIPI_SIMULATOR_H + +int mipi_simulator_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#endif /* MIPI_SIMULATOR_H */ diff --git a/drivers/video/msm/mipi_simulator_video.c b/drivers/video/msm/mipi_simulator_video.c new file mode 100644 index 00000000000..8795554aef5 --- /dev/null +++ b/drivers/video/msm/mipi_simulator_video.c @@ -0,0 +1,88 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_simulator.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { + {0x03, 0x01, 0x01, 0x00}, + {0xaa, 0x3b, 0x1b, 0x00, 0x52, 0x58, 0x20, 0x3f, + 0x2e, 0x03, 0x04}, + {0x7f, 0x00, 0x00, 0x00}, + {0xee, 0x00, 0x86, 0x00}, + {0x40, 0xc7, 0xb0, 0xda, 0x00, 0x50, 0x48, 0x63, + 0x30, 0x07, 0x03, + 0x05, 0x14, 0x03, 0x0, 0x0, 0x54, 0x06, 0x10, 0x04, 0x0}, +}; + +static int __init mipi_video_simulator_init(void) +{ + int ret; + +#ifdef CONFIG_FB_MSM_MIPI_PANEL_DETECT + if (msm_fb_detect_client("mipi_video_simulator")) + return 0; +#endif + pinfo.xres = 640; + pinfo.yres = 480; + pinfo.type = MIPI_VIDEO_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + + pinfo.lcdc.h_back_porch = 6; + pinfo.lcdc.h_front_porch = 6; + pinfo.lcdc.h_pulse_width = 2; + pinfo.lcdc.v_back_porch = 6; + pinfo.lcdc.v_front_porch = 6; + pinfo.lcdc.v_pulse_width = 2; + + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = 15; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + + pinfo.mipi.mode = DSI_VIDEO_MODE; + pinfo.mipi.pulse_mode_hsa_he = TRUE; + pinfo.mipi.hfp_power_stop = TRUE; + pinfo.mipi.hbp_power_stop = TRUE; + pinfo.mipi.hsa_power_stop = TRUE; + pinfo.mipi.eof_bllp_power_stop = TRUE; + pinfo.mipi.bllp_power_stop = TRUE; + pinfo.mipi.traffic_mode = DSI_NON_BURST_SYNCH_PULSE; + pinfo.mipi.dst_format = DSI_VIDEO_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + pinfo.mipi.rgb_swap = DSI_RGB_SWAP_RGB; + pinfo.mipi.data_lane0 = TRUE; + pinfo.mipi.data_lane1 = TRUE; + pinfo.mipi.t_clk_post = 0x03; + pinfo.mipi.t_clk_pre = 0x24; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.frame_rate = 60; + pinfo.mipi.dsi_phy_db = &dsi_video_mode_phy_db; + + ret = mipi_simulator_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_FWVGA_PT); + if (ret) + pr_err("%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_video_simulator_init); diff --git a/drivers/video/msm/mipi_toshiba.c b/drivers/video/msm/mipi_toshiba.c new file mode 100644 index 00000000000..09fbf240b1a --- /dev/null +++ b/drivers/video/msm/mipi_toshiba.c @@ -0,0 +1,292 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_toshiba.h" + +static struct msm_panel_common_pdata *mipi_toshiba_pdata; + +static struct dsi_buf toshiba_tx_buf; +static struct dsi_buf toshiba_rx_buf; + +#ifdef TOSHIBA_CMDS_UNUSED +static char one_lane[3] = {0xEF, 0x60, 0x62}; +static char dmode_wqvga[2] = {0xB3, 0x01}; +static char intern_wr_clk1_wqvga[3] = {0xef, 0x2f, 0x22}; +static char intern_wr_clk2_wqvga[3] = {0xef, 0x6e, 0x33}; +static char hor_addr_2A_wqvga[5] = {0x2A, 0x00, 0x00, 0x00, 0xef}; +static char hor_addr_2B_wqvga[5] = {0x2B, 0x00, 0x00, 0x01, 0xaa}; +static char if_sel_cmd[2] = {0x53, 0x00}; +#endif + +static char exit_sleep[2] = {0x11, 0x00}; +static char display_on[2] = {0x29, 0x00}; +static char display_off[2] = {0x28, 0x00}; +static char enter_sleep[2] = {0x10, 0x00}; + +#ifdef CONFIG_FB_MSM_MIPI_TOSHIBA_VIDEO_WVGA_PT_PANEL +static char mcap_off[2] = {0xb2, 0x00}; +static char ena_test_reg[3] = {0xEF, 0x01, 0x01}; +static char two_lane[3] = {0xEF, 0x60, 0x63}; +static char non_burst_sync_pulse[3] = {0xef, 0x61, 0x09}; +static char dmode_wvga[2] = {0xB3, 0x00}; +static char intern_wr_clk1_wvga[3] = {0xef, 0x2f, 0xcc}; +static char intern_wr_clk2_wvga[3] = {0xef, 0x6e, 0xdd}; +static char hor_addr_2A_wvga[5] = {0x2A, 0x00, 0x00, 0x01, 0xdf}; +static char hor_addr_2B_wvga[5] = {0x2B, 0x00, 0x00, 0x03, 0x55}; +static char if_sel_video[2] = {0x53, 0x01}; + +static struct dsi_cmd_desc toshiba_display_on_cmds[] = { + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(mcap_off), mcap_off}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(ena_test_reg), ena_test_reg}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(two_lane), two_lane}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(non_burst_sync_pulse), + non_burst_sync_pulse}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(dmode_wvga), dmode_wvga}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(intern_wr_clk1_wvga), + intern_wr_clk1_wvga}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(intern_wr_clk2_wvga), + intern_wr_clk2_wvga}, + {DTYPE_DCS_LWRITE, 1, 0, 0, 0, sizeof(hor_addr_2A_wvga), + hor_addr_2A_wvga}, + {DTYPE_DCS_LWRITE, 1, 0, 0, 0, sizeof(hor_addr_2B_wvga), + hor_addr_2B_wvga}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(if_sel_video), if_sel_video}, + {DTYPE_DCS_WRITE, 1, 0, 0, 0, sizeof(exit_sleep), exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, 0, sizeof(display_on), display_on} +}; + +#endif + +#ifdef CONFIG_FB_MSM_MIPI_TOSHIBA_VIDEO_WSVGA_PT_PANEL +static char mcap_start[2] = {0xb0, 0x04}; +static char num_out_pixelform[3] = {0xb3, 0x00, 0x87}; +static char dsi_ctrl[3] = {0xb6, 0x30, 0x83}; +static char panel_driving[7] = {0xc0, 0x01, 0x00, 0x85, 0x00, 0x00, 0x00}; +static char dispV_timing[5] = {0xc1, 0x00, 0x10, 0x00, 0x01}; +static char dispCtrl[3] = {0xc3, 0x00, 0x19}; +static char test_mode_c4[2] = {0xc4, 0x03}; +static char dispH_timing[15] = { + /* TYPE_DCS_LWRITE */ + 0xc5, 0x00, 0x01, 0x05, + 0x04, 0x5e, 0x00, 0x00, + 0x00, 0x00, 0x0b, 0x17, + 0x05, 0x00, 0x00 +}; +static char test_mode_c6[2] = {0xc6, 0x00}; +static char gamma_setA[13] = { + 0xc8, 0x0a, 0x15, 0x18, + 0x1b, 0x1c, 0x0d, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00 +}; +static char gamma_setB[13] = { + 0xc9, 0x0d, 0x1d, 0x1f, + 0x1f, 0x1f, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00 +}; +static char gamma_setC[13] = { + 0xca, 0x1e, 0x1f, 0x1e, + 0x1d, 0x1d, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00 +}; +static char powerSet_ChrgPmp[5] = {0xd0, 0x02, 0x00, 0xa3, 0xb8}; +static char testMode_d1[6] = {0xd1, 0x10, 0x14, 0x53, 0x64, 0x00}; +static char powerSet_SrcAmp[3] = {0xd2, 0xb3, 0x00}; +static char powerInt_PS[3] = {0xd3, 0x33, 0x03}; +static char vreg[2] = {0xd5, 0x00}; +static char test_mode_d6[2] = {0xd6, 0x01}; +static char timingCtrl_d7[9] = { + 0xd7, 0x09, 0x00, 0x84, + 0x81, 0x61, 0xbc, 0xb5, + 0x05 +}; +static char timingCtrl_d8[7] = { + 0xd8, 0x04, 0x25, 0x90, + 0x4c, 0x92, 0x00 +}; +static char timingCtrl_d9[4] = {0xd9, 0x5b, 0x7f, 0x05}; +static char white_balance[6] = {0xcb, 0x00, 0x00, 0x00, 0x1c, 0x00}; +static char vcs_settings[2] = {0xdd, 0x53}; +static char vcom_dc_settings[2] = {0xde, 0x43}; +static char testMode_e3[5] = {0xe3, 0x00, 0x00, 0x00, 0x00}; +static char testMode_e4[6] = {0xe4, 0x00, 0x00, 0x22, 0xaa, 0x00}; +static char testMode_e5[2] = {0xe5, 0x00}; +static char testMode_fa[4] = {0xfa, 0x00, 0x00, 0x00}; +static char testMode_fd[5] = {0xfd, 0x00, 0x00, 0x00, 0x00}; +static char testMode_fe[5] = {0xfe, 0x00, 0x00, 0x00, 0x00}; +static char mcap_end[2] = {0xb0, 0x03}; +static char set_add_mode[2] = {0x36, 0x0}; +static char set_pixel_format[2] = {0x3a, 0x70}; + + +static struct dsi_cmd_desc toshiba_display_on_cmds[] = { + {DTYPE_GEN_WRITE2, 1, 0, 0, 10, sizeof(mcap_start), mcap_start}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 10, sizeof(num_out_pixelform), + num_out_pixelform}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 10, sizeof(dsi_ctrl), dsi_ctrl}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(panel_driving), panel_driving}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(dispV_timing), dispV_timing}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(dispCtrl), dispCtrl}, + {DTYPE_GEN_WRITE2, 1, 0, 0, 0, sizeof(test_mode_c4), test_mode_c4}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(dispH_timing), dispH_timing}, + {DTYPE_GEN_WRITE2, 1, 0, 0, 0, sizeof(test_mode_c6), test_mode_c6}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(gamma_setA), gamma_setA}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(gamma_setB), gamma_setB}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(gamma_setC), gamma_setC}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(powerSet_ChrgPmp), + powerSet_ChrgPmp}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(testMode_d1), testMode_d1}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(powerSet_SrcAmp), + powerSet_SrcAmp}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(powerInt_PS), powerInt_PS}, + {DTYPE_GEN_WRITE2, 1, 0, 0, 0, sizeof(vreg), vreg}, + {DTYPE_GEN_WRITE2, 1, 0, 0, 0, sizeof(test_mode_d6), test_mode_d6}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(timingCtrl_d7), timingCtrl_d7}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(timingCtrl_d8), timingCtrl_d8}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(timingCtrl_d9), timingCtrl_d9}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(white_balance), white_balance}, + {DTYPE_GEN_WRITE2, 1, 0, 0, 0, sizeof(vcs_settings), vcs_settings}, + {DTYPE_GEN_WRITE2, 1, 0, 0, 0, sizeof(vcom_dc_settings), + vcom_dc_settings}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(testMode_e3), testMode_e3}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(testMode_e4), testMode_e4}, + {DTYPE_GEN_WRITE2, 1, 0, 0, 0, sizeof(testMode_e5), testMode_e5}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(testMode_fa), testMode_fa}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(testMode_fd), testMode_fd}, + {DTYPE_GEN_LWRITE, 1, 0, 0, 0, sizeof(testMode_fe), testMode_fe}, + {DTYPE_GEN_WRITE2, 1, 0, 0, 0, sizeof(mcap_end), mcap_end}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 0, sizeof(set_add_mode), set_add_mode}, + {DTYPE_DCS_WRITE1, 1, 0, 0, 0, sizeof(set_pixel_format), + set_pixel_format}, + {DTYPE_DCS_WRITE, 1, 0, 0, 120, sizeof(exit_sleep), exit_sleep}, + {DTYPE_DCS_WRITE, 1, 0, 0, 50, sizeof(display_on), display_on} +}; +#endif + +static struct dsi_cmd_desc toshiba_display_off_cmds[] = { + {DTYPE_DCS_WRITE, 1, 0, 0, 50, sizeof(display_off), display_off}, + {DTYPE_DCS_WRITE, 1, 0, 0, 120, sizeof(enter_sleep), enter_sleep} +}; + +static int mipi_toshiba_lcd_on(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi_dsi_cmds_tx(mfd, &toshiba_tx_buf, toshiba_display_on_cmds, + ARRAY_SIZE(toshiba_display_on_cmds)); + + return 0; +} + +static int mipi_toshiba_lcd_off(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + if (mfd->key != MFD_KEY) + return -EINVAL; + + mipi_dsi_cmds_tx(mfd, &toshiba_tx_buf, toshiba_display_off_cmds, + ARRAY_SIZE(toshiba_display_off_cmds)); + + return 0; +} + +static int __devinit mipi_toshiba_lcd_probe(struct platform_device *pdev) +{ + if (pdev->id == 0) { + mipi_toshiba_pdata = pdev->dev.platform_data; + return 0; + } + + msm_fb_add_device(pdev); + + return 0; +} + +static struct platform_driver this_driver = { + .probe = mipi_toshiba_lcd_probe, + .driver = { + .name = "mipi_toshiba", + }, +}; + +static struct msm_fb_panel_data toshiba_panel_data = { + .on = mipi_toshiba_lcd_on, + .off = mipi_toshiba_lcd_off, +}; + +static int ch_used[3]; + +int mipi_toshiba_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel) +{ + struct platform_device *pdev = NULL; + int ret; + + if ((channel >= 3) || ch_used[channel]) + return -ENODEV; + + ch_used[channel] = TRUE; + + pdev = platform_device_alloc("mipi_toshiba", (panel << 8)|channel); + if (!pdev) + return -ENOMEM; + + toshiba_panel_data.panel_info = *pinfo; + + ret = platform_device_add_data(pdev, &toshiba_panel_data, + sizeof(toshiba_panel_data)); + if (ret) { + printk(KERN_ERR + "%s: platform_device_add_data failed!\n", __func__); + goto err_device_put; + } + + ret = platform_device_add(pdev); + if (ret) { + printk(KERN_ERR + "%s: platform_device_register failed!\n", __func__); + goto err_device_put; + } + + return 0; + +err_device_put: + platform_device_put(pdev); + return ret; +} + +static int __init mipi_toshiba_lcd_init(void) +{ + mipi_dsi_buf_alloc(&toshiba_tx_buf, DSI_BUF_SIZE); + mipi_dsi_buf_alloc(&toshiba_rx_buf, DSI_BUF_SIZE); + + return platform_driver_register(&this_driver); +} + +module_init(mipi_toshiba_lcd_init); diff --git a/drivers/video/msm/mipi_toshiba.h b/drivers/video/msm/mipi_toshiba.h new file mode 100644 index 00000000000..657636a7ee8 --- /dev/null +++ b/drivers/video/msm/mipi_toshiba.h @@ -0,0 +1,21 @@ + +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MIPI_TOSHIBA_H +#define MIPI_TOSHIBA_H + +int mipi_toshiba_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#endif /* MIPI_TOSHIBA_H */ diff --git a/drivers/video/msm/mipi_toshiba_video_wsvga_pt.c b/drivers/video/msm/mipi_toshiba_video_wsvga_pt.c new file mode 100644 index 00000000000..04777251941 --- /dev/null +++ b/drivers/video/msm/mipi_toshiba_video_wsvga_pt.c @@ -0,0 +1,106 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_toshiba.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { + /* 600*1024, RGB888, 3 Lane 55 fps video mode */ + /* regulator */ + {0x03, 0x0a, 0x04, 0x00, 0x20}, + /* timing */ + {0xab, 0x8a, 0x18, 0x00, 0x92, 0x97, 0x1b, 0x8c, + 0x0c, 0x03, 0x04, 0xa0}, + /* phy ctrl */ + {0x5f, 0x00, 0x00, 0x10}, + /* strength */ + {0xff, 0x00, 0x06, 0x00}, + /* pll control */ + {0x0, 0x7f, 0x1, 0x1a, 0x00, 0x50, 0x48, 0x63, + 0x41, 0x0f, 0x01, + 0x00, 0x14, 0x03, 0x00, 0x02, 0x00, 0x20, 0x00, 0x01 }, +}; + +static int __init mipi_video_toshiba_wsvga_pt_init(void) +{ + int ret; + +#ifdef CONFIG_FB_MSM_MIPI_PANEL_DETECT + if (msm_fb_detect_client("mipi_video_toshiba_wsvga")) + return 0; +#endif + + pinfo.xres = 600; + pinfo.yres = 1024; + /* + * + * Panel's Horizontal input timing requirement is to + * include dummy(pad) data of 200 clk in addition to + * width and porch/sync width values + */ + pinfo.mipi.xres_pad = 200; + pinfo.mipi.yres_pad = 0; + + pinfo.type = MIPI_VIDEO_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.lcdc.h_back_porch = 16; + pinfo.lcdc.h_front_porch = 23; + pinfo.lcdc.h_pulse_width = 8; + pinfo.lcdc.v_back_porch = 2; + pinfo.lcdc.v_front_porch = 7; + pinfo.lcdc.v_pulse_width = 2; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = 15; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + pinfo.clk_rate = 384000000; + + pinfo.mipi.mode = DSI_VIDEO_MODE; + pinfo.mipi.pulse_mode_hsa_he = FALSE; + pinfo.mipi.hfp_power_stop = FALSE; + pinfo.mipi.hbp_power_stop = FALSE; + pinfo.mipi.hsa_power_stop = FALSE; + pinfo.mipi.eof_bllp_power_stop = FALSE; + pinfo.mipi.bllp_power_stop = FALSE; + pinfo.mipi.traffic_mode = DSI_NON_BURST_SYNCH_EVENT; + pinfo.mipi.dst_format = DSI_VIDEO_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + pinfo.mipi.rgb_swap = DSI_RGB_SWAP_RGB; + pinfo.mipi.data_lane0 = TRUE; + pinfo.mipi.data_lane1 = TRUE; + pinfo.mipi.data_lane2 = TRUE; + pinfo.mipi.t_clk_post = 0x20; + pinfo.mipi.t_clk_pre = 0x2d; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = 0; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.frame_rate = 55; + pinfo.mipi.dsi_phy_db = &dsi_video_mode_phy_db; + pinfo.mipi.tx_eot_append = TRUE; + + ret = mipi_toshiba_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_WVGA_PT); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_video_toshiba_wsvga_pt_init); diff --git a/drivers/video/msm/mipi_toshiba_video_wvga_pt.c b/drivers/video/msm/mipi_toshiba_video_wvga_pt.c new file mode 100644 index 00000000000..19135134084 --- /dev/null +++ b/drivers/video/msm/mipi_toshiba_video_wvga_pt.c @@ -0,0 +1,108 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "mipi_toshiba.h" + +static struct msm_panel_info pinfo; + +static struct mipi_dsi_phy_ctrl dsi_video_mode_phy_db = { + /* 480*854, RGB888, 2 Lane 60 fps video mode */ + {0x03, 0x01, 0x01, 0x00}, /* regulator */ + /* timing */ + {0x6a, 0x22, 0x0f, 0x00, 0x30, 0x38, 0x13, 0x26, + 0x1b, 0x03, 0x04}, + {0x7f, 0x00, 0x00, 0x00}, /* phy ctrl */ + {0xee, 0x03, 0x86, 0x03}, /* strength */ + /* pll control */ + +#define DSI_BIT_CLK_380MHZ + +#if defined(DSI_BIT_CLK_366MHZ) + {0x41, 0xdb, 0xb2, 0xf5, 0x00, 0x50, 0x48, 0x63, + 0x31, 0x0f, 0x07, + 0x05, 0x14, 0x03, 0x03, 0x03, 0x54, 0x06, 0x10, 0x04, 0x03 }, +#elif defined(DSI_BIT_CLK_380MHZ) + {0x41, 0xf7, 0xb2, 0xf5, 0x00, 0x50, 0x48, 0x63, + 0x31, 0x0f, 0x07, + 0x05, 0x14, 0x03, 0x03, 0x03, 0x54, 0x06, 0x10, 0x04, 0x03 }, +#elif defined(DSI_BIT_CLK_400MHZ) + {0x41, 0x8f, 0xb1, 0xda, 0x00, 0x50, 0x48, 0x63, + 0x31, 0x0f, 0x07, + 0x05, 0x14, 0x03, 0x03, 0x03, 0x54, 0x06, 0x10, 0x04, 0x03 }, +#else /* 200 mhz */ + {0x41, 0x8f, 0xb1, 0xda, 0x00, 0x50, 0x48, 0x63, + 0x33, 0x1f, 0x0f, + 0x05, 0x14, 0x03, 0x03, 0x03, 0x54, 0x06, 0x10, 0x04, 0x03 }, +#endif +}; + +static int __init mipi_video_toshiba_wvga_pt_init(void) +{ + int ret; + +#ifdef CONFIG_FB_MSM_MIPI_PANEL_DETECT + if (msm_fb_detect_client("mipi_video_toshiba_wvga")) + return 0; +#endif + + pinfo.xres = 480; + pinfo.yres = 864; /* 856 for V1 surf */ + pinfo.type = MIPI_VIDEO_PANEL; + pinfo.pdest = DISPLAY_1; + pinfo.wait_cycle = 0; + pinfo.bpp = 24; + pinfo.lcdc.h_back_porch = 64; + pinfo.lcdc.h_front_porch = 64; + pinfo.lcdc.h_pulse_width = 16; + pinfo.lcdc.v_back_porch = 8; + pinfo.lcdc.v_front_porch = 4; + pinfo.lcdc.v_pulse_width = 1; + pinfo.lcdc.border_clr = 0; /* blk */ + pinfo.lcdc.underflow_clr = 0xff; /* blue */ + pinfo.lcdc.hsync_skew = 0; + pinfo.bl_max = 15; + pinfo.bl_min = 1; + pinfo.fb_num = 2; + + pinfo.mipi.mode = DSI_VIDEO_MODE; + pinfo.mipi.pulse_mode_hsa_he = TRUE; + pinfo.mipi.hfp_power_stop = FALSE; + pinfo.mipi.hbp_power_stop = FALSE; + pinfo.mipi.hsa_power_stop = FALSE; + pinfo.mipi.eof_bllp_power_stop = TRUE; + pinfo.mipi.bllp_power_stop = TRUE; + pinfo.mipi.traffic_mode = DSI_NON_BURST_SYNCH_PULSE; + pinfo.mipi.dst_format = DSI_VIDEO_DST_FORMAT_RGB888; + pinfo.mipi.vc = 0; + pinfo.mipi.rgb_swap = DSI_RGB_SWAP_BGR; + pinfo.mipi.data_lane0 = TRUE; + pinfo.mipi.data_lane1 = TRUE; + pinfo.mipi.t_clk_post = 0x04; + pinfo.mipi.t_clk_pre = 0x17; + pinfo.mipi.stream = 0; /* dma_p */ + pinfo.mipi.mdp_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.dma_trigger = DSI_CMD_TRIGGER_SW; + pinfo.mipi.frame_rate = 60; + pinfo.mipi.dsi_phy_db = &dsi_video_mode_phy_db; + + ret = mipi_toshiba_device_register(&pinfo, MIPI_DSI_PRIM, + MIPI_DSI_PANEL_WVGA_PT); + if (ret) + printk(KERN_ERR "%s: failed to register device!\n", __func__); + + return ret; +} + +module_init(mipi_video_toshiba_wvga_pt_init); diff --git a/drivers/video/msm/msm_dss_io_7x27a.c b/drivers/video/msm/msm_dss_io_7x27a.c new file mode 100644 index 00000000000..8e1959a4104 --- /dev/null +++ b/drivers/video/msm/msm_dss_io_7x27a.c @@ -0,0 +1,390 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include "msm_fb.h" +#include "mipi_dsi.h" + +/* multimedia sub system sfpb */ +char *mmss_sfpb_base; +void __iomem *periph_base; + +int mipi_dsi_clk_on; +static struct dsi_clk_desc dsicore_clk; +static struct dsi_clk_desc dsi_pclk; + +static struct clk *dsi_byte_div_clk; +static struct clk *dsi_esc_clk; +static struct clk *dsi_pixel_clk; +static struct clk *dsi_clk; +static struct clk *dsi_ref_clk; +static struct clk *mdp_dsi_pclk; +static struct clk *ahb_m_clk; +static struct clk *ahb_s_clk; + +void mipi_dsi_clk_init(struct device *dev) +{ + dsi_esc_clk = clk_get(NULL, "dsi_esc_clk"); + if (IS_ERR(dsi_esc_clk)) { + printk(KERN_ERR "can't find dsi_esc_clk\n"); + goto mipi_dsi_clk_err; + } + + dsi_byte_div_clk = clk_get(NULL, "dsi_byte_clk"); + if (IS_ERR(dsi_byte_div_clk)) { + pr_err("can't find dsi_byte_div_clk\n"); + goto mipi_dsi_clk_err; + } + + dsi_pixel_clk = clk_get(NULL, "dsi_pixel_clk"); + if (IS_ERR(dsi_pixel_clk)) { + pr_err("can't find dsi_pixel_clk\n"); + goto mipi_dsi_clk_err; + } + + dsi_clk = clk_get(NULL, "dsi_clk"); + if (IS_ERR(dsi_clk)) { + pr_err("can't find dsi_clk\n"); + goto mipi_dsi_clk_err; + } + + dsi_ref_clk = clk_get(NULL, "dsi_ref_clk"); + if (IS_ERR(dsi_ref_clk)) { + pr_err("can't find dsi_ref_clk\n"); + goto mipi_dsi_clk_err; + } + + mdp_dsi_pclk = clk_get(NULL, "mdp_dsi_pclk"); + if (IS_ERR(mdp_dsi_pclk)) { + pr_err("can't find mdp_dsi_pclk\n"); + goto mipi_dsi_clk_err; + } + + ahb_m_clk = clk_get(NULL, "ahb_m_clk"); + if (IS_ERR(ahb_m_clk)) { + pr_err("can't find ahb_m_clk\n"); + goto mipi_dsi_clk_err; + } + + ahb_s_clk = clk_get(NULL, "ahb_s_clk"); + if (IS_ERR(ahb_s_clk)) { + pr_err("can't find ahb_s_clk\n"); + goto mipi_dsi_clk_err; + } + + return; + +mipi_dsi_clk_err: + mipi_dsi_clk_deinit(NULL); + +} + +void mipi_dsi_clk_deinit(struct device *dev) +{ + clk_put(mdp_dsi_pclk); + clk_put(ahb_m_clk); + clk_put(ahb_s_clk); + clk_put(dsi_ref_clk); + clk_put(dsi_byte_div_clk); + clk_put(dsi_esc_clk); +} + +static void mipi_dsi_clk_ctrl(struct dsi_clk_desc *clk, int clk_en) +{ + uint32 data; + if (clk_en) { + data = (clk->pre_div_func) << 24 | + (clk->m) << 16 | (clk->n) << 8 | + ((clk->d) * 2); + clk_set_rate(dsi_clk, data); + clk_enable(dsi_clk); + } else + clk_disable(dsi_clk); +} + +static void mipi_dsi_pclk_ctrl(struct dsi_clk_desc *clk, int clk_en) +{ + uint32 data; + + if (clk_en) { + data = (clk->pre_div_func) << 24 | (clk->m) << 16 + | (clk->n) << 8 | ((clk->d) * 2); + if ((clk_set_rate(dsi_pixel_clk, data)) < 0) + pr_err("%s: pixel clk set rate failed\n", __func__); + if (clk_enable(dsi_pixel_clk)) + pr_err("%s clk enable failed\n", __func__); + } else { + clk_disable(dsi_pixel_clk); + } +} + +static void mipi_dsi_calibration(void) +{ + MIPI_OUTP(MIPI_DSI_BASE + 0xf8, 0x00a105a1); /* cal_hw_ctrl */ +} + +#define PREF_DIV_RATIO 19 +struct dsiphy_pll_divider_config pll_divider_config; + +int mipi_dsi_clk_div_config(uint8 bpp, uint8 lanes, + uint32 *expected_dsi_pclk) +{ + u32 fb_divider, rate, vco; + u32 div_ratio = 0; + struct dsi_clk_mnd_table const *mnd_entry = mnd_table; + if (pll_divider_config.clk_rate == 0) + pll_divider_config.clk_rate = 454000000; + + rate = pll_divider_config.clk_rate / 1000000; /* In Mhz */ + + if (rate < 125) { + vco = rate * 8; + div_ratio = 8; + } else if (rate < 250) { + vco = rate * 4; + div_ratio = 4; + } else if (rate < 500) { + vco = rate * 2; + div_ratio = 2; + } else { + vco = rate * 1; + div_ratio = 1; + } + + /* find the mnd settings from mnd_table entry */ + for (; mnd_entry != mnd_table + ARRAY_SIZE(mnd_table); ++mnd_entry) { + if (((mnd_entry->lanes) == lanes) && + ((mnd_entry->bpp) == bpp)) + break; + } + + if (mnd_entry == mnd_table + ARRAY_SIZE(mnd_table)) { + pr_err("%s: requested Lanes, %u & BPP, %u, not supported\n", + __func__, lanes, bpp); + return -EINVAL; + } + fb_divider = ((vco * PREF_DIV_RATIO) / 27); + pll_divider_config.fb_divider = fb_divider; + pll_divider_config.ref_divider_ratio = PREF_DIV_RATIO; + pll_divider_config.bit_clk_divider = div_ratio; + pll_divider_config.byte_clk_divider = + pll_divider_config.bit_clk_divider * 8; + pll_divider_config.dsi_clk_divider = + (mnd_entry->dsiclk_div) * div_ratio; + + if ((mnd_entry->dsiclk_d == 0) + || (mnd_entry->dsiclk_m == 1)) { + dsicore_clk.mnd_mode = 0; + dsicore_clk.src = 0x3; + dsicore_clk.pre_div_func = (mnd_entry->dsiclk_n - 1); + } else { + dsicore_clk.mnd_mode = 2; + dsicore_clk.src = 0x3; + dsicore_clk.m = mnd_entry->dsiclk_m; + dsicore_clk.n = mnd_entry->dsiclk_n; + dsicore_clk.d = mnd_entry->dsiclk_d; + } + + if ((mnd_entry->pclk_d == 0) + || (mnd_entry->pclk_m == 1)) { + dsi_pclk.mnd_mode = 0; + dsi_pclk.src = 0x3; + dsi_pclk.pre_div_func = (mnd_entry->pclk_n - 1); + *expected_dsi_pclk = ((vco * 1000000) / + ((pll_divider_config.dsi_clk_divider) + * (mnd_entry->pclk_n))); + } else { + dsi_pclk.mnd_mode = 2; + dsi_pclk.src = 0x3; + dsi_pclk.m = mnd_entry->pclk_m; + dsi_pclk.n = mnd_entry->pclk_n; + dsi_pclk.d = mnd_entry->pclk_d; + *expected_dsi_pclk = ((vco * 1000000 * dsi_pclk.m) / + ((pll_divider_config.dsi_clk_divider) + * (mnd_entry->pclk_n))); + } + dsicore_clk.m = 1; + dsicore_clk.n = 1; + dsicore_clk.d = 2; + dsicore_clk.pre_div_func = 0; + + dsi_pclk.m = 1; + dsi_pclk.n = 3; + dsi_pclk.d = 2; + dsi_pclk.pre_div_func = 0; + return 0; +} + +void mipi_dsi_phy_init(int panel_ndx, struct msm_panel_info const *panel_info, + int target_type) +{ + struct mipi_dsi_phy_ctrl *pd; + int i, off; + + MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0001);/* start phy sw reset */ + msleep(100); + MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0000);/* end phy w reset */ + MIPI_OUTP(MIPI_DSI_BASE + 0x2cc, 0x0003);/* regulator_ctrl_0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x2d0, 0x0001);/* regulator_ctrl_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x2d4, 0x0001);/* regulator_ctrl_2 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x2d8, 0x0000);/* regulator_ctrl_3 */ +#ifdef DSI_POWER + MIPI_OUTP(MIPI_DSI_BASE + 0x2dc, 0x0100);/* regulator_ctrl_4 */ +#endif + + pd = (panel_info->mipi).dsi_phy_db; + + off = 0x02cc; /* regulator ctrl 0 */ + for (i = 0; i < 4; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->regulator[i]); + wmb(); + off += 4; + } + + off = 0x0260; /* phy timig ctrl 0 */ + for (i = 0; i < 11; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->timing[i]); + wmb(); + off += 4; + } + + off = 0x0290; /* ctrl 0 */ + for (i = 0; i < 4; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->ctrl[i]); + wmb(); + off += 4; + } + + off = 0x02a0; /* strength 0 */ + for (i = 0; i < 4; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->strength[i]); + wmb(); + off += 4; + } + + mipi_dsi_calibration(); + + off = 0x0204; /* pll ctrl 1, skip 0 */ + for (i = 1; i < 21; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->pll[i]); + wmb(); + off += 4; + } + + MIPI_OUTP(MIPI_DSI_BASE + 0x100, 0x67); + + /* pll ctrl 0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x200, pd->pll[0]); + wmb(); + MIPI_OUTP(MIPI_DSI_BASE + 0x200, (pd->pll[0] | 0x01)); +} + +void mipi_dsi_clk_enable(void) +{ + unsigned data = 0; + + if (mipi_dsi_clk_on) { + pr_err("%s: mipi_dsi_clk already ON\n", __func__); + return; + } + + mipi_dsi_clk_on = 1; + + clk_enable(dsi_ref_clk); + clk_set_rate(dsi_byte_div_clk, data); + clk_set_rate(dsi_esc_clk, data); + clk_enable(mdp_dsi_pclk); + clk_enable(ahb_m_clk); + clk_enable(ahb_s_clk); + clk_enable(dsi_byte_div_clk); + clk_enable(dsi_esc_clk); + mipi_dsi_pclk_ctrl(&dsi_pclk, 1); + mipi_dsi_clk_ctrl(&dsicore_clk, 1); +} + +void mipi_dsi_clk_disable(void) +{ + if (mipi_dsi_clk_on == 0) { + pr_err("%s: mipi_dsi_clk already OFF\n", __func__); + return; + } + + mipi_dsi_clk_on = 0; + + MIPI_OUTP(MIPI_DSI_BASE + 0x0118, 0); + + mipi_dsi_pclk_ctrl(&dsi_pclk, 0); + mipi_dsi_clk_ctrl(&dsicore_clk, 0); + clk_disable(dsi_esc_clk); + clk_disable(dsi_byte_div_clk); + clk_disable(mdp_dsi_pclk); + clk_disable(ahb_m_clk); + clk_disable(ahb_s_clk); + clk_disable(dsi_ref_clk); +} + +void mipi_dsi_phy_ctrl(int on) +{ + if (on) { + /* DSIPHY_PLL_CTRL_5 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0214, 0x050); + + /* DSIPHY_TPA_CTRL_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0258, 0x00f); + + /* DSIPHY_TPA_CTRL_2 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x025c, 0x000); + } else { + /* DSIPHY_PLL_CTRL_5 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0214, 0x05f); + + /* DSIPHY_TPA_CTRL_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0258, 0x08f); + + /* DSIPHY_TPA_CTRL_2 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x025c, 0x001); + + /* DSIPHY_REGULATOR_CTRL_0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x02cc, 0x02); + + /* DSIPHY_CTRL_0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0290, 0x00); + + /* DSIPHY_CTRL_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0294, 0x7f); + + /* DSIPHY_PLL_CTRL_0, disbale dsi pll */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0200, 0x40); + + /* disbale dsi clk */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0118, 0); + } +} + +#ifdef CONFIG_FB_MSM_MDP303 +void update_lane_config(struct msm_panel_info *pinfo) +{ + struct mipi_dsi_phy_ctrl *pd; + + pd = (pinfo->mipi).dsi_phy_db; + pinfo->mipi.data_lane1 = FALSE; + pd->pll[10] |= 0x08; + + pinfo->yres = 320; + pinfo->lcdc.h_back_porch = 15; + pinfo->lcdc.h_front_porch = 21; + pinfo->lcdc.h_pulse_width = 5; + pinfo->lcdc.v_back_porch = 50; + pinfo->lcdc.v_front_porch = 101; + pinfo->lcdc.v_pulse_width = 50; +} +#endif diff --git a/drivers/video/msm/msm_dss_io_8960.c b/drivers/video/msm/msm_dss_io_8960.c new file mode 100644 index 00000000000..ce9fb287708 --- /dev/null +++ b/drivers/video/msm/msm_dss_io_8960.c @@ -0,0 +1,715 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "hdmi_msm.h" +#include + +/* HDMI PHY macros */ +#define HDMI_PHY_REG_0 (0x00000400) +#define HDMI_PHY_REG_1 (0x00000404) +#define HDMI_PHY_REG_2 (0x00000408) +#define HDMI_PHY_REG_3 (0x0000040c) +#define HDMI_PHY_REG_4 (0x00000410) +#define HDMI_PHY_REG_5 (0x00000414) +#define HDMI_PHY_REG_6 (0x00000418) +#define HDMI_PHY_REG_7 (0x0000041c) +#define HDMI_PHY_REG_8 (0x00000420) +#define HDMI_PHY_REG_9 (0x00000424) +#define HDMI_PHY_REG_10 (0x00000428) +#define HDMI_PHY_REG_11 (0x0000042c) +#define HDMI_PHY_REG_12 (0x00000430) +#define HDMI_PHY_REG_BIST_CFG (0x00000434) +#define HDMI_PHY_DEBUG_BUS_SEL (0x00000438) +#define HDMI_PHY_REG_MISC0 (0x0000043c) +#define HDMI_PHY_REG_13 (0x00000440) +#define HDMI_PHY_REG_14 (0x00000444) +#define HDMI_PHY_REG_15 (0x00000448) +#define HDMI_PHY_CTRL (0x000002D4) + +/* HDMI PHY/PLL bit field macros */ +#define HDMI_PHY_PLL_STATUS0 (0x00000598) +#define SW_RESET BIT(2) +#define SW_RESET_PLL BIT(0) +#define PWRDN_B BIT(7) + +/* multimedia sub system clock control */ +char *mmss_cc_base = MSM_MMSS_CLK_CTL_BASE; +/* multimedia sub system sfpb */ +char *mmss_sfpb_base; +void __iomem *periph_base; + +int mipi_dsi_clk_on; +static struct dsi_clk_desc dsicore_clk; +static struct dsi_clk_desc dsi_pclk; + +static struct clk *dsi_byte_div_clk; +static struct clk *dsi_esc_clk; +static struct clk *dsi_m_pclk; +static struct clk *dsi_s_pclk; + +static struct clk *amp_pclk; + +void mipi_dsi_clk_init(struct device *dev) +{ + amp_pclk = clk_get(NULL, "amp_pclk"); + if (IS_ERR(amp_pclk)) { + pr_err("can't find amp_pclk\n"); + goto mipi_dsi_clk_err; + } + + dsi_m_pclk = clk_get(dev, "dsi_m_pclk"); + if (IS_ERR(dsi_m_pclk)) { + pr_err("can't find dsi_m_pclk\n"); + goto mipi_dsi_clk_err; + } + + dsi_s_pclk = clk_get(dev, "dsi_s_pclk"); + if (IS_ERR(dsi_s_pclk)) { + pr_err("can't find dsi_s_pclk\n"); + goto mipi_dsi_clk_err; + } + + dsi_byte_div_clk = clk_get(dev, "dsi_byte_div_clk"); + if (IS_ERR(dsi_byte_div_clk)) { + pr_err("can't find dsi_byte_div_clk\n"); + goto mipi_dsi_clk_err; + } + + dsi_esc_clk = clk_get(dev, "dsi_esc_clk"); + if (IS_ERR(dsi_esc_clk)) { + printk(KERN_ERR "can't find dsi_esc_clk\n"); + goto mipi_dsi_clk_err; + } + + return; + +mipi_dsi_clk_err: + mipi_dsi_clk_deinit(NULL); +} + +void mipi_dsi_clk_deinit(struct device *dev) +{ + clk_put(amp_pclk); + clk_put(dsi_m_pclk); + clk_put(dsi_s_pclk); + clk_put(dsi_byte_div_clk); + clk_put(dsi_esc_clk); +} + +static void mipi_dsi_clk_ctrl(struct dsi_clk_desc *clk, int clk_en) +{ + char *cc, *ns, *md; + int pmxo_sel = 0; + char mnd_en = 1, root_en = 1; + uint32 data, val; + + cc = mmss_cc_base + 0x004c; + md = mmss_cc_base + 0x0050; + ns = mmss_cc_base + 0x0054; + + if (clk_en) { + if (clk->mnd_mode == 0) { + data = clk->pre_div_func << 14; + data |= clk->src; + MIPI_OUTP_SECURE(ns, data); + MIPI_OUTP_SECURE(cc, ((pmxo_sel << 8) + | (clk->mnd_mode << 6) + | (root_en << 2) | clk_en)); + } else { + val = clk->d * 2; + data = (~val) & 0x0ff; + data |= clk->m << 8; + MIPI_OUTP_SECURE(md, data); + + val = clk->n - clk->m; + data = (~val) & 0x0ff; + data <<= 24; + data |= clk->src; + MIPI_OUTP_SECURE(ns, data); + + MIPI_OUTP_SECURE(cc, ((pmxo_sel << 8) + | (clk->mnd_mode << 6) + | (mnd_en << 5) + | (root_en << 2) | clk_en)); + } + } else + MIPI_OUTP_SECURE(cc, 0); + + wmb(); +} + +static void mipi_dsi_sfpb_cfg(void) +{ + char *sfpb; + int data; + + sfpb = mmss_sfpb_base + 0x058; + + data = MIPI_INP(sfpb); + data |= 0x01800; + MIPI_OUTP(sfpb, data); + wmb(); +} + +static void mipi_dsi_pclk_ctrl(struct dsi_clk_desc *clk, int clk_en) +{ + char *cc, *ns, *md; + char mnd_en = 1, root_en = 1; + uint32 data, val; + + cc = mmss_cc_base + 0x0130; + md = mmss_cc_base + 0x0134; + ns = mmss_cc_base + 0x0138; + + if (clk_en) { + if (clk->mnd_mode == 0) { + data = clk->pre_div_func << 12; + data |= clk->src; + MIPI_OUTP_SECURE(ns, data); + MIPI_OUTP_SECURE(cc, ((clk->mnd_mode << 6) + | (root_en << 2) | clk_en)); + } else { + val = clk->d * 2; + data = (~val) & 0x0ff; + data |= clk->m << 8; + MIPI_OUTP_SECURE(md, data); + + val = clk->n - clk->m; + data = (~val) & 0x0ff; + data <<= 24; + data |= clk->src; + MIPI_OUTP_SECURE(ns, data); + + MIPI_OUTP_SECURE(cc, ((clk->mnd_mode << 6) + | (mnd_en << 5) + | (root_en << 2) | clk_en)); + } + } else + MIPI_OUTP_SECURE(cc, 0); + + wmb(); +} + +static void mipi_dsi_ahb_en(void) +{ + char *ahb; + + ahb = mmss_cc_base + 0x08; + + pr_debug("%s: ahb=%x %x\n", + __func__, (int) ahb, MIPI_INP_SECURE(ahb)); +} + +static void mipi_dsi_calibration(void) +{ + int i = 0; + uint32 term_cnt = 5000; + int cal_busy = MIPI_INP(MIPI_DSI_BASE + 0x550); + + /* DSI1_DSIPHY_REGULATOR_CAL_PWR_CFG */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0518, 0x01); + + /* DSI1_DSIPHY_CAL_SW_CFG2 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0534, 0x0); + /* DSI1_DSIPHY_CAL_HW_CFG1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x053c, 0x5a); + /* DSI1_DSIPHY_CAL_HW_CFG3 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0544, 0x10); + /* DSI1_DSIPHY_CAL_HW_CFG4 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0548, 0x01); + /* DSI1_DSIPHY_CAL_HW_CFG0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0538, 0x01); + + /* DSI1_DSIPHY_CAL_HW_TRIGGER */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0528, 0x01); + usleep_range(5000, 5000); + /* DSI1_DSIPHY_CAL_HW_TRIGGER */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0528, 0x00); + + cal_busy = MIPI_INP(MIPI_DSI_BASE + 0x550); + while (cal_busy & 0x10) { + i++; + if (i > term_cnt) { + pr_err("DSI1 PHY REGULATOR NOT READY," + "exceeded polling TIMEOUT!\n"); + break; + } + cal_busy = MIPI_INP(MIPI_DSI_BASE + 0x550); + } +} + +void mipi_dsi_phy_rdy_poll(void) +{ + uint32 phy_pll_busy; + uint32 i = 0; + uint32 term_cnt = 0xFFFFFF; + + phy_pll_busy = MIPI_INP(MIPI_DSI_BASE + 0x280); + while (!(phy_pll_busy & 0x1)) { + i++; + if (i > term_cnt) { + pr_err("DSI1 PHY NOT READY, exceeded polling TIMEOUT!\n"); + break; + } + phy_pll_busy = MIPI_INP(MIPI_DSI_BASE + 0x280); + } +} + +#define PREF_DIV_RATIO 27 +struct dsiphy_pll_divider_config pll_divider_config; + +int mipi_dsi_phy_pll_config(u32 clk_rate) +{ + struct dsiphy_pll_divider_config *dividers; + u32 fb_divider, tmp; + dividers = &pll_divider_config; + + /* DSIPHY_PLL_CTRL_x: 1 2 3 8 9 10 */ + /* masks 0xff 0x07 0x3f 0x0f 0xff 0xff */ + + /* DSIPHY_PLL_CTRL_1 */ + fb_divider = ((dividers->fb_divider) / 2) - 1; + MIPI_OUTP(MIPI_DSI_BASE + 0x204, fb_divider & 0xff); + + /* DSIPHY_PLL_CTRL_2 */ + tmp = MIPI_INP(MIPI_DSI_BASE + 0x208); + tmp &= ~0x07; + tmp |= (fb_divider >> 8) & 0x07; + MIPI_OUTP(MIPI_DSI_BASE + 0x208, tmp); + + /* DSIPHY_PLL_CTRL_3 */ + tmp = MIPI_INP(MIPI_DSI_BASE + 0x20c); + tmp &= ~0x3f; + tmp |= (dividers->ref_divider_ratio - 1) & 0x3f; + MIPI_OUTP(MIPI_DSI_BASE + 0x20c, tmp); + + /* DSIPHY_PLL_CTRL_8 */ + tmp = MIPI_INP(MIPI_DSI_BASE + 0x220); + tmp &= ~0x0f; + tmp |= (dividers->bit_clk_divider - 1) & 0x0f; + MIPI_OUTP(MIPI_DSI_BASE + 0x220, tmp); + + /* DSIPHY_PLL_CTRL_9 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x224, (dividers->byte_clk_divider - 1)); + + /* DSIPHY_PLL_CTRL_10 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x228, (dividers->dsi_clk_divider - 1)); + + return 0; +} + +int mipi_dsi_clk_div_config(uint8 bpp, uint8 lanes, + uint32 *expected_dsi_pclk) +{ + u32 fb_divider, rate, vco; + u32 div_ratio = 0; + struct dsi_clk_mnd_table const *mnd_entry = mnd_table; + if (pll_divider_config.clk_rate == 0) + pll_divider_config.clk_rate = 454000000; + + rate = pll_divider_config.clk_rate / 1000000; /* In Mhz */ + + if (rate < 125) { + vco = rate * 8; + div_ratio = 8; + } else if (rate < 250) { + vco = rate * 4; + div_ratio = 4; + } else if (rate < 500) { + vco = rate * 2; + div_ratio = 2; + } else { + vco = rate * 1; + div_ratio = 1; + } + + /* find the mnd settings from mnd_table entry */ + for (; mnd_entry != mnd_table + ARRAY_SIZE(mnd_table); ++mnd_entry) { + if (((mnd_entry->lanes) == lanes) && + ((mnd_entry->bpp) == bpp)) + break; + } + + if (mnd_entry == mnd_table + ARRAY_SIZE(mnd_table)) { + pr_err("%s: requested Lanes, %u & BPP, %u, not supported\n", + __func__, lanes, bpp); + return -EINVAL; + } + fb_divider = ((vco * PREF_DIV_RATIO) / 27); + pll_divider_config.fb_divider = fb_divider; + pll_divider_config.ref_divider_ratio = PREF_DIV_RATIO; + pll_divider_config.bit_clk_divider = div_ratio; + pll_divider_config.byte_clk_divider = + pll_divider_config.bit_clk_divider * 8; + pll_divider_config.dsi_clk_divider = + (mnd_entry->dsiclk_div) * div_ratio; + + if (mnd_entry->dsiclk_d == 0) { + dsicore_clk.mnd_mode = 0; + dsicore_clk.src = 0x3; + dsicore_clk.pre_div_func = (mnd_entry->dsiclk_n - 1); + } else { + dsicore_clk.mnd_mode = 2; + dsicore_clk.src = 0x3; + dsicore_clk.m = mnd_entry->dsiclk_m; + dsicore_clk.n = mnd_entry->dsiclk_n; + dsicore_clk.d = mnd_entry->dsiclk_d; + } + + if ((mnd_entry->pclk_d == 0) + || (mnd_entry->pclk_m == 1)) { + dsi_pclk.mnd_mode = 0; + dsi_pclk.src = 0x3; + dsi_pclk.pre_div_func = (mnd_entry->pclk_n - 1); + *expected_dsi_pclk = ((vco * 1000000) / + ((pll_divider_config.dsi_clk_divider) + * (mnd_entry->pclk_n))); + } else { + dsi_pclk.mnd_mode = 2; + dsi_pclk.src = 0x3; + dsi_pclk.m = mnd_entry->pclk_m; + dsi_pclk.n = mnd_entry->pclk_n; + dsi_pclk.d = mnd_entry->pclk_d; + *expected_dsi_pclk = ((vco * 1000000 * dsi_pclk.m) / + ((pll_divider_config.dsi_clk_divider) + * (mnd_entry->pclk_n))); + } + return 0; +} + +static void mipi_dsi_configure_serdes(void) +{ + void __iomem *cc; + + /* PHY registers programemd thru S2P interface */ + if (periph_base) { + MIPI_OUTP(periph_base + 0x2c, 0x000000b6); + MIPI_OUTP(periph_base + 0x2c, 0x000001b5); + MIPI_OUTP(periph_base + 0x2c, 0x000001b4); + MIPI_OUTP(periph_base + 0x2c, 0x000003b3); + MIPI_OUTP(periph_base + 0x2c, 0x000003a2); + MIPI_OUTP(periph_base + 0x2c, 0x000002a1); + MIPI_OUTP(periph_base + 0x2c, 0x000008a0); + MIPI_OUTP(periph_base + 0x2c, 0x00000d9f); + MIPI_OUTP(periph_base + 0x2c, 0x0000109e); + MIPI_OUTP(periph_base + 0x2c, 0x0000209d); + MIPI_OUTP(periph_base + 0x2c, 0x0000109c); + MIPI_OUTP(periph_base + 0x2c, 0x0000079a); + MIPI_OUTP(periph_base + 0x2c, 0x00000c99); + MIPI_OUTP(periph_base + 0x2c, 0x00002298); + MIPI_OUTP(periph_base + 0x2c, 0x000000a7); + MIPI_OUTP(periph_base + 0x2c, 0x000000a6); + MIPI_OUTP(periph_base + 0x2c, 0x000000a5); + MIPI_OUTP(periph_base + 0x2c, 0x00007fa4); + MIPI_OUTP(periph_base + 0x2c, 0x0000eea8); + MIPI_OUTP(periph_base + 0x2c, 0x000006aa); + MIPI_OUTP(periph_base + 0x2c, 0x00002095); + MIPI_OUTP(periph_base + 0x2c, 0x00000493); + MIPI_OUTP(periph_base + 0x2c, 0x00001092); + MIPI_OUTP(periph_base + 0x2c, 0x00000691); + MIPI_OUTP(periph_base + 0x2c, 0x00005490); + MIPI_OUTP(periph_base + 0x2c, 0x0000038d); + MIPI_OUTP(periph_base + 0x2c, 0x0000148c); + MIPI_OUTP(periph_base + 0x2c, 0x0000058b); + MIPI_OUTP(periph_base + 0x2c, 0x0000078a); + MIPI_OUTP(periph_base + 0x2c, 0x00001f89); + MIPI_OUTP(periph_base + 0x2c, 0x00003388); + MIPI_OUTP(periph_base + 0x2c, 0x00006387); + MIPI_OUTP(periph_base + 0x2c, 0x00004886); + MIPI_OUTP(periph_base + 0x2c, 0x00005085); + MIPI_OUTP(periph_base + 0x2c, 0x00000084); + MIPI_OUTP(periph_base + 0x2c, 0x0000da83); + MIPI_OUTP(periph_base + 0x2c, 0x0000b182); + MIPI_OUTP(periph_base + 0x2c, 0x00002f81); + MIPI_OUTP(periph_base + 0x2c, 0x00004080); + MIPI_OUTP(periph_base + 0x2c, 0x00004180); + MIPI_OUTP(periph_base + 0x2c, 0x000006aa); + } + + cc = MIPI_DSI_BASE + 0x0130; + MIPI_OUTP(cc, 0x806c11c8); + MIPI_OUTP(cc, 0x804c11c8); + MIPI_OUTP(cc, 0x806d0080); + MIPI_OUTP(cc, 0x804d0080); + MIPI_OUTP(cc, 0x00000000); + MIPI_OUTP(cc, 0x807b1597); + MIPI_OUTP(cc, 0x805b1597); + MIPI_OUTP(cc, 0x807c0080); + MIPI_OUTP(cc, 0x805c0080); + MIPI_OUTP(cc, 0x00000000); + MIPI_OUTP(cc, 0x807911c8); + MIPI_OUTP(cc, 0x805911c8); + MIPI_OUTP(cc, 0x807a0080); + MIPI_OUTP(cc, 0x805a0080); + MIPI_OUTP(cc, 0x00000000); + MIPI_OUTP(cc, 0x80721555); + MIPI_OUTP(cc, 0x80521555); + MIPI_OUTP(cc, 0x80730000); + MIPI_OUTP(cc, 0x80530000); + MIPI_OUTP(cc, 0x00000000); +} + +void mipi_dsi_phy_init(int panel_ndx, struct msm_panel_info const *panel_info, + int target_type) +{ + struct mipi_dsi_phy_ctrl *pd; + int i, off; + + MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0001);/* start phy sw reset */ + msleep(100); + MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0000);/* end phy w reset */ + MIPI_OUTP(MIPI_DSI_BASE + 0x500, 0x0003);/* regulator_ctrl_0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x504, 0x0001);/* regulator_ctrl_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x508, 0x0001);/* regulator_ctrl_2 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x50c, 0x0000);/* regulator_ctrl_3 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x510, 0x0100);/* regulator_ctrl_4 */ + + pd = (panel_info->mipi).dsi_phy_db; + + off = 0x0480; /* strength 0 - 2 */ + for (i = 0; i < 3; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->strength[i]); + wmb(); + off += 4; + } + + off = 0x0470; /* ctrl 0 - 3 */ + for (i = 0; i < 4; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->ctrl[i]); + wmb(); + off += 4; + } + + off = 0x0500; /* regulator ctrl 0 - 4 */ + for (i = 0; i < 5; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->regulator[i]); + wmb(); + off += 4; + } + mipi_dsi_calibration(); + + off = 0x0204; /* pll ctrl 1 - 19, skip 0 */ + for (i = 1; i < 20; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->pll[i]); + wmb(); + off += 4; + } + + if (panel_info) + mipi_dsi_phy_pll_config(panel_info->clk_rate); + + /* pll ctrl 0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x200, pd->pll[0]); + wmb(); + MIPI_OUTP(MIPI_DSI_BASE + 0x200, (pd->pll[0] | 0x01)); + + mipi_dsi_phy_rdy_poll(); + + off = 0x0440; /* phy timig ctrl 0 - 11 */ + for (i = 0; i < 12; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->timing[i]); + wmb(); + off += 4; + } + + if (target_type == 1) + mipi_dsi_configure_serdes(); +} + +void mipi_dsi_clk_enable(void) +{ + if (mipi_dsi_clk_on) { + pr_err("%s: mipi_dsi_clk already ON\n", __func__); + return; + } + + mipi_dsi_clk_on = 1; + + clk_enable(amp_pclk); /* clock for AHB-master to AXI */ + clk_enable(dsi_m_pclk); + clk_enable(dsi_s_pclk); + if (clk_set_rate(dsi_byte_div_clk, 1) < 0) /* divided by 1 */ + pr_err("%s: dsi_byte_div_clk - " + "clk_set_rate failed\n", __func__); + if (clk_set_rate(dsi_esc_clk, 2) < 0) /* divided by 2 */ + pr_err("%s: dsi_esc_clk - " + "clk_set_rate failed\n", __func__); + clk_enable(dsi_byte_div_clk); + clk_enable(dsi_esc_clk); + mipi_dsi_pclk_ctrl(&dsi_pclk, 1); + mipi_dsi_clk_ctrl(&dsicore_clk, 1); + mipi_dsi_ahb_en(); + mipi_dsi_sfpb_cfg(); +} + +void mipi_dsi_clk_disable(void) +{ + if (mipi_dsi_clk_on == 0) { + pr_err("%s: mipi_dsi_clk already OFF\n", __func__); + return; + } + + mipi_dsi_clk_on = 0; + + MIPI_OUTP(MIPI_DSI_BASE + 0x0118, 0); + + mipi_dsi_pclk_ctrl(&dsi_pclk, 0); + mipi_dsi_clk_ctrl(&dsicore_clk, 0); + clk_disable(dsi_esc_clk); + clk_disable(dsi_byte_div_clk); + clk_disable(dsi_m_pclk); + clk_disable(dsi_s_pclk); + clk_disable(amp_pclk); /* clock for AHB-master to AXI */ +} + +void mipi_dsi_phy_ctrl(int on) +{ + if (on) { + /* DSIPHY_PLL_CTRL_5 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0214, 0x050); + } else { + /* DSIPHY_PLL_CTRL_5 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0214, 0x05f); + + /* DSIPHY_REGULATOR_CTRL_0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0500, 0x02); + + /* DSIPHY_CTRL_0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0470, 0x00); + + /* DSIPHY_CTRL_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0474, 0x7f); + + /* DSIPHY_PLL_CTRL_0, disbale dsi pll */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0200, 0x40); + + /* disbale dsi clk */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0118, 0); + } +} + +#ifdef CONFIG_FB_MSM_HDMI_COMMON +void hdmi_phy_reset(void) +{ + unsigned int phy_reset_polarity = 0x0; + unsigned int pll_reset_polarity = 0x0; + + unsigned int val = HDMI_INP_ND(HDMI_PHY_CTRL); + + phy_reset_polarity = val >> 3 & 0x1; + pll_reset_polarity = val >> 1 & 0x1; + + if (phy_reset_polarity == 0) + HDMI_OUTP(HDMI_PHY_CTRL, val | SW_RESET); + else + HDMI_OUTP(HDMI_PHY_CTRL, val & (~SW_RESET)); + + if (pll_reset_polarity == 0) + HDMI_OUTP(HDMI_PHY_CTRL, val | SW_RESET_PLL); + else + HDMI_OUTP(HDMI_PHY_CTRL, val & (~SW_RESET_PLL)); + + msleep(100); + + if (phy_reset_polarity == 0) + HDMI_OUTP(HDMI_PHY_CTRL, val & (~SW_RESET)); + else + HDMI_OUTP(HDMI_PHY_CTRL, val | SW_RESET); + + if (pll_reset_polarity == 0) + HDMI_OUTP(HDMI_PHY_CTRL, val & (~SW_RESET_PLL)); + else + HDMI_OUTP(HDMI_PHY_CTRL, val | SW_RESET_PLL); +} + +void hdmi_msm_init_phy(int video_format) +{ + uint32 offset; + pr_err("Video format is : %u\n", video_format); + + HDMI_OUTP(HDMI_PHY_REG_0, 0x1B); + HDMI_OUTP(HDMI_PHY_REG_1, 0xf2); + HDMI_OUTP(HDMI_PHY_REG_2, 0x7F); + HDMI_OUTP(HDMI_PHY_REG_2, 0x3F); + HDMI_OUTP(HDMI_PHY_REG_2, 0x1F); + + offset = HDMI_PHY_REG_4; + while (offset <= HDMI_PHY_REG_11) { + HDMI_OUTP(offset, 0x0); + offset += 0x4; + } + + HDMI_OUTP(HDMI_PHY_REG_12, HDMI_INP(HDMI_PHY_REG_12) | PWRDN_B); + msleep(100); + + HDMI_OUTP(HDMI_PHY_REG_3, 0x20); + HDMI_OUTP(HDMI_PHY_REG_12, 0x81); + HDMI_OUTP(HDMI_PHY_REG_2, 0x81); +} + +void hdmi_msm_powerdown_phy(void) +{ + /* Power down PHY */ + HDMI_OUTP_ND(HDMI_PHY_REG_2, 0x7F); /*0b01111111*/ +} + +void hdmi_frame_ctrl_cfg(const struct hdmi_disp_mode_timing_type *timing) +{ + /* 0x02C8 HDMI_FRAME_CTRL + * 31 INTERLACED_EN Interlaced or progressive enable bit + * 0: Frame in progressive + * 1: Frame is interlaced + * 29 HSYNC_HDMI_POL HSYNC polarity fed to HDMI core + * 0: Active Hi Hsync, detect the rising edge of hsync + * 1: Active lo Hsync, Detect the falling edge of Hsync + * 28 VSYNC_HDMI_POL VSYNC polarity fed to HDMI core + * 0: Active Hi Vsync, detect the rising edge of vsync + * 1: Active Lo Vsync, Detect the falling edge of Vsync + * 12 RGB_MUX_SEL ALPHA mdp4 input is RGB, mdp4 input is BGR + */ + HDMI_OUTP(0x02C8, + ((timing->interlaced << 31) & 0x80000000) + | ((timing->active_low_h << 29) & 0x20000000) + | ((timing->active_low_v << 28) & 0x10000000)); +} + +void hdmi_msm_phy_status_poll(void) +{ + unsigned int lock_det, phy_ready; + lock_det = 0x1 & HDMI_INP_ND(HDMI_PHY_PLL_STATUS0); + if (lock_det) { + pr_debug("HDMI Phy PLL Lock Detect Bit is set\n"); + } else { + pr_debug("HDMI Phy Lock Detect Bit is not set," + "waiting for lock detection\n"); + do { + lock_det = 0x1 & \ + HDMI_INP_ND(HDMI_PHY_PLL_STATUS0); + } while (!lock_det); + } + + phy_ready = 0x1 & HDMI_INP_ND(HDMI_PHY_REG_15); + if (phy_ready) { + pr_debug("HDMI Phy Status bit is set and ready\n"); + } else { + pr_debug("HDMI Phy Status bit is not set," + "waiting for ready status\n"); + do { + phy_ready = 0x1 & HDMI_INP_ND(HDMI_PHY_REG_15); + } while (!phy_ready); + } +} + +#endif diff --git a/drivers/video/msm/msm_dss_io_8x60.c b/drivers/video/msm/msm_dss_io_8x60.c new file mode 100644 index 00000000000..e38170fdd6e --- /dev/null +++ b/drivers/video/msm/msm_dss_io_8x60.c @@ -0,0 +1,613 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include "msm_fb.h" +#include "mipi_dsi.h" +#include "hdmi_msm.h" +#include + +/* multimedia sub system clock control */ +char *mmss_cc_base = MSM_MMSS_CLK_CTL_BASE; +/* multimedia sub system sfpb */ +char *mmss_sfpb_base; +void __iomem *periph_base; + +int mipi_dsi_clk_on; +static struct dsi_clk_desc dsicore_clk; +static struct dsi_clk_desc dsi_pclk; + +static struct clk *dsi_byte_div_clk; +static struct clk *dsi_esc_clk; +static struct clk *dsi_m_pclk; +static struct clk *dsi_s_pclk; + +static struct clk *amp_pclk; + +void mipi_dsi_clk_init(struct device *dev) +{ + amp_pclk = clk_get(NULL, "amp_pclk"); + if (IS_ERR(amp_pclk)) { + pr_err("can't find amp_pclk\n"); + goto mipi_dsi_clk_err; + } + + dsi_m_pclk = clk_get(NULL, "dsi_m_pclk"); + if (IS_ERR(dsi_m_pclk)) { + pr_err("can't find dsi_m_pclk\n"); + goto mipi_dsi_clk_err; + } + + dsi_s_pclk = clk_get(NULL, "dsi_s_pclk"); + if (IS_ERR(dsi_s_pclk)) { + pr_err("can't find dsi_s_pclk\n"); + goto mipi_dsi_clk_err; + } + + dsi_byte_div_clk = clk_get(NULL, "dsi_byte_div_clk"); + if (IS_ERR(dsi_byte_div_clk)) { + pr_err("can't find dsi_byte_div_clk\n"); + goto mipi_dsi_clk_err; + } + + dsi_esc_clk = clk_get(NULL, "dsi_esc_clk"); + if (IS_ERR(dsi_esc_clk)) { + printk(KERN_ERR "can't find dsi_esc_clk\n"); + goto mipi_dsi_clk_err; + } + + return; + +mipi_dsi_clk_err: + mipi_dsi_clk_deinit(NULL); +} + +void mipi_dsi_clk_deinit(struct device *dev) +{ + clk_put(amp_pclk); + clk_put(dsi_m_pclk); + clk_put(dsi_s_pclk); + clk_put(dsi_byte_div_clk); + clk_put(dsi_esc_clk); +} + +static void mipi_dsi_clk_ctrl(struct dsi_clk_desc *clk, int clk_en) +{ + char *cc, *ns, *md; + int pmxo_sel = 0; + char mnd_en = 1, root_en = 1; + uint32 data, val; + + cc = mmss_cc_base + 0x004c; + md = mmss_cc_base + 0x0050; + ns = mmss_cc_base + 0x0054; + + if (clk_en) { + if (clk->mnd_mode == 0) { + data = clk->pre_div_func << 14; + data |= clk->src; + MIPI_OUTP_SECURE(ns, data); + MIPI_OUTP_SECURE(cc, ((pmxo_sel << 8) + | (clk->mnd_mode << 6) + | (root_en << 2) | clk_en)); + } else { + val = clk->d * 2; + data = (~val) & 0x0ff; + data |= clk->m << 8; + MIPI_OUTP_SECURE(md, data); + + val = clk->n - clk->m; + data = (~val) & 0x0ff; + data <<= 24; + data |= clk->src; + MIPI_OUTP_SECURE(ns, data); + + MIPI_OUTP_SECURE(cc, ((pmxo_sel << 8) + | (clk->mnd_mode << 6) + | (mnd_en << 5) + | (root_en << 2) | clk_en)); + } + + } else + MIPI_OUTP_SECURE(cc, 0); + + wmb(); +} + +static void mipi_dsi_sfpb_cfg(void) +{ + char *sfpb; + int data; + + sfpb = mmss_sfpb_base + 0x058; + + data = MIPI_INP(sfpb); + data |= 0x01800; + MIPI_OUTP(sfpb, data); + wmb(); +} + +static void mipi_dsi_pclk_ctrl(struct dsi_clk_desc *clk, int clk_en) +{ + char *cc, *ns, *md; + char mnd_en = 1, root_en = 1; + uint32 data, val; + + cc = mmss_cc_base + 0x0130; + md = mmss_cc_base + 0x0134; + ns = mmss_cc_base + 0x0138; + + if (clk_en) { + if (clk->mnd_mode == 0) { + data = clk->pre_div_func << 12; + data |= clk->src; + MIPI_OUTP_SECURE(ns, data); + MIPI_OUTP_SECURE(cc, ((clk->mnd_mode << 6) + | (root_en << 2) | clk_en)); + } else { + val = clk->d * 2; + data = (~val) & 0x0ff; + data |= clk->m << 8; + MIPI_OUTP_SECURE(md, data); + + val = clk->n - clk->m; + data = (~val) & 0x0ff; + data <<= 24; + data |= clk->src; + MIPI_OUTP_SECURE(ns, data); + + MIPI_OUTP_SECURE(cc, ((clk->mnd_mode << 6) + | (mnd_en << 5) + | (root_en << 2) | clk_en)); + } + + } else + MIPI_OUTP_SECURE(cc, 0); + + wmb(); +} + +static void mipi_dsi_ahb_en(void) +{ + char *ahb; + + ahb = mmss_cc_base + 0x08; + + pr_debug("%s: ahb=%x %x\n", + __func__, (int) ahb, MIPI_INP_SECURE(ahb)); +} + +static void mipi_dsi_calibration(void) +{ + uint32 data; + + MIPI_OUTP(MIPI_DSI_BASE + 0xf4, 0x0000ff11); /* cal_ctrl */ + MIPI_OUTP(MIPI_DSI_BASE + 0xf0, 0x01); /* cal_hw_trigger */ + + while (1) { + data = MIPI_INP(MIPI_DSI_BASE + 0xfc); /* cal_status */ + if ((data & 0x10000000) == 0) + break; + + udelay(10); + } +} + +#define PREF_DIV_RATIO 27 +struct dsiphy_pll_divider_config pll_divider_config; + + +int mipi_dsi_phy_pll_config(u32 clk_rate) +{ + struct dsiphy_pll_divider_config *dividers; + u32 fb_divider, tmp; + dividers = &pll_divider_config; + + /* DSIPHY_PLL_CTRL_x: 1 2 3 8 9 10 */ + /* masks 0xff 0x07 0x3f 0x0f 0xff 0xff */ + + /* DSIPHY_PLL_CTRL_1 */ + fb_divider = ((dividers->fb_divider) / 2) - 1; + MIPI_OUTP(MIPI_DSI_BASE + 0x204, fb_divider & 0xff); + + /* DSIPHY_PLL_CTRL_2 */ + tmp = MIPI_INP(MIPI_DSI_BASE + 0x208); + tmp &= ~0x07; + tmp |= (fb_divider >> 8) & 0x07; + MIPI_OUTP(MIPI_DSI_BASE + 0x208, tmp); + + /* DSIPHY_PLL_CTRL_3 */ + tmp = MIPI_INP(MIPI_DSI_BASE + 0x20c); + tmp &= ~0x3f; + tmp |= (dividers->ref_divider_ratio - 1) & 0x3f; + MIPI_OUTP(MIPI_DSI_BASE + 0x20c, tmp); + + /* DSIPHY_PLL_CTRL_8 */ + tmp = MIPI_INP(MIPI_DSI_BASE + 0x220); + tmp &= ~0x0f; + tmp |= (dividers->bit_clk_divider - 1) & 0x0f; + MIPI_OUTP(MIPI_DSI_BASE + 0x220, tmp); + + /* DSIPHY_PLL_CTRL_9 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x224, (dividers->byte_clk_divider - 1)); + + /* DSIPHY_PLL_CTRL_10 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x228, (dividers->dsi_clk_divider - 1)); + + return 0; +} + +int mipi_dsi_clk_div_config(uint8 bpp, uint8 lanes, + uint32 *expected_dsi_pclk) +{ + u32 fb_divider, rate, vco; + u32 div_ratio = 0; + struct dsi_clk_mnd_table const *mnd_entry = mnd_table; + if (pll_divider_config.clk_rate == 0) + pll_divider_config.clk_rate = 454000000; + + rate = pll_divider_config.clk_rate / 1000000; /* In Mhz */ + + if (rate < 125) { + vco = rate * 8; + div_ratio = 8; + } else if (rate < 250) { + vco = rate * 4; + div_ratio = 4; + } else if (rate < 500) { + vco = rate * 2; + div_ratio = 2; + } else { + vco = rate * 1; + div_ratio = 1; + } + + /* find the mnd settings from mnd_table entry */ + for (; mnd_entry != mnd_table + ARRAY_SIZE(mnd_table); ++mnd_entry) { + if (((mnd_entry->lanes) == lanes) && + ((mnd_entry->bpp) == bpp)) + break; + } + + if (mnd_entry == mnd_table + ARRAY_SIZE(mnd_table)) { + pr_err("%s: requested Lanes, %u & BPP, %u, not supported\n", + __func__, lanes, bpp); + return -EINVAL; + } + fb_divider = ((vco * PREF_DIV_RATIO) / 27); + pll_divider_config.fb_divider = fb_divider; + pll_divider_config.ref_divider_ratio = PREF_DIV_RATIO; + pll_divider_config.bit_clk_divider = div_ratio; + pll_divider_config.byte_clk_divider = + pll_divider_config.bit_clk_divider * 8; + pll_divider_config.dsi_clk_divider = + (mnd_entry->dsiclk_div) * div_ratio; + + if ((mnd_entry->dsiclk_d == 0) + || (mnd_entry->dsiclk_m == 1)) { + dsicore_clk.mnd_mode = 0; + dsicore_clk.src = 0x3; + dsicore_clk.pre_div_func = (mnd_entry->dsiclk_n - 1); + } else { + dsicore_clk.mnd_mode = 2; + dsicore_clk.src = 0x3; + dsicore_clk.m = mnd_entry->dsiclk_m; + dsicore_clk.n = mnd_entry->dsiclk_n; + dsicore_clk.d = mnd_entry->dsiclk_d; + } + + if ((mnd_entry->pclk_d == 0) + || (mnd_entry->pclk_m == 1)) { + dsi_pclk.mnd_mode = 0; + dsi_pclk.src = 0x3; + dsi_pclk.pre_div_func = (mnd_entry->pclk_n - 1); + *expected_dsi_pclk = ((vco * 1000000) / + ((pll_divider_config.dsi_clk_divider) + * (mnd_entry->pclk_n))); + } else { + dsi_pclk.mnd_mode = 2; + dsi_pclk.src = 0x3; + dsi_pclk.m = mnd_entry->pclk_m; + dsi_pclk.n = mnd_entry->pclk_n; + dsi_pclk.d = mnd_entry->pclk_d; + *expected_dsi_pclk = ((vco * 1000000 * dsi_pclk.m) / + ((pll_divider_config.dsi_clk_divider) + * (mnd_entry->pclk_n))); + } + return 0; +} + +void mipi_dsi_phy_init(int panel_ndx, struct msm_panel_info const *panel_info, + int target_type) +{ + struct mipi_dsi_phy_ctrl *pd; + int i, off; + + MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0001);/* start phy sw reset */ + msleep(100); + MIPI_OUTP(MIPI_DSI_BASE + 0x128, 0x0000);/* end phy w reset */ + MIPI_OUTP(MIPI_DSI_BASE + 0x2cc, 0x0003);/* regulator_ctrl_0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x2d0, 0x0001);/* regulator_ctrl_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x2d4, 0x0001);/* regulator_ctrl_2 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x2d8, 0x0000);/* regulator_ctrl_3 */ +#ifdef DSI_POWER + MIPI_OUTP(MIPI_DSI_BASE + 0x2dc, 0x0100);/* regulator_ctrl_4 */ +#endif + + pd = (panel_info->mipi).dsi_phy_db; + + off = 0x02cc; /* regulator ctrl 0 */ + for (i = 0; i < 4; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->regulator[i]); + wmb(); + off += 4; + } + + off = 0x0260; /* phy timig ctrl 0 */ + for (i = 0; i < 11; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->timing[i]); + wmb(); + off += 4; + } + + off = 0x0290; /* ctrl 0 */ + for (i = 0; i < 4; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->ctrl[i]); + wmb(); + off += 4; + } + + off = 0x02a0; /* strength 0 */ + for (i = 0; i < 4; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->strength[i]); + wmb(); + off += 4; + } + + mipi_dsi_calibration(); + + off = 0x0204; /* pll ctrl 1, skip 0 */ + for (i = 1; i < 21; i++) { + MIPI_OUTP(MIPI_DSI_BASE + off, pd->pll[i]); + wmb(); + off += 4; + } + + if (panel_info) + mipi_dsi_phy_pll_config(panel_info->clk_rate); + + /* pll ctrl 0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x200, pd->pll[0]); + wmb(); + MIPI_OUTP(MIPI_DSI_BASE + 0x200, (pd->pll[0] | 0x01)); +} + +void mipi_dsi_clk_enable(void) +{ + if (mipi_dsi_clk_on) { + pr_err("%s: mipi_dsi_clk already ON\n", __func__); + return; + } + + mipi_dsi_clk_on = 1; + + clk_enable(amp_pclk); /* clock for AHB-master to AXI */ + clk_enable(dsi_m_pclk); + clk_enable(dsi_s_pclk); + if (clk_set_rate(dsi_byte_div_clk, 1) < 0) /* divided by 1 */ + pr_err("%s: clk_set_rate failed\n", __func__); + clk_enable(dsi_byte_div_clk); + clk_enable(dsi_esc_clk); + mipi_dsi_pclk_ctrl(&dsi_pclk, 1); + mipi_dsi_clk_ctrl(&dsicore_clk, 1); + mipi_dsi_ahb_en(); + mipi_dsi_sfpb_cfg(); +} + +void mipi_dsi_clk_disable(void) +{ + if (mipi_dsi_clk_on == 0) { + pr_err("%s: mipi_dsi_clk already OFF\n", __func__); + return; + } + + mipi_dsi_clk_on = 0; + + MIPI_OUTP(MIPI_DSI_BASE + 0x0118, 0); + + mipi_dsi_pclk_ctrl(&dsi_pclk, 0); + mipi_dsi_clk_ctrl(&dsicore_clk, 0); + clk_disable(dsi_esc_clk); + clk_disable(dsi_byte_div_clk); + clk_disable(dsi_m_pclk); + clk_disable(dsi_s_pclk); + clk_disable(amp_pclk); /* clock for AHB-master to AXI */ +} + +void mipi_dsi_phy_ctrl(int on) +{ + if (on) { + /* DSIPHY_PLL_CTRL_5 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0214, 0x050); + + /* DSIPHY_TPA_CTRL_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0258, 0x00f); + + /* DSIPHY_TPA_CTRL_2 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x025c, 0x000); + } else { + /* DSIPHY_PLL_CTRL_5 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0214, 0x05f); + + /* DSIPHY_TPA_CTRL_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0258, 0x08f); + + /* DSIPHY_TPA_CTRL_2 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x025c, 0x001); + + /* DSIPHY_REGULATOR_CTRL_0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x02cc, 0x02); + + /* DSIPHY_CTRL_0 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0290, 0x00); + + /* DSIPHY_CTRL_1 */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0294, 0x7f); + + /* DSIPHY_PLL_CTRL_0, disbale dsi pll */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0200, 0x40); + + /* disbale dsi clk */ + MIPI_OUTP(MIPI_DSI_BASE + 0x0118, 0); + } +} + +#ifdef CONFIG_FB_MSM_HDMI_COMMON +#define SW_RESET BIT(2) +void hdmi_phy_reset(void) +{ + unsigned int phy_reset_polarity = 0x0; + unsigned int val = HDMI_INP_ND(0x2D4); + + phy_reset_polarity = val >> 3 & 0x1; + + if (phy_reset_polarity == 0) + HDMI_OUTP(0x2D4, val | SW_RESET); + else + HDMI_OUTP(0x2D4, val & (~SW_RESET)); + + msleep(100); + + if (phy_reset_polarity == 0) + HDMI_OUTP(0x2D4, val & (~SW_RESET)); + else + HDMI_OUTP(0x2D4, val | SW_RESET); +} + +void hdmi_msm_init_phy(int video_format) +{ + uint32 offset; + /* De-serializer delay D/C for non-lbk mode + * PHY REG0 = (DESER_SEL(0) | DESER_DEL_CTRL(3) + * | AMUX_OUT_SEL(0)) + */ + HDMI_OUTP_ND(0x0300, 0x0C); /*0b00001100*/ + + if (video_format == HDMI_VFRMT_720x480p60_16_9) { + /* PHY REG1 = DTEST_MUX_SEL(5) | PLL_GAIN_SEL(0) + * | OUTVOL_SWING_CTRL(3) + */ + HDMI_OUTP_ND(0x0304, 0x53); /*0b01010011*/ + } else { + /* If the freq. is less than 120MHz, use low gain 0 + * for board with termination + * PHY REG1 = DTEST_MUX_SEL(5) | PLL_GAIN_SEL(0) + * | OUTVOL_SWING_CTRL(4) + */ + HDMI_OUTP_ND(0x0304, 0x54); /*0b01010100*/ + } + + /* No matter what, start from the power down mode + * PHY REG2 = PD_PWRGEN | PD_PLL | PD_DRIVE_4 | PD_DRIVE_3 + * | PD_DRIVE_2 | PD_DRIVE_1 | PD_DESER + */ + HDMI_OUTP_ND(0x0308, 0x7F); /*0b01111111*/ + + /* Turn PowerGen on + * PHY REG2 = PD_PLL | PD_DRIVE_4 | PD_DRIVE_3 + * | PD_DRIVE_2 | PD_DRIVE_1 | PD_DESER + */ + HDMI_OUTP_ND(0x0308, 0x3F); /*0b00111111*/ + + /* Turn PLL power on + * PHY REG2 = PD_DRIVE_4 | PD_DRIVE_3 + * | PD_DRIVE_2 | PD_DRIVE_1 | PD_DESER + */ + HDMI_OUTP_ND(0x0308, 0x1F); /*0b00011111*/ + + /* Write to HIGH after PLL power down de-assert + * PHY REG3 = PLL_ENABLE + */ + HDMI_OUTP_ND(0x030C, 0x01); + /* ASIC power on; PHY REG9 = 0 */ + HDMI_OUTP_ND(0x0324, 0x00); + /* Enable PLL lock detect, PLL lock det will go high after lock + * Enable the re-time logic + * PHY REG12 = PLL_LOCK_DETECT_EN | RETIMING_ENABLE + */ + HDMI_OUTP_ND(0x0330, 0x03); /*0b00000011*/ + + /* Drivers are on + * PHY REG2 = PD_DESER + */ + HDMI_OUTP_ND(0x0308, 0x01); /*0b00000001*/ + /* If the RX detector is needed + * PHY REG2 = RCV_SENSE_EN | PD_DESER + */ + HDMI_OUTP_ND(0x0308, 0x81); /*0b10000001*/ + + offset = 0x0310; + while (offset <= 0x032C) { + HDMI_OUTP(offset, 0x0); + offset += 0x4; + } + + /* If we want to use lock enable based on counting + * PHY REG12 = FORCE_LOCK | PLL_LOCK_DETECT_EN | RETIMING_ENABLE + */ + HDMI_OUTP_ND(0x0330, 0x13); /*0b00010011*/ +} + +void hdmi_msm_powerdown_phy(void) +{ + /* Disable PLL */ + HDMI_OUTP_ND(0x030C, 0x00); + /* Power down PHY */ + HDMI_OUTP_ND(0x0308, 0x7F); /*0b01111111*/ +} + +void hdmi_frame_ctrl_cfg(const struct hdmi_disp_mode_timing_type *timing) +{ + /* 0x02C8 HDMI_FRAME_CTRL + * 31 INTERLACED_EN Interlaced or progressive enable bit + * 0: Frame in progressive + * 1: Frame is interlaced + * 29 HSYNC_HDMI_POL HSYNC polarity fed to HDMI core + * 0: Active Hi Hsync, detect the rising edge of hsync + * 1: Active lo Hsync, Detect the falling edge of Hsync + * 28 VSYNC_HDMI_POL VSYNC polarity fed to HDMI core + * 0: Active Hi Vsync, detect the rising edge of vsync + * 1: Active Lo Vsync, Detect the falling edge of Vsync + * 12 RGB_MUX_SEL ALPHA mdp4 input is RGB, mdp4 input is BGR + */ + HDMI_OUTP(0x02C8, + ((timing->interlaced << 31) & 0x80000000) + | ((timing->active_low_h << 29) & 0x20000000) + | ((timing->active_low_v << 28) & 0x10000000) + | (1 << 12)); +} + +void hdmi_msm_phy_status_poll(void) +{ + unsigned int phy_ready; + phy_ready = 0x1 & HDMI_INP_ND(0x33c); + if (phy_ready) { + pr_debug("HDMI Phy Status bit is set and ready\n"); + } else { + pr_debug("HDMI Phy Status bit is not set," + "waiting for ready status\n"); + do { + phy_ready = 0x1 & HDMI_INP_ND(0x33c); + } while (!phy_ready); + } +} +#endif diff --git a/drivers/video/msm/msm_fb.c b/drivers/video/msm/msm_fb.c index ec351309e60..1c4cc723295 100644 --- a/drivers/video/msm/msm_fb.c +++ b/drivers/video/msm/msm_fb.c @@ -3,6 +3,7 @@ * Core MSM framebuffer driver. * * Copyright (C) 2007 Google Incorporated + * Copyright (c) 2008-2011, Code Aurora Forum. 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 @@ -14,627 +15,3045 @@ * GNU General Public License for more details. */ -#include #include -#include +#include +#include #include #include - -#include -#include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include #include +#include + #include -#include +#include +#include +#include +#include #include -#include - -#define PRINT_FPS 0 -#define PRINT_BLIT_TIME 0 - -#define SLEEPING 0x4 -#define UPDATING 0x3 -#define FULL_UPDATE_DONE 0x2 -#define WAKING 0x1 -#define AWAKE 0x0 - -#define NONE 0 -#define SUSPEND_RESUME 0x1 -#define FPS 0x2 -#define BLIT_TIME 0x4 -#define SHOW_UPDATES 0x8 - -#define DLOG(mask, fmt, args...) \ -do { \ - if (msmfb_debug_mask & mask) \ - printk(KERN_INFO "msmfb: "fmt, ##args); \ -} while (0) - -static int msmfb_debug_mask; -module_param_named(msmfb_debug_mask, msmfb_debug_mask, int, - S_IRUGO | S_IWUSR | S_IWGRP); - -struct mdp_device *mdp; - -struct msmfb_info { - struct fb_info *fb; - struct msm_panel_data *panel; - int xres; - int yres; - unsigned output_format; - unsigned yoffset; - unsigned frame_requested; - unsigned frame_done; - int sleeping; - unsigned update_frame; - struct { - int left; - int top; - int eright; /* exclusive */ - int ebottom; /* exclusive */ - } update_info; - char *black; - - spinlock_t update_lock; - struct mutex panel_init_lock; - wait_queue_head_t frame_wq; - struct work_struct resume_work; - struct msmfb_callback dma_callback; - struct msmfb_callback vsync_callback; - struct hrtimer fake_vsync; - ktime_t vsync_request_time; +#include +#include +#include +#include + +#define MSM_FB_C +#include "msm_fb.h" +#include "mddihosti.h" +#include "tvenc.h" +#include "mdp.h" +#include "mdp4.h" + +#ifdef CONFIG_FB_MSM_LOGO +#define INIT_IMAGE_FILE "/initlogo.rle" +extern int load_565rle_image(char *filename); +#endif + +#ifdef CONFIG_FB_MSM_TRIPLE_BUFFER +#define MSM_FB_NUM 3 +#endif + +static unsigned char *fbram; +static unsigned char *fbram_phys; +static int fbram_size; + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; + +int vsync_mode = 1; + +#define MAX_BLIT_REQ 256 + +#define MAX_FBI_LIST 32 +static struct fb_info *fbi_list[MAX_FBI_LIST]; +static int fbi_list_index; + +static struct msm_fb_data_type *mfd_list[MAX_FBI_LIST]; +static int mfd_list_index; + +static u32 msm_fb_pseudo_palette[16] = { + 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff }; -static int msmfb_open(struct fb_info *info, int user) +u32 msm_fb_debug_enabled; +/* Setting msm_fb_msg_level to 8 prints out ALL messages */ +u32 msm_fb_msg_level = 7; + +/* Setting mddi_msg_level to 8 prints out ALL messages */ +u32 mddi_msg_level = 5; + +extern int32 mdp_block_power_cnt[MDP_MAX_BLOCK]; +extern unsigned long mdp_timer_duration; + +static int msm_fb_register(struct msm_fb_data_type *mfd); +static int msm_fb_open(struct fb_info *info, int user); +static int msm_fb_release(struct fb_info *info, int user); +static int msm_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); +static int msm_fb_stop_sw_refresher(struct msm_fb_data_type *mfd); +int msm_fb_resume_sw_refresher(struct msm_fb_data_type *mfd); +static int msm_fb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info); +static int msm_fb_set_par(struct fb_info *info); +static int msm_fb_blank_sub(int blank_mode, struct fb_info *info, + boolean op_enable); +static int msm_fb_suspend_sub(struct msm_fb_data_type *mfd); +static int msm_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg); +static int msm_fb_mmap(struct fb_info *info, struct vm_area_struct * vma); + +#ifdef MSM_FB_ENABLE_DBGFS + +#define MSM_FB_MAX_DBGFS 1024 +#define MAX_BACKLIGHT_BRIGHTNESS 255 + +int msm_fb_debugfs_file_index; +struct dentry *msm_fb_debugfs_root; +struct dentry *msm_fb_debugfs_file[MSM_FB_MAX_DBGFS]; + +DEFINE_MUTEX(msm_fb_notify_update_sem); +void msmfb_no_update_notify_timer_cb(unsigned long data) { - return 0; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)data; + if (!mfd) + pr_err("%s mfd NULL\n", __func__); + complete(&mfd->msmfb_no_update_notify); } -static int msmfb_release(struct fb_info *info, int user) +struct dentry *msm_fb_get_debugfs_root(void) { - return 0; + if (msm_fb_debugfs_root == NULL) + msm_fb_debugfs_root = debugfs_create_dir("msm_fb", NULL); + + return msm_fb_debugfs_root; } -/* Called from dma interrupt handler, must not sleep */ -static void msmfb_handle_dma_interrupt(struct msmfb_callback *callback) +void msm_fb_debugfs_file_create(struct dentry *root, const char *name, + u32 *var) { - unsigned long irq_flags; - struct msmfb_info *msmfb = container_of(callback, struct msmfb_info, - dma_callback); + if (msm_fb_debugfs_file_index >= MSM_FB_MAX_DBGFS) + return; - spin_lock_irqsave(&msmfb->update_lock, irq_flags); - msmfb->frame_done = msmfb->frame_requested; - if (msmfb->sleeping == UPDATING && - msmfb->frame_done == msmfb->update_frame) { - DLOG(SUSPEND_RESUME, "full update completed\n"); - schedule_work(&msmfb->resume_work); - } - spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); - wake_up(&msmfb->frame_wq); + msm_fb_debugfs_file[msm_fb_debugfs_file_index++] = + debugfs_create_u32(name, S_IRUGO | S_IWUSR, root, var); } +#endif -static int msmfb_start_dma(struct msmfb_info *msmfb) +int msm_fb_cursor(struct fb_info *info, struct fb_cursor *cursor) { - uint32_t x, y, w, h; - unsigned addr; - unsigned long irq_flags; - uint32_t yoffset; - s64 time_since_request; - struct msm_panel_data *panel = msmfb->panel; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; - spin_lock_irqsave(&msmfb->update_lock, irq_flags); - time_since_request = ktime_to_ns(ktime_sub(ktime_get(), - msmfb->vsync_request_time)); - if (time_since_request > 20 * NSEC_PER_MSEC) { - uint32_t us; - us = do_div(time_since_request, NSEC_PER_MSEC) / NSEC_PER_USEC; - printk(KERN_WARNING "msmfb_start_dma %lld.%03u ms after vsync " - "request\n", time_since_request, us); - } - if (msmfb->frame_done == msmfb->frame_requested) { - spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); - return -1; + if (!mfd->cursor_update) + return -ENODEV; + + return mfd->cursor_update(info, cursor); +} + +static int msm_fb_resource_initialized; + +#ifndef CONFIG_FB_BACKLIGHT +static int lcd_backlight_registered; + +static void msm_fb_set_bl_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct msm_fb_data_type *mfd = dev_get_drvdata(led_cdev->dev->parent); + int bl_lvl; + + if (value > MAX_BACKLIGHT_BRIGHTNESS) + value = MAX_BACKLIGHT_BRIGHTNESS; + + /* This maps android backlight level 0 to 255 into + driver backlight level 0 to bl_max with rounding */ + bl_lvl = (2 * value * mfd->panel_info.bl_max + MAX_BACKLIGHT_BRIGHTNESS) + /(2 * MAX_BACKLIGHT_BRIGHTNESS); + + if (!bl_lvl && value) + bl_lvl = 1; + + msm_fb_set_backlight(mfd, bl_lvl); +} + +static struct led_classdev backlight_led = { + .name = "lcd-backlight", + .brightness = MAX_BACKLIGHT_BRIGHTNESS, + .brightness_set = msm_fb_set_bl_brightness, +}; +#endif + +static struct msm_fb_platform_data *msm_fb_pdata; +static char panel_name[128]; +module_param_string(panel_name, panel_name, sizeof(panel_name) , 0); + +int msm_fb_detect_client(const char *name) +{ + int ret = -EPERM; +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + u32 id; +#endif + + MSM_FB_DEBUG("\n name = %s, panel_name = %s", name, panel_name); + if (strlen(panel_name)) { + if (!strcmp((char *)panel_name, name)) + return 0; + else + return -EPERM; } - if (msmfb->sleeping == SLEEPING) { - DLOG(SUSPEND_RESUME, "tried to start dma while asleep\n"); - spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); - return -1; + + if (msm_fb_pdata && msm_fb_pdata->detect_client) { + ret = msm_fb_pdata->detect_client(name); + + /* if it's non mddi panel, we need to pre-scan + mddi client to see if we can disable mddi host */ + +#ifdef CONFIG_FB_MSM_MDDI_AUTO_DETECT + if (!ret && msm_fb_pdata->mddi_prescan) + id = mddi_get_client_id(); +#endif } - x = msmfb->update_info.left; - y = msmfb->update_info.top; - w = msmfb->update_info.eright - x; - h = msmfb->update_info.ebottom - y; - yoffset = msmfb->yoffset; - msmfb->update_info.left = msmfb->xres + 1; - msmfb->update_info.top = msmfb->yres + 1; - msmfb->update_info.eright = 0; - msmfb->update_info.ebottom = 0; - if (unlikely(w > msmfb->xres || h > msmfb->yres || - w == 0 || h == 0)) { - printk(KERN_INFO "invalid update: %d %d %d " - "%d\n", x, y, w, h); - msmfb->frame_done = msmfb->frame_requested; - goto error; - } - spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); - - addr = ((msmfb->xres * (yoffset + y) + x) * 2); - mdp->dma(mdp, addr + msmfb->fb->fix.smem_start, - msmfb->xres * 2, w, h, x, y, &msmfb->dma_callback, - panel->interface_type); - return 0; -error: - spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); - /* some clients need to clear their vsync interrupt */ - if (panel->clear_vsync) - panel->clear_vsync(panel); - wake_up(&msmfb->frame_wq); - return 0; + + return ret; } -/* Called from esync interrupt handler, must not sleep */ -static void msmfb_handle_vsync_interrupt(struct msmfb_callback *callback) +static ssize_t msm_fb_msm_fb_type(struct device *dev, + struct device_attribute *attr, char *buf) { - struct msmfb_info *msmfb = container_of(callback, struct msmfb_info, - vsync_callback); - msmfb_start_dma(msmfb); + ssize_t ret = 0; + struct fb_info *fbi = dev_get_drvdata(dev); + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)fbi->par; + struct msm_fb_panel_data *pdata = + (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; + + switch (pdata->panel_info.type) { + case NO_PANEL: + ret = snprintf(buf, PAGE_SIZE, "no panel\n"); + break; + case MDDI_PANEL: + ret = snprintf(buf, PAGE_SIZE, "mddi panel\n"); + break; + case EBI2_PANEL: + ret = snprintf(buf, PAGE_SIZE, "ebi2 panel\n"); + break; + case LCDC_PANEL: + ret = snprintf(buf, PAGE_SIZE, "lcdc panel\n"); + break; + case EXT_MDDI_PANEL: + ret = snprintf(buf, PAGE_SIZE, "ext mddi panel\n"); + break; + case TV_PANEL: + ret = snprintf(buf, PAGE_SIZE, "tv panel\n"); + break; + case HDMI_PANEL: + ret = snprintf(buf, PAGE_SIZE, "hdmi panel\n"); + break; + case DTV_PANEL: + ret = snprintf(buf, PAGE_SIZE, "dtv panel\n"); + break; + default: + ret = snprintf(buf, PAGE_SIZE, "unknown panel\n"); + break; + } + + return ret; } -static enum hrtimer_restart msmfb_fake_vsync(struct hrtimer *timer) +static DEVICE_ATTR(msm_fb_type, S_IRUGO, msm_fb_msm_fb_type, NULL); +static struct attribute *msm_fb_attrs[] = { + &dev_attr_msm_fb_type.attr, + NULL, +}; +static struct attribute_group msm_fb_attr_group = { + .attrs = msm_fb_attrs, +}; + +static int msm_fb_create_sysfs(struct platform_device *pdev) { - struct msmfb_info *msmfb = container_of(timer, struct msmfb_info, - fake_vsync); - msmfb_start_dma(msmfb); - return HRTIMER_NORESTART; + int rc; + struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); + + rc = sysfs_create_group(&mfd->fbi->dev->kobj, &msm_fb_attr_group); + if (rc) + MSM_FB_ERR("%s: sysfs group creation failed, rc=%d\n", __func__, + rc); + return rc; } - -static void msmfb_pan_update(struct fb_info *info, uint32_t left, uint32_t top, - uint32_t eright, uint32_t ebottom, - uint32_t yoffset, int pan_display) +static void msm_fb_remove_sysfs(struct platform_device *pdev) { - struct msmfb_info *msmfb = info->par; - struct msm_panel_data *panel = msmfb->panel; - unsigned long irq_flags; - int sleeping; - int retry = 1; + struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); + sysfs_remove_group(&mfd->fbi->dev->kobj, &msm_fb_attr_group); +} - DLOG(SHOW_UPDATES, "update %d %d %d %d %d %d\n", - left, top, eright, ebottom, yoffset, pan_display); -restart: - spin_lock_irqsave(&msmfb->update_lock, irq_flags); +static int msm_fb_probe(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + int rc; + int err = 0; + + MSM_FB_DEBUG("msm_fb_probe\n"); + + if ((pdev->id == 0) && (pdev->num_resources > 0)) { + msm_fb_pdata = pdev->dev.platform_data; + fbram_size = + pdev->resource[0].end - pdev->resource[0].start + 1; + fbram_phys = (char *)pdev->resource[0].start; + fbram = ioremap((unsigned long)fbram_phys, fbram_size); + + if (!fbram) { + printk(KERN_ERR "fbram ioremap failed!\n"); + return -ENOMEM; + } + MSM_FB_DEBUG("msm_fb_probe: phy_Addr = 0x%x virt = 0x%x\n", + (int)fbram_phys, (int)fbram); - /* if we are sleeping, on a pan_display wait 10ms (to throttle back - * drawing otherwise return */ - if (msmfb->sleeping == SLEEPING) { - DLOG(SUSPEND_RESUME, "drawing while asleep\n"); - spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); - if (pan_display) - wait_event_interruptible_timeout(msmfb->frame_wq, - msmfb->sleeping != SLEEPING, HZ/10); - return; + msm_fb_resource_initialized = 1; + return 0; } - sleeping = msmfb->sleeping; - /* on a full update, if the last frame has not completed, wait for it */ - if ((pan_display && msmfb->frame_requested != msmfb->frame_done) || - sleeping == UPDATING) { - int ret; - spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); - ret = wait_event_interruptible_timeout(msmfb->frame_wq, - msmfb->frame_done == msmfb->frame_requested && - msmfb->sleeping != UPDATING, 5 * HZ); - if (ret <= 0 && (msmfb->frame_requested != msmfb->frame_done || - msmfb->sleeping == UPDATING)) { - if (retry && panel->request_vsync && - (sleeping == AWAKE)) { - panel->request_vsync(panel, - &msmfb->vsync_callback); - retry = 0; - printk(KERN_WARNING "msmfb_pan_display timeout " - "rerequest vsync\n"); - } else { - printk(KERN_WARNING "msmfb_pan_display timeout " - "waiting for frame start, %d %d\n", - msmfb->frame_requested, - msmfb->frame_done); - return; - } - } - goto restart; + if (!msm_fb_resource_initialized) + return -EPERM; + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; + + mfd->panel_info.frame_count = 0; + mfd->bl_level = 0; +#ifdef CONFIG_FB_MSM_OVERLAY + mfd->overlay_play_enable = 1; +#endif + rc = msm_fb_register(mfd); + if (rc) + return rc; + err = pm_runtime_set_active(mfd->fbi->dev); + if (err < 0) + printk(KERN_ERR "pm_runtime: fail to set active.\n"); + pm_runtime_enable(mfd->fbi->dev); +#ifdef CONFIG_FB_BACKLIGHT + msm_fb_config_backlight(mfd); +#else + /* android supports only one lcd-backlight/lcd for now */ + if (!lcd_backlight_registered) { + if (led_classdev_register(&pdev->dev, &backlight_led)) + printk(KERN_ERR "led_classdev_register failed\n"); + else + lcd_backlight_registered = 1; } +#endif + pdev_list[pdev_list_cnt++] = pdev; + msm_fb_create_sysfs(pdev); + return 0; +} - msmfb->frame_requested++; - /* if necessary, update the y offset, if this is the - * first full update on resume, set the sleeping state */ - if (pan_display) { - msmfb->yoffset = yoffset; - if (left == 0 && top == 0 && eright == info->var.xres && - ebottom == info->var.yres) { - if (sleeping == WAKING) { - msmfb->update_frame = msmfb->frame_requested; - DLOG(SUSPEND_RESUME, "full update starting\n"); - msmfb->sleeping = UPDATING; - } - } +static int msm_fb_remove(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + MSM_FB_DEBUG("msm_fb_remove\n"); + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + msm_fb_remove_sysfs(pdev); + + pm_runtime_disable(mfd->fbi->dev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (msm_fb_suspend_sub(mfd)) + printk(KERN_ERR "msm_fb_remove: can't stop the device %d\n", mfd->index); + + if (mfd->channel_irq != 0) + free_irq(mfd->channel_irq, (void *)mfd); + + if (mfd->vsync_width_boundary) + vfree(mfd->vsync_width_boundary); + + if (mfd->vsync_resync_timer.function) + del_timer(&mfd->vsync_resync_timer); + + if (mfd->refresh_timer.function) + del_timer(&mfd->refresh_timer); + + if (mfd->dma_hrtimer.function) + hrtimer_cancel(&mfd->dma_hrtimer); + + if (mfd->msmfb_no_update_notify_timer.function) + del_timer(&mfd->msmfb_no_update_notify_timer); + complete(&mfd->msmfb_no_update_notify); + complete(&mfd->msmfb_update_notify); + + /* remove /dev/fb* */ + unregister_framebuffer(mfd->fbi); + +#ifdef CONFIG_FB_BACKLIGHT + /* remove /sys/class/backlight */ + backlight_device_unregister(mfd->fbi->bl_dev); +#else + if (lcd_backlight_registered) { + lcd_backlight_registered = 0; + led_classdev_unregister(&backlight_led); } +#endif + +#ifdef MSM_FB_ENABLE_DBGFS + if (mfd->sub_dir) + debugfs_remove(mfd->sub_dir); +#endif + + return 0; +} + +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) +static int msm_fb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct msm_fb_data_type *mfd; + int ret = 0; + + MSM_FB_DEBUG("msm_fb_suspend\n"); + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if ((!mfd) || (mfd->key != MFD_KEY)) + return 0; + + console_lock(); + fb_set_suspend(mfd->fbi, FBINFO_STATE_SUSPENDED); - /* set the update request */ - if (left < msmfb->update_info.left) - msmfb->update_info.left = left; - if (top < msmfb->update_info.top) - msmfb->update_info.top = top; - if (eright > msmfb->update_info.eright) - msmfb->update_info.eright = eright; - if (ebottom > msmfb->update_info.ebottom) - msmfb->update_info.ebottom = ebottom; - DLOG(SHOW_UPDATES, "update queued %d %d %d %d %d\n", - msmfb->update_info.left, msmfb->update_info.top, - msmfb->update_info.eright, msmfb->update_info.ebottom, - msmfb->yoffset); - spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); - - /* if the panel is all the way on wait for vsync, otherwise sleep - * for 16 ms (long enough for the dma to panel) and then begin dma */ - msmfb->vsync_request_time = ktime_get(); - if (panel->request_vsync && (sleeping == AWAKE)) { - panel->request_vsync(panel, &msmfb->vsync_callback); + ret = msm_fb_suspend_sub(mfd); + if (ret != 0) { + printk(KERN_ERR "msm_fb: failed to suspend! %d\n", ret); + fb_set_suspend(mfd->fbi, FBINFO_STATE_RUNNING); } else { - if (!hrtimer_active(&msmfb->fake_vsync)) { - hrtimer_start(&msmfb->fake_vsync, - ktime_set(0, NSEC_PER_SEC/60), - HRTIMER_MODE_REL); - } + pdev->dev.power.power_state = state; } + + console_unlock(); + return ret; } +#else +#define msm_fb_suspend NULL +#endif -static void msmfb_update(struct fb_info *info, uint32_t left, uint32_t top, - uint32_t eright, uint32_t ebottom) +static int msm_fb_suspend_sub(struct msm_fb_data_type *mfd) { - msmfb_pan_update(info, left, top, eright, ebottom, 0, 0); + int ret = 0; + + if ((!mfd) || (mfd->key != MFD_KEY)) + return 0; + + if (mfd->msmfb_no_update_notify_timer.function) + del_timer(&mfd->msmfb_no_update_notify_timer); + complete(&mfd->msmfb_no_update_notify); + + /* + * suspend this channel + */ + mfd->suspend.sw_refreshing_enable = mfd->sw_refreshing_enable; + mfd->suspend.op_enable = mfd->op_enable; + mfd->suspend.panel_power_on = mfd->panel_power_on; + + if (mfd->op_enable) { + ret = + msm_fb_blank_sub(FB_BLANK_POWERDOWN, mfd->fbi, + mfd->suspend.op_enable); + if (ret) { + MSM_FB_INFO + ("msm_fb_suspend: can't turn off display!\n"); + return ret; + } + mfd->op_enable = FALSE; + } + /* + * try to power down + */ + mdp_pipe_ctrl(MDP_MASTER_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + + /* + * detach display channel irq if there's any + * or wait until vsync-resync completes + */ + if ((mfd->dest == DISPLAY_LCD)) { + if (mfd->panel_info.lcd.vsync_enable) { + if (mfd->panel_info.lcd.hw_vsync_mode) { + if (mfd->channel_irq != 0) + disable_irq(mfd->channel_irq); + } else { + volatile boolean vh_pending; + do { + vh_pending = mfd->vsync_handler_pending; + } while (vh_pending); + } + } + } + + return 0; } -static void power_on_panel(struct work_struct *work) +#ifdef CONFIG_PM +static int msm_fb_resume_sub(struct msm_fb_data_type *mfd) { - struct msmfb_info *msmfb = - container_of(work, struct msmfb_info, resume_work); - struct msm_panel_data *panel = msmfb->panel; - unsigned long irq_flags; + int ret = 0; - mutex_lock(&msmfb->panel_init_lock); - DLOG(SUSPEND_RESUME, "turning on panel\n"); - if (msmfb->sleeping == UPDATING) { - if (panel->unblank(panel)) { - printk(KERN_INFO "msmfb: panel unblank failed," - "not starting drawing\n"); - goto error; - } - spin_lock_irqsave(&msmfb->update_lock, irq_flags); - msmfb->sleeping = AWAKE; - wake_up(&msmfb->frame_wq); - spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); + if ((!mfd) || (mfd->key != MFD_KEY)) + return 0; + + /* attach display channel irq if there's any */ + if (mfd->channel_irq != 0) + enable_irq(mfd->channel_irq); + + /* resume state var recover */ + mfd->sw_refreshing_enable = mfd->suspend.sw_refreshing_enable; + mfd->op_enable = mfd->suspend.op_enable; + + if (mfd->suspend.panel_power_on) { + ret = + msm_fb_blank_sub(FB_BLANK_UNBLANK, mfd->fbi, + mfd->op_enable); + if (ret) + MSM_FB_INFO("msm_fb_resume: can't turn on display!\n"); } -error: - mutex_unlock(&msmfb->panel_init_lock); + + return ret; } +#endif + +#if defined(CONFIG_PM) && !defined(CONFIG_HAS_EARLYSUSPEND) +static int msm_fb_resume(struct platform_device *pdev) +{ + /* This resume function is called when interrupt is enabled. + */ + int ret = 0; + struct msm_fb_data_type *mfd; + + MSM_FB_DEBUG("msm_fb_resume\n"); + + mfd = (struct msm_fb_data_type *)platform_get_drvdata(pdev); + + if ((!mfd) || (mfd->key != MFD_KEY)) + return 0; + + console_lock(); + ret = msm_fb_resume_sub(mfd); + pdev->dev.power.power_state = PMSG_ON; + fb_set_suspend(mfd->fbi, FBINFO_STATE_RUNNING); + console_unlock(); + return ret; +} +#else +#define msm_fb_resume NULL +#endif -static int msmfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +static int msm_fb_runtime_suspend(struct device *dev) { - if ((var->xres != info->var.xres) || - (var->yres != info->var.yres) || - (var->xres_virtual != info->var.xres_virtual) || - (var->yres_virtual != info->var.yres_virtual) || - (var->xoffset != info->var.xoffset) || - (var->bits_per_pixel != info->var.bits_per_pixel) || - (var->grayscale != info->var.grayscale)) - return -EINVAL; + dev_dbg(dev, "pm_runtime: suspending...\n"); return 0; } -int msmfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +static int msm_fb_runtime_resume(struct device *dev) { - struct msmfb_info *msmfb = info->par; - struct msm_panel_data *panel = msmfb->panel; - - /* "UPDT" */ - if ((panel->caps & MSMFB_CAP_PARTIAL_UPDATES) && - (var->reserved[0] == 0x54445055)) { - msmfb_pan_update(info, var->reserved[1] & 0xffff, - var->reserved[1] >> 16, - var->reserved[2] & 0xffff, - var->reserved[2] >> 16, var->yoffset, 1); - } else { - msmfb_pan_update(info, 0, 0, info->var.xres, info->var.yres, - var->yoffset, 1); - } + dev_dbg(dev, "pm_runtime: resuming...\n"); return 0; } -static void msmfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +static int msm_fb_runtime_idle(struct device *dev) { - cfb_fillrect(p, rect); - msmfb_update(p, rect->dx, rect->dy, rect->dx + rect->width, - rect->dy + rect->height); + dev_dbg(dev, "pm_runtime: idling...\n"); + return 0; } -static void msmfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) +static struct dev_pm_ops msm_fb_dev_pm_ops = { + .runtime_suspend = msm_fb_runtime_suspend, + .runtime_resume = msm_fb_runtime_resume, + .runtime_idle = msm_fb_runtime_idle, +}; + +static struct platform_driver msm_fb_driver = { + .probe = msm_fb_probe, + .remove = msm_fb_remove, +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = msm_fb_suspend, + .resume = msm_fb_resume, +#endif + .shutdown = NULL, + .driver = { + /* Driver name must match the device name added in platform.c. */ + .name = "msm_fb", + .pm = &msm_fb_dev_pm_ops, + }, +}; + +#if defined(CONFIG_HAS_EARLYSUSPEND) && (defined(CONFIG_FB_MSM_OVERLAY) || \ + defined(CONFIG_FB_MSM_MDP303)) +static void memset32_io(u32 __iomem *_ptr, u32 val, size_t count) { - cfb_copyarea(p, area); - msmfb_update(p, area->dx, area->dy, area->dx + area->width, - area->dy + area->height); + count >>= 2; + while (count--) + writel(val, _ptr++); } +#endif -static void msmfb_imageblit(struct fb_info *p, const struct fb_image *image) +#ifdef CONFIG_HAS_EARLYSUSPEND +static void msmfb_early_suspend(struct early_suspend *h) { - cfb_imageblit(p, image); - msmfb_update(p, image->dx, image->dy, image->dx + image->width, - image->dy + image->height); + struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, + early_suspend); +#if defined(CONFIG_FB_MSM_OVERLAY) || defined(CONFIG_FB_MSM_MDP303) + /* + * For MDP with overlay, set framebuffer with black pixels + * to show black screen on HDMI. + */ + struct fb_info *fbi = mfd->fbi; + switch (mfd->fbi->var.bits_per_pixel) { + case 32: + memset32_io((void *)fbi->screen_base, 0xFF000000, + fbi->fix.smem_len); + break; + default: + memset32_io((void *)fbi->screen_base, 0x00, fbi->fix.smem_len); + break; + } +#endif + msm_fb_suspend_sub(mfd); } +static void msmfb_early_resume(struct early_suspend *h) +{ + struct msm_fb_data_type *mfd = container_of(h, struct msm_fb_data_type, + early_suspend); + msm_fb_resume_sub(mfd); +} +#endif -static int msmfb_blit(struct fb_info *info, - void __user *p) +void msm_fb_set_backlight(struct msm_fb_data_type *mfd, __u32 bkl_lvl) { - struct mdp_blit_req req; - struct mdp_blit_req_list req_list; - int i; - int ret; + struct msm_fb_panel_data *pdata; - if (copy_from_user(&req_list, p, sizeof(req_list))) - return -EFAULT; + pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; - for (i = 0; i < req_list.count; i++) { - struct mdp_blit_req_list *list = - (struct mdp_blit_req_list *)p; - if (copy_from_user(&req, &list->req[i], sizeof(req))) - return -EFAULT; - ret = mdp->blit(mdp, info, &req); - if (ret) - return ret; + if ((pdata) && (pdata->set_backlight)) { + down(&mfd->sem); + mfd->bl_level = bkl_lvl; + pdata->set_backlight(mfd); + up(&mfd->sem); } - return 0; } +static int msm_fb_blank_sub(int blank_mode, struct fb_info *info, + boolean op_enable) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct msm_fb_panel_data *pdata = NULL; + int ret = 0; -DEFINE_MUTEX(mdp_ppp_lock); + if (!op_enable) + return -EPERM; -static int msmfb_ioctl(struct fb_info *p, unsigned int cmd, unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int ret; + pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; + if ((!pdata) || (!pdata->on) || (!pdata->off)) { + printk(KERN_ERR "msm_fb_blank_sub: no panel operation detected!\n"); + return -ENODEV; + } - switch (cmd) { - case MSMFB_GRP_DISP: - mdp->set_grp_disp(mdp, arg); - break; - case MSMFB_BLIT: - ret = msmfb_blit(p, argp); - if (ret) - return ret; + switch (blank_mode) { + case FB_BLANK_UNBLANK: + if (!mfd->panel_power_on) { + msleep(16); + ret = pdata->on(mfd->pdev); + if (ret == 0) { + mfd->panel_power_on = TRUE; + +/* ToDo: possible conflict with android which doesn't expect sw refresher */ +/* + if (!mfd->hw_refresh) + { + if ((ret = msm_fb_resume_sw_refresher(mfd)) != 0) + { + MSM_FB_INFO("msm_fb_blank_sub: msm_fb_resume_sw_refresher failed = %d!\n",ret); + } + } +*/ + } + } break; + + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_NORMAL: + case FB_BLANK_POWERDOWN: default: - printk(KERN_INFO "msmfb unknown ioctl: %d\n", cmd); - return -EINVAL; + if (mfd->panel_power_on) { + int curr_pwr_state; + + mfd->op_enable = FALSE; + curr_pwr_state = mfd->panel_power_on; + mfd->panel_power_on = FALSE; + + msleep(16); + ret = pdata->off(mfd->pdev); + if (ret) + mfd->panel_power_on = curr_pwr_state; + + mfd->op_enable = TRUE; + } + break; } - return 0; -} -static struct fb_ops msmfb_ops = { - .owner = THIS_MODULE, - .fb_open = msmfb_open, - .fb_release = msmfb_release, - .fb_check_var = msmfb_check_var, - .fb_pan_display = msmfb_pan_display, - .fb_fillrect = msmfb_fillrect, - .fb_copyarea = msmfb_copyarea, - .fb_imageblit = msmfb_imageblit, - .fb_ioctl = msmfb_ioctl, -}; + return ret; +} -static unsigned PP[16]; +static void msm_fb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + cfb_fillrect(info, rect); + if (!mfd->hw_refresh && (info->var.yoffset == 0) && + !mfd->sw_currently_refreshing) { + struct fb_var_screeninfo var; + var = info->var; + var.reserved[0] = 0x54445055; + var.reserved[1] = (rect->dy << 16) | (rect->dx); + var.reserved[2] = ((rect->dy + rect->height) << 16) | + (rect->dx + rect->width); -#define BITS_PER_PIXEL 16 + msm_fb_pan_display(&var, info); + } +} -static void setup_fb_info(struct msmfb_info *msmfb) +static void msm_fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) { - struct fb_info *fb_info = msmfb->fb; - int r; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; - /* finish setting up the fb_info struct */ - strncpy(fb_info->fix.id, "msmfb", 16); - fb_info->fix.ypanstep = 1; + cfb_copyarea(info, area); + if (!mfd->hw_refresh && (info->var.yoffset == 0) && + !mfd->sw_currently_refreshing) { + struct fb_var_screeninfo var; - fb_info->fbops = &msmfb_ops; - fb_info->flags = FBINFO_DEFAULT; + var = info->var; + var.reserved[0] = 0x54445055; + var.reserved[1] = (area->dy << 16) | (area->dx); + var.reserved[2] = ((area->dy + area->height) << 16) | + (area->dx + area->width); - fb_info->fix.type = FB_TYPE_PACKED_PIXELS; - fb_info->fix.visual = FB_VISUAL_TRUECOLOR; - fb_info->fix.line_length = msmfb->xres * 2; + msm_fb_pan_display(&var, info); + } +} - fb_info->var.xres = msmfb->xres; - fb_info->var.yres = msmfb->yres; - fb_info->var.width = msmfb->panel->fb_data->width; - fb_info->var.height = msmfb->panel->fb_data->height; - fb_info->var.xres_virtual = msmfb->xres; - fb_info->var.yres_virtual = msmfb->yres * 2; - fb_info->var.bits_per_pixel = BITS_PER_PIXEL; - fb_info->var.accel_flags = 0; +static void msm_fb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; - fb_info->var.yoffset = 0; + cfb_imageblit(info, image); + if (!mfd->hw_refresh && (info->var.yoffset == 0) && + !mfd->sw_currently_refreshing) { + struct fb_var_screeninfo var; - if (msmfb->panel->caps & MSMFB_CAP_PARTIAL_UPDATES) { - /* - * Set the param in the fixed screen, so userspace can't - * change it. This will be used to check for the - * capability. - */ - fb_info->fix.reserved[0] = 0x5444; - fb_info->fix.reserved[1] = 0x5055; + var = info->var; + var.reserved[0] = 0x54445055; + var.reserved[1] = (image->dy << 16) | (image->dx); + var.reserved[2] = ((image->dy + image->height) << 16) | + (image->dx + image->width); - /* - * This preloads the value so that if userspace doesn't - * change it, it will be a full update - */ - fb_info->var.reserved[0] = 0x54445055; - fb_info->var.reserved[1] = 0; - fb_info->var.reserved[2] = (uint16_t)msmfb->xres | - ((uint32_t)msmfb->yres << 16); + msm_fb_pan_display(&var, info); } +} + +static int msm_fb_blank(int blank_mode, struct fb_info *info) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + return msm_fb_blank_sub(blank_mode, info, mfd->op_enable); +} - fb_info->var.red.offset = 11; - fb_info->var.red.length = 5; - fb_info->var.red.msb_right = 0; - fb_info->var.green.offset = 5; - fb_info->var.green.length = 6; - fb_info->var.green.msb_right = 0; - fb_info->var.blue.offset = 0; - fb_info->var.blue.length = 5; - fb_info->var.blue.msb_right = 0; +static int msm_fb_set_lut(struct fb_cmap *cmap, struct fb_info *info) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; - r = fb_alloc_cmap(&fb_info->cmap, 16, 0); - fb_info->pseudo_palette = PP; + if (!mfd->lut_update) + return -ENODEV; - PP[0] = 0; - for (r = 1; r < 16; r++) - PP[r] = 0xffffffff; + mfd->lut_update(info, cmap); + return 0; } -static int setup_fbmem(struct msmfb_info *msmfb, struct platform_device *pdev) +/* + * Custom Framebuffer mmap() function for MSM driver. + * Differs from standard mmap() function by allowing for customized + * page-protection. + */ +static int msm_fb_mmap(struct fb_info *info, struct vm_area_struct * vma) { - struct fb_info *fb = msmfb->fb; - struct resource *resource; - unsigned long size = msmfb->xres * msmfb->yres * - (BITS_PER_PIXEL >> 3) * 2; - unsigned char *fbram; + /* Get frame buffer memory range. */ + unsigned long start = info->fix.smem_start; + u32 len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len); + unsigned long off = vma->vm_pgoff << PAGE_SHIFT; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + if (off >= len) { + /* memory mapped io */ + off -= len; + if (info->var.accel_flags) { + mutex_unlock(&info->lock); + return -EINVAL; + } + start = info->fix.mmio_start; + len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len); + } - /* board file might have attached a resource describing an fb */ - resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!resource) + /* Set VM flags. */ + start &= PAGE_MASK; + if ((vma->vm_end - vma->vm_start + off) > len) return -EINVAL; + off += start; + vma->vm_pgoff = off >> PAGE_SHIFT; + /* This is an IO map - tell maydump to skip this VMA */ + vma->vm_flags |= VM_IO | VM_RESERVED; + + /* Set VM page protection */ + if (mfd->mdp_fb_page_protection == MDP_FB_PAGE_PROTECTION_WRITECOMBINE) + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + else if (mfd->mdp_fb_page_protection == + MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE) + vma->vm_page_prot = pgprot_writethroughcache(vma->vm_page_prot); + else if (mfd->mdp_fb_page_protection == + MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE) + vma->vm_page_prot = pgprot_writebackcache(vma->vm_page_prot); + else if (mfd->mdp_fb_page_protection == + MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE) + vma->vm_page_prot = pgprot_writebackwacache(vma->vm_page_prot); + else + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* Remap the frame buffer I/O range */ + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) + return -EAGAIN; - /* check the resource is large enough to fit the fb */ - if (resource->end - resource->start < size) { - printk(KERN_ERR "allocated resource is too small for " - "fb\n"); - return -ENOMEM; - } - fb->fix.smem_start = resource->start; - fb->fix.smem_len = resource->end - resource->start; - fbram = ioremap(resource->start, - resource->end - resource->start); - if (fbram == 0) { - printk(KERN_ERR "msmfb: cannot allocate fbram!\n"); - return -ENOMEM; - } - fb->screen_base = fbram; return 0; } -static int msmfb_probe(struct platform_device *pdev) +static struct fb_ops msm_fb_ops = { + .owner = THIS_MODULE, + .fb_open = msm_fb_open, + .fb_release = msm_fb_release, + .fb_read = NULL, + .fb_write = NULL, + .fb_cursor = NULL, + .fb_check_var = msm_fb_check_var, /* vinfo check */ + .fb_set_par = msm_fb_set_par, /* set the video mode according to info->var */ + .fb_setcolreg = NULL, /* set color register */ + .fb_blank = msm_fb_blank, /* blank display */ + .fb_pan_display = msm_fb_pan_display, /* pan display */ + .fb_fillrect = msm_fb_fillrect, /* Draws a rectangle */ + .fb_copyarea = msm_fb_copyarea, /* Copy data from area to another */ + .fb_imageblit = msm_fb_imageblit, /* Draws a image to the display */ + .fb_rotate = NULL, + .fb_sync = NULL, /* wait for blit idle, optional */ + .fb_ioctl = msm_fb_ioctl, /* perform fb specific ioctl (optional) */ + .fb_mmap = msm_fb_mmap, +}; + +static __u32 msm_fb_line_length(__u32 fb_index, __u32 xres, int bpp) { - struct fb_info *fb; - struct msmfb_info *msmfb; - struct msm_panel_data *panel = pdev->dev.platform_data; - int ret; + /* The adreno GPU hardware requires that the pitch be aligned to + 32 pixels for color buffers, so for the cases where the GPU + is writing directly to fb0, the framebuffer pitch + also needs to be 32 pixel aligned */ + + if (fb_index == 0) + return ALIGN(xres, 32) * bpp; + else + return xres * bpp; +} - if (!panel) { - pr_err("msmfb_probe: no platform data\n"); - return -EINVAL; - } - if (!panel->fb_data) { - pr_err("msmfb_probe: no fb_data\n"); - return -EINVAL; +static int msm_fb_register(struct msm_fb_data_type *mfd) +{ + int ret = -ENODEV; + int bpp; + struct msm_panel_info *panel_info = &mfd->panel_info; + struct fb_info *fbi = mfd->fbi; + struct fb_fix_screeninfo *fix; + struct fb_var_screeninfo *var; + int *id; + int fbram_offset; + + /* + * fb info initialization + */ + fix = &fbi->fix; + var = &fbi->var; + + fix->type_aux = 0; /* if type == FB_TYPE_INTERLEAVED_PLANES */ + fix->visual = FB_VISUAL_TRUECOLOR; /* True Color */ + fix->ywrapstep = 0; /* No support */ + fix->mmio_start = 0; /* No MMIO Address */ + fix->mmio_len = 0; /* No MMIO Address */ + fix->accel = FB_ACCEL_NONE;/* FB_ACCEL_MSM needes to be added in fb.h */ + + var->xoffset = 0, /* Offset from virtual to visible */ + var->yoffset = 0, /* resolution */ + var->grayscale = 0, /* No graylevels */ + var->nonstd = 0, /* standard pixel format */ + var->activate = FB_ACTIVATE_VBL, /* activate it at vsync */ + var->height = -1, /* height of picture in mm */ + var->width = -1, /* width of picture in mm */ + var->accel_flags = 0, /* acceleration flags */ + var->sync = 0, /* see FB_SYNC_* */ + var->rotate = 0, /* angle we rotate counter clockwise */ + mfd->op_enable = FALSE; + + switch (mfd->fb_imgType) { + case MDP_RGB_565: + fix->type = FB_TYPE_PACKED_PIXELS; + fix->xpanstep = 1; + fix->ypanstep = 1; + var->vmode = FB_VMODE_NONINTERLACED; + var->blue.offset = 0; + var->green.offset = 5; + var->red.offset = 11; + var->blue.length = 5; + var->green.length = 6; + var->red.length = 5; + var->blue.msb_right = 0; + var->green.msb_right = 0; + var->red.msb_right = 0; + var->transp.offset = 0; + var->transp.length = 0; + bpp = 2; + break; + + case MDP_RGB_888: + fix->type = FB_TYPE_PACKED_PIXELS; + fix->xpanstep = 1; + fix->ypanstep = 1; + var->vmode = FB_VMODE_NONINTERLACED; + var->blue.offset = 0; + var->green.offset = 8; + var->red.offset = 16; + var->blue.length = 8; + var->green.length = 8; + var->red.length = 8; + var->blue.msb_right = 0; + var->green.msb_right = 0; + var->red.msb_right = 0; + var->transp.offset = 0; + var->transp.length = 0; + bpp = 3; + break; + + case MDP_ARGB_8888: + fix->type = FB_TYPE_PACKED_PIXELS; + fix->xpanstep = 1; + fix->ypanstep = 1; + var->vmode = FB_VMODE_NONINTERLACED; + var->blue.offset = 0; + var->green.offset = 8; + var->red.offset = 16; + var->blue.length = 8; + var->green.length = 8; + var->red.length = 8; + var->blue.msb_right = 0; + var->green.msb_right = 0; + var->red.msb_right = 0; + var->transp.offset = 24; + var->transp.length = 8; + bpp = 4; + break; + + case MDP_RGBA_8888: + fix->type = FB_TYPE_PACKED_PIXELS; + fix->xpanstep = 1; + fix->ypanstep = 1; + var->vmode = FB_VMODE_NONINTERLACED; + var->blue.offset = 8; + var->green.offset = 16; + var->red.offset = 24; + var->blue.length = 8; + var->green.length = 8; + var->red.length = 8; + var->blue.msb_right = 0; + var->green.msb_right = 0; + var->red.msb_right = 0; + var->transp.offset = 0; + var->transp.length = 8; + bpp = 4; + break; + + case MDP_YCRYCB_H2V1: + /* ToDo: need to check TV-Out YUV422i framebuffer format */ + /* we might need to create new type define */ + fix->type = FB_TYPE_INTERLEAVED_PLANES; + fix->xpanstep = 2; + fix->ypanstep = 1; + var->vmode = FB_VMODE_NONINTERLACED; + + /* how about R/G/B offset? */ + var->blue.offset = 0; + var->green.offset = 5; + var->red.offset = 11; + var->blue.length = 5; + var->green.length = 6; + var->red.length = 5; + var->blue.msb_right = 0; + var->green.msb_right = 0; + var->red.msb_right = 0; + var->transp.offset = 0; + var->transp.length = 0; + bpp = 2; + break; + + default: + MSM_FB_ERR("msm_fb_init: fb %d unkown image type!\n", + mfd->index); + return ret; } - fb = framebuffer_alloc(sizeof(struct msmfb_info), &pdev->dev); - if (!fb) - return -ENOMEM; - msmfb = fb->par; - msmfb->fb = fb; - msmfb->panel = panel; - msmfb->xres = panel->fb_data->xres; - msmfb->yres = panel->fb_data->yres; + fix->type = panel_info->is_3d_panel; - ret = setup_fbmem(msmfb, pdev); - if (ret) - goto error_setup_fbmem; + fix->line_length = msm_fb_line_length(mfd->index, panel_info->xres, + bpp); + /* calculate smem_len based on max size of two supplied modes */ + fix->smem_len = roundup(MAX(msm_fb_line_length(mfd->index, + panel_info->xres, + bpp) * + panel_info->yres * mfd->fb_page, + msm_fb_line_length(mfd->index, + panel_info->mode2_xres, + bpp) * + panel_info->mode2_yres * mfd->fb_page), PAGE_SIZE); - setup_fb_info(msmfb); - spin_lock_init(&msmfb->update_lock); - mutex_init(&msmfb->panel_init_lock); - init_waitqueue_head(&msmfb->frame_wq); - INIT_WORK(&msmfb->resume_work, power_on_panel); - msmfb->black = kzalloc(msmfb->fb->var.bits_per_pixel*msmfb->xres, - GFP_KERNEL); - printk(KERN_INFO "msmfb_probe() installing %d x %d panel\n", - msmfb->xres, msmfb->yres); + mfd->var_xres = panel_info->xres; + mfd->var_yres = panel_info->yres; - msmfb->dma_callback.func = msmfb_handle_dma_interrupt; - msmfb->vsync_callback.func = msmfb_handle_vsync_interrupt; - hrtimer_init(&msmfb->fake_vsync, CLOCK_MONOTONIC, - HRTIMER_MODE_REL); + var->pixclock = mfd->panel_info.clk_rate; + mfd->var_pixclock = var->pixclock; + var->xres = panel_info->xres; + var->yres = panel_info->yres; + var->xres_virtual = panel_info->xres; + var->yres_virtual = panel_info->yres * mfd->fb_page; + var->bits_per_pixel = bpp * 8; /* FrameBuffer color depth */ + if (mfd->dest == DISPLAY_LCD) { + var->reserved[4] = panel_info->lcd.refx100 / 100; + } else { + var->reserved[4] = panel_info->clk_rate / + ((panel_info->lcdc.h_back_porch + + panel_info->lcdc.h_front_porch + + panel_info->lcdc.h_pulse_width + + panel_info->xres) * + (panel_info->lcdc.v_back_porch + + panel_info->lcdc.v_front_porch + + panel_info->lcdc.v_pulse_width + + panel_info->yres)); + } + /* + * id field for fb app + */ + id = (int *)&mfd->panel; + +#if defined(CONFIG_FB_MSM_MDP22) + snprintf(fix->id, sizeof(fix->id), "msmfb22_%x", (__u32) *id); +#elif defined(CONFIG_FB_MSM_MDP30) + snprintf(fix->id, sizeof(fix->id), "msmfb30_%x", (__u32) *id); +#elif defined(CONFIG_FB_MSM_MDP31) + snprintf(fix->id, sizeof(fix->id), "msmfb31_%x", (__u32) *id); +#elif defined(CONFIG_FB_MSM_MDP40) + snprintf(fix->id, sizeof(fix->id), "msmfb40_%x", (__u32) *id); +#else + error CONFIG_FB_MSM_MDP undefined ! +#endif + fbi->fbops = &msm_fb_ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = msm_fb_pseudo_palette; + + mfd->ref_cnt = 0; + mfd->sw_currently_refreshing = FALSE; + mfd->sw_refreshing_enable = TRUE; + mfd->panel_power_on = FALSE; + + mfd->pan_waiting = FALSE; + init_completion(&mfd->pan_comp); + init_completion(&mfd->refresher_comp); + sema_init(&mfd->sem, 1); + + init_timer(&mfd->msmfb_no_update_notify_timer); + mfd->msmfb_no_update_notify_timer.function = + msmfb_no_update_notify_timer_cb; + mfd->msmfb_no_update_notify_timer.data = (unsigned long)mfd; + init_completion(&mfd->msmfb_update_notify); + init_completion(&mfd->msmfb_no_update_notify); + + fbram_offset = PAGE_ALIGN((int)fbram)-(int)fbram; + fbram += fbram_offset; + fbram_phys += fbram_offset; + fbram_size -= fbram_offset; + + if (fbram_size < fix->smem_len) { + printk(KERN_ERR "error: no more framebuffer memory!\n"); + return -ENOMEM; + } - msmfb->fake_vsync.function = msmfb_fake_vsync; + fbi->screen_base = fbram; + fbi->fix.smem_start = (unsigned long)fbram_phys; - ret = register_framebuffer(fb); - if (ret) - goto error_register_framebuffer; + memset(fbi->screen_base, 0x0, fix->smem_len); - msmfb->sleeping = WAKING; + mfd->op_enable = TRUE; + mfd->panel_power_on = FALSE; - return 0; + /* cursor memory allocation */ + if (mfd->cursor_update) { + mfd->cursor_buf = dma_alloc_coherent(NULL, + MDP_CURSOR_SIZE, + (dma_addr_t *) &mfd->cursor_buf_phys, + GFP_KERNEL); + if (!mfd->cursor_buf) + mfd->cursor_update = 0; + } -error_register_framebuffer: - iounmap(fb->screen_base); -error_setup_fbmem: - framebuffer_release(msmfb->fb); - return ret; -} + if (mfd->lut_update) { + ret = fb_alloc_cmap(&fbi->cmap, 256, 0); + if (ret) + printk(KERN_ERR "%s: fb_alloc_cmap() failed!\n", + __func__); + } -static struct platform_driver msm_panel_driver = { - /* need to write remove */ - .probe = msmfb_probe, - .driver = {.name = "msm_panel"}, -}; + if (register_framebuffer(fbi) < 0) { + if (mfd->lut_update) + fb_dealloc_cmap(&fbi->cmap); + if (mfd->cursor_buf) + dma_free_coherent(NULL, + MDP_CURSOR_SIZE, + mfd->cursor_buf, + (dma_addr_t) mfd->cursor_buf_phys); -static int msmfb_add_mdp_device(struct device *dev, - struct class_interface *class_intf) -{ - /* might need locking if mulitple mdp devices */ - if (mdp) - return 0; - mdp = container_of(dev, struct mdp_device, dev); - return platform_driver_register(&msm_panel_driver); + mfd->op_enable = FALSE; + return -EPERM; + } + + fbram += fix->smem_len; + fbram_phys += fix->smem_len; + fbram_size -= fix->smem_len; + + MSM_FB_INFO + ("FrameBuffer[%d] %dx%d size=%d bytes is registered successfully!\n", + mfd->index, fbi->var.xres, fbi->var.yres, fbi->fix.smem_len); + +#ifdef CONFIG_FB_MSM_LOGO + if (!load_565rle_image(INIT_IMAGE_FILE)) ; /* Flip buffer */ +#endif + ret = 0; + +#ifdef CONFIG_HAS_EARLYSUSPEND + if (mfd->panel_info.type != DTV_PANEL) { + mfd->early_suspend.suspend = msmfb_early_suspend; + mfd->early_suspend.resume = msmfb_early_resume; + mfd->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB - 2; + register_early_suspend(&mfd->early_suspend); + } +#endif + +#ifdef MSM_FB_ENABLE_DBGFS + { + struct dentry *root; + struct dentry *sub_dir; + char sub_name[2]; + + root = msm_fb_get_debugfs_root(); + if (root != NULL) { + sub_name[0] = (char)(mfd->index + 0x30); + sub_name[1] = '\0'; + sub_dir = debugfs_create_dir(sub_name, root); + } else { + sub_dir = NULL; + } + + mfd->sub_dir = sub_dir; + + if (sub_dir) { + msm_fb_debugfs_file_create(sub_dir, "op_enable", + (u32 *) &mfd->op_enable); + msm_fb_debugfs_file_create(sub_dir, "panel_power_on", + (u32 *) &mfd-> + panel_power_on); + msm_fb_debugfs_file_create(sub_dir, "ref_cnt", + (u32 *) &mfd->ref_cnt); + msm_fb_debugfs_file_create(sub_dir, "fb_imgType", + (u32 *) &mfd->fb_imgType); + msm_fb_debugfs_file_create(sub_dir, + "sw_currently_refreshing", + (u32 *) &mfd-> + sw_currently_refreshing); + msm_fb_debugfs_file_create(sub_dir, + "sw_refreshing_enable", + (u32 *) &mfd-> + sw_refreshing_enable); + + msm_fb_debugfs_file_create(sub_dir, "xres", + (u32 *) &mfd->panel_info. + xres); + msm_fb_debugfs_file_create(sub_dir, "yres", + (u32 *) &mfd->panel_info. + yres); + msm_fb_debugfs_file_create(sub_dir, "bpp", + (u32 *) &mfd->panel_info. + bpp); + msm_fb_debugfs_file_create(sub_dir, "type", + (u32 *) &mfd->panel_info. + type); + msm_fb_debugfs_file_create(sub_dir, "wait_cycle", + (u32 *) &mfd->panel_info. + wait_cycle); + msm_fb_debugfs_file_create(sub_dir, "pdest", + (u32 *) &mfd->panel_info. + pdest); + msm_fb_debugfs_file_create(sub_dir, "backbuff", + (u32 *) &mfd->panel_info. + fb_num); + msm_fb_debugfs_file_create(sub_dir, "clk_rate", + (u32 *) &mfd->panel_info. + clk_rate); + msm_fb_debugfs_file_create(sub_dir, "frame_count", + (u32 *) &mfd->panel_info. + frame_count); + + + switch (mfd->dest) { + case DISPLAY_LCD: + msm_fb_debugfs_file_create(sub_dir, + "vsync_enable", + (u32 *)&mfd->panel_info.lcd.vsync_enable); + msm_fb_debugfs_file_create(sub_dir, + "refx100", + (u32 *) &mfd->panel_info.lcd. refx100); + msm_fb_debugfs_file_create(sub_dir, + "v_back_porch", + (u32 *) &mfd->panel_info.lcd.v_back_porch); + msm_fb_debugfs_file_create(sub_dir, + "v_front_porch", + (u32 *) &mfd->panel_info.lcd.v_front_porch); + msm_fb_debugfs_file_create(sub_dir, + "v_pulse_width", + (u32 *) &mfd->panel_info.lcd.v_pulse_width); + msm_fb_debugfs_file_create(sub_dir, + "hw_vsync_mode", + (u32 *) &mfd->panel_info.lcd.hw_vsync_mode); + msm_fb_debugfs_file_create(sub_dir, + "vsync_notifier_period", (u32 *) + &mfd->panel_info.lcd.vsync_notifier_period); + break; + + case DISPLAY_LCDC: + msm_fb_debugfs_file_create(sub_dir, + "h_back_porch", + (u32 *) &mfd->panel_info.lcdc.h_back_porch); + msm_fb_debugfs_file_create(sub_dir, + "h_front_porch", + (u32 *) &mfd->panel_info.lcdc.h_front_porch); + msm_fb_debugfs_file_create(sub_dir, + "h_pulse_width", + (u32 *) &mfd->panel_info.lcdc.h_pulse_width); + msm_fb_debugfs_file_create(sub_dir, + "v_back_porch", + (u32 *) &mfd->panel_info.lcdc.v_back_porch); + msm_fb_debugfs_file_create(sub_dir, + "v_front_porch", + (u32 *) &mfd->panel_info.lcdc.v_front_porch); + msm_fb_debugfs_file_create(sub_dir, + "v_pulse_width", + (u32 *) &mfd->panel_info.lcdc.v_pulse_width); + msm_fb_debugfs_file_create(sub_dir, + "border_clr", + (u32 *) &mfd->panel_info.lcdc.border_clr); + msm_fb_debugfs_file_create(sub_dir, + "underflow_clr", + (u32 *) &mfd->panel_info.lcdc.underflow_clr); + msm_fb_debugfs_file_create(sub_dir, + "hsync_skew", + (u32 *) &mfd->panel_info.lcdc.hsync_skew); + break; + + default: + break; + } + } + } +#endif /* MSM_FB_ENABLE_DBGFS */ + + return ret; } -static void msmfb_remove_mdp_device(struct device *dev, - struct class_interface *class_intf) +static int msm_fb_open(struct fb_info *info, int user) { - /* might need locking if mulitple mdp devices */ - if (dev != &mdp->dev) + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + int result; + + result = pm_runtime_get_sync(info->dev); + + if (result < 0) { + printk(KERN_ERR "pm_runtime: fail to wake up\n"); + } + + + if (!mfd->ref_cnt) { + mdp_set_dma_pan_info(info, NULL, TRUE); + + if (msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable)) { + printk(KERN_ERR "msm_fb_open: can't turn on display!\n"); + return -1; + } + } + + mfd->ref_cnt++; + return 0; +} + +static int msm_fb_release(struct fb_info *info, int user) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + int ret = 0; + + if (!mfd->ref_cnt) { + MSM_FB_INFO("msm_fb_release: try to close unopened fb %d!\n", + mfd->index); + return -EINVAL; + } + + mfd->ref_cnt--; + + if (!mfd->ref_cnt) { + if ((ret = + msm_fb_blank_sub(FB_BLANK_POWERDOWN, info, + mfd->op_enable)) != 0) { + printk(KERN_ERR "msm_fb_release: can't turn off display!\n"); + return ret; + } + } + + pm_runtime_put(info->dev); + return ret; +} + +DEFINE_SEMAPHORE(msm_fb_pan_sem); + +static int msm_fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct mdp_dirty_region dirty; + struct mdp_dirty_region *dirtyPtr = NULL; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + + if ((!mfd->op_enable) || (!mfd->panel_power_on)) + return -EPERM; + + if (var->xoffset > (info->var.xres_virtual - info->var.xres)) + return -EINVAL; + + if (var->yoffset > (info->var.yres_virtual - info->var.yres)) + return -EINVAL; + + if (info->fix.xpanstep) + info->var.xoffset = + (var->xoffset / info->fix.xpanstep) * info->fix.xpanstep; + + if (info->fix.ypanstep) + info->var.yoffset = + (var->yoffset / info->fix.ypanstep) * info->fix.ypanstep; + + /* "UPDT" */ + if (var->reserved[0] == 0x54445055) { + dirty.xoffset = var->reserved[1] & 0xffff; + dirty.yoffset = (var->reserved[1] >> 16) & 0xffff; + + if ((var->reserved[2] & 0xffff) <= dirty.xoffset) + return -EINVAL; + if (((var->reserved[2] >> 16) & 0xffff) <= dirty.yoffset) + return -EINVAL; + + dirty.width = (var->reserved[2] & 0xffff) - dirty.xoffset; + dirty.height = + ((var->reserved[2] >> 16) & 0xffff) - dirty.yoffset; + info->var.yoffset = var->yoffset; + + if (dirty.xoffset < 0) + return -EINVAL; + + if (dirty.yoffset < 0) + return -EINVAL; + + if ((dirty.xoffset + dirty.width) > info->var.xres) + return -EINVAL; + + if ((dirty.yoffset + dirty.height) > info->var.yres) + return -EINVAL; + + if ((dirty.width <= 0) || (dirty.height <= 0)) + return -EINVAL; + + dirtyPtr = &dirty; + } + complete(&mfd->msmfb_update_notify); + mutex_lock(&msm_fb_notify_update_sem); + if (mfd->msmfb_no_update_notify_timer.function) + del_timer(&mfd->msmfb_no_update_notify_timer); + + mfd->msmfb_no_update_notify_timer.expires = + jiffies + ((1000 * HZ) / 1000); + add_timer(&mfd->msmfb_no_update_notify_timer); + mutex_unlock(&msm_fb_notify_update_sem); + + down(&msm_fb_pan_sem); + mdp_set_dma_pan_info(info, dirtyPtr, + (var->activate == FB_ACTIVATE_VBL)); + mdp_dma_pan_update(info); + up(&msm_fb_pan_sem); + + ++mfd->panel_info.frame_count; + return 0; +} + +static int msm_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + + if (var->rotate != FB_ROTATE_UR) + return -EINVAL; + if (var->grayscale != info->var.grayscale) + return -EINVAL; + + switch (var->bits_per_pixel) { + case 16: + if ((var->green.offset != 5) || + !((var->blue.offset == 11) + || (var->blue.offset == 0)) || + !((var->red.offset == 11) + || (var->red.offset == 0)) || + (var->blue.length != 5) || + (var->green.length != 6) || + (var->red.length != 5) || + (var->blue.msb_right != 0) || + (var->green.msb_right != 0) || + (var->red.msb_right != 0) || + (var->transp.offset != 0) || + (var->transp.length != 0)) + return -EINVAL; + break; + + case 24: + if ((var->blue.offset != 0) || + (var->green.offset != 8) || + (var->red.offset != 16) || + (var->blue.length != 8) || + (var->green.length != 8) || + (var->red.length != 8) || + (var->blue.msb_right != 0) || + (var->green.msb_right != 0) || + (var->red.msb_right != 0) || + !(((var->transp.offset == 0) && + (var->transp.length == 0)) || + ((var->transp.offset == 24) && + (var->transp.length == 8)))) + return -EINVAL; + break; + + case 32: + /* Figure out if the user meant RGBA or ARGB + and verify the position of the RGB components */ + + if (var->transp.offset == 24) { + if ((var->blue.offset != 0) || + (var->green.offset != 8) || + (var->red.offset != 16)) + return -EINVAL; + } else if (var->transp.offset == 0) { + if ((var->blue.offset != 8) || + (var->green.offset != 16) || + (var->red.offset != 24)) + return -EINVAL; + } else + return -EINVAL; + + /* Check the common values for both RGBA and ARGB */ + + if ((var->blue.length != 8) || + (var->green.length != 8) || + (var->red.length != 8) || + (var->transp.length != 8) || + (var->blue.msb_right != 0) || + (var->green.msb_right != 0) || + (var->red.msb_right != 0)) + return -EINVAL; + + break; + + default: + return -EINVAL; + } + + if ((var->xres_virtual <= 0) || (var->yres_virtual <= 0)) + return -EINVAL; + + if (info->fix.smem_len < + (var->xres_virtual*var->yres_virtual*(var->bits_per_pixel/8))) + return -EINVAL; + + if ((var->xres == 0) || (var->yres == 0)) + return -EINVAL; + + if ((var->xres > MAX(mfd->panel_info.xres, + mfd->panel_info.mode2_xres)) || + (var->yres > MAX(mfd->panel_info.yres, + mfd->panel_info.mode2_yres))) + return -EINVAL; + + if (var->xoffset > (var->xres_virtual - var->xres)) + return -EINVAL; + + if (var->yoffset > (var->yres_virtual - var->yres)) + return -EINVAL; + + return 0; +} + +static int msm_fb_set_par(struct fb_info *info) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct fb_var_screeninfo *var = &info->var; + int old_imgType; + int blank = 0; + + old_imgType = mfd->fb_imgType; + switch (var->bits_per_pixel) { + case 16: + if (var->red.offset == 0) + mfd->fb_imgType = MDP_BGR_565; + else + mfd->fb_imgType = MDP_RGB_565; + break; + + case 24: + if ((var->transp.offset == 0) && (var->transp.length == 0)) + mfd->fb_imgType = MDP_RGB_888; + else if ((var->transp.offset == 24) && + (var->transp.length == 8)) { + mfd->fb_imgType = MDP_ARGB_8888; + info->var.bits_per_pixel = 32; + } + break; + + case 32: + if (var->transp.offset == 24) + mfd->fb_imgType = MDP_ARGB_8888; + else + mfd->fb_imgType = MDP_RGBA_8888; + break; + + default: + return -EINVAL; + } + + if ((mfd->var_pixclock != var->pixclock) || + (mfd->hw_refresh && ((mfd->fb_imgType != old_imgType) || + (mfd->var_pixclock != var->pixclock) || + (mfd->var_xres != var->xres) || + (mfd->var_yres != var->yres)))) { + mfd->var_xres = var->xres; + mfd->var_yres = var->yres; + mfd->var_pixclock = var->pixclock; + blank = 1; + } + mfd->fbi->fix.line_length = msm_fb_line_length(mfd->index, var->xres, + var->bits_per_pixel/8); + + if (blank) { + msm_fb_blank_sub(FB_BLANK_POWERDOWN, info, mfd->op_enable); + msm_fb_blank_sub(FB_BLANK_UNBLANK, info, mfd->op_enable); + } + + return 0; +} + +static int msm_fb_stop_sw_refresher(struct msm_fb_data_type *mfd) +{ + if (mfd->hw_refresh) + return -EPERM; + + if (mfd->sw_currently_refreshing) { + down(&mfd->sem); + mfd->sw_currently_refreshing = FALSE; + up(&mfd->sem); + + /* wait until the refresher finishes the last job */ + wait_for_completion_killable(&mfd->refresher_comp); + } + + return 0; +} + +int msm_fb_resume_sw_refresher(struct msm_fb_data_type *mfd) +{ + boolean do_refresh; + + if (mfd->hw_refresh) + return -EPERM; + + down(&mfd->sem); + if ((!mfd->sw_currently_refreshing) && (mfd->sw_refreshing_enable)) { + do_refresh = TRUE; + mfd->sw_currently_refreshing = TRUE; + } else { + do_refresh = FALSE; + } + up(&mfd->sem); + + if (do_refresh) + mdp_refresh_screen((unsigned long)mfd); + + return 0; +} + +#if defined CONFIG_FB_MSM_MDP31 +static int mdp_blit_split_height(struct fb_info *info, + struct mdp_blit_req *req) +{ + int ret; + struct mdp_blit_req splitreq; + int s_x_0, s_x_1, s_w_0, s_w_1, s_y_0, s_y_1, s_h_0, s_h_1; + int d_x_0, d_x_1, d_w_0, d_w_1, d_y_0, d_y_1, d_h_0, d_h_1; + + splitreq = *req; + /* break dest roi at height*/ + d_x_0 = d_x_1 = req->dst_rect.x; + d_w_0 = d_w_1 = req->dst_rect.w; + d_y_0 = req->dst_rect.y; + if (req->dst_rect.h % 32 == 3) + d_h_1 = (req->dst_rect.h - 3) / 2 - 1; + else if (req->dst_rect.h % 32 == 2) + d_h_1 = (req->dst_rect.h - 2) / 2 - 6; + else + d_h_1 = (req->dst_rect.h - 1) / 2 - 1; + d_h_0 = req->dst_rect.h - d_h_1; + d_y_1 = d_y_0 + d_h_0; + if (req->dst_rect.h == 3) { + d_h_1 = 2; + d_h_0 = 2; + d_y_1 = d_y_0 + 1; + } + + /* blit first region */ + if (((splitreq.flags & 0x07) == 0x04) || + ((splitreq.flags & 0x07) == 0x0)) { + + if (splitreq.flags & MDP_ROT_90) { + s_y_0 = s_y_1 = req->src_rect.y; + s_h_0 = s_h_1 = req->src_rect.h; + s_x_0 = req->src_rect.x; + s_w_1 = (req->src_rect.w * d_h_1) / req->dst_rect.h; + s_w_0 = req->src_rect.w - s_w_1; + s_x_1 = s_x_0 + s_w_0; + if (d_h_1 >= 8 * s_w_1) { + s_w_1++; + s_x_1--; + } + } else { + s_x_0 = s_x_1 = req->src_rect.x; + s_w_0 = s_w_1 = req->src_rect.w; + s_y_0 = req->src_rect.y; + s_h_1 = (req->src_rect.h * d_h_1) / req->dst_rect.h; + s_h_0 = req->src_rect.h - s_h_1; + s_y_1 = s_y_0 + s_h_0; + if (d_h_1 >= 8 * s_h_1) { + s_h_1++; + s_y_1--; + } + } + + splitreq.src_rect.h = s_h_0; + splitreq.src_rect.y = s_y_0; + splitreq.dst_rect.h = d_h_0; + splitreq.dst_rect.y = d_y_0; + splitreq.src_rect.x = s_x_0; + splitreq.src_rect.w = s_w_0; + splitreq.dst_rect.x = d_x_0; + splitreq.dst_rect.w = d_w_0; + } else { + + if (splitreq.flags & MDP_ROT_90) { + s_y_0 = s_y_1 = req->src_rect.y; + s_h_0 = s_h_1 = req->src_rect.h; + s_x_0 = req->src_rect.x; + s_w_1 = (req->src_rect.w * d_h_0) / req->dst_rect.h; + s_w_0 = req->src_rect.w - s_w_1; + s_x_1 = s_x_0 + s_w_0; + if (d_h_0 >= 8 * s_w_1) { + s_w_1++; + s_x_1--; + } + } else { + s_x_0 = s_x_1 = req->src_rect.x; + s_w_0 = s_w_1 = req->src_rect.w; + s_y_0 = req->src_rect.y; + s_h_1 = (req->src_rect.h * d_h_0) / req->dst_rect.h; + s_h_0 = req->src_rect.h - s_h_1; + s_y_1 = s_y_0 + s_h_0; + if (d_h_0 >= 8 * s_h_1) { + s_h_1++; + s_y_1--; + } + } + splitreq.src_rect.h = s_h_0; + splitreq.src_rect.y = s_y_0; + splitreq.dst_rect.h = d_h_1; + splitreq.dst_rect.y = d_y_1; + splitreq.src_rect.x = s_x_0; + splitreq.src_rect.w = s_w_0; + splitreq.dst_rect.x = d_x_1; + splitreq.dst_rect.w = d_w_1; + } + ret = mdp_ppp_blit(info, &splitreq); + if (ret) + return ret; + + /* blit second region */ + if (((splitreq.flags & 0x07) == 0x04) || + ((splitreq.flags & 0x07) == 0x0)) { + splitreq.src_rect.h = s_h_1; + splitreq.src_rect.y = s_y_1; + splitreq.dst_rect.h = d_h_1; + splitreq.dst_rect.y = d_y_1; + splitreq.src_rect.x = s_x_1; + splitreq.src_rect.w = s_w_1; + splitreq.dst_rect.x = d_x_1; + splitreq.dst_rect.w = d_w_1; + } else { + splitreq.src_rect.h = s_h_1; + splitreq.src_rect.y = s_y_1; + splitreq.dst_rect.h = d_h_0; + splitreq.dst_rect.y = d_y_0; + splitreq.src_rect.x = s_x_1; + splitreq.src_rect.w = s_w_1; + splitreq.dst_rect.x = d_x_0; + splitreq.dst_rect.w = d_w_0; + } + ret = mdp_ppp_blit(info, &splitreq); + return ret; +} +#endif + +int mdp_blit(struct fb_info *info, struct mdp_blit_req *req) +{ + int ret; +#if defined CONFIG_FB_MSM_MDP31 || defined CONFIG_FB_MSM_MDP30 + unsigned int remainder = 0, is_bpp_4 = 0; + struct mdp_blit_req splitreq; + int s_x_0, s_x_1, s_w_0, s_w_1, s_y_0, s_y_1, s_h_0, s_h_1; + int d_x_0, d_x_1, d_w_0, d_w_1, d_y_0, d_y_1, d_h_0, d_h_1; + + if (req->flags & MDP_ROT_90) { + if (((req->dst_rect.h == 1) && ((req->src_rect.w != 1) || + (req->dst_rect.w != req->src_rect.h))) || + ((req->dst_rect.w == 1) && ((req->src_rect.h != 1) || + (req->dst_rect.h != req->src_rect.w)))) { + printk(KERN_ERR "mpd_ppp: error scaling when size is 1!\n"); + return -EINVAL; + } + } else { + if (((req->dst_rect.w == 1) && ((req->src_rect.w != 1) || + (req->dst_rect.h != req->src_rect.h))) || + ((req->dst_rect.h == 1) && ((req->src_rect.h != 1) || + (req->dst_rect.w != req->src_rect.w)))) { + printk(KERN_ERR "mpd_ppp: error scaling when size is 1!\n"); + return -EINVAL; + } + } +#endif + if (unlikely(req->src_rect.h == 0 || req->src_rect.w == 0)) { + printk(KERN_ERR "mpd_ppp: src img of zero size!\n"); + return -EINVAL; + } + if (unlikely(req->dst_rect.h == 0 || req->dst_rect.w == 0)) + return 0; + +#if defined CONFIG_FB_MSM_MDP31 + /* MDP width split workaround */ + remainder = (req->dst_rect.w)%32; + ret = mdp_get_bytes_per_pixel(req->dst.format, + (struct msm_fb_data_type *)info->par); + if (ret <= 0) { + printk(KERN_ERR "mdp_ppp: incorrect bpp!\n"); + return -EINVAL; + } + is_bpp_4 = (ret == 4) ? 1 : 0; + + if ((is_bpp_4 && (remainder == 6 || remainder == 14 || + remainder == 22 || remainder == 30)) || remainder == 3 || + (remainder == 1 && req->dst_rect.w != 1) || + (remainder == 2 && req->dst_rect.w != 2)) { + /* make new request as provide by user */ + splitreq = *req; + + /* break dest roi at width*/ + d_y_0 = d_y_1 = req->dst_rect.y; + d_h_0 = d_h_1 = req->dst_rect.h; + d_x_0 = req->dst_rect.x; + + if (remainder == 14) + d_w_1 = (req->dst_rect.w - 14) / 2 + 4; + else if (remainder == 22) + d_w_1 = (req->dst_rect.w - 22) / 2 + 10; + else if (remainder == 30) + d_w_1 = (req->dst_rect.w - 30) / 2 + 10; + else if (remainder == 6) + d_w_1 = req->dst_rect.w / 2 - 1; + else if (remainder == 3) + d_w_1 = (req->dst_rect.w - 3) / 2 - 1; + else if (remainder == 2) + d_w_1 = (req->dst_rect.w - 2) / 2 - 6; + else + d_w_1 = (req->dst_rect.w - 1) / 2 - 1; + d_w_0 = req->dst_rect.w - d_w_1; + d_x_1 = d_x_0 + d_w_0; + if (req->dst_rect.w == 3) { + d_w_1 = 2; + d_w_0 = 2; + d_x_1 = d_x_0 + 1; + } + + /* blit first region */ + if (((splitreq.flags & 0x07) == 0x07) || + ((splitreq.flags & 0x07) == 0x0)) { + + if (splitreq.flags & MDP_ROT_90) { + s_x_0 = s_x_1 = req->src_rect.x; + s_w_0 = s_w_1 = req->src_rect.w; + s_y_0 = req->src_rect.y; + s_h_1 = (req->src_rect.h * d_w_1) / + req->dst_rect.w; + s_h_0 = req->src_rect.h - s_h_1; + s_y_1 = s_y_0 + s_h_0; + if (d_w_1 >= 8 * s_h_1) { + s_h_1++; + s_y_1--; + } + } else { + s_y_0 = s_y_1 = req->src_rect.y; + s_h_0 = s_h_1 = req->src_rect.h; + s_x_0 = req->src_rect.x; + s_w_1 = (req->src_rect.w * d_w_1) / + req->dst_rect.w; + s_w_0 = req->src_rect.w - s_w_1; + s_x_1 = s_x_0 + s_w_0; + if (d_w_1 >= 8 * s_w_1) { + s_w_1++; + s_x_1--; + } + } + + splitreq.src_rect.h = s_h_0; + splitreq.src_rect.y = s_y_0; + splitreq.dst_rect.h = d_h_0; + splitreq.dst_rect.y = d_y_0; + splitreq.src_rect.x = s_x_0; + splitreq.src_rect.w = s_w_0; + splitreq.dst_rect.x = d_x_0; + splitreq.dst_rect.w = d_w_0; + } else { + if (splitreq.flags & MDP_ROT_90) { + s_x_0 = s_x_1 = req->src_rect.x; + s_w_0 = s_w_1 = req->src_rect.w; + s_y_0 = req->src_rect.y; + s_h_1 = (req->src_rect.h * d_w_0) / + req->dst_rect.w; + s_h_0 = req->src_rect.h - s_h_1; + s_y_1 = s_y_0 + s_h_0; + if (d_w_0 >= 8 * s_h_1) { + s_h_1++; + s_y_1--; + } + } else { + s_y_0 = s_y_1 = req->src_rect.y; + s_h_0 = s_h_1 = req->src_rect.h; + s_x_0 = req->src_rect.x; + s_w_1 = (req->src_rect.w * d_w_0) / + req->dst_rect.w; + s_w_0 = req->src_rect.w - s_w_1; + s_x_1 = s_x_0 + s_w_0; + if (d_w_0 >= 8 * s_w_1) { + s_w_1++; + s_x_1--; + } + } + splitreq.src_rect.h = s_h_0; + splitreq.src_rect.y = s_y_0; + splitreq.dst_rect.h = d_h_1; + splitreq.dst_rect.y = d_y_1; + splitreq.src_rect.x = s_x_0; + splitreq.src_rect.w = s_w_0; + splitreq.dst_rect.x = d_x_1; + splitreq.dst_rect.w = d_w_1; + } + + if ((splitreq.dst_rect.h % 32 == 3) || + ((req->dst_rect.h % 32) == 1 && req->dst_rect.h != 1) || + ((req->dst_rect.h % 32) == 2 && req->dst_rect.h != 2)) + ret = mdp_blit_split_height(info, &splitreq); + else + ret = mdp_ppp_blit(info, &splitreq); + if (ret) + return ret; + /* blit second region */ + if (((splitreq.flags & 0x07) == 0x07) || + ((splitreq.flags & 0x07) == 0x0)) { + splitreq.src_rect.h = s_h_1; + splitreq.src_rect.y = s_y_1; + splitreq.dst_rect.h = d_h_1; + splitreq.dst_rect.y = d_y_1; + splitreq.src_rect.x = s_x_1; + splitreq.src_rect.w = s_w_1; + splitreq.dst_rect.x = d_x_1; + splitreq.dst_rect.w = d_w_1; + } else { + splitreq.src_rect.h = s_h_1; + splitreq.src_rect.y = s_y_1; + splitreq.dst_rect.h = d_h_0; + splitreq.dst_rect.y = d_y_0; + splitreq.src_rect.x = s_x_1; + splitreq.src_rect.w = s_w_1; + splitreq.dst_rect.x = d_x_0; + splitreq.dst_rect.w = d_w_0; + } + if (((splitreq.dst_rect.h % 32) == 3) || + ((req->dst_rect.h % 32) == 1 && req->dst_rect.h != 1) || + ((req->dst_rect.h % 32) == 2 && req->dst_rect.h != 2)) + ret = mdp_blit_split_height(info, &splitreq); + else + ret = mdp_ppp_blit(info, &splitreq); + if (ret) + return ret; + } else if ((req->dst_rect.h % 32) == 3 || + ((req->dst_rect.h % 32) == 1 && req->dst_rect.h != 1) || + ((req->dst_rect.h % 32) == 2 && req->dst_rect.h != 2)) + ret = mdp_blit_split_height(info, req); + else + ret = mdp_ppp_blit(info, req); + return ret; +#elif defined CONFIG_FB_MSM_MDP30 + /* MDP width split workaround */ + remainder = (req->dst_rect.w)%16; + ret = mdp_get_bytes_per_pixel(req->dst.format, + (struct msm_fb_data_type *)info->par); + if (ret <= 0) { + printk(KERN_ERR "mdp_ppp: incorrect bpp!\n"); + return -EINVAL; + } + is_bpp_4 = (ret == 4) ? 1 : 0; + + if ((is_bpp_4 && (remainder == 6 || remainder == 14))) { + + /* make new request as provide by user */ + splitreq = *req; + + /* break dest roi at width*/ + d_y_0 = d_y_1 = req->dst_rect.y; + d_h_0 = d_h_1 = req->dst_rect.h; + d_x_0 = req->dst_rect.x; + + if (remainder == 14 || remainder == 6) + d_w_1 = req->dst_rect.w / 2; + else + d_w_1 = (req->dst_rect.w - 1) / 2 - 1; + + d_w_0 = req->dst_rect.w - d_w_1; + d_x_1 = d_x_0 + d_w_0; + + /* blit first region */ + if (((splitreq.flags & 0x07) == 0x07) || + ((splitreq.flags & 0x07) == 0x0)) { + + if (splitreq.flags & MDP_ROT_90) { + s_x_0 = s_x_1 = req->src_rect.x; + s_w_0 = s_w_1 = req->src_rect.w; + s_y_0 = req->src_rect.y; + s_h_1 = (req->src_rect.h * d_w_1) / + req->dst_rect.w; + s_h_0 = req->src_rect.h - s_h_1; + s_y_1 = s_y_0 + s_h_0; + if (d_w_1 >= 8 * s_h_1) { + s_h_1++; + s_y_1--; + } + } else { + s_y_0 = s_y_1 = req->src_rect.y; + s_h_0 = s_h_1 = req->src_rect.h; + s_x_0 = req->src_rect.x; + s_w_1 = (req->src_rect.w * d_w_1) / + req->dst_rect.w; + s_w_0 = req->src_rect.w - s_w_1; + s_x_1 = s_x_0 + s_w_0; + if (d_w_1 >= 8 * s_w_1) { + s_w_1++; + s_x_1--; + } + } + + splitreq.src_rect.h = s_h_0; + splitreq.src_rect.y = s_y_0; + splitreq.dst_rect.h = d_h_0; + splitreq.dst_rect.y = d_y_0; + splitreq.src_rect.x = s_x_0; + splitreq.src_rect.w = s_w_0; + splitreq.dst_rect.x = d_x_0; + splitreq.dst_rect.w = d_w_0; + } else { + if (splitreq.flags & MDP_ROT_90) { + s_x_0 = s_x_1 = req->src_rect.x; + s_w_0 = s_w_1 = req->src_rect.w; + s_y_0 = req->src_rect.y; + s_h_1 = (req->src_rect.h * d_w_0) / + req->dst_rect.w; + s_h_0 = req->src_rect.h - s_h_1; + s_y_1 = s_y_0 + s_h_0; + if (d_w_0 >= 8 * s_h_1) { + s_h_1++; + s_y_1--; + } + } else { + s_y_0 = s_y_1 = req->src_rect.y; + s_h_0 = s_h_1 = req->src_rect.h; + s_x_0 = req->src_rect.x; + s_w_1 = (req->src_rect.w * d_w_0) / + req->dst_rect.w; + s_w_0 = req->src_rect.w - s_w_1; + s_x_1 = s_x_0 + s_w_0; + if (d_w_0 >= 8 * s_w_1) { + s_w_1++; + s_x_1--; + } + } + splitreq.src_rect.h = s_h_0; + splitreq.src_rect.y = s_y_0; + splitreq.dst_rect.h = d_h_1; + splitreq.dst_rect.y = d_y_1; + splitreq.src_rect.x = s_x_0; + splitreq.src_rect.w = s_w_0; + splitreq.dst_rect.x = d_x_1; + splitreq.dst_rect.w = d_w_1; + } + + /* No need to split in height */ + ret = mdp_ppp_blit(info, &splitreq); + + if (ret) + return ret; + + /* blit second region */ + if (((splitreq.flags & 0x07) == 0x07) || + ((splitreq.flags & 0x07) == 0x0)) { + splitreq.src_rect.h = s_h_1; + splitreq.src_rect.y = s_y_1; + splitreq.dst_rect.h = d_h_1; + splitreq.dst_rect.y = d_y_1; + splitreq.src_rect.x = s_x_1; + splitreq.src_rect.w = s_w_1; + splitreq.dst_rect.x = d_x_1; + splitreq.dst_rect.w = d_w_1; + } else { + splitreq.src_rect.h = s_h_1; + splitreq.src_rect.y = s_y_1; + splitreq.dst_rect.h = d_h_0; + splitreq.dst_rect.y = d_y_0; + splitreq.src_rect.x = s_x_1; + splitreq.src_rect.w = s_w_1; + splitreq.dst_rect.x = d_x_0; + splitreq.dst_rect.w = d_w_0; + } + + /* No need to split in height ... just width */ + ret = mdp_ppp_blit(info, &splitreq); + + if (ret) + return ret; + + } else + ret = mdp_ppp_blit(info, req); + return ret; +#else + ret = mdp_ppp_blit(info, req); + return ret; +#endif +} + +typedef void (*msm_dma_barrier_function_pointer) (void *, size_t); + +static inline void msm_fb_dma_barrier_for_rect(struct fb_info *info, + struct mdp_img *img, struct mdp_rect *rect, + msm_dma_barrier_function_pointer dma_barrier_fp + ) +{ + /* + * Compute the start and end addresses of the rectangles. + * NOTE: As currently implemented, the data between + * the end of one row and the start of the next is + * included in the address range rather than + * doing multiple calls for each row. + */ + unsigned long start; + size_t size; + char * const pmem_start = info->screen_base; + int bytes_per_pixel = mdp_get_bytes_per_pixel(img->format, + (struct msm_fb_data_type *)info->par); + if (bytes_per_pixel <= 0) { + printk(KERN_ERR "%s incorrect bpp!\n", __func__); return; - platform_driver_unregister(&msm_panel_driver); - mdp = NULL; + } + start = (unsigned long)pmem_start + img->offset + + (img->width * rect->y + rect->x) * bytes_per_pixel; + size = (rect->h * img->width + rect->w) * bytes_per_pixel; + (*dma_barrier_fp) ((void *) start, size); + } -static struct class_interface msm_fb_interface = { - .add_dev = &msmfb_add_mdp_device, - .remove_dev = &msmfb_remove_mdp_device, -}; +static inline void msm_dma_nc_pre(void) +{ + dmb(); +} +static inline void msm_dma_wt_pre(void) +{ + dmb(); +} +static inline void msm_dma_todevice_wb_pre(void *start, size_t size) +{ + dma_cache_pre_ops(start, size, DMA_TO_DEVICE); +} + +static inline void msm_dma_fromdevice_wb_pre(void *start, size_t size) +{ + dma_cache_pre_ops(start, size, DMA_FROM_DEVICE); +} + +static inline void msm_dma_nc_post(void) +{ + dmb(); +} + +static inline void msm_dma_fromdevice_wt_post(void *start, size_t size) +{ + dma_cache_post_ops(start, size, DMA_FROM_DEVICE); +} + +static inline void msm_dma_todevice_wb_post(void *start, size_t size) +{ + dma_cache_post_ops(start, size, DMA_TO_DEVICE); +} + +static inline void msm_dma_fromdevice_wb_post(void *start, size_t size) +{ + dma_cache_post_ops(start, size, DMA_FROM_DEVICE); +} + +/* + * Do the write barriers required to guarantee data is committed to RAM + * (from CPU cache or internal buffers) before a DMA operation starts. + * NOTE: As currently implemented, the data between + * the end of one row and the start of the next is + * included in the address range rather than + * doing multiple calls for each row. +*/ +static void msm_fb_ensure_memory_coherency_before_dma(struct fb_info *info, + struct mdp_blit_req *req_list, + int req_list_count) +{ +#ifdef CONFIG_ARCH_QSD8X50 + int i; + + /* + * Normally, do the requested barriers for each address + * range that corresponds to a rectangle. + * + * But if at least one write barrier is requested for data + * going to or from the device but no address range is + * needed for that barrier, then do the barrier, but do it + * only once, no matter how many requests there are. + */ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + switch (mfd->mdp_fb_page_protection) { + default: + case MDP_FB_PAGE_PROTECTION_NONCACHED: + case MDP_FB_PAGE_PROTECTION_WRITECOMBINE: + /* + * The following barrier is only done at most once, + * since further calls would be redundant. + */ + for (i = 0; i < req_list_count; i++) { + if (!(req_list[i].flags + & MDP_NO_DMA_BARRIER_START)) { + msm_dma_nc_pre(); + break; + } + } + break; + + case MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE: + /* + * The following barrier is only done at most once, + * since further calls would be redundant. + */ + for (i = 0; i < req_list_count; i++) { + if (!(req_list[i].flags + & MDP_NO_DMA_BARRIER_START)) { + msm_dma_wt_pre(); + break; + } + } + break; + + case MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE: + case MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE: + for (i = 0; i < req_list_count; i++) { + if (!(req_list[i].flags & + MDP_NO_DMA_BARRIER_START)) { + + msm_fb_dma_barrier_for_rect(info, + &(req_list[i].src), + &(req_list[i].src_rect), + msm_dma_todevice_wb_pre + ); + + msm_fb_dma_barrier_for_rect(info, + &(req_list[i].dst), + &(req_list[i].dst_rect), + msm_dma_todevice_wb_pre + ); + } + } + break; + } +#else + dmb(); +#endif +} + + +/* + * Do the write barriers required to guarantee data will be re-read from RAM by + * the CPU after a DMA operation ends. + * NOTE: As currently implemented, the data between + * the end of one row and the start of the next is + * included in the address range rather than + * doing multiple calls for each row. +*/ +static void msm_fb_ensure_memory_coherency_after_dma(struct fb_info *info, + struct mdp_blit_req *req_list, + int req_list_count) +{ +#ifdef CONFIG_ARCH_QSD8X50 + int i; -static int __init msmfb_init(void) + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + switch (mfd->mdp_fb_page_protection) { + default: + case MDP_FB_PAGE_PROTECTION_NONCACHED: + case MDP_FB_PAGE_PROTECTION_WRITECOMBINE: + /* + * The following barrier is only done at most once, + * since further calls would be redundant. + */ + for (i = 0; i < req_list_count; i++) { + if (!(req_list[i].flags + & MDP_NO_DMA_BARRIER_END)) { + msm_dma_nc_post(); + break; + } + } + break; + + case MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE: + for (i = 0; i < req_list_count; i++) { + if (!(req_list[i].flags & + MDP_NO_DMA_BARRIER_END)) { + + msm_fb_dma_barrier_for_rect(info, + &(req_list[i].dst), + &(req_list[i].dst_rect), + msm_dma_fromdevice_wt_post + ); + } + } + break; + case MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE: + case MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE: + for (i = 0; i < req_list_count; i++) { + if (!(req_list[i].flags & + MDP_NO_DMA_BARRIER_END)) { + + msm_fb_dma_barrier_for_rect(info, + &(req_list[i].dst), + &(req_list[i].dst_rect), + msm_dma_fromdevice_wb_post + ); + } + } + break; + } +#else + dmb(); +#endif +} + +/* + * NOTE: The userspace issues blit operations in a sequence, the sequence + * start with a operation marked START and ends in an operation marked + * END. It is guranteed by the userspace that all the blit operations + * between START and END are only within the regions of areas designated + * by the START and END operations and that the userspace doesnt modify + * those areas. Hence it would be enough to perform barrier/cache operations + * only on the START and END operations. + */ +static int msmfb_blit(struct fb_info *info, void __user *p) +{ + /* + * CAUTION: The names of the struct types intentionally *DON'T* match + * the names of the variables declared -- they appear to be swapped. + * Read the code carefully and you should see that the variable names + * make sense. + */ + const int MAX_LIST_WINDOW = 16; + struct mdp_blit_req req_list[MAX_LIST_WINDOW]; + struct mdp_blit_req_list req_list_header; + + int count, i, req_list_count; + + /* Get the count size for the total BLIT request. */ + if (copy_from_user(&req_list_header, p, sizeof(req_list_header))) + return -EFAULT; + p += sizeof(req_list_header); + count = req_list_header.count; + if (count < 0 || count >= MAX_BLIT_REQ) + return -EINVAL; + while (count > 0) { + /* + * Access the requests through a narrow window to decrease copy + * overhead and make larger requests accessible to the + * coherency management code. + * NOTE: The window size is intended to be larger than the + * typical request size, but not require more than 2 + * kbytes of stack storage. + */ + req_list_count = count; + if (req_list_count > MAX_LIST_WINDOW) + req_list_count = MAX_LIST_WINDOW; + if (copy_from_user(&req_list, p, + sizeof(struct mdp_blit_req)*req_list_count)) + return -EFAULT; + + /* + * Ensure that any data CPU may have previously written to + * internal state (but not yet committed to memory) is + * guaranteed to be committed to memory now. + */ + msm_fb_ensure_memory_coherency_before_dma(info, + req_list, req_list_count); + + /* + * Do the blit DMA, if required -- returning early only if + * there is a failure. + */ + for (i = 0; i < req_list_count; i++) { + if (!(req_list[i].flags & MDP_NO_BLIT)) { + /* Do the actual blit. */ + int ret = mdp_blit(info, &(req_list[i])); + + /* + * Note that early returns don't guarantee + * memory coherency. + */ + if (ret) + return ret; + } + } + + /* + * Ensure that CPU cache and other internal CPU state is + * updated to reflect any change in memory modified by MDP blit + * DMA. + */ + msm_fb_ensure_memory_coherency_after_dma(info, + req_list, + req_list_count); + + /* Go to next window of requests. */ + count -= req_list_count; + p += sizeof(struct mdp_blit_req)*req_list_count; + } + return 0; +} + +#ifdef CONFIG_FB_MSM_OVERLAY +static int msmfb_overlay_get(struct fb_info *info, void __user *p) +{ + struct mdp_overlay req; + int ret; + + if (copy_from_user(&req, p, sizeof(req))) + return -EFAULT; + + ret = mdp4_overlay_get(info, &req); + if (ret) { + printk(KERN_ERR "%s: ioctl failed \n", + __func__); + return ret; + } + if (copy_to_user(p, &req, sizeof(req))) { + printk(KERN_ERR "%s: copy2user failed \n", + __func__); + return -EFAULT; + } + + return 0; +} + +static int msmfb_overlay_set(struct fb_info *info, void __user *p) +{ + struct mdp_overlay req; + int ret; + + if (copy_from_user(&req, p, sizeof(req))) + return -EFAULT; + + ret = mdp4_overlay_set(info, &req); + if (ret) { + printk(KERN_ERR "%s: ioctl failed, rc=%d\n", + __func__, ret); + return ret; + } + + if (copy_to_user(p, &req, sizeof(req))) { + printk(KERN_ERR "%s: copy2user failed \n", + __func__); + return -EFAULT; + } + + return 0; +} + +static int msmfb_overlay_unset(struct fb_info *info, unsigned long *argp) +{ + int ret, ndx; + + ret = copy_from_user(&ndx, argp, sizeof(ndx)); + if (ret) { + printk(KERN_ERR "%s:msmfb_overlay_unset ioctl failed \n", + __func__); + return ret; + } + + return mdp4_overlay_unset(info, ndx); +} + +static int msmfb_overlay_play(struct fb_info *info, unsigned long *argp) +{ + int ret; + struct msmfb_overlay_data req; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + struct file *p_src_file = 0; + struct file *p_src_plane1_file = 0, *p_src_plane2_file = 0; + + if (mfd->overlay_play_enable == 0) /* nothing to do */ + return 0; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + printk(KERN_ERR "%s:msmfb_overlay_play ioctl failed \n", + __func__); + return ret; + } + + complete(&mfd->msmfb_update_notify); + mutex_lock(&msm_fb_notify_update_sem); + if (mfd->msmfb_no_update_notify_timer.function) + del_timer(&mfd->msmfb_no_update_notify_timer); + + mfd->msmfb_no_update_notify_timer.expires = + jiffies + ((1000 * HZ) / 1000); + add_timer(&mfd->msmfb_no_update_notify_timer); + mutex_unlock(&msm_fb_notify_update_sem); + + ret = mdp4_overlay_play(info, &req, &p_src_file, &p_src_plane1_file, + &p_src_plane2_file); + +#ifdef CONFIG_ANDROID_PMEM + if (p_src_file) + put_pmem_file(p_src_file); + if (p_src_plane1_file) + put_pmem_file(p_src_plane1_file); + if (p_src_plane2_file) + put_pmem_file(p_src_plane2_file); +#endif + + return ret; +} + +static int msmfb_overlay_play_enable(struct fb_info *info, unsigned long *argp) +{ + int ret, enable; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + + ret = copy_from_user(&enable, argp, sizeof(enable)); + if (ret) { + printk(KERN_ERR "%s:msmfb_overlay_play_enable ioctl failed \n", + __func__); + return ret; + } + + mfd->overlay_play_enable = enable; + + return 0; +} + + +#ifdef CONFIG_FB_MSM_OVERLAY_WRITEBACK +static int msmfb_overlay_blt(struct fb_info *info, unsigned long *argp) { - return register_mdp_client(&msm_fb_interface); + int ret; + struct msmfb_overlay_blt req; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + printk(KERN_ERR "%s:msmfb_overlay_blt ioctl failed\n", + __func__); + return ret; + } + + ret = mdp4_overlay_blt(info, &req); + + return ret; +} + +static int msmfb_overlay_blt_off(struct fb_info *info, unsigned long *argp) +{ + int ret; + struct msmfb_overlay_blt req; + + ret = mdp4_overlay_blt_offset(info, &req); + + ret = copy_to_user(argp, &req, sizeof(req)); + if (ret) + printk(KERN_ERR "%s:msmfb_overlay_blt_off ioctl failed\n", + __func__); + + return ret; +} +#else +static int msmfb_overlay_blt(struct fb_info *info, unsigned long *argp) +{ + return 0; +} +static int msmfb_overlay_blt_off(struct fb_info *info, unsigned long *argp) +{ + return 0; +} +#endif + +static int msmfb_overlay_3d(struct fb_info *info, unsigned long *argp) +{ + int ret; + struct msmfb_overlay_3d req; + + ret = copy_from_user(&req, argp, sizeof(req)); + if (ret) { + pr_err("%s:msmfb_overlay_3d_ctrl ioctl failed\n", + __func__); + return ret; + } + + ret = mdp4_overlay_3d(info, &req); + + return ret; +} + +#endif + +DEFINE_SEMAPHORE(msm_fb_ioctl_ppp_sem); +DEFINE_MUTEX(msm_fb_ioctl_lut_sem); +DEFINE_MUTEX(msm_fb_ioctl_hist_sem); + +/* Set color conversion matrix from user space */ + +#ifndef CONFIG_FB_MSM_MDP40 +static void msmfb_set_color_conv(struct mdp_ccs *p) +{ + int i; + + if (p->direction == MDP_CCS_RGB2YUV) { + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* RGB->YUV primary forward matrix */ + for (i = 0; i < MDP_CCS_SIZE; i++) + writel(p->ccs[i], MDP_CSC_PFMVn(i)); + + #ifdef CONFIG_FB_MSM_MDP31 + for (i = 0; i < MDP_BV_SIZE; i++) + writel(p->bv[i], MDP_CSC_POST_BV2n(i)); + #endif + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + } else { + /* MDP cmd block enable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + + /* YUV->RGB primary reverse matrix */ + for (i = 0; i < MDP_CCS_SIZE; i++) + writel(p->ccs[i], MDP_CSC_PRMVn(i)); + for (i = 0; i < MDP_BV_SIZE; i++) + writel(p->bv[i], MDP_CSC_PRE_BV1n(i)); + + /* MDP cmd block disable */ + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, FALSE); + } +} +#endif + +static int msmfb_notify_update(struct fb_info *info, unsigned long *argp) +{ + int ret, notify; + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + + ret = copy_from_user(¬ify, argp, sizeof(int)); + if (ret) { + pr_err("%s:ioctl failed\n", __func__); + return ret; + } + + if (notify > NOTIFY_UPDATE_STOP) + return -EINVAL; + + if (notify == NOTIFY_UPDATE_START) { + INIT_COMPLETION(mfd->msmfb_update_notify); + wait_for_completion_interruptible(&mfd->msmfb_update_notify); + } else { + INIT_COMPLETION(mfd->msmfb_no_update_notify); + wait_for_completion_interruptible(&mfd->msmfb_no_update_notify); + } + return 0; +} + +static int msm_fb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct msm_fb_data_type *mfd = (struct msm_fb_data_type *)info->par; + void __user *argp = (void __user *)arg; + struct fb_cursor cursor; + struct fb_cmap cmap; + struct mdp_histogram hist; +#ifndef CONFIG_FB_MSM_MDP40 + struct mdp_ccs ccs_matrix; +#endif + struct mdp_page_protection fb_page_protection; + int ret = 0; + + switch (cmd) { +#ifdef CONFIG_FB_MSM_OVERLAY + case MSMFB_OVERLAY_GET: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_overlay_get(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; + case MSMFB_OVERLAY_SET: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_overlay_set(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; + case MSMFB_OVERLAY_UNSET: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_overlay_unset(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; + case MSMFB_OVERLAY_PLAY: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_overlay_play(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; + case MSMFB_OVERLAY_PLAY_ENABLE: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_overlay_play_enable(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; + case MSMFB_OVERLAY_BLT: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_overlay_blt(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; + case MSMFB_OVERLAY_BLT_OFFSET: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_overlay_blt_off(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; + case MSMFB_OVERLAY_3D: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_overlay_3d(info, argp); + up(&msm_fb_ioctl_ppp_sem); + break; +#endif + case MSMFB_BLIT: + down(&msm_fb_ioctl_ppp_sem); + ret = msmfb_blit(info, argp); + up(&msm_fb_ioctl_ppp_sem); + + break; + + /* Ioctl for setting ccs matrix from user space */ + case MSMFB_SET_CCS_MATRIX: +#ifndef CONFIG_FB_MSM_MDP40 + ret = copy_from_user(&ccs_matrix, argp, sizeof(ccs_matrix)); + if (ret) { + printk(KERN_ERR + "%s:MSMFB_SET_CCS_MATRIX ioctl failed \n", + __func__); + return ret; + } + + down(&msm_fb_ioctl_ppp_sem); + if (ccs_matrix.direction == MDP_CCS_RGB2YUV) + mdp_ccs_rgb2yuv = ccs_matrix; + else + mdp_ccs_yuv2rgb = ccs_matrix; + + msmfb_set_color_conv(&ccs_matrix) ; + up(&msm_fb_ioctl_ppp_sem); +#else + ret = -EINVAL; +#endif + + break; + + /* Ioctl for getting ccs matrix to user space */ + case MSMFB_GET_CCS_MATRIX: +#ifndef CONFIG_FB_MSM_MDP40 + ret = copy_from_user(&ccs_matrix, argp, sizeof(ccs_matrix)) ; + if (ret) { + printk(KERN_ERR + "%s:MSMFB_GET_CCS_MATRIX ioctl failed \n", + __func__); + return ret; + } + + down(&msm_fb_ioctl_ppp_sem); + if (ccs_matrix.direction == MDP_CCS_RGB2YUV) + ccs_matrix = mdp_ccs_rgb2yuv; + else + ccs_matrix = mdp_ccs_yuv2rgb; + + ret = copy_to_user(argp, &ccs_matrix, sizeof(ccs_matrix)); + + if (ret) { + printk(KERN_ERR + "%s:MSMFB_GET_CCS_MATRIX ioctl failed \n", + __func__); + return ret ; + } + up(&msm_fb_ioctl_ppp_sem); +#else + ret = -EINVAL; +#endif + + break; + + case MSMFB_GRP_DISP: +#ifdef CONFIG_FB_MSM_MDP22 + { + unsigned long grp_id; + + ret = copy_from_user(&grp_id, argp, sizeof(grp_id)); + if (ret) + return ret; + + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_ON, FALSE); + writel(grp_id, MDP_FULL_BYPASS_WORD43); + mdp_pipe_ctrl(MDP_CMD_BLOCK, MDP_BLOCK_POWER_OFF, + FALSE); + break; + } +#else + return -EFAULT; +#endif + case MSMFB_SUSPEND_SW_REFRESHER: + if (!mfd->panel_power_on) + return -EPERM; + + mfd->sw_refreshing_enable = FALSE; + ret = msm_fb_stop_sw_refresher(mfd); + break; + + case MSMFB_RESUME_SW_REFRESHER: + if (!mfd->panel_power_on) + return -EPERM; + + mfd->sw_refreshing_enable = TRUE; + ret = msm_fb_resume_sw_refresher(mfd); + break; + + case MSMFB_CURSOR: + ret = copy_from_user(&cursor, argp, sizeof(cursor)); + if (ret) + return ret; + + ret = msm_fb_cursor(info, &cursor); + break; + + case MSMFB_SET_LUT: + ret = copy_from_user(&cmap, argp, sizeof(cmap)); + if (ret) + return ret; + + mutex_lock(&msm_fb_ioctl_lut_sem); + ret = msm_fb_set_lut(&cmap, info); + mutex_unlock(&msm_fb_ioctl_lut_sem); + break; + + case MSMFB_HISTOGRAM: + if (!mfd->do_histogram) + return -ENODEV; + + ret = copy_from_user(&hist, argp, sizeof(hist)); + if (ret) + return ret; + + mutex_lock(&msm_fb_ioctl_hist_sem); + ret = mfd->do_histogram(info, &hist); + mutex_unlock(&msm_fb_ioctl_hist_sem); + break; + + case MSMFB_HISTOGRAM_START: + if (!mfd->do_histogram) + return -ENODEV; + ret = mdp_start_histogram(info); + break; + + case MSMFB_HISTOGRAM_STOP: + if (!mfd->do_histogram) + return -ENODEV; + ret = mdp_stop_histogram(info); + break; + + + case MSMFB_GET_PAGE_PROTECTION: + fb_page_protection.page_protection + = mfd->mdp_fb_page_protection; + ret = copy_to_user(argp, &fb_page_protection, + sizeof(fb_page_protection)); + if (ret) + return ret; + break; + + case MSMFB_NOTIFY_UPDATE: + ret = msmfb_notify_update(info, argp); + break; + + case MSMFB_SET_PAGE_PROTECTION: +#if defined CONFIG_ARCH_QSD8X50 || defined CONFIG_ARCH_MSM8X60 + ret = copy_from_user(&fb_page_protection, argp, + sizeof(fb_page_protection)); + if (ret) + return ret; + + /* Validate the proposed page protection settings. */ + switch (fb_page_protection.page_protection) { + case MDP_FB_PAGE_PROTECTION_NONCACHED: + case MDP_FB_PAGE_PROTECTION_WRITECOMBINE: + case MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE: + /* Write-back cache (read allocate) */ + case MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE: + /* Write-back cache (write allocate) */ + case MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE: + mfd->mdp_fb_page_protection = + fb_page_protection.page_protection; + break; + default: + ret = -EINVAL; + break; + } +#else + /* + * Don't allow caching until 7k DMA cache operations are + * available. + */ + ret = -EINVAL; +#endif + break; + + default: + MSM_FB_INFO("MDP: unknown ioctl (cmd=%x) received!\n", cmd); + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_fb_register_driver(void) +{ + return platform_driver_register(&msm_fb_driver); +} + +struct platform_device *msm_fb_add_device(struct platform_device *pdev) +{ + struct msm_fb_panel_data *pdata; + struct platform_device *this_dev = NULL; + struct fb_info *fbi; + struct msm_fb_data_type *mfd = NULL; + u32 type, id, fb_num; + + if (!pdev) + return NULL; + id = pdev->id; + + pdata = pdev->dev.platform_data; + if (!pdata) + return NULL; + type = pdata->panel_info.type; + +#if defined MSM_FB_NUM + /* + * over written fb_num which defined + * at panel_info + * + */ + if (type == HDMI_PANEL || type == DTV_PANEL || type == TV_PANEL) + pdata->panel_info.fb_num = 1; + else + pdata->panel_info.fb_num = MSM_FB_NUM; + + MSM_FB_INFO("setting pdata->panel_info.fb_num to %d. type: %d\n", + pdata->panel_info.fb_num, type); +#endif + fb_num = pdata->panel_info.fb_num; + + if (fb_num <= 0) + return NULL; + + if (fbi_list_index >= MAX_FBI_LIST) { + printk(KERN_ERR "msm_fb: no more framebuffer info list!\n"); + return NULL; + } + /* + * alloc panel device data + */ + this_dev = msm_fb_device_alloc(pdata, type, id); + + if (!this_dev) { + printk(KERN_ERR + "%s: msm_fb_device_alloc failed!\n", __func__); + return NULL; + } + + /* + * alloc framebuffer info + par data + */ + fbi = framebuffer_alloc(sizeof(struct msm_fb_data_type), NULL); + if (fbi == NULL) { + platform_device_put(this_dev); + printk(KERN_ERR "msm_fb: can't alloca framebuffer info data!\n"); + return NULL; + } + + mfd = (struct msm_fb_data_type *)fbi->par; + mfd->key = MFD_KEY; + mfd->fbi = fbi; + mfd->panel.type = type; + mfd->panel.id = id; + mfd->fb_page = fb_num; + mfd->index = fbi_list_index; + mfd->mdp_fb_page_protection = MDP_FB_PAGE_PROTECTION_WRITECOMBINE; + + /* link to the latest pdev */ + mfd->pdev = this_dev; + + mfd_list[mfd_list_index++] = mfd; + fbi_list[fbi_list_index++] = fbi; + + /* + * set driver data + */ + platform_set_drvdata(this_dev, mfd); + + if (platform_device_add(this_dev)) { + printk(KERN_ERR "msm_fb: platform_device_add failed!\n"); + platform_device_put(this_dev); + framebuffer_release(fbi); + fbi_list_index--; + return NULL; + } + return this_dev; +} +EXPORT_SYMBOL(msm_fb_add_device); + +int get_fb_phys_info(unsigned long *start, unsigned long *len, int fb_num) +{ + struct fb_info *info; + + if (fb_num > MAX_FBI_LIST) + return -1; + + info = fbi_list[fb_num]; + if (!info) + return -1; + + *start = info->fix.smem_start; + *len = info->fix.smem_len; + return 0; +} +EXPORT_SYMBOL(get_fb_phys_info); + +int __init msm_fb_init(void) +{ + int rc = -ENODEV; + + if (msm_fb_register_driver()) + return rc; + +#ifdef MSM_FB_ENABLE_DBGFS + { + struct dentry *root; + + if ((root = msm_fb_get_debugfs_root()) != NULL) { + msm_fb_debugfs_file_create(root, + "msm_fb_msg_printing_level", + (u32 *) &msm_fb_msg_level); + msm_fb_debugfs_file_create(root, + "mddi_msg_printing_level", + (u32 *) &mddi_msg_level); + msm_fb_debugfs_file_create(root, "msm_fb_debug_enabled", + (u32 *) &msm_fb_debug_enabled); + } + } +#endif + + return 0; } -module_init(msmfb_init); +module_init(msm_fb_init); diff --git a/drivers/video/msm/msm_fb.h b/drivers/video/msm/msm_fb.h new file mode 100644 index 00000000000..bdf32eb990d --- /dev/null +++ b/drivers/video/msm/msm_fb.h @@ -0,0 +1,171 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MSM_FB_H +#define MSM_FB_H + +#include +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + +#include "msm_fb_panel.h" +#include "mdp.h" + +#define MSM_FB_DEFAULT_PAGE_SIZE 2 +#define MFD_KEY 0x11161126 +#define MSM_FB_MAX_DEV_LIST 32 + +struct disp_info_type_suspend { + boolean op_enable; + boolean sw_refreshing_enable; + boolean panel_power_on; +}; + +struct msm_fb_data_type { + __u32 key; + __u32 index; + __u32 ref_cnt; + __u32 fb_page; + + panel_id_type panel; + struct msm_panel_info panel_info; + + DISP_TARGET dest; + struct fb_info *fbi; + + boolean op_enable; + uint32 fb_imgType; + boolean sw_currently_refreshing; + boolean sw_refreshing_enable; + boolean hw_refresh; +#ifdef CONFIG_FB_MSM_OVERLAY + int overlay_play_enable; +#endif + + MDPIBUF ibuf; + boolean ibuf_flushed; + struct timer_list refresh_timer; + struct completion refresher_comp; + + boolean pan_waiting; + struct completion pan_comp; + + /* vsync */ + boolean use_mdp_vsync; + __u32 vsync_gpio; + __u32 total_lcd_lines; + __u32 total_porch_lines; + __u32 lcd_ref_usec_time; + __u32 refresh_timer_duration; + + struct hrtimer dma_hrtimer; + + boolean panel_power_on; + struct work_struct dma_update_worker; + struct semaphore sem; + + struct timer_list vsync_resync_timer; + boolean vsync_handler_pending; + struct work_struct vsync_resync_worker; + + ktime_t last_vsync_timetick; + + __u32 *vsync_width_boundary; + + unsigned int pmem_id; + struct disp_info_type_suspend suspend; + + __u32 channel_irq; + + struct mdp_dma_data *dma; + void (*dma_fnc) (struct msm_fb_data_type *mfd); + int (*cursor_update) (struct fb_info *info, + struct fb_cursor *cursor); + int (*lut_update) (struct fb_info *info, + struct fb_cmap *cmap); + int (*do_histogram) (struct fb_info *info, + struct mdp_histogram *hist); + void *cursor_buf; + void *cursor_buf_phys; + + void *cmd_port; + void *data_port; + void *data_port_phys; + + __u32 bl_level; + + struct platform_device *pdev; + + __u32 var_xres; + __u32 var_yres; + __u32 var_pixclock; + +#ifdef MSM_FB_ENABLE_DBGFS + struct dentry *sub_dir; +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#ifdef CONFIG_FB_MSM_MDDI + struct early_suspend mddi_early_suspend; + struct early_suspend mddi_ext_early_suspend; +#endif +#endif + u32 mdp_fb_page_protection; + + struct clk *ebi1_clk; + boolean dma_update_flag; + struct timer_list msmfb_no_update_notify_timer; + struct completion msmfb_update_notify; + struct completion msmfb_no_update_notify; +}; + +struct dentry *msm_fb_get_debugfs_root(void); +void msm_fb_debugfs_file_create(struct dentry *root, const char *name, + u32 *var); +void msm_fb_set_backlight(struct msm_fb_data_type *mfd, __u32 bkl_lvl); + +struct platform_device *msm_fb_add_device(struct platform_device *pdev); + +int msm_fb_detect_client(const char *name); + +#ifdef CONFIG_FB_BACKLIGHT +void msm_fb_config_backlight(struct msm_fb_data_type *mfd); +#endif + +void fill_black_screen(void); +void unfill_black_screen(void); + +#endif /* MSM_FB_H */ diff --git a/drivers/video/msm/msm_fb_bl.c b/drivers/video/msm/msm_fb_bl.c new file mode 100644 index 00000000000..9afbbf16a57 --- /dev/null +++ b/drivers/video/msm/msm_fb_bl.c @@ -0,0 +1,75 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb.h" + +static int msm_fb_bl_get_brightness(struct backlight_device *pbd) +{ + return pbd->props.brightness; +} + +static int msm_fb_bl_update_status(struct backlight_device *pbd) +{ + struct msm_fb_data_type *mfd = bl_get_data(pbd); + __u32 bl_lvl; + + bl_lvl = pbd->props.brightness; + bl_lvl = mfd->fbi->bl_curve[bl_lvl]; + msm_fb_set_backlight(mfd, bl_lvl); + return 0; +} + +static struct backlight_ops msm_fb_bl_ops = { + .get_brightness = msm_fb_bl_get_brightness, + .update_status = msm_fb_bl_update_status, +}; + +void msm_fb_config_backlight(struct msm_fb_data_type *mfd) +{ + struct msm_fb_panel_data *pdata; + struct backlight_device *pbd; + struct fb_info *fbi; + char name[16]; + struct backlight_properties props; + + fbi = mfd->fbi; + pdata = (struct msm_fb_panel_data *)mfd->pdev->dev.platform_data; + + if ((pdata) && (pdata->set_backlight)) { + snprintf(name, sizeof(name), "msmfb_bl%d", mfd->index); + props.max_brightness = FB_BACKLIGHT_LEVELS - 1; + props.brightness = FB_BACKLIGHT_LEVELS - 1; + pbd = + backlight_device_register(name, fbi->dev, mfd, + &msm_fb_bl_ops, &props); + if (!IS_ERR(pbd)) { + fbi->bl_dev = pbd; + fb_bl_default_curve(fbi, + 0, + mfd->panel_info.bl_min, + mfd->panel_info.bl_max); + } else { + fbi->bl_dev = NULL; + printk(KERN_ERR "msm_fb: backlight_device_register failed!\n"); + } + } +} diff --git a/drivers/video/msm/msm_fb_def.h b/drivers/video/msm/msm_fb_def.h new file mode 100644 index 00000000000..1c1f3926723 --- /dev/null +++ b/drivers/video/msm/msm_fb_def.h @@ -0,0 +1,204 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MSM_FB_DEF_H +#define MSM_FB_DEF_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linux/proc_fs.h" +#include +#include +#include + +#include +#include + +#include + + +typedef s64 int64; +typedef s32 int32; +typedef s16 int16; +typedef s8 int8; + +typedef u64 uint64; +typedef u32 uint32; +typedef u16 uint16; +typedef u8 uint8; + +typedef s32 int4; +typedef s16 int2; +typedef s8 int1; + +typedef u32 uint4; +typedef u16 uint2; +typedef u8 uint1; + +typedef u32 dword; +typedef u16 word; +typedef u8 byte; + +typedef unsigned int boolean; + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#define MSM_FB_ENABLE_DBGFS +#define FEATURE_MDDI + +#if defined(CONFIG_FB_MSM_DEFAULT_DEPTH_RGB565) +#define MSMFB_DEFAULT_TYPE MDP_RGB_565 +#elif defined(CONFIG_FB_MSM_DEFAULT_DEPTH_ARGB8888) +#define MSMFB_DEFAULT_TYPE MDP_ARGB_8888 +#elif defined(CONFIG_FB_MSM_DEFAULT_DEPTH_RGBA8888) +#define MSMFB_DEFAULT_TYPE MDP_RGBA_8888 +#else +#define MSMFB_DEFAULT_TYPE MDP_RGB_565 +#endif + +#define outp32(addr, val) writel(val, addr) +#define outp16(addr, val) writew(val, addr) +#define outp8(addr, val) writeb(val, addr) +#define outp(addr, val) outp32(addr, val) + +#ifndef MAX +#define MAX( x, y ) (((x) > (y)) ? (x) : (y)) +#endif + +#ifndef MIN +#define MIN( x, y ) (((x) < (y)) ? (x) : (y)) +#endif + +/*--------------------------------------------------------------------------*/ + +#define inp32(addr) readl(addr) +#define inp16(addr) readw(addr) +#define inp8(addr) readb(addr) +#define inp(addr) inp32(addr) + +#define inpw(port) readw(port) +#define outpw(port, val) writew(val, port) +#define inpdw(port) readl(port) +#define outpdw(port, val) writel(val, port) + + +#define clk_busy_wait(x) msleep_interruptible((x)/1000) + +#define memory_barrier() + +#define assert(expr) \ + if(!(expr)) { \ + printk(KERN_ERR "msm_fb: assertion failed! %s,%s,%s,line=%d\n",\ + #expr, __FILE__, __func__, __LINE__); \ + } + +#define ASSERT(x) assert(x) + +#define DISP_EBI2_LOCAL_DEFINE +#ifdef DISP_EBI2_LOCAL_DEFINE +#define LCD_PRIM_BASE_PHYS 0x98000000 +#define LCD_SECD_BASE_PHYS 0x9c000000 +#define EBI2_PRIM_LCD_RS_PIN 0x20000 +#define EBI2_SECD_LCD_RS_PIN 0x20000 + +#define EBI2_PRIM_LCD_CLR 0xC0 +#define EBI2_PRIM_LCD_SEL 0x40 + +#define EBI2_SECD_LCD_CLR 0x300 +#define EBI2_SECD_LCD_SEL 0x100 +#endif + +extern u32 msm_fb_msg_level; + +/* + * Message printing priorities: + * LEVEL 0 KERN_EMERG (highest priority) + * LEVEL 1 KERN_ALERT + * LEVEL 2 KERN_CRIT + * LEVEL 3 KERN_ERR + * LEVEL 4 KERN_WARNING + * LEVEL 5 KERN_NOTICE + * LEVEL 6 KERN_INFO + * LEVEL 7 KERN_DEBUG (Lowest priority) + */ +#define MSM_FB_EMERG(msg, ...) \ + if (msm_fb_msg_level > 0) \ + printk(KERN_EMERG msg, ## __VA_ARGS__); +#define MSM_FB_ALERT(msg, ...) \ + if (msm_fb_msg_level > 1) \ + printk(KERN_ALERT msg, ## __VA_ARGS__); +#define MSM_FB_CRIT(msg, ...) \ + if (msm_fb_msg_level > 2) \ + printk(KERN_CRIT msg, ## __VA_ARGS__); +#define MSM_FB_ERR(msg, ...) \ + if (msm_fb_msg_level > 3) \ + printk(KERN_ERR msg, ## __VA_ARGS__); +#define MSM_FB_WARNING(msg, ...) \ + if (msm_fb_msg_level > 4) \ + printk(KERN_WARNING msg, ## __VA_ARGS__); +#define MSM_FB_NOTICE(msg, ...) \ + if (msm_fb_msg_level > 5) \ + printk(KERN_NOTICE msg, ## __VA_ARGS__); +#define MSM_FB_INFO(msg, ...) \ + if (msm_fb_msg_level > 6) \ + printk(KERN_INFO msg, ## __VA_ARGS__); +#define MSM_FB_DEBUG(msg, ...) \ + if (msm_fb_msg_level > 7) \ + printk(KERN_DEBUG msg, ## __VA_ARGS__); + +#ifdef MSM_FB_C +unsigned char *msm_mdp_base; +unsigned char *msm_pmdh_base; +unsigned char *msm_emdh_base; +unsigned char *mipi_dsi_base; +#else +extern unsigned char *msm_mdp_base; +extern unsigned char *msm_pmdh_base; +extern unsigned char *msm_emdh_base; +extern unsigned char *mipi_dsi_base; +#endif + +#undef ENABLE_MDDI_MULTI_READ_WRITE +#undef ENABLE_FWD_LINK_SKEW_CALIBRATION + +#endif /* MSM_FB_DEF_H */ diff --git a/drivers/video/msm/msm_fb_panel.c b/drivers/video/msm/msm_fb_panel.c new file mode 100644 index 00000000000..84de095d217 --- /dev/null +++ b/drivers/video/msm/msm_fb_panel.c @@ -0,0 +1,141 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm_fb_panel.h" + +int panel_next_on(struct platform_device *pdev) +{ + int ret = 0; + struct msm_fb_panel_data *pdata; + struct msm_fb_panel_data *next_pdata; + struct platform_device *next_pdev; + + pdata = (struct msm_fb_panel_data *)pdev->dev.platform_data; + + if (pdata) { + next_pdev = pdata->next; + if (next_pdev) { + next_pdata = + (struct msm_fb_panel_data *)next_pdev->dev. + platform_data; + if ((next_pdata) && (next_pdata->on)) + ret = next_pdata->on(next_pdev); + } + } + + return ret; +} + +int panel_next_off(struct platform_device *pdev) +{ + int ret = 0; + struct msm_fb_panel_data *pdata; + struct msm_fb_panel_data *next_pdata; + struct platform_device *next_pdev; + + pdata = (struct msm_fb_panel_data *)pdev->dev.platform_data; + + if (pdata) { + next_pdev = pdata->next; + if (next_pdev) { + next_pdata = + (struct msm_fb_panel_data *)next_pdev->dev. + platform_data; + if ((next_pdata) && (next_pdata->on)) + ret = next_pdata->off(next_pdev); + } + } + + return ret; +} + +struct platform_device *msm_fb_device_alloc(struct msm_fb_panel_data *pdata, + u32 type, u32 id) +{ + struct platform_device *this_dev = NULL; + char dev_name[16]; + + switch (type) { + case EBI2_PANEL: + snprintf(dev_name, sizeof(dev_name), "ebi2_lcd"); + break; + + case MDDI_PANEL: + snprintf(dev_name, sizeof(dev_name), "mddi"); + break; + + case EXT_MDDI_PANEL: + snprintf(dev_name, sizeof(dev_name), "mddi_ext"); + break; + + case TV_PANEL: + snprintf(dev_name, sizeof(dev_name), "tvenc"); + break; + + case HDMI_PANEL: + case LCDC_PANEL: + snprintf(dev_name, sizeof(dev_name), "lcdc"); + break; + + case DTV_PANEL: + snprintf(dev_name, sizeof(dev_name), "dtv"); + break; + + case MIPI_VIDEO_PANEL: + case MIPI_CMD_PANEL: + snprintf(dev_name, sizeof(dev_name), "mipi_dsi"); + break; + + default: + return NULL; + } + + if (pdata != NULL) + pdata->next = NULL; + else + return NULL; + + this_dev = + platform_device_alloc(dev_name, ((u32) type << 16) | (u32) id); + + if (this_dev) { + if (platform_device_add_data + (this_dev, pdata, sizeof(struct msm_fb_panel_data))) { + printk + ("msm_fb_device_alloc: platform_device_add_data failed!\n"); + platform_device_put(this_dev); + return NULL; + } + } + + return this_dev; +} diff --git a/drivers/video/msm/msm_fb_panel.h b/drivers/video/msm/msm_fb_panel.h new file mode 100644 index 00000000000..bbf5a388b89 --- /dev/null +++ b/drivers/video/msm/msm_fb_panel.h @@ -0,0 +1,199 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef MSM_FB_PANEL_H +#define MSM_FB_PANEL_H + +#include "msm_fb_def.h" + +struct msm_fb_data_type; + +typedef void (*msm_fb_vsync_handler_type) (void *arg); + +/* panel id type */ +typedef struct panel_id_s { + uint16 id; + uint16 type; +} panel_id_type; + +/* panel type list */ +#define NO_PANEL 0xffff /* No Panel */ +#define MDDI_PANEL 1 /* MDDI */ +#define EBI2_PANEL 2 /* EBI2 */ +#define LCDC_PANEL 3 /* internal LCDC type */ +#define EXT_MDDI_PANEL 4 /* Ext.MDDI */ +#define TV_PANEL 5 /* TV */ +#define HDMI_PANEL 6 /* HDMI TV */ +#define DTV_PANEL 7 /* DTV */ +#define MIPI_VIDEO_PANEL 8 /* MIPI */ +#define MIPI_CMD_PANEL 9 /* MIPI */ + +/* panel class */ +typedef enum { + DISPLAY_LCD = 0, /* lcd = ebi2/mddi */ + DISPLAY_LCDC, /* lcdc */ + DISPLAY_TV, /* TV Out */ + DISPLAY_EXT_MDDI, /* External MDDI */ +} DISP_TARGET; + +/* panel device locaiton */ +typedef enum { + DISPLAY_1 = 0, /* attached as first device */ + DISPLAY_2, /* attached on second device */ + MAX_PHYS_TARGET_NUM, +} DISP_TARGET_PHYS; + +/* panel info type */ +struct lcd_panel_info { + __u32 vsync_enable; + __u32 refx100; + __u32 v_back_porch; + __u32 v_front_porch; + __u32 v_pulse_width; + __u32 hw_vsync_mode; + __u32 vsync_notifier_period; + __u32 rev; +}; + +struct lcdc_panel_info { + __u32 h_back_porch; + __u32 h_front_porch; + __u32 h_pulse_width; + __u32 v_back_porch; + __u32 v_front_porch; + __u32 v_pulse_width; + __u32 border_clr; + __u32 underflow_clr; + __u32 hsync_skew; +}; + +struct mddi_panel_info { + __u32 vdopkt; +}; + +/* DSI PHY configuration */ +struct mipi_dsi_phy_ctrl { + uint32 regulator[5]; + uint32 timing[12]; + uint32 ctrl[4]; + uint32 strength[4]; + uint32 pll[21]; +}; + +struct mipi_panel_info { + char mode; /* video/cmd */ + char interleave_mode; + char crc_check; + char ecc_check; + char dst_format; /* shared by video and command */ + char data_lane0; + char data_lane1; + char data_lane2; + char data_lane3; + char dlane_swap; /* data lane swap */ + char rgb_swap; + char b_sel; + char g_sel; + char r_sel; + char rx_eot_ignore; + char tx_eot_append; + char t_clk_post; /* 0xc0, DSI_CLKOUT_TIMING_CTRL */ + char t_clk_pre; /* 0xc0, DSI_CLKOUT_TIMING_CTRL */ + char vc; /* virtual channel */ + struct mipi_dsi_phy_ctrl *dsi_phy_db; + /* video mode */ + char pulse_mode_hsa_he; + char hfp_power_stop; + char hbp_power_stop; + char hsa_power_stop; + char eof_bllp_power_stop; + char bllp_power_stop; + char traffic_mode; + char frame_rate; + /* command mode */ + char interleave_max; + char insert_dcs_cmd; + char wr_mem_continue; + char wr_mem_start; + char te_sel; + char stream; /* 0 or 1 */ + char mdp_trigger; + char dma_trigger; + uint32 dsi_pclk_rate; + /* Pad width */ + uint32 xres_pad; + /* Pad height */ + uint32 yres_pad; +}; + +struct msm_panel_info { + __u32 xres; + __u32 yres; + __u32 bpp; + __u32 mode2_xres; + __u32 mode2_yres; + __u32 mode2_bpp; + __u32 type; + __u32 wait_cycle; + DISP_TARGET_PHYS pdest; + __u32 bl_max; + __u32 bl_min; + __u32 fb_num; + __u32 clk_rate; + __u32 clk_min; + __u32 clk_max; + __u32 frame_count; + __u32 is_3d_panel; + + + struct mddi_panel_info mddi; + struct lcd_panel_info lcd; + struct lcdc_panel_info lcdc; + + struct mipi_panel_info mipi; +}; + +#define MSM_FB_SINGLE_MODE_PANEL(pinfo) \ + do { \ + (pinfo)->mode2_xres = 0; \ + (pinfo)->mode2_yres = 0; \ + (pinfo)->mode2_bpp = 0; \ + } while (0) + +struct msm_fb_panel_data { + struct msm_panel_info panel_info; + void (*set_rect) (int x, int y, int xres, int yres); + void (*set_vsync_notifier) (msm_fb_vsync_handler_type, void *arg); + void (*set_backlight) (struct msm_fb_data_type *); + + /* function entry chain */ + int (*on) (struct platform_device *pdev); + int (*off) (struct platform_device *pdev); + struct platform_device *next; + int (*clk_func) (int enable); +}; + +/*=========================================================================== + FUNCTIONS PROTOTYPES +============================================================================*/ +struct platform_device *msm_fb_device_alloc(struct msm_fb_panel_data *pdata, + u32 type, u32 id); +int panel_next_on(struct platform_device *pdev); +int panel_next_off(struct platform_device *pdev); + +int lcdc_device_register(struct msm_panel_info *pinfo); + +int mddi_toshiba_device_register(struct msm_panel_info *pinfo, + u32 channel, u32 panel); + +#endif /* MSM_FB_PANEL_H */ diff --git a/drivers/video/msm/tvenc.c b/drivers/video/msm/tvenc.c new file mode 100644 index 00000000000..73e24281261 --- /dev/null +++ b/drivers/video/msm/tvenc.c @@ -0,0 +1,521 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define TVENC_C +#include "tvenc.h" +#include "msm_fb.h" +#include "mdp4.h" +#ifdef CONFIG_MSM_NPA_SYSTEM_BUS +/* NPA Flow ID */ +#define MSM_SYSTEM_BUS_RATE MSM_AXI_FLOW_MDP_DTV_720P_2BPP +#else +/* AXI rate in KHz */ +#define MSM_SYSTEM_BUS_RATE 128000000 +#endif + +static int tvenc_probe(struct platform_device *pdev); +static int tvenc_remove(struct platform_device *pdev); + +static int tvenc_off(struct platform_device *pdev); +static int tvenc_on(struct platform_device *pdev); + +static struct platform_device *pdev_list[MSM_FB_MAX_DEV_LIST]; +static int pdev_list_cnt; + +static struct clk *tvenc_clk; +static struct clk *tvdac_clk; +static struct clk *tvenc_pclk; +static struct clk *mdp_tv_clk; +#ifdef CONFIG_FB_MSM_MDP40 +static struct clk *tv_src_clk; +#endif + +#ifdef CONFIG_MSM_BUS_SCALING +static uint32_t tvenc_bus_scale_handle; +#endif + +static int tvenc_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int tvenc_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static struct dev_pm_ops tvenc_dev_pm_ops = { + .runtime_suspend = tvenc_runtime_suspend, + .runtime_resume = tvenc_runtime_resume, +}; + +static struct platform_driver tvenc_driver = { + .probe = tvenc_probe, + .remove = tvenc_remove, + .suspend = NULL, + .resume = NULL, + .shutdown = NULL, + .driver = { + .name = "tvenc", + .pm = &tvenc_dev_pm_ops + }, +}; + +int tvenc_set_encoder_clock(boolean clock_on) +{ + int ret = 0; + if (clock_on) { +#ifdef CONFIG_FB_MSM_MDP40 + /* Consolidated clock used by both HDMI & TV encoder. + Clock exists only in MDP4 and not in older versions */ + ret = clk_set_rate(tv_src_clk, 27000000); + if (ret) { + pr_err("%s: tvsrc_clk set rate failed! %d\n", + __func__, ret); + goto tvsrc_err; + } +#endif + ret = clk_enable(tvenc_clk); + if (ret) { + pr_err("%s: tvenc_clk enable failed! %d\n", + __func__, ret); + goto tvsrc_err; + } + + if (!IS_ERR(tvenc_pclk)) { + ret = clk_enable(tvenc_pclk); + if (ret) { + pr_err("%s: tvenc_pclk enable failed! %d\n", + __func__, ret); + goto tvencp_err; + } + } + return ret; + } else { + if (!IS_ERR(tvenc_pclk)) + clk_disable(tvenc_pclk); + clk_disable(tvenc_clk); + return ret; + } +tvencp_err: + clk_disable(tvenc_clk); +tvsrc_err: + return ret; +} + +int tvenc_set_clock(boolean clock_on) +{ + int ret = 0; + if (clock_on) { + if (tvenc_pdata->poll) { + ret = tvenc_set_encoder_clock(CLOCK_ON); + if (ret) { + pr_err("%s: TVenc clock(s) enable failed! %d\n", + __func__, ret); + goto tvenc_err; + } + } + ret = clk_enable(tvdac_clk); + if (ret) { + pr_err("%s: tvdac_clk enable failed! %d\n", + __func__, ret); + goto tvdac_err; + } + if (!IS_ERR(mdp_tv_clk)) { + ret = clk_enable(mdp_tv_clk); + if (ret) { + pr_err("%s: mdp_tv_clk enable failed! %d\n", + __func__, ret); + goto mdptv_err; + } + } + return ret; + } else { + if (!IS_ERR(mdp_tv_clk)) + clk_disable(mdp_tv_clk); + clk_disable(tvdac_clk); + if (tvenc_pdata->poll) + tvenc_set_encoder_clock(CLOCK_OFF); + return ret; + } + +mdptv_err: + clk_disable(tvdac_clk); +tvdac_err: + tvenc_set_encoder_clock(CLOCK_OFF); +tvenc_err: + return ret; +} + +static int tvenc_off(struct platform_device *pdev) +{ + int ret = 0; + + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + + ret = panel_next_off(pdev); + if (ret) + pr_err("%s: tvout_off failed! %d\n", + __func__, ret); + + tvenc_set_clock(CLOCK_OFF); + + if (tvenc_pdata && tvenc_pdata->pm_vid_en) + ret = tvenc_pdata->pm_vid_en(0); +#ifdef CONFIG_MSM_BUS_SCALING + if (tvenc_bus_scale_handle > 0) + msm_bus_scale_client_update_request(tvenc_bus_scale_handle, + 0); +#else + if (mfd->ebi1_clk) + clk_disable(mfd->ebi1_clk); +#endif + + if (ret) + pr_err("%s: pm_vid_en(off) failed! %d\n", + __func__, ret); + mdp4_extn_disp = 0; + return ret; +} + +static int tvenc_on(struct platform_device *pdev) +{ + int ret = 0; + +#ifndef CONFIG_MSM_BUS_SCALING + struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); +#endif + +#ifdef CONFIG_MSM_BUS_SCALING + if (tvenc_bus_scale_handle > 0) + msm_bus_scale_client_update_request(tvenc_bus_scale_handle, + 1); +#else + if (mfd->ebi1_clk) + clk_enable(mfd->ebi1_clk); +#endif + mdp_set_core_clk(1); + mdp4_extn_disp = 1; + if (tvenc_pdata && tvenc_pdata->pm_vid_en) + ret = tvenc_pdata->pm_vid_en(1); + if (ret) { + pr_err("%s: pm_vid_en(on) failed! %d\n", + __func__, ret); + return ret; + } + + ret = tvenc_set_clock(CLOCK_ON); + if (ret) { + pr_err("%s: tvenc_set_clock(CLOCK_ON) failed! %d\n", + __func__, ret); + tvenc_pdata->pm_vid_en(0); + goto error; + } + + ret = panel_next_on(pdev); + if (ret) { + pr_err("%s: tvout_on failed! %d\n", + __func__, ret); + tvenc_set_clock(CLOCK_OFF); + tvenc_pdata->pm_vid_en(0); + } + +error: + return ret; + +} + +void tvenc_gen_test_pattern(struct msm_fb_data_type *mfd) +{ + uint32 reg = 0, i; + + reg = readl(MSM_TV_ENC_CTL); + reg |= TVENC_CTL_TEST_PATT_EN; + + for (i = 0; i < 3; i++) { + TV_OUT(TV_ENC_CTL, 0); /* disable TV encoder */ + + switch (i) { + /* + * TV Encoder - Color Bar Test Pattern + */ + case 0: + reg |= TVENC_CTL_TPG_CLRBAR; + break; + /* + * TV Encoder - Red Frame Test Pattern + */ + case 1: + reg |= TVENC_CTL_TPG_REDCLR; + break; + /* + * TV Encoder - Modulated Ramp Test Pattern + */ + default: + reg |= TVENC_CTL_TPG_MODRAMP; + break; + } + + TV_OUT(TV_ENC_CTL, reg); + mdelay(5000); + + switch (i) { + /* + * TV Encoder - Color Bar Test Pattern + */ + case 0: + reg &= ~TVENC_CTL_TPG_CLRBAR; + break; + /* + * TV Encoder - Red Frame Test Pattern + */ + case 1: + reg &= ~TVENC_CTL_TPG_REDCLR; + break; + /* + * TV Encoder - Modulated Ramp Test Pattern + */ + default: + reg &= ~TVENC_CTL_TPG_MODRAMP; + break; + } + } +} + +static int tvenc_resource_initialized; + +static int tvenc_probe(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + struct platform_device *mdp_dev = NULL; + struct msm_fb_panel_data *pdata = NULL; + int rc; + + if (pdev->id == 0) { + tvenc_base = ioremap(pdev->resource[0].start, + pdev->resource[0].end - + pdev->resource[0].start + 1); + if (!tvenc_base) { + pr_err("tvenc_base ioremap failed!\n"); + return -ENOMEM; + } + tvenc_pdata = pdev->dev.platform_data; + tvenc_resource_initialized = 1; + return 0; + } + + if (!tvenc_resource_initialized) + return -EPERM; + + mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + + if (pdev_list_cnt >= MSM_FB_MAX_DEV_LIST) + return -ENOMEM; + + if (tvenc_base == NULL) + return -ENOMEM; + + mdp_dev = platform_device_alloc("mdp", pdev->id); + if (!mdp_dev) + return -ENOMEM; + + /* + * link to the latest pdev + */ + mfd->pdev = mdp_dev; + mfd->dest = DISPLAY_TV; + + /* + * alloc panel device data + */ + if (platform_device_add_data + (mdp_dev, pdev->dev.platform_data, + sizeof(struct msm_fb_panel_data))) { + pr_err("tvenc_probe: platform_device_add_data failed!\n"); + platform_device_put(mdp_dev); + return -ENOMEM; + } + /* + * data chain + */ + pdata = mdp_dev->dev.platform_data; + pdata->on = tvenc_on; + pdata->off = tvenc_off; + pdata->next = pdev; + + /* + * get/set panel specific fb info + */ + mfd->panel_info = pdata->panel_info; +#ifdef CONFIG_FB_MSM_MDP40 + mfd->fb_imgType = MDP_RGB_565; /* base layer */ +#else + mfd->fb_imgType = MDP_YCRYCB_H2V1; +#endif + +#ifdef CONFIG_MSM_BUS_SCALING + if (!tvenc_bus_scale_handle && tvenc_pdata && + tvenc_pdata->bus_scale_table) { + tvenc_bus_scale_handle = + msm_bus_scale_register_client( + tvenc_pdata->bus_scale_table); + if (!tvenc_bus_scale_handle) { + printk(KERN_ERR "%s not able to get bus scale\n", + __func__); + } + } +#else + mfd->ebi1_clk = clk_get(NULL, "ebi1_tv_clk"); + if (IS_ERR(mfd->ebi1_clk)) { + rc = PTR_ERR(mfd->ebi1_clk); + goto tvenc_probe_err; + } + clk_set_rate(mfd->ebi1_clk, MSM_SYSTEM_BUS_RATE); +#endif + + /* + * set driver data + */ + platform_set_drvdata(mdp_dev, mfd); + + /* + * register in mdp driver + */ + rc = platform_device_add(mdp_dev); + if (rc) + goto tvenc_probe_err; + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + + + pdev_list[pdev_list_cnt++] = pdev; + + return 0; + +tvenc_probe_err: +#ifdef CONFIG_MSM_BUS_SCALING + if (tvenc_pdata && tvenc_pdata->bus_scale_table && + tvenc_bus_scale_handle > 0) { + msm_bus_scale_unregister_client(tvenc_bus_scale_handle); + tvenc_bus_scale_handle = 0; + } +#endif + platform_device_put(mdp_dev); + return rc; +} + +static int tvenc_remove(struct platform_device *pdev) +{ + struct msm_fb_data_type *mfd; + + mfd = platform_get_drvdata(pdev); + +#ifdef CONFIG_MSM_BUS_SCALING + if (tvenc_pdata && tvenc_pdata->bus_scale_table && + tvenc_bus_scale_handle > 0) { + msm_bus_scale_unregister_client(tvenc_bus_scale_handle); + tvenc_bus_scale_handle = 0; + } +#else + clk_put(mfd->ebi1_clk); +#endif + + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int tvenc_register_driver(void) +{ + return platform_driver_register(&tvenc_driver); +} + +static int __init tvenc_driver_init(void) +{ + int ret; + tvenc_clk = clk_get(NULL, "tv_enc_clk"); + tvdac_clk = clk_get(NULL, "tv_dac_clk"); + tvenc_pclk = clk_get(NULL, "tv_enc_pclk"); + mdp_tv_clk = clk_get(NULL, "mdp_tv_clk"); + +#ifdef CONFIG_FB_MSM_MDP40 + tv_src_clk = clk_get(NULL, "tv_src_clk"); + if (IS_ERR(tv_src_clk)) + tv_src_clk = tvenc_clk; /* Fallback to slave */ +#endif + + if (IS_ERR(tvenc_clk)) { + pr_err("%s: error: can't get tvenc_clk!\n", __func__); + return PTR_ERR(tvenc_clk); + } + + if (IS_ERR(tvdac_clk)) { + pr_err("%s: error: can't get tvdac_clk!\n", __func__); + return PTR_ERR(tvdac_clk); + } + + if (IS_ERR(tvenc_pclk)) { + ret = PTR_ERR(tvenc_pclk); + if (-ENOENT == ret) + pr_info("%s: tvenc_pclk does not exist!\n", __func__); + else { + pr_err("%s: error: can't get tvenc_pclk!\n", __func__); + return ret; + } + } + + if (IS_ERR(mdp_tv_clk)) { + ret = PTR_ERR(mdp_tv_clk); + if (-ENOENT == ret) + pr_info("%s: mdp_tv_clk does not exist!\n", __func__); + else { + pr_err("%s: error: can't get mdp_tv_clk!\n", __func__); + return ret; + } + } + + return tvenc_register_driver(); +} + +module_init(tvenc_driver_init); diff --git a/drivers/video/msm/tvenc.h b/drivers/video/msm/tvenc.h new file mode 100644 index 00000000000..c64c16067d9 --- /dev/null +++ b/drivers/video/msm/tvenc.h @@ -0,0 +1,129 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef TVENC_H +#define TVENC_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "msm_fb_panel.h" + +#define NTSC_M 0 /* North America, Korea */ +#define NTSC_J 1 /* Japan */ +#define PAL_BDGHIN 2 /* Non-argentina PAL-N */ +#define PAL_M 3 /* PAL-M */ +#define PAL_N 4 /* Argentina PAL-N */ + +#define CLOCK_OFF 0 +#define CLOCK_ON 1 + +/* 3.57954545 Mhz */ +#define TVENC_CTL_TV_MODE_NTSC_M_PAL60 0 +/* 3.57961149 Mhz */ +#define TVENC_CTL_TV_MODE_PAL_M BIT(0) +/*non-Argintina = 4.3361875 Mhz */ +#define TVENC_CTL_TV_MODE_PAL_BDGHIN BIT(1) +/*Argentina = 3.582055625 Mhz */ +#define TVENC_CTL_TV_MODE_PAL_N (BIT(1)|BIT(0)) + +#define TVENC_CTL_ENC_EN BIT(2) +#define TVENC_CTL_CC_EN BIT(3) +#define TVENC_CTL_CGMS_EN BIT(4) +#define TVENC_CTL_MACRO_EN BIT(5) +#define TVENC_CTL_Y_FILTER_W_NOTCH BIT(6) +#define TVENC_CTL_Y_FILTER_WO_NOTCH 0 +#define TVENC_CTL_Y_FILTER_EN BIT(7) +#define TVENC_CTL_CR_FILTER_EN BIT(8) +#define TVENC_CTL_CB_FILTER_EN BIT(9) +#define TVENC_CTL_SINX_FILTER_EN BIT(10) +#define TVENC_CTL_TEST_PATT_EN BIT(11) +#define TVENC_CTL_OUTPUT_INV BIT(12) +#define TVENC_CTL_PAL60_MODE BIT(13) +#define TVENC_CTL_NTSCJ_MODE BIT(14) +#define TVENC_CTL_S_VIDEO_EN BIT(19) + + +#define TVENC_CTL_TPG_CLRBAR 0 +#define TVENC_CTL_TPG_MODRAMP BIT(15) +#define TVENC_CTL_TPG_REDCLR BIT(16) +#define TVENC_CTL_TPG_NTSC_CBAR (BIT(16)|BIT(15)) +#define TVENC_CTL_TPG_BLACK BIT(17) +#define TVENC_CTL_TPG_WHITE100 (BIT(17)|BIT(15)) +#define TVENC_CTL_TPG_YELLOW75 (BIT(17)|BIT(16)) +#define TVENC_CTL_TPG_CYAN75 (BIT(17)|BIT(16)|BIT(15)) +#define TVENC_CTL_TPG_GREEN75 BIT(18) +#define TVENC_CTL_TPG_MAGENTA75 (BIT(18)|BIT(15)) +#define TVENC_CTL_TPG_RED75 (BIT(18)|BIT(16)) +#define TVENC_CTL_TPG_BLUE75 (BIT(18)|BIT(16)|BIT(15)) +#define TVENC_CTL_TPG_WHITE75 (BIT(18)|BIT(17)) +#define TVENC_CTL_TPG_WHITE_TRSTN (BIT(18)|BIT(17)|BIT(15)) + +#define TVENC_LOAD_DETECT_EN BIT(8) + +#ifdef TVENC_C +void *tvenc_base; +struct tvenc_platform_data *tvenc_pdata; +#else +extern void *tvenc_base; +extern struct tvenc_platform_data *tvenc_pdata; +#endif + +#define TV_OUT(reg, v) writel(v, tvenc_base + MSM_##reg) +#define TV_IN(reg) readl(tvenc_base + MSM_##reg) + +#define MSM_TV_ENC_CTL 0x00 +#define MSM_TV_LEVEL 0x04 +#define MSM_TV_GAIN 0x08 +#define MSM_TV_OFFSET 0x0c +#define MSM_TV_CGMS 0x10 +#define MSM_TV_SYNC_1 0x14 +#define MSM_TV_SYNC_2 0x18 +#define MSM_TV_SYNC_3 0x1c +#define MSM_TV_SYNC_4 0x20 +#define MSM_TV_SYNC_5 0x24 +#define MSM_TV_SYNC_6 0x28 +#define MSM_TV_SYNC_7 0x2c +#define MSM_TV_BURST_V1 0x30 +#define MSM_TV_BURST_V2 0x34 +#define MSM_TV_BURST_V3 0x38 +#define MSM_TV_BURST_V4 0x3c +#define MSM_TV_BURST_H 0x40 +#define MSM_TV_SOL_REQ_ODD 0x44 +#define MSM_TV_SOL_REQ_EVEN 0x48 +#define MSM_TV_DAC_CTL 0x4c +#define MSM_TV_TEST_MUX 0x50 +#define MSM_TV_TEST_MODE 0x54 +#define MSM_TV_TEST_MISR_RESET 0x58 +#define MSM_TV_TEST_EXPORT_MISR 0x5c +#define MSM_TV_TEST_MISR_CURR_VAL 0x60 +#define MSM_TV_TEST_SOF_CFG 0x64 +#define MSM_TV_DAC_INTF 0x100 + +#define MSM_TV_INTR_ENABLE 0x200 +#define MSM_TV_INTR_STATUS 0x204 +#define MSM_TV_INTR_CLEAR 0x208 + +int tvenc_set_encoder_clock(boolean clock_on); +int tvenc_set_clock(boolean clock_on); +#endif /* TVENC_H */ diff --git a/drivers/video/msm/tvout_msm.c b/drivers/video/msm/tvout_msm.c new file mode 100644 index 00000000000..69129614d61 --- /dev/null +++ b/drivers/video/msm/tvout_msm.c @@ -0,0 +1,648 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include "msm_fb.h" +#include "tvenc.h" +#include "external_common.h" + +#define TVOUT_HPD_DUTY_CYCLE 3000 + +#define TV_DIMENSION_MAX_WIDTH 720 +#define TV_DIMENSION_MAX_HEIGHT 576 + +struct tvout_msm_state_type { + struct external_common_state_type common; + struct platform_device *pdev; + struct timer_list hpd_state_timer; + struct timer_list hpd_work_timer; + struct work_struct hpd_work; + uint32 hpd_int_status; + uint32 prev_hpd_int_status; + uint32 five_retry; + int irq; + uint16 y_res; + boolean hpd_initialized; + boolean disp_powered_up; +#ifdef CONFIG_SUSPEND + boolean pm_suspended; +#endif + +}; + +static struct tvout_msm_state_type *tvout_msm_state; +static DEFINE_MUTEX(tvout_msm_state_mutex); + +static int tvout_off(struct platform_device *pdev); +static int tvout_on(struct platform_device *pdev); +static void tvout_check_status(void); + +static void tvout_msm_turn_on(boolean power_on) +{ + uint32 reg_val = 0; + reg_val = TV_IN(TV_ENC_CTL); + if (power_on) { + DEV_DBG("%s: TV Encoder turned on\n", __func__); + reg_val |= TVENC_CTL_ENC_EN; + } else { + DEV_DBG("%s: TV Encoder turned off\n", __func__); + reg_val = 0; + } + /* Enable TV Encoder*/ + TV_OUT(TV_ENC_CTL, reg_val); +} + +static void tvout_check_status() +{ + tvout_msm_state->hpd_int_status &= 0x05; + /* hpd_int_status could either be 0x05 or 0x04 for a cable + plug-out event when cable detect is driven by polling. */ + if ((((tvout_msm_state->hpd_int_status == 0x05) || + (tvout_msm_state->hpd_int_status == 0x04)) && + (tvout_msm_state->prev_hpd_int_status == BIT(2))) || + ((tvout_msm_state->hpd_int_status == 0x01) && + (tvout_msm_state->prev_hpd_int_status == BIT(0)))) { + DEV_DBG("%s: cable event sent already!", __func__); + return; + } + + if (tvout_msm_state->hpd_int_status & BIT(2)) { + DEV_DBG("%s: cable plug-out\n", __func__); + mutex_lock(&external_common_state_hpd_mutex); + external_common_state->hpd_state = FALSE; + mutex_unlock(&external_common_state_hpd_mutex); + kobject_uevent(external_common_state->uevent_kobj, + KOBJ_OFFLINE); + tvout_msm_state->prev_hpd_int_status = BIT(2); + } else if (tvout_msm_state->hpd_int_status & BIT(0)) { + DEV_DBG("%s: cable plug-in\n", __func__); + mutex_lock(&external_common_state_hpd_mutex); + external_common_state->hpd_state = TRUE; + mutex_unlock(&external_common_state_hpd_mutex); + kobject_uevent(external_common_state->uevent_kobj, + KOBJ_ONLINE); + tvout_msm_state->prev_hpd_int_status = BIT(0); + } +} + +/* ISR for TV out cable detect */ +static irqreturn_t tvout_msm_isr(int irq, void *dev_id) +{ + tvout_msm_state->hpd_int_status = TV_IN(TV_INTR_STATUS); + TV_OUT(TV_INTR_CLEAR, tvout_msm_state->hpd_int_status); + DEV_DBG("%s: ISR: 0x%02x\n", __func__, + tvout_msm_state->hpd_int_status & 0x05); + + if (tvenc_pdata->poll) + if (!tvout_msm_state || !tvout_msm_state->disp_powered_up) { + DEV_DBG("%s: ISR ignored, display not yet powered on\n", + __func__); + return IRQ_HANDLED; + } + if (tvout_msm_state->hpd_int_status & BIT(0) || + tvout_msm_state->hpd_int_status & BIT(2)) { + /* Use .75sec to debounce the interrupt */ + mod_timer(&tvout_msm_state->hpd_state_timer, jiffies + + msecs_to_jiffies(750)); + } + + return IRQ_HANDLED; +} + +/* Interrupt debounce timer */ +static void tvout_msm_hpd_state_timer(unsigned long data) +{ +#ifdef CONFIG_SUSPEND + mutex_lock(&tvout_msm_state_mutex); + if (tvout_msm_state->pm_suspended) { + mutex_unlock(&tvout_msm_state_mutex); + DEV_WARN("%s: ignored, pm_suspended\n", __func__); + return; + } + mutex_unlock(&tvout_msm_state_mutex); +#endif + + if (tvenc_pdata->poll) + if (!tvout_msm_state || !tvout_msm_state->disp_powered_up) { + DEV_DBG("%s: ignored, display powered off\n", __func__); + return; + } + + /* TV_INTR_STATUS[0x204] + When a TV_ENC interrupt occurs, then reading this register will + indicate what caused the interrupt since that each bit indicates + the source of the interrupt that had happened. If multiple + interrupt sources had happened, then multiple bits of this + register will be set + Bit 0 : Load present on Video1 + Bit 1 : Load present on Video2 + Bit 2 : Load removed on Video1 + Bit 3 : Load removed on Video2 + */ + + /* Locking interrupt status is not required because + last status read after debouncing is used */ + if ((tvout_msm_state->hpd_int_status & 0x05) == 0x05) { + /* SW-workaround :If the status read after debouncing is + 0x05(indicating both load present & load removed- which can't + happen in reality), force an update. If status remains 0x05 + after retry, it's a cable unplug event */ + if (++tvout_msm_state->five_retry < 2) { + uint32 reg; + DEV_DBG("tvout: Timer: 0x05\n"); + TV_OUT(TV_INTR_CLEAR, 0xf); + reg = TV_IN(TV_DAC_INTF); + TV_OUT(TV_DAC_INTF, reg & ~TVENC_LOAD_DETECT_EN); + TV_OUT(TV_INTR_CLEAR, 0xf); + reg = TV_IN(TV_DAC_INTF); + TV_OUT(TV_DAC_INTF, reg | TVENC_LOAD_DETECT_EN); + return; + } + } + tvout_msm_state->five_retry = 0; + tvout_check_status(); +} + +static void tvout_msm_hpd_work(struct work_struct *work) +{ + uint32 reg; + +#ifdef CONFIG_SUSPEND + mutex_lock(&tvout_msm_state_mutex); + if (tvout_msm_state->pm_suspended) { + mutex_unlock(&tvout_msm_state_mutex); + DEV_WARN("%s: ignored, pm_suspended\n", __func__); + return; + } + mutex_unlock(&tvout_msm_state_mutex); +#endif + + /* Enable power lines & clocks */ + tvenc_pdata->pm_vid_en(1); + tvenc_set_clock(CLOCK_ON); + + /* Enable encoder to get a stable interrupt */ + reg = TV_IN(TV_ENC_CTL); + TV_OUT(TV_ENC_CTL, reg | TVENC_CTL_ENC_EN); + + /* SW- workaround to update status register */ + reg = TV_IN(TV_DAC_INTF); + TV_OUT(TV_DAC_INTF, reg & ~TVENC_LOAD_DETECT_EN); + TV_OUT(TV_INTR_CLEAR, 0xf); + reg = TV_IN(TV_DAC_INTF); + TV_OUT(TV_DAC_INTF, reg | TVENC_LOAD_DETECT_EN); + + tvout_msm_state->hpd_int_status = TV_IN(TV_INTR_STATUS); + + /* Disable TV encoder */ + reg = TV_IN(TV_ENC_CTL); + TV_OUT(TV_ENC_CTL, reg & ~TVENC_CTL_ENC_EN); + + /*Disable power lines & clocks */ + tvenc_set_clock(CLOCK_OFF); + tvenc_pdata->pm_vid_en(0); + + DEV_DBG("%s: ISR: 0x%02x\n", __func__, + tvout_msm_state->hpd_int_status & 0x05); + + mod_timer(&tvout_msm_state->hpd_work_timer, jiffies + + msecs_to_jiffies(TVOUT_HPD_DUTY_CYCLE)); + + tvout_check_status(); +} + +static void tvout_msm_hpd_work_timer(unsigned long data) +{ + schedule_work(&tvout_msm_state->hpd_work); +} + +static int tvout_on(struct platform_device *pdev) +{ + uint32 reg = 0; + struct fb_var_screeninfo *var; + struct msm_fb_data_type *mfd = platform_get_drvdata(pdev); + + if (!mfd) + return -ENODEV; + + if (mfd->key != MFD_KEY) + return -EINVAL; + +#ifdef CONFIG_SUSPEND + mutex_lock(&tvout_msm_state_mutex); + if (tvout_msm_state->pm_suspended) { + mutex_unlock(&tvout_msm_state_mutex); + DEV_WARN("%s: ignored, pm_suspended\n", __func__); + return -ENODEV; + } + mutex_unlock(&tvout_msm_state_mutex); +#endif + + var = &mfd->fbi->var; + if (var->reserved[3] >= NTSC_M && var->reserved[3] <= PAL_N) + external_common_state->video_resolution = var->reserved[3]; + + tvout_msm_state->pdev = pdev; + if (del_timer(&tvout_msm_state->hpd_work_timer)) + DEV_DBG("%s: work timer stopped\n", __func__); + + TV_OUT(TV_ENC_CTL, 0); /* disable TV encoder */ + + switch (external_common_state->video_resolution) { + case NTSC_M: + case NTSC_J: + TV_OUT(TV_CGMS, 0x0); + /* NTSC Timing */ + TV_OUT(TV_SYNC_1, 0x0020009e); + TV_OUT(TV_SYNC_2, 0x011306B4); + TV_OUT(TV_SYNC_3, 0x0006000C); + TV_OUT(TV_SYNC_4, 0x0028020D); + TV_OUT(TV_SYNC_5, 0x005E02FB); + TV_OUT(TV_SYNC_6, 0x0006000C); + TV_OUT(TV_SYNC_7, 0x00000012); + TV_OUT(TV_BURST_V1, 0x0013020D); + TV_OUT(TV_BURST_V2, 0x0014020C); + TV_OUT(TV_BURST_V3, 0x0013020D); + TV_OUT(TV_BURST_V4, 0x0014020C); + TV_OUT(TV_BURST_H, 0x00AE00F2); + TV_OUT(TV_SOL_REQ_ODD, 0x00280208); + TV_OUT(TV_SOL_REQ_EVEN, 0x00290209); + + reg |= TVENC_CTL_TV_MODE_NTSC_M_PAL60; + + if (external_common_state->video_resolution == NTSC_M) { + /* Cr gain 11, Cb gain C6, y_gain 97 */ + TV_OUT(TV_GAIN, 0x0081B697); + } else { + /* Cr gain 11, Cb gain C6, y_gain 97 */ + TV_OUT(TV_GAIN, 0x008bc4a3); + reg |= TVENC_CTL_NTSCJ_MODE; + } + + var->yres = 480; + break; + case PAL_BDGHIN: + case PAL_N: + /* PAL Timing */ + TV_OUT(TV_SYNC_1, 0x00180097); + TV_OUT(TV_SYNC_3, 0x0005000a); + TV_OUT(TV_SYNC_4, 0x00320271); + TV_OUT(TV_SYNC_5, 0x005602f9); + TV_OUT(TV_SYNC_6, 0x0005000a); + TV_OUT(TV_SYNC_7, 0x0000000f); + TV_OUT(TV_BURST_V1, 0x0012026e); + TV_OUT(TV_BURST_V2, 0x0011026d); + TV_OUT(TV_BURST_V3, 0x00100270); + TV_OUT(TV_BURST_V4, 0x0013026f); + TV_OUT(TV_SOL_REQ_ODD, 0x0030026e); + TV_OUT(TV_SOL_REQ_EVEN, 0x0031026f); + + if (external_common_state->video_resolution == PAL_BDGHIN) { + /* Cr gain 11, Cb gain C6, y_gain 97 */ + TV_OUT(TV_GAIN, 0x0088c1a0); + TV_OUT(TV_CGMS, 0x00012345); + TV_OUT(TV_SYNC_2, 0x011f06c0); + TV_OUT(TV_BURST_H, 0x00af00ea); + reg |= TVENC_CTL_TV_MODE_PAL_BDGHIN; + } else { + /* Cr gain 11, Cb gain C6, y_gain 97 */ + TV_OUT(TV_GAIN, 0x0081b697); + TV_OUT(TV_CGMS, 0x000af317); + TV_OUT(TV_SYNC_2, 0x12006c0); + TV_OUT(TV_BURST_H, 0x00af00fa); + reg |= TVENC_CTL_TV_MODE_PAL_N; + } + var->yres = 576; + break; + case PAL_M: + /* Cr gain 11, Cb gain C6, y_gain 97 */ + TV_OUT(TV_GAIN, 0x0081b697); + TV_OUT(TV_CGMS, 0x000af317); + TV_OUT(TV_TEST_MUX, 0x000001c3); + TV_OUT(TV_TEST_MODE, 0x00000002); + /* PAL Timing */ + TV_OUT(TV_SYNC_1, 0x0020009e); + TV_OUT(TV_SYNC_2, 0x011306b4); + TV_OUT(TV_SYNC_3, 0x0006000c); + TV_OUT(TV_SYNC_4, 0x0028020D); + TV_OUT(TV_SYNC_5, 0x005e02fb); + TV_OUT(TV_SYNC_6, 0x0006000c); + TV_OUT(TV_SYNC_7, 0x00000012); + TV_OUT(TV_BURST_V1, 0x0012020b); + TV_OUT(TV_BURST_V2, 0x0016020c); + TV_OUT(TV_BURST_V3, 0x00150209); + TV_OUT(TV_BURST_V4, 0x0013020c); + TV_OUT(TV_BURST_H, 0x00bf010b); + TV_OUT(TV_SOL_REQ_ODD, 0x00280208); + TV_OUT(TV_SOL_REQ_EVEN, 0x00290209); + + reg |= TVENC_CTL_TV_MODE_PAL_M; + var->yres = 480; + break; + default: + return -ENODEV; + } + + reg |= TVENC_CTL_Y_FILTER_EN | TVENC_CTL_CR_FILTER_EN | + TVENC_CTL_CB_FILTER_EN | TVENC_CTL_SINX_FILTER_EN; + + /* DC offset to 0. */ + TV_OUT(TV_LEVEL, 0x00000000); + TV_OUT(TV_OFFSET, 0x008080f0); + +#ifdef CONFIG_FB_MSM_TVOUT_SVIDEO + reg |= TVENC_CTL_S_VIDEO_EN; +#endif +#if defined(CONFIG_FB_MSM_MDP31) + TV_OUT(TV_DAC_INTF, 0x29); +#endif + TV_OUT(TV_ENC_CTL, reg); + + if (!tvout_msm_state->hpd_initialized) { + tvout_msm_state->hpd_initialized = TRUE; + /* Load detect enable */ + reg = TV_IN(TV_DAC_INTF); + reg |= TVENC_LOAD_DETECT_EN; + TV_OUT(TV_DAC_INTF, reg); + } + + tvout_msm_state->disp_powered_up = TRUE; + tvout_msm_turn_on(TRUE); + + if (tvenc_pdata->poll) { + /* Enable Load present & removal interrupts for Video1 */ + TV_OUT(TV_INTR_ENABLE, 0x5); + + /* Enable interrupts when display is on */ + enable_irq(tvout_msm_state->irq); + } + return 0; +} + +static int tvout_off(struct platform_device *pdev) +{ + /* Disable TV encoder irqs when display is off */ + if (tvenc_pdata->poll) + disable_irq(tvout_msm_state->irq); + tvout_msm_turn_on(FALSE); + tvout_msm_state->hpd_initialized = FALSE; + tvout_msm_state->disp_powered_up = FALSE; + if (tvenc_pdata->poll) { + mod_timer(&tvout_msm_state->hpd_work_timer, jiffies + + msecs_to_jiffies(TVOUT_HPD_DUTY_CYCLE)); + } + return 0; +} + +static int __devinit tvout_probe(struct platform_device *pdev) +{ + int rc = 0; + uint32 reg; + struct platform_device *fb_dev; + +#ifdef CONFIG_FB_MSM_TVOUT_NTSC_M + external_common_state->video_resolution = NTSC_M; +#elif defined CONFIG_FB_MSM_TVOUT_NTSC_J + external_common_state->video_resolution = NTSC_J; +#elif defined CONFIG_FB_MSM_TVOUT_PAL_M + external_common_state->video_resolution = PAL_M; +#elif defined CONFIG_FB_MSM_TVOUT_PAL_N + external_common_state->video_resolution = PAL_N; +#elif defined CONFIG_FB_MSM_TVOUT_PAL_BDGHIN + external_common_state->video_resolution = PAL_BDGHIN; +#endif + external_common_state->dev = &pdev->dev; + if (pdev->id == 0) { + struct resource *res; + + #define GET_RES(name, mode) do { \ + res = platform_get_resource_byname(pdev, mode, name); \ + if (!res) { \ + DEV_DBG("'" name "' resource not found\n"); \ + rc = -ENODEV; \ + goto error; \ + } \ + } while (0) + + #define GET_IRQ(var, name) do { \ + GET_RES(name, IORESOURCE_IRQ); \ + var = res->start; \ + } while (0) + + GET_IRQ(tvout_msm_state->irq, "tvout_device_irq"); + #undef GET_IRQ + #undef GET_RES + return 0; + } + + DEV_DBG("%s: tvout_msm_state->irq : %d", + __func__, tvout_msm_state->irq); + + rc = request_irq(tvout_msm_state->irq, &tvout_msm_isr, + IRQF_TRIGGER_HIGH, "tvout_msm_isr", NULL); + + if (rc) { + DEV_DBG("Init FAILED: IRQ request, rc=%d\n", rc); + goto error; + } + disable_irq(tvout_msm_state->irq); + + init_timer(&tvout_msm_state->hpd_state_timer); + tvout_msm_state->hpd_state_timer.function = + tvout_msm_hpd_state_timer; + tvout_msm_state->hpd_state_timer.data = (uint32)NULL; + tvout_msm_state->hpd_state_timer.expires = jiffies + + msecs_to_jiffies(1000); + + if (tvenc_pdata->poll) { + init_timer(&tvout_msm_state->hpd_work_timer); + tvout_msm_state->hpd_work_timer.function = + tvout_msm_hpd_work_timer; + tvout_msm_state->hpd_work_timer.data = (uint32)NULL; + tvout_msm_state->hpd_work_timer.expires = jiffies + + msecs_to_jiffies(1000); + } + fb_dev = msm_fb_add_device(pdev); + if (fb_dev) { + rc = external_common_state_create(fb_dev); + if (rc) { + DEV_ERR("Init FAILED: tvout_msm_state_create, rc=%d\n", + rc); + goto error; + } + if (tvenc_pdata->poll) { + /* Start polling timer to detect load */ + mod_timer(&tvout_msm_state->hpd_work_timer, jiffies + + msecs_to_jiffies(TVOUT_HPD_DUTY_CYCLE)); + } else { + /* Enable interrupt to detect load */ + tvenc_set_encoder_clock(CLOCK_ON); + reg = TV_IN(TV_DAC_INTF); + reg |= TVENC_LOAD_DETECT_EN; + TV_OUT(TV_DAC_INTF, reg); + TV_OUT(TV_INTR_ENABLE, 0x5); + enable_irq(tvout_msm_state->irq); + } + } else + DEV_ERR("Init FAILED: failed to add fb device\n"); +error: + return 0; +} + +static int __devexit tvout_remove(struct platform_device *pdev) +{ + external_common_state_remove(); + kfree(tvout_msm_state); + tvout_msm_state = NULL; + return 0; +} + +#ifdef CONFIG_SUSPEND +static int tvout_device_pm_suspend(struct device *dev) +{ + mutex_lock(&tvout_msm_state_mutex); + if (tvout_msm_state->pm_suspended) { + mutex_unlock(&tvout_msm_state_mutex); + return 0; + } + if (tvenc_pdata->poll) { + if (del_timer(&tvout_msm_state->hpd_work_timer)) + DEV_DBG("%s: suspending cable detect timer\n", + __func__); + } else { + disable_irq(tvout_msm_state->irq); + tvenc_set_encoder_clock(CLOCK_OFF); + } + tvout_msm_state->pm_suspended = TRUE; + mutex_unlock(&tvout_msm_state_mutex); + return 0; +} + +static int tvout_device_pm_resume(struct device *dev) +{ + mutex_lock(&tvout_msm_state_mutex); + if (!tvout_msm_state->pm_suspended) { + mutex_unlock(&tvout_msm_state_mutex); + return 0; + } + + if (tvenc_pdata->poll) { + tvout_msm_state->pm_suspended = FALSE; + mod_timer(&tvout_msm_state->hpd_work_timer, jiffies + + msecs_to_jiffies(TVOUT_HPD_DUTY_CYCLE)); + mutex_unlock(&tvout_msm_state_mutex); + DEV_DBG("%s: resuming cable detect timer\n", __func__); + } else { + tvenc_set_encoder_clock(CLOCK_ON); + tvout_msm_state->pm_suspended = FALSE; + mutex_unlock(&tvout_msm_state_mutex); + enable_irq(tvout_msm_state->irq); + DEV_DBG("%s: enable cable detect interrupt\n", __func__); + } + return 0; +} +#else +#define tvout_device_pm_suspend NULL +#define tvout_device_pm_resume NULL +#endif + + +static const struct dev_pm_ops tvout_device_pm_ops = { + .suspend = tvout_device_pm_suspend, + .resume = tvout_device_pm_resume, +}; + +static struct platform_driver this_driver = { + .probe = tvout_probe, + .remove = tvout_remove, + .driver = { + .name = "tvout_device", + .pm = &tvout_device_pm_ops, + }, +}; + +static struct msm_fb_panel_data tvout_panel_data = { + .panel_info.xres = TV_DIMENSION_MAX_WIDTH, + .panel_info.yres = TV_DIMENSION_MAX_HEIGHT, + .panel_info.type = TV_PANEL, + .panel_info.pdest = DISPLAY_2, + .panel_info.wait_cycle = 0, +#ifdef CONFIG_FB_MSM_MDP40 + .panel_info.bpp = 24, +#else + .panel_info.bpp = 16, +#endif + .panel_info.fb_num = 2, + .on = tvout_on, + .off = tvout_off, +}; + +static struct platform_device this_device = { + .name = "tvout_device", + .id = 1, + .dev = { + .platform_data = &tvout_panel_data, + } +}; + +static int __init tvout_init(void) +{ + int ret; + tvout_msm_state = kzalloc(sizeof(*tvout_msm_state), GFP_KERNEL); + if (!tvout_msm_state) { + DEV_ERR("tvout_msm_init FAILED: out of memory\n"); + ret = -ENOMEM; + goto init_exit; + } + + external_common_state = &tvout_msm_state->common; + ret = platform_driver_register(&this_driver); + if (ret) { + DEV_ERR("tvout_device_init FAILED: platform_driver_register\ + rc=%d\n", ret); + goto init_exit; + } + + ret = platform_device_register(&this_device); + if (ret) { + DEV_ERR("tvout_device_init FAILED: platform_driver_register\ + rc=%d\n", ret); + platform_driver_unregister(&this_driver); + goto init_exit; + } + + INIT_WORK(&tvout_msm_state->hpd_work, tvout_msm_hpd_work); + return 0; + +init_exit: + kfree(tvout_msm_state); + tvout_msm_state = NULL; + return ret; +} + +static void __exit tvout_exit(void) +{ + platform_device_unregister(&this_device); + platform_driver_unregister(&this_driver); +} + +module_init(tvout_init); +module_exit(tvout_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("1.0"); +MODULE_AUTHOR("Qualcomm Innovation Center, Inc."); +MODULE_DESCRIPTION("TV out driver"); diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl.c b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl.c new file mode 100644 index 00000000000..b55c8847b1c --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl.c @@ -0,0 +1,634 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include "vcd_ddl.h" +#include "vcd_ddl_metadata.h" +#include "vcd_res_tracker_api.h" + +static unsigned int first_time; + +u32 ddl_device_init(struct ddl_init_config *ddl_init_config, + void *client_data) +{ + struct ddl_context *ddl_context; + struct res_trk_firmware_addr firmware_addr; + u32 status = VCD_S_SUCCESS; + void *ptr = NULL; + DDL_MSG_HIGH("ddl_device_init"); + + if ((!ddl_init_config) || (!ddl_init_config->ddl_callback) || + (!ddl_init_config->core_virtual_base_addr)) { + DDL_MSG_ERROR("ddl_dev_init:Bad_argument"); + return VCD_ERR_ILLEGAL_PARM; + } + ddl_context = ddl_get_context(); + if (DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_dev_init:Multiple_init"); + return VCD_ERR_ILLEGAL_OP; + } + if (!DDL_IS_IDLE(ddl_context)) { + DDL_MSG_ERROR("ddl_dev_init:Ddl_busy"); + return VCD_ERR_BUSY; + } + memset(ddl_context, 0, sizeof(struct ddl_context)); + DDL_BUSY(ddl_context); + ddl_context->memtype = res_trk_get_mem_type(); + if (ddl_context->memtype == -1) { + DDL_MSG_ERROR("ddl_dev_init:Illegal memtype"); + return VCD_ERR_ILLEGAL_PARM; + } + ddl_context->ddl_callback = ddl_init_config->ddl_callback; + if (ddl_init_config->interrupt_clr) + ddl_context->interrupt_clr = + ddl_init_config->interrupt_clr; + ddl_context->core_virtual_base_addr = + ddl_init_config->core_virtual_base_addr; + ddl_context->client_data = client_data; + ddl_context->ddl_hw_response.arg1 = DDL_INVALID_INTR_STATUS; + + ddl_context->frame_channel_depth = VCD_FRAME_COMMAND_DEPTH; + + DDL_MSG_LOW("%s() : virtual address of core(%x)\n", __func__, + (u32) ddl_init_config->core_virtual_base_addr); + vidc_1080p_set_device_base_addr( + ddl_context->core_virtual_base_addr); + ddl_context->cmd_state = DDL_CMD_INVALID; + ddl_client_transact(DDL_INIT_CLIENTS, NULL); + ddl_context->fw_memory_size = + DDL_FW_INST_GLOBAL_CONTEXT_SPACE_SIZE; + if (ddl_context->memtype == MEMTYPE_SMI_KERNEL) { + ptr = ddl_pmem_alloc(&ddl_context->dram_base_a, + ddl_context->fw_memory_size, DDL_KILO_BYTE(128)); + } else { + if (!res_trk_get_firmware_addr(&firmware_addr) && + firmware_addr.buf_size >= ddl_context->fw_memory_size) { + if (DDL_ADDR_IS_ALIGNED(firmware_addr.device_addr, + DDL_KILO_BYTE(128))) { + ptr = (void *) firmware_addr.base_addr; + ddl_context->dram_base_a.physical_base_addr = + ddl_context->dram_base_a.align_physical_addr = + (u8 *)firmware_addr.device_addr; + ddl_context->dram_base_a.align_virtual_addr = + ddl_context->dram_base_a.virtual_base_addr = + firmware_addr.base_addr; + ddl_context->dram_base_a.buffer_size = + ddl_context->fw_memory_size; + } else { + DDL_MSG_ERROR("firmware base not aligned %p", + (void *)firmware_addr.device_addr); + } + } + } + if (!ptr) { + DDL_MSG_ERROR("Memory Aocation Failed for FW Base"); + status = VCD_ERR_ALLOC_FAIL; + } else { + DDL_MSG_LOW("%s() : physical address of base(%x)\n", + __func__, (u32) ddl_context->dram_base_a.\ + align_physical_addr); + ddl_context->dram_base_b.align_physical_addr = + ddl_context->dram_base_a.align_physical_addr; + ddl_context->dram_base_b.align_virtual_addr = + ddl_context->dram_base_a.align_virtual_addr; + } + if (!status) { + ptr = ddl_pmem_alloc(&ddl_context->metadata_shared_input, + DDL_METADATA_TOTAL_INPUTBUFSIZE, + DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (!ptr) { + DDL_MSG_ERROR("ddl_device_init: metadata alloc fail"); + status = VCD_ERR_ALLOC_FAIL; + } + } + if (!status && !ddl_fw_init(&ddl_context->dram_base_a)) { + DDL_MSG_ERROR("ddl_dev_init:fw_init_failed"); + status = VCD_ERR_ALLOC_FAIL; + } + if (!status && ddl_context->memtype == MEMTYPE_EBI1) + clean_caches((unsigned long)firmware_addr.base_addr, + firmware_addr.buf_size, firmware_addr.device_addr); + + if (!status) { + ddl_context->cmd_state = DDL_CMD_DMA_INIT; + ddl_vidc_core_init(ddl_context); + } else { + ddl_release_context_buffers(ddl_context); + DDL_IDLE(ddl_context); + } + return status; +} + +u32 ddl_device_release(void *client_data) +{ + struct ddl_context *ddl_context; + + DDL_MSG_HIGH("ddl_device_release"); + ddl_context = ddl_get_context(); + if (!DDL_IS_IDLE(ddl_context)) { + DDL_MSG_ERROR("ddl_dev_rel:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_dev_rel:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (!ddl_client_transact(DDL_ACTIVE_CLIENT, NULL)) { + DDL_MSG_ERROR("ddl_dev_rel:Client_present_err"); + return VCD_ERR_CLIENT_PRESENT; + } + DDL_BUSY(ddl_context); + ddl_context->device_state = DDL_DEVICE_NOTINIT; + ddl_context->client_data = client_data; + ddl_context->cmd_state = DDL_CMD_INVALID; + ddl_vidc_core_term(ddl_context); + DDL_MSG_LOW("FW_ENDDONE"); + ddl_context->core_virtual_base_addr = NULL; + ddl_release_context_buffers(ddl_context); + DDL_IDLE(ddl_context); + return VCD_S_SUCCESS; +} + +u32 ddl_open(u32 **ddl_handle, u32 decoding) +{ + struct ddl_context *ddl_context; + struct ddl_client_context *ddl; + void *ptr; + u32 status; + + DDL_MSG_HIGH("ddl_open"); + if (!ddl_handle) { + DDL_MSG_ERROR("ddl_open:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_open:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + status = ddl_client_transact(DDL_GET_CLIENT, &ddl); + if (status) { + DDL_MSG_ERROR("ddl_open:Client_trasac_failed"); + return status; + } + ptr = ddl_pmem_alloc(&ddl->shared_mem[0], + DDL_FW_AUX_HOST_CMD_SPACE_SIZE, sizeof(u32)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + if (!status && ddl_context->frame_channel_depth + == VCD_DUAL_FRAME_COMMAND_CHANNEL) { + ptr = ddl_pmem_alloc(&ddl->shared_mem[1], + DDL_FW_AUX_HOST_CMD_SPACE_SIZE, sizeof(u32)); + if (!ptr) { + ddl_pmem_free(&ddl->shared_mem[0]); + status = VCD_ERR_ALLOC_FAIL; + } + } + if (!status) { + memset(ddl->shared_mem[0].align_virtual_addr, 0, + DDL_FW_AUX_HOST_CMD_SPACE_SIZE); + if (ddl_context->frame_channel_depth == + VCD_DUAL_FRAME_COMMAND_CHANNEL) { + memset(ddl->shared_mem[1].align_virtual_addr, 0, + DDL_FW_AUX_HOST_CMD_SPACE_SIZE); + } + DDL_MSG_LOW("ddl_state_transition: %s ~~> DDL_CLIENT_OPEN", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_OPEN; + ddl->codec_data.hdr.decoding = decoding; + ddl->decoding = decoding; + ddl_set_default_meta_data_hdr(ddl); + ddl_set_initial_default_values(ddl); + *ddl_handle = (u32 *) ddl; + } else { + ddl_pmem_free(&ddl->shared_mem[0]); + if (ddl_context->frame_channel_depth + == VCD_DUAL_FRAME_COMMAND_CHANNEL) + ddl_pmem_free(&ddl->shared_mem[1]); + ddl_client_transact(DDL_FREE_CLIENT, &ddl); + } + return status; +} + +u32 ddl_close(u32 **ddl_handle) +{ + struct ddl_context *ddl_context; + struct ddl_client_context **pp_ddl = + (struct ddl_client_context **)ddl_handle; + + DDL_MSG_HIGH("ddl_close"); + if (!pp_ddl || !*pp_ddl) { + DDL_MSG_ERROR("ddl_close:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_close:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (!DDLCLIENT_STATE_IS(*pp_ddl, DDL_CLIENT_OPEN)) { + DDL_MSG_ERROR("ddl_close:Not_in_open_state"); + return VCD_ERR_ILLEGAL_OP; + } + ddl_pmem_free(&(*pp_ddl)->shared_mem[0]); + if (ddl_context->frame_channel_depth == + VCD_DUAL_FRAME_COMMAND_CHANNEL) + ddl_pmem_free(&(*pp_ddl)->shared_mem[1]); + DDL_MSG_LOW("ddl_state_transition: %s ~~> DDL_CLIENT_INVALID", + ddl_get_state_string((*pp_ddl)->client_state)); + (*pp_ddl)->client_state = DDL_CLIENT_INVALID; + ddl_codec_type_transact(*pp_ddl, true, (enum vcd_codec)0); + ddl_client_transact(DDL_FREE_CLIENT, pp_ddl); + return VCD_S_SUCCESS; +} + +u32 ddl_encode_start(u32 *ddl_handle, void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + struct ddl_context *ddl_context; + struct ddl_encoder_data *encoder; + void *ptr; + u32 status = VCD_S_SUCCESS; + DDL_MSG_HIGH("ddl_encode_start"); + if (vidc_msg_timing) { + if (first_time < 2) { + ddl_reset_core_time_variables(ENC_OP_TIME); + first_time++; + } + ddl_set_core_start_time(__func__, ENC_OP_TIME); + } + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_enc_start:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + DDL_MSG_ERROR("ddl_enc_start:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || ddl->decoding) { + DDL_MSG_ERROR("ddl_enc_start:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + DDL_MSG_ERROR("ddl_enc_start:Not_opened"); + return VCD_ERR_ILLEGAL_OP; + } + if (!ddl_encoder_ready_to_start(ddl)) { + DDL_MSG_ERROR("ddl_enc_start:Err_param_settings"); + return VCD_ERR_ILLEGAL_OP; + } + encoder = &ddl->codec_data.encoder; + status = ddl_allocate_enc_hw_buffers(ddl); + if (status) + return status; +#ifdef DDL_BUF_LOG + ddl_list_buffers(ddl); +#endif + if ((encoder->codec.codec == VCD_CODEC_MPEG4 && + !encoder->short_header.short_header) || + encoder->codec.codec == VCD_CODEC_H264) { + ptr = ddl_pmem_alloc(&encoder->seq_header, + DDL_ENC_SEQHEADER_SIZE, DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (!ptr) { + ddl_free_enc_hw_buffers(ddl); + DDL_MSG_ERROR("ddl_enc_start:Seq_hdr_alloc_failed"); + return VCD_ERR_ALLOC_FAIL; + } + } else { + encoder->seq_header.buffer_size = 0; + encoder->seq_header.virtual_base_addr = 0; + encoder->seq_header.align_physical_addr = 0; + encoder->seq_header.align_virtual_addr = 0; + } + if (!ddl_take_command_channel(ddl_context, ddl, client_data)) + return VCD_ERR_BUSY; + ddl_vidc_channel_set(ddl); + return status; +} + +u32 ddl_decode_start(u32 *ddl_handle, struct vcd_sequence_hdr *header, + void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + struct ddl_context *ddl_context; + struct ddl_decoder_data *decoder; + u32 status = VCD_S_SUCCESS; + + DDL_MSG_HIGH("ddl_decode_start"); + if (vidc_msg_timing) { + ddl_reset_core_time_variables(DEC_OP_TIME); + ddl_reset_core_time_variables(DEC_IP_TIME); + } + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_dec_start:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + DDL_MSG_ERROR("ddl_dec_start:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || !ddl->decoding) { + DDL_MSG_ERROR("ddl_dec_start:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + DDL_MSG_ERROR("ddl_dec_start:Not_in_opened_state"); + return VCD_ERR_ILLEGAL_OP; + } + + if ((header) && ((!header->sequence_header_len) || + (!header->sequence_header))) { + DDL_MSG_ERROR("ddl_dec_start:Bad_param_seq_header"); + return VCD_ERR_ILLEGAL_PARM; + } + if (!ddl_decoder_ready_to_start(ddl, header)) { + DDL_MSG_ERROR("ddl_dec_start:Err_param_settings"); + return VCD_ERR_ILLEGAL_OP; + } + decoder = &ddl->codec_data.decoder; + status = ddl_allocate_dec_hw_buffers(ddl); + if (status) + return status; +#ifdef DDL_BUF_LOG + ddl_list_buffers(ddl); +#endif + if (!ddl_take_command_channel(ddl_context, ddl, client_data)) + return VCD_ERR_BUSY; + if (header) { + decoder->header_in_start = true; + decoder->decode_config = *header; + } else { + decoder->header_in_start = false; + decoder->decode_config.sequence_header_len = 0; + } + ddl_vidc_channel_set(ddl); + return status; +} + +u32 ddl_decode_frame(u32 *ddl_handle, + struct ddl_frame_data_tag *input_bits, void *client_data) +{ + u32 vcd_status = VCD_S_SUCCESS; + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + struct ddl_context *ddl_context; + struct ddl_decoder_data *decoder; + DDL_MSG_HIGH("ddl_decode_frame"); + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_dec_frame:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + DDL_MSG_ERROR("ddl_dec_frame:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || !ddl->decoding) { + DDL_MSG_ERROR("ddl_dec_frame:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!input_bits || ((!input_bits->vcd_frm.physical || + !input_bits->vcd_frm.data_len) && + (!(VCD_FRAME_FLAG_EOS & input_bits->vcd_frm.flags)))) { + DDL_MSG_ERROR("ddl_dec_frame:Bad_input_param"); + return VCD_ERR_ILLEGAL_PARM; + } + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODEC) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB)) { + DDL_MSG_ERROR("Dec_frame:Wrong_state"); + return VCD_ERR_ILLEGAL_OP; + } + decoder = &(ddl->codec_data.decoder); + if (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODEC) && + !ddl->codec_data.decoder.dp_buf.no_of_dec_pic_buf) { + DDL_MSG_ERROR("ddl_dec_frame:Dpbs_requied"); + return VCD_ERR_ILLEGAL_OP; + } + if (!ddl_take_command_channel(ddl_context, ddl, client_data)) + return VCD_ERR_BUSY; + + ddl->input_frame = *input_bits; + if (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME)) + ddl_vidc_decode_frame_run(ddl); + else { + if (!ddl->codec_data.decoder.dp_buf.no_of_dec_pic_buf) { + DDL_MSG_ERROR("ddl_dec_frame:Dpbs_requied"); + vcd_status = VCD_ERR_ILLEGAL_OP; + } else if (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB)) { + vcd_status = ddl_vidc_decode_set_buffers(ddl); + if (vcd_status) + ddl_release_command_channel(ddl_context, + ddl->command_channel); + } else if (DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_INITCODEC)) { + if (decoder->codec.codec == VCD_CODEC_DIVX_3) { + if ((!decoder->client_frame_size.width) || + (!decoder->client_frame_size.height)) + return VCD_ERR_ILLEGAL_OP; + } + ddl->codec_data.decoder.decode_config.sequence_header = + ddl->input_frame.vcd_frm.physical; + ddl->codec_data.decoder.decode_config.sequence_header_len = + ddl->input_frame.vcd_frm.data_len; + ddl_vidc_decode_init_codec(ddl); + } else { + DDL_MSG_ERROR("Dec_frame:Wrong_state"); + vcd_status = VCD_ERR_ILLEGAL_OP; + } + if (vcd_status) + DDL_IDLE(ddl_context); + } + return vcd_status; +} + +u32 ddl_encode_frame(u32 *ddl_handle, + struct ddl_frame_data_tag *input_frame, + struct ddl_frame_data_tag *output_bit, void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + struct ddl_context *ddl_context; + u32 vcd_status = VCD_S_SUCCESS; + + DDL_MSG_HIGH("ddl_encode_frame"); + if (vidc_msg_timing) + ddl_set_core_start_time(__func__, ENC_OP_TIME); + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_enc_frame:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + DDL_MSG_ERROR("ddl_enc_frame:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || ddl->decoding) { + DDL_MSG_ERROR("ddl_enc_frame:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!input_frame || !input_frame->vcd_frm.physical || + !input_frame->vcd_frm.data_len) { + DDL_MSG_ERROR("ddl_enc_frame:Bad_input_params"); + return VCD_ERR_ILLEGAL_PARM; + } + if ((((u32) input_frame->vcd_frm.physical + + input_frame->vcd_frm.offset) & + (DDL_STREAMBUF_ALIGN_GUARD_BYTES))) { + DDL_MSG_ERROR("ddl_enc_frame:Un_aligned_yuv_start_address"); + return VCD_ERR_ILLEGAL_PARM; + } + if (!output_bit || !output_bit->vcd_frm.physical || + !output_bit->vcd_frm.alloc_len) { + DDL_MSG_ERROR("ddl_enc_frame:Bad_output_params"); + return VCD_ERR_ILLEGAL_PARM; + } + if ((ddl->codec_data.encoder.output_buf_req.sz + + output_bit->vcd_frm.offset) > + output_bit->vcd_frm.alloc_len) + DDL_MSG_ERROR("ddl_enc_frame:offset_large," + "Exceeds_min_buf_size"); + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME)) { + DDL_MSG_ERROR("ddl_enc_frame:Wrong_state"); + return VCD_ERR_ILLEGAL_OP; + } + if (!ddl_take_command_channel(ddl_context, ddl, client_data)) + return VCD_ERR_BUSY; + + ddl->input_frame = *input_frame; + ddl->output_frame = *output_bit; + if (ddl->codec_data.encoder.i_period.b_frames > 0) { + if (!ddl->b_count) { + ddl->first_output_frame = *output_bit; + ddl->b_count++; + } else if (ddl->codec_data.encoder.i_period.b_frames >= + ddl->b_count) { + ddl->extra_output_frame[ddl->b_count-1] = + *output_bit; + ddl->output_frame = ddl->first_output_frame; + ddl->b_count++; + } + } + ddl_insert_input_frame_to_pool(ddl, input_frame); + if (!vcd_status) + ddl_vidc_encode_frame_run(ddl); + else + DDL_MSG_ERROR("insert to frame pool failed %u", vcd_status); + return vcd_status; +} + +u32 ddl_decode_end(u32 *ddl_handle, void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + struct ddl_context *ddl_context; + + DDL_MSG_HIGH("ddl_decode_end"); + if (vidc_msg_timing) { + ddl_reset_core_time_variables(DEC_OP_TIME); + ddl_reset_core_time_variables(DEC_IP_TIME); + } + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_dec_end:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + DDL_MSG_ERROR("ddl_dec_end:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || !ddl->decoding) { + DDL_MSG_ERROR("ddl_dec_end:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODEC) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_FAVIDC_ERROR)) { + DDL_MSG_ERROR("ddl_dec_end:Wrong_state"); + return VCD_ERR_ILLEGAL_OP; + } + if (!ddl_take_command_channel(ddl_context, ddl, client_data)) + return VCD_ERR_BUSY; + ddl_vidc_channel_end(ddl); + return VCD_S_SUCCESS; +} + +u32 ddl_encode_end(u32 *ddl_handle, void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + struct ddl_context *ddl_context; + + DDL_MSG_HIGH("ddl_encode_end"); + if (vidc_msg_timing) + ddl_reset_core_time_variables(ENC_OP_TIME); + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_enc_end:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + DDL_MSG_ERROR("ddl_enc_end:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || ddl->decoding) { + DDL_MSG_ERROR("ddl_enc_end:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODEC) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_FAVIDC_ERROR)) { + DDL_MSG_ERROR("ddl_enc_end:Wrong_state"); + return VCD_ERR_ILLEGAL_OP; + } + if (!ddl_take_command_channel(ddl_context, ddl, client_data)) + return VCD_ERR_BUSY; + ddl_vidc_channel_end(ddl); + return VCD_S_SUCCESS; +} + +u32 ddl_reset_hw(u32 mode) +{ + struct ddl_context *ddl_context; + struct ddl_client_context *ddl; + u32 i; + + DDL_MSG_HIGH("ddl_reset_hw"); + DDL_MSG_LOW("ddl_reset_hw:called"); + ddl_context = ddl_get_context(); + ddl_context->cmd_state = DDL_CMD_INVALID; + DDL_BUSY(ddl_context); + if (ddl_context->core_virtual_base_addr) { + vidc_1080p_do_sw_reset(VIDC_1080P_RESET_IN_SEQ_FIRST_STAGE); + msleep(DDL_SW_RESET_SLEEP); + vidc_1080p_do_sw_reset(VIDC_1080P_RESET_IN_SEQ_SECOND_STAGE); + msleep(DDL_SW_RESET_SLEEP); + ddl_context->core_virtual_base_addr = NULL; + } + ddl_context->device_state = DDL_DEVICE_NOTINIT; + for (i = 0; i < VCD_MAX_NO_CLIENT; i++) { + ddl = ddl_context->ddl_clients[i]; + ddl_context->ddl_clients[i] = NULL; + if (ddl) { + ddl_release_client_internal_buffers(ddl); + ddl_client_transact(DDL_FREE_CLIENT, &ddl); + } + } + ddl_release_context_buffers(ddl_context); + memset(ddl_context, 0, sizeof(struct ddl_context)); + return true; +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl.h b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl.h new file mode 100644 index 00000000000..9084ea8021e --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl.h @@ -0,0 +1,446 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VCD_DDL_H_ +#define _VCD_DDL_H_ + +#include "vcd_ddl_api.h" +#include "vcd_ddl_core.h" +#include "vcd_ddl_utils.h" +#include "vidc.h" +#include "vidc_hwio.h" +#include "vidc_pix_cache.h" +#include "vidc.h" + +#define DDL_IDLE_STATE 0 +#define DDL_BUSY_STATE 1 +#define DDL_ERROR_STATE 2 +#define DDL_RUN_STATE 3 + +#define DDL_IS_BUSY(ddl_context) \ + ((ddl_context)->ddl_busy == DDL_BUSY_STATE) +#define DDL_IS_IDLE(ddl_context) \ + ((ddl_context)->ddl_busy == DDL_IDLE_STATE) +#define DDL_BUSY(ddl_context) \ + ((ddl_context)->ddl_busy = DDL_BUSY_STATE) +#define DDL_IDLE(ddl_context) \ + ((ddl_context)->ddl_busy = DDL_IDLE_STATE) +#define DDL_ERROR(ddl_context) \ + ((ddl_context)->ddl_busy = DDL_ERROR_STATE) +#define DDL_RUN(ddl_context) \ + ((ddl_context)->ddl_busy = DDL_RUN_STATE) + +#define DDL_DEVICE_NOTINIT 0 +#define DDL_DEVICE_INITED 1 +#define DDL_DEVICE_HWFATAL 2 + +#define DDL_IS_INITIALIZED(ddl_context) \ + (ddl_context->device_state == DDL_DEVICE_INITED) +#define DDLCOMMAND_STATE_IS(ddl_context, command_state) \ + (command_state == (ddl_context)->cmd_state) +#define DDLCLIENT_STATE_IS(ddl, state) \ + (state == (ddl)->client_state) + +#define DDL_DPB_OP_INIT 1 +#define DDL_DPB_OP_MARK_FREE 2 +#define DDL_DPB_OP_MARK_BUSY 3 +#define DDL_DPB_OP_SET_MASK 4 +#define DDL_DPB_OP_RETRIEVE 5 + +#define DDL_INIT_CLIENTS 0 +#define DDL_GET_CLIENT 1 +#define DDL_FREE_CLIENT 2 +#define DDL_ACTIVE_CLIENT 3 + +#define DDL_INVALID_CHANNEL_ID ((u32)~0) +#define DDL_INVALID_CODEC_TYPE ((u32)~0) +#define DDL_INVALID_INTR_STATUS ((u32)~0) + +#define DDL_ENC_REQ_IFRAME 0x01 +#define DDL_ENC_CHANGE_IPERIOD 0x02 +#define DDL_ENC_CHANGE_BITRATE 0x04 +#define DDL_ENC_CHANGE_FRAMERATE 0x08 +#define DDL_ENC_CHANGE_CIR 0x10 + +#define DDL_DEC_REQ_OUTPUT_FLUSH 0x1 + +#define DDL_MIN_NUM_OF_B_FRAME 0 +#define DDL_MAX_NUM_OF_B_FRAME 1 +#define DDL_DEFAULT_NUM_OF_B_FRAME DDL_MIN_NUM_OF_B_FRAME + +#define DDL_MIN_NUM_REF_FOR_P_FRAME 1 +#define DDL_MAX_NUM_REF_FOR_P_FRAME 2 + +#define DDL_MAX_NUM_IN_INPUTFRAME_POOL (DDL_MAX_NUM_OF_B_FRAME + 1) + +struct ddl_buf_addr{ + u8 *virtual_base_addr; + u8 *physical_base_addr; + u8 *align_physical_addr; + u8 *align_virtual_addr; + u32 buffer_size; +}; +enum ddl_cmd_state{ + DDL_CMD_INVALID = 0x0, + DDL_CMD_DMA_INIT = 0x1, + DDL_CMD_CPU_RESET = 0x2, + DDL_CMD_CHANNEL_SET = 0x3, + DDL_CMD_INIT_CODEC = 0x4, + DDL_CMD_HEADER_PARSE = 0x5, + DDL_CMD_DECODE_SET_DPB = 0x6, + DDL_CMD_DECODE_FRAME = 0x7, + DDL_CMD_ENCODE_FRAME = 0x8, + DDL_CMD_EOS = 0x9, + DDL_CMD_CHANNEL_END = 0xA, + DDL_CMD_32BIT = 0x7FFFFFFF +}; +enum ddl_client_state{ + DDL_CLIENT_INVALID = 0x0, + DDL_CLIENT_OPEN = 0x1, + DDL_CLIENT_WAIT_FOR_CHDONE = 0x2, + DDL_CLIENT_WAIT_FOR_INITCODEC = 0x3, + DDL_CLIENT_WAIT_FOR_INITCODECDONE = 0x4, + DDL_CLIENT_WAIT_FOR_DPB = 0x5, + DDL_CLIENT_WAIT_FOR_DPBDONE = 0x6, + DDL_CLIENT_WAIT_FOR_FRAME = 0x7, + DDL_CLIENT_WAIT_FOR_FRAME_DONE = 0x8, + DDL_CLIENT_WAIT_FOR_EOS_DONE = 0x9, + DDL_CLIENT_WAIT_FOR_CHEND = 0xA, + DDL_CLIENT_FATAL_ERROR = 0xB, + DDL_CLIENT_FAVIDC_ERROR = 0xC, + DDL_CLIENT_32BIT = 0x7FFFFFFF +}; +struct ddl_hw_interface{ + u32 cmd; + u32 arg1; + u32 arg2; + u32 arg3; + u32 arg4; +}; +struct ddl_mask{ + u32 client_mask; + u32 hw_mask; +}; +struct ddl_yuv_buffer_size{ + u32 size_yuv; + u32 size_y; + u32 size_c; +}; +struct ddl_dec_buffer_size{ + u32 sz_dpb0; + u32 sz_dpb1; + u32 sz_mv; + u32 sz_vert_nb_mv; + u32 sz_nb_ip; + u32 sz_luma; + u32 sz_chroma; + u32 sz_nb_dcac; + u32 sz_upnb_mv; + u32 sz_sub_anchor_mv; + u32 sz_overlap_xform; + u32 sz_bit_plane3; + u32 sz_bit_plane2; + u32 sz_bit_plane1; + u32 sz_stx_parser; + u32 sz_desc; + u32 sz_cpb; + u32 sz_context; +}; +struct ddl_dec_buffers{ + struct ddl_buf_addr desc; + struct ddl_buf_addr nb_dcac; + struct ddl_buf_addr upnb_mv; + struct ddl_buf_addr sub_anchor_mv; + struct ddl_buf_addr overlay_xform; + struct ddl_buf_addr bit_plane3; + struct ddl_buf_addr bit_plane2; + struct ddl_buf_addr bit_plane1; + struct ddl_buf_addr stx_parser; + struct ddl_buf_addr h264_mv[DDL_MAX_BUFFER_COUNT]; + struct ddl_buf_addr h264_vert_nb_mv; + struct ddl_buf_addr h264_nb_ip; + struct ddl_buf_addr context; +}; +struct ddl_enc_buffer_size{ + u32 sz_cur_y; + u32 sz_cur_c; + u32 sz_dpb_y; + u32 sz_dpb_c; + u32 sz_strm; + u32 sz_mv; + u32 sz_col_zero; + u32 sz_md; + u32 sz_pred; + u32 sz_nbor_info; + u32 sz_acdc_coef; + u32 sz_mb_info; + u32 sz_context; +}; +struct ddl_enc_buffers{ + struct ddl_buf_addr dpb_y[4]; + struct ddl_buf_addr dpb_c[4]; + struct ddl_buf_addr mv; + struct ddl_buf_addr col_zero; + struct ddl_buf_addr md; + struct ddl_buf_addr pred; + struct ddl_buf_addr nbor_info; + struct ddl_buf_addr acdc_coef; + struct ddl_buf_addr mb_info; + struct ddl_buf_addr context; + u32 dpb_count; + u32 sz_dpb_y; + u32 sz_dpb_c; +}; +struct ddl_codec_data_hdr{ + u32 decoding; +}; +struct ddl_encoder_data{ + struct ddl_codec_data_hdr hdr; + struct vcd_property_codec codec; + struct vcd_property_frame_size frame_size; + struct vcd_property_frame_rate frame_rate; + struct vcd_property_target_bitrate target_bit_rate; + struct vcd_property_profile profile; + struct vcd_property_level level; + struct vcd_property_rate_control rc; + struct vcd_property_multi_slice multi_slice; + struct ddl_buf_addr meta_data_input; + struct vcd_property_short_header short_header; + struct vcd_property_vop_timing vop_timing; + struct vcd_property_db_config db_control; + struct vcd_property_entropy_control entropy_control; + struct vcd_property_i_period i_period; + struct vcd_property_session_qp session_qp; + struct vcd_property_qp_range qp_range; + struct vcd_property_rc_level rc_level; + struct vcd_property_frame_level_rc_params frame_level_rc; + struct vcd_property_adaptive_rc_params adaptive_rc; + struct vcd_property_intra_refresh_mb_number intra_refresh; + struct vcd_property_buffer_format buf_format; + struct vcd_property_buffer_format recon_buf_format; + struct ddl_buf_addr seq_header; + struct vcd_buffer_requirement input_buf_req; + struct vcd_buffer_requirement output_buf_req; + struct vcd_buffer_requirement client_input_buf_req; + struct vcd_buffer_requirement client_output_buf_req; + struct ddl_enc_buffers hw_bufs; + struct ddl_yuv_buffer_size input_buf_size; + struct vidc_1080p_enc_frame_info enc_frame_info; + u32 meta_data_enable_flag; + u32 suffix; + u32 meta_data_offset; + u32 hdr_ext_control; + u32 r_cframe_skip; + u32 vb_vbuffer_size; + u32 dynamic_prop_change; + u32 dynmic_prop_change_req; + u32 seq_header_length; + u32 intra_frame_insertion; + u32 mb_info_enable; + u32 ext_enc_control_val; + u32 num_references_for_p_frame; +}; +struct ddl_decoder_data { + struct ddl_codec_data_hdr hdr; + struct vcd_property_codec codec; + struct vcd_property_buffer_format buf_format; + struct vcd_property_frame_size frame_size; + struct vcd_property_frame_size client_frame_size; + struct vcd_property_profile profile; + struct vcd_property_level level; + struct ddl_buf_addr meta_data_input; + struct vcd_property_post_filter post_filter; + struct vcd_sequence_hdr decode_config; + struct ddl_property_dec_pic_buffers dp_buf; + struct ddl_mask dpb_mask; + struct vcd_buffer_requirement actual_input_buf_req; + struct vcd_buffer_requirement min_input_buf_req; + struct vcd_buffer_requirement client_input_buf_req; + struct vcd_buffer_requirement actual_output_buf_req; + struct vcd_buffer_requirement min_output_buf_req; + struct vcd_buffer_requirement client_output_buf_req; + struct ddl_dec_buffers hw_bufs; + struct ddl_yuv_buffer_size dpb_buf_size; + struct vidc_1080p_dec_disp_info dec_disp_info; + u32 progressive_only; + u32 output_order; + u32 meta_data_enable_flag; + u32 suffix; + u32 meta_data_offset; + u32 header_in_start; + u32 min_dpb_num; + u32 y_cb_cr_size; + u32 dynamic_prop_change; + u32 dynmic_prop_change_req; + u32 flush_pending; + u32 meta_data_exists; + u32 idr_only_decoding; + u32 field_needed_for_prev_ip; + u32 prev_ip_frm_tag; + u32 cont_mode; +}; +union ddl_codec_data{ + struct ddl_codec_data_hdr hdr; + struct ddl_decoder_data decoder; + struct ddl_encoder_data encoder; +}; +struct ddl_context{ + int memtype; + u8 *core_virtual_base_addr; + void *client_data; + u32 device_state; + u32 ddl_busy; + u32 cmd_err_status; + u32 disp_pic_err_status; + u32 pix_cache_enable; + u32 fw_version; + u32 fw_memory_size; + u32 cmd_seq_num; + u32 response_cmd_ch_id; + enum ddl_cmd_state cmd_state; + struct ddl_client_context *current_ddl[2]; + struct ddl_buf_addr metadata_shared_input; + struct ddl_client_context *ddl_clients[VCD_MAX_NO_CLIENT]; + struct ddl_buf_addr dram_base_a; + struct ddl_buf_addr dram_base_b; + struct ddl_hw_interface ddl_hw_response; + void (*ddl_callback) (u32 event, u32 status, void *payload, + size_t sz, u32 *ddl_handle, void *const client_data); + void (*interrupt_clr) (void); + void (*vidc_decode_seq_start[2]) + (struct vidc_1080p_dec_seq_start_param *param); + void (*vidc_set_dec_resolution[2]) + (u32 width, u32 height); + void(*vidc_decode_init_buffers[2]) + (struct vidc_1080p_dec_init_buffers_param *param); + void(*vidc_decode_frame_start[2]) + (struct vidc_1080p_dec_frame_start_param *param); + void(*vidc_encode_seq_start[2]) + (struct vidc_1080p_enc_seq_start_param *param); + void(*vidc_encode_frame_start[2]) + (struct vidc_1080p_enc_frame_start_param *param); + u32 frame_channel_depth; +}; +struct ddl_client_context{ + struct ddl_context *ddl_context; + enum ddl_client_state client_state; + struct ddl_frame_data_tag first_output_frame; + struct ddl_frame_data_tag + extra_output_frame[DDL_MAX_NUM_OF_B_FRAME]; + struct ddl_frame_data_tag input_frame; + struct ddl_frame_data_tag output_frame; + struct ddl_frame_data_tag + input_frame_pool[DDL_MAX_NUM_IN_INPUTFRAME_POOL]; + union ddl_codec_data codec_data; + enum ddl_cmd_state cmd_state; + struct ddl_buf_addr shared_mem[2]; + void *client_data; + u32 decoding; + u32 channel_id; + u32 command_channel; + u32 b_count; + s32 extra_output_buf_count; + u32 instance_id; +}; + +struct ddl_context *ddl_get_context(void); +void ddl_vidc_core_init(struct ddl_context *); +void ddl_vidc_core_term(struct ddl_context *); +void ddl_vidc_channel_set(struct ddl_client_context *); +void ddl_vidc_channel_end(struct ddl_client_context *); +void ddl_vidc_encode_init_codec(struct ddl_client_context *); +void ddl_vidc_decode_init_codec(struct ddl_client_context *); +void ddl_vidc_encode_frame_run(struct ddl_client_context *); +void ddl_vidc_decode_frame_run(struct ddl_client_context *); +void ddl_vidc_decode_eos_run(struct ddl_client_context *ddl); +void ddl_release_context_buffers(struct ddl_context *); +void ddl_release_client_internal_buffers(struct ddl_client_context *ddl); +u32 ddl_vidc_decode_set_buffers(struct ddl_client_context *); +u32 ddl_decoder_dpb_transact(struct ddl_decoder_data *decoder, + struct ddl_frame_data_tag *in_out_frame, u32 operation); +u32 ddl_decoder_dpb_init(struct ddl_client_context *ddl); +u32 ddl_client_transact(u32 , struct ddl_client_context **); +u32 ddl_set_default_decoder_buffer_req(struct ddl_decoder_data *decoder, + u32 estimate); +void ddl_set_default_encoder_buffer_req(struct ddl_encoder_data + *encoder); +void ddl_set_default_dec_property(struct ddl_client_context *); +u32 ddl_encoder_ready_to_start(struct ddl_client_context *); +u32 ddl_decoder_ready_to_start(struct ddl_client_context *, + struct vcd_sequence_hdr *); +u32 ddl_get_yuv_buffer_size(struct vcd_property_frame_size *frame_size, + struct vcd_property_buffer_format *buf_format, u32 interlace, + u32 decoding, u32 *pn_c_offset); +void ddl_calculate_stride(struct vcd_property_frame_size *frame_size, + u32 interlace); +u32 ddl_codec_type_transact(struct ddl_client_context *ddl, + u32 remove, enum vcd_codec requested_codec); +void ddl_vidc_encode_dynamic_property(struct ddl_client_context *ddl, + u32 enable); +void ddl_vidc_decode_dynamic_property(struct ddl_client_context *ddl, + u32 enable); +void ddl_set_initial_default_values(struct ddl_client_context *ddl); + +u32 ddl_take_command_channel(struct ddl_context *ddl_context, + struct ddl_client_context *ddl, void *client_data); +void ddl_release_command_channel(struct ddl_context *ddl_context, + u32 command_channel); +struct ddl_client_context *ddl_get_current_ddl_client_for_channel_id( + struct ddl_context *ddl_context, u32 channel_id); +struct ddl_client_context *ddl_get_current_ddl_client_for_command( + struct ddl_context *ddl_context, + enum ddl_cmd_state cmd_state); + +u32 ddl_get_yuv_buf_size(u32 width, u32 height, u32 format); +void ddl_free_dec_hw_buffers(struct ddl_client_context *ddl); +void ddl_free_enc_hw_buffers(struct ddl_client_context *ddl); +void ddl_calc_dec_hw_buffers_size(enum vcd_codec codec, u32 width, + u32 height, u32 h264_dpb, + struct ddl_dec_buffer_size *buf_size); +u32 ddl_allocate_dec_hw_buffers(struct ddl_client_context *ddl); +u32 ddl_calc_enc_hw_buffers_size(enum vcd_codec codec, u32 width, + u32 height, enum vcd_yuv_buffer_format input_format, + struct ddl_client_context *ddl, + struct ddl_enc_buffer_size *buf_size); +u32 ddl_allocate_enc_hw_buffers(struct ddl_client_context *ddl); + +u32 ddl_handle_core_errors(struct ddl_context *ddl_context); +void ddl_client_fatal_cb(struct ddl_client_context *ddl); +void ddl_hw_fatal_cb(struct ddl_client_context *ddl); + +void *ddl_pmem_alloc(struct ddl_buf_addr *addr, size_t sz, u32 alignment); +void ddl_pmem_free(struct ddl_buf_addr *addr); + +u32 ddl_get_input_frame_from_pool(struct ddl_client_context *ddl, + u8 *input_buffer_address); +u32 ddl_insert_input_frame_to_pool(struct ddl_client_context *ddl, + struct ddl_frame_data_tag *ddl_input_frame); + +void ddl_decoder_chroma_dpb_change(struct ddl_client_context *ddl); +u32 ddl_check_reconfig(struct ddl_client_context *ddl); +void ddl_handle_reconfig(u32 res_change, struct ddl_client_context *ddl); + +#ifdef DDL_BUF_LOG +void ddl_list_buffers(struct ddl_client_context *ddl); +#endif +#ifdef DDL_MSG_LOG +s8 *ddl_get_state_string(enum ddl_client_state client_state); +#endif +extern unsigned char *vidc_video_codec_fw; +extern u32 vidc_video_codec_fw_size; + +u32 ddl_fw_init(struct ddl_buf_addr *dram_base); +void ddl_get_fw_info(const unsigned char **fw_array_addr, + unsigned int *fw_size); +void ddl_fw_release(void); +#endif diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_api.h b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_api.h new file mode 100644 index 00000000000..51a0d13381d --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_api.h @@ -0,0 +1,110 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VCD_DDL_API_H_ +#define _VCD_DDL_API_H_ + +#include "vidc.h" +#include "vcd_api.h" + +#define VCD_EVT_RESP_DDL_BASE 0x3000 +#define VCD_EVT_RESP_DEVICE_INIT (VCD_EVT_RESP_DDL_BASE + 0x1) +#define VCD_EVT_RESP_OUTPUT_REQ (VCD_EVT_RESP_DDL_BASE + 0x2) +#define VCD_EVT_RESP_EOS_DONE (VCD_EVT_RESP_DDL_BASE + 0x3) +#define VCD_EVT_RESP_TRANSACTION_PENDING (VCD_EVT_RESP_DDL_BASE + 0x4) + +#define VCD_S_DDL_ERR_BASE 0x90000000 +#define VCD_ERR_MAX_NO_CODEC (VCD_S_DDL_ERR_BASE + 0x1) +#define VCD_ERR_CLIENT_PRESENT (VCD_S_DDL_ERR_BASE + 0x2) +#define VCD_ERR_CLIENT_FATAL (VCD_S_DDL_ERR_BASE + 0x3) +#define VCD_ERR_NO_SEQ_HDR (VCD_S_DDL_ERR_BASE + 0x4) + +#define VCD_I_CUSTOM_BASE (VCD_I_RESERVED_BASE) +#define VCD_I_RC_LEVEL_CONFIG (VCD_I_CUSTOM_BASE + 0x1) +#define VCD_I_FRAME_LEVEL_RC (VCD_I_CUSTOM_BASE + 0x2) +#define VCD_I_ADAPTIVE_RC (VCD_I_CUSTOM_BASE + 0x3) +#define VCD_I_CUSTOM_DDL_BASE (VCD_I_RESERVED_BASE + 0x100) +#define DDL_I_INPUT_BUF_REQ (VCD_I_CUSTOM_DDL_BASE + 0x1) +#define DDL_I_OUTPUT_BUF_REQ (VCD_I_CUSTOM_DDL_BASE + 0x2) +#define DDL_I_DPB (VCD_I_CUSTOM_DDL_BASE + 0x3) +#define DDL_I_DPB_RELEASE (VCD_I_CUSTOM_DDL_BASE + 0x4) +#define DDL_I_DPB_RETRIEVE (VCD_I_CUSTOM_DDL_BASE + 0x5) +#define DDL_I_REQ_OUTPUT_FLUSH (VCD_I_CUSTOM_DDL_BASE + 0x6) +#define DDL_I_SEQHDR_ALIGN_BYTES (VCD_I_CUSTOM_DDL_BASE + 0x7) +#define DDL_I_CAPABILITY (VCD_I_CUSTOM_DDL_BASE + 0x8) +#define DDL_I_FRAME_PROC_UNITS (VCD_I_CUSTOM_DDL_BASE + 0x9) +#define DDL_I_SEQHDR_PRESENT (VCD_I_CUSTOM_DDL_BASE + 0xA) + +#define DDL_FRAME_VGA_SIZE (640*480) +#define DDL_FRAME_720P_WIDTH 1280 +#define DDL_FRAME_720P_HEIGHT 720 + +struct vcd_property_rc_level{ + u32 frame_level_rc; + u32 mb_level_rc; +}; +struct vcd_property_frame_level_rc_params{ + u32 reaction_coeff; +}; +struct vcd_property_adaptive_rc_params{ + u32 disable_dark_region_as_flag; + u32 disable_smooth_region_as_flag; + u32 disable_static_region_as_flag; + u32 disable_activity_region_flag; +}; +struct ddl_property_dec_pic_buffers{ + struct ddl_frame_data_tag *dec_pic_buffers; + u32 no_of_dec_pic_buf; +}; +struct ddl_property_capability{ + u32 max_num_client; + u32 general_command_depth; + u32 exclusive; + u32 frame_command_depth; + u32 ddl_time_out_in_ms; +}; +struct ddl_init_config{ + int memtype; + u8 *core_virtual_base_addr; + void (*interrupt_clr) (void); + void (*ddl_callback) (u32 event, u32 status, void *payload, size_t sz, + u32 *ddl_handle, void *const client_data); +}; +struct ddl_frame_data_tag{ + struct vcd_frame_data vcd_frm; + u32 frm_trans_end; + u32 frm_delta; +}; +u32 ddl_device_init(struct ddl_init_config *ddl_init_config, + void *client_data); +u32 ddl_device_release(void *client_data); +u32 ddl_open(u32 **ddl_handle, u32 decoding); +u32 ddl_close(u32 **ddl_handle); +u32 ddl_encode_start(u32 *ddl_handle, void *client_data); +u32 ddl_encode_frame(u32 *ddl_handle, + struct ddl_frame_data_tag *input_frame, + struct ddl_frame_data_tag *output_bit, void *client_data); +u32 ddl_encode_end(u32 *ddl_handle, void *client_data); +u32 ddl_decode_start(u32 *ddl_handle, struct vcd_sequence_hdr *header, + void *client_data); +u32 ddl_decode_frame(u32 *ddl_handle, + struct ddl_frame_data_tag *input_bits, void *client_data); +u32 ddl_decode_end(u32 *ddl_handle, void *client_data); +u32 ddl_set_property(u32 *ddl_handle, + struct vcd_property_hdr *property_hdr, void *property_value); +u32 ddl_get_property(u32 *ddl_handle, + struct vcd_property_hdr *property_hdr, void *property_value); +u32 ddl_process_core_response(void); +u32 ddl_reset_hw(u32 mode); +void ddl_read_and_clear_interrupt(void); +#endif diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_core.h b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_core.h new file mode 100644 index 00000000000..86ecec3b5ae --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_core.h @@ -0,0 +1,134 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VCD_DDL_CORE_H_ +#define _VCD_DDL_CORE_H_ + +#define DDL_LINEAR_BUF_ALIGN_MASK 0xFFFFF800U +#define DDL_LINEAR_BUF_ALIGN_GUARD_BYTES 0x7FF +#define DDL_LINEAR_BUFFER_ALIGN_BYTES 2048 +#define DDL_TILE_BUF_ALIGN_MASK 0xFFFFE000U +#define DDL_TILE_BUF_ALIGN_GUARD_BYTES 0x1FFF +#define DDL_TILE_BUFFER_ALIGN_BYTES 8192 + +#define DDL_YUV_BUF_TYPE_LINEAR 0 +#define DDL_YUV_BUF_TYPE_TILE 1 + +#define DDL_NO_OF_MB(nWidth, nHeight) \ + ((((nWidth) + 15) >> 4) * (((nHeight) + 15) >> 4)) + +#define DDL_MAX_FRAME_WIDTH 1920 +#define DDL_MAX_FRAME_HEIGHT 1088 + +#define MAX_DPB_SIZE_L4PT0_MBS DDL_KILO_BYTE(32) +#define MAX_FRAME_SIZE_L4PT0_MBS DDL_KILO_BYTE(8) + +#define DDL_MAX_MB_PER_FRAME (DDL_NO_OF_MB(DDL_MAX_FRAME_WIDTH,\ + DDL_MAX_FRAME_HEIGHT)) + +#define DDL_DB_LINE_BUF_SIZE\ + (((((DDL_MAX_FRAME_WIDTH * 4) - 1) / 256) + 1) * 8 * 1024) + +#define DDL_MAX_FRAME_RATE 120 +#define DDL_INITIAL_FRAME_RATE 30 + +#define DDL_MAX_BIT_RATE (20*1024*1024) +#define DDL_MAX_MB_PER_SEC (DDL_MAX_MB_PER_FRAME * DDL_INITIAL_FRAME_RATE) + +#define DDL_SW_RESET_SLEEP 1 +#define VCD_MAX_NO_CLIENT 4 +#define VCD_SINGLE_FRAME_COMMAND_CHANNEL 1 +#define VCD_DUAL_FRAME_COMMAND_CHANNEL 2 +#define VCD_FRAME_COMMAND_DEPTH VCD_SINGLE_FRAME_COMMAND_CHANNEL +#define VCD_GENEVIDC_COMMAND_DEPTH 1 +#define VCD_COMMAND_EXCLUSIVE true +#define DDL_HW_TIMEOUT_IN_MS 1000 +#define DDL_STREAMBUF_ALIGN_GUARD_BYTES 0x7FF + +#define DDL_CONTEXT_MEMORY (1024 * 15 * (VCD_MAX_NO_CLIENT + 1)) + +#define DDL_ENC_MIN_DPB_BUFFERS 2 +#define DDL_ENC_MAX_DPB_BUFFERS 4 + +#define DDL_FW_AUX_HOST_CMD_SPACE_SIZE (DDL_KILO_BYTE(10)) +#define DDL_FW_INST_GLOBAL_CONTEXT_SPACE_SIZE (DDL_KILO_BYTE(500)) +#define DDL_FW_H264DEC_CONTEXT_SPACE_SIZE (DDL_KILO_BYTE(800)) +#define DDL_FW_OTHER_CONTEXT_SPACE_SIZE (DDL_KILO_BYTE(10)) + +#define VCD_DEC_CPB_SIZE (DDL_KILO_BYTE(512)) +#define DDL_DBG_CORE_DUMP_SIZE (DDL_KILO_BYTE(10)) + +#define DDL_BUFEND_PAD 256 +#define DDL_ENC_SEQHEADER_SIZE (512+DDL_BUFEND_PAD) +#define DDL_MAX_BUFFER_COUNT 32 +#define DDL_MIN_BUFFER_COUNT 1 + +#define DDL_MPEG_REFBUF_COUNT 2 +#define DDL_MPEG_COMV_BUF_NO 2 +#define DDL_H263_COMV_BUF_NO 0 +#define DDL_COMV_BUFLINE_NO 128 +#define DDL_VC1_COMV_BUFLINE_NO 32 + +#define DDL_MAX_H264_QP 51 +#define DDL_MAX_MPEG4_QP 31 + +#define DDL_CONCEALMENT_Y_COLOR 16 +#define DDL_CONCEALMENT_C_COLOR 128 + +#define DDL_ALLOW_DEC_FRAMESIZE(width, height) \ + ((DDL_NO_OF_MB(width, height) <= \ + MAX_FRAME_SIZE_L4PT0_MBS) && \ + (width <= DDL_MAX_FRAME_WIDTH) && \ + (height <= DDL_MAX_FRAME_WIDTH) && \ + ((width >= 32 && height >= 16) || \ + (width >= 16 && height >= 32))) + +#define DDL_ALLOW_ENC_FRAMESIZE(width, height) \ + ((DDL_NO_OF_MB(width, height) <= \ + MAX_FRAME_SIZE_L4PT0_MBS) && \ + (width <= DDL_MAX_FRAME_WIDTH) && \ + (height <= DDL_MAX_FRAME_WIDTH) && \ + ((width >= 32 && height >= 32))) + +#define DDL_LINEAR_ALIGN_WIDTH 16 +#define DDL_LINEAR_ALIGN_HEIGHT 16 +#define DDL_LINEAR_MULTIPLY_FACTOR 2048 +#define DDL_TILE_ALIGN_WIDTH 128 +#define DDL_TILE_ALIGN_HEIGHT 32 +#define DDL_TILE_MULTIPLY_FACTOR 8192 +#define DDL_TILE_ALIGN(val, grid) \ + (((val) + (grid) - 1) / (grid) * (grid)) + +#define VCD_DDL_720P_YUV_BUF_SIZE ((1280*720*3) >> 1) +#define VCD_DDL_WVGA_BUF_SIZE (800*480) + +#define VCD_DDL_TEST_MAX_WIDTH (DDL_MAX_FRAME_WIDTH) +#define VCD_DDL_TEST_MAX_HEIGHT (DDL_MAX_FRAME_HEIGHT) + +#define VCD_DDL_TEST_MAX_NUM_H264_DPB 8 + +#define VCD_DDL_TEST_NUM_ENC_INPUT_BUFS 6 +#define VCD_DDL_TEST_NUM_ENC_OUTPUT_BUFS 4 + +#define VCD_DDL_TEST_DEFAULT_WIDTH 176 +#define VCD_DDL_TEST_DEFAULT_HEIGHT 144 + +#define DDL_PIXEL_CACHE_NOT_IDLE 0x4000 +#define DDL_PIXEL_CACHE_STATUS_READ_RETRY 10 +#define DDL_PIXEL_CACHE_STATUS_READ_SLEEP 200 + +#define DDL_RESL_CHANGE_NO_CHANGE 0 +#define DDL_RESL_CHANGE_INCREASED 1 +#define DDL_RESL_CHANGE_DECREASED 2 + +#endif diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_errors.c b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_errors.c new file mode 100644 index 00000000000..d65864761e3 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_errors.c @@ -0,0 +1,755 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vcd_ddl.h" +#include "vcd_ddl_shared_mem.h" +#include "vidc.h" + +static u32 ddl_handle_hw_fatal_errors(struct ddl_client_context *ddl); +static u32 ddl_handle_client_fatal_errors( + struct ddl_client_context *ddl); +static void ddl_input_failed_cb(struct ddl_client_context *ddl, + u32 vcd_event, u32 vcd_status); +static u32 ddl_handle_core_recoverable_errors( + struct ddl_client_context *ddl); +static u32 ddl_handle_core_warnings(u32 error_code); +static void ddl_release_prev_field( + struct ddl_client_context *ddl); +static u32 ddl_handle_dec_seq_hdr_fail_error(struct ddl_client_context *ddl); +static void print_core_errors(u32 error_code); +static void print_core_recoverable_errors(u32 error_code); + +void ddl_hw_fatal_cb(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + u32 error_code = ddl_context->cmd_err_status; + + DDL_MSG_FATAL("VIDC_HW_FATAL"); + ddl->cmd_state = DDL_CMD_INVALID; + ddl_context->device_state = DDL_DEVICE_HWFATAL; + + ddl_context->ddl_callback(VCD_EVT_IND_HWERRFATAL, VCD_ERR_HW_FATAL, + &error_code, sizeof(error_code), + (u32 *)ddl, ddl->client_data); + + ddl_release_command_channel(ddl_context, ddl->command_channel); +} + +static u32 ddl_handle_hw_fatal_errors(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + u32 status = false, error_code = ddl_context->cmd_err_status; + + switch (error_code) { + case VIDC_1080P_ERROR_INVALID_CHANNEL_NUMBER: + case VIDC_1080P_ERROR_INVALID_COMMAND_ID: + case VIDC_1080P_ERROR_CHANNEL_ALREADY_IN_USE: + case VIDC_1080P_ERROR_CHANNEL_NOT_OPEN_BEFORE_CHANNEL_CLOSE: + case VIDC_1080P_ERROR_OPEN_CH_ERROR_SEQ_START: + case VIDC_1080P_ERROR_SEQ_START_ALREADY_CALLED: + case VIDC_1080P_ERROR_OPEN_CH_ERROR_INIT_BUFFERS: + case VIDC_1080P_ERROR_SEQ_START_ERROR_INIT_BUFFERS: + case VIDC_1080P_ERROR_INIT_BUFFER_ALREADY_CALLED: + case VIDC_1080P_ERROR_OPEN_CH_ERROR_FRAME_START: + case VIDC_1080P_ERROR_SEQ_START_ERROR_FRAME_START: + case VIDC_1080P_ERROR_INIT_BUFFERS_ERROR_FRAME_START: + case VIDC_1080P_ERROR_RESOLUTION_CHANGED: + case VIDC_1080P_ERROR_INVALID_COMMAND_LAST_FRAME: + case VIDC_1080P_ERROR_INVALID_COMMAND: + case VIDC_1080P_ERROR_INVALID_CODEC_TYPE: + case VIDC_1080P_ERROR_MEM_ALLOCATION_FAILED: + case VIDC_1080P_ERROR_INSUFFICIENT_CONTEXT_SIZE: + case VIDC_1080P_ERROR_DIVIDE_BY_ZERO: + case VIDC_1080P_ERROR_DESCRIPTOR_BUFFER_EMPTY: + case VIDC_1080P_ERROR_DMA_TX_NOT_COMPLETE: + case VIDC_1080P_ERROR_VSP_NOT_READY: + case VIDC_1080P_ERROR_BUFFER_FULL_STATE: + ddl_hw_fatal_cb(ddl); + status = true; + break; + default: + break; + } + return status; +} + +void ddl_client_fatal_cb(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + + if (ddl->cmd_state == DDL_CMD_DECODE_FRAME) + ddl_vidc_decode_dynamic_property(ddl, false); + else if (ddl->cmd_state == DDL_CMD_ENCODE_FRAME) + ddl_vidc_encode_dynamic_property(ddl, false); + ddl->cmd_state = DDL_CMD_INVALID; + DDL_MSG_LOW("ddl_state_transition: %s ~~> DDL_CLIENT_FAVIDC_ERROR", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_FAVIDC_ERROR; + ddl_context->ddl_callback(VCD_EVT_IND_HWERRFATAL, + VCD_ERR_CLIENT_FATAL, NULL, 0, (u32 *)ddl, + ddl->client_data); + ddl_release_command_channel(ddl_context, ddl->command_channel); +} + +static u32 ddl_handle_client_fatal_errors( + struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + u32 status = false; + + switch (ddl_context->cmd_err_status) { + case VIDC_1080P_ERROR_UNSUPPORTED_FEATURE_IN_PROFILE: + case VIDC_1080P_ERROR_RESOLUTION_NOT_SUPPORTED: + case VIDC_1080P_ERROR_VOS_END_CODE_RECEIVED: + case VIDC_1080P_ERROR_FRAME_RATE_NOT_SUPPORTED: + case VIDC_1080P_ERROR_INVALID_QP_VALUE: + case VIDC_1080P_ERROR_INVALID_RC_REACTION_COEFFICIENT: + case VIDC_1080P_ERROR_INVALID_CPB_SIZE_AT_GIVEN_LEVEL: + case VIDC_1080P_ERROR_B_FRAME_NOT_SUPPORTED: + case VIDC_1080P_ERROR_ALLOC_DPB_SIZE_NOT_SUFFICIENT: + case VIDC_1080P_ERROR_NUM_DPB_OUT_OF_RANGE: + case VIDC_1080P_ERROR_NULL_METADATA_INPUT_POINTER: + case VIDC_1080P_ERROR_NULL_DPB_POINTER: + case VIDC_1080P_ERROR_NULL_OTH_EXT_BUFADDR: + case VIDC_1080P_ERROR_NULL_MV_POINTER: + status = true; + DDL_MSG_ERROR("VIDC_CLIENT_FATAL!!"); + break; + default: + break; + } + if (!status) + DDL_MSG_ERROR("VIDC_UNKNOWN_OP_FAILED %d", + ddl_context->cmd_err_status); + ddl_client_fatal_cb(ddl); + return true; +} + +static void ddl_input_failed_cb(struct ddl_client_context *ddl, + u32 vcd_event, u32 vcd_status) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + u32 payload_size = sizeof(struct ddl_frame_data_tag); + + ddl->cmd_state = DDL_CMD_INVALID; + if (ddl->decoding) + ddl_vidc_decode_dynamic_property(ddl, false); + else + ddl_vidc_encode_dynamic_property(ddl, false); + if (ddl->client_state == DDL_CLIENT_WAIT_FOR_INITCODECDONE) { + payload_size = 0; + DDL_MSG_LOW("ddl_state_transition: %s ~~> " + "DDL_CLIENT_WAIT_FOR_INITCODEC", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_INITCODEC; + } else { + DDL_MSG_LOW("ddl_state_transition: %s ~~> " + "DDL_CLIENT_WAIT_FOR_FRAME", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME; + } + if (vcd_status == VCD_ERR_IFRAME_EXPECTED) + vcd_status = VCD_S_SUCCESS; + ddl_context->ddl_callback(vcd_event, vcd_status, &ddl->input_frame, + payload_size, (u32 *)ddl, ddl->client_data); +} + +static u32 ddl_handle_core_recoverable_errors( + struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + u32 vcd_status = VCD_S_SUCCESS; + u32 vcd_event = VCD_EVT_RESP_INPUT_DONE; + u32 eos = false, status = false; + + if (ddl->decoding) { + if (ddl_handle_dec_seq_hdr_fail_error(ddl)) + return true; + } + + if ((ddl->cmd_state != DDL_CMD_DECODE_FRAME) && + (ddl->cmd_state != DDL_CMD_ENCODE_FRAME)) + return false; + + if (ddl->decoding && + (ddl->codec_data.decoder.field_needed_for_prev_ip == 1)) { + ddl->codec_data.decoder.field_needed_for_prev_ip = 0; + ddl_release_prev_field(ddl); + if (ddl_context->cmd_err_status == + VIDC_1080P_ERROR_NON_PAIRED_FIELD_NOT_SUPPORTED) { + ddl_vidc_decode_frame_run(ddl); + return true; + } + } + + switch (ddl_context->cmd_err_status) { + case VIDC_1080P_ERROR_SYNC_POINT_NOT_RECEIVED: + vcd_status = VCD_ERR_IFRAME_EXPECTED; + break; + case VIDC_1080P_ERROR_NO_BUFFER_RELEASED_FROM_HOST: + { + u32 pending_display = 0, release_mask; + + release_mask = + ddl->codec_data.decoder.\ + dpb_mask.hw_mask; + while (release_mask > 0) { + if (release_mask & 0x1) + pending_display++; + release_mask >>= 1; + } + if (pending_display >= ddl->codec_data.\ + decoder.min_dpb_num) { + DDL_MSG_ERROR("VIDC_FW_ISSUE_REQ_BUF"); + ddl_client_fatal_cb(ddl); + status = true; + } else { + vcd_event = VCD_EVT_RESP_OUTPUT_REQ; + DDL_MSG_LOW("VIDC_OUTPUT_BUF_REQ!!"); + } + break; + } + case VIDC_1080P_ERROR_BIT_STREAM_BUF_EXHAUST: + case VIDC_1080P_ERROR_DESCRIPTOR_TABLE_ENTRY_INVALID: + case VIDC_1080P_ERROR_MB_COEFF_NOT_DONE: + case VIDC_1080P_ERROR_CODEC_SLICE_NOT_DONE: + case VIDC_1080P_ERROR_VIDC_CORE_TIME_OUT: + case VIDC_1080P_ERROR_VC1_BITPLANE_DECODE_ERR: + case VIDC_1080P_ERROR_RESOLUTION_MISMATCH: + case VIDC_1080P_ERROR_NV_QUANT_ERR: + case VIDC_1080P_ERROR_SYNC_MARKER_ERR: + case VIDC_1080P_ERROR_FEATURE_NOT_SUPPORTED: + case VIDC_1080P_ERROR_MEM_CORRUPTION: + case VIDC_1080P_ERROR_INVALID_REFERENCE_FRAME: + case VIDC_1080P_ERROR_PICTURE_CODING_TYPE_ERR: + case VIDC_1080P_ERROR_MV_RANGE_ERR: + case VIDC_1080P_ERROR_PICTURE_STRUCTURE_ERR: + case VIDC_1080P_ERROR_SLICE_ADDR_INVALID: + case VIDC_1080P_ERROR_NON_FRAME_DATA_RECEIVED: + case VIDC_1080P_ERROR_INCOMPLETE_FRAME: + case VIDC_1080P_ERROR_NALU_HEADER_ERROR: + case VIDC_1080P_ERROR_SPS_PARSE_ERROR: + case VIDC_1080P_ERROR_PPS_PARSE_ERROR: + case VIDC_1080P_ERROR_HEADER_NOT_FOUND: + case VIDC_1080P_ERROR_SLICE_PARSE_ERROR: + case VIDC_1080P_ERROR_NON_PAIRED_FIELD_NOT_SUPPORTED: + vcd_status = VCD_ERR_BITSTREAM_ERR; + DDL_MSG_ERROR("VIDC_BIT_STREAM_ERR"); + break; + case VIDC_1080P_ERROR_B_FRAME_NOT_SUPPORTED: + case VIDC_1080P_ERROR_UNSUPPORTED_FEATURE_IN_PROFILE: + case VIDC_1080P_ERROR_RESOLUTION_NOT_SUPPORTED: + if (ddl->decoding) { + vcd_status = VCD_ERR_BITSTREAM_ERR; + DDL_MSG_ERROR("VIDC_BIT_STREAM_ERR"); + } + break; + default: + break; + } + + if (((vcd_status) || (vcd_event != VCD_EVT_RESP_INPUT_DONE)) && + !status) { + ddl->input_frame.frm_trans_end = true; + eos = ((vcd_event == VCD_EVT_RESP_INPUT_DONE) && + (ddl->input_frame.vcd_frm.flags & VCD_FRAME_FLAG_EOS)); + if (((ddl->decoding) && (eos)) || !ddl->decoding) + ddl->input_frame.frm_trans_end = false; + ddl_input_failed_cb(ddl, vcd_event, vcd_status); + if (!ddl->decoding) { + ddl->output_frame.frm_trans_end = !eos; + ddl->output_frame.vcd_frm.data_len = 0; + ddl_context->ddl_callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_ERR_FAIL, &ddl->output_frame, + sizeof(struct ddl_frame_data_tag), (u32 *)ddl, + ddl->client_data); + if (eos) { + DDL_MSG_LOW("VIDC_ENC_EOS_DONE"); + ddl_context->ddl_callback(VCD_EVT_RESP_EOS_DONE, + VCD_S_SUCCESS, NULL, 0, (u32 *)ddl, + ddl->client_data); + } + } + if ((ddl->decoding) && (eos)) + ddl_vidc_decode_eos_run(ddl); + else + ddl_release_command_channel(ddl_context, + ddl->command_channel); + status = true; + } + return status; +} + +static u32 ddl_handle_core_warnings(u32 err_status) +{ + u32 status = false; + + switch (err_status) { + case VIDC_1080P_WARN_COMMAND_FLUSHED: + case VIDC_1080P_WARN_FRAME_RATE_UNKNOWN: + case VIDC_1080P_WARN_ASPECT_RATIO_UNKNOWN: + case VIDC_1080P_WARN_COLOR_PRIMARIES_UNKNOWN: + case VIDC_1080P_WARN_TRANSFER_CHAR_UNKNOWN: + case VIDC_1080P_WARN_MATRIX_COEFF_UNKNOWN: + case VIDC_1080P_WARN_NON_SEQ_SLICE_ADDR: + case VIDC_1080P_WARN_BROKEN_LINK: + case VIDC_1080P_WARN_FRAME_CONCEALED: + case VIDC_1080P_WARN_PROFILE_UNKNOWN: + case VIDC_1080P_WARN_LEVEL_UNKNOWN: + case VIDC_1080P_WARN_BIT_RATE_NOT_SUPPORTED: + case VIDC_1080P_WARN_COLOR_DIFF_FORMAT_NOT_SUPPORTED: + case VIDC_1080P_WARN_NULL_EXTRA_METADATA_POINTER: + case VIDC_1080P_ERROR_NULL_FW_DEBUG_INFO_POINTER: + case VIDC_1080P_ERROR_ALLOC_DEBUG_INFO_SIZE_INSUFFICIENT: + case VIDC_1080P_WARN_METADATA_NO_SPACE_NUM_CONCEAL_MB: + case VIDC_1080P_WARN_METADATA_NO_SPACE_QP: + case VIDC_1080P_WARN_METADATA_NO_SPACE_CONCEAL_MB: + case VIDC_1080P_WARN_METADATA_NO_SPACE_VC1_PARAM: + case VIDC_1080P_WARN_METADATA_NO_SPACE_SEI: + case VIDC_1080P_WARN_METADATA_NO_SPACE_VUI: + case VIDC_1080P_WARN_METADATA_NO_SPACE_EXTRA: + case VIDC_1080P_WARN_METADATA_NO_SPACE_DATA_NONE: + case VIDC_1080P_WARN_METADATA_NO_SPACE_MB_INFO: + case VIDC_1080P_WARN_METADATA_NO_SPACE_SLICE_SIZE: + case VIDC_1080P_WARN_RESOLUTION_WARNING: + status = true; + DDL_MSG_ERROR("VIDC_WARNING_IGNORED"); + break; + default: + break; + } + return status; +} + +u32 ddl_handle_core_errors(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl; + u32 channel_inst_id, status = false; + u32 disp_status; + + if (!ddl_context->cmd_err_status && + !ddl_context->disp_pic_err_status) { + DDL_MSG_ERROR("VIDC_NO_ERROR"); + return false; + } + vidc_1080p_get_returned_channel_inst_id(&channel_inst_id); + vidc_1080p_clear_returned_channel_inst_id(); + ddl = ddl_get_current_ddl_client_for_channel_id(ddl_context, + ddl_context->response_cmd_ch_id); + if (!ddl) { + DDL_MSG_ERROR("VIDC_SPURIOUS_INTERRUPT_ERROR"); + return true; + } + if (ddl_context->cmd_err_status) { + print_core_errors(ddl_context->cmd_err_status); + print_core_recoverable_errors(ddl_context->cmd_err_status); + } + if (ddl_context->disp_pic_err_status) + print_core_errors(ddl_context->disp_pic_err_status); + status = ddl_handle_core_warnings(ddl_context->cmd_err_status); + disp_status = ddl_handle_core_warnings( + ddl_context->disp_pic_err_status); + if (!status && !disp_status) { + DDL_MSG_ERROR("ddl_warning:Unknown"); + status = ddl_handle_hw_fatal_errors(ddl); + if (!status) + status = ddl_handle_core_recoverable_errors(ddl); + if (!status) + status = ddl_handle_client_fatal_errors(ddl); + } + return status; +} + +static void ddl_release_prev_field(struct ddl_client_context *ddl) +{ + ddl->output_frame.vcd_frm.ip_frm_tag = + ddl->codec_data.decoder.prev_ip_frm_tag; + ddl->output_frame.vcd_frm.physical = NULL; + ddl->output_frame.vcd_frm.virtual = NULL; + ddl->output_frame.frm_trans_end = false; + ddl->ddl_context->ddl_callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_ERR_INTRLCD_FIELD_DROP, &(ddl->output_frame), + sizeof(struct ddl_frame_data_tag), + (u32 *) ddl, ddl->client_data); +} + +static u32 ddl_handle_dec_seq_hdr_fail_error(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + u32 status = false; + + if ((ddl->cmd_state != DDL_CMD_HEADER_PARSE) || + (ddl->client_state != DDL_CLIENT_WAIT_FOR_INITCODECDONE)) { + DDL_MSG_ERROR("STATE-CRITICAL-HDDONE"); + return false; + } + + switch (ddl_context->cmd_err_status) { + case VIDC_1080P_ERROR_UNSUPPORTED_FEATURE_IN_PROFILE: + case VIDC_1080P_ERROR_RESOLUTION_NOT_SUPPORTED: + case VIDC_1080P_ERROR_HEADER_NOT_FOUND: + case VIDC_1080P_ERROR_SPS_PARSE_ERROR: + case VIDC_1080P_ERROR_PPS_PARSE_ERROR: + { + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + if (ddl_context->cmd_err_status == + VIDC_1080P_ERROR_UNSUPPORTED_FEATURE_IN_PROFILE + && decoder->codec.codec == VCD_CODEC_H264) { + DDL_MSG_ERROR("Unsupported Feature for H264"); + ddl_client_fatal_cb(ddl); + return true; + } + if ((ddl_context->cmd_err_status == + VIDC_1080P_ERROR_RESOLUTION_NOT_SUPPORTED) + && (decoder->codec.codec == VCD_CODEC_H263 + || decoder->codec.codec == VCD_CODEC_H264 + || decoder->codec.codec == VCD_CODEC_MPEG4 + || decoder->codec.codec == VCD_CODEC_VC1 + || decoder->codec.codec == VCD_CODEC_VC1_RCV)) { + DDL_MSG_ERROR("Unsupported resolution"); + ddl_client_fatal_cb(ddl); + return true; + } + + DDL_MSG_ERROR("SEQHDR-FAILED"); + if (decoder->header_in_start) { + decoder->header_in_start = false; + ddl_context->ddl_callback(VCD_EVT_RESP_START, + VCD_ERR_SEQHDR_PARSE_FAIL, NULL, 0, + (u32 *) ddl, ddl->client_data); + } else { + ddl->input_frame.frm_trans_end = true; + if ((ddl->input_frame.vcd_frm.flags & + VCD_FRAME_FLAG_EOS)) { + ddl->input_frame.frm_trans_end = false; + } + ddl_vidc_decode_dynamic_property(ddl, false); + ddl_context->ddl_callback( + VCD_EVT_RESP_INPUT_DONE, + VCD_ERR_SEQHDR_PARSE_FAIL, &ddl->input_frame, + sizeof(struct ddl_frame_data_tag), (u32 *)ddl, + ddl->client_data); + if ((ddl->input_frame.vcd_frm.flags & + VCD_FRAME_FLAG_EOS)) { + DDL_MSG_HIGH("EOS_DONE-fromDDL"); + ddl_context->ddl_callback(VCD_EVT_RESP_EOS_DONE, + VCD_S_SUCCESS, NULL, 0, (u32 *) ddl, + ddl->client_data); + } + } + DDL_MSG_LOW("ddl_state_transition: %s ~~> " + "DDL_CLIENT_WAIT_FOR_INITCODEC", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_INITCODEC; + ddl_release_command_channel(ddl_context, ddl->command_channel); + status = true; + break; + } + default: + break; + } + return status; +} + +void print_core_errors(u32 error_code) +{ + s8 *string = NULL; + + switch (error_code) { + case VIDC_1080P_ERROR_INVALID_CHANNEL_NUMBER: + string = "VIDC_1080P_ERROR_INVALID_CHANNEL_NUMBER"; + break; + case VIDC_1080P_ERROR_INVALID_COMMAND_ID: + string = "VIDC_1080P_ERROR_INVALID_COMMAND_ID"; + break; + case VIDC_1080P_ERROR_CHANNEL_ALREADY_IN_USE: + string = "VIDC_1080P_ERROR_CHANNEL_ALREADY_IN_USE"; + break; + case VIDC_1080P_ERROR_CHANNEL_NOT_OPEN_BEFORE_CHANNEL_CLOSE: + string = + "VIDC_1080P_ERROR_CHANNEL_NOT_OPEN_BEFORE_CHANNEL_CLOSE"; + break; + case VIDC_1080P_ERROR_OPEN_CH_ERROR_SEQ_START: + string = "VIDC_1080P_ERROR_OPEN_CH_ERROR_SEQ_START"; + break; + case VIDC_1080P_ERROR_SEQ_START_ALREADY_CALLED: + string = "VIDC_1080P_ERROR_SEQ_START_ALREADY_CALLED"; + break; + case VIDC_1080P_ERROR_OPEN_CH_ERROR_INIT_BUFFERS: + string = "VIDC_1080P_ERROR_OPEN_CH_ERROR_INIT_BUFFERS"; + break; + case VIDC_1080P_ERROR_SEQ_START_ERROR_INIT_BUFFERS: + string = "VIDC_1080P_ERROR_SEQ_START_ERROR_INIT_BUFFERS"; + break; + case VIDC_1080P_ERROR_INIT_BUFFER_ALREADY_CALLED: + string = "VIDC_1080P_ERROR_INIT_BUFFER_ALREADY_CALLED"; + break; + case VIDC_1080P_ERROR_OPEN_CH_ERROR_FRAME_START: + string = "VIDC_1080P_ERROR_OPEN_CH_ERROR_FRAME_START"; + break; + case VIDC_1080P_ERROR_SEQ_START_ERROR_FRAME_START: + string = "VIDC_1080P_ERROR_SEQ_START_ERROR_FRAME_START"; + break; + case VIDC_1080P_ERROR_INIT_BUFFERS_ERROR_FRAME_START: + string = "VIDC_1080P_ERROR_INIT_BUFFERS_ERROR_FRAME_START"; + break; + case VIDC_1080P_ERROR_RESOLUTION_CHANGED: + string = "VIDC_1080P_ERROR_RESOLUTION_CHANGED"; + break; + case VIDC_1080P_ERROR_INVALID_COMMAND_LAST_FRAME: + string = "VIDC_1080P_ERROR_INVALID_COMMAND_LAST_FRAME"; + break; + case VIDC_1080P_ERROR_INVALID_COMMAND: + string = "VIDC_1080P_ERROR_INVALID_COMMAND"; + break; + case VIDC_1080P_ERROR_INVALID_CODEC_TYPE: + string = "VIDC_1080P_ERROR_INVALID_CODEC_TYPE"; + break; + case VIDC_1080P_ERROR_MEM_ALLOCATION_FAILED: + string = "VIDC_1080P_ERROR_MEM_ALLOCATION_FAILED"; + break; + case VIDC_1080P_ERROR_INSUFFICIENT_CONTEXT_SIZE: + string = "VIDC_1080P_ERROR_INSUFFICIENT_CONTEXT_SIZE"; + break; + case VIDC_1080P_ERROR_DIVIDE_BY_ZERO: + string = "VIDC_1080P_ERROR_DIVIDE_BY_ZERO"; + break; + case VIDC_1080P_ERROR_DESCRIPTOR_BUFFER_EMPTY: + string = "VIDC_1080P_ERROR_DESCRIPTOR_BUFFER_EMPTY"; + break; + case VIDC_1080P_ERROR_DMA_TX_NOT_COMPLETE: + string = "VIDC_1080P_ERROR_DMA_TX_NOT_COMPLETE"; + break; + case VIDC_1080P_ERROR_VSP_NOT_READY: + string = "VIDC_1080P_ERROR_VSP_NOT_READY"; + break; + case VIDC_1080P_ERROR_BUFFER_FULL_STATE: + string = "VIDC_1080P_ERROR_BUFFER_FULL_STATE"; + break; + case VIDC_1080P_ERROR_UNSUPPORTED_FEATURE_IN_PROFILE: + string = "VIDC_1080P_ERROR_UNSUPPORTED_FEATURE_IN_PROFILE"; + break; + case VIDC_1080P_ERROR_HEADER_NOT_FOUND: + string = "VIDC_1080P_ERROR_HEADER_NOT_FOUND"; + break; + case VIDC_1080P_ERROR_VOS_END_CODE_RECEIVED: + string = "VIDC_1080P_ERROR_VOS_END_CODE_RECEIVED"; + break; + case VIDC_1080P_ERROR_RESOLUTION_NOT_SUPPORTED: + string = "VIDC_1080P_ERROR_RESOLUTION_NOT_SUPPORTED"; + break; + case VIDC_1080P_ERROR_FRAME_RATE_NOT_SUPPORTED: + string = "VIDC_1080P_ERROR_FRAME_RATE_NOT_SUPPORTED"; + break; + case VIDC_1080P_ERROR_INVALID_QP_VALUE: + string = "VIDC_1080P_ERROR_INVALID_QP_VALUE"; + break; + case VIDC_1080P_ERROR_INVALID_RC_REACTION_COEFFICIENT: + string = "VIDC_1080P_ERROR_INVALID_RC_REACTION_COEFFICIENT"; + break; + case VIDC_1080P_ERROR_INVALID_CPB_SIZE_AT_GIVEN_LEVEL: + string = "VIDC_1080P_ERROR_INVALID_CPB_SIZE_AT_GIVEN_LEVEL"; + break; + case VIDC_1080P_ERROR_B_FRAME_NOT_SUPPORTED: + string = "VIDC_1080P_ERROR_B_FRAME_NOT_SUPPORTED"; + break; + case VIDC_1080P_ERROR_ALLOC_DPB_SIZE_NOT_SUFFICIENT: + string = "VIDC_1080P_ERROR_ALLOC_DPB_SIZE_NOT_SUFFICIENT"; + break; + case VIDC_1080P_ERROR_NUM_DPB_OUT_OF_RANGE: + string = "VIDC_1080P_ERROR_NUM_DPB_OUT_OF_RANGE"; + break; + case VIDC_1080P_ERROR_NULL_METADATA_INPUT_POINTER: + string = "VIDC_1080P_ERROR_NULL_METADATA_INPUT_POINTER"; + break; + case VIDC_1080P_ERROR_NULL_DPB_POINTER: + string = "VIDC_1080P_ERROR_NULL_DPB_POINTER"; + break; + case VIDC_1080P_ERROR_NULL_OTH_EXT_BUFADDR: + string = "VIDC_1080P_ERROR_NULL_OTH_EXT_BUFADDR"; + break; + case VIDC_1080P_ERROR_NULL_MV_POINTER: + string = "VIDC_1080P_ERROR_NULL_MV_POINTER"; + break; + case VIDC_1080P_ERROR_NON_PAIRED_FIELD_NOT_SUPPORTED: + string = "VIDC_1080P_ERROR_NON_PAIRED_FIELD_NOT_SUPPORTED"; + break; + case VIDC_1080P_WARN_COMMAND_FLUSHED: + string = "VIDC_1080P_WARN_COMMAND_FLUSHED"; + break; + case VIDC_1080P_WARN_FRAME_RATE_UNKNOWN: + string = "VIDC_1080P_WARN_FRAME_RATE_UNKNOWN"; + break; + case VIDC_1080P_WARN_ASPECT_RATIO_UNKNOWN: + string = "VIDC_1080P_WARN_ASPECT_RATIO_UNKNOWN"; + break; + case VIDC_1080P_WARN_COLOR_PRIMARIES_UNKNOWN: + string = "VIDC_1080P_WARN_COLOR_PRIMARIES_UNKNOWN"; + break; + case VIDC_1080P_WARN_TRANSFER_CHAR_UNKNOWN: + string = "VIDC_1080P_WARN_TRANSFER_CHAR_UNKNOWN"; + break; + case VIDC_1080P_WARN_MATRIX_COEFF_UNKNOWN: + string = "VIDC_1080P_WARN_MATRIX_COEFF_UNKNOWN"; + break; + case VIDC_1080P_WARN_NON_SEQ_SLICE_ADDR: + string = "VIDC_1080P_WARN_NON_SEQ_SLICE_ADDR"; + break; + case VIDC_1080P_WARN_BROKEN_LINK: + string = "VIDC_1080P_WARN_BROKEN_LINK"; + break; + case VIDC_1080P_WARN_FRAME_CONCEALED: + string = "VIDC_1080P_WARN_FRAME_CONCEALED"; + break; + case VIDC_1080P_WARN_PROFILE_UNKNOWN: + string = "VIDC_1080P_WARN_PROFILE_UNKNOWN"; + break; + case VIDC_1080P_WARN_LEVEL_UNKNOWN: + string = "VIDC_1080P_WARN_LEVEL_UNKNOWN"; + break; + case VIDC_1080P_WARN_BIT_RATE_NOT_SUPPORTED: + string = "VIDC_1080P_WARN_BIT_RATE_NOT_SUPPORTED"; + break; + case VIDC_1080P_WARN_COLOR_DIFF_FORMAT_NOT_SUPPORTED: + string = "VIDC_1080P_WARN_COLOR_DIFF_FORMAT_NOT_SUPPORTED"; + break; + case VIDC_1080P_WARN_NULL_EXTRA_METADATA_POINTER: + string = "VIDC_1080P_WARN_NULL_EXTRA_METADATA_POINTER"; + break; + case VIDC_1080P_ERROR_NULL_FW_DEBUG_INFO_POINTER: + string = "VIDC_1080P_ERROR_NULL_FW_DEBUG_INFO_POINTER"; + break; + case VIDC_1080P_ERROR_ALLOC_DEBUG_INFO_SIZE_INSUFFICIENT: + string = + "VIDC_1080P_ERROR_ALLOC_DEBUG_INFO_SIZE_INSUFFICIENT"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_NUM_CONCEAL_MB: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_NUM_CONCEAL_MB"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_QP: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_QP"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_CONCEAL_MB: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_CONCEAL_MB"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_VC1_PARAM: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_VC1_PARAM"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_SEI: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_SEI"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_VUI: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_VUI"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_EXTRA: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_EXTRA"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_DATA_NONE: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_DATA_NONE"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_MB_INFO: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_MB_INFO"; + break; + case VIDC_1080P_WARN_METADATA_NO_SPACE_SLICE_SIZE: + string = "VIDC_1080P_WARN_METADATA_NO_SPACE_SLICE_SIZE"; + break; + case VIDC_1080P_WARN_RESOLUTION_WARNING: + string = "VIDC_1080P_WARN_RESOLUTION_WARNING"; + break; + } + if (string) + DDL_MSG_ERROR("Error code = 0x%x : %s", error_code, string); +} + +void print_core_recoverable_errors(u32 error_code) +{ + s8 *string = NULL; + + switch (error_code) { + case VIDC_1080P_ERROR_SYNC_POINT_NOT_RECEIVED: + string = "VIDC_1080P_ERROR_SYNC_POINT_NOT_RECEIVED"; + break; + case VIDC_1080P_ERROR_NO_BUFFER_RELEASED_FROM_HOST: + string = "VIDC_1080P_ERROR_NO_BUFFER_RELEASED_FROM_HOST"; + break; + case VIDC_1080P_ERROR_BIT_STREAM_BUF_EXHAUST: + string = "VIDC_1080P_ERROR_BIT_STREAM_BUF_EXHAUST"; + break; + case VIDC_1080P_ERROR_DESCRIPTOR_TABLE_ENTRY_INVALID: + string = "VIDC_1080P_ERROR_DESCRIPTOR_TABLE_ENTRY_INVALID"; + break; + case VIDC_1080P_ERROR_MB_COEFF_NOT_DONE: + string = "VIDC_1080P_ERROR_MB_COEFF_NOT_DONE"; + break; + case VIDC_1080P_ERROR_CODEC_SLICE_NOT_DONE: + string = "VIDC_1080P_ERROR_CODEC_SLICE_NOT_DONE"; + break; + case VIDC_1080P_ERROR_VIDC_CORE_TIME_OUT: + string = "VIDC_1080P_ERROR_VIDC_CORE_TIME_OUT"; + break; + case VIDC_1080P_ERROR_VC1_BITPLANE_DECODE_ERR: + string = "VIDC_1080P_ERROR_VC1_BITPLANE_DECODE_ERR"; + break; + case VIDC_1080P_ERROR_RESOLUTION_MISMATCH: + string = "VIDC_1080P_ERROR_RESOLUTION_MISMATCH"; + break; + case VIDC_1080P_ERROR_NV_QUANT_ERR: + string = "VIDC_1080P_ERROR_NV_QUANT_ERR"; + break; + case VIDC_1080P_ERROR_SYNC_MARKER_ERR: + string = "VIDC_1080P_ERROR_SYNC_MARKER_ERR"; + break; + case VIDC_1080P_ERROR_FEATURE_NOT_SUPPORTED: + string = "VIDC_1080P_ERROR_FEATURE_NOT_SUPPORTED"; + break; + case VIDC_1080P_ERROR_MEM_CORRUPTION: + string = "VIDC_1080P_ERROR_MEM_CORRUPTION"; + break; + case VIDC_1080P_ERROR_INVALID_REFERENCE_FRAME: + string = "VIDC_1080P_ERROR_INVALID_REFERENCE_FRAME"; + break; + case VIDC_1080P_ERROR_PICTURE_CODING_TYPE_ERR: + string = "VIDC_1080P_ERROR_PICTURE_CODING_TYPE_ERR"; + break; + case VIDC_1080P_ERROR_MV_RANGE_ERR: + string = "VIDC_1080P_ERROR_MV_RANGE_ERR"; + break; + case VIDC_1080P_ERROR_PICTURE_STRUCTURE_ERR: + string = "VIDC_1080P_ERROR_PICTURE_STRUCTURE_ERR"; + break; + case VIDC_1080P_ERROR_SLICE_ADDR_INVALID: + string = "VIDC_1080P_ERROR_SLICE_ADDR_INVALID"; + break; + case VIDC_1080P_ERROR_NON_FRAME_DATA_RECEIVED: + string = "VIDC_1080P_ERROR_NON_FRAME_DATA_RECEIVED"; + break; + case VIDC_1080P_ERROR_INCOMPLETE_FRAME: + string = "VIDC_1080P_ERROR_INCOMPLETE_FRAME"; + break; + case VIDC_1080P_ERROR_NALU_HEADER_ERROR: + string = "VIDC_1080P_ERROR_NALU_HEADER_ERROR"; + break; + case VIDC_1080P_ERROR_SPS_PARSE_ERROR: + string = "VIDC_1080P_ERROR_SPS_PARSE_ERROR"; + break; + case VIDC_1080P_ERROR_PPS_PARSE_ERROR: + string = "VIDC_1080P_ERROR_PPS_PARSE_ERROR"; + break; + case VIDC_1080P_ERROR_SLICE_PARSE_ERROR: + string = "VIDC_1080P_ERROR_SLICE_PARSE_ERROR"; + break; + } + if (string) + DDL_MSG_ERROR("Recoverable Error code = 0x%x : %s", + error_code, string); +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_helper.c b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_helper.c new file mode 100644 index 00000000000..1b700bd4ccc --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_helper.c @@ -0,0 +1,959 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include "vcd_ddl.h" +#include "vcd_ddl_shared_mem.h" + +struct ddl_context *ddl_get_context(void) +{ + static struct ddl_context ddl_context; + return &ddl_context; +} + +#ifdef DDL_MSG_LOG +s8 *ddl_get_state_string(enum ddl_client_state client_state) +{ + s8 *ptr; + + switch (client_state) { + case DDL_CLIENT_INVALID: + ptr = "INVALID "; + break; + case DDL_CLIENT_OPEN: + ptr = "OPEN "; + break; + case DDL_CLIENT_WAIT_FOR_CHDONE: + ptr = "WAIT_FOR_CHDONE "; + break; + case DDL_CLIENT_WAIT_FOR_INITCODEC: + ptr = "WAIT_FOR_INITCODEC "; + break; + case DDL_CLIENT_WAIT_FOR_INITCODECDONE: + ptr = "WAIT_FOR_INITCODECDONE"; + break; + case DDL_CLIENT_WAIT_FOR_DPB: + ptr = "WAIT_FOR_DPB "; + break; + case DDL_CLIENT_WAIT_FOR_DPBDONE: + ptr = "WAIT_FOR_DPBDONE"; + break; + case DDL_CLIENT_WAIT_FOR_FRAME: + ptr = "WAIT_FOR_FRAME "; + break; + case DDL_CLIENT_WAIT_FOR_FRAME_DONE: + ptr = "WAIT_FOR_FRAME_DONE "; + break; + case DDL_CLIENT_WAIT_FOR_EOS_DONE: + ptr = "WAIT_FOR_EOS_DONE "; + break; + case DDL_CLIENT_WAIT_FOR_CHEND: + ptr = "WAIT_FOR_CHEND "; + break; + case DDL_CLIENT_FATAL_ERROR: + ptr = "FATAL_ERROR"; + break; + default: + ptr = "UNKNOWN "; + break; + } + return ptr; +} +#endif + +u32 ddl_client_transact(u32 operation, + struct ddl_client_context **pddl_client) +{ + struct ddl_context *ddl_context; + u32 ret_status = VCD_ERR_FAIL; + s32 counter; + + ddl_context = ddl_get_context(); + switch (operation) { + case DDL_FREE_CLIENT: + ret_status = VCD_ERR_MAX_CLIENT; + for (counter = 0; (counter < VCD_MAX_NO_CLIENT) && + (ret_status == VCD_ERR_MAX_CLIENT); ++counter) { + if (*pddl_client == ddl_context->ddl_clients + [counter]) { + kfree(*pddl_client); + *pddl_client = NULL; + ddl_context->ddl_clients[counter] + = NULL; + ret_status = VCD_S_SUCCESS; + } + } + break; + case DDL_GET_CLIENT: + ret_status = VCD_ERR_MAX_CLIENT; + for (counter = (VCD_MAX_NO_CLIENT - 1); (counter >= 0) && + (ret_status == VCD_ERR_MAX_CLIENT); --counter) { + if (!ddl_context->ddl_clients[counter]) { + *pddl_client = + (struct ddl_client_context *) + kmalloc(sizeof(struct + ddl_client_context), GFP_KERNEL); + if (!*pddl_client) + ret_status = VCD_ERR_ALLOC_FAIL; + else { + memset(*pddl_client, 0, + sizeof(struct + ddl_client_context)); + ddl_context->ddl_clients + [counter] = *pddl_client; + (*pddl_client)->ddl_context = + ddl_context; + ret_status = VCD_S_SUCCESS; + } + } + } + break; + case DDL_INIT_CLIENTS: + for (counter = 0; counter < VCD_MAX_NO_CLIENT; ++counter) + ddl_context->ddl_clients[counter] = NULL; + ret_status = VCD_S_SUCCESS; + break; + case DDL_ACTIVE_CLIENT: + for (counter = 0; counter < VCD_MAX_NO_CLIENT; + ++counter) { + if (ddl_context->ddl_clients[counter]) { + ret_status = VCD_S_SUCCESS; + break; + } + } + break; + default: + ret_status = VCD_ERR_ILLEGAL_PARM; + break; + } + return ret_status; +} + +u32 ddl_decoder_dpb_transact(struct ddl_decoder_data *decoder, + struct ddl_frame_data_tag *in_out_frame, u32 operation) +{ + struct ddl_frame_data_tag *found_frame = NULL; + struct ddl_mask *dpb_mask = &decoder->dpb_mask; + u32 vcd_status = VCD_S_SUCCESS, loopc; + + switch (operation) { + case DDL_DPB_OP_MARK_BUSY: + case DDL_DPB_OP_MARK_FREE: + for (loopc = 0; !found_frame && loopc < + decoder->dp_buf.no_of_dec_pic_buf; ++loopc) { + if (in_out_frame->vcd_frm.physical == + decoder->dp_buf.dec_pic_buffers[loopc]. + vcd_frm.physical) { + found_frame = &(decoder->dp_buf. + dec_pic_buffers[loopc]); + break; + } + } + if (found_frame) { + if (operation == DDL_DPB_OP_MARK_BUSY) { + dpb_mask->hw_mask &= + (~(u32)(0x1 << loopc)); + *in_out_frame = *found_frame; + } else if (operation == DDL_DPB_OP_MARK_FREE) { + dpb_mask->client_mask |= (0x1 << loopc); + *found_frame = *in_out_frame; + } + } else { + in_out_frame->vcd_frm.physical = NULL; + in_out_frame->vcd_frm.virtual = NULL; + vcd_status = VCD_ERR_BAD_POINTER; + DDL_MSG_ERROR("BUF_NOT_FOUND"); + } + break; + case DDL_DPB_OP_SET_MASK: + dpb_mask->hw_mask |= dpb_mask->client_mask; + dpb_mask->client_mask = 0; + break; + case DDL_DPB_OP_INIT: + { + u32 dpb_size; + dpb_size = (!decoder->meta_data_offset) ? + decoder->dp_buf.dec_pic_buffers[0].vcd_frm.alloc_len : + decoder->meta_data_offset; + } + break; + case DDL_DPB_OP_RETRIEVE: + { + u32 position; + if (dpb_mask->client_mask) { + position = 0x1; + for (loopc = 0; loopc < + decoder->dp_buf.no_of_dec_pic_buf && + !found_frame; ++loopc) { + if (dpb_mask->client_mask & position) { + found_frame = &decoder->dp_buf. + dec_pic_buffers[loopc]; + dpb_mask->client_mask &= + ~(position); + } + position <<= 1; + } + } else if (dpb_mask->hw_mask) { + position = 0x1; + for (loopc = 0; loopc < + decoder->dp_buf.no_of_dec_pic_buf && + !found_frame; ++loopc) { + if (dpb_mask->hw_mask & position) { + found_frame = &decoder->dp_buf. + dec_pic_buffers[loopc]; + dpb_mask->hw_mask &= ~(position); + } + position <<= 1; + } + } + if (found_frame) + *in_out_frame = *found_frame; + else { + in_out_frame->vcd_frm.physical = NULL; + in_out_frame->vcd_frm.virtual = NULL; + } + } + break; + default: + break; + } + return vcd_status; +} + +u32 ddl_decoder_dpb_init(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + struct ddl_dec_buffers *dec_buffers = &decoder->hw_bufs; + struct ddl_frame_data_tag *frame; + u32 luma[DDL_MAX_BUFFER_COUNT], chroma[DDL_MAX_BUFFER_COUNT]; + u32 mv[DDL_MAX_BUFFER_COUNT], luma_size, i, dpb; + + frame = &decoder->dp_buf.dec_pic_buffers[0]; + luma_size = ddl_get_yuv_buf_size(decoder->frame_size.width, + decoder->frame_size.height, DDL_YUV_BUF_TYPE_TILE); + dpb = decoder->dp_buf.no_of_dec_pic_buf; + DDL_MSG_LOW("%s Decoder num DPB buffers = %u Luma Size = %u" + __func__, dpb, luma_size); + if (dpb > DDL_MAX_BUFFER_COUNT) + dpb = DDL_MAX_BUFFER_COUNT; + for (i = 0; i < dpb; i++) { + if (frame[i].vcd_frm.virtual) { + memset(frame[i].vcd_frm.virtual, 0x10101010, luma_size); + memset(frame[i].vcd_frm.virtual + luma_size, 0x80808080, + frame[i].vcd_frm.alloc_len - luma_size); + } + + luma[i] = DDL_OFFSET(ddl_context->dram_base_a. + align_physical_addr, frame[i].vcd_frm.physical); + chroma[i] = luma[i] + luma_size; + DDL_MSG_LOW("%s Decoder Luma address = %x Chroma address = %x" + __func__, luma[i], chroma[i]); + } + switch (decoder->codec.codec) { + case VCD_CODEC_MPEG1: + case VCD_CODEC_MPEG2: + vidc_1080p_set_decode_recon_buffers(dpb, luma, chroma); + break; + case VCD_CODEC_DIVX_3: + case VCD_CODEC_DIVX_4: + case VCD_CODEC_DIVX_5: + case VCD_CODEC_DIVX_6: + case VCD_CODEC_XVID: + case VCD_CODEC_MPEG4: + vidc_1080p_set_decode_recon_buffers(dpb, luma, chroma); + vidc_1080p_set_mpeg4_divx_decode_work_buffers( + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->nb_dcac), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->upnb_mv), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->sub_anchor_mv), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->overlay_xform), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->stx_parser)); + break; + case VCD_CODEC_H263: + vidc_1080p_set_decode_recon_buffers(dpb, luma, chroma); + vidc_1080p_set_h263_decode_work_buffers( + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->nb_dcac), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->upnb_mv), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->sub_anchor_mv), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->overlay_xform)); + break; + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + vidc_1080p_set_decode_recon_buffers(dpb, luma, chroma); + vidc_1080p_set_vc1_decode_work_buffers( + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->nb_dcac), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->upnb_mv), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->sub_anchor_mv), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->overlay_xform), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->bit_plane1), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->bit_plane2), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->bit_plane3)); + break; + case VCD_CODEC_H264: + for (i = 0; i < dpb; i++) + mv[i] = DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->h264_mv[i]); + vidc_1080p_set_h264_decode_buffers(dpb, + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->h264_vert_nb_mv), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + dec_buffers->h264_nb_ip), + luma, chroma, mv); + break; + default: + break; + } + return VCD_S_SUCCESS; +} + +void ddl_release_context_buffers(struct ddl_context *ddl_context) +{ + if (ddl_context->memtype == MEMTYPE_SMI_KERNEL) { + ddl_pmem_free(&ddl_context->dram_base_a); + ddl_pmem_free(&ddl_context->dram_base_b); + } + ddl_pmem_free(&ddl_context->metadata_shared_input); + ddl_fw_release(); +} + +void ddl_release_client_internal_buffers(struct ddl_client_context *ddl) +{ + if (ddl->decoding) { + struct ddl_decoder_data *decoder = + &(ddl->codec_data.decoder); + kfree(decoder->dp_buf.dec_pic_buffers); + decoder->dp_buf.dec_pic_buffers = NULL; + ddl_vidc_decode_dynamic_property(ddl, false); + decoder->decode_config.sequence_header_len = 0; + decoder->decode_config.sequence_header = NULL; + decoder->dpb_mask.client_mask = 0; + decoder->dpb_mask.hw_mask = 0; + decoder->dp_buf.no_of_dec_pic_buf = 0; + decoder->dynamic_prop_change = 0; + ddl_free_dec_hw_buffers(ddl); + } else { + struct ddl_encoder_data *encoder = + &(ddl->codec_data.encoder); + ddl_pmem_free(&encoder->seq_header); + ddl_vidc_encode_dynamic_property(ddl, false); + encoder->dynamic_prop_change = 0; + ddl_free_enc_hw_buffers(ddl); + } +} + +u32 ddl_codec_type_transact(struct ddl_client_context *ddl, + u32 remove, enum vcd_codec requested_codec) +{ + if (requested_codec > VCD_CODEC_VC1_RCV || + requested_codec < VCD_CODEC_H264) + return false; + if (!ddl->decoding && requested_codec != VCD_CODEC_MPEG4 && + requested_codec != VCD_CODEC_H264 && + requested_codec != VCD_CODEC_H263) + return false; + + return true; +} + +u32 ddl_take_command_channel(struct ddl_context *ddl_context, + struct ddl_client_context *ddl, void *client_data) +{ + u32 status = true; + + if (!ddl_context->current_ddl[0]) { + ddl_context->current_ddl[0] = ddl; + ddl->client_data = client_data; + ddl->command_channel = 0; + } else if (!ddl_context->current_ddl[1]) { + ddl_context->current_ddl[1] = ddl; + ddl->client_data = client_data; + ddl->command_channel = 1; + } else + status = false; + if (status) { + if (ddl_context->current_ddl[0] && + ddl_context->current_ddl[1]) + DDL_BUSY(ddl_context); + else + DDL_RUN(ddl_context); + } + return status; +} + +void ddl_release_command_channel(struct ddl_context *ddl_context, + u32 command_channel) +{ + ddl_context->current_ddl[command_channel]->client_data = NULL; + ddl_context->current_ddl[command_channel] = NULL; + if (!ddl_context->current_ddl[0] && + !ddl_context->current_ddl[1]) + DDL_IDLE(ddl_context); + else + DDL_RUN(ddl_context); +} + +struct ddl_client_context *ddl_get_current_ddl_client_for_channel_id( + struct ddl_context *ddl_context, u32 channel_id) +{ + struct ddl_client_context *ddl; + + if (ddl_context->current_ddl[0] && channel_id == + ddl_context->current_ddl[0]->command_channel) + ddl = ddl_context->current_ddl[0]; + else if (ddl_context->current_ddl[1] && channel_id == + ddl_context->current_ddl[1]->command_channel) + ddl = ddl_context->current_ddl[1]; + else { + DDL_MSG_LOW("STATE-CRITICAL-FRMRUN"); + DDL_MSG_ERROR("Unexpected channel ID = %d", channel_id); + ddl = NULL; + } + return ddl; +} + +struct ddl_client_context *ddl_get_current_ddl_client_for_command( + struct ddl_context *ddl_context, + enum ddl_cmd_state cmd_state) +{ + struct ddl_client_context *ddl; + + if (ddl_context->current_ddl[0] && + cmd_state == ddl_context->current_ddl[0]->cmd_state) + ddl = ddl_context->current_ddl[0]; + else if (ddl_context->current_ddl[1] && + cmd_state == ddl_context->current_ddl[1]->cmd_state) + ddl = ddl_context->current_ddl[1]; + else { + DDL_MSG_LOW("STATE-CRITICAL-FRMRUN"); + DDL_MSG_ERROR("Error: Unexpected cmd_state = %d", + cmd_state); + ddl = NULL; + } + return ddl; +} + +u32 ddl_get_yuv_buf_size(u32 width, u32 height, u32 format) +{ + u32 mem_size, width_round_up, height_round_up, align; + + width_round_up = width; + height_round_up = height; + if (format == DDL_YUV_BUF_TYPE_TILE) { + width_round_up = DDL_ALIGN(width, DDL_TILE_ALIGN_WIDTH); + height_round_up = DDL_ALIGN(height, DDL_TILE_ALIGN_HEIGHT); + align = DDL_TILE_MULTIPLY_FACTOR; + } + if (format == DDL_YUV_BUF_TYPE_LINEAR) { + width_round_up = DDL_ALIGN(width, DDL_LINEAR_ALIGN_WIDTH); + align = DDL_LINEAR_MULTIPLY_FACTOR; + } + mem_size = (width_round_up * height_round_up); + mem_size = DDL_ALIGN(mem_size, align); + return mem_size; +} +void ddl_free_dec_hw_buffers(struct ddl_client_context *ddl) +{ + struct ddl_dec_buffers *dec_bufs = + &ddl->codec_data.decoder.hw_bufs; + ddl_pmem_free(&dec_bufs->h264_nb_ip); + ddl_pmem_free(&dec_bufs->h264_vert_nb_mv); + ddl_pmem_free(&dec_bufs->nb_dcac); + ddl_pmem_free(&dec_bufs->upnb_mv); + ddl_pmem_free(&dec_bufs->sub_anchor_mv); + ddl_pmem_free(&dec_bufs->overlay_xform); + ddl_pmem_free(&dec_bufs->bit_plane3); + ddl_pmem_free(&dec_bufs->bit_plane2); + ddl_pmem_free(&dec_bufs->bit_plane1); + ddl_pmem_free(&dec_bufs->stx_parser); + ddl_pmem_free(&dec_bufs->desc); + ddl_pmem_free(&dec_bufs->context); + memset(dec_bufs, 0, sizeof(struct ddl_dec_buffers)); +} + +void ddl_free_enc_hw_buffers(struct ddl_client_context *ddl) +{ + struct ddl_enc_buffers *enc_bufs = + &ddl->codec_data.encoder.hw_bufs; + u32 i; + + for (i = 0; i < enc_bufs->dpb_count; i++) { + ddl_pmem_free(&enc_bufs->dpb_y[i]); + ddl_pmem_free(&enc_bufs->dpb_c[i]); + } + ddl_pmem_free(&enc_bufs->mv); + ddl_pmem_free(&enc_bufs->col_zero); + ddl_pmem_free(&enc_bufs->md); + ddl_pmem_free(&enc_bufs->pred); + ddl_pmem_free(&enc_bufs->nbor_info); + ddl_pmem_free(&enc_bufs->acdc_coef); + ddl_pmem_free(&enc_bufs->context); + memset(enc_bufs, 0, sizeof(struct ddl_enc_buffers)); +} + +u32 ddl_get_input_frame_from_pool(struct ddl_client_context *ddl, + u8 *input_buffer_address) +{ + u32 vcd_status = VCD_S_SUCCESS, i, found = false; + + for (i = 0; i < DDL_MAX_NUM_IN_INPUTFRAME_POOL && !found; i++) { + if (input_buffer_address == + ddl->input_frame_pool[i].vcd_frm.physical) { + found = true; + ddl->input_frame = ddl->input_frame_pool[i]; + memset(&ddl->input_frame_pool[i], 0, + sizeof(struct ddl_frame_data_tag)); + } + } + if (!found) + vcd_status = VCD_ERR_FAIL; + + return vcd_status; +} + +u32 ddl_insert_input_frame_to_pool(struct ddl_client_context *ddl, + struct ddl_frame_data_tag *ddl_input_frame) +{ + u32 vcd_status = VCD_S_SUCCESS, i, found = false; + + for (i = 0; i < DDL_MAX_NUM_IN_INPUTFRAME_POOL && !found; i++) { + if (!ddl->input_frame_pool[i].vcd_frm.physical) { + found = true; + ddl->input_frame_pool[i] = *ddl_input_frame; + } + } + if (!found) + vcd_status = VCD_ERR_FAIL; + + return vcd_status; +} + +void ddl_calc_dec_hw_buffers_size(enum vcd_codec codec, u32 width, + u32 height, u32 dpb, struct ddl_dec_buffer_size *buf_size) +{ + u32 sz_dpb0 = 0, sz_dpb1 = 0, sz_mv = 0; + u32 sz_luma = 0, sz_chroma = 0, sz_nb_dcac = 0, sz_upnb_mv = 0; + u32 sz_sub_anchor_mv = 0, sz_overlap_xform = 0, sz_bit_plane3 = 0; + u32 sz_bit_plane2 = 0, sz_bit_plane1 = 0, sz_stx_parser = 0; + u32 sz_desc, sz_cpb, sz_context, sz_vert_nb_mv = 0, sz_nb_ip = 0; + + if (codec == VCD_CODEC_H264) { + sz_mv = ddl_get_yuv_buf_size(width, + height>>2, DDL_YUV_BUF_TYPE_TILE); + sz_nb_ip = DDL_KILO_BYTE(32); + sz_vert_nb_mv = DDL_KILO_BYTE(16); + } else { + if ((codec == VCD_CODEC_MPEG4) || + (codec == VCD_CODEC_DIVX_3) || + (codec == VCD_CODEC_DIVX_4) || + (codec == VCD_CODEC_DIVX_5) || + (codec == VCD_CODEC_DIVX_6) || + (codec == VCD_CODEC_XVID) || + (codec == VCD_CODEC_H263)) { + sz_nb_dcac = DDL_KILO_BYTE(16); + sz_upnb_mv = DDL_KILO_BYTE(68); + sz_sub_anchor_mv = DDL_KILO_BYTE(136); + sz_overlap_xform = DDL_KILO_BYTE(32); + if (codec != VCD_CODEC_H263) + sz_stx_parser = DDL_KILO_BYTE(68); + } else if ((codec == VCD_CODEC_VC1) || + (codec == VCD_CODEC_VC1_RCV)) { + sz_nb_dcac = DDL_KILO_BYTE(16); + sz_upnb_mv = DDL_KILO_BYTE(68); + sz_sub_anchor_mv = DDL_KILO_BYTE(136); + sz_overlap_xform = DDL_KILO_BYTE(32); + sz_bit_plane3 = DDL_KILO_BYTE(2); + sz_bit_plane2 = DDL_KILO_BYTE(2); + sz_bit_plane1 = DDL_KILO_BYTE(2); + } + } + sz_desc = DDL_KILO_BYTE(128); + sz_cpb = VCD_DEC_CPB_SIZE; + if (codec == VCD_CODEC_H264) + sz_context = DDL_FW_H264DEC_CONTEXT_SPACE_SIZE; + else + sz_context = DDL_FW_OTHER_CONTEXT_SPACE_SIZE; + if (buf_size) { + buf_size->sz_dpb0 = sz_dpb0; + buf_size->sz_dpb1 = sz_dpb1; + buf_size->sz_mv = sz_mv; + buf_size->sz_vert_nb_mv = sz_vert_nb_mv; + buf_size->sz_nb_ip = sz_nb_ip; + buf_size->sz_luma = sz_luma; + buf_size->sz_chroma = sz_chroma; + buf_size->sz_nb_dcac = sz_nb_dcac; + buf_size->sz_upnb_mv = sz_upnb_mv; + buf_size->sz_sub_anchor_mv = sz_sub_anchor_mv; + buf_size->sz_overlap_xform = sz_overlap_xform; + buf_size->sz_bit_plane3 = sz_bit_plane3; + buf_size->sz_bit_plane2 = sz_bit_plane2; + buf_size->sz_bit_plane1 = sz_bit_plane1; + buf_size->sz_stx_parser = sz_stx_parser; + buf_size->sz_desc = sz_desc; + buf_size->sz_cpb = sz_cpb; + buf_size->sz_context = sz_context; + } +} + +u32 ddl_allocate_dec_hw_buffers(struct ddl_client_context *ddl) +{ + struct ddl_dec_buffers *dec_bufs; + struct ddl_dec_buffer_size buf_size; + u32 status = VCD_S_SUCCESS, dpb = 0; + u32 width = 0, height = 0; + u8 *ptr; + + dec_bufs = &ddl->codec_data.decoder.hw_bufs; + ddl_calc_dec_hw_buffers_size(ddl->codec_data.decoder. + codec.codec, width, height, dpb, &buf_size); + if (buf_size.sz_context > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->context, buf_size.sz_context, + DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_nb_ip > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->h264_nb_ip, buf_size.sz_nb_ip, + DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_vert_nb_mv > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->h264_vert_nb_mv, + buf_size.sz_vert_nb_mv, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_nb_dcac > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->nb_dcac, buf_size.sz_nb_dcac, + DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_upnb_mv > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->upnb_mv, buf_size.sz_upnb_mv, + DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_sub_anchor_mv > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->sub_anchor_mv, + buf_size.sz_sub_anchor_mv, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_overlap_xform > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->overlay_xform, + buf_size.sz_overlap_xform, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_bit_plane3 > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->bit_plane3, + buf_size.sz_bit_plane3, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_bit_plane2 > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->bit_plane2, + buf_size.sz_bit_plane2, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_bit_plane1 > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->bit_plane1, + buf_size.sz_bit_plane1, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_stx_parser > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->stx_parser, + buf_size.sz_stx_parser, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_desc > 0) { + ptr = ddl_pmem_alloc(&dec_bufs->desc, buf_size.sz_desc, + DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (status) + ddl_free_dec_hw_buffers(ddl); + return status; +} + +u32 ddl_calc_enc_hw_buffers_size(enum vcd_codec codec, u32 width, + u32 height, enum vcd_yuv_buffer_format input_format, + struct ddl_client_context *ddl, + struct ddl_enc_buffer_size *buf_size) +{ + u32 status = VCD_S_SUCCESS, mb_x, mb_y; + u32 sz_cur_y, sz_cur_c, sz_dpb_y, sz_dpb_c, sz_strm = 0, sz_mv; + u32 sz_md = 0, sz_pred = 0, sz_nbor_info = 0 , sz_acdc_coef = 0; + u32 sz_mb_info = 0, sz_context, sz_col_zero = 0; + + mb_x = (width + 15) / 16; + mb_y = (height + 15) / 16; + sz_dpb_y = ddl_get_yuv_buf_size(width, + height, DDL_YUV_BUF_TYPE_TILE); + sz_dpb_c = ddl_get_yuv_buf_size(width, height>>1, + DDL_YUV_BUF_TYPE_TILE); + if (input_format == + VCD_BUFFER_FORMAT_NV12_16M2KA) { + sz_cur_y = ddl_get_yuv_buf_size(width, height, + DDL_YUV_BUF_TYPE_LINEAR); + sz_cur_c = ddl_get_yuv_buf_size(width, height>>1, + DDL_YUV_BUF_TYPE_LINEAR); + } else if (VCD_BUFFER_FORMAT_TILE_4x2 == input_format) { + sz_cur_y = sz_dpb_y; + sz_cur_c = sz_dpb_c; + } else + status = VCD_ERR_NOT_SUPPORTED; + if (!status) { + sz_strm = DDL_ALIGN(ddl_get_yuv_buf_size(width, height, + DDL_YUV_BUF_TYPE_LINEAR) + ddl_get_yuv_buf_size(width, + height/2, DDL_YUV_BUF_TYPE_LINEAR), DDL_KILO_BYTE(4)); + sz_mv = DDL_ALIGN(2 * mb_x * 8, DDL_KILO_BYTE(2)); + if ((codec == VCD_CODEC_MPEG4) || + (codec == VCD_CODEC_H264)) { + sz_col_zero = DDL_ALIGN(((mb_x * mb_y + 7) / 8) * + 8, DDL_KILO_BYTE(2)); + } + if ((codec == VCD_CODEC_MPEG4) || + (codec == VCD_CODEC_H263)) { + sz_acdc_coef = DDL_ALIGN((width / 2) * 8, + DDL_KILO_BYTE(2)); + } else if (codec == VCD_CODEC_H264) { + sz_md = DDL_ALIGN(mb_x * 48, DDL_KILO_BYTE(2)); + sz_pred = DDL_ALIGN(2 * 8 * 1024, DDL_KILO_BYTE(2)); + if (ddl) { + if (ddl->codec_data.encoder. + entropy_control.entropy_sel == + VCD_ENTROPY_SEL_CAVLC) + sz_nbor_info = DDL_ALIGN(8 * 8 * mb_x, + DDL_KILO_BYTE(2)); + else if (ddl->codec_data.encoder. + entropy_control.entropy_sel == + VCD_ENTROPY_SEL_CABAC) + sz_nbor_info = DDL_ALIGN(8 * 24 * + mb_x, DDL_KILO_BYTE(2)); + if ((ddl->codec_data.encoder. + mb_info_enable) && + (codec == VCD_CODEC_H264)) { + sz_mb_info = DDL_ALIGN(mb_x * mb_y * + 6 * 8, DDL_KILO_BYTE(2)); + } + } + } else { + sz_nbor_info = DDL_ALIGN(8 * 24 * mb_x, + DDL_KILO_BYTE(2)); + sz_mb_info = DDL_ALIGN(mb_x * mb_y * 6 * 8, + DDL_KILO_BYTE(2)); + } + sz_context = DDL_FW_OTHER_CONTEXT_SPACE_SIZE; + if (buf_size) { + buf_size->sz_cur_y = sz_cur_y; + buf_size->sz_cur_c = sz_cur_c; + buf_size->sz_dpb_y = sz_dpb_y; + buf_size->sz_dpb_c = sz_dpb_c; + buf_size->sz_strm = sz_strm; + buf_size->sz_mv = sz_mv; + buf_size->sz_col_zero = sz_col_zero; + buf_size->sz_md = sz_md; + buf_size->sz_pred = sz_pred; + buf_size->sz_nbor_info = sz_nbor_info; + buf_size->sz_acdc_coef = sz_acdc_coef; + buf_size->sz_mb_info = sz_mb_info; + buf_size->sz_context = sz_context; + } + } + return status; +} + +u32 ddl_allocate_enc_hw_buffers(struct ddl_client_context *ddl) +{ + struct ddl_enc_buffers *enc_bufs; + struct ddl_enc_buffer_size buf_size; + void *ptr; + u32 status = VCD_S_SUCCESS; + + enc_bufs = &ddl->codec_data.encoder.hw_bufs; + enc_bufs->dpb_count = DDL_ENC_MIN_DPB_BUFFERS; + + if ((ddl->codec_data.encoder.i_period.b_frames > + DDL_MIN_NUM_OF_B_FRAME) || + (ddl->codec_data.encoder.num_references_for_p_frame + > DDL_MIN_NUM_REF_FOR_P_FRAME)) + enc_bufs->dpb_count = DDL_ENC_MAX_DPB_BUFFERS; + DDL_MSG_HIGH("Encoder num DPB buffers allocated = %d", + enc_bufs->dpb_count); + + status = ddl_calc_enc_hw_buffers_size( + ddl->codec_data.encoder.codec.codec, + ddl->codec_data.encoder.frame_size.width, + ddl->codec_data.encoder.frame_size.height, + ddl->codec_data.encoder.buf_format.buffer_format, + ddl, &buf_size); + buf_size.sz_strm = ddl->codec_data.encoder. + client_output_buf_req.sz; + if (!status) { + enc_bufs->sz_dpb_y = buf_size.sz_dpb_y; + enc_bufs->sz_dpb_c = buf_size.sz_dpb_c; + if (buf_size.sz_mv > 0) { + ptr = ddl_pmem_alloc(&enc_bufs->mv, buf_size.sz_mv, + DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_col_zero > 0) { + ptr = ddl_pmem_alloc(&enc_bufs->col_zero, + buf_size.sz_col_zero, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_md > 0) { + ptr = ddl_pmem_alloc(&enc_bufs->md, buf_size.sz_md, + DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_pred > 0) { + ptr = ddl_pmem_alloc(&enc_bufs->pred, + buf_size.sz_pred, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_nbor_info > 0) { + ptr = ddl_pmem_alloc(&enc_bufs->nbor_info, + buf_size.sz_nbor_info, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_acdc_coef > 0) { + ptr = ddl_pmem_alloc(&enc_bufs->acdc_coef, + buf_size.sz_acdc_coef, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_mb_info > 0) { + ptr = ddl_pmem_alloc(&enc_bufs->mb_info, + buf_size.sz_mb_info, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (buf_size.sz_context > 0) { + ptr = ddl_pmem_alloc(&enc_bufs->context, + buf_size.sz_context, DDL_KILO_BYTE(2)); + if (!ptr) + status = VCD_ERR_ALLOC_FAIL; + } + if (status) + ddl_free_enc_hw_buffers(ddl); + } + return status; +} + +void ddl_decoder_chroma_dpb_change(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + struct ddl_frame_data_tag *frame = + &(decoder->dp_buf.dec_pic_buffers[0]); + u32 luma[DDL_MAX_BUFFER_COUNT]; + u32 chroma[DDL_MAX_BUFFER_COUNT]; + u32 luma_size, i, dpb; + luma_size = decoder->dpb_buf_size.size_y; + dpb = decoder->dp_buf.no_of_dec_pic_buf; + DDL_MSG_HIGH("%s Decoder num DPB buffers = %u Luma Size = %u" + __func__, dpb, luma_size); + if (dpb > DDL_MAX_BUFFER_COUNT) + dpb = DDL_MAX_BUFFER_COUNT; + for (i = 0; i < dpb; i++) { + luma[i] = DDL_OFFSET( + ddl_context->dram_base_a.align_physical_addr, + frame[i].vcd_frm.physical); + chroma[i] = luma[i] + luma_size; + DDL_MSG_LOW("%s Decoder Luma address = %x" + "Chroma address = %x", __func__, luma[i], chroma[i]); + } + vidc_1080p_set_decode_recon_buffers(dpb, luma, chroma); +} + +u32 ddl_check_reconfig(struct ddl_client_context *ddl) +{ + u32 need_reconfig = true; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + if (decoder->cont_mode) { + if ((decoder->actual_output_buf_req.sz <= + decoder->client_output_buf_req.sz) && + (decoder->actual_output_buf_req.actual_count <= + decoder->client_output_buf_req.actual_count)) { + need_reconfig = false; + if (decoder->min_dpb_num > + decoder->min_output_buf_req.min_count) { + decoder->min_output_buf_req = + decoder->actual_output_buf_req; + } + DDL_MSG_LOW("%s Decoder width = %u height = %u " + "Client width = %u height = %u\n", + __func__, decoder->frame_size.width, + decoder->frame_size.height, + decoder->client_frame_size.width, + decoder->client_frame_size.height); + } + } else { + if ((decoder->frame_size.width == + decoder->client_frame_size.width) && + (decoder->frame_size.height == + decoder->client_frame_size.height) && + (decoder->actual_output_buf_req.sz <= + decoder->client_output_buf_req.sz) && + (decoder->actual_output_buf_req.min_count == + decoder->client_output_buf_req.min_count) && + (decoder->actual_output_buf_req.actual_count == + decoder->client_output_buf_req.actual_count) && + (decoder->frame_size.scan_lines == + decoder->client_frame_size.scan_lines) && + (decoder->frame_size.stride == + decoder->client_frame_size.stride)) + need_reconfig = false; + } + return need_reconfig; +} + +void ddl_handle_reconfig(u32 res_change, struct ddl_client_context *ddl) +{ + if (res_change) { + DDL_MSG_LOW("%s Resolution change, start realloc\n", + __func__); + ddl->client_state = DDL_CLIENT_WAIT_FOR_EOS_DONE; + ddl->cmd_state = DDL_CMD_EOS; + vidc_1080p_frame_start_realloc(ddl->instance_id); + } +} + diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_interrupt_handler.c b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_interrupt_handler.c new file mode 100644 index 00000000000..be46e97ee22 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_interrupt_handler.c @@ -0,0 +1,1638 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#include "vcd_ddl.h" +#include "vcd_ddl_shared_mem.h" +#include "vcd_ddl_metadata.h" +#include + +static void ddl_decoder_input_done_callback( + struct ddl_client_context *ddl, u32 frame_transact_end); +static u32 ddl_decoder_output_done_callback( + struct ddl_client_context *ddl, u32 frame_transact_end); +static u32 ddl_get_decoded_frame(struct vcd_frame_data *frame, + enum vidc_1080p_decode_frame frame_type); +static u32 ddl_get_encoded_frame(struct vcd_frame_data *frame, + enum vcd_codec codec, + enum vidc_1080p_encode_frame frame_type); +static void ddl_get_dec_profile_level(struct ddl_decoder_data *decoder, + u32 profile, u32 level); +static void ddl_handle_enc_frame_done(struct ddl_client_context *ddl); + +static void ddl_fw_status_done_callback(struct ddl_context *ddl_context) +{ + DDL_MSG_MED("ddl_fw_status_done_callback"); + if (!DDLCOMMAND_STATE_IS(ddl_context, DDL_CMD_DMA_INIT)) { + DDL_MSG_ERROR("UNKWN_DMADONE"); + } else { + DDL_MSG_LOW("FW_STATUS_DONE"); + vidc_1080p_set_host2risc_cmd(VIDC_1080P_HOST2RISC_CMD_SYS_INIT, + ddl_context->fw_memory_size, 0, 0, 0); + } +} + +static void ddl_sys_init_done_callback(struct ddl_context *ddl_context, + u32 fw_size) +{ + u32 vcd_status = VCD_S_SUCCESS; + + DDL_MSG_MED("ddl_sys_init_done_callback"); + if (!DDLCOMMAND_STATE_IS(ddl_context, DDL_CMD_DMA_INIT)) { + DDL_MSG_ERROR("UNKNOWN_SYS_INIT_DONE"); + } else { + ddl_context->cmd_state = DDL_CMD_INVALID; + DDL_MSG_LOW("SYS_INIT_DONE"); + vidc_1080p_get_fw_version(&ddl_context->fw_version); + if (ddl_context->fw_memory_size >= fw_size) { + ddl_context->device_state = DDL_DEVICE_INITED; + vcd_status = VCD_S_SUCCESS; + } else + vcd_status = VCD_ERR_FAIL; + ddl_context->ddl_callback(VCD_EVT_RESP_DEVICE_INIT, + vcd_status, NULL, 0, NULL, + ddl_context->client_data); + DDL_IDLE(ddl_context); + } +} + +static void ddl_decoder_eos_done_callback( + struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + + if (!ddl->decoding) { + DDL_MSG_ERROR("STATE-CRITICAL-EOSDONE"); + ddl_client_fatal_cb(ddl); + } else { + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME; + DDL_MSG_LOW("EOS_DONE"); + ddl_context->ddl_callback(VCD_EVT_RESP_EOS_DONE, + VCD_S_SUCCESS, NULL, 0, (u32 *)ddl, + ddl->client_data); + ddl_release_command_channel(ddl_context, + ddl->command_channel); + } +} + +static u32 ddl_channel_set_callback(struct ddl_context *ddl_context, + u32 instance_id) +{ + struct ddl_client_context *ddl; + u32 ret = false; + + DDL_MSG_MED("ddl_channel_open_callback"); + ddl = ddl_get_current_ddl_client_for_command(ddl_context, + DDL_CMD_CHANNEL_SET); + if (ddl) { + ddl->cmd_state = DDL_CMD_INVALID; + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_CHDONE)) { + DDL_MSG_ERROR("STATE-CRITICAL-CHSET"); + ddl_release_command_channel(ddl_context, + ddl->command_channel); + } else { + DDL_MSG_LOW("CH_SET_DONE"); + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_INITCODEC", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_INITCODEC; + ddl->instance_id = instance_id; + if (ddl->decoding) { + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, + DEC_OP_TIME); + if (ddl->codec_data.decoder.header_in_start) + ddl_vidc_decode_init_codec(ddl); + else { + ddl_context->ddl_callback( + VCD_EVT_RESP_START, + VCD_S_SUCCESS, NULL, 0, + (u32 *)ddl, + ddl->client_data); + ddl_release_command_channel( + ddl_context, + ddl->command_channel); + ret = true; + } + } else + ddl_vidc_encode_init_codec(ddl); + } + } + return ret; +} + +static u32 ddl_encoder_seq_done_callback(struct ddl_context *ddl_context, + struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder; + + DDL_MSG_MED("ddl_encoder_seq_done_callback"); + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODECDONE)) { + DDL_MSG_ERROR("STATE-CRITICAL-INITCODEC"); + ddl_client_fatal_cb(ddl); + return true; + } + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, ENC_OP_TIME); + ddl->cmd_state = DDL_CMD_INVALID; + DDL_MSG_LOW("ddl_state_transition: %s ~~> DDL_CLIENT_WAIT_FOR_FRAME", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME; + DDL_MSG_LOW("INIT_CODEC_DONE"); + encoder = &ddl->codec_data.encoder; + vidc_1080p_get_encoder_sequence_header_size( + &encoder->seq_header_length); + if ((encoder->codec.codec == VCD_CODEC_H264) && + (encoder->profile.profile == VCD_PROFILE_H264_BASELINE)) + if ((encoder->seq_header.align_virtual_addr) && + (encoder->seq_header_length > 6)) + encoder->seq_header.align_virtual_addr[6] = 0xC0; + ddl_context->ddl_callback(VCD_EVT_RESP_START, VCD_S_SUCCESS, + NULL, 0, (u32 *) ddl, ddl->client_data); + ddl_release_command_channel(ddl_context, + ddl->command_channel); + return true; +} + +static void parse_hdr_size_data(struct ddl_client_context *ddl, + struct vidc_1080p_seq_hdr_info *seq_hdr_info) +{ + u32 progressive; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + if (decoder->output_order == VCD_DEC_ORDER_DISPLAY) { + decoder->frame_size.width = seq_hdr_info->img_size_x; + decoder->frame_size.height = seq_hdr_info->img_size_y; + progressive = seq_hdr_info->disp_progressive; + } else { + vidc_sm_get_dec_order_resl( + &ddl->shared_mem[ddl->command_channel], + &decoder->frame_size.width, + &decoder->frame_size.height); + progressive = seq_hdr_info->dec_progressive; + } + decoder->min_dpb_num = seq_hdr_info->min_num_dpb; + vidc_sm_get_min_yc_dpb_sizes( + &ddl->shared_mem[ddl->command_channel], + &seq_hdr_info->min_luma_dpb_size, + &seq_hdr_info->min_chroma_dpb_size); + decoder->y_cb_cr_size = seq_hdr_info->min_luma_dpb_size + + seq_hdr_info->min_chroma_dpb_size; + decoder->dpb_buf_size.size_yuv = decoder->y_cb_cr_size; + decoder->dpb_buf_size.size_y = + seq_hdr_info->min_luma_dpb_size; + decoder->dpb_buf_size.size_c = + seq_hdr_info->min_chroma_dpb_size; + decoder->progressive_only = progressive ? false : true; +} + +static void parse_hdr_crop_data(struct ddl_client_context *ddl, + struct vidc_1080p_seq_hdr_info *seq_hdr_info) +{ + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + u32 crop_exists = (decoder->output_order == VCD_DEC_ORDER_DISPLAY) ? + seq_hdr_info->disp_crop_exists : seq_hdr_info->dec_crop_exists; + if (crop_exists) { + if (decoder->output_order == + VCD_DEC_ORDER_DISPLAY) + vidc_sm_get_crop_info( + &ddl->shared_mem[ddl->command_channel], + &seq_hdr_info->crop_left_offset, + &seq_hdr_info->crop_right_offset, + &seq_hdr_info->crop_top_offset, + &seq_hdr_info->crop_bottom_offset); + else + vidc_sm_get_dec_order_crop_info( + &ddl->shared_mem[ddl->command_channel], + &seq_hdr_info->crop_left_offset, + &seq_hdr_info->crop_right_offset, + &seq_hdr_info->crop_top_offset, + &seq_hdr_info->crop_bottom_offset); + decoder->frame_size.width -= + seq_hdr_info->crop_right_offset + + seq_hdr_info->crop_left_offset; + decoder->frame_size.height -= + seq_hdr_info->crop_top_offset + + seq_hdr_info->crop_bottom_offset; + } +} + +static u32 ddl_decoder_seq_done_callback(struct ddl_context *ddl_context, + struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + struct vidc_1080p_seq_hdr_info seq_hdr_info; + u32 process_further = true; + struct ddl_profile_info_type disp_profile_info; + + DDL_MSG_MED("ddl_decoder_seq_done_callback"); + if (!ddl->decoding || + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODECDONE)) { + DDL_MSG_ERROR("STATE-CRITICAL-HDDONE"); + ddl_client_fatal_cb(ddl); + } else { + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, DEC_OP_TIME); + ddl->cmd_state = DDL_CMD_INVALID; + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_DPB", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_DPB; + DDL_MSG_LOW("HEADER_DONE"); + vidc_1080p_get_decode_seq_start_result(&seq_hdr_info); + parse_hdr_size_data(ddl, &seq_hdr_info); + if (!seq_hdr_info.img_size_x || !seq_hdr_info.img_size_y) { + DDL_MSG_ERROR("FATAL:ZeroImageSize"); + ddl_client_fatal_cb(ddl); + return process_further; + } + vidc_sm_get_profile_info(&ddl->shared_mem + [ddl->command_channel], &disp_profile_info); + disp_profile_info.pic_profile = seq_hdr_info.profile; + disp_profile_info.pic_level = seq_hdr_info.level; + ddl_get_dec_profile_level(decoder, seq_hdr_info.profile, + seq_hdr_info.level); + switch (decoder->codec.codec) { + case VCD_CODEC_H264: + if (decoder->profile.profile == VCD_PROFILE_H264_HIGH || + decoder->profile.profile == + VCD_PROFILE_UNKNOWN) { + if ((disp_profile_info.chroma_format_idc > + VIDC_1080P_IDCFORMAT_420) || + (disp_profile_info.bit_depth_luma_minus8 + || disp_profile_info. + bit_depth_chroma_minus8)) { + DDL_MSG_ERROR("Unsupported H.264 " + "feature: IDC format : %d, Bitdepth: %d", + disp_profile_info. + chroma_format_idc, + (disp_profile_info. + bit_depth_luma_minus8 + || disp_profile_info. + bit_depth_chroma_minus8)); + ddl_client_fatal_cb(ddl); + return process_further; + } + } + break; + case VCD_CODEC_MPEG4: + case VCD_CODEC_DIVX_4: + case VCD_CODEC_DIVX_5: + case VCD_CODEC_DIVX_6: + case VCD_CODEC_XVID: + if (seq_hdr_info.data_partition) + if ((seq_hdr_info.img_size_x * + seq_hdr_info.img_size_y) > (720 * 576)) { + DDL_MSG_ERROR("Unsupported DP clip"); + ddl_client_fatal_cb(ddl); + return process_further; + } + break; + default: + break; + } + ddl_calculate_stride(&decoder->frame_size, + !decoder->progressive_only); + decoder->frame_size.scan_lines = + DDL_ALIGN(decoder->frame_size.height, DDL_TILE_ALIGN_HEIGHT); + decoder->frame_size.stride = + DDL_ALIGN(decoder->frame_size.width, DDL_TILE_ALIGN_WIDTH); + parse_hdr_crop_data(ddl, &seq_hdr_info); + if (decoder->codec.codec == VCD_CODEC_H264 && + seq_hdr_info.level > VIDC_1080P_H264_LEVEL4) { + DDL_MSG_ERROR("WARNING: H264MaxLevelExceeded : %d", + seq_hdr_info.level); + } + ddl_set_default_decoder_buffer_req(decoder, false); + if (decoder->header_in_start) { + if (!(decoder->cont_mode) || + (decoder->min_dpb_num > + decoder->client_output_buf_req.min_count) || + (decoder->actual_output_buf_req.sz > + decoder->client_output_buf_req.sz)) { + decoder->client_frame_size = + decoder->frame_size; + decoder->client_output_buf_req = + decoder->actual_output_buf_req; + decoder->client_input_buf_req = + decoder->actual_input_buf_req; + } + ddl_context->ddl_callback(VCD_EVT_RESP_START, + VCD_S_SUCCESS, NULL, 0, (u32 *) ddl, + ddl->client_data); + ddl_release_command_channel(ddl_context, + ddl->command_channel); + } else { + u32 seq_hdr_only_frame = false; + u32 need_reconfig = false; + struct vcd_frame_data *input_vcd_frm = + &ddl->input_frame.vcd_frm; + need_reconfig = ddl_check_reconfig(ddl); + DDL_MSG_HIGH("%s : need_reconfig = %u\n", __func__, + need_reconfig); + if (input_vcd_frm->flags & + VCD_FRAME_FLAG_EOS) { + need_reconfig = false; + } + if (((input_vcd_frm->flags & + VCD_FRAME_FLAG_CODECCONFIG) && + (!(input_vcd_frm->flags & + VCD_FRAME_FLAG_SYNCFRAME))) || + input_vcd_frm->data_len <= + seq_hdr_info.dec_frm_size) { + seq_hdr_only_frame = true; + input_vcd_frm->offset += + seq_hdr_info.dec_frm_size; + input_vcd_frm->data_len = 0; + input_vcd_frm->flags |= + VCD_FRAME_FLAG_CODECCONFIG; + ddl->input_frame.frm_trans_end = + !need_reconfig; + ddl_context->ddl_callback( + VCD_EVT_RESP_INPUT_DONE, + VCD_S_SUCCESS, &ddl->input_frame, + sizeof(struct ddl_frame_data_tag), + (u32 *) ddl, ddl->client_data); + } else { + if (decoder->codec.codec == + VCD_CODEC_VC1_RCV) { + vidc_sm_set_start_byte_number( + &ddl->shared_mem + [ddl->command_channel], + seq_hdr_info.dec_frm_size); + } + } + if (need_reconfig) { + struct ddl_frame_data_tag *payload = + &ddl->input_frame; + u32 payload_size = + sizeof(struct ddl_frame_data_tag); + decoder->client_frame_size = + decoder->frame_size; + decoder->client_output_buf_req = + decoder->actual_output_buf_req; + decoder->client_input_buf_req = + decoder->actual_input_buf_req; + if (seq_hdr_only_frame) { + payload = NULL; + payload_size = 0; + } + DDL_MSG_HIGH("%s : sending port reconfig\n", + __func__); + ddl_context->ddl_callback( + VCD_EVT_IND_OUTPUT_RECONFIG, + VCD_S_SUCCESS, payload, + payload_size, (u32 *) ddl, + ddl->client_data); + } + if (!need_reconfig && !seq_hdr_only_frame) { + if (!ddl_vidc_decode_set_buffers(ddl)) + process_further = false; + else { + DDL_MSG_ERROR("ddl_vidc_decode_set_" + "buffers failed"); + ddl_client_fatal_cb(ddl); + } + } else + ddl_release_command_channel(ddl_context, + ddl->command_channel); + } + } + return process_further; +} + +static u32 ddl_sequence_done_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl; + u32 channel_inst_id, ret; + + vidc_1080p_get_returned_channel_inst_id(&channel_inst_id); + vidc_1080p_clear_returned_channel_inst_id(); + ddl = ddl_get_current_ddl_client_for_channel_id(ddl_context, + ddl_context->response_cmd_ch_id); + if (!ddl) { + DDL_MSG_ERROR("UNKWN_SEQ_DONE"); + ret = true; + } else { + if (ddl->decoding) + ret = ddl_decoder_seq_done_callback(ddl_context, + ddl); + else + ret = ddl_encoder_seq_done_callback(ddl_context, + ddl); + } + return ret; +} + +static u32 ddl_dpb_buffers_set_done_callback( + struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl; + u32 channel_inst_id, ret_status = true; + + DDL_MSG_MED("ddl_dpb_buffers_set_done_callback"); + vidc_1080p_get_returned_channel_inst_id(&channel_inst_id); + vidc_1080p_clear_returned_channel_inst_id(); + ddl = ddl_get_current_ddl_client_for_command(ddl_context, + DDL_CMD_DECODE_SET_DPB); + if (ddl) { + ddl->cmd_state = DDL_CMD_INVALID; + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPBDONE)) { + DDL_MSG_ERROR("STATE-CRITICAL-DPBDONE"); + ddl_client_fatal_cb(ddl); + } else { + DDL_MSG_LOW("INTR_DPBDONE"); + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_FRAME", + ddl_get_state_string(ddl->client_state)); + if (vidc_msg_timing) { + ddl_calc_core_proc_time(__func__, DEC_OP_TIME); + ddl_reset_core_time_variables(DEC_OP_TIME); + } + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME; + ddl_vidc_decode_frame_run(ddl); + ret_status = false; + } + } + return ret_status; +} + +static void ddl_encoder_frame_run_callback( + struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_encoder_data *encoder = + &(ddl->codec_data.encoder); + struct vcd_frame_data *output_frame = + &(ddl->output_frame.vcd_frm); + u32 bottom_frame_tag; + + DDL_MSG_MED("ddl_encoder_frame_run_callback"); + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_EOS_DONE)) { + DDL_MSG_ERROR("STATE-CRITICAL-ENCFRMRUN"); + ddl_client_fatal_cb(ddl); + } else { + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, ENC_OP_TIME); + DDL_MSG_LOW("ENC_FRM_RUN_DONE"); + ddl->cmd_state = DDL_CMD_INVALID; + vidc_1080p_get_encode_frame_info(&encoder->enc_frame_info); + vidc_sm_get_frame_tags(&ddl->shared_mem + [ddl->command_channel], + &output_frame->ip_frm_tag, &bottom_frame_tag); + + if (encoder->meta_data_enable_flag) + vidc_sm_get_metadata_status(&ddl->shared_mem + [ddl->command_channel], + &encoder->enc_frame_info.meta_data_exists); + + if (encoder->enc_frame_info.enc_frame_size || + (encoder->enc_frame_info.enc_frame == + VIDC_1080P_ENCODE_FRAMETYPE_SKIPPED) || + DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_EOS_DONE)) { + u8 *input_buffer_address = NULL; + output_frame->data_len = + encoder->enc_frame_info.enc_frame_size; + output_frame->flags |= VCD_FRAME_FLAG_ENDOFFRAME; + ddl_get_encoded_frame(output_frame, + encoder->codec.codec, + encoder->enc_frame_info.enc_frame); + ddl_process_encoder_metadata(ddl); + ddl_vidc_encode_dynamic_property(ddl, false); + ddl->input_frame.frm_trans_end = false; + input_buffer_address = ddl_context->dram_base_a.\ + align_physical_addr + + encoder->enc_frame_info.enc_luma_address; + ddl_get_input_frame_from_pool(ddl, + input_buffer_address); + ddl_context->ddl_callback(VCD_EVT_RESP_INPUT_DONE, + VCD_S_SUCCESS, &(ddl->input_frame), + sizeof(struct ddl_frame_data_tag), + (u32 *)ddl, ddl->client_data); + ddl->output_frame.frm_trans_end = + DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_EOS_DONE) ? false : true; + ddl_context->ddl_callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_S_SUCCESS, &(ddl->output_frame), + sizeof(struct ddl_frame_data_tag), + (u32 *)ddl, ddl->client_data); + + if (DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_EOS_DONE) && + encoder->i_period.b_frames) { + if ((ddl->extra_output_buf_count < 0) || + (ddl->extra_output_buf_count > + encoder->i_period.b_frames)) { + DDL_MSG_ERROR("Invalid B frame output" + "buffer index"); + } else { + struct vidc_1080p_enc_frame_start_param + enc_param; + ddl->output_frame = ddl->\ + extra_output_frame[ddl->\ + extra_output_buf_count]; + ddl->\ + extra_output_buf_count--; + output_frame = + &ddl->output_frame.\ + vcd_frm; + memset(&enc_param, 0, + sizeof(enc_param)); + enc_param.cmd_seq_num = + ++ddl_context->cmd_seq_num; + enc_param.inst_id = ddl->instance_id; + enc_param.shared_mem_addr_offset = + DDL_ADDR_OFFSET(ddl_context->\ + dram_base_a, ddl->shared_mem + [ddl->command_channel]); + enc_param.stream_buffer_addr_offset = + DDL_OFFSET(ddl_context->\ + dram_base_a.\ + align_physical_addr, + output_frame->physical); + enc_param.stream_buffer_size = + encoder->client_output_buf_req.sz; + enc_param.encode = + VIDC_1080P_ENC_TYPE_LAST_FRAME_DATA; + ddl->cmd_state = DDL_CMD_ENCODE_FRAME; + ddl_context->vidc_encode_frame_start + [ddl->command_channel] + (&enc_param); + } } else { + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_FRAME", + ddl_get_state_string( + ddl->client_state)); + ddl->client_state = + DDL_CLIENT_WAIT_FOR_FRAME; + ddl_release_command_channel(ddl_context, + ddl->command_channel); + } + } else { + ddl_context->ddl_callback( + VCD_EVT_RESP_TRANSACTION_PENDING, + VCD_S_SUCCESS, NULL, 0, (u32 *)ddl, + ddl->client_data); + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_FRAME", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME; + ddl_release_command_channel(ddl_context, + ddl->command_channel); + } + } +} + +static void get_dec_status(struct ddl_client_context *ddl, + struct vidc_1080p_dec_disp_info *dec_disp_info, + u32 output_order, u32 *status, u32 *rsl_chg) +{ + if (output_order == VCD_DEC_ORDER_DISPLAY) { + vidc_1080p_get_display_frame_result(dec_disp_info); + *status = dec_disp_info->display_status; + *rsl_chg = dec_disp_info->disp_resl_change; + } else { + vidc_1080p_get_decode_frame_result(dec_disp_info); + vidc_sm_get_dec_order_resl( + &ddl->shared_mem[ddl->command_channel], + &dec_disp_info->img_size_x, + &dec_disp_info->img_size_y); + *status = dec_disp_info->decode_status; + *rsl_chg = dec_disp_info->dec_resl_change; + } +} + +static u32 ddl_decoder_frame_run_callback(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + u32 callback_end = false, ret_status = false; + u32 eos_present = false, rsl_chg; + u32 more_field_needed, extended_rsl_chg; + enum vidc_1080p_display_status disp_status; + DDL_MSG_MED("ddl_decoder_frame_run_callback"); + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE)) { + DDL_MSG_ERROR("STATE-CRITICAL-DECFRMRUN"); + ddl_client_fatal_cb(ddl); + ret_status = true; + } else { + DDL_MSG_LOW("DEC_FRM_RUN_DONE"); + ddl->cmd_state = DDL_CMD_INVALID; + get_dec_status(ddl, &ddl->codec_data.decoder.dec_disp_info, + ddl->codec_data.decoder.output_order, + &disp_status, &rsl_chg); + + vidc_sm_get_extended_decode_status( + &ddl->shared_mem[ddl->command_channel], + &more_field_needed, + &extended_rsl_chg); + decoder->field_needed_for_prev_ip = + more_field_needed; + decoder->prev_ip_frm_tag = + ddl->input_frame.vcd_frm.ip_frm_tag; + + ddl_vidc_decode_dynamic_property(ddl, false); + if (rsl_chg != DDL_RESL_CHANGE_NO_CHANGE) { + ddl_handle_reconfig(rsl_chg, ddl); + ret_status = false; + } else { + if ((VCD_FRAME_FLAG_EOS & + ddl->input_frame.vcd_frm.flags)) { + callback_end = false; + eos_present = true; + } + if (disp_status == + VIDC_1080P_DISPLAY_STATUS_DECODE_ONLY || + disp_status == + VIDC_1080P_DISPLAY_STATUS_DECODE_AND_DISPLAY) { + if (!eos_present) + callback_end = + (disp_status == + VIDC_1080P_DISPLAY_STATUS_DECODE_ONLY); + ddl_decoder_input_done_callback(ddl, + callback_end); + } + if (disp_status == + VIDC_1080P_DISPLAY_STATUS_DECODE_AND_DISPLAY || + disp_status == + VIDC_1080P_DISPLAY_STATUS_DISPLAY_ONLY) { + if (!eos_present) + callback_end = (disp_status == + VIDC_1080P_DISPLAY_STATUS_DECODE_AND_DISPLAY); + if (ddl_decoder_output_done_callback( + ddl, callback_end)) + ret_status = true; + } + if (!ret_status) { + if (disp_status == + VIDC_1080P_DISPLAY_STATUS_DISPLAY_ONLY + || disp_status == + VIDC_1080P_DISPLAY_STATUS_DPB_EMPTY || + disp_status == + VIDC_1080P_DISPLAY_STATUS_NOOP) { + ddl_vidc_decode_frame_run(ddl); + } else if (eos_present) + ddl_vidc_decode_eos_run(ddl); + else { + ddl->client_state = + DDL_CLIENT_WAIT_FOR_FRAME; + ddl_release_command_channel(ddl_context, + ddl->command_channel); + ret_status = true; + } + } + } + } + return ret_status; +} + +static u32 ddl_eos_frame_done_callback( + struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + struct ddl_mask *dpb_mask = &decoder->dpb_mask; + u32 ret_status = true, rsl_chg, more_field_needed; + enum vidc_1080p_display_status disp_status; + + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_EOS_DONE)) { + DDL_MSG_ERROR("STATE-CRITICAL-EOSFRMRUN"); + ddl_client_fatal_cb(ddl); + } else { + DDL_MSG_LOW("EOS_FRM_RUN_DONE"); + ddl->cmd_state = DDL_CMD_INVALID; + get_dec_status(ddl, &ddl->codec_data.decoder.dec_disp_info, + ddl->codec_data.decoder.output_order, + &disp_status, &rsl_chg); + vidc_sm_get_extended_decode_status( + &ddl->shared_mem[ddl->command_channel], + &more_field_needed, &rsl_chg); + + decoder->field_needed_for_prev_ip = + more_field_needed; + decoder->prev_ip_frm_tag = + ddl->input_frame.vcd_frm.ip_frm_tag; + ddl_vidc_decode_dynamic_property(ddl, false); + if (disp_status == + VIDC_1080P_DISPLAY_STATUS_DPB_EMPTY) { + if (rsl_chg) { + decoder->header_in_start = false; + decoder->decode_config.sequence_header = + ddl->input_frame.vcd_frm.physical; + decoder->decode_config.sequence_header_len = + ddl->input_frame.vcd_frm.data_len; + ddl_vidc_decode_init_codec(ddl); + ret_status = false; + } else + ddl_decoder_eos_done_callback(ddl); + } else { + struct vidc_1080p_dec_frame_start_param dec_param; + ret_status = false; + if (disp_status == + VIDC_1080P_DISPLAY_STATUS_DISPLAY_ONLY) { + if (ddl_decoder_output_done_callback( + ddl, false)) + ret_status = true; + } else if (disp_status != + VIDC_1080P_DISPLAY_STATUS_NOOP) + DDL_MSG_ERROR("EOS-STATE-CRITICAL-" + "WRONG-DISP-STATUS"); + if (!ret_status) { + ddl_decoder_dpb_transact(decoder, NULL, + DDL_DPB_OP_SET_MASK); + ddl->cmd_state = DDL_CMD_EOS; + + memset(&dec_param, 0, sizeof(dec_param)); + + dec_param.cmd_seq_num = + ++ddl_context->cmd_seq_num; + dec_param.inst_id = ddl->instance_id; + dec_param.shared_mem_addr_offset = + DDL_ADDR_OFFSET( + ddl_context->dram_base_a, + ddl->shared_mem[ddl->command_channel]); + dec_param.release_dpb_bit_mask = + dpb_mask->hw_mask; + dec_param.decode = + VIDC_1080P_DEC_TYPE_LAST_FRAME_DATA; + + ddl_context->vidc_decode_frame_start[ddl->\ + command_channel](&dec_param); + } + } + } + return ret_status; +} + +static u32 ddl_frame_run_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl; + u32 channel_inst_id; + u32 return_status = true; + + vidc_1080p_get_returned_channel_inst_id(&channel_inst_id); + vidc_1080p_clear_returned_channel_inst_id(); + ddl = ddl_get_current_ddl_client_for_channel_id(ddl_context, + ddl_context->response_cmd_ch_id); + if (ddl) { + if (ddl_context->pix_cache_enable) { + struct vidc_1080P_pix_cache_statistics + pixel_cache_stats; + vidc_pix_cache_get_statistics(&pixel_cache_stats); + + DDL_MSG_HIGH(" pixel cache hits = %d," + "miss = %d", pixel_cache_stats.access_hit, + pixel_cache_stats.access_miss); + DDL_MSG_HIGH(" pixel cache core reqs = %d," + "axi reqs = %d", pixel_cache_stats.core_req, + pixel_cache_stats.axi_req); + DDL_MSG_HIGH(" pixel cache core bus stats = %d," + "axi bus stats = %d", pixel_cache_stats.core_bus, + pixel_cache_stats.axi_bus); + } + + if (ddl->cmd_state == DDL_CMD_DECODE_FRAME) + return_status = ddl_decoder_frame_run_callback(ddl); + else if (ddl->cmd_state == DDL_CMD_ENCODE_FRAME) + ddl_encoder_frame_run_callback(ddl); + else if (ddl->cmd_state == DDL_CMD_EOS) + return_status = ddl_eos_frame_done_callback(ddl); + else { + DDL_MSG_ERROR("UNKWN_FRAME_DONE"); + return_status = false; + } + } else + return_status = false; + + return return_status; +} + +static void ddl_channel_end_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl; + + DDL_MSG_MED("ddl_channel_end_callback"); + ddl = ddl_get_current_ddl_client_for_command(ddl_context, + DDL_CMD_CHANNEL_END); + if (ddl) { + ddl->cmd_state = DDL_CMD_INVALID; + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_CHEND)) { + DDL_MSG_LOW("STATE-CRITICAL-CHEND"); + } else { + DDL_MSG_LOW("CH_END_DONE"); + ddl_release_client_internal_buffers(ddl); + ddl_context->ddl_callback(VCD_EVT_RESP_STOP, + VCD_S_SUCCESS, NULL, 0, (u32 *)ddl, + ddl->client_data); + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_OPEN", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_OPEN; + } + ddl_release_command_channel(ddl_context, + ddl->command_channel); + } +} + +static void ddl_edfu_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl; + u32 channel_inst_id; + + DDL_MSG_MED("ddl_edfu_callback"); + vidc_1080p_get_returned_channel_inst_id(&channel_inst_id); + vidc_1080p_clear_returned_channel_inst_id(); + ddl = ddl_get_current_ddl_client_for_channel_id(ddl_context, + ddl_context->response_cmd_ch_id); + if (ddl) { + if (ddl->cmd_state != DDL_CMD_ENCODE_FRAME) + DDL_MSG_LOW("UNKWN_EDFU"); + } +} + +static void ddl_encoder_eos_done(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl; + u32 channel_inst_id; + + vidc_1080p_get_returned_channel_inst_id(&channel_inst_id); + vidc_1080p_clear_returned_channel_inst_id(); + ddl = ddl_get_current_ddl_client_for_channel_id(ddl_context, + ddl_context->response_cmd_ch_id); + if (!ddl || (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_EOS_DONE))) { + DDL_MSG_ERROR("STATE-CRITICAL-EOSFRMDONE"); + ddl_client_fatal_cb(ddl); + } else { + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + vidc_1080p_get_encode_frame_info(&encoder->enc_frame_info); + ddl_handle_enc_frame_done(ddl); + DDL_MSG_LOW("encoder_eos_done"); + ddl->cmd_state = DDL_CMD_INVALID; + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_FRAME", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME; + DDL_MSG_LOW("eos_done"); + ddl_context->ddl_callback(VCD_EVT_RESP_EOS_DONE, + VCD_S_SUCCESS, NULL, 0, + (u32 *)ddl, ddl->client_data); + ddl_release_command_channel(ddl_context, + ddl->command_channel); + } +} + +static u32 ddl_process_intr_status(struct ddl_context *ddl_context, + u32 intr_status) +{ + u32 return_status = true; + switch (intr_status) { + case VIDC_1080P_RISC2HOST_CMD_OPEN_CH_RET: + return_status = ddl_channel_set_callback(ddl_context, + ddl_context->response_cmd_ch_id); + break; + case VIDC_1080P_RISC2HOST_CMD_CLOSE_CH_RET: + ddl_channel_end_callback(ddl_context); + break; + case VIDC_1080P_RISC2HOST_CMD_SEQ_DONE_RET: + return_status = ddl_sequence_done_callback(ddl_context); + break; + case VIDC_1080P_RISC2HOST_CMD_FRAME_DONE_RET: + return_status = ddl_frame_run_callback(ddl_context); + break; + case VIDC_1080P_RISC2HOST_CMD_SYS_INIT_RET: + ddl_sys_init_done_callback(ddl_context, + ddl_context->response_cmd_ch_id); + break; + case VIDC_1080P_RISC2HOST_CMD_FW_STATUS_RET: + ddl_fw_status_done_callback(ddl_context); + break; + case VIDC_1080P_RISC2HOST_CMD_EDFU_INT_RET: + ddl_edfu_callback(ddl_context); + break; + case VIDC_1080P_RISC2HOST_CMD_ENC_COMPLETE_RET: + ddl_encoder_eos_done(ddl_context); + break; + case VIDC_1080P_RISC2HOST_CMD_ERROR_RET: + DDL_MSG_ERROR("CMD_ERROR_INTR"); + return_status = ddl_handle_core_errors(ddl_context); + break; + case VIDC_1080P_RISC2HOST_CMD_INIT_BUFFERS_RET: + return_status = + ddl_dpb_buffers_set_done_callback(ddl_context); + break; + default: + DDL_MSG_LOW("UNKWN_INTR"); + break; + } + return return_status; +} + +void ddl_read_and_clear_interrupt(void) +{ + struct ddl_context *ddl_context; + struct ddl_hw_interface *ddl_hw_response; + + ddl_context = ddl_get_context(); + if (!ddl_context->core_virtual_base_addr) { + DDL_MSG_LOW("SPURIOUS_INTERRUPT"); + } else { + ddl_hw_response = &ddl_context->ddl_hw_response; + vidc_1080p_get_risc2host_cmd(&ddl_hw_response->cmd, + &ddl_hw_response->arg1, &ddl_hw_response->arg2, + &ddl_hw_response->arg3, &ddl_hw_response->arg4); + vidc_1080p_clear_risc2host_cmd(); + vidc_1080p_clear_interrupt(); + vidc_1080p_get_risc2host_cmd_status(ddl_hw_response->arg2, + &ddl_context->cmd_err_status, + &ddl_context->disp_pic_err_status); + ddl_context->response_cmd_ch_id = ddl_hw_response->arg1; + } +} + +u32 ddl_process_core_response(void) +{ + struct ddl_context *ddl_context; + struct ddl_hw_interface *ddl_hw_response; + u32 status = false; + + ddl_context = ddl_get_context(); + if (!ddl_context->core_virtual_base_addr) { + DDL_MSG_LOW("SPURIOUS_INTERRUPT"); + return status; + } + ddl_hw_response = &ddl_context->ddl_hw_response; + status = ddl_process_intr_status(ddl_context, ddl_hw_response->cmd); + if (ddl_context->interrupt_clr) + (*ddl_context->interrupt_clr)(); + return status; +} + +static void ddl_decoder_input_done_callback( + struct ddl_client_context *ddl, u32 frame_transact_end) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vidc_1080p_dec_disp_info *dec_disp_info = + &decoder->dec_disp_info; + struct vcd_frame_data *input_vcd_frm = &(ddl->input_frame.vcd_frm); + u32 is_interlaced; + vidc_1080p_get_decoded_frame_size( + &dec_disp_info->input_bytes_consumed); + vidc_sm_set_start_byte_number(&ddl->shared_mem + [ddl->command_channel], 0); + vidc_1080p_get_decode_frame(&dec_disp_info->input_frame); + ddl_get_decoded_frame(input_vcd_frm, + dec_disp_info->input_frame); + vidc_1080p_get_decode_frame_result(dec_disp_info); + is_interlaced = (dec_disp_info->decode_coding == + VIDC_1080P_DISPLAY_CODING_INTERLACED); + if (decoder->output_order == VCD_DEC_ORDER_DECODE) { + dec_disp_info->tag_bottom = is_interlaced ? + dec_disp_info->tag_top : + VCD_FRAMETAG_INVALID; + dec_disp_info->tag_top = input_vcd_frm->ip_frm_tag; + } + input_vcd_frm->interlaced = is_interlaced; + input_vcd_frm->offset += dec_disp_info->input_bytes_consumed; + input_vcd_frm->data_len -= dec_disp_info->input_bytes_consumed; + ddl->input_frame.frm_trans_end = frame_transact_end; + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, DEC_IP_TIME); + ddl_context->ddl_callback(VCD_EVT_RESP_INPUT_DONE, VCD_S_SUCCESS, + &ddl->input_frame, sizeof(struct ddl_frame_data_tag), + (u32 *)ddl, ddl->client_data); +} + +static void get_dec_op_done_data(struct vidc_1080p_dec_disp_info *dec_disp_info, + u32 output_order, u8 **physical, u32 *is_interlaced) +{ + enum vidc_1080p_display_coding disp_coding; + if (output_order == VCD_DEC_ORDER_DECODE) { + *physical = (u8 *)(dec_disp_info->decode_y_addr << 11); + disp_coding = dec_disp_info->decode_coding; + } else { + *physical = (u8 *)(dec_disp_info->display_y_addr << 11); + disp_coding = dec_disp_info->display_coding; + } + *is_interlaced = (disp_coding == + VIDC_1080P_DISPLAY_CODING_INTERLACED); +} + +static void get_dec_op_done_crop(u32 output_order, + struct vidc_1080p_dec_disp_info *dec_disp_info, + struct vcd_frame_rect *crop_data, + struct vcd_property_frame_size *op_frame_sz, + struct vcd_property_frame_size *frame_sz, + struct ddl_buf_addr *shared_mem) +{ + u32 crop_exists = + (output_order == VCD_DEC_ORDER_DECODE) ? + dec_disp_info->dec_crop_exists : + dec_disp_info->disp_crop_exists; + crop_data->left = 0; + crop_data->top = 0; + crop_data->right = dec_disp_info->img_size_x; + crop_data->bottom = dec_disp_info->img_size_y; + op_frame_sz->width = dec_disp_info->img_size_x; + op_frame_sz->height = dec_disp_info->img_size_y; + ddl_calculate_stride(op_frame_sz, false); + op_frame_sz->stride = DDL_ALIGN(op_frame_sz->width, + DDL_TILE_ALIGN_WIDTH); + op_frame_sz->scan_lines = DDL_ALIGN(op_frame_sz->height, + DDL_TILE_ALIGN_HEIGHT); + DDL_MSG_LOW("%s img_size_x = %u img_size_y = %u\n", + __func__, dec_disp_info->img_size_x, + dec_disp_info->img_size_y); + if (crop_exists) { + if (output_order == VCD_DEC_ORDER_DECODE) + vidc_sm_get_dec_order_crop_info(shared_mem, + &dec_disp_info->crop_left_offset, + &dec_disp_info->crop_right_offset, + &dec_disp_info->crop_top_offset, + &dec_disp_info->crop_bottom_offset); + else + vidc_sm_get_crop_info(shared_mem, + &dec_disp_info->crop_left_offset, + &dec_disp_info->crop_right_offset, + &dec_disp_info->crop_top_offset, + &dec_disp_info->crop_bottom_offset); + crop_data->left = dec_disp_info->crop_left_offset; + crop_data->top = dec_disp_info->crop_top_offset; + crop_data->right -= dec_disp_info->crop_right_offset; + crop_data->bottom -= dec_disp_info->crop_bottom_offset; + op_frame_sz->width = crop_data->right - crop_data->left; + op_frame_sz->height = crop_data->bottom - crop_data->top; + } +} + +static u32 ddl_decoder_output_done_callback( + struct ddl_client_context *ddl, u32 frame_transact_end) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vidc_1080p_dec_disp_info *dec_disp_info = + &(decoder->dec_disp_info); + struct ddl_frame_data_tag *output_frame = &(ddl->output_frame); + struct vcd_frame_data *output_vcd_frm = &(output_frame->vcd_frm); + u32 vcd_status, free_luma_dpb = 0, disp_pict = 0, is_interlaced; + get_dec_op_done_data(dec_disp_info, decoder->output_order, + &output_vcd_frm->physical, &is_interlaced); + decoder->progressive_only = !(is_interlaced); + output_vcd_frm->frame = VCD_FRAME_YUV; + if (decoder->codec.codec == VCD_CODEC_MPEG4 || + decoder->codec.codec == VCD_CODEC_VC1 || + decoder->codec.codec == VCD_CODEC_VC1_RCV || + (decoder->codec.codec >= VCD_CODEC_DIVX_3 && + decoder->codec.codec <= VCD_CODEC_XVID)) { + vidc_sm_get_displayed_picture_frame(&ddl->shared_mem + [ddl->command_channel], &disp_pict); + if (decoder->output_order == VCD_DEC_ORDER_DISPLAY) { + if (!disp_pict) { + output_vcd_frm->frame = VCD_FRAME_NOTCODED; + vidc_sm_get_available_luma_dpb_address( + &ddl->shared_mem[ddl->command_channel], + &free_luma_dpb); + } + } else { + if (dec_disp_info->input_frame == + VIDC_1080P_DECODE_FRAMETYPE_NOT_CODED) { + output_vcd_frm->frame = VCD_FRAME_NOTCODED; + vidc_sm_get_available_luma_dpb_dec_order_address( + &ddl->shared_mem[ddl->command_channel], + &free_luma_dpb); + } + } + if (free_luma_dpb) + output_vcd_frm->physical = + (u8 *)(free_luma_dpb << 11); + } + vcd_status = ddl_decoder_dpb_transact(decoder, output_frame, + DDL_DPB_OP_MARK_BUSY); + if (vcd_status) { + DDL_MSG_ERROR("CORRUPTED_OUTPUT_BUFFER_ADDRESS"); + ddl_hw_fatal_cb(ddl); + } else { + vidc_sm_get_metadata_status(&ddl->shared_mem + [ddl->command_channel], + &decoder->meta_data_exists); + if (decoder->output_order == VCD_DEC_ORDER_DISPLAY) + vidc_sm_get_frame_tags(&ddl->shared_mem + [ddl->command_channel], + &dec_disp_info->tag_top, + &dec_disp_info->tag_bottom); + output_vcd_frm->ip_frm_tag = dec_disp_info->tag_top; + vidc_sm_get_picture_times(&ddl->shared_mem + [ddl->command_channel], + &dec_disp_info->pic_time_top, + &dec_disp_info->pic_time_bottom); + get_dec_op_done_crop(decoder->output_order, dec_disp_info, + &output_vcd_frm->dec_op_prop.disp_frm, + &output_vcd_frm->dec_op_prop.frm_size, + &decoder->frame_size, + &ddl->shared_mem[ddl_context->response_cmd_ch_id]); + if ((decoder->cont_mode) && + ((output_vcd_frm->dec_op_prop.frm_size.width != + decoder->frame_size.width) || + (output_vcd_frm->dec_op_prop.frm_size.height != + decoder->frame_size.height) || + (decoder->frame_size.width != + decoder->client_frame_size.width) || + (decoder->frame_size.height != + decoder->client_frame_size.height))) { + DDL_MSG_LOW("%s o/p width = %u o/p height = %u" + "decoder width = %u decoder height = %u ", + __func__, + output_vcd_frm->dec_op_prop.frm_size.width, + output_vcd_frm->dec_op_prop.frm_size.height, + decoder->frame_size.width, + decoder->frame_size.height); + DDL_MSG_HIGH("%s Sending INFO_OP_RECONFIG event\n", + __func__); + ddl_context->ddl_callback( + VCD_EVT_IND_INFO_OUTPUT_RECONFIG, + VCD_S_SUCCESS, NULL, 0, + (u32 *)ddl, + ddl->client_data); + decoder->frame_size = + output_vcd_frm->dec_op_prop.frm_size; + decoder->client_frame_size = decoder->frame_size; + decoder->y_cb_cr_size = + ddl_get_yuv_buffer_size(&decoder->frame_size, + &decoder->buf_format, + (!decoder->progressive_only), + decoder->codec.codec, NULL); + decoder->actual_output_buf_req.sz = + decoder->y_cb_cr_size + decoder->suffix; + decoder->min_output_buf_req = + decoder->actual_output_buf_req; + DDL_MSG_LOW("%s y_cb_cr_size = %u " + "actual_output_buf_req.sz = %u" + "min_output_buf_req.sz = %u\n", + decoder->y_cb_cr_size, + decoder->actual_output_buf_req.sz, + decoder->min_output_buf_req.sz); + vidc_sm_set_chroma_addr_change( + &ddl->shared_mem[ddl->command_channel], + false); + } + output_vcd_frm->interlaced = is_interlaced; + output_vcd_frm->intrlcd_ip_frm_tag = + (!is_interlaced || !dec_disp_info->tag_bottom) ? + VCD_FRAMETAG_INVALID : dec_disp_info->tag_bottom; + output_vcd_frm->offset = 0; + output_vcd_frm->data_len = decoder->y_cb_cr_size; + if (free_luma_dpb) { + output_vcd_frm->data_len = 0; + output_vcd_frm->flags |= VCD_FRAME_FLAG_DECODEONLY; + } + output_vcd_frm->flags |= VCD_FRAME_FLAG_ENDOFFRAME; + output_frame->frm_trans_end = frame_transact_end; + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, DEC_OP_TIME); + ddl_process_decoder_metadata(ddl); + ddl_context->ddl_callback(VCD_EVT_RESP_OUTPUT_DONE, + vcd_status, output_frame, + sizeof(struct ddl_frame_data_tag), + (u32 *)ddl, ddl->client_data); + } + return vcd_status; +} + +static u32 ddl_get_decoded_frame(struct vcd_frame_data *frame, + enum vidc_1080p_decode_frame frame_type) +{ + u32 status = true; + + switch (frame_type) { + case VIDC_1080P_DECODE_FRAMETYPE_I: + frame->flags |= VCD_FRAME_FLAG_SYNCFRAME; + frame->frame = VCD_FRAME_I; + break; + case VIDC_1080P_DECODE_FRAMETYPE_P: + frame->frame = VCD_FRAME_P; + break; + case VIDC_1080P_DECODE_FRAMETYPE_B: + frame->frame = VCD_FRAME_B; + break; + case VIDC_1080P_DECODE_FRAMETYPE_NOT_CODED: + frame->frame = VCD_FRAME_NOTCODED; + frame->data_len = 0; + DDL_MSG_HIGH("DDL_INFO:Decoder:NotCodedFrame>"); + break; + case VIDC_1080P_DECODE_FRAMETYPE_OTHERS: + frame->frame = VCD_FRAME_YUV; + break; + case VIDC_1080P_DECODE_FRAMETYPE_32BIT: + default: + DDL_MSG_ERROR("UNKNOWN-FRAMETYPE"); + status = false; + break; + } + return status; +} + +static u32 ddl_get_encoded_frame(struct vcd_frame_data *frame, + enum vcd_codec codec, + enum vidc_1080p_encode_frame frame_type) +{ + u32 status = true; + + if (codec == VCD_CODEC_H264) { + switch (frame_type) { + case VIDC_1080P_ENCODE_FRAMETYPE_NOT_CODED: + frame->frame = VCD_FRAME_P; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_I: + frame->flags |= VCD_FRAME_FLAG_SYNCFRAME; + frame->frame = VCD_FRAME_I; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_P: + frame->frame = VCD_FRAME_P; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_B: + frame->frame = VCD_FRAME_B; + frame->flags |= VCD_FRAME_FLAG_BFRAME; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_SKIPPED: + frame->frame = VCD_FRAME_NOTCODED; + frame->data_len = 0; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_OTHERS: + DDL_MSG_LOW("FRAMETYPE-OTHERS"); + break; + case VIDC_1080P_ENCODE_FRAMETYPE_32BIT: + default: + DDL_MSG_LOW("UNKNOWN-FRAMETYPE"); + status = false; + break; + } + } else if (codec == VCD_CODEC_MPEG4) { + switch (frame_type) { + case VIDC_1080P_ENCODE_FRAMETYPE_NOT_CODED: + frame->frame = VCD_FRAME_P; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_I: + frame->flags |= VCD_FRAME_FLAG_SYNCFRAME; + frame->frame = VCD_FRAME_I; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_P: + frame->frame = VCD_FRAME_P; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_B: + frame->frame = VCD_FRAME_B; + frame->flags |= VCD_FRAME_FLAG_BFRAME; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_SKIPPED: + frame->frame = VCD_FRAME_NOTCODED; + frame->data_len = 0; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_OTHERS: + DDL_MSG_LOW("FRAMETYPE-OTHERS"); + break; + case VIDC_1080P_ENCODE_FRAMETYPE_32BIT: + default: + DDL_MSG_LOW("UNKNOWN-FRAMETYPE"); + status = false; + break; + } + } else if (codec == VCD_CODEC_H263) { + switch (frame_type) { + case VIDC_1080P_ENCODE_FRAMETYPE_NOT_CODED: + frame->frame = VCD_FRAME_P; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_I: + frame->flags |= VCD_FRAME_FLAG_SYNCFRAME; + frame->frame = VCD_FRAME_I; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_P: + frame->frame = VCD_FRAME_P; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_SKIPPED: + frame->frame = VCD_FRAME_NOTCODED; + frame->data_len = 0; + break; + case VIDC_1080P_ENCODE_FRAMETYPE_OTHERS: + DDL_MSG_LOW("FRAMETYPE-OTHERS"); + break; + case VIDC_1080P_ENCODE_FRAMETYPE_32BIT: + default: + DDL_MSG_LOW("UNKNOWN-FRAMETYPE"); + status = false; + break; + } + } else + status = false; + DDL_MSG_HIGH("Enc Frame Type %u", (u32)frame->frame); + return status; +} + +static void ddl_get_mpeg4_dec_level(enum vcd_codec_level *level, + u32 level_codec, enum vcd_codec_profile mpeg4_profile) +{ + switch (level_codec) { + case VIDC_1080P_MPEG4_LEVEL0: + *level = VCD_LEVEL_MPEG4_0; + break; + case VIDC_1080P_MPEG4_LEVEL0b: + *level = VCD_LEVEL_MPEG4_0b; + break; + case VIDC_1080P_MPEG4_LEVEL1: + *level = VCD_LEVEL_MPEG4_1; + break; + case VIDC_1080P_MPEG4_LEVEL2: + *level = VCD_LEVEL_MPEG4_2; + break; + case VIDC_1080P_MPEG4_LEVEL3: + *level = VCD_LEVEL_MPEG4_3; + break; + case VIDC_1080P_MPEG4_LEVEL3b: + if (mpeg4_profile == VCD_PROFILE_MPEG4_SP) + *level = VCD_LEVEL_MPEG4_7; + else + *level = VCD_LEVEL_MPEG4_3b; + break; + case VIDC_1080P_MPEG4_LEVEL4a: + *level = VCD_LEVEL_MPEG4_4a; + break; + case VIDC_1080P_MPEG4_LEVEL5: + *level = VCD_LEVEL_MPEG4_5; + break; + case VIDC_1080P_MPEG4_LEVEL6: + *level = VCD_LEVEL_MPEG4_6; + break; + default: + *level = VCD_LEVEL_UNKNOWN; + break; + } +} + +static void ddl_get_h264_dec_level(enum vcd_codec_level *level, + u32 level_codec) +{ + switch (level_codec) { + case VIDC_1080P_H264_LEVEL1: + *level = VCD_LEVEL_H264_1; + break; + case VIDC_1080P_H264_LEVEL1b: + *level = VCD_LEVEL_H264_1b; + break; + case VIDC_1080P_H264_LEVEL1p1: + *level = VCD_LEVEL_H264_1p1; + break; + case VIDC_1080P_H264_LEVEL1p2: + *level = VCD_LEVEL_H264_1p2; + break; + case VIDC_1080P_H264_LEVEL1p3: + *level = VCD_LEVEL_H264_1p3; + break; + case VIDC_1080P_H264_LEVEL2: + *level = VCD_LEVEL_H264_2; + break; + case VIDC_1080P_H264_LEVEL2p1: + *level = VCD_LEVEL_H264_2p1; + break; + case VIDC_1080P_H264_LEVEL2p2: + *level = VCD_LEVEL_H264_2p2; + break; + case VIDC_1080P_H264_LEVEL3: + *level = VCD_LEVEL_H264_3; + break; + case VIDC_1080P_H264_LEVEL3p1: + *level = VCD_LEVEL_H264_3p1; + break; + case VIDC_1080P_H264_LEVEL3p2: + *level = VCD_LEVEL_H264_3p2; + break; + case VIDC_1080P_H264_LEVEL4: + *level = VCD_LEVEL_H264_4; + break; + default: + *level = VCD_LEVEL_UNKNOWN; + break; + } +} + +static void ddl_get_h263_dec_level(enum vcd_codec_level *level, + u32 level_codec) +{ + switch (level_codec) { + case VIDC_1080P_H263_LEVEL10: + *level = VCD_LEVEL_H263_10; + break; + case VIDC_1080P_H263_LEVEL20: + *level = VCD_LEVEL_H263_20; + break; + case VIDC_1080P_H263_LEVEL30: + *level = VCD_LEVEL_H263_30; + break; + case VIDC_1080P_H263_LEVEL40: + *level = VCD_LEVEL_H263_40; + break; + case VIDC_1080P_H263_LEVEL45: + *level = VCD_LEVEL_H263_45; + break; + case VIDC_1080P_H263_LEVEL50: + *level = VCD_LEVEL_H263_50; + break; + case VIDC_1080P_H263_LEVEL60: + *level = VCD_LEVEL_H263_60; + break; + case VIDC_1080P_H263_LEVEL70: + *level = VCD_LEVEL_H263_70; + break; + default: + *level = VCD_LEVEL_UNKNOWN; + break; + } +} + +static void ddl_get_vc1_dec_level(enum vcd_codec_level *level, + u32 level_codec, enum vcd_codec_profile vc1_profile) +{ + if (vc1_profile == VCD_PROFILE_VC1_ADVANCE) { + switch (level_codec) { + case VIDC_SM_LEVEL_VC1_ADV_0: + *level = VCD_LEVEL_VC1_A_0; + break; + case VIDC_SM_LEVEL_VC1_ADV_1: + *level = VCD_LEVEL_VC1_A_1; + break; + case VIDC_SM_LEVEL_VC1_ADV_2: + *level = VCD_LEVEL_VC1_A_2; + break; + case VIDC_SM_LEVEL_VC1_ADV_3: + *level = VCD_LEVEL_VC1_A_3; + break; + case VIDC_SM_LEVEL_VC1_ADV_4: + *level = VCD_LEVEL_VC1_A_4; + break; + default: + *level = VCD_LEVEL_UNKNOWN; + break; + } + } else if (vc1_profile == VCD_PROFILE_VC1_MAIN) { + switch (level_codec) { + case VIDC_SM_LEVEL_VC1_LOW: + *level = VCD_LEVEL_VC1_M_LOW; + break; + case VIDC_SM_LEVEL_VC1_MEDIUM: + *level = VCD_LEVEL_VC1_M_MEDIUM; + break; + case VIDC_SM_LEVEL_VC1_HIGH: + *level = VCD_LEVEL_VC1_M_HIGH; + break; + default: + *level = VCD_LEVEL_UNKNOWN; + break; + } + } else if (vc1_profile == VCD_PROFILE_VC1_SIMPLE) { + switch (level_codec) { + case VIDC_SM_LEVEL_VC1_LOW: + *level = VCD_LEVEL_VC1_S_LOW; + break; + case VIDC_SM_LEVEL_VC1_MEDIUM: + *level = VCD_LEVEL_VC1_S_MEDIUM; + break; + default: + *level = VCD_LEVEL_UNKNOWN; + break; + } + } +} + +static void ddl_get_mpeg2_dec_level(enum vcd_codec_level *level, + u32 level_codec) +{ + switch (level_codec) { + case VIDC_SM_LEVEL_MPEG2_LOW: + *level = VCD_LEVEL_MPEG2_LOW; + break; + case VIDC_SM_LEVEL_MPEG2_MAIN: + *level = VCD_LEVEL_MPEG2_MAIN; + break; + case VIDC_SM_LEVEL_MPEG2_HIGH_1440: + *level = VCD_LEVEL_MPEG2_HIGH_14; + break; + case VIDC_SM_LEVEL_MPEG2_HIGH: + *level = VCD_LEVEL_MPEG2_HIGH; + break; + default: + *level = VCD_LEVEL_UNKNOWN; + break; + } +} + +static void ddl_get_dec_profile_level(struct ddl_decoder_data *decoder, + u32 profile_codec, u32 level_codec) +{ + enum vcd_codec_profile profile = VCD_PROFILE_UNKNOWN; + enum vcd_codec_level level = VCD_LEVEL_UNKNOWN; + + switch (decoder->codec.codec) { + case VCD_CODEC_MPEG4: + case VCD_CODEC_XVID: + if (profile_codec == VIDC_SM_PROFILE_MPEG4_SIMPLE) + profile = VCD_PROFILE_MPEG4_SP; + else if (profile_codec == VIDC_SM_PROFILE_MPEG4_ADV_SIMPLE) + profile = VCD_PROFILE_MPEG4_ASP; + else + profile = VCD_PROFILE_UNKNOWN; + ddl_get_mpeg4_dec_level(&level, level_codec, profile); + break; + case VCD_CODEC_H264: + if (profile_codec == VIDC_SM_PROFILE_H264_BASELINE) + profile = VCD_PROFILE_H264_BASELINE; + else if (profile_codec == VIDC_SM_PROFILE_H264_MAIN) + profile = VCD_PROFILE_H264_MAIN; + else if (profile_codec == VIDC_SM_PROFILE_H264_HIGH) + profile = VCD_PROFILE_H264_HIGH; + else + profile = VCD_PROFILE_UNKNOWN; + ddl_get_h264_dec_level(&level, level_codec); + break; + case VCD_CODEC_H263: + if (profile_codec == VIDC_SM_PROFILE_H263_BASELINE) + profile = VCD_PROFILE_H263_BASELINE; + else + profile = VCD_PROFILE_UNKNOWN; + ddl_get_h263_dec_level(&level, level_codec); + break; + case VCD_CODEC_MPEG2: + if (profile_codec == VIDC_SM_PROFILE_MPEG2_MAIN) + profile = VCD_PROFILE_MPEG2_MAIN; + else if (profile_codec == VIDC_SM_PROFILE_MPEG2_SIMPLE) + profile = VCD_PROFILE_MPEG2_SIMPLE; + else + profile = VCD_PROFILE_UNKNOWN; + ddl_get_mpeg2_dec_level(&level, level_codec); + break; + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + if (profile_codec == VIDC_SM_PROFILE_VC1_SIMPLE) + profile = VCD_PROFILE_VC1_SIMPLE; + else if (profile_codec == VIDC_SM_PROFILE_VC1_MAIN) + profile = VCD_PROFILE_VC1_MAIN; + else if (profile_codec == VIDC_SM_PROFILE_VC1_ADVANCED) + profile = VCD_PROFILE_VC1_ADVANCE; + else + profile = VCD_PROFILE_UNKNOWN; + ddl_get_vc1_dec_level(&level, level_codec, profile); + break; + default: + if (!profile_codec) + profile = VCD_PROFILE_UNKNOWN; + if (!level) + level = VCD_LEVEL_UNKNOWN; + break; + } + decoder->profile.profile = profile; + decoder->level.level = level; +} + +static void ddl_handle_enc_frame_done(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + struct vcd_frame_data *output_frame = &(ddl->output_frame.vcd_frm); + u32 bottom_frame_tag; + u8 *input_buffer_address = NULL; + + vidc_sm_get_frame_tags(&ddl->shared_mem[ddl->command_channel], + &output_frame->ip_frm_tag, &bottom_frame_tag); + output_frame->data_len = encoder->enc_frame_info.enc_frame_size; + output_frame->flags |= VCD_FRAME_FLAG_ENDOFFRAME; + (void)ddl_get_encoded_frame(output_frame, + encoder->codec.codec, encoder->enc_frame_info.enc_frame); + ddl_process_encoder_metadata(ddl); + ddl_vidc_encode_dynamic_property(ddl, false); + ddl->input_frame.frm_trans_end = false; + input_buffer_address = ddl_context->dram_base_a.align_physical_addr + + encoder->enc_frame_info.enc_luma_address; + ddl_get_input_frame_from_pool(ddl, input_buffer_address); + + ddl_context->ddl_callback(VCD_EVT_RESP_INPUT_DONE, + VCD_S_SUCCESS, &(ddl->input_frame), + sizeof(struct ddl_frame_data_tag), + (u32 *) ddl, ddl->client_data); + + ddl->output_frame.frm_trans_end = + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_EOS_DONE) + ? false : true; + + ddl_context->ddl_callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_S_SUCCESS, &(ddl->output_frame), + sizeof(struct ddl_frame_data_tag), + (u32 *) ddl, ddl->client_data); + +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_metadata.c b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_metadata.c new file mode 100644 index 00000000000..3f54756b278 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_metadata.c @@ -0,0 +1,505 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vcd_ddl.h" +#include "vcd_ddl_shared_mem.h" +#include "vcd_ddl_metadata.h" + +static u32 *ddl_metadata_hdr_entry(struct ddl_client_context *ddl, + u32 meta_data) +{ + u32 skip_words = 0; + u32 *buffer; + + if (ddl->decoding) { + buffer = (u32 *) ddl->codec_data.decoder.meta_data_input. + align_virtual_addr; + skip_words = 32 + 1; + buffer += skip_words; + switch (meta_data) { + default: + case VCD_METADATA_DATANONE: + skip_words = 0; + break; + case VCD_METADATA_QPARRAY: + skip_words = 3; + break; + case VCD_METADATA_CONCEALMB: + skip_words = 6; + break; + case VCD_METADATA_VC1: + skip_words = 9; + break; + case VCD_METADATA_SEI: + skip_words = 12; + break; + case VCD_METADATA_VUI: + skip_words = 15; + break; + case VCD_METADATA_PASSTHROUGH: + skip_words = 18; + break; + case VCD_METADATA_QCOMFILLER: + skip_words = 21; + break; + } + } else { + buffer = (u32 *) ddl->codec_data.encoder.meta_data_input. + align_virtual_addr; + skip_words = 2; + buffer += skip_words; + switch (meta_data) { + default: + case VCD_METADATA_DATANONE: + skip_words = 0; + break; + case VCD_METADATA_ENC_SLICE: + skip_words = 3; + break; + case VCD_METADATA_QCOMFILLER: + skip_words = 6; + break; + } + } + buffer += skip_words; + return buffer; +} + +void ddl_set_default_meta_data_hdr(struct ddl_client_context *ddl) +{ + struct ddl_buf_addr *main_buffer = + &ddl->ddl_context->metadata_shared_input; + struct ddl_buf_addr *client_buffer; + u32 *hdr_entry; + + if (ddl->decoding) + client_buffer = &(ddl->codec_data.decoder.meta_data_input); + else + client_buffer = &(ddl->codec_data.encoder.meta_data_input); + DDL_METADATA_CLIENT_INPUTBUF(main_buffer, client_buffer, + ddl->instance_id); + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_QCOMFILLER); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_QCOMFILLER; + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_DATANONE); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_DATANONE; + if (ddl->decoding) { + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_QPARRAY); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_QPARRAY; + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_CONCEALMB); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_CONCEALMB; + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_SEI); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_SEI; + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_VUI); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_VUI; + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_VC1); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_VC1; + hdr_entry = ddl_metadata_hdr_entry(ddl, + VCD_METADATA_PASSTHROUGH); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = + VCD_METADATA_PASSTHROUGH; + } else { + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_ENC_SLICE); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_ENC_SLICE; + } +} + +static u32 ddl_supported_metadata_flag(struct ddl_client_context *ddl) +{ + u32 flag = 0; + + if (ddl->decoding) { + enum vcd_codec codec = + ddl->codec_data.decoder.codec.codec; + + flag |= (VCD_METADATA_CONCEALMB | VCD_METADATA_PASSTHROUGH | + VCD_METADATA_QPARRAY); + if (codec == VCD_CODEC_H264) + flag |= (VCD_METADATA_SEI | VCD_METADATA_VUI); + else if (codec == VCD_CODEC_VC1 || + codec == VCD_CODEC_VC1_RCV) + flag |= VCD_METADATA_VC1; + } else + flag |= VCD_METADATA_ENC_SLICE; + return flag; +} + +void ddl_set_default_metadata_flag(struct ddl_client_context *ddl) +{ + if (ddl->decoding) + ddl->codec_data.decoder.meta_data_enable_flag = 0; + else + ddl->codec_data.encoder.meta_data_enable_flag = 0; +} + +void ddl_set_default_decoder_metadata_buffer_size(struct ddl_decoder_data + *decoder, struct vcd_property_frame_size *frame_size, + struct vcd_buffer_requirement *output_buf_req) +{ + u32 flag = decoder->meta_data_enable_flag; + u32 suffix = 0, size = 0; + + if (!flag) { + decoder->suffix = 0; + return; + } + if (flag & VCD_METADATA_QPARRAY) { + u32 num_of_mb = DDL_NO_OF_MB(frame_size->width, + frame_size->height); + + size = DDL_METADATA_HDR_SIZE; + size += num_of_mb; + DDL_METADATA_ALIGNSIZE(size); + suffix += size; + } + if (flag & VCD_METADATA_CONCEALMB) { + u32 num_of_mb = DDL_NO_OF_MB(frame_size->width, + frame_size->height); + size = DDL_METADATA_HDR_SIZE + (num_of_mb >> 3); + DDL_METADATA_ALIGNSIZE(size); + suffix += size; + } + if (flag & VCD_METADATA_VC1) { + size = DDL_METADATA_HDR_SIZE; + size += DDL_METADATA_VC1_PAYLOAD_SIZE; + DDL_METADATA_ALIGNSIZE(size); + suffix += size; + } + if (flag & VCD_METADATA_SEI) { + size = DDL_METADATA_HDR_SIZE; + size += DDL_METADATA_SEI_PAYLOAD_SIZE; + DDL_METADATA_ALIGNSIZE(size); + suffix += (size * DDL_METADATA_SEI_MAX); + } + if (flag & VCD_METADATA_VUI) { + size = DDL_METADATA_HDR_SIZE; + size += DDL_METADATA_VUI_PAYLOAD_SIZE; + DDL_METADATA_ALIGNSIZE(size); + suffix += (size); + } + if (flag & VCD_METADATA_PASSTHROUGH) { + size = DDL_METADATA_HDR_SIZE; + size += DDL_METADATA_PASSTHROUGH_PAYLOAD_SIZE; + DDL_METADATA_ALIGNSIZE(size); + suffix += (size); + } + size = DDL_METADATA_EXTRADATANONE_SIZE; + DDL_METADATA_ALIGNSIZE(size); + suffix += (size); + suffix += DDL_METADATA_EXTRAPAD_SIZE; + DDL_METADATA_ALIGNSIZE(suffix); + decoder->suffix = suffix; + output_buf_req->sz += suffix; + DDL_MSG_LOW("metadata output buf size : %d", suffix); +} + +void ddl_set_default_encoder_metadata_buffer_size(struct ddl_encoder_data + *encoder) +{ + u32 flag = encoder->meta_data_enable_flag; + u32 suffix = 0, size = 0; + + if (!flag) { + encoder->suffix = 0; + return; + } + if (flag & VCD_METADATA_ENC_SLICE) { + u32 num_of_mb = DDL_NO_OF_MB(encoder->frame_size.width, + encoder->frame_size.height); + size = DDL_METADATA_HDR_SIZE; + size += 4; + size += (num_of_mb << 3); + DDL_METADATA_ALIGNSIZE(size); + suffix += size; + } + size = DDL_METADATA_EXTRADATANONE_SIZE; + DDL_METADATA_ALIGNSIZE(size); + suffix += (size); + suffix += DDL_METADATA_EXTRAPAD_SIZE; + DDL_METADATA_ALIGNSIZE(suffix); + encoder->suffix = suffix; + encoder->output_buf_req.sz += suffix; +} + +u32 ddl_set_metadata_params(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + if (property_hdr->prop_id == VCD_I_METADATA_ENABLE) { + struct vcd_property_meta_data_enable *meta_data_enable = + (struct vcd_property_meta_data_enable *) property_value; + u32 *meta_data_enable_flag; + enum vcd_codec codec; + + if (ddl->decoding) { + meta_data_enable_flag = + &(ddl->codec_data.decoder.meta_data_enable_flag); + codec = ddl->codec_data.decoder.codec.codec; + } else { + meta_data_enable_flag = + &ddl->codec_data.encoder.meta_data_enable_flag; + codec = ddl->codec_data.encoder.codec.codec; + } + if (sizeof(struct vcd_property_meta_data_enable) == + property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && codec) { + u32 flag = ddl_supported_metadata_flag(ddl); + flag &= (meta_data_enable->meta_data_enable_flag); + if (flag) + flag |= DDL_METADATA_MANDATORY; + if (*meta_data_enable_flag != flag) { + *meta_data_enable_flag = flag; + if (ddl->decoding) + ddl_set_default_decoder_buffer_req( + &ddl->codec_data.decoder, true); + else + ddl_set_default_encoder_buffer_req( + &ddl->codec_data.encoder); + } + vcd_status = VCD_S_SUCCESS; + } + } else if (property_hdr->prop_id == VCD_I_METADATA_HEADER) { + struct vcd_property_metadata_hdr *hdr = + (struct vcd_property_metadata_hdr *) property_value; + + if (sizeof(struct vcd_property_metadata_hdr) == + property_hdr->sz) { + u32 flag = ddl_supported_metadata_flag(ddl); + + flag |= DDL_METADATA_MANDATORY; + flag &= hdr->meta_data_id; + if (!(flag & (flag - 1))) { + u32 *hdr_entry = ddl_metadata_hdr_entry(ddl, + flag); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = + hdr->version; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = + hdr->port_index; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = + hdr->type; + vcd_status = VCD_S_SUCCESS; + } + } + } + return vcd_status; +} + +u32 ddl_get_metadata_params(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + if (property_hdr->prop_id == VCD_I_METADATA_ENABLE && + sizeof(struct vcd_property_meta_data_enable) == + property_hdr->sz) { + struct vcd_property_meta_data_enable *meta_data_enable = + (struct vcd_property_meta_data_enable *) property_value; + + meta_data_enable->meta_data_enable_flag = + ((ddl->decoding) ? + (ddl->codec_data.decoder.meta_data_enable_flag) : + (ddl->codec_data.encoder.meta_data_enable_flag)); + vcd_status = VCD_S_SUCCESS; + } else if (property_hdr->prop_id == VCD_I_METADATA_HEADER && + sizeof(struct vcd_property_metadata_hdr) == + property_hdr->sz) { + struct vcd_property_metadata_hdr *hdr = + (struct vcd_property_metadata_hdr *) property_value; + u32 flag = ddl_supported_metadata_flag(ddl); + + flag |= DDL_METADATA_MANDATORY; + flag &= hdr->meta_data_id; + if (!(flag & (flag - 1))) { + u32 *hdr_entry = ddl_metadata_hdr_entry(ddl, flag); + hdr->version = + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX]; + hdr->port_index = + hdr_entry[DDL_METADATA_HDR_PORT_INDEX]; + hdr->type = hdr_entry[DDL_METADATA_HDR_TYPE_INDEX]; + vcd_status = VCD_S_SUCCESS; + } + } + return vcd_status; +} + +void ddl_vidc_metadata_enable(struct ddl_client_context *ddl) +{ + u32 flag, extradata_enable = false; + u32 qp_enable = false, concealed_mb_enable = false; + u32 vc1_param_enable = false, sei_nal_enable = false; + u32 vui_enable = false, enc_slice_size_enable = false; + + if (ddl->decoding) + flag = ddl->codec_data.decoder.meta_data_enable_flag; + else + flag = ddl->codec_data.encoder.meta_data_enable_flag; + if (flag) { + if (flag & VCD_METADATA_QPARRAY) + qp_enable = true; + if (flag & VCD_METADATA_CONCEALMB) + concealed_mb_enable = true; + if (flag & VCD_METADATA_VC1) + vc1_param_enable = true; + if (flag & VCD_METADATA_SEI) + sei_nal_enable = true; + if (flag & VCD_METADATA_VUI) + vui_enable = true; + if (flag & VCD_METADATA_ENC_SLICE) + enc_slice_size_enable = true; + if (flag & VCD_METADATA_PASSTHROUGH) + extradata_enable = true; + } + + DDL_MSG_LOW("metadata enable flag : %d", sei_nal_enable); + vidc_sm_set_metadata_enable(&ddl->shared_mem + [ddl->command_channel], extradata_enable, qp_enable, + concealed_mb_enable, vc1_param_enable, sei_nal_enable, + vui_enable, enc_slice_size_enable); +} + +u32 ddl_vidc_encode_set_metadata_output_buf(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &ddl->codec_data.encoder; + struct vcd_frame_data *stream = &ddl->output_frame.vcd_frm; + struct ddl_context *ddl_context; + u32 ext_buffer_end, hw_metadata_start; + u32 *buffer; + + ddl_context = ddl_get_context(); + ext_buffer_end = (u32) stream->physical + stream->alloc_len; + if (!encoder->meta_data_enable_flag) { + ext_buffer_end &= ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + return ext_buffer_end; + } + hw_metadata_start = (ext_buffer_end - encoder->suffix) & + ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + ext_buffer_end = (hw_metadata_start - 1) & + ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + buffer = (u32 *) encoder->meta_data_input.align_virtual_addr; + *buffer++ = encoder->suffix; + *buffer = DDL_OFFSET(ddl_context->dram_base_a.align_physical_addr, + hw_metadata_start); + encoder->meta_data_offset = hw_metadata_start - (u32) stream->physical; + return ext_buffer_end; +} + +void ddl_vidc_decode_set_metadata_output(struct ddl_decoder_data *decoder) +{ + struct ddl_context *ddl_context; + u32 loopc, yuv_size; + u32 *buffer; + + if (!decoder->meta_data_enable_flag) { + decoder->meta_data_offset = 0; + return; + } + ddl_context = ddl_get_context(); + yuv_size = ddl_get_yuv_buffer_size(&decoder->client_frame_size, + &decoder->buf_format, !decoder->progressive_only, + decoder->hdr.decoding, NULL); + decoder->meta_data_offset = DDL_ALIGN_SIZE(yuv_size, + DDL_LINEAR_BUF_ALIGN_GUARD_BYTES, DDL_LINEAR_BUF_ALIGN_MASK); + buffer = (u32 *) decoder->meta_data_input.align_virtual_addr; + *buffer++ = decoder->suffix; + DDL_MSG_LOW("Metadata offset & size : %d/%d", + decoder->meta_data_offset, decoder->suffix); + for (loopc = 0; loopc < decoder->dp_buf.no_of_dec_pic_buf; + ++loopc) { + *buffer++ = (u32)(decoder->meta_data_offset + (u8 *) + DDL_OFFSET(ddl_context->dram_base_a. + align_physical_addr, decoder->dp_buf. + dec_pic_buffers[loopc].vcd_frm.physical)); + } +} + +void ddl_process_encoder_metadata(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + struct vcd_frame_data *out_frame = + &(ddl->output_frame.vcd_frm); + u32 *qfiller_hdr, *qfiller, start_addr; + u32 qfiller_size; + if (!encoder->meta_data_enable_flag) { + out_frame->flags &= ~(VCD_FRAME_FLAG_EXTRADATA); + return; + } + if (!encoder->enc_frame_info.meta_data_exists) { + out_frame->flags &= ~(VCD_FRAME_FLAG_EXTRADATA); + return; + } + out_frame->flags |= VCD_FRAME_FLAG_EXTRADATA; + start_addr = (u32) ((u8 *)out_frame->virtual + out_frame->offset); + qfiller = (u32 *)((out_frame->data_len + + start_addr + 3) & ~3); + qfiller_size = (u32)((encoder->meta_data_offset + + (u8 *) out_frame->virtual) - (u8 *) qfiller); + qfiller_hdr = ddl_metadata_hdr_entry(ddl, VCD_METADATA_QCOMFILLER); + *qfiller++ = qfiller_size; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_VERSION_INDEX]; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_PORT_INDEX]; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_TYPE_INDEX]; + *qfiller = (u32)(qfiller_size - DDL_METADATA_HDR_SIZE); +} + +void ddl_process_decoder_metadata(struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vcd_frame_data *output_frame = + &(ddl->output_frame.vcd_frm); + u32 *qfiller_hdr, *qfiller; + u32 qfiller_size; + + if (!decoder->meta_data_enable_flag) { + output_frame->flags &= ~(VCD_FRAME_FLAG_EXTRADATA); + return; + } + if (!decoder->meta_data_exists) { + output_frame->flags &= ~(VCD_FRAME_FLAG_EXTRADATA); + return; + } + DDL_MSG_LOW("processing metadata for decoder"); + DDL_MSG_LOW("data_len/metadata_offset : %d/%d", + output_frame->data_len, decoder->meta_data_offset); + output_frame->flags |= VCD_FRAME_FLAG_EXTRADATA; + if (output_frame->data_len != decoder->meta_data_offset) { + qfiller = (u32 *)((u32)((output_frame->data_len + + output_frame->offset + + (u8 *) output_frame->virtual) + 3) & ~3); + qfiller_size = (u32)((decoder->meta_data_offset + + (u8 *) output_frame->virtual) - + (u8 *) qfiller); + qfiller_hdr = ddl_metadata_hdr_entry(ddl, + VCD_METADATA_QCOMFILLER); + *qfiller++ = qfiller_size; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_VERSION_INDEX]; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_PORT_INDEX]; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_TYPE_INDEX]; + *qfiller = (u32)(qfiller_size - DDL_METADATA_HDR_SIZE); + } +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_metadata.h b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_metadata.h new file mode 100644 index 00000000000..c63b6a9edc0 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_metadata.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VCD_DDL_METADATA_H_ +#define _VCD_DDL_METADATA_H_ + +#define DDL_MAX_DEC_METADATATYPE 8 +#define DDL_MAX_ENC_METADATATYPE 3 +#define DDL_METADATA_EXTRAPAD_SIZE 256 +#define DDL_METADATA_HDR_SIZE 20 +#define DDL_METADATA_EXTRADATANONE_SIZE 24 +#define DDL_METADATA_ALIGNSIZE(x) ((x) = (((x) + 0x7) & ~0x7)) +#define DDL_METADATA_MANDATORY \ + (VCD_METADATA_DATANONE | VCD_METADATA_QCOMFILLER) +#define DDL_METADATA_VC1_PAYLOAD_SIZE (38*4) +#define DDL_METADATA_SEI_PAYLOAD_SIZE 100 +#define DDL_METADATA_SEI_MAX 5 +#define DDL_METADATA_VUI_PAYLOAD_SIZE 256 +#define DDL_METADATA_PASSTHROUGH_PAYLOAD_SIZE 68 +#define DDL_METADATA_CLIENT_INPUTBUFSIZE 256 +#define DDL_METADATA_TOTAL_INPUTBUFSIZE \ + (DDL_METADATA_CLIENT_INPUTBUFSIZE * VCD_MAX_NO_CLIENT) + +#define DDL_METADATA_CLIENT_INPUTBUF(main_buffer, client_buffer,\ + channel_id) { \ + (client_buffer)->align_physical_addr = (u8 *) \ + ((u8 *)(main_buffer)->align_physical_addr + \ + (DDL_METADATA_CLIENT_INPUTBUFSIZE * channel_id)); \ + (client_buffer)->align_virtual_addr = (u8 *) \ + ((u8 *)(main_buffer)->align_virtual_addr + \ + (DDL_METADATA_CLIENT_INPUTBUFSIZE * channel_id)); \ + (client_buffer)->virtual_base_addr = 0; \ + } + +#define DDL_METADATA_HDR_VERSION_INDEX 0 +#define DDL_METADATA_HDR_PORT_INDEX 1 +#define DDL_METADATA_HDR_TYPE_INDEX 2 + +void ddl_set_default_meta_data_hdr(struct ddl_client_context *ddl); +u32 ddl_get_metadata_params(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value); +u32 ddl_set_metadata_params(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value); +void ddl_set_default_metadata_flag(struct ddl_client_context *ddl); +void ddl_set_default_decoder_metadata_buffer_size(struct ddl_decoder_data + *decoder, struct vcd_property_frame_size *frame_size, + struct vcd_buffer_requirement *output_buf_req); +void ddl_set_default_encoder_metadata_buffer_size( + struct ddl_encoder_data *encoder); +void ddl_vidc_metadata_enable(struct ddl_client_context *ddl); +u32 ddl_vidc_encode_set_metadata_output_buf(struct ddl_client_context *ddl); +void ddl_vidc_decode_set_metadata_output(struct ddl_decoder_data *decoder); +void ddl_process_encoder_metadata(struct ddl_client_context *ddl); +void ddl_process_decoder_metadata(struct ddl_client_context *ddl); + +#endif diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_properties.c b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_properties.c new file mode 100644 index 00000000000..b3656cde32d --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_properties.c @@ -0,0 +1,1904 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vcd_ddl.h" +#include "vcd_ddl_metadata.h" + +static u32 ddl_set_dec_property(struct ddl_client_context *pddl, + struct vcd_property_hdr *property_hdr, void *property_value); +static u32 ddl_set_enc_property(struct ddl_client_context *pddl, + struct vcd_property_hdr *property_hdr, void *property_value); +static u32 ddl_get_dec_property(struct ddl_client_context *pddl, + struct vcd_property_hdr *property_hdr, void *property_value); +static u32 ddl_get_enc_property(struct ddl_client_context *pddl, + struct vcd_property_hdr *property_hdr, void *property_value); +static u32 ddl_set_enc_dynamic_property(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value); +static void ddl_set_default_enc_property(struct ddl_client_context *ddl); +static void ddl_set_default_enc_profile( + struct ddl_encoder_data *encoder); +static void ddl_set_default_enc_level(struct ddl_encoder_data *encoder); +static void ddl_set_default_enc_vop_timing( + struct ddl_encoder_data *encoder); +static void ddl_set_default_enc_intra_period( + struct ddl_encoder_data *encoder); +static void ddl_set_default_enc_rc_params( + struct ddl_encoder_data *encoder); +static u32 ddl_valid_buffer_requirement( + struct vcd_buffer_requirement *original_buf_req, + struct vcd_buffer_requirement *req_buf_req); +static u32 ddl_decoder_min_num_dpb(struct ddl_decoder_data *decoder); +static u32 ddl_set_dec_buffers(struct ddl_decoder_data *decoder, + struct ddl_property_dec_pic_buffers *dpb); + +u32 ddl_set_property(u32 *ddl_handle, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + struct ddl_context *ddl_context; + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + u32 vcd_status; + + DDL_MSG_HIGH("ddl_set_property"); + if (!property_hdr || !property_value) { + DDL_MSG_ERROR("ddl_set_prop:Bad_argument"); + return VCD_ERR_ILLEGAL_PARM; + } + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) { + DDL_MSG_ERROR("ddl_set_prop:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (!ddl) { + DDL_MSG_ERROR("ddl_set_prop:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (ddl->decoding) + vcd_status = ddl_set_dec_property(ddl, property_hdr, + property_value); + else + vcd_status = ddl_set_enc_property(ddl, property_hdr, + property_value); + if (vcd_status) + DDL_MSG_ERROR("ddl_set_prop:FAILED"); + return vcd_status; +} + +u32 ddl_get_property(u32 *ddl_handle, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + struct ddl_context *ddl_context; + struct ddl_client_context *ddl = + (struct ddl_client_context *) ddl_handle; + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + + DDL_MSG_HIGH("ddl_get_property"); + if (!property_hdr || !property_value) + return VCD_ERR_ILLEGAL_PARM; + if (property_hdr->prop_id == DDL_I_CAPABILITY) { + if (sizeof(struct ddl_property_capability) == + property_hdr->sz) { + struct ddl_property_capability *ddl_capability = + (struct ddl_property_capability *) + property_value; + + ddl_capability->max_num_client = VCD_MAX_NO_CLIENT; + ddl_capability->exclusive = VCD_COMMAND_EXCLUSIVE; + ddl_capability->frame_command_depth = + VCD_FRAME_COMMAND_DEPTH; + ddl_capability->general_command_depth = + VCD_GENEVIDC_COMMAND_DEPTH; + ddl_capability->ddl_time_out_in_ms = + DDL_HW_TIMEOUT_IN_MS; + vcd_status = VCD_S_SUCCESS; + } + return vcd_status; + } + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) + return VCD_ERR_ILLEGAL_OP; + if (!ddl) + return VCD_ERR_BAD_HANDLE; + if (ddl->decoding) + vcd_status = ddl_get_dec_property(ddl, property_hdr, + property_value); + else + vcd_status = ddl_get_enc_property(ddl, property_hdr, + property_value); + if (vcd_status) + DDL_MSG_ERROR("ddl_get_prop:FAILED"); + else + DDL_MSG_MED("ddl_get_prop:SUCCESS"); + return vcd_status; +} + +u32 ddl_decoder_ready_to_start(struct ddl_client_context *ddl, + struct vcd_sequence_hdr *header) +{ + struct ddl_decoder_data *decoder = + &(ddl->codec_data.decoder); + + if (!decoder->codec.codec) { + DDL_MSG_ERROR("ddl_dec_start_check:Codec_not_set"); + return false; + } + if ((!header) && (!decoder->client_frame_size.height || + !decoder->client_frame_size.width)) { + DDL_MSG_ERROR("ddl_dec_start_check:" + "Client_height_width_default"); + return false; + } + return true; +} + +u32 ddl_encoder_ready_to_start(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + + if (!encoder->codec.codec || !encoder->frame_size.height || + !encoder->frame_size.width || + !encoder->frame_rate.fps_denominator || + !encoder->frame_rate.fps_numerator || + !encoder->target_bit_rate.target_bitrate) + return false; + if (encoder->frame_rate.fps_numerator > + (encoder->frame_rate.fps_denominator * + encoder->vop_timing.vop_time_resolution)) { + DDL_MSG_ERROR("ResVsFrameRateFailed!"); + return false; + } + if (encoder->profile.profile == VCD_PROFILE_H264_BASELINE && + encoder->entropy_control.entropy_sel == VCD_ENTROPY_SEL_CABAC) { + DDL_MSG_ERROR("H264BaseLineCABAC!!"); + return false; + } + return true; +} + +static u32 ddl_set_dec_property(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + u32 vcd_status = VCD_ERR_ILLEGAL_PARM ; + + switch (property_hdr->prop_id) { + case DDL_I_DPB_RELEASE: + if ((sizeof(struct ddl_frame_data_tag) == + property_hdr->sz) && + (decoder->dp_buf.no_of_dec_pic_buf)) + vcd_status = ddl_decoder_dpb_transact(decoder, + (struct ddl_frame_data_tag *) + property_value, DDL_DPB_OP_MARK_FREE); + break; + case DDL_I_DPB: + { + struct ddl_property_dec_pic_buffers *dpb = + (struct ddl_property_dec_pic_buffers *) property_value; + + if ((sizeof(struct ddl_property_dec_pic_buffers) == + property_hdr->sz) && + (DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_INITCODEC) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB)) && + (dpb->no_of_dec_pic_buf == + decoder->client_output_buf_req.actual_count)) + vcd_status = ddl_set_dec_buffers(decoder, dpb); + } + break; + case DDL_I_REQ_OUTPUT_FLUSH: + if (sizeof(u32) == property_hdr->sz) { + decoder->dynamic_prop_change |= + DDL_DEC_REQ_OUTPUT_FLUSH; + decoder->dpb_mask.client_mask = 0; + vcd_status = VCD_S_SUCCESS; + } + break; + case DDL_I_INPUT_BUF_REQ: + { + struct vcd_buffer_requirement *buffer_req = + (struct vcd_buffer_requirement *)property_value; + + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz && + (DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_INITCODEC) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) && + (ddl_valid_buffer_requirement( + &decoder->min_input_buf_req, buffer_req))) { + decoder->client_input_buf_req = *buffer_req; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case DDL_I_OUTPUT_BUF_REQ: + { + struct vcd_buffer_requirement *buffer_req = + (struct vcd_buffer_requirement *)property_value; + + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz && + (DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_INITCODEC) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) && + (ddl_valid_buffer_requirement( + &decoder->min_output_buf_req, buffer_req))) { + decoder->client_output_buf_req = + *buffer_req; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_CODEC: + { + struct vcd_property_codec *codec = + (struct vcd_property_codec *)property_value; + if (sizeof(struct vcd_property_codec) == + property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && + ddl_codec_type_transact(ddl, false, + codec->codec)) { + if (decoder->codec.codec != codec->codec) { + decoder->codec = *codec; + ddl_set_default_dec_property(ddl); + } + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_POST_FILTER: + if (sizeof(struct vcd_property_post_filter) == + property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && ( + decoder->codec.codec == VCD_CODEC_MPEG4 || + decoder->codec.codec == VCD_CODEC_MPEG2)) { + decoder->post_filter = + *(struct vcd_property_post_filter *) + property_value; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_FRAME_SIZE: + { + struct vcd_property_frame_size *frame_size = + (struct vcd_property_frame_size *) property_value; + if ((sizeof(struct vcd_property_frame_size) == + property_hdr->sz) && + (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) && + (DDL_ALLOW_DEC_FRAMESIZE(frame_size->width, + frame_size->height))) { + if (decoder->client_frame_size.height != + frame_size->height || + decoder->client_frame_size.width != + frame_size->width) { + decoder->client_frame_size = *frame_size; + ddl_set_default_decoder_buffer_req(decoder, + true); + } + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_BUFFER_FORMAT: + { + struct vcd_property_buffer_format *tile = + (struct vcd_property_buffer_format *) + property_value; + if (sizeof(struct vcd_property_buffer_format) == + property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && + tile->buffer_format == VCD_BUFFER_FORMAT_TILE_4x2) { + if (tile->buffer_format != + decoder->buf_format.buffer_format) { + decoder->buf_format = *tile; + ddl_set_default_decoder_buffer_req( + decoder, true); + } + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_H264_MV_BUFFER: + { + int index, buffer_size; + u8 *phys_addr; + u8 *virt_addr; + struct vcd_property_h264_mv_buffer *mv_buff = + (struct vcd_property_h264_mv_buffer *) + property_value; + DDL_MSG_LOW("Entered VCD_I_H264_MV_BUFFER Virt: %p, Phys %p," + "fd: %d size: %d count: %d\n", + mv_buff->kernel_virtual_addr, + mv_buff->physical_addr, + mv_buff->pmem_fd, + mv_buff->size, mv_buff->count); + if ((property_hdr->sz == sizeof(struct + vcd_property_h264_mv_buffer)) && + (DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_INITCODEC) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN))) { + phys_addr = mv_buff->physical_addr; + virt_addr = mv_buff->kernel_virtual_addr; + buffer_size = mv_buff->size/mv_buff->count; + + for (index = 0; index < mv_buff->count; index++) { + ddl->codec_data.decoder.hw_bufs. + h264_mv[index].align_physical_addr + = phys_addr; + ddl->codec_data.decoder.hw_bufs. + h264_mv[index].align_virtual_addr + = virt_addr; + ddl->codec_data.decoder.hw_bufs. + h264_mv[index].buffer_size + = buffer_size; + ddl->codec_data.decoder.hw_bufs. + h264_mv[index].physical_base_addr + = phys_addr; + ddl->codec_data.decoder.hw_bufs. + h264_mv[index].virtual_base_addr + = virt_addr; + DDL_MSG_LOW("Assigned %d buffer for " + "virt: %p, phys %p for " + "h264_mv_buffers " + "of size: %d\n", + index, virt_addr, + phys_addr, buffer_size); + phys_addr += buffer_size; + virt_addr += buffer_size; + } + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_FREE_H264_MV_BUFFER: + { + memset(&decoder->hw_bufs.h264_mv, 0, sizeof(struct + ddl_buf_addr) * DDL_MAX_BUFFER_COUNT); + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_OUTPUT_ORDER: + { + if (sizeof(u32) == property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + decoder->output_order = + *(u32 *)property_value; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_DEC_PICTYPE: + { + if ((sizeof(u32) == property_hdr->sz) && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + decoder->idr_only_decoding = + *(u32 *)property_value; + ddl_set_default_decoder_buffer_req( + decoder, true); + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_METADATA_ENABLE: + case VCD_I_METADATA_HEADER: + DDL_MSG_MED("Meta Data Interface is Requested"); + vcd_status = ddl_set_metadata_params(ddl, property_hdr, + property_value); + vcd_status = VCD_S_SUCCESS; + break; + case VCD_I_FRAME_RATE: + vcd_status = VCD_S_SUCCESS; + break; + case VCD_I_CONT_ON_RECONFIG: + { + DDL_MSG_LOW("Set property VCD_I_CONT_ON_RECONFIG\n"); + if (sizeof(u32) == property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + decoder->cont_mode = *(u32 *)property_value; + vcd_status = VCD_S_SUCCESS; + } + } + break; + default: + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + return vcd_status; +} + +static u32 ddl_check_valid_enc_level(struct vcd_property_codec *codec, + struct vcd_property_profile *profile, + struct vcd_property_level *level) +{ + u32 status = false; + + if (codec && profile && level) { + switch (codec->codec) { + case VCD_CODEC_MPEG4: + status = (profile->profile == + VCD_PROFILE_MPEG4_SP) && + (level->level >= VCD_LEVEL_MPEG4_0) && + (level->level <= VCD_LEVEL_MPEG4_6) && + (VCD_LEVEL_MPEG4_3b != level->level); + status = status || + ((profile->profile == + VCD_PROFILE_MPEG4_ASP) && + (level->level >= VCD_LEVEL_MPEG4_0) && + (level->level <= VCD_LEVEL_MPEG4_5)); + break; + case VCD_CODEC_H264: + status = (level->level >= VCD_LEVEL_H264_1) && + (level->level <= VCD_LEVEL_H264_4); + break; + case VCD_CODEC_H263: + status = (level->level >= VCD_LEVEL_H263_10) && + (level->level <= VCD_LEVEL_H263_70); + break; + default: + break; + } + } + return status; +} + +static u32 ddl_set_enc_property(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, + void *property_value) +{ + struct ddl_encoder_data *encoder = + &(ddl->codec_data.encoder); + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + + if (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + vcd_status = ddl_set_enc_dynamic_property(ddl, + property_hdr, property_value); + } + if (vcd_status) { + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) || + vcd_status != VCD_ERR_ILLEGAL_OP) { + DDL_MSG_ERROR("ddl_set_enc_property:" + "Fails_as_not_in_open_state"); + return VCD_ERR_ILLEGAL_OP; + } + } else + return vcd_status; + + switch (property_hdr->prop_id) { + case VCD_I_FRAME_SIZE: + { + struct vcd_property_frame_size *frame_size = + (struct vcd_property_frame_size *) property_value; + if ((sizeof(struct vcd_property_frame_size) == + property_hdr->sz) && + (DDL_ALLOW_ENC_FRAMESIZE(frame_size->width, + frame_size->height))) { + if (encoder->frame_size.height != frame_size->height || + encoder->frame_size.width != + frame_size->width) { + ddl_calculate_stride(frame_size, false); + encoder->frame_size = *frame_size; + ddl_set_default_encoder_buffer_req(encoder); + } + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_CODEC: + { + struct vcd_property_codec *codec = + (struct vcd_property_codec *) property_value; + if ((sizeof(struct vcd_property_codec) == + property_hdr->sz) && + (ddl_codec_type_transact(ddl, false, codec->codec))) { + if (codec->codec != encoder->codec.codec) { + encoder->codec = *codec; + ddl_set_default_enc_property(ddl); + } + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_REQ_IFRAME: + vcd_status = VCD_S_SUCCESS; + break; + case VCD_I_INTRA_PERIOD: + { + struct vcd_property_i_period *i_period = + (struct vcd_property_i_period *)property_value; + if (sizeof(struct vcd_property_i_period) == + property_hdr->sz && + i_period->b_frames <= DDL_MAX_NUM_OF_B_FRAME) { + encoder->i_period = *i_period; + encoder->client_input_buf_req.min_count = + i_period->b_frames + 1; + encoder->client_input_buf_req.actual_count = + DDL_MAX(encoder->client_input_buf_req.\ + actual_count, encoder->\ + client_input_buf_req.min_count); + encoder->client_output_buf_req.min_count = + i_period->b_frames + 2; + encoder->client_output_buf_req.actual_count = + DDL_MAX(encoder->client_output_buf_req.\ + actual_count, encoder->\ + client_output_buf_req.min_count); + ddl->extra_output_buf_count = + i_period->b_frames - 1; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_PROFILE: + { + struct vcd_property_profile *profile = + (struct vcd_property_profile *)property_value; + + if ((sizeof(struct vcd_property_profile) == + property_hdr->sz) && (( + (encoder->codec.codec == VCD_CODEC_MPEG4) && ( + profile->profile == VCD_PROFILE_MPEG4_SP || + profile->profile == VCD_PROFILE_MPEG4_ASP)) || + ((encoder->codec.codec == VCD_CODEC_H264) && + (profile->profile >= VCD_PROFILE_H264_BASELINE) && + (profile->profile <= VCD_PROFILE_H264_HIGH)) || + ((encoder->codec.codec == VCD_CODEC_H263) && + (profile->profile == VCD_PROFILE_H263_BASELINE)))) { + encoder->profile = *profile; + vcd_status = VCD_S_SUCCESS; + if (profile->profile == VCD_PROFILE_H264_BASELINE) + encoder->entropy_control.entropy_sel = + VCD_ENTROPY_SEL_CAVLC; + else + encoder->entropy_control.entropy_sel = + VCD_ENTROPY_SEL_CABAC; + } + } + break; + case VCD_I_LEVEL: + { + struct vcd_property_level *level = + (struct vcd_property_level *) property_value; + + if ((sizeof(struct vcd_property_level) == + property_hdr->sz) && (ddl_check_valid_enc_level + (&encoder->codec, + &encoder->profile, level))) { + encoder->level = *level; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_MULTI_SLICE: + { + struct vcd_property_multi_slice *multi_slice = + (struct vcd_property_multi_slice *) + property_value; + + switch (multi_slice->m_slice_sel) { + case VCD_MSLICE_OFF: + vcd_status = VCD_S_SUCCESS; + break; + case VCD_MSLICE_BY_GOB: + if (encoder->codec.codec == VCD_CODEC_H263) + vcd_status = VCD_S_SUCCESS; + break; + case VCD_MSLICE_BY_MB_COUNT: + if (multi_slice->m_slice_size >= 1 && + (multi_slice->m_slice_size <= + DDL_NO_OF_MB(encoder->frame_size.width, + encoder->frame_size.height))) + vcd_status = VCD_S_SUCCESS; + break; + case VCD_MSLICE_BY_BYTE_COUNT: + if (multi_slice->m_slice_size > 0) + vcd_status = VCD_S_SUCCESS; + break; + default: + break; + } + if (sizeof(struct vcd_property_multi_slice) == + property_hdr->sz && !vcd_status) { + encoder->multi_slice = *multi_slice; + if (multi_slice->m_slice_sel == VCD_MSLICE_OFF) + encoder->multi_slice.m_slice_size = 0; + } + } + break; + case VCD_I_RATE_CONTROL: + { + struct vcd_property_rate_control *rate_control = + (struct vcd_property_rate_control *) + property_value; + if (sizeof(struct vcd_property_rate_control) == + property_hdr->sz && + rate_control->rate_control >= + VCD_RATE_CONTROL_OFF && + rate_control->rate_control <= + VCD_RATE_CONTROL_CBR_CFR) { + encoder->rc = *rate_control; + ddl_set_default_enc_rc_params(encoder); + vcd_status = VCD_S_SUCCESS; + } + + } + break; + case VCD_I_SHORT_HEADER: + if (sizeof(struct vcd_property_short_header) == + property_hdr->sz && + encoder->codec.codec == + VCD_CODEC_MPEG4) { + encoder->short_header = + *(struct vcd_property_short_header *) + property_value; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_VOP_TIMING: + { + struct vcd_property_vop_timing *vop_time = + (struct vcd_property_vop_timing *) + property_value; + + if ((sizeof(struct vcd_property_vop_timing) == + property_hdr->sz) && + (encoder->frame_rate.fps_numerator <= + vop_time->vop_time_resolution) && + (encoder->codec.codec == VCD_CODEC_MPEG4)) { + encoder->vop_timing = *vop_time; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_HEADER_EXTENSION: + if (sizeof(u32) == property_hdr->sz && + encoder->codec.codec == VCD_CODEC_MPEG4) { + encoder->hdr_ext_control = *(u32 *)property_value; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_ENTROPY_CTRL: + { + struct vcd_property_entropy_control *entropy_control = + (struct vcd_property_entropy_control *) + property_value; + if (sizeof(struct vcd_property_entropy_control) == + property_hdr->sz && + encoder->codec.codec == VCD_CODEC_H264 && + entropy_control->entropy_sel >= + VCD_ENTROPY_SEL_CAVLC && + entropy_control->entropy_sel <= + VCD_ENTROPY_SEL_CABAC) { + if ((entropy_control->entropy_sel == + VCD_ENTROPY_SEL_CABAC) && + (encoder->entropy_control.cabac_model == + VCD_CABAC_MODEL_NUMBER_1 || + encoder->entropy_control.cabac_model == + VCD_CABAC_MODEL_NUMBER_2)) { + vcd_status = VCD_ERR_ILLEGAL_PARM; + } else { + encoder->entropy_control = *entropy_control; + vcd_status = VCD_S_SUCCESS; + } + } + } + break; + case VCD_I_DEBLOCKING: + { + struct vcd_property_db_config *db_config = + (struct vcd_property_db_config *) property_value; + if (sizeof(struct vcd_property_db_config) == + property_hdr->sz && + encoder->codec.codec == VCD_CODEC_H264 && + db_config->db_config >= + VCD_DB_ALL_BLOCKING_BOUNDARY && + db_config->db_config <= + VCD_DB_SKIP_SLICE_BOUNDARY) { + encoder->db_control = *db_config; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_QP_RANGE: + { + struct vcd_property_qp_range *qp = + (struct vcd_property_qp_range *)property_value; + + if ((sizeof(struct vcd_property_qp_range) == + property_hdr->sz) && (qp->min_qp <= + qp->max_qp) && ((encoder->codec.codec == + VCD_CODEC_H264 && qp->max_qp <= DDL_MAX_H264_QP) || + (qp->max_qp <= DDL_MAX_MPEG4_QP))) { + encoder->qp_range = *qp; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_SESSION_QP: + { + struct vcd_property_session_qp *qp = + (struct vcd_property_session_qp *)property_value; + if ((sizeof(struct vcd_property_session_qp) == + property_hdr->sz) && + (qp->i_frame_qp >= encoder->qp_range.min_qp) && + (qp->i_frame_qp <= encoder->qp_range.max_qp) && + (qp->p_frame_qp >= encoder->qp_range.min_qp) && + (qp->p_frame_qp <= encoder->qp_range.max_qp)) { + encoder->session_qp = *qp; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_RC_LEVEL_CONFIG: + { + struct vcd_property_rc_level *rc_level = + (struct vcd_property_rc_level *) property_value; + if (sizeof(struct vcd_property_rc_level) == + property_hdr->sz && + (encoder->rc.rate_control >= + VCD_RATE_CONTROL_VBR_VFR || + encoder->rc.rate_control <= + VCD_RATE_CONTROL_CBR_VFR) && + (!rc_level->mb_level_rc || + encoder->codec.codec == VCD_CODEC_H264)) { + encoder->rc_level = *rc_level; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_FRAME_LEVEL_RC: + { + struct vcd_property_frame_level_rc_params + *frame_level_rc = + (struct vcd_property_frame_level_rc_params *) + property_value; + if ((sizeof(struct vcd_property_frame_level_rc_params) == + property_hdr->sz) && + (frame_level_rc->reaction_coeff) && + (encoder->rc_level.frame_level_rc)) { + encoder->frame_level_rc = *frame_level_rc; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_ADAPTIVE_RC: + if ((sizeof(struct vcd_property_adaptive_rc_params) == + property_hdr->sz) && + (encoder->codec.codec == VCD_CODEC_H264) && + (encoder->rc_level.mb_level_rc)) { + encoder->adaptive_rc = + *(struct vcd_property_adaptive_rc_params *) + property_value; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_BUFFER_FORMAT: + { + struct vcd_property_buffer_format *buffer_format = + (struct vcd_property_buffer_format *) + property_value; + + if (sizeof(struct vcd_property_buffer_format) == + property_hdr->sz && + ((buffer_format->buffer_format == + VCD_BUFFER_FORMAT_NV12_16M2KA) || + (VCD_BUFFER_FORMAT_TILE_4x2 == + buffer_format->buffer_format))) { + if (buffer_format->buffer_format != + encoder->buf_format.buffer_format) { + encoder->buf_format = *buffer_format; + ddl_set_default_encoder_buffer_req(encoder); + } + vcd_status = VCD_S_SUCCESS; + } + } + break; + case DDL_I_INPUT_BUF_REQ: + { + struct vcd_buffer_requirement *buffer_req = + (struct vcd_buffer_requirement *)property_value; + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz && (ddl_valid_buffer_requirement( + &encoder->input_buf_req, buffer_req))) { + encoder->client_input_buf_req = *buffer_req; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case DDL_I_OUTPUT_BUF_REQ: + { + struct vcd_buffer_requirement *buffer_req = + (struct vcd_buffer_requirement *)property_value; + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz && (ddl_valid_buffer_requirement( + &encoder->output_buf_req, buffer_req))) { + encoder->client_output_buf_req = *buffer_req; + encoder->client_output_buf_req.sz = + DDL_ALIGN(buffer_req->sz, + DDL_KILO_BYTE(4)); + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_RECON_BUFFERS: + { + int index; + struct vcd_property_enc_recon_buffer *recon_buffers = + (struct vcd_property_enc_recon_buffer *)property_value; + for (index = 0; index < 4; index++) { + if (!encoder->hw_bufs.dpb_y[index].align_physical_addr) + break; + else + continue; + } + if (property_hdr->sz == sizeof(struct + vcd_property_enc_recon_buffer)) { + encoder->hw_bufs.dpb_y[index].align_physical_addr = + recon_buffers->physical_addr; + encoder->hw_bufs.dpb_y[index].align_virtual_addr = + recon_buffers->kernel_virtual_addr; + encoder->hw_bufs.dpb_y[index].buffer_size = + recon_buffers->buffer_size; + encoder->hw_bufs.dpb_c[index].align_physical_addr = + recon_buffers->physical_addr + ddl_get_yuv_buf_size( + encoder->frame_size.width, encoder->frame_size. + height, DDL_YUV_BUF_TYPE_TILE); + encoder->hw_bufs.dpb_c[index].align_virtual_addr = + recon_buffers->kernel_virtual_addr + + recon_buffers->ysize; + DDL_MSG_LOW("Y::KVirt: %p,KPhys: %p" + "UV::KVirt: %p,KPhys: %p\n", + encoder->hw_bufs.dpb_y[index].align_virtual_addr, + encoder->hw_bufs.dpb_y[index].align_physical_addr, + encoder->hw_bufs.dpb_c[index].align_virtual_addr, + encoder->hw_bufs.dpb_c[index].align_physical_addr); + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_FREE_RECON_BUFFERS: + { + memset(&encoder->hw_bufs.dpb_y, 0, + sizeof(struct ddl_buf_addr) * 4); + memset(&encoder->hw_bufs.dpb_c, 0, + sizeof(struct ddl_buf_addr) * 4); + vcd_status = VCD_S_SUCCESS; + break; + } + case VCD_I_METADATA_ENABLE: + case VCD_I_METADATA_HEADER: + DDL_MSG_ERROR("Meta Data Interface is Requested"); + vcd_status = ddl_set_metadata_params(ddl, property_hdr, + property_value); + vcd_status = VCD_S_SUCCESS; + break; + default: + DDL_MSG_ERROR("INVALID ID %d\n", (int)property_hdr->prop_id); + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + return vcd_status; +} + +static u32 ddl_get_dec_property(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + struct vcd_property_frame_size *fz_size; + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + DDL_MSG_HIGH("property_hdr->prop_id:%x\n", property_hdr->prop_id); + switch (property_hdr->prop_id) { + case VCD_I_FRAME_SIZE: + if (sizeof(struct vcd_property_frame_size) == + property_hdr->sz) { + ddl_calculate_stride(&decoder->client_frame_size, + !decoder->progressive_only); + fz_size = + &decoder->client_frame_size; + fz_size->stride = + DDL_TILE_ALIGN(fz_size->width, + DDL_TILE_ALIGN_WIDTH); + fz_size->scan_lines = + DDL_TILE_ALIGN(fz_size->height, + DDL_TILE_ALIGN_HEIGHT); + *(struct vcd_property_frame_size *) + property_value = + decoder->client_frame_size; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_PROFILE: + if (sizeof(struct vcd_property_profile) == + property_hdr->sz) { + *(struct vcd_property_profile *)property_value = + decoder->profile; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_LEVEL: + if (sizeof(struct vcd_property_level) == + property_hdr->sz) { + *(struct vcd_property_level *)property_value = + decoder->level; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_PROGRESSIVE_ONLY: + if (sizeof(u32) == property_hdr->sz) { + *(u32 *)property_value = + decoder->progressive_only; + vcd_status = VCD_S_SUCCESS; + } + break; + case DDL_I_INPUT_BUF_REQ: + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz) { + *(struct vcd_buffer_requirement *) + property_value = + decoder->client_input_buf_req; + vcd_status = VCD_S_SUCCESS; + } + break; + case DDL_I_OUTPUT_BUF_REQ: + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz) { + *(struct vcd_buffer_requirement *) + property_value = decoder->client_output_buf_req; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_CODEC: + if (sizeof(struct vcd_property_codec) == + property_hdr->sz) { + *(struct vcd_property_codec *) property_value = + decoder->codec; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_BUFFER_FORMAT: + if (sizeof(struct vcd_property_buffer_format) == + property_hdr->sz) { + *(struct vcd_property_buffer_format *) + property_value = decoder->buf_format; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_POST_FILTER: + if (sizeof(struct vcd_property_post_filter) == + property_hdr->sz) { + *(struct vcd_property_post_filter *) + property_value = + decoder->post_filter; + vcd_status = VCD_S_SUCCESS; + } + break; + case DDL_I_SEQHDR_ALIGN_BYTES: + if (sizeof(u32) == property_hdr->sz) { + *(u32 *)property_value = + DDL_LINEAR_BUFFER_ALIGN_BYTES; + vcd_status = VCD_S_SUCCESS; + } + break; + case DDL_I_FRAME_PROC_UNITS: + if (sizeof(u32) == property_hdr->sz) { + if (!decoder->progressive_only && + (decoder->client_frame_size.width * + decoder->client_frame_size.height) <= + DDL_FRAME_VGA_SIZE) { + *(u32 *) property_value = DDL_NO_OF_MB( + DDL_FRAME_720P_WIDTH, + DDL_FRAME_720P_HEIGHT); + } else { + *(u32 *) property_value = DDL_NO_OF_MB( + decoder->client_frame_size.width, + decoder->client_frame_size.height); + } + vcd_status = VCD_S_SUCCESS; + } + break; + case DDL_I_DPB_RETRIEVE: + if (sizeof(struct ddl_frame_data_tag) == + property_hdr->sz) { + vcd_status = ddl_decoder_dpb_transact(decoder, + (struct ddl_frame_data_tag *) + property_value, DDL_DPB_OP_RETRIEVE); + } + break; + case VCD_I_GET_H264_MV_SIZE: + if (property_hdr->sz == sizeof(struct + vcd_property_buffer_size)) { + struct vcd_property_buffer_size *mv_size = + (struct vcd_property_buffer_size *) property_value; + mv_size->size = ddl_get_yuv_buf_size(mv_size->width, + mv_size->height, DDL_YUV_BUF_TYPE_TILE); + mv_size->alignment = DDL_TILE_BUFFER_ALIGN_BYTES; + DDL_MSG_LOW("w: %d, h: %d, S: %d, " + "A: %d", mv_size->width, + mv_size->height, mv_size->size, + mv_size->alignment); + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_OUTPUT_ORDER: + { + if (sizeof(u32) == property_hdr->sz) { + *(u32 *)property_value = decoder->output_order; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_METADATA_ENABLE: + case VCD_I_METADATA_HEADER: + DDL_MSG_ERROR("Meta Data Interface is Requested"); + vcd_status = ddl_get_metadata_params(ddl, property_hdr, + property_value); + vcd_status = VCD_S_SUCCESS; + break; + case VCD_I_CONT_ON_RECONFIG: + if (sizeof(u32) == property_hdr->sz) { + *(u32 *)property_value = decoder->cont_mode; + vcd_status = VCD_S_SUCCESS; + } + break; + default: + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + return vcd_status; +} + +static u32 ddl_get_enc_property(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + struct ddl_encoder_data *encoder = &ddl->codec_data.encoder; + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + + switch (property_hdr->prop_id) { + case VCD_I_CODEC: + if (sizeof(struct vcd_property_codec) == + property_hdr->sz) { + *(struct vcd_property_codec *) property_value = + encoder->codec; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_FRAME_SIZE: + if (sizeof(struct vcd_property_frame_size) == + property_hdr->sz) { + *(struct vcd_property_frame_size *) + property_value = encoder->frame_size; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_FRAME_RATE: + if (sizeof(struct vcd_property_frame_rate) == + property_hdr->sz) { + *(struct vcd_property_frame_rate *) + property_value = encoder->frame_rate; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_TARGET_BITRATE: + if (sizeof(struct vcd_property_target_bitrate) == + property_hdr->sz) { + *(struct vcd_property_target_bitrate *) + property_value = encoder->target_bit_rate; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_RATE_CONTROL: + if (sizeof(struct vcd_property_rate_control) == + property_hdr->sz) { + *(struct vcd_property_rate_control *) + property_value = encoder->rc; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_PROFILE: + if (sizeof(struct vcd_property_profile) == + property_hdr->sz) { + *(struct vcd_property_profile *) property_value = + encoder->profile; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_LEVEL: + if (sizeof(struct vcd_property_level) == + property_hdr->sz) { + *(struct vcd_property_level *) property_value = + encoder->level; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_MULTI_SLICE: + if (sizeof(struct vcd_property_multi_slice) == + property_hdr->sz) { + *(struct vcd_property_multi_slice *) + property_value = encoder->multi_slice; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_SEQ_HEADER: + { + struct vcd_sequence_hdr *seq_hdr = + (struct vcd_sequence_hdr *) property_value; + + if (!encoder->seq_header_length) { + seq_hdr->sequence_header_len = + encoder->seq_header_length; + vcd_status = VCD_ERR_NO_SEQ_HDR; + } else if (sizeof(struct vcd_sequence_hdr) == + property_hdr->sz && + encoder->seq_header_length <= + seq_hdr->sequence_header_len) { + memcpy(seq_hdr->sequence_header, + encoder->seq_header.align_virtual_addr, + encoder->seq_header_length); + seq_hdr->sequence_header_len = + encoder->seq_header_length; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case DDL_I_SEQHDR_PRESENT: + if (sizeof(u32) == property_hdr->sz) { + if ((encoder->codec.codec == + VCD_CODEC_MPEG4 && + !encoder->short_header.short_header) || + encoder->codec.codec == VCD_CODEC_H264) + *(u32 *) property_value = 0x1; + else + *(u32 *) property_value = 0x0; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_VOP_TIMING: + if (sizeof(struct vcd_property_vop_timing) == + property_hdr->sz) { + *(struct vcd_property_vop_timing *) + property_value = encoder->vop_timing; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_SHORT_HEADER: + if (sizeof(struct vcd_property_short_header) == + property_hdr->sz) { + if (encoder->codec.codec == VCD_CODEC_MPEG4) { + *(struct vcd_property_short_header *) + property_value = + encoder->short_header; + vcd_status = VCD_S_SUCCESS; + } else + vcd_status = VCD_ERR_ILLEGAL_OP; + } + break; + case VCD_I_ENTROPY_CTRL: + if (sizeof(struct vcd_property_entropy_control) == + property_hdr->sz) { + if (encoder->codec.codec == VCD_CODEC_H264) { + *(struct vcd_property_entropy_control *) + property_value = + encoder->entropy_control; + vcd_status = VCD_S_SUCCESS; + } else + vcd_status = VCD_ERR_ILLEGAL_OP; + } + break; + case VCD_I_DEBLOCKING: + if (sizeof(struct vcd_property_db_config) == + property_hdr->sz) { + if (encoder->codec.codec == VCD_CODEC_H264) { + *(struct vcd_property_db_config *) + property_value = + encoder->db_control; + vcd_status = VCD_S_SUCCESS; + } else + vcd_status = VCD_ERR_ILLEGAL_OP; + } + break; + case VCD_I_INTRA_PERIOD: + if (sizeof(struct vcd_property_i_period) == + property_hdr->sz) { + *(struct vcd_property_i_period *) + property_value = encoder->i_period; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_QP_RANGE: + if (sizeof(struct vcd_property_qp_range) == + property_hdr->sz) { + *(struct vcd_property_qp_range *) + property_value = encoder->qp_range; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_SESSION_QP: + if (sizeof(struct vcd_property_session_qp) == + property_hdr->sz) { + *(struct vcd_property_session_qp *) + property_value = encoder->session_qp; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_RC_LEVEL_CONFIG: + if (sizeof(struct vcd_property_rc_level) == + property_hdr->sz) { + *(struct vcd_property_rc_level *) + property_value = encoder->rc_level; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_FRAME_LEVEL_RC: + if (sizeof(struct vcd_property_frame_level_rc_params) == + property_hdr->sz) { + *(struct vcd_property_frame_level_rc_params *) + property_value = encoder->frame_level_rc; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_ADAPTIVE_RC: + if (sizeof(struct vcd_property_adaptive_rc_params) == + property_hdr->sz) { + *(struct vcd_property_adaptive_rc_params *) + property_value = encoder->adaptive_rc; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_INTRA_REFRESH: + if (sizeof(struct vcd_property_intra_refresh_mb_number) == + property_hdr->sz) { + *(struct vcd_property_intra_refresh_mb_number *) + property_value = encoder->intra_refresh; + vcd_status = VCD_S_SUCCESS; + } + break; + case DDL_I_INPUT_BUF_REQ: + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz) { + *(struct vcd_buffer_requirement *) + property_value = encoder->client_input_buf_req; + vcd_status = VCD_S_SUCCESS; + } + break; + case DDL_I_OUTPUT_BUF_REQ: + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz) { + *(struct vcd_buffer_requirement *) + property_value = encoder->client_output_buf_req; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_BUFFER_FORMAT: + if (sizeof(struct vcd_property_buffer_format) == + property_hdr->sz) { + *(struct vcd_property_buffer_format *) + property_value = encoder->buf_format; + vcd_status = VCD_S_SUCCESS; + } + break; + case DDL_I_FRAME_PROC_UNITS: + if (sizeof(u32) == property_hdr->sz && + encoder->frame_size.width && + encoder->frame_size.height) { + *(u32 *)property_value = DDL_NO_OF_MB( + encoder->frame_size.width, + encoder->frame_size.height); + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_HEADER_EXTENSION: + if (sizeof(u32) == property_hdr->sz && + encoder->codec.codec == VCD_CODEC_MPEG4) { + *(u32 *) property_value = + encoder->hdr_ext_control; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_GET_RECON_BUFFER_SIZE: + { + u32 ysize, uvsize; + if (property_hdr->sz == sizeof(struct + vcd_property_buffer_size)) { + struct vcd_property_buffer_size *recon_buff_size = + (struct vcd_property_buffer_size *) property_value; + + ysize = ddl_get_yuv_buf_size(recon_buff_size->width, + recon_buff_size->height, DDL_YUV_BUF_TYPE_TILE); + uvsize = ddl_get_yuv_buf_size(recon_buff_size->width, + recon_buff_size->height/2, + DDL_YUV_BUF_TYPE_TILE); + recon_buff_size->size = ysize + uvsize; + recon_buff_size->alignment = + DDL_TILE_BUFFER_ALIGN_BYTES; + DDL_MSG_LOW("w: %d, h: %d, S: %d, A: %d", + recon_buff_size->width, recon_buff_size->height, + recon_buff_size->size, recon_buff_size->alignment); + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_METADATA_ENABLE: + case VCD_I_METADATA_HEADER: + DDL_MSG_ERROR("Meta Data Interface is Requested"); + vcd_status = ddl_get_metadata_params(ddl, property_hdr, + property_value); + vcd_status = VCD_S_SUCCESS; + break; + default: + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + return vcd_status; +} + +static u32 ddl_set_enc_dynamic_property(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + struct ddl_encoder_data *encoder = &ddl->codec_data.encoder; + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + u32 dynamic_prop_change = 0x0; + + switch (property_hdr->prop_id) { + case VCD_I_REQ_IFRAME: + if (sizeof(struct vcd_property_req_i_frame) == + property_hdr->sz) { + dynamic_prop_change |= DDL_ENC_REQ_IFRAME; + vcd_status = VCD_S_SUCCESS; + } + break; + case VCD_I_TARGET_BITRATE: + { + struct vcd_property_target_bitrate *bitrate = + (struct vcd_property_target_bitrate *)property_value; + + if (sizeof(struct vcd_property_target_bitrate) == + property_hdr->sz && bitrate->target_bitrate && + bitrate->target_bitrate <= DDL_MAX_BIT_RATE) { + encoder->target_bit_rate = *bitrate; + dynamic_prop_change = DDL_ENC_CHANGE_BITRATE; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_INTRA_PERIOD: + { + struct vcd_property_i_period *i_period = + (struct vcd_property_i_period *)property_value; + + if (sizeof(struct vcd_property_i_period) == + property_hdr->sz) { + encoder->i_period = *i_period; + dynamic_prop_change = DDL_ENC_CHANGE_IPERIOD; + vcd_status = VCD_S_SUCCESS; + } + } + break; + case VCD_I_FRAME_RATE: + { + struct vcd_property_frame_rate *frame_rate = + (struct vcd_property_frame_rate *) + property_value; + if (sizeof(struct vcd_property_frame_rate) == + property_hdr->sz && + frame_rate->fps_denominator && + frame_rate->fps_numerator && + frame_rate->fps_denominator <= + frame_rate->fps_numerator) { + encoder->frame_rate = *frame_rate; + dynamic_prop_change = DDL_ENC_CHANGE_FRAMERATE; + if (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && + (encoder->codec.codec != VCD_CODEC_MPEG4 || + encoder->short_header.short_header)) { + ddl_set_default_enc_vop_timing(encoder); + } + vcd_status = VCD_S_SUCCESS; + } + } + case VCD_I_INTRA_REFRESH: + { + struct vcd_property_intra_refresh_mb_number + *intra_refresh_mb_num = + (struct vcd_property_intra_refresh_mb_number *) + property_value; + u32 frame_mb_num = DDL_NO_OF_MB(encoder->frame_size.width, + encoder->frame_size.height); + + if ((sizeof(struct vcd_property_intra_refresh_mb_number) == + property_hdr->sz) && + (intra_refresh_mb_num->cir_mb_number <= frame_mb_num)) { + encoder->intra_refresh = *intra_refresh_mb_num; + dynamic_prop_change = DDL_ENC_CHANGE_CIR; + vcd_status = VCD_S_SUCCESS; + } + } + break; + default: + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + + if (!vcd_status && (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME) + || DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE))) + encoder->dynamic_prop_change |= dynamic_prop_change; + + return vcd_status; +} + +void ddl_set_default_dec_property(struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = + &(ddl->codec_data.decoder); + + if (decoder->codec.codec >= VCD_CODEC_MPEG2 && + decoder->codec.codec <= VCD_CODEC_XVID) + decoder->post_filter.post_filter = false; + else + decoder->post_filter.post_filter = false; + decoder->buf_format.buffer_format = VCD_BUFFER_FORMAT_TILE_4x2; + decoder->client_frame_size.height = VCD_DDL_TEST_DEFAULT_HEIGHT; + decoder->client_frame_size.width = VCD_DDL_TEST_DEFAULT_WIDTH; + decoder->client_frame_size.stride = VCD_DDL_TEST_DEFAULT_WIDTH; + decoder->client_frame_size.scan_lines = VCD_DDL_TEST_DEFAULT_HEIGHT; + decoder->progressive_only = 1; + decoder->idr_only_decoding = false; + decoder->output_order = VCD_DEC_ORDER_DISPLAY; + decoder->field_needed_for_prev_ip = 0; + decoder->cont_mode = 0; + ddl_set_default_metadata_flag(ddl); + ddl_set_default_decoder_buffer_req(decoder, true); +} + +static void ddl_set_default_enc_property(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + + ddl_set_default_enc_profile(encoder); + ddl_set_default_enc_level(encoder); + encoder->rc.rate_control = VCD_RATE_CONTROL_VBR_VFR; + ddl_set_default_enc_rc_params(encoder); + ddl_set_default_enc_intra_period(encoder); + encoder->intra_refresh.cir_mb_number = 0; + ddl_set_default_enc_vop_timing(encoder); + encoder->multi_slice.m_slice_sel = VCD_MSLICE_OFF; + encoder->multi_slice.m_slice_size = 0; + ddl->b_count = 0; + encoder->short_header.short_header = false; + encoder->entropy_control.entropy_sel = VCD_ENTROPY_SEL_CAVLC; + encoder->entropy_control.cabac_model = VCD_CABAC_MODEL_NUMBER_0; + encoder->db_control.db_config = + VCD_DB_ALL_BLOCKING_BOUNDARY; + encoder->db_control.slice_alpha_offset = 0; + encoder->db_control.slice_beta_offset = 0; + encoder->recon_buf_format.buffer_format = + VCD_BUFFER_FORMAT_TILE_1x1; + encoder->buf_format.buffer_format = VCD_BUFFER_FORMAT_NV12_16M2KA; + encoder->hdr_ext_control = 0; + encoder->mb_info_enable = false; + encoder->num_references_for_p_frame = DDL_MIN_NUM_REF_FOR_P_FRAME; + ddl_set_default_metadata_flag(ddl); + ddl_set_default_encoder_buffer_req(encoder); +} + +static void ddl_set_default_enc_profile(struct ddl_encoder_data *encoder) +{ + enum vcd_codec codec = encoder->codec.codec; + + if (codec == VCD_CODEC_MPEG4) + encoder->profile.profile = VCD_PROFILE_MPEG4_SP; + else if (codec == VCD_CODEC_H264) + encoder->profile.profile = VCD_PROFILE_H264_BASELINE; + else + encoder->profile.profile = VCD_PROFILE_H263_BASELINE; +} + +static void ddl_set_default_enc_level(struct ddl_encoder_data *encoder) +{ + enum vcd_codec codec = encoder->codec.codec; + + if (codec == VCD_CODEC_MPEG4) + encoder->level.level = VCD_LEVEL_MPEG4_1; + else if (codec == VCD_CODEC_H264) + encoder->level.level = VCD_LEVEL_H264_1; + else + encoder->level.level = VCD_LEVEL_H263_10; +} + +static void ddl_set_default_enc_vop_timing( + struct ddl_encoder_data *encoder) +{ + if (encoder->codec.codec == VCD_CODEC_MPEG4) { + encoder->vop_timing.vop_time_resolution = + (encoder->frame_rate.fps_numerator << 1) / + encoder->frame_rate.fps_denominator; + } else + encoder->vop_timing.vop_time_resolution = + DDL_FRAMERATE_SCALE(DDL_INITIAL_FRAME_RATE); +} + +static void ddl_set_default_enc_intra_period( + struct ddl_encoder_data *encoder) +{ + switch (encoder->rc.rate_control) { + default: + case VCD_RATE_CONTROL_VBR_VFR: + case VCD_RATE_CONTROL_VBR_CFR: + case VCD_RATE_CONTROL_CBR_VFR: + case VCD_RATE_CONTROL_OFF: + encoder->i_period.p_frames = + ((encoder->frame_rate.fps_numerator << 1) / + encoder->frame_rate.fps_denominator) - 1; + break; + case VCD_RATE_CONTROL_CBR_CFR: + encoder->i_period.p_frames = + ((encoder->frame_rate.fps_numerator >> 1) / + encoder->frame_rate.fps_denominator) - 1; + break; + } + encoder->i_period.b_frames = DDL_DEFAULT_NUM_OF_B_FRAME; +} + +static void ddl_set_default_enc_rc_params( + struct ddl_encoder_data *encoder) +{ + enum vcd_codec codec = encoder->codec.codec; + encoder->rc_level.frame_level_rc = true; + encoder->qp_range.min_qp = 0x1; + if (codec == VCD_CODEC_H264) { + encoder->qp_range.min_qp = 0x1; + encoder->qp_range.max_qp = 0x33; + encoder->session_qp.i_frame_qp = 0x14; + encoder->session_qp.p_frame_qp = 0x14; + encoder->session_qp.b_frame_qp = 0x14; + encoder->rc_level.mb_level_rc = true; + encoder->adaptive_rc.disable_activity_region_flag = true; + encoder->adaptive_rc.disable_dark_region_as_flag = true; + encoder->adaptive_rc.disable_smooth_region_as_flag = true; + encoder->adaptive_rc.disable_static_region_as_flag = true; + } else { + encoder->qp_range.max_qp = 0x1f; + encoder->qp_range.min_qp = 0x1; + encoder->session_qp.i_frame_qp = 0xd; + encoder->session_qp.p_frame_qp = 0xd; + encoder->session_qp.b_frame_qp = 0xd; + encoder->rc_level.frame_level_rc = true; + encoder->rc_level.mb_level_rc = false; + } + switch (encoder->rc.rate_control) { + case VCD_RATE_CONTROL_VBR_CFR: + encoder->r_cframe_skip = 0; + encoder->frame_level_rc.reaction_coeff = 0x1f4; + break; + case VCD_RATE_CONTROL_CBR_VFR: + encoder->r_cframe_skip = 1; + if (codec != VCD_CODEC_H264) { + encoder->session_qp.i_frame_qp = 0xf; + encoder->session_qp.p_frame_qp = 0xf; + encoder->session_qp.b_frame_qp = 0xf; + } + encoder->frame_level_rc.reaction_coeff = 0x14; + break; + case VCD_RATE_CONTROL_CBR_CFR: + encoder->r_cframe_skip = 0; + encoder->frame_level_rc.reaction_coeff = 0x6; + break; + case VCD_RATE_CONTROL_OFF: + encoder->r_cframe_skip = 0; + encoder->rc_level.frame_level_rc = false; + encoder->rc_level.mb_level_rc = false; + break; + case VCD_RATE_CONTROL_VBR_VFR: + default: + encoder->r_cframe_skip = 1; + encoder->frame_level_rc.reaction_coeff = 0x1f4; + break; + } +} + +void ddl_set_default_encoder_buffer_req(struct ddl_encoder_data *encoder) +{ + u32 y_cb_cr_size, y_size; + memset(&encoder->hw_bufs.dpb_y, 0, sizeof(struct ddl_buf_addr) * 4); + memset(&encoder->hw_bufs.dpb_c, 0, sizeof(struct ddl_buf_addr) * 4); + + y_cb_cr_size = ddl_get_yuv_buffer_size(&encoder->frame_size, + &encoder->buf_format, false, + encoder->hdr.decoding, &y_size); + encoder->input_buf_size.size_yuv = y_cb_cr_size; + encoder->input_buf_size.size_y = y_size; + encoder->input_buf_size.size_c = y_cb_cr_size - y_size; + memset(&encoder->input_buf_req , 0 , + sizeof(struct vcd_buffer_requirement)); + encoder->input_buf_req.min_count = 3; + encoder->input_buf_req.actual_count = + encoder->input_buf_req.min_count; + encoder->input_buf_req.max_count = DDL_MAX_BUFFER_COUNT; + encoder->input_buf_req.sz = y_cb_cr_size; + if (encoder->buf_format.buffer_format == + VCD_BUFFER_FORMAT_NV12_16M2KA) + encoder->input_buf_req.align = + DDL_LINEAR_BUFFER_ALIGN_BYTES; + else if (VCD_BUFFER_FORMAT_TILE_4x2 == + encoder->buf_format.buffer_format) + encoder->input_buf_req.align = DDL_TILE_BUFFER_ALIGN_BYTES; + encoder->client_input_buf_req = encoder->input_buf_req; + memset(&encoder->output_buf_req , 0 , + sizeof(struct vcd_buffer_requirement)); + encoder->output_buf_req.min_count = + encoder->i_period.b_frames + 2; + encoder->output_buf_req.actual_count = + encoder->output_buf_req.min_count + 3; + encoder->output_buf_req.max_count = DDL_MAX_BUFFER_COUNT; + encoder->output_buf_req.align = DDL_LINEAR_BUFFER_ALIGN_BYTES; + if (y_cb_cr_size >= VCD_DDL_720P_YUV_BUF_SIZE) + y_cb_cr_size = y_cb_cr_size>>1; + encoder->output_buf_req.sz = + DDL_ALIGN(y_cb_cr_size, DDL_KILO_BYTE(4)); + ddl_set_default_encoder_metadata_buffer_size(encoder); + encoder->client_output_buf_req = encoder->output_buf_req; +} + +u32 ddl_set_default_decoder_buffer_req(struct ddl_decoder_data *decoder, + u32 estimate) +{ + struct vcd_property_frame_size *frame_size; + struct vcd_buffer_requirement *input_buf_req; + struct vcd_buffer_requirement *output_buf_req; + u32 min_dpb, y_cb_cr_size; + + if (!decoder->codec.codec) + return false; + min_dpb = ddl_decoder_min_num_dpb(decoder); + if (estimate) { + frame_size = &decoder->client_frame_size; + output_buf_req = &decoder->client_output_buf_req; + input_buf_req = &decoder->client_input_buf_req; + y_cb_cr_size = ddl_get_yuv_buffer_size(frame_size, + &decoder->buf_format, + (!decoder->progressive_only), + decoder->hdr.decoding, NULL); + } else { + if (min_dpb >= decoder->min_dpb_num || + decoder->idr_only_decoding) { + frame_size = &decoder->frame_size; + output_buf_req = &decoder->actual_output_buf_req; + input_buf_req = &decoder->actual_input_buf_req; + min_dpb = decoder->min_dpb_num; + y_cb_cr_size = decoder->y_cb_cr_size; + } else { + u32 max_dpb_size; + + max_dpb_size = DDL_NO_OF_MB( + decoder->client_frame_size.stride, + decoder->client_frame_size.scan_lines); + max_dpb_size *= (decoder->min_dpb_num - 2); + DDL_MSG_ERROR("Error: H264MaxDpbSizeExceeded: %d > %d", + max_dpb_size, MAX_DPB_SIZE_L4PT0_MBS); + return false; + } + } + memset(output_buf_req, 0, + sizeof(struct vcd_buffer_requirement)); + if ((!estimate && !decoder->idr_only_decoding) || (decoder->cont_mode)) + output_buf_req->actual_count = min_dpb + 4; + else + output_buf_req->actual_count = min_dpb; + output_buf_req->min_count = min_dpb; + output_buf_req->max_count = DDL_MAX_BUFFER_COUNT; + output_buf_req->sz = y_cb_cr_size; + DDL_MSG_LOW("output_buf_req->sz : %d", output_buf_req->sz); + if (decoder->buf_format.buffer_format != VCD_BUFFER_FORMAT_NV12) + output_buf_req->align = DDL_TILE_BUFFER_ALIGN_BYTES; + else + output_buf_req->align = DDL_LINEAR_BUFFER_ALIGN_BYTES; + ddl_set_default_decoder_metadata_buffer_size(decoder, frame_size, + output_buf_req); + + decoder->min_output_buf_req = *output_buf_req; + memset(input_buf_req, 0, + sizeof(struct vcd_buffer_requirement)); + input_buf_req->min_count = 1; + input_buf_req->actual_count = input_buf_req->min_count + 1; + input_buf_req->max_count = DDL_MAX_BUFFER_COUNT; + input_buf_req->sz = (1024 * 1024); + input_buf_req->align = DDL_LINEAR_BUFFER_ALIGN_BYTES; + decoder->min_input_buf_req = *input_buf_req; + return true; +} + +u32 ddl_get_yuv_buffer_size(struct vcd_property_frame_size *frame_size, + struct vcd_property_buffer_format *buf_format, + u32 interlace, u32 decoding, u32 *pn_c_offset) +{ + struct vcd_property_frame_size frame_sz = *frame_size; + u32 total_memory_size = 0, c_offset = 0; + ddl_calculate_stride(&frame_sz, interlace); + if (buf_format->buffer_format == VCD_BUFFER_FORMAT_TILE_4x2) { + u32 component_mem_size, width_round_up; + u32 height_round_up, height_chroma = (frame_sz.scan_lines >> 1); + + width_round_up = + DDL_ALIGN(frame_sz.stride, DDL_TILE_ALIGN_WIDTH); + height_round_up = + DDL_ALIGN(frame_sz.scan_lines, + DDL_TILE_ALIGN_HEIGHT); + component_mem_size = width_round_up * height_round_up; + component_mem_size = DDL_ALIGN(component_mem_size, + DDL_TILE_MULTIPLY_FACTOR); + c_offset = component_mem_size; + total_memory_size = ((component_mem_size + + DDL_TILE_BUF_ALIGN_GUARD_BYTES) & + DDL_TILE_BUF_ALIGN_MASK); + height_round_up = DDL_ALIGN(height_chroma, + DDL_TILE_ALIGN_HEIGHT); + component_mem_size = width_round_up * height_round_up; + component_mem_size = DDL_ALIGN(component_mem_size, + DDL_TILE_MULTIPLY_FACTOR); + total_memory_size += component_mem_size; + } else { + if (decoding) + total_memory_size = frame_sz.scan_lines * + frame_sz.stride; + else + total_memory_size = frame_sz.height * frame_sz.stride; + c_offset = DDL_ALIGN(total_memory_size, + DDL_LINEAR_MULTIPLY_FACTOR); + total_memory_size = c_offset + DDL_ALIGN( + total_memory_size >> 1, DDL_LINEAR_MULTIPLY_FACTOR); + } + if (pn_c_offset) + *pn_c_offset = c_offset; + return total_memory_size; +} + + +void ddl_calculate_stride(struct vcd_property_frame_size *frame_size, + u32 interlace) +{ + frame_size->stride = DDL_ALIGN(frame_size->width, + DDL_LINEAR_ALIGN_WIDTH); + if (interlace) + frame_size->scan_lines = DDL_ALIGN(frame_size->height, + DDL_TILE_ALIGN_HEIGHT); + else + frame_size->scan_lines = DDL_ALIGN(frame_size->height, + DDL_LINEAR_ALIGN_HEIGHT); +} + + +static u32 ddl_valid_buffer_requirement(struct vcd_buffer_requirement + *original_buf_req, struct vcd_buffer_requirement *req_buf_req) +{ + u32 status = false; + + if (original_buf_req->max_count >= req_buf_req->actual_count && + original_buf_req->min_count <= + req_buf_req->actual_count && + !((original_buf_req->align - (u32)0x1) & + req_buf_req->align) && + /*original_buf_req->align <= req_buf_req->align,*/ + original_buf_req->sz <= req_buf_req->sz) + status = true; + else + DDL_MSG_ERROR("ddl_valid_buf_req:Failed"); + return status; +} + +static u32 ddl_decoder_min_num_dpb(struct ddl_decoder_data *decoder) +{ + u32 min_dpb = 0; + + if (decoder->idr_only_decoding) { + min_dpb = DDL_MIN_BUFFER_COUNT; + if (decoder->post_filter.post_filter) + min_dpb *= 2; + return min_dpb; + } + + switch (decoder->codec.codec) { + case VCD_CODEC_H264: + { + u32 yuv_size_in_mb = DDL_MIN(DDL_NO_OF_MB( + decoder->client_frame_size.stride, + decoder->client_frame_size.scan_lines), + MAX_FRAME_SIZE_L4PT0_MBS); + min_dpb = DDL_MIN((MAX_DPB_SIZE_L4PT0_MBS / + yuv_size_in_mb), 16); + min_dpb += 2; + } + break; + case VCD_CODEC_H263: + min_dpb = 3; + break; + default: + case VCD_CODEC_MPEG1: + case VCD_CODEC_MPEG2: + case VCD_CODEC_MPEG4: + case VCD_CODEC_DIVX_3: + case VCD_CODEC_DIVX_4: + case VCD_CODEC_DIVX_5: + case VCD_CODEC_DIVX_6: + case VCD_CODEC_XVID: + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + min_dpb = 4; + if (decoder->post_filter.post_filter) + min_dpb *= 2; + break; + } + return min_dpb; +} + +static u32 ddl_set_dec_buffers(struct ddl_decoder_data *decoder, + struct ddl_property_dec_pic_buffers *dpb) +{ + u32 vcd_status = VCD_S_SUCCESS, loopc; + + + for (loopc = 0; !vcd_status && + loopc < dpb->no_of_dec_pic_buf; ++loopc) { + if ((!DDL_ADDR_IS_ALIGNED(dpb->dec_pic_buffers[loopc]. + vcd_frm.physical, + decoder->client_output_buf_req.align)) || + (dpb->dec_pic_buffers[loopc].vcd_frm.alloc_len < + decoder->client_output_buf_req.sz)) + vcd_status = VCD_ERR_ILLEGAL_PARM; + } + if (vcd_status) { + DDL_MSG_ERROR("ddl_set_prop:" + "Dpb_align_fail_or_alloc_size_small"); + return vcd_status; + } + if (decoder->dp_buf.no_of_dec_pic_buf) { + kfree(decoder->dp_buf.dec_pic_buffers); + decoder->dp_buf.no_of_dec_pic_buf = 0; + } + decoder->dp_buf.dec_pic_buffers = + kmalloc(dpb->no_of_dec_pic_buf * + sizeof(struct ddl_frame_data_tag), GFP_KERNEL); + if (!decoder->dp_buf.dec_pic_buffers) { + DDL_MSG_ERROR("ddl_dec_set_prop:Dpb_container_alloc_failed"); + return VCD_ERR_ALLOC_FAIL; + } + decoder->dp_buf.no_of_dec_pic_buf = dpb->no_of_dec_pic_buf; + for (loopc = 0; loopc < dpb->no_of_dec_pic_buf; ++loopc) + decoder->dp_buf.dec_pic_buffers[loopc] = + dpb->dec_pic_buffers[loopc]; + decoder->dpb_mask.client_mask = 0; + decoder->dpb_mask.hw_mask = 0; + decoder->dynamic_prop_change = 0; + return VCD_S_SUCCESS; +} + +void ddl_set_initial_default_values(struct ddl_client_context *ddl) +{ + + if (ddl->decoding) { + ddl->codec_data.decoder.codec.codec = VCD_CODEC_MPEG4; + ddl_set_default_dec_property(ddl); + } else { + struct ddl_encoder_data *encoder = + &(ddl->codec_data.encoder); + encoder->codec.codec = VCD_CODEC_MPEG4; + encoder->target_bit_rate.target_bitrate = 64000; + encoder->frame_size.width = VCD_DDL_TEST_DEFAULT_WIDTH; + encoder->frame_size.height = VCD_DDL_TEST_DEFAULT_HEIGHT; + encoder->frame_size.scan_lines = + VCD_DDL_TEST_DEFAULT_HEIGHT; + encoder->frame_size.stride = VCD_DDL_TEST_DEFAULT_WIDTH; + encoder->frame_rate.fps_numerator = DDL_INITIAL_FRAME_RATE; + encoder->frame_rate.fps_denominator = 1; + ddl_set_default_enc_property(ddl); + } +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.c b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.c new file mode 100644 index 00000000000..70e1de1dae5 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.c @@ -0,0 +1,678 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vcd_ddl_shared_mem.h" + +#define VIDC_SM_EXTENDED_DECODE_STATUS_ADDR 0x0000 +#define VIDC_SM_EXT_DEC_STATUS_RESOLUTION_CHANGE_BMSK 0x1 +#define VIDC_SM_EXT_DEC_STATUS_RESOLUTION_CHANGE_SHFT 0x0 +#define VIDC_SM_EXT_DEC_STATUS_MORE_FIELD_NEEDED_BMSK 0x4 +#define VIDC_SM_EXT_DEC_STATUS_MORE_FIELD_NEEDED_SHFT 0x2 + +#define VIDC_SM_SET_FRAME_TAG_ADDR 0x0004 +#define VIDC_SM_GET_FRAME_TAG_TOP_ADDR 0x0008 +#define VIDC_SM_GET_FRAME_TAG_BOTTOM_ADDR 0x000c +#define VIDC_SM_PIC_TIME_TOP_ADDR 0x0010 +#define VIDC_SM_PIC_TIME_BOTTOM_ADDR 0x0014 +#define VIDC_SM_START_BYTE_NUM_ADDR 0x0018 + +#define VIDC_SM_CROP_INFO1_ADDR 0x0020 +#define VIDC_SM_CROP_INFO1_RIGHT_OFFSET_BMSK 0xffff0000 +#define VIDC_SM_CROP_INFO1_RIGHT_OFFSET_SHFT 16 +#define VIDC_SM_CROP_INFO1_LEFT_OFFSET_BMSK 0x0000ffff +#define VIDC_SM_CROP_INFO1_LEFT_OFFSET_SHFT 0 + +#define VIDC_SM_CROP_INFO2_ADDR 0x0024 +#define VIDC_SM_CROP_INFO2_BOTTOM_OFFSET_BMSK 0xffff0000 +#define VIDC_SM_CROP_INFO2_BOTTOM_OFFSET_SHFT 16 +#define VIDC_SM_CROP_INFO2_TOP_OFFSET_BMSK 0x0000ffff +#define VIDC_SM_CROP_INFO2_TOP_OFFSET_SHFT 0 + +#define VIDC_SM_DISP_PIC_PROFILE_ADDR 0x007c +#define VIDC_SM_DISP_PIC_PROFILE_DISP_PIC_LEVEL_BMASK 0x0000ff00 +#define VIDC_SM_DISP_PIC_PROFILE_DISP_PIC_LEVEL_SHFT 8 +#define VIDC_SM_DISP_PIC_PROFILE_DISP_PIC_PROFILE_BMASK 0x0000001f +#define VIDC_SM_DISP_PIC_PROFILE_DISP_PIC_PROFILE_SHFT 0 + +#define VIDC_SM_DISP_PIC_FRAME_TYPE_ADDR 0x00c0 +#define VIDC_SM_DISP_PIC_FRAME_TYPE_BMSK 0x00000003 +#define VIDC_SM_DISP_PIC_FRAME_TYPE_SHFT 0 + +#define VIDC_SM_FREE_LUMA_DPB_ADDR 0x00c4 +#define VIDC_SM_FREE_LUMA_DPB_BMSK 0xffffffff +#define VIDC_SM_FREE_LUMA_DPB_SHFT 0 + +#define VIDC_SM_FREE_LUMA_DPB_DEC_ORDER_ADDR 0x00fc +#define VIDC_SM_FREE_LUMA_DPB_DEC_ORDER_BMSK 0xffffffff +#define VIDC_SM_FREE_LUMA_DPB_DEC_ORDER_SHFT 0 + +#define VIDC_SM_DEC_ORDER_WIDTH_ADDR 0x00e8 +#define VIDC_SM_DEC_ORDER_WIDTH_BMSK 0xffffffff +#define VIDC_SM_DEC_ORDER_WIDTH_SHFT 0 + +#define VIDC_SM_DEC_ORDER_HEIGHT_ADDR 0x00ec +#define VIDC_SM_DEC_ORDER_HEIGHT_BMSK 0xffffffff +#define VIDC_SM_DEC_ORDER_HEIGHT_SHFT 0 + +#define VIDC_SM_DEC_CROP_INFO1_ADDR 0x00f4 +#define VIDC_SM_DEC_CROP_INFO1_RIGHT_OFFSET_BMSK 0xffff0000 +#define VIDC_SM_DEC_CROP_INFO1_RIGHT_OFFSET_SHFT 16 +#define VIDC_SM_DEC_CROP_INFO1_LEFT_OFFSET_BMSK 0x0000ffff +#define VIDC_SM_DEC_CROP_INFO1_LEFT_OFFSET_SHFT 0 + +#define VIDC_SM_DEC_CROP_INFO2_ADDR 0x00f8 +#define VIDC_SM_DEC_CROP_INFO2_BOTTOM_OFFSET_BMSK 0xffff0000 +#define VIDC_SM_DEC_CROP_INFO2_BOTTOM_OFFSET_SHFT 16 +#define VIDC_SM_DEC_CROP_INFO2_TOP_OFFSET_BMSK 0x0000ffff +#define VIDC_SM_DEC_CROP_INFO2_TOP_OFFSET_SHFT 0 + +#define VIDC_SM_IDR_DECODING_ONLY_ADDR 0x0108 +#define VIDC_SM_IDR_DECODING_ONLY_BMSK 0x00000001 +#define VIDC_SM_IDR_DECODING_ONLY_SHIFT 0 + +#define VIDC_SM_ENC_EXT_CTRL_ADDR 0x0028 +#define VIDC_SM_ENC_EXT_CTRL_VBV_BUFFER_SIZE_BMSK 0xffff0000 +#define VIDC_SM_ENC_EXT_CTRL_VBV_BUFFER_SIZE_SHFT 16 +#define VIDC_SM_ENC_EXT_CTRL_SEQ_HDR_CTRL_BMSK 0x8 +#define VIDC_SM_ENC_EXT_CTRL_SEQ_HDR_CTRL_SHFT 3 +#define VIDC_SM_ENC_EXT_CTRL_FRAME_SKIP_ENABLE_BMSK 0x6 +#define VIDC_SM_ENC_EXT_CTRL_FRAME_SKIP_ENABLE_SHFT 1 +#define VIDC_SM_ENC_EXT_CTRL_HEC_ENABLE_BMSK 0x1 +#define VIDC_SM_ENC_EXT_CTRL_HEC_ENABLE_SHFT 0 + +#define VIDC_SM_ENC_PARAM_CHANGE_ADDR 0x002c +#define VIDC_SM_ENC_PARAM_CHANGE_RC_BIT_RATE_BMSK 0x4 +#define VIDC_SM_ENC_PARAM_CHANGE_RC_BIT_RATE_SHFT 2 +#define VIDC_SM_ENC_PARAM_CHANGE_RC_FRAME_RATE_BMSK 0x2 +#define VIDC_SM_ENC_PARAM_CHANGE_RC_FRAME_RATE_SHFT 1 +#define VIDC_SM_ENC_PARAM_CHANGE_I_PERIOD_BMSK 0x1 +#define VIDC_SM_ENC_PARAM_CHANGE_I_PERIOD_SHFT 0 + +#define VIDC_SM_ENC_VOP_TIMING_ADDR 0x0030 +#define VIDC_SM_ENC_VOP_TIMING_ENABLE_BMSK 0x80000000 +#define VIDC_SM_ENC_VOP_TIMING_ENABLE_SHFT 31 +#define VIDC_SM_ENC_VOP_TIMING_TIME_RESOLUTION_BMSK 0x7fff0000 +#define VIDC_SM_ENC_VOP_TIMING_TIME_RESOLUTION_SHFT 16 +#define VIDC_SM_ENC_VOP_TIMING_FRAME_DELTA_BMSK 0x0000ffff +#define VIDC_SM_ENC_VOP_TIMING_FRAME_DELTA_SHFT 0 + +#define VIDC_SM_ENC_HEC_PERIOD_ADDR 0x0034 + +#define VIDC_SM_H264_REF_L0_ADDR 0x005c +#define VIDC_SM_H264_REF_L0_CHRO_BTM_FLG_1_BMSK 0x80000000 +#define VIDC_SM_H264_REF_L0_CHRO_BTM_FLG_1_SHFT 31 +#define VIDC_SM_H264_REF_L0_CHRO_REF_1_BMSK 0x7f000000 +#define VIDC_SM_H264_REF_L0_CHRO_REF_1_SHFT 24 +#define VIDC_SM_H264_REF_L0_CHRO_BTM_FLG_0_BMSK 0x00800000 +#define VIDC_SM_H264_REF_L0_CHRO_BTM_FLG_0_SHFT 23 +#define VIDC_SM_H264_REF_L0_CHRO_REF_0_BMSK 0x007f0000 +#define VIDC_SM_H264_REF_L0_CHRO_REF_0_SHFT 16 +#define VIDC_SM_H264_REF_L0_LUMA_BTM_FLG_1_BMSK 0x00008000 +#define VIDC_SM_H264_REF_L0_LUMA_BTM_FLG_1_SHFT 15 +#define VIDC_SM_H264_REF_L0_LUMA_REF_1_BMSK 0x00007f00 +#define VIDC_SM_H264_REF_L0_LUMA_REF_1_SHFT 8 +#define VIDC_SM_H264_REF_L0_LUMA_BTM_FLG_0_BMSK 0x00000080 +#define VIDC_SM_H264_REF_L0_LUMA_BTM_FLG_0_SHFT 7 +#define VIDC_SM_H264_REF_L0_LUMA_REF_0_BMSK 0x0000007f +#define VIDC_SM_H264_REF_L0_LUMA_REF_0_SHFT 0 + +#define VIDC_SM_H264_REF_L1_ADDR 0x0060 +#define VIDC_SM_H264_REF_L1_CHRO_BTM_FLG_0_BMSK 0x00800000 +#define VIDC_SM_H264_REF_L1_CHRO_BTM_FLG_0_SHFT 23 +#define VIDC_SM_H264_REF_L1_CHRO_REF_0_BMSK 0x007f0000 +#define VIDC_SM_H264_REF_L1_CHRO_REF_0_SHFT 16 +#define VIDC_SM_H264_REF_L1_LUMA_BTM_FLG_0_BMSK 0x00000080 +#define VIDC_SM_H264_REF_L1_LUMA_BTM_FLG_0_SHFT 7 +#define VIDC_SM_H264_REF_L1_LUMA_REF_0_BMSK 0x0000007f +#define VIDC_SM_H264_REF_L1_LUMA_REF_0_SHFT 0 + +#define VIDC_SM_P_B_FRAME_QP_ADDR 0x0070 +#define VIDC_SM_P_B_FRAME_QP_B_FRAME_QP_BMASK 0x00000fc0 +#define VIDC_SM_P_B_FRAME_QP_B_FRAME_QP_SHFT 6 +#define VIDC_SM_P_B_FRAME_QP_P_FRAME_QP_BMASK 0x0000003f +#define VIDC_SM_P_B_FRAME_QP_P_FRAME_QP_SHFT 0 + +#define VIDC_SM_NEW_RC_BIT_RATE_ADDR 0x0090 +#define VIDC_SM_NEW_RC_BIT_RATE_VALUE_BMASK 0xffffffff +#define VIDC_SM_NEW_RC_BIT_RATE_VALUE_SHFT 0 +#define VIDC_SM_NEW_RC_FRAME_RATE_ADDR 0x0094 +#define VIDC_SM_NEW_RC_FRAME_RATE_VALUE_BMASK 0xffffffff +#define VIDC_SM_NEW_RC_FRAME_RATE_VALUE_SHFT 0 +#define VIDC_SM_NEW_I_PERIOD_ADDR 0x0098 +#define VIDC_SM_NEW_I_PERIOD_VALUE_BMASK 0xffffffff +#define VIDC_SM_NEW_I_PERIOD_VALUE_SHFT 0 + + +#define VIDC_SM_ALLOCATED_LUMA_DPB_SIZE_ADDR 0x0064 +#define VIDC_SM_ALLOCATED_CHROMA_DPB_SIZE_ADDR 0x0068 +#define VIDC_SM_ALLOCATED_MV_SIZE_ADDR 0x006c +#define VIDC_SM_FLUSH_CMD_TYPE_ADDR 0x0080 +#define VIDC_SM_FLUSH_CMD_INBUF1_ADDR 0x0084 +#define VIDC_SM_FLUSH_CMD_INBUF2_ADDR 0x0088 +#define VIDC_SM_FLUSH_CMD_OUTBUF_ADDR 0x008c +#define VIDC_SM_MIN_LUMA_DPB_SIZE_ADDR 0x00b0 +#define VIDC_SM_MIN_CHROMA_DPB_SIZE_ADDR 0x00bc + + +#define VIDC_SM_METADATA_ENABLE_ADDR 0x0038 +#define VIDC_SM_METADATA_ENABLE_EXTRADATA_BMSK 0x40 +#define VIDC_SM_METADATA_ENABLE_EXTRADATA_SHFT 6 +#define VIDC_SM_METADATA_ENABLE_ENC_SLICE_SIZE_BMSK 0x20 +#define VIDC_SM_METADATA_ENABLE_ENC_SLICE_SIZE_SHFT 5 +#define VIDC_SM_METADATA_ENABLE_VUI_BMSK 0x10 +#define VIDC_SM_METADATA_ENABLE_VUI_SHFT 4 +#define VIDC_SM_METADATA_ENABLE_SEI_VIDC_BMSK 0x8 +#define VIDC_SM_METADATA_ENABLE_SEI_VIDC_SHFT 3 +#define VIDC_SM_METADATA_ENABLE_VC1_PARAM_BMSK 0x4 +#define VIDC_SM_METADATA_ENABLE_VC1_PARAM_SHFT 2 +#define VIDC_SM_METADATA_ENABLE_CONCEALED_MB_BMSK 0x2 +#define VIDC_SM_METADATA_ENABLE_CONCEALED_MB_SHFT 1 +#define VIDC_SM_METADATA_ENABLE_QP_BMSK 0x1 +#define VIDC_SM_METADATA_ENABLE_QP_SHFT 0 + + +#define VIDC_SM_METADATA_STATUS_ADDR 0x003c +#define VIDC_SM_METADATA_STATUS_STATUS_BMSK 0x1 +#define VIDC_SM_METADATA_STATUS_STATUS_SHFT 0 + +#define VIDC_SM_METADATA_DISPLAY_INDEX_ADDR 0x0040 +#define VIDC_SM_EXT_METADATA_START_ADDR_ADDR 0x0044 + +#define VIDC_SM_PUT_EXTRADATA_ADDR 0x0048 +#define VIDC_SM_PUT_EXTRADATA_PUT_BMSK 0x1 +#define VIDC_SM_PUT_EXTRADATA_PUT_SHFT 0 + +#define VIDC_SM_EXTRADATA_ADDR_ADDR 0x004c + +#define VIDC_SM_CHROMA_ADDR_CHANGE_ADDR 0x0148 +#define VIDC_SM_CHROMA_ADDR_CHANGE_BMASK 0x00000001 +#define VIDC_SM_CHROMA_ADDR_CHANGE_SHFT 0 + +#define DDL_MEM_WRITE_32(base, offset, val) ddl_mem_write_32(\ + (u32 *) ((u8 *) (base)->align_virtual_addr + (offset)), (val)) +#define DDL_MEM_READ_32(base, offset) ddl_mem_read_32(\ + (u32 *) ((u8 *) (base)->align_virtual_addr + (offset))) + +#define DDL_SHARED_MEM_11BIT_RIGHT_SHIFT 11 + +static void ddl_mem_write_32(u32 *addr, u32 data) +{ + *addr = data; +} + +static u32 ddl_mem_read_32(u32 *addr) +{ + return *addr; +} + +void vidc_sm_get_extended_decode_status(struct ddl_buf_addr *shared_mem, + u32 *more_field_needed, + u32 *resl_change) +{ + u32 decode_status = DDL_MEM_READ_32(shared_mem, + VIDC_SM_EXTENDED_DECODE_STATUS_ADDR); + if (more_field_needed) + *more_field_needed = + VIDC_GETFIELD(decode_status, + VIDC_SM_EXT_DEC_STATUS_MORE_FIELD_NEEDED_BMSK, + VIDC_SM_EXT_DEC_STATUS_MORE_FIELD_NEEDED_SHFT); + if (resl_change) + *resl_change = + VIDC_GETFIELD(decode_status, + VIDC_SM_EXT_DEC_STATUS_RESOLUTION_CHANGE_BMSK, + VIDC_SM_EXT_DEC_STATUS_RESOLUTION_CHANGE_SHFT); +} + +void vidc_sm_set_frame_tag(struct ddl_buf_addr *shared_mem, + u32 frame_tag) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_SET_FRAME_TAG_ADDR, frame_tag); +} + +void vidc_sm_get_frame_tags(struct ddl_buf_addr *shared_mem, + u32 *pn_frame_tag_top, u32 *pn_frame_tag_bottom) +{ + *pn_frame_tag_top = DDL_MEM_READ_32(shared_mem, + VIDC_SM_GET_FRAME_TAG_TOP_ADDR); + *pn_frame_tag_bottom = DDL_MEM_READ_32(shared_mem, + VIDC_SM_GET_FRAME_TAG_BOTTOM_ADDR); +} + +void vidc_sm_get_picture_times(struct ddl_buf_addr *shared_mem, + u32 *pn_time_top, u32 *pn_time_bottom) +{ + *pn_time_top = DDL_MEM_READ_32(shared_mem, VIDC_SM_PIC_TIME_TOP_ADDR); + *pn_time_bottom = DDL_MEM_READ_32(shared_mem, + VIDC_SM_PIC_TIME_BOTTOM_ADDR); +} + +void vidc_sm_set_start_byte_number(struct ddl_buf_addr *shared_mem, + u32 byte_num) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_START_BYTE_NUM_ADDR, byte_num); +} + +void vidc_sm_get_crop_info(struct ddl_buf_addr *shared_mem, + u32 *pn_left, u32 *pn_right, u32 *pn_top, u32 *pn_bottom) +{ + u32 info1, info2; + + info1 = DDL_MEM_READ_32(shared_mem, VIDC_SM_CROP_INFO1_ADDR); + + *pn_left = VIDC_GETFIELD(info1, VIDC_SM_CROP_INFO1_LEFT_OFFSET_BMSK, + VIDC_SM_CROP_INFO1_LEFT_OFFSET_SHFT); + *pn_right = VIDC_GETFIELD(info1, VIDC_SM_CROP_INFO1_RIGHT_OFFSET_BMSK, + VIDC_SM_CROP_INFO1_RIGHT_OFFSET_SHFT); + info2 = DDL_MEM_READ_32(shared_mem, VIDC_SM_CROP_INFO2_ADDR); + *pn_top = VIDC_GETFIELD(info2, VIDC_SM_CROP_INFO2_TOP_OFFSET_BMSK, + VIDC_SM_CROP_INFO2_TOP_OFFSET_SHFT); + *pn_bottom = VIDC_GETFIELD(info2, + VIDC_SM_CROP_INFO2_BOTTOM_OFFSET_BMSK, + VIDC_SM_CROP_INFO2_BOTTOM_OFFSET_SHFT); +} + +void vidc_sm_get_displayed_picture_frame(struct ddl_buf_addr + *shared_mem, u32 *n_disp_picture_frame) +{ + u32 disp_pict_frame; + + disp_pict_frame = DDL_MEM_READ_32(shared_mem, + VIDC_SM_DISP_PIC_FRAME_TYPE_ADDR); + *n_disp_picture_frame = VIDC_GETFIELD(disp_pict_frame, + VIDC_SM_DISP_PIC_FRAME_TYPE_BMSK, + VIDC_SM_DISP_PIC_FRAME_TYPE_SHFT); +} +void vidc_sm_get_available_luma_dpb_address(struct ddl_buf_addr + *shared_mem, u32 *pn_free_luma_dpb_address) +{ + *pn_free_luma_dpb_address = DDL_MEM_READ_32(shared_mem, + VIDC_SM_FREE_LUMA_DPB_ADDR); +} + +void vidc_sm_get_available_luma_dpb_dec_order_address( + struct ddl_buf_addr *shared_mem, + u32 *pn_free_luma_dpb_address) +{ + *pn_free_luma_dpb_address = DDL_MEM_READ_32(shared_mem, + VIDC_SM_FREE_LUMA_DPB_DEC_ORDER_ADDR); +} + +void vidc_sm_get_dec_order_resl( + struct ddl_buf_addr *shared_mem, u32 *width, u32 *height) +{ + *width = DDL_MEM_READ_32(shared_mem, + VIDC_SM_DEC_ORDER_WIDTH_ADDR); + *height = DDL_MEM_READ_32(shared_mem, + VIDC_SM_DEC_ORDER_HEIGHT_ADDR); +} + +void vidc_sm_get_dec_order_crop_info( + struct ddl_buf_addr *shared_mem, u32 *left, + u32 *right, u32 *top, u32 *bottom) +{ + u32 crop_data; + crop_data = DDL_MEM_READ_32(shared_mem, + VIDC_SM_DEC_CROP_INFO1_ADDR); + *left = VIDC_GETFIELD(crop_data, + VIDC_SM_DEC_CROP_INFO1_LEFT_OFFSET_BMSK, + VIDC_SM_DEC_CROP_INFO1_LEFT_OFFSET_SHFT); + *right = VIDC_GETFIELD(crop_data, + VIDC_SM_DEC_CROP_INFO1_RIGHT_OFFSET_BMSK, + VIDC_SM_DEC_CROP_INFO1_RIGHT_OFFSET_SHFT); + crop_data = DDL_MEM_READ_32(shared_mem, + VIDC_SM_DEC_CROP_INFO2_ADDR); + *top = VIDC_GETFIELD(crop_data, + VIDC_SM_DEC_CROP_INFO2_TOP_OFFSET_BMSK, + VIDC_SM_DEC_CROP_INFO2_TOP_OFFSET_SHFT); + *bottom = VIDC_GETFIELD(crop_data, + VIDC_SM_DEC_CROP_INFO2_BOTTOM_OFFSET_BMSK, + VIDC_SM_DEC_CROP_INFO2_BOTTOM_OFFSET_SHFT); +} + +void vidc_sm_set_extended_encoder_control(struct ddl_buf_addr + *shared_mem, u32 hec_enable, + enum VIDC_SM_frame_skip frame_skip_mode, + u32 seq_hdr_in_band, u32 vbv_buffer_size) +{ + u32 enc_ctrl; + + enc_ctrl = VIDC_SETFIELD((hec_enable) ? 1 : 0, + VIDC_SM_ENC_EXT_CTRL_HEC_ENABLE_SHFT, + VIDC_SM_ENC_EXT_CTRL_HEC_ENABLE_BMSK) | + VIDC_SETFIELD((u32) frame_skip_mode, + VIDC_SM_ENC_EXT_CTRL_FRAME_SKIP_ENABLE_SHFT, + VIDC_SM_ENC_EXT_CTRL_FRAME_SKIP_ENABLE_BMSK) | + VIDC_SETFIELD((seq_hdr_in_band) ? 1 : 0 , + VIDC_SM_ENC_EXT_CTRL_SEQ_HDR_CTRL_SHFT , + VIDC_SM_ENC_EXT_CTRL_SEQ_HDR_CTRL_BMSK) | + VIDC_SETFIELD(vbv_buffer_size, + VIDC_SM_ENC_EXT_CTRL_VBV_BUFFER_SIZE_SHFT, + VIDC_SM_ENC_EXT_CTRL_VBV_BUFFER_SIZE_BMSK); + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_ENC_EXT_CTRL_ADDR, enc_ctrl); +} + +void vidc_sm_set_encoder_param_change(struct ddl_buf_addr *shared_mem, + u32 bit_rate_chg, u32 frame_rate_chg, u32 i_period_chg) +{ + u32 enc_param_chg; + + enc_param_chg = VIDC_SETFIELD((bit_rate_chg) ? 1 : 0, + VIDC_SM_ENC_PARAM_CHANGE_RC_BIT_RATE_SHFT, + VIDC_SM_ENC_PARAM_CHANGE_RC_BIT_RATE_BMSK) | + VIDC_SETFIELD((frame_rate_chg) ? 1 : 0, + VIDC_SM_ENC_PARAM_CHANGE_RC_FRAME_RATE_SHFT, + VIDC_SM_ENC_PARAM_CHANGE_RC_FRAME_RATE_BMSK) | + VIDC_SETFIELD((i_period_chg) ? 1 : 0, + VIDC_SM_ENC_PARAM_CHANGE_I_PERIOD_SHFT, + VIDC_SM_ENC_PARAM_CHANGE_I_PERIOD_BMSK); + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_ENC_PARAM_CHANGE_ADDR, + enc_param_chg); +} + +void vidc_sm_set_encoder_vop_time(struct ddl_buf_addr *shared_mem, + u32 vop_time_enable, u32 time_resolution, u32 frame_delta) +{ + u32 vop_time; + + vop_time = VIDC_SETFIELD((vop_time_enable) ? 1 : 0, + VIDC_SM_ENC_VOP_TIMING_ENABLE_SHFT , + VIDC_SM_ENC_VOP_TIMING_ENABLE_BMSK) | + VIDC_SETFIELD(time_resolution , + VIDC_SM_ENC_VOP_TIMING_TIME_RESOLUTION_SHFT, + VIDC_SM_ENC_VOP_TIMING_TIME_RESOLUTION_BMSK) | + VIDC_SETFIELD(frame_delta, + VIDC_SM_ENC_VOP_TIMING_FRAME_DELTA_SHFT, + VIDC_SM_ENC_VOP_TIMING_FRAME_DELTA_BMSK); + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_ENC_VOP_TIMING_ADDR, vop_time); +} + +void vidc_sm_set_encoder_hec_period(struct ddl_buf_addr *shared_mem, + u32 hec_period) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_ENC_HEC_PERIOD_ADDR, + hec_period); +} + +void vidc_sm_get_h264_encoder_reference_list0(struct ddl_buf_addr + *shared_mem, enum VIDC_SM_ref_picture *pe_luma_picture0, + u32 *pn_luma_picture_index0, enum VIDC_SM_ref_picture + *pe_luma_picture1, u32 *pn_luma_picture_index1, + enum VIDC_SM_ref_picture *pe_chroma_picture0, + u32 *pn_chroma_picture_index0, + enum VIDC_SM_ref_picture *pe_chroma_picture1, + u32 *pn_chroma_picture_index1) +{ + u32 ref_list; + + ref_list = DDL_MEM_READ_32(shared_mem, VIDC_SM_H264_REF_L0_ADDR); + + *pe_luma_picture0 = (enum VIDC_SM_ref_picture) + VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L0_LUMA_BTM_FLG_0_BMSK, + VIDC_SM_H264_REF_L0_LUMA_BTM_FLG_0_SHFT); + *pn_luma_picture_index0 = + VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L0_LUMA_REF_0_BMSK, + VIDC_SM_H264_REF_L0_LUMA_REF_0_SHFT); + *pe_luma_picture1 = (enum VIDC_SM_ref_picture) + VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L0_LUMA_BTM_FLG_1_BMSK, + VIDC_SM_H264_REF_L0_LUMA_BTM_FLG_1_SHFT); + *pn_luma_picture_index1 = VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L0_LUMA_REF_1_BMSK, + VIDC_SM_H264_REF_L0_LUMA_REF_1_SHFT); + *pe_chroma_picture0 = (enum VIDC_SM_ref_picture) + VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L0_CHRO_BTM_FLG_0_BMSK, + VIDC_SM_H264_REF_L0_CHRO_BTM_FLG_0_SHFT); + *pn_chroma_picture_index0 = VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L0_CHRO_REF_0_BMSK, + VIDC_SM_H264_REF_L0_CHRO_REF_0_SHFT); + *pe_chroma_picture1 = (enum VIDC_SM_ref_picture) + VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L0_CHRO_BTM_FLG_1_BMSK, + VIDC_SM_H264_REF_L0_CHRO_BTM_FLG_1_SHFT); + *pn_chroma_picture_index1 = + VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L0_CHRO_REF_1_BMSK, + VIDC_SM_H264_REF_L0_CHRO_REF_1_SHFT); +} + +void vidc_sm_get_h264_encoder_reference_list1(struct ddl_buf_addr + *shared_mem, enum VIDC_SM_ref_picture *pe_luma_picture, + u32 *pn_luma_picture_index, + enum VIDC_SM_ref_picture *pe_chroma_picture, + u32 *pn_chroma_picture_index) +{ + u32 ref_list; + + ref_list = DDL_MEM_READ_32(shared_mem, VIDC_SM_H264_REF_L1_ADDR); + + *pe_luma_picture = (enum VIDC_SM_ref_picture) + VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L1_LUMA_BTM_FLG_0_BMSK, + VIDC_SM_H264_REF_L1_LUMA_BTM_FLG_0_SHFT); + *pn_luma_picture_index = + VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L1_LUMA_REF_0_BMSK, + VIDC_SM_H264_REF_L1_LUMA_REF_0_SHFT); + *pe_chroma_picture = (enum VIDC_SM_ref_picture) + VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L1_CHRO_BTM_FLG_0_BMSK, + VIDC_SM_H264_REF_L1_CHRO_BTM_FLG_0_SHFT); + *pn_chroma_picture_index = VIDC_GETFIELD(ref_list, + VIDC_SM_H264_REF_L1_CHRO_REF_0_BMSK, + VIDC_SM_H264_REF_L1_CHRO_REF_0_SHFT); +} + +void vidc_sm_set_allocated_dpb_size(struct ddl_buf_addr *shared_mem, + u32 y_size, u32 c_size) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_ALLOCATED_LUMA_DPB_SIZE_ADDR, + y_size); + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_ALLOCATED_CHROMA_DPB_SIZE_ADDR, + c_size); +} + +void vidc_sm_set_allocated_h264_mv_size(struct ddl_buf_addr *shared_mem, + u32 mv_size) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_ALLOCATED_MV_SIZE_ADDR, + mv_size); +} + +void vidc_sm_get_min_yc_dpb_sizes(struct ddl_buf_addr *shared_mem, + u32 *pn_min_luma_dpb_size, u32 *pn_min_chroma_dpb_size) +{ + *pn_min_luma_dpb_size = DDL_MEM_READ_32(shared_mem, + VIDC_SM_MIN_LUMA_DPB_SIZE_ADDR); + *pn_min_chroma_dpb_size = DDL_MEM_READ_32(shared_mem, + VIDC_SM_MIN_CHROMA_DPB_SIZE_ADDR); +} + +void vidc_sm_set_concealment_color(struct ddl_buf_addr *shared_mem, + u32 conceal_ycolor, u32 conceal_ccolor) +{ + u32 conceal_color; + + conceal_color = (((conceal_ycolor << 8) & 0xff00) | + (conceal_ccolor & 0xff)); + DDL_MEM_WRITE_32(shared_mem, 0x00f0, conceal_color); +} + +void vidc_sm_set_metadata_enable(struct ddl_buf_addr *shared_mem, + u32 extradata_enable, u32 qp_enable, u32 concealed_mb_enable, + u32 vc1Param_enable, u32 sei_nal_enable, u32 vui_enable, + u32 enc_slice_size_enable) +{ + u32 metadata_enable; + + metadata_enable = VIDC_SETFIELD((extradata_enable) ? 1 : 0, + VIDC_SM_METADATA_ENABLE_EXTRADATA_SHFT, + VIDC_SM_METADATA_ENABLE_EXTRADATA_BMSK) | + VIDC_SETFIELD((enc_slice_size_enable) ? 1 : 0, + VIDC_SM_METADATA_ENABLE_ENC_SLICE_SIZE_SHFT, + VIDC_SM_METADATA_ENABLE_ENC_SLICE_SIZE_BMSK) | + VIDC_SETFIELD((vui_enable) ? 1 : 0, + VIDC_SM_METADATA_ENABLE_VUI_SHFT, + VIDC_SM_METADATA_ENABLE_VUI_BMSK) | + VIDC_SETFIELD((sei_nal_enable) ? 1 : 0, + VIDC_SM_METADATA_ENABLE_SEI_VIDC_SHFT, + VIDC_SM_METADATA_ENABLE_SEI_VIDC_BMSK) | + VIDC_SETFIELD((vc1Param_enable) ? 1 : 0, + VIDC_SM_METADATA_ENABLE_VC1_PARAM_SHFT, + VIDC_SM_METADATA_ENABLE_VC1_PARAM_BMSK) | + VIDC_SETFIELD((concealed_mb_enable) ? 1 : 0, + VIDC_SM_METADATA_ENABLE_CONCEALED_MB_SHFT, + VIDC_SM_METADATA_ENABLE_CONCEALED_MB_BMSK) | + VIDC_SETFIELD((qp_enable) ? 1 : 0, + VIDC_SM_METADATA_ENABLE_QP_SHFT, + VIDC_SM_METADATA_ENABLE_QP_BMSK); + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_METADATA_ENABLE_ADDR, + metadata_enable); +} + +void vidc_sm_get_metadata_status(struct ddl_buf_addr + *shared_mem, u32 *pb_metadata_present) +{ + u32 status; + + status = DDL_MEM_READ_32(shared_mem, VIDC_SM_METADATA_STATUS_ADDR); + *pb_metadata_present = (u32) VIDC_GETFIELD(status, + VIDC_SM_METADATA_STATUS_STATUS_BMSK, + VIDC_SM_METADATA_STATUS_STATUS_SHFT); +} + +void vidc_sm_get_metadata_display_index(struct ddl_buf_addr *shared_mem, + u32 *pn_dixplay_index) +{ + *pn_dixplay_index = DDL_MEM_READ_32(shared_mem, + VIDC_SM_METADATA_DISPLAY_INDEX_ADDR); +} + +void vidc_sm_set_metadata_start_address(struct ddl_buf_addr *shared_mem, + u32 address) +{ + u32 address_shift = address; + + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_EXT_METADATA_START_ADDR_ADDR, + address_shift); +} + +void vidc_sm_set_extradata_presence(struct ddl_buf_addr *shared_mem, + u32 extradata_present) +{ + u32 put_extradata; + + put_extradata = VIDC_SETFIELD((extradata_present) ? 1 : 0, + VIDC_SM_PUT_EXTRADATA_PUT_SHFT, + VIDC_SM_PUT_EXTRADATA_PUT_BMSK); + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_PUT_EXTRADATA_ADDR, + put_extradata); +} + +void vidc_sm_set_extradata_addr(struct ddl_buf_addr *shared_mem, + u32 extradata_addr) +{ + u32 address_shift = extradata_addr; + + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_EXTRADATA_ADDR_ADDR, + address_shift); +} + +void vidc_sm_set_pand_b_frame_qp(struct ddl_buf_addr *shared_mem, + u32 b_frame_qp, u32 p_frame_qp) +{ + u32 nP_B_frame_qp; + + nP_B_frame_qp = VIDC_SETFIELD(b_frame_qp, + VIDC_SM_P_B_FRAME_QP_B_FRAME_QP_SHFT, + VIDC_SM_P_B_FRAME_QP_B_FRAME_QP_BMASK); + nP_B_frame_qp |= VIDC_SETFIELD(p_frame_qp, + VIDC_SM_P_B_FRAME_QP_P_FRAME_QP_SHFT, + VIDC_SM_P_B_FRAME_QP_P_FRAME_QP_BMASK); + DDL_MEM_WRITE_32(shared_mem , VIDC_SM_P_B_FRAME_QP_ADDR, + nP_B_frame_qp); +} + + +void vidc_sm_get_profile_info(struct ddl_buf_addr *shared_mem, + struct ddl_profile_info_type *ddl_profile_info) +{ + u32 disp_pic_profile; + + disp_pic_profile = DDL_MEM_READ_32(shared_mem, + VIDC_SM_DISP_PIC_PROFILE_ADDR); + ddl_profile_info->bit_depth_chroma_minus8 = + (disp_pic_profile & 0x00380000) >> 19; + ddl_profile_info->bit_depth_luma_minus8 = + (disp_pic_profile & 0x00070000) >> 16; + ddl_profile_info->pic_profile = VIDC_GETFIELD( + disp_pic_profile, + VIDC_SM_DISP_PIC_PROFILE_DISP_PIC_PROFILE_BMASK, + VIDC_SM_DISP_PIC_PROFILE_DISP_PIC_PROFILE_SHFT); + ddl_profile_info->pic_level = VIDC_GETFIELD( + disp_pic_profile, + VIDC_SM_DISP_PIC_PROFILE_DISP_PIC_LEVEL_BMASK, + VIDC_SM_DISP_PIC_PROFILE_DISP_PIC_LEVEL_SHFT); + ddl_profile_info->chroma_format_idc = + (disp_pic_profile & 0x60) >> 5; +} + +void vidc_sm_set_encoder_new_bit_rate(struct ddl_buf_addr *shared_mem, + u32 new_bit_rate) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_NEW_RC_BIT_RATE_ADDR, + new_bit_rate); +} + +void vidc_sm_set_encoder_new_frame_rate(struct ddl_buf_addr *shared_mem, + u32 new_frame_rate) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_NEW_RC_FRAME_RATE_ADDR, + new_frame_rate); +} + +void vidc_sm_set_encoder_new_i_period(struct ddl_buf_addr *shared_mem, + u32 new_i_period) +{ + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_NEW_I_PERIOD_ADDR, + new_i_period); +} +void vidc_sm_set_encoder_init_rc_value(struct ddl_buf_addr *shared_mem, + u32 new_rc_value) +{ + DDL_MEM_WRITE_32(shared_mem, 0x011C, new_rc_value); + +} +void vidc_sm_set_idr_decode_only(struct ddl_buf_addr *shared_mem, + u32 enable) +{ + u32 idr_decode_only = VIDC_SETFIELD((enable) ? 1 : 0, + VIDC_SM_IDR_DECODING_ONLY_SHIFT, + VIDC_SM_IDR_DECODING_ONLY_BMSK + ); + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_IDR_DECODING_ONLY_ADDR, + idr_decode_only); +} + +void vidc_sm_set_chroma_addr_change(struct ddl_buf_addr *shared_mem, + u32 addr_change) +{ + u32 chroma_addr_change = VIDC_SETFIELD((addr_change) ? 1 : 0, + VIDC_SM_CHROMA_ADDR_CHANGE_SHFT, + VIDC_SM_CHROMA_ADDR_CHANGE_BMASK); + DDL_MEM_WRITE_32(shared_mem, VIDC_SM_CHROMA_ADDR_CHANGE_ADDR, + chroma_addr_change); + +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.h b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.h new file mode 100644 index 00000000000..99d9651bdc8 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_shared_mem.h @@ -0,0 +1,157 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VCD_DDL_SHARED_MEM_H_ +#define _VCD_DDL_SHARED_MEM_H_ + +#include "vcd_ddl.h" + +#define VIDC_SM_PROFILE_MPEG4_SIMPLE (0) +#define VIDC_SM_PROFILE_MPEG4_ADV_SIMPLE (1) + +#define VIDC_SM_PROFILE_H264_BASELINE (0) +#define VIDC_SM_PROFILE_H264_MAIN (1) +#define VIDC_SM_PROFILE_H264_HIGH (2) + +#define VIDC_SM_PROFILE_H263_BASELINE (0) + +#define VIDC_SM_PROFILE_VC1_SIMPLE (0) +#define VIDC_SM_PROFILE_VC1_MAIN (1) +#define VIDC_SM_PROFILE_VC1_ADVANCED (2) + +#define VIDC_SM_PROFILE_MPEG2_MAIN (4) +#define VIDC_SM_PROFILE_MPEG2_SIMPLE (5) + +#define VIDC_SM_LEVEL_MPEG2_LOW (10) +#define VIDC_SM_LEVEL_MPEG2_MAIN (8) +#define VIDC_SM_LEVEL_MPEG2_HIGH_1440 (6) +#define VIDC_SM_LEVEL_MPEG2_HIGH (4) + +#define VIDC_SM_LEVEL_VC1_LOW (0) +#define VIDC_SM_LEVEL_VC1_MEDIUM (2) +#define VIDC_SM_LEVEL_VC1_HIGH (4) + +#define VIDC_SM_LEVEL_VC1_ADV_0 (0) +#define VIDC_SM_LEVEL_VC1_ADV_1 (1) +#define VIDC_SM_LEVEL_VC1_ADV_2 (2) +#define VIDC_SM_LEVEL_VC1_ADV_3 (3) +#define VIDC_SM_LEVEL_VC1_ADV_4 (4) + +enum VIDC_SM_frame_skip { + VIDC_SM_FRAME_SKIP_DISABLE = 0, + VIDC_SM_FRAME_SKIP_ENABLE_LEVEL = 1, + VIDC_SM_FRAME_SKIP_ENABLE_VBV = 2 +}; +enum VIDC_SM_ref_picture { + VIDC_SM_REF_PICT_FRAME_OR_TOP_FIELD = 0, + VIDC_SM_REF_PICT_BOTTOM_FIELD = 1 +}; + +struct ddl_profile_info_type { + u32 bit_depth_chroma_minus8; + u32 bit_depth_luma_minus8; + u32 pic_level; + u32 chroma_format_idc; + u32 pic_profile; +}; + +void vidc_sm_get_extended_decode_status(struct ddl_buf_addr *shared_mem, + u32 *more_field_needed, + u32 *resl_change); +void vidc_sm_set_frame_tag(struct ddl_buf_addr *shared_mem, + u32 frame_tag); +void vidc_sm_get_frame_tags(struct ddl_buf_addr *shared_mem, + u32 *pn_frame_tag_top, u32 *pn_frame_tag_bottom); +void vidc_sm_get_picture_times(struct ddl_buf_addr *shared_mem, + u32 *pn_time_top, u32 *pn_time_bottom); +void vidc_sm_set_start_byte_number(struct ddl_buf_addr *shared_mem, + u32 byte_num); +void vidc_sm_get_crop_info(struct ddl_buf_addr *shared_mem, u32 *pn_left, + u32 *pn_right, u32 *pn_top, u32 *pn_bottom); +void vidc_sm_get_displayed_picture_frame(struct ddl_buf_addr + *shared_mem, u32 *n_disp_picture_frame); +void vidc_sm_get_available_luma_dpb_address( + struct ddl_buf_addr *shared_mem, u32 *pn_free_luma_dpb_address); +void vidc_sm_get_available_luma_dpb_dec_order_address( + struct ddl_buf_addr *shared_mem, u32 *pn_free_luma_dpb_address); +void vidc_sm_get_dec_order_resl( + struct ddl_buf_addr *shared_mem, u32 *width, u32 *height); +void vidc_sm_get_dec_order_crop_info( + struct ddl_buf_addr *shared_mem, u32 *left, + u32 *right, u32 *top, u32 *bottom); +void vidc_sm_set_extended_encoder_control( + struct ddl_buf_addr *shared_mem, u32 hec_enable, + enum VIDC_SM_frame_skip frame_skip_mode, u32 seq_hdr_in_band, + u32 vbv_buffer_size); +void vidc_sm_set_encoder_param_change(struct ddl_buf_addr *shared_mem, + u32 bit_rate_chg, u32 frame_rate_chg, u32 i_period_chg); +void vidc_sm_set_encoder_vop_time(struct ddl_buf_addr *shared_mem, + u32 vop_time_enable, u32 time_resolution, u32 frame_delta); +void vidc_sm_set_encoder_hec_period(struct ddl_buf_addr *shared_mem, + u32 hec_period); +void vidc_sm_get_h264_encoder_reference_list0( + struct ddl_buf_addr *shared_mem, + enum VIDC_SM_ref_picture *pe_luma_picture0, + u32 *pn_luma_picture_index0, + enum VIDC_SM_ref_picture *pe_luma_picture1, + u32 *pn_luma_picture_index1, + enum VIDC_SM_ref_picture *pe_chroma_picture0, + u32 *pn_chroma_picture_index0, + enum VIDC_SM_ref_picture *pe_chroma_picture1, + u32 *pn_chroma_picture_index1); + +void vidc_sm_get_h264_encoder_reference_list1( + struct ddl_buf_addr *shared_mem, + enum VIDC_SM_ref_picture *pe_luma_picture, + u32 *pn_luma_picture_index, + enum VIDC_SM_ref_picture *pe_chroma_picture, + u32 *pn_chroma_picture_index); +void vidc_sm_set_allocated_dpb_size(struct ddl_buf_addr *shared_mem, + u32 y_size, u32 c_size); +void vidc_sm_set_allocated_h264_mv_size(struct ddl_buf_addr *shared_mem, + u32 mv_size); +void vidc_sm_get_min_yc_dpb_sizes(struct ddl_buf_addr *shared_mem, + u32 *pn_min_luma_dpb_size, u32 *pn_min_chroma_dpb_size); +void vidc_sm_set_metadata_enable(struct ddl_buf_addr *shared_mem, + u32 extradata_enable, u32 qp_enable, u32 concealed_mb_enable, + u32 vc1Param_enable, u32 sei_nal_enable, u32 vui_enable, + u32 enc_slice_size_enable); +void vidc_sm_get_metadata_status(struct ddl_buf_addr *shared_mem, + u32 *pb_metadata_present); +void vidc_sm_get_metadata_display_index(struct ddl_buf_addr *shared_mem, + u32 *pn_dixplay_index); +void vidc_sm_set_metadata_start_address(struct ddl_buf_addr *shared_mem, + u32 address); +void vidc_sm_set_extradata_presence(struct ddl_buf_addr *shared_mem, + u32 extradata_present); +void vidc_sm_set_extradata_addr(struct ddl_buf_addr *shared_mem, + u32 extradata_addr); +void vidc_sm_set_pand_b_frame_qp(struct ddl_buf_addr *shared_mem, + u32 b_frame_qp, u32 p_frame_qp); +void vidc_sm_get_profile_info(struct ddl_buf_addr *shared_mem, + struct ddl_profile_info_type *ddl_profile_info); +void vidc_sm_set_encoder_new_bit_rate(struct ddl_buf_addr *shared_mem, + u32 new_bit_rate); +void vidc_sm_set_encoder_new_frame_rate(struct ddl_buf_addr *shared_mem, + u32 new_frame_rate); +void vidc_sm_set_encoder_new_i_period(struct ddl_buf_addr *shared_mem, + u32 new_i_period); +void vidc_sm_set_encoder_init_rc_value(struct ddl_buf_addr *shared_mem, + u32 new_rc_value); +void vidc_sm_set_idr_decode_only(struct ddl_buf_addr *shared_mem, + u32 enable); +void vidc_sm_set_concealment_color(struct ddl_buf_addr *shared_mem, + u32 conceal_ycolor, u32 conceal_ccolor); +void vidc_sm_set_chroma_addr_change(struct ddl_buf_addr *shared_mem, + u32 addr_change); +#endif diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.c b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.c new file mode 100644 index 00000000000..46337afe05d --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.c @@ -0,0 +1,310 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include "vcd_ddl_utils.h" +#include "vcd_ddl.h" + +struct time_data { + unsigned int ddl_t1; + unsigned int ddl_ttotal; + unsigned int ddl_count; +}; +static struct time_data proc_time[MAX_TIME_DATA]; +#define DDL_MSG_TIME(x...) printk(KERN_DEBUG x) + +#define DDL_FW_CHANGE_ENDIAN + +#ifdef DDL_BUF_LOG +static void ddl_print_buffer(struct ddl_context *ddl_context, + struct ddl_buf_addr *buf, u32 idx, u8 *str); +static void ddl_print_port(struct ddl_context *ddl_context, + struct ddl_buf_addr *buf); +static void ddl_print_buffer_port(struct ddl_context *ddl_context, + struct ddl_buf_addr *buf, u32 idx, u8 *str); +#endif + +void *ddl_pmem_alloc(struct ddl_buf_addr *addr, size_t sz, u32 alignment) +{ + u32 alloc_size, offset = 0; + struct ddl_context *ddl_context; + DBG_PMEM("\n%s() IN: Requested alloc size(%u)", __func__, (u32)sz); + if (!addr) { + DDL_MSG_ERROR("\n%s() Invalid Parameters", __func__); + return NULL; + } + ddl_context = ddl_get_context(); + alloc_size = (sz + alignment); + addr->physical_base_addr = (u8 *) allocate_contiguous_memory_nomap( + alloc_size, ddl_context->memtype, SZ_4K); + if (!addr->physical_base_addr) { + DDL_MSG_ERROR("%s() : pmem alloc failed (%d)\n", __func__, + alloc_size); + return NULL; + } + DDL_MSG_LOW("%s() : pmem alloc physical base addr/sz 0x%x / %d\n",\ + __func__, (u32)addr->physical_base_addr, alloc_size); + addr->virtual_base_addr = (u8 *)ioremap((unsigned long) + addr->physical_base_addr, alloc_size); + if (!addr->virtual_base_addr) { + DDL_MSG_ERROR("%s() : ioremap failed, virtual(%x)\n", __func__, + (u32)addr->virtual_base_addr); + free_contiguous_memory_by_paddr( + (unsigned long) addr->physical_base_addr); + addr->physical_base_addr = NULL; + return NULL; + } + DDL_MSG_LOW("%s() : pmem alloc virtual base addr/sz 0x%x / %d\n",\ + __func__, (u32)addr->virtual_base_addr, alloc_size); + addr->align_physical_addr = (u8 *) DDL_ALIGN((u32) + addr->physical_base_addr, alignment); + offset = (u32)(addr->align_physical_addr - + addr->physical_base_addr); + addr->align_virtual_addr = addr->virtual_base_addr + offset; + addr->buffer_size = sz; + DDL_MSG_LOW("\n%s() : alig_phy_addr(%p) alig_vir_addr(%p)", + __func__, addr->align_physical_addr, addr->align_virtual_addr); + DBG_PMEM("\n%s() OUT: phy_addr(%p) vir_addr(%p) size(%u)", + __func__, addr->physical_base_addr, addr->virtual_base_addr, + addr->buffer_size); + return addr->virtual_base_addr; +} + +void ddl_pmem_free(struct ddl_buf_addr *addr) +{ + DBG_PMEM("\n%s() IN: phy_addr(%p) vir_addr(%p) size(%u)", + __func__, addr->physical_base_addr, addr->virtual_base_addr, + addr->buffer_size); + if (addr->virtual_base_addr) + iounmap((void *)addr->virtual_base_addr); + if (addr->physical_base_addr) + free_contiguous_memory_by_paddr( + (unsigned long) addr->physical_base_addr); + DBG_PMEM("\n%s() OUT: phy_addr(%p) vir_addr(%p) size(%u)", + __func__, addr->physical_base_addr, addr->virtual_base_addr, + addr->buffer_size); + addr->physical_base_addr = NULL; + addr->virtual_base_addr = NULL; + addr->align_virtual_addr = NULL; + addr->align_physical_addr = NULL; + addr->buffer_size = 0; +} + +#ifdef DDL_BUF_LOG + +static void ddl_print_buffer(struct ddl_context *ddl_context, + struct ddl_buf_addr *buf, u32 idx, u8 *str) +{ + struct ddl_buf_addr *base_ram; + s32 offset; + size_t sz, KB = 0; + + base_ram = &ddl_context->dram_base_a; + offset = (s32) DDL_ADDR_OFFSET(*base_ram, *buf); + sz = buf->buffer_size; + if (sz > 0) { + if (!(sz % 1024)) { + sz /= 1024; + KB++; + if (!(sz % 1024)) { + sz /= 1024; + KB++; + } + } + } + DDL_MSG_LOW("\n%12s [%2d]: 0x%08x [0x%04x], 0x%08x(%d%s), %s", + str, idx, (u32) buf->align_physical_addr, + (offset > 0) ? offset : 0, buf->buffer_size, sz, + ((2 == KB) ? "MB" : (1 == KB) ? "KB" : ""), + (((u32) buf->virtual_base_addr) ? "Alloc" : "")); +} + +static void ddl_print_port(struct ddl_context *ddl_context, + struct ddl_buf_addr *buf) +{ + struct ddl_buf_addr *a = &ddl_context->dram_base_a; + struct ddl_buf_addr *b = &ddl_context->dram_base_b; + + if (!buf->align_physical_addr || !buf->buffer_size) + return; + if (buf->align_physical_addr >= a->align_physical_addr && + buf->align_physical_addr + buf->buffer_size <= + a->align_physical_addr + a->buffer_size) + DDL_MSG_LOW(" -A [0x%x]-", DDL_ADDR_OFFSET(*a, *buf)); + else if (buf->align_physical_addr >= b->align_physical_addr && + buf->align_physical_addr + buf->buffer_size <= + b->align_physical_addr + b->buffer_size) + DDL_MSG_LOW(" -B [0x%x]-", DDL_ADDR_OFFSET(*b, *buf)); + else + DDL_MSG_LOW(" -?-"); +} + +static void ddl_print_buffer_port(struct ddl_context *ddl_context, + struct ddl_buf_addr *buf, u32 idx, u8 *str) +{ + DDL_MSG_LOW("\n"); + ddl_print_buffer(ddl_context, buf, idx, str); + ddl_print_port(ddl_context, buf); +} + +void ddl_list_buffers(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context; + u32 i; + + ddl_context = ddl->ddl_context; + DDL_MSG_LOW("\n\n"); + DDL_MSG_LOW("\n Buffer : Start [offs], Size \ + (Size), Alloc/Port"); + DDL_MSG_LOW("\n-------------------------------------------------------\ + -------------------------"); + ddl_print_buffer(ddl_context, &ddl_context->dram_base_a, 0, + "dram_base_a"); + ddl_print_buffer(ddl_context, &ddl_context->dram_base_b, 0, + "dram_base_b"); + if (ddl->codec_data.hdr.decoding) { + struct ddl_dec_buffers *dec_bufs = + &ddl->codec_data.decoder.hw_bufs; + for (i = 0; i < 32; i++) + ddl_print_buffer_port(ddl_context, + &dec_bufs->h264Mv[i], i, "h264Mv"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->h264Vert_nb_mv, 0, "h264Vert_nb_mv"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->h264Nb_ip, 0, "h264Nb_ip"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->nb_dcac, 0, "nb_dcac"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->upnb_mv, 0, "upnb_mv"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->sub_anchor_mv, 0, "sub_anchor_mv"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->overlay_xform, 0, "overlay_xform"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->bit_plane3, 0, "bit_plane3"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->bit_plane2, 0, "bit_plane2"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->bit_plane1, 0, "bit_plane1"); + ddl_print_buffer_port(ddl_context, + dec_bufs->stx_parser, 0, "stx_parser"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->desc, 0, "desc"); + ddl_print_buffer_port(ddl_context, + &dec_bufs->context, 0, "context"); + } else { + struct ddl_enc_buffers *enc_bufs = + &ddl->codec_data.encoder.hw_bufs; + + for (i = 0; i < 4; i++) + ddl_print_buffer_port(ddl_context, + &enc_bufs->dpb_y[i], i, "dpb_y"); + for (i = 0; i < 4; i++) + ddl_print_buffer_port(ddl_context, + &enc_bufs->dpb_c[i], i, "dpb_c"); + ddl_print_buffer_port(ddl_context, &enc_bufs->mv, 0, "mv"); + ddl_print_buffer_port(ddl_context, + &enc_bufs->col_zero, 0, "col_zero"); + ddl_print_buffer_port(ddl_context, &enc_bufs->md, 0, "md"); + ddl_print_buffer_port(ddl_context, + &enc_bufs->pred, 0, "pred"); + ddl_print_buffer_port(ddl_context, + &enc_bufs->nbor_info, 0, "nbor_info"); + ddl_print_buffer_port(ddl_context, + &enc_bufs->acdc_coef, 0, "acdc_coef"); + ddl_print_buffer_port(ddl_context, + &enc_bufs->context, 0, "context"); + } +} +#endif + +#ifdef DDL_FW_CHANGE_ENDIAN +static void ddl_fw_change_endian(u8 *fw, u32 fw_size) +{ + u32 i = 0; + u8 temp; + for (i = 0; i < fw_size; i = i + 4) { + temp = fw[i]; + fw[i] = fw[i+3]; + fw[i+3] = temp; + temp = fw[i+1]; + fw[i+1] = fw[i+2]; + fw[i+2] = temp; + } + return; +} +#endif + +u32 ddl_fw_init(struct ddl_buf_addr *dram_base) +{ + + u8 *dest_addr; + + dest_addr = DDL_GET_ALIGNED_VITUAL(*dram_base); + if (vidc_video_codec_fw_size > dram_base->buffer_size || + !vidc_video_codec_fw) + return false; + DDL_MSG_LOW("FW Addr / FW Size : %x/%d", (u32)vidc_video_codec_fw, + vidc_video_codec_fw_size); + memcpy(dest_addr, vidc_video_codec_fw, + vidc_video_codec_fw_size); +#ifdef DDL_FW_CHANGE_ENDIAN + ddl_fw_change_endian(dest_addr, vidc_video_codec_fw_size); +#endif + return true; +} + +void ddl_fw_release(void) +{ + +} + +void ddl_set_core_start_time(const char *func_name, u32 index) +{ + u32 act_time; + struct timeval ddl_tv; + struct time_data *time_data = &proc_time[index]; + do_gettimeofday(&ddl_tv); + act_time = (ddl_tv.tv_sec * 1000) + (ddl_tv.tv_usec / 1000); + if (!time_data->ddl_t1) { + time_data->ddl_t1 = act_time; + DDL_MSG_LOW("\n%s(): Start Time (%u)", func_name, act_time); + } else { + DDL_MSG_TIME("\n%s(): Timer already started! St(%u) Act(%u)", + func_name, time_data->ddl_t1, act_time); + } +} + +void ddl_calc_core_proc_time(const char *func_name, u32 index) +{ + struct time_data *time_data = &proc_time[index]; + if (time_data->ddl_t1) { + int ddl_t2; + struct timeval ddl_tv; + do_gettimeofday(&ddl_tv); + ddl_t2 = (ddl_tv.tv_sec * 1000) + (ddl_tv.tv_usec / 1000); + time_data->ddl_ttotal += (ddl_t2 - time_data->ddl_t1); + time_data->ddl_count++; + DDL_MSG_TIME("\n%s(): cnt(%u) End Time (%u) Diff(%u) Avg(%u)", + func_name, time_data->ddl_count, ddl_t2, + ddl_t2 - time_data->ddl_t1, + time_data->ddl_ttotal/time_data->ddl_count); + time_data->ddl_t1 = 0; + } +} + +void ddl_reset_core_time_variables(u32 index) +{ + proc_time[index].ddl_t1 = 0; + proc_time[index].ddl_ttotal = 0; + proc_time[index].ddl_count = 0; +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.h b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.h new file mode 100644 index 00000000000..42a991c7193 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_utils.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VCD_DDL_UTILS_H_ +#define _VCD_DDL_UTILS_H_ + +#include +#include "vidc_type.h" + +extern u32 vidc_msg_pmem; +extern u32 vidc_msg_timing; + +enum timing_data { + DEC_OP_TIME, + DEC_IP_TIME, + ENC_OP_TIME, + MAX_TIME_DATA +}; + +#define DBG_PMEM(x...) \ +do { \ + if (vidc_msg_pmem) \ + printk(KERN_DEBUG x); \ +} while (0) + +#ifdef DDL_MSG_LOG +#define DDL_MSG_LOW(x...) printk(KERN_INFO x) +#define DDL_MSG_MED(x...) printk(KERN_INFO x) +#define DDL_MSG_HIGH(x...) printk(KERN_INFO x) +#else +#define DDL_MSG_LOW(x...) +#define DDL_MSG_MED(x...) +#define DDL_MSG_HIGH(x...) +#endif + +#define DDL_MSG_ERROR(x...) printk(KERN_INFO x) +#define DDL_MSG_FATAL(x...) printk(KERN_INFO x) + +#define DDL_ALIGN_SIZE(sz, guard_bytes, align_mask) \ + (((u32)(sz) + guard_bytes) & align_mask) +#define DDL_ADDR_IS_ALIGNED(addr, align_bytes) \ + (!((u32)(addr) & ((align_bytes) - 1))) +#define DDL_ALIGN(val, grid) ((!(grid)) ? (val) : \ + ((((val) + (grid) - 1) / (grid)) * (grid))) +#define DDL_ALIGN_FLOOR(val, grid) ((!(grid)) ? (val) : \ + (((val) / (grid)) * (grid))) +#define DDL_OFFSET(base, addr) ((!(addr)) ? 0 : (u32)((u8 *) \ + (addr) - (u8 *) (base))) +#define DDL_ADDR_OFFSET(base, addr) DDL_OFFSET((base).align_physical_addr, \ + (addr).align_physical_addr) +#define DDL_GET_ALIGNED_VITUAL(x) ((x).align_virtual_addr) +#define DDL_KILO_BYTE(x) ((x)*1024) +#define DDL_MEGA_BYTE(x) ((x)*1024*1024) +#define DDL_FRAMERATE_SCALE(x) ((x) * 1000) + +#define DDL_MIN(x, y) ((x < y) ? x : y) +#define DDL_MAX(x, y) ((x > y) ? x : y) + +void ddl_set_core_start_time(const char *func_name, u32 index); +void ddl_calc_core_proc_time(const char *func_name, u32 index); +void ddl_reset_core_time_variables(u32 index); + +#endif diff --git a/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_vidc.c b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_vidc.c new file mode 100644 index 00000000000..2efd2114d10 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vcd_ddl_vidc.c @@ -0,0 +1,925 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vcd_ddl.h" +#include "vcd_ddl_metadata.h" +#include "vcd_ddl_shared_mem.h" +#include "vcd_core.h" + +#if defined(PIX_CACHE_DISABLE) +#define DDL_PIX_CACHE_ENABLE false +#else +#define DDL_PIX_CACHE_ENABLE true +#endif + +void ddl_vidc_core_init(struct ddl_context *ddl_context) +{ + struct vidc_1080P_pix_cache_config pixel_cache_config; + + vidc_1080p_do_sw_reset(VIDC_1080P_RESET_IN_SEQ_FIRST_STAGE); + msleep(DDL_SW_RESET_SLEEP); + vidc_1080p_do_sw_reset(VIDC_1080P_RESET_IN_SEQ_SECOND_STAGE); + vidc_1080p_init_memory_controller( + (u32) ddl_context->dram_base_a.align_physical_addr, + (u32) ddl_context->dram_base_b.align_physical_addr); + vidc_1080p_clear_returned_channel_inst_id(); + ddl_context->vidc_decode_seq_start[0] = + vidc_1080p_decode_seq_start_ch0; + ddl_context->vidc_decode_seq_start[1] = + vidc_1080p_decode_seq_start_ch1; + ddl_context->vidc_decode_init_buffers[0] = + vidc_1080p_decode_init_buffers_ch0; + ddl_context->vidc_decode_init_buffers[1] = + vidc_1080p_decode_init_buffers_ch1; + ddl_context->vidc_decode_frame_start[0] = + vidc_1080p_decode_frame_start_ch0; + ddl_context->vidc_decode_frame_start[1] = + vidc_1080p_decode_frame_start_ch1; + ddl_context->vidc_set_dec_resolution[0] = + vidc_1080p_set_dec_resolution_ch0; + ddl_context->vidc_set_dec_resolution[1] = + vidc_1080p_set_dec_resolution_ch1; + ddl_context->vidc_encode_seq_start[0] = + vidc_1080p_encode_seq_start_ch0; + ddl_context->vidc_encode_seq_start[1] = + vidc_1080p_encode_seq_start_ch1; + ddl_context->vidc_encode_frame_start[0] = + vidc_1080p_encode_frame_start_ch0; + ddl_context->vidc_encode_frame_start[1] = + vidc_1080p_encode_frame_start_ch1; + vidc_1080p_release_sw_reset(); + ddl_context->pix_cache_enable = DDL_PIX_CACHE_ENABLE; + if (ddl_context->pix_cache_enable) { + vidc_pix_cache_sw_reset(); + pixel_cache_config.cache_enable = true; + pixel_cache_config.prefetch_en = true; + pixel_cache_config.port_select = VIDC_1080P_PIX_CACHE_PORT_B; + pixel_cache_config.statistics_off = true; + pixel_cache_config.page_size = + VIDC_1080P_PIX_CACHE_PAGE_SIZE_1K; + vidc_pix_cache_init_config(&pixel_cache_config); + } +} + +void ddl_vidc_core_term(struct ddl_context *ddl_context) +{ + if (ddl_context->pix_cache_enable) { + u32 pix_cache_idle = false; + u32 counter = 0; + + vidc_pix_cache_set_halt(true); + + do { + msleep(DDL_SW_RESET_SLEEP); + vidc_pix_cache_get_status_idle(&pix_cache_idle); + counter++; + } while (!pix_cache_idle && + counter < DDL_PIXEL_CACHE_STATUS_READ_RETRY); + + if (!pix_cache_idle) { + ddl_context->cmd_err_status = + DDL_PIXEL_CACHE_NOT_IDLE; + ddl_handle_core_errors(ddl_context); + } + } +} + +void ddl_vidc_channel_set(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + enum vcd_codec *vcd_codec; + enum vidc_1080p_codec codec = VIDC_1080P_H264_DECODE; + const enum vidc_1080p_decode_p_cache_enable + dec_pix_cache = VIDC_1080P_DECODE_PCACHE_DISABLE; + const enum vidc_1080p_encode_p_cache_enable + enc_pix_cache = VIDC_1080P_ENCODE_PCACHE_ENABLE; + u32 pix_cache_ctrl, ctxt_mem_offset, ctxt_mem_size; + + if (ddl->decoding) { + if (vidc_msg_timing) + ddl_set_core_start_time(__func__, DEC_OP_TIME); + vcd_codec = &(ddl->codec_data.decoder.codec.codec); + pix_cache_ctrl = (u32)dec_pix_cache; + ctxt_mem_offset = DDL_ADDR_OFFSET(ddl_context->dram_base_a, + ddl->codec_data.decoder.hw_bufs.context) >> 11; + ctxt_mem_size = + ddl->codec_data.decoder.hw_bufs.context.buffer_size; + } else { + vcd_codec = &(ddl->codec_data.encoder.codec.codec); + pix_cache_ctrl = (u32)enc_pix_cache; + ctxt_mem_offset = DDL_ADDR_OFFSET(ddl_context->dram_base_a, + ddl->codec_data.encoder.hw_bufs.context) >> 11; + ctxt_mem_size = + ddl->codec_data.encoder.hw_bufs.context.buffer_size; + } + switch (*vcd_codec) { + default: + case VCD_CODEC_MPEG4: + if (ddl->decoding) + codec = VIDC_1080P_MPEG4_DECODE; + else + codec = VIDC_1080P_MPEG4_ENCODE; + break; + case VCD_CODEC_H264: + if (ddl->decoding) + codec = VIDC_1080P_H264_DECODE; + else + codec = VIDC_1080P_H264_ENCODE; + break; + case VCD_CODEC_DIVX_3: + if (ddl->decoding) + codec = VIDC_1080P_DIVX311_DECODE; + break; + case VCD_CODEC_DIVX_4: + if (ddl->decoding) + codec = VIDC_1080P_DIVX412_DECODE; + break; + case VCD_CODEC_DIVX_5: + if (ddl->decoding) + codec = VIDC_1080P_DIVX502_DECODE; + break; + case VCD_CODEC_DIVX_6: + if (ddl->decoding) + codec = VIDC_1080P_DIVX503_DECODE; + break; + case VCD_CODEC_XVID: + if (ddl->decoding) + codec = VIDC_1080P_MPEG4_DECODE; + break; + case VCD_CODEC_H263: + if (ddl->decoding) + codec = VIDC_1080P_H263_DECODE; + else + codec = VIDC_1080P_H263_ENCODE; + break; + case VCD_CODEC_MPEG1: + case VCD_CODEC_MPEG2: + if (ddl->decoding) + codec = VIDC_1080P_MPEG2_DECODE; + break; + case VCD_CODEC_VC1: + if (ddl->decoding) + codec = VIDC_1080P_VC1_DECODE; + break; + case VCD_CODEC_VC1_RCV: + if (ddl->decoding) + codec = VIDC_1080P_VC1_RCV_DECODE; + break; + } + ddl->cmd_state = DDL_CMD_CHANNEL_SET; + DDL_MSG_LOW("ddl_state_transition: %s ~~> DDL_CLIENT_WAIT_FOR_CHDONE", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_CHDONE; + vidc_1080p_set_host2risc_cmd(VIDC_1080P_HOST2RISC_CMD_OPEN_CH, + (u32)codec, pix_cache_ctrl, ctxt_mem_offset, + ctxt_mem_size); +} + +void ddl_vidc_decode_init_codec(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vidc_1080p_dec_seq_start_param seq_start_param; + u32 seq_size; + + if (vidc_msg_timing) + ddl_set_core_start_time(__func__, DEC_OP_TIME); + vidc_1080p_set_decode_mpeg4_pp_filter(decoder->post_filter.post_filter); + vidc_sm_set_concealment_color(&ddl->shared_mem[ddl->command_channel], + DDL_CONCEALMENT_Y_COLOR, DDL_CONCEALMENT_C_COLOR); + ddl_vidc_metadata_enable(ddl); + vidc_sm_set_metadata_start_address(&ddl->shared_mem + [ddl->command_channel], + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + ddl->codec_data.decoder.meta_data_input)); + + vidc_sm_set_idr_decode_only(&ddl->shared_mem[ddl->command_channel], + decoder->idr_only_decoding); + + if ((decoder->codec.codec == VCD_CODEC_DIVX_3) || + (decoder->codec.codec == VCD_CODEC_VC1_RCV || + decoder->codec.codec == VCD_CODEC_VC1)) + ddl_context->vidc_set_dec_resolution + [ddl->command_channel](decoder->client_frame_size.width, + decoder->client_frame_size.height); + else + ddl_context->vidc_set_dec_resolution + [ddl->command_channel](0x0, 0x0); + DDL_MSG_LOW("HEADER-PARSE-START"); + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_INITCODECDONE", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_INITCODECDONE; + ddl->cmd_state = DDL_CMD_HEADER_PARSE; + seq_start_param.cmd_seq_num = ++ddl_context->cmd_seq_num; + seq_start_param.inst_id = ddl->instance_id; + seq_start_param.shared_mem_addr_offset = DDL_ADDR_OFFSET( + ddl_context->dram_base_a, ddl->shared_mem + [ddl->command_channel]); + seq_start_param.stream_buffer_addr_offset = + DDL_OFFSET(ddl_context->dram_base_a.align_physical_addr, + decoder->decode_config.sequence_header); + seq_start_param.stream_buffersize = + decoder->client_input_buf_req.sz; + seq_size = decoder->decode_config.sequence_header_len + + DDL_LINEAR_BUFFER_ALIGN_BYTES + VCD_SEQ_HDR_PADDING_BYTES; + if (seq_start_param.stream_buffersize < seq_size) + seq_start_param.stream_buffersize = seq_size; + seq_start_param.stream_frame_size = + decoder->decode_config.sequence_header_len; + seq_start_param.descriptor_buffer_addr_offset = + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + decoder->hw_bufs.desc), + seq_start_param.descriptor_buffer_size = + decoder->hw_bufs.desc.buffer_size; + ddl_context->vidc_decode_seq_start[ddl->command_channel]( + &seq_start_param); +} + +void ddl_vidc_decode_dynamic_property(struct ddl_client_context *ddl, + u32 enable) +{ + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vcd_frame_data *bit_stream = + &(ddl->input_frame.vcd_frm); + struct ddl_context *ddl_context = ddl->ddl_context; + + if (!enable) { + if (decoder->dynmic_prop_change_req) + decoder->dynmic_prop_change_req = false; + return; + } + if ((decoder->dynamic_prop_change & DDL_DEC_REQ_OUTPUT_FLUSH)) { + decoder->dynmic_prop_change_req = true; + decoder->dynamic_prop_change &= ~(DDL_DEC_REQ_OUTPUT_FLUSH); + decoder->dpb_mask.hw_mask = 0; + decoder->flush_pending = true; + } + if (((decoder->meta_data_enable_flag & VCD_METADATA_PASSTHROUGH)) && + ((VCD_FRAME_FLAG_EXTRADATA & bit_stream->flags))) { + u32 extradata_presence = true; + u8* tmp = ((u8 *) bit_stream->physical + + bit_stream->offset + + bit_stream->data_len + 3); + u32 extra_data_start = (u32) ((u32)tmp & ~3); + + extra_data_start = extra_data_start - + (u32)ddl_context->dram_base_a.align_physical_addr; + decoder->dynmic_prop_change_req = true; + vidc_sm_set_extradata_addr(&ddl->shared_mem + [ddl->command_channel], extra_data_start); + vidc_sm_set_extradata_presence(&ddl->shared_mem + [ddl->command_channel], extradata_presence); + } +} + +void ddl_vidc_encode_dynamic_property(struct ddl_client_context *ddl, + u32 enable) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + u32 frame_rate_change = false, bit_rate_change = false; + u32 i_period_change = false, reset_req = false; + + if (!enable) { + if (encoder->dynmic_prop_change_req) { + reset_req = true; + encoder->dynmic_prop_change_req = false; + } + } else { + if ((encoder->dynamic_prop_change & DDL_ENC_REQ_IFRAME)) { + encoder->intra_frame_insertion = true; + encoder->dynamic_prop_change &= + ~(DDL_ENC_REQ_IFRAME); + } + if ((encoder->dynamic_prop_change & + DDL_ENC_CHANGE_BITRATE)) { + bit_rate_change = true; + vidc_sm_set_encoder_new_bit_rate( + &ddl->shared_mem[ddl->command_channel], + encoder->target_bit_rate.target_bitrate); + encoder->dynamic_prop_change &= + ~(DDL_ENC_CHANGE_BITRATE); + } + if ((encoder->dynamic_prop_change + & DDL_ENC_CHANGE_IPERIOD)) { + i_period_change = true; + vidc_sm_set_encoder_new_i_period( + &ddl->shared_mem[ddl->command_channel], + encoder->i_period.p_frames); + encoder->dynamic_prop_change &= + ~(DDL_ENC_CHANGE_IPERIOD); + } + if ((encoder->dynamic_prop_change + & DDL_ENC_CHANGE_FRAMERATE)) { + frame_rate_change = true; + vidc_sm_set_encoder_new_frame_rate( + &ddl->shared_mem[ddl->command_channel], + (u32)(DDL_FRAMERATE_SCALE(encoder->\ + frame_rate.fps_numerator) / + encoder->frame_rate.fps_denominator)); + encoder->dynamic_prop_change &= + ~(DDL_ENC_CHANGE_FRAMERATE); + } + } + if ((enable) || (reset_req)) { + vidc_sm_set_encoder_param_change( + &ddl->shared_mem[ddl->command_channel], + bit_rate_change, frame_rate_change, + i_period_change); + } +} + +static void ddl_vidc_encode_set_profile_level( + struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + u32 encode_profile, level = 0; + + switch (encoder->profile.profile) { + default: + case VCD_PROFILE_MPEG4_SP: + encode_profile = VIDC_1080P_PROFILE_MPEG4_SIMPLE; + break; + case VCD_PROFILE_MPEG4_ASP: + encode_profile = VIDC_1080P_PROFILE_MPEG4_ADV_SIMPLE; + break; + case VCD_PROFILE_H264_BASELINE: + encode_profile = VIDC_1080P_PROFILE_H264_BASELINE; + break; + case VCD_PROFILE_H264_MAIN: + encode_profile = VIDC_1080P_PROFILE_H264_MAIN; + break; + case VCD_PROFILE_H264_HIGH: + encode_profile = VIDC_1080P_PROFILE_H264_HIGH; + break; + } + switch (encoder->level.level) { + default: + case VCD_LEVEL_MPEG4_0: + level = VIDC_1080P_MPEG4_LEVEL0; + break; + case VCD_LEVEL_MPEG4_0b: + level = VIDC_1080P_MPEG4_LEVEL0b; + break; + case VCD_LEVEL_MPEG4_1: + level = VIDC_1080P_MPEG4_LEVEL1; + break; + case VCD_LEVEL_MPEG4_2: + level = VIDC_1080P_MPEG4_LEVEL2; + break; + case VCD_LEVEL_MPEG4_3: + level = VIDC_1080P_MPEG4_LEVEL3; + break; + case VCD_LEVEL_MPEG4_3b: + level = VIDC_1080P_MPEG4_LEVEL3b; + break; + case VCD_LEVEL_MPEG4_4: + level = VIDC_1080P_MPEG4_LEVEL4; + break; + case VCD_LEVEL_MPEG4_4a: + level = VIDC_1080P_MPEG4_LEVEL4a; + break; + case VCD_LEVEL_MPEG4_5: + level = VIDC_1080P_MPEG4_LEVEL5; + break; + case VCD_LEVEL_MPEG4_6: + level = VIDC_1080P_MPEG4_LEVEL6; + break; + case VCD_LEVEL_MPEG4_7: + level = VIDC_1080P_MPEG4_LEVEL7; + break; + case VCD_LEVEL_H264_1: + level = VIDC_1080P_H264_LEVEL1; + break; + case VCD_LEVEL_H264_1b: + level = VIDC_1080P_H264_LEVEL1b; + break; + case VCD_LEVEL_H264_1p1: + level = VIDC_1080P_H264_LEVEL1p1; + break; + case VCD_LEVEL_H264_1p2: + level = VIDC_1080P_H264_LEVEL1p2; + break; + case VCD_LEVEL_H264_1p3: + level = VIDC_1080P_H264_LEVEL1p3; + break; + case VCD_LEVEL_H264_2: + level = VIDC_1080P_H264_LEVEL2; + break; + case VCD_LEVEL_H264_2p1: + level = VIDC_1080P_H264_LEVEL2p1; + break; + case VCD_LEVEL_H264_2p2: + level = VIDC_1080P_H264_LEVEL2p2; + break; + case VCD_LEVEL_H264_3: + level = VIDC_1080P_H264_LEVEL3; + break; + case VCD_LEVEL_H264_3p1: + level = VIDC_1080P_H264_LEVEL3p1; + break; + case VCD_LEVEL_H264_3p2: + level = VIDC_1080P_H264_LEVEL3p2; + break; + case VCD_LEVEL_H264_4: + level = VIDC_1080P_H264_LEVEL4; + break; + case VCD_LEVEL_H263_10: + level = VIDC_1080P_H263_LEVEL10; + break; + case VCD_LEVEL_H263_20: + level = VIDC_1080P_H263_LEVEL20; + break; + case VCD_LEVEL_H263_30: + level = VIDC_1080P_H263_LEVEL30; + break; + case VCD_LEVEL_H263_40: + level = VIDC_1080P_H263_LEVEL40; + break; + case VCD_LEVEL_H263_45: + level = VIDC_1080P_H263_LEVEL45; + break; + case VCD_LEVEL_H263_50: + level = VIDC_1080P_H263_LEVEL50; + break; + case VCD_LEVEL_H263_60: + level = VIDC_1080P_H263_LEVEL60; + break; + case VCD_LEVEL_H263_70: + level = VIDC_1080P_H263_LEVEL70; + break; + } + vidc_1080p_set_encode_profile_level(encode_profile, level); +} + +static void ddl_vidc_encode_set_multi_slice_info( + struct ddl_encoder_data *encoder) +{ + enum vidc_1080p_MSlice_selection m_slice_sel; + u32 i_multi_slice_size = 0, i_multi_slice_byte = 0; + + if (!encoder) { + DDL_MSG_ERROR("Invalid Parameter"); + return; + } + + switch (encoder->multi_slice.m_slice_sel) { + default: + case VCD_MSLICE_OFF: + m_slice_sel = VIDC_1080P_MSLICE_DISABLE; + break; + case VCD_MSLICE_BY_MB_COUNT: + m_slice_sel = VIDC_1080P_MSLICE_BY_MB_COUNT; + i_multi_slice_size = encoder->multi_slice.m_slice_size; + break; + case VCD_MSLICE_BY_BYTE_COUNT: + m_slice_sel = VIDC_1080P_MSLICE_BY_BYTE_COUNT; + i_multi_slice_byte = encoder->multi_slice.m_slice_size; + break; + } + vidc_1080p_set_encode_multi_slice_control(m_slice_sel, + i_multi_slice_size, i_multi_slice_byte); +} + +void ddl_vidc_encode_init_codec(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + struct ddl_enc_buffers *enc_buffers = &encoder->hw_bufs; + struct vidc_1080p_enc_seq_start_param seq_start_param; + enum vidc_1080p_memory_access_method mem_access_method; + enum vidc_1080p_DBConfig db_config; + enum VIDC_SM_frame_skip r_cframe_skip = + VIDC_SM_FRAME_SKIP_DISABLE; + u32 index, luma[4], chroma[4], hdr_ext_control = false; + const u32 recon_bufs = 4; + + ddl_vidc_encode_set_profile_level(ddl); + vidc_1080p_set_encode_frame_size(encoder->frame_size.width, + encoder->frame_size.height); + vidc_1080p_encode_set_qp_params(encoder->qp_range.max_qp, + encoder->qp_range.min_qp); + vidc_1080p_encode_set_rc_config(encoder->rc_level.frame_level_rc, + encoder->rc_level.mb_level_rc, + encoder->session_qp.i_frame_qp); + if (encoder->hdr_ext_control > 0) + hdr_ext_control = true; + if (encoder->r_cframe_skip > 0) + r_cframe_skip = VIDC_SM_FRAME_SKIP_ENABLE_LEVEL; + vidc_sm_set_extended_encoder_control(&ddl->shared_mem + [ddl->command_channel], hdr_ext_control, + r_cframe_skip, false, 0); + vidc_sm_set_encoder_init_rc_value(&ddl->shared_mem + [ddl->command_channel], + encoder->target_bit_rate.target_bitrate); + vidc_sm_set_encoder_hec_period(&ddl->shared_mem + [ddl->command_channel], encoder->hdr_ext_control); + vidc_sm_set_encoder_vop_time(&ddl->shared_mem + [ddl->command_channel], true, + encoder->vop_timing.vop_time_resolution, 0); + if (encoder->rc_level.frame_level_rc) + vidc_1080p_encode_set_frame_level_rc_params(( + DDL_FRAMERATE_SCALE(encoder->\ + frame_rate.fps_numerator) / + encoder->frame_rate.fps_denominator), + encoder->target_bit_rate.target_bitrate, + encoder->frame_level_rc.reaction_coeff); + if (encoder->rc_level.mb_level_rc) + vidc_1080p_encode_set_mb_level_rc_params( + encoder->adaptive_rc.disable_dark_region_as_flag, + encoder->adaptive_rc.disable_smooth_region_as_flag, + encoder->adaptive_rc.disable_static_region_as_flag, + encoder->adaptive_rc.disable_activity_region_flag); + if ((!encoder->rc_level.frame_level_rc) && + (!encoder->rc_level.mb_level_rc)) + vidc_sm_set_pand_b_frame_qp( + &ddl->shared_mem[ddl->command_channel], + encoder->session_qp.b_frame_qp, + encoder->session_qp.p_frame_qp); + if (encoder->codec.codec == VCD_CODEC_MPEG4) { + vidc_1080p_set_mpeg4_encode_quarter_pel_control(false); + vidc_1080p_set_encode_field_picture_structure(false); + } + if (encoder->codec.codec == VCD_CODEC_H264) { + enum vidc_1080p_entropy_sel entropy_sel; + switch (encoder->entropy_control.entropy_sel) { + default: + case VCD_ENTROPY_SEL_CAVLC: + entropy_sel = VIDC_1080P_ENTROPY_SEL_CAVLC; + break; + case VCD_ENTROPY_SEL_CABAC: + entropy_sel = VIDC_1080P_ENTROPY_SEL_CABAC; + break; + } + vidc_1080p_set_h264_encode_entropy(entropy_sel); + switch (encoder->db_control.db_config) { + default: + case VCD_DB_ALL_BLOCKING_BOUNDARY: + db_config = VIDC_1080P_DB_ALL_BLOCKING_BOUNDARY; + break; + case VCD_DB_DISABLE: + db_config = VIDC_1080P_DB_DISABLE; + break; + case VCD_DB_SKIP_SLICE_BOUNDARY: + db_config = VIDC_1080P_DB_SKIP_SLICE_BOUNDARY; + break; + } + vidc_1080p_set_h264_encode_loop_filter(db_config, + encoder->db_control.slice_alpha_offset, + encoder->db_control.slice_beta_offset); + vidc_1080p_set_h264_encoder_p_frame_ref_count(encoder->\ + num_references_for_p_frame); + if (encoder->profile.profile == VCD_PROFILE_H264_HIGH) + vidc_1080p_set_h264_encode_8x8transform_control(true); + } + vidc_1080p_set_encode_picture(encoder->i_period.p_frames, + encoder->i_period.b_frames); + vidc_1080p_set_encode_circular_intra_refresh( + encoder->intra_refresh.cir_mb_number); + ddl_vidc_encode_set_multi_slice_info(encoder); + ddl_vidc_metadata_enable(ddl); + if (encoder->meta_data_enable_flag) + vidc_sm_set_metadata_start_address(&ddl->shared_mem + [ddl->command_channel], DDL_ADDR_OFFSET( + ddl_context->dram_base_a, + ddl->codec_data.encoder.meta_data_input)); + luma[0] = DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->dpb_y[0]); + luma[1] = DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->dpb_y[1]); + if (encoder->hw_bufs.dpb_count == DDL_ENC_MAX_DPB_BUFFERS) { + luma[2] = DDL_ADDR_OFFSET(ddl_context->dram_base_b, + enc_buffers->dpb_y[2]); + luma[3] = DDL_ADDR_OFFSET(ddl_context->dram_base_b, + enc_buffers->dpb_y[3]); + } + for (index = 0; index < recon_bufs; index++) + chroma[index] = DDL_ADDR_OFFSET(ddl_context->dram_base_b, + enc_buffers->dpb_c[index]); + vidc_1080p_set_encode_recon_buffers(recon_bufs, luma, chroma); + switch (encoder->codec.codec) { + case VCD_CODEC_MPEG4: + vidc_1080p_set_mpeg4_encode_work_buffers( + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->col_zero), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->acdc_coef), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->mv)); + break; + case VCD_CODEC_H263: + vidc_1080p_set_h263_encode_work_buffers( + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->mv), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->acdc_coef)); + break; + case VCD_CODEC_H264: + vidc_1080p_set_h264_encode_work_buffers( + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->mv), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->col_zero), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->md), + DDL_ADDR_OFFSET(ddl_context->dram_base_b, + enc_buffers->pred), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->nbor_info), + DDL_ADDR_OFFSET(ddl_context->dram_base_a, + enc_buffers->mb_info)); + break; + default: + break; + } + if (encoder->buf_format.buffer_format == + VCD_BUFFER_FORMAT_NV12_16M2KA) + mem_access_method = VIDC_1080P_TILE_LINEAR; + else + mem_access_method = VIDC_1080P_TILE_64x32; + vidc_1080p_set_encode_input_frame_format(mem_access_method); + vidc_1080p_set_encode_padding_control(0, 0, 0, 0); + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_INITCODECDONE", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_INITCODECDONE; + ddl->cmd_state = DDL_CMD_INIT_CODEC; + vidc_1080p_set_encode_field_picture_structure(false); + seq_start_param.cmd_seq_num = ++ddl_context->cmd_seq_num; + seq_start_param.inst_id = ddl->instance_id; + seq_start_param.shared_mem_addr_offset = DDL_ADDR_OFFSET( + ddl_context->dram_base_a, ddl->shared_mem + [ddl->command_channel]); + seq_start_param.stream_buffer_addr_offset = DDL_ADDR_OFFSET( + ddl_context->dram_base_a, encoder->seq_header); + seq_start_param.stream_buffer_size = + encoder->seq_header.buffer_size; + encoder->seq_header_length = 0; + ddl_context->vidc_encode_seq_start[ddl->command_channel]( + &seq_start_param); +} + +void ddl_vidc_channel_end(struct ddl_client_context *ddl) +{ + DDL_MSG_LOW("ddl_state_transition: %s ~~> DDL_CLIENT_WAIT_FOR_CHEND", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_CHEND; + ddl->cmd_state = DDL_CMD_CHANNEL_END; + vidc_1080p_set_host2risc_cmd(VIDC_1080P_HOST2RISC_CMD_CLOSE_CH, + ddl->instance_id, 0, 0, 0); +} + +void ddl_vidc_encode_frame_run(struct ddl_client_context *ddl) +{ + struct vidc_1080p_enc_frame_start_param enc_param; + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + struct ddl_enc_buffers *enc_buffers = &(encoder->hw_bufs); + struct vcd_frame_data *stream = &(ddl->output_frame.vcd_frm); + struct vcd_frame_data *input_vcd_frm = + &(ddl->input_frame.vcd_frm); + u32 dpb_addr_y[4], dpb_addr_c[4]; + u32 index, y_addr, c_addr; + + ddl_vidc_encode_set_metadata_output_buf(ddl); + + encoder->enc_frame_info.meta_data_exists = false; + + y_addr = DDL_OFFSET(ddl_context->dram_base_b.align_physical_addr, + input_vcd_frm->physical); + c_addr = (y_addr + encoder->input_buf_size.size_y); + if (input_vcd_frm->flags & VCD_FRAME_FLAG_EOS) { + enc_param.encode = VIDC_1080P_ENC_TYPE_LAST_FRAME_DATA; + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_EOS_DONE", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_EOS_DONE; + } else { + enc_param.encode = VIDC_1080P_ENC_TYPE_FRAME_DATA; + DDL_MSG_LOW("ddl_state_transition: %s ~~>" + "DDL_CLIENT_WAIT_FOR_FRAME_DONE", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME_DONE; + } + ddl->cmd_state = DDL_CMD_ENCODE_FRAME; + if (encoder->dynamic_prop_change) { + encoder->dynmic_prop_change_req = true; + ddl_vidc_encode_dynamic_property(ddl, true); + } + + vidc_1080p_set_encode_circular_intra_refresh( + encoder->intra_refresh.cir_mb_number); + ddl_vidc_encode_set_multi_slice_info(encoder); + enc_param.cmd_seq_num = ++ddl_context->cmd_seq_num; + enc_param.inst_id = ddl->instance_id; + enc_param.shared_mem_addr_offset = DDL_ADDR_OFFSET( + ddl_context->dram_base_a, + ddl->shared_mem[ddl->command_channel]); + enc_param.current_y_addr_offset = y_addr; + enc_param.current_c_addr_offset = c_addr; + enc_param.stream_buffer_addr_offset = DDL_OFFSET( + ddl_context->dram_base_a.align_physical_addr, stream->physical); + enc_param.stream_buffer_size = + encoder->client_output_buf_req.sz; + + enc_param.intra_frame = encoder->intra_frame_insertion; + if (encoder->intra_frame_insertion) + encoder->intra_frame_insertion = false; + enc_param.input_flush = false; + vidc_sm_set_encoder_vop_time( + &ddl->shared_mem[ddl->command_channel], true, + encoder->vop_timing.vop_time_resolution, + ddl->input_frame.frm_delta); + vidc_sm_set_frame_tag(&ddl->shared_mem[ddl->command_channel], + ddl->input_frame.vcd_frm.ip_frm_tag); + if (ddl_context->pix_cache_enable) { + for (index = 0; index < enc_buffers->dpb_count; + index++) { + dpb_addr_y[index] = + (u32) VIDC_1080P_DEC_DPB_RESET_VALUE; + dpb_addr_c[index] = (u32) enc_buffers->dpb_c + [index].align_physical_addr; + } + + dpb_addr_y[index] = (u32) input_vcd_frm->physical; + dpb_addr_c[index] = (u32) input_vcd_frm->physical + + encoder->input_buf_size.size_y; + + vidc_pix_cache_init_luma_chroma_base_addr( + enc_buffers->dpb_count + 1, dpb_addr_y, dpb_addr_c); + vidc_pix_cache_set_frame_size(encoder->frame_size.width, + encoder->frame_size.height); + vidc_pix_cache_set_frame_range(enc_buffers->sz_dpb_y, + enc_buffers->sz_dpb_c); + vidc_pix_cache_clear_cache_tags(); + } + ddl_context->vidc_encode_frame_start[ddl->command_channel] ( + &enc_param); +} + +u32 ddl_vidc_decode_set_buffers(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + u32 vcd_status = VCD_S_SUCCESS; + struct vidc_1080p_dec_init_buffers_param init_buf_param; + + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB)) { + DDL_MSG_ERROR("STATE-CRITICAL"); + return VCD_ERR_FAIL; + } + ddl_vidc_decode_set_metadata_output(decoder); + if (decoder->dp_buf.no_of_dec_pic_buf < + decoder->client_output_buf_req.actual_count) + return VCD_ERR_BAD_STATE; + if (decoder->codec.codec == VCD_CODEC_H264) { + vidc_sm_set_allocated_h264_mv_size( + &ddl->shared_mem[ddl->command_channel], + decoder->hw_bufs.h264_mv[0].buffer_size); + } + if (vcd_status) + return vcd_status; +#ifdef DDL_BUF_LOG + ddl_list_buffers(ddl); +#endif + if (vidc_msg_timing) + ddl_set_core_start_time(__func__, DEC_OP_TIME); + ddl_decoder_dpb_transact(decoder, NULL, DDL_DPB_OP_INIT); + ddl_decoder_dpb_init(ddl); + DDL_MSG_LOW("ddl_state_transition: %s ~~> DDL_CLIENT_WAIT_FOR_DPBDONE", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_DPBDONE; + ddl->cmd_state = DDL_CMD_DECODE_SET_DPB; + vidc_sm_set_allocated_dpb_size( + &ddl->shared_mem[ddl->command_channel], + decoder->dpb_buf_size.size_y, + decoder->dpb_buf_size.size_c); + init_buf_param.cmd_seq_num = ++ddl_context->cmd_seq_num; + init_buf_param.inst_id = ddl->instance_id; + init_buf_param.shared_mem_addr_offset = DDL_ADDR_OFFSET( + ddl_context->dram_base_a, ddl->shared_mem + [ddl->command_channel]); + init_buf_param.dpb_count = decoder->dp_buf.no_of_dec_pic_buf; + ddl_context->vidc_decode_init_buffers[ddl->command_channel] ( + &init_buf_param); + return VCD_S_SUCCESS; +} + +void ddl_vidc_decode_frame_run(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vcd_frame_data *bit_stream = + &(ddl->input_frame.vcd_frm); + struct ddl_dec_buffers *dec_buffers = &decoder->hw_bufs; + struct ddl_mask *dpb_mask = &ddl->codec_data.decoder.dpb_mask; + struct vidc_1080p_dec_frame_start_param dec_param; + u32 dpb_addr_y[32], index; + if (vidc_msg_timing) { + ddl_set_core_start_time(__func__, DEC_OP_TIME); + ddl_set_core_start_time(__func__, DEC_IP_TIME); + } + if ((!bit_stream->data_len) || (!bit_stream->physical)) { + ddl_vidc_decode_eos_run(ddl); + return; + } + DDL_MSG_LOW("ddl_state_transition: %s ~~" + "DDL_CLIENT_WAIT_FOR_FRAME_DONE", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_FRAME_DONE; + ddl_vidc_decode_dynamic_property(ddl, true); + ddl_decoder_dpb_transact(decoder, NULL, DDL_DPB_OP_SET_MASK); + ddl->cmd_state = DDL_CMD_DECODE_FRAME; + dec_param.cmd_seq_num = ++ddl_context->cmd_seq_num; + dec_param.inst_id = ddl->instance_id; + dec_param.shared_mem_addr_offset = DDL_ADDR_OFFSET( + ddl_context->dram_base_a, ddl->shared_mem + [ddl->command_channel]); + dec_param.stream_buffer_addr_offset = DDL_OFFSET( + ddl_context->dram_base_a.align_physical_addr, + bit_stream->physical); + dec_param.stream_frame_size = bit_stream->data_len; + dec_param.stream_buffersize = decoder->client_input_buf_req.sz; + dec_param.descriptor_buffer_addr_offset = DDL_ADDR_OFFSET( + ddl_context->dram_base_a, dec_buffers->desc); + dec_param.descriptor_buffer_size = dec_buffers->desc.buffer_size; + dec_param.release_dpb_bit_mask = dpb_mask->hw_mask; + dec_param.decode = VIDC_1080P_DEC_TYPE_FRAME_DATA; + dec_param.dpb_count = decoder->dp_buf.no_of_dec_pic_buf; + if (decoder->flush_pending) { + dec_param.dpb_flush = true; + decoder->flush_pending = false; + } else + dec_param.dpb_flush = false; + vidc_sm_set_frame_tag(&ddl->shared_mem[ddl->command_channel], + bit_stream->ip_frm_tag); + if (ddl_context->pix_cache_enable) { + for (index = 0; index < + decoder->dp_buf.no_of_dec_pic_buf; index++) { + dpb_addr_y[index] = (u32) + decoder->dp_buf.dec_pic_buffers + [index].vcd_frm.physical; + } + vidc_pix_cache_init_luma_chroma_base_addr( + decoder->dp_buf.no_of_dec_pic_buf, + dpb_addr_y, NULL); + vidc_pix_cache_set_frame_range(decoder->dpb_buf_size.size_y, + decoder->dpb_buf_size.size_c); + vidc_pix_cache_clear_cache_tags(); + } + ddl_context->vidc_decode_frame_start[ddl->command_channel] ( + &dec_param); +} + +void ddl_vidc_decode_eos_run(struct ddl_client_context *ddl) +{ + struct ddl_context *ddl_context = ddl->ddl_context; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vcd_frame_data *bit_stream = + &(ddl->input_frame.vcd_frm); + struct ddl_dec_buffers *dec_buffers = &(decoder->hw_bufs); + struct ddl_mask *dpb_mask = + &(ddl->codec_data.decoder.dpb_mask); + struct vidc_1080p_dec_frame_start_param dec_param; + + DDL_MSG_LOW("ddl_state_transition: %s ~~> DDL_CLIENT_WAIT_FOR_EOS_DONE", + ddl_get_state_string(ddl->client_state)); + ddl->client_state = DDL_CLIENT_WAIT_FOR_EOS_DONE; + if (decoder->output_order == VCD_DEC_ORDER_DECODE) + decoder->dynamic_prop_change |= DDL_DEC_REQ_OUTPUT_FLUSH; + ddl_vidc_decode_dynamic_property(ddl, true); + ddl_decoder_dpb_transact(decoder, NULL, DDL_DPB_OP_SET_MASK); + decoder->dynmic_prop_change_req = true; + ddl->cmd_state = DDL_CMD_EOS; + memset(&dec_param, 0, sizeof(dec_param)); + dec_param.cmd_seq_num = ++ddl_context->cmd_seq_num; + dec_param.inst_id = ddl->instance_id; + dec_param.shared_mem_addr_offset = DDL_ADDR_OFFSET( + ddl_context->dram_base_a, + ddl->shared_mem[ddl->command_channel]); + dec_param.descriptor_buffer_addr_offset = DDL_ADDR_OFFSET( + ddl_context->dram_base_a, dec_buffers->desc); + dec_param.descriptor_buffer_size = dec_buffers->desc.buffer_size; + dec_param.release_dpb_bit_mask = dpb_mask->hw_mask; + dec_param.decode = VIDC_1080P_DEC_TYPE_LAST_FRAME_DATA; + dec_param.dpb_count = decoder->dp_buf.no_of_dec_pic_buf; + if (decoder->flush_pending) { + dec_param.dpb_flush = true; + decoder->flush_pending = false; + } else + dec_param.dpb_flush = false; + vidc_sm_set_frame_tag(&ddl->shared_mem[ddl->command_channel], + bit_stream->ip_frm_tag); + ddl_context->vidc_decode_frame_start[ddl->command_channel] ( + &dec_param); +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vidc.c b/drivers/video/msm/vidc/1080p/ddl/vidc.c new file mode 100644 index 00000000000..ae918f0d91d --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vidc.c @@ -0,0 +1,1007 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc.h" +#include "vidc_hwio.h" + + +#define VIDC_1080P_INIT_CH_INST_ID 0x0000ffff +#define VIDC_1080P_RESET_VI 0x3f7 +#define VIDC_1080P_RESET_VI_RISC 0x3f6 +#define VIDC_1080P_RESET_VI_VIDC_RISC 0x3f2 +#define VIDC_1080P_RESET_ALL 0 +#define VIDC_1080P_RESET_RISC 0x3fe +#define VIDC_1080P_RESET_NONE 0x3ff +#define VIDC_1080P_INTERRUPT_CLEAR 0 +#define VIDC_1080P_MAX_H264DECODER_DPB 32 +#define VIDC_1080P_MAX_DEC_RECON_BUF 32 + +#define VIDC_1080P_SI_RG7_DISPLAY_STATUS_MASK 0x00000007 +#define VIDC_1080P_SI_RG7_DISPLAY_STATUS_SHIFT 0 +#define VIDC_1080P_SI_RG7_DISPLAY_CODING_MASK 0x00000008 +#define VIDC_1080P_SI_RG7_DISPLAY_CODING_SHIFT 3 +#define VIDC_1080P_SI_RG7_DISPLAY_RES_MASK 0x00000030 +#define VIDC_1080P_SI_RG7_DISPLAY_RES_SHIFT 4 + +#define VIDC_1080P_SI_RG7_DISPLAY_CROP_MASK 0x00000040 +#define VIDC_1080P_SI_RG7_DISPLAY_CROP_SHIFT 6 + +#define VIDC_1080P_SI_RG8_DECODE_FRAMETYPE_MASK 0x00000007 + +#define VIDC_1080P_SI_RG10_NUM_DPB_BMSK 0x00003fff +#define VIDC_1080P_SI_RG10_NUM_DPB_SHFT 0 +#define VIDC_1080P_SI_RG10_DPB_FLUSH_BMSK 0x00004000 +#define VIDC_1080P_SI_RG10_DPB_FLUSH_SHFT 14 +#define VIDC_1080P_SI_RG10_DMX_DISABLE_BMSK 0x00008000 +#define VIDC_1080P_SI_RG10_DMX_DISABLE_SHFT 15 + +#define VIDC_1080P_SI_RG11_DECODE_STATUS_MASK 0x00000007 +#define VIDC_1080P_SI_RG11_DECODE_STATUS_SHIFT 0 +#define VIDC_1080P_SI_RG11_DECODE_CODING_MASK 0x00000008 +#define VIDC_1080P_SI_RG11_DECODE_CODING_SHIFT 3 +#define VIDC_1080P_SI_RG11_DECODE_RES_MASK 0x000000C0 +#define VIDC_1080P_SI_RG11_DECODE_RES_SHIFT 6 +#define VIDC_1080P_SI_RG11_DECODE_CROPP_MASK 0x00000100 +#define VIDC_1080P_SI_RG11_DECODE_CROPP_SHIFT 8 + +#define VIDC_1080P_BASE_OFFSET_SHIFT 11 + + +#define VIDC_1080P_H264DEC_LUMA_ADDR HWIO_REG_759068_ADDR +#define VIDC_1080P_H264DEC_CHROMA_ADDR HWIO_REG_515200_ADDR +#define VIDC_1080P_H264DEC_MV_PLANE_ADDR HWIO_REG_466192_ADDR + +#define VIDC_1080P_DEC_LUMA_ADDR HWIO_REG_759068_ADDR +#define VIDC_1080P_DEC_CHROMA_ADDR HWIO_REG_515200_ADDR + +#define VIDC_1080P_DEC_TYPE_SEQ_HEADER 0x00010000 +#define VIDC_1080P_DEC_TYPE_FRAME_DATA 0x00020000 +#define VIDC_1080P_DEC_TYPE_LAST_FRAME_DATA 0x00030000 +#define VIDC_1080P_DEC_TYPE_INIT_BUFFERS 0x00040000 + +#define VIDC_1080P_ENC_TYPE_SEQ_HEADER 0x00010000 +#define VIDC_1080P_ENC_TYPE_FRAME_DATA 0x00020000 +#define VIDC_1080P_ENC_TYPE_LAST_FRAME_DATA 0x00030000 + +#define VIDC_1080P_MAX_INTRA_PERIOD 0xffff + +u8 *VIDC_BASE_PTR; + +void vidc_1080p_do_sw_reset(enum vidc_1080p_reset init_flag) +{ + if (init_flag == VIDC_1080P_RESET_IN_SEQ_FIRST_STAGE) { + u32 sw_reset_value = 0; + + VIDC_HWIO_IN(REG_557899, &sw_reset_value); + sw_reset_value &= (~HWIO_REG_557899_RSTN_VI_BMSK); + VIDC_HWIO_OUT(REG_557899, sw_reset_value); + sw_reset_value &= (~HWIO_REG_557899_RSTN_RISC_BMSK); + VIDC_HWIO_OUT(REG_557899, sw_reset_value); + sw_reset_value &= (~(HWIO_REG_557899_RSTN_VIDCCORE_BMSK | + HWIO_REG_557899_RSTN_DMX_BMSK)); + + VIDC_HWIO_OUT(REG_557899, sw_reset_value); + } else if (init_flag == VIDC_1080P_RESET_IN_SEQ_SECOND_STAGE) { + VIDC_HWIO_OUT(REG_557899, VIDC_1080P_RESET_ALL); + VIDC_HWIO_OUT(REG_557899, VIDC_1080P_RESET_RISC); + } +} + +void vidc_1080p_release_sw_reset(void) +{ + u32 nAxiCtl; + u32 nAxiStatus; + u32 nRdWrBurst; + u32 nOut_Order; + + nOut_Order = VIDC_SETFIELD(1, HWIO_REG_5519_AXI_AOOORD_SHFT, + HWIO_REG_5519_AXI_AOOORD_BMSK); + VIDC_HWIO_OUT(REG_5519, nOut_Order); + + nOut_Order = VIDC_SETFIELD(1, HWIO_REG_606364_AXI_AOOOWR_SHFT, + HWIO_REG_606364_AXI_AOOOWR_BMSK); + VIDC_HWIO_OUT(REG_606364, nOut_Order); + + nAxiCtl = VIDC_SETFIELD(1, HWIO_REG_471159_AXI_HALT_REQ_SHFT, + HWIO_REG_471159_AXI_HALT_REQ_BMSK); + + VIDC_HWIO_OUT(REG_471159, nAxiCtl); + + do { + VIDC_HWIO_IN(REG_437878, &nAxiStatus); + nAxiStatus = VIDC_GETFIELD(nAxiStatus, + HWIO_REG_437878_AXI_HALT_ACK_BMSK, + HWIO_REG_437878_AXI_HALT_ACK_SHFT); + } while (0x3 != nAxiStatus); + + nAxiCtl = VIDC_SETFIELD(1, + HWIO_REG_471159_AXI_RESET_SHFT, + HWIO_REG_471159_AXI_RESET_BMSK); + + VIDC_HWIO_OUT(REG_471159, nAxiCtl); + VIDC_HWIO_OUT(REG_471159, 0); + + nRdWrBurst = VIDC_SETFIELD(8, + HWIO_REG_922106_XBAR_OUT_MAX_RD_BURST_SHFT, + HWIO_REG_922106_XBAR_OUT_MAX_RD_BURST_BMSK) | + VIDC_SETFIELD(8, HWIO_REG_922106_XBAR_OUT_MAX_WR_BURST_SHFT, + HWIO_REG_922106_XBAR_OUT_MAX_WR_BURST_BMSK); + + VIDC_HWIO_OUT(REG_922106, nRdWrBurst); + + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_313350, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_611794, VIDC_1080P_HOST2RISC_CMD_EMPTY); + VIDC_HWIO_OUT(REG_557899, VIDC_1080P_RESET_NONE); +} + +void vidc_1080p_clear_interrupt(void) +{ + VIDC_HWIO_OUT(REG_575377, VIDC_1080P_INTERRUPT_CLEAR); +} + +void vidc_1080p_set_host2risc_cmd(enum vidc_1080p_host2risc_cmd + host2risc_command, u32 host2risc_arg1, u32 host2risc_arg2, + u32 host2risc_arg3, u32 host2risc_arg4) +{ + VIDC_HWIO_OUT(REG_611794, VIDC_1080P_HOST2RISC_CMD_EMPTY); + VIDC_HWIO_OUT(REG_356340, host2risc_arg1); + VIDC_HWIO_OUT(REG_899023, host2risc_arg2); + VIDC_HWIO_OUT(REG_987762, host2risc_arg3); + VIDC_HWIO_OUT(REG_544000, host2risc_arg4); + VIDC_HWIO_OUT(REG_611794, host2risc_command); +} + +void vidc_1080p_get_risc2host_cmd(u32 *pn_risc2host_command, + u32 *pn_risc2host_arg1, u32 *pn_risc2host_arg2, + u32 *pn_risc2host_arg3, u32 *pn_risc2host_arg4) +{ + VIDC_HWIO_IN(REG_695082, pn_risc2host_command); + VIDC_HWIO_IN(REG_156596, pn_risc2host_arg1); + VIDC_HWIO_IN(REG_222292, pn_risc2host_arg2); + VIDC_HWIO_IN(REG_790962, pn_risc2host_arg3); + VIDC_HWIO_IN(REG_679882, pn_risc2host_arg4); +} + +void vidc_1080p_get_risc2host_cmd_status(u32 err_status, + u32 *dec_err_status, u32 *disp_err_status) +{ + *dec_err_status = VIDC_GETFIELD(err_status, + VIDC_RISC2HOST_ARG2_VIDC_DEC_ERROR_STATUS_BMSK, + VIDC_RISC2HOST_ARG2_VIDC_DEC_ERROR_STATUS_SHFT); + *disp_err_status = VIDC_GETFIELD(err_status, + VIDC_RISC2HOST_ARG2_VIDC_DISP_ERROR_STATUS_BMSK, + VIDC_RISC2HOST_ARG2_VIDC_DISP_ERROR_STATUS_SHFT); + +} + +void vidc_1080p_clear_risc2host_cmd(void) +{ + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); +} + +void vidc_1080p_get_fw_version(u32 *pn_fw_version) +{ + VIDC_HWIO_IN(REG_653206, pn_fw_version); +} + +void vidc_1080p_get_fw_status(u32 *pn_fw_status) +{ + VIDC_HWIO_IN(REG_350619, pn_fw_status); +} + +void vidc_1080p_init_memory_controller(u32 dram_base_addr_a, + u32 dram_base_addr_b) +{ + VIDC_HWIO_OUT(REG_64440, dram_base_addr_a); + VIDC_HWIO_OUT(REG_675915, dram_base_addr_b); +} + +void vidc_1080p_get_memory_controller_status(u32 *pb_mc_abusy, + u32 *pb_mc_bbusy) +{ + u32 mc_status = 0; + + VIDC_HWIO_IN(REG_399911, &mc_status); + *pb_mc_abusy = (u32) ((mc_status & + HWIO_REG_399911_MC_BUSY_A_BMSK) >> + HWIO_REG_399911_MC_BUSY_A_SHFT); + *pb_mc_bbusy = (u32) ((mc_status & + HWIO_REG_399911_MC_BUSY_B_BMSK) >> + HWIO_REG_399911_MC_BUSY_B_SHFT); +} + +void vidc_1080p_set_h264_decode_buffers(u32 dpb, u32 dec_vert_nb_mv_offset, + u32 dec_nb_ip_offset, u32 *pn_dpb_luma_offset, + u32 *pn_dpb_chroma_offset, u32 *pn_mv_buffer_offset) +{ + u32 count = 0, num_dpb_used = dpb; + u8 *vidc_dpb_luma_reg = (u8 *) VIDC_1080P_H264DEC_LUMA_ADDR; + u8 *vidc_dpb_chroma_reg = (u8 *) VIDC_1080P_H264DEC_CHROMA_ADDR; + u8 *vidc_mv_buffer_reg = (u8 *) VIDC_1080P_H264DEC_MV_PLANE_ADDR; + + VIDC_HWIO_OUT(REG_931311, (dec_vert_nb_mv_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_16277, (dec_nb_ip_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + if (num_dpb_used > VIDC_1080P_MAX_H264DECODER_DPB) + num_dpb_used = VIDC_1080P_MAX_H264DECODER_DPB; + for (count = 0; count < num_dpb_used; count++) { + VIDC_OUT_DWORD(vidc_dpb_luma_reg, + (pn_dpb_luma_offset[count] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_OUT_DWORD(vidc_dpb_chroma_reg, + (pn_dpb_chroma_offset[count] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_OUT_DWORD(vidc_mv_buffer_reg, + (pn_mv_buffer_offset[count] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + vidc_dpb_luma_reg += 4; + vidc_dpb_chroma_reg += 4; + vidc_mv_buffer_reg += 4; + } +} + +void vidc_1080p_set_decode_recon_buffers(u32 recon_buffer, + u32 *pn_dec_luma, u32 *pn_dec_chroma) +{ + u32 count = 0, recon_buf_to_program = recon_buffer; + u8 *dec_recon_luma_reg = (u8 *) VIDC_1080P_DEC_LUMA_ADDR; + u8 *dec_recon_chroma_reg = (u8 *) VIDC_1080P_DEC_CHROMA_ADDR; + + if (recon_buf_to_program > VIDC_1080P_MAX_DEC_RECON_BUF) + recon_buf_to_program = VIDC_1080P_MAX_DEC_RECON_BUF; + for (count = 0; count < recon_buf_to_program; count++) { + VIDC_OUT_DWORD(dec_recon_luma_reg, (pn_dec_luma[count] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_OUT_DWORD(dec_recon_chroma_reg, + (pn_dec_chroma[count] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + dec_recon_luma_reg += 4; + dec_recon_chroma_reg += 4; + } +} + +void vidc_1080p_set_mpeg4_divx_decode_work_buffers(u32 nb_dcac_buffer_offset, + u32 upnb_mv_buffer_offset, u32 sub_anchor_buffer_offset, + u32 overlay_transform_buffer_offset, u32 stx_parser_buffer_offset) +{ + VIDC_HWIO_OUT(REG_931311, (nb_dcac_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_16277, (upnb_mv_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_654169, (sub_anchor_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_802794, + (overlay_transform_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_252167, (stx_parser_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); +} + +void vidc_1080p_set_h263_decode_work_buffers(u32 nb_dcac_buffer_offset, + u32 upnb_mv_buffer_offset, u32 sub_anchor_buffer_offset, + u32 overlay_transform_buffer_offset) +{ + VIDC_HWIO_OUT(REG_931311, (nb_dcac_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_16277, (upnb_mv_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_654169, (sub_anchor_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_802794, + (overlay_transform_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); +} + +void vidc_1080p_set_vc1_decode_work_buffers(u32 nb_dcac_buffer_offset, + u32 upnb_mv_buffer_offset, u32 sub_anchor_buffer_offset, + u32 overlay_transform_buffer_offset, u32 bitplain1Buffer_offset, + u32 bitplain2Buffer_offset, u32 bitplain3Buffer_offset) +{ + VIDC_HWIO_OUT(REG_931311, (nb_dcac_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_16277, (upnb_mv_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_654169, (sub_anchor_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_802794, + (overlay_transform_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_724376, (bitplain3Buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_551674, (bitplain2Buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_115991, (bitplain1Buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); +} + +void vidc_1080p_set_encode_recon_buffers(u32 recon_buffer, + u32 *pn_enc_luma, u32 *pn_enc_chroma) +{ + if (recon_buffer > 0) { + VIDC_HWIO_OUT(REG_294579, (pn_enc_luma[0] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_759068, (pn_enc_chroma[0] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + } + if (recon_buffer > 1) { + VIDC_HWIO_OUT(REG_616802, (pn_enc_luma[1] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_833502, (pn_enc_chroma[1] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + } + if (recon_buffer > 2) { + VIDC_HWIO_OUT(REG_61427, (pn_enc_luma[2] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_68356, (pn_enc_chroma[2] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + } + if (recon_buffer > 3) { + VIDC_HWIO_OUT(REG_23318, (pn_enc_luma[3] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_127855, (pn_enc_chroma[3] >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + } +} + +void vidc_1080p_set_h264_encode_work_buffers(u32 up_row_mv_buffer_offset, + u32 direct_colzero_flag_buffer_offset, + u32 upper_intra_md_buffer_offset, + u32 upper_intra_pred_buffer_offset, u32 nbor_infor_buffer_offset, + u32 mb_info_offset) +{ + VIDC_HWIO_OUT(REG_515200, (up_row_mv_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_69832, + (direct_colzero_flag_buffer_offset>> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_256132, + (upper_intra_md_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_475648, + (upper_intra_pred_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_29510, (nbor_infor_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_175929, (mb_info_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); +} + +void vidc_1080p_set_h263_encode_work_buffers(u32 up_row_mv_buffer_offset, + u32 up_row_inv_quanti_coeff_buffer_offset) +{ + VIDC_HWIO_OUT(REG_515200, (up_row_mv_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_29510, ( + up_row_inv_quanti_coeff_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); +} + +void vidc_1080p_set_mpeg4_encode_work_buffers(u32 skip_flag_buffer_offset, + u32 up_row_inv_quanti_coeff_buffer_offset, u32 upper_mv_offset) +{ + VIDC_HWIO_OUT(REG_69832, (skip_flag_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_29510, ( + up_row_inv_quanti_coeff_buffer_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); + VIDC_HWIO_OUT(REG_515200, (upper_mv_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT)); +} + +void vidc_1080p_set_encode_frame_size(u32 hori_size, u32 vert_size) +{ + VIDC_HWIO_OUT(REG_934655, hori_size); + VIDC_HWIO_OUT(REG_179070, vert_size); +} + +void vidc_1080p_set_encode_profile_level(u32 encode_profile, u32 enc_level) +{ + u32 profile_level = 0; + + profile_level = VIDC_SETFIELD(enc_level, + HWIO_REG_63643_LEVEL_SHFT, + HWIO_REG_63643_LEVEL_BMSK) | + VIDC_SETFIELD(encode_profile, + HWIO_REG_63643_PROFILE_SHFT, + HWIO_REG_63643_PROFILE_BMSK); + VIDC_HWIO_OUT(REG_63643, profile_level); +} + +void vidc_1080p_set_encode_field_picture_structure(u32 enc_field_picture) +{ + VIDC_HWIO_OUT(REG_786024, enc_field_picture); +} + +void vidc_1080p_set_decode_mpeg4_pp_filter(u32 lf_enables) +{ + VIDC_HWIO_OUT(REG_152500, lf_enables); +} + +void vidc_1080p_set_decode_qp_save_control(u32 enable_q_pout) +{ + VIDC_HWIO_OUT(REG_143629, enable_q_pout); +} + +void vidc_1080p_get_returned_channel_inst_id(u32 *pn_rtn_chid) +{ + VIDC_HWIO_IN(REG_607589, pn_rtn_chid); +} + +void vidc_1080p_clear_returned_channel_inst_id(void) +{ + VIDC_HWIO_OUT(REG_607589, VIDC_1080P_INIT_CH_INST_ID); +} + +void vidc_1080p_get_decode_seq_start_result( + struct vidc_1080p_seq_hdr_info *seq_hdr_info) +{ + u32 dec_disp_result; + u32 frame = 0; + VIDC_HWIO_IN(REG_845544, &seq_hdr_info->img_size_y); + VIDC_HWIO_IN(REG_859906, &seq_hdr_info->img_size_x); + VIDC_HWIO_IN(REG_490078, &seq_hdr_info->min_num_dpb); + VIDC_HWIO_IN(REG_489688, &seq_hdr_info->dec_frm_size); + VIDC_HWIO_IN(REG_853667, &dec_disp_result); + seq_hdr_info->disp_progressive = VIDC_GETFIELD(dec_disp_result, + VIDC_1080P_SI_RG7_DISPLAY_CODING_MASK, + VIDC_1080P_SI_RG7_DISPLAY_CODING_SHIFT); + seq_hdr_info->disp_crop_exists = VIDC_GETFIELD(dec_disp_result, + VIDC_1080P_SI_RG7_DISPLAY_CROP_MASK, + VIDC_1080P_SI_RG7_DISPLAY_CROP_SHIFT); + VIDC_HWIO_IN(REG_692991, &dec_disp_result); + seq_hdr_info->dec_progressive = VIDC_GETFIELD(dec_disp_result, + VIDC_1080P_SI_RG11_DECODE_CODING_MASK, + VIDC_1080P_SI_RG11_DECODE_CODING_SHIFT); + seq_hdr_info->dec_crop_exists = VIDC_GETFIELD(dec_disp_result, + VIDC_1080P_SI_RG11_DECODE_CROPP_MASK, + VIDC_1080P_SI_RG11_DECODE_CROPP_SHIFT); + VIDC_HWIO_IN(REG_760102, &frame); + seq_hdr_info->data_partition = ((frame & 0x8) >> 3); +} + +void vidc_1080p_get_decoded_frame_size(u32 *pn_decoded_size) +{ + VIDC_HWIO_IN(REG_489688, pn_decoded_size); +} + +void vidc_1080p_get_display_frame_result( + struct vidc_1080p_dec_disp_info *dec_disp_info) +{ + u32 display_result; + VIDC_HWIO_IN(REG_640904, &dec_disp_info->display_y_addr); + VIDC_HWIO_IN(REG_60114, &dec_disp_info->display_c_addr); + VIDC_HWIO_IN(REG_853667, &display_result); + VIDC_HWIO_IN(REG_845544, &dec_disp_info->img_size_y); + VIDC_HWIO_IN(REG_859906, &dec_disp_info->img_size_x); + dec_disp_info->display_status = + (enum vidc_1080p_display_status) + VIDC_GETFIELD(display_result, + VIDC_1080P_SI_RG7_DISPLAY_STATUS_MASK, + VIDC_1080P_SI_RG7_DISPLAY_STATUS_SHIFT); + dec_disp_info->display_coding = + (enum vidc_1080p_display_coding) + VIDC_GETFIELD(display_result, VIDC_1080P_SI_RG7_DISPLAY_CODING_MASK, + VIDC_1080P_SI_RG7_DISPLAY_CODING_SHIFT); + dec_disp_info->disp_resl_change = VIDC_GETFIELD(display_result, + VIDC_1080P_SI_RG7_DISPLAY_RES_MASK, + VIDC_1080P_SI_RG7_DISPLAY_RES_SHIFT); + dec_disp_info->disp_crop_exists = VIDC_GETFIELD(display_result, + VIDC_1080P_SI_RG7_DISPLAY_CROP_MASK, + VIDC_1080P_SI_RG7_DISPLAY_CROP_SHIFT); +} + +void vidc_1080p_get_decode_frame( + enum vidc_1080p_decode_frame *pe_frame) +{ + u32 frame = 0; + + VIDC_HWIO_IN(REG_760102, &frame); + *pe_frame = (enum vidc_1080p_decode_frame) + (frame & VIDC_1080P_SI_RG8_DECODE_FRAMETYPE_MASK); +} + +void vidc_1080p_get_decode_frame_result( + struct vidc_1080p_dec_disp_info *dec_disp_info) +{ + u32 decode_result; + + VIDC_HWIO_IN(REG_378318, &dec_disp_info->decode_y_addr); + VIDC_HWIO_IN(REG_203487, &dec_disp_info->decode_c_addr); + VIDC_HWIO_IN(REG_692991, &decode_result); + dec_disp_info->decode_status = (enum vidc_1080p_display_status) + VIDC_GETFIELD(decode_result, + VIDC_1080P_SI_RG11_DECODE_STATUS_MASK, + VIDC_1080P_SI_RG11_DECODE_STATUS_SHIFT); + dec_disp_info->decode_coding = (enum vidc_1080p_display_coding) + VIDC_GETFIELD(decode_result, + VIDC_1080P_SI_RG11_DECODE_CODING_MASK, + VIDC_1080P_SI_RG11_DECODE_CODING_SHIFT); + dec_disp_info->dec_resl_change = VIDC_GETFIELD(decode_result, + VIDC_1080P_SI_RG11_DECODE_RES_MASK, + VIDC_1080P_SI_RG11_DECODE_RES_SHIFT); + dec_disp_info->dec_crop_exists = VIDC_GETFIELD(decode_result, + VIDC_1080P_SI_RG11_DECODE_CROPP_MASK, + VIDC_1080P_SI_RG11_DECODE_CROPP_SHIFT); +} + +void vidc_1080p_decode_seq_start_ch0( + struct vidc_1080p_dec_seq_start_param *param) +{ + + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_117192, + param->stream_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_145068, param->stream_frame_size); + VIDC_HWIO_OUT(REG_921356, + param->descriptor_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_190381, param->stream_buffersize); + VIDC_HWIO_OUT(REG_85655, param->descriptor_buffer_size); + VIDC_HWIO_OUT(REG_889944, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_397087, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_DEC_TYPE_SEQ_HEADER | + param->inst_id); +} + +void vidc_1080p_decode_seq_start_ch1( + struct vidc_1080p_dec_seq_start_param *param) +{ + + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_313350, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_980194, + param->stream_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_936704, param->stream_frame_size); + VIDC_HWIO_OUT(REG_821977, + param->descriptor_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_887095, param->stream_buffersize); + VIDC_HWIO_OUT(REG_576987, param->descriptor_buffer_size); + VIDC_HWIO_OUT(REG_652528, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_254093, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_313350, VIDC_1080P_DEC_TYPE_SEQ_HEADER | + param->inst_id); +} + +void vidc_1080p_decode_frame_start_ch0( + struct vidc_1080p_dec_frame_start_param *param) +{ + u32 dpb_config; + + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_INIT_CH_INST_ID); + if ((param->decode == VIDC_1080P_DEC_TYPE_LAST_FRAME_DATA) && + ((!param->stream_buffer_addr_offset) || + (!param->stream_frame_size))) { + VIDC_HWIO_OUT(REG_117192, 0); + VIDC_HWIO_OUT(REG_145068, 0); + VIDC_HWIO_OUT(REG_190381, 0); + } else { + VIDC_HWIO_OUT(REG_117192, + param->stream_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_145068, + param->stream_frame_size); + VIDC_HWIO_OUT(REG_190381, + param->stream_buffersize); + } + dpb_config = VIDC_SETFIELD(param->dpb_flush, + VIDC_1080P_SI_RG10_DPB_FLUSH_SHFT, + VIDC_1080P_SI_RG10_DPB_FLUSH_BMSK) | + VIDC_SETFIELD(param->dpb_count, + VIDC_1080P_SI_RG10_NUM_DPB_SHFT, + VIDC_1080P_SI_RG10_NUM_DPB_BMSK); + VIDC_HWIO_OUT(REG_921356, + param->descriptor_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_85655, param->descriptor_buffer_size); + VIDC_HWIO_OUT(REG_86830, param->release_dpb_bit_mask); + VIDC_HWIO_OUT(REG_889944, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_404623, dpb_config); + VIDC_HWIO_OUT(REG_397087, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_666957, (u32)param->decode | + param->inst_id); +} + + +void vidc_1080p_decode_frame_start_ch1( + struct vidc_1080p_dec_frame_start_param *param) +{ + u32 dpb_config; + + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_313350, VIDC_1080P_INIT_CH_INST_ID); + if ((param->decode == VIDC_1080P_DEC_TYPE_LAST_FRAME_DATA) && + ((!param->stream_buffer_addr_offset) || + (!param->stream_frame_size))) { + VIDC_HWIO_OUT(REG_980194, 0); + VIDC_HWIO_OUT(REG_936704, 0); + VIDC_HWIO_OUT(REG_887095, 0); + } else { + VIDC_HWIO_OUT(REG_980194, + param->stream_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_936704, + param->stream_frame_size); + VIDC_HWIO_OUT(REG_887095, + param->stream_buffersize); + } + dpb_config = VIDC_SETFIELD(param->dpb_flush, + VIDC_1080P_SI_RG10_DPB_FLUSH_SHFT, + VIDC_1080P_SI_RG10_DPB_FLUSH_BMSK) | + VIDC_SETFIELD(param->dpb_count, + VIDC_1080P_SI_RG10_NUM_DPB_SHFT, + VIDC_1080P_SI_RG10_NUM_DPB_BMSK); + VIDC_HWIO_OUT(REG_821977, + param->descriptor_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_576987, param->descriptor_buffer_size); + VIDC_HWIO_OUT(REG_70448, param->release_dpb_bit_mask); + VIDC_HWIO_OUT(REG_652528, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_220637, dpb_config); + VIDC_HWIO_OUT(REG_254093, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_313350, (u32)param->decode | + param->inst_id); +} + +void vidc_1080p_decode_init_buffers_ch0( + struct vidc_1080p_dec_init_buffers_param *param) +{ + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_889944, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_404623, param->dpb_count); + VIDC_HWIO_OUT(REG_397087, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_DEC_TYPE_INIT_BUFFERS | + param->inst_id); +} + +void vidc_1080p_decode_init_buffers_ch1( + struct vidc_1080p_dec_init_buffers_param *param) +{ + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_313350, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_652528, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_220637, param->dpb_count); + VIDC_HWIO_OUT(REG_254093, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_313350, VIDC_1080P_DEC_TYPE_INIT_BUFFERS | + param->inst_id); +} + +void vidc_1080p_set_dec_resolution_ch0(u32 width, u32 height) +{ + VIDC_HWIO_OUT(REG_612810, height); + VIDC_HWIO_OUT(REG_175608, width); +} + +void vidc_1080p_set_dec_resolution_ch1(u32 width, u32 height) +{ + VIDC_HWIO_OUT(REG_655721, height); + VIDC_HWIO_OUT(REG_548308, width); +} + +void vidc_1080p_get_encode_frame_info( + struct vidc_1080p_enc_frame_info *frame_info) +{ + VIDC_HWIO_IN(REG_845544, &(frame_info->enc_frame_size)); + VIDC_HWIO_IN(REG_859906, + &(frame_info->enc_picture_count)); + VIDC_HWIO_IN(REG_490078, + &(frame_info->enc_write_pointer)); + VIDC_HWIO_IN(REG_640904, + (u32 *)(&(frame_info->enc_frame))); + VIDC_HWIO_IN(REG_60114, + &(frame_info->enc_luma_address)); + frame_info->enc_luma_address = frame_info->enc_luma_address << + VIDC_1080P_BASE_OFFSET_SHIFT; + VIDC_HWIO_IN(REG_489688, + &(frame_info->enc_chroma_address)); + frame_info->enc_chroma_address = frame_info->\ + enc_chroma_address << VIDC_1080P_BASE_OFFSET_SHIFT; +} + +void vidc_1080p_encode_seq_start_ch0( + struct vidc_1080p_enc_seq_start_param *param) +{ + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_117192, + param->stream_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_921356, param->stream_buffer_size); + VIDC_HWIO_OUT(REG_889944, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_397087, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_ENC_TYPE_SEQ_HEADER | + param->inst_id); +} + +void vidc_1080p_encode_seq_start_ch1( + struct vidc_1080p_enc_seq_start_param *param) +{ + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_313350, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_980194, + param->stream_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_821977, param->stream_buffer_size); + VIDC_HWIO_OUT(REG_652528, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_254093, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_313350, VIDC_1080P_ENC_TYPE_SEQ_HEADER | + param->inst_id); +} + +void vidc_1080p_encode_frame_start_ch0( + struct vidc_1080p_enc_frame_start_param *param) +{ + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_117192, + param->stream_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_921356, param->stream_buffer_size); + VIDC_HWIO_OUT(REG_612810, param->current_y_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_175608, param->current_c_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_190381, param->intra_frame); + VIDC_HWIO_OUT(REG_889944, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_404623, param->input_flush); + VIDC_HWIO_OUT(REG_397087, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_666957, (u32)param->encode | + param->inst_id); +} + +void vidc_1080p_encode_frame_start_ch1( + struct vidc_1080p_enc_frame_start_param *param) +{ + + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_313350, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_980194, + param->stream_buffer_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_821977, param->stream_buffer_size); + VIDC_HWIO_OUT(REG_655721, param->current_y_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_548308, param->current_c_addr_offset >> + VIDC_1080P_BASE_OFFSET_SHIFT); + VIDC_HWIO_OUT(REG_887095, param->intra_frame); + VIDC_HWIO_OUT(REG_652528, param->shared_mem_addr_offset); + VIDC_HWIO_OUT(REG_404623, param->input_flush); + VIDC_HWIO_OUT(REG_254093, param->cmd_seq_num); + VIDC_HWIO_OUT(REG_313350, (u32)param->encode | + param->inst_id); +} + +void vidc_1080p_set_encode_picture(u32 number_p, u32 number_b) +{ + u32 picture, ifrm_ctrl; + if (number_p >= VIDC_1080P_MAX_INTRA_PERIOD) + ifrm_ctrl = 0; + else + ifrm_ctrl = number_p + 1; + picture = VIDC_SETFIELD(1 , + HWIO_REG_783891_ENC_PIC_TYPE_USE_SHFT, + HWIO_REG_783891_ENC_PIC_TYPE_USE_BMSK) | + VIDC_SETFIELD(ifrm_ctrl, + HWIO_REG_783891_I_FRM_CTRL_SHFT, + HWIO_REG_783891_I_FRM_CTRL_BMSK) + | VIDC_SETFIELD(number_b , + HWIO_REG_783891_B_FRM_CTRL_SHFT , + HWIO_REG_783891_B_FRM_CTRL_BMSK); + VIDC_HWIO_OUT(REG_783891, picture); +} + +void vidc_1080p_set_encode_multi_slice_control( + enum vidc_1080p_MSlice_selection multiple_slice_selection, + u32 mslice_mb, u32 mslice_byte) +{ + VIDC_HWIO_OUT(REG_226332, multiple_slice_selection); + VIDC_HWIO_OUT(REG_696136, mslice_mb); + VIDC_HWIO_OUT(REG_515564, mslice_byte); +} + +void vidc_1080p_set_encode_circular_intra_refresh(u32 cir_num) +{ + VIDC_HWIO_OUT(REG_886210, cir_num); +} + +void vidc_1080p_set_encode_input_frame_format( + enum vidc_1080p_memory_access_method memory_format) +{ + VIDC_HWIO_OUT(REG_645603, memory_format); +} + +void vidc_1080p_set_encode_padding_control(u32 pad_ctrl_on, + u32 cr_pad_val, u32 cb_pad_val, u32 luma_pad_val) +{ + u32 padding = VIDC_SETFIELD(pad_ctrl_on , + HWIO_REG_811733_PAD_CTRL_ON_SHFT, + HWIO_REG_811733_PAD_CTRL_ON_BMSK) | + VIDC_SETFIELD(cr_pad_val , + HWIO_REG_811733_CR_PAD_VIDC_SHFT , + HWIO_REG_811733_CR_PAD_VIDC_BMSK) | + VIDC_SETFIELD(cb_pad_val , + HWIO_REG_811733_CB_PAD_VIDC_SHFT , + HWIO_REG_811733_CB_PAD_VIDC_BMSK) | + VIDC_SETFIELD(luma_pad_val , + HWIO_REG_811733_LUMA_PAD_VIDC_SHFT , + HWIO_REG_811733_LUMA_PAD_VIDC_BMSK) ; + VIDC_HWIO_OUT(REG_811733, padding); +} + +void vidc_1080p_encode_set_rc_config(u32 enable_frame_level_rc, + u32 enable_mb_level_rc_flag, u32 frame_qp) +{ + u32 rc_config = VIDC_SETFIELD(enable_frame_level_rc , + HWIO_REG_559908_FR_RC_EN_SHFT , + HWIO_REG_559908_FR_RC_EN_BMSK) | + VIDC_SETFIELD(enable_mb_level_rc_flag , + HWIO_REG_559908_MB_RC_EN_SHFT, + HWIO_REG_559908_MB_RC_EN_BMSK) | + VIDC_SETFIELD(frame_qp , + HWIO_REG_559908_FRAME_QP_SHFT , + HWIO_REG_559908_FRAME_QP_BMSK); + VIDC_HWIO_OUT(REG_559908, rc_config); +} + +void vidc_1080p_encode_set_frame_level_rc_params(u32 rc_frame_rate, + u32 target_bitrate, u32 reaction_coeff) +{ + VIDC_HWIO_OUT(REG_977937, rc_frame_rate); + VIDC_HWIO_OUT(REG_166135, target_bitrate); + VIDC_HWIO_OUT(REG_550322, reaction_coeff); +} + +void vidc_1080p_encode_set_qp_params(u32 max_qp, u32 min_qp) +{ + u32 qbound = VIDC_SETFIELD(max_qp , HWIO_REG_109072_MAX_QP_SHFT, + HWIO_REG_109072_MAX_QP_BMSK) | + VIDC_SETFIELD(min_qp, + HWIO_REG_109072_MIN_QP_SHFT , + HWIO_REG_109072_MIN_QP_BMSK); + VIDC_HWIO_OUT(REG_109072, qbound); +} + +void vidc_1080p_encode_set_mb_level_rc_params(u32 disable_dark_region_as_flag, + u32 disable_smooth_region_as_flag , u32 disable_static_region_as_flag, + u32 disable_activity_region_flag) +{ + u32 rc_active_feature = VIDC_SETFIELD( + disable_dark_region_as_flag, + HWIO_REG_949086_DARK_DISABLE_SHFT, + HWIO_REG_949086_DARK_DISABLE_BMSK) | + VIDC_SETFIELD( + disable_smooth_region_as_flag, + HWIO_REG_949086_SMOOTH_DISABLE_SHFT, + HWIO_REG_949086_SMOOTH_DISABLE_BMSK) | + VIDC_SETFIELD( + disable_static_region_as_flag, + HWIO_REG_949086_STATIC_DISABLE_SHFT, + HWIO_REG_949086_STATIC_DISABLE_BMSK) | + VIDC_SETFIELD( + disable_activity_region_flag, + HWIO_REG_949086_ACT_DISABLE_SHFT, + HWIO_REG_949086_ACT_DISABLE_BMSK); + VIDC_HWIO_OUT(REG_949086, rc_active_feature); +} + +void vidc_1080p_set_h264_encode_entropy( + enum vidc_1080p_entropy_sel entropy_sel) +{ + VIDC_HWIO_OUT(REG_447796, entropy_sel); +} + +void vidc_1080p_set_h264_encode_loop_filter( + enum vidc_1080p_DBConfig db_config, u32 slice_alpha_offset, + u32 slice_beta_offset) +{ + VIDC_HWIO_OUT(REG_152500, db_config); + VIDC_HWIO_OUT(REG_266285, slice_alpha_offset); + VIDC_HWIO_OUT(REG_964731, slice_beta_offset); +} + +void vidc_1080p_set_h264_encoder_p_frame_ref_count(u32 max_reference) +{ + u32 ref_frames; + ref_frames = VIDC_SETFIELD(max_reference, + HWIO_REG_744348_P_SHFT, + HWIO_REG_744348_P_BMSK); + VIDC_HWIO_OUT(REG_744348, ref_frames); +} + +void vidc_1080p_set_h264_encode_8x8transform_control(u32 enable_8x8transform) +{ + VIDC_HWIO_OUT(REG_672163, enable_8x8transform); +} + +void vidc_1080p_set_mpeg4_encode_quarter_pel_control( + u32 enable_mpeg4_quarter_pel) +{ + VIDC_HWIO_OUT(REG_330132, enable_mpeg4_quarter_pel); +} + +void vidc_1080p_set_device_base_addr(u8 *mapped_va) +{ + VIDC_BASE_PTR = mapped_va; +} + +void vidc_1080p_get_intra_bias(u32 *bias) +{ + u32 intra_bias; + + VIDC_HWIO_IN(REG_676866, &intra_bias); + *bias = VIDC_GETFIELD(intra_bias, + HWIO_REG_676866_RMSK, + HWIO_REG_676866_SHFT); +} + +void vidc_1080p_set_intra_bias(u32 bias) +{ + u32 intra_bias; + + intra_bias = VIDC_SETFIELD(bias, + HWIO_REG_676866_SHFT, + HWIO_REG_676866_RMSK); + VIDC_HWIO_OUT(REG_676866, intra_bias); +} + +void vidc_1080p_get_bi_directional_bias(u32 *bi_directional_bias) +{ + u32 nbi_direct_bias; + + VIDC_HWIO_IN(REG_54267, &nbi_direct_bias); + *bi_directional_bias = VIDC_GETFIELD(nbi_direct_bias, + HWIO_REG_54267_RMSK, + HWIO_REG_54267_SHFT); +} + +void vidc_1080p_set_bi_directional_bias(u32 bi_directional_bias) +{ + u32 nbi_direct_bias; + + nbi_direct_bias = VIDC_SETFIELD(bi_directional_bias, + HWIO_REG_54267_SHFT, + HWIO_REG_54267_RMSK); + VIDC_HWIO_OUT(REG_54267, nbi_direct_bias); +} + +void vidc_1080p_get_encoder_sequence_header_size(u32 *seq_header_size) +{ + VIDC_HWIO_IN(REG_845544, seq_header_size); +} + +void vidc_1080p_get_intermedia_stage_debug_counter( + u32 *intermediate_stage_counter) +{ + VIDC_HWIO_IN(REG_805993, intermediate_stage_counter); +} + +void vidc_1080p_get_exception_status(u32 *exception_status) +{ + VIDC_HWIO_IN(REG_493355, exception_status); +} + +void vidc_1080p_frame_start_realloc(u32 instance_id) +{ + VIDC_HWIO_OUT(REG_695082, VIDC_1080P_RISC2HOST_CMD_EMPTY); + VIDC_HWIO_OUT(REG_666957, VIDC_1080P_INIT_CH_INST_ID); + VIDC_HWIO_OUT(REG_666957, + VIDC_1080P_DEC_TYPE_FRAME_START_REALLOC | instance_id); +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vidc.h b/drivers/video/msm/vidc/1080p/ddl/vidc.h new file mode 100644 index 00000000000..f87150945c0 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vidc.h @@ -0,0 +1,544 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIDC_H_ +#define _VIDC_H_ + +#include "vidc_hwio_reg.h" + +#define VIDC_1080P_RISC2HOST_CMD_EMPTY 0 +#define VIDC_1080P_RISC2HOST_CMD_OPEN_CH_RET 1 +#define VIDC_1080P_RISC2HOST_CMD_CLOSE_CH_RET 2 +#define VIDC_1080P_RISC2HOST_CMD_SEQ_DONE_RET 4 +#define VIDC_1080P_RISC2HOST_CMD_FRAME_DONE_RET 5 +#define VIDC_1080P_RISC2HOST_CMD_ENC_COMPLETE_RET 7 +#define VIDC_1080P_RISC2HOST_CMD_SYS_INIT_RET 8 +#define VIDC_1080P_RISC2HOST_CMD_FW_STATUS_RET 9 +#define VIDC_1080P_RISC2HOST_CMD_FLUSH_COMMAND_RET 12 +#define VIDC_1080P_RISC2HOST_CMD_ABORT_RET 13 +#define VIDC_1080P_RISC2HOST_CMD_INIT_BUFFERS_RET 15 +#define VIDC_1080P_RISC2HOST_CMD_EDFU_INT_RET 16 +#define VIDC_1080P_RISC2HOST_CMD_ERROR_RET 32 + +#define VIDC_RISC2HOST_ARG2_VIDC_DISP_ERROR_STATUS_BMSK 0xffff0000 +#define VIDC_RISC2HOST_ARG2_VIDC_DISP_ERROR_STATUS_SHFT 16 +#define VIDC_RISC2HOST_ARG2_VIDC_DEC_ERROR_STATUS_BMSK 0x0000ffff +#define VIDC_RISC2HOST_ARG2_VIDC_DEC_ERROR_STATUS_SHFT 0 + +#define VIDC_1080P_ERROR_INVALID_CHANNEL_NUMBER 1 +#define VIDC_1080P_ERROR_INVALID_COMMAND_ID 2 +#define VIDC_1080P_ERROR_CHANNEL_ALREADY_IN_USE 3 +#define VIDC_1080P_ERROR_CHANNEL_NOT_OPEN_BEFORE_CHANNEL_CLOSE 4 +#define VIDC_1080P_ERROR_OPEN_CH_ERROR_SEQ_START 5 +#define VIDC_1080P_ERROR_SEQ_START_ALREADY_CALLED 6 +#define VIDC_1080P_ERROR_OPEN_CH_ERROR_INIT_BUFFERS 7 +#define VIDC_1080P_ERROR_SEQ_START_ERROR_INIT_BUFFERS 8 +#define VIDC_1080P_ERROR_INIT_BUFFER_ALREADY_CALLED 9 +#define VIDC_1080P_ERROR_OPEN_CH_ERROR_FRAME_START 10 +#define VIDC_1080P_ERROR_SEQ_START_ERROR_FRAME_START 11 +#define VIDC_1080P_ERROR_INIT_BUFFERS_ERROR_FRAME_START 12 +#define VIDC_1080P_ERROR_RESOLUTION_CHANGED 13 +#define VIDC_1080P_ERROR_INVALID_COMMAND_LAST_FRAME 14 +#define VIDC_1080P_ERROR_INVALID_COMMAND 15 +#define VIDC_1080P_ERROR_INVALID_CODEC_TYPE 16 + +#define VIDC_1080P_ERROR_MEM_ALLOCATION_FAILED 20 +#define VIDC_1080P_ERROR_INSUFFICIENT_CONTEXT_SIZE 25 +#define VIDC_1080P_ERROR_UNSUPPORTED_FEATURE_IN_PROFILE 27 +#define VIDC_1080P_ERROR_RESOLUTION_NOT_SUPPORTED 28 + +#define VIDC_1080P_ERROR_HEADER_NOT_FOUND 52 +#define VIDC_1080P_ERROR_VOS_END_CODE_RECEIVED 53 +#define VIDC_1080P_ERROR_FRAME_RATE_NOT_SUPPORTED 62 +#define VIDC_1080P_ERROR_INVALID_QP_VALUE 63 +#define VIDC_1080P_ERROR_INVALID_RC_REACTION_COEFFICIENT 64 +#define VIDC_1080P_ERROR_INVALID_CPB_SIZE_AT_GIVEN_LEVEL 65 +#define VIDC_1080P_ERROR_B_FRAME_NOT_SUPPORTED 66 +#define VIDC_1080P_ERROR_ALLOC_DPB_SIZE_NOT_SUFFICIENT 71 +#define VIDC_1080P_ERROR_NUM_DPB_OUT_OF_RANGE 74 +#define VIDC_1080P_ERROR_NULL_METADATA_INPUT_POINTER 77 +#define VIDC_1080P_ERROR_NULL_DPB_POINTER 78 +#define VIDC_1080P_ERROR_NULL_OTH_EXT_BUFADDR 79 +#define VIDC_1080P_ERROR_NULL_MV_POINTER 80 +#define VIDC_1080P_ERROR_DIVIDE_BY_ZERO 81 +#define VIDC_1080P_ERROR_BIT_STREAM_BUF_EXHAUST 82 +#define VIDC_1080P_ERROR_DESCRIPTOR_BUFFER_EMPTY 83 +#define VIDC_1080P_ERROR_DMA_TX_NOT_COMPLETE 84 +#define VIDC_1080P_ERROR_DESCRIPTOR_TABLE_ENTRY_INVALID 85 +#define VIDC_1080P_ERROR_MB_COEFF_NOT_DONE 86 +#define VIDC_1080P_ERROR_CODEC_SLICE_NOT_DONE 87 +#define VIDC_1080P_ERROR_VIDC_CORE_TIME_OUT 88 +#define VIDC_1080P_ERROR_VC1_BITPLANE_DECODE_ERR 89 +#define VIDC_1080P_ERROR_VSP_NOT_READY 90 +#define VIDC_1080P_ERROR_BUFFER_FULL_STATE 91 + +#define VIDC_1080P_ERROR_RESOLUTION_MISMATCH 112 +#define VIDC_1080P_ERROR_NV_QUANT_ERR 113 +#define VIDC_1080P_ERROR_SYNC_MARKER_ERR 114 +#define VIDC_1080P_ERROR_FEATURE_NOT_SUPPORTED 115 +#define VIDC_1080P_ERROR_MEM_CORRUPTION 116 +#define VIDC_1080P_ERROR_INVALID_REFERENCE_FRAME 117 +#define VIDC_1080P_ERROR_PICTURE_CODING_TYPE_ERR 118 +#define VIDC_1080P_ERROR_MV_RANGE_ERR 119 +#define VIDC_1080P_ERROR_PICTURE_STRUCTURE_ERR 120 +#define VIDC_1080P_ERROR_SLICE_ADDR_INVALID 121 +#define VIDC_1080P_ERROR_NON_PAIRED_FIELD_NOT_SUPPORTED 122 +#define VIDC_1080P_ERROR_NON_FRAME_DATA_RECEIVED 123 +#define VIDC_1080P_ERROR_INCOMPLETE_FRAME 124 +#define VIDC_1080P_ERROR_NO_BUFFER_RELEASED_FROM_HOST 125 +#define VIDC_1080P_ERROR_NULL_FW_DEBUG_INFO_POINTER 126 +#define VIDC_1080P_ERROR_ALLOC_DEBUG_INFO_SIZE_INSUFFICIENT 127 +#define VIDC_1080P_ERROR_NALU_HEADER_ERROR 128 +#define VIDC_1080P_ERROR_SPS_PARSE_ERROR 129 +#define VIDC_1080P_ERROR_PPS_PARSE_ERROR 130 +#define VIDC_1080P_ERROR_SLICE_PARSE_ERROR 131 +#define VIDC_1080P_ERROR_SYNC_POINT_NOT_RECEIVED 171 + +#define VIDC_1080P_WARN_COMMAND_FLUSHED 145 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_NUM_CONCEAL_MB 150 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_QP 151 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_CONCEAL_MB 152 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_VC1_PARAM 153 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_SEI 154 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_VUI 155 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_EXTRA 156 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_DATA_NONE 157 +#define VIDC_1080P_WARN_FRAME_RATE_UNKNOWN 158 +#define VIDC_1080P_WARN_ASPECT_RATIO_UNKNOWN 159 +#define VIDC_1080P_WARN_COLOR_PRIMARIES_UNKNOWN 160 +#define VIDC_1080P_WARN_TRANSFER_CHAR_UNKNOWN 161 +#define VIDC_1080P_WARN_MATRIX_COEFF_UNKNOWN 162 +#define VIDC_1080P_WARN_NON_SEQ_SLICE_ADDR 163 +#define VIDC_1080P_WARN_BROKEN_LINK 164 +#define VIDC_1080P_WARN_FRAME_CONCEALED 165 +#define VIDC_1080P_WARN_PROFILE_UNKNOWN 166 +#define VIDC_1080P_WARN_LEVEL_UNKNOWN 167 +#define VIDC_1080P_WARN_BIT_RATE_NOT_SUPPORTED 168 +#define VIDC_1080P_WARN_COLOR_DIFF_FORMAT_NOT_SUPPORTED 169 +#define VIDC_1080P_WARN_NULL_EXTRA_METADATA_POINTER 170 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_MB_INFO 180 +#define VIDC_1080P_WARN_METADATA_NO_SPACE_SLICE_SIZE 181 +#define VIDC_1080P_WARN_RESOLUTION_WARNING 182 + +#define VIDC_1080P_H264_ENC_TYPE_P 0 +#define VIDC_1080P_H264_ENC_TYPE_B 1 +#define VIDC_1080P_H264_ENC_TYPE_IDR 2 +#define VIDC_1080P_MP4_H263_ENC_TYPE_I 0 +#define VIDC_1080P_MP4_H263_ENC_TYPE_P 1 +#define VIDC_1080P_MP4_H263_ENC_TYPE_B 2 + +#define VIDC_1080P_MPEG4_LEVEL0 0 +#define VIDC_1080P_MPEG4_LEVEL0b 9 +#define VIDC_1080P_MPEG4_LEVEL1 1 +#define VIDC_1080P_MPEG4_LEVEL2 2 +#define VIDC_1080P_MPEG4_LEVEL3 3 +#define VIDC_1080P_MPEG4_LEVEL3b 7 +#define VIDC_1080P_MPEG4_LEVEL4 4 +#define VIDC_1080P_MPEG4_LEVEL4a 4 +#define VIDC_1080P_MPEG4_LEVEL5 5 +#define VIDC_1080P_MPEG4_LEVEL6 6 +#define VIDC_1080P_MPEG4_LEVEL7 7 + +#define VIDC_1080P_H264_LEVEL1 10 +#define VIDC_1080P_H264_LEVEL1b 9 +#define VIDC_1080P_H264_LEVEL1p1 11 +#define VIDC_1080P_H264_LEVEL1p2 12 +#define VIDC_1080P_H264_LEVEL1p3 13 +#define VIDC_1080P_H264_LEVEL2 20 +#define VIDC_1080P_H264_LEVEL2p1 21 +#define VIDC_1080P_H264_LEVEL2p2 22 +#define VIDC_1080P_H264_LEVEL3 30 +#define VIDC_1080P_H264_LEVEL3p1 31 +#define VIDC_1080P_H264_LEVEL3p2 32 +#define VIDC_1080P_H264_LEVEL4 40 +#define VIDC_1080P_H264_LEVEL5p1 51 +#define VIDC_1080P_H264_LEVEL_MAX VIDC_1080P_H264_LEVEL5p1 + +#define VIDC_1080P_H263_LEVEL10 10 +#define VIDC_1080P_H263_LEVEL20 20 +#define VIDC_1080P_H263_LEVEL30 30 +#define VIDC_1080P_H263_LEVEL40 40 +#define VIDC_1080P_H263_LEVEL45 45 +#define VIDC_1080P_H263_LEVEL50 50 +#define VIDC_1080P_H263_LEVEL60 60 +#define VIDC_1080P_H263_LEVEL70 70 + +#define VIDC_1080P_BUS_ERROR_HANDLER 0x01 +#define VIDC_1080P_ILLEVIDC_INSTRUCTION_HANDLER 0x02 +#define VIDC_1080P_TICK_HANDLER 0x04 +#define VIDC_1080P_TRAP_HANDLER 0x10 +#define VIDC_1080P_ALIGN_HANDLER 0x20 +#define VIDC_1080P_RANGE_HANDLER 0x40 +#define VIDC_1080P_DTLB_MISS_EXCEPTION_HANDLER 0x80 +#define VIDC_1080P_ITLB_MISS_EXCEPTION_HANDLER 0x100 +#define VIDC_1080P_DATA_PAGE_FAULT_EXCEPTION_HANDLER 0x200 +#define VIDC_1080P_INST_PAGE_FAULT_EXCEPTION_HANDLER 0x400 + +enum vidc_1080p_reset{ + VIDC_1080P_RESET_IN_SEQ_FIRST_STAGE = 0x0, + VIDC_1080P_RESET_IN_SEQ_SECOND_STAGE = 0x1, +}; +enum vidc_1080p_memory_access_method{ + VIDC_1080P_TILE_LINEAR = 0, + VIDC_1080P_TILE_16x16 = 2, + VIDC_1080P_TILE_64x32 = 3, + VIDC_1080P_TILE_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_host2risc_cmd{ + VIDC_1080P_HOST2RISC_CMD_EMPTY = 0, + VIDC_1080P_HOST2RISC_CMD_OPEN_CH = 1, + VIDC_1080P_HOST2RISC_CMD_CLOSE_CH = 2, + VIDC_1080P_HOST2RISC_CMD_SYS_INIT = 3, + VIDC_1080P_HOST2RISC_CMD_FLUSH_COMMMAND = 4, + VIDC_1080P_HOST2RISC_CMD_CONTINUE_ENC = 7, + VIDC_1080P_HOST2RISC_CMD_ABORT_ENC = 8, + VIDC_1080P_HOST2RISC_CMD_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_decode_p_cache_enable{ + VIDC_1080P_DECODE_PCACHE_ENABLE_P = 0, + VIDC_1080P_DECODE_PCACHE_ENABLE_B = 1, + VIDC_1080P_DECODE_PCACHE_ENABLE_PB = 2, + VIDC_1080P_DECODE_PCACHE_DISABLE = 3, + VIDC_1080P_DECODE_PCACHE_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_encode_p_cache_enable{ + VIDC_1080P_ENCODE_PCACHE_ENABLE = 0, + VIDC_1080P_ENCODE_PCACHE_DISABLE = 3, + VIDC_1080P_ENCODE_PCACHE_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_codec{ + VIDC_1080P_H264_DECODE = 0, + VIDC_1080P_VC1_DECODE = 1, + VIDC_1080P_MPEG4_DECODE = 2, + VIDC_1080P_MPEG2_DECODE = 3, + VIDC_1080P_H263_DECODE = 4, + VIDC_1080P_VC1_RCV_DECODE = 5, + VIDC_1080P_DIVX311_DECODE = 6, + VIDC_1080P_DIVX412_DECODE = 7, + VIDC_1080P_DIVX502_DECODE = 8, + VIDC_1080P_DIVX503_DECODE = 9, + VIDC_1080P_H264_ENCODE = 16, + VIDC_1080P_MPEG4_ENCODE = 17, + VIDC_1080P_H263_ENCODE = 18, + VIDC_1080P_CODEC_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_entropy_sel{ + VIDC_1080P_ENTROPY_SEL_CAVLC = 0, + VIDC_1080P_ENTROPY_SEL_CABAC = 1, + VIDC_1080P_ENTROPY_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_DBConfig{ + VIDC_1080P_DB_ALL_BLOCKING_BOUNDARY = 0, + VIDC_1080P_DB_DISABLE = 1, + VIDC_1080P_DB_SKIP_SLICE_BOUNDARY = 2, + VIDC_1080P_DB_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_MSlice_selection{ + VIDC_1080P_MSLICE_DISABLE = 0, + VIDC_1080P_MSLICE_BY_MB_COUNT = 1, + VIDC_1080P_MSLICE_BY_BYTE_COUNT = 3, + VIDC_1080P_MSLICE_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_display_status{ + VIDC_1080P_DISPLAY_STATUS_DECODE_ONLY = 0, + VIDC_1080P_DISPLAY_STATUS_DECODE_AND_DISPLAY = 1, + VIDC_1080P_DISPLAY_STATUS_DISPLAY_ONLY = 2, + VIDC_1080P_DISPLAY_STATUS_DPB_EMPTY = 3, + VIDC_1080P_DISPLAY_STATUS_NOOP = 4, + VIDC_1080P_DISPLAY_STATUS_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_display_coding{ + VIDC_1080P_DISPLAY_CODING_PROGRESSIVE_SCAN = 0, + VIDC_1080P_DISPLAY_CODING_INTERLACED = 1, + VIDC_1080P_DISPLAY_CODING_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_decode_frame{ + VIDC_1080P_DECODE_FRAMETYPE_NOT_CODED = 0, + VIDC_1080P_DECODE_FRAMETYPE_I = 1, + VIDC_1080P_DECODE_FRAMETYPE_P = 2, + VIDC_1080P_DECODE_FRAMETYPE_B = 3, + VIDC_1080P_DECODE_FRAMETYPE_OTHERS = 4, + VIDC_1080P_DECODE_FRAMETYPE_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_encode_frame{ + VIDC_1080P_ENCODE_FRAMETYPE_NOT_CODED = 0, + VIDC_1080P_ENCODE_FRAMETYPE_I = 1, + VIDC_1080P_ENCODE_FRAMETYPE_P = 2, + VIDC_1080P_ENCODE_FRAMETYPE_B = 3, + VIDC_1080P_ENCODE_FRAMETYPE_SKIPPED = 4, + VIDC_1080P_ENCODE_FRAMETYPE_OTHERS = 5, + VIDC_1080P_ENCODE_FRAMETYPE_32BIT = 0x7FFFFFFF + +}; + +enum vidc_1080p_decode_idc_format { + VIDC_1080P_IDCFORMAT_MONOCHROME = 0, + VIDC_1080P_IDCFORMAT_420 = 1, + VIDC_1080P_IDCFORMAT_422 = 2, + VIDC_1080P_IDCFORMAT_444 = 3, + VIDC_1080P_IDCFORMAT_OTHERS = 4, + VIDC_1080P_IDCFORMAT_32BIT = 0x7FFFFFFF +}; + +#define VIDC_1080P_PROFILE_MPEG4_SIMPLE 0x00000000 +#define VIDC_1080P_PROFILE_MPEG4_ADV_SIMPLE 0x00000001 + +#define VIDC_1080P_PROFILE_H264_MAIN 0x00000000 +#define VIDC_1080P_PROFILE_H264_HIGH 0x00000001 +#define VIDC_1080P_PROFILE_H264_BASELINE 0x00000002 + + +enum vidc_1080p_decode{ + VIDC_1080P_DEC_TYPE_SEQ_HEADER = 0x00010000, + VIDC_1080P_DEC_TYPE_FRAME_DATA = 0x00020000, + VIDC_1080P_DEC_TYPE_LAST_FRAME_DATA = 0x00030000, + VIDC_1080P_DEC_TYPE_INIT_BUFFERS = 0x00040000, + VIDC_1080P_DEC_TYPE_FRAME_START_REALLOC = 0x00050000, + VIDC_1080P_DEC_TYPE_32BIT = 0x7FFFFFFF +}; +enum vidc_1080p_encode{ + VIDC_1080P_ENC_TYPE_SEQ_HEADER = 0x00010000, + VIDC_1080P_ENC_TYPE_FRAME_DATA = 0x00020000, + VIDC_1080P_ENC_TYPE_LAST_FRAME_DATA = 0x00030000, + VIDC_1080P_ENC_TYPE_32BIT = 0x7FFFFFFF +}; +struct vidc_1080p_dec_seq_start_param{ + u32 cmd_seq_num; + u32 inst_id; + u32 shared_mem_addr_offset; + u32 stream_buffer_addr_offset; + u32 stream_buffersize; + u32 stream_frame_size; + u32 descriptor_buffer_addr_offset; + u32 descriptor_buffer_size; +}; +struct vidc_1080p_dec_frame_start_param{ + u32 cmd_seq_num; + u32 inst_id; + u32 shared_mem_addr_offset; + u32 stream_buffer_addr_offset; + u32 stream_buffersize; + u32 stream_frame_size; + u32 descriptor_buffer_addr_offset; + u32 descriptor_buffer_size; + u32 release_dpb_bit_mask; + u32 dpb_count; + u32 dpb_flush; + enum vidc_1080p_decode decode; +}; +struct vidc_1080p_dec_init_buffers_param{ + u32 cmd_seq_num; + u32 inst_id; + u32 shared_mem_addr_offset; + u32 dpb_count; +}; +struct vidc_1080p_seq_hdr_info{ + u32 img_size_x; + u32 img_size_y; + u32 dec_frm_size; + u32 min_num_dpb; + u32 min_luma_dpb_size; + u32 min_chroma_dpb_size; + u32 profile; + u32 level; + u32 disp_progressive; + u32 disp_crop_exists; + u32 dec_progressive; + u32 dec_crop_exists; + u32 crop_right_offset; + u32 crop_left_offset; + u32 crop_bottom_offset; + u32 crop_top_offset; + u32 data_partition; +}; +struct vidc_1080p_enc_seq_start_param{ + u32 cmd_seq_num; + u32 inst_id; + u32 shared_mem_addr_offset; + u32 stream_buffer_addr_offset; + u32 stream_buffer_size; +}; +struct vidc_1080p_enc_frame_start_param{ + u32 cmd_seq_num; + u32 inst_id; + u32 shared_mem_addr_offset; + u32 current_y_addr_offset; + u32 current_c_addr_offset; + u32 stream_buffer_addr_offset; + u32 stream_buffer_size; + u32 intra_frame; + u32 input_flush; + enum vidc_1080p_encode encode; +}; +struct vidc_1080p_enc_frame_info{ + u32 enc_frame_size; + u32 enc_picture_count; + u32 enc_write_pointer; + u32 enc_luma_address; + u32 enc_chroma_address; + enum vidc_1080p_encode_frame enc_frame; + u32 meta_data_exists; +}; +struct vidc_1080p_dec_disp_info{ + u32 disp_resl_change; + u32 dec_resl_change; + u32 reconfig_flush_done; + u32 img_size_x; + u32 img_size_y; + u32 display_y_addr; + u32 display_c_addr; + u32 decode_y_addr; + u32 decode_c_addr; + u32 tag_top; + u32 pic_time_top; + u32 tag_bottom; + u32 pic_time_bottom; + u32 metadata_exists; + u32 disp_crop_exists; + u32 dec_crop_exists; + u32 crop_right_offset; + u32 crop_left_offset; + u32 crop_bottom_offset; + u32 crop_top_offset; + u32 input_bytes_consumed; + u32 input_is_interlace; + u32 input_frame_num; + enum vidc_1080p_display_status display_status; + enum vidc_1080p_display_status decode_status; + enum vidc_1080p_display_coding display_coding; + enum vidc_1080p_display_coding decode_coding; + enum vidc_1080p_decode_frame input_frame; +}; +void vidc_1080p_do_sw_reset(enum vidc_1080p_reset init_flag); +void vidc_1080p_release_sw_reset(void); +void vidc_1080p_clear_interrupt(void); +void vidc_1080p_set_host2risc_cmd( + enum vidc_1080p_host2risc_cmd host2risc_command, + u32 host2risc_arg1, u32 host2risc_arg2, + u32 host2risc_arg3, u32 host2risc_arg4); +void vidc_1080p_get_risc2host_cmd(u32 *pn_risc2host_command, + u32 *pn_risc2host_arg1, u32 *pn_risc2host_arg2, + u32 *pn_risc2host_arg3, u32 *pn_risc2host_arg4); +void vidc_1080p_get_risc2host_cmd_status(u32 err_status, + u32 *dec_err_status, u32 *disp_err_status); +void vidc_1080p_clear_risc2host_cmd(void); +void vidc_1080p_get_fw_version(u32 *pn_fw_version); +void vidc_1080p_get_fw_status(u32 *pn_fw_status); +void vidc_1080p_init_memory_controller(u32 dram_base_addr_a, + u32 dram_base_addr_b); +void vidc_1080p_get_memory_controller_status(u32 *pb_mc_abusy, + u32 *pb_mc_bbusy); +void vidc_1080p_set_h264_decode_buffers(u32 dpb, u32 dec_vert_nb_mv_offset, + u32 dec_nb_ip_offset, u32 *pn_dpb_luma_offset, + u32 *pn_dpb_chroma_offset, u32 *pn_mv_buffer_offset); +void vidc_1080p_set_decode_recon_buffers(u32 recon_buffer, u32 *pn_dec_luma, + u32 *pn_dec_chroma); +void vidc_1080p_set_mpeg4_divx_decode_work_buffers(u32 nb_dcac_buffer_offset, + u32 upnb_mv_buffer_offset, u32 sub_anchor_buffer_offset, + u32 overlay_transform_buffer_offset, u32 stx_parser_buffer_offset); +void vidc_1080p_set_h263_decode_work_buffers(u32 nb_dcac_buffer_offset, + u32 upnb_mv_buffer_offset, u32 sub_anchor_buffer_offset, + u32 overlay_transform_buffer_offset); +void vidc_1080p_set_vc1_decode_work_buffers(u32 nb_dcac_buffer_offset, + u32 upnb_mv_buffer_offset, u32 sub_anchor_buffer_offset, + u32 overlay_transform_buffer_offset, u32 bitplain1Buffer_offset, + u32 bitplain2Buffer_offset, u32 bitplain3Buffer_offset); +void vidc_1080p_set_encode_recon_buffers(u32 recon_buffer, u32 *pn_enc_luma, + u32 *pn_enc_chroma); +void vidc_1080p_set_h264_encode_work_buffers(u32 up_row_mv_buffer_offset, + u32 direct_colzero_flag_buffer_offset, + u32 upper_intra_md_buffer_offset, + u32 upper_intra_pred_buffer_offset, u32 nbor_infor_buffer_offset, + u32 mb_info_offset); +void vidc_1080p_set_h263_encode_work_buffers(u32 up_row_mv_buffer_offset, + u32 up_row_inv_quanti_coeff_buffer_offset); +void vidc_1080p_set_mpeg4_encode_work_buffers(u32 skip_flag_buffer_offset, + u32 up_row_inv_quanti_coeff_buffer_offset, u32 upper_mv_offset); +void vidc_1080p_set_encode_frame_size(u32 hori_size, u32 vert_size); +void vidc_1080p_set_encode_profile_level(u32 encode_profile, u32 enc_level); +void vidc_1080p_set_encode_field_picture_structure(u32 enc_field_picture); +void vidc_1080p_set_decode_mpeg4_pp_filter(u32 lf_enables); +void vidc_1080p_set_decode_qp_save_control(u32 enable_q_pout); +void vidc_1080p_get_returned_channel_inst_id(u32 *pn_rtn_chid); +void vidc_1080p_clear_returned_channel_inst_id(void); +void vidc_1080p_get_decode_seq_start_result( + struct vidc_1080p_seq_hdr_info *seq_hdr_info); +void vidc_1080p_get_decoded_frame_size(u32 *pn_decoded_size); +void vidc_1080p_get_display_frame_result( + struct vidc_1080p_dec_disp_info *dec_disp_info); +void vidc_1080p_get_decode_frame( + enum vidc_1080p_decode_frame *pe_frame); +void vidc_1080p_get_decode_frame_result( + struct vidc_1080p_dec_disp_info *dec_disp_info); +void vidc_1080p_decode_seq_start_ch0( + struct vidc_1080p_dec_seq_start_param *param); +void vidc_1080p_decode_seq_start_ch1( + struct vidc_1080p_dec_seq_start_param *param); +void vidc_1080p_decode_init_buffers_ch0 + (struct vidc_1080p_dec_init_buffers_param *param); +void vidc_1080p_decode_init_buffers_ch1( + struct vidc_1080p_dec_init_buffers_param *param); +void vidc_1080p_decode_frame_start_ch0( + struct vidc_1080p_dec_frame_start_param *param); +void vidc_1080p_decode_frame_start_ch1( + struct vidc_1080p_dec_frame_start_param *param); +void vidc_1080p_set_dec_resolution_ch0(u32 width, u32 height); +void vidc_1080p_set_dec_resolution_ch1(u32 width, u32 height); +void vidc_1080p_get_encode_frame_info( + struct vidc_1080p_enc_frame_info *frame_info); +void vidc_1080p_encode_seq_start_ch0( + struct vidc_1080p_enc_seq_start_param *param); +void vidc_1080p_encode_seq_start_ch1( + struct vidc_1080p_enc_seq_start_param *param); +void vidc_1080p_encode_frame_start_ch0( + struct vidc_1080p_enc_frame_start_param *param); +void vidc_1080p_encode_frame_start_ch1( + struct vidc_1080p_enc_frame_start_param *param); +void vidc_1080p_set_encode_picture(u32 ifrm_ctrl, u32 number_b); +void vidc_1080p_set_encode_multi_slice_control( + enum vidc_1080p_MSlice_selection multiple_slice_selection, + u32 mslice_mb, u32 mslice_byte); +void vidc_1080p_set_encode_circular_intra_refresh(u32 cir_num); +void vidc_1080p_set_encode_input_frame_format( + enum vidc_1080p_memory_access_method memory_format); +void vidc_1080p_set_encode_padding_control(u32 pad_ctrl_on, + u32 cr_pad_val, u32 cb_pad_val, u32 luma_pad_val); +void vidc_1080p_encode_set_rc_config(u32 enable_frame_level_rc, + u32 enable_mb_level_rc_flag, u32 frame_qp); +void vidc_1080p_encode_set_frame_level_rc_params(u32 rc_frame_rate, + u32 target_bitrate, u32 reaction_coeff); +void vidc_1080p_encode_set_qp_params(u32 max_qp, u32 min_qp); +void vidc_1080p_encode_set_mb_level_rc_params(u32 disable_dark_region_as_flag, + u32 disable_smooth_region_as_flag , u32 disable_static_region_as_flag, + u32 disable_activity_region_flag); +void vidc_1080p_get_qp(u32 *pn_frame_qp); +void vidc_1080p_set_h264_encode_entropy( + enum vidc_1080p_entropy_sel entropy_sel); +void vidc_1080p_set_h264_encode_loop_filter( + enum vidc_1080p_DBConfig db_config, u32 slice_alpha_offset, + u32 slice_beta_offset); +void vidc_1080p_set_h264_encoder_p_frame_ref_count(u32 max_reference); +void vidc_1080p_set_h264_encode_8x8transform_control(u32 enable_8x8transform); +void vidc_1080p_set_mpeg4_encode_quarter_pel_control( + u32 enable_mpeg4_quarter_pel); +void vidc_1080p_set_device_base_addr(u8 *mapped_va); +void vidc_1080p_get_intra_bias(u32 *intra_bias); +void vidc_1080p_set_intra_bias(u32 intra_bias); +void vidc_1080p_get_bi_directional_bias(u32 *bi_directional_bias); +void vidc_1080p_set_bi_directional_bias(u32 bi_directional_bias); +void vidc_1080p_get_encoder_sequence_header_size(u32 *seq_header_size); +void vidc_1080p_get_intermedia_stage_debug_counter( + u32 *intermediate_stage_counter); +void vidc_1080p_get_exception_status(u32 *exception_status); +void vidc_1080p_frame_start_realloc(u32 instance_id); +#endif diff --git a/drivers/video/msm/vidc/1080p/ddl/vidc_hwio.h b/drivers/video/msm/vidc/1080p/ddl/vidc_hwio.h new file mode 100644 index 00000000000..f63ebcd2d7d --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vidc_hwio.h @@ -0,0 +1,115 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIDC_HWIO_H_ +#define _VIDC_HWIO_H_ + +#include "vidc_hwio_reg.h" + +#ifdef VIDC_REGISTER_LOG +#define VIDC_REG_OUT(x...) printk(KERN_DEBUG x) +#define VIDC_REG_IN(x...) printk(KERN_DEBUG x) +#else +#define VIDC_REG_OUT(x...) +#define VIDC_REG_IN(x...) +#endif + +#define __inpdw(port) (*((u32 *) (port))) +#define __outpdw(port, val) (*((u32 *) (port)) = ((u32) (val))) + +#define in_dword(addr) (__inpdw(addr)) +#define in_dword_masked(addr, mask) (__inpdw(addr) & (mask)) +#define out_dword(addr, val) __outpdw(addr, val) + +#define out_dword_masked(io, mask, val, shadow) \ +do { \ + shadow = (shadow & (u32)(~(mask))) | ((u32)((val) & (mask))); \ + out_dword(io, shadow); \ +} while (0) +#define out_dword_masked_ns(io, mask, val, current_reg_content) \ + out_dword(io, ((current_reg_content & (u32)(~(mask))) | \ + ((u32)((val) & (mask))))) + +#define HWIO_IN(hwiosym) HWIO_##hwiosym##_IN +#define HWIO_INI(hwiosym, index) HWIO_##hwiosym##_INI(index) +#define HWIO_INM(hwiosym, mask) HWIO_##hwiosym##_INM(mask) +#define HWIO_INF(hwiosym, field) (HWIO_INM(hwiosym, \ + HWIO_FMSK(hwiosym, field)) >> HWIO_SHFT(hwiosym, field)) + +#define HWIO_OUT(hwiosym, val) HWIO_##hwiosym##_OUT(val) +#define HWIO_OUTI(hwiosym, index, val) HWIO_##hwiosym##_OUTI(index, val) +#define HWIO_OUTM(hwiosym, mask, val) HWIO_##hwiosym##_OUTM(mask, val) +#define HWIO_OUTF(hwiosym, field, val) HWIO_OUTM(hwiosym, \ + HWIO_FMSK(hwiosym, field), (u32)(val) << HWIO_SHFT(hwiosym, field)) + +#define HWIO_SHFT(hwio_regsym, hwio_fldsym) \ + HWIO_##hwiosym##_##hwiofldsym##_SHFT +#define HWIO_FMSK(hwio_regsym, hwio_fldsym) \ + HWIO_##hwiosym##_##hwiofldsym##_BMSK + +#define VIDC_SETFIELD(val, shift, mask) \ + (((val) << (shift)) & (mask)) +#define VIDC_GETFIELD(val, mask, shift) \ + (((val) & (mask)) >> (shift)) + +#define VIDC_HWIO_OUT(hwiosym, val) \ +do { \ + VIDC_REG_OUT("\n(0x%x:"#hwiosym"=0x%x)", \ + HWIO_##hwiosym##_ADDR - VIDC_BASE_PTR, val); \ + mb(); \ + HWIO_OUT(hwiosym, val); \ +} while (0) +#define VIDC_HWIO_OUTI(hwiosym, index, val) \ +do { \ + VIDC_REG_OUT("\n(0x%x:"#hwiosym"(%d)=0x%x)", \ + HWIO_##hwiosym##_ADDR(index) - VIDC_BASE_PTR, index, val); \ + mb(); \ + HWIO_OUTI(hwiosym, index, val); \ +} while (0) +#define VIDC_HWIO_OUTF(hwiosym, field, val) \ +do { \ + VIDC_REG_OUT("\n(0x%x:"#hwiosym":0x%x:=0x%x)" , \ + HWIO_##hwiosym##_ADDR - VIDC_BASE_PTR, \ + HWIO_##hwiosym##_##field##_BMSK, val) \ + mb(); \ + HWIO_OUTF(hwiosym, field, val); \ +} while (0) +#define VIDC_OUT_DWORD(addr, val) \ +do { \ + VIDC_REG_OUT("\n(0x%x:"#addr"=0x%x)", \ + addr - VIDC_BASE_PTR, val); \ + mb(); \ + out_dword(addr, val); \ +} while (0) +#define VIDC_HWIO_IN(hwiosym, pval) \ +do { \ + mb(); \ + *pval = (u32) HWIO_IN(hwiosym); \ + VIDC_REG_IN("\n(0x%x:"#hwiosym"=0x%x)", \ + HWIO_##hwiosym##_ADDR - VIDC_BASE_PTR, *pval);\ +} while (0) +#define VIDC_HWIO_INI(hwiosym, index, pval) \ +do { \ + mb(); \ + *pval = (u32) HWIO_INI(hwiosym, index); \ + VIDC_REG_IN("(0x%x:"#hwiosym"(%d)==0x%x)", \ + HWIO_##hwiosym##_ADDR(index) - VIDC_BASE_PTR, index, *pval); \ +} while (0) +#define VIDC_HWIO_INF(hwiosym, mask, pval) \ +do { \ + mb(); \ + *pval = HWIO_INF(hwiosym, mask); \ + VIDC_REG_IN("\n(0x%x:"#hwiosym"=0x%x)", \ + HWIO_##hwiosym##_ADDR - VIDC_BASE_PTR, *pval); \ +} while (0) +#endif diff --git a/drivers/video/msm/vidc/1080p/ddl/vidc_hwio_reg.h b/drivers/video/msm/vidc/1080p/ddl/vidc_hwio_reg.h new file mode 100644 index 00000000000..819cd6c7847 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vidc_hwio_reg.h @@ -0,0 +1,4544 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIDC_HWIO_REG_H_ +#define _VIDC_HWIO_REG_H_ + +#include +#include +#include +#include "vidc.h" + +extern u8 *VIDC_BASE_PTR; + +#define VIDC_BASE VIDC_BASE_PTR + +#define VIDC_BLACKBIRD_REG_BASE (VIDC_BASE + 0x00000000) +#define VIDC_BLACKBIRD_REG_BASE_PHYS 0x04400000 + +#define HWIO_REG_557899_ADDR (VIDC_BLACKBIRD_REG_BASE + 00000000) +#define HWIO_REG_557899_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 00000000) +#define HWIO_REG_557899_RMSK 0x3ff +#define HWIO_REG_557899_SHFT 0 +#define HWIO_REG_557899_IN in_dword_masked(HWIO_REG_557899_ADDR,\ + HWIO_REG_557899_RMSK) +#define HWIO_REG_557899_INM(m) in_dword_masked(HWIO_REG_557899_ADDR, m) +#define HWIO_REG_557899_OUT(v) out_dword(HWIO_REG_557899_ADDR, v) +#define HWIO_REG_557899_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_557899_ADDR, m, v, HWIO_REG_557899_IN); +#define HWIO_REG_557899_RSTN_RG_MPEG2_BMSK 0x200 +#define HWIO_REG_557899_RSTN_RG_MPEG2_SHFT 0x9 +#define HWIO_REG_557899_RSTN_RG_MPEG4_BMSK 0x100 +#define HWIO_REG_557899_RSTN_RG_MPEG4_SHFT 0x8 +#define HWIO_REG_557899_RSTN_RG_VC1_BMSK 0x80 +#define HWIO_REG_557899_RSTN_RG_VC1_SHFT 0x7 +#define HWIO_REG_557899_RSTN_RG_H264_BMSK 0x40 +#define HWIO_REG_557899_RSTN_RG_H264_SHFT 0x6 +#define HWIO_REG_557899_RSTN_RG_COMMON_BMSK 0x20 +#define HWIO_REG_557899_RSTN_RG_COMMON_SHFT 0x5 +#define HWIO_REG_557899_RSTN_DMX_BMSK 0x10 +#define HWIO_REG_557899_RSTN_DMX_SHFT 0x4 +#define HWIO_REG_557899_RSTN_VI_BMSK 0x8 +#define HWIO_REG_557899_RSTN_VI_SHFT 0x3 +#define HWIO_REG_557899_RSTN_VIDCCORE_BMSK 0x4 +#define HWIO_REG_557899_RSTN_VIDCCORE_SHFT 0x2 +#define HWIO_REG_557899_RSTN_MC_BMSK 0x2 +#define HWIO_REG_557899_RSTN_MC_SHFT 0x1 +#define HWIO_REG_557899_RSTN_RISC_BMSK 0x1 +#define HWIO_REG_557899_RSTN_RISC_SHFT 0 + +#define HWIO_REG_575377_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000008) +#define HWIO_REG_575377_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000008) +#define HWIO_REG_575377_RMSK 0x1 +#define HWIO_REG_575377_SHFT 0 +#define HWIO_REG_575377_IN in_dword_masked(\ + HWIO_REG_575377_ADDR, HWIO_REG_575377_RMSK) +#define HWIO_REG_575377_INM(m) \ + in_dword_masked(HWIO_REG_575377_ADDR, m) +#define HWIO_REG_575377_OUT(v) \ + out_dword(HWIO_REG_575377_ADDR, v) +#define HWIO_REG_575377_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_575377_ADDR, m, v, HWIO_REG_575377_IN); +#define HWIO_REG_575377_INTERRUPT_BMSK 0x1 +#define HWIO_REG_575377_INTERRUPT_SHFT 0 + +#define HWIO_REG_611794_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000030) +#define HWIO_REG_611794_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000030) +#define HWIO_REG_611794_RMSK 0xffffffff +#define HWIO_REG_611794_SHFT 0 +#define HWIO_REG_611794_IN in_dword_masked(\ + HWIO_REG_611794_ADDR, HWIO_REG_611794_RMSK) +#define HWIO_REG_611794_INM(m) \ + in_dword_masked(HWIO_REG_611794_ADDR, m) +#define HWIO_REG_611794_OUT(v) \ + out_dword(HWIO_REG_611794_ADDR, v) +#define HWIO_REG_611794_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_611794_ADDR, m, v,\ + HWIO_REG_611794_IN); +#define HWIO_REG_611794_HOST2RISC_COMMAND_BMSK 0xffffffff +#define HWIO_REG_611794_HOST2RISC_COMMAND_SHFT 0 + +#define HWIO_REG_356340_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000034) +#define HWIO_REG_356340_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000034) +#define HWIO_REG_356340_RMSK 0xffffffff +#define HWIO_REG_356340_SHFT 0 +#define HWIO_REG_356340_IN in_dword_masked(\ + HWIO_REG_356340_ADDR, HWIO_REG_356340_RMSK) +#define HWIO_REG_356340_INM(m) \ + in_dword_masked(HWIO_REG_356340_ADDR, m) +#define HWIO_REG_356340_OUT(v) \ + out_dword(HWIO_REG_356340_ADDR, v) +#define HWIO_REG_356340_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_356340_ADDR, m, v, HWIO_REG_356340_IN); +#define HWIO_REG_356340_HOST2RISC_ARG1_BMSK 0xffffffff +#define HWIO_REG_356340_HOST2RISC_ARG1_SHFT 0 + +#define HWIO_REG_899023_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000038) +#define HWIO_REG_899023_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000038) +#define HWIO_REG_899023_RMSK 0xffffffff +#define HWIO_REG_899023_SHFT 0 +#define HWIO_REG_899023_IN in_dword_masked(\ + HWIO_REG_899023_ADDR, HWIO_REG_899023_RMSK) +#define HWIO_REG_899023_INM(m) \ + in_dword_masked(HWIO_REG_899023_ADDR, m) +#define HWIO_REG_899023_OUT(v) \ + out_dword(HWIO_REG_899023_ADDR, v) +#define HWIO_REG_899023_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_899023_ADDR, m, v, HWIO_REG_899023_IN); +#define HWIO_REG_899023_HOST2RISC_ARG2_BMSK 0xffffffff +#define HWIO_REG_899023_HOST2RISC_ARG2_SHFT 0 + +#define HWIO_REG_987762_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000003c) +#define HWIO_REG_987762_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000003c) +#define HWIO_REG_987762_RMSK 0xffffffff +#define HWIO_REG_987762_SHFT 0 +#define HWIO_REG_987762_IN in_dword_masked(\ + HWIO_REG_987762_ADDR, HWIO_REG_987762_RMSK) +#define HWIO_REG_987762_INM(m) \ + in_dword_masked(HWIO_REG_987762_ADDR, m) +#define HWIO_REG_987762_OUT(v) \ + out_dword(HWIO_REG_987762_ADDR, v) +#define HWIO_REG_987762_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_987762_ADDR, m, v, HWIO_REG_987762_IN); +#define HWIO_REG_987762_HOST2RISC_ARG3_BMSK 0xffffffff +#define HWIO_REG_987762_HOST2RISC_ARG3_SHFT 0 + +#define HWIO_REG_544000_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000040) +#define HWIO_REG_544000_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000040) +#define HWIO_REG_544000_RMSK 0xffffffff +#define HWIO_REG_544000_SHFT 0 +#define HWIO_REG_544000_IN in_dword_masked(\ + HWIO_REG_544000_ADDR, HWIO_REG_544000_RMSK) +#define HWIO_REG_544000_INM(m) \ + in_dword_masked(HWIO_REG_544000_ADDR, m) +#define HWIO_REG_544000_OUT(v) \ + out_dword(HWIO_REG_544000_ADDR, v) +#define HWIO_REG_544000_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_544000_ADDR, m, v, HWIO_REG_544000_IN); +#define HWIO_REG_544000_HOST2RISC_ARG4_BMSK 0xffffffff +#define HWIO_REG_544000_HOST2RISC_ARG4_SHFT 0 + +#define HWIO_REG_695082_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000044) +#define HWIO_REG_695082_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000044) +#define HWIO_REG_695082_RMSK 0xffffffff +#define HWIO_REG_695082_SHFT 0 +#define HWIO_REG_695082_IN in_dword_masked(\ + HWIO_REG_695082_ADDR, HWIO_REG_695082_RMSK) +#define HWIO_REG_695082_INM(m) \ + in_dword_masked(HWIO_REG_695082_ADDR, m) +#define HWIO_REG_695082_OUT(v) \ + out_dword(HWIO_REG_695082_ADDR, v) +#define HWIO_REG_695082_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_695082_ADDR, m, v, HWIO_REG_695082_IN); +#define HWIO_REG_695082_RISC2HOST_COMMAND_BMSK 0xffffffff +#define HWIO_REG_695082_RISC2HOST_COMMAND_SHFT 0 + +#define HWIO_REG_156596_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000048) +#define HWIO_REG_156596_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000048) +#define HWIO_REG_156596_RMSK 0xffffffff +#define HWIO_REG_156596_SHFT 0 +#define HWIO_REG_156596_IN in_dword_masked(\ + HWIO_REG_156596_ADDR, HWIO_REG_156596_RMSK) +#define HWIO_REG_156596_INM(m) \ + in_dword_masked(HWIO_REG_156596_ADDR, m) +#define HWIO_REG_156596_OUT(v) \ + out_dword(HWIO_REG_156596_ADDR, v) +#define HWIO_REG_156596_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_156596_ADDR, m, v, HWIO_REG_156596_IN); +#define HWIO_REG_156596_REG_156596_BMSK 0xffffffff +#define HWIO_REG_156596_REG_156596_SHFT 0 + +#define HWIO_REG_222292_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000004c) +#define HWIO_REG_222292_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000004c) +#define HWIO_REG_222292_RMSK 0xffffffff +#define HWIO_REG_222292_SHFT 0 +#define HWIO_REG_222292_IN in_dword_masked(\ + HWIO_REG_222292_ADDR, HWIO_REG_222292_RMSK) +#define HWIO_REG_222292_INM(m) \ + in_dword_masked(HWIO_REG_222292_ADDR, m) +#define HWIO_REG_222292_OUT(v) \ + out_dword(HWIO_REG_222292_ADDR, v) +#define HWIO_REG_222292_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_222292_ADDR, m, v, HWIO_REG_222292_IN); +#define HWIO_REG_222292_REG_222292_BMSK 0xffffffff +#define HWIO_REG_222292_REG_222292_SHFT 0 + +#define HWIO_REG_790962_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000050) +#define HWIO_REG_790962_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000050) +#define HWIO_REG_790962_RMSK 0xffffffff +#define HWIO_REG_790962_SHFT 0 +#define HWIO_REG_790962_IN in_dword_masked(\ + HWIO_REG_790962_ADDR, HWIO_REG_790962_RMSK) +#define HWIO_REG_790962_INM(m) \ + in_dword_masked(HWIO_REG_790962_ADDR, m) +#define HWIO_REG_790962_OUT(v) \ + out_dword(HWIO_REG_790962_ADDR, v) +#define HWIO_REG_790962_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_790962_ADDR, m, v, HWIO_REG_790962_IN); +#define HWIO_REG_790962_REG_790962_BMSK 0xffffffff +#define HWIO_REG_790962_REG_790962_SHFT 0 + +#define HWIO_REG_679882_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000054) +#define HWIO_REG_679882_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000054) +#define HWIO_REG_679882_RMSK 0xffffffff +#define HWIO_REG_679882_SHFT 0 +#define HWIO_REG_679882_IN in_dword_masked(\ + HWIO_REG_679882_ADDR, HWIO_REG_679882_RMSK) +#define HWIO_REG_679882_INM(m) \ + in_dword_masked(HWIO_REG_679882_ADDR, m) +#define HWIO_REG_679882_OUT(v) \ + out_dword(HWIO_REG_679882_ADDR, v) +#define HWIO_REG_679882_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_679882_ADDR, m, v, HWIO_REG_679882_IN); +#define HWIO_REG_679882_REG_679882_BMSK 0xffffffff +#define HWIO_REG_679882_REG_679882_SHFT 0 + +#define HWIO_REG_653206_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000058) +#define HWIO_REG_653206_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000058) +#define HWIO_REG_653206_RMSK 0xffffff +#define HWIO_REG_653206_SHFT 0 +#define HWIO_REG_653206_IN in_dword_masked(\ + HWIO_REG_653206_ADDR, HWIO_REG_653206_RMSK) +#define HWIO_REG_653206_INM(m) \ + in_dword_masked(HWIO_REG_653206_ADDR, m) +#define HWIO_REG_653206_YEAR_BMSK 0xff0000 +#define HWIO_REG_653206_YEAR_SHFT 0x10 +#define HWIO_REG_653206_MONTH_BMSK 0xff00 +#define HWIO_REG_653206_MONTH_SHFT 0x8 +#define HWIO_REG_653206_DAY_BMSK 0xff +#define HWIO_REG_653206_DAY_SHFT 0 + +#define HWIO_REG_805993_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000064) +#define HWIO_REG_805993_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000064) +#define HWIO_REG_805993_RMSK 0xffffffff +#define HWIO_REG_805993_SHFT 0 +#define HWIO_REG_805993_IN in_dword_masked(\ + HWIO_REG_805993_ADDR, HWIO_REG_805993_RMSK) +#define HWIO_REG_805993_INM(m) \ + in_dword_masked(HWIO_REG_805993_ADDR, m) +#define HWIO_REG_805993_INTERMEDIATE_STAGE_COUNTER_BMSK 0xffffffff +#define HWIO_REG_805993_INTERMEDIATE_STAGE_COUNTER_SHFT 0 + +#define HWIO_REG_493355_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000068) +#define HWIO_REG_493355_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000068) +#define HWIO_REG_493355_RMSK 0xffffffff +#define HWIO_REG_493355_SHFT 0 +#define HWIO_REG_493355_IN in_dword_masked(\ + HWIO_REG_493355_ADDR, HWIO_REG_493355_RMSK) +#define HWIO_REG_493355_INM(m) \ + in_dword_masked(HWIO_REG_493355_ADDR, m) +#define HWIO_REG_493355_EXCEPTION_STATUS_BMSK 0xffffffff +#define HWIO_REG_493355_EXCEPTION_STATUS_SHFT 0 + +#define HWIO_REG_350619_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000080) +#define HWIO_REG_350619_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000080) +#define HWIO_REG_350619_RMSK 0x1 +#define HWIO_REG_350619_SHFT 0 +#define HWIO_REG_350619_IN in_dword_masked(\ + HWIO_REG_350619_ADDR, HWIO_REG_350619_RMSK) +#define HWIO_REG_350619_INM(m) \ + in_dword_masked(HWIO_REG_350619_ADDR, m) +#define HWIO_REG_350619_FIRMWARE_STATUS_BMSK 0x1 +#define HWIO_REG_350619_FIRMWARE_STATUS_SHFT 0 + +#define HWIO_REG_64440_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000508) +#define HWIO_REG_64440_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000508) +#define HWIO_REG_64440_RMSK 0xfffe0000 +#define HWIO_REG_64440_SHFT 0 +#define HWIO_REG_64440_IN in_dword_masked(\ + HWIO_REG_64440_ADDR, HWIO_REG_64440_RMSK) +#define HWIO_REG_64440_INM(m) \ + in_dword_masked(HWIO_REG_64440_ADDR, m) +#define HWIO_REG_64440_OUT(v) \ + out_dword(HWIO_REG_64440_ADDR, v) +#define HWIO_REG_64440_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_64440_ADDR, m, v,\ + HWIO_REG_64440_IN); +#define HWIO_REG_64440_MC_DRAMBASE_ADDR_BMSK 0xfffe0000 +#define HWIO_REG_64440_MC_DRAMBASE_ADDR_SHFT 0x11 + +#define HWIO_REG_675915_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000050c) +#define HWIO_REG_675915_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000050c) +#define HWIO_REG_675915_RMSK 0xfffe0000 +#define HWIO_REG_675915_SHFT 0 +#define HWIO_REG_675915_IN in_dword_masked(\ + HWIO_REG_675915_ADDR, HWIO_REG_675915_RMSK) +#define HWIO_REG_675915_INM(m) \ + in_dword_masked(HWIO_REG_675915_ADDR, m) +#define HWIO_REG_675915_OUT(v) \ + out_dword(HWIO_REG_675915_ADDR, v) +#define HWIO_REG_675915_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_675915_ADDR, m, v,\ + HWIO_REG_675915_IN); +#define HWIO_REG_675915_MC_DRAMBASE_ADDR_BMSK 0xfffe0000 +#define HWIO_REG_675915_MC_DRAMBASE_ADDR_SHFT 0x11 + +#define HWIO_REG_399911_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000510) +#define HWIO_REG_399911_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000510) +#define HWIO_REG_399911_RMSK 0x3 +#define HWIO_REG_399911_SHFT 0 +#define HWIO_REG_399911_IN in_dword_masked(\ + HWIO_REG_399911_ADDR, HWIO_REG_399911_RMSK) +#define HWIO_REG_399911_INM(m) in_dword_masked(HWIO_REG_399911_ADDR, m) +#define HWIO_REG_399911_MC_BUSY_B_BMSK 0x2 +#define HWIO_REG_399911_MC_BUSY_B_SHFT 0x1 +#define HWIO_REG_399911_MC_BUSY_A_BMSK 0x1 +#define HWIO_REG_399911_MC_BUSY_A_SHFT 0 + +#define HWIO_REG_515200_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000600) +#define HWIO_REG_515200_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000600) +#define HWIO_REG_515200_RMSK 0x1ffff +#define HWIO_REG_515200_SHFT 0 +#define HWIO_REG_515200_IN in_dword_masked(\ + HWIO_REG_515200_ADDR, HWIO_REG_515200_RMSK) +#define HWIO_REG_515200_INM(m) \ + in_dword_masked(HWIO_REG_515200_ADDR, m) +#define HWIO_REG_515200_OUT(v) \ + out_dword(HWIO_REG_515200_ADDR, v) +#define HWIO_REG_515200_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_515200_ADDR, m, v,\ + HWIO_REG_515200_IN); +#define HWIO_REG_515200_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_515200_BASE_ADDR_SHFT 0 + +#define HWIO_REG_29510_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000604) +#define HWIO_REG_29510_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000604) +#define HWIO_REG_29510_RMSK 0x1ffff +#define HWIO_REG_29510_SHFT 0 +#define HWIO_REG_29510_IN in_dword_masked(\ + HWIO_REG_29510_ADDR, HWIO_REG_29510_RMSK) +#define HWIO_REG_29510_INM(m) \ + in_dword_masked(HWIO_REG_29510_ADDR, m) +#define HWIO_REG_29510_OUT(v) \ + out_dword(HWIO_REG_29510_ADDR, v) +#define HWIO_REG_29510_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_29510_ADDR, m, v,\ + HWIO_REG_29510_IN); +#define HWIO_REG_29510_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_29510_BASE_ADDR_SHFT 0 + +#define HWIO_REG_256132_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000608) +#define HWIO_REG_256132_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000608) +#define HWIO_REG_256132_RMSK 0x1ffff +#define HWIO_REG_256132_SHFT 0 +#define HWIO_REG_256132_IN in_dword_masked(\ + HWIO_REG_256132_ADDR, HWIO_REG_256132_RMSK) +#define HWIO_REG_256132_INM(m) \ + in_dword_masked(HWIO_REG_256132_ADDR, m) +#define HWIO_REG_256132_OUT(v) \ + out_dword(HWIO_REG_256132_ADDR, v) +#define HWIO_REG_256132_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_256132_ADDR, m, v,\ + HWIO_REG_256132_IN); +#define HWIO_REG_256132_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_256132_BASE_ADDR_SHFT 0 + +#define HWIO_REG_885152_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000060c) +#define HWIO_REG_885152_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000060c) +#define HWIO_REG_885152_RMSK 0x1ffff +#define HWIO_REG_885152_SHFT 0 +#define HWIO_REG_885152_IN in_dword_masked(\ + HWIO_REG_885152_ADDR, HWIO_REG_885152_RMSK) +#define HWIO_REG_885152_INM(m) \ + in_dword_masked(HWIO_REG_885152_ADDR, m) +#define HWIO_REG_885152_OUT(v) \ + out_dword(HWIO_REG_885152_ADDR, v) +#define HWIO_REG_885152_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_885152_ADDR, m, v,\ + HWIO_REG_885152_IN); +#define HWIO_REG_885152_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_885152_BASE_ADDR_SHFT 0 + +#define HWIO_REG_69832_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000610) +#define HWIO_REG_69832_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000610) +#define HWIO_REG_69832_RMSK 0x1ffff +#define HWIO_REG_69832_SHFT 0 +#define HWIO_REG_69832_IN in_dword_masked(\ + HWIO_REG_69832_ADDR, HWIO_REG_69832_RMSK) +#define HWIO_REG_69832_INM(m) \ + in_dword_masked(HWIO_REG_69832_ADDR, m) +#define HWIO_REG_69832_OUT(v) \ + out_dword(HWIO_REG_69832_ADDR, v) +#define HWIO_REG_69832_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_69832_ADDR, m, v,\ + HWIO_REG_69832_IN); +#define HWIO_REG_69832_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_69832_BASE_ADDR_SHFT 0 + +#define HWIO_REG_686205_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000614) +#define HWIO_REG_686205_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000614) +#define HWIO_REG_686205_RMSK 0x1ffff +#define HWIO_REG_686205_SHFT 0 +#define HWIO_REG_686205_IN in_dword_masked(\ + HWIO_REG_686205_ADDR, HWIO_REG_686205_RMSK) +#define HWIO_REG_686205_INM(m) \ + in_dword_masked(HWIO_REG_686205_ADDR, m) +#define HWIO_REG_686205_OUT(v) \ + out_dword(HWIO_REG_686205_ADDR, v) +#define HWIO_REG_686205_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_686205_ADDR, m, v,\ + HWIO_REG_686205_IN); +#define HWIO_REG_686205_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_686205_BASE_ADDR_SHFT 0 + +#define HWIO_REG_728036_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000618) +#define HWIO_REG_728036_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000618) +#define HWIO_REG_728036_RMSK 0x1ffff +#define HWIO_REG_728036_SHFT 0 +#define HWIO_REG_728036_IN in_dword_masked(\ + HWIO_REG_728036_ADDR, HWIO_REG_728036_RMSK) +#define HWIO_REG_728036_INM(m) \ + in_dword_masked(HWIO_REG_728036_ADDR, m) +#define HWIO_REG_728036_OUT(v) \ + out_dword(HWIO_REG_728036_ADDR, v) +#define HWIO_REG_728036_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_728036_ADDR, m, v,\ + HWIO_REG_728036_IN); +#define HWIO_REG_728036_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_728036_BASE_ADDR_SHFT 0 + +#define HWIO_REG_294579_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000061c) +#define HWIO_REG_294579_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000061c) +#define HWIO_REG_294579_RMSK 0x1ffff +#define HWIO_REG_294579_SHFT 0 +#define HWIO_REG_294579_IN in_dword_masked(\ + HWIO_REG_294579_ADDR, HWIO_REG_294579_RMSK) +#define HWIO_REG_294579_INM(m) \ + in_dword_masked(HWIO_REG_294579_ADDR, m) +#define HWIO_REG_294579_OUT(v) \ + out_dword(HWIO_REG_294579_ADDR, v) +#define HWIO_REG_294579_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_294579_ADDR, m, v,\ + HWIO_REG_294579_IN); +#define HWIO_REG_294579_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_294579_BASE_ADDR_SHFT 0 + +#define HWIO_REG_61427_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000620) +#define HWIO_REG_61427_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000620) +#define HWIO_REG_61427_RMSK 0x1ffff +#define HWIO_REG_61427_SHFT 0 +#define HWIO_REG_61427_IN in_dword_masked(\ + HWIO_REG_61427_ADDR, HWIO_REG_61427_RMSK) +#define HWIO_REG_61427_INM(m) \ + in_dword_masked(HWIO_REG_61427_ADDR, m) +#define HWIO_REG_61427_OUT(v) \ + out_dword(HWIO_REG_61427_ADDR, v) +#define HWIO_REG_61427_OUTM(m , v) out_dword_masked_ns(\ + HWIO_REG_61427_ADDR, m, v,\ + HWIO_REG_61427_IN); +#define HWIO_REG_61427_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_61427_BASE_ADDR_SHFT 0 + +#define HWIO_REG_578196_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000624) +#define HWIO_REG_578196_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000624) +#define HWIO_REG_578196_RMSK 0x1ffff +#define HWIO_REG_578196_SHFT 0 +#define HWIO_REG_578196_IN in_dword_masked(\ + HWIO_REG_578196_ADDR, HWIO_REG_578196_RMSK) +#define HWIO_REG_578196_INM(m) \ + in_dword_masked(HWIO_REG_578196_ADDR, m) +#define HWIO_REG_578196_OUT(v) \ + out_dword(HWIO_REG_578196_ADDR, v) +#define HWIO_REG_578196_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_578196_ADDR, m, v,\ + HWIO_REG_578196_IN); +#define HWIO_REG_578196_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_578196_BASE_ADDR_SHFT 0 + +#define HWIO_REG_408588_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000628) +#define HWIO_REG_408588_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000628) +#define HWIO_REG_408588_RMSK 0x1ffff +#define HWIO_REG_408588_SHFT 0 +#define HWIO_REG_408588_IN in_dword_masked(\ + HWIO_REG_408588_ADDR, HWIO_REG_408588_RMSK) +#define HWIO_REG_408588_INM(m) \ + in_dword_masked(HWIO_REG_408588_ADDR, m) +#define HWIO_REG_408588_OUT(v) \ + out_dword(HWIO_REG_408588_ADDR, v) +#define HWIO_REG_408588_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_408588_ADDR, m, v,\ + HWIO_REG_408588_IN); +#define HWIO_REG_408588_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_408588_BASE_ADDR_SHFT 0 + +#define HWIO_REG_55617_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000062c) +#define HWIO_REG_55617_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000062c) +#define HWIO_REG_55617_RMSK 0x1ffff +#define HWIO_REG_55617_SHFT 0 +#define HWIO_REG_55617_IN in_dword_masked(\ + HWIO_REG_55617_ADDR, HWIO_REG_55617_RMSK) +#define HWIO_REG_55617_INM(m) \ + in_dword_masked(HWIO_REG_55617_ADDR, m) +#define HWIO_REG_55617_OUT(v) \ + out_dword(HWIO_REG_55617_ADDR, v) +#define HWIO_REG_55617_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_55617_ADDR, m, v,\ + HWIO_REG_55617_IN); +#define HWIO_REG_55617_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_55617_BASE_ADDR_SHFT 0 + +#define HWIO_REG_555239_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000630) +#define HWIO_REG_555239_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000630) +#define HWIO_REG_555239_RMSK 0x1ffff +#define HWIO_REG_555239_SHFT 0 +#define HWIO_REG_555239_IN in_dword_masked(\ + HWIO_REG_555239_ADDR, HWIO_REG_555239_RMSK) +#define HWIO_REG_555239_INM(m) \ + in_dword_masked(HWIO_REG_555239_ADDR, m) +#define HWIO_REG_555239_OUT(v) \ + out_dword(HWIO_REG_555239_ADDR, v) +#define HWIO_REG_555239_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_555239_ADDR, m, v,\ + HWIO_REG_555239_IN); +#define HWIO_REG_555239_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_555239_BASE_ADDR_SHFT 0 + +#define HWIO_REG_515333_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000634) +#define HWIO_REG_515333_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000634) +#define HWIO_REG_515333_RMSK 0x1ffff +#define HWIO_REG_515333_SHFT 0 +#define HWIO_REG_515333_IN in_dword_masked(\ + HWIO_REG_515333_ADDR, HWIO_REG_515333_RMSK) +#define HWIO_REG_515333_INM(m) \ + in_dword_masked(HWIO_REG_515333_ADDR, m) +#define HWIO_REG_515333_OUT(v) \ + out_dword(HWIO_REG_515333_ADDR, v) +#define HWIO_REG_515333_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_515333_ADDR, m, v,\ + HWIO_REG_515333_IN); +#define HWIO_REG_515333_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_515333_BASE_ADDR_SHFT 0 + +#define HWIO_REG_951675_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000638) +#define HWIO_REG_951675_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000638) +#define HWIO_REG_951675_RMSK 0x1ffff +#define HWIO_REG_951675_SHFT 0 +#define HWIO_REG_951675_IN in_dword_masked(\ + HWIO_REG_951675_ADDR, HWIO_REG_951675_RMSK) +#define HWIO_REG_951675_INM(m) \ + in_dword_masked(HWIO_REG_951675_ADDR, m) +#define HWIO_REG_951675_OUT(v) \ + out_dword(HWIO_REG_951675_ADDR, v) +#define HWIO_REG_951675_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_951675_ADDR, m, v,\ + HWIO_REG_951675_IN); +#define HWIO_REG_951675_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_951675_BASE_ADDR_SHFT 0 + +#define HWIO_REG_500775_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000063c) +#define HWIO_REG_500775_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000063c) +#define HWIO_REG_500775_RMSK 0x1ffff +#define HWIO_REG_500775_SHFT 0 +#define HWIO_REG_500775_IN in_dword_masked(\ + HWIO_REG_500775_ADDR, HWIO_REG_500775_RMSK) +#define HWIO_REG_500775_INM(m) \ + in_dword_masked(HWIO_REG_500775_ADDR, m) +#define HWIO_REG_500775_OUT(v) \ + out_dword(HWIO_REG_500775_ADDR, v) +#define HWIO_REG_500775_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_500775_ADDR, m, v,\ + HWIO_REG_500775_IN); +#define HWIO_REG_500775_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_500775_BASE_ADDR_SHFT 0 + +#define HWIO_REG_649786_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000640) +#define HWIO_REG_649786_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000640) +#define HWIO_REG_649786_RMSK 0x1ffff +#define HWIO_REG_649786_SHFT 0 +#define HWIO_REG_649786_IN in_dword_masked(\ + HWIO_REG_649786_ADDR, HWIO_REG_649786_RMSK) +#define HWIO_REG_649786_INM(m) \ + in_dword_masked(HWIO_REG_649786_ADDR, m) +#define HWIO_REG_649786_OUT(v) \ + out_dword(HWIO_REG_649786_ADDR, v) +#define HWIO_REG_649786_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_649786_ADDR, m, v,\ + HWIO_REG_649786_IN); +#define HWIO_REG_649786_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_649786_BASE_ADDR_SHFT 0 + +#define HWIO_REG_233366_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000644) +#define HWIO_REG_233366_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000644) +#define HWIO_REG_233366_RMSK 0x1ffff +#define HWIO_REG_233366_SHFT 0 +#define HWIO_REG_233366_IN in_dword_masked(\ + HWIO_REG_233366_ADDR, HWIO_REG_233366_RMSK) +#define HWIO_REG_233366_INM(m) \ + in_dword_masked(HWIO_REG_233366_ADDR, m) +#define HWIO_REG_233366_OUT(v) \ + out_dword(HWIO_REG_233366_ADDR, v) +#define HWIO_REG_233366_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_233366_ADDR, m, v,\ + HWIO_REG_233366_IN); +#define HWIO_REG_233366_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_233366_BASE_ADDR_SHFT 0 + +#define HWIO_REG_366750_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000648) +#define HWIO_REG_366750_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000648) +#define HWIO_REG_366750_RMSK 0x1ffff +#define HWIO_REG_366750_SHFT 0 +#define HWIO_REG_366750_IN in_dword_masked(\ + HWIO_REG_366750_ADDR, HWIO_REG_366750_RMSK) +#define HWIO_REG_366750_INM(m) \ + in_dword_masked(HWIO_REG_366750_ADDR, m) +#define HWIO_REG_366750_OUT(v) \ + out_dword(HWIO_REG_366750_ADDR, v) +#define HWIO_REG_366750_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_366750_ADDR, m, v,\ + HWIO_REG_366750_IN); +#define HWIO_REG_366750_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_366750_BASE_ADDR_SHFT 0 + +#define HWIO_REG_616292_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000064c) +#define HWIO_REG_616292_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000064c) +#define HWIO_REG_616292_RMSK 0x1ffff +#define HWIO_REG_616292_SHFT 0 +#define HWIO_REG_616292_IN in_dword_masked(\ + HWIO_REG_616292_ADDR, HWIO_REG_616292_RMSK) +#define HWIO_REG_616292_INM(m) \ + in_dword_masked(HWIO_REG_616292_ADDR, m) +#define HWIO_REG_616292_OUT(v) \ + out_dword(HWIO_REG_616292_ADDR, v) +#define HWIO_REG_616292_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_616292_ADDR, m, v,\ + HWIO_REG_616292_IN); +#define HWIO_REG_616292_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_616292_BASE_ADDR_SHFT 0 + +#define HWIO_REG_666754_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000650) +#define HWIO_REG_666754_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000650) +#define HWIO_REG_666754_RMSK 0x1ffff +#define HWIO_REG_666754_SHFT 0 +#define HWIO_REG_666754_IN in_dword_masked(\ + HWIO_REG_666754_ADDR, HWIO_REG_666754_RMSK) +#define HWIO_REG_666754_INM(m) \ + in_dword_masked(HWIO_REG_666754_ADDR, m) +#define HWIO_REG_666754_OUT(v) \ + out_dword(HWIO_REG_666754_ADDR, v) +#define HWIO_REG_666754_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_666754_ADDR, m, v,\ + HWIO_REG_666754_IN); +#define HWIO_REG_666754_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_666754_BASE_ADDR_SHFT 0 + +#define HWIO_REG_650155_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000654) +#define HWIO_REG_650155_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000654) +#define HWIO_REG_650155_RMSK 0x1ffff +#define HWIO_REG_650155_SHFT 0 +#define HWIO_REG_650155_IN in_dword_masked(\ + HWIO_REG_650155_ADDR, HWIO_REG_650155_RMSK) +#define HWIO_REG_650155_INM(m) \ + in_dword_masked(HWIO_REG_650155_ADDR, m) +#define HWIO_REG_650155_OUT(v) \ + out_dword(HWIO_REG_650155_ADDR, v) +#define HWIO_REG_650155_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_650155_ADDR, m, v,\ + HWIO_REG_650155_IN); +#define HWIO_REG_650155_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_650155_BASE_ADDR_SHFT 0 + +#define HWIO_REG_248198_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000658) +#define HWIO_REG_248198_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000658) +#define HWIO_REG_248198_RMSK 0x1ffff +#define HWIO_REG_248198_SHFT 0 +#define HWIO_REG_248198_IN in_dword_masked(\ + HWIO_REG_248198_ADDR, HWIO_REG_248198_RMSK) +#define HWIO_REG_248198_INM(m) \ + in_dword_masked(HWIO_REG_248198_ADDR, m) +#define HWIO_REG_248198_OUT(v) \ + out_dword(HWIO_REG_248198_ADDR, v) +#define HWIO_REG_248198_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_248198_ADDR, m, v,\ + HWIO_REG_248198_IN); +#define HWIO_REG_248198_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_248198_BASE_ADDR_SHFT 0 + +#define HWIO_REG_389428_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000065c) +#define HWIO_REG_389428_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000065c) +#define HWIO_REG_389428_RMSK 0x1ffff +#define HWIO_REG_389428_SHFT 0 +#define HWIO_REG_389428_IN in_dword_masked(\ + HWIO_REG_389428_ADDR, HWIO_REG_389428_RMSK) +#define HWIO_REG_389428_INM(m) \ + in_dword_masked(HWIO_REG_389428_ADDR, m) +#define HWIO_REG_389428_OUT(v) \ + out_dword(HWIO_REG_389428_ADDR, v) +#define HWIO_REG_389428_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_389428_ADDR, m, v,\ + HWIO_REG_389428_IN); +#define HWIO_REG_389428_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_389428_BASE_ADDR_SHFT 0 + +#define HWIO_REG_504308_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000660) +#define HWIO_REG_504308_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000660) +#define HWIO_REG_504308_RMSK 0x1ffff +#define HWIO_REG_504308_SHFT 0 +#define HWIO_REG_504308_IN in_dword_masked(\ + HWIO_REG_504308_ADDR, HWIO_REG_504308_RMSK) +#define HWIO_REG_504308_INM(m) \ + in_dword_masked(HWIO_REG_504308_ADDR, m) +#define HWIO_REG_504308_OUT(v) \ + out_dword(HWIO_REG_504308_ADDR, v) +#define HWIO_REG_504308_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_504308_ADDR, m, v,\ + HWIO_REG_504308_IN); +#define HWIO_REG_504308_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_504308_BASE_ADDR_SHFT 0 + +#define HWIO_REG_280814_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000664) +#define HWIO_REG_280814_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000664) +#define HWIO_REG_280814_RMSK 0x1ffff +#define HWIO_REG_280814_SHFT 0 +#define HWIO_REG_280814_IN in_dword_masked(\ + HWIO_REG_280814_ADDR, HWIO_REG_280814_RMSK) +#define HWIO_REG_280814_INM(m) \ + in_dword_masked(HWIO_REG_280814_ADDR, m) +#define HWIO_REG_280814_OUT(v) \ + out_dword(HWIO_REG_280814_ADDR, v) +#define HWIO_REG_280814_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_280814_ADDR, m, v,\ + HWIO_REG_280814_IN); +#define HWIO_REG_280814_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_280814_BASE_ADDR_SHFT 0 + +#define HWIO_REG_785484_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000668) +#define HWIO_REG_785484_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000668) +#define HWIO_REG_785484_RMSK 0x1ffff +#define HWIO_REG_785484_SHFT 0 +#define HWIO_REG_785484_IN in_dword_masked(\ + HWIO_REG_785484_ADDR, HWIO_REG_785484_RMSK) +#define HWIO_REG_785484_INM(m) \ + in_dword_masked(HWIO_REG_785484_ADDR, m) +#define HWIO_REG_785484_OUT(v) \ + out_dword(HWIO_REG_785484_ADDR, v) +#define HWIO_REG_785484_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_785484_ADDR, m, v,\ + HWIO_REG_785484_IN); +#define HWIO_REG_785484_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_785484_BASE_ADDR_SHFT 0 + +#define HWIO_REG_218455_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000066c) +#define HWIO_REG_218455_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000066c) +#define HWIO_REG_218455_RMSK 0x1ffff +#define HWIO_REG_218455_SHFT 0 +#define HWIO_REG_218455_IN in_dword_masked(\ + HWIO_REG_218455_ADDR, HWIO_REG_218455_RMSK) +#define HWIO_REG_218455_INM(m) \ + in_dword_masked(HWIO_REG_218455_ADDR, m) +#define HWIO_REG_218455_OUT(v) \ + out_dword(HWIO_REG_218455_ADDR, v) +#define HWIO_REG_218455_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_218455_ADDR, m, v,\ + HWIO_REG_218455_IN); +#define HWIO_REG_218455_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_218455_BASE_ADDR_SHFT 0 + +#define HWIO_REG_886591_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000670) +#define HWIO_REG_886591_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000670) +#define HWIO_REG_886591_RMSK 0x1ffff +#define HWIO_REG_886591_SHFT 0 +#define HWIO_REG_886591_IN in_dword_masked(\ + HWIO_REG_886591_ADDR, HWIO_REG_886591_RMSK) +#define HWIO_REG_886591_INM(m) \ + in_dword_masked(HWIO_REG_886591_ADDR, m) +#define HWIO_REG_886591_OUT(v) \ + out_dword(HWIO_REG_886591_ADDR, v) +#define HWIO_REG_886591_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_886591_ADDR, m, v,\ + HWIO_REG_886591_IN); +#define HWIO_REG_886591_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_886591_BASE_ADDR_SHFT 0 + +#define HWIO_REG_912449_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000674) +#define HWIO_REG_912449_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000674) +#define HWIO_REG_912449_RMSK 0x1ffff +#define HWIO_REG_912449_SHFT 0 +#define HWIO_REG_912449_IN in_dword_masked(\ + HWIO_REG_912449_ADDR, HWIO_REG_912449_RMSK) +#define HWIO_REG_912449_INM(m) \ + in_dword_masked(HWIO_REG_912449_ADDR, m) +#define HWIO_REG_912449_OUT(v) \ + out_dword(HWIO_REG_912449_ADDR, v) +#define HWIO_REG_912449_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_912449_ADDR, m, v,\ + HWIO_REG_912449_IN); +#define HWIO_REG_912449_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_912449_BASE_ADDR_SHFT 0 + +#define HWIO_REG_1065_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000678) +#define HWIO_REG_1065_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000678) +#define HWIO_REG_1065_RMSK 0x1ffff +#define HWIO_REG_1065_SHFT 0 +#define HWIO_REG_1065_IN in_dword_masked(\ + HWIO_REG_1065_ADDR, HWIO_REG_1065_RMSK) +#define HWIO_REG_1065_INM(m) \ + in_dword_masked(HWIO_REG_1065_ADDR, m) +#define HWIO_REG_1065_OUT(v) \ + out_dword(HWIO_REG_1065_ADDR, v) +#define HWIO_REG_1065_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_1065_ADDR, m, v,\ + HWIO_REG_1065_IN); +#define HWIO_REG_1065_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_1065_BASE_ADDR_SHFT 0 + +#define HWIO_REG_61838_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000067c) +#define HWIO_REG_61838_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000067c) +#define HWIO_REG_61838_RMSK 0x1ffff +#define HWIO_REG_61838_SHFT 0 +#define HWIO_REG_61838_IN in_dword_masked(\ + HWIO_REG_61838_ADDR, HWIO_REG_61838_RMSK) +#define HWIO_REG_61838_INM(m) \ + in_dword_masked(HWIO_REG_61838_ADDR, m) +#define HWIO_REG_61838_OUT(v) \ + out_dword(HWIO_REG_61838_ADDR, v) +#define HWIO_REG_61838_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_61838_ADDR, m, v,\ + HWIO_REG_61838_IN); +#define HWIO_REG_61838_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_61838_BASE_ADDR_SHFT 0 + +#define HWIO_REG_169838_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000680) +#define HWIO_REG_169838_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000680) +#define HWIO_REG_169838_RMSK 0x1ffff +#define HWIO_REG_169838_SHFT 0 +#define HWIO_REG_169838_IN in_dword_masked(\ + HWIO_REG_169838_ADDR, HWIO_REG_169838_RMSK) +#define HWIO_REG_169838_INM(m) \ + in_dword_masked(HWIO_REG_169838_ADDR, m) +#define HWIO_REG_169838_OUT(v) \ + out_dword(HWIO_REG_169838_ADDR, v) +#define HWIO_REG_169838_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_169838_ADDR, m, v,\ + HWIO_REG_169838_IN); +#define HWIO_REG_169838_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_169838_BASE_ADDR_SHFT 0 + +#define HWIO_REG_986147_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000684) +#define HWIO_REG_986147_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000684) +#define HWIO_REG_986147_RMSK 0x1ffff +#define HWIO_REG_986147_SHFT 0 +#define HWIO_REG_986147_IN in_dword_masked(\ + HWIO_REG_986147_ADDR, HWIO_REG_986147_RMSK) +#define HWIO_REG_986147_INM(m) \ + in_dword_masked(HWIO_REG_986147_ADDR, m) +#define HWIO_REG_986147_OUT(v) \ + out_dword(HWIO_REG_986147_ADDR, v) +#define HWIO_REG_986147_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_986147_ADDR, m, v,\ + HWIO_REG_986147_IN); +#define HWIO_REG_986147_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_986147_BASE_ADDR_SHFT 0 + +#define HWIO_REG_678637_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000688) +#define HWIO_REG_678637_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000688) +#define HWIO_REG_678637_RMSK 0x1ffff +#define HWIO_REG_678637_SHFT 0 +#define HWIO_REG_678637_IN in_dword_masked(\ + HWIO_REG_678637_ADDR, HWIO_REG_678637_RMSK) +#define HWIO_REG_678637_INM(m) \ + in_dword_masked(HWIO_REG_678637_ADDR, m) +#define HWIO_REG_678637_OUT(v) \ + out_dword(HWIO_REG_678637_ADDR, v) +#define HWIO_REG_678637_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_678637_ADDR, m, v,\ + HWIO_REG_678637_IN); +#define HWIO_REG_678637_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_678637_BASE_ADDR_SHFT 0 + +#define HWIO_REG_931311_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000068c) +#define HWIO_REG_931311_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000068c) +#define HWIO_REG_931311_RMSK 0x1ffff +#define HWIO_REG_931311_SHFT 0 +#define HWIO_REG_931311_IN in_dword_masked(\ + HWIO_REG_931311_ADDR, HWIO_REG_931311_RMSK) +#define HWIO_REG_931311_INM(m) \ + in_dword_masked(HWIO_REG_931311_ADDR, m) +#define HWIO_REG_931311_OUT(v) \ + out_dword(HWIO_REG_931311_ADDR, v) +#define HWIO_REG_931311_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_931311_ADDR, m, v,\ + HWIO_REG_931311_IN); +#define HWIO_REG_931311_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_931311_BASE_ADDR_SHFT 0 + +#define HWIO_REG_16277_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000690) +#define HWIO_REG_16277_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000690) +#define HWIO_REG_16277_RMSK 0x1ffff +#define HWIO_REG_16277_SHFT 0 +#define HWIO_REG_16277_IN in_dword_masked(\ + HWIO_REG_16277_ADDR, HWIO_REG_16277_RMSK) +#define HWIO_REG_16277_INM(m) \ + in_dword_masked(HWIO_REG_16277_ADDR, m) +#define HWIO_REG_16277_OUT(v) \ + out_dword(HWIO_REG_16277_ADDR, v) +#define HWIO_REG_16277_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_16277_ADDR, m, v,\ + HWIO_REG_16277_IN); +#define HWIO_REG_16277_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_16277_BASE_ADDR_SHFT 0 + +#define HWIO_REG_654169_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000694) +#define HWIO_REG_654169_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000694) +#define HWIO_REG_654169_RMSK 0x1ffff +#define HWIO_REG_654169_SHFT 0 +#define HWIO_REG_654169_IN in_dword_masked(\ + HWIO_REG_654169_ADDR, HWIO_REG_654169_RMSK) +#define HWIO_REG_654169_INM(m) \ + in_dword_masked(HWIO_REG_654169_ADDR, m) +#define HWIO_REG_654169_OUT(v) \ + out_dword(HWIO_REG_654169_ADDR, v) +#define HWIO_REG_654169_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_654169_ADDR, m, v,\ + HWIO_REG_654169_IN); +#define HWIO_REG_654169_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_654169_BASE_ADDR_SHFT 0 + +#define HWIO_REG_802794_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000698) +#define HWIO_REG_802794_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000698) +#define HWIO_REG_802794_RMSK 0x1ffff +#define HWIO_REG_802794_SHFT 0 +#define HWIO_REG_802794_IN in_dword_masked(\ + HWIO_REG_802794_ADDR, HWIO_REG_802794_RMSK) +#define HWIO_REG_802794_INM(m) \ + in_dword_masked(HWIO_REG_802794_ADDR, m) +#define HWIO_REG_802794_OUT(v) \ + out_dword(HWIO_REG_802794_ADDR, v) +#define HWIO_REG_802794_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_802794_ADDR, m, v,\ + HWIO_REG_802794_IN); +#define HWIO_REG_802794_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_802794_BASE_ADDR_SHFT 0 + +#define HWIO_REG_724376_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000069c) +#define HWIO_REG_724376_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000069c) +#define HWIO_REG_724376_RMSK 0x1ffff +#define HWIO_REG_724376_SHFT 0 +#define HWIO_REG_724376_IN in_dword_masked(\ + HWIO_REG_724376_ADDR, HWIO_REG_724376_RMSK) +#define HWIO_REG_724376_INM(m) \ + in_dword_masked(HWIO_REG_724376_ADDR, m) +#define HWIO_REG_724376_OUT(v) \ + out_dword(HWIO_REG_724376_ADDR, v) +#define HWIO_REG_724376_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_724376_ADDR, m, v,\ + HWIO_REG_724376_IN); +#define HWIO_REG_724376_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_724376_BASE_ADDR_SHFT 0 + +#define HWIO_REG_551674_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006a0) +#define HWIO_REG_551674_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006a0) +#define HWIO_REG_551674_RMSK 0x1ffff +#define HWIO_REG_551674_SHFT 0 +#define HWIO_REG_551674_IN in_dword_masked(\ + HWIO_REG_551674_ADDR, HWIO_REG_551674_RMSK) +#define HWIO_REG_551674_INM(m) \ + in_dword_masked(HWIO_REG_551674_ADDR, m) +#define HWIO_REG_551674_OUT(v) \ + out_dword(HWIO_REG_551674_ADDR, v) +#define HWIO_REG_551674_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_551674_ADDR, m, v,\ + HWIO_REG_551674_IN); +#define HWIO_REG_551674_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_551674_BASE_ADDR_SHFT 0 + +#define HWIO_REG_115991_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006a4) +#define HWIO_REG_115991_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006a4) +#define HWIO_REG_115991_RMSK 0x1ffff +#define HWIO_REG_115991_SHFT 0 +#define HWIO_REG_115991_IN in_dword_masked(\ + HWIO_REG_115991_ADDR, HWIO_REG_115991_RMSK) +#define HWIO_REG_115991_INM(m) \ + in_dword_masked(HWIO_REG_115991_ADDR, m) +#define HWIO_REG_115991_OUT(v) \ + out_dword(HWIO_REG_115991_ADDR, v) +#define HWIO_REG_115991_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_115991_ADDR, m, v,\ + HWIO_REG_115991_IN); +#define HWIO_REG_115991_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_115991_BASE_ADDR_SHFT 0 + +#define HWIO_REG_252167_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006a8) +#define HWIO_REG_252167_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006a8) +#define HWIO_REG_252167_RMSK 0x1ffff +#define HWIO_REG_252167_SHFT 0 +#define HWIO_REG_252167_IN in_dword_masked(\ + HWIO_REG_252167_ADDR, HWIO_REG_252167_RMSK) +#define HWIO_REG_252167_INM(m) \ + in_dword_masked(HWIO_REG_252167_ADDR, m) +#define HWIO_REG_252167_OUT(v) \ + out_dword(HWIO_REG_252167_ADDR, v) +#define HWIO_REG_252167_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_252167_ADDR, m, v,\ + HWIO_REG_252167_IN); +#define HWIO_REG_252167_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_252167_BASE_ADDR_SHFT 0 + +#define HWIO_REG_695516_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006ac) +#define HWIO_REG_695516_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006ac) +#define HWIO_REG_695516_RMSK 0x1ffff +#define HWIO_REG_695516_SHFT 0 +#define HWIO_REG_695516_IN in_dword_masked(\ + HWIO_REG_695516_ADDR, HWIO_REG_695516_RMSK) +#define HWIO_REG_695516_INM(m) \ + in_dword_masked(HWIO_REG_695516_ADDR, m) +#define HWIO_REG_695516_OUT(v) \ + out_dword(HWIO_REG_695516_ADDR, v) +#define HWIO_REG_695516_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_695516_ADDR, m, v,\ + HWIO_REG_695516_IN); +#define HWIO_REG_695516_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_695516_BASE_ADDR_SHFT 0 + +#define HWIO_REG_152193_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006b0) +#define HWIO_REG_152193_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006b0) +#define HWIO_REG_152193_RMSK 0x1ffff +#define HWIO_REG_152193_SHFT 0 +#define HWIO_REG_152193_IN in_dword_masked(\ + HWIO_REG_152193_ADDR, HWIO_REG_152193_RMSK) +#define HWIO_REG_152193_INM(m) \ + in_dword_masked(HWIO_REG_152193_ADDR, m) +#define HWIO_REG_152193_OUT(v) \ + out_dword(HWIO_REG_152193_ADDR, v) +#define HWIO_REG_152193_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_152193_ADDR, m, v,\ + HWIO_REG_152193_IN); +#define HWIO_REG_152193_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_152193_BASE_ADDR_SHFT 0 + +#define HWIO_REG_358705_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006b4) +#define HWIO_REG_358705_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006b4) +#define HWIO_REG_358705_RMSK 0x1ffff +#define HWIO_REG_358705_SHFT 0 +#define HWIO_REG_358705_IN in_dword_masked(\ + HWIO_REG_358705_ADDR, HWIO_REG_358705_RMSK) +#define HWIO_REG_358705_INM(m) \ + in_dword_masked(HWIO_REG_358705_ADDR, m) +#define HWIO_REG_358705_OUT(v) \ + out_dword(HWIO_REG_358705_ADDR, v) +#define HWIO_REG_358705_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_358705_ADDR, m, v,\ + HWIO_REG_358705_IN); +#define HWIO_REG_358705_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_358705_BASE_ADDR_SHFT 0 + +#define HWIO_REG_457068_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006b8) +#define HWIO_REG_457068_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006b8) +#define HWIO_REG_457068_RMSK 0x1ffff +#define HWIO_REG_457068_SHFT 0 +#define HWIO_REG_457068_IN in_dword_masked(\ + HWIO_REG_457068_ADDR, HWIO_REG_457068_RMSK) +#define HWIO_REG_457068_INM(m) \ + in_dword_masked(HWIO_REG_457068_ADDR, m) +#define HWIO_REG_457068_OUT(v) \ + out_dword(HWIO_REG_457068_ADDR, v) +#define HWIO_REG_457068_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_457068_ADDR, m, v,\ + HWIO_REG_457068_IN); +#define HWIO_REG_457068_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_457068_BASE_ADDR_SHFT 0 + +#define HWIO_REG_485412_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006bc) +#define HWIO_REG_485412_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006bc) +#define HWIO_REG_485412_RMSK 0x1ffff +#define HWIO_REG_485412_SHFT 0 +#define HWIO_REG_485412_IN in_dword_masked(\ + HWIO_REG_485412_ADDR, HWIO_REG_485412_RMSK) +#define HWIO_REG_485412_INM(m) \ + in_dword_masked(HWIO_REG_485412_ADDR, m) +#define HWIO_REG_485412_OUT(v) \ + out_dword(HWIO_REG_485412_ADDR, v) +#define HWIO_REG_485412_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_485412_ADDR, m, v,\ + HWIO_REG_485412_IN); +#define HWIO_REG_485412_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_485412_BASE_ADDR_SHFT 0 + +#define HWIO_REG_223131_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006c0) +#define HWIO_REG_223131_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006c0) +#define HWIO_REG_223131_RMSK 0x1ffff +#define HWIO_REG_223131_SHFT 0 +#define HWIO_REG_223131_IN in_dword_masked(\ + HWIO_REG_223131_ADDR, HWIO_REG_223131_RMSK) +#define HWIO_REG_223131_INM(m) \ + in_dword_masked(HWIO_REG_223131_ADDR, m) +#define HWIO_REG_223131_OUT(v) \ + out_dword(HWIO_REG_223131_ADDR, v) +#define HWIO_REG_223131_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_223131_ADDR, m, v,\ + HWIO_REG_223131_IN); +#define HWIO_REG_223131_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_223131_BASE_ADDR_SHFT 0 + +#define HWIO_REG_683737_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006c4) +#define HWIO_REG_683737_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006c4) +#define HWIO_REG_683737_RMSK 0x1ffff +#define HWIO_REG_683737_SHFT 0 +#define HWIO_REG_683737_IN in_dword_masked(\ + HWIO_REG_683737_ADDR, HWIO_REG_683737_RMSK) +#define HWIO_REG_683737_INM(m) \ + in_dword_masked(HWIO_REG_683737_ADDR, m) +#define HWIO_REG_683737_OUT(v) \ + out_dword(HWIO_REG_683737_ADDR, v) +#define HWIO_REG_683737_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_683737_ADDR, m, v,\ + HWIO_REG_683737_IN); +#define HWIO_REG_683737_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_683737_BASE_ADDR_SHFT 0 + +#define HWIO_REG_750474_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006c8) +#define HWIO_REG_750474_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006c8) +#define HWIO_REG_750474_RMSK 0x1ffff +#define HWIO_REG_750474_SHFT 0 +#define HWIO_REG_750474_IN in_dword_masked(\ + HWIO_REG_750474_ADDR, HWIO_REG_750474_RMSK) +#define HWIO_REG_750474_INM(m) \ + in_dword_masked(HWIO_REG_750474_ADDR, m) +#define HWIO_REG_750474_OUT(v) \ + out_dword(HWIO_REG_750474_ADDR, v) +#define HWIO_REG_750474_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_750474_ADDR, m, v,\ + HWIO_REG_750474_IN); +#define HWIO_REG_750474_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_750474_BASE_ADDR_SHFT 0 + +#define HWIO_REG_170086_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006cc) +#define HWIO_REG_170086_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006cc) +#define HWIO_REG_170086_RMSK 0x1ffff +#define HWIO_REG_170086_SHFT 0 +#define HWIO_REG_170086_IN in_dword_masked(\ + HWIO_REG_170086_ADDR, HWIO_REG_170086_RMSK) +#define HWIO_REG_170086_INM(m) \ + in_dword_masked(HWIO_REG_170086_ADDR, m) +#define HWIO_REG_170086_OUT(v) \ + out_dword(HWIO_REG_170086_ADDR, v) +#define HWIO_REG_170086_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_170086_ADDR, m, v,\ + HWIO_REG_170086_IN); +#define HWIO_REG_170086_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_170086_BASE_ADDR_SHFT 0 + +#define HWIO_REG_838595_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006d0) +#define HWIO_REG_838595_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006d0) +#define HWIO_REG_838595_RMSK 0x1ffff +#define HWIO_REG_838595_SHFT 0 +#define HWIO_REG_838595_IN in_dword_masked(\ + HWIO_REG_838595_ADDR, HWIO_REG_838595_RMSK) +#define HWIO_REG_838595_INM(m) \ + in_dword_masked(HWIO_REG_838595_ADDR, m) +#define HWIO_REG_838595_OUT(v) \ + out_dword(HWIO_REG_838595_ADDR, v) +#define HWIO_REG_838595_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_838595_ADDR, m, v,\ + HWIO_REG_838595_IN); +#define HWIO_REG_838595_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_838595_BASE_ADDR_SHFT 0 + +#define HWIO_REG_569788_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006d4) +#define HWIO_REG_569788_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006d4) +#define HWIO_REG_569788_RMSK 0x1ffff +#define HWIO_REG_569788_SHFT 0 +#define HWIO_REG_569788_IN in_dword_masked(\ + HWIO_REG_569788_ADDR, HWIO_REG_569788_RMSK) +#define HWIO_REG_569788_INM(m) \ + in_dword_masked(HWIO_REG_569788_ADDR, m) +#define HWIO_REG_569788_OUT(v) \ + out_dword(HWIO_REG_569788_ADDR, v) +#define HWIO_REG_569788_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_569788_ADDR, m, v,\ + HWIO_REG_569788_IN); +#define HWIO_REG_569788_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_569788_BASE_ADDR_SHFT 0 + +#define HWIO_REG_974527_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006d8) +#define HWIO_REG_974527_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006d8) +#define HWIO_REG_974527_RMSK 0x1ffff +#define HWIO_REG_974527_SHFT 0 +#define HWIO_REG_974527_IN in_dword_masked(\ + HWIO_REG_974527_ADDR, HWIO_REG_974527_RMSK) +#define HWIO_REG_974527_INM(m) \ + in_dword_masked(HWIO_REG_974527_ADDR, m) +#define HWIO_REG_974527_OUT(v) \ + out_dword(HWIO_REG_974527_ADDR, v) +#define HWIO_REG_974527_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_974527_ADDR, m, v,\ + HWIO_REG_974527_IN); +#define HWIO_REG_974527_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_974527_BASE_ADDR_SHFT 0 + +#define HWIO_REG_316806_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006dc) +#define HWIO_REG_316806_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006dc) +#define HWIO_REG_316806_RMSK 0x1ffff +#define HWIO_REG_316806_SHFT 0 +#define HWIO_REG_316806_IN in_dword_masked(\ + HWIO_REG_316806_ADDR, HWIO_REG_316806_RMSK) +#define HWIO_REG_316806_INM(m) \ + in_dword_masked(HWIO_REG_316806_ADDR, m) +#define HWIO_REG_316806_OUT(v) \ + out_dword(HWIO_REG_316806_ADDR, v) +#define HWIO_REG_316806_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_316806_ADDR, m, v,\ + HWIO_REG_316806_IN); +#define HWIO_REG_316806_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_316806_BASE_ADDR_SHFT 0 + +#define HWIO_REG_900472_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006e0) +#define HWIO_REG_900472_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006e0) +#define HWIO_REG_900472_RMSK 0x1ffff +#define HWIO_REG_900472_SHFT 0 +#define HWIO_REG_900472_IN in_dword_masked(\ + HWIO_REG_900472_ADDR, HWIO_REG_900472_RMSK) +#define HWIO_REG_900472_INM(m) \ + in_dword_masked(HWIO_REG_900472_ADDR, m) +#define HWIO_REG_900472_OUT(v) \ + out_dword(HWIO_REG_900472_ADDR, v) +#define HWIO_REG_900472_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_900472_ADDR, m, v,\ + HWIO_REG_900472_IN); +#define HWIO_REG_900472_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_900472_BASE_ADDR_SHFT 0 + +#define HWIO_REG_256156_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006e4) +#define HWIO_REG_256156_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006e4) +#define HWIO_REG_256156_RMSK 0x1ffff +#define HWIO_REG_256156_SHFT 0 +#define HWIO_REG_256156_IN in_dword_masked(\ + HWIO_REG_256156_ADDR, HWIO_REG_256156_RMSK) +#define HWIO_REG_256156_INM(m) \ + in_dword_masked(HWIO_REG_256156_ADDR, m) +#define HWIO_REG_256156_OUT(v) \ + out_dword(HWIO_REG_256156_ADDR, v) +#define HWIO_REG_256156_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_256156_ADDR, m, v,\ + HWIO_REG_256156_IN); +#define HWIO_REG_256156_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_256156_BASE_ADDR_SHFT 0 + +#define HWIO_REG_335729_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006e8) +#define HWIO_REG_335729_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006e8) +#define HWIO_REG_335729_RMSK 0x1ffff +#define HWIO_REG_335729_SHFT 0 +#define HWIO_REG_335729_IN in_dword_masked(\ + HWIO_REG_335729_ADDR, HWIO_REG_335729_RMSK) +#define HWIO_REG_335729_INM(m) \ + in_dword_masked(HWIO_REG_335729_ADDR, m) +#define HWIO_REG_335729_OUT(v) \ + out_dword(HWIO_REG_335729_ADDR, v) +#define HWIO_REG_335729_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_335729_ADDR, m, v,\ + HWIO_REG_335729_IN); +#define HWIO_REG_335729_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_335729_BASE_ADDR_SHFT 0 + +#define HWIO_REG_303383_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006ec) +#define HWIO_REG_303383_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006ec) +#define HWIO_REG_303383_RMSK 0x1ffff +#define HWIO_REG_303383_SHFT 0 +#define HWIO_REG_303383_IN in_dword_masked(\ + HWIO_REG_303383_ADDR, HWIO_REG_303383_RMSK) +#define HWIO_REG_303383_INM(m) \ + in_dword_masked(HWIO_REG_303383_ADDR, m) +#define HWIO_REG_303383_OUT(v) \ + out_dword(HWIO_REG_303383_ADDR, v) +#define HWIO_REG_303383_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_303383_ADDR, m, v,\ + HWIO_REG_303383_IN); +#define HWIO_REG_303383_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_303383_BASE_ADDR_SHFT 0 + +#define HWIO_REG_180871_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006f0) +#define HWIO_REG_180871_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006f0) +#define HWIO_REG_180871_RMSK 0x1ffff +#define HWIO_REG_180871_SHFT 0 +#define HWIO_REG_180871_IN in_dword_masked(\ + HWIO_REG_180871_ADDR, HWIO_REG_180871_RMSK) +#define HWIO_REG_180871_INM(m) \ + in_dword_masked(HWIO_REG_180871_ADDR, m) +#define HWIO_REG_180871_OUT(v) \ + out_dword(HWIO_REG_180871_ADDR, v) +#define HWIO_REG_180871_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_180871_ADDR, m, v,\ + HWIO_REG_180871_IN); +#define HWIO_REG_180871_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_180871_BASE_ADDR_SHFT 0 + +#define HWIO_REG_514148_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006f4) +#define HWIO_REG_514148_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006f4) +#define HWIO_REG_514148_RMSK 0x1ffff +#define HWIO_REG_514148_SHFT 0 +#define HWIO_REG_514148_IN in_dword_masked(\ + HWIO_REG_514148_ADDR, HWIO_REG_514148_RMSK) +#define HWIO_REG_514148_INM(m) \ + in_dword_masked(HWIO_REG_514148_ADDR, m) +#define HWIO_REG_514148_OUT(v) \ + out_dword(HWIO_REG_514148_ADDR, v) +#define HWIO_REG_514148_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_514148_ADDR, m, v,\ + HWIO_REG_514148_IN); +#define HWIO_REG_514148_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_514148_BASE_ADDR_SHFT 0 + +#define HWIO_REG_578636_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006f8) +#define HWIO_REG_578636_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006f8) +#define HWIO_REG_578636_RMSK 0x1ffff +#define HWIO_REG_578636_SHFT 0 +#define HWIO_REG_578636_IN in_dword_masked(\ + HWIO_REG_578636_ADDR, HWIO_REG_578636_RMSK) +#define HWIO_REG_578636_INM(m) \ + in_dword_masked(HWIO_REG_578636_ADDR, m) +#define HWIO_REG_578636_OUT(v) \ + out_dword(HWIO_REG_578636_ADDR, v) +#define HWIO_REG_578636_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_578636_ADDR, m, v,\ + HWIO_REG_578636_IN); +#define HWIO_REG_578636_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_578636_BASE_ADDR_SHFT 0 + +#define HWIO_REG_888116_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000006fc) +#define HWIO_REG_888116_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000006fc) +#define HWIO_REG_888116_RMSK 0x1ffff +#define HWIO_REG_888116_SHFT 0 +#define HWIO_REG_888116_IN in_dword_masked(\ + HWIO_REG_888116_ADDR, HWIO_REG_888116_RMSK) +#define HWIO_REG_888116_INM(m) \ + in_dword_masked(HWIO_REG_888116_ADDR, m) +#define HWIO_REG_888116_OUT(v) \ + out_dword(HWIO_REG_888116_ADDR, v) +#define HWIO_REG_888116_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_888116_ADDR, m, v,\ + HWIO_REG_888116_IN); +#define HWIO_REG_888116_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_888116_BASE_ADDR_SHFT 0 + +#define HWIO_REG_759068_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000700) +#define HWIO_REG_759068_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000700) +#define HWIO_REG_759068_RMSK 0x1ffff +#define HWIO_REG_759068_SHFT 0 +#define HWIO_REG_759068_IN in_dword_masked(\ + HWIO_REG_759068_ADDR, HWIO_REG_759068_RMSK) +#define HWIO_REG_759068_INM(m) \ + in_dword_masked(HWIO_REG_759068_ADDR, m) +#define HWIO_REG_759068_OUT(v) \ + out_dword(HWIO_REG_759068_ADDR, v) +#define HWIO_REG_759068_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_759068_ADDR, m, v,\ + HWIO_REG_759068_IN); +#define HWIO_REG_759068_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_759068_BASE_ADDR_SHFT 0 + +#define HWIO_REG_68356_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000704) +#define HWIO_REG_68356_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000704) +#define HWIO_REG_68356_RMSK 0x1ffff +#define HWIO_REG_68356_SHFT 0 +#define HWIO_REG_68356_IN in_dword_masked(\ + HWIO_REG_68356_ADDR, HWIO_REG_68356_RMSK) +#define HWIO_REG_68356_INM(m) \ + in_dword_masked(HWIO_REG_68356_ADDR, m) +#define HWIO_REG_68356_OUT(v) \ + out_dword(HWIO_REG_68356_ADDR, v) +#define HWIO_REG_68356_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_68356_ADDR, m, v,\ + HWIO_REG_68356_IN); +#define HWIO_REG_68356_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_68356_BASE_ADDR_SHFT 0 + +#define HWIO_REG_833502_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000708) +#define HWIO_REG_833502_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000708) +#define HWIO_REG_833502_RMSK 0x1ffff +#define HWIO_REG_833502_SHFT 0 +#define HWIO_REG_833502_IN in_dword_masked(\ + HWIO_REG_833502_ADDR, HWIO_REG_833502_RMSK) +#define HWIO_REG_833502_INM(m) \ + in_dword_masked(HWIO_REG_833502_ADDR, m) +#define HWIO_REG_833502_OUT(v) \ + out_dword(HWIO_REG_833502_ADDR, v) +#define HWIO_REG_833502_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_833502_ADDR, m, v,\ + HWIO_REG_833502_IN); +#define HWIO_REG_833502_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_833502_BASE_ADDR_SHFT 0 + +#define HWIO_REG_127855_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000070c) +#define HWIO_REG_127855_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000070c) +#define HWIO_REG_127855_RMSK 0x1ffff +#define HWIO_REG_127855_SHFT 0 +#define HWIO_REG_127855_IN in_dword_masked(\ + HWIO_REG_127855_ADDR, HWIO_REG_127855_RMSK) +#define HWIO_REG_127855_INM(m) \ + in_dword_masked(HWIO_REG_127855_ADDR, m) +#define HWIO_REG_127855_OUT(v) \ + out_dword(HWIO_REG_127855_ADDR, v) +#define HWIO_REG_127855_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_127855_ADDR, m, v,\ + HWIO_REG_127855_IN); +#define HWIO_REG_127855_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_127855_BASE_ADDR_SHFT 0 + +#define HWIO_REG_616802_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000710) +#define HWIO_REG_616802_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000710) +#define HWIO_REG_616802_RMSK 0x1ffff +#define HWIO_REG_616802_SHFT 0 +#define HWIO_REG_616802_IN in_dword_masked(\ + HWIO_REG_616802_ADDR, HWIO_REG_616802_RMSK) +#define HWIO_REG_616802_INM(m) \ + in_dword_masked(HWIO_REG_616802_ADDR, m) +#define HWIO_REG_616802_OUT(v) \ + out_dword(HWIO_REG_616802_ADDR, v) +#define HWIO_REG_616802_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_616802_ADDR, m, v,\ + HWIO_REG_616802_IN); +#define HWIO_REG_616802_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_616802_BASE_ADDR_SHFT 0 + +#define HWIO_REG_23318_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000714) +#define HWIO_REG_23318_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000714) +#define HWIO_REG_23318_RMSK 0x1ffff +#define HWIO_REG_23318_SHFT 0 +#define HWIO_REG_23318_IN in_dword_masked(\ + HWIO_REG_23318_ADDR, HWIO_REG_23318_RMSK) +#define HWIO_REG_23318_INM(m) \ + in_dword_masked(HWIO_REG_23318_ADDR, m) +#define HWIO_REG_23318_OUT(v) \ + out_dword(HWIO_REG_23318_ADDR, v) +#define HWIO_REG_23318_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_23318_ADDR, m, v,\ + HWIO_REG_23318_IN); +#define HWIO_REG_23318_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_23318_BASE_ADDR_SHFT 0 + +#define HWIO_REG_317106_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000718) +#define HWIO_REG_317106_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000718) +#define HWIO_REG_317106_RMSK 0x1ffff +#define HWIO_REG_317106_SHFT 0 +#define HWIO_REG_317106_IN in_dword_masked(\ + HWIO_REG_317106_ADDR, HWIO_REG_317106_RMSK) +#define HWIO_REG_317106_INM(m) \ + in_dword_masked(HWIO_REG_317106_ADDR, m) +#define HWIO_REG_317106_OUT(v) \ + out_dword(HWIO_REG_317106_ADDR, v) +#define HWIO_REG_317106_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_317106_ADDR, m, v,\ + HWIO_REG_317106_IN); +#define HWIO_REG_317106_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_317106_BASE_ADDR_SHFT 0 + +#define HWIO_REG_603772_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000071c) +#define HWIO_REG_603772_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000071c) +#define HWIO_REG_603772_RMSK 0x1ffff +#define HWIO_REG_603772_SHFT 0 +#define HWIO_REG_603772_IN in_dword_masked(\ + HWIO_REG_603772_ADDR, HWIO_REG_603772_RMSK) +#define HWIO_REG_603772_INM(m) \ + in_dword_masked(HWIO_REG_603772_ADDR, m) +#define HWIO_REG_603772_OUT(v) \ + out_dword(HWIO_REG_603772_ADDR, v) +#define HWIO_REG_603772_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_603772_ADDR, m, v,\ + HWIO_REG_603772_IN); +#define HWIO_REG_603772_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_603772_BASE_ADDR_SHFT 0 + +#define HWIO_REG_175929_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000720) +#define HWIO_REG_175929_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000720) +#define HWIO_REG_175929_RMSK 0x1ffff +#define HWIO_REG_175929_SHFT 0 +#define HWIO_REG_175929_IN in_dword_masked(\ + HWIO_REG_175929_ADDR, HWIO_REG_175929_RMSK) +#define HWIO_REG_175929_INM(m) \ + in_dword_masked(HWIO_REG_175929_ADDR, m) +#define HWIO_REG_175929_OUT(v) \ + out_dword(HWIO_REG_175929_ADDR, v) +#define HWIO_REG_175929_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_175929_ADDR, m, v,\ + HWIO_REG_175929_IN); +#define HWIO_REG_175929_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_175929_BASE_ADDR_SHFT 0 + +#define HWIO_REG_11928_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000724) +#define HWIO_REG_11928_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000724) +#define HWIO_REG_11928_RMSK 0x1ffff +#define HWIO_REG_11928_SHFT 0 +#define HWIO_REG_11928_IN in_dword_masked(\ + HWIO_REG_11928_ADDR, HWIO_REG_11928_RMSK) +#define HWIO_REG_11928_INM(m) \ + in_dword_masked(HWIO_REG_11928_ADDR, m) +#define HWIO_REG_11928_OUT(v) \ + out_dword(HWIO_REG_11928_ADDR, v) +#define HWIO_REG_11928_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_11928_ADDR, m, v,\ + HWIO_REG_11928_IN); +#define HWIO_REG_11928_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_11928_BASE_ADDR_SHFT 0 + +#define HWIO_REG_772678_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000728) +#define HWIO_REG_772678_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000728) +#define HWIO_REG_772678_RMSK 0x1ffff +#define HWIO_REG_772678_SHFT 0 +#define HWIO_REG_772678_IN in_dword_masked(\ + HWIO_REG_772678_ADDR, HWIO_REG_772678_RMSK) +#define HWIO_REG_772678_INM(m) \ + in_dword_masked(HWIO_REG_772678_ADDR, m) +#define HWIO_REG_772678_OUT(v) \ + out_dword(HWIO_REG_772678_ADDR, v) +#define HWIO_REG_772678_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_772678_ADDR, m, v,\ + HWIO_REG_772678_IN); +#define HWIO_REG_772678_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_772678_BASE_ADDR_SHFT 0 + +#define HWIO_REG_603389_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000072c) +#define HWIO_REG_603389_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000072c) +#define HWIO_REG_603389_RMSK 0x1ffff +#define HWIO_REG_603389_SHFT 0 +#define HWIO_REG_603389_IN in_dword_masked(\ + HWIO_REG_603389_ADDR, HWIO_REG_603389_RMSK) +#define HWIO_REG_603389_INM(m) \ + in_dword_masked(HWIO_REG_603389_ADDR, m) +#define HWIO_REG_603389_OUT(v) \ + out_dword(HWIO_REG_603389_ADDR, v) +#define HWIO_REG_603389_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_603389_ADDR, m, v,\ + HWIO_REG_603389_IN); +#define HWIO_REG_603389_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_603389_BASE_ADDR_SHFT 0 + +#define HWIO_REG_989918_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000730) +#define HWIO_REG_989918_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000730) +#define HWIO_REG_989918_RMSK 0x1ffff +#define HWIO_REG_989918_SHFT 0 +#define HWIO_REG_989918_IN in_dword_masked(\ + HWIO_REG_989918_ADDR, HWIO_REG_989918_RMSK) +#define HWIO_REG_989918_INM(m) \ + in_dword_masked(HWIO_REG_989918_ADDR, m) +#define HWIO_REG_989918_OUT(v) \ + out_dword(HWIO_REG_989918_ADDR, v) +#define HWIO_REG_989918_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_989918_ADDR, m, v,\ + HWIO_REG_989918_IN); +#define HWIO_REG_989918_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_989918_BASE_ADDR_SHFT 0 + +#define HWIO_REG_5460_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000734) +#define HWIO_REG_5460_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000734) +#define HWIO_REG_5460_RMSK 0x1ffff +#define HWIO_REG_5460_SHFT 0 +#define HWIO_REG_5460_IN in_dword_masked(\ + HWIO_REG_5460_ADDR, HWIO_REG_5460_RMSK) +#define HWIO_REG_5460_INM(m) \ + in_dword_masked(HWIO_REG_5460_ADDR, m) +#define HWIO_REG_5460_OUT(v) \ + out_dword(HWIO_REG_5460_ADDR, v) +#define HWIO_REG_5460_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_5460_ADDR, m, v,\ + HWIO_REG_5460_IN); +#define HWIO_REG_5460_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_5460_BASE_ADDR_SHFT 0 + +#define HWIO_REG_734724_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000738) +#define HWIO_REG_734724_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000738) +#define HWIO_REG_734724_RMSK 0x1ffff +#define HWIO_REG_734724_SHFT 0 +#define HWIO_REG_734724_IN in_dword_masked(\ + HWIO_REG_734724_ADDR, HWIO_REG_734724_RMSK) +#define HWIO_REG_734724_INM(m) \ + in_dword_masked(HWIO_REG_734724_ADDR, m) +#define HWIO_REG_734724_OUT(v) \ + out_dword(HWIO_REG_734724_ADDR, v) +#define HWIO_REG_734724_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_734724_ADDR, m, v,\ + HWIO_REG_734724_IN); +#define HWIO_REG_734724_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_734724_BASE_ADDR_SHFT 0 + +#define HWIO_REG_451742_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000073c) +#define HWIO_REG_451742_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000073c) +#define HWIO_REG_451742_RMSK 0x1ffff +#define HWIO_REG_451742_SHFT 0 +#define HWIO_REG_451742_IN in_dword_masked(\ + HWIO_REG_451742_ADDR, HWIO_REG_451742_RMSK) +#define HWIO_REG_451742_INM(m) \ + in_dword_masked(HWIO_REG_451742_ADDR, m) +#define HWIO_REG_451742_OUT(v) \ + out_dword(HWIO_REG_451742_ADDR, v) +#define HWIO_REG_451742_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_451742_ADDR, m, v,\ + HWIO_REG_451742_IN); +#define HWIO_REG_451742_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_451742_BASE_ADDR_SHFT 0 + +#define HWIO_REG_475648_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000740) +#define HWIO_REG_475648_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000740) +#define HWIO_REG_475648_RMSK 0x1ffff +#define HWIO_REG_475648_SHFT 0 +#define HWIO_REG_475648_IN in_dword_masked(\ + HWIO_REG_475648_ADDR, HWIO_REG_475648_RMSK) +#define HWIO_REG_475648_INM(m) \ + in_dword_masked(HWIO_REG_475648_ADDR, m) +#define HWIO_REG_475648_OUT(v) \ + out_dword(HWIO_REG_475648_ADDR, v) +#define HWIO_REG_475648_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_475648_ADDR, m, v,\ + HWIO_REG_475648_IN); +#define HWIO_REG_475648_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_475648_BASE_ADDR_SHFT 0 + +#define HWIO_REG_284758_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000744) +#define HWIO_REG_284758_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000744) +#define HWIO_REG_284758_RMSK 0x1ffff +#define HWIO_REG_284758_SHFT 0 +#define HWIO_REG_284758_IN in_dword_masked(\ + HWIO_REG_284758_ADDR, HWIO_REG_284758_RMSK) +#define HWIO_REG_284758_INM(m) \ + in_dword_masked(HWIO_REG_284758_ADDR, m) +#define HWIO_REG_284758_OUT(v) \ + out_dword(HWIO_REG_284758_ADDR, v) +#define HWIO_REG_284758_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_284758_ADDR, m, v,\ + HWIO_REG_284758_IN); +#define HWIO_REG_284758_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_284758_BASE_ADDR_SHFT 0 + +#define HWIO_REG_523659_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000748) +#define HWIO_REG_523659_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000748) +#define HWIO_REG_523659_RMSK 0x1ffff +#define HWIO_REG_523659_SHFT 0 +#define HWIO_REG_523659_IN in_dword_masked(\ + HWIO_REG_523659_ADDR, HWIO_REG_523659_RMSK) +#define HWIO_REG_523659_INM(m) \ + in_dword_masked(HWIO_REG_523659_ADDR, m) +#define HWIO_REG_523659_OUT(v) \ + out_dword(HWIO_REG_523659_ADDR, v) +#define HWIO_REG_523659_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_523659_ADDR, m, v,\ + HWIO_REG_523659_IN); +#define HWIO_REG_523659_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_523659_BASE_ADDR_SHFT 0 + +#define HWIO_REG_815580_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000074c) +#define HWIO_REG_815580_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000074c) +#define HWIO_REG_815580_RMSK 0x1ffff +#define HWIO_REG_815580_SHFT 0 +#define HWIO_REG_815580_IN in_dword_masked(\ + HWIO_REG_815580_ADDR, HWIO_REG_815580_RMSK) +#define HWIO_REG_815580_INM(m) \ + in_dword_masked(HWIO_REG_815580_ADDR, m) +#define HWIO_REG_815580_OUT(v) \ + out_dword(HWIO_REG_815580_ADDR, v) +#define HWIO_REG_815580_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_815580_ADDR, m, v,\ + HWIO_REG_815580_IN); +#define HWIO_REG_815580_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_815580_BASE_ADDR_SHFT 0 + +#define HWIO_REG_546551_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000750) +#define HWIO_REG_546551_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000750) +#define HWIO_REG_546551_RMSK 0x1ffff +#define HWIO_REG_546551_SHFT 0 +#define HWIO_REG_546551_IN in_dword_masked(\ + HWIO_REG_546551_ADDR, HWIO_REG_546551_RMSK) +#define HWIO_REG_546551_INM(m) \ + in_dword_masked(HWIO_REG_546551_ADDR, m) +#define HWIO_REG_546551_OUT(v) \ + out_dword(HWIO_REG_546551_ADDR, v) +#define HWIO_REG_546551_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_546551_ADDR, m, v,\ + HWIO_REG_546551_IN); +#define HWIO_REG_546551_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_546551_BASE_ADDR_SHFT 0 + +#define HWIO_REG_769851_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000754) +#define HWIO_REG_769851_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000754) +#define HWIO_REG_769851_RMSK 0x1ffff +#define HWIO_REG_769851_SHFT 0 +#define HWIO_REG_769851_IN in_dword_masked(\ + HWIO_REG_769851_ADDR, HWIO_REG_769851_RMSK) +#define HWIO_REG_769851_INM(m) \ + in_dword_masked(HWIO_REG_769851_ADDR, m) +#define HWIO_REG_769851_OUT(v) \ + out_dword(HWIO_REG_769851_ADDR, v) +#define HWIO_REG_769851_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_769851_ADDR, m, v,\ + HWIO_REG_769851_IN); +#define HWIO_REG_769851_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_769851_BASE_ADDR_SHFT 0 + +#define HWIO_REG_205028_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000758) +#define HWIO_REG_205028_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000758) +#define HWIO_REG_205028_RMSK 0x1ffff +#define HWIO_REG_205028_SHFT 0 +#define HWIO_REG_205028_IN in_dword_masked(\ + HWIO_REG_205028_ADDR, HWIO_REG_205028_RMSK) +#define HWIO_REG_205028_INM(m) \ + in_dword_masked(HWIO_REG_205028_ADDR, m) +#define HWIO_REG_205028_OUT(v) \ + out_dword(HWIO_REG_205028_ADDR, v) +#define HWIO_REG_205028_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_205028_ADDR, m, v,\ + HWIO_REG_205028_IN); +#define HWIO_REG_205028_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_205028_BASE_ADDR_SHFT 0 + +#define HWIO_REG_206835_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000075c) +#define HWIO_REG_206835_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000075c) +#define HWIO_REG_206835_RMSK 0x1ffff +#define HWIO_REG_206835_SHFT 0 +#define HWIO_REG_206835_IN in_dword_masked(\ + HWIO_REG_206835_ADDR, HWIO_REG_206835_RMSK) +#define HWIO_REG_206835_INM(m) \ + in_dword_masked(HWIO_REG_206835_ADDR, m) +#define HWIO_REG_206835_OUT(v) \ + out_dword(HWIO_REG_206835_ADDR, v) +#define HWIO_REG_206835_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_206835_ADDR, m, v,\ + HWIO_REG_206835_IN); +#define HWIO_REG_206835_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_206835_BASE_ADDR_SHFT 0 + +#define HWIO_REG_582575_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000760) +#define HWIO_REG_582575_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000760) +#define HWIO_REG_582575_RMSK 0x1ffff +#define HWIO_REG_582575_SHFT 0 +#define HWIO_REG_582575_IN in_dword_masked(\ + HWIO_REG_582575_ADDR, HWIO_REG_582575_RMSK) +#define HWIO_REG_582575_INM(m) \ + in_dword_masked(HWIO_REG_582575_ADDR, m) +#define HWIO_REG_582575_OUT(v) \ + out_dword(HWIO_REG_582575_ADDR, v) +#define HWIO_REG_582575_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_582575_ADDR, m, v,\ + HWIO_REG_582575_IN); +#define HWIO_REG_582575_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_582575_BASE_ADDR_SHFT 0 + +#define HWIO_REG_120885_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000764) +#define HWIO_REG_120885_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000764) +#define HWIO_REG_120885_RMSK 0x1ffff +#define HWIO_REG_120885_SHFT 0 +#define HWIO_REG_120885_IN in_dword_masked(\ + HWIO_REG_120885_ADDR, HWIO_REG_120885_RMSK) +#define HWIO_REG_120885_INM(m) \ + in_dword_masked(HWIO_REG_120885_ADDR, m) +#define HWIO_REG_120885_OUT(v) \ + out_dword(HWIO_REG_120885_ADDR, v) +#define HWIO_REG_120885_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_120885_ADDR, m, v,\ + HWIO_REG_120885_IN); +#define HWIO_REG_120885_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_120885_BASE_ADDR_SHFT 0 + +#define HWIO_REG_496067_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000768) +#define HWIO_REG_496067_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000768) +#define HWIO_REG_496067_RMSK 0x1ffff +#define HWIO_REG_496067_SHFT 0 +#define HWIO_REG_496067_IN in_dword_masked(\ + HWIO_REG_496067_ADDR, HWIO_REG_496067_RMSK) +#define HWIO_REG_496067_INM(m) \ + in_dword_masked(HWIO_REG_496067_ADDR, m) +#define HWIO_REG_496067_OUT(v) \ + out_dword(HWIO_REG_496067_ADDR, v) +#define HWIO_REG_496067_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_496067_ADDR, m, v,\ + HWIO_REG_496067_IN); +#define HWIO_REG_496067_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_496067_BASE_ADDR_SHFT 0 + +#define HWIO_REG_472919_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000076c) +#define HWIO_REG_472919_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000076c) +#define HWIO_REG_472919_RMSK 0x1ffff +#define HWIO_REG_472919_SHFT 0 +#define HWIO_REG_472919_IN in_dword_masked(\ + HWIO_REG_472919_ADDR, HWIO_REG_472919_RMSK) +#define HWIO_REG_472919_INM(m) \ + in_dword_masked(HWIO_REG_472919_ADDR, m) +#define HWIO_REG_472919_OUT(v) \ + out_dword(HWIO_REG_472919_ADDR, v) +#define HWIO_REG_472919_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_472919_ADDR, m, v,\ + HWIO_REG_472919_IN); +#define HWIO_REG_472919_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_472919_BASE_ADDR_SHFT 0 + +#define HWIO_REG_486985_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000770) +#define HWIO_REG_486985_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000770) +#define HWIO_REG_486985_RMSK 0x1ffff +#define HWIO_REG_486985_SHFT 0 +#define HWIO_REG_486985_IN in_dword_masked(\ + HWIO_REG_486985_ADDR, HWIO_REG_486985_RMSK) +#define HWIO_REG_486985_INM(m) \ + in_dword_masked(HWIO_REG_486985_ADDR, m) +#define HWIO_REG_486985_OUT(v) \ + out_dword(HWIO_REG_486985_ADDR, v) +#define HWIO_REG_486985_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_486985_ADDR, m, v,\ + HWIO_REG_486985_IN); +#define HWIO_REG_486985_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_486985_BASE_ADDR_SHFT 0 + +#define HWIO_REG_964692_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000774) +#define HWIO_REG_964692_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000774) +#define HWIO_REG_964692_RMSK 0x1ffff +#define HWIO_REG_964692_SHFT 0 +#define HWIO_REG_964692_IN in_dword_masked(\ + HWIO_REG_964692_ADDR, HWIO_REG_964692_RMSK) +#define HWIO_REG_964692_INM(m) \ + in_dword_masked(HWIO_REG_964692_ADDR, m) +#define HWIO_REG_964692_OUT(v) \ + out_dword(HWIO_REG_964692_ADDR, v) +#define HWIO_REG_964692_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_964692_ADDR, m, v,\ + HWIO_REG_964692_IN); +#define HWIO_REG_964692_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_964692_BASE_ADDR_SHFT 0 + +#define HWIO_REG_941116_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000778) +#define HWIO_REG_941116_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000778) +#define HWIO_REG_941116_RMSK 0x1ffff +#define HWIO_REG_941116_SHFT 0 +#define HWIO_REG_941116_IN in_dword_masked(\ + HWIO_REG_941116_ADDR, HWIO_REG_941116_RMSK) +#define HWIO_REG_941116_INM(m) \ + in_dword_masked(HWIO_REG_941116_ADDR, m) +#define HWIO_REG_941116_OUT(v) \ + out_dword(HWIO_REG_941116_ADDR, v) +#define HWIO_REG_941116_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_941116_ADDR, m, v,\ + HWIO_REG_941116_IN); +#define HWIO_REG_941116_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_941116_BASE_ADDR_SHFT 0 + +#define HWIO_REG_122567_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000077c) +#define HWIO_REG_122567_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000077c) +#define HWIO_REG_122567_RMSK 0x1ffff +#define HWIO_REG_122567_SHFT 0 +#define HWIO_REG_122567_IN in_dword_masked(\ + HWIO_REG_122567_ADDR, HWIO_REG_122567_RMSK) +#define HWIO_REG_122567_INM(m) \ + in_dword_masked(HWIO_REG_122567_ADDR, m) +#define HWIO_REG_122567_OUT(v) \ + out_dword(HWIO_REG_122567_ADDR, v) +#define HWIO_REG_122567_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_122567_ADDR, m, v,\ + HWIO_REG_122567_IN); +#define HWIO_REG_122567_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_122567_BASE_ADDR_SHFT 0 + +#define HWIO_REG_466192_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000780) +#define HWIO_REG_466192_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000780) +#define HWIO_REG_466192_RMSK 0x1ffff +#define HWIO_REG_466192_SHFT 0 +#define HWIO_REG_466192_IN in_dword_masked(\ + HWIO_REG_466192_ADDR, HWIO_REG_466192_RMSK) +#define HWIO_REG_466192_INM(m) \ + in_dword_masked(HWIO_REG_466192_ADDR, m) +#define HWIO_REG_466192_OUT(v) \ + out_dword(HWIO_REG_466192_ADDR, v) +#define HWIO_REG_466192_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_466192_ADDR, m, v,\ + HWIO_REG_466192_IN); +#define HWIO_REG_466192_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_466192_BASE_ADDR_SHFT 0 + +#define HWIO_REG_554890_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000784) +#define HWIO_REG_554890_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000784) +#define HWIO_REG_554890_RMSK 0x1ffff +#define HWIO_REG_554890_SHFT 0 +#define HWIO_REG_554890_IN in_dword_masked(\ + HWIO_REG_554890_ADDR, HWIO_REG_554890_RMSK) +#define HWIO_REG_554890_INM(m) \ + in_dword_masked(HWIO_REG_554890_ADDR, m) +#define HWIO_REG_554890_OUT(v) \ + out_dword(HWIO_REG_554890_ADDR, v) +#define HWIO_REG_554890_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_554890_ADDR, m, v,\ + HWIO_REG_554890_IN); +#define HWIO_REG_554890_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_554890_BASE_ADDR_SHFT 0 + +#define HWIO_REG_295616_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000788) +#define HWIO_REG_295616_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000788) +#define HWIO_REG_295616_RMSK 0x1ffff +#define HWIO_REG_295616_SHFT 0 +#define HWIO_REG_295616_IN in_dword_masked(\ + HWIO_REG_295616_ADDR, HWIO_REG_295616_RMSK) +#define HWIO_REG_295616_INM(m) \ + in_dword_masked(HWIO_REG_295616_ADDR, m) +#define HWIO_REG_295616_OUT(v) \ + out_dword(HWIO_REG_295616_ADDR, v) +#define HWIO_REG_295616_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_295616_ADDR, m, v,\ + HWIO_REG_295616_IN); +#define HWIO_REG_295616_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_295616_BASE_ADDR_SHFT 0 + +#define HWIO_REG_440836_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000078c) +#define HWIO_REG_440836_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000078c) +#define HWIO_REG_440836_RMSK 0x1ffff +#define HWIO_REG_440836_SHFT 0 +#define HWIO_REG_440836_IN in_dword_masked(\ + HWIO_REG_440836_ADDR, HWIO_REG_440836_RMSK) +#define HWIO_REG_440836_INM(m) \ + in_dword_masked(HWIO_REG_440836_ADDR, m) +#define HWIO_REG_440836_OUT(v) \ + out_dword(HWIO_REG_440836_ADDR, v) +#define HWIO_REG_440836_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_440836_ADDR, m, v,\ + HWIO_REG_440836_IN); +#define HWIO_REG_440836_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_440836_BASE_ADDR_SHFT 0 + +#define HWIO_REG_741154_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000790) +#define HWIO_REG_741154_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000790) +#define HWIO_REG_741154_RMSK 0x1ffff +#define HWIO_REG_741154_SHFT 0 +#define HWIO_REG_741154_IN in_dword_masked(\ + HWIO_REG_741154_ADDR,\ + HWIO_REG_741154_RMSK) +#define HWIO_REG_741154_INM(m) \ + in_dword_masked(HWIO_REG_741154_ADDR, m) +#define HWIO_REG_741154_OUT(v) \ + out_dword(HWIO_REG_741154_ADDR, v) +#define HWIO_REG_741154_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_741154_ADDR, m, v,\ + HWIO_REG_741154_IN); +#define HWIO_REG_741154_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_741154_BASE_ADDR_SHFT 0 + +#define HWIO_REG_753139_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000794) +#define HWIO_REG_753139_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000794) +#define HWIO_REG_753139_RMSK 0x1ffff +#define HWIO_REG_753139_SHFT 0 +#define HWIO_REG_753139_IN in_dword_masked(\ + HWIO_REG_753139_ADDR,\ + HWIO_REG_753139_RMSK) +#define HWIO_REG_753139_INM(m) \ + in_dword_masked(HWIO_REG_753139_ADDR, m) +#define HWIO_REG_753139_OUT(v) \ + out_dword(HWIO_REG_753139_ADDR, v) +#define HWIO_REG_753139_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_753139_ADDR, m, v,\ + HWIO_REG_753139_IN); +#define HWIO_REG_753139_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_753139_BASE_ADDR_SHFT 0 + +#define HWIO_REG_409994_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x00000798) +#define HWIO_REG_409994_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000798) +#define HWIO_REG_409994_RMSK 0x1ffff +#define HWIO_REG_409994_SHFT 0 +#define HWIO_REG_409994_IN in_dword_masked(\ + HWIO_REG_409994_ADDR,\ + HWIO_REG_409994_RMSK) +#define HWIO_REG_409994_INM(m) \ + in_dword_masked(HWIO_REG_409994_ADDR, m) +#define HWIO_REG_409994_OUT(v) \ + out_dword(HWIO_REG_409994_ADDR, v) +#define HWIO_REG_409994_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_409994_ADDR, m, v,\ + HWIO_REG_409994_IN); +#define HWIO_REG_409994_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_409994_BASE_ADDR_SHFT 0 + +#define HWIO_REG_492611_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000079c) +#define HWIO_REG_492611_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000079c) +#define HWIO_REG_492611_RMSK 0x1ffff +#define HWIO_REG_492611_SHFT 0 +#define HWIO_REG_492611_IN in_dword_masked(\ + HWIO_REG_492611_ADDR,\ + HWIO_REG_492611_RMSK) +#define HWIO_REG_492611_INM(m) \ + in_dword_masked(HWIO_REG_492611_ADDR, m) +#define HWIO_REG_492611_OUT(v) \ + out_dword(HWIO_REG_492611_ADDR, v) +#define HWIO_REG_492611_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_492611_ADDR, m, v,\ + HWIO_REG_492611_IN); +#define HWIO_REG_492611_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_492611_BASE_ADDR_SHFT 0 + +#define HWIO_REG_91427_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007a0) +#define HWIO_REG_91427_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007a0) +#define HWIO_REG_91427_RMSK 0x1ffff +#define HWIO_REG_91427_SHFT 0 +#define HWIO_REG_91427_IN in_dword_masked(\ + HWIO_REG_91427_ADDR,\ + HWIO_REG_91427_RMSK) +#define HWIO_REG_91427_INM(m) \ + in_dword_masked(HWIO_REG_91427_ADDR, m) +#define HWIO_REG_91427_OUT(v) \ + out_dword(HWIO_REG_91427_ADDR, v) +#define HWIO_REG_91427_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_91427_ADDR, m, v,\ + HWIO_REG_91427_IN); +#define HWIO_REG_91427_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_91427_BASE_ADDR_SHFT 0 + +#define HWIO_REG_617696_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007a4) +#define HWIO_REG_617696_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007a4) +#define HWIO_REG_617696_RMSK 0x1ffff +#define HWIO_REG_617696_SHFT 0 +#define HWIO_REG_617696_IN in_dword_masked(\ + HWIO_REG_617696_ADDR,\ + HWIO_REG_617696_RMSK) +#define HWIO_REG_617696_INM(m) \ + in_dword_masked(HWIO_REG_617696_ADDR, m) +#define HWIO_REG_617696_OUT(v) \ + out_dword(HWIO_REG_617696_ADDR, v) +#define HWIO_REG_617696_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_617696_ADDR, m, v,\ + HWIO_REG_617696_IN); +#define HWIO_REG_617696_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_617696_BASE_ADDR_SHFT 0 + +#define HWIO_REG_459602_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007a8) +#define HWIO_REG_459602_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007a8) +#define HWIO_REG_459602_RMSK 0x1ffff +#define HWIO_REG_459602_SHFT 0 +#define HWIO_REG_459602_IN in_dword_masked(\ + HWIO_REG_459602_ADDR,\ + HWIO_REG_459602_RMSK) +#define HWIO_REG_459602_INM(m) \ + in_dword_masked(HWIO_REG_459602_ADDR, m) +#define HWIO_REG_459602_OUT(v) \ + out_dword(HWIO_REG_459602_ADDR, v) +#define HWIO_REG_459602_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_459602_ADDR, m, v,\ + HWIO_REG_459602_IN); +#define HWIO_REG_459602_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_459602_BASE_ADDR_SHFT 0 + +#define HWIO_REG_758_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007ac) +#define HWIO_REG_758_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007ac) +#define HWIO_REG_758_RMSK 0x1ffff +#define HWIO_REG_758_SHFT 0 +#define HWIO_REG_758_IN in_dword_masked(\ + HWIO_REG_758_ADDR,\ + HWIO_REG_758_RMSK) +#define HWIO_REG_758_INM(m) \ + in_dword_masked(HWIO_REG_758_ADDR, m) +#define HWIO_REG_758_OUT(v) \ + out_dword(HWIO_REG_758_ADDR, v) +#define HWIO_REG_758_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_758_ADDR, m, v,\ + HWIO_REG_758_IN); +#define HWIO_REG_758_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_758_BASE_ADDR_SHFT 0 + +#define HWIO_REG_710606_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007b0) +#define HWIO_REG_710606_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007b0) +#define HWIO_REG_710606_RMSK 0x1ffff +#define HWIO_REG_710606_SHFT 0 +#define HWIO_REG_710606_IN in_dword_masked(\ + HWIO_REG_710606_ADDR,\ + HWIO_REG_710606_RMSK) +#define HWIO_REG_710606_INM(m) \ + in_dword_masked(HWIO_REG_710606_ADDR, m) +#define HWIO_REG_710606_OUT(v) \ + out_dword(HWIO_REG_710606_ADDR, v) +#define HWIO_REG_710606_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_710606_ADDR, m, v,\ + HWIO_REG_710606_IN); +#define HWIO_REG_710606_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_710606_BASE_ADDR_SHFT 0 + +#define HWIO_REG_122975_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007b4) +#define HWIO_REG_122975_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007b4) +#define HWIO_REG_122975_RMSK 0x1ffff +#define HWIO_REG_122975_SHFT 0 +#define HWIO_REG_122975_IN in_dword_masked(\ + HWIO_REG_122975_ADDR,\ + HWIO_REG_122975_RMSK) +#define HWIO_REG_122975_INM(m)\ + in_dword_masked(HWIO_REG_122975_ADDR, m) +#define HWIO_REG_122975_OUT(v)\ + out_dword(HWIO_REG_122975_ADDR, v) +#define HWIO_REG_122975_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_122975_ADDR, m, v,\ + HWIO_REG_122975_IN); +#define HWIO_REG_122975_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_122975_BASE_ADDR_SHFT 0 + +#define HWIO_REG_860205_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007b8) +#define HWIO_REG_860205_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007b8) +#define HWIO_REG_860205_RMSK 0x1ffff +#define HWIO_REG_860205_SHFT 0 +#define HWIO_REG_860205_IN in_dword_masked(\ + HWIO_REG_860205_ADDR,\ + HWIO_REG_860205_RMSK) +#define HWIO_REG_860205_INM(m) \ + in_dword_masked(HWIO_REG_860205_ADDR, m) +#define HWIO_REG_860205_OUT(v) \ + out_dword(HWIO_REG_860205_ADDR, v) +#define HWIO_REG_860205_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_860205_ADDR, m, v,\ + HWIO_REG_860205_IN); +#define HWIO_REG_860205_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_860205_BASE_ADDR_SHFT 0 + +#define HWIO_REG_366154_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007bc) +#define HWIO_REG_366154_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007bc) +#define HWIO_REG_366154_RMSK 0x1ffff +#define HWIO_REG_366154_SHFT 0 +#define HWIO_REG_366154_IN in_dword_masked(\ + HWIO_REG_366154_ADDR,\ + HWIO_REG_366154_RMSK) +#define HWIO_REG_366154_INM(m) \ + in_dword_masked(HWIO_REG_366154_ADDR, m) +#define HWIO_REG_366154_OUT(v) \ + out_dword(HWIO_REG_366154_ADDR, v) +#define HWIO_REG_366154_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_366154_ADDR, m, v,\ + HWIO_REG_366154_IN); +#define HWIO_REG_366154_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_366154_BASE_ADDR_SHFT 0 + +#define HWIO_REG_632247_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007c0) +#define HWIO_REG_632247_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007c0) +#define HWIO_REG_632247_RMSK 0x1ffff +#define HWIO_REG_632247_SHFT 0 +#define HWIO_REG_632247_IN in_dword_masked(\ + HWIO_REG_632247_ADDR,\ + HWIO_REG_632247_RMSK) +#define HWIO_REG_632247_INM(m) \ + in_dword_masked(HWIO_REG_632247_ADDR, m) +#define HWIO_REG_632247_OUT(v) \ + out_dword(HWIO_REG_632247_ADDR, v) +#define HWIO_REG_632247_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_632247_ADDR, m, v,\ + HWIO_REG_632247_IN); +#define HWIO_REG_632247_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_632247_BASE_ADDR_SHFT 0 + +#define HWIO_REG_709312_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007c4) +#define HWIO_REG_709312_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007c4) +#define HWIO_REG_709312_RMSK 0x1ffff +#define HWIO_REG_709312_SHFT 0 +#define HWIO_REG_709312_IN in_dword_masked(\ + HWIO_REG_709312_ADDR,\ + HWIO_REG_709312_RMSK) +#define HWIO_REG_709312_INM(m) \ + in_dword_masked(HWIO_REG_709312_ADDR, m) +#define HWIO_REG_709312_OUT(v) \ + out_dword(HWIO_REG_709312_ADDR, v) +#define HWIO_REG_709312_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_709312_ADDR, m, v,\ + HWIO_REG_709312_IN); +#define HWIO_REG_709312_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_709312_BASE_ADDR_SHFT 0 + +#define HWIO_REG_891367_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007c8) +#define HWIO_REG_891367_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007c8) +#define HWIO_REG_891367_RMSK 0x1ffff +#define HWIO_REG_891367_SHFT 0 +#define HWIO_REG_891367_IN in_dword_masked(\ + HWIO_REG_891367_ADDR,\ + HWIO_REG_891367_RMSK) +#define HWIO_REG_891367_INM(m) \ + in_dword_masked(HWIO_REG_891367_ADDR, m) +#define HWIO_REG_891367_OUT(v) \ + out_dword(HWIO_REG_891367_ADDR, v) +#define HWIO_REG_891367_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_891367_ADDR, m, v,\ + HWIO_REG_891367_IN); +#define HWIO_REG_891367_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_891367_BASE_ADDR_SHFT 0 + +#define HWIO_REG_628746_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007cc) +#define HWIO_REG_628746_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007cc) +#define HWIO_REG_628746_RMSK 0x1ffff +#define HWIO_REG_628746_SHFT 0 +#define HWIO_REG_628746_IN in_dword_masked(\ + HWIO_REG_628746_ADDR,\ + HWIO_REG_628746_RMSK) +#define HWIO_REG_628746_INM(m) \ + in_dword_masked(HWIO_REG_628746_ADDR, m) +#define HWIO_REG_628746_OUT(v) \ + out_dword(HWIO_REG_628746_ADDR, v) +#define HWIO_REG_628746_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_628746_ADDR, m, v,\ + HWIO_REG_628746_IN); +#define HWIO_REG_628746_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_628746_BASE_ADDR_SHFT 0 + +#define HWIO_REG_821010_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007d0) +#define HWIO_REG_821010_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007d0) +#define HWIO_REG_821010_RMSK 0x1ffff +#define HWIO_REG_821010_SHFT 0 +#define HWIO_REG_821010_IN in_dword_masked(\ + HWIO_REG_821010_ADDR,\ + HWIO_REG_821010_RMSK) +#define HWIO_REG_821010_INM(m) \ + in_dword_masked(HWIO_REG_821010_ADDR, m) +#define HWIO_REG_821010_OUT(v) \ + out_dword(HWIO_REG_821010_ADDR, v) +#define HWIO_REG_821010_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_821010_ADDR, m, v,\ + HWIO_REG_821010_IN); +#define HWIO_REG_821010_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_821010_BASE_ADDR_SHFT 0 + +#define HWIO_REG_902098_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007d4) +#define HWIO_REG_902098_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007d4) +#define HWIO_REG_902098_RMSK 0x1ffff +#define HWIO_REG_902098_SHFT 0 +#define HWIO_REG_902098_IN in_dword_masked(\ + HWIO_REG_902098_ADDR,\ + HWIO_REG_902098_RMSK) +#define HWIO_REG_902098_INM(m) \ + in_dword_masked(HWIO_REG_902098_ADDR, m) +#define HWIO_REG_902098_OUT(v) \ + out_dword(HWIO_REG_902098_ADDR, v) +#define HWIO_REG_902098_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_902098_ADDR, m, v,\ + HWIO_REG_902098_IN); +#define HWIO_REG_902098_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_902098_BASE_ADDR_SHFT 0 + +#define HWIO_REG_939091_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007d8) +#define HWIO_REG_939091_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007d8) +#define HWIO_REG_939091_RMSK 0x1ffff +#define HWIO_REG_939091_SHFT 0 +#define HWIO_REG_939091_IN in_dword_masked(\ + HWIO_REG_939091_ADDR,\ + HWIO_REG_939091_RMSK) +#define HWIO_REG_939091_INM(m) \ + in_dword_masked(HWIO_REG_939091_ADDR, m) +#define HWIO_REG_939091_OUT(v) \ + out_dword(HWIO_REG_939091_ADDR, v) +#define HWIO_REG_939091_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_939091_ADDR, m, v,\ + HWIO_REG_939091_IN); +#define HWIO_REG_939091_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_939091_BASE_ADDR_SHFT 0 + +#define HWIO_REG_261074_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007dc) +#define HWIO_REG_261074_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007dc) +#define HWIO_REG_261074_RMSK 0x1ffff +#define HWIO_REG_261074_SHFT 0 +#define HWIO_REG_261074_IN in_dword_masked(\ + HWIO_REG_261074_ADDR,\ + HWIO_REG_261074_RMSK) +#define HWIO_REG_261074_INM(m) \ + in_dword_masked(HWIO_REG_261074_ADDR, m) +#define HWIO_REG_261074_OUT(v) \ + out_dword(HWIO_REG_261074_ADDR, v) +#define HWIO_REG_261074_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_261074_ADDR, m, v,\ + HWIO_REG_261074_IN); +#define HWIO_REG_261074_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_261074_BASE_ADDR_SHFT 0 + +#define HWIO_REG_157718_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007e0) +#define HWIO_REG_157718_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007e0) +#define HWIO_REG_157718_RMSK 0x1ffff +#define HWIO_REG_157718_SHFT 0 +#define HWIO_REG_157718_IN in_dword_masked(\ + HWIO_REG_157718_ADDR,\ + HWIO_REG_157718_RMSK) +#define HWIO_REG_157718_INM(m) \ + in_dword_masked(HWIO_REG_157718_ADDR, m) +#define HWIO_REG_157718_OUT(v) \ + out_dword(HWIO_REG_157718_ADDR, v) +#define HWIO_REG_157718_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_157718_ADDR, m, v,\ + HWIO_REG_157718_IN); +#define HWIO_REG_5552391_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_5552391_BASE_ADDR_SHFT 0 + +#define HWIO_REG_148889_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007e8) +#define HWIO_REG_148889_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007e8) +#define HWIO_REG_148889_RMSK 0x1ffff +#define HWIO_REG_148889_SHFT 0 +#define HWIO_REG_148889_IN in_dword_masked(\ + HWIO_REG_148889_ADDR,\ + HWIO_REG_148889_RMSK) +#define HWIO_REG_148889_INM(m) \ + in_dword_masked(HWIO_REG_148889_ADDR, m) +#define HWIO_REG_148889_OUT(v) \ + out_dword(HWIO_REG_148889_ADDR, v) +#define HWIO_REG_148889_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_148889_ADDR, m, v,\ + HWIO_REG_148889_IN); +#define HWIO_REG_148889_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_148889_BASE_ADDR_SHFT 0 + +#define HWIO_REG_396380_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007ec) +#define HWIO_REG_396380_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007ec) +#define HWIO_REG_396380_RMSK 0x1ffff +#define HWIO_REG_396380_SHFT 0 +#define HWIO_REG_396380_IN in_dword_masked(\ + HWIO_REG_396380_ADDR,\ + HWIO_REG_396380_RMSK) +#define HWIO_REG_396380_INM(m) \ + in_dword_masked(HWIO_REG_396380_ADDR, m) +#define HWIO_REG_396380_OUT(v) \ + out_dword(HWIO_REG_396380_ADDR, v) +#define HWIO_REG_396380_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_396380_ADDR, m, v,\ + HWIO_REG_396380_IN); +#define HWIO_REG_396380_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_396380_BASE_ADDR_SHFT 0 + +#define HWIO_REG_351005_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007f0) +#define HWIO_REG_351005_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007f0) +#define HWIO_REG_351005_RMSK 0x1ffff +#define HWIO_REG_351005_SHFT 0 +#define HWIO_REG_351005_IN in_dword_masked(\ + HWIO_REG_351005_ADDR,\ + HWIO_REG_351005_RMSK) +#define HWIO_REG_351005_INM(m) \ + in_dword_masked(HWIO_REG_351005_ADDR, m) +#define HWIO_REG_351005_OUT(v) \ + out_dword(HWIO_REG_351005_ADDR, v) +#define HWIO_REG_351005_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_351005_ADDR, m, v,\ + HWIO_REG_351005_IN); +#define HWIO_REG_351005_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_351005_BASE_ADDR_SHFT 0 + +#define HWIO_REG_863263_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007f4) +#define HWIO_REG_863263_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007f4) +#define HWIO_REG_863263_RMSK 0x1ffff +#define HWIO_REG_863263_SHFT 0 +#define HWIO_REG_863263_IN in_dword_masked(\ + HWIO_REG_863263_ADDR,\ + HWIO_REG_863263_RMSK) +#define HWIO_REG_863263_INM(m) \ + in_dword_masked(HWIO_REG_863263_ADDR, m) +#define HWIO_REG_863263_OUT(v) \ + out_dword(HWIO_REG_863263_ADDR, v) +#define HWIO_REG_863263_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_863263_ADDR, m, v,\ + HWIO_REG_863263_IN); +#define HWIO_REG_863263_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_863263_BASE_ADDR_SHFT 0 + +#define HWIO_REG_135009_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007f8) +#define HWIO_REG_135009_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007f8) +#define HWIO_REG_135009_RMSK 0x1ffff +#define HWIO_REG_135009_SHFT 0 +#define HWIO_REG_135009_IN in_dword_masked(\ + HWIO_REG_135009_ADDR,\ + HWIO_REG_135009_RMSK) +#define HWIO_REG_135009_INM(m) \ + in_dword_masked(HWIO_REG_135009_ADDR, m) +#define HWIO_REG_135009_OUT(v) \ + out_dword(HWIO_REG_135009_ADDR, v) +#define HWIO_REG_135009_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_135009_ADDR, m, v,\ + HWIO_REG_135009_IN); +#define HWIO_REG_135009_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_135009_BASE_ADDR_SHFT 0 + +#define HWIO_REG_923883_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x000007fc) +#define HWIO_REG_923883_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000007fc) +#define HWIO_REG_923883_RMSK 0x1ffff +#define HWIO_REG_923883_SHFT 0 +#define HWIO_REG_923883_IN in_dword_masked(\ + HWIO_REG_923883_ADDR,\ + HWIO_REG_923883_RMSK) +#define HWIO_REG_923883_INM(m) \ + in_dword_masked(HWIO_REG_923883_ADDR, m) +#define HWIO_REG_923883_OUT(v) \ + out_dword(HWIO_REG_923883_ADDR, v) +#define HWIO_REG_923883_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_923883_ADDR, m, v,\ + HWIO_REG_923883_IN); +#define HWIO_REG_923883_BASE_ADDR_BMSK 0x1ffff +#define HWIO_REG_923883_BASE_ADDR_SHFT 0 + +#define HWIO_REG_934655_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000818) +#define HWIO_REG_934655_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000818) +#define HWIO_REG_934655_RMSK 0x1fff +#define HWIO_REG_934655_SHFT 0 +#define HWIO_REG_934655_IN \ + in_dword_masked(HWIO_REG_934655_ADDR, HWIO_REG_934655_RMSK) +#define HWIO_REG_934655_INM(m) \ + in_dword_masked(HWIO_REG_934655_ADDR, m) +#define HWIO_REG_934655_OUT(v) \ + out_dword(HWIO_REG_934655_ADDR, v) +#define HWIO_REG_934655_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_934655_ADDR, m, v, HWIO_REG_934655_IN); +#define HWIO_REG_934655_FRAME_WIDTH_BMSK 0x1fff +#define HWIO_REG_934655_FRAME_WIDTH_SHFT 0 + +#define HWIO_REG_179070_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000081c) +#define HWIO_REG_179070_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000081c) +#define HWIO_REG_179070_RMSK 0x1fff +#define HWIO_REG_179070_SHFT 0 +#define HWIO_REG_179070_IN in_dword_masked(\ + HWIO_REG_179070_ADDR, HWIO_REG_179070_RMSK) +#define HWIO_REG_179070_INM(m) \ + in_dword_masked(HWIO_REG_179070_ADDR, m) +#define HWIO_REG_179070_OUT(v) \ + out_dword(HWIO_REG_179070_ADDR, v) +#define HWIO_REG_179070_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_179070_ADDR, m, v, HWIO_REG_179070_IN); +#define HWIO_REG_179070_FRAME_HEIGHT_BMSK 0x1fff +#define HWIO_REG_179070_FRAME_HEIGHT_SHFT 0 + +#define HWIO_REG_63643_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000830) +#define HWIO_REG_63643_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000830) +#define HWIO_REG_63643_RMSK 0xff3f +#define HWIO_REG_63643_SHFT 0 +#define HWIO_REG_63643_IN in_dword_masked(\ + HWIO_REG_63643_ADDR, HWIO_REG_63643_RMSK) +#define HWIO_REG_63643_INM(m) \ + in_dword_masked(HWIO_REG_63643_ADDR, m) +#define HWIO_REG_63643_OUT(v) \ + out_dword(HWIO_REG_63643_ADDR, v) +#define HWIO_REG_63643_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_63643_ADDR, m, v, HWIO_REG_63643_IN); +#define HWIO_REG_63643_LEVEL_BMSK 0xff00 +#define HWIO_REG_63643_LEVEL_SHFT 0x8 +#define HWIO_REG_63643_PROFILE_BMSK 0x3f +#define HWIO_REG_63643_PROFILE_SHFT 0 + +#define HWIO_REG_786024_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000083c) +#define HWIO_REG_786024_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000083c) +#define HWIO_REG_786024_RMSK 0x1 +#define HWIO_REG_786024_SHFT 0 +#define HWIO_REG_786024_IN in_dword_masked(\ + HWIO_REG_786024_ADDR, HWIO_REG_786024_RMSK) +#define HWIO_REG_786024_INM(m) \ + in_dword_masked(HWIO_REG_786024_ADDR, m) +#define HWIO_REG_786024_OUT(v) \ + out_dword(HWIO_REG_786024_ADDR, v) +#define HWIO_REG_786024_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_786024_ADDR, m, v, HWIO_REG_786024_IN); +#define HWIO_REG_786024_FIELD_BMSK 0x1 +#define HWIO_REG_786024_FIELD_SHFT 0 + +#define HWIO_REG_152500_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000848) +#define HWIO_REG_152500_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000848) +#define HWIO_REG_152500_RMSK 0x3 +#define HWIO_REG_152500_SHFT 0 +#define HWIO_REG_152500_IN in_dword_masked(\ + HWIO_REG_152500_ADDR, HWIO_REG_152500_RMSK) +#define HWIO_REG_152500_INM(m) \ + in_dword_masked(HWIO_REG_152500_ADDR, m) +#define HWIO_REG_152500_OUT(v) \ + out_dword(HWIO_REG_152500_ADDR, v) +#define HWIO_REG_152500_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_152500_ADDR, m, v, HWIO_REG_152500_IN); +#define HWIO_REG_152500_LF_CONTROL_BMSK 0x3 +#define HWIO_REG_152500_LF_CONTROL_SHFT 0 + +#define HWIO_REG_266285_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000084c) +#define HWIO_REG_266285_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000084c) +#define HWIO_REG_266285_RMSK 0x1f +#define HWIO_REG_266285_SHFT 0 +#define HWIO_REG_266285_IN in_dword_masked(\ + HWIO_REG_266285_ADDR, HWIO_REG_266285_RMSK) +#define HWIO_REG_266285_INM(m) \ + in_dword_masked(HWIO_REG_266285_ADDR, m) +#define HWIO_REG_266285_OUT(v) \ + out_dword(HWIO_REG_266285_ADDR, v) +#define HWIO_REG_266285_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_266285_ADDR, m, v, HWIO_REG_266285_IN); +#define HWIO_REG_266285_LF_ALPHAS_OFF_BMSK 0x1f +#define HWIO_REG_266285_LF_ALPHAS_OFF_SHFT 0 + +#define HWIO_REG_964731_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000850) +#define HWIO_REG_964731_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000850) +#define HWIO_REG_964731_RMSK 0x1f +#define HWIO_REG_964731_SHFT 0 +#define HWIO_REG_964731_IN in_dword_masked(\ + HWIO_REG_964731_ADDR, HWIO_REG_964731_RMSK) +#define HWIO_REG_964731_INM(m) \ + in_dword_masked(HWIO_REG_964731_ADDR, m) +#define HWIO_REG_964731_OUT(v) \ + out_dword(HWIO_REG_964731_ADDR, v) +#define HWIO_REG_964731_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_964731_ADDR, m, v, HWIO_REG_964731_IN); +#define HWIO_REG_964731_LF_BETA_OFF_BMSK 0x1f +#define HWIO_REG_964731_LF_BETA_OFF_SHFT 0 + +#define HWIO_REG_919924_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000c30) +#define HWIO_REG_919924_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000c30) +#define HWIO_REG_919924_RMSK 0xffffffff +#define HWIO_REG_919924_SHFT 0 +#define HWIO_REG_919924_IN in_dword_masked(\ + HWIO_REG_919924_ADDR, HWIO_REG_919924_RMSK) +#define HWIO_REG_919924_INM(m) \ + in_dword_masked(HWIO_REG_919924_ADDR, m) +#define HWIO_REG_919924_OUT(v) \ + out_dword(HWIO_REG_919924_ADDR, v) +#define HWIO_REG_919924_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_919924_ADDR, m, v, HWIO_REG_919924_IN); +#define HWIO_REG_919924_VIDC_QP_OFFSET_BMSK 0xffffffff +#define HWIO_REG_919924_VIDC_QP_OFFSET_SHFT 0 + +#define HWIO_REG_143629_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00000c34) +#define HWIO_REG_143629_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00000c34) +#define HWIO_REG_143629_RMSK 0x1 +#define HWIO_REG_143629_SHFT 0 +#define HWIO_REG_143629_IN in_dword_masked(\ + HWIO_REG_143629_ADDR, HWIO_REG_143629_RMSK) +#define HWIO_REG_143629_INM(m) \ + in_dword_masked(HWIO_REG_143629_ADDR, m) +#define HWIO_REG_143629_OUT(v) \ + out_dword(HWIO_REG_143629_ADDR, v) +#define HWIO_REG_143629_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_143629_ADDR, m, v, HWIO_REG_143629_IN); +#define HWIO_REG_143629_REG_143629_BMSK 0x1 +#define HWIO_REG_143629_REG_143629_SHFT 0 + +#define HWIO_REG_607589_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002000) +#define HWIO_REG_607589_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002000) +#define HWIO_REG_607589_RMSK 0xffffffff +#define HWIO_REG_607589_SHFT 0 +#define HWIO_REG_607589_IN in_dword_masked(\ + HWIO_REG_607589_ADDR, HWIO_REG_607589_RMSK) +#define HWIO_REG_607589_INM(m) \ + in_dword_masked(HWIO_REG_607589_ADDR, m) +#define HWIO_REG_607589_OUT(v) \ + out_dword(HWIO_REG_607589_ADDR, v) +#define HWIO_REG_607589_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_607589_ADDR, m, v, HWIO_REG_607589_IN); +#define HWIO_REG_607589_RTN_CHID_BMSK 0xffffffff +#define HWIO_REG_607589_RTN_CHID_SHFT 0 + +#define HWIO_REG_845544_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002004) +#define HWIO_REG_845544_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002004) +#define HWIO_REG_845544_RMSK 0xffffffff +#define HWIO_REG_845544_SHFT 0 +#define HWIO_REG_845544_IN in_dword_masked(\ + HWIO_REG_845544_ADDR, HWIO_REG_845544_RMSK) +#define HWIO_REG_845544_INM(m) \ + in_dword_masked(HWIO_REG_845544_ADDR, m) +#define HWIO_REG_845544_OUT(v) \ + out_dword(HWIO_REG_845544_ADDR, v) +#define HWIO_REG_845544_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_845544_ADDR, m, v, HWIO_REG_845544_IN); +#define HWIO_REG_845544_REG_845544_BMSK 0xffffffff +#define HWIO_REG_845544_REG_845544_SHFT 0 + +#define HWIO_REG_859906_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002008) +#define HWIO_REG_859906_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002008) +#define HWIO_REG_859906_RMSK 0xffffffff +#define HWIO_REG_859906_SHFT 0 +#define HWIO_REG_859906_IN in_dword_masked(\ + HWIO_REG_859906_ADDR, HWIO_REG_859906_RMSK) +#define HWIO_REG_859906_INM(m) \ + in_dword_masked(HWIO_REG_859906_ADDR, m) +#define HWIO_REG_859906_OUT(v) \ + out_dword(HWIO_REG_859906_ADDR, v) +#define HWIO_REG_859906_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_859906_ADDR, m, v, HWIO_REG_859906_IN); +#define HWIO_REG_859906_REG_859906_BMSK 0xffffffff +#define HWIO_REG_859906_REG_859906_SHFT 0 + +#define HWIO_REG_490078_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000200c) +#define HWIO_REG_490078_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000200c) +#define HWIO_REG_490078_RMSK 0xffffffff +#define HWIO_REG_490078_SHFT 0 +#define HWIO_REG_490078_IN in_dword_masked(\ + HWIO_REG_490078_ADDR, HWIO_REG_490078_RMSK) +#define HWIO_REG_490078_INM(m) \ + in_dword_masked(HWIO_REG_490078_ADDR, m) +#define HWIO_REG_490078_OUT(v) \ + out_dword(HWIO_REG_490078_ADDR, v) +#define HWIO_REG_490078_OUTM(m, v) \ + out_dword_masked_ns(HWIO_REG_490078_ADDR, m, v,\ + HWIO_REG_490078_IN); +#define HWIO_REG_490078_REG_490078_BMSK 0xffffffff +#define HWIO_REG_490078_REG_490078_SHFT 0 + +#define HWIO_REG_640904_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002010) +#define HWIO_REG_640904_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002010) +#define HWIO_REG_640904_RMSK 0xffffffff +#define HWIO_REG_640904_SHFT 0 +#define HWIO_REG_640904_IN in_dword_masked(\ + HWIO_REG_640904_ADDR, HWIO_REG_640904_RMSK) +#define HWIO_REG_640904_INM(m) \ + in_dword_masked(HWIO_REG_640904_ADDR, m) +#define HWIO_REG_640904_OUT(v) \ + out_dword(HWIO_REG_640904_ADDR, v) +#define HWIO_REG_640904_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_640904_ADDR, m, v, HWIO_REG_640904_IN); +#define HWIO_REG_640904_REG_640904_BMSK 0xffffffff +#define HWIO_REG_640904_REG_640904_SHFT 0 + +#define HWIO_REG_60114_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002014) +#define HWIO_REG_60114_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002014) +#define HWIO_REG_60114_RMSK 0xffffffff +#define HWIO_REG_60114_SHFT 0 +#define HWIO_REG_60114_IN in_dword_masked(\ + HWIO_REG_60114_ADDR, HWIO_REG_60114_RMSK) +#define HWIO_REG_60114_INM(m) \ + in_dword_masked(HWIO_REG_60114_ADDR, m) +#define HWIO_REG_60114_OUT(v) \ + out_dword(HWIO_REG_60114_ADDR, v) +#define HWIO_REG_60114_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_60114_ADDR, m, v, HWIO_REG_60114_IN); +#define HWIO_REG_60114_REG_60114_BMSK 0xffffffff +#define HWIO_REG_60114_REG_60114_SHFT 0 + +#define HWIO_REG_489688_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002018) +#define HWIO_REG_489688_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002018) +#define HWIO_REG_489688_RMSK 0xffffffff +#define HWIO_REG_489688_SHFT 0 +#define HWIO_REG_489688_IN in_dword_masked(\ + HWIO_REG_489688_ADDR, HWIO_REG_489688_RMSK) +#define HWIO_REG_489688_INM(m) \ + in_dword_masked(HWIO_REG_489688_ADDR, m) +#define HWIO_REG_489688_OUT(v) \ + out_dword(HWIO_REG_489688_ADDR, v) +#define HWIO_REG_489688_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_489688_ADDR, m, v, HWIO_REG_489688_IN); +#define HWIO_REG_489688_REG_489688_BMSK 0xffffffff +#define HWIO_REG_489688_REG_489688_SHFT 0 + +#define HWIO_REG_853667_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000201c) +#define HWIO_REG_853667_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000201c) +#define HWIO_REG_853667_RMSK 0xffffffff +#define HWIO_REG_853667_SHFT 0 +#define HWIO_REG_853667_IN in_dword_masked(\ + HWIO_REG_853667_ADDR, HWIO_REG_853667_RMSK) +#define HWIO_REG_853667_INM(m) \ + in_dword_masked(HWIO_REG_853667_ADDR, m) +#define HWIO_REG_853667_OUT(v) \ + out_dword(HWIO_REG_853667_ADDR, v) +#define HWIO_REG_853667_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_853667_ADDR, m, v, HWIO_REG_853667_IN); +#define HWIO_REG_853667_REG_853667_BMSK 0xffffffff +#define HWIO_REG_853667_REG_853667_SHFT 0 + +#define HWIO_REG_760102_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002020) +#define HWIO_REG_760102_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002020) +#define HWIO_REG_760102_RMSK 0xffffffff +#define HWIO_REG_760102_SHFT 0 +#define HWIO_REG_760102_IN in_dword_masked(\ + HWIO_REG_760102_ADDR, HWIO_REG_760102_RMSK) +#define HWIO_REG_760102_INM(m) \ + in_dword_masked(HWIO_REG_760102_ADDR, m) +#define HWIO_REG_760102_OUT(v) \ + out_dword(HWIO_REG_760102_ADDR, v) +#define HWIO_REG_760102_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_760102_ADDR, m, v, HWIO_REG_760102_IN); +#define HWIO_REG_760102_REG_760102_BMSK 0xffffffff +#define HWIO_REG_760102_REG_760102_SHFT 0 + +#define HWIO_REG_378318_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002024) +#define HWIO_REG_378318_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002024) +#define HWIO_REG_378318_RMSK 0xffffffff +#define HWIO_REG_378318_SHFT 0 +#define HWIO_REG_378318_IN in_dword_masked(\ + HWIO_REG_378318_ADDR, HWIO_REG_378318_RMSK) +#define HWIO_REG_378318_INM(m) \ + in_dword_masked(HWIO_REG_378318_ADDR, m) +#define HWIO_REG_378318_OUT(v) \ + out_dword(HWIO_REG_378318_ADDR, v) +#define HWIO_REG_378318_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_378318_ADDR, m, v, HWIO_REG_378318_IN); +#define HWIO_REG_378318_REG_378318_BMSK 0xffffffff +#define HWIO_REG_378318_REG_378318_SHFT 0 + +#define HWIO_REG_203487_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002028) +#define HWIO_REG_203487_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002028) +#define HWIO_REG_203487_RMSK 0xffffffff +#define HWIO_REG_203487_SHFT 0 +#define HWIO_REG_203487_IN in_dword_masked(\ + HWIO_REG_203487_ADDR, HWIO_REG_203487_RMSK) +#define HWIO_REG_203487_INM(m) \ + in_dword_masked(HWIO_REG_203487_ADDR, m) +#define HWIO_REG_203487_OUT(v) \ + out_dword(HWIO_REG_203487_ADDR, v) +#define HWIO_REG_203487_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_203487_ADDR, m, v, HWIO_REG_203487_IN); +#define HWIO_REG_203487_REG_203487_BMSK 0xffffffff +#define HWIO_REG_203487_REG_203487_SHFT 0 + +#define HWIO_REG_692991_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000202c) +#define HWIO_REG_692991_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000202c) +#define HWIO_REG_692991_RMSK 0xffffffff +#define HWIO_REG_692991_SHFT 0 +#define HWIO_REG_692991_IN in_dword_masked(\ + HWIO_REG_692991_ADDR, HWIO_REG_692991_RMSK) +#define HWIO_REG_692991_INM(m) \ + in_dword_masked(HWIO_REG_692991_ADDR, m) +#define HWIO_REG_692991_OUT(v) \ + out_dword(HWIO_REG_692991_ADDR, v) +#define HWIO_REG_692991_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_692991_ADDR, m, v, HWIO_REG_692991_IN); +#define HWIO_REG_692991_REG_692991_BMSK 0xffffffff +#define HWIO_REG_692991_REG_692991_SHFT 0 + +#define HWIO_REG_161740_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002030) +#define HWIO_REG_161740_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002030) +#define HWIO_REG_161740_RMSK 0xffffffff +#define HWIO_REG_161740_SHFT 0 +#define HWIO_REG_161740_IN in_dword_masked(\ + HWIO_REG_161740_ADDR, HWIO_REG_161740_RMSK) +#define HWIO_REG_161740_INM(m) \ + in_dword_masked(HWIO_REG_161740_ADDR, m) +#define HWIO_REG_161740_OUT(v) \ + out_dword(HWIO_REG_161740_ADDR, v) +#define HWIO_REG_161740_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_161740_ADDR, m, v, HWIO_REG_161740_IN); +#define HWIO_REG_161740_REG_161740_BMSK 0xffffffff +#define HWIO_REG_161740_REG_161740_SHFT 0 + +#define HWIO_REG_930239_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002034) +#define HWIO_REG_930239_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002034) +#define HWIO_REG_930239_RMSK 0xffffffff +#define HWIO_REG_930239_SHFT 0 +#define HWIO_REG_930239_IN in_dword_masked(\ + HWIO_REG_930239_ADDR, HWIO_REG_930239_RMSK) +#define HWIO_REG_930239_INM(m) \ + in_dword_masked(HWIO_REG_930239_ADDR, m) +#define HWIO_REG_930239_OUT(v) \ + out_dword(HWIO_REG_930239_ADDR, v) +#define HWIO_REG_930239_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_930239_ADDR, m, v, HWIO_REG_930239_IN); +#define HWIO_REG_930239_REG_930239_BMSK 0xffffffff +#define HWIO_REG_930239_REG_930239_SHFT 0 + +#define HWIO_REG_567827_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002038) +#define HWIO_REG_567827_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002038) +#define HWIO_REG_567827_RMSK 0xffffffff +#define HWIO_REG_567827_SHFT 0 +#define HWIO_REG_567827_IN in_dword_masked(\ + HWIO_REG_567827_ADDR, HWIO_REG_567827_RMSK) +#define HWIO_REG_567827_INM(m) \ + in_dword_masked(HWIO_REG_567827_ADDR, m) +#define HWIO_REG_567827_OUT(v) \ + out_dword(HWIO_REG_567827_ADDR, v) +#define HWIO_REG_567827_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_567827_ADDR, m, v, HWIO_REG_567827_IN); +#define HWIO_REG_567827_REG_567827_BMSK 0xffffffff +#define HWIO_REG_567827_REG_567827_SHFT 0 + +#define HWIO_REG_542997_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000203c) +#define HWIO_REG_542997_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000203c) +#define HWIO_REG_542997_RMSK 0xffffffff +#define HWIO_REG_542997_SHFT 0 +#define HWIO_REG_542997_IN in_dword_masked(\ + HWIO_REG_542997_ADDR, HWIO_REG_542997_RMSK) +#define HWIO_REG_542997_INM(m) \ + in_dword_masked(HWIO_REG_542997_ADDR, m) +#define HWIO_REG_542997_OUT(v) \ + out_dword(HWIO_REG_542997_ADDR, v) +#define HWIO_REG_542997_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_542997_ADDR, m, v, HWIO_REG_542997_IN); +#define HWIO_REG_542997_REG_542997_BMSK 0xffffffff +#define HWIO_REG_542997_REG_542997_SHFT 0 + +#define HWIO_REG_666957_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002040) +#define HWIO_REG_666957_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002040) +#define HWIO_REG_666957_RMSK 0x7ffff +#define HWIO_REG_666957_SHFT 0 +#define HWIO_REG_666957_IN in_dword_masked(\ + HWIO_REG_666957_ADDR, HWIO_REG_666957_RMSK) +#define HWIO_REG_666957_INM(m) \ + in_dword_masked(HWIO_REG_666957_ADDR, m) +#define HWIO_REG_666957_OUT(v) \ + out_dword(HWIO_REG_666957_ADDR, v) +#define HWIO_REG_666957_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_666957_ADDR, m, v, HWIO_REG_666957_IN); +#define HWIO_REG_666957_CH_DEC_TYPE_BMSK 0x70000 +#define HWIO_REG_666957_CH_DEC_TYPE_SHFT 0x10 +#define HWIO_REG_666957_CH_INST_ID_BMSK 0xffff +#define HWIO_REG_666957_CH_INST_ID_SHFT 0 + +#define HWIO_REG_117192_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002044) +#define HWIO_REG_117192_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002044) +#define HWIO_REG_117192_RMSK 0xffffffff +#define HWIO_REG_117192_SHFT 0 +#define HWIO_REG_117192_IN in_dword_masked(\ + HWIO_REG_117192_ADDR, HWIO_REG_117192_RMSK) +#define HWIO_REG_117192_INM(m) \ + in_dword_masked(HWIO_REG_117192_ADDR, m) +#define HWIO_REG_117192_OUT(v) \ + out_dword(HWIO_REG_117192_ADDR, v) +#define HWIO_REG_117192_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_117192_ADDR, m, v, HWIO_REG_117192_IN); +#define HWIO_REG_117192_REG_117192_BMSK 0xffffffff +#define HWIO_REG_117192_REG_117192_SHFT 0 + +#define HWIO_REG_145068_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002048) +#define HWIO_REG_145068_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002048) +#define HWIO_REG_145068_RMSK 0xffffffff +#define HWIO_REG_145068_SHFT 0 +#define HWIO_REG_145068_IN in_dword_masked(\ + HWIO_REG_145068_ADDR, HWIO_REG_145068_RMSK) +#define HWIO_REG_145068_INM(m) \ + in_dword_masked(HWIO_REG_145068_ADDR, m) +#define HWIO_REG_145068_OUT(v) \ + out_dword(HWIO_REG_145068_ADDR, v) +#define HWIO_REG_145068_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_145068_ADDR, m, v, HWIO_REG_145068_IN); +#define HWIO_REG_145068_REG_145068_BMSK 0xffffffff +#define HWIO_REG_145068_REG_145068_SHFT 0 + +#define HWIO_REG_921356_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000204c) +#define HWIO_REG_921356_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000204c) +#define HWIO_REG_921356_RMSK 0xffffffff +#define HWIO_REG_921356_SHFT 0 +#define HWIO_REG_921356_IN in_dword_masked(\ + HWIO_REG_921356_ADDR, HWIO_REG_921356_RMSK) +#define HWIO_REG_921356_INM(m) \ + in_dword_masked(HWIO_REG_921356_ADDR, m) +#define HWIO_REG_921356_OUT(v) \ + out_dword(HWIO_REG_921356_ADDR, v) +#define HWIO_REG_921356_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_921356_ADDR, m, v, HWIO_REG_921356_IN); +#define HWIO_REG_921356_REG_921356_BMSK 0xffffffff +#define HWIO_REG_921356_REG_921356_SHFT 0 + +#define HWIO_REG_612810_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002050) +#define HWIO_REG_612810_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002050) +#define HWIO_REG_612810_RMSK 0xffffffff +#define HWIO_REG_612810_SHFT 0 +#define HWIO_REG_612810_IN in_dword_masked(\ + HWIO_REG_612810_ADDR, HWIO_REG_612810_RMSK) +#define HWIO_REG_612810_INM(m) \ + in_dword_masked(HWIO_REG_612810_ADDR, m) +#define HWIO_REG_612810_OUT(v) \ + out_dword(HWIO_REG_612810_ADDR, v) +#define HWIO_REG_612810_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_612810_ADDR, m, v, HWIO_REG_612810_IN); +#define HWIO_REG_612810_REG_612810_BMSK 0xffffffff +#define HWIO_REG_612810_REG_612810_SHFT 0 + +#define HWIO_REG_175608_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002054) +#define HWIO_REG_175608_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002054) +#define HWIO_REG_175608_RMSK 0xffffffff +#define HWIO_REG_175608_SHFT 0 +#define HWIO_REG_175608_IN in_dword_masked(\ + HWIO_REG_175608_ADDR, HWIO_REG_175608_RMSK) +#define HWIO_REG_175608_INM(m) \ + in_dword_masked(HWIO_REG_175608_ADDR, m) +#define HWIO_REG_175608_OUT(v) \ + out_dword(HWIO_REG_175608_ADDR, v) +#define HWIO_REG_175608_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_175608_ADDR, m, v, HWIO_REG_175608_IN); +#define HWIO_REG_175608_REG_175608_BMSK 0xffffffff +#define HWIO_REG_175608_REG_175608_SHFT 0 + +#define HWIO_REG_190381_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002058) +#define HWIO_REG_190381_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002058) +#define HWIO_REG_190381_RMSK 0xffffffff +#define HWIO_REG_190381_SHFT 0 +#define HWIO_REG_190381_IN in_dword_masked(\ + HWIO_REG_190381_ADDR, HWIO_REG_190381_RMSK) +#define HWIO_REG_190381_INM(m) \ + in_dword_masked(HWIO_REG_190381_ADDR, m) +#define HWIO_REG_190381_OUT(v) \ + out_dword(HWIO_REG_190381_ADDR, v) +#define HWIO_REG_190381_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_190381_ADDR, m, v, HWIO_REG_190381_IN); +#define HWIO_REG_190381_REG_190381_BMSK 0xffffffff +#define HWIO_REG_190381_REG_190381_SHFT 0 + +#define HWIO_REG_85655_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000205c) +#define HWIO_REG_85655_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000205c) +#define HWIO_REG_85655_RMSK 0xffffffff +#define HWIO_REG_85655_SHFT 0 +#define HWIO_REG_85655_IN in_dword_masked(\ + HWIO_REG_85655_ADDR, HWIO_REG_85655_RMSK) +#define HWIO_REG_85655_INM(m) \ + in_dword_masked(HWIO_REG_85655_ADDR, m) +#define HWIO_REG_85655_OUT(v) \ + out_dword(HWIO_REG_85655_ADDR, v) +#define HWIO_REG_85655_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_85655_ADDR, m, v, HWIO_REG_85655_IN); +#define HWIO_REG_85655_REG_85655_BMSK 0xffffffff +#define HWIO_REG_85655_REG_85655_SHFT 0 + +#define HWIO_REG_86830_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002060) +#define HWIO_REG_86830_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002060) +#define HWIO_REG_86830_RMSK 0xffffffff +#define HWIO_REG_86830_SHFT 0 +#define HWIO_REG_86830_IN in_dword_masked(\ + HWIO_REG_86830_ADDR, HWIO_REG_86830_RMSK) +#define HWIO_REG_86830_INM(m) \ + in_dword_masked(HWIO_REG_86830_ADDR, m) +#define HWIO_REG_86830_OUT(v) \ + out_dword(HWIO_REG_86830_ADDR, v) +#define HWIO_REG_86830_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_86830_ADDR, m, v, HWIO_REG_86830_IN); +#define HWIO_REG_86830_REG_86830_BMSK 0xffffffff +#define HWIO_REG_86830_REG_86830_SHFT 0 + +#define HWIO_REG_889944_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002064) +#define HWIO_REG_889944_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002064) +#define HWIO_REG_889944_RMSK 0xffffffff +#define HWIO_REG_889944_SHFT 0 +#define HWIO_REG_889944_IN in_dword_masked(\ + HWIO_REG_889944_ADDR, HWIO_REG_889944_RMSK) +#define HWIO_REG_889944_INM(m) \ + in_dword_masked(HWIO_REG_889944_ADDR, m) +#define HWIO_REG_889944_OUT(v) \ + out_dword(HWIO_REG_889944_ADDR, v) +#define HWIO_REG_889944_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_889944_ADDR, m, v, HWIO_REG_889944_IN); +#define HWIO_REG_889944_HOST_WR_ADDR_BMSK 0xffffffff +#define HWIO_REG_889944_HOST_WR_ADSR_SHFT 0 + +#define HWIO_REG_404623_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002068) +#define HWIO_REG_404623_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002068) +#define HWIO_REG_404623_RMSK 0xffffffff +#define HWIO_REG_404623_SHFT 0 +#define HWIO_REG_404623_IN in_dword_masked(\ + HWIO_REG_404623_ADDR, HWIO_REG_404623_RMSK) +#define HWIO_REG_404623_INM(m) \ + in_dword_masked(HWIO_REG_404623_ADDR, m) +#define HWIO_REG_404623_OUT(v) \ + out_dword(HWIO_REG_404623_ADDR, v) +#define HWIO_REG_404623_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_404623_ADDR, m, v, HWIO_REG_404623_IN); +#define HWIO_REG_404623_REG_404623_BMSK 0xffffffff +#define HWIO_REG_404623_REG_404623_SHFT 0 + +#define HWIO_REG_397087_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000206c) +#define HWIO_REG_397087_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000206c) +#define HWIO_REG_397087_RMSK 0xffffffff +#define HWIO_REG_397087_SHFT 0 +#define HWIO_REG_397087_IN in_dword_masked(\ + HWIO_REG_397087_ADDR, HWIO_REG_397087_RMSK) +#define HWIO_REG_397087_INM(m) \ + in_dword_masked(HWIO_REG_397087_ADDR, m) +#define HWIO_REG_397087_OUT(v) \ + out_dword(HWIO_REG_397087_ADDR, v) +#define HWIO_REG_397087_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_397087_ADDR, m, v, HWIO_REG_397087_IN); +#define HWIO_REG_397087_CMD_SEQ_NUM_BMSK 0xffffffff +#define HWIO_REG_397087_CMD_SEQ_NUM_SHFT 0 + +#define HWIO_REG_212613_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002070) +#define HWIO_REG_212613_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002070) +#define HWIO_REG_212613_RMSK 0xffffffff +#define HWIO_REG_212613_SHFT 0 +#define HWIO_REG_212613_IN in_dword_masked(\ + HWIO_REG_212613_ADDR, HWIO_REG_212613_RMSK) +#define HWIO_REG_212613_INM(m) \ + in_dword_masked(HWIO_REG_212613_ADDR, m) +#define HWIO_REG_212613_OUT(v) \ + out_dword(HWIO_REG_212613_ADDR, v) +#define HWIO_REG_212613_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_212613_ADDR, m, v, HWIO_REG_212613_IN); +#define HWIO_REG_212613_REG_212613_BMSK 0xffffffff +#define HWIO_REG_212613_REG_212613_SHFT 0 + +#define HWIO_REG_840123_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002074) +#define HWIO_REG_840123_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002074) +#define HWIO_REG_840123_RMSK 0xffffffff +#define HWIO_REG_840123_SHFT 0 +#define HWIO_REG_840123_IN in_dword_masked(\ + HWIO_REG_840123_ADDR, HWIO_REG_840123_RMSK) +#define HWIO_REG_840123_INM(m) \ + in_dword_masked(HWIO_REG_840123_ADDR, m) +#define HWIO_REG_840123_OUT(v) \ + out_dword(HWIO_REG_840123_ADDR, v) +#define HWIO_REG_840123_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_840123_ADDR, m, v, HWIO_REG_840123_IN); +#define HWIO_REG_840123_REG_840123_BMSK 0xffffffff +#define HWIO_REG_840123_REG_840123_SHFT 0 + +#define HWIO_REG_520335_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002078) +#define HWIO_REG_520335_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002078) +#define HWIO_REG_520335_RMSK 0xffffffff +#define HWIO_REG_520335_SHFT 0 +#define HWIO_REG_520335_IN in_dword_masked(\ + HWIO_REG_520335_ADDR, HWIO_REG_520335_RMSK) +#define HWIO_REG_520335_INM(m) \ + in_dword_masked(HWIO_REG_520335_ADDR, m) +#define HWIO_REG_520335_OUT(v) \ + out_dword(HWIO_REG_520335_ADDR, v) +#define HWIO_REG_520335_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_520335_ADDR, m, v, HWIO_REG_520335_IN); +#define HWIO_REG_520335_REG_196943_BMSK 0xffffffff +#define HWIO_REG_520335_REG_196943_SHFT 0 + +#define HWIO_REG_196943_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000207c) +#define HWIO_REG_196943_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000207c) +#define HWIO_REG_196943_RMSK 0xffffffff +#define HWIO_REG_196943_SHFT 0 +#define HWIO_REG_196943_IN in_dword_masked(\ + HWIO_REG_196943_ADDR, HWIO_REG_196943_RMSK) +#define HWIO_REG_196943_INM(m) \ + in_dword_masked(HWIO_REG_196943_ADDR, m) +#define HWIO_REG_196943_OUT(v) \ + out_dword(HWIO_REG_196943_ADDR, v) +#define HWIO_REG_196943_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_196943_ADDR, m, v, HWIO_REG_196943_IN); +#define HWIO_REG_196943_REG_196943_BMSK 0xffffffff +#define HWIO_REG_196943_REG_196943_SHFT 0 + +#define HWIO_REG_313350_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002080) +#define HWIO_REG_313350_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002080) +#define HWIO_REG_313350_RMSK 0x7ffff +#define HWIO_REG_313350_SHFT 0 +#define HWIO_REG_313350_IN in_dword_masked(\ + HWIO_REG_313350_ADDR, HWIO_REG_313350_RMSK) +#define HWIO_REG_313350_INM(m) \ + in_dword_masked(HWIO_REG_313350_ADDR, m) +#define HWIO_REG_313350_OUT(v) \ + out_dword(HWIO_REG_313350_ADDR, v) +#define HWIO_REG_313350_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_313350_ADDR, m, v, HWIO_REG_313350_IN); +#define HWIO_REG_313350_CH_DEC_TYPE_BMSK 0x70000 +#define HWIO_REG_313350_CH_DEC_TYPE_SHFT 0x10 +#define HWIO_REG_313350_CH_INST_ID_BMSK 0xffff +#define HWIO_REG_313350_CH_INST_ID_SHFT 0 + +#define HWIO_REG_980194_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002084) +#define HWIO_REG_980194_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002084) +#define HWIO_REG_980194_RMSK 0xffffffff +#define HWIO_REG_980194_SHFT 0 +#define HWIO_REG_980194_IN in_dword_masked(\ + HWIO_REG_980194_ADDR, HWIO_REG_980194_RMSK) +#define HWIO_REG_980194_INM(m) \ + in_dword_masked(HWIO_REG_980194_ADDR, m) +#define HWIO_REG_980194_OUT(v) \ + out_dword(HWIO_REG_980194_ADDR, v) +#define HWIO_REG_980194_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_980194_ADDR, m, v, HWIO_REG_980194_IN); +#define HWIO_REG_980194_REG_980194_BMSK 0xffffffff +#define HWIO_REG_980194_REG_980194_SHFT 0 + +#define HWIO_REG_936704_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002088) +#define HWIO_REG_936704_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002088) +#define HWIO_REG_936704_RMSK 0xffffffff +#define HWIO_REG_936704_SHFT 0 +#define HWIO_REG_936704_IN in_dword_masked(\ + HWIO_REG_936704_ADDR, HWIO_REG_936704_RMSK) +#define HWIO_REG_936704_INM(m) \ + in_dword_masked(HWIO_REG_936704_ADDR, m) +#define HWIO_REG_936704_OUT(v) \ + out_dword(HWIO_REG_936704_ADDR, v) +#define HWIO_REG_936704_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_936704_ADDR, m, v, HWIO_REG_936704_IN); +#define HWIO_REG_936704_REG_936704_BMSK 0xffffffff +#define HWIO_REG_936704_REG_936704_SHFT 0 + +#define HWIO_REG_821977_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000208c) +#define HWIO_REG_821977_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000208c) +#define HWIO_REG_821977_RMSK 0xffffffff +#define HWIO_REG_821977_SHFT 0 +#define HWIO_REG_821977_IN in_dword_masked(\ + HWIO_REG_821977_ADDR, HWIO_REG_821977_RMSK) +#define HWIO_REG_821977_INM(m) \ + in_dword_masked(HWIO_REG_821977_ADDR, m) +#define HWIO_REG_821977_OUT(v) \ + out_dword(HWIO_REG_821977_ADDR, v) +#define HWIO_REG_821977_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_821977_ADDR, m, v, HWIO_REG_821977_IN); +#define HWIO_REG_821977_REG_821977_BMSK 0xffffffff +#define HWIO_REG_821977_REG_821977_SHFT 0 + +#define HWIO_REG_655721_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002090) +#define HWIO_REG_655721_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002090) +#define HWIO_REG_655721_RMSK 0xffffffff +#define HWIO_REG_655721_SHFT 0 +#define HWIO_REG_655721_IN in_dword_masked(\ + HWIO_REG_655721_ADDR, HWIO_REG_655721_RMSK) +#define HWIO_REG_655721_INM(m) \ + in_dword_masked(HWIO_REG_655721_ADDR, m) +#define HWIO_REG_655721_OUT(v) \ + out_dword(HWIO_REG_655721_ADDR, v) +#define HWIO_REG_655721_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_655721_ADDR, m, v, HWIO_REG_655721_IN); +#define HWIO_REG_655721_REG_655721_BMSK 0xffffffff +#define HWIO_REG_655721_REG_655721_SHFT 0 + +#define HWIO_REG_548308_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002094) +#define HWIO_REG_548308_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002094) +#define HWIO_REG_548308_RMSK 0xffffffff +#define HWIO_REG_548308_SHFT 0 +#define HWIO_REG_548308_IN in_dword_masked(\ + HWIO_REG_548308_ADDR, HWIO_REG_548308_RMSK) +#define HWIO_REG_548308_INM(m) \ + in_dword_masked(HWIO_REG_548308_ADDR, m) +#define HWIO_REG_548308_OUT(v) \ + out_dword(HWIO_REG_548308_ADDR, v) +#define HWIO_REG_548308_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_548308_ADDR, m, v, HWIO_REG_548308_IN); +#define HWIO_REG_548308_REG_548308_BMSK 0xffffffff +#define HWIO_REG_548308_REG_548308_SHFT 0 + +#define HWIO_REG_887095_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x00002098) +#define HWIO_REG_887095_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x00002098) +#define HWIO_REG_887095_RMSK 0xffffffff +#define HWIO_REG_887095_SHFT 0 +#define HWIO_REG_887095_IN in_dword_masked(\ + HWIO_REG_887095_ADDR, HWIO_REG_887095_RMSK) +#define HWIO_REG_887095_INM(m) \ + in_dword_masked(HWIO_REG_887095_ADDR, m) +#define HWIO_REG_887095_OUT(v) \ + out_dword(HWIO_REG_887095_ADDR, v) +#define HWIO_REG_887095_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_887095_ADDR, m, v, HWIO_REG_887095_IN); +#define HWIO_REG_887095_REG_887095_BMSK 0xffffffff +#define HWIO_REG_887095_REG_887095_SHFT 0 + +#define HWIO_REG_576987_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000209c) +#define HWIO_REG_576987_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000209c) +#define HWIO_REG_576987_RMSK 0xffffffff +#define HWIO_REG_576987_SHFT 0 +#define HWIO_REG_576987_IN in_dword_masked(\ + HWIO_REG_576987_ADDR, HWIO_REG_576987_RMSK) +#define HWIO_REG_576987_INM(m) \ + in_dword_masked(HWIO_REG_576987_ADDR, m) +#define HWIO_REG_576987_OUT(v) \ + out_dword(HWIO_REG_576987_ADDR, v) +#define HWIO_REG_576987_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_576987_ADDR, m, v, HWIO_REG_576987_IN); +#define HWIO_REG_576987_REG_576987_BMSK 0xffffffff +#define HWIO_REG_576987_REG_576987_SHFT 0 + +#define HWIO_REG_70448_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x000020a0) +#define HWIO_REG_70448_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000020a0) +#define HWIO_REG_70448_RMSK 0xffffffff +#define HWIO_REG_70448_SHFT 0 +#define HWIO_REG_70448_IN in_dword_masked(\ + HWIO_REG_70448_ADDR, HWIO_REG_70448_RMSK) +#define HWIO_REG_70448_INM(m) \ + in_dword_masked(HWIO_REG_70448_ADDR, m) +#define HWIO_REG_70448_OUT(v) \ + out_dword(HWIO_REG_70448_ADDR, v) +#define HWIO_REG_70448_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_70448_ADDR, m, v, HWIO_REG_70448_IN); +#define HWIO_REG_70448_REG_70448_BMSK 0xffffffff +#define HWIO_REG_70448_REG_70448_SHFT 0 + +#define HWIO_REG_652528_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x000020a4) +#define HWIO_REG_652528_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000020a4) +#define HWIO_REG_652528_RMSK 0xffffffff +#define HWIO_REG_652528_SHFT 0 +#define HWIO_REG_652528_IN in_dword_masked(\ + HWIO_REG_652528_ADDR, HWIO_REG_652528_RMSK) +#define HWIO_REG_652528_INM(m) \ + in_dword_masked(HWIO_REG_652528_ADDR, m) +#define HWIO_REG_652528_OUT(v) \ + out_dword(HWIO_REG_652528_ADDR, v) +#define HWIO_REG_652528_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_652528_ADDR, m, v , HWIO_REG_652528_IN); +#define HWIO_REG_652528_REG_652528_BMSK 0xffffffff +#define HWIO_REG_652528_REG_652528_SHFT 0 + +#define HWIO_REG_220637_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x000020a8) +#define HWIO_REG_220637_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000020a8) +#define HWIO_REG_220637_RMSK 0xffffffff +#define HWIO_REG_220637_SHFT 0 +#define HWIO_REG_220637_IN in_dword_masked(\ + HWIO_REG_220637_ADDR, HWIO_REG_220637_RMSK) +#define HWIO_REG_220637_INM(m) \ + in_dword_masked(HWIO_REG_220637_ADDR, m) +#define HWIO_REG_220637_OUT(v) \ + out_dword(HWIO_REG_220637_ADDR, v) +#define HWIO_REG_220637_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_220637_ADDR, m, v, HWIO_REG_220637_IN); +#define HWIO_REG_220637_REG_220637_BMSK 0xffffffff +#define HWIO_REG_220637_REG_220637_SHFT 0 + +#define HWIO_REG_254093_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x000020ac) +#define HWIO_REG_254093_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000020ac) +#define HWIO_REG_254093_RMSK 0xffffffff +#define HWIO_REG_254093_SHFT 0 +#define HWIO_REG_254093_IN in_dword_masked(\ + HWIO_REG_254093_ADDR, HWIO_REG_254093_RMSK) +#define HWIO_REG_254093_INM(m) \ + in_dword_masked(HWIO_REG_254093_ADDR, m) +#define HWIO_REG_254093_OUT(v) \ + out_dword(HWIO_REG_254093_ADDR, v) +#define HWIO_REG_254093_OUTM(m, v) out_dword_masked_ns\ + (HWIO_REG_254093_ADDR, m, v, HWIO_REG_254093_IN); +#define HWIO_REG_254093_REG_254093_BMSK 0xffffffff +#define HWIO_REG_254093_REG_254093_SHFT 0 + +#define HWIO_REG_160474_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x000020b0) +#define HWIO_REG_160474_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000020b0) +#define HWIO_REG_160474_RMSK 0xffffffff +#define HWIO_REG_160474_SHFT 0 +#define HWIO_REG_160474_IN in_dword_masked(\ + HWIO_REG_160474_ADDR, HWIO_REG_160474_RMSK) +#define HWIO_REG_160474_INM(m) \ + in_dword_masked(HWIO_REG_160474_ADDR, m) +#define HWIO_REG_160474_OUT(v) \ + out_dword(HWIO_REG_160474_ADDR, v) +#define HWIO_REG_160474_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_160474_ADDR, m, v, HWIO_REG_160474_IN); +#define HWIO_REG_160474_REG_160474_BMSK 0xffffffff +#define HWIO_REG_160474_REG_160474_SHFT 0 + +#define HWIO_REG_39027_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x000020b4) +#define HWIO_REG_39027_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000020b4) +#define HWIO_REG_39027_RMSK 0xffffffff +#define HWIO_REG_39027_SHFT 0 +#define HWIO_REG_39027_IN in_dword_masked(\ + HWIO_REG_39027_ADDR, HWIO_REG_39027_RMSK) +#define HWIO_REG_39027_INM(m) \ + in_dword_masked(HWIO_REG_39027_ADDR, m) +#define HWIO_REG_39027_OUT(v) \ + out_dword(HWIO_REG_39027_ADDR, v) +#define HWIO_REG_39027_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_39027_ADDR, m, v, HWIO_REG_39027_IN); +#define HWIO_REG_39027_REG_39027_BMSK 0xffffffff +#define HWIO_REG_39027_REG_39027_SHFT 0 + +#define HWIO_REG_74049_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x000020b8) +#define HWIO_REG_74049_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000020b8) +#define HWIO_REG_74049_RMSK 0xffffffff +#define HWIO_REG_74049_SHFT 0 +#define HWIO_REG_74049_IN in_dword_masked(\ + HWIO_REG_74049_ADDR, HWIO_REG_74049_RMSK) +#define HWIO_REG_74049_INM(m) \ + in_dword_masked(HWIO_REG_74049_ADDR, m) +#define HWIO_REG_74049_OUT(v) \ + out_dword(HWIO_REG_74049_ADDR, v) +#define HWIO_REG_74049_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_74049_ADDR, m, v, HWIO_REG_74049_IN); +#define HWIO_REG_74049_REG_74049_BMSK 0xffffffff +#define HWIO_REG_74049_REG_74049_SHFT 0 + +#define HWIO_REG_697870_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x000020bc) +#define HWIO_REG_697870_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x000020bc) +#define HWIO_REG_697870_RMSK 0xffffffff +#define HWIO_REG_697870_SHFT 0 +#define HWIO_REG_697870_IN in_dword_masked(\ + HWIO_REG_697870_ADDR, HWIO_REG_697870_RMSK) +#define HWIO_REG_697870_INM(m) \ + in_dword_masked(HWIO_REG_697870_ADDR, m) +#define HWIO_REG_697870_OUT(v) \ + out_dword(HWIO_REG_697870_ADDR, v) +#define HWIO_REG_697870_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_697870_ADDR, m, v, HWIO_REG_697870_IN); +#define HWIO_REG_697870_REG_697870_BMSK 0xffffffff +#define HWIO_REG_697870_REG_697870_SHFT 0 + +#define HWIO_REG_783891_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c504) +#define HWIO_REG_783891_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c504) +#define HWIO_REG_783891_RMSK 0x7ffff +#define HWIO_REG_783891_SHFT 0 +#define HWIO_REG_783891_IN in_dword_masked(\ + HWIO_REG_783891_ADDR, HWIO_REG_783891_RMSK) +#define HWIO_REG_783891_INM(m) \ + in_dword_masked(HWIO_REG_783891_ADDR, m) +#define HWIO_REG_783891_OUT(v) \ + out_dword(HWIO_REG_783891_ADDR, v) +#define HWIO_REG_783891_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_783891_ADDR, m, v, HWIO_REG_783891_IN); +#define HWIO_REG_783891_ENC_PIC_TYPE_USE_BMSK 0x40000 +#define HWIO_REG_783891_ENC_PIC_TYPE_USE_SHFT 0x12 +#define HWIO_REG_783891_B_FRM_CTRL_BMSK 0x30000 +#define HWIO_REG_783891_B_FRM_CTRL_SHFT 0x10 +#define HWIO_REG_783891_I_FRM_CTRL_BMSK 0xffff +#define HWIO_REG_783891_I_FRM_CTRL_SHFT 0 + +#define HWIO_REG_226332_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c50c) +#define HWIO_REG_226332_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c50c) +#define HWIO_REG_226332_RMSK 0x7 +#define HWIO_REG_226332_SHFT 0 +#define HWIO_REG_226332_IN in_dword_masked(\ + HWIO_REG_226332_ADDR, HWIO_REG_226332_RMSK) +#define HWIO_REG_226332_INM(m) in_dword_masked(HWIO_REG_226332_ADDR, m) +#define HWIO_REG_226332_OUT(v) out_dword(HWIO_REG_226332_ADDR, v) +#define HWIO_REG_226332_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_226332_ADDR, m, v, HWIO_REG_226332_IN); +#define HWIO_REG_226332_MSLICE_MODE_BMSK 0x6 +#define HWIO_REG_226332_MSLICE_MODE_SHFT 0x1 +#define HWIO_REG_226332_MSLICE_ENA_BMSK 0x1 +#define HWIO_REG_226332_MSLICE_ENA_SHFT 0 + +#define HWIO_REG_696136_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c510) +#define HWIO_REG_696136_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c510) +#define HWIO_REG_696136_RMSK 0xffff +#define HWIO_REG_696136_SHFT 0 +#define HWIO_REG_696136_IN in_dword_masked(\ + HWIO_REG_696136_ADDR, HWIO_REG_696136_RMSK) +#define HWIO_REG_696136_INM(m) in_dword_masked(HWIO_REG_696136_ADDR, m) +#define HWIO_REG_696136_OUT(v) out_dword(HWIO_REG_696136_ADDR, v) +#define HWIO_REG_696136_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_696136_ADDR, m, v, HWIO_REG_696136_IN); +#define HWIO_REG_696136_MSLICE_MB_BMSK 0xffff +#define HWIO_REG_696136_MSLICE_MB_SHFT 0 + +#define HWIO_REG_515564_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c514) +#define HWIO_REG_515564_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c514) +#define HWIO_REG_515564_RMSK 0xffffffff +#define HWIO_REG_515564_SHFT 0 +#define HWIO_REG_515564_IN in_dword_masked(\ + HWIO_REG_515564_ADDR, HWIO_REG_515564_RMSK) +#define HWIO_REG_515564_INM(m) \ + in_dword_masked(HWIO_REG_515564_ADDR, m) +#define HWIO_REG_515564_OUT(v) out_dword(HWIO_REG_515564_ADDR, v) +#define HWIO_REG_515564_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_515564_ADDR, m, v, HWIO_REG_515564_IN); +#define HWIO_REG_515564_MSLICE_BIT_BMSK 0xffffffff +#define HWIO_REG_515564_MSLICE_BIT_SHFT 0 + +#define HWIO_REG_886210_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c518) +#define HWIO_REG_886210_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c518) +#define HWIO_REG_886210_RMSK 0xffff +#define HWIO_REG_886210_SHFT 0 +#define HWIO_REG_886210_IN in_dword_masked(\ + HWIO_REG_886210_ADDR, HWIO_REG_886210_RMSK) +#define HWIO_REG_886210_INM(m) in_dword_masked(HWIO_REG_886210_ADDR, m) +#define HWIO_REG_886210_OUT(v) out_dword(HWIO_REG_886210_ADDR, v) +#define HWIO_REG_886210_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_886210_ADDR, m, v, HWIO_REG_886210_IN); +#define HWIO_REG_886210_CIR_NUM_BMSK 0xffff +#define HWIO_REG_886210_CIR_NUM_SHFT 0 + +#define HWIO_REG_645603_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c51c) +#define HWIO_REG_645603_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c51c) +#define HWIO_REG_645603_RMSK 0x3 +#define HWIO_REG_645603_SHFT 0 +#define HWIO_REG_645603_IN in_dword_masked(\ + HWIO_REG_645603_ADDR, HWIO_REG_645603_RMSK) +#define HWIO_REG_645603_INM(m) \ + in_dword_masked(HWIO_REG_645603_ADDR, m) +#define HWIO_REG_645603_OUT(v) out_dword(HWIO_REG_645603_ADDR, v) +#define HWIO_REG_645603_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_645603_ADDR, m, v, HWIO_REG_645603_IN); +#define HWIO_REG_645603_REG_645603_BMSK 0x3 +#define HWIO_REG_645603_REG_645603_SHFT 0 + +#define HWIO_REG_811733_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c520) +#define HWIO_REG_811733_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c520) +#define HWIO_REG_811733_RMSK 0x80ffffff +#define HWIO_REG_811733_SHFT 0 +#define HWIO_REG_811733_IN in_dword_masked(\ + HWIO_REG_811733_ADDR, HWIO_REG_811733_RMSK) +#define HWIO_REG_811733_INM(m) \ + in_dword_masked(HWIO_REG_811733_ADDR, m) +#define HWIO_REG_811733_OUT(v) out_dword(HWIO_REG_811733_ADDR, v) +#define HWIO_REG_811733_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_811733_ADDR, m, v, HWIO_REG_811733_IN); +#define HWIO_REG_811733_PAD_CTRL_ON_BMSK 0x80000000 +#define HWIO_REG_811733_PAD_CTRL_ON_SHFT 0x1f +#define HWIO_REG_811733_CR_PAD_VIDC_BMSK 0xff0000 +#define HWIO_REG_811733_CR_PAD_VIDC_SHFT 0x10 +#define HWIO_REG_811733_CB_PAD_VIDC_BMSK 0xff00 +#define HWIO_REG_811733_CB_PAD_VIDC_SHFT 0x8 +#define HWIO_REG_811733_LUMA_PAD_VIDC_BMSK 0xff +#define HWIO_REG_811733_LUMA_PAD_VIDC_SHFT 0 + +#define HWIO_REG_676866_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c588) +#define HWIO_REG_676866_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c588) +#define HWIO_REG_676866_RMSK 0xffff +#define HWIO_REG_676866_SHFT 0 +#define HWIO_REG_676866_IN in_dword_masked(\ + HWIO_REG_676866_ADDR, HWIO_REG_676866_RMSK) +#define HWIO_REG_676866_INM(m) \ + in_dword_masked(HWIO_REG_676866_ADDR, m) +#define HWIO_REG_676866_OUT(v) \ + out_dword(HWIO_REG_676866_ADDR, v) +#define HWIO_REG_676866_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_676866_ADDR, m, v, HWIO_REG_676866_IN); +#define HWIO_REG_676866_REG_676866_BMSK 0xffff +#define HWIO_REG_676866_REG_676866_SHFT 0 + +#define HWIO_REG_54267_ADDR \ + (VIDC_BLACKBIRD_REG_BASE + 0x0000c58c) +#define HWIO_REG_54267_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c58c) +#define HWIO_REG_54267_RMSK 0xffff +#define HWIO_REG_54267_SHFT 0 +#define HWIO_REG_54267_IN in_dword_masked(\ + HWIO_REG_54267_ADDR,\ + HWIO_REG_54267_RMSK) +#define HWIO_REG_54267_INM(m) \ + in_dword_masked(HWIO_REG_54267_ADDR, m) +#define HWIO_REG_54267_OUT(v) \ + out_dword(HWIO_REG_54267_ADDR, v) +#define HWIO_REG_54267_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_54267_ADDR, m, v,\ + HWIO_REG_54267_IN); +#define HWIO_REG_54267_REG_54267_BMSK 0xffff +#define HWIO_REG_54267_REG_54267_SHFT 0 + +#define HWIO_REG_559908_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c5a0) +#define HWIO_REG_559908_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c5a0) +#define HWIO_REG_559908_RMSK 0x33f +#define HWIO_REG_559908_SHFT 0 +#define HWIO_REG_559908_IN in_dword_masked(\ + HWIO_REG_559908_ADDR, HWIO_REG_559908_RMSK) +#define HWIO_REG_559908_INM(m) in_dword_masked(HWIO_REG_559908_ADDR, m) +#define HWIO_REG_559908_OUT(v) out_dword(HWIO_REG_559908_ADDR, v) +#define HWIO_REG_559908_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_559908_ADDR, m, v, HWIO_REG_559908_IN); +#define HWIO_REG_559908_FR_RC_EN_BMSK 0x200 +#define HWIO_REG_559908_FR_RC_EN_SHFT 0x9 +#define HWIO_REG_559908_MB_RC_EN_BMSK 0x100 +#define HWIO_REG_559908_MB_RC_EN_SHFT 0x8 +#define HWIO_REG_559908_FRAME_QP_BMSK 0x3f +#define HWIO_REG_559908_FRAME_QP_SHFT 0 + +#define HWIO_REG_977937_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000d0d0) +#define HWIO_REG_977937_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000d0d0) +#define HWIO_REG_977937_RMSK 0xff +#define HWIO_REG_977937_SHFT 0 +#define HWIO_REG_977937_IN in_dword_masked(\ + HWIO_REG_977937_ADDR, HWIO_REG_977937_RMSK) +#define HWIO_REG_977937_INM(m) in_dword_masked(HWIO_REG_977937_ADDR, m) +#define HWIO_REG_977937_OUT(v) out_dword(HWIO_REG_977937_ADDR, v) +#define HWIO_REG_977937_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_977937_ADDR, m, v, HWIO_REG_977937_IN); +#define HWIO_REG_977937_FRAME_RATE_BMSK 0xff +#define HWIO_REG_977937_FRAME_RATE_SHFT 0 + +#define HWIO_REG_166135_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c5a8) +#define HWIO_REG_166135_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c5a8) +#define HWIO_REG_166135_RMSK 0xffffffff +#define HWIO_REG_166135_SHFT 0 +#define HWIO_REG_166135_IN in_dword_masked(\ + HWIO_REG_166135_ADDR, HWIO_REG_166135_RMSK) +#define HWIO_REG_166135_INM(m) in_dword_masked(HWIO_REG_166135_ADDR, m) +#define HWIO_REG_166135_OUT(v) out_dword(HWIO_REG_166135_ADDR, v) +#define HWIO_REG_166135_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_166135_ADDR, m, v, HWIO_REG_166135_IN); +#define HWIO_REG_166135_BIT_RATE_BMSK 0xffffffff +#define HWIO_REG_166135_BIT_RATE_SHFT 0 + +#define HWIO_REG_109072_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c5ac) +#define HWIO_REG_109072_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c5ac) +#define HWIO_REG_109072_RMSK 0x3fff +#define HWIO_REG_109072_SHFT 0 +#define HWIO_REG_109072_IN in_dword_masked(\ + HWIO_REG_109072_ADDR, HWIO_REG_109072_RMSK) +#define HWIO_REG_109072_INM(m) in_dword_masked(HWIO_REG_109072_ADDR, m) +#define HWIO_REG_109072_OUT(v) out_dword(HWIO_REG_109072_ADDR, v) +#define HWIO_REG_109072_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_109072_ADDR, m, v, HWIO_REG_109072_IN); +#define HWIO_REG_109072_MAX_QP_BMSK 0x3f00 +#define HWIO_REG_109072_MAX_QP_SHFT 0x8 +#define HWIO_REG_109072_MIN_QP_BMSK 0x3f +#define HWIO_REG_109072_MIN_QP_SHFT 0 + +#define HWIO_REG_550322_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c5b0) +#define HWIO_REG_550322_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c5b0) +#define HWIO_REG_550322_RMSK 0xffff +#define HWIO_REG_550322_SHFT 0 +#define HWIO_REG_550322_IN in_dword_masked(\ + HWIO_REG_550322_ADDR, HWIO_REG_550322_RMSK) +#define HWIO_REG_550322_INM(m) in_dword_masked(HWIO_REG_550322_ADDR, m) +#define HWIO_REG_550322_OUT(v) out_dword(HWIO_REG_550322_ADDR, v) +#define HWIO_REG_550322_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_550322_ADDR, m, v, HWIO_REG_550322_IN); +#define HWIO_REG_550322_REACT_PARA_BMSK 0xffff +#define HWIO_REG_550322_REACT_PARA_SHFT 0 + +#define HWIO_REG_949086_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000c5b4) +#define HWIO_REG_949086_PHYS (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000c5b4) +#define HWIO_REG_949086_RMSK 0xf +#define HWIO_REG_949086_SHFT 0 +#define HWIO_REG_949086_IN in_dword_masked(\ + HWIO_REG_949086_ADDR, HWIO_REG_949086_RMSK) +#define HWIO_REG_949086_INM(m) in_dword_masked(HWIO_REG_949086_ADDR, m) +#define HWIO_REG_949086_OUT(v) out_dword(HWIO_REG_949086_ADDR, v) +#define HWIO_REG_949086_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_949086_ADDR, m, v, HWIO_REG_949086_IN); +#define HWIO_REG_949086_DARK_DISABLE_BMSK 0x8 +#define HWIO_REG_949086_DARK_DISABLE_SHFT 0x3 +#define HWIO_REG_949086_SMOOTH_DISABLE_BMSK 0x4 +#define HWIO_REG_949086_SMOOTH_DISABLE_SHFT 0x2 +#define HWIO_REG_949086_STATIC_DISABLE_BMSK 0x2 +#define HWIO_REG_949086_STATIC_DISABLE_SHFT 0x1 +#define HWIO_REG_949086_ACT_DISABLE_BMSK 0x1 +#define HWIO_REG_949086_ACT_DISABLE_SHFT 0 + +#define HWIO_REG_447796_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000d004) +#define HWIO_REG_447796_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000d004) +#define HWIO_REG_447796_RMSK 0x1 +#define HWIO_REG_447796_SHFT 0 +#define HWIO_REG_447796_IN in_dword_masked(\ + HWIO_REG_447796_ADDR, HWIO_REG_447796_RMSK) +#define HWIO_REG_447796_INM(m) \ + in_dword_masked(HWIO_REG_447796_ADDR, m) +#define HWIO_REG_447796_OUT(v) \ + out_dword(HWIO_REG_447796_ADDR, v) +#define HWIO_REG_447796_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_447796_ADDR, m, v, HWIO_REG_447796_IN); +#define HWIO_REG_447796_REG_447796_BMSK 0x1 +#define HWIO_REG_447796_REG_447796_SHFT 0 + +#define HWIO_REG_744348_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000d010) +#define HWIO_REG_744348_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000d010) +#define HWIO_REG_744348_RMSK 0x7f +#define HWIO_REG_744348_SHFT 0 +#define HWIO_REG_744348_IN in_dword_masked(\ + HWIO_REG_744348_ADDR, HWIO_REG_744348_RMSK) +#define HWIO_REG_744348_INM(m) \ + in_dword_masked(HWIO_REG_744348_ADDR, m) +#define HWIO_REG_744348_OUT(v) \ + out_dword(HWIO_REG_744348_ADDR, v) +#define HWIO_REG_744348_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_744348_ADDR, m, v, HWIO_REG_744348_IN); +#define HWIO_REG_744348_P_BMSK 0x60 +#define HWIO_REG_744348_P_SHFT 0x5 +#define HWIO_REG_744348_BMSK 0x1f +#define HWIO_REG_744348_SHFT 0 + +#define HWIO_REG_672163_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000d034) +#define HWIO_REG_672163_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000d034) +#define HWIO_REG_672163_RMSK 0x1 +#define HWIO_REG_672163_SHFT 0 +#define HWIO_REG_672163_IN in_dword_masked(\ + HWIO_REG_672163_ADDR, HWIO_REG_672163_RMSK) +#define HWIO_REG_672163_INM(m) \ + in_dword_masked(HWIO_REG_672163_ADDR, m) +#define HWIO_REG_672163_OUT(v) \ + out_dword(HWIO_REG_672163_ADDR, v) +#define HWIO_REG_672163_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_672163_ADDR, m, v,\ + HWIO_REG_672163_IN); +#define HWIO_REG_672163_ENC_TRANS_8X8_FLAG_BMSK 0x1 +#define HWIO_REG_672163_ENC_TRANS_8X8_FLAG_SHFT 0 + +#define HWIO_REG_780908_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000d140) +#define HWIO_REG_780908_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000d140) +#define HWIO_REG_780908_RMSK 0x1 +#define HWIO_REG_780908_SHFT 0 +#define HWIO_REG_780908_IN in_dword_masked(\ + HWIO_REG_780908_ADDR, HWIO_REG_780908_RMSK) +#define HWIO_REG_780908_INM(m) in_dword_masked(\ + HWIO_REG_780908_ADDR, m) +#define HWIO_REG_780908_OUT(v) out_dword(\ + HWIO_REG_780908_ADDR, v) +#define HWIO_REG_780908_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_780908_ADDR, m, v,\ + HWIO_REG_780908_IN) +#define HWIO_REG_780908_REG_780908_BMSK 0x1 +#define HWIO_REG_780908_REG_780908_SHFT 0 + +#define HWIO_REG_330132_ADDR (VIDC_BLACKBIRD_REG_BASE + 0x0000e008) +#define HWIO_REG_330132_PHYS \ + (VIDC_BLACKBIRD_REG_BASE_PHYS + 0x0000e008) +#define HWIO_REG_330132_RMSK 0x1 +#define HWIO_REG_330132_SHFT 0 +#define HWIO_REG_330132_IN in_dword_masked(\ + HWIO_REG_330132_ADDR, HWIO_REG_330132_RMSK) +#define HWIO_REG_330132_INM(m) \ + in_dword_masked(HWIO_REG_330132_ADDR, m) +#define HWIO_REG_330132_OUT(v) \ + out_dword(HWIO_REG_330132_ADDR, v) +#define HWIO_REG_330132_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_330132_ADDR, m, v, HWIO_REG_330132_IN); +#define HWIO_REG_330132_MPEG4_QUART_PXL_BMSK 0x1 +#define HWIO_REG_330132_MPEG4_QUART_PXL_SHFT 0 + + +#define VIDC_MGEN2MAXI_REG_BASE (VIDC_BASE + 0x00080000) +#define VIDC_MGEN2MAXI_REG_BASE_PHYS 0x04480000 + +#define HWIO_REG_916352_ADDR (VIDC_MGEN2MAXI_REG_BASE + 00000000) +#define HWIO_REG_916352_PHYS (VIDC_MGEN2MAXI_REG_BASE_PHYS + 00000000) +#define HWIO_REG_916352_RMSK 0xff +#define HWIO_REG_916352_SHFT 0 +#define HWIO_REG_916352_IN in_dword_masked(\ + HWIO_REG_916352_ADDR, HWIO_REG_916352_RMSK) +#define HWIO_REG_916352_INM(m) \ + in_dword_masked(HWIO_REG_916352_ADDR, m) +#define HWIO_REG_916352_VERSION_BMSK 0xff +#define HWIO_REG_916352_VERSION_SHFT 0 + +#define HWIO_REG_5519_ADDR (VIDC_MGEN2MAXI_REG_BASE + 0x00000004) +#define HWIO_REG_5519_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000004) +#define HWIO_REG_5519_RMSK 0x1 +#define HWIO_REG_5519_SHFT 0 +#define HWIO_REG_5519_IN in_dword_masked(\ + HWIO_REG_5519_ADDR, HWIO_REG_5519_RMSK) +#define HWIO_REG_5519_INM(m) \ + in_dword_masked(HWIO_REG_5519_ADDR, m) +#define HWIO_REG_5519_OUT(v) \ + out_dword(HWIO_REG_5519_ADDR, v) +#define HWIO_REG_5519_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_5519_ADDR, m, v, HWIO_REG_5519_IN); +#define HWIO_REG_5519_AXI_AOOORD_BMSK 0x1 +#define HWIO_REG_5519_AXI_AOOORD_SHFT 0 + +#define HWIO_REG_606364_ADDR (VIDC_MGEN2MAXI_REG_BASE + 0x00000008) +#define HWIO_REG_606364_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000008) +#define HWIO_REG_606364_RMSK 0x1 +#define HWIO_REG_606364_SHFT 0 +#define HWIO_REG_606364_IN in_dword_masked(\ + HWIO_REG_606364_ADDR, HWIO_REG_606364_RMSK) +#define HWIO_REG_606364_INM(m) \ + in_dword_masked(HWIO_REG_606364_ADDR, m) +#define HWIO_REG_606364_OUT(v) \ + out_dword(HWIO_REG_606364_ADDR, v) +#define HWIO_REG_606364_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_606364_ADDR, m, v, HWIO_REG_606364_IN); +#define HWIO_REG_606364_AXI_AOOOWR_BMSK 0x1 +#define HWIO_REG_606364_AXI_AOOOWR_SHFT 0 + +#define HWIO_REG_821472_ADDR (VIDC_MGEN2MAXI_REG_BASE + 0x0000000c) +#define HWIO_REG_821472_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x0000000c) +#define HWIO_REG_821472_RMSK 0xf +#define HWIO_REG_821472_SHFT 0 +#define HWIO_REG_821472_IN in_dword_masked(\ + HWIO_REG_821472_ADDR, HWIO_REG_821472_RMSK) +#define HWIO_REG_821472_INM(m) \ + in_dword_masked(HWIO_REG_821472_ADDR, m) +#define HWIO_REG_821472_OUT(v) \ + out_dword(HWIO_REG_821472_ADDR, v) +#define HWIO_REG_821472_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_821472_ADDR, m, v, HWIO_REG_821472_IN); +#define HWIO_REG_821472_AXI_TYPE_BMSK 0xf +#define HWIO_REG_821472_AXI_TYPE_SHFT 0 + +#define HWIO_REG_988424_ADDR \ + (VIDC_MGEN2MAXI_REG_BASE + 0x00000010) +#define HWIO_REG_988424_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000010) +#define HWIO_REG_988424_RMSK 0x3 +#define HWIO_REG_988424_SHFT 0 +#define HWIO_REG_988424_IN in_dword_masked(\ + HWIO_REG_988424_ADDR,\ + HWIO_REG_988424_RMSK) +#define HWIO_REG_988424_INM(m) \ + in_dword_masked(HWIO_REG_988424_ADDR, m) +#define HWIO_REG_988424_OUT(v) \ + out_dword(HWIO_REG_988424_ADDR, v) +#define HWIO_REG_988424_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_988424_ADDR, m, v,\ + HWIO_REG_988424_IN); +#define HWIO_REG_988424_AXI_AREQPRIORITY_BMSK 0x3 +#define HWIO_REG_988424_AXI_AREQPRIORITY_SHFT 0 + +#define HWIO_REG_471159_ADDR (VIDC_MGEN2MAXI_REG_BASE + 0x00000014) +#define HWIO_REG_471159_PHYS (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000014) +#define HWIO_REG_471159_RMSK 0x801f1111 +#define HWIO_REG_471159_SHFT 0 +#define HWIO_REG_471159_IN in_dword_masked(\ + HWIO_REG_471159_ADDR, HWIO_REG_471159_RMSK) +#define HWIO_REG_471159_INM(m) \ + in_dword_masked(HWIO_REG_471159_ADDR, m) +#define HWIO_REG_471159_OUT(v) \ + out_dword(HWIO_REG_471159_ADDR, v) +#define HWIO_REG_471159_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_471159_ADDR, m, v, HWIO_REG_471159_IN); +#define HWIO_REG_471159_AXI_INTR_CLR_BMSK 0x80000000 +#define HWIO_REG_471159_AXI_INTR_CLR_SHFT 0x1f +#define HWIO_REG_471159_AXI_WDTIMEOUT_LOG2_BMSK 0x1e0000 +#define HWIO_REG_471159_AXI_WDTIMEOUT_LOG2_SHFT 0x11 +#define HWIO_REG_471159_AXI_HALT_ON_WDTIMEOUT_BMSK 0x10000 +#define HWIO_REG_471159_AXI_HALT_ON_WDTIMEOUT_SHFT 0x10 +#define HWIO_REG_471159_AXI_HALT_ON_WR_ERR_BMSK 0x1000 +#define HWIO_REG_471159_AXI_HALT_ON_WR_ERR_SHFT 0xc +#define HWIO_REG_471159_AXI_HALT_ON_RD_ERR_BMSK 0x100 +#define HWIO_REG_471159_AXI_HALT_ON_RD_ERR_SHFT 0x8 +#define HWIO_REG_471159_AXI_RESET_BMSK 0x10 +#define HWIO_REG_471159_AXI_RESET_SHFT 0x4 +#define HWIO_REG_471159_AXI_HALT_REQ_BMSK 0x1 +#define HWIO_REG_471159_AXI_HALT_REQ_SHFT 0 + +#define HWIO_REG_437878_ADDR (VIDC_MGEN2MAXI_REG_BASE + 0x00000018) +#define HWIO_REG_437878_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000018) +#define HWIO_REG_437878_RMSK 0x3333 +#define HWIO_REG_437878_SHFT 0 +#define HWIO_REG_437878_IN in_dword_masked(\ + HWIO_REG_437878_ADDR, HWIO_REG_437878_RMSK) +#define HWIO_REG_437878_INM(m) \ + in_dword_masked(HWIO_REG_437878_ADDR, m) +#define HWIO_REG_437878_AXI_WDTIMEOUT_INTR_BMSK 0x3000 +#define HWIO_REG_437878_AXI_WDTIMEOUT_INTR_SHFT 0xc +#define HWIO_REG_437878_AXI_ERR_INTR_BMSK 0x300 +#define HWIO_REG_437878_AXI_ERR_INTR_SHFT 0x8 +#define HWIO_REG_437878_AXI_IDLE_BMSK 0x30 +#define HWIO_REG_437878_AXI_IDLE_SHFT 0x4 +#define HWIO_REG_437878_AXI_HALT_ACK_BMSK 0x3 +#define HWIO_REG_437878_AXI_HALT_ACK_SHFT 0 + +#define HWIO_REG_736158_ADDR \ + (VIDC_MGEN2MAXI_REG_BASE + 0x0000001c) +#define HWIO_REG_736158_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x0000001c) +#define HWIO_REG_736158_RMSK 0x10fff +#define HWIO_REG_736158_SHFT 0 +#define HWIO_REG_736158_IN in_dword_masked(\ + HWIO_REG_736158_ADDR,\ + HWIO_REG_736158_RMSK) +#define HWIO_REG_736158_INM(m) \ + in_dword_masked(HWIO_REG_736158_ADDR, m) +#define HWIO_REG_736158_AXI_WDTIMEOUT_BMSK 0x10000 +#define HWIO_REG_736158_AXI_WDTIMEOUT_SHFT 0x10 +#define HWIO_REG_736158_AXI_ERR_BMSK 0x800 +#define HWIO_REG_736158_AXI_ERR_SHFT 0xb +#define HWIO_REG_736158_AXI_ERR_TYPE_BMSK 0x400 +#define HWIO_REG_736158_AXI_ERR_TYPE_SHFT 0xa +#define HWIO_REG_736158_AXI_RESP_BMSK 0x300 +#define HWIO_REG_736158_AXI_RESP_SHFT 0x8 +#define HWIO_REG_736158_AXI_MID_BMSK 0xf0 +#define HWIO_REG_736158_AXI_MID_SHFT 0x4 +#define HWIO_REG_736158_AXI_TID_BMSK 0xf +#define HWIO_REG_736158_AXI_TID_SHFT 0 + +#define HWIO_REG_598415_ADDR \ + (VIDC_MGEN2MAXI_REG_BASE + 0x00000020) +#define HWIO_REG_598415_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000020) +#define HWIO_REG_598415_RMSK 0x10fff +#define HWIO_REG_598415_SHFT 0 +#define HWIO_REG_598415_IN in_dword_masked(\ + HWIO_REG_598415_ADDR,\ + HWIO_REG_598415_RMSK) +#define HWIO_REG_598415_INM(m) \ + in_dword_masked(HWIO_REG_598415_ADDR, m) +#define HWIO_REG_598415_AXI_WDTIMEOUT_BMSK 0x10000 +#define HWIO_REG_598415_AXI_WDTIMEOUT_SHFT 0x10 +#define HWIO_REG_598415_AXI_ERR_BMSK 0x800 +#define HWIO_REG_598415_AXI_ERR_SHFT 0xb +#define HWIO_REG_598415_AXI_ERR_TYPE_BMSK 0x400 +#define HWIO_REG_598415_AXI_ERR_TYPE_SHFT 0xa +#define HWIO_REG_598415_AXI_RESP_BMSK 0x300 +#define HWIO_REG_598415_AXI_RESP_SHFT 0x8 +#define HWIO_REG_598415_AXI_MID_BMSK 0xf0 +#define HWIO_REG_598415_AXI_MID_SHFT 0x4 +#define HWIO_REG_598415_AXI_TID_BMSK 0xf +#define HWIO_REG_598415_AXI_TID_SHFT 0 + +#define HWIO_REG_439061_ADDR (VIDC_MGEN2MAXI_REG_BASE + 0x00000024) +#define HWIO_REG_439061_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000024) +#define HWIO_REG_439061_RMSK 0x11111ff +#define HWIO_REG_439061_SHFT 0 +#define HWIO_REG_439061_IN in_dword_masked(\ + HWIO_REG_439061_ADDR, HWIO_REG_439061_RMSK) +#define HWIO_REG_439061_INM(m) \ + in_dword_masked(HWIO_REG_439061_ADDR, m) +#define HWIO_REG_439061_OUT(v) \ + out_dword(HWIO_REG_439061_ADDR, v) +#define HWIO_REG_439061_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_439061_ADDR, m, v, HWIO_REG_439061_IN); +#define HWIO_REG_439061_AXI_RD_LAT_REP_EN_BMSK 0x1000000 +#define HWIO_REG_439061_AXI_RD_LAT_REP_EN_SHFT 0x18 +#define HWIO_REG_439061_AXI_LSFR_EN_BMSK 0x100000 +#define HWIO_REG_439061_AXI_LSFR_EN_SHFT 0x14 +#define HWIO_REG_439061_AXI_MISR_RES_BMSK 0x10000 +#define HWIO_REG_439061_AXI_MISR_RES_SHFT 0x10 +#define HWIO_REG_439061_AXI_MISR_EN_BMSK 0x1000 +#define HWIO_REG_439061_AXI_MISR_EN_SHFT 0xc +#define HWIO_REG_439061_AXI_MISR_WD_BMSK 0x100 +#define HWIO_REG_439061_AXI_MISR_WD_SHFT 0x8 +#define HWIO_REG_439061_AXI_CTR_EN_BMSK 0x80 +#define HWIO_REG_439061_AXI_CTR_EN_SHFT 0x7 +#define HWIO_REG_439061_AXI_CTR_RES_BMSK 0x40 +#define HWIO_REG_439061_AXI_CTR_RES_SHFT 0x6 +#define HWIO_REG_439061_AXI_TEST_ARB_SEL_BMSK 0x30 +#define HWIO_REG_439061_AXI_TEST_ARB_SEL_SHFT 0x4 +#define HWIO_REG_439061_AXI_TEST_OUT_SEL_BMSK 0xf +#define HWIO_REG_439061_AXI_TEST_OUT_SEL_SHFT 0 + +#define HWIO_REG_573121_ADDR \ + (VIDC_MGEN2MAXI_REG_BASE + 0x00000028) +#define HWIO_REG_573121_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000028) +#define HWIO_REG_573121_RMSK 0xffffffff +#define HWIO_REG_573121_SHFT 0 +#define HWIO_REG_573121_IN in_dword_masked(\ + HWIO_REG_573121_ADDR,\ + HWIO_REG_573121_RMSK) +#define HWIO_REG_573121_INM(m) \ + in_dword_masked(HWIO_REG_573121_ADDR, m) +#define HWIO_REG_573121_AXI_TEST_OUT_BMSK 0xffffffff +#define HWIO_REG_573121_AXI_TEST_OUT_SHFT 0 + +#define HWIO_REG_806413_ADDR \ + (VIDC_MGEN2MAXI_REG_BASE + 0x0000002c) +#define HWIO_REG_806413_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x0000002c) +#define HWIO_REG_806413_RMSK 0xffffffff +#define HWIO_REG_806413_SHFT 0 +#define HWIO_REG_806413_IN in_dword_masked(\ + HWIO_REG_806413_ADDR,\ + HWIO_REG_806413_RMSK) +#define HWIO_REG_806413_INM(m) \ + in_dword_masked(HWIO_REG_806413_ADDR, m) +#define HWIO_REG_806413_AXI_TEST_OUT_BMSK 0xffffffff +#define HWIO_REG_806413_AXI_TEST_OUT_SHFT 0 + +#define HWIO_REG_804110_ADDR (VIDC_MGEN2MAXI_REG_BASE + 0x00000030) +#define HWIO_REG_804110_PHYS (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000030) +#define HWIO_REG_804110_RMSK 0xc00fffff +#define HWIO_REG_804110_SHFT 0 +#define HWIO_REG_804110_IN in_dword_masked(\ + HWIO_REG_804110_ADDR, HWIO_REG_804110_RMSK) +#define HWIO_REG_804110_INM(m) \ + in_dword_masked(HWIO_REG_804110_ADDR, m) +#define HWIO_REG_804110_OUT(v) out_dword(HWIO_REG_804110_ADDR, v) +#define HWIO_REG_804110_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_804110_ADDR, m, v, HWIO_REG_804110_IN); +#define HWIO_REG_804110_ENABLE_BMSK 0x80000000 +#define HWIO_REG_804110_ENABLE_SHFT 0x1f +#define HWIO_REG_804110_CONST_VIDC_BMSK 0x40000000 +#define HWIO_REG_804110_CONST_VIDC_SHFT 0x1e +#define HWIO_REG_804110_VIDCV_1080P_VERSION_BMSK 0xff000 +#define HWIO_REG_804110_VIDCV_1080P_VERSION_SHFT 0xc +#define HWIO_REG_804110_MGEN2MAXI_DATA_SEL_BMSK 0xf00 +#define HWIO_REG_804110_MGEN2MAXI_DATA_SEL_SHFT 0x8 +#define HWIO_REG_804110_MGEN2MAXI_XIN_SEL_BMSK 0x80 +#define HWIO_REG_804110_MGEN2MAXI_XIN_SEL_SHFT 0x7 +#define HWIO_REG_804110_MGEN2MAXI_ARB_SEL_BMSK 0x40 +#define HWIO_REG_804110_MGEN2MAXI_ARB_SEL_SHFT 0x6 +#define HWIO_REG_804110_MGEN2MAXI_TESTBUS_SEL_BMSK 0x30 +#define HWIO_REG_804110_MGEN2MAXI_TESTBUS_SEL_SHFT 0x4 +#define HWIO_REG_804110_AHB2AHB_TESTBUS_SEL_BMSK 0x8 +#define HWIO_REG_804110_AHB2AHB_TESTBUS_SEL_SHFT 0x3 +#define HWIO_REG_804110_MGEN2MAXI_AXI_SEL_BMSK 0x4 +#define HWIO_REG_804110_MGEN2MAXI_AXI_SEL_SHFT 0x2 +#define HWIO_REG_804110_SELECT_BMSK 0x3 +#define HWIO_REG_804110_SELECT_SHFT 0 + +#define HWIO_REG_616440_ADDR \ + (VIDC_MGEN2MAXI_REG_BASE + 0x00000034) +#define HWIO_REG_616440_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000034) +#define HWIO_REG_616440_RMSK 0xffff +#define HWIO_REG_616440_SHFT 0 +#define HWIO_REG_616440_IN in_dword_masked(\ + HWIO_REG_616440_ADDR,\ + HWIO_REG_616440_RMSK) +#define HWIO_REG_616440_INM(m) \ + in_dword_masked(HWIO_REG_616440_ADDR, m) +#define HWIO_REG_616440_OUT(v) \ + out_dword(HWIO_REG_616440_ADDR, v) +#define HWIO_REG_616440_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_616440_ADDR, m, v,\ + HWIO_REG_616440_IN); +#define HWIO_REG_616440_XBAR_IN_RD_LIM_BMSK 0xff00 +#define HWIO_REG_616440_XBAR_IN_RD_LIM_SHFT 0x8 +#define HWIO_REG_616440_XBAR_IN_WR_LIM_BMSK 0xff +#define HWIO_REG_616440_XBAR_IN_WR_LIM_SHFT 0 + +#define HWIO_REG_527219_ADDR \ + (VIDC_MGEN2MAXI_REG_BASE + 0x00000038) +#define HWIO_REG_527219_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x00000038) +#define HWIO_REG_527219_RMSK 0xffff +#define HWIO_REG_527219_SHFT 0 +#define HWIO_REG_527219_IN in_dword_masked(\ + HWIO_REG_527219_ADDR,\ + HWIO_REG_527219_RMSK) +#define HWIO_REG_527219_INM(m) \ + in_dword_masked(HWIO_REG_527219_ADDR, m) +#define HWIO_REG_527219_OUT(v) \ + out_dword(HWIO_REG_527219_ADDR, v) +#define HWIO_REG_527219_OUTM(m, v) \out_dword_masked_ns(\ + HWIO_REG_527219_ADDR, m, v,\ + HWIO_REG_527219_IN); +#define HWIO_REG_527219_XBAR_OUT_RD_LIM_BMSK 0xff00 +#define HWIO_REG_527219_XBAR_OUT_RD_LIM_SHFT 0x8 +#define HWIO_REG_527219_XBAR_OUT_WR_LIM_BMSK 0xff +#define HWIO_REG_527219_XBAR_OUT_WR_LIM_SHFT 0 + +#define HWIO_REG_922106_ADDR \ + (VIDC_MGEN2MAXI_REG_BASE + 0x0000003c) +#define HWIO_REG_922106_PHYS \ + (VIDC_MGEN2MAXI_REG_BASE_PHYS + 0x0000003c) +#define HWIO_REG_922106_RMSK 0xffff +#define HWIO_REG_922106_SHFT 0 +#define HWIO_REG_922106_IN in_dword_masked(\ + HWIO_REG_922106_ADDR,\ + HWIO_REG_922106_RMSK) +#define HWIO_REG_922106_INM(m) \ + in_dword_masked(HWIO_REG_922106_ADDR, m) +#define HWIO_REG_922106_OUT(v) \ + out_dword(HWIO_REG_922106_ADDR, v) +#define HWIO_REG_922106_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_922106_ADDR, m, v,\ + HWIO_REG_922106_IN); +#define HWIO_REG_922106_XBAR_OUT_MAX_RD_BURST_BMSK 0xff00 +#define HWIO_REG_922106_XBAR_OUT_MAX_RD_BURST_SHFT 0x8 +#define HWIO_REG_922106_XBAR_OUT_MAX_WR_BURST_BMSK 0xff +#define HWIO_REG_922106_XBAR_OUT_MAX_WR_BURST_SHFT 0 + +#define VIDC_ENHANCE_REG_BASE (VIDC_BASE + 0x000c0000) +#define VIDC_ENHANCE_REG_BASE_PHYS 0x044c0000 + +#define HWIO_REG_261029_ADDR (VIDC_ENHANCE_REG_BASE + 00000000) +#define HWIO_REG_261029_PHYS (VIDC_ENHANCE_REG_BASE_PHYS + 00000000) +#define HWIO_REG_261029_RMSK 0x10f +#define HWIO_REG_261029_SHFT 0 +#define HWIO_REG_261029_IN in_dword_masked(\ + HWIO_REG_261029_ADDR, HWIO_REG_261029_RMSK) +#define HWIO_REG_261029_INM(m) \ + in_dword_masked(HWIO_REG_261029_ADDR, m) +#define HWIO_REG_261029_OUT(v) \ + out_dword(HWIO_REG_261029_ADDR, v) +#define HWIO_REG_261029_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_261029_ADDR, m, v, HWIO_REG_261029_IN); +#define HWIO_REG_261029_AUTO_INC_EN_BMSK 0x100 +#define HWIO_REG_261029_AUTO_INC_EN_SHFT 0x8 +#define HWIO_REG_261029_DMI_RAM_SEL_BMSK 0xf +#define HWIO_REG_261029_DMI_RAM_SEL_SHFT 0 + +#define HWIO_REG_576200_ADDR (VIDC_ENHANCE_REG_BASE + 0x00000004) +#define HWIO_REG_576200_PHYS (VIDC_ENHANCE_REG_BASE_PHYS + 0x00000004) +#define HWIO_REG_576200_RMSK 0x7ff +#define HWIO_REG_576200_SHFT 0 +#define HWIO_REG_576200_IN in_dword_masked(\ + HWIO_REG_576200_ADDR, HWIO_REG_576200_RMSK) +#define HWIO_REG_576200_INM(m) \ + in_dword_masked(HWIO_REG_576200_ADDR, m) +#define HWIO_REG_576200_OUT(v) \ + out_dword(HWIO_REG_576200_ADDR, v) +#define HWIO_REG_576200_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_576200_ADDR, m, v, HWIO_REG_576200_IN); +#define HWIO_REG_576200_DMI_ADDR_BMSK 0x7ff +#define HWIO_REG_576200_DMI_ADDR_SHFT 0 + +#define HWIO_REG_917583_ADDR (VIDC_ENHANCE_REG_BASE + 0x00000008) +#define HWIO_REG_917583_PHYS (VIDC_ENHANCE_REG_BASE_PHYS + 0x00000008) +#define HWIO_REG_917583_RMSK 0xffffffff +#define HWIO_REG_917583_SHFT 0 +#define HWIO_REG_917583_IN in_dword_masked(\ + HWIO_REG_917583_ADDR, HWIO_REG_917583_RMSK) +#define HWIO_REG_917583_INM(m) \ + in_dword_masked(HWIO_REG_917583_ADDR, m) +#define HWIO_REG_917583_OUT(v) \ + out_dword(HWIO_REG_917583_ADDR, v) +#define HWIO_REG_917583_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_917583_ADDR, m, v, HWIO_REG_917583_IN); +#define HWIO_REG_917583_DMI_DATA_HI_BMSK 0xffffffff +#define HWIO_REG_917583_DMI_DATA_HI_SHFT 0 + +#define HWIO_REG_556274_ADDR (VIDC_ENHANCE_REG_BASE + 0x0000000c) +#define HWIO_REG_556274_PHYS (VIDC_ENHANCE_REG_BASE_PHYS + 0x0000000c) +#define HWIO_REG_556274_RMSK 0xffffffff +#define HWIO_REG_556274_SHFT 0 +#define HWIO_REG_556274_IN in_dword_masked(\ + HWIO_REG_556274_ADDR, HWIO_REG_556274_RMSK) +#define HWIO_REG_556274_INM(m) \ + in_dword_masked(HWIO_REG_556274_ADDR, m) +#define HWIO_REG_556274_OUT(v) \ + out_dword(HWIO_REG_556274_ADDR, v) +#define HWIO_REG_556274_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_556274_ADDR, m, v, HWIO_REG_556274_IN); +#define HWIO_REG_556274_DMI_DATA_LO_BMSK 0xffffffff +#define HWIO_REG_556274_DMI_DATA_LO_SHFT 0 + +#define HWIO_REG_39703_ADDR (VIDC_ENHANCE_REG_BASE + 0x00000010) +#define HWIO_REG_39703_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x00000010) +#define HWIO_REG_39703_RMSK 0x1f +#define HWIO_REG_39703_SHFT 0 +#define HWIO_REG_39703_IN in_dword_masked(\ + HWIO_REG_39703_ADDR, HWIO_REG_39703_RMSK) +#define HWIO_REG_39703_INM(m) \ + in_dword_masked(HWIO_REG_39703_ADDR, m) +#define HWIO_REG_39703_OUT(v) \ + out_dword(HWIO_REG_39703_ADDR, v) +#define HWIO_REG_39703_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_39703_ADDR, m, v, HWIO_REG_39703_IN); +#define HWIO_REG_39703_PIX_CACHE_TB_SEL_BMSK 0x1f +#define HWIO_REG_39703_PIX_CACHE_TB_SEL_SHFT 0 + +#define HWIO_REG_169013_ADDR (VIDC_ENHANCE_REG_BASE + 0x00000014) +#define HWIO_REG_169013_PHYS (VIDC_ENHANCE_REG_BASE_PHYS + 0x00000014) +#define HWIO_REG_169013_RMSK 0x3 +#define HWIO_REG_169013_SHFT 0 +#define HWIO_REG_169013_IN in_dword_masked(\ + HWIO_REG_169013_ADDR, HWIO_REG_169013_RMSK) +#define HWIO_REG_169013_INM(m) \ + in_dword_masked(HWIO_REG_169013_ADDR, m) +#define HWIO_REG_169013_OUT(v) \ + out_dword(HWIO_REG_169013_ADDR, v) +#define HWIO_REG_169013_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_169013_ADDR, m, v, HWIO_REG_169013_IN); +#define HWIO_REG_169013_PIX_CACHE_SW_RESET_BMSK 0x2 +#define HWIO_REG_169013_PIX_CACHE_SW_RESET_SHFT 0x1 +#define HWIO_REG_169013_CRIF_RESET_BMSK 0x1 +#define HWIO_REG_169013_CRIF_RESET_SHFT 0 + +#define HWIO_REG_22756_ADDR (VIDC_ENHANCE_REG_BASE + 0x00000018) +#define HWIO_REG_22756_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x00000018) +#define HWIO_REG_22756_RMSK 0x133f +#define HWIO_REG_22756_SHFT 0 +#define HWIO_REG_22756_IN in_dword_masked(\ + HWIO_REG_22756_ADDR, HWIO_REG_22756_RMSK) +#define HWIO_REG_22756_INM(m) \ + in_dword_masked(HWIO_REG_22756_ADDR, m) +#define HWIO_REG_22756_OUT(v) \ + out_dword(HWIO_REG_22756_ADDR, v) +#define HWIO_REG_22756_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_22756_ADDR, m, v, HWIO_REG_22756_IN); +#define HWIO_REG_22756_CACHE_HALT_BMSK 0x1000 +#define HWIO_REG_22756_CACHE_HALT_SHFT 0xc +#define HWIO_REG_22756_PAGE_SIZE_BMSK 0x300 +#define HWIO_REG_22756_PAGE_SIZE_SHFT 0x8 +#define HWIO_REG_22756_STATISTICS_OFF_BMSK 0x20 +#define HWIO_REG_22756_STATISTICS_OFF_SHFT 0x5 +#define HWIO_REG_22756_CACHE_PORT_SELECT_BMSK 0x10 +#define HWIO_REG_22756_CACHE_PORT_SELECT_SHFT 0x4 +#define HWIO_REG_22756_PREFETCH_EN_BMSK 0x8 +#define HWIO_REG_22756_PREFETCH_EN_SHFT 0x3 +#define HWIO_REG_22756_SS_TILE_FORMAT_BMSK 0x4 +#define HWIO_REG_22756_SS_TILE_FORMAT_SHFT 0x2 +#define HWIO_REG_22756_CACHE_EN_BMSK 0x2 +#define HWIO_REG_22756_CACHE_EN_SHFT 0x1 +#define HWIO_REG_22756_CACHE_TAG_CLEAR_BMSK 0x1 +#define HWIO_REG_22756_CACHE_TAG_CLEAR_SHFT 0 + +#define HWIO_REG_951731_ADDR \ + (VIDC_ENHANCE_REG_BASE + 0x0000001c) +#define HWIO_REG_951731_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x0000001c) +#define HWIO_REG_951731_RMSK 0x7ff07ff +#define HWIO_REG_951731_SHFT 0 +#define HWIO_REG_951731_IN in_dword_masked(\ + HWIO_REG_951731_ADDR,\ + HWIO_REG_951731_RMSK) +#define HWIO_REG_951731_INM(m) \ + in_dword_masked(HWIO_REG_951731_ADDR, m) +#define HWIO_REG_951731_OUT(v) \ + out_dword(HWIO_REG_951731_ADDR, v) +#define HWIO_REG_951731_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_951731_ADDR, m, v,\ + HWIO_REG_951731_IN); +#define HWIO_REG_951731_FRAME_HEIGHT_BMSK 0x7ff0000 +#define HWIO_REG_951731_FRAME_HEIGHT_SHFT 0x10 +#define HWIO_REG_951731_FRAME_WIDTH_BMSK 0x7ff +#define HWIO_REG_951731_FRAME_WIDTH_SHFT 0 + +#define HWIO_REG_905239_ADDR \ + (VIDC_ENHANCE_REG_BASE + 0x00000020) +#define HWIO_REG_905239_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x00000020) +#define HWIO_REG_905239_RMSK 0x3ffff +#define HWIO_REG_905239_SHFT 0 +#define HWIO_REG_905239_IN in_dword_masked(\ + HWIO_REG_905239_ADDR,\ + HWIO_REG_905239_RMSK) +#define HWIO_REG_905239_INM(m) \ + in_dword_masked(HWIO_REG_905239_ADDR, m) +#define HWIO_REG_905239_OUT(v) \ + out_dword(HWIO_REG_905239_ADDR, v) +#define HWIO_REG_905239_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_905239_ADDR, m, v,\ + HWIO_REG_905239_IN); +#define HWIO_REG_905239_LINEAR_LUMA_BMSK 0x3ffff +#define HWIO_REG_905239_LINEAR_LUMA_SHFT 0 +#define HWIO_REG_905239_TILE_LUMA_BMSK 0xff00 +#define HWIO_REG_905239_TILE_LUMA_SHFT 0x8 +#define HWIO_REG_905239_TILE_CHROMA_BMSK 0xff +#define HWIO_REG_905239_TILE_CHROMA_SHFT 0 + +#define HWIO_REG_804925_ADDR(n) \ + (VIDC_ENHANCE_REG_BASE + 0x00000024 + 4 * (n)) +#define HWIO_REG_804925_PHYS(n) \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x00000024 + 4 * (n)) +#define HWIO_REG_804925_RMSK 0xfffffff8 +#define HWIO_REG_804925_SHFT 0 +#define HWIO_REG_804925_MAXn 0x12 +#define HWIO_REG_804925_INI(n) \ + in_dword(HWIO_REG_804925_ADDR(n)) +#define HWIO_REG_804925_INMI(n, mask) \ + in_dword_masked(HWIO_REG_804925_ADDR(n), mask) +#define HWIO_REG_804925_OUTI(n, val) \ + out_dword(HWIO_REG_804925_ADDR(n), val) +#define HWIO_REG_804925_OUTMI(n, mask, val) \ + out_dword_masked_ns(HWIO_REG_804925_ADDR(n),\ + mask, val, HWIO_REG_804925_INI(n)); +#define HWIO_REG_804925_ADDR_BMSK 0xfffffff8 +#define HWIO_REG_804925_ADDR_SHFT 0x3 + +#define HWIO_REG_41909_ADDR(n) \ + (VIDC_ENHANCE_REG_BASE + 0x00000070 + 4 * (n)) +#define HWIO_REG_41909_PHYS(n) \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x00000070 + 4 * (n)) +#define HWIO_REG_41909_RMSK 0xfffffff8 +#define HWIO_REG_41909_SHFT 0 +#define HWIO_REG_41909_MAXn 0x12 +#define HWIO_REG_41909_INI(n) \ + in_dword(HWIO_REG_41909_ADDR(n)) +#define HWIO_REG_41909_INMI(n, mask) \ + in_dword_masked(HWIO_REG_41909_ADDR(n), mask) +#define HWIO_REG_41909_OUTI(n, val) \ + out_dword(HWIO_REG_41909_ADDR(n), val) +#define HWIO_REG_41909_OUTMI(n, mask, val) \ + out_dword_masked_ns(HWIO_REG_41909_ADDR(n),\ + mask, val, HWIO_REG_41909_INI(n)); +#define HWIO_REG_41909_ADDR_BMSK 0xfffffff8 +#define HWIO_REG_41909_ADDR_SHFT 0x3 + +#define HWIO_REG_919904_ADDR (VIDC_ENHANCE_REG_BASE + 0x000000bc) +#define HWIO_REG_919904_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x000000bc) +#define HWIO_REG_919904_RMSK 0x1 +#define HWIO_REG_919904_SHFT 0 +#define HWIO_REG_919904_IN in_dword_masked(\ + HWIO_REG_919904_ADDR,\ + HWIO_REG_919904_RMSK) +#define HWIO_REG_919904_INM(m) \ + in_dword_masked(HWIO_REG_919904_ADDR, m) +#define HWIO_REG_919904_IDLE_BMSK 0x1 +#define HWIO_REG_919904_IDLE_SHFT 0 + +#define HWIO_REG_278310_ADDR \ + (VIDC_ENHANCE_REG_BASE + 0x000000c0) +#define HWIO_REG_278310_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x000000c0) +#define HWIO_REG_278310_RMSK 0xffffffff +#define HWIO_REG_278310_SHFT 0 +#define HWIO_REG_278310_IN in_dword_masked(\ + HWIO_REG_278310_ADDR,\ + HWIO_REG_278310_RMSK) +#define HWIO_REG_278310_INM(m) \ + in_dword_masked(HWIO_REG_278310_ADDR, m) +#define HWIO_REG_278310_MISS_COUNT_BMSK 0xffffffff +#define HWIO_REG_278310_MISS_COUNT_SHFT 0 + +#define HWIO_REG_421222_ADDR \ + (VIDC_ENHANCE_REG_BASE + 0x000000c4) +#define HWIO_REG_421222_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x000000c4) +#define HWIO_REG_421222_RMSK 0xffffffff +#define HWIO_REG_421222_SHFT 0 +#define HWIO_REG_421222_IN in_dword_masked(\ + HWIO_REG_421222_ADDR,\ + HWIO_REG_421222_RMSK) +#define HWIO_REG_421222_INM(m) \ + in_dword_masked(HWIO_REG_421222_ADDR, m) +#define HWIO_REG_421222_HIT_COUNT_BMSK 0xffffffff +#define HWIO_REG_421222_HIT_COUNT_SHFT 0 + +#define HWIO_REG_609607_ADDR \ + (VIDC_ENHANCE_REG_BASE + 0x000000c8) +#define HWIO_REG_609607_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x000000c8) +#define HWIO_REG_609607_RMSK 0xffffffff +#define HWIO_REG_609607_SHFT 0 +#define HWIO_REG_609607_IN in_dword_masked(\ + HWIO_REG_609607_ADDR,\ + HWIO_REG_609607_RMSK) +#define HWIO_REG_609607_INM(m) \ + in_dword_masked(HWIO_REG_609607_ADDR, m) +#define HWIO_REG_609607_AXI_REQUEST_COUNT_BMSK 0xffffffff +#define HWIO_REG_609607_AXI_REQUEST_COUNT_SHFT 0 + +#define HWIO_REG_395232_ADDR \ + (VIDC_ENHANCE_REG_BASE + 0x000000cc) +#define HWIO_REG_395232_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x000000cc) +#define HWIO_REG_395232_RMSK 0xffffffff +#define HWIO_REG_395232_SHFT 0 +#define HWIO_REG_395232_IN in_dword_masked(\ + HWIO_REG_395232_ADDR,\ + HWIO_REG_395232_RMSK) +#define HWIO_REG_395232_INM(m) \ + in_dword_masked(HWIO_REG_395232_ADDR, m) +#define HWIO_REG_395232_CORE_REQUEST_COUNT_BMSK \ + 0xffffffff +#define HWIO_REG_395232_CORE_REQUEST_COUNT_SHFT 0 + +#define HWIO_REG_450146_ADDR \ + (VIDC_ENHANCE_REG_BASE + 0x000000d0) +#define HWIO_REG_450146_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x000000d0) +#define HWIO_REG_450146_RMSK 0xffffffff +#define HWIO_REG_450146_SHFT 0 +#define HWIO_REG_450146_IN in_dword_masked(\ + HWIO_REG_450146_ADDR,\ + HWIO_REG_450146_RMSK) +#define HWIO_REG_450146_INM(m) \ + in_dword_masked(HWIO_REG_450146_ADDR, m) +#define HWIO_REG_450146_AXI_BEAT_COUNT_BMSK 0xffffffff +#define HWIO_REG_450146_AXI_BEAT_COUNT_SHFT 0 + +#define HWIO_REG_610651_ADDR \ + (VIDC_ENHANCE_REG_BASE + 0x000000d4) +#define HWIO_REG_610651_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x000000d4) +#define HWIO_REG_610651_RMSK 0xffffffff +#define HWIO_REG_610651_SHFT 0 +#define HWIO_REG_610651_IN in_dword_masked(\ + HWIO_REG_610651_ADDR,\ + HWIO_REG_610651_RMSK) +#define HWIO_REG_610651_INM(m) \ + in_dword_masked(HWIO_REG_610651_ADDR, m) +#define HWIO_REG_610651_CORE_BEAT_COUNT_BMSK 0xffffffff +#define HWIO_REG_610651_CORE_BEAT_COUNT_SHFT 0 + +#define HWIO_REG_883784_ADDR \ + (VIDC_ENHANCE_REG_BASE + 0x000000d8) +#define HWIO_REG_883784_PHYS \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x000000d8) +#define HWIO_REG_883784_RMSK 0xffffffff +#define HWIO_REG_883784_SHFT 0 +#define HWIO_REG_883784_IN in_dword_masked(\ + HWIO_REG_883784_ADDR,\ + HWIO_REG_883784_RMSK) +#define HWIO_REG_883784_INM(m) \ + in_dword_masked(HWIO_REG_883784_ADDR, m) +#define HWIO_REG_883784_OUT(v) \ + out_dword(HWIO_REG_883784_ADDR, v) +#define HWIO_REG_883784_OUTM(m, v) out_dword_masked_ns(\ + HWIO_REG_883784_ADDR, m, v,\ + HWIO_REG_883784_IN); +#define HWIO_REG_883784_COUNTER_BMSK 0xffffff00 +#define HWIO_REG_883784_COUNTER_SHFT 0x8 +#define HWIO_REG_883784_ID_BMSK 0xf0 +#define HWIO_REG_883784_ID_SHFT 0x4 +#define HWIO_REG_883784_IGNORE_ID_BMSK 0x8 +#define HWIO_REG_883784_IGNORE_ID_SHFT 0x3 +#define HWIO_REG_883784_INPUT_SEL_BMSK 0x6 +#define HWIO_REG_883784_INPUT_SEL_SHFT 0x1 +#define HWIO_REG_883784_MISR_EN_BMSK 0x1 +#define HWIO_REG_883784_MISR_EN_SHFT 0 + +#define HWIO_REG_651391_ADDR(n) \ + (VIDC_ENHANCE_REG_BASE + 0x000000dc + 4 * (n)) +#define HWIO_REG_651391_PHYS(n) \ + (VIDC_ENHANCE_REG_BASE_PHYS + 0x000000dc + 4 * (n)) +#define HWIO_REG_651391_RMSK 0xffffffff +#define HWIO_REG_651391_SHFT 0 +#define HWIO_REG_651391_MAXn 0x1 +#define HWIO_REG_651391_INI(n) \ + in_dword(HWIO_REG_651391_ADDR(n)) +#define HWIO_REG_651391_INMI(n, mask) \ + in_dword_masked(HWIO_REG_651391_ADDR(n), mask) +#define HWIO_REG_651391_OUTI(n, val) \ + out_dword(HWIO_REG_651391_ADDR(n), val) +#define HWIO_REG_651391_SIGNATURE_BMSK 0xffffffff +#define HWIO_REG_651391_SIGNATURE_SHFT 0 + +#endif + diff --git a/drivers/video/msm/vidc/1080p/ddl/vidc_pix_cache.c b/drivers/video/msm/vidc/1080p/ddl/vidc_pix_cache.c new file mode 100644 index 00000000000..6870525144c --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vidc_pix_cache.c @@ -0,0 +1,349 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_hwio_reg.h" +#include "vidc_hwio.h" +#include "vidc_pix_cache.h" + + +#define VIDC_1080P_MAX_DEC_DPB 19 +#define VIDC_TILE_MULTIPLY_FACTOR 8192 + +void vidc_pix_cache_sw_reset(void) +{ + u32 sw_reset_value = 0; + + VIDC_HWIO_IN(REG_169013, &sw_reset_value); + sw_reset_value |= HWIO_REG_169013_PIX_CACHE_SW_RESET_BMSK; + VIDC_HWIO_OUT(REG_169013, sw_reset_value); + VIDC_HWIO_IN(REG_169013, &sw_reset_value); + sw_reset_value &= (~HWIO_REG_169013_PIX_CACHE_SW_RESET_BMSK); + VIDC_HWIO_OUT(REG_169013, sw_reset_value); +} + +void vidc_pix_cache_init_luma_chroma_base_addr(u32 dpb, + u32 *pn_dpb_luma_offset, u32 *pn_dpb_chroma_offset) +{ + u32 count, num_dpb_used = dpb; + u32 dpb_reset_value = VIDC_1080P_DEC_DPB_RESET_VALUE; + + if (num_dpb_used > VIDC_1080P_MAX_DEC_DPB) + num_dpb_used = VIDC_1080P_MAX_DEC_DPB; + for (count = 0; count < VIDC_1080P_MAX_DEC_DPB; count++) { + if (count < num_dpb_used) { + if (pn_dpb_luma_offset) { + VIDC_HWIO_OUTI( + REG_804925, + count, pn_dpb_luma_offset[count]); + } else { + VIDC_HWIO_OUTI( + REG_804925, + count, dpb_reset_value); + } + if (pn_dpb_chroma_offset) { + VIDC_HWIO_OUTI( + REG_41909, + count, pn_dpb_chroma_offset[count]); + } else { + VIDC_HWIO_OUTI( + REG_41909, + count, dpb_reset_value); + } + } else { + VIDC_HWIO_OUTI(REG_804925, + count, dpb_reset_value); + VIDC_HWIO_OUTI(REG_41909, + count, dpb_reset_value); + } + } +} + +void vidc_pix_cache_set_frame_range(u32 luma_size, u32 chroma_size) +{ + u32 frame_range; + + frame_range = + (((luma_size / VIDC_TILE_MULTIPLY_FACTOR) & 0xFF) << 8)| + ((chroma_size / VIDC_TILE_MULTIPLY_FACTOR) & 0xFF); + VIDC_HWIO_OUT(REG_905239, frame_range); +} +void vidc_pix_cache_set_frame_size(u32 frame_width, u32 frame_height) +{ + u32 frame_size; + frame_size = (((u32) (frame_height << HWIO_REG_951731_FRAME_HEIGHT_SHFT) & + HWIO_REG_951731_FRAME_HEIGHT_BMSK) | + ((u32) (frame_width << HWIO_REG_951731_FRAME_WIDTH_SHFT) & + HWIO_REG_951731_FRAME_WIDTH_BMSK)); + VIDC_HWIO_OUT(REG_951731, frame_size); +} + +void vidc_pix_cache_init_config( + struct vidc_1080P_pix_cache_config *config) +{ + u32 cfg_reg = 0; + + if (config->cache_enable) + cfg_reg |= HWIO_REG_22756_CACHE_EN_BMSK; + else + cfg_reg &= (~HWIO_REG_22756_CACHE_EN_BMSK); + if (config->port_select == VIDC_1080P_PIX_CACHE_PORT_A) + cfg_reg &= + (~HWIO_REG_22756_CACHE_PORT_SELECT_BMSK); + else + cfg_reg |= HWIO_REG_22756_CACHE_PORT_SELECT_BMSK; + if (!config->statistics_off) + cfg_reg |= HWIO_REG_22756_STATISTICS_OFF_BMSK; + else + cfg_reg &= (~HWIO_REG_22756_STATISTICS_OFF_BMSK); + if (config->prefetch_en) + cfg_reg |= HWIO_REG_22756_PREFETCH_EN_BMSK; + else + cfg_reg &= (~HWIO_REG_22756_PREFETCH_EN_BMSK); + cfg_reg &= (~HWIO_REG_22756_PAGE_SIZE_BMSK); + cfg_reg |= VIDC_SETFIELD(config->page_size, + HWIO_REG_22756_PAGE_SIZE_SHFT, + HWIO_REG_22756_PAGE_SIZE_BMSK); + VIDC_HWIO_OUT(REG_22756, cfg_reg); +} + +void vidc_pix_cache_set_prefetch_page_limit(u32 page_size_limit) +{ + u32 cfg_reg = 0; + + VIDC_HWIO_IN(REG_22756, &cfg_reg); + cfg_reg &= (~HWIO_REG_22756_PAGE_SIZE_BMSK); + cfg_reg |= VIDC_SETFIELD(page_size_limit, + HWIO_REG_22756_PAGE_SIZE_SHFT, + HWIO_REG_22756_PAGE_SIZE_BMSK); + VIDC_HWIO_OUT(REG_22756, cfg_reg); +} + +void vidc_pix_cache_enable_prefetch(u32 prefetch_enable) +{ + u32 cfg_reg = 0; + + VIDC_HWIO_IN(REG_22756, &cfg_reg); + if (prefetch_enable) + cfg_reg |= HWIO_REG_22756_PREFETCH_EN_BMSK; + else + cfg_reg &= (~HWIO_REG_22756_PREFETCH_EN_BMSK); + VIDC_HWIO_OUT(REG_22756, cfg_reg); +} + +void vidc_pix_cache_disable_statistics(u32 statistics_off) +{ + u32 cfg_reg = 0; + + VIDC_HWIO_IN(REG_22756, &cfg_reg); + if (!statistics_off) + cfg_reg |= HWIO_REG_22756_STATISTICS_OFF_BMSK; + else + cfg_reg &= (~HWIO_REG_22756_STATISTICS_OFF_BMSK); + VIDC_HWIO_OUT(REG_22756, cfg_reg); +} + +void vidc_pix_cache_set_port( + enum vidc_1080P_pix_cache_port_sel port_select) +{ + u32 cfg_reg = 0; + + VIDC_HWIO_IN(REG_22756, &cfg_reg); + if (port_select == VIDC_1080P_PIX_CACHE_PORT_A) + cfg_reg &= + (~HWIO_REG_22756_CACHE_PORT_SELECT_BMSK); + else + cfg_reg |= HWIO_REG_22756_CACHE_PORT_SELECT_BMSK; + VIDC_HWIO_OUT(REG_22756, cfg_reg); +} + +void vidc_pix_cache_enable_cache(u32 cache_enable) +{ + u32 cfg_reg = 0; + + VIDC_HWIO_IN(REG_22756, &cfg_reg); + if (cache_enable) + cfg_reg |= HWIO_REG_22756_CACHE_EN_BMSK; + else + cfg_reg &= (~HWIO_REG_22756_CACHE_EN_BMSK); + VIDC_HWIO_OUT(REG_22756, cfg_reg); +} + +void vidc_pix_cache_clear_cache_tags(void) +{ + u32 cfg_reg = 0; + + VIDC_HWIO_IN(REG_22756, &cfg_reg); + cfg_reg |= HWIO_REG_22756_CACHE_TAG_CLEAR_BMSK; + VIDC_HWIO_OUT(REG_22756, cfg_reg); + VIDC_HWIO_IN(REG_22756, &cfg_reg); + cfg_reg &= (~HWIO_REG_22756_CACHE_TAG_CLEAR_BMSK); + VIDC_HWIO_OUT(REG_22756, cfg_reg); +} + +void vidc_pix_cache_set_halt(u32 halt_enable) +{ + u32 cfg_reg = 0; + + VIDC_HWIO_IN(REG_22756, &cfg_reg); + if (halt_enable) + cfg_reg |= HWIO_REG_22756_CACHE_HALT_BMSK; + else + cfg_reg &= (~HWIO_REG_22756_CACHE_HALT_BMSK); + VIDC_HWIO_OUT(REG_22756, cfg_reg); +} + +void vidc_pix_cache_get_status_idle(u32 *idle_status) +{ + VIDC_HWIO_IN(REG_919904, idle_status); +} + +void vidc_pix_cache_set_ram(u32 ram_select) +{ + u32 dmi_cfg_reg = 0; + + VIDC_HWIO_IN(REG_261029, &dmi_cfg_reg); + dmi_cfg_reg &= (~HWIO_REG_261029_DMI_RAM_SEL_BMSK); + dmi_cfg_reg |= VIDC_SETFIELD(ram_select, + HWIO_REG_261029_AUTO_INC_EN_SHFT, + HWIO_REG_261029_DMI_RAM_SEL_BMSK); + VIDC_HWIO_OUT(REG_261029, dmi_cfg_reg); +} + +void vidc_pix_cache_set_auto_inc_ram_addr(u32 auto_inc_enable) +{ + u32 dmi_cfg_reg = 0; + + VIDC_HWIO_IN(REG_261029, &dmi_cfg_reg); + if (auto_inc_enable) + dmi_cfg_reg |= HWIO_REG_261029_AUTO_INC_EN_BMSK; + else + dmi_cfg_reg &= (~HWIO_REG_261029_AUTO_INC_EN_BMSK); + VIDC_HWIO_OUT(REG_261029, dmi_cfg_reg); +} + + +void vidc_pix_cache_read_ram_data(u32 src_ram_address, + u32 ram_size, u32 *dest_address) +{ + u32 count, dmi_cfg_reg = 0; + + VIDC_HWIO_IN(REG_261029, &dmi_cfg_reg); + VIDC_HWIO_OUT(REG_576200, src_ram_address); + vidc_pix_cache_set_auto_inc_ram_addr(1); + for (count = 0; count < ram_size; count++) { + VIDC_HWIO_IN(REG_556274, dest_address); + dest_address++; + VIDC_HWIO_IN(REG_917583, dest_address); + dest_address++; + } + VIDC_HWIO_OUT(REG_261029, dmi_cfg_reg); +} + +void vidc_pix_cache_write_ram_data(u32 *src_address, + u32 ram_size, u32 dest_ram_address) +{ + u32 count, dmi_cfg_reg = 0; + + VIDC_HWIO_IN(REG_261029, &dmi_cfg_reg); + VIDC_HWIO_OUT(REG_576200, dest_ram_address); + vidc_pix_cache_set_auto_inc_ram_addr(1); + for (count = 0; count < ram_size; count++) { + VIDC_HWIO_OUT(REG_917583, *src_address); + src_address++; + VIDC_HWIO_OUT(REG_556274, *src_address); + src_address++; + } + VIDC_HWIO_OUT(REG_261029, dmi_cfg_reg); +} + +void vidc_pix_cache_get_statistics( + struct vidc_1080P_pix_cache_statistics *statistics) +{ + VIDC_HWIO_IN(REG_278310, + &statistics->access_miss); + VIDC_HWIO_IN(REG_421222, + &statistics->access_hit); + VIDC_HWIO_IN(REG_609607, + &statistics->axi_req); + VIDC_HWIO_IN(REG_395232, + &statistics->core_req); + VIDC_HWIO_IN(REG_450146, + &statistics->axi_bus); + VIDC_HWIO_IN(REG_610651, + &statistics->core_bus); +} + +void vidc_pix_cache_enable_misr(u32 misr_enable) +{ + u32 misr_cfg_reg = 0; + + VIDC_HWIO_IN(REG_883784, &misr_cfg_reg); + if (misr_enable) + misr_cfg_reg |= HWIO_REG_883784_MISR_EN_BMSK; + else + misr_cfg_reg &= + (~HWIO_REG_883784_MISR_EN_BMSK); + VIDC_HWIO_OUT(REG_261029, misr_cfg_reg); +} + +void vidc_pix_cache_set_misr_interface(u32 input_select) +{ + u32 misr_cfg_reg = 0; + + VIDC_HWIO_IN(REG_883784, &misr_cfg_reg); + misr_cfg_reg &= (~HWIO_REG_883784_INPUT_SEL_BMSK); + misr_cfg_reg |= VIDC_SETFIELD(input_select, + HWIO_REG_883784_INPUT_SEL_SHFT, + HWIO_REG_883784_INPUT_SEL_BMSK); + VIDC_HWIO_OUT(REG_261029, misr_cfg_reg); +} + +void vidc_pix_cache_set_misr_id_filtering( + struct vidc_1080P_pix_cache_misr_id_filtering *filter_id) +{ + u32 misr_cfg_reg = 0; + + VIDC_HWIO_IN(REG_883784, &misr_cfg_reg); + if (filter_id->ignore_id) + misr_cfg_reg |= + HWIO_REG_883784_IGNORE_ID_BMSK; + else + misr_cfg_reg &= + (~HWIO_REG_883784_IGNORE_ID_BMSK); + misr_cfg_reg &= (~HWIO_REG_883784_ID_BMSK); + misr_cfg_reg |= VIDC_SETFIELD(filter_id->id, + HWIO_REG_883784_ID_SHFT, + HWIO_REG_883784_ID_BMSK); + VIDC_HWIO_OUT(REG_261029, misr_cfg_reg); +} + +void vidc_pix_cache_set_misr_filter_trans(u32 no_of_trans) +{ + u32 misr_cfg_reg = 0; + + VIDC_HWIO_IN(REG_883784, &misr_cfg_reg); + misr_cfg_reg &= (~HWIO_REG_883784_COUNTER_BMSK); + misr_cfg_reg |= VIDC_SETFIELD(no_of_trans, + HWIO_REG_883784_COUNTER_SHFT, + HWIO_REG_883784_COUNTER_BMSK); + VIDC_HWIO_OUT(REG_261029, misr_cfg_reg); +} + +void vidc_pix_cache_get_misr_signatures( + struct vidc_1080P_pix_cache_misr_signature *signatures) +{ + VIDC_HWIO_INI(REG_651391, 0, + &signatures->signature0); + VIDC_HWIO_INI(REG_651391, 1, + &signatures->signature1); +} diff --git a/drivers/video/msm/vidc/1080p/ddl/vidc_pix_cache.h b/drivers/video/msm/vidc/1080p/ddl/vidc_pix_cache.h new file mode 100644 index 00000000000..e8a93a1e026 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/ddl/vidc_pix_cache.h @@ -0,0 +1,87 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIDEO_CORE_PIXCACHE_ +#define _VIDEO_CORE_PIXCACHE_ + + +#include "vidc.h" + +#define VIDC_1080P_DEC_DPB_RESET_VALUE 0xFFFFFFF8 + +enum vidc_1080P_pix_cache_port_sel{ + VIDC_1080P_PIX_CACHE_PORT_A = 0, + VIDC_1080P_PIX_CACHE_PORT_B = 1, + VIDC_1080P_PIX_CACHE_PORT_32BIT = 0x7FFFFFFF +}; +enum vidc_1080P_pix_cache_page_size{ + VIDC_1080P_PIX_CACHE_PAGE_SIZE_1K = 0, + VIDC_1080P_PIX_CACHE_PAGE_SIZE_2K = 1, + VIDC_1080P_PIX_CACHE_PAGE_SIZE_4K = 2 +}; +struct vidc_1080P_pix_cache_config{ + u32 cache_enable; + u32 prefetch_en; + enum vidc_1080P_pix_cache_port_sel port_select; + u32 statistics_off; + enum vidc_1080P_pix_cache_page_size page_size; +}; +struct vidc_1080P_pix_cache_statistics{ + u32 access_miss; + u32 access_hit; + u32 axi_req; + u32 core_req; + u32 axi_bus; + u32 core_bus; +}; +struct vidc_1080P_pix_cache_misr_id_filtering{ + u32 ignore_id; + u32 id; +}; +struct vidc_1080P_pix_cache_misr_signature{ + u32 signature0; + u32 signature1; +}; + +void vidc_pix_cache_sw_reset(void); +void vidc_pix_cache_init_luma_chroma_base_addr(u32 dpb, + u32 *pn_dpb_luma_offset, u32 *pn_dpb_chroma_offset); +void vidc_pix_cache_set_frame_range(u32 luma_size, u32 chroma_size); +void vidc_pix_cache_set_frame_size(u32 frame_width, u32 frame_height); +void vidc_pix_cache_init_config( + struct vidc_1080P_pix_cache_config *config); +void vidc_pix_cache_set_prefetch_page_limit(u32 page_size_limit); +void vidc_pix_cache_enable_prefetch(u32 prefetch_enable); +void vidc_pix_cache_disable_statistics(u32 statistics_off); +void vidc_pix_cache_set_port( + enum vidc_1080P_pix_cache_port_sel port_select); +void vidc_pix_cache_enable_cache(u32 cache_enable); +void vidc_pix_cache_clear_cache_tags(void); +void vidc_pix_cache_set_halt(u32 halt_enable); +void vidc_pix_cache_get_status_idle(u32 *idle_status); +void vidc_pix_cache_set_ram(u32 ram_select); +void vidc_pix_cache_set_auto_inc_ram_addr(u32 auto_inc_enable); +void vidc_pix_cache_read_ram_data(u32 src_ram_address, u32 ram_size, + u32 *dest_address); +void vidc_pix_cache_write_ram_data(u32 *src_address, u32 ram_size, + u32 dest_ram_address); +void vidc_pix_cache_get_statistics( + struct vidc_1080P_pix_cache_statistics *statistics); +void vidc_pix_cache_enable_misr(u32 misr_enable); +void vidc_pix_cache_set_misr_interface(u32 input_select); +void vidc_pix_cache_set_misr_id_filtering( + struct vidc_1080P_pix_cache_misr_id_filtering *filter_id); +void vidc_pix_cache_set_misr_filter_trans(u32 no_of_trans); +void vidc_pix_cache_get_misr_signatures( + struct vidc_1080P_pix_cache_misr_signature *signatures); +#endif diff --git a/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.c b/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.c new file mode 100644 index 00000000000..ec6c39ad31b --- /dev/null +++ b/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.c @@ -0,0 +1,508 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vidc.h" +#include "vcd_res_tracker.h" +#include "vidc_init.h" + +static unsigned int vidc_clk_table[3] = { + 48000000, 133330000, 200000000 +}; +static struct res_trk_context resource_context; + +#define VIDC_FW "vidc_1080p.fw" +#define VIDC_FW_SIZE SZ_1M + +unsigned char *vidc_video_codec_fw; +u32 vidc_video_codec_fw_size; +static u32 res_trk_get_clk(void); +static void res_trk_put_clk(void); + +static u32 res_trk_get_clk() +{ + if (resource_context.vcodec_clk || + resource_context.vcodec_pclk) { + VCDRES_MSG_ERROR("%s() Clock reference exists\n", + __func__); + goto bail_out; + } + resource_context.vcodec_clk = clk_get(resource_context.device, + "vcodec_clk"); + if (IS_ERR(resource_context.vcodec_clk)) { + VCDRES_MSG_ERROR("%s(): vcodec_clk get failed\n", + __func__); + goto bail_out; + } + resource_context.vcodec_pclk = clk_get(resource_context.device, + "vcodec_pclk"); + if (IS_ERR(resource_context.vcodec_pclk)) { + VCDRES_MSG_ERROR("%s(): vcodec_pclk get failed\n", + __func__); + goto release_vcodec_clk; + } + resource_context.vcodec_axi_a_clk = clk_get(resource_context.device, + "vcodec_axi_a_clk"); + if (IS_ERR(resource_context.vcodec_axi_a_clk)) { + VCDRES_MSG_ERROR("%s(): vcodec_axi_a_clk get failed\n", + __func__); + resource_context.vcodec_axi_a_clk = NULL; + } + resource_context.vcodec_axi_b_clk = clk_get(resource_context.device, + "vcodec_axi_b_clk"); + if (IS_ERR(resource_context.vcodec_axi_b_clk)) { + VCDRES_MSG_ERROR("%s(): vcodec_axi_b_clk get failed\n", + __func__); + resource_context.vcodec_axi_b_clk = NULL; + } + if (clk_set_rate(resource_context.vcodec_clk, + vidc_clk_table[0])) { + VCDRES_MSG_ERROR("%s(): set rate failed in power up\n", + __func__); + goto release_vcodec_pclk; + } + return true; +release_vcodec_pclk: + if (resource_context.vcodec_axi_a_clk) + clk_put(resource_context.vcodec_axi_a_clk); + if (resource_context.vcodec_axi_b_clk) + clk_put(resource_context.vcodec_axi_b_clk); + clk_put(resource_context.vcodec_pclk); + resource_context.vcodec_pclk = NULL; + resource_context.vcodec_axi_a_clk = NULL; + resource_context.vcodec_axi_b_clk = NULL; +release_vcodec_clk: + clk_put(resource_context.vcodec_clk); + resource_context.vcodec_clk = NULL; +bail_out: + return false; +} + +static void res_trk_put_clk() +{ + if (resource_context.vcodec_clk) + clk_put(resource_context.vcodec_clk); + if (resource_context.vcodec_pclk) + clk_put(resource_context.vcodec_pclk); + if (resource_context.vcodec_axi_a_clk) + clk_put(resource_context.vcodec_axi_a_clk); + if (resource_context.vcodec_axi_b_clk) + clk_put(resource_context.vcodec_axi_b_clk); + resource_context.vcodec_axi_b_clk = NULL; + resource_context.vcodec_axi_a_clk = NULL; + resource_context.vcodec_clk = NULL; + resource_context.vcodec_pclk = NULL; +} + +static u32 res_trk_shutdown_vidc(void) +{ + mutex_lock(&resource_context.lock); + if (resource_context.clock_enabled) { + mutex_unlock(&resource_context.lock); + VCDRES_MSG_LOW("\n Calling CLK disable in Power Down\n"); + res_trk_disable_clocks(); + mutex_lock(&resource_context.lock); + } + res_trk_put_clk(); + if (resource_context.footswitch) { + if (regulator_disable(resource_context.footswitch)) + VCDRES_MSG_ERROR("Regulator disable failed\n"); + regulator_put(resource_context.footswitch); + resource_context.footswitch = NULL; + } + if (pm_runtime_put(resource_context.device) < 0) + VCDRES_MSG_ERROR("Error : pm_runtime_put failed"); + mutex_unlock(&resource_context.lock); + return true; +} + +u32 res_trk_enable_clocks(void) +{ + VCDRES_MSG_LOW("\n in res_trk_enable_clocks()"); + mutex_lock(&resource_context.lock); + if (!resource_context.clock_enabled) { + VCDRES_MSG_LOW("Enabling IRQ in %s()\n", __func__); + enable_irq(resource_context.irq_num); + VCDRES_MSG_LOW("%s(): Enabling the clocks\n", __func__); + if (resource_context.vcodec_clk && + resource_context.vcodec_pclk) { + if (clk_enable(resource_context.vcodec_pclk)) { + VCDRES_MSG_ERROR("vidc pclk Enable fail\n"); + goto bail_out; + } + if (clk_enable(resource_context.vcodec_clk)) { + VCDRES_MSG_ERROR("vidc core clk Enable fail\n"); + goto vidc_disable_pclk; + } + if (resource_context.vcodec_axi_a_clk && + resource_context.vcodec_axi_b_clk) { + if (clk_enable(resource_context. + vcodec_axi_a_clk)) + VCDRES_MSG_ERROR("a_clk Enable fail\n"); + if (clk_enable(resource_context. + vcodec_axi_b_clk)) + VCDRES_MSG_ERROR("b_clk Enable fail\n"); + } + + VCDRES_MSG_LOW("%s(): Clocks enabled!\n", __func__); + } else { + VCDRES_MSG_ERROR("%s(): Clocks enable failed!\n", + __func__); + goto bail_out; + } + } + resource_context.clock_enabled = 1; + mutex_unlock(&resource_context.lock); + return true; +vidc_disable_pclk: + clk_disable(resource_context.vcodec_pclk); +bail_out: + mutex_unlock(&resource_context.lock); + return false; +} + +static u32 res_trk_sel_clk_rate(unsigned long hclk_rate) +{ + u32 status = true; + mutex_lock(&resource_context.lock); + if (clk_set_rate(resource_context.vcodec_clk, + hclk_rate)) { + VCDRES_MSG_ERROR("vidc hclk set rate failed\n"); + status = false; + } else + resource_context.vcodec_clk_rate = hclk_rate; + mutex_unlock(&resource_context.lock); + return status; +} + +static u32 res_trk_get_clk_rate(unsigned long *phclk_rate) +{ + u32 status = true; + mutex_lock(&resource_context.lock); + if (phclk_rate) { + *phclk_rate = clk_get_rate(resource_context.vcodec_clk); + if (!(*phclk_rate)) { + VCDRES_MSG_ERROR("vidc hclk get rate failed\n"); + status = false; + } + } else + status = false; + mutex_unlock(&resource_context.lock); + return status; +} + +u32 res_trk_disable_clocks(void) +{ + u32 status = false; + VCDRES_MSG_LOW("in res_trk_disable_clocks()\n"); + mutex_lock(&resource_context.lock); + if (resource_context.clock_enabled) { + VCDRES_MSG_LOW("Disabling IRQ in %s()\n", __func__); + disable_irq_nosync(resource_context.irq_num); + VCDRES_MSG_LOW("%s(): Disabling the clocks ...\n", __func__); + resource_context.clock_enabled = 0; + if (resource_context.vcodec_clk) + clk_disable(resource_context.vcodec_clk); + if (resource_context.vcodec_pclk) + clk_disable(resource_context.vcodec_pclk); + if (resource_context.vcodec_axi_a_clk) + clk_disable(resource_context.vcodec_axi_a_clk); + if (resource_context.vcodec_axi_b_clk) + clk_disable(resource_context.vcodec_axi_b_clk); + status = true; + } + mutex_unlock(&resource_context.lock); + return status; +} + +static u32 res_trk_vidc_pwr_up(void) +{ + mutex_lock(&resource_context.lock); + + if (pm_runtime_get(resource_context.device) < 0) { + VCDRES_MSG_ERROR("Error : pm_runtime_get failed\n"); + goto bail_out; + } + resource_context.footswitch = regulator_get(NULL, "fs_ved"); + if (IS_ERR(resource_context.footswitch)) { + VCDRES_MSG_ERROR("foot switch get failed\n"); + resource_context.footswitch = NULL; + } else + regulator_enable(resource_context.footswitch); + if (!res_trk_get_clk()) + goto rel_vidc_pm_runtime; + mutex_unlock(&resource_context.lock); + return true; + +rel_vidc_pm_runtime: + if (pm_runtime_put(resource_context.device) < 0) + VCDRES_MSG_ERROR("Error : pm_runtime_put failed"); +bail_out: + mutex_unlock(&resource_context.lock); + return false; +} + +u32 res_trk_power_up(void) +{ + VCDRES_MSG_LOW("clk_regime_rail_enable"); + VCDRES_MSG_LOW("clk_regime_sel_rail_control"); +#ifdef CONFIG_MSM_BUS_SCALING + resource_context.pcl = 0; + if (resource_context.vidc_bus_client_pdata) { + resource_context.pcl = msm_bus_scale_register_client( + resource_context.vidc_bus_client_pdata); + VCDRES_MSG_LOW("%s(), resource_context.pcl = %x", __func__, + resource_context.pcl); + } + if (resource_context.pcl == 0) { + dev_err(resource_context.device, + "register bus client returned NULL\n"); + return false; + } +#endif + return res_trk_vidc_pwr_up(); +} + +u32 res_trk_power_down(void) +{ + VCDRES_MSG_LOW("clk_regime_rail_disable"); +#ifdef CONFIG_MSM_BUS_SCALING + msm_bus_scale_client_update_request(resource_context.pcl, 0); + msm_bus_scale_unregister_client(resource_context.pcl); +#endif + VCDRES_MSG_MED("res_trk_power_down():: Calling " + "res_trk_shutdown_vidc()\n"); + return res_trk_shutdown_vidc(); +} + +u32 res_trk_get_max_perf_level(u32 *pn_max_perf_lvl) +{ + if (!pn_max_perf_lvl) { + VCDRES_MSG_ERROR("%s(): pn_max_perf_lvl is NULL\n", + __func__); + return false; + } + *pn_max_perf_lvl = RESTRK_1080P_MAX_PERF_LEVEL; + return true; +} + +#ifdef CONFIG_MSM_BUS_SCALING +int res_trk_update_bus_perf_level(struct vcd_dev_ctxt *dev_ctxt, u32 perf_level) +{ + struct vcd_clnt_ctxt *cctxt_itr = NULL; + u32 enc_perf_level = 0, dec_perf_level = 0; + u32 bus_clk_index, client_type = 0; + int rc = 0; + + cctxt_itr = dev_ctxt->cctxt_list_head; + while (cctxt_itr) { + if (cctxt_itr->decoding) + dec_perf_level += cctxt_itr->reqd_perf_lvl; + else + enc_perf_level += cctxt_itr->reqd_perf_lvl; + cctxt_itr = cctxt_itr->next; + } + if (!enc_perf_level) + client_type = 1; + if (perf_level <= RESTRK_1080P_VGA_PERF_LEVEL) + bus_clk_index = 0; + else if (perf_level <= RESTRK_1080P_720P_PERF_LEVEL) + bus_clk_index = 1; + else + bus_clk_index = 2; + + if (dev_ctxt->reqd_perf_lvl + dev_ctxt->curr_perf_lvl == 0) + bus_clk_index = 2; + + bus_clk_index = (bus_clk_index << 1) + (client_type + 1); + VCDRES_MSG_LOW("%s(), bus_clk_index = %d", __func__, bus_clk_index); + VCDRES_MSG_LOW("%s(),context.pcl = %x", __func__, resource_context.pcl); + VCDRES_MSG_LOW("%s(), bus_perf_level = %x", __func__, perf_level); + rc = msm_bus_scale_client_update_request(resource_context.pcl, + bus_clk_index); + return rc; +} +#endif + +u32 res_trk_set_perf_level(u32 req_perf_lvl, u32 *pn_set_perf_lvl, + struct vcd_dev_ctxt *dev_ctxt) +{ + u32 vidc_freq = 0; + if (!pn_set_perf_lvl || !dev_ctxt) { + VCDRES_MSG_ERROR("%s(): NULL pointer! dev_ctxt(%p)\n", + __func__, dev_ctxt); + return false; + } + VCDRES_MSG_LOW("%s(), req_perf_lvl = %d", __func__, req_perf_lvl); +#ifdef CONFIG_MSM_BUS_SCALING + if (!res_trk_update_bus_perf_level(dev_ctxt, req_perf_lvl) < 0) { + VCDRES_MSG_ERROR("%s(): update buf perf level failed\n", + __func__); + return false; + } + +#endif + if (dev_ctxt->reqd_perf_lvl + dev_ctxt->curr_perf_lvl == 0) + req_perf_lvl = RESTRK_1080P_MAX_PERF_LEVEL; + + if (req_perf_lvl <= RESTRK_1080P_VGA_PERF_LEVEL) { + vidc_freq = vidc_clk_table[0]; + *pn_set_perf_lvl = RESTRK_1080P_VGA_PERF_LEVEL; + } else if (req_perf_lvl <= RESTRK_1080P_720P_PERF_LEVEL) { + vidc_freq = vidc_clk_table[1]; + *pn_set_perf_lvl = RESTRK_1080P_720P_PERF_LEVEL; + } else { + vidc_freq = vidc_clk_table[2]; + *pn_set_perf_lvl = RESTRK_1080P_MAX_PERF_LEVEL; + } + resource_context.perf_level = *pn_set_perf_lvl; + VCDRES_MSG_MED("VIDC: vidc_freq = %u, req_perf_lvl = %u\n", + vidc_freq, req_perf_lvl); +#ifdef USE_RES_TRACKER + if (req_perf_lvl != RESTRK_1080P_MIN_PERF_LEVEL) { + VCDRES_MSG_MED("%s(): Setting vidc freq to %u\n", + __func__, vidc_freq); + if (!res_trk_sel_clk_rate(vidc_freq)) { + VCDRES_MSG_ERROR("%s(): res_trk_sel_clk_rate FAILED\n", + __func__); + *pn_set_perf_lvl = 0; + return false; + } + } +#endif + VCDRES_MSG_MED("%s() set perl level : %d", __func__, *pn_set_perf_lvl); + return true; +} + +u32 res_trk_get_curr_perf_level(u32 *pn_perf_lvl) +{ + unsigned long freq; + + if (!pn_perf_lvl) { + VCDRES_MSG_ERROR("%s(): pn_perf_lvl is NULL\n", + __func__); + return false; + } + VCDRES_MSG_LOW("clk_regime_msm_get_clk_freq_hz"); + if (!res_trk_get_clk_rate(&freq)) { + VCDRES_MSG_ERROR("%s(): res_trk_get_clk_rate FAILED\n", + __func__); + *pn_perf_lvl = 0; + return false; + } + *pn_perf_lvl = resource_context.perf_level; + VCDRES_MSG_MED("%s(): freq = %lu, *pn_perf_lvl = %u", __func__, + freq, *pn_perf_lvl); + return true; +} + +u32 res_trk_download_firmware(void) +{ + const struct firmware *fw_video = NULL; + int rc = 0; + u32 status = true; + + VCDRES_MSG_HIGH("%s(): Request firmware download\n", + __func__); + mutex_lock(&resource_context.lock); + rc = request_firmware(&fw_video, VIDC_FW, + resource_context.device); + if (rc) { + VCDRES_MSG_ERROR("request_firmware for %s error %d\n", + VIDC_FW, rc); + status = false; + goto bail_out; + } + vidc_video_codec_fw = (unsigned char *)fw_video->data; + vidc_video_codec_fw_size = (u32) fw_video->size; +bail_out: + mutex_unlock(&resource_context.lock); + return status; +} + +void res_trk_init(struct device *device, u32 irq) +{ + if (resource_context.device || resource_context.irq_num || + !device) { + VCDRES_MSG_ERROR("%s() Resource Tracker Init error\n", + __func__); + } else { + memset(&resource_context, 0, sizeof(resource_context)); + mutex_init(&resource_context.lock); + resource_context.device = device; + resource_context.irq_num = irq; + resource_context.vidc_platform_data = + (struct msm_vidc_platform_data *) device->platform_data; + if (resource_context.vidc_platform_data) { + resource_context.memtype = + resource_context.vidc_platform_data->memtype; +#ifdef CONFIG_MSM_BUS_SCALING + resource_context.vidc_bus_client_pdata = + resource_context.vidc_platform_data-> + vidc_bus_client_pdata; +#endif + } else { + resource_context.memtype = -1; + } + resource_context.core_type = VCD_CORE_1080P; + if (resource_context.memtype == MEMTYPE_EBI1) { + resource_context.device_addr = + (phys_addr_t) + allocate_contiguous_memory_nomap(VIDC_FW_SIZE, + resource_context.memtype, SZ_4K); + if (resource_context.device_addr) { + resource_context.base_addr = (u8 *) + ioremap((unsigned long) + resource_context.device_addr, VIDC_FW_SIZE); + if (!resource_context.base_addr) { + free_contiguous_memory_by_paddr( + (unsigned long) + resource_context.device_addr); + resource_context.device_addr = + (phys_addr_t)NULL; + } + } + } + } +} + +u32 res_trk_get_core_type(void){ + return resource_context.core_type; +} + +u32 res_trk_get_firmware_addr(struct res_trk_firmware_addr *firm_addr) +{ + int status = -1; + if (firm_addr && resource_context.base_addr && + resource_context.device_addr) { + firm_addr->base_addr = resource_context.base_addr; + firm_addr->device_addr = resource_context.device_addr; + firm_addr->buf_size = VIDC_FW_SIZE; + status = 0; + } + return status; +} + +u32 res_trk_get_mem_type(void){ + return resource_context.memtype; +} diff --git a/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.h b/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.h new file mode 100644 index 00000000000..16680ad443c --- /dev/null +++ b/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker.h @@ -0,0 +1,69 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VIDEO_720P_RESOURCE_TRACKER_H_ +#define _VIDEO_720P_RESOURCE_TRACKER_H_ + +#include +#include "vcd_res_tracker_api.h" +#ifdef CONFIG_MSM_BUS_SCALING +#include +#include +#endif +#include + +#define RESTRK_1080P_VGA_PERF_LEVEL VCD_MIN_PERF_LEVEL +#define RESTRK_1080P_720P_PERF_LEVEL 108000 +#define RESTRK_1080P_1080P_PERF_LEVEL 244800 + +#define RESTRK_1080P_MIN_PERF_LEVEL RESTRK_1080P_VGA_PERF_LEVEL +#define RESTRK_1080P_MAX_PERF_LEVEL RESTRK_1080P_1080P_PERF_LEVEL +struct res_trk_context { + struct device *device; + u32 irq_num; + struct mutex lock; + struct clk *vcodec_clk; + struct clk *vcodec_pclk; + struct clk *vcodec_axi_a_clk; + struct clk *vcodec_axi_b_clk; + unsigned long vcodec_clk_rate; + unsigned int clock_enabled; + unsigned int perf_level; + struct regulator *footswitch; + struct msm_vidc_platform_data *vidc_platform_data; + int memtype; +#ifdef CONFIG_MSM_BUS_SCALING + struct msm_bus_scale_pdata *vidc_bus_client_pdata; + uint32_t pcl; +#endif + u32 core_type; + u8 *base_addr; + phys_addr_t device_addr; +}; + +#if DEBUG + +#define VCDRES_MSG_LOW(xx_fmt...) printk(KERN_INFO "\n\t* " xx_fmt) +#define VCDRES_MSG_MED(xx_fmt...) printk(KERN_INFO "\n * " xx_fmt) + +#else + +#define VCDRES_MSG_LOW(xx_fmt...) +#define VCDRES_MSG_MED(xx_fmt...) + +#endif + +#define VCDRES_MSG_HIGH(xx_fmt...) printk(KERN_WARNING "\n" xx_fmt) +#define VCDRES_MSG_ERROR(xx_fmt...) printk(KERN_ERR "\n err: " xx_fmt) +#define VCDRES_MSG_FATAL(xx_fmt...) printk(KERN_ERR "\n " xx_fmt) + +#endif diff --git a/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker_api.h b/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker_api.h new file mode 100644 index 00000000000..95cddd91ff7 --- /dev/null +++ b/drivers/video/msm/vidc/1080p/resource_tracker/vcd_res_tracker_api.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VIDEO_720P_RESOURCE_TRACKER_API_H_ +#define _VIDEO_720P_RESOURCE_TRACKER_API_H_ + +#include "vcd_core.h" + +struct res_trk_firmware_addr { + u8 *base_addr; + phys_addr_t device_addr; + u32 buf_size; +}; +void res_trk_init(struct device *device, u32 irq); +u32 res_trk_power_up(void); +u32 res_trk_power_down(void); +u32 res_trk_enable_clocks(void); +u32 res_trk_disable_clocks(void); +u32 res_trk_get_max_perf_level(u32 *pn_max_perf_lvl); +u32 res_trk_set_perf_level(u32 req_perf_lvl, u32 *pn_set_perf_lvl, + struct vcd_dev_ctxt *dev_ctxt); +u32 res_trk_get_curr_perf_level(u32 *pn_perf_lvl); +u32 res_trk_download_firmware(void); +u32 res_trk_get_core_type(void); +u32 res_trk_get_firmware_addr(struct res_trk_firmware_addr *firm_addr); +u32 res_trk_get_mem_type(void); + +#endif diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl.c b/drivers/video/msm/vidc/720p/ddl/vcd_ddl.c new file mode 100644 index 00000000000..d27b354eca6 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl.c @@ -0,0 +1,629 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd_ddl_utils.h" +#include "vcd_ddl_metadata.h" +#include "vcd_res_tracker_api.h" + +u32 ddl_device_init(struct ddl_init_config *ddl_init_config, + void *client_data) +{ + struct ddl_context *ddl_context; + u32 status = VCD_S_SUCCESS; + + if ((!ddl_init_config) || + (!ddl_init_config->ddl_callback) || + (!ddl_init_config->core_virtual_base_addr) + ) { + VIDC_LOGERR_STRING("ddl_dev_init:Bad_argument"); + return VCD_ERR_ILLEGAL_PARM; + } + + ddl_context = ddl_get_context(); + + if (DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dev_init:Multiple_init"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dev_init:Ddl_busy"); + return VCD_ERR_BUSY; + } + + DDL_MEMSET(ddl_context, 0, sizeof(struct ddl_context)); + + DDL_BUSY(ddl_context); + ddl_context->memtype = res_trk_get_mem_type(); + if (ddl_context->memtype == -1) { + VIDC_LOGERR_STRING("ddl_dev_init:Invalid Memtype"); + return VCD_ERR_ILLEGAL_PARM; + } + ddl_context->ddl_callback = ddl_init_config->ddl_callback; + ddl_context->interrupt_clr = ddl_init_config->interrupt_clr; + ddl_context->core_virtual_base_addr = + ddl_init_config->core_virtual_base_addr; + ddl_context->client_data = client_data; + + vidc_720p_set_device_virtual_base(ddl_context-> + core_virtual_base_addr); + + ddl_context->current_ddl = NULL; + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + + ddl_client_transact(DDL_INIT_CLIENTS, NULL); + + ddl_pmem_alloc(&ddl_context->context_buf_addr, + DDL_CONTEXT_MEMORY, DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (!ddl_context->context_buf_addr.virtual_base_addr) { + VIDC_LOGERR_STRING("ddl_dev_init:Context_alloc_fail"); + status = VCD_ERR_ALLOC_FAIL; + } + if (!status) { + ddl_pmem_alloc(&ddl_context->db_line_buffer, + DDL_DB_LINE_BUF_SIZE, + DDL_TILE_BUFFER_ALIGN_BYTES); + if (!ddl_context->db_line_buffer.virtual_base_addr) { + VIDC_LOGERR_STRING("ddl_dev_init:Line_buf_alloc_fail"); + status = VCD_ERR_ALLOC_FAIL; + } + } + + if (!status) { + ddl_pmem_alloc(&ddl_context->data_partition_tempbuf, + DDL_MPEG4_DATA_PARTITION_BUF_SIZE, + DDL_TILE_BUFFER_ALIGN_BYTES); + if (ddl_context->data_partition_tempbuf.virtual_base_addr \ + == NULL) { + VIDC_LOGERR_STRING + ("ddl_dev_init:Data_partition_buf_alloc_fail"); + status = VCD_ERR_ALLOC_FAIL; + } + } + + if (!status) { + + ddl_pmem_alloc(&ddl_context->metadata_shared_input, + DDL_METADATA_TOTAL_INPUTBUFSIZE, + DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (!ddl_context->metadata_shared_input.virtual_base_addr) { + VIDC_LOGERR_STRING + ("ddl_dev_init:metadata_shared_input_alloc_fail"); + status = VCD_ERR_ALLOC_FAIL; + } + } + + if (!status) { + ddl_pmem_alloc(&ddl_context->dbg_core_dump, \ + DDL_DBG_CORE_DUMP_SIZE, \ + DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (!ddl_context->dbg_core_dump.virtual_base_addr) { + VIDC_LOGERR_STRING + ("ddl_dev_init:dbg_core_dump_alloc_failed"); + status = VCD_ERR_ALLOC_FAIL; + } + ddl_context->enable_dbg_core_dump = 0; + } + + if (!status && !vcd_fw_init()) { + VIDC_LOGERR_STRING("ddl_dev_init:fw_init_failed"); + status = VCD_ERR_ALLOC_FAIL; + } + if (status) { + ddl_release_context_buffers(ddl_context); + DDL_IDLE(ddl_context); + return status; + } + + ddl_move_command_state(ddl_context, DDL_CMD_DMA_INIT); + + ddl_core_init(ddl_context); + + return status; +} + +u32 ddl_device_release(void *client_data) +{ + struct ddl_context *ddl_context; + + ddl_context = ddl_get_context(); + + if (DDL_IS_BUSY(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dev_rel:Ddl_busy"); + return VCD_ERR_BUSY; + } + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dev_rel:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + + if (!ddl_client_transact(DDL_ACTIVE_CLIENT, NULL)) { + VIDC_LOGERR_STRING("ddl_dev_rel:Client_present_err"); + return VCD_ERR_CLIENT_PRESENT; + } + DDL_BUSY(ddl_context); + + ddl_context->device_state = DDL_DEVICE_NOTINIT; + ddl_context->client_data = client_data; + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + vidc_720p_stop_fw(); + + VIDC_LOG_STRING("FW_ENDDONE"); + ddl_release_context_buffers(ddl_context); + + DDL_IDLE(ddl_context); + + return VCD_S_SUCCESS; +} + +u32 ddl_open(u32 **ddl_handle, u32 decoding) +{ + struct ddl_context *ddl_context; + struct ddl_client_context *ddl; + u32 status; + + if (!ddl_handle) { + VIDC_LOGERR_STRING("ddl_open:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + + ddl_context = ddl_get_context(); + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_open:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + + status = ddl_client_transact(DDL_GET_CLIENT, &ddl); + + if (status) { + VIDC_LOGERR_STRING("ddl_open:Client_trasac_failed"); + return status; + } + + ddl_move_client_state(ddl, DDL_CLIENT_OPEN); + + ddl->codec_data.hdr.decoding = decoding; + ddl->decoding = decoding; + + ddl_set_default_meta_data_hdr(ddl); + + ddl_set_initial_default_values(ddl); + + *ddl_handle = (u32 *) ddl; + return VCD_S_SUCCESS; +} + +u32 ddl_close(u32 **ddl_handle) +{ + struct ddl_context *ddl_context; + struct ddl_client_context **ddl = + (struct ddl_client_context **)ddl_handle; + + if (!ddl || !*ddl) { + VIDC_LOGERR_STRING("ddl_close:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + + ddl_context = ddl_get_context(); + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_close:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + + if (!DDLCLIENT_STATE_IS(*ddl, DDL_CLIENT_OPEN)) { + VIDC_LOGERR_STRING("ddl_close:Not_in_open_state"); + return VCD_ERR_ILLEGAL_OP; + } + + ddl_move_client_state(*ddl, DDL_CLIENT_INVALID); + if ((*ddl)->decoding) { + vcd_fw_transact(false, true, + (*ddl)->codec_data.decoder.codec.codec); + } else { + vcd_fw_transact(false, false, + (*ddl)->codec_data.encoder.codec.codec); + } + ddl_client_transact(DDL_FREE_CLIENT, ddl); + + return VCD_S_SUCCESS; +} + +u32 ddl_encode_start(u32 *ddl_handle, void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *)ddl_handle; + struct ddl_context *ddl_context; + struct ddl_encoder_data *encoder; + u32 dpb_size; + + ddl_context = ddl_get_context(); + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_enc_start:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + VIDC_LOGERR_STRING("ddl_enc_start:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || ddl->decoding) { + VIDC_LOGERR_STRING("ddl_enc_start:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + VIDC_LOGERR_STRING("ddl_enc_start:Not_opened"); + return VCD_ERR_ILLEGAL_OP; + } + + if (!ddl_encoder_ready_to_start(ddl)) { + VIDC_LOGERR_STRING("ddl_enc_start:Err_param_settings"); + return VCD_ERR_ILLEGAL_OP; + } + + encoder = &ddl->codec_data.encoder; + + dpb_size = ddl_get_yuv_buffer_size(&encoder->frame_size, + &encoder->re_con_buf_format, false, + encoder->codec.codec); + + dpb_size *= DDL_ENC_NUM_DPB_BUFFERS; + ddl_pmem_alloc(&encoder->enc_dpb_addr, + dpb_size, DDL_TILE_BUFFER_ALIGN_BYTES); + if (!encoder->enc_dpb_addr.virtual_base_addr) { + VIDC_LOGERR_STRING("ddl_enc_start:Dpb_alloc_failed"); + return VCD_ERR_ALLOC_FAIL; + } + + if ((encoder->codec.codec == VCD_CODEC_MPEG4 && + !encoder->short_header.short_header) || + encoder->codec.codec == VCD_CODEC_H264) { + ddl_pmem_alloc(&encoder->seq_header, + DDL_ENC_SEQHEADER_SIZE, + DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (!encoder->seq_header.virtual_base_addr) { + ddl_pmem_free(&encoder->enc_dpb_addr); + VIDC_LOGERR_STRING + ("ddl_enc_start:Seq_hdr_alloc_failed"); + return VCD_ERR_ALLOC_FAIL; + } + } else { + encoder->seq_header.buffer_size = 0; + encoder->seq_header.virtual_base_addr = 0; + } + + DDL_BUSY(ddl_context); + + ddl_context->current_ddl = ddl; + ddl_context->client_data = client_data; + ddl_channel_set(ddl); + return VCD_S_SUCCESS; +} + +u32 ddl_decode_start(u32 *ddl_handle, + struct vcd_sequence_hdr *header, void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *)ddl_handle; + struct ddl_context *ddl_context; + struct ddl_decoder_data *decoder; + + ddl_context = ddl_get_context(); + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dec_start:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dec_start:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || !ddl->decoding) { + VIDC_LOGERR_STRING("ddl_dec_start:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + VIDC_LOGERR_STRING("ddl_dec_start:Not_in_opened_state"); + return VCD_ERR_ILLEGAL_OP; + } + + if ((header) && + ((!header->sequence_header_len) || + (!header->sequence_header) + ) + ) { + VIDC_LOGERR_STRING("ddl_dec_start:Bad_param_seq_header"); + return VCD_ERR_ILLEGAL_PARM; + } + + if (!ddl_decoder_ready_to_start(ddl, header)) { + VIDC_LOGERR_STRING("ddl_dec_start:Err_param_settings"); + return VCD_ERR_ILLEGAL_OP; + } + + DDL_BUSY(ddl_context); + + decoder = &ddl->codec_data.decoder; + if (header) { + decoder->header_in_start = true; + decoder->decode_config = *header; + } else { + decoder->header_in_start = false; + decoder->decode_config.sequence_header_len = 0; + } + + if (decoder->codec.codec == VCD_CODEC_H264) { + ddl_pmem_alloc(&decoder->h264Vsp_temp_buffer, + DDL_DECODE_H264_VSPTEMP_BUFSIZE, + DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (!decoder->h264Vsp_temp_buffer.virtual_base_addr) { + DDL_IDLE(ddl_context); + VIDC_LOGERR_STRING + ("ddl_dec_start:H264Sps_alloc_failed"); + return VCD_ERR_ALLOC_FAIL; + } + } + + ddl_context->current_ddl = ddl; + ddl_context->client_data = client_data; + + ddl_channel_set(ddl); + return VCD_S_SUCCESS; +} + +u32 ddl_decode_frame(u32 *ddl_handle, + struct ddl_frame_data_tag *input_bits, void *client_data) +{ + u32 vcd_status = VCD_S_SUCCESS; + struct ddl_client_context *ddl = + (struct ddl_client_context *)ddl_handle; + struct ddl_context *ddl_context = ddl_get_context(); + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dec_frame:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dec_frame:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || !ddl->decoding) { + VIDC_LOGERR_STRING("ddl_dec_frame:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!input_bits || + ((!input_bits->vcd_frm.physical || + !input_bits->vcd_frm.data_len) && + (!(VCD_FRAME_FLAG_EOS & input_bits->vcd_frm.flags)) + ) + ) { + VIDC_LOGERR_STRING("ddl_dec_frame:Bad_input_param"); + return VCD_ERR_ILLEGAL_PARM; + } + + DDL_BUSY(ddl_context); + + ddl_context->current_ddl = ddl; + ddl_context->client_data = client_data; + + ddl->input_frame = *input_bits; + + if (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME)) { + ddl_decode_frame_run(ddl); + } else { + if (!ddl->codec_data.decoder.dp_buf.no_of_dec_pic_buf) { + VIDC_LOGERR_STRING("ddl_dec_frame:Dpbs_requied"); + vcd_status = VCD_ERR_ILLEGAL_OP; + } else if (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB)) { + vcd_status = ddl_decode_set_buffers(ddl); + } else + if (DDLCLIENT_STATE_IS + (ddl, DDL_CLIENT_WAIT_FOR_INITCODEC)) { + ddl->codec_data.decoder.decode_config. + sequence_header = + ddl->input_frame.vcd_frm.physical; + ddl->codec_data.decoder.decode_config. + sequence_header_len = + ddl->input_frame.vcd_frm.data_len; + ddl_decode_init_codec(ddl); + } else { + VIDC_LOGERR_STRING("Dec_frame:Wrong_state"); + vcd_status = VCD_ERR_ILLEGAL_OP; + } + if (vcd_status) + DDL_IDLE(ddl_context); + } + return vcd_status; +} + +u32 ddl_encode_frame(u32 *ddl_handle, + struct ddl_frame_data_tag *input_frame, + struct ddl_frame_data_tag *output_bit, void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *)ddl_handle; + struct ddl_context *ddl_context = ddl_get_context(); + + if (vidc_msg_timing) + ddl_set_core_start_time(__func__, ENC_OP_TIME); + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_enc_frame:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + VIDC_LOGERR_STRING("ddl_enc_frame:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || ddl->decoding) { + VIDC_LOGERR_STRING("ddl_enc_frame:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!input_frame || + !input_frame->vcd_frm.physical || + ddl->codec_data.encoder.input_buf_req.sz != + input_frame->vcd_frm.data_len) { + VIDC_LOGERR_STRING("ddl_enc_frame:Bad_input_params"); + return VCD_ERR_ILLEGAL_PARM; + } + if ((((u32) input_frame->vcd_frm.physical + + input_frame->vcd_frm.offset) & + (DDL_STREAMBUF_ALIGN_GUARD_BYTES) + ) + ) { + VIDC_LOGERR_STRING + ("ddl_enc_frame:Un_aligned_yuv_start_address"); + return VCD_ERR_ILLEGAL_PARM; + } + if (!output_bit || + !output_bit->vcd_frm.physical || + !output_bit->vcd_frm.alloc_len) { + VIDC_LOGERR_STRING("ddl_enc_frame:Bad_output_params"); + return VCD_ERR_ILLEGAL_PARM; + } + if ((ddl->codec_data.encoder.output_buf_req.sz + + output_bit->vcd_frm.offset) > + output_bit->vcd_frm.alloc_len) { + VIDC_LOGERR_STRING + ("ddl_enc_frame:offset_large, Exceeds_min_buf_size"); + } + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME)) { + VIDC_LOGERR_STRING("ddl_enc_frame:Wrong_state"); + return VCD_ERR_ILLEGAL_OP; + } + + DDL_BUSY(ddl_context); + + ddl_context->current_ddl = ddl; + ddl_context->client_data = client_data; + + ddl->input_frame = *input_frame; + ddl->output_frame = *output_bit; + + ddl_encode_frame_run(ddl); + return VCD_S_SUCCESS; +} + +u32 ddl_decode_end(u32 *ddl_handle, void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *)ddl_handle; + struct ddl_context *ddl_context; + + ddl_context = ddl_get_context(); + + if (vidc_msg_timing) { + ddl_reset_core_time_variables(DEC_OP_TIME); + ddl_reset_core_time_variables(DEC_IP_TIME); + } + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dec_end:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + VIDC_LOGERR_STRING("ddl_dec_end:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || !ddl->decoding) { + VIDC_LOGERR_STRING("ddl_dec_end:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODEC) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_FATAL_ERROR) + ) { + VIDC_LOGERR_STRING("ddl_dec_end:Wrong_state"); + return VCD_ERR_ILLEGAL_OP; + } + DDL_BUSY(ddl_context); + + ddl_context->current_ddl = ddl; + ddl_context->client_data = client_data; + + ddl_channel_end(ddl); + return VCD_S_SUCCESS; +} + +u32 ddl_encode_end(u32 *ddl_handle, void *client_data) +{ + struct ddl_client_context *ddl = + (struct ddl_client_context *)ddl_handle; + struct ddl_context *ddl_context; + + ddl_context = ddl_get_context(); + + if (vidc_msg_timing) + ddl_reset_core_time_variables(ENC_OP_TIME); + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_enc_end:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + if (DDL_IS_BUSY(ddl_context)) { + VIDC_LOGERR_STRING("ddl_enc_end:Ddl_busy"); + return VCD_ERR_BUSY; + } + if (!ddl || ddl->decoding) { + VIDC_LOGERR_STRING("ddl_enc_end:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODEC) && + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_FATAL_ERROR)) { + VIDC_LOGERR_STRING("ddl_enc_end:Wrong_state"); + return VCD_ERR_ILLEGAL_OP; + } + DDL_BUSY(ddl_context); + + ddl_context->current_ddl = ddl; + ddl_context->client_data = client_data; + + ddl_channel_end(ddl); + return VCD_S_SUCCESS; +} + +u32 ddl_reset_hw(u32 mode) +{ + struct ddl_context *ddl_context; + struct ddl_client_context *ddl; + int i_client_num; + + VIDC_LOG_STRING("ddl_reset_hw:called"); + ddl_context = ddl_get_context(); + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + DDL_BUSY(ddl_context); + + if (ddl_context->core_virtual_base_addr) + vidc_720p_do_sw_reset(); + + ddl_context->device_state = DDL_DEVICE_NOTINIT; + for (i_client_num = 0; i_client_num < VCD_MAX_NO_CLIENT; + ++i_client_num) { + ddl = ddl_context->ddl_clients[i_client_num]; + ddl_context->ddl_clients[i_client_num] = NULL; + if (ddl) { + ddl_release_client_internal_buffers(ddl); + ddl_client_transact(DDL_FREE_CLIENT, &ddl); + } + } + + ddl_release_context_buffers(ddl_context); + DDL_MEMSET(ddl_context, 0, sizeof(struct ddl_context)); + + return true; +} diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl.h b/drivers/video/msm/vidc/720p/ddl/vcd_ddl.h new file mode 100644 index 00000000000..157b556e8f7 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl.h @@ -0,0 +1,282 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_DDL_H_ +#define _VCD_DDL_H_ +#include "vcd_ddl_api.h" +#include "vcd_ddl_utils.h" +#include "vcd_ddl_firmware.h" +#include "vidc.h" + +#undef DDL_INLINE +#define DDL_INLINE + +#define DDL_BUSY_STATE 1 +#define DDL_IDLE_STATE 0 +#define DDL_ERROR_STATE 2 +#define DDL_IS_BUSY(ddl_context) \ + (((ddl_context)->ddl_busy != DDL_IDLE_STATE)) +#define DDL_BUSY(ddl_context) \ + ((ddl_context)->ddl_busy = DDL_BUSY_STATE) +#define DDL_IDLE(ddl_context) \ + ((ddl_context)->ddl_busy = DDL_IDLE_STATE) +#define DDL_ERROR(ddl_context) \ + ((ddl_context)->ddl_busy = DDL_ERROR_STATE) + +#define DDL_DEVICE_NOTINIT 0 +#define DDL_DEVICE_INITED 1 +#define DDL_DEVICE_HWFATAL 2 +#define DDL_IS_INITIALIZED(ddl_context) \ +(ddl_context->device_state == DDL_DEVICE_INITED) + +#define DDLCOMMAND_STATE_IS(ddl_context, command_state) \ +(command_state == (ddl_context)->cmd_state) + +#define DDLCLIENT_STATE_IS(ddl, current_state) \ +(current_state == (ddl)->client_state) + +#define DDL_DPB_OP_INIT 1 +#define DDL_DPB_OP_MARK_FREE 2 +#define DDL_DPB_OP_MARK_BUSY 3 +#define DDL_DPB_OP_SET_MASK 4 +#define DDL_DPB_OP_RETRIEVE 5 + +#define DDL_INIT_CLIENTS 0 +#define DDL_GET_CLIENT 1 +#define DDL_FREE_CLIENT 2 +#define DDL_ACTIVE_CLIENT 3 + +#define DDL_INVALID_CHANNEL_ID ((u32)~0) +#define DDL_INVALID_CODEC_TYPE ((u32)~0) + +#define DDL_ENC_REQ_IFRAME 0x01 +#define DDL_ENC_CHANGE_IPERIOD 0x02 +#define DDL_ENC_CHANGE_BITRATE 0x04 +#define DDL_ENC_CHANGE_FRAMERATE 0x08 +#define DDL_ENC_CHANGE_CIR 0x10 + +#define DDL_DEC_REQ_OUTPUT_FLUSH 0x1 + +struct ddl_buf_addr { + u32 *physical_base_addr; + u32 *virtual_base_addr; + u32 *align_physical_addr; + u32 *align_virtual_addr; + u32 buffer_size; +}; + +enum ddl_cmd_state { + DDL_CMD_INVALID = 0x0, + DDL_CMD_DMA_INIT = 0x1, + DDL_CMD_CPU_RESET = 0x2, + DDL_CMD_CHANNEL_SET = 0x3, + DDL_CMD_INIT_CODEC = 0x4, + DDL_CMD_HEADER_PARSE = 0x5, + DDL_CMD_DECODE_SET_DPB = 0x6, + DDL_CMD_DECODE_FRAME = 0x7, + DDL_CMD_ENCODE_FRAME = 0x8, + DDL_CMD_EOS = 0x9, + DDL_CMD_CHANNEL_END = 0xA, + DDL_CMD_32BIT = 0x7FFFFFFF +}; + +enum ddl_client_state { + DDL_CLIENT_INVALID = 0x0, + DDL_CLIENT_OPEN = 0x1, + DDL_CLIENT_WAIT_FOR_CHDONE = 0x2, + DDL_CLIENT_WAIT_FOR_INITCODEC = 0x3, + DDL_CLIENT_WAIT_FOR_INITCODECDONE = 0x4, + DDL_CLIENT_WAIT_FOR_DPB = 0x5, + DDL_CLIENT_WAIT_FOR_DPBDONE = 0x6, + DDL_CLIENT_WAIT_FOR_FRAME = 0x7, + DDL_CLIENT_WAIT_FOR_FRAME_DONE = 0x8, + DDL_CLIENT_WAIT_FOR_EOS_DONE = 0x9, + DDL_CLIENT_WAIT_FOR_CHEND = 0xA, + DDL_CLIENT_FATAL_ERROR = 0xB, + DDL_CLIENT_32BIT = 0x7FFFFFFF +}; + +struct ddl_mask { + u32 client_mask; + u32 hw_mask; +}; + +struct ddl_context; + +struct ddl_client_context; + +struct ddl_codec_data_hdr { + u32 decoding; +}; + +struct ddl_encoder_data { + struct ddl_codec_data_hdr hdr; + struct vcd_property_codec codec; + struct vcd_property_frame_size frame_size; + struct vcd_property_frame_rate frame_rate; + struct vcd_property_target_bitrate target_bit_rate; + struct vcd_property_profile profile; + struct vcd_property_level level; + struct vcd_property_rate_control rc; + struct vcd_property_multi_slice multi_slice; + u32 meta_data_enable_flag; + u32 suffix; + struct ddl_buf_addr meta_data_input; + u32 meta_data_offset; + struct vcd_property_short_header short_header; + struct vcd_property_vop_timing vop_timing; + u32 hdr_ext_control; + struct vcd_property_db_config db_control; + struct vcd_property_entropy_control entropy_control; + struct vcd_property_i_period i_period; + struct vcd_property_session_qp session_qp; + struct vcd_property_qp_range qp_range; + struct vcd_property_rc_level rc_level; + u32 r_cframe_skip; + u32 vb_vbuffer_size; + struct vcd_property_frame_level_rc_params frame_level_rc; + struct vcd_property_adaptive_rc_params adaptive_rc; + struct vcd_property_intra_refresh_mb_number intra_refresh; + struct vcd_property_buffer_format buf_format; + struct vcd_property_buffer_format re_con_buf_format; + u32 dynamic_prop_change; + u32 dynmic_prop_change_req; + u32 ext_enc_control_val; + struct vidc_720p_enc_frame_info enc_frame_info; + struct ddl_buf_addr enc_dpb_addr; + struct ddl_buf_addr seq_header; + struct vcd_buffer_requirement input_buf_req; + struct vcd_buffer_requirement output_buf_req; + struct vcd_buffer_requirement client_input_buf_req; + struct vcd_buffer_requirement client_output_buf_req; +}; + +struct ddl_decoder_data { + struct ddl_codec_data_hdr hdr; + struct vcd_property_codec codec; + struct vcd_property_buffer_format buf_format; + struct vcd_property_frame_size frame_size; + struct vcd_property_frame_size client_frame_size; + struct vcd_property_profile profile; + struct vcd_property_level level; + u32 progressive_only; + u32 output_order; + u32 meta_data_enable_flag; + u32 suffix; + struct ddl_buf_addr meta_data_input; + struct ddl_buf_addr ref_buffer; + u32 meta_data_offset; + struct vcd_property_post_filter post_filter; + struct vcd_sequence_hdr decode_config; + u32 header_in_start; + u32 min_dpb_num; + u32 y_cb_cr_size; + struct ddl_property_dec_pic_buffers dp_buf; + struct ddl_mask dpb_mask; + u32 dynamic_prop_change; + u32 dynmic_prop_change_req; + struct vidc_720p_dec_disp_info dec_disp_info; + struct ddl_buf_addr dpb_comv_buffer; + struct ddl_buf_addr h264Vsp_temp_buffer; + struct vcd_buffer_requirement actual_input_buf_req; + struct vcd_buffer_requirement min_input_buf_req; + struct vcd_buffer_requirement client_input_buf_req; + struct vcd_buffer_requirement actual_output_buf_req; + struct vcd_buffer_requirement min_output_buf_req; + struct vcd_buffer_requirement client_output_buf_req; +}; + +union ddl_codec_data { + struct ddl_codec_data_hdr hdr; + struct ddl_decoder_data decoder; + struct ddl_encoder_data encoder; +}; + +struct ddl_context { + int memtype; + u8 *core_virtual_base_addr; + void (*ddl_callback) (u32 event, u32 status, void *payload, size_t sz, + u32 *ddl_handle, void *const client_data); + void *client_data; + void (*interrupt_clr) (void); + enum ddl_cmd_state cmd_state; + struct ddl_client_context *current_ddl; + struct ddl_buf_addr context_buf_addr; + struct ddl_buf_addr db_line_buffer; + struct ddl_buf_addr data_partition_tempbuf; + struct ddl_buf_addr metadata_shared_input; + struct ddl_buf_addr dbg_core_dump; + u32 enable_dbg_core_dump; + struct ddl_client_context *ddl_clients[VCD_MAX_NO_CLIENT]; + u32 device_state; + u32 ddl_busy; + u32 intr_status; + u32 cmd_err_status; + u32 disp_pic_err_status; + u32 op_failed; +}; + +struct ddl_client_context { + struct ddl_context *ddl_context; + enum ddl_client_state client_state; + u32 decoding; + u32 channel_id; + struct ddl_frame_data_tag input_frame; + struct ddl_frame_data_tag output_frame; + union ddl_codec_data codec_data; +}; + +DDL_INLINE struct ddl_context *ddl_get_context(void); +DDL_INLINE void ddl_move_command_state(struct ddl_context *ddl_context, + enum ddl_cmd_state command_state); +DDL_INLINE void ddl_move_client_state(struct ddl_client_context *ddl, + enum ddl_client_state client_state); +void ddl_core_init(struct ddl_context *); +void ddl_core_start_cpu(struct ddl_context *); +void ddl_channel_set(struct ddl_client_context *); +void ddl_channel_end(struct ddl_client_context *); +void ddl_encode_init_codec(struct ddl_client_context *); +void ddl_decode_init_codec(struct ddl_client_context *); +void ddl_encode_frame_run(struct ddl_client_context *); +void ddl_decode_frame_run(struct ddl_client_context *); +void ddl_decode_eos_run(struct ddl_client_context *); +void ddl_release_context_buffers(struct ddl_context *); +void ddl_release_client_internal_buffers(struct ddl_client_context *ddl); +u32 ddl_decode_set_buffers(struct ddl_client_context *); +u32 ddl_decoder_dpb_transact(struct ddl_decoder_data *decoder, + struct ddl_frame_data_tag *in_out_frame, + u32 operation); +u32 ddl_client_transact(u32, struct ddl_client_context **); +void ddl_set_default_decoder_buffer_req + (struct ddl_decoder_data *decoder, u32 estimate); +void ddl_set_default_encoder_buffer_req + (struct ddl_encoder_data *encoder); +void ddl_set_default_dec_property(struct ddl_client_context *); +u32 ddl_encoder_ready_to_start(struct ddl_client_context *); +u32 ddl_decoder_ready_to_start(struct ddl_client_context *, + struct vcd_sequence_hdr *); +u32 ddl_get_yuv_buffer_size + (struct vcd_property_frame_size *frame_size, + struct vcd_property_buffer_format *buf_format, u32 inter_lace, + enum vcd_codec codec); +void ddl_calculate_stride(struct vcd_property_frame_size *frame_size, + u32 inter_lace, enum vcd_codec codec); +void ddl_encode_dynamic_property(struct ddl_client_context *ddl, + u32 enable); +void ddl_decode_dynamic_property(struct ddl_client_context *ddl, + u32 enable); +void ddl_set_initial_default_values(struct ddl_client_context *ddl); +u32 ddl_handle_core_errors(struct ddl_context *ddl_context); +void ddl_client_fatal_cb(struct ddl_context *ddl_context); +void ddl_hw_fatal_cb(struct ddl_context *ddl_context); +u32 ddl_hal_engine_reset(struct ddl_context *ddl_context); +#endif diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_api.h b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_api.h new file mode 100644 index 00000000000..53cc93e526e --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_api.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_DDL_API_H_ +#define _VCD_DDL_API_H_ +#include "vcd_ddl_internal_property.h" + +struct ddl_init_config { + int memtype; + u8 *core_virtual_base_addr; + void (*interrupt_clr) (void); + void (*ddl_callback) (u32 event, u32 status, void *payload, size_t sz, + u32 *ddl_handle, void *const client_data); +}; + +struct ddl_frame_data_tag { + struct vcd_frame_data vcd_frm; + u32 frm_trans_end; + u32 frm_delta; +}; + +u32 ddl_device_init(struct ddl_init_config *ddl_init_config, + void *client_data); +u32 ddl_device_release(void *client_data); +u32 ddl_open(u32 **ddl_handle, u32 decoding); +u32 ddl_close(u32 **ddl_handle); +u32 ddl_encode_start(u32 *ddl_handle, void *client_data); +u32 ddl_encode_frame(u32 *ddl_handle, + struct ddl_frame_data_tag *input_frame, + struct ddl_frame_data_tag *output_bit, void *client_data); +u32 ddl_encode_end(u32 *ddl_handle, void *client_data); +u32 ddl_decode_start(u32 *ddl_handle, struct vcd_sequence_hdr *header, + void *client_data); +u32 ddl_decode_frame(u32 *ddl_handle, + struct ddl_frame_data_tag *input_bits, void *client_data); +u32 ddl_decode_end(u32 *ddl_handle, void *client_data); +u32 ddl_set_property(u32 *ddl_handle, + struct vcd_property_hdr *property_hdr, void *property_value); +u32 ddl_get_property(u32 *ddl_handle, + struct vcd_property_hdr *property_hdr, void *property_value); +void ddl_read_and_clear_interrupt(void); +u32 ddl_process_core_response(void); +u32 ddl_reset_hw(u32 mode); +#endif diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_core.h b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_core.h new file mode 100644 index 00000000000..9fdb6687641 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_core.h @@ -0,0 +1,99 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_DDL_CORE_H_ +#define _VCD_DDL_CORE_H_ + +#define DDL_LINEAR_BUF_ALIGN_MASK 0xFFFFFFF8U +#define DDL_LINEAR_BUF_ALIGN_GUARD_BYTES 0x7 +#define DDL_LINEAR_BUFFER_ALIGN_BYTES 8 + +#define DDL_TILE_BUF_ALIGN_MASK 0xFFFFE000U +#define DDL_TILE_BUF_ALIGN_GUARD_BYTES 0x1FFF +#define DDL_TILE_BUFFER_ALIGN_BYTES 8192 + +#define DDL_MAX_FRAME_WIDTH (1280) +#define DDL_MAX_FRAME_HEIGHT (720) + +#define DDL_MAX_DP_FRAME_WIDTH 352 +#define DDL_MAX_DP_FRAME_HEIGHT 288 + +#define DDL_MAX_BIT_RATE (14*1000*1000) + +#define DDL_SW_RESET_SLEEP 10 + +#define VCD_MAX_NO_CLIENT 4 +#define VCD_FRAME_COMMAND_DEPTH 1 +#define VCD_GENERAL_COMMAND_DEPTH 1 +#define VCD_COMMAND_EXCLUSIVE true + +#define DDL_HW_TIMEOUT_IN_MS 1000 + +#define DDL_STREAMBUF_ALIGN_GUARD_BYTES 0x7 + +#define DDL_CONTEXT_MEMORY (1024 * 15 * (VCD_MAX_NO_CLIENT + 1)) +#define DDL_DB_LINE_BUF_SIZE \ +(((((DDL_MAX_FRAME_WIDTH * 4) - 1) / 256) + 1) * 8 * 1024) +#define DDL_MPEG4_DATA_PARTITION_BUF_SIZE (64 * 1024) +#define DDL_DECODE_H264_VSPTEMP_BUFSIZE 0x59c00 +#define DDL_ENC_NUM_DPB_BUFFERS 2 + +#define DDL_DBG_CORE_DUMP_SIZE (10 * 1024) + +#define DDL_BUFEND_PAD 256 +#define DDL_ENC_SEQHEADER_SIZE (256+DDL_BUFEND_PAD) +#define DDL_MAX_BUFFER_COUNT 32 + +#define DDL_MPEG_REFBUF_COUNT 2 + +#define DDL_MPEG_COMV_BUF_NO 2 +#define DDL_H263_COMV_BUF_NO 2 +#define DDL_COMV_BUFLINE_NO 128 +#define DDL_VC1_COMV_BUFLINE_NO 32 +#define DDL_MINIMUM_BYTE_PER_SLICE 1920 + +#define DDL_MAX_H264_QP 51 +#define DDL_MAX_MPEG4_QP 31 + +#define DDL_PADDING_HACK(addr) \ + (addr) = (u32)((((u32)(addr) + DDL_STREAMBUF_ALIGN_GUARD_BYTES) & \ + ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES)) + DDL_BUFEND_PAD) + +#define DDL_QCIF_MBS 99 +#define DDL_CIF_MBS 396 +#define DDL_QVGA_MBS 300 +#define DDL_VGA_MBS 1200 +#define DDL_WVGA_MBS 1500 +#define DDL_720P_MBS 3600 + +#define DDL_FRAMESIZE_DIV_FACTOR (0xF) + +#define DDL_NO_OF_MB(width, height) \ + (((width + 15) >> 4) * ((height + 15) >> 4)) + +#define DDL_ALLOW_ENC_FRAMESIZE(width, height) \ +((DDL_NO_OF_MB(width, height) <= DDL_720P_MBS) \ + && (((width) <= DDL_MAX_FRAME_WIDTH) && \ + ((height) <= DDL_MAX_FRAME_WIDTH)) \ + && ((width) >= 32 && (height) >= 32)) + +#define DDL_VALIDATE_ENC_FRAMESIZE(width, height) \ + (!((width) & DDL_FRAMESIZE_DIV_FACTOR) && \ + !((height) & DDL_FRAMESIZE_DIV_FACTOR)) + +#define DDL_TILE_ALIGN_WIDTH 128 +#define DDL_TILE_ALIGN_HEIGHT 32 +#define DDL_TILE_MULTIPLY_FACTOR 8192 +#define DDL_TILE_ALIGN(val, grid) \ + (((val) + (grid) - 1) / (grid) * (grid)) + +#endif diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_errors.c b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_errors.c new file mode 100644 index 00000000000..1b62553c1c2 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_errors.c @@ -0,0 +1,595 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd_ddl_utils.h" +#include "vcd_ddl.h" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +#define ERR(x...) printk(KERN_ERR x) + +#define INVALID_CHANNEL_NUMBER 1 +#define INVALID_COMMAND_ID 2 +#define CHANNEL_ALREADY_IN_USE 3 +#define CHANNEL_NOT_SET_BEFORE_CHANNEL_CLOSE 4 +#define CHANNEL_SET_ERROR_INIT_CODEC 5 +#define INIT_CODEC_ALREADY_CALLED 6 +#define CHANNEL_SET_ERROR_INIT_BUFFERS 7 +#define INIT_CODEC_ERROR_INIT_BUFFERS 8 +#define INIT_BUFFER_ALREADY_CALLED 9 +#define CHANNEL_SET_ERROR_FRAME_RUN 10 +#define INIT_CODEC_ERROR_FRAME_RUN 11 +#define INIT_BUFFERS_ERROR_FRAME_RUN 12 +#define CODEC_LIMIT_EXCEEDED 13 +#define FIRMWARE_SIZE_ZERO 14 +#define FIRMWARE_ADDRESS_EXT_ZERO 15 +#define CONTEXT_DMA_IN_ERROR 16 +#define CONTEXT_DMA_OUT_ERROR 17 +#define PROGRAM_DMA_ERROR 18 +#define CONTEXT_STORE_EXT_ADD_ZERO 19 +#define MEM_ALLOCATION_FAILED 20 + + +#define UNSUPPORTED_FEATURE_IN_PROFILE 27 +#define RESOLUTION_NOT_SUPPORTED 28 +#define HEADER_NOT_FOUND 52 +#define MB_NUM_INVALID 61 +#define FRAME_RATE_NOT_SUPPORTED 62 +#define INVALID_QP_VALUE 63 +#define INVALID_RC_REACTION_COEFFICIENT 64 +#define INVALID_CPB_SIZE_AT_GIVEN_LEVEL 65 + +#define ALLOC_DPB_SIZE_NOT_SUFFICIENT 71 +#define ALLOC_DB_SIZE_NOT_SUFFICIENT 72 +#define ALLOC_COMV_SIZE_NOT_SUFFICIENT 73 +#define NUM_BUF_OUT_OF_RANGE 74 +#define NULL_CONTEXT_POINTER 75 +#define NULL_COMAMND_CONTROL_COMM_POINTER 76 +#define NULL_METADATA_INPUT_POINTER 77 +#define NULL_DPB_POINTER 78 +#define NULL_DB_POINTER 79 +#define NULL_COMV_POINTER 80 + +#define DIVIDE_BY_ZERO 81 +#define BIT_STREAM_BUF_EXHAUST 82 +#define DMA_NOT_STOPPED 83 +#define DMA_TX_NOT_COMPLETE 84 + +#define MB_HEADER_NOT_DONE 85 +#define MB_COEFF_NOT_DONE 86 +#define CODEC_SLICE_NOT_DONE 87 +#define VME_NOT_READY 88 +#define VC1_BITPLANE_DECODE_ERR 89 + + +#define VSP_NOT_READY 90 +#define BUFFER_FULL_STATE 91 + +#define RESOLUTION_MISMATCH 112 +#define NV_QUANT_ERR 113 +#define SYNC_MARKER_ERR 114 +#define FEATURE_NOT_SUPPORTED 115 +#define MEM_CORRUPTION 116 +#define INVALID_REFERENCE_FRAME 117 +#define PICTURE_CODING_TYPE_ERR 118 +#define MV_RANGE_ERR 119 +#define PICTURE_STRUCTURE_ERR 120 +#define SLICE_ADDR_INVALID 121 +#define NON_PAIRED_FIELD_NOT_SUPPORTED 122 +#define NON_FRAME_DATA_RECEIVED 123 +#define INCOMPLETE_FRAME 124 +#define NO_BUFFER_RELEASED_FROM_HOST 125 +#define PICTURE_MANAGEMENT_ERROR 128 +#define INVALID_MMCO 129 +#define INVALID_PIC_REORDERING 130 +#define INVALID_POC_TYPE 131 +#define ACTIVE_SPS_NOT_PRESENT 132 +#define ACTIVE_PPS_NOT_PRESENT 133 +#define INVALID_SPS_ID 134 +#define INVALID_PPS_ID 135 + + +#define METADATA_NO_SPACE_QP 151 +#define METADATA_NO_SAPCE_CONCEAL_MB 152 +#define METADATA_NO_SPACE_VC1_PARAM 153 +#define METADATA_NO_SPACE_SEI 154 +#define METADATA_NO_SPACE_VUI 155 +#define METADATA_NO_SPACE_EXTRA 156 +#define METADATA_NO_SPACE_DATA_NONE 157 +#define FRAME_RATE_UNKNOWN 158 +#define ASPECT_RATIO_UNKOWN 159 +#define COLOR_PRIMARIES_UNKNOWN 160 +#define TRANSFER_CHAR_UNKWON 161 +#define MATRIX_COEFF_UNKNOWN 162 +#define NON_SEQ_SLICE_ADDR 163 +#define BROKEN_LINK 164 +#define FRAME_CONCEALED 165 +#define PROFILE_UNKOWN 166 +#define LEVEL_UNKOWN 167 +#define BIT_RATE_NOT_SUPPORTED 168 +#define COLOR_DIFF_FORMAT_NOT_SUPPORTED 169 +#define NULL_EXTRA_METADATA_POINTER 170 +#define SYNC_POINT_NOT_RECEIVED_STARTED_DECODING 171 +#define NULL_FW_DEBUG_INFO_POINTER 172 +#define ALLOC_DEBUG_INFO_SIZE_INSUFFICIENT 173 +#define MAX_STAGE_COUNTER_EXCEEDED 174 + +#define METADATA_NO_SPACE_MB_INFO 180 +#define METADATA_NO_SPACE_SLICE_SIZE 181 +#define RESOLUTION_WARNING 182 + +static void ddl_handle_npf_decoding_error( + struct ddl_context *ddl_context); + +static u32 ddl_handle_seqhdr_fail_error( + struct ddl_context *ddl_context); + +void ddl_hw_fatal_cb(struct ddl_context *ddl_context) +{ + /* Invalidate the command state */ + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + ddl_context->device_state = DDL_DEVICE_HWFATAL; + + /* callback to the client to indicate hw fatal error */ + ddl_context->ddl_callback(VCD_EVT_IND_HWERRFATAL, + VCD_ERR_HW_FATAL, NULL, 0, + (void *)ddl_context->current_ddl, + ddl_context->client_data); + + DDL_IDLE(ddl_context); +} + +static u32 ddl_handle_hw_fatal_errors(struct ddl_context + *ddl_context) +{ + u32 status = false; + + switch (ddl_context->cmd_err_status) { + + case INVALID_CHANNEL_NUMBER: + case INVALID_COMMAND_ID: + case CHANNEL_ALREADY_IN_USE: + case CHANNEL_NOT_SET_BEFORE_CHANNEL_CLOSE: + case CHANNEL_SET_ERROR_INIT_CODEC: + case INIT_CODEC_ALREADY_CALLED: + case CHANNEL_SET_ERROR_INIT_BUFFERS: + case INIT_CODEC_ERROR_INIT_BUFFERS: + case INIT_BUFFER_ALREADY_CALLED: + case CHANNEL_SET_ERROR_FRAME_RUN: + case INIT_CODEC_ERROR_FRAME_RUN: + case INIT_BUFFERS_ERROR_FRAME_RUN: + case CODEC_LIMIT_EXCEEDED: + case FIRMWARE_SIZE_ZERO: + case FIRMWARE_ADDRESS_EXT_ZERO: + + case CONTEXT_DMA_IN_ERROR: + case CONTEXT_DMA_OUT_ERROR: + case PROGRAM_DMA_ERROR: + case CONTEXT_STORE_EXT_ADD_ZERO: + case MEM_ALLOCATION_FAILED: + + case DIVIDE_BY_ZERO: + case DMA_NOT_STOPPED: + case DMA_TX_NOT_COMPLETE: + + case VSP_NOT_READY: + case BUFFER_FULL_STATE: + case NULL_DB_POINTER: + ERR("HW FATAL ERROR"); + ddl_hw_fatal_cb(ddl_context); + status = true; + break; + } + return status; +} + +void ddl_client_fatal_cb(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl = + ddl_context->current_ddl; + + if (ddl_context->cmd_state == DDL_CMD_DECODE_FRAME) + ddl_decode_dynamic_property(ddl, false); + else if (ddl_context->cmd_state == DDL_CMD_ENCODE_FRAME) + ddl_encode_dynamic_property(ddl, false); + + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + + ddl_move_client_state(ddl, DDL_CLIENT_FATAL_ERROR); + + ddl_context->ddl_callback + ( + VCD_EVT_IND_HWERRFATAL, + VCD_ERR_CLIENT_FATAL, + NULL, + 0, + (void *)ddl, + ddl_context->client_data + ); + + DDL_IDLE(ddl_context); +} + +static u32 ddl_handle_client_fatal_errors(struct ddl_context + *ddl_context) +{ + u32 status = false; + + switch (ddl_context->cmd_err_status) { + case MB_NUM_INVALID: + case FRAME_RATE_NOT_SUPPORTED: + case INVALID_QP_VALUE: + case INVALID_RC_REACTION_COEFFICIENT: + case INVALID_CPB_SIZE_AT_GIVEN_LEVEL: + + case ALLOC_DPB_SIZE_NOT_SUFFICIENT: + case ALLOC_DB_SIZE_NOT_SUFFICIENT: + case ALLOC_COMV_SIZE_NOT_SUFFICIENT: + case NUM_BUF_OUT_OF_RANGE: + case NULL_CONTEXT_POINTER: + case NULL_COMAMND_CONTROL_COMM_POINTER: + case NULL_METADATA_INPUT_POINTER: + case NULL_DPB_POINTER: + case NULL_COMV_POINTER: + { + status = true; + break; + } + } + + if (!status) + ERR("UNKNOWN-OP-FAILED"); + + ddl_client_fatal_cb(ddl_context); + + return true; +} + +static void ddl_input_failed_cb(struct ddl_context *ddl_context, + u32 vcd_event, u32 vcd_status) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + + if (ddl->decoding) + ddl_decode_dynamic_property(ddl, false); + else + ddl_encode_dynamic_property(ddl, false); + + ddl_context->ddl_callback(vcd_event, + vcd_status, &ddl->input_frame, + sizeof(struct ddl_frame_data_tag), + (void *)ddl, ddl_context->client_data); + + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_FRAME); +} + +static u32 ddl_handle_core_recoverable_errors(struct ddl_context \ + *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + u32 vcd_status = VCD_S_SUCCESS; + u32 vcd_event = VCD_EVT_RESP_INPUT_DONE; + u32 eos = false, pending_display = 0, release_mask = 0; + + if (ddl->decoding) + if (ddl_handle_seqhdr_fail_error(ddl_context)) + return true; + + if (ddl_context->cmd_state != DDL_CMD_DECODE_FRAME && + ddl_context->cmd_state != DDL_CMD_ENCODE_FRAME) { + return false; + } + switch (ddl_context->cmd_err_status) { + case NON_PAIRED_FIELD_NOT_SUPPORTED: + { + ddl_handle_npf_decoding_error(ddl_context); + return true; + } + case NO_BUFFER_RELEASED_FROM_HOST: + { + /* lets check sanity of this error */ + release_mask = + ddl->codec_data.decoder.dpb_mask.hw_mask; + while (release_mask > 0) { + if ((release_mask & 0x1)) + pending_display += 1; + release_mask >>= 1; + } + + if (pending_display >= + ddl->codec_data.decoder.min_dpb_num) { + DBG("FWISSUE-REQBUF!!"); + /* callback to client for client fatal error */ + ddl_client_fatal_cb(ddl_context); + return true ; + } + vcd_event = VCD_EVT_RESP_OUTPUT_REQ; + break; + } + case BIT_STREAM_BUF_EXHAUST: + case MB_HEADER_NOT_DONE: + case MB_COEFF_NOT_DONE: + case CODEC_SLICE_NOT_DONE: + case VME_NOT_READY: + case VC1_BITPLANE_DECODE_ERR: + { + u32 reset_core; + /* need to reset the internal core hw engine */ + reset_core = ddl_hal_engine_reset(ddl_context); + if (!reset_core) + return true; + /* fall through to process bitstream error handling */ + } + case RESOLUTION_MISMATCH: + case NV_QUANT_ERR: + case SYNC_MARKER_ERR: + case FEATURE_NOT_SUPPORTED: + case MEM_CORRUPTION: + case INVALID_REFERENCE_FRAME: + case PICTURE_CODING_TYPE_ERR: + case MV_RANGE_ERR: + case PICTURE_STRUCTURE_ERR: + case SLICE_ADDR_INVALID: + case NON_FRAME_DATA_RECEIVED: + case INCOMPLETE_FRAME: + case PICTURE_MANAGEMENT_ERROR: + case INVALID_MMCO: + case INVALID_PIC_REORDERING: + case INVALID_POC_TYPE: + case ACTIVE_SPS_NOT_PRESENT: + case ACTIVE_PPS_NOT_PRESENT: + { + vcd_status = VCD_ERR_BITSTREAM_ERR; + break; + } + } + + if (!vcd_status && vcd_event == VCD_EVT_RESP_INPUT_DONE) + return false; + + ddl->input_frame.frm_trans_end = true; + + eos = ((vcd_event == VCD_EVT_RESP_INPUT_DONE) && + ((VCD_FRAME_FLAG_EOS & ddl->input_frame. + vcd_frm.flags))); + + if ((ddl->decoding && eos) || + (!ddl->decoding)) + ddl->input_frame.frm_trans_end = false; + + if (vcd_event == VCD_EVT_RESP_INPUT_DONE && + ddl->decoding && + !ddl->codec_data.decoder.header_in_start && + !ddl->codec_data.decoder.dec_disp_info.img_size_x && + !ddl->codec_data.decoder.dec_disp_info.img_size_y + ) { + /* this is first frame seq. header only case */ + vcd_status = VCD_S_SUCCESS; + ddl->input_frame.vcd_frm.flags |= + VCD_FRAME_FLAG_CODECCONFIG; + ddl->input_frame.frm_trans_end = !eos; + /* put just some non - zero value */ + ddl->codec_data.decoder.dec_disp_info.img_size_x = 0xff; + } + /* inform client about input failed */ + ddl_input_failed_cb(ddl_context, vcd_event, vcd_status); + + /* for Encoder case, we need to send output done also */ + if (!ddl->decoding) { + /* transaction is complete after this callback */ + ddl->output_frame.frm_trans_end = !eos; + /* error case: NO data present */ + ddl->output_frame.vcd_frm.data_len = 0; + /* call back to client for output frame done */ + ddl_context->ddl_callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_ERR_FAIL, &(ddl->output_frame), + sizeof(struct ddl_frame_data_tag), + (void *)ddl, ddl_context->client_data); + + if (eos) { + DBG("ENC-EOS_DONE"); + /* send client EOS DONE callback */ + ddl_context->ddl_callback(VCD_EVT_RESP_EOS_DONE, + VCD_S_SUCCESS, NULL, 0, (void *)ddl, + ddl_context->client_data); + } + } + + /* if it is decoder EOS case */ + if (ddl->decoding && eos) + ddl_decode_eos_run(ddl); + else + DDL_IDLE(ddl_context); + + return true; +} + +static u32 ddl_handle_core_warnings(u32 err_status) +{ + u32 status = false; + + switch (err_status) { + case FRAME_RATE_UNKNOWN: + case ASPECT_RATIO_UNKOWN: + case COLOR_PRIMARIES_UNKNOWN: + case TRANSFER_CHAR_UNKWON: + case MATRIX_COEFF_UNKNOWN: + case NON_SEQ_SLICE_ADDR: + case BROKEN_LINK: + case FRAME_CONCEALED: + case PROFILE_UNKOWN: + case LEVEL_UNKOWN: + case BIT_RATE_NOT_SUPPORTED: + case COLOR_DIFF_FORMAT_NOT_SUPPORTED: + case NULL_EXTRA_METADATA_POINTER: + case SYNC_POINT_NOT_RECEIVED_STARTED_DECODING: + + case NULL_FW_DEBUG_INFO_POINTER: + case ALLOC_DEBUG_INFO_SIZE_INSUFFICIENT: + case MAX_STAGE_COUNTER_EXCEEDED: + + case METADATA_NO_SPACE_MB_INFO: + case METADATA_NO_SPACE_SLICE_SIZE: + case RESOLUTION_WARNING: + + /* decoder warnings */ + case METADATA_NO_SPACE_QP: + case METADATA_NO_SAPCE_CONCEAL_MB: + case METADATA_NO_SPACE_VC1_PARAM: + case METADATA_NO_SPACE_SEI: + case METADATA_NO_SPACE_VUI: + case METADATA_NO_SPACE_EXTRA: + case METADATA_NO_SPACE_DATA_NONE: + { + status = true; + DBG("CMD-WARNING-IGNORED!!"); + break; + } + } + return status; +} + +u32 ddl_handle_core_errors(struct ddl_context *ddl_context) +{ + u32 status = false; + + if (!ddl_context->cmd_err_status && + !ddl_context->disp_pic_err_status && + !ddl_context->op_failed) + return false; + + if (ddl_context->cmd_state == DDL_CMD_INVALID) { + DBG("SPURIOUS_INTERRUPT_ERROR"); + return true; + } + + if (!ddl_context->op_failed) { + u32 disp_status; + status = ddl_handle_core_warnings(ddl_context-> + cmd_err_status); + disp_status = ddl_handle_core_warnings( + ddl_context->disp_pic_err_status); + if (!status && !disp_status) + DBG("ddl_warning:Unknown"); + + return false; + } + + ERR("\n %s(): OPFAILED!!", __func__); + ERR("\n CMD_ERROR_STATUS = %u, DISP_ERR_STATUS = %u", + ddl_context->cmd_err_status, + ddl_context->disp_pic_err_status); + + status = ddl_handle_hw_fatal_errors(ddl_context); + + if (!status) + status = ddl_handle_core_recoverable_errors(ddl_context); + + if (!status) + status = ddl_handle_client_fatal_errors(ddl_context); + + return status; +} + +void ddl_handle_npf_decoding_error(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + if (!ddl->decoding) { + ERR("FWISSUE-ENC-NPF!!!"); + ddl_client_fatal_cb(ddl_context); + return; + } + vidc_720p_decode_display_info(&decoder->dec_disp_info); + ddl_decode_dynamic_property(ddl, false); + ddl->output_frame.vcd_frm.ip_frm_tag = + decoder->dec_disp_info.tag_top; + ddl->output_frame.vcd_frm.physical = NULL; + ddl->output_frame.frm_trans_end = false; + ddl->ddl_context->ddl_callback( + VCD_EVT_RESP_OUTPUT_DONE, + VCD_ERR_INTRLCD_FIELD_DROP, + &ddl->output_frame, + sizeof(struct ddl_frame_data_tag), + (void *)ddl, + ddl->ddl_context->client_data); + ddl_decode_frame_run(ddl); +} + +u32 ddl_handle_seqhdr_fail_error(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + u32 status = false; + if (ddl_context->cmd_state == DDL_CMD_HEADER_PARSE && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODECDONE)) { + switch (ddl_context->cmd_err_status) { + case UNSUPPORTED_FEATURE_IN_PROFILE: + case HEADER_NOT_FOUND: + case INVALID_SPS_ID: + case INVALID_PPS_ID: + case RESOLUTION_NOT_SUPPORTED: + ERR("SEQ-HDR-FAILED!!!"); + if ((ddl_context->cmd_err_status == + RESOLUTION_NOT_SUPPORTED) && + (decoder->codec.codec == VCD_CODEC_H264 || + decoder->codec.codec == VCD_CODEC_H263 || + decoder->codec.codec == VCD_CODEC_MPEG4 || + decoder->codec.codec == VCD_CODEC_VC1_RCV || + decoder->codec.codec == VCD_CODEC_VC1)) { + ddl_client_fatal_cb(ddl_context); + status = true; + break; + } + if (decoder->header_in_start) { + decoder->header_in_start = false; + ddl_context->ddl_callback(VCD_EVT_RESP_START, + VCD_ERR_SEQHDR_PARSE_FAIL, + NULL, 0, (void *)ddl, + ddl_context->client_data); + } else { + if (ddl->input_frame.vcd_frm.flags & + VCD_FRAME_FLAG_EOS) + ddl->input_frame.frm_trans_end = false; + else + ddl->input_frame.frm_trans_end = true; + ddl_decode_dynamic_property(ddl, false); + ddl_context->ddl_callback( + VCD_EVT_RESP_INPUT_DONE, + VCD_ERR_SEQHDR_PARSE_FAIL, + &ddl->input_frame, + sizeof(struct ddl_frame_data_tag), + (void *)ddl, ddl_context->client_data); + if (ddl->input_frame.vcd_frm.flags & + VCD_FRAME_FLAG_EOS) + ddl_context->ddl_callback( + VCD_EVT_RESP_EOS_DONE, + VCD_S_SUCCESS, NULL, + 0, (void *)ddl, + ddl_context->client_data); + } + ddl_move_client_state(ddl, + DDL_CLIENT_WAIT_FOR_INITCODEC); + DDL_IDLE(ddl_context); + status = true; + } + } + return status; +} diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.c b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.c new file mode 100644 index 00000000000..25aa6bc1566 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.c @@ -0,0 +1,352 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd_ddl_firmware.h" +#include "vcd_ddl_utils.h" + +#define VCDFW_TOTALNUM_IMAGE 7 +#define VCDFW_MAX_NO_IMAGE 2 + +struct vcd_firmware { + u32 active_fw_img[VCDFW_TOTALNUM_IMAGE]; + struct ddl_buf_addr boot_code; + + struct ddl_buf_addr enc_mpeg4; + struct ddl_buf_addr encH264; + + struct ddl_buf_addr dec_mpeg4; + struct ddl_buf_addr decH264; + struct ddl_buf_addr decH263; + struct ddl_buf_addr dec_mpeg2; + struct ddl_buf_addr dec_vc1; +}; + +static struct vcd_firmware vcd_firmware; + + +static void vcd_fw_change_endian(unsigned char *fw, u32 fw_size) +{ + u32 i = 0; + unsigned char temp; + for (i = 0; i < fw_size; i = i + 4) { + temp = fw[i]; + fw[i] = fw[i + 3]; + fw[i + 3] = temp; + + temp = fw[i + 1]; + fw[i + 1] = fw[i + 2]; + fw[i + 2] = temp; + } + return; +} + +static u32 vcd_fw_prepare(struct ddl_buf_addr *fw_details, + const unsigned char fw_array[], + const unsigned int fw_array_size, u32 change_endian) +{ + u32 *buffer; + + ddl_pmem_alloc(fw_details, fw_array_size, + DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (!fw_details->virtual_base_addr) + return false; + + fw_details->buffer_size = fw_array_size / 4; + + buffer = fw_details->align_virtual_addr; + + memcpy(buffer, fw_array, fw_array_size); + if (change_endian) + vcd_fw_change_endian((unsigned char *)buffer, fw_array_size); + return true; +} + +u32 vcd_fw_init(void) +{ + u32 status = false; + + status = vcd_fw_prepare(&vcd_firmware.boot_code, + vidc_command_control_fw, + vidc_command_control_fw_size, false); + + if (status) { + status = vcd_fw_prepare(&vcd_firmware.dec_mpeg4, + vidc_mpg4_dec_fw, + vidc_mpg4_dec_fw_size, true); + } + + if (status) { + status = vcd_fw_prepare(&vcd_firmware.decH264, + vidc_h264_dec_fw, + vidc_h264_dec_fw_size, true); + } + + if (status) { + status = vcd_fw_prepare(&vcd_firmware.decH263, + vidc_h263_dec_fw, + vidc_h263_dec_fw_size, true); + } + + if (status) { + status = vcd_fw_prepare(&vcd_firmware.enc_mpeg4, + vidc_mpg4_enc_fw, + vidc_mpg4_enc_fw_size, true); + } + + if (status) { + status = vcd_fw_prepare(&vcd_firmware.encH264, + vidc_h264_enc_fw, + vidc_h264_enc_fw_size, true); + } + + if (status) { + status = vcd_fw_prepare(&vcd_firmware.dec_vc1, + vidc_vc1_dec_fw, + vidc_vc1_dec_fw_size, true); + } + return status; +} + + +static u32 get_dec_fw_image(struct vcd_fw_details *fw_details) +{ + u32 status = true; + switch (fw_details->codec) { + case VCD_CODEC_DIVX_4: + case VCD_CODEC_DIVX_5: + case VCD_CODEC_DIVX_6: + case VCD_CODEC_XVID: + case VCD_CODEC_MPEG4: + { + fw_details->fw_buffer_addr = + vcd_firmware.dec_mpeg4.align_physical_addr; + fw_details->fw_size = + vcd_firmware.dec_mpeg4.buffer_size; + break; + } + case VCD_CODEC_H264: + { + fw_details->fw_buffer_addr = + vcd_firmware.decH264.align_physical_addr; + fw_details->fw_size = + vcd_firmware.decH264.buffer_size; + break; + } + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + { + fw_details->fw_buffer_addr = + vcd_firmware.dec_vc1.align_physical_addr; + fw_details->fw_size = + vcd_firmware.dec_vc1.buffer_size; + break; + } + case VCD_CODEC_MPEG2: + { + fw_details->fw_buffer_addr = + vcd_firmware.dec_mpeg2.align_physical_addr; + fw_details->fw_size = + vcd_firmware.dec_mpeg2.buffer_size; + break; + } + case VCD_CODEC_H263: + { + fw_details->fw_buffer_addr = + vcd_firmware.decH263.align_physical_addr; + fw_details->fw_size = + vcd_firmware.decH263.buffer_size; + break; + } + default: + { + status = false; + break; + } + } + return status; +} + +static u32 get_enc_fw_image(struct vcd_fw_details *fw_details) +{ + u32 status = true; + switch (fw_details->codec) { + case VCD_CODEC_H263: + case VCD_CODEC_MPEG4: + { + fw_details->fw_buffer_addr = + vcd_firmware.enc_mpeg4.align_physical_addr; + fw_details->fw_size = + vcd_firmware.enc_mpeg4.buffer_size; + break; + } + case VCD_CODEC_H264: + { + fw_details->fw_buffer_addr = + vcd_firmware.encH264.align_physical_addr; + fw_details->fw_size = + vcd_firmware.encH264.buffer_size; + break; + } + default: + { + status = false; + break; + } + } + return status; +} + +u32 vcd_get_fw_property(u32 prop_id, void *prop_details) +{ + u32 status = true; + struct vcd_fw_details *fw_details; + switch (prop_id) { + case VCD_FW_ENDIAN: + { + *(u32 *) prop_details = VCD_FW_BIG_ENDIAN; + break; + } + case VCD_FW_BOOTCODE: + { + fw_details = + (struct vcd_fw_details *)prop_details; + fw_details->fw_buffer_addr = + vcd_firmware.boot_code.align_physical_addr; + fw_details->fw_size = + vcd_firmware.boot_code.buffer_size; + break; + } + case VCD_FW_DECODE: + { + fw_details = + (struct vcd_fw_details *)prop_details; + status = get_dec_fw_image(fw_details); + break; + } + case VCD_FW_ENCODE: + { + fw_details = + (struct vcd_fw_details *)prop_details; + status = get_enc_fw_image(fw_details); + break; + } + default: + { + status = false; + break; + } + } + return status; +} + +u32 vcd_fw_transact(u32 add, u32 decoding, enum vcd_codec codec) +{ + u32 status = true; + u32 index = 0, active_fw = 0, loop_count; + + if (decoding) { + switch (codec) { + case VCD_CODEC_DIVX_4: + case VCD_CODEC_DIVX_5: + case VCD_CODEC_DIVX_6: + case VCD_CODEC_XVID: + case VCD_CODEC_MPEG4: + { + index = 0; + break; + } + case VCD_CODEC_H264: + { + index = 1; + break; + } + case VCD_CODEC_H263: + { + index = 2; + break; + } + case VCD_CODEC_MPEG2: + { + index = 3; + break; + } + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + { + index = 4; + break; + } + default: + { + status = false; + break; + } + } + } else { + switch (codec) { + case VCD_CODEC_H263: + case VCD_CODEC_MPEG4: + { + index = 5; + break; + } + case VCD_CODEC_H264: + { + index = 6; + break; + } + default: + { + status = false; + break; + } + } + } + + if (!status) + return status; + + if (!add && + vcd_firmware.active_fw_img[index] + ) { + --vcd_firmware.active_fw_img[index]; + return status; + } + + for (loop_count = 0; loop_count < VCDFW_TOTALNUM_IMAGE; + ++loop_count) { + if (vcd_firmware.active_fw_img[loop_count]) + ++active_fw; + } + + if (active_fw < VCDFW_MAX_NO_IMAGE || + vcd_firmware.active_fw_img[index] > 0) { + ++vcd_firmware.active_fw_img[index]; + } else { + status = false; + } + return status; +} + +void vcd_fw_release(void) +{ + ddl_pmem_free(&vcd_firmware.boot_code); + ddl_pmem_free(&vcd_firmware.enc_mpeg4); + ddl_pmem_free(&vcd_firmware.encH264); + ddl_pmem_free(&vcd_firmware.dec_mpeg4); + ddl_pmem_free(&vcd_firmware.decH264); + ddl_pmem_free(&vcd_firmware.decH263); + ddl_pmem_free(&vcd_firmware.dec_mpeg2); + ddl_pmem_free(&vcd_firmware.dec_vc1); +} diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.h b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.h new file mode 100644 index 00000000000..7952dfb4b88 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_firmware.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_DDL_FIRMWARE_H_ +#define _VCD_DDL_FIRMWARE_H_ +#include "vcd_property.h" + +#define VCD_FW_BIG_ENDIAN 0x0 +#define VCD_FW_LITTLE_ENDIAN 0x1 + +struct vcd_fw_details { + enum vcd_codec codec; + u32 *fw_buffer_addr; + u32 fw_size; +}; + +#define VCD_FW_PROP_BASE 0x0 + +#define VCD_FW_ENDIAN (VCD_FW_PROP_BASE + 0x1) +#define VCD_FW_BOOTCODE (VCD_FW_PROP_BASE + 0x2) +#define VCD_FW_DECODE (VCD_FW_PROP_BASE + 0x3) +#define VCD_FW_ENCODE (VCD_FW_PROP_BASE + 0x4) + +extern unsigned char *vidc_command_control_fw; +extern u32 vidc_command_control_fw_size; +extern unsigned char *vidc_mpg4_dec_fw; +extern u32 vidc_mpg4_dec_fw_size; +extern unsigned char *vidc_h263_dec_fw; +extern u32 vidc_h263_dec_fw_size; +extern unsigned char *vidc_h264_dec_fw; +extern u32 vidc_h264_dec_fw_size; +extern unsigned char *vidc_mpg4_enc_fw; +extern u32 vidc_mpg4_enc_fw_size; +extern unsigned char *vidc_h264_enc_fw; +extern u32 vidc_h264_enc_fw_size; +extern unsigned char *vidc_vc1_dec_fw; +extern u32 vidc_vc1_dec_fw_size; + +u32 vcd_fw_init(void); +u32 vcd_get_fw_property(u32 prop_id, void *prop_details); +u32 vcd_fw_transact(u32 add, u32 decoding, enum vcd_codec codec); +void vcd_fw_release(void); + +#endif diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_hal.c b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_hal.c new file mode 100644 index 00000000000..a81dd84c49a --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_hal.c @@ -0,0 +1,944 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" + +#include "vcd_ddl_utils.h" +#include "vcd_ddl_metadata.h" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +void ddl_core_init(struct ddl_context *ddl_context) +{ + char *psz_version; + struct vcd_fw_details fw_details; + u32 fw_endianness; + enum vidc_720p_endian dma_endian; + u32 interrupt_off; + enum vidc_720p_interrupt_level_selection interrupt_sel; + u32 intr_mask = 0x0; + + vcd_get_fw_property(VCD_FW_BOOTCODE, &fw_details); + vcd_get_fw_property(VCD_FW_ENDIAN, &fw_endianness); + if (fw_endianness == VCD_FW_BIG_ENDIAN) + dma_endian = VIDC_720P_BIG_ENDIAN; + else + dma_endian = VIDC_720P_LITTLE_ENDIAN; + + interrupt_off = false; + interrupt_sel = VIDC_720P_INTERRUPT_LEVEL_SEL; + + intr_mask |= VIDC_720P_INTR_BUFFER_FULL; + intr_mask |= VIDC_720P_INTR_FW_DONE; + intr_mask |= VIDC_720P_INTR_DMA_DONE; + intr_mask |= VIDC_720P_INTR_FRAME_DONE; + + vidc_720p_do_sw_reset(); + + vidc_720p_init(&psz_version, + fw_details.fw_size, + fw_details.fw_buffer_addr, + dma_endian, + interrupt_off, interrupt_sel, intr_mask); + return; +} + +void ddl_core_start_cpu(struct ddl_context *ddl_context) +{ + u32 fw_endianness; + enum vidc_720p_endian dma_endian; + u32 dbg_core_dump_buf_size = 0; + + vcd_get_fw_property(VCD_FW_ENDIAN, &fw_endianness); + if (fw_endianness == VCD_FW_BIG_ENDIAN) + dma_endian = VIDC_720P_LITTLE_ENDIAN; + else + dma_endian = VIDC_720P_BIG_ENDIAN; + + ddl_move_command_state(ddl_context, DDL_CMD_CPU_RESET); + + DBG("VSP_BUF_ADDR_SIZE %d", + ddl_context->context_buf_addr.buffer_size); + if (ddl_context->enable_dbg_core_dump) { + dbg_core_dump_buf_size = ddl_context->dbg_core_dump. + buffer_size; + } + + vidc_720p_start_cpu(dma_endian, + ddl_context->context_buf_addr.align_physical_addr, + ddl_context->dbg_core_dump.align_physical_addr, + dbg_core_dump_buf_size); + + VIDC_DEBUG_REGISTER_LOG; +} + +void ddl_channel_set(struct ddl_client_context *ddl) +{ + enum vidc_720p_enc_dec_selection enc_dec_sel; + enum vidc_720p_codec codec; + enum vcd_codec *vcd_codec; + u32 fw_property_id; + struct vcd_fw_details fw_details; + + if (ddl->decoding) { + if (vidc_msg_timing) + ddl_set_core_start_time(__func__, DEC_OP_TIME); + enc_dec_sel = VIDC_720P_DECODER; + fw_property_id = VCD_FW_DECODE; + vcd_codec = &(ddl->codec_data.decoder.codec.codec); + } else { + enc_dec_sel = VIDC_720P_ENCODER; + fw_property_id = VCD_FW_ENCODE; + vcd_codec = &(ddl->codec_data.encoder.codec.codec); + } + switch (*vcd_codec) { + default: + case VCD_CODEC_MPEG4: + { + codec = VIDC_720P_MPEG4; + + if (ddl->decoding) { + vidc_720p_decode_set_mpeg4_data_partitionbuffer + (ddl->ddl_context->data_partition_tempbuf. + align_physical_addr); + } + + break; + } + case VCD_CODEC_H264: + { + codec = VIDC_720P_H264; + break; + } + case VCD_CODEC_DIVX_4: + case VCD_CODEC_DIVX_5: + case VCD_CODEC_DIVX_6: + { + codec = VIDC_720P_DIVX; + break; + } + case VCD_CODEC_XVID: + { + codec = VIDC_720P_XVID; + break; + } + case VCD_CODEC_H263: + { + codec = VIDC_720P_H263; + break; + } + case VCD_CODEC_MPEG2: + { + codec = VIDC_720P_MPEG2; + break; + } + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + { + codec = VIDC_720P_VC1; + break; + } + } + + fw_details.codec = *vcd_codec; + vcd_get_fw_property(fw_property_id, &fw_details); + VIDC_DEBUG_REGISTER_LOG; + + ddl_move_command_state(ddl->ddl_context, DDL_CMD_CHANNEL_SET); + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_CHDONE); + + vidc_720p_set_channel(ddl->channel_id, + enc_dec_sel, + codec, + fw_details.fw_buffer_addr, + fw_details.fw_size); +} + +void ddl_decode_init_codec(struct ddl_client_context *ddl) +{ + u32 seq_h = 0, seq_e = 0, start_byte_num = 0; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vcd_sequence_hdr *seq_hdr = &decoder->decode_config; + enum vidc_720p_memory_access_method mem_access_method; + if (vidc_msg_timing) + ddl_set_core_start_time(__func__, DEC_OP_TIME); + ddl_metadata_enable(ddl); + + vidc_720p_decode_set_error_control(true); + + vidc_720p_decode_set_mpeg4Post_filter(decoder->post_filter. + post_filter); + + if (decoder->codec.codec == VCD_CODEC_H264) { + vidc_720p_decode_setH264VSPBuffer(decoder-> + h264Vsp_temp_buffer. + align_physical_addr); + VIDC_LOG1("VSP_BUF_ADDR_SIZE", + decoder->h264Vsp_temp_buffer.buffer_size); + } + + if (decoder->codec.codec == VCD_CODEC_VC1_RCV || + decoder->codec.codec == VCD_CODEC_VC1) { + vidc_720p_set_frame_size(decoder->client_frame_size.width, + decoder->client_frame_size.height); + } else { + vidc_720p_set_frame_size(0x0, 0x0); + } + + switch (decoder->buf_format.buffer_format) { + default: + case VCD_BUFFER_FORMAT_NV12: + { + mem_access_method = VIDC_720P_TILE_LINEAR; + break; + } + case VCD_BUFFER_FORMAT_TILE_4x2: + { + mem_access_method = VIDC_720P_TILE_64x32; + break; + } + } + VIDC_LOG_STRING("HEADER-PARSE-START"); + VIDC_DEBUG_REGISTER_LOG; + seq_h = (u32) seq_hdr->sequence_header; + start_byte_num = 8 - (seq_h & DDL_STREAMBUF_ALIGN_GUARD_BYTES); + seq_e = seq_h + seq_hdr->sequence_header_len; + seq_h &= ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + DDL_PADDING_HACK(seq_e); + + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_INITCODECDONE); + ddl_move_command_state(ddl->ddl_context, DDL_CMD_HEADER_PARSE); + + vidc_720p_decode_bitstream_header(ddl->channel_id, + seq_hdr->sequence_header_len, + start_byte_num, + seq_h, + seq_e, + mem_access_method, + decoder->output_order); +} + +void ddl_decode_dynamic_property(struct ddl_client_context *ddl, + u32 enable) +{ + uint8_t *temp = NULL; + u32 extra_datastart = 0; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vcd_frame_data *bit_stream = + &(ddl->input_frame.vcd_frm); + + if (!enable) { + if (decoder->dynmic_prop_change_req) { + decoder->dynmic_prop_change_req = false; + vidc_720p_decode_dynamic_req_reset(); + } + return; + } + if ((decoder->dynamic_prop_change & + DDL_DEC_REQ_OUTPUT_FLUSH)) { + decoder->dynmic_prop_change_req = true; + decoder->dynamic_prop_change &= ~(DDL_DEC_REQ_OUTPUT_FLUSH); + decoder->dpb_mask.hw_mask = 0; + vidc_720p_decode_dynamic_req_set(VIDC_720P_FLUSH_REQ); + } + if (((decoder->meta_data_enable_flag & VCD_METADATA_PASSTHROUGH)) + && ((VCD_FRAME_FLAG_EXTRADATA & bit_stream->flags)) + ) { + + temp = ((uint8_t *)bit_stream->physical + + bit_stream->offset + + bit_stream->data_len + 3); + + extra_datastart = (u32) ((u32)temp & ~3); + decoder->dynmic_prop_change_req = true; + + vidc_720p_decode_setpassthrough_start(extra_datastart); + + vidc_720p_decode_dynamic_req_set(VIDC_720P_EXTRADATA); + } +} + +void ddl_encode_dynamic_property(struct ddl_client_context *ddl, + u32 enable) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + u32 enc_param_change = 0; + + if (!enable) { + if (encoder->dynmic_prop_change_req) { + encoder->dynmic_prop_change_req = false; + encoder->ext_enc_control_val &= + ~(VIDC_720P_ENC_IFRAME_REQ); + vidc_720p_encode_set_control_param + (encoder->ext_enc_control_val); + vidc_720p_encoder_set_param_change(enc_param_change); + } + return; + } + if ((encoder->dynamic_prop_change & DDL_ENC_REQ_IFRAME)) { + encoder->dynamic_prop_change &= ~(DDL_ENC_REQ_IFRAME); + encoder->ext_enc_control_val |= VIDC_720P_ENC_IFRAME_REQ; + vidc_720p_encode_set_control_param + (encoder->ext_enc_control_val); + } + if ((encoder->dynamic_prop_change & DDL_ENC_CHANGE_BITRATE)) { + vidc_720p_encode_set_bit_rate( + encoder->target_bit_rate.target_bitrate); + enc_param_change |= VIDC_720P_ENC_BITRATE_CHANGE; + encoder->dynamic_prop_change &= ~(DDL_ENC_CHANGE_BITRATE); + } + if ((encoder->dynamic_prop_change & DDL_ENC_CHANGE_CIR)) { + vidc_720p_encode_set_intra_refresh_mb_number( + encoder->intra_refresh.cir_mb_number); + encoder->dynamic_prop_change &= ~(DDL_ENC_CHANGE_CIR); + } + if ((encoder->dynamic_prop_change & DDL_ENC_CHANGE_IPERIOD)) { + vidc_720p_encode_set_i_period + (encoder->i_period.p_frames); + enc_param_change |= VIDC_720P_ENC_IPERIOD_CHANGE; + encoder->dynamic_prop_change &= ~(DDL_ENC_CHANGE_IPERIOD); + } + if ((encoder->dynamic_prop_change & + DDL_ENC_CHANGE_FRAMERATE)) { + vidc_720p_encode_set_fps + ((encoder->frame_rate.fps_numerator * 1000) / + encoder->frame_rate.fps_denominator); + enc_param_change |= VIDC_720P_ENC_FRAMERATE_CHANGE; + encoder->dynamic_prop_change &= ~(DDL_ENC_CHANGE_FRAMERATE); + } + if (enc_param_change) + vidc_720p_encoder_set_param_change(enc_param_change); +} + +static void ddl_encode_set_profile_level(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + u32 profile; + u32 level; + + switch (encoder->profile.profile) { + default: + case VCD_PROFILE_MPEG4_SP: + { + profile = VIDC_720P_PROFILE_MPEG4_SP; + break; + } + case VCD_PROFILE_MPEG4_ASP: + { + profile = VIDC_720P_PROFILE_MPEG4_ASP; + break; + } + case VCD_PROFILE_H264_BASELINE: + { + profile = VIDC_720P_PROFILE_H264_CPB; + break; + } + case VCD_PROFILE_H264_MAIN: + { + profile = VIDC_720P_PROFILE_H264_MAIN; + break; + } + case VCD_PROFILE_H264_HIGH: + { + profile = VIDC_720P_PROFILE_H264_HIGH; + break; + } + case VCD_PROFILE_H263_BASELINE: + { + profile = VIDC_720P_PROFILE_H263_BASELINE; + break; + } + } + switch (encoder->level.level) { + default: + case VCD_LEVEL_MPEG4_0: + { + level = VIDC_720P_MPEG4_LEVEL0; + break; + } + case VCD_LEVEL_MPEG4_0b: + { + level = VIDC_720P_MPEG4_LEVEL0b; + break; + } + case VCD_LEVEL_MPEG4_1: + { + level = VIDC_720P_MPEG4_LEVEL1; + break; + } + case VCD_LEVEL_MPEG4_2: + { + level = VIDC_720P_MPEG4_LEVEL2; + break; + } + case VCD_LEVEL_MPEG4_3: + { + level = VIDC_720P_MPEG4_LEVEL3; + break; + } + case VCD_LEVEL_MPEG4_3b: + { + level = VIDC_720P_MPEG4_LEVEL3b; + break; + } + + case VCD_LEVEL_MPEG4_4: + case VCD_LEVEL_MPEG4_4a: + { + level = VIDC_720P_MPEG4_LEVEL4a; + break; + } + case VCD_LEVEL_MPEG4_5: + { + level = VIDC_720P_MPEG4_LEVEL5; + break; + } + case VCD_LEVEL_MPEG4_6: + { + level = VIDC_720P_MPEG4_LEVEL6; + break; + } + case VCD_LEVEL_H264_1: + { + level = VIDC_720P_H264_LEVEL1; + break; + } + case VCD_LEVEL_H264_1b: + { + level = VIDC_720P_H264_LEVEL1b; + break; + } + case VCD_LEVEL_H264_1p1: + { + level = VIDC_720P_H264_LEVEL1p1; + break; + } + case VCD_LEVEL_H264_1p2: + { + level = VIDC_720P_H264_LEVEL1p2; + break; + } + case VCD_LEVEL_H264_1p3: + { + level = VIDC_720P_H264_LEVEL1p3; + break; + } + case VCD_LEVEL_H264_2: + { + level = VIDC_720P_H264_LEVEL2; + break; + } + case VCD_LEVEL_H264_2p1: + { + level = VIDC_720P_H264_LEVEL2p1; + break; + } + case VCD_LEVEL_H264_2p2: + { + level = VIDC_720P_H264_LEVEL2p2; + break; + } + case VCD_LEVEL_H264_3: + { + level = VIDC_720P_H264_LEVEL3; + break; + } + case VCD_LEVEL_H264_3p1: + { + level = VIDC_720P_H264_LEVEL3p1; + break; + } + case VCD_LEVEL_H263_10: + { + level = VIDC_720P_H263_LEVEL10; + break; + } + case VCD_LEVEL_H263_20: + { + level = VIDC_720P_H263_LEVEL20; + break; + } + case VCD_LEVEL_H263_30: + { + level = VIDC_720P_H263_LEVEL30; + break; + } + case VCD_LEVEL_H263_40: + { + level = VIDC_720P_H263_LEVEL40; + break; + } + case VCD_LEVEL_H263_45: + { + level = VIDC_720P_H263_LEVEL45; + break; + } + case VCD_LEVEL_H263_50: + { + level = VIDC_720P_H263_LEVEL50; + break; + } + case VCD_LEVEL_H263_60: + { + level = VIDC_720P_H263_LEVEL60; + break; + } + case VCD_LEVEL_H263_70: + { + level = VIDC_720P_H263_LEVEL70; + break; + } + } + vidc_720p_encode_set_profile(profile, level); +} + +void ddl_encode_init_codec(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + enum vidc_720p_memory_access_method mem_access_method; + enum vidc_720p_DBConfig db_config; + enum vidc_720p_MSlice_selection m_slice_sel; + + ddl_encode_set_profile_level(ddl); + + vidc_720p_set_frame_size + (encoder->frame_size.width, encoder->frame_size.height); + vidc_720p_encode_set_qp_params + (encoder->qp_range.max_qp, encoder->qp_range.min_qp); + vidc_720p_encode_set_rc_config + (encoder->rc_level.frame_level_rc, + encoder->rc_level.mb_level_rc, + encoder->session_qp.i_frame_qp, + encoder->session_qp.p_frame_qp); + + if (encoder->r_cframe_skip) { + if (encoder->vb_vbuffer_size) { + encoder->ext_enc_control_val = (0x2 << 0x2) | + (encoder->vb_vbuffer_size << 0x10); + } else + encoder->ext_enc_control_val = (0x1 << 2); + } else + encoder->ext_enc_control_val = 0; + + vidc_720p_encode_set_fps + ((encoder->frame_rate.fps_numerator * 1000) / + encoder->frame_rate.fps_denominator); + + vidc_720p_encode_set_vop_time( + encoder->vop_timing.vop_time_resolution, 0); + + if (encoder->rc_level.frame_level_rc) { + vidc_720p_encode_set_bit_rate + (encoder->target_bit_rate.target_bitrate); + + vidc_720p_encode_set_frame_level_rc_params + (encoder->frame_level_rc.reaction_coeff); + } + if (encoder->rc_level.mb_level_rc) { + vidc_720p_encode_set_mb_level_rc_params + (encoder->adaptive_rc.dark_region_as_flag, + encoder->adaptive_rc.smooth_region_as_flag, + encoder->adaptive_rc.static_region_as_flag, + encoder->adaptive_rc.activity_region_flag); + } + if (encoder->codec.codec == VCD_CODEC_MPEG4) { + vidc_720p_encode_set_short_header + (encoder->short_header.short_header); + + if (encoder->hdr_ext_control) { + vidc_720p_encode_set_hec_period + (encoder->hdr_ext_control); + encoder->ext_enc_control_val |= (0x1 << 0x1); + } + } + /* set extended encoder control settings */ + vidc_720p_encode_set_control_param + (encoder->ext_enc_control_val); + + if (encoder->codec.codec == VCD_CODEC_H264) { + enum vidc_720p_entropy_sel entropy_sel; + enum vidc_720p_cabac_model cabac_model_number; + switch (encoder->entropy_control.entropy_sel) { + default: + case VCD_ENTROPY_SEL_CAVLC: + { + entropy_sel = VIDC_720P_ENTROPY_SEL_CAVLC; + break; + } + case VCD_ENTROPY_SEL_CABAC: + { + entropy_sel = VIDC_720P_ENTROPY_SEL_CABAC; + break; + } + } + switch (encoder->entropy_control.cabac_model) { + default: + case VCD_CABAC_MODEL_NUMBER_0: + { + cabac_model_number = + VIDC_720P_CABAC_MODEL_NUMBER_0; + break; + } + case VCD_CABAC_MODEL_NUMBER_1: + { + cabac_model_number = + VIDC_720P_CABAC_MODEL_NUMBER_1; + break; + } + case VCD_CABAC_MODEL_NUMBER_2: + { + cabac_model_number = + VIDC_720P_CABAC_MODEL_NUMBER_2; + break; + } + } + vidc_720p_encode_set_entropy_control + (entropy_sel, cabac_model_number); + switch (encoder->db_control.db_config) { + default: + case VCD_DB_ALL_BLOCKING_BOUNDARY: + { + db_config = + VIDC_720P_DB_ALL_BLOCKING_BOUNDARY; + break; + } + case VCD_DB_DISABLE: + { + db_config = + VIDC_720P_DB_DISABLE; + break; + } + case VCD_DB_SKIP_SLICE_BOUNDARY: + { + db_config = + VIDC_720P_DB_SKIP_SLICE_BOUNDARY; + break; + } + } + vidc_720p_encode_set_db_filter_control + (db_config, + encoder->db_control.slice_alpha_offset, + encoder->db_control.slice_beta_offset); + } + + vidc_720p_encode_set_intra_refresh_mb_number + (encoder->intra_refresh.cir_mb_number); + + switch (encoder->multi_slice.m_slice_sel) { + default: + case VCD_MSLICE_OFF: + m_slice_sel = VIDC_720P_MSLICE_OFF; + break; + case VCD_MSLICE_BY_MB_COUNT: + { + m_slice_sel = VIDC_720P_MSLICE_BY_MB_COUNT; + break; + } + case VCD_MSLICE_BY_BYTE_COUNT: + { + m_slice_sel = VIDC_720P_MSLICE_BY_BYTE_COUNT; + break; + } + case VCD_MSLICE_BY_GOB: + { + m_slice_sel = VIDC_720P_MSLICE_BY_GOB; + break; + } + } + vidc_720p_encode_set_multi_slice_info + (m_slice_sel, encoder->multi_slice.m_slice_size); + + vidc_720p_encode_set_dpb_buffer + (encoder->enc_dpb_addr.align_physical_addr, + encoder->enc_dpb_addr.buffer_size); + + VIDC_LOG1("ENC_DPB_ADDR_SIZE", encoder->enc_dpb_addr.buffer_size); + + vidc_720p_encode_set_i_period(encoder->i_period.p_frames); + + ddl_metadata_enable(ddl); + + if (encoder->seq_header.virtual_base_addr) { + u32 ext_buffer_start, ext_buffer_end, start_byte_num; + ext_buffer_start = + (u32) encoder->seq_header.align_physical_addr; + ext_buffer_end = + ext_buffer_start + encoder->seq_header.buffer_size; + start_byte_num = + (ext_buffer_start & DDL_STREAMBUF_ALIGN_GUARD_BYTES); + ext_buffer_start &= ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + ext_buffer_end &= ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + VIDC_LOG1("ENC_SEQHDR_ALLOC_SIZE", + encoder->seq_header.buffer_size); + vidc_720p_encode_set_seq_header_buffer(ext_buffer_start, + ext_buffer_end, + start_byte_num); + } + + if (encoder->re_con_buf_format.buffer_format == + VCD_BUFFER_FORMAT_NV12) + mem_access_method = VIDC_720P_TILE_LINEAR; + else + mem_access_method = VIDC_720P_TILE_16x16; + + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_INITCODECDONE); + ddl_move_command_state(ddl->ddl_context, DDL_CMD_INIT_CODEC); + + vidc_720p_encode_init_codec(ddl->channel_id, mem_access_method); +} + +void ddl_channel_end(struct ddl_client_context *ddl) +{ + VIDC_DEBUG_REGISTER_LOG; + + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_CHEND); + ddl_move_command_state(ddl->ddl_context, DDL_CMD_CHANNEL_END); + + vidc_720p_submit_command(ddl->channel_id, VIDC_720P_CMD_CHEND); +} + +void ddl_encode_frame_run(struct ddl_client_context *ddl) +{ + u32 ext_buffer_start, ext_buffer_end; + u32 y_addr, c_addr; + u32 start_byte_number = 0; + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + struct vcd_frame_data *stream = &(ddl->output_frame.vcd_frm); + + ext_buffer_start = (u32) stream->physical + stream->offset; + ext_buffer_end = ddl_encode_set_metadata_output_buf(ddl); + start_byte_number = + (ext_buffer_start & DDL_STREAMBUF_ALIGN_GUARD_BYTES); + if (start_byte_number) { + u32 upper_data, lower_data; + u32 *align_virtual_addr; + ext_buffer_start &= ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + align_virtual_addr = (u32 *) (((u32) stream->virtual + + stream->offset) - + start_byte_number); + upper_data = *align_virtual_addr; + align_virtual_addr++; + lower_data = *align_virtual_addr; + vidc_720p_encode_unalign_bitstream(upper_data, lower_data); + } + + y_addr = (u32) ddl->input_frame.vcd_frm.physical + + ddl->input_frame.vcd_frm.offset; + c_addr = (y_addr + (encoder->frame_size.scan_lines * + encoder->frame_size.stride)); + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE); + ddl_move_command_state(ddl->ddl_context, DDL_CMD_ENCODE_FRAME); + + if (encoder->dynamic_prop_change) { + encoder->dynmic_prop_change_req = true; + ddl_encode_dynamic_property(ddl, true); + } + vidc_720p_encode_set_vop_time( + encoder->vop_timing.vop_time_resolution, + ddl->input_frame.frm_delta + ); + + vidc_720p_encode_frame(ddl->channel_id, + ext_buffer_start, + ext_buffer_end, + start_byte_number, y_addr, c_addr); +} + +u32 ddl_decode_set_buffers(struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + u32 comv_buf_size = DDL_COMV_BUFLINE_NO, comv_buf_no = 0; + u32 ref_buf_no = 0; + + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPB)) { + VIDC_LOG_STRING("STATE-CRITICAL"); + return VCD_ERR_FAIL; + } + if (vidc_msg_timing) + ddl_set_core_start_time(__func__, DEC_OP_TIME); + switch (decoder->codec.codec) { + default: + case VCD_CODEC_DIVX_4: + case VCD_CODEC_DIVX_5: + case VCD_CODEC_DIVX_6: + case VCD_CODEC_XVID: + case VCD_CODEC_MPEG2: + case VCD_CODEC_MPEG4: + { + comv_buf_no = DDL_MPEG_COMV_BUF_NO; + ref_buf_no = DDL_MPEG_REFBUF_COUNT; + break; + } + case VCD_CODEC_H263: + { + comv_buf_no = DDL_H263_COMV_BUF_NO; + break; + } + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + { + comv_buf_no = + decoder->client_output_buf_req.actual_count + 1; + comv_buf_size = DDL_VC1_COMV_BUFLINE_NO; + break; + } + case VCD_CODEC_H264: + { + comv_buf_no = + decoder->client_output_buf_req.actual_count; + break; + } + } + + if (comv_buf_no) { + comv_buf_size *= (comv_buf_no * + (decoder->client_frame_size.stride >> 4) * + ((decoder->client_frame_size.scan_lines >> 4) + 1)); + if (decoder->dpb_comv_buffer.virtual_base_addr) + ddl_pmem_free(&decoder->dpb_comv_buffer); + ddl_pmem_alloc(&decoder->dpb_comv_buffer, comv_buf_size, + DDL_LINEAR_BUFFER_ALIGN_BYTES); + if (!decoder->dpb_comv_buffer.virtual_base_addr) { + VIDC_LOGERR_STRING + ("Dec_set_buf:Comv_buf_alloc_failed"); + return VCD_ERR_ALLOC_FAIL; + } + vidc_720p_decode_set_comv_buffer(decoder->dpb_comv_buffer. + align_physical_addr, + decoder->dpb_comv_buffer. + buffer_size); + } + decoder->ref_buffer.align_physical_addr = NULL; + if (ref_buf_no) { + size_t sz, align_bytes; + sz = decoder->dp_buf.dec_pic_buffers[0].vcd_frm.alloc_len; + sz *= ref_buf_no; + align_bytes = decoder->client_output_buf_req.align; + if (decoder->ref_buffer.virtual_base_addr) + ddl_pmem_free(&decoder->ref_buffer); + ddl_pmem_alloc(&decoder->ref_buffer, sz, align_bytes); + if (!decoder->ref_buffer.virtual_base_addr) { + ddl_pmem_free(&decoder->dpb_comv_buffer); + VIDC_LOGERR_STRING + ("Dec_set_buf:mpeg_ref_buf_alloc_failed"); + return VCD_ERR_ALLOC_FAIL; + } + } + ddl_decode_set_metadata_output(decoder); + ddl_decoder_dpb_transact(decoder, NULL, DDL_DPB_OP_INIT); + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_DPBDONE); + ddl_move_command_state(ddl->ddl_context, DDL_CMD_DECODE_SET_DPB); + + vidc_720p_submit_command(ddl->channel_id, + VIDC_720P_CMD_INITBUFFERS); + return VCD_S_SUCCESS; +} + +void ddl_decode_frame_run(struct ddl_client_context *ddl) +{ + u32 ext_buffer_start = 0, ext_buffer_end = 0; + u32 start_byte_num = 8; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + struct vcd_frame_data *bit_stream = + &(ddl->input_frame.vcd_frm); + if (vidc_msg_timing) { + ddl_set_core_start_time(__func__, DEC_OP_TIME); + ddl_set_core_start_time(__func__, DEC_IP_TIME); + } + if (!bit_stream->data_len || + !bit_stream->physical) { + ddl_decode_eos_run(ddl); + return; + } + + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE); + + ddl_decode_dynamic_property(ddl, true); + + ddl_decoder_dpb_transact(decoder, NULL, DDL_DPB_OP_SET_MASK); + + ext_buffer_start = (u32)bit_stream->physical + + bit_stream->offset; + start_byte_num = 8 - (ext_buffer_start & + DDL_STREAMBUF_ALIGN_GUARD_BYTES); + ext_buffer_end = ext_buffer_start + bit_stream->data_len; + ext_buffer_start &= ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + DDL_PADDING_HACK(ext_buffer_end); + + ddl_move_command_state(ddl->ddl_context, DDL_CMD_DECODE_FRAME); + + vidc_720p_decode_frame(ddl->channel_id, + ext_buffer_start, + ext_buffer_end, + bit_stream->data_len, + start_byte_num, bit_stream->ip_frm_tag); +} + +void ddl_decode_eos_run(struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_EOS_DONE); + + ddl_decode_dynamic_property(ddl, true); + + ddl_decoder_dpb_transact(decoder, NULL, DDL_DPB_OP_SET_MASK); + + decoder->dynmic_prop_change_req = true; + + ddl_move_command_state(ddl->ddl_context, DDL_CMD_EOS); + + vidc_720p_issue_eos(ddl->channel_id); +} + +u32 ddl_hal_engine_reset(struct ddl_context *ddl_context) +{ + u32 eng_reset; + u32 channel_id = 0; + u32 fw_endianness; + enum vidc_720p_endian dma_endian; + enum vidc_720p_interrupt_level_selection interrupt_sel; + u32 intr_mask = 0x0; + + if (ddl_context->current_ddl) + channel_id = ddl_context->current_ddl->channel_id; + + interrupt_sel = VIDC_720P_INTERRUPT_LEVEL_SEL; + /* Enable all the supported interrupt */ + intr_mask |= VIDC_720P_INTR_BUFFER_FULL; + intr_mask |= VIDC_720P_INTR_FW_DONE; + intr_mask |= VIDC_720P_INTR_DMA_DONE; + intr_mask |= VIDC_720P_INTR_FRAME_DONE; + + vcd_get_fw_property(VCD_FW_ENDIAN, &fw_endianness); + /* Reverse the endianness settings after boot code download */ + if (fw_endianness == VCD_FW_BIG_ENDIAN) + dma_endian = VIDC_720P_LITTLE_ENDIAN; + else + dma_endian = VIDC_720P_BIG_ENDIAN; + + /* Need to reset MFC silently */ + eng_reset = vidc_720p_engine_reset( + channel_id, + dma_endian, interrupt_sel, + intr_mask); + if (!eng_reset) { + /* call the hw fatal callback if engine reset fails */ + ddl_hw_fatal_cb(ddl_context); + } + return eng_reset ; +} diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_helper.c b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_helper.c new file mode 100644 index 00000000000..2899df6fe8f --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_helper.c @@ -0,0 +1,286 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd_ddl_utils.h" + +DDL_INLINE struct ddl_context *ddl_get_context(void) +{ + static struct ddl_context ddl_context; + return &ddl_context; +} + +DDL_INLINE void ddl_move_client_state(struct ddl_client_context *ddl, + enum ddl_client_state client_state) +{ + ddl->client_state = client_state; +} + +DDL_INLINE void ddl_move_command_state(struct ddl_context *ddl_context, + enum ddl_cmd_state command_state) +{ + ddl_context->cmd_state = command_state; +} + +u32 ddl_client_transact(u32 operation, + struct ddl_client_context **pddl_client) +{ + u32 ret_status = VCD_ERR_FAIL; + u32 counter; + struct ddl_context *ddl_context; + + ddl_context = ddl_get_context(); + switch (operation) { + case DDL_FREE_CLIENT: + { + if (pddl_client && *pddl_client) { + u32 channel_id; + channel_id = (*pddl_client)->channel_id; + if (channel_id < VCD_MAX_NO_CLIENT) { + ddl_context-> + ddl_clients[channel_id] = NULL; + } else { + VIDC_LOG_STRING("CHID_CORRUPTION"); + } + DDL_FREE(*pddl_client); + ret_status = VCD_S_SUCCESS; + } + break; + } + case DDL_GET_CLIENT: + { + ret_status = VCD_ERR_MAX_CLIENT; + for (counter = 0; counter < VCD_MAX_NO_CLIENT && + ret_status == VCD_ERR_MAX_CLIENT; ++counter) { + if (!ddl_context->ddl_clients[counter]) { + *pddl_client = + (struct ddl_client_context *) + DDL_MALLOC(sizeof + (struct ddl_client_context) + ); + if (!*pddl_client) { + ret_status = VCD_ERR_ALLOC_FAIL; + } else { + DDL_MEMSET(*pddl_client, 0, + sizeof(struct + ddl_client_context)); + ddl_context-> + ddl_clients[counter] = + *pddl_client; + (*pddl_client)->channel_id = + counter; + (*pddl_client)->ddl_context = + ddl_context; + ret_status = VCD_S_SUCCESS; + } + } + } + break; + } + case DDL_INIT_CLIENTS: + { + for (counter = 0; counter < VCD_MAX_NO_CLIENT; + ++counter) { + ddl_context->ddl_clients[counter] = NULL; + } + ret_status = VCD_S_SUCCESS; + break; + } + case DDL_ACTIVE_CLIENT: + { + for (counter = 0; counter < VCD_MAX_NO_CLIENT; + ++counter) { + if (ddl_context->ddl_clients[counter]) { + ret_status = VCD_S_SUCCESS; + break; + } + } + break; + } + default: + { + ret_status = VCD_ERR_ILLEGAL_PARM; + break; + } + } + return ret_status; +} + +u32 ddl_decoder_dpb_transact(struct ddl_decoder_data *decoder, + struct ddl_frame_data_tag *in_out_frame, + u32 operation) +{ + u32 vcd_status = VCD_S_SUCCESS; + u32 loopc; + struct ddl_frame_data_tag *found_frame = NULL; + struct ddl_mask *dpb_mask = &decoder->dpb_mask; + + switch (operation) { + case DDL_DPB_OP_MARK_BUSY: + case DDL_DPB_OP_MARK_FREE: + { + for (loopc = 0; !found_frame && + loopc < decoder->dp_buf.no_of_dec_pic_buf; + ++loopc) { + if (in_out_frame->vcd_frm.physical == + decoder->dp_buf. + dec_pic_buffers[loopc].vcd_frm. + physical) { + found_frame = + &(decoder->dp_buf. + dec_pic_buffers[loopc]); + break; + } + } + + if (found_frame) { + if (operation == DDL_DPB_OP_MARK_BUSY) { + dpb_mask->hw_mask &= + (~(0x1 << loopc)); + *in_out_frame = *found_frame; + } else if (operation == + DDL_DPB_OP_MARK_FREE) { + dpb_mask->client_mask |= + (0x1 << loopc); + *found_frame = *in_out_frame; + } + } else { + in_out_frame->vcd_frm.physical = NULL; + in_out_frame->vcd_frm.virtual = NULL; + vcd_status = VCD_ERR_BAD_POINTER; + VIDC_LOG_STRING("BUF_NOT_FOUND"); + } + break; + } + case DDL_DPB_OP_SET_MASK: + { + dpb_mask->hw_mask |= dpb_mask->client_mask; + dpb_mask->client_mask = 0; + vidc_720p_decode_set_dpb_release_buffer_mask + (dpb_mask->hw_mask); + break; + } + case DDL_DPB_OP_INIT: + { + u32 dpb_size; + dpb_size = (!decoder->meta_data_offset) ? + decoder->dp_buf.dec_pic_buffers[0].vcd_frm. + alloc_len : decoder->meta_data_offset; + vidc_720p_decode_set_dpb_details(decoder->dp_buf. + no_of_dec_pic_buf, + dpb_size, + decoder->ref_buffer. + align_physical_addr); + for (loopc = 0; + loopc < decoder->dp_buf.no_of_dec_pic_buf; + ++loopc) { + vidc_720p_decode_set_dpb_buffers(loopc, + (u32 *) + decoder-> + dp_buf. + dec_pic_buffers + [loopc]. + vcd_frm. + physical); + VIDC_LOG1("DEC_DPB_BUFn_SIZE", + decoder->dp_buf. + dec_pic_buffers[loopc].vcd_frm. + alloc_len); + } + break; + } + case DDL_DPB_OP_RETRIEVE: + { + u32 position; + if (dpb_mask->client_mask) { + position = 0x1; + for (loopc = 0; + loopc < + decoder->dp_buf.no_of_dec_pic_buf + && !found_frame; ++loopc) { + if (dpb_mask-> + client_mask & position) { + found_frame = + &decoder->dp_buf. + dec_pic_buffers[loopc]; + dpb_mask->client_mask &= + ~(position); + } + position <<= 1; + } + } else if (dpb_mask->hw_mask) { + position = 0x1; + for (loopc = 0; + loopc < + decoder->dp_buf.no_of_dec_pic_buf + && !found_frame; ++loopc) { + if (dpb_mask->hw_mask + & position) { + found_frame = + &decoder->dp_buf. + dec_pic_buffers[loopc]; + dpb_mask->hw_mask &= + ~(position); + } + position <<= 1; + } + } + if (found_frame) + *in_out_frame = *found_frame; + else { + in_out_frame->vcd_frm.physical = NULL; + in_out_frame->vcd_frm.virtual = NULL; + } + break; + } + } + return vcd_status; +} + +void ddl_release_context_buffers(struct ddl_context *ddl_context) +{ + ddl_pmem_free(&ddl_context->context_buf_addr); + ddl_pmem_free(&ddl_context->db_line_buffer); + ddl_pmem_free(&ddl_context->data_partition_tempbuf); + ddl_pmem_free(&ddl_context->metadata_shared_input); + ddl_pmem_free(&ddl_context->dbg_core_dump); + + vcd_fw_release(); +} + +void ddl_release_client_internal_buffers(struct ddl_client_context *ddl) +{ + if (ddl->decoding) { + struct ddl_decoder_data *decoder = + &(ddl->codec_data.decoder); + ddl_pmem_free(&decoder->h264Vsp_temp_buffer); + ddl_pmem_free(&decoder->dpb_comv_buffer); + ddl_pmem_free(&decoder->ref_buffer); + DDL_FREE(decoder->dp_buf.dec_pic_buffers); + ddl_decode_dynamic_property(ddl, false); + decoder->decode_config.sequence_header_len = 0; + decoder->decode_config.sequence_header = NULL; + decoder->dpb_mask.client_mask = 0; + decoder->dpb_mask.hw_mask = 0; + decoder->dp_buf.no_of_dec_pic_buf = 0; + decoder->dynamic_prop_change = 0; + + } else { + struct ddl_encoder_data *encoder = + &(ddl->codec_data.encoder); + ddl_pmem_free(&encoder->enc_dpb_addr); + ddl_pmem_free(&encoder->seq_header); + ddl_encode_dynamic_property(ddl, false); + encoder->dynamic_prop_change = 0; + } +} diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_internal_property.h b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_internal_property.h new file mode 100644 index 00000000000..00c00cdeb9e --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_internal_property.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_DDL_INTERNAL_PROPERTY_H_ +#define _VCD_DDL_INTERNAL_PROPERTY_H_ +#include "vcd_api.h" + +#define VCD_EVT_RESP_DDL_BASE 0x3000 +#define VCD_EVT_RESP_DEVICE_INIT (VCD_EVT_RESP_DDL_BASE + 0x1) +#define VCD_EVT_RESP_OUTPUT_REQ (VCD_EVT_RESP_DDL_BASE + 0x2) +#define VCD_EVT_RESP_EOS_DONE (VCD_EVT_RESP_DDL_BASE + 0x3) +#define VCD_EVT_RESP_TRANSACTION_PENDING (VCD_EVT_RESP_DDL_BASE + 0x4) + +#define VCD_S_DDL_ERR_BASE 0x90000000 +#define VCD_ERR_MAX_NO_CODEC (VCD_S_DDL_ERR_BASE + 0x1) +#define VCD_ERR_CLIENT_PRESENT (VCD_S_DDL_ERR_BASE + 0x2) +#define VCD_ERR_CLIENT_FATAL (VCD_S_DDL_ERR_BASE + 0x3) + +#define VCD_I_CUSTOM_BASE (VCD_I_RESERVED_BASE) +#define VCD_I_RC_LEVEL_CONFIG (VCD_I_CUSTOM_BASE + 0x1) +#define VCD_I_FRAME_LEVEL_RC (VCD_I_CUSTOM_BASE + 0x2) +#define VCD_I_ADAPTIVE_RC (VCD_I_CUSTOM_BASE + 0x3) +#define VCD_I_CUSTOM_DDL_BASE (VCD_I_RESERVED_BASE + 0x100) +#define DDL_I_INPUT_BUF_REQ (VCD_I_CUSTOM_DDL_BASE + 0x1) +#define DDL_I_OUTPUT_BUF_REQ (VCD_I_CUSTOM_DDL_BASE + 0x2) +#define DDL_I_DPB (VCD_I_CUSTOM_DDL_BASE + 0x3) +#define DDL_I_DPB_RELEASE (VCD_I_CUSTOM_DDL_BASE + 0x4) +#define DDL_I_DPB_RETRIEVE (VCD_I_CUSTOM_DDL_BASE + 0x5) +#define DDL_I_REQ_OUTPUT_FLUSH (VCD_I_CUSTOM_DDL_BASE + 0x6) +#define DDL_I_SEQHDR_ALIGN_BYTES (VCD_I_CUSTOM_DDL_BASE + 0x7) +#define DDL_I_SEQHDR_PRESENT (VCD_I_CUSTOM_DDL_BASE + 0xb) +#define DDL_I_CAPABILITY (VCD_I_CUSTOM_DDL_BASE + 0x8) +#define DDL_I_FRAME_PROC_UNITS (VCD_I_CUSTOM_DDL_BASE + 0x9) + +struct vcd_property_rc_level { + u32 frame_level_rc; + u32 mb_level_rc; +}; + +struct vcd_property_frame_level_rc_params { + u32 reaction_coeff; +}; + +struct vcd_property_adaptive_rc_params { + u32 dark_region_as_flag; + u32 smooth_region_as_flag; + u32 static_region_as_flag; + u32 activity_region_flag; +}; + +struct ddl_frame_data_tag; + +struct ddl_property_dec_pic_buffers { + struct ddl_frame_data_tag *dec_pic_buffers; + u32 no_of_dec_pic_buf; +}; + +struct ddl_property_capability { + u32 max_num_client; + u32 general_command_depth; + u32 frame_command_depth; + u32 exclusive; + u32 ddl_time_out_in_ms; +}; + +#endif diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_interrupt_handler.c b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_interrupt_handler.c new file mode 100644 index 00000000000..ef5b717d713 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_interrupt_handler.c @@ -0,0 +1,1122 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vidc.h" +#include "vcd_ddl_utils.h" +#include "vcd_ddl_metadata.h" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +static void ddl_decoder_input_done_callback( + struct ddl_client_context *ddl, u32 frame_transact_end); +static u32 ddl_decoder_output_done_callback( + struct ddl_client_context *ddl, u32 frame_transact_end); + +static u32 ddl_get_frame + (struct vcd_frame_data *frame, u32 frame_type); + +static void ddl_getdec_profilelevel +(struct ddl_decoder_data *decoder, u32 profile, u32 level); + +static void ddl_dma_done_callback(struct ddl_context *ddl_context) +{ + if (!DDLCOMMAND_STATE_IS(ddl_context, DDL_CMD_DMA_INIT)) { + VIDC_LOGERR_STRING("UNKWN_DMADONE"); + return; + } + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + VIDC_LOG_STRING("DMA_DONE"); + ddl_core_start_cpu(ddl_context); +} + +static void ddl_cpu_started_callback(struct ddl_context *ddl_context) +{ + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + VIDC_LOG_STRING("CPU-STARTED"); + + if (!vidc_720p_cpu_start()) { + ddl_hw_fatal_cb(ddl_context); + return; + } + + vidc_720p_set_deblock_line_buffer( + ddl_context->db_line_buffer.align_physical_addr, + ddl_context->db_line_buffer.buffer_size); + ddl_context->device_state = DDL_DEVICE_INITED; + ddl_context->ddl_callback(VCD_EVT_RESP_DEVICE_INIT, VCD_S_SUCCESS, + NULL, 0, NULL, ddl_context->client_data); + DDL_IDLE(ddl_context); +} + + +static u32 ddl_eos_done_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + u32 displaystatus, resl_change; + + if (!DDLCOMMAND_STATE_IS(ddl_context, DDL_CMD_EOS)) { + VIDC_LOGERR_STRING("UNKWN_EOSDONE"); + ddl_client_fatal_cb(ddl_context); + return true; + } + + if (!ddl || + !ddl->decoding || + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_EOS_DONE) + ) { + VIDC_LOG_STRING("STATE-CRITICAL-EOSDONE"); + ddl_client_fatal_cb(ddl_context); + return true; + } + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + + vidc_720p_eos_info(&displaystatus, &resl_change); + if ((enum vidc_720p_display_status)displaystatus + != VIDC_720P_EMPTY_BUFFER) { + VIDC_LOG_STRING("EOSDONE-EMPTYBUF-ISSUE"); + } + + ddl_decode_dynamic_property(ddl, false); + if (resl_change == 0x1) { + ddl->codec_data.decoder.header_in_start = false; + ddl->codec_data.decoder.decode_config.sequence_header = + ddl->input_frame.vcd_frm.physical; + ddl->codec_data.decoder.decode_config.sequence_header_len = + ddl->input_frame.vcd_frm.data_len; + ddl_decode_init_codec(ddl); + return false; + } + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_FRAME); + VIDC_LOG_STRING("EOS_DONE"); + ddl_context->ddl_callback(VCD_EVT_RESP_EOS_DONE, VCD_S_SUCCESS, + NULL, 0, (u32 *) ddl, ddl_context->client_data); + DDL_IDLE(ddl_context); + + return true; +} + +static u32 ddl_channel_set_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + u32 return_status = false; + + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + VIDC_DEBUG_REGISTER_LOG; + + if (!ddl || + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_CHDONE) + ) { + VIDC_LOG_STRING("STATE-CRITICAL-CHSET"); + DDL_IDLE(ddl_context); + return return_status; + } + VIDC_LOG_STRING("Channel-set"); + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_INITCODEC); + + if (ddl->decoding) { + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, DEC_OP_TIME); + if (ddl->codec_data.decoder.header_in_start) { + ddl_decode_init_codec(ddl); + } else { + ddl_context->ddl_callback(VCD_EVT_RESP_START, + VCD_S_SUCCESS, NULL, + 0, (u32 *) ddl, + ddl_context->client_data); + + DDL_IDLE(ddl_context); + return_status = true; + } + } else { + ddl_encode_init_codec(ddl); + } + return return_status; +} + +static void ddl_init_codec_done_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + struct ddl_encoder_data *encoder; + + if (!ddl || + ddl->decoding || + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODECDONE) + ) { + VIDC_LOG_STRING("STATE-CRITICAL-INITCODEC"); + ddl_client_fatal_cb(ddl_context); + return; + } + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_FRAME); + VIDC_LOG_STRING("INIT_CODEC_DONE"); + + encoder = &ddl->codec_data.encoder; + if (encoder->seq_header.virtual_base_addr) { + vidc_720p_encode_get_header(&encoder->seq_header. + buffer_size); + } + + ddl_context->ddl_callback(VCD_EVT_RESP_START, VCD_S_SUCCESS, NULL, + 0, (u32 *) ddl, ddl_context->client_data); + + DDL_IDLE(ddl_context); +} + +static u32 ddl_header_done_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + struct ddl_decoder_data *decoder; + struct vidc_720p_seq_hdr_info seq_hdr_info; + + u32 process_further = true; + u32 seq_hdr_only_frame = false; + u32 need_reconfig = true; + struct vcd_frame_data *input_vcd_frm; + struct ddl_frame_data_tag *reconfig_payload = NULL; + u32 reconfig_payload_size = 0; + + if (!ddl || + !ddl->decoding || + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_INITCODECDONE) + ) { + VIDC_LOG_STRING("STATE-CRITICAL-HDDONE"); + ddl_client_fatal_cb(ddl_context); + return true; + } + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, DEC_OP_TIME); + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_DPB); + VIDC_LOG_STRING("HEADER_DONE"); + VIDC_DEBUG_REGISTER_LOG; + + vidc_720p_decode_get_seq_hdr_info(&seq_hdr_info); + + decoder = &(ddl->codec_data.decoder); + decoder->frame_size.width = seq_hdr_info.img_size_x; + decoder->frame_size.height = seq_hdr_info.img_size_y; + decoder->min_dpb_num = seq_hdr_info.min_num_dpb; + decoder->y_cb_cr_size = seq_hdr_info.min_dpb_size; + decoder->progressive_only = 1 - seq_hdr_info.progressive; + if (!seq_hdr_info.img_size_x || !seq_hdr_info.img_size_y) { + VIDC_LOGERR_STRING("FATAL: ZeroImageSize"); + ddl_client_fatal_cb(ddl_context); + return process_further; + } + if (seq_hdr_info.data_partitioned == 0x1 && + decoder->codec.codec == VCD_CODEC_MPEG4 && + seq_hdr_info.img_size_x > DDL_MAX_DP_FRAME_WIDTH && + seq_hdr_info.img_size_y > DDL_MAX_DP_FRAME_HEIGHT) { + ddl_client_fatal_cb(ddl_context); + return process_further; + } + ddl_getdec_profilelevel(decoder, seq_hdr_info.profile, + seq_hdr_info.level); + ddl_calculate_stride(&decoder->frame_size, + !decoder->progressive_only, + decoder->codec.codec); + if (decoder->buf_format.buffer_format == VCD_BUFFER_FORMAT_TILE_4x2) { + decoder->frame_size.stride = + DDL_TILE_ALIGN(decoder->frame_size.width, + DDL_TILE_ALIGN_WIDTH); + decoder->frame_size.scan_lines = + DDL_TILE_ALIGN(decoder->frame_size.height, + DDL_TILE_ALIGN_HEIGHT); + } + if (seq_hdr_info.crop_exists) { + decoder->frame_size.width -= + (seq_hdr_info.crop_right_offset + + seq_hdr_info.crop_left_offset); + decoder->frame_size.height -= + (seq_hdr_info.crop_top_offset + + seq_hdr_info.crop_bottom_offset); + } + ddl_set_default_decoder_buffer_req(decoder, false); + + if (decoder->header_in_start) { + decoder->client_frame_size = decoder->frame_size; + decoder->client_output_buf_req = + decoder->actual_output_buf_req; + decoder->client_input_buf_req = + decoder->actual_input_buf_req; + ddl_context->ddl_callback(VCD_EVT_RESP_START, VCD_S_SUCCESS, + NULL, 0, (u32 *) ddl, ddl_context->client_data); + DDL_IDLE(ddl_context); + } else { + DBG("%s(): Client data: WxH(%u x %u) SxSL(%u x %u) Sz(%u)\n", + __func__, decoder->client_frame_size.width, + decoder->client_frame_size.height, + decoder->client_frame_size.stride, + decoder->client_frame_size.scan_lines, + decoder->client_output_buf_req.sz); + DBG("%s(): DDL data: WxH(%u x %u) SxSL(%u x %u) Sz(%u)\n", + __func__, decoder->frame_size.width, + decoder->frame_size.height, + decoder->frame_size.stride, + decoder->frame_size.scan_lines, + decoder->actual_output_buf_req.sz); + DBG("%s(): min_dpb_num = %d actual_count = %d\n", __func__, + decoder->min_dpb_num, + decoder->client_output_buf_req.actual_count); + + input_vcd_frm = &(ddl->input_frame.vcd_frm); + + if (decoder->frame_size.width == + decoder->client_frame_size.width + && decoder->frame_size.height == + decoder->client_frame_size.height + && decoder->frame_size.stride == + decoder->client_frame_size.stride + && decoder->frame_size.scan_lines == + decoder->client_frame_size.scan_lines + && decoder->actual_output_buf_req.sz <= + decoder->client_output_buf_req.sz + && decoder->actual_output_buf_req.actual_count <= + decoder->client_output_buf_req.actual_count + && decoder->progressive_only) + need_reconfig = false; + if ((input_vcd_frm->data_len <= seq_hdr_info.dec_frm_size || + (input_vcd_frm->flags & VCD_FRAME_FLAG_CODECCONFIG)) && + (!need_reconfig || + !(input_vcd_frm->flags & VCD_FRAME_FLAG_EOS))) { + input_vcd_frm->flags |= + VCD_FRAME_FLAG_CODECCONFIG; + seq_hdr_only_frame = true; + input_vcd_frm->data_len = 0; + ddl->input_frame.frm_trans_end = !need_reconfig; + ddl_context->ddl_callback( + VCD_EVT_RESP_INPUT_DONE, + VCD_S_SUCCESS, &ddl->input_frame, + sizeof(struct ddl_frame_data_tag), + (u32 *) ddl, + ddl->ddl_context->client_data); + } else if (decoder->codec.codec != VCD_CODEC_H263) { + input_vcd_frm->offset += seq_hdr_info.dec_frm_size; + input_vcd_frm->data_len -= seq_hdr_info.dec_frm_size; + } + if (need_reconfig) { + decoder->client_frame_size = decoder->frame_size; + decoder->client_output_buf_req = + decoder->actual_output_buf_req; + decoder->client_input_buf_req = + decoder->actual_input_buf_req; + if (!seq_hdr_only_frame) { + reconfig_payload = &ddl->input_frame; + reconfig_payload_size = + sizeof(struct ddl_frame_data_tag); + } + ddl_context->ddl_callback(VCD_EVT_IND_OUTPUT_RECONFIG, + VCD_S_SUCCESS, reconfig_payload, + reconfig_payload_size, + (u32 *) ddl, + ddl_context->client_data); + } + if (!need_reconfig && !seq_hdr_only_frame) { + if (ddl_decode_set_buffers(ddl) == VCD_S_SUCCESS) + process_further = false; + else + ddl_client_fatal_cb(ddl_context); + } else + DDL_IDLE(ddl_context); + } + return process_further; +} + +static u32 ddl_dpb_buffers_set_done_callback(struct ddl_context + *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + if (!ddl || + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_DPBDONE) + ) { + VIDC_LOG_STRING("STATE-CRITICAL-DPBDONE"); + ddl_client_fatal_cb(ddl_context); + return true; + } + if (vidc_msg_timing) { + ddl_calc_core_proc_time(__func__, DEC_OP_TIME); + ddl_reset_core_time_variables(DEC_OP_TIME); + } + VIDC_LOG_STRING("INTR_DPBDONE"); + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_FRAME); + ddl->codec_data.decoder.dec_disp_info.img_size_x = 0; + ddl->codec_data.decoder.dec_disp_info.img_size_y = 0; + ddl_decode_frame_run(ddl); + return false; +} + +static void ddl_encoder_frame_run_callback(struct ddl_context + *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + u32 eos_present = false; + + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE) + ) { + VIDC_LOG_STRING("STATE-CRITICAL-ENCFRMRUN"); + ddl_client_fatal_cb(ddl_context); + return; + } + + VIDC_LOG_STRING("ENC_FRM_RUN_DONE"); + + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + vidc_720p_enc_frame_info(&encoder->enc_frame_info); + + ddl->output_frame.vcd_frm.ip_frm_tag = + ddl->input_frame.vcd_frm.ip_frm_tag; + ddl->output_frame.vcd_frm.data_len = + encoder->enc_frame_info.enc_size; + ddl->output_frame.vcd_frm.flags |= VCD_FRAME_FLAG_ENDOFFRAME; + ddl_get_frame + (&(ddl->output_frame.vcd_frm), + encoder->enc_frame_info.frame); + ddl_process_encoder_metadata(ddl); + + ddl_encode_dynamic_property(ddl, false); + + ddl->input_frame.frm_trans_end = false; + ddl_context->ddl_callback(VCD_EVT_RESP_INPUT_DONE, VCD_S_SUCCESS, + &(ddl->input_frame), sizeof(struct ddl_frame_data_tag), + (u32 *) ddl, ddl_context->client_data); + + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, ENC_OP_TIME); + + /* check the presence of EOS */ + eos_present = + ((VCD_FRAME_FLAG_EOS & ddl->input_frame.vcd_frm.flags)); + + ddl->output_frame.frm_trans_end = !eos_present; + ddl_context->ddl_callback(VCD_EVT_RESP_OUTPUT_DONE, VCD_S_SUCCESS, + &(ddl->output_frame), sizeof(struct ddl_frame_data_tag), + (u32 *) ddl, ddl_context->client_data); + + if (eos_present) { + VIDC_LOG_STRING("ENC-EOS_DONE"); + ddl_context->ddl_callback(VCD_EVT_RESP_EOS_DONE, + VCD_S_SUCCESS, NULL, 0, (u32 *)ddl, + ddl_context->client_data); + } + + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_FRAME); + DDL_IDLE(ddl_context); +} + +static u32 ddl_decoder_frame_run_callback(struct ddl_context + *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + struct vidc_720p_dec_disp_info *dec_disp_info = + &(ddl->codec_data.decoder.dec_disp_info); + u32 callback_end = false; + u32 status = true, eos_present = false;; + + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE)) { + VIDC_LOG_STRING("STATE-CRITICAL-DECFRMRUN"); + ddl_client_fatal_cb(ddl_context); + return true; + } + + VIDC_LOG_STRING("DEC_FRM_RUN_DONE"); + + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + + vidc_720p_decode_display_info(dec_disp_info); + + ddl_decode_dynamic_property(ddl, false); + + if (dec_disp_info->resl_change) { + VIDC_LOG_STRING + ("DEC_FRM_RUN_DONE: RECONFIG"); + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_EOS_DONE); + ddl_move_command_state(ddl_context, DDL_CMD_EOS); + vidc_720p_submit_command(ddl->channel_id, + VIDC_720P_CMD_FRAMERUN_REALLOCATE); + return false; + } + + if ((VCD_FRAME_FLAG_EOS & ddl->input_frame.vcd_frm.flags)) { + callback_end = false; + eos_present = true; + } + + + if (dec_disp_info->disp_status == VIDC_720P_DECODE_ONLY || + dec_disp_info->disp_status + == VIDC_720P_DECODE_AND_DISPLAY) { + if (!eos_present) + callback_end = (dec_disp_info->disp_status + == VIDC_720P_DECODE_ONLY); + + ddl_decoder_input_done_callback(ddl, callback_end); + } + + if (dec_disp_info->disp_status == VIDC_720P_DECODE_AND_DISPLAY + || dec_disp_info->disp_status == VIDC_720P_DISPLAY_ONLY) { + if (!eos_present) + callback_end = + (dec_disp_info->disp_status + == VIDC_720P_DECODE_AND_DISPLAY); + + if (ddl_decoder_output_done_callback(ddl, callback_end) + != VCD_S_SUCCESS) + return true; + } + + if (dec_disp_info->disp_status == VIDC_720P_DISPLAY_ONLY || + dec_disp_info->disp_status == VIDC_720P_EMPTY_BUFFER) { + /* send the same input once again for decoding */ + ddl_decode_frame_run(ddl); + /* client need to ignore the interrupt */ + status = false; + } else if (eos_present) { + /* send EOS command to HW */ + ddl_decode_eos_run(ddl); + /* client need to ignore the interrupt */ + status = false; + } else { + ddl_move_client_state(ddl, DDL_CLIENT_WAIT_FOR_FRAME); + /* move to Idle */ + DDL_IDLE(ddl_context); + } + return status; +} + +static u32 ddl_eos_frame_done_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl = ddl_context->current_ddl; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vidc_720p_dec_disp_info *dec_disp_info = + &(decoder->dec_disp_info); + + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_EOS_DONE)) { + VIDC_LOGERR_STRING("STATE-CRITICAL-EOSFRMRUN"); + ddl_client_fatal_cb(ddl_context); + return true; + } + VIDC_LOG_STRING("EOS_FRM_RUN_DONE"); + + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + + vidc_720p_decode_display_info(dec_disp_info); + + ddl_decode_dynamic_property(ddl, false); + + if (dec_disp_info->disp_status == VIDC_720P_DISPLAY_ONLY) { + if (ddl_decoder_output_done_callback(ddl, false) + != VCD_S_SUCCESS) + return true; + } else + VIDC_LOG_STRING("STATE-CRITICAL-WRONG-DISP-STATUS"); + + ddl_decoder_dpb_transact(decoder, NULL, DDL_DPB_OP_SET_MASK); + ddl_move_command_state(ddl_context, DDL_CMD_EOS); + vidc_720p_submit_command(ddl->channel_id, + VIDC_720P_CMD_FRAMERUN); + return false; +} + +static void ddl_channel_end_callback(struct ddl_context *ddl_context) +{ + struct ddl_client_context *ddl; + + ddl_move_command_state(ddl_context, DDL_CMD_INVALID); + VIDC_LOG_STRING("CH_END_DONE"); + + ddl = ddl_context->current_ddl; + if (!ddl || + !DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_CHEND) + ) { + VIDC_LOG_STRING("STATE-CRITICAL-CHEND"); + DDL_IDLE(ddl_context); + return; + } + + ddl_release_client_internal_buffers(ddl); + ddl_context->ddl_callback(VCD_EVT_RESP_STOP, VCD_S_SUCCESS, + NULL, 0, (u32 *) ddl, ddl_context->client_data); + ddl_move_client_state(ddl, DDL_CLIENT_OPEN); + DDL_IDLE(ddl_context); +} + +static u32 ddl_operation_done_callback(struct ddl_context *ddl_context) +{ + u32 return_status = true; + + switch (ddl_context->cmd_state) { + case DDL_CMD_DECODE_FRAME: + { + return_status = ddl_decoder_frame_run_callback( + ddl_context); + break; + } + case DDL_CMD_ENCODE_FRAME: + { + ddl_encoder_frame_run_callback(ddl_context); + break; + } + case DDL_CMD_CHANNEL_SET: + { + return_status = ddl_channel_set_callback( + ddl_context); + break; + } + case DDL_CMD_INIT_CODEC: + { + ddl_init_codec_done_callback(ddl_context); + break; + } + case DDL_CMD_HEADER_PARSE: + { + return_status = ddl_header_done_callback( + ddl_context); + break; + } + case DDL_CMD_DECODE_SET_DPB: + { + return_status = ddl_dpb_buffers_set_done_callback( + ddl_context); + break; + } + case DDL_CMD_CHANNEL_END: + { + ddl_channel_end_callback(ddl_context); + break; + } + case DDL_CMD_EOS: + { + return_status = ddl_eos_frame_done_callback( + ddl_context); + break; + } + case DDL_CMD_CPU_RESET: + { + ddl_cpu_started_callback(ddl_context); + break; + } + default: + { + VIDC_LOG_STRING("UNKWN_OPDONE"); + return_status = false; + break; + } + } + return return_status; +} + +static u32 ddl_process_intr_status(struct ddl_context *ddl_context, + u32 int_status) +{ + u32 status = true; + switch (int_status) { + case VIDC_720P_INTR_FRAME_DONE: + { + status = ddl_operation_done_callback(ddl_context); + break; + } + case VIDC_720P_INTR_DMA_DONE: + { + ddl_dma_done_callback(ddl_context); + status = false; + break; + } + case VIDC_720P_INTR_FW_DONE: + { + status = ddl_eos_done_callback(ddl_context); + break; + } + case VIDC_720P_INTR_BUFFER_FULL: + { + VIDC_LOGERR_STRING("BUF_FULL_INTR"); + ddl_hw_fatal_cb(ddl_context); + break; + } + default: + { + VIDC_LOGERR_STRING("UNKWN_INTR"); + break; + } + } + return status; +} + +void ddl_read_and_clear_interrupt(void) +{ + struct ddl_context *ddl_context; + + ddl_context = ddl_get_context(); + if (!ddl_context->core_virtual_base_addr) { + VIDC_LOGERR_STRING("SPURIOUS_INTERRUPT"); + return; + } + vidc_720p_get_interrupt_status(&ddl_context->intr_status, + &ddl_context->cmd_err_status, + &ddl_context->disp_pic_err_status, + &ddl_context->op_failed + ); + + vidc_720p_interrupt_done_clear(); + +} + +u32 ddl_process_core_response(void) +{ + struct ddl_context *ddl_context; + u32 return_status = true; + + ddl_context = ddl_get_context(); + if (!ddl_context->core_virtual_base_addr) { + VIDC_LOGERR_STRING("UNKWN_INTR"); + return false; + } + + if (!ddl_handle_core_errors(ddl_context)) { + return_status = ddl_process_intr_status(ddl_context, + ddl_context->intr_status); + } + + if (ddl_context->interrupt_clr) + (*ddl_context->interrupt_clr)(); + + return return_status; +} + +static void ddl_decoder_input_done_callback( + struct ddl_client_context *ddl, u32 frame_transact_end) +{ + struct vidc_720p_dec_disp_info *dec_disp_info = + &(ddl->codec_data.decoder.dec_disp_info); + struct vcd_frame_data *input_vcd_frm = + &(ddl->input_frame.vcd_frm); + ddl_get_frame(input_vcd_frm, dec_disp_info-> + input_frame); + + input_vcd_frm->interlaced = (dec_disp_info-> + input_is_interlace); + + input_vcd_frm->offset += dec_disp_info->input_bytes_consumed; + input_vcd_frm->data_len -= dec_disp_info->input_bytes_consumed; + + ddl->input_frame.frm_trans_end = frame_transact_end; + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, DEC_IP_TIME); + ddl->ddl_context->ddl_callback( + VCD_EVT_RESP_INPUT_DONE, + VCD_S_SUCCESS, + &ddl->input_frame, + sizeof(struct ddl_frame_data_tag), + (void *)ddl, + ddl->ddl_context->client_data); +} + +static u32 ddl_decoder_output_done_callback( + struct ddl_client_context *ddl, + u32 frame_transact_end) +{ + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vidc_720p_dec_disp_info *dec_disp_info = + &(decoder->dec_disp_info); + struct ddl_frame_data_tag *output_frame = + &ddl->output_frame; + struct vcd_frame_data *output_vcd_frm = + &(output_frame->vcd_frm); + u32 vcd_status; + u32 free_luma_dpb = 0; + + output_vcd_frm->physical = (u8 *)dec_disp_info->y_addr; + + if (decoder->codec.codec == VCD_CODEC_MPEG4 || + decoder->codec.codec == VCD_CODEC_VC1 || + decoder->codec.codec == VCD_CODEC_VC1_RCV || + (decoder->codec.codec >= VCD_CODEC_DIVX_3 && + decoder->codec.codec <= VCD_CODEC_XVID)){ + vidc_720p_decode_skip_frm_details(&free_luma_dpb); + if (free_luma_dpb) + output_vcd_frm->physical = (u8 *) free_luma_dpb; + } + + + vcd_status = ddl_decoder_dpb_transact( + decoder, + output_frame, + DDL_DPB_OP_MARK_BUSY); + + if (vcd_status != VCD_S_SUCCESS) { + VIDC_LOGERR_STRING("CorruptedOutputBufferAddress"); + ddl_hw_fatal_cb(ddl->ddl_context); + return vcd_status; + } + + output_vcd_frm->ip_frm_tag = dec_disp_info->tag_top; + if (dec_disp_info->crop_exists == 0x1) { + output_vcd_frm->dec_op_prop.disp_frm.left = + dec_disp_info->crop_left_offset; + output_vcd_frm->dec_op_prop.disp_frm.top = + dec_disp_info->crop_top_offset; + output_vcd_frm->dec_op_prop.disp_frm.right = + dec_disp_info->img_size_x - + dec_disp_info->crop_right_offset; + output_vcd_frm->dec_op_prop.disp_frm.bottom = + dec_disp_info->img_size_y - + dec_disp_info->crop_bottom_offset; + } else { + output_vcd_frm->dec_op_prop.disp_frm.left = 0; + output_vcd_frm->dec_op_prop.disp_frm.top = 0; + output_vcd_frm->dec_op_prop.disp_frm.right = + dec_disp_info->img_size_x; + output_vcd_frm->dec_op_prop.disp_frm.bottom = + dec_disp_info->img_size_y; + } + if (!dec_disp_info->disp_is_interlace) { + output_vcd_frm->interlaced = false; + output_vcd_frm->intrlcd_ip_frm_tag = VCD_FRAMETAG_INVALID; + } else { + output_vcd_frm->interlaced = true; + output_vcd_frm->intrlcd_ip_frm_tag = + dec_disp_info->tag_bottom; + } + + output_vcd_frm->offset = 0; + output_vcd_frm->data_len = decoder->y_cb_cr_size; + if (free_luma_dpb) { + output_vcd_frm->data_len = 0; + output_vcd_frm->flags |= VCD_FRAME_FLAG_DECODEONLY; + } + output_vcd_frm->flags |= VCD_FRAME_FLAG_ENDOFFRAME; + ddl_process_decoder_metadata(ddl); + output_frame->frm_trans_end = frame_transact_end; + + if (vidc_msg_timing) + ddl_calc_core_proc_time(__func__, DEC_OP_TIME); + + ddl->ddl_context->ddl_callback( + VCD_EVT_RESP_OUTPUT_DONE, + vcd_status, + output_frame, + sizeof(struct ddl_frame_data_tag), + (void *)ddl, + ddl->ddl_context->client_data); + return vcd_status; +} + +static u32 ddl_get_frame + (struct vcd_frame_data *frame, u32 frametype) { + enum vidc_720p_frame vidc_frame = + (enum vidc_720p_frame)frametype; + u32 status = true; + + switch (vidc_frame) { + case VIDC_720P_IFRAME: + { + frame->flags |= VCD_FRAME_FLAG_SYNCFRAME; + frame->frame = VCD_FRAME_I; + break; + } + case VIDC_720P_PFRAME: + { + frame->frame = VCD_FRAME_P; + break; + } + case VIDC_720P_BFRAME: + { + frame->frame = VCD_FRAME_B; + break; + } + case VIDC_720P_NOTCODED: + { + frame->frame = VCD_FRAME_NOTCODED; + frame->data_len = 0; + break; + } + default: + { + VIDC_LOG_STRING("CRITICAL-FRAMETYPE"); + status = false; + break; + } + } + return status; +} + +static void ddl_getmpeg4_declevel(enum vcd_codec_level *codec_level, + u32 level) +{ + switch (level) { + case VIDC_720P_MPEG4_LEVEL0: + { + *codec_level = VCD_LEVEL_MPEG4_0; + break; + } + case VIDC_720P_MPEG4_LEVEL0b: + { + *codec_level = VCD_LEVEL_MPEG4_0b; + break; + } + case VIDC_720P_MPEG4_LEVEL1: + { + *codec_level = VCD_LEVEL_MPEG4_1; + break; + } + case VIDC_720P_MPEG4_LEVEL2: + { + *codec_level = VCD_LEVEL_MPEG4_2; + break; + } + case VIDC_720P_MPEG4_LEVEL3: + { + *codec_level = VCD_LEVEL_MPEG4_3; + break; + } + case VIDC_720P_MPEG4_LEVEL3b: + { + *codec_level = VCD_LEVEL_MPEG4_3b; + break; + } + case VIDC_720P_MPEG4_LEVEL4a: + { + *codec_level = VCD_LEVEL_MPEG4_4a; + break; + } + case VIDC_720P_MPEG4_LEVEL5: + { + *codec_level = VCD_LEVEL_MPEG4_5; + break; + } + case VIDC_720P_MPEG4_LEVEL6: + { + *codec_level = VCD_LEVEL_MPEG4_6; + break; + } + } +} + +static void ddl_geth264_declevel(enum vcd_codec_level *codec_level, + u32 level) +{ + switch (level) { + case VIDC_720P_H264_LEVEL1: + { + *codec_level = VCD_LEVEL_H264_1; + break; + } + case VIDC_720P_H264_LEVEL1b: + { + *codec_level = VCD_LEVEL_H264_1b; + break; + } + case VIDC_720P_H264_LEVEL1p1: + { + *codec_level = VCD_LEVEL_H264_1p1; + break; + } + case VIDC_720P_H264_LEVEL1p2: + { + *codec_level = VCD_LEVEL_H264_1p2; + break; + } + case VIDC_720P_H264_LEVEL1p3: + { + *codec_level = VCD_LEVEL_H264_1p3; + break; + } + case VIDC_720P_H264_LEVEL2: + { + *codec_level = VCD_LEVEL_H264_2; + break; + } + case VIDC_720P_H264_LEVEL2p1: + { + *codec_level = VCD_LEVEL_H264_2p1; + break; + } + case VIDC_720P_H264_LEVEL2p2: + { + *codec_level = VCD_LEVEL_H264_2p2; + break; + } + case VIDC_720P_H264_LEVEL3: + { + *codec_level = VCD_LEVEL_H264_3; + break; + } + case VIDC_720P_H264_LEVEL3p1: + { + *codec_level = VCD_LEVEL_H264_3p1; + break; + } + case VIDC_720P_H264_LEVEL3p2: + { + *codec_level = VCD_LEVEL_H264_3p2; + break; + } + + } +} + +static void ddl_get_vc1_dec_level( + enum vcd_codec_level *codec_level, u32 level, + enum vcd_codec_profile vc1_profile) +{ + if (vc1_profile == VCD_PROFILE_VC1_ADVANCE) { + switch (level) { + case VIDC_720P_VC1_LEVEL0: + { + *codec_level = VCD_LEVEL_VC1_A_0; + break; + } + case VIDC_720P_VC1_LEVEL1: + { + *codec_level = VCD_LEVEL_VC1_A_1; + break; + } + case VIDC_720P_VC1_LEVEL2: + { + *codec_level = VCD_LEVEL_VC1_A_2; + break; + } + case VIDC_720P_VC1_LEVEL3: + { + *codec_level = VCD_LEVEL_VC1_A_3; + break; + } + case VIDC_720P_VC1_LEVEL4: + { + *codec_level = VCD_LEVEL_VC1_A_4; + break; + } + } + return; + } else if (vc1_profile == VCD_PROFILE_VC1_MAIN) { + switch (level) { + case VIDC_720P_VC1_LEVEL_LOW: + { + *codec_level = VCD_LEVEL_VC1_M_LOW; + break; + } + case VIDC_720P_VC1_LEVEL_MED: + { + *codec_level = VCD_LEVEL_VC1_M_MEDIUM; + break; + } + case VIDC_720P_VC1_LEVEL_HIGH: + { + *codec_level = VCD_LEVEL_VC1_M_HIGH; + break; + } + } + } else if (vc1_profile == VCD_PROFILE_VC1_SIMPLE) { + switch (level) { + case VIDC_720P_VC1_LEVEL_LOW: + { + *codec_level = VCD_LEVEL_VC1_S_LOW; + break; + } + case VIDC_720P_VC1_LEVEL_MED: + { + *codec_level = VCD_LEVEL_VC1_S_MEDIUM; + break; + } + } + } +} + +static void ddl_get_mpeg2_dec_level(enum vcd_codec_level *codec_level, + u32 level) +{ + switch (level) { + case VIDCL_720P_MPEG2_LEVEL_LOW: + { + *codec_level = VCD_LEVEL_MPEG2_LOW; + break; + } + case VIDCL_720P_MPEG2_LEVEL_MAIN: + { + *codec_level = VCD_LEVEL_MPEG2_MAIN; + break; + } + case VIDCL_720P_MPEG2_LEVEL_HIGH14: + { + *codec_level = VCD_LEVEL_MPEG2_HIGH_14; + break; + } + } +} + +static void ddl_getdec_profilelevel(struct ddl_decoder_data *decoder, + u32 profile, u32 level) +{ + enum vcd_codec_profile codec_profile = VCD_PROFILE_UNKNOWN; + enum vcd_codec_level codec_level = VCD_LEVEL_UNKNOWN; + + switch (decoder->codec.codec) { + case VCD_CODEC_MPEG4: + { + if (profile == VIDC_720P_PROFILE_MPEG4_SP) + codec_profile = VCD_PROFILE_MPEG4_SP; + else if (profile == VIDC_720P_PROFILE_MPEG4_ASP) + codec_profile = VCD_PROFILE_MPEG4_ASP; + + ddl_getmpeg4_declevel(&codec_level, level); + break; + } + case VCD_CODEC_H264: + { + if (profile == VIDC_720P_PROFILE_H264_BASELINE) + codec_profile = VCD_PROFILE_H264_BASELINE; + else if (profile == VIDC_720P_PROFILE_H264_MAIN) + codec_profile = VCD_PROFILE_H264_MAIN; + else if (profile == VIDC_720P_PROFILE_H264_HIGH) + codec_profile = VCD_PROFILE_H264_HIGH; + ddl_geth264_declevel(&codec_level, level); + break; + } + default: + case VCD_CODEC_H263: + { + break; + } + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + { + if (profile == VIDC_720P_PROFILE_VC1_SP) + codec_profile = VCD_PROFILE_VC1_SIMPLE; + else if (profile == VIDC_720P_PROFILE_VC1_MAIN) + codec_profile = VCD_PROFILE_VC1_MAIN; + else if (profile == VIDC_720P_PROFILE_VC1_ADV) + codec_profile = VCD_PROFILE_VC1_ADVANCE; + ddl_get_vc1_dec_level(&codec_level, level, profile); + break; + } + case VCD_CODEC_MPEG2: + { + if (profile == VIDC_720P_PROFILE_MPEG2_MAIN) + codec_profile = VCD_PROFILE_MPEG2_MAIN; + else if (profile == VIDC_720P_PROFILE_MPEG2_SP) + codec_profile = VCD_PROFILE_MPEG2_SIMPLE; + ddl_get_mpeg2_dec_level(&codec_level, level); + break; + } + } + + decoder->profile.profile = codec_profile; + decoder->level.level = codec_level; +} diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_metadata.c b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_metadata.c new file mode 100644 index 00000000000..376ea6d4b8f --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_metadata.c @@ -0,0 +1,580 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd_ddl_utils.h" +#include "vcd_ddl_metadata.h" + +static u32 *ddl_metadata_hdr_entry(struct ddl_client_context *ddl, + u32 meta_data) +{ + u32 skip_words = 0; + u32 *buffer; + + if (ddl->decoding) { + buffer = (u32 *) + ddl->codec_data.decoder.meta_data_input. + align_virtual_addr; + skip_words = 32 + 1; + buffer += skip_words; + + switch (meta_data) { + default: + case VCD_METADATA_DATANONE: + { + skip_words = 0; + break; + } + case VCD_METADATA_QPARRAY: + { + skip_words = 3; + break; + } + case VCD_METADATA_CONCEALMB: + { + skip_words = 6; + break; + } + case VCD_METADATA_VC1: + { + skip_words = 9; + break; + } + case VCD_METADATA_SEI: + { + skip_words = 12; + break; + } + case VCD_METADATA_VUI: + { + skip_words = 15; + break; + } + case VCD_METADATA_PASSTHROUGH: + { + skip_words = 18; + break; + } + case VCD_METADATA_QCOMFILLER: + { + skip_words = 21; + break; + } + } + } else { + buffer = (u32 *) + ddl->codec_data.encoder.meta_data_input. + align_virtual_addr; + skip_words = 2; + buffer += skip_words; + + switch (meta_data) { + default: + case VCD_METADATA_DATANONE: + { + skip_words = 0; + break; + } + case VCD_METADATA_ENC_SLICE: + { + skip_words = 3; + break; + } + case VCD_METADATA_QCOMFILLER: + { + skip_words = 6; + break; + } + } + + } + + buffer += skip_words; + return buffer; +} + +void ddl_set_default_meta_data_hdr(struct ddl_client_context *ddl) +{ + struct ddl_buf_addr *main_buffer = + &ddl->ddl_context->metadata_shared_input; + struct ddl_buf_addr *client_buffer; + u32 *hdr_entry; + + if (ddl->decoding) + client_buffer = &(ddl->codec_data.decoder.meta_data_input); + else + client_buffer = &(ddl->codec_data.encoder.meta_data_input); + + DDL_METADATA_CLIENT_INPUTBUF(main_buffer, client_buffer, + ddl->channel_id); + + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_QCOMFILLER); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_QCOMFILLER; + + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_DATANONE); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_DATANONE; + + if (ddl->decoding) { + hdr_entry = + ddl_metadata_hdr_entry(ddl, VCD_METADATA_QPARRAY); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_QPARRAY; + + hdr_entry = + ddl_metadata_hdr_entry(ddl, VCD_METADATA_CONCEALMB); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_CONCEALMB; + + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_SEI); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_SEI; + + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_VUI); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_VUI; + + hdr_entry = ddl_metadata_hdr_entry(ddl, VCD_METADATA_VC1); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = VCD_METADATA_VC1; + + hdr_entry = + ddl_metadata_hdr_entry(ddl, VCD_METADATA_PASSTHROUGH); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = + VCD_METADATA_PASSTHROUGH; + + } else { + hdr_entry = + ddl_metadata_hdr_entry(ddl, VCD_METADATA_ENC_SLICE); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = 0x00000101; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = 1; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = + VCD_METADATA_ENC_SLICE; + } +} + +static u32 ddl_supported_metadata_flag(struct ddl_client_context *ddl) +{ + u32 flag = 0; + + if (ddl->decoding) { + enum vcd_codec codec = + ddl->codec_data.decoder.codec.codec; + + flag |= (VCD_METADATA_CONCEALMB | + VCD_METADATA_PASSTHROUGH | VCD_METADATA_QPARRAY); + if (codec == VCD_CODEC_H264) { + flag |= (VCD_METADATA_SEI | VCD_METADATA_VUI); + } else if (codec == VCD_CODEC_VC1 || + codec == VCD_CODEC_VC1_RCV) { + flag |= VCD_METADATA_VC1; + } + } else { + flag |= VCD_METADATA_ENC_SLICE; + } + + return flag; +} + +void ddl_set_default_metadata_flag(struct ddl_client_context *ddl) +{ + if (ddl->decoding) + ddl->codec_data.decoder.meta_data_enable_flag = 0; + else + ddl->codec_data.encoder.meta_data_enable_flag = 0; +} + +void ddl_set_default_decoder_metadata_buffer_size( + struct ddl_decoder_data *decoder, + struct vcd_property_frame_size *frame_size, + struct vcd_buffer_requirement *output_buf_req) +{ + u32 flag = decoder->meta_data_enable_flag; + u32 suffix = 0; + size_t sz = 0; + + if (!flag) { + decoder->suffix = 0; + return; + } + + if (flag & VCD_METADATA_QPARRAY) { + u32 num_of_mb = + ((frame_size->width * frame_size->height) >> 8); + sz = DDL_METADATA_HDR_SIZE; + sz += num_of_mb; + DDL_METADATA_ALIGNSIZE(sz); + suffix += sz; + } + if (flag & VCD_METADATA_CONCEALMB) { + u32 num_of_mb = + ((frame_size->width * frame_size->height) >> 8); + sz = DDL_METADATA_HDR_SIZE + (num_of_mb >> 3); + DDL_METADATA_ALIGNSIZE(sz); + suffix += sz; + } + if (flag & VCD_METADATA_VC1) { + sz = DDL_METADATA_HDR_SIZE; + sz += DDL_METADATA_VC1_PAYLOAD_SIZE; + DDL_METADATA_ALIGNSIZE(sz); + suffix += sz; + } + if (flag & VCD_METADATA_SEI) { + sz = DDL_METADATA_HDR_SIZE; + sz += DDL_METADATA_SEI_PAYLOAD_SIZE; + DDL_METADATA_ALIGNSIZE(sz); + suffix += (sz * DDL_METADATA_SEI_MAX); + } + if (flag & VCD_METADATA_VUI) { + sz = DDL_METADATA_HDR_SIZE; + sz += DDL_METADATA_VUI_PAYLOAD_SIZE; + DDL_METADATA_ALIGNSIZE(sz); + suffix += (sz); + } + if (flag & VCD_METADATA_PASSTHROUGH) { + sz = DDL_METADATA_HDR_SIZE; + sz += DDL_METADATA_PASSTHROUGH_PAYLOAD_SIZE; + DDL_METADATA_ALIGNSIZE(sz); + suffix += (sz); + } + sz = DDL_METADATA_EXTRADATANONE_SIZE; + DDL_METADATA_ALIGNSIZE(sz); + suffix += (sz); + + suffix += DDL_METADATA_EXTRAPAD_SIZE; + DDL_METADATA_ALIGNSIZE(suffix); + + decoder->suffix = suffix; + output_buf_req->sz += suffix; + return; +} + +void ddl_set_default_encoder_metadata_buffer_size(struct ddl_encoder_data + *encoder) +{ + u32 flag = encoder->meta_data_enable_flag; + u32 suffix = 0; + size_t sz = 0; + + if (!flag) { + encoder->suffix = 0; + return; + } + + if (flag & VCD_METADATA_ENC_SLICE) { + u32 num_of_mb = (encoder->frame_size.width * + encoder->frame_size.height / 16 / 16); + sz = DDL_METADATA_HDR_SIZE; + + sz += 4; + + sz += (8 * num_of_mb); + DDL_METADATA_ALIGNSIZE(sz); + suffix += sz; + } + + sz = DDL_METADATA_EXTRADATANONE_SIZE; + DDL_METADATA_ALIGNSIZE(sz); + suffix += (sz); + + suffix += DDL_METADATA_EXTRAPAD_SIZE; + DDL_METADATA_ALIGNSIZE(suffix); + + encoder->suffix = suffix; + encoder->output_buf_req.sz += suffix; +} + +u32 ddl_set_metadata_params(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, + void *property_value) +{ + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + if (property_hdr->prop_id == VCD_I_METADATA_ENABLE) { + struct vcd_property_meta_data_enable *meta_data_enable = + (struct vcd_property_meta_data_enable *) + property_value; + u32 *meta_data_enable_flag; + enum vcd_codec codec; + if (ddl->decoding) { + meta_data_enable_flag = + &(ddl->codec_data.decoder. + meta_data_enable_flag); + codec = ddl->codec_data.decoder.codec.codec; + } else { + meta_data_enable_flag = + &(ddl->codec_data.encoder. + meta_data_enable_flag); + codec = ddl->codec_data.encoder.codec.codec; + } + if (sizeof(struct vcd_property_meta_data_enable) == + property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && + codec) { + u32 flag = ddl_supported_metadata_flag(ddl); + flag &= (meta_data_enable->meta_data_enable_flag); + if (flag) + flag |= DDL_METADATA_MANDATORY; + if (flag != *meta_data_enable_flag) { + *meta_data_enable_flag = flag; + if (ddl->decoding) { + ddl_set_default_decoder_buffer_req + (&ddl->codec_data.decoder, + true); + } else { + ddl_set_default_encoder_buffer_req + (&ddl->codec_data.encoder); + } + } + vcd_status = VCD_S_SUCCESS; + } + } else if (property_hdr->prop_id == VCD_I_METADATA_HEADER) { + struct vcd_property_metadata_hdr *hdr = + (struct vcd_property_metadata_hdr *)property_value; + if (sizeof(struct vcd_property_metadata_hdr) == + property_hdr->sz) { + u32 flag = ddl_supported_metadata_flag(ddl); + flag |= DDL_METADATA_MANDATORY; + flag &= hdr->meta_data_id; + if (!(flag & (flag - 1))) { + u32 *hdr_entry = + ddl_metadata_hdr_entry(ddl, flag); + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX] = + hdr->version; + hdr_entry[DDL_METADATA_HDR_PORT_INDEX] = + hdr->port_index; + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX] = + hdr->type; + vcd_status = VCD_S_SUCCESS; + } + } + } + return vcd_status; +} + +u32 ddl_get_metadata_params(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, + void *property_value) +{ + u32 vcd_status = VCD_ERR_ILLEGAL_PARM ; + if (property_hdr->prop_id == VCD_I_METADATA_ENABLE && + sizeof(struct vcd_property_meta_data_enable) + == property_hdr->sz) { + struct vcd_property_meta_data_enable *meta_data_enable = + (struct vcd_property_meta_data_enable *) + property_value; + meta_data_enable->meta_data_enable_flag = + ((ddl->decoding) ? + (ddl->codec_data.decoder.meta_data_enable_flag) + : (ddl->codec_data.encoder.meta_data_enable_flag)); + vcd_status = VCD_S_SUCCESS; + } else if (property_hdr->prop_id == VCD_I_METADATA_HEADER && + sizeof(struct vcd_property_metadata_hdr) == + property_hdr->sz) { + struct vcd_property_metadata_hdr *hdr = + (struct vcd_property_metadata_hdr *) + property_value; + u32 flag = ddl_supported_metadata_flag(ddl); + flag |= DDL_METADATA_MANDATORY; + flag &= hdr->meta_data_id; + if (!(flag & (flag - 1))) { + u32 *hdr_entry = ddl_metadata_hdr_entry(ddl, + flag); + hdr->version = + hdr_entry[DDL_METADATA_HDR_VERSION_INDEX]; + hdr->port_index = + hdr_entry[DDL_METADATA_HDR_PORT_INDEX]; + hdr->type = + hdr_entry[DDL_METADATA_HDR_TYPE_INDEX]; + vcd_status = VCD_S_SUCCESS; + } + } + return vcd_status; +} + +void ddl_metadata_enable(struct ddl_client_context *ddl) +{ + u32 flag, hal_flag = 0; + u32 *metadata_input; + if (ddl->decoding) { + flag = ddl->codec_data.decoder.meta_data_enable_flag; + metadata_input = + ddl->codec_data.decoder.meta_data_input. + align_physical_addr; + } else { + flag = ddl->codec_data.encoder.meta_data_enable_flag; + metadata_input = + ddl->codec_data.encoder.meta_data_input. + align_physical_addr; + } + if (flag) { + if (flag & VCD_METADATA_QPARRAY) + hal_flag |= VIDC_720P_METADATA_ENABLE_QP; + if (flag & VCD_METADATA_CONCEALMB) + hal_flag |= VIDC_720P_METADATA_ENABLE_CONCEALMB; + if (flag & VCD_METADATA_VC1) + hal_flag |= VIDC_720P_METADATA_ENABLE_VC1; + if (flag & VCD_METADATA_SEI) + hal_flag |= VIDC_720P_METADATA_ENABLE_SEI; + if (flag & VCD_METADATA_VUI) + hal_flag |= VIDC_720P_METADATA_ENABLE_VUI; + if (flag & VCD_METADATA_ENC_SLICE) + hal_flag |= VIDC_720P_METADATA_ENABLE_ENCSLICE; + if (flag & VCD_METADATA_PASSTHROUGH) + hal_flag |= VIDC_720P_METADATA_ENABLE_PASSTHROUGH; + } else { + metadata_input = 0; + } + vidc_720p_metadata_enable(hal_flag, metadata_input); +} + +u32 ddl_encode_set_metadata_output_buf(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &ddl->codec_data.encoder; + u32 *buffer; + struct vcd_frame_data *stream = &(ddl->output_frame.vcd_frm); + u32 ext_buffer_end, hw_metadata_start; + + ext_buffer_end = (u32) stream->physical + stream->alloc_len; + if (!encoder->meta_data_enable_flag) { + ext_buffer_end &= ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + return ext_buffer_end; + } + hw_metadata_start = (ext_buffer_end - encoder->suffix) & + ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + + ext_buffer_end = (hw_metadata_start - 1) & + ~(DDL_STREAMBUF_ALIGN_GUARD_BYTES); + + buffer = encoder->meta_data_input.align_virtual_addr; + + *buffer++ = encoder->suffix; + + *buffer = hw_metadata_start; + + encoder->meta_data_offset = + hw_metadata_start - (u32) stream->physical; + + return ext_buffer_end; +} + +void ddl_decode_set_metadata_output(struct ddl_decoder_data *decoder) +{ + u32 *buffer; + u32 loopc; + + if (!decoder->meta_data_enable_flag) { + decoder->meta_data_offset = 0; + return; + } + + decoder->meta_data_offset = ddl_get_yuv_buffer_size( + &decoder->client_frame_size, &decoder->buf_format, + (!decoder->progressive_only), decoder->codec.codec); + + buffer = decoder->meta_data_input.align_virtual_addr; + + *buffer++ = decoder->suffix; + + for (loopc = 0; loopc < decoder->dp_buf.no_of_dec_pic_buf; + ++loopc) { + *buffer++ = (u32) (decoder->meta_data_offset + (u8 *) + decoder->dp_buf. + dec_pic_buffers[loopc].vcd_frm. + physical); + } +} + +void ddl_process_encoder_metadata(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + struct vcd_frame_data *out_frame = + &(ddl->output_frame.vcd_frm); + u32 *qfiller_hdr, *qfiller, start_addr; + u32 qfiller_size; + + if (!encoder->meta_data_enable_flag) { + out_frame->flags &= ~(VCD_FRAME_FLAG_EXTRADATA); + return; + } + + if (!encoder->enc_frame_info.metadata_exists) { + out_frame->flags &= ~(VCD_FRAME_FLAG_EXTRADATA); + return; + } + out_frame->flags |= VCD_FRAME_FLAG_EXTRADATA; + + start_addr = (u32) ((u8 *) out_frame->virtual + + out_frame->offset); + qfiller = (u32 *) ((out_frame->data_len + start_addr + 3) & ~3); + + qfiller_size = (u32) ((encoder->meta_data_offset + + (u8 *) out_frame->virtual) - + (u8 *) qfiller); + + qfiller_hdr = ddl_metadata_hdr_entry(ddl, VCD_METADATA_QCOMFILLER); + + *qfiller++ = qfiller_size; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_VERSION_INDEX]; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_PORT_INDEX]; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_TYPE_INDEX]; + *qfiller = (u32) (qfiller_size - DDL_METADATA_HDR_SIZE); +} + +void ddl_process_decoder_metadata(struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + struct vcd_frame_data *output_frame = + &(ddl->output_frame.vcd_frm); + u32 *qfiller_hdr, *qfiller; + u32 qfiller_size; + + if (!decoder->meta_data_enable_flag) { + output_frame->flags &= ~(VCD_FRAME_FLAG_EXTRADATA); + return; + } + + if (!decoder->dec_disp_info.metadata_exists) { + output_frame->flags &= ~(VCD_FRAME_FLAG_EXTRADATA); + return; + } + output_frame->flags |= VCD_FRAME_FLAG_EXTRADATA; + + if (output_frame->data_len != decoder->meta_data_offset) { + qfiller = (u32 *) ((u32) ((output_frame->data_len + + output_frame->offset + + (u8 *) output_frame->virtual) + + 3) & ~3); + + qfiller_size = (u32) ((decoder->meta_data_offset + + (u8 *) output_frame->virtual) - + (u8 *) qfiller); + + qfiller_hdr = + ddl_metadata_hdr_entry(ddl, VCD_METADATA_QCOMFILLER); + *qfiller++ = qfiller_size; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_VERSION_INDEX]; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_PORT_INDEX]; + *qfiller++ = qfiller_hdr[DDL_METADATA_HDR_TYPE_INDEX]; + *qfiller = (u32) (qfiller_size - DDL_METADATA_HDR_SIZE); + } +} diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_metadata.h b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_metadata.h new file mode 100644 index 00000000000..ed43861c225 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_metadata.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_DDL_METADATA_H_ +#define _VCD_DDL_METADATA_H_ + +#define DDL_MAX_DEC_METADATATYPE (8) +#define DDL_MAX_ENC_METADATATYPE (3) + +#define DDL_METADATA_EXTRAPAD_SIZE (256) +#define DDL_METADATA_HDR_SIZE (20) + +#define DDL_METADATA_EXTRADATANONE_SIZE (24) + +#define DDL_METADATA_ALIGNSIZE(x) ((x) = (((x) + 0x7) & ~0x7)) + +#define DDL_METADATA_MANDATORY (VCD_METADATA_DATANONE | \ + VCD_METADATA_QCOMFILLER) + +#define DDL_METADATA_VC1_PAYLOAD_SIZE (38*4) + +#define DDL_METADATA_SEI_PAYLOAD_SIZE (100) +#define DDL_METADATA_SEI_MAX (5) + +#define DDL_METADATA_VUI_PAYLOAD_SIZE (256) + +#define DDL_METADATA_PASSTHROUGH_PAYLOAD_SIZE (68) + +#define DDL_METADATA_CLIENT_INPUTBUFSIZE (256) +#define DDL_METADATA_TOTAL_INPUTBUFSIZE \ + (DDL_METADATA_CLIENT_INPUTBUFSIZE * VCD_MAX_NO_CLIENT) + +#define DDL_METADATA_CLIENT_INPUTBUF(main_buffer, client_buffer, \ + channel_id) \ +{ \ + (client_buffer)->align_physical_addr = (u32 *)\ + ((u8 *)(main_buffer)->align_physical_addr + \ + (DDL_METADATA_CLIENT_INPUTBUFSIZE * (channel_id)) \ + ); \ + (client_buffer)->align_virtual_addr = (u32 *)\ + ((u8 *)(main_buffer)->align_virtual_addr + \ + (DDL_METADATA_CLIENT_INPUTBUFSIZE * (channel_id)) \ + ); \ + (client_buffer)->virtual_base_addr = 0; \ +} + +#define DDL_METADATA_HDR_VERSION_INDEX 0 +#define DDL_METADATA_HDR_PORT_INDEX 1 +#define DDL_METADATA_HDR_TYPE_INDEX 2 + + +void ddl_set_default_meta_data_hdr(struct ddl_client_context *ddl); +u32 ddl_get_metadata_params(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value); +u32 ddl_set_metadata_params(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, + void *property_value); +void ddl_set_default_metadata_flag(struct ddl_client_context *ddl); +void ddl_set_default_decoder_metadata_buffer_size + (struct ddl_decoder_data *decoder, + struct vcd_property_frame_size *frame_size, + struct vcd_buffer_requirement *output_buf_req); +void ddl_set_default_encoder_metadata_buffer_size(struct ddl_encoder_data + *encoder); +void ddl_metadata_enable(struct ddl_client_context *ddl); +u32 ddl_encode_set_metadata_output_buf(struct ddl_client_context *ddl); +void ddl_decode_set_metadata_output(struct ddl_decoder_data *decoder); +void ddl_process_encoder_metadata(struct ddl_client_context *ddl); +void ddl_process_decoder_metadata(struct ddl_client_context *ddl); +#endif diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_properties.c b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_properties.c new file mode 100644 index 00000000000..73dba03c8b9 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_properties.c @@ -0,0 +1,1919 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd_ddl_utils.h" +#include "vcd_ddl_metadata.h" + +static u32 ddl_set_dec_property(struct ddl_client_context *pddl, + struct vcd_property_hdr *property_hdr, + void *property_value); +static u32 ddl_set_enc_property(struct ddl_client_context *pddl, + struct vcd_property_hdr *property_hdr, + void *property_value); +static u32 ddl_get_dec_property(struct ddl_client_context *pddl, + struct vcd_property_hdr *property_hdr, + void *property_value); +static u32 ddl_get_enc_property(struct ddl_client_context *pddl, + struct vcd_property_hdr *property_hdr, + void *property_value); +static u32 ddl_set_enc_dynamic_property(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, + void *property_value); +static void ddl_set_default_enc_property(struct ddl_client_context *ddl); +static void ddl_set_default_enc_profile(struct ddl_encoder_data + *encoder); +static void ddl_set_default_enc_level(struct ddl_encoder_data *encoder); +static void ddl_set_default_enc_vop_timing(struct ddl_encoder_data + *encoder); +static void ddl_set_default_enc_intra_period(struct ddl_encoder_data + *encoder); +static void ddl_set_default_enc_rc_params(struct ddl_encoder_data + *encoder); +static u32 ddl_valid_buffer_requirement(struct vcd_buffer_requirement + *original_buf_req, + struct vcd_buffer_requirement + *req_buf_req); +static u32 ddl_decoder_min_num_dpb(struct ddl_decoder_data *decoder); +static u32 ddl_set_dec_buffers + (struct ddl_decoder_data *decoder, + struct ddl_property_dec_pic_buffers *dpb); + +u32 ddl_set_property(u32 *ddl_handle, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + u32 vcd_status; + struct ddl_context *ddl_context; + struct ddl_client_context *ddl = + (struct ddl_client_context *)ddl_handle; + + if (!property_hdr || !property_value) { + VIDC_LOGERR_STRING("ddl_set_prop:Bad_argument"); + return VCD_ERR_ILLEGAL_PARM; + } + ddl_context = ddl_get_context(); + + if (!DDL_IS_INITIALIZED(ddl_context)) { + VIDC_LOGERR_STRING("ddl_set_prop:Not_inited"); + return VCD_ERR_ILLEGAL_OP; + } + + if (!ddl) { + VIDC_LOGERR_STRING("ddl_set_prop:Bad_handle"); + return VCD_ERR_BAD_HANDLE; + } + if (ddl->decoding) { + vcd_status = + ddl_set_dec_property(ddl, property_hdr, + property_value); + } else { + vcd_status = + ddl_set_enc_property(ddl, property_hdr, + property_value); + } + if (vcd_status) + VIDC_LOGERR_STRING("ddl_set_prop:FAILED"); + + return vcd_status; +} + +u32 ddl_get_property(u32 *ddl_handle, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + struct ddl_context *ddl_context; + struct ddl_client_context *ddl = + (struct ddl_client_context *)ddl_handle; + + if (!property_hdr || !property_value) + return VCD_ERR_ILLEGAL_PARM; + + if (property_hdr->prop_id == DDL_I_CAPABILITY) { + if (sizeof(struct ddl_property_capability) == + property_hdr->sz) { + struct ddl_property_capability *ddl_capability = + (struct ddl_property_capability *) + property_value; + ddl_capability->max_num_client = VCD_MAX_NO_CLIENT; + ddl_capability->exclusive = + VCD_COMMAND_EXCLUSIVE; + ddl_capability->frame_command_depth = + VCD_FRAME_COMMAND_DEPTH; + ddl_capability->general_command_depth = + VCD_GENERAL_COMMAND_DEPTH; + ddl_capability->ddl_time_out_in_ms = + DDL_HW_TIMEOUT_IN_MS; + vcd_status = VCD_S_SUCCESS; + } + return vcd_status; + } + ddl_context = ddl_get_context(); + if (!DDL_IS_INITIALIZED(ddl_context)) + return VCD_ERR_ILLEGAL_OP; + + if (!ddl) + return VCD_ERR_BAD_HANDLE; + + if (ddl->decoding) { + vcd_status = + ddl_get_dec_property(ddl, property_hdr, + property_value); + } else { + vcd_status = + ddl_get_enc_property(ddl, property_hdr, + property_value); + } + if (vcd_status) + VIDC_LOGERR_STRING("ddl_get_prop:FAILED"); + + return vcd_status; +} + +u32 ddl_decoder_ready_to_start(struct ddl_client_context *ddl, + struct vcd_sequence_hdr *header) +{ + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + if (!decoder->codec.codec) { + VIDC_LOGERR_STRING("ddl_dec_start_check:Codec_not_set"); + return false; + } + if ((!header) && + (!decoder->client_frame_size.height || + !decoder->client_frame_size.width) + ) { + VIDC_LOGERR_STRING + ("ddl_dec_start_check:Client_height_width_default"); + return false; + } + return true; +} + +u32 ddl_encoder_ready_to_start(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + + if (!encoder->codec.codec || + !encoder->frame_size.height || + !encoder->frame_size.width || + !encoder->frame_rate.fps_denominator || + !encoder->frame_rate.fps_numerator || + !encoder->target_bit_rate.target_bitrate) { + return false; + } + return true; +} + +static u32 ddl_set_dec_property + (struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) { + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + switch (property_hdr->prop_id) { + case DDL_I_DPB_RELEASE: + { + if (sizeof(struct ddl_frame_data_tag) == + property_hdr->sz + && decoder->dp_buf.no_of_dec_pic_buf) { + vcd_status = + ddl_decoder_dpb_transact(decoder, + (struct ddl_frame_data_tag *) + property_value, + DDL_DPB_OP_MARK_FREE); + } + break; + } + case DDL_I_DPB: + { + struct ddl_property_dec_pic_buffers *dpb = + (struct ddl_property_dec_pic_buffers *) + property_value; + + if (sizeof(struct ddl_property_dec_pic_buffers) == + property_hdr->sz && + (DDLCLIENT_STATE_IS + (ddl, DDL_CLIENT_WAIT_FOR_INITCODEC) + || DDLCLIENT_STATE_IS(ddl, + DDL_CLIENT_WAIT_FOR_DPB) + ) && + dpb->no_of_dec_pic_buf >= + decoder->client_output_buf_req.actual_count) { + vcd_status = + ddl_set_dec_buffers(decoder, dpb); + } + break; + } + case DDL_I_REQ_OUTPUT_FLUSH: + { + if (sizeof(u32) == property_hdr->sz) { + decoder->dynamic_prop_change |= + DDL_DEC_REQ_OUTPUT_FLUSH; + decoder->dpb_mask.client_mask = 0; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_INPUT_BUF_REQ: + { + struct vcd_buffer_requirement *buffer_req = + (struct vcd_buffer_requirement *) + property_value; + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz && + (ddl_valid_buffer_requirement( + &decoder->min_input_buf_req, + buffer_req))) { + decoder->client_input_buf_req = *buffer_req; + decoder->client_input_buf_req.min_count = + decoder->min_input_buf_req.min_count; + decoder->client_input_buf_req.max_count = + decoder->min_input_buf_req.max_count; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_OUTPUT_BUF_REQ: + { + struct vcd_buffer_requirement *buffer_req = + (struct vcd_buffer_requirement *) + property_value; + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz && + (ddl_valid_buffer_requirement( + &decoder->min_output_buf_req, + buffer_req))) { + decoder->client_output_buf_req = + *buffer_req; + decoder->client_output_buf_req.min_count = + decoder->min_output_buf_req.min_count; + decoder->client_output_buf_req.max_count = + decoder->min_output_buf_req.max_count; + vcd_status = VCD_S_SUCCESS; + } + break; + } + + case VCD_I_CODEC: + { + struct vcd_property_codec *codec = + (struct vcd_property_codec *)property_value; + if (sizeof(struct vcd_property_codec) == + property_hdr->sz + && DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) + ) { + u32 status; + vcd_fw_transact(false, true, + decoder->codec.codec); + status = vcd_fw_transact(true, true, + codec->codec); + if (status) { + decoder->codec = *codec; + ddl_set_default_dec_property(ddl); + vcd_status = VCD_S_SUCCESS; + } else { + status = vcd_fw_transact(true, true, + decoder->codec.codec); + vcd_status = VCD_ERR_NOT_SUPPORTED; + } + } + break; + } + case VCD_I_POST_FILTER: + { + if (sizeof(struct vcd_property_post_filter) == + property_hdr->sz + && DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && + (decoder->codec.codec == VCD_CODEC_MPEG4 || + decoder->codec.codec == VCD_CODEC_MPEG2) + ) { + decoder->post_filter = + *(struct vcd_property_post_filter *) + property_value; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_FRAME_SIZE: + { + struct vcd_property_frame_size *frame_size = + (struct vcd_property_frame_size *) + property_value; + + if ((sizeof(struct vcd_property_frame_size) == + property_hdr->sz) && + (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN))) { + if (decoder->client_frame_size.height != + frame_size->height + || decoder->client_frame_size.width != + frame_size->width) { + decoder->client_frame_size = + *frame_size; + ddl_set_default_decoder_buffer_req + (decoder, true); + } + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_BUFFER_FORMAT: + { + struct vcd_property_buffer_format *tile = + (struct vcd_property_buffer_format *) + property_value; + if (sizeof(struct vcd_property_buffer_format) == + property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && + (tile->buffer_format == VCD_BUFFER_FORMAT_NV12 + || tile->buffer_format == + VCD_BUFFER_FORMAT_TILE_4x2) + ) { + if (tile->buffer_format != + decoder->buf_format.buffer_format) { + decoder->buf_format = *tile; + ddl_set_default_decoder_buffer_req + (decoder, true); + } + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_METADATA_ENABLE: + case VCD_I_METADATA_HEADER: + { + vcd_status = ddl_set_metadata_params(ddl, + property_hdr, + property_value); + break; + } + case VCD_I_OUTPUT_ORDER: + { + if (sizeof(u32) == property_hdr->sz && + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN)) { + decoder->output_order = + *(u32 *)property_value; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_FRAME_RATE: + { + vcd_status = VCD_S_SUCCESS; + break; + } + default: + { + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + } + return vcd_status; +} + +static u32 ddl_set_enc_property(struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) +{ + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + + if (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME) || + (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN))) + vcd_status = ddl_set_enc_dynamic_property(ddl, + property_hdr, property_value); + if (vcd_status == VCD_S_SUCCESS) + return vcd_status; + + if (!DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) || + vcd_status != VCD_ERR_ILLEGAL_OP) { + VIDC_LOGERR_STRING + ("ddl_set_enc_property:Fails_as_not_in_open_state"); + return VCD_ERR_ILLEGAL_OP; + } + + switch (property_hdr->prop_id) { + case VCD_I_FRAME_SIZE: + { + struct vcd_property_frame_size *framesize = + (struct vcd_property_frame_size *) + property_value; + + if (sizeof(struct vcd_property_frame_size) + == property_hdr->sz && + DDL_ALLOW_ENC_FRAMESIZE(framesize->width, + framesize->height) && + (encoder->codec.codec == VCD_CODEC_H264 || + DDL_VALIDATE_ENC_FRAMESIZE(framesize->width, + framesize->height)) + ) { + encoder->frame_size = *framesize; + ddl_calculate_stride(&encoder->frame_size, + false, encoder->codec.codec); + ddl_set_default_encoder_buffer_req(encoder); + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_CODEC: + { + struct vcd_property_codec *codec = + (struct vcd_property_codec *) + property_value; + if (sizeof(struct vcd_property_codec) == + property_hdr->sz) { + u32 status; + + vcd_fw_transact(false, false, + encoder->codec.codec); + + status = vcd_fw_transact(true, false, + codec->codec); + if (status) { + encoder->codec = *codec; + ddl_set_default_enc_property(ddl); + vcd_status = VCD_S_SUCCESS; + } else { + status = vcd_fw_transact(true, false, + encoder->codec.codec); + vcd_status = VCD_ERR_NOT_SUPPORTED; + } + } + break; + } + case VCD_I_PROFILE: + { + struct vcd_property_profile *profile = + (struct vcd_property_profile *) + property_value; + if ((sizeof(struct vcd_property_profile) == + property_hdr->sz) && + ((encoder->codec.codec == + VCD_CODEC_MPEG4 && + (profile->profile == + VCD_PROFILE_MPEG4_SP || + profile->profile == + VCD_PROFILE_MPEG4_ASP)) || + (encoder->codec.codec == + VCD_CODEC_H264 && + (profile->profile >= + VCD_PROFILE_H264_BASELINE || + profile->profile <= + VCD_PROFILE_H264_HIGH)) || + (encoder->codec.codec == + VCD_CODEC_H263 && + profile->profile == + VCD_PROFILE_H263_BASELINE)) + ) { + encoder->profile = *profile; + vcd_status = VCD_S_SUCCESS; + + if (profile->profile == + VCD_PROFILE_H264_BASELINE) + encoder->entropy_control.entropy_sel + = VCD_ENTROPY_SEL_CAVLC; + else + encoder->entropy_control.entropy_sel + = VCD_ENTROPY_SEL_CABAC; + } + break; + } + case VCD_I_LEVEL: + { + struct vcd_property_level *level = + (struct vcd_property_level *) + property_value; + if ( + (sizeof(struct vcd_property_level) == + property_hdr->sz + ) && + ( + ( + (encoder->codec. + codec == VCD_CODEC_MPEG4) && + (level->level >= VCD_LEVEL_MPEG4_0) && + (level->level <= VCD_LEVEL_MPEG4_6) + ) || + ( + (encoder->codec. + codec == VCD_CODEC_H264) && + (level->level >= VCD_LEVEL_H264_1) && + (level->level <= VCD_LEVEL_H264_3p1) + ) || + ( + (encoder->codec. + codec == VCD_CODEC_H263) && + (level->level >= VCD_LEVEL_H263_10) && + (level->level <= VCD_LEVEL_H263_70) + ) + ) + ) { + encoder->level = *level; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_MULTI_SLICE: + { + struct vcd_property_multi_slice *multislice = + (struct vcd_property_multi_slice *) + property_value; + switch (multislice->m_slice_sel) { + case VCD_MSLICE_OFF: + { + vcd_status = VCD_S_SUCCESS; + break; + } + case VCD_MSLICE_BY_GOB: + { + if (encoder->codec.codec == + VCD_CODEC_H263) + vcd_status = VCD_S_SUCCESS; + break; + } + case VCD_MSLICE_BY_MB_COUNT: + { + if (multislice->m_slice_size + >= 1 && (multislice-> + m_slice_size <= + (encoder->frame_size.height + * encoder->frame_size.width + / 16 / 16)) + ) { + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_MSLICE_BY_BYTE_COUNT: + { + if (multislice->m_slice_size > 0) + vcd_status = VCD_S_SUCCESS; + break; + } + default: + { + break; + } + } + if (sizeof(struct vcd_property_multi_slice) == + property_hdr->sz && + !vcd_status) { + encoder->multi_slice = *multislice; + if (multislice->m_slice_sel == + VCD_MSLICE_OFF) + encoder->multi_slice.m_slice_size = 0; + } + break; + } + case VCD_I_RATE_CONTROL: + { + struct vcd_property_rate_control + *ratecontrol = + (struct vcd_property_rate_control *) + property_value; + if (sizeof(struct vcd_property_rate_control) == + property_hdr->sz && + ratecontrol-> + rate_control >= VCD_RATE_CONTROL_OFF && + ratecontrol-> + rate_control <= VCD_RATE_CONTROL_CBR_CFR + ) { + encoder->rc = *ratecontrol; + ddl_set_default_enc_rc_params(encoder); + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_SHORT_HEADER: + { + + if (sizeof(struct vcd_property_short_header) == + property_hdr->sz && + encoder->codec.codec == VCD_CODEC_MPEG4) { + encoder->short_header = + *(struct vcd_property_short_header *) + property_value; + vcd_status = VCD_S_SUCCESS; + } + + break; + } + case VCD_I_VOP_TIMING: + { + struct vcd_property_vop_timing *voptime = + (struct vcd_property_vop_timing *) + property_value; + if ( + (sizeof(struct vcd_property_vop_timing) == + property_hdr->sz + ) && + (encoder->frame_rate.fps_numerator <= + voptime->vop_time_resolution) + ) { + encoder->vop_timing = *voptime; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_HEADER_EXTENSION: + { + if (sizeof(u32) == property_hdr->sz && + encoder->codec.codec == VCD_CODEC_MPEG4 + ) { + encoder->hdr_ext_control = *(u32 *) + property_value; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_ENTROPY_CTRL: + { + struct vcd_property_entropy_control + *entropy_control = + (struct vcd_property_entropy_control *) + property_value; + if (sizeof(struct vcd_property_entropy_control) == + property_hdr->sz && + encoder->codec.codec == VCD_CODEC_H264 + && entropy_control-> + entropy_sel >= VCD_ENTROPY_SEL_CAVLC && + entropy_control->entropy_sel <= + VCD_ENTROPY_SEL_CABAC) { + encoder->entropy_control = *entropy_control; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_DEBLOCKING: + { + struct vcd_property_db_config *dbconfig = + (struct vcd_property_db_config *) + property_value; + if (sizeof(struct vcd_property_db_config) == + property_hdr->sz && + encoder->codec.codec == VCD_CODEC_H264 + && dbconfig->db_config >= + VCD_DB_ALL_BLOCKING_BOUNDARY + && dbconfig->db_config <= + VCD_DB_SKIP_SLICE_BOUNDARY + ) { + encoder->db_control = *dbconfig; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_QP_RANGE: + { + struct vcd_property_qp_range *qp = + (struct vcd_property_qp_range *) + property_value; + if ((sizeof(struct vcd_property_qp_range) == + property_hdr->sz) && + (qp->min_qp <= qp->max_qp) && + ( + (encoder->codec.codec == VCD_CODEC_H264 + && qp->max_qp <= DDL_MAX_H264_QP) || + (qp->max_qp <= DDL_MAX_MPEG4_QP) + ) + ) { + encoder->qp_range = *qp; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_SESSION_QP: + { + struct vcd_property_session_qp *qp = + (struct vcd_property_session_qp *) + property_value; + + if ((sizeof(struct vcd_property_session_qp) == + property_hdr->sz) && + (qp->i_frame_qp >= encoder->qp_range.min_qp) && + (qp->i_frame_qp <= encoder->qp_range.max_qp) && + (qp->p_frame_qp >= encoder->qp_range.min_qp) && + (qp->p_frame_qp <= encoder->qp_range.max_qp) + ) { + encoder->session_qp = *qp; + vcd_status = VCD_S_SUCCESS; + } + + break; + } + case VCD_I_RC_LEVEL_CONFIG: + { + struct vcd_property_rc_level *rc_level = + (struct vcd_property_rc_level *) + property_value; + if (sizeof(struct vcd_property_rc_level) == + property_hdr->sz && + ( + encoder->rc. + rate_control >= VCD_RATE_CONTROL_VBR_VFR || + encoder->rc. + rate_control <= VCD_RATE_CONTROL_CBR_VFR + ) && + (!rc_level->mb_level_rc || + encoder->codec.codec == VCD_CODEC_H264 + ) + ) { + encoder->rc_level = *rc_level; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_FRAME_LEVEL_RC: + { + + struct vcd_property_frame_level_rc_params + *frame_levelrc = + (struct vcd_property_frame_level_rc_params *) + property_value; + + if ((sizeof(struct + vcd_property_frame_level_rc_params) + == property_hdr->sz) && + (frame_levelrc->reaction_coeff) && + (encoder->rc_level.frame_level_rc) + ) { + encoder->frame_level_rc = *frame_levelrc; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_ADAPTIVE_RC: + { + + if ((sizeof(struct + vcd_property_adaptive_rc_params) + == property_hdr->sz) && + (encoder->codec. + codec == VCD_CODEC_H264) && + (encoder->rc_level.mb_level_rc)) { + + encoder->adaptive_rc = + *(struct vcd_property_adaptive_rc_params *) + property_value; + + vcd_status = VCD_S_SUCCESS; + } + + break; + } + case VCD_I_BUFFER_FORMAT: + { + struct vcd_property_buffer_format *tile = + (struct vcd_property_buffer_format *) + property_value; + if (sizeof(struct vcd_property_buffer_format) == + property_hdr->sz && + tile->buffer_format == + VCD_BUFFER_FORMAT_NV12) { + encoder->buf_format = *tile; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_INPUT_BUF_REQ: + { + struct vcd_buffer_requirement *buffer_req = + (struct vcd_buffer_requirement *) + property_value; + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz && + (ddl_valid_buffer_requirement( + &encoder->input_buf_req, buffer_req)) + ) { + encoder->client_input_buf_req = *buffer_req; + encoder->client_input_buf_req.min_count = + encoder->input_buf_req.min_count; + encoder->client_input_buf_req.max_count = + encoder->input_buf_req.max_count; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_OUTPUT_BUF_REQ: + { + struct vcd_buffer_requirement *buffer_req = + (struct vcd_buffer_requirement *) + property_value; + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz && + (ddl_valid_buffer_requirement( + &encoder->output_buf_req, buffer_req)) + ) { + encoder->client_output_buf_req = + *buffer_req; + encoder->client_output_buf_req.min_count = + encoder->output_buf_req.min_count; + encoder->client_output_buf_req.max_count = + encoder->output_buf_req.max_count; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_METADATA_ENABLE: + case VCD_I_METADATA_HEADER: + { + vcd_status = ddl_set_metadata_params( + ddl, property_hdr, property_value); + break; + } + default: + { + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + } + return vcd_status; +} + +static u32 ddl_get_dec_property + (struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) { + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + struct ddl_decoder_data *decoder = &ddl->codec_data.decoder; + + switch (property_hdr->prop_id) { + case VCD_I_FRAME_SIZE: + { + struct vcd_property_frame_size *fz_size; + if (sizeof(struct vcd_property_frame_size) == + property_hdr->sz) { + ddl_calculate_stride( + &decoder->client_frame_size, + !decoder->progressive_only, + decoder->codec.codec); + if (decoder->buf_format.buffer_format + == VCD_BUFFER_FORMAT_TILE_4x2) { + fz_size = + &decoder->client_frame_size; + fz_size->stride = + DDL_TILE_ALIGN(fz_size->width, + DDL_TILE_ALIGN_WIDTH); + fz_size->scan_lines = + DDL_TILE_ALIGN(fz_size->height, + DDL_TILE_ALIGN_HEIGHT); + } + *(struct vcd_property_frame_size *) + property_value = + decoder->client_frame_size; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_PROFILE: + { + if (sizeof(struct vcd_property_profile) == + property_hdr->sz) { + *(struct vcd_property_profile *) + property_value = decoder->profile; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_LEVEL: + { + if (sizeof(struct vcd_property_level) == + property_hdr->sz) { + *(struct vcd_property_level *) + property_value = decoder->level; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_PROGRESSIVE_ONLY: + { + if (sizeof(u32) == property_hdr->sz) { + *(u32 *) property_value = + decoder->progressive_only; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_INPUT_BUF_REQ: + { + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz) { + *(struct vcd_buffer_requirement *) + property_value = + decoder->client_input_buf_req; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_OUTPUT_BUF_REQ: + { + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz) { + *(struct vcd_buffer_requirement *) + property_value = + decoder->client_output_buf_req; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_CODEC: + { + if (sizeof(struct vcd_property_codec) == + property_hdr->sz) { + *(struct vcd_property_codec *) + property_value = decoder->codec; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_BUFFER_FORMAT: + { + if (sizeof(struct vcd_property_buffer_format) == + property_hdr->sz) { + *(struct vcd_property_buffer_format *) + property_value = decoder->buf_format; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_POST_FILTER: + { + if (sizeof(struct vcd_property_post_filter) == + property_hdr->sz) { + *(struct vcd_property_post_filter *) + property_value = decoder->post_filter; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_SEQHDR_ALIGN_BYTES: + { + if (sizeof(u32) == property_hdr->sz) { + *(u32 *) property_value = + DDL_LINEAR_BUFFER_ALIGN_BYTES; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_FRAME_PROC_UNITS: + { + if (sizeof(u32) == property_hdr->sz) { + struct vcd_property_frame_size frame_sz = + decoder->client_frame_size; + ddl_calculate_stride(&frame_sz, + !decoder->progressive_only, + decoder->codec.codec); + *(u32 *) property_value = + ((frame_sz.stride >> 4) * + (frame_sz.scan_lines >> 4)); + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_DPB_RETRIEVE: + { + if (sizeof(struct ddl_frame_data_tag) == + property_hdr->sz) { + vcd_status = + ddl_decoder_dpb_transact(decoder, + (struct ddl_frame_data_tag *) + property_value, + DDL_DPB_OP_RETRIEVE); + } + break; + } + case VCD_I_OUTPUT_ORDER: + { + if (sizeof(u32) == property_hdr->sz) { + *(u32 *)property_value = decoder->output_order; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_METADATA_ENABLE: + case VCD_I_METADATA_HEADER: + { + vcd_status = ddl_get_metadata_params( + ddl, + property_hdr, + property_value); + break; + } + default: + { + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + } + return vcd_status; +} + +static u32 ddl_get_enc_property + (struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) { + u32 vcd_status = VCD_ERR_ILLEGAL_PARM; + struct ddl_encoder_data *encoder = &ddl->codec_data.encoder; + + struct vcd_property_entropy_control *entropy_control; + struct vcd_property_intra_refresh_mb_number *intra_refresh; + + switch (property_hdr->prop_id) { + case VCD_I_CODEC: + { + if (sizeof(struct vcd_property_codec) == + property_hdr->sz) { + *(struct vcd_property_codec *) + property_value = + encoder->codec; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_FRAME_SIZE: + { + if (sizeof(struct vcd_property_frame_size) == + property_hdr->sz) { + *(struct vcd_property_frame_size *) + property_value = + encoder->frame_size; + + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_FRAME_RATE: + { + if (sizeof(struct vcd_property_frame_rate) == + property_hdr->sz) { + + *(struct vcd_property_frame_rate *) + property_value = + encoder->frame_rate; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_TARGET_BITRATE: + { + + if (sizeof(struct vcd_property_target_bitrate) == + property_hdr->sz) { + *(struct vcd_property_target_bitrate *) + property_value = + encoder->target_bit_rate; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_RATE_CONTROL: + { + if (sizeof(struct vcd_property_rate_control) == + property_hdr->sz) { + *(struct vcd_property_rate_control *) + property_value = encoder->rc; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_PROFILE: + { + if (sizeof(struct vcd_property_profile) == + property_hdr->sz) { + *(struct vcd_property_profile *) + property_value = encoder->profile; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_LEVEL: + { + if (sizeof(struct vcd_property_level) == + property_hdr->sz) { + *(struct vcd_property_level *) + property_value = encoder->level; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_MULTI_SLICE: + { + if (sizeof(struct vcd_property_multi_slice) == + property_hdr->sz) { + *(struct vcd_property_multi_slice *) + property_value = encoder->multi_slice; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_SEQ_HEADER: + { + struct vcd_sequence_hdr *seq_hdr = + (struct vcd_sequence_hdr *)property_value; + if (encoder->seq_header.buffer_size && + sizeof(struct vcd_sequence_hdr) == + property_hdr->sz + && encoder->seq_header.buffer_size <= + seq_hdr->sequence_header_len) { + DDL_MEMCPY(seq_hdr->sequence_header, + encoder->seq_header. + align_virtual_addr, + encoder->seq_header.buffer_size); + seq_hdr->sequence_header_len = + encoder->seq_header.buffer_size; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_SEQHDR_PRESENT: + { + if (sizeof(u32) == property_hdr->sz) { + if ((encoder->codec. + codec == VCD_CODEC_MPEG4 && + !encoder->short_header.short_header) + || encoder->codec.codec == + VCD_CODEC_H264) { + *(u32 *)property_value = 0x1; + } else { + *(u32 *)property_value = 0x0; + } + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_VOP_TIMING: + { + if (sizeof(struct vcd_property_vop_timing) == + property_hdr->sz) { + *(struct vcd_property_vop_timing *) + property_value = encoder->vop_timing; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_SHORT_HEADER: + { + if (sizeof(struct vcd_property_short_header) == + property_hdr->sz) { + if (encoder->codec.codec == + VCD_CODEC_MPEG4) { + *(struct vcd_property_short_header + *)property_value = + encoder->short_header; + vcd_status = VCD_S_SUCCESS; + } else { + vcd_status = VCD_ERR_ILLEGAL_OP; + } + } + break; + } + case VCD_I_ENTROPY_CTRL: + { + entropy_control = property_value; + if (sizeof(struct vcd_property_entropy_control) == + property_hdr->sz) { + if (encoder->codec.codec == + VCD_CODEC_H264) { + *entropy_control = + encoder->entropy_control; + vcd_status = VCD_S_SUCCESS; + } else { + vcd_status = VCD_ERR_ILLEGAL_OP; + } + } + break; + } + case VCD_I_DEBLOCKING: + { + if (sizeof(struct vcd_property_db_config) == + property_hdr->sz) { + if (encoder->codec.codec == + VCD_CODEC_H264) { + *(struct vcd_property_db_config *) + property_value = + encoder->db_control; + vcd_status = VCD_S_SUCCESS; + } else { + vcd_status = VCD_ERR_ILLEGAL_OP; + } + } + break; + } + case VCD_I_INTRA_PERIOD: + { + if (sizeof(struct vcd_property_i_period) == + property_hdr->sz) { + *(struct vcd_property_i_period *) + property_value = encoder->i_period; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_QP_RANGE: + { + if (sizeof(struct vcd_property_qp_range) == + property_hdr->sz) { + *(struct vcd_property_qp_range *) + property_value = encoder->qp_range; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_SESSION_QP: + { + if (sizeof(struct vcd_property_session_qp) == + property_hdr->sz) { + *(struct vcd_property_session_qp *) + property_value = encoder->session_qp; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_RC_LEVEL_CONFIG: + { + if (sizeof(struct vcd_property_rc_level) == + property_hdr->sz) { + *(struct vcd_property_rc_level *) + property_value = encoder->rc_level; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_FRAME_LEVEL_RC: + { + if (sizeof + (struct vcd_property_frame_level_rc_params) == + property_hdr->sz) { + *(struct vcd_property_frame_level_rc_params + *)property_value = + encoder->frame_level_rc; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_ADAPTIVE_RC: + { + if (sizeof(struct vcd_property_adaptive_rc_params) + == property_hdr->sz) { + *(struct vcd_property_adaptive_rc_params *) + property_value = encoder->adaptive_rc; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_INTRA_REFRESH: + { + intra_refresh = property_value; + if (sizeof + (struct vcd_property_intra_refresh_mb_number) + == property_hdr->sz) { + *intra_refresh = encoder->intra_refresh; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_INPUT_BUF_REQ: + { + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz) { + *(struct vcd_buffer_requirement *) + property_value = + encoder->client_input_buf_req; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_OUTPUT_BUF_REQ: + { + if (sizeof(struct vcd_buffer_requirement) == + property_hdr->sz) { + *(struct vcd_buffer_requirement *) + property_value = + encoder->client_output_buf_req; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_BUFFER_FORMAT: + { + if (sizeof(struct vcd_property_buffer_format) == + property_hdr->sz) { + *(struct vcd_property_buffer_format *) + property_value = encoder->buf_format; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case DDL_I_FRAME_PROC_UNITS: + { + if (sizeof(u32) == property_hdr->sz) { + *(u32 *) property_value = + ((encoder->frame_size.width >> 4) * + (encoder->frame_size.height >> 4) + ); + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_HEADER_EXTENSION: + { + if (sizeof(u32) == property_hdr->sz && + encoder->codec.codec == VCD_CODEC_MPEG4) { + *(u32 *) property_value = + encoder->hdr_ext_control; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_METADATA_ENABLE: + case VCD_I_METADATA_HEADER: + { + vcd_status = ddl_get_metadata_params( + ddl, + property_hdr, + property_value); + break; + } + default: + { + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + } + return vcd_status; +} + +static u32 ddl_set_enc_dynamic_property + (struct ddl_client_context *ddl, + struct vcd_property_hdr *property_hdr, void *property_value) { + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + u32 vcd_status = VCD_ERR_ILLEGAL_PARM, dynamic_prop_change = 0x0; + switch (property_hdr->prop_id) { + case VCD_I_REQ_IFRAME: + { + if (sizeof(struct vcd_property_req_i_frame) == + property_hdr->sz) { + dynamic_prop_change = DDL_ENC_REQ_IFRAME; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_TARGET_BITRATE: + { + struct vcd_property_target_bitrate *bitrate = + (struct vcd_property_target_bitrate *) + property_value; + if (sizeof(struct vcd_property_target_bitrate) == + property_hdr->sz && bitrate->target_bitrate > 0 + && bitrate->target_bitrate <= DDL_MAX_BIT_RATE) { + encoder->target_bit_rate = *bitrate; + dynamic_prop_change = DDL_ENC_CHANGE_BITRATE; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_INTRA_PERIOD: + { + struct vcd_property_i_period *iperiod = + (struct vcd_property_i_period *) + property_value; + if (sizeof(struct vcd_property_i_period) == + property_hdr->sz && + !iperiod->b_frames) { + encoder->i_period = *iperiod; + dynamic_prop_change = DDL_ENC_CHANGE_IPERIOD; + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_FRAME_RATE: + { + struct vcd_property_frame_rate *frame_rate = + (struct vcd_property_frame_rate *) + property_value; + if (sizeof(struct vcd_property_frame_rate) + == property_hdr->sz && + frame_rate->fps_denominator && + frame_rate->fps_numerator && + frame_rate->fps_denominator <= + frame_rate->fps_numerator) { + encoder->frame_rate = *frame_rate; + dynamic_prop_change = DDL_ENC_CHANGE_FRAMERATE; + if (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_OPEN) && + (encoder->codec.codec != VCD_CODEC_MPEG4 + || encoder->short_header.short_header)) + ddl_set_default_enc_vop_timing(encoder); + vcd_status = VCD_S_SUCCESS; + } + break; + } + case VCD_I_INTRA_REFRESH: + { + struct vcd_property_intra_refresh_mb_number + *intra_refresh_mbnum = ( + struct vcd_property_intra_refresh_mb_number *) + property_value; + u32 frame_mbnum = + (encoder->frame_size.width >> 4) * + (encoder->frame_size.height >> 4); + if (sizeof(struct + vcd_property_intra_refresh_mb_number) + == property_hdr->sz && + intra_refresh_mbnum->cir_mb_number <= + frame_mbnum) { + encoder->intra_refresh = + *intra_refresh_mbnum; + dynamic_prop_change = DDL_ENC_CHANGE_CIR; + vcd_status = VCD_S_SUCCESS; + } + + break; + } + default: + { + vcd_status = VCD_ERR_ILLEGAL_OP; + break; + } + } + if (vcd_status == VCD_S_SUCCESS && + (DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME) || + DDLCLIENT_STATE_IS(ddl, DDL_CLIENT_WAIT_FOR_FRAME_DONE))) + encoder->dynamic_prop_change |= dynamic_prop_change; + return vcd_status; +} + +void ddl_set_default_dec_property(struct ddl_client_context *ddl) +{ + struct ddl_decoder_data *decoder = &(ddl->codec_data.decoder); + + if (decoder->codec.codec == VCD_CODEC_MPEG4 || + decoder->codec.codec == VCD_CODEC_MPEG2) + decoder->post_filter.post_filter = true; + else + decoder->post_filter.post_filter = false; + decoder->buf_format.buffer_format = VCD_BUFFER_FORMAT_NV12; + decoder->client_frame_size.height = 144; + decoder->client_frame_size.width = 176; + decoder->client_frame_size.stride = 176; + decoder->client_frame_size.scan_lines = 144; + decoder->progressive_only = 1; + decoder->profile.profile = VCD_PROFILE_UNKNOWN; + decoder->level.level = VCD_LEVEL_UNKNOWN; + decoder->output_order = VCD_DEC_ORDER_DISPLAY; + ddl_set_default_metadata_flag(ddl); + ddl_set_default_decoder_buffer_req(decoder, true); +} + +static void ddl_set_default_enc_property(struct ddl_client_context *ddl) +{ + struct ddl_encoder_data *encoder = &(ddl->codec_data.encoder); + + ddl_set_default_enc_profile(encoder); + ddl_set_default_enc_level(encoder); + + encoder->rc.rate_control = VCD_RATE_CONTROL_VBR_VFR; + ddl_set_default_enc_rc_params(encoder); + + ddl_set_default_enc_intra_period(encoder); + + encoder->intra_refresh.cir_mb_number = 0; + ddl_set_default_enc_vop_timing(encoder); + + encoder->multi_slice.m_slice_sel = VCD_MSLICE_OFF; + encoder->multi_slice.m_slice_size = 0; + encoder->short_header.short_header = false; + + encoder->entropy_control.entropy_sel = VCD_ENTROPY_SEL_CAVLC; + encoder->entropy_control.cabac_model = VCD_CABAC_MODEL_NUMBER_0; + encoder->db_control.db_config = VCD_DB_ALL_BLOCKING_BOUNDARY; + encoder->db_control.slice_alpha_offset = 0; + encoder->db_control.slice_beta_offset = 0; + + encoder->re_con_buf_format.buffer_format = + VCD_BUFFER_FORMAT_TILE_4x2; + + encoder->buf_format.buffer_format = VCD_BUFFER_FORMAT_NV12; + + encoder->hdr_ext_control = 0; + + ddl_set_default_metadata_flag(ddl); + + ddl_set_default_encoder_buffer_req(encoder); +} + +static void ddl_set_default_enc_profile(struct ddl_encoder_data *encoder) +{ + enum vcd_codec codec = encoder->codec.codec; + if (codec == VCD_CODEC_MPEG4) + encoder->profile.profile = VCD_PROFILE_MPEG4_SP; + else if (codec == VCD_CODEC_H264) + encoder->profile.profile = VCD_PROFILE_H264_BASELINE; + else + encoder->profile.profile = VCD_PROFILE_H263_BASELINE; +} + +static void ddl_set_default_enc_level(struct ddl_encoder_data *encoder) +{ + enum vcd_codec codec = encoder->codec.codec; + if (codec == VCD_CODEC_MPEG4) + encoder->level.level = VCD_LEVEL_MPEG4_1; + else if (codec == VCD_CODEC_H264) + encoder->level.level = VCD_LEVEL_H264_1; + else + encoder->level.level = VCD_LEVEL_H263_10; +} + +static void ddl_set_default_enc_vop_timing + (struct ddl_encoder_data *encoder) +{ + if (encoder->codec.codec == VCD_CODEC_MPEG4) + encoder->vop_timing.vop_time_resolution = + (2 * encoder->frame_rate.fps_numerator) / + encoder->frame_rate.fps_denominator; + else + encoder->vop_timing.vop_time_resolution = 0x7530; +} + +static void ddl_set_default_enc_intra_period( + struct ddl_encoder_data *encoder) +{ + switch (encoder->rc.rate_control) { + default: + case VCD_RATE_CONTROL_VBR_VFR: + case VCD_RATE_CONTROL_VBR_CFR: + case VCD_RATE_CONTROL_CBR_VFR: + case VCD_RATE_CONTROL_OFF: + { + encoder->i_period.p_frames = + ((encoder->frame_rate.fps_numerator << 1) / + encoder->frame_rate.fps_denominator) - 1; + break; + } + case VCD_RATE_CONTROL_CBR_CFR: + { + encoder->i_period.p_frames = + ((encoder->frame_rate.fps_numerator >> 1) / + encoder->frame_rate.fps_denominator) - 1; + break; + } + } + encoder->i_period.b_frames = 0; +} + +static void ddl_set_default_enc_rc_params( + struct ddl_encoder_data *encoder) +{ + enum vcd_codec codec = encoder->codec.codec; + + encoder->rc_level.frame_level_rc = true; + encoder->qp_range.min_qp = 0x1; + + if (codec == VCD_CODEC_H264) { + encoder->qp_range.max_qp = 0x33; + encoder->session_qp.i_frame_qp = 0x14; + encoder->session_qp.p_frame_qp = 0x14; + + encoder->rc_level.mb_level_rc = true; + encoder->adaptive_rc.activity_region_flag = true; + encoder->adaptive_rc.dark_region_as_flag = true; + encoder->adaptive_rc.smooth_region_as_flag = true; + encoder->adaptive_rc.static_region_as_flag = true; + } else { + encoder->qp_range.max_qp = 0x1f; + encoder->session_qp.i_frame_qp = 0xd; + encoder->session_qp.p_frame_qp = 0xd; + encoder->rc_level.mb_level_rc = false; + } + + switch (encoder->rc.rate_control) { + default: + case VCD_RATE_CONTROL_VBR_VFR: + { + encoder->r_cframe_skip = 1; + encoder->frame_level_rc.reaction_coeff = 0x1f4; + break; + } + case VCD_RATE_CONTROL_VBR_CFR: + { + encoder->r_cframe_skip = 0; + encoder->frame_level_rc.reaction_coeff = 0x1f4; + break; + } + case VCD_RATE_CONTROL_CBR_VFR: + { + encoder->r_cframe_skip = 1; + if (codec != VCD_CODEC_H264) { + encoder->session_qp.i_frame_qp = 0xf; + encoder->session_qp.p_frame_qp = 0xf; + } + + encoder->frame_level_rc.reaction_coeff = 0x14; + break; + } + case VCD_RATE_CONTROL_CBR_CFR: + { + encoder->r_cframe_skip = 0; + encoder->frame_level_rc.reaction_coeff = 0x6; + break; + } + case VCD_RATE_CONTROL_OFF: + { + encoder->r_cframe_skip = 0; + encoder->rc_level.frame_level_rc = false; + encoder->rc_level.mb_level_rc = false; + break; + } + } +} + +void ddl_set_default_encoder_buffer_req(struct ddl_encoder_data *encoder) +{ + u32 y_cb_cr_size; + + y_cb_cr_size = ddl_get_yuv_buffer_size(&encoder->frame_size, + &encoder->buf_format, false, encoder->codec.codec); + + memset(&encoder->input_buf_req, 0, + sizeof(struct vcd_buffer_requirement)); + + encoder->input_buf_req.min_count = 1; + encoder->input_buf_req.actual_count = + encoder->input_buf_req.min_count + 8; + encoder->input_buf_req.max_count = DDL_MAX_BUFFER_COUNT; + encoder->input_buf_req.sz = y_cb_cr_size; + encoder->input_buf_req.align = DDL_LINEAR_BUFFER_ALIGN_BYTES; + + encoder->client_input_buf_req = encoder->input_buf_req; + + memset(&encoder->output_buf_req, 0, + sizeof(struct vcd_buffer_requirement)); + + encoder->output_buf_req.min_count = 2; + encoder->output_buf_req.actual_count = + encoder->output_buf_req.min_count + 3; + encoder->output_buf_req.max_count = DDL_MAX_BUFFER_COUNT; + encoder->output_buf_req.align = DDL_LINEAR_BUFFER_ALIGN_BYTES; + encoder->output_buf_req.sz = y_cb_cr_size; + ddl_set_default_encoder_metadata_buffer_size(encoder); + encoder->client_output_buf_req = encoder->output_buf_req; +} + +void ddl_set_default_decoder_buffer_req(struct ddl_decoder_data *decoder, + u32 estimate) +{ + u32 y_cb_cr_size, min_dpb, num_mb; + struct vcd_property_frame_size *frame_size; + struct vcd_buffer_requirement *output_buf_req, *input_buf_req; + + if (!decoder->codec.codec) + return; + + if (estimate) { + frame_size = &decoder->client_frame_size; + output_buf_req = &decoder->client_output_buf_req; + input_buf_req = &decoder->client_input_buf_req; + min_dpb = ddl_decoder_min_num_dpb(decoder); + y_cb_cr_size = ddl_get_yuv_buffer_size(frame_size, + &decoder->buf_format, (!decoder->progressive_only), + decoder->codec.codec); + } else { + frame_size = &decoder->frame_size; + output_buf_req = &decoder->actual_output_buf_req; + input_buf_req = &decoder->actual_input_buf_req; + y_cb_cr_size = decoder->y_cb_cr_size; + min_dpb = decoder->min_dpb_num; + } + + memset(output_buf_req, 0, sizeof(struct vcd_buffer_requirement)); + + output_buf_req->min_count = min_dpb; + + num_mb = DDL_NO_OF_MB(frame_size->width, frame_size->height); + if (num_mb >= DDL_WVGA_MBS) { + output_buf_req->actual_count = min_dpb + 2; + if (output_buf_req->actual_count < 10) + output_buf_req->actual_count = 10; + } else + output_buf_req->actual_count = min_dpb + 5; + + output_buf_req->max_count = DDL_MAX_BUFFER_COUNT; + output_buf_req->sz = y_cb_cr_size; + if (decoder->buf_format.buffer_format != VCD_BUFFER_FORMAT_NV12) + output_buf_req->align = DDL_TILE_BUFFER_ALIGN_BYTES; + else + output_buf_req->align = DDL_LINEAR_BUFFER_ALIGN_BYTES; + + ddl_set_default_decoder_metadata_buffer_size(decoder, + frame_size, output_buf_req); + + decoder->min_output_buf_req = *output_buf_req; + + memset(input_buf_req, 0, sizeof(struct vcd_buffer_requirement)); + + input_buf_req->min_count = 1; + input_buf_req->actual_count = input_buf_req->min_count + 3; + input_buf_req->max_count = DDL_MAX_BUFFER_COUNT; + input_buf_req->sz = (1280*720*3*3) >> 3; + input_buf_req->align = DDL_LINEAR_BUFFER_ALIGN_BYTES; + + decoder->min_input_buf_req = *input_buf_req; + +} + +u32 ddl_get_yuv_buffer_size(struct vcd_property_frame_size *frame_size, + struct vcd_property_buffer_format *buf_format, u32 inter_lace, + enum vcd_codec codec) +{ + struct vcd_property_frame_size frame_sz = *frame_size; + u32 total_memory_size; + ddl_calculate_stride(&frame_sz, inter_lace, codec); + + if (buf_format->buffer_format != VCD_BUFFER_FORMAT_NV12) { + u32 component_mem_size; + u32 width_round_up; + u32 height_round_up; + u32 height_chroma = (frame_sz.scan_lines >> 1); + + width_round_up = + DDL_TILE_ALIGN(frame_sz.stride, DDL_TILE_ALIGN_WIDTH); + height_round_up = + DDL_TILE_ALIGN(frame_sz.scan_lines, DDL_TILE_ALIGN_HEIGHT); + + component_mem_size = width_round_up * height_round_up; + component_mem_size = DDL_TILE_ALIGN(component_mem_size, + DDL_TILE_MULTIPLY_FACTOR); + + total_memory_size = ((component_mem_size + + DDL_TILE_BUF_ALIGN_GUARD_BYTES) & + DDL_TILE_BUF_ALIGN_MASK); + + height_round_up = + DDL_TILE_ALIGN(height_chroma, DDL_TILE_ALIGN_HEIGHT); + component_mem_size = width_round_up * height_round_up; + component_mem_size = DDL_TILE_ALIGN(component_mem_size, + DDL_TILE_MULTIPLY_FACTOR); + total_memory_size += component_mem_size; + } else { + total_memory_size = frame_sz.scan_lines * frame_sz.stride; + total_memory_size += (total_memory_size >> 1); + } + return total_memory_size; +} + +void ddl_calculate_stride(struct vcd_property_frame_size *frame_size, + u32 interlace, enum vcd_codec codec) +{ + frame_size->stride = ((frame_size->width + 15) >> 4) << 4; + if (!interlace || codec == VCD_CODEC_MPEG4 || + codec == VCD_CODEC_DIVX_4 || + codec == VCD_CODEC_DIVX_5 || + codec == VCD_CODEC_DIVX_6 || + codec == VCD_CODEC_XVID) { + frame_size->scan_lines = + ((frame_size->height + 15) >> 4) << 4; + } else { + frame_size->scan_lines = + ((frame_size->height + 31) >> 5) << 5; + } + +} + +static u32 ddl_valid_buffer_requirement + (struct vcd_buffer_requirement *original_buf_req, + struct vcd_buffer_requirement *req_buf_req) +{ + u32 status = false; + if (original_buf_req->max_count >= req_buf_req->actual_count && + original_buf_req->min_count <= req_buf_req->actual_count && + original_buf_req->align <= req_buf_req->align && + original_buf_req->sz <= req_buf_req->sz) { + status = true; + } else { + VIDC_LOGERR_STRING("ddl_valid_buf_req:Failed"); + } + return status; +} + +static u32 ddl_decoder_min_num_dpb(struct ddl_decoder_data *decoder) +{ + u32 min_dpb = 0, yuv_size = 0; + struct vcd_property_frame_size frame_sz = decoder->client_frame_size; + switch (decoder->codec.codec) { + default: + case VCD_CODEC_MPEG4: + case VCD_CODEC_MPEG2: + case VCD_CODEC_DIVX_4: + case VCD_CODEC_DIVX_5: + case VCD_CODEC_DIVX_6: + case VCD_CODEC_XVID: + { + min_dpb = 3; + break; + } + case VCD_CODEC_H263: + { + min_dpb = 2; + break; + } + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + { + min_dpb = 4; + break; + } + case VCD_CODEC_H264: + { + ddl_calculate_stride(&frame_sz, + !decoder->progressive_only, + decoder->codec.codec); + yuv_size = + ((frame_sz.scan_lines * + frame_sz.stride * 3) >> 1); + min_dpb = 6912000 / yuv_size; + if (min_dpb > 16) + min_dpb = 16; + + min_dpb += 2; + break; + } + } + return min_dpb; +} + +static u32 ddl_set_dec_buffers + (struct ddl_decoder_data *decoder, + struct ddl_property_dec_pic_buffers *dpb) { + u32 vcd_status = VCD_S_SUCCESS; + u32 loopc; + for (loopc = 0; !vcd_status && + loopc < dpb->no_of_dec_pic_buf; ++loopc) { + if ((!DDL_ADDR_IS_ALIGNED + (dpb->dec_pic_buffers[loopc].vcd_frm.physical, + decoder->client_output_buf_req.align) + ) + || (dpb->dec_pic_buffers[loopc].vcd_frm.alloc_len < + decoder->client_output_buf_req.sz) + ) { + vcd_status = VCD_ERR_ILLEGAL_PARM; + } + } + if (vcd_status) { + VIDC_LOGERR_STRING + ("ddl_set_prop:Dpb_align_fail_or_alloc_size_small"); + return vcd_status; + } + if (decoder->dp_buf.no_of_dec_pic_buf) { + DDL_FREE(decoder->dp_buf.dec_pic_buffers); + decoder->dp_buf.no_of_dec_pic_buf = 0; + } + decoder->dp_buf.dec_pic_buffers = + DDL_MALLOC(dpb->no_of_dec_pic_buf * + sizeof(struct ddl_frame_data_tag)); + + if (!decoder->dp_buf.dec_pic_buffers) { + VIDC_LOGERR_STRING + ("ddl_dec_set_prop:Dpb_container_alloc_failed"); + return VCD_ERR_ALLOC_FAIL; + } + decoder->dp_buf.no_of_dec_pic_buf = dpb->no_of_dec_pic_buf; + for (loopc = 0; loopc < dpb->no_of_dec_pic_buf; ++loopc) { + decoder->dp_buf.dec_pic_buffers[loopc] = + dpb->dec_pic_buffers[loopc]; + } + decoder->dpb_mask.client_mask = 0; + decoder->dpb_mask.hw_mask = 0; + decoder->dynamic_prop_change = 0; + return VCD_S_SUCCESS; +} + +void ddl_set_initial_default_values(struct ddl_client_context *ddl) +{ + if (ddl->decoding) { + ddl->codec_data.decoder.codec.codec = VCD_CODEC_MPEG4; + vcd_fw_transact(true, true, + ddl->codec_data.decoder.codec.codec); + ddl_set_default_dec_property(ddl); + } else { + struct ddl_encoder_data *encoder = + &(ddl->codec_data.encoder); + encoder->codec.codec = VCD_CODEC_MPEG4; + vcd_fw_transact(true, false, + encoder->codec.codec); + + encoder->target_bit_rate.target_bitrate = 64000; + encoder->frame_size.width = 176; + encoder->frame_size.height = 144; + encoder->frame_size.stride = 176; + encoder->frame_size.scan_lines = 144; + encoder->frame_rate.fps_numerator = 30; + encoder->frame_rate.fps_denominator = 1; + ddl_set_default_enc_property(ddl); + } + + return; +} diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_utils.c b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_utils.c new file mode 100644 index 00000000000..3b4528feaff --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_utils.c @@ -0,0 +1,223 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include "vidc_type.h" +#include "vcd_ddl_utils.h" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +#define DBG_TIME(x...) printk(KERN_DEBUG x) +#define ERR(x...) printk(KERN_ERR x) + +struct time_data { + unsigned int ddl_t1; + unsigned int ddl_ttotal; + unsigned int ddl_count; +}; + +static struct time_data proc_time[MAX_TIME_DATA]; + +#ifdef NO_IN_KERNEL_PMEM + +void ddl_pmem_alloc(struct ddl_buf_addr *buff_addr, size_t sz, u32 align) +{ + u32 guard_bytes, align_mask; + u32 physical_addr, align_offset; + dma_addr_t phy_addr; + + if (align == DDL_LINEAR_BUFFER_ALIGN_BYTES) { + + guard_bytes = 31; + align_mask = 0xFFFFFFE0U; + + } else { + + guard_bytes = DDL_TILE_BUF_ALIGN_GUARD_BYTES; + align_mask = DDL_TILE_BUF_ALIGN_MASK; + } + + buff_addr->virtual_base_addr = + kmalloc((sz + guard_bytes), GFP_KERNEL); + + if (!buff_addr->virtual_base_addr) { + ERR("\n ERROR %s:%u kamlloc fails to allocate" + " sz + guard_bytes = %u\n", __func__, __LINE__, + (sz + guard_bytes)); + return; + } + + phy_addr = dma_map_single(NULL, buff_addr->virtual_base_addr, + sz + guard_bytes, DMA_TO_DEVICE); + + buff_addr->buffer_size = sz; + physical_addr = (u32) phy_addr; + buff_addr->align_physical_addr = + (u32 *) ((physical_addr + guard_bytes) & align_mask); + align_offset = + (u32) (buff_addr->align_physical_addr) - physical_addr; + buff_addr->align_virtual_addr = + (u32 *) ((u32) (buff_addr->virtual_base_addr) + + align_offset); +} + +void ddl_pmem_free(struct ddl_buf_addr *buff_addr) +{ + kfree(buff_addr->virtual_base_addr); + buff_addr->buffer_size = 0; + buff_addr->virtual_base_addr = NULL; +} + +#else + +void ddl_pmem_alloc(struct ddl_buf_addr *buff_addr, size_t sz, u32 align) +{ + u32 guard_bytes, align_mask; + u32 physical_addr; + u32 align_offset; + u32 alloc_size; + struct ddl_context *ddl_context; + + if (!buff_addr) { + ERR("\n%s() Invalid Parameters", __func__); + return; + } + + DBG_PMEM("\n%s() IN: Requested alloc size(%u)", __func__, (u32)sz); + + if (align == DDL_LINEAR_BUFFER_ALIGN_BYTES) { + + guard_bytes = 31; + align_mask = 0xFFFFFFE0U; + + } else { + + guard_bytes = DDL_TILE_BUF_ALIGN_GUARD_BYTES; + align_mask = DDL_TILE_BUF_ALIGN_MASK; + } + ddl_context = ddl_get_context(); + alloc_size = sz + guard_bytes; + + physical_addr = (u32) + allocate_contiguous_memory_nomap(alloc_size, + ddl_context->memtype, SZ_4K); + + if (!physical_addr) { + pr_err("%s(): could not allocate kernel pmem buffers\n", + __func__); + goto bailout; + } + buff_addr->physical_base_addr = (u32 *) physical_addr; + + buff_addr->virtual_base_addr = + (u32 *) ioremap((unsigned long)physical_addr, + sz + guard_bytes); + if (!buff_addr->virtual_base_addr) { + + pr_err("%s: could not ioremap in kernel pmem buffers\n", + __func__); + free_contiguous_memory_by_paddr( + (unsigned long) physical_addr); + goto bailout; + } + memset(buff_addr->virtual_base_addr, 0 , sz + guard_bytes); + buff_addr->buffer_size = sz; + + buff_addr->align_physical_addr = + (u32 *) ((physical_addr + guard_bytes) & align_mask); + + align_offset = + (u32) (buff_addr->align_physical_addr) - physical_addr; + + buff_addr->align_virtual_addr = + (u32 *) ((u32) (buff_addr->virtual_base_addr) + + align_offset); + + DBG_PMEM("\n%s() OUT: phy_addr(%p) ker_addr(%p) size(%u)", __func__, + buff_addr->physical_base_addr, buff_addr->virtual_base_addr, + buff_addr->buffer_size); + + return; +bailout: + buff_addr->physical_base_addr = NULL; + buff_addr->virtual_base_addr = NULL; + buff_addr->buffer_size = 0; +} + +void ddl_pmem_free(struct ddl_buf_addr *buff_addr) +{ + if (!buff_addr) { + ERR("\n %s() invalid arguments %p", __func__, buff_addr); + return; + } + DBG_PMEM("\n%s() IN: phy_addr(%p) ker_addr(%p) size(%u)", __func__, + buff_addr->physical_base_addr, buff_addr->virtual_base_addr, + buff_addr->buffer_size); + + if (buff_addr->virtual_base_addr) + iounmap((void *)buff_addr->virtual_base_addr); + if (buff_addr->physical_base_addr) + free_contiguous_memory_by_paddr( + (unsigned long) buff_addr->physical_base_addr); + DBG_PMEM("\n%s() OUT: phy_addr(%p) ker_addr(%p) size(%u)", __func__, + buff_addr->physical_base_addr, buff_addr->virtual_base_addr, + buff_addr->buffer_size); + buff_addr->buffer_size = 0; + buff_addr->physical_base_addr = NULL; + buff_addr->virtual_base_addr = NULL; +} +#endif + +void ddl_set_core_start_time(const char *func_name, u32 index) +{ + u32 act_time; + struct timeval ddl_tv; + struct time_data *time_data = &proc_time[index]; + do_gettimeofday(&ddl_tv); + act_time = (ddl_tv.tv_sec * 1000) + (ddl_tv.tv_usec / 1000); + if (!time_data->ddl_t1) { + time_data->ddl_t1 = act_time; + DBG("\n%s(): Start Time (%u)", func_name, act_time); + } else { + DBG_TIME("\n%s(): Timer already started! St(%u) Act(%u)", + func_name, time_data->ddl_t1, act_time); + } +} + +void ddl_calc_core_proc_time(const char *func_name, u32 index) +{ + struct time_data *time_data = &proc_time[index]; + if (time_data->ddl_t1) { + int ddl_t2; + struct timeval ddl_tv; + do_gettimeofday(&ddl_tv); + ddl_t2 = (ddl_tv.tv_sec * 1000) + (ddl_tv.tv_usec / 1000); + time_data->ddl_ttotal += (ddl_t2 - time_data->ddl_t1); + time_data->ddl_count++; + DBG_TIME("\n%s(): cnt(%u) Diff(%u) Avg(%u)", + func_name, time_data->ddl_count, + ddl_t2 - time_data->ddl_t1, + time_data->ddl_ttotal/time_data->ddl_count); + time_data->ddl_t1 = 0; + } +} + +void ddl_reset_core_time_variables(u32 index) +{ + proc_time[index].ddl_t1 = 0; + proc_time[index].ddl_ttotal = 0; + proc_time[index].ddl_count = 0; +} diff --git a/drivers/video/msm/vidc/720p/ddl/vcd_ddl_utils.h b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_utils.h new file mode 100644 index 00000000000..4d39ef04222 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vcd_ddl_utils.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_DDL_UTILS_H_ +#define _VCD_DDL_UTILS_H_ + +#include "vcd_ddl_core.h" +#include "vcd_ddl.h" + +extern u32 vidc_msg_pmem; +extern u32 vidc_msg_timing; + +enum timing_data { + DEC_OP_TIME, + DEC_IP_TIME, + ENC_OP_TIME, + MAX_TIME_DATA +}; + +#define DDL_INLINE + +#define DDL_ALIGN_SIZE(sz, guard_bytes, align_mask) \ + (((u32)(sz) + guard_bytes) & align_mask) + +#define DDL_MALLOC(x) kmalloc(x, GFP_KERNEL) +#define DDL_FREE(x) { if ((x)) kfree((x)); (x) = NULL; } + +#define DBG_PMEM(x...) \ +do { \ + if (vidc_msg_pmem) \ + printk(KERN_DEBUG x); \ +} while (0) + +void ddl_pmem_alloc(struct ddl_buf_addr *, u32, u32); + +void ddl_pmem_free(struct ddl_buf_addr *); + +void ddl_set_core_start_time(const char *func_name, u32 index); + +void ddl_calc_core_proc_time(const char *func_name, u32 index); + +void ddl_reset_core_time_variables(u32 index); + +#define DDL_ASSERT(x) +#define DDL_MEMSET(src, value, len) memset((src), (value), (len)) +#define DDL_MEMCPY(dest, src, len) memcpy((dest), (src), (len)) + +#define DDL_ADDR_IS_ALIGNED(addr, align_bytes) \ +(!((u32)(addr) & ((align_bytes) - 1))) + +#endif diff --git a/drivers/video/msm/vidc/720p/ddl/vidc.c b/drivers/video/msm/vidc/720p/ddl/vidc.c new file mode 100644 index 00000000000..8e7abc482d0 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vidc.c @@ -0,0 +1,801 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include "vidc.h" +#include "vidc_type.h" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +#define VIDC_720P_VERSION_STRING "VIDC_V1.0" +u8 *vidc_base_addr; + +#ifdef VIDC_REGISTER_LOG_INTO_BUFFER +char vidclog[VIDC_REGLOG_BUFSIZE]; +unsigned int vidclog_index; +#endif + +void vidc_720p_set_device_virtual_base(u8 *core_virtual_base_addr) +{ + vidc_base_addr = core_virtual_base_addr; +} + +void vidc_720p_init(char **ppsz_version, u32 i_firmware_size, + u32 *pi_firmware_address, + enum vidc_720p_endian dma_endian, + u32 interrupt_off, + enum vidc_720p_interrupt_level_selection + interrupt_sel, u32 interrupt_mask) +{ + if (ppsz_version) + *ppsz_version = VIDC_720P_VERSION_STRING; + + if (interrupt_sel == VIDC_720P_INTERRUPT_LEVEL_SEL) + VIDC_IO_OUT(REG_491082, 0); + else + VIDC_IO_OUT(REG_491082, 1); + + if (interrupt_off) + VIDC_IO_OUT(REG_609676, 1); + else + VIDC_IO_OUT(REG_609676, 0); + + VIDC_IO_OUT(REG_614776, 1); + + VIDC_IO_OUT(REG_418173, 0); + + VIDC_IO_OUT(REG_418173, interrupt_mask); + + VIDC_IO_OUT(REG_736316, dma_endian); + + VIDC_IO_OUT(REG_215724, 0); + + VIDC_IO_OUT(REG_361582, 1); + + VIDC_IO_OUT(REG_591577, i_firmware_size); + + VIDC_IO_OUT(REG_203921, pi_firmware_address); + + VIDC_IO_OUT(REG_531515_ADDR, 0); + + VIDC_IO_OUT(REG_614413, 1); +} + +u32 vidc_720p_do_sw_reset(void) +{ + + u32 fw_start = 0; + VIDC_BUSY_WAIT(5); + VIDC_IO_OUT(REG_224135, 0); + VIDC_BUSY_WAIT(5); + VIDC_IO_OUT(REG_193553, 0); + VIDC_BUSY_WAIT(5); + VIDC_IO_OUT(REG_141269, 1); + VIDC_BUSY_WAIT(15); + VIDC_IO_OUT(REG_141269, 0); + VIDC_BUSY_WAIT(5); + VIDC_IO_IN(REG_193553, &fw_start); + + if (!fw_start) { + DBG("\n VIDC-SW-RESET-FAILS!"); + return false; + } + return true; +} + +u32 vidc_720p_reset_is_success() +{ + u32 stagecounter = 0; + VIDC_IO_IN(REG_352831, &stagecounter); + stagecounter &= 0xff; + if (stagecounter != 0xe5) { + DBG("\n VIDC-CPU_RESET-FAILS!"); + VIDC_IO_OUT(REG_224135, 0); + msleep(10); + return false; + } + return true; +} + +void vidc_720p_start_cpu(enum vidc_720p_endian dma_endian, + u32 *icontext_bufferstart, + u32 *debug_core_dump_addr, + u32 debug_buffer_size) +{ + u32 dbg_info_input0_reg = 0x1; + VIDC_IO_OUT(REG_361582, 0); + VIDC_IO_OUT(REG_958768, icontext_bufferstart); + VIDC_IO_OUT(REG_736316, dma_endian); + if (debug_buffer_size) { + dbg_info_input0_reg = (debug_buffer_size << 0x10) + | (0x2 << 1) | 0x1; + VIDC_IO_OUT(REG_166247, debug_core_dump_addr); + } + VIDC_IO_OUT(REG_699747, dbg_info_input0_reg); + VIDC_IO_OUT(REG_224135, 1); +} + +u32 vidc_720p_cpu_start() +{ + u32 fw_status = 0x0; + VIDC_IO_IN(REG_381535, &fw_status); + if (fw_status != 0x02) + return false; + return true; +} + + +void vidc_720p_stop_fw(void) +{ + VIDC_IO_OUT(REG_193553, 0); + VIDC_IO_OUT(REG_224135, 0); +} + +void vidc_720p_get_interrupt_status(u32 *interrupt_status, + u32 *cmd_err_status, u32 *disp_pic_err_status, u32 *op_failed) +{ + u32 err_status; + VIDC_IO_IN(REG_512143, interrupt_status); + VIDC_IO_IN(REG_300310, &err_status); + *cmd_err_status = err_status & 0xffff; + *disp_pic_err_status = (err_status & 0xffff0000) >> 16; + VIDC_IO_INF(REG_724381, OPERATION_FAILED, \ + op_failed); +} + +void vidc_720p_interrupt_done_clear(void) +{ + VIDC_IO_OUT(REG_614776, 1); + VIDC_IO_OUT(REG_97293, 4); +} + +void vidc_720p_submit_command(u32 ch_id, u32 cmd_id) +{ + u32 fw_status; + VIDC_IO_OUT(REG_97293, ch_id); + VIDC_IO_OUT(REG_62325, cmd_id); + VIDC_DEBUG_REGISTER_LOG; + VIDC_IO_IN(REG_381535, &fw_status); + VIDC_IO_OUT(REG_926519, fw_status); +} + +u32 vidc_720p_engine_reset(u32 ch_id, + enum vidc_720p_endian dma_endian, + enum vidc_720p_interrupt_level_selection interrupt_sel, + u32 interrupt_mask +) +{ + u32 op_done = 0; + u32 counter = 0; + + VIDC_LOGERR_STRING("ENG-RESET!!"); + /* issue the engine reset command */ + vidc_720p_submit_command(ch_id, VIDC_720P_CMD_MFC_ENGINE_RESET); + + do { + VIDC_BUSY_WAIT(20); + VIDC_IO_IN(REG_982553, &op_done); + counter++; + } while (!op_done && counter < 10); + + if (!op_done) { + /* Reset fails */ + return false ; + } + + /* write invalid channel id */ + VIDC_IO_OUT(REG_97293, 4); + + /* Set INT_PULSE_SEL */ + if (interrupt_sel == VIDC_720P_INTERRUPT_LEVEL_SEL) + VIDC_IO_OUT(REG_491082, 0); + else + VIDC_IO_OUT(REG_491082, 1); + + if (!interrupt_mask) { + /* Disable interrupt */ + VIDC_IO_OUT(REG_609676, 1); + } else { + /* Enable interrupt */ + VIDC_IO_OUT(REG_609676, 0); + } + + /* Clear any pending interrupt */ + VIDC_IO_OUT(REG_614776, 1); + + /* Set INT_ENABLE_REG */ + VIDC_IO_OUT(REG_418173, interrupt_mask); + + /*Sets the DMA endianness */ + VIDC_IO_OUT(REG_736316, dma_endian); + + /*Restore ARM endianness */ + VIDC_IO_OUT(REG_215724, 0); + + /* retun engine reset success */ + return true ; +} + +void vidc_720p_set_channel(u32 i_ch_id, + enum vidc_720p_enc_dec_selection + enc_dec_sel, enum vidc_720p_codec codec, + u32 *pi_fw, u32 i_firmware_size) +{ + u32 std_sel = 0; + VIDC_IO_OUT(REG_661565, 0); + + if (enc_dec_sel) + std_sel = VIDC_REG_713080_ENC_ON_BMSK; + + std_sel |= (u32) codec; + + VIDC_IO_OUT(REG_713080, std_sel); + + switch (codec) { + default: + case VIDC_720P_DIVX: + case VIDC_720P_XVID: + case VIDC_720P_MPEG4: + { + if (enc_dec_sel == VIDC_720P_ENCODER) + VIDC_IO_OUT(REG_765787, pi_fw); + else + VIDC_IO_OUT(REG_225040, pi_fw); + break; + } + case VIDC_720P_H264: + { + if (enc_dec_sel == VIDC_720P_ENCODER) + VIDC_IO_OUT(REG_942456, pi_fw); + else + VIDC_IO_OUT(REG_942170_ADDR_3, pi_fw); + break; + } + case VIDC_720P_H263: + { + if (enc_dec_sel == VIDC_720P_ENCODER) + VIDC_IO_OUT(REG_765787, pi_fw); + else + VIDC_IO_OUT(REG_942170_ADDR_6, pi_fw); + break; + } + case VIDC_720P_VC1: + { + VIDC_IO_OUT(REG_880188, pi_fw); + break; + } + case VIDC_720P_MPEG2: + { + VIDC_IO_OUT(REG_40293, pi_fw); + break; + } + } + VIDC_IO_OUT(REG_591577, i_firmware_size); + + vidc_720p_submit_command(i_ch_id, VIDC_720P_CMD_CHSET); +} + +void vidc_720p_encode_set_profile(u32 i_profile, u32 i_level) +{ + u32 profile_level = i_profile|(i_level << 0x8); + VIDC_IO_OUT(REG_839021, profile_level); +} + +void vidc_720p_set_frame_size(u32 i_size_x, u32 i_size_y) +{ + VIDC_IO_OUT(REG_999267, i_size_x); + + VIDC_IO_OUT(REG_345712, i_size_y); +} + +void vidc_720p_encode_set_fps(u32 i_rc_frame_rate) +{ + VIDC_IO_OUT(REG_625444, i_rc_frame_rate); +} + +void vidc_720p_encode_set_short_header(u32 i_short_header) +{ + VIDC_IO_OUT(REG_314290, i_short_header); +} + +void vidc_720p_encode_set_vop_time(u32 vop_time_resolution, + u32 vop_time_increment) +{ + u32 enable_vop, vop_timing_reg; + if (!vop_time_resolution) + VIDC_IO_OUT(REG_64895, 0x0); + else { + enable_vop = 0x1; + vop_timing_reg = (enable_vop << 0x1f) | + (vop_time_resolution << 0x10) | vop_time_increment; + VIDC_IO_OUT(REG_64895, vop_timing_reg); + } +} + +void vidc_720p_encode_set_hec_period(u32 hec_period) +{ + VIDC_IO_OUT(REG_407718, hec_period); +} + +void vidc_720p_encode_set_qp_params(u32 i_max_qp, u32 i_min_qp) +{ + u32 qp = i_min_qp | (i_max_qp << 0x8); + VIDC_IO_OUT(REG_734318, qp); +} + +void vidc_720p_encode_set_rc_config(u32 enable_frame_level_rc, + u32 enable_mb_level_rc_flag, + u32 i_frame_qp, u32 pframe_qp) +{ + u32 rc_config = i_frame_qp; + + if (enable_frame_level_rc) + rc_config |= (0x1 << 0x9); + + if (enable_mb_level_rc_flag) + rc_config |= (0x1 << 0x8); + + VIDC_IO_OUT(REG_58211, rc_config); + VIDC_IO_OUT(REG_548359, pframe_qp); +} + +void vidc_720p_encode_set_bit_rate(u32 i_target_bitrate) +{ + VIDC_IO_OUT(REG_174150, i_target_bitrate); +} + +void vidc_720p_encoder_set_param_change(u32 enc_param_change) +{ + VIDC_IO_OUT(REG_804959, enc_param_change); +} + +void vidc_720p_encode_set_control_param(u32 param_val) +{ + VIDC_IO_OUT(REG_128234, param_val); +} + +void vidc_720p_encode_set_frame_level_rc_params(u32 i_reaction_coeff) +{ + VIDC_IO_OUT(REG_677784, i_reaction_coeff); +} + +void vidc_720p_encode_set_mb_level_rc_params(u32 dark_region_as_flag, + u32 smooth_region_as_flag, + u32 static_region_as_flag, + u32 activity_region_flag) +{ + u32 mb_level_rc = 0x0; + if (activity_region_flag) + mb_level_rc |= 0x1; + if (static_region_as_flag) + mb_level_rc |= (0x1 << 0x1); + if (smooth_region_as_flag) + mb_level_rc |= (0x1 << 0x2); + if (dark_region_as_flag) + mb_level_rc |= (0x1 << 0x3); + /* Write MB level rate control */ + VIDC_IO_OUT(REG_995041, mb_level_rc); +} + +void vidc_720p_encode_set_entropy_control(enum vidc_720p_entropy_sel + entropy_sel, + enum vidc_720p_cabac_model + cabac_model_number) +{ + u32 num; + u32 entropy_params = (u32)entropy_sel; + /* Set Model Number */ + if (entropy_sel == VIDC_720P_ENTROPY_SEL_CABAC) { + num = (u32)cabac_model_number; + entropy_params |= (num << 0x2); + } + /* Set Entropy parameters */ + VIDC_IO_OUT(REG_504878, entropy_params); +} + +void vidc_720p_encode_set_db_filter_control(enum vidc_720p_DBConfig + db_config, + u32 i_slice_alpha_offset, + u32 i_slice_beta_offset) +{ + u32 deblock_params; + deblock_params = (u32)db_config; + deblock_params |= + ((i_slice_beta_offset << 0x2) | (i_slice_alpha_offset << 0x7)); + + /* Write deblocking control settings */ + VIDC_IO_OUT(REG_458130, deblock_params); +} + +void vidc_720p_encode_set_intra_refresh_mb_number(u32 i_cir_mb_number) +{ + VIDC_IO_OUT(REG_857491, i_cir_mb_number); +} + +void vidc_720p_encode_set_multi_slice_info(enum + vidc_720p_MSlice_selection + m_slice_sel, + u32 multi_slice_size) +{ + switch (m_slice_sel) { + case VIDC_720P_MSLICE_BY_MB_COUNT: + { + VIDC_IO_OUT(REG_588301, 0x1); + VIDC_IO_OUT(REG_1517, m_slice_sel); + VIDC_IO_OUT(REG_105335, multi_slice_size); + break; + } + case VIDC_720P_MSLICE_BY_BYTE_COUNT: + { + VIDC_IO_OUT(REG_588301, 0x1); + VIDC_IO_OUT(REG_1517, m_slice_sel); + VIDC_IO_OUT(REG_561679, multi_slice_size); + break; + } + case VIDC_720P_MSLICE_BY_GOB: + { + VIDC_IO_OUT(REG_588301, 0x1); + break; + } + default: + case VIDC_720P_MSLICE_OFF: + { + VIDC_IO_OUT(REG_588301, 0x0); + break; + } + } +} + +void vidc_720p_encode_set_dpb_buffer(u32 *pi_enc_dpb_addr, u32 alloc_len) +{ + VIDC_IO_OUT(REG_341928_ADDR, pi_enc_dpb_addr); + VIDC_IO_OUT(REG_319934, alloc_len); +} + +void vidc_720p_encode_set_i_period(u32 i_i_period) +{ + VIDC_IO_OUT(REG_950374, i_i_period); +} + +void vidc_720p_encode_init_codec(u32 i_ch_id, + enum vidc_720p_memory_access_method + memory_access_model) +{ + + VIDC_IO_OUT(REG_841539, memory_access_model); + vidc_720p_submit_command(i_ch_id, VIDC_720P_CMD_INITCODEC); +} + +void vidc_720p_encode_unalign_bitstream(u32 upper_unalign_word, + u32 lower_unalign_word) +{ + VIDC_IO_OUT(REG_792026, upper_unalign_word); + VIDC_IO_OUT(REG_844152, lower_unalign_word); +} + +void vidc_720p_encode_set_seq_header_buffer(u32 ext_buffer_start, + u32 ext_buffer_end, + u32 start_byte_num) +{ + VIDC_IO_OUT(REG_275113_ADDR, ext_buffer_start); + + VIDC_IO_OUT(REG_87912, ext_buffer_start); + + VIDC_IO_OUT(REG_988007_ADDR, ext_buffer_end); + + VIDC_IO_OUT(REG_66693, start_byte_num); +} + +void vidc_720p_encode_frame(u32 ch_id, + u32 ext_buffer_start, + u32 ext_buffer_end, + u32 start_byte_number, u32 y_addr, + u32 c_addr) +{ + VIDC_IO_OUT(REG_275113_ADDR, ext_buffer_start); + + VIDC_IO_OUT(REG_988007_ADDR, ext_buffer_end); + + VIDC_IO_OUT(REG_87912, ext_buffer_start); + + VIDC_IO_OUT(REG_66693, start_byte_number); + + VIDC_IO_OUT(REG_99105, y_addr); + + VIDC_IO_OUT(REG_777113_ADDR, c_addr); + + vidc_720p_submit_command(ch_id, VIDC_720P_CMD_FRAMERUN); +} + +void vidc_720p_encode_get_header(u32 *pi_enc_header_size) +{ + VIDC_IO_IN(REG_114286, pi_enc_header_size); +} + +void vidc_720p_enc_frame_info(struct vidc_720p_enc_frame_info + *enc_frame_info) +{ + VIDC_IO_IN(REG_782249, &enc_frame_info->enc_size); + + VIDC_IO_IN(REG_441270, &enc_frame_info->frame); + + enc_frame_info->frame &= 0x03; + + VIDC_IO_IN(REG_613254, + &enc_frame_info->metadata_exists); +} + +void vidc_720p_decode_bitstream_header(u32 ch_id, + u32 dec_unit_size, + u32 start_byte_num, + u32 ext_buffer_start, + u32 ext_buffer_end, + enum + vidc_720p_memory_access_method + memory_access_model, + u32 decode_order) +{ + VIDC_IO_OUT(REG_965480, decode_order); + + VIDC_IO_OUT(REG_639999, 0x8080); + + VIDC_IO_OUT(REG_275113_ADDR, ext_buffer_start); + + VIDC_IO_OUT(REG_988007_ADDR, ext_buffer_end); + + VIDC_IO_OUT(REG_87912, ext_buffer_end); + + VIDC_IO_OUT(REG_761892, dec_unit_size); + + VIDC_IO_OUT(REG_66693, start_byte_num); + + VIDC_IO_OUT(REG_841539, memory_access_model); + + vidc_720p_submit_command(ch_id, VIDC_720P_CMD_INITCODEC); +} + +void vidc_720p_decode_get_seq_hdr_info(struct vidc_720p_seq_hdr_info + *seq_hdr_info) +{ + u32 display_status; + VIDC_IO_IN(REG_999267, &seq_hdr_info->img_size_x); + + VIDC_IO_IN(REG_345712, &seq_hdr_info->img_size_y); + + VIDC_IO_IN(REG_257463, &seq_hdr_info->min_num_dpb); + + VIDC_IO_IN(REG_854281, &seq_hdr_info->min_dpb_size); + + VIDC_IO_IN(REG_580603, &seq_hdr_info->dec_frm_size); + + VIDC_IO_INF(REG_606447, DISP_PIC_PROFILE, + &seq_hdr_info->profile); + + VIDC_IO_INF(REG_606447, DIS_PIC_LEVEL, + &seq_hdr_info->level); + + VIDC_IO_INF(REG_612715, DISPLAY_STATUS, + &display_status); + seq_hdr_info->progressive = + ((display_status & 0x4) >> 2); + /* bit 3 is for crop existence */ + seq_hdr_info->crop_exists = ((display_status & 0x8) >> 3); + + if (seq_hdr_info->crop_exists) { + /* read the cropping information */ + VIDC_IO_INF(REG_881638, CROP_RIGHT_OFFSET, \ + &seq_hdr_info->crop_right_offset); + VIDC_IO_INF(REG_881638, CROP_LEFT_OFFSET, \ + &seq_hdr_info->crop_left_offset); + VIDC_IO_INF(REG_161486, CROP_BOTTOM_OFFSET, \ + &seq_hdr_info->crop_bottom_offset); + VIDC_IO_INF(REG_161486, CROP_TOP_OFFSET, \ + &seq_hdr_info->crop_top_offset); + } + /* Read the MPEG4 data partitioning indication */ + VIDC_IO_INF(REG_441270, DATA_PARTITIONED, \ + &seq_hdr_info->data_partitioned); + +} + +void vidc_720p_decode_set_dpb_release_buffer_mask(u32 + i_dpb_release_buffer_mask) +{ + VIDC_IO_OUT(REG_603032, i_dpb_release_buffer_mask); +} + +void vidc_720p_decode_set_dpb_buffers(u32 i_buf_index, u32 *pi_dpb_buffer) +{ + VIDC_IO_OUTI(REG_615716, i_buf_index, pi_dpb_buffer); +} + +void vidc_720p_decode_set_comv_buffer(u32 *pi_dpb_comv_buffer, + u32 alloc_len) +{ + VIDC_IO_OUT(REG_456376_ADDR, pi_dpb_comv_buffer); + + VIDC_IO_OUT(REG_490443, alloc_len); +} + +void vidc_720p_decode_set_dpb_details(u32 num_dpb, u32 alloc_len, + u32 *ref_buffer) +{ + VIDC_IO_OUT(REG_518133, ref_buffer); + + VIDC_IO_OUT(REG_267567, 0); + + VIDC_IO_OUT(REG_883500, num_dpb); + + VIDC_IO_OUT(REG_319934, alloc_len); +} + +void vidc_720p_decode_set_mpeg4Post_filter(u32 enable_post_filter) +{ + if (enable_post_filter) + VIDC_IO_OUT(REG_443811, 0x1); + else + VIDC_IO_OUT(REG_443811, 0x0); +} + +void vidc_720p_decode_set_error_control(u32 enable_error_control) +{ + if (enable_error_control) + VIDC_IO_OUT(REG_846346, 0); + else + VIDC_IO_OUT(REG_846346, 1); +} + +void vidc_720p_set_deblock_line_buffer(u32 *pi_deblock_line_buffer_start, + u32 alloc_len) +{ + VIDC_IO_OUT(REG_979942, pi_deblock_line_buffer_start); + + VIDC_IO_OUT(REG_101184, alloc_len); +} + +void vidc_720p_decode_set_mpeg4_data_partitionbuffer(u32 *vsp_buf_start) +{ + VIDC_IO_OUT(REG_958768, vsp_buf_start); +} + +void vidc_720p_decode_setH264VSPBuffer(u32 *pi_vsp_temp_buffer_start) +{ + VIDC_IO_OUT(REG_958768, pi_vsp_temp_buffer_start); +} + +void vidc_720p_decode_frame(u32 ch_id, u32 ext_buffer_start, + u32 ext_buffer_end, u32 dec_unit_size, + u32 start_byte_num, u32 input_frame_tag) +{ + VIDC_IO_OUT(REG_275113_ADDR, ext_buffer_start); + + VIDC_IO_OUT(REG_988007_ADDR, ext_buffer_end); + + VIDC_IO_OUT(REG_87912, ext_buffer_end); + + VIDC_IO_OUT(REG_66693, start_byte_num); + + VIDC_IO_OUT(REG_94750, input_frame_tag); + + VIDC_IO_OUT(REG_761892, dec_unit_size); + + vidc_720p_submit_command(ch_id, VIDC_720P_CMD_FRAMERUN); +} + +void vidc_720p_issue_eos(u32 i_ch_id) +{ + VIDC_IO_OUT(REG_896825, 0x1); + + VIDC_IO_OUT(REG_761892, 0); + + vidc_720p_submit_command(i_ch_id, VIDC_720P_CMD_FRAMERUN); +} + +void vidc_720p_eos_info(u32 *disp_status, u32 *resl_change) +{ + VIDC_IO_INF(REG_612715, DISPLAY_STATUS, disp_status); + (*disp_status) = (*disp_status) & 0x3; + VIDC_IO_INF(REG_724381, RESOLUTION_CHANGE, resl_change); +} + +void vidc_720p_decode_display_info(struct vidc_720p_dec_disp_info + *disp_info) +{ + u32 display_status = 0; + VIDC_IO_INF(REG_612715, DISPLAY_STATUS, &display_status); + + disp_info->disp_status = + (enum vidc_720p_display_status)((display_status & 0x3)); + + disp_info->disp_is_interlace = ((display_status & 0x4) >> 2); + disp_info->crop_exists = ((display_status & 0x8) >> 3); + + disp_info->resl_change = ((display_status & 0x30) >> 4); + + VIDC_IO_INF(REG_724381, RESOLUTION_CHANGE, + &disp_info->reconfig_flush_done); + + VIDC_IO_IN(REG_999267, &disp_info->img_size_x); + + VIDC_IO_IN(REG_345712, &disp_info->img_size_y); + VIDC_IO_IN(REG_151345, &disp_info->y_addr); + VIDC_IO_IN(REG_293983, &disp_info->c_addr); + VIDC_IO_IN(REG_370409, &disp_info->tag_top); + VIDC_IO_IN(REG_438677, &disp_info->tag_bottom); + VIDC_IO_IN(REG_679165, &disp_info->pic_time_top); + VIDC_IO_IN(REG_374150, &disp_info->pic_time_bottom); + + if (disp_info->crop_exists) { + VIDC_IO_INF(REG_881638, CROP_RIGHT_OFFSET, + &disp_info->crop_right_offset); + VIDC_IO_INF(REG_881638, CROP_LEFT_OFFSET, + &disp_info->crop_left_offset); + VIDC_IO_INF(REG_161486, CROP_BOTTOM_OFFSET, + &disp_info->crop_bottom_offset); + VIDC_IO_INF(REG_161486, CROP_TOP_OFFSET, + &disp_info->crop_top_offset); + } + VIDC_IO_IN(REG_613254, &disp_info->metadata_exists); + + VIDC_IO_IN(REG_580603, + &disp_info->input_bytes_consumed); + + VIDC_IO_IN(REG_757835, &disp_info->input_frame_num); + + VIDC_IO_INF(REG_441270, FRAME_TYPE, + &disp_info->input_frame); + + disp_info->input_is_interlace = + ((disp_info->input_frame & 0x4) >> 2); + + disp_info->input_frame &= 0x3; +} + +void vidc_720p_decode_skip_frm_details(u32 *free_luma_dpb) +{ + u32 disp_frm; + VIDC_IO_IN(REG_697961, &disp_frm); + + if (disp_frm == VIDC_720P_NOTCODED) + VIDC_IO_IN(REG_347105, free_luma_dpb); +} + +void vidc_720p_metadata_enable(u32 flag, u32 *input_buffer) +{ + VIDC_IO_OUT(REG_854681, flag); + VIDC_IO_OUT(REG_988552, input_buffer); +} + +void vidc_720p_decode_dynamic_req_reset(void) +{ + VIDC_IO_OUT(REG_76706, 0x0); + VIDC_IO_OUT(REG_147682, 0x0); + VIDC_IO_OUT(REG_896825, 0x0); +} + +void vidc_720p_decode_dynamic_req_set(u32 property) +{ + if (property == VIDC_720P_FLUSH_REQ) + VIDC_IO_OUT(REG_76706, 0x1); + else if (property == VIDC_720P_EXTRADATA) + VIDC_IO_OUT(REG_147682, 0x1); +} + +void vidc_720p_decode_setpassthrough_start(u32 pass_startaddr) +{ + VIDC_IO_OUT(REG_486169, pass_startaddr); +} diff --git a/drivers/video/msm/vidc/720p/ddl/vidc.h b/drivers/video/msm/vidc/720p/ddl/vidc.h new file mode 100644 index 00000000000..685b7cce848 --- /dev/null +++ b/drivers/video/msm/vidc/720p/ddl/vidc.h @@ -0,0 +1,2704 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef VIDC_H +#define VIDC_H +#include +#include +#include + +#define VIDC_720P_IN(reg) VIDC_##reg##_IN +#define VIDC_720P_INM(reg, mask) VIDC_##reg##_INM(mask) +#define VIDC_720P_OUT(reg, val) VIDC_##reg##_OUT(val) +#define VIDC_720P_OUTI(reg, index, val) VIDC_##reg##_OUTI(index, val) +#define VIDC_720P_OUTM(reg, mask, val) VIDC_##reg##_OUTM(mask, val) +#define VIDC_720P_SHFT(reg, field) VIDC_##reg##_##field##_SHFT +#define VIDC_720P_FMSK(reg, field) VIDC_##reg##_##field##_BMSK + +#define VIDC_720P_INF(io, field) (VIDC_720P_INM(io, VIDC_720P_FMSK(io, field)) \ + >> VIDC_720P_SHFT(io, field)) +#define VIDC_720P_OUTF(io, field, val) \ + VIDC_720P_OUTM(io, VIDC_720P_FMSK(io, field), \ + val << VIDC_720P_SHFT(io, field)) + +#define __inpdw(port) ioread32(port) +#define __outpdw(port, val) iowrite32(val, port) + +#define in_dword_masked(addr, mask) (__inpdw(addr) & (mask)) + +#define out_dword(addr, val) __outpdw(addr, val) + +#define out_dword_masked(io, mask, val, shadow) \ +do { \ + shadow = (shadow & (u32)(~(mask))) | ((u32)((val) & (mask))); \ + (void) out_dword(io, shadow); \ +} while (0) + +#define out_dword_masked_ns(io, mask, val, current_reg_content) \ + (void) out_dword(io, ((current_reg_content & (u32)(~(mask))) | \ + ((u32)((val) & (mask))))) + +extern u8 *vidc_base_addr; + +#define VIDC720P_BASE vidc_base_addr +#define VIDC_720P_WRAPPER_REG_BASE (VIDC720P_BASE + \ + 0x00000000) +#define VIDC_720P_WRAPPER_REG_BASE_PHYS VIDC_720P_BASE_PHYS + +#define VIDC_REG_614413_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 00000000) +#define VIDC_REG_614413_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 00000000) +#define VIDC_REG_614413_RMSK 0x1 +#define VIDC_REG_614413_SHFT 0 +#define VIDC_REG_614413_IN \ + in_dword_masked(VIDC_REG_614413_ADDR, \ + VIDC_REG_614413_RMSK) +#define VIDC_REG_614413_INM(m) \ + in_dword_masked(VIDC_REG_614413_ADDR, m) +#define VIDC_REG_614413_OUT(v) \ + out_dword(VIDC_REG_614413_ADDR, v) +#define VIDC_REG_614413_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_614413_ADDR, m, v, \ + VIDC_REG_614413_IN); \ +} while (0) +#define VIDC_REG_614413_DMA_START_BMSK 0x1 +#define VIDC_REG_614413_DMA_START_SHFT 0 + +#define VIDC_REG_591577_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000000c) +#define VIDC_REG_591577_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000000c) +#define VIDC_REG_591577_RMSK 0xffffffff +#define VIDC_REG_591577_SHFT 0 +#define VIDC_REG_591577_IN \ + in_dword_masked(VIDC_REG_591577_ADDR, \ + VIDC_REG_591577_RMSK) +#define VIDC_REG_591577_INM(m) \ + in_dword_masked(VIDC_REG_591577_ADDR, m) +#define VIDC_REG_591577_OUT(v) \ + out_dword(VIDC_REG_591577_ADDR, v) +#define VIDC_REG_591577_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_591577_ADDR, m, v, \ + VIDC_REG_591577_IN); \ +} while (0) +#define VIDC_REG_591577_BOOTCODE_SIZE_BMSK 0xffffffff +#define VIDC_REG_591577_BOOTCODE_SIZE_SHFT 0 + +#define VIDC_REG_203921_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000014) +#define VIDC_REG_203921_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000014) +#define VIDC_REG_203921_RMSK 0xffffffff +#define VIDC_REG_203921_SHFT 0 +#define VIDC_REG_203921_IN \ + in_dword_masked(VIDC_REG_203921_ADDR, \ + VIDC_REG_203921_RMSK) +#define VIDC_REG_203921_INM(m) \ + in_dword_masked(VIDC_REG_203921_ADDR, m) +#define VIDC_REG_203921_OUT(v) \ + out_dword(VIDC_REG_203921_ADDR, v) +#define VIDC_REG_203921_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_203921_ADDR, m, v, \ + VIDC_REG_203921_IN); \ +} while (0) +#define VIDC_REG_203921_DMA_EXTADDR_BMSK 0xffffffff +#define VIDC_REG_203921_DMA_EXTADDR_SHFT 0 + +#define VIDC_REG_275113_ADDR_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000018) +#define VIDC_REG_275113_ADDR_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000018) +#define VIDC_REG_275113_ADDR_RMSK 0xffffffff +#define VIDC_REG_275113_ADDR_SHFT 0 +#define VIDC_REG_275113_ADDR_IN \ + in_dword_masked(VIDC_REG_275113_ADDR_ADDR, \ + VIDC_REG_275113_ADDR_RMSK) +#define VIDC_REG_275113_ADDR_INM(m) \ + in_dword_masked(VIDC_REG_275113_ADDR_ADDR, m) +#define VIDC_REG_275113_ADDR_OUT(v) \ + out_dword(VIDC_REG_275113_ADDR_ADDR, v) +#define VIDC_REG_275113_ADDR_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_275113_ADDR_ADDR, m, v, \ + VIDC_REG_275113_ADDR_IN); \ +} while (0) +#define VIDC_REG_742076_ADDR_BMSK 0xffffffff +#define VIDC_REG_742076_ADDR_SHFT 0 + +#define VIDC_REG_988007_ADDR_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000001c) +#define VIDC_REG_988007_ADDR_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000001c) +#define VIDC_REG_988007_ADDR_RMSK 0xffffffff +#define VIDC_REG_988007_ADDR_SHFT 0 +#define VIDC_REG_988007_ADDR_IN \ + in_dword_masked(VIDC_REG_988007_ADDR_ADDR, \ + VIDC_REG_988007_ADDR_RMSK) +#define VIDC_REG_988007_ADDR_INM(m) \ + in_dword_masked(VIDC_REG_988007_ADDR_ADDR, m) +#define VIDC_REG_988007_ADDR_OUT(v) \ + out_dword(VIDC_REG_988007_ADDR_ADDR, v) +#define VIDC_REG_988007_ADDR_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_988007_ADDR_ADDR, m, v, \ + VIDC_REG_988007_ADDR_IN); \ +} while (0) +#define VIDC_REG_988007_ADDR_EXT_BUF_END_ADDR_BMSK 0xffffffff +#define VIDC_REG_988007_ADDR_EXT_BUF_END_ADDR_SHFT 0 + +#define VIDC_REG_531515_ADDR_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000020) +#define VIDC_REG_531515_ADDR_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000020) +#define VIDC_REG_531515_ADDR_RMSK 0xffffffff +#define VIDC_REG_531515_ADDR_SHFT 0 +#define VIDC_REG_531515_ADDR_IN \ + in_dword_masked(VIDC_REG_531515_ADDR_ADDR, \ + VIDC_REG_531515_ADDR_RMSK) +#define VIDC_REG_531515_ADDR_INM(m) \ + in_dword_masked(VIDC_REG_531515_ADDR_ADDR, m) +#define VIDC_REG_531515_ADDR_OUT(v) \ + out_dword(VIDC_REG_531515_ADDR_ADDR, v) +#define VIDC_REG_531515_ADDR_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_531515_ADDR_ADDR, m, v, \ + VIDC_REG_531515_ADDR_IN); \ +} while (0) +#define VIDC_REG_531515_ADDR_DMA_INT_ADDR_BMSK 0xffffffff +#define VIDC_REG_531515_ADDR_DMA_INT_ADDR_SHFT 0 + +#define VIDC_REG_87912_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000024) +#define VIDC_REG_87912_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000024) +#define VIDC_REG_87912_RMSK 0xffffffff +#define VIDC_REG_87912_SHFT 0 +#define VIDC_REG_87912_IN \ + in_dword_masked(VIDC_REG_87912_ADDR, \ + VIDC_REG_87912_RMSK) +#define VIDC_REG_87912_INM(m) \ + in_dword_masked(VIDC_REG_87912_ADDR, m) +#define VIDC_REG_87912_OUT(v) \ + out_dword(VIDC_REG_87912_ADDR, v) +#define VIDC_REG_87912_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_87912_ADDR, m, v, \ + VIDC_REG_87912_IN); \ +} while (0) +#define VIDC_REG_87912_HOST_PTR_ADDR_BMSK 0xffffffff +#define VIDC_REG_87912_HOST_PTR_ADDR_SHFT 0 + +#define VIDC_REG_896825_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000028) +#define VIDC_REG_896825_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000028) +#define VIDC_REG_896825_RMSK 0x1 +#define VIDC_REG_896825_SHFT 0 +#define VIDC_REG_896825_IN \ + in_dword_masked(VIDC_REG_896825_ADDR, \ + VIDC_REG_896825_RMSK) +#define VIDC_REG_896825_INM(m) \ + in_dword_masked(VIDC_REG_896825_ADDR, m) +#define VIDC_REG_896825_OUT(v) \ + out_dword(VIDC_REG_896825_ADDR, v) +#define VIDC_REG_896825_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_896825_ADDR, m, v, \ + VIDC_REG_896825_IN); \ +} while (0) +#define VIDC_REG_896825_LAST_DEC_BMSK 0x1 +#define VIDC_REG_896825_LAST_DEC_SHFT 0 + +#define VIDC_REG_174526_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000002c) +#define VIDC_REG_174526_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000002c) +#define VIDC_REG_174526_RMSK 0x1 +#define VIDC_REG_174526_SHFT 0 +#define VIDC_REG_174526_IN \ + in_dword_masked(VIDC_REG_174526_ADDR, VIDC_REG_174526_RMSK) +#define VIDC_REG_174526_INM(m) \ + in_dword_masked(VIDC_REG_174526_ADDR, m) +#define VIDC_REG_174526_DONE_M_BMSK 0x1 +#define VIDC_REG_174526_DONE_M_SHFT 0 + +#define VIDC_REG_736316_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000044) +#define VIDC_REG_736316_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000044) +#define VIDC_REG_736316_RMSK 0x1 +#define VIDC_REG_736316_SHFT 0 +#define VIDC_REG_736316_IN \ + in_dword_masked(VIDC_REG_736316_ADDR, \ + VIDC_REG_736316_RMSK) +#define VIDC_REG_736316_INM(m) \ + in_dword_masked(VIDC_REG_736316_ADDR, m) +#define VIDC_REG_736316_OUT(v) \ + out_dword(VIDC_REG_736316_ADDR, v) +#define VIDC_REG_736316_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_736316_ADDR, m, v, \ + VIDC_REG_736316_IN); \ +} while (0) +#define VIDC_REG_736316_BITS_ENDIAN_BMSK 0x1 +#define VIDC_REG_736316_BITS_ENDIAN_SHFT 0 + +#define VIDC_REG_761892_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000054) +#define VIDC_REG_761892_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000054) +#define VIDC_REG_761892_RMSK 0xffffffff +#define VIDC_REG_761892_SHFT 0 +#define VIDC_REG_761892_IN \ + in_dword_masked(VIDC_REG_761892_ADDR, \ + VIDC_REG_761892_RMSK) +#define VIDC_REG_761892_INM(m) \ + in_dword_masked(VIDC_REG_761892_ADDR, m) +#define VIDC_REG_761892_OUT(v) \ + out_dword(VIDC_REG_761892_ADDR, v) +#define VIDC_REG_761892_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_761892_ADDR, m, v, \ + VIDC_REG_761892_IN); \ +} while (0) +#define VIDC_REG_761892_DEC_UNIT_SIZE_BMSK 0xffffffff +#define VIDC_REG_761892_DEC_UNIT_SIZE_SHFT 0 + +#define VIDC_REG_782249_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000058) +#define VIDC_REG_782249_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000058) +#define VIDC_REG_782249_RMSK 0xffffffff +#define VIDC_REG_782249_SHFT 0 +#define VIDC_REG_782249_IN \ + in_dword_masked(VIDC_REG_782249_ADDR, \ + VIDC_REG_782249_RMSK) +#define VIDC_REG_782249_INM(m) \ + in_dword_masked(VIDC_REG_782249_ADDR, m) +#define VIDC_REG_782249_ENC_UNIT_SIZE_BMSK 0xffffffff +#define VIDC_REG_782249_ENC_UNIT_SIZE_SHFT 0 + +#define VIDC_REG_66693_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000005c) +#define VIDC_REG_66693_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000005c) +#define VIDC_REG_66693_RMSK 0xf +#define VIDC_REG_66693_SHFT 0 +#define VIDC_REG_66693_IN \ + in_dword_masked(VIDC_REG_66693_ADDR, \ + VIDC_REG_66693_RMSK) +#define VIDC_REG_66693_INM(m) \ + in_dword_masked(VIDC_REG_66693_ADDR, m) +#define VIDC_REG_66693_OUT(v) \ + out_dword(VIDC_REG_66693_ADDR, v) +#define VIDC_REG_66693_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_66693_ADDR, m, v, \ + VIDC_REG_66693_IN); \ +} while (0) +#define VIDC_REG_66693_START_BYTE_NUM_BMSK 0xf +#define VIDC_REG_66693_START_BYTE_NUM_SHFT 0 + +#define VIDC_REG_114286_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000060) +#define VIDC_REG_114286_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000060) +#define VIDC_REG_114286_RMSK 0xffffffff +#define VIDC_REG_114286_SHFT 0 +#define VIDC_REG_114286_IN \ + in_dword_masked(VIDC_REG_114286_ADDR, \ + VIDC_REG_114286_RMSK) +#define VIDC_REG_114286_INM(m) \ + in_dword_masked(VIDC_REG_114286_ADDR, m) +#define VIDC_REG_114286_ENC_HEADER_SIZE_BMSK 0xffffffff +#define VIDC_REG_114286_ENC_HEADER_SIZE_SHFT 0 + +#define VIDC_REG_713080_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000100) +#define VIDC_REG_713080_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000100) +#define VIDC_REG_713080_RMSK 0x1f +#define VIDC_REG_713080_SHFT 0 +#define VIDC_REG_713080_IN \ + in_dword_masked(VIDC_REG_713080_ADDR, \ + VIDC_REG_713080_RMSK) +#define VIDC_REG_713080_INM(m) \ + in_dword_masked(VIDC_REG_713080_ADDR, m) +#define VIDC_REG_713080_OUT(v) \ + out_dword(VIDC_REG_713080_ADDR, v) +#define VIDC_REG_713080_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_713080_ADDR, m, v, \ + VIDC_REG_713080_IN); \ +} while (0) +#define VIDC_REG_713080_ENC_ON_BMSK 0x10 +#define VIDC_REG_713080_ENC_ON_SHFT 0x4 +#define VIDC_REG_713080_STANDARD_SEL_BMSK 0xf +#define VIDC_REG_713080_STANDARD_SEL_SHFT 0 + +#define VIDC_REG_97293_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000104) +#define VIDC_REG_97293_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000104) +#define VIDC_REG_97293_RMSK 0x1f +#define VIDC_REG_97293_SHFT 0 +#define VIDC_REG_97293_IN \ + in_dword_masked(VIDC_REG_97293_ADDR, VIDC_REG_97293_RMSK) +#define VIDC_REG_97293_INM(m) \ + in_dword_masked(VIDC_REG_97293_ADDR, m) +#define VIDC_REG_97293_OUT(v) \ + out_dword(VIDC_REG_97293_ADDR, v) +#define VIDC_REG_97293_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_97293_ADDR, m, v, \ + VIDC_REG_97293_IN); \ +} while (0) +#define VIDC_REG_97293_CH_ID_BMSK 0x1f +#define VIDC_REG_97293_CH_ID_SHFT 0 + +#define VIDC_REG_224135_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000108) +#define VIDC_REG_224135_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000108) +#define VIDC_REG_224135_RMSK 0x1 +#define VIDC_REG_224135_SHFT 0 +#define VIDC_REG_224135_IN \ + in_dword_masked(VIDC_REG_224135_ADDR, \ + VIDC_REG_224135_RMSK) +#define VIDC_REG_224135_INM(m) \ + in_dword_masked(VIDC_REG_224135_ADDR, m) +#define VIDC_REG_224135_OUT(v) \ + out_dword(VIDC_REG_224135_ADDR, v) +#define VIDC_REG_224135_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_224135_ADDR, m, v, \ + VIDC_REG_224135_IN); \ +} while (0) +#define VIDC_REG_224135_CPU_RESET_BMSK 0x1 +#define VIDC_REG_224135_CPU_RESET_SHFT 0 + +#define VIDC_REG_832522_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000010c) +#define VIDC_REG_832522_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000010c) +#define VIDC_REG_832522_RMSK 0x1 +#define VIDC_REG_832522_SHFT 0 +#define VIDC_REG_832522_IN \ + in_dword_masked(VIDC_REG_832522_ADDR, VIDC_REG_832522_RMSK) +#define VIDC_REG_832522_INM(m) \ + in_dword_masked(VIDC_REG_832522_ADDR, m) +#define VIDC_REG_832522_OUT(v) \ + out_dword(VIDC_REG_832522_ADDR, v) +#define VIDC_REG_832522_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_832522_ADDR, m, v, \ + VIDC_REG_832522_IN); \ +} while (0) +#define VIDC_REG_832522_FW_END_BMSK 0x1 +#define VIDC_REG_832522_FW_END_SHFT 0 + +#define VIDC_REG_361582_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000110) +#define VIDC_REG_361582_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000110) +#define VIDC_REG_361582_RMSK 0x1 +#define VIDC_REG_361582_SHFT 0 +#define VIDC_REG_361582_IN \ + in_dword_masked(VIDC_REG_361582_ADDR, \ + VIDC_REG_361582_RMSK) +#define VIDC_REG_361582_INM(m) \ + in_dword_masked(VIDC_REG_361582_ADDR, m) +#define VIDC_REG_361582_OUT(v) \ + out_dword(VIDC_REG_361582_ADDR, v) +#define VIDC_REG_361582_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_361582_ADDR, m, v, \ + VIDC_REG_361582_IN); \ +} while (0) +#define VIDC_REG_361582_BUS_MASTER_BMSK 0x1 +#define VIDC_REG_361582_BUS_MASTER_SHFT 0 + +#define VIDC_REG_314435_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000114) +#define VIDC_REG_314435_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000114) +#define VIDC_REG_314435_RMSK 0x1 +#define VIDC_REG_314435_SHFT 0 +#define VIDC_REG_314435_IN \ + in_dword_masked(VIDC_REG_314435_ADDR, \ + VIDC_REG_314435_RMSK) +#define VIDC_REG_314435_INM(m) \ + in_dword_masked(VIDC_REG_314435_ADDR, m) +#define VIDC_REG_314435_OUT(v) \ + out_dword(VIDC_REG_314435_ADDR, v) +#define VIDC_REG_314435_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_314435_ADDR, m, v, \ + VIDC_REG_314435_IN); \ +} while (0) +#define VIDC_REG_314435_FRAME_START_BMSK 0x1 +#define VIDC_REG_314435_FRAME_START_SHFT 0 + +#define VIDC_REG_999267_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000118) +#define VIDC_REG_999267_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000118) +#define VIDC_REG_999267_RMSK 0xffff +#define VIDC_REG_999267_SHFT 0 +#define VIDC_REG_999267_IN \ + in_dword_masked(VIDC_REG_999267_ADDR, \ + VIDC_REG_999267_RMSK) +#define VIDC_REG_999267_INM(m) \ + in_dword_masked(VIDC_REG_999267_ADDR, m) +#define VIDC_REG_999267_OUT(v) \ + out_dword(VIDC_REG_999267_ADDR, v) +#define VIDC_REG_999267_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_999267_ADDR, m, v, \ + VIDC_REG_999267_IN); \ +} while (0) +#define VIDC_REG_999267_IMG_SIZE_X_BMSK 0xffff +#define VIDC_REG_999267_IMG_SIZE_X_SHFT 0 + +#define VIDC_REG_345712_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000011c) +#define VIDC_REG_345712_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000011c) +#define VIDC_REG_345712_RMSK 0xffff +#define VIDC_REG_345712_SHFT 0 +#define VIDC_REG_345712_IN \ + in_dword_masked(VIDC_REG_345712_ADDR, \ + VIDC_REG_345712_RMSK) +#define VIDC_REG_345712_INM(m) \ + in_dword_masked(VIDC_REG_345712_ADDR, m) +#define VIDC_REG_345712_OUT(v) \ + out_dword(VIDC_REG_345712_ADDR, v) +#define VIDC_REG_345712_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_345712_ADDR, m, v, \ + VIDC_REG_345712_IN); \ +} while (0) +#define VIDC_REG_345712_IMG_SIZE_Y_BMSK 0xffff +#define VIDC_REG_345712_IMG_SIZE_Y_SHFT 0 + +#define VIDC_REG_443811_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000124) +#define VIDC_REG_443811_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000124) +#define VIDC_REG_443811_RMSK 0x1 +#define VIDC_REG_443811_SHFT 0 +#define VIDC_REG_443811_IN \ + in_dword_masked(VIDC_REG_443811_ADDR, VIDC_REG_443811_RMSK) +#define VIDC_REG_443811_INM(m) \ + in_dword_masked(VIDC_REG_443811_ADDR, m) +#define VIDC_REG_443811_OUT(v) \ + out_dword(VIDC_REG_443811_ADDR, v) +#define VIDC_REG_443811_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_443811_ADDR, m, v, \ + VIDC_REG_443811_IN); \ +} while (0) +#define VIDC_REG_443811_POST_ON_BMSK 0x1 +#define VIDC_REG_443811_POST_ON_SHFT 0 + +#define VIDC_REG_538267_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000128) +#define VIDC_REG_538267_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000128) +#define VIDC_REG_538267_RMSK 0xffffffff +#define VIDC_REG_538267_SHFT 0 +#define VIDC_REG_538267_IN \ + in_dword_masked(VIDC_REG_538267_ADDR, \ + VIDC_REG_538267_RMSK) +#define VIDC_REG_538267_INM(m) \ + in_dword_masked(VIDC_REG_538267_ADDR, m) +#define VIDC_REG_538267_OUT(v) \ + out_dword(VIDC_REG_538267_ADDR, v) +#define VIDC_REG_538267_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_538267_ADDR, m, v, \ + VIDC_REG_538267_IN); \ +} while (0) +#define VIDC_REG_538267_QUOTIENT_VAL_BMSK 0xffff0000 +#define VIDC_REG_538267_QUOTIENT_VAL_SHFT 0x10 +#define VIDC_REG_538267_REMAINDER_VAL_BMSK 0xffff +#define VIDC_REG_538267_REMAINDER_VAL_SHFT 0 + +#define VIDC_REG_661565_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000012c) +#define VIDC_REG_661565_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000012c) +#define VIDC_REG_661565_RMSK 0x1 +#define VIDC_REG_661565_SHFT 0 +#define VIDC_REG_661565_IN \ + in_dword_masked(VIDC_REG_661565_ADDR, \ + VIDC_REG_661565_RMSK) +#define VIDC_REG_661565_INM(m) \ + in_dword_masked(VIDC_REG_661565_ADDR, m) +#define VIDC_REG_661565_OUT(v) \ + out_dword(VIDC_REG_661565_ADDR, v) +#define VIDC_REG_661565_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_661565_ADDR, m, v, \ + VIDC_REG_661565_IN); \ +} while (0) +#define VIDC_REG_661565_SEQUENCE_START_BMSK 0x1 +#define VIDC_REG_661565_SEQUENCE_START_SHFT 0 + +#define VIDC_REG_141269_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000130) +#define VIDC_REG_141269_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000130) +#define VIDC_REG_141269_RMSK 0x1 +#define VIDC_REG_141269_SHFT 0 +#define VIDC_REG_141269_IN \ + in_dword_masked(VIDC_REG_141269_ADDR, \ + VIDC_REG_141269_RMSK) +#define VIDC_REG_141269_INM(m) \ + in_dword_masked(VIDC_REG_141269_ADDR, m) +#define VIDC_REG_141269_OUT(v) \ + out_dword(VIDC_REG_141269_ADDR, v) +#define VIDC_REG_141269_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_141269_ADDR, m, v, \ + VIDC_REG_141269_IN); \ +} while (0) +#define VIDC_REG_141269_SW_RESET_BMSK 0x1 +#define VIDC_REG_141269_SW_RESET_SHFT 0 + +#define VIDC_REG_193553_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000134) +#define VIDC_REG_193553_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000134) +#define VIDC_REG_193553_RMSK 0x1 +#define VIDC_REG_193553_SHFT 0 +#define VIDC_REG_193553_IN \ + in_dword_masked(VIDC_REG_193553_ADDR, \ + VIDC_REG_193553_RMSK) +#define VIDC_REG_193553_INM(m) \ + in_dword_masked(VIDC_REG_193553_ADDR, m) +#define VIDC_REG_193553_OUT(v) \ + out_dword(VIDC_REG_193553_ADDR, v) +#define VIDC_REG_193553_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_193553_ADDR, m, v, \ + VIDC_REG_193553_IN); \ +} while (0) +#define VIDC_REG_193553_FW_START_BMSK 0x1 +#define VIDC_REG_193553_FW_START_SHFT 0 + +#define VIDC_REG_215724_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000138) +#define VIDC_REG_215724_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000138) +#define VIDC_REG_215724_RMSK 0x1 +#define VIDC_REG_215724_SHFT 0 +#define VIDC_REG_215724_IN \ + in_dword_masked(VIDC_REG_215724_ADDR, \ + VIDC_REG_215724_RMSK) +#define VIDC_REG_215724_INM(m) \ + in_dword_masked(VIDC_REG_215724_ADDR, m) +#define VIDC_REG_215724_OUT(v) \ + out_dword(VIDC_REG_215724_ADDR, v) +#define VIDC_REG_215724_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_215724_ADDR, m, v, \ + VIDC_REG_215724_IN); \ +} while (0) +#define VIDC_REG_215724_ARM_ENDIAN_BMSK 0x1 +#define VIDC_REG_215724_ARM_ENDIAN_SHFT 0 + +#define VIDC_REG_846346_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000013c) +#define VIDC_REG_846346_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000013c) +#define VIDC_REG_846346_RMSK 0x1 +#define VIDC_REG_846346_SHFT 0 +#define VIDC_REG_846346_IN \ + in_dword_masked(VIDC_REG_846346_ADDR, \ + VIDC_REG_846346_RMSK) +#define VIDC_REG_846346_INM(m) \ + in_dword_masked(VIDC_REG_846346_ADDR, m) +#define VIDC_REG_846346_OUT(v) \ + out_dword(VIDC_REG_846346_ADDR, v) +#define VIDC_REG_846346_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_846346_ADDR, m, v, \ + VIDC_REG_846346_IN); \ +} while (0) +#define VIDC_REG_846346_ERR_CTRL_BMSK 0x1 +#define VIDC_REG_846346_ERR_CTRL_SHFT 0 + +#define VIDC_REG_765787_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000200) +#define VIDC_REG_765787_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000200) +#define VIDC_REG_765787_RMSK 0xffffffff +#define VIDC_REG_765787_SHFT 0 +#define VIDC_REG_765787_IN \ + in_dword_masked(VIDC_REG_765787_ADDR, \ + VIDC_REG_765787_RMSK) +#define VIDC_REG_765787_INM(m) \ + in_dword_masked(VIDC_REG_765787_ADDR, m) +#define VIDC_REG_765787_OUT(v) \ + out_dword(VIDC_REG_765787_ADDR, v) +#define VIDC_REG_765787_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_765787_ADDR, m, v, \ + VIDC_REG_765787_IN); \ +} while (0) +#define VIDC_REG_765787_FW_STT_ADDR_0_BMSK 0xffffffff +#define VIDC_REG_765787_FW_STT_ADDR_0_SHFT 0 + +#define VIDC_REG_225040_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000204) +#define VIDC_REG_225040_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000204) +#define VIDC_REG_225040_RMSK 0xffffffff +#define VIDC_REG_225040_SHFT 0 +#define VIDC_REG_225040_IN \ + in_dword_masked(VIDC_REG_225040_ADDR, \ + VIDC_REG_225040_RMSK) +#define VIDC_REG_225040_INM(m) \ + in_dword_masked(VIDC_REG_225040_ADDR, m) +#define VIDC_REG_225040_OUT(v) \ + out_dword(VIDC_REG_225040_ADDR, v) +#define VIDC_REG_225040_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_225040_ADDR, m, v, \ + VIDC_REG_225040_IN); \ +} while (0) +#define VIDC_REG_225040_FW_STT_ADDR_1_BMSK 0xffffffff +#define VIDC_REG_225040_FW_STT_ADDR_1_SHFT 0 + +#define VIDC_REG_942456_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000208) +#define VIDC_REG_942456_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000208) +#define VIDC_REG_942456_RMSK 0xffffffff +#define VIDC_REG_942456_SHFT 0 +#define VIDC_REG_942456_IN \ + in_dword_masked(VIDC_REG_942456_ADDR, \ + VIDC_REG_942456_RMSK) +#define VIDC_REG_942456_INM(m) \ + in_dword_masked(VIDC_REG_942456_ADDR, m) +#define VIDC_REG_942456_OUT(v) \ + out_dword(VIDC_REG_942456_ADDR, v) +#define VIDC_REG_942456_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_942456_ADDR, m, v, \ + VIDC_REG_942456_IN); \ +} while (0) +#define VIDC_REG_942456_FW_STT_ADDR_2_BMSK 0xffffffff +#define VIDC_REG_942456_FW_STT_ADDR_2_SHFT 0 + +#define VIDC_REG_942170_ADDR_3_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000020c) +#define VIDC_REG_942170_ADDR_3_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000020c) +#define VIDC_REG_942170_ADDR_3_RMSK 0xffffffff +#define VIDC_REG_942170_ADDR_3_SHFT 0 +#define VIDC_REG_942170_ADDR_3_IN \ + in_dword_masked(VIDC_REG_942170_ADDR_3_ADDR, \ + VIDC_REG_942170_ADDR_3_RMSK) +#define VIDC_REG_942170_ADDR_3_INM(m) \ + in_dword_masked(VIDC_REG_942170_ADDR_3_ADDR, m) +#define VIDC_REG_942170_ADDR_3_OUT(v) \ + out_dword(VIDC_REG_942170_ADDR_3_ADDR, v) +#define VIDC_REG_942170_ADDR_3_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_942170_ADDR_3_ADDR, m, v, \ + VIDC_REG_942170_ADDR_3_IN); \ +} while (0) +#define VIDC_REG_942170_ADDR_3_FW_STT_ADDR_3_BMSK 0xffffffff +#define VIDC_REG_942170_ADDR_3_FW_STT_ADDR_3_SHFT 0 + +#define VIDC_REG_880188_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000210) +#define VIDC_REG_880188_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000210) +#define VIDC_REG_880188_RMSK 0xffffffff +#define VIDC_REG_880188_SHFT 0 +#define VIDC_REG_880188_IN \ + in_dword_masked(VIDC_REG_880188_ADDR, \ + VIDC_REG_880188_RMSK) +#define VIDC_REG_880188_INM(m) \ + in_dword_masked(VIDC_REG_880188_ADDR, m) +#define VIDC_REG_880188_OUT(v) \ + out_dword(VIDC_REG_880188_ADDR, v) +#define VIDC_REG_880188_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_880188_ADDR, m, v, \ + VIDC_REG_880188_IN); \ +} while (0) +#define VIDC_REG_880188_FW_STT_ADDR_4_BMSK 0xffffffff +#define VIDC_REG_880188_FW_STT_ADDR_4_SHFT 0 + +#define VIDC_REG_40293_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000214) +#define VIDC_REG_40293_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000214) +#define VIDC_REG_40293_RMSK 0xffffffff +#define VIDC_REG_40293_SHFT 0 +#define VIDC_REG_40293_IN \ + in_dword_masked(VIDC_REG_40293_ADDR, \ + VIDC_REG_40293_RMSK) +#define VIDC_REG_40293_INM(m) \ + in_dword_masked(VIDC_REG_40293_ADDR, m) +#define VIDC_REG_40293_OUT(v) \ + out_dword(VIDC_REG_40293_ADDR, v) +#define VIDC_REG_40293_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_40293_ADDR, m, v, \ + VIDC_REG_40293_IN); \ +} while (0) +#define VIDC_REG_40293_FW_STT_ADDR_5_BMSK 0xffffffff +#define VIDC_REG_40293_FW_STT_ADDR_5_SHFT 0 + +#define VIDC_REG_942170_ADDR_6_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000218) +#define VIDC_REG_942170_ADDR_6_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000218) +#define VIDC_REG_942170_ADDR_6_RMSK 0xffffffff +#define VIDC_REG_942170_ADDR_6_SHFT 0 +#define VIDC_REG_942170_ADDR_6_IN \ + in_dword_masked(VIDC_REG_942170_ADDR_6_ADDR, \ + VIDC_REG_942170_ADDR_6_RMSK) +#define VIDC_REG_942170_ADDR_6_INM(m) \ + in_dword_masked(VIDC_REG_942170_ADDR_6_ADDR, m) +#define VIDC_REG_942170_ADDR_6_OUT(v) \ + out_dword(VIDC_REG_942170_ADDR_6_ADDR, v) +#define VIDC_REG_942170_ADDR_6_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_942170_ADDR_6_ADDR, m, v, \ + VIDC_REG_942170_ADDR_6_IN); \ +} while (0) +#define VIDC_REG_942170_ADDR_6_FW_STT_ADDR_6_BMSK 0xffffffff +#define VIDC_REG_942170_ADDR_6_FW_STT_ADDR_6_SHFT 0 + +#define VIDC_REG_958768_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000230) +#define VIDC_REG_958768_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000230) +#define VIDC_REG_958768_RMSK 0xffffffff +#define VIDC_REG_958768_SHFT 0 +#define VIDC_REG_958768_IN \ + in_dword_masked(VIDC_REG_958768_ADDR, \ + VIDC_REG_958768_RMSK) +#define VIDC_REG_958768_INM(m) \ + in_dword_masked(VIDC_REG_958768_ADDR, m) +#define VIDC_REG_958768_OUT(v) \ + out_dword(VIDC_REG_958768_ADDR, v) +#define VIDC_REG_958768_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_958768_ADDR, m, v, \ + VIDC_REG_958768_IN); \ +} while (0) +#define VIDC_REG_699384_ADDR_BMSK 0xffffffff +#define VIDC_REG_699384_ADDR_SHFT 0 + +#define VIDC_REG_979942_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000234) +#define VIDC_REG_979942_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000234) +#define VIDC_REG_979942_RMSK 0xffffffff +#define VIDC_REG_979942_SHFT 0 +#define VIDC_REG_979942_IN \ + in_dword_masked(VIDC_REG_979942_ADDR, \ + VIDC_REG_979942_RMSK) +#define VIDC_REG_979942_INM(m) \ + in_dword_masked(VIDC_REG_979942_ADDR, m) +#define VIDC_REG_979942_OUT(v) \ + out_dword(VIDC_REG_979942_ADDR, v) +#define VIDC_REG_979942_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_979942_ADDR, m, v, \ + VIDC_REG_979942_IN); \ +} while (0) +#define VIDC_REG_979942_DB_STT_ADDR_BMSK 0xffffffff +#define VIDC_REG_979942_DB_STT_ADDR_SHFT 0 + +#define VIDC_REG_839021_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000300) +#define VIDC_REG_839021_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000300) +#define VIDC_REG_839021_RMSK 0xff1f +#define VIDC_REG_839021_SHFT 0 +#define VIDC_REG_839021_IN \ + in_dword_masked(VIDC_REG_839021_ADDR, VIDC_REG_839021_RMSK) +#define VIDC_REG_839021_INM(m) \ + in_dword_masked(VIDC_REG_839021_ADDR, m) +#define VIDC_REG_839021_OUT(v) \ + out_dword(VIDC_REG_839021_ADDR, v) +#define VIDC_REG_839021_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_839021_ADDR, m, v, \ + VIDC_REG_839021_IN); \ +} while (0) +#define VIDC_REG_839021_LEVEL_BMSK 0xff00 +#define VIDC_REG_839021_LEVEL_SHFT 0x8 +#define VIDC_REG_839021_PROFILE_BMSK 0x1f +#define VIDC_REG_839021_PROFILE_SHFT 0 + +#define VIDC_REG_950374_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000308) +#define VIDC_REG_950374_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000308) +#define VIDC_REG_950374_RMSK 0xffff +#define VIDC_REG_950374_SHFT 0 +#define VIDC_REG_950374_IN \ + in_dword_masked(VIDC_REG_950374_ADDR, \ + VIDC_REG_950374_RMSK) +#define VIDC_REG_950374_INM(m) \ + in_dword_masked(VIDC_REG_950374_ADDR, m) +#define VIDC_REG_950374_OUT(v) \ + out_dword(VIDC_REG_950374_ADDR, v) +#define VIDC_REG_950374_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_950374_ADDR, m, v, \ + VIDC_REG_950374_IN); \ +} while (0) +#define VIDC_REG_950374_I_PERIOD_BMSK 0xffff +#define VIDC_REG_950374_I_PERIOD_SHFT 0 + +#define VIDC_REG_504878_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000310) +#define VIDC_REG_504878_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000310) +#define VIDC_REG_504878_RMSK 0xd +#define VIDC_REG_504878_SHFT 0 +#define VIDC_REG_504878_IN \ + in_dword_masked(VIDC_REG_504878_ADDR, \ + VIDC_REG_504878_RMSK) +#define VIDC_REG_504878_INM(m) \ + in_dword_masked(VIDC_REG_504878_ADDR, m) +#define VIDC_REG_504878_OUT(v) \ + out_dword(VIDC_REG_504878_ADDR, v) +#define VIDC_REG_504878_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_504878_ADDR, m, v, \ + VIDC_REG_504878_IN); \ +} while (0) +#define VIDC_REG_504878_FIXED_NUMBER_BMSK 0xc +#define VIDC_REG_504878_FIXED_NUMBER_SHFT 0x2 +#define VIDC_REG_504878_ENTROPY_SEL_BMSK 0x1 +#define VIDC_REG_504878_ENTROPY_SEL_SHFT 0 + +#define VIDC_REG_458130_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000314) +#define VIDC_REG_458130_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000314) +#define VIDC_REG_458130_RMSK 0xfff +#define VIDC_REG_458130_SHFT 0 +#define VIDC_REG_458130_IN \ + in_dword_masked(VIDC_REG_458130_ADDR, \ + VIDC_REG_458130_RMSK) +#define VIDC_REG_458130_INM(m) \ + in_dword_masked(VIDC_REG_458130_ADDR, m) +#define VIDC_REG_458130_OUT(v) \ + out_dword(VIDC_REG_458130_ADDR, v) +#define VIDC_REG_458130_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_458130_ADDR, m, v, \ + VIDC_REG_458130_IN); \ +} while (0) +#define VIDC_REG_458130_SLICE_ALPHA_C0_OFFSET_DIV2_BMSK \ + 0xf80 +#define VIDC_REG_458130_SLICE_ALPHA_C0_OFFSET_DIV2_SHFT \ + 0x7 +#define VIDC_REG_458130_SLICE_BETA_OFFSET_DIV2_BMSK 0x7c +#define VIDC_REG_458130_SLICE_BETA_OFFSET_DIV2_SHFT 0x2 +#define \ + \ +VIDC_REG_458130_DISABLE_DEBLOCKING_FILTER_IDC_BMSK 0x3 +#define \ + \ +VIDC_REG_458130_DISABLE_DEBLOCKING_FILTER_IDC_SHFT 0 + +#define VIDC_REG_314290_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000318) +#define VIDC_REG_314290_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000318) +#define VIDC_REG_314290_RMSK 0x1 +#define VIDC_REG_314290_SHFT 0 +#define VIDC_REG_314290_IN \ + in_dword_masked(VIDC_REG_314290_ADDR, \ + VIDC_REG_314290_RMSK) +#define VIDC_REG_314290_INM(m) \ + in_dword_masked(VIDC_REG_314290_ADDR, m) +#define VIDC_REG_314290_OUT(v) \ + out_dword(VIDC_REG_314290_ADDR, v) +#define VIDC_REG_314290_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_314290_ADDR, m, v, \ + VIDC_REG_314290_IN); \ +} while (0) +#define VIDC_REG_314290_SHORT_HD_ON_BMSK 0x1 +#define VIDC_REG_314290_SHORT_HD_ON_SHFT 0 + +#define VIDC_REG_588301_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000031c) +#define VIDC_REG_588301_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000031c) +#define VIDC_REG_588301_RMSK 0x1 +#define VIDC_REG_588301_SHFT 0 +#define VIDC_REG_588301_IN \ + in_dword_masked(VIDC_REG_588301_ADDR, \ + VIDC_REG_588301_RMSK) +#define VIDC_REG_588301_INM(m) \ + in_dword_masked(VIDC_REG_588301_ADDR, m) +#define VIDC_REG_588301_OUT(v) \ + out_dword(VIDC_REG_588301_ADDR, v) +#define VIDC_REG_588301_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_588301_ADDR, m, v, \ + VIDC_REG_588301_IN); \ +} while (0) +#define VIDC_REG_588301_MSLICE_ENA_BMSK 0x1 +#define VIDC_REG_588301_MSLICE_ENA_SHFT 0 + +#define VIDC_REG_1517_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000320) +#define VIDC_REG_1517_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000320) +#define VIDC_REG_1517_RMSK 0x3 +#define VIDC_REG_1517_SHFT 0 +#define VIDC_REG_1517_IN \ + in_dword_masked(VIDC_REG_1517_ADDR, \ + VIDC_REG_1517_RMSK) +#define VIDC_REG_1517_INM(m) \ + in_dword_masked(VIDC_REG_1517_ADDR, m) +#define VIDC_REG_1517_OUT(v) \ + out_dword(VIDC_REG_1517_ADDR, v) +#define VIDC_REG_1517_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_1517_ADDR, m, v, \ + VIDC_REG_1517_IN); \ +} while (0) +#define VIDC_REG_1517_MSLICE_SEL_BMSK 0x3 +#define VIDC_REG_1517_MSLICE_SEL_SHFT 0 + +#define VIDC_REG_105335_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000324) +#define VIDC_REG_105335_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000324) +#define VIDC_REG_105335_RMSK 0xffffffff +#define VIDC_REG_105335_SHFT 0 +#define VIDC_REG_105335_IN \ + in_dword_masked(VIDC_REG_105335_ADDR, \ + VIDC_REG_105335_RMSK) +#define VIDC_REG_105335_INM(m) \ + in_dword_masked(VIDC_REG_105335_ADDR, m) +#define VIDC_REG_105335_OUT(v) \ + out_dword(VIDC_REG_105335_ADDR, v) +#define VIDC_REG_105335_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_105335_ADDR, m, v, \ + VIDC_REG_105335_IN); \ +} while (0) +#define VIDC_REG_105335_MSLICE_MB_BMSK 0xffffffff +#define VIDC_REG_105335_MSLICE_MB_SHFT 0 + +#define VIDC_REG_561679_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000328) +#define VIDC_REG_561679_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000328) +#define VIDC_REG_561679_RMSK 0xffffffff +#define VIDC_REG_561679_SHFT 0 +#define VIDC_REG_561679_IN \ + in_dword_masked(VIDC_REG_561679_ADDR, \ + VIDC_REG_561679_RMSK) +#define VIDC_REG_561679_INM(m) \ + in_dword_masked(VIDC_REG_561679_ADDR, m) +#define VIDC_REG_561679_OUT(v) \ + out_dword(VIDC_REG_561679_ADDR, v) +#define VIDC_REG_561679_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_561679_ADDR, m, v, \ + VIDC_REG_561679_IN); \ +} while (0) +#define VIDC_REG_561679_MSLICE_BYTE_BMSK 0xffffffff +#define VIDC_REG_561679_MSLICE_BYTE_SHFT 0 + +#define VIDC_REG_151345_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000400) +#define VIDC_REG_151345_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000400) +#define VIDC_REG_151345_RMSK 0xffffffff +#define VIDC_REG_151345_SHFT 0 +#define VIDC_REG_151345_IN \ + in_dword_masked(VIDC_REG_151345_ADDR, \ + VIDC_REG_151345_RMSK) +#define VIDC_REG_151345_INM(m) \ + in_dword_masked(VIDC_REG_151345_ADDR, m) +#define VIDC_REG_151345_DISPLAY_Y_ADR_BMSK 0xffffffff +#define VIDC_REG_151345_DISPLAY_Y_ADR_SHFT 0 + +#define VIDC_REG_293983_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000404) +#define VIDC_REG_293983_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000404) +#define VIDC_REG_293983_RMSK 0xffffffff +#define VIDC_REG_293983_SHFT 0 +#define VIDC_REG_293983_IN \ + in_dword_masked(VIDC_REG_293983_ADDR, \ + VIDC_REG_293983_RMSK) +#define VIDC_REG_293983_INM(m) \ + in_dword_masked(VIDC_REG_293983_ADDR, m) +#define VIDC_REG_293983_DISPLAY_C_ADR_BMSK 0xffffffff +#define VIDC_REG_293983_DISPLAY_C_ADR_SHFT 0 + +#define VIDC_REG_612715_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000408) +#define VIDC_REG_612715_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000408) +#define VIDC_REG_612715_RMSK 0x3f +#define VIDC_REG_612715_SHFT 0 +#define VIDC_REG_612715_IN \ + in_dword_masked(VIDC_REG_612715_ADDR, \ + VIDC_REG_612715_RMSK) +#define VIDC_REG_612715_INM(m) \ + in_dword_masked(VIDC_REG_612715_ADDR, m) +#define VIDC_REG_612715_DISPLAY_STATUS_BMSK 0x3f +#define VIDC_REG_612715_DISPLAY_STATUS_SHFT 0 + +#define VIDC_REG_209364_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000040c) +#define VIDC_REG_209364_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000040c) +#define VIDC_REG_209364_RMSK 0x1 +#define VIDC_REG_209364_SHFT 0 +#define VIDC_REG_209364_IN \ + in_dword_masked(VIDC_REG_209364_ADDR, \ + VIDC_REG_209364_RMSK) +#define VIDC_REG_209364_INM(m) \ + in_dword_masked(VIDC_REG_209364_ADDR, m) +#define VIDC_REG_209364_HEADER_DONE_BMSK 0x1 +#define VIDC_REG_209364_HEADER_DONE_SHFT 0 + +#define VIDC_REG_757835_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000410) +#define VIDC_REG_757835_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000410) +#define VIDC_REG_757835_RMSK 0xffffffff +#define VIDC_REG_757835_SHFT 0 +#define VIDC_REG_757835_IN \ + in_dword_masked(VIDC_REG_757835_ADDR, \ + VIDC_REG_757835_RMSK) +#define VIDC_REG_757835_INM(m) \ + in_dword_masked(VIDC_REG_757835_ADDR, m) +#define VIDC_REG_757835_FRAME_NUM_BMSK 0xffffffff +#define VIDC_REG_757835_FRAME_NUM_SHFT 0 + +#define VIDC_REG_352831_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000414) +#define VIDC_REG_352831_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000414) +#define VIDC_REG_352831_RMSK 0xffffffff +#define VIDC_REG_352831_SHFT 0 +#define VIDC_REG_352831_IN \ + in_dword_masked(VIDC_REG_352831_ADDR, \ + VIDC_REG_352831_RMSK) +#define VIDC_REG_352831_INM(m) \ + in_dword_masked(VIDC_REG_352831_ADDR, m) +#define VIDC_REG_352831_DBG_INFO_OUTPUT0_BMSK 0xffffffff +#define VIDC_REG_352831_DBG_INFO_OUTPUT0_SHFT 0 + +#define VIDC_REG_668634_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000418) +#define VIDC_REG_668634_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000418) +#define VIDC_REG_668634_RMSK 0xffffffff +#define VIDC_REG_668634_SHFT 0 +#define VIDC_REG_668634_IN \ + in_dword_masked(VIDC_REG_668634_ADDR, \ + VIDC_REG_668634_RMSK) +#define VIDC_REG_668634_INM(m) \ + in_dword_masked(VIDC_REG_668634_ADDR, m) +#define VIDC_REG_668634_DBG_INFO_OUTPUT1_BMSK 0xffffffff +#define VIDC_REG_668634_DBG_INFO_OUTPUT1_SHFT 0 + +#define VIDC_REG_609676_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000500) +#define VIDC_REG_609676_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000500) +#define VIDC_REG_609676_RMSK 0x1 +#define VIDC_REG_609676_SHFT 0 +#define VIDC_REG_609676_IN \ + in_dword_masked(VIDC_REG_609676_ADDR, VIDC_REG_609676_RMSK) +#define VIDC_REG_609676_INM(m) \ + in_dword_masked(VIDC_REG_609676_ADDR, m) +#define VIDC_REG_609676_OUT(v) \ + out_dword(VIDC_REG_609676_ADDR, v) +#define VIDC_REG_609676_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_609676_ADDR, m, v, \ + VIDC_REG_609676_IN); \ +} while (0) +#define VIDC_REG_609676_INT_OFF_BMSK 0x1 +#define VIDC_REG_609676_INT_OFF_SHFT 0 + +#define VIDC_REG_491082_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000504) +#define VIDC_REG_491082_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000504) +#define VIDC_REG_491082_RMSK 0x1 +#define VIDC_REG_491082_SHFT 0 +#define VIDC_REG_491082_IN \ + in_dword_masked(VIDC_REG_491082_ADDR, \ + VIDC_REG_491082_RMSK) +#define VIDC_REG_491082_INM(m) \ + in_dword_masked(VIDC_REG_491082_ADDR, m) +#define VIDC_REG_491082_OUT(v) \ + out_dword(VIDC_REG_491082_ADDR, v) +#define VIDC_REG_491082_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_491082_ADDR, m, v, \ + VIDC_REG_491082_IN); \ +} while (0) +#define VIDC_REG_491082_INT_PULSE_SEL_BMSK 0x1 +#define VIDC_REG_491082_INT_PULSE_SEL_SHFT 0 + +#define VIDC_REG_614776_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000508) +#define VIDC_REG_614776_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000508) +#define VIDC_REG_614776_RMSK 0x1 +#define VIDC_REG_614776_SHFT 0 +#define VIDC_REG_614776_IN \ + in_dword_masked(VIDC_REG_614776_ADDR, \ + VIDC_REG_614776_RMSK) +#define VIDC_REG_614776_INM(m) \ + in_dword_masked(VIDC_REG_614776_ADDR, m) +#define VIDC_REG_614776_OUT(v) \ + out_dword(VIDC_REG_614776_ADDR, v) +#define VIDC_REG_614776_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_614776_ADDR, m, v, \ + VIDC_REG_614776_IN); \ +} while (0) +#define VIDC_REG_614776_INT_DONE_CLEAR_BMSK 0x1 +#define VIDC_REG_614776_INT_DONE_CLEAR_SHFT 0 + +#define VIDC_REG_982553_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000050c) +#define VIDC_REG_982553_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000050c) +#define VIDC_REG_982553_RMSK 0x1 +#define VIDC_REG_982553_SHFT 0 +#define VIDC_REG_982553_IN \ + in_dword_masked(VIDC_REG_982553_ADDR, \ + VIDC_REG_982553_RMSK) +#define VIDC_REG_982553_INM(m) \ + in_dword_masked(VIDC_REG_982553_ADDR, m) +#define VIDC_REG_982553_OPERATION_DONE_BMSK 0x1 +#define VIDC_REG_982553_OPERATION_DONE_SHFT 0 + +#define VIDC_REG_259967_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000510) +#define VIDC_REG_259967_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000510) +#define VIDC_REG_259967_RMSK 0x1 +#define VIDC_REG_259967_SHFT 0 +#define VIDC_REG_259967_IN \ + in_dword_masked(VIDC_REG_259967_ADDR, VIDC_REG_259967_RMSK) +#define VIDC_REG_259967_INM(m) \ + in_dword_masked(VIDC_REG_259967_ADDR, m) +#define VIDC_REG_259967_FW_DONE_BMSK 0x1 +#define VIDC_REG_259967_FW_DONE_SHFT 0 + +#define VIDC_REG_512143_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000514) +#define VIDC_REG_512143_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000514) +#define VIDC_REG_512143_RMSK 0x1f8 +#define VIDC_REG_512143_SHFT 0 +#define VIDC_REG_512143_IN \ + in_dword_masked(VIDC_REG_512143_ADDR, \ + VIDC_REG_512143_RMSK) +#define VIDC_REG_512143_INM(m) \ + in_dword_masked(VIDC_REG_512143_ADDR, m) +#define VIDC_REG_512143_FRAME_DONE_STAT_BMSK 0x100 +#define VIDC_REG_512143_FRAME_DONE_STAT_SHFT 0x8 +#define VIDC_REG_512143_DMA_DONE_STAT_BMSK 0x80 +#define VIDC_REG_512143_DMA_DONE_STAT_SHFT 0x7 +#define VIDC_REG_512143_HEADER_DONE_STAT_BMSK 0x40 +#define VIDC_REG_512143_HEADER_DONE_STAT_SHFT 0x6 +#define VIDC_REG_512143_FW_DONE_STAT_BMSK 0x20 +#define VIDC_REG_512143_FW_DONE_STAT_SHFT 0x5 +#define VIDC_REG_512143_OPERATION_FAILED_BMSK 0x10 +#define VIDC_REG_512143_OPERATION_FAILED_SHFT 0x4 +#define VIDC_REG_512143_STREAM_HDR_CHANGED_BMSK 0x8 +#define VIDC_REG_512143_STREAM_HDR_CHANGED_SHFT 0x3 + +#define VIDC_REG_418173_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000518) +#define VIDC_REG_418173_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000518) +#define VIDC_REG_418173_RMSK 0x1fa +#define VIDC_REG_418173_SHFT 0 +#define VIDC_REG_418173_IN \ + in_dword_masked(VIDC_REG_418173_ADDR, \ + VIDC_REG_418173_RMSK) +#define VIDC_REG_418173_INM(m) \ + in_dword_masked(VIDC_REG_418173_ADDR, m) +#define VIDC_REG_418173_OUT(v) \ + out_dword(VIDC_REG_418173_ADDR, v) +#define VIDC_REG_418173_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_418173_ADDR, m, v, \ + VIDC_REG_418173_IN); \ +} while (0) +#define VIDC_REG_418173_FRAME_DONE_ENABLE_BMSK 0x100 +#define VIDC_REG_418173_FRAME_DONE_ENABLE_SHFT 0x8 +#define VIDC_REG_418173_DMA_DONE_ENABLE_BMSK 0x80 +#define VIDC_REG_418173_DMA_DONE_ENABLE_SHFT 0x7 +#define VIDC_REG_418173_HEADER_DONE_ENABLE_BMSK 0x40 +#define VIDC_REG_418173_HEADER_DONE_ENABLE_SHFT 0x6 +#define VIDC_REG_418173_FW_DONE_ENABLE_BMSK 0x20 +#define VIDC_REG_418173_FW_DONE_ENABLE_SHFT 0x5 +#define VIDC_REG_418173_OPERATION_FAILED_ENABLE_BMSK 0x10 +#define VIDC_REG_418173_OPERATION_FAILED_ENABLE_SHFT 0x4 +#define VIDC_REG_418173_STREAM_HDR_CHANGED_ENABLE_BMSK 0x8 +#define VIDC_REG_418173_STREAM_HDR_CHANGED_ENABLE_SHFT 0x3 +#define VIDC_REG_418173_BUFFER_FULL_ENABLE_BMSK 0x2 +#define VIDC_REG_418173_BUFFER_FULL_ENABLE_SHFT 0x1 + +#define VIDC_REG_841539_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000600) +#define VIDC_REG_841539_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000600) +#define VIDC_REG_841539_RMSK 0x3 +#define VIDC_REG_841539_SHFT 0 +#define VIDC_REG_841539_IN \ + in_dword_masked(VIDC_REG_841539_ADDR, \ + VIDC_REG_841539_RMSK) +#define VIDC_REG_841539_INM(m) \ + in_dword_masked(VIDC_REG_841539_ADDR, m) +#define VIDC_REG_841539_OUT(v) \ + out_dword(VIDC_REG_841539_ADDR, v) +#define VIDC_REG_841539_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_841539_ADDR, m, v, \ + VIDC_REG_841539_IN); \ +} while (0) +#define VIDC_REG_841539_TILE_MODE_BMSK 0x3 +#define VIDC_REG_841539_TILE_MODE_SHFT 0 + +#define VIDC_REG_99105_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000800) +#define VIDC_REG_99105_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000800) +#define VIDC_REG_99105_RMSK 0xffffffff +#define VIDC_REG_99105_SHFT 0 +#define VIDC_REG_99105_IN \ + in_dword_masked(VIDC_REG_99105_ADDR, \ + VIDC_REG_99105_RMSK) +#define VIDC_REG_99105_INM(m) \ + in_dword_masked(VIDC_REG_99105_ADDR, m) +#define VIDC_REG_99105_OUT(v) \ + out_dword(VIDC_REG_99105_ADDR, v) +#define VIDC_REG_99105_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_99105_ADDR, m, v, \ + VIDC_REG_99105_IN); \ +} while (0) +#define VIDC_REG_99105_ENC_CUR_Y_ADDR_BMSK 0xffffffff +#define VIDC_REG_99105_ENC_CUR_Y_ADDR_SHFT 0 + +#define VIDC_REG_777113_ADDR_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000804) +#define VIDC_REG_777113_ADDR_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000804) +#define VIDC_REG_777113_ADDR_RMSK 0xffffffff +#define VIDC_REG_777113_ADDR_SHFT 0 +#define VIDC_REG_777113_ADDR_IN \ + in_dword_masked(VIDC_REG_777113_ADDR_ADDR, \ + VIDC_REG_777113_ADDR_RMSK) +#define VIDC_REG_777113_ADDR_INM(m) \ + in_dword_masked(VIDC_REG_777113_ADDR_ADDR, m) +#define VIDC_REG_777113_ADDR_OUT(v) \ + out_dword(VIDC_REG_777113_ADDR_ADDR, v) +#define VIDC_REG_777113_ADDR_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_777113_ADDR_ADDR, m, v, \ + VIDC_REG_777113_ADDR_IN); \ +} while (0) +#define VIDC_REG_777113_ADDR_ENC_CUR_C_ADDR_BMSK 0xffffffff +#define VIDC_REG_777113_ADDR_ENC_CUR_C_ADDR_SHFT 0 + +#define VIDC_REG_341928_ADDR_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000080c) +#define VIDC_REG_341928_ADDR_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000080c) +#define VIDC_REG_341928_ADDR_RMSK 0xffffffff +#define VIDC_REG_341928_ADDR_SHFT 0 +#define VIDC_REG_341928_ADDR_IN \ + in_dword_masked(VIDC_REG_341928_ADDR_ADDR, \ + VIDC_REG_341928_ADDR_RMSK) +#define VIDC_REG_341928_ADDR_INM(m) \ + in_dword_masked(VIDC_REG_341928_ADDR_ADDR, m) +#define VIDC_REG_341928_ADDR_OUT(v) \ + out_dword(VIDC_REG_341928_ADDR_ADDR, v) +#define VIDC_REG_341928_ADDR_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_341928_ADDR_ADDR, m, v, \ + VIDC_REG_341928_ADDR_IN); \ +} while (0) +#define VIDC_REG_341928_ADDR_ENC_DPB_ADR_BMSK 0xffffffff +#define VIDC_REG_341928_ADDR_ENC_DPB_ADR_SHFT 0 + +#define VIDC_REG_857491_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000810) +#define VIDC_REG_857491_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000810) +#define VIDC_REG_857491_RMSK 0xfff +#define VIDC_REG_857491_SHFT 0 +#define VIDC_REG_857491_IN \ + in_dword_masked(VIDC_REG_857491_ADDR, \ + VIDC_REG_857491_RMSK) +#define VIDC_REG_857491_INM(m) \ + in_dword_masked(VIDC_REG_857491_ADDR, m) +#define VIDC_REG_857491_OUT(v) \ + out_dword(VIDC_REG_857491_ADDR, v) +#define VIDC_REG_857491_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_857491_ADDR, m, v, \ + VIDC_REG_857491_IN); \ +} while (0) +#define VIDC_REG_857491_CIR_MB_NUM_BMSK 0xfff +#define VIDC_REG_857491_CIR_MB_NUM_SHFT 0 + +#define VIDC_REG_518133_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000900) +#define VIDC_REG_518133_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000900) +#define VIDC_REG_518133_RMSK 0xffffffff +#define VIDC_REG_518133_SHFT 0 +#define VIDC_REG_518133_IN \ + in_dword_masked(VIDC_REG_518133_ADDR, \ + VIDC_REG_518133_RMSK) +#define VIDC_REG_518133_INM(m) \ + in_dword_masked(VIDC_REG_518133_ADDR, m) +#define VIDC_REG_518133_OUT(v) \ + out_dword(VIDC_REG_518133_ADDR, v) +#define VIDC_REG_518133_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_518133_ADDR, m, v, \ + VIDC_REG_518133_IN); \ +} while (0) +#define VIDC_REG_518133_DEC_DPB_ADDR_BMSK 0xffffffff +#define VIDC_REG_518133_DEC_DPB_ADDR_SHFT 0 + +#define VIDC_REG_456376_ADDR_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000904) +#define VIDC_REG_456376_ADDR_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000904) +#define VIDC_REG_456376_ADDR_RMSK 0xffffffff +#define VIDC_REG_456376_ADDR_SHFT 0 +#define VIDC_REG_456376_ADDR_IN \ + in_dword_masked(VIDC_REG_456376_ADDR_ADDR, \ + VIDC_REG_456376_ADDR_RMSK) +#define VIDC_REG_456376_ADDR_INM(m) \ + in_dword_masked(VIDC_REG_456376_ADDR_ADDR, m) +#define VIDC_REG_456376_ADDR_OUT(v) \ + out_dword(VIDC_REG_456376_ADDR_ADDR, v) +#define VIDC_REG_456376_ADDR_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_456376_ADDR_ADDR, m, v, \ + VIDC_REG_456376_ADDR_IN); \ +} while (0) +#define VIDC_REG_456376_ADDR_DPB_COMV_ADDR_BMSK 0xffffffff +#define VIDC_REG_456376_ADDR_DPB_COMV_ADDR_SHFT 0 + +#define VIDC_REG_267567_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000908) +#define VIDC_REG_267567_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000908) +#define VIDC_REG_267567_RMSK 0xffffffff +#define VIDC_REG_267567_SHFT 0 +#define VIDC_REG_267567_IN \ + in_dword_masked(VIDC_REG_267567_ADDR, \ + VIDC_REG_267567_RMSK) +#define VIDC_REG_267567_INM(m) \ + in_dword_masked(VIDC_REG_267567_ADDR, m) +#define VIDC_REG_267567_OUT(v) \ + out_dword(VIDC_REG_267567_ADDR, v) +#define VIDC_REG_267567_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_267567_ADDR, m, v, \ + VIDC_REG_267567_IN); \ +} while (0) +#define VIDC_REG_798486_ADDR_BMSK 0xffffffff +#define VIDC_REG_798486_ADDR_SHFT 0 + +#define VIDC_REG_105770_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x0000090c) +#define VIDC_REG_105770_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x0000090c) +#define VIDC_REG_105770_RMSK 0xff +#define VIDC_REG_105770_SHFT 0 +#define VIDC_REG_105770_IN \ + in_dword_masked(VIDC_REG_105770_ADDR, \ + VIDC_REG_105770_RMSK) +#define VIDC_REG_105770_INM(m) \ + in_dword_masked(VIDC_REG_105770_ADDR, m) +#define VIDC_REG_105770_DPB_SIZE_BMSK 0xff +#define VIDC_REG_105770_DPB_SIZE_SHFT 0 + +#define VIDC_REG_58211_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000a00) +#define VIDC_REG_58211_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000a00) +#define VIDC_REG_58211_RMSK 0x33f +#define VIDC_REG_58211_SHFT 0 +#define VIDC_REG_58211_IN \ + in_dword_masked(VIDC_REG_58211_ADDR, \ + VIDC_REG_58211_RMSK) +#define VIDC_REG_58211_INM(m) \ + in_dword_masked(VIDC_REG_58211_ADDR, m) +#define VIDC_REG_58211_OUT(v) \ + out_dword(VIDC_REG_58211_ADDR, v) +#define VIDC_REG_58211_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_58211_ADDR, m, v, \ + VIDC_REG_58211_IN); \ +} while (0) +#define VIDC_REG_58211_FR_RC_EN_BMSK 0x200 +#define VIDC_REG_58211_FR_RC_EN_SHFT 0x9 +#define VIDC_REG_58211_MB_RC_EN_BMSK 0x100 +#define VIDC_REG_58211_MB_RC_EN_SHFT 0x8 +#define VIDC_REG_58211_FRAME_QP_BMSK 0x3f +#define VIDC_REG_58211_FRAME_QP_SHFT 0 + +#define VIDC_REG_548359_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000a04) +#define VIDC_REG_548359_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000a04) +#define VIDC_REG_548359_RMSK 0x3f +#define VIDC_REG_548359_SHFT 0 +#define VIDC_REG_548359_IN \ + in_dword_masked(VIDC_REG_548359_ADDR, \ + VIDC_REG_548359_RMSK) +#define VIDC_REG_548359_INM(m) \ + in_dword_masked(VIDC_REG_548359_ADDR, m) +#define VIDC_REG_548359_OUT(v) \ + out_dword(VIDC_REG_548359_ADDR, v) +#define VIDC_REG_548359_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_548359_ADDR, m, v, \ + VIDC_REG_548359_IN); \ +} while (0) +#define VIDC_REG_548359_P_FRAME_QP_BMSK 0x3f +#define VIDC_REG_548359_P_FRAME_QP_SHFT 0 + +#define VIDC_REG_174150_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000a08) +#define VIDC_REG_174150_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000a08) +#define VIDC_REG_174150_RMSK 0xffffffff +#define VIDC_REG_174150_SHFT 0 +#define VIDC_REG_174150_IN \ + in_dword_masked(VIDC_REG_174150_ADDR, \ + VIDC_REG_174150_RMSK) +#define VIDC_REG_174150_INM(m) \ + in_dword_masked(VIDC_REG_174150_ADDR, m) +#define VIDC_REG_174150_OUT(v) \ + out_dword(VIDC_REG_174150_ADDR, v) +#define VIDC_REG_174150_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_174150_ADDR, m, v, \ + VIDC_REG_174150_IN); \ +} while (0) +#define VIDC_REG_174150_BIT_RATE_BMSK 0xffffffff +#define VIDC_REG_174150_BIT_RATE_SHFT 0 + +#define VIDC_REG_734318_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000a0c) +#define VIDC_REG_734318_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000a0c) +#define VIDC_REG_734318_RMSK 0x3f3f +#define VIDC_REG_734318_SHFT 0 +#define VIDC_REG_734318_IN \ + in_dword_masked(VIDC_REG_734318_ADDR, \ + VIDC_REG_734318_RMSK) +#define VIDC_REG_734318_INM(m) \ + in_dword_masked(VIDC_REG_734318_ADDR, m) +#define VIDC_REG_734318_OUT(v) \ + out_dword(VIDC_REG_734318_ADDR, v) +#define VIDC_REG_734318_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_734318_ADDR, m, v, \ + VIDC_REG_734318_IN); \ +} while (0) +#define VIDC_REG_734318_MAX_QP_BMSK 0x3f00 +#define VIDC_REG_734318_MAX_QP_SHFT 0x8 +#define VIDC_REG_734318_MIN_QP_BMSK 0x3f +#define VIDC_REG_734318_MIN_QP_SHFT 0 + +#define VIDC_REG_677784_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000a10) +#define VIDC_REG_677784_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000a10) +#define VIDC_REG_677784_RMSK 0xffff +#define VIDC_REG_677784_SHFT 0 +#define VIDC_REG_677784_IN \ + in_dword_masked(VIDC_REG_677784_ADDR, \ + VIDC_REG_677784_RMSK) +#define VIDC_REG_677784_INM(m) \ + in_dword_masked(VIDC_REG_677784_ADDR, m) +#define VIDC_REG_677784_OUT(v) \ + out_dword(VIDC_REG_677784_ADDR, v) +#define VIDC_REG_677784_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_677784_ADDR, m, v, \ + VIDC_REG_677784_IN); \ +} while (0) +#define VIDC_REG_677784_REACT_PARA_BMSK 0xffff +#define VIDC_REG_677784_REACT_PARA_SHFT 0 + +#define VIDC_REG_995041_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000a14) +#define VIDC_REG_995041_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000a14) +#define VIDC_REG_995041_RMSK 0xf +#define VIDC_REG_995041_SHFT 0 +#define VIDC_REG_995041_IN \ + in_dword_masked(VIDC_REG_995041_ADDR, \ + VIDC_REG_995041_RMSK) +#define VIDC_REG_995041_INM(m) \ + in_dword_masked(VIDC_REG_995041_ADDR, m) +#define VIDC_REG_995041_OUT(v) \ + out_dword(VIDC_REG_995041_ADDR, v) +#define VIDC_REG_995041_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_995041_ADDR, m, v, \ + VIDC_REG_995041_IN); \ +} while (0) +#define VIDC_REG_995041_DARK_DISABLE_BMSK 0x8 +#define VIDC_REG_995041_DARK_DISABLE_SHFT 0x3 +#define VIDC_REG_995041_SMOOTH_DISABLE_BMSK 0x4 +#define VIDC_REG_995041_SMOOTH_DISABLE_SHFT 0x2 +#define VIDC_REG_995041_STATIC_DISABLE_BMSK 0x2 +#define VIDC_REG_995041_STATIC_DISABLE_SHFT 0x1 +#define VIDC_REG_995041_ACT_DISABLE_BMSK 0x1 +#define VIDC_REG_995041_ACT_DISABLE_SHFT 0 + +#define VIDC_REG_273649_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000a18) +#define VIDC_REG_273649_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000a18) +#define VIDC_REG_273649_RMSK 0x3f +#define VIDC_REG_273649_SHFT 0 +#define VIDC_REG_273649_IN \ + in_dword_masked(VIDC_REG_273649_ADDR, VIDC_REG_273649_RMSK) +#define VIDC_REG_273649_INM(m) \ + in_dword_masked(VIDC_REG_273649_ADDR, m) +#define VIDC_REG_273649_QP_OUT_BMSK 0x3f +#define VIDC_REG_273649_QP_OUT_SHFT 0 + +#define VIDC_REG_548823_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000b00) +#define VIDC_REG_548823_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000b00) +#define VIDC_REG_548823_RMSK 0xffffffff +#define VIDC_REG_548823_SHFT 0 +#define VIDC_REG_548823_IN \ + in_dword_masked(VIDC_REG_548823_ADDR, \ + VIDC_REG_548823_RMSK) +#define VIDC_REG_548823_INM(m) \ + in_dword_masked(VIDC_REG_548823_ADDR, m) +#define VIDC_REG_548823_720P_VERSION_BMSK 0xffffffff +#define VIDC_REG_548823_720P_VERSION_SHFT 0 + +#define VIDC_REG_881638_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000c00) +#define VIDC_REG_881638_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000c00) +#define VIDC_REG_881638_RMSK 0xffffffff +#define VIDC_REG_881638_SHFT 0 +#define VIDC_REG_881638_IN \ + in_dword_masked(VIDC_REG_881638_ADDR, \ + VIDC_REG_881638_RMSK) +#define VIDC_REG_881638_INM(m) \ + in_dword_masked(VIDC_REG_881638_ADDR, m) +#define VIDC_REG_881638_CROP_RIGHT_OFFSET_BMSK 0xffff0000 +#define VIDC_REG_881638_CROP_RIGHT_OFFSET_SHFT 0x10 +#define VIDC_REG_881638_CROP_LEFT_OFFSET_BMSK 0xffff +#define VIDC_REG_881638_CROP_LEFT_OFFSET_SHFT 0 + +#define VIDC_REG_161486_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000c04) +#define VIDC_REG_161486_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000c04) +#define VIDC_REG_161486_RMSK 0xffffffff +#define VIDC_REG_161486_SHFT 0 +#define VIDC_REG_161486_IN \ + in_dword_masked(VIDC_REG_161486_ADDR, \ + VIDC_REG_161486_RMSK) +#define VIDC_REG_161486_INM(m) \ + in_dword_masked(VIDC_REG_161486_ADDR, m) +#define VIDC_REG_161486_CROP_BOTTOM_OFFSET_BMSK 0xffff0000 +#define VIDC_REG_161486_CROP_BOTTOM_OFFSET_SHFT 0x10 +#define VIDC_REG_161486_CROP_TOP_OFFSET_BMSK 0xffff +#define VIDC_REG_161486_CROP_TOP_OFFSET_SHFT 0 + +#define VIDC_REG_580603_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000c08) +#define VIDC_REG_580603_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000c08) +#define VIDC_REG_580603_RMSK 0xffffffff +#define VIDC_REG_580603_SHFT 0 +#define VIDC_REG_580603_IN \ + in_dword_masked(VIDC_REG_580603_ADDR, \ + VIDC_REG_580603_RMSK) +#define VIDC_REG_580603_INM(m) \ + in_dword_masked(VIDC_REG_580603_ADDR, m) +#define VIDC_REG_580603_720P_DEC_FRM_SIZE_BMSK 0xffffffff +#define VIDC_REG_580603_720P_DEC_FRM_SIZE_SHFT 0 + + +#define VIDC_REG_606447_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000c0c) +#define VIDC_REG_606447_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000c0c) +#define VIDC_REG_606447_RMSK 0xff1f +#define VIDC_REG_606447_SHFT 0 +#define VIDC_REG_606447_IN \ + in_dword_masked(VIDC_REG_606447_ADDR, \ + VIDC_REG_606447_RMSK) +#define VIDC_REG_606447_INM(m) \ + in_dword_masked(VIDC_REG_606447_ADDR, m) +#define VIDC_REG_606447_OUT(v) \ + out_dword(VIDC_REG_606447_ADDR, v) +#define VIDC_REG_606447_OUTM(m, v) \ + out_dword_masked_ns(VIDC_REG_606447_ADDR, \ + m, v, VIDC_REG_606447_IN); \ + +#define VIDC_REG_606447_DIS_PIC_LEVEL_BMSK 0xff00 +#define VIDC_REG_606447_DIS_PIC_LEVEL_SHFT 0x8 +#define VIDC_REG_606447_DISP_PIC_PROFILE_BMSK 0x1f +#define VIDC_REG_606447_DISP_PIC_PROFILE_SHFT 0 + +#define VIDC_REG_854281_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000c10) +#define VIDC_REG_854281_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000c10) +#define VIDC_REG_854281_RMSK 0xffffffff +#define VIDC_REG_854281_SHFT 0 +#define VIDC_REG_854281_IN \ + in_dword_masked(VIDC_REG_854281_ADDR, \ + VIDC_REG_854281_RMSK) +#define VIDC_REG_854281_INM(m) \ + in_dword_masked(VIDC_REG_854281_ADDR, m) +#define VIDC_REG_854281_MIN_DPB_SIZE_BMSK 0xffffffff +#define VIDC_REG_854281_MIN_DPB_SIZE_SHFT 0 + + +#define VIDC_REG_381535_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000c14) +#define VIDC_REG_381535_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000c14) +#define VIDC_REG_381535_RMSK 0xffffffff +#define VIDC_REG_381535_SHFT 0 +#define VIDC_REG_381535_IN \ + in_dword_masked(VIDC_REG_381535_ADDR, \ + VIDC_REG_381535_RMSK) +#define VIDC_REG_381535_INM(m) \ + in_dword_masked(VIDC_REG_381535_ADDR, m) +#define VIDC_REG_381535_720P_FW_STATUS_BMSK 0xffffffff +#define VIDC_REG_381535_720P_FW_STATUS_SHFT 0 + + +#define VIDC_REG_347105_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000c18) +#define VIDC_REG_347105_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000c18) +#define VIDC_REG_347105_RMSK 0xffffffff +#define VIDC_REG_347105_SHFT 0 +#define VIDC_REG_347105_IN \ + in_dword_masked(VIDC_REG_347105_ADDR, \ + VIDC_REG_347105_RMSK) +#define VIDC_REG_347105_INM(m) \ + in_dword_masked(VIDC_REG_347105_ADDR, m) +#define VIDC_REG_347105_FREE_LUMA_DPB_BMSK 0xffffffff +#define VIDC_REG_347105_FREE_LUMA_DPB_SHFT 0 + + +#define VIDC_REG_62325_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000d00) +#define VIDC_REG_62325_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000d00) +#define VIDC_REG_62325_RMSK 0xf +#define VIDC_REG_62325_SHFT 0 +#define VIDC_REG_62325_IN \ + in_dword_masked(VIDC_REG_62325_ADDR, \ + VIDC_REG_62325_RMSK) +#define VIDC_REG_62325_INM(m) \ + in_dword_masked(VIDC_REG_62325_ADDR, m) +#define VIDC_REG_62325_OUT(v) \ + out_dword(VIDC_REG_62325_ADDR, v) +#define VIDC_REG_62325_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_62325_ADDR, m, v, \ + VIDC_REG_62325_IN); \ +} while (0) +#define VIDC_REG_62325_COMMAND_TYPE_BMSK 0xf +#define VIDC_REG_62325_COMMAND_TYPE_SHFT 0 + +#define VIDC_REG_101184_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000d04) +#define VIDC_REG_101184_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000d04) +#define VIDC_REG_101184_RMSK 0xffffffff +#define VIDC_REG_101184_SHFT 0 +#define VIDC_REG_101184_OUT(v) \ + out_dword(VIDC_REG_101184_ADDR, v) + +#define VIDC_REG_490443_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000d08) +#define VIDC_REG_490443_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000d08) +#define VIDC_REG_490443_RMSK \ + 0xffffffff +#define \ + \ +VIDC_REG_490443_SHFT 0 +#define VIDC_REG_490443_OUT(v) \ + out_dword(VIDC_REG_490443_ADDR, v) + +#define VIDC_REG_625444_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000d14) +#define VIDC_REG_625444_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000d14) +#define VIDC_REG_625444_RMSK 0xffffffff +#define VIDC_REG_625444_SHFT 0 +#define VIDC_REG_625444_IN \ + in_dword_masked(VIDC_REG_625444_ADDR, \ + VIDC_REG_625444_RMSK) +#define VIDC_REG_625444_INM(m) \ + in_dword_masked(VIDC_REG_625444_ADDR, m) +#define VIDC_REG_625444_OUT(v) \ + out_dword(VIDC_REG_625444_ADDR, v) +#define VIDC_REG_625444_OUTM(m, v) \ +do { \ + out_dword_masked_ns(VIDC_REG_625444_ADDR, m, v, \ + VIDC_REG_625444_IN); \ +} while (0) +#define VIDC_REG_625444_FRAME_RATE_BMSK 0xffffffff +#define VIDC_REG_625444_FRAME_RATE_SHFT 0 + +#define VIDC_REG_639999_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000d20) +#define VIDC_REG_639999_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000d20) +#define VIDC_REG_639999_RMSK 0xffff +#define VIDC_REG_639999_SHFT 0 +#define VIDC_REG_639999_OUT(v) \ + out_dword(VIDC_REG_639999_ADDR, v) + +#define VIDC_REG_64895_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000e00) +#define VIDC_REG_64895_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000e00) +#define VIDC_REG_64895_RMSK 0xffffffff +#define VIDC_REG_64895_SHFT 0 +#define VIDC_REG_64895_OUT(v) \ + out_dword(VIDC_REG_64895_ADDR, v) + +#define VIDC_REG_965480_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000e04) +#define VIDC_REG_965480_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000e04) +#define VIDC_REG_965480_RMSK 0x1 +#define VIDC_REG_965480_SHFT 0 +#define VIDC_REG_965480_OUT(v) \ + out_dword(VIDC_REG_965480_ADDR, v) + +#define VIDC_REG_804959_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000e08) +#define VIDC_REG_804959_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000e08) +#define VIDC_REG_804959_RMSK 0x7 +#define VIDC_REG_804959_SHFT 0 +#define VIDC_REG_804959_OUT(v) \ + out_dword(VIDC_REG_804959_ADDR, v) + +#define VIDC_REG_257463_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000e10) +#define VIDC_REG_257463_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000e10) +#define VIDC_REG_257463_RMSK 0xffffffff +#define VIDC_REG_257463_SHFT 0 +#define VIDC_REG_257463_IN \ + in_dword_masked(VIDC_REG_257463_ADDR, \ + VIDC_REG_257463_RMSK) +#define VIDC_REG_257463_INM(m) \ + in_dword_masked(VIDC_REG_257463_ADDR, m) +#define VIDC_REG_257463_MIN_NUM_DPB_BMSK 0xffffffff +#define VIDC_REG_257463_MIN_NUM_DPB_SHFT 0 + +#define VIDC_REG_883500_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000e14) +#define VIDC_REG_883500_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000e14) +#define VIDC_REG_883500_RMSK 0xffffffff +#define VIDC_REG_883500_SHFT 0 +#define VIDC_REG_883500_OUT(v) \ + out_dword(VIDC_REG_883500_ADDR, v) + +#define VIDC_REG_615716_ADDR(n) \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000e18 + 4 * (n)) +#define VIDC_REG_615716_PHYS(n) \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000e18 + 4 * (n)) +#define VIDC_REG_615716_RMSK 0xffffffff +#define VIDC_REG_615716_SHFT 0 +#define VIDC_REG_615716_OUTI(n, v) \ + out_dword(VIDC_REG_615716_ADDR(n), v) + +#define VIDC_REG_603032_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000e98) +#define VIDC_REG_603032_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000e98) +#define VIDC_REG_603032_RMSK 0xffffffff +#define VIDC_REG_603032_SHFT 0 +#define VIDC_REG_603032_OUT(v) \ + out_dword(VIDC_REG_603032_ADDR, v) + +#define VIDC_REG_300310_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000e9c) +#define VIDC_REG_300310_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000e9c) +#define VIDC_REG_300310_RMSK 0xffffffff +#define VIDC_REG_300310_SHFT 0 +#define VIDC_REG_300310_IN \ + in_dword_masked(VIDC_REG_300310_ADDR, \ + VIDC_REG_300310_RMSK) +#define VIDC_REG_300310_INM(m) \ + in_dword_masked(VIDC_REG_300310_ADDR, m) +#define VIDC_REG_300310_ERROR_STATUS_BMSK 0xffffffff +#define VIDC_REG_300310_ERROR_STATUS_SHFT 0 + +#define VIDC_REG_792026_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ea0) +#define VIDC_REG_792026_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ea0) +#define VIDC_REG_792026_RMSK 0xffffffff +#define VIDC_REG_792026_SHFT 0 +#define VIDC_REG_792026_OUT(v) \ + out_dword(VIDC_REG_792026_ADDR, v) + +#define VIDC_REG_844152_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ea4) +#define VIDC_REG_844152_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ea4) +#define VIDC_REG_844152_RMSK 0xffffffff +#define VIDC_REG_844152_SHFT 0 +#define VIDC_REG_844152_OUT(v) \ + out_dword(VIDC_REG_844152_ADDR, v) + +#define VIDC_REG_370409_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ea8) +#define VIDC_REG_370409_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ea8) +#define VIDC_REG_370409_RMSK 0xffffffff +#define VIDC_REG_370409_SHFT 0 +#define VIDC_REG_370409_IN \ + in_dword_masked(VIDC_REG_370409_ADDR, \ + VIDC_REG_370409_RMSK) +#define VIDC_REG_370409_INM(m) \ + in_dword_masked(VIDC_REG_370409_ADDR, m) +#define VIDC_REG_370409_GET_FRAME_TAG_TOP_BMSK 0xffffffff +#define VIDC_REG_370409_GET_FRAME_TAG_TOP_SHFT 0 + +#define VIDC_REG_147682_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000eac) +#define VIDC_REG_147682_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000eac) +#define VIDC_REG_147682_RMSK 0x1 +#define VIDC_REG_147682_SHFT 0 +#define VIDC_REG_147682_OUT(v) \ + out_dword(VIDC_REG_147682_ADDR, v) + +#define VIDC_REG_407718_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000eb0) +#define VIDC_REG_407718_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000eb0) +#define VIDC_REG_407718_RMSK 0xffffffff +#define VIDC_REG_407718_SHFT 0 +#define VIDC_REG_407718_OUT(v) \ + out_dword(VIDC_REG_407718_ADDR, v) + +#define VIDC_REG_697961_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000eb4) +#define VIDC_REG_697961_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000eb4) +#define VIDC_REG_697961_RMSK 0x3 +#define VIDC_REG_697961_SHFT 0 +#define VIDC_REG_697961_IN \ + in_dword_masked(VIDC_REG_697961_ADDR, \ + VIDC_REG_697961_RMSK) +#define VIDC_REG_697961_INM(m) \ + in_dword_masked(VIDC_REG_697961_ADDR, m) +#define VIDC_REG_697961_FRAME_TYPE_BMSK 0x3 +#define VIDC_REG_697961_FRAME_TYPE_SHFT 0 + + +#define VIDC_REG_613254_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000eb8) +#define VIDC_REG_613254_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000eb8) +#define VIDC_REG_613254_RMSK 0x1 +#define VIDC_REG_613254_SHFT 0 +#define VIDC_REG_613254_IN \ + in_dword_masked(VIDC_REG_613254_ADDR, \ + VIDC_REG_613254_RMSK) +#define VIDC_REG_613254_INM(m) \ + in_dword_masked(VIDC_REG_613254_ADDR, m) +#define VIDC_REG_613254_METADATA_STATUS_BMSK 0x1 +#define VIDC_REG_613254_METADATA_STATUS_SHFT 0 +#define VIDC_REG_441270_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ebc) +#define VIDC_REG_441270_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ebc) +#define VIDC_REG_441270_RMSK 0xf +#define VIDC_REG_441270_SHFT 0 +#define VIDC_REG_441270_IN \ + in_dword_masked(VIDC_REG_441270_ADDR, \ + VIDC_REG_441270_RMSK) +#define VIDC_REG_441270_INM(m) \ + in_dword_masked(VIDC_REG_441270_ADDR, m) +#define VIDC_REG_441270_DATA_PARTITIONED_BMSK 0x8 +#define VIDC_REG_441270_DATA_PARTITIONED_SHFT 0x3 + +#define VIDC_REG_441270_FRAME_TYPE_BMSK 0x7 +#define VIDC_REG_441270_FRAME_TYPE_SHFT 0 + +#define VIDC_REG_724381_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ec0) +#define VIDC_REG_724381_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ec0) +#define VIDC_REG_724381_RMSK 0x3 +#define VIDC_REG_724381_SHFT 0 +#define VIDC_REG_724381_IN \ + in_dword_masked(VIDC_REG_724381_ADDR, \ + VIDC_REG_724381_RMSK) +#define VIDC_REG_724381_INM(m) \ + in_dword_masked(VIDC_REG_724381_ADDR, m) +#define VIDC_REG_724381_MORE_FIELD_NEEDED_BMSK 0x4 +#define VIDC_REG_724381_MORE_FIELD_NEEDED_SHFT 0x2 +#define VIDC_REG_724381_OPERATION_FAILED_BMSK 0x2 +#define VIDC_REG_724381_OPERATION_FAILED_SHFT 0x1 +#define VIDC_REG_724381_RESOLUTION_CHANGE_BMSK 0x1 +#define VIDC_REG_724381_RESOLUTION_CHANGE_SHFT 0 + +#define VIDC_REG_854681_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ec4) +#define VIDC_REG_854681_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ec4) +#define VIDC_REG_854681_RMSK 0x7f +#define VIDC_REG_854681_SHFT 0 +#define VIDC_REG_854681_OUT(v) \ + out_dword(VIDC_REG_854681_ADDR, v) + +#define VIDC_REG_128234_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ec8) +#define VIDC_REG_128234_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ec8) +#define VIDC_REG_128234_RMSK 0xffff000f +#define VIDC_REG_128234_SHFT 0 +#define VIDC_REG_128234_OUT(v) \ + out_dword(VIDC_REG_128234_ADDR, v) + +#define VIDC_REG_1137_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ecc) +#define VIDC_REG_1137_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ecc) +#define VIDC_REG_1137_RMSK 0xffffffff +#define VIDC_REG_1137_SHFT 0 +#define VIDC_REG_1137_IN \ + in_dword_masked(VIDC_REG_1137_ADDR, \ + VIDC_REG_1137_RMSK) +#define VIDC_REG_1137_INM(m) \ + in_dword_masked(VIDC_REG_1137_ADDR, m) +#define VIDC_REG_1137_METADATA_DISPLAY_INDEX_BMSK \ + 0xffffffff +#define \ + \ +VIDC_REG_1137_METADATA_DISPLAY_INDEX_SHFT 0 + +#define VIDC_REG_988552_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ed0) +#define VIDC_REG_988552_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ed0) +#define VIDC_REG_988552_RMSK 0xffffffff +#define VIDC_REG_988552_SHFT 0 +#define VIDC_REG_988552_OUT(v) \ + out_dword(VIDC_REG_988552_ADDR, v) + +#define VIDC_REG_319934_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ed4) +#define VIDC_REG_319934_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ed4) +#define VIDC_REG_319934_RMSK 0xffffffff +#define VIDC_REG_319934_SHFT 0 +#define VIDC_REG_319934_OUT(v) \ + out_dword(VIDC_REG_319934_ADDR, v) + +#define VIDC_REG_679165_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ed8) +#define VIDC_REG_679165_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ed8) +#define VIDC_REG_679165_RMSK 0xffffffff +#define VIDC_REG_679165_SHFT 0 +#define VIDC_REG_679165_IN \ + in_dword_masked(VIDC_REG_679165_ADDR, \ + VIDC_REG_679165_RMSK) +#define VIDC_REG_679165_INM(m) \ + in_dword_masked(VIDC_REG_679165_ADDR, m) +#define VIDC_REG_679165_PIC_TIME_TOP_BMSK 0xffffffff +#define VIDC_REG_679165_PIC_TIME_TOP_SHFT 0 + +#define VIDC_REG_374150_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000edc) +#define VIDC_REG_374150_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000edc) +#define VIDC_REG_374150_RMSK 0xffffffff +#define VIDC_REG_374150_SHFT 0 +#define VIDC_REG_374150_IN \ + in_dword_masked(VIDC_REG_374150_ADDR, \ + VIDC_REG_374150_RMSK) +#define VIDC_REG_374150_INM(m) \ + in_dword_masked(VIDC_REG_374150_ADDR, m) +#define VIDC_REG_374150_PIC_TIME_BOTTOM_BMSK 0xffffffff +#define VIDC_REG_374150_PIC_TIME_BOTTOM_SHFT 0 + +#define VIDC_REG_94750_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ee0) +#define VIDC_REG_94750_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ee0) +#define VIDC_REG_94750_RMSK 0xffffffff +#define VIDC_REG_94750_SHFT 0 +#define VIDC_REG_94750_OUT(v) \ + out_dword(VIDC_REG_94750_ADDR, v) + +#define VIDC_REG_438677_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ee4) +#define VIDC_REG_438677_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ee4) +#define VIDC_REG_438677_RMSK 0xffffffff +#define VIDC_REG_438677_SHFT 0 +#define VIDC_REG_438677_IN \ + in_dword_masked(VIDC_REG_438677_ADDR, \ + VIDC_REG_438677_RMSK) +#define VIDC_REG_438677_INM(m) \ + in_dword_masked(VIDC_REG_438677_ADDR, m) +#define VIDC_REG_438677_GET_FRAME_TAG_BOTTOM_BMSK 0xffffffff +#define VIDC_REG_438677_GET_FRAME_TAG_BOTTOM_SHFT 0 + +#define VIDC_REG_76706_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000ee8) +#define VIDC_REG_76706_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000ee8) +#define VIDC_REG_76706_RMSK 0x1 +#define VIDC_REG_76706_SHFT 0 +#define VIDC_REG_76706_OUT(v) \ + out_dword(VIDC_REG_76706_ADDR, v) + +#define VIDC_REG_809984_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00001000) +#define VIDC_REG_809984_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00001000) +#define VIDC_REG_809984_RMSK 0xffff0007 +#define VIDC_REG_809984_SHFT 0 +#define VIDC_REG_809984_IN \ + in_dword_masked(VIDC_REG_809984_ADDR, VIDC_REG_809984_RMSK) +#define VIDC_REG_809984_INM(m) \ + in_dword_masked(VIDC_REG_809984_ADDR, m) +#define VIDC_REG_809984_720PV_720P_WRAPPER_VERSION_BMSK 0xffff0000 +#define VIDC_REG_809984_720PV_720P_WRAPPER_VERSION_SHFT 0x10 +#define VIDC_REG_809984_TEST_MUX_SEL_BMSK 0x7 +#define VIDC_REG_809984_TEST_MUX_SEL_SHFT 0 + + +#define VIDC_REG_699747_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000d0c) +#define VIDC_REG_699747_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000d0c) +#define VIDC_REG_699747_RMSK 0xffffffff +#define VIDC_REG_699747_SHFT 0 +#define VIDC_REG_699747_OUT(v) \ + out_dword(VIDC_REG_699747_ADDR, v) + +#define VIDC_REG_166247_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000d10) +#define VIDC_REG_166247_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000d10) +#define VIDC_REG_166247_RMSK 0xffffffff +#define VIDC_REG_166247_SHFT 0 +#define VIDC_REG_166247_OUT(v) \ + out_dword(VIDC_REG_166247_ADDR, v) + +#define VIDC_REG_486169_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000d18) +#define VIDC_REG_486169_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000d18) +#define VIDC_REG_486169_RMSK 0xffffffff +#define VIDC_REG_486169_SHFT 0 +#define VIDC_REG_486169_OUT(v) \ + out_dword(VIDC_REG_486169_ADDR, v) + +#define VIDC_REG_926519_ADDR \ + (VIDC_720P_WRAPPER_REG_BASE + 0x00000d1c) +#define VIDC_REG_926519_PHYS \ + (VIDC_720P_WRAPPER_REG_BASE_PHYS + 0x00000d1c) +#define VIDC_REG_926519_RMSK 0xffffffff +#define VIDC_REG_926519_SHFT 0 +#define VIDC_REG_926519_OUT(v) \ + out_dword(VIDC_REG_926519_ADDR, v) + +/** List all the levels and their register valus */ + +#define VIDC_720P_PROFILE_MPEG4_SP 0 +#define VIDC_720P_PROFILE_MPEG4_ASP 1 +#define VIDC_720P_PROFILE_H264_BASELINE 0 +#define VIDC_720P_PROFILE_H264_MAIN 1 +#define VIDC_720P_PROFILE_H264_HIGH 2 +#define VIDC_720P_PROFILE_H264_CPB 3 +#define VIDC_720P_PROFILE_H263_BASELINE 0 + +#define VIDC_720P_PROFILE_VC1_SP 0 +#define VIDC_720P_PROFILE_VC1_MAIN 1 +#define VIDC_720P_PROFILE_VC1_ADV 2 +#define VIDC_720P_PROFILE_MPEG2_MAIN 4 +#define VIDC_720P_PROFILE_MPEG2_SP 5 + +#define VIDC_720P_MPEG4_LEVEL0 0 +#define VIDC_720P_MPEG4_LEVEL0b 9 +#define VIDC_720P_MPEG4_LEVEL1 1 +#define VIDC_720P_MPEG4_LEVEL2 2 +#define VIDC_720P_MPEG4_LEVEL3 3 +#define VIDC_720P_MPEG4_LEVEL3b 7 +#define VIDC_720P_MPEG4_LEVEL4a 4 +#define VIDC_720P_MPEG4_LEVEL5 5 +#define VIDC_720P_MPEG4_LEVEL6 6 + +#define VIDC_720P_H264_LEVEL1 10 +#define VIDC_720P_H264_LEVEL1b 9 +#define VIDC_720P_H264_LEVEL1p1 11 +#define VIDC_720P_H264_LEVEL1p2 12 +#define VIDC_720P_H264_LEVEL1p3 13 +#define VIDC_720P_H264_LEVEL2 20 +#define VIDC_720P_H264_LEVEL2p1 21 +#define VIDC_720P_H264_LEVEL2p2 22 +#define VIDC_720P_H264_LEVEL3 30 +#define VIDC_720P_H264_LEVEL3p1 31 +#define VIDC_720P_H264_LEVEL3p2 32 + +#define VIDC_720P_H263_LEVEL10 10 +#define VIDC_720P_H263_LEVEL20 20 +#define VIDC_720P_H263_LEVEL30 30 +#define VIDC_720P_H263_LEVEL40 40 +#define VIDC_720P_H263_LEVEL45 45 +#define VIDC_720P_H263_LEVEL50 50 +#define VIDC_720P_H263_LEVEL60 60 +#define VIDC_720P_H263_LEVEL70 70 + +#define VIDC_720P_VC1_LEVEL_LOW 0 +#define VIDC_720P_VC1_LEVEL_MED 2 +#define VIDC_720P_VC1_LEVEL_HIGH 4 +#define VIDC_720P_VC1_LEVEL0 0 +#define VIDC_720P_VC1_LEVEL1 1 +#define VIDC_720P_VC1_LEVEL2 2 +#define VIDC_720P_VC1_LEVEL3 3 +#define VIDC_720P_VC1_LEVEL4 4 + +#define VIDCL_720P_MPEG2_LEVEL_LOW 10 +#define VIDCL_720P_MPEG2_LEVEL_MAIN 8 +#define VIDCL_720P_MPEG2_LEVEL_HIGH14 6 + +#define VIDC_720P_CMD_CHSET 0x0 +#define VIDC_720P_CMD_CHEND 0x2 +#define VIDC_720P_CMD_INITCODEC 0x3 +#define VIDC_720P_CMD_FRAMERUN 0x4 +#define VIDC_720P_CMD_INITBUFFERS 0x5 +#define VIDC_720P_CMD_FRAMERUN_REALLOCATE 0x6 +#define VIDC_720P_CMD_MFC_ENGINE_RESET 0x7 + +enum vidc_720p_endian { + VIDC_720P_BIG_ENDIAN = 0x0, + VIDC_720P_LITTLE_ENDIAN = 0x1 +}; + +enum vidc_720p_memory_access_method { + VIDC_720P_TILE_LINEAR = 0, + VIDC_720P_TILE_16x16 = 2, + VIDC_720P_TILE_64x32 = 3 +}; + +enum vidc_720p_interrupt_control_mode { + VIDC_720P_INTERRUPT_MODE = 0, + VIDC_720P_POLL_MODE = 1 +}; + +enum vidc_720p_interrupt_level_selection { + VIDC_720P_INTERRUPT_LEVEL_SEL = 0, + VIDC_720P_INTERRUPT_PULSE_SEL = 1 +}; + +#define VIDC_720P_INTR_BUFFER_FULL 0x002 +#define VIDC_720P_INTR_FW_DONE 0x020 +#define VIDC_720P_INTR_HEADER_DONE 0x040 +#define VIDC_720P_INTR_DMA_DONE 0x080 +#define VIDC_720P_INTR_FRAME_DONE 0x100 + +enum vidc_720p_enc_dec_selection { + VIDC_720P_DECODER = 0, + VIDC_720P_ENCODER = 1 +}; + +enum vidc_720p_codec { + VIDC_720P_MPEG4 = 0, + VIDC_720P_H264 = 1, + VIDC_720P_DIVX = 2, + VIDC_720P_XVID = 3, + VIDC_720P_H263 = 4, + VIDC_720P_MPEG2 = 5, + VIDC_720P_VC1 = 6 +}; + +enum vidc_720p_frame { + VIDC_720P_NOTCODED = 0, + VIDC_720P_IFRAME = 1, + VIDC_720P_PFRAME = 2, + VIDC_720P_BFRAME = 3 +}; + +enum vidc_720p_entropy_sel { + VIDC_720P_ENTROPY_SEL_CAVLC = 0, + VIDC_720P_ENTROPY_SEL_CABAC = 1 +}; + +enum vidc_720p_cabac_model { + VIDC_720P_CABAC_MODEL_NUMBER_0 = 0, + VIDC_720P_CABAC_MODEL_NUMBER_1 = 1, + VIDC_720P_CABAC_MODEL_NUMBER_2 = 2 +}; + +enum vidc_720p_DBConfig { + VIDC_720P_DB_ALL_BLOCKING_BOUNDARY = 0, + VIDC_720P_DB_DISABLE = 1, + VIDC_720P_DB_SKIP_SLICE_BOUNDARY = 2 +}; + +enum vidc_720p_MSlice_selection { + VIDC_720P_MSLICE_BY_MB_COUNT = 0, + VIDC_720P_MSLICE_BY_BYTE_COUNT = 1, + VIDC_720P_MSLICE_BY_GOB = 2, + VIDC_720P_MSLICE_OFF = 3 +}; + +enum vidc_720p_display_status { + VIDC_720P_DECODE_ONLY = 0, + VIDC_720P_DECODE_AND_DISPLAY = 1, + VIDC_720P_DISPLAY_ONLY = 2, + VIDC_720P_EMPTY_BUFFER = 3 +}; + +#define VIDC_720P_ENC_IFRAME_REQ 0x1 +#define VIDC_720P_ENC_IPERIOD_CHANGE 0x1 +#define VIDC_720P_ENC_FRAMERATE_CHANGE 0x2 +#define VIDC_720P_ENC_BITRATE_CHANGE 0x4 + +#define VIDC_720P_FLUSH_REQ 0x1 +#define VIDC_720P_EXTRADATA 0x2 + +#define VIDC_720P_METADATA_ENABLE_QP 0x01 +#define VIDC_720P_METADATA_ENABLE_CONCEALMB 0x02 +#define VIDC_720P_METADATA_ENABLE_VC1 0x04 +#define VIDC_720P_METADATA_ENABLE_SEI 0x08 +#define VIDC_720P_METADATA_ENABLE_VUI 0x10 +#define VIDC_720P_METADATA_ENABLE_ENCSLICE 0x20 +#define VIDC_720P_METADATA_ENABLE_PASSTHROUGH 0x40 + +struct vidc_720p_dec_disp_info { + enum vidc_720p_display_status disp_status; + u32 resl_change; + u32 reconfig_flush_done; + u32 img_size_x; + u32 img_size_y; + u32 y_addr; + u32 c_addr; + u32 tag_top; + u32 pic_time_top; + u32 disp_is_interlace; + u32 tag_bottom; + u32 pic_time_bottom; + u32 metadata_exists; + u32 crop_exists; + u32 crop_right_offset; + u32 crop_left_offset; + u32 crop_bottom_offset; + u32 crop_top_offset; + u32 input_frame; + u32 input_bytes_consumed; + u32 input_is_interlace; + u32 input_frame_num; +}; + +struct vidc_720p_seq_hdr_info { + u32 img_size_x; + u32 img_size_y; + u32 dec_frm_size; + u32 min_num_dpb; + u32 min_dpb_size; + u32 profile; + u32 level; + u32 progressive; + u32 data_partitioned; + u32 crop_exists; + u32 crop_right_offset; + u32 crop_left_offset; + u32 crop_bottom_offset; + u32 crop_top_offset; +}; + +struct vidc_720p_enc_frame_info { + u32 enc_size; + u32 frame; + u32 metadata_exists; +}; + +void vidc_720p_set_device_virtual_base(u8 *core_virtual_base_addr); + +void vidc_720p_init(char **ppsz_version, u32 i_firmware_size, + u32 *pi_firmware_address, enum vidc_720p_endian dma_endian, + u32 interrupt_off, + enum vidc_720p_interrupt_level_selection interrupt_sel, + u32 interrupt_mask); + +u32 vidc_720p_do_sw_reset(void); + +u32 vidc_720p_reset_is_success(void); + +void vidc_720p_start_cpu(enum vidc_720p_endian dma_endian, + u32 *icontext_bufferstart, u32 *debug_core_dump_addr, + u32 debug_buffer_size); + +u32 vidc_720p_cpu_start(void); + +void vidc_720p_stop_fw(void); + +void vidc_720p_get_interrupt_status(u32 *interrupt_status, + u32 *cmd_err_status, u32 *disp_pic_err_status, + u32 *op_failed); + +void vidc_720p_interrupt_done_clear(void); + +void vidc_720p_submit_command(u32 ch_id, u32 cmd_id); + + +void vidc_720p_set_channel(u32 i_ch_id, + enum vidc_720p_enc_dec_selection enc_dec_sel, + enum vidc_720p_codec codec, u32 *pi_fw, u32 i_firmware_size); + +u32 vidc_720p_engine_reset(u32 ch_id, + enum vidc_720p_endian dma_endian, + enum vidc_720p_interrupt_level_selection interrupt_sel, + u32 interrupt_mask +); + +void vidc_720p_encode_set_profile(u32 i_profile, u32 i_level); + +void vidc_720p_set_frame_size(u32 i_size_x, u32 i_size_y); + +void vidc_720p_encode_set_fps(u32 i_rc_frame_rate); + +void vidc_720p_encode_set_vop_time(u32 vop_time_resolution, + u32 vop_time_increment); + +void vidc_720p_encode_set_hec_period(u32 hec_period); + +void vidc_720p_encode_set_short_header(u32 i_short_header); + +void vidc_720p_encode_set_qp_params(u32 i_max_qp, u32 i_min_qp); + +void vidc_720p_encode_set_rc_config(u32 enable_frame_level_rc, + u32 enable_mb_level_rc_flag, u32 i_frame_qp, u32 pframe_qp); + +void vidc_720p_encode_set_bit_rate(u32 i_target_bitrate); + +void vidc_720p_encoder_set_param_change(u32 enc_param_change); + +void vidc_720p_encode_set_control_param(u32 param_val); + +void vidc_720p_encode_set_frame_level_rc_params(u32 i_reaction_coeff); + +void vidc_720p_encode_set_mb_level_rc_params(u32 dark_region_as_flag, + u32 smooth_region_as_flag, u32 static_region_as_flag, + u32 activity_region_flag); + +void vidc_720p_encode_set_entropy_control(enum vidc_720p_entropy_sel \ + entropy_sel, + enum vidc_720p_cabac_model cabac_model_number); + +void vidc_720p_encode_set_db_filter_control(enum vidc_720p_DBConfig + db_config, u32 i_slice_alpha_offset, u32 i_slice_beta_offset); + +void vidc_720p_encode_set_intra_refresh_mb_number(u32 i_cir_mb_number); + +void vidc_720p_encode_set_multi_slice_info( + enum vidc_720p_MSlice_selection m_slice_sel, + u32 multi_slice_size); + +void vidc_720p_encode_set_dpb_buffer(u32 *pi_enc_dpb_addr, u32 alloc_len); + +void vidc_720p_set_deblock_line_buffer(u32 *pi_deblock_line_buffer_start, + u32 alloc_len); + +void vidc_720p_encode_set_i_period(u32 i_i_period); + +void vidc_720p_encode_init_codec(u32 i_ch_id, + enum vidc_720p_memory_access_method memory_access_model); + +void vidc_720p_encode_unalign_bitstream(u32 upper_unalign_word, + u32 lower_unalign_word); + +void vidc_720p_encode_set_seq_header_buffer(u32 ext_buffer_start, + u32 ext_buffer_end, u32 start_byte_num); + +void vidc_720p_encode_frame(u32 ch_id, u32 ext_buffer_start, + u32 ext_buffer_end, u32 start_byte_number, + u32 y_addr, u32 c_addr); + +void vidc_720p_encode_get_header(u32 *pi_enc_header_size); + +void vidc_720p_enc_frame_info + (struct vidc_720p_enc_frame_info *enc_frame_info); + +void vidc_720p_decode_bitstream_header(u32 ch_id, u32 dec_unit_size, + u32 start_byte_num, u32 ext_buffer_start, u32 ext_buffer_end, + enum vidc_720p_memory_access_method memory_access_model, + u32 decode_order); + +void vidc_720p_decode_get_seq_hdr_info + (struct vidc_720p_seq_hdr_info *seq_hdr_info); + +void vidc_720p_decode_set_dpb_release_buffer_mask + (u32 i_dpb_release_buffer_mask); + +void vidc_720p_decode_set_dpb_buffers(u32 i_buf_index, u32 *pi_dpb_buffer); + +void vidc_720p_decode_set_comv_buffer + (u32 *pi_dpb_comv_buffer, u32 alloc_len); + +void vidc_720p_decode_set_dpb_details + (u32 num_dpb, u32 alloc_len, u32 *ref_buffer); + +void vidc_720p_decode_set_mpeg4Post_filter(u32 enable_post_filter); + +void vidc_720p_decode_set_error_control(u32 enable_error_control); + +void vidc_720p_decode_set_mpeg4_data_partitionbuffer(u32 *vsp_buf_start); + +void vidc_720p_decode_setH264VSPBuffer(u32 *pi_vsp_temp_buffer_start); + +void vidc_720p_decode_frame(u32 ch_id, u32 ext_buffer_start, + u32 ext_buffer_end, u32 dec_unit_size, + u32 start_byte_num, u32 input_frame_tag); + +void vidc_720p_issue_eos(u32 i_ch_id); +void vidc_720p_eos_info(u32 *disp_status, u32 *resl_change); + +void vidc_720p_decode_display_info + (struct vidc_720p_dec_disp_info *disp_info); + +void vidc_720p_decode_skip_frm_details(u32 *free_luma_dpb); + +void vidc_720p_metadata_enable(u32 flag, u32 *input_buffer); + +void vidc_720p_decode_dynamic_req_reset(void); + +void vidc_720p_decode_dynamic_req_set(u32 property); + +void vidc_720p_decode_setpassthrough_start(u32 pass_startaddr); + + + +#define DDL_720P_REG_BASE VIDC_720P_WRAPPER_REG_BASE +#define VIDC_BUSY_WAIT(n) udelay(n) + +#undef VIDC_REGISTER_LOG_MSG +#undef VIDC_REGISTER_LOG_INTO_BUFFER + +#ifdef VIDC_REGISTER_LOG_MSG +#define VIDC_MSG1(msg_format, a) printk(KERN_INFO msg_format, a) +#define VIDC_MSG2(msg_format, a, b) printk(KERN_INFO msg_format, a, b) +#define VIDC_MSG3(msg_format, a, b, c) printk(KERN_INFO msg_format, a, b, c) +#else +#define VIDC_MSG1(msg_format, a) +#define VIDC_MSG2(msg_format, a, b) +#define VIDC_MSG3(msg_format, a, b, c) +#endif + +#ifdef VIDC_REGISTER_LOG_INTO_BUFFER + +#define VIDC_REGLOG_BUFSIZE 200000 +#define VIDC_REGLOG_MAX_PRINT_SIZE 100 +extern char vidclog[VIDC_REGLOG_BUFSIZE]; +extern unsigned int vidclog_index; + +#define VIDC_LOG_BUFFER_INIT \ +{if (vidclog_index) \ + memset(vidclog, 0, vidclog_index+1); \ + vidclog_index = 0; } + +#define VIDC_REGLOG_CHECK_BUFINDEX(req_size) \ + vidclog_index = \ + (vidclog_index+(req_size) < VIDC_REGLOG_BUFSIZE) ? vidclog_index : 0; + +#define VIDC_LOG_WRITE(reg, val) \ +{unsigned int len; \ + VIDC_REGLOG_CHECK_BUFINDEX(VIDC_REGLOG_MAX_PRINT_SIZE); \ + len = snprintf(&vidclog[vidclog_index], VIDC_REGLOG_MAX_PRINT_SIZE, \ + "(0x%x:"#reg"=0x%x)" , VIDC_##reg##_ADDR - DDL_720P_REG_BASE, val);\ + vidclog_index += len; } + +#define VIDC_LOG_WRITEI(reg, index, val) \ +{unsigned int len; \ + VIDC_REGLOG_CHECK_BUFINDEX(VIDC_REGLOG_MAX_PRINT_SIZE); \ + len = snprintf(&vidclog[vidclog_index], VIDC_REGLOG_MAX_PRINT_SIZE, \ + "(0x%x:"#reg"=0x%x)" , VIDC_##reg##_ADDR(index)-DDL_720P_REG_BASE, \ + val); vidclog_index += len; } + +#define VIDC_LOG_WRITEF(reg, field, val) \ +{unsigned int len; \ + VIDC_REGLOG_CHECK_BUFINDEX(VIDC_REGLOG_MAX_PRINT_SIZE); \ + len = snprintf(&vidclog[vidclog_index], VIDC_REGLOG_MAX_PRINT_SIZE, \ + "(0x%x:"#reg":0x%x:=0x%x)" , VIDC_##reg##_ADDR - DDL_720P_REG_BASE, \ + VIDC_##reg##_##field##_BMSK, val);\ + vidclog_index += len; } + +#define VIDC_LOG_READ(reg, pval) \ +{ unsigned int len; \ + VIDC_REGLOG_CHECK_BUFINDEX(VIDC_REGLOG_MAX_PRINT_SIZE); \ + len = snprintf(&vidclog[vidclog_index], VIDC_REGLOG_MAX_PRINT_SIZE, \ + "(0x%x:"#reg"==0x%x)" , VIDC_##reg##_ADDR - DDL_720P_REG_BASE, \ + (u32)*pval); \ + vidclog_index += len; } + +#define VIDC_STR_LOGBUFFER(str) \ +{ unsigned int len; \ + VIDC_REGLOG_CHECK_BUFINDEX(VIDC_REGLOG_MAX_PRINT_SIZE); \ + len = snprintf(&vidclog[vidclog_index], VIDC_REGLOG_MAX_PRINT_SIZE, \ + "<%s>" , str); vidclog_index += len; } + +#define VIDC_LONG_LOGBUFFER(str, arg1) \ +{ unsigned int len; \ + VIDC_REGLOG_CHECK_BUFINDEX(VIDC_REGLOG_MAX_PRINT_SIZE); \ + len = snprintf(&vidclog[vidclog_index], VIDC_REGLOG_MAX_PRINT_SIZE, \ + "<%s=0x%x>" , str, arg1); vidclog_index += len; } + +#define VIDC_DEBUG_REGISTER_LOG \ +{ u32 val; unsigned int len; \ + val = VIDC_720P_IN(REG_881638); \ + VIDC_REGLOG_CHECK_BUFINDEX(VIDC_REGLOG_MAX_PRINT_SIZE); \ + len = snprintf(&vidclog[vidclog_index], 50, "[dbg1=%x]" , val); \ + vidclog_index += len; \ + val = VIDC_720P_IN(REG_161486); \ + VIDC_REGLOG_CHECK_BUFINDEX(VIDC_REGLOG_MAX_PRINT_SIZE); \ + len = snprintf(&vidclog[vidclog_index], 50, "[dbg2=%x]" , val); \ + vidclog_index += len; } + +#else +#define VIDC_LOG_WRITE(reg, val) +#define VIDC_LOG_WRITEI(reg, index, val) +#define VIDC_LOG_WRITEF(reg, field, val) +#define VIDC_LOG_READ(reg, pval) +#define VIDC_LOG_BUFFER_INIT +#define VIDC_STR_LOGBUFFER(str) +#define VIDC_LONG_LOGBUFFER(str, arg1) +#define VIDC_DEBUG_REGISTER_LOG +#endif + +void vidcputlog(char *str); +void vidcput_debug_reglog(void); + +#define VIDC_LOGERR_STRING(str) \ +do { \ + VIDC_STR_LOGBUFFER(str); \ + VIDC_MSG1("\n<%s>", str); \ +} while (0) + +#define VIDC_LOG_STRING(str) \ +do { \ + VIDC_STR_LOGBUFFER(str); \ + VIDC_MSG1("\n<%s>", str); \ +} while (0) + +#define VIDC_LOG1(str, arg1) \ +do { \ + VIDC_LONG_LOGBUFFER(str, arg1); \ + VIDC_MSG2("\n<%s=0x%08x>", str, arg1); \ +} while (0) + +#define VIDC_IO_OUT(reg, val) \ +do { \ + VIDC_LOG_WRITE(reg, (u32)val); \ + VIDC_MSG2("\n(0x%08x:"#reg"=0x%08x)", \ + (u32)(VIDC_##reg##_ADDR - DDL_720P_REG_BASE), (u32)val); \ + mb(); \ + VIDC_720P_OUT(reg, val); \ +} while (0) + +#define VIDC_IO_OUTI(reg, index, val) \ +do { \ + VIDC_LOG_WRITEI(reg, index, (u32)val); \ + VIDC_MSG2("\n(0x%08x:"#reg"=0x%08x)", \ + (u32)(VIDC_##reg##_ADDR(index)-DDL_720P_REG_BASE), (u32)val); \ + mb(); \ + VIDC_720P_OUTI(reg, index, val); \ +} while (0) + +#define VIDC_IO_OUTF(reg, field, val) \ +do { \ + VIDC_LOG_WRITEF(reg, field, val); \ + VIDC_MSG3("\n(0x%08x:"#reg":0x%x:=0x%08x)", \ + (u32)(VIDC_##reg##_ADDR - DDL_720P_REG_BASE), \ + VIDC_##reg##_##field##_BMSK, (u32)val); \ + mb(); \ + VIDC_720P_OUTF(reg, field, val); \ +} while (0) + +#define VIDC_IO_IN(reg, pval) \ +do { \ + mb(); \ + *pval = (u32) VIDC_720P_IN(reg); \ + VIDC_LOG_READ(reg, pval); \ + VIDC_MSG2("\n(0x%08x:"#reg"==0x%08x)", \ + (u32)(VIDC_##reg##_ADDR - DDL_720P_REG_BASE), (u32) *pval); \ +} while (0) + +#define VIDC_IO_INF(reg, mask, pval) \ +do { \ + mb(); \ + *pval = VIDC_720P_INF(reg, mask); \ + VIDC_LOG_READ(reg, pval); \ + VIDC_MSG2("\n(0x%08x:"#reg"==0x%08x)", \ + (u32)(VIDC_##reg##_ADDR - DDL_720P_REG_BASE), *pval); \ +} while (0) + +#endif diff --git a/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.c b/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.c new file mode 100644 index 00000000000..12dcbf3e98f --- /dev/null +++ b/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.c @@ -0,0 +1,722 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include "vidc_type.h" +#include "vcd_res_tracker.h" +#include "vidc_init.h" + +#define MSM_AXI_QOS_NAME "msm_vidc_reg" +#define AXI_CLK_SCALING + +#define QVGA_PERF_LEVEL (300 * 30) +#define VGA_PERF_LEVEL (1200 * 30) +#define WVGA_PERF_LEVEL (1500 * 30) + +static unsigned int mfc_clk_freq_table[3] = { + 61440000, 122880000, 170667000 +}; + +#ifndef CONFIG_MSM_NPA_SYSTEM_BUS +static unsigned int axi_clk_freq_table_enc[2] = { + 122880, 192000 +}; +static unsigned int axi_clk_freq_table_dec[2] = { + 122880, 192000 +}; +#else +static unsigned int axi_clk_freq_table_enc[2] = { + MSM_AXI_FLOW_VIDEO_RECORDING_720P, + MSM_AXI_FLOW_VIDEO_RECORDING_720P +}; +static unsigned int axi_clk_freq_table_dec[2] = { + MSM_AXI_FLOW_VIDEO_PLAYBACK_720P, + MSM_AXI_FLOW_VIDEO_PLAYBACK_720P +}; +#endif + +static struct res_trk_context resource_context; + +#define VIDC_BOOT_FW "vidc_720p_command_control.fw" +#define VIDC_MPG4_DEC_FW "vidc_720p_mp4_dec_mc.fw" +#define VIDC_H263_DEC_FW "vidc_720p_h263_dec_mc.fw" +#define VIDC_H264_DEC_FW "vidc_720p_h264_dec_mc.fw" +#define VIDC_MPG4_ENC_FW "vidc_720p_mp4_enc_mc.fw" +#define VIDC_H264_ENC_FW "vidc_720p_h264_enc_mc.fw" +#define VIDC_VC1_DEC_FW "vidc_720p_vc1_dec_mc.fw" + +unsigned char *vidc_command_control_fw; +u32 vidc_command_control_fw_size; + +unsigned char *vidc_mpg4_dec_fw; +u32 vidc_mpg4_dec_fw_size; + +unsigned char *vidc_h263_dec_fw; +u32 vidc_h263_dec_fw_size; + +unsigned char *vidc_h264_dec_fw; +u32 vidc_h264_dec_fw_size; + +unsigned char *vidc_mpg4_enc_fw; +u32 vidc_mpg4_enc_fw_size; + +unsigned char *vidc_h264_enc_fw; +u32 vidc_h264_enc_fw_size; + +unsigned char *vidc_vc1_dec_fw; +u32 vidc_vc1_dec_fw_size; + +static u32 res_trk_disable_videocore(void) +{ + int rc = -1; + mutex_lock(&resource_context.lock); + + if (!resource_context.rail_enabled) { + mutex_unlock(&resource_context.lock); + return false; + } + + if (!resource_context.clock_enabled && + resource_context.pclk && + resource_context.hclk && + resource_context.hclk_div2) { + + VCDRES_MSG_LOW("\nEnabling clk before disabling pwr rail\n"); + if (clk_set_rate(resource_context.hclk, + mfc_clk_freq_table[0])) { + VCDRES_MSG_ERROR("\n pwr_rail_disable:" + " set clk rate failed\n"); + goto bail_out; + } + + if (clk_enable(resource_context.pclk)) { + VCDRES_MSG_ERROR("vidc pclk Enable failed\n"); + goto bail_out; + } + + if (clk_enable(resource_context.hclk)) { + VCDRES_MSG_ERROR("vidc hclk Enable failed\n"); + goto disable_pclk; + } + + if (clk_enable(resource_context.hclk_div2)) { + VCDRES_MSG_ERROR("vidc hclk_div2 Enable failed\n"); + goto disable_hclk; + } + } else { + VCDRES_MSG_ERROR("\ndisabling pwr rail: Enabling clk failed\n"); + goto bail_out; + } + + resource_context.rail_enabled = 0; + rc = clk_reset(resource_context.pclk, CLK_RESET_ASSERT); + if (rc) { + VCDRES_MSG_ERROR("\n clk_reset failed %d\n", rc); + mutex_unlock(&resource_context.lock); + return false; + } + msleep(20); + + clk_disable(resource_context.pclk); + clk_disable(resource_context.hclk); + clk_disable(resource_context.hclk_div2); + + clk_put(resource_context.hclk_div2); + clk_put(resource_context.hclk); + clk_put(resource_context.pclk); + + rc = regulator_disable(resource_context.regulator); + if (rc) { + VCDRES_MSG_ERROR("\n regulator disable failed %d\n", rc); + mutex_unlock(&resource_context.lock); + return false; + } + + resource_context.hclk_div2 = NULL; + resource_context.hclk = NULL; + resource_context.pclk = NULL; + + mutex_unlock(&resource_context.lock); + + return true; + +disable_hclk: + clk_disable(resource_context.hclk); +disable_pclk: + clk_disable(resource_context.pclk); +bail_out: + if (resource_context.pclk) { + clk_put(resource_context.pclk); + resource_context.pclk = NULL; + } + if (resource_context.hclk) { + clk_put(resource_context.hclk); + resource_context.hclk = NULL; + } + if (resource_context.hclk_div2) { + clk_put(resource_context.hclk_div2); + resource_context.hclk_div2 = NULL; + } + mutex_unlock(&resource_context.lock); + return false; +} + +u32 res_trk_enable_clocks(void) +{ + VCDRES_MSG_LOW("\n in res_trk_enable_clocks()"); + + mutex_lock(&resource_context.lock); + if (!resource_context.clock_enabled) { + VCDRES_MSG_LOW("Enabling IRQ in %s()\n", __func__); + enable_irq(resource_context.irq_num); + + VCDRES_MSG_LOW("%s(): Enabling the clocks ...\n", __func__); + + if (clk_enable(resource_context.pclk)) { + VCDRES_MSG_ERROR("vidc pclk Enable failed\n"); + + clk_put(resource_context.hclk); + clk_put(resource_context.hclk_div2); + mutex_unlock(&resource_context.lock); + return false; + } + + if (clk_enable(resource_context.hclk)) { + VCDRES_MSG_ERROR("vidc hclk Enable failed\n"); + clk_put(resource_context.pclk); + clk_put(resource_context.hclk_div2); + mutex_unlock(&resource_context.lock); + return false; + } + + if (clk_enable(resource_context.hclk_div2)) { + VCDRES_MSG_ERROR("vidc hclk Enable failed\n"); + clk_put(resource_context.hclk); + clk_put(resource_context.pclk); + mutex_unlock(&resource_context.lock); + return false; + } + } + + resource_context.clock_enabled = 1; + mutex_unlock(&resource_context.lock); + return true; +} + +static u32 res_trk_sel_clk_rate(unsigned long hclk_rate) +{ + mutex_lock(&resource_context.lock); + if (clk_set_rate(resource_context.hclk, + hclk_rate)) { + VCDRES_MSG_ERROR("vidc hclk set rate failed\n"); + mutex_unlock(&resource_context.lock); + return false; + } + resource_context.hclk_rate = hclk_rate; + mutex_unlock(&resource_context.lock); + return true; +} + +static u32 res_trk_get_clk_rate(unsigned long *phclk_rate) +{ + if (!phclk_rate) { + VCDRES_MSG_ERROR("%s(): phclk_rate is NULL\n", __func__); + return false; + } + mutex_lock(&resource_context.lock); + *phclk_rate = clk_get_rate(resource_context.hclk); + if (!(*phclk_rate)) { + VCDRES_MSG_ERROR("vidc hclk get rate failed\n"); + mutex_unlock(&resource_context.lock); + return false; + } + mutex_unlock(&resource_context.lock); + return true; +} + +u32 res_trk_disable_clocks(void) +{ + VCDRES_MSG_LOW("in res_trk_disable_clocks()\n"); + + mutex_lock(&resource_context.lock); + + if (!resource_context.clock_enabled) { + mutex_unlock(&resource_context.lock); + return false; + } + + VCDRES_MSG_LOW("Disabling IRQ in %s()\n", __func__); + disable_irq_nosync(resource_context.irq_num); + VCDRES_MSG_LOW("%s(): Disabling the clocks ...\n", __func__); + + resource_context.clock_enabled = 0; + clk_disable(resource_context.hclk); + clk_disable(resource_context.hclk_div2); + clk_disable(resource_context.pclk); + mutex_unlock(&resource_context.lock); + + return true; +} + +static u32 res_trk_enable_videocore(void) +{ + mutex_lock(&resource_context.lock); + if (!resource_context.rail_enabled) { + int rc = -1; + + rc = regulator_enable(resource_context.regulator); + if (rc) { + VCDRES_MSG_ERROR("%s(): regulator_enable failed %d\n", + __func__, rc); + goto bail_out; + } + VCDRES_MSG_LOW("%s(): regulator enable Success %d\n", + __func__, rc); + + resource_context.pclk = clk_get(resource_context.device, + "mfc_pclk"); + + if (IS_ERR(resource_context.pclk)) { + VCDRES_MSG_ERROR("%s(): mfc_pclk get failed\n" + , __func__); + goto disable_regulator; + } + + resource_context.hclk = clk_get(resource_context.device, + "mfc_clk"); + + if (IS_ERR(resource_context.hclk)) { + VCDRES_MSG_ERROR("%s(): mfc_clk get failed\n" + , __func__); + + goto release_pclk; + } + + resource_context.hclk_div2 = + clk_get(resource_context.device, "mfc_div2_clk"); + + if (IS_ERR(resource_context.hclk_div2)) { + VCDRES_MSG_ERROR("%s(): mfc_div2_clk get failed\n" + , __func__); + goto release_hclk_pclk; + } + + if (clk_set_rate(resource_context.hclk, + mfc_clk_freq_table[0])) { + VCDRES_MSG_ERROR("\n pwr_rail_enable:" + " set clk rate failed\n"); + goto release_all_clks; + } + + if (clk_enable(resource_context.pclk)) { + VCDRES_MSG_ERROR("vidc pclk Enable failed\n"); + goto release_all_clks; + } + + if (clk_enable(resource_context.hclk)) { + VCDRES_MSG_ERROR("vidc hclk Enable failed\n"); + goto disable_pclk; + } + + if (clk_enable(resource_context.hclk_div2)) { + VCDRES_MSG_ERROR("vidc hclk_div2 Enable failed\n"); + goto disable_hclk_pclk; + } + + rc = clk_reset(resource_context.pclk, CLK_RESET_DEASSERT); + if (rc) { + VCDRES_MSG_ERROR("\n clk_reset failed %d\n", rc); + goto disable_and_release_all_clks; + } + msleep(20); + + clk_disable(resource_context.pclk); + clk_disable(resource_context.hclk); + clk_disable(resource_context.hclk_div2); + + } + resource_context.rail_enabled = 1; + mutex_unlock(&resource_context.lock); + return true; + +disable_and_release_all_clks: + clk_disable(resource_context.hclk_div2); +disable_hclk_pclk: + clk_disable(resource_context.hclk); +disable_pclk: + clk_disable(resource_context.pclk); +release_all_clks: + clk_put(resource_context.hclk_div2); + resource_context.hclk_div2 = NULL; +release_hclk_pclk: + clk_put(resource_context.hclk); + resource_context.hclk = NULL; +release_pclk: + clk_put(resource_context.pclk); + resource_context.pclk = NULL; +disable_regulator: + regulator_disable(resource_context.regulator); +bail_out: + mutex_unlock(&resource_context.lock); + return false; +} + +static u32 res_trk_convert_freq_to_perf_lvl(u64 freq) +{ + u64 perf_lvl; + u64 temp; + + VCDRES_MSG_MED("\n %s():: freq = %u\n", __func__, (u32)freq); + + if (!freq) + return 0; + + temp = freq * 1000; + do_div(temp, VCD_RESTRK_HZ_PER_1000_PERFLVL); + perf_lvl = (u32)temp; + VCDRES_MSG_MED("\n %s(): perf_lvl = %u\n", __func__, + (u32)perf_lvl); + + return (u32)perf_lvl; +} + +static u32 res_trk_convert_perf_lvl_to_freq(u64 perf_lvl) +{ + u64 freq, temp; + + VCDRES_MSG_MED("\n %s():: perf_lvl = %u\n", __func__, + (u32)perf_lvl); + temp = (perf_lvl * VCD_RESTRK_HZ_PER_1000_PERFLVL) + 999; + do_div(temp, 1000); + freq = (u32)temp; + VCDRES_MSG_MED("\n %s(): freq = %u\n", __func__, (u32)freq); + + return (u32)freq; +} + +static struct clk *ebi1_clk; + +u32 res_trk_power_up(void) +{ + VCDRES_MSG_LOW("clk_regime_rail_enable"); + VCDRES_MSG_LOW("clk_regime_sel_rail_control"); +#ifdef AXI_CLK_SCALING +{ + VCDRES_MSG_MED("\n res_trk_power_up():: " + "Calling AXI add requirement\n"); + ebi1_clk = clk_get(NULL, "ebi1_vcd_clk"); + if (IS_ERR(ebi1_clk)) { + VCDRES_MSG_ERROR("Request AXI bus QOS fails."); + return false; + } + clk_enable(ebi1_clk); +} +#endif + + VCDRES_MSG_MED("\n res_trk_power_up():: Calling " + "vidc_enable_pwr_rail()\n"); + return res_trk_enable_videocore(); +} + +u32 res_trk_power_down(void) +{ + VCDRES_MSG_LOW("clk_regime_rail_disable"); +#ifdef AXI_CLK_SCALING + VCDRES_MSG_MED("\n res_trk_power_down()::" + "Calling AXI remove requirement\n"); + clk_disable(ebi1_clk); + clk_put(ebi1_clk); +#endif + VCDRES_MSG_MED("\n res_trk_power_down():: Calling " + "res_trk_disable_videocore()\n"); + return res_trk_disable_videocore(); +} + +u32 res_trk_get_max_perf_level(u32 *pn_max_perf_lvl) +{ + if (!pn_max_perf_lvl) { + VCDRES_MSG_ERROR("%s(): pn_max_perf_lvl is NULL\n", + __func__); + return false; + } + + *pn_max_perf_lvl = VCD_RESTRK_MAX_PERF_LEVEL; + return true; +} + +u32 res_trk_set_perf_level(u32 req_perf_lvl, u32 *pn_set_perf_lvl, + struct vcd_dev_ctxt *dev_ctxt) +{ + struct vcd_clnt_ctxt *cctxt_itr = NULL; + u32 axi_freq = 0, mfc_freq = 0, calc_mfc_freq = 0; + u8 enc_clnt_present = false; + + if (!pn_set_perf_lvl || !dev_ctxt) { + VCDRES_MSG_ERROR("%s(): NULL pointer! dev_ctxt(%p)\n", + __func__, dev_ctxt); + return false; + } + + VCDRES_MSG_LOW("%s(), req_perf_lvl = %d", __func__, req_perf_lvl); + calc_mfc_freq = res_trk_convert_perf_lvl_to_freq( + (u64)req_perf_lvl); + + if (calc_mfc_freq < VCD_RESTRK_MIN_FREQ_POINT) + calc_mfc_freq = VCD_RESTRK_MIN_FREQ_POINT; + else if (calc_mfc_freq > VCD_RESTRK_MAX_FREQ_POINT) + calc_mfc_freq = VCD_RESTRK_MAX_FREQ_POINT; + + cctxt_itr = dev_ctxt->cctxt_list_head; + while (cctxt_itr) { + VCDRES_MSG_LOW("\n cctxt_itr = %p", cctxt_itr); + if (!cctxt_itr->decoding) { + VCDRES_MSG_LOW("\n Encoder client"); + enc_clnt_present = true; + break; + } else { + VCDRES_MSG_LOW("\n Decoder client"); + } + cctxt_itr = cctxt_itr->next; + } + + if (enc_clnt_present) { + if (req_perf_lvl >= VGA_PERF_LEVEL) { + mfc_freq = mfc_clk_freq_table[2]; + axi_freq = axi_clk_freq_table_enc[1]; + } else { + mfc_freq = mfc_clk_freq_table[0]; + axi_freq = axi_clk_freq_table_enc[0]; + } + VCDRES_MSG_MED("\n ENCODER: axi_freq = %u" + ", mfc_freq = %u, calc_mfc_freq = %u," + " req_perf_lvl = %u", axi_freq, + mfc_freq, calc_mfc_freq, + req_perf_lvl); + } else { + if (req_perf_lvl <= QVGA_PERF_LEVEL) { + mfc_freq = mfc_clk_freq_table[0]; + axi_freq = axi_clk_freq_table_dec[0]; + } else { + axi_freq = axi_clk_freq_table_dec[0]; + if (req_perf_lvl <= VGA_PERF_LEVEL) + mfc_freq = mfc_clk_freq_table[0]; + else if (req_perf_lvl <= WVGA_PERF_LEVEL) + mfc_freq = mfc_clk_freq_table[1]; + else { + mfc_freq = mfc_clk_freq_table[2]; + axi_freq = axi_clk_freq_table_dec[1]; + } + } + VCDRES_MSG_MED("\n DECODER: axi_freq = %u" + ", mfc_freq = %u, calc_mfc_freq = %u," + " req_perf_lvl = %u", axi_freq, + mfc_freq, calc_mfc_freq, + req_perf_lvl); + } + +#ifdef AXI_CLK_SCALING + if (req_perf_lvl != VCD_RESTRK_MIN_PERF_LEVEL) { + VCDRES_MSG_MED("\n %s(): Setting AXI freq to %u", + __func__, axi_freq); + clk_set_rate(ebi1_clk, axi_freq * 1000); + } +#endif + +#ifdef USE_RES_TRACKER + if (req_perf_lvl != VCD_RESTRK_MIN_PERF_LEVEL) { + VCDRES_MSG_MED("\n %s(): Setting MFC freq to %u", + __func__, mfc_freq); + if (!res_trk_sel_clk_rate(mfc_freq)) { + VCDRES_MSG_ERROR("%s(): res_trk_sel_clk_rate FAILED\n", + __func__); + *pn_set_perf_lvl = 0; + return false; + } + } +#endif + + *pn_set_perf_lvl = + res_trk_convert_freq_to_perf_lvl((u64) mfc_freq); + return true; +} + +u32 res_trk_get_curr_perf_level(u32 *pn_perf_lvl) +{ + unsigned long freq; + + if (!pn_perf_lvl) { + VCDRES_MSG_ERROR("%s(): pn_perf_lvl is NULL\n", + __func__); + return false; + } + VCDRES_MSG_LOW("clk_regime_msm_get_clk_freq_hz"); + if (!res_trk_get_clk_rate(&freq)) { + VCDRES_MSG_ERROR("%s(): res_trk_get_clk_rate FAILED\n", + __func__); + *pn_perf_lvl = 0; + return false; + } + + *pn_perf_lvl = res_trk_convert_freq_to_perf_lvl((u64) freq); + VCDRES_MSG_MED("%s(): freq = %lu, *pn_perf_lvl = %u", __func__, + freq, *pn_perf_lvl); + return true; +} + +u32 res_trk_download_firmware(void) +{ + const struct firmware *fw_boot = NULL; + const struct firmware *fw_mpg4_dec = NULL; + const struct firmware *fw_h263_dec = NULL; + const struct firmware *fw_h264_dec = NULL; + const struct firmware *fw_mpg4_enc = NULL; + const struct firmware *fw_h264_enc = NULL; + const struct firmware *fw_vc1_dec = NULL; + int rc = 0; + u32 status = true; + + VCDRES_MSG_HIGH("%s(): Request firmware download\n", + __func__); + mutex_lock(&resource_context.lock); + rc = request_firmware(&fw_boot, VIDC_BOOT_FW, + resource_context.device); + if (rc) { + VCDRES_MSG_ERROR("request_firmware for %s error %d\n", + VIDC_BOOT_FW, rc); + mutex_unlock(&resource_context.lock); + return false; + } + vidc_command_control_fw = (unsigned char *)fw_boot->data; + vidc_command_control_fw_size = (u32) fw_boot->size; + + rc = request_firmware(&fw_mpg4_dec, VIDC_MPG4_DEC_FW, + resource_context.device); + if (rc) { + VCDRES_MSG_ERROR("request_firmware for %s error %d\n", + VIDC_MPG4_DEC_FW, rc); + status = false; + goto boot_fw_free; + } + vidc_mpg4_dec_fw = (unsigned char *)fw_mpg4_dec->data; + vidc_mpg4_dec_fw_size = (u32) fw_mpg4_dec->size; + + + rc = request_firmware(&fw_h263_dec, VIDC_H263_DEC_FW, + resource_context.device); + if (rc) { + VCDRES_MSG_ERROR("request_firmware for %s error %d\n", + VIDC_H263_DEC_FW, rc); + status = false; + goto mp4dec_fw_free; + } + vidc_h263_dec_fw = (unsigned char *)fw_h263_dec->data; + vidc_h263_dec_fw_size = (u32) fw_h263_dec->size; + + rc = request_firmware(&fw_h264_dec, VIDC_H264_DEC_FW, + resource_context.device); + if (rc) { + VCDRES_MSG_ERROR("request_firmware for %s error %d\n", + VIDC_H264_DEC_FW, rc); + status = false; + goto h263dec_fw_free; + } + vidc_h264_dec_fw = (unsigned char *)fw_h264_dec->data; + vidc_h264_dec_fw_size = (u32) fw_h264_dec->size; + + rc = request_firmware(&fw_mpg4_enc, VIDC_MPG4_ENC_FW, + resource_context.device); + if (rc) { + VCDRES_MSG_ERROR("request_firmware for %s error %d\n", + VIDC_MPG4_ENC_FW, rc); + status = false; + goto h264dec_fw_free; + } + vidc_mpg4_enc_fw = (unsigned char *)fw_mpg4_enc->data; + vidc_mpg4_enc_fw_size = (u32) fw_mpg4_enc->size; + + rc = request_firmware(&fw_h264_enc, VIDC_H264_ENC_FW, + resource_context.device); + if (rc) { + VCDRES_MSG_ERROR("request_firmware for %s error %d\n", + VIDC_H264_ENC_FW, rc); + status = false; + goto mp4enc_fw_free; + } + vidc_h264_enc_fw = (unsigned char *)fw_h264_enc->data; + vidc_h264_enc_fw_size = (u32) fw_h264_enc->size; + + rc = request_firmware(&fw_vc1_dec, VIDC_VC1_DEC_FW, + resource_context.device); + if (rc) { + VCDRES_MSG_ERROR("request_firmware for %s error %d\n", + VIDC_VC1_DEC_FW, rc); + status = false; + goto h264enc_fw_free; + } + vidc_vc1_dec_fw = (unsigned char *)fw_vc1_dec->data; + vidc_vc1_dec_fw_size = (u32) fw_vc1_dec->size; + mutex_unlock(&resource_context.lock); + return status; + +h264enc_fw_free: + release_firmware(fw_h264_enc); +mp4enc_fw_free: + release_firmware(fw_mpg4_enc); +h264dec_fw_free: + release_firmware(fw_h264_dec); +h263dec_fw_free: + release_firmware(fw_h263_dec); +mp4dec_fw_free: + release_firmware(fw_mpg4_dec); +boot_fw_free: + release_firmware(fw_boot); + mutex_unlock(&resource_context.lock); + return false; +} + +void res_trk_init(struct device *device, u32 irq) +{ + if (resource_context.device || resource_context.irq_num || + !device) { + VCDRES_MSG_ERROR("%s() Resource Tracker Init error\n", + __func__); + return; + } + memset(&resource_context, 0, sizeof(resource_context)); + mutex_init(&resource_context.lock); + resource_context.device = device; + resource_context.irq_num = irq; + resource_context.core_type = VCD_CORE_720P; + resource_context.regulator = regulator_get(NULL, "fs_mfc"); + resource_context.vidc_platform_data = + (struct msm_vidc_platform_data *) device->platform_data; + if (resource_context.vidc_platform_data) { + resource_context.memtype = + resource_context.vidc_platform_data->memtype; + } else { + resource_context.memtype = -1; + } +} + +u32 res_trk_get_core_type(void){ + return resource_context.core_type; +} + +u32 res_trk_get_mem_type(void){ + return resource_context.memtype; +} diff --git a/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.h b/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.h new file mode 100644 index 00000000000..301285876c9 --- /dev/null +++ b/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker.h @@ -0,0 +1,56 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VIDEO_720P_RESOURCE_TRACKER_H_ +#define _VIDEO_720P_RESOURCE_TRACKER_H_ +#include +#include "vcd_res_tracker_api.h" + +#define VCD_RESTRK_MIN_PERF_LEVEL 37900 +#define VCD_RESTRK_MAX_PERF_LEVEL 108000 +#define VCD_RESTRK_MIN_FREQ_POINT 61440000 +#define VCD_RESTRK_MAX_FREQ_POINT 170667000 +#define VCD_RESTRK_HZ_PER_1000_PERFLVL 1580250 + +struct res_trk_context { + struct device *device; + u32 irq_num; + struct mutex lock; + struct clk *hclk; + struct clk *hclk_div2; + struct clk *pclk; + unsigned long hclk_rate; + unsigned int clock_enabled; + unsigned int rail_enabled; + struct regulator *regulator; + struct msm_vidc_platform_data *vidc_platform_data; + u32 core_type; + int memtype; +}; + +#if DEBUG + +#define VCDRES_MSG_LOW(xx_fmt...) printk(KERN_INFO "\n\t* " xx_fmt) +#define VCDRES_MSG_MED(xx_fmt...) printk(KERN_INFO "\n * " xx_fmt) + +#else + +#define VCDRES_MSG_LOW(xx_fmt...) +#define VCDRES_MSG_MED(xx_fmt...) + +#endif + +#define VCDRES_MSG_HIGH(xx_fmt...) printk(KERN_WARNING "\n" xx_fmt) +#define VCDRES_MSG_ERROR(xx_fmt...) printk(KERN_ERR "\n err: " xx_fmt) +#define VCDRES_MSG_FATAL(xx_fmt...) printk(KERN_ERR "\n " xx_fmt) + +#endif diff --git a/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker_api.h b/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker_api.h new file mode 100644 index 00000000000..db847439ddb --- /dev/null +++ b/drivers/video/msm/vidc/720p/resource_tracker/vcd_res_tracker_api.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VIDEO_720P_RESOURCE_TRACKER_API_H_ +#define _VIDEO_720P_RESOURCE_TRACKER_API_H_ + +#include "vcd_core.h" + +void res_trk_init(struct device *device, u32 irq); +u32 res_trk_power_up(void); +u32 res_trk_power_down(void); +u32 res_trk_enable_clocks(void); +u32 res_trk_disable_clocks(void); +u32 res_trk_get_max_perf_level(u32 *pn_max_perf_lvl); +u32 res_trk_set_perf_level(u32 req_perf_lvl, u32 *pn_set_perf_lvl, + struct vcd_dev_ctxt *dev_ctxt); +u32 res_trk_get_curr_perf_level(u32 *pn_perf_lvl); +u32 res_trk_download_firmware(void); +u32 res_trk_get_core_type(void); +u32 res_trk_get_mem_type(void); +#endif diff --git a/drivers/video/msm/vidc/Kconfig b/drivers/video/msm/vidc/Kconfig new file mode 100644 index 00000000000..9ffcb154af1 --- /dev/null +++ b/drivers/video/msm/vidc/Kconfig @@ -0,0 +1,39 @@ +# +# VIDEO CORE +# +menuconfig MSM_VIDC + bool "Video Core Driver" + depends on ARCH_MSM8X60 || ARCH_MSM7X30 || ARCH_MSM8960 + default y + ---help--- + Say Y here to see options for video device drivers. + If you say N, all options in this submenu will be skipped and disabled. + +config MSM_VIDC_720P + bool "720P Video Core" + depends on MSM_VIDC && ARCH_MSM7X30 + default y + help + This option enables support for Video core. + +config MSM_VIDC_1080P + bool "1080P Video Core" + depends on MSM_VIDC && (ARCH_MSM8X60 || ARCH_MSM8960) + default y + help + This option enables support for Video core. + +config MSM_VIDC_VENC + tristate "Video encoder" + depends on MSM_VIDC + default y + help + This option enables support for Video encoder. + +config MSM_VIDC_VDEC + tristate "Video decoder" + depends on MSM_VIDC + default y + help + This option enables support for Video decoder. + diff --git a/drivers/video/msm/vidc/Makefile b/drivers/video/msm/vidc/Makefile new file mode 100644 index 00000000000..af41f181878 --- /dev/null +++ b/drivers/video/msm/vidc/Makefile @@ -0,0 +1,62 @@ +ifdef CONFIG_MSM_VIDC_720P +EXTRA_CFLAGS += -Idrivers/video/msm/vidc/720p/ddl +EXTRA_CFLAGS += -Idrivers/video/msm/vidc/720p/resource_tracker +endif + +ifdef CONFIG_MSM_VIDC_1080P +EXTRA_CFLAGS += -Idrivers/video/msm/vidc/1080p/ddl +EXTRA_CFLAGS += -Idrivers/video/msm/vidc/1080p/resource_tracker +endif + +EXTRA_CFLAGS += -Idrivers/video/msm/vidc/common/dec +EXTRA_CFLAGS += -Idrivers/video/msm/vidc/common/enc +EXTRA_CFLAGS += -Idrivers/video/msm/vidc/common/vcd +EXTRA_CFLAGS += -Idrivers/video/msm/vidc/common/init + +obj-$(CONFIG_MSM_VIDC) += vidc.o + +vidc-objs := common/init/vidc_init.o \ + common/vcd/vcd_api.o \ + common/vcd/vcd_power_sm.o \ + common/vcd/vcd_client_sm.o \ + common/vcd/vcd_device_sm.o \ + common/vcd/vcd_scheduler.o \ + common/vcd/vcd_sub.o \ + +ifdef CONFIG_MSM_VIDC_720P +vidc-objs += 720p/ddl/vcd_ddl_firmware.o \ + 720p/ddl/vcd_ddl_metadata.o \ + 720p/ddl/vidc.o \ + 720p/ddl/vcd_ddl_utils.o \ + 720p/ddl/vcd_ddl.o \ + 720p/ddl/vcd_ddl_helper.o \ + 720p/ddl/vcd_ddl_interrupt_handler.o \ + 720p/ddl/vcd_ddl_hal.o \ + 720p/ddl/vcd_ddl_properties.o \ + 720p/resource_tracker/vcd_res_tracker.o \ + 720p/ddl/vcd_ddl_errors.o +endif + +ifdef CONFIG_MSM_VIDC_1080P +vidc-objs += 1080p/ddl/vcd_ddl_helper.o \ + 1080p/ddl/vcd_ddl_utils.o \ + 1080p/ddl/vcd_ddl_interrupt_handler.o \ + 1080p/ddl/vcd_ddl_properties.o \ + 1080p/ddl/vcd_ddl_errors.o \ + 1080p/ddl/vcd_ddl_shared_mem.o \ + 1080p/ddl/vidc.o \ + 1080p/ddl/vidc_pix_cache.o \ + 1080p/ddl/vcd_ddl_vidc.o \ + 1080p/ddl/vcd_ddl.o \ + 1080p/ddl/vcd_ddl_metadata.o \ + 1080p/resource_tracker/vcd_res_tracker.o +endif + +obj-$(CONFIG_MSM_VIDC_VDEC) += vidc_vdec.o + +vidc_vdec-objs := common/dec/vdec.o + +obj-$(CONFIG_MSM_VIDC_VENC) += vidc_venc.o + +vidc_venc-objs := common/enc/venc.o \ + common/enc/venc_internal.o diff --git a/drivers/video/msm/vidc/common/dec/vdec.c b/drivers/video/msm/vidc/common/dec/vdec.c new file mode 100644 index 00000000000..48d51199485 --- /dev/null +++ b/drivers/video/msm/vidc/common/dec/vdec.c @@ -0,0 +1,1904 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vidc_type.h" +#include "vcd_api.h" +#include "vdec_internal.h" +#include "vidc_init.h" + + + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +#define INFO(x...) printk(KERN_INFO x) +#define ERR(x...) printk(KERN_ERR x) + +#define VID_DEC_NAME "msm_vidc_dec" + +static struct vid_dec_dev *vid_dec_device_p; +static dev_t vid_dec_dev_num; +static struct class *vid_dec_class; +static s32 vid_dec_get_empty_client_index(void) +{ + u32 i, found = false; + + for (i = 0; i < VIDC_MAX_NUM_CLIENTS; i++) { + if (!vid_dec_device_p->vdec_clients[i].vcd_handle) { + found = true; + break; + } + } + if (!found) { + ERR("%s():ERROR No space for new client\n", __func__); + return -ENOMEM; + } else { + DBG("%s(): available client index = %u\n", __func__, i); + return i; + } +} + +u32 vid_dec_get_status(u32 status) +{ + u32 vdec_status; + + switch (status) { + case VCD_ERR_SEQHDR_PARSE_FAIL: + case VCD_ERR_BITSTREAM_ERR: + case VCD_S_SUCCESS: + vdec_status = VDEC_S_SUCCESS; + break; + case VCD_ERR_FAIL: + vdec_status = VDEC_S_EFAIL; + break; + case VCD_ERR_ALLOC_FAIL: + vdec_status = VDEC_S_ENOSWRES; + break; + case VCD_ERR_ILLEGAL_OP: + vdec_status = VDEC_S_EINVALCMD; + break; + case VCD_ERR_ILLEGAL_PARM: + vdec_status = VDEC_S_EBADPARAM; + break; + case VCD_ERR_BAD_POINTER: + case VCD_ERR_BAD_HANDLE: + vdec_status = VDEC_S_EFATAL; + break; + case VCD_ERR_NOT_SUPPORTED: + vdec_status = VDEC_S_ENOTSUPP; + break; + case VCD_ERR_BAD_STATE: + vdec_status = VDEC_S_EINVALSTATE; + break; + case VCD_ERR_BUSY: + vdec_status = VDEC_S_BUSY; + break; + case VCD_ERR_MAX_CLIENT: + vdec_status = VDEC_S_ENOHWRES; + break; + default: + vdec_status = VDEC_S_EFAIL; + break; + } + + return vdec_status; +} + +static void vid_dec_notify_client(struct video_client_ctx *client_ctx) +{ + if (client_ctx) + complete(&client_ctx->event); +} + +void vid_dec_vcd_open_done(struct video_client_ctx *client_ctx, + struct vcd_handle_container *handle_container) +{ + DBG("vid_dec_vcd_open_done\n"); + + if (client_ctx) { + if (handle_container) + client_ctx->vcd_handle = handle_container->handle; + else + ERR("%s(): ERROR. handle_container is NULL\n", + __func__); + + vid_dec_notify_client(client_ctx); + } else + ERR("%s(): ERROR. client_ctx is NULL\n", __func__); +} + +static void vid_dec_input_frame_done(struct video_client_ctx *client_ctx, + u32 event, u32 status, + struct vcd_frame_data *vcd_frame_data) +{ + struct vid_dec_msg *vdec_msg; + + if (!client_ctx || !vcd_frame_data) { + ERR("vid_dec_input_frame_done() NULL pointer\n"); + return; + } + + vdec_msg = kzalloc(sizeof(struct vid_dec_msg), GFP_KERNEL); + if (!vdec_msg) { + ERR("vid_dec_input_frame_done(): cannot allocate vid_dec_msg " + " buffer\n"); + return; + } + + vdec_msg->vdec_msg_info.status_code = vid_dec_get_status(status); + + if (event == VCD_EVT_RESP_INPUT_DONE) { + vdec_msg->vdec_msg_info.msgcode = + VDEC_MSG_RESP_INPUT_BUFFER_DONE; + DBG("Send INPUT_DON message to client = %p\n", client_ctx); + + } else if (event == VCD_EVT_RESP_INPUT_FLUSHED) { + vdec_msg->vdec_msg_info.msgcode = VDEC_MSG_RESP_INPUT_FLUSHED; + DBG("Send INPUT_FLUSHED message to client = %p\n", client_ctx); + } else { + ERR("vid_dec_input_frame_done(): invalid event type: " + "%d\n", event); + vdec_msg->vdec_msg_info.msgcode = VDEC_MSG_INVALID; + } + + vdec_msg->vdec_msg_info.msgdata.input_frame_clientdata = + (void *)vcd_frame_data->frm_clnt_data; + vdec_msg->vdec_msg_info.msgdatasize = sizeof(void *); + + mutex_lock(&client_ctx->msg_queue_lock); + list_add_tail(&vdec_msg->list, &client_ctx->msg_queue); + mutex_unlock(&client_ctx->msg_queue_lock); + wake_up(&client_ctx->msg_wait); +} + +static void vid_dec_output_frame_done(struct video_client_ctx *client_ctx, + u32 event, u32 status, + struct vcd_frame_data *vcd_frame_data) +{ + struct vid_dec_msg *vdec_msg; + + unsigned long kernel_vaddr = 0, phy_addr = 0, user_vaddr = 0; + int pmem_fd; + struct file *file; + s32 buffer_index = -1; + enum vdec_picture pic_type; + + if (!client_ctx || !vcd_frame_data) { + ERR("vid_dec_input_frame_done() NULL pointer\n"); + return; + } + + vdec_msg = kzalloc(sizeof(struct vid_dec_msg), GFP_KERNEL); + if (!vdec_msg) { + ERR("vid_dec_input_frame_done(): cannot allocate vid_dec_msg " + " buffer\n"); + return; + } + + vdec_msg->vdec_msg_info.status_code = vid_dec_get_status(status); + + if (event == VCD_EVT_RESP_OUTPUT_DONE) + vdec_msg->vdec_msg_info.msgcode = + VDEC_MSG_RESP_OUTPUT_BUFFER_DONE; + else if (event == VCD_EVT_RESP_OUTPUT_FLUSHED) + vdec_msg->vdec_msg_info.msgcode = VDEC_MSG_RESP_OUTPUT_FLUSHED; + else { + ERR("QVD: vid_dec_output_frame_done invalid cmd type: " + "%d\n", event); + vdec_msg->vdec_msg_info.msgcode = VDEC_MSG_INVALID; + } + + kernel_vaddr = (unsigned long)vcd_frame_data->virtual; + + if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_OUTPUT, + false, &user_vaddr, &kernel_vaddr, + &phy_addr, &pmem_fd, &file, + &buffer_index) || + (vcd_frame_data->flags & VCD_FRAME_FLAG_EOS)) { + + /* Buffer address in user space */ + vdec_msg->vdec_msg_info.msgdata.output_frame.bufferaddr = + (u8 *) user_vaddr; + /* Data length */ + vdec_msg->vdec_msg_info.msgdata.output_frame.len = + vcd_frame_data->data_len; + vdec_msg->vdec_msg_info.msgdata.output_frame.flags = + vcd_frame_data->flags; + /* Timestamp pass-through from input frame */ + vdec_msg->vdec_msg_info.msgdata.output_frame.time_stamp = + vcd_frame_data->time_stamp; + /* Output frame client data */ + vdec_msg->vdec_msg_info.msgdata.output_frame.client_data = + (void *)vcd_frame_data->frm_clnt_data; + /* Associated input frame client data */ + vdec_msg->vdec_msg_info.msgdata.output_frame. + input_frame_clientdata = + (void *)vcd_frame_data->ip_frm_tag; + /* Decoded picture width and height */ + vdec_msg->vdec_msg_info.msgdata.output_frame.framesize. + bottom = + vcd_frame_data->dec_op_prop.disp_frm.bottom; + vdec_msg->vdec_msg_info.msgdata.output_frame.framesize.left = + vcd_frame_data->dec_op_prop.disp_frm.left; + vdec_msg->vdec_msg_info.msgdata.output_frame.framesize.right = + vcd_frame_data->dec_op_prop.disp_frm.right; + vdec_msg->vdec_msg_info.msgdata.output_frame.framesize.top = + vcd_frame_data->dec_op_prop.disp_frm.top; + if (vcd_frame_data->interlaced) { + vdec_msg->vdec_msg_info.msgdata. + output_frame.interlaced_format = + VDEC_InterlaceInterleaveFrameTopFieldFirst; + } else { + vdec_msg->vdec_msg_info.msgdata. + output_frame.interlaced_format = + VDEC_InterlaceFrameProgressive; + } + /* Decoded picture type */ + switch (vcd_frame_data->frame) { + case VCD_FRAME_I: + pic_type = PICTURE_TYPE_I; + break; + case VCD_FRAME_P: + pic_type = PICTURE_TYPE_P; + break; + case VCD_FRAME_B: + pic_type = PICTURE_TYPE_B; + break; + case VCD_FRAME_NOTCODED: + pic_type = PICTURE_TYPE_SKIP; + break; + default: + pic_type = PICTURE_TYPE_UNKNOWN; + } + vdec_msg->vdec_msg_info.msgdata.output_frame.pic_type = + pic_type; + vdec_msg->vdec_msg_info.msgdatasize = + sizeof(struct vdec_output_frameinfo); + } else { + ERR("vid_dec_output_frame_done UVA can not be found\n"); + vdec_msg->vdec_msg_info.status_code = VDEC_S_EFATAL; + } + + mutex_lock(&client_ctx->msg_queue_lock); + list_add_tail(&vdec_msg->list, &client_ctx->msg_queue); + mutex_unlock(&client_ctx->msg_queue_lock); + wake_up(&client_ctx->msg_wait); +} + +static void vid_dec_lean_event(struct video_client_ctx *client_ctx, + u32 event, u32 status) +{ + struct vid_dec_msg *vdec_msg; + + if (!client_ctx) { + ERR("%s(): !client_ctx pointer\n", __func__); + return; + } + + vdec_msg = kzalloc(sizeof(struct vid_dec_msg), GFP_KERNEL); + if (!vdec_msg) { + ERR("%s(): cannot allocate vid_dec_msg buffer\n", __func__); + return; + } + + vdec_msg->vdec_msg_info.status_code = vid_dec_get_status(status); + + switch (event) { + case VCD_EVT_IND_OUTPUT_RECONFIG: + INFO("msm_vidc_dec: Sending VDEC_MSG_EVT_CONFIG_CHANGED" + " to client"); + vdec_msg->vdec_msg_info.msgcode = VDEC_MSG_EVT_CONFIG_CHANGED; + break; + case VCD_EVT_IND_RESOURCES_LOST: + INFO("msm_vidc_dec: Sending VDEC_EVT_RESOURCES_LOST" + " to client"); + vdec_msg->vdec_msg_info.msgcode = VDEC_EVT_RESOURCES_LOST; + break; + case VCD_EVT_RESP_FLUSH_INPUT_DONE: + INFO("msm_vidc_dec: Sending VDEC_MSG_RESP_FLUSH_INPUT_DONE" + " to client"); + vdec_msg->vdec_msg_info.msgcode = + VDEC_MSG_RESP_FLUSH_INPUT_DONE; + break; + case VCD_EVT_RESP_FLUSH_OUTPUT_DONE: + INFO("msm_vidc_dec: Sending VDEC_MSG_RESP_FLUSH_OUTPUT_DONE" + " to client"); + vdec_msg->vdec_msg_info.msgcode = + VDEC_MSG_RESP_FLUSH_OUTPUT_DONE; + break; + case VCD_EVT_IND_HWERRFATAL: + INFO("msm_vidc_dec: Sending VDEC_MSG_EVT_HW_ERROR" + " to client"); + vdec_msg->vdec_msg_info.msgcode = VDEC_MSG_EVT_HW_ERROR; + break; + case VCD_EVT_RESP_START: + INFO("msm_vidc_dec: Sending VDEC_MSG_RESP_START_DONE" + " to client"); + vdec_msg->vdec_msg_info.msgcode = VDEC_MSG_RESP_START_DONE; + break; + case VCD_EVT_RESP_STOP: + INFO("msm_vidc_dec: Sending VDEC_MSG_RESP_STOP_DONE" + " to client"); + vdec_msg->vdec_msg_info.msgcode = VDEC_MSG_RESP_STOP_DONE; + break; + case VCD_EVT_RESP_PAUSE: + INFO("msm_vidc_dec: Sending VDEC_MSG_RESP_PAUSE_DONE" + " to client"); + vdec_msg->vdec_msg_info.msgcode = VDEC_MSG_RESP_PAUSE_DONE; + break; + case VCD_EVT_IND_INFO_OUTPUT_RECONFIG: + INFO("msm_vidc_dec: Sending VDEC_MSG_EVT_INFO_CONFIG_CHANGED" + " to client"); + vdec_msg->vdec_msg_info.msgcode = + VDEC_MSG_EVT_INFO_CONFIG_CHANGED; + break; + default: + ERR("%s() : unknown event type\n", __func__); + break; + } + + vdec_msg->vdec_msg_info.msgdatasize = 0; + if (client_ctx->stop_sync_cb && + (event == VCD_EVT_RESP_STOP || event == VCD_EVT_IND_HWERRFATAL)) { + client_ctx->stop_sync_cb = false; + complete(&client_ctx->event); + kfree(vdec_msg); + return; + } + mutex_lock(&client_ctx->msg_queue_lock); + list_add_tail(&vdec_msg->list, &client_ctx->msg_queue); + mutex_unlock(&client_ctx->msg_queue_lock); + wake_up(&client_ctx->msg_wait); +} + + +void vid_dec_vcd_cb(u32 event, u32 status, + void *info, size_t sz, void *handle, void *const client_data) +{ + struct video_client_ctx *client_ctx = + (struct video_client_ctx *)client_data; + + DBG("Entering %s()\n", __func__); + + if (!client_ctx) { + ERR("%s(): client_ctx is NULL\n", __func__); + return; + } + + client_ctx->event_status = status; + + switch (event) { + case VCD_EVT_RESP_OPEN: + vid_dec_vcd_open_done(client_ctx, + (struct vcd_handle_container *) + info); + break; + case VCD_EVT_RESP_INPUT_DONE: + case VCD_EVT_RESP_INPUT_FLUSHED: + vid_dec_input_frame_done(client_ctx, event, status, + (struct vcd_frame_data *)info); + break; + case VCD_EVT_RESP_OUTPUT_DONE: + case VCD_EVT_RESP_OUTPUT_FLUSHED: + vid_dec_output_frame_done(client_ctx, event, status, + (struct vcd_frame_data *)info); + break; + case VCD_EVT_RESP_PAUSE: + case VCD_EVT_RESP_STOP: + case VCD_EVT_RESP_FLUSH_INPUT_DONE: + case VCD_EVT_RESP_FLUSH_OUTPUT_DONE: + case VCD_EVT_IND_OUTPUT_RECONFIG: + case VCD_EVT_IND_HWERRFATAL: + case VCD_EVT_IND_RESOURCES_LOST: + case VCD_EVT_IND_INFO_OUTPUT_RECONFIG: + vid_dec_lean_event(client_ctx, event, status); + break; + case VCD_EVT_RESP_START: + if (!client_ctx->seq_header_set) + vid_dec_lean_event(client_ctx, event, status); + else + vid_dec_notify_client(client_ctx); + break; + default: + ERR("%s() : Error - Invalid event type =%u\n", __func__, + event); + break; + } +} + +static u32 vid_dec_set_codec(struct video_client_ctx *client_ctx, + enum vdec_codec *vdec_codec) +{ + u32 result = true; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_codec codec; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !vdec_codec) + return false; + + vcd_property_hdr.prop_id = VCD_I_CODEC; + vcd_property_hdr.sz = sizeof(struct vcd_property_codec); + + switch (*vdec_codec) { + case VDEC_CODECTYPE_MPEG4: + codec.codec = VCD_CODEC_MPEG4; + break; + case VDEC_CODECTYPE_H264: + codec.codec = VCD_CODEC_H264; + break; + case VDEC_CODECTYPE_DIVX_3: + codec.codec = VCD_CODEC_DIVX_3; + break; + case VDEC_CODECTYPE_DIVX_5: + codec.codec = VCD_CODEC_DIVX_5; + break; + case VDEC_CODECTYPE_XVID: + codec.codec = VCD_CODEC_XVID; + break; + case VDEC_CODECTYPE_H263: + codec.codec = VCD_CODEC_H263; + break; + case VDEC_CODECTYPE_MPEG2: + codec.codec = VCD_CODEC_MPEG2; + break; + case VDEC_CODECTYPE_VC1: + codec.codec = VCD_CODEC_VC1; + break; + case VDEC_CODECTYPE_VC1_RCV: + codec.codec = VCD_CODEC_VC1_RCV; + break; + default: + result = false; + break; + } + + if (result) { + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &codec); + if (vcd_status) + result = false; + } + return result; +} + +static u32 vid_dec_set_output_format(struct video_client_ctx *client_ctx, + enum vdec_output_fromat *output_format) +{ + u32 result = true; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_buffer_format vcd_prop_buffer_format; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !output_format) + return false; + + vcd_property_hdr.prop_id = VCD_I_BUFFER_FORMAT;; + vcd_property_hdr.sz = + sizeof(struct vcd_property_buffer_format); + + switch (*output_format) { + case VDEC_YUV_FORMAT_NV12: + vcd_prop_buffer_format.buffer_format = VCD_BUFFER_FORMAT_NV12; + break; + case VDEC_YUV_FORMAT_TILE_4x2: + vcd_prop_buffer_format.buffer_format = + VCD_BUFFER_FORMAT_TILE_4x2; + break; + default: + result = false; + break; + } + + if (result) + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, + &vcd_prop_buffer_format); + + if (vcd_status) + return false; + else + return true; +} + +static u32 vid_dec_set_frame_resolution(struct video_client_ctx *client_ctx, + struct vdec_picsize *video_resoultion) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_frame_size frame_resolution; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !video_resoultion) + return false; + + vcd_property_hdr.prop_id = VCD_I_FRAME_SIZE; + vcd_property_hdr.sz = sizeof(struct vcd_property_frame_size); + frame_resolution.width = video_resoultion->frame_width; + frame_resolution.height = video_resoultion->frame_height; + frame_resolution.stride = video_resoultion->stride; + frame_resolution.scan_lines = video_resoultion->scan_lines; + + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &frame_resolution); + + if (vcd_status) + return false; + else + return true; +} + +static u32 vid_dec_get_frame_resolution(struct video_client_ctx *client_ctx, + struct vdec_picsize *video_resoultion) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_frame_size frame_resolution; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !video_resoultion) + return false; + + vcd_property_hdr.prop_id = VCD_I_FRAME_SIZE; + vcd_property_hdr.sz = sizeof(struct vcd_property_frame_size); + + vcd_status = vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + &frame_resolution); + + video_resoultion->frame_width = frame_resolution.width; + video_resoultion->frame_height = frame_resolution.height; + video_resoultion->scan_lines = frame_resolution.scan_lines; + video_resoultion->stride = frame_resolution.stride; + + if (vcd_status) + return false; + else + return true; +} + +static u32 vid_dec_get_progressive_only(struct video_client_ctx *client_ctx, + u32 *progressive_only) +{ + struct vcd_property_hdr vcd_property_hdr; + if (!client_ctx || !progressive_only) + return false; + vcd_property_hdr.prop_id = VCD_I_PROGRESSIVE_ONLY; + vcd_property_hdr.sz = sizeof(u32); + if (vcd_get_property(client_ctx->vcd_handle, &vcd_property_hdr, + progressive_only)) + return false; + else + return true; +} + +static u32 vid_dec_set_picture_order(struct video_client_ctx *client_ctx, + u32 *picture_order) +{ + struct vcd_property_hdr vcd_property_hdr; + u32 vcd_status = VCD_ERR_FAIL, vcd_picture_order, ret = true; + if (!client_ctx || !picture_order) + return false; + vcd_property_hdr.prop_id = VCD_I_OUTPUT_ORDER; + vcd_property_hdr.sz = sizeof(u32); + if (*picture_order == VDEC_ORDER_DISPLAY) + vcd_picture_order = VCD_DEC_ORDER_DISPLAY; + else if (*picture_order == VDEC_ORDER_DECODE) + vcd_picture_order = VCD_DEC_ORDER_DECODE; + else + ret = false; + if (ret) { + DBG("%s() : Setting output picture order: %d\n", + __func__, vcd_picture_order); + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_picture_order); + if (vcd_status != VCD_S_SUCCESS) + ret = false; + } + return ret; +} + +static u32 vid_dec_set_frame_rate(struct video_client_ctx *client_ctx, + struct vdec_framerate *frame_rate) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_frame_rate vcd_frame_rate; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !frame_rate) + return false; + + vcd_property_hdr.prop_id = VCD_I_FRAME_RATE; + vcd_property_hdr.sz = sizeof(struct vcd_property_frame_rate); + vcd_frame_rate.fps_numerator = frame_rate->fps_numerator; + vcd_frame_rate.fps_denominator = frame_rate->fps_denominator; + + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_frame_rate); + + if (vcd_status) + return false; + else + return true; +} + +static u32 vid_dec_set_extradata(struct video_client_ctx *client_ctx, + u32 *extradata_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_meta_data_enable vcd_meta_data; + u32 vcd_status = VCD_ERR_FAIL; + if (!client_ctx || !extradata_flag) + return false; + vcd_property_hdr.prop_id = VCD_I_METADATA_ENABLE; + vcd_property_hdr.sz = sizeof(struct vcd_property_meta_data_enable); + vcd_meta_data.meta_data_enable_flag = *extradata_flag; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_meta_data); + if (vcd_status) + return false; + else + return true; +} + +static u32 vid_dec_set_idr_only_decoding(struct video_client_ctx *client_ctx) +{ + struct vcd_property_hdr vcd_property_hdr; + u32 vcd_status = VCD_ERR_FAIL; + u32 enable = true; + if (!client_ctx) + return false; + vcd_property_hdr.prop_id = VCD_I_DEC_PICTYPE; + vcd_property_hdr.sz = sizeof(u32); + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &enable); + if (vcd_status) + return false; + return true; +} + +static u32 vid_dec_set_h264_mv_buffers(struct video_client_ctx *client_ctx, + struct vdec_h264_mv *mv_data) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_h264_mv_buffer vcd_h264_mv_buffer; + u32 vcd_status = VCD_ERR_FAIL; + u32 len; + struct file *file; + + if (!client_ctx || !mv_data) + return false; + + vcd_property_hdr.prop_id = VCD_I_H264_MV_BUFFER; + vcd_property_hdr.sz = sizeof(struct vcd_property_h264_mv_buffer); + + memset(&vcd_h264_mv_buffer, 0, + sizeof(struct vcd_property_h264_mv_buffer)); + vcd_h264_mv_buffer.size = mv_data->size; + vcd_h264_mv_buffer.count = mv_data->count; + vcd_h264_mv_buffer.pmem_fd = mv_data->pmem_fd; + vcd_h264_mv_buffer.offset = mv_data->offset; + + if (get_pmem_file(vcd_h264_mv_buffer.pmem_fd, + (unsigned long *) (&(vcd_h264_mv_buffer.physical_addr)), + (unsigned long *) (&vcd_h264_mv_buffer.kernel_virtual_addr), + (unsigned long *) (&len), &file)) { + ERR("%s(): get_pmem_file failed\n", __func__); + return false; + } + put_pmem_file(file); + + DBG("Virt: %p, Phys %p, fd: %d\n", vcd_h264_mv_buffer. + kernel_virtual_addr, vcd_h264_mv_buffer.physical_addr, + vcd_h264_mv_buffer.pmem_fd); + + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_h264_mv_buffer); + + if (vcd_status) + return false; + else + return true; +} + +static u32 vid_dec_set_cont_on_reconfig(struct video_client_ctx *client_ctx) +{ + struct vcd_property_hdr vcd_property_hdr; + u32 vcd_status = VCD_ERR_FAIL; + u32 enable = true; + if (!client_ctx) + return false; + vcd_property_hdr.prop_id = VCD_I_CONT_ON_RECONFIG; + vcd_property_hdr.sz = sizeof(u32); + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &enable); + if (vcd_status) + return false; + return true; +} + +static u32 vid_dec_get_h264_mv_buffer_size(struct video_client_ctx *client_ctx, + struct vdec_mv_buff_size *mv_buff) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_buffer_size h264_mv_buffer_size; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !mv_buff) + return false; + + vcd_property_hdr.prop_id = VCD_I_GET_H264_MV_SIZE; + vcd_property_hdr.sz = sizeof(struct vcd_property_buffer_size); + + h264_mv_buffer_size.width = mv_buff->width; + h264_mv_buffer_size.height = mv_buff->height; + + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &h264_mv_buffer_size); + + mv_buff->width = h264_mv_buffer_size.width; + mv_buff->height = h264_mv_buffer_size.height; + mv_buff->size = h264_mv_buffer_size.size; + mv_buff->alignment = h264_mv_buffer_size.alignment; + + if (vcd_status) + return false; + else + return true; +} + +static u32 vid_dec_free_h264_mv_buffers(struct video_client_ctx *client_ctx) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_buffer_size h264_mv_buffer_size; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx) + return false; + + vcd_property_hdr.prop_id = VCD_I_FREE_H264_MV_BUFFER; + vcd_property_hdr.sz = sizeof(struct vcd_property_buffer_size); + + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &h264_mv_buffer_size); + if (vcd_status) + return false; + else + return true; +} + +static u32 vid_dec_get_buffer_req(struct video_client_ctx *client_ctx, + struct vdec_allocatorproperty *vdec_buf_req) +{ + u32 vcd_status = VCD_ERR_FAIL; + struct vcd_buffer_requirement vcd_buf_req; + + if (!client_ctx || !vdec_buf_req) + return false; + + if (vdec_buf_req->buffer_type == VDEC_BUFFER_TYPE_INPUT) { + vcd_status = vcd_get_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_INPUT, + &vcd_buf_req); + } else { + vcd_status = vcd_get_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_OUTPUT, + &vcd_buf_req); + } + + if (vcd_status) { + return false; + } else { + vdec_buf_req->mincount = vcd_buf_req.min_count; + vdec_buf_req->maxcount = vcd_buf_req.max_count; + vdec_buf_req->actualcount = vcd_buf_req.actual_count; + vdec_buf_req->buffer_size = vcd_buf_req.sz; + vdec_buf_req->alignment = vcd_buf_req.align; + vdec_buf_req->buf_poolid = vcd_buf_req.buf_pool_id; + + return true; + } +} + +static u32 vid_dec_set_buffer(struct video_client_ctx *client_ctx, + struct vdec_setbuffer_cmd *buffer_info) +{ + enum vcd_buffer_type buffer = VCD_BUFFER_INPUT; + enum buffer_dir dir_buffer = BUFFER_TYPE_INPUT; + u32 vcd_status = VCD_ERR_FAIL; + unsigned long kernel_vaddr, buf_adr_offset = 0; + + if (!client_ctx || !buffer_info) + return false; + + if (buffer_info->buffer_type == VDEC_BUFFER_TYPE_OUTPUT) { + dir_buffer = BUFFER_TYPE_OUTPUT; + buffer = VCD_BUFFER_OUTPUT; + buf_adr_offset = (unsigned long)buffer_info->buffer.offset; + } + + /*If buffer cannot be set, ignore */ + if (!vidc_insert_addr_table(client_ctx, dir_buffer, + (unsigned long)buffer_info->buffer.bufferaddr, + &kernel_vaddr, buffer_info->buffer.pmem_fd, + buf_adr_offset, MAX_VIDEO_NUM_OF_BUFF)) { + DBG("%s() : user_virt_addr = %p cannot be set.", + __func__, buffer_info->buffer.bufferaddr); + return false; + } + + vcd_status = vcd_set_buffer(client_ctx->vcd_handle, + buffer, (u8 *) kernel_vaddr, + buffer_info->buffer.buffer_len); + + if (!vcd_status) + return true; + else + return false; +} + + +static u32 vid_dec_free_buffer(struct video_client_ctx *client_ctx, + struct vdec_setbuffer_cmd *buffer_info) +{ + enum vcd_buffer_type buffer = VCD_BUFFER_INPUT; + enum buffer_dir dir_buffer = BUFFER_TYPE_INPUT; + u32 vcd_status = VCD_ERR_FAIL; + unsigned long kernel_vaddr; + + if (!client_ctx || !buffer_info) + return false; + + if (buffer_info->buffer_type == VDEC_BUFFER_TYPE_OUTPUT) { + dir_buffer = BUFFER_TYPE_OUTPUT; + buffer = VCD_BUFFER_OUTPUT; + } + /*If buffer NOT set, ignore */ + if (!vidc_delete_addr_table(client_ctx, dir_buffer, + (unsigned long)buffer_info->buffer.bufferaddr, + &kernel_vaddr)) { + DBG("%s() : user_virt_addr = %p has not been set.", + __func__, buffer_info->buffer.bufferaddr); + return true; + } + + vcd_status = vcd_free_buffer(client_ctx->vcd_handle, buffer, + (u8 *)kernel_vaddr); + + if (!vcd_status) + return true; + else + return false; +} + +static u32 vid_dec_pause_resume(struct video_client_ctx *client_ctx, u32 pause) +{ + u32 vcd_status; + + if (!client_ctx) { + ERR("\n %s(): Invalid client_ctx", __func__); + return false; + } + + if (pause) { + INFO("msm_vidc_dec: PAUSE command from client = %p\n", + client_ctx); + vcd_status = vcd_pause(client_ctx->vcd_handle); + } else{ + INFO("msm_vidc_dec: RESUME command from client = %p\n", + client_ctx); + vcd_status = vcd_resume(client_ctx->vcd_handle); + } + + if (vcd_status) + return false; + + return true; + +} + +static u32 vid_dec_start_stop(struct video_client_ctx *client_ctx, u32 start) +{ + struct vid_dec_msg *vdec_msg = NULL; + u32 vcd_status; + + INFO("msm_vidc_dec: Inside %s()", __func__); + if (!client_ctx) { + ERR("\n Invalid client_ctx"); + return false; + } + + if (start) { + if (client_ctx->seq_header_set) { + INFO("%s(): Seq Hdr set: Send START_DONE to client", + __func__); + vdec_msg = kzalloc(sizeof(*vdec_msg), GFP_KERNEL); + if (!vdec_msg) { + ERR("vid_dec_start_stop: cannot allocate" + "buffer\n"); + return false; + } + vdec_msg->vdec_msg_info.msgcode = + VDEC_MSG_RESP_START_DONE; + vdec_msg->vdec_msg_info.status_code = VDEC_S_SUCCESS; + vdec_msg->vdec_msg_info.msgdatasize = 0; + mutex_lock(&client_ctx->msg_queue_lock); + list_add_tail(&vdec_msg->list, &client_ctx->msg_queue); + mutex_unlock(&client_ctx->msg_queue_lock); + + wake_up(&client_ctx->msg_wait); + + DBG("Send START_DONE message to client = %p\n", + client_ctx); + + } else { + INFO("%s(): Calling decode_start()", __func__); + vcd_status = + vcd_decode_start(client_ctx->vcd_handle, NULL); + + if (vcd_status) { + ERR("%s(): vcd_decode_start failed." + " vcd_status = %u\n", __func__, vcd_status); + return false; + } + } + } else { + INFO("%s(): Calling vcd_stop()", __func__); + mutex_lock(&vid_dec_device_p->lock); + vcd_status = VCD_ERR_FAIL; + if (!client_ctx->stop_called) { + client_ctx->stop_called = true; + vcd_status = vcd_stop(client_ctx->vcd_handle); + } + if (vcd_status) { + ERR("%s(): vcd_stop failed. vcd_status = %u\n", + __func__, vcd_status); + mutex_unlock(&vid_dec_device_p->lock); + return false; + } + DBG("Send STOP_DONE message to client = %p\n", client_ctx); + mutex_unlock(&vid_dec_device_p->lock); + } + return true; +} + +static u32 vid_dec_decode_frame(struct video_client_ctx *client_ctx, + struct vdec_input_frameinfo *input_frame_info) +{ + struct vcd_frame_data vcd_input_buffer; + unsigned long kernel_vaddr, phy_addr, user_vaddr; + int pmem_fd; + struct file *file; + s32 buffer_index = -1; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !input_frame_info) + return false; + + user_vaddr = (unsigned long)input_frame_info->bufferaddr; + + if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_INPUT, + true, &user_vaddr, &kernel_vaddr, + &phy_addr, &pmem_fd, &file, + &buffer_index)) { + + /* kernel_vaddr is found. send the frame to VCD */ + memset((void *)&vcd_input_buffer, 0, + sizeof(struct vcd_frame_data)); + vcd_input_buffer.virtual = + (u8 *) (kernel_vaddr + input_frame_info->pmem_offset); + vcd_input_buffer.offset = input_frame_info->offset; + vcd_input_buffer.frm_clnt_data = + (u32) input_frame_info->client_data; + vcd_input_buffer.ip_frm_tag = + (u32) input_frame_info->client_data; + vcd_input_buffer.data_len = input_frame_info->datalen; + vcd_input_buffer.time_stamp = input_frame_info->timestamp; + /* Rely on VCD using the same flags as OMX */ + vcd_input_buffer.flags = input_frame_info->flags; + + vcd_status = vcd_decode_frame(client_ctx->vcd_handle, + &vcd_input_buffer); + if (!vcd_status) + return true; + else { + ERR("%s(): vcd_decode_frame failed = %u\n", __func__, + vcd_status); + return false; + } + + } else { + ERR("%s(): kernel_vaddr not found\n", __func__); + return false; + } +} + +static u32 vid_dec_fill_output_buffer(struct video_client_ctx *client_ctx, + struct vdec_fillbuffer_cmd *fill_buffer_cmd) +{ + unsigned long kernel_vaddr, phy_addr, user_vaddr; + int pmem_fd; + struct file *file; + s32 buffer_index = -1; + u32 vcd_status = VCD_ERR_FAIL; + + struct vcd_frame_data vcd_frame; + + if (!client_ctx || !fill_buffer_cmd) + return false; + + user_vaddr = (unsigned long)fill_buffer_cmd->buffer.bufferaddr; + + if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_OUTPUT, + true, &user_vaddr, &kernel_vaddr, + &phy_addr, &pmem_fd, &file, + &buffer_index)) { + + memset((void *)&vcd_frame, 0, + sizeof(struct vcd_frame_data)); + vcd_frame.virtual = (u8 *) kernel_vaddr; + vcd_frame.frm_clnt_data = (u32) fill_buffer_cmd->client_data; + vcd_frame.alloc_len = fill_buffer_cmd->buffer.buffer_len; + + vcd_status = vcd_fill_output_buffer(client_ctx->vcd_handle, + &vcd_frame); + if (!vcd_status) + return true; + else { + ERR("%s(): vcd_fill_output_buffer failed = %u\n", + __func__, vcd_status); + return false; + } + } else { + ERR("%s(): kernel_vaddr not found\n", __func__); + return false; + } +} + + +static u32 vid_dec_flush(struct video_client_ctx *client_ctx, + enum vdec_bufferflush flush_dir) +{ + u32 vcd_status = VCD_ERR_FAIL; + + INFO("msm_vidc_dec: %s() called with dir = %u", __func__, + flush_dir); + if (!client_ctx) { + ERR("\n Invalid client_ctx"); + return false; + } + + switch (flush_dir) { + case VDEC_FLUSH_TYPE_INPUT: + vcd_status = vcd_flush(client_ctx->vcd_handle, VCD_FLUSH_INPUT); + break; + case VDEC_FLUSH_TYPE_OUTPUT: + vcd_status = vcd_flush(client_ctx->vcd_handle, + VCD_FLUSH_OUTPUT); + break; + case VDEC_FLUSH_TYPE_ALL: + vcd_status = vcd_flush(client_ctx->vcd_handle, VCD_FLUSH_ALL); + break; + default: + ERR("%s(): Inavlid flush cmd. flush_dir = %u\n", __func__, + flush_dir); + return false; + break; + } + + if (!vcd_status) + return true; + else { + ERR("%s(): vcd_flush failed. vcd_status = %u " + " flush_dir = %u\n", __func__, vcd_status, flush_dir); + return false; + } +} + +static u32 vid_dec_msg_pending(struct video_client_ctx *client_ctx) +{ + u32 islist_empty = 0; + mutex_lock(&client_ctx->msg_queue_lock); + islist_empty = list_empty(&client_ctx->msg_queue); + mutex_unlock(&client_ctx->msg_queue_lock); + + if (islist_empty) { + DBG("%s(): vid_dec msg queue empty\n", __func__); + if (client_ctx->stop_msg) { + DBG("%s(): List empty and Stop Msg set\n", + __func__); + return client_ctx->stop_msg; + } + } else + DBG("%s(): vid_dec msg queue Not empty\n", __func__); + + return !islist_empty; +} + +static int vid_dec_get_next_msg(struct video_client_ctx *client_ctx, + struct vdec_msginfo *vdec_msg_info) +{ + int rc; + struct vid_dec_msg *vid_dec_msg = NULL; + + if (!client_ctx) + return false; + + rc = wait_event_interruptible(client_ctx->msg_wait, + vid_dec_msg_pending(client_ctx)); + if (rc < 0) { + DBG("rc = %d, stop_msg = %u\n", rc, client_ctx->stop_msg); + return rc; + } else if (client_ctx->stop_msg) { + DBG("rc = %d, stop_msg = %u\n", rc, client_ctx->stop_msg); + return -EIO; + } + + mutex_lock(&client_ctx->msg_queue_lock); + if (!list_empty(&client_ctx->msg_queue)) { + DBG("%s(): After Wait\n", __func__); + vid_dec_msg = list_first_entry(&client_ctx->msg_queue, + struct vid_dec_msg, list); + list_del(&vid_dec_msg->list); + memcpy(vdec_msg_info, &vid_dec_msg->vdec_msg_info, + sizeof(struct vdec_msginfo)); + kfree(vid_dec_msg); + } + mutex_unlock(&client_ctx->msg_queue_lock); + return 0; +} + +static long vid_dec_ioctl(struct file *file, + unsigned cmd, unsigned long u_arg) +{ + struct video_client_ctx *client_ctx = NULL; + struct vdec_ioctl_msg vdec_msg; + u32 vcd_status; + unsigned long kernel_vaddr, phy_addr, len; + struct file *pmem_file; + u32 result = true; + void __user *arg = (void __user *)u_arg; + + DBG("%s\n", __func__); + if (_IOC_TYPE(cmd) != VDEC_IOCTL_MAGIC) + return -ENOTTY; + + client_ctx = (struct video_client_ctx *)file->private_data; + if (!client_ctx) { + ERR("!client_ctx. Cannot attach to device handle\n"); + return -ENODEV; + } + + switch (cmd) { + case VDEC_IOCTL_SET_CODEC: + { + enum vdec_codec vdec_codec; + DBG("VDEC_IOCTL_SET_CODEC\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&vdec_codec, vdec_msg.in, + sizeof(vdec_codec))) + return -EFAULT; + DBG("setting code type = %u\n", vdec_codec); + result = vid_dec_set_codec(client_ctx, &vdec_codec); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_SET_OUTPUT_FORMAT: + { + enum vdec_output_fromat output_format; + DBG("VDEC_IOCTL_SET_OUTPUT_FORMAT\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&output_format, vdec_msg.in, + sizeof(output_format))) + return -EFAULT; + + result = vid_dec_set_output_format(client_ctx, &output_format); + + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_SET_PICRES: + { + struct vdec_picsize video_resoultion; + DBG("VDEC_IOCTL_SET_PICRES\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&video_resoultion, vdec_msg.in, + sizeof(video_resoultion))) + return -EFAULT; + result = + vid_dec_set_frame_resolution(client_ctx, &video_resoultion); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_GET_PICRES: + { + struct vdec_picsize video_resoultion; + DBG("VDEC_IOCTL_GET_PICRES\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&video_resoultion, vdec_msg.out, + sizeof(video_resoultion))) + return -EFAULT; + + result = vid_dec_get_frame_resolution(client_ctx, + &video_resoultion); + + if (result) { + if (copy_to_user(vdec_msg.out, &video_resoultion, + sizeof(video_resoultion))) + return -EFAULT; + } else + return -EIO; + break; + } + case VDEC_IOCTL_SET_BUFFER_REQ: + { + struct vdec_allocatorproperty vdec_buf_req; + struct vcd_buffer_requirement buffer_req; + DBG("VDEC_IOCTL_SET_BUFFER_REQ\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + + if (copy_from_user(&vdec_buf_req, vdec_msg.in, + sizeof(vdec_buf_req))) + return -EFAULT; + + buffer_req.actual_count = vdec_buf_req.actualcount; + buffer_req.align = vdec_buf_req.alignment; + buffer_req.max_count = vdec_buf_req.maxcount; + buffer_req.min_count = vdec_buf_req.mincount; + buffer_req.sz = vdec_buf_req.buffer_size; + + switch (vdec_buf_req.buffer_type) { + case VDEC_BUFFER_TYPE_INPUT: + vcd_status = + vcd_set_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_INPUT, &buffer_req); + break; + case VDEC_BUFFER_TYPE_OUTPUT: + vcd_status = + vcd_set_buffer_requirements(client_ctx->vcd_handle, + VCD_BUFFER_OUTPUT, &buffer_req); + break; + default: + vcd_status = VCD_ERR_BAD_POINTER; + break; + } + + if (vcd_status) + return -EFAULT; + break; + } + case VDEC_IOCTL_GET_BUFFER_REQ: + { + struct vdec_allocatorproperty vdec_buf_req; + DBG("VDEC_IOCTL_GET_BUFFER_REQ\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&vdec_buf_req, vdec_msg.out, + sizeof(vdec_buf_req))) + return -EFAULT; + + result = vid_dec_get_buffer_req(client_ctx, &vdec_buf_req); + + if (result) { + if (copy_to_user(vdec_msg.out, &vdec_buf_req, + sizeof(vdec_buf_req))) + return -EFAULT; + } else + return -EIO; + break; + } + case VDEC_IOCTL_SET_BUFFER: + { + struct vdec_setbuffer_cmd setbuffer; + DBG("VDEC_IOCTL_SET_BUFFER\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&setbuffer, vdec_msg.in, + sizeof(setbuffer))) + return -EFAULT; + result = vid_dec_set_buffer(client_ctx, &setbuffer); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_FREE_BUFFER: + { + struct vdec_setbuffer_cmd setbuffer; + DBG("VDEC_IOCTL_FREE_BUFFER\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&setbuffer, vdec_msg.in, + sizeof(setbuffer))) + return -EFAULT; + result = vid_dec_free_buffer(client_ctx, &setbuffer); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_CMD_START: + { + DBG(" VDEC_IOCTL_CMD_START\n"); + result = vid_dec_start_stop(client_ctx, true); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_CMD_STOP: + { + DBG("VDEC_IOCTL_CMD_STOP\n"); + result = vid_dec_start_stop(client_ctx, false); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_CMD_PAUSE: + { + result = vid_dec_pause_resume(client_ctx, true); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_CMD_RESUME: + { + DBG("VDEC_IOCTL_CMD_PAUSE\n"); + result = vid_dec_pause_resume(client_ctx, false); + + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_DECODE_FRAME: + { + struct vdec_input_frameinfo input_frame_info; + DBG("VDEC_IOCTL_DECODE_FRAME\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&input_frame_info, vdec_msg.in, + sizeof(input_frame_info))) + return -EFAULT; + + result = vid_dec_decode_frame(client_ctx, &input_frame_info); + + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_FILL_OUTPUT_BUFFER: + { + struct vdec_fillbuffer_cmd fill_buffer_cmd; + DBG("VDEC_IOCTL_FILL_OUTPUT_BUFFER\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&fill_buffer_cmd, vdec_msg.in, + sizeof(fill_buffer_cmd))) + return -EFAULT; + result = vid_dec_fill_output_buffer(client_ctx, + &fill_buffer_cmd); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_CMD_FLUSH: + { + enum vdec_bufferflush flush_dir; + DBG("VDEC_IOCTL_CMD_FLUSH\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&flush_dir, vdec_msg.in, + sizeof(flush_dir))) + return -EFAULT; + result = vid_dec_flush(client_ctx, flush_dir); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_GET_NEXT_MSG: + { + struct vdec_msginfo vdec_msg_info; + DBG("VDEC_IOCTL_GET_NEXT_MSG\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + result = vid_dec_get_next_msg(client_ctx, &vdec_msg_info); + if (result) + return result; + if (copy_to_user(vdec_msg.out, &vdec_msg_info, + sizeof(vdec_msg_info))) + return -EFAULT; + break; + } + case VDEC_IOCTL_STOP_NEXT_MSG: + { + DBG("VDEC_IOCTL_STOP_NEXT_MSG\n"); + client_ctx->stop_msg = 1; + wake_up(&client_ctx->msg_wait); + break; + } + case VDEC_IOCTL_SET_SEQUENCE_HEADER: + { + struct vdec_seqheader seq_header; + struct vcd_sequence_hdr vcd_seq_hdr; + DBG("VDEC_IOCTL_SET_SEQUENCE_HEADER\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) { + ERR("Copy from user vdec_msg failed\n"); + return -EFAULT; + } + if (copy_from_user(&seq_header, vdec_msg.in, + sizeof(seq_header))) { + ERR("Copy from user seq_header failed\n"); + return -EFAULT; + } + if (!seq_header.seq_header_len) { + ERR("Seq Len is Zero\n"); + return -EFAULT; + } + + if (get_pmem_file(seq_header.pmem_fd, + &phy_addr, &kernel_vaddr, &len, &pmem_file)) { + ERR("%s(): get_pmem_file failed\n", __func__); + return false; + } + put_pmem_file(pmem_file); + + vcd_seq_hdr.sequence_header_len = seq_header.seq_header_len; + kernel_vaddr += (unsigned long)seq_header.pmem_offset; + vcd_seq_hdr.sequence_header = (u8 *)kernel_vaddr; + if (!vcd_seq_hdr.sequence_header) { + ERR("Sequence Header pointer failed\n"); + return -EFAULT; + } + client_ctx->seq_header_set = true; + if (vcd_decode_start(client_ctx->vcd_handle, &vcd_seq_hdr)) { + ERR("Decode start Failed\n"); + client_ctx->seq_header_set = false; + return -EFAULT; + } + DBG("Wait Client completion Sequence Header\n"); + wait_for_completion(&client_ctx->event); + vcd_seq_hdr.sequence_header = NULL; + if (client_ctx->event_status) { + ERR("Set Seq Header status is failed"); + return -EFAULT; + } + break; + } + case VDEC_IOCTL_GET_NUMBER_INSTANCES: + { + DBG("VDEC_IOCTL_GET_NUMBER_INSTANCES\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_to_user(vdec_msg.out, + &vid_dec_device_p->num_clients, sizeof(u32))) + return -EFAULT; + break; + } + case VDEC_IOCTL_GET_INTERLACE_FORMAT: + { + u32 progressive_only, interlace_format; + DBG("VDEC_IOCTL_GET_INTERLACE_FORMAT\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + result = vid_dec_get_progressive_only(client_ctx, + &progressive_only); + if (result) { + interlace_format = progressive_only ? + VDEC_InterlaceFrameProgressive : + VDEC_InterlaceInterleaveFrameTopFieldFirst; + if (copy_to_user(vdec_msg.out, &interlace_format, + sizeof(u32))) + return -EFAULT; + } else + return -EIO; + break; + } + case VDEC_IOCTL_SET_PICTURE_ORDER: + { + u32 picture_order; + DBG("VDEC_IOCTL_SET_PICTURE_ORDER\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&picture_order, vdec_msg.in, + sizeof(u32))) + return -EFAULT; + result = vid_dec_set_picture_order(client_ctx, &picture_order); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_SET_FRAME_RATE: + { + struct vdec_framerate frame_rate; + DBG("VDEC_IOCTL_SET_FRAME_RATE\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&frame_rate, vdec_msg.in, + sizeof(frame_rate))) + return -EFAULT; + result = vid_dec_set_frame_rate(client_ctx, &frame_rate); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_SET_EXTRADATA: + { + u32 extradata_flag; + DBG("VDEC_IOCTL_SET_EXTRADATA\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&extradata_flag, vdec_msg.in, + sizeof(u32))) + return -EFAULT; + result = vid_dec_set_extradata(client_ctx, &extradata_flag); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_SET_H264_MV_BUFFER: + { + struct vdec_h264_mv mv_data; + DBG("VDEC_IOCTL_SET_H264_MV_BUFFER\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&mv_data, vdec_msg.in, + sizeof(mv_data))) + return -EFAULT; + result = vid_dec_set_h264_mv_buffers(client_ctx, &mv_data); + + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_FREE_H264_MV_BUFFER: + { + DBG("VDEC_IOCTL_FREE_H264_MV_BUFFER\n"); + result = vid_dec_free_h264_mv_buffers(client_ctx); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_GET_MV_BUFFER_SIZE: + { + struct vdec_mv_buff_size mv_buff; + DBG("VDEC_IOCTL_GET_MV_BUFFER_SIZE\n"); + if (copy_from_user(&vdec_msg, arg, sizeof(vdec_msg))) + return -EFAULT; + if (copy_from_user(&mv_buff, vdec_msg.out, + sizeof(mv_buff))) + return -EFAULT; + result = vid_dec_get_h264_mv_buffer_size(client_ctx, &mv_buff); + if (result) { + DBG(" Returning W: %d, H: %d, S: %d, A: %d", + mv_buff.width, mv_buff.height, + mv_buff.size, mv_buff.alignment); + if (copy_to_user(vdec_msg.out, &mv_buff, + sizeof(mv_buff))) + return -EFAULT; + } else + return -EIO; + break; + } + case VDEC_IOCTL_SET_IDR_ONLY_DECODING: + { + result = vid_dec_set_idr_only_decoding(client_ctx); + if (!result) + return -EIO; + break; + } + case VDEC_IOCTL_SET_CONT_ON_RECONFIG: + { + result = vid_dec_set_cont_on_reconfig(client_ctx); + if (!result) + return -EIO; + break; + } + default: + ERR("%s(): Unsupported ioctl\n", __func__); + return -ENOTTY; + break; + } + + return 0; +} + +static u32 vid_dec_close_client(struct video_client_ctx *client_ctx) +{ + struct vid_dec_msg *vdec_msg; + u32 vcd_status; + + INFO("msm_vidc_dec: Inside %s()", __func__); + if (!client_ctx || (!client_ctx->vcd_handle)) { + ERR("\n Invalid client_ctx"); + return false; + } + + mutex_lock(&vid_dec_device_p->lock); + if (!client_ctx->stop_called) { + client_ctx->stop_called = true; + client_ctx->stop_sync_cb = true; + vcd_status = vcd_stop(client_ctx->vcd_handle); + DBG("\n Stuck at the stop call"); + if (!vcd_status) + wait_for_completion(&client_ctx->event); + DBG("\n Came out of wait event"); + } + mutex_lock(&client_ctx->msg_queue_lock); + while (!list_empty(&client_ctx->msg_queue)) { + DBG("%s(): Delete remaining entries\n", __func__); + vdec_msg = list_first_entry(&client_ctx->msg_queue, + struct vid_dec_msg, list); + if (vdec_msg) { + list_del(&vdec_msg->list); + kfree(vdec_msg); + } + } + mutex_unlock(&client_ctx->msg_queue_lock); + vcd_status = vcd_close(client_ctx->vcd_handle); + + if (vcd_status) { + mutex_unlock(&vid_dec_device_p->lock); + return false; + } + memset((void *)client_ctx, 0, sizeof(struct video_client_ctx)); + vid_dec_device_p->num_clients--; + mutex_unlock(&vid_dec_device_p->lock); + return true; +} + +static int vid_dec_open(struct inode *inode, struct file *file) +{ + s32 client_index; + struct video_client_ctx *client_ctx; + u32 vcd_status = VCD_ERR_FAIL; + u8 client_count = 0; + + INFO("msm_vidc_dec: Inside %s()", __func__); + mutex_lock(&vid_dec_device_p->lock); + + client_count = vcd_get_num_of_clients(); + if (client_count == VIDC_MAX_NUM_CLIENTS) { + ERR("ERROR : vid_dec_open() max number of clients" + "limit reached\n"); + mutex_unlock(&vid_dec_device_p->lock); + return -ENODEV; + } + + DBG(" Virtual Address of ioremap is %p\n", vid_dec_device_p->virt_base); + if (!vid_dec_device_p->num_clients) { + if (!vidc_load_firmware()) + return -ENODEV; + } + + client_index = vid_dec_get_empty_client_index(); + if (client_index == -1) { + ERR("%s() : No free clients client_index == -1\n", __func__); + return -ENODEV; + } + client_ctx = &vid_dec_device_p->vdec_clients[client_index]; + vid_dec_device_p->num_clients++; + init_completion(&client_ctx->event); + mutex_init(&client_ctx->msg_queue_lock); + INIT_LIST_HEAD(&client_ctx->msg_queue); + init_waitqueue_head(&client_ctx->msg_wait); + client_ctx->stop_msg = 0; + client_ctx->stop_called = false; + client_ctx->stop_sync_cb = false; + vcd_status = vcd_open(vid_dec_device_p->device_handle, true, + vid_dec_vcd_cb, client_ctx); + if (!vcd_status) { + wait_for_completion(&client_ctx->event); + if (client_ctx->event_status) { + ERR("callback for vcd_open returned error: %u", + client_ctx->event_status); + mutex_unlock(&vid_dec_device_p->lock); + return -EFAULT; + } + } else { + ERR("vcd_open returned error: %u", vcd_status); + mutex_unlock(&vid_dec_device_p->lock); + return -EFAULT; + } + + client_ctx->seq_header_set = false; + file->private_data = client_ctx; + mutex_unlock(&vid_dec_device_p->lock); + return 0; +} + +static int vid_dec_release(struct inode *inode, struct file *file) +{ + struct video_client_ctx *client_ctx = file->private_data; + + INFO("msm_vidc_dec: Inside %s()", __func__); + vid_dec_close_client(client_ctx); + vidc_release_firmware(); +#ifndef USE_RES_TRACKER + vidc_disable_clk(); +#endif + INFO("msm_vidc_dec: Return from %s()", __func__); + return 0; +} + +static const struct file_operations vid_dec_fops = { + .owner = THIS_MODULE, + .open = vid_dec_open, + .release = vid_dec_release, + .unlocked_ioctl = vid_dec_ioctl, +}; + +void vid_dec_interrupt_deregister(void) +{ +} + +void vid_dec_interrupt_register(void *device_name) +{ +} + +void vid_dec_interrupt_clear(void) +{ +} + +void *vid_dec_map_dev_base_addr(void *device_name) +{ + return vid_dec_device_p->virt_base; +} + +static int vid_dec_vcd_init(void) +{ + int rc; + struct vcd_init_config vcd_init_config; + u32 i; + + /* init_timer(&hw_timer); */ + INFO("msm_vidc_dec: Inside %s()", __func__); + vid_dec_device_p->num_clients = 0; + + for (i = 0; i < VIDC_MAX_NUM_CLIENTS; i++) { + memset((void *)&vid_dec_device_p->vdec_clients[i], 0, + sizeof(vid_dec_device_p->vdec_clients[i])); + } + + mutex_init(&vid_dec_device_p->lock); + vid_dec_device_p->virt_base = vidc_get_ioaddr(); + DBG("%s() : base address for VIDC core %u\n", __func__, \ + (int)vid_dec_device_p->virt_base); + + if (!vid_dec_device_p->virt_base) { + ERR("%s() : ioremap failed\n", __func__); + return -ENOMEM; + } + + vcd_init_config.device_name = "VIDC"; + vcd_init_config.map_dev_base_addr = vid_dec_map_dev_base_addr; + vcd_init_config.interrupt_clr = vid_dec_interrupt_clear; + vcd_init_config.register_isr = vid_dec_interrupt_register; + vcd_init_config.deregister_isr = vid_dec_interrupt_deregister; + vcd_init_config.timer_create = vidc_timer_create; + vcd_init_config.timer_release = vidc_timer_release; + vcd_init_config.timer_start = vidc_timer_start; + vcd_init_config.timer_stop = vidc_timer_stop; + + rc = vcd_init(&vcd_init_config, &vid_dec_device_p->device_handle); + + if (rc) { + ERR("%s() : vcd_init failed\n", __func__); + return -ENODEV; + } + return 0; +} + +static int __init vid_dec_init(void) +{ + int rc = 0; + struct device *class_devp; + + INFO("msm_vidc_dec: Inside %s()", __func__); + vid_dec_device_p = kzalloc(sizeof(struct vid_dec_dev), GFP_KERNEL); + if (!vid_dec_device_p) { + ERR("%s Unable to allocate memory for vid_dec_dev\n", + __func__); + return -ENOMEM; + } + + rc = alloc_chrdev_region(&vid_dec_dev_num, 0, 1, VID_DEC_NAME); + if (rc < 0) { + ERR("%s: alloc_chrdev_region Failed rc = %d\n", + __func__, rc); + goto error_vid_dec_alloc_chrdev_region; + } + + vid_dec_class = class_create(THIS_MODULE, VID_DEC_NAME); + if (IS_ERR(vid_dec_class)) { + rc = PTR_ERR(vid_dec_class); + ERR("%s: couldn't create vid_dec_class rc = %d\n", + __func__, rc); + + goto error_vid_dec_class_create; + } + + class_devp = device_create(vid_dec_class, NULL, vid_dec_dev_num, NULL, + VID_DEC_NAME); + + if (IS_ERR(class_devp)) { + rc = PTR_ERR(class_devp); + ERR("%s: class device_create failed %d\n", + __func__, rc); + goto error_vid_dec_class_device_create; + } + + vid_dec_device_p->device = class_devp; + + cdev_init(&vid_dec_device_p->cdev, &vid_dec_fops); + vid_dec_device_p->cdev.owner = THIS_MODULE; + rc = cdev_add(&(vid_dec_device_p->cdev), vid_dec_dev_num, 1); + + if (rc < 0) { + ERR("%s: cdev_add failed %d\n", __func__, rc); + goto error_vid_dec_cdev_add; + } + vid_dec_vcd_init(); + return 0; + +error_vid_dec_cdev_add: + device_destroy(vid_dec_class, vid_dec_dev_num); +error_vid_dec_class_device_create: + class_destroy(vid_dec_class); +error_vid_dec_class_create: + unregister_chrdev_region(vid_dec_dev_num, 1); +error_vid_dec_alloc_chrdev_region: + kfree(vid_dec_device_p); + + return rc; +} + +static void __exit vid_dec_exit(void) +{ + INFO("msm_vidc_dec: Inside %s()", __func__); + cdev_del(&(vid_dec_device_p->cdev)); + device_destroy(vid_dec_class, vid_dec_dev_num); + class_destroy(vid_dec_class); + unregister_chrdev_region(vid_dec_dev_num, 1); + kfree(vid_dec_device_p); + INFO("msm_vidc_dec: Return from %s()", __func__); +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Video decoder driver"); +MODULE_VERSION("1.0"); + +module_init(vid_dec_init); +module_exit(vid_dec_exit); diff --git a/drivers/video/msm/vidc/common/dec/vdec_internal.h b/drivers/video/msm/vidc/common/dec/vdec_internal.h new file mode 100644 index 00000000000..867c3b350ce --- /dev/null +++ b/drivers/video/msm/vidc/common/dec/vdec_internal.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VDEC_INTERNAL_H +#define VDEC_INTERNAL_H + +#include +#include +#include "vidc_init.h" + +struct vid_dec_msg { + struct list_head list; + struct vdec_msginfo vdec_msg_info; +}; + +struct vid_dec_dev { + struct cdev cdev; + struct device *device; + resource_size_t phys_base; + void __iomem *virt_base; + unsigned int irq; + struct clk *hclk; + struct clk *hclk_div2; + struct clk *pclk; + unsigned long hclk_rate; + struct mutex lock; + s32 device_handle; + struct video_client_ctx vdec_clients[VIDC_MAX_NUM_CLIENTS]; + u32 num_clients; + void(*timer_handler)(void *); +}; + +#endif diff --git a/drivers/video/msm/vidc/common/enc/venc.c b/drivers/video/msm/vidc/common/enc/venc.c new file mode 100644 index 00000000000..a69b8100b3e --- /dev/null +++ b/drivers/video/msm/vidc/common/enc/venc.c @@ -0,0 +1,1549 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vidc_type.h" +#include "vcd_api.h" +#include "venc_internal.h" +#include "vidc_init.h" + +#define VID_ENC_NAME "msm_vidc_enc" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +#define INFO(x...) printk(KERN_INFO x) +#define ERR(x...) printk(KERN_ERR x) + +static struct vid_enc_dev *vid_enc_device_p; +static dev_t vid_enc_dev_num; +static struct class *vid_enc_class; +static long vid_enc_ioctl(struct file *file, + unsigned cmd, unsigned long arg); +static int stop_cmd; + +static s32 vid_enc_get_empty_client_index(void) +{ + u32 i; + u32 found = false; + + for (i = 0; i < VIDC_MAX_NUM_CLIENTS; i++) { + if (!vid_enc_device_p->venc_clients[i].vcd_handle) { + found = true; + break; + } + } + if (!found) { + ERR("%s():ERROR No space for new client\n", + __func__); + return -ENOMEM; + } else { + DBG("%s(): available client index = %u\n", + __func__, i); + return i; + } +} + + +u32 vid_enc_get_status(u32 status) +{ + u32 venc_status; + + switch (status) { + case VCD_S_SUCCESS: + venc_status = VEN_S_SUCCESS; + break; + case VCD_ERR_FAIL: + venc_status = VEN_S_EFAIL; + break; + case VCD_ERR_ALLOC_FAIL: + venc_status = VEN_S_ENOSWRES; + break; + case VCD_ERR_ILLEGAL_OP: + venc_status = VEN_S_EINVALCMD; + break; + case VCD_ERR_ILLEGAL_PARM: + venc_status = VEN_S_EBADPARAM; + break; + case VCD_ERR_BAD_POINTER: + case VCD_ERR_BAD_HANDLE: + venc_status = VEN_S_EFATAL; + break; + case VCD_ERR_NOT_SUPPORTED: + venc_status = VEN_S_ENOTSUPP; + break; + case VCD_ERR_BAD_STATE: + venc_status = VEN_S_EINVALSTATE; + break; + case VCD_ERR_MAX_CLIENT: + venc_status = VEN_S_ENOHWRES; + break; + default: + venc_status = VEN_S_EFAIL; + break; + } + return venc_status; +} + +static void vid_enc_notify_client(struct video_client_ctx *client_ctx) +{ + if (client_ctx) + complete(&client_ctx->event); +} + +void vid_enc_vcd_open_done(struct video_client_ctx *client_ctx, + struct vcd_handle_container *handle_container) +{ + DBG("vid_enc_vcd_open_done\n"); + + if (client_ctx) { + if (handle_container) + client_ctx->vcd_handle = handle_container->handle; + else + ERR("%s(): ERROR. handle_container is NULL\n", + __func__); + vid_enc_notify_client(client_ctx); + } else + ERR("%s(): ERROR. client_ctx is NULL\n", + __func__); +} + +static void vid_enc_input_frame_done(struct video_client_ctx *client_ctx, + u32 event, u32 status, + struct vcd_frame_data *vcd_frame_data) +{ + struct vid_enc_msg *venc_msg; + + if (!client_ctx || !vcd_frame_data) { + ERR("vid_enc_input_frame_done() NULL pointer\n"); + return; + } + + venc_msg = kzalloc(sizeof(struct vid_enc_msg), + GFP_KERNEL); + if (!venc_msg) { + ERR("vid_enc_input_frame_done(): cannot allocate vid_enc_msg " + " buffer\n"); + return; + } + + venc_msg->venc_msg_info.statuscode = vid_enc_get_status(status); + + venc_msg->venc_msg_info.msgcode = VEN_MSG_INPUT_BUFFER_DONE; + + switch (event) { + case VCD_EVT_RESP_INPUT_DONE: + DBG("Send INPUT_DON message to client = %p\n", + client_ctx); + break; + case VCD_EVT_RESP_INPUT_FLUSHED: + DBG("Send INPUT_FLUSHED message to client = %p\n", + client_ctx); + break; + default: + ERR("vid_enc_input_frame_done(): invalid event type: " + "%d\n", event); + venc_msg->venc_msg_info.statuscode = VEN_S_EFATAL; + break; + } + + venc_msg->venc_msg_info.buf.clientdata = + (void *)vcd_frame_data->frm_clnt_data; + venc_msg->venc_msg_info.msgdata_size = + sizeof(struct vid_enc_msg); + + mutex_lock(&client_ctx->msg_queue_lock); + list_add_tail(&venc_msg->list, &client_ctx->msg_queue); + mutex_unlock(&client_ctx->msg_queue_lock); + wake_up(&client_ctx->msg_wait); +} + +static void vid_enc_output_frame_done(struct video_client_ctx *client_ctx, + u32 event, u32 status, + struct vcd_frame_data *vcd_frame_data) +{ + struct vid_enc_msg *venc_msg; + unsigned long kernel_vaddr, phy_addr, user_vaddr; + int pmem_fd; + struct file *file; + s32 buffer_index = -1; + + if (!client_ctx || !vcd_frame_data) { + ERR("vid_enc_input_frame_done() NULL pointer\n"); + return; + } + + venc_msg = kzalloc(sizeof(struct vid_enc_msg), + GFP_KERNEL); + if (!venc_msg) { + ERR("vid_enc_input_frame_done(): cannot allocate vid_enc_msg " + " buffer\n"); + return; + } + + venc_msg->venc_msg_info.statuscode = vid_enc_get_status(status); + venc_msg->venc_msg_info.msgcode = VEN_MSG_OUTPUT_BUFFER_DONE; + + switch (event) { + case VCD_EVT_RESP_OUTPUT_DONE: + DBG("Send INPUT_DON message to client = %p\n", + client_ctx); + break; + case VCD_EVT_RESP_OUTPUT_FLUSHED: + DBG("Send INPUT_FLUSHED message to client = %p\n", + client_ctx); + break; + default: + ERR("QVD: vid_enc_output_frame_done invalid cmd type: %d\n", event); + venc_msg->venc_msg_info.statuscode = VEN_S_EFATAL; + break; + } + + kernel_vaddr = + (unsigned long)vcd_frame_data->virtual; + + if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_OUTPUT, + false, &user_vaddr, &kernel_vaddr, + &phy_addr, &pmem_fd, &file, + &buffer_index)) { + + /* Buffer address in user space */ + venc_msg->venc_msg_info.buf.ptrbuffer = (u8 *) user_vaddr; + /* Buffer address in user space */ + venc_msg->venc_msg_info.buf.clientdata = (void *) + vcd_frame_data->frm_clnt_data; + /* Data length */ + venc_msg->venc_msg_info.buf.len = + vcd_frame_data->data_len; + venc_msg->venc_msg_info.buf.flags = + vcd_frame_data->flags; + /* Timestamp pass-through from input frame */ + venc_msg->venc_msg_info.buf.timestamp = + vcd_frame_data->time_stamp; + + /* Decoded picture width and height */ + venc_msg->venc_msg_info.msgdata_size = + sizeof(struct venc_buffer); + } else { + ERR("vid_enc_output_frame_done UVA can not be found\n"); + venc_msg->venc_msg_info.statuscode = + VEN_S_EFATAL; + } + + mutex_lock(&client_ctx->msg_queue_lock); + list_add_tail(&venc_msg->list, &client_ctx->msg_queue); + mutex_unlock(&client_ctx->msg_queue_lock); + wake_up(&client_ctx->msg_wait); +} + +static void vid_enc_lean_event(struct video_client_ctx *client_ctx, + u32 event, u32 status) +{ + struct vid_enc_msg *venc_msg; + if (!client_ctx) { + ERR("%s(): !client_ctx pointer\n", + __func__); + return; + } + + venc_msg = kzalloc(sizeof(struct vid_enc_msg), + GFP_KERNEL); + if (!venc_msg) { + ERR("%s(): cannot allocate vid_enc_msg buffer\n", + __func__); + return; + } + + venc_msg->venc_msg_info.statuscode = + vid_enc_get_status(status); + + switch (event) { + case VCD_EVT_RESP_FLUSH_INPUT_DONE: + INFO("\n msm_vidc_enc: Sending VCD_EVT_RESP_FLUSH_INPUT_DONE" + " to client"); + venc_msg->venc_msg_info.msgcode = + VEN_MSG_FLUSH_INPUT_DONE; + break; + case VCD_EVT_RESP_FLUSH_OUTPUT_DONE: + INFO("\n msm_vidc_enc: Sending VCD_EVT_RESP_FLUSH_OUTPUT_DONE" + " to client"); + venc_msg->venc_msg_info.msgcode = + VEN_MSG_FLUSH_OUPUT_DONE; + break; + + case VCD_EVT_RESP_START: + INFO("\n msm_vidc_enc: Sending VCD_EVT_RESP_START" + " to client"); + venc_msg->venc_msg_info.msgcode = + VEN_MSG_START; + break; + + case VCD_EVT_RESP_STOP: + INFO("\n msm_vidc_enc: Sending VCD_EVT_RESP_STOP" + " to client"); + venc_msg->venc_msg_info.msgcode = + VEN_MSG_STOP; + break; + + case VCD_EVT_RESP_PAUSE: + INFO("\n msm_vidc_enc: Sending VCD_EVT_RESP_PAUSE" + " to client"); + venc_msg->venc_msg_info.msgcode = + VEN_MSG_PAUSE; + break; + + default: + ERR("%s() : unknown event type %u\n", + __func__, event); + break; + } + + venc_msg->venc_msg_info.msgdata_size = 0; + + mutex_lock(&client_ctx->msg_queue_lock); + list_add_tail(&venc_msg->list, &client_ctx->msg_queue); + mutex_unlock(&client_ctx->msg_queue_lock); + wake_up(&client_ctx->msg_wait); +} + + +void vid_enc_vcd_cb(u32 event, u32 status, + void *info, size_t sz, void *handle, + void *const client_data) +{ + struct video_client_ctx *client_ctx = + (struct video_client_ctx *)client_data; + + DBG("Entering %s()\n", __func__); + + if (!client_ctx) { + ERR("%s(): client_ctx is NULL\n", __func__); + return; + } + + client_ctx->event_status = status; + + switch (event) { + case VCD_EVT_RESP_OPEN: + vid_enc_vcd_open_done(client_ctx, + (struct vcd_handle_container *)info); + break; + + case VCD_EVT_RESP_INPUT_DONE: + case VCD_EVT_RESP_INPUT_FLUSHED: + vid_enc_input_frame_done(client_ctx, event, + status, (struct vcd_frame_data *)info); + break; + + case VCD_EVT_RESP_OUTPUT_DONE: + case VCD_EVT_RESP_OUTPUT_FLUSHED: + vid_enc_output_frame_done(client_ctx, event, status, + (struct vcd_frame_data *)info); + break; + + case VCD_EVT_RESP_PAUSE: + case VCD_EVT_RESP_START: + case VCD_EVT_RESP_STOP: + case VCD_EVT_RESP_FLUSH_INPUT_DONE: + case VCD_EVT_RESP_FLUSH_OUTPUT_DONE: + case VCD_EVT_IND_OUTPUT_RECONFIG: + case VCD_EVT_IND_HWERRFATAL: + case VCD_EVT_IND_RESOURCES_LOST: + vid_enc_lean_event(client_ctx, event, status); + break; + + default: + ERR("%s() : Error - Invalid event type =%u\n", + __func__, event); + break; + } +} + +static u32 vid_enc_msg_pending(struct video_client_ctx *client_ctx) +{ + u32 islist_empty = 0; + + mutex_lock(&client_ctx->msg_queue_lock); + islist_empty = list_empty(&client_ctx->msg_queue); + mutex_unlock(&client_ctx->msg_queue_lock); + + if (islist_empty) { + DBG("%s(): vid_enc msg queue empty\n", + __func__); + if (client_ctx->stop_msg) { + DBG("%s(): List empty and Stop Msg set\n", + __func__); + return client_ctx->stop_msg; + } + } else + DBG("%s(): vid_enc msg queue Not empty\n", + __func__); + + return !islist_empty; +} + +static u32 vid_enc_get_next_msg(struct video_client_ctx *client_ctx, + struct venc_msg *venc_msg_info) +{ + int rc; + struct vid_enc_msg *vid_enc_msg = NULL; + + if (!client_ctx) + return false; + + rc = wait_event_interruptible(client_ctx->msg_wait, + vid_enc_msg_pending(client_ctx)); + + if (rc < 0 || client_ctx->stop_msg) { + DBG("rc = %d, stop_msg = %u\n", rc, client_ctx->stop_msg); + return false; + } + + mutex_lock(&client_ctx->msg_queue_lock); + + if (!list_empty(&client_ctx->msg_queue)) { + DBG("%s(): After Wait\n", __func__); + vid_enc_msg = list_first_entry(&client_ctx->msg_queue, + struct vid_enc_msg, list); + list_del(&vid_enc_msg->list); + memcpy(venc_msg_info, &vid_enc_msg->venc_msg_info, + sizeof(struct venc_msg)); + kfree(vid_enc_msg); + } + mutex_unlock(&client_ctx->msg_queue_lock); + return true; +} + +static u32 vid_enc_close_client(struct video_client_ctx *client_ctx) +{ + struct vid_enc_msg *vid_enc_msg = NULL; + u32 vcd_status; + int rc; + + INFO("\n msm_vidc_enc: Inside %s()", __func__); + if (!client_ctx || (!client_ctx->vcd_handle)) { + ERR("\n %s(): Invalid client_ctx", __func__); + return false; + } + + mutex_lock(&vid_enc_device_p->lock); + + if (!stop_cmd) { + vcd_status = vcd_stop(client_ctx->vcd_handle); + DBG("Waiting for VCD_STOP: Before Timeout\n"); + if (!vcd_status) { + rc = wait_for_completion_timeout(&client_ctx->event, + 5 * HZ); + if (!rc) { + ERR("%s:ERROR vcd_stop time out" + "rc = %d\n", __func__, rc); + } + + if (client_ctx->event_status) { + ERR("%s:ERROR " + "vcd_stop Not successs\n", __func__); + } + } + } + DBG("VCD_STOPPED: After Timeout, calling VCD_CLOSE\n"); + mutex_lock(&client_ctx->msg_queue_lock); + while (!list_empty(&client_ctx->msg_queue)) { + DBG("%s(): Delete remaining entries\n", __func__); + vid_enc_msg = list_first_entry(&client_ctx->msg_queue, + struct vid_enc_msg, list); + list_del(&vid_enc_msg->list); + kfree(vid_enc_msg); + } + mutex_unlock(&client_ctx->msg_queue_lock); + vcd_status = vcd_close(client_ctx->vcd_handle); + + if (vcd_status) { + mutex_unlock(&vid_enc_device_p->lock); + return false; + } + + memset((void *)client_ctx, 0, + sizeof(struct video_client_ctx)); + + vid_enc_device_p->num_clients--; + stop_cmd = 0; + mutex_unlock(&vid_enc_device_p->lock); + return true; +} + + +static int vid_enc_open(struct inode *inode, struct file *file) +{ + s32 client_index; + struct video_client_ctx *client_ctx; + u32 vcd_status = VCD_ERR_FAIL; + u8 client_count = 0; + + INFO("\n msm_vidc_enc: Inside %s()", __func__); + + mutex_lock(&vid_enc_device_p->lock); + + stop_cmd = 0; + client_count = vcd_get_num_of_clients(); + if (client_count == VIDC_MAX_NUM_CLIENTS) { + ERR("ERROR : vid_enc_open() max number of clients" + "limit reached\n"); + mutex_unlock(&vid_enc_device_p->lock); + return -ENODEV; + } + + DBG(" Virtual Address of ioremap is %p\n", vid_enc_device_p->virt_base); + if (!vid_enc_device_p->num_clients) { + if (!vidc_load_firmware()) + return -ENODEV; + } + + client_index = vid_enc_get_empty_client_index(); + + if (client_index == -1) { + ERR("%s() : No free clients client_index == -1\n", + __func__); + return -ENODEV; + } + + client_ctx = + &vid_enc_device_p->venc_clients[client_index]; + vid_enc_device_p->num_clients++; + + init_completion(&client_ctx->event); + mutex_init(&client_ctx->msg_queue_lock); + INIT_LIST_HEAD(&client_ctx->msg_queue); + init_waitqueue_head(&client_ctx->msg_wait); + vcd_status = vcd_open(vid_enc_device_p->device_handle, false, + vid_enc_vcd_cb, client_ctx); + client_ctx->stop_msg = 0; + + if (!vcd_status) { + wait_for_completion(&client_ctx->event); + if (client_ctx->event_status) { + ERR("callback for vcd_open returned error: %u", + client_ctx->event_status); + mutex_unlock(&vid_enc_device_p->lock); + return -EFAULT; + } + } else { + ERR("vcd_open returned error: %u", vcd_status); + mutex_unlock(&vid_enc_device_p->lock); + return -EFAULT; + } + file->private_data = client_ctx; + mutex_unlock(&vid_enc_device_p->lock); + return 0; +} + +static int vid_enc_release(struct inode *inode, struct file *file) +{ + struct video_client_ctx *client_ctx = file->private_data; + INFO("\n msm_vidc_enc: Inside %s()", __func__); + vid_enc_close_client(client_ctx); + vidc_release_firmware(); +#ifndef USE_RES_TRACKER + vidc_disable_clk(); +#endif + INFO("\n msm_vidc_enc: Return from %s()", __func__); + return 0; +} + +static const struct file_operations vid_enc_fops = { + .owner = THIS_MODULE, + .open = vid_enc_open, + .release = vid_enc_release, + .unlocked_ioctl = vid_enc_ioctl, +}; + +void vid_enc_interrupt_deregister(void) +{ +} + +void vid_enc_interrupt_register(void *device_name) +{ +} + +void vid_enc_interrupt_clear(void) +{ +} + +void *vid_enc_map_dev_base_addr(void *device_name) +{ + return vid_enc_device_p->virt_base; +} + +static int vid_enc_vcd_init(void) +{ + int rc; + struct vcd_init_config vcd_init_config; + u32 i; + + INFO("\n msm_vidc_enc: Inside %s()", __func__); + vid_enc_device_p->num_clients = 0; + + for (i = 0; i < VIDC_MAX_NUM_CLIENTS; i++) { + memset((void *)&vid_enc_device_p->venc_clients[i], 0, + sizeof(vid_enc_device_p->venc_clients[i])); + } + + mutex_init(&vid_enc_device_p->lock); + vid_enc_device_p->virt_base = vidc_get_ioaddr(); + + if (!vid_enc_device_p->virt_base) { + ERR("%s() : ioremap failed\n", __func__); + return -ENOMEM; + } + + vcd_init_config.device_name = "VIDC"; + vcd_init_config.map_dev_base_addr = + vid_enc_map_dev_base_addr; + vcd_init_config.interrupt_clr = + vid_enc_interrupt_clear; + vcd_init_config.register_isr = + vid_enc_interrupt_register; + vcd_init_config.deregister_isr = + vid_enc_interrupt_deregister; + + rc = vcd_init(&vcd_init_config, + &vid_enc_device_p->device_handle); + + if (rc) { + ERR("%s() : vcd_init failed\n", + __func__); + return -ENODEV; + } + return 0; +} + +static int __init vid_enc_init(void) +{ + int rc = 0; + struct device *class_devp; + + INFO("\n msm_vidc_enc: Inside %s()", __func__); + vid_enc_device_p = kzalloc(sizeof(struct vid_enc_dev), + GFP_KERNEL); + if (!vid_enc_device_p) { + ERR("%s Unable to allocate memory for vid_enc_dev\n", + __func__); + return -ENOMEM; + } + + rc = alloc_chrdev_region(&vid_enc_dev_num, 0, 1, VID_ENC_NAME); + if (rc < 0) { + ERR("%s: alloc_chrdev_region Failed rc = %d\n", + __func__, rc); + goto error_vid_enc_alloc_chrdev_region; + } + + vid_enc_class = class_create(THIS_MODULE, VID_ENC_NAME); + if (IS_ERR(vid_enc_class)) { + rc = PTR_ERR(vid_enc_class); + ERR("%s: couldn't create vid_enc_class rc = %d\n", + __func__, rc); + goto error_vid_enc_class_create; + } + + class_devp = device_create(vid_enc_class, NULL, + vid_enc_dev_num, NULL, VID_ENC_NAME); + + if (IS_ERR(class_devp)) { + rc = PTR_ERR(class_devp); + ERR("%s: class device_create failed %d\n", + __func__, rc); + goto error_vid_enc_class_device_create; + } + + vid_enc_device_p->device = class_devp; + + cdev_init(&vid_enc_device_p->cdev, &vid_enc_fops); + vid_enc_device_p->cdev.owner = THIS_MODULE; + rc = cdev_add(&(vid_enc_device_p->cdev), vid_enc_dev_num, 1); + + if (rc < 0) { + ERR("%s: cdev_add failed %d\n", + __func__, rc); + goto error_vid_enc_cdev_add; + } + vid_enc_vcd_init(); + return 0; + +error_vid_enc_cdev_add: + device_destroy(vid_enc_class, vid_enc_dev_num); +error_vid_enc_class_device_create: + class_destroy(vid_enc_class); +error_vid_enc_class_create: + unregister_chrdev_region(vid_enc_dev_num, 1); +error_vid_enc_alloc_chrdev_region: + kfree(vid_enc_device_p); + + return rc; +} + +static void __exit vid_enc_exit(void) +{ + INFO("\n msm_vidc_enc: Inside %s()", __func__); + cdev_del(&(vid_enc_device_p->cdev)); + device_destroy(vid_enc_class, vid_enc_dev_num); + class_destroy(vid_enc_class); + unregister_chrdev_region(vid_enc_dev_num, 1); + kfree(vid_enc_device_p); + INFO("\n msm_vidc_enc: Return from %s()", __func__); +} +static long vid_enc_ioctl(struct file *file, + unsigned cmd, unsigned long u_arg) +{ + struct video_client_ctx *client_ctx = NULL; + struct venc_ioctl_msg venc_msg; + void __user *arg = (void __user *)u_arg; + u32 result = true; + + DBG("%s\n", __func__); + + client_ctx = (struct video_client_ctx *)file->private_data; + if (!client_ctx) { + ERR("!client_ctx. Cannot attach to device handle\n"); + return -ENODEV; + } + + switch (cmd) { + case VEN_IOCTL_CMD_READ_NEXT_MSG: + { + struct venc_msg cb_msg; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_CMD_READ_NEXT_MSG\n"); + result = vid_enc_get_next_msg(client_ctx, &cb_msg); + if (!result) + return -EIO; + if (copy_to_user(venc_msg.out, &cb_msg, sizeof(cb_msg))) + return -EFAULT; + break; + } + case VEN_IOCTL_CMD_STOP_READ_MSG: + { + DBG("VEN_IOCTL_CMD_STOP_READ_MSG\n"); + client_ctx->stop_msg = 1; + wake_up(&client_ctx->msg_wait); + break; + } + case VEN_IOCTL_CMD_ENCODE_FRAME: + case VEN_IOCTL_CMD_FILL_OUTPUT_BUFFER: + { + struct venc_buffer enc_buffer; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_CMD_ENCODE_FRAME" + "/VEN_IOCTL_CMD_FILL_OUTPUT_BUFFER\n"); + if (copy_from_user(&enc_buffer, venc_msg.in, + sizeof(enc_buffer))) + return -EFAULT; + if (cmd == VEN_IOCTL_CMD_ENCODE_FRAME) + result = vid_enc_encode_frame(client_ctx, + &enc_buffer); + else + result = vid_enc_fill_output_buffer(client_ctx, + &enc_buffer); + if (!result) { + DBG("\n VEN_IOCTL_CMD_ENCODE_FRAME/" + "VEN_IOCTL_CMD_FILL_OUTPUT_BUFFER failed"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_INPUT_BUFFER: + case VEN_IOCTL_SET_OUTPUT_BUFFER: + { + enum venc_buffer_dir buffer_dir; + struct venc_bufferpayload buffer_info; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_SET_INPUT_BUFFER/VEN_IOCTL_SET_OUTPUT_BUFFER\n"); + if (copy_from_user(&buffer_info, venc_msg.in, + sizeof(buffer_info))) + return -EFAULT; + buffer_dir = VEN_BUFFER_TYPE_INPUT; + if (cmd == VEN_IOCTL_SET_OUTPUT_BUFFER) + buffer_dir = VEN_BUFFER_TYPE_OUTPUT; + result = vid_enc_set_buffer(client_ctx, &buffer_info, + buffer_dir); + if (!result) { + DBG("\n VEN_IOCTL_SET_INPUT_BUFFER" + "/VEN_IOCTL_SET_OUTPUT_BUFFER failed"); + return -EIO; + } + break; + } + case VEN_IOCTL_CMD_FREE_INPUT_BUFFER: + case VEN_IOCTL_CMD_FREE_OUTPUT_BUFFER: + { + enum venc_buffer_dir buffer_dir; + struct venc_bufferpayload buffer_info; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("VEN_IOCTL_CMD_FREE_INPUT_BUFFER/" + "VEN_IOCTL_CMD_FREE_OUTPUT_BUFFER\n"); + + if (copy_from_user(&buffer_info, venc_msg.in, + sizeof(buffer_info))) + return -EFAULT; + + buffer_dir = VEN_BUFFER_TYPE_INPUT; + if (cmd == VEN_IOCTL_CMD_FREE_OUTPUT_BUFFER) + buffer_dir = VEN_BUFFER_TYPE_OUTPUT; + + result = vid_enc_free_buffer(client_ctx, &buffer_info, + buffer_dir); + if (!result) { + DBG("\n VEN_IOCTL_CMD_FREE_OUTPUT_BUFFER" + "/VEN_IOCTL_CMD_FREE_OUTPUT_BUFFER failed"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_INPUT_BUFFER_REQ: + case VEN_IOCTL_SET_OUTPUT_BUFFER_REQ: + { + struct venc_allocatorproperty allocatorproperty; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("VEN_IOCTL_SET_INPUT_BUFFER_REQ" + "/VEN_IOCTL_SET_OUTPUT_BUFFER_REQ\n"); + + if (copy_from_user(&allocatorproperty, venc_msg.in, + sizeof(allocatorproperty))) + return -EFAULT; + + if (cmd == VEN_IOCTL_SET_OUTPUT_BUFFER_REQ) + result = vid_enc_set_buffer_req(client_ctx, + &allocatorproperty, false); + else + result = vid_enc_set_buffer_req(client_ctx, + &allocatorproperty, true); + if (!result) { + DBG("setting VEN_IOCTL_SET_OUTPUT_BUFFER_REQ/" + "VEN_IOCTL_SET_INPUT_BUFFER_REQ failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_GET_INPUT_BUFFER_REQ: + case VEN_IOCTL_GET_OUTPUT_BUFFER_REQ: + { + struct venc_allocatorproperty allocatorproperty; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("VEN_IOCTL_GET_INPUT_BUFFER_REQ/" + "VEN_IOCTL_GET_OUTPUT_BUFFER_REQ\n"); + + if (cmd == VEN_IOCTL_GET_OUTPUT_BUFFER_REQ) + result = vid_enc_get_buffer_req(client_ctx, + &allocatorproperty, false); + else + result = vid_enc_get_buffer_req(client_ctx, + &allocatorproperty, true); + if (!result) + return -EIO; + if (copy_to_user(venc_msg.out, &allocatorproperty, + sizeof(allocatorproperty))) + return -EFAULT; + break; + } + case VEN_IOCTL_CMD_FLUSH: + { + struct venc_bufferflush bufferflush; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("VEN_IOCTL_CMD_FLUSH\n"); + if (copy_from_user(&bufferflush, venc_msg.in, + sizeof(bufferflush))) + return -EFAULT; + INFO("\n %s(): Calling vid_enc_flush with mode = %lu", + __func__, bufferflush.flush_mode); + result = vid_enc_flush(client_ctx, &bufferflush); + + if (!result) { + ERR("setting VEN_IOCTL_CMD_FLUSH failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_CMD_START: + { + INFO("\n %s(): Executing VEN_IOCTL_CMD_START", __func__); + result = vid_enc_start_stop(client_ctx, true); + if (!result) { + ERR("setting VEN_IOCTL_CMD_START failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_CMD_STOP: + { + INFO("\n %s(): Executing VEN_IOCTL_CMD_STOP", __func__); + result = vid_enc_start_stop(client_ctx, false); + if (!result) { + ERR("setting VEN_IOCTL_CMD_STOP failed\n"); + return -EIO; + } + stop_cmd = 1; + break; + } + case VEN_IOCTL_CMD_PAUSE: + { + INFO("\n %s(): Executing VEN_IOCTL_CMD_PAUSE", __func__); + result = vid_enc_pause_resume(client_ctx, true); + if (!result) { + ERR("setting VEN_IOCTL_CMD_PAUSE failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_CMD_RESUME: + { + INFO("\n %s(): Executing VEN_IOCTL_CMD_RESUME", __func__); + result = vid_enc_pause_resume(client_ctx, false); + if (!result) { + ERR("setting VEN_IOCTL_CMD_RESUME failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_RECON_BUFFER: + { + struct venc_recon_addr venc_recon; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_SET_RECON_BUFFER\n"); + if (copy_from_user(&venc_recon, venc_msg.in, + sizeof(venc_recon))) + return -EFAULT; + result = vid_enc_set_recon_buffers(client_ctx, + &venc_recon); + if (!result) { + ERR("setting VEN_IOCTL_SET_RECON_BUFFER failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_FREE_RECON_BUFFER: + { + DBG("VEN_IOCTL_FREE_RECON_BUFFER\n"); + result = vid_enc_free_recon_buffers(client_ctx); + if (!result) { + ERR("VEN_IOCTL_FREE_RECON_BUFFER failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_GET_RECON_BUFFER_SIZE: + { + struct venc_recon_buff_size venc_recon_size; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_GET_RECON_BUFFER_SIZE\n"); + if (copy_from_user(&venc_recon_size, venc_msg.out, + sizeof(venc_recon_size))) + return -EFAULT; + result = vid_enc_get_recon_buffer_size(client_ctx, + &venc_recon_size); + if (result) { + if (copy_to_user(venc_msg.out, &venc_recon_size, + sizeof(venc_recon_size))) + return -EFAULT; + } else { + ERR("setting VEN_IOCTL_GET_RECON_BUFFER_SIZE" + "failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_QP_RANGE: + case VEN_IOCTL_GET_QP_RANGE: + { + struct venc_qprange qprange; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_G(S)ET_QP_RANGE\n"); + if (cmd == VEN_IOCTL_SET_QP_RANGE) { + if (copy_from_user(&qprange, venc_msg.in, + sizeof(qprange))) + return -EFAULT; + result = vid_enc_set_get_qprange(client_ctx, + &qprange, true); + } else { + result = vid_enc_set_get_qprange(client_ctx, + &qprange, false); + if (result) { + if (copy_to_user(venc_msg.out, &qprange, + sizeof(qprange))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_G(S)ET_QP_RANGE failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_HEC: + case VEN_IOCTL_GET_HEC: + { + struct venc_headerextension headerextension; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_(G)SET_HEC\n"); + if (cmd == VEN_IOCTL_SET_HEC) { + if (copy_from_user(&headerextension, venc_msg.in, + sizeof(headerextension))) + return -EFAULT; + + result = vid_enc_set_get_headerextension(client_ctx, + &headerextension, true); + } else { + result = vid_enc_set_get_headerextension(client_ctx, + &headerextension, false); + if (result) { + if (copy_to_user(venc_msg.out, &headerextension, + sizeof(headerextension))) + return -EFAULT; + } + } + + if (!result) { + ERR("setting VEN_IOCTL_(G)SET_HEC failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_TARGET_BITRATE: + case VEN_IOCTL_GET_TARGET_BITRATE: + { + struct venc_targetbitrate targetbitrate; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_(G)SET_TARGET_BITRATE\n"); + if (cmd == VEN_IOCTL_SET_TARGET_BITRATE) { + if (copy_from_user(&targetbitrate, venc_msg.in, + sizeof(targetbitrate))) + return -EFAULT; + + result = vid_enc_set_get_bitrate(client_ctx, + &targetbitrate, true); + } else { + result = vid_enc_set_get_bitrate(client_ctx, + &targetbitrate, false); + if (result) { + if (copy_to_user(venc_msg.out, &targetbitrate, + sizeof(targetbitrate))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_(G)SET_TARGET_BITRATE failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_FRAME_RATE: + case VEN_IOCTL_GET_FRAME_RATE: + { + struct venc_framerate framerate; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_(G)SET_FRAME_RATE\n"); + if (cmd == VEN_IOCTL_SET_FRAME_RATE) { + if (copy_from_user(&framerate, venc_msg.in, + sizeof(framerate))) + return -EFAULT; + result = vid_enc_set_get_framerate(client_ctx, + &framerate, true); + } else { + result = vid_enc_set_get_framerate(client_ctx, + &framerate, false); + if (result) { + if (copy_to_user(venc_msg.out, &framerate, + sizeof(framerate))) + return -EFAULT; + } + } + + if (!result) { + ERR("VEN_IOCTL_(G)SET_FRAME_RATE failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_VOP_TIMING_CFG: + case VEN_IOCTL_GET_VOP_TIMING_CFG: + { + struct venc_voptimingcfg voptimingcfg; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("VEN_IOCTL_(G)SET_VOP_TIMING_CFG\n"); + if (cmd == VEN_IOCTL_SET_VOP_TIMING_CFG) { + if (copy_from_user(&voptimingcfg, venc_msg.in, + sizeof(voptimingcfg))) + return -EFAULT; + result = vid_enc_set_get_voptimingcfg(client_ctx, + &voptimingcfg, true); + } else { + result = vid_enc_set_get_voptimingcfg(client_ctx, + &voptimingcfg, false); + if (result) { + if (copy_to_user(venc_msg.out, &voptimingcfg, + sizeof(voptimingcfg))) + return -EFAULT; + } + } + if (!result) { + ERR("VEN_IOCTL_(G)SET_VOP_TIMING_CFG failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_RATE_CTRL_CFG: + case VEN_IOCTL_GET_RATE_CTRL_CFG: + { + struct venc_ratectrlcfg ratectrlcfg; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_(G)SET_RATE_CTRL_CFG\n"); + if (cmd == VEN_IOCTL_SET_RATE_CTRL_CFG) { + if (copy_from_user(&ratectrlcfg, venc_msg.in, + sizeof(ratectrlcfg))) + return -EFAULT; + + result = vid_enc_set_get_ratectrlcfg(client_ctx, + &ratectrlcfg, true); + } else { + result = vid_enc_set_get_ratectrlcfg(client_ctx, + &ratectrlcfg, false); + if (result) { + if (copy_to_user(venc_msg.out, &ratectrlcfg, + sizeof(ratectrlcfg))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_(G)SET_RATE_CTRL_CFG failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_MULTI_SLICE_CFG: + case VEN_IOCTL_GET_MULTI_SLICE_CFG: + { + struct venc_multiclicecfg multiclicecfg; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_(G)SET_MULTI_SLICE_CFG\n"); + if (cmd == VEN_IOCTL_SET_MULTI_SLICE_CFG) { + if (copy_from_user(&multiclicecfg, venc_msg.in, + sizeof(multiclicecfg))) + return -EFAULT; + + result = vid_enc_set_get_multiclicecfg(client_ctx, + &multiclicecfg, true); + } else { + result = vid_enc_set_get_multiclicecfg(client_ctx, + &multiclicecfg, false); + if (result) { + if (copy_to_user(venc_msg.out, &multiclicecfg, + sizeof(multiclicecfg))) + return -EFAULT; + } + } + if (!result) { + ERR("VEN_IOCTL_(G)SET_MULTI_SLICE_CFG failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_INTRA_REFRESH: + case VEN_IOCTL_GET_INTRA_REFRESH: + { + struct venc_intrarefresh intrarefresh; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_(G)SET_INTRA_REFRESH\n"); + if (cmd == VEN_IOCTL_SET_INTRA_REFRESH) { + if (copy_from_user(&intrarefresh, venc_msg.in, + sizeof(intrarefresh))) + return -EFAULT; + result = vid_enc_set_get_intrarefresh(client_ctx, + &intrarefresh, true); + } else { + result = vid_enc_set_get_intrarefresh(client_ctx, + &intrarefresh, false); + if (result) { + if (copy_to_user(venc_msg.out, &intrarefresh, + sizeof(intrarefresh))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_SET_INTRA_REFRESH failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_DEBLOCKING_CFG: + case VEN_IOCTL_GET_DEBLOCKING_CFG: + { + struct venc_dbcfg dbcfg; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("VEN_IOCTL_(G)SET_DEBLOCKING_CFG\n"); + if (cmd == VEN_IOCTL_SET_DEBLOCKING_CFG) { + if (copy_from_user(&dbcfg, venc_msg.in, + sizeof(dbcfg))) + return -EFAULT; + result = vid_enc_set_get_dbcfg(client_ctx, + &dbcfg, true); + } else { + result = vid_enc_set_get_dbcfg(client_ctx, + &dbcfg, false); + if (result) { + if (copy_to_user(venc_msg.out, &dbcfg, + sizeof(dbcfg))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_SET_DEBLOCKING_CFG failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_ENTROPY_CFG: + case VEN_IOCTL_GET_ENTROPY_CFG: + { + struct venc_entropycfg entropy_cfg; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_(G)SET_ENTROPY_CFG\n"); + if (cmd == VEN_IOCTL_SET_ENTROPY_CFG) { + if (copy_from_user(&entropy_cfg, venc_msg.in, + sizeof(entropy_cfg))) + return -EFAULT; + result = vid_enc_set_get_entropy_cfg(client_ctx, + &entropy_cfg, true); + } else { + result = vid_enc_set_get_entropy_cfg(client_ctx, + &entropy_cfg, false); + if (result) { + if (copy_to_user(venc_msg.out, &entropy_cfg, + sizeof(entropy_cfg))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_(G)SET_ENTROPY_CFG failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_GET_SEQUENCE_HDR: + { + struct venc_seqheader seq_header, seq_header_user; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("VEN_IOCTL_GET_SEQUENCE_HDR\n"); + if (copy_from_user(&seq_header_user, venc_msg.in, + sizeof(seq_header_user))) + return -EFAULT; + seq_header.hdrbufptr = NULL; + result = vid_enc_get_sequence_header(client_ctx, + &seq_header); + if (result && ((copy_to_user(seq_header_user.hdrbufptr, + seq_header.hdrbufptr, seq_header.hdrlen)) || + (copy_to_user(&seq_header_user.hdrlen, + &seq_header.hdrlen, + sizeof(seq_header.hdrlen))))) + result = false; + kfree(seq_header.hdrbufptr); + if (!result) + return -EIO; + break; + } + case VEN_IOCTL_CMD_REQUEST_IFRAME: + { + result = vid_enc_request_iframe(client_ctx); + if (!result) { + ERR("setting VEN_IOCTL_CMD_REQUEST_IFRAME failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_INTRA_PERIOD: + case VEN_IOCTL_GET_INTRA_PERIOD: + { + struct venc_intraperiod intraperiod; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_(G)SET_INTRA_PERIOD\n"); + if (cmd == VEN_IOCTL_SET_INTRA_PERIOD) { + if (copy_from_user(&intraperiod, venc_msg.in, + sizeof(intraperiod))) + return -EFAULT; + result = vid_enc_set_get_intraperiod(client_ctx, + &intraperiod, true); + } else { + result = vid_enc_set_get_intraperiod(client_ctx, + &intraperiod, false); + if (result) { + if (copy_to_user(venc_msg.out, &intraperiod, + sizeof(intraperiod))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_(G)SET_INTRA_PERIOD failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_SESSION_QP: + case VEN_IOCTL_GET_SESSION_QP: + { + struct venc_sessionqp session_qp; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("VEN_IOCTL_(G)SET_SESSION_QP\n"); + if (cmd == VEN_IOCTL_SET_SESSION_QP) { + if (copy_from_user(&session_qp, venc_msg.in, + sizeof(session_qp))) + return -EFAULT; + result = vid_enc_set_get_session_qp(client_ctx, + &session_qp, true); + } else { + result = vid_enc_set_get_session_qp(client_ctx, + &session_qp, false); + if (result) { + if (copy_to_user(venc_msg.out, &session_qp, + sizeof(session_qp))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_(G)SET_SESSION_QP failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_PROFILE_LEVEL: + case VEN_IOCTL_GET_PROFILE_LEVEL: + { + struct ven_profilelevel profile_level; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("VEN_IOCTL_(G)SET_PROFILE_LEVEL\n"); + if (cmd == VEN_IOCTL_SET_PROFILE_LEVEL) { + if (copy_from_user(&profile_level, venc_msg.in, + sizeof(profile_level))) + return -EFAULT; + result = vid_enc_set_get_profile_level(client_ctx, + &profile_level, true); + } else { + result = vid_enc_set_get_profile_level(client_ctx, + &profile_level, false); + if (result) { + if (copy_to_user(venc_msg.out, + &profile_level, sizeof(profile_level))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_SET_PROFILE_LEVEL failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_CODEC_PROFILE: + case VEN_IOCTL_GET_CODEC_PROFILE: + { + struct venc_profile profile; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("VEN_IOCTL_(G)SET_CODEC_PROFILE\n"); + if (cmd == VEN_IOCTL_SET_CODEC_PROFILE) { + if (copy_from_user(&profile, venc_msg.in, + sizeof(profile))) + return -EFAULT; + result = vid_enc_set_get_profile(client_ctx, + &profile, true); + } else { + result = vid_enc_set_get_profile(client_ctx, + &profile, false); + if (result) { + if (copy_to_user(venc_msg.out, &profile, + sizeof(profile))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_SET_CODEC_PROFILE failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_SHORT_HDR: + case VEN_IOCTL_GET_SHORT_HDR: + { + struct venc_switch encoder_switch; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + DBG("Getting VEN_IOCTL_(G)SET_SHORT_HDR\n"); + if (cmd == VEN_IOCTL_SET_SHORT_HDR) { + if (copy_from_user(&encoder_switch, venc_msg.in, + sizeof(encoder_switch))) + return -EFAULT; + + result = vid_enc_set_get_short_header(client_ctx, + &encoder_switch, true); + } else { + result = vid_enc_set_get_short_header(client_ctx, + &encoder_switch, false); + if (result) { + if (copy_to_user(venc_msg.out, &encoder_switch, + sizeof(encoder_switch))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_(G)SET_SHORT_HDR failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_BASE_CFG: + case VEN_IOCTL_GET_BASE_CFG: + { + struct venc_basecfg base_config; + DBG("VEN_IOCTL_SET_BASE_CFG\n"); + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + if (cmd == VEN_IOCTL_SET_BASE_CFG) { + if (copy_from_user(&base_config, venc_msg.in, + sizeof(base_config))) + return -EFAULT; + result = vid_enc_set_get_base_cfg(client_ctx, + &base_config, true); + } else { + result = vid_enc_set_get_base_cfg(client_ctx, + &base_config, false); + if (result) { + if (copy_to_user(venc_msg.out, &base_config, + sizeof(base_config))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_SET_BASE_CFG failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_SET_LIVE_MODE: + case VEN_IOCTL_GET_LIVE_MODE: + { + struct venc_switch encoder_switch; + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + + DBG("Getting VEN_IOCTL_(G)SET_LIVE_MODE\n"); + if (cmd == VEN_IOCTL_SET_LIVE_MODE) { + if (copy_from_user(&encoder_switch, venc_msg.in, + sizeof(encoder_switch))) + return -EFAULT; + result = vid_enc_set_get_live_mode(client_ctx, + &encoder_switch, true); + } else { + result = vid_enc_set_get_live_mode(client_ctx, + &encoder_switch, false); + if (result) { + if (copy_to_user(venc_msg.out, &encoder_switch, + sizeof(encoder_switch))) + return -EFAULT; + } + } + if (!result) { + ERR("setting VEN_IOCTL_(G)SET_LIVE_MODE failed\n"); + return -EIO; + } + break; + } + case VEN_IOCTL_GET_NUMBER_INSTANCES: + { + DBG("VEN_IOCTL_GET_NUMBER_INSTANCES\n"); + if (copy_from_user(&venc_msg, arg, sizeof(venc_msg))) + return -EFAULT; + if (copy_to_user(venc_msg.out, + &vid_enc_device_p->num_clients, sizeof(u32))) + return -EFAULT; + break; + } + case VEN_IOCTL_SET_AC_PREDICTION: + case VEN_IOCTL_GET_AC_PREDICTION: + case VEN_IOCTL_SET_RVLC: + case VEN_IOCTL_GET_RVLC: + case VEN_IOCTL_SET_ROTATION: + case VEN_IOCTL_GET_ROTATION: + case VEN_IOCTL_SET_DATA_PARTITION: + case VEN_IOCTL_GET_DATA_PARTITION: + case VEN_IOCTL_GET_CAPABILITY: + default: + ERR("%s(): Unsupported ioctl %d\n", __func__, cmd); + return -ENOTTY; + + break; + } + return 0; +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Video encoder driver"); +MODULE_VERSION("1.0"); + +module_init(vid_enc_init); +module_exit(vid_enc_exit); diff --git a/drivers/video/msm/vidc/common/enc/venc_internal.c b/drivers/video/msm/vidc/common/enc/venc_internal.c new file mode 100644 index 00000000000..d202d810509 --- /dev/null +++ b/drivers/video/msm/vidc/common/enc/venc_internal.c @@ -0,0 +1,1784 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vidc_type.h" +#include "vcd_api.h" +#include "venc_internal.h" +#include "vidc_init.h" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +#define ERR(x...) printk(KERN_ERR x) + +u32 vid_enc_set_get_base_cfg(struct video_client_ctx *client_ctx, + struct venc_basecfg *base_config, u32 set_flag) +{ + struct venc_targetbitrate venc_bitrate; + struct venc_framerate frame_rate; + u32 current_codec; + + if (!client_ctx || !base_config) + return false; + + if (!vid_enc_set_get_codec(client_ctx, ¤t_codec, false)) + return false; + + DBG("%s(): Current Codec Type = %u\n", __func__, current_codec); + if (current_codec != base_config->codectype) { + if (!vid_enc_set_get_codec(client_ctx, + (u32 *)&base_config->codectype, set_flag)) + return false; + } + + if (!vid_enc_set_get_inputformat(client_ctx, + (u32 *)&base_config->inputformat, set_flag)) + return false; + + if (!vid_enc_set_get_framesize(client_ctx, + (u32 *)&base_config->input_height, + (u32 *)&base_config->input_width, set_flag)) + return false; + + if (set_flag) + venc_bitrate.target_bitrate = base_config->targetbitrate; + + if (!vid_enc_set_get_bitrate(client_ctx, &venc_bitrate, set_flag)) + return false; + + if (!set_flag) + base_config->targetbitrate = venc_bitrate.target_bitrate; + + if (set_flag) { + frame_rate.fps_denominator = base_config->fps_den; + frame_rate.fps_numerator = base_config->fps_num; + } + + if (!vid_enc_set_get_framerate(client_ctx, &frame_rate, set_flag)) + return false; + + if (!set_flag) { + base_config->fps_den = frame_rate.fps_denominator; + base_config->fps_num = frame_rate.fps_numerator; + } + + return true; +} + +u32 vid_enc_set_get_inputformat(struct video_client_ctx *client_ctx, + u32 *input_format, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_buffer_format format; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !input_format) + return false; + + vcd_property_hdr.prop_id = VCD_I_BUFFER_FORMAT; + vcd_property_hdr.sz = + sizeof(struct vcd_property_buffer_format); + + if (set_flag) { + switch (*input_format) { + case VEN_INPUTFMT_NV12: + format.buffer_format = VCD_BUFFER_FORMAT_NV12; + break; + case VEN_INPUTFMT_NV12_16M2KA: + format.buffer_format = + VCD_BUFFER_FORMAT_NV12_16M2KA; + break; + default: + status = false; + break; + } + + if (status) { + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &format); + if (vcd_status) { + status = false; + ERR("%s(): Set VCD_I_BUFFER_FORMAT Failed\n", + __func__); + } + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &format); + + if (vcd_status) { + status = false; + ERR("%s(): Get VCD_I_BUFFER_FORMAT Failed\n", __func__); + } else { + switch (format.buffer_format) { + case VCD_BUFFER_FORMAT_NV12: + *input_format = VEN_INPUTFMT_NV12; + break; + case VCD_BUFFER_FORMAT_TILE_4x2: + *input_format = VEN_INPUTFMT_NV21; + break; + default: + status = false; + break; + } + } + } + return status; +} + +u32 vid_enc_set_get_codec(struct video_client_ctx *client_ctx, u32 *codec, + u32 set_flag) +{ + struct vcd_property_codec vcd_property_codec; + struct vcd_property_hdr vcd_property_hdr; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !codec) + return false; + + vcd_property_hdr.prop_id = VCD_I_CODEC; + vcd_property_hdr.sz = sizeof(struct vcd_property_codec); + + if (set_flag) { + switch (*codec) { + case VEN_CODEC_MPEG4: + vcd_property_codec.codec = VCD_CODEC_MPEG4; + break; + case VEN_CODEC_H263: + vcd_property_codec.codec = VCD_CODEC_H263; + break; + case VEN_CODEC_H264: + vcd_property_codec.codec = VCD_CODEC_H264; + break; + default: + status = false; + break; + } + + if (status) { + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_codec); + if (vcd_status) { + status = false; + ERR("%s(): Set VCD_I_CODEC Failed\n", __func__); + } + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_property_codec); + + if (vcd_status) { + status = false; + ERR("%s(): Get VCD_I_CODEC Failed\n", + __func__); + } else { + switch (vcd_property_codec.codec) { + case VCD_CODEC_H263: + *codec = VEN_CODEC_H263; + break; + case VCD_CODEC_H264: + *codec = VEN_CODEC_H264; + break; + case VCD_CODEC_MPEG4: + *codec = VEN_CODEC_MPEG4; + break; + case VCD_CODEC_DIVX_3: + case VCD_CODEC_DIVX_4: + case VCD_CODEC_DIVX_5: + case VCD_CODEC_DIVX_6: + case VCD_CODEC_MPEG1: + case VCD_CODEC_MPEG2: + case VCD_CODEC_VC1: + case VCD_CODEC_VC1_RCV: + case VCD_CODEC_XVID: + default: + status = false; + break; + } + } + } + return status; +} + +u32 vid_enc_set_get_framesize(struct video_client_ctx *client_ctx, + u32 *height, u32 *width, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_frame_size frame_size; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !height || !width) + return false; + + vcd_property_hdr.prop_id = VCD_I_FRAME_SIZE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_frame_size); + + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &frame_size); + + if (vcd_status) { + ERR("%s(): Get VCD_I_FRAME_SIZE Failed\n", + __func__); + return false; + } + if (set_flag) { + if (frame_size.height != *height || + frame_size.width != *width) { + DBG("%s(): ENC Set Size (%d x %d)\n", + __func__, *height, *width); + frame_size.height = *height; + frame_size.width = *width; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &frame_size); + if (vcd_status) { + ERR("%s(): Set VCD_I_FRAME_SIZE Failed\n", + __func__); + return false; + } + } + } else { + *height = frame_size.height; + *width = frame_size.width; + } + return true; +} + +u32 vid_enc_set_get_bitrate(struct video_client_ctx *client_ctx, + struct venc_targetbitrate *venc_bitrate, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_target_bitrate bit_rate; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !venc_bitrate) + return false; + + vcd_property_hdr.prop_id = VCD_I_TARGET_BITRATE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_target_bitrate); + if (set_flag) { + bit_rate.target_bitrate = venc_bitrate->target_bitrate; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &bit_rate); + + if (vcd_status) { + ERR("%s(): Set VCD_I_TARGET_BITRATE Failed\n", + __func__); + return false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &bit_rate); + + if (vcd_status) { + ERR("%s(): Get VCD_I_TARGET_BITRATE Failed\n", + __func__); + return false; + } + venc_bitrate->target_bitrate = bit_rate.target_bitrate; + } + return true; +} + +u32 vid_enc_set_get_framerate(struct video_client_ctx *client_ctx, + struct venc_framerate *frame_rate, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_frame_rate vcd_frame_rate; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !frame_rate) + return false; + + vcd_property_hdr.prop_id = VCD_I_FRAME_RATE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_frame_rate); + + if (set_flag) { + vcd_frame_rate.fps_denominator = frame_rate->fps_denominator; + vcd_frame_rate.fps_numerator = frame_rate->fps_numerator; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_frame_rate); + + if (vcd_status) { + ERR("%s(): Set VCD_I_FRAME_RATE Failed\n", + __func__); + return false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &vcd_frame_rate); + + if (vcd_status) { + ERR("%s(): Get VCD_I_FRAME_RATE Failed\n", + __func__); + return false; + } + frame_rate->fps_denominator = vcd_frame_rate.fps_denominator; + frame_rate->fps_numerator = vcd_frame_rate.fps_numerator; + } + return true; +} + +u32 vid_enc_set_get_live_mode(struct video_client_ctx *client_ctx, + struct venc_switch *encoder_switch, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_live live_mode; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx) + return false; + + vcd_property_hdr.prop_id = VCD_I_LIVE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_live); + + if (set_flag) { + live_mode.live = 1; + if (!encoder_switch->status) + live_mode.live = 0; + + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &live_mode); + if (vcd_status) { + ERR("%s(): Set VCD_I_LIVE Failed\n", + __func__); + return false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &live_mode); + + if (vcd_status) { + ERR("%s(): Get VCD_I_LIVE Failed\n", + __func__); + return false; + } else { + encoder_switch->status = 1; + if (!live_mode.live) + encoder_switch->status = 0; + } + } + return true; +} + +u32 vid_enc_set_get_short_header(struct video_client_ctx *client_ctx, + struct venc_switch *encoder_switch, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_short_header short_header; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !encoder_switch) + return false; + + vcd_property_hdr.prop_id = VCD_I_SHORT_HEADER; + vcd_property_hdr.sz = + sizeof(struct vcd_property_short_header); + + if (set_flag) { + short_header.short_header = (u32) encoder_switch->status; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &short_header); + + if (vcd_status) { + ERR("%s(): Set VCD_I_SHORT_HEADER Failed\n", + __func__); + return false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &short_header); + + if (vcd_status) { + ERR("%s(): Get VCD_I_SHORT_HEADER Failed\n", + __func__); + return false; + } else { + encoder_switch->status = + (u8) short_header.short_header; + } + } + return true; +} + +u32 vid_enc_set_get_profile(struct video_client_ctx *client_ctx, + struct venc_profile *profile, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_profile profile_type; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !profile) + return false; + + vcd_property_hdr.prop_id = VCD_I_PROFILE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_profile); + + if (set_flag) { + switch (profile->profile) { + case VEN_PROFILE_MPEG4_SP: + profile_type.profile = VCD_PROFILE_MPEG4_SP; + break; + case VEN_PROFILE_MPEG4_ASP: + profile_type.profile = VCD_PROFILE_MPEG4_ASP; + break; + case VEN_PROFILE_H264_BASELINE: + profile_type.profile = VCD_PROFILE_H264_BASELINE; + break; + case VEN_PROFILE_H264_MAIN: + profile_type.profile = VCD_PROFILE_H264_MAIN; + break; + case VEN_PROFILE_H264_HIGH: + profile_type.profile = VCD_PROFILE_H264_HIGH; + break; + case VEN_PROFILE_H263_BASELINE: + profile_type.profile = VCD_PROFILE_H263_BASELINE; + break; + default: + status = false; + break; + } + + if (status) { + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &profile_type); + + if (vcd_status) { + ERR("%s(): Set VCD_I_PROFILE Failed\n", + __func__); + return false; + } + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &profile_type); + + if (vcd_status) { + ERR("%s(): Get VCD_I_PROFILE Failed\n", + __func__); + return false; + } else { + switch (profile_type.profile) { + case VCD_PROFILE_H263_BASELINE: + profile->profile = VEN_PROFILE_H263_BASELINE; + break; + case VCD_PROFILE_H264_BASELINE: + profile->profile = VEN_PROFILE_H264_BASELINE; + break; + case VCD_PROFILE_H264_HIGH: + profile->profile = VEN_PROFILE_H264_HIGH; + break; + case VCD_PROFILE_H264_MAIN: + profile->profile = VEN_PROFILE_H264_MAIN; + break; + case VCD_PROFILE_MPEG4_ASP: + profile->profile = VEN_PROFILE_MPEG4_ASP; + break; + case VCD_PROFILE_MPEG4_SP: + profile->profile = VEN_PROFILE_MPEG4_SP; + break; + default: + status = false; + break; + } + } + } + return status; +} + +u32 vid_enc_set_get_profile_level(struct video_client_ctx *client_ctx, + struct ven_profilelevel *profile_level, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_level level; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !profile_level) + return false; + + vcd_property_hdr.prop_id = VCD_I_LEVEL; + vcd_property_hdr.sz = + sizeof(struct vcd_property_level); + + if (set_flag) { + switch (profile_level->level) { + case VEN_LEVEL_MPEG4_0: + level.level = VCD_LEVEL_MPEG4_0; + break; + case VEN_LEVEL_MPEG4_1: + level.level = VCD_LEVEL_MPEG4_1; + break; + case VEN_LEVEL_MPEG4_2: + level.level = VCD_LEVEL_MPEG4_2; + break; + case VEN_LEVEL_MPEG4_3: + level.level = VCD_LEVEL_MPEG4_3; + break; + case VEN_LEVEL_MPEG4_4: + level.level = VCD_LEVEL_MPEG4_4; + break; + case VEN_LEVEL_MPEG4_5: + level.level = VCD_LEVEL_MPEG4_5; + break; + case VEN_LEVEL_MPEG4_3b: + level.level = VCD_LEVEL_MPEG4_3b; + break; + case VEN_LEVEL_MPEG4_6: + level.level = VCD_LEVEL_MPEG4_6; + break; + case VEN_LEVEL_H264_1: + level.level = VCD_LEVEL_H264_1; + break; + case VEN_LEVEL_H264_1b: + level.level = VCD_LEVEL_H264_1b; + break; + case VEN_LEVEL_H264_1p1: + level.level = VCD_LEVEL_H264_1p1; + break; + case VEN_LEVEL_H264_1p2: + level.level = VCD_LEVEL_H264_1p2; + break; + case VEN_LEVEL_H264_1p3: + level.level = VCD_LEVEL_H264_1p3; + break; + case VEN_LEVEL_H264_2: + level.level = VCD_LEVEL_H264_2; + break; + case VEN_LEVEL_H264_2p1: + level.level = VCD_LEVEL_H264_2p1; + break; + case VEN_LEVEL_H264_2p2: + level.level = VCD_LEVEL_H264_2p2; + break; + case VEN_LEVEL_H264_3: + level.level = VCD_LEVEL_H264_3; + break; + case VEN_LEVEL_H264_3p1: + level.level = VCD_LEVEL_H264_3p1; + break; + case VEN_LEVEL_H264_4: + level.level = VCD_LEVEL_H264_4; + break; + case VEN_LEVEL_H263_10: + level.level = VCD_LEVEL_H263_10; + break; + case VEN_LEVEL_H263_20: + level.level = VCD_LEVEL_H263_20; + break; + case VEN_LEVEL_H263_30: + level.level = VCD_LEVEL_H263_30; + break; + case VEN_LEVEL_H263_40: + level.level = VCD_LEVEL_H263_40; + break; + case VEN_LEVEL_H263_45: + level.level = VCD_LEVEL_H263_45; + break; + case VEN_LEVEL_H263_50: + level.level = VCD_LEVEL_H263_50; + break; + case VEN_LEVEL_H263_60: + level.level = VCD_LEVEL_H263_60; + break; + case VEN_LEVEL_H263_70: + level.level = VCD_LEVEL_H263_70; + break; + default: + status = false; + break; + } + if (status) { + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &level); + + if (vcd_status) { + ERR("%s(): Set VCD_I_LEVEL Failed\n", + __func__); + return false; + } + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &level); + + if (vcd_status) { + ERR("%s(): Get VCD_I_LEVEL Failed\n", + __func__); + return false; + } else { + switch (level.level) { + case VCD_LEVEL_MPEG4_0: + profile_level->level = VEN_LEVEL_MPEG4_0; + break; + case VCD_LEVEL_MPEG4_1: + profile_level->level = VEN_LEVEL_MPEG4_1; + break; + case VCD_LEVEL_MPEG4_2: + profile_level->level = VEN_LEVEL_MPEG4_2; + break; + case VCD_LEVEL_MPEG4_3: + profile_level->level = VEN_LEVEL_MPEG4_3; + break; + case VCD_LEVEL_MPEG4_4: + profile_level->level = VEN_LEVEL_MPEG4_4; + break; + case VCD_LEVEL_MPEG4_5: + profile_level->level = VEN_LEVEL_MPEG4_5; + break; + case VCD_LEVEL_MPEG4_3b: + profile_level->level = VEN_LEVEL_MPEG4_3b; + break; + case VCD_LEVEL_H264_1: + profile_level->level = VEN_LEVEL_H264_1; + break; + case VCD_LEVEL_H264_1b: + profile_level->level = VEN_LEVEL_H264_1b; + break; + case VCD_LEVEL_H264_1p1: + profile_level->level = VEN_LEVEL_H264_1p1; + break; + case VCD_LEVEL_H264_1p2: + profile_level->level = VEN_LEVEL_H264_1p2; + break; + case VCD_LEVEL_H264_1p3: + profile_level->level = VEN_LEVEL_H264_1p3; + break; + case VCD_LEVEL_H264_2: + profile_level->level = VEN_LEVEL_H264_2; + break; + case VCD_LEVEL_H264_2p1: + profile_level->level = VEN_LEVEL_H264_2p1; + break; + case VCD_LEVEL_H264_2p2: + profile_level->level = VEN_LEVEL_H264_2p2; + break; + case VCD_LEVEL_H264_3: + profile_level->level = VEN_LEVEL_H264_3; + break; + case VCD_LEVEL_H264_3p1: + profile_level->level = VEN_LEVEL_H264_3p1; + break; + case VCD_LEVEL_H264_3p2: + status = false; + break; + case VCD_LEVEL_H264_4: + profile_level->level = VEN_LEVEL_H264_4; + break; + case VCD_LEVEL_H263_10: + profile_level->level = VEN_LEVEL_H263_10; + break; + case VCD_LEVEL_H263_20: + profile_level->level = VEN_LEVEL_H263_20; + break; + case VCD_LEVEL_H263_30: + profile_level->level = VEN_LEVEL_H263_30; + break; + case VCD_LEVEL_H263_40: + profile_level->level = VEN_LEVEL_H263_40; + break; + case VCD_LEVEL_H263_45: + profile_level->level = VEN_LEVEL_H263_45; + break; + case VCD_LEVEL_H263_50: + profile_level->level = VEN_LEVEL_H263_50; + break; + case VCD_LEVEL_H263_60: + profile_level->level = VEN_LEVEL_H263_60; + break; + case VCD_LEVEL_H263_70: + status = false; + break; + default: + status = false; + break; + } + } + } + return status; +} + +u32 vid_enc_set_get_session_qp(struct video_client_ctx *client_ctx, + struct venc_sessionqp *session_qp, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_session_qp qp; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !session_qp) + return false; + + vcd_property_hdr.prop_id = VCD_I_SESSION_QP; + vcd_property_hdr.sz = + sizeof(struct vcd_property_session_qp); + + if (set_flag) { + qp.i_frame_qp = session_qp->iframeqp; + qp.p_frame_qp = session_qp->pframqp; + + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &qp); + + if (vcd_status) { + ERR("%s(): Set VCD_I_SESSION_QP Failed\n", + __func__); + return false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &qp); + + if (vcd_status) { + ERR("%s(): Set VCD_I_SESSION_QP Failed\n", + __func__); + return false; + } else { + session_qp->iframeqp = qp.i_frame_qp; + session_qp->pframqp = qp.p_frame_qp; + } + } + return true; +} + +u32 vid_enc_set_get_intraperiod(struct video_client_ctx *client_ctx, + struct venc_intraperiod *intraperiod, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_i_period period; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !intraperiod) + return false; + + vcd_property_hdr.prop_id = VCD_I_INTRA_PERIOD; + vcd_property_hdr.sz = + sizeof(struct vcd_property_i_period); + + if (set_flag) { + period.p_frames = intraperiod->num_pframes; + period.b_frames = intraperiod->num_bframes; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &period); + + if (vcd_status) { + ERR("%s(): Set VCD_I_INTRA_PERIOD Failed\n", + __func__); + return false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &period); + + if (vcd_status) { + ERR("%s(): Get VCD_I_INTRA_PERIOD Failed\n", + __func__); + return false; + } else + intraperiod->num_pframes = period.p_frames; + } + return true; +} + +u32 vid_enc_request_iframe(struct video_client_ctx *client_ctx) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_req_i_frame request; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx) + return false; + + vcd_property_hdr.prop_id = VCD_I_REQ_IFRAME; + vcd_property_hdr.sz = + sizeof(struct vcd_property_req_i_frame); + request.req_i_frame = 1; + + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &request); + + if (vcd_status) { + ERR("%s(): Set VCD_I_REQ_IFRAME Failed\n", + __func__); + return false; + } + return status; +} + +u32 vid_enc_get_sequence_header(struct video_client_ctx *client_ctx, + struct venc_seqheader *seq_header) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_sequence_hdr hdr; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || + !seq_header || !seq_header->bufsize) + return false; + + vcd_property_hdr.prop_id = VCD_I_SEQ_HEADER; + vcd_property_hdr.sz = + sizeof(struct vcd_sequence_hdr); + + hdr.sequence_header = + kzalloc(seq_header->bufsize, GFP_KERNEL); + seq_header->hdrbufptr = hdr.sequence_header; + + if (!hdr.sequence_header) + return false; + hdr.sequence_header_len = seq_header->bufsize; + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &hdr); + + if (vcd_status) { + ERR("%s(): Get VCD_I_SEQ_HEADER Failed\n", + __func__); + status = false; + } + return true; +} + +u32 vid_enc_set_get_entropy_cfg(struct video_client_ctx *client_ctx, + struct venc_entropycfg *entropy_cfg, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_entropy_control control; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !entropy_cfg) + return false; + + vcd_property_hdr.prop_id = VCD_I_ENTROPY_CTRL; + vcd_property_hdr.sz = + sizeof(struct vcd_property_entropy_control); + if (set_flag) { + switch (entropy_cfg->longentropysel) { + case VEN_ENTROPY_MODEL_CAVLC: + control.entropy_sel = VCD_ENTROPY_SEL_CAVLC; + break; + case VEN_ENTROPY_MODEL_CABAC: + control.entropy_sel = VCD_ENTROPY_SEL_CABAC; + break; + default: + status = false; + break; + } + + if (status && entropy_cfg->cabacmodel == + VCD_ENTROPY_SEL_CABAC) { + switch (entropy_cfg->cabacmodel) { + case VEN_CABAC_MODEL_0: + control.cabac_model = + VCD_CABAC_MODEL_NUMBER_0; + break; + case VEN_CABAC_MODEL_1: + control.cabac_model = + VCD_CABAC_MODEL_NUMBER_1; + break; + case VEN_CABAC_MODEL_2: + control.cabac_model = + VCD_CABAC_MODEL_NUMBER_2; + break; + default: + status = false; + break; + } + } + if (status) { + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + if (vcd_status) { + ERR("%s(): Set VCD_I_ENTROPY_CTRL Failed\n", + __func__); + status = false; + } + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + if (vcd_status) { + ERR("%s(): Get VCD_I_ENTROPY_CTRL Failed\n", + __func__); + status = false; + } else { + switch (control.entropy_sel) { + case VCD_ENTROPY_SEL_CABAC: + entropy_cfg->cabacmodel = + VEN_ENTROPY_MODEL_CABAC; + break; + case VCD_ENTROPY_SEL_CAVLC: + entropy_cfg->cabacmodel = + VEN_ENTROPY_MODEL_CAVLC; + break; + default: + status = false; + break; + } + + if (status && control.entropy_sel == + VCD_ENTROPY_SEL_CABAC) { + switch (control.cabac_model) { + case VCD_CABAC_MODEL_NUMBER_0: + entropy_cfg->cabacmodel = + VEN_CABAC_MODEL_0; + break; + case VCD_CABAC_MODEL_NUMBER_1: + entropy_cfg->cabacmodel = + VEN_CABAC_MODEL_1; + break; + case VCD_CABAC_MODEL_NUMBER_2: + entropy_cfg->cabacmodel = + VEN_CABAC_MODEL_2; + break; + default: + status = false; + break; + } + } + } + } + return status; +} + +u32 vid_enc_set_get_dbcfg(struct video_client_ctx *client_ctx, + struct venc_dbcfg *dbcfg, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_db_config control; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !dbcfg) + return false; + + vcd_property_hdr.prop_id = VCD_I_DEBLOCKING; + vcd_property_hdr.sz = + sizeof(struct vcd_property_db_config); + + if (set_flag) { + switch (dbcfg->db_mode) { + case VEN_DB_DISABLE: + control.db_config = VCD_DB_DISABLE; + break; + case VEN_DB_ALL_BLKG_BNDRY: + control.db_config = VCD_DB_ALL_BLOCKING_BOUNDARY; + break; + case VEN_DB_SKIP_SLICE_BNDRY: + control.db_config = VCD_DB_SKIP_SLICE_BOUNDARY; + break; + default: + status = false; + break; + } + + if (status) { + control.slice_alpha_offset = + dbcfg->slicealpha_offset; + control.slice_beta_offset = + dbcfg->slicebeta_offset; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + if (vcd_status) { + ERR("%s(): Set VCD_I_DEBLOCKING Failed\n", + __func__); + status = false; + } + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + if (vcd_status) { + ERR("%s(): Get VCD_I_DEBLOCKING Failed\n", + __func__); + status = false; + } else { + switch (control.db_config) { + case VCD_DB_ALL_BLOCKING_BOUNDARY: + dbcfg->db_mode = VEN_DB_ALL_BLKG_BNDRY; + break; + case VCD_DB_DISABLE: + dbcfg->db_mode = VEN_DB_DISABLE; + break; + case VCD_DB_SKIP_SLICE_BOUNDARY: + dbcfg->db_mode = VEN_DB_SKIP_SLICE_BNDRY; + break; + default: + status = false; + break; + } + dbcfg->slicealpha_offset = + control.slice_alpha_offset; + dbcfg->slicebeta_offset = + control.slice_beta_offset; + } + } + return status; +} + +u32 vid_enc_set_get_intrarefresh(struct video_client_ctx *client_ctx, + struct venc_intrarefresh *intrarefresh, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_intra_refresh_mb_number control; + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !intrarefresh) + return false; + + vcd_property_hdr.prop_id = VCD_I_INTRA_REFRESH; + vcd_property_hdr.sz = + sizeof(struct vcd_property_intra_refresh_mb_number); + + if (set_flag) { + control.cir_mb_number = intrarefresh->mbcount; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + if (vcd_status) { + ERR("%s(): Set VCD_I_INTRA_REFRESH Failed\n", + __func__); + return false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + if (vcd_status) { + ERR("%s(): Set VCD_I_INTRA_REFRESH Failed\n", + __func__); + return false; + } else + intrarefresh->mbcount = control.cir_mb_number; + } + return true; +} + +u32 vid_enc_set_get_multiclicecfg(struct video_client_ctx *client_ctx, + struct venc_multiclicecfg *multiclicecfg, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_multi_slice control; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !multiclicecfg) + return false; + + vcd_property_hdr.prop_id = VCD_I_MULTI_SLICE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_multi_slice); + + if (set_flag) { + switch (multiclicecfg->mslice_mode) { + case VEN_MSLICE_OFF: + control.m_slice_sel = + VCD_MSLICE_OFF; + break; + case VEN_MSLICE_CNT_MB: + control.m_slice_sel = + VCD_MSLICE_BY_MB_COUNT; + break; + case VEN_MSLICE_CNT_BYTE: + control.m_slice_sel = + VCD_MSLICE_BY_BYTE_COUNT; + break; + case VEN_MSLICE_GOB: + control.m_slice_sel = + VCD_MSLICE_BY_GOB; + break; + default: + status = false; + break; + } + + if (status) { + control.m_slice_size = + multiclicecfg->mslice_size; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + if (vcd_status) { + ERR("%s(): Set VCD_I_MULTI_SLICE Failed\n", + __func__); + status = false; + } + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + if (vcd_status) { + ERR("%s(): Get VCD_I_MULTI_SLICE Failed\n", + __func__); + status = false; + } else { + multiclicecfg->mslice_size = + control.m_slice_size; + switch (control.m_slice_sel) { + case VCD_MSLICE_OFF: + multiclicecfg->mslice_mode = VEN_MSLICE_OFF; + break; + case VCD_MSLICE_BY_MB_COUNT: + multiclicecfg->mslice_mode = VEN_MSLICE_CNT_MB; + break; + case VCD_MSLICE_BY_BYTE_COUNT: + multiclicecfg->mslice_mode = + VEN_MSLICE_CNT_BYTE; + break; + case VCD_MSLICE_BY_GOB: + multiclicecfg->mslice_mode = + VEN_MSLICE_GOB; + break; + default: + status = false; + break; + } + } + } + return status; +} + +u32 vid_enc_set_get_ratectrlcfg(struct video_client_ctx *client_ctx, + struct venc_ratectrlcfg *ratectrlcfg, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_rate_control control; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !ratectrlcfg) + return false; + + vcd_property_hdr.prop_id = VCD_I_RATE_CONTROL; + vcd_property_hdr.sz = + sizeof(struct vcd_property_rate_control); + + if (set_flag) { + switch (ratectrlcfg->rcmode) { + case VEN_RC_OFF: + control.rate_control = VCD_RATE_CONTROL_OFF; + break; + case VEN_RC_CBR_VFR: + control.rate_control = VCD_RATE_CONTROL_CBR_VFR; + break; + case VEN_RC_VBR_CFR: + control.rate_control = VCD_RATE_CONTROL_VBR_CFR; + break; + case VEN_RC_VBR_VFR: + control.rate_control = VCD_RATE_CONTROL_VBR_VFR; + break; + case VEN_RC_CBR_CFR: + control.rate_control = VCD_RATE_CONTROL_CBR_CFR; + break; + default: + status = false; + break; + } + + if (status) { + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + if (vcd_status) { + ERR("%s(): Set VCD_I_RATE_CONTROL Failed\n", + __func__); + status = false; + } + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + if (vcd_status) { + ERR("%s(): Get VCD_I_RATE_CONTROL Failed\n", + __func__); + status = false; + } else { + switch (control.rate_control) { + case VCD_RATE_CONTROL_OFF: + ratectrlcfg->rcmode = VEN_RC_OFF; + break; + case VCD_RATE_CONTROL_CBR_VFR: + ratectrlcfg->rcmode = VEN_RC_CBR_VFR; + break; + case VCD_RATE_CONTROL_VBR_CFR: + ratectrlcfg->rcmode = VEN_RC_VBR_CFR; + break; + case VCD_RATE_CONTROL_VBR_VFR: + ratectrlcfg->rcmode = VEN_RC_VBR_VFR; + break; + case VCD_RATE_CONTROL_CBR_CFR: + ratectrlcfg->rcmode = VEN_RC_CBR_CFR; + break; + default: + status = false; + break; + } + } + } + return status; +} + +u32 vid_enc_set_get_voptimingcfg(struct video_client_ctx *client_ctx, + struct venc_voptimingcfg *voptimingcfg, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_vop_timing control; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !voptimingcfg) + return false; + + vcd_property_hdr.prop_id = VCD_I_VOP_TIMING; + vcd_property_hdr.sz = + sizeof(struct vcd_property_vop_timing); + + if (set_flag) { + control.vop_time_resolution = + voptimingcfg->voptime_resolution; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + if (vcd_status) { + ERR("%s(): Set VCD_I_VOP_TIMING Failed\n", + __func__); + status = false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + if (vcd_status) { + ERR("%s(): Get VCD_I_VOP_TIMING Failed\n", + __func__); + status = false; + } else + voptimingcfg->voptime_resolution = + control.vop_time_resolution; + } + return status; +} + +u32 vid_enc_set_get_headerextension(struct video_client_ctx *client_ctx, + struct venc_headerextension *headerextension, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + u32 control; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !headerextension) + return false; + + vcd_property_hdr.prop_id = VCD_I_HEADER_EXTENSION; + vcd_property_hdr.sz = sizeof(u32); + + if (set_flag) { + control = headerextension->header_extension; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + if (vcd_status) { + ERR("%s(): Set VCD_I_HEADER_EXTENSION Failed\n", + __func__); + status = false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + if (vcd_status) { + ERR("%s(): Get VCD_I_HEADER_EXTENSION Failed\n", + __func__); + status = false; + } else { + headerextension->header_extension = control; + } + } + return status; +} + +u32 vid_enc_set_get_qprange(struct video_client_ctx *client_ctx, + struct venc_qprange *qprange, u32 set_flag) +{ + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_qp_range control; + u32 vcd_status = VCD_ERR_FAIL; + u32 status = true; + + if (!client_ctx || !qprange) + return false; + + vcd_property_hdr.prop_id = VCD_I_QP_RANGE; + vcd_property_hdr.sz = + sizeof(struct vcd_property_qp_range); + + if (set_flag) { + control.max_qp = qprange->maxqp; + control.min_qp = qprange->minqp; + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + if (vcd_status) { + ERR("%s(): Set VCD_I_QP_RANGE Failed\n", + __func__); + status = false; + } + } else { + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + if (vcd_status) { + ERR("%s(): Get VCD_I_QP_RANGE Failed\n", + __func__); + status = false; + } else { + qprange->maxqp = control.max_qp; + qprange->minqp = control.min_qp; + } + } + return status; +} + +u32 vid_enc_start_stop(struct video_client_ctx *client_ctx, u32 start) +{ + u32 vcd_status; + + if (!client_ctx) + return false; + + if (start) { + vcd_status = vcd_encode_start(client_ctx->vcd_handle); + + if (vcd_status) { + ERR("%s(): vcd_encode_start failed." + " vcd_status = %u\n", __func__, vcd_status); + return false; + } + } else { + vcd_status = vcd_stop(client_ctx->vcd_handle); + if (vcd_status) { + ERR("%s(): vcd_stop failed. vcd_status = %u\n", + __func__, vcd_status); + return false; + } + DBG("Send STOP_DONE message to client = %p\n", + client_ctx); + } + return true; +} + +u32 vid_enc_pause_resume(struct video_client_ctx *client_ctx, u32 pause) +{ + u32 vcd_status; + + if (!client_ctx) + return false; + + if (pause) { + DBG("PAUSE command from client = %p\n", + client_ctx); + vcd_status = vcd_pause(client_ctx->vcd_handle); + } else { + DBG("Resume command from client = %p\n", + client_ctx); + vcd_status = vcd_resume(client_ctx->vcd_handle); + } + + if (vcd_status) + return false; + + return true; +} + +u32 vid_enc_flush(struct video_client_ctx *client_ctx, + struct venc_bufferflush *bufferflush) +{ + u32 status = true, mode, vcd_status; + + if (!client_ctx || !bufferflush) + return false; + + switch (bufferflush->flush_mode) { + case VEN_FLUSH_INPUT: + mode = VCD_FLUSH_INPUT; + break; + case VEN_FLUSH_OUTPUT: + mode = VCD_FLUSH_OUTPUT; + break; + case VEN_FLUSH_ALL: + mode = VCD_FLUSH_ALL; + break; + default: + status = false; + break; + } + if (status) { + vcd_status = vcd_flush(client_ctx->vcd_handle, mode); + if (vcd_status) + status = false; + } + return status; +} + +u32 vid_enc_get_buffer_req(struct video_client_ctx *client_ctx, + struct venc_allocatorproperty *venc_buf_req, u32 input_dir) +{ + enum vcd_buffer_type buffer; + struct vcd_buffer_requirement buffer_req; + u32 status = true; + u32 vcd_status; + + if (!client_ctx || !venc_buf_req) + return false; + + buffer = VCD_BUFFER_OUTPUT; + if (input_dir) + buffer = VCD_BUFFER_INPUT; + + vcd_status = vcd_get_buffer_requirements(client_ctx->vcd_handle, + buffer, &buffer_req); + + if (vcd_status) + status = false; + + if (status) { + venc_buf_req->actualcount = buffer_req.actual_count; + venc_buf_req->alignment = buffer_req.align; + venc_buf_req->datasize = buffer_req.sz; + venc_buf_req->mincount = buffer_req.min_count; + venc_buf_req->maxcount = buffer_req.max_count; + venc_buf_req->alignment = buffer_req.align; + venc_buf_req->bufpoolid = buffer_req.buf_pool_id; + venc_buf_req->suffixsize = 0; + } + return status; +} + +u32 vid_enc_set_buffer_req(struct video_client_ctx *client_ctx, + struct venc_allocatorproperty *venc_buf_req, u32 input_dir) +{ + enum vcd_buffer_type buffer; + struct vcd_buffer_requirement buffer_req; + u32 status = true; + u32 vcd_status; + + if (!client_ctx || !venc_buf_req) + return false; + + buffer = VCD_BUFFER_OUTPUT; + if (input_dir) + buffer = VCD_BUFFER_INPUT; + + buffer_req.actual_count = venc_buf_req->actualcount; + buffer_req.align = venc_buf_req->alignment; + buffer_req.sz = venc_buf_req->datasize; + buffer_req.min_count = venc_buf_req->mincount; + buffer_req.max_count = venc_buf_req->maxcount; + buffer_req.align = venc_buf_req->alignment; + buffer_req.buf_pool_id = 0; + + vcd_status = vcd_set_buffer_requirements(client_ctx->vcd_handle, + buffer, &buffer_req); + + if (vcd_status) + status = false; + return status; +} + +u32 vid_enc_set_buffer(struct video_client_ctx *client_ctx, + struct venc_bufferpayload *buffer_info, + enum venc_buffer_dir buffer) +{ + enum vcd_buffer_type vcd_buffer_t = VCD_BUFFER_INPUT; + enum buffer_dir dir_buffer = BUFFER_TYPE_INPUT; + u32 vcd_status = VCD_ERR_FAIL; + unsigned long kernel_vaddr; + + if (!client_ctx || !buffer_info) + return false; + + if (buffer == VEN_BUFFER_TYPE_OUTPUT) { + dir_buffer = BUFFER_TYPE_OUTPUT; + vcd_buffer_t = VCD_BUFFER_OUTPUT; + } + + /*If buffer cannot be set, ignore */ + if (!vidc_insert_addr_table(client_ctx, dir_buffer, + (unsigned long)buffer_info->pbuffer, + &kernel_vaddr, + buffer_info->fd, + (unsigned long)buffer_info->offset, + VID_ENC_MAX_NUM_OF_BUFF)) { + DBG("%s() : user_virt_addr = %p cannot be set.", + __func__, buffer_info->pbuffer); + return false; + } + + vcd_status = vcd_set_buffer(client_ctx->vcd_handle, + vcd_buffer_t, (u8 *) kernel_vaddr, + buffer_info->sz); + + if (!vcd_status) + return true; + else + return false; +} + +u32 vid_enc_free_buffer(struct video_client_ctx *client_ctx, + struct venc_bufferpayload *buffer_info, + enum venc_buffer_dir buffer) +{ + enum vcd_buffer_type buffer_vcd = VCD_BUFFER_INPUT; + enum buffer_dir dir_buffer = BUFFER_TYPE_INPUT; + u32 vcd_status = VCD_ERR_FAIL; + unsigned long kernel_vaddr; + + if (!client_ctx || !buffer_info) + return false; + + if (buffer == VEN_BUFFER_TYPE_OUTPUT) { + dir_buffer = BUFFER_TYPE_OUTPUT; + buffer_vcd = VCD_BUFFER_OUTPUT; + } + /*If buffer NOT set, ignore */ + if (!vidc_delete_addr_table(client_ctx, dir_buffer, + (unsigned long)buffer_info->pbuffer, + &kernel_vaddr)) { + DBG("%s() : user_virt_addr = %p has not been set.", + __func__, buffer_info->pbuffer); + return true; + } + + vcd_status = vcd_free_buffer(client_ctx->vcd_handle, buffer_vcd, + (u8 *)kernel_vaddr); + + if (!vcd_status) + return true; + else + return false; +} + +u32 vid_enc_encode_frame(struct video_client_ctx *client_ctx, + struct venc_buffer *input_frame_info) +{ + struct vcd_frame_data vcd_input_buffer; + unsigned long kernel_vaddr, phy_addr, user_vaddr; + int pmem_fd; + struct file *file; + s32 buffer_index = -1; + + u32 vcd_status = VCD_ERR_FAIL; + + if (!client_ctx || !input_frame_info) + return false; + + user_vaddr = (unsigned long)input_frame_info->ptrbuffer; + + if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_INPUT, + true, &user_vaddr, &kernel_vaddr, + &phy_addr, &pmem_fd, &file, + &buffer_index)) { + + /* kernel_vaddr is found. send the frame to VCD */ + memset((void *)&vcd_input_buffer, 0, + sizeof(struct vcd_frame_data)); + + vcd_input_buffer.virtual = + (u8 *) (kernel_vaddr + input_frame_info->offset); + + vcd_input_buffer.offset = input_frame_info->offset; + vcd_input_buffer.frm_clnt_data = + (u32) input_frame_info->clientdata; + vcd_input_buffer.ip_frm_tag = + (u32) input_frame_info->clientdata; + vcd_input_buffer.data_len = input_frame_info->len; + vcd_input_buffer.time_stamp = input_frame_info->timestamp; + + /* Rely on VCD using the same flags as OMX */ + vcd_input_buffer.flags = input_frame_info->flags; + + vcd_status = vcd_encode_frame(client_ctx->vcd_handle, + &vcd_input_buffer); + if (!vcd_status) + return true; + else { + ERR("%s(): vcd_encode_frame failed = %u\n", + __func__, vcd_status); + return false; + } + + } else { + ERR("%s(): kernel_vaddr not found\n", + __func__); + return false; + } +} + +u32 vid_enc_fill_output_buffer(struct video_client_ctx *client_ctx, + struct venc_buffer *output_frame_info) +{ + unsigned long kernel_vaddr, phy_addr, user_vaddr; + int pmem_fd; + struct file *file; + s32 buffer_index = -1; + u32 vcd_status = VCD_ERR_FAIL; + + struct vcd_frame_data vcd_frame; + + if (!client_ctx || !output_frame_info) + return false; + + user_vaddr = (unsigned long)output_frame_info->ptrbuffer; + + if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_OUTPUT, + true, &user_vaddr, &kernel_vaddr, + &phy_addr, &pmem_fd, &file, + &buffer_index)) { + + memset((void *)&vcd_frame, 0, + sizeof(struct vcd_frame_data)); + vcd_frame.virtual = (u8 *) kernel_vaddr; + vcd_frame.frm_clnt_data = (u32) output_frame_info->clientdata; + vcd_frame.alloc_len = output_frame_info->sz; + + vcd_status = vcd_fill_output_buffer(client_ctx->vcd_handle, + &vcd_frame); + if (!vcd_status) + return true; + else { + ERR("%s(): vcd_fill_output_buffer failed = %u\n", + __func__, vcd_status); + return false; + } + } else { + ERR("%s(): kernel_vaddr not found\n", __func__); + return false; + } +} +u32 vid_enc_set_recon_buffers(struct video_client_ctx *client_ctx, + struct venc_recon_addr *venc_recon) +{ + u32 vcd_status = VCD_ERR_FAIL; + u32 len; + struct file *file; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_enc_recon_buffer control; + + control.buffer_size = venc_recon->buffer_size; + control.kernel_virtual_addr = NULL; + control.physical_addr = NULL; + control.pmem_fd = venc_recon->pmem_fd; + control.offset = venc_recon->offset; + + if (get_pmem_file(control.pmem_fd, (unsigned long *) + (&(control.physical_addr)), (unsigned long *) + (&control.kernel_virtual_addr), + (unsigned long *) (&len), &file)) { + ERR("%s(): get_pmem_file failed\n", __func__); + return false; + } + put_pmem_file(file); + DBG("Virt: %p, Phys %p, fd: %d", control.kernel_virtual_addr, + control.physical_addr, control.pmem_fd); + + vcd_property_hdr.prop_id = VCD_I_RECON_BUFFERS; + vcd_property_hdr.sz = + sizeof(struct vcd_property_enc_recon_buffer); + + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + if (!vcd_status) { + DBG("vcd_set_property returned success\n"); + return true; + } else { + ERR("%s(): vid_enc_set_recon_buffers failed = %u\n", + __func__, vcd_status); + return false; + } +} + +u32 vid_enc_free_recon_buffers(struct video_client_ctx *client_ctx) +{ + u32 vcd_status = VCD_ERR_FAIL; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_enc_recon_buffer control; + + vcd_property_hdr.prop_id = VCD_I_FREE_RECON_BUFFERS; + vcd_property_hdr.sz = sizeof(struct vcd_property_buffer_size); + + vcd_status = vcd_set_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + return true; +} + +u32 vid_enc_get_recon_buffer_size(struct video_client_ctx *client_ctx, + struct venc_recon_buff_size *venc_recon_size) +{ + u32 vcd_status = VCD_ERR_FAIL; + struct vcd_property_hdr vcd_property_hdr; + struct vcd_property_buffer_size control; + + control.width = venc_recon_size->width; + control.height = venc_recon_size->height; + + vcd_property_hdr.prop_id = VCD_I_GET_RECON_BUFFER_SIZE; + vcd_property_hdr.sz = sizeof(struct vcd_property_buffer_size); + + vcd_status = vcd_get_property(client_ctx->vcd_handle, + &vcd_property_hdr, &control); + + venc_recon_size->width = control.width; + venc_recon_size->height = control.height; + venc_recon_size->size = control.size; + venc_recon_size->alignment = control.alignment; + DBG("W: %d, H: %d, S: %d, A: %d", venc_recon_size->width, + venc_recon_size->height, venc_recon_size->size, + venc_recon_size->alignment); + + if (!vcd_status) { + DBG("vcd_set_property returned success\n"); + return true; + } else { + ERR("%s(): vid_enc_get_recon_buffer_size failed = %u\n", + __func__, vcd_status); + return false; + } +} diff --git a/drivers/video/msm/vidc/common/enc/venc_internal.h b/drivers/video/msm/vidc/common/enc/venc_internal.h new file mode 100644 index 00000000000..7d4ebca3836 --- /dev/null +++ b/drivers/video/msm/vidc/common/enc/venc_internal.h @@ -0,0 +1,151 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VENC_INTERNAL_H +#define VENC_INTERNAL_H + +#include +#include + +#include "vidc_init.h" + +#define VID_ENC_MAX_NUM_OF_BUFF 100 + +enum venc_buffer_dir{ + VEN_BUFFER_TYPE_INPUT, + VEN_BUFFER_TYPE_OUTPUT +}; + +struct vid_enc_msg { + struct list_head list; + struct venc_msg venc_msg_info; +}; + +struct vid_enc_dev { + + struct cdev cdev; + struct device *device; + resource_size_t phys_base; + void __iomem *virt_base; + unsigned int irq; + struct clk *hclk; + struct clk *hclk_div2; + struct clk *pclk; + unsigned long hclk_rate; + struct mutex lock; + s32 device_handle; + struct video_client_ctx venc_clients[VIDC_MAX_NUM_CLIENTS]; + u32 num_clients; +}; + +u32 vid_enc_set_get_base_cfg(struct video_client_ctx *client_ctx, + struct venc_basecfg *base_config, u32 set_flag); + +u32 vid_enc_set_get_inputformat(struct video_client_ctx *client_ctx, + u32 *input_format, u32 set_flag); + +u32 vid_enc_set_get_codec(struct video_client_ctx *client_ctx, u32 *codec, + u32 set_flag); + +u32 vid_enc_set_get_framesize(struct video_client_ctx *client_ctx, + u32 *height, u32 *width, u32 set_flag); + +u32 vid_enc_set_get_bitrate(struct video_client_ctx *client_ctx, + struct venc_targetbitrate *venc_bitrate, u32 set_flag); + +u32 vid_enc_set_get_framerate(struct video_client_ctx *client_ctx, + struct venc_framerate *frame_rate, u32 set_flag); + +u32 vid_enc_set_get_live_mode(struct video_client_ctx *client_ctx, + struct venc_switch *encoder_switch, u32 set_flag); + +u32 vid_enc_set_get_short_header(struct video_client_ctx *client_ctx, + struct venc_switch *encoder_switch, u32 set_flag); + +u32 vid_enc_set_get_profile(struct video_client_ctx *client_ctx, + struct venc_profile *profile, u32 set_flag); + +u32 vid_enc_set_get_profile_level(struct video_client_ctx *client_ctx, + struct ven_profilelevel *profile_level, u32 set_flag); + +u32 vid_enc_set_get_session_qp(struct video_client_ctx *client_ctx, + struct venc_sessionqp *session_qp, u32 set_flag); + +u32 vid_enc_set_get_intraperiod(struct video_client_ctx *client_ctx, + struct venc_intraperiod *intraperiod, u32 set_flag); + +u32 vid_enc_request_iframe(struct video_client_ctx *client_ctx); + +u32 vid_enc_get_sequence_header(struct video_client_ctx *client_ctx, + struct venc_seqheader *seq_header); + +u32 vid_enc_set_get_entropy_cfg(struct video_client_ctx *client_ctx, + struct venc_entropycfg *entropy_cfg, u32 set_flag); + +u32 vid_enc_set_get_dbcfg(struct video_client_ctx *client_ctx, + struct venc_dbcfg *dbcfg, u32 set_flag); + +u32 vid_enc_set_get_intrarefresh(struct video_client_ctx *client_ctx, + struct venc_intrarefresh *intrarefresh, u32 set_flag); + +u32 vid_enc_set_get_multiclicecfg(struct video_client_ctx *client_ctx, + struct venc_multiclicecfg *multiclicecfg, u32 set_flag); + +u32 vid_enc_set_get_ratectrlcfg(struct video_client_ctx *client_ctx, + struct venc_ratectrlcfg *ratectrlcfg, u32 set_flag); + +u32 vid_enc_set_get_voptimingcfg(struct video_client_ctx *client_ctx, + struct venc_voptimingcfg *voptimingcfg, u32 set_flag); + +u32 vid_enc_set_get_headerextension(struct video_client_ctx *client_ctx, + struct venc_headerextension *headerextension, u32 set_flag); + +u32 vid_enc_set_get_qprange(struct video_client_ctx *client_ctx, + struct venc_qprange *qprange, u32 set_flag); + +u32 vid_enc_start_stop(struct video_client_ctx *client_ctx, u32 start); + +u32 vid_enc_pause_resume(struct video_client_ctx *client_ctx, u32 pause); + +u32 vid_enc_flush(struct video_client_ctx *client_ctx, + struct venc_bufferflush *bufferflush); + +u32 vid_enc_get_buffer_req(struct video_client_ctx *client_ctx, + struct venc_allocatorproperty *venc_buf_req, u32 input_dir); + +u32 vid_enc_set_buffer_req(struct video_client_ctx *client_ctx, + struct venc_allocatorproperty *venc_buf_req, u32 input_dir); + +u32 vid_enc_set_buffer(struct video_client_ctx *client_ctx, + struct venc_bufferpayload *buffer_info, + enum venc_buffer_dir buffer); + +u32 vid_enc_free_buffer(struct video_client_ctx *client_ctx, + struct venc_bufferpayload *buffer_info, + enum venc_buffer_dir buffer); + +u32 vid_enc_encode_frame(struct video_client_ctx *client_ctx, + struct venc_buffer *input_frame_info); + +u32 vid_enc_fill_output_buffer(struct video_client_ctx *client_ctx, + struct venc_buffer *output_frame_info); + +u32 vid_enc_set_recon_buffers(struct video_client_ctx *client_ctx, + struct venc_recon_addr *venc_recon); + +u32 vid_enc_free_recon_buffers(struct video_client_ctx *client_ctx); + +u32 vid_enc_get_recon_buffer_size(struct video_client_ctx *client_ctx, + struct venc_recon_buff_size *venc_recon_size); + +#endif diff --git a/drivers/video/msm/vidc/common/init/vidc_init.c b/drivers/video/msm/vidc/common/init/vidc_init.c new file mode 100644 index 00000000000..cda3a91e9a3 --- /dev/null +++ b/drivers/video/msm/vidc/common/init/vidc_init.c @@ -0,0 +1,620 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vcd_api.h" +#include "vidc_init_internal.h" +#include "vidc_init.h" +#include "vcd_res_tracker_api.h" + +#if DEBUG +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) +#endif + +#define VIDC_NAME "msm_vidc_reg" + +#define ERR(x...) printk(KERN_ERR x) + +static struct vidc_dev *vidc_device_p; +static dev_t vidc_dev_num; +static struct class *vidc_class; + +static const struct file_operations vidc_fops = { + .owner = THIS_MODULE, + .open = NULL, + .release = NULL, + .unlocked_ioctl = NULL, +}; + +struct workqueue_struct *vidc_wq; +struct workqueue_struct *vidc_timer_wq; +static irqreturn_t vidc_isr(int irq, void *dev); +static spinlock_t vidc_spin_lock; + +u32 vidc_msg_timing, vidc_msg_pmem; + +#ifdef VIDC_ENABLE_DBGFS +struct dentry *vidc_debugfs_root; + +struct dentry *vidc_get_debugfs_root(void) +{ + if (vidc_debugfs_root == NULL) + vidc_debugfs_root = debugfs_create_dir("vidc", NULL); + return vidc_debugfs_root; +} + +void vidc_debugfs_file_create(struct dentry *root, const char *name, + u32 *var) +{ + struct dentry *vidc_debugfs_file = + debugfs_create_u32(name, S_IRUGO | S_IWUSR, root, var); + if (!vidc_debugfs_file) + ERR("%s(): Error creating/opening file %s\n", __func__, name); +} +#endif + +static void vidc_timer_fn(unsigned long data) +{ + unsigned long flag; + struct vidc_timer *hw_timer = NULL; + ERR("%s() Timer expired\n", __func__); + spin_lock_irqsave(&vidc_spin_lock, flag); + hw_timer = (struct vidc_timer *)data; + list_add_tail(&hw_timer->list, &vidc_device_p->vidc_timer_queue); + spin_unlock_irqrestore(&vidc_spin_lock, flag); + DBG("Queue the work for timer\n"); + queue_work(vidc_timer_wq, &vidc_device_p->vidc_timer_worker); +} + +static void vidc_timer_handler(struct work_struct *work) +{ + unsigned long flag = 0; + u32 islist_empty = 0; + struct vidc_timer *hw_timer = NULL; + + ERR("%s() Timer expired\n", __func__); + do { + spin_lock_irqsave(&vidc_spin_lock, flag); + islist_empty = list_empty(&vidc_device_p->vidc_timer_queue); + if (!islist_empty) { + hw_timer = list_first_entry( + &vidc_device_p->vidc_timer_queue, + struct vidc_timer, list); + list_del(&hw_timer->list); + } + spin_unlock_irqrestore(&vidc_spin_lock, flag); + if (!islist_empty && hw_timer && hw_timer->cb_func) + hw_timer->cb_func(hw_timer->userdata); + } while (!islist_empty); +} + +static void vidc_work_handler(struct work_struct *work) +{ + DBG("vidc_work_handler()"); + vcd_read_and_clear_interrupt(); + vcd_response_handler(); + enable_irq(vidc_device_p->irq); + DBG("vidc_work_handler() done"); +} + +static DECLARE_WORK(vidc_work, vidc_work_handler); + +static int __devinit vidc_720p_probe(struct platform_device *pdev) +{ + struct resource *resource; + DBG("Enter %s()\n", __func__); + + if (pdev->id) { + ERR("Invalid plaform device ID = %d\n", pdev->id); + return -EINVAL; + } + vidc_device_p->irq = platform_get_irq(pdev, 0); + if (unlikely(vidc_device_p->irq < 0)) { + ERR("%s(): Invalid irq = %d\n", __func__, + vidc_device_p->irq); + return -ENXIO; + } + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!resource)) { + ERR("%s(): Invalid resource\n", __func__); + return -ENXIO; + } + + vidc_device_p->phys_base = resource->start; + vidc_device_p->virt_base = ioremap(resource->start, + resource->end - resource->start + 1); + + if (!vidc_device_p->virt_base) { + ERR("%s() : ioremap failed\n", __func__); + return -ENOMEM; + } + vidc_device_p->device = &pdev->dev; + mutex_init(&vidc_device_p->lock); + + vidc_wq = create_singlethread_workqueue("vidc_worker_queue"); + if (!vidc_wq) { + ERR("%s: create workque failed\n", __func__); + return -ENOMEM; + } + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + return 0; +} + +static int __devexit vidc_720p_remove(struct platform_device *pdev) +{ + if (pdev->id) { + ERR("Invalid plaform device ID = %d\n", pdev->id); + return -EINVAL; + } + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int vidc_runtime_suspend(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: suspending...\n"); + return 0; +} + +static int vidc_runtime_resume(struct device *dev) +{ + dev_dbg(dev, "pm_runtime: resuming...\n"); + return 0; +} + +static const struct dev_pm_ops vidc_dev_pm_ops = { + .runtime_suspend = vidc_runtime_suspend, + .runtime_resume = vidc_runtime_resume, +}; + +static struct platform_driver msm_vidc_720p_platform_driver = { + .probe = vidc_720p_probe, + .remove = vidc_720p_remove, + .driver = { + .name = "msm_vidc", + .pm = &vidc_dev_pm_ops, + }, +}; + +static void __exit vidc_exit(void) +{ + platform_driver_unregister(&msm_vidc_720p_platform_driver); +} + +static irqreturn_t vidc_isr(int irq, void *dev) +{ + DBG("\n vidc_isr() %d ", irq); + disable_irq_nosync(irq); + queue_work(vidc_wq, &vidc_work); + return IRQ_HANDLED; +} + +static int __init vidc_init(void) +{ + int rc = 0; + struct device *class_devp; +#ifdef VIDC_ENABLE_DBGFS + struct dentry *root = NULL; +#endif + + vidc_device_p = kzalloc(sizeof(struct vidc_dev), GFP_KERNEL); + if (!vidc_device_p) { + ERR("%s Unable to allocate memory for vidc_dev\n", + __func__); + return -ENOMEM; + } + + rc = alloc_chrdev_region(&vidc_dev_num, 0, 1, VIDC_NAME); + if (rc < 0) { + ERR("%s: alloc_chrdev_region Failed rc = %d\n", + __func__, rc); + goto error_vidc_alloc_chrdev_region; + } + + vidc_class = class_create(THIS_MODULE, VIDC_NAME); + if (IS_ERR(vidc_class)) { + rc = PTR_ERR(vidc_class); + ERR("%s: couldn't create vidc_class rc = %d\n", + __func__, rc); + + goto error_vidc_class_create; + } + + class_devp = device_create(vidc_class, NULL, vidc_dev_num, NULL, + VIDC_NAME); + + if (IS_ERR(class_devp)) { + rc = PTR_ERR(class_devp); + ERR("%s: class device_create failed %d\n", + __func__, rc); + goto error_vidc_class_device_create; + } + + cdev_init(&vidc_device_p->cdev, &vidc_fops); + vidc_device_p->cdev.owner = THIS_MODULE; + rc = cdev_add(&(vidc_device_p->cdev), vidc_dev_num, 1); + + if (rc < 0) { + ERR("%s: cdev_add failed %d\n", __func__, rc); + goto error_vidc_cdev_add; + } + + rc = platform_driver_register(&msm_vidc_720p_platform_driver); + if (rc) { + ERR("%s failed to load\n", __func__); + goto error_vidc_platfom_register; + } + + rc = request_irq(vidc_device_p->irq, vidc_isr, IRQF_TRIGGER_HIGH, + "vidc", vidc_device_p->device); + + if (unlikely(rc)) { + ERR("%s() :request_irq failed\n", __func__); + goto error_vidc_platfom_register; + } + res_trk_init(vidc_device_p->device, vidc_device_p->irq); + vidc_timer_wq = create_singlethread_workqueue("vidc_timer_wq"); + if (!vidc_timer_wq) { + ERR("%s: create workque failed\n", __func__); + rc = -ENOMEM; + goto error_vidc_platfom_register; + } + DBG("Disabling IRQ in %s()\n", __func__); + disable_irq_nosync(vidc_device_p->irq); + INIT_WORK(&vidc_device_p->vidc_timer_worker, + vidc_timer_handler); + spin_lock_init(&vidc_spin_lock); + INIT_LIST_HEAD(&vidc_device_p->vidc_timer_queue); + + vidc_device_p->ref_count = 0; + vidc_device_p->firmware_refcount = 0; + vidc_device_p->get_firmware = 0; +#ifdef VIDC_ENABLE_DBGFS + root = vidc_get_debugfs_root(); + if (root) { + vidc_debugfs_file_create(root, "vidc_msg_timing", + (u32 *) &vidc_msg_timing); + vidc_debugfs_file_create(root, "vidc_msg_pmem", + (u32 *) &vidc_msg_pmem); + } +#endif + return 0; + +error_vidc_platfom_register: + cdev_del(&(vidc_device_p->cdev)); +error_vidc_cdev_add: + device_destroy(vidc_class, vidc_dev_num); +error_vidc_class_device_create: + class_destroy(vidc_class); +error_vidc_class_create: + unregister_chrdev_region(vidc_dev_num, 1); +error_vidc_alloc_chrdev_region: + kfree(vidc_device_p); + + return rc; +} + +void __iomem *vidc_get_ioaddr(void) +{ + return (u8 *)vidc_device_p->virt_base; +} +EXPORT_SYMBOL(vidc_get_ioaddr); + +int vidc_load_firmware(void) +{ + u32 status = true; + + mutex_lock(&vidc_device_p->lock); + if (!vidc_device_p->get_firmware) { + status = res_trk_download_firmware(); + if (!status) + goto error; + vidc_device_p->get_firmware = 1; + } + vidc_device_p->firmware_refcount++; +error: + mutex_unlock(&vidc_device_p->lock); + return status; +} +EXPORT_SYMBOL(vidc_load_firmware); + +void vidc_release_firmware(void) +{ + mutex_lock(&vidc_device_p->lock); + if (vidc_device_p->firmware_refcount > 0) + vidc_device_p->firmware_refcount--; + else + vidc_device_p->firmware_refcount = 0; + mutex_unlock(&vidc_device_p->lock); +} +EXPORT_SYMBOL(vidc_release_firmware); + +u32 vidc_lookup_addr_table(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, + u32 search_with_user_vaddr, + unsigned long *user_vaddr, + unsigned long *kernel_vaddr, + unsigned long *phy_addr, int *pmem_fd, + struct file **file, s32 *buffer_index) +{ + u32 num_of_buffers; + u32 i; + struct buf_addr_table *buf_addr_table; + u32 found = false; + + if (!client_ctx) + return false; + + if (buffer == BUFFER_TYPE_INPUT) { + buf_addr_table = client_ctx->input_buf_addr_table; + num_of_buffers = client_ctx->num_of_input_buffers; + DBG("%s(): buffer = INPUT\n", __func__); + + } else { + buf_addr_table = client_ctx->output_buf_addr_table; + num_of_buffers = client_ctx->num_of_output_buffers; + DBG("%s(): buffer = OUTPUT\n", __func__); + } + + for (i = 0; i < num_of_buffers; ++i) { + if (search_with_user_vaddr) { + if (*user_vaddr == buf_addr_table[i].user_vaddr) { + *kernel_vaddr = buf_addr_table[i].kernel_vaddr; + found = true; + DBG("%s() : client_ctx = %p." + " user_virt_addr = 0x%08lx is found", + __func__, client_ctx, *user_vaddr); + break; + } + } else { + if (*kernel_vaddr == buf_addr_table[i].kernel_vaddr) { + *user_vaddr = buf_addr_table[i].user_vaddr; + found = true; + DBG("%s() : client_ctx = %p." + " kernel_virt_addr = 0x%08lx is found", + __func__, client_ctx, *kernel_vaddr); + break; + } + } + } + + if (found) { + *phy_addr = buf_addr_table[i].phy_addr; + *pmem_fd = buf_addr_table[i].pmem_fd; + *file = buf_addr_table[i].file; + *buffer_index = i; + + if (search_with_user_vaddr) + DBG("kernel_vaddr = 0x%08lx, phy_addr = 0x%08lx " + " pmem_fd = %d, struct *file = %p " + "buffer_index = %d\n", *kernel_vaddr, + *phy_addr, *pmem_fd, *file, *buffer_index); + else + DBG("user_vaddr = 0x%08lx, phy_addr = 0x%08lx " + " pmem_fd = %d, struct *file = %p " + "buffer_index = %d\n", *user_vaddr, *phy_addr, + *pmem_fd, *file, *buffer_index); + return true; + } else { + if (search_with_user_vaddr) + DBG("%s() : client_ctx = %p user_virt_addr = 0x%08lx" + " Not Found.\n", __func__, client_ctx, *user_vaddr); + else + DBG("%s() : client_ctx = %p kernel_virt_addr = 0x%08lx" + " Not Found.\n", __func__, client_ctx, + *kernel_vaddr); + return false; + } +} +EXPORT_SYMBOL(vidc_lookup_addr_table); + +u32 vidc_insert_addr_table(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, unsigned long user_vaddr, + unsigned long *kernel_vaddr, int pmem_fd, + unsigned long buffer_addr_offset, unsigned int max_num_buffers) +{ + unsigned long len, phys_addr; + struct file *file; + u32 *num_of_buffers = NULL; + u32 i; + struct buf_addr_table *buf_addr_table; + + if (!client_ctx) + return false; + + if (buffer == BUFFER_TYPE_INPUT) { + buf_addr_table = client_ctx->input_buf_addr_table; + num_of_buffers = &client_ctx->num_of_input_buffers; + DBG("%s(): buffer = INPUT #Buf = %d\n", + __func__, *num_of_buffers); + + } else { + buf_addr_table = client_ctx->output_buf_addr_table; + num_of_buffers = &client_ctx->num_of_output_buffers; + DBG("%s(): buffer = OUTPUT #Buf = %d\n", + __func__, *num_of_buffers); + } + + if (*num_of_buffers == max_num_buffers) { + ERR("%s(): Num of buffers reached max value : %d", + __func__, max_num_buffers); + return false; + } + + i = 0; + while (i < *num_of_buffers && + user_vaddr != buf_addr_table[i].user_vaddr) + i++; + if (i < *num_of_buffers) { + DBG("%s() : client_ctx = %p." + " user_virt_addr = 0x%08lx already set", + __func__, client_ctx, user_vaddr); + return false; + } else { + if (get_pmem_file(pmem_fd, &phys_addr, + kernel_vaddr, &len, &file)) { + ERR("%s(): get_pmem_file failed\n", __func__); + return false; + } + put_pmem_file(file); + phys_addr += buffer_addr_offset; + (*kernel_vaddr) += buffer_addr_offset; + buf_addr_table[*num_of_buffers].user_vaddr = user_vaddr; + buf_addr_table[*num_of_buffers].kernel_vaddr = *kernel_vaddr; + buf_addr_table[*num_of_buffers].pmem_fd = pmem_fd; + buf_addr_table[*num_of_buffers].file = file; + buf_addr_table[*num_of_buffers].phy_addr = phys_addr; + *num_of_buffers = *num_of_buffers + 1; + DBG("%s() : client_ctx = %p, user_virt_addr = 0x%08lx, " + "kernel_vaddr = 0x%08lx inserted!", __func__, + client_ctx, user_vaddr, *kernel_vaddr); + } + return true; +} +EXPORT_SYMBOL(vidc_insert_addr_table); + +u32 vidc_delete_addr_table(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, + unsigned long user_vaddr, + unsigned long *kernel_vaddr) +{ + u32 *num_of_buffers = NULL; + u32 i; + struct buf_addr_table *buf_addr_table; + + if (!client_ctx) + return false; + + if (buffer == BUFFER_TYPE_INPUT) { + buf_addr_table = client_ctx->input_buf_addr_table; + num_of_buffers = &client_ctx->num_of_input_buffers; + DBG("%s(): buffer = INPUT\n", __func__); + + } else { + buf_addr_table = client_ctx->output_buf_addr_table; + num_of_buffers = &client_ctx->num_of_output_buffers; + DBG("%s(): buffer = OUTPUT\n", __func__); + } + + if (!*num_of_buffers) + return false; + + i = 0; + while (i < *num_of_buffers && + user_vaddr != buf_addr_table[i].user_vaddr) + i++; + if (i == *num_of_buffers) { + DBG("%s() : client_ctx = %p." + " user_virt_addr = 0x%08lx NOT found", + __func__, client_ctx, user_vaddr); + return false; + } + *kernel_vaddr = buf_addr_table[i].kernel_vaddr; + if (i < (*num_of_buffers - 1)) { + buf_addr_table[i].user_vaddr = + buf_addr_table[*num_of_buffers - 1].user_vaddr; + buf_addr_table[i].kernel_vaddr = + buf_addr_table[*num_of_buffers - 1].kernel_vaddr; + buf_addr_table[i].phy_addr = + buf_addr_table[*num_of_buffers - 1].phy_addr; + buf_addr_table[i].pmem_fd = + buf_addr_table[*num_of_buffers - 1].pmem_fd; + buf_addr_table[i].file = + buf_addr_table[*num_of_buffers - 1].file; + } + *num_of_buffers = *num_of_buffers - 1; + DBG("%s() : client_ctx = %p." + " user_virt_addr = 0x%08lx is found and deleted", + __func__, client_ctx, user_vaddr); + return true; +} +EXPORT_SYMBOL(vidc_delete_addr_table); + +u32 vidc_timer_create(void (*timer_handler)(void *), + void *user_data, void **timer_handle) +{ + struct vidc_timer *hw_timer = NULL; + if (!timer_handler || !timer_handle) { + DBG("%s(): timer creation failed\n ", __func__); + return false; + } + hw_timer = kzalloc(sizeof(struct vidc_timer), GFP_KERNEL); + if (!hw_timer) { + DBG("%s(): timer creation failed in allocation\n ", __func__); + return false; + } + init_timer(&hw_timer->hw_timeout); + hw_timer->hw_timeout.data = (unsigned long)hw_timer; + hw_timer->hw_timeout.function = vidc_timer_fn; + hw_timer->cb_func = timer_handler; + hw_timer->userdata = user_data; + *timer_handle = hw_timer; + return true; +} +EXPORT_SYMBOL(vidc_timer_create); + +void vidc_timer_release(void *timer_handle) +{ + kfree(timer_handle); +} +EXPORT_SYMBOL(vidc_timer_release); + +void vidc_timer_start(void *timer_handle, u32 time_out) +{ + struct vidc_timer *hw_timer = (struct vidc_timer *)timer_handle; + DBG("%s(): start timer\n ", __func__); + if (hw_timer) { + hw_timer->hw_timeout.expires = jiffies + 1*HZ; + add_timer(&hw_timer->hw_timeout); + } +} +EXPORT_SYMBOL(vidc_timer_start); + +void vidc_timer_stop(void *timer_handle) +{ + struct vidc_timer *hw_timer = (struct vidc_timer *)timer_handle; + DBG("%s(): stop timer\n ", __func__); + if (hw_timer) + del_timer(&hw_timer->hw_timeout); +} +EXPORT_SYMBOL(vidc_timer_stop); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Video decoder/encoder driver Init Module"); +MODULE_VERSION("1.0"); +module_init(vidc_init); +module_exit(vidc_exit); diff --git a/drivers/video/msm/vidc/common/init/vidc_init.h b/drivers/video/msm/vidc/common/init/vidc_init.h new file mode 100644 index 00000000000..c4727189d42 --- /dev/null +++ b/drivers/video/msm/vidc/common/init/vidc_init.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VIDC_INIT_H +#define VIDC_INIT_H + +#include "vidc_type.h" + +#define VIDC_MAX_NUM_CLIENTS 4 +#define MAX_VIDEO_NUM_OF_BUFF 100 + +enum buffer_dir { + BUFFER_TYPE_INPUT, + BUFFER_TYPE_OUTPUT +}; + +struct buf_addr_table { + unsigned long user_vaddr; + unsigned long kernel_vaddr; + unsigned long phy_addr; + int pmem_fd; + struct file *file; +}; + +struct video_client_ctx { + void *vcd_handle; + u32 num_of_input_buffers; + u32 num_of_output_buffers; + struct buf_addr_table input_buf_addr_table[MAX_VIDEO_NUM_OF_BUFF]; + struct buf_addr_table output_buf_addr_table[MAX_VIDEO_NUM_OF_BUFF]; + struct list_head msg_queue; + struct mutex msg_queue_lock; + wait_queue_head_t msg_wait; + struct completion event; + u32 event_status; + u32 seq_header_set; + u32 stop_msg; + u32 stop_called; + u32 stop_sync_cb; +}; + +void __iomem *vidc_get_ioaddr(void); +int vidc_load_firmware(void); +void vidc_release_firmware(void); +u32 vidc_lookup_addr_table(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, u32 search_with_user_vaddr, + unsigned long *user_vaddr, unsigned long *kernel_vaddr, + unsigned long *phy_addr, int *pmem_fd, struct file **file, + s32 *buffer_index); +u32 vidc_insert_addr_table(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, unsigned long user_vaddr, + unsigned long *kernel_vaddr, int pmem_fd, + unsigned long buffer_addr_offset, + unsigned int max_num_buffers); +u32 vidc_delete_addr_table(struct video_client_ctx *client_ctx, + enum buffer_dir buffer, unsigned long user_vaddr, + unsigned long *kernel_vaddr); + +u32 vidc_timer_create(void (*timer_handler)(void *), + void *user_data, void **timer_handle); +void vidc_timer_release(void *timer_handle); +void vidc_timer_start(void *timer_handle, u32 time_out); +void vidc_timer_stop(void *timer_handle); + + +#endif diff --git a/drivers/video/msm/vidc/common/init/vidc_init_internal.h b/drivers/video/msm/vidc/common/init/vidc_init_internal.h new file mode 100644 index 00000000000..1d903ad2463 --- /dev/null +++ b/drivers/video/msm/vidc/common/init/vidc_init_internal.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VIDC_INIT_INTERNAL_H +#define VIDC_INIT_INTERNAL_H + +#include + +struct vidc_timer { + struct list_head list; + struct timer_list hw_timeout; + void (*cb_func)(void *); + void *userdata; +}; + +struct vidc_dev { + struct cdev cdev; + struct device *device; + resource_size_t phys_base; + void __iomem *virt_base; + unsigned int irq; + unsigned int ref_count; + unsigned int firmware_refcount; + unsigned int get_firmware; + struct mutex lock; + s32 device_handle; + struct list_head vidc_timer_queue; + struct work_struct vidc_timer_worker; +}; + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vcd.h b/drivers/video/msm/vidc/common/vcd/vcd.h new file mode 100644 index 00000000000..b557752f79b --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd.h @@ -0,0 +1,393 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_H_ +#define _VCD_H_ + +#include "vcd_api.h" +#include "vcd_ddl_api.h" +#include "vcd_res_tracker_api.h" +#include "vcd_util.h" +#include "vcd_client_sm.h" +#include "vcd_core.h" +#include "vcd_device_sm.h" + +void vcd_reset_device_channels(struct vcd_dev_ctxt *dev_ctxt); + +u32 vcd_get_command_channel + (struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc **transc); + +u32 vcd_get_command_channel_in_loop + (struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc **transc); + +void vcd_mark_command_channel + (struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc *transc); + +void vcd_release_command_channel + (struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc *transc); + +void vcd_release_multiple_command_channels(struct vcd_dev_ctxt *dev_ctxt, + u32 channels); + +void vcd_release_interim_command_channels(struct vcd_dev_ctxt *dev_ctxt); + +u32 vcd_get_frame_channel + (struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc **transc); + +u32 vcd_get_frame_channel_in_loop + (struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc **transc); + +void vcd_mark_frame_channel(struct vcd_dev_ctxt *dev_ctxt); + +void vcd_release_frame_channel + (struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc *transc); + +void vcd_release_multiple_frame_channels(struct vcd_dev_ctxt *dev_ctxt, + u32 channels); + +void vcd_release_interim_frame_channels(struct vcd_dev_ctxt *dev_ctxt); +u32 vcd_core_is_busy(struct vcd_dev_ctxt *dev_ctxt); + +void vcd_device_timer_start(struct vcd_dev_ctxt *dev_ctxt); +void vcd_device_timer_stop(struct vcd_dev_ctxt *dev_ctxt); + + +u32 vcd_init_device_context + (struct vcd_drv_ctxt *drv_ctxt, u32 ev_code); + +u32 vcd_deinit_device_context + (struct vcd_drv_ctxt *drv_ctxt, u32 ev_code); + +u32 vcd_init_client_context(struct vcd_clnt_ctxt *cctxt); + +void vcd_destroy_client_context(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_check_for_client_context + (struct vcd_dev_ctxt *dev_ctxt, s32 driver_id); + +u32 vcd_validate_driver_handle + (struct vcd_dev_ctxt *dev_ctxt, s32 driver_handle); + +void vcd_handle_for_last_clnt_close + (struct vcd_dev_ctxt *dev_ctxt, u32 send_deinit); + +u32 vcd_common_allocate_set_buffer + (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer, + u32 buf_size, struct vcd_buffer_pool **buf_pool); + +u32 vcd_set_buffer_internal + (struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_pool *buf_pool, u8 *buffer, u32 buf_size); + +u32 vcd_allocate_buffer_internal + (struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_pool *buf_pool, + u32 buf_size, u8 **vir_buf_addr, u8 **phy_buf_addr); + +u32 vcd_free_one_buffer_internal + (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer_type, u8 *buffer); + +u32 vcd_free_buffers_internal + (struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_pool *buf_pool); + +u32 vcd_alloc_buffer_pool_entries + (struct vcd_buffer_pool *buf_pool, + struct vcd_buffer_requirement *buf_req); + +void vcd_free_buffer_pool_entries(struct vcd_buffer_pool *buf_pool); + +void vcd_flush_in_use_buffer_pool_entries(struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_pool *buf_pool, u32 event); + +void vcd_reset_buffer_pool_for_reuse(struct vcd_buffer_pool *buf_pool); + +struct vcd_buffer_entry *vcd_get_free_buffer_pool_entry + (struct vcd_buffer_pool *pool); + +struct vcd_buffer_entry *vcd_find_buffer_pool_entry + (struct vcd_buffer_pool *pool, u8 *v_addr); + +struct vcd_buffer_entry *vcd_buffer_pool_entry_de_q + (struct vcd_buffer_pool *pool); + +u32 vcd_buffer_pool_entry_en_q + (struct vcd_buffer_pool *pool, + struct vcd_buffer_entry *entry); + +u32 vcd_check_if_buffer_req_met(struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer_type); + +u32 vcd_client_cmd_en_q + (struct vcd_clnt_ctxt *cctxt, enum vcd_command command); + +void vcd_client_cmd_flush_and_en_q + (struct vcd_clnt_ctxt *cctxt, enum vcd_command command); + +u32 vcd_client_cmd_de_q + (struct vcd_clnt_ctxt *cctxt, enum vcd_command *command); + +u32 vcd_handle_recvd_eos + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame, u32 * pb_eos_handled); + +u32 vcd_handle_first_decode_frame(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_handle_input_frame + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame); + +u32 vcd_store_seq_hdr + (struct vcd_clnt_ctxt *cctxt, + struct vcd_sequence_hdr *seq_hdr); + +u32 vcd_set_frame_size + (struct vcd_clnt_ctxt *cctxt, + struct vcd_property_frame_size *frm_size); + +u32 vcd_set_frame_rate + (struct vcd_clnt_ctxt *cctxt, + struct vcd_property_frame_rate *fps); + +u32 vcd_calculate_frame_delta + (struct vcd_clnt_ctxt *cctxt, struct vcd_frame_data *frame); + +struct vcd_buffer_entry *vcd_check_fill_output_buffer + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *buffer); + +u32 vcd_handle_first_fill_output_buffer + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *buffer, u32 *b_handled); + +u32 vcd_handle_first_fill_output_buffer_for_enc + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *frm_entry, u32 *b_handled); + +u32 vcd_handle_first_fill_output_buffer_for_dec + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *frm_entry, u32 *b_handled); + +u32 vcd_schedule_frame(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt **cctxt, struct vcd_buffer_entry + **ip_buf_entry); + +u32 vcd_submit_command_in_continue + (struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc *transc); + +u32 vcd_submit_cmd_sess_start(struct vcd_transc *transc); + +u32 vcd_submit_cmd_sess_end(struct vcd_transc *transc); + +void vcd_submit_cmd_client_close(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_submit_frame + (struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc *transc); + +u32 vcd_try_submit_frame_in_continue(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc *transc); + +u32 vcd_process_cmd_sess_start(struct vcd_clnt_ctxt *cctxt); + +void vcd_try_submit_frame(struct vcd_dev_ctxt *dev_ctxt); + +u32 vcd_setup_with_ddl_capabilities(struct vcd_dev_ctxt *dev_ctxt); +void vcd_handle_submit_frame_failed(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc *transc); + +struct vcd_transc *vcd_get_free_trans_tbl_entry + (struct vcd_dev_ctxt *dev_ctxt); + +void vcd_release_trans_tbl_entry(struct vcd_transc *trans_entry); + +void vcd_release_all_clnt_frm_transc(struct vcd_clnt_ctxt *cctxt); +void vcd_release_all_clnt_transc(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_handle_input_done + (struct vcd_clnt_ctxt *cctxt, + void *payload, u32 event, u32 status); + +u32 vcd_handle_input_done_in_eos + (struct vcd_clnt_ctxt *cctxt, void *payload, u32 status); + +void vcd_handle_input_done_failed + (struct vcd_clnt_ctxt *cctxt, struct vcd_transc *transc); + +void vcd_handle_input_done_with_codec_config + (struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, + struct ddl_frame_data_tag *frm); + +void vcd_handle_input_done_for_interlacing + (struct vcd_clnt_ctxt *cctxt); + +void vcd_handle_input_done_with_trans_end + (struct vcd_clnt_ctxt *cctxt); + +u32 vcd_handle_frame_done + (struct vcd_clnt_ctxt *cctxt, + void *payload, u32 event, u32 status); + +void vcd_handle_frame_done_for_interlacing + (struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc_ip1, + struct ddl_frame_data_tag *op_frm, u32 status); + + +u32 vcd_handle_frame_done_in_eos + (struct vcd_clnt_ctxt *cctxt, void *payload, u32 status); + +u32 vcd_handle_output_required(struct vcd_clnt_ctxt *cctxt, + void *payload, u32 status); + +u32 vcd_handle_output_required_in_flushing(struct vcd_clnt_ctxt *cctxt, + void *payload); + +u32 vcd_handle_output_req_tran_end_in_eos(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_validate_io_done_pyld + (struct vcd_clnt_ctxt *cctxt, void *payload, u32 status); + +void vcd_handle_eos_trans_end(struct vcd_clnt_ctxt *cctxt); + + +void vcd_handle_eos_done + (struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, u32 status); + +void vcd_send_frame_done_in_eos + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame, u32 valid_opbuf); + +void vcd_send_frame_done_in_eos_for_dec + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame); + +void vcd_send_frame_done_in_eos_for_enc + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame); + +void vcd_handle_start_done(struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, u32 status); + +void vcd_handle_stop_done(struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, u32 status); + +void vcd_handle_stop_done_in_starting(struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, u32 status); + +void vcd_handle_stop_done_in_invalid(struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, u32 status); + +void vcd_send_flush_done(struct vcd_clnt_ctxt *cctxt, u32 status); + +void vcd_process_pending_flush_in_eos(struct vcd_clnt_ctxt *cctxt); + +void vcd_process_pending_stop_in_eos(struct vcd_clnt_ctxt *cctxt); + +void vcd_handle_trans_pending(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_handle_ind_output_reconfig + (struct vcd_clnt_ctxt *cctxt, void* payload, u32 status); + +u32 vcd_handle_ind_output_reconfig_in_flushing + (struct vcd_clnt_ctxt *cctxt, void* payload, u32 status); + +void vcd_flush_output_buffers(struct vcd_clnt_ctxt *cctxt); + +void vcd_flush_bframe_buffers(struct vcd_clnt_ctxt *cctxt, u32 mode); + +u32 vcd_flush_buffers(struct vcd_clnt_ctxt *cctxt, u32 mode); +void vcd_flush_buffers_in_err_fatal(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_power_event + (struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt *cctxt, u32 event); + +u32 vcd_device_power_event(struct vcd_dev_ctxt *dev_ctxt, u32 event, + struct vcd_clnt_ctxt *cctxt); + +u32 vcd_client_power_event + (struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt *cctxt, u32 event); + +u32 vcd_enable_clock(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt *cctxt); + +u32 vcd_disable_clock(struct vcd_dev_ctxt *dev_ctxt); + +u32 vcd_set_perf_level(struct vcd_dev_ctxt *dev_ctxt, u32 perf_lvl); + +u32 vcd_update_clnt_perf_lvl + (struct vcd_clnt_ctxt *cctxt, + struct vcd_property_frame_rate *fps, u32 frm_p_units); + +u32 vcd_gate_clock(struct vcd_dev_ctxt *dev_ctxt); + +u32 vcd_un_gate_clock(struct vcd_dev_ctxt *dev_ctxt); + +void vcd_handle_err_fatal(struct vcd_clnt_ctxt *cctxt, + u32 event, u32 status); + +void vcd_handle_device_err_fatal(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt *cctxt); + +void vcd_clnt_handle_device_err_fatal(struct vcd_clnt_ctxt *cctxt, + u32 event); + +void vcd_handle_err_in_starting(struct vcd_clnt_ctxt *cctxt, + u32 status); + +void vcd_handle_ind_hw_err_fatal(struct vcd_clnt_ctxt *cctxt, + u32 event, u32 status); + +u32 vcd_return_op_buffer_to_hw(struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_entry *buf_entry); + +u32 vcd_sched_create(struct list_head *sched_list); + +void vcd_sched_destroy(struct list_head *sched_clnt_list); + +u32 vcd_sched_add_client(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_sched_remove_client(struct vcd_sched_clnt_ctx *sched_cctxt); + +u32 vcd_sched_update_config(struct vcd_clnt_ctxt *cctxt); + +u32 vcd_sched_queue_buffer( + struct vcd_sched_clnt_ctx *sched_cctxt, + struct vcd_buffer_entry *buffer, u32 b_tail); + +u32 vcd_sched_dequeue_buffer( + struct vcd_sched_clnt_ctx *sched_cctxt, + struct vcd_buffer_entry **buffer); + +u32 vcd_sched_mark_client_eof(struct vcd_sched_clnt_ctx *sched_cctxt); + +u32 vcd_sched_suspend_resume_clnt( + struct vcd_clnt_ctxt *cctxt, u32 b_state); + +u32 vcd_sched_get_client_frame(struct list_head *sched_clnt_list, + struct vcd_clnt_ctxt **cctxt, + struct vcd_buffer_entry **buffer); + +void vcd_handle_clnt_fatal(struct vcd_clnt_ctxt *cctxt, u32 trans_end); + +void vcd_handle_clnt_fatal_input_done(struct vcd_clnt_ctxt *cctxt, + u32 trans_end); + +void vcd_handle_ind_info_output_reconfig + (struct vcd_clnt_ctxt *cctxt, u32 status); + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vcd_api.c b/drivers/video/msm/vidc/common/vcd/vcd_api.c new file mode 100644 index 00000000000..a57675e6008 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_api.c @@ -0,0 +1,881 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd.h" + +u32 vcd_init(struct vcd_init_config *config, s32 *driver_handle) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_drv_ctxt *drv_ctxt; + + VCD_MSG_MED("vcd_init:"); + + if (!config || + !driver_handle || !config->map_dev_base_addr) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_ILLEGAL_PARM; + } + + drv_ctxt = vcd_get_drv_context(); + mutex_init(&drv_ctxt->dev_mutex); + mutex_lock(&drv_ctxt->dev_mutex); + + if (drv_ctxt->dev_state.state_table->ev_hdlr.init) { + rc = drv_ctxt->dev_state.state_table->ev_hdlr. + init(drv_ctxt, config, driver_handle); + } else { + VCD_MSG_ERROR("Unsupported API in device state %d", + drv_ctxt->dev_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_init); + +u32 vcd_term(s32 driver_handle) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_drv_ctxt *drv_ctxt; + + VCD_MSG_MED("vcd_term:"); + + drv_ctxt = vcd_get_drv_context(); + mutex_lock(&drv_ctxt->dev_mutex); + + if (drv_ctxt->dev_state.state_table->ev_hdlr.term) { + rc = drv_ctxt->dev_state.state_table->ev_hdlr. + term(drv_ctxt, driver_handle); + } else { + VCD_MSG_ERROR("Unsupported API in device state %d", + drv_ctxt->dev_state.state); + + rc = VCD_ERR_BAD_STATE; + } + mutex_unlock(&drv_ctxt->dev_mutex); + return rc; + +} +EXPORT_SYMBOL(vcd_term); + +u32 vcd_open(s32 driver_handle, u32 decoding, + void (*callback) (u32 event, u32 status, void *info, size_t sz, + void *handle, void *const client_data), + void *client_data) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_drv_ctxt *drv_ctxt; + + VCD_MSG_MED("vcd_open:"); + + if (!callback) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_ILLEGAL_PARM; + } + + drv_ctxt = vcd_get_drv_context(); + mutex_lock(&drv_ctxt->dev_mutex); + + if (drv_ctxt->dev_state.state_table->ev_hdlr.open) { + rc = drv_ctxt->dev_state.state_table->ev_hdlr. + open(drv_ctxt, driver_handle, decoding, callback, + client_data); + } else { + VCD_MSG_ERROR("Unsupported API in device state %d", + drv_ctxt->dev_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_open); + +u32 vcd_close(void *handle) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_close:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + drv_ctxt = vcd_get_drv_context(); + mutex_lock(&drv_ctxt->dev_mutex); + if (drv_ctxt->dev_state.state_table->ev_hdlr.close) { + rc = drv_ctxt->dev_state.state_table->ev_hdlr. + close(drv_ctxt, cctxt); + } else { + VCD_MSG_ERROR("Unsupported API in device state %d", + drv_ctxt->dev_state.state); + + rc = VCD_ERR_BAD_STATE; + } + mutex_unlock(&drv_ctxt->dev_mutex); + return rc; + +} +EXPORT_SYMBOL(vcd_close); + +u32 vcd_encode_start(void *handle) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_encode_start:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.encode_start && + drv_ctxt->dev_ctxt.pwr_state != VCD_PWR_STATE_SLEEP) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + encode_start(cctxt); + } else { + VCD_MSG_ERROR + ("Unsupported API in dev power state %d OR client state %d", + drv_ctxt->dev_ctxt.pwr_state, + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_encode_start); + +u32 vcd_encode_frame(void *handle, struct vcd_frame_data *input_frame) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_encode_frame:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + if (!input_frame) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_BAD_POINTER; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.encode_frame) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + encode_frame(cctxt, input_frame); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_encode_frame); + +u32 vcd_decode_start(void *handle, struct vcd_sequence_hdr *seq_hdr) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_decode_start:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.decode_start && + drv_ctxt->dev_ctxt.pwr_state != VCD_PWR_STATE_SLEEP) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + decode_start(cctxt, seq_hdr); + } else { + VCD_MSG_ERROR + ("Unsupported API in dev power state %d OR client state %d", + drv_ctxt->dev_ctxt.pwr_state, + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_decode_start); + +u32 vcd_decode_frame(void *handle, struct vcd_frame_data *input_frame) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_decode_frame:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + if (!input_frame) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_BAD_POINTER; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.decode_frame) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + decode_frame(cctxt, input_frame); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_decode_frame); + +u32 vcd_pause(void *handle) +{ + struct vcd_drv_ctxt *drv_ctxt; + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + u32 rc; + + VCD_MSG_MED("vcd_pause:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.pause) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + pause(cctxt); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_pause); + +u32 vcd_resume(void *handle) +{ + struct vcd_drv_ctxt *drv_ctxt; + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + u32 rc; + + VCD_MSG_MED("vcd_resume:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (drv_ctxt->dev_state.state_table->ev_hdlr.resume && + drv_ctxt->dev_ctxt.pwr_state != VCD_PWR_STATE_SLEEP) { + rc = drv_ctxt->dev_state.state_table->ev_hdlr. + resume(drv_ctxt, cctxt); + } else { + VCD_MSG_ERROR + ("Unsupported API in dev power state %d OR client state %d", + drv_ctxt->dev_ctxt.pwr_state, + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_resume); + +u32 vcd_flush(void *handle, u32 mode) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_flush:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.flush) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + flush(cctxt, mode); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_flush); + +u32 vcd_stop(void *handle) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_stop:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.stop && + drv_ctxt->dev_ctxt.pwr_state != VCD_PWR_STATE_SLEEP) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + stop(cctxt); + } else { + VCD_MSG_ERROR + ("Unsupported API in dev power state %d OR client state %d", + drv_ctxt->dev_ctxt.pwr_state, + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_stop); + +u32 vcd_set_property(void *handle, + struct vcd_property_hdr *prop_hdr, void *prop_val) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_set_property:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + if (!prop_hdr || !prop_val) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_BAD_POINTER; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.set_property) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + set_property(cctxt, prop_hdr, prop_val); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_set_property); + +u32 vcd_get_property(void *handle, + struct vcd_property_hdr *prop_hdr, void *prop_val) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_get_property:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + if (!prop_hdr || !prop_val) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_BAD_POINTER; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.get_property) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + get_property(cctxt, prop_hdr, prop_val); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_get_property); + +u32 vcd_set_buffer_requirements(void *handle, + enum vcd_buffer_type buffer, + struct vcd_buffer_requirement *buffer_req) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_set_buffer_requirements:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + if (!buffer_req) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_BAD_POINTER; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr. + set_buffer_requirements) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + set_buffer_requirements(cctxt, buffer, buffer_req); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_set_buffer_requirements); + +u32 vcd_get_buffer_requirements(void *handle, + enum vcd_buffer_type buffer, + struct vcd_buffer_requirement *buffer_req) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_get_buffer_requirements:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + if (!buffer_req) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_BAD_POINTER; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr. + get_buffer_requirements) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + get_buffer_requirements(cctxt, buffer, buffer_req); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_get_buffer_requirements); + +u32 vcd_set_buffer(void *handle, + enum vcd_buffer_type buffer_type, u8 *buffer, u32 buf_size) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_set_buffer:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + if (!buffer || !buf_size) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_BAD_POINTER; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.set_buffer) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + set_buffer(cctxt, buffer_type, buffer, buf_size); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_set_buffer); + +u32 vcd_allocate_buffer(void *handle, + enum vcd_buffer_type buffer, + u32 buf_size, u8 **vir_buf_addr, u8 **phy_buf_addr) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_allocate_buffer:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + if (!vir_buf_addr || !phy_buf_addr + || !buf_size) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_BAD_POINTER; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.allocate_buffer) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + allocate_buffer(cctxt, buffer, buf_size, + vir_buf_addr, phy_buf_addr); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_allocate_buffer); + +u32 vcd_free_buffer(void *handle, enum vcd_buffer_type buffer_type, u8 *buffer) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_free_buffer:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.free_buffer) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + free_buffer(cctxt, buffer_type, buffer); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_free_buffer); + +u32 vcd_fill_output_buffer(void *handle, struct vcd_frame_data *buffer) +{ + struct vcd_clnt_ctxt *cctxt = + (struct vcd_clnt_ctxt *)handle; + struct vcd_drv_ctxt *drv_ctxt; + u32 rc; + + VCD_MSG_MED("vcd_fill_output_buffer:"); + + if (!cctxt || cctxt->signature != VCD_SIGNATURE) { + VCD_MSG_ERROR("Bad client handle"); + + return VCD_ERR_BAD_HANDLE; + } + + if (!buffer) { + VCD_MSG_ERROR("Bad parameters"); + + return VCD_ERR_BAD_POINTER; + } + + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (cctxt->clnt_state.state_table->ev_hdlr.fill_output_buffer) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + fill_output_buffer(cctxt, buffer); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_fill_output_buffer); + +u32 vcd_set_device_power(s32 driver_handle, + enum vcd_power_state pwr_state) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_drv_ctxt *drv_ctxt; + + VCD_MSG_MED("vcd_set_device_power:"); + + drv_ctxt = vcd_get_drv_context(); + mutex_lock(&drv_ctxt->dev_mutex); + + if (drv_ctxt->dev_state.state_table->ev_hdlr.set_dev_pwr) { + rc = drv_ctxt->dev_state.state_table->ev_hdlr. + set_dev_pwr(drv_ctxt, pwr_state); + } else { + VCD_MSG_ERROR("Unsupported API in device state %d", + drv_ctxt->dev_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + mutex_unlock(&drv_ctxt->dev_mutex); + + return rc; + +} +EXPORT_SYMBOL(vcd_set_device_power); + +void vcd_read_and_clear_interrupt(void) +{ + VCD_MSG_LOW("vcd_read_and_clear_interrupt:"); + ddl_read_and_clear_interrupt(); +} + + +void vcd_response_handler(void) +{ + struct vcd_drv_ctxt *drv_ctxt; + + VCD_MSG_LOW("vcd_response_handler:"); + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + + if (!ddl_process_core_response()) { + VCD_MSG_HIGH + ("ddl_process_core_response indicated no further" + "processing"); + mutex_unlock(&drv_ctxt->dev_mutex); + return; + } + + if (drv_ctxt->dev_ctxt.command_continue) + vcd_continue(); + mutex_unlock(&drv_ctxt->dev_mutex); +} +EXPORT_SYMBOL(vcd_response_handler); + +u8 vcd_get_num_of_clients(void) +{ + struct vcd_drv_ctxt *drv_ctxt; + struct vcd_clnt_ctxt *cctxt; + u8 count = 0; + + VCD_MSG_LOW("vcd_get_num_of_clients:"); + drv_ctxt = vcd_get_drv_context(); + + mutex_lock(&drv_ctxt->dev_mutex); + cctxt = drv_ctxt->dev_ctxt.cctxt_list_head; + while (cctxt) { + count++; + cctxt = cctxt->next; + } + mutex_unlock(&drv_ctxt->dev_mutex); + return count; +} +EXPORT_SYMBOL(vcd_get_num_of_clients); + + + + diff --git a/drivers/video/msm/vidc/common/vcd/vcd_api.h b/drivers/video/msm/vidc/common/vcd/vcd_api.h new file mode 100644 index 00000000000..38ea2027b8a --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_api.h @@ -0,0 +1,140 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_API_H_ +#define _VCD_API_H_ +#include "vcd_property.h" +#include "vcd_status.h" + +#define VCD_FRAME_FLAG_EOS 0x00000001 +#define VCD_FRAME_FLAG_DECODEONLY 0x00000004 +#define VCD_FRAME_FLAG_ENDOFFRAME 0x00000010 +#define VCD_FRAME_FLAG_SYNCFRAME 0x00000020 +#define VCD_FRAME_FLAG_EXTRADATA 0x00000040 +#define VCD_FRAME_FLAG_CODECCONFIG 0x00000080 +#define VCD_FRAME_FLAG_BFRAME 0x00100000 + +#define VCD_FLUSH_INPUT 0x0001 +#define VCD_FLUSH_OUTPUT 0x0002 +#define VCD_FLUSH_ALL 0x0003 + +#define VCD_FRAMETAG_INVALID 0xffffffff + +struct vcd_handle_container { + void *handle; +}; +struct vcd_flush_cmd { + u32 mode; +}; + +enum vcd_frame { + VCD_FRAME_YUV = 1, + VCD_FRAME_I, + VCD_FRAME_P, + VCD_FRAME_B, + VCD_FRAME_NOTCODED, + VCD_FRAME_32BIT = 0x7fffffff +}; + +enum vcd_power_state { + VCD_PWR_STATE_ON = 1, + VCD_PWR_STATE_SLEEP, +}; + +struct vcd_frame_data { + u8 *virtual; + u8 *physical; + u32 alloc_len; + u32 data_len; + u32 offset; + s64 time_stamp; + u32 flags; + u32 frm_clnt_data; + struct vcd_property_dec_output_buffer dec_op_prop; + u32 interlaced; + enum vcd_frame frame; + u32 ip_frm_tag; + u32 intrlcd_ip_frm_tag; +}; + +struct vcd_sequence_hdr { + u8 *sequence_header; + u32 sequence_header_len; + +}; + +enum vcd_buffer_type { + VCD_BUFFER_INPUT = 0x1, + VCD_BUFFER_OUTPUT = 0x2, + VCD_BUFFER_INVALID = 0x3, + VCD_BUFFER_32BIT = 0x7FFFFFFF +}; + +struct vcd_buffer_requirement { + u32 min_count; + u32 actual_count; + u32 max_count; + size_t sz; + u32 align; + u32 buf_pool_id; +}; + +struct vcd_init_config { + void *device_name; + void *(*map_dev_base_addr) (void *device_name); + void (*un_map_dev_base_addr) (void); + void (*interrupt_clr) (void); + void (*register_isr) (void *device_name); + void (*deregister_isr) (void); + u32 (*timer_create) (void (*timer_handler)(void *), + void *user_data, void **timer_handle); + void (*timer_release) (void *timer_handle); + void (*timer_start) (void *timer_handle, u32 time_out); + void (*timer_stop) (void *timer_handle); +}; + +u32 vcd_init(struct vcd_init_config *config, s32 *driver_handle); +u32 vcd_term(s32 driver_handle); +u32 vcd_open(s32 driver_handle, u32 decoding, + void (*callback) (u32 event, u32 status, void *info, size_t sz, + void *handle, void *const client_data), void *client_data); +u32 vcd_close(void *handle); +u32 vcd_encode_start(void *handle); +u32 vcd_encode_frame(void *handle, struct vcd_frame_data *input_frame); +u32 vcd_decode_start(void *handle, struct vcd_sequence_hdr *seq_hdr); +u32 vcd_decode_frame(void *handle, struct vcd_frame_data *input_frame); +u32 vcd_pause(void *handle); +u32 vcd_resume(void *handle); +u32 vcd_flush(void *handle, u32 mode); +u32 vcd_stop(void *handle); +u32 vcd_set_property(void *handle, struct vcd_property_hdr *prop_hdr, + void *prop_val); +u32 vcd_get_property(void *handle, struct vcd_property_hdr *prop_hdr, + void *prop_val); +u32 vcd_set_buffer_requirements(void *handle, enum vcd_buffer_type buffer, + struct vcd_buffer_requirement *buffer_req); +u32 vcd_get_buffer_requirements(void *handle, enum vcd_buffer_type buffer, + struct vcd_buffer_requirement *buffer_req); +u32 vcd_set_buffer(void *handle, enum vcd_buffer_type buffer_type, + u8 *buffer, u32 buf_size); +u32 vcd_allocate_buffer(void *handle, enum vcd_buffer_type buffer, + u32 buf_size, u8 **vir_buf_addr, u8 **phy_buf_addr); + +u32 vcd_free_buffer(void *handle, enum vcd_buffer_type buffer_type, u8 *buffer); +u32 vcd_fill_output_buffer(void *handle, struct vcd_frame_data *buffer); +u32 vcd_set_device_power(s32 driver_handle, + enum vcd_power_state pwr_state); +void vcd_read_and_clear_interrupt(void); +void vcd_response_handler(void); +u8 vcd_get_num_of_clients(void); + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vcd_client_sm.c b/drivers/video/msm/vidc/common/vcd/vcd_client_sm.c new file mode 100644 index 00000000000..973ed485811 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_client_sm.c @@ -0,0 +1,1827 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd.h" + +static const struct vcd_clnt_state_table *vcd_clnt_state_table[]; + +void vcd_clnt_handle_device_err_fatal(struct vcd_clnt_ctxt *cctxt, + u32 event) +{ + if (cctxt->clnt_state.state == VCD_CLIENT_STATE_NULL) { + cctxt->callback(VCD_EVT_RESP_OPEN, VCD_ERR_HW_FATAL, NULL, 0, + cctxt, cctxt->client_data); + vcd_destroy_client_context(cctxt); + return; + } + if (event == VCD_EVT_RESP_BASE) + event = VCD_EVT_IND_HWERRFATAL; + if (cctxt->clnt_state.state != VCD_CLIENT_STATE_INVALID) { + cctxt->callback(event, VCD_ERR_HW_FATAL, NULL, 0, + cctxt, cctxt->client_data); + vcd_flush_buffers_in_err_fatal(cctxt); + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_INVALID, + CLIENT_STATE_EVENT_NUMBER(clnt_cb)); + } +} + +static u32 vcd_close_in_open(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_close_in_open:"); + if (cctxt->in_buf_pool.allocated || + cctxt->out_buf_pool.allocated) { + VCD_MSG_ERROR("\n Allocated buffers are not freed yet"); + return VCD_ERR_ILLEGAL_OP; + } + vcd_destroy_client_context(cctxt); + return rc; +} + +static u32 vcd_close_in_invalid(struct vcd_clnt_ctxt *cctxt) +{ + VCD_MSG_LOW("vcd_close_in_invalid:"); + if (cctxt->in_buf_pool.allocated || + cctxt->out_buf_pool.allocated){ + VCD_MSG_ERROR("Allocated buffers are not freed yet"); + return VCD_ERR_ILLEGAL_OP; + } + + if (cctxt->status.mask & VCD_CLEANING_UP) + cctxt->status.mask |= VCD_CLOSE_PENDING; + else + vcd_destroy_client_context(cctxt); + return VCD_S_SUCCESS; +} + +static u32 vcd_start_in_run_cmn(struct vcd_clnt_ctxt *cctxt) +{ + VCD_MSG_LOW("vcd_start_in_run_cmn:"); + cctxt->callback(VCD_EVT_RESP_START, VCD_S_SUCCESS, NULL, 0, + cctxt, cctxt->client_data); + return VCD_S_SUCCESS; + +} + +static u32 vcd_encode_start_in_open(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_property_hdr prop_hdr; + struct vcd_property_vop_timing timing; + + VCD_MSG_LOW("vcd_encode_start_in_open:"); + + if (cctxt->decoding) { + VCD_MSG_ERROR("vcd_encode_init for decoder client"); + + return VCD_ERR_ILLEGAL_OP; + } + + if (!cctxt->in_buf_pool.entries || + !cctxt->out_buf_pool.entries || + cctxt->in_buf_pool.validated != cctxt->in_buf_pool.count || + cctxt->out_buf_pool.validated != + cctxt->out_buf_pool.count) { + VCD_MSG_ERROR("Buffer pool is not completely setup yet"); + + return VCD_ERR_BAD_STATE; + } + + rc = vcd_sched_add_client(cctxt); + VCD_FAILED_RETURN(rc, "Failed: vcd_sched_add_client"); + + prop_hdr.prop_id = VCD_I_VOP_TIMING; + prop_hdr.sz = sizeof(struct vcd_property_vop_timing); + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, &timing); + + VCD_FAILED_RETURN(rc, "Failed: Get VCD_I_VOP_TIMING"); + if (!timing.vop_time_resolution) { + VCD_MSG_ERROR("Vop_time_resolution value is zero"); + return VCD_ERR_FAIL; + } + cctxt->time_resoln = timing.vop_time_resolution; + + rc = vcd_process_cmd_sess_start(cctxt); + + if (!VCD_FAILED(rc)) { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_STARTING, + CLIENT_STATE_EVENT_NUMBER + (encode_start)); + } + + return rc; +} + +static u32 vcd_encode_start_in_run(struct vcd_clnt_ctxt + *cctxt) +{ + VCD_MSG_LOW("vcd_encode_start_in_run:"); + (void) vcd_start_in_run_cmn(cctxt); + return VCD_S_SUCCESS; +} + + +static u32 vcd_encode_frame_cmn(struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame) +{ + VCD_MSG_LOW("vcd_encode_frame_cmn in %d:", cctxt->clnt_state.state); + + if (cctxt->decoding) { + VCD_MSG_ERROR("vcd_encode_frame for decoder client"); + + return VCD_ERR_ILLEGAL_OP; + } + + return vcd_handle_input_frame(cctxt, input_frame); +} + +static u32 vcd_decode_start_in_open + (struct vcd_clnt_ctxt *cctxt, + struct vcd_sequence_hdr *seq_hdr) +{ + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_decode_start_in_open:"); + + if (!cctxt->decoding) { + VCD_MSG_ERROR("vcd_decode_init for encoder client"); + + return VCD_ERR_ILLEGAL_OP; + } + + if (seq_hdr) { + VCD_MSG_HIGH("Seq hdr supplied. len = %d", + seq_hdr->sequence_header_len); + + rc = vcd_store_seq_hdr(cctxt, seq_hdr); + + } else { + VCD_MSG_HIGH("Seq hdr not supplied"); + + cctxt->seq_hdr.sequence_header_len = 0; + cctxt->seq_hdr.sequence_header = NULL; + } + + VCD_FAILED_RETURN(rc, "Err processing seq hdr"); + + rc = vcd_process_cmd_sess_start(cctxt); + + if (!VCD_FAILED(rc)) { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_STARTING, + CLIENT_STATE_EVENT_NUMBER + (decode_start)); + } + + return rc; +} + +static u32 vcd_decode_start_in_run(struct vcd_clnt_ctxt *cctxt, + struct vcd_sequence_hdr *seqhdr) +{ + VCD_MSG_LOW("vcd_decode_start_in_run:"); + (void) vcd_start_in_run_cmn(cctxt); + return VCD_S_SUCCESS; +} + +static u32 vcd_decode_frame_cmn + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame) +{ + VCD_MSG_LOW("vcd_decode_frame_cmn in %d:", cctxt->clnt_state.state); + + if (!cctxt->decoding) { + VCD_MSG_ERROR("Decode_frame api called for Encoder client"); + + return VCD_ERR_ILLEGAL_OP; + } + + return vcd_handle_input_frame(cctxt, input_frame); +} + +static u32 vcd_pause_in_run(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_pause_in_run:"); + + if (cctxt->sched_clnt_hdl) { + rc = vcd_sched_suspend_resume_clnt(cctxt, false); + VCD_FAILED_RETURN(rc, "Failed: vcd_sched_suspend_resume_clnt"); + } + + if (cctxt->status.frame_submitted > 0) { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_PAUSING, + CLIENT_STATE_EVENT_NUMBER + (pause)); + + } else { + VCD_MSG_HIGH("No client frames are currently being processed"); + + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_PAUSED, + CLIENT_STATE_EVENT_NUMBER + (pause)); + + cctxt->callback(VCD_EVT_RESP_PAUSE, + VCD_S_SUCCESS, + NULL, 0, cctxt, cctxt->client_data); + + rc = vcd_power_event(cctxt->dev_ctxt, cctxt, + VCD_EVT_PWR_CLNT_PAUSE); + + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("VCD_EVT_PWR_CLNT_PAUSE_END failed"); + + } + + return VCD_S_SUCCESS; +} + +static u32 vcd_resume_in_paused(struct vcd_clnt_ctxt *cctxt) +{ + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_resume_in_paused:"); + + + if (cctxt->sched_clnt_hdl) { + rc = vcd_power_event(cctxt->dev_ctxt, + cctxt, VCD_EVT_PWR_CLNT_RESUME); + + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR("VCD_EVT_PWR_CLNT_RESUME failed"); + } else { + rc = vcd_sched_suspend_resume_clnt(cctxt, true); + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR + ("rc = 0x%x. Failed: " + "vcd_sched_suspend_resume_clnt", + rc); + } + + } + if (!VCD_FAILED(rc)) { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_RUN, + CLIENT_STATE_EVENT_NUMBER + (resume)); + vcd_try_submit_frame(dev_ctxt); + } + } else { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_RUN, + CLIENT_STATE_EVENT_NUMBER + (resume)); + } + + return rc; +} + +static u32 vcd_flush_cmn(struct vcd_clnt_ctxt *cctxt, u32 mode) +{ + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_flush_cmn in %d:", cctxt->clnt_state.state); + + rc = vcd_flush_buffers(cctxt, mode); + + VCD_FAILED_RETURN(rc, "Failed: vcd_flush_buffers"); + + if (cctxt->status.frame_submitted > 0) { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_FLUSHING, + CLIENT_STATE_EVENT_NUMBER + (flush)); + } else { + VCD_MSG_HIGH("All buffers are flushed"); + cctxt->status.mask |= (mode & VCD_FLUSH_ALL); + vcd_send_flush_done(cctxt, VCD_S_SUCCESS); + } + + return rc; +} + +static u32 vcd_flush_inopen(struct vcd_clnt_ctxt *cctxt, + u32 mode) +{ + VCD_MSG_LOW("vcd_flush_inopen:"); + cctxt->status.mask |= (mode & VCD_FLUSH_ALL); + vcd_send_flush_done(cctxt, VCD_S_SUCCESS); + return VCD_S_SUCCESS; +} + +static u32 vcd_flush_in_flushing + (struct vcd_clnt_ctxt *cctxt, u32 mode) +{ + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_flush_in_flushing:"); + + rc = vcd_flush_buffers(cctxt, mode); + + return rc; +} + +static u32 vcd_flush_in_eos(struct vcd_clnt_ctxt *cctxt, + u32 mode) +{ + VCD_MSG_LOW("vcd_flush_in_eos:"); + + if (mode > VCD_FLUSH_ALL || !mode) { + VCD_MSG_ERROR("Invalid flush mode %d", mode); + + return VCD_ERR_ILLEGAL_PARM; + } + + VCD_MSG_MED("Flush mode requested %d", mode); + + cctxt->status.mask |= (mode & VCD_FLUSH_ALL); + + return VCD_S_SUCCESS; +} + +static u32 vcd_flush_in_invalid(struct vcd_clnt_ctxt *cctxt, + u32 mode) +{ + u32 rc = VCD_S_SUCCESS; + VCD_MSG_LOW("vcd_flush_in_invalid:"); + if (!(cctxt->status.mask & VCD_CLEANING_UP)) { + rc = vcd_flush_buffers(cctxt, mode); + if (!VCD_FAILED(rc)) { + VCD_MSG_HIGH("All buffers are flushed"); + cctxt->status.mask |= (mode & VCD_FLUSH_ALL); + vcd_send_flush_done(cctxt, VCD_S_SUCCESS); + } + } else + cctxt->status.mask |= (mode & VCD_FLUSH_ALL); + return rc; +} + +static u32 vcd_stop_cmn(struct vcd_clnt_ctxt *cctxt) +{ + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + u32 rc = VCD_S_SUCCESS; + struct vcd_transc *transc; + + VCD_MSG_LOW("vcd_stop_cmn in %d:", cctxt->clnt_state.state); + + rc = vcd_flush_buffers(cctxt, VCD_FLUSH_ALL); + + VCD_FAILED_RETURN(rc, "Failed: vcd_flush_buffers"); + + if (!cctxt->status.frame_submitted) { + + if (vcd_get_command_channel(dev_ctxt, &transc)) { + rc = vcd_power_event(dev_ctxt, cctxt, + VCD_EVT_PWR_CLNT_CMD_BEGIN); + + if (!VCD_FAILED(rc)) { + transc->type = VCD_CMD_CODEC_STOP; + transc->cctxt = cctxt; + + rc = vcd_submit_cmd_sess_end(transc); + } else { + VCD_MSG_ERROR("Failed:" + " VCD_EVT_PWR_CLNT_CMD_BEGIN"); + } + + if (VCD_FAILED(rc)) { + vcd_release_command_channel(dev_ctxt, + transc); + } + + } else { + vcd_client_cmd_flush_and_en_q(cctxt, + VCD_CMD_CODEC_STOP); + } + } + + if (VCD_FAILED(rc)) { + (void)vcd_power_event(dev_ctxt, cctxt, + VCD_EVT_PWR_CLNT_CMD_FAIL); + } else { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_STOPPING, + CLIENT_STATE_EVENT_NUMBER + (stop)); + } + + return rc; +} + + +static u32 vcd_stop_inopen(struct vcd_clnt_ctxt *cctxt) +{ + VCD_MSG_LOW("vcd_stop_inopen:"); + + cctxt->callback(VCD_EVT_RESP_STOP, VCD_S_SUCCESS, + NULL, 0, cctxt, + cctxt->client_data); + + return VCD_S_SUCCESS; +} + +static u32 vcd_stop_in_run(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + VCD_MSG_LOW("vcd_stop_in_run:"); + rc = vcd_stop_cmn(cctxt); + if (!VCD_FAILED(rc) && + (cctxt->status.mask & VCD_FIRST_IP_RCVD)) { + rc = vcd_power_event(cctxt->dev_ctxt, + cctxt, VCD_EVT_PWR_CLNT_LAST_FRAME); + } + return rc; +} + +static u32 vcd_stop_in_eos(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + VCD_MSG_LOW("vcd_stop_in_eos:"); + if (cctxt->status.mask & VCD_EOS_WAIT_OP_BUF) { + rc = vcd_stop_cmn(cctxt); + if (!VCD_FAILED(rc)) { + rc = vcd_power_event(cctxt->dev_ctxt, + cctxt, VCD_EVT_PWR_CLNT_LAST_FRAME); + cctxt->status.mask &= ~VCD_EOS_WAIT_OP_BUF; + } + } else + cctxt->status.mask |= VCD_STOP_PENDING; + return rc; +} + +static u32 vcd_stop_in_invalid(struct vcd_clnt_ctxt *cctxt) +{ + VCD_MSG_LOW("vcd_stop_in_invalid:"); + if (cctxt->status.mask & VCD_CLEANING_UP) { + cctxt->status.mask |= VCD_STOP_PENDING; + } else { + (void) vcd_flush_buffers(cctxt, VCD_FLUSH_ALL); + cctxt->callback(VCD_EVT_RESP_STOP, VCD_S_SUCCESS, NULL, + 0, cctxt, cctxt->client_data); + } + return VCD_S_SUCCESS; +} + +static u32 vcd_set_property_cmn + (struct vcd_clnt_ctxt *cctxt, + struct vcd_property_hdr *prop_hdr, void *prop_val) +{ + u32 rc; + VCD_MSG_LOW("vcd_set_property_cmn in %d:", cctxt->clnt_state.state); + VCD_MSG_LOW("property Id = %d", prop_hdr->prop_id); + if (!prop_hdr->sz || !prop_hdr->prop_id) { + VCD_MSG_MED("Bad parameters"); + return VCD_ERR_ILLEGAL_PARM; + } + + rc = ddl_set_property(cctxt->ddl_handle, prop_hdr, prop_val); + VCD_FAILED_RETURN(rc, "Failed: ddl_set_property"); + switch (prop_hdr->prop_id) { + case VCD_I_LIVE: + { + struct vcd_property_live *live = + (struct vcd_property_live *)prop_val; + cctxt->live = live->live; + break; + } + case VCD_I_FRAME_RATE: + { + if (cctxt->sched_clnt_hdl) { + rc = vcd_set_frame_rate(cctxt, + (struct vcd_property_frame_rate *) + prop_val); + } + break; + } + case VCD_I_FRAME_SIZE: + { + if (cctxt->sched_clnt_hdl) { + rc = vcd_set_frame_size(cctxt, + (struct vcd_property_frame_size *) + prop_val); + } + break; + } + case VCD_I_INTRA_PERIOD: + { + struct vcd_property_i_period *iperiod = + (struct vcd_property_i_period *)prop_val; + cctxt->bframe = iperiod->b_frames; + break; + } + default: + { + break; + } + } + return rc; +} + +static u32 vcd_get_property_cmn + (struct vcd_clnt_ctxt *cctxt, + struct vcd_property_hdr *prop_hdr, void *prop_val) +{ + VCD_MSG_LOW("vcd_get_property_cmn in %d:", cctxt->clnt_state.state); + VCD_MSG_LOW("property Id = %d", prop_hdr->prop_id); + if (!prop_hdr->sz || !prop_hdr->prop_id) { + VCD_MSG_MED("Bad parameters"); + + return VCD_ERR_ILLEGAL_PARM; + } + return ddl_get_property(cctxt->ddl_handle, prop_hdr, prop_val); +} + +static u32 vcd_set_buffer_requirements_cmn + (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer, + struct vcd_buffer_requirement *buffer_req) +{ + struct vcd_property_hdr Prop_hdr; + u32 rc = VCD_S_SUCCESS; + struct vcd_buffer_pool *buf_pool; + u32 first_frm_recvd = 0; + + VCD_MSG_LOW("vcd_set_buffer_requirements_cmn in %d:", + cctxt->clnt_state.state); + + if (!cctxt->decoding && + cctxt->clnt_state.state != VCD_CLIENT_STATE_OPEN) { + VCD_MSG_ERROR("Bad state (%d) for encoder", + cctxt->clnt_state.state); + + return VCD_ERR_BAD_STATE; + } + + VCD_MSG_MED("Buffer type = %d", buffer); + + if (buffer == VCD_BUFFER_INPUT) { + Prop_hdr.prop_id = DDL_I_INPUT_BUF_REQ; + buf_pool = &cctxt->in_buf_pool; + first_frm_recvd = VCD_FIRST_IP_RCVD; + } else if (buffer == VCD_BUFFER_OUTPUT) { + Prop_hdr.prop_id = DDL_I_OUTPUT_BUF_REQ; + buf_pool = &cctxt->out_buf_pool; + first_frm_recvd = VCD_FIRST_OP_RCVD; + } else { + rc = VCD_ERR_ILLEGAL_PARM; + } + + VCD_FAILED_RETURN(rc, "Invalid buffer type provided"); + + if (buf_pool->validated > 0) { + VCD_MSG_ERROR("Need to free allocated buffers"); + return VCD_ERR_ILLEGAL_OP; + } + + first_frm_recvd &= cctxt->status.mask; + if (first_frm_recvd) { + VCD_MSG_ERROR("VCD SetBufReq called when data path is active"); + return VCD_ERR_BAD_STATE; + } + Prop_hdr.sz = sizeof(*buffer_req); + rc = ddl_set_property(cctxt->ddl_handle, &Prop_hdr, buffer_req); + VCD_FAILED_RETURN(rc, "Failed: ddl_set_property"); + if (buf_pool->entries) { + VCD_MSG_MED("Resetting buffer requirements"); + vcd_free_buffer_pool_entries(buf_pool); + } + return rc; +} + +static u32 vcd_get_buffer_requirements_cmn + (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer, + struct vcd_buffer_requirement *buffer_req) +{ + struct vcd_property_hdr Prop_hdr; + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_get_buffer_requirements_cmn in %d:", + cctxt->clnt_state.state); + + VCD_MSG_MED("Buffer type = %d", buffer); + + if (buffer == VCD_BUFFER_INPUT) + Prop_hdr.prop_id = DDL_I_INPUT_BUF_REQ; + else if (buffer == VCD_BUFFER_OUTPUT) + Prop_hdr.prop_id = DDL_I_OUTPUT_BUF_REQ; + else + rc = VCD_ERR_ILLEGAL_PARM; + + VCD_FAILED_RETURN(rc, "Invalid buffer type provided"); + + Prop_hdr.sz = sizeof(*buffer_req); + + return ddl_get_property(cctxt->ddl_handle, &Prop_hdr, buffer_req); + +} + +static u32 vcd_set_buffer_cmn + (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer_type, u8 *buffer, u32 buf_size) +{ + u32 rc; + struct vcd_buffer_pool *buf_pool; + + VCD_MSG_LOW("vcd_set_buffer_cmn in %d:", cctxt->clnt_state.state); + + rc = vcd_common_allocate_set_buffer(cctxt, buffer_type, buf_size, + &buf_pool); + + if (!VCD_FAILED(rc)) { + rc = vcd_set_buffer_internal(cctxt, buf_pool, buffer, + buf_size); + } + + return rc; +} + +static u32 vcd_allocate_buffer_cmn + (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer, + u32 buf_size, u8 **vir_buf_addr, u8 **phy_buf_addr) +{ + u32 rc; + struct vcd_buffer_pool *buf_pool; + + VCD_MSG_LOW("vcd_allocate_buffer_cmn in %d:", + cctxt->clnt_state.state); + + rc = vcd_common_allocate_set_buffer(cctxt, buffer, buf_size, + &buf_pool); + + if (!VCD_FAILED(rc)) { + rc = vcd_allocate_buffer_internal(cctxt, + buf_pool, + buf_size, + vir_buf_addr, + phy_buf_addr); + } + + return rc; +} + +static u32 vcd_free_buffer_cmn + (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer_type, u8 *buffer) +{ + + VCD_MSG_LOW("vcd_free_buffer_cmn in %d:", cctxt->clnt_state.state); + + return vcd_free_one_buffer_internal(cctxt, buffer_type, buffer); +} + +static u32 vcd_fill_output_buffer_cmn + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *buffer) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_buffer_entry *buf_entry; + u32 result = true; + u32 handled = true; + if (!cctxt || !buffer) { + VCD_MSG_ERROR("%s(): Inavlid params cctxt %p buffer %p", + __func__, cctxt, buffer); + return VCD_ERR_BAD_POINTER; + } + VCD_MSG_LOW("vcd_fill_output_buffer_cmn in %d:", + cctxt->clnt_state.state); + if (cctxt->status.mask & VCD_IN_RECONFIG) { + buffer->time_stamp = 0; + buffer->data_len = 0; + VCD_MSG_LOW("In reconfig: Return output buffer"); + cctxt->callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_S_SUCCESS, + buffer, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + return rc; + } + buf_entry = vcd_check_fill_output_buffer(cctxt, buffer); + if (!buf_entry) + return VCD_ERR_BAD_POINTER; + + if (!(cctxt->status.mask & VCD_FIRST_OP_RCVD)) { + rc = vcd_handle_first_fill_output_buffer(cctxt, buffer, + &handled); + VCD_FAILED_RETURN(rc, + "Failed: vcd_handle_first_fill_output_buffer"); + if (handled) + return rc ; + } + + result = + vcd_buffer_pool_entry_en_q(&cctxt->out_buf_pool, buf_entry); + + if (!result && !cctxt->decoding) { + VCD_MSG_ERROR("Failed: vcd_buffer_pool_entry_en_q"); + + return VCD_ERR_FAIL; + } + + buf_entry->frame = *buffer; + rc = vcd_return_op_buffer_to_hw(cctxt, buf_entry); + if (!VCD_FAILED(rc) && cctxt->sched_clnt_hdl) { + cctxt->sched_clnt_hdl->tkns++; + vcd_try_submit_frame(cctxt->dev_ctxt); + } + return rc; +} + +static u32 vcd_fill_output_buffer_in_eos + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *buffer) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_buffer_entry *buf_entry; + + VCD_MSG_LOW("vcd_fill_output_buffer_in_eos:"); + + buf_entry = vcd_check_fill_output_buffer(cctxt, buffer); + if (!buf_entry) + return VCD_ERR_BAD_POINTER; + + if (cctxt->status.mask & VCD_EOS_WAIT_OP_BUF) { + VCD_MSG_HIGH("Got an output buffer we were waiting for"); + + buf_entry->frame = *buffer; + + buf_entry->frame.data_len = 0; + buf_entry->frame.flags |= VCD_FRAME_FLAG_EOS; + buf_entry->frame.ip_frm_tag = + cctxt->status.eos_trig_ip_frm.ip_frm_tag; + buf_entry->frame.time_stamp = + cctxt->status.eos_trig_ip_frm.time_stamp; + + cctxt->callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_S_SUCCESS, + &buf_entry->frame, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + + cctxt->status.mask &= ~VCD_EOS_WAIT_OP_BUF; + + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_RUN, + CLIENT_STATE_EVENT_NUMBER + (fill_output_buffer)); + + } else { + rc = vcd_fill_output_buffer_cmn(cctxt, buffer); + } + + return rc; +} + +static void vcd_clnt_cb_in_starting + (struct vcd_clnt_ctxt *cctxt, + u32 event, u32 status, void *payload, size_t sz, + u32 *ddl_handle, void *const client_data) +{ + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + struct vcd_transc *transc = + (struct vcd_transc *)client_data; + VCD_MSG_LOW("vcd_clnt_cb_in_starting:"); + if (cctxt->ddl_handle != ddl_handle) { + VCD_MSG_ERROR("vcd_clnt_cb_in_initing: Wrong DDL handle %p", + ddl_handle); + return; + } + + switch (event) { + case VCD_EVT_RESP_START: + { + vcd_handle_start_done(cctxt, + (struct vcd_transc *)client_data, + status); + break; + } + case VCD_EVT_RESP_STOP: + { + vcd_handle_stop_done_in_starting(cctxt, + (struct vcd_transc *)client_data, + status); + break; + } + case VCD_EVT_IND_HWERRFATAL: + { + cctxt->status.cmd_submitted--; + vcd_mark_command_channel(cctxt->dev_ctxt, transc); + vcd_handle_err_fatal(cctxt, VCD_EVT_RESP_START, + status); + break; + } + default: + { + VCD_MSG_ERROR("Unexpected callback event=%d status=%d " + "from DDL", event, status); + dev_ctxt->command_continue = false; + break; + } + } +} + +static void vcd_clnt_cb_in_run + (struct vcd_clnt_ctxt *cctxt, + u32 event, + u32 status, + void *payload, size_t sz, u32 *ddl_handle, void *const client_data) +{ + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + u32 rc = VCD_S_SUCCESS; + + if (cctxt->ddl_handle != ddl_handle) { + VCD_MSG_ERROR("ddl_handle mismatch"); + + return; + } + + switch (event) { + case VCD_EVT_RESP_INPUT_DONE: + { + rc = vcd_handle_input_done(cctxt, payload, event, + status); + + break; + } + + case VCD_EVT_RESP_OUTPUT_DONE: + { + + rc = vcd_handle_frame_done(cctxt, payload, event, + status); + + break; + } + case VCD_EVT_RESP_OUTPUT_REQ: + { + rc = vcd_handle_output_required(cctxt, payload, + status); + break; + } + + case VCD_EVT_IND_OUTPUT_RECONFIG: + { + rc = vcd_handle_ind_output_reconfig(cctxt, payload, + status); + break; + } + case VCD_EVT_RESP_TRANSACTION_PENDING: + { + vcd_handle_trans_pending(cctxt); + break; + } + + case VCD_EVT_IND_HWERRFATAL: + { + vcd_handle_ind_hw_err_fatal(cctxt, + VCD_EVT_IND_HWERRFATAL, status); + break; + } + case VCD_EVT_IND_INFO_OUTPUT_RECONFIG: + { + vcd_handle_ind_info_output_reconfig(cctxt, status); + break; + } + default: + { + VCD_MSG_ERROR + ("Unexpected callback event=%d status=%d from DDL", + event, status); + dev_ctxt->command_continue = false; + + break; + } + } + + if (!VCD_FAILED(rc) && + (event == VCD_EVT_RESP_INPUT_DONE || + event == VCD_EVT_RESP_OUTPUT_DONE || + event == VCD_EVT_RESP_OUTPUT_REQ)) { + + if (((struct ddl_frame_data_tag *) + payload)->frm_trans_end) + vcd_mark_frame_channel(cctxt->dev_ctxt); + } +} + +static void vcd_clnt_cb_in_eos + (struct vcd_clnt_ctxt *cctxt, + u32 event, + u32 status, + void *payload, size_t sz, u32 *ddl_handle, void *const client_data) { + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + struct vcd_transc *transc = NULL; + u32 frm_trans_end = false, rc = VCD_S_SUCCESS; + + if (cctxt->ddl_handle != ddl_handle) { + VCD_MSG_ERROR("ddl_handle mismatch"); + + return; + } + + switch (event) { + case VCD_EVT_RESP_INPUT_DONE: + { + rc = vcd_handle_input_done_in_eos(cctxt, payload, + status); + + break; + } + + case VCD_EVT_RESP_OUTPUT_DONE: + { + rc = vcd_handle_frame_done_in_eos(cctxt, payload, + status); + + break; + } + case VCD_EVT_RESP_OUTPUT_REQ: + { + rc = vcd_handle_output_required(cctxt, payload, + status); + break; + } + case VCD_EVT_RESP_EOS_DONE: + { + transc = (struct vcd_transc *)client_data; + vcd_handle_eos_done(cctxt, transc, status); + vcd_mark_frame_channel(cctxt->dev_ctxt); + break; + } + case VCD_EVT_IND_OUTPUT_RECONFIG: + { + rc = vcd_handle_ind_output_reconfig(cctxt, + payload, status); + if (!VCD_FAILED(rc)) { + frm_trans_end = true; + payload = NULL; + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_RUN, + CLIENT_STATE_EVENT_NUMBER + (clnt_cb)); + VCD_MSG_LOW + ("RECONFIGinEOS:Suspending Client"); + rc = vcd_sched_suspend_resume_clnt(cctxt, + false); + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR + ("Failed: suspend_resume_clnt. rc=0x%x", + rc); + } + } + break; + } + case VCD_EVT_IND_HWERRFATAL: + { + vcd_handle_ind_hw_err_fatal(cctxt, + VCD_EVT_IND_HWERRFATAL, status); + break; + } + case VCD_EVT_IND_INFO_OUTPUT_RECONFIG: + { + vcd_handle_ind_info_output_reconfig(cctxt, status); + break; + } + default: + { + VCD_MSG_ERROR + ("Unexpected callback event=%d status=%d from DDL", + event, status); + + dev_ctxt->command_continue = false; + + break; + } + + } + if (!VCD_FAILED(rc) && + (event == VCD_EVT_RESP_INPUT_DONE || + event == VCD_EVT_RESP_OUTPUT_DONE || + event == VCD_EVT_RESP_OUTPUT_REQ || + event == VCD_EVT_IND_OUTPUT_RECONFIG)) { + if (payload && ((struct ddl_frame_data_tag *) + payload)->frm_trans_end) { + vcd_mark_frame_channel(cctxt->dev_ctxt); + frm_trans_end = true; + } + if (frm_trans_end && !cctxt->status.frame_submitted) + vcd_handle_eos_trans_end(cctxt); + } +} + +static void vcd_clnt_cb_in_flushing + (struct vcd_clnt_ctxt *cctxt, + u32 event, + u32 status, + void *payload, size_t sz, u32 *ddl_handle, void *const client_data) { + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + u32 rc = VCD_S_SUCCESS; + u32 frm_trans_end = false; + + VCD_MSG_LOW("vcd_clnt_cb_in_flushing:"); + + if (cctxt->ddl_handle != ddl_handle) { + VCD_MSG_ERROR("ddl_handle mismatch"); + + return; + } + + switch (event) { + case VCD_EVT_RESP_INPUT_DONE: + { + rc = vcd_handle_input_done(cctxt, + payload, + VCD_EVT_RESP_INPUT_FLUSHED, + status); + + break; + } + + case VCD_EVT_RESP_OUTPUT_DONE: + { + + rc = vcd_handle_frame_done(cctxt, + payload, + VCD_EVT_RESP_OUTPUT_FLUSHED, + status); + + break; + } + case VCD_EVT_RESP_OUTPUT_REQ: + { + rc = vcd_handle_output_required_in_flushing(cctxt, + payload); + break; + } + case VCD_EVT_IND_OUTPUT_RECONFIG: + { + rc = vcd_handle_ind_output_reconfig(cctxt, + payload, status); + if (!VCD_FAILED(rc)) { + frm_trans_end = true; + payload = NULL; + } + break; + } + case VCD_EVT_IND_HWERRFATAL: + { + vcd_handle_ind_hw_err_fatal(cctxt, + VCD_EVT_IND_HWERRFATAL, status); + break; + } + default: + { + VCD_MSG_ERROR + ("Unexpected callback event=%d status=%d from DDL", + event, status); + + dev_ctxt->command_continue = false; + + break; + } + } + if (!VCD_FAILED(rc) && ((event == VCD_EVT_RESP_INPUT_DONE || + event == VCD_EVT_RESP_OUTPUT_DONE || + event == VCD_EVT_RESP_OUTPUT_REQ || + event == VCD_EVT_IND_OUTPUT_RECONFIG))) { + if (payload && + ((struct ddl_frame_data_tag *)\ + payload)->frm_trans_end) { + + vcd_mark_frame_channel(cctxt->dev_ctxt); + frm_trans_end = true; + } + if (frm_trans_end && !cctxt->status.frame_submitted) { + VCD_MSG_HIGH + ("All pending frames recvd from DDL"); + if (cctxt->status.mask & VCD_FLUSH_INPUT) + vcd_flush_bframe_buffers(cctxt, + VCD_FLUSH_INPUT); + if (cctxt->status.mask & VCD_FLUSH_OUTPUT) + vcd_flush_output_buffers(cctxt); + vcd_send_flush_done(cctxt, VCD_S_SUCCESS); + vcd_release_interim_frame_channels(dev_ctxt); + VCD_MSG_HIGH("Flush complete"); + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_RUN, + CLIENT_STATE_EVENT_NUMBER + (clnt_cb)); + } + } +} + +static void vcd_clnt_cb_in_stopping + (struct vcd_clnt_ctxt *cctxt, + u32 event, + u32 status, + void *payload, size_t sz, u32 *ddl_handle, void *const client_data) { + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + u32 rc = VCD_S_SUCCESS; + u32 frm_trans_end = false; + + VCD_MSG_LOW("vcd_clnt_cb_in_stopping:"); + + if (cctxt->ddl_handle != ddl_handle) { + VCD_MSG_ERROR("ddl_handle mismatch"); + + return; + } + + switch (event) { + + case VCD_EVT_RESP_INPUT_DONE: + { + rc = vcd_handle_input_done(cctxt, + payload, + VCD_EVT_RESP_INPUT_FLUSHED, + status); + + break; + } + + case VCD_EVT_RESP_OUTPUT_DONE: + { + + rc = vcd_handle_frame_done(cctxt, + payload, + VCD_EVT_RESP_OUTPUT_FLUSHED, + status); + + break; + } + case VCD_EVT_RESP_OUTPUT_REQ: + { + rc = vcd_handle_output_required_in_flushing(cctxt, + payload); + break; + } + case VCD_EVT_RESP_STOP: + { + vcd_handle_stop_done(cctxt, + (struct vcd_transc *) + client_data, status); + + break; + } + case VCD_EVT_IND_OUTPUT_RECONFIG: + { + (void) vcd_handle_ind_output_reconfig(cctxt, + payload, status); + + frm_trans_end = true; + payload = NULL; + + break; + } + case VCD_EVT_IND_HWERRFATAL: + { + vcd_handle_ind_hw_err_fatal(cctxt, VCD_EVT_RESP_STOP, + status); + break; + } + + default: + { + VCD_MSG_ERROR + ("Unexpected callback event=%d status=%d from DDL", + event, status); + + dev_ctxt->command_continue = false; + + break; + } + } + + if (!VCD_FAILED(rc) && ((event == VCD_EVT_RESP_INPUT_DONE || + event == VCD_EVT_RESP_OUTPUT_DONE) || + event == VCD_EVT_RESP_OUTPUT_REQ || + event == VCD_EVT_IND_OUTPUT_RECONFIG)) { + + if (payload && + ((struct ddl_frame_data_tag *)\ + payload)->frm_trans_end) { + + vcd_mark_frame_channel(cctxt->dev_ctxt); + frm_trans_end = true; + } + if (frm_trans_end && !cctxt->status.frame_submitted) { + VCD_MSG_HIGH + ("All pending frames recvd from DDL"); + vcd_flush_bframe_buffers(cctxt, + VCD_FLUSH_INPUT); + vcd_flush_output_buffers(cctxt); + cctxt->status.mask &= ~VCD_FLUSH_ALL; + vcd_release_all_clnt_frm_transc(cctxt); + VCD_MSG_HIGH + ("All buffers flushed. Enqueuing stop cmd"); + vcd_client_cmd_flush_and_en_q(cctxt, + VCD_CMD_CODEC_STOP); + } + } +} + +static void vcd_clnt_cb_in_pausing + (struct vcd_clnt_ctxt *cctxt, + u32 event, + u32 status, + void *payload, size_t sz, u32 *ddl_handle, void *const client_data) +{ + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + u32 rc = VCD_S_SUCCESS; + u32 frm_trans_end = false; + + VCD_MSG_LOW("vcd_clnt_cb_in_pausing:"); + + if (cctxt->ddl_handle != ddl_handle) { + VCD_MSG_ERROR("ddl_handle mismatch"); + + return; + } + + switch (event) { + case VCD_EVT_RESP_INPUT_DONE: + { + rc = vcd_handle_input_done(cctxt, payload, event, + status); + + break; + } + + case VCD_EVT_RESP_OUTPUT_DONE: + { + rc = vcd_handle_frame_done(cctxt, payload, event, + status); + break; + } + case VCD_EVT_RESP_OUTPUT_REQ: + { + rc = vcd_handle_output_required(cctxt, payload, + status); + break; + } + case VCD_EVT_IND_OUTPUT_RECONFIG: + { + rc = vcd_handle_ind_output_reconfig(cctxt, + payload, status); + if (!VCD_FAILED(rc)) { + frm_trans_end = true; + payload = NULL; + } + break; + } + case VCD_EVT_IND_HWERRFATAL: + { + vcd_handle_ind_hw_err_fatal(cctxt, + VCD_EVT_RESP_PAUSE, status); + rc = VCD_ERR_FAIL; + break; + } + default: + { + VCD_MSG_ERROR + ("Unexpected callback event=%d status=%d from DDL", + event, status); + + dev_ctxt->command_continue = false; + + break; + } + + } + + if (!VCD_FAILED(rc)) { + + if (payload && + ((struct ddl_frame_data_tag *)\ + payload)->frm_trans_end) { + + vcd_mark_frame_channel(cctxt->dev_ctxt); + frm_trans_end = true; + } + if (frm_trans_end && !cctxt->status.frame_submitted) { + VCD_MSG_HIGH + ("All pending frames recvd from DDL"); + + cctxt->callback(VCD_EVT_RESP_PAUSE, + VCD_S_SUCCESS, + NULL, + 0, + cctxt, + cctxt->client_data); + + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_PAUSED, + CLIENT_STATE_EVENT_NUMBER + (clnt_cb)); + + rc = vcd_power_event(cctxt->dev_ctxt, + cctxt, + VCD_EVT_PWR_CLNT_PAUSE); + + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR + ("VCD_EVT_PWR_CLNT_PAUSE_END" + "failed"); + } + } + } +} + +static void vcd_clnt_cb_in_invalid( + struct vcd_clnt_ctxt *cctxt, u32 event, u32 status, + void *payload, size_t sz, u32 *ddl_handle, + void *const client_data +) +{ + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + VCD_MSG_LOW("vcd_clnt_cb_in_invalid:"); + if (cctxt->ddl_handle != ddl_handle) { + VCD_MSG_ERROR("ddl_handle mismatch"); + return; + } + switch (event) { + case VCD_EVT_RESP_STOP: + { + vcd_handle_stop_done_in_invalid(cctxt, + (struct vcd_transc *)client_data, + status); + break; + } + case VCD_EVT_RESP_INPUT_DONE: + case VCD_EVT_RESP_OUTPUT_REQ: + { + if (cctxt->status.frame_submitted) + cctxt->status.frame_submitted--; + if (payload && ((struct ddl_frame_data_tag *) + payload)->frm_trans_end) + vcd_mark_frame_channel(cctxt->dev_ctxt); + break; + } + case VCD_EVT_RESP_OUTPUT_DONE: + { + if (payload && ((struct ddl_frame_data_tag *) + payload)->frm_trans_end) + vcd_mark_frame_channel(cctxt->dev_ctxt); + break; + } + case VCD_EVT_RESP_TRANSACTION_PENDING: + { + if (cctxt->status.frame_submitted) + cctxt->status.frame_submitted--; + vcd_mark_frame_channel(cctxt->dev_ctxt); + break; + } + case VCD_EVT_IND_HWERRFATAL: + { + if (status == VCD_ERR_HW_FATAL) + vcd_handle_stop_done_in_invalid(cctxt, + (struct vcd_transc *)client_data, + status); + + break; + } + case VCD_EVT_RESP_EOS_DONE: + { + vcd_mark_frame_channel(cctxt->dev_ctxt); + break; + } + case VCD_EVT_IND_OUTPUT_RECONFIG: + { + if (cctxt->status.frame_submitted > 0) + cctxt->status.frame_submitted--; + else + cctxt->status.frame_delayed--; + vcd_mark_frame_channel(cctxt->dev_ctxt); + break; + } + default: + { + VCD_MSG_ERROR("Unexpected callback event=%d status=%d" + "from DDL", event, status); + dev_ctxt->command_continue = false; + break; + } + } +} + +static void vcd_clnt_enter_open + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Entering CLIENT_STATE_OPEN on api %d", state_event); +} + +static void vcd_clnt_enter_starting + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Entering CLIENT_STATE_STARTING on api %d", + state_event); + cctxt->status.last_evt = VCD_EVT_RESP_START; +} + +static void vcd_clnt_enter_run + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Entering CLIENT_STATE_RUN on api %d", state_event); +} + +static void vcd_clnt_enter_flushing + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Entering CLIENT_STATE_FLUSHING on api %d", + state_event); +} + +static void vcd_clnt_enter_stopping + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Entering CLIENT_STATE_STOPPING on api %d", + state_event); + cctxt->status.last_evt = VCD_EVT_RESP_STOP; +} + +static void vcd_clnt_enter_eos(struct vcd_clnt_ctxt *cctxt, + s32 state_event) +{ + u32 rc; + VCD_MSG_MED("Entering CLIENT_STATE_EOS on api %d", state_event); + rc = vcd_sched_suspend_resume_clnt(cctxt, false); + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("Failed: vcd_sched_suspend_resume_clnt." + "rc=0x%x", rc); +} + +static void vcd_clnt_enter_pausing + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Entering CLIENT_STATE_PAUSING on api %d", + state_event); + cctxt->status.last_evt = VCD_EVT_RESP_PAUSE; +} + +static void vcd_clnt_enter_paused + (struct vcd_clnt_ctxt *cctxt, s32 state_event) +{ + VCD_MSG_MED("Entering CLIENT_STATE_PAUSED on api %d", + state_event); +} + +static void vcd_clnt_enter_invalid(struct vcd_clnt_ctxt *cctxt, + s32 state_event) +{ + VCD_MSG_MED("Entering CLIENT_STATE_INVALID on api %d", + state_event); + cctxt->ddl_hdl_valid = false; + cctxt->status.mask &= ~(VCD_FIRST_IP_RCVD | VCD_FIRST_OP_RCVD); + if (cctxt->sched_clnt_hdl) + vcd_sched_suspend_resume_clnt(cctxt, false); +} + +static void vcd_clnt_exit_open + (struct vcd_clnt_ctxt *cctxt, s32 state_event) +{ + VCD_MSG_MED("Exiting CLIENT_STATE_OPEN on api %d", state_event); +} + +static void vcd_clnt_exit_starting + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Exiting CLIENT_STATE_STARTING on api %d", + state_event); + cctxt->status.last_evt = VCD_EVT_RESP_BASE; +} + +static void vcd_clnt_exit_run + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Exiting CLIENT_STATE_RUN on api %d", state_event); +} + +static void vcd_clnt_exit_flushing + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Exiting CLIENT_STATE_FLUSHING on api %d", + state_event); +} + +static void vcd_clnt_exit_stopping + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Exiting CLIENT_STATE_STOPPING on api %d", + state_event); + cctxt->status.last_evt = VCD_EVT_RESP_BASE; +} + +static void vcd_clnt_exit_eos + (struct vcd_clnt_ctxt *cctxt, s32 state_event) +{ + u32 rc; + VCD_MSG_MED("Exiting CLIENT_STATE_EOS on api %d", state_event); + rc = vcd_sched_suspend_resume_clnt(cctxt, true); + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("Failed: vcd_sched_suspend_resume_clnt. rc=0x%x", + rc); +} + +static void vcd_clnt_exit_pausing + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Exiting CLIENT_STATE_PAUSING on api %d", + state_event); + cctxt->status.last_evt = VCD_EVT_RESP_BASE; +} + +static void vcd_clnt_exit_paused + (struct vcd_clnt_ctxt *cctxt, s32 state_event) { + VCD_MSG_MED("Exiting CLIENT_STATE_PAUSED on api %d", + state_event); +} + +static void vcd_clnt_exit_invalid(struct vcd_clnt_ctxt *cctxt, + s32 state_event) +{ + VCD_MSG_MED("Exiting CLIENT_STATE_INVALID on api %d", + state_event); +} + +void vcd_do_client_state_transition(struct vcd_clnt_ctxt *cctxt, + enum vcd_clnt_state_enum to_state, u32 ev_code) +{ + struct vcd_clnt_state_ctxt *state_ctxt; + + if (!cctxt || to_state >= VCD_CLIENT_STATE_MAX) { + VCD_MSG_ERROR("Bad parameters. cctxt=%p, to_state=%d", + cctxt, to_state); + } + + state_ctxt = &cctxt->clnt_state; + + if (state_ctxt->state == to_state) { + VCD_MSG_HIGH("Client already in requested to_state=%d", + to_state); + + return; + } + + VCD_MSG_MED("vcd_do_client_state_transition: C%d -> C%d, for api %d", + (int)state_ctxt->state, (int)to_state, ev_code); + + if (state_ctxt->state_table->exit) + state_ctxt->state_table->exit(cctxt, ev_code); + + + state_ctxt->state = to_state; + state_ctxt->state_table = vcd_clnt_state_table[to_state]; + + if (state_ctxt->state_table->entry) + state_ctxt->state_table->entry(cctxt, ev_code); +} + +const struct vcd_clnt_state_table *vcd_get_client_state_table + (enum vcd_clnt_state_enum state) { + return vcd_clnt_state_table[state]; +} + +static const struct vcd_clnt_state_table vcd_clnt_table_open = { + { + vcd_close_in_open, + vcd_encode_start_in_open, + NULL, + vcd_decode_start_in_open, + NULL, + NULL, + NULL, + vcd_flush_inopen, + vcd_stop_inopen, + vcd_set_property_cmn, + vcd_get_property_cmn, + vcd_set_buffer_requirements_cmn, + vcd_get_buffer_requirements_cmn, + vcd_set_buffer_cmn, + vcd_allocate_buffer_cmn, + vcd_free_buffer_cmn, + vcd_fill_output_buffer_cmn, + NULL, + }, + vcd_clnt_enter_open, + vcd_clnt_exit_open +}; + +static const struct vcd_clnt_state_table vcd_clnt_table_starting = { + { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + vcd_get_property_cmn, + NULL, + vcd_get_buffer_requirements_cmn, + NULL, + NULL, + NULL, + NULL, + vcd_clnt_cb_in_starting, + }, + vcd_clnt_enter_starting, + vcd_clnt_exit_starting +}; + +static const struct vcd_clnt_state_table vcd_clnt_table_run = { + { + NULL, + vcd_encode_start_in_run, + vcd_encode_frame_cmn, + vcd_decode_start_in_run, + vcd_decode_frame_cmn, + vcd_pause_in_run, + NULL, + vcd_flush_cmn, + vcd_stop_in_run, + vcd_set_property_cmn, + vcd_get_property_cmn, + vcd_set_buffer_requirements_cmn, + vcd_get_buffer_requirements_cmn, + vcd_set_buffer_cmn, + vcd_allocate_buffer_cmn, + vcd_free_buffer_cmn, + vcd_fill_output_buffer_cmn, + vcd_clnt_cb_in_run, + }, + vcd_clnt_enter_run, + vcd_clnt_exit_run +}; + +static const struct vcd_clnt_state_table vcd_clnt_table_flushing = { + { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + vcd_flush_in_flushing, + NULL, + vcd_set_property_cmn, + vcd_get_property_cmn, + NULL, + vcd_get_buffer_requirements_cmn, + NULL, + NULL, + NULL, + vcd_fill_output_buffer_cmn, + vcd_clnt_cb_in_flushing, + }, + vcd_clnt_enter_flushing, + vcd_clnt_exit_flushing +}; + +static const struct vcd_clnt_state_table vcd_clnt_table_stopping = { + { + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + vcd_get_property_cmn, + NULL, + vcd_get_buffer_requirements_cmn, + NULL, + NULL, + NULL, + NULL, + vcd_clnt_cb_in_stopping, + }, + vcd_clnt_enter_stopping, + vcd_clnt_exit_stopping +}; + +static const struct vcd_clnt_state_table vcd_clnt_table_eos = { + { + NULL, + NULL, + vcd_encode_frame_cmn, + NULL, + vcd_decode_frame_cmn, + NULL, + NULL, + vcd_flush_in_eos, + vcd_stop_in_eos, + NULL, + vcd_get_property_cmn, + NULL, + vcd_get_buffer_requirements_cmn, + NULL, + NULL, + NULL, + vcd_fill_output_buffer_in_eos, + vcd_clnt_cb_in_eos, + }, + vcd_clnt_enter_eos, + vcd_clnt_exit_eos +}; + +static const struct vcd_clnt_state_table vcd_clnt_table_pausing = { + { + NULL, + NULL, + vcd_encode_frame_cmn, + NULL, + vcd_decode_frame_cmn, + NULL, + NULL, + NULL, + NULL, + vcd_set_property_cmn, + vcd_get_property_cmn, + NULL, + vcd_get_buffer_requirements_cmn, + NULL, + NULL, + NULL, + vcd_fill_output_buffer_cmn, + vcd_clnt_cb_in_pausing, + }, + vcd_clnt_enter_pausing, + vcd_clnt_exit_pausing +}; + +static const struct vcd_clnt_state_table vcd_clnt_table_paused = { + { + NULL, + NULL, + vcd_encode_frame_cmn, + NULL, + vcd_decode_frame_cmn, + NULL, + vcd_resume_in_paused, + vcd_flush_cmn, + vcd_stop_cmn, + vcd_set_property_cmn, + vcd_get_property_cmn, + vcd_set_buffer_requirements_cmn, + vcd_get_buffer_requirements_cmn, + vcd_set_buffer_cmn, + vcd_allocate_buffer_cmn, + NULL, + vcd_fill_output_buffer_cmn, + NULL, + }, + vcd_clnt_enter_paused, + vcd_clnt_exit_paused +}; +static const struct vcd_clnt_state_table vcd_clnt_table_invalid = { + { + vcd_close_in_invalid, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + vcd_flush_in_invalid, + vcd_stop_in_invalid, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + vcd_free_buffer_cmn, + NULL, + vcd_clnt_cb_in_invalid, + }, + vcd_clnt_enter_invalid, + vcd_clnt_exit_invalid +}; + +static const struct vcd_clnt_state_table *vcd_clnt_state_table[] = { + NULL, + &vcd_clnt_table_open, + &vcd_clnt_table_starting, + &vcd_clnt_table_run, + &vcd_clnt_table_flushing, + &vcd_clnt_table_pausing, + &vcd_clnt_table_paused, + &vcd_clnt_table_stopping, + &vcd_clnt_table_eos, + &vcd_clnt_table_invalid +}; diff --git a/drivers/video/msm/vidc/common/vcd/vcd_client_sm.h b/drivers/video/msm/vidc/common/vcd/vcd_client_sm.h new file mode 100644 index 00000000000..e9ab41cf90e --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_client_sm.h @@ -0,0 +1,110 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_CLIENT_SM_H_ +#define _VCD_CLIENT_SM_H_ +#include "vcd_api.h" +#include "vcd_ddl_api.h" + +struct vcd_clnt_state_table; +struct vcd_clnt_state_ctxt; +struct vcd_clnt_ctxt; + +enum vcd_clnt_state_enum { + VCD_CLIENT_STATE_NULL = 0, + VCD_CLIENT_STATE_OPEN, + VCD_CLIENT_STATE_STARTING, + VCD_CLIENT_STATE_RUN, + VCD_CLIENT_STATE_FLUSHING, + VCD_CLIENT_STATE_PAUSING, + VCD_CLIENT_STATE_PAUSED, + VCD_CLIENT_STATE_STOPPING, + VCD_CLIENT_STATE_EOS, + VCD_CLIENT_STATE_INVALID, + VCD_CLIENT_STATE_MAX, + VCD_CLIENT_STATE_32BIT = 0x7FFFFFFF +}; + +#define CLIENT_STATE_EVENT_NUMBER(ppf) \ + ((u32 *) (&(((struct vcd_clnt_state_table*)0)->ev_hdlr.ppf)) - \ + (u32 *) (&(((struct vcd_clnt_state_table*)0)->ev_hdlr.close)) \ + + 1) + +struct vcd_clnt_state_table { + struct { + u32(*close) (struct vcd_clnt_ctxt *cctxt); + u32(*encode_start) (struct vcd_clnt_ctxt *cctxt); + u32(*encode_frame) (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame); + u32(*decode_start) (struct vcd_clnt_ctxt *cctxt, + struct vcd_sequence_hdr *seq_hdr); + u32(*decode_frame) (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame); + u32(*pause) (struct vcd_clnt_ctxt *cctxt); + u32(*resume) (struct vcd_clnt_ctxt *cctxt); + u32(*flush) (struct vcd_clnt_ctxt *cctxt, + u32 mode); + u32(*stop) (struct vcd_clnt_ctxt *cctxt); + u32(*set_property) (struct vcd_clnt_ctxt *cctxt, + struct vcd_property_hdr *prop_hdr, + void *prop); + u32(*get_property) (struct vcd_clnt_ctxt *cctxt, + struct vcd_property_hdr *prop_hdr, + void *prop); + u32(*set_buffer_requirements) (struct vcd_clnt_ctxt * + cctxt, + enum vcd_buffer_type buffer, + struct + vcd_buffer_requirement * + buffer_req); + u32(*get_buffer_requirements) (struct vcd_clnt_ctxt * + cctxt, + enum vcd_buffer_type buffer, + struct + vcd_buffer_requirement * + buffer_req); + u32(*set_buffer) (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer_type, u8 *buffer, + u32 buf_size); + u32(*allocate_buffer) (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer, u32 buf_size, + u8 **vir_buf_addr, u8 **phy_buf_addr); + u32(*free_buffer) (struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer_type, u8 *buffer); + u32(*fill_output_buffer) ( + struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *buffer); + void (*clnt_cb) (struct vcd_clnt_ctxt *cctxt, + u32 event, u32 status, void *payload, + size_t sz, u32 *ddl_handle, + void *const client_data); + } ev_hdlr; + + void (*entry) (struct vcd_clnt_ctxt *cctxt, + s32 state_event); + void (*exit) (struct vcd_clnt_ctxt *cctxt, + s32 state_event); +}; + +struct vcd_clnt_state_ctxt { + const struct vcd_clnt_state_table *state_table; + enum vcd_clnt_state_enum state; +}; + +extern void vcd_do_client_state_transition + (struct vcd_clnt_ctxt *cctxt, + enum vcd_clnt_state_enum to_state, u32 ev_code); + +extern const struct vcd_clnt_state_table *vcd_get_client_state_table( + enum vcd_clnt_state_enum state); + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vcd_core.h b/drivers/video/msm/vidc/common/vcd/vcd_core.h new file mode 100644 index 00000000000..e681febaff4 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_core.h @@ -0,0 +1,220 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_CORE_H_ +#define _VCD_CORE_H_ + +#include "vcd_api.h" +#include "vcd_ddl_api.h" + +#include "vcd_util.h" +#include "vcd_client_sm.h" +#include "vcd_power_sm.h" + +#define VCD_SIGNATURE 0x75017591U + +#define VCD_MIN_PERF_LEVEL 37900 + +#define VCD_DRIVER_INSTANCE_MAX 4 + +#define VCD_MAX_CLIENT_TRANSACTIONS 32 + +#define VCD_MAX_BUFFER_ENTRIES 32 + +#define VCD_SEQ_HDR_PADDING_BYTES 256 + +#define VCD_DEC_NUM_INTERLACED_FIELDS 2 + +#define VCD_TIMESTAMP_RESOLUTION 1000000 +#define VCD_DEC_INITIAL_FRAME_RATE 30 + +#define VCD_FIRST_IP_RCVD 0x00000004 +#define VCD_FIRST_OP_RCVD 0x00000008 +#define VCD_EOS_PREV_VALID 0x00000010 +#define VCD_EOS_WAIT_OP_BUF 0x00000020 +#define VCD_CLEANING_UP 0x00000040 +#define VCD_STOP_PENDING 0x00000080 +#define VCD_CLOSE_PENDING 0x00000100 +#define VCD_IN_RECONFIG 0x00000200 +#define VCD_FIRST_IP_DONE 0x00000400 + +enum vcd_command { + VCD_CMD_NONE, + VCD_CMD_DEVICE_INIT, + VCD_CMD_DEVICE_TERM, + VCD_CMD_DEVICE_RESET, + VCD_CMD_CODEC_START, + VCD_CMD_CODEC_STOP, + VCD_CMD_CODE_FRAME, + VCD_CMD_OUTPUT_FLUSH, + VCD_CMD_CLIENT_CLOSE +}; + +enum vcd_core_type { + VCD_CORE_1080P, + VCD_CORE_720P +}; + +struct vcd_cmd_q_element { + enum vcd_command pending_cmd; +}; + +struct vcd_buffer_entry { + struct list_head sched_list; + struct list_head list; + u32 valid; + u8 *alloc; + u8 *virtual; + u8 *physical; + size_t sz; + u32 allocated; + u32 in_use; + struct vcd_frame_data frame; + +}; + +struct vcd_buffer_pool { + struct vcd_buffer_entry *entries; + u32 count; + struct vcd_buffer_requirement buf_req; + u32 validated; + u32 allocated; + u32 in_use; + struct list_head queue; + u16 q_len; +}; + +struct vcd_transc { + u32 in_use; + enum vcd_command type; + struct vcd_clnt_ctxt *cctxt; + + struct vcd_buffer_entry *ip_buf_entry; + + s64 time_stamp; + u32 ip_frm_tag; + enum vcd_frame frame; + + struct vcd_buffer_entry *op_buf_entry; + + u32 input_done; + u32 frame_done; +}; + +struct vcd_dev_ctxt { + u32 ddl_cmd_concurrency; + u32 ddl_frame_ch_depth; + u32 ddl_cmd_ch_depth; + u32 ddl_frame_ch_interim; + u32 ddl_cmd_ch_interim; + u32 ddl_frame_ch_free; + u32 ddl_cmd_ch_free; + + struct list_head sched_clnt_list; + + struct vcd_init_config config; + + u32 driver_ids[VCD_DRIVER_INSTANCE_MAX]; + u32 refs; + u8 *device_base_addr; + void *hw_timer_handle; + u32 hw_time_out; + struct vcd_clnt_ctxt *cctxt_list_head; + + enum vcd_command pending_cmd; + + u32 command_continue; + + struct vcd_transc *trans_tbl; + u32 trans_tbl_size; + + enum vcd_power_state pwr_state; + enum vcd_pwr_clk_state pwr_clk_state; + u32 active_clnts; + u32 max_perf_lvl; + u32 reqd_perf_lvl; + u32 curr_perf_lvl; + u32 set_perf_lvl_pending; + +}; + +struct vcd_clnt_status { + u32 req_perf_lvl; + u32 frame_submitted; + u32 frame_delayed; + u32 cmd_submitted; + u32 int_field_cnt; + s64 first_ts; + s64 prev_ts; + u64 time_elapsed; + struct vcd_frame_data eos_trig_ip_frm; + struct ddl_frame_data_tag eos_prev_op_frm; + u32 eos_prev_op_frm_status; + u32 last_err; + u32 last_evt; + u32 mask; +}; + +struct vcd_sched_clnt_ctx { + struct list_head list; + u32 clnt_active; + void *clnt_data; + u32 tkns; + u32 round_perfrm; + u32 rounds; + struct list_head ip_frm_list; +}; + +struct vcd_clnt_ctxt { + u32 signature; + struct vcd_clnt_state_ctxt clnt_state; + + s32 driver_id; + + u32 live; + u32 decoding; + u32 bframe; + + struct vcd_property_frame_rate frm_rate; + u32 frm_p_units; + u32 reqd_perf_lvl; + u32 time_resoln; + + struct vcd_buffer_pool in_buf_pool; + struct vcd_buffer_pool out_buf_pool; + + void (*callback) (u32 event, u32 status, void *info, size_t sz, + void *handle, void *const client_data); + void *client_data; + struct vcd_sched_clnt_ctx *sched_clnt_hdl; + u32 ddl_hdl_valid; + u32 *ddl_handle; + struct vcd_dev_ctxt *dev_ctxt; + struct vcd_cmd_q_element cmd_q; + struct vcd_sequence_hdr seq_hdr; + u8 *seq_hdr_phy_addr; + struct vcd_clnt_status status; + + struct vcd_clnt_ctxt *next; +}; + +#define VCD_BUFFERPOOL_INUSE_DECREMENT(val) \ +do { \ + if ((val) > 0) \ + val--; \ + else { \ + VCD_MSG_ERROR("%s(): Inconsistent val given in " \ + " VCD_BUFFERPOOL_INUSE_DECREMENT\n", __func__); \ + } \ +} while (0) + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vcd_device_sm.c b/drivers/video/msm/vidc/common/vcd/vcd_device_sm.c new file mode 100644 index 00000000000..f8fb0fadbb1 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_device_sm.c @@ -0,0 +1,1203 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd.h" + +static const struct vcd_dev_state_table *vcd_dev_state_table[]; +static const struct vcd_dev_state_table vcd_dev_table_null; + +struct vcd_drv_ctxt *vcd_get_drv_context(void) +{ + static struct vcd_drv_ctxt drv_context = { + {&vcd_dev_table_null, VCD_DEVICE_STATE_NULL}, + {0}, + }; + + return &drv_context; + +} + +void vcd_do_device_state_transition(struct vcd_drv_ctxt *drv_ctxt, + enum vcd_dev_state_enum to_state, u32 ev_code) +{ + struct vcd_dev_state_ctxt *state_ctxt; + + if (!drv_ctxt || to_state >= VCD_DEVICE_STATE_MAX) { + VCD_MSG_ERROR("Bad parameters. drv_ctxt=%p, to_state=%d", + drv_ctxt, to_state); + } + + state_ctxt = &drv_ctxt->dev_state; + + if (state_ctxt->state == to_state) { + VCD_MSG_HIGH("Device already in requested to_state=%d", + to_state); + + return; + } + + VCD_MSG_MED("vcd_do_device_state_transition: D%d -> D%d, for api %d", + (int)state_ctxt->state, (int)to_state, ev_code); + + if (state_ctxt->state_table->exit) + state_ctxt->state_table->exit(drv_ctxt, ev_code); + + + state_ctxt->state = to_state; + state_ctxt->state_table = vcd_dev_state_table[to_state]; + + if (state_ctxt->state_table->entry) + state_ctxt->state_table->entry(drv_ctxt, ev_code); +} + +void vcd_hw_timeout_handler(void *user_data) +{ + struct vcd_drv_ctxt *drv_ctxt; + + VCD_MSG_HIGH("vcd_hw_timeout_handler:"); + user_data = NULL; + drv_ctxt = vcd_get_drv_context(); + mutex_lock(&drv_ctxt->dev_mutex); + if (drv_ctxt->dev_state.state_table->ev_hdlr.timeout) + drv_ctxt->dev_state.state_table->ev_hdlr. + timeout(drv_ctxt, user_data); + else + VCD_MSG_ERROR("hw_timeout unsupported in device state %d", + drv_ctxt->dev_state.state); + mutex_unlock(&drv_ctxt->dev_mutex); +} + +void vcd_ddl_callback(u32 event, u32 status, void *payload, + size_t sz, u32 *ddl_handle, void *const client_data) +{ + struct vcd_drv_ctxt *drv_ctxt; + struct vcd_dev_ctxt *dev_ctxt; + struct vcd_dev_state_ctxt *dev_state; + struct vcd_clnt_ctxt *cctxt; + struct vcd_transc *transc; + + VCD_MSG_LOW("vcd_ddl_callback:"); + + VCD_MSG_LOW("event=0x%x status=0x%x", event, status); + + drv_ctxt = vcd_get_drv_context(); + dev_ctxt = &drv_ctxt->dev_ctxt; + dev_state = &drv_ctxt->dev_state; + + dev_ctxt->command_continue = true; + vcd_device_timer_stop(dev_ctxt); + + switch (dev_state->state) { + case VCD_DEVICE_STATE_NULL: + { + VCD_MSG_HIGH("Callback unexpected in NULL state"); + break; + } + + case VCD_DEVICE_STATE_NOT_INIT: + { + VCD_MSG_HIGH("Callback unexpected in NOT_INIT state"); + break; + } + + case VCD_DEVICE_STATE_INITING: + { + if (dev_state->state_table->ev_hdlr.dev_cb) { + dev_state->state_table->ev_hdlr. + dev_cb(drv_ctxt, event, status, + payload, sz, ddl_handle, + client_data); + } else { + VCD_MSG_HIGH("No device handler in %d state", + dev_state->state); + } + break; + } + + case VCD_DEVICE_STATE_READY: + { + transc = (struct vcd_transc *)client_data; + + if (!transc || !transc->in_use + || !transc->cctxt) { + VCD_MSG_ERROR("Invalid clientdata " + "received from DDL "); + } else { + cctxt = transc->cctxt; + + if (cctxt->clnt_state.state_table->ev_hdlr. + clnt_cb) { + cctxt->clnt_state.state_table-> + ev_hdlr.clnt_cb(cctxt, + event, status, payload, + sz, ddl_handle, + client_data); + } else { + VCD_MSG_HIGH + ("No client handler in" + " (dsm:READY, csm:%d) state", + (int)cctxt->clnt_state.state); + + if (VCD_FAILED(status)) { + VCD_MSG_FATAL("DDL callback" + " returned failure 0x%x", + status); + } + } + } + break; + } + + default: + { + VCD_MSG_ERROR("Unknown state"); + break; + } + + } + +} + +u32 vcd_init_device_context(struct vcd_drv_ctxt *drv_ctxt, + u32 ev_code) +{ + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + u32 rc; + struct ddl_init_config ddl_init; + + VCD_MSG_LOW("vcd_init_device_context:"); + + dev_ctxt->pending_cmd = VCD_CMD_NONE; + + rc = vcd_power_event(dev_ctxt, NULL, VCD_EVT_PWR_DEV_INIT_BEGIN); + VCD_FAILED_RETURN(rc, "VCD_EVT_PWR_DEV_INIT_BEGIN failed"); + + VCD_MSG_HIGH("Device powered ON and clocked"); + rc = vcd_sched_create(&dev_ctxt->sched_clnt_list); + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR("rc = 0x%x. Failed: vcd_sched_create", rc); + + (void)vcd_power_event(dev_ctxt, NULL, + VCD_EVT_PWR_DEV_INIT_FAIL); + + return rc; + } + + VCD_MSG_HIGH("Created scheduler instance."); + + ddl_init.core_virtual_base_addr = dev_ctxt->device_base_addr; + ddl_init.interrupt_clr = dev_ctxt->config.interrupt_clr; + ddl_init.ddl_callback = vcd_ddl_callback; + + rc = ddl_device_init(&ddl_init, NULL); + + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR("rc = 0x%x. Failed: ddl_device_init", rc); + vcd_sched_destroy(&dev_ctxt->sched_clnt_list); + (void)vcd_power_event(dev_ctxt, NULL, + VCD_EVT_PWR_DEV_INIT_FAIL); + } else { + vcd_device_timer_start(dev_ctxt); + vcd_do_device_state_transition(drv_ctxt, + VCD_DEVICE_STATE_INITING, + ev_code); + } + + return rc; +} + +void vcd_handle_device_init_failed(struct vcd_drv_ctxt *drv_ctxt, + u32 status) +{ + struct vcd_clnt_ctxt *client; + struct vcd_clnt_ctxt *tmp_client; + + VCD_MSG_ERROR("Device init failed. status = %d", status); + + client = drv_ctxt->dev_ctxt.cctxt_list_head; + while (client) { + client->callback(VCD_EVT_RESP_OPEN, + status, NULL, 0, 0, client->client_data); + + tmp_client = client; + client = client->next; + + vcd_destroy_client_context(tmp_client); + } + if (ddl_device_release(NULL)) + VCD_MSG_ERROR("Failed: ddl_device_release"); + + vcd_sched_destroy(&drv_ctxt->dev_ctxt.sched_clnt_list); + if (vcd_power_event(&drv_ctxt->dev_ctxt, + NULL, VCD_EVT_PWR_DEV_INIT_FAIL)) + VCD_MSG_ERROR("VCD_EVT_PWR_DEV_INIT_FAIL failed"); + + vcd_do_device_state_transition(drv_ctxt, + VCD_DEVICE_STATE_NOT_INIT, + DEVICE_STATE_EVENT_NUMBER(dev_cb)); +} + +u32 vcd_deinit_device_context(struct vcd_drv_ctxt *drv_ctxt, + u32 ev_code) +{ + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_deinit_device_context:"); + + rc = vcd_power_event(&drv_ctxt->dev_ctxt, NULL, + VCD_EVT_PWR_DEV_TERM_BEGIN); + + VCD_FAILED_RETURN(rc, "VCD_EVT_PWR_DEV_TERM_BEGIN failed"); + + rc = ddl_device_release(NULL); + + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR("rc = 0x%x. Failed: ddl_device_release", rc); + + (void)vcd_power_event(dev_ctxt, NULL, + VCD_EVT_PWR_DEV_TERM_FAIL); + } else { + vcd_sched_destroy(&dev_ctxt->sched_clnt_list); + (void) vcd_power_event(dev_ctxt, NULL, + VCD_EVT_PWR_DEV_TERM_END); + + vcd_do_device_state_transition(drv_ctxt, + VCD_DEVICE_STATE_NOT_INIT, ev_code); + } + return rc; +} + +void vcd_term_driver_context(struct vcd_drv_ctxt *drv_ctxt) +{ + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + + VCD_MSG_HIGH("All driver instances terminated"); + + if (dev_ctxt->config.deregister_isr) + dev_ctxt->config.deregister_isr(); + + if (dev_ctxt->config.un_map_dev_base_addr) + dev_ctxt->config.un_map_dev_base_addr(); + + if (dev_ctxt->config.timer_release) + dev_ctxt->config.timer_release( + dev_ctxt->hw_timer_handle); + + kfree(dev_ctxt->trans_tbl); + + memset(dev_ctxt, 0, sizeof(struct vcd_dev_ctxt)); + + vcd_do_device_state_transition(drv_ctxt, + VCD_DEVICE_STATE_NULL, + DEVICE_STATE_EVENT_NUMBER(term)); + +} + +u32 vcd_reset_device_context(struct vcd_drv_ctxt *drv_ctxt, + u32 ev_code) +{ + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_reset_device_context:"); + vcd_reset_device_channels(dev_ctxt); + rc = vcd_power_event(&drv_ctxt->dev_ctxt, NULL, + VCD_EVT_PWR_DEV_TERM_BEGIN); + VCD_FAILED_RETURN(rc, "VCD_EVT_PWR_DEV_TERM_BEGIN failed"); + if (ddl_reset_hw(0)) + VCD_MSG_HIGH("HW Reset done"); + else + VCD_MSG_FATAL("HW Reset failed"); + + (void)vcd_power_event(dev_ctxt, NULL, VCD_EVT_PWR_DEV_TERM_END); + + return VCD_S_SUCCESS; +} + +void vcd_handle_device_err_fatal(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt *trig_clnt) +{ + struct vcd_clnt_ctxt *cctxt = dev_ctxt->cctxt_list_head; + struct vcd_clnt_ctxt *tmp_clnt = NULL; + VCD_MSG_LOW("vcd_handle_device_err_fatal:"); + while (cctxt) { + tmp_clnt = cctxt; + cctxt = cctxt->next; + if (tmp_clnt != trig_clnt) + vcd_clnt_handle_device_err_fatal(tmp_clnt, + tmp_clnt->status.last_evt); + } + dev_ctxt->pending_cmd = VCD_CMD_DEVICE_RESET; + if (!dev_ctxt->cctxt_list_head) + vcd_do_device_state_transition(vcd_get_drv_context(), + VCD_DEVICE_STATE_NOT_INIT, + DEVICE_STATE_EVENT_NUMBER(timeout)); + else + vcd_do_device_state_transition(vcd_get_drv_context(), + VCD_DEVICE_STATE_INVALID, + DEVICE_STATE_EVENT_NUMBER(dev_cb)); +} + +void vcd_handle_for_last_clnt_close( + struct vcd_dev_ctxt *dev_ctxt, u32 send_deinit) +{ + if (!dev_ctxt->cctxt_list_head) { + VCD_MSG_HIGH("All clients are closed"); + if (send_deinit) + (void) vcd_deinit_device_context( + vcd_get_drv_context(), + DEVICE_STATE_EVENT_NUMBER(close)); + else + dev_ctxt->pending_cmd = + VCD_CMD_DEVICE_TERM; + } +} +void vcd_continue(void) +{ + struct vcd_drv_ctxt *drv_ctxt; + struct vcd_dev_ctxt *dev_ctxt; + u32 command_continue; + struct vcd_transc *transc; + u32 rc; + VCD_MSG_LOW("vcd_continue:"); + + drv_ctxt = vcd_get_drv_context(); + dev_ctxt = &drv_ctxt->dev_ctxt; + + dev_ctxt->command_continue = false; + + if (dev_ctxt->pending_cmd == VCD_CMD_DEVICE_INIT) { + VCD_MSG_HIGH("VCD_CMD_DEVICE_INIT is pending"); + + dev_ctxt->pending_cmd = VCD_CMD_NONE; + + (void)vcd_init_device_context(drv_ctxt, + DEVICE_STATE_EVENT_NUMBER(open)); + } else if (dev_ctxt->pending_cmd == VCD_CMD_DEVICE_TERM) { + VCD_MSG_HIGH("VCD_CMD_DEVICE_TERM is pending"); + + dev_ctxt->pending_cmd = VCD_CMD_NONE; + + (void)vcd_deinit_device_context(drv_ctxt, + DEVICE_STATE_EVENT_NUMBER(close)); + } else if (dev_ctxt->pending_cmd == VCD_CMD_DEVICE_RESET) { + VCD_MSG_HIGH("VCD_CMD_DEVICE_RESET is pending"); + dev_ctxt->pending_cmd = VCD_CMD_NONE; + (void)vcd_reset_device_context(drv_ctxt, + DEVICE_STATE_EVENT_NUMBER(dev_cb)); + } else { + if (dev_ctxt->set_perf_lvl_pending) { + rc = vcd_power_event(dev_ctxt, NULL, + VCD_EVT_PWR_DEV_SET_PERFLVL); + + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR + ("VCD_EVT_PWR_CLNT_SET_PERFLVL failed"); + VCD_MSG_HIGH + ("Not running at desired perf level." + "curr=%d, reqd=%d", + dev_ctxt->curr_perf_lvl, + dev_ctxt->reqd_perf_lvl); + } else { + dev_ctxt->set_perf_lvl_pending = false; + } + } + + do { + command_continue = false; + + if (vcd_get_command_channel_in_loop + (dev_ctxt, &transc)) { + if (vcd_submit_command_in_continue(dev_ctxt, + transc)) + command_continue = true; + else { + VCD_MSG_MED + ("No more commands to submit"); + + vcd_release_command_channel(dev_ctxt, + transc); + + vcd_release_interim_command_channels + (dev_ctxt); + } + } + } while (command_continue); + + do { + command_continue = false; + + if (vcd_get_frame_channel_in_loop + (dev_ctxt, &transc)) { + if (vcd_try_submit_frame_in_continue(dev_ctxt, + transc)) { + command_continue = true; + } else { + VCD_MSG_MED("No more frames to submit"); + + vcd_release_frame_channel(dev_ctxt, + transc); + + vcd_release_interim_frame_channels + (dev_ctxt); + } + } + + } while (command_continue); + + if (!vcd_core_is_busy(dev_ctxt)) { + rc = vcd_power_event(dev_ctxt, NULL, + VCD_EVT_PWR_CLNT_CMD_END); + + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("Failed:" + "VCD_EVT_PWR_CLNT_CMD_END"); + } + } +} + +static void vcd_pause_all_sessions(struct vcd_dev_ctxt *dev_ctxt) +{ + struct vcd_clnt_ctxt *cctxt = dev_ctxt->cctxt_list_head; + u32 rc; + + while (cctxt) { + if (cctxt->clnt_state.state_table->ev_hdlr.pause) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + pause(cctxt); + + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("Client pause failed"); + + } + + cctxt = cctxt->next; + } +} + +static void vcd_resume_all_sessions(struct vcd_dev_ctxt *dev_ctxt) +{ + struct vcd_clnt_ctxt *cctxt = dev_ctxt->cctxt_list_head; + u32 rc; + + while (cctxt) { + if (cctxt->clnt_state.state_table->ev_hdlr.resume) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + resume(cctxt); + + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("Client resume failed"); + + } + + cctxt = cctxt->next; + } +} + +static u32 vcd_init_cmn + (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_init_config *config, s32 *driver_handle) +{ + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + s32 driver_id; + + if (dev_ctxt->config.interrupt_clr != + config->interrupt_clr + || dev_ctxt->config.register_isr != + config->register_isr + || dev_ctxt->config.deregister_isr != + config->deregister_isr + || dev_ctxt->config.map_dev_base_addr != + config->map_dev_base_addr + || dev_ctxt->config.un_map_dev_base_addr != + config->un_map_dev_base_addr) { + VCD_MSG_ERROR("Device config mismatch"); + VCD_MSG_HIGH("VCD will be using config from 1st vcd_init"); + } + + *driver_handle = 0; + + driver_id = 0; + while (driver_id < VCD_DRIVER_INSTANCE_MAX && + dev_ctxt->driver_ids[driver_id]) { + ++driver_id; + } + + if (driver_id == VCD_DRIVER_INSTANCE_MAX) { + VCD_MSG_ERROR("Max driver instances reached"); + + return VCD_ERR_FAIL; + } + + ++dev_ctxt->refs; + dev_ctxt->driver_ids[driver_id] = true; + *driver_handle = driver_id + 1; + + VCD_MSG_HIGH("Driver_id = %d. No of driver instances = %d", + driver_id, dev_ctxt->refs); + + return VCD_S_SUCCESS; + +} + +static u32 vcd_init_in_null + (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_init_config *config, s32 *driver_handle) { + u32 rc = VCD_S_SUCCESS; + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + u32 done_create_timer = false; + VCD_MSG_LOW("vcd_init_in_dev_null:"); + + + dev_ctxt->config = *config; + + dev_ctxt->device_base_addr = + (u8 *)config->map_dev_base_addr( + dev_ctxt->config.device_name); + + if (!dev_ctxt->device_base_addr) { + VCD_MSG_ERROR("NULL Device_base_addr"); + + return VCD_ERR_FAIL; + } + + if (config->register_isr) { + config->register_isr(dev_ctxt->config. + device_name); + } + + if (config->timer_create) { + if (config->timer_create(vcd_hw_timeout_handler, + NULL, &dev_ctxt->hw_timer_handle)) + done_create_timer = true; + else { + VCD_MSG_ERROR("timercreate failed"); + return VCD_ERR_FAIL; + } + } + + + rc = vcd_init_cmn(drv_ctxt, config, driver_handle); + + if (!VCD_FAILED(rc)) { + vcd_do_device_state_transition(drv_ctxt, + VCD_DEVICE_STATE_NOT_INIT, + DEVICE_STATE_EVENT_NUMBER + (init)); + } else { + if (dev_ctxt->config.un_map_dev_base_addr) + dev_ctxt->config.un_map_dev_base_addr(); + + if (dev_ctxt->config.deregister_isr) + dev_ctxt->config.deregister_isr(); + + if (done_create_timer && dev_ctxt->config.timer_release) + dev_ctxt->config.timer_release(dev_ctxt-> + hw_timer_handle); + + } + + return rc; + +} + +static u32 vcd_init_in_not_init + (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_init_config *config, s32 *driver_handle) +{ + + VCD_MSG_LOW("vcd_init_in_dev_not_init:"); + + return vcd_init_cmn(drv_ctxt, config, driver_handle); + +} + +static u32 vcd_init_in_initing + (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_init_config *config, s32 *driver_handle) { + + VCD_MSG_LOW("vcd_init_in_dev_initing:"); + + return vcd_init_cmn(drv_ctxt, config, driver_handle); + +} + +static u32 vcd_init_in_ready + (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_init_config *config, s32 *driver_handle) +{ + VCD_MSG_LOW("vcd_init_in_dev_ready:"); + + return vcd_init_cmn(drv_ctxt, config, driver_handle); +} + +static u32 vcd_term_cmn + (struct vcd_drv_ctxt *drv_ctxt, s32 driver_handle) +{ + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + + if (!vcd_validate_driver_handle(dev_ctxt, driver_handle)) { + VCD_MSG_ERROR("Invalid driver handle = %d", driver_handle); + + return VCD_ERR_BAD_HANDLE; + } + + if (vcd_check_for_client_context(dev_ctxt, + driver_handle - 1)) { + VCD_MSG_ERROR("Driver has active client"); + + return VCD_ERR_BAD_STATE; + } + + --dev_ctxt->refs; + dev_ctxt->driver_ids[driver_handle - 1] = false; + + VCD_MSG_HIGH("Driver_id %d terminated. No of driver instances = %d", + driver_handle - 1, dev_ctxt->refs); + + return VCD_S_SUCCESS; +} + +static u32 vcd_term_in_not_init + (struct vcd_drv_ctxt *drv_ctxt, s32 driver_handle) +{ + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + u32 rc; + + VCD_MSG_LOW("vcd_term_in_dev_not_init:"); + + rc = vcd_term_cmn(drv_ctxt, driver_handle); + + if (!VCD_FAILED(rc) && !dev_ctxt->refs) + vcd_term_driver_context(drv_ctxt); + + return rc; +} + +static u32 vcd_term_in_initing + (struct vcd_drv_ctxt *drv_ctxt, s32 driver_handle) +{ + VCD_MSG_LOW("vcd_term_in_dev_initing:"); + + return vcd_term_cmn(drv_ctxt, driver_handle); +} + +static u32 vcd_term_in_ready + (struct vcd_drv_ctxt *drv_ctxt, s32 driver_handle) +{ + VCD_MSG_LOW("vcd_term_in_dev_ready:"); + + return vcd_term_cmn(drv_ctxt, driver_handle); +} + +static u32 vcd_term_in_invalid(struct vcd_drv_ctxt *drv_ctxt, + s32 driver_handle) +{ + u32 rc; + VCD_MSG_LOW("vcd_term_in_invalid:"); + rc = vcd_term_cmn(drv_ctxt, driver_handle); + if (!VCD_FAILED(rc) && !drv_ctxt->dev_ctxt.refs) + vcd_term_driver_context(drv_ctxt); + + return rc; +} + +static u32 vcd_open_cmn + (struct vcd_drv_ctxt *drv_ctxt, + s32 driver_handle, + u32 decoding, + void (*callback) (u32 event, u32 status, void *info, size_t sz, + void *handle, void *const client_data), + void *client_data, struct vcd_clnt_ctxt ** clnt_cctxt) +{ + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + struct vcd_clnt_ctxt *cctxt; + struct vcd_clnt_ctxt *client; + + if (!vcd_validate_driver_handle(dev_ctxt, driver_handle)) { + VCD_MSG_ERROR("Invalid driver handle = %d", driver_handle); + + return VCD_ERR_BAD_HANDLE; + } + + cctxt = (struct vcd_clnt_ctxt *) + kmalloc(sizeof(struct vcd_clnt_ctxt), GFP_KERNEL); + if (!cctxt) { + VCD_MSG_ERROR("No memory for client ctxt"); + + return VCD_ERR_ALLOC_FAIL; + } + + memset(cctxt, 0, sizeof(struct vcd_clnt_ctxt)); + cctxt->dev_ctxt = dev_ctxt; + cctxt->driver_id = driver_handle - 1; + cctxt->decoding = decoding; + cctxt->callback = callback; + cctxt->client_data = client_data; + cctxt->status.last_evt = VCD_EVT_RESP_OPEN; + INIT_LIST_HEAD(&cctxt->in_buf_pool.queue); + INIT_LIST_HEAD(&cctxt->out_buf_pool.queue); + client = dev_ctxt->cctxt_list_head; + dev_ctxt->cctxt_list_head = cctxt; + cctxt->next = client; + + *clnt_cctxt = cctxt; + + return VCD_S_SUCCESS; + +} + +static u32 vcd_open_in_not_init + (struct vcd_drv_ctxt *drv_ctxt, + s32 driver_handle, + u32 decoding, + void (*callback) (u32 event, u32 status, void *info, size_t sz, + void *handle, void *const client_data), + void *client_data) +{ + struct vcd_clnt_ctxt *cctxt; + u32 rc; + + VCD_MSG_LOW("vcd_open_in_dev_not_init:"); + + rc = vcd_open_cmn(drv_ctxt, driver_handle, decoding, callback, + client_data, &cctxt); + + VCD_FAILED_RETURN(rc, "Failed: vcd_open_cmn"); + + rc = vcd_init_device_context(drv_ctxt, + DEVICE_STATE_EVENT_NUMBER(open)); + + if (VCD_FAILED(rc)) + vcd_destroy_client_context(cctxt); + + return rc; +} + +static u32 vcd_open_in_initing(struct vcd_drv_ctxt *drv_ctxt, + s32 driver_handle, u32 decoding, + void (*callback) (u32 event, u32 status, void *info, size_t sz, + void *handle, void *const client_data), + void *client_data) +{ + struct vcd_clnt_ctxt *cctxt; + + VCD_MSG_LOW("vcd_open_in_dev_initing:"); + + return vcd_open_cmn(drv_ctxt, driver_handle, decoding, callback, + client_data, &cctxt); +} + +static u32 vcd_open_in_ready + (struct vcd_drv_ctxt *drv_ctxt, + s32 driver_handle, + u32 decoding, + void (*callback) (u32 event, u32 status, void *info, size_t sz, + void *handle, void *const client_data), + void *client_data) +{ + struct vcd_clnt_ctxt *cctxt; + struct vcd_handle_container container; + u32 rc; + + VCD_MSG_LOW("vcd_open_in_dev_ready:"); + + rc = vcd_open_cmn(drv_ctxt, driver_handle, decoding, callback, + client_data, &cctxt); + + VCD_FAILED_RETURN(rc, "Failed: vcd_open_cmn"); + + rc = vcd_init_client_context(cctxt); + + if (!VCD_FAILED(rc)) { + container.handle = (void *)cctxt; + + callback(VCD_EVT_RESP_OPEN, + VCD_S_SUCCESS, + &container, + sizeof(container), container.handle, client_data); + } else { + VCD_MSG_ERROR("rc = 0x%x. Failed: vcd_init_client_context", rc); + + vcd_destroy_client_context(cctxt); + } + + return rc; +} + +static u32 vcd_close_in_ready + (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_clnt_ctxt *cctxt) { + u32 rc; + + VCD_MSG_LOW("vcd_close_in_dev_ready:"); + + if (cctxt->clnt_state.state_table->ev_hdlr.close) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + close(cctxt); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + if (!VCD_FAILED(rc)) + vcd_handle_for_last_clnt_close(&drv_ctxt->dev_ctxt, true); + + return rc; +} + +static u32 vcd_close_in_dev_invalid(struct vcd_drv_ctxt *drv_ctxt, + struct vcd_clnt_ctxt *cctxt) +{ + u32 rc; + VCD_MSG_LOW("vcd_close_in_dev_invalid:"); + if (cctxt->clnt_state.state_table->ev_hdlr.close) { + rc = cctxt->clnt_state.state_table-> + ev_hdlr.close(cctxt); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + rc = VCD_ERR_BAD_STATE; + } + if (!VCD_FAILED(rc) && !drv_ctxt->dev_ctxt. + cctxt_list_head) { + VCD_MSG_HIGH("All INVALID clients are closed"); + vcd_do_device_state_transition(drv_ctxt, + VCD_DEVICE_STATE_NOT_INIT, + DEVICE_STATE_EVENT_NUMBER(close)); + } + return rc; +} + +static u32 vcd_resume_in_ready + (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_clnt_ctxt *cctxt) { + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_resume_in_ready:"); + + if (cctxt->clnt_state.state_table->ev_hdlr.resume) { + rc = cctxt->clnt_state.state_table->ev_hdlr. + resume(cctxt); + } else { + VCD_MSG_ERROR("Unsupported API in client state %d", + cctxt->clnt_state.state); + + rc = VCD_ERR_BAD_STATE; + } + + return rc; +} + +static u32 vcd_set_dev_pwr_in_ready + (struct vcd_drv_ctxt *drv_ctxt, + enum vcd_power_state pwr_state) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + + VCD_MSG_LOW("vcd_set_dev_pwr_in_ready:"); + + switch (pwr_state) { + case VCD_PWR_STATE_SLEEP: + { + if (dev_ctxt->pwr_state == VCD_PWR_STATE_ON) + vcd_pause_all_sessions(dev_ctxt); + dev_ctxt->pwr_state = VCD_PWR_STATE_SLEEP; + break; + } + + case VCD_PWR_STATE_ON: + { + if (dev_ctxt->pwr_state == VCD_PWR_STATE_SLEEP) + vcd_resume_all_sessions(dev_ctxt); + dev_ctxt->pwr_state = VCD_PWR_STATE_ON; + break; + } + + default: + { + VCD_MSG_ERROR("Invalid power state requested %d", + pwr_state); + break; + } + + } + + return rc; +} + +static void vcd_dev_cb_in_initing + (struct vcd_drv_ctxt *drv_ctxt, + u32 event, + u32 status, + void *payload, size_t sz, u32 *ddl_handle, void *const client_data) +{ + struct vcd_dev_ctxt *dev_ctxt; + struct vcd_clnt_ctxt *client; + struct vcd_clnt_ctxt *tmp_client; + struct vcd_handle_container container; + u32 rc = VCD_S_SUCCESS; + u32 client_inited = false; + u32 fail_all_open = false; + + VCD_MSG_LOW("vcd_dev_cb_in_initing:"); + + if (event != VCD_EVT_RESP_DEVICE_INIT) { + VCD_MSG_ERROR("vcd_dev_cb_in_initing: Unexpected event %d", + (int)event); + return; + } + + dev_ctxt = &drv_ctxt->dev_ctxt; + + dev_ctxt->command_continue = false; + + if (VCD_FAILED(status)) { + vcd_handle_device_init_failed(drv_ctxt, status); + + return; + } + + vcd_do_device_state_transition(drv_ctxt, + VCD_DEVICE_STATE_READY, + DEVICE_STATE_EVENT_NUMBER(open)); + + if (!dev_ctxt->cctxt_list_head) { + VCD_MSG_HIGH("All clients are closed"); + + dev_ctxt->pending_cmd = VCD_CMD_DEVICE_TERM; + + return; + } + + if (!dev_ctxt->ddl_cmd_ch_depth + || !dev_ctxt->trans_tbl) + rc = vcd_setup_with_ddl_capabilities(dev_ctxt); + + + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR + ("rc = 0x%x: Failed vcd_setup_with_ddl_capabilities", + rc); + + fail_all_open = true; + } + + client = dev_ctxt->cctxt_list_head; + while (client) { + if (!fail_all_open) + rc = vcd_init_client_context(client); + + + if (!VCD_FAILED(rc)) { + container.handle = (void *)client; + client->callback(VCD_EVT_RESP_OPEN, + VCD_S_SUCCESS, + &container, + sizeof(container), + container.handle, + client->client_data); + + client = client->next; + + client_inited = true; + } else { + VCD_MSG_ERROR + ("rc = 0x%x, Failed: vcd_init_client_context", + rc); + + client->callback(VCD_EVT_RESP_OPEN, + rc, + NULL, 0, 0, client->client_data); + + tmp_client = client; + client = client->next; + + vcd_destroy_client_context(tmp_client); + } + } + + if (!client_inited || fail_all_open) { + VCD_MSG_ERROR("All client open requests failed"); + + dev_ctxt->pending_cmd = VCD_CMD_DEVICE_TERM; + } else { + if (vcd_power_event(dev_ctxt, NULL, + VCD_EVT_PWR_DEV_INIT_END)) { + VCD_MSG_ERROR("VCD_EVT_PWR_DEV_INIT_END failed"); + } + } +} + +static void vcd_hw_timeout_cmn(struct vcd_drv_ctxt *drv_ctxt, + void *user_data) +{ + struct vcd_dev_ctxt *dev_ctxt = &drv_ctxt->dev_ctxt; + VCD_MSG_LOW("vcd_hw_timeout_cmn:"); + vcd_device_timer_stop(dev_ctxt); + + vcd_handle_device_err_fatal(dev_ctxt, NULL); + + /* Reset HW. */ + (void) vcd_reset_device_context(drv_ctxt, + DEVICE_STATE_EVENT_NUMBER(timeout)); +} + +static void vcd_dev_enter_null + (struct vcd_drv_ctxt *drv_ctxt, s32 state_event) { + VCD_MSG_MED("Entering DEVICE_STATE_NULL on api %d", state_event); + +} + +static void vcd_dev_enter_not_init + (struct vcd_drv_ctxt *drv_ctxt, s32 state_event) { + VCD_MSG_MED("Entering DEVICE_STATE_NOT_INIT on api %d", + state_event); + +} + +static void vcd_dev_enter_initing + (struct vcd_drv_ctxt *drv_ctxt, s32 state_event) { + VCD_MSG_MED("Entering DEVICE_STATE_INITING on api %d", + state_event); + +} + +static void vcd_dev_enter_ready + (struct vcd_drv_ctxt *drv_ctxt, s32 state_event) { + VCD_MSG_MED("Entering DEVICE_STATE_READY on api %d", + state_event); +} + +static void vcd_dev_enter_invalid(struct vcd_drv_ctxt *drv_ctxt, + s32 state_event) +{ + VCD_MSG_MED("Entering DEVICE_STATE_INVALID on api %d", state_event); +} + +static void vcd_dev_exit_null + (struct vcd_drv_ctxt *drv_ctxt, s32 state_event) { + VCD_MSG_MED("Exiting DEVICE_STATE_NULL on api %d", state_event); +} + +static void vcd_dev_exit_not_init + (struct vcd_drv_ctxt *drv_ctxt, s32 state_event) { + VCD_MSG_MED("Exiting DEVICE_STATE_NOT_INIT on api %d", + state_event); + +} + +static void vcd_dev_exit_initing + (struct vcd_drv_ctxt *drv_ctxt, s32 state_event) { + VCD_MSG_MED("Exiting DEVICE_STATE_INITING on api %d", + state_event); +} + +static void vcd_dev_exit_ready + (struct vcd_drv_ctxt *drv_ctxt, s32 state_event) { + VCD_MSG_MED("Exiting DEVICE_STATE_READY on api %d", state_event); +} + +static void vcd_dev_exit_invalid(struct vcd_drv_ctxt *drv_ctxt, + s32 state_event) +{ + VCD_MSG_MED("Exiting DEVICE_STATE_INVALID on api %d", state_event); +} + +static const struct vcd_dev_state_table vcd_dev_table_null = { + { + vcd_init_in_null, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + }, + vcd_dev_enter_null, + vcd_dev_exit_null +}; + +static const struct vcd_dev_state_table vcd_dev_table_not_init = { + { + vcd_init_in_not_init, + vcd_term_in_not_init, + vcd_open_in_not_init, + NULL, + NULL, + NULL, + NULL, + NULL, + }, + vcd_dev_enter_not_init, + vcd_dev_exit_not_init +}; + +static const struct vcd_dev_state_table vcd_dev_table_initing = { + { + vcd_init_in_initing, + vcd_term_in_initing, + vcd_open_in_initing, + NULL, + NULL, + NULL, + vcd_dev_cb_in_initing, + vcd_hw_timeout_cmn, + }, + vcd_dev_enter_initing, + vcd_dev_exit_initing +}; + +static const struct vcd_dev_state_table vcd_dev_table_ready = { + { + vcd_init_in_ready, + vcd_term_in_ready, + vcd_open_in_ready, + vcd_close_in_ready, + vcd_resume_in_ready, + vcd_set_dev_pwr_in_ready, + NULL, + vcd_hw_timeout_cmn, + }, + vcd_dev_enter_ready, + vcd_dev_exit_ready +}; + +static const struct vcd_dev_state_table vcd_dev_table_in_invalid = { + { + NULL, + vcd_term_in_invalid, + NULL, + vcd_close_in_dev_invalid, + NULL, + NULL, + NULL, + NULL, + }, + vcd_dev_enter_invalid, + vcd_dev_exit_invalid +}; + +static const struct vcd_dev_state_table *vcd_dev_state_table[] = { + &vcd_dev_table_null, + &vcd_dev_table_not_init, + &vcd_dev_table_initing, + &vcd_dev_table_ready, + &vcd_dev_table_in_invalid +}; diff --git a/drivers/video/msm/vidc/common/vcd/vcd_device_sm.h b/drivers/video/msm/vidc/common/vcd/vcd_device_sm.h new file mode 100644 index 00000000000..8245966d81a --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_device_sm.h @@ -0,0 +1,96 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_DEVICE_SM_H_ +#define _VCD_DEVICE_SM_H_ + +#include "vcd_api.h" +#include "vcd_ddl_api.h" +#include "vcd_core.h" + +struct vcd_dev_state_table; +struct vcd_dev_state_ctxt; +struct vcd_drv_ctxt; + +enum vcd_dev_state_enum { + VCD_DEVICE_STATE_NULL = 0, + VCD_DEVICE_STATE_NOT_INIT, + VCD_DEVICE_STATE_INITING, + VCD_DEVICE_STATE_READY, + VCD_DEVICE_STATE_INVALID, + VCD_DEVICE_STATE_MAX, + VCD_DEVICE_STATE_32BIT = 0x7FFFFFFF +}; + +struct vcd_dev_state_table { + struct { + u32(*init) (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_init_config *config, + s32 *driver_handle); + + u32(*term) (struct vcd_drv_ctxt *drv_ctxt, + s32 driver_handle); + + u32(*open) (struct vcd_drv_ctxt *drv_ctxt, + s32 driver_handle, u32 decoding, + void (*callback) (u32 event, u32 status, + void *info, size_t sz, void *handle, + void *const client_data), + void *client_data); + + u32(*close) (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_clnt_ctxt *cctxt); + + u32(*resume) (struct vcd_drv_ctxt *drv_ctxt, + struct vcd_clnt_ctxt *cctxt); + + u32(*set_dev_pwr) (struct vcd_drv_ctxt *drv_ctxt, + enum vcd_power_state pwr_state); + + void (*dev_cb) (struct vcd_drv_ctxt *drv_ctxt, + u32 event, u32 status, void *payload, + size_t sz, u32 *ddl_handle, + void *const client_data); + + void (*timeout) (struct vcd_drv_ctxt *drv_ctxt, + void *user_data); + } ev_hdlr; + + void (*entry) (struct vcd_drv_ctxt *drv_ctxt, + s32 state_event); + void (*exit) (struct vcd_drv_ctxt *drv_ctxt, + s32 state_event); +}; + +#define DEVICE_STATE_EVENT_NUMBER(ppf) \ + ((u32 *) (&(((struct vcd_dev_state_table*)0)->ev_hdlr.ppf)) - \ + (u32 *) (&(((struct vcd_dev_state_table*)0)->ev_hdlr.init)) \ + + 1) + +struct vcd_dev_state_ctxt { + const struct vcd_dev_state_table *state_table; + + enum vcd_dev_state_enum state; +}; + +struct vcd_drv_ctxt { + struct vcd_dev_state_ctxt dev_state; + struct vcd_dev_ctxt dev_ctxt; + struct mutex dev_mutex; +}; + + +extern struct vcd_drv_ctxt *vcd_get_drv_context(void); + +void vcd_continue(void); + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vcd_power_sm.c b/drivers/video/msm/vidc/common/vcd/vcd_power_sm.c new file mode 100644 index 00000000000..fff6a3ba4ed --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_power_sm.c @@ -0,0 +1,351 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd_power_sm.h" +#include "vcd_core.h" +#include "vcd.h" + +u32 vcd_power_event( + struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt *cctxt, u32 event) +{ + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_MED("Device power state = %d", dev_ctxt->pwr_clk_state); + VCD_MSG_MED("event = 0x%x", event); + switch (event) { + + case VCD_EVT_PWR_DEV_INIT_BEGIN: + case VCD_EVT_PWR_DEV_INIT_END: + case VCD_EVT_PWR_DEV_INIT_FAIL: + case VCD_EVT_PWR_DEV_TERM_BEGIN: + case VCD_EVT_PWR_DEV_TERM_END: + case VCD_EVT_PWR_DEV_TERM_FAIL: + case VCD_EVT_PWR_DEV_SLEEP_BEGIN: + case VCD_EVT_PWR_DEV_SLEEP_END: + case VCD_EVT_PWR_DEV_SET_PERFLVL: + case VCD_EVT_PWR_DEV_HWTIMEOUT: + { + rc = vcd_device_power_event(dev_ctxt, event, + cctxt); + break; + } + + case VCD_EVT_PWR_CLNT_CMD_BEGIN: + case VCD_EVT_PWR_CLNT_CMD_END: + case VCD_EVT_PWR_CLNT_CMD_FAIL: + case VCD_EVT_PWR_CLNT_PAUSE: + case VCD_EVT_PWR_CLNT_RESUME: + case VCD_EVT_PWR_CLNT_FIRST_FRAME: + case VCD_EVT_PWR_CLNT_LAST_FRAME: + case VCD_EVT_PWR_CLNT_ERRFATAL: + { + rc = vcd_client_power_event(dev_ctxt, cctxt, event); + break; + } + + } + + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("vcd_power_event: event 0x%x failed", event); + + + return rc; + +} + +u32 vcd_device_power_event(struct vcd_dev_ctxt *dev_ctxt, u32 event, + struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_ERR_FAIL; + u32 set_perf_lvl; + + switch (event) { + + case VCD_EVT_PWR_DEV_INIT_BEGIN: + { + if (dev_ctxt->pwr_clk_state == + VCD_PWRCLK_STATE_OFF) { + if (res_trk_get_max_perf_level(&dev_ctxt-> + max_perf_lvl)) { + if (res_trk_power_up()) { + dev_ctxt->pwr_clk_state = + VCD_PWRCLK_STATE_ON_NOTCLOCKED; + dev_ctxt->curr_perf_lvl = 0; + dev_ctxt->reqd_perf_lvl = 0; + dev_ctxt->active_clnts = 0; + dev_ctxt-> + set_perf_lvl_pending = false; + rc = vcd_enable_clock(dev_ctxt, + cctxt); + if (VCD_FAILED(rc)) { + (void)res_trk_power_down(); + dev_ctxt->pwr_clk_state = + VCD_PWRCLK_STATE_OFF; + } + } + } + } + + break; + } + + case VCD_EVT_PWR_DEV_INIT_END: + case VCD_EVT_PWR_DEV_TERM_FAIL: + case VCD_EVT_PWR_DEV_SLEEP_BEGIN: + case VCD_EVT_PWR_DEV_HWTIMEOUT: + { + rc = vcd_gate_clock(dev_ctxt); + + break; + } + + case VCD_EVT_PWR_DEV_INIT_FAIL: + case VCD_EVT_PWR_DEV_TERM_END: + { + if (dev_ctxt->pwr_clk_state != + VCD_PWRCLK_STATE_OFF) { + (void)vcd_disable_clock(dev_ctxt); + (void)res_trk_power_down(); + + dev_ctxt->pwr_clk_state = + VCD_PWRCLK_STATE_OFF; + dev_ctxt->curr_perf_lvl = 0; + dev_ctxt->reqd_perf_lvl = 0; + dev_ctxt->active_clnts = 0; + dev_ctxt->set_perf_lvl_pending = false; + rc = VCD_S_SUCCESS; + } + + break; + } + + case VCD_EVT_PWR_DEV_TERM_BEGIN: + case VCD_EVT_PWR_DEV_SLEEP_END: + { + rc = vcd_un_gate_clock(dev_ctxt); + + break; + } + + case VCD_EVT_PWR_DEV_SET_PERFLVL: + { + set_perf_lvl = + dev_ctxt->reqd_perf_lvl > + 0 ? dev_ctxt-> + reqd_perf_lvl : VCD_MIN_PERF_LEVEL; + rc = vcd_set_perf_level(dev_ctxt, set_perf_lvl); + break; + } + } + return rc; +} + +u32 vcd_client_power_event( + struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt *cctxt, u32 event) +{ + u32 rc = VCD_ERR_FAIL; + + switch (event) { + + case VCD_EVT_PWR_CLNT_CMD_BEGIN: + { + rc = vcd_un_gate_clock(dev_ctxt); + break; + } + + case VCD_EVT_PWR_CLNT_CMD_END: + { + rc = vcd_gate_clock(dev_ctxt); + break; + } + + case VCD_EVT_PWR_CLNT_CMD_FAIL: + { + if (!vcd_core_is_busy(dev_ctxt)) + rc = vcd_gate_clock(dev_ctxt); + + break; + } + + case VCD_EVT_PWR_CLNT_PAUSE: + case VCD_EVT_PWR_CLNT_LAST_FRAME: + case VCD_EVT_PWR_CLNT_ERRFATAL: + { + if (cctxt) { + rc = VCD_S_SUCCESS; + if (cctxt->status.req_perf_lvl) { + dev_ctxt->reqd_perf_lvl -= + cctxt->reqd_perf_lvl; + cctxt->status.req_perf_lvl = false; + rc = vcd_set_perf_level(dev_ctxt, + dev_ctxt->reqd_perf_lvl); + } + } + + break; + } + + case VCD_EVT_PWR_CLNT_RESUME: + case VCD_EVT_PWR_CLNT_FIRST_FRAME: + { + if (cctxt) { + rc = VCD_S_SUCCESS; + if (!cctxt->status.req_perf_lvl) { + dev_ctxt->reqd_perf_lvl += + cctxt->reqd_perf_lvl; + cctxt->status.req_perf_lvl = true; + + rc = vcd_set_perf_level(dev_ctxt, + dev_ctxt->reqd_perf_lvl); + } + } + break; + } + } + + return rc; +} + +u32 vcd_enable_clock(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + u32 set_perf_lvl; + + if (dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_OFF) { + VCD_MSG_ERROR("vcd_enable_clock(): Already in state " + "VCD_PWRCLK_STATE_OFF\n"); + rc = VCD_ERR_FAIL; + } else if (dev_ctxt->pwr_clk_state == + VCD_PWRCLK_STATE_ON_NOTCLOCKED) { + set_perf_lvl = + dev_ctxt->reqd_perf_lvl > + 0 ? dev_ctxt-> + reqd_perf_lvl : VCD_MIN_PERF_LEVEL; + rc = vcd_set_perf_level(dev_ctxt, set_perf_lvl); + if (!VCD_FAILED(rc)) { + if (res_trk_enable_clocks()) { + dev_ctxt->pwr_clk_state = + VCD_PWRCLK_STATE_ON_CLOCKED; + } + } else { + rc = VCD_ERR_FAIL; + } + + } + + if (!VCD_FAILED(rc)) + dev_ctxt->active_clnts++; + + return rc; +} + +u32 vcd_disable_clock(struct vcd_dev_ctxt *dev_ctxt) +{ + u32 rc = VCD_S_SUCCESS; + + if (dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_OFF) { + VCD_MSG_ERROR("vcd_disable_clock(): Already in state " + "VCD_PWRCLK_STATE_OFF\n"); + rc = VCD_ERR_FAIL; + } else if (dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_ON_CLOCKED || + dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_ON_CLOCKGATED) { + dev_ctxt->active_clnts--; + + if (!dev_ctxt->active_clnts) { + if (!res_trk_disable_clocks()) + rc = VCD_ERR_FAIL; + + dev_ctxt->pwr_clk_state = + VCD_PWRCLK_STATE_ON_NOTCLOCKED; + dev_ctxt->curr_perf_lvl = 0; + } + } + + return rc; +} + +u32 vcd_set_perf_level(struct vcd_dev_ctxt *dev_ctxt, u32 perf_lvl) +{ + u32 rc = VCD_S_SUCCESS; + if (!vcd_core_is_busy(dev_ctxt)) { + if (res_trk_set_perf_level(perf_lvl, + &dev_ctxt->curr_perf_lvl, dev_ctxt)) { + dev_ctxt->set_perf_lvl_pending = false; + } else { + rc = VCD_ERR_FAIL; + dev_ctxt->set_perf_lvl_pending = true; + } + + } else { + dev_ctxt->set_perf_lvl_pending = true; + } + + return rc; +} + +u32 vcd_update_clnt_perf_lvl( + struct vcd_clnt_ctxt *cctxt, + struct vcd_property_frame_rate *fps, u32 frm_p_units) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + u32 new_perf_lvl; + new_perf_lvl = + frm_p_units * fps->fps_numerator / fps->fps_denominator; + if (cctxt->status.req_perf_lvl) { + dev_ctxt->reqd_perf_lvl = + dev_ctxt->reqd_perf_lvl - cctxt->reqd_perf_lvl + + new_perf_lvl; + rc = vcd_set_perf_level(cctxt->dev_ctxt, + dev_ctxt->reqd_perf_lvl); + } + cctxt->reqd_perf_lvl = new_perf_lvl; + return rc; +} + +u32 vcd_gate_clock(struct vcd_dev_ctxt *dev_ctxt) +{ + u32 rc = VCD_S_SUCCESS; + if (dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_OFF || + dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_ON_NOTCLOCKED) { + VCD_MSG_ERROR("%s(): Clk is Off or Not Clked yet\n", __func__); + rc = VCD_ERR_FAIL; + } else if (dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_ON_CLOCKGATED) + rc = VCD_S_SUCCESS; + else if (res_trk_disable_clocks()) + dev_ctxt->pwr_clk_state = VCD_PWRCLK_STATE_ON_CLOCKGATED; + else + rc = VCD_ERR_FAIL; + return rc; +} + +u32 vcd_un_gate_clock(struct vcd_dev_ctxt *dev_ctxt) +{ + u32 rc = VCD_S_SUCCESS; + if (dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_OFF || + dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_ON_NOTCLOCKED) { + VCD_MSG_ERROR("%s(): Clk is Off or Not Clked yet\n", __func__); + rc = VCD_ERR_FAIL; + } else if (dev_ctxt->pwr_clk_state == VCD_PWRCLK_STATE_ON_CLOCKED) + rc = VCD_S_SUCCESS; + else if (res_trk_enable_clocks()) + dev_ctxt->pwr_clk_state = VCD_PWRCLK_STATE_ON_CLOCKED; + else + rc = VCD_ERR_FAIL; + return rc; +} + diff --git a/drivers/video/msm/vidc/common/vcd/vcd_power_sm.h b/drivers/video/msm/vidc/common/vcd/vcd_power_sm.h new file mode 100644 index 00000000000..26ce0196d48 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_power_sm.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_POWERSM_H_ +#define _VCD_POWERSM_H_ + +#define VCD_EVT_PWR_BASE 0x5000 +#define VCD_EVT_PWR_DEV_INIT_BEGIN (VCD_EVT_PWR_BASE + 0x1) +#define VCD_EVT_PWR_DEV_INIT_END (VCD_EVT_PWR_BASE + 0x2) +#define VCD_EVT_PWR_DEV_INIT_FAIL (VCD_EVT_PWR_BASE + 0x3) +#define VCD_EVT_PWR_DEV_TERM_BEGIN (VCD_EVT_PWR_BASE + 0x4) +#define VCD_EVT_PWR_DEV_TERM_END (VCD_EVT_PWR_BASE + 0x5) +#define VCD_EVT_PWR_DEV_TERM_FAIL (VCD_EVT_PWR_BASE + 0x6) +#define VCD_EVT_PWR_DEV_SLEEP_BEGIN (VCD_EVT_PWR_BASE + 0x7) +#define VCD_EVT_PWR_DEV_SLEEP_END (VCD_EVT_PWR_BASE + 0x8) +#define VCD_EVT_PWR_DEV_SET_PERFLVL (VCD_EVT_PWR_BASE + 0x9) +#define VCD_EVT_PWR_DEV_HWTIMEOUT (VCD_EVT_PWR_BASE + 0xa) +#define VCD_EVT_PWR_CLNT_CMD_BEGIN (VCD_EVT_PWR_BASE + 0xb) +#define VCD_EVT_PWR_CLNT_CMD_END (VCD_EVT_PWR_BASE + 0xc) +#define VCD_EVT_PWR_CLNT_CMD_FAIL (VCD_EVT_PWR_BASE + 0xd) +#define VCD_EVT_PWR_CLNT_PAUSE (VCD_EVT_PWR_BASE + 0xe) +#define VCD_EVT_PWR_CLNT_RESUME (VCD_EVT_PWR_BASE + 0xf) +#define VCD_EVT_PWR_CLNT_FIRST_FRAME (VCD_EVT_PWR_BASE + 0x10) +#define VCD_EVT_PWR_CLNT_LAST_FRAME (VCD_EVT_PWR_BASE + 0x11) +#define VCD_EVT_PWR_CLNT_ERRFATAL (VCD_EVT_PWR_BASE + 0x12) + +enum vcd_pwr_clk_state { + VCD_PWRCLK_STATE_OFF = 0, + VCD_PWRCLK_STATE_ON_NOTCLOCKED, + VCD_PWRCLK_STATE_ON_CLOCKED, + VCD_PWRCLK_STATE_ON_CLOCKGATED +}; + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vcd_property.h b/drivers/video/msm/vidc/common/vcd/vcd_property.h new file mode 100644 index 00000000000..2cb894e484c --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_property.h @@ -0,0 +1,342 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_DRIVER_PROPERTY_H_ +#define _VCD_DRIVER_PROPERTY_H_ + +#define VCD_START_BASE 0x0 +#define VCD_I_LIVE (VCD_START_BASE + 0x1) +#define VCD_I_CODEC (VCD_START_BASE + 0x2) +#define VCD_I_FRAME_SIZE (VCD_START_BASE + 0x3) +#define VCD_I_METADATA_ENABLE (VCD_START_BASE + 0x4) +#define VCD_I_METADATA_HEADER (VCD_START_BASE + 0x5) +#define VCD_I_PROFILE (VCD_START_BASE + 0x6) +#define VCD_I_LEVEL (VCD_START_BASE + 0x7) +#define VCD_I_BUFFER_FORMAT (VCD_START_BASE + 0x8) +#define VCD_I_FRAME_RATE (VCD_START_BASE + 0x9) +#define VCD_I_TARGET_BITRATE (VCD_START_BASE + 0xA) +#define VCD_I_MULTI_SLICE (VCD_START_BASE + 0xB) +#define VCD_I_ENTROPY_CTRL (VCD_START_BASE + 0xC) +#define VCD_I_DEBLOCKING (VCD_START_BASE + 0xD) +#define VCD_I_RATE_CONTROL (VCD_START_BASE + 0xE) +#define VCD_I_QP_RANGE (VCD_START_BASE + 0xF) +#define VCD_I_SESSION_QP (VCD_START_BASE + 0x10) +#define VCD_I_INTRA_PERIOD (VCD_START_BASE + 0x11) +#define VCD_I_VOP_TIMING (VCD_START_BASE + 0x12) +#define VCD_I_SHORT_HEADER (VCD_START_BASE + 0x13) +#define VCD_I_SEQ_HEADER (VCD_START_BASE + 0x14) +#define VCD_I_HEADER_EXTENSION (VCD_START_BASE + 0x15) +#define VCD_I_INTRA_REFRESH (VCD_START_BASE + 0x16) +#define VCD_I_POST_FILTER (VCD_START_BASE + 0x17) +#define VCD_I_PROGRESSIVE_ONLY (VCD_START_BASE + 0x18) +#define VCD_I_OUTPUT_ORDER (VCD_START_BASE + 0x19) +#define VCD_I_RECON_BUFFERS (VCD_START_BASE + 0x1A) +#define VCD_I_FREE_RECON_BUFFERS (VCD_START_BASE + 0x1B) +#define VCD_I_GET_RECON_BUFFER_SIZE (VCD_START_BASE + 0x1C) +#define VCD_I_H264_MV_BUFFER (VCD_START_BASE + 0x1D) +#define VCD_I_FREE_H264_MV_BUFFER (VCD_START_BASE + 0x1E) +#define VCD_I_GET_H264_MV_SIZE (VCD_START_BASE + 0x1F) +#define VCD_I_DEC_PICTYPE (VCD_START_BASE + 0x20) +#define VCD_I_CONT_ON_RECONFIG (VCD_START_BASE + 0x21) + +#define VCD_START_REQ (VCD_START_BASE + 0x1000) +#define VCD_I_REQ_IFRAME (VCD_START_REQ + 0x1) + +#define VCD_I_RESERVED_BASE (VCD_START_BASE + 0x10000) + +struct vcd_property_hdr { + u32 prop_id; + size_t sz; +}; + +struct vcd_property_live { + u32 live; +}; + +enum vcd_codec { + VCD_CODEC_H264 = 0x1, + VCD_CODEC_H263 = 0x2, + VCD_CODEC_MPEG1 = 0x3, + VCD_CODEC_MPEG2 = 0x4, + VCD_CODEC_MPEG4 = 0x5, + VCD_CODEC_DIVX_3 = 0x6, + VCD_CODEC_DIVX_4 = 0x7, + VCD_CODEC_DIVX_5 = 0x8, + VCD_CODEC_DIVX_6 = 0x9, + VCD_CODEC_XVID = 0xA, + VCD_CODEC_VC1 = 0xB, + VCD_CODEC_VC1_RCV = 0xC +}; + +struct vcd_property_codec { + enum vcd_codec codec; +}; + +struct vcd_property_frame_size { + u32 width; + u32 height; + u32 stride; + u32 scan_lines; +}; + + +#define VCD_METADATA_DATANONE 0x001 +#define VCD_METADATA_QCOMFILLER 0x002 +#define VCD_METADATA_QPARRAY 0x004 +#define VCD_METADATA_CONCEALMB 0x008 +#define VCD_METADATA_SEI 0x010 +#define VCD_METADATA_VUI 0x020 +#define VCD_METADATA_VC1 0x040 +#define VCD_METADATA_PASSTHROUGH 0x080 +#define VCD_METADATA_ENC_SLICE 0x100 + +struct vcd_property_meta_data_enable { + u32 meta_data_enable_flag; +}; + +struct vcd_property_metadata_hdr { + u32 meta_data_id; + u32 version; + u32 port_index; + u32 type; +}; + +struct vcd_property_frame_rate { + u32 fps_denominator; + u32 fps_numerator; +}; + +struct vcd_property_target_bitrate { + u32 target_bitrate; +}; + +enum vcd_yuv_buffer_format { + VCD_BUFFER_FORMAT_NV12 = 0x1, + VCD_BUFFER_FORMAT_TILE_4x2 = 0x2, + VCD_BUFFER_FORMAT_NV12_16M2KA = 0x3, + VCD_BUFFER_FORMAT_TILE_1x1 = 0x4 +}; + +struct vcd_property_buffer_format { + enum vcd_yuv_buffer_format buffer_format; +}; + +struct vcd_property_post_filter { + u32 post_filter; +}; + +enum vcd_codec_profile { + VCD_PROFILE_UNKNOWN = 0x0, + VCD_PROFILE_MPEG4_SP = 0x1, + VCD_PROFILE_MPEG4_ASP = 0x2, + VCD_PROFILE_H264_BASELINE = 0x3, + VCD_PROFILE_H264_MAIN = 0x4, + VCD_PROFILE_H264_HIGH = 0x5, + VCD_PROFILE_H263_BASELINE = 0x6, + VCD_PROFILE_VC1_SIMPLE = 0x7, + VCD_PROFILE_VC1_MAIN = 0x8, + VCD_PROFILE_VC1_ADVANCE = 0x9, + VCD_PROFILE_MPEG2_MAIN = 0xA, + VCD_PROFILE_MPEG2_SIMPLE = 0xB +}; + +struct vcd_property_profile { + enum vcd_codec_profile profile; +}; + +enum vcd_codec_level { + VCD_LEVEL_UNKNOWN = 0x0, + VCD_LEVEL_MPEG4_0 = 0x1, + VCD_LEVEL_MPEG4_0b = 0x2, + VCD_LEVEL_MPEG4_1 = 0x3, + VCD_LEVEL_MPEG4_2 = 0x4, + VCD_LEVEL_MPEG4_3 = 0x5, + VCD_LEVEL_MPEG4_3b = 0x6, + VCD_LEVEL_MPEG4_4 = 0x7, + VCD_LEVEL_MPEG4_4a = 0x8, + VCD_LEVEL_MPEG4_5 = 0x9, + VCD_LEVEL_MPEG4_6 = 0xA, + VCD_LEVEL_MPEG4_7 = 0xB, + VCD_LEVEL_MPEG4_X = 0xC, + VCD_LEVEL_H264_1 = 0x10, + VCD_LEVEL_H264_1b = 0x11, + VCD_LEVEL_H264_1p1 = 0x12, + VCD_LEVEL_H264_1p2 = 0x13, + VCD_LEVEL_H264_1p3 = 0x14, + VCD_LEVEL_H264_2 = 0x15, + VCD_LEVEL_H264_2p1 = 0x16, + VCD_LEVEL_H264_2p2 = 0x17, + VCD_LEVEL_H264_3 = 0x18, + VCD_LEVEL_H264_3p1 = 0x19, + VCD_LEVEL_H264_3p2 = 0x1A, + VCD_LEVEL_H264_4 = 0x1B, + VCD_LEVEL_H264_4p1 = 0x1C, + VCD_LEVEL_H264_4p2 = 0x1D, + VCD_LEVEL_H264_5 = 0x1E, + VCD_LEVEL_H264_5p1 = 0x1F, + VCD_LEVEL_H263_10 = 0x20, + VCD_LEVEL_H263_20 = 0x21, + VCD_LEVEL_H263_30 = 0x22, + VCD_LEVEL_H263_40 = 0x23, + VCD_LEVEL_H263_45 = 0x24, + VCD_LEVEL_H263_50 = 0x25, + VCD_LEVEL_H263_60 = 0x26, + VCD_LEVEL_H263_70 = 0x27, + VCD_LEVEL_H263_X = 0x28, + VCD_LEVEL_MPEG2_LOW = 0x30, + VCD_LEVEL_MPEG2_MAIN = 0x31, + VCD_LEVEL_MPEG2_HIGH_14 = 0x32, + VCD_LEVEL_MPEG2_HIGH = 0x33, + VCD_LEVEL_MPEG2_X = 0x34, + VCD_LEVEL_VC1_S_LOW = 0x40, + VCD_LEVEL_VC1_S_MEDIUM = 0x41, + VCD_LEVEL_VC1_M_LOW = 0x42, + VCD_LEVEL_VC1_M_MEDIUM = 0x43, + VCD_LEVEL_VC1_M_HIGH = 0x44, + VCD_LEVEL_VC1_A_0 = 0x45, + VCD_LEVEL_VC1_A_1 = 0x46, + VCD_LEVEL_VC1_A_2 = 0x47, + VCD_LEVEL_VC1_A_3 = 0x48, + VCD_LEVEL_VC1_A_4 = 0x49, + VCD_LEVEL_VC1_X = 0x4A +}; + +struct vcd_property_level { + enum vcd_codec_level level; +}; + +enum vcd_m_slice_sel { + VCD_MSLICE_OFF = 0x1, + VCD_MSLICE_BY_MB_COUNT = 0x2, + VCD_MSLICE_BY_BYTE_COUNT = 0x3, + VCD_MSLICE_BY_GOB = 0x4 +}; + +struct vcd_property_multi_slice { + enum vcd_m_slice_sel m_slice_sel; + u32 m_slice_size; +}; + +enum vcd_entropy_sel { + VCD_ENTROPY_SEL_CAVLC = 0x1, + VCD_ENTROPY_SEL_CABAC = 0x2 +}; + +enum vcd_cabac_model { + VCD_CABAC_MODEL_NUMBER_0 = 0x1, + VCD_CABAC_MODEL_NUMBER_1 = 0x2, + VCD_CABAC_MODEL_NUMBER_2 = 0x3 +}; + +struct vcd_property_entropy_control { + enum vcd_entropy_sel entropy_sel; + enum vcd_cabac_model cabac_model; +}; + +enum vcd_db_config { + VCD_DB_ALL_BLOCKING_BOUNDARY = 0x1, + VCD_DB_DISABLE = 0x2, + VCD_DB_SKIP_SLICE_BOUNDARY = 0x3 +}; +struct vcd_property_db_config { + enum vcd_db_config db_config; + u32 slice_alpha_offset; + u32 slice_beta_offset; +}; + +enum vcd_rate_control { + VCD_RATE_CONTROL_OFF = 0x1, + VCD_RATE_CONTROL_VBR_VFR = 0x2, + VCD_RATE_CONTROL_VBR_CFR = 0x3, + VCD_RATE_CONTROL_CBR_VFR = 0x4, + VCD_RATE_CONTROL_CBR_CFR = 0x5 +}; + +struct vcd_property_rate_control { + enum vcd_rate_control rate_control; +}; + +struct vcd_property_qp_range { + u32 max_qp; + u32 min_qp; +}; + +struct vcd_property_session_qp { + u32 i_frame_qp; + u32 p_frame_qp; + u32 b_frame_qp; +}; + +struct vcd_property_i_period { + u32 p_frames; + u32 b_frames; +}; + +struct vcd_property_vop_timing { + u32 vop_time_resolution; +}; + +struct vcd_property_short_header { + u32 short_header; +}; + +struct vcd_property_intra_refresh_mb_number { + u32 cir_mb_number; +}; + +struct vcd_property_req_i_frame { + u32 req_i_frame; +}; + +struct vcd_frame_rect{ + u32 left; + u32 top; + u32 right; + u32 bottom; +}; + +struct vcd_property_dec_output_buffer { + struct vcd_frame_rect disp_frm; + struct vcd_property_frame_size frm_size; +}; + +enum vcd_output_order { + VCD_DEC_ORDER_DISPLAY = 0x0, + VCD_DEC_ORDER_DECODE = 0x1 +}; + +struct vcd_property_enc_recon_buffer{ + u8 *kernel_virtual_addr; + u8 *physical_addr; + u32 buffer_size; + u32 ysize; + int pmem_fd; + u32 offset; +}; + +struct vcd_property_h264_mv_buffer{ + u8 *kernel_virtual_addr; + u8 *physical_addr; + u32 size; + u32 count; + int pmem_fd; + u32 offset; +}; + +struct vcd_property_buffer_size{ + int width; + int height; + int size; + int alignment; +}; + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vcd_scheduler.c b/drivers/video/msm/vidc/common/vcd/vcd_scheduler.c new file mode 100644 index 00000000000..34a3445dcd9 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_scheduler.c @@ -0,0 +1,286 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd.h" + +#define NORMALIZATION_FACTOR 3600 +#define ADJUST_CLIENT_ROUNDS(client, round_adjustment) \ +do {\ + if ((client)->rounds < round_adjustment) {\ + (client)->rounds = 0;\ + VCD_MSG_HIGH("%s(): WARNING: Scheduler list unsorted",\ + __func__);\ + } else\ + (client)->rounds -= round_adjustment;\ +} while (0) + +u32 vcd_sched_create(struct list_head *sched_list) +{ + u32 rc = VCD_S_SUCCESS; + if (!sched_list) { + VCD_MSG_ERROR("%s(): Invalid parameter", __func__); + rc = VCD_ERR_ILLEGAL_PARM; + } else + INIT_LIST_HEAD(sched_list); + return rc; +} + +void vcd_sched_destroy(struct list_head *sched_clnt_list) +{ + struct vcd_sched_clnt_ctx *sched_clnt, *sched_clnt_next; + if (sched_clnt_list) + list_for_each_entry_safe(sched_clnt, + sched_clnt_next, sched_clnt_list, list) { + list_del_init(&sched_clnt->list); + sched_clnt->clnt_active = false; + } +} + +void insert_client_in_list(struct list_head *sched_clnt_list, + struct vcd_sched_clnt_ctx *sched_new_clnt, bool tail) +{ + struct vcd_sched_clnt_ctx *sched_clnt; + if (!list_empty(sched_clnt_list)) { + if (tail) + sched_clnt = list_entry(sched_clnt_list->prev, + struct vcd_sched_clnt_ctx, list); + else + sched_clnt = list_first_entry(sched_clnt_list, + struct vcd_sched_clnt_ctx, list); + sched_new_clnt->rounds = sched_clnt->rounds; + } else + sched_new_clnt->rounds = 0; + if (tail) + list_add_tail(&sched_new_clnt->list, sched_clnt_list); + else + list_add(&sched_new_clnt->list, sched_clnt_list); +} + +u32 vcd_sched_add_client(struct vcd_clnt_ctxt *cctxt) +{ + struct vcd_property_hdr prop_hdr; + struct vcd_sched_clnt_ctx *sched_cctxt; + u32 rc = VCD_S_SUCCESS; + if (!cctxt) { + VCD_MSG_ERROR("%s(): Invalid parameter", __func__); + rc = VCD_ERR_ILLEGAL_PARM; + } else if (cctxt->sched_clnt_hdl) + VCD_MSG_HIGH( + "%s(): Scheduler client already exists!", __func__); + else { + sched_cctxt = (struct vcd_sched_clnt_ctx *) + kmalloc(sizeof(struct vcd_sched_clnt_ctx), + GFP_KERNEL); + if (sched_cctxt) { + + prop_hdr.prop_id = DDL_I_FRAME_PROC_UNITS; + prop_hdr.sz = sizeof(cctxt->frm_p_units); + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, + &cctxt->frm_p_units); + VCD_FAILED_RETURN(rc, + "Failed: Get DDL_I_FRAME_PROC_UNITS"); + if (cctxt->decoding) { + cctxt->frm_rate.fps_numerator = + VCD_DEC_INITIAL_FRAME_RATE; + cctxt->frm_rate.fps_denominator = 1; + } else { + prop_hdr.prop_id = VCD_I_FRAME_RATE; + prop_hdr.sz = sizeof(cctxt->frm_rate); + rc = ddl_get_property(cctxt->ddl_handle, + &prop_hdr, &cctxt->frm_rate); + VCD_FAILED_RETURN(rc, + "Failed: Get VCD_I_FRAME_RATE"); + } + cctxt->reqd_perf_lvl = cctxt->frm_p_units * + cctxt->frm_rate.fps_numerator / + cctxt->frm_rate.fps_denominator; + + cctxt->sched_clnt_hdl = sched_cctxt; + memset(sched_cctxt, 0, + sizeof(struct vcd_sched_clnt_ctx)); + sched_cctxt->tkns = 0; + sched_cctxt->round_perfrm = NORMALIZATION_FACTOR * + cctxt->frm_rate.fps_denominator / + cctxt->frm_rate.fps_numerator; + sched_cctxt->clnt_active = true; + sched_cctxt->clnt_data = cctxt; + INIT_LIST_HEAD(&sched_cctxt->ip_frm_list); + + insert_client_in_list( + &cctxt->dev_ctxt->sched_clnt_list, + sched_cctxt, false); + } + } + return rc; +} + +u32 vcd_sched_remove_client(struct vcd_sched_clnt_ctx *sched_cctxt) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_clnt_ctxt *cctxt; + if (!sched_cctxt) { + VCD_MSG_ERROR("%s(): Invalid handle ptr", __func__); + rc = VCD_ERR_ILLEGAL_PARM; + } else if (!list_empty(&sched_cctxt->ip_frm_list)) { + VCD_MSG_ERROR( + "%s(): Cannot remove client, queue no empty", __func__); + rc = VCD_ERR_ILLEGAL_OP; + } else { + cctxt = sched_cctxt->clnt_data; + list_del(&sched_cctxt->list); + memset(sched_cctxt, 0, + sizeof(struct vcd_sched_clnt_ctx)); + kfree(sched_cctxt); + } + return rc; +} + +u32 vcd_sched_update_config(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + if (!cctxt || !cctxt->sched_clnt_hdl) { + VCD_MSG_ERROR("%s(): Invalid parameter", __func__); + rc = VCD_ERR_ILLEGAL_PARM; + } else { + cctxt->sched_clnt_hdl->rounds /= + cctxt->sched_clnt_hdl->round_perfrm; + cctxt->sched_clnt_hdl->round_perfrm = + NORMALIZATION_FACTOR * + cctxt->frm_rate.fps_denominator / + cctxt->frm_rate.fps_numerator; + cctxt->sched_clnt_hdl->rounds *= + cctxt->sched_clnt_hdl->round_perfrm; + } + return rc; +} + +u32 vcd_sched_queue_buffer( + struct vcd_sched_clnt_ctx *sched_cctxt, + struct vcd_buffer_entry *buffer, u32 tail) +{ + u32 rc = VCD_S_SUCCESS; + if (!sched_cctxt || !buffer) { + VCD_MSG_ERROR("%s(): Invalid parameter", __func__); + rc = VCD_ERR_ILLEGAL_PARM; + } else if (tail) + list_add_tail(&buffer->sched_list, + &sched_cctxt->ip_frm_list); + else + list_add(&buffer->sched_list, &sched_cctxt->ip_frm_list); + return rc; +} + +u32 vcd_sched_dequeue_buffer( + struct vcd_sched_clnt_ctx *sched_cctxt, + struct vcd_buffer_entry **buffer) +{ + u32 rc = VCD_ERR_QEMPTY; + if (!sched_cctxt || !buffer) { + VCD_MSG_ERROR("%s(): Invalid parameter", __func__); + rc = VCD_ERR_ILLEGAL_PARM; + } else { + *buffer = NULL; + if (!list_empty(&sched_cctxt->ip_frm_list)) { + *buffer = list_first_entry( + &sched_cctxt->ip_frm_list, + struct vcd_buffer_entry, + sched_list); + list_del(&(*buffer)->sched_list); + rc = VCD_S_SUCCESS; + } + } + return rc; +} + +u32 vcd_sched_mark_client_eof(struct vcd_sched_clnt_ctx *sched_cctxt) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_buffer_entry *buffer = NULL; + if (!sched_cctxt) { + VCD_MSG_ERROR("%s(): Invalid parameter", __func__); + rc = VCD_ERR_ILLEGAL_PARM; + } else if (!list_empty(&sched_cctxt->ip_frm_list)) { + buffer = list_entry(sched_cctxt->ip_frm_list.prev, + struct vcd_buffer_entry, sched_list); + buffer->frame.flags |= VCD_FRAME_FLAG_EOS; + } else + rc = VCD_ERR_QEMPTY; + return rc; +} + +u32 vcd_sched_suspend_resume_clnt( + struct vcd_clnt_ctxt *cctxt, u32 state) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_sched_clnt_ctx *sched_cctxt; + if (!cctxt || !cctxt->sched_clnt_hdl) { + VCD_MSG_ERROR("%s(): Invalid parameter", __func__); + rc = VCD_ERR_ILLEGAL_PARM; + } else { + sched_cctxt = cctxt->sched_clnt_hdl; + if (state != sched_cctxt->clnt_active) { + sched_cctxt->clnt_active = state; + if (state) + insert_client_in_list(&cctxt->dev_ctxt->\ + sched_clnt_list, sched_cctxt, false); + else + list_del_init(&sched_cctxt->list); + } + } + return rc; +} + +u32 vcd_sched_get_client_frame(struct list_head *sched_clnt_list, + struct vcd_clnt_ctxt **cctxt, + struct vcd_buffer_entry **buffer) +{ + u32 rc = VCD_ERR_QEMPTY, round_adjustment = 0; + struct vcd_sched_clnt_ctx *sched_clnt, *clnt_nxt; + if (!sched_clnt_list || !cctxt || !buffer) { + VCD_MSG_ERROR("%s(): Invalid parameter", __func__); + rc = VCD_ERR_ILLEGAL_PARM; + } else if (!list_empty(sched_clnt_list)) { + *cctxt = NULL; + *buffer = NULL; + list_for_each_entry_safe(sched_clnt, + clnt_nxt, sched_clnt_list, list) { + if (&sched_clnt->list == sched_clnt_list->next) + round_adjustment = sched_clnt->rounds; + if (*cctxt) { + if ((*cctxt)->sched_clnt_hdl->rounds >= + sched_clnt->rounds) + list_move(&(*cctxt)->sched_clnt_hdl\ + ->list, &sched_clnt->list); + ADJUST_CLIENT_ROUNDS(sched_clnt, + round_adjustment); + } else if (sched_clnt->tkns && + !list_empty(&sched_clnt->ip_frm_list)) { + *cctxt = sched_clnt->clnt_data; + sched_clnt->rounds += sched_clnt->round_perfrm; + } else + ADJUST_CLIENT_ROUNDS(sched_clnt, + round_adjustment); + } + if (*cctxt) { + rc = vcd_sched_dequeue_buffer( + (*cctxt)->sched_clnt_hdl, buffer); + if (rc == VCD_S_SUCCESS) { + (*cctxt)->sched_clnt_hdl->tkns--; + ADJUST_CLIENT_ROUNDS((*cctxt)->\ + sched_clnt_hdl, round_adjustment); + } + } + } + return rc; +} diff --git a/drivers/video/msm/vidc/common/vcd/vcd_status.h b/drivers/video/msm/vidc/common/vcd/vcd_status.h new file mode 100644 index 00000000000..807718fbc03 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_status.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VCD_ERR_STATUS_H_ +#define _VCD_ERR_STATUS_H_ + +#define VCD_EVT_RESP_BASE 0x1000 +#define VCD_EVT_RESP_OPEN (VCD_EVT_RESP_BASE + 0x1) +#define VCD_EVT_RESP_START (VCD_EVT_RESP_BASE + 0x2) +#define VCD_EVT_RESP_STOP (VCD_EVT_RESP_BASE + 0x3) +#define VCD_EVT_RESP_PAUSE (VCD_EVT_RESP_BASE + 0x4) +#define VCD_EVT_RESP_FLUSH_INPUT_DONE (VCD_EVT_RESP_BASE + 0x5) +#define VCD_EVT_RESP_FLUSH_OUTPUT_DONE (VCD_EVT_RESP_BASE + 0x6) +#define VCD_EVT_RESP_INPUT_FLUSHED (VCD_EVT_RESP_BASE + 0x7) +#define VCD_EVT_RESP_OUTPUT_FLUSHED (VCD_EVT_RESP_BASE + 0x8) +#define VCD_EVT_RESP_INPUT_DONE (VCD_EVT_RESP_BASE + 0x9) +#define VCD_EVT_RESP_OUTPUT_DONE (VCD_EVT_RESP_BASE + 0xa) + +#define VCD_EVT_IND_BASE 0x2000 +#define VCD_EVT_IND_INPUT_RECONFIG (VCD_EVT_IND_BASE + 0x1) +#define VCD_EVT_IND_OUTPUT_RECONFIG (VCD_EVT_IND_BASE + 0x2) +#define VCD_EVT_IND_HWERRFATAL (VCD_EVT_IND_BASE + 0x3) +#define VCD_EVT_IND_RESOURCES_LOST (VCD_EVT_IND_BASE + 0x4) +#define VCD_EVT_IND_INFO_OUTPUT_RECONFIG (VCD_EVT_IND_BASE + 0x5) + +#define VCD_S_SUCCESS 0x0 + +#define VCD_S_ERR_BASE 0x80000000 +#define VCD_ERR_FAIL (VCD_S_ERR_BASE + 0x01) +#define VCD_ERR_ALLOC_FAIL (VCD_S_ERR_BASE + 0x02) +#define VCD_ERR_ILLEGAL_OP (VCD_S_ERR_BASE + 0x03) +#define VCD_ERR_ILLEGAL_PARM (VCD_S_ERR_BASE + 0x04) +#define VCD_ERR_BAD_POINTER (VCD_S_ERR_BASE + 0x05) +#define VCD_ERR_BAD_HANDLE (VCD_S_ERR_BASE + 0x06) +#define VCD_ERR_NOT_SUPPORTED (VCD_S_ERR_BASE + 0x07) +#define VCD_ERR_BAD_STATE (VCD_S_ERR_BASE + 0x08) +#define VCD_ERR_BUSY (VCD_S_ERR_BASE + 0x09) +#define VCD_ERR_MAX_CLIENT (VCD_S_ERR_BASE + 0x0a) +#define VCD_ERR_IFRAME_EXPECTED (VCD_S_ERR_BASE + 0x0b) +#define VCD_ERR_INTRLCD_FIELD_DROP (VCD_S_ERR_BASE + 0x0c) +#define VCD_ERR_HW_FATAL (VCD_S_ERR_BASE + 0x0d) +#define VCD_ERR_BITSTREAM_ERR (VCD_S_ERR_BASE + 0x0e) +#define VCD_ERR_QEMPTY (VCD_S_ERR_BASE + 0x0f) +#define VCD_ERR_SEQHDR_PARSE_FAIL (VCD_S_ERR_BASE + 0x10) +#define VCD_ERR_INPUT_NOT_PROCESSED (VCD_S_ERR_BASE + 0x11) +#define VCD_ERR_INDEX_NOMORE (VCD_S_ERR_BASE + 0x12) + +#define VCD_FAILED(rc) ((rc > VCD_S_ERR_BASE) ? true : false) + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vcd_sub.c b/drivers/video/msm/vidc/common/vcd/vcd_sub.c new file mode 100644 index 00000000000..6bc591bf318 --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_sub.c @@ -0,0 +1,3057 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include + +#include "vidc_type.h" +#include "vcd.h" +#include "vdec_internal.h" +#include + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) + +static int vcd_pmem_alloc(size_t sz, u8 **kernel_vaddr, u8 **phy_addr) +{ + u32 memtype; + + if (!kernel_vaddr || !phy_addr) { + pr_err("\n%s: Invalid parameters", __func__); + return -ENOMEM; + } + memtype = res_trk_get_mem_type(); + *phy_addr = (u8 *) allocate_contiguous_memory_nomap(sz, + memtype, SZ_4K); + if (!*phy_addr) { + *kernel_vaddr = ioremap((unsigned long)*phy_addr, sz); + + if (!*kernel_vaddr) { + pr_err("%s: could not ioremap in kernel pmem buffers\n", + __func__); + free_contiguous_memory_by_paddr( + (unsigned long) *phy_addr); + *phy_addr = NULL; + return -ENOMEM; + } + pr_debug("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + (u32) *phy_addr, (u32) *kernel_vaddr); + return 0; + } else { + pr_err("%s: could not allocte in kernel pmem buffers\n", + __func__); + return -ENOMEM; + } + +} + +static int vcd_pmem_free(u8 *kernel_vaddr, u8 *phy_addr) +{ + if (kernel_vaddr) + iounmap((void *)kernel_vaddr); + if (phy_addr) + free_contiguous_memory_by_paddr((unsigned long)phy_addr); + kernel_vaddr = NULL; + phy_addr = NULL; + return 0; +} + +u8 *vcd_pmem_get_physical(struct video_client_ctx *client_ctx, + unsigned long kernel_vaddr) +{ + unsigned long phy_addr, user_vaddr; + int pmem_fd; + struct file *file; + s32 buffer_index = -1; + + if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_INPUT, + false, &user_vaddr, &kernel_vaddr, + &phy_addr, &pmem_fd, &file, + &buffer_index)) { + + return (u8 *) phy_addr; + } else if (vidc_lookup_addr_table(client_ctx, BUFFER_TYPE_OUTPUT, + false, &user_vaddr, &kernel_vaddr, &phy_addr, &pmem_fd, &file, + &buffer_index)) { + return (u8 *) phy_addr; + } else { + VCD_MSG_ERROR("Couldn't get physical address"); + + return NULL; + } + +} + +void vcd_reset_device_channels(struct vcd_dev_ctxt *dev_ctxt) +{ + dev_ctxt->ddl_frame_ch_free = dev_ctxt->ddl_frame_ch_depth; + dev_ctxt->ddl_cmd_ch_free = dev_ctxt->ddl_cmd_ch_depth; + dev_ctxt->ddl_frame_ch_interim = 0; + dev_ctxt->ddl_cmd_ch_interim = 0; +} + +u32 vcd_get_command_channel( + struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc **transc) +{ + u32 result = false; + + *transc = NULL; + + if (dev_ctxt->ddl_cmd_ch_free > 0) { + if (dev_ctxt->ddl_cmd_concurrency) { + --dev_ctxt->ddl_cmd_ch_free; + result = true; + } else if ((dev_ctxt->ddl_frame_ch_free + + dev_ctxt->ddl_frame_ch_interim) + == dev_ctxt->ddl_frame_ch_depth) { + --dev_ctxt->ddl_cmd_ch_free; + result = true; + } + } + + if (result) { + *transc = vcd_get_free_trans_tbl_entry(dev_ctxt); + + if (!*transc) { + result = false; + + vcd_release_command_channel(dev_ctxt, *transc); + } + + } + return result; +} + +u32 vcd_get_command_channel_in_loop( + struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc **transc) +{ + u32 result = false; + + *transc = NULL; + + if (dev_ctxt->ddl_cmd_ch_interim > 0) { + if (dev_ctxt->ddl_cmd_concurrency) { + --dev_ctxt->ddl_cmd_ch_interim; + result = true; + } else if ((dev_ctxt->ddl_frame_ch_free + + dev_ctxt->ddl_frame_ch_interim) + == dev_ctxt->ddl_frame_ch_depth) { + --dev_ctxt->ddl_cmd_ch_interim; + result = true; + } + } else { + result = vcd_get_command_channel(dev_ctxt, transc); + } + + if (result && !*transc) { + *transc = vcd_get_free_trans_tbl_entry(dev_ctxt); + + if (!*transc) { + result = false; + + ++dev_ctxt->ddl_cmd_ch_interim; + } + + } + + return result; +} + +void vcd_mark_command_channel(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc *transc) +{ + ++dev_ctxt->ddl_cmd_ch_interim; + + vcd_release_trans_tbl_entry(transc); + if (dev_ctxt->ddl_cmd_ch_interim + + dev_ctxt->ddl_cmd_ch_free > + dev_ctxt->ddl_cmd_ch_depth) { + VCD_MSG_ERROR("\n Command channel access counters messed up"); + } +} + +void vcd_release_command_channel( + struct vcd_dev_ctxt *dev_ctxt, struct vcd_transc *transc) +{ + ++dev_ctxt->ddl_cmd_ch_free; + + vcd_release_trans_tbl_entry(transc); + if (dev_ctxt->ddl_cmd_ch_interim + dev_ctxt->ddl_cmd_ch_free > + dev_ctxt->ddl_cmd_ch_depth) { + VCD_MSG_ERROR("\n Command channel access counters messed up"); + } +} + +void vcd_release_multiple_command_channels(struct vcd_dev_ctxt + *dev_ctxt, u32 channels) +{ + dev_ctxt->ddl_cmd_ch_free += channels; + + if (dev_ctxt->ddl_cmd_ch_interim + + dev_ctxt->ddl_cmd_ch_free > + dev_ctxt->ddl_cmd_ch_depth) { + VCD_MSG_ERROR("\n Command channel access counters messed up"); + } +} + +void vcd_release_interim_command_channels(struct vcd_dev_ctxt *dev_ctxt) +{ + dev_ctxt->ddl_cmd_ch_free += dev_ctxt->ddl_cmd_ch_interim; + dev_ctxt->ddl_cmd_ch_interim = 0; + + if (dev_ctxt->ddl_cmd_ch_interim + dev_ctxt->ddl_cmd_ch_free > + dev_ctxt->ddl_cmd_ch_depth) { + VCD_MSG_ERROR("\n Command channel access counters messed up"); + } +} + +u32 vcd_get_frame_channel(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc **transc) +{ + u32 result = false; + + if (dev_ctxt->ddl_frame_ch_free > 0) { + if (dev_ctxt->ddl_cmd_concurrency) { + --dev_ctxt->ddl_frame_ch_free; + result = true; + } else if ((dev_ctxt->ddl_cmd_ch_free + + dev_ctxt->ddl_cmd_ch_interim) + == dev_ctxt->ddl_cmd_ch_depth) { + --dev_ctxt->ddl_frame_ch_free; + result = true; + } + } + + if (result) { + *transc = vcd_get_free_trans_tbl_entry(dev_ctxt); + + if (!*transc) { + result = false; + + vcd_release_frame_channel(dev_ctxt, *transc); + } else { + (*transc)->type = VCD_CMD_CODE_FRAME; + } + + } + + return result; +} + +u32 vcd_get_frame_channel_in_loop( + struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc **transc) +{ + u32 result = false; + + *transc = NULL; + + if (dev_ctxt->ddl_frame_ch_interim > 0) { + if (dev_ctxt->ddl_cmd_concurrency) { + --dev_ctxt->ddl_frame_ch_interim; + result = true; + } else if ((dev_ctxt->ddl_cmd_ch_free + + dev_ctxt->ddl_cmd_ch_interim) + == dev_ctxt->ddl_cmd_ch_depth) { + --dev_ctxt->ddl_frame_ch_interim; + result = true; + } + } else { + result = vcd_get_frame_channel(dev_ctxt, transc); + } + + if (result && !*transc) { + *transc = vcd_get_free_trans_tbl_entry(dev_ctxt); + + if (!*transc) { + result = false; + VCD_MSG_FATAL("\n%s: All transactions are busy;" + "Couldnt find free one\n", __func__); + ++dev_ctxt->ddl_frame_ch_interim; + } else + (*transc)->type = VCD_CMD_CODE_FRAME; + } + + return result; +} + +void vcd_mark_frame_channel(struct vcd_dev_ctxt *dev_ctxt) +{ + ++dev_ctxt->ddl_frame_ch_interim; + + if (dev_ctxt->ddl_frame_ch_interim + + dev_ctxt->ddl_frame_ch_free > + dev_ctxt->ddl_cmd_ch_depth) { + VCD_MSG_FATAL("Frame channel access counters messed up"); + } +} + +void vcd_release_frame_channel(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc *transc) +{ + ++dev_ctxt->ddl_frame_ch_free; + + vcd_release_trans_tbl_entry(transc); + + if (dev_ctxt->ddl_frame_ch_interim + + dev_ctxt->ddl_frame_ch_free > + dev_ctxt->ddl_cmd_ch_depth) { + VCD_MSG_FATAL("Frame channel access counters messed up"); + } +} + +void vcd_release_multiple_frame_channels(struct vcd_dev_ctxt + *dev_ctxt, u32 channels) +{ + dev_ctxt->ddl_frame_ch_free += channels; + + if (dev_ctxt->ddl_frame_ch_interim + + dev_ctxt->ddl_frame_ch_free > + dev_ctxt->ddl_frame_ch_depth) { + VCD_MSG_FATAL("Frame channel access counters messed up"); + } +} + +void vcd_release_interim_frame_channels(struct vcd_dev_ctxt + *dev_ctxt) +{ + dev_ctxt->ddl_frame_ch_free += + dev_ctxt->ddl_frame_ch_interim; + dev_ctxt->ddl_frame_ch_interim = 0; + + if (dev_ctxt->ddl_frame_ch_free > + dev_ctxt->ddl_cmd_ch_depth) { + VCD_MSG_FATAL("Frame channel access counters messed up"); + } +} + +u32 vcd_core_is_busy(struct vcd_dev_ctxt *dev_ctxt) +{ + if (((dev_ctxt->ddl_cmd_ch_free + + dev_ctxt->ddl_cmd_ch_interim) != + dev_ctxt->ddl_cmd_ch_depth) + || + ((dev_ctxt->ddl_frame_ch_free + + dev_ctxt->ddl_frame_ch_interim) != + dev_ctxt->ddl_frame_ch_depth) + ) { + return true; + } else { + return false; + } +} + +void vcd_device_timer_start(struct vcd_dev_ctxt *dev_ctxt) +{ + if (dev_ctxt->config.timer_start) + dev_ctxt->config.timer_start(dev_ctxt->hw_timer_handle, + dev_ctxt->hw_time_out); +} + +void vcd_device_timer_stop(struct vcd_dev_ctxt *dev_ctxt) +{ + if (dev_ctxt->config.timer_stop) + dev_ctxt->config.timer_stop(dev_ctxt->hw_timer_handle); +} + + +u32 vcd_common_allocate_set_buffer( + struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer, + u32 buf_size, struct vcd_buffer_pool **buffer_pool) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_buffer_requirement Buf_req; + struct vcd_property_hdr Prop_hdr; + struct vcd_buffer_pool *buf_pool; + + if (buffer == VCD_BUFFER_INPUT) { + Prop_hdr.prop_id = DDL_I_INPUT_BUF_REQ; + buf_pool = &cctxt->in_buf_pool; + } else if (buffer == VCD_BUFFER_OUTPUT) { + Prop_hdr.prop_id = DDL_I_OUTPUT_BUF_REQ; + buf_pool = &cctxt->out_buf_pool; + } else { + rc = VCD_ERR_ILLEGAL_PARM; + } + VCD_FAILED_RETURN(rc, "Invalid buffer type provided"); + + *buffer_pool = buf_pool; + + if (buf_pool->count > 0 && + buf_pool->validated == buf_pool->count) { + VCD_MSG_ERROR("Buffer pool is full"); + return VCD_ERR_FAIL; + } + + if (!buf_pool->entries) { + Prop_hdr.sz = sizeof(Buf_req); + rc = ddl_get_property(cctxt->ddl_handle, &Prop_hdr, &Buf_req); + if (!VCD_FAILED(rc)) { + rc = vcd_alloc_buffer_pool_entries(buf_pool, + &Buf_req); + } else { + VCD_MSG_ERROR("rc = 0x%x. Failed: ddl_get_property", + rc); + } + } + + if (!VCD_FAILED(rc)) { + if (buf_pool->buf_req.sz > buf_size) { + VCD_MSG_ERROR("\n required buffer sz %u " + "allocated sz %u", buf_pool->buf_req. + sz, buf_size); + + rc = VCD_ERR_ILLEGAL_PARM; + } + } + + return rc; +} + +u32 vcd_set_buffer_internal( + struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_pool *buf_pool, u8 *buffer, u32 buf_size) +{ + struct vcd_buffer_entry *buf_entry; + u8 *physical; + + buf_entry = vcd_find_buffer_pool_entry(buf_pool, buffer); + if (buf_entry) { + VCD_MSG_ERROR("This buffer address already exists"); + + return VCD_ERR_ILLEGAL_OP; + } + + physical = (u8 *) vcd_pmem_get_physical( + cctxt->client_data, (unsigned long)buffer); + + if (!physical) { + VCD_MSG_ERROR("Couldn't get physical address"); + return VCD_ERR_BAD_POINTER; + } + if (((u32) physical % buf_pool->buf_req.align)) { + VCD_MSG_ERROR("Physical addr is not aligned"); + return VCD_ERR_BAD_POINTER; + } + + buf_entry = vcd_get_free_buffer_pool_entry(buf_pool); + if (!buf_entry) { + VCD_MSG_ERROR("Can't allocate buffer pool is full"); + return VCD_ERR_FAIL; + } + + buf_entry->virtual = buffer; + buf_entry->physical = physical; + buf_entry->sz = buf_size; + buf_entry->frame.alloc_len = buf_size; + buf_entry->allocated = false; + + buf_entry->frame.virtual = buf_entry->virtual; + buf_entry->frame.physical = buf_entry->physical; + + buf_pool->validated++; + + return VCD_S_SUCCESS; + +} + +u32 vcd_allocate_buffer_internal( + struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_pool *buf_pool, + u32 buf_size, u8 **vir_buf_addr, u8 **phy_buf_addr) +{ + struct vcd_buffer_entry *buf_entry; + struct vcd_buffer_requirement *buf_req; + u32 addr; + int rc = 0; + + buf_entry = vcd_get_free_buffer_pool_entry(buf_pool); + if (!buf_entry) { + VCD_MSG_ERROR("Can't allocate buffer pool is full"); + + return VCD_ERR_FAIL; + } + + buf_req = &buf_pool->buf_req; + + buf_size += buf_req->align; + + rc = vcd_pmem_alloc(buf_size, &buf_entry->alloc, + &buf_entry->physical); + + if (rc < 0) { + VCD_MSG_ERROR("Buffer allocation failed"); + + return VCD_ERR_ALLOC_FAIL; + } + + buf_entry->sz = buf_size; + buf_entry->frame.alloc_len = buf_size; + + if (!buf_entry->physical) { + VCD_MSG_ERROR("Couldn't get physical address"); + + return VCD_ERR_BAD_POINTER; + } + + buf_entry->allocated = true; + + if (buf_req->align > 0) { + + addr = (u32) buf_entry->physical; + addr += buf_req->align; + addr -= (addr % buf_req->align); + buf_entry->virtual = buf_entry->alloc; + buf_entry->virtual += (u32) (addr - (u32) + buf_entry->physical); + buf_entry->physical = (u8 *) addr; + } else { + VCD_MSG_LOW("No buffer alignment required"); + + buf_entry->virtual = buf_entry->alloc; + + } + + buf_entry->frame.virtual = buf_entry->virtual; + buf_entry->frame.physical = buf_entry->physical; + + *vir_buf_addr = buf_entry->virtual; + *phy_buf_addr = buf_entry->physical; + + buf_pool->allocated++; + buf_pool->validated++; + + return VCD_S_SUCCESS; +} + +u32 vcd_free_one_buffer_internal( + struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer_type, u8 *buffer) +{ + struct vcd_buffer_pool *buf_pool; + u32 rc = VCD_S_SUCCESS; + struct vcd_buffer_entry *buf_entry; + u32 first_frm_recvd = 0; + + if (buffer_type == VCD_BUFFER_INPUT) { + buf_pool = &cctxt->in_buf_pool; + first_frm_recvd = VCD_FIRST_IP_RCVD; + } else if (buffer_type == VCD_BUFFER_OUTPUT) { + buf_pool = &cctxt->out_buf_pool; + first_frm_recvd = VCD_FIRST_OP_RCVD; + } else + rc = VCD_ERR_ILLEGAL_PARM; + + VCD_FAILED_RETURN(rc, "Invalid buffer type provided"); + + first_frm_recvd &= cctxt->status.mask; + if (first_frm_recvd) { + VCD_MSG_ERROR( + "VCD free buffer called when data path is active"); + return VCD_ERR_BAD_STATE; + } + + buf_entry = vcd_find_buffer_pool_entry(buf_pool, buffer); + if (!buf_entry) { + VCD_MSG_ERROR("Buffer addr %p not found. Can't free buffer", + buffer); + + return VCD_ERR_ILLEGAL_PARM; + } + if (buf_entry->in_use) { + VCD_MSG_ERROR("\n Buffer is in use and is not flushed"); + return VCD_ERR_ILLEGAL_OP; + } + + VCD_MSG_LOW("Freeing buffer %p. Allocated %d", + buf_entry->virtual, buf_entry->allocated); + + if (buf_entry->allocated) { + vcd_pmem_free(buf_entry->alloc, buf_entry->physical); + buf_pool->allocated--; + } + + memset(buf_entry, 0, sizeof(struct vcd_buffer_entry)); + buf_pool->validated--; + if (buf_pool->validated == 0) + vcd_free_buffer_pool_entries(buf_pool); + + return VCD_S_SUCCESS; +} + +u32 vcd_free_buffers_internal( + struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_pool *buf_pool) +{ + u32 rc = VCD_S_SUCCESS; + u32 i; + + VCD_MSG_LOW("vcd_free_buffers_internal:"); + + if (buf_pool->entries) { + for (i = 1; i <= buf_pool->count; i++) { + if (buf_pool->entries[i].valid && + buf_pool->entries[i].allocated) { + vcd_pmem_free(buf_pool->entries[i].alloc, + buf_pool->entries[i]. + physical); + } + } + + } + + vcd_reset_buffer_pool_for_reuse(buf_pool); + + return rc; +} + +u32 vcd_alloc_buffer_pool_entries( + struct vcd_buffer_pool *buf_pool, + struct vcd_buffer_requirement *buf_req) +{ + + VCD_MSG_LOW("vcd_alloc_buffer_pool_entries:"); + + buf_pool->buf_req = *buf_req; + + buf_pool->count = buf_req->actual_count; + buf_pool->entries = (struct vcd_buffer_entry *) + kzalloc((sizeof(struct vcd_buffer_entry) * + (VCD_MAX_BUFFER_ENTRIES + 1)), GFP_KERNEL); + + if (!buf_pool->entries) { + VCD_MSG_ERROR("Buf_pool entries alloc failed"); + return VCD_ERR_ALLOC_FAIL; + } + + INIT_LIST_HEAD(&buf_pool->queue); + buf_pool->entries[0].valid = true; + buf_pool->q_len = 0; + + buf_pool->validated = 0; + buf_pool->allocated = 0; + buf_pool->in_use = 0; + + return VCD_S_SUCCESS; +} + +void vcd_free_buffer_pool_entries(struct vcd_buffer_pool *buf_pool) +{ + VCD_MSG_LOW("vcd_free_buffer_pool_entries:"); + kfree(buf_pool->entries); + memset(buf_pool, 0, sizeof(struct vcd_buffer_pool)); + INIT_LIST_HEAD(&buf_pool->queue); +} + +void vcd_flush_in_use_buffer_pool_entries(struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_pool *buf_pool, u32 event) +{ + u32 i; + VCD_MSG_LOW("vcd_flush_buffer_pool_entries: event=0x%x", event); + + if (buf_pool->entries) { + for (i = 0; i <= buf_pool->count; i++) { + if (buf_pool->entries[i].virtual && + buf_pool->entries[i].in_use) { + cctxt->callback(event, VCD_S_SUCCESS, + &buf_pool->entries[i].frame, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + buf_pool->entries[i].in_use = false; + VCD_BUFFERPOOL_INUSE_DECREMENT( + buf_pool->in_use); + } + } + } +} + + +void vcd_reset_buffer_pool_for_reuse(struct vcd_buffer_pool *buf_pool) +{ + VCD_MSG_LOW("vcd_reset_buffer_pool_for_reuse:"); + + if (buf_pool->entries) { + memset(&buf_pool->entries[1], 0, + sizeof(struct vcd_buffer_entry) * + VCD_MAX_BUFFER_ENTRIES); + } + buf_pool->q_len = 0; + + buf_pool->validated = 0; + buf_pool->allocated = 0; + buf_pool->in_use = 0; + INIT_LIST_HEAD(&buf_pool->queue); +} + +struct vcd_buffer_entry *vcd_get_free_buffer_pool_entry + (struct vcd_buffer_pool *pool) { + u32 i; + + i = 1; + while (i <= pool->count && pool->entries[i].valid) + i++; + + + if (i <= pool->count) { + pool->entries[i].valid = true; + + return &pool->entries[i]; + } else { + return NULL; + } +} + +struct vcd_buffer_entry *vcd_find_buffer_pool_entry + (struct vcd_buffer_pool *pool, u8 *addr) +{ + u32 i; + u32 found = false; + + for (i = 0; i <= pool->count && !found; i++) { + if (pool->entries[i].virtual == addr) + found = true; + + } + + if (found) + return &pool->entries[i - 1]; + else + return NULL; + +} + +u32 vcd_buffer_pool_entry_en_q( + struct vcd_buffer_pool *pool, + struct vcd_buffer_entry *entry) +{ + struct vcd_buffer_entry *list_itr; + + if (pool->q_len == pool->count) + return false; + + list_for_each_entry(list_itr, &pool->queue, list) + if (list_itr == entry) { + VCD_MSG_HIGH("\n this output buffer is already present" + " in queue"); + VCD_MSG_HIGH("\n Vir Addr %p Phys Addr %p", + entry->virtual, entry->physical); + return false; + } + + list_add_tail(&entry->list, &pool->queue); + pool->q_len++; + + return true; +} + +struct vcd_buffer_entry *vcd_buffer_pool_entry_de_q + (struct vcd_buffer_pool *pool) { + struct vcd_buffer_entry *entry; + + if (!pool || !pool->q_len) + return NULL; + + entry = list_first_entry(&pool->queue, + struct vcd_buffer_entry, list); + + if (entry) { + list_del(&entry->list); + pool->q_len--; + } + + return entry; +} + +void vcd_flush_bframe_buffers(struct vcd_clnt_ctxt *cctxt, u32 mode) +{ + int i; + struct vcd_buffer_pool *buf_pool; + + if (!cctxt->decoding && cctxt->bframe) { + buf_pool = (mode == VCD_FLUSH_INPUT) ? + &cctxt->in_buf_pool : &cctxt->out_buf_pool; + if (buf_pool->entries != NULL) { + for (i = 1; i <= buf_pool->count; i++) { + if ((buf_pool->entries[i].in_use) && + (buf_pool->entries[i].frame.virtual + != NULL)) { + if (mode == VCD_FLUSH_INPUT) { + cctxt->callback( + VCD_EVT_RESP_INPUT_FLUSHED, + VCD_S_SUCCESS, + &(buf_pool->entries[i].frame), + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + } else { + buf_pool->entries[i]. + frame.data_len = 0; + cctxt->callback( + VCD_EVT_RESP_OUTPUT_FLUSHED, + VCD_S_SUCCESS, + &(buf_pool->entries[i].frame), + sizeof(struct vcd_frame_data), + cctxt, + cctxt->client_data); + } + VCD_BUFFERPOOL_INUSE_DECREMENT( + buf_pool->in_use); + buf_pool->entries[i].in_use = false; + } + } + } + } +} + +void vcd_flush_output_buffers(struct vcd_clnt_ctxt *cctxt) +{ + struct vcd_buffer_pool *buf_pool; + struct vcd_buffer_entry *buf_entry; + u32 count = 0; + struct vcd_property_hdr prop_hdr; + + VCD_MSG_LOW("vcd_flush_output_buffers:"); + buf_pool = &cctxt->out_buf_pool; + buf_entry = vcd_buffer_pool_entry_de_q(buf_pool); + while (buf_entry) { + if (!cctxt->decoding || buf_entry->in_use) { + buf_entry->frame.data_len = 0; + cctxt->callback(VCD_EVT_RESP_OUTPUT_FLUSHED, + VCD_S_SUCCESS, + &buf_entry->frame, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + if (buf_entry->in_use) { + VCD_BUFFERPOOL_INUSE_DECREMENT( + buf_pool->in_use); + buf_entry->in_use = false; + } + count++; + } + buf_entry = vcd_buffer_pool_entry_de_q(buf_pool); + } + vcd_flush_bframe_buffers(cctxt, VCD_FLUSH_OUTPUT); + if (buf_pool->in_use || buf_pool->q_len) { + VCD_MSG_ERROR("%s(): WARNING in_use(%u) or q_len(%u) not zero!", + __func__, buf_pool->in_use, buf_pool->q_len); + buf_pool->in_use = buf_pool->q_len = 0; + } + if (cctxt->sched_clnt_hdl) { + if (count > cctxt->sched_clnt_hdl->tkns) + cctxt->sched_clnt_hdl->tkns = 0; + else + cctxt->sched_clnt_hdl->tkns -= count; + } + + if (cctxt->ddl_hdl_valid && cctxt->decoding) { + prop_hdr.prop_id = DDL_I_REQ_OUTPUT_FLUSH; + prop_hdr.sz = sizeof(u32); + count = 0x1; + + (void)ddl_set_property(cctxt->ddl_handle, &prop_hdr, + &count); + } + vcd_release_all_clnt_frm_transc(cctxt); + cctxt->status.mask &= ~VCD_IN_RECONFIG; +} + +u32 vcd_flush_buffers(struct vcd_clnt_ctxt *cctxt, u32 mode) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_buffer_entry *buf_entry; + + VCD_MSG_LOW("vcd_flush_buffers:"); + + if (mode > VCD_FLUSH_ALL || !(mode & VCD_FLUSH_ALL)) { + VCD_MSG_ERROR("Invalid flush mode %d", mode); + return VCD_ERR_ILLEGAL_PARM; + } + + VCD_MSG_MED("Flush mode %d requested", mode); + if ((mode & VCD_FLUSH_INPUT) && + cctxt->sched_clnt_hdl) { + + rc = vcd_sched_dequeue_buffer( + cctxt->sched_clnt_hdl, &buf_entry); + while (!VCD_FAILED(rc) && buf_entry) { + if (buf_entry->virtual) { + cctxt->callback(VCD_EVT_RESP_INPUT_FLUSHED, + VCD_S_SUCCESS, + &buf_entry->frame, + sizeof(struct + vcd_frame_data), + cctxt, + cctxt->client_data); + } + + buf_entry->in_use = false; + VCD_BUFFERPOOL_INUSE_DECREMENT( + cctxt->in_buf_pool.in_use); + buf_entry = NULL; + rc = vcd_sched_dequeue_buffer( + cctxt->sched_clnt_hdl, &buf_entry); + } + } + if (rc != VCD_ERR_QEMPTY) + VCD_FAILED_RETURN(rc, "Failed: vcd_sched_dequeue_buffer"); + if (cctxt->status.frame_submitted > 0) + cctxt->status.mask |= mode; + else { + if (mode & VCD_FLUSH_INPUT) + vcd_flush_bframe_buffers(cctxt, VCD_FLUSH_INPUT); + if (mode & VCD_FLUSH_OUTPUT) + vcd_flush_output_buffers(cctxt); + } + return VCD_S_SUCCESS; +} + +void vcd_flush_buffers_in_err_fatal(struct vcd_clnt_ctxt *cctxt) +{ + VCD_MSG_LOW("\n vcd_flush_buffers_in_err_fatal:"); + (void) vcd_flush_buffers(cctxt, VCD_FLUSH_ALL); + vcd_flush_in_use_buffer_pool_entries(cctxt, + &cctxt->in_buf_pool, VCD_EVT_RESP_INPUT_FLUSHED); + vcd_flush_in_use_buffer_pool_entries(cctxt, + &cctxt->out_buf_pool, VCD_EVT_RESP_OUTPUT_FLUSHED); + vcd_send_flush_done(cctxt, VCD_S_SUCCESS); +} + +u32 vcd_init_client_context(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc; + VCD_MSG_LOW("vcd_init_client_context:"); + rc = ddl_open(&cctxt->ddl_handle, cctxt->decoding); + VCD_FAILED_RETURN(rc, "Failed: ddl_open"); + cctxt->ddl_hdl_valid = true; + cctxt->clnt_state.state = VCD_CLIENT_STATE_OPEN; + cctxt->clnt_state.state_table = + vcd_get_client_state_table(VCD_CLIENT_STATE_OPEN); + cctxt->signature = VCD_SIGNATURE; + cctxt->live = true; + cctxt->bframe = 0; + cctxt->cmd_q.pending_cmd = VCD_CMD_NONE; + cctxt->status.last_evt = VCD_EVT_RESP_BASE; + return rc; +} + +void vcd_destroy_client_context(struct vcd_clnt_ctxt *cctxt) +{ + struct vcd_dev_ctxt *dev_ctxt; + struct vcd_clnt_ctxt *client; + struct vcd_buffer_entry *buf_entry; + u32 rc = VCD_S_SUCCESS; + VCD_MSG_LOW("vcd_destroy_client_context:"); + + dev_ctxt = cctxt->dev_ctxt; + + if (cctxt == dev_ctxt->cctxt_list_head) { + VCD_MSG_MED("Clnt list head clnt being removed"); + + dev_ctxt->cctxt_list_head = cctxt->next; + } else { + client = dev_ctxt->cctxt_list_head; + while (client && cctxt != client->next) + client = client->next; + if (client) + client->next = cctxt->next; + if (!client) { + rc = VCD_ERR_FAIL; + VCD_MSG_ERROR("Client not found in client list"); + } + } + + if (VCD_FAILED(rc)) + return; + + if (cctxt->sched_clnt_hdl) { + rc = VCD_S_SUCCESS; + while (!VCD_FAILED(rc)) { + rc = vcd_sched_dequeue_buffer( + cctxt->sched_clnt_hdl, &buf_entry); + if (rc != VCD_ERR_QEMPTY && VCD_FAILED(rc)) + VCD_MSG_ERROR("\n Failed: " + "vcd_sched_de_queue_buffer"); + } + rc = vcd_sched_remove_client(cctxt->sched_clnt_hdl); + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("\n Failed: sched_remove_client"); + cctxt->sched_clnt_hdl = NULL; + } + + if (cctxt->seq_hdr.sequence_header) { + vcd_pmem_free(cctxt->seq_hdr.sequence_header, + cctxt->seq_hdr_phy_addr); + cctxt->seq_hdr.sequence_header = NULL; + } + + vcd_free_buffers_internal(cctxt, &cctxt->in_buf_pool); + vcd_free_buffers_internal(cctxt, &cctxt->out_buf_pool); + vcd_free_buffer_pool_entries(&cctxt->in_buf_pool); + vcd_free_buffer_pool_entries(&cctxt->out_buf_pool); + vcd_release_all_clnt_transc(cctxt); + + if (cctxt->ddl_hdl_valid) { + (void)ddl_close(&cctxt->ddl_handle); + cctxt->ddl_hdl_valid = false; + } + + cctxt->signature = 0; + cctxt->clnt_state.state = VCD_CLIENT_STATE_NULL; + cctxt->clnt_state.state_table = NULL; + + kfree(cctxt); +} + +u32 vcd_check_for_client_context( + struct vcd_dev_ctxt *dev_ctxt, s32 driver_id) +{ + struct vcd_clnt_ctxt *client; + + client = dev_ctxt->cctxt_list_head; + while (client && client->driver_id != driver_id) + client = client->next; + + if (!client) + return false; + else + return true; +} + +u32 vcd_validate_driver_handle( + struct vcd_dev_ctxt *dev_ctxt, s32 driver_handle) +{ + driver_handle--; + + if (driver_handle < 0 || + driver_handle >= VCD_DRIVER_INSTANCE_MAX || + !dev_ctxt->driver_ids[driver_handle]) { + return false; + } else { + return true; + } +} + +u32 vcd_client_cmd_en_q( + struct vcd_clnt_ctxt *cctxt, enum vcd_command command) +{ + u32 result; + + if (cctxt->cmd_q.pending_cmd == VCD_CMD_NONE) { + cctxt->cmd_q.pending_cmd = command; + result = true; + } else { + result = false; + } + + return result; +} + +void vcd_client_cmd_flush_and_en_q( + struct vcd_clnt_ctxt *cctxt, enum vcd_command command) +{ + cctxt->cmd_q.pending_cmd = command; +} + +u32 vcd_client_cmd_de_q(struct vcd_clnt_ctxt *cctxt, + enum vcd_command *command) +{ + if (cctxt->cmd_q.pending_cmd == VCD_CMD_NONE) + return false; + + *command = cctxt->cmd_q.pending_cmd; + cctxt->cmd_q.pending_cmd = VCD_CMD_NONE; + + return true; +} + +u32 vcd_get_next_queued_client_cmd(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt **cctxt, enum vcd_command *command) +{ + struct vcd_clnt_ctxt *client = dev_ctxt->cctxt_list_head; + u32 result = false; + + while (client && !result) { + *cctxt = client; + result = vcd_client_cmd_de_q(client, command); + client = client->next; + } + return result; +} + +u32 vcd_submit_cmd_sess_start(struct vcd_transc *transc) +{ + u32 rc; + struct vcd_sequence_hdr Seq_hdr; + + VCD_MSG_LOW("vcd_submit_cmd_sess_start:"); + + if (transc->cctxt->decoding) { + + if (transc->cctxt->seq_hdr.sequence_header) { + Seq_hdr.sequence_header_len = + transc->cctxt->seq_hdr. + sequence_header_len; + Seq_hdr.sequence_header = + transc->cctxt->seq_hdr_phy_addr; + + rc = ddl_decode_start(transc->cctxt->ddl_handle, + &Seq_hdr, (void *)transc); + } else { + rc = ddl_decode_start(transc->cctxt->ddl_handle, + NULL, (void *)transc); + } + + } else { + rc = ddl_encode_start(transc->cctxt->ddl_handle, + (void *)transc); + } + if (!VCD_FAILED(rc)) { + transc->cctxt->status.cmd_submitted++; + vcd_device_timer_start(transc->cctxt->dev_ctxt); + } else + VCD_MSG_ERROR("rc = 0x%x. Failed: ddl start", rc); + + return rc; +} + +u32 vcd_submit_cmd_sess_end(struct vcd_transc *transc) +{ + u32 rc; + + VCD_MSG_LOW("vcd_submit_cmd_sess_end:"); + + if (transc->cctxt->decoding) { + rc = ddl_decode_end(transc->cctxt->ddl_handle, + (void *)transc); + } else { + rc = ddl_encode_end(transc->cctxt->ddl_handle, + (void *)transc); + } + if (!VCD_FAILED(rc)) { + transc->cctxt->status.cmd_submitted++; + vcd_device_timer_start(transc->cctxt->dev_ctxt); + } else + VCD_MSG_ERROR("rc = 0x%x. Failed: ddl end", rc); + + return rc; +} + +void vcd_submit_cmd_client_close(struct vcd_clnt_ctxt *cctxt) +{ + (void) ddl_close(&cctxt->ddl_handle); + cctxt->ddl_hdl_valid = false; + cctxt->status.mask &= ~VCD_CLEANING_UP; + if (cctxt->status.mask & VCD_CLOSE_PENDING) { + vcd_destroy_client_context(cctxt); + vcd_handle_for_last_clnt_close(cctxt->dev_ctxt, true); + } +} + +u32 vcd_submit_command_in_continue(struct vcd_dev_ctxt + *dev_ctxt, struct vcd_transc *transc) +{ + struct vcd_property_hdr prop_hdr; + struct vcd_clnt_ctxt *client = NULL; + enum vcd_command cmd = VCD_CMD_NONE; + u32 rc = VCD_ERR_FAIL; + u32 result = false, flush = 0, event = 0; + u32 command_break = false; + + VCD_MSG_LOW("\n vcd_submit_command_in_continue:"); + + while (!command_break) { + result = vcd_get_next_queued_client_cmd(dev_ctxt, + &client, &cmd); + + if (!result) + command_break = true; + else { + transc->type = cmd; + transc->cctxt = client; + + switch (cmd) { + case VCD_CMD_CODEC_START: + { + rc = vcd_submit_cmd_sess_start(transc); + event = VCD_EVT_RESP_START; + break; + } + case VCD_CMD_CODEC_STOP: + { + rc = vcd_submit_cmd_sess_end(transc); + event = VCD_EVT_RESP_STOP; + break; + } + case VCD_CMD_OUTPUT_FLUSH: + { + prop_hdr.prop_id = DDL_I_REQ_OUTPUT_FLUSH; + prop_hdr.sz = sizeof(u32); + flush = 0x1; + (void) ddl_set_property(client->ddl_handle, + &prop_hdr, &flush); + vcd_release_command_channel(dev_ctxt, + transc); + rc = VCD_S_SUCCESS; + break; + } + case VCD_CMD_CLIENT_CLOSE: + { + vcd_submit_cmd_client_close(client); + vcd_release_command_channel(dev_ctxt, + transc); + rc = VCD_S_SUCCESS; + break; + } + default: + { + VCD_MSG_ERROR("\n vcd_submit_command: Unknown" + "command %d", (int)cmd); + break; + } + } + + if (!VCD_FAILED(rc)) { + command_break = true; + } else { + VCD_MSG_ERROR("vcd_submit_command %d: failed 0x%x", + cmd, rc); + client->callback(event, rc, NULL, 0, client, + client->client_data); + } + } + } + return result; +} + +u32 vcd_schedule_frame(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_clnt_ctxt **cctxt, struct vcd_buffer_entry + **ip_buf_entry) +{ + u32 rc = VCD_S_SUCCESS; + VCD_MSG_LOW("vcd_schedule_frame:"); + + if (!dev_ctxt->cctxt_list_head) { + VCD_MSG_HIGH("Client list empty"); + return false; + } + rc = vcd_sched_get_client_frame(&dev_ctxt->sched_clnt_list, + cctxt, ip_buf_entry); + if (rc == VCD_ERR_QEMPTY) { + VCD_MSG_HIGH("No frame available. Sched queues are empty"); + return false; + } + if (VCD_FAILED(rc)) { + VCD_MSG_FATAL("vcd_submit_frame: sched_de_queue_frame" + "failed 0x%x", rc); + return false; + } + if (!*cctxt || !*ip_buf_entry) { + VCD_MSG_FATAL("Sched returned invalid values. ctxt=%p," + "ipbuf=%p", *cctxt, *ip_buf_entry); + return false; + } + return true; +} + +void vcd_try_submit_frame(struct vcd_dev_ctxt *dev_ctxt) +{ + struct vcd_transc *transc; + u32 rc = VCD_S_SUCCESS; + struct vcd_clnt_ctxt *cctxt = NULL; + struct vcd_buffer_entry *ip_buf_entry = NULL; + u32 result = false; + + VCD_MSG_LOW("vcd_try_submit_frame:"); + + if (!vcd_get_frame_channel(dev_ctxt, &transc)) + return; + + if (!vcd_schedule_frame(dev_ctxt, &cctxt, &ip_buf_entry)) { + vcd_release_frame_channel(dev_ctxt, transc); + return; + } + + rc = vcd_power_event(dev_ctxt, cctxt, VCD_EVT_PWR_CLNT_CMD_BEGIN); + + if (!VCD_FAILED(rc)) { + transc->cctxt = cctxt; + transc->ip_buf_entry = ip_buf_entry; + + result = vcd_submit_frame(dev_ctxt, transc); + } else { + VCD_MSG_ERROR("Failed: VCD_EVT_PWR_CLNT_CMD_BEGIN"); + (void) vcd_sched_queue_buffer( + cctxt->sched_clnt_hdl, ip_buf_entry, false); + cctxt->sched_clnt_hdl->tkns++; + } + + if (!result) { + vcd_release_frame_channel(dev_ctxt, transc); + (void) vcd_power_event(dev_ctxt, cctxt, + VCD_EVT_PWR_CLNT_CMD_FAIL); + } +} + +u32 vcd_submit_frame(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc *transc) +{ + struct vcd_clnt_ctxt *cctxt = NULL; + struct vcd_frame_data *ip_frm_entry; + struct vcd_buffer_entry *op_buf_entry = NULL; + u32 rc = VCD_S_SUCCESS; + u32 evcode = 0; + struct ddl_frame_data_tag ddl_ip_frm; + struct ddl_frame_data_tag ddl_op_frm; + + VCD_MSG_LOW("vcd_submit_frame:"); + cctxt = transc->cctxt; + ip_frm_entry = &transc->ip_buf_entry->frame; + + transc->op_buf_entry = op_buf_entry; + transc->ip_frm_tag = ip_frm_entry->ip_frm_tag; + transc->time_stamp = ip_frm_entry->time_stamp; + ip_frm_entry->ip_frm_tag = (u32) transc; + memset(&ddl_ip_frm, 0, sizeof(ddl_ip_frm)); + memset(&ddl_op_frm, 0, sizeof(ddl_op_frm)); + if (cctxt->decoding) { + evcode = CLIENT_STATE_EVENT_NUMBER(decode_frame); + ddl_ip_frm.vcd_frm = *ip_frm_entry; + rc = ddl_decode_frame(cctxt->ddl_handle, &ddl_ip_frm, + (void *) transc); + } else { + op_buf_entry = vcd_buffer_pool_entry_de_q( + &cctxt->out_buf_pool); + if (!op_buf_entry) { + VCD_MSG_ERROR("Sched provided frame when no" + "op buffer was present"); + rc = VCD_ERR_FAIL; + } else { + op_buf_entry->in_use = true; + cctxt->out_buf_pool.in_use++; + ddl_ip_frm.vcd_frm = *ip_frm_entry; + ddl_ip_frm.frm_delta = + vcd_calculate_frame_delta(cctxt, + ip_frm_entry); + + ddl_op_frm.vcd_frm = op_buf_entry->frame; + + evcode = CLIENT_STATE_EVENT_NUMBER(encode_frame); + + rc = ddl_encode_frame(cctxt->ddl_handle, + &ddl_ip_frm, &ddl_op_frm, (void *) transc); + } + } + ip_frm_entry->ip_frm_tag = transc->ip_frm_tag; + if (!VCD_FAILED(rc)) { + vcd_device_timer_start(dev_ctxt); + cctxt->status.frame_submitted++; + if (ip_frm_entry->flags & VCD_FRAME_FLAG_EOS) + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_EOS, evcode); + } else { + VCD_MSG_ERROR("Frame submission failed. rc = 0x%x", rc); + vcd_handle_submit_frame_failed(dev_ctxt, transc); + } + return true; +} + +u32 vcd_try_submit_frame_in_continue(struct vcd_dev_ctxt *dev_ctxt, + struct vcd_transc *transc) +{ + struct vcd_clnt_ctxt *cctxt = NULL; + struct vcd_buffer_entry *ip_buf_entry = NULL; + + VCD_MSG_LOW("vcd_try_submit_frame_in_continue:"); + + if (!vcd_schedule_frame(dev_ctxt, &cctxt, &ip_buf_entry)) + return false; + + transc->cctxt = cctxt; + transc->ip_buf_entry = ip_buf_entry; + + return vcd_submit_frame(dev_ctxt, transc); +} + +u32 vcd_process_cmd_sess_start(struct vcd_clnt_ctxt *cctxt) +{ + struct vcd_transc *transc; + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_process_cmd_sess_start:"); + if (vcd_get_command_channel(cctxt->dev_ctxt, &transc)) { + rc = vcd_power_event(cctxt->dev_ctxt, + cctxt, VCD_EVT_PWR_CLNT_CMD_BEGIN); + + if (!VCD_FAILED(rc)) { + transc->type = VCD_CMD_CODEC_START; + transc->cctxt = cctxt; + rc = vcd_submit_cmd_sess_start(transc); + } else { + VCD_MSG_ERROR("Failed: VCD_EVT_PWR_CLNT_CMD_BEGIN"); + } + + if (VCD_FAILED(rc)) { + vcd_release_command_channel(cctxt->dev_ctxt, + transc); + } + } else { + u32 result; + + result = vcd_client_cmd_en_q(cctxt, VCD_CMD_CODEC_START); + if (!result) { + rc = VCD_ERR_BUSY; + VCD_MSG_ERROR("%s(): vcd_client_cmd_en_q() " + "failed\n", __func__); + } + } + + if (VCD_FAILED(rc)) { + (void)vcd_power_event(cctxt->dev_ctxt, + cctxt, VCD_EVT_PWR_CLNT_CMD_FAIL); + } + + return rc; +} + +void vcd_send_frame_done_in_eos(struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame, u32 valid_opbuf) +{ + VCD_MSG_LOW("vcd_send_frame_done_in_eos:"); + + if (!input_frame->virtual && !valid_opbuf) { + VCD_MSG_MED("Sending NULL output with EOS"); + + cctxt->out_buf_pool.entries[0].frame.flags = + VCD_FRAME_FLAG_EOS; + cctxt->out_buf_pool.entries[0].frame.data_len = 0; + cctxt->out_buf_pool.entries[0].frame.time_stamp = + input_frame->time_stamp; + cctxt->out_buf_pool.entries[0].frame.ip_frm_tag = + input_frame->ip_frm_tag; + + cctxt->callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_S_SUCCESS, + &cctxt->out_buf_pool.entries[0].frame, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + + memset(&cctxt->out_buf_pool.entries[0].frame, + 0, sizeof(struct vcd_frame_data)); + } else if (!input_frame->data_len) { + if (cctxt->decoding) { + vcd_send_frame_done_in_eos_for_dec(cctxt, + input_frame); + } else { + vcd_send_frame_done_in_eos_for_enc(cctxt, + input_frame); + } + + } +} + +void vcd_send_frame_done_in_eos_for_dec( + struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame) +{ + struct vcd_buffer_entry *buf_entry; + struct vcd_property_hdr prop_hdr; + u32 rc; + struct ddl_frame_data_tag ddl_frm; + + prop_hdr.prop_id = DDL_I_DPB_RETRIEVE; + prop_hdr.sz = sizeof(struct ddl_frame_data_tag); + memset(&ddl_frm, 0, sizeof(ddl_frm)); + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, &ddl_frm); + + if (VCD_FAILED(rc) || !ddl_frm.vcd_frm.virtual) { + cctxt->status.eos_trig_ip_frm = *input_frame; + cctxt->status.mask |= VCD_EOS_WAIT_OP_BUF; + return; + } + + buf_entry = vcd_find_buffer_pool_entry(&cctxt->out_buf_pool, + ddl_frm.vcd_frm.virtual); + if (!buf_entry) { + VCD_MSG_ERROR("Unrecognized buffer address provided = %p", + ddl_frm.vcd_frm.virtual); + return; + } else { + if (cctxt->sched_clnt_hdl->tkns) + cctxt->sched_clnt_hdl->tkns--; + + VCD_MSG_MED("Sending non-NULL output with EOS"); + + buf_entry->frame.data_len = 0; + buf_entry->frame.offset = 0; + buf_entry->frame.flags |= VCD_FRAME_FLAG_EOS; + buf_entry->frame.ip_frm_tag = input_frame->ip_frm_tag; + buf_entry->frame.time_stamp = input_frame->time_stamp; + + cctxt->callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_S_SUCCESS, + &buf_entry->frame, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + + buf_entry->in_use = false; + VCD_BUFFERPOOL_INUSE_DECREMENT(cctxt->out_buf_pool.in_use); + } +} + +void vcd_send_frame_done_in_eos_for_enc( + struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame) +{ + struct vcd_buffer_entry *op_buf_entry; + + if (!cctxt->out_buf_pool.q_len) { + cctxt->status.eos_trig_ip_frm = *input_frame; + + cctxt->status.mask |= VCD_EOS_WAIT_OP_BUF; + + return; + } + + op_buf_entry = vcd_buffer_pool_entry_de_q(&cctxt->out_buf_pool); + if (!op_buf_entry) { + VCD_MSG_ERROR("%s(): vcd_buffer_pool_entry_de_q() " + "failed\n", __func__); + } else { + if (cctxt->sched_clnt_hdl->tkns) + cctxt->sched_clnt_hdl->tkns--; + + VCD_MSG_MED("Sending non-NULL output with EOS"); + + op_buf_entry->frame.data_len = 0; + op_buf_entry->frame.flags |= VCD_FRAME_FLAG_EOS; + op_buf_entry->frame.ip_frm_tag = + input_frame->ip_frm_tag; + op_buf_entry->frame.time_stamp = input_frame->time_stamp; + + cctxt->callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_S_SUCCESS, + &op_buf_entry->frame, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + } +} + +u32 vcd_handle_recvd_eos( + struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame, u32 *pb_eos_handled) +{ + u32 rc; + + VCD_MSG_LOW("vcd_handle_recvd_eos:"); + + *pb_eos_handled = false; + + if (input_frame->virtual && + input_frame->data_len) + return VCD_S_SUCCESS; + + input_frame->data_len = 0; + rc = vcd_sched_mark_client_eof(cctxt->sched_clnt_hdl); + if (VCD_FAILED(rc) && rc != VCD_ERR_QEMPTY) + return rc; + + if (rc == VCD_S_SUCCESS) + *pb_eos_handled = true; + else if (cctxt->decoding && !input_frame->virtual) + cctxt->sched_clnt_hdl->tkns++; + else if (!cctxt->decoding) { + vcd_send_frame_done_in_eos(cctxt, input_frame, false); + if (cctxt->status.mask & VCD_EOS_WAIT_OP_BUF) { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_EOS, + CLIENT_STATE_EVENT_NUMBER + (encode_frame)); + } + *pb_eos_handled = true; + } + + if (*pb_eos_handled && + input_frame->virtual && + !input_frame->data_len) { + cctxt->callback(VCD_EVT_RESP_INPUT_DONE, + VCD_S_SUCCESS, + input_frame, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + } + return VCD_S_SUCCESS; +} + +u32 vcd_handle_first_decode_frame(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_ERR_BAD_STATE; + + VCD_MSG_LOW("vcd_handle_first_decode_frame:"); + if (!cctxt->in_buf_pool.entries || + !cctxt->out_buf_pool.entries || + cctxt->in_buf_pool.validated != + cctxt->in_buf_pool.count || + cctxt->out_buf_pool.validated != + cctxt->out_buf_pool.count) + VCD_MSG_ERROR("Buffer pool is not completely setup yet"); + else if (!cctxt->sched_clnt_hdl) { + rc = vcd_sched_add_client(cctxt); + VCD_FAILED_RETURN(rc, "Failed: vcd_add_client_to_sched"); + cctxt->sched_clnt_hdl->tkns = + cctxt->out_buf_pool.q_len; + } else + rc = vcd_sched_suspend_resume_clnt(cctxt, true); + return rc; +} + +u32 vcd_setup_with_ddl_capabilities(struct vcd_dev_ctxt *dev_ctxt) +{ + struct vcd_property_hdr Prop_hdr; + struct ddl_property_capability capability; + u32 rc = VCD_S_SUCCESS; + + VCD_MSG_LOW("vcd_setup_with_ddl_capabilities:"); + + if (!dev_ctxt->ddl_cmd_ch_depth) { + Prop_hdr.prop_id = DDL_I_CAPABILITY; + Prop_hdr.sz = sizeof(capability); + + /* + ** Since this is underlying core's property we don't need a + ** ddl client handle. + */ + rc = ddl_get_property(NULL, &Prop_hdr, &capability); + + if (!VCD_FAILED(rc)) { + /* + ** Allocate the transaction table. + */ + dev_ctxt->trans_tbl_size = + (VCD_MAX_CLIENT_TRANSACTIONS * + capability.max_num_client) + + capability.general_command_depth; + + dev_ctxt->trans_tbl = (struct vcd_transc *) + kzalloc((sizeof(struct vcd_transc) * + dev_ctxt->trans_tbl_size), GFP_KERNEL); + + if (!dev_ctxt->trans_tbl) { + VCD_MSG_ERROR("Transaction table alloc failed"); + rc = VCD_ERR_ALLOC_FAIL; + } else { + dev_ctxt->ddl_cmd_concurrency = + !capability.exclusive; + dev_ctxt->ddl_frame_ch_depth = + capability.frame_command_depth; + dev_ctxt->ddl_cmd_ch_depth = + capability.general_command_depth; + + vcd_reset_device_channels(dev_ctxt); + + dev_ctxt->hw_time_out = + capability.ddl_time_out_in_ms; + + } + } + } + return rc; +} + +struct vcd_transc *vcd_get_free_trans_tbl_entry + (struct vcd_dev_ctxt *dev_ctxt) { + u32 i; + + if (!dev_ctxt->trans_tbl) + return NULL; + + i = 0; + while (i < dev_ctxt->trans_tbl_size && + dev_ctxt->trans_tbl[i].in_use) + i++; + + if (i == dev_ctxt->trans_tbl_size) { + return NULL; + } else { + memset(&dev_ctxt->trans_tbl[i], 0, + sizeof(struct vcd_transc)); + + dev_ctxt->trans_tbl[i].in_use = true; + + return &dev_ctxt->trans_tbl[i]; + } +} + +void vcd_release_trans_tbl_entry(struct vcd_transc *trans_entry) +{ + if (trans_entry) + trans_entry->in_use = false; +} + +u32 vcd_handle_input_done( + struct vcd_clnt_ctxt *cctxt, + void *payload, u32 event, u32 status) +{ + struct vcd_transc *transc; + struct ddl_frame_data_tag *frame = + (struct ddl_frame_data_tag *) payload; + u32 rc; + + if (!cctxt->status.frame_submitted && + !cctxt->status.frame_delayed) { + VCD_MSG_ERROR("Input done was not expected"); + return VCD_ERR_BAD_STATE; + } + + rc = vcd_validate_io_done_pyld(cctxt, payload, status); + if (rc == VCD_ERR_CLIENT_FATAL) + vcd_handle_clnt_fatal_input_done(cctxt, frame->frm_trans_end); + VCD_FAILED_RETURN(rc, "Bad input done payload"); + + transc = (struct vcd_transc *)frame->vcd_frm.ip_frm_tag; + + if ((transc->ip_buf_entry->frame.virtual != + frame->vcd_frm.virtual) + || !transc->ip_buf_entry->in_use) { + VCD_MSG_ERROR("Bad frm transaction state"); + vcd_handle_clnt_fatal_input_done(cctxt, frame->frm_trans_end); + return VCD_ERR_BAD_POINTER; + } + + frame->vcd_frm.ip_frm_tag = transc->ip_frm_tag; + transc->frame = frame->vcd_frm.frame; + + cctxt->callback(event, + status, + &frame->vcd_frm, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + + transc->ip_buf_entry->in_use = false; + VCD_BUFFERPOOL_INUSE_DECREMENT(cctxt->in_buf_pool.in_use); + transc->ip_buf_entry = NULL; + transc->input_done = true; + + if (transc->input_done && transc->frame_done) + vcd_release_trans_tbl_entry(transc); + + if (VCD_FAILED(status)) { + VCD_MSG_ERROR("INPUT_DONE returned err = 0x%x", status); + vcd_handle_input_done_failed(cctxt, transc); + } else + cctxt->status.mask |= VCD_FIRST_IP_DONE; + + if (cctxt->status.frame_submitted > 0) + cctxt->status.frame_submitted--; + else + cctxt->status.frame_delayed--; + + if (!VCD_FAILED(status) && + cctxt->decoding) { + if (frame->vcd_frm.flags & VCD_FRAME_FLAG_CODECCONFIG) { + VCD_MSG_HIGH( + "INPUT_DONE with VCD_FRAME_FLAG_CODECCONFIG"); + vcd_handle_input_done_with_codec_config(cctxt, + transc, frame); + frame->vcd_frm.flags &= ~VCD_FRAME_FLAG_CODECCONFIG; + } + if (frame->vcd_frm.interlaced) + vcd_handle_input_done_for_interlacing(cctxt); + if (frame->frm_trans_end) + vcd_handle_input_done_with_trans_end(cctxt); + } + + return VCD_S_SUCCESS; +} + +u32 vcd_handle_input_done_in_eos( + struct vcd_clnt_ctxt *cctxt, void *payload, u32 status) +{ + struct vcd_transc *transc; + struct ddl_frame_data_tag *frame = + (struct ddl_frame_data_tag *) payload; + u32 rc = VCD_ERR_FAIL, codec_config = false; + u32 core_type = res_trk_get_core_type(); + rc = vcd_validate_io_done_pyld(cctxt, payload, status); + if (rc == VCD_ERR_CLIENT_FATAL) + vcd_handle_clnt_fatal_input_done(cctxt, frame->frm_trans_end); + VCD_FAILED_RETURN(rc, "Failed: vcd_validate_io_done_pyld"); + transc = (struct vcd_transc *)frame->vcd_frm.ip_frm_tag; + codec_config = frame->vcd_frm.flags & VCD_FRAME_FLAG_CODECCONFIG; + rc = vcd_handle_input_done(cctxt, + payload, VCD_EVT_RESP_INPUT_DONE, status); + VCD_FAILED_RETURN(rc, "Failed: vcd_handle_input_done"); + if (frame->vcd_frm.flags & VCD_FRAME_FLAG_EOS) { + VCD_MSG_HIGH("Got input done for EOS initiator"); + transc->input_done = false; + transc->in_use = true; + if (codec_config || + ((status == VCD_ERR_BITSTREAM_ERR) && + !(cctxt->status.mask & VCD_FIRST_IP_DONE) && + (core_type == VCD_CORE_720P))) + vcd_handle_eos_done(cctxt, transc, VCD_S_SUCCESS); + } + return rc; +} + +u32 vcd_validate_io_done_pyld( + struct vcd_clnt_ctxt *cctxt, void *payload, u32 status) +{ + struct ddl_frame_data_tag *frame = + (struct ddl_frame_data_tag *) payload; + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + struct vcd_transc *transc = NULL; + u32 rc = VCD_S_SUCCESS, i = 0; + + if (!frame) { + VCD_MSG_ERROR("Bad payload from DDL"); + return VCD_ERR_BAD_POINTER; + } + + transc = (struct vcd_transc *)frame->vcd_frm.ip_frm_tag; + if (dev_ctxt->trans_tbl) { + while (i < dev_ctxt->trans_tbl_size && + transc != &dev_ctxt->trans_tbl[i]) + i++; + if (i == dev_ctxt->trans_tbl_size || + !dev_ctxt->trans_tbl[i].in_use) + rc = VCD_ERR_CLIENT_FATAL; + } else + rc = VCD_ERR_CLIENT_FATAL; + + if (VCD_FAILED(rc)) { + VCD_MSG_FATAL( + "vcd_validate_io_done_pyld: invalid transaction"); + } else if (!frame->vcd_frm.virtual && + status != VCD_ERR_INTRLCD_FIELD_DROP) + rc = VCD_ERR_BAD_POINTER; + + return rc; +} + +void vcd_handle_input_done_failed( + struct vcd_clnt_ctxt *cctxt, struct vcd_transc *transc) +{ + if (cctxt->decoding) { + cctxt->sched_clnt_hdl->tkns++; + vcd_release_trans_tbl_entry(transc); + } +} + +void vcd_handle_input_done_with_codec_config( + struct vcd_clnt_ctxt *cctxt, struct vcd_transc *transc, + struct ddl_frame_data_tag *frm) +{ + cctxt->sched_clnt_hdl->tkns++; + if (frm->frm_trans_end) + vcd_release_trans_tbl_entry(transc); +} + +void vcd_handle_input_done_for_interlacing(struct vcd_clnt_ctxt *cctxt) +{ + cctxt->status.int_field_cnt++; + if (cctxt->status.int_field_cnt == 1) + cctxt->sched_clnt_hdl->tkns++; + else if (cctxt->status.int_field_cnt == + VCD_DEC_NUM_INTERLACED_FIELDS) + cctxt->status.int_field_cnt = 0; +} + +void vcd_handle_input_done_with_trans_end( + struct vcd_clnt_ctxt *cctxt) +{ + if (!cctxt->decoding) + return; + if (cctxt->out_buf_pool.in_use < + cctxt->out_buf_pool.buf_req.min_count) + return; + if (!cctxt->sched_clnt_hdl->tkns) + cctxt->sched_clnt_hdl->tkns++; +} + +u32 vcd_handle_output_required(struct vcd_clnt_ctxt + *cctxt, void *payload, u32 status) +{ + struct vcd_transc *transc; + struct ddl_frame_data_tag *frame = + (struct ddl_frame_data_tag *)payload; + u32 rc = VCD_S_SUCCESS; + + if (!cctxt->status.frame_submitted && + !cctxt->status.frame_delayed) { + VCD_MSG_ERROR("\n Input done was not expected"); + return VCD_ERR_BAD_STATE; + } + + rc = vcd_validate_io_done_pyld(cctxt, payload, status); + if (rc == VCD_ERR_CLIENT_FATAL) + vcd_handle_clnt_fatal_input_done(cctxt, frame->frm_trans_end); + VCD_FAILED_RETURN(rc, "\n Bad input done payload"); + + transc = (struct vcd_transc *)frame-> + vcd_frm.ip_frm_tag; + + if ((transc->ip_buf_entry->frame.virtual != + frame->vcd_frm.virtual) || + !transc->ip_buf_entry->in_use) { + VCD_MSG_ERROR("\n Bad frm transaction state"); + vcd_handle_clnt_fatal_input_done(cctxt, frame->frm_trans_end); + return VCD_ERR_BAD_STATE; + } + rc = vcd_sched_queue_buffer(cctxt->sched_clnt_hdl, + transc->ip_buf_entry, false); + VCD_FAILED_RETURN(rc, "Failed: vcd_sched_queue_buffer"); + + transc->ip_buf_entry = NULL; + vcd_release_trans_tbl_entry(transc); + frame->frm_trans_end = true; + + if (VCD_FAILED(status)) + VCD_MSG_ERROR("\n OUTPUT_REQ returned err = 0x%x", + status); + + if (cctxt->status.frame_submitted > 0) + cctxt->status.frame_submitted--; + else + cctxt->status.frame_delayed--; + + + if (!VCD_FAILED(status) && + cctxt->decoding && + frame->vcd_frm.interlaced) { + if (cctxt->status.int_field_cnt > 0) { + VCD_MSG_ERROR("\n Not expected: OUTPUT_REQ" + "for 2nd interlace field"); + rc = VCD_ERR_FAIL; + } + } + + return rc; +} + +u32 vcd_handle_output_required_in_flushing( +struct vcd_clnt_ctxt *cctxt, void *payload) +{ + u32 rc; + struct vcd_transc *transc; + struct ddl_frame_data_tag *frame = + (struct ddl_frame_data_tag *)payload; + + rc = vcd_validate_io_done_pyld(cctxt, payload, VCD_S_SUCCESS); + if (rc == VCD_ERR_CLIENT_FATAL) + vcd_handle_clnt_fatal_input_done(cctxt, frame->frm_trans_end); + VCD_FAILED_RETURN(rc, "Bad input done payload"); + + transc = (struct vcd_transc *) + (((struct ddl_frame_data_tag *)payload)-> + vcd_frm.ip_frm_tag); + + ((struct ddl_frame_data_tag *)payload)-> + vcd_frm.interlaced = false; + + rc = vcd_handle_input_done(cctxt, payload, + VCD_EVT_RESP_INPUT_FLUSHED, VCD_S_SUCCESS); + VCD_FAILED_RETURN(rc, "Failed: vcd_handle_input_done"); + + vcd_release_trans_tbl_entry(transc); + ((struct ddl_frame_data_tag *)payload)->frm_trans_end = true; + + return rc; +} + +u32 vcd_handle_frame_done( + struct vcd_clnt_ctxt *cctxt, + void *payload, u32 event, u32 status) +{ + struct vcd_buffer_entry *op_buf_entry = NULL; + struct ddl_frame_data_tag *op_frm = + (struct ddl_frame_data_tag *) payload; + struct vcd_transc *transc; + u32 rc; + + rc = vcd_validate_io_done_pyld(cctxt, payload, status); + if (rc == VCD_ERR_CLIENT_FATAL) + vcd_handle_clnt_fatal(cctxt, op_frm->frm_trans_end); + VCD_FAILED_RETURN(rc, "Bad payload recvd"); + + transc = (struct vcd_transc *)op_frm->vcd_frm.ip_frm_tag; + + if (op_frm->vcd_frm.virtual) { + + if (!transc->op_buf_entry) { + op_buf_entry = + vcd_find_buffer_pool_entry( + &cctxt->out_buf_pool, + op_frm->vcd_frm. + virtual); + } else { + op_buf_entry = transc->op_buf_entry; + } + + if (!op_buf_entry) { + VCD_MSG_ERROR("Invalid output buffer returned" + "from DDL"); + vcd_handle_clnt_fatal(cctxt, op_frm->frm_trans_end); + rc = VCD_ERR_BAD_POINTER; + } else if (!op_buf_entry->in_use) { + VCD_MSG_ERROR("Bad output buffer 0x%p recvd from DDL", + op_buf_entry->frame.virtual); + vcd_handle_clnt_fatal(cctxt, op_frm->frm_trans_end); + rc = VCD_ERR_BAD_POINTER; + } else { + op_buf_entry->in_use = false; + VCD_BUFFERPOOL_INUSE_DECREMENT( + cctxt->out_buf_pool.in_use); + VCD_MSG_LOW("outBufPool.InUse = %d", + cctxt->out_buf_pool.in_use); + } + } + VCD_FAILED_RETURN(rc, "Bad output buffer pointer"); + op_frm->vcd_frm.time_stamp = transc->time_stamp; + op_frm->vcd_frm.ip_frm_tag = transc->ip_frm_tag; + if (cctxt->decoding) + op_frm->vcd_frm.frame = transc->frame; + else + transc->frame = op_frm->vcd_frm.frame; + transc->frame_done = true; + + if (transc->input_done && transc->frame_done) + vcd_release_trans_tbl_entry(transc); + + if (status == VCD_ERR_INTRLCD_FIELD_DROP || + (op_frm->vcd_frm.intrlcd_ip_frm_tag != + VCD_FRAMETAG_INVALID && + op_frm->vcd_frm.intrlcd_ip_frm_tag)) { + vcd_handle_frame_done_for_interlacing(cctxt, transc, + op_frm, status); + } + + if (status != VCD_ERR_INTRLCD_FIELD_DROP) { + cctxt->callback(event, + status, + &op_frm->vcd_frm, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + } + return rc; +} + +u32 vcd_handle_frame_done_in_eos( + struct vcd_clnt_ctxt *cctxt, void *payload, u32 status) +{ + struct ddl_frame_data_tag *frame = + (struct ddl_frame_data_tag *) payload; + u32 rc = VCD_S_SUCCESS; + VCD_MSG_LOW("vcd_handle_frame_done_in_eos:"); + rc = vcd_validate_io_done_pyld(cctxt, payload, status); + if (rc == VCD_ERR_CLIENT_FATAL) + vcd_handle_clnt_fatal(cctxt, frame->frm_trans_end); + VCD_FAILED_RETURN(rc, "Bad payload received"); + + if (cctxt->status.mask & VCD_EOS_PREV_VALID) { + rc = vcd_handle_frame_done(cctxt, + (void *)&cctxt->status. + eos_prev_op_frm, + VCD_EVT_RESP_OUTPUT_DONE, + cctxt->status.eos_prev_op_frm_status); + VCD_FAILED_RETURN(rc, "Failed: vcd_handle_frame_done"); + } + + cctxt->status.eos_prev_op_frm = *frame; + cctxt->status.eos_prev_op_frm_status = status; + cctxt->status.mask |= VCD_EOS_PREV_VALID; + return rc; +} + +void vcd_handle_frame_done_for_interlacing( + struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc_ip1, + struct ddl_frame_data_tag *op_frm, u32 status) +{ + struct vcd_transc *transc_ip2 = + (struct vcd_transc *)op_frm->\ + vcd_frm.intrlcd_ip_frm_tag; + + if (status == VCD_ERR_INTRLCD_FIELD_DROP) { + cctxt->status.int_field_cnt = 0; + return; + } + + op_frm->vcd_frm.intrlcd_ip_frm_tag = transc_ip2->ip_frm_tag; + + transc_ip2->frame_done = true; + + if (transc_ip2->input_done && transc_ip2->frame_done) + vcd_release_trans_tbl_entry(transc_ip2); + + if (!transc_ip1->frame || !transc_ip2->frame) { + VCD_MSG_ERROR("DDL didn't provided frame type"); + return; + } +} + +u32 vcd_handle_first_fill_output_buffer( + struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *buffer, + u32 *handled) +{ + u32 rc = VCD_S_SUCCESS; + rc = vcd_check_if_buffer_req_met(cctxt, VCD_BUFFER_OUTPUT); + VCD_FAILED_RETURN(rc, "Output buffer requirements not met"); + if (cctxt->out_buf_pool.q_len > 0) { + VCD_MSG_ERROR("Old output buffers were not flushed out"); + return VCD_ERR_BAD_STATE; + } + cctxt->status.mask |= VCD_FIRST_OP_RCVD; + if (cctxt->sched_clnt_hdl) + rc = vcd_sched_suspend_resume_clnt(cctxt, true); + VCD_FAILED_RETURN(rc, "Failed: vcd_sched_suspend_resume_clnt"); + if (cctxt->decoding) + rc = vcd_handle_first_fill_output_buffer_for_dec( + cctxt, buffer, handled); + else + rc = vcd_handle_first_fill_output_buffer_for_enc( + cctxt, buffer, handled); + return rc; +} + +u32 vcd_handle_first_fill_output_buffer_for_enc( + struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *frm_entry, + u32 *handled) +{ + u32 rc, seqhdr_present = 0; + struct vcd_property_hdr prop_hdr; + struct vcd_sequence_hdr seq_hdr; + struct vcd_property_codec codec; + *handled = true; + prop_hdr.prop_id = DDL_I_SEQHDR_PRESENT; + prop_hdr.sz = sizeof(seqhdr_present); + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, &seqhdr_present); + VCD_FAILED_RETURN(rc, "Failed: DDL_I_SEQHDR_PRESENT"); + if (!seqhdr_present) { + *handled = false; + return VCD_S_SUCCESS; + } + + prop_hdr.prop_id = VCD_I_CODEC; + prop_hdr.sz = sizeof(struct vcd_property_codec); + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, &codec); + if (!VCD_FAILED(rc)) { + if (codec.codec != VCD_CODEC_H263) { + prop_hdr.prop_id = VCD_I_SEQ_HEADER; + prop_hdr.sz = sizeof(struct vcd_sequence_hdr); + seq_hdr.sequence_header = frm_entry->virtual; + seq_hdr.sequence_header_len = + frm_entry->alloc_len; + rc = ddl_get_property(cctxt->ddl_handle, + &prop_hdr, &seq_hdr); + if (!VCD_FAILED(rc)) { + frm_entry->data_len = + seq_hdr.sequence_header_len; + frm_entry->time_stamp = 0; + frm_entry->flags |= + VCD_FRAME_FLAG_CODECCONFIG; + cctxt->callback(VCD_EVT_RESP_OUTPUT_DONE, + VCD_S_SUCCESS, frm_entry, + sizeof(struct vcd_frame_data), + cctxt, + cctxt->client_data); + } else + VCD_MSG_ERROR( + "rc = 0x%x. Failed:\ + ddl_get_property: VCD_I_SEQ_HEADER", + rc); + } else + VCD_MSG_LOW("Codec Type is H.263\n"); + } else + VCD_MSG_ERROR( + "rc = 0x%x. Failed: ddl_get_property:VCD_I_CODEC", + rc); + return rc; +} + +u32 vcd_handle_first_fill_output_buffer_for_dec( + struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *frm_entry, + u32 *handled) +{ + u32 rc; + struct vcd_property_hdr prop_hdr; + struct vcd_buffer_pool *out_buf_pool; + struct ddl_property_dec_pic_buffers dpb; + struct ddl_frame_data_tag *dpb_list; + u8 i; + + (void)frm_entry; + *handled = true; + prop_hdr.prop_id = DDL_I_DPB; + prop_hdr.sz = sizeof(dpb); + out_buf_pool = &cctxt->out_buf_pool; + + dpb_list = (struct ddl_frame_data_tag *) + kmalloc((sizeof(struct ddl_frame_data_tag) * + out_buf_pool->count), GFP_KERNEL); + + if (!dpb_list) { + VCD_MSG_ERROR("Memory allocation failure"); + return VCD_ERR_ALLOC_FAIL; + } + + for (i = 1; i <= out_buf_pool->count; i++) + dpb_list[i - 1].vcd_frm = out_buf_pool->entries[i].frame; + + dpb.dec_pic_buffers = dpb_list; + dpb.no_of_dec_pic_buf = out_buf_pool->count; + rc = ddl_set_property(cctxt->ddl_handle, &prop_hdr, &dpb); + + kfree(dpb_list); + *handled = false; + + return VCD_S_SUCCESS; +} + +void vcd_handle_eos_trans_end(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + if (cctxt->status.mask & VCD_EOS_PREV_VALID) { + rc = vcd_handle_frame_done(cctxt, + (void *)&cctxt->status.eos_prev_op_frm, + VCD_EVT_RESP_OUTPUT_DONE, + cctxt->status.eos_prev_op_frm_status); + cctxt->status.mask &= ~VCD_EOS_PREV_VALID; + } + if (VCD_FAILED(rc)) + return; + + if (cctxt->status.mask & VCD_FLUSH_ALL) + vcd_process_pending_flush_in_eos(cctxt); + + if (cctxt->status.mask & VCD_STOP_PENDING) + vcd_process_pending_stop_in_eos(cctxt); + else { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_RUN, + CLIENT_STATE_EVENT_NUMBER(clnt_cb)); + } +} + +void vcd_handle_eos_done(struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, u32 status) +{ + struct vcd_frame_data vcd_frm; + u32 rc = VCD_S_SUCCESS, sent_eos_frm = false; + VCD_MSG_LOW("vcd_handle_eos_done:"); + + if (VCD_FAILED(status)) + VCD_MSG_ERROR("EOS DONE returned error = 0x%x", status); + + if (cctxt->status.mask & VCD_EOS_PREV_VALID) { + cctxt->status.eos_prev_op_frm.vcd_frm.flags |= + VCD_FRAME_FLAG_EOS; + + rc = vcd_handle_frame_done(cctxt, + (void *)&cctxt->status. + eos_prev_op_frm, + VCD_EVT_RESP_OUTPUT_DONE, + cctxt->status. + eos_prev_op_frm_status); + cctxt->status.mask &= ~VCD_EOS_PREV_VALID; + if (!VCD_FAILED(rc) && + cctxt->status.eos_prev_op_frm_status != + VCD_ERR_INTRLCD_FIELD_DROP) + sent_eos_frm = true; + } + if (!sent_eos_frm) { + if (transc->ip_buf_entry) { + transc->ip_buf_entry->frame.ip_frm_tag = + transc->ip_frm_tag; + + vcd_send_frame_done_in_eos(cctxt, + &transc->ip_buf_entry->frame, false); + } else { + memset(&vcd_frm, 0, sizeof(struct vcd_frame_data)); + vcd_frm.ip_frm_tag = transc->ip_frm_tag; + vcd_frm.time_stamp = transc->time_stamp; + vcd_frm.flags = VCD_FRAME_FLAG_EOS; + vcd_send_frame_done_in_eos(cctxt, &vcd_frm, true); + } + } + if (VCD_FAILED(rc)) + return; + if (transc->ip_buf_entry) { + if (transc->ip_buf_entry->frame.virtual) { + transc->ip_buf_entry->frame.ip_frm_tag = + transc->ip_frm_tag; + cctxt->callback(VCD_EVT_RESP_INPUT_DONE, + VCD_S_SUCCESS, + &transc->ip_buf_entry->frame, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + } + transc->ip_buf_entry->in_use = false; + VCD_BUFFERPOOL_INUSE_DECREMENT(cctxt->in_buf_pool.in_use); + transc->ip_buf_entry = NULL; + if (cctxt->status.frame_submitted) + cctxt->status.frame_submitted--; + else + cctxt->status.frame_delayed--; + } + + vcd_release_trans_tbl_entry(transc); + if (cctxt->status.mask & VCD_FLUSH_ALL) + vcd_process_pending_flush_in_eos(cctxt); + + if (cctxt->status.mask & VCD_STOP_PENDING) { + vcd_process_pending_stop_in_eos(cctxt); + } else if (!(cctxt->status.mask & VCD_EOS_WAIT_OP_BUF)) { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_RUN, + CLIENT_STATE_EVENT_NUMBER + (clnt_cb)); + } +} + +void vcd_handle_start_done(struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, u32 status) +{ + cctxt->status.cmd_submitted--; + vcd_mark_command_channel(cctxt->dev_ctxt, transc); + + if (!VCD_FAILED(status)) { + cctxt->callback(VCD_EVT_RESP_START, status, NULL, + 0, cctxt, cctxt->client_data); + + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_RUN, + CLIENT_STATE_EVENT_NUMBER(clnt_cb)); + } else { + VCD_MSG_ERROR("ddl callback returned failure." + "status = 0x%x", status); + vcd_handle_err_in_starting(cctxt, status); + } +} + +void vcd_handle_stop_done(struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, u32 status) +{ + + VCD_MSG_LOW("vcd_handle_stop_done:"); + cctxt->status.cmd_submitted--; + vcd_mark_command_channel(cctxt->dev_ctxt, transc); + + if (!VCD_FAILED(status)) { + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_OPEN, + CLIENT_STATE_EVENT_NUMBER(clnt_cb)); + } else { + VCD_MSG_FATAL("STOP_DONE returned error = 0x%x", status); + status = VCD_ERR_HW_FATAL; + vcd_handle_device_err_fatal(cctxt->dev_ctxt, cctxt); + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_INVALID, + CLIENT_STATE_EVENT_NUMBER(clnt_cb)); + } + + cctxt->callback(VCD_EVT_RESP_STOP, status, NULL, 0, cctxt, + cctxt->client_data); + + memset(&cctxt->status, 0, sizeof(struct vcd_clnt_status)); +} + +void vcd_handle_stop_done_in_starting(struct vcd_clnt_ctxt + *cctxt, struct vcd_transc *transc, u32 status) +{ + VCD_MSG_LOW("vcd_handle_stop_done_in_starting:"); + cctxt->status.cmd_submitted--; + vcd_mark_command_channel(cctxt->dev_ctxt, transc); + if (!VCD_FAILED(status)) { + cctxt->callback(VCD_EVT_RESP_START, cctxt->status. + last_err, NULL, 0, cctxt, cctxt->client_data); + vcd_do_client_state_transition(cctxt, VCD_CLIENT_STATE_OPEN, + CLIENT_STATE_EVENT_NUMBER(clnt_cb)); + } else { + VCD_MSG_FATAL("VCD Cleanup: STOP_DONE returned error " + "= 0x%x", status); + vcd_handle_err_fatal(cctxt, VCD_EVT_RESP_START, + VCD_ERR_HW_FATAL); + } +} + +void vcd_handle_stop_done_in_invalid(struct vcd_clnt_ctxt *cctxt, + struct vcd_transc *transc, u32 status) +{ + u32 rc; + VCD_MSG_LOW("vcd_handle_stop_done_in_invalid:"); + + cctxt->status.cmd_submitted--; + vcd_mark_command_channel(cctxt->dev_ctxt, transc); + + if (!VCD_FAILED(status)) { + vcd_client_cmd_flush_and_en_q(cctxt, VCD_CMD_CLIENT_CLOSE); + if (cctxt->status.frame_submitted) { + vcd_release_multiple_frame_channels(cctxt->dev_ctxt, + cctxt->status.frame_submitted); + + cctxt->status.frame_submitted = 0; + cctxt->status.frame_delayed = 0; + } + if (cctxt->status.cmd_submitted) { + vcd_release_multiple_command_channels( + cctxt->dev_ctxt, + cctxt->status.cmd_submitted); + cctxt->status.cmd_submitted = 0; + } + } else { + VCD_MSG_FATAL("VCD Cleanup: STOP_DONE returned error " + "= 0x%x", status); + vcd_handle_device_err_fatal(cctxt->dev_ctxt, cctxt); + cctxt->status.mask &= ~VCD_CLEANING_UP; + } + vcd_flush_buffers_in_err_fatal(cctxt); + VCD_MSG_HIGH("VCD cleanup: All buffers are returned"); + if (cctxt->status.mask & VCD_STOP_PENDING) { + cctxt->callback(VCD_EVT_RESP_STOP, VCD_S_SUCCESS, NULL, 0, + cctxt, cctxt->client_data); + cctxt->status.mask &= ~VCD_STOP_PENDING; + } + rc = vcd_power_event(cctxt->dev_ctxt, cctxt, + VCD_EVT_PWR_CLNT_ERRFATAL); + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("VCD_EVT_PWR_CLNT_ERRFATAL failed"); + if (!(cctxt->status.mask & VCD_CLEANING_UP) && + cctxt->status.mask & VCD_CLOSE_PENDING) { + vcd_destroy_client_context(cctxt); + vcd_handle_for_last_clnt_close(cctxt->dev_ctxt, false); + } +} + +u32 vcd_handle_input_frame( + struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *input_frame) +{ + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + struct vcd_buffer_entry *buf_entry; + struct vcd_frame_data *frm_entry; + u32 rc = VCD_S_SUCCESS; + u32 eos_handled = false; + + VCD_MSG_LOW("vcd_handle_input_frame:"); + + VCD_MSG_LOW("input buffer: addr=(0x%p), sz=(%d), len=(%d)", + input_frame->virtual, input_frame->alloc_len, + input_frame->data_len); + + if (!input_frame->virtual && + !(input_frame->flags & VCD_FRAME_FLAG_EOS)) { + VCD_MSG_ERROR("Bad frame ptr/len/EOS combination"); + return VCD_ERR_ILLEGAL_PARM; + } + + + if (!input_frame->data_len && + !(input_frame->flags & VCD_FRAME_FLAG_EOS)) { + VCD_MSG_MED("data_len = 0, returning INPUT DONE"); + cctxt->callback(VCD_EVT_RESP_INPUT_DONE, + VCD_ERR_INPUT_NOT_PROCESSED, + input_frame, + sizeof(struct vcd_frame_data), + cctxt, cctxt->client_data); + return VCD_S_SUCCESS; + } + + if (!(cctxt->status.mask & VCD_FIRST_IP_RCVD)) { + if (cctxt->decoding) + rc = vcd_handle_first_decode_frame(cctxt); + + if (!VCD_FAILED(rc)) { + cctxt->status.first_ts = input_frame->time_stamp; + cctxt->status.prev_ts = cctxt->status.first_ts; + + cctxt->status.mask |= VCD_FIRST_IP_RCVD; + + (void)vcd_power_event(cctxt->dev_ctxt, + cctxt, + VCD_EVT_PWR_CLNT_FIRST_FRAME); + } + } + VCD_FAILED_RETURN(rc, "Failed: First frame handling"); + + buf_entry = vcd_find_buffer_pool_entry(&cctxt->in_buf_pool, + input_frame->virtual); + if (!buf_entry) { + VCD_MSG_ERROR("Bad buffer addr: %p", input_frame->virtual); + return VCD_ERR_FAIL; + } + + if (buf_entry->in_use) { + VCD_MSG_ERROR("An inuse input frame is being" + "re-queued to scheduler"); + return VCD_ERR_FAIL; + } + + if (input_frame->alloc_len > buf_entry->sz) { + VCD_MSG_ERROR("Bad buffer Alloc_len %d, Actual sz=%d", + input_frame->alloc_len, buf_entry->sz); + + return VCD_ERR_ILLEGAL_PARM; + } + + frm_entry = &buf_entry->frame; + + *frm_entry = *input_frame; + frm_entry->physical = buf_entry->physical; + + if (input_frame->flags & VCD_FRAME_FLAG_EOS) { + rc = vcd_handle_recvd_eos(cctxt, input_frame, + &eos_handled); + } + + if (VCD_FAILED(rc) || eos_handled) { + VCD_MSG_HIGH("rc = 0x%x, eos_handled = %d", rc, + eos_handled); + + return rc; + } + rc = vcd_sched_queue_buffer( + cctxt->sched_clnt_hdl, buf_entry, true); + VCD_FAILED_RETURN(rc, "Failed: vcd_sched_queue_buffer"); + + buf_entry->in_use = true; + cctxt->in_buf_pool.in_use++; + vcd_try_submit_frame(dev_ctxt); + return rc; +} + +void vcd_release_all_clnt_frm_transc(struct vcd_clnt_ctxt *cctxt) +{ + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + u32 i, cntr = 0; + VCD_MSG_LOW("vcd_release_all_clnt_frm_transc:"); + for (i = 0; i < dev_ctxt->trans_tbl_size; i++) { + if (dev_ctxt->trans_tbl[i].in_use && + cctxt == dev_ctxt->trans_tbl[i].cctxt) { + if (dev_ctxt->trans_tbl[i]. + type == VCD_CMD_CODE_FRAME || + dev_ctxt->trans_tbl[i]. + type == VCD_CMD_NONE) { + vcd_release_trans_tbl_entry(&dev_ctxt-> + trans_tbl[i]); + } else { + VCD_MSG_LOW("vcd_transaction in use type(%u)", + dev_ctxt->trans_tbl[i].type); + cntr++; + } + } + } + if (cntr) + VCD_MSG_ERROR("vcd_transactions still in use: (%d)", cntr); +} + +void vcd_release_all_clnt_transc(struct vcd_clnt_ctxt *cctxt) +{ + struct vcd_dev_ctxt *dev_ctxt = cctxt->dev_ctxt; + u32 i; + + VCD_MSG_LOW("vcd_release_all_clnt_transc:"); + + for (i = 0; i < dev_ctxt->trans_tbl_size; i++) { + if (dev_ctxt->trans_tbl[i].in_use && + cctxt == dev_ctxt->trans_tbl[i].cctxt) { + vcd_release_trans_tbl_entry( + &dev_ctxt->trans_tbl[i]); + } + } +} + +void vcd_send_flush_done(struct vcd_clnt_ctxt *cctxt, u32 status) +{ + VCD_MSG_LOW("vcd_send_flush_done:"); + + if (cctxt->status.mask & VCD_FLUSH_INPUT) { + cctxt->callback(VCD_EVT_RESP_FLUSH_INPUT_DONE, + status, NULL, 0, cctxt, cctxt->client_data); + cctxt->status.mask &= ~VCD_FLUSH_INPUT; + } + + if (cctxt->status.mask & VCD_FLUSH_OUTPUT) { + cctxt->callback(VCD_EVT_RESP_FLUSH_OUTPUT_DONE, + status, NULL, 0, cctxt, cctxt->client_data); + cctxt->status.mask &= ~VCD_FLUSH_OUTPUT; + } +} + +u32 vcd_store_seq_hdr( + struct vcd_clnt_ctxt *cctxt, + struct vcd_sequence_hdr *seq_hdr) +{ + u32 rc; + struct vcd_property_hdr prop_hdr; + u32 align; + u8 *virtual_aligned; + u32 addr; + int ret = 0; + + if (!seq_hdr->sequence_header_len + || !seq_hdr->sequence_header) { + VCD_MSG_ERROR("Bad seq hdr"); + + return VCD_ERR_BAD_POINTER; + } + + if (cctxt->seq_hdr.sequence_header) { + VCD_MSG_HIGH("Old seq hdr detected"); + + vcd_pmem_free(cctxt->seq_hdr.sequence_header, + cctxt->seq_hdr_phy_addr); + cctxt->seq_hdr.sequence_header = NULL; + } + + cctxt->seq_hdr.sequence_header_len = + seq_hdr->sequence_header_len; + + prop_hdr.prop_id = DDL_I_SEQHDR_ALIGN_BYTES; + prop_hdr.sz = sizeof(u32); + + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, &align); + + VCD_FAILED_RETURN(rc, + "Failed: ddl_get_property DDL_I_SEQHDR_ALIGN_BYTES"); + + VCD_MSG_MED("Seq hdr alignment bytes = %d", align); + + ret = vcd_pmem_alloc(cctxt->seq_hdr.sequence_header_len + align + + VCD_SEQ_HDR_PADDING_BYTES, + &(cctxt->seq_hdr.sequence_header), + &(cctxt->seq_hdr_phy_addr)); + + if (ret < 0) { + VCD_MSG_ERROR("Seq hdr allocation failed"); + + return VCD_ERR_ALLOC_FAIL; + } + + if (!cctxt->seq_hdr_phy_addr) { + VCD_MSG_ERROR("Couldn't get physical address"); + + return VCD_ERR_BAD_POINTER; + } + + if (align > 0) { + addr = (u32) cctxt->seq_hdr_phy_addr; + addr += align; + addr -= (addr % align); + virtual_aligned = cctxt->seq_hdr.sequence_header; + virtual_aligned += (u32) (addr - + (u32) cctxt->seq_hdr_phy_addr); + cctxt->seq_hdr_phy_addr = (u8 *) addr; + } else { + virtual_aligned = cctxt->seq_hdr.sequence_header; + } + + memcpy(virtual_aligned, seq_hdr->sequence_header, + seq_hdr->sequence_header_len); + + return VCD_S_SUCCESS; +} + +u32 vcd_set_frame_rate( + struct vcd_clnt_ctxt *cctxt, + struct vcd_property_frame_rate *fps) +{ + u32 rc; + cctxt->frm_rate = *fps; + rc = vcd_update_clnt_perf_lvl(cctxt, &cctxt->frm_rate, + cctxt->frm_p_units); + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR("rc = 0x%x. Failed: vcd_update_clnt_perf_lvl", + rc); + } + rc = vcd_sched_update_config(cctxt); + return rc; +} + +u32 vcd_set_frame_size( + struct vcd_clnt_ctxt *cctxt, + struct vcd_property_frame_size *frm_size) +{ + struct vcd_property_hdr prop_hdr; + u32 rc; + u32 frm_p_units; + (void)frm_size; + + prop_hdr.prop_id = DDL_I_FRAME_PROC_UNITS; + prop_hdr.sz = sizeof(frm_p_units); + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, &frm_p_units); + VCD_FAILED_RETURN(rc, "Failed: Get DDL_I_FRAME_PROC_UNITS"); + + cctxt->frm_p_units = frm_p_units; + + rc = vcd_update_clnt_perf_lvl(cctxt, &cctxt->frm_rate, + frm_p_units); + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR("rc = 0x%x. Failed: vcd_update_clnt_perf_lvl", + rc); + } + return rc; +} + +void vcd_process_pending_flush_in_eos(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + VCD_MSG_HIGH("Buffer flush is pending"); + rc = vcd_flush_buffers(cctxt, cctxt->status.mask & VCD_FLUSH_ALL); + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("rc = 0x%x. Failed: vcd_flush_buffers", rc); + cctxt->status.mask &= ~VCD_EOS_WAIT_OP_BUF; + vcd_send_flush_done(cctxt, VCD_S_SUCCESS); +} + +void vcd_process_pending_stop_in_eos(struct vcd_clnt_ctxt *cctxt) +{ + u32 rc = VCD_S_SUCCESS; + rc = vcd_flush_buffers(cctxt, VCD_FLUSH_ALL); + if (VCD_FAILED(rc)) + VCD_MSG_ERROR("rc = 0x%x. Failed: vcd_flush_buffers", rc); + VCD_MSG_HIGH("All buffers are returned. Enqueuing stop cmd"); + vcd_client_cmd_flush_and_en_q(cctxt, VCD_CMD_CODEC_STOP); + cctxt->status.mask &= ~VCD_STOP_PENDING; + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_STOPPING, + CLIENT_STATE_EVENT_NUMBER(stop)); +} + +u32 vcd_calculate_frame_delta( + struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *frame) +{ + u32 frm_delta; + u64 temp, max = ~((u64)0); + + if (frame->time_stamp >= cctxt->status.prev_ts) + temp = frame->time_stamp - cctxt->status.prev_ts; + else + temp = (max - cctxt->status.prev_ts) + + frame->time_stamp; + + VCD_MSG_LOW("Curr_ts=%lld Prev_ts=%lld Diff=%llu", + frame->time_stamp, cctxt->status.prev_ts, temp); + + temp *= cctxt->time_resoln; + (void)do_div(temp, VCD_TIMESTAMP_RESOLUTION); + frm_delta = temp; + cctxt->status.time_elapsed += frm_delta; + + temp = (cctxt->status.time_elapsed * VCD_TIMESTAMP_RESOLUTION); + (void)do_div(temp, cctxt->time_resoln); + cctxt->status.prev_ts = cctxt->status.first_ts + temp; + + VCD_MSG_LOW("Time_elapsed=%llu, Drift=%llu, new Prev_ts=%lld", + cctxt->status.time_elapsed, temp, + cctxt->status.prev_ts); + + return frm_delta; +} + +struct vcd_buffer_entry *vcd_check_fill_output_buffer + (struct vcd_clnt_ctxt *cctxt, + struct vcd_frame_data *buffer) { + struct vcd_buffer_pool *buf_pool = &cctxt->out_buf_pool; + struct vcd_buffer_entry *buf_entry; + + if (!buf_pool->entries) { + VCD_MSG_ERROR("Buffers not set or allocated yet"); + + return NULL; + } + + if (!buffer->virtual) { + VCD_MSG_ERROR("NULL buffer address provided"); + return NULL; + } + + buf_entry = + vcd_find_buffer_pool_entry(buf_pool, buffer->virtual); + if (!buf_entry) { + VCD_MSG_ERROR("Unrecognized buffer address provided = %p", + buffer->virtual); + return NULL; + } + + if (buf_entry->in_use) { + VCD_MSG_ERROR + ("An inuse output frame is being provided for reuse"); + return NULL; + } + + if ((buffer->alloc_len < buf_pool->buf_req.sz || + buffer->alloc_len > buf_entry->sz) && + !(cctxt->status.mask & VCD_IN_RECONFIG)) { + VCD_MSG_ERROR + ("Bad buffer Alloc_len = %d, Actual sz = %d, " + " Min sz = %u", + buffer->alloc_len, buf_entry->sz, + buf_pool->buf_req.sz); + return NULL; + } + + return buf_entry; +} + +void vcd_handle_ind_hw_err_fatal(struct vcd_clnt_ctxt *cctxt, + u32 event, u32 status) +{ + if (cctxt->status.frame_submitted) { + cctxt->status.frame_submitted--; + vcd_mark_frame_channel(cctxt->dev_ctxt); + } + vcd_handle_err_fatal(cctxt, event, status); +} + +void vcd_handle_err_fatal(struct vcd_clnt_ctxt *cctxt, u32 event, + u32 status) +{ + VCD_MSG_LOW("vcd_handle_err_fatal: event=%x, err=%x", event, status); + if (!VCD_FAILED_FATAL(status)) + return; + + if (VCD_FAILED_DEVICE_FATAL(status)) { + vcd_clnt_handle_device_err_fatal(cctxt, event); + vcd_handle_device_err_fatal(cctxt->dev_ctxt, cctxt); + } else if (VCD_FAILED_CLIENT_FATAL(status)) { + cctxt->status.last_evt = event; + cctxt->callback(event, VCD_ERR_HW_FATAL, NULL, 0, cctxt, + cctxt->client_data); + cctxt->status.mask |= VCD_CLEANING_UP; + vcd_client_cmd_flush_and_en_q(cctxt, VCD_CMD_CODEC_STOP); + vcd_do_client_state_transition(cctxt, + VCD_CLIENT_STATE_INVALID, + CLIENT_STATE_EVENT_NUMBER(clnt_cb)); + } +} + +void vcd_handle_err_in_starting(struct vcd_clnt_ctxt *cctxt, + u32 status) +{ + VCD_MSG_LOW("\n vcd_handle_err_in_starting:"); + if (VCD_FAILED_FATAL(status)) { + vcd_handle_err_fatal(cctxt, VCD_EVT_RESP_START, status); + } else { + cctxt->status.last_err = status; + VCD_MSG_HIGH("\n VCD cleanup: Enqueuing stop cmd"); + vcd_client_cmd_flush_and_en_q(cctxt, VCD_CMD_CODEC_STOP); + } +} + +void vcd_handle_trans_pending(struct vcd_clnt_ctxt *cctxt) +{ + if (!cctxt->status.frame_submitted) { + VCD_MSG_ERROR("Transaction pending response was not expected"); + return; + } + cctxt->status.frame_submitted--; + cctxt->status.frame_delayed++; + vcd_mark_frame_channel(cctxt->dev_ctxt); +} +void vcd_handle_submit_frame_failed(struct vcd_dev_ctxt + *dev_ctxt, struct vcd_transc *transc) +{ + struct vcd_clnt_ctxt *cctxt = transc->cctxt; + u32 rc; + + vcd_mark_frame_channel(dev_ctxt); + vcd_release_trans_tbl_entry(transc); + + vcd_handle_err_fatal(cctxt, VCD_EVT_IND_HWERRFATAL, + VCD_ERR_CLIENT_FATAL); + + if (vcd_get_command_channel(dev_ctxt, &transc)) { + transc->type = VCD_CMD_CODEC_STOP; + transc->cctxt = cctxt; + rc = vcd_submit_cmd_sess_end(transc); + if (VCD_FAILED(rc)) { + vcd_release_command_channel(dev_ctxt, transc); + VCD_MSG_ERROR("rc = 0x%x. Failed: VCD_SubmitCmdSessEnd", + rc); + } + } +} + +u32 vcd_check_if_buffer_req_met(struct vcd_clnt_ctxt *cctxt, + enum vcd_buffer_type buffer) +{ + struct vcd_property_hdr prop_hdr; + struct vcd_buffer_pool *buf_pool; + struct vcd_buffer_requirement buf_req; + u32 rc; + u8 i; + + if (buffer == VCD_BUFFER_INPUT) { + prop_hdr.prop_id = DDL_I_INPUT_BUF_REQ; + buf_pool = &cctxt->in_buf_pool; + } else { + prop_hdr.prop_id = DDL_I_OUTPUT_BUF_REQ; + buf_pool = &cctxt->out_buf_pool; + } + + prop_hdr.sz = sizeof(buf_req); + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, &buf_req); + VCD_FAILED_RETURN(rc, "Failed: ddl_GetProperty"); + + buf_pool->buf_req = buf_req; + if (buf_pool->count < buf_req.actual_count) { + VCD_MSG_ERROR("Buf requirement count not met"); + return VCD_ERR_FAIL; + } + + if (buf_pool->count > buf_req.actual_count) + buf_pool->count = buf_req.actual_count; + + if (!buf_pool->entries || + buf_pool->validated != buf_pool->count) { + VCD_MSG_ERROR("Buffer pool is not completely setup yet"); + return VCD_ERR_BAD_STATE; + } + for (i = 1; (rc == VCD_S_SUCCESS && i <= buf_pool->count); i++) { + if (buf_pool->entries[i].sz < + buf_pool->buf_req.sz) { + VCD_MSG_ERROR( + "BufReq sz not met:\ + addr=(0x%p) sz=%d ReqSize=%d", + buf_pool->entries[i].virtual, + buf_pool->entries[i].sz, + buf_pool->buf_req.sz); + rc = VCD_ERR_FAIL; + } + } + return rc; +} + +u32 vcd_handle_ind_output_reconfig( + struct vcd_clnt_ctxt *cctxt, void* payload, u32 status) +{ + struct ddl_frame_data_tag *frame = + (struct ddl_frame_data_tag *)payload; + struct vcd_property_hdr prop_hdr; + u32 rc = VCD_S_SUCCESS; + struct vcd_buffer_pool *out_buf_pool; + struct vcd_buffer_requirement buf_req; + + if (frame) + rc = vcd_handle_output_required(cctxt, payload, status); + VCD_FAILED_RETURN(rc, "Failed: vcd_handle_output_required in reconfig"); + vcd_mark_frame_channel(cctxt->dev_ctxt); + + rc = vcd_sched_suspend_resume_clnt(cctxt, false); + VCD_FAILED_RETURN(rc, "Failed: vcd_sched_suspend_resume_clnt"); + out_buf_pool = &cctxt->out_buf_pool; + prop_hdr.prop_id = DDL_I_OUTPUT_BUF_REQ; + prop_hdr.sz = sizeof(buf_req); + rc = ddl_get_property(cctxt->ddl_handle, &prop_hdr, &buf_req); + VCD_FAILED_RETURN(rc, "Failed: ddl_GetProperty"); + + out_buf_pool->buf_req = buf_req; + + if (out_buf_pool->count < buf_req.actual_count) { + VCD_MSG_HIGH("Output buf requirement count increased"); + out_buf_pool->count = buf_req.actual_count; + } + + if (buf_req.actual_count > VCD_MAX_BUFFER_ENTRIES) { + VCD_MSG_ERROR("\n New act count exceeds Max count(32)"); + return VCD_ERR_FAIL; + } + + if (!VCD_FAILED(rc)) { + rc = vcd_set_frame_size(cctxt, NULL); + VCD_FAILED_RETURN(rc, "Failed: set_frame_size in reconfig"); + cctxt->status.mask &= ~VCD_FIRST_OP_RCVD; + cctxt->status.mask |= VCD_IN_RECONFIG; + cctxt->callback(VCD_EVT_IND_OUTPUT_RECONFIG, + status, NULL, 0, cctxt, + cctxt->client_data); + } + return rc; +} + +u32 vcd_handle_ind_output_reconfig_in_flushing( + struct vcd_clnt_ctxt *cctxt, void* payload, u32 status) +{ + u32 rc = VCD_S_SUCCESS; + if (cctxt->status.mask & VCD_FLUSH_INPUT && payload) { + (void)vcd_handle_input_done(cctxt, payload, + VCD_EVT_RESP_INPUT_FLUSHED, status); + payload = NULL; + } + rc = vcd_handle_ind_output_reconfig(cctxt, payload, status); + return rc; +} + +u32 vcd_return_op_buffer_to_hw(struct vcd_clnt_ctxt *cctxt, + struct vcd_buffer_entry *buf_entry) +{ + u32 rc = VCD_S_SUCCESS; + struct vcd_frame_data *frm_entry = &buf_entry->frame; + + VCD_MSG_LOW("vcd_return_op_buffer_to_hw in %d:", + cctxt->clnt_state.state); + frm_entry->physical = buf_entry->physical; + frm_entry->ip_frm_tag = VCD_FRAMETAG_INVALID; + frm_entry->intrlcd_ip_frm_tag = VCD_FRAMETAG_INVALID; + frm_entry->data_len = 0; + + if (cctxt->decoding) { + struct vcd_property_hdr Prop_hdr; + struct ddl_frame_data_tag ddl_frm; + Prop_hdr.prop_id = DDL_I_DPB_RELEASE; + Prop_hdr.sz = + sizeof(struct ddl_frame_data_tag); + memset(&ddl_frm, 0, sizeof(ddl_frm)); + ddl_frm.vcd_frm = *frm_entry; + rc = ddl_set_property(cctxt->ddl_handle, &Prop_hdr, + &ddl_frm); + if (VCD_FAILED(rc)) { + VCD_MSG_ERROR("Error returning output buffer to" + " HW. rc = 0x%x", rc); + buf_entry->in_use = false; + } else { + cctxt->out_buf_pool.in_use++; + buf_entry->in_use = true; + } + } + return rc; +} + +void vcd_handle_clnt_fatal(struct vcd_clnt_ctxt *cctxt, u32 trans_end) +{ + if (trans_end) + vcd_mark_frame_channel(cctxt->dev_ctxt); + vcd_handle_err_fatal(cctxt, + VCD_EVT_IND_HWERRFATAL, VCD_ERR_CLIENT_FATAL); +} + +void vcd_handle_clnt_fatal_input_done(struct vcd_clnt_ctxt *cctxt, + u32 trans_end) +{ + if (cctxt->status.frame_submitted > 0) + cctxt->status.frame_submitted--; + vcd_handle_clnt_fatal(cctxt, trans_end); +} + +void vcd_handle_ind_info_output_reconfig( + struct vcd_clnt_ctxt *cctxt, u32 status) +{ + if (cctxt) { + cctxt->callback(VCD_EVT_IND_INFO_OUTPUT_RECONFIG, status, NULL, + 0, cctxt, cctxt->client_data); + } +} diff --git a/drivers/video/msm/vidc/common/vcd/vcd_util.c b/drivers/video/msm/vidc/common/vcd/vcd_util.c new file mode 100644 index 00000000000..ba991f1ccde --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_util.c @@ -0,0 +1,106 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "vidc_type.h" +#include "vcd_util.h" + +u32 vcd_critical_section_create(u32 **p_cs) +{ + struct mutex *lock; + if (!p_cs) { + VCD_MSG_ERROR("Bad critical section ptr"); + return VCD_ERR_BAD_POINTER; + } else { + lock = kmalloc(sizeof(struct mutex), GFP_KERNEL); + if (!lock) { + VCD_MSG_ERROR("Failed: vcd_critical_section_create"); + return VCD_ERR_ALLOC_FAIL; + } + mutex_init(lock); + *p_cs = (u32 *) lock; + return VCD_S_SUCCESS; + } +} + +u32 vcd_critical_section_release(u32 *cs) +{ + struct mutex *lock = (struct mutex *)cs; + if (!lock) { + VCD_MSG_ERROR("Bad critical section object"); + return VCD_ERR_BAD_POINTER; + } + + mutex_destroy(lock); + kfree(cs); + return VCD_S_SUCCESS; +} + +u32 vcd_critical_section_enter(u32 *cs) +{ + struct mutex *lock = (struct mutex *)cs; + if (!lock) { + VCD_MSG_ERROR("Bad critical section object"); + return VCD_ERR_BAD_POINTER; + } else + mutex_lock(lock); + + return VCD_S_SUCCESS; +} + +u32 vcd_critical_section_leave(u32 *cs) +{ + struct mutex *lock = (struct mutex *)cs; + + if (!lock) { + VCD_MSG_ERROR("Bad critical section object"); + + return VCD_ERR_BAD_POINTER; + } else + mutex_unlock(lock); + + return VCD_S_SUCCESS; +} + +int vcd_pmem_alloc(u32 size, u8 **kernel_vaddr, u8 **phy_addr) +{ + *phy_addr = + (u8 *) pmem_kalloc(size, PMEM_MEMTYPE | PMEM_ALIGNMENT_4K); + + if (!IS_ERR((void *)*phy_addr)) { + + *kernel_vaddr = ioremap((unsigned long)*phy_addr, size); + + if (!*kernel_vaddr) { + pr_err("%s: could not ioremap in kernel pmem buffers\n", + __func__); + pmem_kfree((s32) *phy_addr); + return -ENOMEM; + } + pr_debug("write buf: phy addr 0x%08x kernel addr 0x%08x\n", + (u32) *phy_addr, (u32) *kernel_vaddr); + return 0; + } else { + pr_err("%s: could not allocte in kernel pmem buffers\n", + __func__); + return -ENOMEM; + } + +} + +int vcd_pmem_free(u8 *kernel_vaddr, u8 *phy_addr) +{ + iounmap((void *)kernel_vaddr); + pmem_kfree((s32) phy_addr); + + return 0; +} diff --git a/drivers/video/msm/vidc/common/vcd/vcd_util.h b/drivers/video/msm/vidc/common/vcd/vcd_util.h new file mode 100644 index 00000000000..07ad651127f --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vcd_util.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _VCD_UTIL_H_ +#define _VCD_UTIL_H_ +#include "vidc_type.h" +#include "vcd_api.h" + +#if DEBUG + +#define VCD_MSG_LOW(xx_fmt...) printk(KERN_INFO "\n\t* " xx_fmt) +#define VCD_MSG_MED(xx_fmt...) printk(KERN_INFO "\n * " xx_fmt) +#define VCD_MSG_HIGH(xx_fmt...) printk(KERN_WARNING "\n" xx_fmt) + +#else + +#define VCD_MSG_LOW(xx_fmt...) +#define VCD_MSG_MED(xx_fmt...) +#define VCD_MSG_HIGH(xx_fmt...) + +#endif + +#define VCD_MSG_ERROR(xx_fmt...) printk(KERN_ERR "\n err: " xx_fmt) +#define VCD_MSG_FATAL(xx_fmt...) printk(KERN_ERR "\n " xx_fmt) + +#define VCD_FAILED_RETURN(rc, xx_fmt...) \ + do { \ + if (VCD_FAILED(rc)) { \ + printk(KERN_ERR xx_fmt); \ + return rc; \ + } \ + } while (0) + +#define VCD_FAILED_DEVICE_FATAL(rc) \ + (rc == VCD_ERR_HW_FATAL ? true : false) +#define VCD_FAILED_CLIENT_FATAL(rc) \ + (rc == VCD_ERR_CLIENT_FATAL ? true : false) + +#define VCD_FAILED_FATAL(rc) \ + ((VCD_FAILED_DEVICE_FATAL(rc) || VCD_FAILED_CLIENT_FATAL(rc)) \ + ? true : false) + +#endif diff --git a/drivers/video/msm/vidc/common/vcd/vidc_type.h b/drivers/video/msm/vidc/common/vcd/vidc_type.h new file mode 100644 index 00000000000..bd87c0d9f0b --- /dev/null +++ b/drivers/video/msm/vidc/common/vcd/vidc_type.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef VIDC_TYPE_H +#define VIDC_TYPE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG 0 +#define VIDC_ENABLE_DBGFS + +#define USE_RES_TRACKER +#endif diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h index 3b859a3e6a0..98227b4fe07 100644 --- a/fs/btrfs/ctree.h +++ b/fs/btrfs/ctree.h @@ -1381,6 +1381,8 @@ struct btrfs_ioctl_defrag_range_args { #define BTRFS_INODE_ROOT_ITEM_INIT (1 << 31) +#define BTRFS_INODE_ROOT_ITEM_INIT (1 << 31) + /* some macros to generate set/get funcs for the struct fields. This * assumes there is a lefoo_to_cpu for every type, so lets make a simple * one for u8: diff --git a/fs/compat_ioctl.c b/fs/compat_ioctl.c index 61abb638b4b..1125f0584d0 100644 --- a/fs/compat_ioctl.c +++ b/fs/compat_ioctl.c @@ -1208,6 +1208,7 @@ COMPATIBLE_IOCTL(HCIGETDEVINFO) COMPATIBLE_IOCTL(HCIGETCONNLIST) COMPATIBLE_IOCTL(HCIGETCONNINFO) COMPATIBLE_IOCTL(HCIGETAUTHINFO) +COMPATIBLE_IOCTL(HCISETAUTHINFO) COMPATIBLE_IOCTL(HCISETRAW) COMPATIBLE_IOCTL(HCISETSCAN) COMPATIBLE_IOCTL(HCISETAUTH) diff --git a/fs/fat/Makefile b/fs/fat/Makefile index e06190322c1..3f3e5354f8e 100644 --- a/fs/fat/Makefile +++ b/fs/fat/Makefile @@ -2,6 +2,7 @@ # Makefile for the Linux fat filesystem support. # + obj-$(CONFIG_FAT_FS) += fat.o obj-$(CONFIG_VFAT_FS) += vfat.o obj-$(CONFIG_MSDOS_FS) += msdos.o diff --git a/fs/yaffs2/yaffs_vfs.c b/fs/yaffs2/yaffs_vfs.c index d5b87531400..a4db5ccf9ff 100644 --- a/fs/yaffs2/yaffs_vfs.c +++ b/fs/yaffs2/yaffs_vfs.c @@ -43,7 +43,6 @@ #include #include #include -#include #include #include #include @@ -2375,19 +2374,17 @@ static int yaffs_internal_read_super_mtd(struct super_block *sb, void *data, return yaffs_internal_read_super(1, sb, data, silent) ? 0 : -EINVAL; } -static int yaffs_read_super(struct file_system_type *fs, - int flags, const char *dev_name, - void *data, struct vfsmount *mnt) +static struct dentry *yaffs_mount(struct file_system_type *fs, int flags, + const char *dev_name, void *data) { - - return get_sb_bdev(fs, flags, dev_name, data, - yaffs_internal_read_super_mtd, mnt); + return mount_bdev(fs, flags, dev_name, data, + yaffs_internal_read_super_mtd); } static struct file_system_type yaffs_fs_type = { .owner = THIS_MODULE, .name = "yaffs", - .get_sb = yaffs_read_super, + .mount = yaffs_mount, .kill_sb = kill_block_super, .fs_flags = FS_REQUIRES_DEV, }; @@ -2400,18 +2397,17 @@ static int yaffs2_internal_read_super_mtd(struct super_block *sb, void *data, return yaffs_internal_read_super(2, sb, data, silent) ? 0 : -EINVAL; } -static int yaffs2_read_super(struct file_system_type *fs, - int flags, const char *dev_name, void *data, - struct vfsmount *mnt) +static struct dentry *yaffs2_mount(struct file_system_type *fs, + int flags, const char *dev_name, void *data) { - return get_sb_bdev(fs, flags, dev_name, data, - yaffs2_internal_read_super_mtd, mnt); + return mount_bdev(fs, flags, dev_name, data, + yaffs2_internal_read_super_mtd); } static struct file_system_type yaffs2_fs_type = { .owner = THIS_MODULE, .name = "yaffs2", - .get_sb = yaffs2_read_super, + .mount = yaffs2_mount, .kill_sb = kill_block_super, .fs_flags = FS_REQUIRES_DEV, }; diff --git a/include/Kbuild b/include/Kbuild index 8d226bfa269..5f65ac2887a 100644 --- a/include/Kbuild +++ b/include/Kbuild @@ -10,3 +10,4 @@ header-y += video/ header-y += drm/ header-y += xen/ header-y += scsi/ +header-y += media/ diff --git a/include/drm/Kbuild b/include/drm/Kbuild index 3a60ac88952..a0d20587782 100644 --- a/include/drm/Kbuild +++ b/include/drm/Kbuild @@ -11,3 +11,4 @@ header-y += savage_drm.h header-y += sis_drm.h header-y += via_drm.h header-y += vmwgfx_drm.h +header-y += kgsl_drm.h diff --git a/include/drm/drm.h b/include/drm/drm.h index 4be33b4ca2f..120befd2c07 100644 --- a/include/drm/drm.h +++ b/include/drm/drm.h @@ -36,7 +36,7 @@ #ifndef _DRM_H_ #define _DRM_H_ -#if defined(__linux__) +#if defined(__KERNEL__) || defined(_LINUX) || defined(__linux__) #include #include diff --git a/include/drm/kgsl_drm.h b/include/drm/kgsl_drm.h new file mode 100644 index 00000000000..f1c7f4e22f3 --- /dev/null +++ b/include/drm/kgsl_drm.h @@ -0,0 +1,192 @@ +#ifndef _KGSL_DRM_H_ +#define _KGSL_DRM_H_ + +#include "drm.h" + +#define DRM_KGSL_GEM_CREATE 0x00 +#define DRM_KGSL_GEM_PREP 0x01 +#define DRM_KGSL_GEM_SETMEMTYPE 0x02 +#define DRM_KGSL_GEM_GETMEMTYPE 0x03 +#define DRM_KGSL_GEM_MMAP 0x04 +#define DRM_KGSL_GEM_ALLOC 0x05 +#define DRM_KGSL_GEM_BIND_GPU 0x06 +#define DRM_KGSL_GEM_UNBIND_GPU 0x07 + +#define DRM_KGSL_GEM_GET_BUFINFO 0x08 +#define DRM_KGSL_GEM_SET_BUFCOUNT 0x09 +#define DRM_KGSL_GEM_SET_ACTIVE 0x0A +#define DRM_KGSL_GEM_LOCK_HANDLE 0x0B +#define DRM_KGSL_GEM_UNLOCK_HANDLE 0x0C +#define DRM_KGSL_GEM_UNLOCK_ON_TS 0x0D +#define DRM_KGSL_GEM_CREATE_FD 0x0E + +#define DRM_IOCTL_KGSL_GEM_CREATE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_CREATE, struct drm_kgsl_gem_create) + +#define DRM_IOCTL_KGSL_GEM_PREP \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_PREP, struct drm_kgsl_gem_prep) + +#define DRM_IOCTL_KGSL_GEM_SETMEMTYPE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_SETMEMTYPE, \ +struct drm_kgsl_gem_memtype) + +#define DRM_IOCTL_KGSL_GEM_GETMEMTYPE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_GETMEMTYPE, \ +struct drm_kgsl_gem_memtype) + +#define DRM_IOCTL_KGSL_GEM_MMAP \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_MMAP, struct drm_kgsl_gem_mmap) + +#define DRM_IOCTL_KGSL_GEM_ALLOC \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_ALLOC, struct drm_kgsl_gem_alloc) + +#define DRM_IOCTL_KGSL_GEM_BIND_GPU \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_BIND_GPU, struct drm_kgsl_gem_bind_gpu) + +#define DRM_IOCTL_KGSL_GEM_UNBIND_GPU \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_UNBIND_GPU, \ +struct drm_kgsl_gem_bind_gpu) + +#define DRM_IOCTL_KGSL_GEM_GET_BUFINFO \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_GET_BUFINFO, \ + struct drm_kgsl_gem_bufinfo) + +#define DRM_IOCTL_KGSL_GEM_SET_BUFCOUNT \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_SET_BUFCOUNT, \ + struct drm_kgsl_gem_bufcount) + +#define DRM_IOCTL_KGSL_GEM_SET_ACTIVE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_SET_ACTIVE, \ + struct drm_kgsl_gem_active) + +#define DRM_IOCTL_KGSL_GEM_LOCK_HANDLE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_LOCK_HANDLE, \ +struct drm_kgsl_gem_lock_handles) + +#define DRM_IOCTL_KGSL_GEM_UNLOCK_HANDLE \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_UNLOCK_HANDLE, \ +struct drm_kgsl_gem_unlock_handles) + +#define DRM_IOCTL_KGSL_GEM_UNLOCK_ON_TS \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_UNLOCK_ON_TS, \ +struct drm_kgsl_gem_unlock_on_ts) + +#define DRM_IOCTL_KGSL_GEM_CREATE_FD \ +DRM_IOWR(DRM_COMMAND_BASE + DRM_KGSL_GEM_CREATE_FD, \ +struct drm_kgsl_gem_create_fd) + +/* Maximum number of sub buffers per GEM object */ +#define DRM_KGSL_GEM_MAX_BUFFERS 2 + +/* Memory types - these define the source and caching policies + of the GEM memory chunk */ + +/* Legacy definitions left for compatability */ + +#define DRM_KGSL_GEM_TYPE_EBI 0 +#define DRM_KGSL_GEM_TYPE_SMI 1 +#define DRM_KGSL_GEM_TYPE_KMEM 2 +#define DRM_KGSL_GEM_TYPE_KMEM_NOCACHE 3 +#define DRM_KGSL_GEM_TYPE_MEM_MASK 0xF + +/* Contiguous memory (PMEM) */ +#define DRM_KGSL_GEM_TYPE_PMEM 0x000100 + +/* PMEM memory types */ +#define DRM_KGSL_GEM_PMEM_EBI 0x001000 +#define DRM_KGSL_GEM_PMEM_SMI 0x002000 + +/* Standard paged memory */ +#define DRM_KGSL_GEM_TYPE_MEM 0x010000 + +/* Caching controls */ +#define DRM_KGSL_GEM_CACHE_NONE 0x000000 +#define DRM_KGSL_GEM_CACHE_WCOMBINE 0x100000 +#define DRM_KGSL_GEM_CACHE_WTHROUGH 0x200000 +#define DRM_KGSL_GEM_CACHE_WBACK 0x400000 +#define DRM_KGSL_GEM_CACHE_WBACKWA 0x800000 +#define DRM_KGSL_GEM_CACHE_MASK 0xF00000 + +/* FD based objects */ +#define DRM_KGSL_GEM_TYPE_FD_FBMEM 0x1000000 +#define DRM_KGSL_GEM_TYPE_FD_MASK 0xF000000 + +/* Timestamp types */ +#define DRM_KGSL_GEM_TS_3D 0x00000430 +#define DRM_KGSL_GEM_TS_2D 0x00000180 + + +struct drm_kgsl_gem_create { + uint32_t size; + uint32_t handle; +}; + +struct drm_kgsl_gem_prep { + uint32_t handle; + uint32_t phys; + uint64_t offset; +}; + +struct drm_kgsl_gem_memtype { + uint32_t handle; + uint32_t type; +}; + +struct drm_kgsl_gem_mmap { + uint32_t handle; + uint32_t size; + uint32_t hostptr; + uint64_t offset; +}; + +struct drm_kgsl_gem_alloc { + uint32_t handle; + uint64_t offset; +}; + +struct drm_kgsl_gem_bind_gpu { + uint32_t handle; + uint32_t gpuptr; +}; + +struct drm_kgsl_gem_bufinfo { + uint32_t handle; + uint32_t count; + uint32_t active; + uint32_t offset[DRM_KGSL_GEM_MAX_BUFFERS]; + uint32_t gpuaddr[DRM_KGSL_GEM_MAX_BUFFERS]; +}; + +struct drm_kgsl_gem_bufcount { + uint32_t handle; + uint32_t bufcount; +}; + +struct drm_kgsl_gem_active { + uint32_t handle; + uint32_t active; +}; + +struct drm_kgsl_gem_lock_handles { + uint32_t num_handles; + uint32_t *handle_list; + uint32_t pid; + uint32_t lock_id; /* Returned lock id used for unlocking */ +}; + +struct drm_kgsl_gem_unlock_handles { + uint32_t lock_id; +}; + +struct drm_kgsl_gem_unlock_on_ts { + uint32_t lock_id; + uint32_t timestamp; /* This field is a hw generated ts */ + uint32_t type; /* Which pipe to check for ts generation */ +}; + +struct drm_kgsl_gem_create_fd { + uint32_t fd; + uint32_t handle; +}; + +#endif diff --git a/include/linux/Kbuild b/include/linux/Kbuild index 01f63627505..32d814b3d28 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -18,6 +18,7 @@ header-y += netfilter_ipv4/ header-y += netfilter_ipv6/ header-y += usb/ header-y += wimax/ +header-y += mfd/ objhdr-y += version.h @@ -96,6 +97,7 @@ header-y += comstats.h header-y += connector.h header-y += const.h header-y += cramfs_fs.h +header-y += csdio.h header-y += cuda.h header-y += cyclades.h header-y += cycx_cfm.h @@ -228,6 +230,7 @@ header-y += keyctl.h header-y += l2tp.h header-y += limits.h header-y += llc.h +header-y += l2tp.h header-y += loop.h header-y += lp.h header-y += magic.h @@ -246,6 +249,7 @@ header-y += mroute.h header-y += mroute6.h header-y += msdos_fs.h header-y += msg.h +header-y += msm_adc.h header-y += mtio.h header-y += n_r3964.h header-y += nbd.h @@ -399,3 +403,21 @@ header-y += wireless.h header-y += x25.h header-y += xattr.h header-y += xfrm.h +header-y += msm_adsp.h +header-y += msm_mdp.h +header-y += msm_kgsl.h +header-y += msm_q6venc.h +header-y += msm_q6vdec.h +header-y += msm_rotator.h +header-y += msm_vidc_dec.h +header-y += msm_vidc_enc.h +header-y += msm_audio.h +header-y += msm_audio_aac.h +header-y += msm_audio_acdb.h +header-y += android_pmem.h +header-y += msm_audio_wma.h +header-y += msm_audio_wmapro.h +header-y += msm_audio_mvs.h +header-y += msm_ipc.h +header-y += msm_charm.h +header-y += tzcom.h diff --git a/include/linux/adv7520.h b/include/linux/adv7520.h new file mode 100644 index 00000000000..96db7b7b91c --- /dev/null +++ b/include/linux/adv7520.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ADV7520_H_ +#define _ADV7520_H_ +#define ADV7520_DRV_NAME "adv7520" + +/* Configure the 20-bit 'N' used with the CTS to +regenerate the audio clock in the receiver +Pixel clock: 74.25 Mhz, Audio sampling: 44.1 Khz -> N +value = 6272 */ +#define ADV7520_AUDIO_CTS_20BIT_N 6272 + +#endif diff --git a/include/linux/android_alarm.h b/include/linux/android_alarm.h index f8f14e793db..cbfeafce329 100644 --- a/include/linux/android_alarm.h +++ b/include/linux/android_alarm.h @@ -74,6 +74,7 @@ ktime_t alarm_get_elapsed_realtime(void); /* set rtc while preserving elapsed realtime */ int alarm_set_rtc(const struct timespec ts); +void alarm_update_timedelta(struct timespec tv, struct timespec ts); #endif diff --git a/include/linux/android_pmem.h b/include/linux/android_pmem.h index f633621f5be..f42d74b6127 100644 --- a/include/linux/android_pmem.h +++ b/include/linux/android_pmem.h @@ -16,6 +16,20 @@ #ifndef _ANDROID_PMEM_H_ #define _ANDROID_PMEM_H_ +#include + +#define PMEM_KERNEL_TEST_MAGIC 0xc0 +#define PMEM_KERNEL_TEST_NOMINAL_TEST_IOCTL \ + _IO(PMEM_KERNEL_TEST_MAGIC, 1) +#define PMEM_KERNEL_TEST_ADVERSARIAL_TEST_IOCTL \ + _IO(PMEM_KERNEL_TEST_MAGIC, 2) +#define PMEM_KERNEL_TEST_HUGE_ALLOCATION_TEST_IOCTL \ + _IO(PMEM_KERNEL_TEST_MAGIC, 3) +#define PMEM_KERNEL_TEST_FREE_UNALLOCATED_TEST_IOCTL \ + _IO(PMEM_KERNEL_TEST_MAGIC, 4) +#define PMEM_KERNEL_TEST_LARGE_REGION_NUMBER_TEST_IOCTL \ + _IO(PMEM_KERNEL_TEST_MAGIC, 5) + #define PMEM_IOCTL_MAGIC 'p' #define PMEM_GET_PHYS _IOW(PMEM_IOCTL_MAGIC, 1, unsigned int) #define PMEM_MAP _IOW(PMEM_IOCTL_MAGIC, 2, unsigned int) @@ -33,61 +47,123 @@ * struct (with offset set to 0). */ #define PMEM_GET_TOTAL_SIZE _IOW(PMEM_IOCTL_MAGIC, 7, unsigned int) -#define PMEM_CACHE_FLUSH _IOW(PMEM_IOCTL_MAGIC, 8, unsigned int) +/* Revokes gpu registers and resets the gpu. Pass a pointer to the + * start of the mapped gpu regs (the vaddr returned by mmap) as the argument. + */ +#define HW3D_REVOKE_GPU _IOW(PMEM_IOCTL_MAGIC, 8, unsigned int) +#define HW3D_GRANT_GPU _IOW(PMEM_IOCTL_MAGIC, 9, unsigned int) +#define HW3D_WAIT_FOR_INTERRUPT _IOW(PMEM_IOCTL_MAGIC, 10, unsigned int) + +#define PMEM_CLEAN_INV_CACHES _IOW(PMEM_IOCTL_MAGIC, 11, unsigned int) +#define PMEM_CLEAN_CACHES _IOW(PMEM_IOCTL_MAGIC, 12, unsigned int) +#define PMEM_INV_CACHES _IOW(PMEM_IOCTL_MAGIC, 13, unsigned int) + +#define PMEM_GET_FREE_SPACE _IOW(PMEM_IOCTL_MAGIC, 14, unsigned int) +#define PMEM_ALLOCATE_ALIGNED _IOW(PMEM_IOCTL_MAGIC, 15, unsigned int) +struct pmem_region { + unsigned long offset; + unsigned long len; +}; + +struct pmem_addr { + unsigned long vaddr; + unsigned long offset; + unsigned long length; +}; + +struct pmem_freespace { + unsigned long total; + unsigned long largest; +}; + +struct pmem_allocation { + unsigned long size; + unsigned int align; +}; + +#ifdef __KERNEL__ +int get_pmem_file(unsigned int fd, unsigned long *start, unsigned long *vstart, + unsigned long *end, struct file **filp); +int get_pmem_fd(int fd, unsigned long *start, unsigned long *end); +int get_pmem_user_addr(struct file *file, unsigned long *start, + unsigned long *end); +void put_pmem_file(struct file* file); +void put_pmem_fd(int fd); +void flush_pmem_fd(int fd, unsigned long start, unsigned long len); +void flush_pmem_file(struct file *file, unsigned long start, unsigned long len); +int pmem_cache_maint(struct file *file, unsigned int cmd, + struct pmem_addr *pmem_addr); + +enum pmem_allocator_type { + /* Zero is a default in platform PMEM structures in the board files, + * when the "allocator_type" structure element is not explicitly + * defined + */ + PMEM_ALLOCATORTYPE_BITMAP = 0, /* forced to be zero here */ + PMEM_ALLOCATORTYPE_SYSTEM, + + PMEM_ALLOCATORTYPE_ALLORNOTHING, + PMEM_ALLOCATORTYPE_BUDDYBESTFIT, + + PMEM_ALLOCATORTYPE_MAX, +}; + +#define PMEM_MEMTYPE_MASK 0x7 +#define PMEM_INVALID_MEMTYPE 0x0 +#define PMEM_MEMTYPE_EBI1 0x1 +#define PMEM_MEMTYPE_SMI 0x2 +#define PMEM_MEMTYPE_RESERVED_INVALID2 0x3 +#define PMEM_MEMTYPE_RESERVED_INVALID3 0x4 +#define PMEM_MEMTYPE_RESERVED_INVALID4 0x5 +#define PMEM_MEMTYPE_RESERVED_INVALID5 0x6 +#define PMEM_MEMTYPE_RESERVED_INVALID6 0x7 + +#define PMEM_ALIGNMENT_MASK 0x18 +#define PMEM_ALIGNMENT_RESERVED_INVALID1 0x0 +#define PMEM_ALIGNMENT_4K 0x8 /* the default */ +#define PMEM_ALIGNMENT_1M 0x10 +#define PMEM_ALIGNMENT_RESERVED_INVALID2 0x18 + +/* flags in the following function defined as above. */ +int32_t pmem_kalloc(const size_t size, const uint32_t flags); +int32_t pmem_kfree(const int32_t physaddr); + +/* kernel api names for board specific data structures */ +#define PMEM_KERNEL_EBI1_DATA_NAME "pmem_kernel_ebi1" +#define PMEM_KERNEL_SMI_DATA_NAME "pmem_kernel_smi" struct android_pmem_platform_data { const char* name; - /* starting physical address of memory region */ - unsigned long start; /* size of memory region */ unsigned long size; - /* set to indicate the region should not be managed with an allocator */ - unsigned no_allocator; + + enum pmem_allocator_type allocator_type; + /* treated as a 'hidden' variable in the board files. Can be + * set, but default is the system init value of 0 which becomes a + * quantum of 4K pages. + */ + unsigned int quantum; + /* set to indicate maps of this region should be cached, if a mix of * cached and uncached is desired, set this and open the device with * O_SYNC to get an uncached region */ unsigned cached; /* The MSM7k has bits to enable a write buffer in the bus controller*/ unsigned buffered; + /* This PMEM is on memory that may be powered off */ + unsigned unstable; + /* which memory type (i.e. SMI, EBI1) this PMEM device is backed by */ + unsigned memory_type; }; -struct pmem_region { - unsigned long offset; - unsigned long len; -}; - -#ifdef CONFIG_ANDROID_PMEM -int is_pmem_file(struct file *file); -int get_pmem_file(int fd, unsigned long *start, unsigned long *vstart, - unsigned long *end, struct file **filp); -int get_pmem_user_addr(struct file *file, unsigned long *start, - unsigned long *end); -void put_pmem_file(struct file* file); -void flush_pmem_file(struct file *file, unsigned long start, unsigned long len); int pmem_setup(struct android_pmem_platform_data *pdata, long (*ioctl)(struct file *, unsigned int, unsigned long), int (*release)(struct inode *, struct file *)); + int pmem_remap(struct pmem_region *region, struct file *file, unsigned operation); - -#else -static inline int is_pmem_file(struct file *file) { return 0; } -static inline int get_pmem_file(int fd, unsigned long *start, - unsigned long *vstart, unsigned long *end, - struct file **filp) { return -ENOSYS; } -static inline int get_pmem_user_addr(struct file *file, unsigned long *start, - unsigned long *end) { return -ENOSYS; } -static inline void put_pmem_file(struct file* file) { return; } -static inline void flush_pmem_file(struct file *file, unsigned long start, - unsigned long len) { return; } -static inline int pmem_setup(struct android_pmem_platform_data *pdata, - long (*ioctl)(struct file *, unsigned int, unsigned long), - int (*release)(struct inode *, struct file *)) { return -ENOSYS; } - -static inline int pmem_remap(struct pmem_region *region, struct file *file, - unsigned operation) { return -ENOSYS; } -#endif +#endif /* __KERNEL__ */ #endif //_ANDROID_PPP_H_ diff --git a/include/linux/ashmem.h b/include/linux/ashmem.h index 1976b10ef93..c05c180d404 100644 --- a/include/linux/ashmem.h +++ b/include/linux/ashmem.h @@ -44,5 +44,10 @@ struct ashmem_pin { #define ASHMEM_UNPIN _IOW(__ASHMEMIOC, 8, struct ashmem_pin) #define ASHMEM_GET_PIN_STATUS _IO(__ASHMEMIOC, 9) #define ASHMEM_PURGE_ALL_CACHES _IO(__ASHMEMIOC, 10) +#define ASHMEM_CACHE_FLUSH_RANGE _IO(__ASHMEMIOC, 11) + +int get_ashmem_file(int fd, struct file **filp, struct file **vm_file, + unsigned long *len); +void put_ashmem_file(struct file *file); #endif /* _LINUX_ASHMEM_H */ diff --git a/include/linux/atmel_maxtouch.h b/include/linux/atmel_maxtouch.h new file mode 100644 index 00000000000..c582529ed4f --- /dev/null +++ b/include/linux/atmel_maxtouch.h @@ -0,0 +1,317 @@ +/* + * Atmel maXTouch header file + * + * Copyright (c) 2010 Atmel Corporation + * Copyright (C) 2010 Ulf Samuelsson (ulf@atmel.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. + * See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +#define MXT224_I2C_ADDR1 0x4A +#define MXT224_I2C_ADDR2 0x4B +#define MXT1386_I2C_ADDR1 0x4C +#define MXT1386_I2C_ADDR2 0x4D +#define MXT1386_I2C_ADDR3 0x5A +#define MXT1386_I2C_ADDR4 0x5B + +/* + * Select this address from above depending on what maXTouch + * chip you have and how it's address pins are configured; + * see datasheet. + */ + +#define MXT_I2C_ADDRESS MXT224_I2C_ADDR2 + +#define MXT_BL_ADDRESS 0x25 + +#define MXT224_FAMILYID 0x80 +#define MXT1386_FAMILYID 0xA0 + +#define MXT224_CAL_VARIANTID 0x01 +#define MXT224_UNCAL_VARIANTID 0x00 +#define MXT1386_CAL_VARIANTID 0x00 + +#define MXT_MAX_REPORTED_WIDTH 255 +#define MXT_MAX_REPORTED_PRESSURE 255 +#define MXT_MAX_TOUCH_SIZE 255 +#define MXT_MAX_NUM_TOUCHES 10 + +/* Fixed addresses inside maXTouch device */ +#define MXT_ADDR_INFO_BLOCK 0 +#define MXT_ADDR_OBJECT_TABLE 7 +#define MXT_ID_BLOCK_SIZE 7 +#define MXT_OBJECT_TABLE_ELEMENT_SIZE 6 + +/* Object types */ +#define MXT_DEBUG_DELTAS_T2 2 +#define MXT_DEBUG_REFERENCES_T3 3 +#define MXT_GEN_MESSAGEPROCESSOR_T5 5 +#define MXT_GEN_COMMANDPROCESSOR_T6 6 +#define MXT_GEN_POWERCONFIG_T7 7 +#define MXT_GEN_ACQUIRECONFIG_T8 8 +#define MXT_TOUCH_MULTITOUCHSCREEN_T9 9 +#define MXT_TOUCH_SINGLETOUCHSCREEN_T10 10 +#define MXT_TOUCH_XSLIDER_T11 11 +#define MXT_TOUCH_YSLIDER_T12 12 +#define MXT_TOUCH_XWHEEL_T13 13 +#define MXT_TOUCH_YWHEEL_T14 14 +#define MXT_TOUCH_KEYARRAY_T15 15 +#define MXT_SPT_GPIOPWM_T19 19 +#define MXT_PROCI_GRIPFACESUPPRESSION_T20 20 +#define MXT_PROCG_NOISESUPPRESSION_T22 22 +#define MXT_TOUCH_PROXIMITY_T23 23 +#define MXT_PROCI_ONETOUCHGESTUREPROCESSOR_T24 24 +#define MXT_SPT_SELFTEST_T25 25 +#define MXT_DEBUG_CTERANGE_T26 26 +#define MXT_PROCI_TWOTOUCHGESTUREPROCESSOR_T27 27 +#define MXT_SPT_CTECONFIG_T28 28 +#define MXT_TOUCH_KEYSET_T31 31 +#define MXT_TOUCH_XSLIDERSET_T32 32 +#define MXT_DEBUG_DIAGNOSTIC_T37 37 +#define MXT_USER_INFO_T38 38 + + +/* + * If a message is read from mXT when there's no new messages available, + * the report ID of the message will be 0xFF. + */ +#define MXT_END_OF_MESSAGES 0xFF + + +/* GEN_COMMANDPROCESSOR_T6 Register offsets from T6 base address */ +#define MXT_ADR_T6_RESET 0x00 +#define MXT_ADR_T6_BACKUPNV 0x01 +#define MXT_ADR_T6_CALIBRATE 0x02 +#define MXT_ADR_T6_REPORTALL 0x03 +#define MXT_ADR_T6_RESERVED 0x04 +#define MXT_ADR_T6_DIAGNOSTIC 0x05 + +/* T6 Debug Diagnostics Commands */ +#define MXT_CMD_T6_PAGE_UP 0x01 +#define MXT_CMD_T6_PAGE_DOWN 0x02 +#define MXT_CMD_T6_DELTAS_MODE 0x10 +#define MXT_CMD_T6_REFERENCES_MODE 0x11 +#define MXT_CMD_T6_CTE_MODE 0x31 + +/* T6 Backup Command */ +#define MXT_CMD_T6_BACKUP 0x55 + +/* SPT_DEBUG_DIAGNOSTIC_T37 Register offsets from T37 base address */ +#define MXT_ADR_T37_PAGE 0x01 +#define MXT_ADR_T37_DATA 0x02 + + + +/************************************************************************ + * MESSAGE OBJECTS ADDRESS FIELDS + * + ************************************************************************/ +#define MXT_MSG_REPORTID 0x00 + + +/* MXT_GEN_MESSAGEPROCESSOR_T5 Message address definitions */ +#define MXT_MSG_T5_REPORTID 0x00 +#define MXT_MSG_T5_MESSAGE 0x01 +#define MXT_MSG_T5_CHECKSUM 0x08 + +/* MXT_GEN_COMMANDPROCESSOR_T6 Message address definitions */ +#define MXT_MSG_T6_STATUS 0x01 +#define MXT_MSGB_T6_COMSERR 0x04 +#define MXT_MSGB_T6_CFGERR 0x08 +#define MXT_MSGB_T6_CAL 0x10 +#define MXT_MSGB_T6_SIGERR 0x20 +#define MXT_MSGB_T6_OFL 0x40 +#define MXT_MSGB_T6_RESET 0x80 +/* Three bytes */ +#define MXT_MSG_T6_CHECKSUM 0x02 + +/* MXT_GEN_POWERCONFIG_T7 NO Message address definitions */ +/* MXT_GEN_ACQUIRECONFIG_T8 Message address definitions */ +/* MXT_TOUCH_MULTITOUCHSCREEN_T9 Message address definitions */ + +#define MXT_MSG_T9_STATUS 0x01 +/* Status bit field */ +#define MXT_MSGB_T9_SUPPRESS 0x02 +#define MXT_MSGB_T9_AMP 0x04 +#define MXT_MSGB_T9_VECTOR 0x08 +#define MXT_MSGB_T9_MOVE 0x10 +#define MXT_MSGB_T9_RELEASE 0x20 +#define MXT_MSGB_T9_PRESS 0x40 +#define MXT_MSGB_T9_DETECT 0x80 + +#define MXT_MSG_T9_XPOSMSB 0x02 +#define MXT_MSG_T9_YPOSMSB 0x03 +#define MXT_MSG_T9_XYPOSLSB 0x04 +#define MXT_MSG_T9_TCHAREA 0x05 +#define MXT_MSG_T9_TCHAMPLITUDE 0x06 +#define MXT_MSG_T9_TCHVECTOR 0x07 + + +/* MXT_SPT_GPIOPWM_T19 Message address definitions */ +#define MXT_MSG_T19_STATUS 0x01 + +/* MXT_PROCI_GRIPFACESUPPRESSION_T20 Message address definitions */ +#define MXT_MSG_T20_STATUS 0x01 +#define MXT_MSGB_T20_FACE_SUPPRESS 0x01 +/* MXT_PROCG_NOISESUPPRESSION_T22 Message address definitions */ +#define MXT_MSG_T22_STATUS 0x01 +#define MXT_MSGB_T22_FHCHG 0x01 +#define MXT_MSGB_T22_GCAFERR 0x04 +#define MXT_MSGB_T22_FHERR 0x08 +#define MXT_MSG_T22_GCAFDEPTH 0x02 + +/* MXT_TOUCH_PROXIMITY_T23 Message address definitions */ +#define MXT_MSG_T23_STATUS 0x01 +#define MXT_MSGB_T23_FALL 0x20 +#define MXT_MSGB_T23_RISE 0x40 +#define MXT_MSGB_T23_DETECT 0x80 +/* 16 bit */ +#define MXT_MSG_T23_PROXDELTA 0x02 + +/* MXT_PROCI_ONETOUCHGESTUREPROCESSOR_T24 Message address definitions */ +#define MXT_MSG_T24_STATUS 0x01 +#define MXT_MSG_T24_XPOSMSB 0x02 +#define MXT_MSG_T24_YPOSMSB 0x03 +#define MXT_MSG_T24_XYPOSLSB 0x04 +#define MXT_MSG_T24_DIR 0x05 +/* 16 bit */ +#define MXT_MSG_T24_DIST 0x06 + +/* MXT_SPT_SELFTEST_T25 Message address definitions */ +#define MXT_MSG_T25_STATUS 0x01 +/* 5 Bytes */ +#define MXT_MSGR_T25_OK 0xFE +#define MXT_MSGR_T25_INVALID_TEST 0xFD +#define MXT_MSGR_T25_PIN_FAULT 0x11 +#define MXT_MSGR_T25_SIGNAL_LIMIT_FAULT 0x17 +#define MXT_MSGR_T25_GAIN_ERROR 0x20 +#define MXT_MSG_T25_INFO 0x02 + +/* MXT_PROCI_TWOTOUCHGESTUREPROCESSOR_T27 Message address definitions */ +#define MXT_MSG_T27_STATUS 0x01 +#define MXT_MSGB_T27_ROTATEDIR 0x10 +#define MXT_MSGB_T27_PINCH 0x20 +#define MXT_MSGB_T27_ROTATE 0x40 +#define MXT_MSGB_T27_STRETCH 0x80 +#define MXT_MSG_T27_XPOSMSB 0x02 +#define MXT_MSG_T27_YPOSMSB 0x03 +#define MXT_MSG_T27_XYPOSLSB 0x04 +#define MXT_MSG_T27_ANGLE 0x05 + +/* 16 bit */ +#define MXT_MSG_T27_SEPARATION 0x06 + +/* MXT_SPT_CTECONFIG_T28 Message address definitions */ +#define MXT_MSG_T28_STATUS 0x01 +#define MXT_MSGB_T28_CHKERR 0x01 + + +/* One Touch Events */ +#define MXT_GESTURE_RESERVED 0x00 +#define MXT_GESTURE_PRESS 0x01 +#define MXT_GESTURE_RELEASE 0x02 +#define MXT_GESTURE_TAP 0x03 +#define MXT_GESTURE_DOUBLE_TAP 0x04 +#define MXT_GESTURE_FLICK 0x05 +#define MXT_GESTURE_DRAG 0x06 +#define MXT_GESTURE_SHORT_PRESS 0x07 +#define MXT_GESTURE_LONG_PRESS 0x08 +#define MXT_GESTURE_REPEAT_PRESS 0x09 +#define MXT_GESTURE_TAP_AND_PRESS 0x0a +#define MXT_GESTURE_THROW 0x0b + +/* Two-touch events */ +#define MXT_GESTURE_STRETCH (1 << 7) +#define MXT_GESTURE_ROTATE (1 << 6) +#define MXT_GESTURE_PINCH (1 << 5) +#define MXT_GESTURE_ROTATEDIR (1 << 4) + + + +/* Bootloader states */ +#define WAITING_BOOTLOAD_COMMAND 0xC0 +#define WAITING_FRAME_DATA 0x80 +#define APP_CRC_FAIL 0x40 +#define FRAME_CRC_CHECK 0x02 +#define FRAME_CRC_PASS 0x04 +#define FRAME_CRC_FAIL 0x03 + +#define MXT_MAX_FRAME_SIZE 276 + +/* Debug levels */ +#define DEBUG_INFO 1 +#define DEBUG_VERBOSE 2 +#define DEBUG_MESSAGES 5 +#define DEBUG_RAW 8 +#define DEBUG_TRACE 10 + +/* IOCTL commands */ +/* TODO: get correct numbers! */ +#define MXT_SET_ADDRESS_IOCTL ('x' + 1) /* Sets the internal address pointer */ +#define MXT_RESET_IOCTL ('x' + 2) /* Resets the device */ +#define MXT_CALIBRATE_IOCTL ('x' + 3) /* Calibrates the device */ +/* Backups the current state of registers to NVM */ +#define MXT_BACKUP_IOCTL ('x' + 4) +/* + * Only non-touch messages can be read from the message buffer + * (/dev/maXTouch_messages) + */ +#define MXT_NONTOUCH_MSG_IOCTL ('x' + 5) +/* All messages can be read from the message buffer */ +#define MXT_ALL_MSG_IOCTL ('x' + 6) + + +/* Message buffer size. This is a ring buffer, and when full, the oldest entry + will be overwritten. */ +#define MXT_MESSAGE_BUFFER_SIZE 128 + +/* Routines for memory access within a 16 bit address space */ + +/* TODO: - won't compile if functions aren't defined*/ +/* Bootloader specific function prototypes. */ + +#if 0 +static int mxt_read_byte_bl(struct i2c_client *client, u8 *value); +static int mxt_read_block_bl(struct i2c_client *client, u16 length, u8 *value); +static int mxt_write_byte_bl(struct i2c_client *client, u8 value); +static int mxt_write_block_bl(struct i2c_client *client, u16 length, u8 *value); +#endif + +/** + * struct mxt_platform_data - includes platform specific informatio + * related to Atmel maXTouch touchscreen controller. + * + * @numtouch: Number of simultaneous touches supported + * @init_platform_hw(): Initialization function, which can for example + * trigger a hardware reset by toggling a GPIO pin + * @exit_platform_hw(): Function to run when the driver is unloaded. + * @valid_interrupt(): Function that checks the validity of the interrupt - + * function that check the validity of a interrupt (by + * reading the changeline interrupt pin and checking that + * it really is low for example). + * @max_x: Reported X range + * @max_y: Reported Y range + */ + +struct mxt_platform_data { + u8 numtouch; /* Number of touches to report */ + int (*init_platform_hw)(struct i2c_client *client); + int (*exit_platform_hw)(struct i2c_client *client); + int display_res_x; + int display_res_y; + int min_x; + int min_y; + int max_x; /* The default reported X range */ + int max_y; /* The default reported Y range */ + u8 (*valid_interrupt) (void); + u8 (*read_chg) (void); + bool wakeup; + int (*power_on)(bool on); +}; + +void mxt_hw_reset(void); diff --git a/include/linux/bitmap.h b/include/linux/bitmap.h index dcafe0bf000..50e2c16b20f 100644 --- a/include/linux/bitmap.h +++ b/include/linux/bitmap.h @@ -45,6 +45,7 @@ * bitmap_set(dst, pos, nbits) Set specified bit area * bitmap_clear(dst, pos, nbits) Clear specified bit area * bitmap_find_next_zero_area(buf, len, pos, n, mask) Find bit free area + * bitmap_find_next_zero_area_off(buf, len, pos, n, mask) as above * bitmap_shift_right(dst, src, n, nbits) *dst = *src >> n * bitmap_shift_left(dst, src, n, nbits) *dst = *src << n * bitmap_remap(dst, src, old, new, nbits) *dst = map(old, new)(src) @@ -114,11 +115,24 @@ extern int __bitmap_weight(const unsigned long *bitmap, int bits); extern void bitmap_set(unsigned long *map, int i, int len); extern void bitmap_clear(unsigned long *map, int start, int nr); -extern unsigned long bitmap_find_next_zero_area(unsigned long *map, - unsigned long size, - unsigned long start, - unsigned int nr, - unsigned long align_mask); + +extern unsigned long bitmap_find_next_zero_area_off(unsigned long *map, + unsigned long size, + unsigned long start, + unsigned int nr, + unsigned long align_mask, + unsigned long align_offset); + +static inline unsigned long +bitmap_find_next_zero_area(unsigned long *map, + unsigned long size, + unsigned long start, + unsigned int nr, + unsigned long align_mask) +{ + return bitmap_find_next_zero_area_off(map, size, start, nr, + align_mask, 0); +} extern int bitmap_scnprintf(char *buf, unsigned int len, const unsigned long *src, int nbits); diff --git a/include/linux/bma150.h b/include/linux/bma150.h new file mode 100644 index 00000000000..a3d1c4fde7d --- /dev/null +++ b/include/linux/bma150.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef LINUX_BMA150_MODULE_H +#define LINUX_BMA150_MODULE_H + +/** + * struct bma150_platform_data - data to set up bma150 driver + * + * @setup: optional callback to activate the driver. + * @teardown: optional callback to invalidate the driver. + * +**/ + +struct bma150_platform_data { + int (*setup)(struct device *); + void (*teardown)(struct device *); + int (*power_on)(void); + void (*power_off)(void); +}; + +#endif /* LINUX_BMA150_MODULE_H */ diff --git a/include/linux/completion.h b/include/linux/completion.h index 51494e6b554..a5b2e1ca5ce 100644 --- a/include/linux/completion.h +++ b/include/linux/completion.h @@ -77,6 +77,7 @@ static inline void init_completion(struct completion *x) } extern void wait_for_completion(struct completion *); +extern void wait_for_completion_io(struct completion *); extern int wait_for_completion_interruptible(struct completion *x); extern int wait_for_completion_killable(struct completion *x); extern unsigned long wait_for_completion_timeout(struct completion *x, diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index ae06dc9a0cd..957c5b414be 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -199,6 +199,8 @@ extern int __cpufreq_driver_getavg(struct cpufreq_policy *policy, int cpufreq_register_governor(struct cpufreq_governor *governor); void cpufreq_unregister_governor(struct cpufreq_governor *governor); +int lock_policy_rwsem_write(int cpu); +void unlock_policy_rwsem_write(int cpu); /********************************************************************* * CPUFREQ DRIVER INTERFACE * diff --git a/include/linux/csdio.h b/include/linux/csdio.h new file mode 100644 index 00000000000..260c49d6082 --- /dev/null +++ b/include/linux/csdio.h @@ -0,0 +1,37 @@ +#ifndef CSDIO_H +#define CSDIO_H + +#include + +#define CSDIO_IOC_MAGIC 'm' + +#define CSDIO_IOC_ENABLE_HIGHSPEED_MODE _IO(CSDIO_IOC_MAGIC, 0) +#define CSDIO_IOC_SET_DATA_TRANSFER_CLOCKS _IO(CSDIO_IOC_MAGIC, 1) +#define CSDIO_IOC_SET_OP_CODE _IO(CSDIO_IOC_MAGIC, 2) +#define CSDIO_IOC_FUNCTION_SET_BLOCK_SIZE _IO(CSDIO_IOC_MAGIC, 3) +#define CSDIO_IOC_SET_BLOCK_MODE _IO(CSDIO_IOC_MAGIC, 4) +#define CSDIO_IOC_CONNECT_ISR _IO(CSDIO_IOC_MAGIC, 5) +#define CSDIO_IOC_DISCONNECT_ISR _IO(CSDIO_IOC_MAGIC, 6) +#define CSDIO_IOC_CMD52 _IO(CSDIO_IOC_MAGIC, 7) +#define CSDIO_IOC_CMD53 _IO(CSDIO_IOC_MAGIC, 8) +#define CSDIO_IOC_ENABLE_ISR _IO(CSDIO_IOC_MAGIC, 9) +#define CSDIO_IOC_DISABLE_ISR _IO(CSDIO_IOC_MAGIC, 10) +#define CSDIO_IOC_SET_VDD _IO(CSDIO_IOC_MAGIC, 11) +#define CSDIO_IOC_GET_VDD _IO(CSDIO_IOC_MAGIC, 12) + +#define CSDIO_IOC_MAXNR 12 + +struct csdio_cmd53_ctrl_t { + uint32_t m_block_mode; /* data tran. byte(0)/block(1) mode */ + uint32_t m_op_code; /* address auto increment flag */ + uint32_t m_address; +} __attribute__ ((packed)); + +struct csdio_cmd52_ctrl_t { + uint32_t m_write; + uint32_t m_address; + uint32_t m_data; + uint32_t m_ret; +} __attribute__ ((packed)); + +#endif diff --git a/include/linux/cyttsp.h b/include/linux/cyttsp.h new file mode 100644 index 00000000000..0644f1a7036 --- /dev/null +++ b/include/linux/cyttsp.h @@ -0,0 +1,679 @@ +/* Header file for: + * Cypress TrueTouch(TM) Standard Product touchscreen drivers. + * include/linux/cyttsp.h + * + * Copyright (C) 2009, 2010 Cypress Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Cypress reserves the right to make changes without further notice + * to the materials described herein. Cypress does not assume any + * liability arising out of the application described herein. + * + * Contact Cypress Semiconductor at www.cypress.com + * + */ + + +#ifndef __CYTTSP_H__ +#define __CYTTSP_H__ + +#include +#include +#include +#include +#include + +#include + +#define CYPRESS_TTSP_NAME "cyttsp" +#define CY_I2C_NAME "cyttsp-i2c" +#define CY_SPI_NAME "cyttsp-spi" + +#ifdef CY_DECLARE_GLOBALS + uint32_t cyttsp_tsdebug; + module_param_named(tsdebug, cyttsp_tsdebug, uint, 0664); + uint32_t cyttsp_tsxdebug; + module_param_named(tsxdebug, cyttsp_tsxdebug, uint, 0664); + + uint32_t cyttsp_disable_touch; + module_param_named(disable_touch, cyttsp_disable_touch, uint, 0664); +#else + extern uint32_t cyttsp_tsdebug; + extern uint32_t cyttsp_tsxdebug; + extern uint32_t cyttsp_disable_touch; +#endif + + + +/****************************************************************************** + * Global Control, Used to control the behavior of the driver + */ + +/* defines for Gen2 (Txx2xx); Gen3 (Txx3xx) + * use these defines to set cyttsp_platform_data.gen in board config file + */ +#define CY_GEN2 2 +#define CY_GEN3 3 + +/* define for using I2C driver + */ +#define CY_USE_I2C_DRIVER + +/* defines for using SPI driver */ +/* +#define CY_USE_SPI_DRIVER + */ +#define CY_SPI_DFLT_SPEED_HZ 1000000 +#define CY_SPI_MAX_SPEED_HZ 4000000 +#define CY_SPI_SPEED_HZ CY_SPI_DFLT_SPEED_HZ +#define CY_SPI_BITS_PER_WORD 8 +#define CY_SPI_DAV 139 /* set correct gpio id */ +#define CY_SPI_BUFSIZE 512 + +/* Voltage and Current ratings */ +#define CY_TMA300_VTG_MAX_UV 5500000 +#define CY_TMA300_VTG_MIN_UV 1710000 +#define CY_TMA300_CURR_24HZ_UA 17500 +#define CY_I2C_VTG_MAX_UV 1800000 +#define CY_I2C_VTG_MIN_UV 1800000 +#define CY_I2C_CURR_UA 9630 + + +/* define for inclusion of TTSP App Update Load File + * use this define if update to the TTSP Device is desired + */ +/* +#define CY_INCLUDE_LOAD_FILE +*/ + +/* define if force new load file for bootloader load */ +/* +#define CY_FORCE_FW_UPDATE +*/ + +/* undef for production use */ +/* +#define CY_USE_DEBUG +*/ + +/* undef for irq use; use this define in the board configuration file */ +/* +#define CY_USE_TIMER + */ + +/* undef to allow use of extra debug capability */ +/* +#define CY_ALLOW_EXTRA_DEBUG +*/ + +/* undef to remove additional debug prints */ +/* +#define CY_USE_EXTRA_DEBUG +*/ + +/* undef to remove additional debug prints */ +/* +#define CY_USE_EXTRA_DEBUG1 + */ + +/* undef to use operational touch timer jiffies; else use test jiffies */ +/* + */ +#define CY_USE_TIMER_DEBUG + +/* define to use canned test data */ +/* +#define CY_USE_TEST_DATA + */ + +/* define to activate power management */ +/* +#define CY_USE_LOW_POWER + */ + +/* define if wake on i2c addr is activated */ +/* +#define CY_USE_DEEP_SLEEP + */ + +/* define if gesture signaling is used + * and which gesture groups to use + */ +/* +#define CY_USE_GEST +#define CY_USE_GEST_GRP1 +#define CY_USE_GEST_GRP2 +#define CY_USE_GEST_GRP3 +#define CY_USE_GEST_GRP4 + */ +/* Active distance in pixels for a gesture to be reported + * if set to 0, then all gesture movements are reported + */ +#define CY_ACT_DIST_DFLT 8 +#define CY_ACT_DIST CY_ACT_DIST_DFLT + +/* define if MT signals are desired */ +/* +*/ +#define CY_USE_MT_SIGNALS + +/* define if MT tracking id signals are used */ +/* +#define CY_USE_MT_TRACK_ID + */ + +/* define if ST signals are required */ +/* +*/ +#define CY_USE_ST_SIGNALS + +/* define to send handshake to device */ +/* +*/ +#define CY_USE_HNDSHK + +/* define if log all raw motion signals to a sysfs file */ +/* +#define CY_LOG_TO_FILE +*/ + + +/* End of the Global Control section + ****************************************************************************** + */ +#define CY_DIFF(m, n) ((m) != (n)) + +#ifdef CY_LOG_TO_FILE + #define cyttsp_openlog() /* use sysfs */ +#else + #define cyttsp_openlog() +#endif /* CY_LOG_TO_FILE */ + +/* see kernel.h for pr_xxx def'ns */ +#define cyttsp_info(f, a...) pr_info("%s:" f, __func__ , ## a) +#define cyttsp_error(f, a...) pr_err("%s:" f, __func__ , ## a) +#define cyttsp_alert(f, a...) pr_alert("%s:" f, __func__ , ## a) + +#ifdef CY_USE_DEBUG + #define cyttsp_debug(f, a...) pr_alert("%s:" f, __func__ , ## a) +#else + #define cyttsp_debug(f, a...) {if (cyttsp_tsdebug) \ + pr_alert("%s:" f, __func__ , ## a); } +#endif /* CY_USE_DEBUG */ + +#ifdef CY_ALLOW_EXTRA_DEBUG +#ifdef CY_USE_EXTRA_DEBUG + #define cyttsp_xdebug(f, a...) pr_alert("%s:" f, __func__ , ## a) +#else + #define cyttsp_xdebug(f, a...) {if (cyttsp_tsxdebug) \ + pr_alert("%s:" f, __func__ , ## a); } +#endif /* CY_USE_EXTRA_DEBUG */ + +#ifdef CY_USE_EXTRA_DEBUG1 + #define cyttsp_xdebug1(f, a...) pr_alert("%s:" f, __func__ , ## a) +#else + #define cyttsp_xdebug1(f, a...) +#endif /* CY_USE_EXTRA_DEBUG1 */ +#else + #define cyttsp_xdebug(f, a...) + #define cyttsp_xdebug1(f, a...) +#endif /* CY_ALLOW_EXTRA_DEBUG */ + +#ifdef CY_USE_TIMER_DEBUG + #define TOUCHSCREEN_TIMEOUT (msecs_to_jiffies(1000)) +#else + #define TOUCHSCREEN_TIMEOUT (msecs_to_jiffies(28)) +#endif + +/* reduce extra signals in MT only build + * be careful not to lose backward compatibility for pre-MT apps + */ +#ifdef CY_USE_ST_SIGNALS + #define CY_USE_ST 1 +#else + #define CY_USE_ST 0 +#endif /* CY_USE_ST_SIGNALS */ + +/* rely on kernel input.h to define Multi-Touch capability */ +/* if input.h defines the Multi-Touch signals, then use MT */ +#if defined(ABS_MT_TOUCH_MAJOR) && defined(CY_USE_MT_SIGNALS) + #define CY_USE_MT 1 + #define CY_MT_SYNC(input) input_mt_sync(input) +#else + #define CY_USE_MT 0 + #define CY_MT_SYNC(input) + /* the following includes are provided to ensure a compile; + * the code that compiles with these defines will not be executed if + * the CY_USE_MT is properly used in the platform structure init + */ + #ifndef ABS_MT_TOUCH_MAJOR + #define ABS_MT_TOUCH_MAJOR 0x30 /* touching ellipse */ + #define ABS_MT_TOUCH_MINOR 0x31 /* (omit if circular) */ + #define ABS_MT_WIDTH_MAJOR 0x32 /* approaching ellipse */ + #define ABS_MT_WIDTH_MINOR 0x33 /* (omit if circular) */ + #define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ + #define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */ + #define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */ + #define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */ + #define ABS_MT_BLOB_ID 0x38 /* Group set of pkts as blob */ + #endif /* ABS_MT_TOUCH_MAJOR */ +#endif /* ABS_MT_TOUCH_MAJOR and CY_USE_MT_SIGNALS */ +#if defined(ABS_MT_TRACKING_ID) && defined(CY_USE_MT_TRACK_ID) + #define CY_USE_TRACKING_ID 1 +#else + #define CY_USE_TRACKING_ID 0 +/* define only if not defined already by system; + * value based on linux kernel 2.6.30.10 + */ +#ifndef ABS_MT_TRACKING_ID + #define ABS_MT_TRACKING_ID (ABS_MT_BLOB_ID+1) +#endif +#endif /* ABS_MT_TRACKING_ID */ + +#ifdef CY_USE_DEEP_SLEEP + #define CY_USE_DEEP_SLEEP_SEL 0x80 +#else + #define CY_USE_DEEP_SLEEP_SEL 0x00 +#endif +#ifdef CY_USE_LOW_POWER + #define CY_USE_SLEEP (CY_USE_DEEP_SLEEP_SEL | 0x01) +#else + #define CY_USE_SLEEP 0x00 +#endif /* CY_USE_LOW_POWER */ + +#ifdef CY_USE_TEST_DATA + #define cyttsp_testdat(ray1, ray2, sizeofray) \ + { \ + int i; \ + u8 *up1 = (u8 *)ray1; \ + u8 *up2 = (u8 *)ray2; \ + for (i = 0; i < sizeofray; i++) { \ + up1[i] = up2[i]; \ + } \ + } +#else + #define cyttsp_testdat(xy, test_xy, sizeofray) +#endif /* CY_USE_TEST_DATA */ + +/* helper macros */ +#define GET_NUM_TOUCHES(x) ((x) & 0x0F) +#define GET_TOUCH1_ID(x) (((x) & 0xF0) >> 4) +#define GET_TOUCH2_ID(x) ((x) & 0x0F) +#define GET_TOUCH3_ID(x) (((x) & 0xF0) >> 4) +#define GET_TOUCH4_ID(x) ((x) & 0x0F) +#define IS_LARGE_AREA(x) (((x) & 0x10) >> 4) +#define FLIP_DATA_FLAG 0x01 +#define REVERSE_X_FLAG 0x02 +#define REVERSE_Y_FLAG 0x04 +#define FLIP_DATA(flags) ((flags) & FLIP_DATA_FLAG) +#define REVERSE_X(flags) ((flags) & REVERSE_X_FLAG) +#define REVERSE_Y(flags) ((flags) & REVERSE_Y_FLAG) +#define FLIP_XY(x, y) { \ + u16 tmp; \ + tmp = (x); \ + (x) = (y); \ + (y) = tmp; \ + } +#define INVERT_X(x, xmax) ((xmax) - (x)) +#define INVERT_Y(y, maxy) ((maxy) - (y)) +#define SET_HSTMODE(reg, mode) ((reg) & (mode)) +#define GET_HSTMODE(reg) ((reg & 0x70) >> 4) +#define GET_BOOTLOADERMODE(reg) ((reg & 0x10) >> 4) + +/* constant definitions */ +/* maximum number of concurrent ST track IDs */ +#define CY_NUM_ST_TCH_ID 2 + +/* maximum number of concurrent MT track IDs */ +#define CY_NUM_MT_TCH_ID 4 + +/* maximum number of track IDs */ +#define CY_NUM_TRK_ID 16 + +#define CY_NTCH 0 /* no touch (lift off) */ +#define CY_TCH 1 /* active touch (touchdown) */ +#define CY_ST_FNGR1_IDX 0 +#define CY_ST_FNGR2_IDX 1 +#define CY_MT_TCH1_IDX 0 +#define CY_MT_TCH2_IDX 1 +#define CY_MT_TCH3_IDX 2 +#define CY_MT_TCH4_IDX 3 +#define CY_XPOS 0 +#define CY_YPOS 1 +#define CY_IGNR_TCH (-1) +#define CY_SMALL_TOOL_WIDTH 10 +#define CY_LARGE_TOOL_WIDTH 255 +#define CY_REG_BASE 0x00 +#define CY_REG_GEST_SET 0x1E +#define CY_REG_ACT_INTRVL 0x1D +#define CY_REG_TCH_TMOUT (CY_REG_ACT_INTRVL+1) +#define CY_REG_LP_INTRVL (CY_REG_TCH_TMOUT+1) +#define CY_SOFT_RESET ((1 << 0)) +#define CY_DEEP_SLEEP ((1 << 1)) +#define CY_LOW_POWER ((1 << 2)) +#define CY_MAXZ 255 +#define CY_OK 0 +#define CY_INIT 1 +#define CY_DLY_DFLT 10 /* ms */ +#define CY_DLY_SYSINFO 20 /* ms */ +#define CY_DLY_BL 300 +#define CY_DLY_DNLOAD 100 /* ms */ +#define CY_NUM_RETRY 4 /* max num touch data read */ + +/* handshake bit in the hst_mode reg */ +#define CY_HNDSHK_BIT 0x80 +#ifdef CY_USE_HNDSHK + #define CY_SEND_HNDSHK 1 +#else + #define CY_SEND_HNDSHK 0 +#endif + +/* Bootloader File 0 offset */ +#define CY_BL_FILE0 0x00 + +/* Bootloader command directive */ +#define CY_BL_CMD 0xFF + +/* Bootloader Initiate Bootload */ +#define CY_BL_INIT_LOAD 0x38 + +/* Bootloader Write a Block */ +#define CY_BL_WRITE_BLK 0x39 + +/* Bootloader Terminate Bootload */ +#define CY_BL_TERMINATE 0x3B + +/* Bootloader Exit and Verify Checksum command */ +#define CY_BL_EXIT 0xA5 + +/* Bootloader default keys */ +#define CY_BL_KEY0 0x00 +#define CY_BL_KEY1 0x01 +#define CY_BL_KEY2 0x02 +#define CY_BL_KEY3 0x03 +#define CY_BL_KEY4 0x04 +#define CY_BL_KEY5 0x05 +#define CY_BL_KEY6 0x06 +#define CY_BL_KEY7 0x07 + +/* Active Power state scanning/processing refresh interval */ +#define CY_ACT_INTRVL_DFLT 0x00 + +/* touch timeout for the Active power */ +#define CY_TCH_TMOUT_DFLT 0xFF + +/* Low Power state scanning/processing refresh interval */ +#define CY_LP_INTRVL_DFLT 0x0A + +#define CY_IDLE_STATE 0 +#define CY_ACTIVE_STATE 1 +#define CY_LOW_PWR_STATE 2 +#define CY_SLEEP_STATE 3 + +/* device mode bits */ +#define CY_OP_MODE 0x00 +#define CY_SYSINFO_MODE 0x10 + +/* power mode select bits */ +#define CY_SOFT_RESET_MODE 0x01 /* return to Bootloader mode */ +#define CY_DEEP_SLEEP_MODE 0x02 +#define CY_LOW_PWR_MODE 0x04 + +#define CY_NUM_KEY 8 + +#ifdef CY_USE_GEST + #define CY_USE_GESTURES 1 +#else + #define CY_USE_GESTURES 0 +#endif /* CY_USE_GESTURE_SIGNALS */ + +#ifdef CY_USE_GEST_GRP1 + #define CY_GEST_GRP1 0x10 +#else + #define CY_GEST_GRP1 0x00 +#endif /* CY_USE_GEST_GRP1 */ +#ifdef CY_USE_GEST_GRP2 + #define CY_GEST_GRP2 0x20 +#else + #define CY_GEST_GRP2 0x00 +#endif /* CY_USE_GEST_GRP2 */ +#ifdef CY_USE_GEST_GRP3 + #define CY_GEST_GRP3 0x40 +#else + #define CY_GEST_GRP3 0x00 +#endif /* CY_USE_GEST_GRP3 */ +#ifdef CY_USE_GEST_GRP4 + #define CY_GEST_GRP4 0x80 +#else + #define CY_GEST_GRP4 0x00 +#endif /* CY_USE_GEST_GRP4 */ + +struct cyttsp_regulator { + const char *name; + u32 min_uV; + u32 max_uV; + u32 load_uA; +}; + +struct cyttsp_platform_data { + u32 panel_maxx; + u32 panel_maxy; + u32 disp_resx; + u32 disp_resy; + u32 disp_minx; + u32 disp_miny; + u32 disp_maxx; + u32 disp_maxy; + u8 correct_fw_ver; + u32 flags; + u8 gen; + u8 use_st; + u8 use_mt; + u8 use_hndshk; + u8 use_trk_id; + u8 use_sleep; + u8 use_gestures; + u8 gest_set; + u8 act_intrvl; + u8 tch_tmout; + u8 lp_intrvl; + u8 power_state; + bool wakeup; + int sleep_gpio; + int resout_gpio; + int irq_gpio; + struct cyttsp_regulator *regulator_info; + u8 num_regulators; + const char *fw_fname; +#ifdef CY_USE_I2C_DRIVER + s32 (*init)(struct i2c_client *client); + s32 (*resume)(struct i2c_client *client); +#endif +#ifdef CY_USE_SPI_DRIVER + s32 (*init)(struct spi_device *spi); + s32 (*resume)(struct spi_device *spi); +#endif +}; + +/* TrueTouch Standard Product Gen3 (Txx3xx) interface definition */ +struct cyttsp_gen3_xydata_t { + u8 hst_mode; + u8 tt_mode; + u8 tt_stat; + u16 x1 __attribute__ ((packed)); + u16 y1 __attribute__ ((packed)); + u8 z1; + u8 touch12_id; + u16 x2 __attribute__ ((packed)); + u16 y2 __attribute__ ((packed)); + u8 z2; + u8 gest_cnt; + u8 gest_id; + u16 x3 __attribute__ ((packed)); + u16 y3 __attribute__ ((packed)); + u8 z3; + u8 touch34_id; + u16 x4 __attribute__ ((packed)); + u16 y4 __attribute__ ((packed)); + u8 z4; + u8 tt_undef[3]; + u8 gest_set; + u8 tt_reserved; +}; + +/* TrueTouch Standard Product Gen2 (Txx2xx) interface definition */ +#define CY_GEN2_NOTOUCH 0x03 /* Both touches removed */ +#define CY_GEN2_GHOST 0x02 /* ghost */ +#define CY_GEN2_2TOUCH 0x03 /* 2 touch; no ghost */ +#define CY_GEN2_1TOUCH 0x01 /* 1 touch only */ +#define CY_GEN2_TOUCH2 0x01 /* 1st touch removed; + * 2nd touch remains */ +struct cyttsp_gen2_xydata_t { + u8 hst_mode; + u8 tt_mode; + u8 tt_stat; + u16 x1 __attribute__ ((packed)); + u16 y1 __attribute__ ((packed)); + u8 z1; + u8 evnt_idx; + u16 x2 __attribute__ ((packed)); + u16 y2 __attribute__ ((packed)); + u8 tt_undef1; + u8 gest_cnt; + u8 gest_id; + u8 tt_undef[14]; + u8 gest_set; + u8 tt_reserved; +}; + +/* TTSP System Information interface definition */ +struct cyttsp_sysinfo_data_t { + u8 hst_mode; + u8 mfg_cmd; + u8 mfg_stat; + u8 cid[3]; + u8 tt_undef1; + u8 uid[8]; + u8 bl_verh; + u8 bl_verl; + u8 tts_verh; + u8 tts_verl; + u8 app_idh; + u8 app_idl; + u8 app_verh; + u8 app_verl; + u8 tt_undef[6]; + u8 act_intrvl; + u8 tch_tmout; + u8 lp_intrvl; +}; + +/* TTSP Bootloader Register Map interface definition */ +#define CY_BL_CHKSUM_OK 0x01 +struct cyttsp_bootloader_data_t { + u8 bl_file; + u8 bl_status; + u8 bl_error; + u8 blver_hi; + u8 blver_lo; + u8 bld_blver_hi; + u8 bld_blver_lo; + u8 ttspver_hi; + u8 ttspver_lo; + u8 appid_hi; + u8 appid_lo; + u8 appver_hi; + u8 appver_lo; + u8 cid_0; + u8 cid_1; + u8 cid_2; +}; + +#define cyttsp_wake_data_t cyttsp_gen3_xydata_t +#ifdef CY_DECLARE_GLOBALS + #ifdef CY_INCLUDE_LOAD_FILE + /* this file declares: + * firmware download block array (cyttsp_fw[]), + * the number of command block records (cyttsp_fw_records), + * and the version variables + */ + #include "cyttsp_fw.h" /* imports cyttsp_fw[] array */ + #define cyttsp_app_load() 1 + #ifdef CY_FORCE_FW_UPDATE + #define cyttsp_force_fw_load() 1 + #else + #define cyttsp_force_fw_load() 0 + #endif + + #else + /* the following declarations are to allow + * some debugging capability + */ + unsigned char cyttsp_fw_tts_verh = 0x00; + unsigned char cyttsp_fw_tts_verl = 0x01; + unsigned char cyttsp_fw_app_idh = 0x02; + unsigned char cyttsp_fw_app_idl = 0x03; + unsigned char cyttsp_fw_app_verh = 0x04; + unsigned char cyttsp_fw_app_verl = 0x05; + unsigned char cyttsp_fw_cid_0 = 0x06; + unsigned char cyttsp_fw_cid_1 = 0x07; + unsigned char cyttsp_fw_cid_2 = 0x08; + #define cyttsp_app_load() 0 + #define cyttsp_force_fw_load() 0 + #endif + #define cyttsp_tts_verh() cyttsp_fw_tts_verh + #define cyttsp_tts_verl() cyttsp_fw_tts_verl + #define cyttsp_app_idh() cyttsp_fw_app_idh + #define cyttsp_app_idl() cyttsp_fw_app_idl + #define cyttsp_app_verh() cyttsp_fw_app_verh + #define cyttsp_app_verl() cyttsp_fw_app_verl + #define cyttsp_cid_0() cyttsp_fw_cid_0 + #define cyttsp_cid_1() cyttsp_fw_cid_1 + #define cyttsp_cid_2() cyttsp_fw_cid_2 + #ifdef CY_USE_TEST_DATA + static struct cyttsp_gen2_xydata_t tt_gen2_testray[] = { + {0x00}, {0x00}, {0x04}, + {0x4000}, {0x8000}, {0x80}, + {0x03}, + {0x2000}, {0x1000}, {0x00}, + {0x00}, + {0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00}, + {0x00} + }; + + static struct cyttsp_gen3_xydata_t tt_gen3_testray[] = { + {0x00}, {0x00}, {0x04}, + {0x4000}, {0x8000}, {0x80}, + {0x12}, + {0x2000}, {0x1000}, {0xA0}, + {0x00}, {0x00}, + {0x8000}, {0x4000}, {0xB0}, + {0x34}, + {0x4000}, {0x1000}, {0xC0}, + {0x00, 0x00, 0x00}, + {0x00}, + {0x00} + }; + #endif /* CY_USE_TEST_DATA */ + +#else + extern u8 g_appload_ray[]; +#endif + +#endif /* __CYTTSP_H__ */ diff --git a/include/linux/debugobjects.h b/include/linux/debugobjects.h index 65970b811e2..0e5f5785d9f 100644 --- a/include/linux/debugobjects.h +++ b/include/linux/debugobjects.h @@ -46,6 +46,8 @@ struct debug_obj { * fails * @fixup_free: fixup function, which is called when the free check * fails + * @fixup_assert_init: fixup function, which is called when the assert_init + * check fails */ struct debug_obj_descr { const char *name; @@ -54,6 +56,7 @@ struct debug_obj_descr { int (*fixup_activate) (void *addr, enum debug_obj_state state); int (*fixup_destroy) (void *addr, enum debug_obj_state state); int (*fixup_free) (void *addr, enum debug_obj_state state); + int (*fixup_assert_init)(void *addr, enum debug_obj_state state); }; #ifdef CONFIG_DEBUG_OBJECTS @@ -64,6 +67,7 @@ extern void debug_object_activate (void *addr, struct debug_obj_descr *descr); extern void debug_object_deactivate(void *addr, struct debug_obj_descr *descr); extern void debug_object_destroy (void *addr, struct debug_obj_descr *descr); extern void debug_object_free (void *addr, struct debug_obj_descr *descr); +extern void debug_object_assert_init(void *addr, struct debug_obj_descr *descr); /* * Active state: @@ -89,6 +93,8 @@ static inline void debug_object_destroy (void *addr, struct debug_obj_descr *descr) { } static inline void debug_object_free (void *addr, struct debug_obj_descr *descr) { } +static inline void +debug_object_assert_init(void *addr, struct debug_obj_descr *descr) { } static inline void debug_objects_early_init(void) { } static inline void debug_objects_mem_init(void) { } diff --git a/include/linux/delay.h b/include/linux/delay.h index a6ecb34cf54..0e303d1aacd 100644 --- a/include/linux/delay.h +++ b/include/linux/delay.h @@ -47,6 +47,11 @@ void msleep(unsigned int msecs); unsigned long msleep_interruptible(unsigned int msecs); void usleep_range(unsigned long min, unsigned long max); +static inline void usleep(unsigned long usecs) +{ + usleep_range(usecs, usecs); +} + static inline void ssleep(unsigned int seconds) { msleep(seconds * 1000); diff --git a/include/linux/diagchar.h b/include/linux/diagchar.h new file mode 100644 index 00000000000..22270de0a70 --- /dev/null +++ b/include/linux/diagchar.h @@ -0,0 +1,589 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef DIAGCHAR_SHARED +#define DIAGCHAR_SHARED + +#define MSG_MASKS_TYPE 1 +#define LOG_MASKS_TYPE 2 +#define EVENT_MASKS_TYPE 4 +#define PKT_TYPE 8 +#define DEINIT_TYPE 16 +#define MEMORY_DEVICE_LOG_TYPE 32 +#define USB_MODE 1 +#define MEMORY_DEVICE_MODE 2 +#define NO_LOGGING_MODE 3 + +/* different values that go in for diag_data_type */ +#define DATA_TYPE_EVENT 0 +#define DATA_TYPE_F3 1 +#define DATA_TYPE_LOG 2 +#define DATA_TYPE_RESPONSE 3 + +/* Different IOCTL values */ +#define DIAG_IOCTL_COMMAND_REG 0 +#define DIAG_IOCTL_SWITCH_LOGGING 7 +#define DIAG_IOCTL_GET_DELAYED_RSP_ID 8 +#define DIAG_IOCTL_LSM_DEINIT 9 + +/* Machine ID and corresponding PC Tools IDs */ +#define APQ8060_MACHINE_ID 86 +#define AO8960_MACHINE_ID 87 +#define MSM8660_MACHINE_ID 71 +#define APQ8060_TOOLS_ID 4062 +#define AO8960_TOOLS_ID 4064 + +#define MSG_MASK_0 (0x00000001) +#define MSG_MASK_1 (0x00000002) +#define MSG_MASK_2 (0x00000004) +#define MSG_MASK_3 (0x00000008) +#define MSG_MASK_4 (0x00000010) +#define MSG_MASK_5 (0x00000020) +#define MSG_MASK_6 (0x00000040) +#define MSG_MASK_7 (0x00000080) +#define MSG_MASK_8 (0x00000100) +#define MSG_MASK_9 (0x00000200) +#define MSG_MASK_10 (0x00000400) +#define MSG_MASK_11 (0x00000800) +#define MSG_MASK_12 (0x00001000) +#define MSG_MASK_13 (0x00002000) +#define MSG_MASK_14 (0x00004000) +#define MSG_MASK_15 (0x00008000) +#define MSG_MASK_16 (0x00010000) +#define MSG_MASK_17 (0x00020000) +#define MSG_MASK_18 (0x00040000) +#define MSG_MASK_19 (0x00080000) +#define MSG_MASK_20 (0x00100000) +#define MSG_MASK_21 (0x00200000) +#define MSG_MASK_22 (0x00400000) +#define MSG_MASK_23 (0x00800000) +#define MSG_MASK_24 (0x01000000) +#define MSG_MASK_25 (0x02000000) +#define MSG_MASK_26 (0x04000000) +#define MSG_MASK_27 (0x08000000) +#define MSG_MASK_28 (0x10000000) +#define MSG_MASK_29 (0x20000000) +#define MSG_MASK_30 (0x40000000) +#define MSG_MASK_31 (0x80000000) + +/* These masks are to be used for support of all legacy messages in the sw. +The user does not need to remember the names as they will be embedded in +the appropriate macros. */ +#define MSG_LEGACY_LOW MSG_MASK_0 +#define MSG_LEGACY_MED MSG_MASK_1 +#define MSG_LEGACY_HIGH MSG_MASK_2 +#define MSG_LEGACY_ERROR MSG_MASK_3 +#define MSG_LEGACY_FATAL MSG_MASK_4 + +/* Legacy Message Priorities */ +#define MSG_LVL_FATAL (MSG_LEGACY_FATAL) +#define MSG_LVL_ERROR (MSG_LEGACY_ERROR | MSG_LVL_FATAL) +#define MSG_LVL_HIGH (MSG_LEGACY_HIGH | MSG_LVL_ERROR) +#define MSG_LVL_MED (MSG_LEGACY_MED | MSG_LVL_HIGH) +#define MSG_LVL_LOW (MSG_LEGACY_LOW | MSG_LVL_MED) + +#define MSG_LVL_NONE 0 + +/* This needs to be modified manually now, when we add + a new RANGE of SSIDs to the msg_mask_tbl */ +#define MSG_MASK_TBL_CNT 19 +#define EVENT_LAST_ID 0x083F + +#define MSG_SSID_0 0 +#define MSG_SSID_0_LAST 68 +#define MSG_SSID_1 500 +#define MSG_SSID_1_LAST 506 +#define MSG_SSID_2 1000 +#define MSG_SSID_2_LAST 1007 +#define MSG_SSID_3 2000 +#define MSG_SSID_3_LAST 2008 +#define MSG_SSID_4 3000 +#define MSG_SSID_4_LAST 3012 +#define MSG_SSID_5 4000 +#define MSG_SSID_5_LAST 4010 +#define MSG_SSID_6 4500 +#define MSG_SSID_6_LAST 4526 +#define MSG_SSID_7 4600 +#define MSG_SSID_7_LAST 4611 +#define MSG_SSID_8 5000 +#define MSG_SSID_8_LAST 5024 +#define MSG_SSID_9 5500 +#define MSG_SSID_9_LAST 5514 +#define MSG_SSID_10 6000 +#define MSG_SSID_10_LAST 6050 +#define MSG_SSID_11 6500 +#define MSG_SSID_11_LAST 6521 +#define MSG_SSID_12 7000 +#define MSG_SSID_12_LAST 7003 +#define MSG_SSID_13 7100 +#define MSG_SSID_13_LAST 7111 +#define MSG_SSID_14 7200 +#define MSG_SSID_14_LAST 7201 +#define MSG_SSID_15 8000 +#define MSG_SSID_15_LAST 8000 +#define MSG_SSID_16 8500 +#define MSG_SSID_16_LAST 8523 +#define MSG_SSID_17 9000 +#define MSG_SSID_17_LAST 9008 +#define MSG_SSID_18 9500 +#define MSG_SSID_18_LAST 9509 + +struct diagpkt_delay_params { + void *rsp_ptr; + int size; + int *num_bytes_ptr; +}; + +static const uint32_t msg_bld_masks_0[] = { + MSG_LVL_LOW, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_ERROR, + MSG_LVL_LOW, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_HIGH, + MSG_LVL_ERROR, + MSG_LVL_LOW, + MSG_LVL_ERROR, + MSG_LVL_ERROR, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_LOW, + MSG_LVL_ERROR, + MSG_LVL_LOW, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_LOW, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED|MSG_MASK_7 | \ + MSG_MASK_8|MSG_MASK_9|MSG_MASK_10|MSG_MASK_11|MSG_MASK_12 | \ + MSG_MASK_13|MSG_MASK_14|MSG_MASK_15|MSG_MASK_16 | \ + MSG_MASK_17|MSG_MASK_18|MSG_MASK_19|MSG_MASK_20|MSG_MASK_21, + MSG_LVL_MED|MSG_MASK_5 | \ + MSG_MASK_6|MSG_MASK_7|MSG_MASK_8|MSG_MASK_9|MSG_MASK_10| \ + MSG_MASK_11|MSG_MASK_12|MSG_MASK_13|MSG_MASK_14| \ + MSG_MASK_15|MSG_MASK_16|MSG_MASK_17, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED|MSG_MASK_5 | \ + MSG_MASK_6|MSG_MASK_7|MSG_MASK_8, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_MED, + MSG_LVL_MED|MSG_MASK_5 | \ + MSG_MASK_6|MSG_MASK_7|MSG_MASK_8|MSG_MASK_9|MSG_MASK_10| \ + MSG_MASK_11|MSG_MASK_12|MSG_MASK_13|MSG_MASK_14|MSG_MASK_15| \ + MSG_MASK_16|MSG_MASK_17|MSG_MASK_18|MSG_MASK_19|MSG_MASK_20| \ + MSG_MASK_21|MSG_MASK_22|MSG_MASK_23|MSG_MASK_24|MSG_MASK_25, + MSG_LVL_MED|MSG_MASK_5 | \ + MSG_MASK_6|MSG_MASK_7|MSG_MASK_8|MSG_MASK_9|MSG_MASK_10, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_LOW, + MSG_LVL_MED, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_HIGH, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW | MSG_MASK_5 | \ + MSG_MASK_6 | MSG_MASK_7 | MSG_MASK_8, + MSG_LVL_LOW | MSG_MASK_5 | \ + MSG_MASK_6, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_LOW, + MSG_LVL_MED | MSG_MASK_5 | \ + MSG_MASK_6|MSG_MASK_7|MSG_MASK_8|MSG_MASK_9|MSG_MASK_10| \ + MSG_MASK_11|MSG_MASK_12|MSG_MASK_13|MSG_MASK_14|MSG_MASK_15 | \ + MSG_MASK_16|MSG_MASK_17|MSG_MASK_18|MSG_MASK_19|MSG_MASK_20, + MSG_LVL_LOW, + MSG_LVL_MED, + MSG_LVL_LOW, + MSG_LVL_LOW, +}; + +static const uint32_t msg_bld_masks_1[] = { + MSG_LVL_MED, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_LOW, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, +}; + +static const uint32_t msg_bld_masks_2[] = { + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED, + MSG_LVL_MED +}; + +static const uint32_t msg_bld_masks_3[] = { + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED +}; + +static const uint32_t msg_bld_masks_4[] = { + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH, + MSG_LVL_HIGH +}; + +static const uint32_t msg_bld_masks_5[] = { + MSG_LVL_HIGH, + MSG_LVL_MED, + MSG_LVL_HIGH, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED +}; + +static const uint32_t msg_bld_masks_6[] = { + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW +}; + +static const uint32_t msg_bld_masks_7[] = { + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, +}; + +static const uint32_t msg_bld_masks_8[] = { + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, +}; + +static const uint32_t msg_bld_masks_9[] = { + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, + MSG_LVL_MED|MSG_MASK_5, +}; + +static const uint32_t msg_bld_masks_10[] = { + MSG_LVL_MED, + MSG_LVL_ERROR, + MSG_LVL_LOW, + MSG_LVL_LOW|MSG_MASK_5 | \ + MSG_MASK_6|MSG_MASK_7|MSG_MASK_8|MSG_MASK_9|MSG_MASK_10| \ + MSG_MASK_11|MSG_MASK_12|MSG_MASK_13|MSG_MASK_14|MSG_MASK_15| \ + MSG_MASK_16|MSG_MASK_17|MSG_MASK_18|MSG_MASK_19|MSG_MASK_20| \ + MSG_MASK_21|MSG_MASK_22, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW, + MSG_LVL_MED, + MSG_LVL_HIGH, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW|MSG_MASK_5, + MSG_LVL_LOW|MSG_MASK_0 | MSG_MASK_1 | MSG_MASK_2 | \ + MSG_MASK_3 | MSG_MASK_4 | MSG_MASK_5 | MSG_MASK_6, + MSG_LVL_HIGH, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, +}; + +static const uint32_t msg_bld_masks_11[] = { + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, +}; + +static const uint32_t msg_bld_masks_12[] = { + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, +}; + +static const uint32_t msg_bld_masks_13[] = { + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, +}; + +static const uint32_t msg_bld_masks_14[] = { + MSG_LVL_MED, + MSG_LVL_MED, +}; + +static const uint32_t msg_bld_masks_15[] = { + MSG_LVL_MED +}; + +static const uint32_t msg_bld_masks_16[] = { + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW, +}; + +static const uint32_t msg_bld_masks_17[] = { + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED, + MSG_LVL_MED | MSG_MASK_6 | \ + MSG_MASK_7 | MSG_MASK_8 | MSG_MASK_9, + MSG_LVL_MED | MSG_MASK_5 | \ + MSG_MASK_6 | MSG_MASK_7 | MSG_MASK_8 | MSG_MASK_9 | \ + MSG_MASK_10 | MSG_MASK_11 | MSG_MASK_12 | MSG_MASK_13 | \ + MSG_MASK_14 | MSG_MASK_15 | MSG_MASK_16 | MSG_MASK_17, + MSG_LVL_MED, + MSG_LVL_MED | MSG_MASK_5 | \ + MSG_MASK_6 | MSG_MASK_7 | MSG_MASK_8 | MSG_MASK_9 | \ + MSG_MASK_10 | MSG_MASK_11 | MSG_MASK_12 | MSG_MASK_13 | \ + MSG_MASK_14 | MSG_MASK_15 | MSG_MASK_16 | MSG_MASK_17 | \ + MSG_MASK_18 | MSG_MASK_19 | MSG_MASK_20 | MSG_MASK_21 | \ + MSG_MASK_22, + MSG_LVL_MED, + MSG_LVL_MED, +}; + +static const uint32_t msg_bld_masks_18[] = { + MSG_LVL_LOW, + MSG_LVL_LOW | MSG_MASK_8 | MSG_MASK_9 | MSG_MASK_10 | \ + MSG_MASK_11|MSG_MASK_12|MSG_MASK_13|MSG_MASK_14|MSG_MASK_15 | \ + MSG_MASK_16|MSG_MASK_17|MSG_MASK_18|MSG_MASK_19|MSG_MASK_20, + MSG_LVL_LOW | MSG_MASK_5 | MSG_MASK_6, + MSG_LVL_LOW | MSG_MASK_5, + MSG_LVL_LOW | MSG_MASK_5 | MSG_MASK_6, + MSG_LVL_LOW, + MSG_LVL_LOW | MSG_MASK_5 | \ + MSG_MASK_6 | MSG_MASK_7 | MSG_MASK_8 | MSG_MASK_9, + MSG_LVL_LOW, + MSG_LVL_LOW, + MSG_LVL_LOW +}; + +/* LOG CODES */ + +#define LOG_0 0x0 +#define LOG_1 0x1520 +#define LOG_2 0x0 +#define LOG_3 0x0 +#define LOG_4 0x4910 +#define LOG_5 0x5420 +#define LOG_6 0x0 +#define LOG_7 0x74FF +#define LOG_8 0x0 +#define LOG_9 0x0 +#define LOG_10 0xA38A +#define LOG_11 0xB201 +#define LOG_12 0x0 +#define LOG_13 0x0 +#define LOG_14 0x0 +#define LOG_15 0x0 + +#define LOG_GET_ITEM_NUM(xx_code) (xx_code & 0x0FFF) + +#endif diff --git a/include/linux/fsm_dfe_hh.h b/include/linux/fsm_dfe_hh.h new file mode 100644 index 00000000000..5f09f1e196b --- /dev/null +++ b/include/linux/fsm_dfe_hh.h @@ -0,0 +1,85 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _FSM_DFE_HH_H_ +#define _FSM_DFE_HH_H_ + +#include + +/* + * Device interface + */ + +#define DFE_HH_DEVICE_NAME "dfe_hh" + +/* + * IOCTL interface + */ + +enum { + DFE_IOCTL_COMMAND_CODE_WRITE, + DFE_IOCTL_COMMAND_CODE_WRITE_WITH_MASK, +}; + +struct dfe_write_register_param { + unsigned int offset; + unsigned int value; +}; + +struct dfe_write_register_mask_param { + unsigned int offset; + unsigned int value; + unsigned int mask; +}; + +struct dfe_read_write_array_param { + unsigned int offset; + unsigned int num; /* number of 16 bit registers */ + unsigned int *pArray; +}; + +struct dfe_command_entry { + unsigned int code; + unsigned int offset; + unsigned int value; + unsigned int mask; /* DFE_IOCTL_COMMAND_CODE_WRITE_WITH_MASK only */ +}; + +struct dfe_command_param { + unsigned int num; + struct dfe_command_entry *pEntry; +}; + +#define DFE_IOCTL_MAGIC 'h' +#define DFE_IOCTL_IS_UMTS \ + _IOC(_IOC_READ, DFE_IOCTL_MAGIC, 0x00, \ + 0) +#define DFE_IOCTL_READ_REGISTER \ + _IOC(_IOC_READ, DFE_IOCTL_MAGIC, 0x01, \ + sizeof(unsigned int *)) +#define DFE_IOCTL_WRITE_REGISTER \ + _IOC(_IOC_WRITE, DFE_IOCTL_MAGIC, 0x02, \ + sizeof(struct dfe_write_register_param *)) +#define DFE_IOCTL_WRITE_REGISTER_WITH_MASK \ + _IOC(_IOC_WRITE, DFE_IOCTL_MAGIC, 0x03, \ + sizeof(struct dfe_write_register_mask_param *)) +#define DFE_IOCTL_READ_REGISTER_ARRAY \ + _IOC(_IOC_READ, DFE_IOCTL_MAGIC, 0x04, \ + sizeof(struct dfe_read_write_array_param *)) +#define DFE_IOCTL_WRITE_REGISTER_ARRAY \ + _IOC(_IOC_WRITE, DFE_IOCTL_MAGIC, 0x05, \ + sizeof(struct dfe_read_write_array_param *)) +#define DFE_IOCTL_COMMAND \ + _IOC(_IOC_WRITE, DFE_IOCTL_MAGIC, 0x10, \ + sizeof(struct dfe_command_param *)) + +#endif /* _FSM_DFE_HH_H_ */ diff --git a/include/linux/fsm_rfic_ftr.h b/include/linux/fsm_rfic_ftr.h new file mode 100644 index 00000000000..5e8cd89668d --- /dev/null +++ b/include/linux/fsm_rfic_ftr.h @@ -0,0 +1,74 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _FSM_RFIC_FTR_H_ +#define _FSM_RFIC_FTR_H_ + +#include + +/* + * Device interface + */ + +#define RFIC_FTR_DEVICE_NAME "rfic_ftr" + +/* + * IOCTL interface + */ + +/* + Macro to associate the "bus" and "address" pair when accessing the RFIC. + Using a 32 bit address, reserve the upper 8 bits for the bus value, and + the lower 24 bits for the address. + */ +#define RFIC_FTR_ADDR(bus, addr) (((bus&0x03)<<24)|(addr&0xFFFFFF)) +#define RFIC_FTR_GET_ADDR(busAddr) (busAddr&0xFFFFFF) +#define RFIC_FTR_GET_BUS(busAddr) ((busAddr>>24)&0x03) + +struct rfic_write_register_param { + unsigned int rficAddr; + unsigned int value; +}; + +struct rfic_write_register_mask_param { + unsigned int rficAddr; + unsigned int value; + unsigned int mask; +}; + +struct rfic_grfc_param { + unsigned int grfcId; + unsigned int maskValue; + unsigned int ctrlValue; +}; + +#define RFIC_IOCTL_MAGIC 'f' +#define RFIC_IOCTL_IS_UMTS \ + _IOC(_IOC_READ, RFIC_IOCTL_MAGIC, 0x00, \ + 0) +#define RFIC_IOCTL_READ_REGISTER \ + _IOC(_IOC_READ, RFIC_IOCTL_MAGIC, 0x01, \ + sizeof(unsigned int *)) +#define RFIC_IOCTL_WRITE_REGISTER \ + _IOC(_IOC_WRITE, RFIC_IOCTL_MAGIC, 0x02, \ + sizeof(struct rfic_write_register_param *)) +#define RFIC_IOCTL_WRITE_REGISTER_WITH_MASK \ + _IOC(_IOC_WRITE, RFIC_IOCTL_MAGIC, 0x03, \ + sizeof(struct rfic_write_register_mask_param *)) +#define RFIC_IOCTL_GET_GRFC \ + _IOC(_IOC_WRITE, RFIC_IOCTL_MAGIC, 0x10, \ + sizeof(struct rfic_grfc_param *)) +#define RFIC_IOCTL_SET_GRFC \ + _IOC(_IOC_WRITE, RFIC_IOCTL_MAGIC, 0x11, \ + sizeof(struct rfic_grfc_param *)) + +#endif /* _FSM_RFIC_FTR_H_ */ diff --git a/include/linux/genalloc.h b/include/linux/genalloc.h index 5bbebda78b0..3bc7dd7394b 100644 --- a/include/linux/genalloc.h +++ b/include/linux/genalloc.h @@ -8,31 +8,35 @@ * Version 2. See the file COPYING for more details. */ - #ifndef __GENALLOC_H__ #define __GENALLOC_H__ -/* - * General purpose special memory pool descriptor. - */ -struct gen_pool { - rwlock_t lock; - struct list_head chunks; /* list of chunks in this pool */ - int min_alloc_order; /* minimum allocation order */ -}; -/* - * General purpose special memory pool chunk descriptor. +struct gen_pool; + +struct gen_pool *__must_check gen_pool_create(unsigned order, int nid); + +void gen_pool_destroy(struct gen_pool *pool); + +unsigned long __must_check +gen_pool_alloc_aligned(struct gen_pool *pool, size_t size, + unsigned alignment_order); + +/** + * gen_pool_alloc() - allocate special memory from the pool + * @pool: Pool to allocate from. + * @size: Number of bytes to allocate from the pool. + * + * Allocate the requested number of bytes from the specified pool. + * Uses a first-fit algorithm. */ -struct gen_pool_chunk { - spinlock_t lock; - struct list_head next_chunk; /* next chunk in pool */ - phys_addr_t phys_addr; /* physical starting address of memory chunk */ - unsigned long start_addr; /* starting address of memory chunk */ - unsigned long end_addr; /* ending address of memory chunk */ - unsigned long bits[0]; /* bitmap for allocating memory chunk */ -}; - -extern struct gen_pool *gen_pool_create(int, int); +static inline unsigned long __must_check +gen_pool_alloc(struct gen_pool *pool, size_t size) +{ + return gen_pool_alloc_aligned(pool, size, 0); +} + +void gen_pool_free(struct gen_pool *pool, unsigned long addr, size_t size); + extern phys_addr_t gen_pool_virt_to_phys(struct gen_pool *pool, unsigned long); extern int gen_pool_add_virt(struct gen_pool *, unsigned long, phys_addr_t, size_t, int); @@ -48,12 +52,9 @@ extern int gen_pool_add_virt(struct gen_pool *, unsigned long, phys_addr_t, * * Returns 0 on success or a -ve errno on failure. */ -static inline int gen_pool_add(struct gen_pool *pool, unsigned long addr, +static inline int __must_check gen_pool_add(struct gen_pool *pool, unsigned long addr, size_t size, int nid) { return gen_pool_add_virt(pool, addr, -1, size, nid); } -extern void gen_pool_destroy(struct gen_pool *); -extern unsigned long gen_pool_alloc(struct gen_pool *, size_t); -extern void gen_pool_free(struct gen_pool *, unsigned long, size_t); #endif /* __GENALLOC_H__ */ diff --git a/include/linux/gpio-pm8xxx-rpc.h b/include/linux/gpio-pm8xxx-rpc.h new file mode 100644 index 00000000000..5b6f09787bf --- /dev/null +++ b/include/linux/gpio-pm8xxx-rpc.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Qualcomm PMIC8XXX gpio rpc driver header file + * + */ + +#ifndef __GPIO_PM8XXX_RPC_H +#define __GPIO_PM8XXX_RPC_H + +#define PM8XXX_GPIO_DEV_NAME "pm8xxx-gpio-rpc" + +struct pm8xxx_gpio_rpc_platform_data { + int ngpios; + int gpio_base; +}; + +/* GPIO parameters */ +/* direction */ +#define PM_GPIO_DIR_OUT 0x01 +#define PM_GPIO_DIR_IN 0x02 +#define PM_GPIO_DIR_BOTH (PM_GPIO_DIR_OUT | PM_GPIO_DIR_IN) + +#endif diff --git a/include/linux/gpio_event.h b/include/linux/gpio_event.h index 360b4ddb46a..8c3f090e0f0 100644 --- a/include/linux/gpio_event.h +++ b/include/linux/gpio_event.h @@ -83,10 +83,10 @@ struct gpio_event_matrix_info { unsigned int ninputs; unsigned int noutputs; /* time to wait before reading inputs after driving each output */ - ktime_t settle_time; + struct timespec settle_time; /* time to wait before scanning the keypad a second time */ - ktime_t debounce_delay; - ktime_t poll_time; + struct timespec debounce_delay; + struct timespec poll_time; unsigned flags; }; diff --git a/include/linux/i2c/bq27520.h b/include/linux/i2c/bq27520.h new file mode 100644 index 00000000000..70c5a4c2a11 --- /dev/null +++ b/include/linux/i2c/bq27520.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LINUX_BQ27520_H +#define __LINUX_BQ27520_H +struct bq27520_platform_data { + const char *name; + unsigned int soc_int; + unsigned int bi_tout; + unsigned int chip_en; /* CE */ + const char *vreg_name; /* regulater used by bq27520 */ + int vreg_value; /* its value */ + int enable_dlog; /* if enable on-chip coulomb counter data logger */ +}; + +#endif /* __LINUX_BQ27520_H */ diff --git a/include/linux/i2c/isa1200.h b/include/linux/i2c/isa1200.h new file mode 100644 index 00000000000..7d2e53fd83b --- /dev/null +++ b/include/linux/i2c/isa1200.h @@ -0,0 +1,46 @@ +/* + * isa1200.h - ISA1200 Haptic Motor driver + * + * Copyright (C) 2009 Samsung Electronics + * Kyungmin Park + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_ISA1200_H +#define __LINUX_ISA1200_H + +enum mode_control { + POWER_DOWN_MODE = 0, + PWM_INPUT_MODE, + PWM_GEN_MODE, + WAVE_GEN_MODE +}; + +union pwm_div_freq { + unsigned int pwm_div; /* PWM gen mode */ + unsigned int pwm_freq; /* PWM input mode */ +}; + +struct isa1200_platform_data { + const char *name; + unsigned int pwm_ch_id; /* pwm channel id */ + unsigned int max_timeout; + unsigned int hap_en_gpio; + bool overdrive_high; /* high/low overdrive */ + bool overdrive_en; /* enable/disable overdrive */ + enum mode_control mode_ctrl; /* input/generation/wave */ + union pwm_div_freq pwm_fd; + bool smart_en; /* smart mode enable/disable */ + bool is_erm; + bool ext_clk_en; + unsigned int chip_en; + unsigned int duty; + int (*power_on)(int on); + int (*dev_setup)(bool on); +}; + +#endif /* __LINUX_ISA1200_H */ diff --git a/include/linux/i2c/isl9519.h b/include/linux/i2c/isl9519.h new file mode 100644 index 00000000000..8c98bf7c6ef --- /dev/null +++ b/include/linux/i2c/isl9519.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __ISL9519_H__ +#define __ISL9519_H__ + +/** + * struct isl_platform_data + * @chgcurrent: max current the islchip can draw + * @valid_irq: interrupt for insertion/removal notification + * @valid_n_gpio: gpio to debounce insertion/removal + * @valid_config: machine specific func to configure gpio line + * @max_system_voltage: the max voltage isl should charge battery to + * @min_system_voltage: the min voltage isl should trkl charge the + * battery + * @term_current: the batt current when isl charging should stop + * @input_current: the max current isl should pull from the adapter + */ +struct isl_platform_data { + int chgcurrent; + int valid_n_gpio; + int (*chg_detection_config) (void); + int max_system_voltage; + int min_system_voltage; + int term_current; + int input_current; +}; + +#endif diff --git a/include/linux/i2c/smb137b.h b/include/linux/i2c/smb137b.h new file mode 100644 index 00000000000..a72b8957a03 --- /dev/null +++ b/include/linux/i2c/smb137b.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __SMB137B_H__ +#define __SMB137B_H__ + +/** + * struct smb137b_platform_data + * structure to pass board specific information to the smb137b charger driver + * @chgcurrent: max current the smb137bchip can draw + * @valid_n_gpio: gpio to debounce insertion/removal + * @chg_detection_config: machine specific func to configure + * insertion/removal gpio line + * @batt_mah_rating: the battery current rating + */ +struct smb137b_platform_data { + int valid_n_gpio; + int (*chg_detection_config) (void); + int batt_mah_rating; +}; + +void smb137b_otg_power(int on); + +#endif diff --git a/include/linux/i2c/sx150x.h b/include/linux/i2c/sx150x.h index 52baa79d69a..e73dfd930f6 100644 --- a/include/linux/i2c/sx150x.h +++ b/include/linux/i2c/sx150x.h @@ -11,11 +11,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #ifndef __LINUX_I2C_SX150X_H #define __LINUX_I2C_SX150X_H diff --git a/include/linux/i2c/tsc2007.h b/include/linux/i2c/tsc2007.h index 591427a63b0..195e9040390 100644 --- a/include/linux/i2c/tsc2007.h +++ b/include/linux/i2c/tsc2007.h @@ -13,12 +13,18 @@ struct tsc2007_platform_data { int fuzzx; /* fuzz factor for X, Y and pressure axes */ int fuzzy; int fuzzz; + unsigned long irq_flags; + bool invert_x; + bool invert_y; + bool invert_z1; + bool invert_z2; int (*get_pendown_state)(void); void (*clear_penirq)(void); /* If needed, clear 2nd level interrupt source */ int (*init_platform_hw)(void); void (*exit_platform_hw)(void); + int (*power_shutdown)(bool); }; #endif diff --git a/include/linux/if_arp.h b/include/linux/if_arp.h index 6d722f41ee7..2cc79aeddd8 100644 --- a/include/linux/if_arp.h +++ b/include/linux/if_arp.h @@ -59,6 +59,7 @@ #define ARPHRD_LAPB 516 /* LAPB */ #define ARPHRD_DDCMP 517 /* Digital's DDCMP protocol */ #define ARPHRD_RAWHDLC 518 /* Raw HDLC */ +#define ARPHRD_RAWIP 530 /* Raw IP */ #define ARPHRD_TUNNEL 768 /* IPIP tunnel */ #define ARPHRD_TUNNEL6 769 /* IP6IP6 tunnel */ diff --git a/include/linux/input.h b/include/linux/input.h index 771d6d85667..d9133f2b67b 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -658,7 +658,7 @@ struct input_keymap_entry { #define KEY_NUMERIC_9 0x209 #define KEY_NUMERIC_STAR 0x20a #define KEY_NUMERIC_POUND 0x20b - +#define KEY_CAMERA_SNAPSHOT 0x2fe #define KEY_CAMERA_FOCUS 0x210 #define KEY_WPS_BUTTON 0x211 /* WiFi Protected Setup key */ diff --git a/include/linux/input/cy8c_ts.h b/include/linux/input/cy8c_ts.h new file mode 100644 index 00000000000..d25f31d66da --- /dev/null +++ b/include/linux/input/cy8c_ts.h @@ -0,0 +1,65 @@ +/* Header file for: + * Cypress CY8CTMA300 Prototype touchscreen driver. + * + * Copyright (C) 2009, 2010 Cypress Semiconductor, Inc. + * Copyright (c) 2010, Code Aurora Forum. 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, and only version 2, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Cypress reserves the right to make changes without further notice + * to the materials described herein. Cypress does not assume any + * liability arising out of the application described herein. + * + * Contact Cypress Semiconductor at www.cypress.com + * + * History: + * (C) 2010 Cypress - Update for GPL distribution + * (C) 2009 Cypress - Assume maintenance ownership + * (C) 2009 Enea - Original prototype + * + */ +#ifndef __CY8C8CTS_H__ +#define __CY8C8CTS_H__ + + +/* CY8CTMA300-TMG200 platform data + */ +struct cy8c_ts_platform_data { + int (*power_on)(int on); + int (*dev_setup)(bool on); + const char *ts_name; + u32 dis_min_x; /* display resoltion */ + u32 dis_max_x; + u32 dis_min_y; + u32 dis_max_y; + u32 min_touch; /* no.of touches supported */ + u32 max_touch; + u32 min_tid; /* track id */ + u32 max_tid; + u32 min_width;/* size of the finger */ + u32 max_width; + u32 res_x; /* TS resolution */ + u32 res_y; + u32 swap_xy; + u32 flags; + u16 invert_x; + u16 invert_y; + u8 nfingers; + u32 irq_gpio; + int resout_gpio; + bool wakeup; +}; + +#endif diff --git a/include/linux/input/kp_flip_switch.h b/include/linux/input/kp_flip_switch.h new file mode 100644 index 00000000000..31c0cc4a9e5 --- /dev/null +++ b/include/linux/input/kp_flip_switch.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __KP_FLIP_SWITCH_H_ +#define __KP_FLIP_SWITCH_H_ +/* flip switch driver platform data */ +struct flip_switch_pdata { + int flip_gpio; + int left_key; + int right_key; + int wakeup; + int active_low; + int (*flip_mpp_config) (void); + char name[25]; +}; +#endif diff --git a/include/linux/input/matrix_keypad.h b/include/linux/input/matrix_keypad.h index fe7c4b9ae27..d80845ed000 100644 --- a/include/linux/input/matrix_keypad.h +++ b/include/linux/input/matrix_keypad.h @@ -4,12 +4,12 @@ #include #include -#define MATRIX_MAX_ROWS 32 -#define MATRIX_MAX_COLS 32 +#define MATRIX_MAX_ROWS 18 +#define MATRIX_MAX_COLS 18 -#define KEY(row, col, val) ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |\ - (((col) & (MATRIX_MAX_COLS - 1)) << 16) |\ - ((val) & 0xffff)) +#define KEY(row, col, val) ((((row) % (MATRIX_MAX_ROWS)) << 24) |\ + (((col) % (MATRIX_MAX_COLS)) << 16) |\ + (val & 0xffff)) #define KEY_ROW(k) (((k) >> 24) & 0xff) #define KEY_COL(k) (((k) >> 16) & 0xff) diff --git a/include/linux/input/msm_ts.h b/include/linux/input/msm_ts.h new file mode 100644 index 00000000000..45df9f7bc3c --- /dev/null +++ b/include/linux/input/msm_ts.h @@ -0,0 +1,51 @@ +/* + * Internal platform definitions for msm/qsd touchscreen devices + * + * Copyright (C) 2008 Google Incorporated + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ASM_ARCH_MSM_TS_H +#define __ASM_ARCH_MSM_TS_H + +#include + +/* The dimensions for the virtual key are for the other axis, i.e. if + * virtual keys are in the Y dimension then min/max is the range in the X + * dimension where that key would be activated */ +struct ts_virt_key { + int key; + int min; + int max; +}; + +struct msm_ts_virtual_keys { + struct ts_virt_key *keys; + int num_keys; +}; + +struct msm_ts_platform_data { + uint32_t min_x; + uint32_t max_x; + uint32_t min_y; + uint32_t max_y; + uint32_t min_press; + uint32_t max_press; + struct msm_ts_virtual_keys *vkeys_x; + uint32_t virt_x_start; + struct msm_ts_virtual_keys *vkeys_y; + uint32_t virt_y_start; + uint32_t inv_x; + uint32_t inv_y; + bool can_wakeup; +}; + +#endif /* __ASM_ARCH_MSM_TS_H */ diff --git a/include/linux/input/pmic8058-keypad.h b/include/linux/input/pmic8058-keypad.h new file mode 100644 index 00000000000..a2fd6acdd7e --- /dev/null +++ b/include/linux/input/pmic8058-keypad.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __PMIC8058_KEYPAD_H__ +#define __PMIC8058_KEYPAD_H__ + +#include + +/* + * NOTE: Assumption of maximum of five revisions + * of PMIC8058 chip. + */ +#define MAX_PM8058_REVS 0x5 + +struct pmic8058_keypad_data { + const struct matrix_keymap_data *keymap_data; + + const char *input_name; + const char *input_phys_device; + + unsigned int num_cols; + unsigned int num_rows; + + unsigned int rows_gpio_start; + unsigned int cols_gpio_start; + + unsigned int debounce_ms[MAX_PM8058_REVS]; + unsigned int scan_delay_ms; + unsigned int row_hold_ns; + + int keymap_size; + const unsigned int *keymap; + + unsigned int wakeup; + unsigned int rep; +}; + +#endif /*__PMIC8058_KEYPAD_H__ */ diff --git a/include/linux/input/qci_kbd.h b/include/linux/input/qci_kbd.h new file mode 100644 index 00000000000..5afda7da818 --- /dev/null +++ b/include/linux/input/qci_kbd.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __QCI_KBD_H__ +#define __QCI_KBD_H__ + +/** + * struct qci_kbd_platform_data - platform data for keyboard + * @repeat: enable or disable key repeate feature + * + * platform data structure for QCI keyboard driver. + */ +struct qci_kbd_platform_data { + bool repeat; + bool standard_scancodes; + bool kb_leds; +}; + +#endif /*__QCI_KBD_H__*/ diff --git a/include/linux/input/tdisc_shinetsu.h b/include/linux/input/tdisc_shinetsu.h new file mode 100644 index 00000000000..88f84f2a848 --- /dev/null +++ b/include/linux/input/tdisc_shinetsu.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _TDISC_SHINETSU_H_ +#define _TDISC_SHINETSU_H_ + +struct tdisc_abs_values { + int x_max; + int y_max; + int x_min; + int y_min; + int pressure_max; + int pressure_min; +}; + +struct tdisc_platform_data { + int (*tdisc_setup) (void); + void (*tdisc_release) (void); + int (*tdisc_enable) (void); + int (*tdisc_disable)(void); + int tdisc_wakeup; + int tdisc_gpio; + bool tdisc_report_keys; + bool tdisc_report_relative; + bool tdisc_report_absolute; + bool tdisc_report_wheel; + bool tdisc_reverse_x; + bool tdisc_reverse_y; + struct tdisc_abs_values *tdisc_abs; +}; + +#endif /* _TDISC_SHINETSU_H_ */ diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h index f6efed0039e..150107ba216 100644 --- a/include/linux/interrupt.h +++ b/include/linux/interrupt.h @@ -337,6 +337,7 @@ static inline void enable_irq_lockdep_irqrestore(unsigned int irq, unsigned long /* IRQ wakeup (PM) control: */ extern int irq_set_irq_wake(unsigned int irq, unsigned int on); +extern int irq_read_line(unsigned int irq); static inline int enable_irq_wake(unsigned int irq) { @@ -686,5 +687,5 @@ int arch_show_interrupts(struct seq_file *p, int prec); extern int early_irq_init(void); extern int arch_probe_nr_irqs(void); extern int arch_early_irq_init(void); - +extern void irq_set_pending(unsigned int irq); #endif diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 0a2ba409899..d56318c891c 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -19,6 +19,9 @@ #ifndef __LINUX_IOMMU_H #define __LINUX_IOMMU_H +#include +#include + #define IOMMU_READ (1) #define IOMMU_WRITE (2) #define IOMMU_CACHE (4) /* DMA cache coherency */ diff --git a/include/linux/ioport.h b/include/linux/ioport.h index e9bb22cba76..07988d0380e 100644 --- a/include/linux/ioport.h +++ b/include/linux/ioport.h @@ -115,6 +115,8 @@ extern struct resource iomem_resource; extern struct resource *request_resource_conflict(struct resource *root, struct resource *new); extern int request_resource(struct resource *root, struct resource *new); +extern struct resource *locate_resource(struct resource *root, + struct resource *search); extern int release_resource(struct resource *new); void release_child_resources(struct resource *new); extern void reserve_region_with_split(struct resource *root, diff --git a/include/linux/irq.h b/include/linux/irq.h index baa397eb9c3..243d8e9d7da 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -276,6 +276,7 @@ static inline void irqd_clr_chained_irq_inprogress(struct irq_data *d) * @irq_retrigger: resend an IRQ to the CPU * @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ * @irq_set_wake: enable/disable power-management wake-on of an IRQ + * @irq_read_line: return the current value on the irq line * @irq_bus_lock: function to lock access to slow bus (i2c) chips * @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips * @irq_cpu_online: configure an interrupt source for a secondary CPU @@ -304,6 +305,7 @@ struct irq_chip { int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force); int (*irq_retrigger)(struct irq_data *data); int (*irq_set_type)(struct irq_data *data, unsigned int flow_type); + int (*irq_read_line)(struct irq_data *data); int (*irq_set_wake)(struct irq_data *data, unsigned int on); void (*irq_bus_lock)(struct irq_data *data); @@ -397,6 +399,8 @@ extern void handle_nested_irq(unsigned int irq); extern void note_interrupt(unsigned int irq, struct irq_desc *desc, irqreturn_t action_ret); +/* Resending of interrupts :*/ +void check_irq_resend(struct irq_desc *desc, unsigned int irq); /* Enable/disable irq debugging output: */ extern int noirqdebug_setup(char *str); diff --git a/include/linux/kexec.h b/include/linux/kexec.h index c2478a342cd..61d429bf16a 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -116,6 +116,7 @@ extern asmlinkage long sys_kexec_load(unsigned long entry, unsigned long nr_segments, struct kexec_segment __user *segments, unsigned long flags); +extern void __weak arch_kexec(void); extern int kernel_kexec(void); #ifdef CONFIG_COMPAT extern asmlinkage long compat_sys_kexec_load(unsigned long entry, diff --git a/include/linux/leds-pm8xxx.h b/include/linux/leds-pm8xxx.h new file mode 100644 index 00000000000..3a03100836d --- /dev/null +++ b/include/linux/leds-pm8xxx.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LEDS_PM8XXX_H__ +#define __LEDS_PM8XXX_H__ + + +#define PM8XXX_LEDS_DEV_NAME "pm8xxx-led" + +/** + * enum pm8xxx_leds - PMIC8XXX supported led ids + * @PM8XXX_ID_LED_KB_LIGHT - keyboard backlight led + * @PM8XXX_ID_LED_0 - First low current led + * @PM8XXX_ID_LED_1 - Second low current led + * @PM8XXX_ID_LED_2 - Third low current led + * @PM8XXX_ID_FLASH_LED_0 - First flash led + * @PM8XXX_ID_FLASH_LED_0 - Second flash led + */ +enum pm8xxx_leds { + PM8XXX_ID_LED_KB_LIGHT = 1, + PM8XXX_ID_LED_0, + PM8XXX_ID_LED_1, + PM8XXX_ID_LED_2, + PM8XXX_ID_FLASH_LED_0, + PM8XXX_ID_FLASH_LED_1, +}; + +#endif /* __LEDS_PM8XXX_H__ */ diff --git a/include/linux/leds-pmic8058.h b/include/linux/leds-pmic8058.h new file mode 100644 index 00000000000..cbfde9f7b24 --- /dev/null +++ b/include/linux/leds-pmic8058.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __LEDS_PMIC8058_H__ +#define __LEDS_PMIC8058_H__ + +enum pmic8058_leds { + PMIC8058_ID_LED_KB_LIGHT = 1, + PMIC8058_ID_LED_0, + PMIC8058_ID_LED_1, + PMIC8058_ID_LED_2, + PMIC8058_ID_FLASH_LED_0, + PMIC8058_ID_FLASH_LED_1, +}; + +struct pmic8058_led { + const char *name; + const char *default_trigger; + unsigned max_brightness; + int id; +}; + +struct pmic8058_leds_platform_data { + int num_leds; + struct pmic8058_led *leds; +}; + +int pm8058_set_flash_led_current(enum pmic8058_leds id, unsigned mA); +int pm8058_set_led_current(enum pmic8058_leds id, unsigned mA); + +#endif /* __LEDS_PMIC8058_H__ */ diff --git a/include/linux/libra_sdioif.h b/include/linux/libra_sdioif.h new file mode 100644 index 00000000000..965e2b511c5 --- /dev/null +++ b/include/linux/libra_sdioif.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LIBRA_SDIOIF_H__ +#define __LIBRA_SDIOIF_H__ + +/* + * Header for SDIO Card Interface Functions + */ +#include +#include +#include +#include + +/* + * Common Defines + */ +#define LIBRA_MAN_ID 0x70 +#define LIBRA_REV_1_0_CARD_ID 0x0 + +#define VOLANS_MAN_ID 0x70 +#define VOLANS_REV_1_0_CARD_ID 0x0 +#define VOLANS_REV_2_0_CARD_ID 0x2881 + +typedef int (suspend_handler_t)(struct sdio_func *); +typedef void (resume_handler_t)(struct sdio_func *); + +int libra_sdio_configure(sdio_irq_handler_t libra_sdio_rxhandler, + void (*func_drv_fn)(int *status), + u32 funcdrv_timeout, u32 blksize); +void libra_sdio_deconfigure(struct sdio_func *func); +struct sdio_func *libra_getsdio_funcdev(void); +void libra_sdio_setprivdata(struct sdio_func *sdio_func_dev, + void *padapter); +void *libra_sdio_getprivdata(struct sdio_func *sdio_func_dev); +void libra_claim_host(struct sdio_func *sdio_func_dev, + pid_t *curr_claimed, pid_t current_pid, + atomic_t *claim_count); +void libra_release_host(struct sdio_func *sdio_func_dev, + pid_t *curr_claimed, pid_t current_pid, + atomic_t *claim_count); +void libra_sdiocmd52(struct sdio_func *sdio_func_dev, + u32 addr, u8 *b, int write, int *err_ret); +u8 libra_sdio_readsb(struct sdio_func *func, void *dst, + unsigned int addr, int count); +int libra_sdio_memcpy_fromio(struct sdio_func *func, + void *dst, unsigned int addr, int count); +int libra_sdio_writesb(struct sdio_func *func, + unsigned int addr, void *src, int count); +int libra_sdio_memcpy_toio(struct sdio_func *func, + unsigned int addr, void *src, int count); +int libra_sdio_enable_polling(void); + +int libra_sdio_configure_suspend_resume( + suspend_handler_t *libra_sdio_suspend_hdlr, + resume_handler_t *libra_sdio_resume_hdlr); + +int libra_detect_card_change(void); + +void libra_sdio_set_clock(struct sdio_func *func, unsigned int clk_freq); +void libra_sdio_get_card_id(struct sdio_func *func, unsigned short *card_id); +void libra_sdio_release_irq(struct sdio_func *func); +int libra_enable_sdio_irq(struct sdio_func *func, u8 enable); +void libra_sdio_disable_func(struct sdio_func *func); +int libra_disable_sdio_irq_capability(struct sdio_func *func, u8 disable); + +#endif /* __LIBRA_SDIOIF_H__ */ diff --git a/include/linux/m_adcproc.h b/include/linux/m_adcproc.h new file mode 100644 index 00000000000..e36a90a335f --- /dev/null +++ b/include/linux/m_adcproc.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _M_ADC_PROC_H +#define _M_ADC_PROC_H + +#include +int32_t tdkntcgtherm(int32_t adc_code, const struct adc_properties *, + const struct chan_properties *, struct adc_chan_result *); +int32_t scale_default(int32_t adc_code, const struct adc_properties *, + const struct chan_properties *, struct adc_chan_result *); +int32_t scale_msm_therm(int32_t adc_code, const struct adc_properties *, + const struct chan_properties *, struct adc_chan_result *); +int32_t scale_batt_therm(int32_t adc_code, const struct adc_properties *, + const struct chan_properties *, struct adc_chan_result *); +int32_t scale_pmic_therm(int32_t adc_code, const struct adc_properties *, + const struct chan_properties *, struct adc_chan_result *); +int32_t scale_xtern_chgr_cur(int32_t adc_code, const struct adc_properties *, + const struct chan_properties *, struct adc_chan_result *); +#endif /* _M_ADC_PROC_H */ diff --git a/include/linux/memory_alloc.h b/include/linux/memory_alloc.h new file mode 100644 index 00000000000..88e64ce5740 --- /dev/null +++ b/include/linux/memory_alloc.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LINUX_MEMALLOC_H +#define _LINUX_MEMALLOC_H + +#include +#include +#include + +struct mem_pool { + struct mutex pool_mutex; + struct gen_pool *gpool; + unsigned long paddr; + unsigned long size; + unsigned long free; +}; + +struct alloc { + struct rb_node rb_node; + void *vaddr; + unsigned long paddr; + struct mem_pool *mpool; + unsigned long len; +}; + +struct mem_pool *initialize_memory_pool(unsigned long start, + unsigned long size, int mem_type); + +void *allocate_contiguous_memory(unsigned long size, + int mem_type, unsigned long align, int cached); + +unsigned long allocate_contiguous_memory_nomap(unsigned long size, + int mem_type, unsigned long align); + +void free_contiguous_memory(void *addr); +void free_contiguous_memory_by_paddr(unsigned long paddr); + +unsigned long memory_pool_node_paddr(void *vaddr); + +unsigned long memory_pool_node_len(void *vaddr); + +int memory_pool_init(void); +#endif /* _LINUX_MEMALLOC_H */ diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h index 8122018d300..2d2414be44c 100644 --- a/include/linux/memory_hotplug.h +++ b/include/linux/memory_hotplug.h @@ -10,6 +10,9 @@ struct zone; struct pglist_data; struct mem_section; +extern unsigned long movable_reserved_start, movable_reserved_size; +extern unsigned long low_power_memory_start, low_power_memory_size; + #ifdef CONFIG_MEMORY_HOTPLUG /* @@ -232,4 +235,14 @@ extern void sparse_remove_one_section(struct zone *zone, struct mem_section *ms) extern struct page *sparse_decode_mem_map(unsigned long coded_mem_map, unsigned long pnum); +extern void reserve_hotplug_pages(unsigned long start_pfn, + unsigned long nr_pages); +extern void unreserve_hotplug_pages(unsigned long start_pfn, + unsigned long nr_pages); #endif /* __LINUX_MEMORY_HOTPLUG_H */ +extern int physical_remove_memory(u64 start, u64 size); +extern int arch_physical_remove_memory(u64 start, u64 size); +extern int physical_low_power_memory(u64 start, u64 size); +extern int arch_physical_low_power_memory(u64 start, u64 size); +extern int physical_active_memory(u64 start, u64 size); +extern int arch_physical_active_memory(u64 start, u64 size); diff --git a/include/linux/mfd/Kbuild b/include/linux/mfd/Kbuild new file mode 100644 index 00000000000..7f4d8b36e63 --- /dev/null +++ b/include/linux/mfd/Kbuild @@ -0,0 +1,2 @@ +header-y += timpani-audio.h +header-y += msm-adie-codec.h diff --git a/include/linux/mfd/marimba-codec.h b/include/linux/mfd/marimba-codec.h new file mode 100644 index 00000000000..bfda1469361 --- /dev/null +++ b/include/linux/mfd/marimba-codec.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __LINUX_MFD_MSM_MARIMBA_CODEC_H +#define __LINUX_MFD_MSM_MARIMBA_CODEC_H + +#include + +struct adie_codec_register { + u8 reg; + u8 mask; + u8 val; +}; + +struct adie_codec_register_image { + struct adie_codec_register *regs; + u32 img_sz; +}; + +struct adie_codec_path { + struct adie_codec_dev_profile *profile; + struct adie_codec_register_image img; + u32 hwsetting_idx; + u32 stage_idx; + u32 curr_stage; +}; + +int adie_codec_open(struct adie_codec_dev_profile *profile, + struct adie_codec_path **path_pptr); +int adie_codec_setpath(struct adie_codec_path *path_ptr, + u32 freq_plan, u32 osr); +int adie_codec_proceed_stage(struct adie_codec_path *path_ptr, u32 state); +int adie_codec_close(struct adie_codec_path *path_ptr); +u32 adie_codec_freq_supported(struct adie_codec_dev_profile *profile, + u32 requested_freq); +int adie_codec_enable_sidetone(struct adie_codec_path *rx_path_ptr, u32 enable); + +int adie_codec_set_device_digital_volume(struct adie_codec_path *path_ptr, + u32 num_channels, u32 vol_percentage /* in percentage */); + +int adie_codec_set_device_analog_volume(struct adie_codec_path *path_ptr, + u32 num_channels, u32 volume /* in percentage */); +#endif diff --git a/include/linux/mfd/marimba-tsadc.h b/include/linux/mfd/marimba-tsadc.h new file mode 100644 index 00000000000..6a05b43b939 --- /dev/null +++ b/include/linux/mfd/marimba-tsadc.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _MARIMBA_TSADC_H_ +#define _MARIMBA_TSADC_H_ + +struct marimba_tsadc_client; + +#define TSSC_SUSPEND_LEVEL 1 +#define TSADC_SUSPEND_LEVEL 2 + +int marimba_tsadc_start(struct marimba_tsadc_client *client); + +struct marimba_tsadc_client * +marimba_tsadc_register(struct platform_device *pdev, unsigned int is_ts); + +void marimba_tsadc_unregister(struct marimba_tsadc_client *client); + +#endif /* _MARIMBA_TSADC_H_ */ diff --git a/include/linux/mfd/marimba.h b/include/linux/mfd/marimba.h new file mode 100644 index 00000000000..32fe748a84e --- /dev/null +++ b/include/linux/mfd/marimba.h @@ -0,0 +1,191 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm Marimba Core Driver header file + */ + +#ifndef _MARIMBA_H +#define _MARIMBA_H_ + +#include +#include +#include +#include + +#define MARIMBA_NUM_CHILD 4 + +#define MARIMBA_SLAVE_ID_MARIMBA 0x00 +#define MARIMBA_SLAVE_ID_FM 0x01 +#define MARIMBA_SLAVE_ID_CDC 0x02 +#define MARIMBA_SLAVE_ID_QMEMBIST 0x03 + +#define MARIMBA_ID_TSADC 0x04 + +#define BAHAMA_SLAVE_ID_FM_ID 0x02 +#define SLAVE_ID_BAHAMA 0x05 +#define SLAVE_ID_BAHAMA_FM 0x07 +#define SLAVE_ID_BAHAMA_QMEMBIST 0x08 + +#if defined(CONFIG_ARCH_MSM7X30) +#define MARIMBA_SSBI_ADAP 0x7 +#elif defined(CONFIG_ARCH_MSM8X60) +#define MARIMBA_SSBI_ADAP 0X8 +#endif + +enum chip_id { + MARIMBA_ID = 0, + TIMPANI_ID, + BAHAMA_ID, + CHIP_ID_MAX +}; + +enum bahama_version { + BAHAMA_VER_1_0, + BAHAMA_VER_2_0, + BAHAMA_VER_UNSUPPORTED = 0xFF +}; +enum { + BT_PCM_ON, + BT_PCM_OFF, + FM_I2S_ON, + FM_I2S_OFF, +}; +struct marimba { + struct i2c_client *client; + + struct i2c_msg xfer_msg[2]; + + struct mutex xfer_lock; + + int mod_id; +}; + +struct marimba_top_level_platform_data { + int slave_id; /* Member added for eg. */ +}; + +struct marimba_fm_platform_data { + int irq; + int (*fm_setup)(struct marimba_fm_platform_data *pdata); + void (*fm_shutdown)(struct marimba_fm_platform_data *pdata); + struct vreg *vreg_s2; + struct vreg *vreg_xo_out; + /* + This is to indicate whether Fm SoC is I2S master/slave + false - FM SoC is I2S slave + true - FM SoC is I2S master + */ + bool is_fm_soc_i2s_master; + int (*config_i2s_gpio)(int mode); +}; + +struct marimba_codec_platform_data { + int (*marimba_codec_power)(int vreg_on); + void (*snddev_profile_init) (void); +}; + +struct marimba_tsadc_setup_params { + bool pen_irq_en; + bool tsadc_en; +}; + +enum sample_period { + TSADC_CLK_3 = 0, + TSADC_CLK_24, + TSADC_CLK_36, + TSADC_CLK_48, + TSADC_CLK_1, + TSADC_CLK_2, + TSADC_CLK_6, + TSADC_CLK_12, + TSADC_CLOCK_MAX +}; + +struct marimba_tsadc_config_params2 { + unsigned long input_clk_khz; + enum sample_period sample_prd; +}; + +struct marimba_tsadc_config_params3 { + unsigned long prechg_time_nsecs; + unsigned long stable_time_nsecs; + unsigned long tsadc_test_mode; +}; + +struct marimba_tsadc_platform_data { + int (*marimba_tsadc_power)(int vreg_on); + int (*init)(void); + int (*exit)(void); + int (*level_vote)(int vote_on); + bool tsadc_prechg_en; + bool can_wakeup; + struct marimba_tsadc_setup_params setup; + struct marimba_tsadc_config_params2 params2; + struct marimba_tsadc_config_params3 params3; + + struct msm_ts_platform_data *tssc_data; +}; + +/* + * Marimba Platform Data + * */ +struct marimba_platform_data { + struct marimba_top_level_platform_data *marimba_tp_level; + struct marimba_fm_platform_data *fm; + struct marimba_codec_platform_data *codec; + struct marimba_tsadc_platform_data *tsadc; + u8 slave_id[(MARIMBA_NUM_CHILD + 1) * CHIP_ID_MAX]; + u32 (*marimba_setup) (void); + void (*marimba_shutdown) (void); + u32 (*bahama_setup) (void); + u32 (*bahama_shutdown) (int); + u32 (*marimba_gpio_config) (int); + u32 (*bahama_core_config) (int type); + u32 tsadc_ssbi_adap; +}; + +/* + * Read and Write to register + * */ +int marimba_read(struct marimba *, u8 reg, u8 *value, unsigned num_bytes); +int marimba_write(struct marimba *, u8 reg, u8 *value, unsigned num_bytes); + +/* + * Read and Write single 8 bit register with bit mask + * */ +int marimba_read_bit_mask(struct marimba *, u8 reg, u8 *value, + unsigned num_bytes, u8 mask); +int marimba_write_bit_mask(struct marimba *, u8 reg, u8 *value, + unsigned num_bytes, u8 mask); + +/* + * Read and Write to TSADC registers across the SSBI + * * */ +int marimba_ssbi_read(struct marimba *, u16 reg, u8 *value, int len); +int marimba_ssbi_write(struct marimba *, u16 reg , u8 *value, int len); + +/* Read and write to Timpani */ +int timpani_read(struct marimba*, u8 reg, u8 *value, unsigned num_bytes); +int timpani_write(struct marimba*, u8 reg, u8 *value, + unsigned num_bytes); + +/* Get the detected codec type */ +int adie_get_detected_codec_type(void); +int adie_get_detected_connectivity_type(void); +int marimba_gpio_config(int gpio_value); +bool marimba_get_fm_status(struct marimba *); +bool marimba_get_bt_status(struct marimba *); +void marimba_set_fm_status(struct marimba *, bool); +void marimba_set_bt_status(struct marimba *, bool); +int marimba_read_bahama_ver(struct marimba *); +#endif diff --git a/include/linux/mfd/msm-adie-codec.h b/include/linux/mfd/msm-adie-codec.h new file mode 100644 index 00000000000..651d34a5c05 --- /dev/null +++ b/include/linux/mfd/msm-adie-codec.h @@ -0,0 +1,146 @@ +#ifndef __LINUX_MFD_MSM_ADIE_CODEC_H +#define __LINUX_MFD_MSM_ADIE_CODEC_H + +#include + +/* Value Represents a entry */ +#define ADIE_CODEC_ACTION_ENTRY 0x1 +/* Value representing a delay wait */ +#define ADIE_CODEC_ACTION_DELAY_WAIT 0x2 +/* Value representing a stage reached */ +#define ADIE_CODEC_ACTION_STAGE_REACHED 0x3 + +/* This value is the state after the client sets the path */ +#define ADIE_CODEC_PATH_OFF 0x0050 + +/* State to which client asks the drv to proceed to where it can + * set up the clocks and 0-fill PCM buffers + */ +#define ADIE_CODEC_DIGITAL_READY 0x0100 + +/* State to which client asks the drv to proceed to where it can + * start sending data after internal steady state delay + */ +#define ADIE_CODEC_DIGITAL_ANALOG_READY 0x1000 + + +/* Client Asks adie to switch off the Analog portion of the + * the internal codec. After the use of this path + */ +#define ADIE_CODEC_ANALOG_OFF 0x0750 + + +/* Client Asks adie to switch off the digital portion of the + * the internal codec. After switching off the analog portion. + * + * 0-fill PCM may or maynot be sent at this point + * + */ +#define ADIE_CODEC_DIGITAL_OFF 0x0600 + +/* State to which client asks the drv to write the default values + * to the registers */ +#define ADIE_CODEC_FLASH_IMAGE 0x0001 + +/* Path type */ +#define ADIE_CODEC_RX 0 +#define ADIE_CODEC_TX 1 +#define ADIE_CODEC_LB 3 +#define ADIE_CODEC_MAX 4 + +#define ADIE_CODEC_PACK_ENTRY(reg, mask, val) ((val)|(mask << 8)|(reg << 16)) + +#define ADIE_CODEC_UNPACK_ENTRY(packed, reg, mask, val) \ + do { \ + ((reg) = ((packed >> 16) & (0xff))); \ + ((mask) = ((packed >> 8) & (0xff))); \ + ((val) = ((packed) & (0xff))); \ + } while (0); + +struct adie_codec_action_unit { + u32 type; + u32 action; +}; + +struct adie_codec_hwsetting_entry{ + struct adie_codec_action_unit *actions; + u32 action_sz; + u32 freq_plan; + u32 osr; + /* u32 VolMask; + * u32 SidetoneMask; + */ +}; + +struct adie_codec_dev_profile { + u32 path_type; /* RX or TX */ + u32 setting_sz; + struct adie_codec_hwsetting_entry *settings; +}; + +struct adie_codec_register { + u8 reg; + u8 mask; + u8 val; +}; + +struct adie_codec_register_image { + struct adie_codec_register *regs; + u32 img_sz; +}; + +struct adie_codec_path; + +struct adie_codec_anc_data { + u32 size; + u32 writes[]; +}; + +struct adie_codec_operations { + int codec_id; + int (*codec_open) (struct adie_codec_dev_profile *profile, + struct adie_codec_path **path_pptr); + int (*codec_close) (struct adie_codec_path *path_ptr); + int (*codec_setpath) (struct adie_codec_path *path_ptr, + u32 freq_plan, u32 osr); + int (*codec_proceed_stage) (struct adie_codec_path *path_ptr, + u32 state); + u32 (*codec_freq_supported) (struct adie_codec_dev_profile *profile, + u32 requested_freq); + int (*codec_enable_sidetone) (struct adie_codec_path *rx_path_ptr, + u32 enable); + int (*codec_enable_anc) (struct adie_codec_path *rx_path_ptr, + u32 enable, struct adie_codec_anc_data *calibration_writes); + int (*codec_set_device_digital_volume) ( + struct adie_codec_path *path_ptr, + u32 num_channels, + u32 vol_percentage); + + int (*codec_set_device_analog_volume) (struct adie_codec_path *path_ptr, + u32 num_channels, + u32 volume); + int (*codec_set_master_mode) (struct adie_codec_path *path_ptr, + u8 master); +}; + +int adie_codec_register_codec_operations( + const struct adie_codec_operations *codec_ops); +int adie_codec_open(struct adie_codec_dev_profile *profile, + struct adie_codec_path **path_pptr); +int adie_codec_setpath(struct adie_codec_path *path_ptr, + u32 freq_plan, u32 osr); +int adie_codec_proceed_stage(struct adie_codec_path *path_ptr, u32 state); +int adie_codec_close(struct adie_codec_path *path_ptr); +u32 adie_codec_freq_supported(struct adie_codec_dev_profile *profile, + u32 requested_freq); +int adie_codec_enable_sidetone(struct adie_codec_path *rx_path_ptr, u32 enable); +int adie_codec_enable_anc(struct adie_codec_path *rx_path_ptr, u32 enable, + struct adie_codec_anc_data *calibration_writes); +int adie_codec_set_device_digital_volume(struct adie_codec_path *path_ptr, + u32 num_channels, u32 vol_percentage /* in percentage */); + +int adie_codec_set_device_analog_volume(struct adie_codec_path *path_ptr, + u32 num_channels, u32 volume /* in percentage */); + +int adie_codec_set_master_mode(struct adie_codec_path *path_ptr, u8 master); +#endif diff --git a/include/linux/mfd/pm8921-adc.h b/include/linux/mfd/pm8921-adc.h new file mode 100644 index 00000000000..3de621de2c1 --- /dev/null +++ b/include/linux/mfd/pm8921-adc.h @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * Qualcomm PMIC 8921 ADC driver header file + * + */ + +#ifndef __MFD_PM8921_ADC_H +#define __MFD_PM8921_ADC_H + +#include +#include + +/** + * enum pm8921_adc_channels - PM8921 AMUX arbiter channels + * %CHANNEL_VCOIN: Backup voltage for certain register set + * %CHANNEL_VBAT: Battery voltage + * %CHANNEL_DCIN: Charger input voltage without internal OVP + * %CHANNEL_ICHG: Charge-current monitor + * %CHANNEL_VPH_PWR: Main system power + * %CHANNEL_IBAT: Battery charge current + * %CHANNEL_MPP_1: 16:1 pre-mux unity scale MPP input + * %CHANNEL_MPP_2: 16:1 pre-mux 1/3 scale MPP input + * %CHANNEL_BATT_THERM: Battery temperature + * %CHANNEL_BATT_ID: Battery detection + * %CHANNEL_USBIN: Charger input voltage with internal OVP + * %CHANNEL_DIE_TEMP: Pmic_die temperature + * %CHANNEL_625MV: 625mv reference channel + * %CHANNEL_125V: 1.25v reference channel + * %CHANNEL_CHG_TEMP: Charger temperature + * %CHANNEL_MUXOFF: Channel to reduce input load on the mux + * %CHANNEL_NONE: Do not use this channel + */ +enum pm8921_adc_channels { + CHANNEL_VCOIN = 0, + CHANNEL_VBAT, + CHANNEL_DCIN, + CHANNEL_ICHG, + CHANNEL_VPH_PWR, + CHANNEL_IBAT, + CHANNEL_MPP_1, + CHANNEL_MPP_2, + CHANNEL_BATT_THERM, + CHANNEL_BATT_ID, + CHANNEL_USBIN, + CHANNEL_DIE_TEMP, + CHANNEL_625MV, + CHANNEL_125V, + CHANNEL_CHG_TEMP, + CHANNEL_MUXOFF, + CHANNEL_NONE, +}; + +/** + * enum pm8921_adc_mpp_channels - PM8921 AMUX arbiter MPP channels + * Yet to be defined, each of the value is representative + * of the device connected to the MPP + * %ADC_MPP_AMUX8: Fixed mappaing to PA THERM + */ +enum pm8921_adc_mpp_channels { + ADC_MPP_ATEST_8 = 0, + ADC_MPP_USB_SNS_DIV20, + ADC_MPP_DCIN_SNS_DIV20, + ADC_MPP_AMUX3, + ADC_MPP_AMUX4, + ADC_MPP_AMUX5, + ADC_MPP_AMUX6, + ADC_MPP_AMUX7, + ADC_MPP_AMUX8, + ADC_MPP_ATEST_1, + ADC_MPP_ATEST_2, + ADC_MPP_ATEST_3, + ADC_MPP_ATEST_4, + ADC_MPP_ATEST_5, + ADC_MPP_ATEST_6, + ADC_MPP_ATEST_7, + ADC_MPP_CHANNEL_NONE, +}; + +#define PM8921_ADC_PMIC_0 0x0 + +#define PM8921_CHANNEL_ADC_625_MV 625 + +#define PM8921_AMUX_MPP_3 0x3 +#define PM8921_AMUX_MPP_4 0x4 +#define PM8921_AMUX_MPP_5 0x5 +#define PM8921_AMUX_MPP_6 0x6 +#define PM8921_AMUX_MPP_8 0x8 + +#define PM8921_ADC_DEV_NAME "pm8921-adc" + +/** + * enum pm8921_adc_decimation_type - Sampling rate supported + * %ADC_DECIMATION_TYPE1: 512 + * %ADC_DECIMATION_TYPE2: 1K + * %ADC_DECIMATION_TYPE3: 2K + * %ADC_DECIMATION_TYPE4: 4k + * %ADC_DECIMATION_NONE: Do not use this Sampling type + * + * The Sampling rate is specific to each channel of the PM8921 ADC arbiter. + */ +enum pm8921_adc_decimation_type { + ADC_DECIMATION_TYPE1 = 0, + ADC_DECIMATION_TYPE2, + ADC_DECIMATION_TYPE3, + ADC_DECIMATION_TYPE4, + ADC_DECIMATION_NONE, +}; + +/** + * enum pm8921_adc_calib_type - PM8921 ADC Calibration type + * %ADC_CALIB_ABSOLUTE: Use 625mV and 1.25V reference channels + * %ADC_CALIB_RATIOMETRIC: Use reference Voltage/GND + * %ADC_CALIB_CONFIG_NONE: Do not use this calibration type + * + * Use the input reference voltage depending on the calibration type + * to calcluate the offset and gain parameters. The calibration is + * specific to each channel of the PM8921 ADC. + */ +enum pm8921_adc_calib_type { + ADC_CALIB_ABSOLUTE = 0, + ADC_CALIB_RATIOMETRIC, + ADC_CALIB_CONFIG_NONE, +}; + +/** + * enum pm8921_adc_channel_scaling_param - pre-scaling AMUX ratio + * %CHAN_PATH_SCALING1: ratio of {1, 1} + * %CHAN_PATH_SCALING2: ratio of {1, 3} + * %CHAN_PATH_SCALING3: ratio of {1, 4} + * %CHAN_PATH_SCALING4: ratio of {1, 6} + * %CHAN_PATH_NONE: Do not use this pre-scaling ratio type + * + * The pre-scaling is applied for signals to be within the voltage range + * of the ADC. + */ +enum pm8921_adc_channel_scaling_param { + CHAN_PATH_SCALING1 = 0, + CHAN_PATH_SCALING2, + CHAN_PATH_SCALING3, + CHAN_PATH_SCALING4, + CHAN_PATH_SCALING_NONE, +}; + +/** + * enum pm8921_adc_amux_input_rsv - HK/XOADC reference voltage + * %AMUX_RSV0: XO_IN/XOADC_GND + * %AMUX_RSV1: PMIC_IN/XOADC_GND + * %AMUX_RSV2: PMIC_IN/BMS_CSP + * %AMUX_RSV3: not used + * %AMUX_RSV4: XOADC_GND/XOADC_GND + * %AMUX_RSV5: XOADC_VREF/XOADC_GND + * %AMUX_NONE: Do not use this input reference voltage selection + */ +enum pm8921_adc_amux_input_rsv { + AMUX_RSV0 = 0, + AMUX_RSV1, + AMUX_RSV2, + AMUX_RSV3, + AMUX_RSV4, + AMUX_RSV5, + AMUX_NONE, +}; + +/** + * enum pm8921_adc_premux_mpp_scale_type - 16:1 pre-mux scale ratio + * %PREMUX_MPP_SCALE_0: No scaling to the input signal + * %PREMUX_MPP_SCALE_1: Unity scaling selected by the user for MPP input + * %PREMUX_MPP_SCALE_1_DIV3: 1/3 pre-scale to the input MPP signal + * %PREMUX_MPP_NONE: Do not use this pre-scale mpp type + */ +enum pm8921_adc_premux_mpp_scale_type { + PREMUX_MPP_SCALE_0 = 0, + PREMUX_MPP_SCALE_1, + PREMUX_MPP_SCALE_1_DIV3, + PREMUX_MPP_NONE, +}; + +/** + * enum pm8921_adc_scale_fn_type - Scaling function for pm8921 pre calibrated + * digital data relative to ADC reference + * %ADC_SCALE_DEFAULT: Default scaling to convert raw adc code to voltage + * %ADC_SCALE_BATT_THERM: Conversion to temperature based on btm parameters + * %ADC_SCALE_PMIC_THERM: Returns result in milli degree's Centigrade + * %ADC_SCALE_XTERN_CHGR_CUR: Returns current across 0.1 ohm resistor + * %ADC_SCALE_NONE: Do not use this scaling type + */ +enum pm8921_adc_scale_fn_type { + ADC_SCALE_DEFAULT = 0, + ADC_SCALE_BATT_THERM, + ADC_SCALE_PMIC_THERM, + ADC_SCALE_XTERN_CHGR_CUR, + ADC_SCALE_NONE, +}; + +/** + * struct pm8921_adc_linear_graph - Represent ADC characteristics + * @offset: Offset with respect to the actual curve + * @dy: Numerator slope to calculate the gain + * @dx: Denominator slope to calculate the gain + * + * Each ADC device has different offset and gain parameters which are computed + * to calibrate the device. + */ +struct pm8921_adc_linear_graph { + int32_t offset; + int32_t dy; + int32_t dx; +}; + +/** + * struct pm8921_adc_map_pt - Map the graph representation for ADC channel + * @x: Represent the ADC digitized code + * @y: Represent the physical data which can be temperature, voltage, + * resistance + */ +struct pm8921_adc_map_pt { + int32_t x; + int32_t y; +}; + +/** + * struct pm8921_adc_scaling_ratio - Represent scaling ratio for adc input + * @num: Numerator scaling parameter + * @den: Denominator scaling parameter + */ +struct pm8921_adc_scaling_ratio { + int32_t num; + int32_t den; +}; + +/** + * struct pm8921_adc_properties - Represent the ADC properties + * @adc_reference: Reference voltage for PM8921 ADC + * @bitresolution: ADC bit resolution for PM8921 ADC + * @biploar: Polarity for PM8921 ADC + */ +struct pm8921_adc_properties { + uint32_t adc_vdd_reference; + uint32_t bitresolution; + bool bipolar; +}; + +/** + * struct pm8921_adc_chan_properties - Represent channel properties of the ADC + * @offset_gain_numerator: The inverse numerator of the gain applied to the + * input channel + * @offset_gain_denominator: The inverse denominator of the gain applied to the + * input channel + * @adc_graph: ADC graph for the channel of struct type pm8921_adc_linear_graph + */ +struct pm8921_adc_chan_properties { + uint32_t offset_gain_numerator; + uint32_t offset_gain_denominator; + struct pm8921_adc_linear_graph adc_graph[2]; +}; + +/** + * struct pm8921_adc_chan_result - Represent the result of the PM8921 ADC + * @chan: The channel number of the requested conversion + * @adc_code: The pre-calibrated digital output of a given ADC relative to the + * the ADC reference + * @measurement: In units specific for a given ADC; most ADC uses reference + * voltage but some ADC uses reference current. This measurement + * here is a number relative to a reference of a given ADC + * @physical: The data meaningful for each individual channel whether it is + * voltage, current, temperature, etc. + */ +struct pm8921_adc_chan_result { + uint32_t chan; + int32_t adc_code; + int64_t measurement; + int64_t physical; +}; + +#if defined(CONFIG_MFD_PM8921_ADC) || defined(CONFIG_MFD_PM8921_ADC_MODULE) +/** + * pm8921_adc_scale_default() - Scales the pre-calibrated digital output + * of an ADC to the ADC reference and compensates for the + * gain and offset. + * @adc_code: pre-calibrated digital ouput of the ADC. + * @adc_prop: adc properties of the pm8921 adc such as bit resolution, + * reference voltage. + * @chan_prop: individual channel properties to compensate the i/p scaling, + * slope and offset. + * @chan_rslt: Physical result to be stored. + */ +int32_t pm8921_adc_scale_default(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt); +/** + * pm8921_adc_scale_tdkntcg_therm() - Scales the pre-calibrated digital output + * of an ADC to the ADC reference and compensates for the + * gain and offset. Returns the temperature of the xo therm in mili + degC. + * @adc_code: pre-calibrated digital ouput of the ADC. + * @adc_prop: adc properties of the pm8921 adc such as bit resolution, + * reference voltage. + * @chan_prop: individual channel properties to compensate the i/p scaling, + * slope and offset. + * @chan_rslt: physical result to be stored. + */ +int32_t pm8921_adc_tdkntcg_therm(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt); +/** + * pm8921_adc_scale_batt_therm() - Scales the pre-calibrated digital output + * of an ADC to the ADC reference and compensates for the + * gain and offset. Returns the temperature in degC. + * @adc_code: pre-calibrated digital ouput of the ADC. + * @adc_prop: adc properties of the pm8921 adc such as bit resolution, + * reference voltage. + * @chan_prop: individual channel properties to compensate the i/p scaling, + * slope and offset. + * @chan_rslt: physical result to be stored. + */ +int32_t pm8921_adc_scale_batt_therm(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt); +/** + * pm8921_adc_scale_pmic_therm() - Scales the pre-calibrated digital output + * of an ADC to the ADC reference and compensates for the + * gain and offset. Performs the AMUX out as 2mv/K and returns + * the temperature in mili degC. + * @adc_code: pre-calibrated digital ouput of the ADC. + * @adc_prop: adc properties of the pm8921 adc such as bit resolution, + * reference voltage. + * @chan_prop: individual channel properties to compensate the i/p scaling, + * slope and offset. + * @chan_rslt: physical result to be stored. + */ +int32_t pm8921_adc_scale_pmic_therm(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt); +/** + * pm8921_adc_scale_xtern_chgr_cur() - Scales the pre-calibrated digital output + * of an ADC to the ADC reference and compensates for the + * gain and offset. Returns the current across the 10m ohm + * resistor. + * @adc_code: pre-calibrated digital ouput of the ADC. + * @adc_prop: adc properties of the pm8921 adc such as bit resolution, + * reference voltage. + * @chan_prop: individual channel properties to compensate the i/p scaling, + * slope and offset. + * @chan_rslt: physical result to be stored. + */ +int32_t pm8921_adc_scale_xtern_chgr_cur(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt); + +#else +static inline int32_t pm8921_adc_scale_default(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt) +{ return -ENXIO; } +static inline int32_t pm8921_adc_tdkntcg_therm(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt) +{ return -ENXIO; } +static inline int32_t pm8921_adc_scale_batt_therm(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt) +{ return -ENXIO; } +static inline int32_t pm8921_adc_scale_pmic_therm(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt) +{ return -ENXIO; } +static inline int32_t pm8921_adc_scale_xtern_chgr_cur(int32_t adc_code, + const struct pm8921_adc_properties *adc_prop, + const struct pm8921_adc_chan_properties *chan_prop, + struct pm8921_adc_chan_result *chan_rslt) +{ return -ENXIO; } +#endif + +/** + * struct pm8921_adc_scale_fn - Scaling function prototype + * @chan: Function pointer to one of the scaling functions + * which takes the adc properties, channel properties, + * and returns the physical result + */ +struct pm8921_adc_scale_fn { + int32_t (*chan) (int32_t, + const struct pm8921_adc_properties *, + const struct pm8921_adc_chan_properties *, + struct pm8921_adc_chan_result *); +}; + +/** + * struct pm8921_adc_amux - AMUX properties for individual channel + * @name: Channel name + * @channel_name: Channel in integer used from pm8921_adc_channels + * @chan_path_prescaling: Channel scaling performed on the input signal + * @adc_rsv: Input reference Voltage/GND selection to the ADC + * @adc_decimation: Sampling rate desired for the channel + * adc_scale_fn: Scaling function to convert to the data meaningful for + * each individual channel whether it is voltage, current, + * temperature, etc and compensates the channel properties + */ +struct pm8921_adc_amux { + char *name; + enum pm8921_adc_channels channel_name; + enum pm8921_adc_channel_scaling_param chan_path_prescaling; + enum pm8921_adc_amux_input_rsv adc_rsv; + enum pm8921_adc_decimation_type adc_decimation; + enum pm8921_adc_scale_fn_type adc_scale_fn; +}; + +/** + * struct pm8921_adc_arb_btm - PM8921 ADC BTM parameters + * @btm_prop: BTM parameters such as input resistance, voltage and Rtherm across + * the thermistor + * @btm_param: BTM temperature thresholds and interval to program the BTM + * @btm_channel_prop: Channel specific properties of the BTM channel + */ +struct pm8921_adc_arb_btm { + struct pm8921_adc_btm_prop *btm_prop; + struct pm8921_adc_arb_btm_param *btm_param; + struct pm8921_adc_btm_channel_properties *btm_channel_prop; +}; + +/** + * struct pm8921_adc_btm_channel_properties - PM8921 ADC BTM channel properties + * @btm_channel: Channel name + * @decimation: Sampling rate + * @btm_rsv: Input selection of Vref/GND + * @chan_prop: Channel properties for the BTM channel + */ +struct pm8921_adc_btm_channel_properties { + enum pm8921_adc_channels btm_channel; + enum pm8921_adc_decimation_type decimation; + enum pm8921_adc_amux_input_rsv btm_rsv; + struct pm8921_adc_chan_properties *chan_prop; +}; + +/** + * struct pm8921_adc_btm_prop - BTM specific resistors, voltage reference to + * calcluate the temperature across Rthm + * @rs1: Resistor of the Vref_therm + * @rs2: Resistor of BTM + * @r_thm: Resistance of the thermistor + * vref_thm: Voltage of vref_therm + */ +struct pm8921_adc_btm_prop { + uint32_t rs_1; + uint32_t rs_2; + uint32_t r_thm; + uint32_t vref_thm; +}; + +/** + * struct pm8921_adc_arb_btm_param - PM8921 ADC BTM parameters to set threshold + * temperature for client notification + * @low_thr_temp: low temperature threshold request for notification + * @high_thr_temp: high temperature threshold request for notification + * @low_thr_voltage: low temperature converted to voltage by arbiter driver + * @high_thr_voltage: high temperature converted to voltage by arbiter driver + * @interval: Interval period to check for temperature notification + * @btm_warm_fn: Remote function call for warm threshold + * @btm_cold_fn: Remote function call for cold threshold + * + * BTM client passes the parameters to be set for the + * temperature threshold notifications. The client is + * responsible for setting the new threshold + * levels once the thresholds are reached + */ +struct pm8921_adc_arb_btm_param { + uint32_t low_thr_temp; + uint32_t high_thr_temp; + uint32_t low_thr_voltage; + uint32_t high_thr_voltage; + int32_t interval; + void (*btm_warm_fn) (void); + void (*btm_cold_fn) (void); +}; + +int32_t pm8921_adc_batt_scaler(struct pm8921_adc_arb_btm_param *); + +/** + * struct pm8921_adc_platform_data - PM8921 ADC platform data + * @adc_prop: ADC specific parameters, voltage and channel setup + * @adc_channel: Channel properties of the ADC arbiter + * @adc_num_channel: Total number of chanels supported + */ +struct pm8921_adc_platform_data { + struct pm8921_adc_properties *adc_prop; + struct pm8921_adc_amux *adc_channel; + uint32_t adc_num_channel; + u32 adc_wakeup; +}; + +/* Public API */ +#if defined(CONFIG_MFD_PM8921_ADC) || defined(CONFIG_MFD_PM8921_ADC_MODULE) +/** + * pm8921_adc_read() - Performs ADC read on the channel. + * @channel: Input channel to perform the ADC read. + * @result: Structure pointer of type adc_chan_result + * in which the ADC read results are stored. + */ +uint32_t pm8921_adc_read(enum pm8921_adc_channels channel, + struct pm8921_adc_chan_result *result); +/** + * pm8921_mpp_adc_read() - Performs ADC read on the channel. + * @channel: Input channel to perform the ADC read. + * @result: Structure pointer of type adc_chan_result + * in which the ADC read results are stored. + * @mpp_scale: The pre scale value to be performed to the input signal + * passed. Currently the pre-scale support is for 1 and 1/3. + */ +uint32_t pm8921_adc_mpp_read(enum pm8921_adc_mpp_channels channel, + struct pm8921_adc_chan_result *result, + enum pm8921_adc_premux_mpp_scale_type); +/** + * pm8921_adc_btm_start() - Configure the BTM registers and start + monitoring the BATT_THERM channel for + threshold warm/cold temperature set + by the Battery client. The btm_start + api is to be used after calling the + pm8921_btm_configure() api which sets + the temperature thresholds, interval + and functions to call when warm/cold + events are triggered. + * @param: none. + */ +uint32_t pm8921_adc_btm_start(void); + +/** + * pm8921_adc_btm_end() - Configures the BTM registers to stop + * monitoring the BATT_THERM channel for + * warm/cold events and disables the + * interval timer. + * @param: none. + */ +uint32_t pm8921_adc_btm_end(void); + +/** + * pm8921_adc_btm_configure() - Configures the BATT_THERM channel + * parameters for warm/cold thresholds. + * Sets the interval timer for perfoming + * reading the temperature done by the HW. + * @btm_param: Structure pointer of type adc_arb_btm_param * + * which client provides for threshold warm/cold, + * interval and functions to call when warm/cold + * events are triggered. + */ +uint32_t pm8921_adc_btm_configure(struct pm8921_adc_arb_btm_param *); +#else +static inline uint32_t pm8921_adc_read(uint32_t channel, + struct pm8921_adc_chan_result *result) +{ return -ENXIO; } +static inline uint32_t pm8921_mpp_adc_read(uint32_t channel, + struct pm8921_adc_chan_result *result, + enum pm8921_adc_premux_mpp_scale_type scale) +{ return -ENXIO; } +static inline uint32_t pm8921_adc_btm_start(void) +{ return -ENXIO; } +static inline uint32_t pm8921_adc_btm_end(void) +{ return -ENXIO; } +static inline uint32_t pm8921_adc_btm_configure( + struct pm8921_adc_arb_btm_param *param) +{ return -ENXIO; } +#endif + +#endif /* MFD_PM8921_ADC_H */ diff --git a/include/linux/mfd/pm8xxx/batt-alarm.h b/include/linux/mfd/pm8xxx/batt-alarm.h new file mode 100644 index 00000000000..eb874e923fa --- /dev/null +++ b/include/linux/mfd/pm8xxx/batt-alarm.h @@ -0,0 +1,200 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * Qualcomm PMIC PM8xxx Battery Alarm driver + * + */ +#ifndef __MFD_PM8XXX_BATT_ALARM_H__ +#define __MFD_PM8XXX_BATT_ALARM_H__ + +#include +#include + +#define PM8XXX_BATT_ALARM_DEV_NAME "pm8xxx-batt-alarm" + +/** + * enum pm8xxx_batt_alarm_core_data - PMIC core specific core passed into the + * batter alarm driver as platform data + * @irq_name: + * @reg_addr_batt_alarm_threshold: PMIC threshold register address + * @reg_addr_batt_alarm_ctrl1: PMIC control 1 register address + * @reg_addr_batt_alarm_ctrl2: PMIC control 2 register address + * @reg_addr_batt_alarm_pwm_ctrl: PMIC PWM control register address + */ +struct pm8xxx_batt_alarm_core_data { + char *irq_name; + u16 reg_addr_threshold; + u16 reg_addr_ctrl1; + u16 reg_addr_ctrl2; + u16 reg_addr_pwm_ctrl; +}; + +/** + * enum pm8xxx_batt_alarm_comparator - battery alarm comparator ID values + */ +enum pm8xxx_batt_alarm_comparator { + PM8XXX_BATT_ALARM_LOWER_COMPARATOR, + PM8XXX_BATT_ALARM_UPPER_COMPARATOR, +}; + +/** + * enum pm8xxx_batt_alarm_hold_time - hold time required for out of range + * battery voltage needed to trigger a status change. Enum names denote + * hold time in milliseconds. + */ +enum pm8xxx_batt_alarm_hold_time { + PM8XXX_BATT_ALARM_HOLD_TIME_0p125_MS = 0, + PM8XXX_BATT_ALARM_HOLD_TIME_0p25_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_0p5_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_1_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_2_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_4_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_8_MS, + PM8XXX_BATT_ALARM_HOLD_TIME_16_MS, +}; + +/* + * Bits that are set in the return value of pm8xxx_batt_alarm_status_read + * to indicate crossing of the upper or lower threshold. + */ +#define PM8XXX_BATT_ALARM_STATUS_BELOW_LOWER BIT(0) +#define PM8XXX_BATT_ALARM_STATUS_ABOVE_UPPER BIT(1) + +#if defined(CONFIG_MFD_PM8XXX_BATT_ALARM) \ + || defined(CONFIG_MFD_PM8XXX_BATT_ALARM_MODULE) + +/** + * pm8xxx_batt_alarm_enable - enable one of the battery voltage threshold + * comparators + * @comparator: selects which comparator to enable + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_enable(enum pm8xxx_batt_alarm_comparator comparator); + +/** + * pm8xxx_batt_alarm_disable - disable one of the battery voltage threshold + * comparators + * @comparator: selects which comparator to disable + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_disable(enum pm8xxx_batt_alarm_comparator comparator); + + +/** + * pm8xxx_batt_alarm_threshold_set - set the lower and upper alarm thresholds + * @comparator: selects which comparator to set the threshold of + * @threshold_mV: battery voltage threshold in millivolts + * set points = 2500-5675 mV in 25 mV steps + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_threshold_set( + enum pm8xxx_batt_alarm_comparator comparator, int threshold_mV); + +/** + * pm8xxx_batt_alarm_status_read - get status of both threshold comparators + * + * RETURNS: < 0 = error + * 0 = battery voltage ok + * BIT(0) set = battery voltage below lower threshold + * BIT(1) set = battery voltage above upper threshold + */ +int pm8xxx_batt_alarm_status_read(void); + +/** + * pm8xxx_batt_alarm_register_notifier - register a notifier to run when a + * battery voltage change interrupt fires + * @nb: notifier block containing callback function to register + * + * nb->notifier_call must point to a function of this form - + * int (*notifier_call)(struct notifier_block *nb, unsigned long status, + * void *unused); + * "status" will receive the battery alarm status; "unused" will be NULL. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_register_notifier(struct notifier_block *nb); + +/** + * pm8xxx_batt_alarm_unregister_notifier - unregister a notifier that is run + * when a battery voltage change interrupt fires + * @nb: notifier block containing callback function to unregister + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_unregister_notifier(struct notifier_block *nb); + +/** + * pm8xxx_batt_alarm_hold_time_set - set hold time of interrupt output * + * @hold_time: amount of time that battery voltage must remain outside of the + * threshold range before the battery alarm interrupt triggers + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_hold_time_set(enum pm8xxx_batt_alarm_hold_time hold_time); + +/** + * pm8xxx_batt_alarm_pwm_rate_set - set battery alarm update rate * + * @use_pwm: 1 = use PWM update rate, 0 = comparators always active + * @clock_scaler: PWM clock scaler = 2 to 9 + * @clock_divider: PWM clock divider = 2 to 8 + * + * This function sets the rate at which the battery alarm module enables + * the threshold comparators. The rate is determined by the following equation: + * + * f_update = (1024 Hz) / (clock_divider * (2 ^ clock_scaler)) + * + * Thus, the update rate can range from 0.25 Hz to 128 Hz. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_batt_alarm_pwm_rate_set(int use_pwm, int clock_scaler, + int clock_divider); +#else + +static inline int +pm8xxx_batt_alarm_enable(enum pm8xxx_batt_alarm_comparator comparator) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_disable(enum pm8xxx_batt_alarm_comparator comparator) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_threshold_set(enum pm8xxx_batt_alarm_comparator comparator, + int threshold_mV) +{ return -ENODEV; } + +static inline int pm8xxx_batt_alarm_status_read(void) +{ return -ENODEV; } + +static inline int pm8xxx_batt_alarm_register_notifier(struct notifier_block *nb) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_unregister_notifier(struct notifier_block *nb) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_hold_time_set(enum pm8xxx_batt_alarm_hold_time hold_time) +{ return -ENODEV; } + +static inline int +pm8xxx_batt_alarm_pwm_rate_set(int use_pwm, int clock_scaler, int clock_divider) +{ return -ENODEV; } + +#endif + + +#endif /* __MFD_PM8XXX_BATT_ALARM_H__ */ diff --git a/include/linux/mfd/pm8xxx/core.h b/include/linux/mfd/pm8xxx/core.h index bd2f4f64e93..74c35bf4c34 100644 --- a/include/linux/mfd/pm8xxx/core.h +++ b/include/linux/mfd/pm8xxx/core.h @@ -20,15 +20,43 @@ #include +enum pm8xxx_version { + PM8XXX_VERSION_8058, + PM8XXX_VERSION_8901, + PM8XXX_VERSION_8921, +}; + +/* PMIC version specific silicon revisions */ +#define PM8XXX_REVISION_8058_TEST 0 +#define PM8XXX_REVISION_8058_1p0 1 +#define PM8XXX_REVISION_8058_2p0 2 +#define PM8XXX_REVISION_8058_2p1 3 + +#define PM8XXX_REVISION_8901_TEST 0 +#define PM8XXX_REVISION_8901_1p0 1 +#define PM8XXX_REVISION_8901_1p1 2 +#define PM8XXX_REVISION_8901_2p0 3 +#define PM8XXX_REVISION_8901_2p1 4 + +#define PM8XXX_REVISION_8921_TEST 0 +#define PM8XXX_REVISION_8921_1p0 1 +#define PM8XXX_REVISION_8921_1p1 2 +#define PM8XXX_REVISION_8921_2p0 3 + struct pm8xxx_drvdata { - int (*pmic_readb) (const struct device *dev, u16 addr, u8 *val); - int (*pmic_writeb) (const struct device *dev, u16 addr, u8 val); - int (*pmic_read_buf) (const struct device *dev, u16 addr, u8 *buf, - int n); - int (*pmic_write_buf) (const struct device *dev, u16 addr, u8 *buf, - int n); - int (*pmic_read_irq_stat) (const struct device *dev, int irq); - void *pm_chip_data; + int (*pmic_readb) (const struct device *dev, + u16 addr, u8 *val); + int (*pmic_writeb) (const struct device *dev, + u16 addr, u8 val); + int (*pmic_read_buf) (const struct device *dev, + u16 addr, u8 *buf, int n); + int (*pmic_write_buf) (const struct device *dev, + u16 addr, u8 *buf, int n); + int (*pmic_read_irq_stat) (const struct device *dev, + int irq); + enum pm8xxx_version (*pmic_get_version) (const struct device *dev); + int (*pmic_get_revision) (const struct device *dev); + void *pm_chip_data; }; static inline int pm8xxx_readb(const struct device *dev, u16 addr, u8 *val) @@ -78,4 +106,22 @@ static inline int pm8xxx_read_irq_stat(const struct device *dev, int irq) return dd->pmic_read_irq_stat(dev, irq); } +static inline enum pm8xxx_version pm8xxx_get_version(const struct device *dev) +{ + struct pm8xxx_drvdata *dd = dev_get_drvdata(dev); + + if (!dd) + return -EINVAL; + return dd->pmic_get_version(dev); +} + +static inline int pm8xxx_get_revision(const struct device *dev) +{ + struct pm8xxx_drvdata *dd = dev_get_drvdata(dev); + + if (!dd) + return -EINVAL; + return dd->pmic_get_revision(dev); +} + #endif diff --git a/include/linux/mfd/pm8xxx/gpio.h b/include/linux/mfd/pm8xxx/gpio.h new file mode 100644 index 00000000000..0a9c95df3f6 --- /dev/null +++ b/include/linux/mfd/pm8xxx/gpio.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Qualcomm PMIC8XXX gpio driver header file + * + */ + +#ifndef __PM8XXX_GPIO_H +#define __PM8XXX_GPIO_H + +#include + +#define PM8XXX_GPIO_DEV_NAME "pm8xxx-gpio" + +struct pm8xxx_gpio_core_data { + int ngpios; +}; + +struct pm8xxx_gpio_platform_data { + struct pm8xxx_gpio_core_data gpio_cdata; + int gpio_base; +}; + +/* GPIO parameters */ +/* direction */ +#define PM_GPIO_DIR_OUT 0x01 +#define PM_GPIO_DIR_IN 0x02 +#define PM_GPIO_DIR_BOTH (PM_GPIO_DIR_OUT | PM_GPIO_DIR_IN) + +/* output_buffer */ +#define PM_GPIO_OUT_BUF_OPEN_DRAIN 1 +#define PM_GPIO_OUT_BUF_CMOS 0 + +/* pull */ +#define PM_GPIO_PULL_UP_30 0 +#define PM_GPIO_PULL_UP_1P5 1 +#define PM_GPIO_PULL_UP_31P5 2 +#define PM_GPIO_PULL_UP_1P5_30 3 +#define PM_GPIO_PULL_DN 4 +#define PM_GPIO_PULL_NO 5 + +/* vin_sel: Voltage Input Select */ +#define PM_GPIO_VIN_VPH 0 /* 3v ~ 4.4v */ +#define PM_GPIO_VIN_BB 1 /* ~3.3v */ +#define PM_GPIO_VIN_S4 2 /* 1.8v */ +#define PM_GPIO_VIN_L15 3 +#define PM_GPIO_VIN_L4 4 +#define PM_GPIO_VIN_L3 5 +#define PM_GPIO_VIN_L17 6 + +/* out_strength */ +#define PM_GPIO_STRENGTH_NO 0 +#define PM_GPIO_STRENGTH_HIGH 1 +#define PM_GPIO_STRENGTH_MED 2 +#define PM_GPIO_STRENGTH_LOW 3 + +/* function */ +#define PM_GPIO_FUNC_NORMAL 0 +#define PM_GPIO_FUNC_PAIRED 1 +#define PM_GPIO_FUNC_1 2 +#define PM_GPIO_FUNC_2 3 +#define PM_GPIO_DTEST1 4 +#define PM_GPIO_DTEST2 5 +#define PM_GPIO_DTEST3 6 +#define PM_GPIO_DTEST4 7 + +/** + * struct pm_gpio - structure to specify gpio configurtion values + * @direction: indicates whether the gpio should be input, output, or + * both. Should be of the type PM_GPIO_DIR_* + * @output_buffer: indicates gpio should be configured as CMOS or open + * drain. Should be of the type PM_GPIO_OUT_BUF_* + * @output_value: The gpio output value of the gpio line - 0 or 1 + * @pull: Indicates whether a pull up or pull down should be + * applied. If a pullup is required the current strength + * needs to be specified. Current values of 30uA, 1.5uA, + * 31.5uA, 1.5uA with 30uA boost are supported. This value + * should be one of the PM_GPIO_PULL_* + * @vin_sel: specifies the voltage level when the output is set to 1. + * For an input gpio specifies the voltage level at which + * the input is interpreted as a logical 1. + * @out_strength: the amount of current supplied for an output gpio, + * should be of the type PM_GPIO_STRENGTH_* + * @function: choose alternate function for the gpio. Certain gpios + * can be paired (shorted) with each other. Some gpio pin + * can act as alternate functions. This parameter should + * be of type PM_GPIO_FUNC_* + * @inv_int_pol: Invert polarity before feeding the line to the interrupt + * module in pmic. This feature will almost be never used + * since the pm8xxx interrupt block can detect both edges + * and both levels. + * @disable_pin: Disable the gpio by configuring it as high impedance. + */ +struct pm_gpio { + int direction; + int output_buffer; + int output_value; + int pull; + int vin_sel; + int out_strength; + int function; + int inv_int_pol; + int disable_pin; +}; + +#if defined(CONFIG_GPIO_PM8XXX) || defined(CONFIG_GPIO_PM8XXX_MODULE) +/** + * pm8xxx_gpio_config - configure a gpio controlled by a pm8xxx chip + * @gpio: gpio number to configure + * @param: configuration values + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_gpio_config(int gpio, struct pm_gpio *param); +#else +static inline int pm8xxx_gpio_config(int gpio, struct pm_gpio *param) +{ + return -ENXIO; +} +#endif + +#endif diff --git a/include/linux/mfd/pm8xxx/misc.h b/include/linux/mfd/pm8xxx/misc.h new file mode 100644 index 00000000000..17ec31b738c --- /dev/null +++ b/include/linux/mfd/pm8xxx/misc.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MFD_PM8XXX_MISC_H__ +#define __MFD_PM8XXX_MISC_H__ + +#include + +#define PM8XXX_MISC_DEV_NAME "pm8xxx-misc" + +/** + * struct pm8xxx_misc_platform_data - PM8xxx misc driver platform data + * @priority: PMIC prority level in a multi-PMIC system. Lower value means + * greater priority. Actions are performed from highest to lowest + * priority PMIC. + */ +struct pm8xxx_misc_platform_data { + int priority; +}; + +#if defined(CONFIG_MFD_PM8XXX_MISC) || defined(CONFIG_MFD_PM8XXX_MISC_MODULE) + +/** + * pm8xxx_reset_pwr_off - switch all PM8XXX PMIC chips attached to the system to + * either reset or shutdown when they are turned off + * @reset: 0 = shudown the PMICs, 1 = shutdown and then restart the PMICs + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_reset_pwr_off(int reset); + +#else + +static inline int pm8xxx_reset_pwr_off(int reset) +{ + return -ENODEV; +} + +#endif + +#endif diff --git a/include/linux/mfd/pm8xxx/mpp.h b/include/linux/mfd/pm8xxx/mpp.h new file mode 100644 index 00000000000..1bec5a2f8af --- /dev/null +++ b/include/linux/mfd/pm8xxx/mpp.h @@ -0,0 +1,242 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PM8XXX_MPP_H +#define __PM8XXX_MPP_H + +#include + +#define PM8XXX_MPP_DEV_NAME "pm8xxx-mpp" + +struct pm8xxx_mpp_core_data { + int base_addr; + int nmpps; +}; + +struct pm8xxx_mpp_platform_data { + struct pm8xxx_mpp_core_data core_data; + int mpp_base; +}; + +/** + * struct pm8xxx_mpp_config_data - structure to specify mpp configuration values + * @type: MPP type which determines the overall MPP function (i.e. digital + * in/out/bi, analog in/out, current sink, or test). It should be + * set to the value of one of PM8XXX_MPP_TYPE_D_*. + * @level: meaning depends upon MPP type specified + * @control: meaning depends upon MPP type specified + * + * Usage of level argument: + * 1. type = PM8XXX_MPP_TYPE_D_INPUT, PM8XXX_MPP_TYPE_D_OUTPUT, + * PM8XXX_MPP_TYPE_D_BI_DIR, or PM8XXX_MPP_TYPE_DTEST_OUTPUT - + * + * level specifies that digital logic level to use for the MPP. It should + * be set to the value of one of PM8XXX_MPP_DIG_LEVEL_*. Actual regulator + * connections for these level choices are PMIC chip specific. + * + * 2. type = PM8XXX_MPP_TYPE_A_INPUT - + * + * level specifies where in the PMIC chip the analog input value should + * be routed to. It should be set to the value of one of + * PM8XXX_MPP_AIN_AMUX_*. + * + * 3. type = PM8XXX_MPP_TYPE_A_OUTPUT - + * + * level specifies the output analog voltage reference level. It should + * be set to the value of one of PM8XXX_MPP_AOUT_LVL_*. + * + * 4. type = PM8XXX_MPP_TYPE_SINK or PM8XXX_MPP_TYPE_DTEST_SINK - + * + * level specifies the output current level. It should be set to the value + * of one of PM8XXX_MPP_CS_OUT_*. + * + * Usage of control argument: + * 1. type = PM8XXX_MPP_TYPE_D_INPUT - + * + * control specifies how the digital input should be routed in the chip. + * It should be set to the value of one of PM8XXX_MPP_DIN_TO_*. + * + * 2. type = PM8XXX_MPP_TYPE_D_OUTPUT - + * + * control specifies the digital output value. It should be set to the + * value of one of PM8XXX_MPP_DOUT_CTRL_*. + * + * 3. type = PM8XXX_MPP_TYPE_D_BI_DIR - + * + * control specifies the pullup resistor value. It should be set to the + * value of one of PM8XXX_MPP_BI_PULLUP_*. + * + * 4. type = PM8XXX_MPP_TYPE_A_INPUT - + * + * control is unused; a value of 0 is sufficient. + * + * 5. type = PM8XXX_MPP_TYPE_A_OUTPUT - + * + * control specifies if analog output is enabled. It should be set to the + * value of one of PM8XXX_MPP_AOUT_CTRL_*. + * + * 6. type = PM8XXX_MPP_TYPE_SINK - + * + * control specifies if current sinking is enabled. It should be set to + * the value of one of PM8XXX_MPP_CS_CTRL_*. + * + * 7. type = PM8XXX_MPP_TYPE_DTEST_SINK - + * + * control specifies if current sinking is enabled. It should be set to + * the value of one of PM8XXX_MPP_DTEST_CS_CTRL_*. + * + * 8. type = PM8XXX_MPP_TYPE_DTEST_OUTPUT - + * + * control specifies which DTEST bus value to output. It should be set to + * the value of one of PM8XXX_MPP_DTEST_*. + */ +struct pm8xxx_mpp_config_data { + unsigned type; + unsigned level; + unsigned control; +}; + +/* API */ +#if defined(CONFIG_GPIO_PM8XXX_MPP) || defined(CONFIG_GPIO_PM8XXX_MPP_MODULE) + +/** + * pm8xxx_mpp_config() - configure control options of a multi-purpose pin (MPP) + * @mpp: global GPIO number corresponding to the MPP + * @config: configuration to set for this MPP + * Context: can sleep + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8xxx_mpp_config(unsigned mpp, struct pm8xxx_mpp_config_data *config); + +#else + +static inline int pm8xxx_mpp_config(unsigned mpp, + struct pm8xxx_mpp_config_data *config) +{ + return -ENXIO; +} + +#endif + +/* MPP Type: type */ +#define PM8XXX_MPP_TYPE_D_INPUT 0 +#define PM8XXX_MPP_TYPE_D_OUTPUT 1 +#define PM8XXX_MPP_TYPE_D_BI_DIR 2 +#define PM8XXX_MPP_TYPE_A_INPUT 3 +#define PM8XXX_MPP_TYPE_A_OUTPUT 4 +#define PM8XXX_MPP_TYPE_SINK 5 +#define PM8XXX_MPP_TYPE_DTEST_SINK 6 +#define PM8XXX_MPP_TYPE_DTEST_OUTPUT 7 + +/* Digital Input/Output: level */ +#define PM8XXX_MPP_DIG_LEVEL_VIO_0 0 +#define PM8XXX_MPP_DIG_LEVEL_VIO_1 1 +#define PM8XXX_MPP_DIG_LEVEL_VIO_2 2 +#define PM8XXX_MPP_DIG_LEVEL_VIO_3 3 +#define PM8XXX_MPP_DIG_LEVEL_VIO_4 4 +#define PM8XXX_MPP_DIG_LEVEL_VIO_5 5 +#define PM8XXX_MPP_DIG_LEVEL_VIO_6 6 +#define PM8XXX_MPP_DIG_LEVEL_VIO_7 7 + +/* Digital Input/Output: level [PM8058] */ +#define PM8058_MPP_DIG_LEVEL_VPH 0 +#define PM8058_MPP_DIG_LEVEL_S3 1 +#define PM8058_MPP_DIG_LEVEL_L2 2 +#define PM8058_MPP_DIG_LEVEL_L3 3 + +/* Digital Input/Output: level [PM8901] */ +#define PM8901_MPP_DIG_LEVEL_MSMIO 0 +#define PM8901_MPP_DIG_LEVEL_DIG 1 +#define PM8901_MPP_DIG_LEVEL_L5 2 +#define PM8901_MPP_DIG_LEVEL_S4 3 +#define PM8901_MPP_DIG_LEVEL_VPH 4 + +/* Digital Input/Output: level [PM8921] */ +#define PM8921_MPP_DIG_LEVEL_S4 1 +#define PM8921_MPP_DIG_LEVEL_L15 3 +#define PM8921_MPP_DIG_LEVEL_L17 4 +#define PM8921_MPP_DIG_LEVEL_VPH 7 + +/* Digital Input: control */ +#define PM8XXX_MPP_DIN_TO_INT 0 +#define PM8XXX_MPP_DIN_TO_DBUS1 1 +#define PM8XXX_MPP_DIN_TO_DBUS2 2 +#define PM8XXX_MPP_DIN_TO_DBUS3 3 + +/* Digital Output: control */ +#define PM8XXX_MPP_DOUT_CTRL_LOW 0 +#define PM8XXX_MPP_DOUT_CTRL_HIGH 1 +#define PM8XXX_MPP_DOUT_CTRL_MPP 2 +#define PM8XXX_MPP_DOUT_CTRL_INV_MPP 3 + +/* Bidirectional: control */ +#define PM8XXX_MPP_BI_PULLUP_1KOHM 0 +#define PM8XXX_MPP_BI_PULLUP_OPEN 1 +#define PM8XXX_MPP_BI_PULLUP_10KOHM 2 +#define PM8XXX_MPP_BI_PULLUP_30KOHM 3 + +/* Analog Input: level */ +#define PM8XXX_MPP_AIN_AMUX_CH5 0 +#define PM8XXX_MPP_AIN_AMUX_CH6 1 +#define PM8XXX_MPP_AIN_AMUX_CH7 2 +#define PM8XXX_MPP_AIN_AMUX_CH8 3 +#define PM8XXX_MPP_AIN_AMUX_CH9 4 +#define PM8XXX_MPP_AIN_AMUX_ABUS1 5 +#define PM8XXX_MPP_AIN_AMUX_ABUS2 6 +#define PM8XXX_MPP_AIN_AMUX_ABUS3 7 + +/* Analog Output: level */ +#define PM8XXX_MPP_AOUT_LVL_1V25 0 +#define PM8XXX_MPP_AOUT_LVL_1V25_2 1 +#define PM8XXX_MPP_AOUT_LVL_0V625 2 +#define PM8XXX_MPP_AOUT_LVL_0V3125 3 +#define PM8XXX_MPP_AOUT_LVL_MPP 4 +#define PM8XXX_MPP_AOUT_LVL_ABUS1 5 +#define PM8XXX_MPP_AOUT_LVL_ABUS2 6 +#define PM8XXX_MPP_AOUT_LVL_ABUS3 7 + +/* Analog Output: control */ +#define PM8XXX_MPP_AOUT_CTRL_DISABLE 0 +#define PM8XXX_MPP_AOUT_CTRL_ENABLE 1 +#define PM8XXX_MPP_AOUT_CTRL_MPP_HIGH_EN 2 +#define PM8XXX_MPP_AOUT_CTRL_MPP_LOW_EN 3 + +/* Current Sink: level */ +#define PM8XXX_MPP_CS_OUT_5MA 0 +#define PM8XXX_MPP_CS_OUT_10MA 1 +#define PM8XXX_MPP_CS_OUT_15MA 2 +#define PM8XXX_MPP_CS_OUT_20MA 3 +#define PM8XXX_MPP_CS_OUT_25MA 4 +#define PM8XXX_MPP_CS_OUT_30MA 5 +#define PM8XXX_MPP_CS_OUT_35MA 6 +#define PM8XXX_MPP_CS_OUT_40MA 7 + +/* Current Sink: control */ +#define PM8XXX_MPP_CS_CTRL_DISABLE 0 +#define PM8XXX_MPP_CS_CTRL_ENABLE 1 +#define PM8XXX_MPP_CS_CTRL_MPP_HIGH_EN 2 +#define PM8XXX_MPP_CS_CTRL_MPP_LOW_EN 3 + +/* DTEST Current Sink: control */ +#define PM8XXX_MPP_DTEST_CS_CTRL_EN1 0 +#define PM8XXX_MPP_DTEST_CS_CTRL_EN2 1 +#define PM8XXX_MPP_DTEST_CS_CTRL_EN3 2 +#define PM8XXX_MPP_DTEST_CS_CTRL_EN4 3 + +/* DTEST Digital Output: control */ +#define PM8XXX_MPP_DTEST_DBUS1 0 +#define PM8XXX_MPP_DTEST_DBUS2 1 +#define PM8XXX_MPP_DTEST_DBUS3 2 +#define PM8XXX_MPP_DTEST_DBUS4 3 + +#endif diff --git a/include/linux/mfd/pm8xxx/pm8921-bms.h b/include/linux/mfd/pm8xxx/pm8921-bms.h new file mode 100644 index 00000000000..4b60e787970 --- /dev/null +++ b/include/linux/mfd/pm8xxx/pm8921-bms.h @@ -0,0 +1,124 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PM8XXX_BMS_H +#define __PM8XXX_BMS_H + +#include + +#define PM8921_BMS_DEV_NAME "pm8921-bms" + +#define FCC_CC_COLS 5 +#define FCC_TEMP_COLS 8 + +#define PC_CC_ROWS 10 +#define PC_CC_COLS 5 + +#define PC_TEMP_ROWS 29 +#define PC_TEMP_COLS 8 + +#define MAX_SINGLE_LUT_COLS 20 + +struct single_row_lut { + int x[MAX_SINGLE_LUT_COLS]; + int y[MAX_SINGLE_LUT_COLS]; + int cols; +}; + +/** + * struct pc_sf_lut - + * @rows: number of percent charge entries should be <= PC_CC_ROWS + * @cols: number of charge cycle entries should be <= PC_CC_COLS + * @cycles: the charge cycles at which sf data is available in the table. + * The charge cycles must be in increasing order from 0 to rows. + * @percent: the percent charge at which sf data is available in the table + * The percentcharge must be in decreasing order from 0 to cols. + * @sf: the scaling factor data + */ +struct pc_sf_lut { + int rows; + int cols; + int cycles[PC_CC_COLS]; + int percent[PC_CC_ROWS]; + int sf[PC_CC_ROWS][PC_CC_COLS]; +}; + +/** + * struct pc_temp_ocv_lut - + * @rows: number of percent charge entries should be <= PC_TEMP_ROWS + * @cols: number of temperature entries should be <= PC_TEMP_COLS + * @temp: the temperatures at which ocv data is available in the table + * The temperatures must be in increasing order from 0 to rows. + * @percent: the percent charge at which ocv data is available in the table + * The percentcharge must be in decreasing order from 0 to cols. + * @ocv: the open circuit voltage + */ +struct pc_temp_ocv_lut { + int rows; + int cols; + int temp[PC_TEMP_COLS]; + int percent[PC_TEMP_ROWS]; + int ocv[PC_TEMP_ROWS][PC_TEMP_COLS]; +}; + +/** + * struct pm8921_bms_battery_data - + * @fcc: full charge capacity (mAmpHour) + * @fcc_temp_lut: table to get fcc at a given temp + * @fcc_sf_lut: table to get fcc scaling factor for given charge cycles + * @pc_temp_ocv_lut: table to get percent charge given batt temp and cycles + * @pc_sf_lut: table to get percent charge scaling factor given cycles + * and percent charge + */ +struct pm8921_bms_battery_data { + unsigned int fcc; + struct single_row_lut *fcc_temp_lut; + struct single_row_lut *fcc_sf_lut; + struct pc_temp_ocv_lut *pc_temp_ocv_lut; + struct pc_sf_lut *pc_sf_lut; +}; + +/** + * struct pm8921_bms_platform_data - + * @r_sense: sense resistor value in (mOhms) + * @i_test: current at which the unusable charger cutoff is to be + * calculated or the peak system current (mA) + * @v_failure: the voltage at which the battery is considered empty(mV) + * @calib_delay_ms: how often should the adc calculate gain and offset + * @batt_data: the battery profile data for the one used in the board + */ +struct pm8921_bms_platform_data { + unsigned int r_sense; + unsigned int i_test; + unsigned int v_failure; + unsigned int calib_delay_ms; + struct pm8921_bms_battery_data *batt_data; +}; + +#if defined(CONFIG_PM8921_BMS) || defined(CONFIG_PM8921_BMS_MODULE) +int pm8921_bms_get_percent_charge(void); +void pm8921_bms_charging_began(void); +void pm8921_bms_charging_end(void); +#else +static inline int pm8921_bms_get_percent_charge(void) +{ + return -ENXIO; +} +static inline void pm8921_bms_charging_began(void) +{ +} +static inline void pm8921_bms_charging_end(void) +{ +} +#endif + +#endif diff --git a/include/linux/mfd/pm8xxx/pm8921-charger.h b/include/linux/mfd/pm8xxx/pm8921-charger.h new file mode 100644 index 00000000000..a79d753856a --- /dev/null +++ b/include/linux/mfd/pm8xxx/pm8921-charger.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PM8XXX_CHARGER_H +#define __PM8XXX_CHARGER_H + +#include + +#define PM8921_CHARGER_DEV_NAME "pm8921-charger" + +struct pm8xxx_charger_core_data { + unsigned int vbat_channel; +}; + +/** + * struct pm8921_charger_platform_data - + * @safety_time: max charging time in minutes + * @update_time: how often the userland be updated of the charging + * @max_voltage: the max voltage the battery should be charged up to + * @min_voltage: the voltage where charging method switches from trickle + * to fast. This is also the minimum voltage the system + * operates at + * @resume_voltage: the voltage to wait for before resume charging after the + * battery has been fully charged + * @term_current: the charger current at which EOC happens + * @get_batt_capacity_percent: + * a board specific function to return battery + * capacity. If null - a default one will be used + * + */ +struct pm8921_charger_platform_data { + struct pm8xxx_charger_core_data charger_cdata; + unsigned int safety_time; + unsigned int update_time; + unsigned int max_voltage; + unsigned int min_voltage; + unsigned int resume_voltage; + unsigned int term_current; + unsigned int (*get_batt_capacity_percent) (void); +}; + +#if defined(CONFIG_PM8921_CHARGER) || defined(CONFIG_PM8921_CHARGER_MODULE) +void pm8921_charger_vbus_draw(unsigned int mA); +int pm8921_charger_register_vbus_sn(void (*callback)(int)); +void pm8921_charger_unregister_vbus_sn(void (*callback)(int)); +#else +static inline void pm8921_charger_vbus_draw(unsigned int mA) +{ +} +static inline int pm8921_charger_register_vbus_sn(void (*callback)(int)) +{ + return -ENXIO; +} +static inline void pm8921_charger_unregister_vbus_sn(void (*callback)(int)) +{ +} +#endif + +#endif diff --git a/include/linux/mfd/pm8xxx/pm8921.h b/include/linux/mfd/pm8xxx/pm8921.h index d5517fd32d1..d9ab1a11b0e 100644 --- a/include/linux/mfd/pm8xxx/pm8921.h +++ b/include/linux/mfd/pm8xxx/pm8921.h @@ -20,12 +20,111 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #define PM8921_NR_IRQS 256 +#define PM8921_NR_GPIOS 44 + +#define PM8921_NR_MPPS 12 + +#define PM8921_GPIO_BLOCK_START 24 +#define PM8921_MPP_BLOCK_START 16 +#define PM8921_IRQ_BLOCK_BIT(block, bit) ((block) * 8 + (bit)) + +/* GPIOs and MPPs [1,N] */ +#define PM8921_GPIO_IRQ(base, gpio) ((base) + \ + PM8921_IRQ_BLOCK_BIT(PM8921_GPIO_BLOCK_START, (gpio)-1)) +#define PM8921_MPP_IRQ(base, mpp) ((base) + \ + PM8921_IRQ_BLOCK_BIT(PM8921_MPP_BLOCK_START, (mpp)-1)) + +/* PMIC Interrupts */ +#define PM8921_RTC_ALARM_IRQ PM8921_IRQ_BLOCK_BIT(4, 7) +#define PM8921_BATT_ALARM_IRQ PM8921_IRQ_BLOCK_BIT(5, 6) +#define PM8921_PWRKEY_REL_IRQ PM8921_IRQ_BLOCK_BIT(6, 2) +#define PM8921_PWRKEY_PRESS_IRQ PM8921_IRQ_BLOCK_BIT(6, 3) +#define PM8921_KEYPAD_IRQ PM8921_IRQ_BLOCK_BIT(9, 2) +#define PM8921_KEYSTUCK_IRQ PM8921_IRQ_BLOCK_BIT(9, 3) +#define PM8921_ADC_EOC_USR_IRQ PM8921_IRQ_BLOCK_BIT(9, 6) +#define PM8921_ADC_BATT_TEMP_WARM_IRQ PM8921_IRQ_BLOCK_BIT(9, 1) +#define PM8921_ADC_BATT_TEMP_COLD_IRQ PM8921_IRQ_BLOCK_BIT(9, 0) +#define PM8921_USB_ID_IN_IRQ(base) (base + PM8921_IRQ_BLOCK_BIT(6, 1)) + +#define PM8921_USBIN_VALID_IRQ PM8921_IRQ_BLOCK_BIT(1, 7) +#define PM8921_USBIN_OV_IRQ PM8921_IRQ_BLOCK_BIT(1, 6) +#define PM8921_BATT_INSERTED_IRQ PM8921_IRQ_BLOCK_BIT(1, 5) +#define PM8921_VBATDET_LOW_IRQ PM8921_IRQ_BLOCK_BIT(1, 4) +#define PM8921_USBIN_UV_IRQ PM8921_IRQ_BLOCK_BIT(1, 3) +#define PM8921_VBAT_OV_IRQ PM8921_IRQ_BLOCK_BIT(1, 2) +#define PM8921_CHGWDOG_IRQ PM8921_IRQ_BLOCK_BIT(1, 1) +#define PM8921_VCP_IRQ PM8921_IRQ_BLOCK_BIT(1, 0) +#define PM8921_ATCDONE_IRQ PM8921_IRQ_BLOCK_BIT(2, 7) +#define PM8921_ATCFAIL_IRQ PM8921_IRQ_BLOCK_BIT(2, 6) +#define PM8921_CHGDONE_IRQ PM8921_IRQ_BLOCK_BIT(2, 5) +#define PM8921_CHGFAIL_IRQ PM8921_IRQ_BLOCK_BIT(2, 4) +#define PM8921_CHGSTATE_IRQ PM8921_IRQ_BLOCK_BIT(2, 3) +#define PM8921_LOOP_CHANGE_IRQ PM8921_IRQ_BLOCK_BIT(2, 2) +#define PM8921_FASTCHG_IRQ PM8921_IRQ_BLOCK_BIT(2, 1) +#define PM8921_TRKLCHG_IRQ PM8921_IRQ_BLOCK_BIT(2, 0) +#define PM8921_BATT_REMOVED_IRQ PM8921_IRQ_BLOCK_BIT(3, 7) +#define PM8921_BATTTEMP_HOT_IRQ PM8921_IRQ_BLOCK_BIT(3, 6) +#define PM8921_CHGHOT_IRQ PM8921_IRQ_BLOCK_BIT(3, 5) +#define PM8921_BATTTEMP_COLD_IRQ PM8921_IRQ_BLOCK_BIT(3, 4) +#define PM8921_CHG_GONE_IRQ PM8921_IRQ_BLOCK_BIT(3, 3) +#define PM8921_BAT_TEMP_OK_IRQ PM8921_IRQ_BLOCK_BIT(3, 2) +#define PM8921_COARSE_DET_LOW_IRQ PM8921_IRQ_BLOCK_BIT(3, 1) +#define PM8921_VDD_LOOP_IRQ PM8921_IRQ_BLOCK_BIT(3, 0) +#define PM8921_VREG_OV_IRQ PM8921_IRQ_BLOCK_BIT(5, 7) +#define PM8921_VBATDET_IRQ PM8921_IRQ_BLOCK_BIT(5, 5) +#define PM8921_BATFET_IRQ PM8921_IRQ_BLOCK_BIT(5, 4) +#define PM8921_PSI_IRQ PM8921_IRQ_BLOCK_BIT(5, 3) +#define PM8921_DCIN_VALID_IRQ PM8921_IRQ_BLOCK_BIT(5, 2) +#define PM8921_DCIN_OV_IRQ PM8921_IRQ_BLOCK_BIT(5, 1) +#define PM8921_DCIN_UV_IRQ PM8921_IRQ_BLOCK_BIT(5, 0) + +#define PM8921_BMS_SBI_WRITE_OK PM8921_IRQ_BLOCK_BIT(15, 7) +#define PM8921_BMS_CC_THR PM8921_IRQ_BLOCK_BIT(15, 6) +#define PM8921_BMS_VSENSE_THR PM8921_IRQ_BLOCK_BIT(15, 5) +#define PM8921_BMS_VSENSE_FOR_R PM8921_IRQ_BLOCK_BIT(15, 4) +#define PM8921_BMS_OCV_FOR_R PM8921_IRQ_BLOCK_BIT(15, 3) +#define PM8921_BMS_GOOD_OCV PM8921_IRQ_BLOCK_BIT(15, 2) +#define PM8921_BMS_VSENSE_AVG PM8921_IRQ_BLOCK_BIT(15, 1) +#define PM8921_BMS_CCADC_EOC PM8921_IRQ_BLOCK_BIT(15, 0) + +#define PM8921_OVERTEMP_IRQ PM8921_IRQ_BLOCK_BIT(4, 2) +#define PM8921_TEMPSTAT_IRQ PM8921_IRQ_BLOCK_BIT(6, 7) + +/* PMIC I/O Resources */ +#define PM8921_RTC_BASE 0x11D + struct pm8921_platform_data { int irq_base; struct pm8xxx_irq_platform_data *irq_pdata; + struct pm8xxx_gpio_platform_data *gpio_pdata; + struct pm8xxx_mpp_platform_data *mpp_pdata; + struct pm8xxx_rtc_platform_data *rtc_pdata; + struct pm8xxx_pwrkey_platform_data *pwrkey_pdata; + struct pm8xxx_keypad_platform_data *keypad_pdata; + struct pm8921_charger_platform_data *charger_pdata; + struct pm8921_bms_platform_data *bms_pdata; + struct pm8xxx_misc_platform_data *misc_pdata; + struct pm8921_regulator_platform_data *regulator_pdatas; + int num_regulators; + struct pm8921_adc_platform_data *adc_pdata; + struct led_platform_data *leds_pdata; }; #endif diff --git a/include/linux/mfd/pm8xxx/pwm.h b/include/linux/mfd/pm8xxx/pwm.h new file mode 100644 index 00000000000..d85eae0a712 --- /dev/null +++ b/include/linux/mfd/pm8xxx/pwm.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PM8XXX_PWM_H__ +#define __PM8XXX_PWM_H__ + +#include + +#define PM8XXX_PWM_DEV_NAME "pm8xxx-pwm" + +#define PM8XXX_PWM_PERIOD_MAX (327 * USEC_PER_SEC) +#define PM8XXX_PWM_PERIOD_MIN 7 /* micro seconds */ + +#define PM_PWM_LUT_SIZE 64 +#define PM_PWM_LUT_DUTY_TIME_MAX 512 /* ms */ +#define PM_PWM_LUT_PAUSE_MAX (7000 * PM_PWM_LUT_DUTY_TIME_MAX) + +/* Flags for Look Up Table */ +#define PM_PWM_LUT_LOOP 0x01 +#define PM_PWM_LUT_RAMP_UP 0x02 +#define PM_PWM_LUT_REVERSE 0x04 +#define PM_PWM_LUT_PAUSE_HI_EN 0x10 +#define PM_PWM_LUT_PAUSE_LO_EN 0x20 + +#define PM_PWM_LUT_NO_TABLE 0x100 + +/** + * pm8xxx_pwm_lut_config - change a PWM device configuration to use LUT + * @pwm: the PWM device + * @period_us: period in micro second + * @duty_pct: arrary of duty cycles in percent, like 20, 50. + * @duty_time_ms: time for each duty cycle in millisecond + * @start_idx: start index in lookup table from 0 to MAX-1 + * @idx_len: number of index + * @pause_lo: pause time in millisecond at low index + * @pause_hi: pause time in millisecond at high index + * @flags: control flags + */ +int pm8xxx_pwm_lut_config(struct pwm_device *pwm, int period_us, + int duty_pct[], int duty_time_ms, int start_idx, + int len, int pause_lo, int pause_hi, int flags); + +/** + * pm8xxx_pwm_lut_enable - control a PWM device to start/stop LUT ramp + * @pwm: the PWM device + * @start: to start (1), or stop (0) + */ +int pm8xxx_pwm_lut_enable(struct pwm_device *pwm, int start); + +/* Standard APIs supported */ +/** + * pwm_request - request a PWM device + * @pwm_id: PWM id or channel + * @label: the label to identify the user + */ + +/** + * pwm_free - free a PWM device + * @pwm: the PWM device + */ + +/** + * pwm_config - change a PWM device configuration + * @pwm: the PWM device + * @period_us: period in microsecond + * @duty_us: duty cycle in microsecond + */ + +/** + * pwm_enable - start a PWM output toggling + * @pwm: the PWM device + */ + +/** + * pwm_disable - stop a PWM output toggling + * @pwm: the PWM device + */ + +#endif /* __PM8XXX_PWM_H__ */ diff --git a/include/linux/mfd/pm8xxx/rtc.h b/include/linux/mfd/pm8xxx/rtc.h new file mode 100644 index 00000000000..14f1983eaec --- /dev/null +++ b/include/linux/mfd/pm8xxx/rtc.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __RTC_PM8XXX_H__ +#define __RTC_PM8XXX_H__ + +#define PM8XXX_RTC_DEV_NAME "rtc-pm8xxx" +/** + * struct pm8xxx_rtc_pdata - RTC driver platform data + * @rtc_write_enable: variable stating RTC write capability + */ +struct pm8xxx_rtc_platform_data { + bool rtc_write_enable; +}; + +#endif /* __RTC_PM8XXX_H__ */ diff --git a/include/linux/mfd/pm8xxx/tm.h b/include/linux/mfd/pm8xxx/tm.h new file mode 100644 index 00000000000..01edb971cbc --- /dev/null +++ b/include/linux/mfd/pm8xxx/tm.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Qualcomm PMIC PM8xxx Thermal Manager driver + */ + +#ifndef __PM8XXX_TM_H +#define __PM8XXX_TM_H + +#include + +#define PM8XXX_TM_DEV_NAME "pm8xxx-tm" + +enum pm8xxx_tm_adc_type { + PM8XXX_TM_ADC_NONE, /* Estimates temp based on overload level. */ + PM8XXX_TM_ADC_PM8921_ADC, +}; + +struct pm8xxx_tm_core_data { + int adc_channel; + unsigned long default_no_adc_temp; + enum pm8xxx_tm_adc_type adc_type; + u16 reg_addr_temp_alarm_ctrl; + u16 reg_addr_temp_alarm_pwm; + char *tm_name; + char *irq_name_temp_stat; + char *irq_name_over_temp; +}; + +#endif diff --git a/include/linux/mfd/pmic8058.h b/include/linux/mfd/pmic8058.h new file mode 100644 index 00000000000..4603aedfc6b --- /dev/null +++ b/include/linux/mfd/pmic8058.h @@ -0,0 +1,238 @@ +/* Copyright (c) 2009-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Qualcomm PMIC8058 driver header file + * + */ + +#include +#include + +#define PM8058_GPIOS 40 +#define PM8058_MPPS 12 + +#define PM8058_IRQ_BLOCK_BIT(block, bit) ((block) * 8 + (bit)) + +/* MPPs and GPIOs [0,N) */ +#define PM8058_MPP_IRQ(base, mpp) ((base) + \ + PM8058_IRQ_BLOCK_BIT(16, (mpp))) +#define PM8058_GPIO_IRQ(base, gpio) ((base) + \ + PM8058_IRQ_BLOCK_BIT(24, (gpio))) + +#define PM8058_KEYPAD_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(9, 2)) +#define PM8058_KEYSTUCK_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(9, 3)) + +#define PM8058_VCP_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(1, 0)) +#define PM8058_CHGILIM_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(1, 3)) +#define PM8058_VBATDET_LOW_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(1, 4)) +#define PM8058_BATT_REPLACE_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(1, 5)) +#define PM8058_CHGINVAL_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(1, 6)) +#define PM8058_CHGVAL_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(1, 7)) +#define PM8058_CHG_END_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(2, 0)) +#define PM8058_FASTCHG_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(2, 1)) +#define PM8058_CHGSTATE_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(2, 3)) +#define PM8058_AUTO_CHGFAIL_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(2, 4)) +#define PM8058_AUTO_CHGDONE_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(2, 5)) +#define PM8058_ATCFAIL_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(2, 6)) +#define PM8058_ATC_DONE_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(2, 7)) +#define PM8058_OVP_OK_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(3, 0)) +#define PM8058_COARSE_DET_OVP_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(3, 1)) +#define PM8058_VCPMAJOR_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(3, 2)) +#define PM8058_CHG_GONE_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(3, 3)) +#define PM8058_CHGTLIMIT_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(3, 4)) +#define PM8058_CHGHOT_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(3, 5)) +#define PM8058_BATTTEMP_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(3, 6)) +#define PM8058_BATTCONNECT_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(3, 7)) +#define PM8058_BATFET_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(5, 4)) +#define PM8058_VBATDET_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(5, 5)) +#define PM8058_VBAT_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(5, 6)) + +#define PM8058_CBLPWR_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(4, 3)) + +#define PM8058_PWRKEY_REL_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(6, 2)) +#define PM8058_PWRKEY_PRESS_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(6, 3)) +#define PM8058_SW_0_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(7, 1)) +#define PM8058_IR_0_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(7, 0)) +#define PM8058_SW_1_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(7, 3)) +#define PM8058_IR_1_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(7, 2)) +#define PM8058_SW_2_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(7, 5)) +#define PM8058_IR_2_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(7, 4)) +#define PM8058_RTC_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(6, 5)) +#define PM8058_RTC_ALARM_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(4, 7)) +#define PM8058_ADC_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(9, 4)) +#define PM8058_TEMP_ALARM_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(6, 7)) +#define PM8058_OSCHALT_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(4, 6)) +#define PM8058_BATT_ALARM_IRQ(base) ((base) + PM8058_IRQ_BLOCK_BIT(5, 6)) + +struct pm8058_chip; + +struct pm8058_platform_data { + /* This table is only needed for misc interrupts. */ + int irq_base; + int (*init)(struct pm8058_chip *pm_chip); + + int num_subdevs; + struct mfd_cell *sub_devices; + int irq_trigger_flags; + struct mfd_cell *charger_sub_device; +}; + +struct pm8058_gpio_platform_data { + int gpio_base; + int irq_base; + int (*init)(void); +}; + +/* GPIO parameters */ +/* direction */ +#define PM_GPIO_DIR_OUT 0x01 +#define PM_GPIO_DIR_IN 0x02 +#define PM_GPIO_DIR_BOTH (PM_GPIO_DIR_OUT | PM_GPIO_DIR_IN) + +/* output_buffer */ +#define PM_GPIO_OUT_BUF_OPEN_DRAIN 1 +#define PM_GPIO_OUT_BUF_CMOS 0 + +/* pull */ +#define PM_GPIO_PULL_UP_30 0 +#define PM_GPIO_PULL_UP_1P5 1 +#define PM_GPIO_PULL_UP_31P5 2 +#define PM_GPIO_PULL_UP_1P5_30 3 +#define PM_GPIO_PULL_DN 4 +#define PM_GPIO_PULL_NO 5 + +/* vin_sel: Voltage Input Select */ +#define PM_GPIO_VIN_VPH 0 +#define PM_GPIO_VIN_BB 1 +#define PM_GPIO_VIN_S3 2 +#define PM_GPIO_VIN_L3 3 +#define PM_GPIO_VIN_L7 4 +#define PM_GPIO_VIN_L6 5 +#define PM_GPIO_VIN_L5 6 +#define PM_GPIO_VIN_L2 7 + +/* out_strength */ +#define PM_GPIO_STRENGTH_NO 0 +#define PM_GPIO_STRENGTH_HIGH 1 +#define PM_GPIO_STRENGTH_MED 2 +#define PM_GPIO_STRENGTH_LOW 3 + +/* function */ +#define PM_GPIO_FUNC_NORMAL 0 +#define PM_GPIO_FUNC_PAIRED 1 +#define PM_GPIO_FUNC_1 2 +#define PM_GPIO_FUNC_2 3 +#define PM_GPIO_DTEST1 4 +#define PM_GPIO_DTEST2 5 +#define PM_GPIO_DTEST3 6 +#define PM_GPIO_DTEST4 7 + +struct pm8058_gpio { + int direction; + int output_buffer; + int output_value; + int pull; + int vin_sel; /* 0..7 */ + int out_strength; + int function; + int inv_int_pol; /* invert interrupt polarity */ + int disable_pin; /* disable pin and tri-state its pad */ +}; + +/* chip revision */ +#define PM_8058_REV_1p0 0xE1 +#define PM_8058_REV_2p0 0xE2 +#define PM_8058_REV_2p1 0xE3 + +/* misc: control mask and flag */ +#define PM8058_UART_MUX_MASK 0x60 + +#define PM8058_UART_MUX_NO 0x0 +#define PM8058_UART_MUX_1 0x20 +#define PM8058_UART_MUX_2 0x40 +#define PM8058_UART_MUX_3 0x60 + +enum pon_config{ + DISABLE_HARD_RESET = 0, + SHUTDOWN_ON_HARD_RESET, + RESTART_ON_HARD_RESET, + MAX_PON_CONFIG, +}; + +enum pm8058_smpl_delay { + PM8058_SMPL_DELAY_0p5, + PM8058_SMPL_DELAY_1p0, + PM8058_SMPL_DELAY_1p5, + PM8058_SMPL_DELAY_2p0, +}; + +/* Note -do not call pm8058_read and pm8058_write in an atomic context */ +int pm8058_read(struct pm8058_chip *pm_chip, u16 addr, u8 *values, + unsigned int len); +int pm8058_write(struct pm8058_chip *pm_chip, u16 addr, u8 *values, + unsigned int len); + +int pm8058_gpio_config(int gpio, struct pm8058_gpio *param); + +int pm8058_rev(struct pm8058_chip *pm_chip); + +int pm8058_irq_get_rt_status(struct pm8058_chip *pm_chip, int irq); + +int pm8058_misc_control(struct pm8058_chip *pm_chip, int mask, int flag); + +#ifdef CONFIG_PMIC8058 +int pm8058_reset_pwr_off(int reset); +#else +static inline int pm8058_reset_pwr_off(int reset) { return 0; } +#endif + + +int pm8058_hard_reset_config(enum pon_config config); + +/** + * pm8058_smpl_control - enables/disables SMPL detection + * @enable: 0 = shutdown PMIC on power loss, 1 = reset PMIC on power loss + * + * This function enables or disables the Sudden Momentary Power Loss detection + * module. If SMPL detection is enabled, then when a sufficiently long power + * loss event occurs, the PMIC will automatically reset itself. If SMPL + * detection is disabled, then the PMIC will shutdown when power loss occurs. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_smpl_control(int enable); + +/** + * pm8058_smpl_set_delay - sets the SMPL detection time delay + * @delay: enum value corresponding to delay time + * + * This function sets the time delay of the SMPL detection module. If power + * is reapplied within this interval, then the PMIC reset automatically. The + * SMPL detection module must be enabled for this delay time to take effect. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_smpl_set_delay(enum pm8058_smpl_delay delay); + +/** + * pm8058_watchdog_reset_control - enables/disables watchdog reset detection + * @enable: 0 = shutdown when PS_HOLD goes low, 1 = reset when PS_HOLD goes low + * + * This function enables or disables the PMIC watchdog reset detection feature. + * If watchdog reset detection is enabled, then the PMIC will reset itself + * when PS_HOLD goes low. If it is not enabled, then the PMIC will shutdown + * when PS_HOLD goes low. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_watchdog_reset_control(int enable); diff --git a/include/linux/mfd/pmic8901.h b/include/linux/mfd/pmic8901.h new file mode 100644 index 00000000000..8b628c5f311 --- /dev/null +++ b/include/linux/mfd/pmic8901.h @@ -0,0 +1,72 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __PMIC8901_H__ +#define __PMIC8901_H__ +/* + * Qualcomm PMIC8901 driver header file + * + */ + +#include +#include + +/* PM8901 interrupt numbers */ + +#define PM8901_MPPS 4 + +#define PM8901_IRQ_BLOCK_BIT(block, bit) ((block) * 8 + (bit)) + +/* MPPs [0,N) */ +#define PM8901_MPP_IRQ(base, mpp) ((base) + \ + PM8901_IRQ_BLOCK_BIT(6, (mpp))) + +#define PM8901_TEMP_ALARM_IRQ(base) ((base) + PM8901_IRQ_BLOCK_BIT(6, 4)) +#define PM8901_TEMP_HI_ALARM_IRQ(base) ((base) + PM8901_IRQ_BLOCK_BIT(6, 5)) + +struct pm8901_chip; + +struct pm8901_platform_data { + /* This table is only needed for misc interrupts. */ + int irq_base; + + int num_subdevs; + struct mfd_cell *sub_devices; + int irq_trigger_flags; +}; + +struct pm8901_gpio_platform_data { + int gpio_base; + int irq_base; +}; + +/* chip revision */ +#define PM_8901_REV_1p0 0xF1 +#define PM_8901_REV_1p1 0xF2 +#define PM_8901_REV_2p0 0xF3 + +int pm8901_read(struct pm8901_chip *pm_chip, u16 addr, u8 *values, + unsigned int len); +int pm8901_write(struct pm8901_chip *pm_chip, u16 addr, u8 *values, + unsigned int len); + +int pm8901_rev(struct pm8901_chip *pm_chip); + +int pm8901_irq_get_rt_status(struct pm8901_chip *pm_chip, int irq); + +#ifdef CONFIG_PMIC8901 +int pm8901_reset_pwr_off(int reset); +#else +static inline int pm8901_reset_pwr_off(int reset) { return 0; } +#endif + +#endif /* __PMIC8901_H__ */ diff --git a/include/linux/mfd/timpani-audio.h b/include/linux/mfd/timpani-audio.h new file mode 100644 index 00000000000..49fd49ba215 --- /dev/null +++ b/include/linux/mfd/timpani-audio.h @@ -0,0 +1,5016 @@ +#ifndef __LINUX_MFD_TIMPANI_AUDIO_H +#define __LINUX_MFD_TIMPANI_AUDIO_H + +/* + * MREF + */ +#define TIMPANI_A_MREF (0x3) +#define TIMPANI_MREF_RWC "RW" +#define TIMPANI_MREF_POR 0xe2 +#define TIMPANI_MREF_S 0 +#define TIMPANI_MREF_M 0xFF + +#define TIMPANI_MREF_MREF_BG_EN_S 7 +#define TIMPANI_MREF_MREF_BG_EN_M 0x80 +#define TIMPANI_MREF_MREF_BG_EN_ENABLE 0x0 +#define TIMPANI_MREF_MREF_BG_EN_DISABLE 0x1 + +#define TIMPANI_MREF_MREF_BG_REF_CUR_EN_S 6 +#define TIMPANI_MREF_MREF_BG_REF_CUR_EN_M 0x40 +#define TIMPANI_MREF_MREF_BG_REF_CUR_EN_ENABLE_NORMAL_OP 0x0 +#define TIMPANI_MREF_MREF_BG_REF_CUR_EN_DISABLE 0x1 + +#define TIMPANI_MREF_MREF_200K_MODE_EN_S 5 +#define TIMPANI_MREF_MREF_200K_MODE_EN_M 0x20 +#define TIMPANI_MREF_MREF_200K_MODE_EN_ENABLE 0x0 +#define TIMPANI_MREF_MREF_200K_MODE_EN_DISABLE 0x1 + +#define TIMPANI_MREF_MREF_PRE_CHARGE_EN_S 4 +#define TIMPANI_MREF_MREF_PRE_CHARGE_EN_M 0x10 +#define TIMPANI_MREF_MREF_PRE_CHARGE_EN_DISABLE 0x0 +#define TIMPANI_MREF_MREF_PRE_CHARGE_EN_ENABLE 0x1 + +#define TIMPANI_MREF_MREF_100UA_CUR_CONN_S 3 +#define TIMPANI_MREF_MREF_100UA_CUR_CONN_M 0x8 +#define TIMPANI_MREF_MREF_100UA_CUR_CONN_ON_CHIP_RESISTOR_NORMAL_OP 0x0 +#define TIMPANI_MREF_MREF_100UA_CUR_CONN_ATEST 0x1 + +#define TIMPANI_MREF_MREF_PTAT_CURRENT_S 2 +#define TIMPANI_MREF_MREF_PTAT_CURRENT_M 0x4 +#define TIMPANI_MREF_MREF_PTAT_CURRENT_V_10UA_PTAT_NORMAL_OP 0x0 +#define TIMPANI_MREF_MREF_PTAT_CURRENT_V_5UA_PTAT_BIAS_CURRENT 0x1 + +#define TIMPANI_MREF_MREF_400K_MODE_EN_S 1 +#define TIMPANI_MREF_MREF_400K_MODE_EN_M 0x2 +#define TIMPANI_MREF_MREF_400K_MODE_EN_ENABLE 0x0 +#define TIMPANI_MREF_MREF_400K_MODE_EN_DISABLE 0x1 + +#define TIMPANI_MREF_RESERVED_S 0 +#define TIMPANI_MREF_RESERVED_M 0x1 + + +/* For CDAC_IDAC_REF_CUR */ +#define TIMPANI_A_CDAC_IDAC_REF_CUR (0x4) +#define TIMPANI_CDAC_IDAC_REF_CUR_RWC "RW" +#define TIMPANI_CDAC_IDAC_REF_CUR_POR 0x8c +#define TIMPANI_CDAC_IDAC_REF_CUR_S 0 +#define TIMPANI_CDAC_IDAC_REF_CUR_M 0xFF + + +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_S 5 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_M 0xE0 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_V_4UA 0x0 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_V_6UA 0x1 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_V_8UA 0x2 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_V_9UA 0x3 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_V_10UA_NORMAL_OP 0x4 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_V_11UA 0x5 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_V_13UA 0x6 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_REF_BUFF_CUR_V_15UA 0x7 + +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_S 2 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_M 0x1C +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_V_8_5UA 0x0 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_V_9_0UA 0x1 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_V_9_5UA 0x2 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_V_10_0UA_NORMAL_OP 0x3 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_V_10_5UA 0x4 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_V_11_0UA 0x5 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_V_11_5UA 0x6 +#define TIMPANI_CDAC_IDAC_REF_CUR_CDAC_BIAS_CUR_V_12_0UA 0x7 + +#define TIMPANI_CDAC_IDAC_REF_CUR_IDAC_REF_CUR_S 0 +#define TIMPANI_CDAC_IDAC_REF_CUR_IDAC_REF_CUR_M 0x3 +#define TIMPANI_CDAC_IDAC_REF_CUR_IDAC_REF_CUR_V_2UA 0x0 +#define TIMPANI_CDAC_IDAC_REF_CUR_IDAC_REF_CUR_V_3UA 0x1 +#define TIMPANI_CDAC_IDAC_REF_CUR_IDAC_REF_CUR_V_5UA_NORMAL_OP 0x2 +#define TIMPANI_CDAC_IDAC_REF_CUR_IDAC_REF_CUR_V_8UA 0x3 + + +/* -- For TXADC12_REF_CURR */ +#define TIMPANI_A_TXADC12_REF_CURR (0x5) +#define TIMPANI_TXADC12_REF_CURR_RWC "RW" +#define TIMPANI_TXADC12_REF_CURR_POR 0xa0 +#define TIMPANI_TXADC12_REF_CURR_S 0 +#define TIMPANI_TXADC12_REF_CURR_M 0xFF + + +#define TIMPANI_TXADC12_REF_CURR_TXADC1_REF_BUFF_CUR_S 6 +#define TIMPANI_TXADC12_REF_CURR_TXADC1_REF_BUFF_CUR_M 0xC0 +#define TIMPANI_TXADC12_REF_CURR_TXADC1_REF_BUFF_CUR_V_50UA 0x0 +#define TIMPANI_TXADC12_REF_CURR_TXADC1_REF_BUFF_CUR_V_45UA 0x1 +#define TIMPANI_TXADC12_REF_CURR_TXADC1_REF_BUFF_CUR_V_40UA_NORMAL_OP 0x2 +#define TIMPANI_TXADC12_REF_CURR_TXADC1_REF_BUFF_CUR_V_35UA 0x3 + +#define TIMPANI_TXADC12_REF_CURR_TXADC2_REF_BUFF_CUR_S 4 +#define TIMPANI_TXADC12_REF_CURR_TXADC2_REF_BUFF_CUR_M 0x30 +#define TIMPANI_TXADC12_REF_CURR_TXADC2_REF_BUFF_CUR_V_50UA 0x0 +#define TIMPANI_TXADC12_REF_CURR_TXADC2_REF_BUFF_CUR_V_45UA 0x1 +#define TIMPANI_TXADC12_REF_CURR_TXADC2_REF_BUFF_CUR_V_40UA_NORMAL_OP 0x2 +#define TIMPANI_TXADC12_REF_CURR_TXADC2_REF_BUFF_CUR_V_35UA 0x3 + +#define TIMPANI_TXADC12_REF_CURR_RESERVED_S 0 +#define TIMPANI_TXADC12_REF_CURR_RESERVED_M 0xF + + +/* -- For TXADC3_EN */ +#define TIMPANI_A_TXADC3_EN (0x9) +#define TIMPANI_TXADC3_EN_RWC "RW" +#define TIMPANI_TXADC3_EN_POR 0 +#define TIMPANI_TXADC3_EN_S 0 +#define TIMPANI_TXADC3_EN_M 0xFF + + +#define TIMPANI_TXADC3_EN_TXADC3_REF_EN_S 7 +#define TIMPANI_TXADC3_EN_TXADC3_REF_EN_M 0x80 +#define TIMPANI_TXADC3_EN_TXADC3_REF_EN_DISABLE 0x0 +#define TIMPANI_TXADC3_EN_TXADC3_REF_EN_ENABLE 0x1 + +#define TIMPANI_TXADC3_EN_TXADC3_DAC_REF_CUR_COMPENSATION_EN_S 6 +#define TIMPANI_TXADC3_EN_TXADC3_DAC_REF_CUR_COMPENSATION_EN_M 0x40 +#define TIMPANI_TXADC3_EN_TXADC3_DAC_REF_CUR_COMPENSATION_EN_DISABLE 0x0 +#define TIMPANI_TXADC3_EN_TXADC3_DAC_REF_CUR_COMPENSATION_EN_ENABLE 0x1 + +#define TIMPANI_TXADC3_EN_TXADC3_OTA1_EN_S 5 +#define TIMPANI_TXADC3_EN_TXADC3_OTA1_EN_M 0x20 +#define TIMPANI_TXADC3_EN_TXADC3_OTA1_EN_DISABLE 0x0 +#define TIMPANI_TXADC3_EN_TXADC3_OTA1_EN_ENABLE 0x1 + +#define TIMPANI_TXADC3_EN_TXADC3_OTA2_EN_S 4 +#define TIMPANI_TXADC3_EN_TXADC3_OTA2_EN_M 0x10 +#define TIMPANI_TXADC3_EN_TXADC3_OTA2_EN_DISABLE 0x0 +#define TIMPANI_TXADC3_EN_TXADC3_OTA2_EN_ENABLE 0x1 + +#define TIMPANI_TXADC3_EN_TXADC3_COMP_EN_S 3 +#define TIMPANI_TXADC3_EN_TXADC3_COMP_EN_M 0x8 +#define TIMPANI_TXADC3_EN_TXADC3_COMP_EN_DISABLE 0x0 +#define TIMPANI_TXADC3_EN_TXADC3_COMP_EN_ENABLE 0x1 + +#define TIMPANI_TXADC3_EN_TXADC3_DEM_EN_S 2 +#define TIMPANI_TXADC3_EN_TXADC3_DEM_EN_M 0x4 +#define TIMPANI_TXADC3_EN_TXADC3_DEM_EN_DISABLE 0x0 +#define TIMPANI_TXADC3_EN_TXADC3_DEM_EN_ENABLE 0x1 + +#define TIMPANI_TXADC3_EN_TXADC3_DEM_ERROR_DET_EN_S 1 +#define TIMPANI_TXADC3_EN_TXADC3_DEM_ERROR_DET_EN_M 0x2 +#define TIMPANI_TXADC3_EN_TXADC3_DEM_ERROR_DET_EN_DISABLE 0x0 +#define TIMPANI_TXADC3_EN_TXADC3_DEM_ERROR_DET_EN_ENABLE 0x1 + +#define TIMPANI_TXADC3_EN_RESERVED_S 0 +#define TIMPANI_TXADC3_EN_RESERVED_M 0x1 + + +/* -- For TXADC4_EN */ +#define TIMPANI_A_TXADC4_EN (0xA) +#define TIMPANI_TXADC4_EN_RWC "RW" +#define TIMPANI_TXADC4_EN_POR 0 +#define TIMPANI_TXADC4_EN_S 0 +#define TIMPANI_TXADC4_EN_M 0xFF + + +#define TIMPANI_TXADC4_EN_TXADC4_REF_EN_S 7 +#define TIMPANI_TXADC4_EN_TXADC4_REF_EN_M 0x80 +#define TIMPANI_TXADC4_EN_TXADC4_REF_EN_DISABLE 0x0 +#define TIMPANI_TXADC4_EN_TXADC4_REF_EN_ENABLE 0x1 + +#define TIMPANI_TXADC4_EN_TXADC4_DAC_REF_CUR_COMPENSATION_EN_S 6 +#define TIMPANI_TXADC4_EN_TXADC4_DAC_REF_CUR_COMPENSATION_EN_M 0x40 +#define TIMPANI_TXADC4_EN_TXADC4_DAC_REF_CUR_COMPENSATION_EN_DISABLE 0x0 +#define TIMPANI_TXADC4_EN_TXADC4_DAC_REF_CUR_COMPENSATION_EN_ENABLE 0x1 + +#define TIMPANI_TXADC4_EN_TXADC4_OTA1_EN_S 5 +#define TIMPANI_TXADC4_EN_TXADC4_OTA1_EN_M 0x20 +#define TIMPANI_TXADC4_EN_TXADC4_OTA1_EN_DISABLE 0x0 +#define TIMPANI_TXADC4_EN_TXADC4_OTA1_EN_ENABLE 0x1 + +#define TIMPANI_TXADC4_EN_TXADC4_OTA2_EN_S 4 +#define TIMPANI_TXADC4_EN_TXADC4_OTA2_EN_M 0x10 +#define TIMPANI_TXADC4_EN_TXADC4_OTA2_EN_DISABLE 0x0 +#define TIMPANI_TXADC4_EN_TXADC4_OTA2_EN_ENABLE 0x1 + +#define TIMPANI_TXADC4_EN_TXADC4_COMP_EN_S 3 +#define TIMPANI_TXADC4_EN_TXADC4_COMP_EN_M 0x8 +#define TIMPANI_TXADC4_EN_TXADC4_COMP_EN_DISABLE 0x0 +#define TIMPANI_TXADC4_EN_TXADC4_COMP_EN_ENABLE 0x1 + +#define TIMPANI_TXADC4_EN_TXADC4_DEM_EN_S 2 +#define TIMPANI_TXADC4_EN_TXADC4_DEM_EN_M 0x4 +#define TIMPANI_TXADC4_EN_TXADC4_DEM_EN_DISABLE 0x0 +#define TIMPANI_TXADC4_EN_TXADC4_DEM_EN_ENABLE 0x1 + +#define TIMPANI_TXADC4_EN_TXADC4_DEM_ERROR_DET_EN_S 1 +#define TIMPANI_TXADC4_EN_TXADC4_DEM_ERROR_DET_EN_M 0x2 +#define TIMPANI_TXADC4_EN_TXADC4_DEM_ERROR_DET_EN_DISABLE 0x0 +#define TIMPANI_TXADC4_EN_TXADC4_DEM_ERROR_DET_EN_ENABLE 0x1 + +#define TIMPANI_TXADC4_EN_RESERVED_S 0 +#define TIMPANI_TXADC4_EN_RESERVED_M 0x1 + + +/* -- For CODEC_TXADC_STATUS_REGISTER_1 */ +#define TIMPANI_A_CODEC_TXADC_STATUS_REGISTER_1 (0xB) +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_RWC "R" +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_POR 0 +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_S 0 +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_M 0xFF + + +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_TXADC1_DEM_ERROR_S 7 +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_TXADC1_DEM_ERROR_M 0x80 + +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_TXADC2_DEM_ERROR_S 6 +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_TXADC2_DEM_ERROR_M 0x40 + +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_TXADC3_DEM_ERROR_S 5 +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_TXADC3_DEM_ERROR_M 0x20 + +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_TXADC4_DEM_ERROR_S 4 +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_TXADC4_DEM_ERROR_M 0x10 + +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_RESERVED_S 0 +#define TIMPANI_CODEC_TXADC_STATUS_REGISTER_1_RESERVED_M 0xF + + +/* -- For TXFE1 */ +#define TIMPANI_A_TXFE1 (0xD) +#define TIMPANI_TXFE1_RWC "RW" +#define TIMPANI_TXFE1_POR 0 +#define TIMPANI_TXFE1_S 0 +#define TIMPANI_TXFE1_M 0xFF + + +#define TIMPANI_TXFE1_TXFE1_EN_S 7 +#define TIMPANI_TXFE1_TXFE1_EN_M 0x80 +#define TIMPANI_TXFE1_TXFE1_EN_DISABLE 0x0 +#define TIMPANI_TXFE1_TXFE1_EN_ENABLE 0x1 + +#define TIMPANI_TXFE1_TXFE1_GAIN_S 5 +#define TIMPANI_TXFE1_TXFE1_GAIN_M 0x60 +#define TIMPANI_TXFE1_TXFE1_GAIN_V_0DB 0x0 +#define TIMPANI_TXFE1_TXFE1_GAIN_V_4_5DB 0x1 +#define TIMPANI_TXFE1_TXFE1_GAIN_V_24DB_1 0x2 +#define TIMPANI_TXFE1_TXFE1_GAIN_V_24DB_2 0x3 + +#define TIMPANI_TXFE1_TXFE1_IN_MIC1_CONN_S 4 +#define TIMPANI_TXFE1_TXFE1_IN_MIC1_CONN_M 0x10 +#define TIMPANI_TXFE1_TXFE1_IN_MIC1_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE1_TXFE1_IN_MIC1_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE1_TXFE1_IN_MIC2_CONN_S 3 +#define TIMPANI_TXFE1_TXFE1_IN_MIC2_CONN_M 0x8 +#define TIMPANI_TXFE1_TXFE1_IN_MIC2_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE1_TXFE1_IN_MIC2_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE1_TXFE1_IN_LINE_I_L_CONN_S 2 +#define TIMPANI_TXFE1_TXFE1_IN_LINE_I_L_CONN_M 0x4 +#define TIMPANI_TXFE1_TXFE1_IN_LINE_I_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE1_TXFE1_IN_LINE_I_L_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE1_TXFE1_IN_LINE_I_R_CONN_S 1 +#define TIMPANI_TXFE1_TXFE1_IN_LINE_I_R_CONN_M 0x2 +#define TIMPANI_TXFE1_TXFE1_IN_LINE_I_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE1_TXFE1_IN_LINE_I_R_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE1_TXFE1_IN_AUXI_CONN_S 0 +#define TIMPANI_TXFE1_TXFE1_IN_AUXI_CONN_M 0x1 +#define TIMPANI_TXFE1_TXFE1_IN_AUXI_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE1_TXFE1_IN_AUXI_CONN_CONNECT 0x1 + + +/* -- For TXFE2 */ +#define TIMPANI_A_TXFE2 (0xE) +#define TIMPANI_TXFE2_RWC "RW" +#define TIMPANI_TXFE2_POR 0 +#define TIMPANI_TXFE2_S 0 +#define TIMPANI_TXFE2_M 0xFF + + +#define TIMPANI_TXFE2_TXFE2_EN_S 7 +#define TIMPANI_TXFE2_TXFE2_EN_M 0x80 +#define TIMPANI_TXFE2_TXFE2_EN_DISABLE 0x0 +#define TIMPANI_TXFE2_TXFE2_EN_ENABLE 0x1 + +#define TIMPANI_TXFE2_TXFE2_GAIN_S 5 +#define TIMPANI_TXFE2_TXFE2_GAIN_M 0x60 +#define TIMPANI_TXFE2_TXFE2_GAIN_V_0DB 0x0 +#define TIMPANI_TXFE2_TXFE2_GAIN_V_4_5DB 0x1 +#define TIMPANI_TXFE2_TXFE2_GAIN_V_24DB_1 0x2 +#define TIMPANI_TXFE2_TXFE2_GAIN_V_24DB_2 0x3 + +#define TIMPANI_TXFE2_TXFE2_IN_MIC1_CONN_S 4 +#define TIMPANI_TXFE2_TXFE2_IN_MIC1_CONN_M 0x10 +#define TIMPANI_TXFE2_TXFE2_IN_MIC1_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE2_TXFE2_IN_MIC1_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE2_TXFE2_IN_MIC2_CONN_S 3 +#define TIMPANI_TXFE2_TXFE2_IN_MIC2_CONN_M 0x8 +#define TIMPANI_TXFE2_TXFE2_IN_MIC2_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE2_TXFE2_IN_MIC2_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE2_TXFE2_IN_LINE_I_L_CONN_S 2 +#define TIMPANI_TXFE2_TXFE2_IN_LINE_I_L_CONN_M 0x4 +#define TIMPANI_TXFE2_TXFE2_IN_LINE_I_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE2_TXFE2_IN_LINE_I_L_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE2_TXFE2_IN_LINE_I_R_CONN_S 1 +#define TIMPANI_TXFE2_TXFE2_IN_LINE_I_R_CONN_M 0x2 +#define TIMPANI_TXFE2_TXFE2_IN_LINE_I_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE2_TXFE2_IN_LINE_I_R_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE2_TXFE2_IN_AUXI_CONN_S 0 +#define TIMPANI_TXFE2_TXFE2_IN_AUXI_CONN_M 0x1 +#define TIMPANI_TXFE2_TXFE2_IN_AUXI_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE2_TXFE2_IN_AUXI_CONN_CONNECT 0x1 + + +/* -- For TXFE12_ATEST */ +#define TIMPANI_A_TXFE12_ATEST (0xF) +#define TIMPANI_TXFE12_ATEST_RWC "RW" +#define TIMPANI_TXFE12_ATEST_POR 0 +#define TIMPANI_TXFE12_ATEST_S 0 +#define TIMPANI_TXFE12_ATEST_M 0xFF + + +#define TIMPANI_TXFE12_ATEST_TXFE1_OUT_SHORT_TO_VICM_EN_S 7 +#define TIMPANI_TXFE12_ATEST_TXFE1_OUT_SHORT_TO_VICM_EN_M 0x80 +#define TIMPANI_TXFE12_ATEST_TXFE1_OUT_SHORT_TO_VICM_EN_DISABLE 0x0 +#define TIMPANI_TXFE12_ATEST_TXFE1_OUT_SHORT_TO_VICM_EN_ENABLE 0x1 + +#define TIMPANI_TXFE12_ATEST_TXFE1_BYPASS_EN_S 6 +#define TIMPANI_TXFE12_ATEST_TXFE1_BYPASS_EN_M 0x40 +#define TIMPANI_TXFE12_ATEST_TXFE1_BYPASS_EN_DISABLE 0x0 +#define TIMPANI_TXFE12_ATEST_TXFE1_BYPASS_EN_ENABLE 0x1 + +#define TIMPANI_TXFE12_ATEST_TXFE1_CMOUT_ATEST_CONN_S 5 +#define TIMPANI_TXFE12_ATEST_TXFE1_CMOUT_ATEST_CONN_M 0x20 +#define TIMPANI_TXFE12_ATEST_TXFE1_CMOUT_ATEST_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE12_ATEST_TXFE1_CMOUT_ATEST_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE12_ATEST_TXFE1_OUT_ATEST_CONN_S 4 +#define TIMPANI_TXFE12_ATEST_TXFE1_OUT_ATEST_CONN_M 0x10 +#define TIMPANI_TXFE12_ATEST_TXFE1_OUT_ATEST_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE12_ATEST_TXFE1_OUT_ATEST_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE12_ATEST_TXFE2_OUT_SHORT_TO_VICM_EN_S 3 +#define TIMPANI_TXFE12_ATEST_TXFE2_OUT_SHORT_TO_VICM_EN_M 0x8 +#define TIMPANI_TXFE12_ATEST_TXFE2_OUT_SHORT_TO_VICM_EN_DISABLE 0x0 +#define TIMPANI_TXFE12_ATEST_TXFE2_OUT_SHORT_TO_VICM_EN_ENABLE 0x1 + +#define TIMPANI_TXFE12_ATEST_TXFE2_BYPASS_EN_S 2 +#define TIMPANI_TXFE12_ATEST_TXFE2_BYPASS_EN_M 0x4 +#define TIMPANI_TXFE12_ATEST_TXFE2_BYPASS_EN_DISABLE 0x0 +#define TIMPANI_TXFE12_ATEST_TXFE2_BYPASS_EN_ENABLE 0x1 + +#define TIMPANI_TXFE12_ATEST_TXFE2_CMOUT_ATEST_CONN_S 1 +#define TIMPANI_TXFE12_ATEST_TXFE2_CMOUT_ATEST_CONN_M 0x2 +#define TIMPANI_TXFE12_ATEST_TXFE2_CMOUT_ATEST_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE12_ATEST_TXFE2_CMOUT_ATEST_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE12_ATEST_TXFE2_OUT_ATEST_CONN_S 0 +#define TIMPANI_TXFE12_ATEST_TXFE2_OUT_ATEST_CONN_M 0x1 +#define TIMPANI_TXFE12_ATEST_TXFE2_OUT_ATEST_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE12_ATEST_TXFE2_OUT_ATEST_CONN_CONNECT 0x1 + + +/* -- For TXFE_CLT */ +#define TIMPANI_A_TXFE_CLT (0x10) +#define TIMPANI_TXFE_CLT_RWC "RW" +#define TIMPANI_TXFE_CLT_POR 0x68 +#define TIMPANI_TXFE_CLT_S 0 +#define TIMPANI_TXFE_CLT_M 0xFF + + +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_S 5 +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_M 0xE0 +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_V_1_125V 0x0 +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_V_1_100V 0x1 +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_V_1_075V 0x2 +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_V_1_050V_NORMAL_OP 0x3 +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_V_1_025V 0x4 +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_V_1_000V 0x5 +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_V_0_975V 0x6 +#define TIMPANI_TXFE_CLT_TXFE_OUT_CM_VOLT_V_0_950V 0x7 + +#define TIMPANI_TXFE_CLT_TXFE_BIAS_CUR_S 3 +#define TIMPANI_TXFE_CLT_TXFE_BIAS_CUR_M 0x18 +#define TIMPANI_TXFE_CLT_TXFE_BIAS_CUR_V_3UA 0x0 +#define TIMPANI_TXFE_CLT_TXFE_BIAS_CUR_V_4UA_NORMAL_OP 0x1 +#define TIMPANI_TXFE_CLT_TXFE_BIAS_CUR_V_6UA 0x2 +#define TIMPANI_TXFE_CLT_TXFE_BIAS_CUR_V_8UA 0x3 + +#define TIMPANI_TXFE_CLT_RESERVED_S 0 +#define TIMPANI_TXFE_CLT_RESERVED_M 0x7 + + +/* -- For TXADC1_EN */ +#define TIMPANI_A_TXADC1_EN (0x11) +#define TIMPANI_TXADC1_EN_RWC "RW" +#define TIMPANI_TXADC1_EN_POR 0 +#define TIMPANI_TXADC1_EN_S 0 +#define TIMPANI_TXADC1_EN_M 0xFF + + +#define TIMPANI_TXADC1_EN_TXADC1_REF_EN_S 7 +#define TIMPANI_TXADC1_EN_TXADC1_REF_EN_M 0x80 +#define TIMPANI_TXADC1_EN_TXADC1_REF_EN_DISABLE 0x0 +#define TIMPANI_TXADC1_EN_TXADC1_REF_EN_ENABLE 0x1 + +#define TIMPANI_TXADC1_EN_TXADC1_DAC_REF_CUR_COMPENSATION_EN_S 6 +#define TIMPANI_TXADC1_EN_TXADC1_DAC_REF_CUR_COMPENSATION_EN_M 0x40 +#define TIMPANI_TXADC1_EN_TXADC1_DAC_REF_CUR_COMPENSATION_EN_DISABLE 0x0 +#define TIMPANI_TXADC1_EN_TXADC1_DAC_REF_CUR_COMPENSATION_EN_ENABLE 0x1 + +#define TIMPANI_TXADC1_EN_TXADC1_OTA1_EN_S 5 +#define TIMPANI_TXADC1_EN_TXADC1_OTA1_EN_M 0x20 +#define TIMPANI_TXADC1_EN_TXADC1_OTA1_EN_DISABLE 0x0 +#define TIMPANI_TXADC1_EN_TXADC1_OTA1_EN_ENABLE 0x1 + +#define TIMPANI_TXADC1_EN_TXADC1_OTA2_EN_S 4 +#define TIMPANI_TXADC1_EN_TXADC1_OTA2_EN_M 0x10 +#define TIMPANI_TXADC1_EN_TXADC1_OTA2_EN_DISABLE 0x0 +#define TIMPANI_TXADC1_EN_TXADC1_OTA2_EN_ENABLE 0x1 + +#define TIMPANI_TXADC1_EN_TXADC1_COMP_EN_S 3 +#define TIMPANI_TXADC1_EN_TXADC1_COMP_EN_M 0x8 +#define TIMPANI_TXADC1_EN_TXADC1_COMP_EN_DISABLE 0x0 +#define TIMPANI_TXADC1_EN_TXADC1_COMP_EN_ENABLE 0x1 + +#define TIMPANI_TXADC1_EN_TXADC1_DEM_EN_S 2 +#define TIMPANI_TXADC1_EN_TXADC1_DEM_EN_M 0x4 +#define TIMPANI_TXADC1_EN_TXADC1_DEM_EN_DISABLE 0x0 +#define TIMPANI_TXADC1_EN_TXADC1_DEM_EN_ENABLE 0x1 + +#define TIMPANI_TXADC1_EN_TXADC1_DEM_ERROR_DET_EN_S 1 +#define TIMPANI_TXADC1_EN_TXADC1_DEM_ERROR_DET_EN_M 0x2 +#define TIMPANI_TXADC1_EN_TXADC1_DEM_ERROR_DET_EN_DISABLE 0x0 +#define TIMPANI_TXADC1_EN_TXADC1_DEM_ERROR_DET_EN_ENABLE 0x1 + +#define TIMPANI_TXADC1_EN_RESERVED_S 0 +#define TIMPANI_TXADC1_EN_RESERVED_M 0x1 + + +/* -- For TXADC2_EN */ +#define TIMPANI_A_TXADC2_EN (0x12) +#define TIMPANI_TXADC2_EN_RWC "RW" +#define TIMPANI_TXADC2_EN_POR 0 +#define TIMPANI_TXADC2_EN_S 0 +#define TIMPANI_TXADC2_EN_M 0xFF + + +#define TIMPANI_TXADC2_EN_TXADC2_REF_EN_S 7 +#define TIMPANI_TXADC2_EN_TXADC2_REF_EN_M 0x80 +#define TIMPANI_TXADC2_EN_TXADC2_REF_EN_DISABLE 0x0 +#define TIMPANI_TXADC2_EN_TXADC2_REF_EN_ENABLE 0x1 + +#define TIMPANI_TXADC2_EN_TXADC2_DAC_REF_CUR_COMPENSATION_EN_S 6 +#define TIMPANI_TXADC2_EN_TXADC2_DAC_REF_CUR_COMPENSATION_EN_M 0x40 +#define TIMPANI_TXADC2_EN_TXADC2_DAC_REF_CUR_COMPENSATION_EN_DISABLE 0x0 +#define TIMPANI_TXADC2_EN_TXADC2_DAC_REF_CUR_COMPENSATION_EN_ENABLE 0x1 + +#define TIMPANI_TXADC2_EN_TXADC2_OTA1_EN_S 5 +#define TIMPANI_TXADC2_EN_TXADC2_OTA1_EN_M 0x20 +#define TIMPANI_TXADC2_EN_TXADC2_OTA1_EN_DISABLE 0x0 +#define TIMPANI_TXADC2_EN_TXADC2_OTA1_EN_ENABLE 0x1 + +#define TIMPANI_TXADC2_EN_TXADC2_OTA2_EN_S 4 +#define TIMPANI_TXADC2_EN_TXADC2_OTA2_EN_M 0x10 +#define TIMPANI_TXADC2_EN_TXADC2_OTA2_EN_DISABLE 0x0 +#define TIMPANI_TXADC2_EN_TXADC2_OTA2_EN_ENABLE 0x1 + +#define TIMPANI_TXADC2_EN_TXADC2_COMP_EN_S 3 +#define TIMPANI_TXADC2_EN_TXADC2_COMP_EN_M 0x8 +#define TIMPANI_TXADC2_EN_TXADC2_COMP_EN_DISABLE 0x0 +#define TIMPANI_TXADC2_EN_TXADC2_COMP_EN_ENABLE 0x1 + +#define TIMPANI_TXADC2_EN_TXADC2_DEM_EN_S 2 +#define TIMPANI_TXADC2_EN_TXADC2_DEM_EN_M 0x4 +#define TIMPANI_TXADC2_EN_TXADC2_DEM_EN_DISABLE 0x0 +#define TIMPANI_TXADC2_EN_TXADC2_DEM_EN_ENABLE 0x1 + +#define TIMPANI_TXADC2_EN_TXADC2_DEM_ERROR_DET_EN_S 1 +#define TIMPANI_TXADC2_EN_TXADC2_DEM_ERROR_DET_EN_M 0x2 +#define TIMPANI_TXADC2_EN_TXADC2_DEM_ERROR_DET_EN_DISABLE 0x0 +#define TIMPANI_TXADC2_EN_TXADC2_DEM_ERROR_DET_EN_ENABLE 0x1 + +#define TIMPANI_TXADC2_EN_RESERVED_S 0 +#define TIMPANI_TXADC2_EN_RESERVED_M 0x1 + + +/* -- For TXADC_CTL */ +#define TIMPANI_A_TXADC_CTL (0x13) +#define TIMPANI_TXADC_CTL_RWC "RW" +#define TIMPANI_TXADC_CTL_POR 0x58 +#define TIMPANI_TXADC_CTL_S 0 +#define TIMPANI_TXADC_CTL_M 0xFF + + +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_DDA_AMP_BIAS_CUR_S 6 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_DDA_AMP_BIAS_CUR_M 0xC0 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_DDA_AMP_BIAS_CUR_V_5UA 0x0 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_DDA_AMP_BIAS_CUR_V_10UA_NORMAL_OP 0x1 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_DDA_AMP_BIAS_CUR_V_15UA 0x2 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_DDA_AMP_BIAS_CUR_V_20UA 0x3 + +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_SRC_FOLLOWER_BIAS_CUR_S 4 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_SRC_FOLLOWER_BIAS_CUR_M 0x30 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_SRC_FOLLOWER_BIAS_CUR_V_40UA 0x0 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_SRC_FOLLOWER_BIAS_CUR_V_80UA 0x1 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_SRC_FOLLOWER_BIAS_CUR_V_120UA 0x2 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_SRC_FOLLOWER_BIAS_CUR_V_160UA 0x3 + +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_VOLT_S 2 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_VOLT_M 0xC +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_VOLT_V_1_8V 0x0 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_VOLT_V_1_7V 0x1 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_VOLT_V_1_6V_NORMAL_OP 0x2 +#define TIMPANI_TXADC_CTL_TXADC_DAC_REF_VOLT_V_1_5V 0x3 + +#define TIMPANI_TXADC_CTL_TXADC_VREFMID_BIAS_CUR_S 0 +#define TIMPANI_TXADC_CTL_TXADC_VREFMID_BIAS_CUR_M 0x3 +#define TIMPANI_TXADC_CTL_TXADC_VREFMID_BIAS_CUR_V_20UA_NORMAL_OP 0x0 +#define TIMPANI_TXADC_CTL_TXADC_VREFMID_BIAS_CUR_V_40UA 0x1 +#define TIMPANI_TXADC_CTL_TXADC_VREFMID_BIAS_CUR_V_80UA 0x2 +#define TIMPANI_TXADC_CTL_TXADC_VREFMID_BIAS_CUR_V_160UA 0x3 + + +/* -- For TXADC_CTL2 */ +#define TIMPANI_A_TXADC_CTL2 (0x14) +#define TIMPANI_TXADC_CTL2_RWC "RW" +#define TIMPANI_TXADC_CTL2_POR 0x64 +#define TIMPANI_TXADC_CTL2_S 0 +#define TIMPANI_TXADC_CTL2_M 0xFF + + +#define TIMPANI_TXADC_CTL2_TXADC_COMP_THRESH_VOLT_S 6 +#define TIMPANI_TXADC_CTL2_TXADC_COMP_THRESH_VOLT_M 0xC0 +#define TIMPANI_TXADC_CTL2_TXADC_COMP_THRESH_VOLT_V_333MV 0x0 +#define TIMPANI_TXADC_CTL2_TXADC_COMP_THRESH_VOLT_V_356MV_NORMAL_OP 0x1 +#define TIMPANI_TXADC_CTL2_TXADC_COMP_THRESH_VOLT_V_378MV 0x2 +#define TIMPANI_TXADC_CTL2_TXADC_COMP_THRESH_VOLT_V_400MV 0x3 + +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_BIAS_CUR_S 4 +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_BIAS_CUR_M 0x30 +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_BIAS_CUR_V_50UA 0x0 +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_BIAS_CUR_V_100UA 0x1 +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_BIAS_CUR_V_200UA_NORMAL_OP 0x2 +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_BIAS_CUR_V_400UA 0x3 + +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_OUT_VOLT_S 2 +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_OUT_VOLT_M 0xC +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_OUT_VOLT_V_1_1V 0x0 +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_OUT_VOLT_V_1_15V_NORMAL_OP 0x1 +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_OUT_VOLT_V_1_2V 0x2 +#define TIMPANI_TXADC_CTL2_TXADC_VICM_REF_BUFF_OUT_VOLT_V_1_25V 0x3 + +#define TIMPANI_TXADC_CTL2_TXADC_VOCM_BUFFER_BIAS_CUR_S 1 +#define TIMPANI_TXADC_CTL2_TXADC_VOCM_BUFFER_BIAS_CUR_M 0x2 +#define TIMPANI_TXADC_CTL2_TXADC_VOCM_BUFFER_BIAS_CUR_V_50UA_NORMAL_OP 0x0 +#define TIMPANI_TXADC_CTL2_TXADC_VOCM_BUFFER_BIAS_CUR_V_100UA 0x1 + +#define TIMPANI_TXADC_CTL2_TXADC_DIG_OUT_EN_S 0 +#define TIMPANI_TXADC_CTL2_TXADC_DIG_OUT_EN_M 0x1 +#define TIMPANI_TXADC_CTL2_TXADC_DIG_OUT_EN_DISABLE 0x0 +#define TIMPANI_TXADC_CTL2_TXADC_DIG_OUT_EN_ENABLE_NORMAL_OP 0x1 + + +/* -- For TXADC_CTL3 */ +#define TIMPANI_A_TXADC_CTL3 (0x15) +#define TIMPANI_TXADC_CTL3_RWC "RW" +#define TIMPANI_TXADC_CTL3_POR 0x64 +#define TIMPANI_TXADC_CTL3_S 0 +#define TIMPANI_TXADC_CTL3_M 0xFF + + +#define TIMPANI_TXADC_CTL3_TXADC_VOCM_REF_BUFF_VOLT_S 6 +#define TIMPANI_TXADC_CTL3_TXADC_VOCM_REF_BUFF_VOLT_M 0xC0 +#define TIMPANI_TXADC_CTL3_TXADC_VOCM_REF_BUFF_VOLT_V_0_85V 0x0 +#define TIMPANI_TXADC_CTL3_TXADC_VOCM_REF_BUFF_VOLT_V_0_90V_NORMAL_OP 0x1 +#define TIMPANI_TXADC_CTL3_TXADC_VOCM_REF_BUFF_VOLT_V_0_95V 0x2 +#define TIMPANI_TXADC_CTL3_TXADC_VOCM_REF_BUFF_VOLT_V_1_00V 0x3 + +#define TIMPANI_TXADC_CTL3_TXADC_OTA1_BIAS_CUR_S 4 +#define TIMPANI_TXADC_CTL3_TXADC_OTA1_BIAS_CUR_M 0x30 +#define TIMPANI_TXADC_CTL3_TXADC_OTA1_BIAS_CUR_V_10UA 0x0 +#define TIMPANI_TXADC_CTL3_TXADC_OTA1_BIAS_CUR_V_15UA 0x1 +#define TIMPANI_TXADC_CTL3_TXADC_OTA1_BIAS_CUR_V_20UA_NORMAL_OP 0x2 +#define TIMPANI_TXADC_CTL3_TXADC_OTA1_BIAS_CUR_V_25UA 0x3 + +#define TIMPANI_TXADC_CTL3_TXADC_OTA2_BIAS_CUR_S 2 +#define TIMPANI_TXADC_CTL3_TXADC_OTA2_BIAS_CUR_M 0xC +#define TIMPANI_TXADC_CTL3_TXADC_OTA2_BIAS_CUR_V_5UA 0x0 +#define TIMPANI_TXADC_CTL3_TXADC_OTA2_BIAS_CUR_V_10UA_NORMAL_OP 0x1 +#define TIMPANI_TXADC_CTL3_TXADC_OTA2_BIAS_CUR_V_15UA 0x2 +#define TIMPANI_TXADC_CTL3_TXADC_OTA2_BIAS_CUR_V_20UA 0x3 + +#define TIMPANI_TXADC_CTL3_TXADC_COMP_BIAS_CUR_S 1 +#define TIMPANI_TXADC_CTL3_TXADC_COMP_BIAS_CUR_M 0x2 +#define TIMPANI_TXADC_CTL3_TXADC_COMP_BIAS_CUR_V_5UA_NORMAL_OP 0x0 +#define TIMPANI_TXADC_CTL3_TXADC_COMP_BIAS_CUR_V_10UA 0x1 + +#define TIMPANI_TXADC_CTL3_RESERVED_S 0 +#define TIMPANI_TXADC_CTL3_RESERVED_M 0x1 + + +/* -- For TXADC_CHOP_CTL */ +#define TIMPANI_A_TXADC_CHOP_CTL (0x16) +#define TIMPANI_TXADC_CHOP_CTL_RWC "RW" +#define TIMPANI_TXADC_CHOP_CTL_POR 0 +#define TIMPANI_TXADC_CHOP_CTL_S 0 +#define TIMPANI_TXADC_CHOP_CTL_M 0xFF + + +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_EN_S 7 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_EN_M 0x80 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_EN_DISABLE 0x0 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_EN_ENABLE 0x1 + +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_S 4 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_M 0x70 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_V_2_NORMAL_OP 0x0 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_V_4 0x1 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_V_8 0x2 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_V_16 0x3 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_V_32 0x4 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_V_64 0x5 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_V_128 0x6 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_DIV_RATIO_V_256 0x7 + +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_STATE_RESET_S 3 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_STATE_RESET_M 0x8 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_STATE_RESET_NORMAL_OP 0x0 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_STATE_RESET_RESET_CHOP 0x1 + +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_CLK_PHASE_SEL_S 2 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_CLK_PHASE_SEL_M 0x4 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_CLK_PHASE_SEL_FALLING_EDGE_CK1 0x0 +#define TIMPANI_TXADC_CHOP_CTL_TXADC_CHOP_CLK_PHASE_SEL_FALLING_EDGE_CK2 0x1 + +#define TIMPANI_TXADC_CHOP_CTL_RESERVED_S 0 +#define TIMPANI_TXADC_CHOP_CTL_RESERVED_M 0x3 + + +/* -- For TXFE3 */ +#define TIMPANI_A_TXFE3 (0x18) +#define TIMPANI_TXFE3_RWC "RW" +#define TIMPANI_TXFE3_POR 0 +#define TIMPANI_TXFE3_S 0 +#define TIMPANI_TXFE3_M 0xFF + + +#define TIMPANI_TXFE3_TXFE3_EN_S 7 +#define TIMPANI_TXFE3_TXFE3_EN_M 0x80 +#define TIMPANI_TXFE3_TXFE3_EN_DISABLE 0x0 +#define TIMPANI_TXFE3_TXFE3_EN_ENABLE 0x1 + +#define TIMPANI_TXFE3_TXFE3_GAIN_S 5 +#define TIMPANI_TXFE3_TXFE3_GAIN_M 0x60 +#define TIMPANI_TXFE3_TXFE3_GAIN_V_0DB 0x0 +#define TIMPANI_TXFE3_TXFE3_GAIN_V_4_5DB 0x1 +#define TIMPANI_TXFE3_TXFE3_GAIN_V_24DB_1 0x2 +#define TIMPANI_TXFE3_TXFE3_GAIN_V_24DB_2 0x3 + +#define TIMPANI_TXFE3_RESERVED_1_S 2 +#define TIMPANI_TXFE3_RESERVED_1_M 0x1C + +#define TIMPANI_TXFE3_TXFE3_IN_CONN_S 1 +#define TIMPANI_TXFE3_TXFE3_IN_CONN_M 0x2 +#define TIMPANI_TXFE3_TXFE3_IN_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE3_TXFE3_IN_CONN_LINE_IN_L 0x1 + +#define TIMPANI_TXFE3_RESERVED_2_S 0 +#define TIMPANI_TXFE3_RESERVED_2_M 0x1 + + +/* -- For TXFE4 */ +#define TIMPANI_A_TXFE4 (0x19) +#define TIMPANI_TXFE4_RWC "RW" +#define TIMPANI_TXFE4_POR 0 +#define TIMPANI_TXFE4_S 0 +#define TIMPANI_TXFE4_M 0xFF + + +#define TIMPANI_TXFE4_TXFE4_EN_S 7 +#define TIMPANI_TXFE4_TXFE4_EN_M 0x80 +#define TIMPANI_TXFE4_TXFE4_EN_DISABLE 0x0 +#define TIMPANI_TXFE4_TXFE4_EN_ENABLE 0x1 + +#define TIMPANI_TXFE4_TXFE4_GAIN_S 5 +#define TIMPANI_TXFE4_TXFE4_GAIN_M 0x60 +#define TIMPANI_TXFE4_TXFE4_GAIN_V_0DB 0x0 +#define TIMPANI_TXFE4_TXFE4_GAIN_V_4_5DB 0x1 +#define TIMPANI_TXFE4_TXFE4_GAIN_V_24DB_1 0x2 +#define TIMPANI_TXFE4_TXFE4_GAIN_V_24DB_2 0x3 + +#define TIMPANI_TXFE4_RESERVED_1_S 2 +#define TIMPANI_TXFE4_RESERVED_1_M 0x1C + +#define TIMPANI_TXFE4_TXFE4_IN_CONN_S 1 +#define TIMPANI_TXFE4_TXFE4_IN_CONN_M 0x2 +#define TIMPANI_TXFE4_TXFE4_IN_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE4_TXFE4_IN_CONN_LINE_IN_R 0x1 + +#define TIMPANI_TXFE4_RESERVED_2_S 0 +#define TIMPANI_TXFE4_RESERVED_2_M 0x1 + + +/* -- For TXFE3_ATEST */ +#define TIMPANI_A_TXFE3_ATEST (0x1A) +#define TIMPANI_TXFE3_ATEST_RWC "RW" +#define TIMPANI_TXFE3_ATEST_POR 0 +#define TIMPANI_TXFE3_ATEST_S 0 +#define TIMPANI_TXFE3_ATEST_M 0xFF + + +#define TIMPANI_TXFE3_ATEST_TXFE3_OUT_SHORT_TO_VICM_EN_S 7 +#define TIMPANI_TXFE3_ATEST_TXFE3_OUT_SHORT_TO_VICM_EN_M 0x80 +#define TIMPANI_TXFE3_ATEST_TXFE3_OUT_SHORT_TO_VICM_EN_DISABLE 0x0 +#define TIMPANI_TXFE3_ATEST_TXFE3_OUT_SHORT_TO_VICM_EN_ENABLE 0x1 + +#define TIMPANI_TXFE3_ATEST_TXFE3_BYPASS_EN_S 6 +#define TIMPANI_TXFE3_ATEST_TXFE3_BYPASS_EN_M 0x40 +#define TIMPANI_TXFE3_ATEST_TXFE3_BYPASS_EN_DISABLE 0x0 +#define TIMPANI_TXFE3_ATEST_TXFE3_BYPASS_EN_ENABLE 0x1 + +#define TIMPANI_TXFE3_ATEST_TXFE3_CMOUT_ATEST_CONN_S 5 +#define TIMPANI_TXFE3_ATEST_TXFE3_CMOUT_ATEST_CONN_M 0x20 +#define TIMPANI_TXFE3_ATEST_TXFE3_CMOUT_ATEST_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE3_ATEST_TXFE3_CMOUT_ATEST_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE3_ATEST_TXFE3_OUT_ATEST_CONN_S 4 +#define TIMPANI_TXFE3_ATEST_TXFE3_OUT_ATEST_CONN_M 0x10 +#define TIMPANI_TXFE3_ATEST_TXFE3_OUT_ATEST_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE3_ATEST_TXFE3_OUT_ATEST_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE3_ATEST_TXFE4_OUT_SHORT_TO_VICM_EN_S 3 +#define TIMPANI_TXFE3_ATEST_TXFE4_OUT_SHORT_TO_VICM_EN_M 0x8 +#define TIMPANI_TXFE3_ATEST_TXFE4_OUT_SHORT_TO_VICM_EN_DISABLE 0x0 +#define TIMPANI_TXFE3_ATEST_TXFE4_OUT_SHORT_TO_VICM_EN_ENABLE 0x1 + +#define TIMPANI_TXFE3_ATEST_TXFE4_BYPASS_EN_S 2 +#define TIMPANI_TXFE3_ATEST_TXFE4_BYPASS_EN_M 0x4 +#define TIMPANI_TXFE3_ATEST_TXFE4_BYPASS_EN_DISABLE 0x0 +#define TIMPANI_TXFE3_ATEST_TXFE4_BYPASS_EN_ENABLE 0x1 + +#define TIMPANI_TXFE3_ATEST_TXFE4_CMOUT_ATEST_CONN_S 1 +#define TIMPANI_TXFE3_ATEST_TXFE4_CMOUT_ATEST_CONN_M 0x2 +#define TIMPANI_TXFE3_ATEST_TXFE4_CMOUT_ATEST_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE3_ATEST_TXFE4_CMOUT_ATEST_CONN_CONNECT 0x1 + +#define TIMPANI_TXFE3_ATEST_TXFE4_OUT_ATEST_CONN_S 0 +#define TIMPANI_TXFE3_ATEST_TXFE4_OUT_ATEST_CONN_M 0x1 +#define TIMPANI_TXFE3_ATEST_TXFE4_OUT_ATEST_CONN_NO_CONNECT 0x0 +#define TIMPANI_TXFE3_ATEST_TXFE4_OUT_ATEST_CONN_CONNECT 0x1 + + +/* -- For TXFE_DIFF_SE */ +#define TIMPANI_A_TXFE_DIFF_SE (0x1B) +#define TIMPANI_TXFE_DIFF_SE_RWC "RW" +#define TIMPANI_TXFE_DIFF_SE_POR 0 +#define TIMPANI_TXFE_DIFF_SE_S 0 +#define TIMPANI_TXFE_DIFF_SE_M 0xFF + + +#define TIMPANI_TXFE_DIFF_SE_RESERVED_S 4 +#define TIMPANI_TXFE_DIFF_SE_RESERVED_M 0xF0 + +#define TIMPANI_TXFE_DIFF_SE_TXADC1_IN_MODE_S 3 +#define TIMPANI_TXFE_DIFF_SE_TXADC1_IN_MODE_M 0x8 +#define TIMPANI_TXFE_DIFF_SE_TXADC1_IN_MODE_DIFF 0x0 +#define TIMPANI_TXFE_DIFF_SE_TXADC1_IN_MODE_SINGLE_ENDED 0x1 + +#define TIMPANI_TXFE_DIFF_SE_TXADC2_IN_MODE_S 2 +#define TIMPANI_TXFE_DIFF_SE_TXADC2_IN_MODE_M 0x4 +#define TIMPANI_TXFE_DIFF_SE_TXADC2_IN_MODE_DIFF 0x0 +#define TIMPANI_TXFE_DIFF_SE_TXADC2_IN_MODE_SINGLE_ENDED 0x1 + +#define TIMPANI_TXFE_DIFF_SE_TXADC3_IN_MODE_S 1 +#define TIMPANI_TXFE_DIFF_SE_TXADC3_IN_MODE_M 0x2 +#define TIMPANI_TXFE_DIFF_SE_TXADC3_IN_MODE_DIFF 0x0 +#define TIMPANI_TXFE_DIFF_SE_TXADC3_IN_MODE_SINGLE_ENDED 0x1 + +#define TIMPANI_TXFE_DIFF_SE_TXADC4_IN_MODE_S 0 +#define TIMPANI_TXFE_DIFF_SE_TXADC4_IN_MODE_M 0x1 +#define TIMPANI_TXFE_DIFF_SE_TXADC4_IN_MODE_DIFF 0x0 +#define TIMPANI_TXFE_DIFF_SE_TXADC4_IN_MODE_SINGLE_ENDED 0x1 + + +/* -- For CDAC_RX_CLK_CTL */ +#define TIMPANI_A_CDAC_RX_CLK_CTL (0x20) +#define TIMPANI_CDAC_RX_CLK_CTL_RWC "RW" +#define TIMPANI_CDAC_RX_CLK_CTL_POR 0x98 +#define TIMPANI_CDAC_RX_CLK_CTL_S 0 +#define TIMPANI_CDAC_RX_CLK_CTL_M 0xFF + + +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_RESET_PULSE_GEN_EN_S 7 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_RESET_PULSE_GEN_EN_M 0x80 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_RESET_PULSE_GEN_EN_DISABLE 0x0 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_RESET_PULSE_GEN_EN_ENABLE_NORMAL_OP 0x1 + +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_CTRL_EN_S 6 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_CTRL_EN_M 0x40 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_CTRL_EN_DISABLE_NORMAL_OP 0x0 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_CTRL_EN_ENABLE 0x1 + +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_S 2 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_M 0x3C +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_6NS 0x0 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_8_4NS 0x1 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_10_8NS 0x2 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_13_2NS 0x3 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_15_6NS 0x4 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_18NS 0x5 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_20_4NS_NORMAL_OP 0x6 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_22_8NS 0x7 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_25_2NS 0x8 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_27_6NS 0x9 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_30NS 0xA +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_32_4NS 0xB +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_34_8NS 0xC +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_37_2NS 0xD +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_39_6NS 0xE +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_EXTERNAL_DELAY_V_42NS 0xF + +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_FF_RESET_S 1 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_FF_RESET_M 0x2 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_FF_RESET_ENABLE 0x1 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_FF_RESET_DISABLE 0x0 + +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_RESET_PULSE_GEN_ATEST_CONN_S 0 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_RESET_PULSE_GEN_ATEST_CONN_M 0x1 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_RESET_PULSE_GEN_ATEST_CONN_NO_CONNECT 0x0 +#define TIMPANI_CDAC_RX_CLK_CTL_CDAC_RESET_PULSE_GEN_ATEST_CONN_CONNECT 0x1 + + +/* -- For CDAC_BUFF_CTL */ +#define TIMPANI_A_CDAC_BUFF_CTL (0x21) +#define TIMPANI_CDAC_BUFF_CTL_RWC "RW" +#define TIMPANI_CDAC_BUFF_CTL_POR 0x60 +#define TIMPANI_CDAC_BUFF_CTL_S 0 +#define TIMPANI_CDAC_BUFF_CTL_M 0xFF + + +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_S 5 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_M 0xE0 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_V_40UA 0x0 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_V_60UA_NORMAL_OP 0x1 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_V_80UA 0x2 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_V_100UA 0x3 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_V_120UA 0x4 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_V_140UA 0x5 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_V_160UA 0x6 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_DM_BUFF_CUR_V_180UA 0x7 + +#define TIMPANI_CDAC_BUFF_CTL_CDAC_CM_BUFF_CUR_S 3 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_CM_BUFF_CUR_M 0x18 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_CM_BUFF_CUR_V_20UA 0x0 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_CM_BUFF_CUR_V_30UA_NORMAL_OP 0x1 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_CM_BUFF_CUR_V_40UA 0x2 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_CM_BUFF_CUR_V_50UA 0x3 + +#define TIMPANI_CDAC_BUFF_CTL_CDAC_REF_BUFF_OTA_BIAS_CUR_S 1 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_REF_BUFF_OTA_BIAS_CUR_M 0x6 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_REF_BUFF_OTA_BIAS_CUR_V_5UA_5UA 0x0 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_REF_BUFF_OTA_BIAS_CUR_V_5UA_10UA 0x1 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_REF_BUFF_OTA_BIAS_CUR_V_10UA_5UA 0x2 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_REF_BUFF_OTA_BIAS_CUR_V_10UA_10UA 0x3 + +#define TIMPANI_CDAC_BUFF_CTL_CDAC_VCOM_SOURCE_S 0 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_VCOM_SOURCE_M 0x1 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_VCOM_SOURCE_CURRENT_TO_VCOM_NORMAL_OP 0x0 +#define TIMPANI_CDAC_BUFF_CTL_CDAC_VCOM_SOURCE_MASTER_BIAS_TO_VCOM 0x1 + + +/* -- For CDAC_REF_CTL1 */ +#define TIMPANI_A_CDAC_REF_CTL1 (0x22) +#define TIMPANI_CDAC_REF_CTL1_RWC "RW" +#define TIMPANI_CDAC_REF_CTL1_POR 0xe1 +#define TIMPANI_CDAC_REF_CTL1_S 0 +#define TIMPANI_CDAC_REF_CTL1_M 0xFF + + +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_S 5 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_M 0xE0 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_V_1_8V 0x0 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_V_1_825V 0x1 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_V_1_85V 0x2 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_V_1_9V 0x3 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_V_1_925V 0x4 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_V_1_95V_NORMAL_OP 0x5 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_V_1_975 0x6 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACH_VOLT_V_2_0V 0x7 + +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_S 2 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_M 0x1C +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_V_0_1V 0x0 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_V_0_125V 0x1 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_V_0_15V_NORMAL_OP 0x2 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_V_0_175V 0x3 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_V_0_2V 0x4 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_V_0_25V 0x5 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_V_0_275V 0x6 +#define TIMPANI_CDAC_REF_CTL1_CDAC_DACL_VOLT_V_0_3V 0x7 + +#define TIMPANI_CDAC_REF_CTL1_CDAC_CM_VOLT_S 0 +#define TIMPANI_CDAC_REF_CTL1_CDAC_CM_VOLT_M 0x3 +#define TIMPANI_CDAC_REF_CTL1_CDAC_CM_VOLT_V_1_025V 0x0 +#define TIMPANI_CDAC_REF_CTL1_CDAC_CM_VOLT_V_1_05V_NORMAL_OP 0x1 +#define TIMPANI_CDAC_REF_CTL1_CDAC_CM_VOLT_V_1_075V 0x2 +#define TIMPANI_CDAC_REF_CTL1_CDAC_CM_VOLT_V_1_1V 0x3 + + +/* -- For IDAC_DWA_FIR_CTL */ +#define TIMPANI_A_IDAC_DWA_FIR_CTL (0x23) +#define TIMPANI_IDAC_DWA_FIR_CTL_RWC "RW" +#define TIMPANI_IDAC_DWA_FIR_CTL_POR 0x28 +#define TIMPANI_IDAC_DWA_FIR_CTL_S 0 +#define TIMPANI_IDAC_DWA_FIR_CTL_M 0xFF + + +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_CLK_NON_OL_TIME_S 7 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_CLK_NON_OL_TIME_M 0x80 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_CLK_NON_OL_TIME_NORMAL_OP 0x0 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_CLK_NON_OL_TIME_V_150PSEC_REDUCTION 0x1 + +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_FIR_S 4 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_FIR_M 0x70 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_FIR_FIR0 0x0 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_FIR_FIR1 0x1 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_FIR_FIR2 0x2 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_FIR_FIR3 0x3 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_FIR_FIR4 0x4 + +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_EN_SOURCE_S 3 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_EN_SOURCE_M 0x8 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_EN_SOURCE_INTERNAL_NORMAL_OP 0x1 +#define TIMPANI_IDAC_DWA_FIR_CTL_IDAC_EN_SOURCE_EXTERNAL 0x0 + +#define TIMPANI_IDAC_DWA_FIR_CTL_RESERVED_S 0 +#define TIMPANI_IDAC_DWA_FIR_CTL_RESERVED_M 0x7 + + +/* -- For CDAC_REF_CTL2 */ +#define TIMPANI_A_CDAC_REF_CTL2 (0x24) +#define TIMPANI_CDAC_REF_CTL2_RWC "RW" +#define TIMPANI_CDAC_REF_CTL2_POR 0xc +#define TIMPANI_CDAC_REF_CTL2_S 0 +#define TIMPANI_CDAC_REF_CTL2_M 0xFF + + +#define TIMPANI_CDAC_REF_CTL2_RESERVED_1_S 7 +#define TIMPANI_CDAC_REF_CTL2_RESERVED_1_M 0x80 + +#define TIMPANI_CDAC_REF_CTL2_CDAC_L_EN_S 6 +#define TIMPANI_CDAC_REF_CTL2_CDAC_L_EN_M 0x40 +#define TIMPANI_CDAC_REF_CTL2_CDAC_L_EN_DISABLE 0x0 +#define TIMPANI_CDAC_REF_CTL2_CDAC_L_EN_ENABLE 0x1 + +#define TIMPANI_CDAC_REF_CTL2_CDAC_R_EN_S 5 +#define TIMPANI_CDAC_REF_CTL2_CDAC_R_EN_M 0x20 +#define TIMPANI_CDAC_REF_CTL2_CDAC_R_EN_DISABLE 0x0 +#define TIMPANI_CDAC_REF_CTL2_CDAC_R_EN_ENABLE 0x1 + +#define TIMPANI_CDAC_REF_CTL2_RESERVED_2_S 4 +#define TIMPANI_CDAC_REF_CTL2_RESERVED_2_M 0x10 + +#define TIMPANI_CDAC_REF_CTL2_CDAC_DWA_RX_FILTER_TIMING_S 2 +#define TIMPANI_CDAC_REF_CTL2_CDAC_DWA_RX_FILTER_TIMING_M 0xC +#define TIMPANI_CDAC_REF_CTL2_CDAC_DWA_RX_FILTER_TIMING_CLK_SYNC_CK11DBAR 0x1 +#define TIMPANI_CDAC_REF_CTL2_CDAC_DWA_RX_FILTER_TIMING_CLK_SYNC_CK21 0x3 + +#define TIMPANI_CDAC_REF_CTL2_CDAC_OSR_S 0 +#define TIMPANI_CDAC_REF_CTL2_CDAC_OSR_M 0x3 +#define TIMPANI_CDAC_REF_CTL2_CDAC_OSR_V_256 0x0 +#define TIMPANI_CDAC_REF_CTL2_CDAC_OSR_V_128 0x1 +#define TIMPANI_CDAC_REF_CTL2_CDAC_OSR_V_64 0x3 + + +/* -- For CDAC_CTL1 */ +#define TIMPANI_A_CDAC_CTL1 (0x25) +#define TIMPANI_CDAC_CTL1_RWC "RW" +#define TIMPANI_CDAC_CTL1_POR 0xb +#define TIMPANI_CDAC_CTL1_S 0 +#define TIMPANI_CDAC_CTL1_M 0xFF + + +#define TIMPANI_CDAC_CTL1_RESERVED_S 6 +#define TIMPANI_CDAC_CTL1_RESERVED_M 0xC0 + +#define TIMPANI_CDAC_CTL1_CDAC_L_OUT_SHORT_EN_S 5 +#define TIMPANI_CDAC_CTL1_CDAC_L_OUT_SHORT_EN_M 0x20 +#define TIMPANI_CDAC_CTL1_CDAC_L_OUT_SHORT_EN_DISABLE 0x0 +#define TIMPANI_CDAC_CTL1_CDAC_L_OUT_SHORT_EN_ENABLE 0x1 + +#define TIMPANI_CDAC_CTL1_CDAC_R_OUT_SHORT_EN_S 4 +#define TIMPANI_CDAC_CTL1_CDAC_R_OUT_SHORT_EN_M 0x10 +#define TIMPANI_CDAC_CTL1_CDAC_R_OUT_SHORT_EN_DISABLE 0x0 +#define TIMPANI_CDAC_CTL1_CDAC_R_OUT_SHORT_EN_ENABLE 0x1 + +#define TIMPANI_CDAC_CTL1_CDAC_REF_RESISTOR_VOLT_S 2 +#define TIMPANI_CDAC_CTL1_CDAC_REF_RESISTOR_VOLT_M 0xC +#define TIMPANI_CDAC_CTL1_CDAC_REF_RESISTOR_VOLT_V_1_0V 0x0 +#define TIMPANI_CDAC_CTL1_CDAC_REF_RESISTOR_VOLT_V_1_025V 0x1 +#define TIMPANI_CDAC_CTL1_CDAC_REF_RESISTOR_VOLT_V_1_05V_NORMAL_OP 0x2 +#define TIMPANI_CDAC_CTL1_CDAC_REF_RESISTOR_VOLT_V_1_0752V 0x3 + +#define TIMPANI_CDAC_CTL1_CDAC_SAMP_CAP_RESET_EN_S 1 +#define TIMPANI_CDAC_CTL1_CDAC_SAMP_CAP_RESET_EN_M 0x2 +#define TIMPANI_CDAC_CTL1_CDAC_SAMP_CAP_RESET_EN_DISABLE 0x0 +#define TIMPANI_CDAC_CTL1_CDAC_SAMP_CAP_RESET_EN_ENABLE_NORMAL_OP 0x1 + +#define TIMPANI_CDAC_CTL1_CDAC_RESET_SOURCE_S 0 +#define TIMPANI_CDAC_CTL1_CDAC_RESET_SOURCE_M 0x1 +#define TIMPANI_CDAC_CTL1_CDAC_RESET_SOURCE_INTERNAL_NORMAL_OP 0x1 +#define TIMPANI_CDAC_CTL1_CDAC_RESET_SOURCE_EXTERNAL_REGISTER_RESET 0x0 + + +/* -- For CDAC_CTL2 */ +#define TIMPANI_A_CDAC_CTL2 (0x26) +#define TIMPANI_CDAC_CTL2_RWC "RW" +#define TIMPANI_CDAC_CTL2_POR 0xd0 +#define TIMPANI_CDAC_CTL2_S 0 +#define TIMPANI_CDAC_CTL2_M 0xFF + + +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_S 5 +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_M 0xE0 +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_V_10UA 0x0 +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_V_8_75UA 0x1 +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_V_7_5UA 0x2 +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_V_6_25UA 0x3 +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_V_5UA 0x4 +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_V_3_75UA 0x5 +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_V_2_5UA_NORMAL_OP 0x6 +#define TIMPANI_CDAC_CTL2_CDAC_OTA_BIAS_V_1_25UA 0x7 + +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_S 2 +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_M 0x1C +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_V_10UA 0x0 +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_V_8_75UA 0x1 +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_V_7_5UA 0x2 +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_V_6_25UA 0x3 +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_V_5UA_NORMAL_OP 0x4 +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_V_3_75UA 0x5 +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_V_2_5UA 0x6 +#define TIMPANI_CDAC_CTL2_CDAC_REF_BUFF_OTA_BIAS_V_1_25UA 0x7 + +#define TIMPANI_CDAC_CTL2_CDAC_RESET_PULSE_GEN_UPDATE_RATE_S 0 +#define TIMPANI_CDAC_CTL2_CDAC_RESET_PULSE_GEN_UPDATE_RATE_M 0x3 +#define TIMPANI_CDAC_CTL2_CDAC_RESET_PULSE_GEN_UPDATE_RATE_FS 0x0 +#define TIMPANI_CDAC_CTL2_CDAC_RESET_PULSE_GEN_UPDATE_RATE_FS_BY_8 0x1 +#define TIMPANI_CDAC_CTL2_CDAC_RESET_PULSE_GEN_UPDATE_RATE_FS_BY_16 0x2 + + +/* -- For IDAC_L_CTL */ +#define TIMPANI_A_IDAC_L_CTL (0x28) +#define TIMPANI_IDAC_L_CTL_RWC "RW" +#define TIMPANI_IDAC_L_CTL_POR 0xe +#define TIMPANI_IDAC_L_CTL_S 0 +#define TIMPANI_IDAC_L_CTL_M 0xFF + + +#define TIMPANI_IDAC_L_CTL_IDAC_L_EN_S 7 +#define TIMPANI_IDAC_L_CTL_IDAC_L_EN_M 0x80 +#define TIMPANI_IDAC_L_CTL_IDAC_L_EN_DISABLE 0x0 +#define TIMPANI_IDAC_L_CTL_IDAC_L_EN_ENABLE 0x1 + +#define TIMPANI_IDAC_L_CTL_IDAC_L_REF_SEL_S 5 +#define TIMPANI_IDAC_L_CTL_IDAC_L_REF_SEL_M 0x60 +#define TIMPANI_IDAC_L_CTL_IDAC_L_REF_SEL_GROUND 0x0 +#define TIMPANI_IDAC_L_CTL_IDAC_L_REF_SEL_IBIAS_X_R_REF 0x1 +#define TIMPANI_IDAC_L_CTL_IDAC_L_REF_SEL_BG_VOLTAGE_NORMAL_OP 0x2 +#define TIMPANI_IDAC_L_CTL_IDAC_L_REF_SEL_VDD_BY_2 0x3 + +#define TIMPANI_IDAC_L_CTL_IDAC_L_GAIN_S 3 +#define TIMPANI_IDAC_L_CTL_IDAC_L_GAIN_M 0x18 +#define TIMPANI_IDAC_L_CTL_IDAC_L_GAIN_NEG_1_5DB 0x0 +#define TIMPANI_IDAC_L_CTL_IDAC_L_GAIN_V_0_0DB_NORMAL_OP 0x1 +#define TIMPANI_IDAC_L_CTL_IDAC_L_GAIN_POS_1_5DB 0x2 +#define TIMPANI_IDAC_L_CTL_IDAC_L_GAIN_POS_3_0DB 0x3 + +#define TIMPANI_IDAC_L_CTL_IDAC_L_LOW_RESISTANCE_S 2 +#define TIMPANI_IDAC_L_CTL_IDAC_L_LOW_RESISTANCE_M 0x4 +#define TIMPANI_IDAC_L_CTL_IDAC_L_LOW_RESISTANCE_V_30K 0x0 +#define TIMPANI_IDAC_L_CTL_IDAC_L_LOW_RESISTANCE_V_10K_NORMAL_OP 0x1 + +#define TIMPANI_IDAC_L_CTL_IDAC_L_SYNC_EN_S 1 +#define TIMPANI_IDAC_L_CTL_IDAC_L_SYNC_EN_M 0x2 +#define TIMPANI_IDAC_L_CTL_IDAC_L_SYNC_EN_ASYNCHRONOUSLY 0x0 +#define TIMPANI_IDAC_L_CTL_IDAC_L_SYNC_EN_ENABLE_NORMAL_OP 0x1 + +#define TIMPANI_IDAC_L_CTL_IDAC_L_REPLICA_BIAS_S 0 +#define TIMPANI_IDAC_L_CTL_IDAC_L_REPLICA_BIAS_M 0x1 +#define TIMPANI_IDAC_L_CTL_IDAC_L_REPLICA_BIAS_REPLICA_BIAS_NORMAL_OP 0x0 +#define TIMPANI_IDAC_L_CTL_IDAC_L_REPLICA_BIAS_SERVO_LOOP_BIAS 0x1 + + +/* -- For IDAC_R_CTL */ +#define TIMPANI_A_IDAC_R_CTL (0x29) +#define TIMPANI_IDAC_R_CTL_RWC "RW" +#define TIMPANI_IDAC_R_CTL_POR 0xe +#define TIMPANI_IDAC_R_CTL_S 0 +#define TIMPANI_IDAC_R_CTL_M 0xFF + + +#define TIMPANI_IDAC_R_CTL_IDAC_R_EN_S 7 +#define TIMPANI_IDAC_R_CTL_IDAC_R_EN_M 0x80 +#define TIMPANI_IDAC_R_CTL_IDAC_R_EN_DISABLED 0x0 +#define TIMPANI_IDAC_R_CTL_IDAC_R_EN_ENABLED 0x1 + +#define TIMPANI_IDAC_R_CTL_IDAC_R_REF_SEL_S 5 +#define TIMPANI_IDAC_R_CTL_IDAC_R_REF_SEL_M 0x60 +#define TIMPANI_IDAC_R_CTL_IDAC_R_REF_SEL_GROUND 0x0 +#define TIMPANI_IDAC_R_CTL_IDAC_R_REF_SEL_IBIAS_X_R_REF 0x1 +#define TIMPANI_IDAC_R_CTL_IDAC_R_REF_SEL_BG_VOLTAGE_NORMAL_OP 0x2 +#define TIMPANI_IDAC_R_CTL_IDAC_R_REF_SEL_VDD_BY_2 0x3 + +#define TIMPANI_IDAC_R_CTL_IDAC_R_GAIN_S 3 +#define TIMPANI_IDAC_R_CTL_IDAC_R_GAIN_M 0x18 +#define TIMPANI_IDAC_R_CTL_IDAC_R_GAIN_NEG_1_5DB 0x0 +#define TIMPANI_IDAC_R_CTL_IDAC_R_GAIN_V_0_0DB_NORMAL_OP 0x1 +#define TIMPANI_IDAC_R_CTL_IDAC_R_GAIN_POS_1_5DB 0x2 +#define TIMPANI_IDAC_R_CTL_IDAC_R_GAIN_POS_3_0DB 0x3 + +#define TIMPANI_IDAC_R_CTL_IDAC_R_LOW_RESISTANCE_S 2 +#define TIMPANI_IDAC_R_CTL_IDAC_R_LOW_RESISTANCE_M 0x4 +#define TIMPANI_IDAC_R_CTL_IDAC_R_LOW_RESISTANCE_V_30K 0x0 +#define TIMPANI_IDAC_R_CTL_IDAC_R_LOW_RESISTANCE_V_10K_NORMAL_OP 0x1 + +#define TIMPANI_IDAC_R_CTL_IDAC_R_SYNC_EN_S 1 +#define TIMPANI_IDAC_R_CTL_IDAC_R_SYNC_EN_M 0x2 +#define TIMPANI_IDAC_R_CTL_IDAC_R_SYNC_EN_ASYNCHRONOUSLY 0x0 +#define TIMPANI_IDAC_R_CTL_IDAC_R_SYNC_EN_ENABLE_NORMAL_OP 0x1 + +#define TIMPANI_IDAC_R_CTL_IDAC_R_REPLICA_BIAS_S 0 +#define TIMPANI_IDAC_R_CTL_IDAC_R_REPLICA_BIAS_M 0x1 +#define TIMPANI_IDAC_R_CTL_IDAC_R_REPLICA_BIAS_REPLICA_BIAS_NORMAL_OP 0x0 +#define TIMPANI_IDAC_R_CTL_IDAC_R_REPLICA_BIAS_SERVO_LOOP_BIAS 0x1 + + +/* -- For PA_MASTER_BIAS */ +#define TIMPANI_A_PA_MASTER_BIAS (0x2D) +#define TIMPANI_PA_MASTER_BIAS_RWC "RW" +#define TIMPANI_PA_MASTER_BIAS_POR 0x6f +#define TIMPANI_PA_MASTER_BIAS_S 0 +#define TIMPANI_PA_MASTER_BIAS_M 0xFF + + +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_S 5 +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_M 0xE0 +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_V_17_5UA 0x0 +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_V_15_0UA 0x1 +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_V_12_5UA 0x2 +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_V_10_0UA 0x3 +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_V_7_5UA 0x4 +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_V_5_0UA 0x5 +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_V_2_5UA 0x6 +#define TIMPANI_PA_MASTER_BIAS_LINE_MASTER_BIAS_CUR_V_0_0UA 0x7 + +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_S 2 +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_M 0x1C +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_V_17_5UA 0x0 +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_V_15_0UA 0x1 +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_V_12_5UA 0x2 +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_V_10_0UA 0x3 +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_V_7_5UA 0x4 +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_V_5_0UA 0x5 +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_V_2_5UA 0x6 +#define TIMPANI_PA_MASTER_BIAS_HPH_MASTER_BIAS_CUR_V_0_0UA 0x7 + +#define TIMPANI_PA_MASTER_BIAS_CLASSD_REF_BUF_MASTER_BIAS_CUR_S 0 +#define TIMPANI_PA_MASTER_BIAS_CLASSD_REF_BUF_MASTER_BIAS_CUR_M 0x3 +#define TIMPANI_PA_MASTER_BIAS_CLASSD_REF_BUF_MASTER_BIAS_CUR_V_6_25UA 0x0 +#define TIMPANI_PA_MASTER_BIAS_CLASSD_REF_BUF_MASTER_BIAS_CUR_V_5_0UA 0x1 +#define TIMPANI_PA_MASTER_BIAS_CLASSD_REF_BUF_MASTER_BIAS_CUR_V_3_75UA 0x2 +#define TIMPANI_PA_MASTER_BIAS_CLASSD_REF_BUF_MASTER_BIAS_CUR_V_2_5UA 0x3 + + +/* -- For PA_CLASSD_BIAS */ +#define TIMPANI_A_PA_CLASSD_BIAS (0x2E) +#define TIMPANI_PA_CLASSD_BIAS_RWC "RW" +#define TIMPANI_PA_CLASSD_BIAS_POR 0x55 +#define TIMPANI_PA_CLASSD_BIAS_S 0 +#define TIMPANI_PA_CLASSD_BIAS_M 0xFF + + +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_COMP_BIAS_CUR_S 6 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_COMP_BIAS_CUR_M 0xC0 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_COMP_BIAS_CUR_V_6_25UA 0x0 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_COMP_BIAS_CUR_V_5_0UA 0x1 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_COMP_BIAS_CUR_V_3_75UA 0x2 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_COMP_BIAS_CUR_V_2_5UA 0x3 + +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA1_BIAS_CUR_S 4 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA1_BIAS_CUR_M 0x30 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA1_BIAS_CUR_V_6_25UA 0x0 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA1_BIAS_CUR_V_5_0U 0x1 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA1_BIAS_CUR_V_3_75UA 0x2 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA1_BIAS_CUR_V_2_5UA 0x3 + +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA2_BIAS_CUR_S 2 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA2_BIAS_CUR_M 0xC +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA2_BIAS_CUR_V_6_25UA 0x0 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA2_BIAS_CUR_V_5_0UA 0x1 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA2_BIAS_CUR_V_3_75UA 0x2 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OTA2_BIAS_CUR_V_2_5UA 0x3 + +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OCP_BIAS_CUR_S 0 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OCP_BIAS_CUR_M 0x3 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OCP_BIAS_CUR_V_6_25UA 0x0 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OCP_BIAS_CUR_V_5_0UA 0x1 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OCP_BIAS_CUR_V_3_75UA 0x2 +#define TIMPANI_PA_CLASSD_BIAS_CLASSD_OCP_BIAS_CUR_V_2_5UA 0x3 + + +/* -- For AUXPGA_CUR */ +#define TIMPANI_A_AUXPGA_CUR (0x2F) +#define TIMPANI_AUXPGA_CUR_RWC "RW" +#define TIMPANI_AUXPGA_CUR_POR 0x44 +#define TIMPANI_AUXPGA_CUR_S 0 +#define TIMPANI_AUXPGA_CUR_M 0xFF + + +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_S 4 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_M 0xF0 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_0UA 0x0 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_0_3125UA 0x1 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_0_625UA 0x2 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_0_9375UA 0x3 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_1_25UA 0x4 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_1_5625UA 0x5 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_1_875UA 0x6 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_2_1875UA 0x7 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_2_5UA 0x8 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_2_8125UA 0x9 +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_3_125UA 0xA +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_3_4375UA 0xB +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_3_75UA 0xC +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_4_0625UA 0xD +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_4_375UA 0xE +#define TIMPANI_AUXPGA_CUR_AUXPGA_PMOSAB_CUR_V_4_6875UA 0xF + +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_S 0 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_M 0xF +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_0UA 0x0 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_0_3125UA 0x1 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_0_625UA 0x2 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_0_9375UA 0x3 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_1_25UA 0x4 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_1_5625UA 0x5 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_1_875UA 0x6 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_2_1875UA 0x7 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_2_5UA 0x8 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_2_8125UA 0x9 +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_3_125UA 0xA +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_3_4375UA 0xB +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_3_75UA 0xC +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_4_0625UA 0xD +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_4_375UA 0xE +#define TIMPANI_AUXPGA_CUR_AUXPGA_NMOSAB_CUR_V_4_6875UA 0xF + + +/* -- For AUXPGA_CM */ +#define TIMPANI_A_AUXPGA_CM (0x30) +#define TIMPANI_AUXPGA_CM_RWC "RW" +#define TIMPANI_AUXPGA_CM_POR 0x92 +#define TIMPANI_AUXPGA_CM_S 0 +#define TIMPANI_AUXPGA_CM_M 0xFF + + +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_S 5 +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_M 0xE0 +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_V_7_5UA 0x0 +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_V_7_925UA 0x1 +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_V_8_75UA 0x2 +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_V_9_375UA 0x3 +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_V_10UA 0x4 +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_V_10_625UA 0x5 +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_V_11_25UA 0x6 +#define TIMPANI_AUXPGA_CM_AUXPGA_R_CM_DIFF_PAIR_TAIL_CUR_V_11_875UA 0x7 + +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_S 2 +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_M 0x1C +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_V_7_5UA 0x0 +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_V_7_925UA 0x1 +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_V_8_75UA 0x2 +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_V_9_375UA 0x3 +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_V_10UA 0x4 +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_V_10_625UA 0x5 +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_V_11_25UA 0x6 +#define TIMPANI_AUXPGA_CM_AUXPGA_L_CM_DIFF_PAIR_TAIL_CUR_V_11_875UA 0x7 + +#define TIMPANI_AUXPGA_CM_AUXPGA_R2R_CM_S 1 +#define TIMPANI_AUXPGA_CM_AUXPGA_R2R_CM_M 0x2 +#define TIMPANI_AUXPGA_CM_AUXPGA_R2R_CM_VCMI_TO_R2R_CM 0x1 +#define TIMPANI_AUXPGA_CM_AUXPGA_R2R_CM_R2R_CM_FLOATING 0x0 + +#define TIMPANI_AUXPGA_CM_AUXPGA_VCM_REF_GEN_S 0 +#define TIMPANI_AUXPGA_CM_AUXPGA_VCM_REF_GEN_M 0x1 +#define TIMPANI_AUXPGA_CM_AUXPGA_VCM_REF_GEN_GEN_VCM_LOCALLY 0x1 +#define TIMPANI_AUXPGA_CM_AUXPGA_VCM_REF_GEN_BG_VCM 0x0 + + +/* -- For PA_HPH_EARPA_MSTB_EN */ +#define TIMPANI_A_PA_HPH_EARPA_MSTB_EN (0x31) +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_RWC "RW" +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_POR 0x4 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_S 0 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_M 0xFF + + +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_L_EN_S 7 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_L_EN_M 0x80 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_L_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_L_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_L_BIAS_EN_S 6 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_L_BIAS_EN_M 0x40 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_L_BIAS_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_L_BIAS_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_R_EN_S 5 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_R_EN_M 0x20 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_R_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_R_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_R_BIAS_EN_S 4 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_R_BIAS_EN_M 0x10 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_R_BIAS_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_R_BIAS_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_VCM_BUFFER_EN_S 3 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_VCM_BUFFER_EN_M 0x8 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_VCM_BUFFER_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_VCM_BUFFER_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_CAPLESS_MODE_S 2 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_CAPLESS_MODE_M 0x4 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_CAPLESS_MODE_CAPLESS 0x1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_HPH_CAPLESS_MODE_LEGACY 0x0 + +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_EARPA_EN_S 1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_EARPA_EN_M 0x2 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_EARPA_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_EARPA_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_PA_MASTER_BIAS_EN_S 0 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_PA_MASTER_BIAS_EN_M 0x1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_PA_MASTER_BIAS_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_EARPA_MSTB_EN_PA_MASTER_BIAS_EN_DISABLE 0x0 + + +/* -- For PA_LINE_AUXO_EN */ +#define TIMPANI_A_PA_LINE_AUXO_EN (0x32) +#define TIMPANI_PA_LINE_AUXO_EN_RWC "RW" +#define TIMPANI_PA_LINE_AUXO_EN_POR 0 +#define TIMPANI_PA_LINE_AUXO_EN_S 0 +#define TIMPANI_PA_LINE_AUXO_EN_M 0xFF + + +#define TIMPANI_PA_LINE_AUXO_EN_LINE_L_EN_S 7 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_L_EN_M 0x80 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_L_EN_ENABLE 0x1 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_L_EN_DISABLE 0x0 + +#define TIMPANI_PA_LINE_AUXO_EN_LINE_L_BIAS_EN_S 6 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_L_BIAS_EN_M 0x40 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_L_BIAS_EN_ENABLE 0x1 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_L_BIAS_EN_DISABLE 0x0 + +#define TIMPANI_PA_LINE_AUXO_EN_LINE_R_EN_S 5 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_R_EN_M 0x20 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_R_EN_ENABLE 0x1 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_R_EN_DISABLE 0x0 + +#define TIMPANI_PA_LINE_AUXO_EN_LINE_R_BIAS_EN_S 4 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_R_BIAS_EN_M 0x10 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_R_BIAS_EN_ENABLE 0x1 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_R_BIAS_EN_DISABLE 0x0 + +#define TIMPANI_PA_LINE_AUXO_EN_LINE_VCM_BUFFER_EN_S 3 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_VCM_BUFFER_EN_M 0x8 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_VCM_BUFFER_EN_ENABLE 0x1 +#define TIMPANI_PA_LINE_AUXO_EN_LINE_VCM_BUFFER_EN_DISABLE 0x0 + +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_EN_S 2 +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_EN_M 0x4 +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_EN_ENABLE 0x1 +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_EN_DISABLE 0x0 + +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_BIAS_EN_S 1 +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_BIAS_EN_M 0x2 +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_BIAS_EN_ENABLE 0x1 +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_BIAS_EN_DISABLE 0x0 + +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_VCM_BUFFER_EN_S 0 +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_VCM_BUFFER_EN_M 0x1 +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_VCM_BUFFER_EN_ENABLE 0x1 +#define TIMPANI_PA_LINE_AUXO_EN_AUXOUT_VCM_BUFFER_EN_DISABLE 0x0 + + +/* -- For PA_CLASSD_AUXPGA_EN */ +#define TIMPANI_A_PA_CLASSD_AUXPGA_EN (0x33) +#define TIMPANI_PA_CLASSD_AUXPGA_EN_RWC "RW" +#define TIMPANI_PA_CLASSD_AUXPGA_EN_POR 0 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_S 0 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_M 0xFF + + +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_L_MUTE_S 7 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_L_MUTE_M 0x80 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_L_MUTE_MUTE 0x1 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_L_MUTE_UNMUTE 0x0 + +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_R_MUTE_S 6 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_R_MUTE_M 0x40 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_R_MUTE_MUTE 0x1 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_R_MUTE_UNMUTE 0x0 + +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_L_EN_S 5 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_L_EN_M 0x20 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_L_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_L_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_R_EN_S 4 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_R_EN_M 0x10 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_R_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_AUXPGA_R_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_L_REF_EN_S 3 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_L_REF_EN_M 0x8 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_L_REF_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_L_REF_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_L_EN_S 2 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_L_EN_M 0x4 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_L_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_L_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_R_REF_EN_S 1 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_R_REF_EN_M 0x2 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_R_REF_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_R_REF_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_R_EN_S 0 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_R_EN_M 0x1 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_R_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_AUXPGA_EN_CLASSD_R_EN_ENABLE 0x1 + + +/* -- For PA_LINE_L_GAIN */ +#define TIMPANI_A_PA_LINE_L_GAIN (0x34) +#define TIMPANI_PA_LINE_L_GAIN_RWC "RW" +#define TIMPANI_PA_LINE_L_GAIN_POR 0xac +#define TIMPANI_PA_LINE_L_GAIN_S 0 +#define TIMPANI_PA_LINE_L_GAIN_M 0xFF + + +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_S 2 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_M 0xFC +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_POS_1_5 0x0 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_POS_0_0 0x1 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_1_5 0x2 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_3_0 0x3 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_4_5 0x4 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_6_0 0x5 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_7_5 0x6 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_9_0 0x7 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_10_5 0x8 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_12_0 0x9 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_13_5 0xA +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_15_0 0xB +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_16_5 0xC +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_18_0 0xD +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_19_5 0xE +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_21_0 0xF +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_22_5 0x10 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_24_0 0x11 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_25_5 0x12 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_27_0 0x13 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_28_5 0x14 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_30_0 0x15 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_31_5 0x16 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_33_0 0x17 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_34_5 0x18 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_36_0 0x19 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_37_5 0x1A +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_39_0 0x1B +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_40_5 0x1C +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_42_0 0x1D +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_43_5 0x1E +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_45_0 0x1F +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_46_5 0x20 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_48_0 0x21 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_49_5 0x22 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_51_0 0x23 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_52_5 0x24 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_54_0 0x25 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_55_5 0x26 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_57_0 0x27 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_58_5 0x28 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_60_0 0x29 +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_61_5 0x2A +#define TIMPANI_PA_LINE_L_GAIN_LINE_L_GAIN_NEG_63_0 0x2B + +#define TIMPANI_PA_LINE_L_GAIN_RESERVED_S 0 +#define TIMPANI_PA_LINE_L_GAIN_RESERVED_M 0x3 + + +/* -- For PA_LINE_R_GAIN */ +#define TIMPANI_A_PA_LINE_R_GAIN (0x35) +#define TIMPANI_PA_LINE_R_GAIN_RWC "RW" +#define TIMPANI_PA_LINE_R_GAIN_POR 0xac +#define TIMPANI_PA_LINE_R_GAIN_S 0 +#define TIMPANI_PA_LINE_R_GAIN_M 0xFF + + +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_S 2 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_M 0xFC +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_POS_1_5 0x0 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_POS_0_0 0x1 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_1_5 0x2 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_3_0 0x3 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_4_5 0x4 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_6_0 0x5 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_7_5 0x6 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_9_0 0x7 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_10_5 0x8 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_12_0 0x9 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_13_5 0xA +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_15_0 0xB +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_16_5 0xC +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_18_0 0xD +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_19_5 0xE +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_21_0 0xF +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_22_5 0x10 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_24_0 0x11 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_25_5 0x12 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_27_0 0x13 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_28_5 0x14 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_30_0 0x15 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_31_5 0x16 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_33_0 0x17 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_34_5 0x18 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_36_0 0x19 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_37_5 0x1A +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_39_0 0x1B +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_40_5 0x1C +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_42_0 0x1D +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_43_5 0x1E +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_45_0 0x1F +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_46_5 0x20 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_48_0 0x21 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_49_5 0x22 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_51_0 0x23 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_52_5 0x24 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_54_0 0x25 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_55_5 0x26 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_57_0 0x27 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_58_5 0x28 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_60_0 0x29 +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_61_5 0x2A +#define TIMPANI_PA_LINE_R_GAIN_LINE_R_GAIN_NEG_63_0 0x2B + +#define TIMPANI_PA_LINE_R_GAIN_RESERVED_S 0 +#define TIMPANI_PA_LINE_R_GAIN_RESERVED_M 0x3 + + +/* -- For PA_HPH_L_GAIN */ +#define TIMPANI_A_PA_HPH_L_GAIN (0x36) +#define TIMPANI_PA_HPH_L_GAIN_RWC "RW" +#define TIMPANI_PA_HPH_L_GAIN_POR 0xae +#define TIMPANI_PA_HPH_L_GAIN_S 0 +#define TIMPANI_PA_HPH_L_GAIN_M 0xFF + + +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_S 2 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_M 0xFC +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_POS_1_5 0x0 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_POS_0_0 0x1 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_1_5 0x2 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_3_0 0x3 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_4_5 0x4 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_6_0 0x5 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_7_5 0x6 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_9_0 0x7 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_10_5 0x8 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_12_0 0x9 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_13_5 0xA +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_15_0 0xB +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_16_5 0xC +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_18_0 0xD +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_19_5 0xE +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_21_0 0xF +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_22_5 0x10 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_24_0 0x11 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_25_5 0x12 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_27_0 0x13 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_28_5 0x14 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_30_0 0x15 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_31_5 0x16 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_33_0 0x17 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_34_5 0x18 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_36_0 0x19 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_37_5 0x1A +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_39_0 0x1B +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_40_5 0x1C +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_42_0 0x1D +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_43_5 0x1E +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_45_0 0x1F +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_46_5 0x20 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_48_0 0x21 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_49_5 0x22 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_51_0 0x23 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_52_5 0x24 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_54_0 0x25 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_55_5 0x26 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_57_0 0x27 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_58_5 0x28 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_60_0 0x29 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_61_5 0x2A +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_GAIN_NEG_63_0 0x2B + +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_MUTE_S 1 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_MUTE_M 0x2 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_MUTE_MUTE 0x1 +#define TIMPANI_PA_HPH_L_GAIN_HPH_L_MUTE_UNMUTE 0x0 + +#define TIMPANI_PA_HPH_L_GAIN_RESERVED_S 0 +#define TIMPANI_PA_HPH_L_GAIN_RESERVED_M 0x1 + + +/* -- For PA_HPH_R_GAIN */ +#define TIMPANI_A_PA_HPH_R_GAIN (0x37) +#define TIMPANI_PA_HPH_R_GAIN_RWC "RW" +#define TIMPANI_PA_HPH_R_GAIN_POR 0xae +#define TIMPANI_PA_HPH_R_GAIN_S 0 +#define TIMPANI_PA_HPH_R_GAIN_M 0xFF + + +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_S 2 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_M 0xFC +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_POS_1_5 0x0 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_POS_0_0 0x1 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_1_5 0x2 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_3_0 0x3 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_4_5 0x4 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_6_0 0x5 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_7_5 0x6 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_9_0 0x7 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_10_5 0x8 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_12_0 0x9 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_13_5 0xA +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_15_0 0xB +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_16_5 0xC +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_18_0 0xD +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_19_5 0xE +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_21_0 0xF +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_22_5 0x10 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_24_0 0x11 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_25_5 0x12 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_27_0 0x13 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_28_5 0x14 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_30_0 0x15 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_31_5 0x16 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_33_0 0x17 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_34_5 0x18 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_36_0 0x19 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_37_5 0x1A +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_39_0 0x1B +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_40_5 0x1C +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_42_0 0x1D +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_43_5 0x1E +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_45_0 0x1F +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_46_5 0x20 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_48_0 0x21 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_49_5 0x22 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_51_0 0x23 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_52_5 0x24 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_54_0 0x25 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_55_5 0x26 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_57_0 0x27 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_58_5 0x28 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_60_0 0x29 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_61_5 0x2A +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_GAIN_NEG_63_0 0x2B + +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_MUTE_S 1 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_MUTE_M 0x2 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_MUTE_MUTE 0x1 +#define TIMPANI_PA_HPH_R_GAIN_HPH_R_MUTE_UNMUTE 0x0 + +#define TIMPANI_PA_HPH_R_GAIN_RESERVED_S 0 +#define TIMPANI_PA_HPH_R_GAIN_RESERVED_M 0x1 + + +/* -- For AUXPGA_LR_GAIN */ +#define TIMPANI_A_AUXPGA_LR_GAIN (0x38) +#define TIMPANI_AUXPGA_LR_GAIN_RWC "RW" +#define TIMPANI_AUXPGA_LR_GAIN_POR 0xaa +#define TIMPANI_AUXPGA_LR_GAIN_S 0 +#define TIMPANI_AUXPGA_LR_GAIN_M 0xFF + + +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_S 4 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_M 0xF0 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_30DB 0x0 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_27DB 0x1 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_24DB 0x2 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_21DB 0x3 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_18DB 0x4 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_15DB 0x5 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_12DB 0x6 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_9_0DB 0x7 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_6_0DB 0x8 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_NEG_3_0DB 0x9 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_POS_0_0DB 0xA +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_POS_3_0DB 0xB +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_POS_6_0DB 0xC +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_POS_9_0DB 0xD +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_POS_12_0DB_1 0xE +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_L_GAIN_POS_12_0DB_2 0xF + +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_S 0 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_M 0xF +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_30DB 0x0 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_27DB 0x1 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_24DB 0x2 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_21DB 0x3 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_18DB 0x4 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_15DB 0x5 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_12DB 0x6 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_9_0DB 0x7 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_6_0DB 0x8 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_NEG_3_0DB 0x9 +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_POS_0_0DB 0xA +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_POS_3_0DB 0xB +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_POS_6_0DB 0xC +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_POS_9_0DB 0xD +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_POS_12_0DB_1 0xE +#define TIMPANI_AUXPGA_LR_GAIN_AUXPGA_R_GAIN_POS_12_0DB_2 0xF + + +/* -- For PA_AUXO_EARPA_CONN */ +#define TIMPANI_A_PA_AUXO_EARPA_CONN (0x39) +#define TIMPANI_PA_AUXO_EARPA_CONN_RWC "RW" +#define TIMPANI_PA_AUXO_EARPA_CONN_POR 0 +#define TIMPANI_PA_AUXO_EARPA_CONN_S 0 +#define TIMPANI_PA_AUXO_EARPA_CONN_M 0xFF + + +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_AUXPGA_L_CONN_S 7 +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_AUXPGA_L_CONN_M 0x80 +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_AUXPGA_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_AUXPGA_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_IDAC_L_CONN_S 6 +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_IDAC_L_CONN_M 0x40 +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_IDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_IDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_CDAC_L_CONN_S 5 +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_CDAC_L_CONN_M 0x20 +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_CDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_AUXO_EARPA_CONN_AUXOUT_CDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_AUXO_EARPA_CONN_RESERVED_S 4 +#define TIMPANI_PA_AUXO_EARPA_CONN_RESERVED_M 0x10 + +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_GAIN_S 3 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_GAIN_M 0x8 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_GAIN_V_3_52DB 0x1 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_GAIN_V_2_02DB 0x0 + +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_AUXPGA_L_CONN_S 2 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_AUXPGA_L_CONN_M 0x4 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_AUXPGA_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_AUXPGA_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_IDAC_L_CONN_S 1 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_IDAC_L_CONN_M 0x2 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_IDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_IDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_CDAC_L_CONN_S 0 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_CDAC_L_CONN_M 0x1 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_CDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_AUXO_EARPA_CONN_EARPA_CDAC_L_CONN_CONNECT 0x1 + + +/* -- For PA_LINE_ST_CONN */ +#define TIMPANI_A_PA_LINE_ST_CONN (0x3A) +#define TIMPANI_PA_LINE_ST_CONN_RWC "RW" +#define TIMPANI_PA_LINE_ST_CONN_POR 0 +#define TIMPANI_PA_LINE_ST_CONN_S 0 +#define TIMPANI_PA_LINE_ST_CONN_M 0xFF + + +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_AUXPGA_L_CONN_S 7 +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_AUXPGA_L_CONN_M 0x80 +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_AUXPGA_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_AUXPGA_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_IDAC_L_CONN_S 6 +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_IDAC_L_CONN_M 0x40 +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_IDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_IDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_CDAC_L_CONN_S 5 +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_CDAC_L_CONN_M 0x20 +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_CDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_ST_CONN_LINE_L_CDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_AUXPGA_R_CONN_S 4 +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_AUXPGA_R_CONN_M 0x10 +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_AUXPGA_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_AUXPGA_R_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_IDAC_R_CONN_S 3 +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_IDAC_R_CONN_M 0x8 +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_IDAC_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_IDAC_R_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_CDAC_R_CONN_S 2 +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_CDAC_R_CONN_M 0x4 +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_CDAC_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_ST_CONN_LINE_R_CDAC_R_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_ST_CONN_AUXPGA_L_VCM_ADD_CURR_S 0 +#define TIMPANI_PA_LINE_ST_CONN_AUXPGA_L_VCM_ADD_CURR_M 0x3 +#define TIMPANI_PA_LINE_ST_CONN_AUXPGA_L_VCM_ADD_CURR_NONE 0x0 +#define TIMPANI_PA_LINE_ST_CONN_AUXPGA_L_VCM_ADD_CURR_V_1_25UA 0x1 +#define TIMPANI_PA_LINE_ST_CONN_AUXPGA_L_VCM_ADD_CURR_V_2_5UA 0x2 +#define TIMPANI_PA_LINE_ST_CONN_AUXPGA_L_VCM_ADD_CURR_V_3_75UA 0x3 + + +/* -- For PA_LINE_MONO_CONN */ +#define TIMPANI_A_PA_LINE_MONO_CONN (0x3B) +#define TIMPANI_PA_LINE_MONO_CONN_RWC "RW" +#define TIMPANI_PA_LINE_MONO_CONN_POR 0 +#define TIMPANI_PA_LINE_MONO_CONN_S 0 +#define TIMPANI_PA_LINE_MONO_CONN_M 0xFF + + +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_AUXPGA_L_CONN_S 7 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_AUXPGA_L_CONN_M 0x80 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_AUXPGA_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_AUXPGA_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_IDAC_L_CONN_S 6 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_IDAC_L_CONN_M 0x40 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_IDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_IDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_CDAC_L_CONN_S 5 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_CDAC_L_CONN_M 0x20 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_CDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_CDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_AUXPGA_L_INV_CONN_S 4 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_AUXPGA_L_INV_CONN_M 0x10 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_AUXPGA_L_INV_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_AUXPGA_L_INV_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_IDAC_L_INV_CONN_S 3 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_IDAC_L_INV_CONN_M 0x8 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_IDAC_L_INV_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_IDAC_L_INV_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_CDAC_L_INV_CONN_S 2 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_CDAC_L_INV_CONN_M 0x4 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_CDAC_L_INV_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_LINE_MONO_CONN_LINE_R_CDAC_L_INV_CONN_CONNECT 0x1 + +#define TIMPANI_PA_LINE_MONO_CONN_AUXPGA_R_VCM_ADD_CURR_S 0 +#define TIMPANI_PA_LINE_MONO_CONN_AUXPGA_R_VCM_ADD_CURR_M 0x3 +#define TIMPANI_PA_LINE_MONO_CONN_AUXPGA_R_VCM_ADD_CURR_NONE 0x0 +#define TIMPANI_PA_LINE_MONO_CONN_AUXPGA_R_VCM_ADD_CURR_V_1_25UA 0x1 +#define TIMPANI_PA_LINE_MONO_CONN_AUXPGA_R_VCM_ADD_CURR_V_2_5UA 0x2 +#define TIMPANI_PA_LINE_MONO_CONN_AUXPGA_R_VCM_ADD_CURR_V_3_75UA 0x3 + + +/* -- For PA_HPH_ST_CONN */ +#define TIMPANI_A_PA_HPH_ST_CONN (0x3C) +#define TIMPANI_PA_HPH_ST_CONN_RWC "RW" +#define TIMPANI_PA_HPH_ST_CONN_POR 0 +#define TIMPANI_PA_HPH_ST_CONN_S 0 +#define TIMPANI_PA_HPH_ST_CONN_M 0xFF + + +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_AUXPGA_L_CONN_S 7 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_AUXPGA_L_CONN_M 0x80 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_AUXPGA_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_AUXPGA_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_IDAC_L_CONN_S 6 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_IDAC_L_CONN_M 0x40 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_IDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_IDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_CDAC_L_CONN_S 5 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_CDAC_L_CONN_M 0x20 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_CDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_CDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_AUXPGA_R_CONN_S 4 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_AUXPGA_R_CONN_M 0x10 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_AUXPGA_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_AUXPGA_R_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_IDAC_R_CONN_S 3 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_IDAC_R_CONN_M 0x8 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_IDAC_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_IDAC_R_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_CDAC_R_CONN_S 2 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_CDAC_R_CONN_M 0x4 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_CDAC_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_CDAC_R_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_RAMP_GEN_EN_S 1 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_RAMP_GEN_EN_M 0x2 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_RAMP_GEN_EN_DISABLE 0x1 +#define TIMPANI_PA_HPH_ST_CONN_HPH_L_RAMP_GEN_EN_ENABLE 0x0 + +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_RAMP_GEN_EN_S 0 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_RAMP_GEN_EN_M 0x1 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_RAMP_GEN_EN_DISABLE 0x1 +#define TIMPANI_PA_HPH_ST_CONN_HPH_R_RAMP_GEN_EN_ENABLE 0x0 + + +/* -- For PA_HPH_MONO_CONN */ +#define TIMPANI_A_PA_HPH_MONO_CONN (0x3D) +#define TIMPANI_PA_HPH_MONO_CONN_RWC "RW" +#define TIMPANI_PA_HPH_MONO_CONN_POR 0 +#define TIMPANI_PA_HPH_MONO_CONN_S 0 +#define TIMPANI_PA_HPH_MONO_CONN_M 0xFF + + +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_AUXPGA_L_CONN_S 7 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_AUXPGA_L_CONN_M 0x80 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_AUXPGA_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_AUXPGA_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_IDAC_L_CONN_S 6 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_IDAC_L_CONN_M 0x40 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_IDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_IDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_CDAC_L_CONN_S 5 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_CDAC_L_CONN_M 0x20 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_CDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_CDAC_L_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_AUXPGA_L_INV_CONN_S 4 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_AUXPGA_L_INV_CONN_M 0x10 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_AUXPGA_L_INV_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_AUXPGA_L_INV_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_IDAC_L_INV_CONN_S 3 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_IDAC_L_INV_CONN_M 0x8 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_IDAC_L_INV_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_IDAC_L_INV_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_CDAC_L_INV_CONN_S 2 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_CDAC_L_INV_CONN_M 0x4 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_CDAC_L_INV_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_HPH_MONO_CONN_HPH_R_CDAC_L_INV_CONN_CONNECT 0x1 + +#define TIMPANI_PA_HPH_MONO_CONN_RESERVED_S 0 +#define TIMPANI_PA_HPH_MONO_CONN_RESERVED_M 0x3 + + +/* -- For PA_CLASSD_CONN */ +#define TIMPANI_A_PA_CLASSD_CONN (0x3E) +#define TIMPANI_PA_CLASSD_CONN_RWC "RW" +#define TIMPANI_PA_CLASSD_CONN_POR 0 +#define TIMPANI_PA_CLASSD_CONN_S 0 +#define TIMPANI_PA_CLASSD_CONN_M 0xFF + + +#define TIMPANI_PA_CLASSD_CONN_CLASSD_CDAC_CONN_S 7 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_CDAC_CONN_M 0x80 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_CDAC_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_CDAC_CONN_CONNECT 0x1 + +#define TIMPANI_PA_CLASSD_CONN_CLASSD_IDAC_CONN_S 6 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_IDAC_CONN_M 0x40 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_IDAC_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_IDAC_CONN_CONNECT 0x1 + +#define TIMPANI_PA_CLASSD_CONN_CLASSD_AUXPGA_CONN_S 5 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_AUXPGA_CONN_M 0x20 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_AUXPGA_CONN_NO_CONNECT 0x0 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_AUXPGA_CONN_CONNECT 0x1 + +#define TIMPANI_PA_CLASSD_CONN_CLASSD_PA_MODE_S 4 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_PA_MODE_M 0x10 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_PA_MODE_MONO_DIFF 0x1 +#define TIMPANI_PA_CLASSD_CONN_CLASSD_PA_MODE_STEREO 0x0 + +#define TIMPANI_PA_CLASSD_CONN_RESERVED_S 0 +#define TIMPANI_PA_CLASSD_CONN_RESERVED_M 0xF + + +/* -- For PA_CNP_CTL */ +#define TIMPANI_A_PA_CNP_CTL (0x3F) +#define TIMPANI_PA_CNP_CTL_RWC "RW" +#define TIMPANI_PA_CNP_CTL_POR 0x07 +#define TIMPANI_PA_CNP_CTL_S 0 +#define TIMPANI_PA_CNP_CTL_M 0xFF + + +#define TIMPANI_PA_CNP_CTL_CNP_RAMP_GEN_CURRENT_S 6 +#define TIMPANI_PA_CNP_CTL_CNP_RAMP_GEN_CURRENT_M 0xC0 +#define TIMPANI_PA_CNP_CTL_CNP_RAMP_GEN_CURRENT_V_1_75_NA 0x0 +#define TIMPANI_PA_CNP_CTL_CNP_RAMP_GEN_CURRENT_V_3_5_NA_NORMAL_OP 0x1 +#define TIMPANI_PA_CNP_CTL_CNP_RAMP_GEN_CURRENT_V_5_25_NA 0x2 +#define TIMPANI_PA_CNP_CTL_CNP_RAMP_GEN_CURRENT_V_10_NA 0x3 + +#define TIMPANI_PA_CNP_CTL_RESERVED_S 4 +#define TIMPANI_PA_CNP_CTL_RESERVED_M 0x30 + +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_CIRCUIT_EN_S 3 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_CIRCUIT_EN_M 0x8 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_CIRCUIT_EN_DISABLE 0x0 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_CIRCUIT_EN_ENABLE 0x1 + +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_S 0 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_M 0x7 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_V_2_220_V 0x0 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_V_2_243_V 0x1 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_V_2_266_V 0x2 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_V_2_290_V 0x3 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_V_2_341_V 0x4 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_V_2_339_V 0x5 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_V_2_365_V 0x6 +#define TIMPANI_PA_CNP_CTL_CLASSD_SUPPLY_DUMP_THRESH_V_2_391_V 0x7 + + +/* -- For PA_CLASSD_L_CTL */ +#define TIMPANI_A_PA_CLASSD_L_CTL (0x40) +#define TIMPANI_PA_CLASSD_L_CTL_RWC "RW" +#define TIMPANI_PA_CLASSD_L_CTL_POR 0x08 +#define TIMPANI_PA_CLASSD_L_CTL_S 0 +#define TIMPANI_PA_CLASSD_L_CTL_M 0xFF + + +#define TIMPANI_PA_CLASSD_L_CTL_RESERVED_S 6 +#define TIMPANI_PA_CLASSD_L_CTL_RESERVED_M 0xC0 + +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_LOGIC_RESET_S 5 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_LOGIC_RESET_M 0x20 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_LOGIC_RESET_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_LOGIC_RESET_RESET_PA_LOGIC 0x1 + +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_INT_RESET_S 4 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_INT_RESET_M 0x10 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_INT_RESET_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_INT_RESET_DISCHARGE_CAPS 0x1 + +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_REF_SEL_S 2 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_REF_SEL_M 0xC +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_REF_SEL_GND 0x0 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_REF_SEL_IBIAS_X_R_REF 0x1 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_REF_SEL_BG_VOLTAGE 0x2 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_REF_SEL_VDD_BY_2 0x3 + +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_PA_FORCE_1_S 1 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_PA_FORCE_1_M 0x2 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_PA_FORCE_1_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_PA_FORCE_1_PA_OUT_TO_VDD 0x1 + +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_PA_FORCE_0_S 0 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_PA_FORCE_0_M 0x1 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_PA_FORCE_0_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_L_CTL_CLASSD_L_PA_FORCE_0_PA_OUT_TO_GND 0x1 + + +/* -- For PA_CLASSD_R_CTL */ +#define TIMPANI_A_PA_CLASSD_R_CTL (0x41) +#define TIMPANI_PA_CLASSD_R_CTL_RWC "RW" +#define TIMPANI_PA_CLASSD_R_CTL_POR 0x08 +#define TIMPANI_PA_CLASSD_R_CTL_S 0 +#define TIMPANI_PA_CLASSD_R_CTL_M 0xFF + + +#define TIMPANI_PA_CLASSD_R_CTL_RESERVED_S 6 +#define TIMPANI_PA_CLASSD_R_CTL_RESERVED_M 0xC0 + +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_LOGIC_RESET_S 5 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_LOGIC_RESET_M 0x20 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_LOGIC_RESET_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_LOGIC_RESET_RESET_PA_LOGIC 0x1 + +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_INT_RESET_S 4 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_INT_RESET_M 0x10 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_INT_RESET_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_INT_RESET_DISCHARGE_CAPS 0x1 + +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_REF_SEL_S 2 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_REF_SEL_M 0xC +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_REF_SEL_GND 0x0 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_REF_SEL_IBIAS_X_R_REF 0x1 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_REF_SEL_BG_VOLTAGE 0x2 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_REF_SEL_VDD_BY_2 0x3 + +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_PA_FORCE_1_S 1 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_PA_FORCE_1_M 0x2 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_PA_FORCE_1_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_PA_FORCE_1_PA_OUT_TO_VDD 0x1 + +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_PA_FORCE_0_S 0 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_PA_FORCE_0_M 0x1 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_PA_FORCE_0_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_R_CTL_CLASSD_R_PA_FORCE_0_PA_OUT_TO_GND 0x1 + + +/* -- For PA_CLASSD_INT2_CTL */ +#define TIMPANI_A_PA_CLASSD_INT2_CTL (0x42) +#define TIMPANI_PA_CLASSD_INT2_CTL_RWC "RW" +#define TIMPANI_PA_CLASSD_INT2_CTL_POR 0xb0 +#define TIMPANI_PA_CLASSD_INT2_CTL_S 0 +#define TIMPANI_PA_CLASSD_INT2_CTL_M 0xFF + + +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_CFB_S 6 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_CFB_M 0xC0 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_CFB_V_5_0PF 0x0 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_CFB_V_7_5PF 0x1 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_CFB_V_10PF 0x2 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_CFB_V_15PF 0x3 + +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_RIN_S 4 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_RIN_M 0x30 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_RIN_V_100K 0x0 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_RIN_V_150K 0x1 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_RIN_V_175K 0x2 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_L_INT2_RIN_V_200K 0x3 + +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_CFB_S 2 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_CFB_M 0xC +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_CFB_V_5_0PF 0x0 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_CFB_V_7_5PF 0x1 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_CFB_V_10PF 0x2 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_CFB_V_15PF 0x3 + +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_RIN_S 0 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_RIN_M 0x3 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_RIN_V_100K 0x0 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_RIN_V_150K 0x1 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_RIN_V_175K 0x2 +#define TIMPANI_PA_CLASSD_INT2_CTL_CLASSD_R_INT2_RIN_V_200K 0x3 + + +/* -- For PA_HPH_L_OCP_CLK_CTL */ +#define TIMPANI_A_PA_HPH_L_OCP_CLK_CTL (0x43) +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_RWC "RW" +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_POR 0xf2 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_S 0 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_M 0xFF + + +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_DIV_2_EN_S 7 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_DIV_2_EN_M 0x80 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_DIV_2_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_DIV_2_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_EN_S 6 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_EN_M 0x40 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_RATIO_S 4 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_RATIO_M 0x30 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_RATIO_DIV2 0x0 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_RATIO_DIV4 0x1 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_RATIO_DIV6 0x2 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CARRIER_PROG_DIV_RATIO_DIV8 0x3 + +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CLK_SEL_LEFT_S 3 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CLK_SEL_LEFT_M 0x8 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CLK_SEL_LEFT_CLK_FROM_CH_2 0x1 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_CLK_SEL_LEFT_CLK_FROM_CH_1 0x0 + +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_2_EN_S 2 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_2_EN_M 0x4 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_2_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_2_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_RATIO_S 0 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_RATIO_M 0x3 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_RATIO_DIV_BY_4 0x0 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_RATIO_DIV_BY_8 0x1 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_RATIO_DIV_BY_12 0x2 +#define TIMPANI_PA_HPH_L_OCP_CLK_CTL_HPH_L_OCP_TIMER_DIV_RATIO_DIV_BY_16 0x3 + + +/* -- For PA_CLASSD_L_SW_CTL */ +#define TIMPANI_A_PA_CLASSD_L_SW_CTL (0x44) +#define TIMPANI_PA_CLASSD_L_SW_CTL_RWC "RW" +#define TIMPANI_PA_CLASSD_L_SW_CTL_POR 0x37 +#define TIMPANI_PA_CLASSD_L_SW_CTL_S 0 +#define TIMPANI_PA_CLASSD_L_SW_CTL_M 0xFF + + +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_BREAK_BEFORE_MAKE_DELAY_S 6 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_BREAK_BEFORE_MAKE_DELAY_M 0xC0 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_BREAK_BEFORE_MAKE_DELAY_V_1 0x0 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_BREAK_BEFORE_MAKE_DELAY_V_2 0x1 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_BREAK_BEFORE_MAKE_DELAY_V_3 0x2 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_BREAK_BEFORE_MAKE_DELAY_V_4 0x3 + +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_OUT_DRIVE_STREN_S 4 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_OUT_DRIVE_STREN_M 0x30 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_OUT_DRIVE_STREN_V_3_OF_6_UNITS 0x0 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_OUT_DRIVE_STREN_V_4_OF_6_UNITS 0x1 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_OUT_DRIVE_STREN_V_5_OF_6_UNITS 0x2 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_OUT_DRIVE_STREN_V_6_OF_6_UNITS 0x3 + +#define TIMPANI_PA_CLASSD_L_SW_CTL_RESERVED_S 3 +#define TIMPANI_PA_CLASSD_L_SW_CTL_RESERVED_M 0x8 + +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_BYPASS_CAP_EN_S 2 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_BYPASS_CAP_EN_M 0x4 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_BYPASS_CAP_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_BYPASS_CAP_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_NON_OVERLAP_EN_S 1 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_NON_OVERLAP_EN_M 0x2 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_NON_OVERLAP_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_NON_OVERLAP_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_SWITCH_MODE_S 0 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_SWITCH_MODE_M 0x1 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_SWITCH_MODE_POWER_GROUND 0x0 +#define TIMPANI_PA_CLASSD_L_SW_CTL_CLASSD_L_CDAC_SWITCH_MODE_RST_MIDPOINT 0x1 + +/* -- For PA_CLASSD_L_OCP1 */ +#define TIMPANI_A_PA_CLASSD_L_OCP1 (0x45) +#define TIMPANI_PA_CLASSD_L_OCP1_RWC "RW" +#define TIMPANI_PA_CLASSD_L_OCP1_POR 0xff +#define TIMPANI_PA_CLASSD_L_OCP1_S 0 +#define TIMPANI_PA_CLASSD_L_OCP1_M 0xFF + + +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_EN_S 7 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_EN_M 0x80 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_LOCK_S 6 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_LOCK_M 0x40 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_LOCK_NEVER_LOCKS 0x0 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_LOCK_LOCKS 0x1 + +#define TIMPANI_PA_CLASSD_L_OCP1_OCP_CUR_THRESH_S 4 +#define TIMPANI_PA_CLASSD_L_OCP1_OCP_CUR_THRESH_M 0x30 +#define TIMPANI_PA_CLASSD_L_OCP1_OCP_CUR_THRESH_V_100MA_83_3MA_66_7MA_50MA 0x0 +#define TIMPANI_PA_CLASSD_L_OCP1_OCP_CUR_THRESH_V_133MA_111MA_88_7MA_66_7MA 0x1 +#define TIMPANI_PA_CLASSD_L_OCP1_OCP_CUR_THRESH_V_166MA_138MA_111MA_83_3MA 0x2 +#define TIMPANI_PA_CLASSD_L_OCP1_OCP_CUR_THRESH_V_200MA_166MA_133MA_100MA 0x3 + +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_S 0 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_M 0xF +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_1 0x1 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_2 0x2 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_3 0x3 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_4 0x4 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_5 0x5 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_6 0x6 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_7 0x7 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_8 0x8 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_9 0x9 +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_10 0xA +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_11 0xB +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_12 0xC +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_13 0xD +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_14 0xE +#define TIMPANI_PA_CLASSD_L_OCP1_CLASSD_L_OCP_NUM_CONN_ATTEMPTS_V_15 0xF + +/* -- For PA_CLASSD_L_OCP2 */ +#define TIMPANI_A_PA_CLASSD_L_OCP2 (0x46) +#define TIMPANI_PA_CLASSD_L_OCP2_RWC "RW" +#define TIMPANI_PA_CLASSD_L_OCP2_POR 0x77 +#define TIMPANI_PA_CLASSD_L_OCP2_S 0 +#define TIMPANI_PA_CLASSD_L_OCP2_M 0xFF + + +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_S 4 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_M 0xF0 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_255 0x0 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_511 0x1 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_767 0x2 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_1023 0x3 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_1279 0x4 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_1535 0x5 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_1791 0x6 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_2047 0x7 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_2303 0x8 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_2559 0x9 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_2815 0xA +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_3071 0xB +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_3327 0xC +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_3583 0xD +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_3839 0xE +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_WAIT_CNT_V_4095 0xF + +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_S 0 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_M 0xF +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_255 0x0 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_511 0x1 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_767 0x2 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_1023 0x3 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_1279 0x4 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_1535 0x5 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_1791 0x6 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_2047 0x7 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_2303 0x8 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_2559 0x9 +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_2815 0xA +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_3071 0xB +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_3327 0xC +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_3583 0xD +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_3839 0xE +#define TIMPANI_PA_CLASSD_L_OCP2_CLASSD_L_OCP_OCP_RUN_CNT_V_4095 0xF + + +/* -- For PA_HPH_R_OCP_CLK_CTL */ +#define TIMPANI_A_PA_HPH_R_OCP_CLK_CTL (0x47) +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_RWC "RW" +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_POR 0xf2 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_S 0 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_M 0xFF + + +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_DIV_2_EN_S 7 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_DIV_2_EN_M 0x80 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_DIV_2_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_DIV_2_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_EN_S 6 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_EN_M 0x40 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_RATIO_S 4 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_RATIO_M 0x30 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_RATIO_DIV2 0x0 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_RATIO_DIV4 0x1 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_RATIO_DIV6 0x2 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CARRIER_PROG_DIV_RATIO_DIV8 0x3 + +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CLK_SEL_RIGHT_S 3 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CLK_SEL_RIGHT_M 0x8 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CLK_SEL_RIGHT_CLK_FROM_CH_2 0x1 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_CLK_SEL_RIGHT_CLK_FROM_CH_1 0x0 + +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_2_EN_S 2 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_2_EN_M 0x4 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_2_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_2_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_RATIO_S 0 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_RATIO_M 0x3 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_RATIO_DIV_BY_4 0x0 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_RATIO_DIV_BY_8 0x1 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_RATIO_DIV_BY_12 0x2 +#define TIMPANI_PA_HPH_R_OCP_CLK_CTL_HPH_R_OCP_TIMER_DIV_RATIO_DIV_BY_16 0x3 + + +/* -- For PA_CLASSD_R_SW_CTL */ +#define TIMPANI_A_PA_CLASSD_R_SW_CTL (0x48) +#define TIMPANI_PA_CLASSD_R_SW_CTL_RWC "RW" +#define TIMPANI_PA_CLASSD_R_SW_CTL_POR 0x37 +#define TIMPANI_PA_CLASSD_R_SW_CTL_S 0 +#define TIMPANI_PA_CLASSD_R_SW_CTL_M 0xFF + + +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_BREAK_BEFORE_MAKE_DELAY_S 6 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_BREAK_BEFORE_MAKE_DELAY_M 0xC0 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_BREAK_BEFORE_MAKE_DELAY_V_1 0x0 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_BREAK_BEFORE_MAKE_DELAY_V_2 0x1 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_BREAK_BEFORE_MAKE_DELAY_V_3 0x2 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_BREAK_BEFORE_MAKE_DELAY_V_4 0x3 + +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_OUT_DRIVE_STREN_S 4 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_OUT_DRIVE_STREN_M 0x30 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_OUT_DRIVE_STREN_V_3_OF_6_UNITS 0x0 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_OUT_DRIVE_STREN_V_4_OF_6_UNITS 0x1 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_OUT_DRIVE_STREN_V_5_OF_6_UNITS 0x2 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_OUT_DRIVE_STREN_V_6_OF_6_UNITS 0x3 + +#define TIMPANI_PA_CLASSD_R_SW_CTL_RESERVED_S 3 +#define TIMPANI_PA_CLASSD_R_SW_CTL_RESERVED_M 0x8 + +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_BYPASS_CAP_EN_S 2 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_BYPASS_CAP_EN_M 0x4 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_BYPASS_CAP_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_BYPASS_CAP_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_NON_OVERLAP_EN_S 1 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_NON_OVERLAP_EN_M 0x2 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_NON_OVERLAP_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_NON_OVERLAP_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_SWITCH_MODE_S 0 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_SWITCH_MODE_M 0x1 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_SWITCH_MODE_POWER_GROUND 0x0 +#define TIMPANI_PA_CLASSD_R_SW_CTL_CLASSD_R_CDAC_SWITCH_MODE_RST_MIDPOINT 0x1 + + +/* -- For PA_CLASSD_R_OCP1 */ +#define TIMPANI_A_PA_CLASSD_R_OCP1 (0x49) +#define TIMPANI_PA_CLASSD_R_OCP1_RWC "RW" +#define TIMPANI_PA_CLASSD_R_OCP1_POR 0xff +#define TIMPANI_PA_CLASSD_R_OCP1_S 0 +#define TIMPANI_PA_CLASSD_R_OCP1_M 0xFF + + +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_EN_S 7 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_EN_M 0x80 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_EN_DISABLE 0x0 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_EN_ENABLE 0x1 + +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_LOCK_S 6 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_LOCK_M 0x40 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_LOCK_NEVER_LOCKS 0x0 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_LOCK_LOCKS 0x1 + +#define TIMPANI_PA_CLASSD_R_OCP1_OCP_CUR_THRESH_S 4 +#define TIMPANI_PA_CLASSD_R_OCP1_OCP_CUR_THRESH_M 0x30 +#define TIMPANI_PA_CLASSD_R_OCP1_OCP_CUR_THRESH_V_100MA_83_3MA_66_7MA_50MA 0x0 +#define TIMPANI_PA_CLASSD_R_OCP1_OCP_CUR_THRESH_V_133MA_111MA_88_7MA_66_7MA 0x1 +#define TIMPANI_PA_CLASSD_R_OCP1_OCP_CUR_THRESH_V_166MA_138MA_111MA_83_3MA 0x2 +#define TIMPANI_PA_CLASSD_R_OCP1_OCP_CUR_THRESH_V_200MA_166MA_133MA_100MA 0x3 + +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_S 0 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_M 0xF +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_1 0x1 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_2 0x2 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_3 0x3 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_4 0x4 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_5 0x5 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_6 0x6 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_7 0x7 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_8 0x8 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_9 0x9 +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_10 0xA +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_11 0xB +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_12 0xC +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_13 0xD +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_14 0xE +#define TIMPANI_PA_CLASSD_R_OCP1_CLASSD_R_OCP_NUM_CONN_ATTEMPTS_V_15 0xF + + +/* -- For PA_CLASSD_R_OCP2 */ +#define TIMPANI_A_PA_CLASSD_R_OCP2 (0x4A) +#define TIMPANI_PA_CLASSD_R_OCP2_RWC "RW" +#define TIMPANI_PA_CLASSD_R_OCP2_POR 0x77 +#define TIMPANI_PA_CLASSD_R_OCP2_S 0 +#define TIMPANI_PA_CLASSD_R_OCP2_M 0xFF + + +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_S 4 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_M 0xF0 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_255 0x0 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_511 0x1 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_767 0x2 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_1023 0x3 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_1279 0x4 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_1535 0x5 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_1791 0x6 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_2047 0x7 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_2303 0x8 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_2559 0x9 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_2815 0xA +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_3071 0xB +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_3327 0xC +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_3583 0xD +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_3839 0xE +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_WAIT_CNT_V_4095 0xF + +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_S 0 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_M 0xF +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_255 0x0 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_511 0x1 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_767 0x2 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_1023 0x3 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_1279 0x4 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_1535 0x5 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_1791 0x6 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_2047 0x7 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_2303 0x8 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_2559 0x9 +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_2815 0xA +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_3071 0xB +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_3327 0xC +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_3583 0xD +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_3839 0xE +#define TIMPANI_PA_CLASSD_R_OCP2_CLASSD_R_OCP_OCP_RUN_CNT_V_4095 0xF + + +/* -- For PA_HPH_CTL1 */ +#define TIMPANI_A_PA_HPH_CTL1 (0x4B) +#define TIMPANI_PA_HPH_CTL1_RWC "RW" +#define TIMPANI_PA_HPH_CTL1_POR 0x44 +#define TIMPANI_PA_HPH_CTL1_S 0 +#define TIMPANI_PA_HPH_CTL1_M 0xFF + + +#define TIMPANI_PA_HPH_CTL1_HPH_GM3_BIAS_S 4 +#define TIMPANI_PA_HPH_CTL1_HPH_GM3_BIAS_M 0xF0 +#define TIMPANI_PA_HPH_CTL1_HPH_GM3_BIAS_V_400PER 0x1 +#define TIMPANI_PA_HPH_CTL1_HPH_GM3_BIAS_V_200PER 0x2 +#define TIMPANI_PA_HPH_CTL1_HPH_GM3_BIAS_V_133PER 0x3 +#define TIMPANI_PA_HPH_CTL1_HPH_GM3_BIAS_V_100PER 0x4 +#define TIMPANI_PA_HPH_CTL1_HPH_GM3_BIAS_V_66PER 0x6 +#define TIMPANI_PA_HPH_CTL1_HPH_GM3_BIAS_V_50PER 0x8 +#define TIMPANI_PA_HPH_CTL1_HPH_GM3_BIAS_V_33PER 0xC + +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_DET_EN_S 3 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_DET_EN_M 0x8 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_DET_EN_DISABLE 0x0 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_DET_EN_ENABLE 0x1 + +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_CUR_LIMIT_S 0 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_CUR_LIMIT_M 0x7 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_CUR_LIMIT_V_300MA 0x0 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_CUR_LIMIT_V_350MA 0x2 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_CUR_LIMIT_V_365MA 0x3 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_CUR_LIMIT_V_150MA 0x4 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_CUR_LIMIT_V_190MA 0x6 +#define TIMPANI_PA_HPH_CTL1_HPH_SHORT_CIRCUIT_CUR_LIMIT_V_220MA 0x7 + + +/* -- For PA_HPH_CTL2 */ +#define TIMPANI_A_PA_HPH_CTL2 (0x4C) +#define TIMPANI_PA_HPH_CTL2_RWC "RW" +#define TIMPANI_PA_HPH_CTL2_POR 0xC8 +#define TIMPANI_PA_HPH_CTL2_S 0 +#define TIMPANI_PA_HPH_CTL2_M 0xFF + + +#define TIMPANI_PA_HPH_CTL2_HPH_SW_VNEG_CTL_S 7 +#define TIMPANI_PA_HPH_CTL2_HPH_SW_VNEG_CTL_M 0x80 +#define TIMPANI_PA_HPH_CTL2_HPH_SW_VNEG_CTL_VNEG 0x1 +#define TIMPANI_PA_HPH_CTL2_HPH_SW_VNEG_CTL_VSS 0x0 + +#define TIMPANI_PA_HPH_CTL2_HPH_VNEG_PS_GAIN_S 6 +#define TIMPANI_PA_HPH_CTL2_HPH_VNEG_PS_GAIN_M 0x40 +#define TIMPANI_PA_HPH_CTL2_HPH_VNEG_PS_GAIN_V_1_5 0x1 +#define TIMPANI_PA_HPH_CTL2_HPH_VNEG_PS_GAIN_V_2_5 0x0 + +#define TIMPANI_PA_HPH_CTL2_HPH_PS_FILTER_EN_S 5 +#define TIMPANI_PA_HPH_CTL2_HPH_PS_FILTER_EN_M 0x20 +#define TIMPANI_PA_HPH_CTL2_HPH_PS_FILTER_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_CTL2_HPH_PS_FILTER_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_CTL2_HPH_OCP_EN_S 4 +#define TIMPANI_PA_HPH_CTL2_HPH_OCP_EN_M 0x10 +#define TIMPANI_PA_HPH_CTL2_HPH_OCP_EN_ENABLE 0x1 +#define TIMPANI_PA_HPH_CTL2_HPH_OCP_EN_DISABLE 0x0 + +#define TIMPANI_PA_HPH_CTL2_HPH_VREF_SEL_S 2 +#define TIMPANI_PA_HPH_CTL2_HPH_VREF_SEL_M 0xC +#define TIMPANI_PA_HPH_CTL2_HPH_VREF_SEL_GROUND 0x0 +#define TIMPANI_PA_HPH_CTL2_HPH_VREF_SEL_IBIAS_ON_RESISTOR 0x1 +#define TIMPANI_PA_HPH_CTL2_HPH_VREF_SEL_BG 0x2 +#define TIMPANI_PA_HPH_CTL2_HPH_VREF_SEL_AVDD_BY_2 0x3 + +#define TIMPANI_PA_HPH_CTL2_HPH_OUT_SHUNT_EN_S 1 +#define TIMPANI_PA_HPH_CTL2_HPH_OUT_SHUNT_EN_M 0x2 +#define TIMPANI_PA_HPH_CTL2_HPH_OUT_SHUNT_EN_DISABLE 0x0 +#define TIMPANI_PA_HPH_CTL2_HPH_OUT_SHUNT_EN_ENABLE 0x1 + +#define TIMPANI_PA_HPH_CTL2_RESERVED_S 0 +#define TIMPANI_PA_HPH_CTL2_RESERVED_M 0x1 + + +/* -- For PA_LINE_AUXO_CTL */ +#define TIMPANI_A_PA_LINE_AUXO_CTL (0x4D) +#define TIMPANI_PA_LINE_AUXO_CTL_RWC "RW" +#define TIMPANI_PA_LINE_AUXO_CTL_POR 0x2 +#define TIMPANI_PA_LINE_AUXO_CTL_S 0 +#define TIMPANI_PA_LINE_AUXO_CTL_M 0xFF + + +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_RAMPGEN_CNT_S 6 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_RAMPGEN_CNT_M 0xC0 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_RAMPGEN_CNT_V_1_75NA 0x0 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_RAMPGEN_CNT_V_3_5NA 0x1 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_RAMPGEN_CNT_V_5_25NA 0x2 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_RAMPGEN_CNT_V_10NA 0x3 + +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_L_BIAS_CUR_S 4 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_L_BIAS_CUR_M 0x30 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_L_BIAS_CUR_V_60UA 0x0 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_L_BIAS_CUR_V_30UA_1 0x1 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_L_BIAS_CUR_V_30UA_2 0x2 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_L_BIAS_CUR_V_15UA 0x3 + +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_R_BIAS_CUR_S 2 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_R_BIAS_CUR_M 0xC +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_R_BIAS_CUR_V_60UA 0x0 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_R_BIAS_CUR_V_30UA_1 0x1 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_R_BIAS_CUR_V_30UA_2 0x2 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_R_BIAS_CUR_V_15UA 0x3 + +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_CM_REF_SEL_S 0 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_CM_REF_SEL_M 0x3 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_CM_REF_SEL_VSSA 0x0 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_CM_REF_SEL_BG 0x2 +#define TIMPANI_PA_LINE_AUXO_CTL_LINEO_AUXO_CM_REF_SEL_VDDA_BY_2 0x3 + + +/* -- For PA_AUXO_EARPA_CTL */ +#define TIMPANI_A_PA_AUXO_EARPA_CTL (0x4E) +#define TIMPANI_PA_AUXO_EARPA_CTL_RWC "RW" +#define TIMPANI_PA_AUXO_EARPA_CTL_POR 0xe +#define TIMPANI_PA_AUXO_EARPA_CTL_S 0 +#define TIMPANI_PA_AUXO_EARPA_CTL_M 0xFF + + +#define TIMPANI_PA_AUXO_EARPA_CTL_RESERVED_S 6 +#define TIMPANI_PA_AUXO_EARPA_CTL_RESERVED_M 0xC0 + +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_BIAS_CUR_S 4 +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_BIAS_CUR_M 0x30 +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_BIAS_CUR_V_60UA 0x0 +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_BIAS_CUR_V_30UA 0x1 +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_BIAS_CUR_V_30UA_SAME_AS_01 0x2 +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_BIAS_CUR_V_15UA 0x3 + +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_GAIN_S 3 +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_GAIN_M 0x8 +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_GAIN_NEG_4_5DB 0x1 +#define TIMPANI_PA_AUXO_EARPA_CTL_AUXO_GAIN_NEG_3_0DB 0x0 + +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_MASTER_BIAS_CUR_S 1 +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_MASTER_BIAS_CUR_M 0x6 +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_MASTER_BIAS_CUR_V_12_5UA 0x0 +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_MASTER_BIAS_CUR_V_10_0UA 0x1 +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_MASTER_BIAS_CUR_V_7_5UA 0x2 +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_MASTER_BIAS_CUR_V_5_0UA 0x3 + +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_VCM_SOURCE_S 0 +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_VCM_SOURCE_M 0x1 +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_VCM_SOURCE_BG 0x1 +#define TIMPANI_PA_AUXO_EARPA_CTL_EARPA_VCM_SOURCE_LOCAL_VCM 0x0 + + +/* -- For PA_EARO_CTL */ +#define TIMPANI_A_PA_EARO_CTL (0x4F) +#define TIMPANI_PA_EARO_CTL_RWC "RW" +#define TIMPANI_PA_EARO_CTL_POR 0x0 +#define TIMPANI_PA_EARO_CTL_S 0 +#define TIMPANI_PA_EARO_CTL_M 0xFF + + +#define TIMPANI_PA_EARO_CTL_EARPA_STARTUP_S 7 +#define TIMPANI_PA_EARO_CTL_EARPA_STARTUP_M 0x80 +#define TIMPANI_PA_EARO_CTL_EARPA_STARTUP_NORMAL_OP 0x0 +#define TIMPANI_PA_EARO_CTL_EARPA_STARTUP_CONNECT_INPUTS_TO_GROUND 0x1 + +#define TIMPANI_PA_EARO_CTL_EARPA_BYPASS_INPUT_CM_S 6 +#define TIMPANI_PA_EARO_CTL_EARPA_BYPASS_INPUT_CM_M 0x40 +#define TIMPANI_PA_EARO_CTL_EARPA_BYPASS_INPUT_CM_NO_BYPASS 0x0 +#define TIMPANI_PA_EARO_CTL_EARPA_BYPASS_INPUT_CM_BYPASS 0x1 + +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_S 3 +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_M 0x38 +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_V_213UA 0x0 +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_V_280UA 0x1 +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_V_408UA_1 0x2 +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_V_780UA_1 0x3 +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_V_408UA_2 0x4 +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_V_530UA 0x5 +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_V_780UA_2 0x6 +#define TIMPANI_PA_EARO_CTL_EARPA_NMOS_BIAS_CUR_V_1480UA 0x7 + +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_S 0 +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_M 0x7 +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_V_213UA 0x0 +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_V_280UA 0x1 +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_V_408UA_1 0x2 +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_V_780UA_1 0x3 +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_V_408UA_2 0x4 +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_V_530UA 0x5 +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_V_780UA_2 0x6 +#define TIMPANI_PA_EARO_CTL_EARPA_PMOS_BIAS_CUR_V_1480UA 0x7 + + +/* -- For PA_MASTER_BIAS_CUR */ +#define TIMPANI_A_PA_MASTER_BIAS_CUR (0x50) +#define TIMPANI_PA_MASTER_BIAS_CUR_RWC "RW" +#define TIMPANI_PA_MASTER_BIAS_CUR_POR 0xea +#define TIMPANI_PA_MASTER_BIAS_CUR_S 0 +#define TIMPANI_PA_MASTER_BIAS_CUR_M 0xFF + + +#define TIMPANI_PA_MASTER_BIAS_CUR_RAMPGEN_MASTER_BIAS_CUR_S 7 +#define TIMPANI_PA_MASTER_BIAS_CUR_RAMPGEN_MASTER_BIAS_CUR_M 0x80 +#define TIMPANI_PA_MASTER_BIAS_CUR_RAMPGEN_MASTER_BIAS_CUR_V_2_5UA 0x1 +#define TIMPANI_PA_MASTER_BIAS_CUR_RAMPGEN_MASTER_BIAS_CUR_V_5UA 0x0 + +#define TIMPANI_PA_MASTER_BIAS_CUR_AUXPGA_BIAS_CUR_S 5 +#define TIMPANI_PA_MASTER_BIAS_CUR_AUXPGA_BIAS_CUR_M 0x60 +#define TIMPANI_PA_MASTER_BIAS_CUR_AUXPGA_BIAS_CUR_V_10UA 0x0 +#define TIMPANI_PA_MASTER_BIAS_CUR_AUXPGA_BIAS_CUR_V_7_5UA 0x1 +#define TIMPANI_PA_MASTER_BIAS_CUR_AUXPGA_BIAS_CUR_V_5_0UA 0x2 +#define TIMPANI_PA_MASTER_BIAS_CUR_AUXPGA_BIAS_CUR_V_2_5UA 0x3 + +#define TIMPANI_PA_MASTER_BIAS_CUR_HPH_VCM_BUFF_BIAS_CURR_S 3 +#define TIMPANI_PA_MASTER_BIAS_CUR_HPH_VCM_BUFF_BIAS_CURR_M 0x18 +#define TIMPANI_PA_MASTER_BIAS_CUR_HPH_VCM_BUFF_BIAS_CURR_V_6_25UA 0x0 +#define TIMPANI_PA_MASTER_BIAS_CUR_HPH_VCM_BUFF_BIAS_CURR_V_5_0UA 0x1 +#define TIMPANI_PA_MASTER_BIAS_CUR_HPH_VCM_BUFF_BIAS_CURR_V_3_75UA 0x2 +#define TIMPANI_PA_MASTER_BIAS_CUR_HPH_VCM_BUFF_BIAS_CURR_V_2_5UA 0x3 + +#define TIMPANI_PA_MASTER_BIAS_CUR_LINE_VCM_BUFF_BIAS_CURR_S 1 +#define TIMPANI_PA_MASTER_BIAS_CUR_LINE_VCM_BUFF_BIAS_CURR_M 0x6 +#define TIMPANI_PA_MASTER_BIAS_CUR_LINE_VCM_BUFF_BIAS_CURR_V_6_25UA 0x0 +#define TIMPANI_PA_MASTER_BIAS_CUR_LINE_VCM_BUFF_BIAS_CURR_V_5_0UA 0x1 +#define TIMPANI_PA_MASTER_BIAS_CUR_LINE_VCM_BUFF_BIAS_CURR_V_3_75UA 0x2 +#define TIMPANI_PA_MASTER_BIAS_CUR_LINE_VCM_BUFF_BIAS_CURR_V_2_5UA 0x3 + +#define TIMPANI_PA_MASTER_BIAS_CUR_RESERVED_S 0 +#define TIMPANI_PA_MASTER_BIAS_CUR_RESERVED_M 0x1 + + +/* -- For PA_CLASSD_SC_STATUS */ +#define TIMPANI_A_PA_CLASSD_SC_STATUS (0x51) +#define TIMPANI_PA_CLASSD_SC_STATUS_RWC "R" +#define TIMPANI_PA_CLASSD_SC_STATUS_POR 0 +#define TIMPANI_PA_CLASSD_SC_STATUS_S 0 +#define TIMPANI_PA_CLASSD_SC_STATUS_M 0xFF + + +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_L_SC_DET_S 7 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_L_SC_DET_M 0x80 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_L_SC_DET_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_L_SC_DET_SC_DET 0x1 + +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_L_PWR_STAGE_HI_Z_S 6 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_L_PWR_STAGE_HI_Z_M 0x40 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_L_PWR_STAGE_HI_Z_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_L_PWR_STAGE_HI_Z_POWER_STAGE_OFF 0x1 + +#define TIMPANI_PA_CLASSD_SC_STATUS_RESERVED_1_S 4 +#define TIMPANI_PA_CLASSD_SC_STATUS_RESERVED_1_M 0x30 + +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_R_SC_DET_S 3 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_R_SC_DET_M 0x8 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_R_SC_DET_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_R_SC_DET_SC_DET 0x1 + +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_R_PWR_STAGE_HI_Z_S 2 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_R_PWR_STAGE_HI_Z_M 0x4 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_R_PWR_STAGE_HI_Z_NORMAL_OP 0x0 +#define TIMPANI_PA_CLASSD_SC_STATUS_CLASSD_R_PWR_STAGE_HI_Z_POWER_STAGE_OFF 0x1 + +#define TIMPANI_PA_CLASSD_SC_STATUS_RESERVED_2_S 1 +#define TIMPANI_PA_CLASSD_SC_STATUS_RESERVED_2_M 0x2 + +#define TIMPANI_PA_CLASSD_SC_STATUS_RESERVED_S 0 +#define TIMPANI_PA_CLASSD_SC_STATUS_RESERVED_M 0x1 + + +/* -- For PA_HPH_SC_STATUS */ +#define TIMPANI_A_PA_HPH_SC_STATUS (0x52) +#define TIMPANI_PA_HPH_SC_STATUS_RWC "R" +#define TIMPANI_PA_HPH_SC_STATUS_POR 0 +#define TIMPANI_PA_HPH_SC_STATUS_S 0 +#define TIMPANI_PA_HPH_SC_STATUS_M 0xFF + + +#define TIMPANI_PA_HPH_SC_STATUS_HPH_L_SC_DET_S 7 +#define TIMPANI_PA_HPH_SC_STATUS_HPH_L_SC_DET_M 0x80 +#define TIMPANI_PA_HPH_SC_STATUS_HPH_L_SC_DET_NORMAL_OP 0x0 +#define TIMPANI_PA_HPH_SC_STATUS_HPH_L_SC_DET_SC_DET 0x1 + +#define TIMPANI_PA_HPH_SC_STATUS_RESERVED_1_S 4 +#define TIMPANI_PA_HPH_SC_STATUS_RESERVED_1_M 0x70 + +#define TIMPANI_PA_HPH_SC_STATUS_HPH_R_SC_DET_S 3 +#define TIMPANI_PA_HPH_SC_STATUS_HPH_R_SC_DET_M 0x8 +#define TIMPANI_PA_HPH_SC_STATUS_HPH_R_SC_DET_NORMAL_OP 0x0 +#define TIMPANI_PA_HPH_SC_STATUS_HPH_R_SC_DET_SC_DET 0x1 + +#define TIMPANI_PA_HPH_SC_STATUS_RESERVED_2_S 2 +#define TIMPANI_PA_HPH_SC_STATUS_RESERVED_2_M 0x4 + +#define TIMPANI_PA_HPH_SC_STATUS_RESERVED_S 0 +#define TIMPANI_PA_HPH_SC_STATUS_RESERVED_M 0x3 + + +/* -- For ATEST_EN */ +#define TIMPANI_A_ATEST_EN (0x53) +#define TIMPANI_ATEST_EN_RWC "RW" +#define TIMPANI_ATEST_EN_POR 0 +#define TIMPANI_ATEST_EN_S 0 +#define TIMPANI_ATEST_EN_M 0xFF + + +#define TIMPANI_ATEST_EN_ATEST_EN_S 7 +#define TIMPANI_ATEST_EN_ATEST_EN_M 0x80 +#define TIMPANI_ATEST_EN_ATEST_EN_DISABLE 0x0 +#define TIMPANI_ATEST_EN_ATEST_EN_ENABLE 0x1 + +#define TIMPANI_ATEST_EN_RESERVED_S 0 +#define TIMPANI_ATEST_EN_RESERVED_M 0x7F + + +/* -- For ATEST_TSHKADC */ +#define TIMPANI_A_ATEST_TSHKADC (0x54) +#define TIMPANI_ATEST_TSHKADC_RWC "RW" +#define TIMPANI_ATEST_TSHKADC_POR 0 +#define TIMPANI_ATEST_TSHKADC_S 0 +#define TIMPANI_ATEST_TSHKADC_M 0xFF + + +#define TIMPANI_ATEST_TSHKADC_RESERVED_S 4 +#define TIMPANI_ATEST_TSHKADC_RESERVED_M 0xF0 + +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_AN_CONN_S 2 +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_AN_CONN_M 0xC +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_AN_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_AN_CONN_MUX1 0x1 +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_AN_CONN_MUX2 0x2 +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_AN_CONN_MUX3 0x3 + +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_DIG_CONN_S 0 +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_DIG_CONN_M 0x3 +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_DIG_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_DIG_CONN_MUX1 0x1 +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_DIG_CONN_MUX2 0x2 +#define TIMPANI_ATEST_TSHKADC_ATEST_TSADC_DIG_CONN_MUX3 0x3 + + +/* -- For ATEST_TXADC13 */ +#define TIMPANI_A_ATEST_TXADC13 (0x55) +#define TIMPANI_ATEST_TXADC13_RWC "RW" +#define TIMPANI_ATEST_TXADC13_POR 0 +#define TIMPANI_ATEST_TXADC13_S 0 +#define TIMPANI_ATEST_TXADC13_M 0xFF + + +#define TIMPANI_ATEST_TXADC13_RESERVED_S 7 +#define TIMPANI_ATEST_TXADC13_RESERVED_M 0x80 + +#define TIMPANI_ATEST_TXADC13_ATEST_SEL_L_S 6 +#define TIMPANI_ATEST_TXADC13_ATEST_SEL_L_M 0x40 +#define TIMPANI_ATEST_TXADC13_ATEST_SEL_L_TXADC1 0x0 +#define TIMPANI_ATEST_TXADC13_ATEST_SEL_L_TXADC3 0x1 + +#define TIMPANI_ATEST_TXADC13_ATEST1_TXADC13_CONN_S 3 +#define TIMPANI_ATEST_TXADC13_ATEST1_TXADC13_CONN_M 0x38 +#define TIMPANI_ATEST_TXADC13_ATEST1_TXADC13_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_TXADC13_ATEST1_TXADC13_CONN_ICMP1_TO_ATEST1 0x1 +#define TIMPANI_ATEST_TXADC13_ATEST1_TXADC13_CONN_IOTA2_TO_ATEST1 0x2 +#define TIMPANI_ATEST_TXADC13_ATEST1_TXADC13_CONN_IOTA1_TO_ATEST1 0x3 +#define TIMPANI_ATEST_TXADC13_ATEST1_TXADC13_CONN_VICM_TO_ATEST1 0x4 +#define TIMPANI_ATEST_TXADC13_ATEST1_TXADC13_CONN_VTH_P_TO_ATEST1 0x5 +#define TIMPANI_ATEST_TXADC13_ATEST1_TXADC13_CONN_VREFP_TO_ATEST1 0x6 + +#define TIMPANI_ATEST_TXADC13_ATEST2_TXADC13_CONN_S 0 +#define TIMPANI_ATEST_TXADC13_ATEST2_TXADC13_CONN_M 0x7 +#define TIMPANI_ATEST_TXADC13_ATEST2_TXADC13_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_TXADC13_ATEST2_TXADC13_CONN_IDACREF_TO_ATEST2 0x1 +#define TIMPANI_ATEST_TXADC13_ATEST2_TXADC13_CONN_IB_10U_TO_ATEST2 0x2 +#define TIMPANI_ATEST_TXADC13_ATEST2_TXADC13_CONN_VREFMID_TO_ATEST2 0x3 +#define TIMPANI_ATEST_TXADC13_ATEST2_TXADC13_CONN_VOCM_TO_ATEST2 0x4 +#define TIMPANI_ATEST_TXADC13_ATEST2_TXADC13_CONN_VTH_N_TO_ATEST2 0x5 +#define TIMPANI_ATEST_TXADC13_ATEST2_TXADC13_CONN_VREFN_TO_ATEST2 0x6 + + +/* -- For ATEST_TXADC24 */ +#define TIMPANI_A_ATEST_TXADC24 (0x56) +#define TIMPANI_ATEST_TXADC24_RWC "RW" +#define TIMPANI_ATEST_TXADC24_POR 0 +#define TIMPANI_ATEST_TXADC24_S 0 +#define TIMPANI_ATEST_TXADC24_M 0xFF + + +#define TIMPANI_ATEST_TXADC24_RESERVED_S 7 +#define TIMPANI_ATEST_TXADC24_RESERVED_M 0x80 + +#define TIMPANI_ATEST_TXADC24_ATEST_SEL_R_S 6 +#define TIMPANI_ATEST_TXADC24_ATEST_SEL_R_M 0x40 +#define TIMPANI_ATEST_TXADC24_ATEST_SEL_R_TXADC1 0x0 +#define TIMPANI_ATEST_TXADC24_ATEST_SEL_R_TXADC3 0x1 + +#define TIMPANI_ATEST_TXADC24_ATEST1_TXADC24_CONN_S 3 +#define TIMPANI_ATEST_TXADC24_ATEST1_TXADC24_CONN_M 0x38 +#define TIMPANI_ATEST_TXADC24_ATEST1_TXADC24_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_TXADC24_ATEST1_TXADC24_CONN_ICMP1_TO_ATEST1 0x1 +#define TIMPANI_ATEST_TXADC24_ATEST1_TXADC24_CONN_IOTA2_TO_ATEST1 0x2 +#define TIMPANI_ATEST_TXADC24_ATEST1_TXADC24_CONN_IOTA1_TO_ATEST1 0x3 +#define TIMPANI_ATEST_TXADC24_ATEST1_TXADC24_CONN_VICM_TO_ATEST1 0x4 +#define TIMPANI_ATEST_TXADC24_ATEST1_TXADC24_CONN_VTH_P_TO_ATEST1 0x5 +#define TIMPANI_ATEST_TXADC24_ATEST1_TXADC24_CONN_VREFP_TO_ATEST1 0x6 + +#define TIMPANI_ATEST_TXADC24_ATEST2_TXADC24_CONN_S 0 +#define TIMPANI_ATEST_TXADC24_ATEST2_TXADC24_CONN_M 0x7 +#define TIMPANI_ATEST_TXADC24_ATEST2_TXADC24_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_TXADC24_ATEST2_TXADC24_CONN_IDACREF_TO_ATEST2 0x1 +#define TIMPANI_ATEST_TXADC24_ATEST2_TXADC24_CONN_IB_10U_TO_ATEST2 0x2 +#define TIMPANI_ATEST_TXADC24_ATEST2_TXADC24_CONN_VREFMID_TO_ATEST2 0x3 +#define TIMPANI_ATEST_TXADC24_ATEST2_TXADC24_CONN_VOCM_TO_ATEST2 0x4 +#define TIMPANI_ATEST_TXADC24_ATEST2_TXADC24_CONN_VTH_N_TO_ATEST2 0x5 +#define TIMPANI_ATEST_TXADC24_ATEST2_TXADC24_CONN_VREFN_TO_ATEST2 0x6 + + +/* -- For ATEST_AUXPGA */ +#define TIMPANI_A_ATEST_AUXPGA (0x57) +#define TIMPANI_ATEST_AUXPGA_RWC "RW" +#define TIMPANI_ATEST_AUXPGA_POR 0 +#define TIMPANI_ATEST_AUXPGA_S 0 +#define TIMPANI_ATEST_AUXPGA_M 0xFF + + +#define TIMPANI_ATEST_AUXPGA_ATEST1_AUXPGA_INT_VCM_CONN_S 7 +#define TIMPANI_ATEST_AUXPGA_ATEST1_AUXPGA_INT_VCM_CONN_M 0x80 +#define TIMPANI_ATEST_AUXPGA_ATEST1_AUXPGA_INT_VCM_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_AUXPGA_ATEST1_AUXPGA_INT_VCM_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_VCMI_VDD_CONN_S 6 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_VCMI_VDD_CONN_M 0x40 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_VCMI_VDD_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_VCMI_VDD_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_VCMO_R_L_CONN_S 5 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_VCMO_R_L_CONN_M 0x20 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_VCMO_R_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_VCMO_R_L_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_R_CONN_S 4 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_R_CONN_M 0x10 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_R_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_L_CONN_S 3 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_L_CONN_M 0x8 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_AUXPGA_ATEST_AUXPGA_L_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_AUXPGA_RESERVED_S 0 +#define TIMPANI_ATEST_AUXPGA_RESERVED_M 0x7 + + +/* -- For ATEST_CDAC */ +#define TIMPANI_A_ATEST_CDAC (0x58) +#define TIMPANI_ATEST_CDAC_RWC "RW" +#define TIMPANI_ATEST_CDAC_POR 0 +#define TIMPANI_ATEST_CDAC_S 0 +#define TIMPANI_ATEST_CDAC_M 0xFF + + +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_DWA_IN_CONN_S 7 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_DWA_IN_CONN_M 0x80 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_DWA_IN_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_DWA_IN_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_DWA_OUT_CONN_S 6 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_DWA_OUT_CONN_M 0x40 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_DWA_OUT_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_DWA_OUT_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_CDAC_ATEST_FILTER_L_OUT_CONN_S 5 +#define TIMPANI_ATEST_CDAC_ATEST_FILTER_L_OUT_CONN_M 0x20 +#define TIMPANI_ATEST_CDAC_ATEST_FILTER_L_OUT_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_CDAC_ATEST_FILTER_L_OUT_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_CDAC_ATEST_FILTER_R_OUT_CONN_S 4 +#define TIMPANI_ATEST_CDAC_ATEST_FILTER_R_OUT_CONN_M 0x10 +#define TIMPANI_ATEST_CDAC_ATEST_FILTER_R_OUT_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_CDAC_ATEST_FILTER_R_OUT_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_L_CONN_S 2 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_L_CONN_M 0xC +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_L_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_L_CONN_TEST1 0x1 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_L_CONN_TEST2 0x2 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_L_CONN_TEST3 0x3 + +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_R_CONN_S 0 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_R_CONN_M 0x3 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_R_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_R_CONN_TEST1 0x1 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_R_CONN_TEST2 0x2 +#define TIMPANI_ATEST_CDAC_ATEST_CDAC_R_CONN_TEST3 0x3 + + +/* -- For ATEST_IDAC */ +#define TIMPANI_A_ATEST_IDAC (0x59) +#define TIMPANI_ATEST_IDAC_RWC "RW" +#define TIMPANI_ATEST_IDAC_POR 0 +#define TIMPANI_ATEST_IDAC_S 0 +#define TIMPANI_ATEST_IDAC_M 0xFF + + +#define TIMPANI_ATEST_IDAC_ATEST1_LR_CONN_S 7 +#define TIMPANI_ATEST_IDAC_ATEST1_LR_CONN_M 0x80 +#define TIMPANI_ATEST_IDAC_ATEST1_LR_CONN_RIGHT 0x1 +#define TIMPANI_ATEST_IDAC_ATEST1_LR_CONN_LEFT 0x0 + +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_S 4 +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_M 0x70 +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_IDAC_NEG_OUT 0x7 +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_CT_FILTER_POS_OUT 0x6 +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_CT_FILTER_IBIAS 0x5 +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_NO_CONNECT_1 0x4 +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_NO_CONNECT_2 0x3 +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_NO_CONNECT_3 0x2 +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_NO_CONNECT_4 0x1 +#define TIMPANI_ATEST_IDAC_ATEST1_CONN_NO_CONNECT_5 0x0 + +#define TIMPANI_ATEST_IDAC_ATEST2_LR_CONN_S 3 +#define TIMPANI_ATEST_IDAC_ATEST2_LR_CONN_M 0x8 +#define TIMPANI_ATEST_IDAC_ATEST2_LR_CONN_RIGHT 0x1 +#define TIMPANI_ATEST_IDAC_ATEST2_LR_CONN_LEFT 0x0 + +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_S 0 +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_M 0x7 +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_IDAC_POS_OUT 0x7 +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_CT_FILTER_NEG_OUT 0x6 +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_IDAC_IBIAS 0x5 +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_NO_CONNECT_1 0x4 +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_NO_CONNECT_2 0x3 +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_NO_CONNECT_3 0x2 +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_NO_CONNECT_4 0x1 +#define TIMPANI_ATEST_IDAC_ATEST2_CONN_NO_CONNECT_5 0x0 + + +/* -- For ATEST_PA1 */ +#define TIMPANI_A_ATEST_PA1 (0x5A) +#define TIMPANI_ATEST_PA1_RWC "RW" +#define TIMPANI_ATEST_PA1_POR 0 +#define TIMPANI_ATEST_PA1_S 0 +#define TIMPANI_ATEST_PA1_M 0xFF + + +#define TIMPANI_ATEST_PA1_ATEST_EARPA_FSV_NP_CONN_S 7 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_FSV_NP_CONN_M 0x80 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_FSV_NP_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_FSV_NP_CONN_EN 0x1 + +#define TIMPANI_ATEST_PA1_ATEST_EARPA_NCASC_NMIRR_CONN_S 6 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_NCASC_NMIRR_CONN_M 0x40 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_NCASC_NMIRR_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_NCASC_NMIRR_CONN_EN 0x1 + +#define TIMPANI_ATEST_PA1_ATEST_EARPA_NMIRR_PCASC_CONN_S 5 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_NMIRR_PCASC_CONN_M 0x20 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_NMIRR_PCASC_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_NMIRR_PCASC_CONN_EN 0x1 + +#define TIMPANI_ATEST_PA1_ATEST_EARPA_VCM_PTAIL1_CONN_S 4 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_VCM_PTAIL1_CONN_M 0x10 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_VCM_PTAIL1_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_VCM_PTAIL1_CONN_EN 0x1 + +#define TIMPANI_ATEST_PA1_ATEST_EARPA_IBTEST_VSS2P2_CONN_S 3 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_IBTEST_VSS2P2_CONN_M 0x8 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_IBTEST_VSS2P2_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_IBTEST_VSS2P2_CONN_EN 0x1 + +#define TIMPANI_ATEST_PA1_ATEST_EARPA_ITEST1_ITEST2_CONN_S 2 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_ITEST1_ITEST2_CONN_M 0x4 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_ITEST1_ITEST2_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_PA1_ATEST_EARPA_ITEST1_ITEST2_CONN_EN 0x1 + +#define TIMPANI_ATEST_PA1_ATEST_CLASSD_CLK_GATING_S 1 +#define TIMPANI_ATEST_PA1_ATEST_CLASSD_CLK_GATING_M 0x2 +#define TIMPANI_ATEST_PA1_ATEST_CLASSD_CLK_GATING_PASS 0x0 +#define TIMPANI_ATEST_PA1_ATEST_CLASSD_CLK_GATING_GATE 0x1 + +#define TIMPANI_ATEST_PA1_ATEST2_HPH_VCM_CONN_S 0 +#define TIMPANI_ATEST_PA1_ATEST2_HPH_VCM_CONN_M 0x1 +#define TIMPANI_ATEST_PA1_ATEST2_HPH_VCM_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_PA1_ATEST2_HPH_VCM_CONN_CONNECT 0x1 + + +/* -- For ATEST_CLASSD */ +#define TIMPANI_A_ATEST_CLASSD (0x5B) +#define TIMPANI_ATEST_CLASSD_RWC "RW" +#define TIMPANI_ATEST_CLASSD_POR 0 +#define TIMPANI_ATEST_CLASSD_S 0 +#define TIMPANI_ATEST_CLASSD_M 0xFF + + +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_S 4 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_M 0xF0 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_NO_CONNECT_1 0x0 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_L_SC_OCP 0x1 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_L_CDAC_CLK 0x2 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_L_POS_CDAC 0x3 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_L_BREAK_BEFORE_MAKE_OUT_CP 0x4 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_L_COMP_OUT 0x5 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_L_INT2_POS_OUT 0x6 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_L_INT1_POS_OUT 0x7 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_NO_CONNECT_2 0x8 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_R_SC_OCP_SIGNAL 0x9 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_R_CDAC_CLK 0xA +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_R_POS_CDAC 0xB +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_R_BREAK_BEFORE_MAKE_OUT_CP 0xC +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_R_COMP_OUT 0xD +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_R_INT2_POS_OUT 0xE +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST1_CONN_R_INT1_POS_OUT 0xF + +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_S 0 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_M 0xF +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_NO_CONNECT_1 0x0 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_L_HI_Z_OCP 0x1 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_L_OCP_CLOCK 0x2 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_L_NEG_CDAC 0x3 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_L_BREAK_BEFORE_MAKE_OUT_CN 0x4 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_L_CM_BUFF_OUT 0x5 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_L_INT2_NEG_OUT 0x6 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_L_INT1_NEG_OUT 0x7 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_NO_CONNECT_2 0x8 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_R_HI_Z_OCP 0x9 +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_R_OCP_CLOCK 0xA +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_R_NEGATIVE_CDAC 0xB +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_R_BREAK_BEFORE_MAKE_OUT_CN 0xC +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_R_CM_BUFF_OUT 0xD +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_R_INTR2_NEG_OUT 0xE +#define TIMPANI_ATEST_CLASSD_CLASSD_ATEST2_CONN_R_INT1_NEG_OUT 0xF + + +/* -- For ATEST_LINEO_AUXO */ +#define TIMPANI_A_ATEST_LINEO_AUXO (0x5C) +#define TIMPANI_ATEST_LINEO_AUXO_RWC "RW" +#define TIMPANI_ATEST_LINEO_AUXO_POR 0 +#define TIMPANI_ATEST_LINEO_AUXO_S 0 +#define TIMPANI_ATEST_LINEO_AUXO_M 0xFF + + +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_AUXO_EN_S 7 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_AUXO_EN_M 0x80 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_AUXO_EN_DISABLE 0x0 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_AUXO_EN_ENABLE 0x1 + +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_AUXO_VCM_CONN_S 6 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_AUXO_VCM_CONN_M 0x40 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_AUXO_VCM_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_AUXO_VCM_CONN_CONNECT 0x1 + +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_L_NREFIN_STG1OP_CONN_S 5 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_L_NREFIN_STG1OP_CONN_M 0x20 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_L_NREFIN_STG1OP_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_L_NREFIN_STG1OP_CONN_EN 0x1 + +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_L_NMOS_PMOS_CONN_S 4 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_L_NMOS_PMOS_CONN_M 0x10 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_L_NMOS_PMOS_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_L_NMOS_PMOS_CONN_EN 01 + +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_R_NREFIN_STG1OP_CONN_S 3 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_R_NREFIN_STG1OP_CONN_M 0x8 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_R_NREFIN_STG1OP_CONN_NO_CONNECT 0x0 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_R_NREFIN_STG1OP_CONN_EN 01 + +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_R_NMOS_PMOS_CONN_S 2 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_R_NMOS_PMOS_CONN_M 0x4 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_R_NMOS_PMOS_CONN_DISABLE 0x0 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_LINEO_R_NMOS_PMOS_CONN_EN 0x1 + +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_AUXO_NREFIN_STG1OP_CONN_S 1 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_AUXO_NREFIN_STG1OP_CONN_M 0x2 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_AUXO_NREFIN_STG1OP_CONN_DISABLE 0x0 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_AUXO_NREFIN_STG1OP_CONN_EN 0x1 + +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_AUXO_NMOS_PMOS_CONN_S 0 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_AUXO_NMOS_PMOS_CONN_M 0x1 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_AUXO_NMOS_PMOS_CONN_DISABLE 0x0 +#define TIMPANI_ATEST_LINEO_AUXO_ATEST_AUXO_NMOS_PMOS_CONN_EN 0x1 + + +/* -- For CDC_RESET_CTL */ +#define TIMPANI_A_CDC_RESET_CTL (0x80) +#define TIMPANI_CDC_RESET_CTL_RWC "RW" +#define TIMPANI_CDC_RESET_CTL_POR 0 +#define TIMPANI_CDC_RESET_CTL_S 0 +#define TIMPANI_CDC_RESET_CTL_M 0x7F + + +#define TIMPANI_CDC_RESET_CTL_ARB_SOFT_RESET_S 6 +#define TIMPANI_CDC_RESET_CTL_ARB_SOFT_RESET_M 0x40 + +#define TIMPANI_CDC_RESET_CTL_TX2_SOFT_RESET_R_S 5 +#define TIMPANI_CDC_RESET_CTL_TX2_SOFT_RESET_R_M 0x20 + +#define TIMPANI_CDC_RESET_CTL_TX2_SOFT_RESET_L_S 4 +#define TIMPANI_CDC_RESET_CTL_TX2_SOFT_RESET_L_M 0x10 + +#define TIMPANI_CDC_RESET_CTL_RX2_SOFT_RESET_S 3 +#define TIMPANI_CDC_RESET_CTL_RX2_SOFT_RESET_M 0x8 + +#define TIMPANI_CDC_RESET_CTL_TX1_SOFT_RESET_R_S 2 +#define TIMPANI_CDC_RESET_CTL_TX1_SOFT_RESET_R_M 0x4 + +#define TIMPANI_CDC_RESET_CTL_RX1_SOFT_RESET_S 1 +#define TIMPANI_CDC_RESET_CTL_RX1_SOFT_RESET_M 0x2 + +#define TIMPANI_CDC_RESET_CTL_TX1_SOFT_RESET_L_S 0 +#define TIMPANI_CDC_RESET_CTL_TX1_SOFT_RESET_L_M 0x1 + + +/* -- For CDC_RX1_CTL */ +#define TIMPANI_A_CDC_RX1_CTL (0x81) +#define TIMPANI_CDC_RX1_CTL_RWC "RW" +#define TIMPANI_CDC_RX1_CTL_POR 0xc +#define TIMPANI_CDC_RX1_CTL_S 0 +#define TIMPANI_CDC_RX1_CTL_M 0x3F + + +#define TIMPANI_CDC_RX1_CTL_SIDETONE_EN1_R_S 5 +#define TIMPANI_CDC_RX1_CTL_SIDETONE_EN1_R_M 0x20 + +#define TIMPANI_CDC_RX1_CTL_SIDETONE_EN1_L_S 4 +#define TIMPANI_CDC_RX1_CTL_SIDETONE_EN1_L_M 0x10 + +#define TIMPANI_CDC_RX1_CTL_RX1_RATE_S 2 +#define TIMPANI_CDC_RX1_CTL_RX1_RATE_M 0xC +#define TIMPANI_CDC_RX1_CTL_RX1_RATE_OSR_256 0x3 +#define TIMPANI_CDC_RX1_CTL_RX1_RATE_OSR_128 0x1 +#define TIMPANI_CDC_RX1_CTL_RX1_RATE_OSR_64 0x0 + +#define TIMPANI_CDC_RX1_CTL_RX1_I2S_RATE_S 1 +#define TIMPANI_CDC_RX1_CTL_RX1_I2S_RATE_M 0x2 +#define TIMPANI_CDC_RX1_CTL_RX1_I2S_RATE_BR_32 0x1 +#define TIMPANI_CDC_RX1_CTL_RX1_I2S_RATE_BR_64 0x0 + +#define TIMPANI_CDC_RX1_CTL_RX1_I2S_MODE_S 0 +#define TIMPANI_CDC_RX1_CTL_RX1_I2S_MODE_M 0x1 +#define TIMPANI_CDC_RX1_CTL_RX1_I2S_MODE_MASTER 0x1 +#define TIMPANI_CDC_RX1_CTL_RX1_I2S_MODE_SLAVE 0x0 + + +/* -- For CDC_TX_I2S_CTL */ +#define TIMPANI_A_CDC_TX_I2S_CTL (0x82) +#define TIMPANI_CDC_TX_I2S_CTL_RWC "RW" +#define TIMPANI_CDC_TX_I2S_CTL_POR 0xc +#define TIMPANI_CDC_TX_I2S_CTL_S 0 +#define TIMPANI_CDC_TX_I2S_CTL_M 0x3F + + +#define TIMPANI_CDC_TX_I2S_CTL_TX2_I2S_SD_OE_S 5 +#define TIMPANI_CDC_TX_I2S_CTL_TX2_I2S_SD_OE_M 0x20 + +#define TIMPANI_CDC_TX_I2S_CTL_TX1_I2S_SD_OE_S 4 +#define TIMPANI_CDC_TX_I2S_CTL_TX1_I2S_SD_OE_M 0x10 + +#define TIMPANI_CDC_TX_I2S_CTL_TX_RATE_S 2 +#define TIMPANI_CDC_TX_I2S_CTL_TX_RATE_M 0xC +#define TIMPANI_CDC_TX_I2S_CTL_TX_RATE_OSR_256 0x3 +#define TIMPANI_CDC_TX_I2S_CTL_TX_RATE_OSR_128 0x1 +#define TIMPANI_CDC_TX_I2S_CTL_TX_RATE_OSR_64 0x0 + +#define TIMPANI_CDC_TX_I2S_CTL_TX_I2S_RATE_S 1 +#define TIMPANI_CDC_TX_I2S_CTL_TX_I2S_RATE_M 0x2 +#define TIMPANI_CDC_TX_I2S_CTL_TX_I2S_RATE_BR_32 0x1 +#define TIMPANI_CDC_TX_I2S_CTL_TX_I2S_RATE_BR_64 0x0 + +#define TIMPANI_CDC_TX_I2S_CTL_TX_I2S_MODE_S 0 +#define TIMPANI_CDC_TX_I2S_CTL_TX_I2S_MODE_M 0x1 +#define TIMPANI_CDC_TX_I2S_CTL_TX_I2S_MODE_MASTER 0x1 +#define TIMPANI_CDC_TX_I2S_CTL_TX_I2S_MODE_SLAVE 0x0 + + +/* -- For CDC_CH_CTL */ +#define TIMPANI_A_CDC_CH_CTL (0x83) +#define TIMPANI_CDC_CH_CTL_RWC "RW" +#define TIMPANI_CDC_CH_CTL_POR 0 +#define TIMPANI_CDC_CH_CTL_S 0 +#define TIMPANI_CDC_CH_CTL_M 0xFF + + +#define TIMPANI_CDC_CH_CTL_TX2_EN_R_S 7 +#define TIMPANI_CDC_CH_CTL_TX2_EN_R_M 0x80 + +#define TIMPANI_CDC_CH_CTL_TX2_EN_L_S 6 +#define TIMPANI_CDC_CH_CTL_TX2_EN_L_M 0x40 + +#define TIMPANI_CDC_CH_CTL_RX2_EN_R_S 5 +#define TIMPANI_CDC_CH_CTL_RX2_EN_R_M 0x20 + +#define TIMPANI_CDC_CH_CTL_RX2_EN_L_S 4 +#define TIMPANI_CDC_CH_CTL_RX2_EN_L_M 0x10 + +#define TIMPANI_CDC_CH_CTL_TX1_EN_R_S 3 +#define TIMPANI_CDC_CH_CTL_TX1_EN_R_M 0x8 + +#define TIMPANI_CDC_CH_CTL_TX1_EN_L_S 2 +#define TIMPANI_CDC_CH_CTL_TX1_EN_L_M 0x4 + +#define TIMPANI_CDC_CH_CTL_RX1_EN_R_S 1 +#define TIMPANI_CDC_CH_CTL_RX1_EN_R_M 0x2 + +#define TIMPANI_CDC_CH_CTL_RX1_EN_L_S 0 +#define TIMPANI_CDC_CH_CTL_RX1_EN_L_M 0x1 + + +/* -- For CDC_RX1LG */ +#define TIMPANI_A_CDC_RX1LG (0x84) +#define TIMPANI_CDC_RX1LG_RWC "RW" +#define TIMPANI_CDC_RX1LG_POR 0xac +#define TIMPANI_CDC_RX1LG_S 0 +#define TIMPANI_CDC_RX1LG_M 0xFF + + +#define TIMPANI_CDC_RX1LG_GAIN_S 0 +#define TIMPANI_CDC_RX1LG_GAIN_M 0xFF + + +/* -- For CDC_RX1RG */ +#define TIMPANI_A_CDC_RX1RG (0x85) +#define TIMPANI_CDC_RX1RG_RWC "RW" +#define TIMPANI_CDC_RX1RG_POR 0xac +#define TIMPANI_CDC_RX1RG_S 0 +#define TIMPANI_CDC_RX1RG_M 0xFF + + +#define TIMPANI_CDC_RX1RG_GAIN_S 0 +#define TIMPANI_CDC_RX1RG_GAIN_M 0xFF + + +/* -- For CDC_TX1LG */ +#define TIMPANI_A_CDC_TX1LG (0x86) +#define TIMPANI_CDC_TX1LG_RWC "RW" +#define TIMPANI_CDC_TX1LG_POR 0xac +#define TIMPANI_CDC_TX1LG_S 0 +#define TIMPANI_CDC_TX1LG_M 0xFF + + +#define TIMPANI_CDC_TX1LG_GAIN_S 0 +#define TIMPANI_CDC_TX1LG_GAIN_M 0xFF + + +/* -- For CDC_TX1RG */ +#define TIMPANI_A_CDC_TX1RG (0x87) +#define TIMPANI_CDC_TX1RG_RWC "RW" +#define TIMPANI_CDC_TX1RG_POR 0xac +#define TIMPANI_CDC_TX1RG_S 0 +#define TIMPANI_CDC_TX1RG_M 0xFF + + +#define TIMPANI_CDC_TX1RG_GAIN_S 0 +#define TIMPANI_CDC_TX1RG_GAIN_M 0xFF + + +/* -- For CDC_RX_PGA_TIMER */ +#define TIMPANI_A_CDC_RX_PGA_TIMER (0x88) +#define TIMPANI_CDC_RX_PGA_TIMER_RWC "RW" +#define TIMPANI_CDC_RX_PGA_TIMER_POR 0xff +#define TIMPANI_CDC_RX_PGA_TIMER_S 0 +#define TIMPANI_CDC_RX_PGA_TIMER_M 0xFF + + +#define TIMPANI_CDC_RX_PGA_TIMER_TIMER_VAL_S 0 +#define TIMPANI_CDC_RX_PGA_TIMER_TIMER_VAL_M 0xFF + + +/* -- For CDC_TX_PGA_TIMER */ +#define TIMPANI_A_CDC_TX_PGA_TIMER (0x89) +#define TIMPANI_CDC_TX_PGA_TIMER_RWC "RW" +#define TIMPANI_CDC_TX_PGA_TIMER_POR 0xff +#define TIMPANI_CDC_TX_PGA_TIMER_S 0 +#define TIMPANI_CDC_TX_PGA_TIMER_M 0xFF + + +#define TIMPANI_CDC_TX_PGA_TIMER_TIMER_VAL_S 0 +#define TIMPANI_CDC_TX_PGA_TIMER_TIMER_VAL_M 0xFF + + +/* -- For CDC_GCTL1 */ +#define TIMPANI_A_CDC_GCTL1 (0x8A) +#define TIMPANI_CDC_GCTL1_RWC "RW" +#define TIMPANI_CDC_GCTL1_POR 0x33 +#define TIMPANI_CDC_GCTL1_S 0 +#define TIMPANI_CDC_GCTL1_M 0xFF + + +#define TIMPANI_CDC_GCTL1_TX1_PGA_UPDATE_R_S 7 +#define TIMPANI_CDC_GCTL1_TX1_PGA_UPDATE_R_M 0x80 + +#define TIMPANI_CDC_GCTL1_TX1_PGA_UPDATE_L_S 6 +#define TIMPANI_CDC_GCTL1_TX1_PGA_UPDATE_L_M 0x40 + +#define TIMPANI_CDC_GCTL1_TX1_PGA_MUTE_EN_R_S 5 +#define TIMPANI_CDC_GCTL1_TX1_PGA_MUTE_EN_R_M 0x20 + +#define TIMPANI_CDC_GCTL1_TX1_PGA_MUTE_EN_L_S 4 +#define TIMPANI_CDC_GCTL1_TX1_PGA_MUTE_EN_L_M 0x10 + +#define TIMPANI_CDC_GCTL1_RX1_PGA_UPDATE_R_S 3 +#define TIMPANI_CDC_GCTL1_RX1_PGA_UPDATE_R_M 0x8 + +#define TIMPANI_CDC_GCTL1_RX1_PGA_UPDATE_L_S 2 +#define TIMPANI_CDC_GCTL1_RX1_PGA_UPDATE_L_M 0x4 + +#define TIMPANI_CDC_GCTL1_RX1_PGA_MUTE_EN_R_S 1 +#define TIMPANI_CDC_GCTL1_RX1_PGA_MUTE_EN_R_M 0x2 + +#define TIMPANI_CDC_GCTL1_RX1_PGA_MUTE_EN_L_S 0 +#define TIMPANI_CDC_GCTL1_RX1_PGA_MUTE_EN_L_M 0x1 + + +/* -- For CDC_TX1L_STG */ +#define TIMPANI_A_CDC_TX1L_STG (0x8B) +#define TIMPANI_CDC_TX1L_STG_RWC "RW" +#define TIMPANI_CDC_TX1L_STG_POR 0xac +#define TIMPANI_CDC_TX1L_STG_S 0 +#define TIMPANI_CDC_TX1L_STG_M 0xFF + + +#define TIMPANI_CDC_TX1L_STG_GAIN_S 0 +#define TIMPANI_CDC_TX1L_STG_GAIN_M 0xFF + + +/* -- For CDC_ST_CTL */ +#define TIMPANI_A_CDC_ST_CTL (0x8C) +#define TIMPANI_CDC_ST_CTL_RWC "RW" +#define TIMPANI_CDC_ST_CTL_POR 0x55 +#define TIMPANI_CDC_ST_CTL_S 0 +#define TIMPANI_CDC_ST_CTL_M 0xFF + + +#define TIMPANI_CDC_ST_CTL_TX2_R_SIDETONE_UPDATE_S 7 +#define TIMPANI_CDC_ST_CTL_TX2_R_SIDETONE_UPDATE_M 0x80 + +#define TIMPANI_CDC_ST_CTL_TX2_R_SIDETONE_MUTE_EN_S 6 +#define TIMPANI_CDC_ST_CTL_TX2_R_SIDETONE_MUTE_EN_M 0x40 + +#define TIMPANI_CDC_ST_CTL_TX2_L_SIDETONE_UPDATE_S 5 +#define TIMPANI_CDC_ST_CTL_TX2_L_SIDETONE_UPDATE_M 0x20 + +#define TIMPANI_CDC_ST_CTL_TX2_L_SIDETONE_MUTE_EN_S 4 +#define TIMPANI_CDC_ST_CTL_TX2_L_SIDETONE_MUTE_EN_M 0x10 + +#define TIMPANI_CDC_ST_CTL_TX1_R_SIDETONE_UPDATE_S 3 +#define TIMPANI_CDC_ST_CTL_TX1_R_SIDETONE_UPDATE_M 0x8 + +#define TIMPANI_CDC_ST_CTL_TX1_R_SIDETONE_MUTE_EN_S 2 +#define TIMPANI_CDC_ST_CTL_TX1_R_SIDETONE_MUTE_EN_M 0x4 + +#define TIMPANI_CDC_ST_CTL_TX1_L_SIDETONE_UPDATE_S 1 +#define TIMPANI_CDC_ST_CTL_TX1_L_SIDETONE_UPDATE_M 0x2 + +#define TIMPANI_CDC_ST_CTL_TX1_L_SIDETONE_MUTE_EN_S 0 +#define TIMPANI_CDC_ST_CTL_TX1_L_SIDETONE_MUTE_EN_M 0x1 + + +/* -- For CDC_RX1L_DCOFFSET */ +#define TIMPANI_A_CDC_RX1L_DCOFFSET (0x8D) +#define TIMPANI_CDC_RX1L_DCOFFSET_RWC "RW" +#define TIMPANI_CDC_RX1L_DCOFFSET_POR 0 +#define TIMPANI_CDC_RX1L_DCOFFSET_S 0 +#define TIMPANI_CDC_RX1L_DCOFFSET_M 0xFF + + +#define TIMPANI_CDC_RX1L_DCOFFSET_OFFSET_S 0 +#define TIMPANI_CDC_RX1L_DCOFFSET_OFFSET_M 0xFF + + +/* -- For CDC_RX1R_DCOFFSET */ +#define TIMPANI_A_CDC_RX1R_DCOFFSET (0x8E) +#define TIMPANI_CDC_RX1R_DCOFFSET_RWC "RW" +#define TIMPANI_CDC_RX1R_DCOFFSET_POR 0 +#define TIMPANI_CDC_RX1R_DCOFFSET_S 0 +#define TIMPANI_CDC_RX1R_DCOFFSET_M 0xFF + + +#define TIMPANI_CDC_RX1R_DCOFFSET_OFFSET_S 0 +#define TIMPANI_CDC_RX1R_DCOFFSET_OFFSET_M 0xFF + + +/* -- For CDC_BYPASS_CTL1 */ +#define TIMPANI_A_CDC_BYPASS_CTL1 (0x8F) +#define TIMPANI_CDC_BYPASS_CTL1_RWC "RW" +#define TIMPANI_CDC_BYPASS_CTL1_POR 0x2 +#define TIMPANI_CDC_BYPASS_CTL1_S 0 +#define TIMPANI_CDC_BYPASS_CTL1_M 0xF + + +#define TIMPANI_CDC_BYPASS_CTL1_DITHER_BP_S 3 +#define TIMPANI_CDC_BYPASS_CTL1_DITHER_BP_M 0x8 + +#define TIMPANI_CDC_BYPASS_CTL1_DITHER_SHAPE_SEL_S 2 +#define TIMPANI_CDC_BYPASS_CTL1_DITHER_SHAPE_SEL_M 0x4 + +#define TIMPANI_CDC_BYPASS_CTL1_DITHER_DLY_SEL_S 1 +#define TIMPANI_CDC_BYPASS_CTL1_DITHER_DLY_SEL_M 0x2 + +#define TIMPANI_CDC_BYPASS_CTL1_RX1_HPF_BP_S 0 +#define TIMPANI_CDC_BYPASS_CTL1_RX1_HPF_BP_M 0x1 + + +/* -- For CDC_PDM_CONFIG */ +#define TIMPANI_A_CDC_PDM_CONFIG (0x90) +#define TIMPANI_CDC_PDM_CONFIG_RWC "RW" +#define TIMPANI_CDC_PDM_CONFIG_POR 0 +#define TIMPANI_CDC_PDM_CONFIG_S 0 +#define TIMPANI_CDC_PDM_CONFIG_M 0xF + + +#define TIMPANI_CDC_PDM_CONFIG_PDM_SEL_S 0 +#define TIMPANI_CDC_PDM_CONFIG_PDM_SEL_M 0xF + + +/* -- For CDC_TESTMODE1 */ +#define TIMPANI_A_CDC_TESTMODE1 (0x91) +#define TIMPANI_CDC_TESTMODE1_RWC "RW" +#define TIMPANI_CDC_TESTMODE1_POR 0 +#define TIMPANI_CDC_TESTMODE1_S 0 +#define TIMPANI_CDC_TESTMODE1_M 0x3F + + +#define TIMPANI_CDC_TESTMODE1_COMP_I2C_TEST_EN_S 5 +#define TIMPANI_CDC_TESTMODE1_COMP_I2C_TEST_EN_M 0x20 + +#define TIMPANI_CDC_TESTMODE1_RX1_TEST_EN_R_S 4 +#define TIMPANI_CDC_TESTMODE1_RX1_TEST_EN_R_M 0x10 + +#define TIMPANI_CDC_TESTMODE1_RX1_TEST_EN_L_S 3 +#define TIMPANI_CDC_TESTMODE1_RX1_TEST_EN_L_M 0x8 + +#define TIMPANI_CDC_TESTMODE1_TX1_TEST_EN_R_S 2 +#define TIMPANI_CDC_TESTMODE1_TX1_TEST_EN_R_M 0x4 + +#define TIMPANI_CDC_TESTMODE1_TX1_TEST_EN_L_S 1 +#define TIMPANI_CDC_TESTMODE1_TX1_TEST_EN_L_M 0x2 + +#define TIMPANI_CDC_TESTMODE1_A_LOOPBACK_EN1_S 0 +#define TIMPANI_CDC_TESTMODE1_A_LOOPBACK_EN1_M 0x1 + + +/* -- For CDC_DMIC_CLK_CTL */ +#define TIMPANI_A_CDC_DMIC_CLK_CTL (0x92) +#define TIMPANI_CDC_DMIC_CLK_CTL_RWC "RW" +#define TIMPANI_CDC_DMIC_CLK_CTL_POR 0 +#define TIMPANI_CDC_DMIC_CLK_CTL_S 0 +#define TIMPANI_CDC_DMIC_CLK_CTL_M 0x3F + + +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_DIV_SEL_S 3 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_DIV_SEL_M 0x38 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_DIV_SEL_DIV_6 0x4 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_DIV_SEL_DIV_4 0x3 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_DIV_SEL_DIV_3 0x2 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_DIV_SEL_DIV_2 0x1 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_DIV_SEL_DIV_1 0x0 + +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_SEL_S 1 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_SEL_M 0x6 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_SEL_RX_MCLK2 0x2 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_SEL_RX_MCLK1 0x1 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_MCLK_SEL_TX_MCLK 0x0 + +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_CLK_EN_S 0 +#define TIMPANI_CDC_DMIC_CLK_CTL_DMIC_CLK_EN_M 0x1 + + +/* -- For CDC_ADC12_CLK_CTL */ +#define TIMPANI_A_CDC_ADC12_CLK_CTL (0x93) +#define TIMPANI_CDC_ADC12_CLK_CTL_RWC "RW" +#define TIMPANI_CDC_ADC12_CLK_CTL_POR 0 +#define TIMPANI_CDC_ADC12_CLK_CTL_S 0 +#define TIMPANI_CDC_ADC12_CLK_CTL_M 0xFF + + +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_SEL_S 6 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_SEL_M 0xC0 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_SEL_RX_MCLK2 0x2 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_SEL_RX_MCLK1 0x1 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_SEL_TX_MCLK 0x0 + +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_R_S 3 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_R_M 0x38 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_R_DIV_6 0x4 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_R_DIV_4 0x3 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_R_DIV_3 0x2 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_R_DIV_2 0x1 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_R_DIV_1 0x0 + +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_L_S 0 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_L_M 0x7 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_L_DIV_6 0x4 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_L_DIV_4 0x3 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_L_DIV_3 0x2 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_L_DIV_2 0x1 +#define TIMPANI_CDC_ADC12_CLK_CTL_TX1_MCLK_DIV_SEL_L_DIV_1 0x0 + + +/* -- For CDC_TX1_CTL */ +#define TIMPANI_A_CDC_TX1_CTL (0x94) +#define TIMPANI_CDC_TX1_CTL_RWC "RW" +#define TIMPANI_CDC_TX1_CTL_POR 0x1b +#define TIMPANI_CDC_TX1_CTL_S 0 +#define TIMPANI_CDC_TX1_CTL_M 0x3F + + +#define TIMPANI_CDC_TX1_CTL_TX1_DMIC_SEL_R_S 5 +#define TIMPANI_CDC_TX1_CTL_TX1_DMIC_SEL_R_M 0x20 + +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_R_S 3 +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_R_M 0x18 +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_R_OSR_256 0x3 +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_R_OSR_128 0x1 +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_R_OSR_64 0x0 + +#define TIMPANI_CDC_TX1_CTL_TX1_DMIC_SEL_L_S 2 +#define TIMPANI_CDC_TX1_CTL_TX1_DMIC_SEL_L_M 0x4 + +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_L_S 0 +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_L_M 0x3 +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_L_OSR_256 0x3 +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_L_OSR_128 0x1 +#define TIMPANI_CDC_TX1_CTL_TX1_RATE_L_OSR_64 0x0 + + +/* -- For CDC_ADC34_CLK_CTL */ +#define TIMPANI_A_CDC_ADC34_CLK_CTL (0x95) +#define TIMPANI_CDC_ADC34_CLK_CTL_RWC "RW" +#define TIMPANI_CDC_ADC34_CLK_CTL_POR 0 +#define TIMPANI_CDC_ADC34_CLK_CTL_S 0 +#define TIMPANI_CDC_ADC34_CLK_CTL_M 0xFF + + +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_SEL_S 6 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_SEL_M 0xC0 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_SEL_RX_MCLK2 0x2 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_SEL_RX_MCLK1 0x1 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_SEL_TX_MCLK 0x0 + +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_R_S 3 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_R_M 0x38 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_R_DIV_6 0x4 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_R_DIV_4 0x3 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_R_DIV_3 0x2 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_R_DIV_2 0x1 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_R_DIV_1 0x0 + +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_L_S 0 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_L_M 0x7 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_L_DIV_6 0x4 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_L_DIV_4 0x3 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_L_DIV_3 0x2 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_L_DIV_2 0x1 +#define TIMPANI_CDC_ADC34_CLK_CTL_TX2_MCLK_DIV_SEL_L_DIV_1 0x0 + + +/* -- For CDC_TX2_CTL */ +#define TIMPANI_A_CDC_TX2_CTL (0x96) +#define TIMPANI_CDC_TX2_CTL_RWC "RW" +#define TIMPANI_CDC_TX2_CTL_POR 0x1b +#define TIMPANI_CDC_TX2_CTL_S 0 +#define TIMPANI_CDC_TX2_CTL_M 0x3F + + +#define TIMPANI_CDC_TX2_CTL_TX2_DMIC_SEL_R_S 5 +#define TIMPANI_CDC_TX2_CTL_TX2_DMIC_SEL_R_M 0x20 + +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_R_S 3 +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_R_M 0x18 +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_R_OSR_256 0x3 +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_R_OSR_128 0x1 +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_R_OSR_64 0x0 + +#define TIMPANI_CDC_TX2_CTL_TX2_DMIC_SEL_L_S 2 +#define TIMPANI_CDC_TX2_CTL_TX2_DMIC_SEL_L_M 0x4 + +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_L_S 0 +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_L_M 0x3 +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_L_OSR_256 0x3 +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_L_OSR_128 0x1 +#define TIMPANI_CDC_TX2_CTL_TX2_RATE_L_OSR_64 0x0 + + +/* -- For CDC_RX1_CLK_CTL */ +#define TIMPANI_A_CDC_RX1_CLK_CTL (0x97) +#define TIMPANI_CDC_RX1_CLK_CTL_RWC "RW" +#define TIMPANI_CDC_RX1_CLK_CTL_POR 0x1 +#define TIMPANI_CDC_RX1_CLK_CTL_S 0 +#define TIMPANI_CDC_RX1_CLK_CTL_M 0x1F + + +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_DIV_SEL_S 2 +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_DIV_SEL_M 0x1C +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_DIV_SEL_DIV_6 0x4 +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_DIV_SEL_DIV_4 0x3 +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_DIV_SEL_DIV_3 0x2 +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_DIV_SEL_DIV_2 0x1 +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_DIV_SEL_DIV_1 0x0 + +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_SEL_S 0 +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_SEL_M 0x3 +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_SEL_RX_MCLK2 0x2 +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_SEL_RX_MCLK1 0x1 +#define TIMPANI_CDC_RX1_CLK_CTL_RX1_MCLK_SEL_TX_MCLK 0x0 + + +/* -- For CDC_RX2_CLK_CTL */ +#define TIMPANI_A_CDC_RX2_CLK_CTL (0x98) +#define TIMPANI_CDC_RX2_CLK_CTL_RWC "RW" +#define TIMPANI_CDC_RX2_CLK_CTL_POR 0x2 +#define TIMPANI_CDC_RX2_CLK_CTL_S 0 +#define TIMPANI_CDC_RX2_CLK_CTL_M 0x1F + + +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_DIV_SEL_S 2 +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_DIV_SEL_M 0x1C +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_DIV_SEL_DIV_6 0x4 +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_DIV_SEL_DIV_4 0x3 +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_DIV_SEL_DIV_3 0x2 +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_DIV_SEL_DIV_2 0x1 +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_DIV_SEL_DIV_1 0x0 + +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_SEL_S 0 +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_SEL_M 0x3 +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_SEL_RX_MCLK2 0x2 +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_SEL_RX_MCLK1 0x1 +#define TIMPANI_CDC_RX2_CLK_CTL_RX2_MCLK_SEL_TX_MCLK 0x0 + + +/* -- For CDC_DEC_ADC_SEL */ +#define TIMPANI_A_CDC_DEC_ADC_SEL (0x99) +#define TIMPANI_CDC_DEC_ADC_SEL_RWC "RW" +#define TIMPANI_CDC_DEC_ADC_SEL_POR 0 +#define TIMPANI_CDC_DEC_ADC_SEL_S 0 +#define TIMPANI_CDC_DEC_ADC_SEL_M 0xFF + + +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_R_S 6 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_R_M 0xC0 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_R_ADC4 0x3 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_R_ADC3 0x2 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_R_ADC2 0x1 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_R_ADC1 0x0 + +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_L_S 4 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_L_M 0x30 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_L_ADC4 0x3 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_L_ADC3 0x2 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_L_ADC2 0x1 +#define TIMPANI_CDC_DEC_ADC_SEL_TX2_ADC_SEL_L_ADC1 0x0 + +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_R_S 2 +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_R_M 0xC +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_R_ADC4 0x3 +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_R_ADC3 0x2 +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_R_ADC2 0x1 +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_R_ADC1 0x0 + +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_L_S 0 +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_L_M 0x3 +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_L_ADC4 0x3 +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_L_ADC3 0x2 +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_L_ADC2 0x1 +#define TIMPANI_CDC_DEC_ADC_SEL_TX1_ADC_SEL_L_ADC1 0x0 + + +/* -- For CDC_ANC_INPUT_MUX */ +#define TIMPANI_A_CDC_ANC_INPUT_MUX (0x9A) +#define TIMPANI_CDC_ANC_INPUT_MUX_RWC "RW" +#define TIMPANI_CDC_ANC_INPUT_MUX_POR 0 +#define TIMPANI_CDC_ANC_INPUT_MUX_S 0 +#define TIMPANI_CDC_ANC_INPUT_MUX_M 0xFF + + +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_ADC_SEL_S 6 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_ADC_SEL_M 0xC0 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_ADC_SEL_A_CDC_TX2DOR 0x3 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_ADC_SEL_A_CDC_TX2DOL 0x2 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_ADC_SEL_A_CDC_TX1DOR 0x1 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_ADC_SEL_A_CDC_TX1DOL 0x0 + +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_DMIC_SEL_S 4 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_DMIC_SEL_M 0x30 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_DMIC_SEL_MIC2_DIN_R 0x3 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_DMIC_SEL_MIC2_DIN_L 0x2 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_DMIC_SEL_MIC1_DIN_R 0x1 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC2_DMIC_SEL_MIC1_DIN_L 0x0 + +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_ADC_SEL_S 2 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_ADC_SEL_M 0xC +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_ADC_SEL_A_CDC_TX2DOR 0x3 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_ADC_SEL_A_CDC_TX2DOL 0x2 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_ADC_SEL_A_CDC_TX1DOR 0x1 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_ADC_SEL_A_CDC_TX1DOL 0x0 + +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_DMIC_SEL_S 0 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_DMIC_SEL_M 0x3 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_DMIC_SEL_MIC2_DIN_R 0x3 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_DMIC_SEL_MIC2_DIN_L 0x2 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_DMIC_SEL_MIC1_DIN_R 0x1 +#define TIMPANI_CDC_ANC_INPUT_MUX_ANC1_DMIC_SEL_MIC1_DIN_L 0x0 + + +/* -- For CDC_ANC_RX_CLK_NS_SEL */ +#define TIMPANI_A_CDC_ANC_RX_CLK_NS_SEL (0x9B) +#define TIMPANI_CDC_ANC_RX_CLK_NS_SEL_RWC "RW" +#define TIMPANI_CDC_ANC_RX_CLK_NS_SEL_POR 0 +#define TIMPANI_CDC_ANC_RX_CLK_NS_SEL_S 0 +#define TIMPANI_CDC_ANC_RX_CLK_NS_SEL_M 0x1 + + +#define TIMPANI_CDC_ANC_RX_CLK_NS_SEL_ANC_RX_CLK_NS_SEL_S 0 +#define TIMPANI_CDC_ANC_RX_CLK_NS_SEL_ANC_RX_CLK_NS_SEL_M 0x1 + + +/* -- For CDC_ANC_FB_TUNE_SEL */ +#define TIMPANI_A_CDC_ANC_FB_TUNE_SEL (0x9C) +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_RWC "RW" +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_POR 0 +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_S 0 +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_M 0x3 + + +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_ANC2_FB_ADC_SEL_S 1 +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_ANC2_FB_ADC_SEL_M 0x2 +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_ANC2_FB_ADC_SEL_FB_TUNE_EN 0x1 +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_ANC2_FB_ADC_SEL_FB_TUNE_DIS 0x0 + +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_ANC1_FB_ADC_SEL_S 0 +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_ANC1_FB_ADC_SEL_M 0x1 +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_ANC1_FB_ADC_SEL_FB_TUNE_EN 0x1 +#define TIMPANI_CDC_ANC_FB_TUNE_SEL_ANC1_FB_ADC_SEL_FB_TUNE_DIS 0x0 + + +/* -- For CLK_DIV_SYNC_CTL */ +#define TIMPANI_A_CLK_DIV_SYNC_CTL (0x9E) +#define TIMPANI_CLK_DIV_SYNC_CTL_RWC "RW" +#define TIMPANI_CLK_DIV_SYNC_CTL_POR 0 +#define TIMPANI_CLK_DIV_SYNC_CTL_S 0 +#define TIMPANI_CLK_DIV_SYNC_CTL_M 0x3 + + +#define TIMPANI_CLK_DIV_SYNC_CTL_GLBL_DIV_SYNC_S 1 +#define TIMPANI_CLK_DIV_SYNC_CTL_GLBL_DIV_SYNC_M 0x2 + +#define TIMPANI_CLK_DIV_SYNC_CTL_TX_DIV_SYNC_S 0 +#define TIMPANI_CLK_DIV_SYNC_CTL_TX_DIV_SYNC_M 0x1 + + +/* -- For CDC_ADC_CLK_EN */ +#define TIMPANI_A_CDC_ADC_CLK_EN (0x9F) +#define TIMPANI_CDC_ADC_CLK_EN_RWC "RW" +#define TIMPANI_CDC_ADC_CLK_EN_POR 0 +#define TIMPANI_CDC_ADC_CLK_EN_S 0 +#define TIMPANI_CDC_ADC_CLK_EN_M 0xF + + +#define TIMPANI_CDC_ADC_CLK_EN_A_TX2_R_EN_S 3 +#define TIMPANI_CDC_ADC_CLK_EN_A_TX2_R_EN_M 0x8 + +#define TIMPANI_CDC_ADC_CLK_EN_A_TX2_L_EN_S 2 +#define TIMPANI_CDC_ADC_CLK_EN_A_TX2_L_EN_M 0x4 + +#define TIMPANI_CDC_ADC_CLK_EN_A_TX1_R_EN_S 1 +#define TIMPANI_CDC_ADC_CLK_EN_A_TX1_R_EN_M 0x2 + +#define TIMPANI_CDC_ADC_CLK_EN_A_TX1_L_EN_S 0 +#define TIMPANI_CDC_ADC_CLK_EN_A_TX1_L_EN_M 0x1 + + +/* -- For CDC_ST_MIXING */ +#define TIMPANI_A_CDC_ST_MIXING (0xA0) +#define TIMPANI_CDC_ST_MIXING_RWC "RW" +#define TIMPANI_CDC_ST_MIXING_POR 0 +#define TIMPANI_CDC_ST_MIXING_S 0 +#define TIMPANI_CDC_ST_MIXING_M 0xF + + +#define TIMPANI_CDC_ST_MIXING_TX2_R_S 3 +#define TIMPANI_CDC_ST_MIXING_TX2_R_M 0x8 + +#define TIMPANI_CDC_ST_MIXING_TX2_L_S 2 +#define TIMPANI_CDC_ST_MIXING_TX2_L_M 0x4 + +#define TIMPANI_CDC_ST_MIXING_TX1_R_S 1 +#define TIMPANI_CDC_ST_MIXING_TX1_R_M 0x2 + +#define TIMPANI_CDC_ST_MIXING_TX1_L_S 0 +#define TIMPANI_CDC_ST_MIXING_TX1_L_M 0x1 + + +/* -- For CDC_RX2_CTL */ +#define TIMPANI_A_CDC_RX2_CTL (0xA1) +#define TIMPANI_CDC_RX2_CTL_RWC "RW" +#define TIMPANI_CDC_RX2_CTL_POR 0xc +#define TIMPANI_CDC_RX2_CTL_S 0 +#define TIMPANI_CDC_RX2_CTL_M 0x3F + + +#define TIMPANI_CDC_RX2_CTL_SIDETONE_EN2_R_S 5 +#define TIMPANI_CDC_RX2_CTL_SIDETONE_EN2_R_M 0x20 + +#define TIMPANI_CDC_RX2_CTL_SIDETONE_EN2_L_S 4 +#define TIMPANI_CDC_RX2_CTL_SIDETONE_EN2_L_M 0x10 + +#define TIMPANI_CDC_RX2_CTL_RX2_RATE_S 2 +#define TIMPANI_CDC_RX2_CTL_RX2_RATE_M 0xC +#define TIMPANI_CDC_RX2_CTL_RX2_RATE_OSR_256 0x3 +#define TIMPANI_CDC_RX2_CTL_RX2_RATE_OSR_128 0x1 +#define TIMPANI_CDC_RX2_CTL_RX2_RATE_OSR_64 0x0 + +#define TIMPANI_CDC_RX2_CTL_RX2_I2S_RATE_S 1 +#define TIMPANI_CDC_RX2_CTL_RX2_I2S_RATE_M 0x2 +#define TIMPANI_CDC_RX2_CTL_RX2_I2S_RATE_BR_32 0x1 +#define TIMPANI_CDC_RX2_CTL_RX2_I2S_RATE_BR_64 0x0 + +#define TIMPANI_CDC_RX2_CTL_RX2_I2S_MODE_S 0 +#define TIMPANI_CDC_RX2_CTL_RX2_I2S_MODE_M 0x1 +#define TIMPANI_CDC_RX2_CTL_RX2_I2S_MODE_MASTER 0x1 +#define TIMPANI_CDC_RX2_CTL_RX2_I2S_MODE_SLAVE 0x0 + + +/* -- For CDC_ARB_CLK_EN */ +#define TIMPANI_A_CDC_ARB_CLK_EN (0xA2) +#define TIMPANI_CDC_ARB_CLK_EN_RWC "RW" +#define TIMPANI_CDC_ARB_CLK_EN_POR 0 +#define TIMPANI_CDC_ARB_CLK_EN_S 0 +#define TIMPANI_CDC_ARB_CLK_EN_M 0x1 + + +#define TIMPANI_CDC_ARB_CLK_EN_ARB_CLK_EN_S 0 +#define TIMPANI_CDC_ARB_CLK_EN_ARB_CLK_EN_M 0x1 + + +/* -- For CDC_I2S_CTL2 */ +#define TIMPANI_A_CDC_I2S_CTL2 (0xA3) +#define TIMPANI_CDC_I2S_CTL2_RWC "RW" +#define TIMPANI_CDC_I2S_CTL2_POR 0 +#define TIMPANI_CDC_I2S_CTL2_S 0 +#define TIMPANI_CDC_I2S_CTL2_M 0x3F + + +#define TIMPANI_CDC_I2S_CTL2_TX_I2S_CLK_SEL_S 3 +#define TIMPANI_CDC_I2S_CTL2_TX_I2S_CLK_SEL_M 0x38 +#define TIMPANI_CDC_I2S_CTL2_TX_I2S_CLK_SEL_CLK_DMIC 0x4 +#define TIMPANI_CDC_I2S_CTL2_TX_I2S_CLK_SEL_CLK_TX2_R 0x3 +#define TIMPANI_CDC_I2S_CTL2_TX_I2S_CLK_SEL_CLK_TX2_L 0x2 +#define TIMPANI_CDC_I2S_CTL2_TX_I2S_CLK_SEL_CLK_TX1_R 0x1 +#define TIMPANI_CDC_I2S_CTL2_TX_I2S_CLK_SEL_CLK_TX1_L 0x0 + +#define TIMPANI_CDC_I2S_CTL2_RX2_I2SCLK_EN_S 2 +#define TIMPANI_CDC_I2S_CTL2_RX2_I2SCLK_EN_M 0x4 + +#define TIMPANI_CDC_I2S_CTL2_RX1_I2SCLK_EN_S 1 +#define TIMPANI_CDC_I2S_CTL2_RX1_I2SCLK_EN_M 0x2 + +#define TIMPANI_CDC_I2S_CTL2_TX_I2SCLK_EN_S 0 +#define TIMPANI_CDC_I2S_CTL2_TX_I2SCLK_EN_M 0x1 + + +/* -- For CDC_RX2LG */ +#define TIMPANI_A_CDC_RX2LG (0xA4) +#define TIMPANI_CDC_RX2LG_RWC "RW" +#define TIMPANI_CDC_RX2LG_POR 0xac +#define TIMPANI_CDC_RX2LG_S 0 +#define TIMPANI_CDC_RX2LG_M 0xFF + + +#define TIMPANI_CDC_RX2LG_GAIN_S 0 +#define TIMPANI_CDC_RX2LG_GAIN_M 0xFF + + +/* -- For CDC_RX2RG */ +#define TIMPANI_A_CDC_RX2RG (0xA5) +#define TIMPANI_CDC_RX2RG_RWC "RW" +#define TIMPANI_CDC_RX2RG_POR 0xac +#define TIMPANI_CDC_RX2RG_S 0 +#define TIMPANI_CDC_RX2RG_M 0xFF + + +#define TIMPANI_CDC_RX2RG_GAIN_S 0 +#define TIMPANI_CDC_RX2RG_GAIN_M 0xFF + + +/* -- For CDC_TX2LG */ +#define TIMPANI_A_CDC_TX2LG (0xA6) +#define TIMPANI_CDC_TX2LG_RWC "RW" +#define TIMPANI_CDC_TX2LG_POR 0xac +#define TIMPANI_CDC_TX2LG_S 0 +#define TIMPANI_CDC_TX2LG_M 0xFF + + +#define TIMPANI_CDC_TX2LG_GAIN_S 0 +#define TIMPANI_CDC_TX2LG_GAIN_M 0xFF + + +/* -- For CDC_TX2RG */ +#define TIMPANI_A_CDC_TX2RG (0xA7) +#define TIMPANI_CDC_TX2RG_RWC "RW" +#define TIMPANI_CDC_TX2RG_POR 0xac +#define TIMPANI_CDC_TX2RG_S 0 +#define TIMPANI_CDC_TX2RG_M 0xFF + + +#define TIMPANI_CDC_TX2RG_GAIN_S 0 +#define TIMPANI_CDC_TX2RG_GAIN_M 0xFF + + +/* -- For CDC_DMIC_MUX */ +#define TIMPANI_A_CDC_DMIC_MUX (0xA8) +#define TIMPANI_CDC_DMIC_MUX_RWC "RW" +#define TIMPANI_CDC_DMIC_MUX_POR 0 +#define TIMPANI_CDC_DMIC_MUX_S 0 +#define TIMPANI_CDC_DMIC_MUX_M 0xFF + + +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_R_S 6 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_R_M 0xC0 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_R_MIC2_DIN_R 0x3 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_R_MIC2_DIN_L 0x2 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_R_MIC1_DIN_R 0x1 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_R_MIC1_DIN_L 0x0 + +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_L_S 4 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_L_M 0x30 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_L_MIC2_DIN_R 0x3 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_L_MIC2_DIN_L 0x2 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_L_MIC1_DIN_R 0x1 +#define TIMPANI_CDC_DMIC_MUX_TX2_DMIC_MUX_SEL_L_MIC1_DIN_L 0x0 + +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_R_S 2 +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_R_M 0xC +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_R_MIC2_DIN_R 0x3 +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_R_MIC2_DIN_L 0x2 +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_R_MIC1_DIN_R 0x1 +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_R_MIC1_DIN_L 0x0 + +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_L_S 0 +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_L_M 0x3 +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_L_MIC2_DIN_R 0x3 +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_L_MIC2_DIN_L 0x2 +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_L_MIC1_DIN_R 0x1 +#define TIMPANI_CDC_DMIC_MUX_TX1_DMIC_MUX_SEL_L_MIC1_DIN_L 0x0 + + +/* -- For CDC_ARB_CLK_CTL */ +#define TIMPANI_A_CDC_ARB_CLK_CTL (0xA9) +#define TIMPANI_CDC_ARB_CLK_CTL_RWC "RW" +#define TIMPANI_CDC_ARB_CLK_CTL_POR 0 +#define TIMPANI_CDC_ARB_CLK_CTL_S 0 +#define TIMPANI_CDC_ARB_CLK_CTL_M 0x3 + + +#define TIMPANI_CDC_ARB_CLK_CTL_ARB_CLK_SEL_S 0 +#define TIMPANI_CDC_ARB_CLK_CTL_ARB_CLK_SEL_M 0x3 +#define TIMPANI_CDC_ARB_CLK_CTL_ARB_CLK_SEL_TX_MCLK 0x0 +#define TIMPANI_CDC_ARB_CLK_CTL_ARB_CLK_SEL_RX_MCLK1 0x1 +#define TIMPANI_CDC_ARB_CLK_CTL_ARB_CLK_SEL_RX_MCLK2 0x2 +#define TIMPANI_CDC_ARB_CLK_CTL_ARB_CLK_SEL_TCXO 0x3 + + +/* -- For CDC_GCTL2 */ +#define TIMPANI_A_CDC_GCTL2 (0xAA) +#define TIMPANI_CDC_GCTL2_RWC "RW" +#define TIMPANI_CDC_GCTL2_POR 0x33 +#define TIMPANI_CDC_GCTL2_S 0 +#define TIMPANI_CDC_GCTL2_M 0xFF + + +#define TIMPANI_CDC_GCTL2_TX2_PGA_UPDATE_R_S 7 +#define TIMPANI_CDC_GCTL2_TX2_PGA_UPDATE_R_M 0x80 + +#define TIMPANI_CDC_GCTL2_TX2_PGA_UPDATE_L_S 6 +#define TIMPANI_CDC_GCTL2_TX2_PGA_UPDATE_L_M 0x40 + +#define TIMPANI_CDC_GCTL2_TX2_PGA_MUTE_EN_R_S 5 +#define TIMPANI_CDC_GCTL2_TX2_PGA_MUTE_EN_R_M 0x20 + +#define TIMPANI_CDC_GCTL2_TX2_PGA_MUTE_EN_L_S 4 +#define TIMPANI_CDC_GCTL2_TX2_PGA_MUTE_EN_L_M 0x10 + +#define TIMPANI_CDC_GCTL2_RX2_PGA_UPDATE_R_S 3 +#define TIMPANI_CDC_GCTL2_RX2_PGA_UPDATE_R_M 0x8 + +#define TIMPANI_CDC_GCTL2_RX2_PGA_UPDATE_L_S 2 +#define TIMPANI_CDC_GCTL2_RX2_PGA_UPDATE_L_M 0x4 + +#define TIMPANI_CDC_GCTL2_RX2_PGA_MUTE_EN_R_S 1 +#define TIMPANI_CDC_GCTL2_RX2_PGA_MUTE_EN_R_M 0x2 + +#define TIMPANI_CDC_GCTL2_RX2_PGA_MUTE_EN_L_S 0 +#define TIMPANI_CDC_GCTL2_RX2_PGA_MUTE_EN_L_M 0x1 + + +/* -- For CDC_BYPASS_CTL2 */ +#define TIMPANI_A_CDC_BYPASS_CTL2 (0xAB) +#define TIMPANI_CDC_BYPASS_CTL2_RWC "RW" +#define TIMPANI_CDC_BYPASS_CTL2_POR 0x2D +#define TIMPANI_CDC_BYPASS_CTL2_S 0 +#define TIMPANI_CDC_BYPASS_CTL2_M 0x3F + + +#define TIMPANI_CDC_BYPASS_CTL2_TX1_DMIC_GAIN_BP_R_S 5 +#define TIMPANI_CDC_BYPASS_CTL2_TX1_DMIC_GAIN_BP_R_M 0x20 + +#define TIMPANI_CDC_BYPASS_CTL2_TX1_ADC_GAIN_BP_R_S 4 +#define TIMPANI_CDC_BYPASS_CTL2_TX1_ADC_GAIN_BP_R_M 0x10 + +#define TIMPANI_CDC_BYPASS_CTL2_TX1_HPF_BP_R_S 3 +#define TIMPANI_CDC_BYPASS_CTL2_TX1_HPF_BP_R_M 0x8 + +#define TIMPANI_CDC_BYPASS_CTL2_TX1_DMIC_GAIN_BP_L_S 2 +#define TIMPANI_CDC_BYPASS_CTL2_TX1_DMIC_GAIN_BP_L_M 0x4 + +#define TIMPANI_CDC_BYPASS_CTL2_TX1_ADC_GAIN_BP_L_S 1 +#define TIMPANI_CDC_BYPASS_CTL2_TX1_ADC_GAIN_BP_L_M 0x2 + +#define TIMPANI_CDC_BYPASS_CTL2_TX1_HPF_BP_L_S 0 +#define TIMPANI_CDC_BYPASS_CTL2_TX1_HPF_BP_L_M 0x1 + + +/* -- For CDC_BYPASS_CTL3 */ +#define TIMPANI_A_CDC_BYPASS_CTL3 (0xAC) +#define TIMPANI_CDC_BYPASS_CTL3_RWC "RW" +#define TIMPANI_CDC_BYPASS_CTL3_POR 0x2D +#define TIMPANI_CDC_BYPASS_CTL3_S 0 +#define TIMPANI_CDC_BYPASS_CTL3_M 0x3F + + +#define TIMPANI_CDC_BYPASS_CTL3_TX2_DMIC_GAIN_BP_R_S 5 +#define TIMPANI_CDC_BYPASS_CTL3_TX2_DMIC_GAIN_BP_R_M 0x20 + +#define TIMPANI_CDC_BYPASS_CTL3_TX2_ADC_GAIN_BP_R_S 4 +#define TIMPANI_CDC_BYPASS_CTL3_TX2_ADC_GAIN_BP_R_M 0x10 + +#define TIMPANI_CDC_BYPASS_CTL3_TX2_HPF_BP_R_S 3 +#define TIMPANI_CDC_BYPASS_CTL3_TX2_HPF_BP_R_M 0x8 + +#define TIMPANI_CDC_BYPASS_CTL3_TX2_DMIC_GAIN_BP_L_S 2 +#define TIMPANI_CDC_BYPASS_CTL3_TX2_DMIC_GAIN_BP_L_M 0x4 + +#define TIMPANI_CDC_BYPASS_CTL3_TX2_ADC_GAIN_BP_L_S 1 +#define TIMPANI_CDC_BYPASS_CTL3_TX2_ADC_GAIN_BP_L_M 0x2 + +#define TIMPANI_CDC_BYPASS_CTL3_TX2_HPF_BP_L_S 0 +#define TIMPANI_CDC_BYPASS_CTL3_TX2_HPF_BP_L_M 0x1 + + +/* -- For CDC_BYPASS_CTL4 */ +#define TIMPANI_A_CDC_BYPASS_CTL4 (0xAD) +#define TIMPANI_CDC_BYPASS_CTL4_RWC "RW" +#define TIMPANI_CDC_BYPASS_CTL4_POR 0x2 +#define TIMPANI_CDC_BYPASS_CTL4_S 0 +#define TIMPANI_CDC_BYPASS_CTL4_M 0xF + + +#define TIMPANI_CDC_BYPASS_CTL4_DITHER_BP_S 3 +#define TIMPANI_CDC_BYPASS_CTL4_DITHER_BP_M 0x8 + +#define TIMPANI_CDC_BYPASS_CTL4_DITHER_SHAPE_SEL_S 2 +#define TIMPANI_CDC_BYPASS_CTL4_DITHER_SHAPE_SEL_M 0x4 + +#define TIMPANI_CDC_BYPASS_CTL4_DITHER_DLY_SEL_S 1 +#define TIMPANI_CDC_BYPASS_CTL4_DITHER_DLY_SEL_M 0x2 + +#define TIMPANI_CDC_BYPASS_CTL4_RX2_HPF_BP_S 0 +#define TIMPANI_CDC_BYPASS_CTL4_RX2_HPF_BP_M 0x1 + + +/* -- For CDC_RX2L_DCOFFSET */ +#define TIMPANI_A_CDC_RX2L_DCOFFSET (0xAE) +#define TIMPANI_CDC_RX2L_DCOFFSET_RWC "RW" +#define TIMPANI_CDC_RX2L_DCOFFSET_POR 0 +#define TIMPANI_CDC_RX2L_DCOFFSET_S 0 +#define TIMPANI_CDC_RX2L_DCOFFSET_M 0xFF + + +#define TIMPANI_CDC_RX2L_DCOFFSET_OFFSET_S 0 +#define TIMPANI_CDC_RX2L_DCOFFSET_OFFSET_M 0xFF + + +/* -- For CDC_RX2R_DCOFFSET */ +#define TIMPANI_A_CDC_RX2R_DCOFFSET (0xAF) +#define TIMPANI_CDC_RX2R_DCOFFSET_RWC "RW" +#define TIMPANI_CDC_RX2R_DCOFFSET_POR 0 +#define TIMPANI_CDC_RX2R_DCOFFSET_S 0 +#define TIMPANI_CDC_RX2R_DCOFFSET_M 0xFF + + +#define TIMPANI_CDC_RX2R_DCOFFSET_OFFSET_S 0 +#define TIMPANI_CDC_RX2R_DCOFFSET_OFFSET_M 0xFF + + +/* -- For CDC_RX_MIX_CTL */ +#define TIMPANI_A_CDC_RX_MIX_CTL (0xB0) +#define TIMPANI_CDC_RX_MIX_CTL_RWC "RW" +#define TIMPANI_CDC_RX_MIX_CTL_POR 0 +#define TIMPANI_CDC_RX_MIX_CTL_S 0 +#define TIMPANI_CDC_RX_MIX_CTL_M 0x3 + + +#define TIMPANI_CDC_RX_MIX_CTL_RX2TO1_EN_S 1 +#define TIMPANI_CDC_RX_MIX_CTL_RX2TO1_EN_M 0x2 + +#define TIMPANI_CDC_RX_MIX_CTL_RX1TO2_EN_S 0 +#define TIMPANI_CDC_RX_MIX_CTL_RX1TO2_EN_M 0x1 + + +/* -- For CDC_SPARE_CTL */ +#define TIMPANI_A_CDC_SPARE_CTL (0xB1) +#define TIMPANI_CDC_SPARE_CTL_RWC "RW" +#define TIMPANI_CDC_SPARE_CTL_POR 0 +#define TIMPANI_CDC_SPARE_CTL_S 0 +#define TIMPANI_CDC_SPARE_CTL_M 0xFF + + +#define TIMPANI_CDC_SPARE_CTL_CDC_SPARE_S 0 +#define TIMPANI_CDC_SPARE_CTL_CDC_SPARE_M 0xFF + + +/* -- For CDC_TESTMODE2 */ +#define TIMPANI_A_CDC_TESTMODE2 (0xB2) +#define TIMPANI_CDC_TESTMODE2_RWC "RW" +#define TIMPANI_CDC_TESTMODE2_POR 0 +#define TIMPANI_CDC_TESTMODE2_S 0 +#define TIMPANI_CDC_TESTMODE2_M 0x1F + + +#define TIMPANI_CDC_TESTMODE2_RX2_TEST_EN_R_S 4 +#define TIMPANI_CDC_TESTMODE2_RX2_TEST_EN_R_M 0x10 + +#define TIMPANI_CDC_TESTMODE2_RX2_TEST_EN_L_S 3 +#define TIMPANI_CDC_TESTMODE2_RX2_TEST_EN_L_M 0x8 + +#define TIMPANI_CDC_TESTMODE2_TX2_TEST_EN_R_S 2 +#define TIMPANI_CDC_TESTMODE2_TX2_TEST_EN_R_M 0x4 + +#define TIMPANI_CDC_TESTMODE2_TX2_TEST_EN_L_S 1 +#define TIMPANI_CDC_TESTMODE2_TX2_TEST_EN_L_M 0x2 + +#define TIMPANI_CDC_TESTMODE2_A_LOOPBACK_EN2_S 0 +#define TIMPANI_CDC_TESTMODE2_A_LOOPBACK_EN2_M 0x1 + + +/* -- For CDC_PDM_OE */ +#define TIMPANI_A_CDC_PDM_OE (0xB3) +#define TIMPANI_CDC_PDM_OE_RWC "RW" +#define TIMPANI_CDC_PDM_OE_POR 0 +#define TIMPANI_CDC_PDM_OE_S 0 +#define TIMPANI_CDC_PDM_OE_M 0x3F + + +#define TIMPANI_CDC_PDM_OE_PDM_23_20_OE_S 5 +#define TIMPANI_CDC_PDM_OE_PDM_23_20_OE_M 0x20 + +#define TIMPANI_CDC_PDM_OE_PDM_19_16_OE_S 4 +#define TIMPANI_CDC_PDM_OE_PDM_19_16_OE_M 0x10 + +#define TIMPANI_CDC_PDM_OE_PDM_15_12_OE_S 3 +#define TIMPANI_CDC_PDM_OE_PDM_15_12_OE_M 0x8 + +#define TIMPANI_CDC_PDM_OE_PDM_11_8_OE_S 2 +#define TIMPANI_CDC_PDM_OE_PDM_11_8_OE_M 0x4 + +#define TIMPANI_CDC_PDM_OE_PDM_7_4_OE_S 1 +#define TIMPANI_CDC_PDM_OE_PDM_7_4_OE_M 0x2 + +#define TIMPANI_CDC_PDM_OE_PDM_3_0_OE_S 0 +#define TIMPANI_CDC_PDM_OE_PDM_3_0_OE_M 0x1 + + +/* -- For CDC_TX1R_STG */ +#define TIMPANI_A_CDC_TX1R_STG (0xB4) +#define TIMPANI_CDC_TX1R_STG_RWC "RW" +#define TIMPANI_CDC_TX1R_STG_POR 0xac +#define TIMPANI_CDC_TX1R_STG_S 0 +#define TIMPANI_CDC_TX1R_STG_M 0xFF + + +#define TIMPANI_CDC_TX1R_STG_GAIN_S 0 +#define TIMPANI_CDC_TX1R_STG_GAIN_M 0xFF + + +/* -- For CDC_TX2L_STG */ +#define TIMPANI_A_CDC_TX2L_STG (0xB5) +#define TIMPANI_CDC_TX2L_STG_RWC "RW" +#define TIMPANI_CDC_TX2L_STG_POR 0xac +#define TIMPANI_CDC_TX2L_STG_S 0 +#define TIMPANI_CDC_TX2L_STG_M 0xFF + + +#define TIMPANI_CDC_TX2L_STG_GAIN_S 0 +#define TIMPANI_CDC_TX2L_STG_GAIN_M 0xFF + + +/* -- For CDC_TX2R_STG */ +#define TIMPANI_A_CDC_TX2R_STG (0xB6) +#define TIMPANI_CDC_TX2R_STG_RWC "RW" +#define TIMPANI_CDC_TX2R_STG_POR 0xac +#define TIMPANI_CDC_TX2R_STG_S 0 +#define TIMPANI_CDC_TX2R_STG_M 0xFF + + +#define TIMPANI_CDC_TX2R_STG_GAIN_S 0 +#define TIMPANI_CDC_TX2R_STG_GAIN_M 0xFF + + +/* -- For CDC_ARB_BYPASS_CTL */ +#define TIMPANI_A_CDC_ARB_BYPASS_CTL (0xB7) +#define TIMPANI_CDC_ARB_BYPASS_CTL_RWC "RW" +#define TIMPANI_CDC_ARB_BYPASS_CTL_POR 0 +#define TIMPANI_CDC_ARB_BYPASS_CTL_S 0 +#define TIMPANI_CDC_ARB_BYPASS_CTL_M 0x1 + + +#define TIMPANI_CDC_ARB_BYPASS_CTL_ARB_BYPASS_EN_S 0 +#define TIMPANI_CDC_ARB_BYPASS_CTL_ARB_BYPASS_EN_M 0x1 +#define TIMPANI_CDC_ARB_BYPASS_CTL_ARB_BYPASS_EN_BYPASS 0x1 +#define TIMPANI_CDC_ARB_BYPASS_CTL_ARB_BYPASS_EN_NO_BYPASS 0x0 + + +/* -- For CDC_ANC1_CTL1 */ +#define TIMPANI_A_CDC_ANC1_CTL1 (0xC0) +#define TIMPANI_CDC_ANC1_CTL1_RWC "RW" +#define TIMPANI_CDC_ANC1_CTL1_POR 0 +#define TIMPANI_CDC_ANC1_CTL1_S 0 +#define TIMPANI_CDC_ANC1_CTL1_M 0x3F + + +#define TIMPANI_CDC_ANC1_CTL1_ANC1_FF_OUT_DIS_S 5 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_FF_OUT_DIS_M 0x20 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_FF_OUT_DIS_FF_OUT_DIS 0x1 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_FF_OUT_DIS_FF_OUT_EN 0x0 + +#define TIMPANI_CDC_ANC1_CTL1_ANC1_ADC_DMIC_SEL_S 4 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_ADC_DMIC_SEL_M 0x10 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_ADC_DMIC_SEL_DMIC 0x1 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_ADC_DMIC_SEL_ADC 0x0 + +#define TIMPANI_CDC_ANC1_CTL1_ANC1_LR_EN_S 3 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_LR_EN_M 0x8 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_LR_EN_LR_MIX_EN 0x1 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_LR_EN_LR_MIX_DIS 0x0 + +#define TIMPANI_CDC_ANC1_CTL1_ANC1_FB_EN_S 2 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_FB_EN_M 0x4 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_FB_EN_FB_MIX_EN 0x1 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_FB_EN_FB_MIX_DIS 0x0 + +#define TIMPANI_CDC_ANC1_CTL1_ANC1_EN_S 1 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_EN_M 0x2 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_EN_ANC_EN 0x1 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_EN_ANC_DIS 0x0 + +#define TIMPANI_CDC_ANC1_CTL1_ANC1_SOFT_RESET_S 0 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_SOFT_RESET_M 0x1 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_SOFT_RESET_ANC_RESET 0x1 +#define TIMPANI_CDC_ANC1_CTL1_ANC1_SOFT_RESET_ANC_ACTIVE 0x0 + + +/* -- For CDC_ANC1_CTL2 */ +#define TIMPANI_A_CDC_ANC1_CTL2 (0xC1) +#define TIMPANI_CDC_ANC1_CTL2_RWC "RW" +#define TIMPANI_CDC_ANC1_CTL2_POR 0 +#define TIMPANI_CDC_ANC1_CTL2_S 0 +#define TIMPANI_CDC_ANC1_CTL2_M 0x1F + + +#define TIMPANI_CDC_ANC1_CTL2_ANC1_FREQ_SEL_S 0 +#define TIMPANI_CDC_ANC1_CTL2_ANC1_FREQ_SEL_M 0x1F + + +/* -- For CDC_ANC1_FF_FB_SHIFT */ +#define TIMPANI_A_CDC_ANC1_FF_FB_SHIFT (0xC2) +#define TIMPANI_CDC_ANC1_FF_FB_SHIFT_RWC "RW" +#define TIMPANI_CDC_ANC1_FF_FB_SHIFT_POR 0 +#define TIMPANI_CDC_ANC1_FF_FB_SHIFT_S 0 +#define TIMPANI_CDC_ANC1_FF_FB_SHIFT_M 0xFF + + +#define TIMPANI_CDC_ANC1_FF_FB_SHIFT_ANC1_FB_LPF_SHIFT_S 4 +#define TIMPANI_CDC_ANC1_FF_FB_SHIFT_ANC1_FB_LPF_SHIFT_M 0xF0 + +#define TIMPANI_CDC_ANC1_FF_FB_SHIFT_ANC1_FF_LPF_SHIFT_S 0 +#define TIMPANI_CDC_ANC1_FF_FB_SHIFT_ANC1_FF_LPF_SHIFT_M 0xF + + +/* -- For CDC_ANC1_RX_NS */ +#define TIMPANI_A_CDC_ANC1_RX_NS (0xC3) +#define TIMPANI_CDC_ANC1_RX_NS_RWC "RW" +#define TIMPANI_CDC_ANC1_RX_NS_POR 0x1 +#define TIMPANI_CDC_ANC1_RX_NS_S 0 +#define TIMPANI_CDC_ANC1_RX_NS_M 0x7 + + +#define TIMPANI_CDC_ANC1_RX_NS_ANC1_DITHER_BP_S 2 +#define TIMPANI_CDC_ANC1_RX_NS_ANC1_DITHER_BP_M 0x4 + +#define TIMPANI_CDC_ANC1_RX_NS_ANC1_DITHER_SHAPE_SEL_S 1 +#define TIMPANI_CDC_ANC1_RX_NS_ANC1_DITHER_SHAPE_SEL_M 0x2 + +#define TIMPANI_CDC_ANC1_RX_NS_ANC1_DITHER_DLY_SEL_S 0 +#define TIMPANI_CDC_ANC1_RX_NS_ANC1_DITHER_DLY_SEL_M 0x1 + + +/* -- For CDC_ANC1_SPARE */ +#define TIMPANI_A_CDC_ANC1_SPARE (0xC4) +#define TIMPANI_CDC_ANC1_SPARE_RWC "RW" +#define TIMPANI_CDC_ANC1_SPARE_POR 0 +#define TIMPANI_CDC_ANC1_SPARE_S 0 +#define TIMPANI_CDC_ANC1_SPARE_M 0xFF + + +#define TIMPANI_CDC_ANC1_SPARE_ANC1_SPARE_S 0 +#define TIMPANI_CDC_ANC1_SPARE_ANC1_SPARE_M 0xFF + + +/* -- For CDC_ANC1_IIR_COEFF_PTR */ +#define TIMPANI_A_CDC_ANC1_IIR_COEFF_PTR (0xC5) +#define TIMPANI_CDC_ANC1_IIR_COEFF_PTR_RWC "RW" +#define TIMPANI_CDC_ANC1_IIR_COEFF_PTR_POR 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_PTR_S 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_PTR_M 0x1F + + +#define TIMPANI_CDC_ANC1_IIR_COEFF_PTR_ANC1_IIR_COEFF_PTR_S 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_PTR_ANC1_IIR_COEFF_PTR_M 0x1F + + +/* -- For CDC_ANC1_IIR_COEFF_MSB */ +#define TIMPANI_A_CDC_ANC1_IIR_COEFF_MSB (0xC6) +#define TIMPANI_CDC_ANC1_IIR_COEFF_MSB_RWC "RW" +#define TIMPANI_CDC_ANC1_IIR_COEFF_MSB_POR 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_MSB_S 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_MSB_M 0x1 + + +#define TIMPANI_CDC_ANC1_IIR_COEFF_MSB_ANC1_IIR_COEFF_MSB_S 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_MSB_ANC1_IIR_COEFF_MSB_M 0x1 + + +/* -- For CDC_ANC1_IIR_COEFF_LSB */ +#define TIMPANI_A_CDC_ANC1_IIR_COEFF_LSB (0xC7) +#define TIMPANI_CDC_ANC1_IIR_COEFF_LSB_RWC "RW" +#define TIMPANI_CDC_ANC1_IIR_COEFF_LSB_POR 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_LSB_S 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_LSB_M 0xFF + + +#define TIMPANI_CDC_ANC1_IIR_COEFF_LSB_ANC1_IIR_COEFF_LSB_S 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_LSB_ANC1_IIR_COEFF_LSB_M 0xFF + + +/* -- For CDC_ANC1_IIR_COEFF_CTL */ +#define TIMPANI_A_CDC_ANC1_IIR_COEFF_CTL (0xC8) +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_RWC "RW" +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_POR 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_S 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_M 0x3 + + +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_ANC1_IIR_COEFF_ADAPTIVE_S 1 +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_ANC1_IIR_COEFF_ADAPTIVE_M 0x2 +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_ANC1_IIR_COEFF_ADAPTIVE_ADAPTIVE 0x1 +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_ANC1_IIR_COEFF_ADAPTIVE_NON_ADAPTIVE 0x0 + +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_ANC1_IIR_COEFF_EN_S 0 +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_ANC1_IIR_COEFF_EN_M 0x1 +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_ANC1_IIR_COEFF_EN_UPDATE 0x1 +#define TIMPANI_CDC_ANC1_IIR_COEFF_CTL_ANC1_IIR_COEFF_EN_NO_UPDATE 0x0 + + +/* -- For CDC_ANC1_LPF_COEFF_PTR */ +#define TIMPANI_A_CDC_ANC1_LPF_COEFF_PTR (0xC9) +#define TIMPANI_CDC_ANC1_LPF_COEFF_PTR_RWC "RW" +#define TIMPANI_CDC_ANC1_LPF_COEFF_PTR_POR 0 +#define TIMPANI_CDC_ANC1_LPF_COEFF_PTR_S 0 +#define TIMPANI_CDC_ANC1_LPF_COEFF_PTR_M 0xF + + +#define TIMPANI_CDC_ANC1_LPF_COEFF_PTR_ANC1_LPF_COEFF_PTR_S 0 +#define TIMPANI_CDC_ANC1_LPF_COEFF_PTR_ANC1_LPF_COEFF_PTR_M 0xF + + +/* -- For CDC_ANC1_LPF_COEFF_MSB */ +#define TIMPANI_A_CDC_ANC1_LPF_COEFF_MSB (0xCA) +#define TIMPANI_CDC_ANC1_LPF_COEFF_MSB_RWC "RW" +#define TIMPANI_CDC_ANC1_LPF_COEFF_MSB_POR 0 +#define TIMPANI_CDC_ANC1_LPF_COEFF_MSB_S 0 +#define TIMPANI_CDC_ANC1_LPF_COEFF_MSB_M 0xF + + +#define TIMPANI_CDC_ANC1_LPF_COEFF_MSB_ANC1_LPF_COEFF_MSB_S 0 +#define TIMPANI_CDC_ANC1_LPF_COEFF_MSB_ANC1_LPF_COEFF_MSB_M 0xF + + +/* -- For CDC_ANC1_LPF_COEFF_LSB */ +#define TIMPANI_A_CDC_ANC1_LPF_COEFF_LSB (0xCB) +#define TIMPANI_CDC_ANC1_LPF_COEFF_LSB_RWC "RW" +#define TIMPANI_CDC_ANC1_LPF_COEFF_LSB_POR 0 +#define TIMPANI_CDC_ANC1_LPF_COEFF_LSB_S 0 +#define TIMPANI_CDC_ANC1_LPF_COEFF_LSB_M 0xFF + + +#define TIMPANI_CDC_ANC1_LPF_COEFF_LSB_ANC1_LPF_COEFF_LSB_S 0 +#define TIMPANI_CDC_ANC1_LPF_COEFF_LSB_ANC1_LPF_COEFF_LSB_M 0xFF + + +/* -- For CDC_ANC1_SCALE_PTR */ +#define TIMPANI_A_CDC_ANC1_SCALE_PTR (0xCC) +#define TIMPANI_CDC_ANC1_SCALE_PTR_RWC "RW" +#define TIMPANI_CDC_ANC1_SCALE_PTR_POR 0 +#define TIMPANI_CDC_ANC1_SCALE_PTR_S 0 +#define TIMPANI_CDC_ANC1_SCALE_PTR_M 0x7 + + +#define TIMPANI_CDC_ANC1_SCALE_PTR_ANC1_SCALE_PTR_S 0 +#define TIMPANI_CDC_ANC1_SCALE_PTR_ANC1_SCALE_PTR_M 0x7 + + +/* -- For CDC_ANC1_SCALE */ +#define TIMPANI_A_CDC_ANC1_SCALE (0xCD) +#define TIMPANI_CDC_ANC1_SCALE_RWC "RW" +#define TIMPANI_CDC_ANC1_SCALE_POR 0 +#define TIMPANI_CDC_ANC1_SCALE_S 0 +#define TIMPANI_CDC_ANC1_SCALE_M 0xFF + + +#define TIMPANI_CDC_ANC1_SCALE_ANC1_SCALE_S 0 +#define TIMPANI_CDC_ANC1_SCALE_ANC1_SCALE_M 0xFF + + +/* -- For CDC_ANC1_DEBUG */ +#define TIMPANI_A_CDC_ANC1_DEBUG (0xCE) +#define TIMPANI_CDC_ANC1_DEBUG_RWC "RW" +#define TIMPANI_CDC_ANC1_DEBUG_POR 0 +#define TIMPANI_CDC_ANC1_DEBUG_S 0 +#define TIMPANI_CDC_ANC1_DEBUG_M 0xF + + +#define TIMPANI_CDC_ANC1_DEBUG_ANC1_DEBUG_SEL_S 0 +#define TIMPANI_CDC_ANC1_DEBUG_ANC1_DEBUG_SEL_M 0xF + + +/* -- For CDC_ANC2_CTL1 */ +#define TIMPANI_A_CDC_ANC2_CTL1 (0xD0) +#define TIMPANI_CDC_ANC2_CTL1_RWC "RW" +#define TIMPANI_CDC_ANC2_CTL1_POR 0 +#define TIMPANI_CDC_ANC2_CTL1_S 0 +#define TIMPANI_CDC_ANC2_CTL1_M 0x3F + + +#define TIMPANI_CDC_ANC2_CTL1_ANC2_FF_OUT_DIS_S 5 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_FF_OUT_DIS_M 0x20 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_FF_OUT_DIS_FF_OUT_DIS 0x1 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_FF_OUT_DIS_FF_OUT_EN 0x0 + +#define TIMPANI_CDC_ANC2_CTL1_ANC2_ADC_DMIC_SEL_S 4 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_ADC_DMIC_SEL_M 0x10 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_ADC_DMIC_SEL_DMIC 0x1 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_ADC_DMIC_SEL_ADC 0x0 + +#define TIMPANI_CDC_ANC2_CTL1_ANC2_LR_EN_S 3 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_LR_EN_M 0x8 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_LR_EN_LR_MIX_EN 0x1 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_LR_EN_LR_MIX_DIS 0x0 + +#define TIMPANI_CDC_ANC2_CTL1_ANC2_FB_EN_S 2 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_FB_EN_M 0x4 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_FB_EN_FB_MIX_EN 0x1 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_FB_EN_FB_MIX_DIS 0x0 + +#define TIMPANI_CDC_ANC2_CTL1_ANC2_EN_S 1 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_EN_M 0x2 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_EN_ANC_EN 0x1 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_EN_ANC_DIS 0x0 + +#define TIMPANI_CDC_ANC2_CTL1_ANC2_SOFT_RESET_S 0 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_SOFT_RESET_M 0x1 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_SOFT_RESET_ANC_RESET 0x1 +#define TIMPANI_CDC_ANC2_CTL1_ANC2_SOFT_RESET_ANC_ACTIVE 0x0 + + +/* -- For CDC_ANC2_CTL2 */ +#define TIMPANI_A_CDC_ANC2_CTL2 (0xD1) +#define TIMPANI_CDC_ANC2_CTL2_RWC "RW" +#define TIMPANI_CDC_ANC2_CTL2_POR 0 +#define TIMPANI_CDC_ANC2_CTL2_S 0 +#define TIMPANI_CDC_ANC2_CTL2_M 0x1F + + +#define TIMPANI_CDC_ANC2_CTL2_ANC2_FREQ_SEL_S 0 +#define TIMPANI_CDC_ANC2_CTL2_ANC2_FREQ_SEL_M 0x1F + + +/* -- For CDC_ANC2_FF_FB_SHIFT */ +#define TIMPANI_A_CDC_ANC2_FF_FB_SHIFT (0xD2) +#define TIMPANI_CDC_ANC2_FF_FB_SHIFT_RWC "RW" +#define TIMPANI_CDC_ANC2_FF_FB_SHIFT_POR 0 +#define TIMPANI_CDC_ANC2_FF_FB_SHIFT_S 0 +#define TIMPANI_CDC_ANC2_FF_FB_SHIFT_M 0xFF + + +#define TIMPANI_CDC_ANC2_FF_FB_SHIFT_ANC2_FB_LPF_SHIFT_S 4 +#define TIMPANI_CDC_ANC2_FF_FB_SHIFT_ANC2_FB_LPF_SHIFT_M 0xF0 + +#define TIMPANI_CDC_ANC2_FF_FB_SHIFT_ANC2_FF_LPF_SHIFT_S 0 +#define TIMPANI_CDC_ANC2_FF_FB_SHIFT_ANC2_FF_LPF_SHIFT_M 0xF + + +/* -- For CDC_ANC2_RX_NS */ +#define TIMPANI_A_CDC_ANC2_RX_NS (0xD3) +#define TIMPANI_CDC_ANC2_RX_NS_RWC "RW" +#define TIMPANI_CDC_ANC2_RX_NS_POR 0x1 +#define TIMPANI_CDC_ANC2_RX_NS_S 0 +#define TIMPANI_CDC_ANC2_RX_NS_M 0x7 + + +#define TIMPANI_CDC_ANC2_RX_NS_ANC2_DITHER_BP_S 2 +#define TIMPANI_CDC_ANC2_RX_NS_ANC2_DITHER_BP_M 0x4 + +#define TIMPANI_CDC_ANC2_RX_NS_ANC2_DITHER_SHAPE_SEL_S 1 +#define TIMPANI_CDC_ANC2_RX_NS_ANC2_DITHER_SHAPE_SEL_M 0x2 + +#define TIMPANI_CDC_ANC2_RX_NS_ANC2_DITHER_DLY_SEL_S 0 +#define TIMPANI_CDC_ANC2_RX_NS_ANC2_DITHER_DLY_SEL_M 0x1 + + +/* -- For CDC_ANC2_SPARE */ +#define TIMPANI_A_CDC_ANC2_SPARE (0xD4) +#define TIMPANI_CDC_ANC2_SPARE_RWC "RW" +#define TIMPANI_CDC_ANC2_SPARE_POR 0 +#define TIMPANI_CDC_ANC2_SPARE_S 0 +#define TIMPANI_CDC_ANC2_SPARE_M 0xFF + + +#define TIMPANI_CDC_ANC2_SPARE_ANC2_SPARE_S 0 +#define TIMPANI_CDC_ANC2_SPARE_ANC2_SPARE_M 0xFF + + +/* -- For CDC_ANC2_IIR_COEFF_PTR */ +#define TIMPANI_A_CDC_ANC2_IIR_COEFF_PTR (0xD5) +#define TIMPANI_CDC_ANC2_IIR_COEFF_PTR_RWC "RW" +#define TIMPANI_CDC_ANC2_IIR_COEFF_PTR_POR 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_PTR_S 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_PTR_M 0x1F + + +#define TIMPANI_CDC_ANC2_IIR_COEFF_PTR_ANC2_IIR_COEFF_PTR_S 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_PTR_ANC2_IIR_COEFF_PTR_M 0x1F + + +/* -- For CDC_ANC2_IIR_COEFF_MSB */ +#define TIMPANI_A_CDC_ANC2_IIR_COEFF_MSB (0xD6) +#define TIMPANI_CDC_ANC2_IIR_COEFF_MSB_RWC "RW" +#define TIMPANI_CDC_ANC2_IIR_COEFF_MSB_POR 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_MSB_S 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_MSB_M 0x1 + + +#define TIMPANI_CDC_ANC2_IIR_COEFF_MSB_ANC2_IIR_COEFF_MSB_S 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_MSB_ANC2_IIR_COEFF_MSB_M 0x1 + + +/* -- For CDC_ANC2_IIR_COEFF_LSB */ +#define TIMPANI_A_CDC_ANC2_IIR_COEFF_LSB (0xD7) +#define TIMPANI_CDC_ANC2_IIR_COEFF_LSB_RWC "RW" +#define TIMPANI_CDC_ANC2_IIR_COEFF_LSB_POR 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_LSB_S 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_LSB_M 0xFF + + +#define TIMPANI_CDC_ANC2_IIR_COEFF_LSB_ANC2_IIR_COEFF_LSB_S 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_LSB_ANC2_IIR_COEFF_LSB_M 0xFF + + +/* -- For CDC_ANC2_IIR_COEFF_CTL */ +#define TIMPANI_A_CDC_ANC2_IIR_COEFF_CTL (0xD8) +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_RWC "RW" +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_POR 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_S 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_M 0x3 + + +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_ANC2_IIR_COEFF_ADAPTIVE_S 1 +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_ANC2_IIR_COEFF_ADAPTIVE_M 0x2 +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_ANC2_IIR_COEFF_ADAPTIVE_ADAPTIVE 0x1 +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_ANC2_IIR_COEFF_ADAPTIVE_NON_ADAPTIVE 0x0 + +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_ANC2_IIR_COEFF_EN_S 0 +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_ANC2_IIR_COEFF_EN_M 0x1 +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_ANC2_IIR_COEFF_EN_UPDATE 0x1 +#define TIMPANI_CDC_ANC2_IIR_COEFF_CTL_ANC2_IIR_COEFF_EN_NO_UPDATE 0x0 + + +/* -- For CDC_ANC2_LPF_COEFF_PTR */ +#define TIMPANI_A_CDC_ANC2_LPF_COEFF_PTR (0xD9) +#define TIMPANI_CDC_ANC2_LPF_COEFF_PTR_RWC "RW" +#define TIMPANI_CDC_ANC2_LPF_COEFF_PTR_POR 0 +#define TIMPANI_CDC_ANC2_LPF_COEFF_PTR_S 0 +#define TIMPANI_CDC_ANC2_LPF_COEFF_PTR_M 0xF + + +#define TIMPANI_CDC_ANC2_LPF_COEFF_PTR_ANC2_LPF_COEFF_PTR_S 0 +#define TIMPANI_CDC_ANC2_LPF_COEFF_PTR_ANC2_LPF_COEFF_PTR_M 0xF + + +/* -- For CDC_ANC2_LPF_COEFF_MSB */ +#define TIMPANI_A_CDC_ANC2_LPF_COEFF_MSB (0xDA) +#define TIMPANI_CDC_ANC2_LPF_COEFF_MSB_RWC "RW" +#define TIMPANI_CDC_ANC2_LPF_COEFF_MSB_POR 0 +#define TIMPANI_CDC_ANC2_LPF_COEFF_MSB_S 0 +#define TIMPANI_CDC_ANC2_LPF_COEFF_MSB_M 0xF + + +#define TIMPANI_CDC_ANC2_LPF_COEFF_MSB_ANC2_LPF_COEFF_MSB_S 0 +#define TIMPANI_CDC_ANC2_LPF_COEFF_MSB_ANC2_LPF_COEFF_MSB_M 0xF + + +/* -- For CDC_ANC2_LPF_COEFF_LSB */ +#define TIMPANI_A_CDC_ANC2_LPF_COEFF_LSB (0xDB) +#define TIMPANI_CDC_ANC2_LPF_COEFF_LSB_RWC "RW" +#define TIMPANI_CDC_ANC2_LPF_COEFF_LSB_POR 0 +#define TIMPANI_CDC_ANC2_LPF_COEFF_LSB_S 0 +#define TIMPANI_CDC_ANC2_LPF_COEFF_LSB_M 0xFF + + +#define TIMPANI_CDC_ANC2_LPF_COEFF_LSB_ANC2_LPF_COEFF_LSB_S 0 +#define TIMPANI_CDC_ANC2_LPF_COEFF_LSB_ANC2_LPF_COEFF_LSB_M 0xFF + + +/* -- For CDC_ANC2_SCALE_PTR */ +#define TIMPANI_A_CDC_ANC2_SCALE_PTR (0xDC) +#define TIMPANI_CDC_ANC2_SCALE_PTR_RWC "RW" +#define TIMPANI_CDC_ANC2_SCALE_PTR_POR 0 +#define TIMPANI_CDC_ANC2_SCALE_PTR_S 0 +#define TIMPANI_CDC_ANC2_SCALE_PTR_M 0x7 + + +#define TIMPANI_CDC_ANC2_SCALE_PTR_ANC2_SCALE_PTR_S 0 +#define TIMPANI_CDC_ANC2_SCALE_PTR_ANC2_SCALE_PTR_M 0x7 + + +/* -- For CDC_ANC2_SCALE */ +#define TIMPANI_A_CDC_ANC2_SCALE (0xDD) +#define TIMPANI_CDC_ANC2_SCALE_RWC "RW" +#define TIMPANI_CDC_ANC2_SCALE_POR 0 +#define TIMPANI_CDC_ANC2_SCALE_S 0 +#define TIMPANI_CDC_ANC2_SCALE_M 0xFF + + +#define TIMPANI_CDC_ANC2_SCALE_ANC2_SCALE_S 0 +#define TIMPANI_CDC_ANC2_SCALE_ANC2_SCALE_M 0xFF + + +/* -- For CDC_ANC2_DEBUG */ +#define TIMPANI_A_CDC_ANC2_DEBUG (0xDE) +#define TIMPANI_CDC_ANC2_DEBUG_RWC "RW" +#define TIMPANI_CDC_ANC2_DEBUG_POR 0 +#define TIMPANI_CDC_ANC2_DEBUG_S 0 +#define TIMPANI_CDC_ANC2_DEBUG_M 0xF + + +#define TIMPANI_CDC_ANC2_DEBUG_ANC2_DEBUG_SEL_S 0 +#define TIMPANI_CDC_ANC2_DEBUG_ANC2_DEBUG_SEL_M 0xF + + +/* -- For CDC_LINE_L_AVOL */ +#define TIMPANI_A_CDC_LINE_L_AVOL (0xE0) +#define TIMPANI_CDC_LINE_L_AVOL_RWC "RW" +#define TIMPANI_CDC_LINE_L_AVOL_POR 0xac +#define TIMPANI_CDC_LINE_L_AVOL_S 0 +#define TIMPANI_CDC_LINE_L_AVOL_M 0xFF + + +#define TIMPANI_CDC_LINE_L_AVOL_USER_GAIN_S 2 +#define TIMPANI_CDC_LINE_L_AVOL_USER_GAIN_M 0xFC + +#define TIMPANI_CDC_LINE_L_AVOL_DUMMY_S 0 +#define TIMPANI_CDC_LINE_L_AVOL_DUMMY_M 0x3 + + +/* -- For CDC_LINE_R_AVOL */ +#define TIMPANI_A_CDC_LINE_R_AVOL (0xE1) +#define TIMPANI_CDC_LINE_R_AVOL_RWC "RW" +#define TIMPANI_CDC_LINE_R_AVOL_POR 0xac +#define TIMPANI_CDC_LINE_R_AVOL_S 0 +#define TIMPANI_CDC_LINE_R_AVOL_M 0xFF + + +#define TIMPANI_CDC_LINE_R_AVOL_USER_GAIN_S 2 +#define TIMPANI_CDC_LINE_R_AVOL_USER_GAIN_M 0xFC + +#define TIMPANI_CDC_LINE_R_AVOL_DUMMY_S 0 +#define TIMPANI_CDC_LINE_R_AVOL_DUMMY_M 0x3 + + +/* -- For CDC_HPH_L_AVOL */ +#define TIMPANI_A_CDC_HPH_L_AVOL (0xE2) +#define TIMPANI_CDC_HPH_L_AVOL_RWC "RW" +#define TIMPANI_CDC_HPH_L_AVOL_POR 0xae +#define TIMPANI_CDC_HPH_L_AVOL_S 0 +#define TIMPANI_CDC_HPH_L_AVOL_M 0xFF + + +#define TIMPANI_CDC_HPH_L_AVOL_USER_GAIN_S 2 +#define TIMPANI_CDC_HPH_L_AVOL_USER_GAIN_M 0xFC + +#define TIMPANI_CDC_HPH_L_AVOL_MUTE_S 1 +#define TIMPANI_CDC_HPH_L_AVOL_MUTE_M 0x2 +#define TIMPANI_CDC_HPH_L_AVOL_MUTE_MUTE 0x1 +#define TIMPANI_CDC_HPH_L_AVOL_MUTE_UNMUTE 0x0 + +#define TIMPANI_CDC_HPH_L_AVOL_DUMMY_S 0 +#define TIMPANI_CDC_HPH_L_AVOL_DUMMY_M 0x1 + + +/* -- For CDC_HPH_R_AVOL */ +#define TIMPANI_A_CDC_HPH_R_AVOL (0xE3) +#define TIMPANI_CDC_HPH_R_AVOL_RWC "RW" +#define TIMPANI_CDC_HPH_R_AVOL_POR 0xae +#define TIMPANI_CDC_HPH_R_AVOL_S 0 +#define TIMPANI_CDC_HPH_R_AVOL_M 0xFF + + +#define TIMPANI_CDC_HPH_R_AVOL_USER_GAIN_S 2 +#define TIMPANI_CDC_HPH_R_AVOL_USER_GAIN_M 0xFC + +#define TIMPANI_CDC_HPH_R_AVOL_MUTE_S 1 +#define TIMPANI_CDC_HPH_R_AVOL_MUTE_M 0x2 +#define TIMPANI_CDC_HPH_R_AVOL_MUTE_MUTE 0x1 +#define TIMPANI_CDC_HPH_R_AVOL_MUTE_UNMUTE 0x0 + +#define TIMPANI_CDC_HPH_R_AVOL_DUMMY_S 0 +#define TIMPANI_CDC_HPH_R_AVOL_DUMMY_M 0x1 + + +/* -- For CDC_COMP_CTL1 */ +#define TIMPANI_A_CDC_COMP_CTL1 (0xE4) +#define TIMPANI_CDC_COMP_CTL1_RWC "RW" +#define TIMPANI_CDC_COMP_CTL1_POR 0 +#define TIMPANI_CDC_COMP_CTL1_S 0 +#define TIMPANI_CDC_COMP_CTL1_M 0xFF + + +#define TIMPANI_CDC_COMP_CTL1_LO_CLK_EN_S 7 +#define TIMPANI_CDC_COMP_CTL1_LO_CLK_EN_M 0x80 + +#define TIMPANI_CDC_COMP_CTL1_HPH_CLK_EN_S 6 +#define TIMPANI_CDC_COMP_CTL1_HPH_CLK_EN_M 0x40 + +#define TIMPANI_CDC_COMP_CTL1_LO_SOFT_RESET_S 5 +#define TIMPANI_CDC_COMP_CTL1_LO_SOFT_RESET_M 0x20 + +#define TIMPANI_CDC_COMP_CTL1_HPH_SOFT_RESET_S 4 +#define TIMPANI_CDC_COMP_CTL1_HPH_SOFT_RESET_M 0x10 + +#define TIMPANI_CDC_COMP_CTL1_LO_R_EN_S 3 +#define TIMPANI_CDC_COMP_CTL1_LO_R_EN_M 0x8 + +#define TIMPANI_CDC_COMP_CTL1_LO_L_EN_S 2 +#define TIMPANI_CDC_COMP_CTL1_LO_L_EN_M 0x4 + +#define TIMPANI_CDC_COMP_CTL1_HPH_R_EN_S 1 +#define TIMPANI_CDC_COMP_CTL1_HPH_R_EN_M 0x2 + +#define TIMPANI_CDC_COMP_CTL1_HPH_L_EN_S 0 +#define TIMPANI_CDC_COMP_CTL1_HPH_L_EN_M 0x1 + + +/* -- For CDC_COMP_CTL2 */ +#define TIMPANI_A_CDC_COMP_CTL2 (0xE5) +#define TIMPANI_CDC_COMP_CTL2_RWC "RW" +#define TIMPANI_CDC_COMP_CTL2_POR 0xe +#define TIMPANI_CDC_COMP_CTL2_S 0 +#define TIMPANI_CDC_COMP_CTL2_M 0xF + + +#define TIMPANI_CDC_COMP_CTL2_LINEOUT_IN_MUX_S 2 +#define TIMPANI_CDC_COMP_CTL2_LINEOUT_IN_MUX_M 0xC + +#define TIMPANI_CDC_COMP_CTL2_HPH_IN_MUX_S 0 +#define TIMPANI_CDC_COMP_CTL2_HPH_IN_MUX_M 0x3 + + +/* -- For CDC_COMP_PEAK_METER */ +#define TIMPANI_A_CDC_COMP_PEAK_METER (0xE6) +#define TIMPANI_CDC_COMP_PEAK_METER_RWC "RW" +#define TIMPANI_CDC_COMP_PEAK_METER_POR 0x9 +#define TIMPANI_CDC_COMP_PEAK_METER_S 0 +#define TIMPANI_CDC_COMP_PEAK_METER_M 0xF + + +#define TIMPANI_CDC_COMP_PEAK_METER_TIME_OUT_S 0 +#define TIMPANI_CDC_COMP_PEAK_METER_TIME_OUT_M 0xF + + +/* -- For CDC_COMP_LEVEL_METER_CTL1 */ +#define TIMPANI_A_CDC_COMP_LEVEL_METER_CTL1 (0xE7) +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL1_RWC "RW" +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL1_POR 0x7 +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL1_S 0 +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL1_M 0xF + + +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL1_DIV_FACTOR_S 0 +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL1_DIV_FACTOR_M 0xF + + +/* -- For CDC_COMP_LEVEL_METER_CTL2 */ +#define TIMPANI_A_CDC_COMP_LEVEL_METER_CTL2 (0xE8) +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL2_RWC "RW" +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL2_POR 0x28 +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL2_S 0 +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL2_M 0xFF + + +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL2_RESAMPLE_RATE_S 0 +#define TIMPANI_CDC_COMP_LEVEL_METER_CTL2_RESAMPLE_RATE_M 0xFF + + +/* -- For CDC_COMP_ZONE_SELECT */ +#define TIMPANI_A_CDC_COMP_ZONE_SELECT (0xE9) +#define TIMPANI_CDC_COMP_ZONE_SELECT_RWC "RW" +#define TIMPANI_CDC_COMP_ZONE_SELECT_POR 0x3b +#define TIMPANI_CDC_COMP_ZONE_SELECT_S 0 +#define TIMPANI_CDC_COMP_ZONE_SELECT_M 0x7F + + +#define TIMPANI_CDC_COMP_ZONE_SELECT_ENTRY_S 3 +#define TIMPANI_CDC_COMP_ZONE_SELECT_ENTRY_M 0x78 + +#define TIMPANI_CDC_COMP_ZONE_SELECT_SHIFT_S 0 +#define TIMPANI_CDC_COMP_ZONE_SELECT_SHIFT_M 0x7 + + +/* -- For CDC_COMP_ZC_MSB */ +#define TIMPANI_A_CDC_COMP_ZC_MSB (0xEA) +#define TIMPANI_CDC_COMP_ZC_MSB_RWC "RW" +#define TIMPANI_CDC_COMP_ZC_MSB_POR 0 +#define TIMPANI_CDC_COMP_ZC_MSB_S 0 +#define TIMPANI_CDC_COMP_ZC_MSB_M 0x7 + + +#define TIMPANI_CDC_COMP_ZC_MSB_DET_WINDOW_S 0 +#define TIMPANI_CDC_COMP_ZC_MSB_DET_WINDOW_M 0x7 + + +/* -- For CDC_COMP_ZC_LSB */ +#define TIMPANI_A_CDC_COMP_ZC_LSB (0xEB) +#define TIMPANI_CDC_COMP_ZC_LSB_RWC "RW" +#define TIMPANI_CDC_COMP_ZC_LSB_POR 0x1f +#define TIMPANI_CDC_COMP_ZC_LSB_S 0 +#define TIMPANI_CDC_COMP_ZC_LSB_M 0xFF + + +#define TIMPANI_CDC_COMP_ZC_LSB_DET_WINDOW_S 0 +#define TIMPANI_CDC_COMP_ZC_LSB_DET_WINDOW_M 0xFF + + +/* -- For CDC_COMP_SHUT_DOWN */ +#define TIMPANI_A_CDC_COMP_SHUT_DOWN (0xEC) +#define TIMPANI_CDC_COMP_SHUT_DOWN_RWC "RW" +#define TIMPANI_CDC_COMP_SHUT_DOWN_POR 0x1b +#define TIMPANI_CDC_COMP_SHUT_DOWN_S 0 +#define TIMPANI_CDC_COMP_SHUT_DOWN_M 0x3F + + +#define TIMPANI_CDC_COMP_SHUT_DOWN_HPH_TIMEOUT_S 3 +#define TIMPANI_CDC_COMP_SHUT_DOWN_HPH_TIMEOUT_M 0x38 + +#define TIMPANI_CDC_COMP_SHUT_DOWN_LO_TIMEOUT_S 0 +#define TIMPANI_CDC_COMP_SHUT_DOWN_LO_TIMEOUT_M 0x7 + + +/* -- For CDC_COMP_SHUT_DOWN_STATUS */ +#define TIMPANI_A_CDC_COMP_SHUT_DOWN_STATUS (0xED) +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_RWC "RW" +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_POR 0 +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_S 0 +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_M 0xF + + +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_LO_R_S 3 +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_LO_R_M 0x8 + +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_LO_L_S 2 +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_LO_L_M 0x4 + +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_HPH_R_S 1 +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_HPH_R_M 0x2 + +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_HPH_L_S 0 +#define TIMPANI_CDC_COMP_SHUT_DOWN_STATUS_HPH_L_M 0x1 + + +/* -- For CDC_COMP_HALT */ +#define TIMPANI_A_CDC_COMP_HALT (0xEE) +#define TIMPANI_CDC_COMP_HALT_RWC "RW" +#define TIMPANI_CDC_COMP_HALT_POR 0 +#define TIMPANI_CDC_COMP_HALT_S 0 +#define TIMPANI_CDC_COMP_HALT_M 0x1 + + +#define TIMPANI_CDC_COMP_HALT_COMPANDER_HALT_S 0 +#define TIMPANI_CDC_COMP_HALT_COMPANDER_HALT_M 0x1 + + +#endif diff --git a/include/linux/mfd/tps65023.h b/include/linux/mfd/tps65023.h new file mode 100644 index 00000000000..4cce0913997 --- /dev/null +++ b/include/linux/mfd/tps65023.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LINUX_I2C_TPS65023_H +#define __LINUX_I2C_TPS65023_H + +#ifndef CONFIG_TPS65023 +/* Set the output voltage for the DCDC1 convertor */ +#define tps65023_set_dcdc1_level(mvolts) (-ENODEV) + +/* Read the output voltage from the DCDC1 convertor */ +#define tps65023_get_dcdc1_level(mvolts) (-ENODEV) + +#else +/* Set the output voltage for the DCDC1 convertor */ +extern int tps65023_set_dcdc1_level(int mvolts); + +/* Read the output voltage from the DCDC1 convertor */ +extern int tps65023_get_dcdc1_level(int *mvolts); +#endif + +#endif diff --git a/include/linux/mfd/wcd9310/core.h b/include/linux/mfd/wcd9310/core.h new file mode 100644 index 00000000000..2d03c95f31d --- /dev/null +++ b/include/linux/mfd/wcd9310/core.h @@ -0,0 +1,115 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MFD_TABLA_CORE_H__ +#define __MFD_TABLA_CORE_H__ + +#include + +#define TABLA_NUM_IRQ_REGS 3 + +#define TABLA_SLIM_NUM_PORT_REG 3 + +enum { + TABLA_IRQ_SLIMBUS = 0, + TABLA_IRQ_MBHC_REMOVAL, + TABLA_IRQ_MBHC_SHORT_TERM, + TABLA_IRQ_MBHC_PRESS, + TABLA_IRQ_MBHC_RELEASE, + TABLA_IRQ_MBHC_POTENTIAL, + TABLA_IRQ_MBHC_INSERTION, + TABLA_IRQ_BG_PRECHARGE, + TABLA_IRQ_PA1_STARTUP, + TABLA_IRQ_PA2_STARTUP, + TABLA_IRQ_PA3_STARTUP, + TABLA_IRQ_PA4_STARTUP, + TABLA_IRQ_PA5_STARTUP, + TABLA_IRQ_MICBIAS1_PRECHARGE, + TABLA_IRQ_MICBIAS2_PRECHARGE, + TABLA_IRQ_MICBIAS3_PRECHARGE, + TABLA_IRQ_HPH_PA_OCPL_FAULT, + TABLA_IRQ_HPH_PA_OCPR_FAULT, + TABLA_IRQ_EAR_PA_OCPL_FAULT, + TABLA_IRQ_HPH_L_PA_STARTUP, + TABLA_IRQ_HPH_R_PA_STARTUP, + TABLA_IRQ_EAR_PA_STARTUP, + TABLA_NUM_IRQS, +}; + +struct tabla { + struct device *dev; + struct slim_device *slim; + struct slim_device *slim_slave; + struct mutex io_lock; + struct mutex xfer_lock; + struct mutex irq_lock; + + unsigned int irq_base; + unsigned int irq; + u8 irq_masks_cur[TABLA_NUM_IRQ_REGS]; + u8 irq_masks_cache[TABLA_NUM_IRQ_REGS]; + u8 irq_level[TABLA_NUM_IRQ_REGS]; + + int reset_gpio; + + int (*read_dev)(struct tabla *tabla, unsigned short reg, + int bytes, void *dest, bool interface_reg); + int (*write_dev)(struct tabla *tabla, unsigned short reg, + int bytes, void *src, bool interface_reg); + + struct regulator_bulk_data *supplies; +}; + +int tabla_reg_read(struct tabla *tabla, unsigned short reg); +int tabla_reg_write(struct tabla *tabla, unsigned short reg, + u8 val); +int tabla_interface_reg_read(struct tabla *tabla, unsigned short reg); +int tabla_interface_reg_write(struct tabla *tabla, unsigned short reg, + u8 val); +int tabla_bulk_read(struct tabla *tabla, unsigned short reg, + int count, u8 *buf); +int tabla_bulk_write(struct tabla *tabla, unsigned short reg, + int count, u8 *buf); +int tabla_irq_init(struct tabla *tabla); +void tabla_irq_exit(struct tabla *tabla); +int tabla_get_logical_addresses(u8 *pgd_la, u8 *inf_la); + +static inline int tabla_request_irq(struct tabla *tabla, int irq, + irq_handler_t handler, const char *name, + void *data) +{ + if (!tabla->irq_base) + return -EINVAL; + return request_threaded_irq(tabla->irq_base + irq, NULL, handler, + IRQF_TRIGGER_RISING, name, + data); +} +static inline void tabla_free_irq(struct tabla *tabla, int irq, void *data) +{ + if (!tabla->irq_base) + return; + free_irq(tabla->irq_base + irq, data); +} +static inline void tabla_enable_irq(struct tabla *tabla, int irq) +{ + if (!tabla->irq_base) + return; + enable_irq(tabla->irq_base + irq); +} +static inline void tabla_disable_irq(struct tabla *tabla, int irq) +{ + if (!tabla->irq_base) + return; + disable_irq_nosync(tabla->irq_base + irq); +} + +#endif diff --git a/include/linux/mfd/wcd9310/pdata.h b/include/linux/mfd/wcd9310/pdata.h new file mode 100644 index 00000000000..f8f7912c008 --- /dev/null +++ b/include/linux/mfd/wcd9310/pdata.h @@ -0,0 +1,27 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __MFD_TABLA_PDATA_H__ + +#define __MFD_TABLA_PDATA_H__ + +#include + +struct tabla_pdata { + int irq; + int irq_base; + int num_irqs; + int reset_gpio; + struct slim_device slimbus_slave_device; +}; + +#endif diff --git a/include/linux/mfd/wcd9310/registers.h b/include/linux/mfd/wcd9310/registers.h new file mode 100644 index 00000000000..364f0a7be62 --- /dev/null +++ b/include/linux/mfd/wcd9310/registers.h @@ -0,0 +1,1013 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef TABLA_CODEC_DIGITAL_H + +#define TABLA_CODEC_DIGITAL_H + +#define TABLA_A_CHIP_CTL (0x00) +#define TABLA_A_CHIP_CTL__POR (0x00000000) +#define TABLA_A_CHIP_STATUS (0x01) +#define TABLA_A_CHIP_STATUS__POR (0x00000000) +#define TABLA_A_CHIP_ID_BYTE_0 (0x04) +#define TABLA_A_CHIP_ID_BYTE_0__POR (0x00000000) +#define TABLA_A_CHIP_ID_BYTE_1 (0x05) +#define TABLA_A_CHIP_ID_BYTE_1__POR (0x00000000) +#define TABLA_A_CHIP_ID_BYTE_2 (0x06) +#define TABLA_A_CHIP_ID_BYTE_2__POR (0x00000000) +#define TABLA_A_CHIP_ID_BYTE_3 (0x07) +#define TABLA_A_CHIP_ID_BYTE_3__POR (0x00000001) +#define TABLA_A_CHIP_VERSION (0x08) +#define TABLA_A_CHIP_VERSION__POR (0x00000020) +#define TABLA_A_SB_VERSION (0x09) +#define TABLA_A_SB_VERSION__POR (0x00000010) +#define TABLA_A_SLAVE_ID_1 (0x0C) +#define TABLA_A_SLAVE_ID_1__POR (0x00000077) +#define TABLA_A_SLAVE_ID_2 (0x0D) +#define TABLA_A_SLAVE_ID_2__POR (0x00000066) +#define TABLA_A_SLAVE_ID_3 (0x0E) +#define TABLA_A_SLAVE_ID_3__POR (0x00000055) +#define TABLA_A_PIN_CTL_OE0 (0x10) +#define TABLA_A_PIN_CTL_OE0__POR (0x00000000) +#define TABLA_A_PIN_CTL_OE1 (0x11) +#define TABLA_A_PIN_CTL_OE1__POR (0x00000000) +#define TABLA_A_PIN_CTL_DATA0 (0x12) +#define TABLA_A_PIN_CTL_DATA0__POR (0x00000000) +#define TABLA_A_PIN_CTL_DATA1 (0x13) +#define TABLA_A_PIN_CTL_DATA1__POR (0x00000000) +#define TABLA_A_HDRIVE_GENERIC (0x18) +#define TABLA_A_HDRIVE_GENERIC__POR (0x00000000) +#define TABLA_A_HDRIVE_OVERRIDE (0x19) +#define TABLA_A_HDRIVE_OVERRIDE__POR (0x00000008) +#define TABLA_A_ANA_CSR_WAIT_STATE (0x20) +#define TABLA_A_ANA_CSR_WAIT_STATE__POR (0x00000044) +#define TABLA_A_PROCESS_MONITOR_CTL0 (0x40) +#define TABLA_A_PROCESS_MONITOR_CTL0__POR (0x00000080) +#define TABLA_A_PROCESS_MONITOR_CTL1 (0x41) +#define TABLA_A_PROCESS_MONITOR_CTL1__POR (0x00000000) +#define TABLA_A_PROCESS_MONITOR_CTL2 (0x42) +#define TABLA_A_PROCESS_MONITOR_CTL2__POR (0x00000000) +#define TABLA_A_PROCESS_MONITOR_CTL3 (0x43) +#define TABLA_A_PROCESS_MONITOR_CTL3__POR (0x00000001) +#define TABLA_A_QFUSE_CTL (0x48) +#define TABLA_A_QFUSE_CTL__POR (0x00000000) +#define TABLA_A_QFUSE_STATUS (0x49) +#define TABLA_A_QFUSE_STATUS__POR (0x00000000) +#define TABLA_A_QFUSE_DATA_OUT0 (0x4A) +#define TABLA_A_QFUSE_DATA_OUT0__POR (0x00000000) +#define TABLA_A_QFUSE_DATA_OUT1 (0x4B) +#define TABLA_A_QFUSE_DATA_OUT1__POR (0x00000000) +#define TABLA_A_QFUSE_DATA_OUT2 (0x4C) +#define TABLA_A_QFUSE_DATA_OUT2__POR (0x00000000) +#define TABLA_A_QFUSE_DATA_OUT3 (0x4D) +#define TABLA_A_QFUSE_DATA_OUT3__POR (0x00000000) +#define TABLA_A_CDC_CTL (0x80) +#define TABLA_A_CDC_CTL__POR (0x00000000) +#define TABLA_A_LEAKAGE_CTL (0x88) +#define TABLA_A_LEAKAGE_CTL__POR (0x00000004) +#define TABLA_A_INTR_MODE (0x90) +#define TABLA_A_INTR_MODE__POR (0x00000000) +#define TABLA_A_INTR_MASK0 (0x94) +#define TABLA_A_INTR_MASK0__POR (0x000000ff) +#define TABLA_A_INTR_MASK1 (0x95) +#define TABLA_A_INTR_MASK1__POR (0x000000ff) +#define TABLA_A_INTR_MASK2 (0x96) +#define TABLA_A_INTR_MASK2__POR (0x000000ff) +#define TABLA_A_INTR_STATUS0 (0x98) +#define TABLA_A_INTR_STATUS0__POR (0x00000000) +#define TABLA_A_INTR_STATUS1 (0x99) +#define TABLA_A_INTR_STATUS1__POR (0x00000000) +#define TABLA_A_INTR_STATUS2 (0x9A) +#define TABLA_A_INTR_STATUS2__POR (0x00000000) +#define TABLA_A_INTR_CLEAR0 (0x9C) +#define TABLA_A_INTR_CLEAR0__POR (0x00000000) +#define TABLA_A_INTR_CLEAR1 (0x9D) +#define TABLA_A_INTR_CLEAR1__POR (0x00000000) +#define TABLA_A_INTR_CLEAR2 (0x9E) +#define TABLA_A_INTR_CLEAR2__POR (0x00000000) +#define TABLA_A_INTR_LEVEL0 (0xA0) +#define TABLA_A_INTR_LEVEL0__POR (0x00000001) +#define TABLA_A_INTR_LEVEL1 (0xA1) +#define TABLA_A_INTR_LEVEL1__POR (0x00000000) +#define TABLA_A_INTR_LEVEL2 (0xA2) +#define TABLA_A_INTR_LEVEL2__POR (0x00000000) +#define TABLA_A_INTR_TEST0 (0xA4) +#define TABLA_A_INTR_TEST0__POR (0x00000000) +#define TABLA_A_INTR_TEST1 (0xA5) +#define TABLA_A_INTR_TEST1__POR (0x00000000) +#define TABLA_A_INTR_TEST2 (0xA6) +#define TABLA_A_INTR_TEST2__POR (0x00000000) +#define TABLA_A_INTR_SET0 (0xA8) +#define TABLA_A_INTR_SET0__POR (0x00000000) +#define TABLA_A_INTR_SET1 (0xA9) +#define TABLA_A_INTR_SET1__POR (0x00000000) +#define TABLA_A_INTR_SET2 (0xAA) +#define TABLA_A_INTR_SET2__POR (0x00000000) +#define TABLA_A_CDC_TX_I2S_SCK_MODE (0xC0) +#define TABLA_A_CDC_TX_I2S_SCK_MODE__POR (0x00000000) +#define TABLA_A_CDC_TX_I2S_WS_MODE (0xC1) +#define TABLA_A_CDC_TX_I2S_WS_MODE__POR (0x00000000) +#define TABLA_A_CDC_DMIC_DATA0_MODE (0xC4) +#define TABLA_A_CDC_DMIC_DATA0_MODE__POR (0x00000000) +#define TABLA_A_CDC_DMIC_CLK0_MODE (0xC5) +#define TABLA_A_CDC_DMIC_CLK0_MODE__POR (0x00000000) +#define TABLA_A_CDC_DMIC_DATA1_MODE (0xC6) +#define TABLA_A_CDC_DMIC_DATA1_MODE__POR (0x00000000) +#define TABLA_A_CDC_DMIC_CLK1_MODE (0xC7) +#define TABLA_A_CDC_DMIC_CLK1_MODE__POR (0x00000000) +#define TABLA_A_CDC_RX_I2S_SCK_MODE (0xC8) +#define TABLA_A_CDC_RX_I2S_SCK_MODE__POR (0x00000000) +#define TABLA_A_CDC_RX_I2S_WS_MODE (0xC9) +#define TABLA_A_CDC_RX_I2S_WS_MODE__POR (0x00000000) +#define TABLA_A_CDC_DMIC_DATA2_MODE (0xCA) +#define TABLA_A_CDC_DMIC_DATA2_MODE__POR (0x00000000) +#define TABLA_A_CDC_DMIC_CLK2_MODE (0xCB) +#define TABLA_A_CDC_DMIC_CLK2_MODE__POR (0x00000000) +#define TABLA_A_CDC_INTR_MODE (0xCC) +#define TABLA_A_CDC_INTR_MODE__POR (0x00000000) +#define TABLA_A_BIAS_REF_CTL (0x0100) +#define TABLA_A_BIAS_REF_CTL__POR (0x0000001C) +#define TABLA_A_BIAS_CENTRAL_BG_CTL (0x0101) +#define TABLA_A_BIAS_CENTRAL_BG_CTL__POR (0x00000050) +#define TABLA_A_BIAS_PRECHRG_CTL (0x0102) +#define TABLA_A_BIAS_PRECHRG_CTL__POR (0x00000007) +#define TABLA_A_BIAS_CURR_CTL_1 (0x0103) +#define TABLA_A_BIAS_CURR_CTL_1__POR (0x00000052) +#define TABLA_A_BIAS_CURR_CTL_2 (0x0104) +#define TABLA_A_BIAS_CURR_CTL_2__POR (0x00000000) +#define TABLA_A_BIAS_CONFIG_MODE_BG_CTL (0x0105) +#define TABLA_A_BIAS_CONFIG_MODE_BG_CTL__POR (0x00000016) +#define TABLA_A_BIAS_BG_STATUS (0x0106) +#define TABLA_A_BIAS_BG_STATUS__POR (0x00000000) +#define TABLA_A_CLK_BUFF_EN1 (0x0108) +#define TABLA_A_CLK_BUFF_EN1__POR (0x00000004) +#define TABLA_A_CLK_BUFF_EN2 (0x0109) +#define TABLA_A_CLK_BUFF_EN2__POR (0x00000002) +#define TABLA_A_LDO_H_MODE_1 (0x0110) +#define TABLA_A_LDO_H_MODE_1__POR (0x00000065) +#define TABLA_A_LDO_H_MODE_2 (0x0111) +#define TABLA_A_LDO_H_MODE_2__POR (0x000000A8) +#define TABLA_A_LDO_H_LOOP_CTL (0x0112) +#define TABLA_A_LDO_H_LOOP_CTL__POR (0x0000006B) +#define TABLA_A_LDO_H_COMP_1 (0x0113) +#define TABLA_A_LDO_H_COMP_1__POR (0x00000084) +#define TABLA_A_LDO_H_COMP_2 (0x0114) +#define TABLA_A_LDO_H_COMP_2__POR (0x000000E0) +#define TABLA_A_LDO_H_BIAS_1 (0x0115) +#define TABLA_A_LDO_H_BIAS_1__POR (0x0000006D) +#define TABLA_A_LDO_H_BIAS_2 (0x0116) +#define TABLA_A_LDO_H_BIAS_2__POR (0x000000A5) +#define TABLA_A_LDO_H_BIAS_3 (0x0117) +#define TABLA_A_LDO_H_BIAS_3__POR (0x00000060) +#define TABLA_A_LDO_L_MODE_1 (0x0118) +#define TABLA_A_LDO_L_MODE_1__POR (0x00000028) +#define TABLA_A_LDO_L_MODE_2 (0x0119) +#define TABLA_A_LDO_L_MODE_2__POR (0x000000A8) +#define TABLA_A_LDO_L_LOOP_CTL (0x011A) +#define TABLA_A_LDO_L_LOOP_CTL__POR (0x0000006D) +#define TABLA_A_LDO_L_COMP_1 (0x011B) +#define TABLA_A_LDO_L_COMP_1__POR (0x00000031) +#define TABLA_A_LDO_L_COMP_2 (0x011C) +#define TABLA_A_LDO_L_COMP_2__POR (0x000000A0) +#define TABLA_A_LDO_L_BIAS_1 (0x011D) +#define TABLA_A_LDO_L_BIAS_1__POR (0x0000006D) +#define TABLA_A_LDO_L_BIAS_2 (0x011E) +#define TABLA_A_LDO_L_BIAS_2__POR (0x00000065) +#define TABLA_A_LDO_L_BIAS_3 (0x011F) +#define TABLA_A_LDO_L_BIAS_3__POR (0x00000050) +#define TABLA_A_MICB_CFILT_1_CTL (0x0128) +#define TABLA_A_MICB_CFILT_1_CTL__POR (0x00000040) +#define TABLA_A_MICB_CFILT_1_VAL (0x0129) +#define TABLA_A_MICB_CFILT_1_VAL__POR (0x00000080) +#define TABLA_A_MICB_CFILT_1_PRECHRG (0x012A) +#define TABLA_A_MICB_CFILT_1_PRECHRG__POR (0x00000038) +#define TABLA_A_MICB_1_CTL (0x012B) +#define TABLA_A_MICB_1_CTL__POR (0x00000016) +#define TABLA_A_MICB_1_INT_RBIAS (0x012C) +#define TABLA_A_MICB_1_INT_RBIAS__POR (0x00000000) +#define TABLA_A_MICB_1_MBHC (0x012D) +#define TABLA_A_MICB_1_MBHC__POR (0x00000001) +#define TABLA_A_MICB_CFILT_2_CTL (0x012E) +#define TABLA_A_MICB_CFILT_2_CTL__POR (0x00000040) +#define TABLA_A_MICB_CFILT_2_VAL (0x012F) +#define TABLA_A_MICB_CFILT_2_VAL__POR (0x00000080) +#define TABLA_A_MICB_CFILT_2_PRECHRG (0x0130) +#define TABLA_A_MICB_CFILT_2_PRECHRG__POR (0x00000038) +#define TABLA_A_MICB_2_CTL (0x0131) +#define TABLA_A_MICB_2_CTL__POR (0x00000016) +#define TABLA_A_MICB_2_INT_RBIAS (0x0132) +#define TABLA_A_MICB_2_INT_RBIAS__POR (0x00000000) +#define TABLA_A_MICB_2_MBHC (0x0133) +#define TABLA_A_MICB_2_MBHC__POR (0x00000000) +#define TABLA_A_MICB_CFILT_3_CTL (0x0134) +#define TABLA_A_MICB_CFILT_3_CTL__POR (0x00000040) +#define TABLA_A_MICB_CFILT_3_VAL (0x0135) +#define TABLA_A_MICB_CFILT_3_VAL__POR (0x00000080) +#define TABLA_A_MICB_CFILT_3_PRECHRG (0x0136) +#define TABLA_A_MICB_CFILT_3_PRECHRG__POR (0x00000038) +#define TABLA_A_MICB_3_CTL (0x0137) +#define TABLA_A_MICB_3_CTL__POR (0x00000016) +#define TABLA_A_MICB_3_INT_RBIAS (0x0138) +#define TABLA_A_MICB_3_INT_RBIAS__POR (0x00000000) +#define TABLA_A_MICB_3_MBHC (0x0139) +#define TABLA_A_MICB_3_MBHC__POR (0x00000000) +#define TABLA_A_MICB_4_CTL (0x013A) +#define TABLA_A_MICB_4_CTL__POR (0x00000016) +#define TABLA_A_MICB_4_INT_RBIAS (0x013B) +#define TABLA_A_MICB_4_INT_RBIAS__POR (0x00000000) +#define TABLA_A_MICB_4_MBHC (0x013C) +#define TABLA_A_MICB_4_MBHC__POR (0x00000001) +#define TABLA_A_TX_COM_BIAS (0x014C) +#define TABLA_A_TX_COM_BIAS__POR (0x000000E0) +#define TABLA_A_MBHC_SCALING_MUX_1 (0x014E) +#define TABLA_A_MBHC_SCALING_MUX_1__POR (0x00000000) +#define TABLA_A_MBHC_SCALING_MUX_2 (0x014F) +#define TABLA_A_MBHC_SCALING_MUX_2__POR (0x00000080) +#define TABLA_A_TX_SUP_SWITCH_CTRL_1 (0x0151) +#define TABLA_A_TX_SUP_SWITCH_CTRL_1__POR (0x00000000) +#define TABLA_A_TX_SUP_SWITCH_CTRL_2 (0x0152) +#define TABLA_A_TX_SUP_SWITCH_CTRL_2__POR (0x00000080) +#define TABLA_A_TX_1_2_EN (0x0153) +#define TABLA_A_TX_1_2_EN__POR (0x00000000) +#define TABLA_A_TX_1_2_TEST_EN (0x0154) +#define TABLA_A_TX_1_2_TEST_EN__POR (0x000000CC) +#define TABLA_A_TX_1_2_ADC_CH1 (0x0155) +#define TABLA_A_TX_1_2_ADC_CH1__POR (0x00000044) +#define TABLA_A_TX_1_2_ADC_CH2 (0x0156) +#define TABLA_A_TX_1_2_ADC_CH2__POR (0x00000044) +#define TABLA_A_TX_1_2_ATEST_REFCTRL (0x0157) +#define TABLA_A_TX_1_2_ATEST_REFCTRL__POR (0x00000000) +#define TABLA_A_TX_1_2_TEST_CTL (0x0158) +#define TABLA_A_TX_1_2_TEST_CTL__POR (0x00000038) +#define TABLA_A_TX_1_2_TEST_BLOCK_EN (0x0159) +#define TABLA_A_TX_1_2_TEST_BLOCK_EN__POR (0x000000FF) +#define TABLA_A_TX_1_2_TXFE_CLKDIV (0x015A) +#define TABLA_A_TX_1_2_TXFE_CLKDIV__POR (0x000000EE) +#define TABLA_A_TX_1_2_SAR_ERR_CH1 (0x015B) +#define TABLA_A_TX_1_2_SAR_ERR_CH1__POR (0x00000000) +#define TABLA_A_TX_1_2_SAR_ERR_CH2 (0x015C) +#define TABLA_A_TX_1_2_SAR_ERR_CH2__POR (0x00000000) +#define TABLA_A_TX_3_4_EN (0x015D) +#define TABLA_A_TX_3_4_EN__POR (0x00000000) +#define TABLA_A_TX_3_4_TEST_EN (0x015E) +#define TABLA_A_TX_3_4_TEST_EN__POR (0x000000CC) +#define TABLA_A_TX_3_4_ADC_CH3 (0x015F) +#define TABLA_A_TX_3_4_ADC_CH3__POR (0x00000044) +#define TABLA_A_TX_3_4_ADC_CH4 (0x0160) +#define TABLA_A_TX_3_4_ADC_CH4__POR (0x00000044) +#define TABLA_A_TX_3_4_ATEST_REFCTRL (0x0161) +#define TABLA_A_TX_3_4_ATEST_REFCTRL__POR (0x00000000) +#define TABLA_A_TX_3_4_TEST_CTL (0x0162) +#define TABLA_A_TX_3_4_TEST_CTL__POR (0x00000038) +#define TABLA_A_TX_3_4_TEST_BLOCK_EN (0x0163) +#define TABLA_A_TX_3_4_TEST_BLOCK_EN__POR (0x000000FF) +#define TABLA_A_TX_3_4_TXFE_CKDIV (0x0164) +#define TABLA_A_TX_3_4_TXFE_CKDIV__POR (0x000000EE) +#define TABLA_A_TX_3_4_SAR_ERR_CH3 (0x0165) +#define TABLA_A_TX_3_4_SAR_ERR_CH3__POR (0x00000000) +#define TABLA_A_TX_3_4_SAR_ERR_CH4 (0x0166) +#define TABLA_A_TX_3_4_SAR_ERR_CH4__POR (0x00000000) +#define TABLA_A_TX_5_6_EN (0x0167) +#define TABLA_A_TX_5_6_EN__POR (0x00000011) +#define TABLA_A_TX_5_6_TEST_EN (0x0168) +#define TABLA_A_TX_5_6_TEST_EN__POR (0x000000CC) +#define TABLA_A_TX_5_6_ADC_CH5 (0x0169) +#define TABLA_A_TX_5_6_ADC_CH5__POR (0x00000044) +#define TABLA_A_TX_5_6_ADC_CH6 (0x016A) +#define TABLA_A_TX_5_6_ADC_CH6__POR (0x00000044) +#define TABLA_A_TX_5_6_ATEST_REFCTRL (0x016B) +#define TABLA_A_TX_5_6_ATEST_REFCTRL__POR (0x00000000) +#define TABLA_A_TX_5_6_TEST_CTL (0x016C) +#define TABLA_A_TX_5_6_TEST_CTL__POR (0x00000038) +#define TABLA_A_TX_5_6_TEST_BLOCK_EN (0x016D) +#define TABLA_A_TX_5_6_TEST_BLOCK_EN__POR (0x000000FF) +#define TABLA_A_TX_5_6_TXFE_CKDIV (0x016E) +#define TABLA_A_TX_5_6_TXFE_CKDIV__POR (0x000000EE) +#define TABLA_A_TX_5_6_SAR_ERR_CH5 (0x016F) +#define TABLA_A_TX_5_6_SAR_ERR_CH5__POR (0x00000000) +#define TABLA_A_TX_5_6_SAR_ERR_CH6 (0x0170) +#define TABLA_A_TX_5_6_SAR_ERR_CH6__POR (0x00000000) +#define TABLA_A_TX_7_MBHC_EN (0x0171) +#define TABLA_A_TX_7_MBHC_EN__POR (0x0000000C) +#define TABLA_A_TX_7_MBHC_ATEST_REFCTRL (0x0172) +#define TABLA_A_TX_7_MBHC_ATEST_REFCTRL__POR (0x00000000) +#define TABLA_A_TX_7_MBHC_ADC (0x0173) +#define TABLA_A_TX_7_MBHC_ADC__POR (0x00000044) +#define TABLA_A_TX_7_MBHC_TEST_CTL (0x0174) +#define TABLA_A_TX_7_MBHC_TEST_CTL__POR (0x00000038) +#define TABLA_A_TX_7_MBHC_SAR_ERR (0x0175) +#define TABLA_A_TX_7_MBHC_SAR_ERR__POR (0x00000000) +#define TABLA_A_TX_7_TXFE_CLKDIV (0x0176) +#define TABLA_A_TX_7_TXFE_CLKDIV__POR (0x0000001C) +#define TABLA_A_AUX_COM_CTL (0x0180) +#define TABLA_A_AUX_COM_CTL__POR (0x00000034) +#define TABLA_A_AUX_COM_ATEST (0x0181) +#define TABLA_A_AUX_COM_ATEST__POR (0x00000000) +#define TABLA_A_AUX_L_EN (0x0182) +#define TABLA_A_AUX_L_EN__POR (0x00000000) +#define TABLA_A_AUX_L_GAIN (0x0183) +#define TABLA_A_AUX_L_GAIN__POR (0x0000001F) +#define TABLA_A_AUX_L_PA_CONN (0x0184) +#define TABLA_A_AUX_L_PA_CONN__POR (0x00000000) +#define TABLA_A_AUX_L_PA_CONN_INV (0x0185) +#define TABLA_A_AUX_L_PA_CONN_INV__POR (0x00000000) +#define TABLA_A_AUX_R_EN (0x0186) +#define TABLA_A_AUX_R_EN__POR (0x00000000) +#define TABLA_A_AUX_R_GAIN (0x0187) +#define TABLA_A_AUX_R_GAIN__POR (0x0000001F) +#define TABLA_A_AUX_R_PA_CONN (0x0188) +#define TABLA_A_AUX_R_PA_CONN__POR (0x00000000) +#define TABLA_A_AUX_R_PA_CONN_INV (0x0189) +#define TABLA_A_AUX_R_PA_CONN_INV__POR (0x00000000) +#define TABLA_A_CP_EN (0x0192) +#define TABLA_A_CP_EN__POR (0x000000E6) +#define TABLA_A_CP_CLK (0x0193) +#define TABLA_A_CP_CLK__POR (0x00000029) +#define TABLA_A_CP_STATIC (0x0194) +#define TABLA_A_CP_STATIC__POR (0x00000010) +#define TABLA_A_CP_DCC1 (0x0195) +#define TABLA_A_CP_DCC1__POR (0x00000052) +#define TABLA_A_CP_DCC3 (0x0196) +#define TABLA_A_CP_DCC3__POR (0x00000001) +#define TABLA_A_CP_ATEST (0x0197) +#define TABLA_A_CP_ATEST__POR (0x00000000) +#define TABLA_A_CP_DTEST (0x0198) +#define TABLA_A_CP_DTEST__POR (0x00000000) +#define TABLA_A_RX_COM_TIMER_DIV (0x019E) +#define TABLA_A_RX_COM_TIMER_DIV__POR (0x000000E8) +#define TABLA_A_RX_COM_OCP_CTL (0x019F) +#define TABLA_A_RX_COM_OCP_CTL__POR (0x0000001F) +#define TABLA_A_RX_COM_OCP_COUNT (0x01A0) +#define TABLA_A_RX_COM_OCP_COUNT__POR (0x00000077) +#define TABLA_A_RX_COM_DAC_CTL (0x01A1) +#define TABLA_A_RX_COM_DAC_CTL__POR (0x00000000) +#define TABLA_A_RX_COM_BIAS (0x01A2) +#define TABLA_A_RX_COM_BIAS__POR (0x00000000) +#define TABLA_A_RX_HPH_BIAS_PA (0x01A6) +#define TABLA_A_RX_HPH_BIAS_PA__POR (0x000000AA) +#define TABLA_A_RX_HPH_BIAS_LDO (0x01A7) +#define TABLA_A_RX_HPH_BIAS_LDO__POR (0x00000086) +#define TABLA_A_RX_HPH_BIAS_CNP (0x01A8) +#define TABLA_A_RX_HPH_BIAS_CNP__POR (0x0000008A) +#define TABLA_A_RX_HPH_BIAS_WG (0x01A9) +#define TABLA_A_RX_HPH_BIAS_WG__POR (0x00000060) +#define TABLA_A_RX_HPH_OCP_CTL (0x01AA) +#define TABLA_A_RX_HPH_OCP_CTL__POR (0x000000E8) +#define TABLA_A_RX_HPH_CNP_EN (0x01AB) +#define TABLA_A_RX_HPH_CNP_EN__POR (0x00000080) +#define TABLA_A_RX_HPH_CNP_WG_CTL (0x01AC) +#define TABLA_A_RX_HPH_CNP_WG_CTL__POR (0x000000DC) +#define TABLA_A_RX_HPH_CNP_WG_TIME (0x01AD) +#define TABLA_A_RX_HPH_CNP_WG_TIME__POR (0x00000028) +#define TABLA_A_RX_HPH_L_GAIN (0x01AE) +#define TABLA_A_RX_HPH_L_GAIN__POR (0x00000000) +#define TABLA_A_RX_HPH_L_TEST (0x01AF) +#define TABLA_A_RX_HPH_L_TEST__POR (0x00000001) +#define TABLA_A_RX_HPH_L_PA_CTL (0x01B0) +#define TABLA_A_RX_HPH_L_PA_CTL__POR (0x00000040) +#define TABLA_A_RX_HPH_L_DAC_CTL (0x01B1) +#define TABLA_A_RX_HPH_L_DAC_CTL__POR (0x00000000) +#define TABLA_A_RX_HPH_L_ATEST (0x01B2) +#define TABLA_A_RX_HPH_L_ATEST__POR (0x00000000) +#define TABLA_A_RX_HPH_L_STATUS (0x01B3) +#define TABLA_A_RX_HPH_L_STATUS__POR (0x00000004) +#define TABLA_A_RX_HPH_R_GAIN (0x01B4) +#define TABLA_A_RX_HPH_R_GAIN__POR (0x00000000) +#define TABLA_A_RX_HPH_R_TEST (0x01B5) +#define TABLA_A_RX_HPH_R_TEST__POR (0x00000001) +#define TABLA_A_RX_HPH_R_PA_CTL (0x01B6) +#define TABLA_A_RX_HPH_R_PA_CTL__POR (0x00000040) +#define TABLA_A_RX_HPH_R_DAC_CTL (0x01B7) +#define TABLA_A_RX_HPH_R_DAC_CTL__POR (0x00000000) +#define TABLA_A_RX_HPH_R_ATEST (0x01B8) +#define TABLA_A_RX_HPH_R_ATEST__POR (0x00000000) +#define TABLA_A_RX_HPH_R_STATUS (0x01B9) +#define TABLA_A_RX_HPH_R_STATUS__POR (0x00000004) +#define TABLA_A_RX_EAR_BIAS_PA (0x01BA) +#define TABLA_A_RX_EAR_BIAS_PA__POR (0x000000AA) +#define TABLA_A_RX_EAR_BIAS_CMBUFF (0x01BB) +#define TABLA_A_RX_EAR_BIAS_CMBUFF__POR (0x000000A0) +#define TABLA_A_RX_EAR_EN (0x01BC) +#define TABLA_A_RX_EAR_EN__POR (0x00000000) +#define TABLA_A_RX_EAR_GAIN (0x01BD) +#define TABLA_A_RX_EAR_GAIN__POR (0x00000008) +#define TABLA_A_RX_EAR_CMBUFF (0x01BE) +#define TABLA_A_RX_EAR_CMBUFF__POR (0x00000000) +#define TABLA_A_RX_EAR_ICTL (0x01BF) +#define TABLA_A_RX_EAR_ICTL__POR (0x00000040) +#define TABLA_A_RX_EAR_CCOMP (0x01C0) +#define TABLA_A_RX_EAR_CCOMP__POR (0x00000008) +#define TABLA_A_RX_EAR_VCM (0x01C1) +#define TABLA_A_RX_EAR_VCM__POR (0x00000000) +#define TABLA_A_RX_EAR_CNP (0x01C2) +#define TABLA_A_RX_EAR_CNP__POR (0x00000080) +#define TABLA_A_RX_EAR_ATEST (0x01C3) +#define TABLA_A_RX_EAR_ATEST__POR (0x00000000) +#define TABLA_A_RX_EAR_STATUS (0x01C5) +#define TABLA_A_RX_EAR_STATUS__POR (0x00000004) +#define TABLA_A_RX_LINE_BIAS_PA (0x01C6) +#define TABLA_A_RX_LINE_BIAS_PA__POR (0x000000AA) +#define TABLA_A_RX_LINE_BIAS_DAC (0x01C7) +#define TABLA_A_RX_LINE_BIAS_DAC__POR (0x000000A0) +#define TABLA_A_RX_LINE_BIAS_CNP (0x01C8) +#define TABLA_A_RX_LINE_BIAS_CNP__POR (0x0000003A) +#define TABLA_A_RX_LINE_COM (0x01C9) +#define TABLA_A_RX_LINE_COM__POR (0x00000000) +#define TABLA_A_RX_LINE_CNP_EN (0x01CA) +#define TABLA_A_RX_LINE_CNP_EN__POR (0x00000080) +#define TABLA_A_RX_LINE_CNP_WG_CTL (0x01CB) +#define TABLA_A_RX_LINE_CNP_WG_CTL__POR (0x0000001C) +#define TABLA_A_RX_LINE_CNP_WG_TIME (0x01CC) +#define TABLA_A_RX_LINE_CNP_WG_TIME__POR (0x00000064) +#define TABLA_A_RX_LINE_1_GAIN (0x01CD) +#define TABLA_A_RX_LINE_1_GAIN__POR (0x00000000) +#define TABLA_A_RX_LINE_1_TEST (0x01CE) +#define TABLA_A_RX_LINE_1_TEST__POR (0x00000000) +#define TABLA_A_RX_LINE_1_DAC_CTL (0x01CF) +#define TABLA_A_RX_LINE_1_DAC_CTL__POR (0x0000000C) +#define TABLA_A_RX_LINE_1_STATUS (0x01D0) +#define TABLA_A_RX_LINE_1_STATUS__POR (0x00000000) +#define TABLA_A_RX_LINE_2_GAIN (0x01D1) +#define TABLA_A_RX_LINE_2_GAIN__POR (0x00000000) +#define TABLA_A_RX_LINE_2_TEST (0x01D2) +#define TABLA_A_RX_LINE_2_TEST__POR (0x00000000) +#define TABLA_A_RX_LINE_2_DAC_CTL (0x01D3) +#define TABLA_A_RX_LINE_2_DAC_CTL__POR (0x0000000C) +#define TABLA_A_RX_LINE_2_STATUS (0x01D4) +#define TABLA_A_RX_LINE_2_STATUS__POR (0x00000000) +#define TABLA_A_RX_LINE_3_GAIN (0x01D5) +#define TABLA_A_RX_LINE_3_GAIN__POR (0x00000000) +#define TABLA_A_RX_LINE_3_TEST (0x01D6) +#define TABLA_A_RX_LINE_3_TEST__POR (0x00000000) +#define TABLA_A_RX_LINE_3_DAC_CTL (0x01D7) +#define TABLA_A_RX_LINE_3_DAC_CTL__POR (0x0000000C) +#define TABLA_A_RX_LINE_3_STATUS (0x01D8) +#define TABLA_A_RX_LINE_3_STATUS__POR (0x00000000) +#define TABLA_A_RX_LINE_4_GAIN (0x01D9) +#define TABLA_A_RX_LINE_4_GAIN__POR (0x00000000) +#define TABLA_A_RX_LINE_4_TEST (0x01DA) +#define TABLA_A_RX_LINE_4_TEST__POR (0x00000000) +#define TABLA_A_RX_LINE_4_DAC_CTL (0x01DB) +#define TABLA_A_RX_LINE_4_DAC_CTL__POR (0x0000000C) +#define TABLA_A_RX_LINE_4_STATUS (0x01DC) +#define TABLA_A_RX_LINE_4_STATUS__POR (0x00000000) +#define TABLA_A_RX_LINE_5_GAIN (0x01DD) +#define TABLA_A_RX_LINE_5_GAIN__POR (0x00000000) +#define TABLA_A_RX_LINE_5_TEST (0x01DE) +#define TABLA_A_RX_LINE_5_TEST__POR (0x00000000) +#define TABLA_A_RX_LINE_5_DAC_CTL (0x01DF) +#define TABLA_A_RX_LINE_5_DAC_CTL__POR (0x0000000C) +#define TABLA_A_RX_LINE_5_STATUS (0x01E0) +#define TABLA_A_RX_LINE_5_STATUS__POR (0x00000000) +#define TABLA_A_RX_LINE_CNP_DBG (0x01EC) +#define TABLA_A_RX_LINE_CNP_DBG__POR (0x00000000) +#define TABLA_A_MBHC_HPH (0x01ED) +#define TABLA_A_MBHC_HPH__POR (0x00000048) +#define TABLA_A_CONFIG_MODE_FREQ (0x01F7) +#define TABLA_A_CONFIG_MODE_FREQ__POR (0x00000047) +#define TABLA_A_CONFIG_MODE_TEST (0x01F8) +#define TABLA_A_CONFIG_MODE_TEST__POR (0x0000000A) +#define TABLA_A_CONFIG_MODE_STATUS (0x01F9) +#define TABLA_A_CONFIG_MODE_STATUS__POR (0x0000001C) +#define TABLA_A_CONFIG_MODE_TUNER (0x01FA) +#define TABLA_A_CONFIG_MODE_TUNER__POR (0x00000000) +#define TABLA_A_CDC_TX1_VOL_CTL_TIMER (0x00000220) +#define TABLA_A_CDC_TX1_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX2_VOL_CTL_TIMER (0x00000228) +#define TABLA_A_CDC_TX2_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX3_VOL_CTL_TIMER (0x00000230) +#define TABLA_A_CDC_TX3_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX4_VOL_CTL_TIMER (0x00000238) +#define TABLA_A_CDC_TX4_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX5_VOL_CTL_TIMER (0x00000240) +#define TABLA_A_CDC_TX5_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX6_VOL_CTL_TIMER (0x00000248) +#define TABLA_A_CDC_TX6_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX7_VOL_CTL_TIMER (0x00000250) +#define TABLA_A_CDC_TX7_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX8_VOL_CTL_TIMER (0x00000258) +#define TABLA_A_CDC_TX8_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX9_VOL_CTL_TIMER (0x00000260) +#define TABLA_A_CDC_TX9_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX10_VOL_CTL_TIMER (0x00000268) +#define TABLA_A_CDC_TX10_VOL_CTL_TIMER__POR (0x00000000) +#define TABLA_A_CDC_TX1_VOL_CTL_GAIN (0x00000221) +#define TABLA_A_CDC_TX1_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX2_VOL_CTL_GAIN (0x00000229) +#define TABLA_A_CDC_TX2_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX3_VOL_CTL_GAIN (0x00000231) +#define TABLA_A_CDC_TX3_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX4_VOL_CTL_GAIN (0x00000239) +#define TABLA_A_CDC_TX4_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX5_VOL_CTL_GAIN (0x00000241) +#define TABLA_A_CDC_TX5_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX6_VOL_CTL_GAIN (0x00000249) +#define TABLA_A_CDC_TX6_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX7_VOL_CTL_GAIN (0x00000251) +#define TABLA_A_CDC_TX7_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX8_VOL_CTL_GAIN (0x00000259) +#define TABLA_A_CDC_TX8_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX9_VOL_CTL_GAIN (0x00000261) +#define TABLA_A_CDC_TX9_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX10_VOL_CTL_GAIN (0x00000269) +#define TABLA_A_CDC_TX10_VOL_CTL_GAIN__POR (0x00000000) +#define TABLA_A_CDC_TX1_VOL_CTL_CFG (0x00000222) +#define TABLA_A_CDC_TX1_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX2_VOL_CTL_CFG (0x0000022A) +#define TABLA_A_CDC_TX2_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX3_VOL_CTL_CFG (0x00000232) +#define TABLA_A_CDC_TX3_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX4_VOL_CTL_CFG (0x0000023A) +#define TABLA_A_CDC_TX4_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX5_VOL_CTL_CFG (0x00000242) +#define TABLA_A_CDC_TX5_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX6_VOL_CTL_CFG (0x0000024A) +#define TABLA_A_CDC_TX6_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX7_VOL_CTL_CFG (0x00000252) +#define TABLA_A_CDC_TX7_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX8_VOL_CTL_CFG (0x0000025A) +#define TABLA_A_CDC_TX8_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX9_VOL_CTL_CFG (0x00000262) +#define TABLA_A_CDC_TX9_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX10_VOL_CTL_CFG (0x0000026A) +#define TABLA_A_CDC_TX10_VOL_CTL_CFG__POR (0x00000000) +#define TABLA_A_CDC_TX1_MUX_CTL (0x00000223) +#define TABLA_A_CDC_TX1_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX2_MUX_CTL (0x0000022B) +#define TABLA_A_CDC_TX2_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX3_MUX_CTL (0x00000233) +#define TABLA_A_CDC_TX3_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX4_MUX_CTL (0x0000023B) +#define TABLA_A_CDC_TX4_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX5_MUX_CTL (0x00000243) +#define TABLA_A_CDC_TX5_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX6_MUX_CTL (0x0000024B) +#define TABLA_A_CDC_TX6_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX7_MUX_CTL (0x00000253) +#define TABLA_A_CDC_TX7_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX8_MUX_CTL (0x0000025B) +#define TABLA_A_CDC_TX8_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX9_MUX_CTL (0x00000263) +#define TABLA_A_CDC_TX9_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX10_MUX_CTL (0x0000026B) +#define TABLA_A_CDC_TX10_MUX_CTL__POR (0x00000008) +#define TABLA_A_CDC_TX1_CLK_FS_CTL (0x00000224) +#define TABLA_A_CDC_TX1_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX2_CLK_FS_CTL (0x0000022C) +#define TABLA_A_CDC_TX2_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX3_CLK_FS_CTL (0x00000234) +#define TABLA_A_CDC_TX3_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX4_CLK_FS_CTL (0x0000023C) +#define TABLA_A_CDC_TX4_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX5_CLK_FS_CTL (0x00000244) +#define TABLA_A_CDC_TX5_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX6_CLK_FS_CTL (0x0000024C) +#define TABLA_A_CDC_TX6_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX7_CLK_FS_CTL (0x00000254) +#define TABLA_A_CDC_TX7_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX8_CLK_FS_CTL (0x0000025C) +#define TABLA_A_CDC_TX8_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX9_CLK_FS_CTL (0x00000264) +#define TABLA_A_CDC_TX9_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX10_CLK_FS_CTL (0x0000026C) +#define TABLA_A_CDC_TX10_CLK_FS_CTL__POR (0x00000003) +#define TABLA_A_CDC_TX1_DMIC_CTL (0x00000225) +#define TABLA_A_CDC_TX1_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_TX2_DMIC_CTL (0x0000022D) +#define TABLA_A_CDC_TX2_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_TX3_DMIC_CTL (0x00000235) +#define TABLA_A_CDC_TX3_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_TX4_DMIC_CTL (0x0000023D) +#define TABLA_A_CDC_TX4_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_TX5_DMIC_CTL (0x00000245) +#define TABLA_A_CDC_TX5_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_TX6_DMIC_CTL (0x0000024D) +#define TABLA_A_CDC_TX6_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_TX7_DMIC_CTL (0x00000255) +#define TABLA_A_CDC_TX7_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_TX8_DMIC_CTL (0x0000025D) +#define TABLA_A_CDC_TX8_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_TX9_DMIC_CTL (0x00000265) +#define TABLA_A_CDC_TX9_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_TX10_DMIC_CTL (0x0000026D) +#define TABLA_A_CDC_TX10_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_SRC1_PDA_CFG (0x000002A0) +#define TABLA_A_CDC_SRC1_PDA_CFG__POR (0x00000000) +#define TABLA_A_CDC_SRC2_PDA_CFG (0x000002A8) +#define TABLA_A_CDC_SRC2_PDA_CFG__POR (0x00000000) +#define TABLA_A_CDC_SRC1_FS_CTL (0x000002A1) +#define TABLA_A_CDC_SRC1_FS_CTL__POR (0x0000001b) +#define TABLA_A_CDC_SRC2_FS_CTL (0x000002A9) +#define TABLA_A_CDC_SRC2_FS_CTL__POR (0x0000001b) +#define TABLA_A_CDC_RX1_B1_CTL (0x000002B0) +#define TABLA_A_CDC_RX1_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX2_B1_CTL (0x000002B8) +#define TABLA_A_CDC_RX2_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX3_B1_CTL (0x000002C0) +#define TABLA_A_CDC_RX3_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX4_B1_CTL (0x000002C8) +#define TABLA_A_CDC_RX4_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX5_B1_CTL (0x000002D0) +#define TABLA_A_CDC_RX5_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX6_B1_CTL (0x000002D8) +#define TABLA_A_CDC_RX6_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX7_B1_CTL (0x000002E0) +#define TABLA_A_CDC_RX7_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX1_B2_CTL (0x000002B1) +#define TABLA_A_CDC_RX1_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX2_B2_CTL (0x000002B9) +#define TABLA_A_CDC_RX2_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX3_B2_CTL (0x000002C1) +#define TABLA_A_CDC_RX3_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX4_B2_CTL (0x000002C9) +#define TABLA_A_CDC_RX4_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX5_B2_CTL (0x000002D1) +#define TABLA_A_CDC_RX5_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX6_B2_CTL (0x000002D9) +#define TABLA_A_CDC_RX6_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX7_B2_CTL (0x000002E1) +#define TABLA_A_CDC_RX7_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX1_B3_CTL (0x000002B2) +#define TABLA_A_CDC_RX1_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX2_B3_CTL (0x000002BA) +#define TABLA_A_CDC_RX2_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX3_B3_CTL (0x000002C2) +#define TABLA_A_CDC_RX3_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX4_B3_CTL (0x000002CA) +#define TABLA_A_CDC_RX4_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX5_B3_CTL (0x000002D2) +#define TABLA_A_CDC_RX5_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX6_B3_CTL (0x000002DA) +#define TABLA_A_CDC_RX6_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX7_B3_CTL (0x000002E2) +#define TABLA_A_CDC_RX7_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX1_B4_CTL (0x000002B3) +#define TABLA_A_CDC_RX1_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX2_B4_CTL (0x000002BB) +#define TABLA_A_CDC_RX2_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX3_B4_CTL (0x000002C3) +#define TABLA_A_CDC_RX3_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX4_B4_CTL (0x000002CB) +#define TABLA_A_CDC_RX4_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX5_B4_CTL (0x000002D3) +#define TABLA_A_CDC_RX5_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX6_B4_CTL (0x000002DB) +#define TABLA_A_CDC_RX6_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX7_B4_CTL (0x000002E3) +#define TABLA_A_CDC_RX7_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX1_B5_CTL (0x000002B4) +#define TABLA_A_CDC_RX1_B5_CTL__POR (0x00000060) +#define TABLA_A_CDC_RX2_B5_CTL (0x000002BC) +#define TABLA_A_CDC_RX2_B5_CTL__POR (0x00000060) +#define TABLA_A_CDC_RX3_B5_CTL (0x000002C4) +#define TABLA_A_CDC_RX3_B5_CTL__POR (0x00000060) +#define TABLA_A_CDC_RX4_B5_CTL (0x000002CC) +#define TABLA_A_CDC_RX4_B5_CTL__POR (0x00000060) +#define TABLA_A_CDC_RX5_B5_CTL (0x000002D4) +#define TABLA_A_CDC_RX5_B5_CTL__POR (0x00000060) +#define TABLA_A_CDC_RX6_B5_CTL (0x000002DC) +#define TABLA_A_CDC_RX6_B5_CTL__POR (0x00000060) +#define TABLA_A_CDC_RX7_B5_CTL (0x000002E4) +#define TABLA_A_CDC_RX7_B5_CTL__POR (0x00000060) +#define TABLA_A_CDC_RX1_B6_CTL (0x000002B5) +#define TABLA_A_CDC_RX1_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX2_B6_CTL (0x000002BD) +#define TABLA_A_CDC_RX2_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX3_B6_CTL (0x000002C5) +#define TABLA_A_CDC_RX3_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX4_B6_CTL (0x000002CD) +#define TABLA_A_CDC_RX4_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX5_B6_CTL (0x000002D5) +#define TABLA_A_CDC_RX5_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX6_B6_CTL (0x000002DD) +#define TABLA_A_CDC_RX6_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX7_B6_CTL (0x000002E5) +#define TABLA_A_CDC_RX7_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX1_VOL_CTL_B1_CTL (0x000002B6) +#define TABLA_A_CDC_RX1_VOL_CTL_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX2_VOL_CTL_B1_CTL (0x000002BE) +#define TABLA_A_CDC_RX2_VOL_CTL_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX3_VOL_CTL_B1_CTL (0x000002C6) +#define TABLA_A_CDC_RX3_VOL_CTL_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX4_VOL_CTL_B1_CTL (0x000002CE) +#define TABLA_A_CDC_RX4_VOL_CTL_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX5_VOL_CTL_B1_CTL (0x000002D6) +#define TABLA_A_CDC_RX5_VOL_CTL_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX6_VOL_CTL_B1_CTL (0x000002DE) +#define TABLA_A_CDC_RX6_VOL_CTL_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX7_VOL_CTL_B1_CTL (0x000002E6) +#define TABLA_A_CDC_RX7_VOL_CTL_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX1_VOL_CTL_B2_CTL (0x000002B7) +#define TABLA_A_CDC_RX1_VOL_CTL_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX2_VOL_CTL_B2_CTL (0x000002BF) +#define TABLA_A_CDC_RX2_VOL_CTL_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX3_VOL_CTL_B2_CTL (0x000002C7) +#define TABLA_A_CDC_RX3_VOL_CTL_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX4_VOL_CTL_B2_CTL (0x000002CF) +#define TABLA_A_CDC_RX4_VOL_CTL_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX5_VOL_CTL_B2_CTL (0x000002D7) +#define TABLA_A_CDC_RX5_VOL_CTL_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX6_VOL_CTL_B2_CTL (0x000002DF) +#define TABLA_A_CDC_RX6_VOL_CTL_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_RX7_VOL_CTL_B2_CTL (0x000002E7) +#define TABLA_A_CDC_RX7_VOL_CTL_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_RX_RESET_CTL (0x00000301) +#define TABLA_A_CDC_CLK_RX_RESET_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_TX_RESET_B1_CTL (0x00000302) +#define TABLA_A_CDC_CLK_TX_RESET_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_TX_RESET_B2_CTL (0x00000303) +#define TABLA_A_CDC_CLK_TX_RESET_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_DMIC_CTL (0x00000304) +#define TABLA_A_CDC_CLK_DMIC_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_RX_I2S_CTL (0x00000305) +#define TABLA_A_CDC_CLK_RX_I2S_CTL__POR (0x00000003) +#define TABLA_A_CDC_CLK_TX_I2S_CTL (0x00000306) +#define TABLA_A_CDC_CLK_TX_I2S_CTL__POR (0x00000003) +#define TABLA_A_CDC_CLK_OTHR_RESET_CTL (0x00000307) +#define TABLA_A_CDC_CLK_OTHR_RESET_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL (0x00000308) +#define TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL (0x00000309) +#define TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_OTHR_CTL (0x0000030A) +#define TABLA_A_CDC_CLK_OTHR_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_RDAC_CLK_EN_CTL (0x0000030B) +#define TABLA_A_CDC_CLK_RDAC_CLK_EN_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_RX_B1_CTL (0x0000030D) +#define TABLA_A_CDC_CLK_RX_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_RX_B2_CTL (0x0000030E) +#define TABLA_A_CDC_CLK_RX_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_MCLK_CTL (0x0000030F) +#define TABLA_A_CDC_CLK_MCLK_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_PDM_CTL (0x00000310) +#define TABLA_A_CDC_CLK_PDM_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLK_SD_CTL (0x00000311) +#define TABLA_A_CDC_CLK_SD_CTL__POR (0x00000000) +#define TABLA_A_CDC_CLSG_FREQ_THRESH_B1_CTL (0x00000320) +#define TABLA_A_CDC_CLSG_FREQ_THRESH_B1_CTL__POR (0x00000007) +#define TABLA_A_CDC_CLSG_FREQ_THRESH_B2_CTL (0x00000321) +#define TABLA_A_CDC_CLSG_FREQ_THRESH_B2_CTL__POR (0x00000013) +#define TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL (0x00000322) +#define TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL__POR (0x00000053) +#define TABLA_A_CDC_CLSG_FREQ_THRESH_B4_CTL (0x00000323) +#define TABLA_A_CDC_CLSG_FREQ_THRESH_B4_CTL__POR (0x0000007f) +#define TABLA_A_CDC_CLSG_GAIN_THRESH_CTL (0x00000324) +#define TABLA_A_CDC_CLSG_GAIN_THRESH_CTL__POR (0x00000026) +#define TABLA_A_CDC_CLSG_TIMER_B1_CFG (0x00000325) +#define TABLA_A_CDC_CLSG_TIMER_B1_CFG__POR (0x0000000a) +#define TABLA_A_CDC_CLSG_TIMER_B2_CFG (0x00000326) +#define TABLA_A_CDC_CLSG_TIMER_B2_CFG__POR (0x00000000) +#define TABLA_A_CDC_CLSG_CTL (0x00000327) +#define TABLA_A_CDC_CLSG_CTL__POR (0x00000013) +#define TABLA_A_CDC_IIR1_GAIN_B1_CTL (0x00000340) +#define TABLA_A_CDC_IIR1_GAIN_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_GAIN_B1_CTL (0x00000350) +#define TABLA_A_CDC_IIR2_GAIN_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_GAIN_B2_CTL (0x00000341) +#define TABLA_A_CDC_IIR1_GAIN_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_GAIN_B2_CTL (0x00000351) +#define TABLA_A_CDC_IIR2_GAIN_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_GAIN_B3_CTL (0x00000342) +#define TABLA_A_CDC_IIR1_GAIN_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_GAIN_B3_CTL (0x00000352) +#define TABLA_A_CDC_IIR2_GAIN_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_GAIN_B4_CTL (0x00000343) +#define TABLA_A_CDC_IIR1_GAIN_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_GAIN_B4_CTL (0x00000353) +#define TABLA_A_CDC_IIR2_GAIN_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_GAIN_B5_CTL (0x00000344) +#define TABLA_A_CDC_IIR1_GAIN_B5_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_GAIN_B5_CTL (0x00000354) +#define TABLA_A_CDC_IIR2_GAIN_B5_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_GAIN_B6_CTL (0x00000345) +#define TABLA_A_CDC_IIR1_GAIN_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_GAIN_B6_CTL (0x00000355) +#define TABLA_A_CDC_IIR2_GAIN_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_GAIN_B7_CTL (0x00000346) +#define TABLA_A_CDC_IIR1_GAIN_B7_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_GAIN_B7_CTL (0x00000356) +#define TABLA_A_CDC_IIR2_GAIN_B7_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_GAIN_B8_CTL (0x00000347) +#define TABLA_A_CDC_IIR1_GAIN_B8_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_GAIN_B8_CTL (0x00000357) +#define TABLA_A_CDC_IIR2_GAIN_B8_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_CTL (0x00000348) +#define TABLA_A_CDC_IIR1_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_CTL (0x00000358) +#define TABLA_A_CDC_IIR2_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_GAIN_TIMER_CTL (0x00000349) +#define TABLA_A_CDC_IIR1_GAIN_TIMER_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_GAIN_TIMER_CTL (0x00000359) +#define TABLA_A_CDC_IIR2_GAIN_TIMER_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_COEF_B1_CTL (0x0000034A) +#define TABLA_A_CDC_IIR1_COEF_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_COEF_B1_CTL (0x0000035A) +#define TABLA_A_CDC_IIR2_COEF_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_COEF_B2_CTL (0x0000034B) +#define TABLA_A_CDC_IIR1_COEF_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_COEF_B2_CTL (0x0000035B) +#define TABLA_A_CDC_IIR2_COEF_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_COEF_B3_CTL (0x0000034C) +#define TABLA_A_CDC_IIR1_COEF_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_COEF_B3_CTL (0x0000035C) +#define TABLA_A_CDC_IIR2_COEF_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_COEF_B4_CTL (0x0000034D) +#define TABLA_A_CDC_IIR1_COEF_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_COEF_B4_CTL (0x0000035D) +#define TABLA_A_CDC_IIR2_COEF_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR1_COEF_B5_CTL (0x0000034E) +#define TABLA_A_CDC_IIR1_COEF_B5_CTL__POR (0x00000000) +#define TABLA_A_CDC_IIR2_COEF_B5_CTL (0x0000035E) +#define TABLA_A_CDC_IIR2_COEF_B5_CTL__POR (0x00000000) +#define TABLA_A_CDC_TOP_GAIN_UPDATE (0x00000360) +#define TABLA_A_CDC_TOP_GAIN_UPDATE__POR (0x00000000) +#define TABLA_A_CDC_DEBUG_B1_CTL (0x00000368) +#define TABLA_A_CDC_DEBUG_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_DEBUG_B2_CTL (0x00000369) +#define TABLA_A_CDC_DEBUG_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_DEBUG_B3_CTL (0x0000036A) +#define TABLA_A_CDC_DEBUG_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_DEBUG_B4_CTL (0x0000036B) +#define TABLA_A_CDC_DEBUG_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_DEBUG_B5_CTL (0x0000036C) +#define TABLA_A_CDC_DEBUG_B5_CTL__POR (0x00000000) +#define TABLA_A_CDC_DEBUG_B6_CTL (0x0000036D) +#define TABLA_A_CDC_DEBUG_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX1_B1_CTL (0x00000380) +#define TABLA_A_CDC_CONN_RX1_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX1_B2_CTL (0x00000381) +#define TABLA_A_CDC_CONN_RX1_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX1_B3_CTL (0x00000382) +#define TABLA_A_CDC_CONN_RX1_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX2_B1_CTL (0x00000383) +#define TABLA_A_CDC_CONN_RX2_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX2_B2_CTL (0x00000384) +#define TABLA_A_CDC_CONN_RX2_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX2_B3_CTL (0x00000385) +#define TABLA_A_CDC_CONN_RX2_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX3_B1_CTL (0x00000386) +#define TABLA_A_CDC_CONN_RX3_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX3_B2_CTL (0x00000387) +#define TABLA_A_CDC_CONN_RX3_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX3_B3_CTL (0x00000388) +#define TABLA_A_CDC_CONN_RX3_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX4_B1_CTL (0x00000389) +#define TABLA_A_CDC_CONN_RX4_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX4_B2_CTL (0x0000038A) +#define TABLA_A_CDC_CONN_RX4_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX5_B1_CTL (0x0000038B) +#define TABLA_A_CDC_CONN_RX5_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX5_B2_CTL (0x0000038C) +#define TABLA_A_CDC_CONN_RX5_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX6_B1_CTL (0x0000038D) +#define TABLA_A_CDC_CONN_RX6_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX6_B2_CTL (0x0000038E) +#define TABLA_A_CDC_CONN_RX6_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX7_B1_CTL (0x0000038F) +#define TABLA_A_CDC_CONN_RX7_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX7_B2_CTL (0x00000390) +#define TABLA_A_CDC_CONN_RX7_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_B1_CTL (0x00000393) +#define TABLA_A_CDC_CONN_TX_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_B2_CTL (0x00000394) +#define TABLA_A_CDC_CONN_TX_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_B3_CTL (0x00000395) +#define TABLA_A_CDC_CONN_TX_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_B4_CTL (0x00000396) +#define TABLA_A_CDC_CONN_TX_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_EQ1_B1_CTL (0x00000397) +#define TABLA_A_CDC_CONN_EQ1_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_EQ1_B2_CTL (0x00000398) +#define TABLA_A_CDC_CONN_EQ1_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_EQ1_B3_CTL (0x00000399) +#define TABLA_A_CDC_CONN_EQ1_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_EQ1_B4_CTL (0x0000039A) +#define TABLA_A_CDC_CONN_EQ1_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_EQ2_B1_CTL (0x0000039B) +#define TABLA_A_CDC_CONN_EQ2_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_EQ2_B2_CTL (0x0000039C) +#define TABLA_A_CDC_CONN_EQ2_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_EQ2_B3_CTL (0x0000039D) +#define TABLA_A_CDC_CONN_EQ2_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_EQ2_B4_CTL (0x0000039E) +#define TABLA_A_CDC_CONN_EQ2_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_SRC1_B1_CTL (0x0000039F) +#define TABLA_A_CDC_CONN_SRC1_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_SRC1_B2_CTL (0x000003A0) +#define TABLA_A_CDC_CONN_SRC1_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_SRC2_B1_CTL (0x000003A1) +#define TABLA_A_CDC_CONN_SRC2_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_SRC2_B2_CTL (0x000003A2) +#define TABLA_A_CDC_CONN_SRC2_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B1_CTL (0x000003A3) +#define TABLA_A_CDC_CONN_TX_SB_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B2_CTL (0x000003A4) +#define TABLA_A_CDC_CONN_TX_SB_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B3_CTL (0x000003A5) +#define TABLA_A_CDC_CONN_TX_SB_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B4_CTL (0x000003A6) +#define TABLA_A_CDC_CONN_TX_SB_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B5_CTL (0x000003A7) +#define TABLA_A_CDC_CONN_TX_SB_B5_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B6_CTL (0x000003A8) +#define TABLA_A_CDC_CONN_TX_SB_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B7_CTL (0x000003A9) +#define TABLA_A_CDC_CONN_TX_SB_B7_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B8_CTL (0x000003AA) +#define TABLA_A_CDC_CONN_TX_SB_B8_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B9_CTL (0x000003AB) +#define TABLA_A_CDC_CONN_TX_SB_B9_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B10_CTL (0x000003AC) +#define TABLA_A_CDC_CONN_TX_SB_B10_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_TX_SB_B11_CTL (0x000003AD) +#define TABLA_A_CDC_CONN_TX_SB_B11_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX_SB_B1_CTL (0x000003AE) +#define TABLA_A_CDC_CONN_RX_SB_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_RX_SB_B2_CTL (0x000003AF) +#define TABLA_A_CDC_CONN_RX_SB_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_CLSG_CTL (0x000003B0) +#define TABLA_A_CDC_CONN_CLSG_CTL__POR (0x00000000) +#define TABLA_A_CDC_CONN_SPARE (0x000003B1) +#define TABLA_A_CDC_CONN_SPARE__POR (0x00000000) +#define TABLA_A_CDC_MBHC_EN_CTL (0x000003C0) +#define TABLA_A_CDC_MBHC_EN_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_FEATURE_B1_CFG (0x000003C1) +#define TABLA_A_CDC_MBHC_FEATURE_B1_CFG__POR (0x00000000) +#define TABLA_A_CDC_MBHC_FEATURE_B2_CFG (0x000003C2) +#define TABLA_A_CDC_MBHC_FEATURE_B2_CFG__POR (0x00000006) +#define TABLA_A_CDC_MBHC_TIMER_B1_CTL (0x000003C3) +#define TABLA_A_CDC_MBHC_TIMER_B1_CTL__POR (0x00000003) +#define TABLA_A_CDC_MBHC_TIMER_B2_CTL (0x000003C4) +#define TABLA_A_CDC_MBHC_TIMER_B2_CTL__POR (0x00000009) +#define TABLA_A_CDC_MBHC_TIMER_B3_CTL (0x000003C5) +#define TABLA_A_CDC_MBHC_TIMER_B3_CTL__POR (0x0000001e) +#define TABLA_A_CDC_MBHC_TIMER_B4_CTL (0x000003C6) +#define TABLA_A_CDC_MBHC_TIMER_B4_CTL__POR (0x00000045) +#define TABLA_A_CDC_MBHC_TIMER_B5_CTL (0x000003C7) +#define TABLA_A_CDC_MBHC_TIMER_B5_CTL__POR (0x00000004) +#define TABLA_A_CDC_MBHC_TIMER_B6_CTL (0x000003C8) +#define TABLA_A_CDC_MBHC_TIMER_B6_CTL__POR (0x00000078) +#define TABLA_A_CDC_MBHC_B1_STATUS (0x000003C9) +#define TABLA_A_CDC_MBHC_B1_STATUS__POR (0x00000000) +#define TABLA_A_CDC_MBHC_B2_STATUS (0x000003CA) +#define TABLA_A_CDC_MBHC_B2_STATUS__POR (0x00000000) +#define TABLA_A_CDC_MBHC_B3_STATUS (0x000003CB) +#define TABLA_A_CDC_MBHC_B3_STATUS__POR (0x00000000) +#define TABLA_A_CDC_MBHC_B4_STATUS (0x000003CC) +#define TABLA_A_CDC_MBHC_B4_STATUS__POR (0x00000000) +#define TABLA_A_CDC_MBHC_B5_STATUS (0x000003CD) +#define TABLA_A_CDC_MBHC_B5_STATUS__POR (0x00000000) +#define TABLA_A_CDC_MBHC_B1_CTL (0x000003CE) +#define TABLA_A_CDC_MBHC_B1_CTL__POR (0x000000c0) +#define TABLA_A_CDC_MBHC_B2_CTL (0x000003CF) +#define TABLA_A_CDC_MBHC_B2_CTL__POR (0x0000005d) +#define TABLA_A_CDC_MBHC_VOLT_B1_CTL (0x000003D0) +#define TABLA_A_CDC_MBHC_VOLT_B1_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_VOLT_B2_CTL (0x000003D1) +#define TABLA_A_CDC_MBHC_VOLT_B2_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_VOLT_B3_CTL (0x000003D2) +#define TABLA_A_CDC_MBHC_VOLT_B3_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_VOLT_B4_CTL (0x000003D3) +#define TABLA_A_CDC_MBHC_VOLT_B4_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_VOLT_B5_CTL (0x000003D4) +#define TABLA_A_CDC_MBHC_VOLT_B5_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_VOLT_B6_CTL (0x000003D5) +#define TABLA_A_CDC_MBHC_VOLT_B6_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_VOLT_B7_CTL (0x000003D6) +#define TABLA_A_CDC_MBHC_VOLT_B7_CTL__POR (0x000000ff) +#define TABLA_A_CDC_MBHC_VOLT_B8_CTL (0x000003D7) +#define TABLA_A_CDC_MBHC_VOLT_B8_CTL__POR (0x00000007) +#define TABLA_A_CDC_MBHC_VOLT_B9_CTL (0x000003D8) +#define TABLA_A_CDC_MBHC_VOLT_B9_CTL__POR (0x000000ff) +#define TABLA_A_CDC_MBHC_VOLT_B10_CTL (0x000003D9) +#define TABLA_A_CDC_MBHC_VOLT_B10_CTL__POR (0x0000007f) +#define TABLA_A_CDC_MBHC_VOLT_B11_CTL (0x000003DA) +#define TABLA_A_CDC_MBHC_VOLT_B11_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_VOLT_B12_CTL (0x000003DB) +#define TABLA_A_CDC_MBHC_VOLT_B12_CTL__POR (0x00000080) +#define TABLA_A_CDC_MBHC_CLK_CTL (0x000003DC) +#define TABLA_A_CDC_MBHC_CLK_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_INT_CTL (0x000003DD) +#define TABLA_A_CDC_MBHC_INT_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_DEBUG_CTL (0x000003DE) +#define TABLA_A_CDC_MBHC_DEBUG_CTL__POR (0x00000000) +#define TABLA_A_CDC_MBHC_SPARE (0x000003DF) +#define TABLA_A_CDC_MBHC_SPARE__POR (0x00000000) + + +/* SLIMBUS Slave Registers */ +#define TABLA_SLIM_PGD_PORT_INT_EN0 (0x30) +#define TABLA_SLIM_PGD_PORT_INT_STATUS0 (0x34) +#define TABLA_SLIM_PGD_PORT_INT_CLR0 (0x38) +#define TABLA_SLIM_PGD_PORT_INT_SOURCE0 (0x60) + +#endif diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 1584b5236bb..fd62a22777e 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -221,7 +221,6 @@ struct mmc_host { struct work_struct clk_gate_work; /* delayed clock gate */ unsigned int clk_old; /* old clock value cache */ spinlock_t clk_lock; /* lock for clk fields */ - struct mutex clk_gate_mutex; /* mutex for clock gating */ #endif /* host specific block data */ @@ -258,6 +257,7 @@ struct mmc_host { wait_queue_head_t wq; struct task_struct *claimer; /* task that has host claimed */ + struct task_struct *suspend_task; int claim_cnt; /* "claim" nesting count */ struct delayed_work detect; @@ -294,6 +294,20 @@ struct mmc_host { } embedded_sdio_data; #endif +#ifdef CONFIG_MMC_PERF_PROFILING + struct { + + unsigned long rbytes_mmcq; /* Rd bytes MMC queue */ + unsigned long wbytes_mmcq; /* Wr bytes MMC queue */ + unsigned long rbytes_drv; /* Rd bytes MMC Host */ + unsigned long wbytes_drv; /* Wr bytes MMC Host */ + ktime_t rtime_mmcq; /* Rd time MMC queue */ + ktime_t wtime_mmcq; /* Wr time MMC queue */ + ktime_t rtime_drv; /* Rd time MMC Host */ + ktime_t wtime_drv; /* Wr time MMC Host */ + ktime_t start; + } perf; +#endif unsigned long private[0] ____cacheline_aligned; }; @@ -376,7 +390,12 @@ int mmc_card_can_sleep(struct mmc_host *host); int mmc_host_enable(struct mmc_host *host); int mmc_host_disable(struct mmc_host *host); int mmc_host_lazy_disable(struct mmc_host *host); -int mmc_pm_notify(struct notifier_block *notify_block, unsigned long, void *); +#ifdef CONFIG_PM +int mmc_pm_notify(struct notifier_block *notify_block, unsigned long mode, + void *unused); +#else +#define mmc_pm_notify NULL +#endif static inline void mmc_set_disable_delay(struct mmc_host *host, unsigned int disable_delay) diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index ac26a685cca..e10b2786d22 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -211,6 +211,7 @@ struct _mmc_csd { * OCR bits are mostly in host.h */ #define MMC_CARD_BUSY 0x80000000 /* Card Power up status bit */ +#define MMC_CARD_SECTOR_ADDR 0x40000000 /* Card supports sectors */ /* * Card Command Classes (CCC) diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index 245cdacee54..e376b376f7f 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -96,6 +96,7 @@ #define SDIO_BUS_WIDTH_1BIT 0x00 #define SDIO_BUS_WIDTH_4BIT 0x02 +#define SDIO_BUS_WIDTH_8BIT 0x03 #define SDIO_BUS_ECSI 0x20 /* Enable continuous SPI interrupt */ #define SDIO_BUS_SCSI 0x40 /* Support continuous SPI interrupt */ diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h index 9f7c3ebcbba..26360cced2c 100644 --- a/include/linux/mmzone.h +++ b/include/linux/mmzone.h @@ -454,6 +454,12 @@ static inline int zone_is_oom_locked(const struct zone *zone) return test_bit(ZONE_OOM_LOCKED, &zone->flags); } +#ifdef CONFIG_SMP +unsigned long zone_nr_free_pages(struct zone *zone); +#else +#define zone_nr_free_pages(zone) zone_page_state(zone, NR_FREE_PAGES) +#endif /* CONFIG_SMP */ + /* * The "priority" of VM scanning is how much of the queues we will scan in one * go. A value of 12 for DEF_PRIORITY implies that we will scan 1/4096th of the @@ -1089,7 +1095,10 @@ static inline int pfn_present(unsigned long pfn) #define pfn_to_nid(pfn) (0) #endif +#ifndef early_pfn_valid #define early_pfn_valid(pfn) pfn_valid(pfn) +#endif + void sparse_init(void); #else #define sparse_init() do {} while (0) diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index ae28e93fd07..18997d7713e 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -427,6 +427,15 @@ struct spi_device_id { __attribute__((aligned(sizeof(kernel_ulong_t)))); }; +#define SLIMBUS_NAME_SIZE 32 +#define SLIMBUS_MODULE_PREFIX "slim:" + +struct slim_device_id { + char name[SLIMBUS_NAME_SIZE]; + kernel_ulong_t driver_data /* Data private to the driver */ + __attribute__((aligned(sizeof(kernel_ulong_t)))); +}; + /* dmi */ enum dmi_field { DMI_NONE, diff --git a/include/linux/msm-charger.h b/include/linux/msm-charger.h new file mode 100644 index 00000000000..1e4d479a4f6 --- /dev/null +++ b/include/linux/msm-charger.h @@ -0,0 +1,136 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __MSM_CHARGER_H__ +#define __MSM_CHARGER_H__ + +#include + +enum { + CHG_TYPE_USB, + CHG_TYPE_AC +}; + +enum msm_hardware_charger_event { + CHG_INSERTED_EVENT, + CHG_ENUMERATED_EVENT, + CHG_REMOVED_EVENT, + CHG_DONE_EVENT, + CHG_BATT_BEGIN_FAST_CHARGING, + CHG_BATT_CHG_RESUME, + CHG_BATT_TEMP_OUTOFRANGE, + CHG_BATT_TEMP_INRANGE, + CHG_BATT_INSERTED, + CHG_BATT_REMOVED, + CHG_BATT_STATUS_CHANGE, + CHG_BATT_NEEDS_RECHARGING, +}; + +/** + * enum hardware_charger_state + * @CHG_ABSENT_STATE: charger cable is unplugged + * @CHG_PRESENT_STATE: charger cable is plugged but charge current isnt drawn + * @CHG_READY_STATE: charger cable is plugged and kernel knows how much current + * it can draw + * @CHG_CHARGING_STATE: charger cable is plugged and current is drawn for + * charging + */ +enum msm_hardware_charger_state { + CHG_ABSENT_STATE, + CHG_PRESENT_STATE, + CHG_READY_STATE, + CHG_CHARGING_STATE, +}; + +struct msm_hardware_charger { + int type; + int rating; + const char *name; + int (*start_charging) (struct msm_hardware_charger *hw_chg, + int chg_voltage, int chg_current); + int (*stop_charging) (struct msm_hardware_charger *hw_chg); + int (*charging_switched) (struct msm_hardware_charger *hw_chg); + + void *charger_private; /* used by the msm_charger.c */ +}; + +struct msm_battery_gauge { + int (*get_battery_mvolts) (void); + int (*get_battery_temperature) (void); + int (*is_battery_present) (void); + int (*is_battery_temp_within_range) (void); + int (*is_battery_id_valid) (void); + int (*get_battery_status)(void); + int (*get_batt_remaining_capacity) (void); + int (*monitor_for_recharging) (void); +}; +/** + * struct msm_charger_platform_data + * @safety_time: max charging time in minutes + * @update_time: how often the userland be updated of the charging progress + * @max_voltage: the max voltage the battery should be charged upto + * @min_voltage: the voltage where charging method switches from trickle to fast + * @get_batt_capacity_percent: a board specific function to return battery + * capacity. Can be null - a default one will be used + */ +struct msm_charger_platform_data { + unsigned int safety_time; + unsigned int update_time; + unsigned int max_voltage; + unsigned int min_voltage; + unsigned int (*get_batt_capacity_percent) (void); +}; + +typedef void (*notify_vbus_state) (int); +#if defined(CONFIG_BATTERY_MSM8X60) || defined(CONFIG_BATTERY_MSM8X60_MODULE) +void msm_battery_gauge_register(struct msm_battery_gauge *batt_gauge); +void msm_battery_gauge_unregister(struct msm_battery_gauge *batt_gauge); +int msm_charger_register(struct msm_hardware_charger *hw_chg); +int msm_charger_unregister(struct msm_hardware_charger *hw_chg); +int msm_charger_notify_event(struct msm_hardware_charger *hw_chg, + enum msm_hardware_charger_event event); +void msm_charger_vbus_draw(unsigned int mA); + +int msm_charger_register_vbus_sn(void (*callback)(int)); +void msm_charger_unregister_vbus_sn(void (*callback)(int)); +#else +static inline void msm_battery_gauge_register(struct msm_battery_gauge *gauge) +{ +} +static inline void msm_battery_gauge_unregister(struct msm_battery_gauge *gauge) +{ +} +static inline int msm_charger_register(struct msm_hardware_charger *hw_chg) +{ + return -ENXIO; +} +static inline int msm_charger_unregister(struct msm_hardware_charger *hw_chg) +{ + return -ENXIO; +} +static inline int msm_charger_notify_event(struct msm_hardware_charger *hw_chg, + enum msm_hardware_charger_event event) +{ + return -ENXIO; +} +static inline void msm_charger_vbus_draw(unsigned int mA) +{ +} +static inline int msm_charger_register_vbus_sn(void (*callback)(int)) +{ + return -ENXIO; +} +static inline void msm_charger_unregister_vbus_sn(void (*callback)(int)) +{ +} +#endif +#endif /* __MSM_CHARGER_H__ */ diff --git a/include/linux/msm_adc.h b/include/linux/msm_adc.h new file mode 100644 index 00000000000..b8ab5990cee --- /dev/null +++ b/include/linux/msm_adc.h @@ -0,0 +1,347 @@ +#ifndef __MSM_ADC_H +#define __MSM_ADC_H + +#include + +#define MSM_ADC_MAX_CHAN_STR 64 + +/* must be <= to the max buffer size in the modem implementation */ +#define MSM_ADC_DEV_MAX_INFLIGHT 9 + +#define MSM_ADC_IOCTL_CODE 0x90 + +struct msm_adc_conversion { + /* hwmon channel number - this is not equivalent to the DAL chan */ + uint32_t chan; + /* returned result in ms */ + int result; +}; + +struct adc_chan_result { + /* The channel number of the requesting/requested conversion */ + uint32_t chan; + /* The pre-calibrated digital output of a given ADC relative to the + ADC reference */ + int32_t adc_code; + /* in units specific for a given ADC; most ADC uses reference voltage + * but some ADC uses reference current. This measurement here is + * a number relative to a reference of a given ADC */ + int64_t measurement; + /* The data meaningful for each individual channel whether it is + * voltage, current, temperature, etc. */ + int64_t physical; +}; + +/* + * Issue a blocking adc conversion request. Once the call returns, the data + * can be found in the 'physical' field of adc_chan_result. This call will + * return ENODATA if there is an invalid result returned by the modem driver. + */ +#define MSM_ADC_REQUEST _IOWR(MSM_ADC_IOCTL_CODE, 1, \ + struct adc_chan_result) + +/* + * Issue a non-blocking adc conversion request. The results from this + * request can be obtained by calling AIO_READ once the transfer is + * completed. To verify completion, the blocking call AIO_POLL can be used. + * If there are no slot resources, this call will return an error with errno + * set to EWOULDBLOCK. + */ +#define MSM_ADC_AIO_REQUEST _IOWR(MSM_ADC_IOCTL_CODE, 2, \ + struct adc_chan_result) + +/* + * Same non-blocking semantics as AIO_REQUEST, except this call will block + * if there are no available slot resources. This call can fail with errno + * set to EDEADLK if there are no resources and the file descriptor in question + * has outstanding conversion requests already. This is done so the client + * does not block on resources that can only be freed by reading the results -- + * effectively deadlocking the system. In this case, the client must read + * pending results before proceeding to free up resources. + */ +#define MSM_ADC_AIO_REQUEST_BLOCK_RES _IOWR(MSM_ADC_IOCTL_CODE, 3, \ + struct adc_chan_result) + +/* + * Returns the number of pending results that are associated with a particular + * file descriptor. If there are no pending results, this call will block until + * there is at least one. If there are no requests queued at all on this file + * descriptor, this call will fail with EDEADLK. This is to prevent deadlock in + * a single-threaded scenario where POLL would never return. + */ +#define MSM_ADC_AIO_POLL _IOR(MSM_ADC_IOCTL_CODE, 4, \ + uint32_t) + +#define MSM_ADC_FLUID_INIT _IOR(MSM_ADC_IOCTL_CODE, 5, \ + uint32_t) + +#define MSM_ADC_FLUID_DEINIT _IOR(MSM_ADC_IOCTL_CODE, 6, \ + uint32_t) + +struct msm_adc_aio_result { + uint32_t chan; + int result; +}; + +/* + * Read the results from an AIO / non-blocking conversion request. AIO_POLL + * should be used before using this command to verify how many pending requests + * are available for the file descriptor. This call will fail with errno set to + * ENOMSG if there are no pending messages to be read at the time of the call. + * The call will return ENODATA if there is an invalid result returned by the + * modem driver. + */ +#define MSM_ADC_AIO_READ _IOR(MSM_ADC_IOCTL_CODE, 5, \ + struct adc_chan_result) + +struct msm_adc_lookup { + /* channel name (input) */ + char name[MSM_ADC_MAX_CHAN_STR]; + /* local channel index (output) */ + uint32_t chan_idx; +}; + +/* + * Look up a channel name and get back an index that can be used + * as a parameter to the conversion request commands. + */ +#define MSM_ADC_LOOKUP _IOWR(MSM_ADC_IOCTL_CODE, 6, \ + struct msm_adc_lookup) + + +#ifdef __KERNEL__ +#define MSM_ADC_MAX_NUM_DEVS 3 + +enum { + ADC_CONFIG_TYPE1, + ADC_CONFIG_TYPE2, + ADC_CONFIG_NONE = 0xffffffff +}; + +enum { + ADC_CALIB_CONFIG_TYPE1, + ADC_CALIB_CONFIG_TYPE2, + ADC_CALIB_CONFIG_TYPE3, + ADC_CALIB_CONFIG_TYPE4, + ADC_CALIB_CONFIG_TYPE5, + ADC_CALIB_CONFIG_TYPE6, + ADC_CALIB_CONFIG_TYPE7, + ADC_CALIB_CONFIG_NONE = 0xffffffff +}; + +enum { + /* CHAN_PATH_TYPEn is specific for each ADC driver + and can be used however way it wants*/ + CHAN_PATH_TYPE1, + CHAN_PATH_TYPE2, + CHAN_PATH_TYPE3, + CHAN_PATH_TYPE4, + CHAN_PATH_TYPE5, + CHAN_PATH_TYPE6, + CHAN_PATH_TYPE7, + CHAN_PATH_TYPE8, + CHAN_PATH_TYPE9, + CHAN_PATH_TYPE10, + CHAN_PATH_TYPE11, + CHAN_PATH_TYPE12, + CHAN_PATH_TYPE13, + CHAN_PATH_TYPE14, + CHAN_PATH_TYPE15, + CHAN_PATH_TYPE16, + /* A given channel connects directly to the ADC */ + CHAN_PATH_TYPE_NONE = 0xffffffff +}; + +#define CHANNEL_ADC_BATT_ID 0 +#define CHANNEL_ADC_BATT_THERM 1 +#define CHANNEL_ADC_BATT_AMON 2 +#define CHANNEL_ADC_VBATT 3 +#define CHANNEL_ADC_VCOIN 4 +#define CHANNEL_ADC_VCHG 5 +#define CHANNEL_ADC_CHG_MONITOR 6 +#define CHANNEL_ADC_VPH_PWR 7 +#define CHANNEL_ADC_USB_VBUS 8 +#define CHANNEL_ADC_DIE_TEMP 9 +#define CHANNEL_ADC_DIE_TEMP_4K 0xa +#define CHANNEL_ADC_XOTHERM 0xb +#define CHANNEL_ADC_XOTHERM_4K 0xc +#define CHANNEL_ADC_HDSET 0xd +#define CHANNEL_ADC_MSM_THERM 0xe +#define CHANNEL_ADC_625_REF 0xf +#define CHANNEL_ADC_1250_REF 0x10 +#define CHANNEL_ADC_325_REF 0x11 +#define CHANNEL_ADC_FSM_THERM 0x12 +#define CHANNEL_ADC_PA_THERM 0x13 + +enum { + CALIB_STARTED, + CALIB_NOT_REQUIRED = 0xffffffff, +}; + +struct linear_graph { + int32_t offset; + int32_t dy; /* Slope numerator */ + int32_t dx; /* Slope denominator */ +}; + +struct adc_map_pt { + int32_t x; + int32_t y; +}; + +struct adc_properties { + uint32_t adc_reference; /* milli-voltage for this adc */ + uint32_t bitresolution; + bool bipolar; + uint32_t conversiontime; +}; + +struct chan_properties { + uint32_t gain_numerator; + uint32_t gain_denominator; + struct linear_graph *adc_graph; +/* this maybe the same as adc_properties.ConversionTime + if channel does not change the adc properties */ + uint32_t chan_conv_time; +}; + +struct msm_adc_channels { + char *name; + uint32_t channel_name; + uint32_t adc_dev_instance; + struct adc_access_fn *adc_access_fn; + uint32_t chan_path_type; + uint32_t adc_config_type; + uint32_t adc_calib_type; + int32_t (*chan_processor)(int32_t, const struct adc_properties *, + const struct chan_properties *, struct adc_chan_result *); + +}; + +struct msm_adc_platform_data { + struct msm_adc_channels *channel; + uint32_t num_chan_supported; + uint32_t num_adc; + uint32_t chan_per_adc; + char **dev_names; + uint32_t target_hw; + uint32_t gpio_config; + u32 (*adc_gpio_enable) (int); + u32 (*adc_gpio_disable) (int); + u32 (*adc_fluid_enable) (void); + u32 (*adc_fluid_disable) (void); +}; + +enum hw_type { + MSM_7x30, + MSM_8x60, + FSM_9xxx, +}; + +enum epm_gpio_config { + MPROC_CONFIG, + APROC_CONFIG +}; + +enum adc_request { + START_OF_CONV, + END_OF_CONV, + START_OF_CALIBRATION, + END_OF_CALIBRATION, +}; + +struct adc_dev_spec { + uint32_t hwmon_dev_idx; + struct dal_dev_spec { + uint32_t dev_idx; + uint32_t chan_idx; + } dal; +}; + +struct dal_conv_request { + struct dal_dev_spec target; + void *cb_h; +}; + +struct dal_adc_result { + uint32_t status; + uint32_t token; + uint32_t dev_idx; + uint32_t chan_idx; + int physical; + uint32_t percent; + uint32_t microvolts; + uint32_t reserved; +}; + +struct dal_conv_slot { + void *cb_h; + struct dal_adc_result result; + struct completion comp; + struct list_head list; + uint32_t idx; + uint32_t chan_idx; + bool blocking; + struct msm_client_data *client; +}; + +struct dal_translation { + uint32_t dal_dev_idx; + uint32_t hwmon_dev_idx; + uint32_t hwmon_start; + uint32_t hwmon_end; +}; + +struct msm_client_data { + struct list_head complete_list; + bool online; + int32_t adc_chan; + uint32_t num_complete; + uint32_t num_outstanding; + wait_queue_head_t data_wait; + wait_queue_head_t outst_wait; + struct mutex lock; +}; + +struct adc_conv_slot { + void *cb_h; + union { + struct adc_chan_result result; + struct dal_adc_result dal_result; + } conv; + struct completion comp; + struct completion *compk; + struct list_head list; + uint32_t idx; + enum adc_request adc_request; + bool blocking; + struct msm_client_data *client; + struct work_struct work; + struct chan_properties chan_properties; + uint32_t chan_path; + uint32_t chan_adc_config; + uint32_t chan_adc_calib; +}; + +struct adc_access_fn { + int32_t (*adc_select_chan_and_start_conv)(uint32_t, + struct adc_conv_slot*); + int32_t (*adc_read_adc_code)(uint32_t dev_instance, int32_t *data); + struct adc_properties *(*adc_get_properties)(uint32_t dev_instance); + void (*adc_slot_request)(uint32_t dev_instance, + struct adc_conv_slot **); + void (*adc_restore_slot)(uint32_t dev_instance, + struct adc_conv_slot *slot); + int32_t (*adc_calibrate)(uint32_t dev_instance, struct adc_conv_slot*, + int *); +}; + +void msm_adc_wq_work(struct work_struct *work); +void msm_adc_conv_cb(void *context, u32 param, void *evt_buf, u32 len); +int32_t adc_channel_open(uint32_t channel, void **h); +int32_t adc_channel_close(void *h); +int32_t adc_channel_request_conv(void *h, struct completion *conv_complete_evt); +int32_t adc_channel_read_result(void *h, struct adc_chan_result *chan_result); +int32_t adc_calib_request(void *h, struct completion *calib_complete_evt); +#endif +#endif /* __MSM_ADC_H */ diff --git a/include/linux/msm_adsp.h b/include/linux/msm_adsp.h new file mode 100644 index 00000000000..ca23ad8dd74 --- /dev/null +++ b/include/linux/msm_adsp.h @@ -0,0 +1,78 @@ +/* include/linux/msm_adsp.h + * + * Copyright (C) 2007 Google, Inc. + * Author: Iliyan Malchev + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __LINUX_MSM_ADSP_H +#define __LINUX_MSM_ADSP_H + +#include +#include + +#define ADSP_IOCTL_MAGIC 'q' + +/* ADSP_IOCTL_WRITE_COMMAND */ +struct adsp_command_t { + uint16_t queue; + uint32_t len; /* bytes */ + uint8_t *data; +}; + +/* ADSP_IOCTL_GET_EVENT */ +struct adsp_event_t { + uint16_t type; /* 1 == event (RPC), 0 == message (adsp) */ + uint32_t timeout_ms; /* -1 for infinite, 0 for immediate return */ + uint16_t msg_id; + uint16_t flags; /* 1 == 16--bit event, 0 == 32-bit event */ + uint32_t len; /* size in, number of bytes out */ + uint8_t *data; +}; + +#define ADSP_IOCTL_ENABLE \ + _IOR(ADSP_IOCTL_MAGIC, 1, unsigned) + +#define ADSP_IOCTL_DISABLE \ + _IOR(ADSP_IOCTL_MAGIC, 2, unsigned) + +#define ADSP_IOCTL_DISABLE_ACK \ + _IOR(ADSP_IOCTL_MAGIC, 3, unsigned) + +#define ADSP_IOCTL_WRITE_COMMAND \ + _IOR(ADSP_IOCTL_MAGIC, 4, struct adsp_command_t *) + +#define ADSP_IOCTL_GET_EVENT \ + _IOWR(ADSP_IOCTL_MAGIC, 5, struct adsp_event_data_t *) + +#define ADSP_IOCTL_SET_CLKRATE \ + _IOR(ADSP_IOCTL_MAGIC, 6, unsigned) + +#define ADSP_IOCTL_DISABLE_EVENT_RSP \ + _IOR(ADSP_IOCTL_MAGIC, 10, unsigned) + +#define ADSP_IOCTL_REGISTER_PMEM \ + _IOW(ADSP_IOCTL_MAGIC, 13, unsigned) + +#define ADSP_IOCTL_UNREGISTER_PMEM \ + _IOW(ADSP_IOCTL_MAGIC, 14, unsigned) + +/* Cause any further GET_EVENT ioctls to fail (-ENODEV) + * until the device is closed and reopened. Useful for + * terminating event dispatch threads + */ +#define ADSP_IOCTL_ABORT_EVENT_READ \ + _IOW(ADSP_IOCTL_MAGIC, 15, unsigned) + +#define ADSP_IOCTL_LINK_TASK \ + _IOW(ADSP_IOCTL_MAGIC, 16, unsigned) + +#endif diff --git a/include/linux/msm_audio.h b/include/linux/msm_audio.h new file mode 100644 index 00000000000..30d74ce8098 --- /dev/null +++ b/include/linux/msm_audio.h @@ -0,0 +1,353 @@ +/* include/linux/msm_audio.h + * + * Copyright (C) 2008 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __LINUX_MSM_AUDIO_H +#define __LINUX_MSM_AUDIO_H + +#include +#include + +/* PCM Audio */ + +#define AUDIO_IOCTL_MAGIC 'a' + +#define AUDIO_START _IOW(AUDIO_IOCTL_MAGIC, 0, unsigned) +#define AUDIO_STOP _IOW(AUDIO_IOCTL_MAGIC, 1, unsigned) +#define AUDIO_FLUSH _IOW(AUDIO_IOCTL_MAGIC, 2, unsigned) +#define AUDIO_GET_CONFIG _IOR(AUDIO_IOCTL_MAGIC, 3, unsigned) +#define AUDIO_SET_CONFIG _IOW(AUDIO_IOCTL_MAGIC, 4, unsigned) +#define AUDIO_GET_STATS _IOR(AUDIO_IOCTL_MAGIC, 5, unsigned) +#define AUDIO_ENABLE_AUDPP _IOW(AUDIO_IOCTL_MAGIC, 6, unsigned) +#define AUDIO_SET_ADRC _IOW(AUDIO_IOCTL_MAGIC, 7, unsigned) +#define AUDIO_SET_EQ _IOW(AUDIO_IOCTL_MAGIC, 8, unsigned) +#define AUDIO_SET_RX_IIR _IOW(AUDIO_IOCTL_MAGIC, 9, unsigned) +#define AUDIO_SET_VOLUME _IOW(AUDIO_IOCTL_MAGIC, 10, unsigned) +#define AUDIO_PAUSE _IOW(AUDIO_IOCTL_MAGIC, 11, unsigned) +#define AUDIO_PLAY_DTMF _IOW(AUDIO_IOCTL_MAGIC, 12, unsigned) +#define AUDIO_GET_EVENT _IOR(AUDIO_IOCTL_MAGIC, 13, unsigned) +#define AUDIO_ABORT_GET_EVENT _IOW(AUDIO_IOCTL_MAGIC, 14, unsigned) +#define AUDIO_REGISTER_PMEM _IOW(AUDIO_IOCTL_MAGIC, 15, unsigned) +#define AUDIO_DEREGISTER_PMEM _IOW(AUDIO_IOCTL_MAGIC, 16, unsigned) +#define AUDIO_ASYNC_WRITE _IOW(AUDIO_IOCTL_MAGIC, 17, unsigned) +#define AUDIO_ASYNC_READ _IOW(AUDIO_IOCTL_MAGIC, 18, unsigned) +#define AUDIO_SET_INCALL _IOW(AUDIO_IOCTL_MAGIC, 19, struct msm_voicerec_mode) +#define AUDIO_GET_NUM_SND_DEVICE _IOR(AUDIO_IOCTL_MAGIC, 20, unsigned) +#define AUDIO_GET_SND_DEVICES _IOWR(AUDIO_IOCTL_MAGIC, 21, \ + struct msm_snd_device_list) +#define AUDIO_ENABLE_SND_DEVICE _IOW(AUDIO_IOCTL_MAGIC, 22, unsigned) +#define AUDIO_DISABLE_SND_DEVICE _IOW(AUDIO_IOCTL_MAGIC, 23, unsigned) +#define AUDIO_ROUTE_STREAM _IOW(AUDIO_IOCTL_MAGIC, 24, \ + struct msm_audio_route_config) +#define AUDIO_GET_PCM_CONFIG _IOR(AUDIO_IOCTL_MAGIC, 30, unsigned) +#define AUDIO_SET_PCM_CONFIG _IOW(AUDIO_IOCTL_MAGIC, 31, unsigned) +#define AUDIO_SWITCH_DEVICE _IOW(AUDIO_IOCTL_MAGIC, 32, unsigned) +#define AUDIO_SET_MUTE _IOW(AUDIO_IOCTL_MAGIC, 33, unsigned) +#define AUDIO_UPDATE_ACDB _IOW(AUDIO_IOCTL_MAGIC, 34, unsigned) +#define AUDIO_START_VOICE _IOW(AUDIO_IOCTL_MAGIC, 35, unsigned) +#define AUDIO_STOP_VOICE _IOW(AUDIO_IOCTL_MAGIC, 36, unsigned) +#define AUDIO_REINIT_ACDB _IOW(AUDIO_IOCTL_MAGIC, 39, unsigned) +#define AUDIO_OUTPORT_FLUSH _IOW(AUDIO_IOCTL_MAGIC, 40, unsigned short) +#define AUDIO_SET_ERR_THRESHOLD_VALUE _IOW(AUDIO_IOCTL_MAGIC, 41, \ + unsigned short) +#define AUDIO_GET_BITSTREAM_ERROR_INFO _IOR(AUDIO_IOCTL_MAGIC, 42, \ + struct msm_audio_bitstream_error_info) +/* Qualcomm extensions */ +#define AUDIO_SET_STREAM_CONFIG _IOW(AUDIO_IOCTL_MAGIC, 80, \ + struct msm_audio_stream_config) +#define AUDIO_GET_STREAM_CONFIG _IOR(AUDIO_IOCTL_MAGIC, 81, \ + struct msm_audio_stream_config) +#define AUDIO_GET_SESSION_ID _IOR(AUDIO_IOCTL_MAGIC, 82, unsigned short) +#define AUDIO_GET_STREAM_INFO _IOR(AUDIO_IOCTL_MAGIC, 83, \ + struct msm_audio_bitstream_info) +#define AUDIO_SET_PAN _IOW(AUDIO_IOCTL_MAGIC, 84, unsigned) +#define AUDIO_SET_QCONCERT_PLUS _IOW(AUDIO_IOCTL_MAGIC, 85, unsigned) +#define AUDIO_SET_MBADRC _IOW(AUDIO_IOCTL_MAGIC, 86, unsigned) +#define AUDIO_SET_VOLUME_PATH _IOW(AUDIO_IOCTL_MAGIC, 87, \ + struct msm_vol_info) +#define AUDIO_SET_MAX_VOL_ALL _IOW(AUDIO_IOCTL_MAGIC, 88, unsigned) +#define AUDIO_ENABLE_AUDPRE _IOW(AUDIO_IOCTL_MAGIC, 89, unsigned) +#define AUDIO_SET_AGC _IOW(AUDIO_IOCTL_MAGIC, 90, unsigned) +#define AUDIO_SET_NS _IOW(AUDIO_IOCTL_MAGIC, 91, unsigned) +#define AUDIO_SET_TX_IIR _IOW(AUDIO_IOCTL_MAGIC, 92, unsigned) +#define AUDIO_GET_BUF_CFG _IOW(AUDIO_IOCTL_MAGIC, 93, \ + struct msm_audio_buf_cfg) +#define AUDIO_SET_BUF_CFG _IOW(AUDIO_IOCTL_MAGIC, 94, \ + struct msm_audio_buf_cfg) +#define AUDIO_SET_ACDB_BLK _IOW(AUDIO_IOCTL_MAGIC, 95, \ + struct msm_acdb_cmd_device) +#define AUDIO_GET_ACDB_BLK _IOW(AUDIO_IOCTL_MAGIC, 96, \ + struct msm_acdb_cmd_device) + +#define AUDIO_MAX_COMMON_IOCTL_NUM 100 + + +#define HANDSET_MIC 0x01 +#define HANDSET_SPKR 0x02 +#define HEADSET_MIC 0x03 +#define HEADSET_SPKR_MONO 0x04 +#define HEADSET_SPKR_STEREO 0x05 +#define SPKR_PHONE_MIC 0x06 +#define SPKR_PHONE_MONO 0x07 +#define SPKR_PHONE_STEREO 0x08 +#define BT_SCO_MIC 0x09 +#define BT_SCO_SPKR 0x0A +#define BT_A2DP_SPKR 0x0B +#define TTY_HEADSET_MIC 0x0C +#define TTY_HEADSET_SPKR 0x0D + +/* Default devices are not supported in a */ +/* device switching context. Only supported */ +/* for stream devices. */ +/* DO NOT USE */ +#define DEFAULT_TX 0x0E +#define DEFAULT_RX 0x0F + +#define BT_A2DP_TX 0x10 + +#define HEADSET_MONO_PLUS_SPKR_MONO_RX 0x11 +#define HEADSET_MONO_PLUS_SPKR_STEREO_RX 0x12 +#define HEADSET_STEREO_PLUS_SPKR_MONO_RX 0x13 +#define HEADSET_STEREO_PLUS_SPKR_STEREO_RX 0x14 + +#define I2S_RX 0x20 +#define I2S_TX 0x21 + +#define ADRC_ENABLE 0x0001 +#define EQ_ENABLE 0x0002 +#define IIR_ENABLE 0x0004 +#define QCONCERT_PLUS_ENABLE 0x0008 +#define MBADRC_ENABLE 0x0010 + +#define AGC_ENABLE 0x0001 +#define NS_ENABLE 0x0002 +#define TX_IIR_ENABLE 0x0004 +#define FLUENCE_ENABLE 0x0008 + +#define VOC_REC_UPLINK 0x00 +#define VOC_REC_DOWNLINK 0x01 +#define VOC_REC_BOTH 0x02 + +struct msm_audio_config { + uint32_t buffer_size; + uint32_t buffer_count; + uint32_t channel_count; + uint32_t sample_rate; + uint32_t type; + uint32_t meta_field; + uint32_t bits; + uint32_t unused[3]; +}; + +struct msm_audio_stream_config { + uint32_t buffer_size; + uint32_t buffer_count; +}; + +struct msm_audio_buf_cfg{ + uint32_t meta_info_enable; + uint32_t frames_per_buf; +}; + +struct msm_audio_stats { + uint32_t byte_count; + uint32_t sample_count; + uint32_t unused[2]; +}; + +struct msm_audio_pmem_info { + int fd; + void *vaddr; +}; + +struct msm_audio_aio_buf { + void *buf_addr; + uint32_t buf_len; + uint32_t data_len; + void *private_data; + unsigned short mfield_sz; /*only useful for data has meta field */ +}; + +/* Audio routing */ + +#define SND_IOCTL_MAGIC 's' + +#define SND_MUTE_UNMUTED 0 +#define SND_MUTE_MUTED 1 + +struct msm_mute_info { + uint32_t mute; + uint32_t path; +}; + +struct msm_vol_info { + uint32_t vol; + uint32_t path; +}; + +struct msm_voicerec_mode { + uint32_t rec_mode; +}; + +struct msm_snd_device_config { + uint32_t device; + uint32_t ear_mute; + uint32_t mic_mute; +}; + +#define SND_SET_DEVICE _IOW(SND_IOCTL_MAGIC, 2, struct msm_device_config *) + +#define SND_METHOD_VOICE 0 + +struct msm_snd_volume_config { + uint32_t device; + uint32_t method; + uint32_t volume; +}; + +#define SND_SET_VOLUME _IOW(SND_IOCTL_MAGIC, 3, struct msm_snd_volume_config *) + +/* Returns the number of SND endpoints supported. */ + +#define SND_GET_NUM_ENDPOINTS _IOR(SND_IOCTL_MAGIC, 4, unsigned *) + +struct msm_snd_endpoint { + int id; /* input and output */ + char name[64]; /* output only */ +}; + +/* Takes an index between 0 and one less than the number returned by + * SND_GET_NUM_ENDPOINTS, and returns the SND index and name of a + * SND endpoint. On input, the .id field contains the number of the + * endpoint, and on exit it contains the SND index, while .name contains + * the description of the endpoint. + */ + +#define SND_GET_ENDPOINT _IOWR(SND_IOCTL_MAGIC, 5, struct msm_snd_endpoint *) + + +#define SND_AVC_CTL _IOW(SND_IOCTL_MAGIC, 6, unsigned *) +#define SND_AGC_CTL _IOW(SND_IOCTL_MAGIC, 7, unsigned *) + +struct msm_audio_pcm_config { + uint32_t pcm_feedback; /* 0 - disable > 0 - enable */ + uint32_t buffer_count; /* Number of buffers to allocate */ + uint32_t buffer_size; /* Size of buffer for capturing of + PCM samples */ +}; + +#define AUDIO_EVENT_SUSPEND 0 +#define AUDIO_EVENT_RESUME 1 +#define AUDIO_EVENT_WRITE_DONE 2 +#define AUDIO_EVENT_READ_DONE 3 +#define AUDIO_EVENT_STREAM_INFO 4 +#define AUDIO_EVENT_BITSTREAM_ERROR_INFO 5 + +#define AUDIO_CODEC_TYPE_MP3 0 +#define AUDIO_CODEC_TYPE_AAC 1 + +struct msm_audio_bitstream_info { + uint32_t codec_type; + uint32_t chan_info; + uint32_t sample_rate; + uint32_t bit_stream_info; + uint32_t bit_rate; + uint32_t unused[3]; +}; + +struct msm_audio_bitstream_error_info { + uint32_t dec_id; + uint32_t err_msg_indicator; + uint32_t err_type; +}; + +union msm_audio_event_payload { + struct msm_audio_aio_buf aio_buf; + struct msm_audio_bitstream_info stream_info; + struct msm_audio_bitstream_error_info error_info; + int reserved; +}; + +struct msm_audio_event { + int event_type; + int timeout_ms; + union msm_audio_event_payload event_payload; +}; + +#define MSM_SNDDEV_CAP_RX 0x1 +#define MSM_SNDDEV_CAP_TX 0x2 +#define MSM_SNDDEV_CAP_VOICE 0x4 + +struct msm_snd_device_info { + uint32_t dev_id; + uint32_t dev_cap; /* bitmask describe capability of device */ + char dev_name[64]; +}; + +struct msm_snd_device_list { + uint32_t num_dev; /* Indicate number of device info to be retrieved */ + struct msm_snd_device_info *list; +}; + +struct msm_dtmf_config { + uint16_t path; + uint16_t dtmf_hi; + uint16_t dtmf_low; + uint16_t duration; + uint16_t tx_gain; + uint16_t rx_gain; + uint16_t mixing; +}; + +#define AUDIO_ROUTE_STREAM_VOICE_RX 0 +#define AUDIO_ROUTE_STREAM_VOICE_TX 1 +#define AUDIO_ROUTE_STREAM_PLAYBACK 2 +#define AUDIO_ROUTE_STREAM_REC 3 + +struct msm_audio_route_config { + uint32_t stream_type; + uint32_t stream_id; + uint32_t dev_id; +}; + +#define AUDIO_MAX_EQ_BANDS 12 + +struct msm_audio_eq_band { + uint16_t band_idx; /* The band index, 0 .. 11 */ + uint32_t filter_type; /* Filter band type */ + uint32_t center_freq_hz; /* Filter band center frequency */ + uint32_t filter_gain; /* Filter band initial gain (dB) */ + /* Range is +12 dB to -12 dB with 1dB increments. */ + uint32_t q_factor; +} __attribute__ ((packed)); + +struct msm_audio_eq_stream_config { + uint32_t enable; /* Number of consequtive bands specified */ + uint32_t num_bands; + struct msm_audio_eq_band eq_bands[AUDIO_MAX_EQ_BANDS]; +} __attribute__ ((packed)); + +struct msm_acdb_cmd_device { + uint32_t command_id; + uint32_t device_id; + uint32_t network_id; + uint32_t sample_rate_id; /* Actual sample rate value */ + uint32_t interface_id; /* See interface id's above */ + uint32_t algorithm_block_id; /* See enumerations above */ + uint32_t total_bytes; /* Length in bytes used by buffer */ + uint32_t *phys_buf; /* Physical Address of data */ +}; + + +#endif diff --git a/include/linux/msm_audio_aac.h b/include/linux/msm_audio_aac.h new file mode 100644 index 00000000000..6e6acff94a0 --- /dev/null +++ b/include/linux/msm_audio_aac.h @@ -0,0 +1,71 @@ +#ifndef __MSM_AUDIO_AAC_H +#define __MSM_AUDIO_AAC_H + +#include + +#define AUDIO_SET_AAC_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+0), unsigned) +#define AUDIO_GET_AAC_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+1), unsigned) + +#define AUDIO_SET_AAC_ENC_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+3), struct msm_audio_aac_enc_config) + +#define AUDIO_GET_AAC_ENC_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+4), struct msm_audio_aac_enc_config) + +#define AUDIO_AAC_FORMAT_ADTS -1 +#define AUDIO_AAC_FORMAT_RAW 0x0000 +#define AUDIO_AAC_FORMAT_PSUEDO_RAW 0x0001 +#define AUDIO_AAC_FORMAT_LOAS 0x0002 + +#define AUDIO_AAC_OBJECT_LC 0x0002 +#define AUDIO_AAC_OBJECT_LTP 0x0004 +#define AUDIO_AAC_OBJECT_ERLC 0x0011 +#define AUDIO_AAC_OBJECT_BSAC 0x0016 + +#define AUDIO_AAC_SEC_DATA_RES_ON 0x0001 +#define AUDIO_AAC_SEC_DATA_RES_OFF 0x0000 + +#define AUDIO_AAC_SCA_DATA_RES_ON 0x0001 +#define AUDIO_AAC_SCA_DATA_RES_OFF 0x0000 + +#define AUDIO_AAC_SPEC_DATA_RES_ON 0x0001 +#define AUDIO_AAC_SPEC_DATA_RES_OFF 0x0000 + +#define AUDIO_AAC_SBR_ON_FLAG_ON 0x0001 +#define AUDIO_AAC_SBR_ON_FLAG_OFF 0x0000 + +#define AUDIO_AAC_SBR_PS_ON_FLAG_ON 0x0001 +#define AUDIO_AAC_SBR_PS_ON_FLAG_OFF 0x0000 + +/* Primary channel on both left and right channels */ +#define AUDIO_AAC_DUAL_MONO_PL_PR 0 +/* Secondary channel on both left and right channels */ +#define AUDIO_AAC_DUAL_MONO_SL_SR 1 +/* Primary channel on right channel and 2nd on left channel */ +#define AUDIO_AAC_DUAL_MONO_SL_PR 2 +/* 2nd channel on right channel and primary on left channel */ +#define AUDIO_AAC_DUAL_MONO_PL_SR 3 + +struct msm_audio_aac_config { + signed short format; + unsigned short audio_object; + unsigned short ep_config; /* 0 ~ 3 useful only obj = ERLC */ + unsigned short aac_section_data_resilience_flag; + unsigned short aac_scalefactor_data_resilience_flag; + unsigned short aac_spectral_data_resilience_flag; + unsigned short sbr_on_flag; + unsigned short sbr_ps_on_flag; + unsigned short dual_mono_mode; + unsigned short channel_configuration; +}; + +struct msm_audio_aac_enc_config { + uint32_t channels; + uint32_t sample_rate; + uint32_t bit_rate; + uint32_t stream_format; +}; + +#endif /* __MSM_AUDIO_AAC_H */ diff --git a/include/linux/msm_audio_acdb.h b/include/linux/msm_audio_acdb.h new file mode 100644 index 00000000000..04cb67f2127 --- /dev/null +++ b/include/linux/msm_audio_acdb.h @@ -0,0 +1,73 @@ +#ifndef __MSM_AUDIO_ACDB_H +#define __MSM_AUDIO_ACDB_H + +#include + +#define AUDIO_SET_VOCPROC_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+0), unsigned) +#define AUDIO_SET_VOCPROC_STREAM_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+1), unsigned) +#define AUDIO_SET_VOCPROC_VOL_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+2), unsigned) +#define AUDIO_SET_AUDPROC_RX_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+3), unsigned) +#define AUDIO_SET_AUDPROC_RX_STREAM_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+4), unsigned) +#define AUDIO_SET_AUDPROC_RX_VOL_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+5), unsigned) +#define AUDIO_SET_AUDPROC_TX_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+6), unsigned) +#define AUDIO_SET_AUDPROC_TX_STREAM_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+7), unsigned) +#define AUDIO_SET_AUDPROC_TX_VOL_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+8), unsigned) +#define AUDIO_SET_SIDETONE_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+9), unsigned) +#define AUDIO_SET_ANC_CAL _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+10), unsigned) +#define AUDIO_SET_VOICE_RX_TOPOLOGY _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+11), unsigned) +#define AUDIO_SET_VOICE_TX_TOPOLOGY _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+12), unsigned) +#define AUDIO_SET_ADM_TOPOLOGY _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+13), unsigned) +#define AUDIO_SET_ASM_TOPOLOGY _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+14), unsigned) + +/* ACDB structures */ +struct cal_block { + uint32_t cal_size; /* Size of Cal Data */ + uint32_t cal_offset; /* offset pointer to Cal Data */ +}; + +struct sidetone_cal { + uint16_t enable; + uint16_t gain; +}; + +/* For Real-Time Audio Calibration */ +#define AUDIO_GET_RTAC_DEV_CTRL_INFO _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+15), unsigned) +#define AUDIO_GET_RTAC_ADM_INFO _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+16), unsigned) +#define AUDIO_GET_RTAC_VOICE_INFO _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+17), unsigned) +#define AUDIO_GET_RTAC_ADM_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+18), unsigned) +#define AUDIO_SET_RTAC_ADM_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+19), unsigned) +#define AUDIO_GET_RTAC_ASM_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+20), unsigned) +#define AUDIO_SET_RTAC_ASM_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+21), unsigned) +#define AUDIO_GET_RTAC_CVS_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+22), unsigned) +#define AUDIO_SET_RTAC_CVS_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+23), unsigned) +#define AUDIO_GET_RTAC_CVP_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+24), unsigned) +#define AUDIO_SET_RTAC_CVP_CAL _IOWR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+25), unsigned) + + +#endif /* __MSM_AUDIO_ACDB_H */ diff --git a/include/linux/msm_audio_amrnb.h b/include/linux/msm_audio_amrnb.h new file mode 100644 index 00000000000..977335ebfdd --- /dev/null +++ b/include/linux/msm_audio_amrnb.h @@ -0,0 +1,51 @@ +/* arch/arm/mach-msm/include/mach/msm_audio_amrnb.h + * + * Copyright (c) 2009, Code Aurora Forum. 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + * + */ + +#ifndef __MSM_AUDIO_AMRNB_H +#define __MSM_AUDIO_AMRNB_H + +#include + +#define AUDIO_GET_AMRNB_ENC_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+0), unsigned) +#define AUDIO_SET_AMRNB_ENC_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+1), unsigned) +#define AUDIO_GET_AMRNB_ENC_CONFIG_V2 _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+2), \ + struct msm_audio_amrnb_enc_config_v2) +#define AUDIO_SET_AMRNB_ENC_CONFIG_V2 _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+3), \ + struct msm_audio_amrnb_enc_config_v2) + +struct msm_audio_amrnb_enc_config { + unsigned short voicememoencweight1; + unsigned short voicememoencweight2; + unsigned short voicememoencweight3; + unsigned short voicememoencweight4; + unsigned short dtx_mode_enable; /* 0xFFFF - enable, 0- disable */ + unsigned short test_mode_enable; /* 0xFFFF - enable, 0- disable */ + unsigned short enc_mode; /* 0-MR475,1-MR515,2-MR59,3-MR67,4-MR74 + 5-MR795, 6- MR102, 7- MR122(default) */ +}; + +struct msm_audio_amrnb_enc_config_v2 { + uint32_t band_mode; + uint32_t dtx_enable; + uint32_t frame_format; +}; +#endif /* __MSM_AUDIO_AMRNB_H */ diff --git a/include/linux/msm_audio_mvs.h b/include/linux/msm_audio_mvs.h new file mode 100644 index 00000000000..f401c1f1b61 --- /dev/null +++ b/include/linux/msm_audio_mvs.h @@ -0,0 +1,111 @@ +#ifndef __MSM_AUDIO_MVS_H +#define __MSM_AUDIO_MVS_H + +#include + +#define AUDIO_GET_MVS_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 0), unsigned) +#define AUDIO_SET_MVS_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 1), unsigned) + +/* MVS modes */ +#define MVS_MODE_IS733 1 +#define MVS_MODE_IS127 2 +#define MVS_MODE_4GV_NB 3 +#define MVS_MODE_4GV_WB 4 +#define MVS_MODE_AMR 5 +#define MVS_MODE_EFR 6 +#define MVS_MODE_FR 7 +#define MVS_MODE_HR 8 +#define MVS_MODE_LINEAR_PCM 9 +#define MVS_MODE_G711 10 +#define MVS_MODE_PCM 12 +#define MVS_MODE_AMR_WB 13 +#define MVS_MODE_G729A 14 +#define MVS_MODE_G711A 15 +#define MVS_MODE_G722 16 + +enum msm_audio_amr_mode { + MVS_AMR_MODE_0475, /* AMR 4.75 kbps */ + MVS_AMR_MODE_0515, /* AMR 5.15 kbps */ + MVS_AMR_MODE_0590, /* AMR 5.90 kbps */ + MVS_AMR_MODE_0670, /* AMR 6.70 kbps */ + MVS_AMR_MODE_0740, /* AMR 7.40 kbps */ + MVS_AMR_MODE_0795, /* AMR 7.95 kbps */ + MVS_AMR_MODE_1020, /* AMR 10.20 kbps */ + MVS_AMR_MODE_1220, /* AMR 12.20 kbps */ + MVS_AMR_MODE_0660, /* AMR-WB 6.60 kbps */ + MVS_AMR_MODE_0885, /* AMR-WB 8.85 kbps */ + MVS_AMR_MODE_1265, /* AMR-WB 12.65 kbps */ + MVS_AMR_MODE_1425, /* AMR-WB 14.25 kbps */ + MVS_AMR_MODE_1585, /* AMR-WB 15.85 kbps */ + MVS_AMR_MODE_1825, /* AMR-WB 18.25 kbps */ + MVS_AMR_MODE_1985, /* AMR-WB 19.85 kbps */ + MVS_AMR_MODE_2305, /* AMR-WB 23.05 kbps */ + MVS_AMR_MODE_2385, /* AMR-WB 23.85 kbps */ + MVS_AMR_MODE_UNDEF +}; + +enum msm_audio_voc_rate { + MVS_VOC_0_RATE, /* Blank frame */ + MVS_VOC_8_RATE, /* 1/8 rate */ + MVS_VOC_4_RATE, /* 1/4 rate */ + MVS_VOC_2_RATE, /* 1/2 rate */ + MVS_VOC_1_RATE /* Full rate */ +}; + +enum msm_audio_amr_frame_type { + MVS_AMR_SPEECH_GOOD, /* Good speech frame */ + MVS_AMR_SPEECH_DEGRADED, /* Speech degraded */ + MVS_AMR_ONSET, /* Onset */ + MVS_AMR_SPEECH_BAD, /* Corrupt speech frame (bad CRC) */ + MVS_AMR_SID_FIRST, /* First silence descriptor */ + MVS_AMR_SID_UPDATE, /* Comfort noise frame */ + MVS_AMR_SID_BAD, /* Corrupt SID frame (bad CRC) */ + MVS_AMR_NO_DATA, /* Nothing to transmit */ + MVS_AMR_SPEECH_LOST /* Downlink speech lost */ +}; + +enum msm_audio_g711a_mode { + MVS_G711A_MODE_MULAW, + MVS_G711A_MODE_ALAW +}; + +enum mvs_g722_mode_type { + MVS_G722_MODE_01, + MVS_G722_MODE_02, + MVS_G722_MODE_03, + MVS_G722_MODE_MAX, + MVS_G722_MODE_UNDEF +}; + +enum msm_audio_g711a_frame_type { + MVS_G711A_SPEECH_GOOD, + MVS_G711A_SID, + MVS_G711A_NO_DATA, + MVS_G711A_ERASURE +}; + +enum msm_audio_g729a_frame_type { + MVS_G729A_NO_DATA, + MVS_G729A_SPEECH_GOOD, + MVS_G729A_SID, + MVS_G729A_ERASURE +}; + +struct msm_audio_mvs_config { + uint32_t mvs_mode; + uint32_t rate_type; + uint32_t dtx_mode; +}; + +#define MVS_MAX_VOC_PKT_SIZE 320 + +struct msm_audio_mvs_frame { + uint32_t frame_type; + uint32_t len; + uint8_t voc_pkt[MVS_MAX_VOC_PKT_SIZE]; + +}; + +#endif /* __MSM_AUDIO_MVS_H */ diff --git a/include/linux/msm_audio_qcp.h b/include/linux/msm_audio_qcp.h new file mode 100644 index 00000000000..c5e42cf0c05 --- /dev/null +++ b/include/linux/msm_audio_qcp.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_AUDIO_QCP_H +#define __MSM_AUDIO_QCP_H + +#include + +#define AUDIO_SET_QCELP_ENC_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + 0, struct msm_audio_qcelp_enc_config) + +#define AUDIO_GET_QCELP_ENC_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + 1, struct msm_audio_qcelp_enc_config) + +#define AUDIO_SET_EVRC_ENC_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + 2, struct msm_audio_evrc_enc_config) + +#define AUDIO_GET_EVRC_ENC_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + 3, struct msm_audio_evrc_enc_config) + +#define CDMA_RATE_BLANK 0x00 +#define CDMA_RATE_EIGHTH 0x01 +#define CDMA_RATE_QUARTER 0x02 +#define CDMA_RATE_HALF 0x03 +#define CDMA_RATE_FULL 0x04 +#define CDMA_RATE_ERASURE 0x05 + +struct msm_audio_qcelp_enc_config { + uint32_t cdma_rate; + uint32_t min_bit_rate; + uint32_t max_bit_rate; +}; + +struct msm_audio_evrc_enc_config { + uint32_t cdma_rate; + uint32_t min_bit_rate; + uint32_t max_bit_rate; +}; + +#endif /* __MSM_AUDIO_QCP_H */ diff --git a/include/linux/msm_audio_sbc.h b/include/linux/msm_audio_sbc.h new file mode 100644 index 00000000000..0a7602a2570 --- /dev/null +++ b/include/linux/msm_audio_sbc.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_AUDIO_SBC_H +#define __MSM_AUDIO_SBC_H + +#include + +#define AUDIO_SET_SBC_ENC_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+0), struct msm_audio_sbc_enc_config) + +#define AUDIO_GET_SBC_ENC_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+1), struct msm_audio_sbc_enc_config) + +#define AUDIO_SBC_BA_LOUDNESS 0x0 +#define AUDIO_SBC_BA_SNR 0x1 + +#define AUDIO_SBC_MODE_MONO 0x0 +#define AUDIO_SBC_MODE_DUAL 0x1 +#define AUDIO_SBC_MODE_STEREO 0x2 +#define AUDIO_SBC_MODE_JSTEREO 0x3 + +#define AUDIO_SBC_BANDS_8 0x1 + +#define AUDIO_SBC_BLOCKS_4 0x0 +#define AUDIO_SBC_BLOCKS_8 0x1 +#define AUDIO_SBC_BLOCKS_12 0x2 +#define AUDIO_SBC_BLOCKS_16 0x3 + +struct msm_audio_sbc_enc_config { + uint32_t channels; + uint32_t sample_rate; + uint32_t bit_allocation; + uint32_t number_of_subbands; + uint32_t number_of_blocks; + uint32_t bit_rate; + uint32_t mode; +}; +#endif /* __MSM_AUDIO_SBC_H */ diff --git a/include/linux/msm_audio_voicememo.h b/include/linux/msm_audio_voicememo.h new file mode 100644 index 00000000000..5cb1d65bc20 --- /dev/null +++ b/include/linux/msm_audio_voicememo.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __MSM_AUDIO_VOICEMEMO_H +#define __MSM_AUDIO_VOICEMEMO_H + +#include + +#define AUDIO_GET_VOICEMEMO_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+0), unsigned) +#define AUDIO_SET_VOICEMEMO_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+1), unsigned) + +/* rec_type */ +enum rpc_voc_rec_dir_type { + RPC_VOC_REC_NONE, + RPC_VOC_REC_FORWARD, + RPC_VOC_REC_REVERSE, + RPC_VOC_REC_BOTH, + RPC_VOC_MAX_REC_TYPE +}; + +/* capability */ +enum rpc_voc_capability_type { + RPC_VOC_CAP_IS733 = 4, + RPC_VOC_CAP_IS127 = 8, + RPC_VOC_CAP_AMR = 64, + RPC_VOC_CAP_32BIT_DUMMY = 2147483647 +}; + +/* Rate */ +enum rpc_voc_rate_type { + RPC_VOC_0_RATE = 0, + RPC_VOC_8_RATE, + RPC_VOC_4_RATE, + RPC_VOC_2_RATE, + RPC_VOC_1_RATE, + RPC_VOC_ERASURE, + RPC_VOC_ERR_RATE, + RPC_VOC_AMR_RATE_475 = 0, + RPC_VOC_AMR_RATE_515 = 1, + RPC_VOC_AMR_RATE_590 = 2, + RPC_VOC_AMR_RATE_670 = 3, + RPC_VOC_AMR_RATE_740 = 4, + RPC_VOC_AMR_RATE_795 = 5, + RPC_VOC_AMR_RATE_1020 = 6, + RPC_VOC_AMR_RATE_1220 = 7, +}; + +/* frame_format */ +enum rpc_voc_pb_len_rate_var_type { + RPC_VOC_PB_NATIVE_QCP = 3, + RPC_VOC_PB_AMR, + RPC_VOC_PB_EVB +}; + +struct msm_audio_voicememo_config { + uint32_t rec_type; + uint32_t rec_interval_ms; + uint32_t auto_stop_ms; + uint32_t capability; + uint32_t max_rate; + uint32_t min_rate; + uint32_t frame_format; + uint32_t dtx_enable; + uint32_t data_req_ms; +}; + +#endif /* __MSM_AUDIO_VOICEMEMO_H */ diff --git a/include/linux/msm_audio_wma.h b/include/linux/msm_audio_wma.h new file mode 100644 index 00000000000..24ff2643c90 --- /dev/null +++ b/include/linux/msm_audio_wma.h @@ -0,0 +1,33 @@ +#ifndef __MSM_AUDIO_WMA_H +#define __MSM_AUDIO_WMA_H + +#define AUDIO_GET_WMA_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+0), unsigned) +#define AUDIO_SET_WMA_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+1), unsigned) + +#define AUDIO_GET_WMA_CONFIG_V2 _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+2), struct msm_audio_wma_config_v2) +#define AUDIO_SET_WMA_CONFIG_V2 _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+3), struct msm_audio_wma_config_v2) + +struct msm_audio_wma_config { + unsigned short armdatareqthr; + unsigned short channelsdecoded; + unsigned short wmabytespersec; + unsigned short wmasamplingfreq; + unsigned short wmaencoderopts; +}; + +struct msm_audio_wma_config_v2 { + unsigned short format_tag; + unsigned short numchannels; + uint32_t samplingrate; + uint32_t avgbytespersecond; + unsigned short block_align; + unsigned short validbitspersample; + uint32_t channelmask; + unsigned short encodeopt; +}; + +#endif /* __MSM_AUDIO_WMA_H */ diff --git a/include/linux/msm_audio_wmapro.h b/include/linux/msm_audio_wmapro.h new file mode 100644 index 00000000000..b680f419ecc --- /dev/null +++ b/include/linux/msm_audio_wmapro.h @@ -0,0 +1,22 @@ +#ifndef __MSM_AUDIO_WMAPRO_H +#define __MSM_AUDIO_WMAPRO_H + +#define AUDIO_GET_WMAPRO_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+0), unsigned) +#define AUDIO_SET_WMAPRO_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM+1), unsigned) + +struct msm_audio_wmapro_config { + unsigned short armdatareqthr; + uint8_t validbitspersample; + uint8_t numchannels; + unsigned short formattag; + unsigned short samplingrate; + unsigned short avgbytespersecond; + unsigned short asfpacketlength; + unsigned short channelmask; + unsigned short encodeopt; + unsigned short advancedencodeopt; + uint32_t advancedencodeopt2; +}; +#endif /* __MSM_AUDIO_WMAPRO_H */ diff --git a/include/linux/msm_charm.h b/include/linux/msm_charm.h new file mode 100644 index 00000000000..779fd3817f8 --- /dev/null +++ b/include/linux/msm_charm.h @@ -0,0 +1,19 @@ +#ifndef _ARCH_ARM_MACH_MSM_MDM_IOCTLS_H +#define _ARXH_ARM_MACH_MSM_MDM_IOCTLS_H + + +#define CHARM_CODE 0xCC +#define WAKE_CHARM _IO(CHARM_CODE, 1) +#define RESET_CHARM _IO(CHARM_CODE, 2) +#define CHECK_FOR_BOOT _IOR(CHARM_CODE, 3, int) +#define WAIT_FOR_BOOT _IO(CHARM_CODE, 4) +#define NORMAL_BOOT_DONE _IOW(CHARM_CODE, 5, int) +#define RAM_DUMP_DONE _IOW(CHARM_CODE, 6, int) +#define WAIT_FOR_RESTART _IOR(CHARM_CODE, 7, int) + +enum charm_boot_type { + CHARM_NORMAL_BOOT = 0, + CHARM_RAM_DUMPS, +}; + +#endif diff --git a/include/linux/msm_dsps.h b/include/linux/msm_dsps.h new file mode 100644 index 00000000000..7f4d2dd27eb --- /dev/null +++ b/include/linux/msm_dsps.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DSPS_H_ +#define _DSPS_H_ + +#include + +#define DSPS_IOCTL_MAGIC 'd' + +#define DSPS_IOCTL_ON _IO(DSPS_IOCTL_MAGIC, 1) +#define DSPS_IOCTL_OFF _IO(DSPS_IOCTL_MAGIC, 2) + +#define DSPS_IOCTL_READ_SLOW_TIMER _IOR(DSPS_IOCTL_MAGIC, 3, unsigned int*) +#define DSPS_IOCTL_READ_FAST_TIMER _IOR(DSPS_IOCTL_MAGIC, 4, unsigned int*) + +#endif /* _DSPS_H_ */ diff --git a/include/linux/msm_ipc.h b/include/linux/msm_ipc.h new file mode 100644 index 00000000000..19cb82e9dbd --- /dev/null +++ b/include/linux/msm_ipc.h @@ -0,0 +1,72 @@ +#ifndef _LINUX_MSM_IPC_H_ +#define _LINUX_MSM_IPC_H_ + +#include +#include + +struct msm_ipc_port_addr { + uint32_t node_id; + uint32_t port_id; +}; + +struct msm_ipc_port_name { + uint32_t service; + uint32_t instance; +}; + +struct msm_ipc_addr { + unsigned char addrtype; + union { + struct msm_ipc_port_addr port_addr; + struct msm_ipc_port_name port_name; + } addr; +}; + +#define MSM_IPC_WAIT_FOREVER (~0) /* timeout for permanent subscription */ + +/* + * Socket API + */ + +#ifndef AF_MSM_IPC +#define AF_MSM_IPC 27 +#endif + +#ifndef PF_MSM_IPC +#define PF_MSM_IPC AF_MSM_IPC +#endif + +#define MSM_IPC_ADDR_NAME 1 +#define MSM_IPC_ADDR_ID 2 + +struct sockaddr_msm_ipc { + unsigned short family; + struct msm_ipc_addr address; + unsigned char reserved; +}; + +#define IPC_ROUTER_IOCTL_MAGIC (0xC3) + +#define IPC_ROUTER_IOCTL_GET_VERSION \ + _IOR(IPC_ROUTER_IOCTL_MAGIC, 0, unsigned int) + +#define IPC_ROUTER_IOCTL_GET_MTU \ + _IOR(IPC_ROUTER_IOCTL_MAGIC, 1, unsigned int) + +#define IPC_ROUTER_IOCTL_LOOKUP_SERVER \ + _IOWR(IPC_ROUTER_IOCTL_MAGIC, 2, struct sockaddr_msm_ipc) + +#define IPC_ROUTER_IOCTL_GET_CURR_PKT_SIZE \ + _IOR(IPC_ROUTER_IOCTL_MAGIC, 3, unsigned int) + +#define IPC_ROUTER_IOCTL_BIND_CONTROL_PORT \ + _IOR(IPC_ROUTER_IOCTL_MAGIC, 4, unsigned int) + +struct server_lookup_args { + struct msm_ipc_port_name port_name; + int num_entries_in_array; + int num_entries_found; + struct msm_ipc_port_addr port_addr[0]; +}; + +#endif diff --git a/include/linux/msm_kgsl.h b/include/linux/msm_kgsl.h new file mode 100644 index 00000000000..2a38d52d1e9 --- /dev/null +++ b/include/linux/msm_kgsl.h @@ -0,0 +1,408 @@ +#ifndef _MSM_KGSL_H +#define _MSM_KGSL_H + +#define KGSL_VERSION_MAJOR 3 +#define KGSL_VERSION_MINOR 4 + +/*context flags */ +#define KGSL_CONTEXT_SAVE_GMEM 1 +#define KGSL_CONTEXT_NO_GMEM_ALLOC 2 +#define KGSL_CONTEXT_SUBMIT_IB_LIST 4 +#define KGSL_CONTEXT_CTX_SWITCH 8 + +/* Memory allocayion flags */ +#define KGSL_MEMFLAGS_GPUREADONLY 0x01000000 + +/* generic flag values */ +#define KGSL_FLAGS_NORMALMODE 0x00000000 +#define KGSL_FLAGS_SAFEMODE 0x00000001 +#define KGSL_FLAGS_INITIALIZED0 0x00000002 +#define KGSL_FLAGS_INITIALIZED 0x00000004 +#define KGSL_FLAGS_STARTED 0x00000008 +#define KGSL_FLAGS_ACTIVE 0x00000010 +#define KGSL_FLAGS_RESERVED0 0x00000020 +#define KGSL_FLAGS_RESERVED1 0x00000040 +#define KGSL_FLAGS_RESERVED2 0x00000080 +#define KGSL_FLAGS_SOFT_RESET 0x00000100 + +#define KGSL_MAX_PWRLEVELS 5 + +/* device id */ +enum kgsl_deviceid { + KGSL_DEVICE_3D0 = 0x00000000, + KGSL_DEVICE_2D0 = 0x00000001, + KGSL_DEVICE_2D1 = 0x00000002, + KGSL_DEVICE_MAX = 0x00000003 +}; + +enum kgsl_user_mem_type { + KGSL_USER_MEM_TYPE_PMEM = 0x00000000, + KGSL_USER_MEM_TYPE_ASHMEM = 0x00000001, + KGSL_USER_MEM_TYPE_ADDR = 0x00000002 +}; + +struct kgsl_devinfo { + + unsigned int device_id; + /* chip revision id + * coreid:8 majorrev:8 minorrev:8 patch:8 + */ + unsigned int chip_id; + unsigned int mmu_enabled; + unsigned int gmem_gpubaseaddr; + /* + * This field contains the adreno revision + * number 200, 205, 220, etc... + */ + unsigned int gpu_id; + unsigned int gmem_sizebytes; +}; + +/* this structure defines the region of memory that can be mmap()ed from this + driver. The timestamp fields are volatile because they are written by the + GPU +*/ +struct kgsl_devmemstore { + volatile unsigned int soptimestamp; + unsigned int sbz; + volatile unsigned int eoptimestamp; + unsigned int sbz2; + volatile unsigned int ts_cmp_enable; + unsigned int sbz3; + volatile unsigned int ref_wait_ts; + unsigned int sbz4; + unsigned int current_context; + unsigned int sbz5; +}; + +#define KGSL_DEVICE_MEMSTORE_OFFSET(field) \ + offsetof(struct kgsl_devmemstore, field) + + +/* timestamp id*/ +enum kgsl_timestamp_type { + KGSL_TIMESTAMP_CONSUMED = 0x00000001, /* start-of-pipeline timestamp */ + KGSL_TIMESTAMP_RETIRED = 0x00000002, /* end-of-pipeline timestamp*/ + KGSL_TIMESTAMP_MAX = 0x00000002, +}; + +/* property types - used with kgsl_device_getproperty */ +enum kgsl_property_type { + KGSL_PROP_DEVICE_INFO = 0x00000001, + KGSL_PROP_DEVICE_SHADOW = 0x00000002, + KGSL_PROP_DEVICE_POWER = 0x00000003, + KGSL_PROP_SHMEM = 0x00000004, + KGSL_PROP_SHMEM_APERTURES = 0x00000005, + KGSL_PROP_MMU_ENABLE = 0x00000006, + KGSL_PROP_INTERRUPT_WAITS = 0x00000007, + KGSL_PROP_VERSION = 0x00000008, +}; + +struct kgsl_shadowprop { + unsigned int gpuaddr; + unsigned int size; + unsigned int flags; /* contains KGSL_FLAGS_ values */ +}; + +struct kgsl_pwrlevel { + unsigned int gpu_freq; + unsigned int bus_freq; +}; + +struct kgsl_version { + unsigned int drv_major; + unsigned int drv_minor; + unsigned int dev_major; + unsigned int dev_minor; +}; + +#ifdef __KERNEL__ + +#define KGSL_3D0_REG_MEMORY "kgsl_3d0_reg_memory" +#define KGSL_3D0_IRQ "kgsl_3d0_irq" +#define KGSL_2D0_REG_MEMORY "kgsl_2d0_reg_memory" +#define KGSL_2D0_IRQ "kgsl_2d0_irq" +#define KGSL_2D1_REG_MEMORY "kgsl_2d1_reg_memory" +#define KGSL_2D1_IRQ "kgsl_2d1_irq" + +struct kgsl_grp_clk_name { + const char *clk; + const char *pclk; +}; + +struct kgsl_device_pwr_data { + struct kgsl_pwrlevel pwrlevel[KGSL_MAX_PWRLEVELS]; + int init_level; + int num_levels; + int (*set_grp_async)(void); + unsigned int idle_timeout; + unsigned int nap_allowed; + unsigned int idle_pass; +}; + +struct kgsl_clk_data { + struct kgsl_grp_clk_name name; + struct msm_bus_scale_pdata *bus_scale_table; +}; + +struct kgsl_device_platform_data { + struct kgsl_device_pwr_data pwr_data; + struct kgsl_clk_data clk; + /* imem_clk_name is for 3d only, not used in 2d devices */ + struct kgsl_grp_clk_name imem_clk_name; +}; + +#endif + +/* structure holds list of ibs */ +struct kgsl_ibdesc { + unsigned int gpuaddr; + void *hostptr; + unsigned int sizedwords; + unsigned int ctrl; +}; + +/* ioctls */ +#define KGSL_IOC_TYPE 0x09 + +/* get misc info about the GPU + type should be a value from enum kgsl_property_type + value points to a structure that varies based on type + sizebytes is sizeof() that structure + for KGSL_PROP_DEVICE_INFO, use struct kgsl_devinfo + this structure contaings hardware versioning info. + for KGSL_PROP_DEVICE_SHADOW, use struct kgsl_shadowprop + this is used to find mmap() offset and sizes for mapping + struct kgsl_memstore into userspace. +*/ +struct kgsl_device_getproperty { + unsigned int type; + void *value; + unsigned int sizebytes; +}; + +#define IOCTL_KGSL_DEVICE_GETPROPERTY \ + _IOWR(KGSL_IOC_TYPE, 0x2, struct kgsl_device_getproperty) + + +/* read a GPU register. + offsetwords it the 32 bit word offset from the beginning of the + GPU register space. + */ +struct kgsl_device_regread { + unsigned int offsetwords; + unsigned int value; /* output param */ +}; + +#define IOCTL_KGSL_DEVICE_REGREAD \ + _IOWR(KGSL_IOC_TYPE, 0x3, struct kgsl_device_regread) + + +/* block until the GPU has executed past a given timestamp + * timeout is in milliseconds. + */ +struct kgsl_device_waittimestamp { + unsigned int timestamp; + unsigned int timeout; +}; + +#define IOCTL_KGSL_DEVICE_WAITTIMESTAMP \ + _IOW(KGSL_IOC_TYPE, 0x6, struct kgsl_device_waittimestamp) + + +/* issue indirect commands to the GPU. + * drawctxt_id must have been created with IOCTL_KGSL_DRAWCTXT_CREATE + * ibaddr and sizedwords must specify a subset of a buffer created + * with IOCTL_KGSL_SHAREDMEM_FROM_PMEM + * flags may be a mask of KGSL_CONTEXT_ values + * timestamp is a returned counter value which can be passed to + * other ioctls to determine when the commands have been executed by + * the GPU. + */ +struct kgsl_ringbuffer_issueibcmds { + unsigned int drawctxt_id; + unsigned int ibdesc_addr; + unsigned int numibs; + unsigned int timestamp; /*output param */ + unsigned int flags; +}; + +#define IOCTL_KGSL_RINGBUFFER_ISSUEIBCMDS \ + _IOWR(KGSL_IOC_TYPE, 0x10, struct kgsl_ringbuffer_issueibcmds) + +/* read the most recently executed timestamp value + * type should be a value from enum kgsl_timestamp_type + */ +struct kgsl_cmdstream_readtimestamp { + unsigned int type; + unsigned int timestamp; /*output param */ +}; + +#define IOCTL_KGSL_CMDSTREAM_READTIMESTAMP \ + _IOR(KGSL_IOC_TYPE, 0x11, struct kgsl_cmdstream_readtimestamp) + +/* free memory when the GPU reaches a given timestamp. + * gpuaddr specify a memory region created by a + * IOCTL_KGSL_SHAREDMEM_FROM_PMEM call + * type should be a value from enum kgsl_timestamp_type + */ +struct kgsl_cmdstream_freememontimestamp { + unsigned int gpuaddr; + unsigned int type; + unsigned int timestamp; +}; + +#define IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP \ + _IOW(KGSL_IOC_TYPE, 0x12, struct kgsl_cmdstream_freememontimestamp) + +/* Previous versions of this header had incorrectly defined + IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP as a read-only ioctl instead + of a write only ioctl. To ensure binary compatability, the following + #define will be used to intercept the incorrect ioctl +*/ + +#define IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP_OLD \ + _IOR(KGSL_IOC_TYPE, 0x12, struct kgsl_cmdstream_freememontimestamp) + +/* create a draw context, which is used to preserve GPU state. + * The flags field may contain a mask KGSL_CONTEXT_* values + */ +struct kgsl_drawctxt_create { + unsigned int flags; + unsigned int drawctxt_id; /*output param */ +}; + +#define IOCTL_KGSL_DRAWCTXT_CREATE \ + _IOWR(KGSL_IOC_TYPE, 0x13, struct kgsl_drawctxt_create) + +/* destroy a draw context */ +struct kgsl_drawctxt_destroy { + unsigned int drawctxt_id; +}; + +#define IOCTL_KGSL_DRAWCTXT_DESTROY \ + _IOW(KGSL_IOC_TYPE, 0x14, struct kgsl_drawctxt_destroy) + +/* add a block of pmem, fb, ashmem or user allocated address + * into the GPU address space */ +struct kgsl_map_user_mem { + int fd; + unsigned int gpuaddr; /*output param */ + unsigned int len; + unsigned int offset; + unsigned int hostptr; /*input param */ + enum kgsl_user_mem_type memtype; + unsigned int reserved; /* May be required to add + params for another mem type */ +}; + +#define IOCTL_KGSL_MAP_USER_MEM \ + _IOWR(KGSL_IOC_TYPE, 0x15, struct kgsl_map_user_mem) + +/* add a block of pmem or fb into the GPU address space */ +struct kgsl_sharedmem_from_pmem { + int pmem_fd; + unsigned int gpuaddr; /*output param */ + unsigned int len; + unsigned int offset; +}; + +#define IOCTL_KGSL_SHAREDMEM_FROM_PMEM \ + _IOWR(KGSL_IOC_TYPE, 0x20, struct kgsl_sharedmem_from_pmem) + +/* remove memory from the GPU's address space */ +struct kgsl_sharedmem_free { + unsigned int gpuaddr; +}; + +#define IOCTL_KGSL_SHAREDMEM_FREE \ + _IOW(KGSL_IOC_TYPE, 0x21, struct kgsl_sharedmem_free) + + +struct kgsl_gmem_desc { + unsigned int x; + unsigned int y; + unsigned int width; + unsigned int height; + unsigned int pitch; +}; + +struct kgsl_buffer_desc { + void *hostptr; + unsigned int gpuaddr; + int size; + unsigned int format; + unsigned int pitch; + unsigned int enabled; +}; + +struct kgsl_bind_gmem_shadow { + unsigned int drawctxt_id; + struct kgsl_gmem_desc gmem_desc; + unsigned int shadow_x; + unsigned int shadow_y; + struct kgsl_buffer_desc shadow_buffer; + unsigned int buffer_id; +}; + +#define IOCTL_KGSL_DRAWCTXT_BIND_GMEM_SHADOW \ + _IOW(KGSL_IOC_TYPE, 0x22, struct kgsl_bind_gmem_shadow) + +/* add a block of memory into the GPU address space */ +struct kgsl_sharedmem_from_vmalloc { + unsigned int gpuaddr; /*output param */ + unsigned int hostptr; + unsigned int flags; +}; + +#define IOCTL_KGSL_SHAREDMEM_FROM_VMALLOC \ + _IOWR(KGSL_IOC_TYPE, 0x23, struct kgsl_sharedmem_from_vmalloc) + +#define IOCTL_KGSL_SHAREDMEM_FLUSH_CACHE \ + _IOW(KGSL_IOC_TYPE, 0x24, struct kgsl_sharedmem_free) + +struct kgsl_drawctxt_set_bin_base_offset { + unsigned int drawctxt_id; + unsigned int offset; +}; + +#define IOCTL_KGSL_DRAWCTXT_SET_BIN_BASE_OFFSET \ + _IOW(KGSL_IOC_TYPE, 0x25, struct kgsl_drawctxt_set_bin_base_offset) + +enum kgsl_cmdwindow_type { + KGSL_CMDWINDOW_MIN = 0x00000000, + KGSL_CMDWINDOW_2D = 0x00000000, + KGSL_CMDWINDOW_3D = 0x00000001, /* legacy */ + KGSL_CMDWINDOW_MMU = 0x00000002, + KGSL_CMDWINDOW_ARBITER = 0x000000FF, + KGSL_CMDWINDOW_MAX = 0x000000FF, +}; + +/* write to the command window */ +struct kgsl_cmdwindow_write { + enum kgsl_cmdwindow_type target; + unsigned int addr; + unsigned int data; +}; + +#define IOCTL_KGSL_CMDWINDOW_WRITE \ + _IOW(KGSL_IOC_TYPE, 0x2e, struct kgsl_cmdwindow_write) + +struct kgsl_gpumem_alloc { + unsigned long gpuaddr; + size_t size; + unsigned int flags; +}; + +#define IOCTL_KGSL_GPUMEM_ALLOC \ + _IOWR(KGSL_IOC_TYPE, 0x2f, struct kgsl_gpumem_alloc) + +#ifdef __KERNEL__ +#ifdef CONFIG_MSM_KGSL_DRM +int kgsl_gem_obj_addr(int drm_fd, int handle, unsigned long *start, + unsigned long *len); +#else +#define kgsl_gem_obj_addr(...) 0 +#endif +#endif +#endif /* _MSM_KGSL_H */ diff --git a/include/linux/msm_mdp.h b/include/linux/msm_mdp.h index fe722c1fb61..9dced1a03b9 100644 --- a/include/linux/msm_mdp.h +++ b/include/linux/msm_mdp.h @@ -15,25 +15,73 @@ #define _MSM_MDP_H_ #include +#include #define MSMFB_IOCTL_MAGIC 'm' #define MSMFB_GRP_DISP _IOW(MSMFB_IOCTL_MAGIC, 1, unsigned int) #define MSMFB_BLIT _IOW(MSMFB_IOCTL_MAGIC, 2, unsigned int) +#define MSMFB_SUSPEND_SW_REFRESHER _IOW(MSMFB_IOCTL_MAGIC, 128, unsigned int) +#define MSMFB_RESUME_SW_REFRESHER _IOW(MSMFB_IOCTL_MAGIC, 129, unsigned int) +#define MSMFB_CURSOR _IOW(MSMFB_IOCTL_MAGIC, 130, struct fb_cursor) +#define MSMFB_SET_LUT _IOW(MSMFB_IOCTL_MAGIC, 131, struct fb_cmap) +#define MSMFB_HISTOGRAM _IOWR(MSMFB_IOCTL_MAGIC, 132, struct mdp_histogram) +/* new ioctls's for set/get ccs matrix */ +#define MSMFB_GET_CCS_MATRIX _IOWR(MSMFB_IOCTL_MAGIC, 133, struct mdp_ccs) +#define MSMFB_SET_CCS_MATRIX _IOW(MSMFB_IOCTL_MAGIC, 134, struct mdp_ccs) +#define MSMFB_OVERLAY_SET _IOWR(MSMFB_IOCTL_MAGIC, 135, \ + struct mdp_overlay) +#define MSMFB_OVERLAY_UNSET _IOW(MSMFB_IOCTL_MAGIC, 136, unsigned int) +#define MSMFB_OVERLAY_PLAY _IOW(MSMFB_IOCTL_MAGIC, 137, \ + struct msmfb_overlay_data) +#define MSMFB_GET_PAGE_PROTECTION _IOR(MSMFB_IOCTL_MAGIC, 138, \ + struct mdp_page_protection) +#define MSMFB_SET_PAGE_PROTECTION _IOW(MSMFB_IOCTL_MAGIC, 139, \ + struct mdp_page_protection) +#define MSMFB_OVERLAY_GET _IOR(MSMFB_IOCTL_MAGIC, 140, \ + struct mdp_overlay) +#define MSMFB_OVERLAY_PLAY_ENABLE _IOW(MSMFB_IOCTL_MAGIC, 141, unsigned int) +#define MSMFB_OVERLAY_BLT _IOWR(MSMFB_IOCTL_MAGIC, 142, \ + struct msmfb_overlay_blt) +#define MSMFB_OVERLAY_BLT_OFFSET _IOW(MSMFB_IOCTL_MAGIC, 143, unsigned int) +#define MSMFB_HISTOGRAM_START _IO(MSMFB_IOCTL_MAGIC, 144) +#define MSMFB_HISTOGRAM_STOP _IO(MSMFB_IOCTL_MAGIC, 145) +#define MSMFB_NOTIFY_UPDATE _IOW(MSMFB_IOCTL_MAGIC, 146, unsigned int) + +#define MSMFB_OVERLAY_3D _IOWR(MSMFB_IOCTL_MAGIC, 147, \ + struct msmfb_overlay_3d) + +#define FB_TYPE_3D_PANEL 0x10101010 +#define MDP_IMGTYPE2_START 0x10000 +#define MSMFB_DRIVER_VERSION 0xF9E8D701 + +enum { + NOTIFY_UPDATE_START, + NOTIFY_UPDATE_STOP, +}; enum { - MDP_RGB_565, /* RGB 565 planar */ - MDP_XRGB_8888, /* RGB 888 padded */ - MDP_Y_CBCR_H2V2, /* Y and CbCr, pseudo planar w/ Cb is in MSB */ - MDP_ARGB_8888, /* ARGB 888 */ - MDP_RGB_888, /* RGB 888 planar */ - MDP_Y_CRCB_H2V2, /* Y and CrCb, pseudo planar w/ Cr is in MSB */ - MDP_YCRYCB_H2V1, /* YCrYCb interleave */ - MDP_Y_CRCB_H2V1, /* Y and CrCb, pseduo planar w/ Cr is in MSB */ - MDP_Y_CBCR_H2V1, /* Y and CrCb, pseduo planar w/ Cr is in MSB */ - MDP_RGBA_8888, /* ARGB 888 */ - MDP_BGRA_8888, /* ABGR 888 */ - MDP_RGBX_8888, /* RGBX 888 */ - MDP_IMGTYPE_LIMIT /* Non valid image type after this enum */ + MDP_RGB_565, /* RGB 565 planer */ + MDP_XRGB_8888, /* RGB 888 padded */ + MDP_Y_CBCR_H2V2, /* Y and CbCr, pseudo planer w/ Cb is in MSB */ + MDP_ARGB_8888, /* ARGB 888 */ + MDP_RGB_888, /* RGB 888 planer */ + MDP_Y_CRCB_H2V2, /* Y and CrCb, pseudo planer w/ Cr is in MSB */ + MDP_YCRYCB_H2V1, /* YCrYCb interleave */ + MDP_Y_CRCB_H2V1, /* Y and CrCb, pseduo planer w/ Cr is in MSB */ + MDP_Y_CBCR_H2V1, /* Y and CrCb, pseduo planer w/ Cr is in MSB */ + MDP_RGBA_8888, /* ARGB 888 */ + MDP_BGRA_8888, /* ABGR 888 */ + MDP_RGBX_8888, /* RGBX 888 */ + MDP_Y_CRCB_H2V2_TILE, /* Y and CrCb, pseudo planer tile */ + MDP_Y_CBCR_H2V2_TILE, /* Y and CbCr, pseudo planer tile */ + MDP_Y_CR_CB_H2V2, /* Y, Cr and Cb, planar */ + MDP_Y_CB_CR_H2V2, /* Y, Cb and Cr, planar */ + MDP_Y_CRCB_H1V1, /* Y and CrCb, pseduo planer w/ Cr is in MSB */ + MDP_Y_CBCR_H1V1, /* Y and CbCr, pseduo planer w/ Cb is in MSB */ + MDP_IMGTYPE_LIMIT, + MDP_BGR_565 = MDP_IMGTYPE2_START, /* BGR 565 planer */ + MDP_FB_FORMAT, /* framebuffer format */ + MDP_IMGTYPE_LIMIT2 /* Non valid image type after this enum */ }; enum { @@ -41,39 +89,182 @@ enum { FB_IMG, }; -/* flag values */ -#define MDP_ROT_NOP 0 -#define MDP_FLIP_LR 0x1 -#define MDP_FLIP_UD 0x2 -#define MDP_ROT_90 0x4 -#define MDP_ROT_180 (MDP_FLIP_UD|MDP_FLIP_LR) -#define MDP_ROT_270 (MDP_ROT_90|MDP_FLIP_UD|MDP_FLIP_LR) -#define MDP_DITHER 0x8 -#define MDP_BLUR 0x10 +/* mdp_blit_req flag values */ +#define MDP_ROT_NOP 0 +#define MDP_FLIP_LR 0x1 +#define MDP_FLIP_UD 0x2 +#define MDP_ROT_90 0x4 +#define MDP_ROT_180 (MDP_FLIP_UD|MDP_FLIP_LR) +#define MDP_ROT_270 (MDP_ROT_90|MDP_FLIP_UD|MDP_FLIP_LR) +#define MDP_DITHER 0x8 +#define MDP_BLUR 0x10 +#define MDP_BLEND_FG_PREMULT 0x20000 +#define MDP_DEINTERLACE 0x80000000 +#define MDP_SHARPENING 0x40000000 +#define MDP_NO_DMA_BARRIER_START 0x20000000 +#define MDP_NO_DMA_BARRIER_END 0x10000000 +#define MDP_NO_BLIT 0x08000000 +#define MDP_BLIT_WITH_DMA_BARRIERS 0x000 +#define MDP_BLIT_WITH_NO_DMA_BARRIERS \ + (MDP_NO_DMA_BARRIER_START | MDP_NO_DMA_BARRIER_END) +#define MDP_BLIT_SRC_GEM 0x04000000 +#define MDP_BLIT_DST_GEM 0x02000000 +#define MDP_BLIT_NON_CACHED 0x01000000 +#define MDP_OV_PIPE_SHARE 0x00800000 +#define MDP_DEINTERLACE_ODD 0x00400000 +#define MDP_OV_PLAY_NOWAIT 0x00200000 +#define MDP_SOURCE_ROTATED_90 0x00100000 + +#define MDP_TRANSP_NOP 0xffffffff +#define MDP_ALPHA_NOP 0xff -#define MDP_TRANSP_NOP 0xffffffff -#define MDP_ALPHA_NOP 0xff +#define MDP_FB_PAGE_PROTECTION_NONCACHED (0) +#define MDP_FB_PAGE_PROTECTION_WRITECOMBINE (1) +#define MDP_FB_PAGE_PROTECTION_WRITETHROUGHCACHE (2) +#define MDP_FB_PAGE_PROTECTION_WRITEBACKCACHE (3) +#define MDP_FB_PAGE_PROTECTION_WRITEBACKWACACHE (4) +/* Sentinel: Don't use! */ +#define MDP_FB_PAGE_PROTECTION_INVALID (5) +/* Count of the number of MDP_FB_PAGE_PROTECTION_... values. */ +#define MDP_NUM_FB_PAGE_PROTECTION_VALUES (5) struct mdp_rect { - u32 x, y, w, h; + uint32_t x; + uint32_t y; + uint32_t w; + uint32_t h; }; struct mdp_img { - u32 width, height, format, offset; + uint32_t width; + uint32_t height; + uint32_t format; + uint32_t offset; int memory_id; /* the file descriptor */ + uint32_t priv; }; +/* + * {3x3} + {3} ccs matrix + */ + +#define MDP_CCS_RGB2YUV 0 +#define MDP_CCS_YUV2RGB 1 + +#define MDP_CCS_SIZE 9 +#define MDP_BV_SIZE 3 + +struct mdp_ccs { + int direction; /* MDP_CCS_RGB2YUV or YUV2RGB */ + uint16_t ccs[MDP_CCS_SIZE]; /* 3x3 color coefficients */ + uint16_t bv[MDP_BV_SIZE]; /* 1x3 bias vector */ +}; + +/* The version of the mdp_blit_req structure so that + * user applications can selectively decide which functionality + * to include + */ + +#define MDP_BLIT_REQ_VERSION 2 + struct mdp_blit_req { struct mdp_img src; struct mdp_img dst; struct mdp_rect src_rect; struct mdp_rect dst_rect; - u32 alpha, transp_mask, flags; + uint32_t alpha; + uint32_t transp_mask; + uint32_t flags; + int sharpening_strength; /* -127 <--> 127, default 64 */ }; struct mdp_blit_req_list { - u32 count; + uint32_t count; struct mdp_blit_req req[]; }; -#endif /* _MSM_MDP_H_ */ +#define MSMFB_DATA_VERSION 2 + +struct msmfb_data { + uint32_t offset; + int memory_id; + int id; + uint32_t flags; + uint32_t priv; +}; + +#define MSMFB_NEW_REQUEST -1 + +struct msmfb_overlay_data { + uint32_t id; + struct msmfb_data data; + uint32_t version_key; + struct msmfb_data plane1_data; + struct msmfb_data plane2_data; +}; + +struct msmfb_img { + uint32_t width; + uint32_t height; + uint32_t format; +}; + +struct dpp_ctrl { + /* + *'sharp_strength' has inputs = -128 <-> 127 + * Increasingly positive values correlate with increasingly sharper + * picture. Increasingly negative values correlate with increasingly + * smoothed picture. + */ + int8_t sharp_strength; +}; + +struct mdp_overlay { + struct msmfb_img src; + struct mdp_rect src_rect; + struct mdp_rect dst_rect; + uint32_t z_order; /* stage number */ + uint32_t is_fg; /* control alpha & transp */ + uint32_t alpha; + uint32_t transp_mask; + uint32_t flags; + uint32_t id; + uint32_t user_data[8]; + struct dpp_ctrl dpp; +}; + +struct msmfb_overlay_3d { + uint32_t is_3d; + uint32_t width; + uint32_t height; +}; + + +struct msmfb_overlay_blt { + uint32_t enable; + uint32_t offset; + uint32_t width; + uint32_t height; + uint32_t bpp; +}; + +struct mdp_histogram { + uint32_t frame_cnt; + uint32_t bin_cnt; + uint32_t *r; + uint32_t *g; + uint32_t *b; +}; + +struct mdp_page_protection { + uint32_t page_protection; +}; + +#ifdef __KERNEL__ + +/* get the framebuffer physical address information */ +int get_fb_phys_info(unsigned long *start, unsigned long *len, int fb_num); + +#endif + +#endif /*_MSM_MDP_H_*/ diff --git a/include/linux/msm_q6vdec.h b/include/linux/msm_q6vdec.h new file mode 100644 index 00000000000..47b8163fe01 --- /dev/null +++ b/include/linux/msm_q6vdec.h @@ -0,0 +1,277 @@ +#ifndef _MSM_VDEC_H_ +#define _MSM_VDEC_H_ + +#include + +#define VDEC_IOCTL_MAGIC 'v' + +#define VDEC_IOCTL_INITIALIZE _IOWR(VDEC_IOCTL_MAGIC, 1, struct vdec_init) +#define VDEC_IOCTL_SETBUFFERS _IOW(VDEC_IOCTL_MAGIC, 2, struct vdec_buffer) +#define VDEC_IOCTL_QUEUE _IOWR(VDEC_IOCTL_MAGIC, 3, \ + struct vdec_input_buf) +#define VDEC_IOCTL_REUSEFRAMEBUFFER _IOW(VDEC_IOCTL_MAGIC, 4, unsigned int) +#define VDEC_IOCTL_FLUSH _IOW(VDEC_IOCTL_MAGIC, 5, unsigned int) +#define VDEC_IOCTL_EOS _IO(VDEC_IOCTL_MAGIC, 6) +#define VDEC_IOCTL_GETMSG _IOR(VDEC_IOCTL_MAGIC, 7, struct vdec_msg) +#define VDEC_IOCTL_CLOSE _IO(VDEC_IOCTL_MAGIC, 8) +#define VDEC_IOCTL_FREEBUFFERS _IOW(VDEC_IOCTL_MAGIC, 9, struct vdec_buf_info) +#define VDEC_IOCTL_GETDECATTRIBUTES _IOR(VDEC_IOCTL_MAGIC, 10, \ + struct vdec_dec_attributes) +#define VDEC_IOCTL_GETVERSION _IOR(VDEC_IOCTL_MAGIC, 11, struct vdec_version) +#define VDEC_IOCTL_SETPROPERTY _IOW \ + (VDEC_IOCTL_MAGIC, 12, struct vdec_property_info) +#define VDEC_IOCTL_GETPROPERTY _IOR \ + (VDEC_IOCTL_MAGIC, 13, struct vdec_property_info) +#define VDEC_IOCTL_PERFORMANCE_CHANGE_REQ _IOW(VDEC_IOCTL_MAGIC, 14, \ + unsigned int) + +enum { + VDEC_FRAME_DECODE_OK, + VDEC_FRAME_DECODE_ERR, + VDEC_FATAL_ERR, + VDEC_FLUSH_FINISH, + VDEC_EOS, + VDEC_FRAME_FLUSH, + VDEC_STREAM_SWITCH, + VDEC_SUSPEND_FINISH, + VDEC_BUFFER_CONSUMED +}; + +enum { + VDEC_FLUSH_INPUT, + VDEC_FLUSH_OUTPUT, + VDEC_FLUSH_ALL +}; + +enum { + VDEC_BUFFER_TYPE_INPUT, + VDEC_BUFFER_TYPE_OUTPUT, + VDEC_BUFFER_TYPE_INTERNAL1, + VDEC_BUFFER_TYPE_INTERNAL2, +}; + +enum { + VDEC_QUEUE_SUCCESS, + VDEC_QUEUE_FAILED, + VDEC_QUEUE_BADSTATE, +}; + +enum { + VDEC_COLOR_FORMAT_NV21 = 0x01, + VDEC_COLOR_FORMAT_NV21_YAMOTO = 0x02 + }; + +enum vdec_property_id { + VDEC_FOURCC, + VDEC_PROFILE, + VDEC_LEVEL, + VDEC_DIMENSIONS, + VDEC_CWIN, + VDEC_INPUT_BUF_REQ, + VDEC_OUTPUT_BUF_REQ, + VDEC_LUMA_CHROMA_STRIDE, + VDEC_NUM_DAL_PORTS, + VDEC_PRIORITY, + VDEC_FRAME_ALIGNMENT +}; + +enum { + PERF_REQUEST_SET_MIN = 0, + PERF_REQUEST_LOWER, + PERF_REQUEST_RAISE, + PERF_REQUEST_SET_MAX +}; + +struct vdec_input_buf_info { + u32 offset; + u32 data; + u32 size; + int timestamp_lo; + int timestamp_hi; + int avsync_state; + u32 flags; +}; + +struct vdec_buf_desc { + u32 bufsize; + u32 num_min_buffers; + u32 num_max_buffers; +}; + +struct vdec_buf_req { + u32 max_input_queue_size; + struct vdec_buf_desc input; + struct vdec_buf_desc output; + struct vdec_buf_desc dec_req1; + struct vdec_buf_desc dec_req2; +}; + +struct vdec_region_info { + u32 src_id; + u32 offset; + u32 size; +}; + +struct vdec_config { + u32 fourcc; /* video format */ + u32 width; /* source width */ + u32 height; /* source height */ + u32 order; /* render decoder order */ + u32 notify_enable; /* enable notify input buffer done event */ + u32 vc1_rowbase; + u32 h264_startcode_detect; + u32 h264_nal_len_size; + u32 postproc_flag; + u32 fruc_enable; + u32 color_format; /* used to set YUV color format */ +}; + +struct vdec_vc1_panscan_regions { + int num; + int width[4]; + int height[4]; + int xoffset[4]; + int yoffset[4]; +}; + +struct vdec_cropping_window { + u32 x1; + u32 y1; + u32 x2; + u32 y2; +}; + +struct vdec_frame_info { + u32 status; /* video decode status */ + u32 offset; /* buffer offset */ + u32 data1; /* user data field 1 */ + u32 data2; /* user data field 2 */ + int timestamp_lo; /* lower 32 bits timestamp, in msec */ + int timestamp_hi; /* higher 32 bits timestamp, in msec */ + int cal_timestamp_lo; /* lower 32 bits cal timestamp, in msec */ + int cal_timestamp_hi; /* higher 32 bits cal timestamp, in msec */ + u32 dec_width; /* frame roi width */ + u32 dec_height; /* frame roi height */ + struct vdec_cropping_window cwin; /* The frame cropping window */ + u32 picture_type[2]; /* picture coding type */ + u32 picture_format; /* picture coding format */ + u32 vc1_rangeY; /* luma range mapping */ + u32 vc1_rangeUV; /* chroma range mapping */ + u32 picture_resolution; /* scaling factor */ + u32 frame_disp_repeat; /* how often repeated by disp */ + u32 repeat_first_field; /* repeat 1st field after 2nd */ + u32 top_field_first; /* top field displayed first */ + u32 interframe_interp; /* not for inter-frame interp */ + struct vdec_vc1_panscan_regions panscan; /* pan region */ + u32 concealed_macblk_num; /* number of concealed macro blk */ + u32 flags; /* input flags */ + u32 performance_stats; /* performance statistics returned by decoder */ + u32 data3; /* user data field 3 */ +}; + +struct vdec_buf_info { + u32 buf_type; + struct vdec_region_info region; + u32 num_buf; + u32 islast; +}; + +struct vdec_buffer { + u32 pmem_id; + struct vdec_buf_info buf; +}; + +struct vdec_sequence { + u8 *header; + u32 len; +}; + +struct vdec_config_sps { + struct vdec_config cfg; + struct vdec_sequence seq; +}; + +#define VDEC_MSG_REUSEINPUTBUFFER 1 +#define VDEC_MSG_FRAMEDONE 2 + +struct vdec_msg { + u32 id; + + union { + /* id = VDEC_MSG_REUSEINPUTBUFFER */ + u32 buf_id; + /* id = VDEC_MSG_FRAMEDONE */ + struct vdec_frame_info vfr_info; + }; +}; + +struct vdec_init { + struct vdec_config_sps sps_cfg; + struct vdec_buf_req *buf_req; +}; + +struct vdec_input_buf { + u32 pmem_id; + struct vdec_input_buf_info buffer; + struct vdec_queue_status *queue_status; +}; + +struct vdec_queue_status { + u32 status; +}; + +struct vdec_dec_attributes { + u32 fourcc; + u32 profile; + u32 level; + u32 dec_pic_width; + u32 dec_pic_height; + struct vdec_buf_desc input; + struct vdec_buf_desc output; + struct vdec_buf_desc dec_req1; + struct vdec_buf_desc dec_req2; +}; + +struct vdec_version { + u32 major; + u32 minor; +}; + +struct dal_vdec_rectangle { + u32 width; + u32 height; +}; + +struct stride_type { + u32 luma; + u32 chroma; +}; + +struct frame_alignment_type { + u32 luma_width; + u32 luma_height; + u32 chroma_width; + u32 chroma_height; + u32 chroma_offset; +}; + +union vdec_property { + u32 fourcc; + u32 profile; + u32 level; + struct dal_vdec_rectangle dim; + struct vdec_cropping_window cw; + struct vdec_buf_desc input_req; + struct vdec_buf_desc output_req; + struct stride_type stride; + u32 num_dal_ports; + u32 priority; + struct frame_alignment_type frame_alignment; + u32 def_type; +}; + +struct vdec_property_info { + enum vdec_property_id id; + union vdec_property property; +}; +#endif /* _MSM_VDEC_H_ */ diff --git a/include/linux/msm_q6venc.h b/include/linux/msm_q6venc.h new file mode 100644 index 00000000000..c6bf20c7b1b --- /dev/null +++ b/include/linux/msm_q6venc.h @@ -0,0 +1,303 @@ +#ifndef _MSM_VENC_H_ +#define _MSM_VENC_H_ + +#include + +#define VENC_MAX_RECON_BUFFERS 2 + +#define VENC_FLAG_EOS 0x00000001 +#define VENC_FLAG_END_OF_FRAME 0x00000010 +#define VENC_FLAG_SYNC_FRAME 0x00000020 +#define VENC_FLAG_EXTRA_DATA 0x00000040 +#define VENC_FLAG_CODEC_CONFIG 0x00000080 + +enum venc_flush_type { + VENC_FLUSH_INPUT, + VENC_FLUSH_OUTPUT, + VENC_FLUSH_ALL +}; + +enum venc_state_type { + VENC_STATE_PAUSE = 0x1, + VENC_STATE_START = 0x2, + VENC_STATE_STOP = 0x4 +}; + +enum venc_event_type_enum { + VENC_EVENT_START_STATUS, + VENC_EVENT_STOP_STATUS, + VENC_EVENT_SUSPEND_STATUS, + VENC_EVENT_RESUME_STATUS, + VENC_EVENT_FLUSH_STATUS, + VENC_EVENT_RELEASE_INPUT, + VENC_EVENT_DELIVER_OUTPUT, + VENC_EVENT_UNKNOWN_STATUS +}; + +enum venc_status_code { + VENC_STATUS_SUCCESS, + VENC_STATUS_ERROR, + VENC_STATUS_INVALID_STATE, + VENC_STATUS_FLUSHING, + VENC_STATUS_INVALID_PARAM, + VENC_STATUS_CMD_QUEUE_FULL, + VENC_STATUS_CRITICAL, + VENC_STATUS_INSUFFICIENT_RESOURCES, + VENC_STATUS_TIMEOUT +}; + +enum venc_msg_code { + VENC_MSG_INDICATION, + VENC_MSG_INPUT_BUFFER_DONE, + VENC_MSG_OUTPUT_BUFFER_DONE, + VENC_MSG_NEED_OUTPUT_BUFFER, + VENC_MSG_FLUSH, + VENC_MSG_START, + VENC_MSG_STOP, + VENC_MSG_PAUSE, + VENC_MSG_RESUME, + VENC_MSG_STOP_READING_MSG +}; + +enum venc_error_code { + VENC_S_SUCCESS, + VENC_S_EFAIL, + VENC_S_EFATAL, + VENC_S_EBADPARAM, + VENC_S_EINVALSTATE, + VENC_S_ENOSWRES, + VENC_S_ENOHWRES, + VENC_S_EBUFFREQ, + VENC_S_EINVALCMD, + VENC_S_ETIMEOUT, + VENC_S_ENOREATMPT, + VENC_S_ENOPREREQ, + VENC_S_ECMDQFULL, + VENC_S_ENOTSUPP, + VENC_S_ENOTIMPL, + VENC_S_ENOTPMEM, + VENC_S_EFLUSHED, + VENC_S_EINSUFBUF, + VENC_S_ESAMESTATE, + VENC_S_EINVALTRANS +}; + +enum venc_mem_region_enum { + VENC_PMEM_EBI1, + VENC_PMEM_SMI +}; + +struct venc_buf_type { + u32 region; + u32 phys; + u32 size; + int offset; +}; + +struct venc_qp_range { + u32 min_qp; + u32 max_qp; +}; + +struct venc_frame_rate { + u32 frame_rate_num; + u32 frame_rate_den; +}; + +struct venc_slice_info { + u32 slice_mode; + u32 units_per_slice; +}; + +struct venc_extra_data { + u32 slice_extra_data_flag; + u32 slice_client_data1; + u32 slice_client_data2; + u32 slice_client_data3; + u32 none_extra_data_flag; + u32 none_client_data1; + u32 none_client_data2; + u32 none_client_data3; +}; + +struct venc_common_config { + u32 standard; + u32 input_frame_height; + u32 input_frame_width; + u32 output_frame_height; + u32 output_frame_width; + u32 rotation_angle; + u32 intra_period; + u32 rate_control; + struct venc_frame_rate frame_rate; + u32 bitrate; + struct venc_qp_range qp_range; + u32 iframe_qp; + u32 pframe_qp; + struct venc_slice_info slice_config; + struct venc_extra_data extra_data; +}; + +struct venc_nonio_buf_config { + struct venc_buf_type recon_buf1; + struct venc_buf_type recon_buf2; + struct venc_buf_type wb_buf; + struct venc_buf_type cmd_buf; + struct venc_buf_type vlc_buf; +}; + +struct venc_mpeg4_config { + u32 profile; + u32 level; + u32 time_resolution; + u32 ac_prediction; + u32 hec_interval; + u32 data_partition; + u32 short_header; + u32 rvlc_enable; +}; + +struct venc_h263_config { + u32 profile; + u32 level; +}; + +struct venc_h264_config { + u32 profile; + u32 level; + u32 max_nal; + u32 idr_period; +}; + +struct venc_pmem { + int src; + int fd; + u32 offset; + void *virt; + void *phys; + u32 size; +}; + +struct venc_buffer { + unsigned char *ptr_buffer; + u32 size; + u32 len; + u32 offset; + long long time_stamp; + u32 flags; + u32 client_data; + +}; + +struct venc_buffers { + struct venc_pmem recon_buf[VENC_MAX_RECON_BUFFERS]; + struct venc_pmem wb_buf; + struct venc_pmem cmd_buf; + struct venc_pmem vlc_buf; +}; + +struct venc_buffer_flush { + u32 flush_mode; +}; + +union venc_msg_data { + struct venc_buffer buf; + struct venc_buffer_flush flush_ret; + +}; + +struct venc_msg { + u32 status_code; + u32 msg_code; + u32 msg_data_size; + union venc_msg_data msg_data; +}; + +union venc_codec_config { + struct venc_mpeg4_config mpeg4_params; + struct venc_h263_config h263_params; + struct venc_h264_config h264_params; +}; + +struct venc_q6_config { + struct venc_common_config config_params; + union venc_codec_config codec_params; + struct venc_nonio_buf_config buf_params; + void *callback_event; +}; + +struct venc_hdr_config { + struct venc_common_config config_params; + union venc_codec_config codec_params; +}; + +struct venc_init_config { + struct venc_q6_config q6_config; + struct venc_buffers q6_bufs; +}; + +struct venc_seq_config { + int size; + struct venc_pmem buf; + struct venc_q6_config q6_config; +}; + +struct venc_version { + u32 major; + u32 minor; +}; + +#define VENC_IOCTL_MAGIC 'V' + +#define VENC_IOCTL_CMD_READ_NEXT_MSG \ + _IOWR(VENC_IOCTL_MAGIC, 1, struct venc_msg) + +#define VENC_IOCTL_CMD_STOP_READ_MSG _IO(VENC_IOCTL_MAGIC, 2) + +#define VENC_IOCTL_SET_INPUT_BUFFER \ + _IOW(VENC_IOCTL_MAGIC, 3, struct venc_pmem) + +#define VENC_IOCTL_SET_OUTPUT_BUFFER \ + _IOW(VENC_IOCTL_MAGIC, 4, struct venc_pmem) + +#define VENC_IOCTL_CMD_START _IOW(VENC_IOCTL_MAGIC, 5, struct venc_init_config) + +#define VENC_IOCTL_CMD_ENCODE_FRAME \ + _IOW(VENC_IOCTL_MAGIC, 6, struct venc_buffer) + +#define VENC_IOCTL_CMD_FILL_OUTPUT_BUFFER \ + _IOW(VENC_IOCTL_MAGIC, 7, struct venc_buffer) + +#define VENC_IOCTL_CMD_FLUSH \ + _IOW(VENC_IOCTL_MAGIC, 8, struct venc_buffer_flush) + +#define VENC_IOCTL_CMD_PAUSE _IO(VENC_IOCTL_MAGIC, 9) + +#define VENC_IOCTL_CMD_RESUME _IO(VENC_IOCTL_MAGIC, 10) + +#define VENC_IOCTL_CMD_STOP _IO(VENC_IOCTL_MAGIC, 11) + +#define VENC_IOCTL_SET_INTRA_PERIOD \ + _IOW(VENC_IOCTL_MAGIC, 12, int) + +#define VENC_IOCTL_CMD_REQUEST_IFRAME _IO(VENC_IOCTL_MAGIC, 13) + +#define VENC_IOCTL_GET_SEQUENCE_HDR \ + _IOWR(VENC_IOCTL_MAGIC, 14, struct venc_seq_config) + +#define VENC_IOCTL_SET_INTRA_REFRESH \ + _IOW(VENC_IOCTL_MAGIC, 15, int) + +#define VENC_IOCTL_SET_FRAME_RATE \ + _IOW(VENC_IOCTL_MAGIC, 16, struct venc_frame_rate) + +#define VENC_IOCTL_SET_TARGET_BITRATE \ + _IOW(VENC_IOCTL_MAGIC, 17, int) + +#define VENC_IOCTL_SET_QP_RANGE \ + _IOW(VENC_IOCTL_MAGIC, 18, struct venc_qp_range) + +#define VENC_IOCTL_GET_VERSION \ + _IOR(VENC_IOCTL_MAGIC, 19, struct venc_version) + +#endif diff --git a/include/linux/msm_rmnet.h b/include/linux/msm_rmnet.h new file mode 100644 index 00000000000..9f524649102 --- /dev/null +++ b/include/linux/msm_rmnet.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _MSM_RMNET_H_ +#define _MSM_RMNET_H_ + +/* Bitmap macros for RmNET driver operation mode. */ +#define RMNET_MODE_NONE (0x00) +#define RMNET_MODE_LLP_ETH (0x01) +#define RMNET_MODE_LLP_IP (0x02) +#define RMNET_MODE_QOS (0x04) +#define RMNET_MODE_MASK (RMNET_MODE_LLP_ETH | \ + RMNET_MODE_LLP_IP | \ + RMNET_MODE_QOS) + +#define RMNET_IS_MODE_QOS(mode) \ + ((mode & RMNET_MODE_QOS) == RMNET_MODE_QOS) +#define RMNET_IS_MODE_IP(mode) \ + ((mode & RMNET_MODE_LLP_IP) == RMNET_MODE_LLP_IP) + +/* IOCTL command enum + * Values chosen to not conflict with other drivers in the ecosystem */ +enum rmnet_ioctl_cmds_e { + RMNET_IOCTL_SET_LLP_ETHERNET = 0x000089F1, /* Set Ethernet protocol */ + RMNET_IOCTL_SET_LLP_IP = 0x000089F2, /* Set RAWIP protocol */ + RMNET_IOCTL_GET_LLP = 0x000089F3, /* Get link protocol */ + RMNET_IOCTL_SET_QOS_ENABLE = 0x000089F4, /* Set QoS header enabled */ + RMNET_IOCTL_SET_QOS_DISABLE = 0x000089F5, /* Set QoS header disabled*/ + RMNET_IOCTL_GET_QOS = 0x000089F6, /* Get QoS header state */ + RMNET_IOCTL_GET_OPMODE = 0x000089F7, /* Get operation mode */ + RMNET_IOCTL_OPEN = 0x000089F8, /* Open transport port */ + RMNET_IOCTL_CLOSE = 0x000089F9, /* Close transport port */ + RMNET_IOCTL_MAX +}; + +/* QMI QoS header definition */ +#define QMI_QOS_HDR_S __attribute((__packed__)) qmi_qos_hdr_s +struct QMI_QOS_HDR_S { + unsigned char version; + unsigned char flags; + unsigned long flow_id; +}; + +#endif /* _MSM_RMNET_H_ */ diff --git a/include/linux/msm_rotator.h b/include/linux/msm_rotator.h new file mode 100644 index 00000000000..98352b722a1 --- /dev/null +++ b/include/linux/msm_rotator.h @@ -0,0 +1,57 @@ +#ifndef __MSM_ROTATOR_H__ + +#include +#include + +#define MSM_ROTATOR_IOCTL_MAGIC 'R' + +#define MSM_ROTATOR_IOCTL_START \ + _IOWR(MSM_ROTATOR_IOCTL_MAGIC, 1, struct msm_rotator_img_info) +#define MSM_ROTATOR_IOCTL_ROTATE \ + _IOW(MSM_ROTATOR_IOCTL_MAGIC, 2, struct msm_rotator_data_info) +#define MSM_ROTATOR_IOCTL_FINISH \ + _IOW(MSM_ROTATOR_IOCTL_MAGIC, 3, int) + +#define ROTATOR_VERSION_01 0xA5B4C301 + +enum rotator_clk_type { + ROTATOR_CORE_CLK, + ROTATOR_AXI_CLK, + ROTATOR_PCLK, + ROTATOR_IMEM_CLK +}; + +struct msm_rotator_img_info { + unsigned int session_id; + struct msmfb_img src; + struct msmfb_img dst; + struct mdp_rect src_rect; + unsigned int dst_x; + unsigned int dst_y; + unsigned char rotations; + int enable; +}; + +struct msm_rotator_data_info { + int session_id; + struct msmfb_data src; + struct msmfb_data dst; + unsigned int version_key; + struct msmfb_data src_chroma; + struct msmfb_data dst_chroma; +}; + +struct msm_rot_clocks { + const char *clk_name; + enum rotator_clk_type clk_type; + unsigned int clk_rate; +}; + +struct msm_rotator_platform_data { + unsigned int number_of_clocks; + unsigned int hardware_version_number; + struct msm_rot_clocks *rotator_clks; + const char *regulator_name; +}; +#endif + diff --git a/include/linux/msm_rpcrouter.h b/include/linux/msm_rpcrouter.h new file mode 100644 index 00000000000..01d3809e574 --- /dev/null +++ b/include/linux/msm_rpcrouter.h @@ -0,0 +1,50 @@ +/* include/linux/msm_rpcrouter.h + * + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Copyright (C) 2007 Google, Inc. + * Author: San Mehat + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __LINUX_MSM_RPCROUTER_H +#define __LINUX_MSM_RPCROUTER_H + +#include +#include + +#define RPC_ROUTER_VERSION_V1 0x00010000 + +struct rpcrouter_ioctl_server_args { + uint32_t prog; + uint32_t vers; +}; + +#define RPC_ROUTER_IOCTL_MAGIC (0xC1) + +#define RPC_ROUTER_IOCTL_GET_VERSION \ + _IOR(RPC_ROUTER_IOCTL_MAGIC, 0, unsigned int) + +#define RPC_ROUTER_IOCTL_GET_MTU \ + _IOR(RPC_ROUTER_IOCTL_MAGIC, 1, unsigned int) + +#define RPC_ROUTER_IOCTL_REGISTER_SERVER \ + _IOWR(RPC_ROUTER_IOCTL_MAGIC, 2, unsigned int) + +#define RPC_ROUTER_IOCTL_UNREGISTER_SERVER \ + _IOWR(RPC_ROUTER_IOCTL_MAGIC, 3, unsigned int) + +#define RPC_ROUTER_IOCTL_CLEAR_NETRESET \ + _IOWR(RPC_ROUTER_IOCTL_MAGIC, 4, unsigned int) + +#define RPC_ROUTER_IOCTL_GET_CURR_PKT_SIZE \ + _IOR(RPC_ROUTER_IOCTL_MAGIC, 5, unsigned int) + +#endif diff --git a/include/linux/msm_smd_pkt.h b/include/linux/msm_smd_pkt.h new file mode 100644 index 00000000000..dc7328f6672 --- /dev/null +++ b/include/linux/msm_smd_pkt.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __LINUX_MSM_SMD_PKT_H +#define __LINUX_MSM_SMD_PKT_H + +#include + +#define SMD_PKT_IOCTL_MAGIC (0xC2) + +#define SMD_PKT_IOCTL_BLOCKING_WRITE \ + _IOR(SMD_PKT_IOCTL_MAGIC, 0, unsigned int) + +#endif /* __LINUX_MSM_SMD_PKT_H */ diff --git a/include/linux/msm_ssbi.h b/include/linux/msm_ssbi.h new file mode 100644 index 00000000000..e90398a17e8 --- /dev/null +++ b/include/linux/msm_ssbi.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2010 Google, Inc. + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * Author: Dima Zavin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LINUX_MSM_SSBI_H +#define _LINUX_MSM_SSBI_H + +#include + +struct msm_ssbi_slave_info { + const char *name; + void *platform_data; +}; + +enum msm_ssbi_controller_type { + MSM_SBI_CTRL_SSBI = 0, + MSM_SBI_CTRL_SSBI2, + MSM_SBI_CTRL_PMIC_ARBITER, + FSM_SBI_CTRL_SSBI, +}; + +struct msm_ssbi_platform_data { + struct msm_ssbi_slave_info slave; + enum msm_ssbi_controller_type controller_type; +}; + +#ifdef CONFIG_MSM_SSBI +int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len); +int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len); +#else +static inline int msm_ssbi_write(struct device *dev, u16 addr, u8 *buf, int len) +{ + return -ENXIO; +} +static inline int msm_ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) +{ + return -ENXIO; +} +#endif +#endif diff --git a/include/linux/msm_vidc_dec.h b/include/linux/msm_vidc_dec.h new file mode 100644 index 00000000000..fb781f42eff --- /dev/null +++ b/include/linux/msm_vidc_dec.h @@ -0,0 +1,547 @@ +#ifndef _MSM_VIDC_DEC_H_ +#define _MSM_VIDC_DEC_H_ + +#include +#include + +/* STATUS CODES */ +/* Base value for status codes */ +#define VDEC_S_BASE 0x40000000 +/* Success */ +#define VDEC_S_SUCCESS (VDEC_S_BASE) +/* General failure */ +#define VDEC_S_EFAIL (VDEC_S_BASE + 1) +/* Fatal irrecoverable failure. Need to tear down session. */ +#define VDEC_S_EFATAL (VDEC_S_BASE + 2) +/* Error detected in the passed parameters */ +#define VDEC_S_EBADPARAM (VDEC_S_BASE + 3) +/* Command called in invalid state. */ +#define VDEC_S_EINVALSTATE (VDEC_S_BASE + 4) + /* Insufficient OS resources - thread, memory etc. */ +#define VDEC_S_ENOSWRES (VDEC_S_BASE + 5) + /* Insufficient HW resources - core capacity maxed out. */ +#define VDEC_S_ENOHWRES (VDEC_S_BASE + 6) +/* Invalid command called */ +#define VDEC_S_EINVALCMD (VDEC_S_BASE + 7) +/* Command timeout. */ +#define VDEC_S_ETIMEOUT (VDEC_S_BASE + 8) +/* Pre-requirement is not met for API. */ +#define VDEC_S_ENOPREREQ (VDEC_S_BASE + 9) +/* Command queue is full. */ +#define VDEC_S_ECMDQFULL (VDEC_S_BASE + 10) +/* Command is not supported by this driver */ +#define VDEC_S_ENOTSUPP (VDEC_S_BASE + 11) +/* Command is not implemented by thedriver. */ +#define VDEC_S_ENOTIMPL (VDEC_S_BASE + 12) +/* Command is not implemented by the driver. */ +#define VDEC_S_BUSY (VDEC_S_BASE + 13) + +#define VDEC_INTF_VER 1 +#define VDEC_MSG_BASE 0x0000000 +/* Codes to identify asynchronous message responses and events that driver + wants to communicate to the app.*/ +#define VDEC_MSG_INVALID (VDEC_MSG_BASE + 0) +#define VDEC_MSG_RESP_INPUT_BUFFER_DONE (VDEC_MSG_BASE + 1) +#define VDEC_MSG_RESP_OUTPUT_BUFFER_DONE (VDEC_MSG_BASE + 2) +#define VDEC_MSG_RESP_INPUT_FLUSHED (VDEC_MSG_BASE + 3) +#define VDEC_MSG_RESP_OUTPUT_FLUSHED (VDEC_MSG_BASE + 4) +#define VDEC_MSG_RESP_FLUSH_INPUT_DONE (VDEC_MSG_BASE + 5) +#define VDEC_MSG_RESP_FLUSH_OUTPUT_DONE (VDEC_MSG_BASE + 6) +#define VDEC_MSG_RESP_START_DONE (VDEC_MSG_BASE + 7) +#define VDEC_MSG_RESP_STOP_DONE (VDEC_MSG_BASE + 8) +#define VDEC_MSG_RESP_PAUSE_DONE (VDEC_MSG_BASE + 9) +#define VDEC_MSG_RESP_RESUME_DONE (VDEC_MSG_BASE + 10) +#define VDEC_MSG_RESP_RESOURCE_LOADED (VDEC_MSG_BASE + 11) +#define VDEC_EVT_RESOURCES_LOST (VDEC_MSG_BASE + 12) +#define VDEC_MSG_EVT_CONFIG_CHANGED (VDEC_MSG_BASE + 13) +#define VDEC_MSG_EVT_HW_ERROR (VDEC_MSG_BASE + 14) +#define VDEC_MSG_EVT_INFO_CONFIG_CHANGED (VDEC_MSG_BASE + 15) + +/*Buffer flags bits masks.*/ +#define VDEC_BUFFERFLAG_EOS 0x00000001 +#define VDEC_BUFFERFLAG_DECODEONLY 0x00000004 +#define VDEC_BUFFERFLAG_DATACORRUPT 0x00000008 +#define VDEC_BUFFERFLAG_ENDOFFRAME 0x00000010 +#define VDEC_BUFFERFLAG_SYNCFRAME 0x00000020 +#define VDEC_BUFFERFLAG_EXTRADATA 0x00000040 +#define VDEC_BUFFERFLAG_CODECCONFIG 0x00000080 + +/*Post processing flags bit masks*/ +#define VDEC_EXTRADATA_NONE 0x001 +#define VDEC_EXTRADATA_QP 0x004 +#define VDEC_EXTRADATA_MB_ERROR_MAP 0x008 +#define VDEC_EXTRADATA_SEI 0x010 +#define VDEC_EXTRADATA_VUI 0x020 +#define VDEC_EXTRADATA_VC1 0x040 + +#define VDEC_CMDBASE 0x800 +#define VDEC_CMD_SET_INTF_VERSION (VDEC_CMDBASE) + +#define VDEC_IOCTL_MAGIC 'v' + +struct vdec_ioctl_msg { + void __user *in; + void __user *out; +}; + +/* CMD params: InputParam:enum vdec_codec + OutputParam: struct vdec_profile_level*/ +#define VDEC_IOCTL_GET_PROFILE_LEVEL_SUPPORTED \ + _IOWR(VDEC_IOCTL_MAGIC, 0, struct vdec_ioctl_msg) + +/*CMD params:InputParam: NULL + OutputParam: uint32_t(bitmask)*/ +#define VDEC_IOCTL_GET_INTERLACE_FORMAT \ + _IOR(VDEC_IOCTL_MAGIC, 1, struct vdec_ioctl_msg) + +/* CMD params: InputParam: enum vdec_codec + OutputParam: struct vdec_profile_level*/ +#define VDEC_IOCTL_GET_CURRENT_PROFILE_LEVEL \ + _IOWR(VDEC_IOCTL_MAGIC, 2, struct vdec_ioctl_msg) + +/*CMD params: SET: InputParam: enum vdec_output_fromat OutputParam: NULL + GET: InputParam: NULL OutputParam: enum vdec_output_fromat*/ +#define VDEC_IOCTL_SET_OUTPUT_FORMAT \ + _IOWR(VDEC_IOCTL_MAGIC, 3, struct vdec_ioctl_msg) +#define VDEC_IOCTL_GET_OUTPUT_FORMAT \ + _IOWR(VDEC_IOCTL_MAGIC, 4, struct vdec_ioctl_msg) + +/*CMD params: SET: InputParam: enum vdec_codec OutputParam: NULL + GET: InputParam: NULL OutputParam: enum vdec_codec*/ +#define VDEC_IOCTL_SET_CODEC \ + _IOW(VDEC_IOCTL_MAGIC, 5, struct vdec_ioctl_msg) +#define VDEC_IOCTL_GET_CODEC \ + _IOR(VDEC_IOCTL_MAGIC, 6, struct vdec_ioctl_msg) + +/*CMD params: SET: InputParam: struct vdec_picsize outputparam: NULL + GET: InputParam: NULL outputparam: struct vdec_picsize*/ +#define VDEC_IOCTL_SET_PICRES \ + _IOW(VDEC_IOCTL_MAGIC, 7, struct vdec_ioctl_msg) +#define VDEC_IOCTL_GET_PICRES \ + _IOR(VDEC_IOCTL_MAGIC, 8, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_SET_EXTRADATA \ + _IOW(VDEC_IOCTL_MAGIC, 9, struct vdec_ioctl_msg) +#define VDEC_IOCTL_GET_EXTRADATA \ + _IOR(VDEC_IOCTL_MAGIC, 10, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_SET_SEQUENCE_HEADER \ + _IOW(VDEC_IOCTL_MAGIC, 11, struct vdec_ioctl_msg) + +/* CMD params: SET: InputParam - vdec_allocatorproperty, OutputParam - NULL + GET: InputParam - NULL, OutputParam - vdec_allocatorproperty*/ +#define VDEC_IOCTL_SET_BUFFER_REQ \ + _IOW(VDEC_IOCTL_MAGIC, 12, struct vdec_ioctl_msg) +#define VDEC_IOCTL_GET_BUFFER_REQ \ + _IOR(VDEC_IOCTL_MAGIC, 13, struct vdec_ioctl_msg) +/* CMD params: InputParam - vdec_buffer, OutputParam - uint8_t** */ +#define VDEC_IOCTL_ALLOCATE_BUFFER \ + _IOWR(VDEC_IOCTL_MAGIC, 14, struct vdec_ioctl_msg) +/* CMD params: InputParam - uint8_t *, OutputParam - NULL.*/ +#define VDEC_IOCTL_FREE_BUFFER \ + _IOW(VDEC_IOCTL_MAGIC, 15, struct vdec_ioctl_msg) + +/*CMD params: CMD: InputParam - struct vdec_setbuffer_cmd, OutputParam - NULL*/ +#define VDEC_IOCTL_SET_BUFFER \ + _IOW(VDEC_IOCTL_MAGIC, 16, struct vdec_ioctl_msg) + +/* CMD params: InputParam - struct vdec_fillbuffer_cmd, OutputParam - NULL*/ +#define VDEC_IOCTL_FILL_OUTPUT_BUFFER \ + _IOW(VDEC_IOCTL_MAGIC, 17, struct vdec_ioctl_msg) + +/*CMD params: InputParam - struct vdec_frameinfo , OutputParam - NULL*/ +#define VDEC_IOCTL_DECODE_FRAME \ + _IOW(VDEC_IOCTL_MAGIC, 18, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_LOAD_RESOURCES _IO(VDEC_IOCTL_MAGIC, 19) +#define VDEC_IOCTL_CMD_START _IO(VDEC_IOCTL_MAGIC, 20) +#define VDEC_IOCTL_CMD_STOP _IO(VDEC_IOCTL_MAGIC, 21) +#define VDEC_IOCTL_CMD_PAUSE _IO(VDEC_IOCTL_MAGIC, 22) +#define VDEC_IOCTL_CMD_RESUME _IO(VDEC_IOCTL_MAGIC, 23) + +/*CMD params: InputParam - enum vdec_bufferflush , OutputParam - NULL */ +#define VDEC_IOCTL_CMD_FLUSH _IOW(VDEC_IOCTL_MAGIC, 24, struct vdec_ioctl_msg) + +/* ======================================================== + * IOCTL for getting asynchronous notification from driver + * ========================================================*/ + +/*IOCTL params: InputParam - NULL, OutputParam - struct vdec_msginfo*/ +#define VDEC_IOCTL_GET_NEXT_MSG \ + _IOR(VDEC_IOCTL_MAGIC, 25, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_STOP_NEXT_MSG _IO(VDEC_IOCTL_MAGIC, 26) + +#define VDEC_IOCTL_GET_NUMBER_INSTANCES \ + _IOR(VDEC_IOCTL_MAGIC, 27, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_SET_PICTURE_ORDER \ + _IOW(VDEC_IOCTL_MAGIC, 28, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_SET_FRAME_RATE \ + _IOW(VDEC_IOCTL_MAGIC, 29, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_SET_H264_MV_BUFFER \ + _IOW(VDEC_IOCTL_MAGIC, 30, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_FREE_H264_MV_BUFFER \ + _IOW(VDEC_IOCTL_MAGIC, 31, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_GET_MV_BUFFER_SIZE \ + _IOR(VDEC_IOCTL_MAGIC, 32, struct vdec_ioctl_msg) + +#define VDEC_IOCTL_SET_IDR_ONLY_DECODING \ + _IO(VDEC_IOCTL_MAGIC, 33) + +#define VDEC_IOCTL_SET_CONT_ON_RECONFIG \ + _IO(VDEC_IOCTL_MAGIC, 34) + +enum vdec_picture { + PICTURE_TYPE_I, + PICTURE_TYPE_P, + PICTURE_TYPE_B, + PICTURE_TYPE_BI, + PICTURE_TYPE_SKIP, + PICTURE_TYPE_UNKNOWN +}; + +enum vdec_buffer { + VDEC_BUFFER_TYPE_INPUT, + VDEC_BUFFER_TYPE_OUTPUT +}; + +struct vdec_allocatorproperty { + enum vdec_buffer buffer_type; + uint32_t mincount; + uint32_t maxcount; + uint32_t actualcount; + size_t buffer_size; + uint32_t alignment; + uint32_t buf_poolid; +}; + +struct vdec_bufferpayload { + void __user *bufferaddr; + size_t buffer_len; + int pmem_fd; + size_t offset; + size_t mmaped_size; +}; + +struct vdec_setbuffer_cmd { + enum vdec_buffer buffer_type; + struct vdec_bufferpayload buffer; +}; + +struct vdec_fillbuffer_cmd { + struct vdec_bufferpayload buffer; + void *client_data; +}; + +enum vdec_bufferflush { + VDEC_FLUSH_TYPE_INPUT, + VDEC_FLUSH_TYPE_OUTPUT, + VDEC_FLUSH_TYPE_ALL +}; + +enum vdec_codec { + VDEC_CODECTYPE_H264 = 0x1, + VDEC_CODECTYPE_H263 = 0x2, + VDEC_CODECTYPE_MPEG4 = 0x3, + VDEC_CODECTYPE_DIVX_3 = 0x4, + VDEC_CODECTYPE_DIVX_4 = 0x5, + VDEC_CODECTYPE_DIVX_5 = 0x6, + VDEC_CODECTYPE_DIVX_6 = 0x7, + VDEC_CODECTYPE_XVID = 0x8, + VDEC_CODECTYPE_MPEG1 = 0x9, + VDEC_CODECTYPE_MPEG2 = 0xa, + VDEC_CODECTYPE_VC1 = 0xb, + VDEC_CODECTYPE_VC1_RCV = 0xc +}; + +enum vdec_mpeg2_profile { + VDEC_MPEG2ProfileSimple = 0x1, + VDEC_MPEG2ProfileMain = 0x2, + VDEC_MPEG2Profile422 = 0x4, + VDEC_MPEG2ProfileSNR = 0x8, + VDEC_MPEG2ProfileSpatial = 0x10, + VDEC_MPEG2ProfileHigh = 0x20, + VDEC_MPEG2ProfileKhronosExtensions = 0x6F000000, + VDEC_MPEG2ProfileVendorStartUnused = 0x7F000000, + VDEC_MPEG2ProfileMax = 0x7FFFFFFF +}; + +enum vdec_mpeg2_level { + + VDEC_MPEG2LevelLL = 0x1, + VDEC_MPEG2LevelML = 0x2, + VDEC_MPEG2LevelH14 = 0x4, + VDEC_MPEG2LevelHL = 0x8, + VDEC_MPEG2LevelKhronosExtensions = 0x6F000000, + VDEC_MPEG2LevelVendorStartUnused = 0x7F000000, + VDEC_MPEG2LevelMax = 0x7FFFFFFF +}; + +enum vdec_mpeg4_profile { + VDEC_MPEG4ProfileSimple = 0x01, + VDEC_MPEG4ProfileSimpleScalable = 0x02, + VDEC_MPEG4ProfileCore = 0x04, + VDEC_MPEG4ProfileMain = 0x08, + VDEC_MPEG4ProfileNbit = 0x10, + VDEC_MPEG4ProfileScalableTexture = 0x20, + VDEC_MPEG4ProfileSimpleFace = 0x40, + VDEC_MPEG4ProfileSimpleFBA = 0x80, + VDEC_MPEG4ProfileBasicAnimated = 0x100, + VDEC_MPEG4ProfileHybrid = 0x200, + VDEC_MPEG4ProfileAdvancedRealTime = 0x400, + VDEC_MPEG4ProfileCoreScalable = 0x800, + VDEC_MPEG4ProfileAdvancedCoding = 0x1000, + VDEC_MPEG4ProfileAdvancedCore = 0x2000, + VDEC_MPEG4ProfileAdvancedScalable = 0x4000, + VDEC_MPEG4ProfileAdvancedSimple = 0x8000, + VDEC_MPEG4ProfileKhronosExtensions = 0x6F000000, + VDEC_MPEG4ProfileVendorStartUnused = 0x7F000000, + VDEC_MPEG4ProfileMax = 0x7FFFFFFF +}; + +enum vdec_mpeg4_level { + VDEC_MPEG4Level0 = 0x01, + VDEC_MPEG4Level0b = 0x02, + VDEC_MPEG4Level1 = 0x04, + VDEC_MPEG4Level2 = 0x08, + VDEC_MPEG4Level3 = 0x10, + VDEC_MPEG4Level4 = 0x20, + VDEC_MPEG4Level4a = 0x40, + VDEC_MPEG4Level5 = 0x80, + VDEC_MPEG4LevelKhronosExtensions = 0x6F000000, + VDEC_MPEG4LevelVendorStartUnused = 0x7F000000, + VDEC_MPEG4LevelMax = 0x7FFFFFFF +}; + +enum vdec_avc_profile { + VDEC_AVCProfileBaseline = 0x01, + VDEC_AVCProfileMain = 0x02, + VDEC_AVCProfileExtended = 0x04, + VDEC_AVCProfileHigh = 0x08, + VDEC_AVCProfileHigh10 = 0x10, + VDEC_AVCProfileHigh422 = 0x20, + VDEC_AVCProfileHigh444 = 0x40, + VDEC_AVCProfileKhronosExtensions = 0x6F000000, + VDEC_AVCProfileVendorStartUnused = 0x7F000000, + VDEC_AVCProfileMax = 0x7FFFFFFF +}; + +enum vdec_avc_level { + VDEC_AVCLevel1 = 0x01, + VDEC_AVCLevel1b = 0x02, + VDEC_AVCLevel11 = 0x04, + VDEC_AVCLevel12 = 0x08, + VDEC_AVCLevel13 = 0x10, + VDEC_AVCLevel2 = 0x20, + VDEC_AVCLevel21 = 0x40, + VDEC_AVCLevel22 = 0x80, + VDEC_AVCLevel3 = 0x100, + VDEC_AVCLevel31 = 0x200, + VDEC_AVCLevel32 = 0x400, + VDEC_AVCLevel4 = 0x800, + VDEC_AVCLevel41 = 0x1000, + VDEC_AVCLevel42 = 0x2000, + VDEC_AVCLevel5 = 0x4000, + VDEC_AVCLevel51 = 0x8000, + VDEC_AVCLevelKhronosExtensions = 0x6F000000, + VDEC_AVCLevelVendorStartUnused = 0x7F000000, + VDEC_AVCLevelMax = 0x7FFFFFFF +}; + +enum vdec_divx_profile { + VDEC_DIVXProfile_qMobile = 0x01, + VDEC_DIVXProfile_Mobile = 0x02, + VDEC_DIVXProfile_HD = 0x04, + VDEC_DIVXProfile_Handheld = 0x08, + VDEC_DIVXProfile_Portable = 0x10, + VDEC_DIVXProfile_HomeTheater = 0x20 +}; + +enum vdec_xvid_profile { + VDEC_XVIDProfile_Simple = 0x1, + VDEC_XVIDProfile_Advanced_Realtime_Simple = 0x2, + VDEC_XVIDProfile_Advanced_Simple = 0x4 +}; + +enum vdec_xvid_level { + VDEC_XVID_LEVEL_S_L0 = 0x1, + VDEC_XVID_LEVEL_S_L1 = 0x2, + VDEC_XVID_LEVEL_S_L2 = 0x4, + VDEC_XVID_LEVEL_S_L3 = 0x8, + VDEC_XVID_LEVEL_ARTS_L1 = 0x10, + VDEC_XVID_LEVEL_ARTS_L2 = 0x20, + VDEC_XVID_LEVEL_ARTS_L3 = 0x40, + VDEC_XVID_LEVEL_ARTS_L4 = 0x80, + VDEC_XVID_LEVEL_AS_L0 = 0x100, + VDEC_XVID_LEVEL_AS_L1 = 0x200, + VDEC_XVID_LEVEL_AS_L2 = 0x400, + VDEC_XVID_LEVEL_AS_L3 = 0x800, + VDEC_XVID_LEVEL_AS_L4 = 0x1000 +}; + +enum vdec_h263profile { + VDEC_H263ProfileBaseline = 0x01, + VDEC_H263ProfileH320Coding = 0x02, + VDEC_H263ProfileBackwardCompatible = 0x04, + VDEC_H263ProfileISWV2 = 0x08, + VDEC_H263ProfileISWV3 = 0x10, + VDEC_H263ProfileHighCompression = 0x20, + VDEC_H263ProfileInternet = 0x40, + VDEC_H263ProfileInterlace = 0x80, + VDEC_H263ProfileHighLatency = 0x100, + VDEC_H263ProfileKhronosExtensions = 0x6F000000, + VDEC_H263ProfileVendorStartUnused = 0x7F000000, + VDEC_H263ProfileMax = 0x7FFFFFFF +}; + +enum vdec_h263level { + VDEC_H263Level10 = 0x01, + VDEC_H263Level20 = 0x02, + VDEC_H263Level30 = 0x04, + VDEC_H263Level40 = 0x08, + VDEC_H263Level45 = 0x10, + VDEC_H263Level50 = 0x20, + VDEC_H263Level60 = 0x40, + VDEC_H263Level70 = 0x80, + VDEC_H263LevelKhronosExtensions = 0x6F000000, + VDEC_H263LevelVendorStartUnused = 0x7F000000, + VDEC_H263LevelMax = 0x7FFFFFFF +}; + +enum vdec_wmv_format { + VDEC_WMVFormatUnused = 0x01, + VDEC_WMVFormat7 = 0x02, + VDEC_WMVFormat8 = 0x04, + VDEC_WMVFormat9 = 0x08, + VDEC_WMFFormatKhronosExtensions = 0x6F000000, + VDEC_WMFFormatVendorStartUnused = 0x7F000000, + VDEC_WMVFormatMax = 0x7FFFFFFF +}; + +enum vdec_vc1_profile { + VDEC_VC1ProfileSimple = 0x1, + VDEC_VC1ProfileMain = 0x2, + VDEC_VC1ProfileAdvanced = 0x4 +}; + +enum vdec_vc1_level { + VDEC_VC1_LEVEL_S_Low = 0x1, + VDEC_VC1_LEVEL_S_Medium = 0x2, + VDEC_VC1_LEVEL_M_Low = 0x4, + VDEC_VC1_LEVEL_M_Medium = 0x8, + VDEC_VC1_LEVEL_M_High = 0x10, + VDEC_VC1_LEVEL_A_L0 = 0x20, + VDEC_VC1_LEVEL_A_L1 = 0x40, + VDEC_VC1_LEVEL_A_L2 = 0x80, + VDEC_VC1_LEVEL_A_L3 = 0x100, + VDEC_VC1_LEVEL_A_L4 = 0x200 +}; + +struct vdec_profile_level { + uint32_t profiles; + uint32_t levels; +}; + +enum vdec_interlaced_format { + VDEC_InterlaceFrameProgressive = 0x1, + VDEC_InterlaceInterleaveFrameTopFieldFirst = 0x2, + VDEC_InterlaceInterleaveFrameBottomFieldFirst = 0x4 +}; + +enum vdec_output_fromat { + VDEC_YUV_FORMAT_NV12 = 0x1, + VDEC_YUV_FORMAT_TILE_4x2 = 0x2 +}; + +enum vdec_output_order { + VDEC_ORDER_DISPLAY = 0x1, + VDEC_ORDER_DECODE = 0x2 +}; + +struct vdec_picsize { + uint32_t frame_width; + uint32_t frame_height; + uint32_t stride; + uint32_t scan_lines; +}; + +struct vdec_seqheader { + void __user *ptr_seqheader; + size_t seq_header_len; + int pmem_fd; + size_t pmem_offset; +}; + +struct vdec_mberror { + void __user *ptr_errormap; + size_t err_mapsize; +}; + +struct vdec_input_frameinfo { + void __user *bufferaddr; + size_t offset; + size_t datalen; + uint32_t flags; + int64_t timestamp; + void *client_data; + int pmem_fd; + size_t pmem_offset; +}; + +struct vdec_framesize { + uint32_t left; + uint32_t top; + uint32_t right; + uint32_t bottom; +}; + +struct vdec_output_frameinfo { + void __user *bufferaddr; + size_t offset; + size_t len; + uint32_t flags; + int64_t time_stamp; + enum vdec_picture pic_type; + void *client_data; + void *input_frame_clientdata; + struct vdec_framesize framesize; + enum vdec_interlaced_format interlaced_format; +}; + +union vdec_msgdata { + struct vdec_output_frameinfo output_frame; + void *input_frame_clientdata; +}; + +struct vdec_msginfo { + uint32_t status_code; + uint32_t msgcode; + union vdec_msgdata msgdata; + size_t msgdatasize; +}; + +struct vdec_framerate { + unsigned long fps_denominator; + unsigned long fps_numerator; +}; + +struct vdec_h264_mv{ + size_t size; + int count; + int pmem_fd; + int offset; +}; + +struct vdec_mv_buff_size{ + int width; + int height; + int size; + int alignment; +}; + +#endif /* end of macro _VDECDECODER_H_ */ diff --git a/include/linux/msm_vidc_enc.h b/include/linux/msm_vidc_enc.h new file mode 100644 index 00000000000..12c7afd5ddf --- /dev/null +++ b/include/linux/msm_vidc_enc.h @@ -0,0 +1,594 @@ +#ifndef _MSM_VIDC_ENC_H_ +#define _MSM_VIDC_ENC_H_ + +#include +#include + +/** STATUS CODES*/ +/* Base value for status codes */ +#define VEN_S_BASE 0x00000000 +#define VEN_S_SUCCESS (VEN_S_BASE)/* Success */ +#define VEN_S_EFAIL (VEN_S_BASE+1)/* General failure */ +#define VEN_S_EFATAL (VEN_S_BASE+2)/* Fatal irrecoverable failure*/ +#define VEN_S_EBADPARAM (VEN_S_BASE+3)/* Error passed parameters*/ +/*Command called in invalid state*/ +#define VEN_S_EINVALSTATE (VEN_S_BASE+4) +#define VEN_S_ENOSWRES (VEN_S_BASE+5)/* Insufficient OS resources*/ +#define VEN_S_ENOHWRES (VEN_S_BASE+6)/*Insufficient HW resources */ +#define VEN_S_EBUFFREQ (VEN_S_BASE+7)/* Buffer requirements were not met*/ +#define VEN_S_EINVALCMD (VEN_S_BASE+8)/* Invalid command called */ +#define VEN_S_ETIMEOUT (VEN_S_BASE+9)/* Command timeout. */ +/*Re-attempt was made when multiple invocation not supported for API.*/ +#define VEN_S_ENOREATMPT (VEN_S_BASE+10) +#define VEN_S_ENOPREREQ (VEN_S_BASE+11)/*Pre-requirement is not met for API*/ +#define VEN_S_ECMDQFULL (VEN_S_BASE+12)/*Command queue is full*/ +#define VEN_S_ENOTSUPP (VEN_S_BASE+13)/*Command not supported*/ +#define VEN_S_ENOTIMPL (VEN_S_BASE+14)/*Command not implemented.*/ +#define VEN_S_ENOTPMEM (VEN_S_BASE+15)/*Buffer is not from PMEM*/ +#define VEN_S_EFLUSHED (VEN_S_BASE+16)/*returned buffer was flushed*/ +#define VEN_S_EINSUFBUF (VEN_S_BASE+17)/*provided buffer size insufficient*/ +#define VEN_S_ESAMESTATE (VEN_S_BASE+18) +#define VEN_S_EINVALTRANS (VEN_S_BASE+19) + +#define VEN_INTF_VER 1 + +/*Asynchronous messages from driver*/ +#define VEN_MSG_INDICATION 0 +#define VEN_MSG_INPUT_BUFFER_DONE 1 +#define VEN_MSG_OUTPUT_BUFFER_DONE 2 +#define VEN_MSG_NEED_OUTPUT_BUFFER 3 +#define VEN_MSG_FLUSH_INPUT_DONE 4 +#define VEN_MSG_FLUSH_OUPUT_DONE 5 +#define VEN_MSG_START 6 +#define VEN_MSG_STOP 7 +#define VEN_MSG_PAUSE 8 +#define VEN_MSG_RESUME 9 +#define VEN_MSG_STOP_READING_MSG 10 + +/*Buffer flags bits masks*/ +#define VEN_BUFFLAG_EOS 0x00000001 +#define VEN_BUFFLAG_ENDOFFRAME 0x00000010 +#define VEN_BUFFLAG_SYNCFRAME 0x00000020 +#define VEN_BUFFLAG_EXTRADATA 0x00000040 +#define VEN_BUFFLAG_CODECCONFIG 0x00000080 + +/*ENCODER CONFIGURATION CONSTANTS*/ + +/*Encoded video frame types*/ +#define VEN_FRAME_TYPE_I 1/* I frame type */ +#define VEN_FRAME_TYPE_P 2/* P frame type */ +#define VEN_FRAME_TYPE_B 3/* B frame type */ + +/*Video codec types*/ +#define VEN_CODEC_MPEG4 1/* MPEG4 Codec */ +#define VEN_CODEC_H264 2/* H.264 Codec */ +#define VEN_CODEC_H263 3/* H.263 Codec */ + +/*Video codec profile types.*/ +#define VEN_PROFILE_MPEG4_SP 1/* 1 - MPEG4 SP profile */ +#define VEN_PROFILE_MPEG4_ASP 2/* 2 - MPEG4 ASP profile */ +#define VEN_PROFILE_H264_BASELINE 3/* 3 - H264 Baseline profile */ +#define VEN_PROFILE_H264_MAIN 4/* 4 - H264 Main profile */ +#define VEN_PROFILE_H264_HIGH 5/* 5 - H264 High profile */ +#define VEN_PROFILE_H263_BASELINE 6/* 6 - H263 Baseline profile */ + +/*Video codec profile level types.*/ +#define VEN_LEVEL_MPEG4_0 0x1/* MPEG4 Level 0 */ +#define VEN_LEVEL_MPEG4_1 0x2/* MPEG4 Level 1 */ +#define VEN_LEVEL_MPEG4_2 0x3/* MPEG4 Level 2 */ +#define VEN_LEVEL_MPEG4_3 0x4/* MPEG4 Level 3 */ +#define VEN_LEVEL_MPEG4_4 0x5/* MPEG4 Level 4 */ +#define VEN_LEVEL_MPEG4_5 0x6/* MPEG4 Level 5 */ +#define VEN_LEVEL_MPEG4_3b 0x7/* MPEG4 Level 3b */ +#define VEN_LEVEL_MPEG4_6 0x8/* MPEG4 Level 6 */ + +#define VEN_LEVEL_H264_1 0x9/* H.264 Level 1 */ +#define VEN_LEVEL_H264_1b 0xA/* H.264 Level 1b */ +#define VEN_LEVEL_H264_1p1 0xB/* H.264 Level 1.1 */ +#define VEN_LEVEL_H264_1p2 0xC/* H.264 Level 1.2 */ +#define VEN_LEVEL_H264_1p3 0xD/* H.264 Level 1.3 */ +#define VEN_LEVEL_H264_2 0xE/* H.264 Level 2 */ +#define VEN_LEVEL_H264_2p1 0xF/* H.264 Level 2.1 */ +#define VEN_LEVEL_H264_2p2 0x10/* H.264 Level 2.2 */ +#define VEN_LEVEL_H264_3 0x11/* H.264 Level 3 */ +#define VEN_LEVEL_H264_3p1 0x12/* H.264 Level 3.1 */ +#define VEN_LEVEL_H264_4 0x13/* H.264 Level 4 */ + +#define VEN_LEVEL_H263_10 0x14/* H.263 Level 10 */ +#define VEN_LEVEL_H263_20 0x15/* H.263 Level 20 */ +#define VEN_LEVEL_H263_30 0x16/* H.263 Level 30 */ +#define VEN_LEVEL_H263_40 0x17/* H.263 Level 40 */ +#define VEN_LEVEL_H263_45 0x18/* H.263 Level 45 */ +#define VEN_LEVEL_H263_50 0x19/* H.263 Level 50 */ +#define VEN_LEVEL_H263_60 0x1A/* H.263 Level 60 */ +#define VEN_LEVEL_H263_70 0x1B/* H.263 Level 70 */ + +/*Entropy coding model selection for H.264 encoder.*/ +#define VEN_ENTROPY_MODEL_CAVLC 1 +#define VEN_ENTROPY_MODEL_CABAC 2 +/*Cabac model number (0,1,2) for encoder.*/ +#define VEN_CABAC_MODEL_0 1/* CABAC Model 0. */ +#define VEN_CABAC_MODEL_1 2/* CABAC Model 1. */ +#define VEN_CABAC_MODEL_2 3/* CABAC Model 2. */ + +/*Deblocking filter control type for encoder.*/ +#define VEN_DB_DISABLE 1/* 1 - Disable deblocking filter*/ +#define VEN_DB_ALL_BLKG_BNDRY 2/* 2 - All blocking boundary filtering*/ +#define VEN_DB_SKIP_SLICE_BNDRY 3/* 3 - Filtering except sliceboundary*/ + +/*Different methods of Multi slice selection.*/ +#define VEN_MSLICE_OFF 1 +#define VEN_MSLICE_CNT_MB 2 /*number of MBscount per slice*/ +#define VEN_MSLICE_CNT_BYTE 3 /*number of bytes count per slice.*/ +#define VEN_MSLICE_GOB 4 /*Multi slice by GOB for H.263 only.*/ + +/*Different modes for Rate Control.*/ +#define VEN_RC_OFF 1 +#define VEN_RC_VBR_VFR 2 +#define VEN_RC_VBR_CFR 3 +#define VEN_RC_CBR_VFR 4 +#define VEN_RC_CBR_CFR 5 + +/*Different modes for flushing buffers*/ +#define VEN_FLUSH_INPUT 1 +#define VEN_FLUSH_OUTPUT 2 +#define VEN_FLUSH_ALL 3 + +/*Different input formats for YUV data.*/ +#define VEN_INPUTFMT_NV12 1/* NV12 Linear */ +#define VEN_INPUTFMT_NV21 2/* NV21 Linear */ +#define VEN_INPUTFMT_NV12_16M2KA 3/* NV12 Linear */ + +/*Different allowed rotation modes.*/ +#define VEN_ROTATION_0 1/* 0 degrees */ +#define VEN_ROTATION_90 2/* 90 degrees */ +#define VEN_ROTATION_180 3/* 180 degrees */ +#define VEN_ROTATION_270 4/* 270 degrees */ + +/*IOCTL timeout values*/ +#define VEN_TIMEOUT_INFINITE 0xffffffff + +/*Different allowed intra refresh modes.*/ +#define VEN_IR_OFF 1 +#define VEN_IR_CYCLIC 2 +#define VEN_IR_RANDOM 3 + +/*IOCTL BASE CODES Not to be used directly by the client.*/ +/* Base value for ioctls that are not related to encoder configuration.*/ +#define VEN_IOCTLBASE_NENC 0x800 +/* Base value for encoder configuration ioctls*/ +#define VEN_IOCTLBASE_ENC 0x850 + +struct venc_ioctl_msg{ + void __user *in; + void __user *out; +}; + +/*NON ENCODER CONFIGURATION IOCTLs*/ + +/*IOCTL params:SET: InputData - unsigned long, OutputData - NULL*/ +#define VEN_IOCTL_SET_INTF_VERSION \ + _IOW(VEN_IOCTLBASE_NENC, 0, struct venc_ioctl_msg) + +/*IOCTL params:CMD: InputData - venc_timeout, OutputData - venc_msg*/ +#define VEN_IOCTL_CMD_READ_NEXT_MSG \ + _IOWR(VEN_IOCTLBASE_NENC, 1, struct venc_ioctl_msg) + +/*IOCTL params:CMD: InputData - NULL, OutputData - NULL*/ +#define VEN_IOCTL_CMD_STOP_READ_MSG _IO(VEN_IOCTLBASE_NENC, 2) + +/*IOCTL params:SET: InputData - venc_allocatorproperty, OutputData - NULL + GET: InputData - NULL, OutputData - venc_allocatorproperty*/ +#define VEN_IOCTL_SET_INPUT_BUFFER_REQ \ + _IOW(VEN_IOCTLBASE_NENC, 3, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_INPUT_BUFFER_REQ \ + _IOR(VEN_IOCTLBASE_NENC, 4, struct venc_ioctl_msg) + +/*IOCTL params:CMD: InputData - venc_bufferpayload, OutputData - NULL*/ +#define VEN_IOCTL_CMD_ALLOC_INPUT_BUFFER \ + _IOW(VEN_IOCTLBASE_NENC, 5, struct venc_ioctl_msg) + +/*IOCTL params:CMD: InputData - venc_bufferpayload, OutputData - NULL*/ +#define VEN_IOCTL_SET_INPUT_BUFFER \ + _IOW(VEN_IOCTLBASE_NENC, 6, struct venc_ioctl_msg) + +/*IOCTL params: CMD: InputData - venc_bufferpayload, OutputData - NULL*/ +#define VEN_IOCTL_CMD_FREE_INPUT_BUFFER \ + _IOW(VEN_IOCTLBASE_NENC, 7, struct venc_ioctl_msg) + +/*IOCTL params:SET: InputData - venc_allocatorproperty, OutputData - NULL + GET: InputData - NULL, OutputData - venc_allocatorproperty*/ +#define VEN_IOCTL_SET_OUTPUT_BUFFER_REQ \ + _IOW(VEN_IOCTLBASE_NENC, 8, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_OUTPUT_BUFFER_REQ \ + _IOR(VEN_IOCTLBASE_NENC, 9, struct venc_ioctl_msg) + +/*IOCTL params:CMD: InputData - venc_bufferpayload, OutputData - NULL*/ +#define VEN_IOCTL_CMD_ALLOC_OUTPUT_BUFFER \ + _IOW(VEN_IOCTLBASE_NENC, 10, struct venc_ioctl_msg) + + +/*IOCTL params:CMD: InputData - venc_bufferpayload, OutputData - NULL*/ +#define VEN_IOCTL_SET_OUTPUT_BUFFER \ + _IOW(VEN_IOCTLBASE_NENC, 11, struct venc_ioctl_msg) + +/*IOCTL params:CMD: InputData - venc_bufferpayload, OutputData - NULL.*/ +#define VEN_IOCTL_CMD_FREE_OUTPUT_BUFFER \ + _IOW(VEN_IOCTLBASE_NENC, 12, struct venc_ioctl_msg) + + +/* Asynchronous respone message code:* VEN_MSG_START*/ +#define VEN_IOCTL_CMD_START _IO(VEN_IOCTLBASE_NENC, 13) + + +/*IOCTL params:CMD: InputData - venc_buffer, OutputData - NULL + Asynchronous respone message code:VEN_MSG_INPUT_BUFFER_DONE*/ +#define VEN_IOCTL_CMD_ENCODE_FRAME \ + _IOW(VEN_IOCTLBASE_NENC, 14, struct venc_ioctl_msg) + + +/*IOCTL params:CMD: InputData - venc_buffer, OutputData - NULL + Asynchronous response message code:VEN_MSG_OUTPUT_BUFFER_DONE*/ +#define VEN_IOCTL_CMD_FILL_OUTPUT_BUFFER \ + _IOW(VEN_IOCTLBASE_NENC, 15, struct venc_ioctl_msg) + +/*IOCTL params:CMD: InputData - venc_bufferflush, OutputData - NULL + * Asynchronous response message code:VEN_MSG_INPUT_BUFFER_DONE*/ +#define VEN_IOCTL_CMD_FLUSH \ + _IOW(VEN_IOCTLBASE_NENC, 16, struct venc_ioctl_msg) + + +/*Asynchronous respone message code:VEN_MSG_PAUSE*/ +#define VEN_IOCTL_CMD_PAUSE _IO(VEN_IOCTLBASE_NENC, 17) + +/*Asynchronous respone message code:VEN_MSG_RESUME*/ +#define VEN_IOCTL_CMD_RESUME _IO(VEN_IOCTLBASE_NENC, 18) + +/* Asynchronous respone message code:VEN_MSG_STOP*/ +#define VEN_IOCTL_CMD_STOP _IO(VEN_IOCTLBASE_NENC, 19) + +#define VEN_IOCTL_SET_RECON_BUFFER \ + _IOW(VEN_IOCTLBASE_NENC, 20, struct venc_ioctl_msg) + +#define VEN_IOCTL_FREE_RECON_BUFFER \ + _IOW(VEN_IOCTLBASE_NENC, 21, struct venc_ioctl_msg) + +#define VEN_IOCTL_GET_RECON_BUFFER_SIZE \ + _IOW(VEN_IOCTLBASE_NENC, 22, struct venc_ioctl_msg) + +/*ENCODER PROPERTY CONFIGURATION & CAPABILITY IOCTLs*/ + +/*IOCTL params:SET: InputData - venc_basecfg, OutputData - NULL + GET: InputData - NULL, OutputData - venc_basecfg*/ +#define VEN_IOCTL_SET_BASE_CFG \ + _IOW(VEN_IOCTLBASE_ENC, 1, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_BASE_CFG \ + _IOR(VEN_IOCTLBASE_ENC, 2, struct venc_ioctl_msg) + +/*IOCTL params:SET: InputData - venc_switch, OutputData - NULL + GET: InputData - NULL, OutputData - venc_switch*/ +#define VEN_IOCTL_SET_LIVE_MODE \ + _IOW(VEN_IOCTLBASE_ENC, 3, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_LIVE_MODE \ + _IOR(VEN_IOCTLBASE_ENC, 4, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_profile, OutputData - NULL + GET: InputData - NULL, OutputData - venc_profile*/ +#define VEN_IOCTL_SET_CODEC_PROFILE \ + _IOW(VEN_IOCTLBASE_ENC, 5, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_CODEC_PROFILE \ + _IOR(VEN_IOCTLBASE_ENC, 6, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - ven_profilelevel, OutputData - NULL + GET: InputData - NULL, OutputData - ven_profilelevel*/ +#define VEN_IOCTL_SET_PROFILE_LEVEL \ + _IOW(VEN_IOCTLBASE_ENC, 7, struct venc_ioctl_msg) + +#define VEN_IOCTL_GET_PROFILE_LEVEL \ + _IOR(VEN_IOCTLBASE_ENC, 8, struct venc_ioctl_msg) + +/*IOCTL params:SET: InputData - venc_switch, OutputData - NULL + GET: InputData - NULL, OutputData - venc_switch*/ +#define VEN_IOCTL_SET_SHORT_HDR \ + _IOW(VEN_IOCTLBASE_ENC, 9, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_SHORT_HDR \ + _IOR(VEN_IOCTLBASE_ENC, 10, struct venc_ioctl_msg) + + +/*IOCTL params: SET: InputData - venc_sessionqp, OutputData - NULL + GET: InputData - NULL, OutputData - venc_sessionqp*/ +#define VEN_IOCTL_SET_SESSION_QP \ + _IOW(VEN_IOCTLBASE_ENC, 11, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_SESSION_QP \ + _IOR(VEN_IOCTLBASE_ENC, 12, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_intraperiod, OutputData - NULL + GET: InputData - NULL, OutputData - venc_intraperiod*/ +#define VEN_IOCTL_SET_INTRA_PERIOD \ + _IOW(VEN_IOCTLBASE_ENC, 13, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_INTRA_PERIOD \ + _IOR(VEN_IOCTLBASE_ENC, 14, struct venc_ioctl_msg) + + +/* Request an Iframe*/ +#define VEN_IOCTL_CMD_REQUEST_IFRAME _IO(VEN_IOCTLBASE_ENC, 15) + +/*IOCTL params:GET: InputData - NULL, OutputData - venc_capability*/ +#define VEN_IOCTL_GET_CAPABILITY \ + _IOR(VEN_IOCTLBASE_ENC, 16, struct venc_ioctl_msg) + + +/*IOCTL params:GET: InputData - NULL, OutputData - venc_seqheader*/ +#define VEN_IOCTL_GET_SEQUENCE_HDR \ + _IOR(VEN_IOCTLBASE_ENC, 17, struct venc_ioctl_msg) + +/*IOCTL params:SET: InputData - venc_entropycfg, OutputData - NULL + GET: InputData - NULL, OutputData - venc_entropycfg*/ +#define VEN_IOCTL_SET_ENTROPY_CFG \ + _IOW(VEN_IOCTLBASE_ENC, 18, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_ENTROPY_CFG \ + _IOR(VEN_IOCTLBASE_ENC, 19, struct venc_ioctl_msg) + +/*IOCTL params:SET: InputData - venc_dbcfg, OutputData - NULL + GET: InputData - NULL, OutputData - venc_dbcfg*/ +#define VEN_IOCTL_SET_DEBLOCKING_CFG \ + _IOW(VEN_IOCTLBASE_ENC, 20, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_DEBLOCKING_CFG \ + _IOR(VEN_IOCTLBASE_ENC, 21, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_intrarefresh, OutputData - NULL + GET: InputData - NULL, OutputData - venc_intrarefresh*/ +#define VEN_IOCTL_SET_INTRA_REFRESH \ + _IOW(VEN_IOCTLBASE_ENC, 22, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_INTRA_REFRESH \ + _IOR(VEN_IOCTLBASE_ENC, 23, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_multiclicecfg, OutputData - NULL + GET: InputData - NULL, OutputData - venc_multiclicecfg*/ +#define VEN_IOCTL_SET_MULTI_SLICE_CFG \ + _IOW(VEN_IOCTLBASE_ENC, 24, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_MULTI_SLICE_CFG \ + _IOR(VEN_IOCTLBASE_ENC, 25, struct venc_ioctl_msg) + +/*IOCTL params:SET: InputData - venc_ratectrlcfg, OutputData - NULL + GET: InputData - NULL, OutputData - venc_ratectrlcfg*/ +#define VEN_IOCTL_SET_RATE_CTRL_CFG \ + _IOW(VEN_IOCTLBASE_ENC, 26, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_RATE_CTRL_CFG \ + _IOR(VEN_IOCTLBASE_ENC, 27, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_voptimingcfg, OutputData - NULL + GET: InputData - NULL, OutputData - venc_voptimingcfg*/ +#define VEN_IOCTL_SET_VOP_TIMING_CFG \ + _IOW(VEN_IOCTLBASE_ENC, 28, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_VOP_TIMING_CFG \ + _IOR(VEN_IOCTLBASE_ENC, 29, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_framerate, OutputData - NULL + GET: InputData - NULL, OutputData - venc_framerate*/ +#define VEN_IOCTL_SET_FRAME_RATE \ + _IOW(VEN_IOCTLBASE_ENC, 30, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_FRAME_RATE \ + _IOR(VEN_IOCTLBASE_ENC, 31, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_targetbitrate, OutputData - NULL + GET: InputData - NULL, OutputData - venc_targetbitrate*/ +#define VEN_IOCTL_SET_TARGET_BITRATE \ + _IOW(VEN_IOCTLBASE_ENC, 32, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_TARGET_BITRATE \ + _IOR(VEN_IOCTLBASE_ENC, 33, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_rotation, OutputData - NULL + GET: InputData - NULL, OutputData - venc_rotation*/ +#define VEN_IOCTL_SET_ROTATION \ + _IOW(VEN_IOCTLBASE_ENC, 34, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_ROTATION \ + _IOR(VEN_IOCTLBASE_ENC, 35, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_headerextension, OutputData - NULL + GET: InputData - NULL, OutputData - venc_headerextension*/ +#define VEN_IOCTL_SET_HEC \ + _IOW(VEN_IOCTLBASE_ENC, 36, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_HEC \ + _IOR(VEN_IOCTLBASE_ENC, 37, struct venc_ioctl_msg) + +/*IOCTL params:SET: InputData - venc_switch, OutputData - NULL + GET: InputData - NULL, OutputData - venc_switch*/ +#define VEN_IOCTL_SET_DATA_PARTITION \ + _IOW(VEN_IOCTLBASE_ENC, 38, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_DATA_PARTITION \ + _IOR(VEN_IOCTLBASE_ENC, 39, struct venc_ioctl_msg) + +/*IOCTL params:SET: InputData - venc_switch, OutputData - NULL + GET: InputData - NULL, OutputData - venc_switch*/ +#define VEN_IOCTL_SET_RVLC \ + _IOW(VEN_IOCTLBASE_ENC, 40, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_RVLC \ + _IOR(VEN_IOCTLBASE_ENC, 41, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_switch, OutputData - NULL + GET: InputData - NULL, OutputData - venc_switch*/ +#define VEN_IOCTL_SET_AC_PREDICTION \ + _IOW(VEN_IOCTLBASE_ENC, 42, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_AC_PREDICTION \ + _IOR(VEN_IOCTLBASE_ENC, 43, struct venc_ioctl_msg) + + +/*IOCTL params:SET: InputData - venc_qprange, OutputData - NULL + GET: InputData - NULL, OutputData - venc_qprange*/ +#define VEN_IOCTL_SET_QP_RANGE \ + _IOW(VEN_IOCTLBASE_ENC, 44, struct venc_ioctl_msg) +#define VEN_IOCTL_GET_QP_RANGE \ + _IOR(VEN_IOCTLBASE_ENC, 45, struct venc_ioctl_msg) + +#define VEN_IOCTL_GET_NUMBER_INSTANCES \ + _IOR(VEN_IOCTLBASE_ENC, 46, struct venc_ioctl_msg) + +struct venc_switch{ + unsigned char status; +}; + +struct venc_allocatorproperty{ + unsigned long mincount; + unsigned long maxcount; + unsigned long actualcount; + unsigned long datasize; + unsigned long suffixsize; + unsigned long alignment; + unsigned long bufpoolid; +}; + +struct venc_bufferpayload{ + unsigned char *pbuffer; + size_t sz; + int fd; + unsigned int offset; + unsigned int maped_size; + unsigned long filled_len; +}; + +struct venc_buffer{ + unsigned char *ptrbuffer; + unsigned long sz; + unsigned long len; + unsigned long offset; + long long timestamp; + unsigned long flags; + void *clientdata; +}; + +struct venc_basecfg{ + unsigned long input_width; + unsigned long input_height; + unsigned long dvs_width; + unsigned long dvs_height; + unsigned long codectype; + unsigned long fps_num; + unsigned long fps_den; + unsigned long targetbitrate; + unsigned long inputformat; +}; + +struct venc_profile{ + unsigned long profile; +}; +struct ven_profilelevel{ + unsigned long level; +}; + +struct venc_sessionqp{ + unsigned long iframeqp; + unsigned long pframqp; +}; + +struct venc_qprange{ + unsigned long maxqp; + unsigned long minqp; +}; +struct venc_intraperiod{ + unsigned long num_pframes; + unsigned long num_bframes; +}; +struct venc_seqheader{ + unsigned char *hdrbufptr; + unsigned long bufsize; + unsigned long hdrlen; +}; + +struct venc_capability{ + unsigned long codec_types; + unsigned long maxframe_width; + unsigned long maxframe_height; + unsigned long maxtarget_bitrate; + unsigned long maxframe_rate; + unsigned long input_formats; + unsigned char dvs; +}; + +struct venc_entropycfg{ + unsigned longentropysel; + unsigned long cabacmodel; +}; + +struct venc_dbcfg{ + unsigned long db_mode; + unsigned long slicealpha_offset; + unsigned long slicebeta_offset; +}; + +struct venc_intrarefresh{ + unsigned long irmode; + unsigned long mbcount; +}; + +struct venc_multiclicecfg{ + unsigned long mslice_mode; + unsigned long mslice_size; +}; + +struct venc_bufferflush{ + unsigned long flush_mode; +}; + +struct venc_ratectrlcfg{ + unsigned long rcmode; +}; + +struct venc_voptimingcfg{ + unsigned long voptime_resolution; +}; +struct venc_framerate{ + unsigned long fps_denominator; + unsigned long fps_numerator; +}; + +struct venc_targetbitrate{ + unsigned long target_bitrate; +}; + + +struct venc_rotation{ + unsigned long rotation; +}; + +struct venc_timeout{ + unsigned long millisec; +}; + +struct venc_headerextension{ + unsigned long header_extension; +}; + +struct venc_msg{ + unsigned long statuscode; + unsigned long msgcode; + struct venc_buffer buf; + unsigned long msgdata_size; +}; + +struct venc_recon_addr{ + unsigned char *pbuffer; + unsigned long buffer_size; + unsigned long pmem_fd; + unsigned long offset; +}; + +struct venc_recon_buff_size{ + int width; + int height; + int size; + int alignment; +}; + +#endif /* _MSM_VIDC_ENC_H_ */ diff --git a/include/linux/ofn_atlab.h b/include/linux/ofn_atlab.h new file mode 100644 index 00000000000..16c34d7d97c --- /dev/null +++ b/include/linux/ofn_atlab.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +/* + * Atlab optical Finger Navigation driver + * + */ + +struct ofn_function1 { + bool no_motion1_en; + bool touch_sensor_en; + bool ofn_en; + u16 clock_select_khz; + u32 cpi_selection; +}; + +struct ofn_function2 { + bool invert_y; + bool invert_x; + bool swap_x_y; + bool hold_a_b_en; + bool motion_filter_en; +}; + +struct ofn_atlab_platform_data { + int irq_button_l; + int irq_button_r; + int gpio_button_l; + int gpio_button_r; + int rotate_xy; + int (*gpio_setup)(void); + void (*gpio_release)(void); + int (*optnav_on)(void); + void (*optnav_off)(void); + struct ofn_function1 function1; + struct ofn_function2 function2; +}; diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index e0786e35f24..fc9408957b5 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -32,6 +32,7 @@ enum perf_type_id { PERF_TYPE_HW_CACHE = 3, PERF_TYPE_RAW = 4, PERF_TYPE_BREAKPOINT = 5, + PERF_TYPE_SHARED = 6, PERF_TYPE_MAX, /* non-ABI */ }; diff --git a/include/linux/pkt_sched.h b/include/linux/pkt_sched.h index c5336705921..addaebb8a9d 100644 --- a/include/linux/pkt_sched.h +++ b/include/linux/pkt_sched.h @@ -118,6 +118,7 @@ struct tc_fifo_qopt { struct tc_prio_qopt { int bands; /* Number of bands */ __u8 priomap[TC_PRIO_MAX+1]; /* Map: logical priority -> PRIO band */ + __u8 enable_flow; /* Enable dequeue */ }; /* MULTIQ section */ diff --git a/include/linux/platform_data/qcom_crypto_device.h b/include/linux/platform_data/qcom_crypto_device.h new file mode 100644 index 00000000000..c6ef40a02ab --- /dev/null +++ b/include/linux/platform_data/qcom_crypto_device.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __QCOM_CRYPTO_DEVICE__H +#define __QCOM_CRYPTO_DEVICE__H + +struct msm_ce_hw_support { + uint32_t ce_shared; + uint32_t shared_ce_resource; + uint32_t hw_key_support; + uint32_t sha_hmac; +}; + +#endif /* __QCOM_CRYPTO_DEVICE__H */ diff --git a/include/linux/platform_data/usb_rmnet.h b/include/linux/platform_data/usb_rmnet.h new file mode 100644 index 00000000000..68c0db709dc --- /dev/null +++ b/include/linux/platform_data/usb_rmnet.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#ifndef __LINUX_USB_GADGET_RMNET_H__ +#define __LINUX_USB_GADGET_RMNET_H__ + +#include + +struct usb_rmnet_pdata { + unsigned num_instances; +}; + +#endif diff --git a/include/linux/pmic8058-batt-alarm.h b/include/linux/pmic8058-batt-alarm.h new file mode 100644 index 00000000000..0a0ac78e6fa --- /dev/null +++ b/include/linux/pmic8058-batt-alarm.h @@ -0,0 +1,124 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* + * Qualcomm PMIC 8058 Battery Alarm Device driver + * + */ +#ifndef __PMIC8058_BATT_ALARM_H__ +#define __PMIC8058_BATT_ALARM_H__ + +#include + +/** + * enum pm8058_batt_alarm_hold_time - hold time required for out of range + * battery voltage needed to trigger a status change. Enum names denote + * hold time in milliseconds. + */ +enum pm8058_batt_alarm_hold_time { + PM8058_BATT_ALARM_HOLD_TIME_0p125_MS = 0, + PM8058_BATT_ALARM_HOLD_TIME_0p25_MS, + PM8058_BATT_ALARM_HOLD_TIME_0p5_MS, + PM8058_BATT_ALARM_HOLD_TIME_1_MS, + PM8058_BATT_ALARM_HOLD_TIME_2_MS, + PM8058_BATT_ALARM_HOLD_TIME_4_MS, + PM8058_BATT_ALARM_HOLD_TIME_8_MS, + PM8058_BATT_ALARM_HOLD_TIME_16_MS, +}; + +/* + * Bits that are set in the return value of pm8058_batt_alarm_status_read + * to indicate crossing of the upper or lower threshold. + */ +#define PM8058_BATT_ALARM_STATUS_BELOW_LOWER BIT(0) +#define PM8058_BATT_ALARM_STATUS_ABOVE_UPPER BIT(1) + +/** + * pm8058_batt_alarm_state_set - enable or disable the threshold comparators + * @enable_lower_comparator: 1 = enable comparator, 0 = disable comparator + * @enable_upper_comparator: 1 = enable comparator, 0 = disable comparator + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_state_set(int enable_lower_comparator, + int enable_upper_comparator); + +/** + * pm8058_batt_alarm_threshold_set - set the lower and upper alarm thresholds + * @lower_threshold_mV: battery undervoltage threshold in millivolts + * @upper_threshold_mV: battery overvoltage threshold in millivolts + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_threshold_set(int lower_threshold_mV, + int upper_threshold_mV); + +/** + * pm8058_batt_alarm_status_read - get status of both threshold comparators + * + * RETURNS: < 0 = error + * 0 = battery voltage ok + * BIT(0) set = battery voltage below lower threshold + * BIT(1) set = battery voltage above upper threshold + */ +int pm8058_batt_alarm_status_read(void); + +/** + * pm8058_batt_alarm_register_notifier - register a notifier to run when a + * battery voltage change interrupt fires + * @nb: notifier block containing callback function to register + * + * nb->notifier_call must point to a function of this form - + * int (*notifier_call)(struct notifier_block *nb, unsigned long status, + * void *unused); + * "status" will receive the battery alarm status; "unused" will be NULL. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_register_notifier(struct notifier_block *nb); + +/** + * pm8058_batt_alarm_unregister_notifier - unregister a notifier that is run + * when a battery voltage change interrupt fires + * @nb: notifier block containing callback function to unregister + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_unregister_notifier(struct notifier_block *nb); + +/** + * pm8058_batt_alarm_hold_time_set - set hold time of interrupt output * + * @hold_time: amount of time that battery voltage must remain outside of the + * threshold range before the battery alarm interrupt triggers + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_hold_time_set(enum pm8058_batt_alarm_hold_time hold_time); + +/** + * pm8058_batt_alarm_pwm_rate_set - set battery alarm update rate * + * @use_pwm: 1 = use PWM update rate, 0 = comparators always active + * @clock_scaler: PWM clock scaler = 2 to 9 + * @clock_divider: PWM clock divider = 2 to 8 + * + * This function sets the rate at which the battery alarm module enables + * the threshold comparators. The rate is determined by the following equation: + * + * f_update = (1024 Hz) / (clock_divider * (2 ^ clock_scaler)) + * + * Thus, the update rate can range from 0.25 Hz to 128 Hz. + * + * RETURNS: an appropriate -ERRNO error value on error, or zero for success. + */ +int pm8058_batt_alarm_pwm_rate_set(int use_pwm, int clock_scaler, + int clock_divider); + +#endif /* __PMIC8058_BATT_ALARM_H__ */ diff --git a/include/linux/pmic8058-charger.h b/include/linux/pmic8058-charger.h new file mode 100644 index 00000000000..f1fd25c9d94 --- /dev/null +++ b/include/linux/pmic8058-charger.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PMIC8058_CHARGER_H__ +#define __PMIC8058_CHARGER_H__ + +#if defined(CONFIG_BATTERY_MSM8X60) || defined(CONFIG_BATTERY_MSM8X60_MODULE) +int pmic8058_get_charge_batt(void); +int pmic8058_set_charge_batt(int); +#else +int pmic8058_get_charge_batt(void) +{ + return -ENXIO; +} +int pmic8058_set_charge_batt(int) +{ + return -ENXIO; +} +#endif +#endif /* __PMIC8058_CHARGER_H__ */ diff --git a/include/linux/pmic8058-misc.h b/include/linux/pmic8058-misc.h new file mode 100644 index 00000000000..5675a93e720 --- /dev/null +++ b/include/linux/pmic8058-misc.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#ifndef __PMIC8058_MISC_H__ +#define __PMIC8058_MISC_H__ + +enum pm8058_vib_en_mode { + PM8058_VIB_MANUAL, + PM8058_VIB_DTEST1, + PM8058_VIB_DTEST2, + PM8058_VIB_DTEST3 +}; + +enum pm8058_coincell_chg_voltage { + PM8058_COINCELL_VOLTAGE_3p2V = 1, + PM8058_COINCELL_VOLTAGE_3p1V, + PM8058_COINCELL_VOLTAGE_3p0V, + PM8058_COINCELL_VOLTAGE_2p5V = 16 +}; + +enum pm8058_coincell_chg_resistor { + PM8058_COINCELL_RESISTOR_2100_OHMS, + PM8058_COINCELL_RESISTOR_1700_OHMS, + PM8058_COINCELL_RESISTOR_1200_OHMS, + PM8058_COINCELL_RESISTOR_800_OHMS +}; + +enum pm8058_coincell_chg_state { + PM8058_COINCELL_CHG_DISABLE, + PM8058_COINCELL_CHG_ENABLE +}; + +struct pm8058_vib_config { + u16 drive_mV; + u8 active_low; + enum pm8058_vib_en_mode enable_mode; +}; + +struct pm8058_coincell_chg_config { + enum pm8058_coincell_chg_state state; + enum pm8058_coincell_chg_voltage voltage; + enum pm8058_coincell_chg_resistor resistor; +}; + +int pm8058_vibrator_config(struct pm8058_vib_config *vib_config); +int pm8058_coincell_chg_config(struct pm8058_coincell_chg_config *chg_config); + +#endif /* __PMIC8058_MISC_H__ */ diff --git a/include/linux/pmic8058-nfc.h b/include/linux/pmic8058-nfc.h new file mode 100644 index 00000000000..5b2d6cfb0d8 --- /dev/null +++ b/include/linux/pmic8058-nfc.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __PMIC8058_NFC_H__ +#define __PMIC8058_NFC_H__ + +struct pm8058_nfc_device; + +/* masks, flags and status */ +#define PM_NFC_VDDLDO_MON_LEVEL 0x0003 +#define PM_NFC_VPH_PWR_EN 0x0008 +#define PM_NFC_EXT_VDDLDO_EN 0x0010 +#define PM_NFC_EN 0x0020 +#define PM_NFC_LDO_EN 0x0040 +#define PM_NFC_SUPPORT_EN 0x0080 + +#define PM_NFC_EXT_EN_HIGH 0x0100 +#define PM_NFC_MBG_EN_HIGH 0x0200 +#define PM_NFC_VDDLDO_OK_HIGH 0x0400 +#define PM_NFC_DTEST1_MODE 0x2000 +#define PM_NFC_ATEST_EN 0x4000 +#define PM_NFC_VDDLDO_MON_EN 0x8000 + +#define PM_NFC_CTRL_REQ (PM_NFC_SUPPORT_EN |\ + PM_NFC_LDO_EN |\ + PM_NFC_EN |\ + PM_NFC_EXT_VDDLDO_EN |\ + PM_NFC_VPH_PWR_EN |\ + PM_NFC_VDDLDO_MON_LEVEL) + +#define PM_NFC_TEST_REQ (PM_NFC_VDDLDO_MON_EN |\ + PM_NFC_DTEST1_MODE |\ + PM_NFC_ATEST_EN) + +#define PM_NFC_TEST_STATUS (PM_NFC_EXT_EN_HIGH |\ + PM_NFC_MBG_EN_HIGH |\ + PM_NFC_VDDLDO_OK_HIGH) + +/* + * pm8058_nfc_request - request a handle to access NFC device + */ +struct pm8058_nfc_device *pm8058_nfc_request(void); + +/* + * pm8058_nfc_config - configure NFC signals + * + * @nfcdev: the NFC device + * @mask: signal mask to configure + * @flags: control flags + */ +int pm8058_nfc_config(struct pm8058_nfc_device *nfcdev, u32 mask, u32 flags); + +/* + * pm8058_nfc_get_status - get NFC status + * + * @nfcdev: the NFC device + * @mask: of status mask to read + * @status: pointer to the status variable + */ +int pm8058_nfc_get_status(struct pm8058_nfc_device *nfcdev, + u32 mask, u32 *status); + +/* + * pm8058_nfc_free - free the NFC device + */ +void pm8058_nfc_free(struct pm8058_nfc_device *nfcdev); + +#endif /* __PMIC8058_NFC_H__ */ diff --git a/include/linux/pmic8058-othc.h b/include/linux/pmic8058-othc.h new file mode 100644 index 00000000000..4c598452c86 --- /dev/null +++ b/include/linux/pmic8058-othc.h @@ -0,0 +1,146 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __PMIC8058_OTHC_H__ +#define __PMIC8058_OTHC_H__ + +/* Accessory detecion flags */ +#define OTHC_MICBIAS_DETECT BIT(0) +#define OTHC_GPIO_DETECT BIT(1) +#define OTHC_SWITCH_DETECT BIT(2) +#define OTHC_ADC_DETECT BIT(3) + +enum othc_accessory_type { + OTHC_NO_DEVICE = 0, + OTHC_HEADSET = 1 << 0, + OTHC_HEADPHONE = 1 << 1, + OTHC_MICROPHONE = 1 << 2, + OTHC_ANC_HEADSET = 1 << 3, + OTHC_ANC_HEADPHONE = 1 << 4, + OTHC_ANC_MICROPHONE = 1 << 5, + OTHC_SVIDEO_OUT = 1 << 6, +}; + +struct accessory_adc_thres { + int min_threshold; + int max_threshold; +}; + +struct othc_accessory_info { + unsigned int accessory; + unsigned int detect_flags; + unsigned int gpio; + unsigned int active_low; + unsigned int key_code; + bool enabled; + struct accessory_adc_thres adc_thres; +}; + +enum othc_headset_type { + OTHC_HEADSET_NO, + OTHC_HEADSET_NC, +}; + +struct othc_regulator_config { + const char *regulator; + unsigned int max_uV; + unsigned int min_uV; +}; + +/* Signal control for OTHC module */ +enum othc_micbias_enable { + /* Turn off MICBIAS signal */ + OTHC_SIGNAL_OFF, + /* Turn on MICBIAS signal when TCXO is enabled */ + OTHC_SIGNAL_TCXO, + /* Turn on MICBIAS signal when PWM is high or TCXO is enabled */ + OTHC_SIGNAL_PWM_TCXO, + /* MICBIAS always enabled */ + OTHC_SIGNAL_ALWAYS_ON, +}; + +/* Number of MICBIAS lines supported by PMIC8058 */ +enum othc_micbias { + OTHC_MICBIAS_0, + OTHC_MICBIAS_1, + OTHC_MICBIAS_2, + OTHC_MICBIAS_MAX, +}; + +enum othc_micbias_capability { + /* MICBIAS used only for BIAS with on/off capability */ + OTHC_MICBIAS, + /* MICBIAS used to support HSED functionality */ + OTHC_MICBIAS_HSED, +}; + +struct othc_switch_info { + u32 min_adc_threshold; + u32 max_adc_threshold; + u32 key_code; +}; + +struct othc_n_switch_config { + u32 voltage_settling_time_ms; + u8 num_adc_samples; + uint32_t adc_channel; + struct othc_switch_info *switch_info; + u8 num_keys; + bool default_sw_en; + u8 default_sw_idx; +}; + +struct hsed_bias_config { + enum othc_headset_type othc_headset; + u16 othc_lowcurr_thresh_uA; + u16 othc_highcurr_thresh_uA; + u32 othc_hyst_prediv_us; + u32 othc_period_clkdiv_us; + u32 othc_hyst_clk_us; + u32 othc_period_clk_us; + int othc_wakeup; +}; + +/* Configuration data for HSED */ +struct othc_hsed_config { + struct hsed_bias_config *hsed_bias_config; + unsigned long detection_delay_ms; + /* Switch configuration */ + unsigned long switch_debounce_ms; + bool othc_support_n_switch; /* Set if supporting > 1 switch */ + struct othc_n_switch_config *switch_config; + /* Accessory configuration */ + bool accessories_support; + bool accessories_adc_support; + uint32_t accessories_adc_channel; + struct othc_accessory_info *accessories; + int othc_num_accessories; + int video_out_gpio; + int ir_gpio; +}; + +struct pmic8058_othc_config_pdata { + enum othc_micbias micbias_select; + enum othc_micbias_enable micbias_enable; + enum othc_micbias_capability micbias_capability; + struct othc_hsed_config *hsed_config; + const char *hsed_name; + struct othc_regulator_config *micbias_regulator; +}; + +int pm8058_micbias_enable(enum othc_micbias micbias, + enum othc_micbias_enable enable); +int pm8058_othc_svideo_enable(enum othc_micbias micbias, + bool enable); + +#endif /* __PMIC8058_OTHC_H__ */ diff --git a/include/linux/pmic8058-pwm.h b/include/linux/pmic8058-pwm.h new file mode 100644 index 00000000000..2523f99d41d --- /dev/null +++ b/include/linux/pmic8058-pwm.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __PMIC8058_PWM_H__ +#define __PMIC8058_PWM_H__ + +/* The MAX value is computation limit. Hardware limit is 393 seconds. */ +#define PM_PWM_PERIOD_MAX (274 * USEC_PER_SEC) +/* The MIN value is hardware limit. */ +#define PM_PWM_PERIOD_MIN 7 /* micro seconds */ + +struct pm8058_pwm_pdata { + int (*config)(struct pwm_device *pwm, int ch, int on); + int (*enable)(struct pwm_device *pwm, int ch, int on); +}; + +#define PM_PWM_LUT_SIZE 64 +#define PM_PWM_LUT_DUTY_TIME_MAX 512 /* ms */ +#define PM_PWM_LUT_PAUSE_MAX (7000 * PM_PWM_LUT_DUTY_TIME_MAX) + +/* Flags for Look Up Table */ +#define PM_PWM_LUT_LOOP 0x01 +#define PM_PWM_LUT_RAMP_UP 0x02 +#define PM_PWM_LUT_REVERSE 0x04 +#define PM_PWM_LUT_PAUSE_HI_EN 0x10 +#define PM_PWM_LUT_PAUSE_LO_EN 0x20 + +#define PM_PWM_LUT_NO_TABLE 0x100 + +/* PWM LED ID */ +#define PM_PWM_LED_0 0 +#define PM_PWM_LED_1 1 +#define PM_PWM_LED_2 2 +#define PM_PWM_LED_KPD 3 +#define PM_PWM_LED_FLASH 4 +#define PM_PWM_LED_FLASH1 5 + +/* PWM LED configuration mode */ +#define PM_PWM_CONF_NONE 0x0 +#define PM_PWM_CONF_PWM1 0x1 +#define PM_PWM_CONF_PWM2 0x2 +#define PM_PWM_CONF_PWM3 0x3 +#define PM_PWM_CONF_DTEST1 0x4 +#define PM_PWM_CONF_DTEST2 0x5 +#define PM_PWM_CONF_DTEST3 0x6 +#define PM_PWM_CONF_DTEST4 0x7 + +/* + * pm8058_pwm_lut_config - change a PWM device configuration to use LUT + * + * @pwm: the PWM device + * @period_us: period in micro second + * @duty_pct: arrary of duty cycles in percent, like 20, 50. + * @duty_time_ms: time for each duty cycle in millisecond + * @start_idx: start index in lookup table from 0 to MAX-1 + * @idx_len: number of index + * @pause_lo: pause time in millisecond at low index + * @pause_hi: pause time in millisecond at high index + * @flags: control flags + * + */ +int pm8058_pwm_lut_config(struct pwm_device *pwm, int period_us, + int duty_pct[], int duty_time_ms, int start_idx, + int len, int pause_lo, int pause_hi, int flags); + +/* + * pm8058_pwm_lut_enable - control a PWM device to start/stop LUT ramp + * + * @pwm: the PWM device + * @start: to start (1), or stop (0) + */ +int pm8058_pwm_lut_enable(struct pwm_device *pwm, int start); + +int pm8058_pwm_set_dtest(struct pwm_device *pwm, int enable); + +int pm8058_pwm_config_led(struct pwm_device *pwm, int id, + int mode, int max_current); + +#endif /* __PMIC8058_PWM_H__ */ diff --git a/include/linux/pmic8058-pwrkey.h b/include/linux/pmic8058-pwrkey.h new file mode 100644 index 00000000000..6953872a446 --- /dev/null +++ b/include/linux/pmic8058-pwrkey.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __PMIC8058_PWRKEY_H__ +#define __PMIC8058_PWRKEY_H__ + +struct pmic8058_pwrkey_pdata { + bool pull_up; + /* time after which pwr key event should be generated, if key is + * released before that then end key event is reported + */ + u16 pwrkey_time_ms; + /* time delay for pwr-key state change + * interrupt triggering. + */ + u32 kpd_trigger_delay_us; + u32 wakeup; +}; + +#endif /* __PMIC8058_PWRKEY_H__ */ diff --git a/include/linux/pmic8058-upl.h b/include/linux/pmic8058-upl.h new file mode 100644 index 00000000000..a8979f42fc1 --- /dev/null +++ b/include/linux/pmic8058-upl.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __PMIC8058_UPL_H__ +#define __PMIC8058_UPL_H__ + +struct pm8058_upl_device; + +/* control masks and flags */ +#define PM8058_UPL_MOD_ENABLE_MASK (0x10) +#define PM8058_UPL_MOD_ENABLE (0x10) +#define PM8058_UPL_MOD_DISABLE (0x00) + +#define PM8058_UPL_OUT_DTEST_MASK (0xE0) +#define PM8058_UPL_OUT_GPIO_ONLY (0x00) +#define PM8058_UPL_OUT_DTEST_1 (0x80) +#define PM8058_UPL_OUT_DTEST_2 (0xA0) +#define PM8058_UPL_OUT_DTEST_3 (0xC0) +#define PM8058_UPL_OUT_DTEST_4 (0xE0) + +#define PM8058_UPL_IN_A_MASK (0x01) +#define PM8058_UPL_IN_A_GPIO (0x00) +#define PM8058_UPL_IN_A_DTEST (0x01) +#define PM8058_UPL_IN_B_MASK (0x02) +#define PM8058_UPL_IN_B_GPIO (0x00) +#define PM8058_UPL_IN_B_DTEST (0x02) +#define PM8058_UPL_IN_C_MASK (0x04) +#define PM8058_UPL_IN_C_GPIO (0x00) +#define PM8058_UPL_IN_C_DTEST (0x04) +#define PM8058_UPL_IN_D_MASK (0x08) +#define PM8058_UPL_IN_D_GPIO (0x00) +#define PM8058_UPL_IN_D_DTEST (0x08) + +/* + * pm8058_upl_request - request a handle to access UPL device + */ +struct pm8058_upl_device *pm8058_upl_request(void); + +int pm8058_upl_read_truthtable(struct pm8058_upl_device *upldev, + u16 *truthtable); + +int pm8058_upl_write_truthtable(struct pm8058_upl_device *upldev, + u16 truthtable); + +/* + * pm8058_upl_config - configure UPL I/O settings and UPL enable/disable + * + * @upldev: the UPL device + * @mask: setting mask to configure + * @flags: setting flags + */ +int pm8058_upl_config(struct pm8058_upl_device *upldev, u32 mask, u32 flags); + +#endif /* __PMIC8058_UPL_H__ */ diff --git a/include/linux/pmic8058-vibrator.h b/include/linux/pmic8058-vibrator.h new file mode 100644 index 00000000000..e5390587146 --- /dev/null +++ b/include/linux/pmic8058-vibrator.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __PMIC8058_VIBRATOR_H__ +#define __PMIC8058_VIBRATOR_H__ + +struct pmic8058_vibrator_pdata { + int initial_vibrate_ms; + int max_timeout_ms; + + int level_mV; +}; + +#endif /* __PMIC8058_VIBRATOR_H__ */ diff --git a/include/linux/pmic8058-xoadc.h b/include/linux/pmic8058-xoadc.h new file mode 100644 index 00000000000..8a5820f27ae --- /dev/null +++ b/include/linux/pmic8058-xoadc.h @@ -0,0 +1,84 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Qualcomm XOADC Driver header file + */ + +#ifndef _XOADC_H +#define _XOADC_H_ + +#include +#include +#include + +struct xoadc_conv_state { + struct adc_conv_slot *context; + struct list_head slots; + struct mutex list_lock; +}; + +#define CHANNEL_VCOIN 0 +#define CHANNEL_VBAT 1 +#define CHANNEL_VCHG 2 +#define CHANNEL_CHG_MONITOR 3 +#define CHANNEL_VPH_PWR 4 +#define CHANNEL_MPP5 5 +#define CHANNEL_MPP6 6 +#define CHANNEL_MPP7 7 +#define CHANNEL_MPP8 8 +#define CHANNEL_MPP9 9 +#define CHANNEL_USB_VBUS 0Xa +#define CHANNEL_DIE_TEMP 0Xb +#define CHANNEL_INTERNAL 0xc +#define CHANNEL_125V 0xd +#define CHANNEL_INTERNAL_2 0Xe +#define CHANNEL_MUXOFF 0xf + +#define XOADC_MPP_3 0x2 +#define XOADC_MPP_4 0X3 +#define XOADC_MPP_5 0x4 +#define XOADC_MPP_7 0x6 +#define XOADC_MPP_8 0x7 +#define XOADC_MPP_10 0X9 + +#define XOADC_PMIC_0 0x0 + +#define CHANNEL_ADC_625_MV 625 + +struct xoadc_platform_data { + struct adc_properties *xoadc_prop; + u32 (*xoadc_setup) (void); + void (*xoadc_shutdown) (void); + void (*xoadc_mpp_config) (void); + int (*xoadc_vreg_set) (int); + int (*xoadc_vreg_setup) (void); + void (*xoadc_vreg_shutdown) (void); + u32 xoadc_num; + u32 xoadc_wakeup; +}; + +int32_t pm8058_xoadc_read_adc_code(uint32_t , int32_t *data); + +int32_t pm8058_xoadc_select_chan_and_start_conv(uint32_t, + struct adc_conv_slot *); + +void pm8058_xoadc_slot_request(uint32_t, struct adc_conv_slot **slot); + +void pm8058_xoadc_restore_slot(uint32_t, struct adc_conv_slot *slot); + +struct adc_properties *pm8058_xoadc_get_properties(uint32_t); + +int32_t pm8058_xoadc_calibrate(uint32_t, struct adc_conv_slot *, int *); + +int32_t pm8058_xoadc_registered(void); + +int32_t pm8058_xoadc_calib_device(uint32_t adc_instance); +#endif diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 7c775751392..e0c8c3fd5f4 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -3,29 +3,31 @@ struct pwm_device; +/* Add __weak functions to support PWM */ + /* * pwm_request - request a PWM device */ -struct pwm_device *pwm_request(int pwm_id, const char *label); +struct pwm_device __weak *pwm_request(int pwm_id, const char *label); /* * pwm_free - free a PWM device */ -void pwm_free(struct pwm_device *pwm); +void __weak pwm_free(struct pwm_device *pwm); /* * pwm_config - change a PWM device configuration */ -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns); +int __weak pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns); /* * pwm_enable - start a PWM output toggling */ -int pwm_enable(struct pwm_device *pwm); +int __weak pwm_enable(struct pwm_device *pwm); /* * pwm_disable - stop a PWM output toggling */ -void pwm_disable(struct pwm_device *pwm); +void __weak pwm_disable(struct pwm_device *pwm); #endif /* __LINUX_PWM_H */ diff --git a/include/linux/qcomwlan7x27a_pwrif.h b/include/linux/qcomwlan7x27a_pwrif.h new file mode 100644 index 00000000000..16e1783f39e --- /dev/null +++ b/include/linux/qcomwlan7x27a_pwrif.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __QCOM_WLAN_PWRIF_H__ +#define __QCOM_WLAN_PWRIF_H__ + +#include + +int chip_power_qrf6285(bool on); + +#endif /* __QCOM_WLAN_PWRIF_H__ */ diff --git a/include/linux/qcomwlan_pwrif.h b/include/linux/qcomwlan_pwrif.h new file mode 100644 index 00000000000..74d2a800629 --- /dev/null +++ b/include/linux/qcomwlan_pwrif.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __QCOM_WLAN_PWRIF_H__ +#define __QCOM_WLAN_PWRIF_H__ + +/* + * Headers for WLAN Power Interface Functions + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHIP_POWER_ON 1 +#define CHIP_POWER_OFF 0 + +int vos_chip_power_qrf8615(int on); +int qcomwlan_pmic_xo_core_force_enable(int on); +int qcomwlan_freq_change_1p3v_supply(enum rpm_vreg_freq freq); + +#endif /* __QCOM_WLAN_PWRIF_H__ */ diff --git a/include/linux/qcomwlan_secif.h b/include/linux/qcomwlan_secif.h new file mode 100644 index 00000000000..8c6e4253259 --- /dev/null +++ b/include/linux/qcomwlan_secif.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __QCOM_WLAN_SECIF_H__ +#define __QCOM_WLAN_SECIF_H__ + +#include + +/* + * Prototypes for WLAN Security Interface Functions + */ + +extern struct crypto_ahash * +wcnss_wlan_crypto_alloc_ahash(const char *alg_name, u32 type, u32 mask); + +extern int wcnss_wlan_crypto_ahash_digest(struct ahash_request *req); +extern void wcnss_wlan_crypto_free_ahash(struct crypto_ahash *tfm); +extern int wcnss_wlan_crypto_ahash_setkey(struct crypto_ahash *tfm, + const u8 *key, unsigned int keylen); +extern struct crypto_ablkcipher * +wcnss_wlan_crypto_alloc_ablkcipher(const char *alg_name, u32 type, u32 mask); +extern void wcnss_wlan_ablkcipher_request_free(struct ablkcipher_request *req); + +#endif /* __QCOM_WLAN_SECIF_H__ */ diff --git a/include/linux/qcota.h b/include/linux/qcota.h new file mode 100644 index 00000000000..afc6b7fbd15 --- /dev/null +++ b/include/linux/qcota.h @@ -0,0 +1,165 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __QCOTA__H +#define __QCOTA__H + +#include +#include + +#define QCE_OTA_MAX_BEARER 31 +#define OTA_KEY_SIZE 16 /* 128 bits of keys. */ + +enum qce_ota_dir_enum { + QCE_OTA_DIR_UPLINK = 0, + QCE_OTA_DIR_DOWNLINK = 1, + QCE_OTA_DIR_LAST +}; + +enum qce_ota_algo_enum { + QCE_OTA_ALGO_KASUMI = 0, + QCE_OTA_ALGO_SNOW3G = 1, + QCE_OTA_ALGO_LAST +}; + +/** + * struct qce_f8_req - qce f8 request + * @data_in: packets input data stream to be ciphered. + * If NULL, streaming mode operation. + * @data_out: ciphered packets output data. + * @data_len: length of data_in and data_out in bytes. + * @count_c: count-C, ciphering sequence number, 32 bit + * @bearer: 5 bit of radio bearer identifier. + * @ckey: 128 bits of confidentiality key, + * ckey[0] bit 127-120, ckey[1] bit 119-112,.., ckey[15] bit 7-0. + * @direction: uplink or donwlink. + * @algorithm: Kasumi, or Snow3G. + * + * If data_in is NULL, the engine will run in a special mode called + * key stream mode. In this special mode, the engine will generate + * key stream output for the number of bytes specified in the + * data_len, based on the input parameters of direction, algorithm, + * ckey, bearer, and count_c. The data_len is restricted to + * the length of multiple of 16 bytes. Application can then take the + * output stream, do a exclusive or to the input data stream, and + * generate the final cipher data stream. + */ +struct qce_f8_req { + uint8_t *data_in; + uint8_t *data_out; + uint16_t data_len; + uint32_t count_c; + uint8_t bearer; + uint8_t ckey[OTA_KEY_SIZE]; + enum qce_ota_dir_enum direction; + enum qce_ota_algo_enum algorithm; +}; + +/** + * struct qce_f8_multi_pkt_req - qce f8 multiple packet request + * Muliptle packets with uniform size, and + * F8 ciphering parameters can be ciphered in a + * single request. + * + * @num_pkt: number of packets. + * + * @cipher_start: ciphering starts offset within a packet. + * + * @cipher_size: number of bytes to be ciphered within a packet. + * + * @qce_f8_req: description of the packet and F8 parameters. + * The following fields have special meaning for + * multiple packet operation, + * + * @data_len: data_len indicates the length of a packet. + * + * @data_in: packets are concatenated together in a byte + * stream started at data_in. + * + * @data_out: The returned ciphered output for multiple + * packets. + * Each packet ciphered output are concatenated + * together into a byte stream started at data_out. + * Note, each ciphered packet output area from + * offset 0 to cipher_start-1, and from offset + * cipher_size to data_len -1 are remained + * unaltered from packet input area. + * @count_c: count-C of the first packet, 32 bit. + * + * + * In one request, multiple packets can be ciphered, and output to the + * data_out stream. + * + * Packet data are layed out contiguously in sequence in data_in, + * and data_out area. Every packet is identical size. + * If the PDU is not byte aligned, set the data_len value of + * to the rounded up value of the packet size. Eg, PDU size of + * 253 bits, set the packet size to 32 bytes. Next packet starts on + * the next byte boundary. + * + * For each packet, data from offset 0 to cipher_start + * will be left unchanged and output to the data_out area. + * This area of the packet can be for the RLC header, which is not + * to be ciphered. + * + * The ciphering of a packet starts from offset cipher_start, for + * cipher_size bytes of data. Data starting from + * offset cipher_start + cipher_size to the end of packet will be left + * unchanged and output to the dataOut area. + * + * For each packet the input arguments of bearer, direction, + * ckey, algoritm have to be the same. count_c is the ciphering sequence + * number of the first packet. The 2nd packet's ciphering sequence + * number is assumed to be count_c + 1. The 3rd packet's ciphering sequence + * number is count_c + 2..... + * + */ +struct qce_f8_multi_pkt_req { + uint16_t num_pkt; + uint16_t cipher_start; + uint16_t cipher_size; + struct qce_f8_req qce_f8_req; +}; + +/** + * struct qce_f9_req - qce f9 request + * @message: message + * @msize: message size in bytes (include the last partial byte). + * @last_bits: valid bits in the last byte of message. + * @mac_i: 32 bit message authentication code, to be returned. + * @fresh: random 32 bit number, one per user. + * @count_i: 32 bit count-I integrity sequence number. + * @direction: uplink or donwlink. + * @ikey: 128 bits of integrity key, + * ikey[0] bit 127-120, ikey[1] bit 119-112,.., ikey[15] bit 7-0. + * @algorithm: Kasumi, or Snow3G. + */ +struct qce_f9_req { + uint8_t *message; + uint16_t msize; + uint8_t last_bits; + uint32_t mac_i; + uint32_t fresh; + uint32_t count_i; + enum qce_ota_dir_enum direction; + uint8_t ikey[OTA_KEY_SIZE]; + enum qce_ota_algo_enum algorithm; +}; + +#define QCOTA_IOC_MAGIC 0x85 + +#define QCOTA_F8_REQ _IOWR(QCOTA_IOC_MAGIC, 1, struct qce_f8_req) +#define QCOTA_F8_MPKT_REQ _IOWR(QCOTA_IOC_MAGIC, 2, struct qce_f8_multi_pkt_req) +#define QCOTA_F9_REQ _IOWR(QCOTA_IOC_MAGIC, 3, struct qce_f9_req) + +#endif /* __QCOTA__H */ diff --git a/include/linux/qfp_fuse.h b/include/linux/qfp_fuse.h new file mode 100644 index 00000000000..8e3fd5e680b --- /dev/null +++ b/include/linux/qfp_fuse.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _QFP_FUSE_H_ +#define _QFP_FUSE_H_ + +#include +#include + +#define QFP_FUSE_IOC_MAGIC 0x92 + +#define QFP_FUSE_IOC_WRITE _IO(QFP_FUSE_IOC_MAGIC, 1) +#define QFP_FUSE_IOC_READ _IO(QFP_FUSE_IOC_MAGIC, 2) + + +/* + * This structure is used to exchange the fuse parameters with the user + * space application. The pointer to this structure is passed to the ioctl + * function. + * offset = offset from the QFPROM base for the data to be read/written. + * size = number of 32-bit words to be read/written. + * data = pointer to the 32 bit word denoting userspace data. + */ +struct qfp_fuse_req { + u32 offset; + u32 size; + u32 *data; +}; + +#endif diff --git a/include/linux/regulator/consumer.h b/include/linux/regulator/consumer.h index 9e87c1cb727..d21040716d3 100644 --- a/include/linux/regulator/consumer.h +++ b/include/linux/regulator/consumer.h @@ -237,6 +237,11 @@ static inline void regulator_bulk_free(int num_consumers, { } +static inline int regulator_count_voltages(struct regulator *regulator) +{ + return 0; +} + static inline int regulator_set_voltage(struct regulator *regulator, int min_uV, int max_uV) { diff --git a/include/linux/regulator/gpio-regulator.h b/include/linux/regulator/gpio-regulator.h new file mode 100644 index 00000000000..68abf4562e1 --- /dev/null +++ b/include/linux/regulator/gpio-regulator.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __GPIO_REGULATOR_H__ +#define __GPIO_REGULATOR_H__ + +#include + +#define GPIO_REGULATOR_DEV_NAME "gpio-regulator" + +/** + * struct gpio_regulator_platform_data - GPIO regulator platform data + * @init_data: regulator constraints + * @gpio_label: label to use when requesting the GPIO + * @regulator_name: name for regulator used during registration + * @gpio: gpio number + * @active_low: 0 = regulator is enabled when GPIO outputs high + * 1 = regulator is enabled when GPIO outputs low + */ +struct gpio_regulator_platform_data { + struct regulator_init_data init_data; + char *gpio_label; + char *regulator_name; + unsigned gpio; + int active_low; +}; + +#endif diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h index ce3127a75c8..6804ef3a390 100644 --- a/include/linux/regulator/machine.h +++ b/include/linux/regulator/machine.h @@ -192,6 +192,7 @@ int regulator_suspend_finish(void); #ifdef CONFIG_REGULATOR void regulator_has_full_constraints(void); void regulator_use_dummy_regulator(void); +void regulator_suppress_info_printing(void); #else static inline void regulator_has_full_constraints(void) { @@ -200,6 +201,10 @@ static inline void regulator_has_full_constraints(void) static inline void regulator_use_dummy_regulator(void) { } + +static inline void regulator_suppress_info_printing(void) +{ +} #endif #endif diff --git a/include/linux/regulator/pm8058-xo.h b/include/linux/regulator/pm8058-xo.h new file mode 100644 index 00000000000..9b363c47e0d --- /dev/null +++ b/include/linux/regulator/pm8058-xo.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PM8058_XO_H__ +#define __PM8058_XO_H__ + +#include + +#define PM8058_XO_BUFFER_DEV_NAME "pm8058-xo-buffer" + +/* XO buffer control ids */ +#define PM8058_XO_ID_A0 0 +#define PM8058_XO_ID_A1 1 + +#define PM8058_XO_ID_MAX (PM8058_XO_ID_A1 + 1) + +struct pm8058_xo_pdata { + struct regulator_init_data init_data; +}; + +#endif diff --git a/include/linux/regulator/pm8921-regulator.h b/include/linux/regulator/pm8921-regulator.h new file mode 100644 index 00000000000..5ed5b0d6049 --- /dev/null +++ b/include/linux/regulator/pm8921-regulator.h @@ -0,0 +1,155 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PM8921_REGULATOR_H__ +#define __PM8921_REGULATOR_H__ + +#include + +#define PM8921_REGULATOR_DEV_NAME "pm8921-regulator" + +/** + * enum pm8921_vreg_id - PMIC 8921 regulator ID numbers + */ +enum pm8921_vreg_id { + PM8921_VREG_ID_L1 = 0, + PM8921_VREG_ID_L2, + PM8921_VREG_ID_L3, + PM8921_VREG_ID_L4, + PM8921_VREG_ID_L5, + PM8921_VREG_ID_L6, + PM8921_VREG_ID_L7, + PM8921_VREG_ID_L8, + PM8921_VREG_ID_L9, + PM8921_VREG_ID_L10, + PM8921_VREG_ID_L11, + PM8921_VREG_ID_L12, + PM8921_VREG_ID_L14, + PM8921_VREG_ID_L15, + PM8921_VREG_ID_L16, + PM8921_VREG_ID_L17, + PM8921_VREG_ID_L18, + PM8921_VREG_ID_L21, + PM8921_VREG_ID_L22, + PM8921_VREG_ID_L23, + PM8921_VREG_ID_L24, + PM8921_VREG_ID_L25, + PM8921_VREG_ID_L26, + PM8921_VREG_ID_L27, + PM8921_VREG_ID_L28, + PM8921_VREG_ID_L29, + PM8921_VREG_ID_S1, + PM8921_VREG_ID_S2, + PM8921_VREG_ID_S3, + PM8921_VREG_ID_S4, + PM8921_VREG_ID_S5, + PM8921_VREG_ID_S6, + PM8921_VREG_ID_S7, + PM8921_VREG_ID_S8, + PM8921_VREG_ID_LVS1, + PM8921_VREG_ID_LVS2, + PM8921_VREG_ID_LVS3, + PM8921_VREG_ID_LVS4, + PM8921_VREG_ID_LVS5, + PM8921_VREG_ID_LVS6, + PM8921_VREG_ID_LVS7, + PM8921_VREG_ID_USB_OTG, + PM8921_VREG_ID_HDMI_MVS, + PM8921_VREG_ID_NCP, + /* The following are IDs for regulator devices to enable pin control. */ + PM8921_VREG_ID_L1_PC, + PM8921_VREG_ID_L2_PC, + PM8921_VREG_ID_L3_PC, + PM8921_VREG_ID_L4_PC, + PM8921_VREG_ID_L5_PC, + PM8921_VREG_ID_L6_PC, + PM8921_VREG_ID_L7_PC, + PM8921_VREG_ID_L8_PC, + PM8921_VREG_ID_L9_PC, + PM8921_VREG_ID_L10_PC, + PM8921_VREG_ID_L11_PC, + PM8921_VREG_ID_L12_PC, + PM8921_VREG_ID_L14_PC, + PM8921_VREG_ID_L15_PC, + PM8921_VREG_ID_L16_PC, + PM8921_VREG_ID_L17_PC, + PM8921_VREG_ID_L18_PC, + PM8921_VREG_ID_L21_PC, + PM8921_VREG_ID_L22_PC, + PM8921_VREG_ID_L23_PC, + + PM8921_VREG_ID_L29_PC, + PM8921_VREG_ID_S1_PC, + PM8921_VREG_ID_S2_PC, + PM8921_VREG_ID_S3_PC, + PM8921_VREG_ID_S4_PC, + + PM8921_VREG_ID_S7_PC, + PM8921_VREG_ID_S8_PC, + PM8921_VREG_ID_LVS1_PC, + + PM8921_VREG_ID_LVS3_PC, + PM8921_VREG_ID_LVS4_PC, + PM8921_VREG_ID_LVS5_PC, + PM8921_VREG_ID_LVS6_PC, + PM8921_VREG_ID_LVS7_PC, + + PM8921_VREG_ID_MAX, +}; + +/* Pin control input pins. */ +#define PM8921_VREG_PIN_CTRL_NONE 0x00 +#define PM8921_VREG_PIN_CTRL_D1 0x01 +#define PM8921_VREG_PIN_CTRL_A0 0x02 +#define PM8921_VREG_PIN_CTRL_A1 0x04 +#define PM8921_VREG_PIN_CTRL_A2 0x08 + +/* Minimum high power mode loads in uA. */ +#define PM8921_VREG_LDO_50_HPM_MIN_LOAD 5000 +#define PM8921_VREG_LDO_150_HPM_MIN_LOAD 10000 +#define PM8921_VREG_LDO_300_HPM_MIN_LOAD 10000 +#define PM8921_VREG_LDO_600_HPM_MIN_LOAD 10000 +#define PM8921_VREG_LDO_1200_HPM_MIN_LOAD 10000 +#define PM8921_VREG_SMPS_1500_HPM_MIN_LOAD 100000 +#define PM8921_VREG_SMPS_2000_HPM_MIN_LOAD 100000 + +/** + * enum pm8921_vreg_pin_function - action to perform when pin control is active + * %PM8921_VREG_PIN_FN_ENABLE: pin control enables the regulator + * %PM8921_VREG_PIN_FN_MODE: pin control changes mode from LPM to HPM + */ +enum pm8921_vreg_pin_function { + PM8921_VREG_PIN_FN_ENABLE = 0, + PM8921_VREG_PIN_FN_MODE, +}; + +/** + * struct pm8921_regulator_platform_data - PMIC 8921 regulator platform data + * @init_data: regulator constraints + * @id: regulator id; from enum pm8921_vreg_id + * @pull_down_enable: 0 = no pulldown, 1 = pulldown when regulator disabled + * @pin_ctrl: pin control inputs to use for the regulator; should be + * a combination of PM8921_VREG_PIN_CTRL_* values + * @pin_fn: action to perform when pin control pin is active + * @system_uA: current drawn from regulator not accounted for by any + * regulator framework consumer + */ +struct pm8921_regulator_platform_data { + struct regulator_init_data init_data; + enum pm8921_vreg_id id; + unsigned pull_down_enable; + unsigned pin_ctrl; + enum pm8921_vreg_pin_function pin_fn; + int system_uA; +}; + +#endif diff --git a/include/linux/regulator/pmic8058-regulator.h b/include/linux/regulator/pmic8058-regulator.h new file mode 100644 index 00000000000..83d44128a05 --- /dev/null +++ b/include/linux/regulator/pmic8058-regulator.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __PMIC8058_REGULATOR_H__ +#define __PMIC8058_REGULATOR_H__ + +#include + +/* Low dropout regulator ids */ +#define PM8058_VREG_ID_L0 0 +#define PM8058_VREG_ID_L1 1 +#define PM8058_VREG_ID_L2 2 +#define PM8058_VREG_ID_L3 3 +#define PM8058_VREG_ID_L4 4 +#define PM8058_VREG_ID_L5 5 +#define PM8058_VREG_ID_L6 6 +#define PM8058_VREG_ID_L7 7 +#define PM8058_VREG_ID_L8 8 +#define PM8058_VREG_ID_L9 9 +#define PM8058_VREG_ID_L10 10 +#define PM8058_VREG_ID_L11 11 +#define PM8058_VREG_ID_L12 12 +#define PM8058_VREG_ID_L13 13 +#define PM8058_VREG_ID_L14 14 +#define PM8058_VREG_ID_L15 15 +#define PM8058_VREG_ID_L16 16 +#define PM8058_VREG_ID_L17 17 +#define PM8058_VREG_ID_L18 18 +#define PM8058_VREG_ID_L19 19 +#define PM8058_VREG_ID_L20 20 +#define PM8058_VREG_ID_L21 21 +#define PM8058_VREG_ID_L22 22 +#define PM8058_VREG_ID_L23 23 +#define PM8058_VREG_ID_L24 24 +#define PM8058_VREG_ID_L25 25 + +/* Switched-mode power supply regulator ids */ +#define PM8058_VREG_ID_S0 26 +#define PM8058_VREG_ID_S1 27 +#define PM8058_VREG_ID_S2 28 +#define PM8058_VREG_ID_S3 29 +#define PM8058_VREG_ID_S4 30 + +/* Low voltage switch regulator ids */ +#define PM8058_VREG_ID_LVS0 31 +#define PM8058_VREG_ID_LVS1 32 + +/* Negative charge pump regulator id */ +#define PM8058_VREG_ID_NCP 33 + +#define PM8058_VREG_MAX (PM8058_VREG_ID_NCP + 1) + +#define PM8058_VREG_PIN_CTRL_NONE 0x00 +#define PM8058_VREG_PIN_CTRL_A0 0x01 +#define PM8058_VREG_PIN_CTRL_A1 0x02 +#define PM8058_VREG_PIN_CTRL_D0 0x04 +#define PM8058_VREG_PIN_CTRL_D1 0x08 + +/* Minimum high power mode loads in uA. */ +#define PM8058_VREG_LDO_50_HPM_MIN_LOAD 5000 +#define PM8058_VREG_LDO_150_HPM_MIN_LOAD 10000 +#define PM8058_VREG_LDO_300_HPM_MIN_LOAD 10000 +#define PM8058_VREG_SMPS_HPM_MIN_LOAD 50000 + +/* Pin ctrl enables/disables or toggles high/low power modes */ +enum pm8058_vreg_pin_fn { + PM8058_VREG_PIN_FN_ENABLE = 0, + PM8058_VREG_PIN_FN_MODE, +}; + +struct pm8058_vreg_pdata { + struct regulator_init_data init_data; + unsigned pull_down_enable; + unsigned pin_ctrl; + enum pm8058_vreg_pin_fn pin_fn; +}; + +#endif diff --git a/include/linux/regulator/pmic8901-regulator.h b/include/linux/regulator/pmic8901-regulator.h new file mode 100644 index 00000000000..953c4addf9c --- /dev/null +++ b/include/linux/regulator/pmic8901-regulator.h @@ -0,0 +1,79 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __PMIC8901_REGULATOR_H__ +#define __PMIC8901_REGULATOR_H__ + +#include + +/* Low dropout regulator ids */ +#define PM8901_VREG_ID_L0 0 +#define PM8901_VREG_ID_L1 1 +#define PM8901_VREG_ID_L2 2 +#define PM8901_VREG_ID_L3 3 +#define PM8901_VREG_ID_L4 4 +#define PM8901_VREG_ID_L5 5 +#define PM8901_VREG_ID_L6 6 + +/* Switched-mode power supply regulator ids */ +#define PM8901_VREG_ID_S0 7 +#define PM8901_VREG_ID_S1 8 +#define PM8901_VREG_ID_S2 9 +#define PM8901_VREG_ID_S3 10 +#define PM8901_VREG_ID_S4 11 + +/* External regulator controlled by MPP pin ids */ +#define PM8901_VREG_ID_MPP0 12 + +/* Low voltage switch regulator ids */ +#define PM8901_VREG_ID_LVS0 13 +#define PM8901_VREG_ID_LVS1 14 +#define PM8901_VREG_ID_LVS2 15 +#define PM8901_VREG_ID_LVS3 16 + +/* Medium voltage switch regulator ids */ +#define PM8901_VREG_ID_MVS0 17 + +/* USB OTG voltage switch regulator ids */ +#define PM8901_VREG_ID_USB_OTG 18 + +/* HDMI medium voltage switch regulator ids */ +#define PM8901_VREG_ID_HDMI_MVS 19 + +#define PM8901_VREG_MAX (PM8901_VREG_ID_HDMI_MVS + 1) + +#define PM8901_VREG_PIN_CTRL_NONE 0x00 +#define PM8901_VREG_PIN_CTRL_A0 0x01 +#define PM8901_VREG_PIN_CTRL_A1 0x02 +#define PM8901_VREG_PIN_CTRL_D0 0x04 +#define PM8901_VREG_PIN_CTRL_D1 0x08 + +/* Minimum high power mode loads in uA. */ +#define PM8901_VREG_LDO_300_HPM_MIN_LOAD 10000 +#define PM8901_VREG_FTSMPS_HPM_MIN_LOAD 100000 + +/* Pin ctrl enables/disables or toggles high/low power modes */ +enum pm8901_vreg_pin_fn { + PM8901_VREG_PIN_FN_ENABLE = 0, + PM8901_VREG_PIN_FN_MODE, +}; + +struct pm8901_vreg_pdata { + struct regulator_init_data init_data; + unsigned pull_down_enable; + unsigned pin_ctrl; + enum pm8901_vreg_pin_fn pin_fn; + unsigned active_high; /* For use with MPP. */ +}; + +#endif diff --git a/include/linux/remote_spinlock.h b/include/linux/remote_spinlock.h new file mode 100644 index 00000000000..535592e47d6 --- /dev/null +++ b/include/linux/remote_spinlock.h @@ -0,0 +1,116 @@ +/* Copyright (c) 2008-2009, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __LINUX_REMOTE_SPINLOCK_H +#define __LINUX_REMOTE_SPINLOCK_H + +#include +#include + +#include + +/* Grabbing a local spin lock before going for a remote lock has several + * advantages: + * 1. Get calls to preempt enable/disable and IRQ save/restore for free. + * 2. For UP kernel, there is no overhead. + * 3. Reduces the possibility of executing the remote spin lock code. This is + * especially useful when the remote CPUs' mutual exclusion instructions + * don't work with the local CPUs' instructions. In such cases, one has to + * use software based mutex algorithms (e.g. Lamport's bakery algorithm) + * which could get expensive when the no. of contending CPUs is high. + * 4. In the case of software based mutex algorithm the exection time will be + * smaller since the no. of contending CPUs is reduced by having just one + * contender for all the local CPUs. + * 5. Get most of the spin lock debug features for free. + * 6. The code will continue to work "gracefully" even when the remote spin + * lock code is stubbed out for debug purposes or when there is no remote + * CPU in some board/machine types. + */ +typedef struct { + spinlock_t local; + _remote_spinlock_t remote; +} remote_spinlock_t; + +#define remote_spin_lock_init(lock, id) \ + ({ \ + spin_lock_init(&((lock)->local)); \ + _remote_spin_lock_init(id, &((lock)->remote)); \ + }) +#define remote_spin_lock(lock) \ + do { \ + spin_lock(&((lock)->local)); \ + _remote_spin_lock(&((lock)->remote)); \ + } while (0) +#define remote_spin_unlock(lock) \ + do { \ + _remote_spin_unlock(&((lock)->remote)); \ + spin_unlock(&((lock)->local)); \ + } while (0) +#define remote_spin_lock_irqsave(lock, flags) \ + do { \ + spin_lock_irqsave(&((lock)->local), flags); \ + _remote_spin_lock(&((lock)->remote)); \ + } while (0) +#define remote_spin_unlock_irqrestore(lock, flags) \ + do { \ + _remote_spin_unlock(&((lock)->remote)); \ + spin_unlock_irqrestore(&((lock)->local), flags); \ + } while (0) +#define remote_spin_trylock(lock) \ + ({ \ + spin_trylock(&((lock)->local)) \ + ? _remote_spin_trylock(&((lock)->remote)) \ + ? 1 \ + : ({ spin_unlock(&((lock)->local)); 0; }) \ + : 0; \ + }) +#define remote_spin_trylock_irqsave(lock, flags) \ + ({ \ + spin_trylock_irqsave(&((lock)->local), flags) \ + ? _remote_spin_trylock(&((lock)->remote)) \ + ? 1 \ + : ({ spin_unlock_irqrestore(&((lock)->local), flags); \ + 0; }) \ + : 0; \ + }) + + +typedef struct { + struct mutex local; + _remote_mutex_t remote; +} remote_mutex_t; + +#define remote_mutex_init(lock, id) \ + ({ \ + mutex_init(&((lock)->local)); \ + _remote_mutex_init(id, &((lock)->remote)); \ + }) +#define remote_mutex_lock(lock) \ + do { \ + mutex_lock(&((lock)->local)); \ + _remote_mutex_lock(&((lock)->remote)); \ + } while (0) +#define remote_mutex_trylock(lock) \ + ({ \ + mutex_trylock(&((lock)->local)) \ + ? _remote_mutex_trylock(&((lock)->remote)) \ + ? 1 \ + : ({mutex_unlock(&((lock)->local)); 0; }) \ + : 0; \ + }) +#define remote_mutex_unlock(lock) \ + do { \ + _remote_mutex_unlock(&((lock)->remote)); \ + mutex_unlock(&((lock)->local)); \ + } while (0) + +#endif diff --git a/include/linux/rmt_storage_client.h b/include/linux/rmt_storage_client.h new file mode 100644 index 00000000000..f56819ab7cb --- /dev/null +++ b/include/linux/rmt_storage_client.h @@ -0,0 +1,83 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __RMT_STORAGE_SERVER_H +#define __RMT_STORAGE_SERVER_H + +#include +#include + +#define RMT_STORAGE_OPEN 0 +#define RMT_STORAGE_WRITE 1 +#define RMT_STORAGE_CLOSE 2 +#define RMT_STORAGE_SEND_USER_DATA 3 +#define RMT_STORAGE_READ 4 +#define RMT_STORAGE_NOOP 255 + +#define RMT_STORAGE_MAX_IOVEC_XFR_CNT 5 +#define MAX_NUM_CLIENTS 10 +#define MAX_RAMFS_TBL_ENTRIES 3 +#define RAMFS_BLOCK_SIZE 512 + + +enum { + RMT_STORAGE_NO_ERROR = 0, /* Success */ + RMT_STORAGE_ERROR_PARAM, /* Invalid parameters */ + RMT_STORAGE_ERROR_PIPE, /* RPC pipe failure */ + RMT_STORAGE_ERROR_UNINIT, /* Server is not initalized */ + RMT_STORAGE_ERROR_BUSY, /* Device busy */ + RMT_STORAGE_ERROR_DEVICE /* Remote storage device */ +} rmt_storage_status; + +struct rmt_storage_iovec_desc { + uint32_t sector_addr; + uint32_t data_phy_addr; + uint32_t num_sector; +}; + +#define MAX_PATH_NAME 32 +struct rmt_storage_event { + uint32_t id; /* Event ID */ + uint32_t sid; /* Storage ID */ + uint32_t handle; /* Client handle */ + char path[MAX_PATH_NAME]; + struct rmt_storage_iovec_desc xfer_desc[RMT_STORAGE_MAX_IOVEC_XFR_CNT]; + uint32_t xfer_cnt; + uint32_t usr_data; +}; + +struct rmt_storage_send_sts { + uint32_t err_code; + uint32_t data; + uint32_t handle; + uint32_t xfer_dir; +}; + +struct rmt_shrd_mem_param { + uint32_t sid; /* Storage ID */ + uint32_t start; /* Physical memory address */ + uint32_t size; /* Physical memory size */ + void *base; /* Virtual user-space memory address */ +}; + +#define RMT_STORAGE_IOCTL_MAGIC (0xC2) + +#define RMT_STORAGE_SHRD_MEM_PARAM \ + _IOWR(RMT_STORAGE_IOCTL_MAGIC, 0, struct rmt_shrd_mem_param) + +#define RMT_STORAGE_WAIT_FOR_REQ \ + _IOR(RMT_STORAGE_IOCTL_MAGIC, 1, struct rmt_storage_event) + +#define RMT_STORAGE_SEND_STATUS \ + _IOW(RMT_STORAGE_IOCTL_MAGIC, 2, struct rmt_storage_send_sts) +#endif diff --git a/include/linux/rtc-msm.h b/include/linux/rtc-msm.h new file mode 100644 index 00000000000..f8f6a165f12 --- /dev/null +++ b/include/linux/rtc-msm.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __RTC_MSM_H__ +#define __RTC_MSM_H__ + +/* + * This is the only function which updates the xtime structure. This + * function is supposed to be called only once during kernel initialization. + * But we need to call this function whenever we receive an RTC update + * from MODEM. + */ +int rtc_hctosys(void); + +extern void msm_pm_set_max_sleep_time(int64_t sleep_time_ns); +void msmrtc_updateatsuspend(struct timespec *ts); + +#ifdef CONFIG_PM +int64_t msm_timer_get_sclk_time(int64_t *period); +#endif /* CONFIG_PM */ + +#endif /* __RTC_MSM_H__ */ diff --git a/include/linux/rtc/rtc-pm8058.h b/include/linux/rtc/rtc-pm8058.h new file mode 100644 index 00000000000..0690e1a9e7a --- /dev/null +++ b/include/linux/rtc/rtc-pm8058.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __RTC_PM8058_H__ +#define __RTC_PM8058_H__ + +struct pm8058_rtc_pdata { + bool rtc_write_enable; +}; + +#endif /* __RTC_PM8058_H__ */ diff --git a/include/linux/slimbus/slimbus.h b/include/linux/slimbus/slimbus.h new file mode 100644 index 00000000000..fc76b77024b --- /dev/null +++ b/include/linux/slimbus/slimbus.h @@ -0,0 +1,934 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _LINUX_SLIMBUS_H +#define _LINUX_SLIMBUS_H +#include +#include +#include +#include + +/* Interfaces between SLIMbus manager drivers and SLIMbus infrastructure. */ + +extern struct bus_type slimbus_type; + +/* Standard values per SLIMbus spec needed by controllers and devices */ +#define SLIM_CL_PER_SUPERFRAME 6144 +#define SLIM_CL_PER_SUPERFRAME_DIV8 (SLIM_CL_PER_SUPERFRAME >> 3) +#define SLIM_MAX_CLK_GEAR 10 +#define SLIM_CL_PER_SL 4 +#define SLIM_SL_PER_SUPERFRAME (SLIM_CL_PER_SUPERFRAME >> 2) +#define SLIM_FRM_SLOTS_PER_SUPERFRAME 16 +#define SLIM_GDE_SLOTS_PER_SUPERFRAME 2 + +/* + * SLIMbus message types. Related to interpretation of message code. + * Values are defined in Table 32 (slimbus spec 1.01.01) + */ +#define SLIM_MSG_MT_CORE 0x0 +#define SLIM_MSG_MT_DEST_REFERRED_CLASS 0x1 +#define SLIM_MSG_MT_DEST_REFERRED_USER 0x2 +#define SLIM_MSG_MT_SRC_REFERRED_CLASS 0x5 +#define SLIM_MSG_MT_SRC_REFERRED_USER 0x6 + +/* + * SLIMbus core type Message Codes. + * Values are defined in Table 65 (slimbus spec 1.01.01) + */ +/* Device management messages */ +#define SLIM_MSG_MC_REPORT_PRESENT 0x1 +#define SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS 0x2 +#define SLIM_MSG_MC_RESET_DEVICE 0x4 +#define SLIM_MSG_MC_CHANGE_LOGICAL_ADDRESS 0x8 +#define SLIM_MSG_MC_CHANGE_ARBITRATION_PRIORITY 0x9 +#define SLIM_MSG_MC_REQUEST_SELF_ANNOUNCEMENT 0xC +#define SLIM_MSG_MC_REPORT_ABSENT 0xF + +/* Data channel management messages */ +#define SLIM_MSG_MC_CONNECT_SOURCE 0x10 +#define SLIM_MSG_MC_CONNECT_SINK 0x11 +#define SLIM_MSG_MC_DISCONNECT_PORT 0x14 +#define SLIM_MSG_MC_CHANGE_CONTENT 0x18 + +/* Information management messages */ +#define SLIM_MSG_MC_REQUEST_INFORMATION 0x20 +#define SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION 0x21 +#define SLIM_MSG_MC_REPLY_INFORMATION 0x24 +#define SLIM_MSG_MC_CLEAR_INFORMATION 0x28 +#define SLIM_MSG_MC_REPORT_INFORMATION 0x29 + +/* Reconfiguration messages */ +#define SLIM_MSG_MC_BEGIN_RECONFIGURATION 0x40 +#define SLIM_MSG_MC_NEXT_ACTIVE_FRAMER 0x44 +#define SLIM_MSG_MC_NEXT_SUBFRAME_MODE 0x45 +#define SLIM_MSG_MC_NEXT_CLOCK_GEAR 0x46 +#define SLIM_MSG_MC_NEXT_ROOT_FREQUENCY 0x47 +#define SLIM_MSG_MC_NEXT_PAUSE_CLOCK 0x4A +#define SLIM_MSG_MC_NEXT_RESET_BUS 0x4B +#define SLIM_MSG_MC_NEXT_SHUTDOWN_BUS 0x4C +#define SLIM_MSG_MC_NEXT_DEFINE_CHANNEL 0x50 +#define SLIM_MSG_MC_NEXT_DEFINE_CONTENT 0x51 +#define SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL 0x54 +#define SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL 0x55 +#define SLIM_MSG_MC_NEXT_REMOVE_CHANNEL 0x58 +#define SLIM_MSG_MC_RECONFIGURE_NOW 0x5F + +/* Value management messages */ +#define SLIM_MSG_MC_REQUEST_VALUE 0x60 +#define SLIM_MSG_MC_REQUEST_CHANGE_VALUE 0x61 +#define SLIM_MSG_MC_REPLY_VALUE 0x64 +#define SLIM_MSG_MC_CHANGE_VALUE 0x68 + +struct slim_controller; +struct slim_device; + +/* Destination type Values defined in Table 33 (slimbus spec 1.01.01) */ +#define SLIM_MSG_DEST_LOGICALADDR 0 +#define SLIM_MSG_DEST_ENUMADDR 1 +#define SLIM_MSG_DEST_BROADCAST 3 + +/* + * @start_offset: Specifies starting offset in information/value element map + * @num_bytes: Can be 1, 2, 3, 4, 6, 8, 12, 16 per spec. This ensures that the + * message will fit in the 40-byte message limit and the slicesize can be + * compatible with values in table 21 (slimbus spec 1.01.01) + * @comp: Completion to indicate end of message-transfer. Used if client wishes + * to use the API asynchronously. + */ +struct slim_ele_access { + u16 start_offset; + u8 num_bytes; + struct completion *comp; +}; + +/* + * struct slim_framer - Represents Slimbus framer. + * Every controller may have multiple framers. + * Manager is responsible for framer hand-over. + * @e_addr: 6 byte Elemental address of the framer. + * @rootfreq: Root Frequency at which the framer can run. This is maximum + * frequency (clock gear 10 per slimbus spec) at which the bus can operate. + * @superfreq: Superframes per root frequency. Every frame is 6144 cells (bits) + * per slimbus specification. + */ +struct slim_framer { + u8 e_addr[6]; + int rootfreq; + int superfreq; +}; +#define to_slim_framer(d) container_of(d, struct slim_framer, dev); + +/* + * struct slim_addrt: slimbus address used internally by the slimbus framework. + * @valid: If the device is still there or if the address can be reused. + * @eaddr: 6-bytes-long elemental address + */ +struct slim_addrt { + bool valid; + u8 eaddr[6]; +}; + +/* + * struct slim_msg_txn: Message to be sent by the controller. + * Linux framework uses this structure with drivers implementing controller. + * This structure has packet header, payload and buffer to be filled (if any) + * For the header information, refer to Table 34-36. + * @rl: Header field. remaining length. + * @mt: Header field. Message type. + * @mc: Header field. Message code for type mt. + * @dt: Header field. Destination type. + * @ec: Element size. Used for elemental access APIs. + * @len: Length of payload. (excludes ec) + * @tid: Transaction ID. Used for messages expecting response. + * (e.g. relevant for mc = SLIM_MSG_MC_REQUEST_INFORMATION) + * @la: Logical address of the device this message is going to. + * (Not used when destination type is broadcast.) + * @rbuf: Buffer to be populated by controller when response is received. + * @wbuf: Payload of the message. (e.g. channel number for DATA channel APIs) + * @comp: Completion structure. Used by controller to notify response. + * (Field is relevant when tid is used) + */ +struct slim_msg_txn { + u8 rl; + u8 mt; + u8 mc; + u8 dt; + u16 ec; + u8 len; + u8 tid; + u8 la; + u8 *rbuf; + const u8 *wbuf; + struct completion *comp; +}; + +/* Internal port state used by slimbus framework to manage data-ports */ +enum slim_port_state { + SLIM_P_FREE, + SLIM_P_UNCFG, + SLIM_P_CFG, +}; + +/* + * enum slim_port_req: Request port type by user through APIs to manage ports + * User can request default, half-duplex or port to be used in multi-channel + * configuration. Default indicates a simplex port. + */ +enum slim_port_req { + SLIM_REQ_DEFAULT, + SLIM_REQ_HALF_DUP, + SLIM_REQ_MULTI_CH, +}; + +/* + * enum slim_port_cfg: Port configuration parameters requested. + * User can request no configuration, packed data, or MSB aligned data port + */ +enum slim_port_cfg { + SLIM_CFG_NONE, + SLIM_CFG_PACKED, + SLIM_CFG_ALIGN_MSB, +}; + +/* enum slim_port_flow: Port flow type (inbound/outbound). */ +enum slim_port_flow { + SLIM_SRC, + SLIM_SINK, +}; + +/* enum slim_port_err: Port errors */ +enum slim_port_err { + SLIM_P_INPROGRESS, + SLIM_P_OVERFLOW, + SLIM_P_UNDERFLOW, + SLIM_P_DISCONNECT, + SLIM_P_NOT_OWNED, +}; + +/* + * struct slim_port: Internal structure used by framework to manage ports + * @err: Port error if any for this port. Refer to enum above. + * @state: Port state. Refer to enum above. + * @req: Port request for this port. + * @cfg: Port configuration for this port. + * @flow: Flow type of this port. + * @ch: Channel association of this port. + * @xcomp: Completion to indicate error, data transfer done event. + * @ctrl: Controller to which this port belongs to. This is useful to associate + * port with the SW since port hardware interrupts may only contain port + * information. + */ +struct slim_port { + enum slim_port_err err; + enum slim_port_state state; + enum slim_port_req req; + enum slim_port_cfg cfg; + enum slim_port_flow flow; + struct slim_ch *ch; + struct completion *xcomp; + struct slim_controller *ctrl; +}; + +/* + * enum slim_ch_state: Channel state of a channel. + * Channel transition happens from free-to-allocated-to-defined-to-pending- + * active-to-active. + * Once active, channel can be removed or suspended. Suspended channels are + * still scheduled, but data transfer doesn't happen. + * Removed channels are not deallocated until dealloc_ch API is used. + * Deallocation reset channel state back to free. + * Removed channels can be defined with different parameters. + */ +enum slim_ch_state { + SLIM_CH_FREE, + SLIM_CH_ALLOCATED, + SLIM_CH_DEFINED, + SLIM_CH_PENDING_ACTIVE, + SLIM_CH_ACTIVE, + SLIM_CH_SUSPENDED, + SLIM_CH_PENDING_REMOVAL, +}; + +/* + * enum slim_ch_proto: Channel protocol used by the channel. + * Hard Isochronous channel is not scheduled if current frequency doesn't allow + * the channel to be run without flow-control. + * Auto isochronous channel will be scheduled as hard-isochronous or push-pull + * depending on current bus frequency. + * Currently, Push-pull or async or extended channels are not supported. + * For more details, refer to slimbus spec + */ +enum slim_ch_proto { + SLIM_HARD_ISO, + SLIM_AUTO_ISO, + SLIM_PUSH, + SLIM_PULL, + SLIM_ASYNC_SMPLX, + SLIM_ASYNC_HALF_DUP, + SLIM_EXT_SMPLX, + SLIM_EXT_HALF_DUP, +}; + +/* + * enum slim_ch_rate: Most commonly used frequency rate families. + * Use 1HZ for push-pull transport. + * 4KHz and 11.025KHz are most commonly used in audio applications. + * Typically, slimbus runs at frequencies to support channels running at 4KHz + * and/or 11.025KHz isochronously. + */ +enum slim_ch_rate { + SLIM_RATE_1HZ, + SLIM_RATE_4000HZ, + SLIM_RATE_11025HZ, +}; + +/* + * enum slim_ch_coeff: Coefficient of a channel used internally by framework. + * Coefficient is applicable to channels running isochronously. + * Coefficient is calculated based on channel rate multiplier. + * (If rate multiplier is power of 2, it's coeff.1 channel. Otherwise it's + * coeff.3 channel. + */ +enum slim_ch_coeff { + SLIM_COEFF_1, + SLIM_COEFF_3, +}; + +/* + * enum slim_ch_control: Channel control. + * Activate will schedule channel and/or group of channels in the TDM frame. + * Suspend will keep the schedule but data-transfer won't happen. + * Remove will remove the channel/group from the TDM frame. + */ +enum slim_ch_control { + SLIM_CH_ACTIVATE, + SLIM_CH_SUSPEND, + SLIM_CH_REMOVE, +}; + +/* enum slim_ch_dataf: Data format per table 60 from slimbus spec 1.01.01 */ +enum slim_ch_dataf { + SLIM_CH_DATAF_NOT_DEFINED = 0, + SLIM_CH_DATAF_LPCM_AUDIO = 1, + SLIM_CH_DATAF_IEC61937_COMP_AUDIO = 2, + SLIM_CH_DATAF_PACKED_PDM_AUDIO = 3, +}; + +/* enum slim_ch_auxf: Auxiliary field format per table 59 from slimbus spec */ +enum slim_ch_auxf { + SLIM_CH_AUXF_NOT_APPLICABLE = 0, + SLIM_CH_AUXF_ZCUV_TUNNEL_IEC60958 = 1, + SLIM_CH_USER_DEFINED = 0xF, +}; + +/* + * struct slim_ch: Channel structure used externally by users of channel APIs. + * @prot: Desired slimbus protocol. + * @baser: Desired base rate. (Typical isochronous rates are: 4KHz, or 11.025KHz + * @dataf: Data format. + * @auxf: Auxiliary format. + * @ratem: Channel rate multiplier. (e.g. 48KHz channel will have 4KHz base rate + * and 12 as rate multiplier. + * @sampleszbits: Sample size in bits. + */ +struct slim_ch { + enum slim_ch_proto prot; + enum slim_ch_rate baser; + enum slim_ch_dataf dataf; + enum slim_ch_auxf auxf; + u32 ratem; + u32 sampleszbits; +}; + +/* + * struct slim_ich: Internal channel structure used by slimbus framework. + * @prop: structure passed by the client. + * @coeff: Coefficient of this channel. + * @state: Current state of the channel. + * @nextgrp: If this channel is part of group, next channel in this group. + * @prrate: Presence rate of this channel (per table 62 of the spec) + * @offset: Offset of this channel in the superframe. + * @newoff: Used during scheduling to hold temporary new offset until the offset + * is accepted/rejected by slimbus reconfiguration. + * @interval: Interval of this channel per superframe. + * @newintr: Used during scheduling to new interval temporarily. + * @seglen: Segment length of this channel. + * @rootexp: root exponent of this channel. Rate can be found using rootexp and + * coefficient. Used during scheduling. + * @srch: Source ports used by this channel. + * @nsrc: number of source ports used by this channel. + * @sinkh: Sink port used by this channel. + */ +struct slim_ich { + struct slim_ch prop; + enum slim_ch_coeff coeff; + enum slim_ch_state state; + u16 nextgrp; + u32 prrate; + u32 offset; + u32 newoff; + u32 interval; + u32 newintr; + u32 seglen; + u8 rootexp; + u32 *srch; + int nsrc; + u32 sinkh; +}; + +/* + * struct slim_sched: Framework uses this structure internally for scheduling. + * @chc3: Array of all active coeffient 3 channels. + * @num_cc3: Number of active coeffient 3 channels. + * @chc1: Array of all active coeffient 1 channels. + * @num_cc1: Number of active coeffient 1 channels. + * @subfrmcode: Current subframe-code used by TDM. This is decided based on + * requested message bandwidth and current channels scheduled. + * @usedslots: Slots used by all active channels. + * @msgsl: Slots used by message-bandwidth. + * @pending_msgsl: Used to store pending request of message bandwidth (in slots) + * until the scheduling is accepted by reconfiguration. + * @m_reconf: This mutex is held until current reconfiguration (data channel + * scheduling, message bandwidth reservation) is done. Message APIs can + * use the bus concurrently when this mutex is held since elemental access + * messages can be sent on the bus when reconfiguration is in progress. + * @slots: Used for debugging purposes to debug/verify current schedule in TDM. + */ +struct slim_sched { + struct slim_ich **chc3; + int num_cc3; + struct slim_ich **chc1; + int num_cc1; + u32 subfrmcode; + u32 usedslots; + u32 msgsl; + u32 pending_msgsl; + struct mutex m_reconf; + u8 *slots; +}; + +/* + * struct slim_controller: Represents manager for a SlimBUS + * (similar to 'master' on I2C) + * @dev: Device interface to this driver + * @nr: Board-specific number identifier for this controller/bus + * @list: Link with other slimbus controllers + * @name: Name for this controller + * @clkgear: Current clock gear in which this bus is running + * @a_framer: Active framer which is clocking the bus managed by this controller + * @m_ctrl: Mutex protecting controller data structures (ports, channels etc) + * @addrt: Logical address table + * @num_dev: Number of active slimbus slaves on this bus + * @txnt: Table of transactions having transaction ID + * @last_tid: size of the table txnt (can't grow beyond 256 since TID is 8-bits) + * @ports: Ports associated with this controller + * @nports: Number of ports supported by the controller + * @chans: Channels associated with this controller + * @nchans: Number of channels supported + * @sched: scheduler structure used by the controller + * @dev_released: completion used to signal when sysfs has released this + * controller so that it can be deleted during shutdown + * @xfer_msg: Transfer a message on this controller (this can be a broadcast + * control/status message like data channel setup, or a unicast message + * like value element read/write. + * @set_laddr: Setup logical address at laddr for the slave with elemental + * address e_addr. Drivers implementing controller will be expected to + * send unicast message to this device with its logical address. + * @config_port: Configure a port and make it ready for data transfer. This is + * called by framework after connect_port message is sent successfully. + * @framer_handover: If this controller has multiple framers, this API will + * be called to switch between framers if controller desires to change + * the active framer. + * @port_xfer: Called to schedule a transfer on port pn. iobuf is physical + * address and the buffer may have to be DMA friendly since data channels + * will be using data from this buffers without SW intervention. + * @port_xfer_status: Called by framework when client calls get_xfer_status + * API. Returns how much buffer is actually processed and the port + * errors (e.g. overflow/underflow) if any. + */ +struct slim_controller { + struct device dev; + unsigned int nr; + struct list_head list; + char name[SLIMBUS_NAME_SIZE]; + int clkgear; + struct slim_framer *a_framer; + struct mutex m_ctrl; + struct slim_addrt *addrt; + u8 num_dev; + struct slim_msg_txn **txnt; + u8 last_tid; + struct slim_port *ports; + int nports; + struct slim_ich *chans; + int nchans; + struct slim_sched sched; + struct completion dev_released; + int (*xfer_msg)(struct slim_controller *ctrl, + struct slim_msg_txn *txn); + int (*set_laddr)(struct slim_controller *ctrl, + const u8 *ea, u8 elen, u8 laddr); + int (*config_port)(struct slim_controller *ctrl, + u8 port); + int (*framer_handover)(struct slim_controller *ctrl, + struct slim_framer *new_framer); + int (*port_xfer)(struct slim_controller *ctrl, + u8 pn, u8 *iobuf, u32 len, + struct completion *comp); + enum slim_port_err (*port_xfer_status)(struct slim_controller *ctr, + u8 pn, u8 **done_buf, u32 *done_len); +}; +#define to_slim_controller(d) container_of(d, struct slim_controller, dev) + +/* + * struct slim_driver: Manage Slimbus generic/slave device driver + * @probe: Binds this driver to a slimbus device. + * @remove: Unbinds this driver from the slimbus device. + * @shutdown: Standard shutdown callback used during powerdown/halt. + * @suspend: Standard suspend callback used during system suspend + * @resume: Standard resume callback used during system resume + * @driver: Slimbus device drivers should initialize name and owner field of + * this structure + * @id_table: List of slimbus devices supported by this driver + */ +struct slim_driver { + int (*probe)(struct slim_device *sldev); + int (*remove)(struct slim_device *sldev); + void (*shutdown)(struct slim_device *sldev); + int (*suspend)(struct slim_device *sldev, + pm_message_t pmesg); + int (*resume)(struct slim_device *sldev); + + struct device_driver driver; + const struct slim_device_id *id_table; +}; +#define to_slim_driver(d) container_of(d, struct slim_driver, driver) + +/* + * struct slim_pending_ch: List of pending channels used by framework. + * @chan: Channel number + * @pending: list of channels + */ +struct slim_pending_ch { + u8 chan; + struct list_head pending; +}; + +/* + * Client/device handle (struct slim_device): + * ------------------------------------------ + * This is the client/device handle returned when a slimbus + * device is registered with a controller. This structure can be provided + * during register_board_info, or can be allocated using slim_add_device API. + * Pointer to this structure is used by client-driver as a handle. + * @dev: Driver model representation of the device. + * @name: Name of driver to use with this device. + * @e_addr: 6-byte elemental address of this device. + * @driver: Device's driver. Pointer to access routines. + * @ctrl: Slimbus controller managing the bus hosting this device. + * @laddr: 1-byte Logical address of this device. + * @mark_define: List of channels pending definition/activation. + * @mark_suspend: List of channels pending suspend. + * @mark_removal: List of channels pending removal. + * @sldev_reconf: Mutex to protect the pending data-channel lists. + * @pending_msgsl: Message bandwidth reservation request by this client in + * slots that's pending reconfiguration. + * @cur_msgsl: Message bandwidth reserved by this client in slots. + * These 3 lists are managed by framework. Lists are populated when client + * calls channel control API without reconfig-flag set and the lists are + * emptied when the reconfiguration is done by this client. + */ +struct slim_device { + struct device dev; + const char *name; + u8 e_addr[6]; + struct slim_driver *driver; + struct slim_controller *ctrl; + u8 laddr; + struct list_head mark_define; + struct list_head mark_suspend; + struct list_head mark_removal; + struct mutex sldev_reconf; + u32 pending_msgsl; + u32 cur_msgsl; +}; +#define to_slim_device(d) container_of(d, struct slim_device, dev) + +/* + * struct slim_boardinfo: Declare board info for Slimbus device bringup. + * @bus_num: Controller number (bus) on which this device will sit. + * @slim_slave: Device to be registered with slimbus. + */ +struct slim_boardinfo { + int bus_num; + struct slim_device *slim_slave; +}; + +/* + * slim_get_logical_addr: Return the logical address of a slimbus device. + * @sb: client handle requesting the adddress. + * @e_addr: Elemental address of the device. + * @e_len: Length of e_addr + * @laddr: output buffer to store the address + * context: can sleep + * -EINVAL is returned in case of invalid parameters, and -ENXIO is returned if + * the device with this elemental address is not found. + */ + +extern int slim_get_logical_addr(struct slim_device *sb, const u8 *e_addr, + u8 e_len, u8 *laddr); + + +/* Message APIs Unicast message APIs used by slimbus slave drivers */ + +/* + * Message API access routines. + * @sb: client handle requesting elemental message reads, writes. + * @msg: Input structure for start-offset, number of bytes to read. + * @rbuf: data buffer to be filled with values read. + * @len: data buffer size + * @wbuf: data buffer containing value/information to be written + * context: can sleep + * Returns: + * -EINVAL: Invalid parameters + * -ETIMEDOUT: If controller could not complete the request. This may happen if + * the bus lines are not clocked, controller is not powered-on, slave with + * given address is not enumerated/responding. + */ +extern int slim_request_val_element(struct slim_device *sb, + struct slim_ele_access *msg, u8 *buf, + u8 len); +extern int slim_request_inf_element(struct slim_device *sb, + struct slim_ele_access *msg, u8 *buf, + u8 len); +extern int slim_change_val_element(struct slim_device *sb, + struct slim_ele_access *msg, + const u8 *buf, u8 len); +extern int slim_clear_inf_element(struct slim_device *sb, + struct slim_ele_access *msg, u8 *buf, + u8 len); +extern int slim_request_change_val_element(struct slim_device *sb, + struct slim_ele_access *msg, u8 *rbuf, + const u8 *wbuf, u8 len); +extern int slim_request_clear_inf_element(struct slim_device *sb, + struct slim_ele_access *msg, u8 *rbuf, + const u8 *wbuf, u8 len); + +/* + * Broadcast message API: + * call this API directly with sbdev = NULL. + * For broadcast reads, make sure that buffers are big-enough to incorporate + * replies from all logical addresses. + * All controllers may not support broadcast + */ +extern int slim_xfer_msg(struct slim_controller *ctrl, + struct slim_device *sbdev, struct slim_ele_access *msg, + u8 mc, u8 *rbuf, const u8 *wbuf, u8 len); +/* end of message apis */ + +/* Port management for manager device APIs */ + +/* + * slim_alloc_mgrports: Allocate port on manager side. + * @sb: device/client handle. + * @req: Port request type. + * @nports: Number of ports requested + * @rh: output buffer to store the port handles + * @hsz: size of buffer storing handles + * context: can sleep + * This port will be typically used by SW. e.g. client driver wants to receive + * some data from audio codec HW using a data channel. + * Port allocated using this API will be used to receive the data. + * If half-duplex ports are requested, two adjacent ports are allocated for + * 1 half-duplex port. So the handle-buffer size should be twice the number + * of half-duplex ports to be allocated. + * -EDQUOT is returned if all ports are in use. + */ +extern int slim_alloc_mgrports(struct slim_device *sb, enum slim_port_req req, + int nports, u32 *rh, int hsz); + +/* Deallocate the port(s) allocated using the API above */ +extern int slim_dealloc_mgrports(struct slim_device *sb, u32 *hdl, int hsz); + +/* + * slim_port_xfer: Schedule buffer to be transferred/received using port-handle. + * @sb: client handle + * @ph: port-handle + * @iobuf: buffer to be transferred or populated + * @len: buffer size. + * @comp: completion signal to indicate transfer done or error. + * context: can sleep + * Returns number of bytes transferred/received if used synchronously. + * Will return 0 if used asynchronously. + * Client will call slim_port_get_xfer_status to get error and/or number of + * bytes transferred if used asynchronously. + */ +extern int slim_port_xfer(struct slim_device *sb, u32 ph, u8 *iobuf, u32 len, + struct completion *comp); + +/* + * slim_port_get_xfer_status: Poll for port transfers, or get transfer status + * after completion is done. + * @sb: client handle + * @ph: port-handle + * @done_buf: return pointer (iobuf from slim_port_xfer) which is processed. + * @done_len: Number of bytes transferred. + * This can be called when port_xfer complition is signalled. + * The API will return port transfer error (underflow/overflow/disconnect) + * and/or done_len will reflect number of bytes transferred. Note that + * done_len may be valid even if port error (overflow/underflow) has happened. + * e.g. If the transfer was scheduled with a few bytes to be transferred and + * client has not supplied more data to be transferred, done_len will indicate + * number of bytes transferred with underflow error. To avoid frequent underflow + * errors, multiple transfers can be queued (e.g. ping-pong buffers) so that + * channel has data to be transferred even if client is not ready to transfer + * data all the time. done_buf will indicate address of the last buffer + * processed from the multiple transfers. + */ +extern enum slim_port_err slim_port_get_xfer_status(struct slim_device *sb, + u32 ph, u8 **done_buf, u32 *done_len); + +/* + * slim_connect_ports: Connect port(s) to channel. + * @sb: client handle + * @srch: source handles to be connected to this channel + * @nrsc: number of source ports + * @sinkh: sink handle to be connected to this channel + * @chanh: Channel with which the ports need to be associated with. + * Per slimbus specification, a channel may have multiple source-ports and 1 + * sink port.Channel specified in chanh needs to be allocated first. + */ +extern int slim_connect_ports(struct slim_device *sb, u32 *srch, int nsrc, + u32 sinkh, u16 chanh); + +/* + * slim_disconnect_ports: Disconnect port(s) from channel + * @sb: client handle + * @ph: ports to be disconnected + * @nph: number of ports. + * Disconnects ports from a channel. + */ +extern int slim_disconnect_ports(struct slim_device *sb, u32 *ph, int nph); + +/* + * slim_get_slaveport: Get slave port handle + * @la: slave device logical address. + * @idx: port index at slave + * @rh: return handle + * @flw: Flow type (source or destination) + * This API only returns a slave port's representation as expected by slimbus + * driver. This port is not managed by the slimbus driver. Caller is expected + * to have visibility of this port since it's a device-port. + */ +extern int slim_get_slaveport(u8 la, int idx, u32 *rh, enum slim_port_flow flw); + + +/* Channel functions. */ + +/* + * slim_alloc_ch: Allocate a slimbus channel and return its handle. + * @sb: client handle. + * @chanh: return channel handle + * Slimbus channels are limited to 256 per specification. LSB of the handle + * indicates channel number and MSB of the handle is used by the slimbus + * framework. -EXFULL is returned if all channels are in use. + * Although slimbus specification supports 256 channels, a controller may not + * support that many channels. + */ +extern int slim_alloc_ch(struct slim_device *sb, u16 *chanh); + +/* + * slim_dealloc_ch: Deallocate channel allocated using the API above + * -EISCONN is returned if the channel is tried to be deallocated without + * being removed first. + */ +extern int slim_dealloc_ch(struct slim_device *sb, u16 chanh); + + +/* + * slim_define_ch: Define a channel.This API defines channel parameters for a + * given channel. + * @sb: client handle. + * @prop: slim_ch structure with channel parameters desired to be used. + * @chanh: list of channels to be defined. + * @nchan: number of channels in a group (1 if grp is false) + * @grp: Are the channels grouped + * @grph: return group handle if grouping of channels is desired. + * Channels can be grouped if multiple channels use same parameters + * (e.g. 5.1 audio has 6 channels with same parameters. They will all be + * grouped and given 1 handle for simplicity and avoid repeatedly calling + * the API) + * -EISCONN is returned if the channel is already connected. -EBUSY is + * returned if the channel is already allocated to some other client. + */ +extern int slim_define_ch(struct slim_device *sb, struct slim_ch *prop, + u16 *chanh, u8 nchan, bool grp, u16 *grph); + +/* + * slim_control_ch: Channel control API. + * @sb: client handle + * @grpchanh: group or channel handle to be controlled + * @chctrl: Control command (activate/suspend/remove) + * @commit: flag to indicate whether the control should take effect right-away. + * This API activates, removes or suspends a channel (or group of channels) + * grpchanh indicates the channel or group handle (returned by the define_ch + * API). Reconfiguration may be time-consuming since it can change all other + * active channel allocations on the bus, change in clock gear used by the + * slimbus, and change in the control space width used for messaging. + * commit makes sure that multiple channels can be activated/deactivated before + * reconfiguration is started. + * -EXFULL is returned if there is no space in TDM to reserve the bandwidth. + * -EISCONN/-ENOTCONN is returned if the channel is already connected or not + * yet defined. + */ +extern int slim_control_ch(struct slim_device *sb, u16 grpchanh, + enum slim_ch_control chctrl, bool commit); + +/* + * slim_get_ch_state: Channel state. + * This API returns the channel's state (active, suspended, inactive etc) + */ +extern enum slim_ch_state slim_get_ch_state(struct slim_device *sb, + u16 chanh); + +/* + * slim_reservemsg_bw: Request to reserve bandwidth for messages. + * @sb: client handle + * @bw_bps: message bandwidth in bits per second to be requested + * @commit: indicates whether the reconfiguration needs to be acted upon. + * This API call can be grouped with slim_control_ch API call with only one of + * the APIs specifying the commit flag to avoid reconfiguration being called too + * frequently. -EXFULL is returned if there is no space in TDM to reserve the + * bandwidth. -EBUSY is returned if reconfiguration is requested, but a request + * is already in progress. + */ +extern int slim_reservemsg_bw(struct slim_device *sb, u32 bw_bps, bool commit); + +/* + * slim_reconfigure_now: Request reconfiguration now. + * @sb: client handle + * This API does what commit flag in other scheduling APIs do. + * -EXFULL is returned if there is no space in TDM to reserve the + * bandwidth. -EBUSY is returned if reconfiguration request is already in + * progress. + */ +extern int slim_reconfigure_now(struct slim_device *sb); + +/* + * slim_driver_register: Client driver registration with slimbus + * @drv:Client driver to be associated with client-device. + * This API will register the client driver with the slimbus + * It is called from the driver's module-init function. + */ +extern int slim_driver_register(struct slim_driver *drv); + +/* + * slim_add_numbered_controller: Controller bring-up. + * @ctrl: Controller to be registered. + * A controller is registered with the framework using this API. ctrl->nr is the + * desired number with which slimbus framework registers the controller. + * Function will return -EBUSY if the number is in use. + */ +extern int slim_add_numbered_controller(struct slim_controller *ctrl); + +/* + * slim_del_controller: Controller tear-down. + * Controller added with the above API is teared down using this API. + */ +extern int slim_del_controller(struct slim_controller *ctrl); + +/* + * slim_add_device: Add a new device without register board info. + * @ctrl: Controller to which this device is to be added to. + * Called when device doesn't have an explicit client-driver to be probed, or + * the client-driver is a module installed dynamically. + */ +extern int slim_add_device(struct slim_controller *ctrl, + struct slim_device *sbdev); + +/* slim_remove_device: Remove the effect of slim_add_device() */ +extern void slim_remove_device(struct slim_device *sbdev); + +/* + * slim_assign_laddr: Assign logical address to a device enumerated. + * @ctrl: Controller with which device is enumerated. + * @e_addr: 6-byte elemental address of the device. + * @e_len: buffer length for e_addr + * @laddr: Return logical address. + * Called by controller in response to REPORT_PRESENT. Framework will assign + * a logical address to this enumeration address. + * Function returns -EXFULL to indicate that all logical addresses are already + * taken. + */ +extern int slim_assign_laddr(struct slim_controller *ctrl, const u8 *e_addr, + u8 e_len, u8 *laddr); + +/* + * slim_msg_response: Deliver Message response received from a device to the + * framework. + * @ctrl: Controller handle + * @reply: Reply received from the device + * @len: Length of the reply + * @tid: Transaction ID received with which framework can associate reply. + * Called by controller to inform framework about the response received. + * This helps in making the API asynchronous, and controller-driver doesn't need + * to manage 1 more table other than the one managed by framework mapping TID + * with buffers + */ +extern void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, + u8 len); + +/* + * slim_busnum_to_ctrl: Map bus number to controller + * @busnum: Bus number + * Returns controller representing this bus number + */ +extern struct slim_controller *slim_busnum_to_ctrl(u32 busnum); + +/* + * slim_register_board_info: Board-initialization routine. + * @info: List of all devices on all controllers present on the board. + * @n: number of entries. + * API enumerates respective devices on corresponding controller. + * Called from board-init function. + */ +#ifdef CONFIG_SLIMBUS +extern int slim_register_board_info(struct slim_boardinfo const *info, + unsigned n); +#else +int slim_register_board_info(struct slim_boardinfo const *info, + unsigned n) +{ + return 0; +} +#endif + +static inline void *slim_get_ctrldata(const struct slim_controller *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void slim_set_ctrldata(struct slim_controller *dev, void *data) +{ + dev_set_drvdata(&dev->dev, data); +} + +static inline void *slim_get_devicedata(const struct slim_device *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void slim_set_clientdata(struct slim_device *dev, void *data) +{ + dev_set_drvdata(&dev->dev, data); +} +#endif /* _LINUX_SLIMBUS_H */ diff --git a/include/linux/smsc911x.h b/include/linux/smsc911x.h index 4dde70e7482..0493fbdf776 100644 --- a/include/linux/smsc911x.h +++ b/include/linux/smsc911x.h @@ -24,7 +24,14 @@ #include /* platform_device configuration data, should be assigned to - * the platform_device's dev.platform_data */ + * the platform_device's dev.platform_data + * Provides 2 GPIO-related fields + * reset_gpio to map the ETHERNET_RESET GPIO pin + * has_reset_gpio - to indicate if the GPIO is being set(1) or not(0) + * and remain compatible with architectures not using GPIOs + * Default would be zero if its not being assigned any value. + * Both values would need to set in the appropriate board file + */ struct smsc911x_platform_config { unsigned int irq_polarity; unsigned int irq_type; @@ -32,6 +39,8 @@ struct smsc911x_platform_config { unsigned int shift; phy_interface_t phy_interface; unsigned char mac[6]; + unsigned char has_reset_gpio; + unsigned int reset_gpio; }; /* Constants for platform_device irq polarity configuration */ diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 11684d9e6bd..dc29f941cff 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -153,6 +153,7 @@ enum KERN_MAX_LOCK_DEPTH=74, /* int: rtmutex's maximum lock depth */ KERN_NMI_WATCHDOG=75, /* int: enable/disable nmi watchdog */ KERN_PANIC_ON_NMI=76, /* int: whether we will panic on an unrecovered */ + KERN_BOOT_REASON = 77, /* int: identify reason system was booted */ }; diff --git a/include/linux/thermal.h b/include/linux/thermal.h index d3ec89fb412..11b410c1814 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -37,11 +37,19 @@ enum thermal_device_mode { THERMAL_DEVICE_ENABLED, }; +enum thermal_trip_activation_mode { + THERMAL_TRIP_ACTIVATION_DISABLED = 0, + THERMAL_TRIP_ACTIVATION_ENABLED, +}; + enum thermal_trip_type { THERMAL_TRIP_ACTIVE = 0, THERMAL_TRIP_PASSIVE, THERMAL_TRIP_HOT, THERMAL_TRIP_CRITICAL, + THERMAL_TRIP_CONFIGURABLE_HI, + THERMAL_TRIP_CONFIGURABLE_LOW, + THERMAL_TRIP_CRITICAL_LOW, }; struct thermal_zone_device_ops { @@ -56,8 +64,12 @@ struct thermal_zone_device_ops { enum thermal_device_mode); int (*get_trip_type) (struct thermal_zone_device *, int, enum thermal_trip_type *); + int (*activate_trip_type) (struct thermal_zone_device *, int, + enum thermal_trip_activation_mode); int (*get_trip_temp) (struct thermal_zone_device *, int, unsigned long *); + int (*set_trip_temp) (struct thermal_zone_device *, int, + long); int (*get_crit_temp) (struct thermal_zone_device *, unsigned long *); int (*notify) (struct thermal_zone_device *, int, enum thermal_trip_type); diff --git a/include/linux/tsif_api.h b/include/linux/tsif_api.h new file mode 100644 index 00000000000..3142b7b48cb --- /dev/null +++ b/include/linux/tsif_api.h @@ -0,0 +1,207 @@ +/** + * TSIF driver + * + * Kernel API + * + * Copyright (c) 2009-2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef _TSIF_API_H_ +#define _TSIF_API_H_ +/** + * Theory of operation + * + * TSIF driver maintains internal cyclic data buffer where + * received TSIF packets are stored. Size of buffer, in packets, + * and its address, may be obtained by tsif_get_info(). + * + * TSIF stream delivered to the client that should register with + * TSIF driver using tsif_attach() + * + * Producer-consumer pattern used. TSIF driver act as producer, + * writing data to the buffer; clientis consumer. + * 2 indexes maintained by the TSIF driver: + * - wi (write index) points to the next item to be written by + * TSIF + * - ri (read index) points to the next item available for read + * by the client. + * Write index advanced by the TSIF driver when new data + * received; + * Read index advanced only when client tell so to the TSIF + * driver by tsif_reclaim_packets() + * + * Consumer may directly access data TSIF buffer between ri and + * wi. When ri==wi, buffer is empty. + * + * TSIF driver notifies client about any change by calling + * notify function. Client should use tsif_get_state() to query + * new state. + */ + +/* bytes in TSIF packet. not customizable */ +#define TSIF_PKT_SIZE (192) + +/** + * tsif_pkt_status - get TSIF packet status + * + * @pkt: TSIF packet location + * + * Return last DWORD of packet, containing status. + * Status dword consists of: + * - 3 low bytes TTS + * - 1 byte (last byte of packet) with status bits + */ +static inline u32 tsif_pkt_status(void *pkt) +{ + u32 *x = pkt; + return x[TSIF_PKT_SIZE / sizeof(u32) - 1]; +} + +/** + * Status dword parts for status returned by @tsif_pkt_status + */ +#define TSIF_STATUS_TTS(x) ((x) & 0xffffff) +#define TSIF_STATUS_VALID(x) ((x) & (1<<24)) +#define TSIF_STATUS_FIRST(x) ((x) & (1<<25)) +#define TSIF_STATUS_OVFLW(x) ((x) & (1<<26)) +#define TSIF_STATUS_ERROR(x) ((x) & (1<<27)) +#define TSIF_STATUS_NULL(x) ((x) & (1<<28)) +#define TSIF_STATUS_TIMEO(x) ((x) & (1<<30)) + +/** + * enum tsif_state - TSIF device state + * @tsif_state_stopped: Idle state, data acquisition not running + * @tsif_state_running: Data acquisition in progress + * @tsif_state_flushing: Device is flushing + * + * State transition diagram: + * + * init -> tsif_state_stopped + * + * tsif_state_stopped: + * - open -> tsif_state_running + * + * tsif_state_running: + * - close -> tsif_state_flushing + * + * tsif_state_flushing: + * - flushed -> tsif_state_stopped + */ +enum tsif_state { + tsif_state_stopped = 0, + tsif_state_running = 1, + tsif_state_flushing = 2, + tsif_state_error = 3, +}; + +/** + * tsif_attach - Attach to the device. + * @id: TSIF device ID, used to identify TSIF instance. + * @notify: client callback, called when + * any client visible TSIF state changed. + * This includes new data available and device state change + * @data: client data, will be passed to @notify + * + * Return TSIF cookie or error code + * + * Should be called prior to any other tsif_XXX function. + */ +void *tsif_attach(int id, void (*notify)(void *client_data), void *client_data); +/** + * tsif_detach - detach from device + * @cookie: TSIF cookie previously obtained with tsif_attach() + */ +void tsif_detach(void *cookie); +/** + * tsif_get_info - get data buffer info + * @cookie: TSIF cookie previously obtained with tsif_attach() + * @pdata: if not NULL, TSIF data buffer will be stored there + * @psize: if not NULL, TSIF data buffer size, in packets, + * will be stored there + * + * Data buffer information should be queried after each tsif_start() before + * using data; since data buffer will be re-allocated on tsif_start() + */ +void tsif_get_info(void *cookie, void **pdata, int *psize); +/** + * tsif_set_mode - set TSIF mode + * @cookie: TSIF cookie previously obtained with tsif_attach() + * @mode: desired mode of operation + * + * Return error code + * + * Mode may be changed only when TSIF device is stopped. + */ +int tsif_set_mode(void *cookie, int mode); +/** + * tsif_set_time_limit - set TSIF time limit + * @cookie: TSIF cookie previously obtained with tsif_attach() + * @value: desired time limit, 0 to disable + * + * Return error code + * + * Time limit may be changed only when TSIF device is stopped. + */ +int tsif_set_time_limit(void *cookie, u32 value); +/** + * tsif_set_buf_config - configure data buffer + * + * @cookie: TSIF cookie previously obtained with tsif_attach() + * @pkts_in_chunk: requested number of packets per chunk + * @chunks_in_buf: requested number of chunks in buffer + * + * Return error code + * + * Parameter selection criteria: + * + * - @pkts_in_chunk defines size of DMA transfer and, in turn, time between + * consecutive DMA transfers. Increase @pkts_in_chunk reduces chance for + * hardware overflow. If TSIF stats reports overflows, increase it. + * + * - @chunks_in_buf * @pkts_in_chunk defines total buffer size. Increase this + * parameter if client latency is large and TSIF reports "soft drop" in its + * stats + */ +int tsif_set_buf_config(void *cookie, u32 pkts_in_chunk, u32 chunks_in_buf); +/** + * tsif_get_state - query current data buffer information + * @cookie: TSIF cookie previously obtained with tsif_attach() + * @ri: if not NULL, read index will be stored here + * @wi: if not NULL, write index will be stored here + * @state: if not NULL, state will be stored here + */ +void tsif_get_state(void *cookie, int *ri, int *wi, enum tsif_state *state); +/** + * tsif_start - start data acquisition + * @cookie: TSIF cookie previously obtained with tsif_attach() + * + * Return error code + */ +int tsif_start(void *cookie); +/** + * tsif_stop - stop data acquisition + * @cookie: TSIF cookie previously obtained with tsif_attach() + * + * Data buffer allocated during this function call; thus client should + * query data buffer info using tsif_get_info() and reset its data pointers. + */ +void tsif_stop(void *cookie); +/** + * tsif_reclaim_packets - inform that buffer space may be reclaimed + * @cookie: TSIF cookie previously obtained with tsif_attach() + * @ri: new value for read index + */ +void tsif_reclaim_packets(void *cookie, int ri); + +#endif /* _TSIF_API_H_ */ + diff --git a/include/linux/tzcom.h b/include/linux/tzcom.h new file mode 100644 index 00000000000..1a3c7746430 --- /dev/null +++ b/include/linux/tzcom.h @@ -0,0 +1,101 @@ +/* Qualcomm TrustZone communicator API */ + +#ifndef __TZCOM_H_ +#define __TZCOM_H_ + +#include +#include + +/** + * struct tzcom_register_svc_op_req - for register service ioctl request + * @svc_id - service id (shared between userspace and TZ) + * @cmd_id_low - low number in cmd_id range (shared between userspace and TZ) + * @cmd_id_high - high number in cmd_id range (shared between userspace and TZ) + * @instance_id - unique id for the given service generated by tzcom driver + */ +struct tzcom_register_svc_op_req { + uint32_t svc_id; /* in */ + uint32_t cmd_id_low; /* in */ + uint32_t cmd_id_high; /* in */ + uint32_t instance_id; /* out */ +}; + +/** + * struct tzcom_unregister_svc_op_req - for unregister service ioctl request + * @svc_id - service id to unregister (provided in register_service request) + * @instance_id - instance id generated in register service request + */ +struct tzcom_unregister_svc_op_req { + uint32_t svc_id; /* in */ + uint32_t instance_id; /* in */ +}; + +/** + * struct tzcom_next_cmd_op_req - for read next command ioctl request + * @svc_id - has to be a registered svc_id (see @tzcom_register_svc_op_req) + * @instance_id - unique id for the given service (see @tzcom_register_svc_op_req) + * @cmd_id - command to execute on the given service, received from TZ + * @req_len - request buffer length, received from TZ + * @req - request buffer, received from TZ + */ +struct tzcom_next_cmd_op_req { + uint32_t svc_id; /* in */ + uint32_t instance_id; /* in */ + uint32_t cmd_id; /* out */ + unsigned int req_len; /* in/out */ + void *req_buf; /* in/out */ +}; + +/** + * struct tzcom_send_cmd_op_req - for send command ioctl request + * @cmd_id - command to execute on TZBSP side + * @cmd_len - command buffer length + * @cmd_buf - command buffer + * @resp_len - response buffer length + * @resp_buf - response buffer + */ +struct tzcom_send_cmd_op_req { + uint32_t cmd_id; /* in */ + unsigned int cmd_len; /* in */ + void *cmd_buf; /* in */ + unsigned int resp_len; /* in/out */ + void *resp_buf; /* in/out */ +}; + +/** + * struct tzcom_cont_cmd_op_req - for continue command ioctl request. used + * as a trigger from HLOS service to notify TZCOM that it's done with its + * operation and provide the response for TZCOM can continue the incomplete + * command execution + * @cmd_id - Command to continue filled in by tzcom as tzcom knows about the + * last incomplete command. + * @instance_id - Instance id of the svc + * @resp_len - Length of the response + * @resp_buf - Response buffer where the response of the cmd should go. + */ +struct tzcom_cont_cmd_op_req { + uint32_t cmd_id; /* out */ + uint32_t instance_id; /* in */ + unsigned int resp_len; /* in */ + void *resp_buf; /* in */ +}; + +#define TZCOM_IOC_MAGIC 0x97 + +/* For HLOS service */ +#define TZCOM_IOCTL_REGISTER_SERVICE_REQ \ + _IOWR(TZCOM_IOC_MAGIC, 1, struct tzcom_register_svc_op_req) +/* For HLOS service */ +#define TZCOM_IOCTL_UNREGISTER_SERVICE_REQ \ + _IOWR(TZCOM_IOC_MAGIC, 2, struct tzcom_unregister_svc_op_req) +/* For TZ service */ +#define TZCOM_IOCTL_SEND_CMD_REQ \ + _IOWR(TZCOM_IOC_MAGIC, 3, struct tzcom_send_cmd_op_req) +/* For HLOS service */ +#define TZCOM_IOCTL_READ_NEXT_CMD_REQ \ + _IOWR(TZCOM_IOC_MAGIC, 4, struct tzcom_next_cmd_op_req) +/* For TZ service */ +#define TZCOM_IOCTL_CONTINUE_CMD_REQ \ + _IOWR(TZCOM_IOC_MAGIC, 5, struct tzcom_cont_cmd_op_req) + +#endif /* __TZCOM_H_ */ diff --git a/include/linux/usb.h b/include/linux/usb.h index 73c7df48960..583ceb890ca 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -321,6 +321,8 @@ struct usb_bus { u8 otg_port; /* 0, or number of OTG/HNP port */ unsigned is_b_host:1; /* true during some HNP roleswitches */ unsigned b_hnp_enable:1; /* OTG: did A-Host enable HNP? */ + unsigned hnp_support:1; /* OTG: HNP is supported on OTG port */ + struct delayed_work hnp_polling;/* OTG: HNP polling work */ unsigned sg_tablesize; /* 0 or largest number of sg list entries */ int devnum_next; /* Next open device number in @@ -362,6 +364,16 @@ struct usb_bus { * limit. Because the arrays need to add a bit for hub status data, we * do 31, so plus one evens out to four bytes. */ + +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) +#define USB_OTG_SUSPEND 0x1 +#define USB_OTG_ENUMERATE 0x2 +#define USB_OTG_DISCONNECT 0x4 +#define USB_OTG_RESUME 0x8 +#define USB_OTG_REMOTEWAKEUP 0x10 +#define USB_OTG_WAKEUP_ALL 0x20 +#endif + #define USB_MAXCHILDREN (31) struct usb_tt; @@ -475,6 +487,18 @@ struct usb_device { struct dentry *usbfs_dentry; #endif +#if defined(CONFIG_USB_PEHCI_HCD) || defined(CONFIG_USB_PEHCI_HCD_MODULE) + /*otg add ons */ + u8 otgdevice; /*device is otg type */ + + /*otg states from otg driver, suspend, enumerate, disconnect */ + u8 otgstate; + void *otgpriv; + void (*otg_notif) (void *otg_priv, + unsigned long notif, unsigned long data); + void *hcd_priv; + void (*hcd_suspend) (void *hcd_priv); +#endif int maxchild; struct usb_device *children[USB_MAXCHILDREN]; @@ -1584,8 +1608,15 @@ usb_maxpacket(struct usb_device *udev, int pipe, int is_out) #define USB_DEVICE_REMOVE 0x0002 #define USB_BUS_ADD 0x0003 #define USB_BUS_REMOVE 0x0004 +#define USB_DEVICE_CONFIG 0x0005 + +#ifdef CONFIG_USB extern void usb_register_notify(struct notifier_block *nb); extern void usb_unregister_notify(struct notifier_block *nb); +#else +static inline void usb_register_notify(struct notifier_block *nb) {} +static inline void usb_unregister_notify(struct notifier_block *nb) {} +#endif #ifdef DEBUG #define dbg(format, arg...) \ diff --git a/include/linux/usb/android.h b/include/linux/usb/android.h new file mode 100644 index 00000000000..9d7e4a84802 --- /dev/null +++ b/include/linux/usb/android.h @@ -0,0 +1,24 @@ +/* + * Platform data for Android USB + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __LINUX_USB_ANDROID_H +#define __LINUX_USB_ANDROID_H + +struct android_usb_platform_data { + int (*update_pid_and_serial_num)(uint32_t, const char *); +}; + +#endif /* __LINUX_USB_ANDROID_H */ diff --git a/include/linux/usb/android_composite.h b/include/linux/usb/android_composite.h new file mode 100644 index 00000000000..438dfa4f7be --- /dev/null +++ b/include/linux/usb/android_composite.h @@ -0,0 +1,97 @@ +/* + * Platform data for Android USB + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __LINUX_USB_ANDROID_H +#define __LINUX_USB_ANDROID_H + +#include +#include + +struct android_usb_function { + struct list_head list; + char *name; + int (*bind_config)(struct usb_configuration *c); +}; + +struct android_usb_product { + /* Default product ID. */ + __u16 product_id; + + /* List of function names associated with this product. + * This is used to compute the USB product ID dynamically + * based on which functions are enabled. + */ + int num_functions; + char **functions; +}; + +struct android_usb_platform_data { + /* USB device descriptor fields */ + __u16 vendor_id; + + /* Default product ID. */ + __u16 product_id; + + __u16 version; + + char *product_name; + char *manufacturer_name; + char *serial_number; + + /* List of available USB products. + * This is used to compute the USB product ID dynamically + * based on which functions are enabled. + * if num_products is zero or no match can be found, + * we use the default product ID + */ + int num_products; + struct android_usb_product *products; + + /* List of all supported USB functions. + * This list is used to define the order in which + * the functions appear in the configuration's list of USB interfaces. + * This is necessary to avoid depending upon the order in which + * the individual function drivers are initialized. + */ + int num_functions; + char **functions; +}; + +/* Platform data for "usb_mass_storage" driver. */ +struct usb_mass_storage_platform_data { + /* Contains values for the SC_INQUIRY SCSI command. */ + char *vendor; + char *product; + int release; + + char can_stall; + /* number of LUNS */ + int nluns; +}; + +/* Platform data for USB ethernet driver. */ +struct usb_ether_platform_data { + u8 ethaddr[ETH_ALEN]; + u32 vendorID; + const char *vendorDescr; +}; + +extern void android_register_function(struct android_usb_function *f); + +extern int android_enable_function(struct usb_function *f, int enable); + + +#endif /* __LINUX_USB_ANDROID_H */ diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h index 0fd3fbdd828..736203bb9ee 100644 --- a/include/linux/usb/ch9.h +++ b/include/linux/usb/ch9.h @@ -146,6 +146,10 @@ #define USB_ENDPOINT_HALT 0 /* IN/OUT will STALL */ +#define OTG_STATUS_SELECTOR 0xF000 +#define THOST_REQ_POLL 2000 /* msec */ +#define HOST_REQUEST_FLAG 0 + /* Bit array elements as returned by the USB_REQ_GET_STATUS request. */ #define USB_DEV_STAT_U1_ENABLED 2 /* transition into U1 state */ #define USB_DEV_STAT_U2_ENABLED 3 /* transition into U2 state */ diff --git a/include/linux/usb/f_mtp.h b/include/linux/usb/f_mtp.h index 7422b17c6eb..9f21846e93c 100644 --- a/include/linux/usb/f_mtp.h +++ b/include/linux/usb/f_mtp.h @@ -35,6 +35,11 @@ struct mtp_data_header { #endif /* __KERNEL__ */ +/* Constants for MTP_SET_INTERFACE_MODE */ +#define MTP_INTERFACE_MODE_MTP 0 +#define MTP_INTERFACE_MODE_PTP 1 + + struct mtp_file_range { /* file descriptor for file to transfer */ int fd; @@ -65,6 +70,8 @@ struct mtp_event { * The file is created if it does not exist. */ #define MTP_RECEIVE_FILE _IOW('M', 1, struct mtp_file_range) +/* Sets the driver mode to either MTP or PTP */ +#define MTP_SET_INTERFACE_MODE _IOW('M', 2, int) /* Sends an event to the host via the interrupt endpoint */ #define MTP_SEND_EVENT _IOW('M', 3, struct mtp_event) /* Sends the specified file range to the host, diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index dd1571db55e..c83035dd365 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -486,6 +486,7 @@ struct usb_gadget { unsigned b_hnp_enable:1; unsigned a_hnp_support:1; unsigned a_alt_hnp_support:1; + unsigned host_request:1; const char *name; struct device dev; }; diff --git a/include/linux/usb/msm_hsusb.h b/include/linux/usb/msm_hsusb.h index 00311fe9d0d..8942eead492 100644 --- a/include/linux/usb/msm_hsusb.h +++ b/include/linux/usb/msm_hsusb.h @@ -20,6 +20,7 @@ #include #include +#include /** * Supported USB modes @@ -68,6 +69,7 @@ enum msm_usb_phy_type { }; #define IDEV_CHG_MAX 1500 +#define IDEV_CHG_MIN 500 #define IUNIT 100 /** @@ -102,13 +104,27 @@ enum usb_chg_state { * USB_DCP_CHARGER Dedicated charger port (AC charger/ Wall charger). * USB_CDP_CHARGER Charging downstream port. Enumeration can happen and * IDEV_CHG_MAX can be drawn irrespective of USB state. - * + * USB_ACA_A_CHARGER B-device is connected on accessory port with charger + * connected on charging port. This configuration allows + * charging in host mode. + * USB_ACA_B_CHARGER No device (or A-device without VBUS) is connected on + * accessory port with charger connected on charging port. + * USB_ACA_C_CHARGER A-device (with VBUS) is connected on + * accessory port with charger connected on charging port. + * USB_ACA_DOCK_CHARGER A docking station that has one upstream port and one + * or more downstream ports. Capable of supplying + * IDEV_CHG_MAX irrespective of devices connected on + * accessory ports. */ enum usb_chg_type { USB_INVALID_CHARGER = 0, USB_SDP_CHARGER, USB_DCP_CHARGER, USB_CDP_CHARGER, + USB_ACA_A_CHARGER, + USB_ACA_B_CHARGER, + USB_ACA_C_CHARGER, + USB_ACA_DOCK_CHARGER, }; /** @@ -124,6 +140,7 @@ enum usb_chg_type { * OTG switch is controller by user. * @pclk_src_name: pclk is derived from ebi1_usb_clk in case of 7x27 and 8k * dfab_usb_hs_clk in case of 8660 and 8960. + * @pmic_id_irq: IRQ number assigned for PMIC USB ID line. */ struct msm_otg_platform_data { int *phy_init_seq; @@ -135,6 +152,7 @@ struct msm_otg_platform_data { enum msm_usb_phy_type phy_type; void (*setup_gpio)(enum usb_otg_state state); char *pclk_src_name; + int pmic_id_irq; }; /** @@ -158,6 +176,12 @@ struct msm_otg_platform_data { * @chg_type: The type of charger attached. * @dcd_retires: The retry count used to track Data contact * detection process. + * @wlock: Wake lock struct to prevent system suspend when + * USB is active. + * @usbdev_nb: The notifier block used to know about the B-device + * connected. Useful only when ACA_A charger is + * connected. + * @mA_port: The amount of current drawn by the attached B-device. */ struct msm_otg { struct otg_transceiver otg; @@ -171,6 +195,9 @@ struct msm_otg { void __iomem *regs; #define ID 0 #define B_SESS_VLD 1 +#define ID_A 2 +#define ID_B 3 +#define ID_C 4 unsigned long inputs; struct work_struct sm_work; atomic_t in_lpm; @@ -180,6 +207,32 @@ struct msm_otg { enum usb_chg_state chg_state; enum usb_chg_type chg_type; u8 dcd_retries; + struct wake_lock wlock; + struct notifier_block usbdev_nb; + unsigned mA_port; + unsigned long caps; + /* + * Allowing PHY power collpase turns off the HSUSB 3.3v and 1.8v + * analog regulators while going to low power mode. + * Currently only 8960(28nm PHY) has the support to allowing PHY + * power collapse since it doesn't have leakage currents while + * turning off the power rails. + */ +#define ALLOW_PHY_POWER_COLLAPSE BIT(0) + /* + * Allow PHY RETENTION mode before turning off the digital + * voltage regulator(VDDCX). + */ +#define ALLOW_PHY_RETENTION BIT(1) + /* + * Disable the OTG comparators to save more power + * if depends on PMIC for VBUS and ID interrupts. + */ +#define ALLOW_PHY_COMP_DISABLE BIT(2) + unsigned long lpm_flags; +#define PHY_PWR_COLLAPSED BIT(0) +#define PHY_RETENTIONED BIT(1) +#define PHY_OTG_COMP_DISABLED BIT(2) }; #endif diff --git a/include/linux/usb/msm_hsusb_hw.h b/include/linux/usb/msm_hsusb_hw.h index 6e97a2d3d39..55dc1341192 100644 --- a/include/linux/usb/msm_hsusb_hw.h +++ b/include/linux/usb/msm_hsusb_hw.h @@ -21,6 +21,7 @@ #define USB_CAPLENGTH (MSM_USB_BASE + 0x0100) /* 8 bit */ #define USB_USBCMD (MSM_USB_BASE + 0x0140) +#define USB_USBSTS (MSM_USB_BASE + 0x0144) #define USB_PORTSC (MSM_USB_BASE + 0x0184) #define USB_OTGSC (MSM_USB_BASE + 0x01A4) #define USB_USBMODE (MSM_USB_BASE + 0x01A8) @@ -41,6 +42,11 @@ #define ULPI_DATA(n) ((n) & 255) #define ULPI_DATA_READ(n) (((n) >> 8) & 255) +/* synopsys 28nm phy registers */ +#define ULPI_PWR_CLK_MNG_REG 0x88 +#define OTG_COMP_DISABLE BIT(0) + +#define PHY_ALT_INT (1 << 28) /* PHY alternate interrupt */ #define ASYNC_INTR_CTRL (1 << 29) /* Enable async interrupt */ #define ULPI_STP_CTRL (1 << 30) /* Block communication with PHY */ #define PHY_RETEN (1 << 1) /* PHY retention enable/disable */ diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h index d87f44f5b04..61814a7d728 100644 --- a/include/linux/usb/otg.h +++ b/include/linux/usb/otg.h @@ -35,6 +35,34 @@ enum usb_otg_state { OTG_STATE_A_VBUS_ERR, }; +enum usb_otg_event { + /* Device is not connected within + * TA_WAIT_BCON or not responding. + */ + OTG_EVENT_DEV_CONN_TMOUT, + /* B-device returned STALL for + * B_HNP_ENABLE feature request. + */ + OTG_EVENT_NO_RESP_FOR_HNP_ENABLE, + /* HUB class devices are not + * supported. + */ + OTG_EVENT_HUB_NOT_SUPPORTED, + /* Device is not supported i.e + * not listed in TPL. + */ + OTG_EVENT_DEV_NOT_SUPPORTED, + /* HNP failed due to + * TA_AIDL_BDIS timeout or + * TB_ASE0_BRST timeout + */ + OTG_EVENT_HNP_FAILED, + /* B-device did not detect VBUS + * within TB_SRP_FAIL time. + */ + OTG_EVENT_NO_RESP_FOR_SRP, +}; + enum usb_xceiv_events { USB_EVENT_NONE, /* no events or cable disconnected */ USB_EVENT_VBUS, /* vbus valid event */ @@ -111,6 +139,10 @@ struct otg_transceiver { /* start or continue HNP role switch */ int (*start_hnp)(struct otg_transceiver *otg); + /* send events to user space */ + int (*send_event)(struct otg_transceiver *otg, + enum usb_otg_event event); + }; @@ -164,6 +196,10 @@ otg_shutdown(struct otg_transceiver *otg) otg->shutdown(otg); } +/* for USB core, host and peripheral controller drivers */ +/* Context: can sleep */ +extern int otg_send_event(enum usb_otg_event event); + /* for usb host and peripheral controller drivers */ #ifdef CONFIG_USB_OTG_UTILS extern struct otg_transceiver *otg_get_transceiver(void); diff --git a/include/linux/vcm.h b/include/linux/vcm.h new file mode 100644 index 00000000000..776b8b26af8 --- /dev/null +++ b/include/linux/vcm.h @@ -0,0 +1,652 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VCM_H_ +#define _VCM_H_ + +/* All undefined types must be defined using platform specific headers */ + +#include + +/* + * Virtual contiguous memory (VCM) region primitives. + * + * Current memory mapping software uses a CPU centric management + * model. This makes sense in general, average hardware only contains an + * CPU MMU and possibly a graphics MMU. If every device in the system + * has one or more MMUs a CPU centric MM programming model breaks down. + * + * Looking at mapping from a system-wide perspective reveals a general + * graph problem. Each node that talks to memory, either through an MMU + * or directly (via physical memory) can be thought of as the device end + * of a mapping edge. The other edge is the physical memory that is + * mapped. + * + * In the direct mapped case, it is useful to give the device an + * MMU. This one-to-one MMU allows direct mapped devices to + * participate in graph management, they simply see memory through a + * one-to-one mapping. + * + * The CPU nodes can also be brought under the same mapping + * abstraction with the use of a light overlay on the existing + * VMM. This light overlay brings the VMM's page table abstraction for + * each process and the kernel into the graph management API. + * + * Taken together this system wide approach provides a capability that + * is greater than the sum of its parts by allowing users to reason + * about system wide mapping issues without getting bogged down in CPU + * centric device page table management issues. + */ + + +/* + * Creating, freeing and managing VCMs. + * + * A VCM region is a virtual space that can be reserved from and + * associated with one or more devices. At creation the user can + * specify an offset to start addresses and a length of the entire VCM + * region. Reservations out of a VCM region are always contiguous. + */ + +/** + * vcm_create() - Create a VCM region + * @start_addr: The starting address of the VCM region. + * @len: The len of the VCM region. This must be at least + * vcm_get_min_page_size() bytes. + * + * A VCM typically abstracts a page table. + * + * All functions in this API are passed and return opaque things + * because the underlying implementations will vary. The goal + * is really graph management. vcm_create() creates the "device end" + * of an edge in the mapping graph. + * + * The return value is non-zero if a VCM has successfully been + * created. It will return zero if a VCM region cannot be created or + * len is invalid. + */ +struct vcm *vcm_create(unsigned long start_addr, size_t len); + + +/** + * vcm_create_from_prebuilt() - Create a VCM region from an existing region + * @ext_vcm_id: An external opaque value that allows the + * implementation to reference an already built table. + * + * The ext_vcm_id will probably reference a page table that's been built + * by the VM. + * + * The platform specific implementation will provide this. + * + * The return value is non-zero if a VCM has successfully been created. + */ +struct vcm *vcm_create_from_prebuilt(size_t ext_vcm_id); + + +/** + * vcm_clone() - Clone a VCM + * @vcm: A VCM to clone from. + * + * Perform a VCM "deep copy." The resulting VCM will match the original at + * the point of cloning. Subsequent updates to either VCM will only be + * seen by that VCM. + * + * The return value is non-zero if a VCM has been successfully cloned. + */ +struct vcm *vcm_clone(struct vcm *vcm); + + +/** + * vcm_get_start_addr() - Get the starting address of the VCM region. + * @vcm: The VCM we're interested in getting the starting + * address of. + * + * The return value will be 1 if an error has occurred. + */ +size_t vcm_get_start_addr(struct vcm *vcm); + + +/** + * vcm_get_len() - Get the length of the VCM region. + * @vcm: The VCM we're interested in reading the length from. + * + * The return value will be non-zero for a valid VCM. VCM regions + * cannot have 0 len. + */ +size_t vcm_get_len(struct vcm *vcm); + + +/** + * vcm_free() - Free a VCM. + * @vcm: The VCM we're interested in freeing. + * + * The return value is 0 if the VCM has been freed or: + * -EBUSY The VCM region contains reservations or has been + * associated (active or not) and cannot be freed. + * -EINVAL The vcm argument is invalid. + */ +int vcm_free(struct vcm *vcm); + + +/* + * Creating, freeing and managing reservations out of a VCM. + * + */ + +/** + * vcm_reserve() - Create a reservation from a VCM region. + * @vcm: The VCM region to reserve from. + * @len: The length of the reservation. Must be at least + * vcm_get_min_page_size() bytes. + * @attr: See 'Reservation Attributes'. + * + * A reservation, res_t, is a contiguous range from a VCM region. + * + * The return value is non-zero if a reservation has been successfully + * created. It is 0 if any of the parameters are invalid. + */ +struct res *vcm_reserve(struct vcm *vcm, size_t len, u32 attr); + + +/** + * vcm_reserve_at() - Make a reservation at a given logical location. + * @memtarget: A logical location to start the reservation from. + * @vcm: The VCM region to start the reservation from. + * @len: The length of the reservation. + * @attr: See 'Reservation Attributes'. + * + * The return value is non-zero if a reservation has been successfully + * created. + */ +struct res *vcm_reserve_at(enum memtarget_t memtarget, struct vcm *vcm, + size_t len, u32 attr); + + +/** + * vcm_get_vcm_from_res() - Return the VCM region of a reservation. + * @res: The reservation to return the VCM region of. + * + * Te return value will be non-zero if the reservation is valid. A valid + * reservation is always associated with a VCM region; there is no such + * thing as an orphan reservation. + */ +struct vcm *vcm_get_vcm_from_res(struct res *res); + + +/** + * vcm_unreserve() - Unreserve the reservation. + * @res: The reservation to unreserve. + * + * The return value will be 0 if the reservation was successfully + * unreserved and: + * -EBUSY The reservation is still backed, + * -EINVAL The vcm argument is invalid. + */ +int vcm_unreserve(struct res *res); + + +/** + * vcm_set_res_attr() - Set attributes of an existing reservation. + * @res: An existing reservation of interest. + * @attr: See 'Reservation Attributes'. + * + * This function can only be used on an existing reservation; there + * are no orphan reservations. All attributes can be set on a existing + * reservation. + * + * The return value will be 0 for a success, otherwise it will be: + * -EINVAL res or attr are invalid. + */ +int vcm_set_res_attr(struct res *res, u32 attr); + + +/** + * vcm_get_num_res() - Return the number of reservations in a VCM region. + * @vcm: The VCM region of interest. + */ +size_t vcm_get_num_res(struct vcm *vcm); + + +/** + * vcm_get_next_res() - Read each reservation one at a time. + * @vcm: The VCM region of interest. + * @res: Contains the last reservation. Pass NULL on the + * first call. + * + * This function works like a foreach reservation in a VCM region. + * + * The return value will be non-zero for each reservation in a VCM. A + * zero indicates no further reservations. + */ +struct res *vcm_get_next_res(struct vcm *vcm, struct res *res); + + +/** + * vcm_res_copy() - Copy len bytes from one reservation to another. + * @to: The reservation to copy to. + * @from: The reservation to copy from. + * @len: The length of bytes to copy. + * + * The return value is the number of bytes copied. + */ +size_t vcm_res_copy(struct res *to, size_t to_off, struct res *from, size_t + from_off, size_t len); + + +/** + * vcm_get_min_page_size() - Return the minimum page size supported by + * the architecture. + */ +size_t vcm_get_min_page_size(void); + + +/** + * vcm_back() - Physically back a reservation. + * @res: The reservation containing the virtual contiguous + * region to back. + * @physmem: The physical memory that will back the virtual + * contiguous memory region. + * + * One VCM can be associated with multiple devices. When you vcm_back() + * each association must be active. This is not strictly necessary. It may + * be changed in the future. + * + * This function returns 0 on a successful physical backing. Otherwise + * it returns: + * -EINVAL res or physmem is invalid or res's len + * is different from physmem's len. + * -EAGAIN Try again, one of the devices hasn't been activated. + */ +int vcm_back(struct res *res, struct physmem *physmem); + + +/** + * vcm_unback() - Unback a reservation. + * @res: The reservation to unback. + * + * One VCM can be associated with multiple devices. When you vcm_unback() + * each association must be active. + * + * This function returns 0 on a successful unbacking. Otherwise + * it returns: + * -EINVAL res is invalid. + * -EAGAIN Try again, one of the devices hasn't been activated. + */ +int vcm_unback(struct res *res); + + +/** + * vcm_phys_alloc() - Allocate physical memory for the VCM region. + * @memtype: The memory type to allocate. + * @len: The length of the allocation. + * @attr: See 'Physical Allocation Attributes'. + * + * This function will allocate chunks of memory according to the attr + * it is passed. + * + * The return value is non-zero if physical memory has been + * successfully allocated. + */ +struct physmem *vcm_phys_alloc(enum memtype_t memtype, size_t len, u32 attr); + + +/** + * vcm_phys_free() - Free a physical allocation. + * @physmem: The physical allocation to free. + * + * The return value is 0 if the physical allocation has been freed or: + * -EBUSY Their are reservation mapping the physical memory. + * -EINVAL The physmem argument is invalid. + */ +int vcm_phys_free(struct physmem *physmem); + + +/** + * vcm_get_physmem_from_res() - Return a reservation's physmem + * @res: An existing reservation of interest. + * + * The return value will be non-zero on success, otherwise it will be: + * -EINVAL res is invalid + * -ENOMEM res is unbacked + */ +struct physmem *vcm_get_physmem_from_res(struct res *res); + + +/** + * vcm_get_memtype_of_physalloc() - Return the memtype of a reservation. + * @physmem: The physical allocation of interest. + * + * This function returns the memtype of a reservation or VCM_INVALID + * if res is invalid. + */ +enum memtype_t vcm_get_memtype_of_physalloc(struct physmem *physmem); + + +/* + * Associate a VCM with a device, activate that association and remove it. + * + */ + +/** + * vcm_assoc() - Associate a VCM with a device. + * @vcm: The VCM region of interest. + * @dev: The device to associate the VCM with. + * @attr: See 'Association Attributes'. + * + * This function returns non-zero if a association is made. It returns 0 + * if any of its parameters are invalid or VCM_ATTR_VALID is not present. + */ +struct avcm *vcm_assoc(struct vcm *vcm, struct device *dev, u32 attr); + + +/** + * vcm_deassoc() - Deassociate a VCM from a device. + * @avcm: The association we want to break. + * + * The function returns 0 on success or: + * -EBUSY The association is currently activated. + * -EINVAL The avcm parameter is invalid. + */ +int vcm_deassoc(struct avcm *avcm); + + +/** + * vcm_set_assoc_attr() - Set an AVCM's attributes. + * @avcm: The AVCM of interest. + * @attr: The new attr. See 'Association Attributes'. + * + * Every attribute can be set at runtime if an association isn't activated. + * + * This function returns 0 on success or: + * -EBUSY The association is currently activated. + * -EINVAL The avcm parameter is invalid. + */ +int vcm_set_assoc_attr(struct avcm *avcm, u32 attr); + + +/** + * vcm_get_assoc_attr() - Return an AVCM's attributes. + * @avcm: The AVCM of interest. + * + * This function returns 0 on error. + */ +u32 vcm_get_assoc_attr(struct avcm *avcm); + + +/** + * vcm_activate() - Activate an AVCM. + * @avcm: The AVCM to activate. + * + * You have to deactivate, before you activate. + * + * This function returns 0 on success or: + * -EINVAL avcm is invalid + * -ENODEV no device + * -EBUSY device is already active + * -1 hardware failure + */ +int vcm_activate(struct avcm *avcm); + + +/** + * vcm_deactivate() - Deactivate an association. + * @avcm: The AVCM to deactivate. + * + * This function returns 0 on success or: + * -ENOENT avcm is not activate + * -EINVAL avcm is invalid + * -1 hardware failure + */ +int vcm_deactivate(struct avcm *avcm); + + +/** + * vcm_is_active() - Query if an AVCM is active. + * @avcm: The AVCM of interest. + * + * returns 0 for not active, 1 for active or -EINVAL for error. + * + */ +int vcm_is_active(struct avcm *avcm); + + +/* + * Create, manage and remove a boundary in a VCM. + */ + +/** + * vcm_create_bound() - Create a bound in a VCM. + * @vcm: The VCM that needs a bound. + * @len: The len of the bound. + * + * The allocator picks the virtual addresses of the bound. + * + * This function returns non-zero if a bound was created. + */ +struct bound *vcm_create_bound(struct vcm *vcm, size_t len); + + +/** + * vcm_free_bound() - Free a bound. + * @bound: The bound to remove. + * + * This function returns 0 if bound has been removed or: + * -EBUSY The bound contains reservations and cannot be removed. + * -EINVAL The bound is invalid. + */ +int vcm_free_bound(struct bound *bound); + + +/** + * vcm_reserve_from_bound() - Make a reservation from a bounded area. + * @bound: The bound to reserve from. + * @len: The len of the reservation. + * @attr: See 'Reservation Attributes'. + * + * The return value is non-zero on success. It is 0 if any parameter + * is invalid. + */ +struct res *vcm_reserve_from_bound(struct bound *bound, size_t len, + u32 attr); + + +/** + * vcm_get_bound_start_addr() - Return the starting device address of the bound + * @bound: The bound of interest. + * + * On success this function returns the starting addres of the bound. On error + * it returns: + * 1 bound_id is invalid. + */ +size_t vcm_get_bound_start_addr(struct bound *bound); + + + +/* + * Perform low-level control over VCM regions and reservations. + */ + +/** + * vcm_map_phys_addr() - Produce a physmem from a contiguous + * physical address + * + * @phys: The physical address of the contiguous range. + * @len: The len of the contiguous address range. + * + * Returns non-zero on success, 0 on failure. + */ +struct physmem *vcm_map_phys_addr(phys_addr_t phys, size_t len); + + +/** + * vcm_get_next_phys_addr() - Get the next physical addr and len of a physmem. + * @physmem: The physmem of interest. + * @phys: The current physical address. Set this to NULL to + * start the iteration. + * @len An output: the len of the next physical segment. + * + * physmems may contain physically discontiguous sections. This + * function returns the next physical address and len. Pass NULL to + * phys to get the first physical address. The len of the physical + * segment is returned in *len. + * + * Returns 0 if there is no next physical address. + */ +size_t vcm_get_next_phys_addr(struct physmem *physmem, phys_addr_t phys, + size_t *len); + + +/** + * vcm_get_dev_addr() - Return the device address of a reservation. + * @res: The reservation of interest. + * + * + * On success this function returns the device address of a reservation. On + * error it returns: + * 1 res is invalid. + * + * Note: This may return a kernel address if the reservation was + * created from vcm_create_from_prebuilt() and the prebuilt ext_vcm_id + * references a VM page table. + */ +phys_addr_t vcm_get_dev_addr(struct res *res); + + +/** + * vcm_get_res() - Return the reservation from a device address and a VCM + * @dev_addr: The device address of interest. + * @vcm: The VCM that contains the reservation + * + * This function returns 0 if there is no reservation whose device + * address is dev_addr. + */ +struct res *vcm_get_res(unsigned long dev_addr, struct vcm *vcm); + + +/** + * vcm_translate() - Translate from one device address to another. + * @src_dev: The source device address. + * @src_vcm: The source VCM region. + * @dst_vcm: The destination VCM region. + * + * Derive the device address from a VCM region that maps the same physical + * memory as a device address from another VCM region. + * + * On success this function returns the device address of a translation. On + * error it returns: + * 1 res_id is invalid. + */ +size_t vcm_translate(struct device *src_dev, struct vcm *src_vcm, + struct vcm *dst_vcm); + + +/** + * vcm_get_phys_num_res() - Return the number of reservations mapping a + * physical address. + * @phys: The physical address to read. + */ +size_t vcm_get_phys_num_res(phys_addr_t phys); + + +/** + * vcm_get_next_phys_res() - Return the next reservation mapped to a physical + * address. + * @phys: The physical address to map. + * @res: The starting reservation. Set this to NULL for the first + * reservation. + * @len: The virtual length of the reservation + * + * This function returns 0 for the last reservation or no reservation. + */ +struct res *vcm_get_next_phys_res(phys_addr_t phys, struct res *res, + size_t *len); + + +/** + * vcm_get_pgtbl_pa() - Return the physcial address of a VCM's page table. + * @vcm: The VCM region of interest. + * + * This function returns non-zero on success. + */ +phys_addr_t vcm_get_pgtbl_pa(struct vcm *vcm); + + +/** + * vcm_get_cont_memtype_pa() - Return the phys base addr of a memtype's + * first contiguous region. + * @memtype: The memtype of interest. + * + * This function returns non-zero on success. A zero return indicates that + * the given memtype does not have a contiguous region or that the memtype + * is invalid. + */ +phys_addr_t vcm_get_cont_memtype_pa(enum memtype_t memtype); + + +/** + * vcm_get_cont_memtype_len() - Return the len of a memtype's + * first contiguous region. + * @memtype: The memtype of interest. + * + * This function returns non-zero on success. A zero return indicates that + * the given memtype does not have a contiguous region or that the memtype + * is invalid. + */ +size_t vcm_get_cont_memtype_len(enum memtype_t memtype); + + +/** + * vcm_dev_addr_to_phys_addr() - Perform a device address page-table lookup. + * @vcm: VCM to use for translation. + * @dev_addr: The device address to map. + * + * This function returns the pa of a va from a device's page-table. It will + * fault if the dev_addr is not mapped. + */ +phys_addr_t vcm_dev_addr_to_phys_addr(struct vcm *vcm, unsigned long dev_addr); + + +/* + * Fault Hooks + * + * vcm_hook() + */ + +/** + * vcm_hook() - Add a fault handler. + * @dev: The device. + * @handler: The handler. + * @data: A private piece of data that will get passed to the + * handler. + * + * This function returns 0 for a successful registration or: + * -EINVAL The arguments are invalid. + */ +int vcm_hook(struct device *dev, vcm_handler handler, void *data); + + + +/* + * Low level, platform agnostic, HW control. + * + * vcm_hw_ver() + */ + +/** + * vcm_hw_ver() - Return the hardware version of a device, if it has one. + * @dev The device. + */ +size_t vcm_hw_ver(size_t dev); + +#endif /* _VCM_H_ */ + diff --git a/include/linux/vcm_alloc.h b/include/linux/vcm_alloc.h new file mode 100644 index 00000000000..f0e4ea4a6ef --- /dev/null +++ b/include/linux/vcm_alloc.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VCM_ALLOC_H +#define VCM_ALLOC_H + +#include +#include +#include + +#define MAX_NUM_PRIO_POOLS 8 + +/* Data structure to inform VCM about the memory it manages */ +struct physmem_region { + size_t addr; + size_t size; + int chunk_size; +}; + +/* Mapping between memtypes and physmem_regions based on chunk size */ +struct vcm_memtype_map { + int pool_id[MAX_NUM_PRIO_POOLS]; + int num_pools; +}; + +int vcm_alloc_pool_idx_to_size(int pool_idx); +int vcm_alloc_idx_to_size(int idx); +int vcm_alloc_get_mem_size(void); +int vcm_alloc_blocks_avail(enum memtype_t memtype, int idx); +int vcm_alloc_get_num_chunks(enum memtype_t memtype); +int vcm_alloc_all_blocks_avail(enum memtarget_t memtype); +int vcm_alloc_count_allocated(enum memtype_t memtype); +void vcm_alloc_print_list(enum memtype_t memtype, int just_allocated); +int vcm_alloc_idx_to_size(int idx); +int vcm_alloc_destroy(void); +int vcm_alloc_init(struct physmem_region *mem, int n_regions, + struct vcm_memtype_map *mt_map, int n_mt); +int vcm_alloc_free_blocks(enum memtype_t memtype, + struct phys_chunk *alloc_head); +int vcm_alloc_num_blocks(int num, enum memtype_t memtype, + int idx, /* chunk size */ + struct phys_chunk *alloc_head); +int vcm_alloc_max_munch(int len, enum memtype_t memtype, + struct phys_chunk *alloc_head); + +/* bring-up init, destroy */ +int vcm_sys_init(struct physmem_region *mem, int n_regions, + struct vcm_memtype_map *mt_map, int n_mt, + void *cont_pa, unsigned int cont_sz); + +int vcm_sys_destroy(void); + +#endif /* VCM_ALLOC_H */ diff --git a/include/linux/vcm_mm.h b/include/linux/vcm_mm.h new file mode 100644 index 00000000000..4cc53583738 --- /dev/null +++ b/include/linux/vcm_mm.h @@ -0,0 +1,105 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* Architecture-specific VCM functions */ + +/* Device attributes */ + +/* + * Sharing attributes. Pick only one. + */ +#define VCM_DEV_ATTR_NON_SH (0x00) +#define VCM_DEV_ATTR_SH (0x04) + +/* + * Caching attributes. Pick only one. + */ +#define VCM_DEV_ATTR_NONCACHED (0x00) +#define VCM_DEV_ATTR_CACHED_WB_WA (0x01) +#define VCM_DEV_ATTR_CACHED_WB_NWA (0x02) +#define VCM_DEV_ATTR_CACHED_WT (0x03) + +/* + * A "good" default set of attributes: shareable and non-cacheable. + */ +#define VCM_DEV_DEFAULT_ATTR (VCM_DEV_ATTR_SH | VCM_DEV_ATTR_NONCACHED) + +/** + * set_arm7_pte_attr() - Set ARMv7 page table attributes + * pt_base Virtual address of the first-level page table + * @va Virtual address whose attributes are to be set + * @len Page size used to map the given virtual address + * @attr Attributes to set for this mapping. + * + * Modify a mapping attribute. The base address of the page table must + * be a virtual address containing a valid ARMv7 page table. The + * virtual address must refer to an existing mapping and must be + * aligned to the length with which it was mapped. The mapping length + * must similarly be the same as was specified when the mapping was + * made (one of 4KB, 64KB, 1MB, or 16MB). The attribute must be one of + * the shareability attributes above ORed with one of the cacheability + * attributes. Any previous attributes are completely replaced by the + * most recent call to this function. This function only sets the + * cacheability and shareability attributes. This is accomplished by + * modifying the TEX class and the S bit in the PTE. It is an error to + * call this function without having called vcm_setup_tex_classes at + * least once. + * + * The return value is zero on success and non-zero on failure. + */ +int set_arm7_pte_attr(unsigned long pt_base, unsigned long va, + unsigned long len, unsigned int attr); + + +/** + * cpu_set_attr() - Set page table attributes on the CPU's page tables + * @va Virtual address whose attributes are to be set + * @len Page size used to map the given virtual address + * @attr Attributes to set for this mapping. + * + * Modify a mapping attribute within the ARM page tables. The va must + * refer to an existing mapping and must be aligned to the length with + * which it was mapped. The mapping length must similarly be the same + * as was specified when the mapping was made (one of 4KB, 64KB, 1MB, + * or 16MB). The attribute must be one of the shareability attributes + * above ORed with one of the cacheability attributes. Any previous + * attributes are completely replaced by the most recent call to this + * function. This function only sets the cacheability and shareability + * attributes. This is accomplished by modifying the TEX class and the + * S bit in the PTE. It is an error to call this function without + * having called vcm_setup_tex_classes at least once. It is an error + * to call this function on any system using a memory configuration + * that is anything OTHER than ARMv7 with TEX remap enabled. Only the + * HW page tables are modified; the Linux page tables are left + * untouched. + * + * The return value is zero on success and non-zero on failure. + */ +int cpu_set_attr(unsigned long va, unsigned long len, unsigned int attr); + + +/** + * vcm_setup_tex_classes() - Prepare TEX class table for use + * + * Initialize the attribute mapping table by examining the TEX classes + * used by the CPU and finding the classes that match the device + * attributes (VCM_DEV_xx) defined above. This function is only + * relevant if TEX remap is enabled. The results will be unpredictable + * and irrelevant if TEX remap is not in use. It is an error to call + * this function in any system using a memory configuration of + * anything OTHER than ARMv7 with TEX remap enabled. + * + * The return value is zero on success or non-zero on failure. In the + * present version, a failure will result in a panic. + */ +int vcm_setup_tex_classes(void); diff --git a/include/linux/vcm_types.h b/include/linux/vcm_types.h new file mode 100644 index 00000000000..7ec20a6b63e --- /dev/null +++ b/include/linux/vcm_types.h @@ -0,0 +1,355 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef VCM_TYPES_H +#define VCM_TYPES_H + +#include +#include +#include +#include +#include +#include + +/* + * Reservation Attributes + * + * Used in vcm_reserve(), vcm_reserve_at(), vcm_set_res_attr() and + * vcm_reserve_bound(). + * + * VCM_READ Specifies that the reservation can be read. + * VCM_WRITE Specifies that the reservation can be written. + * VCM_EXECUTE Specifies that the reservation can be executed. + * VCM_USER Specifies that this reservation is used for + * userspace access. + * VCM_SUPERVISOR Specifies that this reservation is used for + * supervisor access. + * VCM_SECURE Specifies that the target of the reservation is + * secure. The usage of this setting is TBD. + * + * Caching behavior as a 4 bit field: + * VCM_NOTCACHED The VCM region is not cached. + * VCM_INNER_WB_WA The VCM region is inner cached + * and is write-back and write-allocate. + * VCM_INNER_WT_NWA The VCM region is inner cached and is + * write-through and no-write-allocate. + * VCM_INNER_WB_NWA The VCM region is inner cached and is + * write-back and no-write-allocate. + * VCM_OUTER_WB_WA The VCM region is outer cached and is + * write-back and write-allocate. + * VCM_OUTER_WT_NWA The VCM region is outer cached and is + * write-through and no-write-allocate. + * VCM_OUTER_WB_NWA The VCM region is outer cached and is + * write-back and no-write-allocate. + * VCM_WB_WA The VCM region is cached and is write + * -back and write-allocate. + * VCM_WT_NWA The VCM region is cached and is write + * -through and no-write-allocate. + * VCM_WB_NWA The VCM region is cached and is write + * -back and no-write-allocate. + */ + +/* Order of alignment (power of 2). Ie, 12 = 4k, 13 = 8k, 14 = 16k + * Alignments of less than 1MB on buffers of size 1MB or greater should be + * avoided. Alignments of less than 64KB on buffers of size 64KB or greater + * should be avoided. Strictly speaking, it will work, but will result in + * suboptimal performance, and a warning will be printed to that effect if + * VCM_PERF_WARN is enabled. + */ +#define VCM_ALIGN_SHIFT 10 +#define VCM_ALIGN_MASK 0x1F +#define VCM_ALIGN_ATTR(order) (((order) & VCM_ALIGN_MASK) << VCM_ALIGN_SHIFT) + +#define VCM_ALIGN_DEFAULT 0 +#define VCM_ALIGN_4K (VCM_ALIGN_ATTR(12)) +#define VCM_ALIGN_8K (VCM_ALIGN_ATTR(13)) +#define VCM_ALIGN_16K (VCM_ALIGN_ATTR(14)) +#define VCM_ALIGN_32K (VCM_ALIGN_ATTR(15)) +#define VCM_ALIGN_64K (VCM_ALIGN_ATTR(16)) +#define VCM_ALIGN_128K (VCM_ALIGN_ATTR(17)) +#define VCM_ALIGN_256K (VCM_ALIGN_ATTR(18)) +#define VCM_ALIGN_512K (VCM_ALIGN_ATTR(19)) +#define VCM_ALIGN_1M (VCM_ALIGN_ATTR(20)) +#define VCM_ALIGN_2M (VCM_ALIGN_ATTR(21)) +#define VCM_ALIGN_4M (VCM_ALIGN_ATTR(22)) +#define VCM_ALIGN_8M (VCM_ALIGN_ATTR(23)) +#define VCM_ALIGN_16M (VCM_ALIGN_ATTR(24)) +#define VCM_ALIGN_32M (VCM_ALIGN_ATTR(25)) +#define VCM_ALIGN_64M (VCM_ALIGN_ATTR(26)) +#define VCM_ALIGN_128M (VCM_ALIGN_ATTR(27)) +#define VCM_ALIGN_256M (VCM_ALIGN_ATTR(28)) +#define VCM_ALIGN_512M (VCM_ALIGN_ATTR(29)) +#define VCM_ALIGN_1GB (VCM_ALIGN_ATTR(30)) + + +#define VCM_CACHE_POLICY (0xF << 0) +#define VCM_READ (1UL << 9) +#define VCM_WRITE (1UL << 8) +#define VCM_EXECUTE (1UL << 7) +#define VCM_USER (1UL << 6) +#define VCM_SUPERVISOR (1UL << 5) +#define VCM_SECURE (1UL << 4) +#define VCM_NOTCACHED (0UL << 0) +#define VCM_WB_WA (1UL << 0) +#define VCM_WB_NWA (2UL << 0) +#define VCM_WT (3UL << 0) + + +/* + * Physical Allocation Attributes + * + * Used in vcm_phys_alloc(). + * + * Alignment as a power of 2 starting at 4 KB. 5 bit field. + * 1 = 4KB, 2 = 8KB, etc. + * + * Specifies that the reservation should have the + * alignment specified. + * + * VCM_4KB Specifies that the reservation should use 4KB pages. + * VCM_64KB Specifies that the reservation should use 64KB pages. + * VCM_1MB specifies that the reservation should use 1MB pages. + * VCM_ALL Specifies that the reservation should use all + * available page sizes. + * VCM_PHYS_CONT Specifies that a reservation should be backed with + * physically contiguous memory. + * VCM_COHERENT Specifies that the reservation must be kept coherent + * because it's shared. + */ + +#define VCM_4KB (1UL << 5) +#define VCM_64KB (1UL << 4) +#define VCM_1MB (1UL << 3) +#define VCM_ALL (1UL << 2) +#define VCM_PAGE_SEL_MASK (0xFUL << 2) +#define VCM_PHYS_CONT (1UL << 1) +#define VCM_COHERENT (1UL << 0) + + +#define SHIFT_4KB (12) + +#define ALIGN_REQ_BYTES(attr) (1UL << (((attr & VCM_ALIGNMENT_MASK) >> 6) + 12)) +/* set the alignment in pow 2, 0 = 4KB */ +#define SET_ALIGN_REQ_BYTES(attr, align) \ + ((attr & ~VCM_ALIGNMENT_MASK) | ((align << 6) & VCM_ALIGNMENT_MASK)) + +/* + * Association Attributes + * + * Used in vcm_assoc(), vcm_set_assoc_attr(). + * + * VCM_USE_LOW_BASE Use the low base register. + * VCM_USE_HIGH_BASE Use the high base register. + * + * VCM_SPLIT A 5 bit field that defines the + * high/low split. This value defines + * the number of 0's left-filled into the + * split register. Addresses that match + * this will use VCM_USE_LOW_BASE + * otherwise they'll use + * VCM_USE_HIGH_BASE. An all 0's value + * directs all translations to + * VCM_USE_LOW_BASE. + */ + +#define VCM_SPLIT (1UL << 3) +#define VCM_USE_LOW_BASE (1UL << 2) +#define VCM_USE_HIGH_BASE (1UL << 1) + + +/* + * External VCMs + * + * Used in vcm_create_from_prebuilt() + * + * Externally created VCM IDs for creating kernel and user space + * mappings to VCMs and kernel and user space buffers out of + * VCM_MEMTYPE_0,1,2, etc. + * + */ +#define VCM_PREBUILT_KERNEL 1 +#define VCM_PREBUILT_USER 2 + +/** + * enum memtarget_t - A logical location in a VCM. + * @VCM_START: Indicates the start of a VCM_REGION. + */ +enum memtarget_t { + VCM_START +}; + + +/** + * enum memtype_t - A logical location in a VCM. + * @VCM_MEMTYPE_0: Generic memory type 0 + * @VCM_MEMTYPE_1: Generic memory type 1 + * @VCM_MEMTYPE_2: Generic memory type 2 + * + * A memtype encapsulates a platform specific memory arrangement. The + * memtype needn't refer to a single type of memory, it can refer to a + * set of memories that can back a reservation. + * + */ +enum memtype_t { + VCM_MEMTYPE_0 = 0, + VCM_MEMTYPE_1 = 1, + VCM_MEMTYPE_2 = 2, + VCM_MEMTYPE_3 = 3, + VCM_INVALID = 4, +}; + +/** + * vcm_handler - The signature of the fault hook. + * @dev: The device id of the faulting device. + * @data: The generic data pointer. + * @fault_data: System specific common fault data. + * + * The handler should return 0 for success. This indicates that the + * fault was handled. A non-zero return value is an error and will be + * propagated up the stack. + */ +typedef int (*vcm_handler)(struct device *dev, void *data, void *fault_data); + + +/** + * enum vcm_type - The type of VCM. + * @VCM_DEVICE: VCM used for device mappings + * @VCM_EXT_KERNEL: VCM used for kernel-side mappings + * @VCM_EXT_USER: VCM used for userspace mappings + * @VCM_ONE_TO_ONE: VCM used for devices without SMMUs + * + */ +enum vcm_type { + VCM_DEVICE, + VCM_EXT_KERNEL, + VCM_EXT_USER, + VCM_ONE_TO_ONE, +}; + + +/** + * struct vcm - A Virtually Contiguous Memory region. + * @start_addr: The starting address of the VCM region. + * @len: The len of the VCM region. This must be at least + * vcm_min() bytes. + */ +struct vcm { + /* public */ + unsigned long start_addr; + size_t len; + + /* private */ + enum vcm_type type; + + struct device *dev; /* opaque device control */ + + struct iommu_domain *domain; + + /* allocator dependent */ + struct gen_pool *pool; + + struct list_head res_head; + + /* this will be a very short list */ + struct list_head assoc_head; +}; + +/** + * struct avcm - A VCM to device association + * @vcm: The VCM region of interest. + * @dev: The device to associate the VCM with. + * @attr: See 'Association Attributes'. + */ +struct avcm { + /* public */ + struct vcm *vcm; + struct device *dev; + u32 attr; + + /* private */ + struct list_head assoc_elm; + + int is_active; /* is this particular association active */ +}; + +/** + * struct bound - A boundary to reserve from in a VCM region. + * @vcm: The VCM that needs a bound. + * @len: The len of the bound. + */ +struct bound { + struct vcm *vcm; + size_t len; +}; + +struct phys_chunk { + struct list_head list; + struct list_head allocated; /* used to record is allocated */ + + struct list_head refers_to; + phys_addr_t pa; + int pool_idx; + int size; +}; + +/** + * struct physmem - A physical memory allocation. + * @memtype: The memory type of the VCM region. + * @len: The len of the physical memory allocation. + * @attr: See 'Physical Allocation Attributes'. + */ +struct physmem { + /* public */ + enum memtype_t memtype; + size_t len; + u32 attr; + + /* private */ + struct phys_chunk alloc_head; + + /* if the physmem is cont then use the built in VCM */ + int is_cont; + struct res *res; +}; + + +/** + * struct res - A reservation in a VCM region. + * @vcm: The VCM region to reserve from. + * @len: The length of the reservation. Must be at least + * vcm_min() bytes. + * @attr: See 'Reservation Attributes'. + * @dev_addr: The device-side address. + */ +struct res { + /* public */ + struct vcm *vcm; + size_t len; + u32 attr; + unsigned long dev_addr; + + /* private */ + struct physmem *physmem; + /* allocator dependent */ + size_t alignment_req; + size_t aligned_len; + unsigned long ptr; + + struct list_head res_elm; + + /* type VCM_EXT_KERNEL */ + struct vm_struct *vm_area; + int mapped; +}; + +#endif /* VCM_TYPES_H */ diff --git a/include/linux/wait.h b/include/linux/wait.h index 3efc9f3f43a..42d9e34afc0 100644 --- a/include/linux/wait.h +++ b/include/linux/wait.h @@ -346,6 +346,93 @@ do { \ __ret; \ }) +#define __wait_io_event_interruptible(wq, condition, ret) \ +do { \ + DEFINE_WAIT(__wait); \ + \ + for (;;) { \ + prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \ + if (condition) \ + break; \ + if (!signal_pending(current)) { \ + io_schedule(); \ + continue; \ + } \ + ret = -ERESTARTSYS; \ + break; \ + } \ + finish_wait(&wq, &__wait); \ +} while (0) + +/** + * wait_io_event_interruptible - sleep until an io condition gets true + * @wq: the waitqueue to wait on + * @condition: a C expression for the event to wait for + * + * The process is put to sleep (TASK_INTERRUPTIBLE) until the + * @condition evaluates to true or a signal is received. + * The @condition is checked each time the waitqueue @wq is woken up. + * + * wake_up() has to be called after changing any variable that could + * change the result of the wait condition. + * + * The function will return -ERESTARTSYS if it was interrupted by a + * signal and 0 if @condition evaluated to true. + */ +#define wait_io_event_interruptible(wq, condition) \ +({ \ + int __ret = 0; \ + if (!(condition)) \ + __wait_io_event_interruptible(wq, condition, __ret); \ + __ret; \ +}) + +#define __wait_io_event_interruptible_timeout(wq, condition, ret) \ +do { \ + DEFINE_WAIT(__wait); \ + \ + for (;;) { \ + prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \ + if (condition) \ + break; \ + if (!signal_pending(current)) { \ + ret = io_schedule_timeout(ret); \ + if (!ret) \ + break; \ + continue; \ + } \ + ret = -ERESTARTSYS; \ + break; \ + } \ + finish_wait(&wq, &__wait); \ +} while (0) + +/** + * wait_io_event_interruptible_timeout - sleep until an io condition gets true or a timeout elapses + * @wq: the waitqueue to wait on + * @condition: a C expression for the event to wait for + * @timeout: timeout, in jiffies + * + * The process is put to sleep (TASK_INTERRUPTIBLE) until the + * @condition evaluates to true or a signal is received. + * The @condition is checked each time the waitqueue @wq is woken up. + * + * wake_up() has to be called after changing any variable that could + * change the result of the wait condition. + * + * The function returns 0 if the @timeout elapsed, -ERESTARTSYS if it + * was interrupted by a signal, and the remaining jiffies otherwise + * if the condition evaluated to true before the timeout elapsed. + */ + +#define wait_io_event_interruptible_timeout(wq, condition, timeout) \ +({ \ + long __ret = timeout; \ + if (!(condition)) \ + __wait_io_event_interruptible_timeout(wq, condition, __ret); \ + __ret; \ +}) + #define __wait_event_interruptible_exclusive(wq, condition, ret) \ do { \ DEFINE_WAIT(__wait); \ diff --git a/include/linux/wcnss_wlan.h b/include/linux/wcnss_wlan.h new file mode 100644 index 00000000000..d1c21340a5a --- /dev/null +++ b/include/linux/wcnss_wlan.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _WCNSS_WLAN_H_ +#define _WCNSS_WLAN_H_ + +#include + +#define WCNSS_WLAN_IRQ_INVALID -1 + +struct device *wcnss_wlan_get_device(void); +struct resource *wcnss_wlan_get_memory_map(struct device *dev); +int wcnss_wlan_get_dxe_tx_irq(struct device *dev); +int wcnss_wlan_get_dxe_rx_irq(struct device *dev); +void wcnss_wlan_register_pm_ops(struct device *dev, + const struct dev_pm_ops *pm_ops); + +#define wcnss_wlan_get_drvdata(dev) dev_get_drvdata(dev) +#define wcnss_wlan_set_drvdata(dev, data) dev_set_drvdata((dev), (data)) + +#endif /* _WCNSS_WLAN_H_ */ diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h index f584aba78ca..647d6b09c98 100644 --- a/include/linux/workqueue.h +++ b/include/linux/workqueue.h @@ -346,6 +346,8 @@ alloc_ordered_workqueue(const char *name, unsigned int flags) extern void destroy_workqueue(struct workqueue_struct *wq); +extern bool workqueue_empty(struct workqueue_struct *wq); + extern int queue_work(struct workqueue_struct *wq, struct work_struct *work); extern int queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work); diff --git a/include/linux/wpce775x.h b/include/linux/wpce775x.h new file mode 100644 index 00000000000..1803122d1e7 --- /dev/null +++ b/include/linux/wpce775x.h @@ -0,0 +1,30 @@ +/* Quanta EC driver for the Winbond Embedded Controller + * + * Copyright (C) 2009 Quanta Computer 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 WPCE775X_DRV_H +#define WPCE775X_DRV_H + +#include + +struct i2c_client *wpce_get_i2c_client(void); +int wpce_smbus_write_word_data(u8 command, u16 value); +struct i2c_client *wpce_get_i2c_client(void); +void wpce_poweroff(void); +void wpce_restart(void); +int wpce_i2c_transfer(struct i2c_msg *msg); +int wpce_smbus_write_word_data(u8 command, u16 value); +int wpce_smbus_write_byte_data(u8 command, u8 value); + +#endif diff --git a/include/media/Kbuild b/include/media/Kbuild new file mode 100644 index 00000000000..1033517e31b --- /dev/null +++ b/include/media/Kbuild @@ -0,0 +1,4 @@ +header-y += tavarua.h + +header-y += msm_camera.h +header-y += msm_gemini.h diff --git a/include/media/msm_camera.h b/include/media/msm_camera.h new file mode 100644 index 00000000000..4544a676c88 --- /dev/null +++ b/include/media/msm_camera.h @@ -0,0 +1,798 @@ +#ifndef __LINUX_MSM_CAMERA_H +#define __LINUX_MSM_CAMERA_H + +#ifdef MSM_CAMERA_BIONIC +#include +#endif +#include +#include +#include +#ifdef MSM_CAMERA_GCC +#include +#else +#include +#endif + +#define MSM_CAM_IOCTL_MAGIC 'm' + +#define MSM_CAM_IOCTL_GET_SENSOR_INFO \ + _IOR(MSM_CAM_IOCTL_MAGIC, 1, struct msm_camsensor_info *) + +#define MSM_CAM_IOCTL_REGISTER_PMEM \ + _IOW(MSM_CAM_IOCTL_MAGIC, 2, struct msm_pmem_info *) + +#define MSM_CAM_IOCTL_UNREGISTER_PMEM \ + _IOW(MSM_CAM_IOCTL_MAGIC, 3, unsigned) + +#define MSM_CAM_IOCTL_CTRL_COMMAND \ + _IOW(MSM_CAM_IOCTL_MAGIC, 4, struct msm_ctrl_cmd *) + +#define MSM_CAM_IOCTL_CONFIG_VFE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 5, struct msm_camera_vfe_cfg_cmd *) + +#define MSM_CAM_IOCTL_GET_STATS \ + _IOR(MSM_CAM_IOCTL_MAGIC, 6, struct msm_camera_stats_event_ctrl *) + +#define MSM_CAM_IOCTL_GETFRAME \ + _IOR(MSM_CAM_IOCTL_MAGIC, 7, struct msm_camera_get_frame *) + +#define MSM_CAM_IOCTL_ENABLE_VFE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 8, struct camera_enable_cmd *) + +#define MSM_CAM_IOCTL_CTRL_CMD_DONE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 9, struct camera_cmd *) + +#define MSM_CAM_IOCTL_CONFIG_CMD \ + _IOW(MSM_CAM_IOCTL_MAGIC, 10, struct camera_cmd *) + +#define MSM_CAM_IOCTL_DISABLE_VFE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 11, struct camera_enable_cmd *) + +#define MSM_CAM_IOCTL_PAD_REG_RESET2 \ + _IOW(MSM_CAM_IOCTL_MAGIC, 12, struct camera_enable_cmd *) + +#define MSM_CAM_IOCTL_VFE_APPS_RESET \ + _IOW(MSM_CAM_IOCTL_MAGIC, 13, struct camera_enable_cmd *) + +#define MSM_CAM_IOCTL_RELEASE_FRAME_BUFFER \ + _IOW(MSM_CAM_IOCTL_MAGIC, 14, struct camera_enable_cmd *) + +#define MSM_CAM_IOCTL_RELEASE_STATS_BUFFER \ + _IOW(MSM_CAM_IOCTL_MAGIC, 15, struct msm_stats_buf *) + +#define MSM_CAM_IOCTL_AXI_CONFIG \ + _IOW(MSM_CAM_IOCTL_MAGIC, 16, struct msm_camera_vfe_cfg_cmd *) + +#define MSM_CAM_IOCTL_GET_PICTURE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 17, struct msm_frame *) + +#define MSM_CAM_IOCTL_SET_CROP \ + _IOW(MSM_CAM_IOCTL_MAGIC, 18, struct crop_info *) + +#define MSM_CAM_IOCTL_PICT_PP \ + _IOW(MSM_CAM_IOCTL_MAGIC, 19, uint8_t *) + +#define MSM_CAM_IOCTL_PICT_PP_DONE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 20, struct msm_snapshot_pp_status *) + +#define MSM_CAM_IOCTL_SENSOR_IO_CFG \ + _IOW(MSM_CAM_IOCTL_MAGIC, 21, struct sensor_cfg_data *) + +#define MSM_CAM_IOCTL_FLASH_LED_CFG \ + _IOW(MSM_CAM_IOCTL_MAGIC, 22, unsigned *) + +#define MSM_CAM_IOCTL_UNBLOCK_POLL_FRAME \ + _IO(MSM_CAM_IOCTL_MAGIC, 23) + +#define MSM_CAM_IOCTL_CTRL_COMMAND_2 \ + _IOW(MSM_CAM_IOCTL_MAGIC, 24, struct msm_ctrl_cmd *) + +#define MSM_CAM_IOCTL_AF_CTRL \ + _IOR(MSM_CAM_IOCTL_MAGIC, 25, struct msm_ctrl_cmt_t *) + +#define MSM_CAM_IOCTL_AF_CTRL_DONE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 26, struct msm_ctrl_cmt_t *) + +#define MSM_CAM_IOCTL_CONFIG_VPE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 27, struct msm_camera_vpe_cfg_cmd *) + +#define MSM_CAM_IOCTL_AXI_VPE_CONFIG \ + _IOW(MSM_CAM_IOCTL_MAGIC, 28, struct msm_camera_vpe_cfg_cmd *) + +#define MSM_CAM_IOCTL_STROBE_FLASH_CFG \ + _IOW(MSM_CAM_IOCTL_MAGIC, 29, uint32_t *) + +#define MSM_CAM_IOCTL_STROBE_FLASH_CHARGE \ + _IOW(MSM_CAM_IOCTL_MAGIC, 30, uint32_t *) + +#define MSM_CAM_IOCTL_STROBE_FLASH_RELEASE \ + _IO(MSM_CAM_IOCTL_MAGIC, 31) + +#define MSM_CAM_IOCTL_FLASH_CTRL \ + _IOW(MSM_CAM_IOCTL_MAGIC, 32, struct flash_ctrl_data *) + +#define MSM_CAM_IOCTL_ERROR_CONFIG \ + _IOW(MSM_CAM_IOCTL_MAGIC, 33, uint32_t *) + +#define MSM_CAM_IOCTL_ABORT_CAPTURE \ + _IO(MSM_CAM_IOCTL_MAGIC, 34) + +#define MSM_CAM_IOCTL_SET_FD_ROI \ + _IOW(MSM_CAM_IOCTL_MAGIC, 35, struct fd_roi_info *) + +#define MSM_CAM_IOCTL_GET_CAMERA_INFO \ + _IOR(MSM_CAM_IOCTL_MAGIC, 36, struct msm_camera_info *) + +#define MSM_CAM_IOCTL_UNBLOCK_POLL_PIC_FRAME \ + _IO(MSM_CAM_IOCTL_MAGIC, 37) + +#define MSM_CAM_IOCTL_RELEASE_PIC_BUFFER \ + _IOW(MSM_CAM_IOCTL_MAGIC, 38, struct camera_enable_cmd *) + +#define MSM_CAM_IOCTL_PUT_ST_FRAME \ + _IOW(MSM_CAM_IOCTL_MAGIC, 39, struct msm_camera_st_frame *) + +#define MSM_CAM_IOCTL_GET_CONFIG_INFO \ + _IOR(MSM_CAM_IOCTL_MAGIC, 40, struct msm_cam_config_dev_info *) + +#define MSM_CAMERA_LED_OFF 0 +#define MSM_CAMERA_LED_LOW 1 +#define MSM_CAMERA_LED_HIGH 2 + +#define MSM_CAMERA_STROBE_FLASH_NONE 0 +#define MSM_CAMERA_STROBE_FLASH_XENON 1 + +#define MSM_MAX_CAMERA_SENSORS 5 +#define MAX_SENSOR_NAME 32 + +#define MSM_MAX_CAMERA_CONFIGS 2 + +#define PP_SNAP 0x01 +#define PP_RAW_SNAP ((0x01)<<1) +#define PP_PREV ((0x01)<<2) +#define PP_MASK (PP_SNAP|PP_RAW_SNAP|PP_PREV) + +#define MSM_CAM_CTRL_CMD_DONE 0 +#define MSM_CAM_SENSOR_VFE_CMD 1 + +/***************************************************** + * structure + *****************************************************/ + +/* define five type of structures for userspace <==> kernel + * space communication: + * command 1 - 2 are from userspace ==> kernel + * command 3 - 4 are from kernel ==> userspace + * + * 1. control command: control command(from control thread), + * control status (from config thread); + */ +struct msm_ctrl_cmd { + uint16_t type; + uint16_t length; + void *value; + uint16_t status; + uint32_t timeout_ms; + int resp_fd; /* FIXME: to be used by the kernel, pass-through for now */ + int vnode_id; /* video dev id. Can we overload resp_fd? */ + uint32_t stream_type; /* used to pass value to qcamera server */ +}; + +struct msm_cam_evt_msg { + unsigned short type; /* 1 == event (RPC), 0 == message (adsp) */ + unsigned short msg_id; + unsigned int len; /* size in, number of bytes out */ + uint32_t frame_id; + void *data; +}; + +struct msm_isp_stats_event_ctrl { + unsigned short resptype; + union { + struct msm_cam_evt_msg isp_msg; + struct msm_ctrl_cmd ctrl; + } isp_data; +}; + +#define MSM_CAM_RESP_CTRL 0 +#define MSM_CAM_RESP_STAT_EVT_MSG 1 +#define MSM_CAM_RESP_STEREO_OP_1 2 +#define MSM_CAM_RESP_STEREO_OP_2 3 +#define MSM_CAM_RESP_V4L2 4 +#define MSM_CAM_RESP_MAX 5 + +/* driver event types */ +#define MSM_CAM_EVT_HISTOGRAM_NOTIFY (V4L2_EVENT_PRIVATE_START+1) +#define MSM_CAM_EVT_STREAMING_NOTIFY (V4L2_EVENT_PRIVATE_START+2) +#define MSM_CAM_EVT_ASYNC_CMD_NOTIFY (V4L2_EVENT_PRIVATE_START+3) +#define MSM_CAM_EVT_CNT_MAX 3 + +struct msm_event_histogram { + int32_t fd; + uint32_t size; + uint32_t offset; +}; +struct msm_event_async_cmd_status { + uint32_t cmd; + int32_t status; +}; +struct msm_event_streaming_status { + uint32_t ext_mode; + int32_t streamon; + int32_t status; +}; +struct msm_event_payload { + uint32_t type; + union { + struct msm_event_histogram hist; + struct msm_event_async_cmd_status cmd_status; + struct msm_event_streaming_status stream_status; + }; +}; +/* this one is used to send ctrl/status up to config thread */ +struct msm_stats_event_ctrl { + /* 0 - ctrl_cmd from control thread, + * 1 - stats/event kernel, + * 2 - V4L control or read request */ + int resptype; + int timeout_ms; + struct msm_ctrl_cmd ctrl_cmd; + /* struct vfe_event_t stats_event; */ + struct msm_cam_evt_msg stats_event; +}; + +/* 2. config command: config command(from config thread); */ +struct msm_camera_cfg_cmd { + /* what to config: + * 1 - sensor config, 2 - vfe config */ + uint16_t cfg_type; + + /* sensor config type */ + uint16_t cmd_type; + uint16_t queue; + uint16_t length; + void *value; +}; + +#define CMD_GENERAL 0 +#define CMD_AXI_CFG_OUT1 1 +#define CMD_AXI_CFG_SNAP_O1_AND_O2 2 +#define CMD_AXI_CFG_OUT2 3 +#define CMD_PICT_T_AXI_CFG 4 +#define CMD_PICT_M_AXI_CFG 5 +#define CMD_RAW_PICT_AXI_CFG 6 + +#define CMD_FRAME_BUF_RELEASE 7 +#define CMD_PREV_BUF_CFG 8 +#define CMD_SNAP_BUF_RELEASE 9 +#define CMD_SNAP_BUF_CFG 10 +#define CMD_STATS_DISABLE 11 +#define CMD_STATS_AEC_AWB_ENABLE 12 +#define CMD_STATS_AF_ENABLE 13 +#define CMD_STATS_AEC_ENABLE 14 +#define CMD_STATS_AWB_ENABLE 15 +#define CMD_STATS_ENABLE 16 + +#define CMD_STATS_AXI_CFG 17 +#define CMD_STATS_AEC_AXI_CFG 18 +#define CMD_STATS_AF_AXI_CFG 19 +#define CMD_STATS_AWB_AXI_CFG 20 +#define CMD_STATS_RS_AXI_CFG 21 +#define CMD_STATS_CS_AXI_CFG 22 +#define CMD_STATS_IHIST_AXI_CFG 23 +#define CMD_STATS_SKIN_AXI_CFG 24 + +#define CMD_STATS_BUF_RELEASE 25 +#define CMD_STATS_AEC_BUF_RELEASE 26 +#define CMD_STATS_AF_BUF_RELEASE 27 +#define CMD_STATS_AWB_BUF_RELEASE 28 +#define CMD_STATS_RS_BUF_RELEASE 29 +#define CMD_STATS_CS_BUF_RELEASE 30 +#define CMD_STATS_IHIST_BUF_RELEASE 31 +#define CMD_STATS_SKIN_BUF_RELEASE 32 + +#define UPDATE_STATS_INVALID 33 +#define CMD_AXI_CFG_SNAP_GEMINI 34 +#define CMD_AXI_CFG_SNAP 35 +#define CMD_AXI_CFG_PREVIEW 36 +#define CMD_AXI_CFG_VIDEO 37 + +#define CMD_STATS_IHIST_ENABLE 38 +#define CMD_STATS_RS_ENABLE 39 +#define CMD_STATS_CS_ENABLE 40 +#define CMD_VPE 41 +#define CMD_AXI_CFG_VPE 42 +#define CMD_AXI_CFG_ZSL 43 +#define CMD_AXI_CFG_SNAP_VPE 44 +#define CMD_AXI_CFG_SNAP_THUMB_VPE 45 + +/* vfe config command: config command(from config thread)*/ +struct msm_vfe_cfg_cmd { + int cmd_type; + uint16_t length; + void *value; +}; + +struct msm_vpe_cfg_cmd { + int cmd_type; + uint16_t length; + void *value; +}; + +#define MAX_CAMERA_ENABLE_NAME_LEN 32 +struct camera_enable_cmd { + char name[MAX_CAMERA_ENABLE_NAME_LEN]; +}; + +#define MSM_PMEM_OUTPUT1 0 +#define MSM_PMEM_OUTPUT2 1 +#define MSM_PMEM_OUTPUT1_OUTPUT2 2 +#define MSM_PMEM_THUMBNAIL 3 +#define MSM_PMEM_MAINIMG 4 +#define MSM_PMEM_RAW_MAINIMG 5 +#define MSM_PMEM_AEC_AWB 6 +#define MSM_PMEM_AF 7 +#define MSM_PMEM_AEC 8 +#define MSM_PMEM_AWB 9 +#define MSM_PMEM_RS 10 +#define MSM_PMEM_CS 11 +#define MSM_PMEM_IHIST 12 +#define MSM_PMEM_SKIN 13 +#define MSM_PMEM_VIDEO 14 +#define MSM_PMEM_PREVIEW 15 +#define MSM_PMEM_VIDEO_VPE 16 +#define MSM_PMEM_C2D 17 +#define MSM_PMEM_MAINIMG_VPE 18 +#define MSM_PMEM_THUMBNAIL_VPE 19 +#define MSM_PMEM_MAX 20 + +#define STAT_AEAW 0 +#define STAT_AEC 1 +#define STAT_AF 2 +#define STAT_AWB 3 +#define STAT_RS 4 +#define STAT_CS 5 +#define STAT_IHIST 6 +#define STAT_SKIN 7 +#define STAT_MAX 8 + +#define FRAME_PREVIEW_OUTPUT1 0 +#define FRAME_PREVIEW_OUTPUT2 1 +#define FRAME_SNAPSHOT 2 +#define FRAME_THUMBNAIL 3 +#define FRAME_RAW_SNAPSHOT 4 +#define FRAME_MAX 5 + +struct msm_pmem_info { + int type; + int fd; + void *vaddr; + uint32_t offset; + uint32_t len; + uint32_t y_off; + uint32_t cbcr_off; + uint8_t active; +}; + +struct outputCfg { + uint32_t height; + uint32_t width; + + uint32_t window_height_firstline; + uint32_t window_height_lastline; +}; + +#define OUTPUT_1 0 +#define OUTPUT_2 1 +#define OUTPUT_1_AND_2 2 /* snapshot only */ +#define OUTPUT_1_AND_3 3 /* video */ +#define CAMIF_TO_AXI_VIA_OUTPUT_2 4 +#define OUTPUT_1_AND_CAMIF_TO_AXI_VIA_OUTPUT_2 5 +#define OUTPUT_2_AND_CAMIF_TO_AXI_VIA_OUTPUT_1 6 +#define OUTPUT_1_2_AND_3 7 +#define LAST_AXI_OUTPUT_MODE_ENUM = OUTPUT_1_2_AND_3 7 + +#define MSM_FRAME_PREV_1 0 +#define MSM_FRAME_PREV_2 1 +#define MSM_FRAME_ENC 2 + +#define OUTPUT_TYPE_P (1<<0) +#define OUTPUT_TYPE_T (1<<1) +#define OUTPUT_TYPE_S (1<<2) +#define OUTPUT_TYPE_V (1<<3) +#define OUTPUT_TYPE_L (1<<4) +#define OUTPUT_TYPE_ST_L (1<<5) +#define OUTPUT_TYPE_ST_R (1<<6) +#define OUTPUT_TYPE_ST_D (1<<7) + +struct fd_roi_info { + void *info; + int info_len; +}; + +struct msm_frame { + struct timespec ts; + int path; + int type; + unsigned long buffer; + uint32_t phy_offset; + uint32_t y_off; + uint32_t cbcr_off; + int fd; + + void *cropinfo; + int croplen; + uint32_t error_code; + struct fd_roi_info roi_info; + uint32_t frame_id; + int stcam_quality_ind; + uint32_t stcam_conv_value; +}; + +enum msm_st_frame_packing { + SIDE_BY_SIDE_HALF, + SIDE_BY_SIDE_FULL, + TOP_DOWN_HALF, + TOP_DOWN_FULL, +}; + +struct msm_st_crop { + uint32_t in_w; + uint32_t in_h; + uint32_t out_w; + uint32_t out_h; +}; + +struct msm_st_half { + uint32_t buf_y_off; + uint32_t buf_cbcr_off; + uint32_t buf_y_stride; + uint32_t buf_cbcr_stride; + uint32_t pix_x_off; + uint32_t pix_y_off; + struct msm_st_crop stCropInfo; +}; + +struct msm_st_frame { + struct msm_frame buf_info; + int type; + enum msm_st_frame_packing packing; + struct msm_st_half L; + struct msm_st_half R; + int frame_id; +}; + +#define MSM_CAMERA_ERR_MASK (0xFFFFFFFF & 1) + +struct stats_buff { + unsigned long buff; + int fd; +}; + +struct msm_stats_buf { + struct stats_buff aec; + struct stats_buff awb; + struct stats_buff af; + struct stats_buff ihist; + struct stats_buff rs; + struct stats_buff cs; + struct stats_buff skin; + int type; + uint32_t status_bits; + unsigned long buffer; + int fd; + uint32_t frame_id; +}; +#define MSM_V4L2_EXT_CAPTURE_MODE_DEFAULT 0 +/* video capture mode in VIDIOC_S_PARM */ +#define MSM_V4L2_EXT_CAPTURE_MODE_PREVIEW \ + (MSM_V4L2_EXT_CAPTURE_MODE_DEFAULT+1) +/* extendedmode for video recording in VIDIOC_S_PARM */ +#define MSM_V4L2_EXT_CAPTURE_MODE_VIDEO \ + (MSM_V4L2_EXT_CAPTURE_MODE_DEFAULT+2) +/* extendedmode for the full size main image in VIDIOC_S_PARM */ +#define MSM_V4L2_EXT_CAPTURE_MODE_MAIN (MSM_V4L2_EXT_CAPTURE_MODE_DEFAULT+3) +/* extendedmode for the thumb nail image in VIDIOC_S_PARM */ +#define MSM_V4L2_EXT_CAPTURE_MODE_THUMBNAIL \ + (MSM_V4L2_EXT_CAPTURE_MODE_DEFAULT+4) +#define MSM_V4L2_EXT_CAPTURE_MODE_RAW \ + (MSM_V4L2_EXT_CAPTURE_MODE_DEFAULT+5) +#define MSM_V4L2_EXT_CAPTURE_MODE_MAX (MSM_V4L2_EXT_CAPTURE_MODE_DEFAULT+6) + + +#define MSM_V4L2_PID_MOTION_ISO V4L2_CID_PRIVATE_BASE +#define MSM_V4L2_PID_EFFECT (V4L2_CID_PRIVATE_BASE+1) +#define MSM_V4L2_PID_HJR (V4L2_CID_PRIVATE_BASE+2) +#define MSM_V4L2_PID_LED_MODE (V4L2_CID_PRIVATE_BASE+3) +#define MSM_V4L2_PID_PREP_SNAPSHOT (V4L2_CID_PRIVATE_BASE+4) +#define MSM_V4L2_PID_EXP_METERING (V4L2_CID_PRIVATE_BASE+5) +#define MSM_V4L2_PID_ISO (V4L2_CID_PRIVATE_BASE+6) +#define MSM_V4L2_PID_CAM_MODE (V4L2_CID_PRIVATE_BASE+7) +#define MSM_V4L2_PID_LUMA_ADAPTATION (V4L2_CID_PRIVATE_BASE+8) +#define MSM_V4L2_PID_BEST_SHOT (V4L2_CID_PRIVATE_BASE+9) +#define MSM_V4L2_PID_FOCUS_MODE (V4L2_CID_PRIVATE_BASE+10) +#define MSM_V4L2_PID_BL_DETECTION (V4L2_CID_PRIVATE_BASE+11) +#define MSM_V4L2_PID_SNOW_DETECTION (V4L2_CID_PRIVATE_BASE+12) +#define MSM_V4L2_PID_CTRL_CMD (V4L2_CID_PRIVATE_BASE+13) +#define MSM_V4L2_PID_EVT_SUB_INFO (V4L2_CID_PRIVATE_BASE+14) +#define MSM_V4L2_PID_STROBE_FLASH (V4L2_CID_PRIVATE_BASE+15) +#define MSM_V4L2_PID_MAX MSM_V4L2_PID_STROBE_FLASH + +/* camera operation mode for video recording - two frame output queues */ +#define MSM_V4L2_CAM_OP_DEFAULT 0 +/* camera operation mode for video recording - two frame output queues */ +#define MSM_V4L2_CAM_OP_PREVIEW (MSM_V4L2_CAM_OP_DEFAULT+1) +/* camera operation mode for video recording - two frame output queues */ +#define MSM_V4L2_CAM_OP_VIDEO (MSM_V4L2_CAM_OP_DEFAULT+2) +/* camera operation mode for standard shapshot - two frame output queues */ +#define MSM_V4L2_CAM_OP_CAPTURE (MSM_V4L2_CAM_OP_DEFAULT+3) +/* camera operation mode for zsl shapshot - three output queues */ +#define MSM_V4L2_CAM_OP_ZSL (MSM_V4L2_CAM_OP_DEFAULT+4) +/* camera operation mode for raw snapshot - one frame output queue */ +#define MSM_V4L2_CAM_OP_RAW (MSM_V4L2_CAM_OP_DEFAULT+5) + +#define MSM_V4L2_VID_CAP_TYPE 0 +#define MSM_V4L2_STREAM_ON 1 +#define MSM_V4L2_STREAM_OFF 2 +#define MSM_V4L2_SNAPSHOT 3 +#define MSM_V4L2_QUERY_CTRL 4 +#define MSM_V4L2_GET_CTRL 5 +#define MSM_V4L2_SET_CTRL 6 +#define MSM_V4L2_QUERY 7 +#define MSM_V4L2_GET_CROP 8 +#define MSM_V4L2_SET_CROP 9 +#define MSM_V4L2_OPEN 10 +#define MSM_V4L2_CLOSE 11 +#define MSM_V4L2_SET_CTRL_CMD 12 +#define MSM_V4L2_EVT_SUB_MASK 13 +#define MSM_V4L2_MAX 14 +#define V4L2_CAMERA_EXIT 43 + +struct crop_info { + void *info; + int len; +}; + +struct msm_postproc { + int ftnum; + struct msm_frame fthumnail; + int fmnum; + struct msm_frame fmain; +}; + +struct msm_snapshot_pp_status { + void *status; +}; + +#define CFG_SET_MODE 0 +#define CFG_SET_EFFECT 1 +#define CFG_START 2 +#define CFG_PWR_UP 3 +#define CFG_PWR_DOWN 4 +#define CFG_WRITE_EXPOSURE_GAIN 5 +#define CFG_SET_DEFAULT_FOCUS 6 +#define CFG_MOVE_FOCUS 7 +#define CFG_REGISTER_TO_REAL_GAIN 8 +#define CFG_REAL_TO_REGISTER_GAIN 9 +#define CFG_SET_FPS 10 +#define CFG_SET_PICT_FPS 11 +#define CFG_SET_BRIGHTNESS 12 +#define CFG_SET_CONTRAST 13 +#define CFG_SET_ZOOM 14 +#define CFG_SET_EXPOSURE_MODE 15 +#define CFG_SET_WB 16 +#define CFG_SET_ANTIBANDING 17 +#define CFG_SET_EXP_GAIN 18 +#define CFG_SET_PICT_EXP_GAIN 19 +#define CFG_SET_LENS_SHADING 20 +#define CFG_GET_PICT_FPS 21 +#define CFG_GET_PREV_L_PF 22 +#define CFG_GET_PREV_P_PL 23 +#define CFG_GET_PICT_L_PF 24 +#define CFG_GET_PICT_P_PL 25 +#define CFG_GET_AF_MAX_STEPS 26 +#define CFG_GET_PICT_MAX_EXP_LC 27 +#define CFG_SEND_WB_INFO 28 +#define CFG_SENSOR_INIT 29 +#define CFG_GET_3D_CALI_DATA 30 +#define CFG_GET_CALIB_DATA 31 +#define CFG_MAX 32 + + +#define MOVE_NEAR 0 +#define MOVE_FAR 1 + +#define SENSOR_PREVIEW_MODE 0 +#define SENSOR_SNAPSHOT_MODE 1 +#define SENSOR_RAW_SNAPSHOT_MODE 2 +#define SENSOR_HFR_60FPS_MODE 3 +#define SENSOR_HFR_90FPS_MODE 4 +#define SENSOR_HFR_120FPS_MODE 5 + +#define SENSOR_QTR_SIZE 0 +#define SENSOR_FULL_SIZE 1 +#define SENSOR_QVGA_SIZE 2 +#define SENSOR_INVALID_SIZE 3 + +#define CAMERA_EFFECT_OFF 0 +#define CAMERA_EFFECT_MONO 1 +#define CAMERA_EFFECT_NEGATIVE 2 +#define CAMERA_EFFECT_SOLARIZE 3 +#define CAMERA_EFFECT_SEPIA 4 +#define CAMERA_EFFECT_POSTERIZE 5 +#define CAMERA_EFFECT_WHITEBOARD 6 +#define CAMERA_EFFECT_BLACKBOARD 7 +#define CAMERA_EFFECT_AQUA 8 +#define CAMERA_EFFECT_MAX 9 + +struct sensor_pict_fps { + uint16_t prevfps; + uint16_t pictfps; +}; + +struct exp_gain_cfg { + uint16_t gain; + uint32_t line; +}; + +struct focus_cfg { + int32_t steps; + int dir; +}; + +struct fps_cfg { + uint16_t f_mult; + uint16_t fps_div; + uint32_t pict_fps_div; +}; +struct wb_info_cfg { + uint16_t red_gain; + uint16_t green_gain; + uint16_t blue_gain; +}; +struct sensor_3d_exp_cfg { + uint16_t gain; + uint32_t line; + uint16_t r_gain; + uint16_t b_gain; + uint16_t gr_gain; + uint16_t gb_gain; + uint16_t gain_adjust; +}; +struct sensor_3d_cali_data_t{ + unsigned char left_p_matrix[3][4][8]; + unsigned char right_p_matrix[3][4][8]; + unsigned char square_len[8]; + unsigned char focal_len[8]; + unsigned char pixel_pitch[8]; + uint16_t left_r; + uint16_t left_b; + uint16_t left_gb; + uint16_t left_af_far; + uint16_t left_af_mid; + uint16_t left_af_short; + uint16_t left_af_5um; + uint16_t left_af_50up; + uint16_t left_af_50down; + uint16_t right_r; + uint16_t right_b; + uint16_t right_gb; + uint16_t right_af_far; + uint16_t right_af_mid; + uint16_t right_af_short; + uint16_t right_af_5um; + uint16_t right_af_50up; + uint16_t right_af_50down; +}; +struct sensor_init_cfg { + uint8_t prev_res; + uint8_t pict_res; +}; + +struct sensor_calib_data { + /* Color Related Measurements */ + uint16_t r_over_g; + uint16_t b_over_g; + uint16_t gr_over_gb; + + /* Lens Related Measurements */ + uint16_t macro_2_inf; + uint16_t inf_2_macro; + uint16_t stroke_amt; + uint16_t af_pos_1m; + uint16_t af_pos_inf; +}; + +struct sensor_cfg_data { + int cfgtype; + int mode; + int rs; + uint8_t max_steps; + + union { + int8_t effect; + uint8_t lens_shading; + uint16_t prevl_pf; + uint16_t prevp_pl; + uint16_t pictl_pf; + uint16_t pictp_pl; + uint32_t pict_max_exp_lc; + uint16_t p_fps; + struct sensor_init_cfg init_info; + struct sensor_pict_fps gfps; + struct exp_gain_cfg exp_gain; + struct focus_cfg focus; + struct fps_cfg fps; + struct wb_info_cfg wb_info; + struct sensor_3d_exp_cfg sensor_3d_exp; + struct sensor_calib_data calib_info; + } cfg; +}; + +struct sensor_large_data { + int cfgtype; + union { + struct sensor_3d_cali_data_t sensor_3d_cali_data; + } data; +}; + +enum sensor_type_t { + BAYER, + YUV, + JPEG_SOC, +}; + +enum flash_type { + LED_FLASH, + STROBE_FLASH, +}; + +enum strobe_flash_ctrl_type { + STROBE_FLASH_CTRL_INIT, + STROBE_FLASH_CTRL_CHARGE, + STROBE_FLASH_CTRL_RELEASE +}; + +struct strobe_flash_ctrl_data { + enum strobe_flash_ctrl_type type; + int charge_en; +}; + +struct msm_camera_info { + int num_cameras; + uint8_t has_3d_support[MSM_MAX_CAMERA_SENSORS]; + uint8_t is_internal_cam[MSM_MAX_CAMERA_SENSORS]; + uint32_t s_mount_angle[MSM_MAX_CAMERA_SENSORS]; + const char *video_dev_name[MSM_MAX_CAMERA_SENSORS]; + enum sensor_type_t sensor_type[MSM_MAX_CAMERA_SENSORS]; + +}; + +struct msm_cam_config_dev_info { + int num_config_nodes; + const char *config_dev_name[MSM_MAX_CAMERA_CONFIGS]; +}; + +struct flash_ctrl_data { + int flashtype; + union { + int led_state; + struct strobe_flash_ctrl_data strobe_ctrl; + } ctrl_data; +}; + +#define GET_NAME 0 +#define GET_PREVIEW_LINE_PER_FRAME 1 +#define GET_PREVIEW_PIXELS_PER_LINE 2 +#define GET_SNAPSHOT_LINE_PER_FRAME 3 +#define GET_SNAPSHOT_PIXELS_PER_LINE 4 +#define GET_SNAPSHOT_FPS 5 +#define GET_SNAPSHOT_MAX_EP_LINE_CNT 6 + +struct msm_camsensor_info { + char name[MAX_SENSOR_NAME]; + uint8_t flash_enabled; + int8_t total_steps; + uint8_t support_3d; +}; +#endif /* __LINUX_MSM_CAMERA_H */ diff --git a/include/media/msm_gemini.h b/include/media/msm_gemini.h new file mode 100644 index 00000000000..0167335d961 --- /dev/null +++ b/include/media/msm_gemini.h @@ -0,0 +1,114 @@ +#ifndef __LINUX_MSM_GEMINI_H +#define __LINUX_MSM_GEMINI_H + +#include +#include + +#define MSM_GMN_IOCTL_MAGIC 'g' + +#define MSM_GMN_IOCTL_GET_HW_VERSION \ + _IOW(MSM_GMN_IOCTL_MAGIC, 1, struct msm_gemini_hw_cmd *) + +#define MSM_GMN_IOCTL_RESET \ + _IOW(MSM_GMN_IOCTL_MAGIC, 2, struct msm_gemini_ctrl_cmd *) + +#define MSM_GMN_IOCTL_STOP \ + _IOW(MSM_GMN_IOCTL_MAGIC, 3, struct msm_gemini_hw_cmds *) + +#define MSM_GMN_IOCTL_START \ + _IOW(MSM_GMN_IOCTL_MAGIC, 4, struct msm_gemini_hw_cmds *) + +#define MSM_GMN_IOCTL_INPUT_BUF_ENQUEUE \ + _IOW(MSM_GMN_IOCTL_MAGIC, 5, struct msm_gemini_buf *) + +#define MSM_GMN_IOCTL_INPUT_GET \ + _IOW(MSM_GMN_IOCTL_MAGIC, 6, struct msm_gemini_buf *) + +#define MSM_GMN_IOCTL_INPUT_GET_UNBLOCK \ + _IOW(MSM_GMN_IOCTL_MAGIC, 7, int) + +#define MSM_GMN_IOCTL_OUTPUT_BUF_ENQUEUE \ + _IOW(MSM_GMN_IOCTL_MAGIC, 8, struct msm_gemini_buf *) + +#define MSM_GMN_IOCTL_OUTPUT_GET \ + _IOW(MSM_GMN_IOCTL_MAGIC, 9, struct msm_gemini_buf *) + +#define MSM_GMN_IOCTL_OUTPUT_GET_UNBLOCK \ + _IOW(MSM_GMN_IOCTL_MAGIC, 10, int) + +#define MSM_GMN_IOCTL_EVT_GET \ + _IOW(MSM_GMN_IOCTL_MAGIC, 11, struct msm_gemini_ctrl_cmd *) + +#define MSM_GMN_IOCTL_EVT_GET_UNBLOCK \ + _IOW(MSM_GMN_IOCTL_MAGIC, 12, int) + +#define MSM_GMN_IOCTL_HW_CMD \ + _IOW(MSM_GMN_IOCTL_MAGIC, 13, struct msm_gemini_hw_cmd *) + +#define MSM_GMN_IOCTL_HW_CMDS \ + _IOW(MSM_GMN_IOCTL_MAGIC, 14, struct msm_gemini_hw_cmds *) + +#define MSM_GMN_IOCTL_TEST_DUMP_REGION \ + _IOW(MSM_GMN_IOCTL_MAGIC, 15, unsigned long) + +#define MSM_GEMINI_MODE_REALTIME_ENCODE 0 +#define MSM_GEMINI_MODE_OFFLINE_ENCODE 1 +#define MSM_GEMINI_MODE_REALTIME_ROTATION 2 +#define MSM_GEMINI_MODE_OFFLINE_ROTATION 3 +struct msm_gemini_ctrl_cmd { + uint32_t type; + uint32_t len; + void *value; +}; + +#define MSM_GEMINI_EVT_RESET 0 +#define MSM_GEMINI_EVT_FRAMEDONE 1 +#define MSM_GEMINI_EVT_ERR 2 + +struct msm_gemini_buf { + uint32_t type; + int fd; + + void *vaddr; + + uint32_t y_off; + uint32_t y_len; + uint32_t framedone_len; + + uint32_t cbcr_off; + uint32_t cbcr_len; + + uint32_t num_of_mcu_rows; + uint32_t offset; +}; + +#define MSM_GEMINI_HW_CMD_TYPE_READ 0 +#define MSM_GEMINI_HW_CMD_TYPE_WRITE 1 +#define MSM_GEMINI_HW_CMD_TYPE_WRITE_OR 2 +#define MSM_GEMINI_HW_CMD_TYPE_UWAIT 3 +#define MSM_GEMINI_HW_CMD_TYPE_MWAIT 4 +#define MSM_GEMINI_HW_CMD_TYPE_MDELAY 5 +#define MSM_GEMINI_HW_CMD_TYPE_UDELAY 6 +struct msm_gemini_hw_cmd { + + uint32_t type:4; + + /* n microseconds of timeout for WAIT */ + /* n microseconds of time for DELAY */ + /* repeat n times for READ/WRITE */ + /* max is 0xFFF, 4095 */ + uint32_t n:12; + uint32_t offset:16; + uint32_t mask; + union { + uint32_t data; /* for single READ/WRITE/WAIT, n = 1 */ + uint32_t *pdata; /* for multiple READ/WRITE/WAIT, n > 1 */ + }; +}; + +struct msm_gemini_hw_cmds { + uint32_t m; /* number of elements in the hw_cmd array */ + struct msm_gemini_hw_cmd hw_cmd[1]; +}; + +#endif /* __LINUX_MSM_GEMINI_H */ diff --git a/include/media/radio-iris.h b/include/media/radio-iris.h new file mode 100644 index 00000000000..9bb2d897358 --- /dev/null +++ b/include/media/radio-iris.h @@ -0,0 +1,558 @@ +/* + * + * Copyright (c) 2011 Code Aurora Forum. All rights reserved. + * + * This file is based on include/net/bluetooth/hci_core.h + * + * Written 2000,2001 by Maxim Krasnyansky + * + * 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; + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY + * CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + * COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + * SOFTWARE IS DISCLAIMED. + */ + +#ifndef __RADIO_HCI_CORE_H +#define __RADIO_HCI_CORE_H + +#include +#include +#include +#include + +/* ---- HCI Packet structures ---- */ +#define RADIO_HCI_COMMAND_HDR_SIZE sizeof(struct radio_hci_command_hdr) +#define RADIO_HCI_EVENT_HDR_SIZE sizeof(struct radio_hci_event_hdr) + +/* HCI data types */ +#define RADIO_HCI_COMMAND_PKT 0x11 +#define RADIO_HCI_EVENT_PKT 0x14 + +/* HCI timeouts */ +#define RADIO_HCI_TIMEOUT (10000) /* 10 seconds */ + +#define TUNE_PARAM 16 +struct radio_hci_command_hdr { + __le16 opcode; /* OCF & OGF */ + __u8 plen; +} __packed; + +struct radio_hci_event_hdr { + __u8 evt; + __u8 plen; +} __packed; + +struct radio_hci_dev { + char name[8]; + unsigned long flags; + __u16 id; + __u8 bus; + __u8 dev_type; + __u8 dev_name[248]; + __u8 dev_class[3]; + __u8 features[8]; + __u8 commands[64]; + + unsigned int data_block_len; + unsigned long cmd_last_tx; + + struct sk_buff *sent_cmd; + + __u32 req_status; + __u32 req_result; + atomic_t cmd_cnt; + + struct tasklet_struct cmd_task; + struct tasklet_struct rx_task; + struct tasklet_struct tx_task; + + struct sk_buff_head rx_q; + struct sk_buff_head raw_q; + struct sk_buff_head cmd_q; + + struct mutex req_lock; + wait_queue_head_t req_wait_q; + + int (*open)(struct radio_hci_dev *hdev); + int (*close)(struct radio_hci_dev *hdev); + int (*flush)(struct radio_hci_dev *hdev); + int (*send)(struct sk_buff *skb); + void (*destruct)(struct radio_hci_dev *hdev); + void (*notify)(struct radio_hci_dev *hdev, unsigned int evt); +}; + +int radio_hci_register_dev(struct radio_hci_dev *hdev); +int radio_hci_unregister_dev(struct radio_hci_dev *hdev); +int radio_hci_recv_frame(struct sk_buff *skb); +int radio_hci_send_cmd(struct radio_hci_dev *hdev, __u16 opcode, __u32 plen, + void *param); +void radio_hci_event_packet(struct radio_hci_dev *hdev, struct sk_buff *skb); + +/* Opcode OCF */ +/* HCI recv control commands opcode */ +#define HCI_OCF_FM_ENABLE_RECV_REQ 0x0001 +#define HCI_OCF_FM_DISABLE_RECV_REQ 0x0002 +#define HCI_OCF_FM_GET_RECV_CONF_REQ 0x0003 +#define HCI_OCF_FM_SET_RECV_CONF_REQ 0x0004 +#define HCI_OCF_FM_SET_MUTE_MODE_REQ 0x0005 +#define HCI_OCF_FM_SET_STEREO_MODE_REQ 0x0006 +#define HCI_OCF_FM_SET_ANTENNA 0x0007 +#define HCI_OCF_FM_SET_SIGNAL_THRESHOLD 0x0008 +#define HCI_OCF_FM_GET_SIGNAL_THRESHOLD 0x0009 +#define HCI_OCF_FM_GET_STATION_PARAM_REQ 0x000A +#define HCI_OCF_FM_GET_PROGRAM_SERVICE_REQ 0x000B +#define HCI_OCF_FM_GET_RADIO_TEXT_REQ 0x000C +#define HCI_OCF_FM_GET_AF_LIST_REQ 0x000D +#define HCI_OCF_FM_SEARCH_STATIONS 0x000E +#define HCI_OCF_FM_SEARCH_RDS_STATIONS 0x000F +#define HCI_OCF_FM_SEARCH_STATIONS_LIST 0x0010 +#define HCI_OCF_FM_CANCEL_SEARCH 0x0011 +#define HCI_OCF_FM_RDS_GRP 0x0012 +#define HCI_OCF_FM_RDS_GRP_PROCESS 0x0013 +#define HCI_OCF_FM_EN_WAN_AVD_CTRL 0x0014 +#define HCI_OCF_FM_EN_NOTCH_CTRL 0x0015 + +/* HCI common control commands opcode */ +#define HCI_OCF_FM_TUNE_STATION_REQ 0x0001 +#define HCI_OCF_FM_DEFAULT_DATA_READ 0x0002 +#define HCI_OCF_FM_DEFAULT_DATA_WRITE 0x0003 +#define HCI_OCF_FM_RESET 0x0004 +#define HCI_OCF_FM_GET_FEATURE_LIST 0x0005 +#define HCI_OCF_FM_DO_CALIBRATION 0x0006 + +/*HCI Status parameters commands*/ +#define HCI_OCF_FM_READ_GRP_COUNTERS 0x0001 + +/*HCI Diagnostic commands*/ +#define HCI_OCF_FM_PEEK_DATA 0x0002 +#define HCI_OCF_FM_POKE_DATA 0x0003 +#define HCI_OCF_FM_SSBI_PEEK_REG 0x0004 +#define HCI_OCF_FM_SSBI_POKE_REG 0x0005 +#define HCI_OCF_FM_STATION_DBG_PARAM 0x0007 + +/* Opcode OGF */ +#define HCI_OGF_FM_RECV_CTRL_CMD_REQ 0x0013 +#define HCI_OGF_FM_COMMON_CTRL_CMD_REQ 0x0015 +#define HCI_OGF_FM_STATUS_PARAMETERS_CMD_REQ 0x0016 +#define HCI_OGF_FM_TEST_CMD_REQ 0x0017 +#define HCI_OGF_FM_DIAGNOSTIC_CMD_REQ 0x003F + +/* Command opcode pack/unpack */ +#define hci_opcode_pack(ogf, ocf) (__u16) ((ocf & 0x03ff)|(ogf << 10)) +#define hci_opcode_ogf(op) (op >> 10) +#define hci_opcode_ocf(op) (op & 0x03ff) +#define hci_recv_ctrl_cmd_op_pack(ocf) \ + (__u16) hci_opcode_pack(HCI_OGF_FM_RECV_CTRL_CMD_REQ, ocf) +#define hci_common_cmd_op_pack(ocf) \ + (__u16) hci_opcode_pack(HCI_OGF_FM_COMMON_CTRL_CMD_REQ, ocf) +#define hci_status_param_op_pack(ocf) \ + (__u16) hci_opcode_pack(HCI_OGF_FM_STATUS_PARAMETERS_CMD_REQ, ocf) +#define hci_diagnostic_cmd_op_pack(ocf) \ + (__u16) hci_opcode_pack(HCI_OGF_FM_DIAGNOSTIC_CMD_REQ, ocf) + + +/* HCI commands with no arguments*/ +#define HCI_FM_ENABLE_RECV_CMD 1 +#define HCI_FM_DISABLE_RECV_CMD 2 +#define HCI_FM_GET_RECV_CONF_CMD 3 +#define HCI_FM_GET_STATION_PARAM_CMD 4 +#define HCI_FM_GET_SIGNAL_TH_CMD 5 +#define HCI_FM_GET_PROGRAM_SERVICE_CMD 6 +#define HCI_FM_GET_RADIO_TEXT_CMD 7 +#define HCI_FM_GET_AF_LIST_CMD 8 +#define HCI_FM_CANCEL_SEARCH_CMD 9 +#define HCI_FM_RESET_CMD 10 +#define HCI_FM_GET_FEATURES_CMD 11 +#define HCI_FM_STATION_DBG_PARAM_CMD 12 + +/* ----- HCI Command request ----- */ +struct hci_fm_recv_conf_req { + __u8 emphasis; + __u8 ch_spacing; + __u8 rds_std; + __u8 hlsi; + __u32 band_low_limit; + __u32 band_high_limit; +} __packed; + +struct hci_fm_mute_mode_req { + __u8 hard_mute; + __u8 soft_mute; +} __packed; + +struct hci_fm_stereo_mode_req { + __u8 stereo_mode; + __u8 sig_blend; + __u8 intf_blend; + __u8 most_switch; +} __packed; + +struct hci_fm_search_station_req { + __u8 srch_mode; + __u8 scan_time; + __u8 srch_dir; +} __packed; + +struct hci_fm_search_rds_station_req { + struct hci_fm_search_station_req srch_station; + __u8 srch_pty; + __u16 srch_pi; +} __packed; + +struct hci_fm_search_station_list_req { + __u8 srch_list_mode; + __u8 srch_list_dir; + __u32 srch_list_max; + __u8 srch_pty; +} __packed; + +struct hci_fm_rds_grp_req { + __u32 rds_grp_enable_mask; + __u32 rds_buf_size; + __u8 en_rds_change_filter; +} __packed; + +struct hci_fm_en_avd_ctrl_req { + __u8 no_freqs; + __u8 freq_index; + __u8 lo_shft; + __u16 freq_min; + __u16 freq_max; +} __packed; + +struct hci_fm_def_data_rd_req { + __u8 mode; + __u8 length; +} __packed; + +struct hci_fm_def_data_wr_req { + struct hci_fm_def_data_rd_req data_rd; + __u8 data[256]; +} __packed; + +struct hci_fm_peek_req { + __u32 start_addr; + __u8 length; +} __packed; + +struct hci_fm_poke_req { + struct hci_fm_peek_req peek_req; + __u8 data[256]; +} __packed; + +struct hci_fm_ssbi_req { + __u16 start_addr; + __u8 data; +} __packed; + +/*HCI events*/ +#define HCI_EV_TUNE_STATUS 0x01 +#define HCI_EV_RDS_LOCK_STATUS 0x02 +#define HCI_EV_STEREO_STATUS 0x03 +#define HCI_EV_SERVICE_AVAILABLE 0x04 +#define HCI_EV_SEARCH_PROGRESS 0x05 +#define HCI_EV_SEARCH_RDS_PROGRESS 0x06 +#define HCI_EV_SEARCH_LIST_PROGRESS 0x07 +#define HCI_EV_RDS_RX_DATA 0x08 +#define HCI_EV_PROGRAM_SERVICE 0x09 +#define HCI_EV_RADIO_TEXT 0x0A +#define HCI_EV_FM_AF_LIST 0x0B +#define HCI_EV_TX_RDS_GRP_AVBLE 0x0C +#define HCI_EV_TX_RDS_GRP_COMPL 0x0D +#define HCI_EV_TX_RDS_CONT_GRP_COMPL 0x0E +#define HCI_EV_CMD_COMPLETE 0x0F +#define HCI_EV_CMD_STATUS 0x10 +#define HCI_EV_TUNE_COMPLETE 0x11 +#define HCI_EV_SEARCH_COMPLETE 0x12 +#define HCI_EV_SEARCH_RDS_COMPLETE 0x13 +#define HCI_EV_SEARCH_LIST_COMPLETE 0x14 + +#define HCI_REQ_DONE 0 +#define HCI_REQ_PEND 1 +#define HCI_REQ_CANCELED 2 +#define HCI_REQ_STATUS 3 + +struct hci_ev_tune_status { + __u8 sub_event; + __le32 station_freq; + __u8 serv_avble; + __u8 rssi; + __u8 stereo_prg; + __u8 rds_sync_status; + __u8 mute_mode; +} __packed; + +struct hci_ev_rds_rx_data { + __u8 num_rds_grps; + __u8 rds_grp_data[12]; +} __packed; + +struct hci_ev_prg_service { + __le16 pi_prg_id; + __u8 pty_prg_type; + __u8 ta_prg_code_type; + __u8 ta_ann_code_flag; + __u8 ms_switch_code_flag; + __u8 dec_id_ctrl_code_flag; + __u8 ps_num; + __u8 prg_service_name[119]; +} __packed; + +struct hci_ev_radio_text { + __le16 pi_prg_id; + __u8 pty_prg_type; + __u8 ta_prg_code_type; + __u8 txt_ab_flag; + __u8 radio_txt[64]; +} __packed; + +struct hci_ev_af_list { + __le32 tune_freq; + __le16 pi_code; + __u8 af_size; + __u8 af_list[25]; +} __packed; + +struct hci_ev_cmd_complete { + __u8 num_hci_cmd_pkts; + __le16 cmd_opcode; +} __packed; + +struct hci_ev_cmd_status { + __u8 status; + __u8 num_hci_cmd_pkts; + __le16 status_opcode; +} __packed; + +struct hci_ev_srch_st { + __le32 station_freq; + __u8 rds_cap; + __u8 pty; + __le16 status_opcode; +} __packed; + +struct hci_ev_srch_list_compl { + __u8 status; + __u8 num_stations_found; + struct hci_ev_srch_st srch_st[20]; +} __packed; + +/* ----- HCI Event Response ----- */ +struct hci_fm_conf_rsp { + __u8 status; + struct hci_fm_recv_conf_req recv_conf_rsp; +} __packed; + +struct hci_fm_sig_threshold_rsp { + __u8 status; + __u8 sig_threshold; +} __packed; + +struct hci_fm_station_rsp { + struct hci_ev_tune_status station_rsp; +} __packed; + +struct hci_fm_prgm_srv_rsp { + __u8 status; + struct hci_ev_prg_service prg_srv; +} __packed; + +struct hci_fm_radio_txt_rsp { + __u8 status; + struct hci_ev_radio_text rd_txt; +} __packed; + +struct hci_fm_af_list_rsp { + __u8 status; + struct hci_ev_af_list rd_txt; +} __packed; + +struct hci_fm_data_rd_rsp { + __u8 status; + __u8 ret_data_len; + __u8 data[256]; +} __packed; + +struct hci_fm_feature_list_rsp { + __u8 status; + __u8 feature_mask; +} __packed; + +struct hci_fm_dbg_param_rsp { + __u8 status; + __u8 blend; + __u8 soft_mute; + __u8 inf_blend; + __u8 inf_soft_mute; + __u8 pilot_pil; + __u8 io_verc; + __u8 in_det_out; +} __packed; + +/* HCI dev events */ +#define RADIO_HCI_DEV_REG 1 +#define RADIO_HCI_DEV_WRITE 2 + +#define hci_req_lock(d) mutex_lock(&d->req_lock) +#define hci_req_unlock(d) mutex_unlock(&d->req_lock) + +/*FM states*/ + +enum radio_state_t { + FM_OFF, + FM_RECV, + FM_TRANS, + FM_RESET, +}; + +enum v4l2_cid_private_iris_t { + V4L2_CID_PRIVATE_IRIS_SRCHMODE = (0x08000000 + 1), + V4L2_CID_PRIVATE_IRIS_SCANDWELL, + V4L2_CID_PRIVATE_IRIS_SRCHON, + V4L2_CID_PRIVATE_IRIS_STATE, + V4L2_CID_PRIVATE_IRIS_TRANSMIT_MODE, + V4L2_CID_PRIVATE_IRIS_RDSGROUP_MASK, + V4L2_CID_PRIVATE_IRIS_REGION, + V4L2_CID_PRIVATE_IRIS_SIGNAL_TH, + V4L2_CID_PRIVATE_IRIS_SRCH_PTY, + V4L2_CID_PRIVATE_IRIS_SRCH_PI, + V4L2_CID_PRIVATE_IRIS_SRCH_CNT, + V4L2_CID_PRIVATE_IRIS_EMPHASIS, + V4L2_CID_PRIVATE_IRIS_RDS_STD, + V4L2_CID_PRIVATE_IRIS_SPACING, + V4L2_CID_PRIVATE_IRIS_RDSON, + V4L2_CID_PRIVATE_IRIS_RDSGROUP_PROC, + V4L2_CID_PRIVATE_IRIS_LP_MODE, + V4L2_CID_PRIVATE_IRIS_ANTENNA, + V4L2_CID_PRIVATE_IRIS_RDSD_BUF, + V4L2_CID_PRIVATE_IRIS_PSALL, + /*v4l2 Tx controls*/ + V4L2_CID_PRIVATE_IRIS_TX_SETPSREPEATCOUNT, + V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_PS_NAME, + V4L2_CID_PRIVATE_IRIS_STOP_RDS_TX_RT, + V4L2_CID_PRIVATE_IRIS_IOVERC, + V4L2_CID_PRIVATE_IRIS_INTDET, +}; + +enum iris_evt_t { + IRIS_EVT_RADIO_READY, + IRIS_EVT_TUNE_SUCC, + IRIS_EVT_SEEK_COMPLETE, + IRIS_EVT_SCAN_NEXT, + IRIS_EVT_NEW_RAW_RDS, + IRIS_EVT_NEW_RT_RDS, + IRIS_EVT_NEW_PS_RDS, + IRIS_EVT_ERROR, + IRIS_EVT_BELOW_TH, + IRIS_EVT_ABOVE_TH, + IRIS_EVT_STEREO, + IRIS_EVT_MONO, + IRIS_EVT_RDS_AVAIL, + IRIS_EVT_RDS_NOT_AVAIL, + IRIS_EVT_NEW_SRCH_LIST, + IRIS_EVT_NEW_AF_LIST, + IRIS_EVT_TXRDSDAT, + IRIS_EVT_TXRDSDONE +}; + +enum iris_region_t { + IRIS_REGION_US, + IRIS_REGION_EU, + IRIS_REGION_JAPAN, + IRIS_REGION_JAPAN_WIDE, + IRIS_REGION_OTHER +}; + +#define STD_BUF_SIZE (64) + +enum iris_buf_t { + IRIS_BUF_SRCH_LIST, + IRIS_BUF_EVENTS, + IRIS_BUF_RT_RDS, + IRIS_BUF_PS_RDS, + IRIS_BUF_RAW_RDS, + IRIS_BUF_AF_LIST, + IRIS_BUF_MAX +}; + +enum iris_xfr_t { + IRIS_XFR_SYNC, + IRIS_XFR_ERROR, + IRIS_XFR_SRCH_LIST, + IRIS_XFR_RT_RDS, + IRIS_XFR_PS_RDS, + IRIS_XFR_AF_LIST, + IRIS_XFR_MAX +}; + +#undef FMDBG +#ifdef FM_DEBUG +#define FMDBG(fmt, args...) pr_info("iris_radio: " fmt, ##args) +#else +#define FMDBG(fmt, args...) +#endif + +#undef FMDERR +#define FMDERR(fmt, args...) pr_err("iris_radio: " fmt, ##args) + +/* Search options */ +enum search_t { + SEEK, + SCAN, + SCAN_FOR_STRONG, + SCAN_FOR_WEAK, + RDS_SEEK_PTY, + RDS_SCAN_PTY, + RDS_SEEK_PI, + RDS_AF_JUMP, +}; + +#define SRCH_MODE 0x07 +#define SRCH_DIR 0x08 /* 0-up 1-down */ +#define SCAN_DWELL 0x70 +#define SRCH_ON 0x80 + +/* I/O Control */ +#define IOC_HRD_MUTE 0x03 +#define IOC_SFT_MUTE 0x01 +#define IOC_MON_STR 0x01 +#define IOC_SIG_BLND 0x01 +#define IOC_INTF_BLND 0x01 +#define IOC_ANTENNA 0x01 + +/* RDS Control */ +#define RDS_ON 0x01 +#define RDS_BUF_SZ 100 + +/* Search direction */ +#define SRCH_DIR_UP (0) +#define SRCH_DIR_DOWN (1) + +/* control options */ +#define CTRL_ON (1) +#define CTRL_OFF (0) + +int hci_def_data_read(struct hci_fm_def_data_rd_req *arg, + struct radio_hci_dev *hdev); +int hci_def_data_write(struct hci_fm_def_data_wr_req *arg, + struct radio_hci_dev *hdev); +int hci_fm_do_calibration(__u8 *arg, struct radio_hci_dev *hdev); +int hci_fm_do_calibration(__u8 *arg, struct radio_hci_dev *hdev); +int hci_peek_data(struct hci_fm_peek_req *arg, struct radio_hci_dev *hdev); +int hci_poke_data(struct hci_fm_poke_req *arg, struct radio_hci_dev *hdev); +int hci_poke_data(struct hci_fm_poke_req *arg, struct radio_hci_dev *hdev); +int hci_poke_data(struct hci_fm_poke_req *arg, struct radio_hci_dev *hdev); + +#endif /* __RADIO_HCI_CORE_H */ diff --git a/include/media/tavarua.h b/include/media/tavarua.h new file mode 100644 index 00000000000..b82ec58c311 --- /dev/null +++ b/include/media/tavarua.h @@ -0,0 +1,417 @@ +#ifndef __LINUX_TAVARUA_H +#define __LINUX_TAVARUA_H + +#ifdef __KERNEL__ +#include +#include +#else +#include +#endif +#include +#include + + +#undef FM_DEBUG + +/* constants */ +#define RDS_BLOCKS_NUM (4) +#define BYTES_PER_BLOCK (3) +#define MAX_PS_LENGTH (96) +#define MAX_RT_LENGTH (64) + +#define XFRDAT0 (0x20) +#define XFRDAT1 (0x21) +#define XFRDAT2 (0x22) + +#define INTDET_PEEK_MSB (0x88) +#define INTDET_PEEK_LSB (0x26) + +#define RMSSI_PEEK_MSB (0x88) +#define RMSSI_PEEK_LSB (0xA8) + +#define MPX_DCC_BYPASS_POKE_MSB (0x88) +#define MPX_DCC_BYPASS_POKE_LSB (0xC0) + +#define MPX_DCC_PEEK_MSB_REG1 (0x88) +#define MPX_DCC_PEEK_LSB_REG1 (0xC2) + +#define MPX_DCC_PEEK_MSB_REG2 (0x88) +#define MPX_DCC_PEEK_LSB_REG2 (0xC3) + +#define MPX_DCC_PEEK_MSB_REG3 (0x88) +#define MPX_DCC_PEEK_LSB_REG3 (0xC4) + +/* Standard buffer size */ +#define STD_BUF_SIZE (64) +/* Search direction */ +#define SRCH_DIR_UP (0) +#define SRCH_DIR_DOWN (1) + +/* control options */ +#define CTRL_ON (1) +#define CTRL_OFF (0) + +#define US_LOW_BAND (87.5) +#define US_HIGH_BAND (108) + +/* constant for Tx */ + +#define MASK_PI (0x0000FFFF) +#define MASK_PI_MSB (0x0000FF00) +#define MASK_PI_LSB (0x000000FF) +#define MASK_PTY (0x0000001F) +#define MASK_TXREPCOUNT (0x0000000F) + +#undef FMDBG +#ifdef FM_DEBUG + #define FMDBG(fmt, args...) printk(KERN_INFO "tavarua_radio: " fmt, ##args) +#else + #define FMDBG(fmt, args...) +#endif + +#undef FMDERR +#define FMDERR(fmt, args...) printk(KERN_INFO "tavarua_radio: " fmt, ##args) + +#undef FMDBG_I2C +#ifdef FM_DEBUG_I2C + #define FMDBG_I2C(fmt, args...) printk(KERN_INFO "fm_i2c: " fmt, ##args) +#else + #define FMDBG_I2C(fmt, args...) +#endif + +/* function declarations */ +/* FM Core audio paths. */ +#define TAVARUA_AUDIO_OUT_ANALOG_OFF (0) +#define TAVARUA_AUDIO_OUT_ANALOG_ON (1) +#define TAVARUA_AUDIO_OUT_DIGITAL_OFF (0) +#define TAVARUA_AUDIO_OUT_DIGITAL_ON (1) + +int tavarua_set_audio_path(int digital_on, int analog_on); + +/* defines and enums*/ + +#define MARIMBA_A0 0x01010013 +#define MARIMBA_2_1 0x02010204 +#define BAHAMA_1_0 0x0302010A +#define BAHAMA_2_0 0x04020205 +#define WAIT_TIMEOUT 2000 +#define RADIO_INIT_TIME 15 +#define TAVARUA_DELAY 10 +/* + * The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW, + * 62.5 kHz otherwise. + * The tuner is able to have a channel spacing of 50, 100 or 200 kHz. + * tuner->capability is therefore set to V4L2_TUNER_CAP_LOW + * The FREQ_MUL is then: 1 MHz / 62.5 Hz = 16000 + */ +#define FREQ_MUL (1000000 / 62.5) + +enum v4l2_cid_private_tavarua_t { + V4L2_CID_PRIVATE_TAVARUA_SRCHMODE = (V4L2_CID_PRIVATE_BASE + 1), + V4L2_CID_PRIVATE_TAVARUA_SCANDWELL, + V4L2_CID_PRIVATE_TAVARUA_SRCHON, + V4L2_CID_PRIVATE_TAVARUA_STATE, + V4L2_CID_PRIVATE_TAVARUA_TRANSMIT_MODE, + V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK, + V4L2_CID_PRIVATE_TAVARUA_REGION, + V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH, + V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY, + V4L2_CID_PRIVATE_TAVARUA_SRCH_PI, + V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT, + V4L2_CID_PRIVATE_TAVARUA_EMPHASIS, + V4L2_CID_PRIVATE_TAVARUA_RDS_STD, + V4L2_CID_PRIVATE_TAVARUA_SPACING, + V4L2_CID_PRIVATE_TAVARUA_RDSON, + V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC, + V4L2_CID_PRIVATE_TAVARUA_LP_MODE, + V4L2_CID_PRIVATE_TAVARUA_ANTENNA, + V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF, + V4L2_CID_PRIVATE_TAVARUA_PSALL, + /*v4l2 Tx controls*/ + V4L2_CID_PRIVATE_TAVARUA_TX_SETPSREPEATCOUNT, + V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_PS_NAME, + V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_RT, + V4L2_CID_PRIVATE_TAVARUA_IOVERC, + V4L2_CID_PRIVATE_TAVARUA_INTDET, + V4L2_CID_PRIVATE_TAVARUA_MPX_DCC, +}; + +enum tavarua_buf_t { + TAVARUA_BUF_SRCH_LIST, + TAVARUA_BUF_EVENTS, + TAVARUA_BUF_RT_RDS, + TAVARUA_BUF_PS_RDS, + TAVARUA_BUF_RAW_RDS, + TAVARUA_BUF_AF_LIST, + TAVARUA_BUF_MAX +}; + +enum tavarua_xfr_t { + TAVARUA_XFR_SYNC, + TAVARUA_XFR_ERROR, + TAVARUA_XFR_SRCH_LIST, + TAVARUA_XFR_RT_RDS, + TAVARUA_XFR_PS_RDS, + TAVARUA_XFR_AF_LIST, + TAVARUA_XFR_MAX +}; + +/* offsets */ +#define RAW_RDS 0x0F +#define RDS_BLOCK 3 + +/* registers*/ +#define MARIMBA_XO_BUFF_CNTRL 0x07 +#define RADIO_REGISTERS 0x30 +#define XFR_REG_NUM 16 +#define STATUS_REG_NUM 3 + +/* TX constants */ +#define HEADER_SIZE 4 +#define TX_ON 0x80 +#define TAVARUA_TX_RT RDS_RT_0 +#define TAVARUA_TX_PS RDS_PS_0 + +enum register_t { + STATUS_REG1 = 0, + STATUS_REG2, + STATUS_REG3, + RDCTRL, + FREQ, + TUNECTRL, + SRCHRDS1, + SRCHRDS2, + SRCHCTRL, + IOCTRL, + RDSCTRL, + ADVCTRL, + AUDIOCTRL, + RMSSI, + IOVERC, + AUDIOIND = 0x1E, + XFRCTRL, + FM_CTL0 = 0xFF, + LEAKAGE_CNTRL = 0xFE, +}; +#define BAHAMA_RBIAS_CTL1 0x07 +#define BAHAMA_FM_MODE_REG 0xFD +#define BAHAMA_FM_CTL1_REG 0xFE +#define BAHAMA_FM_CTL0_REG 0xFF +#define BAHAMA_FM_MODE_NORMAL 0x00 +#define BAHAMA_LDO_DREG_CTL0 0xF0 +#define BAHAMA_LDO_AREG_CTL0 0xF4 + +/* Radio Control */ +#define RDCTRL_STATE_OFFSET 0 +#define RDCTRL_STATE_MASK (3 << RDCTRL_STATE_OFFSET) +#define RDCTRL_BAND_OFFSET 2 +#define RDCTRL_BAND_MASK (1 << RDCTRL_BAND_OFFSET) +#define RDCTRL_CHSPACE_OFFSET 3 +#define RDCTRL_CHSPACE_MASK (3 << RDCTRL_CHSPACE_OFFSET) +#define RDCTRL_DEEMPHASIS_OFFSET 5 +#define RDCTRL_DEEMPHASIS_MASK (1 << RDCTRL_DEEMPHASIS_OFFSET) +#define RDCTRL_HLSI_OFFSET 6 +#define RDCTRL_HLSI_MASK (3 << RDCTRL_HLSI_OFFSET) + +/* Tune Control */ +#define TUNE_STATION 0x01 +#define ADD_OFFSET (1 << 1) +#define SIGSTATE (1 << 5) +#define MOSTSTATE (1 << 6) +#define RDSSYNC (1 << 7) +/* Search Control */ +#define SRCH_MODE_OFFSET 0 +#define SRCH_MODE_MASK (7 << SRCH_MODE_OFFSET) +#define SRCH_DIR_OFFSET 3 +#define SRCH_DIR_MASK (1 << SRCH_DIR_OFFSET) +#define SRCH_DWELL_OFFSET 4 +#define SRCH_DWELL_MASK (7 << SRCH_DWELL_OFFSET) +#define SRCH_STATE_OFFSET 7 +#define SRCH_STATE_MASK (1 << SRCH_STATE_OFFSET) + +/* I/O Control */ +#define IOC_HRD_MUTE 0x03 +#define IOC_SFT_MUTE (1 << 2) +#define IOC_MON_STR (1 << 3) +#define IOC_SIG_BLND (1 << 4) +#define IOC_INTF_BLND (1 << 5) +#define IOC_ANTENNA (1 << 6) +#define IOC_ANTENNA_OFFSET 6 +#define IOC_ANTENNA_MASK (1 << IOC_ANTENNA_OFFSET) + +/* RDS Control */ +#define RDS_ON 0x01 +#define RDSCTRL_STANDARD_OFFSET 1 +#define RDSCTRL_STANDARD_MASK (1 << RDSCTRL_STANDARD_OFFSET) + +/* Advanced features controls */ +#define RDSRTEN (1 << 3) +#define RDSPSEN (1 << 4) + +/* Audio path control */ +#define AUDIORX_ANALOG_OFFSET 0 +#define AUDIORX_ANALOG_MASK (1 << AUDIORX_ANALOG_OFFSET) +#define AUDIORX_DIGITAL_OFFSET 1 +#define AUDIORX_DIGITAL_MASK (1 << AUDIORX_DIGITAL_OFFSET) +#define AUDIOTX_OFFSET 2 +#define AUDIOTX_MASK (1 << AUDIOTX_OFFSET) +#define I2SCTRL_OFFSET 3 +#define I2SCTRL_MASK (1 << I2SCTRL_OFFSET) + +/* Search options */ +enum search_t { + SEEK, + SCAN, + SCAN_FOR_STRONG, + SCAN_FOR_WEAK, + RDS_SEEK_PTY, + RDS_SCAN_PTY, + RDS_SEEK_PI, + RDS_AF_JUMP, +}; + +#define SRCH_MODE 0x07 +#define SRCH_DIR 0x08 /* 0-up 1-down */ +#define SCAN_DWELL 0x70 +#define SRCH_ON 0x80 + +/* RDS CONFIG */ +#define RDS_CONFIG_PSALL 0x01 + +#define FM_ENABLE 0x22 +#define SET_REG_FIELD(reg, val, offset, mask) \ + (reg = (reg & ~mask) | (((val) << offset) & mask)) +#define GET_REG_FIELD(reg, offset, mask) ((reg & mask) >> offset) + +enum radio_state_t { + FM_OFF, + FM_RECV, + FM_TRANS, + FM_RESET, +}; + +#define XFRCTRL_WRITE (1 << 7) + +/* Interrupt status */ + +/* interrupt register 1 */ +#define READY (1 << 0) /* Radio ready after powerup or reset */ +#define TUNE (1 << 1) /* Tune completed */ +#define SEARCH (1 << 2) /* Search completed (read FREQ) */ +#define SCANNEXT (1 << 3) /* Scanning for next station */ +#define SIGNAL (1 << 4) /* Signal indicator change (read SIGSTATE) */ +#define INTF (1 << 5) /* Interference cnt has fallen outside range */ +#define SYNC (1 << 6) /* RDS sync state change (read RDSSYNC) */ +#define AUDIO (1 << 7) /* Audio Control indicator (read AUDIOIND) */ + +/* interrupt register 2 */ +#define RDSDAT (1 << 0) /* New unread RDS data group available */ +#define BLOCKB (1 << 1) /* Block-B match condition exists */ +#define PROGID (1 << 2) /* Block-A or Block-C matched stored PI value*/ +#define RDSPS (1 << 3) /* New RDS Program Service Table available */ +#define RDSRT (1 << 4) /* New RDS Radio Text available */ +#define RDSAF (1 << 5) /* New RDS AF List available */ +#define TXRDSDAT (1 << 6) /* Transmitted an RDS group */ +#define TXRDSDONE (1 << 7) /* RDS raw group one-shot transmit completed */ + +/* interrupt register 3 */ +#define TRANSFER (1 << 0) /* Data transfer (XFR) completed */ +#define RDSPROC (1 << 1) /* Dynamic RDS Processing complete */ +#define ERROR (1 << 7) /* Err occurred.Read code to determine cause */ + + +#define FM_TX_PWR_LVL_0 0 /* Lowest power lvl that can be set for Tx */ +#define FM_TX_PWR_LVL_MAX 7 /* Max power lvl for Tx */ +/* Transfer */ +enum tavarua_xfr_ctrl_t { + RDS_PS_0 = 0x01, + RDS_PS_1, + RDS_PS_2, + RDS_PS_3, + RDS_PS_4, + RDS_PS_5, + RDS_PS_6, + RDS_RT_0, + RDS_RT_1, + RDS_RT_2, + RDS_RT_3, + RDS_RT_4, + RDS_AF_0, + RDS_AF_1, + RDS_CONFIG, + RDS_TX_GROUPS, + RDS_COUNT_0, + RDS_COUNT_1, + RDS_COUNT_2, + RADIO_CONFIG, + RX_CONFIG, + RX_TIMERS, + RX_STATIONS_0, + RX_STATIONS_1, + INT_CTRL, + ERROR_CODE, + CHIPID, + CAL_DAT_0 = 0x20, + CAL_DAT_1, + CAL_DAT_2, + CAL_DAT_3, + CAL_CFG_0, + CAL_CFG_1, + DIG_INTF_0, + DIG_INTF_1, + DIG_AGC_0, + DIG_AGC_1, + DIG_AGC_2, + DIG_AUDIO_0, + DIG_AUDIO_1, + DIG_AUDIO_2, + DIG_AUDIO_3, + DIG_AUDIO_4, + DIG_RXRDS, + DIG_DCC, + DIG_SPUR, + DIG_MPXDCC, + DIG_PILOT, + DIG_DEMOD, + DIG_MOST, + DIG_TX_0, + DIG_TX_1, + PHY_TXGAIN = 0x3B, + PHY_CONFIG, + PHY_TXBLOCK, + PHY_TCB, + XFR_PEEK_MODE = 0x40, + XFR_POKE_MODE = 0xC0, + TAVARUA_XFR_CTRL_MAX +}; + +enum tavarua_evt_t { + TAVARUA_EVT_RADIO_READY, + TAVARUA_EVT_TUNE_SUCC, + TAVARUA_EVT_SEEK_COMPLETE, + TAVARUA_EVT_SCAN_NEXT, + TAVARUA_EVT_NEW_RAW_RDS, + TAVARUA_EVT_NEW_RT_RDS, + TAVARUA_EVT_NEW_PS_RDS, + TAVARUA_EVT_ERROR, + TAVARUA_EVT_BELOW_TH, + TAVARUA_EVT_ABOVE_TH, + TAVARUA_EVT_STEREO, + TAVARUA_EVT_MONO, + TAVARUA_EVT_RDS_AVAIL, + TAVARUA_EVT_RDS_NOT_AVAIL, + TAVARUA_EVT_NEW_SRCH_LIST, + TAVARUA_EVT_NEW_AF_LIST, + TAVARUA_EVT_TXRDSDAT, + TAVARUA_EVT_TXRDSDONE +}; + +enum tavarua_region_t { + TAVARUA_REGION_US, + TAVARUA_REGION_EU, + TAVARUA_REGION_JAPAN, + TAVARUA_REGION_JAPAN_WIDE, + TAVARUA_REGION_OTHER +}; + +#endif /* __LINUX_TAVARUA_H */ diff --git a/include/media/videobuf-msm-mem.h b/include/media/videobuf-msm-mem.h new file mode 100644 index 00000000000..19dd93e4b7f --- /dev/null +++ b/include/media/videobuf-msm-mem.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * helper functions for physically contiguous PMEM capture buffers + */ + +#ifndef _VIDEOBUF_PMEM_CONTIG_H +#define _VIDEOBUF_PMEM_CONTIG_H + +#include + +struct videobuf_contig_pmem { + u32 magic; + void *vaddr; + int phyaddr; + unsigned long size; + int is_userptr; + uint32_t y_off; + uint32_t cbcr_off; + int buffer_type; + struct file *file; +}; + +void videobuf_queue_pmem_contig_init(struct videobuf_queue *q, + const struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv, + struct mutex *ext_lock); + +int videobuf_to_pmem_contig(struct videobuf_buffer *buf); +int videobuf_pmem_contig_free(struct videobuf_queue *q, + struct videobuf_buffer *buf); + +#endif /* _VIDEOBUF_PMEM_CONTIG_H */ diff --git a/include/mtd/mtd-abi.h b/include/mtd/mtd-abi.h index 2f7d45bcbd2..83a46a063d5 100644 --- a/include/mtd/mtd-abi.h +++ b/include/mtd/mtd-abi.h @@ -156,7 +156,7 @@ struct nand_oobfree { */ struct nand_ecclayout_user { __u32 eccbytes; - __u32 eccpos[MTD_MAX_ECCPOS_ENTRIES]; + __u32 eccpos[256]; __u32 oobavail; struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES]; }; diff --git a/include/net/bluetooth/amp.h b/include/net/bluetooth/amp.h new file mode 100644 index 00000000000..4b080791ffc --- /dev/null +++ b/include/net/bluetooth/amp.h @@ -0,0 +1,296 @@ +/* + Copyright (c) 2010-2011 Code Aurora Forum. 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 and + only version 2 as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#ifndef __AMP_H +#define __AMP_H + +/* AMP defaults */ + +#define A2MP_RSP_TIMEOUT (20000) /* 20 seconds */ + +/* A2MP Protocol */ + +/* A2MP command codes */ +#define A2MP_COMMAND_REJ 0x01 +#define A2MP_DISCOVER_REQ 0x02 +#define A2MP_DISCOVER_RSP 0x03 +#define A2MP_CHANGE_NOTIFY 0x04 +#define A2MP_CHANGE_RSP 0x05 +#define A2MP_GETINFO_REQ 0x06 +#define A2MP_GETINFO_RSP 0x07 +#define A2MP_GETAMPASSOC_REQ 0x08 +#define A2MP_GETAMPASSOC_RSP 0x09 +#define A2MP_CREATEPHYSLINK_REQ 0x0A +#define A2MP_CREATEPHYSLINK_RSP 0x0B +#define A2MP_DISCONNPHYSLINK_REQ 0x0C +#define A2MP_DISCONNPHYSLINK_RSP 0x0D + +struct a2mp_cmd_hdr { + __u8 code; + __u8 ident; + __le16 len; +} __packed; + +struct a2mp_cmd_rej { + __le16 reason; +} __packed; + +#define HCI_A2MP_ID(id) ((id)+0x10) /* convert HCI dev index to AMP ID */ +#define A2MP_HCI_ID(id) ((id)-0x10) /* convert AMP ID to HCI dev index */ + +struct a2mp_discover_req { + __le16 mtu; + __le16 ext_feat; +} __packed; + +struct a2mp_cl { + __u8 id; + __u8 type; + __u8 status; +} __packed; + +struct a2mp_discover_rsp { + __le16 mtu; + __le16 ext_feat; + struct a2mp_cl cl[0]; +} __packed; + +struct a2mp_getinfo_req { + __u8 id; +} __packed; + +struct a2mp_getinfo_rsp { + __u8 id; + __u8 status; + __le32 total_bw; + __le32 max_bw; + __le32 min_latency; + __le16 pal_cap; + __le16 assoc_size; +} __packed; + +struct a2mp_getampassoc_req { + __u8 id; +} __packed; + +struct a2mp_getampassoc_rsp { + __u8 id; + __u8 status; + __u8 amp_assoc[0]; +} __packed; + +struct a2mp_createphyslink_req { + __u8 local_id; + __u8 remote_id; + __u8 amp_assoc[0]; +} __packed; + +struct a2mp_createphyslink_rsp { + __u8 local_id; + __u8 remote_id; + __u8 status; +} __packed; + +struct a2mp_disconnphyslink_req { + __u8 local_id; + __u8 remote_id; +} __packed; + +struct a2mp_disconnphyslink_rsp { + __u8 local_id; + __u8 remote_id; + __u8 status; +} __packed; + + +/* L2CAP-AMP module interface */ +int amp_init(void); +void amp_exit(void); + +/* L2CAP-AMP fixed channel interface */ +void amp_conn_ind(struct l2cap_conn *conn, struct sk_buff *skb); + +/* L2CAP-AMP link interface */ +void amp_create_physical(struct l2cap_conn *conn, struct sock *sk); +void amp_accept_physical(struct l2cap_conn *conn, u8 id, struct sock *sk); + +/* AMP manager internals */ +struct amp_ctrl { + struct amp_mgr *mgr; + __u8 id; + __u8 type; + __u8 status; + __u32 total_bw; + __u32 max_bw; + __u32 min_latency; + __u16 pal_cap; + __u16 max_assoc_size; +}; + +struct amp_mgr { + struct list_head list; + __u8 discovered; + __u8 next_ident; + struct l2cap_conn *l2cap_conn; + struct socket *a2mp_sock; + struct list_head ctx_list; + rwlock_t ctx_list_lock; + struct amp_ctrl *ctrls; /* @@ TODO s.b. list of controllers */ + struct sk_buff *skb; + __u8 connected; +}; + +/* AMP Manager signalling contexts */ +#define AMP_GETAMPASSOC 1 +#define AMP_CREATEPHYSLINK 2 +#define AMP_ACCEPTPHYSLINK 3 +#define AMP_CREATELOGLINK 4 +#define AMP_ACCEPTLOGLINK 5 + +/* Get AMP Assoc sequence */ +#define AMP_GAA_INIT 0 +#define AMP_GAA_RLAA_COMPLETE 1 +struct amp_gaa_state { + __u8 req_ident; + __u16 len_so_far; + __u8 *assoc; +}; + +/* Create Physical Link sequence */ +#define AMP_CPL_INIT 0 +#define AMP_CPL_DISC_RSP 1 +#define AMP_CPL_GETINFO_RSP 2 +#define AMP_CPL_GAA_RSP 3 +#define AMP_CPL_CPL_STATUS 4 +#define AMP_CPL_WRA_COMPLETE 5 +#define AMP_CPL_CHANNEL_SELECT 6 +#define AMP_CPL_RLA_COMPLETE 7 +#define AMP_CPL_PL_COMPLETE 8 +#define AMP_CPL_PL_CANCEL 9 +struct amp_cpl_state { + __u8 remote_id; + __u16 max_len; + __u8 *remote_assoc; + __u8 *local_assoc; + __u16 len_so_far; + __u16 rem_len; + __u8 phy_handle; +}; + +/* Accept Physical Link sequence */ +#define AMP_APL_INIT 0 +#define AMP_APL_APL_STATUS 1 +#define AMP_APL_WRA_COMPLETE 2 +#define AMP_APL_PL_COMPLETE 3 +struct amp_apl_state { + __u8 remote_id; + __u8 req_ident; + __u8 *remote_assoc; + __u16 len_so_far; + __u16 rem_len; + __u8 phy_handle; +}; + +/* Create/Accept Logical Link sequence */ +#define AMP_LOG_INIT 0 +#define AMP_LOG_LL_STATUS 1 +#define AMP_LOG_LL_COMPLETE 2 +struct amp_log_state { + __u8 remote_id; +}; + +/* Possible event types a context may wait for */ +#define AMP_INIT 0x01 +#define AMP_HCI_EVENT 0x02 +#define AMP_HCI_CMD_CMPLT 0x04 +#define AMP_HCI_CMD_STATUS 0x08 +#define AMP_A2MP_RSP 0x10 +#define AMP_KILLED 0x20 +#define AMP_CANCEL 0x40 +struct amp_ctx { + struct list_head list; + struct amp_mgr *mgr; + struct hci_dev *hdev; + __u8 type; + __u8 state; + union { + struct amp_gaa_state gaa; + struct amp_cpl_state cpl; + struct amp_apl_state apl; + } d; + __u8 evt_type; + __u8 evt_code; + __u16 opcode; + __u8 id; + __u8 rsp_ident; + + struct sock *sk; + struct amp_ctx *deferred; + struct timer_list timer; +}; + +/* AMP work */ +struct amp_work_pl_timeout { + struct work_struct work; + struct amp_ctrl *ctrl; +}; +struct amp_work_ctx_timeout { + struct work_struct work; + struct amp_ctx *ctx; +}; +struct amp_work_data_ready { + struct work_struct work; + struct sock *sk; + int bytes; +}; +struct amp_work_state_change { + struct work_struct work; + struct sock *sk; +}; +struct amp_work_conn_ind { + struct work_struct work; + struct l2cap_conn *conn; + struct sk_buff *skb; +}; +struct amp_work_create_physical { + struct work_struct work; + struct l2cap_conn *conn; + u8 id; + struct sock *sk; +}; +struct amp_work_accept_physical { + struct work_struct work; + struct l2cap_conn *conn; + u8 id; + struct sock *sk; +}; +struct amp_work_cmd_cmplt { + struct work_struct work; + struct hci_dev *hdev; + u16 opcode; + struct sk_buff *skb; +}; +struct amp_work_cmd_status { + struct work_struct work; + struct hci_dev *hdev; + u16 opcode; + u8 status; +}; +struct amp_work_event { + struct work_struct work; + struct hci_dev *hdev; + u8 event; + struct sk_buff *skb; +}; + +#endif /* __AMP_H */ diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index e727555d4ee..643a4970bcd 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -1,6 +1,6 @@ -/* +/* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2000-2001, 2010-2011 Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -12,20 +12,20 @@ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY - CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, - COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED. */ #ifndef __BLUETOOTH_H #define __BLUETOOTH_H -#include +#include #include #include #include @@ -38,6 +38,7 @@ /* Reserv for core and drivers use */ #define BT_SKB_RESERVE 8 +#define BT_SKB_RESERVE_80211 32 #define BTPROTO_L2CAP 0 #define BTPROTO_HCI 1 @@ -65,24 +66,41 @@ struct bt_security { #define BT_DEFER_SETUP 7 -#define BT_FLUSHABLE 8 - -#define BT_FLUSHABLE_OFF 0 -#define BT_FLUSHABLE_ON 1 - -#define BT_POWER 9 +#define BT_POWER 8 struct bt_power { __u8 force_active; }; -#define BT_POWER_FORCE_ACTIVE_OFF 0 -#define BT_POWER_FORCE_ACTIVE_ON 1 -__attribute__((format (printf, 2, 3))) -int bt_printk(const char *level, const char *fmt, ...); - -#define BT_INFO(fmt, arg...) bt_printk(KERN_INFO, pr_fmt(fmt), ##arg) -#define BT_ERR(fmt, arg...) bt_printk(KERN_ERR, pr_fmt(fmt), ##arg) -#define BT_DBG(fmt, arg...) pr_debug(fmt "\n", ##arg) +#define BT_AMP_POLICY 9 + +/* Require BR/EDR (default policy) + * AMP controllers cannot be used + * Channel move requests from the remote device are denied + * If the L2CAP channel is currently using AMP, move the channel to BR/EDR + */ +#define BT_AMP_POLICY_REQUIRE_BR_EDR 0 + +/* Prefer AMP + * Allow use of AMP controllers + * If the L2CAP channel is currently on BR/EDR and AMP controller + * resources are available, initiate a channel move to AMP + * Channel move requests from the remote device are allowed + * If the L2CAP socket has not been connected yet, try to create + * and configure the channel directly on an AMP controller rather + * than BR/EDR + */ +#define BT_AMP_POLICY_PREFER_AMP 1 + +/* Prefer BR/EDR + * Allow use of AMP controllers + * If the L2CAP channel is currently on AMP, move it to BR/EDR + * Channel move requests from the remote device are allowed + */ +#define BT_AMP_POLICY_PREFER_BR_EDR 2 + +#define BT_INFO(fmt, arg...) printk(KERN_INFO "Bluetooth: " fmt "\n" , ## arg) +#define BT_ERR(fmt, arg...) printk(KERN_ERR "%s: " fmt "\n" , __func__ , ## arg) +#define BT_DBG(fmt, arg...) pr_debug("%s: " fmt "\n" , __func__ , ## arg) /* Connection and socket states */ enum { @@ -102,8 +120,8 @@ typedef struct { __u8 b[6]; } __packed bdaddr_t; -#define BDADDR_ANY (&(bdaddr_t) {{0, 0, 0, 0, 0, 0}}) -#define BDADDR_LOCAL (&(bdaddr_t) {{0, 0, 0, 0xff, 0xff, 0xff}}) +#define BDADDR_ANY (&(bdaddr_t) {{0, 0, 0, 0, 0, 0} }) +#define BDADDR_LOCAL (&(bdaddr_t) {{0, 0, 0, 0xff, 0xff, 0xff} }) /* Copy, swap, convert BD Address */ static inline int bacmp(bdaddr_t *ba1, bdaddr_t *ba2) @@ -145,7 +163,7 @@ int bt_sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len, int flags); int bt_sock_stream_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len, int flags); -uint bt_sock_poll(struct file * file, struct socket *sock, poll_table *wait); +uint bt_sock_poll(struct file *file, struct socket *sock, poll_table *wait); int bt_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg); int bt_sock_wait_state(struct sock *sk, int state, unsigned long timeo); @@ -154,15 +172,25 @@ void bt_accept_unlink(struct sock *sk); struct sock *bt_accept_dequeue(struct sock *parent, struct socket *newsock); /* Skb helpers */ +struct bt_l2cap_control { + __u8 frame_type; + __u8 final; + __u8 sar; + __u8 super; + __u16 reqseq; + __u16 txseq; + __u8 poll; + __u8 fcs; +}; + struct bt_skb_cb { __u8 pkt_type; __u8 incoming; __u16 expect; - __u8 tx_seq; __u8 retries; - __u8 sar; - unsigned short channel; __u8 force_active; + unsigned short channel; + struct bt_l2cap_control control; }; #define bt_cb(skb) ((struct bt_skb_cb *)((skb)->cb)) @@ -170,7 +198,8 @@ static inline struct sk_buff *bt_skb_alloc(unsigned int len, gfp_t how) { struct sk_buff *skb; - if ((skb = alloc_skb(len + BT_SKB_RESERVE, how))) { + skb = alloc_skb(len + BT_SKB_RESERVE, how); + if (skb) { skb_reserve(skb, BT_SKB_RESERVE); bt_cb(skb)->incoming = 0; } @@ -183,7 +212,8 @@ static inline struct sk_buff *bt_skb_send_alloc(struct sock *sk, struct sk_buff *skb; release_sock(sk); - if ((skb = sock_alloc_send_skb(sk, len + BT_SKB_RESERVE, nb, err))) { + skb = sock_alloc_send_skb(sk, len + BT_SKB_RESERVE, nb, err); + if (skb) { skb_reserve(skb, BT_SKB_RESERVE); bt_cb(skb)->incoming = 0; } @@ -208,7 +238,7 @@ static inline struct sk_buff *bt_skb_send_alloc(struct sock *sk, return NULL; } -int bt_to_errno(__u16 code); +int bt_err(__u16 code); extern int hci_sock_init(void); extern void hci_sock_cleanup(void); diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 0489b8b90fe..3466efa2b04 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -1,6 +1,6 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2000-2001, 2010-2011 Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -25,7 +25,7 @@ #ifndef __HCI_H #define __HCI_H -#define HCI_MAX_ACL_SIZE 1024 +#define HCI_MAX_ACL_SIZE 1500 #define HCI_MAX_SCO_SIZE 255 #define HCI_MAX_EVENT_SIZE 260 #define HCI_MAX_FRAME_SIZE (HCI_MAX_ACL_SIZE + 4) @@ -37,6 +37,7 @@ #define HCI_DEV_DOWN 4 #define HCI_DEV_SUSPEND 5 #define HCI_DEV_RESUME 6 +#define HCI_DEV_WRITE 7 /* HCI notify events */ #define HCI_NOTIFY_CONN_ADD 1 @@ -51,6 +52,7 @@ #define HCI_RS232 4 #define HCI_PCI 5 #define HCI_SDIO 6 +#define HCI_SMD 7 /* HCI controller types */ #define HCI_BREDR 0x00 @@ -99,6 +101,7 @@ enum { #define HCIGETCONNLIST _IOR('H', 212, int) #define HCIGETCONNINFO _IOR('H', 213, int) #define HCIGETAUTHINFO _IOR('H', 215, int) +#define HCISETAUTHINFO _IOR('H', 216, int) #define HCISETRAW _IOW('H', 220, int) #define HCISETSCAN _IOW('H', 221, int) @@ -166,9 +169,12 @@ enum { #define ACL_START_NO_FLUSH 0x00 #define ACL_CONT 0x01 #define ACL_START 0x02 +#define ACL_COMPLETE 0x03 #define ACL_ACTIVE_BCAST 0x04 #define ACL_PICO_BCAST 0x08 +#define ACL_PB_MASK (ACL_CONT | ACL_START) + /* Baseband links */ #define SCO_LINK 0x00 #define ACL_LINK 0x01 @@ -213,16 +219,11 @@ enum { #define LMP_EDR_3S_ESCO 0x80 #define LMP_EXT_INQ 0x01 -#define LMP_SIMUL_LE_BR 0x02 #define LMP_SIMPLE_PAIR 0x08 #define LMP_NO_FLUSH 0x40 #define LMP_LSTO 0x01 #define LMP_INQ_TX_PWR 0x02 -#define LMP_EXTFEATURES 0x80 - -/* Extended LMP features */ -#define LMP_HOST_LE 0x02 /* Connection modes */ #define HCI_CM_ACTIVE 0x0000 @@ -253,18 +254,9 @@ enum { #define HCI_AT_GENERAL_BONDING 0x04 #define HCI_AT_GENERAL_BONDING_MITM 0x05 -/* Link Key types */ -#define HCI_LK_COMBINATION 0x00 -#define HCI_LK_LOCAL_UNIT 0x01 -#define HCI_LK_REMOTE_UNIT 0x02 -#define HCI_LK_DEBUG_COMBINATION 0x03 -#define HCI_LK_UNAUTH_COMBINATION 0x04 -#define HCI_LK_AUTH_COMBINATION 0x05 -#define HCI_LK_CHANGED_COMBINATION 0x06 -/* The spec doesn't define types for SMP keys */ -#define HCI_LK_SMP_LTK 0x81 -#define HCI_LK_SMP_IRK 0x82 -#define HCI_LK_SMP_CSRK 0x83 +/* Flow control modes */ +#define HCI_PACKET_BASED_FLOW_CTL_MODE 0x00 +#define HCI_BLOCK_BASED_FLOW_CTL_MODE 0x01 /* ----- HCI Commands ---- */ #define HCI_OP_NOP 0x0000 @@ -325,6 +317,11 @@ struct hci_cp_link_key_reply { __u8 link_key[16]; } __packed; +struct hci_rp_link_key_reply { + __u8 status; + bdaddr_t bdaddr; +} __packed; + #define HCI_OP_LINK_KEY_NEG_REPLY 0x040c struct hci_cp_link_key_neg_reply { bdaddr_t bdaddr; @@ -466,6 +463,69 @@ struct hci_cp_io_capability_neg_reply { __u8 reason; } __packed; +#define HCI_OP_CREATE_PHYS_LINK 0x0435 +struct hci_cp_create_phys_link { + __u8 phy_handle; + __u8 key_len; + __u8 type; + __u8 data[32]; +} __packed; + +#define HCI_OP_ACCEPT_PHYS_LINK 0x0436 +struct hci_cp_accept_phys_link { + __u8 phy_handle; + __u8 key_len; + __u8 type; + __u8 data[32]; +} __packed; + +#define HCI_OP_DISCONN_PHYS_LINK 0x0437 +struct hci_cp_disconn_phys_link { + __u8 phy_handle; + __u8 reason; +} __packed; + +struct hci_ext_fs { + __u8 id; + __u8 type; + __le16 max_sdu; + __le32 sdu_arr_time; + __le32 acc_latency; + __le32 flush_to; +} __packed; + +#define HCI_OP_CREATE_LOGICAL_LINK 0x0438 +#define HCI_OP_ACCEPT_LOGICAL_LINK 0x0439 +struct hci_cp_create_logical_link { + __u8 phy_handle; + struct hci_ext_fs tx_fs; + struct hci_ext_fs rx_fs; +} __packed; + +#define HCI_OP_DISCONN_LOGICAL_LINK 0x043a +struct hci_cp_disconn_logical_link { + __le16 log_handle; +} __packed; + +#define HCI_OP_LOGICAL_LINK_CANCEL 0x043b +struct hci_cp_logical_link_cancel { + __u8 phy_handle; + __u8 flow_spec_id; +} __packed; + +struct hci_rp_logical_link_cancel { + __u8 status; + __u8 phy_handle; + __u8 flow_spec_id; +} __packed; + +#define HCI_OP_FLOW_SPEC_MODIFY 0x043c +struct hci_cp_flow_spec_modify { + __le16 log_handle; + struct hci_ext_fs tx_fs; + struct hci_ext_fs rx_fs; +} __packed; + #define HCI_OP_SNIFF_MODE 0x0803 struct hci_cp_sniff_mode { __le16 handle; @@ -664,10 +724,70 @@ struct hci_rp_read_local_oob_data { #define HCI_OP_READ_INQ_RSP_TX_POWER 0x0c58 -#define HCI_OP_WRITE_LE_HOST_SUPPORTED 0x0c6d -struct hci_cp_write_le_host_supported { - __u8 le; - __u8 simul; +#define HCI_OP_READ_LL_TIMEOUT 0x0c61 +struct hci_rp_read_ll_timeout { + __u8 status; + __le16 timeout; +} __packed; + +#define HCI_OP_WRITE_LL_TIMEOUT 0x0c62 +struct hci_cp_write_ll_timeout { + __le16 timeout; +} __packed; + +#define HCI_OP_SET_EVENT_MASK_PAGE2 0x0c63 +struct hci_cp_set_event_mask_page2 { + __u8 mask[8]; +} __packed; + +#define HCI_OP_READ_LOCATION_DATA 0x0c64 +struct hci_rp_read_location_data { + __u8 status; + __u8 loc_dom_aware; + __u8 loc_dom; + __u8 loc_dom_opts; + __u8 loc_opts; +} __packed; + +#define HCI_OP_WRITE_LOCATION_DATA 0x0c65 +struct hci_cp_write_location_data { + __u8 loc_dom_aware; + __u8 loc_dom; + __u8 loc_dom_opts; + __u8 loc_opts; +} __packed; + +#define HCI_OP_READ_FLOW_CONTROL_MODE 0x0c66 +struct hci_rp_read_flow_control_mode { + __u8 status; + __u8 mode; +} __packed; + +#define HCI_OP_WRITE_FLOW_CONTROL_MODE 0x0c67 +struct hci_cp_write_flow_control_mode { + __u8 mode; +} __packed; + +#define HCI_OP_READ_BE_FLUSH_TIMEOUT 0x0c69 +struct hci_cp_read_be_flush_timeout { + __le16 log_handle; +} __packed; + +struct hci_rp_read_be_flush_timeout { + __u8 status; + __le32 timeout; +} __packed; + +#define HCI_OP_WRITE_BE_FLUSH_TIMEOUT 0x0c6a +struct hci_cp_write_be_flush_timeout { + __le16 log_handle; + __le32 timeout; +} __packed; + +#define HCI_OP_SHORT_RANGE_MODE 0x0c6b +struct hci_cp_short_range_mode { + __u8 phy_handle; + __u8 mode; } __packed; #define HCI_OP_READ_LOCAL_VERSION 0x1001 @@ -693,9 +813,6 @@ struct hci_rp_read_local_features { } __packed; #define HCI_OP_READ_LOCAL_EXT_FEATURES 0x1004 -struct hci_cp_read_local_ext_features { - __u8 page; -} __packed; struct hci_rp_read_local_ext_features { __u8 status; __u8 page; @@ -718,6 +835,56 @@ struct hci_rp_read_bd_addr { bdaddr_t bdaddr; } __packed; +#define HCI_OP_READ_DATA_BLOCK_SIZE 0x100a +struct hci_rp_read_data_block_size { + __u8 status; + __le16 max_acl_len; + __le16 data_block_len; + __le16 num_blocks; +} __packed; + +#define HCI_OP_READ_LOCAL_AMP_INFO 0x1409 +struct hci_rp_read_local_amp_info { + __u8 status; + __u8 amp_status; + __le32 total_bw; + __le32 max_bw; + __le32 min_latency; + __le32 max_pdu; + __u8 amp_type; + __le16 pal_cap; + __le16 max_assoc_size; + __le32 max_flush_to; + __le32 be_flush_to; +} __packed; + +#define HCI_OP_READ_LOCAL_AMP_ASSOC 0x140a +struct hci_cp_read_local_amp_assoc { + __u8 phy_handle; + __le16 len_so_far; + __le16 max_len; +} __packed; + +struct hci_rp_read_local_amp_assoc { + __u8 status; + __u8 phy_handle; + __le16 rem_len; + __u8 frag[248]; +} __packed; + +#define HCI_OP_WRITE_REMOTE_AMP_ASSOC 0x140b +struct hci_cp_write_remote_amp_assoc { + __u8 phy_handle; + __le16 len_so_far; + __le16 rem_len; + __u8 frag[248]; +} __packed; + +struct hci_rp_write_remote_amp_assoc { + __u8 status; + __u8 phy_handle; +} __packed; + #define HCI_OP_LE_SET_EVENT_MASK 0x2001 struct hci_cp_le_set_event_mask { __u8 mask[8]; @@ -731,10 +898,6 @@ struct hci_rp_le_read_buffer_size { } __packed; #define HCI_OP_LE_SET_SCAN_ENABLE 0x200c -struct hci_cp_le_set_scan_enable { - __u8 enable; - __u8 filter_dup; -} __packed; #define HCI_OP_LE_CREATE_CONN 0x200d struct hci_cp_le_create_conn { @@ -1082,14 +1245,6 @@ struct hci_ev_le_conn_complete { __u8 clk_accurancy; } __packed; -#define HCI_EV_LE_LTK_REQ 0x05 -struct hci_ev_le_ltk_req { - __le16 handle; - __u8 random[8]; - __le16 ediv; -} __packed; - -/* Advertising report event types */ #define ADV_IND 0x00 #define ADV_DIRECT_IND 0x01 #define ADV_SCAN_IND 0x02 @@ -1108,6 +1263,72 @@ struct hci_ev_le_advertising_info { __u8 data[0]; } __packed; +#define HCI_EV_LE_LTK_REQ 0x05 +struct hci_ev_le_ltk_req { + __le16 handle; + __u8 random[8]; + __le16 ediv; +} __packed; + +#define HCI_EV_PHYS_LINK_COMPLETE 0x40 +struct hci_ev_phys_link_complete { + __u8 status; + __u8 phy_handle; +} __packed; + +#define HCI_EV_CHANNEL_SELECTED 0x41 +struct hci_ev_channel_selected { + __u8 phy_handle; +} __packed; + +#define HCI_EV_DISCONN_PHYS_LINK_COMPLETE 0x42 +struct hci_ev_disconn_phys_link_complete { + __u8 status; + __u8 phy_handle; + __u8 reason; +} __packed; + +#define HCI_EV_LOG_LINK_COMPLETE 0x45 +struct hci_ev_log_link_complete { + __u8 status; + __le16 log_handle; + __u8 phy_handle; + __u8 flow_spec_id; +} __packed; + +#define HCI_EV_DISCONN_LOG_LINK_COMPLETE 0x46 +struct hci_ev_disconn_log_link_complete { + __u8 status; + __le16 log_handle; + __u8 reason; +} __packed; + +#define HCI_EV_FLOW_SPEC_MODIFY_COMPLETE 0x47 +struct hci_ev_flow_spec_modify_complete { + __u8 status; + __le16 log_handle; +} __packed; + +#define HCI_EV_NUM_COMP_BLOCKS 0x48 +struct hci_ev_num_comp_blocks { + __le16 total_num_blocks; + __u8 num_hndl; + /* variable length part */ +} __packed; + +#define HCI_EV_SHORT_RANGE_MODE_COMPLETE 0x4c +struct hci_ev_short_range_mode_complete { + __u8 status; + __u8 phy_handle; + __u8 mode; +} __packed; + +#define HCI_EV_AMP_STATUS_CHANGE 0x4d +struct hci_ev_amp_status_change { + __u8 status; + __u8 amp_status; +} __packed; + /* Internal events generated by Bluetooth stack */ #define HCI_EV_STACK_INTERNAL 0xfd struct hci_ev_stack_internal { @@ -1155,6 +1376,7 @@ struct hci_sco_hdr { __u8 dlen; } __packed; +#ifdef __KERNEL__ #include static inline struct hci_event_hdr *hci_event_hdr(const struct sk_buff *skb) { @@ -1170,6 +1392,7 @@ static inline struct hci_sco_hdr *hci_sco_hdr(const struct sk_buff *skb) { return (struct hci_sco_hdr *) skb->data; } +#endif /* Command opcode pack/unpack */ #define hci_opcode_pack(ogf, ocf) (__u16) ((ocf & 0x03ff)|(ogf << 10)) @@ -1266,6 +1489,8 @@ struct hci_conn_info { __u32 mtu; __u32 cnt; __u32 pkts; + __u8 pending_sec_level; + __u8 ssp_mode; }; struct hci_dev_req { diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 27e1b36b602..7e0e1518b90 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1,6 +1,6 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (c) 2000-2001, 2010, Code Aurora Forum. All rights reserved. + Copyright (c) 2000-2001, 2010-2011, Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -63,6 +63,11 @@ struct hci_conn_hash { unsigned int le_num; }; +struct hci_chan_list { + struct list_head list; + spinlock_t lock; +}; + struct bdaddr_list { struct list_head list; bdaddr_t bdaddr; @@ -79,6 +84,10 @@ struct key_master_id { u8 rand[8]; } __packed; +#define KEY_TYPE_LTK 0x11 +#define KEY_TYPE_IRK 0x12 +#define KEY_TYPE_CSRK 0x13 + struct link_key_data { bdaddr_t bdaddr; u8 type; @@ -129,7 +138,6 @@ struct hci_dev { __u8 major_class; __u8 minor_class; __u8 features[8]; - __u8 extfeatures[8]; __u8 commands[64]; __u8 ssp_mode; __u8 hci_ver; @@ -149,7 +157,16 @@ struct hci_dev { __u16 sniff_min_interval; __u16 sniff_max_interval; - unsigned int auto_accept_delay; + __u8 amp_status; + __u32 amp_total_bw; + __u32 amp_max_bw; + __u32 amp_min_latency; + __u32 amp_max_pdu; + __u8 amp_type; + __u16 amp_pal_cap; + __u16 amp_assoc_size; + __u32 amp_max_flush_to; + __u32 amp_be_flush_to; unsigned long quirks; @@ -158,6 +175,8 @@ struct hci_dev { unsigned int sco_cnt; unsigned int le_cnt; + __u8 flow_ctl_mode; + unsigned int acl_mtu; unsigned int sco_mtu; unsigned int le_mtu; @@ -165,6 +184,8 @@ struct hci_dev { unsigned int sco_pkts; unsigned int le_pkts; + unsigned int data_block_len; + unsigned long acl_last_tx; unsigned long sco_last_tx; unsigned long le_last_tx; @@ -198,6 +219,7 @@ struct hci_dev { struct inquiry_cache inq_cache; struct hci_conn_hash conn_hash; + struct hci_chan_list chan_list; struct list_head blacklist; struct list_head uuids; @@ -207,6 +229,7 @@ struct hci_dev { struct list_head remote_oob_data; struct list_head adv_entries; + rwlock_t adv_entries_lock; struct timer_list adv_timer; struct hci_dev_stats stat; @@ -240,9 +263,11 @@ struct hci_conn { struct list_head list; atomic_t refcnt; + spinlock_t lock; bdaddr_t dst; - __u8 dst_type; + __u8 dst_id; + __u8 dst_type; __u16 handle; __u16 state; __u8 mode; @@ -256,7 +281,6 @@ struct hci_conn { __u16 pkt_type; __u16 link_policy; __u32 link_mode; - __u8 key_type; __u8 auth_type; __u8 sec_level; __u8 pending_sec_level; @@ -277,7 +301,6 @@ struct hci_conn { struct timer_list disc_timer; struct timer_list idle_timer; - struct timer_list auto_accept_timer; struct work_struct work_add; struct work_struct work_del; @@ -288,6 +311,10 @@ struct hci_conn { struct hci_dev *hdev; void *l2cap_data; void *sco_data; + void *priv; + + __u8 link_key[16]; + __u8 key_type; struct hci_conn *link; @@ -296,6 +323,18 @@ struct hci_conn { void (*disconn_cfm_cb) (struct hci_conn *conn, u8 reason); }; +struct hci_chan { + struct list_head list; + struct hci_dev *hdev; + __u16 state; + atomic_t refcnt; + __u16 ll_handle; + struct hci_ext_fs tx_fs; + struct hci_ext_fs rx_fs; + struct hci_conn *conn; + void *l2cap_sk; +}; + extern struct hci_proto *hci_proto[]; extern struct list_head hci_dev_list; extern struct list_head hci_cb_list; @@ -335,14 +374,12 @@ static inline long inquiry_entry_age(struct inquiry_entry *e) return jiffies - e->timestamp; } -struct inquiry_entry *hci_inquiry_cache_lookup(struct hci_dev *hdev, - bdaddr_t *bdaddr); +struct inquiry_entry *hci_inquiry_cache_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr); void hci_inquiry_cache_update(struct hci_dev *hdev, struct inquiry_data *data); /* ----- HCI Connections ----- */ enum { HCI_CONN_AUTH_PEND, - HCI_CONN_REAUTH_PEND, HCI_CONN_ENCRYPT_PEND, HCI_CONN_RSWITCH_PEND, HCI_CONN_MODE_CHANGE_PEND, @@ -409,6 +446,13 @@ static inline struct hci_conn *hci_conn_hash_lookup_handle(struct hci_dev *hdev, return NULL; } +static inline void hci_chan_list_init(struct hci_dev *hdev) +{ + struct hci_chan_list *h = &hdev->chan_list; + INIT_LIST_HEAD(&h->list); + spin_lock_init(&h->lock); +} + static inline struct hci_conn *hci_conn_hash_lookup_ba(struct hci_dev *hdev, __u8 type, bdaddr_t *ba) { @@ -424,6 +468,21 @@ static inline struct hci_conn *hci_conn_hash_lookup_ba(struct hci_dev *hdev, return NULL; } +static inline struct hci_conn *hci_conn_hash_lookup_id(struct hci_dev *hdev, + bdaddr_t *ba, __u8 id) +{ + struct hci_conn_hash *h = &hdev->conn_hash; + struct list_head *p; + struct hci_conn *c; + + list_for_each(p, &h->list) { + c = list_entry(p, struct hci_conn, list); + if (!bacmp(&c->dst, ba) && (c->dst_id == id)) + return c; + } + return NULL; +} + static inline struct hci_conn *hci_conn_hash_lookup_state(struct hci_dev *hdev, __u8 type, __u16 state) { @@ -439,6 +498,36 @@ static inline struct hci_conn *hci_conn_hash_lookup_state(struct hci_dev *hdev, return NULL; } +static inline struct hci_chan *hci_chan_list_lookup_handle(struct hci_dev *hdev, + __u16 handle) +{ + struct hci_chan_list *l = &hdev->chan_list; + struct list_head *p; + struct hci_chan *c; + + list_for_each(p, &l->list) { + c = list_entry(p, struct hci_chan, list); + if (c->ll_handle == handle) + return c; + } + return NULL; +} + +static inline struct hci_chan *hci_chan_list_lookup_id(struct hci_dev *hdev, + __u8 handle) +{ + struct hci_chan_list *l = &hdev->chan_list; + struct list_head *p; + struct hci_chan *c; + + list_for_each(p, &l->list) { + c = list_entry(p, struct hci_chan, list); + if (c->conn->handle == handle) + return c; + } + return NULL; +} + void hci_acl_connect(struct hci_conn *conn); void hci_acl_disconn(struct hci_conn *conn, __u8 reason); void hci_add_sco(struct hci_conn *conn, __u16 handle); @@ -447,18 +536,39 @@ void hci_sco_setup(struct hci_conn *conn, __u8 status); struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, __u16 pkt_type, bdaddr_t *dst); +struct hci_conn *hci_le_conn_add(struct hci_dev *hdev, bdaddr_t *dst, + __u8 addr_type); int hci_conn_del(struct hci_conn *conn); void hci_conn_hash_flush(struct hci_dev *hdev); void hci_conn_check_pending(struct hci_dev *hdev); +struct hci_chan *hci_chan_add(struct hci_dev *hdev); +int hci_chan_del(struct hci_chan *chan); +static inline void hci_chan_hold(struct hci_chan *chan) +{ + atomic_inc(&chan->refcnt); +} +void hci_chan_put(struct hci_chan *chan); + +struct hci_chan *hci_chan_accept(struct hci_conn *hcon, + struct hci_ext_fs *tx_fs, + struct hci_ext_fs *rx_fs); +struct hci_chan *hci_chan_create(struct hci_conn *hcon, + struct hci_ext_fs *tx_fs, + struct hci_ext_fs *rx_fs); +void hci_chan_modify(struct hci_chan *chan, + struct hci_ext_fs *tx_fs, + struct hci_ext_fs *rx_fs); + struct hci_conn *hci_connect(struct hci_dev *hdev, int type, - __u16 pkt_type, bdaddr_t *dst, - __u8 sec_level, __u8 auth_type); + __u16 pkt_type, bdaddr_t *dst, + __u8 sec_level, __u8 auth_type); int hci_conn_check_link_mode(struct hci_conn *conn); -int hci_conn_check_secure(struct hci_conn *conn, __u8 sec_level); int hci_conn_security(struct hci_conn *conn, __u8 sec_level, __u8 auth_type); int hci_conn_change_link_key(struct hci_conn *conn); int hci_conn_switch_role(struct hci_conn *conn, __u8 role); +void hci_disconnect(struct hci_conn *conn, __u8 reason); +void hci_disconnect_amp(struct hci_conn *conn, __u8 reason); void hci_conn_enter_active_mode(struct hci_conn *conn, __u8 force_active); void hci_conn_enter_sniff_mode(struct hci_conn *conn); @@ -482,12 +592,10 @@ static inline void hci_conn_put(struct hci_conn *conn) timeo = msecs_to_jiffies(conn->disc_timeout); if (!conn->out) timeo *= 20; - } else { + } else timeo = msecs_to_jiffies(10); - } - } else { + } else timeo = msecs_to_jiffies(10); - } mod_timer(&conn->disc_timer, jiffies + timeo); } } @@ -525,6 +633,7 @@ static inline struct hci_dev *hci_dev_hold(struct hci_dev *d) struct hci_dev *hci_dev_get(int index); struct hci_dev *hci_get_route(bdaddr_t *src, bdaddr_t *dst); +struct hci_dev *hci_dev_get_type(__u8 amp_type); struct hci_dev *hci_alloc_dev(void); void hci_free_dev(struct hci_dev *hdev); @@ -542,19 +651,18 @@ int hci_get_dev_info(void __user *arg); int hci_get_conn_list(void __user *arg); int hci_get_conn_info(struct hci_dev *hdev, void __user *arg); int hci_get_auth_info(struct hci_dev *hdev, void __user *arg); +int hci_set_auth_info(struct hci_dev *hdev, void __user *arg); int hci_inquiry(void __user *arg); struct bdaddr_list *hci_blacklist_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr); int hci_blacklist_clear(struct hci_dev *hdev); -int hci_blacklist_add(struct hci_dev *hdev, bdaddr_t *bdaddr); -int hci_blacklist_del(struct hci_dev *hdev, bdaddr_t *bdaddr); int hci_uuids_clear(struct hci_dev *hdev); int hci_link_keys_clear(struct hci_dev *hdev); struct link_key *hci_find_link_key(struct hci_dev *hdev, bdaddr_t *bdaddr); -int hci_add_link_key(struct hci_dev *hdev, struct hci_conn *conn, int new_key, - bdaddr_t *bdaddr, u8 *val, u8 type, u8 pin_len); +int hci_add_link_key(struct hci_dev *hdev, int new_key, bdaddr_t *bdaddr, + u8 *key, u8 type, u8 pin_len); struct link_key *hci_find_ltk(struct hci_dev *hdev, __le16 ediv, u8 rand[8]); struct link_key *hci_find_link_key_type(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type); @@ -601,9 +709,6 @@ void hci_conn_del_sysfs(struct hci_conn *conn); #define lmp_no_flush_capable(dev) ((dev)->features[6] & LMP_NO_FLUSH) #define lmp_le_capable(dev) ((dev)->features[4] & LMP_LE) -/* ----- Extended LMP capabilities ----- */ -#define lmp_host_le_capable(dev) ((dev)->extfeatures[0] & LMP_HOST_LE) - /* ----- HCI protocols ----- */ struct hci_proto { char *name; @@ -612,20 +717,19 @@ struct hci_proto { void *priv; - int (*connect_ind) (struct hci_dev *hdev, bdaddr_t *bdaddr, - __u8 type); + int (*connect_ind) (struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type); int (*connect_cfm) (struct hci_conn *conn, __u8 status); int (*disconn_ind) (struct hci_conn *conn); int (*disconn_cfm) (struct hci_conn *conn, __u8 reason); - int (*recv_acldata) (struct hci_conn *conn, struct sk_buff *skb, - __u16 flags); + int (*recv_acldata) (struct hci_conn *conn, struct sk_buff *skb, __u16 flags); int (*recv_scodata) (struct hci_conn *conn, struct sk_buff *skb); - int (*security_cfm) (struct hci_conn *conn, __u8 status, - __u8 encrypt); + int (*security_cfm) (struct hci_conn *conn, __u8 status, __u8 encrypt); + int (*create_cfm) (struct hci_chan *chan, __u8 status); + int (*modify_cfm) (struct hci_chan *chan, __u8 status); + int (*destroy_cfm) (struct hci_chan *chan, __u8 status); }; -static inline int hci_proto_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, - __u8 type) +static inline int hci_proto_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type) { register struct hci_proto *hp; int mask = 0; @@ -711,8 +815,7 @@ static inline void hci_proto_auth_cfm(struct hci_conn *conn, __u8 status) conn->security_cfm_cb(conn, status); } -static inline void hci_proto_encrypt_cfm(struct hci_conn *conn, __u8 status, - __u8 encrypt) +static inline void hci_proto_encrypt_cfm(struct hci_conn *conn, __u8 status, __u8 encrypt) { register struct hci_proto *hp; @@ -728,6 +831,33 @@ static inline void hci_proto_encrypt_cfm(struct hci_conn *conn, __u8 status, conn->security_cfm_cb(conn, status); } +static inline void hci_proto_create_cfm(struct hci_chan *chan, __u8 status) +{ + register struct hci_proto *hp; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->create_cfm) + hp->create_cfm(chan, status); +} + +static inline void hci_proto_modify_cfm(struct hci_chan *chan, __u8 status) +{ + register struct hci_proto *hp; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->modify_cfm) + hp->modify_cfm(chan, status); +} + +static inline void hci_proto_destroy_cfm(struct hci_chan *chan, __u8 status) +{ + register struct hci_proto *hp; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->destroy_cfm) + hp->destroy_cfm(chan, status); +} + int hci_register_proto(struct hci_proto *hproto); int hci_unregister_proto(struct hci_proto *hproto); @@ -737,8 +867,7 @@ struct hci_cb { char *name; - void (*security_cfm) (struct hci_conn *conn, __u8 status, - __u8 encrypt); + void (*security_cfm) (struct hci_conn *conn, __u8 status, __u8 encrypt); void (*key_change_cfm) (struct hci_conn *conn, __u8 status); void (*role_switch_cfm) (struct hci_conn *conn, __u8 status, __u8 role); }; @@ -764,8 +893,7 @@ static inline void hci_auth_cfm(struct hci_conn *conn, __u8 status) read_unlock_bh(&hci_cb_list_lock); } -static inline void hci_encrypt_cfm(struct hci_conn *conn, __u8 status, - __u8 encrypt) +static inline void hci_encrypt_cfm(struct hci_conn *conn, __u8 status, __u8 encrypt) { struct list_head *p; @@ -799,8 +927,7 @@ static inline void hci_key_change_cfm(struct hci_conn *conn, __u8 status) read_unlock_bh(&hci_cb_list_lock); } -static inline void hci_role_switch_cfm(struct hci_conn *conn, __u8 status, - __u8 role) +static inline void hci_role_switch_cfm(struct hci_conn *conn, __u8 status, __u8 role) { struct list_head *p; @@ -819,8 +946,29 @@ int hci_unregister_cb(struct hci_cb *hcb); int hci_register_notifier(struct notifier_block *nb); int hci_unregister_notifier(struct notifier_block *nb); +/* AMP Manager event callbacks */ +struct amp_mgr_cb { + struct list_head list; + void (*amp_cmd_complete_event) (struct hci_dev *hdev, __u16 opcode, + struct sk_buff *skb); + void (*amp_cmd_status_event) (struct hci_dev *hdev, __u16 opcode, + __u8 status); + void (*amp_event) (struct hci_dev *hdev, __u8 ev_code, + struct sk_buff *skb); +}; + +void hci_amp_cmd_complete(struct hci_dev *hdev, __u16 opcode, + struct sk_buff *skb); +void hci_amp_cmd_status(struct hci_dev *hdev, __u16 opcode, __u8 status); +void hci_amp_event_packet(struct hci_dev *hdev, __u8 ev_code, + struct sk_buff *skb); + +int hci_register_amp(struct amp_mgr_cb *acb); +int hci_unregister_amp(struct amp_mgr_cb *acb); + int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param); -void hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags); +void hci_send_acl(struct hci_conn *conn, struct hci_chan *chan, + struct sk_buff *skb, __u16 flags); void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb); void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 opcode); @@ -838,16 +986,15 @@ int mgmt_index_removed(u16 index); int mgmt_powered(u16 index, u8 powered); int mgmt_discoverable(u16 index, u8 discoverable); int mgmt_connectable(u16 index, u8 connectable); -int mgmt_new_key(u16 index, struct link_key *key, u8 persistent); +int mgmt_new_key(u16 index, struct link_key *key, u8 old_key_type); int mgmt_connected(u16 index, bdaddr_t *bdaddr); int mgmt_disconnected(u16 index, bdaddr_t *bdaddr); int mgmt_disconnect_failed(u16 index); int mgmt_connect_failed(u16 index, bdaddr_t *bdaddr, u8 status); -int mgmt_pin_code_request(u16 index, bdaddr_t *bdaddr, u8 secure); +int mgmt_pin_code_request(u16 index, bdaddr_t *bdaddr); int mgmt_pin_code_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status); int mgmt_pin_code_neg_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status); -int mgmt_user_confirm_request(u16 index, bdaddr_t *bdaddr, __le32 value, - u8 confirm_hint); +int mgmt_user_confirm_request(u16 index, bdaddr_t *bdaddr, __le32 value); int mgmt_user_confirm_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status); int mgmt_user_confirm_neg_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status); @@ -858,7 +1005,6 @@ int mgmt_read_local_oob_data_reply_complete(u16 index, u8 *hash, u8 *randomizer, int mgmt_device_found(u16 index, bdaddr_t *bdaddr, u8 *dev_class, s8 rssi, u8 *eir); int mgmt_remote_name(u16 index, bdaddr_t *bdaddr, u8 *name); -int mgmt_discovering(u16 index, u8 discovering); /* HCI info for socket */ #define hci_pi(sk) ((struct hci_pinfo *) sk) diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 578545a2a49..be2147eff8c 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -1,6 +1,6 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2000-2001, 2010-2011 Code Aurora Forum. All rights reserved. Copyright (C) 2009-2010 Gustavo F. Padovan Copyright (C) 2010 Google Inc. @@ -30,17 +30,28 @@ /* L2CAP defaults */ #define L2CAP_DEFAULT_MTU 672 #define L2CAP_DEFAULT_MIN_MTU 48 +#define L2CAP_DEFAULT_MAX_SDU_SIZE 0xffff #define L2CAP_DEFAULT_FLUSH_TO 0xffff #define L2CAP_DEFAULT_TX_WINDOW 63 #define L2CAP_DEFAULT_MAX_TX 3 #define L2CAP_DEFAULT_RETRANS_TO 2000 /* 2 seconds */ #define L2CAP_DEFAULT_MONITOR_TO 12000 /* 12 seconds */ -#define L2CAP_DEFAULT_MAX_PDU_SIZE 1009 /* Sized for 3-DH5 packet */ +#define L2CAP_DEFAULT_MAX_PDU_SIZE 1482 /* Sized for AMP or BR/EDR */ #define L2CAP_DEFAULT_ACK_TO 200 +#define L2CAP_BREDR_MAX_PAYLOAD 1019 /* 3-DH5 packet */ +#define L2CAP_MAX_ERTM_QUEUED 5 +#define L2CAP_MIN_ERTM_QUEUED 2 + +#define L2CAP_A2MP_DEFAULT_MTU 670 + +#define L2CAP_TX_WIN_MAX_ENHANCED 0x3f +#define L2CAP_TX_WIN_MAX_EXTENDED 0x3fff #define L2CAP_LE_DEFAULT_MTU 23 #define L2CAP_CONN_TIMEOUT (40000) /* 40 seconds */ #define L2CAP_INFO_TIMEOUT (4000) /* 4 seconds */ +#define L2CAP_MOVE_TIMEOUT (2*HZ) /* 2 seconds */ +#define L2CAP_MOVE_ERTX_TIMEOUT (60*HZ) /* 60 seconds */ /* L2CAP socket address */ struct sockaddr_l2 { @@ -75,19 +86,26 @@ struct l2cap_conninfo { #define L2CAP_LM_TRUSTED 0x0008 #define L2CAP_LM_RELIABLE 0x0010 #define L2CAP_LM_SECURE 0x0020 +#define L2CAP_LM_FLUSHABLE 0x0040 /* L2CAP command codes */ -#define L2CAP_COMMAND_REJ 0x01 -#define L2CAP_CONN_REQ 0x02 -#define L2CAP_CONN_RSP 0x03 -#define L2CAP_CONF_REQ 0x04 -#define L2CAP_CONF_RSP 0x05 -#define L2CAP_DISCONN_REQ 0x06 -#define L2CAP_DISCONN_RSP 0x07 -#define L2CAP_ECHO_REQ 0x08 -#define L2CAP_ECHO_RSP 0x09 -#define L2CAP_INFO_REQ 0x0a -#define L2CAP_INFO_RSP 0x0b +#define L2CAP_COMMAND_REJ 0x01 +#define L2CAP_CONN_REQ 0x02 +#define L2CAP_CONN_RSP 0x03 +#define L2CAP_CONF_REQ 0x04 +#define L2CAP_CONF_RSP 0x05 +#define L2CAP_DISCONN_REQ 0x06 +#define L2CAP_DISCONN_RSP 0x07 +#define L2CAP_ECHO_REQ 0x08 +#define L2CAP_ECHO_RSP 0x09 +#define L2CAP_INFO_REQ 0x0a +#define L2CAP_INFO_RSP 0x0b +#define L2CAP_CREATE_CHAN_REQ 0x0c +#define L2CAP_CREATE_CHAN_RSP 0x0d +#define L2CAP_MOVE_CHAN_REQ 0x0e +#define L2CAP_MOVE_CHAN_RSP 0x0f +#define L2CAP_MOVE_CHAN_CFM 0x10 +#define L2CAP_MOVE_CHAN_CFM_RSP 0x11 #define L2CAP_CONN_PARAM_UPDATE_REQ 0x12 #define L2CAP_CONN_PARAM_UPDATE_RSP 0x13 @@ -98,36 +116,63 @@ struct l2cap_conninfo { #define L2CAP_FEAT_STREAMING 0x00000010 #define L2CAP_FEAT_FCS 0x00000020 #define L2CAP_FEAT_FIXED_CHAN 0x00000080 +#define L2CAP_FEAT_EXT_WINDOW 0x00000100 +#define L2CAP_FEAT_UCD 0x00000200 /* L2CAP checksum option */ #define L2CAP_FCS_NONE 0x00 #define L2CAP_FCS_CRC16 0x01 -/* L2CAP Control Field bit masks */ +/* L2CAP fixed channels */ +#define L2CAP_FC_L2CAP 0x02 +#define L2CAP_FC_A2MP 0x08 + +/* L2CAP Control Field */ #define L2CAP_CTRL_SAR 0xC000 #define L2CAP_CTRL_REQSEQ 0x3F00 #define L2CAP_CTRL_TXSEQ 0x007E -#define L2CAP_CTRL_RETRANS 0x0080 #define L2CAP_CTRL_FINAL 0x0080 #define L2CAP_CTRL_POLL 0x0010 #define L2CAP_CTRL_SUPERVISE 0x000C #define L2CAP_CTRL_FRAME_TYPE 0x0001 /* I- or S-Frame */ #define L2CAP_CTRL_TXSEQ_SHIFT 1 +#define L2CAP_CTRL_SUPERVISE_SHIFT 2 +#define L2CAP_CTRL_POLL_SHIFT 4 +#define L2CAP_CTRL_FINAL_SHIFT 7 #define L2CAP_CTRL_REQSEQ_SHIFT 8 #define L2CAP_CTRL_SAR_SHIFT 14 -/* L2CAP Supervisory Function */ -#define L2CAP_SUPER_RCV_READY 0x0000 -#define L2CAP_SUPER_REJECT 0x0004 -#define L2CAP_SUPER_RCV_NOT_READY 0x0008 -#define L2CAP_SUPER_SELECT_REJECT 0x000C +#define L2CAP_EXT_CTRL_SAR 0x00030000 +#define L2CAP_EXT_CTRL_REQSEQ 0x0000FFFC +#define L2CAP_EXT_CTRL_TXSEQ 0xFFFC0000 +#define L2CAP_EXT_CTRL_FINAL 0x00000002 +#define L2CAP_EXT_CTRL_POLL 0x00040000 +#define L2CAP_EXT_CTRL_SUPERVISE 0x00030000 +#define L2CAP_EXT_CTRL_FRAME_TYPE 0x00000001 /* I- or S-Frame */ + +#define L2CAP_EXT_CTRL_FINAL_SHIFT 1 +#define L2CAP_EXT_CTRL_REQSEQ_SHIFT 2 +#define L2CAP_EXT_CTRL_SAR_SHIFT 16 +#define L2CAP_EXT_CTRL_SUPERVISE_SHIFT 16 +#define L2CAP_EXT_CTRL_POLL_SHIFT 18 +#define L2CAP_EXT_CTRL_TXSEQ_SHIFT 18 + +/* L2CAP Supervisory Frame Types */ +#define L2CAP_SFRAME_RR 0x00 +#define L2CAP_SFRAME_REJ 0x01 +#define L2CAP_SFRAME_RNR 0x02 +#define L2CAP_SFRAME_SREJ 0x03 /* L2CAP Segmentation and Reassembly */ -#define L2CAP_SDU_UNSEGMENTED 0x0000 -#define L2CAP_SDU_START 0x4000 -#define L2CAP_SDU_END 0x8000 -#define L2CAP_SDU_CONTINUE 0xC000 +#define L2CAP_SAR_UNSEGMENTED 0x00 +#define L2CAP_SAR_START 0x01 +#define L2CAP_SAR_END 0x02 +#define L2CAP_SAR_CONTINUE 0x03 + +/* L2CAP ERTM / Streaming extra field lengths */ +#define L2CAP_SDULEN_SIZE 2 +#define L2CAP_FCS_SIZE 2 /* L2CAP structures */ struct l2cap_hdr { @@ -135,6 +180,8 @@ struct l2cap_hdr { __le16 cid; } __packed; #define L2CAP_HDR_SIZE 4 +#define L2CAP_ENHANCED_HDR_SIZE 6 +#define L2CAP_EXTENDED_HDR_SIZE 8 struct l2cap_cmd_hdr { __u8 code; @@ -162,6 +209,7 @@ struct l2cap_conn_rsp { /* channel indentifier */ #define L2CAP_CID_SIGNALING 0x0001 #define L2CAP_CID_CONN_LESS 0x0002 +#define L2CAP_CID_A2MP 0x0003 #define L2CAP_CID_LE_DATA 0x0004 #define L2CAP_CID_LE_SIGNALING 0x0005 #define L2CAP_CID_SMP 0x0006 @@ -197,6 +245,8 @@ struct l2cap_conf_rsp { #define L2CAP_CONF_UNACCEPT 0x0001 #define L2CAP_CONF_REJECT 0x0002 #define L2CAP_CONF_UNKNOWN 0x0003 +#define L2CAP_CONF_PENDING 0x0004 +#define L2CAP_CONF_FLOW_SPEC_REJECT 0x0005 struct l2cap_conf_opt { __u8 type; @@ -213,6 +263,13 @@ struct l2cap_conf_opt { #define L2CAP_CONF_QOS 0x03 #define L2CAP_CONF_RFC 0x04 #define L2CAP_CONF_FCS 0x05 +#define L2CAP_CONF_EXT_FS 0x06 +#define L2CAP_CONF_EXT_WINDOW 0x07 + +/* QOS Service type */ +#define L2CAP_SERVICE_NO_TRAFFIC 0x00 +#define L2CAP_SERVICE_BEST_EFFORT 0x01 +#define L2CAP_SERVICE_GUARANTEED 0x02 #define L2CAP_CONF_MAX_SIZE 22 @@ -225,6 +282,22 @@ struct l2cap_conf_rfc { __le16 max_pdu_size; } __packed; +struct l2cap_conf_ext_fs { + __u8 id; + __u8 type; + __le16 max_sdu; + __le32 sdu_arr_time; + __le32 acc_latency; + __le32 flush_to; +} __packed; + +struct l2cap_conf_prm { + __u8 fcs; + __le16 retrans_timeout; + __le16 monitor_timeout; + __le32 flush_to; +}; + #define L2CAP_MODE_BASIC 0x00 #define L2CAP_MODE_RETRANS 0x01 #define L2CAP_MODE_FLOWCTL 0x02 @@ -251,6 +324,79 @@ struct l2cap_info_rsp { __u8 data[0]; } __packed; +struct l2cap_create_chan_req { + __le16 psm; + __le16 scid; + __u8 amp_id; +} __attribute__ ((packed)); + +struct l2cap_create_chan_rsp { + __le16 dcid; + __le16 scid; + __le16 result; + __le16 status; +} __attribute__ ((packed)); + +#define L2CAP_CREATE_CHAN_SUCCESS (0x0000) +#define L2CAP_CREATE_CHAN_PENDING (0x0001) +#define L2CAP_CREATE_CHAN_REFUSED_PSM (0x0002) +#define L2CAP_CREATE_CHAN_REFUSED_SECURITY (0x0003) +#define L2CAP_CREATE_CHAN_REFUSED_RESOURCES (0x0004) +#define L2CAP_CREATE_CHAN_REFUSED_CONTROLLER (0x0005) + +#define L2CAP_CREATE_CHAN_STATUS_NONE (0x0000) +#define L2CAP_CREATE_CHAN_STATUS_AUTHENTICATION (0x0001) +#define L2CAP_CREATE_CHAN_STATUS_AUTHORIZATION (0x0002) + +struct l2cap_move_chan_req { + __le16 icid; + __u8 dest_amp_id; +} __attribute__ ((packed)); + +struct l2cap_move_chan_rsp { + __le16 icid; + __le16 result; +} __attribute__ ((packed)); + +#define L2CAP_MOVE_CHAN_SUCCESS (0x0000) +#define L2CAP_MOVE_CHAN_PENDING (0x0001) +#define L2CAP_MOVE_CHAN_REFUSED_CONTROLLER (0x0002) +#define L2CAP_MOVE_CHAN_REFUSED_SAME_ID (0x0003) +#define L2CAP_MOVE_CHAN_REFUSED_CONFIG (0x0004) +#define L2CAP_MOVE_CHAN_REFUSED_COLLISION (0x0005) +#define L2CAP_MOVE_CHAN_REFUSED_NOT_ALLOWED (0x0006) + +struct l2cap_move_chan_cfm { + __le16 icid; + __le16 result; +} __attribute__ ((packed)); + +#define L2CAP_MOVE_CHAN_CONFIRMED (0x0000) +#define L2CAP_MOVE_CHAN_UNCONFIRMED (0x0001) + +struct l2cap_move_chan_cfm_rsp { + __le16 icid; +} __attribute__ ((packed)); + +struct l2cap_amp_signal_work { + struct work_struct work; + struct l2cap_cmd_hdr cmd; + struct l2cap_conn *conn; + struct sk_buff *skb; + u8 *data; +}; + +struct l2cap_resegment_work { + struct work_struct work; + struct sock *sk; +}; + +struct l2cap_logical_link_work { + struct work_struct work; + struct hci_chan *chan; + u8 status; +}; + /* info type */ #define L2CAP_IT_CL_MTU 0x0001 #define L2CAP_IT_FEAT_MASK 0x0002 @@ -275,98 +421,10 @@ struct l2cap_conn_param_update_rsp { #define L2CAP_CONN_PARAM_ACCEPTED 0x0000 #define L2CAP_CONN_PARAM_REJECTED 0x0001 -/* ----- L2CAP channels and connections ----- */ -struct srej_list { - __u8 tx_seq; - struct list_head list; -}; - -struct l2cap_chan { - struct sock *sk; - - struct l2cap_conn *conn; - - __u8 state; - - atomic_t refcnt; - - __le16 psm; - __u16 dcid; - __u16 scid; - - __u16 imtu; - __u16 omtu; - __u16 flush_to; - __u8 mode; - __u8 chan_type; - - __le16 sport; - - __u8 sec_level; - __u8 role_switch; - __u8 force_reliable; - __u8 flushable; - __u8 force_active; - - __u8 ident; - - __u8 conf_req[64]; - __u8 conf_len; - __u8 num_conf_req; - __u8 num_conf_rsp; - - __u8 fcs; - - __u8 tx_win; - __u8 max_tx; - __u16 retrans_timeout; - __u16 monitor_timeout; - __u16 mps; - - unsigned long conf_state; - unsigned long conn_state; - - __u8 next_tx_seq; - __u8 expected_ack_seq; - __u8 expected_tx_seq; - __u8 buffer_seq; - __u8 buffer_seq_srej; - __u8 srej_save_reqseq; - __u8 frames_sent; - __u8 unacked_frames; - __u8 retry_count; - __u8 num_acked; - __u16 sdu_len; - __u16 partial_sdu_len; - struct sk_buff *sdu; - - __u8 remote_tx_win; - __u8 remote_max_tx; - __u16 remote_mps; - - struct timer_list chan_timer; - struct timer_list retrans_timer; - struct timer_list monitor_timer; - struct timer_list ack_timer; - struct sk_buff *tx_send_head; - struct sk_buff_head tx_q; - struct sk_buff_head srej_q; - struct list_head srej_l; - - struct list_head list; - struct list_head global_l; - - void *data; - struct l2cap_ops *ops; -}; - -struct l2cap_ops { - char *name; - - struct l2cap_chan *(*new_connection) (void *data); - int (*recv) (void *data, struct sk_buff *skb); - void (*close) (void *data); - void (*state_change) (void *data, int state); +/* ----- L2CAP connections ----- */ +struct l2cap_chan_list { + struct sock *head; + rwlock_t lock; }; struct l2cap_conn { @@ -378,6 +436,8 @@ struct l2cap_conn { unsigned int mtu; __u32 feat_mask; + __u8 fc_mask; + struct amp_mgr *mgr; __u8 info_state; __u8 info_ident; @@ -401,100 +461,258 @@ struct l2cap_conn { struct timer_list security_timer; - struct list_head chan_l; - rwlock_t chan_lock; + struct l2cap_chan_list chan_list; +}; + +struct sock_del_list { + struct sock *sk; + struct list_head list; }; #define L2CAP_INFO_CL_MTU_REQ_SENT 0x01 #define L2CAP_INFO_FEAT_MASK_REQ_SENT 0x04 #define L2CAP_INFO_FEAT_MASK_REQ_DONE 0x08 -#define L2CAP_CHAN_RAW 1 -#define L2CAP_CHAN_CONN_LESS 2 -#define L2CAP_CHAN_CONN_ORIENTED 3 - -/* ----- L2CAP socket info ----- */ +/* ----- L2CAP channel and socket info ----- */ #define l2cap_pi(sk) ((struct l2cap_pinfo *) sk) +#define TX_QUEUE(sk) (&l2cap_pi(sk)->tx_queue) +#define SREJ_QUEUE(sk) (&l2cap_pi(sk)->srej_queue) + +struct l2cap_seq_list { + __u16 head; + __u16 tail; + __u16 size; + __u16 mask; + __u16 *list; +}; struct l2cap_pinfo { struct bt_sock bt; - struct l2cap_chan *chan; - struct sk_buff *rx_busy_skb; -}; + __le16 psm; + __u16 dcid; + __u16 scid; -enum { - CONF_REQ_SENT, - CONF_INPUT_DONE, - CONF_OUTPUT_DONE, - CONF_MTU_DONE, - CONF_MODE_DONE, - CONF_CONNECT_PEND, - CONF_NO_FCS_RECV, - CONF_STATE2_DEVICE, -}; + __u16 imtu; + __u16 omtu; + __u16 flush_to; + __u8 mode; + __u8 fixed_channel; + __u8 num_conf_req; + __u8 num_conf_rsp; -#define L2CAP_CONF_MAX_CONF_REQ 2 -#define L2CAP_CONF_MAX_CONF_RSP 2 + __u8 fcs; + __u8 sec_level; + __u8 role_switch; + __u8 force_reliable; + __u8 flushable; + __u8 force_active; -enum { - CONN_SAR_SDU, - CONN_SREJ_SENT, - CONN_WAIT_F, - CONN_SREJ_ACT, - CONN_SEND_PBIT, - CONN_REMOTE_BUSY, - CONN_LOCAL_BUSY, - CONN_REJ_ACT, - CONN_SEND_FBIT, - CONN_RNR_SENT, -}; + __u8 conf_req[64]; + __u8 conf_len; + __u8 conf_ident; + __u16 conf_state; + __u8 conn_state; + __u8 tx_state; + __u8 rx_state; + __u8 reconf_state; + + __u8 amp_id; + __u8 amp_move_id; + __u8 amp_move_state; + __u8 amp_move_role; + __u8 amp_move_cmd_ident; + __u16 amp_move_reqseq; + __u16 amp_move_event; + + __u16 next_tx_seq; + __u16 expected_ack_seq; + __u16 expected_tx_seq; + __u16 buffer_seq; + __u16 srej_save_reqseq; + __u16 last_acked_seq; + __u32 frames_sent; + __u16 unacked_frames; + __u8 retry_count; + __u16 srej_queue_next; + __u16 sdu_len; + struct sk_buff *sdu; + struct sk_buff *sdu_last_frag; + atomic_t ertm_queued; -#define __set_chan_timer(c, t) l2cap_set_timer(c, &c->chan_timer, (t)) -#define __clear_chan_timer(c) l2cap_clear_timer(c, &c->chan_timer) -#define __set_retrans_timer(c) l2cap_set_timer(c, &c->retrans_timer, \ - L2CAP_DEFAULT_RETRANS_TO); -#define __clear_retrans_timer(c) l2cap_clear_timer(c, &c->retrans_timer) -#define __set_monitor_timer(c) l2cap_set_timer(c, &c->monitor_timer, \ - L2CAP_DEFAULT_MONITOR_TO); -#define __clear_monitor_timer(c) l2cap_clear_timer(c, &c->monitor_timer) -#define __set_ack_timer(c) l2cap_set_timer(c, &chan->ack_timer, \ - L2CAP_DEFAULT_ACK_TO); -#define __clear_ack_timer(c) l2cap_clear_timer(c, &c->ack_timer) + __u8 ident; -static inline int l2cap_tx_window_full(struct l2cap_chan *ch) -{ - int sub; + __u16 tx_win; + __u16 tx_win_max; + __u8 max_tx; + __u8 amp_pref; + __u16 remote_tx_win; + __u8 remote_max_tx; + __u8 extended_control; + __u16 retrans_timeout; + __u16 monitor_timeout; + __u16 remote_mps; + __u16 mps; - sub = (ch->next_tx_seq - ch->expected_ack_seq) % 64; + __le16 sport; - if (sub < 0) - sub += 64; + struct delayed_work retrans_work; + struct delayed_work monitor_work; + struct delayed_work ack_work; + struct work_struct tx_work; + struct sk_buff_head tx_queue; + struct sk_buff_head srej_queue; + struct l2cap_seq_list srej_list; + struct l2cap_seq_list retrans_list; + struct hci_conn *ampcon; + struct hci_chan *ampchan; + struct l2cap_conn *conn; + struct l2cap_conf_prm local_conf; + struct l2cap_conf_prm remote_conf; + struct l2cap_conf_ext_fs local_fs; + struct l2cap_conf_ext_fs remote_fs; + struct sock *next_c; + struct sock *prev_c; +}; - return sub == ch->remote_tx_win; -} +#define L2CAP_CONF_REQ_SENT 0x0001 +#define L2CAP_CONF_INPUT_DONE 0x0002 +#define L2CAP_CONF_OUTPUT_DONE 0x0004 +#define L2CAP_CONF_MTU_DONE 0x0008 +#define L2CAP_CONF_MODE_DONE 0x0010 +#define L2CAP_CONF_CONNECT_PEND 0x0020 +#define L2CAP_CONF_NO_FCS_RECV 0x0040 +#define L2CAP_CONF_STATE2_DEVICE 0x0080 +#define L2CAP_CONF_EXT_WIN_RECV 0x0100 +#define L2CAP_CONF_LOCKSTEP 0x0200 +#define L2CAP_CONF_LOCKSTEP_PEND 0x0400 +#define L2CAP_CONF_PEND_SENT 0x0800 +#define L2CAP_CONF_EFS_RECV 0x1000 -#define __get_txseq(ctrl) (((ctrl) & L2CAP_CTRL_TXSEQ) >> 1) -#define __get_reqseq(ctrl) (((ctrl) & L2CAP_CTRL_REQSEQ) >> 8) -#define __is_iframe(ctrl) (!((ctrl) & L2CAP_CTRL_FRAME_TYPE)) -#define __is_sframe(ctrl) ((ctrl) & L2CAP_CTRL_FRAME_TYPE) -#define __is_sar_start(ctrl) (((ctrl) & L2CAP_CTRL_SAR) == L2CAP_SDU_START) +#define L2CAP_CONF_MAX_CONF_REQ 2 +#define L2CAP_CONF_MAX_CONF_RSP 2 + +#define L2CAP_RECONF_NONE 0x00 +#define L2CAP_RECONF_INT 0x01 +#define L2CAP_RECONF_ACC 0x02 + +#define L2CAP_CONN_SREJ_ACT 0x01 +#define L2CAP_CONN_REJ_ACT 0x02 +#define L2CAP_CONN_REMOTE_BUSY 0x04 +#define L2CAP_CONN_LOCAL_BUSY 0x08 +#define L2CAP_CONN_SEND_FBIT 0x10 +#define L2CAP_CONN_SENT_RNR 0x20 + +#define L2CAP_SEQ_LIST_CLEAR 0xFFFF +#define L2CAP_SEQ_LIST_TAIL 0x8000 + +#define L2CAP_ERTM_TX_STATE_XMIT 0x01 +#define L2CAP_ERTM_TX_STATE_WAIT_F 0x02 + +#define L2CAP_ERTM_RX_STATE_RECV 0x01 +#define L2CAP_ERTM_RX_STATE_SREJ_SENT 0x02 +#define L2CAP_ERTM_RX_STATE_AMP_MOVE 0x03 +#define L2CAP_ERTM_RX_STATE_WAIT_P_FLAG 0x04 +#define L2CAP_ERTM_RX_STATE_WAIT_P_FLAG_RECONFIGURE 0x05 +#define L2CAP_ERTM_RX_STATE_WAIT_F_FLAG 0x06 + +#define L2CAP_ERTM_TXSEQ_EXPECTED 0x00 +#define L2CAP_ERTM_TXSEQ_EXPECTED_SREJ 0x01 +#define L2CAP_ERTM_TXSEQ_UNEXPECTED 0x02 +#define L2CAP_ERTM_TXSEQ_UNEXPECTED_SREJ 0x03 +#define L2CAP_ERTM_TXSEQ_DUPLICATE 0x04 +#define L2CAP_ERTM_TXSEQ_DUPLICATE_SREJ 0x05 +#define L2CAP_ERTM_TXSEQ_INVALID 0x06 +#define L2CAP_ERTM_TXSEQ_INVALID_IGNORE 0x07 + +#define L2CAP_ERTM_EVENT_DATA_REQUEST 0x01 +#define L2CAP_ERTM_EVENT_LOCAL_BUSY_DETECTED 0x02 +#define L2CAP_ERTM_EVENT_LOCAL_BUSY_CLEAR 0x03 +#define L2CAP_ERTM_EVENT_RECV_REQSEQ_AND_FBIT 0x04 +#define L2CAP_ERTM_EVENT_RECV_FBIT 0x05 +#define L2CAP_ERTM_EVENT_RETRANS_TIMER_EXPIRES 0x06 +#define L2CAP_ERTM_EVENT_MONITOR_TIMER_EXPIRES 0x07 +#define L2CAP_ERTM_EVENT_EXPLICIT_POLL 0x08 +#define L2CAP_ERTM_EVENT_RECV_IFRAME 0x09 +#define L2CAP_ERTM_EVENT_RECV_RR 0x0a +#define L2CAP_ERTM_EVENT_RECV_REJ 0x0b +#define L2CAP_ERTM_EVENT_RECV_RNR 0x0c +#define L2CAP_ERTM_EVENT_RECV_SREJ 0x0d +#define L2CAP_ERTM_EVENT_RECV_FRAME 0x0e + +#define L2CAP_AMP_MOVE_NONE 0 +#define L2CAP_AMP_MOVE_INITIATOR 1 +#define L2CAP_AMP_MOVE_RESPONDER 2 + +#define L2CAP_AMP_STATE_STABLE 0 +#define L2CAP_AMP_STATE_WAIT_CREATE 1 +#define L2CAP_AMP_STATE_WAIT_CREATE_RSP 2 +#define L2CAP_AMP_STATE_WAIT_MOVE 3 +#define L2CAP_AMP_STATE_WAIT_MOVE_RSP 4 +#define L2CAP_AMP_STATE_WAIT_MOVE_RSP_SUCCESS 5 +#define L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM 6 +#define L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM_RSP 7 +#define L2CAP_AMP_STATE_WAIT_LOGICAL_COMPLETE 8 +#define L2CAP_AMP_STATE_WAIT_LOGICAL_CONFIRM 9 +#define L2CAP_AMP_STATE_WAIT_LOCAL_BUSY 10 +#define L2CAP_AMP_STATE_WAIT_PREPARE 11 +#define L2CAP_AMP_STATE_RESEGMENT 12 + +#define __delta_seq(x, y, pi) ((x) >= (y) ? (x) - (y) : \ + (pi)->tx_win_max + 1 - (y) + (x)) +#define __next_seq(x, pi) ((x + 1) & ((pi)->tx_win_max)) extern int disable_ertm; +extern const struct proto_ops l2cap_sock_ops; +extern struct bt_sock_list l2cap_sk_list; int l2cap_init_sockets(void); void l2cap_cleanup_sockets(void); -void __l2cap_connect_rsp_defer(struct l2cap_chan *chan); +u8 l2cap_get_ident(struct l2cap_conn *conn); +void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len, void *data); +int l2cap_build_conf_req(struct sock *sk, void *data); int __l2cap_wait_ack(struct sock *sk); -int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm); -int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid); - -struct l2cap_chan *l2cap_chan_create(struct sock *sk); -void l2cap_chan_close(struct l2cap_chan *chan, int reason); -void l2cap_chan_destroy(struct l2cap_chan *chan); -int l2cap_chan_connect(struct l2cap_chan *chan); -int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len); -void l2cap_chan_busy(struct l2cap_chan *chan, int busy); +struct sk_buff *l2cap_create_connless_pdu(struct sock *sk, struct msghdr *msg, size_t len); +struct sk_buff *l2cap_create_basic_pdu(struct sock *sk, struct msghdr *msg, size_t len); +struct sk_buff *l2cap_create_iframe_pdu(struct sock *sk, struct msghdr *msg, + size_t len, u16 sdulen, int reseg); +int l2cap_segment_sdu(struct sock *sk, struct sk_buff_head* seg_queue, + struct msghdr *msg, size_t len, int reseg); +int l2cap_resegment_queue(struct sock *sk, struct sk_buff_head *queue); +void l2cap_do_send(struct sock *sk, struct sk_buff *skb); +void l2cap_streaming_send(struct sock *sk); +int l2cap_ertm_send(struct sock *sk); +int l2cap_strm_tx(struct sock *sk, struct sk_buff_head *skbs); +int l2cap_ertm_tx(struct sock *sk, struct bt_l2cap_control *control, + struct sk_buff_head *skbs, u8 event); + +void l2cap_sock_set_timer(struct sock *sk, long timeout); +void l2cap_sock_clear_timer(struct sock *sk); +void __l2cap_sock_close(struct sock *sk, int reason); +void l2cap_sock_kill(struct sock *sk); +void l2cap_sock_init(struct sock *sk, struct sock *parent); +struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, + int proto, gfp_t prio); +void l2cap_send_disconn_req(struct l2cap_conn *conn, struct sock *sk, int err); +void l2cap_chan_del(struct sock *sk, int err); +int l2cap_do_connect(struct sock *sk); +int l2cap_data_channel(struct sock *sk, struct sk_buff *skb); +void l2cap_amp_move_init(struct sock *sk); +void l2cap_ertm_destruct(struct sock *sk); +void l2cap_ertm_shutdown(struct sock *sk); +void l2cap_ertm_recv_done(struct sock *sk); + +void l2cap_fixed_channel_config(struct sock *sk, struct l2cap_options *opt); + +void l2cap_recv_deferred_frame(struct sock *sk, struct sk_buff *skb); + +void l2cap_amp_physical_complete(int result, u8 remote_id, u8 local_id, + struct sock *sk); + +void l2cap_amp_logical_complete(int result, struct hci_conn *ampcon, + struct hci_chan *ampchan, struct sock *sk); + +void l2cap_amp_logical_destroyed(struct hci_conn *ampcon); #endif /* __L2CAP_H */ diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h index 5428fd32cce..f4ef1312606 100644 --- a/include/net/bluetooth/mgmt.h +++ b/include/net/bluetooth/mgmt.h @@ -197,20 +197,6 @@ struct mgmt_cp_remove_remote_oob_data { bdaddr_t bdaddr; } __packed; -#define MGMT_OP_START_DISCOVERY 0x001B - -#define MGMT_OP_STOP_DISCOVERY 0x001C - -#define MGMT_OP_BLOCK_DEVICE 0x001D -struct mgmt_cp_block_device { - bdaddr_t bdaddr; -} __packed; - -#define MGMT_OP_UNBLOCK_DEVICE 0x001E -struct mgmt_cp_unblock_device { - bdaddr_t bdaddr; -} __packed; - #define MGMT_EV_CMD_COMPLETE 0x0001 struct mgmt_ev_cmd_complete { __le16 opcode; @@ -242,7 +228,7 @@ struct mgmt_ev_controller_error { #define MGMT_EV_NEW_KEY 0x000A struct mgmt_ev_new_key { - __u8 store_hint; + __u8 old_key_type; struct mgmt_key_info key; } __packed; @@ -265,13 +251,11 @@ struct mgmt_ev_connect_failed { #define MGMT_EV_PIN_CODE_REQUEST 0x000E struct mgmt_ev_pin_code_request { bdaddr_t bdaddr; - __u8 secure; } __packed; #define MGMT_EV_USER_CONFIRM_REQUEST 0x000F struct mgmt_ev_user_confirm_request { bdaddr_t bdaddr; - __u8 confirm_hint; __le32 value; } __packed; @@ -299,5 +283,3 @@ struct mgmt_ev_remote_name { bdaddr_t bdaddr; __u8 name[MGMT_MAX_NAME_LENGTH]; } __packed; - -#define MGMT_EV_DISCOVERING 0x0014 diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index d5eee2093b1..6eac4a760c3 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -234,8 +234,7 @@ int rfcomm_send_rpn(struct rfcomm_session *s, int cr, u8 dlci, /* ---- RFCOMM DLCs (channels) ---- */ struct rfcomm_dlc *rfcomm_dlc_alloc(gfp_t prio); void rfcomm_dlc_free(struct rfcomm_dlc *d); -int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, - u8 channel); +int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel); int rfcomm_dlc_close(struct rfcomm_dlc *d, int reason); int rfcomm_dlc_send(struct rfcomm_dlc *d, struct sk_buff *skb); int rfcomm_dlc_set_modem_status(struct rfcomm_dlc *d, u8 v24_sig); @@ -272,8 +271,7 @@ static inline void rfcomm_dlc_unthrottle(struct rfcomm_dlc *d) } /* ---- RFCOMM sessions ---- */ -void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src, - bdaddr_t *dst); +void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src, bdaddr_t *dst); static inline void rfcomm_session_hold(struct rfcomm_session *s) { @@ -314,8 +312,7 @@ struct rfcomm_pinfo { int rfcomm_init_sockets(void); void rfcomm_cleanup_sockets(void); -int rfcomm_connect_ind(struct rfcomm_session *s, u8 channel, - struct rfcomm_dlc **d); +int rfcomm_connect_ind(struct rfcomm_session *s, u8 channel, struct rfcomm_dlc **d); /* ---- RFCOMM TTY ---- */ #define RFCOMM_MAX_DEV 256 diff --git a/include/sound/apr_audio.h b/include/sound/apr_audio.h new file mode 100644 index 00000000000..44f2541e350 --- /dev/null +++ b/include/sound/apr_audio.h @@ -0,0 +1,1061 @@ + +/* + * + * Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _APR_AUDIO_H_ +#define _APR_AUDIO_H_ + +/* ASM opcodes without APR payloads*/ +#include + +/* + * Audio Front End (AFE) + */ + +/* Port ID. Update afe_get_port_index when a new port is added here. */ +#define PRIMARY_I2S_RX 0 /* index = 0 */ +#define PRIMARY_I2S_TX 1 /* index = 1 */ +#define PCM_RX 2 /* index = 2 */ +#define PCM_TX 3 /* index = 3 */ +#define SECONDARY_I2S_RX 4 /* index = 4 */ +#define SECONDARY_I2S_TX 5 /* index = 5 */ +#define MI2S_RX 6 /* index = 6 */ +#define MI2S_TX 7 /* index = 7 */ +#define HDMI_RX 8 /* index = 8 */ +#define RSVD_2 9 /* index = 9 */ +#define RSVD_3 10 /* index = 10 */ +#define DIGI_MIC_TX 11 /* index = 11 */ +#define VOICE_RECORD_RX 0x8003 /* index = 12 */ +#define VOICE_RECORD_TX 0x8004 /* index = 13 */ +#define VOICE_PLAYBACK_TX 0x8005 /* index = 14 */ + +/* Slimbus Multi channel port id pool */ +#define SLIMBUS_0_RX 0x4000 /* index = 15 */ +#define SLIMBUS_0_TX 0x4001 /* index = 16 */ +#define SLIMBUS_1_RX 0x4002 /* index = 17 */ +#define SLIMBUS_1_TX 0x4003 /* index = 18 */ +#define SLIMBUS_2_RX 0x4004 +#define SLIMBUS_2_TX 0x4005 +#define SLIMBUS_3_RX 0x4006 +#define SLIMBUS_3_TX 0x4007 +#define SLIMBUS_4_RX 0x4008 +#define SLIMBUS_4_TX 0x4009 /* index = 24 */ + +#define INT_BT_SCO_RX 0x3000 /* index = 25 */ +#define INT_BT_SCO_TX 0x3001 /* index = 26 */ +#define INT_BT_A2DP_RX 0x3002 /* index = 27 */ +#define INT_FM_RX 0x3004 /* index = 28 */ +#define INT_FM_TX 0x3005 /* index = 29 */ + +#define AFE_PORT_INVALID 0xFFFF + +#define AFE_PORT_CMD_START 0x000100ca +struct afe_port_start_command { + struct apr_hdr hdr; + u16 port_id; + u16 gain; /* Q13 */ + u32 sample_rate; /* 8 , 16, 48khz */ +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_STOP 0x000100cb +struct afe_port_stop_command { + struct apr_hdr hdr; + u16 port_id; + u16 reserved; +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_APPLY_GAIN 0x000100cc +struct afe_port_gain_command { + struct apr_hdr hdr; + u16 port_id; + u16 gain;/* Q13 */ +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_SIDETONE_CTL 0x000100cd +struct afe_port_sidetone_command { + struct apr_hdr hdr; + u16 rx_port_id; /* Primary i2s tx = 1 */ + /* PCM tx = 3 */ + /* Secondary i2s tx = 5 */ + /* Mi2s tx = 7 */ + /* Digital mic tx = 11 */ + u16 tx_port_id; /* Primary i2s rx = 0 */ + /* PCM rx = 2 */ + /* Secondary i2s rx = 4 */ + /* Mi2S rx = 6 */ + /* HDMI rx = 8 */ + u16 gain; /* Q13 */ + u16 enable; /* 1 = enable, 0 = disable */ +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_LOOPBACK 0x000100ce +struct afe_loopback_command { + struct apr_hdr hdr; + u16 tx_port_id; /* Primary i2s rx = 0 */ + /* PCM rx = 2 */ + /* Secondary i2s rx = 4 */ + /* Mi2S rx = 6 */ + /* HDMI rx = 8 */ + u16 rx_port_id; /* Primary i2s tx = 1 */ + /* PCM tx = 3 */ + /* Secondary i2s tx = 5 */ + /* Mi2s tx = 7 */ + /* Digital mic tx = 11 */ + u16 mode; /* Default -1, DSP will conver + the tx to rx format */ + u16 enable; /* 1 = enable, 0 = disable */ +} __attribute__ ((packed)); + +#define AFE_PSEUDOPORT_CMD_START 0x000100cf +struct afe_pseudoport_start_command { + struct apr_hdr hdr; + u16 port_id; /* Pseudo Port 1 = 0x8000 */ + /* Pseudo Port 2 = 0x8001 */ + /* Pseudo Port 3 = 0x8002 */ + u16 timing; /* FTRT = 0 , AVTimer = 1, */ +} __attribute__ ((packed)); + +#define AFE_PSEUDOPORT_CMD_STOP 0x000100d0 +struct afe_pseudoport_stop_command { + struct apr_hdr hdr; + u16 port_id; /* Pseudo Port 1 = 0x8000 */ + /* Pseudo Port 2 = 0x8001 */ + /* Pseudo Port 3 = 0x8002 */ + u16 reserved; +} __attribute__ ((packed)); + +#define AFE_CMD_GET_ACTIVE_PORTS 0x000100d1 + + +#define AFE_CMD_GET_ACTIVE_HANDLES_FOR_PORT 0x000100d2 +struct afe_get_active_handles_command { + struct apr_hdr hdr; + u16 port_id; + u16 reserved; +} __attribute__ ((packed)); + +#define AFE_PCM_CFG_MODE_PCM 0x0 +#define AFE_PCM_CFG_MODE_AUX 0x1 +#define AFE_PCM_CFG_SYNC_EXT 0x0 +#define AFE_PCM_CFG_SYNC_INT 0x1 +#define AFE_PCM_CFG_FRM_8BPF 0x0 +#define AFE_PCM_CFG_FRM_16BPF 0x1 +#define AFE_PCM_CFG_FRM_32BPF 0x2 +#define AFE_PCM_CFG_FRM_64BPF 0x3 +#define AFE_PCM_CFG_FRM_128BPF 0x4 +#define AFE_PCM_CFG_FRM_256BPF 0x5 +#define AFE_PCM_CFG_QUANT_ALAW_NOPAD 0x0 +#define AFE_PCM_CFG_QUANT_MULAW_NOPAD 0x1 +#define AFE_PCM_CFG_QUANT_LINEAR_NOPAD 0x2 +#define AFE_PCM_CFG_QUANT_ALAW_PAD 0x3 +#define AFE_PCM_CFG_QUANT_MULAW_PAD 0x4 +#define AFE_PCM_CFG_QUANT_LINEAR_PAD 0x5 +#define AFE_PCM_CFG_CDATAOE_MASTER 0x0 +#define AFE_PCM_CFG_CDATAOE_SHARE 0x1 + +struct afe_port_pcm_cfg { + u16 mode; /* PCM (short sync) = 0, AUXPCM (long sync) = 1 */ + u16 sync; /* external = 0 , internal = 1 */ + u16 frame; /* 8 bpf = 0 */ + /* 16 bpf = 1 */ + /* 32 bpf = 2 */ + /* 64 bpf = 3 */ + /* 128 bpf = 4 */ + /* 256 bpf = 5 */ + u16 quant; + u16 slot; /* Slot for PCM stream , 0 - 31 */ + u16 data; /* 0, PCM block is the only master */ + /* 1, PCM block is shares to driver data out signal */ + /* other master */ + u16 reserved; +} __attribute__ ((packed)); + +enum { + AFE_I2S_SD0 = 1, + AFE_I2S_SD1, + AFE_I2S_SD2, + AFE_I2S_SD3, + AFE_I2S_QUAD01, + AFE_I2S_QUAD23, + AFE_I2S_6CHS, + AFE_I2S_8CHS, +}; + +#define AFE_MI2S_MONO 0 +#define AFE_MI2S_STEREO 3 +#define AFE_MI2S_4CHANNELS 4 +#define AFE_MI2S_6CHANNELS 6 +#define AFE_MI2S_8CHANNELS 8 + +struct afe_port_mi2s_cfg { + u16 bitwidth; /* 16,24,32 */ + u16 line; /* Called ChannelMode in documentation */ + /* i2s_sd0 = 1 */ + /* i2s_sd1 = 2 */ + /* i2s_sd2 = 3 */ + /* i2s_sd3 = 4 */ + /* i2s_quad01 = 5 */ + /* i2s_quad23 = 6 */ + /* i2s_6chs = 7 */ + /* i2s_8chs = 8 */ + u16 channel; /* Called MonoStereo in documentation */ + /* i2s mono = 0 */ + /* i2s mono right = 1 */ + /* i2s mono left = 2 */ + /* i2s stereo = 3 */ + u16 ws; /* 0, word select signal from external source */ + /* 1, word select signal from internal source */ + u16 reserved; +} __attribute__ ((packed)); + +struct afe_port_hdmi_cfg { + u16 bitwidth; /* 16,24,32 */ + u16 channel_mode; /* HDMI Stereo = 0 */ + /* HDMI_3Point1 (4-ch) = 1 */ + /* HDMI_5Point1 (6-ch) = 2 */ + /* HDMI_6Point1 (8-ch) = 3 */ + u16 data_type; /* HDMI_Linear = 0 */ + /* HDMI_non_Linaer = 1 */ +} __attribute__ ((packed)); + + +/* Slimbus Device Ids */ +#define AFE_SLIMBUS_DEVICE_1 0x0 +#define AFE_SLIMBUS_DEVICE_2 0x1 +#define AFE_PORT_MAX_AUDIO_CHAN_CNT 16 + +struct afe_port_slimbus_cfg { + u16 slimbus_dev_id; /* SLIMBUS Device id.*/ + + u16 slave_dev_pgd_la; /* Slave ported generic device + * logical address. + */ + u16 slave_dev_intfdev_la; /* Slave interface device logical + * address. + */ + u16 bit_width; /** bit width of the samples, 16, 24.*/ + + u16 data_format; /** data format.*/ + + u16 num_channels; /** Number of channels.*/ + + /** Slave port mapping for respective channels.*/ + u16 slave_port_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT]; + + u16 reserved; +} __packed; + + +#define AFE_PORT_AUDIO_IF_CONFIG 0x000100d3 + +union afe_port_config { + struct afe_port_pcm_cfg pcm; + struct afe_port_mi2s_cfg mi2s; + struct afe_port_hdmi_cfg hdmi; + struct afe_port_slimbus_cfg slimbus; +} __attribute__((packed)); + +struct afe_audioif_config_command { + struct apr_hdr hdr; + u16 port_id; + union afe_port_config port; +} __attribute__ ((packed)); + +#define AFE_TEST_CODEC_LOOPBACK_CTL 0x000100d5 +struct afe_codec_loopback_command { + u16 port_inf; /* Primary i2s = 0 */ + /* PCM = 2 */ + /* Secondary i2s = 4 */ + /* Mi2s = 6 */ + u16 enable; /* 0, disable. 1, enable */ +} __attribute__ ((packed)); + + +#define AFE_PARAM_ID_SIDETONE_GAIN 0x00010300 +struct afe_param_sidetone_gain { + u16 gain; + u16 reserved; +} __attribute__ ((packed)); + +#define AFE_PARAM_ID_SAMPLING_RATE 0x00010301 +struct afe_param_sampling_rate { + u32 sampling_rate; +} __attribute__ ((packed)); + + +#define AFE_PARAM_ID_CHANNELS 0x00010302 +struct afe_param_channels { + u16 channels; + u16 reserved; +} __attribute__ ((packed)); + + +#define AFE_PARAM_ID_LOOPBACK_GAIN 0x00010303 +struct afe_param_loopback_gain { + u16 gain; + u16 reserved; +} __attribute__ ((packed)); + + +#define AFE_MODULE_ID_PORT_INFO 0x00010200 +struct afe_param_payload { + u32 module_id; + u32 param_id; + u16 param_size; + u16 reserved; + union { + struct afe_param_sidetone_gain sidetone_gain; + struct afe_param_sampling_rate sampling_rate; + struct afe_param_channels channels; + struct afe_param_loopback_gain loopback_gain; + } __attribute__((packed)) param; +} __attribute__ ((packed)); + +#define AFE_PORT_CMD_SET_PARAM 0x000100dc + +struct afe_port_cmd_set_param { + struct apr_hdr hdr; + u16 port_id; + u16 payload_size; + u32 payload_address; + struct afe_param_payload payload; +} __attribute__ ((packed)); + + +#define AFE_EVENT_GET_ACTIVE_PORTS 0x00010100 +struct afe_get_active_ports_rsp { + u16 num_ports; + u16 port_id; +} __attribute__ ((packed)); + + +#define AFE_EVENT_GET_ACTIVE_HANDLES 0x00010102 +struct afe_get_active_handles_rsp { + u16 port_id; + u16 num_handles; + u16 mode; /* 0, voice rx */ + /* 1, voice tx */ + /* 2, audio rx */ + /* 3, audio tx */ + u16 handle; +} __attribute__ ((packed)); + +#define ADM_MAX_COPPS 5 + +#define ADM_SERVICE_CMD_GET_COPP_HANDLES 0x00010300 +struct adm_get_copp_handles_command { + struct apr_hdr hdr; +} __attribute__ ((packed)); + +#define ADM_CMD_MATRIX_MAP_ROUTINGS 0x00010301 +struct adm_routings_session { + u16 id; + u16 num_copps; + u16 copp_id[ADM_MAX_COPPS+1]; /*Padding if numCopps is odd */ +} __packed; + +struct adm_routings_command { + struct apr_hdr hdr; + u32 path; /* 0 = Rx, 1 Tx */ + u32 num_sessions; + struct adm_routings_session session[8]; +} __attribute__ ((packed)); + + +#define ADM_CMD_MATRIX_RAMP_GAINS 0x00010302 +struct adm_ramp_gain { + struct apr_hdr hdr; + u16 session_id; + u16 copp_id; + u16 initial_gain; + u16 gain_increment; + u16 ramp_duration; + u16 reserved; +} __attribute__ ((packed)); + +struct adm_ramp_gains_command { + struct apr_hdr hdr; + u32 id; + u32 num_gains; + struct adm_ramp_gain gains[ADM_MAX_COPPS]; +} __attribute__ ((packed)); + + +#define ADM_CMD_COPP_OPEN 0x00010304 +struct adm_copp_open_command { + struct apr_hdr hdr; + u16 flags; + u16 mode; /* 1-RX, 2-Live TX, 3-Non Live TX */ + u16 endpoint_id1; + u16 endpoint_id2; + u32 topology_id; + u16 channel_config; + u16 reserved; + u32 rate; +} __attribute__ ((packed)); + +#define ADM_CMD_COPP_CLOSE 0x00010305 + +#define ADM_CMD_MEMORY_MAP 0x00010C30 +struct adm_cmd_memory_map{ + struct apr_hdr hdr; + u32 buf_add; + u32 buf_size; + u16 mempool_id; + u16 reserved; +} __attribute__((packed)); + +#define ADM_CMD_MEMORY_UNMAP 0x00010C31 +struct adm_cmd_memory_unmap{ + struct apr_hdr hdr; + u32 buf_add; +} __attribute__((packed)); + +#define ADM_CMD_MEMORY_MAP_REGIONS 0x00010C47 +struct adm_memory_map_regions{ + u32 phys; + u32 buf_size; +} __attribute__((packed)); + +struct adm_cmd_memory_map_regions{ + struct apr_hdr hdr; + u16 mempool_id; + u16 nregions; +} __attribute__((packed)); + +#define ADM_CMD_MEMORY_UNMAP_REGIONS 0x00010C48 +struct adm_memory_unmap_regions{ + u32 phys; +} __attribute__((packed)); + +struct adm_cmd_memory_unmap_regions{ + struct apr_hdr hdr; + u16 nregions; + u16 reserved; +} __attribute__((packed)); + +#define DEFAULT_COPP_TOPOLOGY 0x00010be3 +#define DEFAULT_POPP_TOPOLOGY 0x00010be4 +#define VPM_TX_SM_ECNS_COPP_TOPOLOGY 0x00010F71 +#define VPM_TX_DM_FLUENCE_COPP_TOPOLOGY 0x00010F72 + +#define ASM_MAX_EQ_BANDS 12 + +struct asm_eq_band { + u32 band_idx; /* The band index, 0 .. 11 */ + u32 filter_type; /* Filter band type */ + u32 center_freq_hz; /* Filter band center frequency */ + u32 filter_gain; /* Filter band initial gain (dB) */ + /* Range is +12 dB to -12 dB with 1dB increments. */ + u32 q_factor; +} __attribute__ ((packed)); + +struct asm_equalizer_params { + u32 enable; + u32 num_bands; + struct asm_eq_band eq_bands[ASM_MAX_EQ_BANDS]; +} __attribute__ ((packed)); + +struct asm_master_gain_params { + u16 master_gain; + u16 padding; +} __attribute__ ((packed)); + +struct asm_lrchannel_gain_params { + u16 left_gain; + u16 right_gain; +} __attribute__ ((packed)); + +struct asm_mute_params { + u32 muteflag; +} __attribute__ ((packed)); + +struct asm_softvolume_params { + u32 period; + u32 step; + u32 rampingcurve; +} __attribute__ ((packed)); + +struct asm_softpause_params { + u32 enable; + u32 period; + u32 step; + u32 rampingcurve; +} __packed; + +struct asm_pp_param_data_hdr { + u32 module_id; + u32 param_id; + u16 param_size; + u16 reserved; +} __attribute__ ((packed)); + +struct asm_pp_params_command { + struct apr_hdr hdr; + u32 *payload; + u32 payload_size; + struct asm_pp_param_data_hdr params; +} __attribute__ ((packed)); + +#define EQUALIZER_MODULE_ID 0x00010c27 +#define EQUALIZER_PARAM_ID 0x00010c28 + +#define VOLUME_CONTROL_MODULE_ID 0x00010bfe +#define MASTER_GAIN_PARAM_ID 0x00010bff +#define L_R_CHANNEL_GAIN_PARAM_ID 0x00010c00 +#define MUTE_CONFIG_PARAM_ID 0x00010c01 +#define SOFT_PAUSE_PARAM_ID 0x00010D6A + +#define IIR_FILTER_ENABLE_PARAM_ID 0x00010c03 +#define IIR_FILTER_PREGAIN_PARAM_ID 0x00010c04 +#define IIR_FILTER_CONFIG_PARAM_ID 0x00010c05 + +#define MBADRC_MODULE_ID 0x00010c06 +#define MBADRC_ENABLE_PARAM_ID 0x00010c07 +#define MBADRC_CONFIG_PARAM_ID 0x00010c08 + + +#define ADM_CMD_SET_PARAMS 0x00010306 +#define ADM_CMD_GET_PARAMS 0x0001030B +#define ADM_CMDRSP_GET_PARAMS 0x0001030C +struct adm_set_params_command { + struct apr_hdr hdr; + u32 payload; + u32 payload_size; +} __attribute__ ((packed)); + + +#define ADM_CMD_TAP_COPP_PCM 0x00010307 +struct adm_tap_copp_pcm_command { + struct apr_hdr hdr; +} __attribute__ ((packed)); + + +/* QDSP6 to Client messages +*/ +#define ADM_SERVICE_CMDRSP_GET_COPP_HANDLES 0x00010308 +struct adm_get_copp_handles_respond { + struct apr_hdr hdr; + u32 handles; + u32 copp_id; +} __attribute__ ((packed)); + +#define ADM_CMDRSP_COPP_OPEN 0x0001030A +struct adm_copp_open_respond { + u32 status; + u16 copp_id; + u16 reserved; +} __attribute__ ((packed)); + +#define ASM_STREAM_PRIORITY_NORMAL 0 +#define ASM_STREAM_PRIORITY_LOW 1 +#define ASM_STREAM_PRIORITY_HIGH 2 +#define ASM_STREAM_PRIORITY_RESERVED 3 + +#define ASM_END_POINT_DEVICE_MATRIX 0 +#define ASM_END_POINT_STREAM 1 + +#define AAC_ENC_MODE_AAC_LC 0x02 +#define AAC_ENC_MODE_AAC_P 0x05 +#define AAC_ENC_MODE_EAAC_P 0x1D + +#define ASM_STREAM_CMD_CLOSE 0x00010BCD +#define ASM_STREAM_CMD_FLUSH 0x00010BCE +#define ASM_STREAM_CMD_SET_PP_PARAMS 0x00010BCF +#define ASM_STREAM_CMD_GET_PP_PARAMS 0x00010BD0 +#define ASM_STREAM_CMDRSP_GET_PP_PARAMS 0x00010BD1 +#define ASM_SESSION_CMD_PAUSE 0x00010BD3 +#define ASM_SESSION_CMD_GET_SESSION_TIME 0x00010BD4 +#define ASM_DATA_CMD_EOS 0x00010BDB +#define ASM_DATA_EVENT_EOS 0x00010BDD + +#define ASM_SERVICE_CMD_GET_STREAM_HANDLES 0x00010C0B +#define ASM_STREAM_CMD_FLUSH_READBUFS 0x00010C09 + +#define ASM_SESSION_EVENT_RX_UNDERFLOW 0x00010C17 +#define ASM_SESSION_EVENT_TX_OVERFLOW 0x00010C18 +#define ASM_SERVICE_CMD_GET_WALLCLOCK_TIME 0x00010C19 +#define ASM_DATA_CMDRSP_EOS 0x00010C1C + +/* ASM Data structures */ + +/* common declarations */ +struct asm_pcm_cfg { + u16 ch_cfg; + u16 bits_per_sample; + u32 sample_rate; + u16 is_signed; + u16 interleaved; +}; + +struct asm_adpcm_cfg { + u16 ch_cfg; + u16 bits_per_sample; + u32 sample_rate; + u32 block_size; +}; + +struct asm_yadpcm_cfg { + u16 ch_cfg; + u16 bits_per_sample; + u32 sample_rate; +}; + +struct asm_midi_cfg { + u32 nMode; +}; + +struct asm_wma_cfg { + u16 format_tag; + u16 ch_cfg; + u32 sample_rate; + u32 avg_bytes_per_sec; + u16 block_align; + u16 valid_bits_per_sample; + u32 ch_mask; + u16 encode_opt; + u16 adv_encode_opt; + u32 adv_encode_opt2; + u32 drc_peak_ref; + u32 drc_peak_target; + u32 drc_ave_ref; + u32 drc_ave_target; +}; + +struct asm_wmapro_cfg { + u16 format_tag; + u16 ch_cfg; + u32 sample_rate; + u32 avg_bytes_per_sec; + u16 block_align; + u16 valid_bits_per_sample; + u32 ch_mask; + u16 encode_opt; + u16 adv_encode_opt; + u32 adv_encode_opt2; + u32 drc_peak_ref; + u32 drc_peak_target; + u32 drc_ave_ref; + u32 drc_ave_target; +}; + +struct asm_aac_cfg { + u16 format; + u16 aot; + u16 ep_config; + u16 section_data_resilience; + u16 scalefactor_data_resilience; + u16 spectral_data_resilience; + u16 ch_cfg; + u16 reserved; + u32 sample_rate; +}; + +struct asm_flac_cfg { + u16 stream_info_present; + u16 min_blk_size; + u16 max_blk_size; + u16 ch_cfg; + u16 sample_size; + u16 sample_rate; + u16 md5_sum; + u32 ext_sample_rate; + u32 min_frame_size; + u32 max_frame_size; +}; + +struct asm_vorbis_cfg { + u32 ch_cfg; + u32 bit_rate; + u32 min_bit_rate; + u32 max_bit_rate; + u16 bit_depth_pcm_sample; + u16 bit_stream_format; +}; + +struct asm_aac_read_cfg { + u32 bitrate; + u32 enc_mode; + u16 format; + u16 ch_cfg; + u32 sample_rate; +}; + +struct asm_amrnb_read_cfg { + u16 mode; + u16 dtx_mode; +}; + +struct asm_evrc_read_cfg { + u16 max_rate; + u16 min_rate; + u16 rate_modulation_cmd; + u16 reserved; +}; + +struct asm_qcelp13_read_cfg { + u16 max_rate; + u16 min_rate; + u16 reduced_rate_level; + u16 rate_modulation_cmd; +}; + +struct asm_sbc_read_cfg { + u32 subband; + u32 block_len; + u32 ch_mode; + u32 alloc_method; + u32 bit_rate; + u32 sample_rate; +}; + +struct asm_sbc_bitrate { + u32 bitrate; +}; + +struct asm_immed_decode { + u32 mode; +}; + +struct asm_sbr_ps { + u32 enable; +}; + +struct asm_encode_cfg_blk { + u32 frames_per_buf; + u32 format_id; + u32 cfg_size; + union { + struct asm_pcm_cfg pcm; + struct asm_aac_read_cfg aac; + struct asm_amrnb_read_cfg amrnb; + struct asm_evrc_read_cfg evrc; + struct asm_qcelp13_read_cfg qcelp13; + struct asm_sbc_read_cfg sbc; + } __attribute__((packed)) cfg; +}; + +struct asm_frame_meta_info { + u32 offset_to_frame; + u32 frame_size; + u32 encoded_pcm_samples; + u32 msw_ts; + u32 lsw_ts; + u32 nflags; +}; + +/* Stream level commands */ +#define ASM_STREAM_CMD_OPEN_READ 0x00010BCB +struct asm_stream_cmd_open_read { + struct apr_hdr hdr; + u32 uMode; + u32 src_endpoint; + u32 pre_proc_top; + u32 format; +} __attribute__((packed)); + +/* Supported formats */ +#define LINEAR_PCM 0x00010BE5 +#define DTMF 0x00010BE6 +#define ADPCM 0x00010BE7 +#define YADPCM 0x00010BE8 +#define MP3 0x00010BE9 +#define MPEG4_AAC 0x00010BEA +#define AMRNB_FS 0x00010BEB +#define V13K_FS 0x00010BED +#define EVRC_FS 0x00010BEE +#define EVRCB_FS 0x00010BEF +#define EVRCWB_FS 0x00010BF0 +#define MIDI 0x00010BF1 +#define SBC 0x00010BF2 +#define WMA_V10PRO 0x00010BF3 +#define WMA_V9 0x00010BF4 +#define AMR_WB_PLUS 0x00010BF5 +#define AC3_DECODER 0x00010BF6 +#define G711_ALAW_FS 0x00010BF7 +#define G711_MLAW_FS 0x00010BF8 +#define G711_PCM_FS 0x00010BF9 + +#define ASM_ENCDEC_SBCRATE 0x00010C13 +#define ASM_ENCDEC_IMMDIATE_DECODE 0x00010C14 +#define ASM_ENCDEC_CFG_BLK 0x00010C2C + +#define ASM_ENCDEC_SBCRATE 0x00010C13 +#define ASM_ENCDEC_IMMDIATE_DECODE 0x00010C14 +#define ASM_ENCDEC_CFG_BLK 0x00010C2C + +#define ASM_STREAM_CMD_OPEN_WRITE 0x00010BCA +struct asm_stream_cmd_open_write { + struct apr_hdr hdr; + u32 uMode; + u16 sink_endpoint; + u16 stream_handle; + u32 post_proc_top; + u32 format; +} __attribute__((packed)); + +#define ASM_STREAM_CMD_OPEN_READWRITE 0x00010BCC + +struct asm_stream_cmd_open_read_write { + struct apr_hdr hdr; + u32 uMode; + u32 post_proc_top; + u32 write_format; + u32 read_format; +} __attribute__((packed)); + +#define ASM_STREAM_CMD_SET_ENCDEC_PARAM 0x00010C10 +#define ASM_STREAM_CMD_GET_ENCDEC_PARAM 0x00010C11 +#define ASM_ENCDEC_CFG_BLK_ID 0x00010C2C +#define ASM_ENABLE_SBR_PS 0x00010C63 +struct asm_stream_cmd_encdec_cfg_blk{ + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_encode_cfg_blk enc_blk; +} __attribute__((packed)); + +struct asm_stream_cmd_encdec_sbc_bitrate{ + struct apr_hdr hdr; + u32 param_id; + struct asm_sbc_bitrate sbc_bitrate; +} __attribute__((packed)); + +struct asm_stream_cmd_encdec_immed_decode{ + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_immed_decode dec; +} __attribute__((packed)); + +struct asm_stream_cmd_encdec_sbr{ + struct apr_hdr hdr; + u32 param_id; + u32 param_size; + struct asm_sbr_ps sbr_ps; +} __attribute__((packed)); + +#define ASM_STREAM _CMD_ADJUST_SAMPLES 0x00010C0A +struct asm_stream_cmd_adjust_samples{ + struct apr_hdr hdr; + u16 nsamples; + u16 reserved; +} __attribute__((packed)); + +#define ASM_STREAM_CMD_TAP_POPP_PCM 0x00010BF9 +struct asm_stream_cmd_tap_popp_pcm{ + struct apr_hdr hdr; + u16 enable; + u16 reserved; + u32 module_id; +} __attribute__((packed)); + +/* Session Level commands */ +#define ASM_SESSION_CMD_MEMORY_MAP 0x00010C32 +struct asm_stream_cmd_memory_map{ + struct apr_hdr hdr; + u32 buf_add; + u32 buf_size; + u16 mempool_id; + u16 reserved; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_MEMORY_UNMAP 0x00010C33 +struct asm_stream_cmd_memory_unmap{ + struct apr_hdr hdr; + u32 buf_add; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_MEMORY_MAP_REGIONS 0x00010C45 +struct asm_memory_map_regions{ + u32 phys; + u32 buf_size; +} __attribute__((packed)); + +struct asm_stream_cmd_memory_map_regions{ + struct apr_hdr hdr; + u16 mempool_id; + u16 nregions; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_MEMORY_UNMAP_REGIONS 0x00010C46 +struct asm_memory_unmap_regions{ + u32 phys; +} __attribute__((packed)); + +struct asm_stream_cmd_memory_unmap_regions{ + struct apr_hdr hdr; + u16 nregions; + u16 reserved; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_RUN 0x00010BD2 +struct asm_stream_cmd_run{ + struct apr_hdr hdr; + u32 flags; + u32 msw_ts; + u32 lsw_ts; +} __attribute__((packed)); + +/* Session level events */ +#define ASM_SESSION_CMD_REGISTER_FOR_RX_UNDERFLOW_EVENTS 0x00010BD5 +struct asm_stream_cmd_reg_rx_underflow_event{ + struct apr_hdr hdr; + u16 enable; + u16 reserved; +} __attribute__((packed)); + +#define ASM_SESSION_CMD_REGISTER_FOR_TX_OVERFLOW_EVENTS 0x00010BD6 +struct asm_stream_cmd_reg_tx_overflow_event{ + struct apr_hdr hdr; + u16 enable; + u16 reserved; +} __attribute__((packed)); + +/* Data Path commands */ +#define ASM_DATA_CMD_WRITE 0x00010BD9 +struct asm_stream_cmd_write{ + struct apr_hdr hdr; + u32 buf_add; + u32 avail_bytes; + u32 uid; + u32 msw_ts; + u32 lsw_ts; + u32 uflags; +} __attribute__((packed)); + +#define ASM_DATA_CMD_READ 0x00010BDA +struct asm_stream_cmd_read{ + struct apr_hdr hdr; + u32 buf_add; + u32 buf_size; + u32 uid; +} __attribute__((packed)); + +#define ASM_DATA_CMD_MEDIA_FORMAT_UPDATE 0x00010BDC +#define ASM_DATA_EVENT_MEDIA_FORMAT_UPDATE 0x00010BDE +struct asm_stream_media_format_update{ + struct apr_hdr hdr; + u32 format; + u32 cfg_size; + union { + struct asm_pcm_cfg pcm_cfg; + struct asm_adpcm_cfg adpcm_cfg; + struct asm_yadpcm_cfg yadpcm_cfg; + struct asm_midi_cfg midi_cfg; + struct asm_wma_cfg wma_cfg; + struct asm_wmapro_cfg wmapro_cfg; + struct asm_aac_cfg aac_cfg; + struct asm_flac_cfg flac_cfg; + struct asm_vorbis_cfg vorbis_cfg; + } __attribute__((packed)) write_cfg; +} __attribute__((packed)); + + +/* Command Responses */ +#define ASM_STREAM_CMDRSP_GET_ENCDEC_PARAM 0x00010C12 +struct asm_stream_cmdrsp_get_readwrite_param{ + struct apr_hdr hdr; + u32 status; + u32 param_id; + u16 param_size; + u16 padding; + union { + struct asm_sbc_bitrate sbc_bitrate; + struct asm_immed_decode aac_dec; + } __attribute__((packed)) read_write_cfg; +} __attribute__((packed)); + + +#define ASM_SESSION_CMDRSP_GET_SESSION_TIME 0x00010BD8 +struct asm_stream_cmdrsp_get_session_time{ + struct apr_hdr hdr; + u32 status; + u32 msw_ts; + u32 lsw_ts; +} __attribute__((packed)); + +#define ASM_DATA_EVENT_WRITE_DONE 0x00010BDF +struct asm_data_event_write_done{ + u32 buf_add; + u32 status; +} __attribute__((packed)); + +#define ASM_DATA_EVENT_READ_DONE 0x00010BE0 +struct asm_data_event_read_done{ + u32 status; + u32 buffer_add; + u32 enc_frame_size; + u32 offset; + u32 msw_ts; + u32 lsw_ts; + u32 flags; + u32 num_frames; + u32 id; +} __attribute__((packed)); + +#define ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY 0x00010C65 +struct asm_data_event_sr_cm_change_notify { + u32 sample_rate; + u16 no_of_channels; + u16 reserved; + u8 channel_map[8]; +} __packed; + +/* service level events */ + +#define ASM_SERVICE_CMDRSP_GET_STREAM_HANDLES 0x00010C1B +struct asm_svc_cmdrsp_get_strm_handles{ + struct apr_hdr hdr; + u32 num_handles; + u32 stream_handles; +} __attribute__((packed)); + + +#define ASM_SERVICE_CMDRSP_GET_WALLCLOCK_TIME 0x00010C1A +struct asm_svc_cmdrsp_get_wallclock_time{ + struct apr_hdr hdr; + u32 status; + u32 msw_ts; + u32 lsw_ts; +} __attribute__((packed)); + +/* + * Error code +*/ +#define ADSP_EOK 0x00000000 /* Success / completed / no errors. */ +#define ADSP_EFAILED 0x00000001 /* General failure. */ +#define ADSP_EBADPARAM 0x00000002 /* Bad operation parameter(s). */ +#define ADSP_EUNSUPPORTED 0x00000003 /* Unsupported routine/operation. */ +#define ADSP_EVERSION 0x00000004 /* Unsupported version. */ +#define ADSP_EUNEXPECTED 0x00000005 /* Unexpected problem encountered. */ +#define ADSP_EPANIC 0x00000006 /* Unhandled problem occurred. */ +#define ADSP_ENORESOURCE 0x00000007 /* Unable to allocate resource(s). */ +#define ADSP_EHANDLE 0x00000008 /* Invalid handle. */ +#define ADSP_EALREADY 0x00000009 /* Operation is already processed. */ +#define ADSP_ENOTREADY 0x0000000A /* Operation not ready to be processed*/ +#define ADSP_EPENDING 0x0000000B /* Operation is pending completion*/ +#define ADSP_EBUSY 0x0000000C /* Operation could not be accepted or + processed. */ +#define ADSP_EABORTED 0x0000000D /* Operation aborted due to an error. */ +#define ADSP_EPREEMPTED 0x0000000E /* Operation preempted by higher priority*/ +#define ADSP_ECONTINUE 0x0000000F /* Operation requests intervention + to complete. */ +#define ADSP_EIMMEDIATE 0x00000010 /* Operation requests immediate + intervention to complete. */ +#define ADSP_ENOTIMPL 0x00000011 /* Operation is not implemented. */ +#define ADSP_ENEEDMORE 0x00000012 /* Operation needs more data or resources*/ + +#endif /*_APR_AUDIO_H_*/ diff --git a/include/sound/dai.h b/include/sound/dai.h new file mode 100644 index 00000000000..27ff9806c09 --- /dev/null +++ b/include/sound/dai.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __DAI_H__ +#define __DAI_H__ + +struct dai_dma_params { + u8 *buffer; + uint32_t src_start; + uint32_t bus_id; + int buffer_size; + int period_size; + int channels; +}; + +enum { + DAI_SPKR = 0, + DAI_MIC, + DAI_MI2S, + DAI_SEC_SPKR, + DAI_SEC_MIC, +}; + +/* Function Prototypes */ +int dai_open(uint32_t dma_ch); +void dai_close(uint32_t dma_ch); +int dai_start(uint32_t dma_ch); +int dai_stop(uint32_t dma_ch); +int dai_set_params(uint32_t dma_ch, struct dai_dma_params *params); +uint32_t dai_get_dma_pos(uint32_t dma_ch); +void register_dma_irq_handler(int dma_ch, + irqreturn_t (*callback) (int intrSrc, void *private_data), + void *private_data); +void unregister_dma_irq_handler(int dma_ch); +void dai_set_master_mode(uint32_t dma_ch, int mode); +int dai_start_hdmi(uint32_t dma_ch); +void dai_stop_hdmi(uint32_t dma_ch); + +#endif diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 6a187658e8f..bf9041cb76a 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -5,12 +5,12 @@ * Digital Audio (PCM) abstract layer * Copyright (c) by Jaroslav Kysela * Abramo Bagnara + * Copyright (c) 2011, Code Aurora Forum. 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. + * the Free Software Foundation; only version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/include/sound/q6adm.h b/include/sound/q6adm.h new file mode 100644 index 00000000000..4b9eb903aae --- /dev/null +++ b/include/sound/q6adm.h @@ -0,0 +1,49 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6_ADM_H__ +#define __Q6_ADM_H__ +#include + +#define ADM_PATH_PLAYBACK 0x1 +#define ADM_PATH_LIVE_REC 0x2 +#define ADM_PATH_NONLIVE_REC 0x3 + +/* multiple copp per stream. */ +struct route_payload { + unsigned int copp_ids[AFE_MAX_PORTS]; + unsigned short num_copps; + unsigned int session_id; +}; + +int adm_open_mixer(int port_id, int path, int rate, int channel_mode, + int topology); + +int adm_route_session(int port_id, uint session_id, int set); + +int adm_open(int port, int path, int rate, int mode, int topology); + +int adm_memory_map_regions(uint32_t *buf_add, uint32_t mempool_id, + uint32_t *bufsz, uint32_t bufcnt); + +int adm_memory_unmap_regions(uint32_t *buf_add, uint32_t *bufsz, + uint32_t bufcnt); + +int adm_close(int port); + +int adm_matrix_map(int session_id, int path, int num_copps, + unsigned int *port_id, int copp_id); + +#ifdef CONFIG_MSM8X60_RTAC +int adm_get_copp_id(int port_id); +#endif + +#endif /* __Q6_ADM_H__ */ diff --git a/include/sound/q6afe.h b/include/sound/q6afe.h new file mode 100644 index 00000000000..b9436bfb6c2 --- /dev/null +++ b/include/sound/q6afe.h @@ -0,0 +1,67 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6AFE_H__ +#define __Q6AFE_H__ +#include + +#define MSM_AFE_MONO 0 +#define MSM_AFE_MONO_RIGHT 1 +#define MSM_AFE_MONO_LEFT 2 +#define MSM_AFE_STEREO 3 + +enum { + IDX_PRIMARY_I2S_RX = 0, + IDX_PRIMARY_I2S_TX = 1, + IDX_PCM_RX = 2, + IDX_PCM_TX = 3, + IDX_SECONDARY_I2S_RX = 4, + IDX_SECONDARY_I2S_TX = 5, + IDX_MI2S_RX = 6, + IDX_MI2S_TX = 7, + IDX_HDMI_RX = 8, + IDX_RSVD_2 = 9, + IDX_RSVD_3 = 10, + IDX_DIGI_MIC_TX = 11, + IDX_VOICE_RECORD_RX = 12, + IDX_VOICE_RECORD_TX = 13, + IDX_VOICE_PLAYBACK_TX = 14, + IDX_SLIMBUS_0_RX = 15, + IDX_SLIMBUS_0_TX = 16, + IDX_SLIMBUS_1_RX = 17, + IDX_SLIMBUS_1_TX = 18, + IDX_SLIMBUS_2_RX = 19, + IDX_SLIMBUS_2_TX = 20, + IDX_SLIMBUS_3_RX = 21, + IDX_SLIMBUS_3_TX = 22, + IDX_SLIMBUS_4_RX = 23, + IDX_SLIMBUS_4_TX = 24, + IDX_INT_BT_SCO_RX = 25, + IDX_INT_BT_SCO_TX = 26, + IDX_INT_BT_A2DP_RX = 27, + IDX_INT_FM_RX = 28, + IDX_INT_FM_TX = 29, + AFE_MAX_PORTS +}; + +int afe_open(u16 port_id, union afe_port_config *afe_config, int rate); +int afe_close(int port_id); +int afe_loopback(u16 enable, u16 rx_port, u16 tx_port); +int afe_sidetone(u16 tx_port_id, u16 rx_port_id, u16 enable, uint16_t gain); +int afe_loopback_gain(u16 port_id, u16 volume); +int afe_validate_port(u16 port_id); +int afe_get_port_index(u16 port_id); +int afe_start_pseudo_port(u16 port_id); +int afe_stop_pseudo_port(u16 port_id); +int afe_port_start_nowait(u16 port_id, union afe_port_config *afe_config, + u32 rate); +int afe_port_stop_nowait(int port_id); +#endif /* __Q6AFE_H__ */ diff --git a/include/sound/q6asm.h b/include/sound/q6asm.h new file mode 100644 index 00000000000..af82b760dc3 --- /dev/null +++ b/include/sound/q6asm.h @@ -0,0 +1,254 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __Q6_ASM_H__ +#define __Q6_ASM_H__ + +#include +#include +#include + +#define IN 0x000 +#define OUT 0x001 +#define CH_MODE_MONO 0x001 +#define CH_MODE_STEREO 0x002 + +#define FORMAT_LINEAR_PCM 0x0000 +#define FORMAT_DTMF 0x0001 +#define FORMAT_ADPCM 0x0002 +#define FORMAT_YADPCM 0x0003 +#define FORMAT_MP3 0x0004 +#define FORMAT_MPEG4_AAC 0x0005 +#define FORMAT_AMRNB 0x0006 +#define FORMAT_AMRWB 0x0007 +#define FORMAT_V13K 0x0008 +#define FORMAT_EVRC 0x0009 +#define FORMAT_EVRCB 0x000a +#define FORMAT_EVRCWB 0x000b +#define FORMAT_MIDI 0x000c +#define FORMAT_SBC 0x000d +#define FORMAT_WMA_V10PRO 0x000e +#define FORMAT_WMA_V9 0x000f +#define FORMAT_AMR_WB_PLUS 0x0010 + +#define ENCDEC_SBCBITRATE 0x0001 +#define ENCDEC_IMMEDIATE_DECODE 0x0002 +#define ENCDEC_CFG_BLK 0x0003 + +#define CMD_PAUSE 0x0001 +#define CMD_FLUSH 0x0002 +#define CMD_EOS 0x0003 +#define CMD_CLOSE 0x0004 +#define CMD_OUT_FLUSH 0x0005 + +/* bit 0:1 represents priority of stream */ +#define STREAM_PRIORITY_NORMAL 0x0000 +#define STREAM_PRIORITY_LOW 0x0001 +#define STREAM_PRIORITY_HIGH 0x0002 + +/* bit 4 represents META enable of encoded data buffer */ +#define BUFFER_META_ENABLE 0x0010 + +/* Enable Sample_Rate/Channel_Mode notification event from Decoder */ +#define SR_CM_NOTIFY_ENABLE 0x0004 + +#define ASYNC_IO_MODE 0x0002 +#define SYNC_IO_MODE 0x0001 +#define NO_TIMESTAMP 0xFF00 +#define SET_TIMESTAMP 0x0000 + +#define SOFT_PAUSE_ENABLE 1 +#define SOFT_PAUSE_DISABLE 0 + +#define SESSION_MAX 0x08 + +typedef void (*app_cb)(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv); + +struct audio_buffer { + dma_addr_t phys; + void *data; + struct msm_mapped_buffer *mem_buffer; + uint32_t used; + uint32_t size;/* size of buffer */ + uint32_t actual_size; /* actual number of bytes read by DSP */ +}; + +struct audio_aio_write_param { + unsigned long paddr; + uint32_t uid; + uint32_t len; + uint32_t msw_ts; + uint32_t lsw_ts; + uint32_t flags; +}; + +struct audio_aio_read_param { + unsigned long paddr; + uint32_t len; + uint32_t uid; +}; + +struct audio_port_data { + struct audio_buffer *buf; + uint32_t max_buf_cnt; + uint32_t dsp_buf; + uint32_t cpu_buf; + /* read or write locks */ + struct mutex lock; + spinlock_t dsp_lock; +}; + +struct audio_client { + int session; + /* idx:1 out port, 0: in port*/ + struct audio_port_data port[2]; + + struct apr_svc *apr; + struct mutex cmd_lock; + + atomic_t cmd_state; + atomic_t time_flag; + wait_queue_head_t cmd_wait; + wait_queue_head_t time_wait; + + app_cb cb; + void *priv; + uint32_t io_mode; + uint64_t time_stamp; +}; + +void q6asm_audio_client_free(struct audio_client *ac); + +struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv); + +int q6asm_audio_client_buf_alloc(unsigned int dir/* 1:Out,0:In */, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt); +int q6asm_audio_client_buf_alloc_contiguous(unsigned int dir + /* 1:Out,0:In */, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt); + +int q6asm_audio_client_buf_free_contiguous(unsigned int dir, + struct audio_client *ac); + +int q6asm_open_read(struct audio_client *ac, uint32_t format); + +int q6asm_open_write(struct audio_client *ac, uint32_t format); + +int q6asm_open_read_write(struct audio_client *ac, + uint32_t rd_format, + uint32_t wr_format); + +int q6asm_write(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags); +int q6asm_write_nolock(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags); + +int q6asm_async_write(struct audio_client *ac, + struct audio_aio_write_param *param); + +int q6asm_async_read(struct audio_client *ac, + struct audio_aio_read_param *param); + +int q6asm_read(struct audio_client *ac); +int q6asm_read_nolock(struct audio_client *ac); + +int q6asm_memory_map(struct audio_client *ac, uint32_t buf_add, + int dir, uint32_t bufsz, uint32_t bufcnt); + +int q6asm_memory_unmap(struct audio_client *ac, uint32_t buf_add, + int dir); + +int q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts); + +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts); + +int q6asm_reg_tx_overflow(struct audio_client *ac, uint16_t enable); + +int q6asm_cmd(struct audio_client *ac, int cmd); + +int q6asm_cmd_nowait(struct audio_client *ac, int cmd); + +void *q6asm_is_cpu_buf_avail(int dir, struct audio_client *ac, + uint32_t *size, uint32_t *idx); + +int q6asm_is_dsp_buf_avail(int dir, struct audio_client *ac); + +/* File format specific configurations to be added below */ + +int q6asm_enc_cfg_blk_aac(struct audio_client *ac, + uint32_t frames_per_buf, + uint32_t sample_rate, uint32_t channels, + uint32_t bit_rate, + uint32_t mode, uint32_t format); + +int q6asm_enc_cfg_blk_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels); + +int q6asm_enable_sbrps(struct audio_client *ac, + uint32_t sbr_ps); + +int q6asm_enc_cfg_blk_qcelp(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t reduced_rate_level, uint16_t rate_modulation_cmd); + +int q6asm_enc_cfg_blk_evrc(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t rate_modulation_cmd); + +int q6asm_enc_cfg_blk_amrnb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable); + +int q6asm_media_format_block_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels); + +int q6asm_media_format_block_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg); + +int q6asm_media_format_block_wma(struct audio_client *ac, + void *cfg); + +int q6asm_media_format_block_wmapro(struct audio_client *ac, + void *cfg); + +/* PP specific */ +int q6asm_equalizer(struct audio_client *ac, void *eq); + +/* Send Volume Command */ +int q6asm_set_volume(struct audio_client *ac, int volume); + +/* Set SoftPause Params */ +int q6asm_set_softpause(struct audio_client *ac, + struct asm_softpause_params *param); + +/* Send left-right channel gain */ +int q6asm_set_lrgain(struct audio_client *ac, int left_gain, int right_gain); + +/* Enable Mute/unmute flag */ +int q6asm_set_mute(struct audio_client *ac, int muteflag); + +uint64_t q6asm_get_session_time(struct audio_client *ac); + +/* Client can set the IO mode to either AIO/SIO mode */ +int q6asm_set_io_mode(struct audio_client *ac, uint32_t mode); + +#ifdef CONFIG_MSM8X60_RTAC +/* Get Service ID for APR communication */ +int q6asm_get_apr_service_id(int session_id); +#endif + +#endif /* __Q6_ASM_H__ */ diff --git a/include/sound/soc.h b/include/sound/soc.h index 5b5de418b52..d858f4a7ec1 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -202,6 +202,11 @@ #define SOC_VALUE_ENUM_SINGLE_DECL(name, xreg, xshift, xmask, xtexts, xvalues) \ SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xmask, xtexts, xvalues) + +/* DAI Link Host Mode Support */ +#define SND_SOC_DAI_LINK_NO_HOST 0x1 +#define SND_SOC_DAI_LINK_OPT_HOST 0x2 + /* * Component probe and remove ordering levels for components with runtime * dependencies. diff --git a/init/Kconfig b/init/Kconfig index 4fe9168149f..6364e8d4e0f 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1234,6 +1234,7 @@ config SLAB per cpu and per node queues. config SLUB + depends on BROKEN || NUMA || !DISCONTIGMEM bool "SLUB (Unqueued Allocator)" help SLUB is a slab allocator that minimizes cache line usage diff --git a/kernel/cgroup_freezer.c b/kernel/cgroup_freezer.c index e691818d7e4..6ebda1df9b7 100644 --- a/kernel/cgroup_freezer.c +++ b/kernel/cgroup_freezer.c @@ -164,6 +164,14 @@ static int freezer_can_attach(struct cgroup_subsys *ss, { struct freezer *freezer; + if ((current != task) && (!capable(CAP_SYS_ADMIN))) { + const struct cred *cred = current_cred(), *tcred; + + tcred = __task_cred(task); + if (cred->euid != tcred->uid && cred->euid != tcred->suid) + return -EPERM; + } + /* * Anything frozen can't move or be moved to/from. */ diff --git a/kernel/cpuset.c b/kernel/cpuset.c index 9c9b7545c81..f4c101b509b 100644 --- a/kernel/cpuset.c +++ b/kernel/cpuset.c @@ -1373,6 +1373,13 @@ static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cont, { struct cpuset *cs = cgroup_cs(cont); + if ((current != tsk) && (!capable(CAP_SYS_ADMIN))) { + const struct cred *cred = current_cred(), *tcred; + + if (cred->euid != tcred->uid && cred->euid != tcred->suid) + return -EPERM; + } + if (cpumask_empty(cs->cpus_allowed) || nodes_empty(cs->mems_allowed)) return -ENOSPC; diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c index 0a7840aeb0f..3b67d89173d 100644 --- a/kernel/irq/manage.c +++ b/kernel/irq/manage.c @@ -521,6 +521,32 @@ int irq_set_irq_wake(unsigned int irq, unsigned int on) } EXPORT_SYMBOL(irq_set_irq_wake); +/** + * irq_read_line - read the value on an irq line + * @irq: Interrupt number representing a hardware line + * + * This function is meant to be called from within the irq handler. + * Slowbus irq controllers might sleep, but it is assumed that the irq + * handler for slowbus interrupts will execute in thread context, so + * sleeping is okay. + */ +int irq_read_line(unsigned int irq) +{ + struct irq_desc *desc = irq_to_desc(irq); + int val; + + if (!desc || !desc->irq_data.chip->irq_read_line) + return -EINVAL; + + chip_bus_lock(desc); + raw_spin_lock(&desc->lock); + val = desc->irq_data.chip->irq_read_line(&desc->irq_data); + raw_spin_unlock(&desc->lock); + chip_bus_sync_unlock(desc); + return val; +} +EXPORT_SYMBOL_GPL(irq_read_line); + /* * Internal function that tells the architecture code whether a * particular irq has been exclusively allocated or is available @@ -1400,3 +1426,16 @@ int request_any_context_irq(unsigned int irq, irq_handler_t handler, return !ret ? IRQC_IS_HARDIRQ : ret; } EXPORT_SYMBOL_GPL(request_any_context_irq); + +void irq_set_pending(unsigned int irq) +{ + struct irq_desc *desc = irq_to_desc(irq); + unsigned long flags; + + if (desc) { + raw_spin_lock_irqsave(&desc->lock, flags); + desc->istate |= IRQS_PENDING; + raw_spin_unlock_irqrestore(&desc->lock, flags); + } +} +EXPORT_SYMBOL_GPL(irq_set_pending); diff --git a/kernel/irq/pm.c b/kernel/irq/pm.c index f323a4cd58e..0e5ab4ddbee 100644 --- a/kernel/irq/pm.c +++ b/kernel/irq/pm.c @@ -50,7 +50,7 @@ void resume_device_irqs(void) struct irq_desc *desc; int irq; - for_each_irq_desc(irq, desc) { + for_each_irq_desc_reverse(irq, desc) { unsigned long flags; raw_spin_lock_irqsave(&desc->lock, flags); diff --git a/kernel/pm_qos_params.c b/kernel/pm_qos_params.c index 37f05d0f079..b6ff61a626f 100644 --- a/kernel/pm_qos_params.c +++ b/kernel/pm_qos_params.c @@ -471,9 +471,11 @@ static int __init pm_qos_power_init(void) return ret; } ret = register_pm_qos_misc(&network_throughput_pm_qos); - if (ret < 0) + if (ret < 0) { printk(KERN_ERR "pm_qos_param: network_throughput setup failed\n"); + return 0; + } return ret; } diff --git a/kernel/power/consoleearlysuspend.c b/kernel/power/consoleearlysuspend.c index a3edcb26738..aedf4aad31c 100644 --- a/kernel/power/consoleearlysuspend.c +++ b/kernel/power/consoleearlysuspend.c @@ -25,28 +25,23 @@ static int orig_fgconsole; static void console_early_suspend(struct early_suspend *h) { - acquire_console_sem(); orig_fgconsole = fg_console; if (vc_allocate(EARLY_SUSPEND_CONSOLE)) goto err; if (set_console(EARLY_SUSPEND_CONSOLE)) goto err; - release_console_sem(); if (vt_waitactive(EARLY_SUSPEND_CONSOLE + 1)) pr_warning("console_early_suspend: Can't switch VCs.\n"); return; err: pr_warning("console_early_suspend: Can't set console\n"); - release_console_sem(); } static void console_late_resume(struct early_suspend *h) { int ret; - acquire_console_sem(); ret = set_console(orig_fgconsole); - release_console_sem(); if (ret) { pr_warning("console_late_resume: Can't set console.\n"); return; diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index 63774df522b..6799c423777 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c @@ -276,9 +276,7 @@ int enter_state(suspend_state_t state) if (!mutex_trylock(&pm_mutex)) return -EBUSY; - printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync(); - printk("done.\n"); pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]); error = suspend_prepare(); diff --git a/kernel/printk.c b/kernel/printk.c index 4835df7fcd5..ff1a52c4d2a 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -1184,6 +1184,14 @@ void resume_console(void) console_unlock(); } +static void __cpuinit console_flush(struct work_struct *work) +{ + console_lock(); + console_unlock(); +} + +static __cpuinitdata DECLARE_WORK(console_cpu_notify_work, console_flush); + /** * console_cpu_notify - print deferred console messages after CPU hotplug * @self: notifier struct @@ -1194,6 +1202,9 @@ void resume_console(void) * will be spooled but will not show up on the console. This function is * called when a new CPU comes online (or fails to come up), and ensures * that any such output gets printed. + * + * Special handling must be done for cases invoked from an atomic context, + * as we can't be taking the console semaphore here. */ static int __cpuinit console_cpu_notify(struct notifier_block *self, unsigned long action, void *hcpu) @@ -1205,6 +1216,12 @@ static int __cpuinit console_cpu_notify(struct notifier_block *self, case CPU_UP_CANCELED: console_lock(); console_unlock(); + /* invoked with preemption disabled, so defer */ + case CPU_DYING: + if (!console_trylock()) + schedule_work(&console_cpu_notify_work); + else + console_unlock(); } return NOTIFY_OK; } diff --git a/kernel/resource.c b/kernel/resource.c index 3ff40178dce..fdd39398a71 100644 --- a/kernel/resource.c +++ b/kernel/resource.c @@ -261,6 +261,24 @@ int request_resource(struct resource *root, struct resource *new) EXPORT_SYMBOL(request_resource); +/** + * locate_resource - locate an already reserved I/O or memory resource + * @root: root resource descriptor + * @search: resource descriptor to be located + * + * Returns pointer to desired resource or NULL if not found. + */ +struct resource *locate_resource(struct resource *root, struct resource *search) +{ + struct resource *found; + + write_lock(&resource_lock); + found = __request_resource(root, search); + write_unlock(&resource_lock); + return found; +} +EXPORT_SYMBOL(locate_resource); + /** * release_resource - release a previously reserved resource * @old: resource pointer diff --git a/kernel/sched.c b/kernel/sched.c index bb4035cd582..a13457da703 100644 --- a/kernel/sched.c +++ b/kernel/sched.c @@ -4602,7 +4602,7 @@ void complete_all(struct completion *x) EXPORT_SYMBOL(complete_all); static inline long __sched -do_wait_for_common(struct completion *x, long timeout, int state) +do_wait_for_common(struct completion *x, long timeout, int state, int iowait) { if (!x->done) { DECLARE_WAITQUEUE(wait, current); @@ -4615,7 +4615,10 @@ do_wait_for_common(struct completion *x, long timeout, int state) } __set_current_state(state); spin_unlock_irq(&x->wait.lock); - timeout = schedule_timeout(timeout); + if (iowait) + timeout = io_schedule_timeout(timeout); + else + timeout = schedule_timeout(timeout); spin_lock_irq(&x->wait.lock); } while (!x->done && timeout); __remove_wait_queue(&x->wait, &wait); @@ -4627,12 +4630,12 @@ do_wait_for_common(struct completion *x, long timeout, int state) } static long __sched -wait_for_common(struct completion *x, long timeout, int state) +wait_for_common(struct completion *x, long timeout, int state, int iowait) { might_sleep(); spin_lock_irq(&x->wait.lock); - timeout = do_wait_for_common(x, timeout, state); + timeout = do_wait_for_common(x, timeout, state, iowait); spin_unlock_irq(&x->wait.lock); return timeout; } @@ -4649,10 +4652,23 @@ wait_for_common(struct completion *x, long timeout, int state) */ void __sched wait_for_completion(struct completion *x) { - wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE); + wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE, 0); } EXPORT_SYMBOL(wait_for_completion); +/** + * wait_for_completion_io: - waits for completion of a task + * @x: holds the state of this particular completion + * + * This waits for completion of a specific task to be signaled. Treats any + * sleeping as waiting for IO for the purposes of process accounting. + */ +void __sched wait_for_completion_io(struct completion *x) +{ + wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE, 1); +} +EXPORT_SYMBOL(wait_for_completion_io); + /** * wait_for_completion_timeout: - waits for completion of a task (w/timeout) * @x: holds the state of this particular completion @@ -4665,7 +4681,7 @@ EXPORT_SYMBOL(wait_for_completion); unsigned long __sched wait_for_completion_timeout(struct completion *x, unsigned long timeout) { - return wait_for_common(x, timeout, TASK_UNINTERRUPTIBLE); + return wait_for_common(x, timeout, TASK_UNINTERRUPTIBLE, 0); } EXPORT_SYMBOL(wait_for_completion_timeout); @@ -4678,7 +4694,8 @@ EXPORT_SYMBOL(wait_for_completion_timeout); */ int __sched wait_for_completion_interruptible(struct completion *x) { - long t = wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_INTERRUPTIBLE); + long t = + wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_INTERRUPTIBLE, 0); if (t == -ERESTARTSYS) return t; return 0; @@ -4697,7 +4714,7 @@ long __sched wait_for_completion_interruptible_timeout(struct completion *x, unsigned long timeout) { - return wait_for_common(x, timeout, TASK_INTERRUPTIBLE); + return wait_for_common(x, timeout, TASK_INTERRUPTIBLE, 0); } EXPORT_SYMBOL(wait_for_completion_interruptible_timeout); @@ -4710,7 +4727,7 @@ EXPORT_SYMBOL(wait_for_completion_interruptible_timeout); */ int __sched wait_for_completion_killable(struct completion *x) { - long t = wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_KILLABLE); + long t = wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_KILLABLE, 0); if (t == -ERESTARTSYS) return t; return 0; @@ -4730,7 +4747,7 @@ long __sched wait_for_completion_killable_timeout(struct completion *x, unsigned long timeout) { - return wait_for_common(x, timeout, TASK_KILLABLE); + return wait_for_common(x, timeout, TASK_KILLABLE, 0); } EXPORT_SYMBOL(wait_for_completion_killable_timeout); @@ -5723,6 +5740,7 @@ long __sched io_schedule_timeout(long timeout) delayacct_blkio_end(); return ret; } +EXPORT_SYMBOL(io_schedule_timeout); /** * sys_sched_get_priority_max - return maximum RT priority. @@ -7808,6 +7826,9 @@ static int cpuset_cpu_active(struct notifier_block *nfb, unsigned long action, { switch (action & ~CPU_TASKS_FROZEN) { case CPU_ONLINE: + case CPU_ONLINE_FROZEN: + case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: case CPU_DOWN_FAILED: cpuset_update_active_cpus(); return NOTIFY_OK; @@ -8971,6 +8992,15 @@ cpu_cgroup_allow_attach(struct cgroup *cgrp, struct task_struct *tsk) static int cpu_cgroup_can_attach_task(struct cgroup *cgrp, struct task_struct *tsk) { + if ((current != tsk) && (!capable(CAP_SYS_NICE))) { + const struct cred *cred = current_cred(), *tcred; + + tcred = __task_cred(tsk); + + if (cred->euid != tcred->uid && cred->euid != tcred->suid) + return -EPERM; + } + #ifdef CONFIG_RT_GROUP_SCHED if (!sched_rt_can_attach(cgroup_tg(cgrp), tsk)) return -EINVAL; diff --git a/kernel/sysctl.c b/kernel/sysctl.c index ea468b1232a..4c5eac46cd4 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -985,6 +985,19 @@ static struct ctl_table kern_table[] = { .proc_handler = proc_dointvec, }, #endif +#ifdef CONFIG_ARM + { + .procname = "boot_reason", + .data = &boot_reason, + .maxlen = sizeof(int), + .mode = 0444, + .proc_handler = proc_dointvec, +}, +#endif +/* + * NOTE: do not add new entries to this table unless you have read + * Documentation/sysctl/ctl_unnumbered.txt + */ { } }; diff --git a/kernel/sysctl_binary.c b/kernel/sysctl_binary.c index 3b8e028b960..4406ba2e985 100644 --- a/kernel/sysctl_binary.c +++ b/kernel/sysctl_binary.c @@ -137,6 +137,7 @@ static const struct bin_table bin_kern_table[] = { { CTL_INT, KERN_COMPAT_LOG, "compat-log" }, { CTL_INT, KERN_MAX_LOCK_DEPTH, "max_lock_depth" }, { CTL_INT, KERN_PANIC_ON_NMI, "panic_on_unrecovered_nmi" }, + { CTL_INT, KERN_BOOT_REASON, "boot_reason" }, {} }; diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 0400553f0d0..d86d61feb2f 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3068,6 +3068,32 @@ void destroy_workqueue(struct workqueue_struct *wq) } EXPORT_SYMBOL_GPL(destroy_workqueue); +/** + * workqueue_empty - test whether a workqueue is empty + * @wq: target workqueue + * + * Test whether @wq's cpu workqueue(s) are empty. + * + * Returns: false - workqueue is not empty + * true - workqueue is empty + */ +bool workqueue_empty(struct workqueue_struct *wq) +{ + int cpu; + + for_each_cwq_cpu(cpu, wq) { + struct cpu_workqueue_struct *cwq = get_cwq(cpu, wq); + + if (!cwq->nr_active && list_empty(&cwq->delayed_works)) + continue; + + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(workqueue_empty); + /** * workqueue_set_max_active - adjust max_active of a workqueue * @wq: target workqueue diff --git a/lib/Makefile b/lib/Makefile index 6b597fdb189..d1f8ea2a30a 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -12,7 +12,7 @@ lib-y := ctype.o string.o vsprintf.o cmdline.o \ idr.o int_sqrt.o extable.o prio_tree.o \ sha1.o irq_regs.o reciprocal_div.o argv_split.o \ proportions.o prio_heap.o ratelimit.o show_mem.o \ - is_single_threaded.o plist.o decompress.o find_next_bit.o + is_single_threaded.o plist.o decompress.o find_next_bit.o memory_alloc.o lib-$(CONFIG_MMU) += ioremap.o lib-$(CONFIG_SMP) += cpumask.o diff --git a/lib/bitmap.c b/lib/bitmap.c index 3f3b68199d7..cf12bb86d7c 100644 --- a/lib/bitmap.c +++ b/lib/bitmap.c @@ -315,30 +315,32 @@ void bitmap_clear(unsigned long *map, int start, int nr) } EXPORT_SYMBOL(bitmap_clear); -/* +/** * bitmap_find_next_zero_area - find a contiguous aligned zero area * @map: The address to base the search on * @size: The bitmap size in bits * @start: The bitnumber to start searching at * @nr: The number of zeroed bits we're looking for * @align_mask: Alignment mask for zero area + * @align_offset: Alignment offset for zero area. * * The @align_mask should be one less than a power of 2; the effect is that - * the bit offset of all zero areas this function finds is multiples of that - * power of 2. A @align_mask of 0 means no alignment is required. + * the bit offset of all zero areas this function finds plus @align_offset + * is multiple of that power of 2. */ -unsigned long bitmap_find_next_zero_area(unsigned long *map, - unsigned long size, - unsigned long start, - unsigned int nr, - unsigned long align_mask) +unsigned long bitmap_find_next_zero_area_off(unsigned long *map, + unsigned long size, + unsigned long start, + unsigned int nr, + unsigned long align_mask, + unsigned long align_offset) { unsigned long index, end, i; again: index = find_next_zero_bit(map, size, start); /* Align allocation */ - index = __ALIGN_MASK(index, align_mask); + index = __ALIGN_MASK(index + align_offset, align_mask) - align_offset; end = index + nr; if (end > size) @@ -350,7 +352,7 @@ unsigned long bitmap_find_next_zero_area(unsigned long *map, } return index; } -EXPORT_SYMBOL(bitmap_find_next_zero_area); +EXPORT_SYMBOL(bitmap_find_next_zero_area_off); /* * Bitmap printing & parsing functions: first version by Bill Irwin, diff --git a/lib/debugobjects.c b/lib/debugobjects.c index a78b7c6e042..b07b5b82f39 100644 --- a/lib/debugobjects.c +++ b/lib/debugobjects.c @@ -562,6 +562,39 @@ void debug_object_free(void *addr, struct debug_obj_descr *descr) raw_spin_unlock_irqrestore(&db->lock, flags); } +/** + * debug_object_assert_init - debug checks when object should be init-ed + * @addr: address of the object + * @descr: pointer to an object specific debug description structure + */ +void debug_object_assert_init(void *addr, struct debug_obj_descr *descr) +{ + struct debug_bucket *db; + struct debug_obj *obj; + unsigned long flags; + + if (!debug_objects_enabled) + return; + + db = get_bucket((unsigned long) addr); + + raw_spin_lock_irqsave(&db->lock, flags); + + obj = lookup_object(addr, db); + if (!obj) { + raw_spin_unlock_irqrestore(&db->lock, flags); + /* + * Maybe the object is static. Let the type specific + * code decide what to do. + */ + debug_object_fixup(descr->fixup_assert_init, addr, + ODEBUG_STATE_NOTAVAILABLE); + return; + } + + raw_spin_unlock_irqrestore(&db->lock, flags); +} + /** * debug_object_active_state - debug checks object usage state machine * @addr: address of the object diff --git a/lib/genalloc.c b/lib/genalloc.c index 577ddf80597..c7b9b9c41b7 100644 --- a/lib/genalloc.c +++ b/lib/genalloc.c @@ -16,23 +16,45 @@ #include +/* General purpose special memory pool descriptor. */ +struct gen_pool { + rwlock_t lock; /* protects chunks list */ + struct list_head chunks; /* list of chunks in this pool */ + unsigned order; /* minimum allocation order */ +}; + +/* General purpose special memory pool chunk descriptor. */ +struct gen_pool_chunk { + spinlock_t lock; /* protects bits */ + struct list_head next_chunk; /* next chunk in pool */ + phys_addr_t phys_addr; /* physical starting address of memory chunk */ + unsigned long start; /* start of memory chunk */ + unsigned long size; /* number of bits */ + unsigned long bits[0]; /* bitmap for allocating memory chunk */ +}; + /** - * gen_pool_create - create a new special memory pool - * @min_alloc_order: log base 2 of number of bytes each bitmap bit represents - * @nid: node id of the node the pool structure should be allocated on, or -1 + * gen_pool_create() - create a new special memory pool + * @order: Log base 2 of number of bytes each bitmap bit + * represents. + * @nid: Node id of the node the pool structure should be allocated + * on, or -1. This will be also used for other allocations. * * Create a new special memory pool that can be used to manage special purpose * memory not managed by the regular kmalloc/kfree interface. */ -struct gen_pool *gen_pool_create(int min_alloc_order, int nid) +struct gen_pool *__must_check gen_pool_create(unsigned order, int nid) { struct gen_pool *pool; - pool = kmalloc_node(sizeof(struct gen_pool), GFP_KERNEL, nid); - if (pool != NULL) { + if (WARN_ON(order >= BITS_PER_LONG)) + return NULL; + + pool = kmalloc_node(sizeof *pool, GFP_KERNEL, nid); + if (pool) { rwlock_init(&pool->lock); INIT_LIST_HEAD(&pool->chunks); - pool->min_alloc_order = min_alloc_order; + pool->order = order; } return pool; } @@ -51,22 +73,29 @@ EXPORT_SYMBOL(gen_pool_create); * * Returns 0 on success or a -ve errno on failure. */ -int gen_pool_add_virt(struct gen_pool *pool, unsigned long virt, phys_addr_t phys, +int __must_check gen_pool_add_virt(struct gen_pool *pool, unsigned long virt, phys_addr_t phys, size_t size, int nid) { struct gen_pool_chunk *chunk; - int nbits = size >> pool->min_alloc_order; - int nbytes = sizeof(struct gen_pool_chunk) + - (nbits + BITS_PER_BYTE - 1) / BITS_PER_BYTE; + size_t nbytes; + + if (WARN_ON(!virt || virt + size < virt || + (virt & ((1 << pool->order) - 1)))) + return -EINVAL; - chunk = kmalloc_node(nbytes, GFP_KERNEL | __GFP_ZERO, nid); - if (unlikely(chunk == NULL)) + size = size >> pool->order; + if (WARN_ON(!size)) + return -EINVAL; + + nbytes = sizeof *chunk + BITS_TO_LONGS(size) * sizeof *chunk->bits; + chunk = kzalloc_node(nbytes, GFP_KERNEL, nid); + if (!chunk) return -ENOMEM; spin_lock_init(&chunk->lock); chunk->phys_addr = phys; - chunk->start_addr = virt; - chunk->end_addr = virt + size; + chunk->start = virt >> pool->order; + chunk->size = size; write_lock(&pool->lock); list_add(&chunk->next_chunk, &pool->chunks); @@ -92,8 +121,9 @@ phys_addr_t gen_pool_virt_to_phys(struct gen_pool *pool, unsigned long addr) list_for_each(_chunk, &pool->chunks) { chunk = list_entry(_chunk, struct gen_pool_chunk, next_chunk); - if (addr >= chunk->start_addr && addr < chunk->end_addr) - return chunk->phys_addr + addr - chunk->start_addr; + if (addr >= chunk->start && + addr < (chunk->start + chunk->size)) + return chunk->phys_addr + addr - chunk->start; } read_unlock(&pool->lock); @@ -102,115 +132,116 @@ phys_addr_t gen_pool_virt_to_phys(struct gen_pool *pool, unsigned long addr) EXPORT_SYMBOL(gen_pool_virt_to_phys); /** - * gen_pool_destroy - destroy a special memory pool - * @pool: pool to destroy + * gen_pool_destroy() - destroy a special memory pool + * @pool: Pool to destroy. * * Destroy the specified special memory pool. Verifies that there are no * outstanding allocations. */ void gen_pool_destroy(struct gen_pool *pool) { - struct list_head *_chunk, *_next_chunk; struct gen_pool_chunk *chunk; - int order = pool->min_alloc_order; - int bit, end_bit; - + int bit; - list_for_each_safe(_chunk, _next_chunk, &pool->chunks) { - chunk = list_entry(_chunk, struct gen_pool_chunk, next_chunk); + while (!list_empty(&pool->chunks)) { + chunk = list_entry(pool->chunks.next, struct gen_pool_chunk, + next_chunk); list_del(&chunk->next_chunk); - end_bit = (chunk->end_addr - chunk->start_addr) >> order; - bit = find_next_bit(chunk->bits, end_bit, 0); - BUG_ON(bit < end_bit); + bit = find_next_bit(chunk->bits, chunk->size, 0); + BUG_ON(bit < chunk->size); kfree(chunk); } kfree(pool); - return; } EXPORT_SYMBOL(gen_pool_destroy); /** - * gen_pool_alloc - allocate special memory from the pool - * @pool: pool to allocate from - * @size: number of bytes to allocate from the pool + * gen_pool_alloc_aligned() - allocate special memory from the pool + * @pool: Pool to allocate from. + * @size: Number of bytes to allocate from the pool. + * @alignment_order: Order the allocated space should be + * aligned to (eg. 20 means allocated space + * must be aligned to 1MiB). * * Allocate the requested number of bytes from the specified pool. * Uses a first-fit algorithm. */ -unsigned long gen_pool_alloc(struct gen_pool *pool, size_t size) +unsigned long __must_check +gen_pool_alloc_aligned(struct gen_pool *pool, size_t size, + unsigned alignment_order) { - struct list_head *_chunk; + unsigned long addr, align_mask = 0, flags, start; struct gen_pool_chunk *chunk; - unsigned long addr, flags; - int order = pool->min_alloc_order; - int nbits, start_bit, end_bit; if (size == 0) return 0; - nbits = (size + (1UL << order) - 1) >> order; + if (alignment_order > pool->order) + align_mask = (1 << (alignment_order - pool->order)) - 1; - read_lock(&pool->lock); - list_for_each(_chunk, &pool->chunks) { - chunk = list_entry(_chunk, struct gen_pool_chunk, next_chunk); + size = (size + (1UL << pool->order) - 1) >> pool->order; - end_bit = (chunk->end_addr - chunk->start_addr) >> order; + read_lock(&pool->lock); + list_for_each_entry(chunk, &pool->chunks, next_chunk) { + if (chunk->size < size) + continue; spin_lock_irqsave(&chunk->lock, flags); - start_bit = bitmap_find_next_zero_area(chunk->bits, end_bit, 0, - nbits, 0); - if (start_bit >= end_bit) { + start = bitmap_find_next_zero_area_off(chunk->bits, chunk->size, + 0, size, align_mask, + chunk->start); + if (start >= chunk->size) { spin_unlock_irqrestore(&chunk->lock, flags); continue; } - addr = chunk->start_addr + ((unsigned long)start_bit << order); - - bitmap_set(chunk->bits, start_bit, nbits); + bitmap_set(chunk->bits, start, size); spin_unlock_irqrestore(&chunk->lock, flags); - read_unlock(&pool->lock); - return addr; + addr = (chunk->start + start) << pool->order; + goto done; } + + addr = 0; +done: read_unlock(&pool->lock); - return 0; + return addr; } -EXPORT_SYMBOL(gen_pool_alloc); +EXPORT_SYMBOL(gen_pool_alloc_aligned); /** - * gen_pool_free - free allocated special memory back to the pool - * @pool: pool to free to - * @addr: starting address of memory to free back to pool - * @size: size in bytes of memory to free + * gen_pool_free() - free allocated special memory back to the pool + * @pool: Pool to free to. + * @addr: Starting address of memory to free back to pool. + * @size: Size in bytes of memory to free. * * Free previously allocated special memory back to the specified pool. */ void gen_pool_free(struct gen_pool *pool, unsigned long addr, size_t size) { - struct list_head *_chunk; struct gen_pool_chunk *chunk; unsigned long flags; - int order = pool->min_alloc_order; - int bit, nbits; - nbits = (size + (1UL << order) - 1) >> order; + if (!size) + return; - read_lock(&pool->lock); - list_for_each(_chunk, &pool->chunks) { - chunk = list_entry(_chunk, struct gen_pool_chunk, next_chunk); + addr = addr >> pool->order; + size = (size + (1UL << pool->order) - 1) >> pool->order; + + BUG_ON(addr + size < addr); - if (addr >= chunk->start_addr && addr < chunk->end_addr) { - BUG_ON(addr + size > chunk->end_addr); + read_lock(&pool->lock); + list_for_each_entry(chunk, &pool->chunks, next_chunk) + if (addr >= chunk->start && + addr + size <= chunk->start + chunk->size) { spin_lock_irqsave(&chunk->lock, flags); - bit = (addr - chunk->start_addr) >> order; - while (nbits--) - __clear_bit(bit++, chunk->bits); + bitmap_clear(chunk->bits, addr - chunk->start, size); spin_unlock_irqrestore(&chunk->lock, flags); - break; + goto done; } - } - BUG_ON(nbits > 0); + BUG_ON(1); +done: read_unlock(&pool->lock); } EXPORT_SYMBOL(gen_pool_free); diff --git a/lib/memory_alloc.c b/lib/memory_alloc.c new file mode 100644 index 00000000000..2e020deab3c --- /dev/null +++ b/lib/memory_alloc.c @@ -0,0 +1,328 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_MEMPOOLS 8 + +struct mem_pool mpools[MAX_MEMPOOLS]; + +/* The tree contains all allocations over all memory pools */ +static struct rb_root alloc_root; +static struct mutex alloc_mutex; + +static struct alloc *find_alloc(void *addr) +{ + struct rb_root *root = &alloc_root; + struct rb_node *p = root->rb_node; + + mutex_lock(&alloc_mutex); + + while (p) { + struct alloc *node; + + node = rb_entry(p, struct alloc, rb_node); + if (addr < node->vaddr) + p = p->rb_left; + else if (addr > node->vaddr) + p = p->rb_right; + else { + mutex_unlock(&alloc_mutex); + return node; + } + } + mutex_unlock(&alloc_mutex); + return NULL; +} + +static int add_alloc(struct alloc *node) +{ + struct rb_root *root = &alloc_root; + struct rb_node **p = &root->rb_node; + struct rb_node *parent = NULL; + + mutex_lock(&alloc_mutex); + while (*p) { + struct alloc *tmp; + parent = *p; + + tmp = rb_entry(parent, struct alloc, rb_node); + + if (node->vaddr < tmp->vaddr) + p = &(*p)->rb_left; + else if (node->vaddr > tmp->vaddr) + p = &(*p)->rb_right; + else { + WARN(1, "memory at %p already allocated", tmp->vaddr); + mutex_unlock(&alloc_mutex); + return -EINVAL; + } + } + rb_link_node(&node->rb_node, parent, p); + rb_insert_color(&node->rb_node, root); + mutex_unlock(&alloc_mutex); + return 0; +} + +static int remove_alloc(struct alloc *victim_node) +{ + struct rb_root *root = &alloc_root; + if (!victim_node) + return -EINVAL; + + mutex_lock(&alloc_mutex); + rb_erase(&victim_node->rb_node, root); + mutex_unlock(&alloc_mutex); + return 0; +} + +static struct gen_pool *initialize_gpool(unsigned long start, + unsigned long size) +{ + struct gen_pool *gpool; + + gpool = gen_pool_create(PAGE_SHIFT, -1); + + if (!gpool) + return NULL; + if (gen_pool_add(gpool, start, size, -1)) { + gen_pool_destroy(gpool); + return NULL; + } + + return gpool; +} + +static void *__alloc(struct mem_pool *mpool, unsigned long size, + unsigned long align, int cached) +{ + unsigned long paddr; + void __iomem *vaddr; + + unsigned long aligned_size; + int log_align = ilog2(align); + + struct alloc *node; + + aligned_size = PFN_ALIGN(size); + paddr = gen_pool_alloc_aligned(mpool->gpool, aligned_size, log_align); + if (!paddr) + return NULL; + + node = kmalloc(sizeof(struct alloc), GFP_KERNEL); + if (!node) + goto out; + + if (cached) + vaddr = ioremap_cached(paddr, aligned_size); + else + vaddr = ioremap(paddr, aligned_size); + + if (!vaddr) + goto out_kfree; + + node->vaddr = vaddr; + node->paddr = paddr; + node->len = aligned_size; + node->mpool = mpool; + if (add_alloc(node)) + goto out_kfree; + + mpool->free -= aligned_size; + + return vaddr; +out_kfree: + if (vaddr) + iounmap(vaddr); + kfree(node); +out: + gen_pool_free(mpool->gpool, paddr, aligned_size); + return NULL; +} + +static void __free(void *vaddr, bool unmap) +{ + struct alloc *node = find_alloc(vaddr); + + if (!node) + return; + + if (unmap) + iounmap(node->vaddr); + + gen_pool_free(node->mpool->gpool, node->paddr, node->len); + node->mpool->free += node->len; + + remove_alloc(node); + kfree(node); +} + +static struct mem_pool *mem_type_to_memory_pool(int mem_type) +{ + struct mem_pool *mpool = &mpools[mem_type]; + + if (!mpool->size) + return NULL; + + mutex_lock(&mpool->pool_mutex); + if (!mpool->gpool) + mpool->gpool = initialize_gpool(mpool->paddr, mpool->size); + mutex_unlock(&mpool->pool_mutex); + if (!mpool->gpool) + return NULL; + + return mpool; +} + +struct mem_pool *initialize_memory_pool(unsigned long start, + unsigned long size, int mem_type) +{ + int id = mem_type; + + if (id >= MAX_MEMPOOLS || size <= PAGE_SIZE || size % PAGE_SIZE) + return NULL; + + mutex_lock(&mpools[id].pool_mutex); + + mpools[id].paddr = start; + mpools[id].size = size; + mpools[id].free = size; + mutex_unlock(&mpools[id].pool_mutex); + + pr_info("memory pool %d (start %lx size %lx) initialized\n", + id, start, size); + return &mpools[id]; +} +EXPORT_SYMBOL_GPL(initialize_memory_pool); + +void *allocate_contiguous_memory(unsigned long size, + int mem_type, unsigned long align, int cached) +{ + unsigned long aligned_size = PFN_ALIGN(size); + struct mem_pool *mpool; + + mpool = mem_type_to_memory_pool(mem_type); + if (!mpool) + return NULL; + return __alloc(mpool, aligned_size, align, cached); + +} +EXPORT_SYMBOL_GPL(allocate_contiguous_memory); + +unsigned long allocate_contiguous_memory_nomap(unsigned long size, + int mem_type, unsigned long align) +{ + unsigned long paddr; + unsigned long aligned_size; + + struct alloc *node; + struct mem_pool *mpool; + int log_align = ilog2(align); + + mpool = mem_type_to_memory_pool(mem_type); + if (!mpool || !mpool->gpool) + return 0; + + aligned_size = PFN_ALIGN(size); + paddr = gen_pool_alloc_aligned(mpool->gpool, aligned_size, log_align); + if (!paddr) + return 0; + + node = kmalloc(sizeof(struct alloc), GFP_KERNEL); + if (!node) + goto out; + + node->paddr = paddr; + + /* We search the tree using node->vaddr, so set + * it to something unique even though we don't + * use it for physical allocation nodes. + * The virtual and physical address ranges + * are disjoint, so there won't be any chance of + * a duplicate node->vaddr value. + */ + node->vaddr = (void *)paddr; + node->len = aligned_size; + node->mpool = mpool; + if (add_alloc(node)) + goto out_kfree; + + mpool->free -= aligned_size; + return paddr; +out_kfree: + kfree(node); +out: + gen_pool_free(mpool->gpool, paddr, aligned_size); + return 0; +} +EXPORT_SYMBOL_GPL(allocate_contiguous_memory_nomap); + +void free_contiguous_memory(void *addr) +{ + if (!addr) + return; + __free(addr, true); + return; +} +EXPORT_SYMBOL_GPL(free_contiguous_memory); + +void free_contiguous_memory_by_paddr(unsigned long paddr) +{ + if (!paddr) + return; + __free((void *)paddr, false); + return; +} +EXPORT_SYMBOL_GPL(free_contiguous_memory_by_paddr); + +unsigned long memory_pool_node_paddr(void *vaddr) +{ + struct alloc *node = find_alloc(vaddr); + + if (!node) + return -EINVAL; + + return node->paddr; +} +EXPORT_SYMBOL_GPL(memory_pool_node_paddr); + +unsigned long memory_pool_node_len(void *vaddr) +{ + struct alloc *node = find_alloc(vaddr); + + if (!node) + return -EINVAL; + + return node->len; +} +EXPORT_SYMBOL_GPL(memory_pool_node_len); + +int __init memory_pool_init(void) +{ + int i; + + alloc_root = RB_ROOT; + mutex_init(&alloc_mutex); + for (i = 0; i < ARRAY_SIZE(mpools); i++) { + mutex_init(&mpools[i].pool_mutex); + mpools[i].gpool = NULL; + } + return 0; +} diff --git a/mm/ashmem.c b/mm/ashmem.c index 66e3f23ee33..c7e72bb107f 100644 --- a/mm/ashmem.c +++ b/mm/ashmem.c @@ -29,6 +29,7 @@ #include #include #include +#include #define ASHMEM_NAME_PREFIX "dev/ashmem/" #define ASHMEM_NAME_PREFIX_LEN (sizeof(ASHMEM_NAME_PREFIX) - 1) @@ -45,6 +46,8 @@ struct ashmem_area { struct list_head unpinned_list; /* list of all ashmem areas */ struct file *file; /* the shmem-based backing file */ size_t size; /* size of the mapping, in bytes */ + unsigned long vm_start; /* Start address of vm_area + * which maps this ashmem */ unsigned long prot_mask; /* allowed prot bits, as vm_flags */ }; @@ -326,6 +329,7 @@ static int ashmem_mmap(struct file *file, struct vm_area_struct *vma) vma->vm_file = asma->file; } vma->vm_flags |= VM_CAN_NONLINEAR; + asma->vm_start = vma->vm_start; out: mutex_unlock(&ashmem_mutex); @@ -626,6 +630,84 @@ static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd, return ret; } +#ifdef CONFIG_OUTER_CACHE +static unsigned int kgsl_virtaddr_to_physaddr(unsigned int virtaddr) +{ + unsigned int physaddr = 0; + pgd_t *pgd_ptr = NULL; + pmd_t *pmd_ptr = NULL; + pte_t *pte_ptr = NULL, pte; + + pgd_ptr = pgd_offset(current->mm, virtaddr); + if (pgd_none(*pgd) || pgd_bad(*pgd)) { + pr_info + ("Invalid pgd entry found while trying to convert virtual " + "address to physical\n"); + return 0; + } + + pmd_ptr = pmd_offset(pgd_ptr, virtaddr); + if (pmd_none(*pmd_ptr) || pmd_bad(*pmd_ptr)) { + pr_info + ("Invalid pmd entry found while trying to convert virtual " + "address to physical\n"); + return 0; + } + + pte_ptr = pte_offset_map(pmd_ptr, virtaddr); + if (!pte_ptr) { + pr_info + ("Unable to map pte entry while trying to convert virtual " + "address to physical\n"); + return 0; + } + pte = *pte_ptr; + physaddr = pte_pfn(pte); + pte_unmap(pte_ptr); + physaddr <<= PAGE_SHIFT; + return physaddr; +} +#endif + +static int ashmem_flush_cache_range(struct ashmem_area *asma) +{ +#ifdef CONFIG_OUTER_CACHE + unsigned long end; +#endif + unsigned long addr; + unsigned int size, result = 0; + + mutex_lock(&ashmem_mutex); + + size = asma->size; + addr = asma->vm_start; + if (!addr || (addr & (PAGE_SIZE - 1)) || !size || + (size & (PAGE_SIZE - 1))) { + result = -EINVAL; + goto done; + } + +#ifdef CONFIG_OUTER_CACHE + flush_cache_user_range(addr, addr + size); + for (end = addr; end < (addr + size); end += PAGE_SIZE) { + unsigned long physaddr; + physaddr = kgsl_virtaddr_to_physaddr(end); + if (!physaddr) { + result = -EINVAL; + goto done; + } + + outer_flush_range(physaddr, physaddr + PAGE_SIZE); + } + mb(); +#else + clean_and_invalidate_caches(addr, size, 0); +#endif +done: + mutex_unlock(&ashmem_mutex); + return 0; +} + static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ashmem_area *asma = file->private_data; @@ -671,11 +753,67 @@ static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ashmem_shrink(&ashmem_shrinker, &sc); } break; + case ASHMEM_CACHE_FLUSH_RANGE: + ret = ashmem_flush_cache_range(asma); + break; } return ret; } +static int is_ashmem_file(struct file *file) +{ + char fname[256], *name; + name = dentry_path(file->f_dentry, fname, 256); + return strcmp(name, "/ashmem") ? 0 : 1; +} + +int get_ashmem_file(int fd, struct file **filp, struct file **vm_file, + unsigned long *len) +{ + int ret = -1; + struct file *file = fget(fd); + *filp = NULL; + *vm_file = NULL; + if (unlikely(file == NULL)) { + pr_err("ashmem: %s: requested data from file " + "descriptor that doesn't exist.\n", __func__); + } else { + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; + pr_debug("filp %p rdev %d pid %u(%s) file %p(%ld)" + " dev id: %d\n", filp, + file->f_dentry->d_inode->i_rdev, + current->pid, get_task_comm(currtask_name, current), + file, file_count(file), + MINOR(file->f_dentry->d_inode->i_rdev)); + if (is_ashmem_file(file)) { + struct ashmem_area *asma = file->private_data; + *filp = file; + *vm_file = asma->file; + *len = asma->size; + ret = 0; + } else { + pr_err("file descriptor is not an ashmem " + "region fd: %d\n", fd); + fput(file); + } + } + return ret; +} +EXPORT_SYMBOL(get_ashmem_file); + +void put_ashmem_file(struct file *file) +{ + char currtask_name[FIELD_SIZEOF(struct task_struct, comm) + 1]; + pr_debug("rdev %d pid %u(%s) file %p(%ld)" " dev id: %d\n", + file->f_dentry->d_inode->i_rdev, current->pid, + get_task_comm(currtask_name, current), file, + file_count(file), MINOR(file->f_dentry->d_inode->i_rdev)); + if (file && is_ashmem_file(file)) + fput(file); +} +EXPORT_SYMBOL(put_ashmem_file); + static struct file_operations ashmem_fops = { .owner = THIS_MODULE, .open = ashmem_open, diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c index c46887b5a11..b4e4296e80a 100644 --- a/mm/memory_hotplug.c +++ b/mm/memory_hotplug.c @@ -112,9 +112,10 @@ void __ref put_page_bootmem(struct page *page) static void register_page_bootmem_info_section(unsigned long start_pfn) { - unsigned long *usemap, mapsize, section_nr, i; + unsigned long *usemap, mapsize, page_mapsize, section_nr, i, j; struct mem_section *ms; - struct page *page, *memmap; + struct page *page, *memmap, *page_page; + int memmap_page_valid; if (!pfn_valid(start_pfn)) return; @@ -133,9 +134,21 @@ static void register_page_bootmem_info_section(unsigned long start_pfn) mapsize = sizeof(struct page) * PAGES_PER_SECTION; mapsize = PAGE_ALIGN(mapsize) >> PAGE_SHIFT; - /* remember memmap's page */ - for (i = 0; i < mapsize; i++, page++) - get_page_bootmem(section_nr, page, SECTION_INFO); + page_mapsize = PAGE_SIZE/sizeof(struct page); + + /* remember memmap's page, except those that reference only holes */ + for (i = 0; i < mapsize; i++, page++) { + memmap_page_valid = 0; + page_page = __va(page_to_pfn(page) << PAGE_SHIFT); + for (j = 0; j < page_mapsize; j++, page_page++) { + if (early_pfn_valid(page_to_pfn(page_page))) { + memmap_page_valid = 1; + break; + } + } + if (memmap_page_valid) + get_page_bootmem(section_nr, page, SECTION_INFO); + } usemap = __nr_to_section(section_nr)->pageblock_flags; page = virt_to_page(usemap); @@ -596,6 +609,51 @@ int __ref add_memory(int nid, u64 start, u64 size) } EXPORT_SYMBOL_GPL(add_memory); +int __ref physical_remove_memory(u64 start, u64 size) +{ + int ret; + struct resource *res, *res_old; + res = kzalloc(sizeof(struct resource), GFP_KERNEL); + BUG_ON(!res); + + ret = arch_physical_remove_memory(start, size); + if (ret) { + kfree(res); + return ret; + } + + res->name = "System RAM"; + res->start = start; + res->end = start + size - 1; + res->flags = IORESOURCE_MEM | IORESOURCE_BUSY; + + res_old = locate_resource(&iomem_resource, res); + if (res_old) + release_memory_resource(res_old); + kfree(res); + + return ret; +} +EXPORT_SYMBOL_GPL(physical_remove_memory); + +int __ref physical_active_memory(u64 start, u64 size) +{ + int ret; + + ret = arch_physical_active_memory(start, size); + return ret; +} +EXPORT_SYMBOL_GPL(physical_active_memory); + +int __ref physical_low_power_memory(u64 start, u64 size) +{ + int ret; + + ret = arch_physical_low_power_memory(start, size); + return ret; +} +EXPORT_SYMBOL_GPL(physical_low_power_memory); + #ifdef CONFIG_MEMORY_HOTREMOVE /* * A free page on the buddy free lists (not the per-cpu lists) has PageBuddy @@ -934,6 +992,23 @@ int remove_memory(u64 start, u64 size) end_pfn = start_pfn + PFN_DOWN(size); return offline_pages(start_pfn, end_pfn, 120 * HZ); } + +void reserve_hotplug_pages(unsigned long start_pfn, unsigned long nr_pages) +{ + nr_pages = ((nr_pages + pageblock_nr_pages - 1) >> pageblock_order) + << pageblock_order; + offline_isolated_pages(start_pfn, start_pfn + nr_pages); +} + +void unreserve_hotplug_pages(unsigned long start_pfn, unsigned long nr_pages) +{ + unsigned long onlined_pages = 0; + + nr_pages = ((nr_pages + pageblock_nr_pages - 1) >> pageblock_order) + << pageblock_order; + online_pages_range(start_pfn, nr_pages, &onlined_pages); +} + #else int remove_memory(u64 start, u64 size) { diff --git a/mm/sparse.c b/mm/sparse.c index aa64b12831a..8193ed88b01 100644 --- a/mm/sparse.c +++ b/mm/sparse.c @@ -121,8 +121,10 @@ static inline int sparse_index_init(unsigned long section_nr, int nid) int __section_nr(struct mem_section* ms) { unsigned long root_nr; - struct mem_section* root; + struct mem_section *root; + if (NR_SECTION_ROOTS == 0) + return ms - __nr_to_section(0); for (root_nr = 0; root_nr < NR_SECTION_ROOTS; root_nr++) { root = __nr_to_section(root_nr * SECTIONS_PER_ROOT); if (!root) diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig index bfb3dc03c9d..8bb69df41ce 100644 --- a/net/bluetooth/Kconfig +++ b/net/bluetooth/Kconfig @@ -6,7 +6,6 @@ menuconfig BT tristate "Bluetooth subsystem support" depends on NET && !S390 depends on RFKILL || !RFKILL - select CRYPTO help Bluetooth is low-cost, low-power, short-range wireless technology. It was designed as a replacement for cables and other short-range @@ -37,11 +36,11 @@ if BT != n config BT_L2CAP bool "L2CAP protocol support" + depends on CRYPTO_HMAC + depends on CRYPTO_SHA256 select CRC16 - select CRYPTO select CRYPTO_BLKCIPHER select CRYPTO_AES - select CRYPTO_ECB help L2CAP (Logical Link Control and Adaptation Protocol) provides connection oriented and connection-less data transport. L2CAP diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index 9b67f3d08fa..6dfe088c1b4 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -9,5 +9,5 @@ obj-$(CONFIG_BT_CMTP) += cmtp/ obj-$(CONFIG_BT_HIDP) += hidp/ bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o hci_sock.o hci_sysfs.o lib.o -bluetooth-$(CONFIG_BT_L2CAP) += l2cap_core.o l2cap_sock.o smp.o +bluetooth-$(CONFIG_BT_L2CAP) += l2cap_core.o l2cap_sock.o amp.o smp.o bluetooth-$(CONFIG_BT_SCO) += sco.o diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index 7c73a10d7ed..219df5ca811 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -387,7 +387,7 @@ int bt_sock_stream_recvmsg(struct kiocb *iocb, struct socket *sock, } chunk = min_t(unsigned int, skb->len, size); - if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) { + if (skb_copy_datagram_iovec(skb, 0, msg->msg_iov, chunk)) { skb_queue_head(&sk->sk_receive_queue, skb); if (!copied) copied = -EFAULT; @@ -399,7 +399,33 @@ int bt_sock_stream_recvmsg(struct kiocb *iocb, struct socket *sock, sock_recv_ts_and_drops(msg, sk, skb); if (!(flags & MSG_PEEK)) { - skb_pull(skb, chunk); + int skb_len = skb_headlen(skb); + + if (chunk <= skb_len) { + __skb_pull(skb, chunk); + } else { + struct sk_buff *frag; + + __skb_pull(skb, skb_len); + chunk -= skb_len; + + skb_walk_frags(skb, frag) { + if (chunk <= frag->len) { + /* Pulling partial data */ + skb->len -= chunk; + skb->data_len -= chunk; + __skb_pull(frag, chunk); + break; + } else if (frag->len) { + /* Pulling all frag data */ + chunk -= frag->len; + skb->len -= frag->len; + skb->data_len -= frag->len; + __skb_pull(frag, frag->len); + } + } + } + if (skb->len) { skb_queue_head(&sk->sk_receive_queue, skb); break; @@ -532,8 +558,9 @@ int bt_sock_wait_state(struct sock *sk, int state, unsigned long timeo) BT_DBG("sk %p", sk); add_wait_queue(sk_sleep(sk), &wait); - set_current_state(TASK_INTERRUPTIBLE); while (sk->sk_state != state) { + set_current_state(TASK_INTERRUPTIBLE); + if (!timeo) { err = -EINPROGRESS; break; @@ -547,13 +574,12 @@ int bt_sock_wait_state(struct sock *sk, int state, unsigned long timeo) release_sock(sk); timeo = schedule_timeout(timeo); lock_sock(sk); - set_current_state(TASK_INTERRUPTIBLE); err = sock_error(sk); if (err) break; } - __set_current_state(TASK_RUNNING); + set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); return err; } diff --git a/net/bluetooth/amp.c b/net/bluetooth/amp.c new file mode 100644 index 00000000000..cb43a9caf45 --- /dev/null +++ b/net/bluetooth/amp.c @@ -0,0 +1,2035 @@ +/* + Copyright (c) 2010-2011 Code Aurora Forum. 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 and + only version 2 as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +static struct workqueue_struct *amp_workqueue; + +LIST_HEAD(amp_mgr_list); +DEFINE_RWLOCK(amp_mgr_list_lock); + +static int send_a2mp(struct socket *sock, u8 *data, int len); + +static void ctx_timeout(unsigned long data); + +static void launch_ctx(struct amp_mgr *mgr); +static int execute_ctx(struct amp_ctx *ctx, u8 evt_type, void *data); +static int kill_ctx(struct amp_ctx *ctx); +static int cancel_ctx(struct amp_ctx *ctx); + +static struct socket *open_fixed_channel(bdaddr_t *src, bdaddr_t *dst); + +static void remove_amp_mgr(struct amp_mgr *mgr) +{ + BT_DBG("mgr %p", mgr); + + write_lock_bh(&_mgr_list_lock); + list_del(&mgr->list); + write_unlock_bh(&_mgr_list_lock); + + read_lock_bh(&mgr->ctx_list_lock); + while (!list_empty(&mgr->ctx_list)) { + struct amp_ctx *ctx; + ctx = list_first_entry(&mgr->ctx_list, struct amp_ctx, list); + read_unlock_bh(&mgr->ctx_list_lock); + BT_DBG("kill ctx %p", ctx); + kill_ctx(ctx); + read_lock_bh(&mgr->ctx_list_lock); + } + read_unlock_bh(&mgr->ctx_list_lock); + + kfree(mgr->ctrls); + + kfree(mgr); +} + +static struct amp_mgr *get_amp_mgr_sk(struct sock *sk) +{ + struct amp_mgr *mgr; + struct amp_mgr *found = NULL; + + read_lock_bh(&_mgr_list_lock); + list_for_each_entry(mgr, &_mgr_list, list) { + if ((mgr->a2mp_sock) && (mgr->a2mp_sock->sk == sk)) { + found = mgr; + break; + } + } + read_unlock_bh(&_mgr_list_lock); + return found; +} + +static struct amp_mgr *get_create_amp_mgr(struct l2cap_conn *conn, + struct sk_buff *skb) +{ + struct amp_mgr *mgr; + + write_lock_bh(&_mgr_list_lock); + list_for_each_entry(mgr, &_mgr_list, list) { + if (mgr->l2cap_conn == conn) { + BT_DBG("conn %p found %p", conn, mgr); + goto gc_finished; + } + } + + mgr = kzalloc(sizeof(*mgr), GFP_ATOMIC); + if (!mgr) + goto gc_finished; + + mgr->l2cap_conn = conn; + mgr->next_ident = 1; + INIT_LIST_HEAD(&mgr->ctx_list); + rwlock_init(&mgr->ctx_list_lock); + mgr->skb = skb; + BT_DBG("conn %p mgr %p", conn, mgr); + mgr->a2mp_sock = open_fixed_channel(conn->src, conn->dst); + if (!mgr->a2mp_sock) { + kfree(mgr); + goto gc_finished; + } + list_add(&(mgr->list), &_mgr_list); + +gc_finished: + write_unlock_bh(&_mgr_list_lock); + return mgr; +} + +static struct amp_ctrl *get_ctrl(struct amp_mgr *mgr, u8 remote_id) +{ + if ((mgr->ctrls) && (mgr->ctrls->id == remote_id)) + return mgr->ctrls; + else + return NULL; +} + +static struct amp_ctrl *get_create_ctrl(struct amp_mgr *mgr, u8 id) +{ + struct amp_ctrl *ctrl; + + BT_DBG("mgr %p, id %d", mgr, id); + if ((mgr->ctrls) && (mgr->ctrls->id == id)) + ctrl = mgr->ctrls; + else { + kfree(mgr->ctrls); + ctrl = kzalloc(sizeof(struct amp_ctrl), GFP_ATOMIC); + if (ctrl) { + ctrl->mgr = mgr; + ctrl->id = id; + } + mgr->ctrls = ctrl; + } + + return ctrl; +} + +static struct amp_ctx *create_ctx(u8 type, u8 state) +{ + struct amp_ctx *ctx = NULL; + + ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); + if (ctx) { + ctx->type = type; + ctx->state = state; + init_timer(&(ctx->timer)); + ctx->timer.function = ctx_timeout; + ctx->timer.data = (unsigned long) ctx; + } + BT_DBG("ctx %p, type %d", ctx, type); + return ctx; +} + +static inline void start_ctx(struct amp_mgr *mgr, struct amp_ctx *ctx) +{ + BT_DBG("ctx %p", ctx); + write_lock_bh(&mgr->ctx_list_lock); + list_add(&ctx->list, &mgr->ctx_list); + write_unlock_bh(&mgr->ctx_list_lock); + ctx->mgr = mgr; + execute_ctx(ctx, AMP_INIT, 0); +} + +static void destroy_ctx(struct amp_ctx *ctx) +{ + struct amp_mgr *mgr = ctx->mgr; + + BT_DBG("ctx %p deferred %p", ctx, ctx->deferred); + del_timer(&ctx->timer); + write_lock_bh(&mgr->ctx_list_lock); + list_del(&ctx->list); + write_unlock_bh(&mgr->ctx_list_lock); + if (ctx->deferred) + execute_ctx(ctx->deferred, AMP_INIT, 0); + kfree(ctx); +} + +static struct amp_ctx *get_ctx_mgr(struct amp_mgr *mgr, u8 type) +{ + struct amp_ctx *fnd = NULL; + struct amp_ctx *ctx; + + read_lock_bh(&mgr->ctx_list_lock); + list_for_each_entry(ctx, &mgr->ctx_list, list) { + if (ctx->type == type) { + fnd = ctx; + break; + } + } + read_unlock_bh(&mgr->ctx_list_lock); + return fnd; +} + +static struct amp_ctx *get_ctx_type(struct amp_ctx *cur, u8 type) +{ + struct amp_mgr *mgr = cur->mgr; + struct amp_ctx *fnd = NULL; + struct amp_ctx *ctx; + + read_lock_bh(&mgr->ctx_list_lock); + list_for_each_entry(ctx, &mgr->ctx_list, list) { + if ((ctx->type == type) && (ctx != cur)) { + fnd = ctx; + break; + } + } + read_unlock_bh(&mgr->ctx_list_lock); + return fnd; +} + +static struct amp_ctx *get_ctx_a2mp(struct amp_mgr *mgr, u8 ident) +{ + struct amp_ctx *fnd = NULL; + struct amp_ctx *ctx; + + read_lock_bh(&mgr->ctx_list_lock); + list_for_each_entry(ctx, &mgr->ctx_list, list) { + if ((ctx->evt_type & AMP_A2MP_RSP) && + (ctx->rsp_ident == ident)) { + fnd = ctx; + break; + } + } + read_unlock_bh(&mgr->ctx_list_lock); + return fnd; +} + +static struct amp_ctx *get_ctx_hdev(struct hci_dev *hdev, u8 evt_type, + u16 evt_value) +{ + struct amp_mgr *mgr; + struct amp_ctx *fnd = NULL; + + read_lock_bh(&_mgr_list_lock); + list_for_each_entry(mgr, &_mgr_list, list) { + struct amp_ctx *ctx; + read_lock_bh(&mgr->ctx_list_lock); + list_for_each_entry(ctx, &mgr->ctx_list, list) { + struct hci_dev *ctx_hdev; + ctx_hdev = hci_dev_get(A2MP_HCI_ID(ctx->id)); + if ((ctx_hdev == hdev) && (ctx->evt_type & evt_type)) { + switch (evt_type) { + case AMP_HCI_CMD_STATUS: + case AMP_HCI_CMD_CMPLT: + if (ctx->opcode == evt_value) + fnd = ctx; + break; + case AMP_HCI_EVENT: + if (ctx->evt_code == (u8) evt_value) + fnd = ctx; + break; + } + } + if (ctx_hdev) + hci_dev_put(ctx_hdev); + + if (fnd) + break; + } + read_unlock_bh(&mgr->ctx_list_lock); + } + read_unlock_bh(&_mgr_list_lock); + return fnd; +} + +static inline u8 next_ident(struct amp_mgr *mgr) +{ + if (++mgr->next_ident == 0) + mgr->next_ident = 1; + return mgr->next_ident; +} + +static inline void send_a2mp_cmd2(struct amp_mgr *mgr, u8 ident, u8 code, + u16 len, void *data, u16 len2, void *data2) +{ + struct a2mp_cmd_hdr *hdr; + int plen; + u8 *p, *cmd; + + BT_DBG("ident %d code 0x%02x", ident, code); + if (!mgr->a2mp_sock) + return; + plen = sizeof(*hdr) + len + len2; + cmd = kzalloc(plen, GFP_ATOMIC); + if (!cmd) + return; + hdr = (struct a2mp_cmd_hdr *) cmd; + hdr->code = code; + hdr->ident = ident; + hdr->len = cpu_to_le16(len+len2); + p = cmd + sizeof(*hdr); + memcpy(p, data, len); + p += len; + memcpy(p, data2, len2); + send_a2mp(mgr->a2mp_sock, cmd, plen); + kfree(cmd); +} + +static inline void send_a2mp_cmd(struct amp_mgr *mgr, u8 ident, + u8 code, u16 len, void *data) +{ + send_a2mp_cmd2(mgr, ident, code, len, data, 0, NULL); +} + +static inline int command_rej(struct amp_mgr *mgr, struct sk_buff *skb) +{ + struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; + struct a2mp_cmd_rej *rej; + struct amp_ctx *ctx; + + BT_DBG("ident %d code %d", hdr->ident, hdr->code); + rej = (struct a2mp_cmd_rej *) skb_pull(skb, sizeof(*hdr)); + if (skb->len < sizeof(*rej)) + return -EINVAL; + BT_DBG("reason %d", le16_to_cpu(rej->reason)); + ctx = get_ctx_a2mp(mgr, hdr->ident); + if (ctx) + kill_ctx(ctx); + skb_pull(skb, sizeof(*rej)); + return 0; +} + +static int send_a2mp_cl(struct amp_mgr *mgr, u8 ident, u8 code, u16 len, + void *msg) +{ + struct a2mp_cl clist[16]; + struct a2mp_cl *cl; + struct hci_dev *hdev; + int num_ctrls = 1, id; + + cl = clist; + cl->id = 0; + cl->type = 0; + cl->status = 1; + + for (id = 0; id < 16; ++id) { + hdev = hci_dev_get(id); + if (hdev) { + if ((hdev->amp_type != HCI_BREDR) && + test_bit(HCI_UP, &hdev->flags)) { + (cl + num_ctrls)->id = HCI_A2MP_ID(hdev->id); + (cl + num_ctrls)->type = hdev->amp_type; + (cl + num_ctrls)->status = hdev->amp_status; + ++num_ctrls; + } + hci_dev_put(hdev); + } + } + send_a2mp_cmd2(mgr, ident, code, len, msg, + num_ctrls*sizeof(*cl), clist); + + return 0; +} + +static void send_a2mp_change_notify(void) +{ + struct amp_mgr *mgr; + + read_lock_bh(&_mgr_list_lock); + list_for_each_entry(mgr, &_mgr_list, list) { + if (mgr->discovered) + send_a2mp_cl(mgr, next_ident(mgr), + A2MP_CHANGE_NOTIFY, 0, NULL); + } + read_unlock_bh(&_mgr_list_lock); +} + +static inline int discover_req(struct amp_mgr *mgr, struct sk_buff *skb) +{ + struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; + struct a2mp_discover_req *req; + u16 *efm; + struct a2mp_discover_rsp rsp; + + req = (struct a2mp_discover_req *) skb_pull(skb, sizeof(*hdr)); + if (skb->len < sizeof(*req)) + return -EINVAL; + efm = (u16 *) skb_pull(skb, sizeof(*req)); + + BT_DBG("mtu %d efm 0x%4.4x", le16_to_cpu(req->mtu), + le16_to_cpu(req->ext_feat)); + + while (le16_to_cpu(req->ext_feat) & 0x8000) { + if (skb->len < sizeof(*efm)) + return -EINVAL; + req->ext_feat = *efm; + BT_DBG("efm 0x%4.4x", le16_to_cpu(req->ext_feat)); + efm = (u16 *) skb_pull(skb, sizeof(*efm)); + } + + rsp.mtu = cpu_to_le16(L2CAP_A2MP_DEFAULT_MTU); + rsp.ext_feat = 0; + + mgr->discovered = 1; + + return send_a2mp_cl(mgr, hdr->ident, A2MP_DISCOVER_RSP, + sizeof(rsp), &rsp); +} + +static inline int change_notify(struct amp_mgr *mgr, struct sk_buff *skb) +{ + struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; + struct a2mp_cl *cl; + + cl = (struct a2mp_cl *) skb_pull(skb, sizeof(*hdr)); + while (skb->len >= sizeof(*cl)) { + struct amp_ctrl *ctrl; + if (cl->id != 0) { + ctrl = get_create_ctrl(mgr, cl->id); + if (ctrl != NULL) { + ctrl->type = cl->type; + ctrl->status = cl->status; + } + } + cl = (struct a2mp_cl *) skb_pull(skb, sizeof(*cl)); + } + + /* TODO find controllers in manager that were not on received */ + /* controller list and destroy them */ + send_a2mp_cmd(mgr, hdr->ident, A2MP_CHANGE_RSP, 0, NULL); + + return 0; +} + +static inline int getinfo_req(struct amp_mgr *mgr, struct sk_buff *skb) +{ + struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; + u8 *data; + int id; + struct hci_dev *hdev; + struct a2mp_getinfo_rsp rsp; + + data = (u8 *) skb_pull(skb, sizeof(*hdr)); + if (le16_to_cpu(hdr->len) < sizeof(*data)) + return -EINVAL; + if (skb->len < sizeof(*data)) + return -EINVAL; + id = *data; + skb_pull(skb, sizeof(*data)); + rsp.id = id; + rsp.status = 1; + + BT_DBG("id %d", id); + hdev = hci_dev_get(A2MP_HCI_ID(id)); + + if (hdev && hdev->amp_type != HCI_BREDR) { + rsp.status = 0; + rsp.total_bw = cpu_to_le32(hdev->amp_total_bw); + rsp.max_bw = cpu_to_le32(hdev->amp_max_bw); + rsp.min_latency = cpu_to_le32(hdev->amp_min_latency); + rsp.pal_cap = cpu_to_le16(hdev->amp_pal_cap); + rsp.assoc_size = cpu_to_le16(hdev->amp_assoc_size); + } + + send_a2mp_cmd(mgr, hdr->ident, A2MP_GETINFO_RSP, sizeof(rsp), &rsp); + + if (hdev) + hci_dev_put(hdev); + + return 0; +} + +static void create_physical(struct l2cap_conn *conn, struct sock *sk) +{ + struct amp_mgr *mgr; + struct amp_ctx *ctx = NULL; + + BT_DBG("conn %p", conn); + mgr = get_create_amp_mgr(conn, NULL); + if (!mgr) + goto cp_finished; + BT_DBG("mgr %p", mgr); + ctx = create_ctx(AMP_CREATEPHYSLINK, AMP_CPL_INIT); + if (!ctx) + goto cp_finished; + ctx->sk = sk; + sock_hold(sk); + start_ctx(mgr, ctx); + return; + +cp_finished: + l2cap_amp_physical_complete(-ENOMEM, 0, 0, sk); +} + +static void accept_physical(struct l2cap_conn *lcon, u8 id, struct sock *sk) +{ + struct amp_mgr *mgr; + struct hci_dev *hdev; + struct hci_conn *conn; + struct amp_ctx *aplctx = NULL; + u8 remote_id = 0; + int result = -EINVAL; + + BT_DBG("lcon %p", lcon); + mgr = get_create_amp_mgr(lcon, NULL); + if (!mgr) + goto ap_finished; + BT_DBG("mgr %p", mgr); + hdev = hci_dev_get(A2MP_HCI_ID(id)); + if (!hdev) + goto ap_finished; + BT_DBG("hdev %p", hdev); + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, + &mgr->l2cap_conn->hcon->dst); + if (conn) { + BT_DBG("conn %p", hdev); + result = 0; + remote_id = conn->dst_id; + goto ap_finished; + } + aplctx = get_ctx_mgr(mgr, AMP_ACCEPTPHYSLINK); + if (!aplctx) + goto ap_finished; + aplctx->sk = sk; + sock_hold(sk); + return; + +ap_finished: + l2cap_amp_physical_complete(result, id, remote_id, sk); +} + +static int getampassoc_req(struct amp_mgr *mgr, struct sk_buff *skb) +{ + struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; + struct amp_ctx *ctx; + struct a2mp_getampassoc_req *req; + + if (hdr->len < sizeof(*req)) + return -EINVAL; + req = (struct a2mp_getampassoc_req *) skb_pull(skb, sizeof(*hdr)); + skb_pull(skb, sizeof(*req)); + + ctx = create_ctx(AMP_GETAMPASSOC, AMP_GAA_INIT); + if (!ctx) + return -ENOMEM; + ctx->id = req->id; + ctx->d.gaa.req_ident = hdr->ident; + ctx->hdev = hci_dev_get(A2MP_HCI_ID(ctx->id)); + if (ctx->hdev) + ctx->d.gaa.assoc = kmalloc(ctx->hdev->amp_assoc_size, + GFP_ATOMIC); + start_ctx(mgr, ctx); + return 0; +} + +static u8 getampassoc_handler(struct amp_ctx *ctx, u8 evt_type, void *data) +{ + struct sk_buff *skb = (struct sk_buff *) data; + struct hci_cp_read_local_amp_assoc cp; + struct hci_rp_read_local_amp_assoc *rp; + struct a2mp_getampassoc_rsp rsp; + u16 rem_len; + u16 frag_len; + + rsp.status = 1; + if ((evt_type == AMP_KILLED) || (!ctx->hdev) || (!ctx->d.gaa.assoc)) + goto gaa_finished; + + switch (ctx->state) { + case AMP_GAA_INIT: + ctx->state = AMP_GAA_RLAA_COMPLETE; + ctx->evt_type = AMP_HCI_CMD_CMPLT; + ctx->opcode = HCI_OP_READ_LOCAL_AMP_ASSOC; + ctx->d.gaa.len_so_far = 0; + cp.phy_handle = 0; + cp.len_so_far = 0; + cp.max_len = ctx->hdev->amp_assoc_size; + hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(cp), &cp); + break; + + case AMP_GAA_RLAA_COMPLETE: + if (skb->len < 4) + goto gaa_finished; + rp = (struct hci_rp_read_local_amp_assoc *) skb->data; + if (rp->status) + goto gaa_finished; + rem_len = le16_to_cpu(rp->rem_len); + skb_pull(skb, 4); + frag_len = skb->len; + + if (ctx->d.gaa.len_so_far + rem_len <= + ctx->hdev->amp_assoc_size) { + struct hci_cp_read_local_amp_assoc cp; + u8 *assoc = ctx->d.gaa.assoc + ctx->d.gaa.len_so_far; + memcpy(assoc, rp->frag, frag_len); + ctx->d.gaa.len_so_far += rem_len; + rem_len -= frag_len; + if (rem_len == 0) { + rsp.status = 0; + goto gaa_finished; + } + /* more assoc data to read */ + cp.phy_handle = 0; + cp.len_so_far = ctx->d.gaa.len_so_far; + cp.max_len = ctx->hdev->amp_assoc_size; + hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(cp), &cp); + } + break; + + default: + goto gaa_finished; + break; + } + return 0; + +gaa_finished: + rsp.id = ctx->id; + send_a2mp_cmd2(ctx->mgr, ctx->d.gaa.req_ident, A2MP_GETAMPASSOC_RSP, + sizeof(rsp), &rsp, + ctx->d.gaa.len_so_far, ctx->d.gaa.assoc); + kfree(ctx->d.gaa.assoc); + if (ctx->hdev) + hci_dev_put(ctx->hdev); + return 1; +} + +struct hmac_sha256_result { + struct completion completion; + int err; +}; + +static void hmac_sha256_final(struct crypto_async_request *req, int err) +{ + struct hmac_sha256_result *r = req->data; + if (err == -EINPROGRESS) + return; + r->err = err; + complete(&r->completion); +} + +int hmac_sha256(u8 *key, u8 ksize, char *plaintext, u8 psize, + u8 *output, u8 outlen) +{ + int ret = 0; + struct crypto_ahash *tfm; + struct scatterlist sg; + struct ahash_request *req; + struct hmac_sha256_result tresult; + void *hash_buff = NULL; + + unsigned char hash_result[64]; + int i; + + memset(output, 0, outlen); + + init_completion(&tresult.completion); + + tfm = crypto_alloc_ahash("hmac(sha256)", CRYPTO_ALG_TYPE_AHASH, + CRYPTO_ALG_TYPE_AHASH_MASK); + if (IS_ERR(tfm)) { + BT_DBG("crypto_alloc_ahash failed"); + ret = PTR_ERR(tfm); + goto err_tfm; + } + + req = ahash_request_alloc(tfm, GFP_KERNEL); + if (!req) { + BT_DBG("failed to allocate request for hmac(sha256)"); + ret = -ENOMEM; + goto err_req; + } + + ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, + hmac_sha256_final, &tresult); + + hash_buff = kzalloc(psize, GFP_KERNEL); + if (!hash_buff) { + BT_DBG("failed to kzalloc hash_buff"); + ret = -ENOMEM; + goto err_hash_buf; + } + + memset(hash_result, 0, 64); + memcpy(hash_buff, plaintext, psize); + sg_init_one(&sg, hash_buff, psize); + + if (ksize) { + crypto_ahash_clear_flags(tfm, ~0); + ret = crypto_ahash_setkey(tfm, key, ksize); + + if (ret) { + BT_DBG("crypto_ahash_setkey failed"); + goto err_setkey; + } + } + + ahash_request_set_crypt(req, &sg, hash_result, psize); + ret = crypto_ahash_digest(req); + + BT_DBG("ret 0x%x", ret); + + switch (ret) { + case 0: + for (i = 0; i < outlen; i++) + output[i] = hash_result[i]; + break; + case -EINPROGRESS: + case -EBUSY: + ret = wait_for_completion_interruptible(&tresult.completion); + if (!ret && !tresult.err) { + INIT_COMPLETION(tresult.completion); + break; + } else { + BT_DBG("wait_for_completion_interruptible failed"); + if (!ret) + ret = tresult.err; + goto out; + } + default: + goto out; + } + +out: +err_setkey: + kfree(hash_buff); +err_hash_buf: + ahash_request_free(req); +err_req: + crypto_free_ahash(tfm); +err_tfm: + return ret; +} + +static void show_key(u8 *k) +{ + int i = 0; + for (i = 0; i < 32; i += 8) + BT_DBG(" %02x %02x %02x %02x %02x %02x %02x %02x", + *(k+i+0), *(k+i+1), *(k+i+2), *(k+i+3), + *(k+i+4), *(k+i+5), *(k+i+6), *(k+i+7)); +} + +static int physlink_security(struct hci_conn *conn, u8 *data, u8 *len, u8 *type) +{ + u8 bt2_key[32]; + u8 gamp_key[32]; + u8 b802_key[32]; + int result; + + if (!hci_conn_check_link_mode(conn)) + return -EACCES; + + BT_DBG("key_type %d", conn->key_type); + if (conn->key_type < 3) + return -EACCES; + + *type = conn->key_type; + *len = 32; + memcpy(&bt2_key[0], conn->link_key, 16); + memcpy(&bt2_key[16], conn->link_key, 16); + result = hmac_sha256(bt2_key, 32, "gamp", 4, gamp_key, 32); + if (result) + goto ps_finished; + + if (conn->key_type == 3) { + BT_DBG("gamp_key"); + show_key(gamp_key); + memcpy(data, gamp_key, 32); + goto ps_finished; + } + + result = hmac_sha256(gamp_key, 32, "802b", 4, b802_key, 32); + if (result) + goto ps_finished; + + BT_DBG("802b_key"); + show_key(b802_key); + memcpy(data, b802_key, 32); + +ps_finished: + return result; +} + +static u8 amp_next_handle; +static inline u8 physlink_handle(struct hci_dev *hdev) +{ + /* TODO amp_next_handle should be part of hci_dev */ + if (amp_next_handle == 0) + amp_next_handle = 1; + return amp_next_handle++; +} + +/* Start an Accept Physical Link sequence */ +static int createphyslink_req(struct amp_mgr *mgr, struct sk_buff *skb) +{ + struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; + struct amp_ctx *ctx = NULL; + struct a2mp_createphyslink_req *req; + + if (hdr->len < sizeof(*req)) + return -EINVAL; + req = (struct a2mp_createphyslink_req *) skb_pull(skb, sizeof(*hdr)); + skb_pull(skb, sizeof(*req)); + BT_DBG("local_id %d, remote_id %d", req->local_id, req->remote_id); + + /* initialize the context */ + ctx = create_ctx(AMP_ACCEPTPHYSLINK, AMP_APL_INIT); + if (!ctx) + return -ENOMEM; + ctx->d.apl.req_ident = hdr->ident; + ctx->d.apl.remote_id = req->local_id; + ctx->id = req->remote_id; + + /* add the supplied remote assoc to the context */ + ctx->d.apl.remote_assoc = kmalloc(skb->len, GFP_ATOMIC); + if (ctx->d.apl.remote_assoc) + memcpy(ctx->d.apl.remote_assoc, skb->data, skb->len); + ctx->d.apl.len_so_far = 0; + ctx->d.apl.rem_len = skb->len; + skb_pull(skb, skb->len); + ctx->hdev = hci_dev_get(A2MP_HCI_ID(ctx->id)); + start_ctx(mgr, ctx); + return 0; +} + +static u8 acceptphyslink_handler(struct amp_ctx *ctx, u8 evt_type, void *data) +{ + struct sk_buff *skb = data; + struct hci_cp_accept_phys_link acp; + struct hci_cp_write_remote_amp_assoc wcp; + struct hci_rp_write_remote_amp_assoc *wrp; + struct hci_ev_cmd_status *cs = data; + struct hci_ev_phys_link_complete *ev; + struct a2mp_createphyslink_rsp rsp; + struct amp_ctx *cplctx; + struct amp_ctx *aplctx; + u16 frag_len; + struct hci_conn *conn; + int result; + + BT_DBG("state %d", ctx->state); + result = -EINVAL; + rsp.status = 1; /* Invalid Controller ID */ + if (!ctx->hdev || !test_bit(HCI_UP, &ctx->hdev->flags)) + goto apl_finished; + if (evt_type == AMP_KILLED) { + result = -EAGAIN; + rsp.status = 4; /* Disconnect request received */ + goto apl_finished; + } + if (!ctx->d.apl.remote_assoc) { + result = -ENOMEM; + rsp.status = 2; /* Unable to Start */ + goto apl_finished; + } + + switch (ctx->state) { + case AMP_APL_INIT: + BT_DBG("local_id %d, remote_id %d", + ctx->id, ctx->d.apl.remote_id); + conn = hci_conn_hash_lookup_id(ctx->hdev, + &ctx->mgr->l2cap_conn->hcon->dst, + ctx->d.apl.remote_id); + if (conn) { + result = -EEXIST; + rsp.status = 5; /* Already Exists */ + goto apl_finished; + } + + aplctx = get_ctx_type(ctx, AMP_ACCEPTPHYSLINK); + if ((aplctx) && + (aplctx->d.cpl.remote_id == ctx->d.apl.remote_id)) { + BT_DBG("deferred to %p", aplctx); + aplctx->deferred = ctx; + break; + } + + cplctx = get_ctx_type(ctx, AMP_CREATEPHYSLINK); + if ((cplctx) && + (cplctx->d.cpl.remote_id == ctx->d.apl.remote_id)) { + struct hci_conn *bcon = ctx->mgr->l2cap_conn->hcon; + BT_DBG("local %s remote %s", + batostr(&bcon->hdev->bdaddr), + batostr(&bcon->dst)); + if ((cplctx->state < AMP_CPL_PL_COMPLETE) || + (bacmp(&bcon->hdev->bdaddr, &bcon->dst) < 0)) { + BT_DBG("COLLISION LOSER"); + cplctx->deferred = ctx; + cancel_ctx(cplctx); + break; + } else { + BT_DBG("COLLISION WINNER"); + result = -EISCONN; + rsp.status = 3; /* Collision */ + goto apl_finished; + } + } + + result = physlink_security(ctx->mgr->l2cap_conn->hcon, acp.data, + &acp.key_len, &acp.type); + if (result) { + BT_DBG("SECURITY"); + rsp.status = 6; /* Security Violation */ + goto apl_finished; + } + + ctx->d.apl.phy_handle = physlink_handle(ctx->hdev); + ctx->state = AMP_APL_APL_STATUS; + ctx->evt_type = AMP_HCI_CMD_STATUS; + ctx->opcode = HCI_OP_ACCEPT_PHYS_LINK; + acp.phy_handle = ctx->d.apl.phy_handle; + hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(acp), &acp); + break; + + case AMP_APL_APL_STATUS: + if (cs->status != 0) + goto apl_finished; + /* PAL will accept link, send a2mp response */ + rsp.local_id = ctx->id; + rsp.remote_id = ctx->d.apl.remote_id; + rsp.status = 0; + send_a2mp_cmd(ctx->mgr, ctx->d.apl.req_ident, + A2MP_CREATEPHYSLINK_RSP, sizeof(rsp), &rsp); + + /* send the first assoc fragment */ + wcp.phy_handle = ctx->d.apl.phy_handle; + wcp.len_so_far = cpu_to_le16(ctx->d.apl.len_so_far); + wcp.rem_len = cpu_to_le16(ctx->d.apl.rem_len); + frag_len = min_t(u16, 248, ctx->d.apl.rem_len); + memcpy(wcp.frag, ctx->d.apl.remote_assoc, frag_len); + ctx->state = AMP_APL_WRA_COMPLETE; + ctx->evt_type = AMP_HCI_CMD_CMPLT; + ctx->opcode = HCI_OP_WRITE_REMOTE_AMP_ASSOC; + hci_send_cmd(ctx->hdev, ctx->opcode, 5+frag_len, &wcp); + break; + + case AMP_APL_WRA_COMPLETE: + /* received write remote amp assoc command complete event */ + wrp = (struct hci_rp_write_remote_amp_assoc *) skb->data; + if (wrp->status != 0) + goto apl_finished; + if (wrp->phy_handle != ctx->d.apl.phy_handle) + goto apl_finished; + /* update progress */ + frag_len = min_t(u16, 248, ctx->d.apl.rem_len); + ctx->d.apl.len_so_far += frag_len; + ctx->d.apl.rem_len -= frag_len; + if (ctx->d.apl.rem_len > 0) { + u8 *assoc; + /* another assoc fragment to send */ + wcp.phy_handle = ctx->d.apl.phy_handle; + wcp.len_so_far = cpu_to_le16(ctx->d.apl.len_so_far); + wcp.rem_len = cpu_to_le16(ctx->d.apl.rem_len); + frag_len = min_t(u16, 248, ctx->d.apl.rem_len); + assoc = ctx->d.apl.remote_assoc + ctx->d.apl.len_so_far; + memcpy(wcp.frag, assoc, frag_len); + hci_send_cmd(ctx->hdev, ctx->opcode, 5+frag_len, &wcp); + break; + } + /* wait for physical link complete event */ + ctx->state = AMP_APL_PL_COMPLETE; + ctx->evt_type = AMP_HCI_EVENT; + ctx->evt_code = HCI_EV_PHYS_LINK_COMPLETE; + break; + + case AMP_APL_PL_COMPLETE: + /* physical link complete event received */ + if (skb->len < sizeof(*ev)) + goto apl_finished; + ev = (struct hci_ev_phys_link_complete *) skb->data; + if (ev->phy_handle != ctx->d.apl.phy_handle) + break; + if (ev->status != 0) + goto apl_finished; + conn = hci_conn_hash_lookup_handle(ctx->hdev, ev->phy_handle); + if (!conn) + goto apl_finished; + result = 0; + BT_DBG("PL_COMPLETE phy_handle %x", ev->phy_handle); + conn->dst_id = ctx->d.apl.remote_id; + bacpy(&conn->dst, &ctx->mgr->l2cap_conn->hcon->dst); + goto apl_finished; + break; + + default: + goto apl_finished; + break; + } + return 0; + +apl_finished: + if (ctx->sk) + l2cap_amp_physical_complete(result, ctx->id, + ctx->d.apl.remote_id, ctx->sk); + if ((result) && (ctx->state < AMP_APL_PL_COMPLETE)) { + rsp.local_id = ctx->id; + rsp.remote_id = ctx->d.apl.remote_id; + send_a2mp_cmd(ctx->mgr, ctx->d.apl.req_ident, + A2MP_CREATEPHYSLINK_RSP, sizeof(rsp), &rsp); + } + kfree(ctx->d.apl.remote_assoc); + if (ctx->sk) + sock_put(ctx->sk); + if (ctx->hdev) + hci_dev_put(ctx->hdev); + return 1; +} + +static void cancel_cpl_ctx(struct amp_ctx *ctx, u8 reason) +{ + struct hci_cp_disconn_phys_link dcp; + + ctx->state = AMP_CPL_PL_CANCEL; + ctx->evt_type = AMP_HCI_EVENT; + ctx->evt_code = HCI_EV_DISCONN_PHYS_LINK_COMPLETE; + dcp.phy_handle = ctx->d.cpl.phy_handle; + dcp.reason = reason; + hci_send_cmd(ctx->hdev, HCI_OP_DISCONN_PHYS_LINK, sizeof(dcp), &dcp); +} + +static u8 createphyslink_handler(struct amp_ctx *ctx, u8 evt_type, void *data) +{ + struct amp_ctrl *ctrl; + struct sk_buff *skb = data; + struct a2mp_cmd_hdr *hdr; + struct hci_ev_cmd_status *cs = data; + struct amp_ctx *cplctx; + struct a2mp_discover_req dreq; + struct a2mp_discover_rsp *drsp; + u16 *efm; + struct a2mp_getinfo_req greq; + struct a2mp_getinfo_rsp *grsp; + struct a2mp_cl *cl; + struct a2mp_getampassoc_req areq; + struct a2mp_getampassoc_rsp *arsp; + struct hci_cp_create_phys_link cp; + struct hci_cp_write_remote_amp_assoc wcp; + struct hci_rp_write_remote_amp_assoc *wrp; + struct hci_ev_channel_selected *cev; + struct hci_cp_read_local_amp_assoc rcp; + struct hci_rp_read_local_amp_assoc *rrp; + struct a2mp_createphyslink_req creq; + struct a2mp_createphyslink_rsp *crsp; + struct hci_ev_phys_link_complete *pev; + struct hci_ev_disconn_phys_link_complete *dev; + u8 *assoc, *rassoc, *lassoc; + u16 frag_len; + u16 rem_len; + int result = -EAGAIN; + struct hci_conn *conn; + + BT_DBG("state %d", ctx->state); + if (evt_type == AMP_KILLED) + goto cpl_finished; + + if (evt_type == AMP_CANCEL) { + if ((ctx->state < AMP_CPL_CPL_STATUS) || + ((ctx->state == AMP_CPL_PL_COMPLETE) && + !(ctx->evt_type & AMP_HCI_EVENT))) + goto cpl_finished; + + cancel_cpl_ctx(ctx, 0x16); + return 0; + } + + switch (ctx->state) { + case AMP_CPL_INIT: + cplctx = get_ctx_type(ctx, AMP_CREATEPHYSLINK); + if (cplctx) { + BT_DBG("deferred to %p", cplctx); + cplctx->deferred = ctx; + break; + } + ctx->state = AMP_CPL_DISC_RSP; + ctx->evt_type = AMP_A2MP_RSP; + ctx->rsp_ident = next_ident(ctx->mgr); + dreq.mtu = cpu_to_le16(L2CAP_A2MP_DEFAULT_MTU); + dreq.ext_feat = 0; + send_a2mp_cmd(ctx->mgr, ctx->rsp_ident, A2MP_DISCOVER_REQ, + sizeof(dreq), &dreq); + break; + + case AMP_CPL_DISC_RSP: + drsp = (struct a2mp_discover_rsp *) skb_pull(skb, sizeof(*hdr)); + if (skb->len < (sizeof(*drsp))) { + result = -EINVAL; + goto cpl_finished; + } + + efm = (u16 *) skb_pull(skb, sizeof(*drsp)); + BT_DBG("mtu %d efm 0x%4.4x", le16_to_cpu(drsp->mtu), + le16_to_cpu(drsp->ext_feat)); + + while (le16_to_cpu(drsp->ext_feat) & 0x8000) { + if (skb->len < sizeof(*efm)) { + result = -EINVAL; + goto cpl_finished; + } + drsp->ext_feat = *efm; + BT_DBG("efm 0x%4.4x", le16_to_cpu(drsp->ext_feat)); + efm = (u16 *) skb_pull(skb, sizeof(*efm)); + } + cl = (struct a2mp_cl *) efm; + + /* find the first remote and local controller with the + * same type + */ + greq.id = 0; + result = -ENODEV; + while (skb->len >= sizeof(*cl)) { + if ((cl->id != 0) && (greq.id == 0)) { + struct hci_dev *hdev; + hdev = hci_dev_get_type(cl->type); + if (hdev) { + struct hci_conn *conn; + ctx->hdev = hdev; + ctx->id = HCI_A2MP_ID(hdev->id); + ctx->d.cpl.remote_id = cl->id; + conn = hci_conn_hash_lookup_ba(hdev, + ACL_LINK, + &ctx->mgr->l2cap_conn->hcon->dst); + if (conn) { + BT_DBG("PL_COMPLETE exists %x", + (int) conn->handle); + result = 0; + } + ctrl = get_create_ctrl(ctx->mgr, + cl->id); + if (ctrl) { + ctrl->type = cl->type; + ctrl->status = cl->status; + } + greq.id = cl->id; + } + } + cl = (struct a2mp_cl *) skb_pull(skb, sizeof(*cl)); + } + if ((!greq.id) || (!result)) + goto cpl_finished; + ctx->state = AMP_CPL_GETINFO_RSP; + ctx->evt_type = AMP_A2MP_RSP; + ctx->rsp_ident = next_ident(ctx->mgr); + send_a2mp_cmd(ctx->mgr, ctx->rsp_ident, A2MP_GETINFO_REQ, + sizeof(greq), &greq); + break; + + case AMP_CPL_GETINFO_RSP: + if (skb->len < sizeof(*grsp)) + goto cpl_finished; + grsp = (struct a2mp_getinfo_rsp *) skb_pull(skb, sizeof(*hdr)); + if (grsp->status) + goto cpl_finished; + if (grsp->id != ctx->d.cpl.remote_id) + goto cpl_finished; + ctrl = get_ctrl(ctx->mgr, grsp->id); + if (!ctrl) + goto cpl_finished; + ctrl->status = grsp->status; + ctrl->total_bw = le32_to_cpu(grsp->total_bw); + ctrl->max_bw = le32_to_cpu(grsp->max_bw); + ctrl->min_latency = le32_to_cpu(grsp->min_latency); + ctrl->pal_cap = le16_to_cpu(grsp->pal_cap); + ctrl->max_assoc_size = le16_to_cpu(grsp->assoc_size); + skb_pull(skb, sizeof(*grsp)); + + ctx->d.cpl.max_len = ctrl->max_assoc_size; + + /* setup up GAA request */ + areq.id = ctx->d.cpl.remote_id; + + /* advance context state */ + ctx->state = AMP_CPL_GAA_RSP; + ctx->evt_type = AMP_A2MP_RSP; + ctx->rsp_ident = next_ident(ctx->mgr); + send_a2mp_cmd(ctx->mgr, ctx->rsp_ident, A2MP_GETAMPASSOC_REQ, + sizeof(areq), &areq); + break; + + case AMP_CPL_GAA_RSP: + if (skb->len < sizeof(*arsp)) + goto cpl_finished; + hdr = (void *) skb->data; + arsp = (void *) skb_pull(skb, sizeof(*hdr)); + if (arsp->id != ctx->d.cpl.remote_id) + goto cpl_finished; + if (arsp->status != 0) + goto cpl_finished; + + /* store away remote assoc */ + assoc = (u8 *) skb_pull(skb, sizeof(*arsp)); + ctx->d.cpl.len_so_far = 0; + ctx->d.cpl.rem_len = hdr->len - sizeof(*arsp); + rassoc = kmalloc(ctx->d.cpl.rem_len, GFP_ATOMIC); + if (!rassoc) + goto cpl_finished; + memcpy(rassoc, assoc, ctx->d.cpl.rem_len); + ctx->d.cpl.remote_assoc = rassoc; + skb_pull(skb, ctx->d.cpl.rem_len); + + /* set up CPL command */ + ctx->d.cpl.phy_handle = physlink_handle(ctx->hdev); + cp.phy_handle = ctx->d.cpl.phy_handle; + if (physlink_security(ctx->mgr->l2cap_conn->hcon, cp.data, + &cp.key_len, &cp.type)) { + result = -EPERM; + goto cpl_finished; + } + + /* advance context state */ + ctx->state = AMP_CPL_CPL_STATUS; + ctx->evt_type = AMP_HCI_CMD_STATUS; + ctx->opcode = HCI_OP_CREATE_PHYS_LINK; + hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(cp), &cp); + break; + + case AMP_CPL_CPL_STATUS: + /* received create physical link command status */ + if (cs->status != 0) + goto cpl_finished; + /* send the first assoc fragment */ + wcp.phy_handle = ctx->d.cpl.phy_handle; + wcp.len_so_far = ctx->d.cpl.len_so_far; + wcp.rem_len = cpu_to_le16(ctx->d.cpl.rem_len); + frag_len = min_t(u16, 248, ctx->d.cpl.rem_len); + memcpy(wcp.frag, ctx->d.cpl.remote_assoc, frag_len); + ctx->state = AMP_CPL_WRA_COMPLETE; + ctx->evt_type = AMP_HCI_CMD_CMPLT; + ctx->opcode = HCI_OP_WRITE_REMOTE_AMP_ASSOC; + hci_send_cmd(ctx->hdev, ctx->opcode, 5+frag_len, &wcp); + break; + + case AMP_CPL_WRA_COMPLETE: + /* received write remote amp assoc command complete event */ + if (skb->len < sizeof(*wrp)) + goto cpl_finished; + wrp = (struct hci_rp_write_remote_amp_assoc *) skb->data; + if (wrp->status != 0) + goto cpl_finished; + if (wrp->phy_handle != ctx->d.cpl.phy_handle) + goto cpl_finished; + + /* update progress */ + frag_len = min_t(u16, 248, ctx->d.cpl.rem_len); + ctx->d.cpl.len_so_far += frag_len; + ctx->d.cpl.rem_len -= frag_len; + if (ctx->d.cpl.rem_len > 0) { + /* another assoc fragment to send */ + wcp.phy_handle = ctx->d.cpl.phy_handle; + wcp.len_so_far = cpu_to_le16(ctx->d.cpl.len_so_far); + wcp.rem_len = cpu_to_le16(ctx->d.cpl.rem_len); + frag_len = min_t(u16, 248, ctx->d.cpl.rem_len); + memcpy(wcp.frag, + ctx->d.cpl.remote_assoc + ctx->d.cpl.len_so_far, + frag_len); + hci_send_cmd(ctx->hdev, ctx->opcode, 5+frag_len, &wcp); + break; + } + /* now wait for channel selected event */ + ctx->state = AMP_CPL_CHANNEL_SELECT; + ctx->evt_type = AMP_HCI_EVENT; + ctx->evt_code = HCI_EV_CHANNEL_SELECTED; + break; + + case AMP_CPL_CHANNEL_SELECT: + /* received channel selection event */ + if (skb->len < sizeof(*cev)) + goto cpl_finished; + cev = (void *) skb->data; +/* TODO - PK This check is valid but Libra PAL returns 0 for handle during + Create Physical Link collision scenario + if (cev->phy_handle != ctx->d.cpl.phy_handle) + goto cpl_finished; +*/ + + /* request the first local assoc fragment */ + rcp.phy_handle = ctx->d.cpl.phy_handle; + rcp.len_so_far = 0; + rcp.max_len = ctx->d.cpl.max_len; + lassoc = kmalloc(ctx->d.cpl.max_len, GFP_ATOMIC); + if (!lassoc) + goto cpl_finished; + ctx->d.cpl.local_assoc = lassoc; + ctx->d.cpl.len_so_far = 0; + ctx->state = AMP_CPL_RLA_COMPLETE; + ctx->evt_type = AMP_HCI_CMD_CMPLT; + ctx->opcode = HCI_OP_READ_LOCAL_AMP_ASSOC; + hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(rcp), &rcp); + break; + + case AMP_CPL_RLA_COMPLETE: + /* received read local amp assoc command complete event */ + if (skb->len < 4) + goto cpl_finished; + rrp = (struct hci_rp_read_local_amp_assoc *) skb->data; + if (rrp->status) + goto cpl_finished; + if (rrp->phy_handle != ctx->d.cpl.phy_handle) + goto cpl_finished; + rem_len = le16_to_cpu(rrp->rem_len); + skb_pull(skb, 4); + frag_len = skb->len; + + if (ctx->d.cpl.len_so_far + rem_len > ctx->d.cpl.max_len) + goto cpl_finished; + + /* save this fragment in context */ + lassoc = ctx->d.cpl.local_assoc + ctx->d.cpl.len_so_far; + memcpy(lassoc, rrp->frag, frag_len); + ctx->d.cpl.len_so_far += frag_len; + rem_len -= frag_len; + if (rem_len > 0) { + /* request another local assoc fragment */ + rcp.phy_handle = ctx->d.cpl.phy_handle; + rcp.len_so_far = ctx->d.cpl.len_so_far; + rcp.max_len = ctx->d.cpl.max_len; + hci_send_cmd(ctx->hdev, ctx->opcode, sizeof(rcp), &rcp); + } else { + creq.local_id = ctx->id; + creq.remote_id = ctx->d.cpl.remote_id; + /* wait for A2MP rsp AND phys link complete event */ + ctx->state = AMP_CPL_PL_COMPLETE; + ctx->evt_type = AMP_A2MP_RSP | AMP_HCI_EVENT; + ctx->rsp_ident = next_ident(ctx->mgr); + ctx->evt_code = HCI_EV_PHYS_LINK_COMPLETE; + send_a2mp_cmd2(ctx->mgr, ctx->rsp_ident, + A2MP_CREATEPHYSLINK_REQ, sizeof(creq), &creq, + ctx->d.cpl.len_so_far, ctx->d.cpl.local_assoc); + } + break; + + case AMP_CPL_PL_COMPLETE: + if (evt_type == AMP_A2MP_RSP) { + /* create physical link response received */ + ctx->evt_type &= ~AMP_A2MP_RSP; + if (skb->len < sizeof(*crsp)) + goto cpl_finished; + crsp = (void *) skb_pull(skb, sizeof(*hdr)); + if ((crsp->local_id != ctx->d.cpl.remote_id) || + (crsp->remote_id != ctx->id) || + (crsp->status != 0)) { + cancel_cpl_ctx(ctx, 0x13); + break; + } + + /* notify Qualcomm PAL */ + if (ctx->hdev->manufacturer == 0x001d) + hci_send_cmd(ctx->hdev, + hci_opcode_pack(0x3f, 0x00), 0, NULL); + } + if (evt_type == AMP_HCI_EVENT) { + ctx->evt_type &= ~AMP_HCI_EVENT; + /* physical link complete event received */ + if (skb->len < sizeof(*pev)) + goto cpl_finished; + pev = (void *) skb->data; + if (pev->phy_handle != ctx->d.cpl.phy_handle) + break; + if (pev->status != 0) + goto cpl_finished; + } + if (ctx->evt_type) + break; + conn = hci_conn_hash_lookup_handle(ctx->hdev, + ctx->d.cpl.phy_handle); + if (!conn) + goto cpl_finished; + result = 0; + BT_DBG("PL_COMPLETE phy_handle %x", ctx->d.cpl.phy_handle); + bacpy(&conn->dst, &ctx->mgr->l2cap_conn->hcon->dst); + conn->dst_id = ctx->d.cpl.remote_id; + conn->out = 1; + goto cpl_finished; + break; + + case AMP_CPL_PL_CANCEL: + dev = (void *) skb->data; + BT_DBG("PL_COMPLETE cancelled %x", dev->phy_handle); + result = -EISCONN; + goto cpl_finished; + break; + + default: + goto cpl_finished; + break; + } + return 0; + +cpl_finished: + l2cap_amp_physical_complete(result, ctx->id, ctx->d.cpl.remote_id, + ctx->sk); + if (ctx->sk) + sock_put(ctx->sk); + if (ctx->hdev) + hci_dev_put(ctx->hdev); + kfree(ctx->d.cpl.remote_assoc); + kfree(ctx->d.cpl.local_assoc); + return 1; +} + +static int disconnphyslink_req(struct amp_mgr *mgr, struct sk_buff *skb) +{ + struct a2mp_cmd_hdr *hdr = (void *) skb->data; + struct a2mp_disconnphyslink_req *req; + struct a2mp_disconnphyslink_rsp rsp; + struct hci_dev *hdev; + struct hci_conn *conn; + struct amp_ctx *aplctx; + + BT_DBG("mgr %p skb %p", mgr, skb); + if (hdr->len < sizeof(*req)) + return -EINVAL; + req = (void *) skb_pull(skb, sizeof(*hdr)); + skb_pull(skb, sizeof(*req)); + + rsp.local_id = req->remote_id; + rsp.remote_id = req->local_id; + rsp.status = 0; + BT_DBG("local_id %d remote_id %d", + (int) rsp.local_id, (int) rsp.remote_id); + hdev = hci_dev_get(A2MP_HCI_ID(rsp.local_id)); + if (!hdev) { + rsp.status = 1; /* Invalid Controller ID */ + goto dpl_finished; + } + BT_DBG("hdev %p", hdev); + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, + &mgr->l2cap_conn->hcon->dst); + if (!conn) { + aplctx = get_ctx_mgr(mgr, AMP_ACCEPTPHYSLINK); + if (aplctx) { + kill_ctx(aplctx); + rsp.status = 0; + goto dpl_finished; + } + rsp.status = 2; /* No Physical Link exists */ + goto dpl_finished; + } + BT_DBG("conn %p", conn); + hci_disconnect(conn, 0x13); + +dpl_finished: + send_a2mp_cmd(mgr, hdr->ident, + A2MP_DISCONNPHYSLINK_RSP, sizeof(rsp), &rsp); + if (hdev) + hci_dev_put(hdev); + return 0; +} + +static int execute_ctx(struct amp_ctx *ctx, u8 evt_type, void *data) +{ + struct amp_mgr *mgr = ctx->mgr; + u8 finished = 0; + + if (!mgr->connected) + return 0; + + switch (ctx->type) { + case AMP_GETAMPASSOC: + finished = getampassoc_handler(ctx, evt_type, data); + break; + case AMP_CREATEPHYSLINK: + finished = createphyslink_handler(ctx, evt_type, data); + break; + case AMP_ACCEPTPHYSLINK: + finished = acceptphyslink_handler(ctx, evt_type, data); + break; + } + + if (!finished) + mod_timer(&(ctx->timer), jiffies + + msecs_to_jiffies(A2MP_RSP_TIMEOUT)); + else + destroy_ctx(ctx); + return finished; +} + +static int cancel_ctx(struct amp_ctx *ctx) +{ + return execute_ctx(ctx, AMP_CANCEL, 0); +} + +static int kill_ctx(struct amp_ctx *ctx) +{ + return execute_ctx(ctx, AMP_KILLED, 0); +} + +static void ctx_timeout_worker(struct work_struct *w) +{ + struct amp_work_ctx_timeout *work = (struct amp_work_ctx_timeout *) w; + struct amp_ctx *ctx = work->ctx; + kill_ctx(ctx); + kfree(work); +} + +static void ctx_timeout(unsigned long data) +{ + struct amp_ctx *ctx = (struct amp_ctx *) data; + struct amp_work_ctx_timeout *work; + + BT_DBG("ctx %p", ctx); + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, ctx_timeout_worker); + work->ctx = ctx; + if (queue_work(amp_workqueue, (struct work_struct *) work) == 0) + kfree(work); + } +} + +static void launch_ctx(struct amp_mgr *mgr) +{ + struct amp_ctx *ctx = NULL; + + BT_DBG("mgr %p", mgr); + read_lock_bh(&mgr->ctx_list_lock); + if (!list_empty(&mgr->ctx_list)) + ctx = list_first_entry(&mgr->ctx_list, struct amp_ctx, list); + read_unlock_bh(&mgr->ctx_list_lock); + BT_DBG("ctx %p", ctx); + if (ctx) + execute_ctx(ctx, AMP_INIT, NULL); +} + +static inline int a2mp_rsp(struct amp_mgr *mgr, struct sk_buff *skb) +{ + struct amp_ctx *ctx; + struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; + u16 hdr_len = le16_to_cpu(hdr->len); + + /* find context waiting for A2MP rsp with this rsp's identifier */ + BT_DBG("ident %d code %d", hdr->ident, hdr->code); + ctx = get_ctx_a2mp(mgr, hdr->ident); + if (ctx) { + execute_ctx(ctx, AMP_A2MP_RSP, skb); + } else { + BT_DBG("context not found"); + skb_pull(skb, sizeof(*hdr)); + if (hdr_len > skb->len) + hdr_len = skb->len; + skb_pull(skb, hdr_len); + } + return 0; +} + +/* L2CAP-A2MP interface */ + +void a2mp_receive(struct sock *sk, struct sk_buff *skb) +{ + struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; + int len; + int err = 0; + struct amp_mgr *mgr; + + mgr = get_amp_mgr_sk(sk); + if (!mgr) + goto a2mp_finished; + + len = skb->len; + while (len >= sizeof(*hdr)) { + struct a2mp_cmd_hdr *hdr = (struct a2mp_cmd_hdr *) skb->data; + u16 clen = le16_to_cpu(hdr->len); + + BT_DBG("code 0x%02x id %d len %d", hdr->code, hdr->ident, clen); + if (clen > len || !hdr->ident) { + err = -EINVAL; + break; + } + switch (hdr->code) { + case A2MP_COMMAND_REJ: + command_rej(mgr, skb); + break; + case A2MP_DISCOVER_REQ: + err = discover_req(mgr, skb); + break; + case A2MP_CHANGE_NOTIFY: + err = change_notify(mgr, skb); + break; + case A2MP_GETINFO_REQ: + err = getinfo_req(mgr, skb); + break; + case A2MP_GETAMPASSOC_REQ: + err = getampassoc_req(mgr, skb); + break; + case A2MP_CREATEPHYSLINK_REQ: + err = createphyslink_req(mgr, skb); + break; + case A2MP_DISCONNPHYSLINK_REQ: + err = disconnphyslink_req(mgr, skb); + break; + case A2MP_CHANGE_RSP: + case A2MP_DISCOVER_RSP: + case A2MP_GETINFO_RSP: + case A2MP_GETAMPASSOC_RSP: + case A2MP_CREATEPHYSLINK_RSP: + case A2MP_DISCONNPHYSLINK_RSP: + err = a2mp_rsp(mgr, skb); + break; + default: + BT_ERR("Unknown A2MP signaling command 0x%2.2x", + hdr->code); + skb_pull(skb, sizeof(*hdr)); + err = -EINVAL; + break; + } + len = skb->len; + } + +a2mp_finished: + if (err && mgr) { + struct a2mp_cmd_rej rej; + rej.reason = cpu_to_le16(0); + send_a2mp_cmd(mgr, hdr->ident, A2MP_COMMAND_REJ, + sizeof(rej), &rej); + } +} + +/* L2CAP-A2MP interface */ + +static int send_a2mp(struct socket *sock, u8 *data, int len) +{ + struct kvec iv = { data, len }; + struct msghdr msg; + + memset(&msg, 0, sizeof(msg)); + + return kernel_sendmsg(sock, &msg, &iv, 1, len); +} + +static void data_ready_worker(struct work_struct *w) +{ + struct amp_work_data_ready *work = (struct amp_work_data_ready *) w; + struct sock *sk = work->sk; + struct sk_buff *skb; + + /* skb_dequeue() is thread-safe */ + while ((skb = skb_dequeue(&sk->sk_receive_queue))) { + a2mp_receive(sk, skb); + kfree_skb(skb); + } + sock_put(work->sk); + kfree(work); +} + +static void data_ready(struct sock *sk, int bytes) +{ + struct amp_work_data_ready *work; + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, data_ready_worker); + sock_hold(sk); + work->sk = sk; + work->bytes = bytes; + if (!queue_work(amp_workqueue, (struct work_struct *) work)) { + kfree(work); + sock_put(sk); + } + } +} + +static void state_change_worker(struct work_struct *w) +{ + struct amp_work_state_change *work = (struct amp_work_state_change *) w; + struct amp_mgr *mgr; + switch (work->sk->sk_state) { + case BT_CONNECTED: + /* socket is up */ + BT_DBG("CONNECTED"); + mgr = get_amp_mgr_sk(work->sk); + if (mgr) { + mgr->connected = 1; + if (mgr->skb) { + l2cap_recv_deferred_frame(work->sk, mgr->skb); + mgr->skb = NULL; + } + launch_ctx(mgr); + } + break; + + case BT_CLOSED: + /* connection is gone */ + BT_DBG("CLOSED"); + mgr = get_amp_mgr_sk(work->sk); + if (mgr) { + if (!sock_flag(work->sk, SOCK_DEAD)) + sock_release(mgr->a2mp_sock); + mgr->a2mp_sock = NULL; + remove_amp_mgr(mgr); + } + break; + + default: + /* something else happened */ + break; + } + sock_put(work->sk); + kfree(work); +} + +static void state_change(struct sock *sk) +{ + struct amp_work_state_change *work; + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, state_change_worker); + sock_hold(sk); + work->sk = sk; + if (!queue_work(amp_workqueue, (struct work_struct *) work)) { + kfree(work); + sock_put(sk); + } + } +} + +static struct socket *open_fixed_channel(bdaddr_t *src, bdaddr_t *dst) +{ + int err; + struct socket *sock; + struct sockaddr_l2 addr; + struct sock *sk; + struct l2cap_options opts = {L2CAP_A2MP_DEFAULT_MTU, + L2CAP_A2MP_DEFAULT_MTU, L2CAP_DEFAULT_FLUSH_TO, + L2CAP_MODE_ERTM, 1, 0xFF, 1}; + + + err = sock_create_kern(PF_BLUETOOTH, SOCK_SEQPACKET, + BTPROTO_L2CAP, &sock); + + if (err) { + BT_ERR("sock_create_kern failed %d", err); + return NULL; + } + + sk = sock->sk; + sk->sk_data_ready = data_ready; + sk->sk_state_change = state_change; + + memset(&addr, 0, sizeof(addr)); + bacpy(&addr.l2_bdaddr, src); + addr.l2_family = AF_BLUETOOTH; + addr.l2_cid = L2CAP_CID_A2MP; + err = kernel_bind(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err) { + BT_ERR("kernel_bind failed %d", err); + sock_release(sock); + return NULL; + } + + l2cap_fixed_channel_config(sk, &opts); + + memset(&addr, 0, sizeof(addr)); + bacpy(&addr.l2_bdaddr, dst); + addr.l2_family = AF_BLUETOOTH; + addr.l2_cid = L2CAP_CID_A2MP; + err = kernel_connect(sock, (struct sockaddr *) &addr, sizeof(addr), + O_NONBLOCK); + if ((err == 0) || (err == -EINPROGRESS)) + return sock; + else { + BT_ERR("kernel_connect failed %d", err); + sock_release(sock); + return NULL; + } +} + +static void conn_ind_worker(struct work_struct *w) +{ + struct amp_work_conn_ind *work = (struct amp_work_conn_ind *) w; + struct l2cap_conn *conn = work->conn; + struct sk_buff *skb = work->skb; + struct amp_mgr *mgr; + + mgr = get_create_amp_mgr(conn, skb); + BT_DBG("mgr %p", mgr); + kfree(work); +} + +static void create_physical_worker(struct work_struct *w) +{ + struct amp_work_create_physical *work = + (struct amp_work_create_physical *) w; + + create_physical(work->conn, work->sk); + sock_put(work->sk); + kfree(work); +} + +static void accept_physical_worker(struct work_struct *w) +{ + struct amp_work_accept_physical *work = + (struct amp_work_accept_physical *) w; + + accept_physical(work->conn, work->id, work->sk); + sock_put(work->sk); + kfree(work); +} + +/* L2CAP Fixed Channel interface */ + +void amp_conn_ind(struct l2cap_conn *conn, struct sk_buff *skb) +{ + struct amp_work_conn_ind *work; + BT_DBG("conn %p, skb %p", conn, skb); + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, conn_ind_worker); + work->conn = conn; + work->skb = skb; + if (queue_work(amp_workqueue, (struct work_struct *) work) == 0) + kfree(work); + } +} + +/* L2CAP Physical Link interface */ + +void amp_create_physical(struct l2cap_conn *conn, struct sock *sk) +{ + struct amp_work_create_physical *work; + BT_DBG("conn %p", conn); + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, create_physical_worker); + work->conn = conn; + work->sk = sk; + sock_hold(sk); + if (!queue_work(amp_workqueue, (struct work_struct *) work)) { + sock_put(sk); + kfree(work); + } + } +} + +void amp_accept_physical(struct l2cap_conn *conn, u8 id, struct sock *sk) +{ + struct amp_work_accept_physical *work; + BT_DBG("conn %p", conn); + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, accept_physical_worker); + work->conn = conn; + work->sk = sk; + work->id = id; + sock_hold(sk); + if (!queue_work(amp_workqueue, (struct work_struct *) work)) { + sock_put(sk); + kfree(work); + } + } +} + +/* HCI interface */ + +static void amp_cmd_cmplt_worker(struct work_struct *w) +{ + struct amp_work_cmd_cmplt *work = (struct amp_work_cmd_cmplt *) w; + struct hci_dev *hdev = work->hdev; + u16 opcode = work->opcode; + struct sk_buff *skb = work->skb; + struct amp_ctx *ctx; + + ctx = get_ctx_hdev(hdev, AMP_HCI_CMD_CMPLT, opcode); + if (ctx) + execute_ctx(ctx, AMP_HCI_CMD_CMPLT, skb); + kfree_skb(skb); + kfree(w); +} + +static void amp_cmd_cmplt_evt(struct hci_dev *hdev, u16 opcode, + struct sk_buff *skb) +{ + struct amp_work_cmd_cmplt *work; + struct sk_buff *skbc; + BT_DBG("hdev %p opcode 0x%x skb %p len %d", + hdev, opcode, skb, skb->len); + skbc = skb_clone(skb, GFP_ATOMIC); + if (!skbc) + return; + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, amp_cmd_cmplt_worker); + work->hdev = hdev; + work->opcode = opcode; + work->skb = skbc; + if (queue_work(amp_workqueue, (struct work_struct *) work) == 0) + kfree(work); + } +} + +static void amp_cmd_status_worker(struct work_struct *w) +{ + struct amp_work_cmd_status *work = (struct amp_work_cmd_status *) w; + struct hci_dev *hdev = work->hdev; + u16 opcode = work->opcode; + u8 status = work->status; + struct amp_ctx *ctx; + + ctx = get_ctx_hdev(hdev, AMP_HCI_CMD_STATUS, opcode); + if (ctx) + execute_ctx(ctx, AMP_HCI_CMD_STATUS, &status); + kfree(w); +} + +static void amp_cmd_status_evt(struct hci_dev *hdev, u16 opcode, u8 status) +{ + struct amp_work_cmd_status *work; + BT_DBG("hdev %p opcode 0x%x status %d", hdev, opcode, status); + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, amp_cmd_status_worker); + work->hdev = hdev; + work->opcode = opcode; + work->status = status; + if (queue_work(amp_workqueue, (struct work_struct *) work) == 0) + kfree(work); + } +} + +static void amp_event_worker(struct work_struct *w) +{ + struct amp_work_event *work = (struct amp_work_event *) w; + struct hci_dev *hdev = work->hdev; + u8 event = work->event; + struct sk_buff *skb = work->skb; + struct amp_ctx *ctx; + + if (event == HCI_EV_AMP_STATUS_CHANGE) { + struct hci_ev_amp_status_change *ev; + if (skb->len < sizeof(*ev)) + goto amp_event_finished; + ev = (void *) skb->data; + if (ev->status != 0) + goto amp_event_finished; + if (ev->amp_status == hdev->amp_status) + goto amp_event_finished; + hdev->amp_status = ev->amp_status; + send_a2mp_change_notify(); + goto amp_event_finished; + } + ctx = get_ctx_hdev(hdev, AMP_HCI_EVENT, (u16) event); + if (ctx) + execute_ctx(ctx, AMP_HCI_EVENT, skb); + +amp_event_finished: + kfree_skb(skb); + kfree(w); +} + +static void amp_evt(struct hci_dev *hdev, u8 event, struct sk_buff *skb) +{ + struct amp_work_event *work; + struct sk_buff *skbc; + BT_DBG("hdev %p event 0x%x skb %p", hdev, event, skb); + skbc = skb_clone(skb, GFP_ATOMIC); + if (!skbc) + return; + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, amp_event_worker); + work->hdev = hdev; + work->event = event; + work->skb = skbc; + if (queue_work(amp_workqueue, (struct work_struct *) work) == 0) + kfree(work); + } +} + +static void amp_dev_event_worker(struct work_struct *w) +{ + send_a2mp_change_notify(); + kfree(w); +} + +static int amp_dev_event(struct notifier_block *this, unsigned long event, + void *ptr) +{ + struct hci_dev *hdev = (struct hci_dev *) ptr; + struct amp_work_event *work; + + if (hdev->amp_type == HCI_BREDR) + return NOTIFY_DONE; + + switch (event) { + case HCI_DEV_UNREG: + case HCI_DEV_REG: + case HCI_DEV_UP: + case HCI_DEV_DOWN: + BT_DBG("hdev %p event %ld", hdev, event); + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK((struct work_struct *) work, + amp_dev_event_worker); + if (queue_work(amp_workqueue, + (struct work_struct *) work) == 0) + kfree(work); + } + } + return NOTIFY_DONE; +} + + +/* L2CAP module init continued */ + +static struct notifier_block amp_notifier = { + .notifier_call = amp_dev_event +}; + +static struct amp_mgr_cb hci_amp = { + .amp_cmd_complete_event = amp_cmd_cmplt_evt, + .amp_cmd_status_event = amp_cmd_status_evt, + .amp_event = amp_evt +}; + +int amp_init(void) +{ + hci_register_amp(&hci_amp); + hci_register_notifier(&_notifier); + amp_next_handle = 1; + amp_workqueue = create_singlethread_workqueue("a2mp"); + if (!amp_workqueue) + return -EPERM; + return 0; +} + +void amp_exit(void) +{ + hci_unregister_amp(&hci_amp); + hci_unregister_notifier(&_notifier); + flush_workqueue(amp_workqueue); + destroy_workqueue(amp_workqueue); +} diff --git a/net/bluetooth/bnep/bnep.h b/net/bluetooth/bnep/bnep.h index e7ee5314f39..8e6c06158f8 100644 --- a/net/bluetooth/bnep/bnep.h +++ b/net/bluetooth/bnep/bnep.h @@ -155,7 +155,6 @@ struct bnep_session { unsigned int role; unsigned long state; unsigned long flags; - atomic_t terminate; struct task_struct *task; struct ethhdr eh; diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c index d9edfe8bf9d..dfadb65012d 100644 --- a/net/bluetooth/bnep/core.c +++ b/net/bluetooth/bnep/core.c @@ -484,15 +484,16 @@ static int bnep_session(void *arg) init_waitqueue_entry(&wait, current); add_wait_queue(sk_sleep(sk), &wait); - while (1) { + while (!kthread_should_stop()) { set_current_state(TASK_INTERRUPTIBLE); - if (atomic_read(&s->terminate)) - break; /* RX */ while ((skb = skb_dequeue(&sk->sk_receive_queue))) { skb_orphan(skb); - bnep_rx_frame(s, skb); + if (!skb_linearize(skb)) + bnep_rx_frame(s, skb); + else + kfree_skb(skb); } if (sk->sk_state != BT_CONNECTED) @@ -506,7 +507,7 @@ static int bnep_session(void *arg) schedule(); } - __set_current_state(TASK_RUNNING); + set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); /* Cleanup session */ @@ -642,10 +643,9 @@ int bnep_del_connection(struct bnep_conndel_req *req) down_read(&bnep_session_sem); s = __bnep_get_session(req->dst); - if (s) { - atomic_inc(&s->terminate); - wake_up_process(s->task); - } else + if (s) + kthread_stop(s->task); + else err = -ENOENT; up_read(&bnep_session_sem); diff --git a/net/bluetooth/cmtp/capi.c b/net/bluetooth/cmtp/capi.c index 040f67b1297..744233cba24 100644 --- a/net/bluetooth/cmtp/capi.c +++ b/net/bluetooth/cmtp/capi.c @@ -326,7 +326,7 @@ void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb) { struct capi_ctr *ctrl = &session->ctrl; struct cmtp_application *application; - __u16 appl; + __u16 cmd, appl; __u32 contr; BT_DBG("session %p skb %p len %d", session, skb, skb->len); @@ -344,6 +344,7 @@ void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb) return; } + cmd = CAPICMD(CAPIMSG_COMMAND(skb->data), CAPIMSG_SUBCOMMAND(skb->data)); appl = CAPIMSG_APPID(skb->data); contr = CAPIMSG_CONTROL(skb->data); diff --git a/net/bluetooth/cmtp/core.c b/net/bluetooth/cmtp/core.c index c5b11af908b..bff02adbc90 100644 --- a/net/bluetooth/cmtp/core.c +++ b/net/bluetooth/cmtp/core.c @@ -300,7 +300,10 @@ static int cmtp_session(void *arg) while ((skb = skb_dequeue(&sk->sk_receive_queue))) { skb_orphan(skb); - cmtp_recv_frame(session, skb); + if (!skb_linearize(skb)) + cmtp_recv_frame(session, skb); + else + kfree_skb(skb); } cmtp_process_transmit(session); @@ -346,8 +349,7 @@ int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock) bacpy(&session->bdaddr, &bt_sk(sock->sk)->dst); - session->mtu = min_t(uint, l2cap_pi(sock->sk)->chan->omtu, - l2cap_pi(sock->sk)->chan->imtu); + session->mtu = min_t(uint, l2cap_pi(sock->sk)->omtu, l2cap_pi(sock->sk)->imtu); BT_DBG("mtu %d", session->mtu); diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 33c4e0cd83b..0ecb942c6f7 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1,6 +1,6 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (c) 2000-2001, 2010, Code Aurora Forum. All rights reserved. + Copyright (c) 2000-2001, 2010-2011 Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -132,15 +132,22 @@ static void hci_acl_connect_cancel(struct hci_conn *conn) void hci_acl_disconn(struct hci_conn *conn, __u8 reason) { - struct hci_cp_disconnect cp; - BT_DBG("%p", conn); conn->state = BT_DISCONN; - cp.handle = cpu_to_le16(conn->handle); - cp.reason = reason; - hci_send_cmd(conn->hdev, HCI_OP_DISCONNECT, sizeof(cp), &cp); + if (conn->hdev->dev_type == HCI_BREDR) { + struct hci_cp_disconnect cp; + cp.handle = cpu_to_le16(conn->handle); + cp.reason = reason; + hci_send_cmd(conn->hdev, HCI_OP_DISCONNECT, sizeof(cp), &cp); + } else { + struct hci_cp_disconn_phys_link cp; + cp.phy_handle = (u8) conn->handle; + cp.reason = reason; + hci_send_cmd(conn->hdev, HCI_OP_DISCONN_PHYS_LINK, + sizeof(cp), &cp); + } } void hci_add_sco(struct hci_conn *conn, __u16 handle) @@ -178,9 +185,9 @@ void hci_setup_sync(struct hci_conn *conn, __u16 handle) cp.tx_bandwidth = cpu_to_le32(0x00001f40); cp.rx_bandwidth = cpu_to_le32(0x00001f40); - cp.max_latency = cpu_to_le16(0xffff); + cp.max_latency = cpu_to_le16(0x000A); cp.voice_setting = cpu_to_le16(hdev->voice_setting); - cp.retrans_effort = 0xff; + cp.retrans_effort = 0x01; hci_send_cmd(hdev, HCI_OP_SETUP_SYNC_CONN, sizeof(cp), &cp); } @@ -320,19 +327,6 @@ static void hci_conn_idle(unsigned long arg) hci_conn_enter_sniff_mode(conn); } -static void hci_conn_auto_accept(unsigned long arg) -{ - struct hci_conn *conn = (void *) arg; - struct hci_dev *hdev = conn->hdev; - - hci_dev_lock(hdev); - - hci_send_cmd(hdev, HCI_OP_USER_CONFIRM_REPLY, sizeof(conn->dst), - &conn->dst); - - hci_dev_unlock(hdev); -} - struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, __u16 pkt_type, bdaddr_t *dst) { @@ -352,7 +346,6 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, conn->auth_type = HCI_AT_GENERAL_BONDING; conn->io_capability = hdev->io_capability; conn->remote_auth = 0xff; - conn->key_type = 0xff; conn->power_save = 1; conn->disc_timeout = HCI_DISCONN_TIMEOUT; @@ -385,8 +378,6 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, setup_timer(&conn->disc_timer, hci_conn_timeout, (unsigned long)conn); setup_timer(&conn->idle_timer, hci_conn_idle, (unsigned long)conn); - setup_timer(&conn->auto_accept_timer, hci_conn_auto_accept, - (unsigned long) conn); atomic_set(&conn->refcnt, 0); @@ -407,6 +398,18 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, return conn; } +struct hci_conn *hci_le_conn_add(struct hci_dev *hdev, bdaddr_t *dst, + __u8 addr_type) +{ + struct hci_conn *conn = hci_conn_add(hdev, LE_LINK, 0, dst); + if (!conn) + return NULL; + + conn->dst_type = addr_type; + + return conn; +} + int hci_conn_del(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; @@ -417,8 +420,6 @@ int hci_conn_del(struct hci_conn *conn) del_timer(&conn->disc_timer); - del_timer(&conn->auto_accept_timer); - if (conn->type == ACL_LINK) { struct hci_conn *sco = conn->link; if (sco) @@ -453,12 +454,62 @@ int hci_conn_del(struct hci_conn *conn) hci_dev_put(hdev); - if (conn->handle == 0) - kfree(conn); + return 0; +} + +struct hci_chan *hci_chan_add(struct hci_dev *hdev) +{ + struct hci_chan *chan; + + BT_DBG("%s", hdev->name); + + chan = kzalloc(sizeof(struct hci_chan), GFP_ATOMIC); + if (!chan) + return NULL; + + atomic_set(&chan->refcnt, 0); + + hci_dev_hold(hdev); + + chan->hdev = hdev; + + list_add(&chan->list, &hdev->chan_list.list); + + return chan; +} + +int hci_chan_del(struct hci_chan *chan) +{ + BT_DBG("%s chan %p", chan->hdev->name, chan); + + list_del(&chan->list); + + hci_conn_put(chan->conn); + hci_dev_put(chan->hdev); + + kfree(chan); return 0; } +void hci_chan_put(struct hci_chan *chan) +{ + struct hci_cp_disconn_logical_link cp; + + BT_DBG("chan %p refcnt %d", chan, atomic_read(&chan->refcnt)); + if (!atomic_dec_and_test(&chan->refcnt)) + return; + + BT_DBG("chan->conn->state %d", chan->conn->state); + if (chan->conn->state == BT_CONNECTED) { + cp.log_handle = cpu_to_le16(chan->ll_handle); + hci_send_cmd(chan->conn->hdev, HCI_OP_DISCONN_LOGICAL_LINK, + sizeof(cp), &cp); + } else + hci_chan_del(chan); +} +EXPORT_SYMBOL(hci_chan_put); + struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src) { int use_src = bacmp(src, BDADDR_ANY); @@ -472,6 +523,8 @@ struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src) list_for_each(p, &hci_dev_list) { struct hci_dev *d = list_entry(p, struct hci_dev, list); + if (d->dev_type != HCI_BREDR) + continue; if (!test_bit(HCI_UP, &d->flags) || test_bit(HCI_RAW, &d->flags)) continue; @@ -499,6 +552,58 @@ struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src) } EXPORT_SYMBOL(hci_get_route); +struct hci_dev *hci_dev_get_type(u8 amp_type) +{ + struct hci_dev *hdev = NULL; + struct hci_dev *d; + + BT_DBG("amp_type %d", amp_type); + + read_lock_bh(&hci_dev_list_lock); + + list_for_each_entry(d, &hci_dev_list, list) { + if ((d->amp_type == amp_type) && test_bit(HCI_UP, &d->flags)) { + hdev = d; + break; + } + } + + if (hdev) + hdev = hci_dev_hold(hdev); + + read_unlock_bh(&hci_dev_list_lock); + return hdev; +} +EXPORT_SYMBOL(hci_dev_get_type); + +struct hci_dev *hci_dev_get_amp(bdaddr_t *dst) +{ + struct hci_dev *d; + struct hci_dev *hdev = NULL; + + BT_DBG("%s dst %s", hdev->name, batostr(dst)); + + read_lock_bh(&hci_dev_list_lock); + + list_for_each_entry(d, &hci_dev_list, list) { + struct hci_conn *conn; + if (d->dev_type == HCI_BREDR) + continue; + conn = hci_conn_hash_lookup_ba(d, ACL_LINK, dst); + if (conn) { + hdev = d; + break; + } + } + + if (hdev) + hdev = hci_dev_hold(hdev); + + read_unlock_bh(&hci_dev_list_lock); + return hdev; +} +EXPORT_SYMBOL(hci_dev_get_amp); + /* Create SCO, ACL or LE connection. * Device _must_ be locked */ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, @@ -522,12 +627,10 @@ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, if (!entry) return ERR_PTR(-EHOSTUNREACH); - le = hci_conn_add(hdev, LE_LINK, 0, dst); + le = hci_le_conn_add(hdev, dst, entry->bdaddr_type); if (!le) return ERR_PTR(-ENOMEM); - le->dst_type = entry->bdaddr_type; - hci_le_connect(le); hci_conn_hold(le); @@ -571,7 +674,7 @@ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, if (acl->state == BT_CONNECTED && (sco->state == BT_OPEN || sco->state == BT_CLOSED)) { acl->power_save = 1; - hci_conn_enter_active_mode(acl, BT_POWER_FORCE_ACTIVE_ON); + hci_conn_enter_active_mode(acl, 1); if (test_bit(HCI_CONN_MODE_CHANGE_PEND, &acl->pend)) { /* defer SCO setup until mode change completed */ @@ -586,6 +689,36 @@ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, } EXPORT_SYMBOL(hci_connect); +void hci_disconnect(struct hci_conn *conn, __u8 reason) +{ + BT_DBG("conn %p", conn); + + hci_proto_disconn_cfm(conn, reason); +} +EXPORT_SYMBOL(hci_disconnect); + +void hci_disconnect_amp(struct hci_conn *conn, __u8 reason) +{ + struct hci_dev *hdev = NULL; + + BT_DBG("conn %p", conn); + + read_lock_bh(&hci_dev_list_lock); + + list_for_each_entry(hdev, &hci_dev_list, list) { + struct hci_conn *c; + if (hdev == conn->hdev) + continue; + if (hdev->amp_type == HCI_BREDR) + continue; + c = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &conn->dst); + if (c) + hci_disconnect(c, reason); + } + + read_unlock_bh(&hci_dev_list_lock); +} + /* Check link security requirement */ int hci_conn_check_link_mode(struct hci_conn *conn) { @@ -619,105 +752,44 @@ static int hci_conn_auth(struct hci_conn *conn, __u8 sec_level, __u8 auth_type) if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) { struct hci_cp_auth_requested cp; - - /* encrypt must be pending if auth is also pending */ - set_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend); - cp.handle = cpu_to_le16(conn->handle); hci_send_cmd(conn->hdev, HCI_OP_AUTH_REQUESTED, sizeof(cp), &cp); - if (conn->key_type != 0xff) - set_bit(HCI_CONN_REAUTH_PEND, &conn->pend); } return 0; } -/* Encrypt the the link */ -static void hci_conn_encrypt(struct hci_conn *conn) -{ - BT_DBG("conn %p", conn); - - if (!test_and_set_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) { - struct hci_cp_set_conn_encrypt cp; - cp.handle = cpu_to_le16(conn->handle); - cp.encrypt = 0x01; - hci_send_cmd(conn->hdev, HCI_OP_SET_CONN_ENCRYPT, sizeof(cp), - &cp); - } -} - /* Enable security */ int hci_conn_security(struct hci_conn *conn, __u8 sec_level, __u8 auth_type) { BT_DBG("conn %p", conn); - /* For sdp we don't need the link key. */ if (sec_level == BT_SECURITY_SDP) return 1; - /* For non 2.1 devices and low security level we don't need the link - key. */ if (sec_level == BT_SECURITY_LOW && (!conn->ssp_mode || !conn->hdev->ssp_mode)) return 1; - /* For other security levels we need the link key. */ - if (!(conn->link_mode & HCI_LM_AUTH)) - goto auth; - - /* An authenticated combination key has sufficient security for any - security level. */ - if (conn->key_type == HCI_LK_AUTH_COMBINATION) - goto encrypt; - - /* An unauthenticated combination key has sufficient security for - security level 1 and 2. */ - if (conn->key_type == HCI_LK_UNAUTH_COMBINATION && - (sec_level == BT_SECURITY_MEDIUM || - sec_level == BT_SECURITY_LOW)) - goto encrypt; - - /* A combination key has always sufficient security for the security - levels 1 or 2. High security level requires the combination key - is generated using maximum PIN code length (16). - For pre 2.1 units. */ - if (conn->key_type == HCI_LK_COMBINATION && - (sec_level != BT_SECURITY_HIGH || - conn->pin_length == 16)) - goto encrypt; - -auth: - if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) - return 0; + if (conn->link_mode & HCI_LM_ENCRYPT) + return hci_conn_auth(conn, sec_level, auth_type); - if (!hci_conn_auth(conn, sec_level, auth_type)) + if (test_and_set_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) return 0; -encrypt: - if (conn->link_mode & HCI_LM_ENCRYPT) - return 1; + if (hci_conn_auth(conn, sec_level, auth_type)) { + struct hci_cp_set_conn_encrypt cp; + cp.handle = cpu_to_le16(conn->handle); + cp.encrypt = 1; + hci_send_cmd(conn->hdev, HCI_OP_SET_CONN_ENCRYPT, + sizeof(cp), &cp); + } - hci_conn_encrypt(conn); return 0; } EXPORT_SYMBOL(hci_conn_security); -/* Check secure link requirement */ -int hci_conn_check_secure(struct hci_conn *conn, __u8 sec_level) -{ - BT_DBG("conn %p", conn); - - if (sec_level != BT_SECURITY_HIGH) - return 1; /* Accept if non-secure is required */ - - if (conn->sec_level == BT_SECURITY_HIGH) - return 1; - - return 0; /* Reject not secure link */ -} -EXPORT_SYMBOL(hci_conn_check_secure); - /* Change link key */ int hci_conn_change_link_key(struct hci_conn *conn) { @@ -817,6 +889,98 @@ void hci_conn_enter_sniff_mode(struct hci_conn *conn) } } +struct hci_chan *hci_chan_accept(struct hci_conn *conn, + struct hci_ext_fs *tx_fs, struct hci_ext_fs *rx_fs) +{ + struct hci_chan *chan; + struct hci_cp_create_logical_link cp; + + chan = hci_chan_add(conn->hdev); + if (!chan) + return NULL; + + chan->state = BT_CONNECT; + chan->conn = conn; + chan->tx_fs = *tx_fs; + chan->rx_fs = *rx_fs; + cp.phy_handle = chan->conn->handle; + cp.tx_fs.id = chan->tx_fs.id; + cp.tx_fs.type = chan->tx_fs.type; + cp.tx_fs.max_sdu = cpu_to_le16(chan->tx_fs.max_sdu); + cp.tx_fs.sdu_arr_time = cpu_to_le32(chan->tx_fs.sdu_arr_time); + cp.tx_fs.acc_latency = cpu_to_le32(chan->tx_fs.acc_latency); + cp.tx_fs.flush_to = cpu_to_le32(chan->tx_fs.flush_to); + cp.rx_fs.id = chan->rx_fs.id; + cp.rx_fs.type = chan->rx_fs.type; + cp.rx_fs.max_sdu = cpu_to_le16(chan->rx_fs.max_sdu); + cp.rx_fs.sdu_arr_time = cpu_to_le32(chan->rx_fs.sdu_arr_time); + cp.rx_fs.acc_latency = cpu_to_le32(chan->rx_fs.acc_latency); + cp.rx_fs.flush_to = cpu_to_le32(chan->rx_fs.flush_to); + hci_conn_hold(chan->conn); + hci_send_cmd(conn->hdev, HCI_OP_ACCEPT_LOGICAL_LINK, sizeof(cp), &cp); + return chan; +} +EXPORT_SYMBOL(hci_chan_accept); + +struct hci_chan *hci_chan_create(struct hci_conn *conn, + struct hci_ext_fs *tx_fs, struct hci_ext_fs *rx_fs) +{ + struct hci_chan *chan; + struct hci_cp_create_logical_link cp; + + chan = hci_chan_add(conn->hdev); + if (!chan) + return NULL; + + chan->state = BT_CONNECT; + chan->conn = conn; + chan->tx_fs = *tx_fs; + chan->rx_fs = *rx_fs; + cp.phy_handle = chan->conn->handle; + cp.tx_fs.id = chan->tx_fs.id; + cp.tx_fs.type = chan->tx_fs.type; + cp.tx_fs.max_sdu = cpu_to_le16(chan->tx_fs.max_sdu); + cp.tx_fs.sdu_arr_time = cpu_to_le32(chan->tx_fs.sdu_arr_time); + cp.tx_fs.acc_latency = cpu_to_le32(chan->tx_fs.acc_latency); + cp.tx_fs.flush_to = cpu_to_le32(chan->tx_fs.flush_to); + cp.rx_fs.id = chan->rx_fs.id; + cp.rx_fs.type = chan->rx_fs.type; + cp.rx_fs.max_sdu = cpu_to_le16(chan->rx_fs.max_sdu); + cp.rx_fs.sdu_arr_time = cpu_to_le32(chan->rx_fs.sdu_arr_time); + cp.rx_fs.acc_latency = cpu_to_le32(chan->rx_fs.acc_latency); + cp.rx_fs.flush_to = cpu_to_le32(chan->rx_fs.flush_to); + hci_conn_hold(chan->conn); + hci_send_cmd(conn->hdev, HCI_OP_CREATE_LOGICAL_LINK, sizeof(cp), &cp); + return chan; +} +EXPORT_SYMBOL(hci_chan_create); + +void hci_chan_modify(struct hci_chan *chan, + struct hci_ext_fs *tx_fs, struct hci_ext_fs *rx_fs) +{ + struct hci_cp_flow_spec_modify cp; + + chan->tx_fs = *tx_fs; + chan->rx_fs = *rx_fs; + cp.log_handle = cpu_to_le16(chan->ll_handle); + cp.tx_fs.id = tx_fs->id; + cp.tx_fs.type = tx_fs->type; + cp.tx_fs.max_sdu = cpu_to_le16(tx_fs->max_sdu); + cp.tx_fs.sdu_arr_time = cpu_to_le32(tx_fs->sdu_arr_time); + cp.tx_fs.acc_latency = cpu_to_le32(tx_fs->acc_latency); + cp.tx_fs.flush_to = cpu_to_le32(tx_fs->flush_to); + cp.rx_fs.id = rx_fs->id; + cp.rx_fs.type = rx_fs->type; + cp.rx_fs.max_sdu = cpu_to_le16(rx_fs->max_sdu); + cp.rx_fs.sdu_arr_time = cpu_to_le32(rx_fs->sdu_arr_time); + cp.rx_fs.acc_latency = cpu_to_le32(rx_fs->acc_latency); + cp.rx_fs.flush_to = cpu_to_le32(rx_fs->flush_to); + hci_conn_hold(chan->conn); + hci_send_cmd(chan->conn->hdev, HCI_OP_FLOW_SPEC_MODIFY, sizeof(cp), + &cp); +} +EXPORT_SYMBOL(hci_chan_modify); + /* Drop all connection on the device */ void hci_conn_hash_flush(struct hci_dev *hdev) { @@ -961,6 +1125,8 @@ int hci_get_conn_info(struct hci_dev *hdev, void __user *arg) ci.cnt = hdev->acl_cnt; ci.pkts = hdev->acl_pkts; } + ci.pending_sec_level = conn->pending_sec_level; + ci.ssp_mode = conn->ssp_mode; } hci_dev_unlock_bh(hdev); @@ -989,3 +1155,23 @@ int hci_get_auth_info(struct hci_dev *hdev, void __user *arg) return copy_to_user(arg, &req, sizeof(req)) ? -EFAULT : 0; } + +int hci_set_auth_info(struct hci_dev *hdev, void __user *arg) +{ + struct hci_auth_info_req req; + struct hci_conn *conn; + + if (copy_from_user(&req, arg, sizeof(req))) + return -EFAULT; + + hci_dev_lock_bh(hdev); + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &req.bdaddr); + if (conn) + conn->auth_type = req.type; + hci_dev_unlock_bh(hdev); + + if (!conn) + return -ENOENT; + + return copy_to_user(arg, &req, sizeof(req)) ? -EFAULT : 0; +} diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 3b391986407..b095e996e3b 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1,6 +1,6 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2000-2001, 2010-2011 Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -60,6 +60,8 @@ static void hci_tx_task(unsigned long arg); static DEFINE_RWLOCK(hci_task_lock); +static int enable_smp; + /* HCI device list */ LIST_HEAD(hci_dev_list); DEFINE_RWLOCK(hci_dev_list_lock); @@ -68,6 +70,10 @@ DEFINE_RWLOCK(hci_dev_list_lock); LIST_HEAD(hci_cb_list); DEFINE_RWLOCK(hci_cb_list_lock); +/* AMP Manager event callbacks */ +LIST_HEAD(amp_mgr_cb_list); +DEFINE_RWLOCK(amp_mgr_cb_list_lock); + /* HCI protocols */ #define HCI_MAX_PROTO 2 struct hci_proto *hci_proto[HCI_MAX_PROTO]; @@ -146,7 +152,7 @@ static int __hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, switch (hdev->req_status) { case HCI_REQ_DONE: - err = -bt_to_errno(hdev->req_result); + err = -bt_err(hdev->req_result); break; case HCI_REQ_CANCELED: @@ -219,15 +225,25 @@ static void hci_init_req(struct hci_dev *hdev, unsigned long opt) hci_send_cmd(hdev, HCI_OP_RESET, 0, NULL); } - /* Read Local Supported Features */ - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_FEATURES, 0, NULL); - /* Read Local Version */ hci_send_cmd(hdev, HCI_OP_READ_LOCAL_VERSION, 0, NULL); + + /* Set default HCI Flow Control Mode */ + if (hdev->dev_type == HCI_BREDR) + hdev->flow_ctl_mode = HCI_PACKET_BASED_FLOW_CTL_MODE; + else + hdev->flow_ctl_mode = HCI_BLOCK_BASED_FLOW_CTL_MODE; + + /* Read HCI Flow Control Mode */ + hci_send_cmd(hdev, HCI_OP_READ_FLOW_CONTROL_MODE, 0, NULL); + /* Read Buffer Size (ACL mtu, max pkt, etc.) */ hci_send_cmd(hdev, HCI_OP_READ_BUFFER_SIZE, 0, NULL); + /* Read Data Block Size (ACL mtu, max pkt, etc.) */ + hci_send_cmd(hdev, HCI_OP_READ_DATA_BLOCK_SIZE, 0, NULL); + #if 0 /* Host buffer size */ { @@ -240,31 +256,46 @@ static void hci_init_req(struct hci_dev *hdev, unsigned long opt) } #endif - /* Read BD Address */ - hci_send_cmd(hdev, HCI_OP_READ_BD_ADDR, 0, NULL); + if (hdev->dev_type == HCI_BREDR) { + /* BR-EDR initialization */ + + /* Read Local Supported Features */ + hci_send_cmd(hdev, HCI_OP_READ_LOCAL_FEATURES, 0, NULL); - /* Read Class of Device */ - hci_send_cmd(hdev, HCI_OP_READ_CLASS_OF_DEV, 0, NULL); + /* Read BD Address */ + hci_send_cmd(hdev, HCI_OP_READ_BD_ADDR, 0, NULL); - /* Read Local Name */ - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_NAME, 0, NULL); + /* Read Class of Device */ + hci_send_cmd(hdev, HCI_OP_READ_CLASS_OF_DEV, 0, NULL); - /* Read Voice Setting */ - hci_send_cmd(hdev, HCI_OP_READ_VOICE_SETTING, 0, NULL); + /* Read Local Name */ + hci_send_cmd(hdev, HCI_OP_READ_LOCAL_NAME, 0, NULL); - /* Optional initialization */ + /* Read Voice Setting */ + hci_send_cmd(hdev, HCI_OP_READ_VOICE_SETTING, 0, NULL); - /* Clear Event Filters */ - flt_type = HCI_FLT_CLEAR_ALL; - hci_send_cmd(hdev, HCI_OP_SET_EVENT_FLT, 1, &flt_type); + /* Optional initialization */ + /* Clear Event Filters */ + flt_type = HCI_FLT_CLEAR_ALL; + hci_send_cmd(hdev, HCI_OP_SET_EVENT_FLT, 1, &flt_type); - /* Connection accept timeout ~20 secs */ - param = cpu_to_le16(0x7d00); - hci_send_cmd(hdev, HCI_OP_WRITE_CA_TIMEOUT, 2, ¶m); + /* Connection accept timeout ~20 secs */ + param = cpu_to_le16(0x7d00); + hci_send_cmd(hdev, HCI_OP_WRITE_CA_TIMEOUT, 2, ¶m); + + bacpy(&cp.bdaddr, BDADDR_ANY); + cp.delete_all = 1; + hci_send_cmd(hdev, HCI_OP_DELETE_STORED_LINK_KEY, + sizeof(cp), &cp); + } else { + /* AMP initialization */ + /* Connection accept timeout ~5 secs */ + param = cpu_to_le16(0x1f40); + hci_send_cmd(hdev, HCI_OP_WRITE_CA_TIMEOUT, 2, ¶m); - bacpy(&cp.bdaddr, BDADDR_ANY); - cp.delete_all = 1; - hci_send_cmd(hdev, HCI_OP_DELETE_STORED_LINK_KEY, sizeof(cp), &cp); + /* Read AMP Info */ + hci_send_cmd(hdev, HCI_OP_READ_LOCAL_AMP_INFO, 0, NULL); + } } static void hci_le_init_req(struct hci_dev *hdev, unsigned long opt) @@ -338,6 +369,7 @@ struct hci_dev *hci_dev_get(int index) read_unlock(&hci_dev_list_lock); return hdev; } +EXPORT_SYMBOL(hci_dev_get); /* ---- Inquiry support ---- */ static void inquiry_cache_flush(struct hci_dev *hdev) @@ -523,10 +555,6 @@ int hci_dev_open(__u16 dev) if (test_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks)) set_bit(HCI_RAW, &hdev->flags); - /* Treat all non BR/EDR controllers as raw devices for now */ - if (hdev->dev_type != HCI_BREDR) - set_bit(HCI_RAW, &hdev->flags); - if (hdev->open(hdev)) { ret = -EIO; goto done; @@ -540,7 +568,7 @@ int hci_dev_open(__u16 dev) ret = __hci_request(hdev, hci_init_req, 0, msecs_to_jiffies(HCI_INIT_TIMEOUT)); - if (lmp_host_le_capable(hdev)) + if (lmp_le_capable(hdev)) ret = __hci_request(hdev, hci_le_init_req, 0, msecs_to_jiffies(HCI_INIT_TIMEOUT)); @@ -1021,50 +1049,17 @@ struct link_key *hci_find_link_key(struct hci_dev *hdev, bdaddr_t *bdaddr) return NULL; } -static int hci_persistent_key(struct hci_dev *hdev, struct hci_conn *conn, - u8 key_type, u8 old_key_type) -{ - /* Legacy key */ - if (key_type < 0x03) - return 1; - - /* Debug keys are insecure so don't store them persistently */ - if (key_type == HCI_LK_DEBUG_COMBINATION) - return 0; - - /* Changed combination key and there's no previous one */ - if (key_type == HCI_LK_CHANGED_COMBINATION && old_key_type == 0xff) - return 0; - - /* Security mode 3 case */ - if (!conn) - return 1; - - /* Neither local nor remote side had no-bonding as requirement */ - if (conn->auth_type > 0x01 && conn->remote_auth > 0x01) - return 1; - - /* Local side had dedicated bonding as requirement */ - if (conn->auth_type == 0x02 || conn->auth_type == 0x03) - return 1; - - /* Remote side had dedicated bonding as requirement */ - if (conn->remote_auth == 0x02 || conn->remote_auth == 0x03) - return 1; - - /* If none of the above criteria match, then don't store the key - * persistently */ - return 0; -} - struct link_key *hci_find_ltk(struct hci_dev *hdev, __le16 ediv, u8 rand[8]) { - struct link_key *k; + struct list_head *p; - list_for_each_entry(k, &hdev->link_keys, list) { + list_for_each(p, &hdev->link_keys) { + struct link_key *k; struct key_master_id *id; - if (k->type != HCI_LK_SMP_LTK) + k = list_entry(p, struct link_key, list); + + if (k->type != KEY_TYPE_LTK) continue; if (k->dlen != sizeof(*id)) @@ -1083,28 +1078,33 @@ EXPORT_SYMBOL(hci_find_ltk); struct link_key *hci_find_link_key_type(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) { - struct link_key *k; + struct list_head *p; + + list_for_each(p, &hdev->link_keys) { + struct link_key *k; + + k = list_entry(p, struct link_key, list); - list_for_each_entry(k, &hdev->link_keys, list) - if (k->type == type && bacmp(bdaddr, &k->bdaddr) == 0) + if ((k->type == type) && (bacmp(bdaddr, &k->bdaddr) == 0)) return k; + } return NULL; } EXPORT_SYMBOL(hci_find_link_key_type); -int hci_add_link_key(struct hci_dev *hdev, struct hci_conn *conn, int new_key, - bdaddr_t *bdaddr, u8 *val, u8 type, u8 pin_len) +int hci_add_link_key(struct hci_dev *hdev, int new_key, bdaddr_t *bdaddr, + u8 *val, u8 type, u8 pin_len) { struct link_key *key, *old_key; - u8 old_key_type, persistent; + u8 old_key_type; old_key = hci_find_link_key(hdev, bdaddr); if (old_key) { old_key_type = old_key->type; key = old_key; } else { - old_key_type = conn ? conn->key_type : 0xff; + old_key_type = 0xff; key = kzalloc(sizeof(*key), GFP_ATOMIC); if (!key) return -ENOMEM; @@ -1113,37 +1113,16 @@ int hci_add_link_key(struct hci_dev *hdev, struct hci_conn *conn, int new_key, BT_DBG("%s key for %s type %u", hdev->name, batostr(bdaddr), type); - /* Some buggy controller combinations generate a changed - * combination key for legacy pairing even when there's no - * previous key */ - if (type == HCI_LK_CHANGED_COMBINATION && - (!conn || conn->remote_auth == 0xff) && - old_key_type == 0xff) { - type = HCI_LK_COMBINATION; - if (conn) - conn->key_type = type; - } - bacpy(&key->bdaddr, bdaddr); memcpy(key->val, val, 16); + key->type = type; key->pin_len = pin_len; - if (type == HCI_LK_CHANGED_COMBINATION) - key->type = old_key_type; - else - key->type = type; - - if (!new_key) - return 0; - - persistent = hci_persistent_key(hdev, conn, type, old_key_type); - - mgmt_new_key(hdev->id, key, persistent); + if (new_key) + mgmt_new_key(hdev->id, key, old_key_type); - if (!persistent) { - list_del(&key->list); - kfree(key); - } + if (type == 0x06) + key->type = old_key_type; return 0; } @@ -1157,7 +1136,7 @@ int hci_add_ltk(struct hci_dev *hdev, int new_key, bdaddr_t *bdaddr, BT_DBG("%s addr %s", hdev->name, batostr(bdaddr)); - old_key = hci_find_link_key_type(hdev, bdaddr, HCI_LK_SMP_LTK); + old_key = hci_find_link_key_type(hdev, bdaddr, KEY_TYPE_LTK); if (old_key) { key = old_key; old_key_type = old_key->type; @@ -1173,7 +1152,7 @@ int hci_add_ltk(struct hci_dev *hdev, int new_key, bdaddr_t *bdaddr, bacpy(&key->bdaddr, bdaddr); memcpy(key->val, ltk, sizeof(key->val)); - key->type = HCI_LK_SMP_LTK; + key->type = KEY_TYPE_LTK; key->pin_len = key_size; id = (void *) &key->data; @@ -1209,6 +1188,7 @@ static void hci_cmd_timer(unsigned long arg) BT_ERR("%s command tx timeout", hdev->name); atomic_set(&hdev->cmd_cnt, 1); + clear_bit(HCI_RESET, &hdev->flags); tasklet_schedule(&hdev->cmd_task); } @@ -1252,161 +1232,83 @@ int hci_remote_oob_data_clear(struct hci_dev *hdev) return 0; } -int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 *hash, - u8 *randomizer) -{ - struct oob_data *data; - - data = hci_find_remote_oob_data(hdev, bdaddr); - - if (!data) { - data = kmalloc(sizeof(*data), GFP_ATOMIC); - if (!data) - return -ENOMEM; - - bacpy(&data->bdaddr, bdaddr); - list_add(&data->list, &hdev->remote_oob_data); - } - - memcpy(data->hash, hash, sizeof(data->hash)); - memcpy(data->randomizer, randomizer, sizeof(data->randomizer)); - - BT_DBG("%s for %s", hdev->name, batostr(bdaddr)); - - return 0; -} - -struct bdaddr_list *hci_blacklist_lookup(struct hci_dev *hdev, - bdaddr_t *bdaddr) +static void hci_adv_clear(unsigned long arg) { - struct list_head *p; - - list_for_each(p, &hdev->blacklist) { - struct bdaddr_list *b; - - b = list_entry(p, struct bdaddr_list, list); - - if (bacmp(bdaddr, &b->bdaddr) == 0) - return b; - } + struct hci_dev *hdev = (void *) arg; - return NULL; + hci_adv_entries_clear(hdev); } -int hci_blacklist_clear(struct hci_dev *hdev) +int hci_adv_entries_clear(struct hci_dev *hdev) { struct list_head *p, *n; - list_for_each_safe(p, n, &hdev->blacklist) { - struct bdaddr_list *b; + write_lock_bh(&hdev->adv_entries_lock); - b = list_entry(p, struct bdaddr_list, list); + list_for_each_safe(p, n, &hdev->adv_entries) { + struct adv_entry *entry; + + entry = list_entry(p, struct adv_entry, list); list_del(p); - kfree(b); + kfree(entry); } + write_unlock_bh(&hdev->adv_entries_lock); + return 0; } -int hci_blacklist_add(struct hci_dev *hdev, bdaddr_t *bdaddr) +struct adv_entry *hci_find_adv_entry(struct hci_dev *hdev, bdaddr_t *bdaddr) { - struct bdaddr_list *entry; - int err; + struct list_head *p; + struct adv_entry *res = NULL; - if (bacmp(bdaddr, BDADDR_ANY) == 0) - return -EBADF; + read_lock_bh(&hdev->adv_entries_lock); - hci_dev_lock_bh(hdev); + list_for_each(p, &hdev->adv_entries) { + struct adv_entry *entry; - if (hci_blacklist_lookup(hdev, bdaddr)) { - err = -EEXIST; - goto err; - } + entry = list_entry(p, struct adv_entry, list); - entry = kzalloc(sizeof(struct bdaddr_list), GFP_KERNEL); - if (!entry) { - return -ENOMEM; - goto err; + if (bacmp(bdaddr, &entry->bdaddr) == 0) { + res = entry; + goto out; + } } - - bacpy(&entry->bdaddr, bdaddr); - - list_add(&entry->list, &hdev->blacklist); - - err = 0; - -err: - hci_dev_unlock_bh(hdev); - return err; +out: + read_unlock_bh(&hdev->adv_entries_lock); + return res; } -int hci_blacklist_del(struct hci_dev *hdev, bdaddr_t *bdaddr) +static inline int is_connectable_adv(u8 evt_type) { - struct bdaddr_list *entry; - int err = 0; - - hci_dev_lock_bh(hdev); - - if (bacmp(bdaddr, BDADDR_ANY) == 0) { - hci_blacklist_clear(hdev); - goto done; - } - - entry = hci_blacklist_lookup(hdev, bdaddr); - if (!entry) { - err = -ENOENT; - goto done; - } - - list_del(&entry->list); - kfree(entry); + if (evt_type == ADV_IND || evt_type == ADV_DIRECT_IND) + return 1; -done: - hci_dev_unlock_bh(hdev); - return err; + return 0; } -static void hci_clear_adv_cache(unsigned long arg) +int hci_add_remote_oob_data(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 *hash, + u8 *randomizer) { - struct hci_dev *hdev = (void *) arg; - - hci_dev_lock(hdev); - - hci_adv_entries_clear(hdev); + struct oob_data *data; - hci_dev_unlock(hdev); -} + data = hci_find_remote_oob_data(hdev, bdaddr); -int hci_adv_entries_clear(struct hci_dev *hdev) -{ - struct adv_entry *entry, *tmp; + if (!data) { + data = kmalloc(sizeof(*data), GFP_ATOMIC); + if (!data) + return -ENOMEM; - list_for_each_entry_safe(entry, tmp, &hdev->adv_entries, list) { - list_del(&entry->list); - kfree(entry); + bacpy(&data->bdaddr, bdaddr); + list_add(&data->list, &hdev->remote_oob_data); } - BT_DBG("%s adv cache cleared", hdev->name); - - return 0; -} - -struct adv_entry *hci_find_adv_entry(struct hci_dev *hdev, bdaddr_t *bdaddr) -{ - struct adv_entry *entry; - - list_for_each_entry(entry, &hdev->adv_entries, list) - if (bacmp(bdaddr, &entry->bdaddr) == 0) - return entry; - - return NULL; -} + memcpy(data->hash, hash, sizeof(data->hash)); + memcpy(data->randomizer, randomizer, sizeof(data->randomizer)); -static inline int is_connectable_adv(u8 evt_type) -{ - if (evt_type == ADV_IND || evt_type == ADV_DIRECT_IND) - return 1; + BT_DBG("%s for %s", hdev->name, batostr(bdaddr)); return 0; } @@ -1419,9 +1321,10 @@ int hci_add_adv_entry(struct hci_dev *hdev, if (!is_connectable_adv(ev->evt_type)) return -EINVAL; + entry = hci_find_adv_entry(hdev, &ev->bdaddr); /* Only new entries should be added to adv_entries. So, if * bdaddr was found, don't add it. */ - if (hci_find_adv_entry(hdev, &ev->bdaddr)) + if (entry) return 0; entry = kzalloc(sizeof(*entry), GFP_ATOMIC); @@ -1431,14 +1334,21 @@ int hci_add_adv_entry(struct hci_dev *hdev, bacpy(&entry->bdaddr, &ev->bdaddr); entry->bdaddr_type = ev->bdaddr_type; + write_lock(&hdev->adv_entries_lock); list_add(&entry->list, &hdev->adv_entries); - - BT_DBG("%s adv entry added: address %s type %u", hdev->name, - batostr(&entry->bdaddr), entry->bdaddr_type); + write_unlock(&hdev->adv_entries_lock); return 0; } +static struct crypto_blkcipher *alloc_cypher(void) +{ + if (enable_smp) + return crypto_alloc_blkcipher("ecb(aes)", 0, CRYPTO_ALG_ASYNC); + + return ERR_PTR(-ENOTSUPP); +} + /* Register HCI device */ int hci_register_dev(struct hci_dev *hdev) { @@ -1496,6 +1406,7 @@ int hci_register_dev(struct hci_dev *hdev) inquiry_cache_init(hdev); hci_conn_hash_init(hdev); + hci_chan_list_init(hdev); INIT_LIST_HEAD(&hdev->blacklist); @@ -1506,8 +1417,8 @@ int hci_register_dev(struct hci_dev *hdev) INIT_LIST_HEAD(&hdev->remote_oob_data); INIT_LIST_HEAD(&hdev->adv_entries); - setup_timer(&hdev->adv_timer, hci_clear_adv_cache, - (unsigned long) hdev); + rwlock_init(&hdev->adv_entries_lock); + setup_timer(&hdev->adv_timer, hci_adv_clear, (unsigned long) hdev); INIT_WORK(&hdev->power_on, hci_power_on); INIT_WORK(&hdev->power_off, hci_power_off); @@ -1523,7 +1434,7 @@ int hci_register_dev(struct hci_dev *hdev) if (!hdev->workqueue) goto nomem; - hdev->tfm = crypto_alloc_blkcipher("ecb(aes)", 0, CRYPTO_ALG_ASYNC); + hdev->tfm = alloc_cypher(); if (IS_ERR(hdev->tfm)) BT_INFO("Failed to load transform for ecb(aes): %ld", PTR_ERR(hdev->tfm)); @@ -1883,6 +1794,74 @@ int hci_unregister_cb(struct hci_cb *cb) } EXPORT_SYMBOL(hci_unregister_cb); +int hci_register_amp(struct amp_mgr_cb *cb) +{ + BT_DBG("%p", cb); + + write_lock_bh(&_mgr_cb_list_lock); + list_add(&cb->list, &_mgr_cb_list); + write_unlock_bh(&_mgr_cb_list_lock); + + return 0; +} +EXPORT_SYMBOL(hci_register_amp); + +int hci_unregister_amp(struct amp_mgr_cb *cb) +{ + BT_DBG("%p", cb); + + write_lock_bh(&_mgr_cb_list_lock); + list_del(&cb->list); + write_unlock_bh(&_mgr_cb_list_lock); + + return 0; +} +EXPORT_SYMBOL(hci_unregister_amp); + +void hci_amp_cmd_complete(struct hci_dev *hdev, __u16 opcode, + struct sk_buff *skb) +{ + struct amp_mgr_cb *cb; + + BT_DBG("opcode 0x%x", opcode); + + read_lock_bh(&_mgr_cb_list_lock); + list_for_each_entry(cb, &_mgr_cb_list, list) { + if (cb->amp_cmd_complete_event) + cb->amp_cmd_complete_event(hdev, opcode, skb); + } + read_unlock_bh(&_mgr_cb_list_lock); +} + +void hci_amp_cmd_status(struct hci_dev *hdev, __u16 opcode, __u8 status) +{ + struct amp_mgr_cb *cb; + + BT_DBG("opcode 0x%x, status %d", opcode, status); + + read_lock_bh(&_mgr_cb_list_lock); + list_for_each_entry(cb, &_mgr_cb_list, list) { + if (cb->amp_cmd_status_event) + cb->amp_cmd_status_event(hdev, opcode, status); + } + read_unlock_bh(&_mgr_cb_list_lock); +} + +void hci_amp_event_packet(struct hci_dev *hdev, __u8 ev_code, + struct sk_buff *skb) +{ + struct amp_mgr_cb *cb; + + BT_DBG("ev_code 0x%x", ev_code); + + read_lock_bh(&_mgr_cb_list_lock); + list_for_each_entry(cb, &_mgr_cb_list, list) { + if (cb->amp_event) + cb->amp_event(hdev, ev_code, skb); + } + read_unlock_bh(&_mgr_cb_list_lock); +} + static int hci_send_frame(struct sk_buff *skb) { struct hci_dev *hdev = (struct hci_dev *) skb->dev; @@ -1904,6 +1883,7 @@ static int hci_send_frame(struct sk_buff *skb) /* Get rid of skb owner, prior to sending to the driver. */ skb_orphan(skb); + hci_notify(hdev, HCI_DEV_WRITE); return hdev->send(skb); } @@ -1942,6 +1922,7 @@ int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param) return 0; } +EXPORT_SYMBOL(hci_send_cmd); /* Get data from the previously sent command */ void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 opcode) @@ -1974,16 +1955,20 @@ static void hci_add_acl_hdr(struct sk_buff *skb, __u16 handle, __u16 flags) hdr->dlen = cpu_to_le16(len); } -void hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags) +void hci_send_acl(struct hci_conn *conn, struct hci_chan *chan, + struct sk_buff *skb, __u16 flags) { struct hci_dev *hdev = conn->hdev; struct sk_buff *list; - BT_DBG("%s conn %p flags 0x%x", hdev->name, conn, flags); + BT_DBG("%s conn %p chan %p flags 0x%x", hdev->name, conn, chan, flags); skb->dev = (void *) hdev; bt_cb(skb)->pkt_type = HCI_ACLDATA_PKT; - hci_add_acl_hdr(skb, conn->handle, flags); + if (hdev->dev_type == HCI_BREDR) + hci_add_acl_hdr(skb, conn->handle, flags); + else + hci_add_acl_hdr(skb, chan->ll_handle, flags); list = skb_shinfo(skb)->frag_list; if (!list) { @@ -2001,8 +1986,7 @@ void hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags) spin_lock_bh(&conn->data_q.lock); __skb_queue_tail(&conn->data_q, skb); - - flags &= ~ACL_START; + flags &= ~ACL_PB_MASK; flags |= ACL_CONT; do { skb = list; list = list->next; @@ -2134,21 +2118,38 @@ static inline void hci_sched_acl(struct hci_dev *hdev) if (!test_bit(HCI_RAW, &hdev->flags)) { /* ACL tx timeout must be longer than maximum * link supervision timeout (40.9 seconds) */ - if (!hdev->acl_cnt && time_after(jiffies, hdev->acl_last_tx + HZ * 45)) + if (hdev->acl_cnt <= 0 && + time_after(jiffies, hdev->acl_last_tx + HZ * 45)) hci_link_tx_to(hdev, ACL_LINK); } - while (hdev->acl_cnt && (conn = hci_low_sent(hdev, ACL_LINK, "e))) { - while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + while (hdev->acl_cnt > 0 && + (conn = hci_low_sent(hdev, ACL_LINK, "e))) { + while (quote > 0 && (skb = skb_dequeue(&conn->data_q))) { + int count = 1; + BT_DBG("skb %p len %d", skb, skb->len); + if (hdev->flow_ctl_mode == + HCI_BLOCK_BASED_FLOW_CTL_MODE) + /* Calculate count of blocks used by + * this packet + */ + count = ((skb->len - HCI_ACL_HDR_SIZE - 1) / + hdev->data_block_len) + 1; + + if (count > hdev->acl_cnt) + return; + hci_conn_enter_active_mode(conn, bt_cb(skb)->force_active); hci_send_frame(skb); hdev->acl_last_tx = jiffies; - hdev->acl_cnt--; - conn->sent++; + hdev->acl_cnt -= count; + quote -= count; + + conn->sent += count; } } } @@ -2255,7 +2256,7 @@ static void hci_tx_task(unsigned long arg) read_unlock(&hci_task_lock); } -/* ----- HCI RX task (incoming data processing) ----- */ +/* ----- HCI RX task (incoming data proccessing) ----- */ /* ACL data packet */ static inline void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb) @@ -2407,10 +2408,7 @@ static void hci_cmd_task(unsigned long arg) if (hdev->sent_cmd) { atomic_dec(&hdev->cmd_cnt); hci_send_frame(skb); - if (test_bit(HCI_RESET, &hdev->flags)) - del_timer(&hdev->cmd_timer); - else - mod_timer(&hdev->cmd_timer, + mod_timer(&hdev->cmd_timer, jiffies + msecs_to_jiffies(HCI_CMD_TIMEOUT)); } else { skb_queue_head(&hdev->cmd_q, skb); @@ -2418,3 +2416,6 @@ static void hci_cmd_task(unsigned long arg) } } } + +module_param(enable_smp, bool, 0644); +MODULE_PARM_DESC(enable_smp, "Enable SMP support (LE only)"); diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 6cddd03cf8c..84e397575d2 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -1,6 +1,6 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (c) 2000-2001, 2010, Code Aurora Forum. All rights reserved. + Copyright (c) 2000-2001, 2010-2011, Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -45,8 +45,6 @@ #include #include -static int enable_le; - /* Handle HCI Event packets */ static void hci_cc_inquiry_cancel(struct hci_dev *hdev, struct sk_buff *skb) @@ -58,9 +56,7 @@ static void hci_cc_inquiry_cancel(struct hci_dev *hdev, struct sk_buff *skb) if (status) return; - if (test_bit(HCI_MGMT, &hdev->flags) && - test_and_clear_bit(HCI_INQUIRY, &hdev->flags)) - mgmt_discovering(hdev->id, 0); + clear_bit(HCI_INQUIRY, &hdev->flags); hci_req_complete(hdev, HCI_OP_INQUIRY_CANCEL, status); @@ -76,13 +72,36 @@ static void hci_cc_exit_periodic_inq(struct hci_dev *hdev, struct sk_buff *skb) if (status) return; - if (test_bit(HCI_MGMT, &hdev->flags) && - test_and_clear_bit(HCI_INQUIRY, &hdev->flags)) - mgmt_discovering(hdev->id, 0); + clear_bit(HCI_INQUIRY, &hdev->flags); hci_conn_check_pending(hdev); } +static void hci_cc_link_key_reply(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_rp_link_key_reply *rp = (void *) skb->data; + struct hci_conn *conn; + struct hci_cp_link_key_reply *cp; + + BT_DBG("%s status 0x%x", hdev->name, rp->status); + if (rp->status) + return; + + cp = hci_sent_cmd_data(hdev, HCI_OP_LINK_KEY_REPLY); + if (!cp) + return; + + hci_dev_lock(hdev); + conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); + if (conn) { + hci_conn_hold(conn); + memcpy(conn->link_key, cp->link_key, sizeof(conn->link_key)); + conn->key_type = 5; + hci_conn_put(conn); + } + hci_dev_unlock(hdev); +} + static void hci_cc_remote_name_req_cancel(struct hci_dev *hdev, struct sk_buff *skb) { BT_DBG("%s", hdev->name); @@ -479,16 +498,14 @@ static void hci_setup_event_mask(struct hci_dev *hdev) * command otherwise */ u8 events[8] = { 0xff, 0xff, 0xfb, 0xff, 0x00, 0x00, 0x00, 0x00 }; - /* CSR 1.1 dongles does not accept any bitfield so don't try to set - * any event mask for pre 1.2 devices */ - if (hdev->lmp_ver <= 1) - return; - - events[4] |= 0x01; /* Flow Specification Complete */ - events[4] |= 0x02; /* Inquiry Result with RSSI */ - events[4] |= 0x04; /* Read Remote Extended Features Complete */ - events[5] |= 0x08; /* Synchronous Connection Complete */ - events[5] |= 0x10; /* Synchronous Connection Changed */ + /* Events for 1.2 and newer controllers */ + if (hdev->lmp_ver > 1) { + events[4] |= 0x01; /* Flow Specification Complete */ + events[4] |= 0x02; /* Inquiry Result with RSSI */ + events[4] |= 0x04; /* Read Remote Extended Features Complete */ + events[5] |= 0x08; /* Synchronous Connection Complete */ + events[5] |= 0x10; /* Synchronous Connection Changed */ + } if (hdev->features[3] & LMP_RSSI_INQ) events[4] |= 0x04; /* Inquiry Result with RSSI */ @@ -527,20 +544,6 @@ static void hci_setup_event_mask(struct hci_dev *hdev) hci_send_cmd(hdev, HCI_OP_SET_EVENT_MASK, sizeof(events), events); } -static void hci_set_le_support(struct hci_dev *hdev) -{ - struct hci_cp_write_le_host_supported cp; - - memset(&cp, 0, sizeof(cp)); - - if (enable_le) { - cp.le = 1; - cp.simul = !!(hdev->features[6] & LMP_SIMUL_LE_BR); - } - - hci_send_cmd(hdev, HCI_OP_WRITE_LE_HOST_SUPPORTED, sizeof(cp), &cp); -} - static void hci_setup(struct hci_dev *hdev) { hci_setup_event_mask(hdev); @@ -558,17 +561,6 @@ static void hci_setup(struct hci_dev *hdev) if (hdev->features[7] & LMP_INQ_TX_PWR) hci_send_cmd(hdev, HCI_OP_READ_INQ_RSP_TX_POWER, 0, NULL); - - if (hdev->features[7] & LMP_EXTFEATURES) { - struct hci_cp_read_local_ext_features cp; - - cp.page = 0x01; - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_EXT_FEATURES, - sizeof(cp), &cp); - } - - if (hdev->features[4] & LMP_LE) - hci_set_le_support(hdev); } static void hci_cc_read_local_version(struct hci_dev *hdev, struct sk_buff *skb) @@ -590,7 +582,7 @@ static void hci_cc_read_local_version(struct hci_dev *hdev, struct sk_buff *skb) hdev->manufacturer, hdev->hci_ver, hdev->hci_rev); - if (test_bit(HCI_INIT, &hdev->flags)) + if (hdev->dev_type == HCI_BREDR && test_bit(HCI_INIT, &hdev->flags)) hci_setup(hdev); } @@ -685,19 +677,17 @@ static void hci_cc_read_local_features(struct hci_dev *hdev, struct sk_buff *skb hdev->features[6], hdev->features[7]); } -static void hci_cc_read_local_ext_features(struct hci_dev *hdev, - struct sk_buff *skb) +static void hci_cc_read_flow_control_mode(struct hci_dev *hdev, + struct sk_buff *skb) { - struct hci_rp_read_local_ext_features *rp = (void *) skb->data; + struct hci_rp_read_flow_control_mode *rp = (void *) skb->data; BT_DBG("%s status 0x%x", hdev->name, rp->status); if (rp->status) return; - memcpy(hdev->extfeatures, rp->features, 8); - - hci_req_complete(hdev, HCI_OP_READ_LOCAL_EXT_FEATURES, rp->status); + hdev->flow_ctl_mode = rp->mode; } static void hci_cc_read_buffer_size(struct hci_dev *hdev, struct sk_buff *skb) @@ -709,18 +699,20 @@ static void hci_cc_read_buffer_size(struct hci_dev *hdev, struct sk_buff *skb) if (rp->status) return; - hdev->acl_mtu = __le16_to_cpu(rp->acl_mtu); - hdev->sco_mtu = rp->sco_mtu; - hdev->acl_pkts = __le16_to_cpu(rp->acl_max_pkt); - hdev->sco_pkts = __le16_to_cpu(rp->sco_max_pkt); + if (hdev->flow_ctl_mode == HCI_PACKET_BASED_FLOW_CTL_MODE) { + hdev->acl_mtu = __le16_to_cpu(rp->acl_mtu); + hdev->sco_mtu = rp->sco_mtu; + hdev->acl_pkts = __le16_to_cpu(rp->acl_max_pkt); + hdev->sco_pkts = __le16_to_cpu(rp->sco_max_pkt); + hdev->acl_cnt = hdev->acl_pkts; + hdev->sco_cnt = hdev->sco_pkts; + } if (test_bit(HCI_QUIRK_FIXUP_BUFFER_SIZE, &hdev->quirks)) { hdev->sco_mtu = 64; hdev->sco_pkts = 8; } - hdev->acl_cnt = hdev->acl_pkts; - hdev->sco_cnt = hdev->sco_pkts; BT_DBG("%s acl mtu %d:%d sco mtu %d:%d", hdev->name, hdev->acl_mtu, hdev->acl_pkts, @@ -748,6 +740,55 @@ static void hci_cc_write_ca_timeout(struct hci_dev *hdev, struct sk_buff *skb) hci_req_complete(hdev, HCI_OP_WRITE_CA_TIMEOUT, status); } +static void hci_cc_read_data_block_size(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_rp_read_data_block_size *rp = (void *) skb->data; + + BT_DBG("%s status 0x%x", hdev->name, rp->status); + + if (rp->status) + return; + + if (hdev->flow_ctl_mode == HCI_BLOCK_BASED_FLOW_CTL_MODE) { + hdev->acl_mtu = __le16_to_cpu(rp->max_acl_len); + hdev->sco_mtu = 0; + hdev->data_block_len = __le16_to_cpu(rp->data_block_len); + /* acl_pkts indicates the number of blocks */ + hdev->acl_pkts = __le16_to_cpu(rp->num_blocks); + hdev->sco_pkts = 0; + hdev->acl_cnt = hdev->acl_pkts; + hdev->sco_cnt = 0; + } + + BT_DBG("%s acl mtu %d:%d, data block len %d", hdev->name, + hdev->acl_mtu, hdev->acl_cnt, hdev->data_block_len); +} + +static void hci_cc_read_local_amp_info(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_rp_read_local_amp_info *rp = (void *) skb->data; + + BT_DBG("%s status 0x%x", hdev->name, rp->status); + + if (rp->status) + return; + + hdev->amp_status = rp->amp_status; + hdev->amp_total_bw = __le32_to_cpu(rp->total_bw); + hdev->amp_max_bw = __le32_to_cpu(rp->max_bw); + hdev->amp_min_latency = __le32_to_cpu(rp->min_latency); + hdev->amp_max_pdu = __le32_to_cpu(rp->max_pdu); + hdev->amp_type = rp->amp_type; + hdev->amp_pal_cap = __le16_to_cpu(rp->pal_cap); + hdev->amp_assoc_size = __le16_to_cpu(rp->max_assoc_size); + hdev->amp_be_flush_to = __le32_to_cpu(rp->be_flush_to); + hdev->amp_max_flush_to = __le32_to_cpu(rp->max_flush_to); + + hci_req_complete(hdev, HCI_OP_READ_LOCAL_AMP_INFO, rp->status); +} + static void hci_cc_delete_stored_link_key(struct hci_dev *hdev, struct sk_buff *skb) { @@ -883,33 +924,6 @@ static void hci_cc_read_local_oob_data_reply(struct hci_dev *hdev, rp->randomizer, rp->status); } -static void hci_cc_le_set_scan_enable(struct hci_dev *hdev, - struct sk_buff *skb) -{ - struct hci_cp_le_set_scan_enable *cp; - __u8 status = *((__u8 *) skb->data); - - BT_DBG("%s status 0x%x", hdev->name, status); - - if (status) - return; - - cp = hci_sent_cmd_data(hdev, HCI_OP_LE_SET_SCAN_ENABLE); - if (!cp) - return; - - hci_dev_lock(hdev); - - if (cp->enable == 0x01) { - del_timer(&hdev->adv_timer); - hci_adv_entries_clear(hdev); - } else if (cp->enable == 0x00) { - mod_timer(&hdev->adv_timer, jiffies + ADV_CLEAR_TIMEOUT); - } - - hci_dev_unlock(hdev); -} - static void hci_cc_le_ltk_reply(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_rp_le_ltk_reply *rp = (void *) skb->data; @@ -934,19 +948,27 @@ static void hci_cc_le_ltk_neg_reply(struct hci_dev *hdev, struct sk_buff *skb) hci_req_complete(hdev, HCI_OP_LE_LTK_NEG_REPLY, rp->status); } -static inline void hci_cc_write_le_host_supported(struct hci_dev *hdev, - struct sk_buff *skb) +static void hci_cc_le_set_scan_enable(struct hci_dev *hdev, + struct sk_buff *skb) { - struct hci_cp_read_local_ext_features cp; + void *sent; + __u8 param_scan_enable; __u8 status = *((__u8 *) skb->data); - BT_DBG("%s status 0x%x", hdev->name, status); - if (status) return; - cp.page = 0x01; - hci_send_cmd(hdev, HCI_OP_READ_LOCAL_EXT_FEATURES, sizeof(cp), &cp); + sent = hci_sent_cmd_data(hdev, HCI_OP_LE_SET_SCAN_ENABLE); + if (!sent) + return; + + param_scan_enable = *((__u8 *) sent); + if (param_scan_enable == 0x01) { + del_timer(&hdev->adv_timer); + hci_adv_entries_clear(hdev); + } else if (param_scan_enable == 0x00) { + mod_timer(&hdev->adv_timer, jiffies + ADV_CLEAR_TIMEOUT); + } } static inline void hci_cs_inquiry(struct hci_dev *hdev, __u8 status) @@ -955,14 +977,10 @@ static inline void hci_cs_inquiry(struct hci_dev *hdev, __u8 status) if (status) { hci_req_complete(hdev, HCI_OP_INQUIRY, status); - hci_conn_check_pending(hdev); - return; - } - if (test_bit(HCI_MGMT, &hdev->flags) && - !test_and_set_bit(HCI_INQUIRY, - &hdev->flags)) - mgmt_discovering(hdev->id, 1); + hci_conn_check_pending(hdev); + } else + set_bit(HCI_INQUIRY, &hdev->flags); } static inline void hci_cs_create_conn(struct hci_dev *hdev, __u8 status) @@ -1131,19 +1149,12 @@ static void hci_cs_remote_name_req(struct hci_dev *hdev, __u8 status) hci_dev_lock(hdev); conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); - if (!conn) - goto unlock; - - if (!hci_outgoing_auth_needed(hdev, conn)) - goto unlock; - - if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) { + if (conn && hci_outgoing_auth_needed(hdev, conn)) { struct hci_cp_auth_requested cp; cp.handle = __cpu_to_le16(conn->handle); hci_send_cmd(hdev, HCI_OP_AUTH_REQUESTED, sizeof(cp), &cp); } -unlock: hci_dev_unlock(hdev); } @@ -1316,19 +1327,150 @@ static void hci_cs_le_create_conn(struct hci_dev *hdev, __u8 status) } } else { if (!conn) { - conn = hci_conn_add(hdev, LE_LINK, 0, &cp->peer_addr); - if (conn) { - conn->dst_type = cp->peer_addr_type; + conn = hci_le_conn_add(hdev, &cp->peer_addr, + cp->peer_addr_type); + if (conn) conn->out = 1; - } else { + else BT_ERR("No memory for new connection"); - } } } hci_dev_unlock(hdev); } +static void hci_cs_accept_logical_link(struct hci_dev *hdev, __u8 status) +{ + struct hci_cp_create_logical_link *ap; + struct hci_chan *chan; + + BT_DBG("%s status 0x%x", hdev->name, status); + + ap = hci_sent_cmd_data(hdev, HCI_OP_ACCEPT_LOGICAL_LINK); + if (!ap) + return; + + hci_dev_lock(hdev); + + chan = hci_chan_list_lookup_id(hdev, ap->phy_handle); + + BT_DBG("%s chan %p", hdev->name, chan); + + if (status) { + if (chan && chan->state == BT_CONNECT) { + chan->state = BT_CLOSED; + hci_proto_create_cfm(chan, status); + hci_chan_del(chan); + } + } else if (chan) + chan->state = BT_CONNECT2; + + hci_dev_unlock(hdev); +} + +static void hci_cs_create_logical_link(struct hci_dev *hdev, __u8 status) +{ + struct hci_cp_create_logical_link *cp; + struct hci_chan *chan; + + BT_DBG("%s status 0x%x", hdev->name, status); + + cp = hci_sent_cmd_data(hdev, HCI_OP_CREATE_LOGICAL_LINK); + if (!cp) + return; + + hci_dev_lock(hdev); + + chan = hci_chan_list_lookup_id(hdev, cp->phy_handle); + + BT_DBG("%s chan %p", hdev->name, chan); + + if (status) { + if (chan && chan->state == BT_CONNECT) { + chan->state = BT_CLOSED; + hci_proto_create_cfm(chan, status); + hci_chan_del(chan); + } + } else if (chan) + chan->state = BT_CONNECT2; + + hci_dev_unlock(hdev); +} + +static void hci_cs_flow_spec_modify(struct hci_dev *hdev, __u8 status) +{ + struct hci_cp_flow_spec_modify *cp; + struct hci_chan *chan; + + BT_DBG("%s status 0x%x", hdev->name, status); + + cp = hci_sent_cmd_data(hdev, HCI_OP_FLOW_SPEC_MODIFY); + if (!cp) + return; + + hci_dev_lock(hdev); + + chan = hci_chan_list_lookup_handle(hdev, cp->log_handle); + if (chan) { + if (status) + hci_proto_modify_cfm(chan, status); + else { + chan->tx_fs = cp->tx_fs; + chan->rx_fs = cp->rx_fs; + } + } + + hci_dev_unlock(hdev); +} + +static void hci_cs_disconn_logical_link(struct hci_dev *hdev, __u8 status) +{ + struct hci_cp_disconn_logical_link *cp; + struct hci_chan *chan; + + if (!status) + return; + + BT_DBG("%s status 0x%x", hdev->name, status); + + cp = hci_sent_cmd_data(hdev, HCI_OP_DISCONN_LOGICAL_LINK); + if (!cp) + return; + + hci_dev_lock(hdev); + + chan = hci_chan_list_lookup_handle(hdev, cp->log_handle); + if (chan) + hci_chan_del(chan); + + hci_dev_unlock(hdev); +} + +static void hci_cs_disconn_physical_link(struct hci_dev *hdev, __u8 status) +{ + struct hci_cp_disconn_phys_link *cp; + struct hci_conn *conn; + + if (!status) + return; + + BT_DBG("%s status 0x%x", hdev->name, status); + + cp = hci_sent_cmd_data(hdev, HCI_OP_DISCONN_PHYS_LINK); + if (!cp) + return; + + hci_dev_lock(hdev); + + conn = hci_conn_hash_lookup_handle(hdev, cp->phy_handle); + if (conn) { + conn->state = BT_CLOSED; + hci_conn_del(conn); + } + + hci_dev_unlock(hdev); +} + static void hci_cs_le_start_enc(struct hci_dev *hdev, u8 status) { BT_DBG("%s status 0x%x", hdev->name, status); @@ -1340,9 +1482,7 @@ static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff BT_DBG("%s status %d", hdev->name, status); - if (test_bit(HCI_MGMT, &hdev->flags) && - test_and_clear_bit(HCI_INQUIRY, &hdev->flags)) - mgmt_discovering(hdev->id, 0); + clear_bit(HCI_INQUIRY, &hdev->flags); hci_req_complete(hdev, HCI_OP_INQUIRY, status); @@ -1362,12 +1502,6 @@ static inline void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff * hci_dev_lock(hdev); - if (!test_and_set_bit(HCI_INQUIRY, &hdev->flags)) { - - if (test_bit(HCI_MGMT, &hdev->flags)) - mgmt_discovering(hdev->id, 1); - } - for (; num_rsp; num_rsp--, info++) { bacpy(&data.bdaddr, &info->bdaddr); data.pscan_rep_mode = info->pscan_rep_mode; @@ -1521,9 +1655,9 @@ static inline void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *sk cp.tx_bandwidth = cpu_to_le32(0x00001f40); cp.rx_bandwidth = cpu_to_le32(0x00001f40); - cp.max_latency = cpu_to_le16(0xffff); + cp.max_latency = cpu_to_le16(0x000A); cp.content_format = cpu_to_le16(hdev->voice_setting); - cp.retrans_effort = 0xff; + cp.retrans_effort = 0x01; hci_send_cmd(hdev, HCI_OP_ACCEPT_SYNC_CONN_REQ, sizeof(cp), &cp); @@ -1558,7 +1692,7 @@ static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff conn->state = BT_CLOSED; - if (conn->type == ACL_LINK || conn->type == LE_LINK) + if (conn->type == ACL_LINK) mgmt_disconnected(hdev->id, &conn->dst); hci_proto_disconn_cfm(conn, ev->reason); @@ -1578,58 +1712,63 @@ static inline void hci_auth_complete_evt(struct hci_dev *hdev, struct sk_buff *s hci_dev_lock(hdev); conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle)); - if (!conn) - goto unlock; + if (conn) { + if (ev->status == 0x06 && hdev->ssp_mode > 0 && + conn->ssp_mode > 0) { + struct hci_cp_auth_requested cp; + cp.handle = cpu_to_le16(conn->handle); + hci_send_cmd(conn->hdev, HCI_OP_AUTH_REQUESTED, + sizeof(cp), &cp); + hci_dev_unlock(hdev); + BT_INFO("Pin or key missing"); + return; + } - if (!ev->status) { - if (!(conn->ssp_mode > 0 && hdev->ssp_mode > 0) && - test_bit(HCI_CONN_REAUTH_PEND, &conn->pend)) { - BT_INFO("re-auth of legacy device is not possible."); - } else { + if (!ev->status) { conn->link_mode |= HCI_LM_AUTH; conn->sec_level = conn->pending_sec_level; + } else { + mgmt_auth_failed(hdev->id, &conn->dst, ev->status); + conn->sec_level = BT_SECURITY_LOW; } - } else { - mgmt_auth_failed(hdev->id, &conn->dst, ev->status); - } - clear_bit(HCI_CONN_AUTH_PEND, &conn->pend); - clear_bit(HCI_CONN_REAUTH_PEND, &conn->pend); + clear_bit(HCI_CONN_AUTH_PEND, &conn->pend); - if (conn->state == BT_CONFIG) { - if (!ev->status && hdev->ssp_mode > 0 && conn->ssp_mode > 0) { - struct hci_cp_set_conn_encrypt cp; - cp.handle = ev->handle; - cp.encrypt = 0x01; - hci_send_cmd(hdev, HCI_OP_SET_CONN_ENCRYPT, sizeof(cp), - &cp); + if (conn->state == BT_CONFIG) { + if (!ev->status && hdev->ssp_mode > 0 && + conn->ssp_mode > 0) { + struct hci_cp_set_conn_encrypt cp; + cp.handle = ev->handle; + cp.encrypt = 0x01; + hci_send_cmd(hdev, HCI_OP_SET_CONN_ENCRYPT, + sizeof(cp), &cp); + } else { + conn->state = BT_CONNECTED; + hci_proto_connect_cfm(conn, ev->status); + hci_conn_put(conn); + } } else { - conn->state = BT_CONNECTED; - hci_proto_connect_cfm(conn, ev->status); + hci_auth_cfm(conn, ev->status); + + hci_conn_hold(conn); + conn->disc_timeout = HCI_DISCONN_TIMEOUT; hci_conn_put(conn); } - } else { - hci_auth_cfm(conn, ev->status); - - hci_conn_hold(conn); - conn->disc_timeout = HCI_DISCONN_TIMEOUT; - hci_conn_put(conn); - } - if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) { - if (!ev->status) { - struct hci_cp_set_conn_encrypt cp; - cp.handle = ev->handle; - cp.encrypt = 0x01; - hci_send_cmd(hdev, HCI_OP_SET_CONN_ENCRYPT, sizeof(cp), - &cp); - } else { - clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend); - hci_encrypt_cfm(conn, ev->status, 0x00); + if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) { + if (!ev->status) { + struct hci_cp_set_conn_encrypt cp; + cp.handle = ev->handle; + cp.encrypt = 0x01; + hci_send_cmd(hdev, HCI_OP_SET_CONN_ENCRYPT, + sizeof(cp), &cp); + } else { + clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend); + hci_encrypt_cfm(conn, ev->status, 0x00); + } } } -unlock: hci_dev_unlock(hdev); } @@ -1648,19 +1787,12 @@ static inline void hci_remote_name_evt(struct hci_dev *hdev, struct sk_buff *skb mgmt_remote_name(hdev->id, &ev->bdaddr, ev->name); conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &ev->bdaddr); - if (!conn) - goto unlock; - - if (!hci_outgoing_auth_needed(hdev, conn)) - goto unlock; - - if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) { + if (conn && hci_outgoing_auth_needed(hdev, conn)) { struct hci_cp_auth_requested cp; cp.handle = __cpu_to_le16(conn->handle); hci_send_cmd(hdev, HCI_OP_AUTH_REQUESTED, sizeof(cp), &cp); } -unlock: hci_dev_unlock(hdev); } @@ -1796,6 +1928,10 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk hci_cc_exit_periodic_inq(hdev, skb); break; + case HCI_OP_LINK_KEY_REPLY: + hci_cc_link_key_reply(hdev, skb); + break; + case HCI_OP_REMOTE_NAME_REQ_CANCEL: hci_cc_remote_name_req_cancel(hdev, skb); break; @@ -1884,10 +2020,6 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk hci_cc_read_local_features(hdev, skb); break; - case HCI_OP_READ_LOCAL_EXT_FEATURES: - hci_cc_read_local_ext_features(hdev, skb); - break; - case HCI_OP_READ_BUFFER_SIZE: hci_cc_read_buffer_size(hdev, skb); break; @@ -1900,6 +2032,23 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk hci_cc_write_ca_timeout(hdev, skb); break; + case HCI_OP_READ_FLOW_CONTROL_MODE: + hci_cc_read_flow_control_mode(hdev, skb); + break; + + case HCI_OP_READ_DATA_BLOCK_SIZE: + hci_cc_read_data_block_size(hdev, skb); + break; + + case HCI_OP_READ_LOCAL_AMP_INFO: + hci_cc_read_local_amp_info(hdev, skb); + break; + + case HCI_OP_READ_LOCAL_AMP_ASSOC: + case HCI_OP_WRITE_REMOTE_AMP_ASSOC: + hci_amp_cmd_complete(hdev, opcode, skb); + break; + case HCI_OP_DELETE_STORED_LINK_KEY: hci_cc_delete_stored_link_key(hdev, skb); break; @@ -1944,10 +2093,6 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk hci_cc_user_confirm_neg_reply(hdev, skb); break; - case HCI_OP_LE_SET_SCAN_ENABLE: - hci_cc_le_set_scan_enable(hdev, skb); - break; - case HCI_OP_LE_LTK_REPLY: hci_cc_le_ltk_reply(hdev, skb); break; @@ -1956,8 +2101,8 @@ static inline void hci_cmd_complete_evt(struct hci_dev *hdev, struct sk_buff *sk hci_cc_le_ltk_neg_reply(hdev, skb); break; - case HCI_OP_WRITE_LE_HOST_SUPPORTED: - hci_cc_write_le_host_supported(hdev, skb); + case HCI_OP_LE_SET_SCAN_ENABLE: + hci_cc_le_set_scan_enable(hdev, skb); break; default: @@ -2029,6 +2174,30 @@ static inline void hci_cmd_status_evt(struct hci_dev *hdev, struct sk_buff *skb) hci_cs_exit_sniff_mode(hdev, ev->status); break; + case HCI_OP_CREATE_LOGICAL_LINK: + hci_cs_create_logical_link(hdev, ev->status); + break; + + case HCI_OP_ACCEPT_LOGICAL_LINK: + hci_cs_accept_logical_link(hdev, ev->status); + break; + + case HCI_OP_DISCONN_LOGICAL_LINK: + hci_cs_disconn_logical_link(hdev, ev->status); + break; + + case HCI_OP_FLOW_SPEC_MODIFY: + hci_cs_flow_spec_modify(hdev, ev->status); + break; + + case HCI_OP_CREATE_PHYS_LINK: + case HCI_OP_ACCEPT_PHYS_LINK: + hci_amp_cmd_status(hdev, opcode, ev->status); + break; + + case HCI_OP_DISCONN_PHYS_LINK: + hci_cs_disconn_physical_link(hdev, ev->status); + case HCI_OP_DISCONNECT: if (ev->status != 0) mgmt_disconnect_failed(hdev->id); @@ -2101,13 +2270,20 @@ static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *s tasklet_disable(&hdev->tx_task); for (i = 0, ptr = (__le16 *) skb->data; i < ev->num_hndl; i++) { - struct hci_conn *conn; + struct hci_conn *conn = NULL; + struct hci_chan *chan; __u16 handle, count; handle = get_unaligned_le16(ptr++); count = get_unaligned_le16(ptr++); - conn = hci_conn_hash_lookup_handle(hdev, handle); + if (hdev->dev_type == HCI_BREDR) + conn = hci_conn_hash_lookup_handle(hdev, handle); + else { + chan = hci_chan_list_lookup_handle(hdev, handle); + if (chan) + conn = chan->conn; + } if (conn) { conn->sent -= count; @@ -2138,6 +2314,68 @@ static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *s tasklet_enable(&hdev->tx_task); } +static inline void hci_num_comp_blocks_evt(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_num_comp_blocks *ev = (void *) skb->data; + __le16 *ptr; + int i; + + skb_pull(skb, sizeof(*ev)); + + BT_DBG("%s total_num_blocks %d num_hndl %d", + hdev->name, ev->total_num_blocks, ev->num_hndl); + + if (skb->len < ev->num_hndl * 6) { + BT_DBG("%s bad parameters", hdev->name); + return; + } + + tasklet_disable(&hdev->tx_task); + + for (i = 0, ptr = (__le16 *) skb->data; i < ev->num_hndl; i++) { + struct hci_conn *conn = NULL; + struct hci_chan *chan; + __u16 handle, block_count; + + handle = get_unaligned_le16(ptr++); + + /* Skip packet count */ + ptr++; + block_count = get_unaligned_le16(ptr++); + + BT_DBG("%s handle %d count %d", hdev->name, handle, + block_count); + + if (hdev->dev_type == HCI_BREDR) + conn = hci_conn_hash_lookup_handle(hdev, handle); + else { + chan = hci_chan_list_lookup_handle(hdev, handle); + if (chan) + conn = chan->conn; + } + if (conn) { + BT_DBG("%s conn %p sent %d", hdev->name, + conn, conn->sent); + + conn->sent -= block_count; + + if (conn->type == ACL_LINK) { + hdev->acl_cnt += block_count; + if (hdev->acl_cnt > hdev->acl_pkts) + hdev->acl_cnt = hdev->acl_pkts; + } else { + /* We should not find ourselves here */ + BT_DBG("Unexpected event for SCO connection"); + } + } + } + + tasklet_schedule(&hdev->tx_task); + + tasklet_enable(&hdev->tx_task); +} + static inline void hci_mode_change_evt(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_ev_mode_change *ev = (void *) skb->data; @@ -2185,16 +2423,9 @@ static inline void hci_pin_code_request_evt(struct hci_dev *hdev, struct sk_buff if (!test_bit(HCI_PAIRABLE, &hdev->flags)) hci_send_cmd(hdev, HCI_OP_PIN_CODE_NEG_REPLY, sizeof(ev->bdaddr), &ev->bdaddr); - else if (test_bit(HCI_MGMT, &hdev->flags)) { - u8 secure; - - if (conn->pending_sec_level == BT_SECURITY_HIGH) - secure = 1; - else - secure = 0; - mgmt_pin_code_request(hdev->id, &ev->bdaddr, secure); - } + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_pin_code_request(hdev->id, &ev->bdaddr); hci_dev_unlock(hdev); } @@ -2223,30 +2454,17 @@ static inline void hci_link_key_request_evt(struct hci_dev *hdev, struct sk_buff BT_DBG("%s found key type %u for %s", hdev->name, key->type, batostr(&ev->bdaddr)); - if (!test_bit(HCI_DEBUG_KEYS, &hdev->flags) && - key->type == HCI_LK_DEBUG_COMBINATION) { + if (!test_bit(HCI_DEBUG_KEYS, &hdev->flags) && key->type == 0x03) { BT_DBG("%s ignoring debug key", hdev->name); goto not_found; } conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &ev->bdaddr); - if (conn) { - if (key->type == HCI_LK_UNAUTH_COMBINATION && - conn->auth_type != 0xff && - (conn->auth_type & 0x01)) { - BT_DBG("%s ignoring unauthenticated key", hdev->name); - goto not_found; - } - if (key->type == HCI_LK_COMBINATION && key->pin_len < 16 && - conn->pending_sec_level == BT_SECURITY_HIGH) { - BT_DBG("%s ignoring key unauthenticated for high \ - security", hdev->name); - goto not_found; - } - - conn->key_type = key->type; - conn->pin_length = key->pin_len; + if (key->type == 0x04 && conn && conn->auth_type != 0xff && + (conn->auth_type & 0x01)) { + BT_DBG("%s ignoring unauthenticated key", hdev->name); + goto not_found; } bacpy(&cp.bdaddr, &ev->bdaddr); @@ -2269,7 +2487,7 @@ static inline void hci_link_key_notify_evt(struct hci_dev *hdev, struct sk_buff struct hci_conn *conn; u8 pin_len = 0; - BT_DBG("%s", hdev->name); + BT_DBG("%s type %d", hdev->name, ev->key_type); hci_dev_lock(hdev); @@ -2277,16 +2495,17 @@ static inline void hci_link_key_notify_evt(struct hci_dev *hdev, struct sk_buff if (conn) { hci_conn_hold(conn); conn->disc_timeout = HCI_DISCONN_TIMEOUT; - pin_len = conn->pin_length; - if (ev->key_type != HCI_LK_CHANGED_COMBINATION) - conn->key_type = ev->key_type; + memcpy(conn->link_key, ev->link_key, 16); + conn->key_type = ev->key_type; + hci_disconnect_amp(conn, 0x06); + pin_len = conn->pin_length; hci_conn_put(conn); } if (test_bit(HCI_LINK_KEYS, &hdev->flags)) - hci_add_link_key(hdev, conn, 1, &ev->bdaddr, ev->link_key, + hci_add_link_key(hdev, 1, &ev->bdaddr, ev->link_key, ev->key_type, pin_len); hci_dev_unlock(hdev); @@ -2361,12 +2580,6 @@ static inline void hci_inquiry_result_with_rssi_evt(struct hci_dev *hdev, struct hci_dev_lock(hdev); - if (!test_and_set_bit(HCI_INQUIRY, &hdev->flags)) { - - if (test_bit(HCI_MGMT, &hdev->flags)) - mgmt_discovering(hdev->id, 1); - } - if ((skb->len - 1) / num_rsp != sizeof(struct inquiry_info_with_rssi)) { struct inquiry_info_with_rssi_and_pscan_mode *info; info = (void *) (skb->data + 1); @@ -2481,7 +2694,6 @@ static inline void hci_sync_conn_complete_evt(struct hci_dev *hdev, struct sk_bu hci_conn_add_sysfs(conn); break; - case 0x10: /* Connection Accept Timeout */ case 0x11: /* Unsupported Feature or Parameter Value */ case 0x1c: /* SCO interval rejected */ case 0x1a: /* Unsupported Remote Feature */ @@ -2530,12 +2742,6 @@ static inline void hci_extended_inquiry_result_evt(struct hci_dev *hdev, struct if (!num_rsp) return; - if (!test_and_set_bit(HCI_INQUIRY, &hdev->flags)) { - - if (test_bit(HCI_MGMT, &hdev->flags)) - mgmt_discovering(hdev->id, 1); - } - hci_dev_lock(hdev); for (; num_rsp; num_rsp--, info++) { @@ -2569,7 +2775,7 @@ static inline u8 hci_get_auth_req(struct hci_conn *conn) /* If remote requests no-bonding follow that lead */ if (conn->remote_auth == 0x00 || conn->remote_auth == 0x01) - return conn->remote_auth | (conn->auth_type & 0x01); + return 0x00; return conn->auth_type; } @@ -2598,8 +2804,7 @@ static inline void hci_io_capa_request_evt(struct hci_dev *hdev, struct sk_buff bacpy(&cp.bdaddr, &ev->bdaddr); cp.capability = conn->io_capability; - conn->auth_type = hci_get_auth_req(conn); - cp.authentication = conn->auth_type; + cp.authentication = hci_get_auth_req(conn); if ((conn->out == 0x01 || conn->remote_oob == 0x01) && hci_find_remote_oob_data(hdev, &conn->dst)) @@ -2613,7 +2818,7 @@ static inline void hci_io_capa_request_evt(struct hci_dev *hdev, struct sk_buff struct hci_cp_io_capability_neg_reply cp; bacpy(&cp.bdaddr, &ev->bdaddr); - cp.reason = 0x18; /* Pairing not allowed */ + cp.reason = 0x16; /* Pairing not allowed */ hci_send_cmd(hdev, HCI_OP_IO_CAPABILITY_NEG_REPLY, sizeof(cp), &cp); @@ -2648,67 +2853,14 @@ static inline void hci_user_confirm_request_evt(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_ev_user_confirm_req *ev = (void *) skb->data; - int loc_mitm, rem_mitm, confirm_hint = 0; - struct hci_conn *conn; BT_DBG("%s", hdev->name); hci_dev_lock(hdev); - if (!test_bit(HCI_MGMT, &hdev->flags)) - goto unlock; - - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &ev->bdaddr); - if (!conn) - goto unlock; - - loc_mitm = (conn->auth_type & 0x01); - rem_mitm = (conn->remote_auth & 0x01); - - /* If we require MITM but the remote device can't provide that - * (it has NoInputNoOutput) then reject the confirmation - * request. The only exception is when we're dedicated bonding - * initiators (connect_cfm_cb set) since then we always have the MITM - * bit set. */ - if (!conn->connect_cfm_cb && loc_mitm && conn->remote_cap == 0x03) { - BT_DBG("Rejecting request: remote device can't provide MITM"); - hci_send_cmd(hdev, HCI_OP_USER_CONFIRM_NEG_REPLY, - sizeof(ev->bdaddr), &ev->bdaddr); - goto unlock; - } - - /* If no side requires MITM protection; auto-accept */ - if ((!loc_mitm || conn->remote_cap == 0x03) && - (!rem_mitm || conn->io_capability == 0x03)) { - - /* If we're not the initiators request authorization to - * proceed from user space (mgmt_user_confirm with - * confirm_hint set to 1). */ - if (!test_bit(HCI_CONN_AUTH_PEND, &conn->pend)) { - BT_DBG("Confirming auto-accept as acceptor"); - confirm_hint = 1; - goto confirm; - } - - BT_DBG("Auto-accept of user confirmation with %ums delay", - hdev->auto_accept_delay); - - if (hdev->auto_accept_delay > 0) { - int delay = msecs_to_jiffies(hdev->auto_accept_delay); - mod_timer(&conn->auto_accept_timer, jiffies + delay); - goto unlock; - } - - hci_send_cmd(hdev, HCI_OP_USER_CONFIRM_REPLY, - sizeof(ev->bdaddr), &ev->bdaddr); - goto unlock; - } - -confirm: - mgmt_user_confirm_request(hdev->id, &ev->bdaddr, ev->passkey, - confirm_hint); + if (test_bit(HCI_MGMT, &hdev->flags)) + mgmt_user_confirm_request(hdev->id, &ev->bdaddr, ev->passkey); -unlock: hci_dev_unlock(hdev); } @@ -2801,26 +2953,21 @@ static inline void hci_le_conn_complete_evt(struct hci_dev *hdev, struct sk_buff conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &ev->bdaddr); if (!conn) { - conn = hci_conn_add(hdev, LE_LINK, 0, &ev->bdaddr); + conn = hci_le_conn_add(hdev, &ev->bdaddr, ev->bdaddr_type); if (!conn) { BT_ERR("No memory for new connection"); hci_dev_unlock(hdev); return; } - - conn->dst_type = ev->bdaddr_type; } if (ev->status) { - mgmt_connect_failed(hdev->id, &ev->bdaddr, ev->status); hci_proto_connect_cfm(conn, ev->status); conn->state = BT_CLOSED; hci_conn_del(conn); goto unlock; } - mgmt_connected(hdev->id, &ev->bdaddr); - conn->sec_level = BT_SECURITY_LOW; conn->handle = __le16_to_cpu(ev->handle); conn->state = BT_CONNECTED; @@ -2834,27 +2981,6 @@ static inline void hci_le_conn_complete_evt(struct hci_dev *hdev, struct sk_buff hci_dev_unlock(hdev); } -static inline void hci_le_adv_report_evt(struct hci_dev *hdev, - struct sk_buff *skb) -{ - struct hci_ev_le_advertising_info *ev; - u8 num_reports; - - num_reports = skb->data[0]; - ev = (void *) &skb->data[1]; - - hci_dev_lock(hdev); - - hci_add_adv_entry(hdev, ev); - - while (--num_reports) { - ev = (void *) (ev->data + ev->length + 1); - hci_add_adv_entry(hdev, ev); - } - - hci_dev_unlock(hdev); -} - static inline void hci_le_ltk_request_evt(struct hci_dev *hdev, struct sk_buff *skb) { @@ -2892,6 +3018,22 @@ static inline void hci_le_ltk_request_evt(struct hci_dev *hdev, hci_dev_unlock(hdev); } +static inline void hci_le_adv_report_evt(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_le_advertising_info *ev; + u8 num_reports; + + num_reports = skb->data[0]; + ev = (void *) &skb->data[1]; + hci_add_adv_entry(hdev, ev); + + while (--num_reports) { + ev = (void *) (ev->data + ev->length + 1); + hci_add_adv_entry(hdev, ev); + } +} + static inline void hci_le_meta_evt(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_ev_le_meta *le_ev = (void *) skb->data; @@ -2903,19 +3045,145 @@ static inline void hci_le_meta_evt(struct hci_dev *hdev, struct sk_buff *skb) hci_le_conn_complete_evt(hdev, skb); break; - case HCI_EV_LE_ADVERTISING_REPORT: - hci_le_adv_report_evt(hdev, skb); - break; - case HCI_EV_LE_LTK_REQ: hci_le_ltk_request_evt(hdev, skb); break; + case HCI_EV_LE_ADVERTISING_REPORT: + hci_le_adv_report_evt(hdev, skb); + break; + default: break; } } +static inline void hci_phy_link_complete(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_phys_link_complete *ev = (void *) skb->data; + struct hci_conn *conn; + + BT_DBG("%s handle %d status %d", hdev->name, ev->phy_handle, + ev->status); + + hci_dev_lock(hdev); + + if (ev->status == 0) { + conn = hci_conn_add(hdev, ACL_LINK, 0, BDADDR_ANY); + if (conn) { + conn->handle = ev->phy_handle; + conn->state = BT_CONNECTED; + + hci_conn_hold(conn); + conn->disc_timeout = HCI_DISCONN_TIMEOUT/2; + hci_conn_put(conn); + + hci_conn_hold_device(conn); + hci_conn_add_sysfs(conn); + } else + BT_ERR("No memory for new connection"); + } + + hci_dev_unlock(hdev); +} + +static inline void hci_log_link_complete(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_log_link_complete *ev = (void *) skb->data; + struct hci_chan *chan; + + BT_DBG("%s handle %d status %d", hdev->name, + __le16_to_cpu(ev->log_handle), ev->status); + + hci_dev_lock(hdev); + + chan = hci_chan_list_lookup_id(hdev, ev->phy_handle); + + if (ev->status == 0) { + if (chan) { + chan->ll_handle = __le16_to_cpu(ev->log_handle); + chan->state = BT_CONNECTED; + hci_proto_create_cfm(chan, ev->status); + hci_chan_hold(chan); + } + } else { + if (chan) { + chan->state = BT_CLOSED; + hci_proto_create_cfm(chan, ev->status); + hci_chan_del(chan); + } + } + + hci_dev_unlock(hdev); +} + +static inline void hci_flow_spec_modify_complete(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_flow_spec_modify_complete *ev = (void *) skb->data; + struct hci_chan *chan; + + BT_DBG("%s handle %d status %d", hdev->name, + __le16_to_cpu(ev->log_handle), ev->status); + + hci_dev_lock(hdev); + + chan = hci_chan_list_lookup_handle(hdev, ev->log_handle); + if (chan) + hci_proto_modify_cfm(chan, ev->status); + + hci_dev_unlock(hdev); +} + +static inline void hci_disconn_log_link_complete_evt(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_disconn_log_link_complete *ev = (void *) skb->data; + struct hci_chan *chan; + + BT_DBG("%s handle %d status %d", hdev->name, + __le16_to_cpu(ev->log_handle), ev->status); + + if (ev->status) + return; + + hci_dev_lock(hdev); + + chan = hci_chan_list_lookup_handle(hdev, __le16_to_cpu(ev->log_handle)); + if (chan) { + hci_proto_destroy_cfm(chan, ev->reason); + hci_chan_del(chan); + } + + hci_dev_unlock(hdev); +} + +static inline void hci_disconn_phy_link_complete_evt(struct hci_dev *hdev, + struct sk_buff *skb) +{ + struct hci_ev_disconn_phys_link_complete *ev = (void *) skb->data; + struct hci_conn *conn; + + BT_DBG("%s status %d", hdev->name, ev->status); + + if (ev->status) + return; + + hci_dev_lock(hdev); + + conn = hci_conn_hash_lookup_handle(hdev, ev->phy_handle); + if (conn) { + conn->state = BT_CLOSED; + + hci_proto_disconn_cfm(conn, ev->reason); + hci_conn_del(conn); + } + + hci_dev_unlock(hdev); +} + void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_event_hdr *hdr = (void *) skb->data; @@ -3068,6 +3336,40 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb) hci_remote_oob_data_request_evt(hdev, skb); break; + case HCI_EV_PHYS_LINK_COMPLETE: + hci_phy_link_complete(hdev, skb); + hci_amp_event_packet(hdev, event, skb); + break; + + case HCI_EV_LOG_LINK_COMPLETE: + hci_log_link_complete(hdev, skb); + break; + + case HCI_EV_FLOW_SPEC_MODIFY_COMPLETE: + hci_flow_spec_modify_complete(hdev, skb); + break; + + case HCI_EV_DISCONN_LOG_LINK_COMPLETE: + hci_disconn_log_link_complete_evt(hdev, skb); + break; + + case HCI_EV_DISCONN_PHYS_LINK_COMPLETE: + hci_disconn_phy_link_complete_evt(hdev, skb); + hci_amp_event_packet(hdev, event, skb); + break; + + case HCI_EV_NUM_COMP_BLOCKS: + hci_num_comp_blocks_evt(hdev, skb); + break; + + case HCI_EV_CHANNEL_SELECTED: + hci_amp_event_packet(hdev, event, skb); + break; + + case HCI_EV_AMP_STATUS_CHANGE: + hci_amp_event_packet(hdev, event, skb); + break; + default: BT_DBG("%s event 0x%x", hdev->name, event); break; @@ -3104,6 +3406,3 @@ void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data) hci_send_to_sock(hdev, skb, NULL); kfree_skb(skb); } - -module_param(enable_le, bool, 0444); -MODULE_PARM_DESC(enable_le, "Enable LE support"); diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index ff02cf5e77c..00469456b64 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -1,6 +1,6 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2000-2001, 2011, Code Aurora Forum. All rights reserved. Written 2000,2001 by Maxim Krasnyansky @@ -180,24 +180,82 @@ static int hci_sock_release(struct socket *sock) return 0; } -static int hci_sock_blacklist_add(struct hci_dev *hdev, void __user *arg) +struct bdaddr_list *hci_blacklist_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr) +{ + struct list_head *p; + + list_for_each(p, &hdev->blacklist) { + struct bdaddr_list *b; + + b = list_entry(p, struct bdaddr_list, list); + + if (bacmp(bdaddr, &b->bdaddr) == 0) + return b; + } + + return NULL; +} + +static int hci_blacklist_add(struct hci_dev *hdev, void __user *arg) { bdaddr_t bdaddr; + struct bdaddr_list *entry; if (copy_from_user(&bdaddr, arg, sizeof(bdaddr))) return -EFAULT; - return hci_blacklist_add(hdev, &bdaddr); + if (bacmp(&bdaddr, BDADDR_ANY) == 0) + return -EBADF; + + if (hci_blacklist_lookup(hdev, &bdaddr)) + return -EEXIST; + + entry = kzalloc(sizeof(struct bdaddr_list), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + bacpy(&entry->bdaddr, &bdaddr); + + list_add(&entry->list, &hdev->blacklist); + + return 0; } -static int hci_sock_blacklist_del(struct hci_dev *hdev, void __user *arg) +int hci_blacklist_clear(struct hci_dev *hdev) +{ + struct list_head *p, *n; + + list_for_each_safe(p, n, &hdev->blacklist) { + struct bdaddr_list *b; + + b = list_entry(p, struct bdaddr_list, list); + + list_del(p); + kfree(b); + } + + return 0; +} + +static int hci_blacklist_del(struct hci_dev *hdev, void __user *arg) { bdaddr_t bdaddr; + struct bdaddr_list *entry; if (copy_from_user(&bdaddr, arg, sizeof(bdaddr))) return -EFAULT; - return hci_blacklist_del(hdev, &bdaddr); + if (bacmp(&bdaddr, BDADDR_ANY) == 0) + return hci_blacklist_clear(hdev); + + entry = hci_blacklist_lookup(hdev, &bdaddr); + if (!entry) + return -ENOENT; + + list_del(&entry->list); + kfree(entry); + + return 0; } /* Ioctls that require bound socket */ @@ -232,12 +290,15 @@ static inline int hci_sock_bound_ioctl(struct sock *sk, unsigned int cmd, unsign case HCIBLOCKADDR: if (!capable(CAP_NET_ADMIN)) return -EACCES; - return hci_sock_blacklist_add(hdev, (void __user *) arg); + return hci_blacklist_add(hdev, (void __user *) arg); case HCIUNBLOCKADDR: if (!capable(CAP_NET_ADMIN)) return -EACCES; - return hci_sock_blacklist_del(hdev, (void __user *) arg); + return hci_blacklist_del(hdev, (void __user *) arg); + + case HCISETAUTHINFO: + return hci_set_auth_info(hdev, (void __user *) arg); default: if (hdev->ioctl) diff --git a/net/bluetooth/hci_sysfs.c b/net/bluetooth/hci_sysfs.c index a6c3aa8be1f..8775933ea83 100644 --- a/net/bluetooth/hci_sysfs.c +++ b/net/bluetooth/hci_sysfs.c @@ -511,35 +511,6 @@ static const struct file_operations uuids_fops = { .release = single_release, }; -static int auto_accept_delay_set(void *data, u64 val) -{ - struct hci_dev *hdev = data; - - hci_dev_lock_bh(hdev); - - hdev->auto_accept_delay = val; - - hci_dev_unlock_bh(hdev); - - return 0; -} - -static int auto_accept_delay_get(void *data, u64 *val) -{ - struct hci_dev *hdev = data; - - hci_dev_lock_bh(hdev); - - *val = hdev->auto_accept_delay; - - hci_dev_unlock_bh(hdev); - - return 0; -} - -DEFINE_SIMPLE_ATTRIBUTE(auto_accept_delay_fops, auto_accept_delay_get, - auto_accept_delay_set, "%llu\n"); - int hci_register_sysfs(struct hci_dev *hdev) { struct device *dev = &hdev->dev; @@ -574,8 +545,6 @@ int hci_register_sysfs(struct hci_dev *hdev) debugfs_create_file("uuids", 0444, hdev->debugfs, hdev, &uuids_fops); - debugfs_create_file("auto_accept_delay", 0444, hdev->debugfs, hdev, - &auto_accept_delay_fops); return 0; } diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index fb68f344c34..04f77842eb1 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -36,8 +36,6 @@ #include #include #include -#include -#include #include #include @@ -320,144 +318,24 @@ static int hidp_send_report(struct hidp_session *session, struct hid_report *rep return hidp_queue_report(session, buf, rsize); } -static int hidp_get_raw_report(struct hid_device *hid, - unsigned char report_number, - unsigned char *data, size_t count, - unsigned char report_type) -{ - struct hidp_session *session = hid->driver_data; - struct sk_buff *skb; - size_t len; - int numbered_reports = hid->report_enum[report_type].numbered; - - switch (report_type) { - case HID_FEATURE_REPORT: - report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_FEATURE; - break; - case HID_INPUT_REPORT: - report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_INPUT; - break; - case HID_OUTPUT_REPORT: - report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_OUPUT; - break; - default: - return -EINVAL; - } - - if (mutex_lock_interruptible(&session->report_mutex)) - return -ERESTARTSYS; - - /* Set up our wait, and send the report request to the device. */ - session->waiting_report_type = report_type & HIDP_DATA_RTYPE_MASK; - session->waiting_report_number = numbered_reports ? report_number : -1; - set_bit(HIDP_WAITING_FOR_RETURN, &session->flags); - data[0] = report_number; - if (hidp_send_ctrl_message(hid->driver_data, report_type, data, 1)) - goto err_eio; - - /* Wait for the return of the report. The returned report - gets put in session->report_return. */ - while (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags)) { - int res; - - res = wait_event_interruptible_timeout(session->report_queue, - !test_bit(HIDP_WAITING_FOR_RETURN, &session->flags), - 5*HZ); - if (res == 0) { - /* timeout */ - goto err_eio; - } - if (res < 0) { - /* signal */ - goto err_restartsys; - } - } - - skb = session->report_return; - if (skb) { - len = skb->len < count ? skb->len : count; - memcpy(data, skb->data, len); - - kfree_skb(skb); - session->report_return = NULL; - } else { - /* Device returned a HANDSHAKE, indicating protocol error. */ - len = -EIO; - } - - clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); - mutex_unlock(&session->report_mutex); - - return len; - -err_restartsys: - clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); - mutex_unlock(&session->report_mutex); - return -ERESTARTSYS; -err_eio: - clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); - mutex_unlock(&session->report_mutex); - return -EIO; -} - static int hidp_output_raw_report(struct hid_device *hid, unsigned char *data, size_t count, unsigned char report_type) { - struct hidp_session *session = hid->driver_data; - int ret; - switch (report_type) { case HID_FEATURE_REPORT: report_type = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE; break; case HID_OUTPUT_REPORT: - report_type = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_OUPUT; + report_type = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT; break; default: return -EINVAL; } - if (mutex_lock_interruptible(&session->report_mutex)) - return -ERESTARTSYS; - - /* Set up our wait, and send the report request to the device. */ - set_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags); if (hidp_send_ctrl_message(hid->driver_data, report_type, - data, count)) { - ret = -ENOMEM; - goto err; - } - - /* Wait for the ACK from the device. */ - while (test_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags)) { - int res; - - res = wait_event_interruptible_timeout(session->report_queue, - !test_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags), - 10*HZ); - if (res == 0) { - /* timeout */ - ret = -EIO; - goto err; - } - if (res < 0) { - /* signal */ - ret = -ERESTARTSYS; - goto err; - } - } - - if (!session->output_report_success) { - ret = -EIO; - goto err; - } - - ret = count; - -err: - clear_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags); - mutex_unlock(&session->report_mutex); - return ret; + data, count)) + return -ENOMEM; + return count; } static void hidp_idle_timeout(unsigned long arg) @@ -465,7 +343,7 @@ static void hidp_idle_timeout(unsigned long arg) struct hidp_session *session = (struct hidp_session *) arg; atomic_inc(&session->terminate); - wake_up_process(session->task); + hidp_schedule(session); } static void hidp_set_timer(struct hidp_session *session) @@ -484,22 +362,16 @@ static void hidp_process_handshake(struct hidp_session *session, unsigned char param) { BT_DBG("session %p param 0x%02x", session, param); - session->output_report_success = 0; /* default condition */ switch (param) { case HIDP_HSHK_SUCCESSFUL: /* FIXME: Call into SET_ GET_ handlers here */ - session->output_report_success = 1; break; case HIDP_HSHK_NOT_READY: case HIDP_HSHK_ERR_INVALID_REPORT_ID: case HIDP_HSHK_ERR_UNSUPPORTED_REQUEST: case HIDP_HSHK_ERR_INVALID_PARAMETER: - if (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags)) { - clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); - wake_up_interruptible(&session->report_queue); - } /* FIXME: Call into SET_ GET_ handlers here */ break; @@ -518,12 +390,6 @@ static void hidp_process_handshake(struct hidp_session *session, HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0); break; } - - /* Wake up the waiting thread. */ - if (test_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags)) { - clear_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags); - wake_up_interruptible(&session->report_queue); - } } static void hidp_process_hid_control(struct hidp_session *session, @@ -536,16 +402,15 @@ static void hidp_process_hid_control(struct hidp_session *session, skb_queue_purge(&session->ctrl_transmit); skb_queue_purge(&session->intr_transmit); + /* Kill session thread */ atomic_inc(&session->terminate); - wake_up_process(current); + hidp_schedule(session); } } -/* Returns true if the passed-in skb should be freed by the caller. */ -static int hidp_process_data(struct hidp_session *session, struct sk_buff *skb, +static void hidp_process_data(struct hidp_session *session, struct sk_buff *skb, unsigned char param) { - int done_with_skb = 1; BT_DBG("session %p skb %p len %d param 0x%02x", session, skb, skb->len, param); switch (param) { @@ -557,6 +422,7 @@ static int hidp_process_data(struct hidp_session *session, struct sk_buff *skb, if (session->hid) hid_input_report(session->hid, HID_INPUT_REPORT, skb->data, skb->len, 0); + break; case HIDP_DATA_RTYPE_OTHER: @@ -568,27 +434,12 @@ static int hidp_process_data(struct hidp_session *session, struct sk_buff *skb, __hidp_send_ctrl_message(session, HIDP_TRANS_HANDSHAKE | HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0); } - - if (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags) && - param == session->waiting_report_type) { - if (session->waiting_report_number < 0 || - session->waiting_report_number == skb->data[0]) { - /* hidp_get_raw_report() is waiting on this report. */ - session->report_return = skb; - done_with_skb = 0; - clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags); - wake_up_interruptible(&session->report_queue); - } - } - - return done_with_skb; } static void hidp_recv_ctrl_frame(struct hidp_session *session, struct sk_buff *skb) { unsigned char hdr, type, param; - int free_skb = 1; BT_DBG("session %p skb %p len %d", session, skb, skb->len); @@ -608,7 +459,7 @@ static void hidp_recv_ctrl_frame(struct hidp_session *session, break; case HIDP_TRANS_DATA: - free_skb = hidp_process_data(session, skb, param); + hidp_process_data(session, skb, param); break; default: @@ -617,8 +468,7 @@ static void hidp_recv_ctrl_frame(struct hidp_session *session, break; } - if (free_skb) - kfree_skb(skb); + kfree_skb(skb); } static void hidp_recv_intr_frame(struct hidp_session *session, @@ -696,38 +546,54 @@ static int hidp_session(void *arg) struct sock *ctrl_sk = session->ctrl_sock->sk; struct sock *intr_sk = session->intr_sock->sk; struct sk_buff *skb; + int vendor = 0x0000, product = 0x0000; wait_queue_t ctrl_wait, intr_wait; BT_DBG("session %p", session); + if (session->input) { + vendor = session->input->id.vendor; + product = session->input->id.product; + } + + if (session->hid) { + vendor = session->hid->vendor; + product = session->hid->product; + } + + daemonize("khidpd_%04x%04x", vendor, product); set_user_nice(current, -15); init_waitqueue_entry(&ctrl_wait, current); init_waitqueue_entry(&intr_wait, current); add_wait_queue(sk_sleep(ctrl_sk), &ctrl_wait); add_wait_queue(sk_sleep(intr_sk), &intr_wait); - session->waiting_for_startup = 0; - wake_up_interruptible(&session->startup_queue); - set_current_state(TASK_INTERRUPTIBLE); while (!atomic_read(&session->terminate)) { + set_current_state(TASK_INTERRUPTIBLE); + if (ctrl_sk->sk_state != BT_CONNECTED || intr_sk->sk_state != BT_CONNECTED) break; while ((skb = skb_dequeue(&ctrl_sk->sk_receive_queue))) { skb_orphan(skb); - hidp_recv_ctrl_frame(session, skb); + if (!skb_linearize(skb)) + hidp_recv_ctrl_frame(session, skb); + else + kfree_skb(skb); } while ((skb = skb_dequeue(&intr_sk->sk_receive_queue))) { skb_orphan(skb); - hidp_recv_intr_frame(session, skb); + if (!skb_linearize(skb)) + hidp_recv_intr_frame(session, skb); + else + kfree_skb(skb); } hidp_process_transmit(session); schedule(); - set_current_state(TASK_INTERRUPTIBLE); } set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(intr_sk), &intr_wait); @@ -764,7 +630,6 @@ static int hidp_session(void *arg) up_write(&hidp_session_sem); - kfree(session->rd_data); kfree(session); return 0; } @@ -842,8 +707,7 @@ static int hidp_setup_input(struct hidp_session *session, err = input_register_device(input); if (err < 0) { - input_free_device(input); - session->input = NULL; + hci_conn_put_device(session->conn); return err; } @@ -902,8 +766,6 @@ static struct hid_ll_driver hidp_hid_driver = { .hidinput_input_event = hidp_hidinput_event, }; -/* This function sets up the hid device. It does not add it - to the HID system. That is done in hidp_add_connection(). */ static int hidp_setup_hid(struct hidp_session *session, struct hidp_connadd_req *req) { @@ -943,11 +805,18 @@ static int hidp_setup_hid(struct hidp_session *session, hid->dev.parent = hidp_get_device(session); hid->ll_driver = &hidp_hid_driver; - hid->hid_get_raw_report = hidp_get_raw_report; hid->hid_output_raw_report = hidp_output_raw_report; + err = hid_add_device(hid); + if (err < 0) + goto failed; + return 0; +failed: + hid_destroy_device(hid); + session->hid = NULL; + fault: kfree(session->rd_data); session->rd_data = NULL; @@ -958,7 +827,6 @@ static int hidp_setup_hid(struct hidp_session *session, int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, struct socket *intr_sock) { struct hidp_session *session, *s; - int vendor, product; int err; BT_DBG(""); @@ -983,10 +851,8 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, bacpy(&session->bdaddr, &bt_sk(ctrl_sock->sk)->dst); - session->ctrl_mtu = min_t(uint, l2cap_pi(ctrl_sock->sk)->chan->omtu, - l2cap_pi(ctrl_sock->sk)->chan->imtu); - session->intr_mtu = min_t(uint, l2cap_pi(intr_sock->sk)->chan->omtu, - l2cap_pi(intr_sock->sk)->chan->imtu); + session->ctrl_mtu = min_t(uint, l2cap_pi(ctrl_sock->sk)->omtu, l2cap_pi(ctrl_sock->sk)->imtu); + session->intr_mtu = min_t(uint, l2cap_pi(intr_sock->sk)->omtu, l2cap_pi(intr_sock->sk)->imtu); BT_DBG("ctrl mtu %d intr mtu %d", session->ctrl_mtu, session->intr_mtu); @@ -999,10 +865,6 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, skb_queue_head_init(&session->ctrl_transmit); skb_queue_head_init(&session->intr_transmit); - mutex_init(&session->report_mutex); - init_waitqueue_head(&session->report_queue); - init_waitqueue_head(&session->startup_queue); - session->waiting_for_startup = 1; session->flags = req->flags & (1 << HIDP_BLUETOOTH_VENDOR_ID); session->idle_to = req->idle_to; @@ -1022,36 +884,9 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, hidp_set_timer(session); - if (session->hid) { - vendor = session->hid->vendor; - product = session->hid->product; - } else if (session->input) { - vendor = session->input->id.vendor; - product = session->input->id.product; - } else { - vendor = 0x0000; - product = 0x0000; - } - - session->task = kthread_run(hidp_session, session, "khidpd_%04x%04x", - vendor, product); - if (IS_ERR(session->task)) { - err = PTR_ERR(session->task); + err = kernel_thread(hidp_session, session, CLONE_KERNEL); + if (err < 0) goto unlink; - } - - while (session->waiting_for_startup) { - wait_event_interruptible(session->startup_queue, - !session->waiting_for_startup); - } - - err = hid_add_device(session->hid); - if (err < 0) { - atomic_inc(&session->terminate); - wake_up_process(session->task); - up_write(&hidp_session_sem); - return err; - } if (session->input) { hidp_send_ctrl_message(session, @@ -1090,6 +925,7 @@ int hidp_add_connection(struct hidp_connadd_req *req, struct socket *ctrl_sock, failed: up_write(&hidp_session_sem); + input_free_device(session->input); kfree(session); return err; } @@ -1113,8 +949,13 @@ int hidp_del_connection(struct hidp_conndel_req *req) skb_queue_purge(&session->ctrl_transmit); skb_queue_purge(&session->intr_transmit); + /* Wakeup user-space polling for socket errors */ + session->intr_sock->sk->sk_err = EUNATCH; + session->ctrl_sock->sk->sk_err = EUNATCH; + + /* Kill session thread */ atomic_inc(&session->terminate); - wake_up_process(session->task); + hidp_schedule(session); } } else err = -ENOENT; diff --git a/net/bluetooth/hidp/hidp.h b/net/bluetooth/hidp/hidp.h index af1bcc823f2..28bb9ce40bf 100644 --- a/net/bluetooth/hidp/hidp.h +++ b/net/bluetooth/hidp/hidp.h @@ -80,8 +80,6 @@ #define HIDP_VIRTUAL_CABLE_UNPLUG 0 #define HIDP_BOOT_PROTOCOL_MODE 1 #define HIDP_BLUETOOTH_VENDOR_ID 9 -#define HIDP_WAITING_FOR_RETURN 10 -#define HIDP_WAITING_FOR_SEND_ACK 11 struct hidp_connadd_req { int ctrl_sock; /* Connected control socket */ @@ -143,7 +141,6 @@ struct hidp_session { uint intr_mtu; atomic_t terminate; - struct task_struct *task; unsigned char keys[8]; unsigned char leds; @@ -157,22 +154,9 @@ struct hidp_session { struct sk_buff_head ctrl_transmit; struct sk_buff_head intr_transmit; - /* Used in hidp_get_raw_report() */ - int waiting_report_type; /* HIDP_DATA_RTYPE_* */ - int waiting_report_number; /* -1 for not numbered */ - struct mutex report_mutex; - struct sk_buff *report_return; - wait_queue_head_t report_queue; - - /* Used in hidp_output_raw_report() */ - int output_report_success; /* boolean */ - /* Report descriptor */ __u8 *rd_data; uint rd_size; - - wait_queue_head_t startup_queue; - int waiting_for_startup; }; static inline void hidp_schedule(struct hidp_session *session) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 5a0ce738751..e753c5f8788 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1,6 +1,6 @@ /* BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated + Copyright (c) 2000-2001, 2010-2011 Code Aurora Forum. All rights reserved. Copyright (C) 2009-2010 Gustavo F. Padovan Copyright (C) 2010 Google Inc. @@ -46,6 +46,7 @@ #include #include #include +#include #include #include @@ -55,317 +56,509 @@ #include #include #include +#include int disable_ertm; +int enable_reconfig; static u32 l2cap_feat_mask = L2CAP_FEAT_FIXED_CHAN; -static u8 l2cap_fixed_chan[8] = { 0x02, }; +static u8 l2cap_fixed_chan[8] = { L2CAP_FC_L2CAP | L2CAP_FC_A2MP, }; -static LIST_HEAD(chan_list); -static DEFINE_RWLOCK(chan_list_lock); +struct workqueue_struct *_l2cap_wq; + +struct bt_sock_list l2cap_sk_list = { + .lock = __RW_LOCK_UNLOCKED(l2cap_sk_list.lock) +}; + +static void l2cap_send_move_chan_req(struct l2cap_conn *conn, + struct l2cap_pinfo *pi, u16 icid, u8 dest_amp_id); +static void l2cap_send_move_chan_cfm(struct l2cap_conn *conn, + struct l2cap_pinfo *pi, u16 icid, u16 result); +static void l2cap_send_move_chan_rsp(struct l2cap_conn *conn, u8 ident, + u16 icid, u16 result); + +static void l2cap_amp_move_setup(struct sock *sk); +static void l2cap_amp_move_success(struct sock *sk); +static void l2cap_amp_move_revert(struct sock *sk); + +static int l2cap_ertm_rx_queued_iframes(struct sock *sk); static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn, u8 code, u8 ident, u16 dlen, void *data); -static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len, - void *data); -static int l2cap_build_conf_req(struct l2cap_chan *chan, void *data); -static void l2cap_send_disconn_req(struct l2cap_conn *conn, - struct l2cap_chan *chan, int err); - -static int l2cap_ertm_data_rcv(struct sock *sk, struct sk_buff *skb); +static int l2cap_answer_move_poll(struct sock *sk); +static int l2cap_create_cfm(struct hci_chan *chan, u8 status); +static int l2cap_deaggregate(struct hci_chan *chan, struct l2cap_pinfo *pi); +static void l2cap_chan_ready(struct sock *sk); +static void l2cap_conn_del(struct hci_conn *hcon, int err); /* ---- L2CAP channels ---- */ +static struct sock *__l2cap_get_chan_by_dcid(struct l2cap_chan_list *l, u16 cid) +{ + struct sock *s; + for (s = l->head; s; s = l2cap_pi(s)->next_c) { + if (l2cap_pi(s)->dcid == cid) + break; + } + return s; +} -static inline void chan_hold(struct l2cap_chan *c) +/* Find channel with given DCID. + * Returns locked socket */ +static inline struct sock *l2cap_get_chan_by_dcid(struct l2cap_chan_list *l, + u16 cid) { - atomic_inc(&c->refcnt); + struct sock *s; + read_lock(&l->lock); + s = __l2cap_get_chan_by_dcid(l, cid); + if (s) + bh_lock_sock(s); + read_unlock(&l->lock); + return s; } -static inline void chan_put(struct l2cap_chan *c) +static struct sock *__l2cap_get_chan_by_scid(struct l2cap_chan_list *l, u16 cid) { - if (atomic_dec_and_test(&c->refcnt)) - kfree(c); + struct sock *s; + for (s = l->head; s; s = l2cap_pi(s)->next_c) { + if (l2cap_pi(s)->scid == cid) + break; + } + return s; } -static struct l2cap_chan *__l2cap_get_chan_by_dcid(struct l2cap_conn *conn, u16 cid) +/* Find channel with given SCID. + * Returns locked socket */ +static inline struct sock *l2cap_get_chan_by_scid(struct l2cap_chan_list *l, u16 cid) { - struct l2cap_chan *c; + struct sock *s; + read_lock(&l->lock); + s = __l2cap_get_chan_by_scid(l, cid); + if (s) + bh_lock_sock(s); + read_unlock(&l->lock); + return s; +} - list_for_each_entry(c, &conn->chan_l, list) { - if (c->dcid == cid) - return c; +static struct sock *__l2cap_get_chan_by_ident(struct l2cap_chan_list *l, u8 ident) +{ + struct sock *s; + for (s = l->head; s; s = l2cap_pi(s)->next_c) { + if (l2cap_pi(s)->ident == ident) + break; } - return NULL; + return s; +} +static inline struct sock *l2cap_get_chan_by_ident(struct l2cap_chan_list *l, u8 ident) +{ + struct sock *s; + read_lock(&l->lock); + s = __l2cap_get_chan_by_ident(l, ident); + if (s) + bh_lock_sock(s); + read_unlock(&l->lock); + return s; } -static struct l2cap_chan *__l2cap_get_chan_by_scid(struct l2cap_conn *conn, u16 cid) +static inline struct sk_buff *l2cap_ertm_seq_in_queue(struct sk_buff_head *head, + u16 seq) { - struct l2cap_chan *c; + struct sk_buff *skb; - list_for_each_entry(c, &conn->chan_l, list) { - if (c->scid == cid) - return c; + skb_queue_walk(head, skb) { + if (bt_cb(skb)->control.txseq == seq) + return skb; } + return NULL; } -/* Find channel with given SCID. - * Returns locked socket */ -static struct l2cap_chan *l2cap_get_chan_by_scid(struct l2cap_conn *conn, u16 cid) +static int l2cap_seq_list_init(struct l2cap_seq_list *seq_list, u16 size) { - struct l2cap_chan *c; + u16 allocSize = 1; + int err = 0; + int i; + + /* Actual allocated size must be a power of 2 */ + while (allocSize && allocSize <= size) + allocSize <<= 1; + if (!allocSize) + return -ENOMEM; + + seq_list->list = kzalloc(sizeof(u16) * allocSize, GFP_ATOMIC); + if (!seq_list->list) + return -ENOMEM; - read_lock(&conn->chan_lock); - c = __l2cap_get_chan_by_scid(conn, cid); - if (c) - bh_lock_sock(c->sk); - read_unlock(&conn->chan_lock); - return c; + seq_list->size = allocSize; + seq_list->mask = allocSize - 1; + seq_list->head = L2CAP_SEQ_LIST_CLEAR; + seq_list->tail = L2CAP_SEQ_LIST_CLEAR; + for (i = 0; i < allocSize; i++) + seq_list->list[i] = L2CAP_SEQ_LIST_CLEAR; + + return err; } -static struct l2cap_chan *__l2cap_get_chan_by_ident(struct l2cap_conn *conn, u8 ident) +static inline void l2cap_seq_list_free(struct l2cap_seq_list *seq_list) { - struct l2cap_chan *c; + kfree(seq_list->list); +} - list_for_each_entry(c, &conn->chan_l, list) { - if (c->ident == ident) - return c; - } - return NULL; +static inline bool l2cap_seq_list_contains(struct l2cap_seq_list *seq_list, + u16 seq) +{ + return seq_list->list[seq & seq_list->mask] != L2CAP_SEQ_LIST_CLEAR; } -static inline struct l2cap_chan *l2cap_get_chan_by_ident(struct l2cap_conn *conn, u8 ident) +static u16 l2cap_seq_list_remove(struct l2cap_seq_list *seq_list, u16 seq) { - struct l2cap_chan *c; + u16 mask = seq_list->mask; + + BT_DBG("seq_list %p, seq %d", seq_list, (int) seq); + + if (seq_list->head == L2CAP_SEQ_LIST_CLEAR) { + /* In case someone tries to pop the head of an empty list */ + BT_DBG("List empty"); + return L2CAP_SEQ_LIST_CLEAR; + } else if (seq_list->head == seq) { + /* Head can be removed quickly */ + BT_DBG("Remove head"); + seq_list->head = seq_list->list[seq & mask]; + seq_list->list[seq & mask] = L2CAP_SEQ_LIST_CLEAR; + + if (seq_list->head == L2CAP_SEQ_LIST_TAIL) { + seq_list->head = L2CAP_SEQ_LIST_CLEAR; + seq_list->tail = L2CAP_SEQ_LIST_CLEAR; + } + } else { + /* Non-head item must be found first */ + u16 prev = seq_list->head; + BT_DBG("Find and remove"); + while (seq_list->list[prev & mask] != seq) { + prev = seq_list->list[prev & mask]; + if (prev == L2CAP_SEQ_LIST_TAIL) { + BT_DBG("seq %d not in list", (int) seq); + return L2CAP_SEQ_LIST_CLEAR; + } + } + + seq_list->list[prev & mask] = seq_list->list[seq & mask]; + seq_list->list[seq & mask] = L2CAP_SEQ_LIST_CLEAR; + if (seq_list->tail == seq) + seq_list->tail = prev; + } + return seq; +} - read_lock(&conn->chan_lock); - c = __l2cap_get_chan_by_ident(conn, ident); - if (c) - bh_lock_sock(c->sk); - read_unlock(&conn->chan_lock); - return c; +static inline u16 l2cap_seq_list_pop(struct l2cap_seq_list *seq_list) +{ + return l2cap_seq_list_remove(seq_list, seq_list->head); } -static struct l2cap_chan *__l2cap_global_chan_by_addr(__le16 psm, bdaddr_t *src) +static void l2cap_seq_list_clear(struct l2cap_seq_list *seq_list) { - struct l2cap_chan *c; + if (seq_list->head != L2CAP_SEQ_LIST_CLEAR) { + u16 i; + for (i = 0; i < seq_list->size; i++) + seq_list->list[i] = L2CAP_SEQ_LIST_CLEAR; - list_for_each_entry(c, &chan_list, global_l) { - if (c->sport == psm && !bacmp(&bt_sk(c->sk)->src, src)) - goto found; + seq_list->head = L2CAP_SEQ_LIST_CLEAR; + seq_list->tail = L2CAP_SEQ_LIST_CLEAR; } - - c = NULL; -found: - return c; } -int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm) +static void l2cap_seq_list_append(struct l2cap_seq_list *seq_list, u16 seq) { - int err; + u16 mask = seq_list->mask; - write_lock_bh(&chan_list_lock); + BT_DBG("seq_list %p, seq %d", seq_list, (int) seq); - if (psm && __l2cap_global_chan_by_addr(psm, src)) { - err = -EADDRINUSE; - goto done; + if (seq_list->list[seq & mask] == L2CAP_SEQ_LIST_CLEAR) { + if (seq_list->tail == L2CAP_SEQ_LIST_CLEAR) + seq_list->head = seq; + else + seq_list->list[seq_list->tail & mask] = seq; + + seq_list->tail = seq; + seq_list->list[seq & mask] = L2CAP_SEQ_LIST_TAIL; } +} - if (psm) { - chan->psm = psm; - chan->sport = psm; - err = 0; +static u16 __pack_enhanced_control(struct bt_l2cap_control *control) +{ + u16 packed; + + packed = (control->reqseq << L2CAP_CTRL_REQSEQ_SHIFT) & + L2CAP_CTRL_REQSEQ; + packed |= (control->final << L2CAP_CTRL_FINAL_SHIFT) & + L2CAP_CTRL_FINAL; + + if (control->frame_type == 's') { + packed |= (control->poll << L2CAP_CTRL_POLL_SHIFT) & + L2CAP_CTRL_POLL; + packed |= (control->super << L2CAP_CTRL_SUPERVISE_SHIFT) & + L2CAP_CTRL_SUPERVISE; + packed |= L2CAP_CTRL_FRAME_TYPE; } else { - u16 p; - - err = -EINVAL; - for (p = 0x1001; p < 0x1100; p += 2) - if (!__l2cap_global_chan_by_addr(cpu_to_le16(p), src)) { - chan->psm = cpu_to_le16(p); - chan->sport = cpu_to_le16(p); - err = 0; - break; - } + packed |= (control->sar << L2CAP_CTRL_SAR_SHIFT) & + L2CAP_CTRL_SAR; + packed |= (control->txseq << L2CAP_CTRL_TXSEQ_SHIFT) & + L2CAP_CTRL_TXSEQ; } -done: - write_unlock_bh(&chan_list_lock); - return err; + return packed; } -int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid) +static void __get_enhanced_control(u16 enhanced, + struct bt_l2cap_control *control) { - write_lock_bh(&chan_list_lock); - - chan->scid = scid; - - write_unlock_bh(&chan_list_lock); - - return 0; + control->reqseq = (enhanced & L2CAP_CTRL_REQSEQ) >> + L2CAP_CTRL_REQSEQ_SHIFT; + control->final = (enhanced & L2CAP_CTRL_FINAL) >> + L2CAP_CTRL_FINAL_SHIFT; + + if (enhanced & L2CAP_CTRL_FRAME_TYPE) { + control->frame_type = 's'; + control->poll = (enhanced & L2CAP_CTRL_POLL) >> + L2CAP_CTRL_POLL_SHIFT; + control->super = (enhanced & L2CAP_CTRL_SUPERVISE) >> + L2CAP_CTRL_SUPERVISE_SHIFT; + + control->sar = 0; + control->txseq = 0; + } else { + control->frame_type = 'i'; + control->sar = (enhanced & L2CAP_CTRL_SAR) >> + L2CAP_CTRL_SAR_SHIFT; + control->txseq = (enhanced & L2CAP_CTRL_TXSEQ) >> + L2CAP_CTRL_TXSEQ_SHIFT; + + control->poll = 0; + control->super = 0; + } } -static u16 l2cap_alloc_cid(struct l2cap_conn *conn) +static u32 __pack_extended_control(struct bt_l2cap_control *control) { - u16 cid = L2CAP_CID_DYN_START; - - for (; cid < L2CAP_CID_DYN_END; cid++) { - if (!__l2cap_get_chan_by_scid(conn, cid)) - return cid; + u32 packed; + + packed = (control->reqseq << L2CAP_EXT_CTRL_REQSEQ_SHIFT) & + L2CAP_EXT_CTRL_REQSEQ; + packed |= (control->final << L2CAP_EXT_CTRL_FINAL_SHIFT) & + L2CAP_EXT_CTRL_FINAL; + + if (control->frame_type == 's') { + packed |= (control->poll << L2CAP_EXT_CTRL_POLL_SHIFT) & + L2CAP_EXT_CTRL_POLL; + packed |= (control->super << L2CAP_EXT_CTRL_SUPERVISE_SHIFT) & + L2CAP_EXT_CTRL_SUPERVISE; + packed |= L2CAP_EXT_CTRL_FRAME_TYPE; + } else { + packed |= (control->sar << L2CAP_EXT_CTRL_SAR_SHIFT) & + L2CAP_EXT_CTRL_SAR; + packed |= (control->txseq << L2CAP_EXT_CTRL_TXSEQ_SHIFT) & + L2CAP_EXT_CTRL_TXSEQ; } - return 0; + return packed; } -static void l2cap_set_timer(struct l2cap_chan *chan, struct timer_list *timer, long timeout) +static void __get_extended_control(u32 extended, + struct bt_l2cap_control *control) { - BT_DBG("chan %p state %d timeout %ld", chan->sk, chan->state, timeout); - - if (!mod_timer(timer, jiffies + msecs_to_jiffies(timeout))) - chan_hold(chan); + control->reqseq = (extended & L2CAP_EXT_CTRL_REQSEQ) >> + L2CAP_EXT_CTRL_REQSEQ_SHIFT; + control->final = (extended & L2CAP_EXT_CTRL_FINAL) >> + L2CAP_EXT_CTRL_FINAL_SHIFT; + + if (extended & L2CAP_EXT_CTRL_FRAME_TYPE) { + control->frame_type = 's'; + control->poll = (extended & L2CAP_EXT_CTRL_POLL) >> + L2CAP_EXT_CTRL_POLL_SHIFT; + control->super = (extended & L2CAP_EXT_CTRL_SUPERVISE) >> + L2CAP_EXT_CTRL_SUPERVISE_SHIFT; + + control->sar = 0; + control->txseq = 0; + } else { + control->frame_type = 'i'; + control->sar = (extended & L2CAP_EXT_CTRL_SAR) >> + L2CAP_EXT_CTRL_SAR_SHIFT; + control->txseq = (extended & L2CAP_EXT_CTRL_TXSEQ) >> + L2CAP_EXT_CTRL_TXSEQ_SHIFT; + + control->poll = 0; + control->super = 0; + } } -static void l2cap_clear_timer(struct l2cap_chan *chan, struct timer_list *timer) +static inline void l2cap_ertm_stop_ack_timer(struct l2cap_pinfo *pi) { - BT_DBG("chan %p state %d", chan, chan->state); - - if (timer_pending(timer) && del_timer(timer)) - chan_put(chan); + BT_DBG("pi %p", pi); + __cancel_delayed_work(&pi->ack_work); } -static void l2cap_state_change(struct l2cap_chan *chan, int state) +static inline void l2cap_ertm_start_ack_timer(struct l2cap_pinfo *pi) { - chan->state = state; - chan->ops->state_change(chan->data, state); + BT_DBG("pi %p, pending %d", pi, delayed_work_pending(&pi->ack_work)); + if (!delayed_work_pending(&pi->ack_work)) { + queue_delayed_work(_l2cap_wq, &pi->ack_work, + msecs_to_jiffies(L2CAP_DEFAULT_ACK_TO)); + } } -static void l2cap_chan_timeout(unsigned long arg) +static inline void l2cap_ertm_stop_retrans_timer(struct l2cap_pinfo *pi) { - struct l2cap_chan *chan = (struct l2cap_chan *) arg; - struct sock *sk = chan->sk; - int reason; + BT_DBG("pi %p", pi); + __cancel_delayed_work(&pi->retrans_work); +} - BT_DBG("chan %p state %d", chan, chan->state); +static inline void l2cap_ertm_start_retrans_timer(struct l2cap_pinfo *pi) +{ + BT_DBG("pi %p", pi); + if (!delayed_work_pending(&pi->monitor_work) && pi->retrans_timeout) { + __cancel_delayed_work(&pi->retrans_work); + queue_delayed_work(_l2cap_wq, &pi->retrans_work, + msecs_to_jiffies(pi->retrans_timeout)); + } +} - bh_lock_sock(sk); +static inline void l2cap_ertm_stop_monitor_timer(struct l2cap_pinfo *pi) +{ + BT_DBG("pi %p", pi); + __cancel_delayed_work(&pi->monitor_work); +} - if (sock_owned_by_user(sk)) { - /* sk is owned by user. Try again later */ - __set_chan_timer(chan, HZ / 5); - bh_unlock_sock(sk); - chan_put(chan); - return; +static inline void l2cap_ertm_start_monitor_timer(struct l2cap_pinfo *pi) +{ + BT_DBG("pi %p", pi); + l2cap_ertm_stop_retrans_timer(pi); + __cancel_delayed_work(&pi->monitor_work); + if (pi->monitor_timeout) { + queue_delayed_work(_l2cap_wq, &pi->monitor_work, + msecs_to_jiffies(pi->monitor_timeout)); } +} - if (chan->state == BT_CONNECTED || chan->state == BT_CONFIG) - reason = ECONNREFUSED; - else if (chan->state == BT_CONNECT && - chan->sec_level != BT_SECURITY_SDP) - reason = ECONNREFUSED; - else - reason = ETIMEDOUT; - - l2cap_chan_close(chan, reason); +static u16 l2cap_alloc_cid(struct l2cap_chan_list *l) +{ + u16 cid = L2CAP_CID_DYN_START; - bh_unlock_sock(sk); + for (; cid < L2CAP_CID_DYN_END; cid++) { + if (!__l2cap_get_chan_by_scid(l, cid)) + return cid; + } - chan->ops->close(chan->data); - chan_put(chan); + return 0; } -struct l2cap_chan *l2cap_chan_create(struct sock *sk) +static inline void __l2cap_chan_link(struct l2cap_chan_list *l, struct sock *sk) { - struct l2cap_chan *chan; - - chan = kzalloc(sizeof(*chan), GFP_ATOMIC); - if (!chan) - return NULL; + sock_hold(sk); - chan->sk = sk; + if (l->head) + l2cap_pi(l->head)->prev_c = sk; - write_lock_bh(&chan_list_lock); - list_add(&chan->global_l, &chan_list); - write_unlock_bh(&chan_list_lock); + l2cap_pi(sk)->next_c = l->head; + l2cap_pi(sk)->prev_c = NULL; + l->head = sk; +} - setup_timer(&chan->chan_timer, l2cap_chan_timeout, (unsigned long) chan); +static inline void l2cap_chan_unlink(struct l2cap_chan_list *l, struct sock *sk) +{ + struct sock *next = l2cap_pi(sk)->next_c, *prev = l2cap_pi(sk)->prev_c; - chan->state = BT_OPEN; + write_lock_bh(&l->lock); + if (sk == l->head) + l->head = next; - atomic_set(&chan->refcnt, 1); + if (next) + l2cap_pi(next)->prev_c = prev; + if (prev) + l2cap_pi(prev)->next_c = next; + write_unlock_bh(&l->lock); - return chan; + __sock_put(sk); } -void l2cap_chan_destroy(struct l2cap_chan *chan) +static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk) { - write_lock_bh(&chan_list_lock); - list_del(&chan->global_l); - write_unlock_bh(&chan_list_lock); - - chan_put(chan); -} + struct l2cap_chan_list *l = &conn->chan_list; -static void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) -{ BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn, - chan->psm, chan->dcid); + l2cap_pi(sk)->psm, l2cap_pi(sk)->dcid); conn->disc_reason = 0x13; - chan->conn = conn; + l2cap_pi(sk)->conn = conn; - if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED) { + if (!l2cap_pi(sk)->fixed_channel && + (sk->sk_type == SOCK_SEQPACKET || sk->sk_type == SOCK_STREAM)) { if (conn->hcon->type == LE_LINK) { /* LE connection */ - chan->omtu = L2CAP_LE_DEFAULT_MTU; - chan->scid = L2CAP_CID_LE_DATA; - chan->dcid = L2CAP_CID_LE_DATA; + if (l2cap_pi(sk)->imtu < L2CAP_LE_DEFAULT_MTU) + l2cap_pi(sk)->imtu = L2CAP_LE_DEFAULT_MTU; + if (l2cap_pi(sk)->omtu < L2CAP_LE_DEFAULT_MTU) + l2cap_pi(sk)->omtu = L2CAP_LE_DEFAULT_MTU; + + l2cap_pi(sk)->scid = L2CAP_CID_LE_DATA; + l2cap_pi(sk)->dcid = L2CAP_CID_LE_DATA; } else { /* Alloc CID for connection-oriented socket */ - chan->scid = l2cap_alloc_cid(conn); - chan->omtu = L2CAP_DEFAULT_MTU; + l2cap_pi(sk)->scid = l2cap_alloc_cid(l); + l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU; } - } else if (chan->chan_type == L2CAP_CHAN_CONN_LESS) { + } else if (sk->sk_type == SOCK_DGRAM) { /* Connectionless socket */ - chan->scid = L2CAP_CID_CONN_LESS; - chan->dcid = L2CAP_CID_CONN_LESS; - chan->omtu = L2CAP_DEFAULT_MTU; - } else { + l2cap_pi(sk)->scid = L2CAP_CID_CONN_LESS; + l2cap_pi(sk)->dcid = L2CAP_CID_CONN_LESS; + l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU; + } else if (sk->sk_type == SOCK_RAW) { /* Raw socket can send/recv signalling messages only */ - chan->scid = L2CAP_CID_SIGNALING; - chan->dcid = L2CAP_CID_SIGNALING; - chan->omtu = L2CAP_DEFAULT_MTU; + l2cap_pi(sk)->scid = L2CAP_CID_SIGNALING; + l2cap_pi(sk)->dcid = L2CAP_CID_SIGNALING; + l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU; } + /* Otherwise, do not set scid/dcid/omtu. These will be set up + * by l2cap_fixed_channel_config() + */ - chan_hold(chan); - - list_add(&chan->list, &conn->chan_l); + __l2cap_chan_link(l, sk); } /* Delete channel. * Must be called on the locked socket. */ -static void l2cap_chan_del(struct l2cap_chan *chan, int err) +void l2cap_chan_del(struct sock *sk, int err) { - struct sock *sk = chan->sk; - struct l2cap_conn *conn = chan->conn; + struct l2cap_conn *conn = l2cap_pi(sk)->conn; struct sock *parent = bt_sk(sk)->parent; - __clear_chan_timer(chan); + l2cap_sock_clear_timer(sk); - BT_DBG("chan %p, conn %p, err %d", chan, conn, err); + BT_DBG("sk %p, conn %p, err %d", sk, conn, err); if (conn) { - /* Delete from channel list */ - write_lock_bh(&conn->chan_lock); - list_del(&chan->list); - write_unlock_bh(&conn->chan_lock); - chan_put(chan); + /* Unlink from channel list */ + l2cap_chan_unlink(&conn->chan_list, sk); + l2cap_pi(sk)->conn = NULL; + if (!l2cap_pi(sk)->fixed_channel) + hci_conn_put(conn->hcon); + } - chan->conn = NULL; - hci_conn_put(conn->hcon); + if (l2cap_pi(sk)->ampcon) { + l2cap_pi(sk)->ampcon->l2cap_data = NULL; + l2cap_pi(sk)->ampcon = NULL; + if (l2cap_pi(sk)->ampchan) { + hci_chan_put(l2cap_pi(sk)->ampchan); + if (atomic_read(&l2cap_pi(sk)->ampchan->refcnt)) + l2cap_deaggregate(l2cap_pi(sk)->ampchan, + l2cap_pi(sk)); + } + l2cap_pi(sk)->ampchan = NULL; + l2cap_pi(sk)->amp_id = 0; } - l2cap_state_change(chan, BT_CLOSED); + sk->sk_state = BT_CLOSED; sock_set_flag(sk, SOCK_ZAPPED); if (err) @@ -377,109 +570,24 @@ static void l2cap_chan_del(struct l2cap_chan *chan, int err) } else sk->sk_state_change(sk); - if (!(test_bit(CONF_OUTPUT_DONE, &chan->conf_state) && - test_bit(CONF_INPUT_DONE, &chan->conf_state))) - return; - - skb_queue_purge(&chan->tx_q); - - if (chan->mode == L2CAP_MODE_ERTM) { - struct srej_list *l, *tmp; - - __clear_retrans_timer(chan); - __clear_monitor_timer(chan); - __clear_ack_timer(chan); - - skb_queue_purge(&chan->srej_q); - - list_for_each_entry_safe(l, tmp, &chan->srej_l, list) { - list_del(&l->list); - kfree(l); - } - } -} - -static void l2cap_chan_cleanup_listen(struct sock *parent) -{ - struct sock *sk; - - BT_DBG("parent %p", parent); - - /* Close not yet accepted channels */ - while ((sk = bt_accept_dequeue(parent, NULL))) { - struct l2cap_chan *chan = l2cap_pi(sk)->chan; - __clear_chan_timer(chan); - lock_sock(sk); - l2cap_chan_close(chan, ECONNRESET); - release_sock(sk); - chan->ops->close(chan->data); - } -} - -void l2cap_chan_close(struct l2cap_chan *chan, int reason) -{ - struct l2cap_conn *conn = chan->conn; - struct sock *sk = chan->sk; - - BT_DBG("chan %p state %d socket %p", chan, chan->state, sk->sk_socket); - - switch (chan->state) { - case BT_LISTEN: - l2cap_chan_cleanup_listen(sk); - - l2cap_state_change(chan, BT_CLOSED); - sock_set_flag(sk, SOCK_ZAPPED); - break; - - case BT_CONNECTED: - case BT_CONFIG: - if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED && - conn->hcon->type == ACL_LINK) { - __clear_chan_timer(chan); - __set_chan_timer(chan, sk->sk_sndtimeo); - l2cap_send_disconn_req(conn, chan, reason); - } else - l2cap_chan_del(chan, reason); - break; - - case BT_CONNECT2: - if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED && - conn->hcon->type == ACL_LINK) { - struct l2cap_conn_rsp rsp; - __u16 result; - - if (bt_sk(sk)->defer_setup) - result = L2CAP_CR_SEC_BLOCK; - else - result = L2CAP_CR_BAD_PSM; - l2cap_state_change(chan, BT_DISCONN); - - rsp.scid = cpu_to_le16(chan->dcid); - rsp.dcid = cpu_to_le16(chan->scid); - rsp.result = cpu_to_le16(result); - rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); - l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_RSP, - sizeof(rsp), &rsp); - } + skb_queue_purge(TX_QUEUE(sk)); - l2cap_chan_del(chan, reason); - break; + if (l2cap_pi(sk)->mode == L2CAP_MODE_ERTM) { + if (l2cap_pi(sk)->sdu) + kfree_skb(l2cap_pi(sk)->sdu); - case BT_CONNECT: - case BT_DISCONN: - l2cap_chan_del(chan, reason); - break; + skb_queue_purge(SREJ_QUEUE(sk)); - default: - sock_set_flag(sk, SOCK_ZAPPED); - break; + __cancel_delayed_work(&l2cap_pi(sk)->ack_work); + __cancel_delayed_work(&l2cap_pi(sk)->retrans_work); + __cancel_delayed_work(&l2cap_pi(sk)->monitor_work); } } -static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan) +static inline u8 l2cap_get_auth_type(struct sock *sk) { - if (chan->chan_type == L2CAP_CHAN_RAW) { - switch (chan->sec_level) { + if (sk->sk_type == SOCK_RAW) { + switch (l2cap_pi(sk)->sec_level) { case BT_SECURITY_HIGH: return HCI_AT_DEDICATED_BONDING_MITM; case BT_SECURITY_MEDIUM: @@ -487,16 +595,16 @@ static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan) default: return HCI_AT_NO_BONDING; } - } else if (chan->psm == cpu_to_le16(0x0001)) { - if (chan->sec_level == BT_SECURITY_LOW) - chan->sec_level = BT_SECURITY_SDP; + } else if (l2cap_pi(sk)->psm == cpu_to_le16(0x0001)) { + if (l2cap_pi(sk)->sec_level == BT_SECURITY_LOW) + l2cap_pi(sk)->sec_level = BT_SECURITY_SDP; - if (chan->sec_level == BT_SECURITY_HIGH) + if (l2cap_pi(sk)->sec_level == BT_SECURITY_HIGH) return HCI_AT_NO_BONDING_MITM; else return HCI_AT_NO_BONDING; } else { - switch (chan->sec_level) { + switch (l2cap_pi(sk)->sec_level) { case BT_SECURITY_HIGH: return HCI_AT_GENERAL_BONDING_MITM; case BT_SECURITY_MEDIUM: @@ -508,17 +616,18 @@ static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan) } /* Service level security */ -static inline int l2cap_check_security(struct l2cap_chan *chan) +static inline int l2cap_check_security(struct sock *sk) { - struct l2cap_conn *conn = chan->conn; + struct l2cap_conn *conn = l2cap_pi(sk)->conn; __u8 auth_type; - auth_type = l2cap_get_auth_type(chan); + auth_type = l2cap_get_auth_type(sk); - return hci_conn_security(conn->hcon, chan->sec_level, auth_type); + return hci_conn_security(conn->hcon, l2cap_pi(sk)->sec_level, + auth_type); } -static u8 l2cap_get_ident(struct l2cap_conn *conn) +u8 l2cap_get_ident(struct l2cap_conn *conn) { u8 id; @@ -540,112 +649,99 @@ static u8 l2cap_get_ident(struct l2cap_conn *conn) return id; } -static void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len, void *data) +static void apply_fcs(struct sk_buff *skb) { - struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data); - u8 flags; + size_t len; + u16 partial_crc; + struct sk_buff *iter; + struct sk_buff *final_frag = skb; - BT_DBG("code 0x%2.2x", code); + if (skb_has_frag_list(skb)) + len = skb_headlen(skb); + else + len = skb->len - L2CAP_FCS_SIZE; - if (!skb) - return; + partial_crc = crc16(0, (u8 *) skb->data, len); - if (lmp_no_flush_capable(conn->hcon->hdev)) - flags = ACL_START_NO_FLUSH; - else - flags = ACL_START; + skb_walk_frags(skb, iter) { + len = iter->len; + if (!iter->next) + len -= L2CAP_FCS_SIZE; - bt_cb(skb)->force_active = BT_POWER_FORCE_ACTIVE_ON; + partial_crc = crc16(partial_crc, iter->data, len); + final_frag = iter; + } - hci_send_acl(conn->hcon, skb, flags); + put_unaligned_le16(partial_crc, + final_frag->data + final_frag->len - L2CAP_FCS_SIZE); } -static inline void l2cap_send_sframe(struct l2cap_chan *chan, u16 control) +void l2cap_send_cmd(struct l2cap_conn *conn, u8 ident, u8 code, u16 len, void *data) { - struct sk_buff *skb; - struct l2cap_hdr *lh; - struct l2cap_conn *conn = chan->conn; - int count, hlen = L2CAP_HDR_SIZE + 2; + struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data); u8 flags; - if (chan->state != BT_CONNECTED) - return; - - if (chan->fcs == L2CAP_FCS_CRC16) - hlen += 2; - - BT_DBG("chan %p, control 0x%2.2x", chan, control); - - count = min_t(unsigned int, conn->mtu, hlen); - control |= L2CAP_CTRL_FRAME_TYPE; - - if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state)) - control |= L2CAP_CTRL_FINAL; - - if (test_and_clear_bit(CONN_SEND_PBIT, &chan->conn_state)) - control |= L2CAP_CTRL_POLL; + BT_DBG("code 0x%2.2x", code); - skb = bt_skb_alloc(count, GFP_ATOMIC); if (!skb) return; - lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); - lh->len = cpu_to_le16(hlen - L2CAP_HDR_SIZE); - lh->cid = cpu_to_le16(chan->dcid); - put_unaligned_le16(control, skb_put(skb, 2)); - - if (chan->fcs == L2CAP_FCS_CRC16) { - u16 fcs = crc16(0, (u8 *)lh, count - 2); - put_unaligned_le16(fcs, skb_put(skb, 2)); - } - if (lmp_no_flush_capable(conn->hcon->hdev)) flags = ACL_START_NO_FLUSH; else flags = ACL_START; - bt_cb(skb)->force_active = chan->force_active; + bt_cb(skb)->force_active = 1; - hci_send_acl(chan->conn->hcon, skb, flags); + hci_send_acl(conn->hcon, NULL, skb, flags); } -static inline void l2cap_send_rr_or_rnr(struct l2cap_chan *chan, u16 control) +static inline int __l2cap_no_conn_pending(struct sock *sk) { - if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { - control |= L2CAP_SUPER_RCV_NOT_READY; - set_bit(CONN_RNR_SENT, &chan->conn_state); - } else - control |= L2CAP_SUPER_RCV_READY; + return !(l2cap_pi(sk)->conf_state & L2CAP_CONF_CONNECT_PEND); +} - control |= chan->buffer_seq << L2CAP_CTRL_REQSEQ_SHIFT; +static void l2cap_send_conn_req(struct sock *sk) +{ + struct l2cap_conn_req req; + req.scid = cpu_to_le16(l2cap_pi(sk)->scid); + req.psm = l2cap_pi(sk)->psm; + + l2cap_pi(sk)->ident = l2cap_get_ident(l2cap_pi(sk)->conn); - l2cap_send_sframe(chan, control); + l2cap_send_cmd(l2cap_pi(sk)->conn, l2cap_pi(sk)->ident, + L2CAP_CONN_REQ, sizeof(req), &req); } -static inline int __l2cap_no_conn_pending(struct l2cap_chan *chan) +static void l2cap_send_create_chan_req(struct sock *sk, u8 amp_id) { - return !test_bit(CONF_CONNECT_PEND, &chan->conf_state); + struct l2cap_create_chan_req req; + req.scid = cpu_to_le16(l2cap_pi(sk)->scid); + req.psm = l2cap_pi(sk)->psm; + req.amp_id = amp_id; + + l2cap_pi(sk)->conf_state |= L2CAP_CONF_LOCKSTEP; + l2cap_pi(sk)->ident = l2cap_get_ident(l2cap_pi(sk)->conn); + + l2cap_send_cmd(l2cap_pi(sk)->conn, l2cap_pi(sk)->ident, + L2CAP_CREATE_CHAN_REQ, sizeof(req), &req); } -static void l2cap_do_start(struct l2cap_chan *chan) +static void l2cap_do_start(struct sock *sk) { - struct l2cap_conn *conn = chan->conn; + struct l2cap_conn *conn = l2cap_pi(sk)->conn; if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) { if (!(conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE)) return; - if (l2cap_check_security(chan) && - __l2cap_no_conn_pending(chan)) { - struct l2cap_conn_req req; - req.scid = cpu_to_le16(chan->scid); - req.psm = chan->psm; + if (l2cap_check_security(sk) && __l2cap_no_conn_pending(sk)) { + l2cap_pi(sk)->conf_state |= L2CAP_CONF_CONNECT_PEND; - chan->ident = l2cap_get_ident(conn); - set_bit(CONF_CONNECT_PEND, &chan->conf_state); - - l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_REQ, - sizeof(req), &req); + if (l2cap_pi(sk)->amp_pref == BT_AMP_POLICY_PREFER_AMP) + amp_create_physical(l2cap_pi(sk)->conn, sk); + else + l2cap_send_conn_req(sk); } } else { struct l2cap_info_req req; @@ -678,87 +774,87 @@ static inline int l2cap_mode_supported(__u8 mode, __u32 feat_mask) } } -static void l2cap_send_disconn_req(struct l2cap_conn *conn, struct l2cap_chan *chan, int err) +void l2cap_send_disconn_req(struct l2cap_conn *conn, struct sock *sk, int err) { - struct sock *sk; struct l2cap_disconn_req req; if (!conn) return; - sk = chan->sk; + skb_queue_purge(TX_QUEUE(sk)); + + if (l2cap_pi(sk)->mode == L2CAP_MODE_ERTM) { + skb_queue_purge(SREJ_QUEUE(sk)); - if (chan->mode == L2CAP_MODE_ERTM) { - __clear_retrans_timer(chan); - __clear_monitor_timer(chan); - __clear_ack_timer(chan); + __cancel_delayed_work(&l2cap_pi(sk)->ack_work); + __cancel_delayed_work(&l2cap_pi(sk)->retrans_work); + __cancel_delayed_work(&l2cap_pi(sk)->monitor_work); } - req.dcid = cpu_to_le16(chan->dcid); - req.scid = cpu_to_le16(chan->scid); + req.dcid = cpu_to_le16(l2cap_pi(sk)->dcid); + req.scid = cpu_to_le16(l2cap_pi(sk)->scid); l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_DISCONN_REQ, sizeof(req), &req); - l2cap_state_change(chan, BT_DISCONN); + sk->sk_state = BT_DISCONN; sk->sk_err = err; } /* ---- L2CAP connections ---- */ static void l2cap_conn_start(struct l2cap_conn *conn) { - struct l2cap_chan *chan, *tmp; + struct l2cap_chan_list *l = &conn->chan_list; + struct sock_del_list del, *tmp1, *tmp2; + struct sock *sk; BT_DBG("conn %p", conn); - read_lock(&conn->chan_lock); + INIT_LIST_HEAD(&del.list); - list_for_each_entry_safe(chan, tmp, &conn->chan_l, list) { - struct sock *sk = chan->sk; + read_lock(&l->lock); + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { bh_lock_sock(sk); - if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) { + if (sk->sk_type != SOCK_SEQPACKET && + sk->sk_type != SOCK_STREAM) { bh_unlock_sock(sk); continue; } - if (chan->state == BT_CONNECT) { - struct l2cap_conn_req req; - - if (!l2cap_check_security(chan) || - !__l2cap_no_conn_pending(chan)) { + if (sk->sk_state == BT_CONNECT) { + if (!l2cap_check_security(sk) || + !__l2cap_no_conn_pending(sk)) { bh_unlock_sock(sk); continue; } - if (!l2cap_mode_supported(chan->mode, conn->feat_mask) - && test_bit(CONF_STATE2_DEVICE, - &chan->conf_state)) { - /* l2cap_chan_close() calls list_del(chan) - * so release the lock */ - read_unlock(&conn->chan_lock); - l2cap_chan_close(chan, ECONNRESET); - read_lock(&conn->chan_lock); + if (!l2cap_mode_supported(l2cap_pi(sk)->mode, + conn->feat_mask) + && l2cap_pi(sk)->conf_state & + L2CAP_CONF_STATE2_DEVICE) { + tmp1 = kzalloc(sizeof(struct sock_del_list), + GFP_ATOMIC); + tmp1->sk = sk; + list_add_tail(&tmp1->list, &del.list); bh_unlock_sock(sk); continue; } - req.scid = cpu_to_le16(chan->scid); - req.psm = chan->psm; + l2cap_pi(sk)->conf_state |= L2CAP_CONF_CONNECT_PEND; - chan->ident = l2cap_get_ident(conn); - set_bit(CONF_CONNECT_PEND, &chan->conf_state); - - l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_REQ, - sizeof(req), &req); + if (l2cap_pi(sk)->amp_pref == BT_AMP_POLICY_PREFER_AMP) + amp_create_physical(l2cap_pi(sk)->conn, sk); + else + l2cap_send_conn_req(sk); - } else if (chan->state == BT_CONNECT2) { + } else if (sk->sk_state == BT_CONNECT2) { struct l2cap_conn_rsp rsp; char buf[128]; - rsp.scid = cpu_to_le16(chan->dcid); - rsp.dcid = cpu_to_le16(chan->scid); + rsp.scid = cpu_to_le16(l2cap_pi(sk)->dcid); + rsp.dcid = cpu_to_le16(l2cap_pi(sk)->scid); - if (l2cap_check_security(chan)) { + if (l2cap_check_security(sk)) { if (bt_sk(sk)->defer_setup) { struct sock *parent = bt_sk(sk)->parent; rsp.result = cpu_to_le16(L2CAP_CR_PEND); @@ -767,7 +863,7 @@ static void l2cap_conn_start(struct l2cap_conn *conn) parent->sk_data_ready(parent, 0); } else { - l2cap_state_change(chan, BT_CONFIG); + sk->sk_state = BT_CONFIG; rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS); rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); } @@ -776,75 +872,86 @@ static void l2cap_conn_start(struct l2cap_conn *conn) rsp.status = cpu_to_le16(L2CAP_CS_AUTHEN_PEND); } - l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_RSP, - sizeof(rsp), &rsp); + if (rsp.result == cpu_to_le16(L2CAP_CR_SUCCESS) && + l2cap_pi(sk)->amp_id) { + amp_accept_physical(conn, + l2cap_pi(sk)->amp_id, sk); + bh_unlock_sock(sk); + continue; + } - if (test_bit(CONF_REQ_SENT, &chan->conf_state) || + l2cap_send_cmd(conn, l2cap_pi(sk)->ident, + L2CAP_CONN_RSP, sizeof(rsp), &rsp); + + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_REQ_SENT || rsp.result != L2CAP_CR_SUCCESS) { bh_unlock_sock(sk); continue; } - set_bit(CONF_REQ_SENT, &chan->conf_state); + l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT; l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, - l2cap_build_conf_req(chan, buf), buf); - chan->num_conf_req++; + l2cap_build_conf_req(sk, buf), buf); + l2cap_pi(sk)->num_conf_req++; } bh_unlock_sock(sk); } - read_unlock(&conn->chan_lock); + read_unlock(&l->lock); + + list_for_each_entry_safe(tmp1, tmp2, &del.list, list) { + bh_lock_sock(tmp1->sk); + __l2cap_sock_close(tmp1->sk, ECONNRESET); + bh_unlock_sock(tmp1->sk); + list_del(&tmp1->list); + kfree(tmp1); + } } /* Find socket with cid and source bdaddr. * Returns closest match, locked. */ -static struct l2cap_chan *l2cap_global_chan_by_scid(int state, __le16 cid, bdaddr_t *src) +static struct sock *l2cap_get_sock_by_scid(int state, __le16 cid, bdaddr_t *src) { - struct l2cap_chan *c, *c1 = NULL; - - read_lock(&chan_list_lock); + struct sock *sk = NULL, *sk1 = NULL; + struct hlist_node *node; - list_for_each_entry(c, &chan_list, global_l) { - struct sock *sk = c->sk; + read_lock(&l2cap_sk_list.lock); - if (state && c->state != state) + sk_for_each(sk, node, &l2cap_sk_list.head) { + if (state && sk->sk_state != state) continue; - if (c->scid == cid) { + if (l2cap_pi(sk)->scid == cid) { /* Exact match. */ - if (!bacmp(&bt_sk(sk)->src, src)) { - read_unlock(&chan_list_lock); - return c; - } + if (!bacmp(&bt_sk(sk)->src, src)) + break; /* Closest match */ if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY)) - c1 = c; + sk1 = sk; } } - read_unlock(&chan_list_lock); + read_unlock(&l2cap_sk_list.lock); - return c1; + return node ? sk : sk1; } static void l2cap_le_conn_ready(struct l2cap_conn *conn) { - struct sock *parent, *sk; - struct l2cap_chan *chan, *pchan; + struct l2cap_chan_list *list = &conn->chan_list; + struct sock *parent, *uninitialized_var(sk); BT_DBG(""); /* Check if we have socket listening on cid */ - pchan = l2cap_global_chan_by_scid(BT_LISTEN, L2CAP_CID_LE_DATA, + parent = l2cap_get_sock_by_scid(BT_LISTEN, L2CAP_CID_LE_DATA, conn->src); - if (!pchan) + if (!parent) return; - parent = pchan->sk; - bh_lock_sock(parent); /* Check for backlog size */ @@ -853,102 +960,82 @@ static void l2cap_le_conn_ready(struct l2cap_conn *conn) goto clean; } - chan = pchan->ops->new_connection(pchan->data); - if (!chan) + sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP, GFP_ATOMIC); + if (!sk) goto clean; - sk = chan->sk; - - write_lock_bh(&conn->chan_lock); + write_lock_bh(&list->lock); hci_conn_hold(conn->hcon); + l2cap_sock_init(sk, parent); bacpy(&bt_sk(sk)->src, conn->src); bacpy(&bt_sk(sk)->dst, conn->dst); bt_accept_enqueue(parent, sk); - __l2cap_chan_add(conn, chan); + __l2cap_chan_add(conn, sk); - __set_chan_timer(chan, sk->sk_sndtimeo); + l2cap_sock_set_timer(sk, sk->sk_sndtimeo); - l2cap_state_change(chan, BT_CONNECTED); + sk->sk_state = BT_CONNECTED; parent->sk_data_ready(parent, 0); - write_unlock_bh(&conn->chan_lock); + write_unlock_bh(&list->lock); clean: bh_unlock_sock(parent); } -static void l2cap_chan_ready(struct sock *sk) -{ - struct l2cap_chan *chan = l2cap_pi(sk)->chan; - struct sock *parent = bt_sk(sk)->parent; - - BT_DBG("sk %p, parent %p", sk, parent); - - chan->conf_state = 0; - __clear_chan_timer(chan); - - l2cap_state_change(chan, BT_CONNECTED); - sk->sk_state_change(sk); - - if (parent) - parent->sk_data_ready(parent, 0); -} - static void l2cap_conn_ready(struct l2cap_conn *conn) { - struct l2cap_chan *chan; + struct l2cap_chan_list *l = &conn->chan_list; + struct sock *sk; BT_DBG("conn %p", conn); if (!conn->hcon->out && conn->hcon->type == LE_LINK) l2cap_le_conn_ready(conn); - read_lock(&conn->chan_lock); - - list_for_each_entry(chan, &conn->chan_l, list) { - struct sock *sk = chan->sk; + read_lock(&l->lock); + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { bh_lock_sock(sk); - if (conn->hcon->type == LE_LINK) { - if (smp_conn_security(conn, chan->sec_level)) + if (l2cap_pi(sk)->scid == L2CAP_CID_LE_DATA) { + if (smp_conn_security(conn, l2cap_pi(sk)->sec_level)) l2cap_chan_ready(sk); - } else if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) { - __clear_chan_timer(chan); - l2cap_state_change(chan, BT_CONNECTED); + } else if (sk->sk_type != SOCK_SEQPACKET && + sk->sk_type != SOCK_STREAM) { + l2cap_sock_clear_timer(sk); + sk->sk_state = BT_CONNECTED; sk->sk_state_change(sk); - - } else if (chan->state == BT_CONNECT) - l2cap_do_start(chan); + } else if (sk->sk_state == BT_CONNECT) + l2cap_do_start(sk); bh_unlock_sock(sk); } - read_unlock(&conn->chan_lock); + read_unlock(&l->lock); } /* Notify sockets that we cannot guaranty reliability anymore */ static void l2cap_conn_unreliable(struct l2cap_conn *conn, int err) { - struct l2cap_chan *chan; + struct l2cap_chan_list *l = &conn->chan_list; + struct sock *sk; BT_DBG("conn %p", conn); - read_lock(&conn->chan_lock); + read_lock(&l->lock); - list_for_each_entry(chan, &conn->chan_l, list) { - struct sock *sk = chan->sk; - - if (chan->force_reliable) + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + if (l2cap_pi(sk)->force_reliable) sk->sk_err = err; } - read_unlock(&conn->chan_lock); + read_unlock(&l->lock); } static void l2cap_info_timeout(unsigned long arg) @@ -961,38 +1048,6 @@ static void l2cap_info_timeout(unsigned long arg) l2cap_conn_start(conn); } -static void l2cap_conn_del(struct hci_conn *hcon, int err) -{ - struct l2cap_conn *conn = hcon->l2cap_data; - struct l2cap_chan *chan, *l; - struct sock *sk; - - if (!conn) - return; - - BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); - - kfree_skb(conn->rx_skb); - - /* Kill channels */ - list_for_each_entry_safe(chan, l, &conn->chan_l, list) { - sk = chan->sk; - bh_lock_sock(sk); - l2cap_chan_del(chan, err); - bh_unlock_sock(sk); - chan->ops->close(chan->data); - } - - if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) - del_timer_sync(&conn->info_timer); - - if (test_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend)) - del_timer(&conn->security_timer); - - hcon->l2cap_data = NULL; - kfree(conn); -} - static void security_timeout(unsigned long arg) { struct l2cap_conn *conn = (void *) arg; @@ -1027,9 +1082,7 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status) conn->feat_mask = 0; spin_lock_init(&conn->lock); - rwlock_init(&conn->chan_lock); - - INIT_LIST_HEAD(&conn->chan_l); + rwlock_init(&conn->chan_list.lock); if (hcon->type == LE_LINK) setup_timer(&conn->security_timer, security_timeout, @@ -1043,51 +1096,88 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status) return conn; } -static inline void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) +static void l2cap_conn_del(struct hci_conn *hcon, int err) { - write_lock_bh(&conn->chan_lock); - __l2cap_chan_add(conn, chan); - write_unlock_bh(&conn->chan_lock); -} - -/* ---- Socket interface ---- */ + struct l2cap_conn *conn = hcon->l2cap_data; + struct sock *sk; + struct sock *next; -/* Find socket with psm and source bdaddr. - * Returns closest match. - */ -static struct l2cap_chan *l2cap_global_chan_by_psm(int state, __le16 psm, bdaddr_t *src) -{ - struct l2cap_chan *c, *c1 = NULL; + if (!conn) + return; - read_lock(&chan_list_lock); + BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); - list_for_each_entry(c, &chan_list, global_l) { - struct sock *sk = c->sk; + if ((conn->hcon == hcon) && (conn->rx_skb)) + kfree_skb(conn->rx_skb); - if (state && c->state != state) - continue; + BT_DBG("conn->hcon %p", conn->hcon); - if (c->psm == psm) { + /* Kill channels */ + for (sk = conn->chan_list.head; sk; ) { + BT_DBG("ampcon %p", l2cap_pi(sk)->ampcon); + if ((conn->hcon == hcon) || (l2cap_pi(sk)->ampcon == hcon)) { + next = l2cap_pi(sk)->next_c; + bh_lock_sock(sk); + l2cap_chan_del(sk, err); + bh_unlock_sock(sk); + l2cap_sock_kill(sk); + sk = next; + } else + sk = l2cap_pi(sk)->next_c; + } + + if (conn->hcon == hcon) { + if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) + del_timer_sync(&conn->info_timer); + + hcon->l2cap_data = NULL; + + kfree(conn); + } +} + +static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk) +{ + struct l2cap_chan_list *l = &conn->chan_list; + write_lock_bh(&l->lock); + __l2cap_chan_add(conn, sk); + write_unlock_bh(&l->lock); +} + +/* ---- Socket interface ---- */ + +/* Find socket with psm and source bdaddr. + * Returns closest match. + */ +static struct sock *l2cap_get_sock_by_psm(int state, __le16 psm, bdaddr_t *src) +{ + struct sock *sk = NULL, *sk1 = NULL; + struct hlist_node *node; + + read_lock(&l2cap_sk_list.lock); + + sk_for_each(sk, node, &l2cap_sk_list.head) { + if (state && sk->sk_state != state) + continue; + + if (l2cap_pi(sk)->psm == psm) { /* Exact match. */ - if (!bacmp(&bt_sk(sk)->src, src)) { - read_unlock(&chan_list_lock); - return c; - } + if (!bacmp(&bt_sk(sk)->src, src)) + break; /* Closest match */ if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY)) - c1 = c; + sk1 = sk; } } - read_unlock(&chan_list_lock); + read_unlock(&l2cap_sk_list.lock); - return c1; + return node ? sk : sk1; } -int l2cap_chan_connect(struct l2cap_chan *chan) +int l2cap_do_connect(struct sock *sk) { - struct sock *sk = chan->sk; bdaddr_t *src = &bt_sk(sk)->src; bdaddr_t *dst = &bt_sk(sk)->dst; struct l2cap_conn *conn; @@ -1097,7 +1187,7 @@ int l2cap_chan_connect(struct l2cap_chan *chan) int err; BT_DBG("%s -> %s psm 0x%2.2x", batostr(src), batostr(dst), - chan->psm); + l2cap_pi(sk)->psm); hdev = hci_get_route(dst, src); if (!hdev) @@ -1105,42 +1195,64 @@ int l2cap_chan_connect(struct l2cap_chan *chan) hci_dev_lock_bh(hdev); - auth_type = l2cap_get_auth_type(chan); + auth_type = l2cap_get_auth_type(sk); - if (chan->dcid == L2CAP_CID_LE_DATA) - hcon = hci_connect(hdev, LE_LINK, 0, dst, - chan->sec_level, auth_type); - else - hcon = hci_connect(hdev, ACL_LINK, 0, dst, - chan->sec_level, auth_type); + if (l2cap_pi(sk)->fixed_channel) { + /* Fixed channels piggyback on existing ACL connections */ + hcon = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst); + if (!hcon || !hcon->l2cap_data) { + err = -ENOTCONN; + goto done; + } - if (IS_ERR(hcon)) { - err = PTR_ERR(hcon); - goto done; - } + conn = hcon->l2cap_data; + } else { + if (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA) + hcon = hci_connect(hdev, LE_LINK, 0, dst, + l2cap_pi(sk)->sec_level, auth_type); + else + hcon = hci_connect(hdev, ACL_LINK, 0, dst, + l2cap_pi(sk)->sec_level, auth_type); - conn = l2cap_conn_add(hcon, 0); - if (!conn) { - hci_conn_put(hcon); - err = -ENOMEM; - goto done; + if (IS_ERR(hcon)) { + err = PTR_ERR(hcon); + goto done; + } + + conn = l2cap_conn_add(hcon, 0); + if (!conn) { + hci_conn_put(hcon); + err = -ENOMEM; + goto done; + } } /* Update source addr of the socket */ bacpy(src, conn->src); - l2cap_chan_add(conn, chan); + l2cap_chan_add(conn, sk); - l2cap_state_change(chan, BT_CONNECT); - __set_chan_timer(chan, sk->sk_sndtimeo); + BT_DBG("hcon->state %d", (int) hcon->state); - if (hcon->state == BT_CONNECTED) { - if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) { - __clear_chan_timer(chan); - if (l2cap_check_security(chan)) - l2cap_state_change(chan, BT_CONNECTED); - } else - l2cap_do_start(chan); + if (l2cap_pi(sk)->fixed_channel) { + sk->sk_state = BT_CONNECTED; + sk->sk_state_change(sk); + } else { + sk->sk_state = BT_CONNECT; + l2cap_sock_set_timer(sk, sk->sk_sndtimeo); + sk->sk_state_change(sk); + + if (hcon->state == BT_CONNECTED) { + if (sk->sk_type != SOCK_SEQPACKET && + sk->sk_type != SOCK_STREAM) { + l2cap_sock_clear_timer(sk); + if (l2cap_check_security(sk)) { + sk->sk_state = BT_CONNECTED; + sk->sk_state_change(sk); + } + } else + l2cap_do_start(sk); + } } err = 0; @@ -1153,14 +1265,15 @@ int l2cap_chan_connect(struct l2cap_chan *chan) int __l2cap_wait_ack(struct sock *sk) { - struct l2cap_chan *chan = l2cap_pi(sk)->chan; DECLARE_WAITQUEUE(wait, current); int err = 0; int timeo = HZ/5; add_wait_queue(sk_sleep(sk), &wait); - set_current_state(TASK_INTERRUPTIBLE); - while (chan->unacked_frames > 0 && chan->conn) { + while (l2cap_pi(sk)->unacked_frames > 0 && l2cap_pi(sk)->conn && + atomic_read(&l2cap_pi(sk)->ertm_queued)) { + set_current_state(TASK_INTERRUPTIBLE); + if (!timeo) timeo = HZ/5; @@ -1172,7 +1285,6 @@ int __l2cap_wait_ack(struct sock *sk) release_sock(sk); timeo = schedule_timeout(timeo); lock_sock(sk); - set_current_state(TASK_INTERRUPTIBLE); err = sock_error(sk); if (err) @@ -1183,287 +1295,322 @@ int __l2cap_wait_ack(struct sock *sk) return err; } -static void l2cap_monitor_timeout(unsigned long arg) +static void l2cap_ertm_tx_worker(struct work_struct *work) { - struct l2cap_chan *chan = (void *) arg; - struct sock *sk = chan->sk; - - BT_DBG("chan %p", chan); - - bh_lock_sock(sk); - if (chan->retry_count >= chan->remote_max_tx) { - l2cap_send_disconn_req(chan->conn, chan, ECONNABORTED); - bh_unlock_sock(sk); - return; - } - - chan->retry_count++; - __set_monitor_timer(chan); - - l2cap_send_rr_or_rnr(chan, L2CAP_CTRL_POLL); - bh_unlock_sock(sk); + struct l2cap_pinfo *pi = + container_of(work, struct l2cap_pinfo, tx_work); + struct sock *sk = (struct sock *)pi; + BT_DBG("%p", pi); + + lock_sock(sk); + l2cap_ertm_send(sk); + release_sock(sk); } -static void l2cap_retrans_timeout(unsigned long arg) +static void l2cap_skb_destructor(struct sk_buff *skb) { - struct l2cap_chan *chan = (void *) arg; - struct sock *sk = chan->sk; - - BT_DBG("chan %p", chan); + struct sock *sk = skb->sk; + int queued; - bh_lock_sock(sk); - chan->retry_count = 1; - __set_monitor_timer(chan); - - set_bit(CONN_WAIT_F, &chan->conn_state); - - l2cap_send_rr_or_rnr(chan, L2CAP_CTRL_POLL); - bh_unlock_sock(sk); + queued = atomic_sub_return(1, &l2cap_pi(sk)->ertm_queued); + if (queued < L2CAP_MIN_ERTM_QUEUED) + queue_work(_l2cap_wq, &l2cap_pi(sk)->tx_work); } -static void l2cap_drop_acked_frames(struct l2cap_chan *chan) +void l2cap_do_send(struct sock *sk, struct sk_buff *skb) { - struct sk_buff *skb; + struct l2cap_pinfo *pi = l2cap_pi(sk); - while ((skb = skb_peek(&chan->tx_q)) && - chan->unacked_frames) { - if (bt_cb(skb)->tx_seq == chan->expected_ack_seq) - break; + BT_DBG("sk %p, skb %p len %d", sk, skb, skb->len); - skb = skb_dequeue(&chan->tx_q); - kfree_skb(skb); + if (pi->ampcon && (pi->amp_move_state == L2CAP_AMP_STATE_STABLE || + pi->amp_move_state == L2CAP_AMP_STATE_WAIT_PREPARE)) { + BT_DBG("Sending on AMP connection %p %p", + pi->ampcon, pi->ampchan); + if (pi->ampchan) + hci_send_acl(pi->ampcon, pi->ampchan, skb, + ACL_COMPLETE); + else + kfree_skb(skb); + } else { + u16 flags; - chan->unacked_frames--; - } + bt_cb(skb)->force_active = pi->force_active; + BT_DBG("Sending on BR/EDR connection %p", pi->conn->hcon); + + if (lmp_no_flush_capable(pi->conn->hcon->hdev) && + !l2cap_pi(sk)->flushable) + flags = ACL_START_NO_FLUSH; + else + flags = ACL_START; - if (!chan->unacked_frames) - __clear_retrans_timer(chan); + hci_send_acl(pi->conn->hcon, NULL, skb, flags); + } } -void l2cap_do_send(struct l2cap_chan *chan, struct sk_buff *skb) +int l2cap_ertm_send(struct sock *sk) { - struct hci_conn *hcon = chan->conn->hcon; - u16 flags; - - BT_DBG("chan %p, skb %p len %d", chan, skb, skb->len); - - if (!chan->flushable && lmp_no_flush_capable(hcon->hdev)) - flags = ACL_START_NO_FLUSH; - else - flags = ACL_START; + struct sk_buff *skb, *tx_skb; + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct bt_l2cap_control *control; + int sent = 0; - bt_cb(skb)->force_active = chan->force_active; - hci_send_acl(hcon, skb, flags); -} + BT_DBG("sk %p", sk); -void l2cap_streaming_send(struct l2cap_chan *chan) -{ - struct sk_buff *skb; - u16 control, fcs; + if (sk->sk_state != BT_CONNECTED) + return -ENOTCONN; - while ((skb = skb_dequeue(&chan->tx_q))) { - control = get_unaligned_le16(skb->data + L2CAP_HDR_SIZE); - control |= chan->next_tx_seq << L2CAP_CTRL_TXSEQ_SHIFT; - put_unaligned_le16(control, skb->data + L2CAP_HDR_SIZE); + if (pi->conn_state & L2CAP_CONN_REMOTE_BUSY) + return 0; - if (chan->fcs == L2CAP_FCS_CRC16) { - fcs = crc16(0, (u8 *)skb->data, skb->len - 2); - put_unaligned_le16(fcs, skb->data + skb->len - 2); - } + if (pi->amp_move_state != L2CAP_AMP_STATE_STABLE && + pi->amp_move_state != L2CAP_AMP_STATE_WAIT_PREPARE) + return 0; - l2cap_do_send(chan, skb); + while (sk->sk_send_head && (pi->unacked_frames < pi->remote_tx_win) && + atomic_read(&pi->ertm_queued) < L2CAP_MAX_ERTM_QUEUED && + (pi->tx_state == L2CAP_ERTM_TX_STATE_XMIT)) { - chan->next_tx_seq = (chan->next_tx_seq + 1) % 64; - } -} + skb = sk->sk_send_head; -static void l2cap_retransmit_one_frame(struct l2cap_chan *chan, u8 tx_seq) -{ - struct sk_buff *skb, *tx_skb; - u16 control, fcs; + bt_cb(skb)->retries = 1; + control = &bt_cb(skb)->control; - skb = skb_peek(&chan->tx_q); - if (!skb) - return; + if (pi->conn_state & L2CAP_CONN_SEND_FBIT) { + control->final = 1; + pi->conn_state &= ~L2CAP_CONN_SEND_FBIT; + } + control->reqseq = pi->buffer_seq; + pi->last_acked_seq = pi->buffer_seq; + control->txseq = pi->next_tx_seq; - do { - if (bt_cb(skb)->tx_seq == tx_seq) - break; + if (pi->extended_control) { + put_unaligned_le32(__pack_extended_control(control), + skb->data + L2CAP_HDR_SIZE); + } else { + put_unaligned_le16(__pack_enhanced_control(control), + skb->data + L2CAP_HDR_SIZE); + } - if (skb_queue_is_last(&chan->tx_q, skb)) - return; + if (pi->fcs == L2CAP_FCS_CRC16) + apply_fcs(skb); - } while ((skb = skb_queue_next(&chan->tx_q, skb))); + /* Clone after data has been modified. Data is assumed to be + read-only (for locking purposes) on cloned sk_buffs. + */ + tx_skb = skb_clone(skb, GFP_ATOMIC); - if (chan->remote_max_tx && - bt_cb(skb)->retries == chan->remote_max_tx) { - l2cap_send_disconn_req(chan->conn, chan, ECONNABORTED); - return; - } + tx_skb->sk = sk; + tx_skb->destructor = l2cap_skb_destructor; + atomic_inc(&pi->ertm_queued); - tx_skb = skb_clone(skb, GFP_ATOMIC); - bt_cb(skb)->retries++; - control = get_unaligned_le16(tx_skb->data + L2CAP_HDR_SIZE); - control &= L2CAP_CTRL_SAR; + l2cap_do_send(sk, tx_skb); - if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state)) - control |= L2CAP_CTRL_FINAL; + BT_DBG("Sent txseq %d", (int)control->txseq); - control |= (chan->buffer_seq << L2CAP_CTRL_REQSEQ_SHIFT) - | (tx_seq << L2CAP_CTRL_TXSEQ_SHIFT); + l2cap_ertm_start_retrans_timer(pi); - put_unaligned_le16(control, tx_skb->data + L2CAP_HDR_SIZE); + pi->next_tx_seq = __next_seq(pi->next_tx_seq, pi); + pi->unacked_frames += 1; + pi->frames_sent += 1; + sent += 1; - if (chan->fcs == L2CAP_FCS_CRC16) { - fcs = crc16(0, (u8 *)tx_skb->data, tx_skb->len - 2); - put_unaligned_le16(fcs, tx_skb->data + tx_skb->len - 2); + if (skb_queue_is_last(TX_QUEUE(sk), skb)) + sk->sk_send_head = NULL; + else + sk->sk_send_head = skb_queue_next(TX_QUEUE(sk), skb); } - l2cap_do_send(chan, tx_skb); + BT_DBG("Sent %d, %d unacked, %d in ERTM queue, %d in HCI queue", sent, + (int) pi->unacked_frames, skb_queue_len(TX_QUEUE(sk)), + atomic_read(&pi->ertm_queued)); + + return sent; } -int l2cap_ertm_send(struct l2cap_chan *chan) +int l2cap_strm_tx(struct sock *sk, struct sk_buff_head *skbs) { - struct sk_buff *skb, *tx_skb; - u16 control, fcs; - int nsent = 0; + struct sk_buff *skb; + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct bt_l2cap_control *control; + int sent = 0; - if (chan->state != BT_CONNECTED) + BT_DBG("sk %p, skbs %p", sk, skbs); + + if (sk->sk_state != BT_CONNECTED) return -ENOTCONN; - while ((skb = chan->tx_send_head) && (!l2cap_tx_window_full(chan))) { + if (pi->amp_move_state != L2CAP_AMP_STATE_STABLE && + pi->amp_move_state != L2CAP_AMP_STATE_WAIT_PREPARE) + return 0; - if (chan->remote_max_tx && - bt_cb(skb)->retries == chan->remote_max_tx) { - l2cap_send_disconn_req(chan->conn, chan, ECONNABORTED); - break; - } + skb_queue_splice_tail_init(skbs, TX_QUEUE(sk)); - tx_skb = skb_clone(skb, GFP_ATOMIC); + BT_DBG("skb queue empty 0x%2.2x", skb_queue_empty(TX_QUEUE(sk))); + while (!skb_queue_empty(TX_QUEUE(sk))) { - bt_cb(skb)->retries++; + skb = skb_dequeue(TX_QUEUE(sk)); - control = get_unaligned_le16(tx_skb->data + L2CAP_HDR_SIZE); - control &= L2CAP_CTRL_SAR; + BT_DBG("skb %p", skb); - if (test_and_clear_bit(CONN_SEND_FBIT, &chan->conn_state)) - control |= L2CAP_CTRL_FINAL; + bt_cb(skb)->retries = 1; + control = &bt_cb(skb)->control; - control |= (chan->buffer_seq << L2CAP_CTRL_REQSEQ_SHIFT) - | (chan->next_tx_seq << L2CAP_CTRL_TXSEQ_SHIFT); - put_unaligned_le16(control, tx_skb->data + L2CAP_HDR_SIZE); + BT_DBG("control %p", control); + control->reqseq = 0; + control->txseq = pi->next_tx_seq; - if (chan->fcs == L2CAP_FCS_CRC16) { - fcs = crc16(0, (u8 *)skb->data, tx_skb->len - 2); - put_unaligned_le16(fcs, skb->data + tx_skb->len - 2); + if (pi->extended_control) { + put_unaligned_le32(__pack_extended_control(control), + skb->data + L2CAP_HDR_SIZE); + } else { + put_unaligned_le16(__pack_enhanced_control(control), + skb->data + L2CAP_HDR_SIZE); } - l2cap_do_send(chan, tx_skb); + if (pi->fcs == L2CAP_FCS_CRC16) + apply_fcs(skb); - __set_retrans_timer(chan); + l2cap_do_send(sk, skb); - bt_cb(skb)->tx_seq = chan->next_tx_seq; - chan->next_tx_seq = (chan->next_tx_seq + 1) % 64; - - if (bt_cb(skb)->retries == 1) - chan->unacked_frames++; - - chan->frames_sent++; - - if (skb_queue_is_last(&chan->tx_q, skb)) - chan->tx_send_head = NULL; - else - chan->tx_send_head = skb_queue_next(&chan->tx_q, skb); + BT_DBG("Sent txseq %d", (int)control->txseq); - nsent++; + pi->next_tx_seq = __next_seq(pi->next_tx_seq, pi); + pi->frames_sent += 1; + sent += 1; } - return nsent; -} - -static int l2cap_retransmit_frames(struct l2cap_chan *chan) -{ - int ret; - - if (!skb_queue_empty(&chan->tx_q)) - chan->tx_send_head = chan->tx_q.next; + BT_DBG("Sent %d", sent); - chan->next_tx_seq = chan->expected_ack_seq; - ret = l2cap_ertm_send(chan); - return ret; + return 0; } -static void l2cap_send_ack(struct l2cap_chan *chan) +static int memcpy_fromkvec(unsigned char *kdata, struct kvec *iv, int len) { - u16 control = 0; - - control |= chan->buffer_seq << L2CAP_CTRL_REQSEQ_SHIFT; - - if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { - control |= L2CAP_SUPER_RCV_NOT_READY; - set_bit(CONN_RNR_SENT, &chan->conn_state); - l2cap_send_sframe(chan, control); - return; + while (len > 0) { + if (iv->iov_len) { + int copy = min_t(unsigned int, len, iv->iov_len); + memcpy(kdata, iv->iov_base, copy); + len -= copy; + kdata += copy; + iv->iov_base += copy; + iv->iov_len -= copy; + } + iv++; } - if (l2cap_ertm_send(chan) > 0) - return; - - control |= L2CAP_SUPER_RCV_READY; - l2cap_send_sframe(chan, control); + return 0; } -static void l2cap_send_srejtail(struct l2cap_chan *chan) +static inline int l2cap_skbuff_fromiovec(struct sock *sk, struct msghdr *msg, + int len, int count, struct sk_buff *skb, + int reseg) { - struct srej_list *tail; - u16 control; - - control = L2CAP_SUPER_SELECT_REJECT; - control |= L2CAP_CTRL_FINAL; + struct l2cap_conn *conn = l2cap_pi(sk)->conn; + struct sk_buff **frag; + struct sk_buff *final; + int err, sent = 0; - tail = list_entry((&chan->srej_l)->prev, struct srej_list, list); - control |= tail->tx_seq << L2CAP_CTRL_REQSEQ_SHIFT; + BT_DBG("sk %p, msg %p, len %d, count %d, skb %p", sk, + msg, (int)len, (int)count, skb); - l2cap_send_sframe(chan, control); -} + if (!conn) + return -ENOTCONN; -static inline int l2cap_skbuff_fromiovec(struct sock *sk, struct msghdr *msg, int len, int count, struct sk_buff *skb) -{ - struct l2cap_conn *conn = l2cap_pi(sk)->chan->conn; - struct sk_buff **frag; - int err, sent = 0; + /* When resegmenting, data is copied from kernel space */ + if (reseg) { + err = memcpy_fromkvec(skb_put(skb, count), + (struct kvec *) msg->msg_iov, count); + } else { + err = memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, + count); + } - if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) + if (err) return -EFAULT; sent += count; len -= count; + final = skb; /* Continuation fragments (no L2CAP header) */ frag = &skb_shinfo(skb)->frag_list; while (len) { + int skblen; count = min_t(unsigned int, conn->mtu, len); - *frag = bt_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err); + /* Add room for the FCS if it fits */ + if (bt_cb(skb)->control.fcs == L2CAP_FCS_CRC16 && + len + L2CAP_FCS_SIZE <= conn->mtu) + skblen = count + L2CAP_FCS_SIZE; + else + skblen = count; + + /* Don't use bt_skb_send_alloc() while resegmenting, since + * it is not ok to block. + */ + if (reseg) { + *frag = bt_skb_alloc(skblen, GFP_ATOMIC); + if (*frag) + skb_set_owner_w(*frag, sk); + } else { + *frag = bt_skb_send_alloc(sk, skblen, + msg->msg_flags & MSG_DONTWAIT, &err); + } + if (!*frag) - return err; - if (memcpy_fromiovec(skb_put(*frag, count), msg->msg_iov, count)) + return -EFAULT; + + /* When resegmenting, data is copied from kernel space */ + if (reseg) { + err = memcpy_fromkvec(skb_put(*frag, count), + (struct kvec *) msg->msg_iov, + count); + } else { + err = memcpy_fromiovec(skb_put(*frag, count), + msg->msg_iov, count); + } + + if (err) return -EFAULT; sent += count; len -= count; + final = *frag; + frag = &(*frag)->next; } + if (bt_cb(skb)->control.fcs == L2CAP_FCS_CRC16) { + if (skb_tailroom(final) < L2CAP_FCS_SIZE) { + if (reseg) { + *frag = bt_skb_alloc(L2CAP_FCS_SIZE, + GFP_ATOMIC); + if (*frag) + skb_set_owner_w(*frag, sk); + } else { + *frag = bt_skb_send_alloc(sk, L2CAP_FCS_SIZE, + msg->msg_flags & MSG_DONTWAIT, + &err); + } + + if (!*frag) + return -EFAULT; + + final = *frag; + } + + skb_put(final, L2CAP_FCS_SIZE); + } + return sent; } -struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan, struct msghdr *msg, size_t len) +struct sk_buff *l2cap_create_connless_pdu(struct sock *sk, struct msghdr *msg, size_t len) { - struct sock *sk = chan->sk; - struct l2cap_conn *conn = chan->conn; + struct l2cap_conn *conn = l2cap_pi(sk)->conn; struct sk_buff *skb; int err, count, hlen = L2CAP_HDR_SIZE + 2; struct l2cap_hdr *lh; @@ -1478,11 +1625,11 @@ struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan, struct msghdr /* Create L2CAP header */ lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); - lh->cid = cpu_to_le16(chan->dcid); + lh->cid = cpu_to_le16(l2cap_pi(sk)->dcid); lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE)); - put_unaligned_le16(chan->psm, skb_put(skb, 2)); + put_unaligned_le16(l2cap_pi(sk)->psm, skb_put(skb, 2)); - err = l2cap_skbuff_fromiovec(sk, msg, len, count, skb); + err = l2cap_skbuff_fromiovec(sk, msg, len, count, skb, 0); if (unlikely(err < 0)) { kfree_skb(skb); return ERR_PTR(err); @@ -1490,10 +1637,9 @@ struct sk_buff *l2cap_create_connless_pdu(struct l2cap_chan *chan, struct msghdr return skb; } -struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan, struct msghdr *msg, size_t len) +struct sk_buff *l2cap_create_basic_pdu(struct sock *sk, struct msghdr *msg, size_t len) { - struct sock *sk = chan->sk; - struct l2cap_conn *conn = chan->conn; + struct l2cap_conn *conn = l2cap_pi(sk)->conn; struct sk_buff *skb; int err, count, hlen = L2CAP_HDR_SIZE; struct l2cap_hdr *lh; @@ -1508,10 +1654,10 @@ struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan, struct msghdr *m /* Create L2CAP header */ lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); - lh->cid = cpu_to_le16(chan->dcid); + lh->cid = cpu_to_le16(l2cap_pi(sk)->dcid); lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE)); - err = l2cap_skbuff_fromiovec(sk, msg, len, count, skb); + err = l2cap_skbuff_fromiovec(sk, msg, len, count, skb, 0); if (unlikely(err < 0)) { kfree_skb(skb); return ERR_PTR(err); @@ -1519,1329 +1665,3806 @@ struct sk_buff *l2cap_create_basic_pdu(struct l2cap_chan *chan, struct msghdr *m return skb; } -struct sk_buff *l2cap_create_iframe_pdu(struct l2cap_chan *chan, struct msghdr *msg, size_t len, u16 control, u16 sdulen) +struct sk_buff *l2cap_create_iframe_pdu(struct sock *sk, + struct msghdr *msg, size_t len, + u16 sdulen, int reseg) { - struct sock *sk = chan->sk; - struct l2cap_conn *conn = chan->conn; struct sk_buff *skb; - int err, count, hlen = L2CAP_HDR_SIZE + 2; + int err, count, hlen; + int reserve = 0; struct l2cap_hdr *lh; + u8 fcs = l2cap_pi(sk)->fcs; - BT_DBG("sk %p len %d", sk, (int)len); - - if (!conn) - return ERR_PTR(-ENOTCONN); + if (l2cap_pi(sk)->extended_control) + hlen = L2CAP_EXTENDED_HDR_SIZE; + else + hlen = L2CAP_ENHANCED_HDR_SIZE; if (sdulen) - hlen += 2; + hlen += L2CAP_SDULEN_SIZE; - if (chan->fcs == L2CAP_FCS_CRC16) - hlen += 2; + if (fcs == L2CAP_FCS_CRC16) + hlen += L2CAP_FCS_SIZE; - count = min_t(unsigned int, (conn->mtu - hlen), len); - skb = bt_skb_send_alloc(sk, count + hlen, - msg->msg_flags & MSG_DONTWAIT, &err); + BT_DBG("sk %p, msg %p, len %d, sdulen %d, hlen %d", + sk, msg, (int)len, (int)sdulen, hlen); + + count = min_t(unsigned int, (l2cap_pi(sk)->conn->mtu - hlen), len); + + /* Allocate extra headroom for Qualcomm PAL. This is only + * necessary in two places (here and when creating sframes) + * because only unfragmented iframes and sframes are sent + * using AMP controllers. + */ + if (l2cap_pi(sk)->ampcon && + l2cap_pi(sk)->ampcon->hdev->manufacturer == 0x001d) + reserve = BT_SKB_RESERVE_80211; + + /* Don't use bt_skb_send_alloc() while resegmenting, since + * it is not ok to block. + */ + if (reseg) { + skb = bt_skb_alloc(count + hlen + reserve, GFP_ATOMIC); + if (skb) + skb_set_owner_w(skb, sk); + } else { + skb = bt_skb_send_alloc(sk, count + hlen + reserve, + msg->msg_flags & MSG_DONTWAIT, &err); + } if (!skb) return ERR_PTR(err); + if (reserve) + skb_reserve(skb, reserve); + + bt_cb(skb)->control.fcs = fcs; + /* Create L2CAP header */ lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); - lh->cid = cpu_to_le16(chan->dcid); - lh->len = cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE)); - put_unaligned_le16(control, skb_put(skb, 2)); + lh->cid = cpu_to_le16(l2cap_pi(sk)->dcid); + lh->len = cpu_to_le16(len + hlen - L2CAP_HDR_SIZE); + + /* Control header is populated later */ + if (l2cap_pi(sk)->extended_control) + put_unaligned_le32(0, skb_put(skb, 4)); + else + put_unaligned_le16(0, skb_put(skb, 2)); + if (sdulen) - put_unaligned_le16(sdulen, skb_put(skb, 2)); + put_unaligned_le16(sdulen, skb_put(skb, L2CAP_SDULEN_SIZE)); - err = l2cap_skbuff_fromiovec(sk, msg, len, count, skb); + err = l2cap_skbuff_fromiovec(sk, msg, len, count, skb, reseg); if (unlikely(err < 0)) { + BT_DBG("err %d", err); kfree_skb(skb); return ERR_PTR(err); } - if (chan->fcs == L2CAP_FCS_CRC16) - put_unaligned_le16(0, skb_put(skb, 2)); - bt_cb(skb)->retries = 0; return skb; } -int l2cap_sar_segment_sdu(struct l2cap_chan *chan, struct msghdr *msg, size_t len) +static void l2cap_ertm_process_reqseq(struct sock *sk, u16 reqseq) { - struct sk_buff *skb; - struct sk_buff_head sar_queue; - u16 control; - size_t size = 0; + struct l2cap_pinfo *pi; + struct sk_buff *acked_skb; + u16 ackseq; - skb_queue_head_init(&sar_queue); - control = L2CAP_SDU_START; - skb = l2cap_create_iframe_pdu(chan, msg, chan->remote_mps, control, len); - if (IS_ERR(skb)) - return PTR_ERR(skb); + BT_DBG("sk %p, reqseq %d", sk, (int) reqseq); - __skb_queue_tail(&sar_queue, skb); - len -= chan->remote_mps; - size += chan->remote_mps; + pi = l2cap_pi(sk); - while (len > 0) { - size_t buflen; + if (pi->unacked_frames == 0 || reqseq == pi->expected_ack_seq) + return; - if (len > chan->remote_mps) { - control = L2CAP_SDU_CONTINUE; - buflen = chan->remote_mps; - } else { - control = L2CAP_SDU_END; - buflen = len; - } + BT_DBG("expected_ack_seq %d, unacked_frames %d", + (int) pi->expected_ack_seq, (int) pi->unacked_frames); - skb = l2cap_create_iframe_pdu(chan, msg, buflen, control, 0); - if (IS_ERR(skb)) { - skb_queue_purge(&sar_queue); - return PTR_ERR(skb); + for (ackseq = pi->expected_ack_seq; ackseq != reqseq; + ackseq = __next_seq(ackseq, pi)) { + + acked_skb = l2cap_ertm_seq_in_queue(TX_QUEUE(sk), ackseq); + if (acked_skb) { + skb_unlink(acked_skb, TX_QUEUE(sk)); + kfree_skb(acked_skb); + pi->unacked_frames--; } + } + + pi->expected_ack_seq = reqseq; + + if (pi->unacked_frames == 0) + l2cap_ertm_stop_retrans_timer(pi); + + BT_DBG("unacked_frames %d", (int) pi->unacked_frames); +} + +static struct sk_buff *l2cap_create_sframe_pdu(struct sock *sk, u32 control) +{ + struct sk_buff *skb; + int len; + int reserve = 0; + struct l2cap_hdr *lh; + + if (l2cap_pi(sk)->extended_control) + len = L2CAP_EXTENDED_HDR_SIZE; + else + len = L2CAP_ENHANCED_HDR_SIZE; - __skb_queue_tail(&sar_queue, skb); - len -= buflen; - size += buflen; + if (l2cap_pi(sk)->fcs == L2CAP_FCS_CRC16) + len += L2CAP_FCS_SIZE; + + /* Allocate extra headroom for Qualcomm PAL */ + if (l2cap_pi(sk)->ampcon && + l2cap_pi(sk)->ampcon->hdev->manufacturer == 0x001d) + reserve = BT_SKB_RESERVE_80211; + + skb = bt_skb_alloc(len + reserve, GFP_ATOMIC); + + if (!skb) + return ERR_PTR(-ENOMEM); + + if (reserve) + skb_reserve(skb, reserve); + + lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); + lh->cid = cpu_to_le16(l2cap_pi(sk)->dcid); + lh->len = cpu_to_le16(len - L2CAP_HDR_SIZE); + + if (l2cap_pi(sk)->extended_control) + put_unaligned_le32(control, skb_put(skb, 4)); + else + put_unaligned_le16(control, skb_put(skb, 2)); + + if (l2cap_pi(sk)->fcs == L2CAP_FCS_CRC16) { + u16 fcs = crc16(0, (u8 *) skb->data, skb->len); + put_unaligned_le16(fcs, skb_put(skb, L2CAP_FCS_SIZE)); } - skb_queue_splice_tail(&sar_queue, &chan->tx_q); - if (chan->tx_send_head == NULL) - chan->tx_send_head = sar_queue.next; - return size; + return skb; } -int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len) +static void l2cap_ertm_send_sframe(struct sock *sk, + struct bt_l2cap_control *control) { + struct l2cap_pinfo *pi; struct sk_buff *skb; - u16 control; - int err; + u32 control_field; - /* Connectionless channel */ - if (chan->chan_type == L2CAP_CHAN_CONN_LESS) { - skb = l2cap_create_connless_pdu(chan, msg, len); - if (IS_ERR(skb)) - return PTR_ERR(skb); + BT_DBG("sk %p, control %p", sk, control); + + if (control->frame_type != 's') + return; + + pi = l2cap_pi(sk); - l2cap_do_send(chan, skb); - return len; + if (pi->amp_move_state != L2CAP_AMP_STATE_STABLE && + pi->amp_move_state != L2CAP_AMP_STATE_WAIT_PREPARE && + pi->amp_move_state != L2CAP_AMP_STATE_RESEGMENT) { + BT_DBG("AMP error - attempted S-Frame send during AMP move"); + return; } - switch (chan->mode) { - case L2CAP_MODE_BASIC: - /* Check outgoing MTU */ - if (len > chan->omtu) - return -EMSGSIZE; + if ((pi->conn_state & L2CAP_CONN_SEND_FBIT) && !control->poll) { + control->final = 1; + pi->conn_state &= ~L2CAP_CONN_SEND_FBIT; + } - /* Create a basic PDU */ - skb = l2cap_create_basic_pdu(chan, msg, len); - if (IS_ERR(skb)) - return PTR_ERR(skb); + if (control->super == L2CAP_SFRAME_RR) + pi->conn_state &= ~L2CAP_CONN_SENT_RNR; + else if (control->super == L2CAP_SFRAME_RNR) + pi->conn_state |= L2CAP_CONN_SENT_RNR; - l2cap_do_send(chan, skb); - err = len; - break; + if (control->super != L2CAP_SFRAME_SREJ) { + pi->last_acked_seq = control->reqseq; + l2cap_ertm_stop_ack_timer(pi); + } - case L2CAP_MODE_ERTM: - case L2CAP_MODE_STREAMING: - /* Entire SDU fits into one PDU */ - if (len <= chan->remote_mps) { - control = L2CAP_SDU_UNSEGMENTED; - skb = l2cap_create_iframe_pdu(chan, msg, len, control, - 0); - if (IS_ERR(skb)) - return PTR_ERR(skb); + BT_DBG("reqseq %d, final %d, poll %d, super %d", (int) control->reqseq, + (int) control->final, (int) control->poll, + (int) control->super); - __skb_queue_tail(&chan->tx_q, skb); + if (pi->extended_control) + control_field = __pack_extended_control(control); + else + control_field = __pack_enhanced_control(control); - if (chan->tx_send_head == NULL) - chan->tx_send_head = skb; + skb = l2cap_create_sframe_pdu(sk, control_field); + if (!IS_ERR(skb)) + l2cap_do_send(sk, skb); +} - } else { - /* Segment SDU into multiples PDUs */ - err = l2cap_sar_segment_sdu(chan, msg, len); - if (err < 0) - return err; +static void l2cap_ertm_send_ack(struct sock *sk) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct bt_l2cap_control control; + u16 frames_to_ack = __delta_seq(pi->buffer_seq, pi->last_acked_seq, pi); + int threshold; + + BT_DBG("sk %p", sk); + BT_DBG("last_acked_seq %d, buffer_seq %d", (int)pi->last_acked_seq, + (int)pi->buffer_seq); + + memset(&control, 0, sizeof(control)); + control.frame_type = 's'; + + if ((pi->conn_state & L2CAP_CONN_LOCAL_BUSY) && + pi->rx_state == L2CAP_ERTM_RX_STATE_RECV) { + l2cap_ertm_stop_ack_timer(pi); + control.super = L2CAP_SFRAME_RNR; + control.reqseq = pi->buffer_seq; + l2cap_ertm_send_sframe(sk, &control); + } else { + if (!(pi->conn_state & L2CAP_CONN_REMOTE_BUSY)) { + l2cap_ertm_send(sk); + /* If any i-frames were sent, they included an ack */ + if (pi->buffer_seq == pi->last_acked_seq) + frames_to_ack = 0; } - if (chan->mode == L2CAP_MODE_STREAMING) { - l2cap_streaming_send(chan); - err = len; - break; + /* Ack now if the tx window is 3/4ths full. + * Calculate without mul or div + */ + threshold = pi->tx_win; + threshold += threshold << 1; + threshold >>= 2; + + BT_DBG("frames_to_ack %d, threshold %d", (int)frames_to_ack, + threshold); + + if (frames_to_ack >= threshold) { + l2cap_ertm_stop_ack_timer(pi); + control.super = L2CAP_SFRAME_RR; + control.reqseq = pi->buffer_seq; + l2cap_ertm_send_sframe(sk, &control); + frames_to_ack = 0; } - if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state) && - test_bit(CONN_WAIT_F, &chan->conn_state)) { - err = len; - break; - } + if (frames_to_ack) + l2cap_ertm_start_ack_timer(pi); + } +} - err = l2cap_ertm_send(chan); - if (err >= 0) - err = len; +static void l2cap_ertm_send_rr_or_rnr(struct sock *sk, bool poll) +{ + struct l2cap_pinfo *pi; + struct bt_l2cap_control control; - break; + BT_DBG("sk %p, poll %d", sk, (int) poll); - default: - BT_DBG("bad state %1.1x", chan->mode); - err = -EBADFD; - } + pi = l2cap_pi(sk); - return err; + memset(&control, 0, sizeof(control)); + control.frame_type = 's'; + control.poll = poll; + + if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) + control.super = L2CAP_SFRAME_RNR; + else + control.super = L2CAP_SFRAME_RR; + + control.reqseq = pi->buffer_seq; + l2cap_ertm_send_sframe(sk, &control); } -/* Copy frame to all raw sockets on that connection */ -static void l2cap_raw_recv(struct l2cap_conn *conn, struct sk_buff *skb) +static void l2cap_ertm_send_i_or_rr_or_rnr(struct sock *sk) { - struct sk_buff *nskb; - struct l2cap_chan *chan; + struct l2cap_pinfo *pi; + struct bt_l2cap_control control; - BT_DBG("conn %p", conn); + BT_DBG("sk %p", sk); - read_lock(&conn->chan_lock); - list_for_each_entry(chan, &conn->chan_l, list) { - struct sock *sk = chan->sk; - if (chan->chan_type != L2CAP_CHAN_RAW) - continue; + pi = l2cap_pi(sk); - /* Don't send frame to the socket it came from */ - if (skb->sk == sk) - continue; - nskb = skb_clone(skb, GFP_ATOMIC); - if (!nskb) - continue; + memset(&control, 0, sizeof(control)); + control.frame_type = 's'; + control.final = 1; + control.reqseq = pi->buffer_seq; + pi->conn_state |= L2CAP_CONN_SEND_FBIT; - if (chan->ops->recv(chan->data, nskb)) - kfree_skb(nskb); + if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) { + control.super = L2CAP_SFRAME_RNR; + l2cap_ertm_send_sframe(sk, &control); + } + + if ((pi->conn_state & L2CAP_CONN_REMOTE_BUSY) && + (pi->unacked_frames > 0)) + l2cap_ertm_start_retrans_timer(pi); + + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; + + /* Send pending iframes */ + l2cap_ertm_send(sk); + + if (pi->conn_state & L2CAP_CONN_SEND_FBIT) { + /* F-bit wasn't sent in an s-frame or i-frame yet, so + * send it now. + */ + control.super = L2CAP_SFRAME_RR; + l2cap_ertm_send_sframe(sk, &control); } - read_unlock(&conn->chan_lock); } -/* ---- L2CAP signalling commands ---- */ -static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn, - u8 code, u8 ident, u16 dlen, void *data) +static void l2cap_ertm_send_srej(struct sock *sk, u16 txseq) { - struct sk_buff *skb, **frag; - struct l2cap_cmd_hdr *cmd; - struct l2cap_hdr *lh; - int len, count; - - BT_DBG("conn %p, code 0x%2.2x, ident 0x%2.2x, len %d", - conn, code, ident, dlen); + struct bt_l2cap_control control; + struct l2cap_pinfo *pi; + u16 seq; + + BT_DBG("sk %p, txseq %d", sk, (int)txseq); + + pi = l2cap_pi(sk); + memset(&control, 0, sizeof(control)); + control.frame_type = 's'; + control.super = L2CAP_SFRAME_SREJ; + + for (seq = pi->expected_tx_seq; seq != txseq; + seq = __next_seq(seq, pi)) { + if (!l2cap_ertm_seq_in_queue(SREJ_QUEUE(pi), seq)) { + control.reqseq = seq; + l2cap_ertm_send_sframe(sk, &control); + l2cap_seq_list_append(&pi->srej_list, seq); + } + } - len = L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE + dlen; - count = min_t(unsigned int, conn->mtu, len); + pi->expected_tx_seq = __next_seq(txseq, pi); +} - skb = bt_skb_alloc(count, GFP_ATOMIC); - if (!skb) - return NULL; +static void l2cap_ertm_send_srej_tail(struct sock *sk) +{ + struct bt_l2cap_control control; + struct l2cap_pinfo *pi; - lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); - lh->len = cpu_to_le16(L2CAP_CMD_HDR_SIZE + dlen); + BT_DBG("sk %p", sk); - if (conn->hcon->type == LE_LINK) - lh->cid = cpu_to_le16(L2CAP_CID_LE_SIGNALING); - else - lh->cid = cpu_to_le16(L2CAP_CID_SIGNALING); + pi = l2cap_pi(sk); - cmd = (struct l2cap_cmd_hdr *) skb_put(skb, L2CAP_CMD_HDR_SIZE); - cmd->code = code; - cmd->ident = ident; - cmd->len = cpu_to_le16(dlen); + if (pi->srej_list.tail == L2CAP_SEQ_LIST_CLEAR) + return; - if (dlen) { - count -= L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE; - memcpy(skb_put(skb, count), data, count); - data += count; - } + memset(&control, 0, sizeof(control)); + control.frame_type = 's'; + control.super = L2CAP_SFRAME_SREJ; + control.reqseq = pi->srej_list.tail; + l2cap_ertm_send_sframe(sk, &control); +} - len -= skb->len; +static void l2cap_ertm_send_srej_list(struct sock *sk, u16 txseq) +{ + struct bt_l2cap_control control; + struct l2cap_pinfo *pi; + u16 initial_head; + u16 seq; - /* Continuation fragments (no L2CAP header) */ - frag = &skb_shinfo(skb)->frag_list; - while (len) { - count = min_t(unsigned int, conn->mtu, len); + BT_DBG("sk %p, txseq %d", sk, (int) txseq); - *frag = bt_skb_alloc(count, GFP_ATOMIC); - if (!*frag) - goto fail; + pi = l2cap_pi(sk); + memset(&control, 0, sizeof(control)); + control.frame_type = 's'; + control.super = L2CAP_SFRAME_SREJ; - memcpy(skb_put(*frag, count), data, count); + /* Capture initial list head to allow only one pass through the list. */ + initial_head = pi->srej_list.head; - len -= count; - data += count; + do { + seq = l2cap_seq_list_pop(&pi->srej_list); + if ((seq == txseq) || (seq == L2CAP_SEQ_LIST_CLEAR)) + break; - frag = &(*frag)->next; - } + control.reqseq = seq; + l2cap_ertm_send_sframe(sk, &control); + l2cap_seq_list_append(&pi->srej_list, seq); + } while (pi->srej_list.head != initial_head); +} - return skb; +static void l2cap_ertm_abort_rx_srej_sent(struct sock *sk) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + BT_DBG("sk %p", sk); -fail: - kfree_skb(skb); - return NULL; + pi->expected_tx_seq = pi->buffer_seq; + l2cap_seq_list_clear(&l2cap_pi(sk)->srej_list); + skb_queue_purge(SREJ_QUEUE(sk)); + pi->rx_state = L2CAP_ERTM_RX_STATE_RECV; } -static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, unsigned long *val) +static int l2cap_ertm_tx_state_xmit(struct sock *sk, + struct bt_l2cap_control *control, + struct sk_buff_head *skbs, u8 event) { - struct l2cap_conf_opt *opt = *ptr; - int len; + struct l2cap_pinfo *pi; + int err = 0; - len = L2CAP_CONF_OPT_SIZE + opt->len; - *ptr += len; + BT_DBG("sk %p, control %p, skbs %p, event %d", sk, control, skbs, + (int)event); + pi = l2cap_pi(sk); - *type = opt->type; - *olen = opt->len; + switch (event) { + case L2CAP_ERTM_EVENT_DATA_REQUEST: + if (sk->sk_send_head == NULL) + sk->sk_send_head = skb_peek(skbs); - switch (opt->len) { - case 1: - *val = *((u8 *) opt->val); + skb_queue_splice_tail_init(skbs, TX_QUEUE(sk)); + l2cap_ertm_send(sk); break; + case L2CAP_ERTM_EVENT_LOCAL_BUSY_DETECTED: + BT_DBG("Enter LOCAL_BUSY"); + pi->conn_state |= L2CAP_CONN_LOCAL_BUSY; + + if (pi->rx_state == L2CAP_ERTM_RX_STATE_SREJ_SENT) { + /* The SREJ_SENT state must be aborted if we are to + * enter the LOCAL_BUSY state. + */ + l2cap_ertm_abort_rx_srej_sent(sk); + } - case 2: - *val = get_unaligned_le16(opt->val); - break; + l2cap_ertm_send_ack(sk); - case 4: - *val = get_unaligned_le32(opt->val); break; + case L2CAP_ERTM_EVENT_LOCAL_BUSY_CLEAR: + BT_DBG("Exit LOCAL_BUSY"); + pi->conn_state &= ~L2CAP_CONN_LOCAL_BUSY; + + if (pi->amp_move_state == L2CAP_AMP_STATE_WAIT_LOCAL_BUSY) { + if (pi->amp_move_role == L2CAP_AMP_MOVE_INITIATOR) { + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM_RSP; + l2cap_send_move_chan_cfm(pi->conn, pi, + pi->scid, + L2CAP_MOVE_CHAN_CONFIRMED); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); + } else if (pi->amp_move_role == + L2CAP_AMP_MOVE_RESPONDER) { + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM; + l2cap_send_move_chan_rsp(pi->conn, + pi->amp_move_cmd_ident, + pi->dcid, + L2CAP_MOVE_CHAN_SUCCESS); + } + break; + } + if (pi->amp_move_role == L2CAP_AMP_MOVE_NONE && + (pi->conn_state & L2CAP_CONN_SENT_RNR)) { + struct bt_l2cap_control local_control; + + memset(&local_control, 0, sizeof(local_control)); + local_control.frame_type = 's'; + local_control.super = L2CAP_SFRAME_RR; + local_control.poll = 1; + local_control.reqseq = pi->buffer_seq; + l2cap_ertm_send_sframe(sk, &local_control); + + pi->retry_count = 1; + l2cap_ertm_start_monitor_timer(pi); + pi->tx_state = L2CAP_ERTM_TX_STATE_WAIT_F; + } + break; + case L2CAP_ERTM_EVENT_RECV_REQSEQ_AND_FBIT: + l2cap_ertm_process_reqseq(sk, control->reqseq); + break; + case L2CAP_ERTM_EVENT_EXPLICIT_POLL: + l2cap_ertm_send_rr_or_rnr(sk, 1); + pi->retry_count = 1; + l2cap_ertm_start_monitor_timer(pi); + l2cap_ertm_stop_ack_timer(pi); + pi->tx_state = L2CAP_ERTM_TX_STATE_WAIT_F; + break; + case L2CAP_ERTM_EVENT_RETRANS_TIMER_EXPIRES: + l2cap_ertm_send_rr_or_rnr(sk, 1); + pi->retry_count = 1; + l2cap_ertm_start_monitor_timer(pi); + pi->tx_state = L2CAP_ERTM_TX_STATE_WAIT_F; + break; + case L2CAP_ERTM_EVENT_RECV_FBIT: + /* Nothing to process */ + break; default: - *val = (unsigned long) opt->val; break; } - BT_DBG("type 0x%2.2x len %d val 0x%lx", *type, opt->len, *val); - return len; + return err; } -static void l2cap_add_conf_opt(void **ptr, u8 type, u8 len, unsigned long val) +static int l2cap_ertm_tx_state_wait_f(struct sock *sk, + struct bt_l2cap_control *control, + struct sk_buff_head *skbs, u8 event) { - struct l2cap_conf_opt *opt = *ptr; - - BT_DBG("type 0x%2.2x len %d val 0x%lx", type, len, val); + struct l2cap_pinfo *pi; + int err = 0; - opt->type = type; - opt->len = len; + BT_DBG("sk %p, control %p, skbs %p, event %d", sk, control, skbs, + (int)event); + pi = l2cap_pi(sk); - switch (len) { - case 1: - *((u8 *) opt->val) = val; + switch (event) { + case L2CAP_ERTM_EVENT_DATA_REQUEST: + if (sk->sk_send_head == NULL) + sk->sk_send_head = skb_peek(skbs); + /* Queue data, but don't send. */ + skb_queue_splice_tail_init(skbs, TX_QUEUE(sk)); break; + case L2CAP_ERTM_EVENT_LOCAL_BUSY_DETECTED: + BT_DBG("Enter LOCAL_BUSY"); + pi->conn_state |= L2CAP_CONN_LOCAL_BUSY; + + if (pi->rx_state == L2CAP_ERTM_RX_STATE_SREJ_SENT) { + /* The SREJ_SENT state must be aborted if we are to + * enter the LOCAL_BUSY state. + */ + l2cap_ertm_abort_rx_srej_sent(sk); + } - case 2: - put_unaligned_le16(val, opt->val); - break; + l2cap_ertm_send_ack(sk); - case 4: - put_unaligned_le32(val, opt->val); break; - + case L2CAP_ERTM_EVENT_LOCAL_BUSY_CLEAR: + BT_DBG("Exit LOCAL_BUSY"); + pi->conn_state &= ~L2CAP_CONN_LOCAL_BUSY; + + if (pi->conn_state & L2CAP_CONN_SENT_RNR) { + struct bt_l2cap_control local_control; + memset(&local_control, 0, sizeof(local_control)); + local_control.frame_type = 's'; + local_control.super = L2CAP_SFRAME_RR; + local_control.poll = 1; + local_control.reqseq = pi->buffer_seq; + l2cap_ertm_send_sframe(sk, &local_control); + + pi->retry_count = 1; + l2cap_ertm_start_monitor_timer(pi); + pi->tx_state = L2CAP_ERTM_TX_STATE_WAIT_F; + } + break; + case L2CAP_ERTM_EVENT_RECV_REQSEQ_AND_FBIT: + l2cap_ertm_process_reqseq(sk, control->reqseq); + + /* Fall through */ + + case L2CAP_ERTM_EVENT_RECV_FBIT: + if (control && control->final) { + l2cap_ertm_stop_monitor_timer(pi); + if (pi->unacked_frames > 0) + l2cap_ertm_start_retrans_timer(pi); + pi->retry_count = 0; + pi->tx_state = L2CAP_ERTM_TX_STATE_XMIT; + BT_DBG("recv fbit tx_state 0x2.2%x", pi->tx_state); + } + break; + case L2CAP_ERTM_EVENT_EXPLICIT_POLL: + /* Ignore */ + break; + case L2CAP_ERTM_EVENT_MONITOR_TIMER_EXPIRES: + if ((pi->max_tx == 0) || (pi->retry_count < pi->max_tx)) { + l2cap_ertm_send_rr_or_rnr(sk, 1); + l2cap_ertm_start_monitor_timer(pi); + pi->retry_count += 1; + } else + l2cap_send_disconn_req(pi->conn, sk, ECONNABORTED); + break; default: - memcpy(opt->val, (void *) val, len); break; } - *ptr += L2CAP_CONF_OPT_SIZE + len; + return err; } -static void l2cap_ack_timeout(unsigned long arg) +int l2cap_ertm_tx(struct sock *sk, struct bt_l2cap_control *control, + struct sk_buff_head *skbs, u8 event) { - struct l2cap_chan *chan = (void *) arg; + struct l2cap_pinfo *pi; + int err = 0; - bh_lock_sock(chan->sk); - l2cap_send_ack(chan); - bh_unlock_sock(chan->sk); + BT_DBG("sk %p, control %p, skbs %p, event %d, state %d", + sk, control, skbs, (int)event, l2cap_pi(sk)->tx_state); + + pi = l2cap_pi(sk); + + switch (pi->tx_state) { + case L2CAP_ERTM_TX_STATE_XMIT: + err = l2cap_ertm_tx_state_xmit(sk, control, skbs, event); + break; + case L2CAP_ERTM_TX_STATE_WAIT_F: + err = l2cap_ertm_tx_state_wait_f(sk, control, skbs, event); + break; + default: + /* Ignore event */ + break; + } + + return err; } -static inline void l2cap_ertm_init(struct l2cap_chan *chan) +int l2cap_segment_sdu(struct sock *sk, struct sk_buff_head* seg_queue, + struct msghdr *msg, size_t len, int reseg) { - struct sock *sk = chan->sk; + struct sk_buff *skb; + u16 sdu_len; + size_t pdu_len; + int err = 0; + u8 sar; - chan->expected_ack_seq = 0; - chan->unacked_frames = 0; - chan->buffer_seq = 0; - chan->num_acked = 0; - chan->frames_sent = 0; + BT_DBG("sk %p, msg %p, len %d", sk, msg, (int)len); - setup_timer(&chan->retrans_timer, l2cap_retrans_timeout, - (unsigned long) chan); - setup_timer(&chan->monitor_timer, l2cap_monitor_timeout, - (unsigned long) chan); - setup_timer(&chan->ack_timer, l2cap_ack_timeout, (unsigned long) chan); + /* It is critical that ERTM PDUs fit in a single HCI fragment, + * so fragmented skbs are not used. The HCI layer's handling + * of fragmented skbs is not compatible with ERTM's queueing. + */ - skb_queue_head_init(&chan->srej_q); + /* PDU size is derived from the HCI MTU */ + pdu_len = l2cap_pi(sk)->conn->mtu; - INIT_LIST_HEAD(&chan->srej_l); + /* Constrain BR/EDR PDU size to fit within the largest radio packet */ + if (!l2cap_pi(sk)->ampcon) + pdu_len = min_t(size_t, pdu_len, L2CAP_BREDR_MAX_PAYLOAD); + /* Adjust for largest possible L2CAP overhead. */ + pdu_len -= L2CAP_EXTENDED_HDR_SIZE + L2CAP_FCS_SIZE; - sk->sk_backlog_rcv = l2cap_ertm_data_rcv; -} + /* Remote device may have requested smaller PDUs */ + pdu_len = min_t(size_t, pdu_len, l2cap_pi(sk)->remote_mps); -static inline __u8 l2cap_select_mode(__u8 mode, __u16 remote_feat_mask) -{ - switch (mode) { - case L2CAP_MODE_STREAMING: - case L2CAP_MODE_ERTM: - if (l2cap_mode_supported(mode, remote_feat_mask)) - return mode; - /* fall through */ - default: - return L2CAP_MODE_BASIC; + if (len <= pdu_len) { + sar = L2CAP_SAR_UNSEGMENTED; + sdu_len = 0; + pdu_len = len; + } else { + sar = L2CAP_SAR_START; + sdu_len = len; + pdu_len -= L2CAP_SDULEN_SIZE; } -} -static int l2cap_build_conf_req(struct l2cap_chan *chan, void *data) -{ - struct l2cap_conf_req *req = data; - struct l2cap_conf_rfc rfc = { .mode = chan->mode }; - void *ptr = req->data; + while (len) { + skb = l2cap_create_iframe_pdu(sk, msg, pdu_len, sdu_len, reseg); - BT_DBG("chan %p", chan); + BT_DBG("iframe skb %p", skb); - if (chan->num_conf_req || chan->num_conf_rsp) - goto done; + if (IS_ERR(skb)) { + __skb_queue_purge(seg_queue); + return PTR_ERR(skb); + } - switch (chan->mode) { - case L2CAP_MODE_STREAMING: - case L2CAP_MODE_ERTM: - if (test_bit(CONF_STATE2_DEVICE, &chan->conf_state)) - break; + bt_cb(skb)->control.sar = sar; + __skb_queue_tail(seg_queue, skb); - /* fall through */ - default: - chan->mode = l2cap_select_mode(rfc.mode, chan->conn->feat_mask); - break; + len -= pdu_len; + if (sdu_len) { + sdu_len = 0; + pdu_len += L2CAP_SDULEN_SIZE; + } + + if (len <= pdu_len) { + sar = L2CAP_SAR_END; + pdu_len = len; + } else { + sar = L2CAP_SAR_CONTINUE; + } } -done: - if (chan->imtu != L2CAP_DEFAULT_MTU) - l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, chan->imtu); + return err; +} - switch (chan->mode) { - case L2CAP_MODE_BASIC: - if (!(chan->conn->feat_mask & L2CAP_FEAT_ERTM) && - !(chan->conn->feat_mask & L2CAP_FEAT_STREAMING)) - break; +static inline int is_initial_frame(u8 sar) +{ + return (sar == L2CAP_SAR_UNSEGMENTED || + sar == L2CAP_SAR_START); +} - rfc.mode = L2CAP_MODE_BASIC; - rfc.txwin_size = 0; - rfc.max_transmit = 0; - rfc.retrans_timeout = 0; - rfc.monitor_timeout = 0; - rfc.max_pdu_size = 0; +static inline int l2cap_skbuff_to_kvec(struct sk_buff *skb, struct kvec *iv, + size_t veclen) +{ + struct sk_buff *frag_iter; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc), - (unsigned long) &rfc); - break; + BT_DBG("skb %p (len %d), iv %p", skb, (int)skb->len, iv); - case L2CAP_MODE_ERTM: - rfc.mode = L2CAP_MODE_ERTM; - rfc.txwin_size = chan->tx_win; - rfc.max_transmit = chan->max_tx; - rfc.retrans_timeout = 0; - rfc.monitor_timeout = 0; - rfc.max_pdu_size = cpu_to_le16(L2CAP_DEFAULT_MAX_PDU_SIZE); - if (L2CAP_DEFAULT_MAX_PDU_SIZE > chan->conn->mtu - 10) - rfc.max_pdu_size = cpu_to_le16(chan->conn->mtu - 10); + if (iv->iov_len + skb->len > veclen) + return -ENOMEM; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc), - (unsigned long) &rfc); + memcpy(iv->iov_base + iv->iov_len, skb->data, skb->len); + iv->iov_len += skb->len; - if (!(chan->conn->feat_mask & L2CAP_FEAT_FCS)) - break; + skb_walk_frags(skb, frag_iter) { + if (iv->iov_len + skb->len > veclen) + return -ENOMEM; - if (chan->fcs == L2CAP_FCS_NONE || - test_bit(CONF_NO_FCS_RECV, &chan->conf_state)) { - chan->fcs = L2CAP_FCS_NONE; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, chan->fcs); - } - break; + BT_DBG("Copying %d bytes", (int)frag_iter->len); + memcpy(iv->iov_base + iv->iov_len, frag_iter->data, + frag_iter->len); + iv->iov_len += frag_iter->len; + } - case L2CAP_MODE_STREAMING: - rfc.mode = L2CAP_MODE_STREAMING; - rfc.txwin_size = 0; - rfc.max_transmit = 0; - rfc.retrans_timeout = 0; - rfc.monitor_timeout = 0; - rfc.max_pdu_size = cpu_to_le16(L2CAP_DEFAULT_MAX_PDU_SIZE); - if (L2CAP_DEFAULT_MAX_PDU_SIZE > chan->conn->mtu - 10) - rfc.max_pdu_size = cpu_to_le16(chan->conn->mtu - 10); + return 0; +} - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc), - (unsigned long) &rfc); +int l2cap_resegment_queue(struct sock *sk, struct sk_buff_head *queue) +{ + void *buf; + int buflen; + int err = 0; + struct sk_buff *skb; + struct msghdr msg; + struct kvec iv; + struct sk_buff_head old_frames; + struct l2cap_pinfo *pi = l2cap_pi(sk); - if (!(chan->conn->feat_mask & L2CAP_FEAT_FCS)) - break; + BT_DBG("sk %p", sk); - if (chan->fcs == L2CAP_FCS_NONE || - test_bit(CONF_NO_FCS_RECV, &chan->conf_state)) { - chan->fcs = L2CAP_FCS_NONE; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, chan->fcs); - } - break; + if (skb_queue_empty(queue)) + return 0; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = (struct iovec *) &iv; + + buflen = pi->omtu + L2CAP_FCS_SIZE; + buf = kzalloc(buflen, GFP_TEMPORARY); + + if (!buf) { + BT_DBG("Could not allocate resegmentation buffer"); + return -ENOMEM; } - req->dcid = cpu_to_le16(chan->dcid); - req->flags = cpu_to_le16(0); + /* Move current frames off the original queue */ + __skb_queue_head_init(&old_frames); + skb_queue_splice_tail_init(queue, &old_frames); - return ptr - data; -} + while (!skb_queue_empty(&old_frames)) { + struct sk_buff_head current_sdu; + u8 original_sar; -static int l2cap_parse_conf_req(struct l2cap_chan *chan, void *data) -{ - struct l2cap_conf_rsp *rsp = data; - void *ptr = rsp->data; - void *req = chan->conf_req; - int len = chan->conf_len; - int type, hint, olen; - unsigned long val; - struct l2cap_conf_rfc rfc = { .mode = L2CAP_MODE_BASIC }; - u16 mtu = L2CAP_DEFAULT_MTU; - u16 result = L2CAP_CONF_SUCCESS; + /* Reassemble each SDU from one or more PDUs */ - BT_DBG("chan %p", chan); + iv.iov_base = buf; + iv.iov_len = 0; - while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&req, &type, &olen, &val); + skb = skb_peek(&old_frames); + original_sar = bt_cb(skb)->control.sar; - hint = type & L2CAP_CONF_HINT; - type &= L2CAP_CONF_MASK; + __skb_unlink(skb, &old_frames); - switch (type) { - case L2CAP_CONF_MTU: - mtu = val; - break; + /* Append data to SDU */ + if (pi->extended_control) + skb_pull(skb, L2CAP_EXTENDED_HDR_SIZE); + else + skb_pull(skb, L2CAP_ENHANCED_HDR_SIZE); - case L2CAP_CONF_FLUSH_TO: - chan->flush_to = val; - break; + if (original_sar == L2CAP_SAR_START) + skb_pull(skb, L2CAP_SDULEN_SIZE); - case L2CAP_CONF_QOS: - break; + err = l2cap_skbuff_to_kvec(skb, &iv, buflen); - case L2CAP_CONF_RFC: - if (olen == sizeof(rfc)) - memcpy(&rfc, (void *) val, olen); - break; + if (bt_cb(skb)->control.fcs == L2CAP_FCS_CRC16) + iv.iov_len -= L2CAP_FCS_SIZE; - case L2CAP_CONF_FCS: - if (val == L2CAP_FCS_NONE) - set_bit(CONF_NO_FCS_RECV, &chan->conf_state); + /* Free skb */ + kfree_skb(skb); + if (err) break; - default: - if (hint) + while (!skb_queue_empty(&old_frames) && !err) { + /* Check next frame */ + skb = skb_peek(&old_frames); + + if (is_initial_frame(bt_cb(skb)->control.sar)) + break; + + __skb_unlink(skb, &old_frames); + + /* Append data to SDU */ + if (pi->extended_control) + skb_pull(skb, L2CAP_EXTENDED_HDR_SIZE); + else + skb_pull(skb, L2CAP_ENHANCED_HDR_SIZE); + + if (bt_cb(skb)->control.sar == L2CAP_SAR_START) + skb_pull(skb, L2CAP_SDULEN_SIZE); + + err = l2cap_skbuff_to_kvec(skb, &iv, buflen); + + if (bt_cb(skb)->control.fcs == L2CAP_FCS_CRC16) + iv.iov_len -= L2CAP_FCS_SIZE; + + /* Free skb */ + kfree_skb(skb); + } + + if (err) + break; + + /* Segment data */ + + __skb_queue_head_init(¤t_sdu); + + /* skbs for the SDU were just freed, but the + * resegmenting process could produce more, smaller + * skbs due to smaller PDUs and reduced HCI MTU. The + * overhead from the sk_buff structs could put us over + * the sk_sndbuf limit. + * + * Since this code is running in response to a + * received poll/final packet, it cannot block. + * Therefore, memory allocation needs to be allowed by + * falling back to bt_skb_alloc() (with + * skb_set_owner_w() to maintain sk_wmem_alloc + * correctly). + */ + msg.msg_iovlen = iv.iov_len; + err = l2cap_segment_sdu(sk, ¤t_sdu, &msg, + msg.msg_iovlen, 1); + + if (err || skb_queue_empty(¤t_sdu)) { + BT_DBG("Error %d resegmenting data for socket %p", + err, sk); + __skb_queue_purge(¤t_sdu); + break; + } + + /* Fix up first PDU SAR bits */ + if (!is_initial_frame(original_sar)) { + BT_DBG("Changing SAR bits, %d PDUs", + skb_queue_len(¤t_sdu)); + skb = skb_peek(¤t_sdu); + + if (skb_queue_len(¤t_sdu) == 1) { + /* Change SAR from 'unsegmented' to 'end' */ + bt_cb(skb)->control.sar = L2CAP_SAR_END; + } else { + struct l2cap_hdr *lh; + size_t hdrlen; + + /* Change SAR from 'start' to 'continue' */ + bt_cb(skb)->control.sar = L2CAP_SAR_CONTINUE; + + /* Start frames contain 2 bytes for + * sdulen and continue frames don't. + * Must rewrite header to eliminate + * sdulen and then adjust l2cap frame + * length. + */ + if (pi->extended_control) + hdrlen = L2CAP_EXTENDED_HDR_SIZE; + else + hdrlen = L2CAP_ENHANCED_HDR_SIZE; + + memmove(skb->data + L2CAP_SDULEN_SIZE, + skb->data, hdrlen); + skb_pull(skb, L2CAP_SDULEN_SIZE); + lh = (struct l2cap_hdr *)skb->data; + lh->len = cpu_to_le16(le16_to_cpu(lh->len) - + L2CAP_SDULEN_SIZE); + } + } + + /* Add to queue */ + skb_queue_splice_tail(¤t_sdu, queue); + } + + __skb_queue_purge(&old_frames); + if (err) + __skb_queue_purge(queue); + + kfree(buf); + + BT_DBG("Queue resegmented, err=%d", err); + return err; +} + +static void l2cap_resegment_worker(struct work_struct *work) +{ + int err = 0; + struct l2cap_resegment_work *seg_work = + container_of(work, struct l2cap_resegment_work, work); + struct sock *sk = seg_work->sk; + + kfree(seg_work); + + BT_DBG("sk %p", sk); + lock_sock(sk); + + if (l2cap_pi(sk)->amp_move_state != L2CAP_AMP_STATE_RESEGMENT) { + release_sock(sk); + return; + } + + err = l2cap_resegment_queue(sk, TX_QUEUE(sk)); + + l2cap_pi(sk)->amp_move_state = L2CAP_AMP_STATE_STABLE; + + if (skb_queue_empty(TX_QUEUE(sk))) + sk->sk_send_head = NULL; + else + sk->sk_send_head = skb_peek(TX_QUEUE(sk)); + + if (err) + l2cap_send_disconn_req(l2cap_pi(sk)->conn, sk, ECONNRESET); + else + l2cap_ertm_send(sk); + + release_sock(sk); +} + +static int l2cap_setup_resegment(struct sock *sk) +{ + struct l2cap_resegment_work *seg_work; + + BT_DBG("sk %p", sk); + + if (skb_queue_empty(TX_QUEUE(sk))) + return 0; + + seg_work = kzalloc(sizeof(*seg_work), GFP_ATOMIC); + if (!seg_work) + return -ENOMEM; + + INIT_WORK(&seg_work->work, l2cap_resegment_worker); + seg_work->sk = sk; + + if (!queue_work(_l2cap_wq, &seg_work->work)) { + kfree(seg_work); + return -ENOMEM; + } + + l2cap_pi(sk)->amp_move_state = L2CAP_AMP_STATE_RESEGMENT; + + return 0; +} + +static inline int l2cap_rmem_available(struct sock *sk) +{ + BT_DBG("sk_rmem_alloc %d, sk_rcvbuf %d", + atomic_read(&sk->sk_rmem_alloc), sk->sk_rcvbuf); + return atomic_read(&sk->sk_rmem_alloc) < sk->sk_rcvbuf / 3; +} + +static inline int l2cap_rmem_full(struct sock *sk) +{ + BT_DBG("sk_rmem_alloc %d, sk_rcvbuf %d", + atomic_read(&sk->sk_rmem_alloc), sk->sk_rcvbuf); + return atomic_read(&sk->sk_rmem_alloc) > (2 * sk->sk_rcvbuf) / 3; +} + +void l2cap_amp_move_init(struct sock *sk) +{ + BT_DBG("sk %p", sk); + + if (!l2cap_pi(sk)->conn) + return; + + if (!(l2cap_pi(sk)->conn->fc_mask & L2CAP_FC_A2MP)) + return; + + if (l2cap_pi(sk)->amp_id == 0) { + if (l2cap_pi(sk)->amp_pref != BT_AMP_POLICY_PREFER_AMP) + return; + l2cap_pi(sk)->amp_move_role = L2CAP_AMP_MOVE_INITIATOR; + l2cap_pi(sk)->amp_move_state = L2CAP_AMP_STATE_WAIT_PREPARE; + amp_create_physical(l2cap_pi(sk)->conn, sk); + } else { + l2cap_pi(sk)->amp_move_role = L2CAP_AMP_MOVE_INITIATOR; + l2cap_pi(sk)->amp_move_state = + L2CAP_AMP_STATE_WAIT_MOVE_RSP_SUCCESS; + l2cap_pi(sk)->amp_move_id = 0; + l2cap_amp_move_setup(sk); + l2cap_send_move_chan_req(l2cap_pi(sk)->conn, + l2cap_pi(sk), l2cap_pi(sk)->scid, 0); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); + } +} + +static void l2cap_chan_ready(struct sock *sk) +{ + struct sock *parent = bt_sk(sk)->parent; + + BT_DBG("sk %p, parent %p", sk, parent); + + l2cap_pi(sk)->conf_state = 0; + l2cap_sock_clear_timer(sk); + + if (!parent) { + /* Outgoing channel. + * Wake up socket sleeping on connect. + */ + sk->sk_state = BT_CONNECTED; + sk->sk_state_change(sk); + } else { + /* Incoming channel. + * Wake up socket sleeping on accept. + */ + parent->sk_data_ready(parent, 0); + } +} + +/* Copy frame to all raw sockets on that connection */ +static void l2cap_raw_recv(struct l2cap_conn *conn, struct sk_buff *skb) +{ + struct l2cap_chan_list *l = &conn->chan_list; + struct sk_buff *nskb; + struct sock *sk; + + BT_DBG("conn %p", conn); + + read_lock(&l->lock); + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + if (sk->sk_type != SOCK_RAW) + continue; + + /* Don't send frame to the socket it came from */ + if (skb->sk == sk) + continue; + nskb = skb_clone(skb, GFP_ATOMIC); + if (!nskb) + continue; + + if (sock_queue_rcv_skb(sk, nskb)) + kfree_skb(nskb); + } + read_unlock(&l->lock); +} + +/* ---- L2CAP signalling commands ---- */ +static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn, + u8 code, u8 ident, u16 dlen, void *data) +{ + struct sk_buff *skb, **frag; + struct l2cap_cmd_hdr *cmd; + struct l2cap_hdr *lh; + int len, count; + unsigned int mtu = conn->hcon->hdev->acl_mtu; + + BT_DBG("conn %p, code 0x%2.2x, ident 0x%2.2x, len %d", + conn, code, ident, dlen); + + len = L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE + dlen; + count = min_t(unsigned int, mtu, len); + + skb = bt_skb_alloc(count, GFP_ATOMIC); + if (!skb) + return NULL; + + lh = (struct l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); + lh->len = cpu_to_le16(L2CAP_CMD_HDR_SIZE + dlen); + + if (conn->hcon->type == LE_LINK) + lh->cid = cpu_to_le16(L2CAP_CID_LE_SIGNALING); + else + lh->cid = cpu_to_le16(L2CAP_CID_SIGNALING); + + cmd = (struct l2cap_cmd_hdr *) skb_put(skb, L2CAP_CMD_HDR_SIZE); + cmd->code = code; + cmd->ident = ident; + cmd->len = cpu_to_le16(dlen); + + if (dlen) { + count -= L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE; + memcpy(skb_put(skb, count), data, count); + data += count; + } + + len -= skb->len; + + /* Continuation fragments (no L2CAP header) */ + frag = &skb_shinfo(skb)->frag_list; + while (len) { + count = min_t(unsigned int, mtu, len); + + *frag = bt_skb_alloc(count, GFP_ATOMIC); + if (!*frag) + goto fail; + + memcpy(skb_put(*frag, count), data, count); + + len -= count; + data += count; + + frag = &(*frag)->next; + } + + return skb; + +fail: + kfree_skb(skb); + return NULL; +} + +static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, unsigned long *val) +{ + struct l2cap_conf_opt *opt = *ptr; + int len; + + len = L2CAP_CONF_OPT_SIZE + opt->len; + *ptr += len; + + *type = opt->type; + *olen = opt->len; + + switch (opt->len) { + case 1: + *val = *((u8 *) opt->val); + break; + + case 2: + *val = get_unaligned_le16(opt->val); + break; + + case 4: + *val = get_unaligned_le32(opt->val); + break; + + default: + *val = (unsigned long) opt->val; + break; + } + + BT_DBG("type 0x%2.2x len %d val 0x%lx", *type, opt->len, *val); + return len; +} + +static void l2cap_add_conf_opt(void **ptr, u8 type, u8 len, unsigned long val) +{ + struct l2cap_conf_opt *opt = *ptr; + + BT_DBG("type 0x%2.2x len %d val 0x%lx", type, len, val); + + opt->type = type; + opt->len = len; + + switch (len) { + case 1: + *((u8 *) opt->val) = val; + break; + + case 2: + put_unaligned_le16(val, opt->val); + break; + + case 4: + put_unaligned_le32(val, opt->val); + break; + + default: + memcpy(opt->val, (void *) val, len); + break; + } + + *ptr += L2CAP_CONF_OPT_SIZE + len; +} + +static void l2cap_ertm_ack_timeout(struct work_struct *work) +{ + struct delayed_work *delayed = + container_of(work, struct delayed_work, work); + struct l2cap_pinfo *pi = + container_of(delayed, struct l2cap_pinfo, ack_work); + struct sock *sk = (struct sock *)pi; + u16 frames_to_ack; + + BT_DBG("sk %p", sk); + + if (!sk) + return; + + lock_sock(sk); + + if (!l2cap_pi(sk)->conn) { + release_sock(sk); + return; + } + + frames_to_ack = __delta_seq(l2cap_pi(sk)->buffer_seq, + l2cap_pi(sk)->last_acked_seq, + l2cap_pi(sk)); + + if (frames_to_ack) + l2cap_ertm_send_rr_or_rnr(sk, 0); + + release_sock(sk); +} + +static void l2cap_ertm_retrans_timeout(struct work_struct *work) +{ + struct delayed_work *delayed = + container_of(work, struct delayed_work, work); + struct l2cap_pinfo *pi = + container_of(delayed, struct l2cap_pinfo, retrans_work); + struct sock *sk = (struct sock *)pi; + + BT_DBG("sk %p", sk); + + if (!sk) + return; + + lock_sock(sk); + + if (!l2cap_pi(sk)->conn) { + release_sock(sk); + return; + } + + l2cap_ertm_tx(sk, 0, 0, L2CAP_ERTM_EVENT_RETRANS_TIMER_EXPIRES); + release_sock(sk); +} + +static void l2cap_ertm_monitor_timeout(struct work_struct *work) +{ + struct delayed_work *delayed = + container_of(work, struct delayed_work, work); + struct l2cap_pinfo *pi = + container_of(delayed, struct l2cap_pinfo, monitor_work); + struct sock *sk = (struct sock *)pi; + + BT_DBG("sk %p", sk); + + if (!sk) + return; + + lock_sock(sk); + + if (!l2cap_pi(sk)->conn) { + release_sock(sk); + return; + } + + l2cap_ertm_tx(sk, 0, 0, L2CAP_ERTM_EVENT_MONITOR_TIMER_EXPIRES); + + release_sock(sk); +} + +static inline void l2cap_ertm_init(struct sock *sk) +{ + l2cap_pi(sk)->next_tx_seq = 0; + l2cap_pi(sk)->expected_tx_seq = 0; + l2cap_pi(sk)->expected_ack_seq = 0; + l2cap_pi(sk)->unacked_frames = 0; + l2cap_pi(sk)->buffer_seq = 0; + l2cap_pi(sk)->frames_sent = 0; + l2cap_pi(sk)->last_acked_seq = 0; + l2cap_pi(sk)->sdu = NULL; + l2cap_pi(sk)->sdu_last_frag = NULL; + l2cap_pi(sk)->sdu_len = 0; + atomic_set(&l2cap_pi(sk)->ertm_queued, 0); + + l2cap_pi(sk)->rx_state = L2CAP_ERTM_RX_STATE_RECV; + l2cap_pi(sk)->tx_state = L2CAP_ERTM_TX_STATE_XMIT; + + BT_DBG("tx_state 0x2.2%x rx_state 0x2.2%x", l2cap_pi(sk)->tx_state, + l2cap_pi(sk)->rx_state); + + l2cap_pi(sk)->amp_id = 0; + l2cap_pi(sk)->amp_move_state = L2CAP_AMP_STATE_STABLE; + l2cap_pi(sk)->amp_move_role = L2CAP_AMP_MOVE_NONE; + l2cap_pi(sk)->amp_move_reqseq = 0; + l2cap_pi(sk)->amp_move_event = 0; + + INIT_DELAYED_WORK(&l2cap_pi(sk)->ack_work, l2cap_ertm_ack_timeout); + INIT_DELAYED_WORK(&l2cap_pi(sk)->retrans_work, + l2cap_ertm_retrans_timeout); + INIT_DELAYED_WORK(&l2cap_pi(sk)->monitor_work, + l2cap_ertm_monitor_timeout); + INIT_WORK(&l2cap_pi(sk)->tx_work, l2cap_ertm_tx_worker); + skb_queue_head_init(SREJ_QUEUE(sk)); + skb_queue_head_init(TX_QUEUE(sk)); + + l2cap_seq_list_init(&l2cap_pi(sk)->srej_list, l2cap_pi(sk)->tx_win); + l2cap_seq_list_init(&l2cap_pi(sk)->retrans_list, + l2cap_pi(sk)->remote_tx_win); +} + +void l2cap_ertm_destruct(struct sock *sk) +{ + l2cap_seq_list_free(&l2cap_pi(sk)->srej_list); + l2cap_seq_list_free(&l2cap_pi(sk)->retrans_list); +} + +void l2cap_ertm_shutdown(struct sock *sk) +{ + l2cap_ertm_stop_ack_timer(l2cap_pi(sk)); + l2cap_ertm_stop_retrans_timer(l2cap_pi(sk)); + l2cap_ertm_stop_monitor_timer(l2cap_pi(sk)); +} + +void l2cap_ertm_recv_done(struct sock *sk) +{ + lock_sock(sk); + + if (l2cap_pi(sk)->mode != L2CAP_MODE_ERTM) { + release_sock(sk); + return; + } + + /* Consume any queued incoming frames and update local busy status */ + if (l2cap_pi(sk)->rx_state == L2CAP_ERTM_RX_STATE_SREJ_SENT && + l2cap_ertm_rx_queued_iframes(sk)) + l2cap_send_disconn_req(l2cap_pi(sk)->conn, sk, ECONNRESET); + else if ((l2cap_pi(sk)->conn_state & L2CAP_CONN_LOCAL_BUSY) && + l2cap_rmem_available(sk)) + l2cap_ertm_tx(sk, 0, 0, L2CAP_ERTM_EVENT_LOCAL_BUSY_CLEAR); + + release_sock(sk); +} + +static inline __u8 l2cap_select_mode(__u8 mode, __u16 remote_feat_mask) +{ + switch (mode) { + case L2CAP_MODE_STREAMING: + case L2CAP_MODE_ERTM: + if (l2cap_mode_supported(mode, remote_feat_mask)) + return mode; + /* fall through */ + default: + return L2CAP_MODE_BASIC; + } +} + +static void l2cap_setup_txwin(struct l2cap_pinfo *pi) +{ + if (pi->tx_win > L2CAP_TX_WIN_MAX_ENHANCED && + (pi->conn->feat_mask & L2CAP_FEAT_EXT_WINDOW)) { + pi->tx_win_max = L2CAP_TX_WIN_MAX_EXTENDED; + pi->extended_control = 1; + } else { + if (pi->tx_win > L2CAP_TX_WIN_MAX_ENHANCED) + pi->tx_win = L2CAP_TX_WIN_MAX_ENHANCED; + + pi->tx_win_max = L2CAP_TX_WIN_MAX_ENHANCED; + pi->extended_control = 0; + } +} + +static void l2cap_aggregate_fs(struct hci_ext_fs *cur, + struct hci_ext_fs *new, + struct hci_ext_fs *agg) +{ + *agg = *cur; + if ((cur->max_sdu != 0xFFFF) && (cur->sdu_arr_time != 0xFFFFFFFF)) { + /* current flow spec has known rate */ + if ((new->max_sdu == 0xFFFF) || + (new->sdu_arr_time == 0xFFFFFFFF)) { + /* new fs has unknown rate, so aggregate is unknown */ + agg->max_sdu = 0xFFFF; + agg->sdu_arr_time = 0xFFFFFFFF; + } else { + /* new fs has known rate, so aggregate is known */ + u64 cur_rate; + u64 new_rate; + cur_rate = cur->max_sdu * 1000000ULL; + if (cur->sdu_arr_time) + cur_rate = div_u64(cur_rate, cur->sdu_arr_time); + new_rate = new->max_sdu * 1000000ULL; + if (new->sdu_arr_time) + new_rate = div_u64(new_rate, new->sdu_arr_time); + cur_rate = cur_rate + new_rate; + agg->sdu_arr_time = div64_u64(agg->max_sdu * 1000000ULL, + cur_rate); + } + } +} + +static int l2cap_aggregate(struct hci_chan *chan, struct l2cap_pinfo *pi) +{ + struct hci_ext_fs tx_fs; + struct hci_ext_fs rx_fs; + + BT_DBG("chan %p", chan); + + if (((chan->tx_fs.max_sdu == 0xFFFF) || + (chan->tx_fs.sdu_arr_time == 0xFFFFFFFF)) && + ((chan->rx_fs.max_sdu == 0xFFFF) || + (chan->rx_fs.sdu_arr_time == 0xFFFFFFFF))) + return 0; + + l2cap_aggregate_fs(&chan->tx_fs, + (struct hci_ext_fs *) &pi->local_fs, &tx_fs); + l2cap_aggregate_fs(&chan->rx_fs, + (struct hci_ext_fs *) &pi->remote_fs, &rx_fs); + hci_chan_modify(chan, &tx_fs, &rx_fs); + return 1; +} + +static void l2cap_deaggregate_fs(struct hci_ext_fs *cur, + struct hci_ext_fs *old, + struct hci_ext_fs *agg) +{ + *agg = *cur; + if ((cur->max_sdu != 0xFFFF) && (cur->sdu_arr_time != 0xFFFFFFFF)) { + u64 cur_rate; + u64 old_rate; + cur_rate = cur->max_sdu * 1000000ULL; + if (cur->sdu_arr_time) + cur_rate = div_u64(cur_rate, cur->sdu_arr_time); + old_rate = old->max_sdu * 1000000ULL; + if (old->sdu_arr_time) + old_rate = div_u64(old_rate, old->sdu_arr_time); + cur_rate = cur_rate - old_rate; + agg->sdu_arr_time = div64_u64(agg->max_sdu * 1000000ULL, + cur_rate); + } +} + +static int l2cap_deaggregate(struct hci_chan *chan, struct l2cap_pinfo *pi) +{ + struct hci_ext_fs tx_fs; + struct hci_ext_fs rx_fs; + + BT_DBG("chan %p", chan); + + if (((chan->tx_fs.max_sdu == 0xFFFF) || + (chan->tx_fs.sdu_arr_time == 0xFFFFFFFF)) && + ((chan->rx_fs.max_sdu == 0xFFFF) || + (chan->rx_fs.sdu_arr_time == 0xFFFFFFFF))) + return 0; + + l2cap_deaggregate_fs(&chan->tx_fs, + (struct hci_ext_fs *) &pi->local_fs, &tx_fs); + l2cap_deaggregate_fs(&chan->rx_fs, + (struct hci_ext_fs *) &pi->remote_fs, &rx_fs); + hci_chan_modify(chan, &tx_fs, &rx_fs); + return 1; +} + +static struct hci_chan *l2cap_chan_admit(u8 amp_id, struct l2cap_pinfo *pi) +{ + struct hci_dev *hdev; + struct hci_conn *hcon; + struct hci_chan *chan; + + hdev = hci_dev_get(A2MP_HCI_ID(amp_id)); + if (!hdev) + return NULL; + + BT_DBG("hdev %s", hdev->name); + + hcon = hci_conn_hash_lookup_ba(hdev, ACL_LINK, pi->conn->dst); + if (!hcon) + return NULL; + + chan = hci_chan_list_lookup_id(hdev, hcon->handle); + if (chan) { + l2cap_aggregate(chan, pi); + hci_chan_hold(chan); + return chan; + } + + if (bt_sk(pi)->parent) { + /* Incoming connection */ + chan = hci_chan_accept(hcon, + (struct hci_ext_fs *) &pi->local_fs, + (struct hci_ext_fs *) &pi->remote_fs); + } else { + /* Outgoing connection */ + chan = hci_chan_create(hcon, + (struct hci_ext_fs *) &pi->local_fs, + (struct hci_ext_fs *) &pi->remote_fs); + } + return chan; +} + +int l2cap_build_conf_req(struct sock *sk, void *data) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct l2cap_conf_req *req = data; + struct l2cap_conf_rfc rfc = { .mode = pi->mode }; + void *ptr = req->data; + + BT_DBG("sk %p", sk); + + if (pi->num_conf_req || pi->num_conf_rsp) + goto done; + + switch (pi->mode) { + case L2CAP_MODE_STREAMING: + case L2CAP_MODE_ERTM: + if (pi->conf_state & L2CAP_CONF_STATE2_DEVICE) + break; + + /* fall through */ + default: + pi->mode = l2cap_select_mode(rfc.mode, pi->conn->feat_mask); + break; + } + +done: + if (pi->imtu != L2CAP_DEFAULT_MTU) + l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu); + + switch (pi->mode) { + case L2CAP_MODE_BASIC: + if (!(pi->conn->feat_mask & L2CAP_FEAT_ERTM) && + !(pi->conn->feat_mask & L2CAP_FEAT_STREAMING)) + break; + + rfc.txwin_size = 0; + rfc.max_transmit = 0; + rfc.retrans_timeout = 0; + rfc.monitor_timeout = 0; + rfc.max_pdu_size = 0; + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc), + (unsigned long) &rfc); + break; + + case L2CAP_MODE_ERTM: + l2cap_setup_txwin(pi); + if (pi->tx_win > L2CAP_TX_WIN_MAX_ENHANCED) + rfc.txwin_size = L2CAP_TX_WIN_MAX_ENHANCED; + else + rfc.txwin_size = pi->tx_win; + rfc.max_transmit = pi->max_tx; + rfc.retrans_timeout = cpu_to_le16(L2CAP_DEFAULT_RETRANS_TO); + rfc.monitor_timeout = cpu_to_le16(L2CAP_DEFAULT_MONITOR_TO); + rfc.max_pdu_size = cpu_to_le16(L2CAP_DEFAULT_MAX_PDU_SIZE); + if (L2CAP_DEFAULT_MAX_PDU_SIZE > pi->imtu) + rfc.max_pdu_size = cpu_to_le16(pi->imtu); + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc), + (unsigned long) &rfc); + + if ((pi->conn->feat_mask & L2CAP_FEAT_EXT_WINDOW) && + pi->extended_control) { + l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_WINDOW, 2, + pi->tx_win); + } + + if (pi->amp_id) { + /* default best effort extended flow spec */ + struct l2cap_conf_ext_fs fs = {1, 1, 0xFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; + l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_FS, + sizeof(fs), (unsigned long) &fs); + } + + if (!(pi->conn->feat_mask & L2CAP_FEAT_FCS)) + break; + + if (pi->fcs == L2CAP_FCS_NONE || + pi->conf_state & L2CAP_CONF_NO_FCS_RECV) { + pi->fcs = L2CAP_FCS_NONE; + l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, pi->fcs); + } + break; + + case L2CAP_MODE_STREAMING: + rfc.txwin_size = 0; + rfc.max_transmit = 0; + rfc.retrans_timeout = 0; + rfc.monitor_timeout = 0; + rfc.max_pdu_size = cpu_to_le16(L2CAP_DEFAULT_MAX_PDU_SIZE); + if (L2CAP_DEFAULT_MAX_PDU_SIZE > pi->imtu) + rfc.max_pdu_size = cpu_to_le16(pi->imtu); + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc), + (unsigned long) &rfc); + + if ((pi->conn->feat_mask & L2CAP_FEAT_EXT_WINDOW) && + pi->extended_control) { + l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_WINDOW, 2, 0); + } + + if (!(pi->conn->feat_mask & L2CAP_FEAT_FCS)) + break; + + if (pi->fcs == L2CAP_FCS_NONE || + pi->conf_state & L2CAP_CONF_NO_FCS_RECV) { + pi->fcs = L2CAP_FCS_NONE; + l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, pi->fcs); + } + break; + } + + req->dcid = cpu_to_le16(pi->dcid); + req->flags = cpu_to_le16(0); + + return ptr - data; +} + + +static int l2cap_build_amp_reconf_req(struct sock *sk, void *data) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct l2cap_conf_req *req = data; + struct l2cap_conf_rfc rfc = { .mode = pi->mode }; + void *ptr = req->data; + u32 be_flush_to; + + BT_DBG("sk %p", sk); + + /* convert to milliseconds, round up */ + be_flush_to = (pi->conn->hcon->hdev->amp_be_flush_to + 999) / 1000; + + switch (pi->mode) { + case L2CAP_MODE_ERTM: + rfc.mode = L2CAP_MODE_ERTM; + rfc.txwin_size = pi->tx_win; + rfc.max_transmit = pi->max_tx; + if (pi->amp_move_id) { + rfc.retrans_timeout = + cpu_to_le16((3 * be_flush_to) + 500); + rfc.monitor_timeout = + cpu_to_le16((3 * be_flush_to) + 500); + } else { + rfc.retrans_timeout = + cpu_to_le16(L2CAP_DEFAULT_RETRANS_TO); + rfc.monitor_timeout = + cpu_to_le16(L2CAP_DEFAULT_MONITOR_TO); + } + rfc.max_pdu_size = cpu_to_le16(L2CAP_DEFAULT_MAX_PDU_SIZE); + if (L2CAP_DEFAULT_MAX_PDU_SIZE > pi->imtu) + rfc.max_pdu_size = cpu_to_le16(pi->imtu); + + break; + + default: + return -ECONNREFUSED; + } + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, sizeof(rfc), + (unsigned long) &rfc); + + if (pi->conn->feat_mask & L2CAP_FEAT_FCS) { + + /* TODO assign fcs for br/edr based on socket config option */ + if (pi->amp_move_id) + pi->local_conf.fcs = L2CAP_FCS_NONE; + else + pi->local_conf.fcs = L2CAP_FCS_CRC16; + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_FCS, 1, + pi->local_conf.fcs); + + pi->fcs = pi->local_conf.fcs | pi->remote_conf.fcs; + } + + req->dcid = cpu_to_le16(pi->dcid); + req->flags = cpu_to_le16(0); + + return ptr - data; +} + +static int l2cap_parse_conf_req(struct sock *sk, void *data) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct l2cap_conf_rsp *rsp = data; + void *ptr = rsp->data; + void *req = pi->conf_req; + int len = pi->conf_len; + int type, hint, olen; + unsigned long val; + struct l2cap_conf_rfc rfc = { .mode = L2CAP_MODE_BASIC }; + struct l2cap_conf_ext_fs fs; + u16 mtu = L2CAP_DEFAULT_MTU; + u16 result = L2CAP_CONF_SUCCESS; + + BT_DBG("sk %p", sk); + + while (len >= L2CAP_CONF_OPT_SIZE) { + len -= l2cap_get_conf_opt(&req, &type, &olen, &val); + + hint = type & L2CAP_CONF_HINT; + type &= L2CAP_CONF_MASK; + + switch (type) { + case L2CAP_CONF_MTU: + mtu = val; + break; + + case L2CAP_CONF_FLUSH_TO: + pi->flush_to = val; + if (pi->conf_state & L2CAP_CONF_LOCKSTEP) + result = L2CAP_CONF_UNACCEPT; + else + pi->remote_conf.flush_to = val; + break; + + case L2CAP_CONF_QOS: + if (pi->conf_state & L2CAP_CONF_LOCKSTEP) + result = L2CAP_CONF_UNACCEPT; + break; + + case L2CAP_CONF_RFC: + if (olen == sizeof(rfc)) + memcpy(&rfc, (void *) val, olen); + break; + + case L2CAP_CONF_FCS: + if (val == L2CAP_FCS_NONE) + pi->conf_state |= L2CAP_CONF_NO_FCS_RECV; + pi->remote_conf.fcs = val; + break; + + case L2CAP_CONF_EXT_FS: + if (olen == sizeof(fs)) { + pi->conf_state |= L2CAP_CONF_EFS_RECV; + if (!(pi->conf_state & L2CAP_CONF_LOCKSTEP)) { + result = L2CAP_CONF_UNACCEPT; + break; + } + memcpy(&fs, (void *) val, olen); + if (fs.type != L2CAP_SERVICE_BEST_EFFORT) { + result = L2CAP_CONF_FLOW_SPEC_REJECT; + break; + } + pi->remote_conf.flush_to = + le32_to_cpu(fs.flush_to); + pi->remote_fs.id = fs.id; + pi->remote_fs.type = fs.type; + pi->remote_fs.max_sdu = + le16_to_cpu(fs.max_sdu); + pi->remote_fs.sdu_arr_time = + le32_to_cpu(fs.sdu_arr_time); + pi->remote_fs.acc_latency = + le32_to_cpu(fs.acc_latency); + pi->remote_fs.flush_to = + le32_to_cpu(fs.flush_to); + } + break; + + case L2CAP_CONF_EXT_WINDOW: + pi->extended_control = 1; + pi->remote_tx_win = val; + pi->tx_win_max = L2CAP_TX_WIN_MAX_EXTENDED; + pi->conf_state |= L2CAP_CONF_EXT_WIN_RECV; + break; + + default: + if (hint) + break; + + result = L2CAP_CONF_UNKNOWN; + *((u8 *) ptr++) = type; + break; + } + } + + if (pi->num_conf_rsp || pi->num_conf_req > 1) + goto done; + + switch (pi->mode) { + case L2CAP_MODE_STREAMING: + case L2CAP_MODE_ERTM: + if (!(pi->conf_state & L2CAP_CONF_STATE2_DEVICE)) { + pi->mode = l2cap_select_mode(rfc.mode, + pi->conn->feat_mask); + break; + } + + if (pi->mode != rfc.mode) + return -ECONNREFUSED; + + break; + } + +done: + if (pi->mode != rfc.mode) { + result = L2CAP_CONF_UNACCEPT; + rfc.mode = pi->mode; + + if (pi->num_conf_rsp == 1) + return -ECONNREFUSED; + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, + sizeof(rfc), (unsigned long) &rfc); + } + + + if ((pi->conf_state & L2CAP_CONF_LOCKSTEP) && + !(pi->conf_state & L2CAP_CONF_EFS_RECV)) + return -ECONNREFUSED; + + if (result == L2CAP_CONF_SUCCESS) { + /* Configure output options and let the other side know + * which ones we don't like. */ + + if (mtu < L2CAP_DEFAULT_MIN_MTU) { + result = L2CAP_CONF_UNACCEPT; + pi->omtu = L2CAP_DEFAULT_MIN_MTU; + } + else { + pi->omtu = mtu; + pi->conf_state |= L2CAP_CONF_MTU_DONE; + } + l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu); + + switch (rfc.mode) { + case L2CAP_MODE_BASIC: + pi->fcs = L2CAP_FCS_NONE; + pi->conf_state |= L2CAP_CONF_MODE_DONE; + break; + + case L2CAP_MODE_ERTM: + if (!(pi->conf_state & L2CAP_CONF_EXT_WIN_RECV)) + pi->remote_tx_win = rfc.txwin_size; + + pi->remote_max_tx = rfc.max_transmit; + + pi->remote_mps = le16_to_cpu(rfc.max_pdu_size); + + rfc.retrans_timeout = + cpu_to_le16(L2CAP_DEFAULT_RETRANS_TO); + rfc.monitor_timeout = + cpu_to_le16(L2CAP_DEFAULT_MONITOR_TO); + + pi->conf_state |= L2CAP_CONF_MODE_DONE; + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, + sizeof(rfc), (unsigned long) &rfc); + + if (pi->conf_state & L2CAP_CONF_LOCKSTEP) + l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_FS, + sizeof(fs), (unsigned long) &fs); + + break; + + case L2CAP_MODE_STREAMING: + pi->remote_mps = le16_to_cpu(rfc.max_pdu_size); + + pi->conf_state |= L2CAP_CONF_MODE_DONE; + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, + sizeof(rfc), (unsigned long) &rfc); + + break; + + default: + result = L2CAP_CONF_UNACCEPT; + + memset(&rfc, 0, sizeof(rfc)); + rfc.mode = pi->mode; + } + + if (pi->conf_state & L2CAP_CONF_LOCKSTEP && + !(pi->conf_state & L2CAP_CONF_PEND_SENT)) { + pi->conf_state |= L2CAP_CONF_PEND_SENT; + result = L2CAP_CONF_PENDING; + + if (pi->conf_state & L2CAP_CONF_LOCKSTEP_PEND && + pi->amp_id) { + struct hci_chan *chan; + /* Trigger logical link creation only on AMP */ + + chan = l2cap_chan_admit(pi->amp_id, pi); + if (!chan) + return -ECONNREFUSED; + + chan->l2cap_sk = sk; + if (chan->state == BT_CONNECTED) + l2cap_create_cfm(chan, 0); + } + } + + if (result == L2CAP_CONF_SUCCESS) + pi->conf_state |= L2CAP_CONF_OUTPUT_DONE; + } + rsp->scid = cpu_to_le16(pi->dcid); + rsp->result = cpu_to_le16(result); + rsp->flags = cpu_to_le16(0x0000); + + return ptr - data; +} + +static int l2cap_parse_amp_move_reconf_req(struct sock *sk, void *data) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct l2cap_conf_rsp *rsp = data; + void *ptr = rsp->data; + void *req = pi->conf_req; + int len = pi->conf_len; + int type, hint, olen; + unsigned long val; + struct l2cap_conf_rfc rfc = { .mode = L2CAP_MODE_BASIC }; + struct l2cap_conf_ext_fs fs; + u16 mtu = pi->omtu; + u16 tx_win = pi->remote_tx_win; + u16 result = L2CAP_CONF_SUCCESS; + + BT_DBG("sk %p", sk); + + while (len >= L2CAP_CONF_OPT_SIZE) { + len -= l2cap_get_conf_opt(&req, &type, &olen, &val); + + hint = type & L2CAP_CONF_HINT; + type &= L2CAP_CONF_MASK; + + switch (type) { + case L2CAP_CONF_MTU: + mtu = val; + break; + + case L2CAP_CONF_FLUSH_TO: + if (pi->amp_move_id) + result = L2CAP_CONF_UNACCEPT; + else + pi->remote_conf.flush_to = val; + break; + + case L2CAP_CONF_QOS: + if (pi->amp_move_id) + result = L2CAP_CONF_UNACCEPT; + break; + + case L2CAP_CONF_RFC: + if (olen == sizeof(rfc)) + memcpy(&rfc, (void *) val, olen); + if (pi->mode != rfc.mode || + rfc.mode == L2CAP_MODE_BASIC) + result = L2CAP_CONF_UNACCEPT; + break; + + case L2CAP_CONF_FCS: + pi->remote_conf.fcs = val; + break; + + case L2CAP_CONF_EXT_FS: + if (olen == sizeof(fs)) { + memcpy(&fs, (void *) val, olen); + if (fs.type != L2CAP_SERVICE_BEST_EFFORT) + result = L2CAP_CONF_FLOW_SPEC_REJECT; + else { + pi->remote_conf.flush_to = + le32_to_cpu(fs.flush_to); + } + } + break; + + case L2CAP_CONF_EXT_WINDOW: + tx_win = val; + break; + + default: + if (hint) break; result = L2CAP_CONF_UNKNOWN; *((u8 *) ptr++) = type; break; + } + } + + BT_DBG("result 0x%2.2x cur mode 0x%2.2x req mode 0x%2.2x", + result, pi->mode, rfc.mode); + + if (result == L2CAP_CONF_SUCCESS) { + /* Configure output options and let the other side know + * which ones we don't like. */ + + /* Don't allow mtu to decrease. */ + if (mtu < pi->omtu) + result = L2CAP_CONF_UNACCEPT; + + BT_DBG("mtu %d omtu %d", mtu, pi->omtu); + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu); + + /* Don't allow extended transmit window to change. */ + if (tx_win != pi->remote_tx_win) { + result = L2CAP_CONF_UNACCEPT; + l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_WINDOW, 2, + pi->remote_tx_win); + } + + if (rfc.mode == L2CAP_MODE_ERTM) { + pi->remote_conf.retrans_timeout = + le16_to_cpu(rfc.retrans_timeout); + pi->remote_conf.monitor_timeout = + le16_to_cpu(rfc.monitor_timeout); + + BT_DBG("remote conf monitor timeout %d", + pi->remote_conf.monitor_timeout); + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, + sizeof(rfc), (unsigned long) &rfc); + } + + } + + if (result != L2CAP_CONF_SUCCESS) + goto done; + + pi->fcs = pi->remote_conf.fcs | pi->local_conf.fcs ; + + if (pi->rx_state == L2CAP_ERTM_RX_STATE_WAIT_F_FLAG) { + pi->flush_to = pi->remote_conf.flush_to; + pi->retrans_timeout = pi->remote_conf.retrans_timeout; + + if (pi->amp_move_id) + pi->monitor_timeout = pi->remote_conf.monitor_timeout; + else + pi->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO; + BT_DBG("mode %d monitor timeout %d", + pi->mode, pi->monitor_timeout); + + } + +done: + rsp->scid = cpu_to_le16(pi->dcid); + rsp->result = cpu_to_le16(result); + rsp->flags = cpu_to_le16(0x0000); + + return ptr - data; +} + +static int l2cap_parse_conf_rsp(struct sock *sk, void *rsp, int len, void *data, u16 *result) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct l2cap_conf_req *req = data; + void *ptr = req->data; + int type, olen; + unsigned long val; + struct l2cap_conf_rfc rfc; + + BT_DBG("sk %p, rsp %p, len %d, req %p", sk, rsp, len, data); + + while (len >= L2CAP_CONF_OPT_SIZE) { + len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); + + switch (type) { + case L2CAP_CONF_MTU: + if (val < L2CAP_DEFAULT_MIN_MTU) { + *result = L2CAP_CONF_UNACCEPT; + pi->imtu = L2CAP_DEFAULT_MIN_MTU; + } else + pi->imtu = val; + l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu); + break; + + case L2CAP_CONF_FLUSH_TO: + pi->flush_to = val; + l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO, + 2, pi->flush_to); + break; + + case L2CAP_CONF_RFC: + if (olen == sizeof(rfc)) + memcpy(&rfc, (void *)val, olen); + + if ((pi->conf_state & L2CAP_CONF_STATE2_DEVICE) && + rfc.mode != pi->mode) + return -ECONNREFUSED; + + pi->fcs = 0; + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, + sizeof(rfc), (unsigned long) &rfc); + break; + + case L2CAP_CONF_EXT_WINDOW: + pi->tx_win = val; + + if (pi->tx_win > L2CAP_TX_WIN_MAX_ENHANCED) + pi->tx_win = L2CAP_TX_WIN_MAX_ENHANCED; + + l2cap_add_conf_opt(&ptr, L2CAP_CONF_EXT_WINDOW, + 2, pi->tx_win); + break; + + default: + break; + } + } + + if (pi->mode == L2CAP_MODE_BASIC && pi->mode != rfc.mode) + return -ECONNREFUSED; + + pi->mode = rfc.mode; + + if (*result == L2CAP_CONF_SUCCESS) { + switch (rfc.mode) { + case L2CAP_MODE_ERTM: + pi->retrans_timeout = le16_to_cpu(rfc.retrans_timeout); + pi->monitor_timeout = le16_to_cpu(rfc.monitor_timeout); + pi->mps = le16_to_cpu(rfc.max_pdu_size); + break; + case L2CAP_MODE_STREAMING: + pi->mps = le16_to_cpu(rfc.max_pdu_size); + } + } + + req->dcid = cpu_to_le16(pi->dcid); + req->flags = cpu_to_le16(0x0000); + + return ptr - data; +} + +static int l2cap_build_conf_rsp(struct sock *sk, void *data, u16 result, u16 flags) +{ + struct l2cap_conf_rsp *rsp = data; + void *ptr = rsp->data; + + BT_DBG("sk %p", sk); + + rsp->scid = cpu_to_le16(l2cap_pi(sk)->dcid); + rsp->result = cpu_to_le16(result); + rsp->flags = cpu_to_le16(flags); + + return ptr - data; +} + +static void l2cap_conf_rfc_get(struct sock *sk, void *rsp, int len) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + int type, olen; + unsigned long val; + struct l2cap_conf_rfc rfc; + + BT_DBG("sk %p, rsp %p, len %d", sk, rsp, len); + + if ((pi->mode != L2CAP_MODE_ERTM) && (pi->mode != L2CAP_MODE_STREAMING)) + return; + + while (len >= L2CAP_CONF_OPT_SIZE) { + len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); + + switch (type) { + case L2CAP_CONF_RFC: + if (olen == sizeof(rfc)) + memcpy(&rfc, (void *)val, olen); + goto done; + } + } + +done: + switch (rfc.mode) { + case L2CAP_MODE_ERTM: + pi->retrans_timeout = le16_to_cpu(rfc.retrans_timeout); + pi->monitor_timeout = le16_to_cpu(rfc.monitor_timeout); + pi->mps = le16_to_cpu(rfc.max_pdu_size); + break; + case L2CAP_MODE_STREAMING: + pi->mps = le16_to_cpu(rfc.max_pdu_size); + } +} + +static void l2cap_conf_ext_fs_get(struct sock *sk, void *rsp, int len) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + int type, olen; + unsigned long val; + struct l2cap_conf_ext_fs fs; + + BT_DBG("sk %p, rsp %p, len %d", sk, rsp, len); + + while (len >= L2CAP_CONF_OPT_SIZE) { + len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); + if ((type == L2CAP_CONF_EXT_FS) && + (olen == sizeof(struct l2cap_conf_ext_fs))) { + memcpy(&fs, (void *)val, olen); + pi->local_fs.id = fs.id; + pi->local_fs.type = fs.type; + pi->local_fs.max_sdu = le16_to_cpu(fs.max_sdu); + pi->local_fs.sdu_arr_time = + le32_to_cpu(fs.sdu_arr_time); + pi->local_fs.acc_latency = le32_to_cpu(fs.acc_latency); + pi->local_fs.flush_to = le32_to_cpu(fs.flush_to); + break; + } + } + +} + +static int l2cap_finish_amp_move(struct sock *sk) +{ + struct l2cap_pinfo *pi; + int err; + + BT_DBG("sk %p", sk); + + pi = l2cap_pi(sk); + + pi->amp_move_role = L2CAP_AMP_MOVE_NONE; + pi->rx_state = L2CAP_ERTM_RX_STATE_RECV; + + if (pi->ampcon) + pi->conn->mtu = pi->ampcon->hdev->acl_mtu; + else + pi->conn->mtu = pi->conn->hcon->hdev->acl_mtu; + + err = l2cap_setup_resegment(sk); + + return err; +} + +static int l2cap_amp_move_reconf_rsp(struct sock *sk, void *rsp, int len, + u16 result) +{ + int err = 0; + struct l2cap_conf_rfc rfc = {.mode = L2CAP_MODE_BASIC}; + struct l2cap_pinfo *pi = l2cap_pi(sk); + + BT_DBG("sk %p, rsp %p, len %d, res 0x%2.2x", sk, rsp, len, result); + + if (pi->reconf_state == L2CAP_RECONF_NONE) + return -ECONNREFUSED; + + if (result == L2CAP_CONF_SUCCESS) { + while (len >= L2CAP_CONF_OPT_SIZE) { + int type, olen; + unsigned long val; + + len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); + + if (type == L2CAP_CONF_RFC) { + if (olen == sizeof(rfc)) + memcpy(&rfc, (void *)val, olen); + if (rfc.mode != pi->mode && + rfc.mode != L2CAP_MODE_ERTM) { + err = -ECONNREFUSED; + goto done; + } + break; + } + } + } + +done: + l2cap_ertm_stop_ack_timer(pi); + l2cap_ertm_stop_retrans_timer(pi); + l2cap_ertm_stop_monitor_timer(pi); + + if (l2cap_pi(sk)->reconf_state == L2CAP_RECONF_ACC) { + l2cap_pi(sk)->reconf_state = L2CAP_RECONF_NONE; + + /* Respond to poll */ + err = l2cap_answer_move_poll(sk); + + } else if (l2cap_pi(sk)->reconf_state == L2CAP_RECONF_INT) { + + /* If moving to BR/EDR, use default timeout defined by + * the spec */ + if (pi->amp_move_id == 0) + pi->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO; + + if (pi->mode == L2CAP_MODE_ERTM) { + l2cap_ertm_tx(sk, NULL, NULL, + L2CAP_ERTM_EVENT_EXPLICIT_POLL); + pi->rx_state = L2CAP_ERTM_RX_STATE_WAIT_F_FLAG; + } + } + + return err; +} + + +static inline int l2cap_command_rej(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_cmd_rej *rej = (struct l2cap_cmd_rej *) data; + + if (rej->reason != 0x0000) + return 0; + + if ((conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) && + cmd->ident == conn->info_ident) { + del_timer(&conn->info_timer); + + conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; + conn->info_ident = 0; + + l2cap_conn_start(conn); + } + + return 0; +} + +static struct sock *l2cap_create_connect(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, + u8 *data, u8 rsp_code, + u8 amp_id) +{ + struct l2cap_chan_list *list = &conn->chan_list; + struct l2cap_conn_req *req = (struct l2cap_conn_req *) data; + struct l2cap_conn_rsp rsp; + struct sock *parent, *sk = NULL; + int result, status = L2CAP_CS_NO_INFO; + + u16 dcid = 0, scid = __le16_to_cpu(req->scid); + __le16 psm = req->psm; + + BT_DBG("psm 0x%2.2x scid 0x%4.4x", psm, scid); + + /* Check if we have socket listening on psm */ + parent = l2cap_get_sock_by_psm(BT_LISTEN, psm, conn->src); + if (!parent) { + result = L2CAP_CR_BAD_PSM; + goto sendresp; + } + + bh_lock_sock(parent); + + /* Check if the ACL is secure enough (if not SDP) */ + if (psm != cpu_to_le16(0x0001) && + !hci_conn_check_link_mode(conn->hcon)) { + conn->disc_reason = 0x05; + result = L2CAP_CR_SEC_BLOCK; + goto response; + } + + result = L2CAP_CR_NO_MEM; + + /* Check for backlog size */ + if (sk_acceptq_is_full(parent)) { + BT_DBG("backlog full %d", parent->sk_ack_backlog); + goto response; + } + + sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP, GFP_ATOMIC); + if (!sk) + goto response; + + write_lock_bh(&list->lock); + + /* Check if we already have channel with that dcid */ + if (__l2cap_get_chan_by_dcid(list, scid)) { + write_unlock_bh(&list->lock); + sock_set_flag(sk, SOCK_ZAPPED); + l2cap_sock_kill(sk); + sk = NULL; + goto response; + } + + hci_conn_hold(conn->hcon); + + l2cap_sock_init(sk, parent); + bacpy(&bt_sk(sk)->src, conn->src); + bacpy(&bt_sk(sk)->dst, conn->dst); + l2cap_pi(sk)->psm = psm; + l2cap_pi(sk)->dcid = scid; + + bt_accept_enqueue(parent, sk); + + __l2cap_chan_add(conn, sk); + dcid = l2cap_pi(sk)->scid; + l2cap_pi(sk)->amp_id = amp_id; + + l2cap_sock_set_timer(sk, sk->sk_sndtimeo); + + l2cap_pi(sk)->ident = cmd->ident; + + if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE) { + if (l2cap_check_security(sk)) { + if (bt_sk(sk)->defer_setup) { + sk->sk_state = BT_CONNECT2; + result = L2CAP_CR_PEND; + status = L2CAP_CS_AUTHOR_PEND; + parent->sk_data_ready(parent, 0); + } else { + /* Force pending result for AMP controllers. + * The connection will succeed after the + * physical link is up. */ + if (amp_id) { + sk->sk_state = BT_CONNECT2; + result = L2CAP_CR_PEND; + } else { + sk->sk_state = BT_CONFIG; + result = L2CAP_CR_SUCCESS; + } + status = L2CAP_CS_NO_INFO; + } + } else { + sk->sk_state = BT_CONNECT2; + result = L2CAP_CR_PEND; + status = L2CAP_CS_AUTHEN_PEND; + } + } else { + sk->sk_state = BT_CONNECT2; + result = L2CAP_CR_PEND; + status = L2CAP_CS_NO_INFO; + } + + write_unlock_bh(&list->lock); + +response: + bh_unlock_sock(parent); + +sendresp: + rsp.scid = cpu_to_le16(scid); + rsp.dcid = cpu_to_le16(dcid); + rsp.result = cpu_to_le16(result); + rsp.status = cpu_to_le16(status); + l2cap_send_cmd(conn, cmd->ident, rsp_code, sizeof(rsp), &rsp); + + if (!(conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE)) { + struct l2cap_info_req info; + info.type = cpu_to_le16(L2CAP_IT_FEAT_MASK); + + conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_SENT; + conn->info_ident = l2cap_get_ident(conn); + + mod_timer(&conn->info_timer, jiffies + + msecs_to_jiffies(L2CAP_INFO_TIMEOUT)); + + l2cap_send_cmd(conn, conn->info_ident, + L2CAP_INFO_REQ, sizeof(info), &info); + } + + if (sk && !(l2cap_pi(sk)->conf_state & L2CAP_CONF_REQ_SENT) && + result == L2CAP_CR_SUCCESS) { + u8 buf[128]; + l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT; + l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, + l2cap_build_conf_req(sk, buf), buf); + l2cap_pi(sk)->num_conf_req++; + } + + return sk; +} + +static inline int l2cap_connect_req(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u8 *data) +{ + l2cap_create_connect(conn, cmd, data, L2CAP_CONN_RSP, 0); + return 0; +} + +static inline int l2cap_connect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_conn_rsp *rsp = (struct l2cap_conn_rsp *) data; + u16 scid, dcid, result, status; + struct sock *sk; + u8 req[128]; + + scid = __le16_to_cpu(rsp->scid); + dcid = __le16_to_cpu(rsp->dcid); + result = __le16_to_cpu(rsp->result); + status = __le16_to_cpu(rsp->status); + + BT_DBG("dcid 0x%4.4x scid 0x%4.4x result 0x%2.2x status 0x%2.2x", dcid, scid, result, status); + + if (scid) { + sk = l2cap_get_chan_by_scid(&conn->chan_list, scid); + if (!sk) + return -EFAULT; + } else { + sk = l2cap_get_chan_by_ident(&conn->chan_list, cmd->ident); + if (!sk) + return -EFAULT; + } + + switch (result) { + case L2CAP_CR_SUCCESS: + sk->sk_state = BT_CONFIG; + l2cap_pi(sk)->ident = 0; + l2cap_pi(sk)->dcid = dcid; + l2cap_pi(sk)->conf_state &= ~L2CAP_CONF_CONNECT_PEND; + + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_REQ_SENT) + break; + + l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT; + + l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, + l2cap_build_conf_req(sk, req), req); + l2cap_pi(sk)->num_conf_req++; + break; + + case L2CAP_CR_PEND: + l2cap_pi(sk)->conf_state |= L2CAP_CONF_CONNECT_PEND; + break; + + default: + /* don't delete l2cap channel if sk is owned by user */ + if (sock_owned_by_user(sk)) { + sk->sk_state = BT_DISCONN; + l2cap_sock_clear_timer(sk); + l2cap_sock_set_timer(sk, HZ / 5); + break; } + + l2cap_chan_del(sk, ECONNREFUSED); + break; + } + + bh_unlock_sock(sk); + return 0; +} + +static inline void set_default_fcs(struct l2cap_pinfo *pi) +{ + /* FCS is enabled only in ERTM or streaming mode, if one or both + * sides request it. + */ + if (pi->mode != L2CAP_MODE_ERTM && pi->mode != L2CAP_MODE_STREAMING) + pi->fcs = L2CAP_FCS_NONE; + else if (!(pi->conf_state & L2CAP_CONF_NO_FCS_RECV)) + pi->fcs = L2CAP_FCS_CRC16; +} + +static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) +{ + struct l2cap_conf_req *req = (struct l2cap_conf_req *) data; + u16 dcid, flags; + u8 rspbuf[64]; + struct l2cap_conf_rsp *rsp = (struct l2cap_conf_rsp *) rspbuf; + struct sock *sk; + int len; + u8 amp_move_reconf = 0; + + dcid = __le16_to_cpu(req->dcid); + flags = __le16_to_cpu(req->flags); + + BT_DBG("dcid 0x%4.4x flags 0x%2.2x", dcid, flags); + + sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid); + if (!sk) + return -ENOENT; + + BT_DBG("sk_state 0x%2.2x rx_state 0x%2.2x " + "reconf_state 0x%2.2x amp_id 0x%2.2x amp_move_id 0x%2.2x", + sk->sk_state, l2cap_pi(sk)->rx_state, + l2cap_pi(sk)->reconf_state, l2cap_pi(sk)->amp_id, + l2cap_pi(sk)->amp_move_id); + + /* Detect a reconfig request due to channel move between + * BR/EDR and AMP + */ + if (sk->sk_state == BT_CONNECTED && + l2cap_pi(sk)->rx_state == + L2CAP_ERTM_RX_STATE_WAIT_P_FLAG_RECONFIGURE) + l2cap_pi(sk)->reconf_state = L2CAP_RECONF_ACC; + + if (l2cap_pi(sk)->reconf_state != L2CAP_RECONF_NONE) + amp_move_reconf = 1; + + if (sk->sk_state != BT_CONFIG && !amp_move_reconf) { + struct l2cap_cmd_rej rej; + + rej.reason = cpu_to_le16(0x0002); + l2cap_send_cmd(conn, cmd->ident, L2CAP_COMMAND_REJ, + sizeof(rej), &rej); + goto unlock; + } + + /* Reject if config buffer is too small. */ + len = cmd_len - sizeof(*req); + if (l2cap_pi(sk)->conf_len + len > sizeof(l2cap_pi(sk)->conf_req)) { + l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, + l2cap_build_conf_rsp(sk, rspbuf, + L2CAP_CONF_REJECT, flags), rspbuf); + goto unlock; + } + + /* Store config. */ + memcpy(l2cap_pi(sk)->conf_req + l2cap_pi(sk)->conf_len, req->data, len); + l2cap_pi(sk)->conf_len += len; + + if (flags & 0x0001) { + /* Incomplete config. Send empty response. */ + l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, + l2cap_build_conf_rsp(sk, rspbuf, + L2CAP_CONF_SUCCESS, 0x0001), rspbuf); + goto unlock; + } + + /* Complete config. */ + if (!amp_move_reconf) + len = l2cap_parse_conf_req(sk, rspbuf); + else + len = l2cap_parse_amp_move_reconf_req(sk, rspbuf); + + if (len < 0) { + l2cap_send_disconn_req(conn, sk, ECONNRESET); + goto unlock; + } + + l2cap_pi(sk)->conf_ident = cmd->ident; + l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, len, rspbuf); + + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_LOCKSTEP && + rsp->result == cpu_to_le16(L2CAP_CONF_PENDING) && + !l2cap_pi(sk)->amp_id) { + /* Send success response right after pending if using + * lockstep config on BR/EDR + */ + rsp->result = cpu_to_le16(L2CAP_CONF_SUCCESS); + l2cap_pi(sk)->conf_state |= L2CAP_CONF_OUTPUT_DONE; + l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, len, rspbuf); + } + + /* Reset config buffer. */ + l2cap_pi(sk)->conf_len = 0; + + if (amp_move_reconf) + goto unlock; + + l2cap_pi(sk)->num_conf_rsp++; + + if (!(l2cap_pi(sk)->conf_state & L2CAP_CONF_OUTPUT_DONE)) + goto unlock; + + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_INPUT_DONE) { + set_default_fcs(l2cap_pi(sk)); + + sk->sk_state = BT_CONNECTED; + + if (l2cap_pi(sk)->mode == L2CAP_MODE_ERTM || + l2cap_pi(sk)->mode == L2CAP_MODE_STREAMING) + l2cap_ertm_init(sk); + + l2cap_chan_ready(sk); + goto unlock; + } + + if (!(l2cap_pi(sk)->conf_state & L2CAP_CONF_REQ_SENT)) { + u8 buf[64]; + l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT; + l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, + l2cap_build_conf_req(sk, buf), buf); + l2cap_pi(sk)->num_conf_req++; } - if (chan->num_conf_rsp || chan->num_conf_req > 1) +unlock: + bh_unlock_sock(sk); + return 0; +} + +static inline int l2cap_config_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_conf_rsp *rsp = (struct l2cap_conf_rsp *)data; + u16 scid, flags, result; + struct sock *sk; + struct l2cap_pinfo *pi; + int len = cmd->len - sizeof(*rsp); + + scid = __le16_to_cpu(rsp->scid); + flags = __le16_to_cpu(rsp->flags); + result = __le16_to_cpu(rsp->result); + + BT_DBG("scid 0x%4.4x flags 0x%2.2x result 0x%2.2x", + scid, flags, result); + + sk = l2cap_get_chan_by_scid(&conn->chan_list, scid); + if (!sk) + return 0; + + pi = l2cap_pi(sk); + + if (pi->reconf_state != L2CAP_RECONF_NONE) { + l2cap_amp_move_reconf_rsp(sk, rsp->data, len, result); goto done; + } - switch (chan->mode) { - case L2CAP_MODE_STREAMING: - case L2CAP_MODE_ERTM: - if (!test_bit(CONF_STATE2_DEVICE, &chan->conf_state)) { - chan->mode = l2cap_select_mode(rfc.mode, - chan->conn->feat_mask); + switch (result) { + case L2CAP_CONF_SUCCESS: + if (pi->conf_state & L2CAP_CONF_LOCKSTEP && + !(pi->conf_state & L2CAP_CONF_LOCKSTEP_PEND)) { + /* Lockstep procedure requires a pending response + * before success. + */ + l2cap_send_disconn_req(conn, sk, ECONNRESET); + goto done; + } + + l2cap_conf_rfc_get(sk, rsp->data, len); + break; + + case L2CAP_CONF_PENDING: + if (!(pi->conf_state & L2CAP_CONF_LOCKSTEP)) { + l2cap_send_disconn_req(conn, sk, ECONNRESET); + goto done; + } + + l2cap_conf_rfc_get(sk, rsp->data, len); + + pi->conf_state |= L2CAP_CONF_LOCKSTEP_PEND; + + l2cap_conf_ext_fs_get(sk, rsp->data, len); + + if (pi->amp_id && pi->conf_state & L2CAP_CONF_PEND_SENT) { + struct hci_chan *chan; + + /* Already sent a 'pending' response, so set up + * the logical link now + */ + chan = l2cap_chan_admit(pi->amp_id, pi); + if (!chan) { + l2cap_send_disconn_req(pi->conn, sk, + ECONNRESET); + goto done; + } + + chan->l2cap_sk = sk; + if (chan->state == BT_CONNECTED) + l2cap_create_cfm(chan, 0); + } + + goto done; + + case L2CAP_CONF_UNACCEPT: + if (pi->num_conf_rsp <= L2CAP_CONF_MAX_CONF_RSP) { + char req[64]; + + if (len > sizeof(req) - sizeof(struct l2cap_conf_req)) { + l2cap_send_disconn_req(conn, sk, ECONNRESET); + goto done; + } + + /* throw out any old stored conf requests */ + result = L2CAP_CONF_SUCCESS; + len = l2cap_parse_conf_rsp(sk, rsp->data, + len, req, &result); + if (len < 0) { + l2cap_send_disconn_req(conn, sk, ECONNRESET); + goto done; + } + + l2cap_send_cmd(conn, l2cap_get_ident(conn), + L2CAP_CONF_REQ, len, req); + pi->num_conf_req++; + if (result != L2CAP_CONF_SUCCESS) + goto done; break; } - if (chan->mode != rfc.mode) - return -ECONNREFUSED; + default: + sk->sk_err = ECONNRESET; + l2cap_sock_set_timer(sk, HZ * 5); + l2cap_send_disconn_req(conn, sk, ECONNRESET); + goto done; + } - break; + if (flags & 0x01) + goto done; + + pi->conf_state |= L2CAP_CONF_INPUT_DONE; + + if (pi->conf_state & L2CAP_CONF_OUTPUT_DONE) { + set_default_fcs(pi); + + sk->sk_state = BT_CONNECTED; + + if (pi->mode == L2CAP_MODE_ERTM || + pi->mode == L2CAP_MODE_STREAMING) + l2cap_ertm_init(sk); + + l2cap_chan_ready(sk); } done: - if (chan->mode != rfc.mode) { - result = L2CAP_CONF_UNACCEPT; - rfc.mode = chan->mode; + bh_unlock_sock(sk); + return 0; +} - if (chan->num_conf_rsp == 1) - return -ECONNREFUSED; +static inline int l2cap_disconnect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_disconn_req *req = (struct l2cap_disconn_req *) data; + struct l2cap_disconn_rsp rsp; + u16 dcid, scid; + struct sock *sk; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, - sizeof(rfc), (unsigned long) &rfc); - } + scid = __le16_to_cpu(req->scid); + dcid = __le16_to_cpu(req->dcid); + BT_DBG("scid 0x%4.4x dcid 0x%4.4x", scid, dcid); - if (result == L2CAP_CONF_SUCCESS) { - /* Configure output options and let the other side know - * which ones we don't like. */ + sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid); + if (!sk) + return 0; - if (mtu < L2CAP_DEFAULT_MIN_MTU) - result = L2CAP_CONF_UNACCEPT; - else { - chan->omtu = mtu; - set_bit(CONF_MTU_DONE, &chan->conf_state); + rsp.dcid = cpu_to_le16(l2cap_pi(sk)->scid); + rsp.scid = cpu_to_le16(l2cap_pi(sk)->dcid); + l2cap_send_cmd(conn, cmd->ident, L2CAP_DISCONN_RSP, sizeof(rsp), &rsp); + + /* Only do cleanup if a disconnect request was not sent already */ + if (sk->sk_state != BT_DISCONN) { + sk->sk_shutdown = SHUTDOWN_MASK; + + skb_queue_purge(TX_QUEUE(sk)); + + if (l2cap_pi(sk)->mode == L2CAP_MODE_ERTM) { + skb_queue_purge(SREJ_QUEUE(sk)); + + __cancel_delayed_work(&l2cap_pi(sk)->ack_work); + __cancel_delayed_work(&l2cap_pi(sk)->retrans_work); + __cancel_delayed_work(&l2cap_pi(sk)->monitor_work); } - l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, chan->omtu); + } - switch (rfc.mode) { - case L2CAP_MODE_BASIC: - chan->fcs = L2CAP_FCS_NONE; - set_bit(CONF_MODE_DONE, &chan->conf_state); - break; + /* don't delete l2cap channel if sk is owned by user */ + if (sock_owned_by_user(sk)) { + sk->sk_state = BT_DISCONN; + l2cap_sock_clear_timer(sk); + l2cap_sock_set_timer(sk, HZ / 5); + bh_unlock_sock(sk); + return 0; + } + + l2cap_chan_del(sk, ECONNRESET); + + bh_unlock_sock(sk); + + l2cap_sock_kill(sk); + return 0; +} + +static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_disconn_rsp *rsp = (struct l2cap_disconn_rsp *) data; + u16 dcid, scid; + struct sock *sk; + + scid = __le16_to_cpu(rsp->scid); + dcid = __le16_to_cpu(rsp->dcid); + + BT_DBG("dcid 0x%4.4x scid 0x%4.4x", dcid, scid); + + sk = l2cap_get_chan_by_scid(&conn->chan_list, scid); + if (!sk) + return 0; + + /* don't delete l2cap channel if sk is owned by user */ + if (sock_owned_by_user(sk)) { + sk->sk_state = BT_DISCONN; + l2cap_sock_clear_timer(sk); + l2cap_sock_set_timer(sk, HZ / 5); + bh_unlock_sock(sk); + return 0; + } + + l2cap_chan_del(sk, 0); + bh_unlock_sock(sk); + + l2cap_sock_kill(sk); + return 0; +} + +static inline int l2cap_information_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_info_req *req = (struct l2cap_info_req *) data; + u16 type; + + type = __le16_to_cpu(req->type); + + BT_DBG("type 0x%4.4x", type); + + if (type == L2CAP_IT_FEAT_MASK) { + u8 buf[8]; + u32 feat_mask = l2cap_feat_mask; + struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) buf; + rsp->type = cpu_to_le16(L2CAP_IT_FEAT_MASK); + rsp->result = cpu_to_le16(L2CAP_IR_SUCCESS); + if (!disable_ertm) + feat_mask |= L2CAP_FEAT_ERTM | L2CAP_FEAT_STREAMING + | L2CAP_FEAT_FCS | L2CAP_FEAT_EXT_WINDOW; + put_unaligned_le32(feat_mask, rsp->data); + l2cap_send_cmd(conn, cmd->ident, + L2CAP_INFO_RSP, sizeof(buf), buf); + } else if (type == L2CAP_IT_FIXED_CHAN) { + u8 buf[12]; + struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) buf; + rsp->type = cpu_to_le16(L2CAP_IT_FIXED_CHAN); + rsp->result = cpu_to_le16(L2CAP_IR_SUCCESS); + memcpy(buf + 4, l2cap_fixed_chan, 8); + l2cap_send_cmd(conn, cmd->ident, + L2CAP_INFO_RSP, sizeof(buf), buf); + } else { + struct l2cap_info_rsp rsp; + rsp.type = cpu_to_le16(type); + rsp.result = cpu_to_le16(L2CAP_IR_NOTSUPP); + l2cap_send_cmd(conn, cmd->ident, + L2CAP_INFO_RSP, sizeof(rsp), &rsp); + } - case L2CAP_MODE_ERTM: - chan->remote_tx_win = rfc.txwin_size; - chan->remote_max_tx = rfc.max_transmit; + return 0; +} - if (le16_to_cpu(rfc.max_pdu_size) > chan->conn->mtu - 10) - rfc.max_pdu_size = cpu_to_le16(chan->conn->mtu - 10); +static inline int l2cap_information_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) data; + u16 type, result; - chan->remote_mps = le16_to_cpu(rfc.max_pdu_size); + type = __le16_to_cpu(rsp->type); + result = __le16_to_cpu(rsp->result); - rfc.retrans_timeout = - le16_to_cpu(L2CAP_DEFAULT_RETRANS_TO); - rfc.monitor_timeout = - le16_to_cpu(L2CAP_DEFAULT_MONITOR_TO); + BT_DBG("type 0x%4.4x result 0x%2.2x", type, result); - set_bit(CONF_MODE_DONE, &chan->conf_state); + /* L2CAP Info req/rsp are unbound to channels, add extra checks */ + if (cmd->ident != conn->info_ident || + conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE) + return 0; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, - sizeof(rfc), (unsigned long) &rfc); + del_timer(&conn->info_timer); - break; + if (result != L2CAP_IR_SUCCESS) { + conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; + conn->info_ident = 0; - case L2CAP_MODE_STREAMING: - if (le16_to_cpu(rfc.max_pdu_size) > chan->conn->mtu - 10) - rfc.max_pdu_size = cpu_to_le16(chan->conn->mtu - 10); + l2cap_conn_start(conn); - chan->remote_mps = le16_to_cpu(rfc.max_pdu_size); + return 0; + } - set_bit(CONF_MODE_DONE, &chan->conf_state); + if (type == L2CAP_IT_FEAT_MASK) { + conn->feat_mask = get_unaligned_le32(rsp->data); - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, - sizeof(rfc), (unsigned long) &rfc); + if (conn->feat_mask & L2CAP_FEAT_FIXED_CHAN) { + struct l2cap_info_req req; + req.type = cpu_to_le16(L2CAP_IT_FIXED_CHAN); - break; + conn->info_ident = l2cap_get_ident(conn); - default: - result = L2CAP_CONF_UNACCEPT; + l2cap_send_cmd(conn, conn->info_ident, + L2CAP_INFO_REQ, sizeof(req), &req); + } else { + conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; + conn->info_ident = 0; - memset(&rfc, 0, sizeof(rfc)); - rfc.mode = chan->mode; + l2cap_conn_start(conn); } + } else if (type == L2CAP_IT_FIXED_CHAN) { + conn->fc_mask = rsp->data[0]; + conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; + conn->info_ident = 0; - if (result == L2CAP_CONF_SUCCESS) - set_bit(CONF_OUTPUT_DONE, &chan->conf_state); + l2cap_conn_start(conn); } - rsp->scid = cpu_to_le16(chan->dcid); - rsp->result = cpu_to_le16(result); - rsp->flags = cpu_to_le16(0x0000); - return ptr - data; + return 0; } -static int l2cap_parse_conf_rsp(struct l2cap_chan *chan, void *rsp, int len, void *data, u16 *result) +static void l2cap_send_move_chan_req(struct l2cap_conn *conn, + struct l2cap_pinfo *pi, u16 icid, u8 dest_amp_id) { - struct l2cap_conf_req *req = data; - void *ptr = req->data; - int type, olen; - unsigned long val; - struct l2cap_conf_rfc rfc; + struct l2cap_move_chan_req req; + u8 ident; - BT_DBG("chan %p, rsp %p, len %d, req %p", chan, rsp, len, data); + BT_DBG("pi %p, icid %d, dest_amp_id %d", pi, (int) icid, + (int) dest_amp_id); - while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); + ident = l2cap_get_ident(conn); + if (pi) + pi->ident = ident; - switch (type) { - case L2CAP_CONF_MTU: - if (val < L2CAP_DEFAULT_MIN_MTU) { - *result = L2CAP_CONF_UNACCEPT; - chan->imtu = L2CAP_DEFAULT_MIN_MTU; - } else - chan->imtu = val; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, chan->imtu); - break; + req.icid = cpu_to_le16(icid); + req.dest_amp_id = dest_amp_id; - case L2CAP_CONF_FLUSH_TO: - chan->flush_to = val; - l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO, - 2, chan->flush_to); - break; + l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_REQ, sizeof(req), &req); +} - case L2CAP_CONF_RFC: - if (olen == sizeof(rfc)) - memcpy(&rfc, (void *)val, olen); +static void l2cap_send_move_chan_rsp(struct l2cap_conn *conn, u8 ident, + u16 icid, u16 result) +{ + struct l2cap_move_chan_rsp rsp; - if (test_bit(CONF_STATE2_DEVICE, &chan->conf_state) && - rfc.mode != chan->mode) - return -ECONNREFUSED; + BT_DBG("icid %d, result %d", (int) icid, (int) result); - chan->fcs = 0; + rsp.icid = cpu_to_le16(icid); + rsp.result = cpu_to_le16(result); - l2cap_add_conf_opt(&ptr, L2CAP_CONF_RFC, - sizeof(rfc), (unsigned long) &rfc); - break; - } - } + l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_RSP, sizeof(rsp), &rsp); +} - if (chan->mode == L2CAP_MODE_BASIC && chan->mode != rfc.mode) - return -ECONNREFUSED; +static void l2cap_send_move_chan_cfm(struct l2cap_conn *conn, + struct l2cap_pinfo *pi, u16 icid, u16 result) +{ + struct l2cap_move_chan_cfm cfm; + u8 ident; - chan->mode = rfc.mode; + BT_DBG("icid %d, result %d", (int) icid, (int) result); - if (*result == L2CAP_CONF_SUCCESS) { - switch (rfc.mode) { - case L2CAP_MODE_ERTM: - chan->retrans_timeout = le16_to_cpu(rfc.retrans_timeout); - chan->monitor_timeout = le16_to_cpu(rfc.monitor_timeout); - chan->mps = le16_to_cpu(rfc.max_pdu_size); - break; - case L2CAP_MODE_STREAMING: - chan->mps = le16_to_cpu(rfc.max_pdu_size); - } - } + ident = l2cap_get_ident(conn); + if (pi) + pi->ident = ident; - req->dcid = cpu_to_le16(chan->dcid); - req->flags = cpu_to_le16(0x0000); + cfm.icid = cpu_to_le16(icid); + cfm.result = cpu_to_le16(result); - return ptr - data; + l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM, sizeof(cfm), &cfm); } -static int l2cap_build_conf_rsp(struct l2cap_chan *chan, void *data, u16 result, u16 flags) +static void l2cap_send_move_chan_cfm_rsp(struct l2cap_conn *conn, u8 ident, + u16 icid) { - struct l2cap_conf_rsp *rsp = data; - void *ptr = rsp->data; + struct l2cap_move_chan_cfm_rsp rsp; - BT_DBG("chan %p", chan); - - rsp->scid = cpu_to_le16(chan->dcid); - rsp->result = cpu_to_le16(result); - rsp->flags = cpu_to_le16(flags); + BT_DBG("icid %d", (int) icid); - return ptr - data; + rsp.icid = cpu_to_le16(icid); + l2cap_send_cmd(conn, ident, L2CAP_MOVE_CHAN_CFM_RSP, sizeof(rsp), &rsp); } -void __l2cap_connect_rsp_defer(struct l2cap_chan *chan) +static inline int l2cap_create_channel_req(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u8 *data) { - struct l2cap_conn_rsp rsp; - struct l2cap_conn *conn = chan->conn; - u8 buf[128]; + struct l2cap_create_chan_req *req = + (struct l2cap_create_chan_req *) data; + struct sock *sk; + u16 psm, scid; - rsp.scid = cpu_to_le16(chan->dcid); - rsp.dcid = cpu_to_le16(chan->scid); - rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS); - rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); - l2cap_send_cmd(conn, chan->ident, - L2CAP_CONN_RSP, sizeof(rsp), &rsp); + psm = le16_to_cpu(req->psm); + scid = le16_to_cpu(req->scid); - if (test_and_set_bit(CONF_REQ_SENT, &chan->conf_state)) - return; + BT_DBG("psm %d, scid %d, amp_id %d", (int) psm, (int) scid, + (int) req->amp_id); - l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, - l2cap_build_conf_req(chan, buf), buf); - chan->num_conf_req++; -} + if (req->amp_id) { + struct hci_dev *hdev; -static void l2cap_conf_rfc_get(struct l2cap_chan *chan, void *rsp, int len) -{ - int type, olen; - unsigned long val; - struct l2cap_conf_rfc rfc; + /* Validate AMP controller id */ + hdev = hci_dev_get(A2MP_HCI_ID(req->amp_id)); + if (!hdev || !test_bit(HCI_UP, &hdev->flags)) { + struct l2cap_create_chan_rsp rsp; - BT_DBG("chan %p, rsp %p, len %d", chan, rsp, len); + rsp.dcid = 0; + rsp.scid = cpu_to_le16(scid); + rsp.result = L2CAP_CREATE_CHAN_REFUSED_CONTROLLER; + rsp.status = L2CAP_CREATE_CHAN_STATUS_NONE; - if ((chan->mode != L2CAP_MODE_ERTM) && (chan->mode != L2CAP_MODE_STREAMING)) - return; + l2cap_send_cmd(conn, cmd->ident, L2CAP_CREATE_CHAN_RSP, + sizeof(rsp), &rsp); - while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&rsp, &type, &olen, &val); + if (hdev) + hci_dev_put(hdev); - switch (type) { - case L2CAP_CONF_RFC: - if (olen == sizeof(rfc)) - memcpy(&rfc, (void *)val, olen); - goto done; + return 0; } - } -done: - switch (rfc.mode) { - case L2CAP_MODE_ERTM: - chan->retrans_timeout = le16_to_cpu(rfc.retrans_timeout); - chan->monitor_timeout = le16_to_cpu(rfc.monitor_timeout); - chan->mps = le16_to_cpu(rfc.max_pdu_size); - break; - case L2CAP_MODE_STREAMING: - chan->mps = le16_to_cpu(rfc.max_pdu_size); + hci_dev_put(hdev); } -} - -static inline int l2cap_command_rej(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) -{ - struct l2cap_cmd_rej *rej = (struct l2cap_cmd_rej *) data; - - if (rej->reason != 0x0000) - return 0; - if ((conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_SENT) && - cmd->ident == conn->info_ident) { - del_timer(&conn->info_timer); + sk = l2cap_create_connect(conn, cmd, data, L2CAP_CREATE_CHAN_RSP, + req->amp_id); - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; - conn->info_ident = 0; + l2cap_pi(sk)->conf_state |= L2CAP_CONF_LOCKSTEP; - l2cap_conn_start(conn); - } + if (sk && req->amp_id) + amp_accept_physical(conn, req->amp_id, sk); return 0; } -static inline int l2cap_connect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +static inline int l2cap_create_channel_rsp(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u8 *data) { - struct l2cap_conn_req *req = (struct l2cap_conn_req *) data; - struct l2cap_conn_rsp rsp; - struct l2cap_chan *chan = NULL, *pchan; - struct sock *parent, *sk = NULL; - int result, status = L2CAP_CS_NO_INFO; + BT_DBG("conn %p", conn); - u16 dcid = 0, scid = __le16_to_cpu(req->scid); - __le16 psm = req->psm; + return l2cap_connect_rsp(conn, cmd, data); +} - BT_DBG("psm 0x%2.2x scid 0x%4.4x", psm, scid); +static inline int l2cap_move_channel_req(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_move_chan_req *req = (struct l2cap_move_chan_req *) data; + struct sock *sk; + struct l2cap_pinfo *pi; + u16 icid = 0; + u16 result = L2CAP_MOVE_CHAN_REFUSED_NOT_ALLOWED; - /* Check if we have socket listening on psm */ - pchan = l2cap_global_chan_by_psm(BT_LISTEN, psm, conn->src); - if (!pchan) { - result = L2CAP_CR_BAD_PSM; - goto sendresp; - } + icid = le16_to_cpu(req->icid); - parent = pchan->sk; + BT_DBG("icid %d, dest_amp_id %d", (int) icid, (int) req->dest_amp_id); - bh_lock_sock(parent); + read_lock(&conn->chan_list.lock); + sk = __l2cap_get_chan_by_dcid(&conn->chan_list, icid); + read_unlock(&conn->chan_list.lock); - /* Check if the ACL is secure enough (if not SDP) */ - if (psm != cpu_to_le16(0x0001) && - !hci_conn_check_link_mode(conn->hcon)) { - conn->disc_reason = 0x05; - result = L2CAP_CR_SEC_BLOCK; - goto response; - } + if (!sk) + goto send_move_response; - result = L2CAP_CR_NO_MEM; + lock_sock(sk); + pi = l2cap_pi(sk); - /* Check for backlog size */ - if (sk_acceptq_is_full(parent)) { - BT_DBG("backlog full %d", parent->sk_ack_backlog); - goto response; + if (pi->scid < L2CAP_CID_DYN_START || + (pi->mode != L2CAP_MODE_ERTM && + pi->mode != L2CAP_MODE_STREAMING)) { + goto send_move_response; } - chan = pchan->ops->new_connection(pchan->data); - if (!chan) - goto response; - - sk = chan->sk; - - write_lock_bh(&conn->chan_lock); - - /* Check if we already have channel with that dcid */ - if (__l2cap_get_chan_by_dcid(conn, scid)) { - write_unlock_bh(&conn->chan_lock); - sock_set_flag(sk, SOCK_ZAPPED); - chan->ops->close(chan->data); - goto response; + if (pi->amp_id == req->dest_amp_id) { + result = L2CAP_MOVE_CHAN_REFUSED_SAME_ID; + goto send_move_response; } - hci_conn_hold(conn->hcon); - - bacpy(&bt_sk(sk)->src, conn->src); - bacpy(&bt_sk(sk)->dst, conn->dst); - chan->psm = psm; - chan->dcid = scid; - - bt_accept_enqueue(parent, sk); - - __l2cap_chan_add(conn, chan); + if (req->dest_amp_id) { + struct hci_dev *hdev; + hdev = hci_dev_get(A2MP_HCI_ID(req->dest_amp_id)); + if (!hdev || !test_bit(HCI_UP, &hdev->flags)) { + if (hdev) + hci_dev_put(hdev); - dcid = chan->scid; - - __set_chan_timer(chan, sk->sk_sndtimeo); - - chan->ident = cmd->ident; - - if (conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE) { - if (l2cap_check_security(chan)) { - if (bt_sk(sk)->defer_setup) { - l2cap_state_change(chan, BT_CONNECT2); - result = L2CAP_CR_PEND; - status = L2CAP_CS_AUTHOR_PEND; - parent->sk_data_ready(parent, 0); - } else { - l2cap_state_change(chan, BT_CONFIG); - result = L2CAP_CR_SUCCESS; - status = L2CAP_CS_NO_INFO; - } - } else { - l2cap_state_change(chan, BT_CONNECT2); - result = L2CAP_CR_PEND; - status = L2CAP_CS_AUTHEN_PEND; + result = L2CAP_MOVE_CHAN_REFUSED_CONTROLLER; + goto send_move_response; } - } else { - l2cap_state_change(chan, BT_CONNECT2); - result = L2CAP_CR_PEND; - status = L2CAP_CS_NO_INFO; } - write_unlock_bh(&conn->chan_lock); - -response: - bh_unlock_sock(parent); - -sendresp: - rsp.scid = cpu_to_le16(scid); - rsp.dcid = cpu_to_le16(dcid); - rsp.result = cpu_to_le16(result); - rsp.status = cpu_to_le16(status); - l2cap_send_cmd(conn, cmd->ident, L2CAP_CONN_RSP, sizeof(rsp), &rsp); - - if (result == L2CAP_CR_PEND && status == L2CAP_CS_NO_INFO) { - struct l2cap_info_req info; - info.type = cpu_to_le16(L2CAP_IT_FEAT_MASK); - - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_SENT; - conn->info_ident = l2cap_get_ident(conn); - - mod_timer(&conn->info_timer, jiffies + - msecs_to_jiffies(L2CAP_INFO_TIMEOUT)); - - l2cap_send_cmd(conn, conn->info_ident, - L2CAP_INFO_REQ, sizeof(info), &info); + if (((pi->amp_move_state != L2CAP_AMP_STATE_STABLE && + pi->amp_move_state != L2CAP_AMP_STATE_WAIT_PREPARE) || + pi->amp_move_role != L2CAP_AMP_MOVE_NONE) && + bacmp(conn->src, conn->dst) > 0) { + result = L2CAP_MOVE_CHAN_REFUSED_COLLISION; + goto send_move_response; } - if (chan && !test_bit(CONF_REQ_SENT, &chan->conf_state) && - result == L2CAP_CR_SUCCESS) { - u8 buf[128]; - set_bit(CONF_REQ_SENT, &chan->conf_state); - l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, - l2cap_build_conf_req(chan, buf), buf); - chan->num_conf_req++; + if (pi->amp_pref == BT_AMP_POLICY_REQUIRE_BR_EDR) { + result = L2CAP_MOVE_CHAN_REFUSED_NOT_ALLOWED; + goto send_move_response; } - return 0; -} + pi->amp_move_cmd_ident = cmd->ident; + pi->amp_move_role = L2CAP_AMP_MOVE_RESPONDER; + l2cap_amp_move_setup(sk); + pi->amp_move_id = req->dest_amp_id; + icid = pi->dcid; + + if (req->dest_amp_id == 0) { + /* Moving to BR/EDR */ + if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) { + pi->amp_move_state = L2CAP_AMP_STATE_WAIT_LOCAL_BUSY; + result = L2CAP_MOVE_CHAN_PENDING; + } else { + pi->amp_move_state = L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM; + result = L2CAP_MOVE_CHAN_SUCCESS; + } + } else { + pi->amp_move_state = L2CAP_AMP_STATE_WAIT_PREPARE; + amp_accept_physical(pi->conn, req->dest_amp_id, sk); + result = L2CAP_MOVE_CHAN_PENDING; + } -static inline int l2cap_connect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) -{ - struct l2cap_conn_rsp *rsp = (struct l2cap_conn_rsp *) data; - u16 scid, dcid, result, status; - struct l2cap_chan *chan; - struct sock *sk; - u8 req[128]; +send_move_response: + l2cap_send_move_chan_rsp(conn, cmd->ident, icid, result); - scid = __le16_to_cpu(rsp->scid); - dcid = __le16_to_cpu(rsp->dcid); - result = __le16_to_cpu(rsp->result); - status = __le16_to_cpu(rsp->status); + if (sk) + release_sock(sk); - BT_DBG("dcid 0x%4.4x scid 0x%4.4x result 0x%2.2x status 0x%2.2x", dcid, scid, result, status); + return 0; +} - if (scid) { - chan = l2cap_get_chan_by_scid(conn, scid); - if (!chan) - return -EFAULT; - } else { - chan = l2cap_get_chan_by_ident(conn, cmd->ident); - if (!chan) - return -EFAULT; - } +static inline int l2cap_move_channel_rsp(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_move_chan_rsp *rsp = (struct l2cap_move_chan_rsp *) data; + struct sock *sk; + struct l2cap_pinfo *pi; + u16 icid, result; - sk = chan->sk; + icid = le16_to_cpu(rsp->icid); + result = le16_to_cpu(rsp->result); - switch (result) { - case L2CAP_CR_SUCCESS: - l2cap_state_change(chan, BT_CONFIG); - chan->ident = 0; - chan->dcid = dcid; - clear_bit(CONF_CONNECT_PEND, &chan->conf_state); + BT_DBG("icid %d, result %d", (int) icid, (int) result); - if (test_and_set_bit(CONF_REQ_SENT, &chan->conf_state)) + switch (result) { + case L2CAP_MOVE_CHAN_SUCCESS: + case L2CAP_MOVE_CHAN_PENDING: + read_lock(&conn->chan_list.lock); + sk = __l2cap_get_chan_by_scid(&conn->chan_list, icid); + read_unlock(&conn->chan_list.lock); + + if (!sk) { + l2cap_send_move_chan_cfm(conn, NULL, icid, + L2CAP_MOVE_CHAN_UNCONFIRMED); break; + } - l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, - l2cap_build_conf_req(chan, req), req); - chan->num_conf_req++; - break; - - case L2CAP_CR_PEND: - set_bit(CONF_CONNECT_PEND, &chan->conf_state); + lock_sock(sk); + pi = l2cap_pi(sk); + + l2cap_sock_clear_timer(sk); + if (result == L2CAP_MOVE_CHAN_PENDING) + l2cap_sock_set_timer(sk, L2CAP_MOVE_ERTX_TIMEOUT); + + if (pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_LOGICAL_COMPLETE) { + /* Move confirm will be sent when logical link + * is complete. + */ + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_LOGICAL_CONFIRM; + } else if (result == L2CAP_MOVE_CHAN_SUCCESS && + pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_MOVE_RSP_SUCCESS) { + /* Logical link is up or moving to BR/EDR, + * proceed with move */ + if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) { + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_LOCAL_BUSY; + } else { + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM_RSP; + l2cap_send_move_chan_cfm(conn, pi, pi->scid, + L2CAP_MOVE_CHAN_CONFIRMED); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); + } + } else if (pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_MOVE_RSP) { + struct l2cap_conf_ext_fs default_fs = {1, 1, 0xFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; + struct hci_chan *chan; + /* Moving to AMP */ + if (result == L2CAP_MOVE_CHAN_SUCCESS) { + /* Remote is ready, send confirm immediately + * after logical link is ready + */ + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_LOGICAL_CONFIRM; + } else { + /* Both logical link and move success + * are required to confirm + */ + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_LOGICAL_COMPLETE; + } + pi->remote_fs = default_fs; + pi->local_fs = default_fs; + chan = l2cap_chan_admit(pi->amp_move_id, pi); + if (!chan) { + /* Logical link not available */ + l2cap_send_move_chan_cfm(conn, pi, pi->scid, + L2CAP_MOVE_CHAN_UNCONFIRMED); + break; + } + if (chan->state == BT_CONNECTED) { + /* Logical link is already ready to go */ + pi->ampchan = chan; + pi->ampcon = chan->conn; + pi->ampcon->l2cap_data = pi->conn; + if (result == L2CAP_MOVE_CHAN_SUCCESS) { + /* Can confirm now */ + l2cap_send_move_chan_cfm(conn, pi, + pi->scid, + L2CAP_MOVE_CHAN_CONFIRMED); + } else { + /* Now only need move success + * required to confirm + */ + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_MOVE_RSP_SUCCESS; + } + } else + chan->l2cap_sk = sk; + } else { + /* Any other amp move state means the move failed. */ + l2cap_send_move_chan_cfm(conn, pi, pi->scid, + L2CAP_MOVE_CHAN_UNCONFIRMED); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); + } break; - default: - /* don't delete l2cap channel if sk is owned by user */ - if (sock_owned_by_user(sk)) { - l2cap_state_change(chan, BT_DISCONN); - __clear_chan_timer(chan); - __set_chan_timer(chan, HZ / 5); + /* Failed (including collision case) */ + read_lock(&conn->chan_list.lock); + sk = __l2cap_get_chan_by_ident(&conn->chan_list, cmd->ident); + read_unlock(&conn->chan_list.lock); + + if (!sk) { + /* Could not locate channel, icid is best guess */ + l2cap_send_move_chan_cfm(conn, NULL, icid, + L2CAP_MOVE_CHAN_UNCONFIRMED); break; } - l2cap_chan_del(chan, ECONNREFUSED); + lock_sock(sk); + pi = l2cap_pi(sk); + + l2cap_sock_clear_timer(sk); + + if (pi->amp_move_role == L2CAP_AMP_MOVE_INITIATOR) { + if (result == L2CAP_MOVE_CHAN_REFUSED_COLLISION) + pi->amp_move_role = L2CAP_AMP_MOVE_RESPONDER; + else { + /* Cleanup - cancel move */ + pi->amp_move_id = pi->amp_id; + pi->amp_move_state = L2CAP_AMP_STATE_STABLE; + l2cap_amp_move_revert(sk); + pi->amp_move_role = L2CAP_AMP_MOVE_NONE; + } + } else { + /* State is STABLE so the confirm response is + * ignored. + */ + pi->amp_move_state = L2CAP_AMP_STATE_STABLE; + } + + l2cap_send_move_chan_cfm(conn, pi, pi->scid, + L2CAP_MOVE_CHAN_UNCONFIRMED); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); break; } - bh_unlock_sock(sk); - return 0; -} + if (sk) + release_sock(sk); -static inline void set_default_fcs(struct l2cap_chan *chan) -{ - /* FCS is enabled only in ERTM or streaming mode, if one or both - * sides request it. - */ - if (chan->mode != L2CAP_MODE_ERTM && chan->mode != L2CAP_MODE_STREAMING) - chan->fcs = L2CAP_FCS_NONE; - else if (!test_bit(CONF_NO_FCS_RECV, &chan->conf_state)) - chan->fcs = L2CAP_FCS_CRC16; + return 0; } -static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) +static inline int l2cap_move_channel_confirm(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u8 *data) { - struct l2cap_conf_req *req = (struct l2cap_conf_req *) data; - u16 dcid, flags; - u8 rsp[64]; - struct l2cap_chan *chan; + struct l2cap_move_chan_cfm *cfm = (struct l2cap_move_chan_cfm *) data; struct sock *sk; - int len; - - dcid = __le16_to_cpu(req->dcid); - flags = __le16_to_cpu(req->flags); - - BT_DBG("dcid 0x%4.4x flags 0x%2.2x", dcid, flags); + u16 icid, result; - chan = l2cap_get_chan_by_scid(conn, dcid); - if (!chan) - return -ENOENT; + icid = le16_to_cpu(cfm->icid); + result = le16_to_cpu(cfm->result); - sk = chan->sk; + BT_DBG("icid %d, result %d", (int) icid, (int) result); - if (sk->sk_state != BT_CONFIG && sk->sk_state != BT_CONNECT2) { - struct l2cap_cmd_rej rej; + read_lock(&conn->chan_list.lock); + sk = __l2cap_get_chan_by_dcid(&conn->chan_list, icid); + read_unlock(&conn->chan_list.lock); - rej.reason = cpu_to_le16(0x0002); - l2cap_send_cmd(conn, cmd->ident, L2CAP_COMMAND_REJ, - sizeof(rej), &rej); - goto unlock; + if (!sk) { + BT_DBG("Bad channel (%d)", (int) icid); + goto send_move_confirm_response; } - /* Reject if config buffer is too small. */ - len = cmd_len - sizeof(*req); - if (len < 0 || chan->conf_len + len > sizeof(chan->conf_req)) { - l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, - l2cap_build_conf_rsp(chan, rsp, - L2CAP_CONF_REJECT, flags), rsp); - goto unlock; + lock_sock(sk); + + if (l2cap_pi(sk)->amp_move_state == L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM) { + l2cap_pi(sk)->amp_move_state = L2CAP_AMP_STATE_STABLE; + if (result == L2CAP_MOVE_CHAN_CONFIRMED) { + l2cap_pi(sk)->amp_id = l2cap_pi(sk)->amp_move_id; + if ((!l2cap_pi(sk)->amp_id) && + (l2cap_pi(sk)->ampchan)) { + /* Have moved off of AMP, free the channel */ + hci_chan_put(l2cap_pi(sk)->ampchan); + if (atomic_read(&l2cap_pi(sk)->ampchan->refcnt)) + l2cap_deaggregate(l2cap_pi(sk)->ampchan, + l2cap_pi(sk)); + l2cap_pi(sk)->ampchan = NULL; + l2cap_pi(sk)->ampcon = NULL; + } + l2cap_amp_move_success(sk); + } else { + l2cap_pi(sk)->amp_move_id = l2cap_pi(sk)->amp_id; + l2cap_amp_move_revert(sk); + } + l2cap_pi(sk)->amp_move_role = L2CAP_AMP_MOVE_NONE; + } else if (l2cap_pi(sk)->amp_move_state == + L2CAP_AMP_STATE_WAIT_LOGICAL_CONFIRM) { + BT_DBG("Bad AMP_MOVE_STATE (%d)", l2cap_pi(sk)->amp_move_state); } - /* Store config. */ - memcpy(chan->conf_req + chan->conf_len, req->data, len); - chan->conf_len += len; +send_move_confirm_response: + l2cap_send_move_chan_cfm_rsp(conn, cmd->ident, icid); - if (flags & 0x0001) { - /* Incomplete config. Send empty response. */ - l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, - l2cap_build_conf_rsp(chan, rsp, - L2CAP_CONF_SUCCESS, 0x0001), rsp); - goto unlock; - } + if (sk) + release_sock(sk); - /* Complete config. */ - len = l2cap_parse_conf_req(chan, rsp); - if (len < 0) { - l2cap_send_disconn_req(conn, chan, ECONNRESET); - goto unlock; - } + return 0; +} - l2cap_send_cmd(conn, cmd->ident, L2CAP_CONF_RSP, len, rsp); - chan->num_conf_rsp++; +static inline int l2cap_move_channel_confirm_rsp(struct l2cap_conn *conn, + struct l2cap_cmd_hdr *cmd, u8 *data) +{ + struct l2cap_move_chan_cfm_rsp *rsp = + (struct l2cap_move_chan_cfm_rsp *) data; + struct sock *sk; + u16 icid; - /* Reset config buffer. */ - chan->conf_len = 0; + icid = le16_to_cpu(rsp->icid); - if (!test_bit(CONF_OUTPUT_DONE, &chan->conf_state)) - goto unlock; + BT_DBG("icid %d", (int) icid); - if (test_bit(CONF_INPUT_DONE, &chan->conf_state)) { - set_default_fcs(chan); + read_lock(&conn->chan_list.lock); + sk = __l2cap_get_chan_by_scid(&conn->chan_list, icid); + read_unlock(&conn->chan_list.lock); - l2cap_state_change(chan, BT_CONNECTED); + if (!sk) + return 0; - chan->next_tx_seq = 0; - chan->expected_tx_seq = 0; - skb_queue_head_init(&chan->tx_q); - if (chan->mode == L2CAP_MODE_ERTM) - l2cap_ertm_init(chan); + lock_sock(sk); - l2cap_chan_ready(sk); - goto unlock; - } + l2cap_sock_clear_timer(sk); - if (!test_and_set_bit(CONF_REQ_SENT, &chan->conf_state)) { - u8 buf[64]; - l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, - l2cap_build_conf_req(chan, buf), buf); - chan->num_conf_req++; + if (l2cap_pi(sk)->amp_move_state == + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM_RSP) { + l2cap_pi(sk)->amp_move_state = L2CAP_AMP_STATE_STABLE; + l2cap_pi(sk)->amp_id = l2cap_pi(sk)->amp_move_id; + + if (!l2cap_pi(sk)->amp_id) { + /* Have moved off of AMP, free the channel */ + l2cap_pi(sk)->ampcon = NULL; + if (l2cap_pi(sk)->ampchan) { + hci_chan_put(l2cap_pi(sk)->ampchan); + if (atomic_read(&l2cap_pi(sk)->ampchan->refcnt)) + l2cap_deaggregate(l2cap_pi(sk)->ampchan, + l2cap_pi(sk)); + } + l2cap_pi(sk)->ampchan = NULL; + } + + l2cap_amp_move_success(sk); + + l2cap_pi(sk)->amp_move_role = L2CAP_AMP_MOVE_NONE; } -unlock: - bh_unlock_sock(sk); + release_sock(sk); + return 0; } -static inline int l2cap_config_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +static void l2cap_amp_signal_worker(struct work_struct *work) { - struct l2cap_conf_rsp *rsp = (struct l2cap_conf_rsp *)data; - u16 scid, flags, result; - struct l2cap_chan *chan; - struct sock *sk; - int len = cmd->len - sizeof(*rsp); + int err = 0; + struct l2cap_amp_signal_work *ampwork = + container_of(work, struct l2cap_amp_signal_work, work); - scid = __le16_to_cpu(rsp->scid); - flags = __le16_to_cpu(rsp->flags); - result = __le16_to_cpu(rsp->result); + switch (ampwork->cmd.code) { + case L2CAP_MOVE_CHAN_REQ: + err = l2cap_move_channel_req(ampwork->conn, &work->cmd, + ampwork->data); + break; - BT_DBG("scid 0x%4.4x flags 0x%2.2x result 0x%2.2x", - scid, flags, result); + case L2CAP_MOVE_CHAN_RSP: + err = l2cap_move_channel_rsp(ampwork->conn, &work->cmd, + ampwork->data); + break; - chan = l2cap_get_chan_by_scid(conn, scid); - if (!chan) - return 0; + case L2CAP_MOVE_CHAN_CFM: + err = l2cap_move_channel_confirm(ampwork->conn, &work->cmd, + ampwork->data); + break; - sk = chan->sk; + case L2CAP_MOVE_CHAN_CFM_RSP: + err = l2cap_move_channel_confirm_rsp(ampwork->conn, + &work->cmd, ampwork->data); + break; - switch (result) { - case L2CAP_CONF_SUCCESS: - l2cap_conf_rfc_get(chan, rsp->data, len); + default: + BT_ERR("Unknown signaling command 0x%2.2x", ampwork->cmd.code); + err = -EINVAL; break; + } - case L2CAP_CONF_UNACCEPT: - if (chan->num_conf_rsp <= L2CAP_CONF_MAX_CONF_RSP) { - char req[64]; + if (err) { + struct l2cap_cmd_rej rej; + BT_DBG("error %d", err); + + /* In this context, commands are only rejected with + * "command not understood", code 0. + */ + rej.reason = cpu_to_le16(0); + l2cap_send_cmd(ampwork->conn, ampwork->cmd.ident, + L2CAP_COMMAND_REJ, sizeof(rej), &rej); + } - if (len > sizeof(req) - sizeof(struct l2cap_conf_req)) { - l2cap_send_disconn_req(conn, chan, ECONNRESET); - goto done; - } + kfree_skb(ampwork->skb); + kfree(ampwork); +} - /* throw out any old stored conf requests */ - result = L2CAP_CONF_SUCCESS; - len = l2cap_parse_conf_rsp(chan, rsp->data, len, - req, &result); - if (len < 0) { - l2cap_send_disconn_req(conn, chan, ECONNRESET); - goto done; - } +void l2cap_amp_physical_complete(int result, u8 local_id, u8 remote_id, + struct sock *sk) +{ + struct l2cap_pinfo *pi; - l2cap_send_cmd(conn, l2cap_get_ident(conn), - L2CAP_CONF_REQ, len, req); - chan->num_conf_req++; - if (result != L2CAP_CONF_SUCCESS) - goto done; - break; - } + BT_DBG("result %d, local_id %d, remote_id %d, sk %p", result, + (int) local_id, (int) remote_id, sk); - default: - sk->sk_err = ECONNRESET; - __set_chan_timer(chan, HZ * 5); - l2cap_send_disconn_req(conn, chan, ECONNRESET); - goto done; + lock_sock(sk); + + if (sk->sk_state == BT_DISCONN || sk->sk_state == BT_CLOSED) { + release_sock(sk); + return; } - if (flags & 0x01) - goto done; + pi = l2cap_pi(sk); + + if (sk->sk_state != BT_CONNECTED) { + if (bt_sk(sk)->parent) { + struct l2cap_conn_rsp rsp; + char buf[128]; + rsp.scid = cpu_to_le16(l2cap_pi(sk)->dcid); + rsp.dcid = cpu_to_le16(l2cap_pi(sk)->scid); + + /* Incoming channel on AMP */ + if (result == L2CAP_CREATE_CHAN_SUCCESS) { + /* Send successful response */ + rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS); + rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); + } else { + /* Send negative response */ + rsp.result = cpu_to_le16(L2CAP_CR_NO_MEM); + rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); + } - set_bit(CONF_INPUT_DONE, &chan->conf_state); + l2cap_send_cmd(pi->conn, pi->ident, + L2CAP_CREATE_CHAN_RSP, + sizeof(rsp), &rsp); + + if (result == L2CAP_CREATE_CHAN_SUCCESS) { + sk->sk_state = BT_CONFIG; + pi->conf_state |= L2CAP_CONF_REQ_SENT; + l2cap_send_cmd(pi->conn, + l2cap_get_ident(pi->conn), + L2CAP_CONF_REQ, + l2cap_build_conf_req(sk, buf), buf); + l2cap_pi(sk)->num_conf_req++; + } + } else { + /* Outgoing channel on AMP */ + if (result != L2CAP_CREATE_CHAN_SUCCESS) { + /* Revert to BR/EDR connect */ + l2cap_send_conn_req(sk); + } else { + pi->amp_id = local_id; + l2cap_send_create_chan_req(sk, remote_id); + } + } + } else if (result == L2CAP_MOVE_CHAN_SUCCESS && + pi->amp_move_role == L2CAP_AMP_MOVE_INITIATOR) { + l2cap_amp_move_setup(sk); + pi->amp_move_id = local_id; + pi->amp_move_state = L2CAP_AMP_STATE_WAIT_MOVE_RSP; + + l2cap_send_move_chan_req(pi->conn, pi, pi->scid, remote_id); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); + } else if (result == L2CAP_MOVE_CHAN_SUCCESS && + pi->amp_move_role == L2CAP_AMP_MOVE_RESPONDER) { + struct hci_chan *chan; + struct l2cap_conf_ext_fs default_fs = {1, 1, 0xFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; + pi->remote_fs = default_fs; + pi->local_fs = default_fs; + chan = l2cap_chan_admit(local_id, pi); + if (chan) { + if (chan->state == BT_CONNECTED) { + /* Logical link is ready to go */ + pi->ampchan = chan; + pi->ampcon = chan->conn; + pi->ampcon->l2cap_data = pi->conn; + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM; + l2cap_send_move_chan_rsp(pi->conn, + pi->amp_move_cmd_ident, pi->dcid, + L2CAP_MOVE_CHAN_SUCCESS); + } else { + /* Wait for logical link to be ready */ + chan->l2cap_sk = sk; + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_LOGICAL_CONFIRM; + } + } else { + /* Logical link not available */ + l2cap_send_move_chan_rsp(pi->conn, + pi->amp_move_cmd_ident, pi->dcid, + L2CAP_MOVE_CHAN_REFUSED_NOT_ALLOWED); + } + } else { + BT_DBG("result %d, role %d, local_busy %d", result, + (int) pi->amp_move_role, + (int) ((pi->conn_state & L2CAP_CONN_LOCAL_BUSY) != 0)); + + if (pi->amp_move_role == L2CAP_AMP_MOVE_RESPONDER) { + if (result == -EINVAL) + l2cap_send_move_chan_rsp(pi->conn, + pi->amp_move_cmd_ident, pi->dcid, + L2CAP_MOVE_CHAN_REFUSED_CONTROLLER); + else + l2cap_send_move_chan_rsp(pi->conn, + pi->amp_move_cmd_ident, pi->dcid, + L2CAP_MOVE_CHAN_REFUSED_NOT_ALLOWED); + } - if (test_bit(CONF_OUTPUT_DONE, &chan->conf_state)) { - set_default_fcs(chan); + pi->amp_move_role = L2CAP_AMP_MOVE_NONE; + pi->amp_move_state = L2CAP_AMP_STATE_STABLE; - l2cap_state_change(chan, BT_CONNECTED); - chan->next_tx_seq = 0; - chan->expected_tx_seq = 0; - skb_queue_head_init(&chan->tx_q); - if (chan->mode == L2CAP_MODE_ERTM) - l2cap_ertm_init(chan); + if ((l2cap_pi(sk)->conn_state & L2CAP_CONN_LOCAL_BUSY) && + l2cap_rmem_available(sk)) + l2cap_ertm_tx(sk, 0, 0, + L2CAP_ERTM_EVENT_LOCAL_BUSY_CLEAR); - l2cap_chan_ready(sk); + /* Restart data transmission */ + l2cap_ertm_send(sk); } -done: - bh_unlock_sock(sk); - return 0; + release_sock(sk); } -static inline int l2cap_disconnect_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +int l2cap_logical_link_complete(struct hci_chan *chan, u8 status) { - struct l2cap_disconn_req *req = (struct l2cap_disconn_req *) data; - struct l2cap_disconn_rsp rsp; - u16 dcid, scid; - struct l2cap_chan *chan; + struct l2cap_pinfo *pi; struct sock *sk; - scid = __le16_to_cpu(req->scid); - dcid = __le16_to_cpu(req->dcid); - - BT_DBG("scid 0x%4.4x dcid 0x%4.4x", scid, dcid); - - chan = l2cap_get_chan_by_scid(conn, dcid); - if (!chan) - return 0; + BT_DBG("status %d, chan %p, conn %p", (int) status, chan, chan->conn); - sk = chan->sk; + sk = chan->l2cap_sk; - rsp.dcid = cpu_to_le16(chan->scid); - rsp.scid = cpu_to_le16(chan->dcid); - l2cap_send_cmd(conn, cmd->ident, L2CAP_DISCONN_RSP, sizeof(rsp), &rsp); + BT_DBG("sk %p", sk); - sk->sk_shutdown = SHUTDOWN_MASK; + lock_sock(sk); - /* don't delete l2cap channel if sk is owned by user */ - if (sock_owned_by_user(sk)) { - l2cap_state_change(chan, BT_DISCONN); - __clear_chan_timer(chan); - __set_chan_timer(chan, HZ / 5); - bh_unlock_sock(sk); + if (sk->sk_state != BT_CONNECTED && !l2cap_pi(sk)->amp_id) { + release_sock(sk); return 0; } - l2cap_chan_del(chan, ECONNRESET); - bh_unlock_sock(sk); + pi = l2cap_pi(sk); - chan->ops->close(chan->data); - return 0; -} + if ((!status) && (chan != NULL)) { + pi->ampchan = chan; + pi->ampcon = chan->conn; + pi->ampcon->l2cap_data = pi->conn; -static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) -{ - struct l2cap_disconn_rsp *rsp = (struct l2cap_disconn_rsp *) data; - u16 dcid, scid; - struct l2cap_chan *chan; - struct sock *sk; + if (sk->sk_state != BT_CONNECTED) { + struct l2cap_conf_rsp rsp; - scid = __le16_to_cpu(rsp->scid); - dcid = __le16_to_cpu(rsp->dcid); + /* Must use spinlock to prevent concurrent + * execution of l2cap_config_rsp() + */ + bh_lock_sock(sk); + l2cap_send_cmd(pi->conn, pi->conf_ident, L2CAP_CONF_RSP, + l2cap_build_conf_rsp(sk, &rsp, + L2CAP_CONF_SUCCESS, 0), &rsp); + pi->conf_state |= L2CAP_CONF_OUTPUT_DONE; - BT_DBG("dcid 0x%4.4x scid 0x%4.4x", dcid, scid); + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_INPUT_DONE) { + set_default_fcs(l2cap_pi(sk)); - chan = l2cap_get_chan_by_scid(conn, scid); - if (!chan) - return 0; + sk->sk_state = BT_CONNECTED; - sk = chan->sk; + if (l2cap_pi(sk)->mode == L2CAP_MODE_ERTM || + l2cap_pi(sk)->mode == L2CAP_MODE_STREAMING) + l2cap_ertm_init(sk); - /* don't delete l2cap channel if sk is owned by user */ - if (sock_owned_by_user(sk)) { - l2cap_state_change(chan,BT_DISCONN); - __clear_chan_timer(chan); - __set_chan_timer(chan, HZ / 5); - bh_unlock_sock(sk); - return 0; - } + l2cap_chan_ready(sk); + } + bh_unlock_sock(sk); + } else if (pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_LOGICAL_COMPLETE) { + /* Move confirm will be sent after a success + * response is received + */ + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_MOVE_RSP_SUCCESS; + } else if (pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_LOGICAL_CONFIRM) { + if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_LOCAL_BUSY; + else if (pi->amp_move_role == + L2CAP_AMP_MOVE_INITIATOR) { + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM_RSP; + l2cap_send_move_chan_cfm(pi->conn, pi, pi->scid, + L2CAP_MOVE_CHAN_SUCCESS); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); + } else if (pi->amp_move_role == + L2CAP_AMP_MOVE_RESPONDER) { + pi->amp_move_state = + L2CAP_AMP_STATE_WAIT_MOVE_CONFIRM; + l2cap_send_move_chan_rsp(pi->conn, + pi->amp_move_cmd_ident, pi->dcid, + L2CAP_MOVE_CHAN_SUCCESS); + } + } else { + /* Move was not in expected state, free the + * logical link + */ + hci_chan_put(pi->ampchan); + pi->ampcon = NULL; + pi->ampchan = NULL; + } + } else { + /* Logical link setup failed. */ + + if (sk->sk_state != BT_CONNECTED) + l2cap_send_disconn_req(pi->conn, sk, ECONNRESET); + else if (pi->amp_move_role == L2CAP_AMP_MOVE_RESPONDER) { + l2cap_amp_move_revert(sk); + l2cap_pi(sk)->amp_move_role = L2CAP_AMP_MOVE_NONE; + pi->amp_move_state = L2CAP_AMP_STATE_STABLE; + l2cap_send_move_chan_rsp(pi->conn, + pi->amp_move_cmd_ident, pi->dcid, + L2CAP_MOVE_CHAN_REFUSED_CONFIG); + } else if (pi->amp_move_role == L2CAP_AMP_MOVE_INITIATOR) { + if ((pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_LOGICAL_COMPLETE) || + (pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_LOGICAL_CONFIRM)) { + /* Remote has only sent pending or + * success responses, clean up + */ + l2cap_amp_move_revert(sk); + l2cap_pi(sk)->amp_move_role = + L2CAP_AMP_MOVE_NONE; + pi->amp_move_state = L2CAP_AMP_STATE_STABLE; + } - l2cap_chan_del(chan, 0); - bh_unlock_sock(sk); + /* Other amp move states imply that the move + * has already aborted + */ + l2cap_send_move_chan_cfm(pi->conn, pi, pi->scid, + L2CAP_MOVE_CHAN_UNCONFIRMED); + l2cap_sock_set_timer(sk, L2CAP_MOVE_TIMEOUT); + } + + pi->ampcon = NULL; + pi->ampchan = NULL; + } - chan->ops->close(chan->data); + release_sock(sk); return 0; } -static inline int l2cap_information_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +static void l2cap_logical_link_worker(struct work_struct *work) { - struct l2cap_info_req *req = (struct l2cap_info_req *) data; - u16 type; - - type = __le16_to_cpu(req->type); + struct l2cap_logical_link_work *log_link_work = + container_of(work, struct l2cap_logical_link_work, work); - BT_DBG("type 0x%4.4x", type); + l2cap_logical_link_complete(log_link_work->chan, log_link_work->status); + kfree(log_link_work); +} - if (type == L2CAP_IT_FEAT_MASK) { - u8 buf[8]; - u32 feat_mask = l2cap_feat_mask; - struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) buf; - rsp->type = cpu_to_le16(L2CAP_IT_FEAT_MASK); - rsp->result = cpu_to_le16(L2CAP_IR_SUCCESS); - if (!disable_ertm) - feat_mask |= L2CAP_FEAT_ERTM | L2CAP_FEAT_STREAMING - | L2CAP_FEAT_FCS; - put_unaligned_le32(feat_mask, rsp->data); - l2cap_send_cmd(conn, cmd->ident, - L2CAP_INFO_RSP, sizeof(buf), buf); - } else if (type == L2CAP_IT_FIXED_CHAN) { - u8 buf[12]; - struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) buf; - rsp->type = cpu_to_le16(L2CAP_IT_FIXED_CHAN); - rsp->result = cpu_to_le16(L2CAP_IR_SUCCESS); - memcpy(buf + 4, l2cap_fixed_chan, 8); - l2cap_send_cmd(conn, cmd->ident, - L2CAP_INFO_RSP, sizeof(buf), buf); - } else { - struct l2cap_info_rsp rsp; - rsp.type = cpu_to_le16(type); - rsp.result = cpu_to_le16(L2CAP_IR_NOTSUPP); - l2cap_send_cmd(conn, cmd->ident, - L2CAP_INFO_RSP, sizeof(rsp), &rsp); +static int l2cap_create_cfm(struct hci_chan *chan, u8 status) +{ + struct l2cap_logical_link_work *amp_work; + + amp_work = kzalloc(sizeof(*amp_work), GFP_ATOMIC); + if (!amp_work) + return -ENOMEM; + + INIT_WORK(&_work->work, l2cap_logical_link_worker); + amp_work->chan = chan; + amp_work->status = status; + if (!queue_work(_l2cap_wq, &_work->work)) { + kfree(amp_work); + return -ENOMEM; } return 0; } -static inline int l2cap_information_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u8 *data) +int l2cap_modify_cfm(struct hci_chan *chan, u8 status) +{ + struct l2cap_conn *conn = chan->conn->l2cap_data; + + BT_DBG("chan %p conn %p status %d", chan, conn, status); + + /* TODO: if failed status restore previous fs */ + return 0; +} + +int l2cap_destroy_cfm(struct hci_chan *chan, u8 reason) { - struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) data; - u16 type, result; - - type = __le16_to_cpu(rsp->type); - result = __le16_to_cpu(rsp->result); + struct l2cap_chan_list *l; + struct l2cap_conn *conn = chan->conn->l2cap_data; + struct sock *sk; - BT_DBG("type 0x%4.4x result 0x%2.2x", type, result); + BT_DBG("chan %p conn %p", chan, conn); - /* L2CAP Info req/rsp are unbound to channels, add extra checks */ - if (cmd->ident != conn->info_ident || - conn->info_state & L2CAP_INFO_FEAT_MASK_REQ_DONE) + if (!conn) return 0; - del_timer(&conn->info_timer); - - if (result != L2CAP_IR_SUCCESS) { - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; - conn->info_ident = 0; + l = &conn->chan_list; - l2cap_conn_start(conn); + read_lock(&l->lock); - return 0; + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + bh_lock_sock(sk); + /* TODO MM/PK - What to do if connection is LOCAL_BUSY? */ + if (l2cap_pi(sk)->ampchan == chan) { + l2cap_pi(sk)->ampchan = NULL; + l2cap_amp_move_init(sk); + } + bh_unlock_sock(sk); } - if (type == L2CAP_IT_FEAT_MASK) { - conn->feat_mask = get_unaligned_le32(rsp->data); + read_unlock(&l->lock); - if (conn->feat_mask & L2CAP_FEAT_FIXED_CHAN) { - struct l2cap_info_req req; - req.type = cpu_to_le16(L2CAP_IT_FIXED_CHAN); + return 0; - conn->info_ident = l2cap_get_ident(conn); - l2cap_send_cmd(conn, conn->info_ident, - L2CAP_INFO_REQ, sizeof(req), &req); - } else { - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; - conn->info_ident = 0; +} - l2cap_conn_start(conn); - } - } else if (type == L2CAP_IT_FIXED_CHAN) { - conn->info_state |= L2CAP_INFO_FEAT_MASK_REQ_DONE; - conn->info_ident = 0; +static int l2cap_sig_amp(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, + u8 *data, struct sk_buff *skb) +{ + struct l2cap_amp_signal_work *amp_work; + + amp_work = kzalloc(sizeof(*amp_work), GFP_ATOMIC); + if (!amp_work) + return -ENOMEM; + + INIT_WORK(&_work->work, l2cap_amp_signal_worker); + amp_work->conn = conn; + amp_work->cmd = *cmd; + amp_work->data = data; + amp_work->skb = skb_clone(skb, GFP_ATOMIC); + if (!amp_work->skb) { + kfree(amp_work); + return -ENOMEM; + } - l2cap_conn_start(conn); + if (!queue_work(_l2cap_wq, &_work->work)) { + kfree_skb(amp_work->skb); + kfree(amp_work); + return -ENOMEM; } return 0; @@ -2911,7 +5534,8 @@ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn, } static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn, - struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data) + struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data, + struct sk_buff *skb) { int err = 0; @@ -2959,6 +5583,20 @@ static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn, err = l2cap_information_rsp(conn, cmd, data); break; + case L2CAP_CREATE_CHAN_REQ: + err = l2cap_create_channel_req(conn, cmd, data); + break; + + case L2CAP_CREATE_CHAN_RSP: + err = l2cap_create_channel_rsp(conn, cmd, data); + break; + + case L2CAP_MOVE_CHAN_REQ: + case L2CAP_MOVE_CHAN_RSP: + case L2CAP_MOVE_CHAN_CFM: + case L2CAP_MOVE_CHAN_CFM_RSP: + err = l2cap_sig_amp(conn, cmd, data, skb); + break; default: BT_ERR("Unknown BR/EDR signaling command 0x%2.2x", cmd->code); err = -EINVAL; @@ -3015,7 +5653,8 @@ static inline void l2cap_sig_channel(struct l2cap_conn *conn, if (conn->hcon->type == LE_LINK) err = l2cap_le_sig_cmd(conn, &cmd, data); else - err = l2cap_bredr_sig_cmd(conn, &cmd, cmd_len, data); + err = l2cap_bredr_sig_cmd(conn, &cmd, cmd_len, + data, skb); if (err) { struct l2cap_cmd_rej rej; @@ -3034,833 +5673,1331 @@ static inline void l2cap_sig_channel(struct l2cap_conn *conn, kfree_skb(skb); } -static int l2cap_check_fcs(struct l2cap_chan *chan, struct sk_buff *skb) +static int l2cap_check_fcs(struct l2cap_pinfo *pi, struct sk_buff *skb) { u16 our_fcs, rcv_fcs; - int hdr_size = L2CAP_HDR_SIZE + 2; + int hdr_size; + + if (pi->extended_control) + hdr_size = L2CAP_EXTENDED_HDR_SIZE; + else + hdr_size = L2CAP_ENHANCED_HDR_SIZE; - if (chan->fcs == L2CAP_FCS_CRC16) { - skb_trim(skb, skb->len - 2); + if (pi->fcs == L2CAP_FCS_CRC16) { + skb_trim(skb, skb->len - L2CAP_FCS_SIZE); rcv_fcs = get_unaligned_le16(skb->data + skb->len); our_fcs = crc16(0, skb->data - hdr_size, skb->len + hdr_size); - if (our_fcs != rcv_fcs) + if (our_fcs != rcv_fcs) { + BT_DBG("Bad FCS"); return -EBADMSG; + } } return 0; } -static inline void l2cap_send_i_or_rr_or_rnr(struct l2cap_chan *chan) +static void l2cap_ertm_pass_to_tx(struct sock *sk, + struct bt_l2cap_control *control) { - u16 control = 0; + BT_DBG("sk %p, control %p", sk, control); + l2cap_ertm_tx(sk, control, 0, L2CAP_ERTM_EVENT_RECV_REQSEQ_AND_FBIT); +} - chan->frames_sent = 0; +static void l2cap_ertm_pass_to_tx_fbit(struct sock *sk, + struct bt_l2cap_control *control) +{ + BT_DBG("sk %p, control %p", sk, control); + l2cap_ertm_tx(sk, control, 0, L2CAP_ERTM_EVENT_RECV_FBIT); +} - control |= chan->buffer_seq << L2CAP_CTRL_REQSEQ_SHIFT; +static void l2cap_ertm_resend(struct sock *sk) +{ + struct bt_l2cap_control control; + struct l2cap_pinfo *pi; + struct sk_buff *skb; + struct sk_buff *tx_skb; + u16 seq; - if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { - control |= L2CAP_SUPER_RCV_NOT_READY; - l2cap_send_sframe(chan, control); - set_bit(CONN_RNR_SENT, &chan->conn_state); - } + BT_DBG("sk %p", sk); - if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state)) - l2cap_retransmit_frames(chan); + pi = l2cap_pi(sk); - l2cap_ertm_send(chan); + if (pi->conn_state & L2CAP_CONN_REMOTE_BUSY) + return; - if (!test_bit(CONN_LOCAL_BUSY, &chan->conn_state) && - chan->frames_sent == 0) { - control |= L2CAP_SUPER_RCV_READY; - l2cap_send_sframe(chan, control); - } -} + if (pi->amp_move_state != L2CAP_AMP_STATE_STABLE && + pi->amp_move_state != L2CAP_AMP_STATE_WAIT_PREPARE) + return; -static int l2cap_add_to_srej_queue(struct l2cap_chan *chan, struct sk_buff *skb, u8 tx_seq, u8 sar) -{ - struct sk_buff *next_skb; - int tx_seq_offset, next_tx_seq_offset; + while (pi->retrans_list.head != L2CAP_SEQ_LIST_CLEAR) { + seq = l2cap_seq_list_pop(&pi->retrans_list); - bt_cb(skb)->tx_seq = tx_seq; - bt_cb(skb)->sar = sar; + skb = l2cap_ertm_seq_in_queue(TX_QUEUE(sk), seq); + if (!skb) { + BT_DBG("Error: Can't retransmit seq %d, frame missing", + (int) seq); + continue; + } - next_skb = skb_peek(&chan->srej_q); - if (!next_skb) { - __skb_queue_tail(&chan->srej_q, skb); - return 0; - } + bt_cb(skb)->retries += 1; + control = bt_cb(skb)->control; - tx_seq_offset = (tx_seq - chan->buffer_seq) % 64; - if (tx_seq_offset < 0) - tx_seq_offset += 64; + if ((pi->max_tx != 0) && (bt_cb(skb)->retries > pi->max_tx)) { + BT_DBG("Retry limit exceeded (%d)", (int) pi->max_tx); + l2cap_send_disconn_req(pi->conn, sk, ECONNRESET); + l2cap_seq_list_clear(&pi->retrans_list); + break; + } - do { - if (bt_cb(next_skb)->tx_seq == tx_seq) - return -EINVAL; + control.reqseq = pi->buffer_seq; + if (pi->conn_state & L2CAP_CONN_SEND_FBIT) { + control.final = 1; + pi->conn_state &= ~L2CAP_CONN_SEND_FBIT; + } else { + control.final = 0; + } - next_tx_seq_offset = (bt_cb(next_skb)->tx_seq - - chan->buffer_seq) % 64; - if (next_tx_seq_offset < 0) - next_tx_seq_offset += 64; + if (skb_cloned(skb)) { + /* Cloned sk_buffs are read-only, so we need a + * writeable copy + */ + tx_skb = skb_copy(skb, GFP_ATOMIC); + } else { + tx_skb = skb_clone(skb, GFP_ATOMIC); + } - if (next_tx_seq_offset > tx_seq_offset) { - __skb_queue_before(&chan->srej_q, next_skb, skb); - return 0; + /* Update skb contents */ + if (pi->extended_control) { + put_unaligned_le32(__pack_extended_control(&control), + tx_skb->data + L2CAP_HDR_SIZE); + } else { + put_unaligned_le16(__pack_enhanced_control(&control), + tx_skb->data + L2CAP_HDR_SIZE); } - if (skb_queue_is_last(&chan->srej_q, next_skb)) - break; + if (pi->fcs == L2CAP_FCS_CRC16) + apply_fcs(tx_skb); - } while ((next_skb = skb_queue_next(&chan->srej_q, next_skb))); + tx_skb->sk = sk; + tx_skb->destructor = l2cap_skb_destructor; + atomic_inc(&pi->ertm_queued); - __skb_queue_tail(&chan->srej_q, skb); + l2cap_do_send(sk, tx_skb); - return 0; + BT_DBG("Resent txseq %d", (int)control.txseq); + + pi->last_acked_seq = pi->buffer_seq; + } } -static int l2cap_ertm_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control) +static inline void l2cap_ertm_retransmit(struct sock *sk, + struct bt_l2cap_control *control) { - struct sk_buff *_skb; - int err; + BT_DBG("sk %p, control %p", sk, control); - switch (control & L2CAP_CTRL_SAR) { - case L2CAP_SDU_UNSEGMENTED: - if (test_bit(CONN_SAR_SDU, &chan->conn_state)) - goto drop; + l2cap_seq_list_append(&l2cap_pi(sk)->retrans_list, control->reqseq); + l2cap_ertm_resend(sk); +} - return chan->ops->recv(chan->data, skb); +static void l2cap_ertm_retransmit_all(struct sock *sk, + struct bt_l2cap_control *control) +{ + struct l2cap_pinfo *pi; + struct sk_buff *skb; - case L2CAP_SDU_START: - if (test_bit(CONN_SAR_SDU, &chan->conn_state)) - goto drop; + BT_DBG("sk %p, control %p", sk, control); - chan->sdu_len = get_unaligned_le16(skb->data); + pi = l2cap_pi(sk); - if (chan->sdu_len > chan->imtu) - goto disconnect; + if (control->poll) + pi->conn_state |= L2CAP_CONN_SEND_FBIT; - chan->sdu = bt_skb_alloc(chan->sdu_len, GFP_ATOMIC); - if (!chan->sdu) - return -ENOMEM; + l2cap_seq_list_clear(&pi->retrans_list); - /* pull sdu_len bytes only after alloc, because of Local Busy - * condition we have to be sure that this will be executed - * only once, i.e., when alloc does not fail */ - skb_pull(skb, 2); + if (pi->conn_state & L2CAP_CONN_REMOTE_BUSY) + return; + + if (pi->unacked_frames) { + skb_queue_walk(TX_QUEUE(sk), skb) { + if ((bt_cb(skb)->control.txseq == control->reqseq) || + skb == sk->sk_send_head) + break; + } - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); + skb_queue_walk_from(TX_QUEUE(sk), skb) { + if (skb == sk->sk_send_head) + break; - set_bit(CONN_SAR_SDU, &chan->conn_state); - chan->partial_sdu_len = skb->len; - break; + l2cap_seq_list_append(&pi->retrans_list, + bt_cb(skb)->control.txseq); + } - case L2CAP_SDU_CONTINUE: - if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) - goto disconnect; + l2cap_ertm_resend(sk); + } +} + +static inline void append_skb_frag(struct sk_buff *skb, + struct sk_buff *new_frag, struct sk_buff **last_frag) +{ + /* skb->len reflects data in skb as well as all fragments + skb->data_len reflects only data in fragments + */ + BT_DBG("skb %p, new_frag %p, *last_frag %p", skb, new_frag, *last_frag); - if (!chan->sdu) - goto disconnect; + if (!skb_has_frag_list(skb)) + skb_shinfo(skb)->frag_list = new_frag; - chan->partial_sdu_len += skb->len; - if (chan->partial_sdu_len > chan->sdu_len) - goto drop; + new_frag->next = NULL; - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); + (*last_frag)->next = new_frag; + *last_frag = new_frag; - break; + skb->len += new_frag->len; + skb->data_len += new_frag->len; + skb->truesize += new_frag->truesize; +} - case L2CAP_SDU_END: - if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) - goto disconnect; +static int l2cap_ertm_rx_expected_iframe(struct sock *sk, + struct bt_l2cap_control *control, struct sk_buff *skb) +{ + struct l2cap_pinfo *pi; + int err = -EINVAL; - if (!chan->sdu) - goto disconnect; + BT_DBG("sk %p, control %p, skb %p len %d truesize %d", sk, control, + skb, skb->len, skb->truesize); - chan->partial_sdu_len += skb->len; + if (!control) + return err; - if (chan->partial_sdu_len > chan->imtu) - goto drop; + pi = l2cap_pi(sk); - if (chan->partial_sdu_len != chan->sdu_len) - goto drop; + BT_DBG("type %c, sar %d, txseq %d, reqseq %d, final %d", + control->frame_type, control->sar, control->txseq, + control->reqseq, control->final); - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); + switch (control->sar) { + case L2CAP_SAR_UNSEGMENTED: + if (pi->sdu) { + BT_DBG("Unexpected unsegmented PDU during reassembly"); + kfree_skb(pi->sdu); + pi->sdu = NULL; + pi->sdu_last_frag = NULL; + pi->sdu_len = 0; + } - _skb = skb_clone(chan->sdu, GFP_ATOMIC); - if (!_skb) { - return -ENOMEM; + BT_DBG("Unsegmented"); + err = sock_queue_rcv_skb(sk, skb); + break; + + case L2CAP_SAR_START: + if (pi->sdu) { + BT_DBG("Unexpected start PDU during reassembly"); + kfree_skb(pi->sdu); } - err = chan->ops->recv(chan->data, _skb); - if (err < 0) { - kfree_skb(_skb); - return err; + pi->sdu_len = get_unaligned_le16(skb->data); + skb_pull(skb, 2); + + if (pi->sdu_len > pi->imtu) { + err = -EMSGSIZE; + break; } - clear_bit(CONN_SAR_SDU, &chan->conn_state); + if (skb->len >= pi->sdu_len) + break; + + pi->sdu = skb; + pi->sdu_last_frag = skb; + + BT_DBG("Start"); - kfree_skb(chan->sdu); + skb = NULL; + err = 0; break; - } - kfree_skb(skb); - return 0; + case L2CAP_SAR_CONTINUE: + if (!pi->sdu) + break; -drop: - kfree_skb(chan->sdu); - chan->sdu = NULL; + append_skb_frag(pi->sdu, skb, + &pi->sdu_last_frag); + skb = NULL; -disconnect: - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - kfree_skb(skb); - return 0; -} + if (pi->sdu->len >= pi->sdu_len) + break; -static void l2cap_ertm_enter_local_busy(struct l2cap_chan *chan) -{ - u16 control; + BT_DBG("Continue, reassembled %d", pi->sdu->len); + + err = 0; + break; + + case L2CAP_SAR_END: + if (!pi->sdu) + break; - BT_DBG("chan %p, Enter local busy", chan); + append_skb_frag(pi->sdu, skb, + &pi->sdu_last_frag); + skb = NULL; - set_bit(CONN_LOCAL_BUSY, &chan->conn_state); + if (pi->sdu->len != pi->sdu_len) + break; + + BT_DBG("End, reassembled %d", pi->sdu->len); + /* If the sender used tiny PDUs, the rcv queuing could fail. + * Applications that have issues here should use a larger + * sk_rcvbuf. + */ + err = sock_queue_rcv_skb(sk, pi->sdu); + + if (!err) { + /* Reassembly complete */ + pi->sdu = NULL; + pi->sdu_last_frag = NULL; + pi->sdu_len = 0; + } + break; + + default: + BT_DBG("Bad SAR value"); + break; + } - control = chan->buffer_seq << L2CAP_CTRL_REQSEQ_SHIFT; - control |= L2CAP_SUPER_RCV_NOT_READY; - l2cap_send_sframe(chan, control); + if (err) { + BT_DBG("Reassembly error %d, sk_rcvbuf %d, sk_rmem_alloc %d", + err, sk->sk_rcvbuf, atomic_read(&sk->sk_rmem_alloc)); + if (pi->sdu) { + kfree_skb(pi->sdu); + pi->sdu = NULL; + } + pi->sdu_last_frag = NULL; + pi->sdu_len = 0; + if (skb) + kfree_skb(skb); + } - set_bit(CONN_RNR_SENT, &chan->conn_state); + /* Update local busy state */ + if (!(pi->conn_state & L2CAP_CONN_LOCAL_BUSY) && l2cap_rmem_full(sk)) + l2cap_ertm_tx(sk, 0, 0, L2CAP_ERTM_EVENT_LOCAL_BUSY_DETECTED); - __clear_ack_timer(chan); + return err; } -static void l2cap_ertm_exit_local_busy(struct l2cap_chan *chan) +static int l2cap_ertm_rx_queued_iframes(struct sock *sk) { - u16 control; + int err = 0; + /* Pass sequential frames to l2cap_ertm_rx_expected_iframe() + * until a gap is encountered. + */ - if (!test_bit(CONN_RNR_SENT, &chan->conn_state)) - goto done; + struct l2cap_pinfo *pi; - control = chan->buffer_seq << L2CAP_CTRL_REQSEQ_SHIFT; - control |= L2CAP_SUPER_RCV_READY | L2CAP_CTRL_POLL; - l2cap_send_sframe(chan, control); - chan->retry_count = 1; + BT_DBG("sk %p", sk); + pi = l2cap_pi(sk); - __clear_retrans_timer(chan); - __set_monitor_timer(chan); + while (l2cap_rmem_available(sk)) { + struct sk_buff *skb; + BT_DBG("Searching for skb with txseq %d (queue len %d)", + (int) pi->buffer_seq, skb_queue_len(SREJ_QUEUE(sk))); - set_bit(CONN_WAIT_F, &chan->conn_state); + skb = l2cap_ertm_seq_in_queue(SREJ_QUEUE(sk), pi->buffer_seq); -done: - clear_bit(CONN_LOCAL_BUSY, &chan->conn_state); - clear_bit(CONN_RNR_SENT, &chan->conn_state); + if (!skb) + break; - BT_DBG("chan %p, Exit local busy", chan); -} + skb_unlink(skb, SREJ_QUEUE(sk)); + pi->buffer_seq = __next_seq(pi->buffer_seq, pi); + err = l2cap_ertm_rx_expected_iframe(sk, + &bt_cb(skb)->control, skb); + if (err) + break; + } -void l2cap_chan_busy(struct l2cap_chan *chan, int busy) -{ - if (chan->mode == L2CAP_MODE_ERTM) { - if (busy) - l2cap_ertm_enter_local_busy(chan); - else - l2cap_ertm_exit_local_busy(chan); + if (skb_queue_empty(SREJ_QUEUE(sk))) { + pi->rx_state = L2CAP_ERTM_RX_STATE_RECV; + l2cap_ertm_send_ack(sk); } + + return err; } -static int l2cap_streaming_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control) +static void l2cap_ertm_handle_srej(struct sock *sk, + struct bt_l2cap_control *control) { - struct sk_buff *_skb; - int err = -EINVAL; + struct l2cap_pinfo *pi; + struct sk_buff *skb; - /* - * TODO: We have to notify the userland if some data is lost with the - * Streaming Mode. - */ + BT_DBG("sk %p, control %p", sk, control); - switch (control & L2CAP_CTRL_SAR) { - case L2CAP_SDU_UNSEGMENTED: - if (test_bit(CONN_SAR_SDU, &chan->conn_state)) { - kfree_skb(chan->sdu); - break; - } + pi = l2cap_pi(sk); - err = chan->ops->recv(chan->data, skb); - if (!err) - return 0; + if (control->reqseq == pi->next_tx_seq) { + BT_DBG("Invalid reqseq %d, disconnecting", + (int) control->reqseq); + l2cap_send_disconn_req(pi->conn, sk, ECONNRESET); + return; + } - break; + skb = l2cap_ertm_seq_in_queue(TX_QUEUE(sk), control->reqseq); - case L2CAP_SDU_START: - if (test_bit(CONN_SAR_SDU, &chan->conn_state)) { - kfree_skb(chan->sdu); - break; - } + if (skb == NULL) { + BT_DBG("Seq %d not available for retransmission", + (int) control->reqseq); + return; + } - chan->sdu_len = get_unaligned_le16(skb->data); - skb_pull(skb, 2); + if ((pi->max_tx != 0) && (bt_cb(skb)->retries >= pi->max_tx)) { + BT_DBG("Retry limit exceeded (%d)", (int) pi->max_tx); + l2cap_send_disconn_req(pi->conn, sk, ECONNRESET); + return; + } - if (chan->sdu_len > chan->imtu) { - err = -EMSGSIZE; - break; + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; + + if (control->poll) { + l2cap_ertm_pass_to_tx(sk, control); + + pi->conn_state |= L2CAP_CONN_SEND_FBIT; + l2cap_ertm_retransmit(sk, control); + l2cap_ertm_send(sk); + + if (pi->tx_state == L2CAP_ERTM_TX_STATE_WAIT_F) { + pi->conn_state |= L2CAP_CONN_SREJ_ACT; + pi->srej_save_reqseq = control->reqseq; } + } else { + l2cap_ertm_pass_to_tx_fbit(sk, control); - chan->sdu = bt_skb_alloc(chan->sdu_len, GFP_ATOMIC); - if (!chan->sdu) { - err = -ENOMEM; - break; + if (control->final) { + if ((pi->conn_state & L2CAP_CONN_SREJ_ACT) && + (pi->srej_save_reqseq == control->reqseq)) { + pi->conn_state &= ~L2CAP_CONN_SREJ_ACT; + } else { + l2cap_ertm_retransmit(sk, control); + } + } else { + l2cap_ertm_retransmit(sk, control); + if (pi->tx_state == L2CAP_ERTM_TX_STATE_WAIT_F) { + pi->conn_state |= L2CAP_CONN_SREJ_ACT; + pi->srej_save_reqseq = control->reqseq; + } } + } +} + +static void l2cap_ertm_handle_rej(struct sock *sk, + struct bt_l2cap_control *control) +{ + struct l2cap_pinfo *pi; + struct sk_buff *skb; - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); + BT_DBG("sk %p, control %p", sk, control); - set_bit(CONN_SAR_SDU, &chan->conn_state); - chan->partial_sdu_len = skb->len; - err = 0; - break; + pi = l2cap_pi(sk); - case L2CAP_SDU_CONTINUE: - if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) - break; + if (control->reqseq == pi->next_tx_seq) { + BT_DBG("Invalid reqseq %d, disconnecting", + (int) control->reqseq); + l2cap_send_disconn_req(pi->conn, sk, ECONNRESET); + return; + } - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); + skb = l2cap_ertm_seq_in_queue(TX_QUEUE(sk), control->reqseq); - chan->partial_sdu_len += skb->len; - if (chan->partial_sdu_len > chan->sdu_len) - kfree_skb(chan->sdu); - else - err = 0; + if (pi->max_tx && skb && bt_cb(skb)->retries >= pi->max_tx) { + BT_DBG("Retry limit exceeded (%d)", (int) pi->max_tx); + l2cap_send_disconn_req(pi->conn, sk, ECONNRESET); + return; + } - break; + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; - case L2CAP_SDU_END: - if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) - break; + l2cap_ertm_pass_to_tx(sk, control); + + if (control->final) { + if (pi->conn_state & L2CAP_CONN_REJ_ACT) + pi->conn_state &= ~L2CAP_CONN_REJ_ACT; + else + l2cap_ertm_retransmit_all(sk, control); + } else { + l2cap_ertm_retransmit_all(sk, control); + l2cap_ertm_send(sk); + if (pi->tx_state == L2CAP_ERTM_TX_STATE_WAIT_F) + pi->conn_state |= L2CAP_CONN_REJ_ACT; + } +} - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); +static u8 l2cap_ertm_classify_txseq(struct sock *sk, u16 txseq) +{ + struct l2cap_pinfo *pi; + + BT_DBG("sk %p, txseq %d", sk, (int)txseq); + pi = l2cap_pi(sk); + + BT_DBG("last_acked_seq %d, expected_tx_seq %d", (int)pi->last_acked_seq, + (int)pi->expected_tx_seq); + + if (pi->rx_state == L2CAP_ERTM_RX_STATE_SREJ_SENT) { + if (__delta_seq(txseq, pi->last_acked_seq, pi) >= pi->tx_win) { + /* See notes below regarding "double poll" and + * invalid packets. + */ + if (pi->tx_win <= ((pi->tx_win_max + 1) >> 1)) { + BT_DBG("Invalid/Ignore - txseq outside " + "tx window after SREJ sent"); + return L2CAP_ERTM_TXSEQ_INVALID_IGNORE; + } else { + BT_DBG("Invalid - bad txseq within tx " + "window after SREJ sent"); + return L2CAP_ERTM_TXSEQ_INVALID; + } + } - clear_bit(CONN_SAR_SDU, &chan->conn_state); - chan->partial_sdu_len += skb->len; + if (pi->srej_list.head == txseq) { + BT_DBG("Expected SREJ"); + return L2CAP_ERTM_TXSEQ_EXPECTED_SREJ; + } - if (chan->partial_sdu_len > chan->imtu) - goto drop; + if (l2cap_ertm_seq_in_queue(SREJ_QUEUE(sk), txseq)) { + BT_DBG("Duplicate SREJ - txseq already stored"); + return L2CAP_ERTM_TXSEQ_DUPLICATE_SREJ; + } - if (chan->partial_sdu_len == chan->sdu_len) { - _skb = skb_clone(chan->sdu, GFP_ATOMIC); - err = chan->ops->recv(chan->data, _skb); - if (err < 0) - kfree_skb(_skb); + if (l2cap_seq_list_contains(&pi->srej_list, txseq)) { + BT_DBG("Unexpected SREJ - txseq not requested " + "with SREJ"); + return L2CAP_ERTM_TXSEQ_UNEXPECTED_SREJ; } - err = 0; + } -drop: - kfree_skb(chan->sdu); - break; + if (pi->expected_tx_seq == txseq) { + if (__delta_seq(txseq, pi->last_acked_seq, pi) >= pi->tx_win) { + BT_DBG("Invalid - txseq outside tx window"); + return L2CAP_ERTM_TXSEQ_INVALID; + } else { + BT_DBG("Expected"); + return L2CAP_ERTM_TXSEQ_EXPECTED; + } } - kfree_skb(skb); - return err; + if (__delta_seq(txseq, pi->last_acked_seq, pi) < + __delta_seq(pi->expected_tx_seq, pi->last_acked_seq, pi)) { + BT_DBG("Duplicate - expected_tx_seq later than txseq"); + return L2CAP_ERTM_TXSEQ_DUPLICATE; + } + + if (__delta_seq(txseq, pi->last_acked_seq, pi) >= pi->tx_win) { + /* A source of invalid packets is a "double poll" condition, + * where delays cause us to send multiple poll packets. If + * the remote stack receives and processes both polls, + * sequence numbers can wrap around in such a way that a + * resent frame has a sequence number that looks like new data + * with a sequence gap. This would trigger an erroneous SREJ + * request. + * + * Fortunately, this is impossible with a tx window that's + * less than half of the maximum sequence number, which allows + * invalid frames to be safely ignored. + * + * With tx window sizes greater than half of the tx window + * maximum, the frame is invalid and cannot be ignored. This + * causes a disconnect. + */ + + if (pi->tx_win <= ((pi->tx_win_max + 1) >> 1)) { + BT_DBG("Invalid/Ignore - txseq outside tx window"); + return L2CAP_ERTM_TXSEQ_INVALID_IGNORE; + } else { + BT_DBG("Invalid - txseq outside tx window"); + return L2CAP_ERTM_TXSEQ_INVALID; + } + } else { + BT_DBG("Unexpected - txseq indicates missing frames"); + return L2CAP_ERTM_TXSEQ_UNEXPECTED; + } } -static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq) +static int l2cap_ertm_rx_state_recv(struct sock *sk, + struct bt_l2cap_control *control, + struct sk_buff *skb, u8 event) { - struct sk_buff *skb; - u16 control; + struct l2cap_pinfo *pi; + int err = 0; + bool skb_in_use = 0; - while ((skb = skb_peek(&chan->srej_q)) && - !test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) { - int err; + BT_DBG("sk %p, control %p, skb %p, event %d", sk, control, skb, + (int)event); + pi = l2cap_pi(sk); - if (bt_cb(skb)->tx_seq != tx_seq) - break; + switch (event) { + case L2CAP_ERTM_EVENT_RECV_IFRAME: + switch (l2cap_ertm_classify_txseq(sk, control->txseq)) { + case L2CAP_ERTM_TXSEQ_EXPECTED: + l2cap_ertm_pass_to_tx(sk, control); - skb = skb_dequeue(&chan->srej_q); - control = bt_cb(skb)->sar << L2CAP_CTRL_SAR_SHIFT; - err = l2cap_ertm_reassembly_sdu(chan, skb, control); + if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) { + BT_DBG("Busy, discarding expected seq %d", + control->txseq); + break; + } + + pi->expected_tx_seq = __next_seq(control->txseq, pi); + pi->buffer_seq = pi->expected_tx_seq; + skb_in_use = 1; + + err = l2cap_ertm_rx_expected_iframe(sk, control, skb); + if (err) + break; + + if (control->final) { + if (pi->conn_state & L2CAP_CONN_REJ_ACT) + pi->conn_state &= ~L2CAP_CONN_REJ_ACT; + else { + control->final = 0; + l2cap_ertm_retransmit_all(sk, control); + l2cap_ertm_send(sk); + } + } - if (err < 0) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); + if (!(pi->conn_state & L2CAP_CONN_LOCAL_BUSY)) + l2cap_ertm_send_ack(sk); break; - } + case L2CAP_ERTM_TXSEQ_UNEXPECTED: + l2cap_ertm_pass_to_tx(sk, control); + + /* Can't issue SREJ frames in the local busy state. + * Drop this frame, it will be seen as missing + * when local busy is exited. + */ + if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) { + BT_DBG("Busy, discarding unexpected seq %d", + control->txseq); + break; + } - chan->buffer_seq_srej = - (chan->buffer_seq_srej + 1) % 64; - tx_seq = (tx_seq + 1) % 64; - } -} + /* There was a gap in the sequence, so an SREJ + * must be sent for each missing frame. The + * current frame is stored for later use. + */ + skb_queue_tail(SREJ_QUEUE(sk), skb); + skb_in_use = 1; + BT_DBG("Queued %p (queue len %d)", skb, + skb_queue_len(SREJ_QUEUE(sk))); -static void l2cap_resend_srejframe(struct l2cap_chan *chan, u8 tx_seq) -{ - struct srej_list *l, *tmp; - u16 control; + pi->conn_state &= ~L2CAP_CONN_SREJ_ACT; + l2cap_seq_list_clear(&pi->srej_list); + l2cap_ertm_send_srej(sk, control->txseq); - list_for_each_entry_safe(l, tmp, &chan->srej_l, list) { - if (l->tx_seq == tx_seq) { - list_del(&l->list); - kfree(l); - return; + pi->rx_state = L2CAP_ERTM_RX_STATE_SREJ_SENT; + break; + case L2CAP_ERTM_TXSEQ_DUPLICATE: + l2cap_ertm_pass_to_tx(sk, control); + break; + case L2CAP_ERTM_TXSEQ_INVALID_IGNORE: + break; + case L2CAP_ERTM_TXSEQ_INVALID: + default: + l2cap_send_disconn_req(l2cap_pi(sk)->conn, sk, + ECONNRESET); + break; + } + break; + case L2CAP_ERTM_EVENT_RECV_RR: + l2cap_ertm_pass_to_tx(sk, control); + if (control->final) { + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; + + if (pi->conn_state & L2CAP_CONN_REJ_ACT) + pi->conn_state &= ~L2CAP_CONN_REJ_ACT; + else if (pi->amp_move_state == L2CAP_AMP_STATE_STABLE || + pi->amp_move_state == + L2CAP_AMP_STATE_WAIT_PREPARE) { + control->final = 0; + l2cap_ertm_retransmit_all(sk, control); + } + + l2cap_ertm_send(sk); + } else if (control->poll) { + l2cap_ertm_send_i_or_rr_or_rnr(sk); + } else { + if ((pi->conn_state & L2CAP_CONN_REMOTE_BUSY) && + pi->unacked_frames) + l2cap_ertm_start_retrans_timer(pi); + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; + l2cap_ertm_send(sk); + } + break; + case L2CAP_ERTM_EVENT_RECV_RNR: + pi->conn_state |= L2CAP_CONN_REMOTE_BUSY; + l2cap_ertm_pass_to_tx(sk, control); + if (control && control->poll) { + pi->conn_state |= L2CAP_CONN_SEND_FBIT; + l2cap_ertm_send_rr_or_rnr(sk, 0); } - control = L2CAP_SUPER_SELECT_REJECT; - control |= l->tx_seq << L2CAP_CTRL_REQSEQ_SHIFT; - l2cap_send_sframe(chan, control); - list_del(&l->list); - list_add_tail(&l->list, &chan->srej_l); + l2cap_ertm_stop_retrans_timer(pi); + l2cap_seq_list_clear(&pi->retrans_list); + break; + case L2CAP_ERTM_EVENT_RECV_REJ: + l2cap_ertm_handle_rej(sk, control); + break; + case L2CAP_ERTM_EVENT_RECV_SREJ: + l2cap_ertm_handle_srej(sk, control); + break; + default: + break; } -} - -static void l2cap_send_srejframe(struct l2cap_chan *chan, u8 tx_seq) -{ - struct srej_list *new; - u16 control; - - while (tx_seq != chan->expected_tx_seq) { - control = L2CAP_SUPER_SELECT_REJECT; - control |= chan->expected_tx_seq << L2CAP_CTRL_REQSEQ_SHIFT; - l2cap_send_sframe(chan, control); - new = kzalloc(sizeof(struct srej_list), GFP_ATOMIC); - new->tx_seq = chan->expected_tx_seq; - chan->expected_tx_seq = (chan->expected_tx_seq + 1) % 64; - list_add_tail(&new->list, &chan->srej_l); + if (skb && !skb_in_use) { + BT_DBG("Freeing %p", skb); + kfree_skb(skb); } - chan->expected_tx_seq = (chan->expected_tx_seq + 1) % 64; + + return err; } -static inline int l2cap_data_channel_iframe(struct l2cap_chan *chan, u16 rx_control, struct sk_buff *skb) +static int l2cap_ertm_rx_state_srej_sent(struct sock *sk, + struct bt_l2cap_control *control, + struct sk_buff *skb, u8 event) { - u8 tx_seq = __get_txseq(rx_control); - u8 req_seq = __get_reqseq(rx_control); - u8 sar = rx_control >> L2CAP_CTRL_SAR_SHIFT; - int tx_seq_offset, expected_tx_seq_offset; - int num_to_ack = (chan->tx_win/6) + 1; + struct l2cap_pinfo *pi; int err = 0; + u16 txseq = control->txseq; + bool skb_in_use = 0; + + BT_DBG("sk %p, control %p, skb %p, event %d", sk, control, skb, + (int)event); + pi = l2cap_pi(sk); + + switch (event) { + case L2CAP_ERTM_EVENT_RECV_IFRAME: + switch (l2cap_ertm_classify_txseq(sk, txseq)) { + case L2CAP_ERTM_TXSEQ_EXPECTED: + /* Keep frame for reassembly later */ + l2cap_ertm_pass_to_tx(sk, control); + skb_queue_tail(SREJ_QUEUE(sk), skb); + skb_in_use = 1; + BT_DBG("Queued %p (queue len %d)", skb, + skb_queue_len(SREJ_QUEUE(sk))); + + pi->expected_tx_seq = __next_seq(txseq, pi); + break; + case L2CAP_ERTM_TXSEQ_EXPECTED_SREJ: + l2cap_seq_list_pop(&pi->srej_list); - BT_DBG("chan %p len %d tx_seq %d rx_control 0x%4.4x", chan, skb->len, - tx_seq, rx_control); + l2cap_ertm_pass_to_tx(sk, control); + skb_queue_tail(SREJ_QUEUE(sk), skb); + skb_in_use = 1; + BT_DBG("Queued %p (queue len %d)", skb, + skb_queue_len(SREJ_QUEUE(sk))); - if (L2CAP_CTRL_FINAL & rx_control && - test_bit(CONN_WAIT_F, &chan->conn_state)) { - __clear_monitor_timer(chan); - if (chan->unacked_frames > 0) - __set_retrans_timer(chan); - clear_bit(CONN_WAIT_F, &chan->conn_state); - } + err = l2cap_ertm_rx_queued_iframes(sk); + if (err) + break; + + break; + case L2CAP_ERTM_TXSEQ_UNEXPECTED: + /* Got a frame that can't be reassembled yet. + * Save it for later, and send SREJs to cover + * the missing frames. + */ + skb_queue_tail(SREJ_QUEUE(sk), skb); + skb_in_use = 1; + BT_DBG("Queued %p (queue len %d)", skb, + skb_queue_len(SREJ_QUEUE(sk))); + + l2cap_ertm_pass_to_tx(sk, control); + l2cap_ertm_send_srej(sk, control->txseq); + break; + case L2CAP_ERTM_TXSEQ_UNEXPECTED_SREJ: + /* This frame was requested with an SREJ, but + * some expected retransmitted frames are + * missing. Request retransmission of missing + * SREJ'd frames. + */ + skb_queue_tail(SREJ_QUEUE(sk), skb); + skb_in_use = 1; + BT_DBG("Queued %p (queue len %d)", skb, + skb_queue_len(SREJ_QUEUE(sk))); + + l2cap_ertm_pass_to_tx(sk, control); + l2cap_ertm_send_srej_list(sk, control->txseq); + break; + case L2CAP_ERTM_TXSEQ_DUPLICATE_SREJ: + /* We've already queued this frame. Drop this copy. */ + l2cap_ertm_pass_to_tx(sk, control); + break; + case L2CAP_ERTM_TXSEQ_DUPLICATE: + /* Expecting a later sequence number, so this frame + * was already received. Ignore it completely. + */ + break; + case L2CAP_ERTM_TXSEQ_INVALID_IGNORE: + break; + case L2CAP_ERTM_TXSEQ_INVALID: + default: + l2cap_send_disconn_req(l2cap_pi(sk)->conn, sk, + ECONNRESET); + break; + } + break; + case L2CAP_ERTM_EVENT_RECV_RR: + l2cap_ertm_pass_to_tx(sk, control); + if (control->final) { + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; + + if (pi->conn_state & L2CAP_CONN_REJ_ACT) + pi->conn_state &= ~L2CAP_CONN_REJ_ACT; + else { + control->final = 0; + l2cap_ertm_retransmit_all(sk, control); + } - chan->expected_ack_seq = req_seq; - l2cap_drop_acked_frames(chan); + l2cap_ertm_send(sk); + } else if (control->poll) { + if ((pi->conn_state & L2CAP_CONN_REMOTE_BUSY) && + pi->unacked_frames) { + l2cap_ertm_start_retrans_timer(pi); + } + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; + pi->conn_state |= L2CAP_CONN_SEND_FBIT; + l2cap_ertm_send_srej_tail(sk); + } else { + if ((pi->conn_state & L2CAP_CONN_REMOTE_BUSY) && + pi->unacked_frames) { + l2cap_ertm_start_retrans_timer(pi); + } + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; + l2cap_ertm_send_ack(sk); + } + break; + case L2CAP_ERTM_EVENT_RECV_RNR: + pi->conn_state |= L2CAP_CONN_REMOTE_BUSY; + l2cap_ertm_pass_to_tx(sk, control); + if (control->poll) + l2cap_ertm_send_srej_tail(sk); + else { + struct bt_l2cap_control rr_control; + memset(&rr_control, 0, sizeof(rr_control)); + rr_control.frame_type = 's'; + rr_control.super = L2CAP_SFRAME_RR; + rr_control.reqseq = pi->buffer_seq; + l2cap_ertm_send_sframe(sk, &rr_control); + } - tx_seq_offset = (tx_seq - chan->buffer_seq) % 64; - if (tx_seq_offset < 0) - tx_seq_offset += 64; + break; + case L2CAP_ERTM_EVENT_RECV_REJ: + l2cap_ertm_handle_rej(sk, control); + break; + case L2CAP_ERTM_EVENT_RECV_SREJ: + l2cap_ertm_handle_srej(sk, control); + break; + } - /* invalid tx_seq */ - if (tx_seq_offset >= chan->tx_win) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - goto drop; + if (skb && !skb_in_use) { + BT_DBG("Freeing %p", skb); + kfree_skb(skb); } - if (test_bit(CONN_LOCAL_BUSY, &chan->conn_state)) - goto drop; + return err; +} - if (tx_seq == chan->expected_tx_seq) - goto expected; +static int l2cap_ertm_rx_state_amp_move(struct sock *sk, + struct bt_l2cap_control *control, + struct sk_buff *skb, u8 event) +{ + struct l2cap_pinfo *pi; + int err = 0; + bool skb_in_use = 0; - if (test_bit(CONN_SREJ_SENT, &chan->conn_state)) { - struct srej_list *first; + BT_DBG("sk %p, control %p, skb %p, event %d", sk, control, skb, + (int)event); + pi = l2cap_pi(sk); - first = list_first_entry(&chan->srej_l, - struct srej_list, list); - if (tx_seq == first->tx_seq) { - l2cap_add_to_srej_queue(chan, skb, tx_seq, sar); - l2cap_check_srej_gap(chan, tx_seq); + /* Only handle expected frames, to avoid state changes. */ - list_del(&first->list); - kfree(first); + switch (event) { + case L2CAP_ERTM_EVENT_RECV_IFRAME: + if (l2cap_ertm_classify_txseq(sk, control->txseq) == + L2CAP_ERTM_TXSEQ_EXPECTED) { + l2cap_ertm_pass_to_tx(sk, control); - if (list_empty(&chan->srej_l)) { - chan->buffer_seq = chan->buffer_seq_srej; - clear_bit(CONN_SREJ_SENT, &chan->conn_state); - l2cap_send_ack(chan); - BT_DBG("chan %p, Exit SREJ_SENT", chan); + if (pi->conn_state & L2CAP_CONN_LOCAL_BUSY) { + BT_DBG("Busy, discarding expected seq %d", + control->txseq); + break; } - } else { - struct srej_list *l; - /* duplicated tx_seq */ - if (l2cap_add_to_srej_queue(chan, skb, tx_seq, sar) < 0) - goto drop; + pi->expected_tx_seq = __next_seq(control->txseq, pi); + pi->buffer_seq = pi->expected_tx_seq; + skb_in_use = 1; - list_for_each_entry(l, &chan->srej_l, list) { - if (l->tx_seq == tx_seq) { - l2cap_resend_srejframe(chan, tx_seq); - return 0; - } + err = l2cap_ertm_rx_expected_iframe(sk, control, skb); + if (err) + break; + + if (control->final) { + if (pi->conn_state & L2CAP_CONN_REJ_ACT) + pi->conn_state &= ~L2CAP_CONN_REJ_ACT; + else + control->final = 0; } - l2cap_send_srejframe(chan, tx_seq); } - } else { - expected_tx_seq_offset = - (chan->expected_tx_seq - chan->buffer_seq) % 64; - if (expected_tx_seq_offset < 0) - expected_tx_seq_offset += 64; - - /* duplicated tx_seq */ - if (tx_seq_offset < expected_tx_seq_offset) - goto drop; + break; + case L2CAP_ERTM_EVENT_RECV_RR: + case L2CAP_ERTM_EVENT_RECV_RNR: + case L2CAP_ERTM_EVENT_RECV_REJ: + l2cap_ertm_process_reqseq(sk, control->reqseq); + break; + case L2CAP_ERTM_EVENT_RECV_SREJ: + /* Ignore */ + break; + default: + break; + } - set_bit(CONN_SREJ_SENT, &chan->conn_state); + if (skb && !skb_in_use) { + BT_DBG("Freeing %p", skb); + kfree_skb(skb); + } - BT_DBG("chan %p, Enter SREJ", chan); + return err; +} - INIT_LIST_HEAD(&chan->srej_l); - chan->buffer_seq_srej = chan->buffer_seq; +static int l2cap_answer_move_poll(struct sock *sk) +{ + struct l2cap_pinfo *pi; + struct bt_l2cap_control control; + int err = 0; - __skb_queue_head_init(&chan->srej_q); - l2cap_add_to_srej_queue(chan, skb, tx_seq, sar); + BT_DBG("sk %p", sk); - set_bit(CONN_SEND_PBIT, &chan->conn_state); + pi = l2cap_pi(sk); - l2cap_send_srejframe(chan, tx_seq); + l2cap_ertm_process_reqseq(sk, pi->amp_move_reqseq); - __clear_ack_timer(chan); - } - return 0; + if (!skb_queue_empty(TX_QUEUE(sk))) + sk->sk_send_head = skb_peek(TX_QUEUE(sk)); + else + sk->sk_send_head = NULL; -expected: - chan->expected_tx_seq = (chan->expected_tx_seq + 1) % 64; + /* Rewind next_tx_seq to the point expected + * by the receiver. + */ + pi->next_tx_seq = pi->amp_move_reqseq; + pi->unacked_frames = 0; - if (test_bit(CONN_SREJ_SENT, &chan->conn_state)) { - bt_cb(skb)->tx_seq = tx_seq; - bt_cb(skb)->sar = sar; - __skb_queue_tail(&chan->srej_q, skb); - return 0; - } + err = l2cap_finish_amp_move(sk); - err = l2cap_ertm_reassembly_sdu(chan, skb, rx_control); - chan->buffer_seq = (chan->buffer_seq + 1) % 64; - if (err < 0) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); + if (err) return err; - } - - if (rx_control & L2CAP_CTRL_FINAL) { - if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state)) - l2cap_retransmit_frames(chan); - } - __set_ack_timer(chan); + pi->conn_state |= L2CAP_CONN_SEND_FBIT; + l2cap_ertm_send_i_or_rr_or_rnr(sk); - chan->num_acked = (chan->num_acked + 1) % num_to_ack; - if (chan->num_acked == num_to_ack - 1) - l2cap_send_ack(chan); + memset(&control, 0, sizeof(control)); + control.reqseq = pi->amp_move_reqseq; - return 0; + if (pi->amp_move_event == L2CAP_ERTM_EVENT_RECV_IFRAME) + err = -EPROTO; + else + err = l2cap_ertm_rx_state_recv(sk, &control, NULL, + pi->amp_move_event); -drop: - kfree_skb(skb); - return 0; + return err; } -static inline void l2cap_data_channel_rrframe(struct l2cap_chan *chan, u16 rx_control) +static void l2cap_amp_move_setup(struct sock *sk) { - BT_DBG("chan %p, req_seq %d ctrl 0x%4.4x", chan, __get_reqseq(rx_control), - rx_control); - - chan->expected_ack_seq = __get_reqseq(rx_control); - l2cap_drop_acked_frames(chan); - - if (rx_control & L2CAP_CTRL_POLL) { - set_bit(CONN_SEND_FBIT, &chan->conn_state); - if (test_bit(CONN_SREJ_SENT, &chan->conn_state)) { - if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state) && - (chan->unacked_frames > 0)) - __set_retrans_timer(chan); - - clear_bit(CONN_REMOTE_BUSY, &chan->conn_state); - l2cap_send_srejtail(chan); - } else { - l2cap_send_i_or_rr_or_rnr(chan); - } + struct l2cap_pinfo *pi; + struct sk_buff *skb; - } else if (rx_control & L2CAP_CTRL_FINAL) { - clear_bit(CONN_REMOTE_BUSY, &chan->conn_state); + BT_DBG("sk %p", sk); - if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state)) - l2cap_retransmit_frames(chan); + pi = l2cap_pi(sk); - } else { - if (test_bit(CONN_REMOTE_BUSY, &chan->conn_state) && - (chan->unacked_frames > 0)) - __set_retrans_timer(chan); + l2cap_ertm_stop_ack_timer(pi); + l2cap_ertm_stop_retrans_timer(pi); + l2cap_ertm_stop_monitor_timer(pi); - clear_bit(CONN_REMOTE_BUSY, &chan->conn_state); - if (test_bit(CONN_SREJ_SENT, &chan->conn_state)) - l2cap_send_ack(chan); + pi->retry_count = 0; + skb_queue_walk(TX_QUEUE(sk), skb) { + if (bt_cb(skb)->retries) + bt_cb(skb)->retries = 1; else - l2cap_ertm_send(chan); + break; } -} - -static inline void l2cap_data_channel_rejframe(struct l2cap_chan *chan, u16 rx_control) -{ - u8 tx_seq = __get_reqseq(rx_control); - BT_DBG("chan %p, req_seq %d ctrl 0x%4.4x", chan, tx_seq, rx_control); + pi->expected_tx_seq = pi->buffer_seq; - clear_bit(CONN_REMOTE_BUSY, &chan->conn_state); + pi->conn_state &= ~(L2CAP_CONN_REJ_ACT | L2CAP_CONN_SREJ_ACT); + l2cap_seq_list_clear(&pi->retrans_list); + l2cap_seq_list_clear(&l2cap_pi(sk)->srej_list); + skb_queue_purge(SREJ_QUEUE(sk)); - chan->expected_ack_seq = tx_seq; - l2cap_drop_acked_frames(chan); + pi->tx_state = L2CAP_ERTM_TX_STATE_XMIT; + pi->rx_state = L2CAP_ERTM_RX_STATE_AMP_MOVE; - if (rx_control & L2CAP_CTRL_FINAL) { - if (!test_and_clear_bit(CONN_REJ_ACT, &chan->conn_state)) - l2cap_retransmit_frames(chan); - } else { - l2cap_retransmit_frames(chan); + BT_DBG("tx_state 0x2.2%x rx_state 0x2.2%x", pi->tx_state, + pi->rx_state); - if (test_bit(CONN_WAIT_F, &chan->conn_state)) - set_bit(CONN_REJ_ACT, &chan->conn_state); - } + pi->conn_state |= L2CAP_CONN_REMOTE_BUSY; } -static inline void l2cap_data_channel_srejframe(struct l2cap_chan *chan, u16 rx_control) -{ - u8 tx_seq = __get_reqseq(rx_control); - BT_DBG("chan %p, req_seq %d ctrl 0x%4.4x", chan, tx_seq, rx_control); - - clear_bit(CONN_REMOTE_BUSY, &chan->conn_state); - - if (rx_control & L2CAP_CTRL_POLL) { - chan->expected_ack_seq = tx_seq; - l2cap_drop_acked_frames(chan); +static void l2cap_amp_move_revert(struct sock *sk) +{ + struct l2cap_pinfo *pi; - set_bit(CONN_SEND_FBIT, &chan->conn_state); - l2cap_retransmit_one_frame(chan, tx_seq); + BT_DBG("sk %p", sk); - l2cap_ertm_send(chan); + pi = l2cap_pi(sk); - if (test_bit(CONN_WAIT_F, &chan->conn_state)) { - chan->srej_save_reqseq = tx_seq; - set_bit(CONN_SREJ_ACT, &chan->conn_state); - } - } else if (rx_control & L2CAP_CTRL_FINAL) { - if (test_bit(CONN_SREJ_ACT, &chan->conn_state) && - chan->srej_save_reqseq == tx_seq) - clear_bit(CONN_SREJ_ACT, &chan->conn_state); - else - l2cap_retransmit_one_frame(chan, tx_seq); - } else { - l2cap_retransmit_one_frame(chan, tx_seq); - if (test_bit(CONN_WAIT_F, &chan->conn_state)) { - chan->srej_save_reqseq = tx_seq; - set_bit(CONN_SREJ_ACT, &chan->conn_state); - } - } + if (pi->amp_move_role == L2CAP_AMP_MOVE_INITIATOR) { + l2cap_ertm_tx(sk, NULL, NULL, L2CAP_ERTM_EVENT_EXPLICIT_POLL); + pi->rx_state = L2CAP_ERTM_RX_STATE_WAIT_F_FLAG; + } else if (pi->amp_move_role == L2CAP_AMP_MOVE_RESPONDER) + pi->rx_state = L2CAP_ERTM_RX_STATE_WAIT_P_FLAG; } -static inline void l2cap_data_channel_rnrframe(struct l2cap_chan *chan, u16 rx_control) +static int l2cap_amp_move_reconf(struct sock *sk) { - u8 tx_seq = __get_reqseq(rx_control); - - BT_DBG("chan %p, req_seq %d ctrl 0x%4.4x", chan, tx_seq, rx_control); - - set_bit(CONN_REMOTE_BUSY, &chan->conn_state); - chan->expected_ack_seq = tx_seq; - l2cap_drop_acked_frames(chan); + struct l2cap_pinfo *pi; + u8 buf[64]; + int err = 0; - if (rx_control & L2CAP_CTRL_POLL) - set_bit(CONN_SEND_FBIT, &chan->conn_state); + BT_DBG("sk %p", sk); - if (!test_bit(CONN_SREJ_SENT, &chan->conn_state)) { - __clear_retrans_timer(chan); - if (rx_control & L2CAP_CTRL_POLL) - l2cap_send_rr_or_rnr(chan, L2CAP_CTRL_FINAL); - return; - } + pi = l2cap_pi(sk); - if (rx_control & L2CAP_CTRL_POLL) - l2cap_send_srejtail(chan); - else - l2cap_send_sframe(chan, L2CAP_SUPER_RCV_READY); + l2cap_send_cmd(pi->conn, l2cap_get_ident(pi->conn), L2CAP_CONF_REQ, + l2cap_build_amp_reconf_req(sk, buf), buf); + return err; } -static inline int l2cap_data_channel_sframe(struct l2cap_chan *chan, u16 rx_control, struct sk_buff *skb) +static void l2cap_amp_move_success(struct sock *sk) { - BT_DBG("chan %p rx_control 0x%4.4x len %d", chan, rx_control, skb->len); - - if (L2CAP_CTRL_FINAL & rx_control && - test_bit(CONN_WAIT_F, &chan->conn_state)) { - __clear_monitor_timer(chan); - if (chan->unacked_frames > 0) - __set_retrans_timer(chan); - clear_bit(CONN_WAIT_F, &chan->conn_state); - } + struct l2cap_pinfo *pi; - switch (rx_control & L2CAP_CTRL_SUPERVISE) { - case L2CAP_SUPER_RCV_READY: - l2cap_data_channel_rrframe(chan, rx_control); - break; + BT_DBG("sk %p", sk); - case L2CAP_SUPER_REJECT: - l2cap_data_channel_rejframe(chan, rx_control); - break; + pi = l2cap_pi(sk); - case L2CAP_SUPER_SELECT_REJECT: - l2cap_data_channel_srejframe(chan, rx_control); - break; + if (pi->amp_move_role == L2CAP_AMP_MOVE_INITIATOR) { + int err = 0; + /* Send reconfigure request */ + if (pi->mode == L2CAP_MODE_ERTM) { + pi->reconf_state = L2CAP_RECONF_INT; + if (enable_reconfig) + err = l2cap_amp_move_reconf(sk); - case L2CAP_SUPER_RCV_NOT_READY: - l2cap_data_channel_rnrframe(chan, rx_control); - break; + if (err || !enable_reconfig) { + pi->reconf_state = L2CAP_RECONF_NONE; + l2cap_ertm_tx(sk, NULL, NULL, + L2CAP_ERTM_EVENT_EXPLICIT_POLL); + pi->rx_state = L2CAP_ERTM_RX_STATE_WAIT_F_FLAG; + } + } else + pi->rx_state = L2CAP_ERTM_RX_STATE_RECV; + } else if (pi->amp_move_role == L2CAP_AMP_MOVE_RESPONDER) { + if (pi->mode == L2CAP_MODE_ERTM) + pi->rx_state = + L2CAP_ERTM_RX_STATE_WAIT_P_FLAG_RECONFIGURE; + else + pi->rx_state = L2CAP_ERTM_RX_STATE_RECV; } +} - kfree_skb(skb); - return 0; +static inline bool __valid_reqseq(struct l2cap_pinfo *pi, u16 reqseq) +{ + /* Make sure reqseq is for a packet that has been sent but not acked */ + u16 unacked = __delta_seq(pi->next_tx_seq, pi->expected_ack_seq, pi); + return __delta_seq(pi->next_tx_seq, reqseq, pi) <= unacked; } -static int l2cap_ertm_data_rcv(struct sock *sk, struct sk_buff *skb) +static int l2cap_strm_rx(struct sock *sk, struct bt_l2cap_control *control, + struct sk_buff *skb) { - struct l2cap_chan *chan = l2cap_pi(sk)->chan; - u16 control; - u8 req_seq; - int len, next_tx_seq_offset, req_seq_offset; + struct l2cap_pinfo *pi; + int err = 0; - control = get_unaligned_le16(skb->data); - skb_pull(skb, 2); - len = skb->len; + BT_DBG("sk %p, control %p, skb %p, state %d", + sk, control, skb, l2cap_pi(sk)->rx_state); - /* - * We can just drop the corrupted I-frame here. - * Receiver will miss it and start proper recovery - * procedures and ask retransmission. - */ - if (l2cap_check_fcs(chan, skb)) - goto drop; + pi = l2cap_pi(sk); - if (__is_sar_start(control) && __is_iframe(control)) - len -= 2; + if (l2cap_ertm_classify_txseq(sk, control->txseq) == + L2CAP_ERTM_TXSEQ_EXPECTED) { + l2cap_ertm_pass_to_tx(sk, control); - if (chan->fcs == L2CAP_FCS_CRC16) - len -= 2; + BT_DBG("buffer_seq %d->%d", pi->buffer_seq, + __next_seq(pi->buffer_seq, pi)); - if (len > chan->mps) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - goto drop; + pi->buffer_seq = __next_seq(pi->buffer_seq, pi); + + l2cap_ertm_rx_expected_iframe(sk, control, skb); + } else { + if (pi->sdu) { + kfree_skb(pi->sdu); + pi->sdu = NULL; + } + pi->sdu_last_frag = NULL; + pi->sdu_len = 0; + + if (skb) { + BT_DBG("Freeing %p", skb); + kfree_skb(skb); + } } - req_seq = __get_reqseq(control); - req_seq_offset = (req_seq - chan->expected_ack_seq) % 64; - if (req_seq_offset < 0) - req_seq_offset += 64; + pi->last_acked_seq = control->txseq; + pi->expected_tx_seq = __next_seq(control->txseq, pi); - next_tx_seq_offset = - (chan->next_tx_seq - chan->expected_ack_seq) % 64; - if (next_tx_seq_offset < 0) - next_tx_seq_offset += 64; + return err; +} - /* check for invalid req-seq */ - if (req_seq_offset > next_tx_seq_offset) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - goto drop; - } +static int l2cap_ertm_rx(struct sock *sk, struct bt_l2cap_control *control, + struct sk_buff *skb, u8 event) +{ + struct l2cap_pinfo *pi; + int err = 0; - if (__is_iframe(control)) { - if (len < 0) { - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - goto drop; - } + BT_DBG("sk %p, control %p, skb %p, event %d, state %d", + sk, control, skb, (int)event, l2cap_pi(sk)->rx_state); - l2cap_data_channel_iframe(chan, control, skb); - } else { - if (len != 0) { - BT_ERR("%d", len); - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - goto drop; - } + pi = l2cap_pi(sk); - l2cap_data_channel_sframe(chan, control, skb); + if (__valid_reqseq(pi, control->reqseq)) { + switch (pi->rx_state) { + case L2CAP_ERTM_RX_STATE_RECV: + err = l2cap_ertm_rx_state_recv(sk, control, skb, event); + break; + case L2CAP_ERTM_RX_STATE_SREJ_SENT: + err = l2cap_ertm_rx_state_srej_sent(sk, control, skb, + event); + break; + case L2CAP_ERTM_RX_STATE_AMP_MOVE: + err = l2cap_ertm_rx_state_amp_move(sk, control, skb, + event); + break; + case L2CAP_ERTM_RX_STATE_WAIT_F_FLAG: + if (control->final) { + pi->conn_state &= ~L2CAP_CONN_REMOTE_BUSY; + pi->amp_move_role = L2CAP_AMP_MOVE_NONE; + + pi->rx_state = L2CAP_ERTM_RX_STATE_RECV; + l2cap_ertm_process_reqseq(sk, control->reqseq); + + if (!skb_queue_empty(TX_QUEUE(sk))) + sk->sk_send_head = + skb_peek(TX_QUEUE(sk)); + else + sk->sk_send_head = NULL; + + /* Rewind next_tx_seq to the point expected + * by the receiver. + */ + pi->next_tx_seq = control->reqseq; + pi->unacked_frames = 0; + + if (pi->ampcon) + pi->conn->mtu = + pi->ampcon->hdev->acl_mtu; + else + pi->conn->mtu = + pi->conn->hcon->hdev->acl_mtu; + + err = l2cap_setup_resegment(sk); + + if (err) + break; + + err = l2cap_ertm_rx_state_recv(sk, control, skb, + event); + } + break; + case L2CAP_ERTM_RX_STATE_WAIT_P_FLAG: + if (control->poll) { + pi->amp_move_reqseq = control->reqseq; + pi->amp_move_event = event; + err = l2cap_answer_move_poll(sk); + } + break; + case L2CAP_ERTM_RX_STATE_WAIT_P_FLAG_RECONFIGURE: + if (control->poll) { + pi->amp_move_reqseq = control->reqseq; + pi->amp_move_event = event; + + BT_DBG("amp_move_role 0x%2.2x, " + "reconf_state 0x%2.2x", + pi->amp_move_role, pi->reconf_state); + + if (pi->reconf_state == L2CAP_RECONF_ACC) + err = l2cap_amp_move_reconf(sk); + else + err = l2cap_answer_move_poll(sk); + } + break; + default: + /* shut it down */ + break; + } + } else { + BT_DBG("Invalid reqseq %d (next_tx_seq %d, expected_ack_seq %d", + control->reqseq, pi->next_tx_seq, pi->expected_ack_seq); + l2cap_send_disconn_req(pi->conn, sk, ECONNRESET); } - return 0; - -drop: - kfree_skb(skb); - return 0; + return err; } -static inline int l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk_buff *skb) +void l2cap_fixed_channel_config(struct sock *sk, struct l2cap_options *opt) { - struct l2cap_chan *chan; - struct sock *sk = NULL; - u16 control; - u8 tx_seq; - int len; + lock_sock(sk); + + l2cap_pi(sk)->fixed_channel = 1; + + l2cap_pi(sk)->imtu = opt->imtu; + l2cap_pi(sk)->omtu = opt->omtu; + l2cap_pi(sk)->remote_mps = opt->omtu; + l2cap_pi(sk)->mps = opt->omtu; + l2cap_pi(sk)->flush_to = opt->flush_to; + l2cap_pi(sk)->mode = opt->mode; + l2cap_pi(sk)->fcs = opt->fcs; + l2cap_pi(sk)->max_tx = opt->max_tx; + l2cap_pi(sk)->remote_max_tx = opt->max_tx; + l2cap_pi(sk)->tx_win = opt->txwin_size; + l2cap_pi(sk)->remote_tx_win = opt->txwin_size; + l2cap_pi(sk)->retrans_timeout = L2CAP_DEFAULT_RETRANS_TO; + l2cap_pi(sk)->monitor_timeout = L2CAP_DEFAULT_MONITOR_TO; + + if (opt->mode == L2CAP_MODE_ERTM || + l2cap_pi(sk)->mode == L2CAP_MODE_STREAMING) + l2cap_ertm_init(sk); + + release_sock(sk); + + return; +} - chan = l2cap_get_chan_by_scid(conn, cid); - if (!chan) { - BT_DBG("unknown cid 0x%4.4x", cid); - goto drop; - } +static const u8 l2cap_ertm_rx_func_to_event[4] = { + L2CAP_ERTM_EVENT_RECV_RR, L2CAP_ERTM_EVENT_RECV_REJ, + L2CAP_ERTM_EVENT_RECV_RNR, L2CAP_ERTM_EVENT_RECV_SREJ +}; - sk = chan->sk; +int l2cap_data_channel(struct sock *sk, struct sk_buff *skb) +{ + struct l2cap_pinfo *pi; + struct bt_l2cap_control *control; + u16 len; + u8 event; + pi = l2cap_pi(sk); - BT_DBG("chan %p, len %d", chan, skb->len); + BT_DBG("sk %p, len %d, mode %d", sk, skb->len, pi->mode); - if (chan->state != BT_CONNECTED) + if (sk->sk_state != BT_CONNECTED) goto drop; - switch (chan->mode) { + switch (pi->mode) { case L2CAP_MODE_BASIC: /* If socket recv buffers overflows we drop data here * which is *bad* because L2CAP has to be reliable. * But we don't have any other choice. L2CAP doesn't * provide flow control mechanism. */ - if (chan->imtu < skb->len) + if (pi->imtu < skb->len) goto drop; - if (!chan->ops->recv(chan->data, skb)) + if (!sock_queue_rcv_skb(sk, skb)) goto done; break; case L2CAP_MODE_ERTM: - if (!sock_owned_by_user(sk)) { - l2cap_ertm_data_rcv(sk, skb); + case L2CAP_MODE_STREAMING: + control = &bt_cb(skb)->control; + if (pi->extended_control) { + __get_extended_control(get_unaligned_le32(skb->data), + control); + skb_pull(skb, 4); } else { - if (sk_add_backlog(sk, skb)) - goto drop; + __get_enhanced_control(get_unaligned_le16(skb->data), + control); + skb_pull(skb, 2); } - goto done; - - case L2CAP_MODE_STREAMING: - control = get_unaligned_le16(skb->data); - skb_pull(skb, 2); len = skb->len; - if (l2cap_check_fcs(chan, skb)) + if (l2cap_check_fcs(pi, skb)) goto drop; - if (__is_sar_start(control)) + if ((control->frame_type == 'i') && + (control->sar == L2CAP_SAR_START)) len -= 2; - if (chan->fcs == L2CAP_FCS_CRC16) + if (pi->fcs == L2CAP_FCS_CRC16) len -= 2; - if (len > chan->mps || len < 0 || __is_sframe(control)) + /* + * We can just drop the corrupted I-frame here. + * Receiver will miss it and start proper recovery + * procedures and ask for retransmission. + */ + if (len > pi->mps) { + l2cap_send_disconn_req(pi->conn, sk, ECONNRESET); goto drop; + } - tx_seq = __get_txseq(control); + if (control->frame_type == 'i') { - if (chan->expected_tx_seq == tx_seq) - chan->expected_tx_seq = (chan->expected_tx_seq + 1) % 64; - else - chan->expected_tx_seq = (tx_seq + 1) % 64; + int err; + + BT_DBG("iframe sar %d, reqseq %d, final %d, txseq %d", + control->sar, control->reqseq, control->final, + control->txseq); + + /* Validate F-bit - F=0 always valid, F=1 only + * valid in TX WAIT_F + */ + if (control->final && (pi->tx_state != + L2CAP_ERTM_TX_STATE_WAIT_F)) + goto drop; + + if (pi->mode != L2CAP_MODE_STREAMING) { + event = L2CAP_ERTM_EVENT_RECV_IFRAME; + err = l2cap_ertm_rx(sk, control, skb, event); + } else + err = l2cap_strm_rx(sk, control, skb); + if (err) + l2cap_send_disconn_req(pi->conn, sk, + ECONNRESET); + } else { + /* Only I-frames are expected in streaming mode */ + if (pi->mode == L2CAP_MODE_STREAMING) + goto drop; + + BT_DBG("sframe reqseq %d, final %d, poll %d, super %d", + control->reqseq, control->final, control->poll, + control->super); + + if (len != 0) { + l2cap_send_disconn_req(pi->conn, sk, + ECONNRESET); + goto drop; + } - l2cap_streaming_reassembly_sdu(chan, skb, control); + /* Validate F and P bits */ + if (control->final && + ((pi->tx_state != L2CAP_ERTM_TX_STATE_WAIT_F) + || control->poll)) + goto drop; + + event = l2cap_ertm_rx_func_to_event[control->super]; + if (l2cap_ertm_rx(sk, control, skb, event)) + l2cap_send_disconn_req(pi->conn, sk, + ECONNRESET); + } goto done; default: - BT_DBG("chan %p: bad mode 0x%2.2x", chan, chan->mode); + BT_DBG("sk %p: bad mode 0x%2.2x", sk, pi->mode); break; } @@ -3868,34 +7005,35 @@ static inline int l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk kfree_skb(skb); done: - if (sk) - bh_unlock_sock(sk); - return 0; } +void l2cap_recv_deferred_frame(struct sock *sk, struct sk_buff *skb) +{ + lock_sock(sk); + l2cap_data_channel(sk, skb); + release_sock(sk); +} + static inline int l2cap_conless_channel(struct l2cap_conn *conn, __le16 psm, struct sk_buff *skb) { - struct sock *sk = NULL; - struct l2cap_chan *chan; + struct sock *sk; - chan = l2cap_global_chan_by_psm(0, psm, conn->src); - if (!chan) + sk = l2cap_get_sock_by_psm(0, psm, conn->src); + if (!sk) goto drop; - sk = chan->sk; - bh_lock_sock(sk); BT_DBG("sk %p, len %d", sk, skb->len); - if (chan->state != BT_BOUND && chan->state != BT_CONNECTED) + if (sk->sk_state != BT_BOUND && sk->sk_state != BT_CONNECTED) goto drop; - if (chan->imtu < skb->len) + if (l2cap_pi(sk)->imtu < skb->len) goto drop; - if (!chan->ops->recv(chan->data, skb)) + if (!sock_queue_rcv_skb(sk, skb)) goto done; drop: @@ -3909,26 +7047,23 @@ static inline int l2cap_conless_channel(struct l2cap_conn *conn, __le16 psm, str static inline int l2cap_att_channel(struct l2cap_conn *conn, __le16 cid, struct sk_buff *skb) { - struct sock *sk = NULL; - struct l2cap_chan *chan; + struct sock *sk; - chan = l2cap_global_chan_by_scid(0, cid, conn->src); - if (!chan) + sk = l2cap_get_sock_by_scid(0, cid, conn->src); + if (!sk) goto drop; - sk = chan->sk; - bh_lock_sock(sk); BT_DBG("sk %p, len %d", sk, skb->len); - if (chan->state != BT_BOUND && chan->state != BT_CONNECTED) + if (sk->sk_state != BT_BOUND && sk->sk_state != BT_CONNECTED) goto drop; - if (chan->imtu < skb->len) + if (l2cap_pi(sk)->imtu < skb->len) goto drop; - if (!chan->ops->recv(chan->data, skb)) + if (!sock_queue_rcv_skb(sk, skb)) goto done; drop: @@ -3943,6 +7078,7 @@ static inline int l2cap_att_channel(struct l2cap_conn *conn, __le16 cid, struct static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) { struct l2cap_hdr *lh = (void *) skb->data; + struct sock *sk; u16 cid, len; __le16 psm; @@ -3979,7 +7115,24 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) break; default: - l2cap_data_channel(conn, cid, skb); + sk = l2cap_get_chan_by_scid(&conn->chan_list, cid); + if (sk) { + if (sock_owned_by_user(sk)) { + BT_DBG("backlog sk %p", sk); + if (sk_add_backlog(sk, skb)) + kfree_skb(skb); + } else + l2cap_data_channel(sk, skb); + + bh_unlock_sock(sk); + } else if (cid == L2CAP_CID_A2MP) { + BT_DBG("A2MP"); + amp_conn_ind(conn, skb); + } else { + BT_DBG("unknown cid 0x%4.4x", cid); + kfree_skb(skb); + } + break; } } @@ -3989,7 +7142,8 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) static int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) { int exact = 0, lm1 = 0, lm2 = 0; - struct l2cap_chan *c; + register struct sock *sk; + struct hlist_node *node; if (type != ACL_LINK) return -EINVAL; @@ -3997,25 +7151,23 @@ static int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 type) BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr)); /* Find listening sockets and check their link_mode */ - read_lock(&chan_list_lock); - list_for_each_entry(c, &chan_list, global_l) { - struct sock *sk = c->sk; - - if (c->state != BT_LISTEN) + read_lock(&l2cap_sk_list.lock); + sk_for_each(sk, node, &l2cap_sk_list.head) { + if (sk->sk_state != BT_LISTEN) continue; if (!bacmp(&bt_sk(sk)->src, &hdev->bdaddr)) { lm1 |= HCI_LM_ACCEPT; - if (c->role_switch) + if (l2cap_pi(sk)->role_switch) lm1 |= HCI_LM_MASTER; exact++; } else if (!bacmp(&bt_sk(sk)->src, BDADDR_ANY)) { lm2 |= HCI_LM_ACCEPT; - if (c->role_switch) + if (l2cap_pi(sk)->role_switch) lm2 |= HCI_LM_MASTER; } } - read_unlock(&chan_list_lock); + read_unlock(&l2cap_sk_list.lock); return exact ? lm1 : lm2; } @@ -4034,7 +7186,7 @@ static int l2cap_connect_cfm(struct hci_conn *hcon, u8 status) if (conn) l2cap_conn_ready(conn); } else - l2cap_conn_del(hcon, bt_to_errno(status)); + l2cap_conn_del(hcon, bt_err(status)); return 0; } @@ -4045,7 +7197,7 @@ static int l2cap_disconn_ind(struct hci_conn *hcon) BT_DBG("hcon %p", hcon); - if ((hcon->type != ACL_LINK && hcon->type != LE_LINK) || !conn) + if (hcon->type != ACL_LINK || !conn) return 0x13; return conn->disc_reason; @@ -4058,50 +7210,51 @@ static int l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason) if (!(hcon->type == ACL_LINK || hcon->type == LE_LINK)) return -EINVAL; - l2cap_conn_del(hcon, bt_to_errno(reason)); + l2cap_conn_del(hcon, bt_err(reason)); return 0; } -static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt) +static inline void l2cap_check_encryption(struct sock *sk, u8 encrypt) { - if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) + if (sk->sk_type != SOCK_SEQPACKET && sk->sk_type != SOCK_STREAM) return; if (encrypt == 0x00) { - if (chan->sec_level == BT_SECURITY_MEDIUM) { - __clear_chan_timer(chan); - __set_chan_timer(chan, HZ * 5); - } else if (chan->sec_level == BT_SECURITY_HIGH) - l2cap_chan_close(chan, ECONNREFUSED); + if (l2cap_pi(sk)->sec_level == BT_SECURITY_MEDIUM) { + l2cap_sock_clear_timer(sk); + l2cap_sock_set_timer(sk, HZ * 5); + } else if (l2cap_pi(sk)->sec_level == BT_SECURITY_HIGH) + __l2cap_sock_close(sk, ECONNREFUSED); } else { - if (chan->sec_level == BT_SECURITY_MEDIUM) - __clear_chan_timer(chan); + if (l2cap_pi(sk)->sec_level == BT_SECURITY_MEDIUM) + l2cap_sock_clear_timer(sk); } } static int l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt) { + struct l2cap_chan_list *l; struct l2cap_conn *conn = hcon->l2cap_data; - struct l2cap_chan *chan; + struct sock *sk; if (!conn) return 0; - BT_DBG("conn %p", conn); + l = &conn->chan_list; - read_lock(&conn->chan_lock); + BT_DBG("conn %p", conn); - list_for_each_entry(chan, &conn->chan_l, list) { - struct sock *sk = chan->sk; + read_lock(&l->lock); + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { bh_lock_sock(sk); - BT_DBG("chan->scid %d", chan->scid); + BT_DBG("sk->scid %d", l2cap_pi(sk)->scid); - if (chan->scid == L2CAP_CID_LE_DATA) { + if (l2cap_pi(sk)->scid == L2CAP_CID_LE_DATA) { if (!status && encrypt) { - chan->sec_level = hcon->sec_level; + l2cap_pi(sk)->sec_level = hcon->sec_level; del_timer(&conn->security_timer); l2cap_chan_ready(sk); smp_distribute_keys(conn, 0); @@ -4111,68 +7264,64 @@ static int l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt) continue; } - if (test_bit(CONF_CONNECT_PEND, &chan->conf_state)) { + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_CONNECT_PEND) { bh_unlock_sock(sk); continue; } - if (!status && (chan->state == BT_CONNECTED || - chan->state == BT_CONFIG)) { - l2cap_check_encryption(chan, encrypt); + if (!status && (sk->sk_state == BT_CONNECTED || + sk->sk_state == BT_CONFIG)) { + l2cap_check_encryption(sk, encrypt); bh_unlock_sock(sk); continue; } - if (chan->state == BT_CONNECT) { + if (sk->sk_state == BT_CONNECT) { if (!status) { - struct l2cap_conn_req req; - req.scid = cpu_to_le16(chan->scid); - req.psm = chan->psm; - - chan->ident = l2cap_get_ident(conn); - set_bit(CONF_CONNECT_PEND, &chan->conf_state); - - l2cap_send_cmd(conn, chan->ident, - L2CAP_CONN_REQ, sizeof(req), &req); + l2cap_pi(sk)->conf_state |= + L2CAP_CONF_CONNECT_PEND; + if (l2cap_pi(sk)->amp_pref == + BT_AMP_POLICY_PREFER_AMP) { + amp_create_physical(l2cap_pi(sk)->conn, + sk); + } else + l2cap_send_conn_req(sk); } else { - __clear_chan_timer(chan); - __set_chan_timer(chan, HZ / 10); + l2cap_sock_clear_timer(sk); + l2cap_sock_set_timer(sk, HZ / 10); } - } else if (chan->state == BT_CONNECT2) { + } else if (sk->sk_state == BT_CONNECT2) { struct l2cap_conn_rsp rsp; - __u16 res, stat; + __u16 result; if (!status) { - if (bt_sk(sk)->defer_setup) { - struct sock *parent = bt_sk(sk)->parent; - res = L2CAP_CR_PEND; - stat = L2CAP_CS_AUTHOR_PEND; - if (parent) - parent->sk_data_ready(parent, 0); - } else { - l2cap_state_change(chan, BT_CONFIG); - res = L2CAP_CR_SUCCESS; - stat = L2CAP_CS_NO_INFO; + if (l2cap_pi(sk)->amp_id) { + amp_accept_physical(conn, + l2cap_pi(sk)->amp_id, sk); + bh_unlock_sock(sk); + continue; } + + sk->sk_state = BT_CONFIG; + result = L2CAP_CR_SUCCESS; } else { - l2cap_state_change(chan, BT_DISCONN); - __set_chan_timer(chan, HZ / 10); - res = L2CAP_CR_SEC_BLOCK; - stat = L2CAP_CS_NO_INFO; + sk->sk_state = BT_DISCONN; + l2cap_sock_set_timer(sk, HZ / 10); + result = L2CAP_CR_SEC_BLOCK; } - rsp.scid = cpu_to_le16(chan->dcid); - rsp.dcid = cpu_to_le16(chan->scid); - rsp.result = cpu_to_le16(res); - rsp.status = cpu_to_le16(stat); - l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_RSP, - sizeof(rsp), &rsp); + rsp.scid = cpu_to_le16(l2cap_pi(sk)->dcid); + rsp.dcid = cpu_to_le16(l2cap_pi(sk)->scid); + rsp.result = cpu_to_le16(result); + rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); + l2cap_send_cmd(conn, l2cap_pi(sk)->ident, + L2CAP_CONN_RSP, sizeof(rsp), &rsp); } bh_unlock_sock(sk); } - read_unlock(&conn->chan_lock); + read_unlock(&l->lock); return 0; } @@ -4181,6 +7330,9 @@ static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 fl { struct l2cap_conn *conn = hcon->l2cap_data; + if (!conn && hcon->hdev->dev_type != HCI_BREDR) + goto drop; + if (!conn) conn = l2cap_conn_add(hcon, 0); @@ -4189,9 +7341,9 @@ static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 fl BT_DBG("conn %p len %d flags 0x%x", conn, skb->len, flags); - if (!(flags & ACL_CONT)) { + if (flags & ACL_START) { struct l2cap_hdr *hdr; - struct l2cap_chan *chan; + struct sock *sk; u16 cid; int len; @@ -4220,6 +7372,14 @@ static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 fl return 0; } + if (flags & ACL_CONT) { + BT_ERR("Complete frame is incomplete " + "(len %d, expected len %d)", + skb->len, len); + l2cap_conn_unreliable(conn, ECOMM); + goto drop; + } + BT_DBG("Start: total len %d, frag len %d", len, skb->len); if (skb->len > len) { @@ -4229,22 +7389,19 @@ static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 fl goto drop; } - chan = l2cap_get_chan_by_scid(conn, cid); - - if (chan && chan->sk) { - struct sock *sk = chan->sk; + sk = l2cap_get_chan_by_scid(&conn->chan_list, cid); - if (chan->imtu < len - L2CAP_HDR_SIZE) { - BT_ERR("Frame exceeding recv MTU (len %d, " - "MTU %d)", len, - chan->imtu); - bh_unlock_sock(sk); - l2cap_conn_unreliable(conn, ECOMM); - goto drop; - } + if (sk && l2cap_pi(sk)->imtu < len - L2CAP_HDR_SIZE) { + BT_ERR("Frame exceeding recv MTU (len %d, MTU %d)", + len, l2cap_pi(sk)->imtu); bh_unlock_sock(sk); + l2cap_conn_unreliable(conn, ECOMM); + goto drop; } + if (sk) + bh_unlock_sock(sk); + /* Allocate skb for the complete frame (with header) */ conn->rx_skb = bt_skb_alloc(len, GFP_ATOMIC); if (!conn->rx_skb) @@ -4290,22 +7447,24 @@ static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 fl static int l2cap_debugfs_show(struct seq_file *f, void *p) { - struct l2cap_chan *c; + struct sock *sk; + struct hlist_node *node; - read_lock_bh(&chan_list_lock); + read_lock_bh(&l2cap_sk_list.lock); - list_for_each_entry(c, &chan_list, global_l) { - struct sock *sk = c->sk; + sk_for_each(sk, node, &l2cap_sk_list.head) { + struct l2cap_pinfo *pi = l2cap_pi(sk); seq_printf(f, "%s %s %d %d 0x%4.4x 0x%4.4x %d %d %d %d\n", batostr(&bt_sk(sk)->src), batostr(&bt_sk(sk)->dst), - c->state, __le16_to_cpu(c->psm), - c->scid, c->dcid, c->imtu, c->omtu, - c->sec_level, c->mode); + sk->sk_state, __le16_to_cpu(pi->psm), + pi->scid, pi->dcid, + pi->imtu, pi->omtu, pi->sec_level, + pi->mode); } - read_unlock_bh(&chan_list_lock); + read_unlock_bh(&l2cap_sk_list.lock); return 0; } @@ -4332,7 +7491,10 @@ static struct hci_proto l2cap_hci_proto = { .disconn_ind = l2cap_disconn_ind, .disconn_cfm = l2cap_disconn_cfm, .security_cfm = l2cap_security_cfm, - .recv_acldata = l2cap_recv_acldata + .recv_acldata = l2cap_recv_acldata, + .create_cfm = l2cap_create_cfm, + .modify_cfm = l2cap_modify_cfm, + .destroy_cfm = l2cap_destroy_cfm, }; int __init l2cap_init(void) @@ -4343,6 +7505,12 @@ int __init l2cap_init(void) if (err < 0) return err; + _l2cap_wq = create_singlethread_workqueue("l2cap"); + if (!_l2cap_wq) { + err = -ENOMEM; + goto error; + } + err = hci_register_proto(&l2cap_hci_proto); if (err < 0) { BT_ERR("L2CAP protocol registration failed"); @@ -4357,17 +7525,28 @@ int __init l2cap_init(void) BT_ERR("Failed to create L2CAP debug file"); } + if (amp_init() < 0) { + BT_ERR("AMP Manager initialization failed"); + goto error; + } + return 0; error: + destroy_workqueue(_l2cap_wq); l2cap_cleanup_sockets(); return err; } void l2cap_exit(void) { + amp_exit(); + debugfs_remove(l2cap_debugfs); + flush_workqueue(_l2cap_wq); + destroy_workqueue(_l2cap_wq); + if (hci_unregister_proto(&l2cap_hci_proto) < 0) BT_ERR("L2CAP protocol unregistration failed"); @@ -4376,3 +7555,6 @@ void l2cap_exit(void) module_param(disable_ertm, bool, 0644); MODULE_PARM_DESC(disable_ertm, "Disable enhanced retransmission mode"); + +module_param(enable_reconfig, bool, 0644); +MODULE_PARM_DESC(enable_reconfig, "Enable reconfig after initiating AMP move"); diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 61f1f623091..76947648af9 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -30,15 +30,69 @@ #include #include #include +#include -static const struct proto_ops l2cap_sock_ops; -static void l2cap_sock_init(struct sock *sk, struct sock *parent); -static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, int proto, gfp_t prio); +/* ---- L2CAP timers ---- */ +static void l2cap_sock_timeout(unsigned long arg) +{ + struct sock *sk = (struct sock *) arg; + int reason; + + BT_DBG("sock %p state %d", sk, sk->sk_state); + + bh_lock_sock(sk); + + if (sock_owned_by_user(sk)) { + /* sk is owned by user. Try again later */ + l2cap_sock_set_timer(sk, HZ / 5); + bh_unlock_sock(sk); + sock_put(sk); + return; + } + + if (sk->sk_state == BT_CONNECTED || sk->sk_state == BT_CONFIG) + reason = ECONNREFUSED; + else if (sk->sk_state == BT_CONNECT && + l2cap_pi(sk)->sec_level != BT_SECURITY_SDP) + reason = ECONNREFUSED; + else + reason = ETIMEDOUT; + + __l2cap_sock_close(sk, reason); + + bh_unlock_sock(sk); + + l2cap_sock_kill(sk); + sock_put(sk); +} + +void l2cap_sock_set_timer(struct sock *sk, long timeout) +{ + BT_DBG("sk %p state %d timeout %ld", sk, sk->sk_state, timeout); + sk_reset_timer(sk, &sk->sk_timer, jiffies + timeout); +} + +void l2cap_sock_clear_timer(struct sock *sk) +{ + BT_DBG("sock %p state %d", sk, sk->sk_state); + sk_stop_timer(sk, &sk->sk_timer); +} + +static struct sock *__l2cap_get_sock_by_addr(__le16 psm, bdaddr_t *src) +{ + struct sock *sk; + struct hlist_node *node; + sk_for_each(sk, node, &l2cap_sk_list.head) + if (l2cap_pi(sk)->sport == psm && !bacmp(&bt_sk(sk)->src, src)) + goto found; + sk = NULL; +found: + return sk; +} static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int alen) { struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; struct sockaddr_l2 la; int len, err = 0; @@ -77,22 +131,26 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int alen) } } - if (la.l2_cid) - err = l2cap_add_scid(chan, la.l2_cid); - else - err = l2cap_add_psm(chan, &la.l2_bdaddr, la.l2_psm); - - if (err < 0) - goto done; + write_lock_bh(&l2cap_sk_list.lock); - if (__le16_to_cpu(la.l2_psm) == 0x0001 || - __le16_to_cpu(la.l2_psm) == 0x0003) - chan->sec_level = BT_SECURITY_SDP; + if (la.l2_psm && __l2cap_get_sock_by_addr(la.l2_psm, &la.l2_bdaddr)) { + err = -EADDRINUSE; + } else { + /* Save source address */ + bacpy(&bt_sk(sk)->src, &la.l2_bdaddr); + l2cap_pi(sk)->psm = la.l2_psm; + l2cap_pi(sk)->sport = la.l2_psm; + sk->sk_state = BT_BOUND; + + if (__le16_to_cpu(la.l2_psm) == 0x0001 || + __le16_to_cpu(la.l2_psm) == 0x0003) + l2cap_pi(sk)->sec_level = BT_SECURITY_SDP; + } - bacpy(&bt_sk(sk)->src, &la.l2_bdaddr); + if (la.l2_cid) + l2cap_pi(sk)->scid = la.l2_cid; - chan->state = BT_BOUND; - sk->sk_state = BT_BOUND; + write_unlock_bh(&l2cap_sk_list.lock); done: release_sock(sk); @@ -102,11 +160,11 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int alen) static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags) { struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; struct sockaddr_l2 la; int len, err = 0; - BT_DBG("sk %p", sk); + BT_DBG("sk %p type %d mode %d state %d", sk, sk->sk_type, + l2cap_pi(sk)->mode, sk->sk_state); if (!addr || alen < sizeof(addr->sa_family) || addr->sa_family != AF_BLUETOOTH) @@ -121,13 +179,13 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int al lock_sock(sk); - if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED - && !(la.l2_psm || la.l2_cid)) { + if ((sk->sk_type == SOCK_SEQPACKET || sk->sk_type == SOCK_STREAM) + && !(la.l2_psm || la.l2_cid || l2cap_pi(sk)->fixed_channel)) { err = -EINVAL; goto done; } - switch (chan->mode) { + switch (l2cap_pi(sk)->mode) { case L2CAP_MODE_BASIC: break; case L2CAP_MODE_ERTM: @@ -163,18 +221,20 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int al } /* PSM must be odd and lsb of upper byte must be 0 */ - if ((__le16_to_cpu(la.l2_psm) & 0x0101) != 0x0001 && !la.l2_cid && - chan->chan_type != L2CAP_CHAN_RAW) { + if ((__le16_to_cpu(la.l2_psm) & 0x0101) != 0x0001 && + !l2cap_pi(sk)->fixed_channel && + sk->sk_type != SOCK_RAW && !la.l2_cid) { + BT_DBG("Bad PSM 0x%x", (int)__le16_to_cpu(la.l2_psm)); err = -EINVAL; goto done; } /* Set destination address and psm */ bacpy(&bt_sk(sk)->dst, &la.l2_bdaddr); - chan->psm = la.l2_psm; - chan->dcid = la.l2_cid; + l2cap_pi(sk)->psm = la.l2_psm; + l2cap_pi(sk)->dcid = la.l2_cid; - err = l2cap_chan_connect(l2cap_pi(sk)->chan); + err = l2cap_do_connect(sk); if (err) goto done; @@ -182,6 +242,8 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int al err = bt_sock_wait_state(sk, BT_CONNECTED, sock_sndtimeo(sk, flags & O_NONBLOCK)); done: + if (err) + BT_ERR("failed %d", err); release_sock(sk); return err; } @@ -189,7 +251,6 @@ static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int al static int l2cap_sock_listen(struct socket *sock, int backlog) { struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; int err = 0; BT_DBG("sk %p backlog %d", sk, backlog); @@ -202,7 +263,7 @@ static int l2cap_sock_listen(struct socket *sock, int backlog) goto done; } - switch (chan->mode) { + switch (l2cap_pi(sk)->mode) { case L2CAP_MODE_BASIC: break; case L2CAP_MODE_ERTM: @@ -215,10 +276,30 @@ static int l2cap_sock_listen(struct socket *sock, int backlog) goto done; } + if (!l2cap_pi(sk)->psm && !l2cap_pi(sk)->scid) { + bdaddr_t *src = &bt_sk(sk)->src; + u16 psm; + + err = -EINVAL; + + write_lock_bh(&l2cap_sk_list.lock); + + for (psm = 0x1001; psm < 0x1100; psm += 2) + if (!__l2cap_get_sock_by_addr(cpu_to_le16(psm), src)) { + l2cap_pi(sk)->psm = cpu_to_le16(psm); + l2cap_pi(sk)->sport = cpu_to_le16(psm); + err = 0; + break; + } + + write_unlock_bh(&l2cap_sk_list.lock); + + if (err < 0) + goto done; + } + sk->sk_max_ack_backlog = backlog; sk->sk_ack_backlog = 0; - - chan->state = BT_LISTEN; sk->sk_state = BT_LISTEN; done: @@ -235,26 +316,30 @@ static int l2cap_sock_accept(struct socket *sock, struct socket *newsock, int fl lock_sock_nested(sk, SINGLE_DEPTH_NESTING); + if (sk->sk_state != BT_LISTEN) { + err = -EBADFD; + goto done; + } + timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); BT_DBG("sk %p timeo %ld", sk, timeo); /* Wait for an incoming connection. (wake-one). */ add_wait_queue_exclusive(sk_sleep(sk), &wait); - while (1) { + while (!(nsk = bt_accept_dequeue(sk, newsock))) { set_current_state(TASK_INTERRUPTIBLE); - - if (sk->sk_state != BT_LISTEN) { - err = -EBADFD; + if (!timeo) { + err = -EAGAIN; break; } - nsk = bt_accept_dequeue(sk, newsock); - if (nsk) - break; + release_sock(sk); + timeo = schedule_timeout(timeo); + lock_sock_nested(sk, SINGLE_DEPTH_NESTING); - if (!timeo) { - err = -EAGAIN; + if (sk->sk_state != BT_LISTEN) { + err = -EBADFD; break; } @@ -262,12 +347,8 @@ static int l2cap_sock_accept(struct socket *sock, struct socket *newsock, int fl err = sock_intr_errno(timeo); break; } - - release_sock(sk); - timeo = schedule_timeout(timeo); - lock_sock_nested(sk, SINGLE_DEPTH_NESTING); } - __set_current_state(TASK_RUNNING); + set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); if (err) @@ -286,7 +367,6 @@ static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr, int *l { struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; BT_DBG("sock %p, sk %p", sock, sk); @@ -294,13 +374,13 @@ static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr, int *l *len = sizeof(struct sockaddr_l2); if (peer) { - la->l2_psm = chan->psm; + la->l2_psm = l2cap_pi(sk)->psm; bacpy(&la->l2_bdaddr, &bt_sk(sk)->dst); - la->l2_cid = cpu_to_le16(chan->dcid); + la->l2_cid = cpu_to_le16(l2cap_pi(sk)->dcid); } else { - la->l2_psm = chan->sport; + la->l2_psm = l2cap_pi(sk)->sport; bacpy(&la->l2_bdaddr, &bt_sk(sk)->src); - la->l2_cid = cpu_to_le16(chan->scid); + la->l2_cid = cpu_to_le16(l2cap_pi(sk)->scid); } return 0; @@ -309,7 +389,6 @@ static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr, int *l static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, char __user *optval, int __user *optlen) { struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; struct l2cap_options opts; struct l2cap_conninfo cinfo; int len, err = 0; @@ -325,13 +404,13 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, char __us switch (optname) { case L2CAP_OPTIONS: memset(&opts, 0, sizeof(opts)); - opts.imtu = chan->imtu; - opts.omtu = chan->omtu; - opts.flush_to = chan->flush_to; - opts.mode = chan->mode; - opts.fcs = chan->fcs; - opts.max_tx = chan->max_tx; - opts.txwin_size = (__u16)chan->tx_win; + opts.imtu = l2cap_pi(sk)->imtu; + opts.omtu = l2cap_pi(sk)->omtu; + opts.flush_to = l2cap_pi(sk)->flush_to; + opts.mode = l2cap_pi(sk)->mode; + opts.fcs = l2cap_pi(sk)->fcs; + opts.max_tx = l2cap_pi(sk)->max_tx; + opts.txwin_size = l2cap_pi(sk)->tx_win; len = min_t(unsigned int, len, sizeof(opts)); if (copy_to_user(optval, (char *) &opts, len)) @@ -340,7 +419,7 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, char __us break; case L2CAP_LM: - switch (chan->sec_level) { + switch (l2cap_pi(sk)->sec_level) { case BT_SECURITY_LOW: opt = L2CAP_LM_AUTH; break; @@ -356,12 +435,15 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, char __us break; } - if (chan->role_switch) + if (l2cap_pi(sk)->role_switch) opt |= L2CAP_LM_MASTER; - if (chan->force_reliable) + if (l2cap_pi(sk)->force_reliable) opt |= L2CAP_LM_RELIABLE; + if (l2cap_pi(sk)->flushable) + opt |= L2CAP_LM_FLUSHABLE; + if (put_user(opt, (u32 __user *) optval)) err = -EFAULT; break; @@ -374,9 +456,8 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, char __us break; } - memset(&cinfo, 0, sizeof(cinfo)); - cinfo.hci_handle = chan->conn->hcon->handle; - memcpy(cinfo.dev_class, chan->conn->hcon->dev_class, 3); + cinfo.hci_handle = l2cap_pi(sk)->conn->hcon->handle; + memcpy(cinfo.dev_class, l2cap_pi(sk)->conn->hcon->dev_class, 3); len = min_t(unsigned int, len, sizeof(cinfo)); if (copy_to_user(optval, (char *) &cinfo, len)) @@ -396,7 +477,6 @@ static int l2cap_sock_getsockopt_old(struct socket *sock, int optname, char __us static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen) { struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; struct bt_security sec; struct bt_power pwr; int len, err = 0; @@ -416,17 +496,17 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch switch (optname) { case BT_SECURITY: - if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED && - chan->chan_type != L2CAP_CHAN_RAW) { + if (sk->sk_type != SOCK_SEQPACKET && sk->sk_type != SOCK_STREAM + && sk->sk_type != SOCK_RAW) { err = -EINVAL; break; } memset(&sec, 0, sizeof(sec)); - sec.level = chan->sec_level; + sec.level = l2cap_pi(sk)->sec_level; if (sk->sk_state == BT_CONNECTED) - sec.key_size = chan->conn->hcon->enc_key_size; + sec.key_size = l2cap_pi(sk)->conn->hcon->enc_key_size; len = min_t(unsigned int, len, sizeof(sec)); if (copy_to_user(optval, (char *) &sec, len)) @@ -445,12 +525,6 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch break; - case BT_FLUSHABLE: - if (put_user(chan->flushable, (u32 __user *) optval)) - err = -EFAULT; - - break; - case BT_POWER: if (sk->sk_type != SOCK_SEQPACKET && sk->sk_type != SOCK_STREAM && sk->sk_type != SOCK_RAW) { @@ -458,7 +532,7 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch break; } - pwr.force_active = chan->force_active; + pwr.force_active = l2cap_pi(sk)->force_active; len = min_t(unsigned int, len, sizeof(pwr)); if (copy_to_user(optval, (char *) &pwr, len)) @@ -466,6 +540,11 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch break; + case BT_AMP_POLICY: + if (put_user(l2cap_pi(sk)->amp_pref, (u32 __user *) optval)) + err = -EFAULT; + break; + default: err = -ENOPROTOOPT; break; @@ -478,29 +557,30 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, ch static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, char __user *optval, unsigned int optlen) { struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; struct l2cap_options opts; - int len, err = 0; + int len, le_sock, err = 0; u32 opt; BT_DBG("sk %p", sk); lock_sock(sk); + le_sock = l2cap_pi(sk)->scid == L2CAP_CID_LE_DATA; + switch (optname) { case L2CAP_OPTIONS: - if (sk->sk_state == BT_CONNECTED) { + if (sk->sk_state == BT_CONNECTED && !le_sock) { err = -EINVAL; break; } - opts.imtu = chan->imtu; - opts.omtu = chan->omtu; - opts.flush_to = chan->flush_to; - opts.mode = chan->mode; - opts.fcs = chan->fcs; - opts.max_tx = chan->max_tx; - opts.txwin_size = (__u16)chan->tx_win; + opts.imtu = l2cap_pi(sk)->imtu; + opts.omtu = l2cap_pi(sk)->omtu; + opts.flush_to = l2cap_pi(sk)->flush_to; + opts.mode = l2cap_pi(sk)->mode; + opts.fcs = l2cap_pi(sk)->fcs; + opts.max_tx = l2cap_pi(sk)->max_tx; + opts.txwin_size = l2cap_pi(sk)->tx_win; len = min_t(unsigned int, sizeof(opts), optlen); if (copy_from_user((char *) &opts, optval, len)) { @@ -508,15 +588,28 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, char __us break; } - if (opts.txwin_size > L2CAP_DEFAULT_TX_WINDOW) { + if ((opts.imtu || opts.omtu) && le_sock && + (sk->sk_state == BT_CONNECTED)) { + if (opts.imtu >= L2CAP_LE_DEFAULT_MTU) + l2cap_pi(sk)->imtu = opts.imtu; + if (opts.omtu >= L2CAP_LE_DEFAULT_MTU) + l2cap_pi(sk)->omtu = opts.omtu; + if (opts.imtu < L2CAP_LE_DEFAULT_MTU || + opts.omtu < L2CAP_LE_DEFAULT_MTU) + err = -EINVAL; + break; + } + + if (opts.txwin_size < 1 || + opts.txwin_size > L2CAP_TX_WIN_MAX_EXTENDED) { err = -EINVAL; break; } - chan->mode = opts.mode; - switch (chan->mode) { + l2cap_pi(sk)->mode = opts.mode; + switch (l2cap_pi(sk)->mode) { case L2CAP_MODE_BASIC: - clear_bit(CONF_STATE2_DEVICE, &chan->conf_state); + l2cap_pi(sk)->conf_state &= ~L2CAP_CONF_STATE2_DEVICE; break; case L2CAP_MODE_ERTM: case L2CAP_MODE_STREAMING: @@ -528,11 +621,11 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, char __us break; } - chan->imtu = opts.imtu; - chan->omtu = opts.omtu; - chan->fcs = opts.fcs; - chan->max_tx = opts.max_tx; - chan->tx_win = (__u8)opts.txwin_size; + l2cap_pi(sk)->imtu = opts.imtu; + l2cap_pi(sk)->omtu = opts.omtu; + l2cap_pi(sk)->fcs = opts.fcs; + l2cap_pi(sk)->max_tx = opts.max_tx; + l2cap_pi(sk)->tx_win = opts.txwin_size; break; case L2CAP_LM: @@ -542,14 +635,15 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, char __us } if (opt & L2CAP_LM_AUTH) - chan->sec_level = BT_SECURITY_LOW; + l2cap_pi(sk)->sec_level = BT_SECURITY_LOW; if (opt & L2CAP_LM_ENCRYPT) - chan->sec_level = BT_SECURITY_MEDIUM; + l2cap_pi(sk)->sec_level = BT_SECURITY_MEDIUM; if (opt & L2CAP_LM_SECURE) - chan->sec_level = BT_SECURITY_HIGH; + l2cap_pi(sk)->sec_level = BT_SECURITY_HIGH; - chan->role_switch = (opt & L2CAP_LM_MASTER); - chan->force_reliable = (opt & L2CAP_LM_RELIABLE); + l2cap_pi(sk)->role_switch = (opt & L2CAP_LM_MASTER); + l2cap_pi(sk)->force_reliable = (opt & L2CAP_LM_RELIABLE); + l2cap_pi(sk)->flushable = (opt & L2CAP_LM_FLUSHABLE); break; default: @@ -564,7 +658,6 @@ static int l2cap_sock_setsockopt_old(struct socket *sock, int optname, char __us static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen) { struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; struct bt_security sec; struct bt_power pwr; struct l2cap_conn *conn; @@ -583,8 +676,8 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, ch switch (optname) { case BT_SECURITY: - if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED && - chan->chan_type != L2CAP_CHAN_RAW) { + if (sk->sk_type != SOCK_SEQPACKET && sk->sk_type != SOCK_STREAM + && sk->sk_type != SOCK_RAW) { err = -EINVAL; break; } @@ -603,10 +696,10 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, ch break; } - chan->sec_level = sec.level; + l2cap_pi(sk)->sec_level = sec.level; - conn = chan->conn; - if (conn && chan->scid == L2CAP_CID_LE_DATA) { + conn = l2cap_pi(sk)->conn; + if (conn && l2cap_pi(sk)->scid == L2CAP_CID_LE_DATA) { if (!conn->hcon->out) { err = -EINVAL; break; @@ -634,45 +727,44 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, ch bt_sk(sk)->defer_setup = opt; break; - case BT_FLUSHABLE: - if (get_user(opt, (u32 __user *) optval)) { - err = -EFAULT; + case BT_POWER: + if (sk->sk_type != SOCK_SEQPACKET && sk->sk_type != SOCK_STREAM + && sk->sk_type != SOCK_RAW) { + err = -EINVAL; break; } - if (opt > BT_FLUSHABLE_ON) { - err = -EINVAL; + pwr.force_active = 1; + + len = min_t(unsigned int, sizeof(pwr), optlen); + if (copy_from_user((char *) &pwr, optval, len)) { + err = -EFAULT; break; } + l2cap_pi(sk)->force_active = pwr.force_active; + break; - if (opt == BT_FLUSHABLE_OFF) { - struct l2cap_conn *conn = chan->conn; - /* proceed further only when we have l2cap_conn and - No Flush support in the LM */ - if (!conn || !lmp_no_flush_capable(conn->hcon->hdev)) { - err = -EINVAL; - break; - } + case BT_AMP_POLICY: + if (get_user(opt, (u32 __user *) optval)) { + err = -EFAULT; + break; } - chan->flushable = opt; - break; - - case BT_POWER: - if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED && - chan->chan_type != L2CAP_CHAN_RAW) { + if ((opt > BT_AMP_POLICY_PREFER_BR_EDR) || + ((l2cap_pi(sk)->mode != L2CAP_MODE_ERTM) && + (l2cap_pi(sk)->mode != L2CAP_MODE_STREAMING))) { err = -EINVAL; break; } - pwr.force_active = BT_POWER_FORCE_ACTIVE_ON; + l2cap_pi(sk)->amp_pref = (u8) opt; + BT_DBG("BT_AMP_POLICY now %d", opt); + + if ((sk->sk_state == BT_CONNECTED) && + (l2cap_pi(sk)->amp_move_role == L2CAP_AMP_MOVE_NONE) && + (l2cap_pi(sk)->conn->fc_mask & L2CAP_FC_A2MP)) + l2cap_amp_move_init(sk); - len = min_t(unsigned int, sizeof(pwr), optlen); - if (copy_from_user((char *) &pwr, optval, len)) { - err = -EFAULT; - break; - } - chan->force_active = pwr.force_active; break; default: @@ -687,8 +779,11 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, ch static int l2cap_sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len) { struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; + struct l2cap_pinfo *pi = l2cap_pi(sk); + struct sk_buff *skb; + struct sk_buff_head seg_queue; int err; + u8 amp_id; BT_DBG("sock %p, sk %p", sock, sk); @@ -702,12 +797,102 @@ static int l2cap_sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct ms lock_sock(sk); if (sk->sk_state != BT_CONNECTED) { - release_sock(sk); - return -ENOTCONN; + err = -ENOTCONN; + goto done; } - err = l2cap_chan_send(chan, msg, len); + /* Connectionless channel */ + if (sk->sk_type == SOCK_DGRAM) { + skb = l2cap_create_connless_pdu(sk, msg, len); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + } else { + l2cap_do_send(sk, skb); + err = len; + } + goto done; + } + + switch (pi->mode) { + case L2CAP_MODE_BASIC: + /* Check outgoing MTU */ + if (len > pi->omtu) { + err = -EMSGSIZE; + goto done; + } + + /* Create a basic PDU */ + skb = l2cap_create_basic_pdu(sk, msg, len); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + goto done; + } + + l2cap_do_send(sk, skb); + err = len; + break; + + case L2CAP_MODE_ERTM: + case L2CAP_MODE_STREAMING: + + /* Check outgoing MTU */ + if (len > pi->omtu) { + err = -EMSGSIZE; + goto done; + } + + __skb_queue_head_init(&seg_queue); + + /* Do segmentation before calling in to the state machine, + * since it's possible to block while waiting for memory + * allocation. + */ + amp_id = pi->amp_id; + err = l2cap_segment_sdu(sk, &seg_queue, msg, len, 0); + + /* The socket lock is released while segmenting, so check + * that the socket is still connected + */ + if (sk->sk_state != BT_CONNECTED) { + __skb_queue_purge(&seg_queue); + err = -ENOTCONN; + } + + if (err) { + BT_DBG("Error %d, sk_sndbuf %d, sk_wmem_alloc %d", + err, sk->sk_sndbuf, + atomic_read(&sk->sk_wmem_alloc)); + break; + } + + if (pi->amp_id != amp_id) { + /* Channel moved while unlocked. Resegment. */ + err = l2cap_resegment_queue(sk, &seg_queue); + + if (err) + break; + } + + if (pi->mode != L2CAP_MODE_STREAMING) + err = l2cap_ertm_tx(sk, 0, &seg_queue, + L2CAP_ERTM_EVENT_DATA_REQUEST); + else + err = l2cap_strm_tx(sk, &seg_queue); + if (!err) + err = len; + + /* If the skbs were not queued for sending, they'll still be in + * seg_queue and need to be purged. + */ + __skb_queue_purge(&seg_queue); + break; + default: + BT_DBG("bad state %1.1x", pi->mode); + err = -EBADFD; + } + +done: release_sock(sk); return err; } @@ -715,15 +900,43 @@ static int l2cap_sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct ms static int l2cap_sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len, int flags) { struct sock *sk = sock->sk; - struct l2cap_pinfo *pi = l2cap_pi(sk); int err; lock_sock(sk); if (sk->sk_state == BT_CONNECT2 && bt_sk(sk)->defer_setup) { + struct l2cap_conn_rsp rsp; + struct l2cap_conn *conn = l2cap_pi(sk)->conn; + u8 buf[128]; + + if (l2cap_pi(sk)->amp_id) { + /* Physical link must be brought up before connection + * completes. + */ + amp_accept_physical(conn, l2cap_pi(sk)->amp_id, sk); + release_sock(sk); + return 0; + } + sk->sk_state = BT_CONFIG; - __l2cap_connect_rsp_defer(pi->chan); + rsp.scid = cpu_to_le16(l2cap_pi(sk)->dcid); + rsp.dcid = cpu_to_le16(l2cap_pi(sk)->scid); + rsp.result = cpu_to_le16(L2CAP_CR_SUCCESS); + rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); + l2cap_send_cmd(l2cap_pi(sk)->conn, l2cap_pi(sk)->ident, + L2CAP_CONN_RSP, sizeof(rsp), &rsp); + + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_REQ_SENT) { + release_sock(sk); + return 0; + } + + l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT; + l2cap_send_cmd(conn, l2cap_get_ident(conn), L2CAP_CONF_REQ, + l2cap_build_conf_req(sk, buf), buf); + l2cap_pi(sk)->num_conf_req++; + release_sock(sk); return 0; } @@ -735,39 +948,15 @@ static int l2cap_sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct ms else err = bt_sock_recvmsg(iocb, sock, msg, len, flags); - if (pi->chan->mode != L2CAP_MODE_ERTM) - return err; - - /* Attempt to put pending rx data in the socket buffer */ - - lock_sock(sk); + l2cap_ertm_recv_done(sk); - if (!test_bit(CONN_LOCAL_BUSY, &pi->chan->conn_state)) - goto done; - - if (pi->rx_busy_skb) { - if (!sock_queue_rcv_skb(sk, pi->rx_busy_skb)) - pi->rx_busy_skb = NULL; - else - goto done; - } - - /* Restore data flow when half of the receive buffer is - * available. This avoids resending large numbers of - * frames. - */ - if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf >> 1) - l2cap_chan_busy(pi->chan, 0); - -done: - release_sock(sk); return err; } /* Kill socket (only if zapped and orphan) * Must be called on unlocked socket. */ -static void l2cap_sock_kill(struct sock *sk) +void l2cap_sock_kill(struct sock *sk) { if (!sock_flag(sk, SOCK_ZAPPED) || sk->sk_socket) return; @@ -775,16 +964,95 @@ static void l2cap_sock_kill(struct sock *sk) BT_DBG("sk %p state %d", sk, sk->sk_state); /* Kill poor orphan */ - - l2cap_chan_destroy(l2cap_pi(sk)->chan); + bt_sock_unlink(&l2cap_sk_list, sk); sock_set_flag(sk, SOCK_DEAD); sock_put(sk); } +/* Must be called on unlocked socket. */ +static void l2cap_sock_close(struct sock *sk) +{ + l2cap_sock_clear_timer(sk); + lock_sock(sk); + __l2cap_sock_close(sk, ECONNRESET); + release_sock(sk); + l2cap_sock_kill(sk); +} + +static void l2cap_sock_cleanup_listen(struct sock *parent) +{ + struct sock *sk; + + BT_DBG("parent %p", parent); + + /* Close not yet accepted channels */ + while ((sk = bt_accept_dequeue(parent, NULL))) + l2cap_sock_close(sk); + + parent->sk_state = BT_CLOSED; + sock_set_flag(parent, SOCK_ZAPPED); +} + +void __l2cap_sock_close(struct sock *sk, int reason) +{ + struct l2cap_conn *conn = l2cap_pi(sk)->conn; + + BT_DBG("sk %p state %d socket %p", sk, sk->sk_state, sk->sk_socket); + + switch (sk->sk_state) { + case BT_LISTEN: + l2cap_sock_cleanup_listen(sk); + break; + + case BT_CONNECTED: + case BT_CONFIG: + if ((sk->sk_type == SOCK_SEQPACKET || + sk->sk_type == SOCK_STREAM) && + conn->hcon->type == ACL_LINK) { + l2cap_sock_set_timer(sk, sk->sk_sndtimeo); + l2cap_send_disconn_req(conn, sk, reason); + } else + l2cap_chan_del(sk, reason); + break; + + case BT_CONNECT2: + if ((sk->sk_type == SOCK_SEQPACKET || + sk->sk_type == SOCK_STREAM) && + conn->hcon->type == ACL_LINK) { + struct l2cap_conn_rsp rsp; + __u16 result; + + if (bt_sk(sk)->defer_setup) + result = L2CAP_CR_SEC_BLOCK; + else + result = L2CAP_CR_BAD_PSM; + sk->sk_state = BT_DISCONN; + + rsp.scid = cpu_to_le16(l2cap_pi(sk)->dcid); + rsp.dcid = cpu_to_le16(l2cap_pi(sk)->scid); + rsp.result = cpu_to_le16(result); + rsp.status = cpu_to_le16(L2CAP_CS_NO_INFO); + l2cap_send_cmd(conn, l2cap_pi(sk)->ident, + L2CAP_CONN_RSP, sizeof(rsp), &rsp); + } + + l2cap_chan_del(sk, reason); + break; + + case BT_CONNECT: + case BT_DISCONN: + l2cap_chan_del(sk, reason); + break; + + default: + sock_set_flag(sk, SOCK_ZAPPED); + break; + } +} + static int l2cap_sock_shutdown(struct socket *sock, int how) { struct sock *sk = sock->sk; - struct l2cap_chan *chan = l2cap_pi(sk)->chan; int err = 0; BT_DBG("sock %p, sk %p", sock, sk); @@ -794,11 +1062,15 @@ static int l2cap_sock_shutdown(struct socket *sock, int how) lock_sock(sk); if (!sk->sk_shutdown) { - if (chan->mode == L2CAP_MODE_ERTM) + + if (l2cap_pi(sk)->mode == L2CAP_MODE_ERTM) { err = __l2cap_wait_ack(sk); + l2cap_ertm_shutdown(sk); + } sk->sk_shutdown = SHUTDOWN_MASK; - l2cap_chan_close(chan, 0); + l2cap_sock_clear_timer(sk); + __l2cap_sock_close(sk, 0); if (sock_flag(sk, SOCK_LINGER) && sk->sk_lingertime) err = bt_sock_wait_state(sk, BT_CLOSED, @@ -829,149 +1101,96 @@ static int l2cap_sock_release(struct socket *sock) return err; } -static struct l2cap_chan *l2cap_sock_new_connection_cb(void *data) -{ - struct sock *sk, *parent = data; - - sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP, - GFP_ATOMIC); - if (!sk) - return NULL; - - l2cap_sock_init(sk, parent); - - return l2cap_pi(sk)->chan; -} - -static int l2cap_sock_recv_cb(void *data, struct sk_buff *skb) -{ - int err; - struct sock *sk = data; - struct l2cap_pinfo *pi = l2cap_pi(sk); - - if (pi->rx_busy_skb) - return -ENOMEM; - - err = sock_queue_rcv_skb(sk, skb); - - /* For ERTM, handle one skb that doesn't fit into the recv - * buffer. This is important to do because the data frames - * have already been acked, so the skb cannot be discarded. - * - * Notify the l2cap core that the buffer is full, so the - * LOCAL_BUSY state is entered and no more frames are - * acked and reassembled until there is buffer space - * available. - */ - if (err < 0 && pi->chan->mode == L2CAP_MODE_ERTM) { - pi->rx_busy_skb = skb; - l2cap_chan_busy(pi->chan, 1); - err = 0; - } - - return err; -} - -static void l2cap_sock_close_cb(void *data) -{ - struct sock *sk = data; - - l2cap_sock_kill(sk); -} - -static void l2cap_sock_state_change_cb(void *data, int state) -{ - struct sock *sk = data; - - sk->sk_state = state; -} - -static struct l2cap_ops l2cap_chan_ops = { - .name = "L2CAP Socket Interface", - .new_connection = l2cap_sock_new_connection_cb, - .recv = l2cap_sock_recv_cb, - .close = l2cap_sock_close_cb, - .state_change = l2cap_sock_state_change_cb, -}; - static void l2cap_sock_destruct(struct sock *sk) { BT_DBG("sk %p", sk); - if (l2cap_pi(sk)->rx_busy_skb) { - kfree_skb(l2cap_pi(sk)->rx_busy_skb); - l2cap_pi(sk)->rx_busy_skb = NULL; - } - skb_queue_purge(&sk->sk_receive_queue); skb_queue_purge(&sk->sk_write_queue); + + l2cap_ertm_destruct(sk); +} + +static void set_default_config(struct l2cap_conf_prm *conf_prm) +{ + conf_prm->fcs = L2CAP_FCS_CRC16; + conf_prm->retrans_timeout = 0; + conf_prm->monitor_timeout = 0; + conf_prm->flush_to = L2CAP_DEFAULT_FLUSH_TO; } -static void l2cap_sock_init(struct sock *sk, struct sock *parent) +void l2cap_sock_init(struct sock *sk, struct sock *parent) { struct l2cap_pinfo *pi = l2cap_pi(sk); - struct l2cap_chan *chan = pi->chan; - BT_DBG("sk %p", sk); + BT_DBG("sk %p parent %p", sk, parent); if (parent) { - struct l2cap_chan *pchan = l2cap_pi(parent)->chan; - sk->sk_type = parent->sk_type; + sk->sk_rcvbuf = parent->sk_rcvbuf; + sk->sk_sndbuf = parent->sk_sndbuf; bt_sk(sk)->defer_setup = bt_sk(parent)->defer_setup; - chan->chan_type = pchan->chan_type; - chan->imtu = pchan->imtu; - chan->omtu = pchan->omtu; - chan->conf_state = pchan->conf_state; - chan->mode = pchan->mode; - chan->fcs = pchan->fcs; - chan->max_tx = pchan->max_tx; - chan->tx_win = pchan->tx_win; - chan->sec_level = pchan->sec_level; - chan->role_switch = pchan->role_switch; - chan->force_reliable = pchan->force_reliable; - chan->flushable = pchan->flushable; - chan->force_active = pchan->force_active; + pi->imtu = l2cap_pi(parent)->imtu; + pi->omtu = l2cap_pi(parent)->omtu; + pi->conf_state = l2cap_pi(parent)->conf_state; + pi->mode = l2cap_pi(parent)->mode; + pi->fcs = l2cap_pi(parent)->fcs; + pi->max_tx = l2cap_pi(parent)->max_tx; + pi->tx_win = l2cap_pi(parent)->tx_win; + pi->sec_level = l2cap_pi(parent)->sec_level; + pi->role_switch = l2cap_pi(parent)->role_switch; + pi->force_reliable = l2cap_pi(parent)->force_reliable; + pi->flushable = l2cap_pi(parent)->flushable; + pi->force_active = l2cap_pi(parent)->force_active; + pi->amp_pref = l2cap_pi(parent)->amp_pref; } else { - - switch (sk->sk_type) { - case SOCK_RAW: - chan->chan_type = L2CAP_CHAN_RAW; - break; - case SOCK_DGRAM: - chan->chan_type = L2CAP_CHAN_CONN_LESS; - break; - case SOCK_SEQPACKET: - case SOCK_STREAM: - chan->chan_type = L2CAP_CHAN_CONN_ORIENTED; - break; - } - - chan->imtu = L2CAP_DEFAULT_MTU; - chan->omtu = 0; + pi->imtu = L2CAP_DEFAULT_MTU; + pi->omtu = 0; if (!disable_ertm && sk->sk_type == SOCK_STREAM) { - chan->mode = L2CAP_MODE_ERTM; - set_bit(CONF_STATE2_DEVICE, &chan->conf_state); + pi->mode = L2CAP_MODE_ERTM; + pi->conf_state |= L2CAP_CONF_STATE2_DEVICE; } else { - chan->mode = L2CAP_MODE_BASIC; + pi->mode = L2CAP_MODE_BASIC; } - chan->max_tx = L2CAP_DEFAULT_MAX_TX; - chan->fcs = L2CAP_FCS_CRC16; - chan->tx_win = L2CAP_DEFAULT_TX_WINDOW; - chan->sec_level = BT_SECURITY_LOW; - chan->role_switch = 0; - chan->force_reliable = 0; - chan->flushable = BT_FLUSHABLE_OFF; - chan->force_active = BT_POWER_FORCE_ACTIVE_ON; - + pi->reconf_state = L2CAP_RECONF_NONE; + pi->max_tx = L2CAP_DEFAULT_MAX_TX; + pi->fcs = L2CAP_FCS_CRC16; + pi->tx_win = L2CAP_DEFAULT_TX_WINDOW; + pi->sec_level = BT_SECURITY_LOW; + pi->role_switch = 0; + pi->force_reliable = 0; + pi->flushable = 0; + pi->force_active = 1; + pi->amp_pref = BT_AMP_POLICY_REQUIRE_BR_EDR; } /* Default config options */ - chan->flush_to = L2CAP_DEFAULT_FLUSH_TO; + sk->sk_backlog_rcv = l2cap_data_channel; + pi->ampcon = NULL; + pi->ampchan = NULL; + pi->conf_len = 0; + pi->flush_to = L2CAP_DEFAULT_FLUSH_TO; + pi->scid = 0; + pi->dcid = 0; + pi->tx_win_max = L2CAP_TX_WIN_MAX_ENHANCED; + pi->extended_control = 0; + + pi->local_conf.fcs = pi->fcs; + if (pi->mode == L2CAP_MODE_BASIC) { + pi->local_conf.retrans_timeout = 0; + pi->local_conf.monitor_timeout = 0; + } else { + pi->local_conf.retrans_timeout = L2CAP_DEFAULT_RETRANS_TO; + pi->local_conf.monitor_timeout = L2CAP_DEFAULT_MONITOR_TO; + } + + pi->local_conf.flush_to = pi->flush_to; - chan->data = sk; - chan->ops = &l2cap_chan_ops; + set_default_config(&pi->remote_conf); + + skb_queue_head_init(TX_QUEUE(sk)); + skb_queue_head_init(SREJ_QUEUE(sk)); } static struct proto l2cap_proto = { @@ -980,10 +1199,9 @@ static struct proto l2cap_proto = { .obj_size = sizeof(struct l2cap_pinfo) }; -static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, int proto, gfp_t prio) +struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, int proto, gfp_t prio) { struct sock *sk; - struct l2cap_chan *chan; sk = sk_alloc(net, PF_BLUETOOTH, prio, &l2cap_proto); if (!sk) @@ -993,21 +1211,16 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, int p INIT_LIST_HEAD(&bt_sk(sk)->accept_q); sk->sk_destruct = l2cap_sock_destruct; - sk->sk_sndtimeo = L2CAP_CONN_TIMEOUT; + sk->sk_sndtimeo = msecs_to_jiffies(L2CAP_CONN_TIMEOUT); sock_reset_flag(sk, SOCK_ZAPPED); sk->sk_protocol = proto; sk->sk_state = BT_OPEN; - chan = l2cap_chan_create(sk); - if (!chan) { - l2cap_sock_kill(sk); - return NULL; - } - - l2cap_pi(sk)->chan = chan; + setup_timer(&sk->sk_timer, l2cap_sock_timeout, (unsigned long) sk); + bt_sock_link(&l2cap_sk_list, sk); return sk; } @@ -1037,7 +1250,7 @@ static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol, return 0; } -static const struct proto_ops l2cap_sock_ops = { +const struct proto_ops l2cap_sock_ops = { .family = PF_BLUETOOTH, .owner = THIS_MODULE, .release = l2cap_sock_release, diff --git a/net/bluetooth/lib.c b/net/bluetooth/lib.c index 86a6bed229d..b826d1bf10d 100644 --- a/net/bluetooth/lib.c +++ b/net/bluetooth/lib.c @@ -59,7 +59,7 @@ char *batostr(bdaddr_t *ba) EXPORT_SYMBOL(batostr); /* Bluetooth error codes to Unix errno mapping */ -int bt_to_errno(__u16 code) +int bt_err(__u16 code) { switch (code) { case 0: @@ -149,23 +149,4 @@ int bt_to_errno(__u16 code) return ENOSYS; } } -EXPORT_SYMBOL(bt_to_errno); - -int bt_printk(const char *level, const char *format, ...) -{ - struct va_format vaf; - va_list args; - int r; - - va_start(args, format); - - vaf.fmt = format; - vaf.va = &args; - - r = printk("%sBluetooth: %pV\n", level, &vaf); - - va_end(args); - - return r; -} -EXPORT_SYMBOL(bt_printk); +EXPORT_SYMBOL(bt_err); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 98327213d93..6d45a60a048 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -41,7 +41,7 @@ struct pending_cmd { void *user_data; }; -static LIST_HEAD(cmd_list); +LIST_HEAD(cmd_list); static int cmd_status(struct sock *sk, u16 index, u16 cmd, u8 status) { @@ -179,7 +179,7 @@ static int read_controller_info(struct sock *sk, u16 index) hci_del_off_timer(hdev); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); set_bit(HCI_MGMT, &hdev->flags); @@ -208,7 +208,7 @@ static int read_controller_info(struct sock *sk, u16 index) memcpy(rp.name, hdev->dev_name, sizeof(hdev->dev_name)); - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return cmd_complete(sk, index, MGMT_OP_READ_INFO, &rp, sizeof(rp)); @@ -316,7 +316,7 @@ static int set_powered(struct sock *sk, u16 index, unsigned char *data, u16 len) if (!hdev) return cmd_status(sk, index, MGMT_OP_SET_POWERED, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); up = test_bit(HCI_UP, &hdev->flags); if ((cp->val && up) || (!cp->val && !up)) { @@ -343,7 +343,7 @@ static int set_powered(struct sock *sk, u16 index, unsigned char *data, u16 len) err = 0; failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; } @@ -368,7 +368,7 @@ static int set_discoverable(struct sock *sk, u16 index, unsigned char *data, if (!hdev) return cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (!test_bit(HCI_UP, &hdev->flags)) { err = cmd_status(sk, index, MGMT_OP_SET_DISCOVERABLE, ENETDOWN); @@ -403,7 +403,7 @@ static int set_discoverable(struct sock *sk, u16 index, unsigned char *data, mgmt_pending_remove(cmd); failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -429,7 +429,7 @@ static int set_connectable(struct sock *sk, u16 index, unsigned char *data, if (!hdev) return cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (!test_bit(HCI_UP, &hdev->flags)) { err = cmd_status(sk, index, MGMT_OP_SET_CONNECTABLE, ENETDOWN); @@ -463,7 +463,7 @@ static int set_connectable(struct sock *sk, u16 index, unsigned char *data, mgmt_pending_remove(cmd); failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -522,7 +522,7 @@ static int set_pairable(struct sock *sk, u16 index, unsigned char *data, if (!hdev) return cmd_status(sk, index, MGMT_OP_SET_PAIRABLE, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (cp->val) set_bit(HCI_PAIRABLE, &hdev->flags); @@ -538,7 +538,7 @@ static int set_pairable(struct sock *sk, u16 index, unsigned char *data, err = mgmt_event(MGMT_EV_PAIRABLE, index, &ev, sizeof(ev), sk); failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -739,7 +739,7 @@ static int add_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) if (!hdev) return cmd_status(sk, index, MGMT_OP_ADD_UUID, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); uuid = kmalloc(sizeof(*uuid), GFP_ATOMIC); if (!uuid) { @@ -763,7 +763,7 @@ static int add_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) err = cmd_complete(sk, index, MGMT_OP_ADD_UUID, NULL, 0); failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -788,7 +788,7 @@ static int remove_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) if (!hdev) return cmd_status(sk, index, MGMT_OP_REMOVE_UUID, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (memcmp(cp->uuid, bt_uuid_any, 16) == 0) { err = hci_uuids_clear(hdev); @@ -823,7 +823,7 @@ static int remove_uuid(struct sock *sk, u16 index, unsigned char *data, u16 len) err = cmd_complete(sk, index, MGMT_OP_REMOVE_UUID, NULL, 0); unlock: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -847,7 +847,7 @@ static int set_dev_class(struct sock *sk, u16 index, unsigned char *data, if (!hdev) return cmd_status(sk, index, MGMT_OP_SET_DEV_CLASS, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); hdev->major_class = cp->major; hdev->minor_class = cp->minor; @@ -857,7 +857,7 @@ static int set_dev_class(struct sock *sk, u16 index, unsigned char *data, if (err == 0) err = cmd_complete(sk, index, MGMT_OP_SET_DEV_CLASS, NULL, 0); - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -879,7 +879,7 @@ static int set_service_cache(struct sock *sk, u16 index, unsigned char *data, if (!hdev) return cmd_status(sk, index, MGMT_OP_SET_SERVICE_CACHE, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); BT_DBG("hci%u enable %d", index, cp->enable); @@ -897,7 +897,7 @@ static int set_service_cache(struct sock *sk, u16 index, unsigned char *data, err = cmd_complete(sk, index, MGMT_OP_SET_SERVICE_CACHE, NULL, 0); - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -931,7 +931,7 @@ static int load_keys(struct sock *sk, u16 index, unsigned char *data, u16 len) BT_DBG("hci%u debug_keys %u key_count %u", index, cp->debug_keys, key_count); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); hci_link_keys_clear(hdev); @@ -950,7 +950,7 @@ static int load_keys(struct sock *sk, u16 index, unsigned char *data, u16 len) i += sizeof(*key) + key->dlen; - if (key->type == HCI_LK_SMP_LTK) { + if (key->type == KEY_TYPE_LTK) { struct key_master_id *id = (void *) key->data; if (key->dlen != sizeof(struct key_master_id)) @@ -962,13 +962,13 @@ static int load_keys(struct sock *sk, u16 index, unsigned char *data, u16 len) continue; } - hci_add_link_key(hdev, NULL, 0, &key->bdaddr, key->val, key->type, + hci_add_link_key(hdev, 0, &key->bdaddr, key->val, key->type, key->pin_len); } err = cmd_complete(sk, index, MGMT_OP_LOAD_KEYS, NULL, 0); - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -990,7 +990,7 @@ static int remove_key(struct sock *sk, u16 index, unsigned char *data, u16 len) if (!hdev) return cmd_status(sk, index, MGMT_OP_REMOVE_KEY, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); err = hci_remove_link_key(hdev, &cp->bdaddr); if (err < 0) { @@ -1009,11 +1009,11 @@ static int remove_key(struct sock *sk, u16 index, unsigned char *data, u16 len) put_unaligned_le16(conn->handle, &dc.handle); dc.reason = 0x13; /* Remote User Terminated Connection */ - err = hci_send_cmd(hdev, HCI_OP_DISCONNECT, sizeof(dc), &dc); + err = hci_send_cmd(hdev, HCI_OP_DISCONNECT, 0, NULL); } unlock: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1039,7 +1039,7 @@ static int disconnect(struct sock *sk, u16 index, unsigned char *data, u16 len) if (!hdev) return cmd_status(sk, index, MGMT_OP_DISCONNECT, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (!test_bit(HCI_UP, &hdev->flags)) { err = cmd_status(sk, index, MGMT_OP_DISCONNECT, ENETDOWN); @@ -1052,9 +1052,6 @@ static int disconnect(struct sock *sk, u16 index, unsigned char *data, u16 len) } conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); - if (!conn) - conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->bdaddr); - if (!conn) { err = cmd_status(sk, index, MGMT_OP_DISCONNECT, ENOTCONN); goto failed; @@ -1074,7 +1071,7 @@ static int disconnect(struct sock *sk, u16 index, unsigned char *data, u16 len) mgmt_pending_remove(cmd); failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1095,7 +1092,7 @@ static int get_connections(struct sock *sk, u16 index) if (!hdev) return cmd_status(sk, index, MGMT_OP_GET_CONNECTIONS, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); count = 0; list_for_each(p, &hdev->conn_hash.list) { @@ -1111,6 +1108,8 @@ static int get_connections(struct sock *sk, u16 index) put_unaligned_le16(count, &rp->conn_count); + read_lock(&hci_dev_list_lock); + i = 0; list_for_each(p, &hdev->conn_hash.list) { struct hci_conn *c = list_entry(p, struct hci_conn, list); @@ -1118,41 +1117,22 @@ static int get_connections(struct sock *sk, u16 index) bacpy(&rp->conn[i++], &c->dst); } + read_unlock(&hci_dev_list_lock); + err = cmd_complete(sk, index, MGMT_OP_GET_CONNECTIONS, rp, rp_len); unlock: kfree(rp); - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; } -static int send_pin_code_neg_reply(struct sock *sk, u16 index, - struct hci_dev *hdev, struct mgmt_cp_pin_code_neg_reply *cp) -{ - struct pending_cmd *cmd; - int err; - - cmd = mgmt_pending_add(sk, MGMT_OP_PIN_CODE_NEG_REPLY, index, cp, - sizeof(*cp)); - if (!cmd) - return -ENOMEM; - - err = hci_send_cmd(hdev, HCI_OP_PIN_CODE_NEG_REPLY, sizeof(cp->bdaddr), - &cp->bdaddr); - if (err < 0) - mgmt_pending_remove(cmd); - - return err; -} - static int pin_code_reply(struct sock *sk, u16 index, unsigned char *data, u16 len) { struct hci_dev *hdev; - struct hci_conn *conn; struct mgmt_cp_pin_code_reply *cp; - struct mgmt_cp_pin_code_neg_reply ncp; struct hci_cp_pin_code_reply reply; struct pending_cmd *cmd; int err; @@ -1168,32 +1148,13 @@ static int pin_code_reply(struct sock *sk, u16 index, unsigned char *data, if (!hdev) return cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (!test_bit(HCI_UP, &hdev->flags)) { err = cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, ENETDOWN); goto failed; } - conn = hci_conn_hash_lookup_ba(hdev, ACL_LINK, &cp->bdaddr); - if (!conn) { - err = cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, ENOTCONN); - goto failed; - } - - if (conn->pending_sec_level == BT_SECURITY_HIGH && cp->pin_len != 16) { - bacpy(&ncp.bdaddr, &cp->bdaddr); - - BT_ERR("PIN code is not 16 bytes long"); - - err = send_pin_code_neg_reply(sk, index, hdev, &ncp); - if (err >= 0) - err = cmd_status(sk, index, MGMT_OP_PIN_CODE_REPLY, - EINVAL); - - goto failed; - } - cmd = mgmt_pending_add(sk, MGMT_OP_PIN_CODE_REPLY, index, data, len); if (!cmd) { err = -ENOMEM; @@ -1202,14 +1163,14 @@ static int pin_code_reply(struct sock *sk, u16 index, unsigned char *data, bacpy(&reply.bdaddr, &cp->bdaddr); reply.pin_len = cp->pin_len; - memcpy(reply.pin_code, cp->pin_code, sizeof(reply.pin_code)); + memcpy(reply.pin_code, cp->pin_code, 16); err = hci_send_cmd(hdev, HCI_OP_PIN_CODE_REPLY, sizeof(reply), &reply); if (err < 0) mgmt_pending_remove(cmd); failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1220,6 +1181,7 @@ static int pin_code_neg_reply(struct sock *sk, u16 index, unsigned char *data, { struct hci_dev *hdev; struct mgmt_cp_pin_code_neg_reply *cp; + struct pending_cmd *cmd; int err; BT_DBG(""); @@ -1235,7 +1197,7 @@ static int pin_code_neg_reply(struct sock *sk, u16 index, unsigned char *data, return cmd_status(sk, index, MGMT_OP_PIN_CODE_NEG_REPLY, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (!test_bit(HCI_UP, &hdev->flags)) { err = cmd_status(sk, index, MGMT_OP_PIN_CODE_NEG_REPLY, @@ -1243,10 +1205,20 @@ static int pin_code_neg_reply(struct sock *sk, u16 index, unsigned char *data, goto failed; } - err = send_pin_code_neg_reply(sk, index, hdev, cp); + cmd = mgmt_pending_add(sk, MGMT_OP_PIN_CODE_NEG_REPLY, index, + data, len); + if (!cmd) { + err = -ENOMEM; + goto failed; + } + + err = hci_send_cmd(hdev, HCI_OP_PIN_CODE_NEG_REPLY, sizeof(cp->bdaddr), + &cp->bdaddr); + if (err < 0) + mgmt_pending_remove(cmd); failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1269,14 +1241,14 @@ static int set_io_capability(struct sock *sk, u16 index, unsigned char *data, if (!hdev) return cmd_status(sk, index, MGMT_OP_SET_IO_CAPABILITY, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); hdev->io_capability = cp->io_capability; BT_DBG("%s IO capability set to 0x%02x", hdev->name, hdev->io_capability); - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return cmd_complete(sk, index, MGMT_OP_SET_IO_CAPABILITY, NULL, 0); @@ -1362,7 +1334,7 @@ static int pair_device(struct sock *sk, u16 index, unsigned char *data, u16 len) if (!hdev) return cmd_status(sk, index, MGMT_OP_PAIR_DEVICE, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (cp->io_cap == 0x03) { sec_level = BT_SECURITY_MEDIUM; @@ -1372,7 +1344,8 @@ static int pair_device(struct sock *sk, u16 index, unsigned char *data, u16 len) auth_type = HCI_AT_DEDICATED_BONDING_MITM; } - conn = hci_connect(hdev, ACL_LINK, 0, &cp->bdaddr, sec_level, auth_type); + conn = hci_connect(hdev, ACL_LINK, 0, &cp->bdaddr, sec_level, + auth_type); if (IS_ERR(conn)) { err = PTR_ERR(conn); goto unlock; @@ -1404,7 +1377,7 @@ static int pair_device(struct sock *sk, u16 index, unsigned char *data, u16 len) err = 0; unlock: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1436,7 +1409,7 @@ static int user_confirm_reply(struct sock *sk, u16 index, unsigned char *data, if (!hdev) return cmd_status(sk, index, mgmt_op, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (!test_bit(HCI_UP, &hdev->flags)) { err = cmd_status(sk, index, mgmt_op, ENETDOWN); @@ -1454,7 +1427,7 @@ static int user_confirm_reply(struct sock *sk, u16 index, unsigned char *data, mgmt_pending_remove(cmd); failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1478,7 +1451,7 @@ static int set_local_name(struct sock *sk, u16 index, unsigned char *data, if (!hdev) return cmd_status(sk, index, MGMT_OP_SET_LOCAL_NAME, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); cmd = mgmt_pending_add(sk, MGMT_OP_SET_LOCAL_NAME, index, data, len); if (!cmd) { @@ -1493,7 +1466,7 @@ static int set_local_name(struct sock *sk, u16 index, unsigned char *data, mgmt_pending_remove(cmd); failed: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1512,7 +1485,7 @@ static int read_local_oob_data(struct sock *sk, u16 index) return cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); if (!test_bit(HCI_UP, &hdev->flags)) { err = cmd_status(sk, index, MGMT_OP_READ_LOCAL_OOB_DATA, @@ -1542,7 +1515,7 @@ static int read_local_oob_data(struct sock *sk, u16 index) mgmt_pending_remove(cmd); unlock: - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1566,7 +1539,7 @@ static int add_remote_oob_data(struct sock *sk, u16 index, unsigned char *data, return cmd_status(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); err = hci_add_remote_oob_data(hdev, &cp->bdaddr, cp->hash, cp->randomizer); @@ -1576,7 +1549,7 @@ static int add_remote_oob_data(struct sock *sk, u16 index, unsigned char *data, err = cmd_complete(sk, index, MGMT_OP_ADD_REMOTE_OOB_DATA, NULL, 0); - hci_dev_unlock_bh(hdev); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1600,7 +1573,7 @@ static int remove_remote_oob_data(struct sock *sk, u16 index, return cmd_status(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, ENODEV); - hci_dev_lock_bh(hdev); + hci_dev_lock(hdev); err = hci_remove_remote_oob_data(hdev, &cp->bdaddr); if (err < 0) @@ -1610,140 +1583,7 @@ static int remove_remote_oob_data(struct sock *sk, u16 index, err = cmd_complete(sk, index, MGMT_OP_REMOVE_REMOTE_OOB_DATA, NULL, 0); - hci_dev_unlock_bh(hdev); - hci_dev_put(hdev); - - return err; -} - -static int start_discovery(struct sock *sk, u16 index) -{ - u8 lap[3] = { 0x33, 0x8b, 0x9e }; - struct hci_cp_inquiry cp; - struct pending_cmd *cmd; - struct hci_dev *hdev; - int err; - - BT_DBG("hci%u", index); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_START_DISCOVERY, ENODEV); - - hci_dev_lock_bh(hdev); - - cmd = mgmt_pending_add(sk, MGMT_OP_START_DISCOVERY, index, NULL, 0); - if (!cmd) { - err = -ENOMEM; - goto failed; - } - - memset(&cp, 0, sizeof(cp)); - memcpy(&cp.lap, lap, 3); - cp.length = 0x08; - cp.num_rsp = 0x00; - - err = hci_send_cmd(hdev, HCI_OP_INQUIRY, sizeof(cp), &cp); - if (err < 0) - mgmt_pending_remove(cmd); - -failed: - hci_dev_unlock_bh(hdev); - hci_dev_put(hdev); - - return err; -} - -static int stop_discovery(struct sock *sk, u16 index) -{ - struct hci_dev *hdev; - struct pending_cmd *cmd; - int err; - - BT_DBG("hci%u", index); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_STOP_DISCOVERY, ENODEV); - - hci_dev_lock_bh(hdev); - - cmd = mgmt_pending_add(sk, MGMT_OP_STOP_DISCOVERY, index, NULL, 0); - if (!cmd) { - err = -ENOMEM; - goto failed; - } - - err = hci_send_cmd(hdev, HCI_OP_INQUIRY_CANCEL, 0, NULL); - if (err < 0) - mgmt_pending_remove(cmd); - -failed: - hci_dev_unlock_bh(hdev); - hci_dev_put(hdev); - - return err; -} - -static int block_device(struct sock *sk, u16 index, unsigned char *data, - u16 len) -{ - struct hci_dev *hdev; - struct mgmt_cp_block_device *cp; - int err; - - BT_DBG("hci%u", index); - - cp = (void *) data; - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_BLOCK_DEVICE, - EINVAL); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_BLOCK_DEVICE, - ENODEV); - - err = hci_blacklist_add(hdev, &cp->bdaddr); - - if (err < 0) - err = cmd_status(sk, index, MGMT_OP_BLOCK_DEVICE, -err); - else - err = cmd_complete(sk, index, MGMT_OP_BLOCK_DEVICE, - NULL, 0); - hci_dev_put(hdev); - - return err; -} - -static int unblock_device(struct sock *sk, u16 index, unsigned char *data, - u16 len) -{ - struct hci_dev *hdev; - struct mgmt_cp_unblock_device *cp; - int err; - - BT_DBG("hci%u", index); - - cp = (void *) data; - - if (len != sizeof(*cp)) - return cmd_status(sk, index, MGMT_OP_UNBLOCK_DEVICE, - EINVAL); - - hdev = hci_dev_get(index); - if (!hdev) - return cmd_status(sk, index, MGMT_OP_UNBLOCK_DEVICE, - ENODEV); - - err = hci_blacklist_del(hdev, &cp->bdaddr); - - if (err < 0) - err = cmd_status(sk, index, MGMT_OP_UNBLOCK_DEVICE, -err); - else - err = cmd_complete(sk, index, MGMT_OP_UNBLOCK_DEVICE, - NULL, 0); + hci_dev_unlock(hdev); hci_dev_put(hdev); return err; @@ -1857,18 +1697,7 @@ int mgmt_control(struct sock *sk, struct msghdr *msg, size_t msglen) err = remove_remote_oob_data(sk, index, buf + sizeof(*hdr), len); break; - case MGMT_OP_START_DISCOVERY: - err = start_discovery(sk, index); - break; - case MGMT_OP_STOP_DISCOVERY: - err = stop_discovery(sk, index); - break; - case MGMT_OP_BLOCK_DEVICE: - err = block_device(sk, index, buf + sizeof(*hdr), len); - break; - case MGMT_OP_UNBLOCK_DEVICE: - err = unblock_device(sk, index, buf + sizeof(*hdr), len); - break; + default: BT_DBG("Unknown op %u", opcode); err = cmd_status(sk, index, opcode, 0x01); @@ -1975,7 +1804,7 @@ int mgmt_connectable(u16 index, u8 connectable) return ret; } -int mgmt_new_key(u16 index, struct link_key *key, u8 persistent) +int mgmt_new_key(u16 index, struct link_key *key, u8 old_key_type) { struct mgmt_ev_new_key *ev; int err, total; @@ -1989,8 +1818,8 @@ int mgmt_new_key(u16 index, struct link_key *key, u8 persistent) ev->key.type = key->type; memcpy(ev->key.val, key->val, 16); ev->key.pin_len = key->pin_len; + ev->old_key_type = old_key_type; ev->key.dlen = key->dlen; - ev->store_hint = persistent; memcpy(ev->key.data, key->data, key->dlen); @@ -2070,12 +1899,11 @@ int mgmt_connect_failed(u16 index, bdaddr_t *bdaddr, u8 status) return mgmt_event(MGMT_EV_CONNECT_FAILED, index, &ev, sizeof(ev), NULL); } -int mgmt_pin_code_request(u16 index, bdaddr_t *bdaddr, u8 secure) +int mgmt_pin_code_request(u16 index, bdaddr_t *bdaddr) { struct mgmt_ev_pin_code_request ev; bacpy(&ev.bdaddr, bdaddr); - ev.secure = secure; return mgmt_event(MGMT_EV_PIN_CODE_REQUEST, index, &ev, sizeof(ev), NULL); @@ -2123,15 +1951,13 @@ int mgmt_pin_code_neg_reply_complete(u16 index, bdaddr_t *bdaddr, u8 status) return err; } -int mgmt_user_confirm_request(u16 index, bdaddr_t *bdaddr, __le32 value, - u8 confirm_hint) +int mgmt_user_confirm_request(u16 index, bdaddr_t *bdaddr, __le32 value) { struct mgmt_ev_user_confirm_request ev; BT_DBG("hci%u", index); bacpy(&ev.bdaddr, bdaddr); - ev.confirm_hint = confirm_hint; put_unaligned_le32(value, &ev.value); return mgmt_event(MGMT_EV_USER_CONFIRM_REQUEST, index, &ev, sizeof(ev), @@ -2280,9 +2106,3 @@ int mgmt_remote_name(u16 index, bdaddr_t *bdaddr, u8 *name) return mgmt_event(MGMT_EV_REMOTE_NAME, index, &ev, sizeof(ev), NULL); } - -int mgmt_discovering(u16 index, u8 discovering) -{ - return mgmt_event(MGMT_EV_DISCOVERING, index, &discovering, - sizeof(discovering), NULL); -} diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index c2486a53714..6dac31fbb99 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -62,6 +62,7 @@ static DEFINE_MUTEX(rfcomm_mutex); #define rfcomm_lock() mutex_lock(&rfcomm_mutex) #define rfcomm_unlock() mutex_unlock(&rfcomm_mutex) +static unsigned long rfcomm_event; static LIST_HEAD(session_list); @@ -119,6 +120,7 @@ static inline void rfcomm_schedule(void) { if (!rfcomm_thread) return; + set_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event); wake_up_process(rfcomm_thread); } @@ -230,8 +232,6 @@ static int rfcomm_l2sock_create(struct socket **sock) static inline int rfcomm_check_security(struct rfcomm_dlc *d) { struct sock *sk = d->session->sock->sk; - struct l2cap_conn *conn = l2cap_pi(sk)->chan->conn; - __u8 auth_type; switch (d->sec_level) { @@ -246,7 +246,8 @@ static inline int rfcomm_check_security(struct rfcomm_dlc *d) break; } - return hci_conn_security(conn->hcon, d->sec_level, auth_type); + return hci_conn_security(l2cap_pi(sk)->conn->hcon, d->sec_level, + auth_type); } static void rfcomm_session_timeout(unsigned long arg) @@ -708,10 +709,10 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, /* Set L2CAP options */ sk = sock->sk; lock_sock(sk); - l2cap_pi(sk)->chan->imtu = l2cap_mtu; - l2cap_pi(sk)->chan->sec_level = sec_level; + l2cap_pi(sk)->imtu = l2cap_mtu; + l2cap_pi(sk)->sec_level = sec_level; if (l2cap_ertm) - l2cap_pi(sk)->chan->mode = L2CAP_MODE_ERTM; + l2cap_pi(sk)->mode = L2CAP_MODE_ERTM; release_sock(sk); s = rfcomm_session_add(sock, BT_BOUND); @@ -1239,7 +1240,6 @@ static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci) void rfcomm_dlc_accept(struct rfcomm_dlc *d) { struct sock *sk = d->session->sock->sk; - struct l2cap_conn *conn = l2cap_pi(sk)->chan->conn; BT_DBG("dlc %p", d); @@ -1253,7 +1253,7 @@ void rfcomm_dlc_accept(struct rfcomm_dlc *d) rfcomm_dlc_unlock(d); if (d->role_switch) - hci_conn_switch_role(conn->hcon, 0x00); + hci_conn_switch_role(l2cap_pi(sk)->conn->hcon, 0x00); rfcomm_send_msc(d->session, 1, d->dlci, d->v24_sig); } @@ -1852,7 +1852,10 @@ static inline void rfcomm_process_rx(struct rfcomm_session *s) /* Get data directly from socket receive queue without copying it. */ while ((skb = skb_dequeue(&sk->sk_receive_queue))) { skb_orphan(skb); - rfcomm_recv_frame(s, skb); + if (!skb_linearize(skb)) + rfcomm_recv_frame(s, skb); + else + kfree_skb(skb); } if (sk->sk_state == BT_CLOSED) { @@ -1889,8 +1892,7 @@ static inline void rfcomm_accept_connection(struct rfcomm_session *s) /* We should adjust MTU on incoming sessions. * L2CAP MTU minus UIH header and FCS. */ - s->mtu = min(l2cap_pi(nsock->sk)->chan->omtu, - l2cap_pi(nsock->sk)->chan->imtu) - 5; + s->mtu = min(l2cap_pi(nsock->sk)->omtu, l2cap_pi(nsock->sk)->imtu) - 5; rfcomm_schedule(); } else @@ -1909,7 +1911,7 @@ static inline void rfcomm_check_connection(struct rfcomm_session *s) /* We can adjust MTU on outgoing sessions. * L2CAP MTU minus UIH header and FCS. */ - s->mtu = min(l2cap_pi(sk)->chan->omtu, l2cap_pi(sk)->chan->imtu) - 5; + s->mtu = min(l2cap_pi(sk)->omtu, l2cap_pi(sk)->imtu) - 5; rfcomm_send_sabm(s, 0); break; @@ -1992,7 +1994,7 @@ static int rfcomm_add_listener(bdaddr_t *ba) /* Set L2CAP options */ sk = sock->sk; lock_sock(sk); - l2cap_pi(sk)->chan->imtu = l2cap_mtu; + l2cap_pi(sk)->imtu = l2cap_mtu; release_sock(sk); /* Start listening on the socket */ @@ -2035,18 +2037,19 @@ static int rfcomm_run(void *unused) rfcomm_add_listener(BDADDR_ANY); - while (1) { + while (!kthread_should_stop()) { set_current_state(TASK_INTERRUPTIBLE); - - if (kthread_should_stop()) - break; + if (!test_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event)) { + /* No pending events. Let's sleep. + * Incoming connections and data will wake us up. */ + schedule(); + } + set_current_state(TASK_RUNNING); /* Process stuff */ + clear_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event); rfcomm_process_sessions(); - - schedule(); } - __set_current_state(TASK_RUNNING); rfcomm_kill_listener(); @@ -2092,7 +2095,7 @@ static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt) if (!test_and_clear_bit(RFCOMM_AUTH_PENDING, &d->flags)) continue; - if (!status && hci_conn_check_secure(conn, d->sec_level)) + if (!status) set_bit(RFCOMM_AUTH_ACCEPT, &d->flags); else set_bit(RFCOMM_AUTH_REJECT, &d->flags); diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index b02f0d47ab8..66cc1f0c3df 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -485,6 +485,11 @@ static int rfcomm_sock_accept(struct socket *sock, struct socket *newsock, int f lock_sock(sk); + if (sk->sk_state != BT_LISTEN) { + err = -EBADFD; + goto done; + } + if (sk->sk_type != SOCK_STREAM) { err = -EINVAL; goto done; @@ -496,20 +501,19 @@ static int rfcomm_sock_accept(struct socket *sock, struct socket *newsock, int f /* Wait for an incoming connection. (wake-one). */ add_wait_queue_exclusive(sk_sleep(sk), &wait); - while (1) { + while (!(nsk = bt_accept_dequeue(sk, newsock))) { set_current_state(TASK_INTERRUPTIBLE); - - if (sk->sk_state != BT_LISTEN) { - err = -EBADFD; + if (!timeo) { + err = -EAGAIN; break; } - nsk = bt_accept_dequeue(sk, newsock); - if (nsk) - break; + release_sock(sk); + timeo = schedule_timeout(timeo); + lock_sock(sk); - if (!timeo) { - err = -EAGAIN; + if (sk->sk_state != BT_LISTEN) { + err = -EBADFD; break; } @@ -517,12 +521,8 @@ static int rfcomm_sock_accept(struct socket *sock, struct socket *newsock, int f err = sock_intr_errno(timeo); break; } - - release_sock(sk); - timeo = schedule_timeout(timeo); - lock_sock(sk); } - __set_current_state(TASK_RUNNING); + set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); if (err) @@ -679,8 +679,7 @@ static int rfcomm_sock_setsockopt(struct socket *sock, int level, int optname, c { struct sock *sk = sock->sk; struct bt_security sec; - int err = 0; - size_t len; + int len, err = 0; u32 opt; BT_DBG("sk %p", sk); @@ -742,8 +741,8 @@ static int rfcomm_sock_setsockopt(struct socket *sock, int level, int optname, c static int rfcomm_sock_getsockopt_old(struct socket *sock, int optname, char __user *optval, int __user *optlen) { struct sock *sk = sock->sk; + struct sock *l2cap_sk; struct rfcomm_conninfo cinfo; - struct l2cap_conn *conn = l2cap_pi(sk)->chan->conn; int len, err = 0; u32 opt; @@ -786,10 +785,10 @@ static int rfcomm_sock_getsockopt_old(struct socket *sock, int optname, char __u break; } + l2cap_sk = rfcomm_pi(sk)->dlc->session->sock->sk; - memset(&cinfo, 0, sizeof(cinfo)); - cinfo.hci_handle = conn->hcon->handle; - memcpy(cinfo.dev_class, conn->hcon->dev_class, 3); + cinfo.hci_handle = l2cap_pi(l2cap_sk)->conn->hcon->handle; + memcpy(cinfo.dev_class, l2cap_pi(l2cap_sk)->conn->hcon->dev_class, 3); len = min_t(unsigned int, len, sizeof(cinfo)); if (copy_to_user(optval, (char *) &cinfo, len)) diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index d3d48b5b542..e692b9de062 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -198,7 +198,8 @@ static int sco_connect(struct sock *sk) pkt_type &= SCO_ESCO_MASK; } - hcon = hci_connect(hdev, type, pkt_type, dst, BT_SECURITY_LOW, HCI_AT_NO_BONDING); + hcon = hci_connect(hdev, type, pkt_type, dst, + BT_SECURITY_LOW, HCI_AT_NO_BONDING); if (IS_ERR(hcon)) { err = PTR_ERR(hcon); goto done; @@ -372,15 +373,6 @@ static void __sco_sock_close(struct sock *sk) case BT_CONNECTED: case BT_CONFIG: - if (sco_pi(sk)->conn) { - sk->sk_state = BT_DISCONN; - sco_sock_set_timer(sk, SCO_DISCONN_TIMEOUT); - hci_conn_put(sco_pi(sk)->conn->hcon); - sco_pi(sk)->conn->hcon = NULL; - } else - sco_chan_del(sk, ECONNRESET); - break; - case BT_CONNECT: case BT_DISCONN: sco_chan_del(sk, ECONNRESET); @@ -579,26 +571,30 @@ static int sco_sock_accept(struct socket *sock, struct socket *newsock, int flag lock_sock(sk); + if (sk->sk_state != BT_LISTEN) { + err = -EBADFD; + goto done; + } + timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); BT_DBG("sk %p timeo %ld", sk, timeo); /* Wait for an incoming connection. (wake-one). */ add_wait_queue_exclusive(sk_sleep(sk), &wait); - while (1) { + while (!(ch = bt_accept_dequeue(sk, newsock))) { set_current_state(TASK_INTERRUPTIBLE); - - if (sk->sk_state != BT_LISTEN) { - err = -EBADFD; + if (!timeo) { + err = -EAGAIN; break; } - ch = bt_accept_dequeue(sk, newsock); - if (ch) - break; + release_sock(sk); + timeo = schedule_timeout(timeo); + lock_sock(sk); - if (!timeo) { - err = -EAGAIN; + if (sk->sk_state != BT_LISTEN) { + err = -EBADFD; break; } @@ -606,12 +602,8 @@ static int sco_sock_accept(struct socket *sock, struct socket *newsock, int flag err = sock_intr_errno(timeo); break; } - - release_sock(sk); - timeo = schedule_timeout(timeo); - lock_sock(sk); } - __set_current_state(TASK_RUNNING); + set_current_state(TASK_RUNNING); remove_wait_queue(sk_sleep(sk), &wait); if (err) @@ -844,9 +836,7 @@ static void sco_chan_del(struct sock *sk, int err) conn->sk = NULL; sco_pi(sk)->conn = NULL; sco_conn_unlock(conn); - - if (conn->hcon) - hci_conn_put(conn->hcon); + hci_conn_put(conn->hcon); } sk->sk_state = BT_CLOSED; @@ -948,7 +938,7 @@ static int sco_connect_cfm(struct hci_conn *hcon, __u8 status) if (conn) sco_conn_ready(conn); } else - sco_conn_del(hcon, bt_to_errno(status)); + sco_conn_del(hcon, bt_err(status)); return 0; } @@ -960,7 +950,7 @@ static int sco_disconn_cfm(struct hci_conn *hcon, __u8 reason) if (hcon->type != SCO_LINK && hcon->type != ESCO_LINK) return -EINVAL; - sco_conn_del(hcon, bt_to_errno(reason)); + sco_conn_del(hcon, bt_err(reason)); return 0; } diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index 391888b88a9..e1c4ef37f2a 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #define SMP_TIMEOUT 30000 /* 30 seconds */ @@ -146,7 +145,7 @@ static int smp_rand(u8 *buf) } static struct sk_buff *smp_build_cmd(struct l2cap_conn *conn, u8 code, - u16 dlen, void *data) + u16 dlen, void *data) { struct sk_buff *skb; struct l2cap_hdr *lh; @@ -181,7 +180,7 @@ static void smp_send_cmd(struct l2cap_conn *conn, u8 code, u16 len, void *data) if (!skb) return; - hci_send_acl(conn->hcon, skb, 0); + hci_send_acl(conn->hcon, NULL, skb, 0); } static __u8 seclevel_to_authreq(__u8 level) @@ -201,11 +200,12 @@ static void build_pairing_cmd(struct l2cap_conn *conn, struct smp_cmd_pairing *rsp, __u8 authreq) { + u8 all_keys = 0; u8 dist_keys; dist_keys = 0; if (test_bit(HCI_PAIRABLE, &conn->hcon->hdev->flags)) { - dist_keys = SMP_DIST_ENC_KEY | SMP_DIST_ID_KEY | SMP_DIST_SIGN; + dist_keys = SMP_DIST_ENC_KEY; authreq |= SMP_AUTH_BONDING; } @@ -214,7 +214,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, req->oob_flag = SMP_OOB_NOT_PRESENT; req->max_key_size = SMP_MAX_ENC_KEY_SIZE; req->init_key_dist = dist_keys; - req->resp_key_dist = dist_keys; + req->resp_key_dist = all_keys; req->auth_req = authreq; return; } @@ -222,7 +222,7 @@ static void build_pairing_cmd(struct l2cap_conn *conn, rsp->io_capability = conn->hcon->io_capability; rsp->oob_flag = SMP_OOB_NOT_PRESENT; rsp->max_key_size = SMP_MAX_ENC_KEY_SIZE; - rsp->init_key_dist = req->init_key_dist & dist_keys; + rsp->init_key_dist = req->init_key_dist & all_keys; rsp->resp_key_dist = req->resp_key_dist & dist_keys; rsp->auth_req = authreq; } @@ -461,29 +461,25 @@ int smp_conn_security(struct l2cap_conn *conn, __u8 sec_level) BT_DBG("conn %p hcon %p level 0x%2.2x", conn, hcon, sec_level); - if (!lmp_host_le_capable(hcon->hdev)) - return 1; - if (IS_ERR(hcon->hdev->tfm)) return 1; if (test_bit(HCI_CONN_ENCRYPT_PEND, &hcon->pend)) - return 0; + return -EINPROGRESS; if (sec_level == BT_SECURITY_LOW) return 1; - if (hcon->sec_level >= sec_level) + if (hcon->sec_level > sec_level) return 1; authreq = seclevel_to_authreq(sec_level); if (hcon->link_mode & HCI_LM_MASTER) { - struct smp_cmd_pairing cp; struct link_key *key; key = hci_find_link_key_type(hcon->hdev, conn->dst, - HCI_LK_SMP_LTK); + KEY_TYPE_LTK); if (key) { struct key_master_id *master = (void *) key->data; @@ -493,6 +489,10 @@ int smp_conn_security(struct l2cap_conn *conn, __u8 sec_level) goto done; } + } + + if (hcon->link_mode & HCI_LM_MASTER) { + struct smp_cmd_pairing cp; build_pairing_cmd(conn, &cp, NULL, authreq); conn->preq[0] = SMP_CMD_PAIRING_REQ; @@ -518,10 +518,18 @@ int smp_conn_security(struct l2cap_conn *conn, __u8 sec_level) static int smp_cmd_encrypt_info(struct l2cap_conn *conn, struct sk_buff *skb) { struct smp_cmd_encrypt_info *rp = (void *) skb->data; + u8 rand[8]; + int err; skb_pull(skb, sizeof(*rp)); - memcpy(conn->tk, rp->ltk, sizeof(conn->tk)); + BT_DBG("conn %p", conn); + + memset(rand, 0, sizeof(rand)); + + err = hci_add_ltk(conn->hcon->hdev, 0, conn->dst, 0, 0, rand, rp->ltk); + if (err) + return SMP_UNSPECIFIED; return 0; } @@ -529,11 +537,25 @@ static int smp_cmd_encrypt_info(struct l2cap_conn *conn, struct sk_buff *skb) static int smp_cmd_master_ident(struct l2cap_conn *conn, struct sk_buff *skb) { struct smp_cmd_master_ident *rp = (void *) skb->data; + struct smp_cmd_pairing *paircmd = (void *) &conn->prsp[1]; + struct link_key *key; + struct key_master_id *id; + u8 keydist = paircmd->init_key_dist; skb_pull(skb, sizeof(*rp)); + key = hci_find_link_key_type(conn->hcon->hdev, conn->dst, KEY_TYPE_LTK); + if (key == NULL) + return SMP_UNSPECIFIED; + + BT_DBG("keydist 0x%x", keydist); + + id = (void *) key->data; + id->ediv = rp->ediv; + memcpy(id->rand, rp->rand, sizeof(rp->rand)); + hci_add_ltk(conn->hcon->hdev, 1, conn->src, conn->smp_key_size, - rp->ediv, rp->rand, conn->tk); + rp->ediv, rp->rand, key->val); smp_distribute_keys(conn, 1); @@ -546,12 +568,6 @@ int smp_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb) __u8 reason; int err = 0; - if (!lmp_host_le_capable(conn->hcon->hdev)) { - err = -ENOTSUPP; - reason = SMP_PAIRING_NOTSUPP; - goto done; - } - if (IS_ERR(conn->hcon->hdev->tfm)) { err = PTR_ERR(conn->hcon->hdev->tfm); reason = SMP_PAIRING_NOTSUPP; diff --git a/net/ipv4/netfilter/Makefile b/net/ipv4/netfilter/Makefile index dca2082ec68..575782910b4 100644 --- a/net/ipv4/netfilter/Makefile +++ b/net/ipv4/netfilter/Makefile @@ -69,4 +69,3 @@ obj-$(CONFIG_IP_NF_ARP_MANGLE) += arpt_mangle.o obj-$(CONFIG_IP_NF_ARPFILTER) += arptable_filter.o obj-$(CONFIG_IP_NF_QUEUE) += ip_queue.o - diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 5eb7af23461..f2d98137778 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -3350,9 +3350,9 @@ int tcp_nuke_addr(struct net *net, struct sockaddr *addr) int family = addr->sa_family; unsigned int bucket; - struct in_addr *in; + struct in_addr *in = NULL; #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) - struct in6_addr *in6; + struct in6_addr *in6 = NULL; #endif if (family == AF_INET) { in = &((struct sockaddr_in *)addr)->sin_addr; diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 708dc203b03..378ddf9688d 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -1955,6 +1955,49 @@ void tcp_v4_destroy_sock(struct sock *sk) } EXPORT_SYMBOL(tcp_v4_destroy_sock); +/* + * tcp_v4_nuke_addr - destroy all sockets on the given local address + */ +void tcp_v4_nuke_addr(__u32 saddr) +{ + unsigned int bucket; + + for (bucket = 0; bucket < tcp_hashinfo.ehash_mask; bucket++) { + struct hlist_nulls_node *node; + struct sock *sk; + spinlock_t *lock = inet_ehash_lockp(&tcp_hashinfo, bucket); + +restart: + spin_lock_bh(lock); + sk_nulls_for_each(sk, node, &tcp_hashinfo.ehash[bucket].chain) { + struct inet_sock *inet = inet_sk(sk); + + if (inet->inet_rcv_saddr != saddr) + continue; + if (sysctl_ip_dynaddr && sk->sk_state == TCP_SYN_SENT) + continue; + if (sock_flag(sk, SOCK_DEAD)) + continue; + + sock_hold(sk); + spin_unlock_bh(lock); + + local_bh_disable(); + bh_lock_sock(sk); + sk->sk_err = ETIMEDOUT; + sk->sk_error_report(sk); + + tcp_done(sk); + bh_unlock_sock(sk); + local_bh_enable(); + sock_put(sk); + + goto restart; + } + spin_unlock_bh(lock); + } +} + #ifdef CONFIG_PROC_FS /* Proc filesystem TCP sock list dumping. */ diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index cf2cf62f33f..713e09d0704 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -1572,6 +1572,16 @@ static int ipv6_generate_eui64(u8 *eui, struct net_device *dev) return addrconf_ifid_infiniband(eui, dev); case ARPHRD_SIT: return addrconf_ifid_sit(eui, dev); + case ARPHRD_RAWIP: { + struct in6_addr lladdr; + + if (ipv6_get_lladdr(dev, &lladdr, IFA_F_TENTATIVE)) + get_random_bytes(eui, 8); + else + memcpy(eui, lladdr.s6_addr + 8, 8); + + return 0; + } } return -1; } @@ -2396,6 +2406,7 @@ static void addrconf_dev_config(struct net_device *dev) (dev->type != ARPHRD_FDDI) && (dev->type != ARPHRD_IEEE802_TR) && (dev->type != ARPHRD_ARCNET) && + (dev->type != ARPHRD_RAWIP) && (dev->type != ARPHRD_INFINIBAND)) { /* Alas, we support only Ethernet autoconfiguration. */ return; diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 9d4b165837d..77b1a28066e 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -437,8 +437,9 @@ int ip6_forward(struct sk_buff *skb) } /* XXX: idev->cnf.proxy_ndp? */ - if (net->ipv6.devconf_all->proxy_ndp && - pneigh_lookup(&nd_tbl, net, &hdr->daddr, skb->dev, 0)) { + if ((net->ipv6.devconf_all->proxy_ndp == 1 && + pneigh_lookup(&nd_tbl, net, &hdr->daddr, skb->dev, 0)) + || net->ipv6.devconf_all->proxy_ndp >= 2) { int proxied = ip6_forward_proxy_check(skb); if (proxied > 0) return ip6_input(skb); diff --git a/net/sched/sch_prio.c b/net/sched/sch_prio.c index 2a318f2dc3e..5cea0206017 100644 --- a/net/sched/sch_prio.c +++ b/net/sched/sch_prio.c @@ -27,6 +27,7 @@ struct prio_sched_data { struct tcf_proto *filter_list; u8 prio2band[TC_PRIO_MAX+1]; struct Qdisc *queues[TCQ_PRIO_BANDS]; + u8 enable_flow; }; @@ -96,6 +97,9 @@ static struct sk_buff *prio_peek(struct Qdisc *sch) struct prio_sched_data *q = qdisc_priv(sch); int prio; + if (!q->enable_flow) + return NULL; + for (prio = 0; prio < q->bands; prio++) { struct Qdisc *qdisc = q->queues[prio]; struct sk_buff *skb = qdisc->ops->peek(qdisc); @@ -110,6 +114,9 @@ static struct sk_buff *prio_dequeue(struct Qdisc *sch) struct prio_sched_data *q = qdisc_priv(sch); int prio; + if (!q->enable_flow) + return NULL; + for (prio = 0; prio < q->bands; prio++) { struct Qdisc *qdisc = q->queues[prio]; struct sk_buff *skb = qdisc->dequeue(qdisc); @@ -150,6 +157,7 @@ prio_reset(struct Qdisc *sch) for (prio = 0; prio < q->bands; prio++) qdisc_reset(q->queues[prio]); sch->q.qlen = 0; + q->enable_flow = 1; } static void @@ -182,6 +190,7 @@ static int prio_tune(struct Qdisc *sch, struct nlattr *opt) } sch_tree_lock(sch); + q->enable_flow = qopt->enable_flow; q->bands = qopt->bands; memcpy(q->prio2band, qopt->priomap, TC_PRIO_MAX+1); @@ -245,6 +254,7 @@ static int prio_dump(struct Qdisc *sch, struct sk_buff *skb) struct tc_prio_qopt opt; opt.bands = q->bands; + opt.enable_flow = q->enable_flow; memcpy(&opt.priomap, q->prio2band, TC_PRIO_MAX + 1); NLA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt); diff --git a/scripts/Makefile.modpost b/scripts/Makefile.modpost index 56dfafc73c1..93b2f698476 100644 --- a/scripts/Makefile.modpost +++ b/scripts/Makefile.modpost @@ -80,6 +80,7 @@ modpost = scripts/mod/modpost \ $(if $(KBUILD_EXTRA_SYMBOLS), $(patsubst %, -e %,$(KBUILD_EXTRA_SYMBOLS))) \ $(if $(KBUILD_EXTMOD),-o $(modulesymfile)) \ $(if $(CONFIG_DEBUG_SECTION_MISMATCH),,-S) \ + $(if $(CONFIG_NO_ERROR_ON_MISMATCH),,-E) \ $(if $(KBUILD_EXTMOD)$(KBUILD_MODPOST_WARN),-w) \ $(if $(cross_build),-c) diff --git a/scripts/build-all.py b/scripts/build-all.py new file mode 100755 index 00000000000..6135253da64 --- /dev/null +++ b/scripts/build-all.py @@ -0,0 +1,266 @@ +#! /usr/bin/env python + +# Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Code Aurora nor +# the names of its contributors may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Build the kernel for all targets using the Android build environment. +# +# TODO: Accept arguments to indicate what to build. + +import glob +from optparse import OptionParser +import subprocess +import os +import os.path +import shutil +import sys + +version = 'build-all.py, version 0.01' + +build_dir = '../all-kernels' +make_command = ["vmlinux", "modules"] +make_env = os.environ +make_env.update({ + 'ARCH': 'arm', + 'CROSS_COMPILE': 'arm-none-linux-gnueabi-', + 'KCONFIG_NOTIMESTAMP': 'true' }) +all_options = {} + +def error(msg): + sys.stderr.write("error: %s\n" % msg) + +def fail(msg): + """Fail with a user-printed message""" + error(msg) + sys.exit(1) + +def check_kernel(): + """Ensure that PWD is a kernel directory""" + if (not os.path.isfile('MAINTAINERS') or + not os.path.isfile('arch/arm/mach-msm/Kconfig')): + fail("This doesn't seem to be an MSM kernel dir") + +def check_build(): + """Ensure that the build directory is present.""" + if not os.path.isdir(build_dir): + try: + os.makedirs(build_dir) + except OSError as exc: + if exc.errno == errno.EEXIST: + pass + else: + raise + +def update_config(file, str): + print 'Updating %s with \'%s\'\n' % (file, str) + defconfig = open(file, 'a') + defconfig.write(str + '\n') + defconfig.close() + +def scan_configs(): + """Get the full list of defconfigs appropriate for this tree.""" + names = {} + for n in glob.glob('arch/arm/configs/msm[0-9]*_defconfig'): + names[os.path.basename(n)[:-10]] = n + for n in glob.glob('arch/arm/configs/qsd*_defconfig'): + names[os.path.basename(n)[:-10]] = n + return names + +class Builder: + def __init__(self, logname): + self.logname = logname + self.fd = open(logname, 'w') + + def run(self, args): + devnull = open('/dev/null', 'r') + proc = subprocess.Popen(args, stdin=devnull, + env=make_env, + bufsize=0, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + count = 0 + # for line in proc.stdout: + rawfd = proc.stdout.fileno() + while True: + line = os.read(rawfd, 1024) + if not line: + break + self.fd.write(line) + self.fd.flush() + if all_options.verbose: + sys.stdout.write(line) + sys.stdout.flush() + else: + for i in range(line.count('\n')): + count += 1 + if count == 64: + count = 0 + print + sys.stdout.write('.') + sys.stdout.flush() + print + result = proc.wait() + + self.fd.close() + return result + +failed_targets = [] + +def build(target): + dest_dir = os.path.join(build_dir, target) + log_name = '%s/log-%s.log' % (build_dir, target) + print 'Building %s in %s log %s' % (target, dest_dir, log_name) + if not os.path.isdir(dest_dir): + os.mkdir(dest_dir) + defconfig = 'arch/arm/configs/%s_defconfig' % target + dotconfig = '%s/.config' % dest_dir + savedefconfig = '%s/defconfig' % dest_dir + shutil.copyfile(defconfig, dotconfig) + + devnull = open('/dev/null', 'r') + subprocess.check_call(['make', 'O=%s' % dest_dir, + '%s_defconfig' % target], env=make_env, stdin=devnull) + devnull.close() + + if not all_options.updateconfigs: + build = Builder(log_name) + + result = build.run(['make', 'O=%s' % dest_dir] + make_command) + + if result != 0: + if all_options.keep_going: + failed_targets.append(target) + fail_or_error = error + else: + fail_or_error = fail + fail_or_error("Failed to build %s, see %s" % (target, build.logname)) + + # Copy the defconfig back. + if all_options.configs or all_options.updateconfigs: + devnull = open('/dev/null', 'r') + subprocess.check_call(['make', 'O=%s' % dest_dir, + 'savedefconfig'], env=make_env, stdin=devnull) + devnull.close() + shutil.copyfile(savedefconfig, defconfig) + +def build_many(allconf, targets): + print "Building %d target(s)" % len(targets) + for target in targets: + if all_options.updateconfigs: + update_config(allconf[target], all_options.updateconfigs) + build(target) + if failed_targets: + fail('\n '.join(["Failed targets:"] + + [target for target in failed_targets])) + +def main(): + global make_command + + check_kernel() + check_build() + + configs = scan_configs() + + usage = (""" + %prog [options] all -- Build all targets + %prog [options] target target ... -- List specific targets + %prog [options] perf -- Build all perf targets + %prog [options] noperf -- Build all non-perf targets""") + parser = OptionParser(usage=usage, version=version) + parser.add_option('--configs', action='store_true', + dest='configs', + help="Copy configs back into tree") + parser.add_option('--list', action='store_true', + dest='list', + help='List available targets') + parser.add_option('-v', '--verbose', action='store_true', + dest='verbose', + help='Output to stdout in addition to log file') + parser.add_option('--oldconfig', action='store_true', + dest='oldconfig', + help='Only process "make oldconfig"') + parser.add_option('--updateconfigs', + dest='updateconfigs', + help="Update defconfigs with provided option setting, " + "e.g. --updateconfigs=\'CONFIG_USE_THING=y\'") + parser.add_option('-j', '--jobs', type='int', dest="jobs", + help="Number of simultaneous jobs") + parser.add_option('-l', '--load-average', type='int', + dest='load_average', + help="Don't start multiple jobs unless load is below LOAD_AVERAGE") + parser.add_option('-k', '--keep-going', action='store_true', + dest='keep_going', default=False, + help="Keep building other targets if a target fails") + parser.add_option('-m', '--make-target', action='append', + help='Build the indicated make target (default: %s)' % + ' '.join(make_command)) + + (options, args) = parser.parse_args() + global all_options + all_options = options + + if options.list: + print "Available targets:" + for target in configs.keys(): + print " %s" % target + sys.exit(0) + + if options.oldconfig: + make_command = ["oldconfig"] + elif options.make_target: + make_command = options.make_target + + if options.jobs: + make_command.append("-j%d" % options.jobs) + if options.load_average: + make_command.append("-l%d" % options.load_average) + + if args == ['all']: + build_many(configs, configs.keys()) + elif args == ['perf']: + targets = [] + for t in configs.keys(): + if "perf" in t: + targets.append(t) + build_many(configs, targets) + elif args == ['noperf']: + targets = [] + for t in configs.keys(): + if "perf" not in t: + targets.append(t) + build_many(configs, targets) + elif len(args) > 0: + targets = [] + for t in args: + if t not in configs.keys(): + parser.error("Target '%s' not one of %s" % (t, configs.keys())) + targets.append(t) + build_many(configs, targets) + else: + parser.error("Must specify a target to build, or 'all'") + +if __name__ == "__main__": + main() diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index b0aa2c68059..2d4ce154401 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -7,6 +7,12 @@ use strict; +use constant BEFORE_SHORTTEXT => 0; +use constant IN_SHORTTEXT => 1; +use constant AFTER_SHORTTEXT => 2; +use constant CHECK_NEXT_SHORTTEXT => 3; +use constant SHORTTEXT_LIMIT => 75; + my $P = $0; $P =~ s@.*/@@g; @@ -1155,6 +1161,8 @@ sub process { my $prevrawline=""; my $stashline=""; my $stashrawline=""; + my $subjectline=""; + my $sublinenr=""; my $length; my $indent; @@ -1194,8 +1202,36 @@ sub process { my @setup_docs = (); my $setup_docs = 0; + my $in_code_block = 0; + my $exec_file = ""; + + my $shorttext = BEFORE_SHORTTEXT; + my $shorttext_exspc = 0; + sanitise_line_reset(); my $line; + # Before sanitizing the rawlines, collapse any header-continuation + # lines into a single line so they can be parsed meaningfully. + my $end_of_hdrs = 0; + foreach my $rawline (@rawlines) { + if ($rawline=~/^\s*$/) { + last; + } else { + $end_of_hdrs++; + } + } + my @continuation_hdrs; + foreach my $n (0 .. $end_of_hdrs) { + if ($rawlines[$n]=~/^\s+/) { + push @continuation_hdrs, $n; + } + } + while (my $n = pop @continuation_hdrs) { + $line = splice @rawlines, $n, 1; + $line=~s/^\s+/ /; + $rawlines[$n - 1] .= $line; + } + foreach my $rawline (@rawlines) { $linenr++; $line = $rawline; @@ -1346,15 +1382,84 @@ sub process { if ($realfile =~ m@^include/asm/@) { ERROR("do not modify files in include/asm, change architecture specific files in include/asm-\n" . "$here$rawline\n"); } + $in_code_block = 1; next; } - + elsif ($rawline =~ /^diff.+a\/(.+)\sb\/.+$/) { + $exec_file = $1; + $in_code_block = 0; + } + #Check state to make sure we aren't in code block. + elsif (!$in_code_block && + ($exec_file =~ /^.+\.[chS]$/ or + $exec_file =~ /^.+\.txt$/ or + $exec_file =~ /^.+\.ihex$/ or + $exec_file =~ /^.+\.hex$/ or + $exec_file =~ /^.+\.HEX$/ or + $exec_file =~ /^.+defconfig$/ or + $exec_file =~ /^Makefile$/ or + $exec_file =~ /^Kconfig$/) && + $rawline =~ /^new (file )?mode\s([0-9]+)$/ && + (oct($2) & 0111)) { + ERROR("Source file has +x permissions: " . + "$exec_file\n"); + } $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); my $hereline = "$here\n$rawline\n"; my $herecurr = "$here\n$rawline\n"; my $hereprev = "$here\n$prevrawline\n$rawline\n"; + if ($shorttext != AFTER_SHORTTEXT) { + if ($shorttext == IN_SHORTTEXT) { + if ($line=~/^---/ || $line=~/^diff.*/) { + $shorttext = AFTER_SHORTTEXT; + } elsif (length($line) > (SHORTTEXT_LIMIT + + $shorttext_exspc) + && $line !~ /^:([0-7]{6}\s){2} + ([[:xdigit:]]+\.* + \s){2}\w+\s\w+/xms) { + WARN("commit text line over " . + SHORTTEXT_LIMIT . + " characters\n" . $herecurr); + } + } elsif ($shorttext == CHECK_NEXT_SHORTTEXT) { +# The Subject line doesn't have to be the last header in the patch. +# Avoid moving to the IN_SHORTTEXT state until clear of all headers. +# Per RFC5322, continuation lines must be folded, so any left-justified +# text which looks like a header is definitely a header. + if ($line!~/^[\x21-\x39\x3b-\x7e]+:/) { + $shorttext = IN_SHORTTEXT; +# Check for Subject line followed by a blank line. + if (length($line) != 0) { + WARN("non-blank line after " . + "summary line\n" . + $sublinenr . $here . + "\n" . $subjectline . + "\n" . $line . "\n"); + } + } + } elsif ($line=~/^Subject: \[[^\]]*\] (.*)/) { + $shorttext = CHECK_NEXT_SHORTTEXT; + $subjectline = $line; + $sublinenr = "#$linenr & "; +# Check for Subject line less than line limit + if (length($1) > SHORTTEXT_LIMIT) { + WARN("summary line over " . + SHORTTEXT_LIMIT . + " characters\n" . $herecurr); + } + } elsif ($line=~/^ (.*)/) { + $shorttext = IN_SHORTTEXT; + $shorttext_exspc = 4; + if (length($1) > SHORTTEXT_LIMIT) { + WARN("summary line over " . + SHORTTEXT_LIMIT . + " characters\n" . $herecurr); + } + } + } + $cnt_lines++ if ($realcnt != 0); # Check for incorrect file permissions @@ -1377,6 +1482,14 @@ sub process { WARN("space required after Signed-off-by:\n" . $herecurr); } + if ($line =~ /^\s*signed-off-by:.*(quicinc|qualcomm)\.com/i) { + WARN("invalid Signed-off-by identity\n" . $line ); + } + } + +#check the patch for invalid author credentials + if ($line =~ /^From:.*(quicinc|qualcomm)\.com/) { + WARN("invalid author identity\n" . $line ); } # Check for wrappage within a valid hunk of the file @@ -1425,37 +1538,6 @@ sub process { $rpt_cleaners = 1; } -# check for Kconfig help text having a real description -# Only applies when adding the entry originally, after that we do not have -# sufficient context to determine whether it is indeed long enough. - if ($realfile =~ /Kconfig/ && - $line =~ /\+\s*(?:---)?help(?:---)?$/) { - my $length = 0; - my $cnt = $realcnt; - my $ln = $linenr + 1; - my $f; - my $is_end = 0; - while ($cnt > 0 && defined $lines[$ln - 1]) { - $f = $lines[$ln - 1]; - $cnt-- if ($lines[$ln - 1] !~ /^-/); - $is_end = $lines[$ln - 1] =~ /^\+/; - $ln++; - - next if ($f =~ /^-/); - $f =~ s/^.//; - $f =~ s/#.*//; - $f =~ s/^\s+//; - next if ($f =~ /^$/); - if ($f =~ /^\s*config\s/) { - $is_end = 1; - last; - } - $length++; - } - WARN("please write a paragraph that describes the config symbol fully\n" . $herecurr) if ($is_end && $length < 4); - #print "is_end<$is_end> length<$length>\n"; - } - # check we are in a valid source file if not then ignore this hunk next if ($realfile !~ /\.(h|c|s|S|pl|sh)$/); @@ -1464,6 +1546,7 @@ sub process { $rawline !~ /^.\s*\*\s*\@$Ident\s/ && !($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(KERN_\S+\s*|[^"]*))?"[X\t]*"\s*(?:|,|\)\s*;)\s*$/ || $line =~ /^\+\s*"[^"]*"\s*(?:\s*|,|\)\s*;)\s*$/) && + $realfile ne "scripts/checkpatch.pl" && $length > 80) { WARN("line over 80 characters\n" . $herecurr); @@ -2245,7 +2328,7 @@ sub process { # check spacing on parentheses if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && - $line !~ /for\s*\(\s+;/) { + $line !~ /for\s*\(\s+;/ && $line !~ /^\+\s*[A-Z_][A-Z\d_]*\(\s*\d+(\,.*)?\)\,?$/) { ERROR("space prohibited after that open parenthesis '('\n" . $herecurr); } if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && @@ -2439,7 +2522,7 @@ sub process { if ($realfile !~ m@/vmlinux.lds.h$@ && $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { my $ln = $linenr; - my $cnt = $realcnt; + my $cnt = $realcnt - 1; my ($off, $dstat, $dcond, $rest); my $ctx = ''; @@ -2495,12 +2578,19 @@ sub process { { } + # Extremely long macros may fall off the end of the + # available context without closing. Give a dangling + # backslash the benefit of the doubt and allow it + # to gobble any hanging open-parens. + $dstat =~ s/\(.+\\$/1/; + my $exceptions = qr{ $Declare| module_param_named| MODULE_PARAM_DESC| DECLARE_PER_CPU| DEFINE_PER_CPU| + CLK_[A-Z\d_]+| __typeof__\(| union| struct| @@ -2659,10 +2749,70 @@ sub process { WARN("Use of volatile is usually wrong: see Documentation/volatile-considered-harmful.txt\n" . $herecurr); } +# sys_open/read/write/close are not allowed in the kernel + if ($line =~ /\b(sys_(?:open|read|write|close))\b/) { + ERROR("$1 is inappropriate in kernel code.\n" . + $herecurr); + } + +# filp_open is a backdoor for sys_open + if ($line =~ /\b(filp_open)\b/) { + ERROR("$1 is inappropriate in kernel code.\n" . + $herecurr); + } + +# read[bwl] & write[bwl] use too many barriers, use the _relaxed variants + if ($line =~ /\b((?:read|write)[bwl])\b/) { + ERROR("Use of $1 is deprecated: use $1_relaxed\n\t" . + "with appropriate memory barriers instead.\n" . + $herecurr); + } + +# likewise, in/out[bwl] should be __raw_read/write[bwl]... + if ($line =~ /\b((in|out)([bwl]))\b/) { + my ($all, $pref, $suf) = ($1, $2, $3); + $pref =~ s/in/read/; + $pref =~ s/out/write/; + ERROR("Use of $all is deprecated: use " . + "__raw_$pref$suf\n\t" . + "with appropriate memory barriers instead.\n" . + $herecurr); + } + +# dsb is too ARMish, and should usually be mb. + if ($line =~ /\bdsb\b/) { + WARN("Use of dsb is discouranged: prefer mb.\n" . + $herecurr); + } + +# unbounded string functions are overflow risks + my %str_fns = ( + "sprintf" => "snprintf", + "strcpy" => "strncpy", + "strcat" => "strncat", + "strcmp" => "strncmp", + "strcasecmp" => "strncasecmp", + "strchr" => "strnchr", + "strstr" => "strnstr", + "strlen" => "strnlen", + ); + foreach my $k (keys %str_fns) { + if ($line =~ /\b$k\b/) { + ERROR("Use of $k is deprecated: " . + "use $str_fns{$k} instead.\n" . + $herecurr); + } + } + # warn about #if 0 if ($line =~ /^.\s*\#\s*if\s+0\b/) { - CHK("if this code is redundant consider removing it\n" . - $herecurr); + WARN("if this code is redundant consider removing it\n" + . $herecurr); + } +# warn about #if 1 + if ($line =~ /^.\s*\#\s*if\s+1\b/) { + WARN("if this code is required consider removing" + . " #if 1\n" . $herecurr); } # check for needless kfree() checks @@ -2695,6 +2845,11 @@ sub process { } } +# check the patch for use of mdelay + if ($line =~ /\bmdelay\s*\(/) { + WARN("use of mdelay() found: msleep() is the preferred API.\n" . $line ); + } + # warn about #ifdefs in C files # if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { # print "#ifdef in C files should be avoided\n"; @@ -2804,6 +2959,11 @@ sub process { WARN("Statements terminations use 1 semicolon\n" . $herecurr); } +# check for return codes on error paths + if ($line =~ /\breturn\s+-\d+/) { + ERROR("illegal return value, please use an error code\n" . $herecurr); + } + # check for gcc specific __FUNCTION__ if ($line =~ /__FUNCTION__/) { WARN("__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr); diff --git a/scripts/gcc-wrapper.py b/scripts/gcc-wrapper.py new file mode 100755 index 00000000000..cbe2eae7f43 --- /dev/null +++ b/scripts/gcc-wrapper.py @@ -0,0 +1,117 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2011, Code Aurora Forum. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Code Aurora nor +# the names of its contributors may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Invoke gcc, looking for warnings, and causing a failure if there are +# non-whitelisted warnings. + +import errno +import re +import os +import sys +import subprocess + +# Note that gcc uses unicode, which may depend on the locale. TODO: +# force LANG to be set to en_US.UTF-8 to get consistent warnings. + +allowed_warnings = set([ + "alignment.c:720", + "async.c:122", + "async.c:270", + "dir.c:43", + "dm.c:1053", + "dm.c:1080", + "dm-table.c:1120", + "dm-table.c:1126", + "drm_edid.c:1303", + "eventpoll.c:1143", + "f_mass_storage.c:3368", + "inode.c:72", + "inode.c:73", + "inode.c:74", + "msm_sdcc.c:126", + "msm_sdcc.c:128", + "nf_conntrack_netlink.c:790", + "nf_nat_standalone.c:118", + "return_address.c:62", + "soc-core.c:1719", + "xt_log.h:50", + "vx6953.c:3124", + ]) + +# Capture the name of the object file, can find it. +ofile = None + +warning_re = re.compile(r'''(.*/|)([^/]+\.[a-z]+:\d+):(\d+:)? warning:''') +def interpret_warning(line): + """Decode the message from gcc. The messages we care about have a filename, and a warning""" + line = line.rstrip('\n') + m = warning_re.match(line) + if m and m.group(2) not in allowed_warnings: + print "error, forbidden warning:", m.group(2) + + # If there is a warning, remove any object if it exists. + if ofile: + try: + os.remove(ofile) + except OSError: + pass + sys.exit(1) + +def run_gcc(): + args = sys.argv[1:] + # Look for -o + try: + i = args.index('-o') + global ofile + ofile = args[i+1] + except (ValueError, IndexError): + pass + + compiler = sys.argv[0] + + try: + proc = subprocess.Popen(args, stderr=subprocess.PIPE) + for line in proc.stderr: + print line, + interpret_warning(line) + + result = proc.wait() + except OSError as e: + result = e.errno + if result == errno.ENOENT: + print args[0] + ':',e.strerror + print 'Is your PATH set correctly?' + else: + print ' '.join(args), str(e) + + return result + +if __name__ == '__main__': + status = run_gcc() + sys.exit(status) diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c index 413c53693e6..d3e509edfd5 100644 --- a/scripts/mod/modpost.c +++ b/scripts/mod/modpost.c @@ -37,6 +37,8 @@ static int all_versions = 0; static int external_module = 0; /* Warn about section mismatch in vmlinux if set to 1 */ static int vmlinux_section_warnings = 1; +/* Exit with an error when there is a section mismatch if set to 1 */ +static int section_error_on_mismatch; /* Only warn about unresolved symbols */ static int warn_unresolved = 0; /* How a symbol is exported */ @@ -2068,7 +2070,7 @@ int main(int argc, char **argv) struct ext_sym_list *extsym_iter; struct ext_sym_list *extsym_start = NULL; - while ((opt = getopt(argc, argv, "i:I:e:cmsSo:awM:K:")) != -1) { + while ((opt = getopt(argc, argv, "i:I:e:cmsSo:awM:K:E")) != -1) { switch (opt) { case 'i': kernel_read = optarg; @@ -2106,6 +2108,9 @@ int main(int argc, char **argv) case 'w': warn_unresolved = 1; break; + case 'E': + section_error_on_mismatch = 1; + break; default: exit(1); } @@ -2154,11 +2159,23 @@ int main(int argc, char **argv) if (dump_write) write_dump(dump_write); - if (sec_mismatch_count && !sec_mismatch_verbose) - warn("modpost: Found %d section mismatch(es).\n" - "To see full details build your kernel with:\n" - "'make CONFIG_DEBUG_SECTION_MISMATCH=y'\n", - sec_mismatch_count); + + if (sec_mismatch_count && !sec_mismatch_verbose) { + merror( + "modpost: Found %d section mismatch(es).\n" + "To see full details build your kernel with:\n" + "'make CONFIG_DEBUG_SECTION_MISMATCH=y'\n", + sec_mismatch_count); + + } + + if (sec_mismatch_count && section_error_on_mismatch) { + err |= 1; + printf( + "To build the kernel despite the mismatches, " + "build with:\n'make CONFIG_NO_ERROR_ON_MISMATCH=y'\n" + "(NOTE: This is not recommended)\n"); + } return err; } diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 13b95ddfd68..50f6e07b8bf 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -6,8 +6,7 @@ * * 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. + * the Free Software Foundation; only version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 0e972485449..34712cbd488 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -5,8 +5,7 @@ * * 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. + * the Free Software Foundation; only version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 8224db5f043..8dbfdcc241d 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -57,6 +57,7 @@ source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" +source "sound/soc/msm/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 0af701644ad..fff81f23102 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ +obj-$(CONFIG_SND_SOC) += msm/ diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c index 42f699c7b28..f6735169477 100644 --- a/sound/soc/atmel/atmel-pcm.c +++ b/sound/soc/atmel/atmel-pcm.c @@ -18,8 +18,7 @@ * * 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. + * the Free Software Foundation; only version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -509,4 +508,4 @@ module_exit(snd_atmel_pcm_exit); MODULE_AUTHOR("Sedji Gaouaou "); MODULE_DESCRIPTION("Atmel PCM module"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/blackfin/bf5xx-ac97-pcm.c b/sound/soc/blackfin/bf5xx-ac97-pcm.c index 9e59f680bc1..3c8ea9c5b8c 100644 --- a/sound/soc/blackfin/bf5xx-ac97-pcm.c +++ b/sound/soc/blackfin/bf5xx-ac97-pcm.c @@ -12,8 +12,7 @@ * * 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. + * the Free Software Foundation; only version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -489,4 +488,4 @@ module_exit(snd_bf5xx_pcm_exit); MODULE_AUTHOR("Cliff Cai"); MODULE_DESCRIPTION("ADI Blackfin AC97 PCM DMA module"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.c b/sound/soc/blackfin/bf5xx-i2s-pcm.c index c42fb732e22..4d6c1651719 100644 --- a/sound/soc/blackfin/bf5xx-i2s-pcm.c +++ b/sound/soc/blackfin/bf5xx-i2s-pcm.c @@ -12,8 +12,7 @@ * * 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. + * the Free Software Foundation; only version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -328,4 +327,4 @@ module_exit(snd_bfin_i2s_pcm_exit); MODULE_AUTHOR("Cliff Cai"); MODULE_DESCRIPTION("ADI Blackfin I2S PCM DMA module"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.c b/sound/soc/blackfin/bf5xx-tdm-pcm.c index c95cc03d583..3c96242ef66 100644 --- a/sound/soc/blackfin/bf5xx-tdm-pcm.c +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.c @@ -12,8 +12,7 @@ * * 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. + * the Free Software Foundation; only version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -353,4 +352,4 @@ module_exit(snd_bfin_tdm_exit); MODULE_AUTHOR("Barry Song"); MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 922f59f9b82..b923a25650e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -96,6 +96,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS select SND_SOC_WM9713 if SND_SOC_AC97_BUS + select SND_SOC_TIMPANI if MARIMBA_CORE help Normally ASoC codec drivers are only built if a machine driver which uses them is also built since they are only usable with a machine @@ -249,6 +250,9 @@ config SND_SOC_UDA134X config SND_SOC_UDA1380 tristate +config SND_SOC_WCD9310 + tristate + config SND_SOC_WL1273 tristate @@ -387,3 +391,6 @@ config SND_SOC_WM2000 config SND_SOC_WM9090 tristate + +config SND_SOC_MSM_STUB + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fd8558406ef..78740aba441 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -38,6 +38,7 @@ snd-soc-twl4030-objs := twl4030.o snd-soc-twl6040-objs := twl6040.o snd-soc-uda134x-objs := uda134x.o snd-soc-uda1380-objs := uda1380.o +snd-soc-wcd9310-objs := wcd9310.o wcd9310-tables.o snd-soc-wl1273-objs := wl1273.o snd-soc-wm1250-ev1-objs := wm1250-ev1.o snd-soc-wm8350-objs := wm8350.o @@ -81,7 +82,8 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-jz4740-codec-objs := jz4740.o - +snd-soc-timpani-objs := timpani.o +snd-soc-msm-stub-objs := msm_stub.o # Amp snd-soc-lm4857-objs := lm4857.o snd-soc-max9877-objs := max9877.o @@ -130,6 +132,7 @@ obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o +obj-$(CONFIG_SND_SOC_WCD9310) += snd-soc-wcd9310.o obj-$(CONFIG_SND_SOC_WL1273) += snd-soc-wl1273.o obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o @@ -172,6 +175,7 @@ obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o +obj-$(CONFIG_SND_SOC_MSM_STUB) += snd-soc-msm-stub.o # Amp obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o diff --git a/sound/soc/codecs/msm_stub.c b/sound/soc/codecs/msm_stub.c new file mode 100644 index 00000000000..21691dfbe14 --- /dev/null +++ b/sound/soc/codecs/msm_stub.c @@ -0,0 +1,79 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include + +/* A dummy driver useful only to advertise hardware parameters */ +static struct snd_soc_dai_driver msm_stub_dais[] = { + { + .name = "msm-stub-rx", + .playback = { /* Support maximum range */ + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "msm-stub-tx", + .capture = { /* Support maximum range */ + .stream_name = "Record", + .channels_min = 1, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + } +}; + +static struct snd_soc_codec_driver soc_msm_stub = {}; + +static int __devinit msm_stub_dev_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, + &soc_msm_stub, msm_stub_dais, ARRAY_SIZE(msm_stub_dais)); +} + +static int __devexit msm_stub_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver msm_stub_driver = { + .driver = { + .name = "msm-stub-codec", + .owner = THIS_MODULE, + }, + .probe = msm_stub_dev_probe, + .remove = __devexit_p(msm_stub_dev_remove), +}; + +static int __init msm_stub_init(void) +{ + return platform_driver_register(&msm_stub_driver); +} +module_init(msm_stub_init); + +static void __exit msm_stub_exit(void) +{ + platform_driver_unregister(&msm_stub_driver); +} +module_exit(msm_stub_exit); + +MODULE_DESCRIPTION("Generic MSM CODEC driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/timpani.c b/sound/soc/codecs/timpani.c new file mode 100644 index 00000000000..786b2d6b06a --- /dev/null +++ b/sound/soc/codecs/timpani.c @@ -0,0 +1,482 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* Debug purpose */ +#include +#include +#include +/* End of debug purpose */ + +#define ADIE_CODEC_MAX 2 + +struct adie_codec_register { + u8 reg; + u8 mask; + u8 val; +}; + +static struct adie_codec_register dmic_on[] = { + {0x80, 0x05, 0x05}, + {0x80, 0x05, 0x00}, + {0x83, 0x0C, 0x00}, + {0x8A, 0xF0, 0x30}, + {0x86, 0xFF, 0xAC}, + {0x87, 0xFF, 0xAC}, + {0x8A, 0xF0, 0xF0}, + {0x82, 0x1F, 0x1E}, + {0x83, 0x0C, 0x0C}, + {0x92, 0x3F, 0x21}, + {0x94, 0x3F, 0x24}, + {0xA3, 0x39, 0x01}, + {0xA8, 0x0F, 0x00}, + {0xAB, 0x3F, 0x00}, + {0x86, 0xFF, 0x00}, + {0x87, 0xFF, 0x00}, + {0x8A, 0xF0, 0xC0}, +}; + +static struct adie_codec_register dmic_off[] = { + {0x8A, 0xF0, 0xF0}, + {0x83, 0x0C, 0x00}, + {0x92, 0xFF, 0x00}, + {0x94, 0xFF, 0x1B}, +}; + +static struct adie_codec_register spk_on[] = { + {0x80, 0x02, 0x02}, + {0x80, 0x02, 0x00}, + {0x83, 0x03, 0x00}, + {0x8A, 0x0F, 0x03}, + {0xA3, 0x02, 0x02}, + {0x84, 0xFF, 0x00}, + {0x85, 0xFF, 0x00}, + {0x8A, 0x0F, 0x0C}, + {0x81, 0xFF, 0x0E}, + {0x83, 0x03, 0x03}, + {0x24, 0x6F, 0x6C}, + {0xB7, 0x01, 0x01}, + {0x31, 0x01, 0x01}, + {0x32, 0xF8, 0x08}, + {0x32, 0xF8, 0x48}, + {0x32, 0xF8, 0xF8}, + {0xE0, 0xFE, 0xAC}, + {0xE1, 0xFE, 0xAC}, + {0x3A, 0x24, 0x24}, + {0xE0, 0xFE, 0x3C}, + {0xE1, 0xFE, 0x3C}, + {0xE0, 0xFE, 0x1C}, + {0xE1, 0xFE, 0x1C}, + {0xE0, 0xFE, 0x10}, + {0xE1, 0xFE, 0x10}, +}; + +static struct adie_codec_register spk_off[] = { + {0x8A, 0x0F, 0x0F}, + {0xE0, 0xFE, 0x1C}, + {0xE1, 0xFE, 0x1C}, + {0xE0, 0xFE, 0x3C}, + {0xE1, 0xFE, 0x3C}, + {0xE0, 0xFC, 0xAC}, + {0xE1, 0xFC, 0xAC}, + {0x32, 0xF8, 0x00}, + {0x31, 0x05, 0x00}, + {0x3A, 0x24, 0x00}, +}; + +static struct adie_codec_register spk_mute[] = { + {0x84, 0xFF, 0xAC}, + {0x85, 0xFF, 0xAC}, + {0x8A, 0x0F, 0x0C}, +}; + +static struct adie_codec_register spk_unmute[] = { + {0x84, 0xFF, 0x00}, + {0x85, 0xFF, 0x00}, + {0x8A, 0x0F, 0x0C}, +}; + +struct adie_codec_path { + int rate; /* sample rate of path */ + u32 reg_owner; +}; + +struct timpani_drv_data { /* member undecided */ + struct snd_soc_codec codec; + struct adie_codec_path path[ADIE_CODEC_MAX]; + u32 ref_cnt; + struct marimba_codec_platform_data *codec_pdata; +}; + +static struct snd_soc_codec *timpani_codec; + +enum /* regaccess blk id */ +{ + RA_BLOCK_RX1 = 0, + RA_BLOCK_RX2, + RA_BLOCK_TX1, + RA_BLOCK_TX2, + RA_BLOCK_LB, + RA_BLOCK_SHARED_RX_LB, + RA_BLOCK_SHARED_TX, + RA_BLOCK_TXFE1, + RA_BLOCK_TXFE2, + RA_BLOCK_PA_COMMON, + RA_BLOCK_PA_EAR, + RA_BLOCK_PA_HPH, + RA_BLOCK_PA_LINE, + RA_BLOCK_PA_AUX, + RA_BLOCK_ADC, + RA_BLOCK_DMIC, + RA_BLOCK_TX_I2S, + RA_BLOCK_DRV, + RA_BLOCK_TEST, + RA_BLOCK_RESERVED, + RA_BLOCK_NUM, +}; + +enum /* regaccess onwer ID */ +{ + RA_OWNER_NONE = 0, + RA_OWNER_PATH_RX1, + RA_OWNER_PATH_RX2, + RA_OWNER_PATH_TX1, + RA_OWNER_PATH_TX2, + RA_OWNER_PATH_LB, + RA_OWNER_DRV, + RA_OWNER_NUM, +}; + +struct reg_acc_blk_cfg { + u8 valid_owners[RA_OWNER_NUM]; +}; + +struct timpani_regaccess { + u8 reg_addr; + u8 blk_mask[RA_BLOCK_NUM]; + u8 reg_mask; + u8 reg_default; +}; + +static unsigned int timpani_codec_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct marimba *pdrv = codec->control_data; + int rc; + u8 val; + + rc = marimba_read(pdrv, reg, &val, 1); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: fail to write reg %x\n", __func__, reg); + return 0; + } + return val; +} + +static int timpani_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct marimba *pdrv = codec->control_data; + int rc; + + rc = marimba_write_bit_mask(pdrv, reg, (u8 *)&value, 1, 0xFF); + if (IS_ERR_VALUE(rc)) { + pr_err("%s: fail to write reg %x\n", __func__, reg); + return -EIO; + } + pr_debug("%s: write reg %x val %x\n", __func__, reg, value); + return 0; +} + +static void timpani_codec_bring_up(struct snd_soc_codec *codec) +{ + struct timpani_drv_data *timpani = snd_soc_codec_get_drvdata(codec); + int rc; + + if (timpani->codec_pdata && + timpani->codec_pdata->marimba_codec_power) { + if (timpani->ref_cnt) + return; + /* Codec power up sequence */ + rc = timpani->codec_pdata->marimba_codec_power(1); + if (rc) + pr_err("%s: could not power up timpani " + "codec\n", __func__); + else { + timpani_codec_write(codec, 0xFF, 0x08); + timpani_codec_write(codec, 0xFF, 0x0A); + timpani_codec_write(codec, 0xFF, 0x0E); + timpani_codec_write(codec, 0xFF, 0x07); + timpani_codec_write(codec, 0xFF, 0x17); + timpani_codec_write(codec, TIMPANI_A_MREF, 0x22); + msleep(15); + timpani->ref_cnt++; + } + } +} + +static void timpani_codec_bring_down(struct snd_soc_codec *codec) +{ + struct timpani_drv_data *timpani = snd_soc_codec_get_drvdata(codec); + int rc; + + if (timpani->codec_pdata && + timpani->codec_pdata->marimba_codec_power) { + timpani->ref_cnt--; + if (timpani->ref_cnt >= 1) + return; + timpani_codec_write(codec, TIMPANI_A_MREF, TIMPANI_MREF_POR); + timpani_codec_write(codec, 0xFF, 0x07); + timpani_codec_write(codec, 0xFF, 0x06); + timpani_codec_write(codec, 0xFF, 0x0E); + timpani_codec_write(codec, 0xFF, 0x08); + rc = timpani->codec_pdata->marimba_codec_power(0); + if (rc) + pr_err("%s: could not power down timpani " + "codec\n", __func__); + } +} + +static void timpani_dmic_config(struct snd_soc_codec *codec, int on) +{ + struct adie_codec_register *regs; + int regs_sz, i; + + if (on) { + regs = dmic_on; + regs_sz = ARRAY_SIZE(dmic_on); + } else { + regs = dmic_off; + regs_sz = ARRAY_SIZE(dmic_off); + } + + for (i = 0; i < regs_sz; i++) + timpani_codec_write(codec, regs[i].reg, + (regs[i].mask & regs[i].val)); +} + +static void timpani_spk_config(struct snd_soc_codec *codec, int on) +{ + struct adie_codec_register *regs; + int regs_sz, i; + + if (on) { + regs = spk_on; + regs_sz = ARRAY_SIZE(spk_on); + } else { + regs = spk_off; + regs_sz = ARRAY_SIZE(spk_off); + } + + for (i = 0; i < regs_sz; i++) + timpani_codec_write(codec, regs[i].reg, + (regs[i].mask & regs[i].val)); +} + +static int timpani_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + pr_info("%s()\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_info("%s: playback\n", __func__); + timpani_codec_bring_up(codec); + timpani_spk_config(codec, 1); + } else { + pr_info("%s: Capture\n", __func__); + timpani_codec_bring_up(codec); + timpani_dmic_config(codec, 1); + } + return 0; +} + +static void timpani_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + pr_info("%s()\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + timpani_codec_bring_down(codec); + timpani_spk_config(codec, 0); + } else { + timpani_codec_bring_down(codec); + timpani_dmic_config(codec, 0); + } + return; +} + +int digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct adie_codec_register *regs; + int regs_sz, i; + + if (mute) { + regs = spk_mute; + regs_sz = ARRAY_SIZE(spk_mute); + } else { + regs = spk_unmute; + regs_sz = ARRAY_SIZE(spk_unmute); + } + + for (i = 0; i < regs_sz; i++) { + timpani_codec_write(codec, regs[i].reg, + (regs[i].mask & regs[i].val)); + msleep(10); + } + + return 0; +} + +static struct snd_soc_dai_ops timpani_dai_ops = { + .startup = timpani_startup, + .shutdown = timpani_shutdown, +}; + +struct snd_soc_dai timpani_codec_dai[] = { + { + .name = "TIMPANI Rx", + .playback = { + .stream_name = "Handset Playback", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_max = 96000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &timpani_dai_ops, + }, + { + .name = "TIMPANI Tx", + .capture = { + .stream_name = "Handset Capture", + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_max = 96000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &timpani_dai_ops, + } +}; + +static int timpani_soc_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (!timpani_codec) { + dev_err(&pdev->dev, "core driver not yet probed\n"); + return -ENODEV; + } + + socdev->card->codec = timpani_codec; + codec = timpani_codec; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + dev_err(codec->dev, "failed to create pcms\n"); + return ret; +} + +/* power down chip */ +static int timpani_soc_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_timpani = { + .probe = timpani_soc_probe, + .remove = timpani_soc_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_timpani); + +static int timpani_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_codec *codec; + struct timpani_drv_data *priv; + + pr_info("%s()\n", __func__); + priv = kzalloc(sizeof(struct timpani_drv_data), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + codec = &priv->codec; + snd_soc_codec_set_drvdata(codec, priv); + priv->codec_pdata = pdev->dev.platform_data; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->name = "TIMPANI"; + codec->owner = THIS_MODULE; + codec->read = timpani_codec_read; + codec->write = timpani_codec_write; + codec->dai = timpani_codec_dai; + codec->num_dai = ARRAY_SIZE(timpani_codec_dai); + codec->control_data = platform_get_drvdata(pdev); + timpani_codec = codec; + + snd_soc_register_dais(timpani_codec_dai, ARRAY_SIZE(timpani_codec_dai)); + snd_soc_register_codec(codec); + + return 0; +} + +static struct platform_driver timpani_codec_driver = { + .probe = timpani_codec_probe, + .driver = { + .name = "timpani_codec", + .owner = THIS_MODULE, + }, +}; + +static int __init timpani_codec_init(void) +{ + return platform_driver_register(&timpani_codec_driver); +} + +static void __exit timpani_codec_exit(void) +{ + platform_driver_unregister(&timpani_codec_driver); +} + +module_init(timpani_codec_init); +module_exit(timpani_codec_exit); + +MODULE_DESCRIPTION("Timpani codec driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/timpani.h b/sound/soc/codecs/timpani.h new file mode 100644 index 00000000000..bd14eea3a6c --- /dev/null +++ b/sound/soc/codecs/timpani.h @@ -0,0 +1,15 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#define NUM_I2S 2 +extern struct snd_soc_dai timpani_codec_dai[NUM_I2S]; +extern struct snd_soc_codec_device soc_codec_dev_timpani; diff --git a/sound/soc/codecs/wcd9310-tables.c b/sound/soc/codecs/wcd9310-tables.c new file mode 100644 index 00000000000..bc6bf4f1c1c --- /dev/null +++ b/sound/soc/codecs/wcd9310-tables.c @@ -0,0 +1,1024 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "wcd9310.h" + +const u8 tabla_reg_readable[TABLA_CACHE_SIZE] = { + [TABLA_A_CHIP_CTL] = 1, + [TABLA_A_CHIP_STATUS] = 1, + [TABLA_A_CHIP_ID_BYTE_0] = 1, + [TABLA_A_CHIP_ID_BYTE_1] = 1, + [TABLA_A_CHIP_ID_BYTE_2] = 1, + [TABLA_A_CHIP_ID_BYTE_3] = 1, + [TABLA_A_CHIP_VERSION] = 1, + [TABLA_A_SB_VERSION] = 1, + [TABLA_A_SLAVE_ID_1] = 1, + [TABLA_A_SLAVE_ID_2] = 1, + [TABLA_A_SLAVE_ID_3] = 1, + [TABLA_A_PIN_CTL_OE0] = 1, + [TABLA_A_PIN_CTL_OE1] = 1, + [TABLA_A_PIN_CTL_DATA0] = 1, + [TABLA_A_PIN_CTL_DATA1] = 1, + [TABLA_A_HDRIVE_GENERIC] = 1, + [TABLA_A_HDRIVE_OVERRIDE] = 1, + [TABLA_A_ANA_CSR_WAIT_STATE] = 1, + [TABLA_A_PROCESS_MONITOR_CTL0] = 1, + [TABLA_A_PROCESS_MONITOR_CTL1] = 1, + [TABLA_A_PROCESS_MONITOR_CTL2] = 1, + [TABLA_A_PROCESS_MONITOR_CTL3] = 1, + [TABLA_A_QFUSE_CTL] = 1, + [TABLA_A_QFUSE_STATUS] = 1, + [TABLA_A_QFUSE_DATA_OUT0] = 1, + [TABLA_A_QFUSE_DATA_OUT1] = 1, + [TABLA_A_QFUSE_DATA_OUT2] = 1, + [TABLA_A_QFUSE_DATA_OUT3] = 1, + [TABLA_A_CDC_CTL] = 1, + [TABLA_A_LEAKAGE_CTL] = 1, + [TABLA_A_INTR_MODE] = 1, + [TABLA_A_INTR_MASK0] = 1, + [TABLA_A_INTR_MASK1] = 1, + [TABLA_A_INTR_MASK2] = 1, + [TABLA_A_INTR_STATUS0] = 1, + [TABLA_A_INTR_STATUS1] = 1, + [TABLA_A_INTR_STATUS2] = 1, + [TABLA_A_INTR_CLEAR0] = 0, + [TABLA_A_INTR_CLEAR1] = 0, + [TABLA_A_INTR_CLEAR2] = 0, + [TABLA_A_INTR_LEVEL0] = 1, + [TABLA_A_INTR_LEVEL1] = 1, + [TABLA_A_INTR_LEVEL2] = 1, + [TABLA_A_INTR_TEST0] = 1, + [TABLA_A_INTR_TEST1] = 1, + [TABLA_A_INTR_TEST2] = 1, + [TABLA_A_INTR_SET0] = 1, + [TABLA_A_INTR_SET1] = 1, + [TABLA_A_INTR_SET2] = 1, + [TABLA_A_CDC_TX_I2S_SCK_MODE] = 1, + [TABLA_A_CDC_TX_I2S_WS_MODE] = 1, + [TABLA_A_CDC_DMIC_DATA0_MODE] = 1, + [TABLA_A_CDC_DMIC_CLK0_MODE] = 1, + [TABLA_A_CDC_DMIC_DATA1_MODE] = 1, + [TABLA_A_CDC_DMIC_CLK1_MODE] = 1, + [TABLA_A_CDC_RX_I2S_SCK_MODE] = 1, + [TABLA_A_CDC_RX_I2S_WS_MODE] = 1, + [TABLA_A_CDC_DMIC_DATA2_MODE] = 1, + [TABLA_A_CDC_DMIC_CLK2_MODE] = 1, + [TABLA_A_CDC_INTR_MODE] = 1, + [TABLA_A_BIAS_REF_CTL] = 1, + [TABLA_A_BIAS_CENTRAL_BG_CTL] = 1, + [TABLA_A_BIAS_PRECHRG_CTL] = 1, + [TABLA_A_BIAS_CURR_CTL_1] = 1, + [TABLA_A_BIAS_CURR_CTL_2] = 1, + [TABLA_A_BIAS_CONFIG_MODE_BG_CTL] = 1, + [TABLA_A_BIAS_BG_STATUS] = 1, + [TABLA_A_CLK_BUFF_EN1] = 1, + [TABLA_A_CLK_BUFF_EN2] = 1, + [TABLA_A_LDO_H_MODE_1] = 1, + [TABLA_A_LDO_H_MODE_2] = 1, + [TABLA_A_LDO_H_LOOP_CTL] = 1, + [TABLA_A_LDO_H_COMP_1] = 1, + [TABLA_A_LDO_H_COMP_2] = 1, + [TABLA_A_LDO_H_BIAS_1] = 1, + [TABLA_A_LDO_H_BIAS_2] = 1, + [TABLA_A_LDO_H_BIAS_3] = 1, + [TABLA_A_LDO_L_MODE_1] = 1, + [TABLA_A_LDO_L_MODE_2] = 1, + [TABLA_A_LDO_L_LOOP_CTL] = 1, + [TABLA_A_LDO_L_COMP_1] = 1, + [TABLA_A_LDO_L_COMP_2] = 1, + [TABLA_A_LDO_L_BIAS_1] = 1, + [TABLA_A_LDO_L_BIAS_2] = 1, + [TABLA_A_LDO_L_BIAS_3] = 1, + [TABLA_A_MICB_CFILT_1_CTL] = 1, + [TABLA_A_MICB_CFILT_1_VAL] = 1, + [TABLA_A_MICB_CFILT_1_PRECHRG] = 1, + [TABLA_A_MICB_1_CTL] = 1, + [TABLA_A_MICB_1_INT_RBIAS] = 1, + [TABLA_A_MICB_1_MBHC] = 1, + [TABLA_A_MICB_CFILT_2_CTL] = 1, + [TABLA_A_MICB_CFILT_2_VAL] = 1, + [TABLA_A_MICB_CFILT_2_PRECHRG] = 1, + [TABLA_A_MICB_2_CTL] = 1, + [TABLA_A_MICB_2_INT_RBIAS] = 1, + [TABLA_A_MICB_2_MBHC] = 1, + [TABLA_A_MICB_CFILT_3_CTL] = 1, + [TABLA_A_MICB_CFILT_3_VAL] = 1, + [TABLA_A_MICB_CFILT_3_PRECHRG] = 1, + [TABLA_A_MICB_3_CTL] = 1, + [TABLA_A_MICB_3_INT_RBIAS] = 1, + [TABLA_A_MICB_3_MBHC] = 1, + [TABLA_A_MICB_4_CTL] = 1, + [TABLA_A_MICB_4_INT_RBIAS] = 1, + [TABLA_A_MICB_4_MBHC] = 1, + [TABLA_A_TX_COM_BIAS] = 1, + [TABLA_A_MBHC_SCALING_MUX_1] = 1, + [TABLA_A_MBHC_SCALING_MUX_2] = 1, + [TABLA_A_TX_SUP_SWITCH_CTRL_1] = 1, + [TABLA_A_TX_SUP_SWITCH_CTRL_2] = 1, + [TABLA_A_TX_1_2_EN] = 1, + [TABLA_A_TX_1_2_TEST_EN] = 1, + [TABLA_A_TX_1_2_ADC_CH1] = 1, + [TABLA_A_TX_1_2_ADC_CH2] = 1, + [TABLA_A_TX_1_2_ATEST_REFCTRL] = 1, + [TABLA_A_TX_1_2_TEST_CTL] = 1, + [TABLA_A_TX_1_2_TEST_BLOCK_EN] = 1, + [TABLA_A_TX_1_2_TXFE_CLKDIV] = 1, + [TABLA_A_TX_1_2_SAR_ERR_CH1] = 1, + [TABLA_A_TX_1_2_SAR_ERR_CH2] = 1, + [TABLA_A_TX_3_4_EN] = 1, + [TABLA_A_TX_3_4_TEST_EN] = 1, + [TABLA_A_TX_3_4_ADC_CH3] = 1, + [TABLA_A_TX_3_4_ADC_CH4] = 1, + [TABLA_A_TX_3_4_ATEST_REFCTRL] = 1, + [TABLA_A_TX_3_4_TEST_CTL] = 1, + [TABLA_A_TX_3_4_TEST_BLOCK_EN] = 1, + [TABLA_A_TX_3_4_TXFE_CKDIV] = 1, + [TABLA_A_TX_3_4_SAR_ERR_CH3] = 1, + [TABLA_A_TX_3_4_SAR_ERR_CH4] = 1, + [TABLA_A_TX_5_6_EN] = 1, + [TABLA_A_TX_5_6_TEST_EN] = 1, + [TABLA_A_TX_5_6_ADC_CH5] = 1, + [TABLA_A_TX_5_6_ADC_CH6] = 1, + [TABLA_A_TX_5_6_ATEST_REFCTRL] = 1, + [TABLA_A_TX_5_6_TEST_CTL] = 1, + [TABLA_A_TX_5_6_TEST_BLOCK_EN] = 1, + [TABLA_A_TX_5_6_TXFE_CKDIV] = 1, + [TABLA_A_TX_5_6_SAR_ERR_CH5] = 1, + [TABLA_A_TX_5_6_SAR_ERR_CH6] = 1, + [TABLA_A_TX_7_MBHC_EN] = 1, + [TABLA_A_TX_7_MBHC_ATEST_REFCTRL] = 1, + [TABLA_A_TX_7_MBHC_ADC] = 1, + [TABLA_A_TX_7_MBHC_TEST_CTL] = 1, + [TABLA_A_TX_7_MBHC_SAR_ERR] = 1, + [TABLA_A_TX_7_TXFE_CLKDIV] = 1, + [TABLA_A_AUX_COM_CTL] = 1, + [TABLA_A_AUX_COM_ATEST] = 1, + [TABLA_A_AUX_L_EN] = 1, + [TABLA_A_AUX_L_GAIN] = 1, + [TABLA_A_AUX_L_PA_CONN] = 1, + [TABLA_A_AUX_L_PA_CONN_INV] = 1, + [TABLA_A_AUX_R_EN] = 1, + [TABLA_A_AUX_R_GAIN] = 1, + [TABLA_A_AUX_R_PA_CONN] = 1, + [TABLA_A_AUX_R_PA_CONN_INV] = 1, + [TABLA_A_CP_EN] = 1, + [TABLA_A_CP_CLK] = 1, + [TABLA_A_CP_STATIC] = 1, + [TABLA_A_CP_DCC1] = 1, + [TABLA_A_CP_DCC3] = 1, + [TABLA_A_CP_ATEST] = 1, + [TABLA_A_CP_DTEST] = 1, + [TABLA_A_RX_COM_TIMER_DIV] = 1, + [TABLA_A_RX_COM_OCP_CTL] = 1, + [TABLA_A_RX_COM_OCP_COUNT] = 1, + [TABLA_A_RX_COM_DAC_CTL] = 1, + [TABLA_A_RX_COM_BIAS] = 1, + [TABLA_A_RX_HPH_BIAS_PA] = 1, + [TABLA_A_RX_HPH_BIAS_LDO] = 1, + [TABLA_A_RX_HPH_BIAS_CNP] = 1, + [TABLA_A_RX_HPH_BIAS_WG] = 1, + [TABLA_A_RX_HPH_OCP_CTL] = 1, + [TABLA_A_RX_HPH_CNP_EN] = 1, + [TABLA_A_RX_HPH_CNP_WG_CTL] = 1, + [TABLA_A_RX_HPH_CNP_WG_TIME] = 1, + [TABLA_A_RX_HPH_L_GAIN] = 1, + [TABLA_A_RX_HPH_L_TEST] = 1, + [TABLA_A_RX_HPH_L_PA_CTL] = 1, + [TABLA_A_RX_HPH_L_DAC_CTL] = 1, + [TABLA_A_RX_HPH_L_ATEST] = 1, + [TABLA_A_RX_HPH_L_STATUS] = 1, + [TABLA_A_RX_HPH_R_GAIN] = 1, + [TABLA_A_RX_HPH_R_TEST] = 1, + [TABLA_A_RX_HPH_R_PA_CTL] = 1, + [TABLA_A_RX_HPH_R_DAC_CTL] = 1, + [TABLA_A_RX_HPH_R_ATEST] = 1, + [TABLA_A_RX_HPH_R_STATUS] = 1, + [TABLA_A_RX_EAR_BIAS_PA] = 1, + [TABLA_A_RX_EAR_BIAS_CMBUFF] = 1, + [TABLA_A_RX_EAR_EN] = 1, + [TABLA_A_RX_EAR_GAIN] = 1, + [TABLA_A_RX_EAR_CMBUFF] = 1, + [TABLA_A_RX_EAR_ICTL] = 1, + [TABLA_A_RX_EAR_CCOMP] = 1, + [TABLA_A_RX_EAR_VCM] = 1, + [TABLA_A_RX_EAR_CNP] = 1, + [TABLA_A_RX_EAR_ATEST] = 1, + [TABLA_A_RX_EAR_STATUS] = 1, + [TABLA_A_RX_LINE_BIAS_PA] = 1, + [TABLA_A_RX_LINE_BIAS_DAC] = 1, + [TABLA_A_RX_LINE_BIAS_CNP] = 1, + [TABLA_A_RX_LINE_COM] = 1, + [TABLA_A_RX_LINE_CNP_EN] = 1, + [TABLA_A_RX_LINE_CNP_WG_CTL] = 1, + [TABLA_A_RX_LINE_CNP_WG_TIME] = 1, + [TABLA_A_RX_LINE_1_GAIN] = 1, + [TABLA_A_RX_LINE_1_TEST] = 1, + [TABLA_A_RX_LINE_1_DAC_CTL] = 1, + [TABLA_A_RX_LINE_1_STATUS] = 1, + [TABLA_A_RX_LINE_2_GAIN] = 1, + [TABLA_A_RX_LINE_2_TEST] = 1, + [TABLA_A_RX_LINE_2_DAC_CTL] = 1, + [TABLA_A_RX_LINE_2_STATUS] = 1, + [TABLA_A_RX_LINE_3_GAIN] = 1, + [TABLA_A_RX_LINE_3_TEST] = 1, + [TABLA_A_RX_LINE_3_DAC_CTL] = 1, + [TABLA_A_RX_LINE_3_STATUS] = 1, + [TABLA_A_RX_LINE_4_GAIN] = 1, + [TABLA_A_RX_LINE_4_TEST] = 1, + [TABLA_A_RX_LINE_4_DAC_CTL] = 1, + [TABLA_A_RX_LINE_4_STATUS] = 1, + [TABLA_A_RX_LINE_5_GAIN] = 1, + [TABLA_A_RX_LINE_5_TEST] = 1, + [TABLA_A_RX_LINE_5_DAC_CTL] = 1, + [TABLA_A_RX_LINE_5_STATUS] = 1, + [TABLA_A_RX_LINE_CNP_DBG] = 1, + [TABLA_A_MBHC_HPH] = 1, + [TABLA_A_CONFIG_MODE_FREQ] = 1, + [TABLA_A_CONFIG_MODE_TEST] = 1, + [TABLA_A_CONFIG_MODE_STATUS] = 1, + [TABLA_A_CONFIG_MODE_TUNER] = 1, + [TABLA_A_CDC_TX1_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX2_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX3_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX4_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX5_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX6_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX7_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX8_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX9_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX10_VOL_CTL_TIMER] = 1, + [TABLA_A_CDC_TX1_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX2_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX3_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX4_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX5_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX6_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX7_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX8_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX9_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX10_VOL_CTL_GAIN] = 1, + [TABLA_A_CDC_TX1_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX2_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX3_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX4_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX5_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX6_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX7_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX8_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX9_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX10_VOL_CTL_CFG] = 1, + [TABLA_A_CDC_TX1_MUX_CTL] = 1, + [TABLA_A_CDC_TX2_MUX_CTL] = 1, + [TABLA_A_CDC_TX3_MUX_CTL] = 1, + [TABLA_A_CDC_TX4_MUX_CTL] = 1, + [TABLA_A_CDC_TX5_MUX_CTL] = 1, + [TABLA_A_CDC_TX6_MUX_CTL] = 1, + [TABLA_A_CDC_TX7_MUX_CTL] = 1, + [TABLA_A_CDC_TX8_MUX_CTL] = 1, + [TABLA_A_CDC_TX9_MUX_CTL] = 1, + [TABLA_A_CDC_TX10_MUX_CTL] = 1, + [TABLA_A_CDC_TX1_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX2_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX3_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX4_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX5_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX6_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX7_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX8_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX9_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX10_CLK_FS_CTL] = 1, + [TABLA_A_CDC_TX1_DMIC_CTL] = 1, + [TABLA_A_CDC_TX2_DMIC_CTL] = 1, + [TABLA_A_CDC_TX3_DMIC_CTL] = 1, + [TABLA_A_CDC_TX4_DMIC_CTL] = 1, + [TABLA_A_CDC_TX5_DMIC_CTL] = 1, + [TABLA_A_CDC_TX6_DMIC_CTL] = 1, + [TABLA_A_CDC_TX7_DMIC_CTL] = 1, + [TABLA_A_CDC_TX8_DMIC_CTL] = 1, + [TABLA_A_CDC_TX9_DMIC_CTL] = 1, + [TABLA_A_CDC_TX10_DMIC_CTL] = 1, + [TABLA_A_CDC_SRC1_PDA_CFG] = 1, + [TABLA_A_CDC_SRC2_PDA_CFG] = 1, + [TABLA_A_CDC_SRC1_FS_CTL] = 1, + [TABLA_A_CDC_SRC2_FS_CTL] = 1, + [TABLA_A_CDC_RX1_B1_CTL] = 1, + [TABLA_A_CDC_RX2_B1_CTL] = 1, + [TABLA_A_CDC_RX3_B1_CTL] = 1, + [TABLA_A_CDC_RX4_B1_CTL] = 1, + [TABLA_A_CDC_RX5_B1_CTL] = 1, + [TABLA_A_CDC_RX6_B1_CTL] = 1, + [TABLA_A_CDC_RX7_B1_CTL] = 1, + [TABLA_A_CDC_RX1_B2_CTL] = 1, + [TABLA_A_CDC_RX2_B2_CTL] = 1, + [TABLA_A_CDC_RX3_B2_CTL] = 1, + [TABLA_A_CDC_RX4_B2_CTL] = 1, + [TABLA_A_CDC_RX5_B2_CTL] = 1, + [TABLA_A_CDC_RX6_B2_CTL] = 1, + [TABLA_A_CDC_RX7_B2_CTL] = 1, + [TABLA_A_CDC_RX1_B3_CTL] = 1, + [TABLA_A_CDC_RX2_B3_CTL] = 1, + [TABLA_A_CDC_RX3_B3_CTL] = 1, + [TABLA_A_CDC_RX4_B3_CTL] = 1, + [TABLA_A_CDC_RX5_B3_CTL] = 1, + [TABLA_A_CDC_RX6_B3_CTL] = 1, + [TABLA_A_CDC_RX7_B3_CTL] = 1, + [TABLA_A_CDC_RX1_B4_CTL] = 1, + [TABLA_A_CDC_RX2_B4_CTL] = 1, + [TABLA_A_CDC_RX3_B4_CTL] = 1, + [TABLA_A_CDC_RX4_B4_CTL] = 1, + [TABLA_A_CDC_RX5_B4_CTL] = 1, + [TABLA_A_CDC_RX6_B4_CTL] = 1, + [TABLA_A_CDC_RX7_B4_CTL] = 1, + [TABLA_A_CDC_RX1_B5_CTL] = 1, + [TABLA_A_CDC_RX2_B5_CTL] = 1, + [TABLA_A_CDC_RX3_B5_CTL] = 1, + [TABLA_A_CDC_RX4_B5_CTL] = 1, + [TABLA_A_CDC_RX5_B5_CTL] = 1, + [TABLA_A_CDC_RX6_B5_CTL] = 1, + [TABLA_A_CDC_RX7_B5_CTL] = 1, + [TABLA_A_CDC_RX1_B6_CTL] = 1, + [TABLA_A_CDC_RX2_B6_CTL] = 1, + [TABLA_A_CDC_RX3_B6_CTL] = 1, + [TABLA_A_CDC_RX4_B6_CTL] = 1, + [TABLA_A_CDC_RX5_B6_CTL] = 1, + [TABLA_A_CDC_RX6_B6_CTL] = 1, + [TABLA_A_CDC_RX7_B6_CTL] = 1, + [TABLA_A_CDC_RX1_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX2_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX3_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX4_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX5_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX6_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX7_VOL_CTL_B1_CTL] = 1, + [TABLA_A_CDC_RX1_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX2_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX3_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX4_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX5_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX6_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_RX7_VOL_CTL_B2_CTL] = 1, + [TABLA_A_CDC_CLK_RX_RESET_CTL] = 1, + [TABLA_A_CDC_CLK_TX_RESET_B1_CTL] = 1, + [TABLA_A_CDC_CLK_TX_RESET_B2_CTL] = 1, + [TABLA_A_CDC_CLK_DMIC_CTL] = 1, + [TABLA_A_CDC_CLK_RX_I2S_CTL] = 1, + [TABLA_A_CDC_CLK_TX_I2S_CTL] = 1, + [TABLA_A_CDC_CLK_OTHR_RESET_CTL] = 1, + [TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL] = 1, + [TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL] = 1, + [TABLA_A_CDC_CLK_OTHR_CTL] = 1, + [TABLA_A_CDC_CLK_RDAC_CLK_EN_CTL] = 1, + [TABLA_A_CDC_CLK_RX_B1_CTL] = 1, + [TABLA_A_CDC_CLK_RX_B2_CTL] = 1, + [TABLA_A_CDC_CLK_MCLK_CTL] = 1, + [TABLA_A_CDC_CLK_PDM_CTL] = 1, + [TABLA_A_CDC_CLK_SD_CTL] = 1, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B1_CTL] = 1, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B2_CTL] = 1, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL] = 1, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B4_CTL] = 1, + [TABLA_A_CDC_CLSG_GAIN_THRESH_CTL] = 1, + [TABLA_A_CDC_CLSG_TIMER_B1_CFG] = 1, + [TABLA_A_CDC_CLSG_TIMER_B2_CFG] = 1, + [TABLA_A_CDC_CLSG_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B1_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B1_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B2_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B2_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B3_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B3_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B4_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B4_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B5_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B5_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B6_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B6_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B7_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B7_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_B8_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_B8_CTL] = 1, + [TABLA_A_CDC_IIR1_CTL] = 1, + [TABLA_A_CDC_IIR2_CTL] = 1, + [TABLA_A_CDC_IIR1_GAIN_TIMER_CTL] = 1, + [TABLA_A_CDC_IIR2_GAIN_TIMER_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B1_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B1_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B2_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B2_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B3_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B3_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B4_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B4_CTL] = 1, + [TABLA_A_CDC_IIR1_COEF_B5_CTL] = 1, + [TABLA_A_CDC_IIR2_COEF_B5_CTL] = 1, + [TABLA_A_CDC_TOP_GAIN_UPDATE] = 1, + [TABLA_A_CDC_DEBUG_B1_CTL] = 1, + [TABLA_A_CDC_DEBUG_B2_CTL] = 1, + [TABLA_A_CDC_DEBUG_B3_CTL] = 1, + [TABLA_A_CDC_DEBUG_B4_CTL] = 1, + [TABLA_A_CDC_DEBUG_B5_CTL] = 1, + [TABLA_A_CDC_DEBUG_B6_CTL] = 1, + [TABLA_A_CDC_CONN_RX1_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX1_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX1_B3_CTL] = 1, + [TABLA_A_CDC_CONN_RX2_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX2_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX2_B3_CTL] = 1, + [TABLA_A_CDC_CONN_RX3_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX3_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX3_B3_CTL] = 1, + [TABLA_A_CDC_CONN_RX4_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX4_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX5_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX5_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX6_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX6_B2_CTL] = 1, + [TABLA_A_CDC_CONN_RX7_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX7_B2_CTL] = 1, + [TABLA_A_CDC_CONN_TX_B1_CTL] = 1, + [TABLA_A_CDC_CONN_TX_B2_CTL] = 1, + [TABLA_A_CDC_CONN_TX_B3_CTL] = 1, + [TABLA_A_CDC_CONN_TX_B4_CTL] = 1, + [TABLA_A_CDC_CONN_EQ1_B1_CTL] = 1, + [TABLA_A_CDC_CONN_EQ1_B2_CTL] = 1, + [TABLA_A_CDC_CONN_EQ1_B3_CTL] = 1, + [TABLA_A_CDC_CONN_EQ1_B4_CTL] = 1, + [TABLA_A_CDC_CONN_EQ2_B1_CTL] = 1, + [TABLA_A_CDC_CONN_EQ2_B2_CTL] = 1, + [TABLA_A_CDC_CONN_EQ2_B3_CTL] = 1, + [TABLA_A_CDC_CONN_EQ2_B4_CTL] = 1, + [TABLA_A_CDC_CONN_SRC1_B1_CTL] = 1, + [TABLA_A_CDC_CONN_SRC1_B2_CTL] = 1, + [TABLA_A_CDC_CONN_SRC2_B1_CTL] = 1, + [TABLA_A_CDC_CONN_SRC2_B2_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B1_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B2_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B3_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B4_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B5_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B6_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B7_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B8_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B9_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B10_CTL] = 1, + [TABLA_A_CDC_CONN_TX_SB_B11_CTL] = 1, + [TABLA_A_CDC_CONN_RX_SB_B1_CTL] = 1, + [TABLA_A_CDC_CONN_RX_SB_B2_CTL] = 1, + [TABLA_A_CDC_CONN_CLSG_CTL] = 1, + [TABLA_A_CDC_CONN_SPARE] = 1, + [TABLA_A_CDC_MBHC_EN_CTL] = 1, + [TABLA_A_CDC_MBHC_FEATURE_B1_CFG] = 1, + [TABLA_A_CDC_MBHC_FEATURE_B2_CFG] = 1, + [TABLA_A_CDC_MBHC_TIMER_B1_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B2_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B3_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B4_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B5_CTL] = 1, + [TABLA_A_CDC_MBHC_TIMER_B6_CTL] = 1, + [TABLA_A_CDC_MBHC_B1_STATUS] = 1, + [TABLA_A_CDC_MBHC_B2_STATUS] = 1, + [TABLA_A_CDC_MBHC_B3_STATUS] = 1, + [TABLA_A_CDC_MBHC_B4_STATUS] = 1, + [TABLA_A_CDC_MBHC_B5_STATUS] = 1, + [TABLA_A_CDC_MBHC_B1_CTL] = 1, + [TABLA_A_CDC_MBHC_B2_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B1_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B2_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B3_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B4_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B5_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B6_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B7_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B8_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B9_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B10_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B11_CTL] = 1, + [TABLA_A_CDC_MBHC_VOLT_B12_CTL] = 1, + [TABLA_A_CDC_MBHC_CLK_CTL] = 1, + [TABLA_A_CDC_MBHC_INT_CTL] = 1, + [TABLA_A_CDC_MBHC_DEBUG_CTL] = 1, + [TABLA_A_CDC_MBHC_SPARE] = 1, +}; + +const u8 tabla_reg_defaults[TABLA_CACHE_SIZE] = { + [TABLA_A_CHIP_CTL] = TABLA_A_CHIP_CTL__POR, + [TABLA_A_CHIP_STATUS] = TABLA_A_CHIP_STATUS__POR, + [TABLA_A_CHIP_ID_BYTE_0] = TABLA_A_CHIP_ID_BYTE_0__POR, + [TABLA_A_CHIP_ID_BYTE_1] = TABLA_A_CHIP_ID_BYTE_1__POR, + [TABLA_A_CHIP_ID_BYTE_2] = TABLA_A_CHIP_ID_BYTE_2__POR, + [TABLA_A_CHIP_ID_BYTE_3] = TABLA_A_CHIP_ID_BYTE_3__POR, + [TABLA_A_CHIP_VERSION] = TABLA_A_CHIP_VERSION__POR, + [TABLA_A_SB_VERSION] = TABLA_A_SB_VERSION__POR, + [TABLA_A_SLAVE_ID_1] = TABLA_A_SLAVE_ID_1__POR, + [TABLA_A_SLAVE_ID_2] = TABLA_A_SLAVE_ID_2__POR, + [TABLA_A_SLAVE_ID_3] = TABLA_A_SLAVE_ID_3__POR, + [TABLA_A_PIN_CTL_OE0] = TABLA_A_PIN_CTL_OE0__POR, + [TABLA_A_PIN_CTL_OE1] = TABLA_A_PIN_CTL_OE1__POR, + [TABLA_A_PIN_CTL_DATA0] = TABLA_A_PIN_CTL_DATA0__POR, + [TABLA_A_PIN_CTL_DATA1] = TABLA_A_PIN_CTL_DATA1__POR, + [TABLA_A_HDRIVE_GENERIC] = TABLA_A_HDRIVE_GENERIC__POR, + [TABLA_A_HDRIVE_OVERRIDE] = TABLA_A_HDRIVE_OVERRIDE__POR, + [TABLA_A_ANA_CSR_WAIT_STATE] = TABLA_A_ANA_CSR_WAIT_STATE__POR, + [TABLA_A_PROCESS_MONITOR_CTL0] = TABLA_A_PROCESS_MONITOR_CTL0__POR, + [TABLA_A_PROCESS_MONITOR_CTL1] = TABLA_A_PROCESS_MONITOR_CTL1__POR, + [TABLA_A_PROCESS_MONITOR_CTL2] = TABLA_A_PROCESS_MONITOR_CTL2__POR, + [TABLA_A_PROCESS_MONITOR_CTL3] = TABLA_A_PROCESS_MONITOR_CTL3__POR, + [TABLA_A_QFUSE_CTL] = TABLA_A_QFUSE_CTL__POR, + [TABLA_A_QFUSE_STATUS] = TABLA_A_QFUSE_STATUS__POR, + [TABLA_A_QFUSE_DATA_OUT0] = TABLA_A_QFUSE_DATA_OUT0__POR, + [TABLA_A_QFUSE_DATA_OUT1] = TABLA_A_QFUSE_DATA_OUT1__POR, + [TABLA_A_QFUSE_DATA_OUT2] = TABLA_A_QFUSE_DATA_OUT2__POR, + [TABLA_A_QFUSE_DATA_OUT3] = TABLA_A_QFUSE_DATA_OUT3__POR, + [TABLA_A_CDC_CTL] = TABLA_A_CDC_CTL__POR, + [TABLA_A_LEAKAGE_CTL] = TABLA_A_LEAKAGE_CTL__POR, + [TABLA_A_INTR_MODE] = TABLA_A_INTR_MODE__POR, + [TABLA_A_INTR_MASK0] = TABLA_A_INTR_MASK0__POR, + [TABLA_A_INTR_MASK1] = TABLA_A_INTR_MASK1__POR, + [TABLA_A_INTR_MASK2] = TABLA_A_INTR_MASK2__POR, + [TABLA_A_INTR_STATUS0] = TABLA_A_INTR_STATUS0__POR, + [TABLA_A_INTR_STATUS1] = TABLA_A_INTR_STATUS1__POR, + [TABLA_A_INTR_STATUS2] = TABLA_A_INTR_STATUS2__POR, + [TABLA_A_INTR_CLEAR0] = TABLA_A_INTR_CLEAR0__POR, + [TABLA_A_INTR_CLEAR1] = TABLA_A_INTR_CLEAR1__POR, + [TABLA_A_INTR_CLEAR2] = TABLA_A_INTR_CLEAR2__POR, + [TABLA_A_INTR_LEVEL0] = TABLA_A_INTR_LEVEL0__POR, + [TABLA_A_INTR_LEVEL1] = TABLA_A_INTR_LEVEL1__POR, + [TABLA_A_INTR_LEVEL2] = TABLA_A_INTR_LEVEL2__POR, + [TABLA_A_INTR_TEST0] = TABLA_A_INTR_TEST0__POR, + [TABLA_A_INTR_TEST1] = TABLA_A_INTR_TEST1__POR, + [TABLA_A_INTR_TEST2] = TABLA_A_INTR_TEST2__POR, + [TABLA_A_INTR_SET0] = TABLA_A_INTR_SET0__POR, + [TABLA_A_INTR_SET1] = TABLA_A_INTR_SET1__POR, + [TABLA_A_INTR_SET2] = TABLA_A_INTR_SET2__POR, + [TABLA_A_CDC_TX_I2S_SCK_MODE] = TABLA_A_CDC_TX_I2S_SCK_MODE__POR, + [TABLA_A_CDC_TX_I2S_WS_MODE] = TABLA_A_CDC_TX_I2S_WS_MODE__POR, + [TABLA_A_CDC_DMIC_DATA0_MODE] = TABLA_A_CDC_DMIC_DATA0_MODE__POR, + [TABLA_A_CDC_DMIC_CLK0_MODE] = TABLA_A_CDC_DMIC_CLK0_MODE__POR, + [TABLA_A_CDC_DMIC_DATA1_MODE] = TABLA_A_CDC_DMIC_DATA1_MODE__POR, + [TABLA_A_CDC_DMIC_CLK1_MODE] = TABLA_A_CDC_DMIC_CLK1_MODE__POR, + [TABLA_A_CDC_RX_I2S_SCK_MODE] = TABLA_A_CDC_RX_I2S_SCK_MODE__POR, + [TABLA_A_CDC_RX_I2S_WS_MODE] = TABLA_A_CDC_RX_I2S_WS_MODE__POR, + [TABLA_A_CDC_DMIC_DATA2_MODE] = TABLA_A_CDC_DMIC_DATA2_MODE__POR, + [TABLA_A_CDC_DMIC_CLK2_MODE] = TABLA_A_CDC_DMIC_CLK2_MODE__POR, + [TABLA_A_CDC_INTR_MODE] = TABLA_A_CDC_INTR_MODE__POR, + [TABLA_A_BIAS_REF_CTL] = TABLA_A_BIAS_REF_CTL__POR, + [TABLA_A_BIAS_CENTRAL_BG_CTL] = TABLA_A_BIAS_CENTRAL_BG_CTL__POR, + [TABLA_A_BIAS_PRECHRG_CTL] = TABLA_A_BIAS_PRECHRG_CTL__POR, + [TABLA_A_BIAS_CURR_CTL_1] = TABLA_A_BIAS_CURR_CTL_1__POR, + [TABLA_A_BIAS_CURR_CTL_2] = TABLA_A_BIAS_CURR_CTL_2__POR, + [TABLA_A_BIAS_CONFIG_MODE_BG_CTL] = + TABLA_A_BIAS_CONFIG_MODE_BG_CTL__POR, + [TABLA_A_BIAS_BG_STATUS] = TABLA_A_BIAS_BG_STATUS__POR, + [TABLA_A_CLK_BUFF_EN1] = TABLA_A_CLK_BUFF_EN1__POR, + [TABLA_A_CLK_BUFF_EN2] = TABLA_A_CLK_BUFF_EN2__POR, + [TABLA_A_LDO_H_MODE_1] = TABLA_A_LDO_H_MODE_1__POR, + [TABLA_A_LDO_H_MODE_2] = TABLA_A_LDO_H_MODE_2__POR, + [TABLA_A_LDO_H_LOOP_CTL] = TABLA_A_LDO_H_LOOP_CTL__POR, + [TABLA_A_LDO_H_COMP_1] = TABLA_A_LDO_H_COMP_1__POR, + [TABLA_A_LDO_H_COMP_2] = TABLA_A_LDO_H_COMP_2__POR, + [TABLA_A_LDO_H_BIAS_1] = TABLA_A_LDO_H_BIAS_1__POR, + [TABLA_A_LDO_H_BIAS_2] = TABLA_A_LDO_H_BIAS_2__POR, + [TABLA_A_LDO_H_BIAS_3] = TABLA_A_LDO_H_BIAS_3__POR, + [TABLA_A_LDO_L_MODE_1] = TABLA_A_LDO_L_MODE_1__POR, + [TABLA_A_LDO_L_MODE_2] = TABLA_A_LDO_L_MODE_2__POR, + [TABLA_A_LDO_L_LOOP_CTL] = TABLA_A_LDO_L_LOOP_CTL__POR, + [TABLA_A_LDO_L_COMP_1] = TABLA_A_LDO_L_COMP_1__POR, + [TABLA_A_LDO_L_COMP_2] = TABLA_A_LDO_L_COMP_2__POR, + [TABLA_A_LDO_L_BIAS_1] = TABLA_A_LDO_L_BIAS_1__POR, + [TABLA_A_LDO_L_BIAS_2] = TABLA_A_LDO_L_BIAS_2__POR, + [TABLA_A_LDO_L_BIAS_3] = TABLA_A_LDO_L_BIAS_3__POR, + [TABLA_A_MICB_CFILT_1_CTL] = TABLA_A_MICB_CFILT_1_CTL__POR, + [TABLA_A_MICB_CFILT_1_VAL] = TABLA_A_MICB_CFILT_1_VAL__POR, + [TABLA_A_MICB_CFILT_1_PRECHRG] = TABLA_A_MICB_CFILT_1_PRECHRG__POR, + [TABLA_A_MICB_1_CTL] = TABLA_A_MICB_1_CTL__POR, + [TABLA_A_MICB_1_INT_RBIAS] = TABLA_A_MICB_1_INT_RBIAS__POR, + [TABLA_A_MICB_1_MBHC] = TABLA_A_MICB_1_MBHC__POR, + [TABLA_A_MICB_CFILT_2_CTL] = TABLA_A_MICB_CFILT_2_CTL__POR, + [TABLA_A_MICB_CFILT_2_VAL] = TABLA_A_MICB_CFILT_2_VAL__POR, + [TABLA_A_MICB_CFILT_2_PRECHRG] = TABLA_A_MICB_CFILT_2_PRECHRG__POR, + [TABLA_A_MICB_2_CTL] = TABLA_A_MICB_2_CTL__POR, + [TABLA_A_MICB_2_INT_RBIAS] = TABLA_A_MICB_2_INT_RBIAS__POR, + [TABLA_A_MICB_2_MBHC] = TABLA_A_MICB_2_MBHC__POR, + [TABLA_A_MICB_CFILT_3_CTL] = TABLA_A_MICB_CFILT_3_CTL__POR, + [TABLA_A_MICB_CFILT_3_VAL] = TABLA_A_MICB_CFILT_3_VAL__POR, + [TABLA_A_MICB_CFILT_3_PRECHRG] = TABLA_A_MICB_CFILT_3_PRECHRG__POR, + [TABLA_A_MICB_3_CTL] = TABLA_A_MICB_3_CTL__POR, + [TABLA_A_MICB_3_INT_RBIAS] = TABLA_A_MICB_3_INT_RBIAS__POR, + [TABLA_A_MICB_3_MBHC] = TABLA_A_MICB_3_MBHC__POR, + [TABLA_A_MICB_4_CTL] = TABLA_A_MICB_4_CTL__POR, + [TABLA_A_MICB_4_INT_RBIAS] = TABLA_A_MICB_4_INT_RBIAS__POR, + [TABLA_A_MICB_4_MBHC] = TABLA_A_MICB_4_MBHC__POR, + [TABLA_A_TX_COM_BIAS] = TABLA_A_TX_COM_BIAS__POR, + [TABLA_A_MBHC_SCALING_MUX_1] = TABLA_A_MBHC_SCALING_MUX_1__POR, + [TABLA_A_MBHC_SCALING_MUX_2] = TABLA_A_MBHC_SCALING_MUX_2__POR, + [TABLA_A_TX_SUP_SWITCH_CTRL_1] = TABLA_A_TX_SUP_SWITCH_CTRL_1__POR, + [TABLA_A_TX_SUP_SWITCH_CTRL_2] = TABLA_A_TX_SUP_SWITCH_CTRL_2__POR, + [TABLA_A_TX_1_2_EN] = TABLA_A_TX_1_2_EN__POR, + [TABLA_A_TX_1_2_TEST_EN] = TABLA_A_TX_1_2_TEST_EN__POR, + [TABLA_A_TX_1_2_ADC_CH1] = TABLA_A_TX_1_2_ADC_CH1__POR, + [TABLA_A_TX_1_2_ADC_CH2] = TABLA_A_TX_1_2_ADC_CH2__POR, + [TABLA_A_TX_1_2_ATEST_REFCTRL] = TABLA_A_TX_1_2_ATEST_REFCTRL__POR, + [TABLA_A_TX_1_2_TEST_CTL] = TABLA_A_TX_1_2_TEST_CTL__POR, + [TABLA_A_TX_1_2_TEST_BLOCK_EN] = TABLA_A_TX_1_2_TEST_BLOCK_EN__POR, + [TABLA_A_TX_1_2_TXFE_CLKDIV] = TABLA_A_TX_1_2_TXFE_CLKDIV__POR, + [TABLA_A_TX_1_2_SAR_ERR_CH1] = TABLA_A_TX_1_2_SAR_ERR_CH1__POR, + [TABLA_A_TX_1_2_SAR_ERR_CH2] = TABLA_A_TX_1_2_SAR_ERR_CH2__POR, + [TABLA_A_TX_3_4_EN] = TABLA_A_TX_3_4_EN__POR, + [TABLA_A_TX_3_4_TEST_EN] = TABLA_A_TX_3_4_TEST_EN__POR, + [TABLA_A_TX_3_4_ADC_CH3] = TABLA_A_TX_3_4_ADC_CH3__POR, + [TABLA_A_TX_3_4_ADC_CH4] = TABLA_A_TX_3_4_ADC_CH4__POR, + [TABLA_A_TX_3_4_ATEST_REFCTRL] = TABLA_A_TX_3_4_ATEST_REFCTRL__POR, + [TABLA_A_TX_3_4_TEST_CTL] = TABLA_A_TX_3_4_TEST_CTL__POR, + [TABLA_A_TX_3_4_TEST_BLOCK_EN] = TABLA_A_TX_3_4_TEST_BLOCK_EN__POR, + [TABLA_A_TX_3_4_TXFE_CKDIV] = TABLA_A_TX_3_4_TXFE_CKDIV__POR, + [TABLA_A_TX_3_4_SAR_ERR_CH3] = TABLA_A_TX_3_4_SAR_ERR_CH3__POR, + [TABLA_A_TX_3_4_SAR_ERR_CH4] = TABLA_A_TX_3_4_SAR_ERR_CH4__POR, + [TABLA_A_TX_5_6_EN] = TABLA_A_TX_5_6_EN__POR, + [TABLA_A_TX_5_6_TEST_EN] = TABLA_A_TX_5_6_TEST_EN__POR, + [TABLA_A_TX_5_6_ADC_CH5] = TABLA_A_TX_5_6_ADC_CH5__POR, + [TABLA_A_TX_5_6_ADC_CH6] = TABLA_A_TX_5_6_ADC_CH6__POR, + [TABLA_A_TX_5_6_ATEST_REFCTRL] = TABLA_A_TX_5_6_ATEST_REFCTRL__POR, + [TABLA_A_TX_5_6_TEST_CTL] = TABLA_A_TX_5_6_TEST_CTL__POR, + [TABLA_A_TX_5_6_TEST_BLOCK_EN] = TABLA_A_TX_5_6_TEST_BLOCK_EN__POR, + [TABLA_A_TX_5_6_TXFE_CKDIV] = TABLA_A_TX_5_6_TXFE_CKDIV__POR, + [TABLA_A_TX_5_6_SAR_ERR_CH5] = TABLA_A_TX_5_6_SAR_ERR_CH5__POR, + [TABLA_A_TX_5_6_SAR_ERR_CH6] = TABLA_A_TX_5_6_SAR_ERR_CH6__POR, + [TABLA_A_TX_7_MBHC_EN] = TABLA_A_TX_7_MBHC_EN__POR, + [TABLA_A_TX_7_MBHC_ATEST_REFCTRL] = + TABLA_A_TX_7_MBHC_ATEST_REFCTRL__POR, + [TABLA_A_TX_7_MBHC_ADC] = TABLA_A_TX_7_MBHC_ADC__POR, + [TABLA_A_TX_7_MBHC_TEST_CTL] = TABLA_A_TX_7_MBHC_TEST_CTL__POR, + [TABLA_A_TX_7_MBHC_SAR_ERR] = TABLA_A_TX_7_MBHC_SAR_ERR__POR, + [TABLA_A_TX_7_TXFE_CLKDIV] = TABLA_A_TX_7_TXFE_CLKDIV__POR, + [TABLA_A_AUX_COM_CTL] = TABLA_A_AUX_COM_CTL__POR, + [TABLA_A_AUX_COM_ATEST] = TABLA_A_AUX_COM_ATEST__POR, + [TABLA_A_AUX_L_EN] = TABLA_A_AUX_L_EN__POR, + [TABLA_A_AUX_L_GAIN] = TABLA_A_AUX_L_GAIN__POR, + [TABLA_A_AUX_L_PA_CONN] = TABLA_A_AUX_L_PA_CONN__POR, + [TABLA_A_AUX_L_PA_CONN_INV] = TABLA_A_AUX_L_PA_CONN_INV__POR, + [TABLA_A_AUX_R_EN] = TABLA_A_AUX_R_EN__POR, + [TABLA_A_AUX_R_GAIN] = TABLA_A_AUX_R_GAIN__POR, + [TABLA_A_AUX_R_PA_CONN] = TABLA_A_AUX_R_PA_CONN__POR, + [TABLA_A_AUX_R_PA_CONN_INV] = TABLA_A_AUX_R_PA_CONN_INV__POR, + [TABLA_A_CP_EN] = TABLA_A_CP_EN__POR, + [TABLA_A_CP_CLK] = TABLA_A_CP_CLK__POR, + [TABLA_A_CP_STATIC] = TABLA_A_CP_STATIC__POR, + [TABLA_A_CP_DCC1] = TABLA_A_CP_DCC1__POR, + [TABLA_A_CP_DCC3] = TABLA_A_CP_DCC3__POR, + [TABLA_A_CP_ATEST] = TABLA_A_CP_ATEST__POR, + [TABLA_A_CP_DTEST] = TABLA_A_CP_DTEST__POR, + [TABLA_A_RX_COM_TIMER_DIV] = TABLA_A_RX_COM_TIMER_DIV__POR, + [TABLA_A_RX_COM_OCP_CTL] = TABLA_A_RX_COM_OCP_CTL__POR, + [TABLA_A_RX_COM_OCP_COUNT] = TABLA_A_RX_COM_OCP_COUNT__POR, + [TABLA_A_RX_COM_DAC_CTL] = TABLA_A_RX_COM_DAC_CTL__POR, + [TABLA_A_RX_COM_BIAS] = TABLA_A_RX_COM_BIAS__POR, + [TABLA_A_RX_HPH_BIAS_PA] = TABLA_A_RX_HPH_BIAS_PA__POR, + [TABLA_A_RX_HPH_BIAS_LDO] = TABLA_A_RX_HPH_BIAS_LDO__POR, + [TABLA_A_RX_HPH_BIAS_CNP] = TABLA_A_RX_HPH_BIAS_CNP__POR, + [TABLA_A_RX_HPH_BIAS_WG] = TABLA_A_RX_HPH_BIAS_WG__POR, + [TABLA_A_RX_HPH_OCP_CTL] = TABLA_A_RX_HPH_OCP_CTL__POR, + [TABLA_A_RX_HPH_CNP_EN] = TABLA_A_RX_HPH_CNP_EN__POR, + [TABLA_A_RX_HPH_CNP_WG_CTL] = TABLA_A_RX_HPH_CNP_WG_CTL__POR, + [TABLA_A_RX_HPH_CNP_WG_TIME] = TABLA_A_RX_HPH_CNP_WG_TIME__POR, + [TABLA_A_RX_HPH_L_GAIN] = TABLA_A_RX_HPH_L_GAIN__POR, + [TABLA_A_RX_HPH_L_TEST] = TABLA_A_RX_HPH_L_TEST__POR, + [TABLA_A_RX_HPH_L_PA_CTL] = TABLA_A_RX_HPH_L_PA_CTL__POR, + [TABLA_A_RX_HPH_L_DAC_CTL] = TABLA_A_RX_HPH_L_DAC_CTL__POR, + [TABLA_A_RX_HPH_L_ATEST] = TABLA_A_RX_HPH_L_ATEST__POR, + [TABLA_A_RX_HPH_L_STATUS] = TABLA_A_RX_HPH_L_STATUS__POR, + [TABLA_A_RX_HPH_R_GAIN] = TABLA_A_RX_HPH_R_GAIN__POR, + [TABLA_A_RX_HPH_R_TEST] = TABLA_A_RX_HPH_R_TEST__POR, + [TABLA_A_RX_HPH_R_PA_CTL] = TABLA_A_RX_HPH_R_PA_CTL__POR, + [TABLA_A_RX_HPH_R_DAC_CTL] = TABLA_A_RX_HPH_R_DAC_CTL__POR, + [TABLA_A_RX_HPH_R_ATEST] = TABLA_A_RX_HPH_R_ATEST__POR, + [TABLA_A_RX_HPH_R_STATUS] = TABLA_A_RX_HPH_R_STATUS__POR, + [TABLA_A_RX_EAR_BIAS_PA] = TABLA_A_RX_EAR_BIAS_PA__POR, + [TABLA_A_RX_EAR_BIAS_CMBUFF] = TABLA_A_RX_EAR_BIAS_CMBUFF__POR, + [TABLA_A_RX_EAR_EN] = TABLA_A_RX_EAR_EN__POR, + [TABLA_A_RX_EAR_GAIN] = TABLA_A_RX_EAR_GAIN__POR, + [TABLA_A_RX_EAR_CMBUFF] = TABLA_A_RX_EAR_CMBUFF__POR, + [TABLA_A_RX_EAR_ICTL] = TABLA_A_RX_EAR_ICTL__POR, + [TABLA_A_RX_EAR_CCOMP] = TABLA_A_RX_EAR_CCOMP__POR, + [TABLA_A_RX_EAR_VCM] = TABLA_A_RX_EAR_VCM__POR, + [TABLA_A_RX_EAR_CNP] = TABLA_A_RX_EAR_CNP__POR, + [TABLA_A_RX_EAR_ATEST] = TABLA_A_RX_EAR_ATEST__POR, + [TABLA_A_RX_EAR_STATUS] = TABLA_A_RX_EAR_STATUS__POR, + [TABLA_A_RX_LINE_BIAS_PA] = TABLA_A_RX_LINE_BIAS_PA__POR, + [TABLA_A_RX_LINE_BIAS_DAC] = TABLA_A_RX_LINE_BIAS_DAC__POR, + [TABLA_A_RX_LINE_BIAS_CNP] = TABLA_A_RX_LINE_BIAS_CNP__POR, + [TABLA_A_RX_LINE_COM] = TABLA_A_RX_LINE_COM__POR, + [TABLA_A_RX_LINE_CNP_EN] = TABLA_A_RX_LINE_CNP_EN__POR, + [TABLA_A_RX_LINE_CNP_WG_CTL] = TABLA_A_RX_LINE_CNP_WG_CTL__POR, + [TABLA_A_RX_LINE_CNP_WG_TIME] = TABLA_A_RX_LINE_CNP_WG_TIME__POR, + [TABLA_A_RX_LINE_1_GAIN] = TABLA_A_RX_LINE_1_GAIN__POR, + [TABLA_A_RX_LINE_1_TEST] = TABLA_A_RX_LINE_1_TEST__POR, + [TABLA_A_RX_LINE_1_DAC_CTL] = TABLA_A_RX_LINE_1_DAC_CTL__POR, + [TABLA_A_RX_LINE_1_STATUS] = TABLA_A_RX_LINE_1_STATUS__POR, + [TABLA_A_RX_LINE_2_GAIN] = TABLA_A_RX_LINE_2_GAIN__POR, + [TABLA_A_RX_LINE_2_TEST] = TABLA_A_RX_LINE_2_TEST__POR, + [TABLA_A_RX_LINE_2_DAC_CTL] = TABLA_A_RX_LINE_2_DAC_CTL__POR, + [TABLA_A_RX_LINE_2_STATUS] = TABLA_A_RX_LINE_2_STATUS__POR, + [TABLA_A_RX_LINE_3_GAIN] = TABLA_A_RX_LINE_3_GAIN__POR, + [TABLA_A_RX_LINE_3_TEST] = TABLA_A_RX_LINE_3_TEST__POR, + [TABLA_A_RX_LINE_3_DAC_CTL] = TABLA_A_RX_LINE_3_DAC_CTL__POR, + [TABLA_A_RX_LINE_3_STATUS] = TABLA_A_RX_LINE_3_STATUS__POR, + [TABLA_A_RX_LINE_4_GAIN] = TABLA_A_RX_LINE_4_GAIN__POR, + [TABLA_A_RX_LINE_4_TEST] = TABLA_A_RX_LINE_4_TEST__POR, + [TABLA_A_RX_LINE_4_DAC_CTL] = TABLA_A_RX_LINE_4_DAC_CTL__POR, + [TABLA_A_RX_LINE_4_STATUS] = TABLA_A_RX_LINE_4_STATUS__POR, + [TABLA_A_RX_LINE_5_GAIN] = TABLA_A_RX_LINE_5_GAIN__POR, + [TABLA_A_RX_LINE_5_TEST] = TABLA_A_RX_LINE_5_TEST__POR, + [TABLA_A_RX_LINE_5_DAC_CTL] = TABLA_A_RX_LINE_5_DAC_CTL__POR, + [TABLA_A_RX_LINE_5_STATUS] = TABLA_A_RX_LINE_5_STATUS__POR, + [TABLA_A_RX_LINE_CNP_DBG] = TABLA_A_RX_LINE_CNP_DBG__POR, + [TABLA_A_MBHC_HPH] = TABLA_A_MBHC_HPH__POR, + [TABLA_A_CONFIG_MODE_FREQ] = TABLA_A_CONFIG_MODE_FREQ__POR, + [TABLA_A_CONFIG_MODE_TEST] = TABLA_A_CONFIG_MODE_TEST__POR, + [TABLA_A_CONFIG_MODE_STATUS] = TABLA_A_CONFIG_MODE_STATUS__POR, + [TABLA_A_CONFIG_MODE_TUNER] = TABLA_A_CONFIG_MODE_TUNER__POR, + [TABLA_A_CDC_TX1_VOL_CTL_TIMER] = TABLA_A_CDC_TX1_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX2_VOL_CTL_TIMER] = TABLA_A_CDC_TX2_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX3_VOL_CTL_TIMER] = TABLA_A_CDC_TX3_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX4_VOL_CTL_TIMER] = TABLA_A_CDC_TX4_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX5_VOL_CTL_TIMER] = TABLA_A_CDC_TX5_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX6_VOL_CTL_TIMER] = TABLA_A_CDC_TX6_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX7_VOL_CTL_TIMER] = TABLA_A_CDC_TX7_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX8_VOL_CTL_TIMER] = TABLA_A_CDC_TX8_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX9_VOL_CTL_TIMER] = TABLA_A_CDC_TX9_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX10_VOL_CTL_TIMER] = TABLA_A_CDC_TX10_VOL_CTL_TIMER__POR, + [TABLA_A_CDC_TX1_VOL_CTL_GAIN] = TABLA_A_CDC_TX1_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX2_VOL_CTL_GAIN] = TABLA_A_CDC_TX2_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX3_VOL_CTL_GAIN] = TABLA_A_CDC_TX3_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX4_VOL_CTL_GAIN] = TABLA_A_CDC_TX4_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX5_VOL_CTL_GAIN] = TABLA_A_CDC_TX5_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX6_VOL_CTL_GAIN] = TABLA_A_CDC_TX6_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX7_VOL_CTL_GAIN] = TABLA_A_CDC_TX7_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX8_VOL_CTL_GAIN] = TABLA_A_CDC_TX8_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX9_VOL_CTL_GAIN] = TABLA_A_CDC_TX9_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX10_VOL_CTL_GAIN] = TABLA_A_CDC_TX10_VOL_CTL_GAIN__POR, + [TABLA_A_CDC_TX1_VOL_CTL_CFG] = TABLA_A_CDC_TX1_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX2_VOL_CTL_CFG] = TABLA_A_CDC_TX2_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX3_VOL_CTL_CFG] = TABLA_A_CDC_TX3_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX4_VOL_CTL_CFG] = TABLA_A_CDC_TX4_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX5_VOL_CTL_CFG] = TABLA_A_CDC_TX5_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX6_VOL_CTL_CFG] = TABLA_A_CDC_TX6_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX7_VOL_CTL_CFG] = TABLA_A_CDC_TX7_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX8_VOL_CTL_CFG] = TABLA_A_CDC_TX8_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX9_VOL_CTL_CFG] = TABLA_A_CDC_TX9_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX10_VOL_CTL_CFG] = TABLA_A_CDC_TX10_VOL_CTL_CFG__POR, + [TABLA_A_CDC_TX1_MUX_CTL] = TABLA_A_CDC_TX1_MUX_CTL__POR, + [TABLA_A_CDC_TX2_MUX_CTL] = TABLA_A_CDC_TX2_MUX_CTL__POR, + [TABLA_A_CDC_TX3_MUX_CTL] = TABLA_A_CDC_TX3_MUX_CTL__POR, + [TABLA_A_CDC_TX4_MUX_CTL] = TABLA_A_CDC_TX4_MUX_CTL__POR, + [TABLA_A_CDC_TX5_MUX_CTL] = TABLA_A_CDC_TX5_MUX_CTL__POR, + [TABLA_A_CDC_TX6_MUX_CTL] = TABLA_A_CDC_TX6_MUX_CTL__POR, + [TABLA_A_CDC_TX7_MUX_CTL] = TABLA_A_CDC_TX7_MUX_CTL__POR, + [TABLA_A_CDC_TX8_MUX_CTL] = TABLA_A_CDC_TX8_MUX_CTL__POR, + [TABLA_A_CDC_TX9_MUX_CTL] = TABLA_A_CDC_TX9_MUX_CTL__POR, + [TABLA_A_CDC_TX10_MUX_CTL] = TABLA_A_CDC_TX10_MUX_CTL__POR, + [TABLA_A_CDC_TX1_CLK_FS_CTL] = TABLA_A_CDC_TX1_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX2_CLK_FS_CTL] = TABLA_A_CDC_TX2_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX3_CLK_FS_CTL] = TABLA_A_CDC_TX3_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX4_CLK_FS_CTL] = TABLA_A_CDC_TX4_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX5_CLK_FS_CTL] = TABLA_A_CDC_TX5_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX6_CLK_FS_CTL] = TABLA_A_CDC_TX6_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX7_CLK_FS_CTL] = TABLA_A_CDC_TX7_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX8_CLK_FS_CTL] = TABLA_A_CDC_TX8_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX9_CLK_FS_CTL] = TABLA_A_CDC_TX9_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX10_CLK_FS_CTL] = TABLA_A_CDC_TX10_CLK_FS_CTL__POR, + [TABLA_A_CDC_TX1_DMIC_CTL] = TABLA_A_CDC_TX1_DMIC_CTL__POR, + [TABLA_A_CDC_TX2_DMIC_CTL] = TABLA_A_CDC_TX2_DMIC_CTL__POR, + [TABLA_A_CDC_TX3_DMIC_CTL] = TABLA_A_CDC_TX3_DMIC_CTL__POR, + [TABLA_A_CDC_TX4_DMIC_CTL] = TABLA_A_CDC_TX4_DMIC_CTL__POR, + [TABLA_A_CDC_TX5_DMIC_CTL] = TABLA_A_CDC_TX5_DMIC_CTL__POR, + [TABLA_A_CDC_TX6_DMIC_CTL] = TABLA_A_CDC_TX6_DMIC_CTL__POR, + [TABLA_A_CDC_TX7_DMIC_CTL] = TABLA_A_CDC_TX7_DMIC_CTL__POR, + [TABLA_A_CDC_TX8_DMIC_CTL] = TABLA_A_CDC_TX8_DMIC_CTL__POR, + [TABLA_A_CDC_TX9_DMIC_CTL] = TABLA_A_CDC_TX9_DMIC_CTL__POR, + [TABLA_A_CDC_TX10_DMIC_CTL] = TABLA_A_CDC_TX10_DMIC_CTL__POR, + [TABLA_A_CDC_SRC1_PDA_CFG] = TABLA_A_CDC_SRC1_PDA_CFG__POR, + [TABLA_A_CDC_SRC2_PDA_CFG] = TABLA_A_CDC_SRC2_PDA_CFG__POR, + [TABLA_A_CDC_SRC1_FS_CTL] = TABLA_A_CDC_SRC1_FS_CTL__POR, + [TABLA_A_CDC_SRC2_FS_CTL] = TABLA_A_CDC_SRC2_FS_CTL__POR, + [TABLA_A_CDC_RX1_B1_CTL] = TABLA_A_CDC_RX1_B1_CTL__POR, + [TABLA_A_CDC_RX2_B1_CTL] = TABLA_A_CDC_RX2_B1_CTL__POR, + [TABLA_A_CDC_RX3_B1_CTL] = TABLA_A_CDC_RX3_B1_CTL__POR, + [TABLA_A_CDC_RX4_B1_CTL] = TABLA_A_CDC_RX4_B1_CTL__POR, + [TABLA_A_CDC_RX5_B1_CTL] = TABLA_A_CDC_RX5_B1_CTL__POR, + [TABLA_A_CDC_RX6_B1_CTL] = TABLA_A_CDC_RX6_B1_CTL__POR, + [TABLA_A_CDC_RX7_B1_CTL] = TABLA_A_CDC_RX7_B1_CTL__POR, + [TABLA_A_CDC_RX1_B2_CTL] = TABLA_A_CDC_RX1_B2_CTL__POR, + [TABLA_A_CDC_RX2_B2_CTL] = TABLA_A_CDC_RX2_B2_CTL__POR, + [TABLA_A_CDC_RX3_B2_CTL] = TABLA_A_CDC_RX3_B2_CTL__POR, + [TABLA_A_CDC_RX4_B2_CTL] = TABLA_A_CDC_RX4_B2_CTL__POR, + [TABLA_A_CDC_RX5_B2_CTL] = TABLA_A_CDC_RX5_B2_CTL__POR, + [TABLA_A_CDC_RX6_B2_CTL] = TABLA_A_CDC_RX6_B2_CTL__POR, + [TABLA_A_CDC_RX7_B2_CTL] = TABLA_A_CDC_RX7_B2_CTL__POR, + [TABLA_A_CDC_RX1_B3_CTL] = TABLA_A_CDC_RX1_B3_CTL__POR, + [TABLA_A_CDC_RX2_B3_CTL] = TABLA_A_CDC_RX2_B3_CTL__POR, + [TABLA_A_CDC_RX3_B3_CTL] = TABLA_A_CDC_RX3_B3_CTL__POR, + [TABLA_A_CDC_RX4_B3_CTL] = TABLA_A_CDC_RX4_B3_CTL__POR, + [TABLA_A_CDC_RX5_B3_CTL] = TABLA_A_CDC_RX5_B3_CTL__POR, + [TABLA_A_CDC_RX6_B3_CTL] = TABLA_A_CDC_RX6_B3_CTL__POR, + [TABLA_A_CDC_RX7_B3_CTL] = TABLA_A_CDC_RX7_B3_CTL__POR, + [TABLA_A_CDC_RX1_B4_CTL] = TABLA_A_CDC_RX1_B4_CTL__POR, + [TABLA_A_CDC_RX2_B4_CTL] = TABLA_A_CDC_RX2_B4_CTL__POR, + [TABLA_A_CDC_RX3_B4_CTL] = TABLA_A_CDC_RX3_B4_CTL__POR, + [TABLA_A_CDC_RX4_B4_CTL] = TABLA_A_CDC_RX4_B4_CTL__POR, + [TABLA_A_CDC_RX5_B4_CTL] = TABLA_A_CDC_RX5_B4_CTL__POR, + [TABLA_A_CDC_RX6_B4_CTL] = TABLA_A_CDC_RX6_B4_CTL__POR, + [TABLA_A_CDC_RX7_B4_CTL] = TABLA_A_CDC_RX7_B4_CTL__POR, + [TABLA_A_CDC_RX1_B5_CTL] = TABLA_A_CDC_RX1_B5_CTL__POR, + [TABLA_A_CDC_RX2_B5_CTL] = TABLA_A_CDC_RX2_B5_CTL__POR, + [TABLA_A_CDC_RX3_B5_CTL] = TABLA_A_CDC_RX3_B5_CTL__POR, + [TABLA_A_CDC_RX4_B5_CTL] = TABLA_A_CDC_RX4_B5_CTL__POR, + [TABLA_A_CDC_RX5_B5_CTL] = TABLA_A_CDC_RX5_B5_CTL__POR, + [TABLA_A_CDC_RX6_B5_CTL] = TABLA_A_CDC_RX6_B5_CTL__POR, + [TABLA_A_CDC_RX7_B5_CTL] = TABLA_A_CDC_RX7_B5_CTL__POR, + [TABLA_A_CDC_RX1_B6_CTL] = TABLA_A_CDC_RX1_B6_CTL__POR, + [TABLA_A_CDC_RX2_B6_CTL] = TABLA_A_CDC_RX2_B6_CTL__POR, + [TABLA_A_CDC_RX3_B6_CTL] = TABLA_A_CDC_RX3_B6_CTL__POR, + [TABLA_A_CDC_RX4_B6_CTL] = TABLA_A_CDC_RX4_B6_CTL__POR, + [TABLA_A_CDC_RX5_B6_CTL] = TABLA_A_CDC_RX5_B6_CTL__POR, + [TABLA_A_CDC_RX6_B6_CTL] = TABLA_A_CDC_RX6_B6_CTL__POR, + [TABLA_A_CDC_RX7_B6_CTL] = TABLA_A_CDC_RX7_B6_CTL__POR, + [TABLA_A_CDC_RX1_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX1_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX2_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX2_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX3_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX3_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX4_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX4_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX5_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX5_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX6_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX6_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX7_VOL_CTL_B1_CTL] = TABLA_A_CDC_RX7_VOL_CTL_B1_CTL__POR, + [TABLA_A_CDC_RX1_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX1_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX2_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX2_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX3_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX3_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX4_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX4_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX5_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX5_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX6_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX6_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_RX7_VOL_CTL_B2_CTL] = TABLA_A_CDC_RX7_VOL_CTL_B2_CTL__POR, + [TABLA_A_CDC_CLK_RX_RESET_CTL] = TABLA_A_CDC_CLK_RX_RESET_CTL__POR, + [TABLA_A_CDC_CLK_TX_RESET_B1_CTL] = + TABLA_A_CDC_CLK_TX_RESET_B1_CTL__POR, + [TABLA_A_CDC_CLK_TX_RESET_B2_CTL] = + TABLA_A_CDC_CLK_TX_RESET_B2_CTL__POR, + [TABLA_A_CDC_CLK_DMIC_CTL] = TABLA_A_CDC_CLK_DMIC_CTL__POR, + [TABLA_A_CDC_CLK_RX_I2S_CTL] = TABLA_A_CDC_CLK_RX_I2S_CTL__POR, + [TABLA_A_CDC_CLK_TX_I2S_CTL] = TABLA_A_CDC_CLK_TX_I2S_CTL__POR, + [TABLA_A_CDC_CLK_OTHR_RESET_CTL] = TABLA_A_CDC_CLK_OTHR_RESET_CTL__POR, + [TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL] = + TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL__POR, + [TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL] = + TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL__POR, + [TABLA_A_CDC_CLK_OTHR_CTL] = TABLA_A_CDC_CLK_OTHR_CTL__POR, + [TABLA_A_CDC_CLK_RDAC_CLK_EN_CTL] = + TABLA_A_CDC_CLK_RDAC_CLK_EN_CTL__POR, + [TABLA_A_CDC_CLK_RX_B1_CTL] = TABLA_A_CDC_CLK_RX_B1_CTL__POR, + [TABLA_A_CDC_CLK_RX_B2_CTL] = TABLA_A_CDC_CLK_RX_B2_CTL__POR, + [TABLA_A_CDC_CLK_MCLK_CTL] = TABLA_A_CDC_CLK_MCLK_CTL__POR, + [TABLA_A_CDC_CLK_PDM_CTL] = TABLA_A_CDC_CLK_PDM_CTL__POR, + [TABLA_A_CDC_CLK_SD_CTL] = TABLA_A_CDC_CLK_SD_CTL__POR, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B1_CTL] = + TABLA_A_CDC_CLSG_FREQ_THRESH_B1_CTL__POR, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B2_CTL] = + TABLA_A_CDC_CLSG_FREQ_THRESH_B2_CTL__POR, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL] = + TABLA_A_CDC_CLSG_FREQ_THRESH_B3_CTL__POR, + [TABLA_A_CDC_CLSG_FREQ_THRESH_B4_CTL] = + TABLA_A_CDC_CLSG_FREQ_THRESH_B4_CTL__POR, + [TABLA_A_CDC_CLSG_GAIN_THRESH_CTL] = + TABLA_A_CDC_CLSG_GAIN_THRESH_CTL__POR, + [TABLA_A_CDC_CLSG_TIMER_B1_CFG] = TABLA_A_CDC_CLSG_TIMER_B1_CFG__POR, + [TABLA_A_CDC_CLSG_TIMER_B2_CFG] = TABLA_A_CDC_CLSG_TIMER_B2_CFG__POR, + [TABLA_A_CDC_CLSG_CTL] = TABLA_A_CDC_CLSG_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B1_CTL] = TABLA_A_CDC_IIR1_GAIN_B1_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B1_CTL] = TABLA_A_CDC_IIR2_GAIN_B1_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B2_CTL] = TABLA_A_CDC_IIR1_GAIN_B2_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B2_CTL] = TABLA_A_CDC_IIR2_GAIN_B2_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B3_CTL] = TABLA_A_CDC_IIR1_GAIN_B3_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B3_CTL] = TABLA_A_CDC_IIR2_GAIN_B3_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B4_CTL] = TABLA_A_CDC_IIR1_GAIN_B4_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B4_CTL] = TABLA_A_CDC_IIR2_GAIN_B4_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B5_CTL] = TABLA_A_CDC_IIR1_GAIN_B5_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B5_CTL] = TABLA_A_CDC_IIR2_GAIN_B5_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B6_CTL] = TABLA_A_CDC_IIR1_GAIN_B6_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B6_CTL] = TABLA_A_CDC_IIR2_GAIN_B6_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B7_CTL] = TABLA_A_CDC_IIR1_GAIN_B7_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B7_CTL] = TABLA_A_CDC_IIR2_GAIN_B7_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_B8_CTL] = TABLA_A_CDC_IIR1_GAIN_B8_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_B8_CTL] = TABLA_A_CDC_IIR2_GAIN_B8_CTL__POR, + [TABLA_A_CDC_IIR1_CTL] = TABLA_A_CDC_IIR1_CTL__POR, + [TABLA_A_CDC_IIR2_CTL] = TABLA_A_CDC_IIR2_CTL__POR, + [TABLA_A_CDC_IIR1_GAIN_TIMER_CTL] = + TABLA_A_CDC_IIR1_GAIN_TIMER_CTL__POR, + [TABLA_A_CDC_IIR2_GAIN_TIMER_CTL] = + TABLA_A_CDC_IIR2_GAIN_TIMER_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B1_CTL] = TABLA_A_CDC_IIR1_COEF_B1_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B1_CTL] = TABLA_A_CDC_IIR2_COEF_B1_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B2_CTL] = TABLA_A_CDC_IIR1_COEF_B2_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B2_CTL] = TABLA_A_CDC_IIR2_COEF_B2_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B3_CTL] = TABLA_A_CDC_IIR1_COEF_B3_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B3_CTL] = TABLA_A_CDC_IIR2_COEF_B3_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B4_CTL] = TABLA_A_CDC_IIR1_COEF_B4_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B4_CTL] = TABLA_A_CDC_IIR2_COEF_B4_CTL__POR, + [TABLA_A_CDC_IIR1_COEF_B5_CTL] = TABLA_A_CDC_IIR1_COEF_B5_CTL__POR, + [TABLA_A_CDC_IIR2_COEF_B5_CTL] = TABLA_A_CDC_IIR2_COEF_B5_CTL__POR, + [TABLA_A_CDC_TOP_GAIN_UPDATE] = TABLA_A_CDC_TOP_GAIN_UPDATE__POR, + [TABLA_A_CDC_DEBUG_B1_CTL] = TABLA_A_CDC_DEBUG_B1_CTL__POR, + [TABLA_A_CDC_DEBUG_B2_CTL] = TABLA_A_CDC_DEBUG_B2_CTL__POR, + [TABLA_A_CDC_DEBUG_B3_CTL] = TABLA_A_CDC_DEBUG_B3_CTL__POR, + [TABLA_A_CDC_DEBUG_B4_CTL] = TABLA_A_CDC_DEBUG_B4_CTL__POR, + [TABLA_A_CDC_DEBUG_B5_CTL] = TABLA_A_CDC_DEBUG_B5_CTL__POR, + [TABLA_A_CDC_DEBUG_B6_CTL] = TABLA_A_CDC_DEBUG_B6_CTL__POR, + [TABLA_A_CDC_CONN_RX1_B1_CTL] = TABLA_A_CDC_CONN_RX1_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX1_B2_CTL] = TABLA_A_CDC_CONN_RX1_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX1_B3_CTL] = TABLA_A_CDC_CONN_RX1_B3_CTL__POR, + [TABLA_A_CDC_CONN_RX2_B1_CTL] = TABLA_A_CDC_CONN_RX2_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX2_B2_CTL] = TABLA_A_CDC_CONN_RX2_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX2_B3_CTL] = TABLA_A_CDC_CONN_RX2_B3_CTL__POR, + [TABLA_A_CDC_CONN_RX3_B1_CTL] = TABLA_A_CDC_CONN_RX3_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX3_B2_CTL] = TABLA_A_CDC_CONN_RX3_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX3_B3_CTL] = TABLA_A_CDC_CONN_RX3_B3_CTL__POR, + [TABLA_A_CDC_CONN_RX4_B1_CTL] = TABLA_A_CDC_CONN_RX4_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX4_B2_CTL] = TABLA_A_CDC_CONN_RX4_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX5_B1_CTL] = TABLA_A_CDC_CONN_RX5_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX5_B2_CTL] = TABLA_A_CDC_CONN_RX5_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX6_B1_CTL] = TABLA_A_CDC_CONN_RX6_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX6_B2_CTL] = TABLA_A_CDC_CONN_RX6_B2_CTL__POR, + [TABLA_A_CDC_CONN_RX7_B1_CTL] = TABLA_A_CDC_CONN_RX7_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX7_B2_CTL] = TABLA_A_CDC_CONN_RX7_B2_CTL__POR, + [TABLA_A_CDC_CONN_TX_B1_CTL] = TABLA_A_CDC_CONN_TX_B1_CTL__POR, + [TABLA_A_CDC_CONN_TX_B2_CTL] = TABLA_A_CDC_CONN_TX_B2_CTL__POR, + [TABLA_A_CDC_CONN_TX_B3_CTL] = TABLA_A_CDC_CONN_TX_B3_CTL__POR, + [TABLA_A_CDC_CONN_TX_B4_CTL] = TABLA_A_CDC_CONN_TX_B4_CTL__POR, + [TABLA_A_CDC_CONN_EQ1_B1_CTL] = TABLA_A_CDC_CONN_EQ1_B1_CTL__POR, + [TABLA_A_CDC_CONN_EQ1_B2_CTL] = TABLA_A_CDC_CONN_EQ1_B2_CTL__POR, + [TABLA_A_CDC_CONN_EQ1_B3_CTL] = TABLA_A_CDC_CONN_EQ1_B3_CTL__POR, + [TABLA_A_CDC_CONN_EQ1_B4_CTL] = TABLA_A_CDC_CONN_EQ1_B4_CTL__POR, + [TABLA_A_CDC_CONN_EQ2_B1_CTL] = TABLA_A_CDC_CONN_EQ2_B1_CTL__POR, + [TABLA_A_CDC_CONN_EQ2_B2_CTL] = TABLA_A_CDC_CONN_EQ2_B2_CTL__POR, + [TABLA_A_CDC_CONN_EQ2_B3_CTL] = TABLA_A_CDC_CONN_EQ2_B3_CTL__POR, + [TABLA_A_CDC_CONN_EQ2_B4_CTL] = TABLA_A_CDC_CONN_EQ2_B4_CTL__POR, + [TABLA_A_CDC_CONN_SRC1_B1_CTL] = TABLA_A_CDC_CONN_SRC1_B1_CTL__POR, + [TABLA_A_CDC_CONN_SRC1_B2_CTL] = TABLA_A_CDC_CONN_SRC1_B2_CTL__POR, + [TABLA_A_CDC_CONN_SRC2_B1_CTL] = TABLA_A_CDC_CONN_SRC2_B1_CTL__POR, + [TABLA_A_CDC_CONN_SRC2_B2_CTL] = TABLA_A_CDC_CONN_SRC2_B2_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B1_CTL] = TABLA_A_CDC_CONN_TX_SB_B1_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B2_CTL] = TABLA_A_CDC_CONN_TX_SB_B2_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B3_CTL] = TABLA_A_CDC_CONN_TX_SB_B3_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B4_CTL] = TABLA_A_CDC_CONN_TX_SB_B4_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B5_CTL] = TABLA_A_CDC_CONN_TX_SB_B5_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B6_CTL] = TABLA_A_CDC_CONN_TX_SB_B6_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B7_CTL] = TABLA_A_CDC_CONN_TX_SB_B7_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B8_CTL] = TABLA_A_CDC_CONN_TX_SB_B8_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B9_CTL] = TABLA_A_CDC_CONN_TX_SB_B9_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B10_CTL] = TABLA_A_CDC_CONN_TX_SB_B10_CTL__POR, + [TABLA_A_CDC_CONN_TX_SB_B11_CTL] = TABLA_A_CDC_CONN_TX_SB_B11_CTL__POR, + [TABLA_A_CDC_CONN_RX_SB_B1_CTL] = TABLA_A_CDC_CONN_RX_SB_B1_CTL__POR, + [TABLA_A_CDC_CONN_RX_SB_B2_CTL] = TABLA_A_CDC_CONN_RX_SB_B2_CTL__POR, + [TABLA_A_CDC_CONN_CLSG_CTL] = TABLA_A_CDC_CONN_CLSG_CTL__POR, + [TABLA_A_CDC_CONN_SPARE] = TABLA_A_CDC_CONN_SPARE__POR, + [TABLA_A_CDC_MBHC_EN_CTL] = TABLA_A_CDC_MBHC_EN_CTL__POR, + [TABLA_A_CDC_MBHC_FEATURE_B1_CFG] = + TABLA_A_CDC_MBHC_FEATURE_B1_CFG__POR, + [TABLA_A_CDC_MBHC_FEATURE_B2_CFG] = + TABLA_A_CDC_MBHC_FEATURE_B2_CFG__POR, + [TABLA_A_CDC_MBHC_TIMER_B1_CTL] = TABLA_A_CDC_MBHC_TIMER_B1_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B2_CTL] = TABLA_A_CDC_MBHC_TIMER_B2_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B3_CTL] = TABLA_A_CDC_MBHC_TIMER_B3_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B4_CTL] = TABLA_A_CDC_MBHC_TIMER_B4_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B5_CTL] = TABLA_A_CDC_MBHC_TIMER_B5_CTL__POR, + [TABLA_A_CDC_MBHC_TIMER_B6_CTL] = TABLA_A_CDC_MBHC_TIMER_B6_CTL__POR, + [TABLA_A_CDC_MBHC_B1_STATUS] = TABLA_A_CDC_MBHC_B1_STATUS__POR, + [TABLA_A_CDC_MBHC_B2_STATUS] = TABLA_A_CDC_MBHC_B2_STATUS__POR, + [TABLA_A_CDC_MBHC_B3_STATUS] = TABLA_A_CDC_MBHC_B3_STATUS__POR, + [TABLA_A_CDC_MBHC_B4_STATUS] = TABLA_A_CDC_MBHC_B4_STATUS__POR, + [TABLA_A_CDC_MBHC_B5_STATUS] = TABLA_A_CDC_MBHC_B5_STATUS__POR, + [TABLA_A_CDC_MBHC_B1_CTL] = TABLA_A_CDC_MBHC_B1_CTL__POR, + [TABLA_A_CDC_MBHC_B2_CTL] = TABLA_A_CDC_MBHC_B2_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B1_CTL] = TABLA_A_CDC_MBHC_VOLT_B1_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B2_CTL] = TABLA_A_CDC_MBHC_VOLT_B2_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B3_CTL] = TABLA_A_CDC_MBHC_VOLT_B3_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B4_CTL] = TABLA_A_CDC_MBHC_VOLT_B4_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B5_CTL] = TABLA_A_CDC_MBHC_VOLT_B5_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B6_CTL] = TABLA_A_CDC_MBHC_VOLT_B6_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B7_CTL] = TABLA_A_CDC_MBHC_VOLT_B7_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B8_CTL] = TABLA_A_CDC_MBHC_VOLT_B8_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B9_CTL] = TABLA_A_CDC_MBHC_VOLT_B9_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B10_CTL] = TABLA_A_CDC_MBHC_VOLT_B10_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B11_CTL] = TABLA_A_CDC_MBHC_VOLT_B11_CTL__POR, + [TABLA_A_CDC_MBHC_VOLT_B12_CTL] = TABLA_A_CDC_MBHC_VOLT_B12_CTL__POR, + [TABLA_A_CDC_MBHC_CLK_CTL] = TABLA_A_CDC_MBHC_CLK_CTL__POR, + [TABLA_A_CDC_MBHC_INT_CTL] = TABLA_A_CDC_MBHC_INT_CTL__POR, + [TABLA_A_CDC_MBHC_DEBUG_CTL] = TABLA_A_CDC_MBHC_DEBUG_CTL__POR, + [TABLA_A_CDC_MBHC_SPARE] = TABLA_A_CDC_MBHC_SPARE__POR, +}; diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c new file mode 100644 index 00000000000..de1b1995fbb --- /dev/null +++ b/sound/soc/codecs/wcd9310.c @@ -0,0 +1,1598 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wcd9310.h" + +static const DECLARE_TLV_DB_SCALE(digital_gain, 0, 1, 0); +static const DECLARE_TLV_DB_SCALE(line_gain, 0, 7, 1); +static const DECLARE_TLV_DB_SCALE(analog_gain, 0, 25, 1); + +enum tabla_bandgap_type { + TABLA_BANDGAP_OFF = 0, + TABLA_BANDGAP_AUDIO_MODE, + TABLA_BANDGAP_MBHC_MODE, +}; + +struct tabla_priv { /* member undecided */ + struct snd_soc_codec *codec; + u32 ref_cnt; + u32 adc_count; + u32 dec_count; + enum tabla_bandgap_type bandgap_type; + bool clock_active; + bool config_mode_active; + bool mbhc_polling_active; + + struct tabla_mbhc_calibration *calibration; + + struct snd_soc_jack *jack; +}; + +static int tabla_codec_enable_charge_pump(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s %d\n", __func__, event); + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if ((tabla->bandgap_type != TABLA_BANDGAP_AUDIO_MODE) || + (!tabla->clock_active)) { + pr_err("%s: Error, Tabla must have clocks enabled for " + "charge pump\n", __func__); + return -EINVAL; + } + + snd_soc_update_bits(codec, TABLA_A_CP_EN, 0x01, 0x01); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, 0x01, + 0x01); + snd_soc_update_bits(codec, TABLA_A_CDC_CLSG_CTL, 0x08, 0x08); + usleep_range(200, 200); + snd_soc_update_bits(codec, TABLA_A_CP_STATIC, 0x10, 0x00); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_RESET_CTL, 0x10, + 0x10); + usleep_range(20, 20); + snd_soc_update_bits(codec, TABLA_A_CP_STATIC, 0x08, 0x08); + snd_soc_update_bits(codec, TABLA_A_CP_STATIC, 0x10, 0x10); + snd_soc_update_bits(codec, TABLA_A_CDC_CLSG_CTL, 0x08, 0x00); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, 0x01, + 0x00); + snd_soc_update_bits(codec, TABLA_A_CP_STATIC, 0x08, 0x00); + snd_soc_update_bits(codec, TABLA_A_CP_EN, 0x01, 0x00); + break; + } + return 0; +} + +static const struct snd_kcontrol_new tabla_snd_controls[] = { + SOC_SINGLE_TLV("LINEOUT1 Volume", TABLA_A_RX_LINE_1_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("LINEOUT3 Volume", TABLA_A_RX_LINE_3_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("HPHL Volume", TABLA_A_RX_HPH_L_GAIN, 0, 12, 1, + line_gain), + SOC_SINGLE_TLV("HPHR Volume", TABLA_A_RX_HPH_R_GAIN, 0, 12, 1, + line_gain), + + SOC_SINGLE_TLV("RX1 Digital Volume", TABLA_A_CDC_RX1_VOL_CTL_B2_CTL, 0, + 100, 0, digital_gain), + SOC_SINGLE_TLV("RX2 Digital Volume", TABLA_A_CDC_RX2_VOL_CTL_B2_CTL, 0, + 100, 0, digital_gain), + + SOC_SINGLE_TLV("DEC5 Volume", TABLA_A_CDC_TX5_VOL_CTL_GAIN, 0, 100, 0, + digital_gain), + SOC_SINGLE_TLV("DEC6 Volume", TABLA_A_CDC_TX6_VOL_CTL_GAIN, 0, 100, 0, + digital_gain), + + SOC_SINGLE_TLV("ADC1 Volume", TABLA_A_TX_1_2_EN, 1, 3, 0, analog_gain), + SOC_SINGLE_TLV("ADC2 Volume", TABLA_A_TX_1_2_EN, 5, 3, 0, analog_gain), + + SOC_SINGLE("MICBIAS1 CAPLESS Switch", TABLA_A_MICB_1_CTL, 4, 1, 1), +}; + +static const char *rx_mix1_text[] = { + "ZERO", "SRC1", "SRC2", "IIR1", "IIR2", "RX1", "RX2", "RX3", "RX4", + "RX5", "RX6", "RX7" +}; + +static const char *sb_tx1_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC1" +}; + +static const char *sb_tx5_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC5" +}; + +static const char *sb_tx6_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC6" +}; + +static const char const *sb_tx7_to_tx10_mux_text[] = { + "ZERO", "RMIX1", "RMIX2", "RMIX3", "RMIX4", "RMIX5", "RMIX6", "RMIX7", + "DEC1", "DEC2", "DEC3", "DEC4", "DEC5", "DEC6", "DEC7", "DEC8", + "DEC9", "DEC10" +}; + +static const char *dec1_mux_text[] = { + "ZERO", "DMIC1", "ADC6", +}; + +static const char *dec5_mux_text[] = { + "ZERO", "DMIC5", "ADC2", +}; + +static const char *dec6_mux_text[] = { + "ZERO", "DMIC6", "ADC1", +}; + +static const char const *dec7_mux_text[] = { + "ZERO", "DMIC1", "DMIC6", "ADC1", "ADC6", "ANC1_FB", "ANC2_FB", +}; + +static const char *iir1_inp1_text[] = { + "ZERO", "DEC1", "DEC2", "DEC3", "DEC4", "DEC5", "DEC6", "DEC7", "DEC8", + "DEC9", "DEC10", "RX1", "RX2", "RX3", "RX4", "RX5", "RX6", "RX7" +}; + +static const struct soc_enum rx_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX1_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx2_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX2_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx3_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX3_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx4_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX4_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum rx5_mix1_inp1_chain_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_RX5_B1_CTL, 0, 12, rx_mix1_text); + +static const struct soc_enum sb_tx5_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B5_CTL, 0, 9, sb_tx5_mux_text); + +static const struct soc_enum sb_tx6_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B6_CTL, 0, 9, sb_tx6_mux_text); + +static const struct soc_enum sb_tx7_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B7_CTL, 0, 18, + sb_tx7_to_tx10_mux_text); + +static const struct soc_enum sb_tx8_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B8_CTL, 0, 18, + sb_tx7_to_tx10_mux_text); + +static const struct soc_enum sb_tx1_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_SB_B1_CTL, 0, 9, sb_tx1_mux_text); + +static const struct soc_enum dec1_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B1_CTL, 0, 3, dec1_mux_text); + +static const struct soc_enum dec5_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B2_CTL, 0, 3, dec5_mux_text); + +static const struct soc_enum dec6_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B2_CTL, 2, 3, dec6_mux_text); + +static const struct soc_enum dec7_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_TX_B2_CTL, 4, 7, dec7_mux_text); + +static const struct soc_enum iir1_inp1_mux_enum = + SOC_ENUM_SINGLE(TABLA_A_CDC_CONN_EQ1_B1_CTL, 0, 18, iir1_inp1_text); + +static const struct snd_kcontrol_new rx_mix1_inp1_mux = + SOC_DAPM_ENUM("RX1 MIX1 INP1 Mux", rx_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx2_mix1_inp1_mux = + SOC_DAPM_ENUM("RX2 MIX1 INP1 Mux", rx2_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx3_mix1_inp1_mux = + SOC_DAPM_ENUM("RX3 MIX1 INP1 Mux", rx3_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx4_mix1_inp1_mux = + SOC_DAPM_ENUM("RX4 MIX1 INP1 Mux", rx4_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new rx5_mix1_inp1_mux = + SOC_DAPM_ENUM("RX5 MIX1 INP1 Mux", rx5_mix1_inp1_chain_enum); + +static const struct snd_kcontrol_new sb_tx5_mux = + SOC_DAPM_ENUM("SLIM TX5 MUX Mux", sb_tx5_mux_enum); + +static const struct snd_kcontrol_new sb_tx6_mux = + SOC_DAPM_ENUM("SLIM TX6 MUX Mux", sb_tx6_mux_enum); + +static const struct snd_kcontrol_new sb_tx7_mux = + SOC_DAPM_ENUM("SLIM TX7 MUX Mux", sb_tx7_mux_enum); + +static const struct snd_kcontrol_new sb_tx8_mux = + SOC_DAPM_ENUM("SLIM TX8 MUX Mux", sb_tx8_mux_enum); + +static const struct snd_kcontrol_new sb_tx1_mux = + SOC_DAPM_ENUM("SLIM TX1 MUX Mux", sb_tx1_mux_enum); + +static const struct snd_kcontrol_new dec1_mux = + SOC_DAPM_ENUM("DEC1 MUX Mux", dec1_mux_enum); + +static const struct snd_kcontrol_new dec5_mux = + SOC_DAPM_ENUM("DEC5 MUX Mux", dec5_mux_enum); + +static const struct snd_kcontrol_new dec6_mux = + SOC_DAPM_ENUM("DEC6 MUX Mux", dec6_mux_enum); + +static const struct snd_kcontrol_new dec7_mux = + SOC_DAPM_ENUM("DEC7 MUX Mux", dec7_mux_enum); + +static const struct snd_kcontrol_new iir1_inp1_mux = + SOC_DAPM_ENUM("IIR1 INP1 Mux", iir1_inp1_mux_enum); + +static const struct snd_kcontrol_new dac1_control = + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_EAR_EN, 5, 1, 0); + +static const struct snd_kcontrol_new hphl_switch = + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_HPH_L_DAC_CTL, 6, 1, 0); + +static const struct snd_kcontrol_new hphr_switch = + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_HPH_R_DAC_CTL, 6, 1, 0); + +static const struct snd_kcontrol_new lineout1_switch = + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_LINE_1_DAC_CTL, 6, 1, 0); + +static const struct snd_kcontrol_new lineout3_switch = + SOC_DAPM_SINGLE("Switch", TABLA_A_RX_LINE_3_DAC_CTL, 6, 1, 0); + +static void tabla_codec_enable_adc_block(struct snd_soc_codec *codec, + int enable) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s %d\n", __func__, enable); + + if (enable) { + tabla->adc_count++; + snd_soc_update_bits(codec, TABLA_A_TX_COM_BIAS, 0xE0, 0xE0); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, 0x2, 0x2); + } else { + tabla->adc_count--; + if (!tabla->adc_count) { + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, + 0x2, 0x0); + if (!tabla->mbhc_polling_active) + snd_soc_update_bits(codec, TABLA_A_TX_COM_BIAS, + 0xE0, 0x0); + } + } +} + +static int tabla_codec_enable_adc(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 adc_reg; + + pr_debug("%s %d\n", __func__, event); + + if (w->reg == TABLA_A_TX_1_2_EN) + adc_reg = TABLA_A_TX_1_2_TEST_CTL; + else if (w->reg == TABLA_A_TX_3_4_EN) + adc_reg = TABLA_A_TX_3_4_TEST_CTL; + else if (w->reg == TABLA_A_TX_5_6_EN) + adc_reg = TABLA_A_TX_5_6_TEST_CTL; + else { + pr_err("%s: Error, invalid adc register\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + tabla_codec_enable_adc_block(codec, 1); + break; + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, adc_reg, 1 << w->shift, + 1 << w->shift); + usleep_range(1000, 1000); + snd_soc_update_bits(codec, adc_reg, 1 << w->shift, 0x00); + usleep_range(1000, 1000); + break; + case SND_SOC_DAPM_POST_PMD: + tabla_codec_enable_adc_block(codec, 0); + break; + } + return 0; +} + +static int tabla_codec_enable_pamp_gain(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d\n", __func__, event); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, TABLA_A_RX_EAR_GAIN, 0x80, 0x80); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, TABLA_A_RX_EAR_GAIN, 0x80, 0x00); + break; + } + return 0; +} + +static int tabla_codec_enable_lineout(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 lineout_gain_reg; + + pr_debug("%s %d\n", __func__, event); + + switch (w->shift) { + case 0: + lineout_gain_reg = TABLA_A_RX_LINE_1_GAIN; + break; + case 1: + lineout_gain_reg = TABLA_A_RX_LINE_2_GAIN; + break; + case 2: + lineout_gain_reg = TABLA_A_RX_LINE_3_GAIN; + break; + case 3: + lineout_gain_reg = TABLA_A_RX_LINE_4_GAIN; + break; + case 4: + lineout_gain_reg = TABLA_A_RX_LINE_5_GAIN; + break; + default: + pr_err("%s: Error, incorrect lineout register value\n", + __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, lineout_gain_reg, 0x40, 0x40); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(40000, 40000); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, lineout_gain_reg, 0x40, 0x00); + break; + } + return 0; +} + +static int tabla_codec_enable_dmic1(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + pr_debug("%s %d\n", __func__, event); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, TABLA_A_CDC_TX1_MUX_CTL, 0x1, 0x1); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_DMIC_CTL, 0x2, 0x2); + snd_soc_update_bits(codec, TABLA_A_CDC_TX1_DMIC_CTL, 0x1, 0x1); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_DMIC_CTL, 0x1, 0x1); + break; + case SND_SOC_DAPM_POST_PMD: + snd_soc_update_bits(codec, TABLA_A_CDC_TX1_DMIC_CTL, 0x1, 0); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_DMIC_CTL, 0x3, 0); + break; + } + return 0; +} + +static int tabla_codec_enable_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 micb_cfilt_reg, micb_int_reg; + char *internal_text = "Internal"; + + pr_debug("%s %d\n", __func__, event); + switch (w->reg) { + case TABLA_A_MICB_1_CTL: + micb_cfilt_reg = TABLA_A_MICB_CFILT_1_CTL; + micb_int_reg = TABLA_A_MICB_1_INT_RBIAS; + break; + case TABLA_A_MICB_2_CTL: + micb_cfilt_reg = TABLA_A_MICB_CFILT_2_CTL; + micb_int_reg = TABLA_A_MICB_2_INT_RBIAS; + break; + case TABLA_A_MICB_3_CTL: + micb_cfilt_reg = TABLA_A_MICB_CFILT_3_CTL; + micb_int_reg = TABLA_A_MICB_3_INT_RBIAS; + break; + default: + pr_err("%s: Error, invalid micbias register\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0x80); + if (strnstr(w->name, internal_text, 20)) + snd_soc_update_bits(codec, micb_int_reg, 0xE0, 0xE0); + break; + case SND_SOC_DAPM_POST_PMD: + if (strnstr(w->name, internal_text, 20)) + snd_soc_update_bits(codec, micb_int_reg, 0x80, 0x00); + snd_soc_update_bits(codec, micb_cfilt_reg, 0x80, 0); + break; + } + + return 0; +} + +static void tabla_codec_enable_dec_clock(struct snd_soc_codec *codec, + int enable) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (enable) { + tabla->dec_count++; + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, 0x4, 0x4); + } else { + tabla->dec_count--; + if (!tabla->dec_count) + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_OTHR_CTL, + 0x4, 0x0); + } +} + +static int tabla_codec_enable_dec(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 dec_reset_reg; + + pr_debug("%s %d\n", __func__, event); + + if (w->reg == TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL) + dec_reset_reg = TABLA_A_CDC_CLK_TX_RESET_B1_CTL; + else if (w->reg == TABLA_A_CDC_CLK_TX_CLK_EN_B2_CTL) + dec_reset_reg = TABLA_A_CDC_CLK_TX_RESET_B2_CTL; + else { + pr_err("%s: Error, incorrect dec\n", __func__); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + tabla_codec_enable_dec_clock(codec, 1); + snd_soc_update_bits(codec, dec_reset_reg, 1 << w->shift, + 1 << w->shift); + snd_soc_update_bits(codec, dec_reset_reg, 1 << w->shift, 0x0); + break; + case SND_SOC_DAPM_POST_PMD: + tabla_codec_enable_dec_clock(codec, 0); + break; + } + return 0; +} + +static int tabla_codec_reset_interpolator_1(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x1, + 0x1); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x1, + 0x0); + break; + } + return 0; +} + +static int tabla_codec_reset_interpolator_2(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x2, + 0x2); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x2, + 0x0); + break; + } + return 0; +} + +static int tabla_codec_reset_interpolator_3(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x4, + 0x4); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x4, + 0x0); + break; + } + return 0; +} + +static int tabla_codec_reset_interpolator_4(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x8, + 0x8); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x8, + 0x0); + break; + } + return 0; +} + +static int tabla_codec_reset_interpolator_5(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x10, + 0x10); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_RX_RESET_CTL, 0x10, + 0x0); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget tabla_dapm_widgets[] = { + /*RX stuff */ + SND_SOC_DAPM_OUTPUT("EAR"), + + SND_SOC_DAPM_PGA_E("EAR PA", TABLA_A_RX_EAR_EN, 4, 0, NULL, 0, + tabla_codec_enable_pamp_gain, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_PGA("EAR PA Input", TABLA_A_CDC_CLSG_CTL, 2, 0, NULL, 0), + + SND_SOC_DAPM_SWITCH("DAC1", TABLA_A_RX_EAR_EN, 6, 0, &dac1_control), + SND_SOC_DAPM_PGA_E("RX1 CP", SND_SOC_NOPM, 0, 0, NULL, 0, + tabla_codec_enable_charge_pump, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA("RX BIAS", TABLA_A_RX_COM_BIAS, 7, 0, NULL, 0), + SND_SOC_DAPM_MUX_E("RX1 MIX1 INP1", TABLA_A_CDC_CLK_RX_B1_CTL, 0, 0, + &rx_mix1_inp1_mux, tabla_codec_reset_interpolator_1, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_AIF_IN("SLIM RX1", "AIF1 Playback", 0, + TABLA_A_CDC_RX1_B6_CTL, 5, 0), + + /* RX 2 path */ + SND_SOC_DAPM_PGA_E("RX2 CP", SND_SOC_NOPM, 0, 0, NULL, 0, + tabla_codec_enable_charge_pump, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MUX_E("RX2 MIX1 INP1", TABLA_A_CDC_CLK_RX_B1_CTL, 1, 0, + &rx2_mix1_inp1_mux, tabla_codec_reset_interpolator_2, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_AIF_IN("SLIM RX2", "AIF1 Playback", 0, + TABLA_A_CDC_RX2_B6_CTL, 5, 0), + + /* Headphone */ + SND_SOC_DAPM_OUTPUT("HEADPHONE"), + SND_SOC_DAPM_PGA("HPHL", TABLA_A_RX_HPH_CNP_EN, 5, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("HPHL DAC", TABLA_A_RX_HPH_L_DAC_CTL, 7, 0, + &hphl_switch), + + SND_SOC_DAPM_PGA("HPHR", TABLA_A_RX_HPH_CNP_EN, 4, 0, NULL, 0), + SND_SOC_DAPM_SWITCH("HPHR DAC", TABLA_A_RX_HPH_R_DAC_CTL, 7, 0, + &hphr_switch), + + /* Speaker */ + SND_SOC_DAPM_OUTPUT("LINEOUT"), + SND_SOC_DAPM_PGA_E("LINEOUT1", TABLA_A_RX_LINE_CNP_EN, 0, 0, NULL, 0, + tabla_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("LINEOUT1 DAC", TABLA_A_RX_LINE_1_DAC_CTL, 7, 0, + &lineout1_switch), + SND_SOC_DAPM_MUX_E("RX3 MIX1 INP1", TABLA_A_CDC_CLK_RX_B1_CTL, 2, 0, + &rx3_mix1_inp1_mux, tabla_codec_reset_interpolator_3, + SND_SOC_DAPM_PRE_PMU), + + SND_SOC_DAPM_PGA_E("LINEOUT3", TABLA_A_RX_LINE_CNP_EN, 2, 0, NULL, 0, + tabla_codec_enable_lineout, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SWITCH("LINEOUT3 DAC", TABLA_A_RX_LINE_3_DAC_CTL, 7, 0, + &lineout3_switch), + SND_SOC_DAPM_MUX_E("RX4 MIX1 INP1", TABLA_A_CDC_CLK_RX_B1_CTL, 3, 0, + &rx4_mix1_inp1_mux, tabla_codec_reset_interpolator_4, + SND_SOC_DAPM_PRE_PMU), + SND_SOC_DAPM_MUX_E("RX5 MIX1 INP1", TABLA_A_CDC_CLK_RX_B1_CTL, 4, 0, + &rx5_mix1_inp1_mux, tabla_codec_reset_interpolator_5, + SND_SOC_DAPM_PRE_PMU), + + /* TX */ + SND_SOC_DAPM_INPUT("AMIC1"), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 External", TABLA_A_MICB_1_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS1 Internal", TABLA_A_MICB_1_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC1", NULL, TABLA_A_TX_1_2_EN, 7, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC1 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 0, 0, + &dec1_mux, tabla_codec_enable_dec, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC5 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 4, 0, + &dec5_mux, tabla_codec_enable_dec, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC6 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 5, 0, + &dec6_mux, tabla_codec_enable_dec, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX_E("DEC7 MUX", TABLA_A_CDC_CLK_TX_CLK_EN_B1_CTL, 6, 0, + &dec7_mux, tabla_codec_enable_dec, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_INPUT("AMIC2"), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 External", TABLA_A_MICB_2_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS2 Internal", TABLA_A_MICB_2_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS3 External", TABLA_A_MICB_3_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MICBIAS_E("MIC BIAS3 Internal", TABLA_A_MICB_3_CTL, 7, 0, + tabla_codec_enable_micbias, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_ADC_E("ADC2", NULL, TABLA_A_TX_1_2_EN, 3, 0, + tabla_codec_enable_adc, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("SLIM TX1 MUX", SND_SOC_NOPM, 0, 0, &sb_tx1_mux), + SND_SOC_DAPM_AIF_OUT("SLIM TX1", "AIF1 Capture", NULL, SND_SOC_NOPM, + 0, 0), + + SND_SOC_DAPM_MUX("SLIM TX5 MUX", SND_SOC_NOPM, 0, 0, &sb_tx5_mux), + SND_SOC_DAPM_AIF_OUT("SLIM TX5", "AIF1 Capture", NULL, SND_SOC_NOPM, + 4, 0), + + SND_SOC_DAPM_MUX("SLIM TX6 MUX", SND_SOC_NOPM, 0, 0, &sb_tx6_mux), + SND_SOC_DAPM_AIF_OUT("SLIM TX6", "AIF1 Capture", NULL, SND_SOC_NOPM, + 5, 0), + + SND_SOC_DAPM_MUX("SLIM TX7 MUX", SND_SOC_NOPM, 0, 0, &sb_tx7_mux), + SND_SOC_DAPM_AIF_OUT("SLIM TX7", "AIF1 Capture", NULL, SND_SOC_NOPM, + 0, 0), + + SND_SOC_DAPM_MUX("SLIM TX8 MUX", SND_SOC_NOPM, 0, 0, &sb_tx8_mux), + SND_SOC_DAPM_AIF_OUT("SLIM TX8", "AIF1 Capture", NULL, SND_SOC_NOPM, + 0, 0), + + /* Digital Mic */ + SND_SOC_DAPM_INPUT("DMIC1 IN"), + SND_SOC_DAPM_MIC("DMIC1", &tabla_codec_enable_dmic1), + + /* Sidetone */ + SND_SOC_DAPM_MUX("IIR1 INP1 MUX", SND_SOC_NOPM, 0, 0, &iir1_inp1_mux), + SND_SOC_DAPM_PGA("IIR1", TABLA_A_CDC_CLK_SD_CTL, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* SLIMBUS Connections */ + {"RX BIAS", NULL, "SLIM RX1"}, + {"RX BIAS", NULL, "SLIM RX2"}, + + {"SLIM TX1", NULL, "SLIM TX1 MUX"}, + {"SLIM TX1 MUX", "DEC1", "DEC1 MUX"}, + + {"SLIM TX5", NULL, "SLIM TX5 MUX"}, + {"SLIM TX5 MUX", "DEC5", "DEC5 MUX"}, + + {"SLIM TX6", NULL, "SLIM TX6 MUX"}, + {"SLIM TX6 MUX", "DEC6", "DEC6 MUX"}, + + {"SLIM TX7", NULL, "SLIM TX7 MUX"}, + {"SLIM TX7 MUX", "DEC1", "DEC1 MUX"}, + {"SLIM TX7 MUX", "DEC7", "DEC7 MUX"}, + {"SLIM TX7 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX7 MUX", "DEC6", "DEC6 MUX"}, + + {"SLIM TX8", NULL, "SLIM TX8 MUX"}, + {"SLIM TX8 MUX", "DEC5", "DEC5 MUX"}, + {"SLIM TX8 MUX", "DEC6", "DEC6 MUX"}, + + /* Earpiece (RX MIX1) */ + {"EAR", NULL, "EAR PA"}, + {"EAR PA", NULL, "EAR PA Input"}, + {"EAR PA Input", NULL, "DAC1"}, + {"DAC1", "Switch", "RX1 CP"}, + {"RX1 CP", NULL, "RX1 MIX1 INP1"}, + {"RX1 MIX1 INP1", "RX1", "RX BIAS"}, + + /* Headset (RX MIX1 and RX MIX2) */ + {"HEADPHONE", NULL, "HPHL"}, + {"HPHL", NULL, "HPHL DAC"}, + {"HPHL DAC", "Switch", "RX1 MIX1 INP1"}, + + {"HEADPHONE", NULL, "HPHR"}, + {"HPHR", NULL, "HPHR DAC"}, + {"HPHR DAC", "Switch", "RX2 CP"}, + {"RX2 CP", NULL, "RX2 MIX1 INP1"}, + {"RX2 MIX1 INP1", "RX2", "RX BIAS"}, + + /* Speaker (RX MIX3 and RX MIX4) */ + {"LINEOUT", NULL, "LINEOUT1"}, + {"LINEOUT1", NULL, "LINEOUT1 DAC"}, + {"LINEOUT1 DAC", "Switch", "RX3 MIX1 INP1"}, + {"RX3 MIX1 INP1", "RX1", "RX BIAS"}, + + {"LINEOUT", NULL, "LINEOUT3"}, + {"LINEOUT3", NULL, "LINEOUT3 DAC"}, + {"LINEOUT3 DAC", "Switch", "RX5 MIX1 INP1"}, + {"RX4 MIX1 INP1", "RX2", "RX BIAS"}, + {"RX5 MIX1 INP1", "RX2", "RX BIAS"}, + + /* Handset TX */ + {"DEC5 MUX", "ADC2", "ADC2"}, + {"DEC6 MUX", "ADC1", "ADC1"}, + {"ADC1", NULL, "AMIC1"}, + {"ADC2", NULL, "AMIC2"}, + + /* Digital Mic */ + {"DEC1 MUX", "DMIC1", "DMIC1"}, + {"DEC7 MUX", "DMIC1", "DMIC1"}, + {"DMIC1", NULL, "DMIC1 IN"}, + + /* Sidetone (IIR1) */ + {"RX1 MIX1 INP1", "IIR1", "IIR1"}, + {"IIR1", NULL, "IIR1 INP1 MUX"}, + {"IIR1 INP1 MUX", "DEC6", "DEC6 MUX"}, + +}; + +static int tabla_readable(struct snd_soc_codec *ssc, unsigned int reg) +{ + return tabla_reg_readable[reg]; +} + +static int tabla_volatile(struct snd_soc_codec *ssc, unsigned int reg) +{ + /* Registers lower than 0x100 are top level registers which can be + * written by the Tabla core driver. + */ + + if ((reg >= TABLA_A_CDC_MBHC_EN_CTL) || (reg < 0x100)) + return 1; + + return 0; +} + +#define TABLA_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) +static int tabla_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + int ret; + pr_debug("%s: write reg %x val %x\n", __func__, reg, value); + + BUG_ON(reg > TABLA_MAX_REGISTER); + + if (!tabla_volatile(codec, reg)) { + pr_debug("writing to cache\n"); + ret = snd_soc_cache_write(codec, reg, value); + if (ret != 0) + dev_err(codec->dev, "Cache write to %x failed: %d\n", + reg, ret); + } + + return tabla_reg_write(codec->control_data, reg, value); +} +static unsigned int tabla_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + unsigned int val; + int ret; + + BUG_ON(reg > TABLA_MAX_REGISTER); + + if (!tabla_volatile(codec, reg) && tabla_readable(codec, reg) && + reg < codec->driver->reg_cache_size) { + pr_debug("reading from cache\n"); + ret = snd_soc_cache_read(codec, reg, &val); + if (ret >= 0) { + pr_debug("register %d, value %d\n", reg, val); + return val; + } else + dev_err(codec->dev, "Cache read from %x failed: %d\n", + reg, ret); + } + + val = tabla_reg_read(codec->control_data, reg); + pr_debug("%s: read reg %x val %x\n", __func__, reg, val); + return val; +} + +static void tabla_codec_enable_audio_mode_bandgap(struct snd_soc_codec *codec) +{ + snd_soc_write(codec, TABLA_A_BIAS_REF_CTL, 0x1C); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x80); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x04, + 0x04); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x01, + 0x01); + usleep_range(1000, 1000); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x00); +} + +static void tabla_codec_enable_bandgap(struct snd_soc_codec *codec, + enum tabla_bandgap_type choice) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + /* TODO lock resources accessed by audio streams and threaded + * interrupt handlers + */ + + pr_debug("%s, choice is %d, current is %d\n", __func__, choice, + tabla->bandgap_type); + + if (tabla->bandgap_type == choice) + return; + + if ((tabla->bandgap_type == TABLA_BANDGAP_OFF) && + (choice == TABLA_BANDGAP_AUDIO_MODE)) { + tabla_codec_enable_audio_mode_bandgap(codec); + } else if ((tabla->bandgap_type == TABLA_BANDGAP_AUDIO_MODE) && + (choice == TABLA_BANDGAP_MBHC_MODE)) { + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x2, + 0x2); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x80); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x4, + 0x4); + usleep_range(1000, 1000); + snd_soc_update_bits(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x80, + 0x00); + } else if ((tabla->bandgap_type == TABLA_BANDGAP_MBHC_MODE) && + (choice == TABLA_BANDGAP_AUDIO_MODE)) { + snd_soc_write(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x00); + usleep_range(100, 100); + tabla_codec_enable_audio_mode_bandgap(codec); + } else if (choice == TABLA_BANDGAP_OFF) { + snd_soc_write(codec, TABLA_A_BIAS_CENTRAL_BG_CTL, 0x00); + } else { + pr_err("%s: Error, Invalid bandgap settings\n", __func__); + } + tabla->bandgap_type = choice; +} + +static int tabla_codec_enable_config_mode(struct snd_soc_codec *codec, + int enable) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + if (enable) { + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_FREQ, 0x10, 0); + snd_soc_write(codec, TABLA_A_BIAS_CONFIG_MODE_BG_CTL, 0x17); + usleep_range(5, 5); + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_FREQ, 0x80, + 0x80); + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_TEST, 0x80, + 0x80); + usleep_range(10, 10); + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_TEST, 0x80, 0); + usleep_range(20, 20); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x08, 0x08); + } else { + snd_soc_update_bits(codec, TABLA_A_BIAS_CONFIG_MODE_BG_CTL, 0x1, + 0); + snd_soc_update_bits(codec, TABLA_A_CONFIG_MODE_FREQ, 0x80, 0); + } + tabla->config_mode_active = enable ? true : false; + + return 0; +} + +static int tabla_codec_enable_clock_block(struct snd_soc_codec *codec, + int config_mode) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s\n", __func__); + + if (config_mode) { + tabla_codec_enable_config_mode(codec, 1); + snd_soc_write(codec, TABLA_A_CLK_BUFF_EN2, 0x00); + snd_soc_write(codec, TABLA_A_CLK_BUFF_EN2, 0x02); + snd_soc_write(codec, TABLA_A_CLK_BUFF_EN1, 0x0D); + usleep_range(1000, 1000); + } else + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x08, 0x00); + + if (!config_mode && tabla->mbhc_polling_active) { + snd_soc_write(codec, TABLA_A_CLK_BUFF_EN2, 0x02); + tabla_codec_enable_config_mode(codec, 0); + + } + + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x05, 0x05); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x02, 0x00); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x04, 0x04); + snd_soc_update_bits(codec, TABLA_A_CDC_CLK_MCLK_CTL, 0x01, 0x01); + usleep_range(50, 50); + tabla->clock_active = true; + return 0; +} +static void tabla_codec_disable_clock_block(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + pr_debug("%s\n", __func__); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x04, 0x00); + ndelay(160); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN2, 0x02, 0x02); + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x05, 0x00); + tabla->clock_active = false; +} + +static int tabla_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + pr_debug("%s()\n", __func__); + + if (!codec) { + pr_err("Error, no codec found\n"); + return -EINVAL; + } + tabla->ref_cnt++; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* Enable LDO */ + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_1_VAL, 0xFC, + 0xA0); + snd_soc_update_bits(codec, TABLA_A_LDO_H_MODE_1, 0x80, 0x80); + usleep_range(1000, 1000); + } + + if (tabla->mbhc_polling_active && (tabla->ref_cnt == 1)) { + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_AUDIO_MODE); + tabla_codec_enable_clock_block(codec, 0); + } + + return ret; +} + +static void tabla_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + + pr_debug("%s()\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* Disable LDO */ + snd_soc_update_bits(codec, TABLA_A_LDO_H_MODE_1, 0x80, 0x00); + usleep_range(1000, 1000); + } + + if (!tabla->ref_cnt) { + pr_err("Error, trying to shutdown codec when already down\n"); + return; + } + tabla->ref_cnt--; + + if (tabla->mbhc_polling_active) { + if (!tabla->ref_cnt) { + tabla_codec_enable_bandgap(codec, + TABLA_BANDGAP_MBHC_MODE); + snd_soc_update_bits(codec, TABLA_A_RX_COM_BIAS, 0x80, + 0x80); + tabla_codec_enable_clock_block(codec, 1); + } + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x05, 0x01); + } +} + +static int tabla_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + + pr_debug("%s %d\n", __func__, mute); + + /* TODO mute TX */ + if (mute) + snd_soc_update_bits(codec, TABLA_A_CDC_RX1_B6_CTL, 0x01, 0x01); + else + snd_soc_update_bits(codec, TABLA_A_CDC_RX1_B6_CTL, 0x01, 0x00); + + return 0; +} + +static int tabla_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static int tabla_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static int tabla_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + pr_debug("%s: DAI-ID %x\n", __func__, dai->id); + return 0; +} + +static struct snd_soc_dai_ops tabla_dai_ops = { + .startup = tabla_startup, + .shutdown = tabla_shutdown, + .hw_params = tabla_hw_params, + .set_sysclk = tabla_set_dai_sysclk, + .set_fmt = tabla_set_dai_fmt, + .digital_mute = tabla_digital_mute, +}; + +static struct snd_soc_dai_driver tabla_dai[] = { + { + .name = "tabla_rx1", + .id = 1, + .playback = { + .stream_name = "AIF1 Playback", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = TABLA_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, + { + .name = "tabla_tx1", + .id = 2, + .capture = { + .stream_name = "AIF1 Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = TABLA_FORMATS, + .rate_max = 48000, + .rate_min = 8000, + .channels_min = 1, + .channels_max = 2, + }, + .ops = &tabla_dai_ops, + }, +}; + +static void tabla_codec_setup_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + struct tabla_mbhc_calibration *calibration = tabla->calibration; + int micbias_ctl_reg, micbias_cfilt_val_reg, micbias_cfilt_ctl_reg; + + if (!calibration) { + pr_err("Error, no tabla calibration\n"); + return; + } + + tabla->mbhc_polling_active = true; + + if (!tabla->ref_cnt) { + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_MBHC_MODE); + snd_soc_update_bits(codec, TABLA_A_RX_COM_BIAS, 0x80, 0x80); + tabla_codec_enable_clock_block(codec, 1); + } + + snd_soc_update_bits(codec, TABLA_A_CLK_BUFF_EN1, 0x05, 0x01); + + /* TODO store register values in calibration */ + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B4_CTL, 0x09); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B3_CTL, 0xEE); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B2_CTL, 0xFC); + snd_soc_write(codec, TABLA_A_CDC_MBHC_VOLT_B1_CTL, 0xCE); + + snd_soc_update_bits(codec, TABLA_A_LDO_H_MODE_1, 0x0F, 0x0D); + snd_soc_update_bits(codec, TABLA_A_TX_COM_BIAS, 0xE0, 0xE0); + + /* TODO select cfilt separately from the micbias line inside the machine + * driver + */ + switch (calibration->bias) { + case TABLA_MICBIAS1: + micbias_ctl_reg = TABLA_A_MICB_1_CTL; + micbias_cfilt_ctl_reg = TABLA_A_MICB_CFILT_1_CTL; + micbias_cfilt_val_reg = TABLA_A_MICB_CFILT_1_VAL; + break; + case TABLA_MICBIAS2: + micbias_ctl_reg = TABLA_A_MICB_2_CTL; + micbias_cfilt_ctl_reg = TABLA_A_MICB_CFILT_2_CTL; + micbias_cfilt_val_reg = TABLA_A_MICB_CFILT_2_VAL; + break; + case TABLA_MICBIAS3: + micbias_ctl_reg = TABLA_A_MICB_3_CTL; + micbias_cfilt_ctl_reg = TABLA_A_MICB_CFILT_3_CTL; + micbias_cfilt_val_reg = TABLA_A_MICB_CFILT_3_VAL; + break; + case TABLA_MICBIAS4: + pr_err("%s: Error, microphone bias 4 not supported\n", + __func__); + return; + default: + pr_err("Error, invalid mic bias line\n"); + return; + } + snd_soc_write(codec, micbias_cfilt_ctl_reg, 0x40); + + snd_soc_write(codec, micbias_ctl_reg, 0x36); + + snd_soc_write(codec, micbias_cfilt_val_reg, 0x68); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x2, 0x2); + snd_soc_write(codec, TABLA_A_MBHC_SCALING_MUX_1, 0x4); + + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN, 0x80, 0x80); + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN, 0x1F, 0x1C); + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_TEST_CTL, 0x40, 0x40); + + snd_soc_update_bits(codec, TABLA_A_TX_7_MBHC_EN, 0x80, 0x00); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x80, 0x80); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x80, 0x00); + + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B1_CTL, 3); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B2_CTL, 9); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B3_CTL, 30); + snd_soc_write(codec, TABLA_A_CDC_MBHC_TIMER_B6_CTL, 120); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_TIMER_B1_CTL, 0x78, 0x58); + snd_soc_write(codec, TABLA_A_CDC_MBHC_B2_CTL, 11); + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, 0x4, 0x4); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x8); + + tabla_enable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + tabla_enable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x1); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_CLK_CTL, 0x8, 0x0); + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x1); + /* TODO check if we need to maintain the other register bits */ +} + +static int tabla_codec_enable_hs_detect(struct snd_soc_codec *codec, + int insertion) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + struct tabla_mbhc_calibration *calibration = tabla->calibration; + int central_bias_enabled = 0; + int micbias_int_reg, micbias_ctl_reg, micbias_mbhc_reg; + + if (!calibration) { + pr_err("Error, no tabla calibration\n"); + return -EINVAL; + } + + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x1, 0); + + if (insertion) + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x2, 0); + else + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x2, 0x2); + + if (snd_soc_read(codec, TABLA_A_CDC_MBHC_B1_CTL) & 0x4) { + if (!(tabla->clock_active)) { + tabla_codec_enable_config_mode(codec, 1); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, + 0x04, 0); + usleep_range(calibration->shutdown_plug_removal, + calibration->shutdown_plug_removal); + tabla_codec_enable_config_mode(codec, 0); + } else + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_B1_CTL, + 0x04, 0); + } + + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0xC, + calibration->hph_current << 2); + + snd_soc_update_bits(codec, TABLA_A_MBHC_HPH, 0x13, 0x13); + + switch (calibration->bias) { + case TABLA_MICBIAS1: + micbias_mbhc_reg = TABLA_A_MICB_1_MBHC; + micbias_int_reg = TABLA_A_MICB_1_INT_RBIAS; + micbias_ctl_reg = TABLA_A_MICB_1_CTL; + break; + case TABLA_MICBIAS2: + micbias_mbhc_reg = TABLA_A_MICB_2_MBHC; + micbias_int_reg = TABLA_A_MICB_2_INT_RBIAS; + micbias_ctl_reg = TABLA_A_MICB_2_CTL; + break; + case TABLA_MICBIAS3: + micbias_mbhc_reg = TABLA_A_MICB_3_MBHC; + micbias_int_reg = TABLA_A_MICB_3_INT_RBIAS; + micbias_ctl_reg = TABLA_A_MICB_3_CTL; + break; + case TABLA_MICBIAS4: + micbias_mbhc_reg = TABLA_A_MICB_4_MBHC; + micbias_int_reg = TABLA_A_MICB_4_INT_RBIAS; + micbias_ctl_reg = TABLA_A_MICB_4_CTL; + break; + default: + pr_err("Error, invalid mic bias line\n"); + return -EINVAL; + } + snd_soc_update_bits(codec, micbias_int_reg, 0x80, 0); + snd_soc_update_bits(codec, micbias_ctl_reg, 0x1, 0); + + /* If central bandgap disabled */ + if (!(snd_soc_read(codec, TABLA_A_PIN_CTL_OE1) & 1)) { + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE1, 0x3, 0x3); + usleep_range(calibration->bg_fast_settle, + calibration->bg_fast_settle); + central_bias_enabled = 1; + } + + /* If LDO_H disabled */ + if (snd_soc_read(codec, TABLA_A_PIN_CTL_OE0) & 0x80) { + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE0, 0x10, 0); + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE0, 0x80, 0x80); + usleep_range(calibration->tldoh, calibration->tldoh); + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE0, 0x80, 0); + + if (central_bias_enabled) + snd_soc_update_bits(codec, TABLA_A_PIN_CTL_OE1, 0x1, 0); + } + snd_soc_update_bits(codec, micbias_mbhc_reg, 0x60, + calibration->mic_current << 5); + snd_soc_update_bits(codec, micbias_mbhc_reg, 0x80, 0x80); + usleep_range(calibration->mic_pid, calibration->mic_pid); + snd_soc_update_bits(codec, micbias_mbhc_reg, 0x10, 0x10); + + snd_soc_update_bits(codec, TABLA_A_MICB_4_MBHC, 0x3, calibration->bias); + + tabla_enable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + snd_soc_update_bits(codec, TABLA_A_CDC_MBHC_INT_CTL, 0x1, 0x1); + return 0; +} + +int tabla_hs_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, + struct tabla_mbhc_calibration *calibration) +{ + struct tabla_priv *tabla; + if (!codec || !calibration) { + pr_err("Error: no codec or calibration\n"); + return -EINVAL; + } + tabla = snd_soc_codec_get_drvdata(codec); + tabla->jack = jack; + tabla->calibration = calibration; + + return tabla_codec_enable_hs_detect(codec, 1); +} +EXPORT_SYMBOL_GPL(tabla_hs_detect); + +static irqreturn_t tabla_dummy_handler(int irq, void *data) +{ + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + snd_soc_write(codec, TABLA_A_CDC_MBHC_EN_CTL, 0x1); + return IRQ_HANDLED; +} + +static irqreturn_t tabla_hs_insert_irq(int irq, void *data) +{ + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + + if (priv->jack) { + pr_debug("reporting insertion %d\n", SND_JACK_HEADSET); + snd_soc_jack_report(priv->jack, SND_JACK_HEADSET, + SND_JACK_HEADSET); + } + usleep_range(priv->calibration->setup_plug_removal_delay, + priv->calibration->setup_plug_removal_delay); + + tabla_codec_setup_hs_polling(codec); + + return IRQ_HANDLED; +} + +static void tabla_codec_shutdown_hs_polling(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + if (!tabla->ref_cnt) { + snd_soc_update_bits(codec, TABLA_A_TX_COM_BIAS, 0xE0, 0x00); + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_AUDIO_MODE); + tabla_codec_enable_clock_block(codec, 0); + } + + tabla->mbhc_polling_active = false; +} + +static irqreturn_t tabla_hs_remove_irq(int irq, void *data) +{ + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + + tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL); + tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + + usleep_range(priv->calibration->shutdown_plug_removal, + priv->calibration->shutdown_plug_removal); + + if (priv->jack) { + pr_debug("reporting removal\n"); + snd_soc_jack_report(priv->jack, 0, SND_JACK_HEADSET); + } + tabla_codec_shutdown_hs_polling(codec); + + tabla_codec_enable_hs_detect(codec, 1); + + return IRQ_HANDLED; +} + +static unsigned long slimbus_value; + +static irqreturn_t tabla_slimbus_irq(int irq, void *data) +{ + struct tabla_priv *priv = data; + struct snd_soc_codec *codec = priv->codec; + int i, j; + u8 val; + + for (i = 0; i < TABLA_SLIM_NUM_PORT_REG; i++) { + slimbus_value = tabla_interface_reg_read(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_STATUS0 + i); + for_each_set_bit(j, &slimbus_value, BITS_PER_BYTE) { + val = tabla_interface_reg_read(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_SOURCE0 + i*8 + j); + if (val & 0x1) + pr_err_ratelimited("overflow error on port %x," + " value %x\n", i*8 + j, val); + if (val & 0x2) + pr_err_ratelimited("underflow error on port %x," + " value %x\n", i*8 + j, val); + } + tabla_interface_reg_write(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_CLR0 + i, 0xFF); + } + + return IRQ_HANDLED; +} + +static int tabla_codec_probe(struct snd_soc_codec *codec) +{ + struct tabla *control; + struct tabla_priv *tabla; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret = 0; + int i; + int tx_channel; + + codec->control_data = dev_get_drvdata(codec->dev->parent); + control = codec->control_data; + + tabla = kzalloc(sizeof(struct tabla_priv), GFP_KERNEL); + if (!tabla) { + dev_err(codec->dev, "Failed to allocate private data\n"); + return -ENOMEM; + } + + snd_soc_codec_set_drvdata(codec, tabla); + + tabla->ref_cnt = 0; + tabla->bandgap_type = TABLA_BANDGAP_OFF; + tabla->clock_active = false; + tabla->config_mode_active = false; + tabla->mbhc_polling_active = false; + tabla->codec = codec; + + /* TODO only enable bandgap when necessary in order to save power */ + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_AUDIO_MODE); + tabla_codec_enable_clock_block(codec, 0); + + /* Initialize gain registers to use register gain */ + snd_soc_update_bits(codec, TABLA_A_RX_HPH_L_GAIN, 0x10, 0x10); + snd_soc_update_bits(codec, TABLA_A_RX_HPH_R_GAIN, 0x10, 0x10); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_1_GAIN, 0x10, 0x10); + snd_soc_update_bits(codec, TABLA_A_RX_LINE_3_GAIN, 0x10, 0x10); + + /* Initialize mic biases to differential mode */ + snd_soc_update_bits(codec, TABLA_A_MICB_1_INT_RBIAS, 0x24, 0x24); + snd_soc_update_bits(codec, TABLA_A_MICB_2_INT_RBIAS, 0x24, 0x24); + snd_soc_update_bits(codec, TABLA_A_MICB_3_INT_RBIAS, 0x24, 0x24); + snd_soc_update_bits(codec, TABLA_A_MICB_4_INT_RBIAS, 0x24, 0x24); + + /* Set headset CFILT to fast mode */ + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_1_CTL, 0x00, 0x00); + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_2_CTL, 0x00, 0x00); + snd_soc_update_bits(codec, TABLA_A_MICB_CFILT_3_CTL, 0x00, 0x00); + + snd_soc_update_bits(codec, TABLA_A_CDC_CONN_CLSG_CTL, 0x30, 0x10); + + /* Use 16 bit sample size for now */ + for (tx_channel = 0; tx_channel < 6; tx_channel++) { + snd_soc_update_bits(codec, + TABLA_A_CDC_CONN_TX_SB_B1_CTL + tx_channel, 0x30, 0x20); + snd_soc_update_bits(codec, + TABLA_A_CDC_TX1_MUX_CTL + tx_channel, 0x8, 0x0); + + } + for (tx_channel = 6; tx_channel < 10; tx_channel++) { + snd_soc_update_bits(codec, + TABLA_A_CDC_CONN_TX_SB_B1_CTL + tx_channel, 0x60, 0x40); + snd_soc_update_bits(codec, + TABLA_A_CDC_TX1_MUX_CTL + tx_channel, 0x8, 0x0); + } + snd_soc_write(codec, TABLA_A_CDC_CONN_RX_SB_B1_CTL, 0xAA); + snd_soc_write(codec, TABLA_A_CDC_CONN_RX_SB_B2_CTL, 0xAA); + + snd_soc_add_controls(codec, tabla_snd_controls, + ARRAY_SIZE(tabla_snd_controls)); + snd_soc_dapm_new_controls(dapm, tabla_dapm_widgets, + ARRAY_SIZE(tabla_dapm_widgets)); + snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + snd_soc_dapm_sync(dapm); + + ret = tabla_request_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, + tabla_hs_insert_irq, "Headset insert detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_MBHC_INSERTION); + goto err_insert_irq; + } + tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION); + + ret = tabla_request_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, + tabla_hs_remove_irq, "Headset remove detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_MBHC_REMOVAL); + goto err_remove_irq; + } + tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL); + + ret = tabla_request_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, + tabla_dummy_handler, "DC Estimation detect", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_MBHC_POTENTIAL); + goto err_potential_irq; + } + tabla_disable_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL); + + ret = tabla_request_irq(codec->control_data, TABLA_IRQ_SLIMBUS, + tabla_slimbus_irq, "SLIMBUS Slave", tabla); + if (ret) { + pr_err("%s: Failed to request irq %d\n", __func__, + TABLA_IRQ_SLIMBUS); + goto err_slimbus_irq; + } + + for (i = 0; i < TABLA_SLIM_NUM_PORT_REG; i++) + tabla_interface_reg_write(codec->control_data, + TABLA_SLIM_PGD_PORT_INT_EN0 + i, 0xFF); + + return ret; + +err_slimbus_irq: + tabla_free_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, tabla); +err_potential_irq: + tabla_free_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, tabla); +err_remove_irq: + tabla_free_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, tabla); +err_insert_irq: + kfree(tabla); + return ret; +} +static int tabla_codec_remove(struct snd_soc_codec *codec) +{ + struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec); + tabla_free_irq(codec->control_data, TABLA_IRQ_SLIMBUS, tabla); + tabla_free_irq(codec->control_data, TABLA_IRQ_MBHC_POTENTIAL, tabla); + tabla_free_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, tabla); + tabla_free_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, tabla); + tabla_codec_disable_clock_block(codec); + tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_OFF); + kfree(tabla); + return 0; +} +static struct snd_soc_codec_driver soc_codec_dev_tabla = { + .probe = tabla_codec_probe, + .remove = tabla_codec_remove, + .read = tabla_read, + .write = tabla_write, + + .readable_register = tabla_readable, + .volatile_register = tabla_volatile, + + .reg_cache_size = TABLA_CACHE_SIZE, + .reg_cache_default = tabla_reg_defaults, + .reg_word_size = 1, +}; +static int __devinit tabla_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_tabla, + tabla_dai, ARRAY_SIZE(tabla_dai)); +} +static int __devexit tabla_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} +static struct platform_driver tabla_codec_driver = { + .probe = tabla_probe, + .remove = tabla_remove, + .driver = { + .name = "tabla_codec", + .owner = THIS_MODULE, + }, +}; + +static int __init tabla_codec_init(void) +{ + return platform_driver_register(&tabla_codec_driver); +} + +static void __exit tabla_codec_exit(void) +{ + platform_driver_unregister(&tabla_codec_driver); +} + +module_init(tabla_codec_init); +module_exit(tabla_codec_exit); + +MODULE_DESCRIPTION("Tabla codec driver"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/wcd9310.h b/sound/soc/codecs/wcd9310.h new file mode 100644 index 00000000000..9b4faefe338 --- /dev/null +++ b/sound/soc/codecs/wcd9310.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#define TABLA_NUM_REGISTERS 0x400 +#define TABLA_MAX_REGISTER (TABLA_NUM_REGISTERS-1) +#define TABLA_CACHE_SIZE TABLA_NUM_REGISTERS + +extern const u8 tabla_reg_readable[TABLA_CACHE_SIZE]; +extern const u8 tabla_reg_defaults[TABLA_CACHE_SIZE]; + +enum tabla_micbias_num { + TABLA_MICBIAS1, + TABLA_MICBIAS2, + TABLA_MICBIAS3, + TABLA_MICBIAS4, +}; + +enum tabla_pid_current { + TABLA_PID_MIC_2P5_UA, + TABLA_PID_MIC_5_UA, + TABLA_PID_MIC_10_UA, + TABLA_PID_MIC_20_UA, +}; + +struct tabla_mbhc_calibration { + enum tabla_micbias_num bias; + int tldoh; + int bg_fast_settle; + enum tabla_pid_current mic_current; + int mic_pid; + enum tabla_pid_current hph_current; + int setup_plug_removal_delay; + int shutdown_plug_removal; +}; + +extern int tabla_hs_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, struct tabla_mbhc_calibration *calibration); diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/imx/imx-pcm-fiq.c index 309c59e6fb6..25e9e62e2a1 100644 --- a/sound/soc/imx/imx-pcm-fiq.c +++ b/sound/soc/imx/imx-pcm-fiq.c @@ -8,8 +8,7 @@ * * 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. + * Free Software Foundation; only version 2 of the License. */ #include #include diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c index a7c9578be98..81153b1f657 100644 --- a/sound/soc/jz4740/jz4740-pcm.c +++ b/sound/soc/jz4740/jz4740-pcm.c @@ -3,8 +3,7 @@ * * 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. + * Free Software Foundation; only version 2 of the License. * * 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., @@ -370,4 +369,4 @@ module_exit(jz4740_soc_platform_exit); MODULE_AUTHOR("Lars-Peter Clausen "); MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/kirkwood/kirkwood-dma.c b/sound/soc/kirkwood/kirkwood-dma.c index cd33de1c5b7..b21a5e40736 100644 --- a/sound/soc/kirkwood/kirkwood-dma.c +++ b/sound/soc/kirkwood/kirkwood-dma.c @@ -6,8 +6,7 @@ * * 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. + * Free Software Foundation; only version 2 of the License. */ #include @@ -402,5 +401,5 @@ module_exit(kirkwood_pcm_exit); MODULE_AUTHOR("Arnaud Patard "); MODULE_DESCRIPTION("Marvell Kirkwood Audio DMA module"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:kirkwood-pcm-audio"); diff --git a/sound/soc/msm/Kconfig b/sound/soc/msm/Kconfig new file mode 100644 index 00000000000..485d3db5599 --- /dev/null +++ b/sound/soc/msm/Kconfig @@ -0,0 +1,134 @@ +menu "MSM SoC Audio support" + +#7201 7625 variants +config SND_MSM_DAI_SOC + tristate + +config SND_MSM_SOC_MSM7K + tristate + +config SND_MSM_SOC + tristate "SoC Audio for the MSM series chips" + depends on ARCH_MSM_ARM11 && SND_SOC && MSM_ADSP + select SND_MSM_DAI_SOC + select SND_MSM_SOC_MSM7K + default n + help + To add support for ALSA PCM driver for MSM board. + +#7630 Variants +config SND_MSM7KV2_DAI_SOC + tristate + +config SND_MSM_SOC_MSM7KV2 + tristate + +config SND_MSM7KV2_SOC + tristate "SoC Audio for the MSM7KV2 chip" + depends on ARCH_MSM7X30 && SND_SOC && MSM7KV2_AUDIO + select SND_MSM_SOC_MSM7KV2 + select SND_MSM7KV2_DAI_SOC + default n + help + To add support for ALSA PCM driver for QSD8k board. + +config SND_MSM_MVS7x30_SOC + tristate + +config SND_MSM_MVS_DAI_SOC + tristate + +config SND_MVS_SOC + tristate "SoC Mvs support for MSM7X30" + depends on SND_MSM7KV2_SOC + select SND_MSM_MVS7x30_SOC + select SND_MSM_MVS_DAI_SOC + default n + help + To support Mvs packet capture/playback + +#8660 Variants +config SND_SOC_MSM8X60_PCM + tristate + +config SND_SOC_MSM8X60_DAI + tristate + +config SND_SOC_MSM8X60 + tristate "SoC Audio over DSP support for MSM8660" + depends on ARCH_MSM8X60 && SND_SOC && MSM8X60_AUDIO + select SND_SOC_MSM8X60_PCM + select SND_SOC_MSM8X60_DAI + select SND_SOC_MSM_QDSP6_INTF + default y + help + To add support for SoC audio on MSM8X60. This driver + Adds support for audio over DSP. The driver adds Kcontrols + to do device switch/routing and volume control support for all + audio sessions. The kcontols also does sesion management for + voice calls + +config SND_SOC_MSM_HOSTLESS_PCM + tristate + +config SND_SOC_MSM8660_PCM + tristate + +config SND_SOC_MSM8660_LPAIF + tristate + +config SND_SOC_MSM8660 + tristate "SoC Machine driver for MSM8660" + depends on !SND_SOC_MSM8X60 && ARCH_MSM8X60 + select SND_SOC_MSM8660_PCM + select SND_SOC_MSM8660_LPAIF + select SND_SOC_TIMPANI + select MARIMBA_CORE + select SND_SOC_MSM_QDSP6_INTF + default n + help + To add support for SoC audio on MSM8660 for direct playback + to LPA buffer over DMA.The interface bypasses DSP and hence + does not support any post/pre processing features.The driver + would support full duplex playback/record sessions. + +config SND_VOIP_PCM + tristate + +config MSM_8x60_VOIP + tristate "SoC Machine driver for voip" + depends on SND_SOC_MSM8X60 + select SND_MSM_MVS_DAI_SOC + select SND_VOIP_PCM + default n + help + To support ALSA VOIP driver for MSM8x60 target. + This driver communicates with QDSP6, for getting + uplink and downlink voice packets. + +config SND_SOC_MSM_QDSP6_INTF + bool "SoC Q6 audio driver for MSM8960" + depends on MSM_QDSP6_APR + default n + help + To add support for SoC audio on MSM8960. + +config SND_SOC_QDSP6 + tristate "SoC ALSA audio driver for QDSP6" + select SND_SOC_MSM_QDSP6_INTF + default n + help + To add support for MSM QDSP6 Soc Audio. + +config SND_SOC_MSM8960 + tristate "SoC Machine driver for MSM8960 boards" + depends on ARCH_MSM8960 + select SND_SOC_QDSP6 + select SND_SOC_MSM_STUB + select SND_SOC_WCD9310 + select SND_SOC_MSM_HOSTLESS_PCM + default n + help + To add support for SoC audio on MSM8960 boards + +endmenu diff --git a/sound/soc/msm/Makefile b/sound/soc/msm/Makefile new file mode 100644 index 00000000000..92b008d5e04 --- /dev/null +++ b/sound/soc/msm/Makefile @@ -0,0 +1,71 @@ +# MSM CPU/CODEC DAI Support +snd-soc-msm-dai-objs := msm-dai.o +obj-$(CONFIG_SND_MSM_DAI_SOC) += snd-soc-msm-dai.o + +snd-soc-msm7kv2-dai-objs := msm7kv2-dai.o +obj-$(CONFIG_SND_MSM7KV2_DAI_SOC) += snd-soc-msm7kv2-dai.o + +# MSM Platform Support +snd-soc-msm-objs := msm-pcm.o msm7k-pcm.o +obj-$(CONFIG_SND_MSM_SOC) += snd-soc-msm.o + +snd-soc-msmv2-objs := msm7kv2-dsp.o msm7kv2-pcm.o +obj-$(CONFIG_SND_MSM7KV2_SOC) += snd-soc-msmv2.o + +# MSM Machine Support +snd-soc-msm7k-objs := msm7201.o +obj-$(CONFIG_SND_MSM_SOC_MSM7K) += snd-soc-msm7k.o + +snd-soc-msm7kv2-objs := msm7x30.o +obj-$(CONFIG_SND_MSM_SOC_MSM7KV2) += snd-soc-msm7kv2.o + +# 8660 ALSA Support +snd-soc-msm8x60-dai-objs := msm8x60-dai.o +obj-$(CONFIG_SND_SOC_MSM8X60_DAI) += snd-soc-msm8x60-dai.o + +snd-soc-msm8x60-pcm-objs := msm8x60-pcm.o +obj-$(CONFIG_SND_SOC_MSM8X60_PCM) += snd-soc-msm8x60-pcm.o + +snd-soc-msm8x60-objs := msm8x60.o +obj-$(CONFIG_SND_SOC_MSM8X60) += snd-soc-msm8x60.o + + +#MVS Support +snd-soc-msm-mvs-dai-objs := mvs-dai.o +obj-$(CONFIG_SND_MSM_MVS_DAI_SOC) += snd-soc-msm-mvs-dai.o + +snd-soc-msm-mvs-objs := msm-mvs.o +obj-$(CONFIG_SND_MVS_SOC) += snd-soc-msm-mvs.o + +# 8660 ALSA Support +snd-soc-msm8660-lpa-objs := msm8660-i2s.o msm8660-dma.o +obj-$(CONFIG_SND_SOC_MSM8660_LPAIF) += snd-soc-msm8660-lpa.o + +snd-soc-msm8660-pcm-objs := msm8660-pcm.o +obj-$(CONFIG_SND_SOC_MSM8660_PCM) += snd-soc-msm8660-pcm.o + +snd-soc-msm8660-objs := msm8660.o +obj-$(CONFIG_SND_SOC_MSM8660) += snd-soc-msm8660.o + +#8660 VOIP Driver Support + +snd-soc-msm-voip-objs := msm-voip.o +obj-$(CONFIG_SND_VOIP_PCM) += snd-soc-msm-voip.o + +snd-soc-msm8660-dma-objs := msm8660-dma.o +obj-$(CONFIG_SND_SOC_MSM8X60) += snd-soc-msm8660-dma.o + +# for MSM 8960 sound card driver + +obj-$(CONFIG_SND_SOC_MSM_QDSP6_INTF) += qdsp6/ + +snd-soc-qdsp6-objs := msm-dai-q6.o msm-pcm-q6.o msm-pcm-routing.o msm-dai-fe.o msm-pcm-voice.o msm-pcm-voip.o +snd-soc-qdsp6-objs += msm-pcm-lpa.o +obj-$(CONFIG_SND_SOC_QDSP6) += snd-soc-qdsp6.o + +snd-soc-msm8960-objs := msm8960.o +obj-$(CONFIG_SND_SOC_MSM8960) += snd-soc-msm8960.o + +# Generic MSM drivers +snd-soc-hostless-pcm-objs := msm-pcm-hostless.o +obj-$(CONFIG_SND_SOC_MSM_HOSTLESS_PCM) += snd-soc-hostless-pcm.o diff --git a/sound/soc/msm/msm-dai-fe.c b/sound/soc/msm/msm-dai-fe.c new file mode 100644 index 00000000000..c937a5af1fe --- /dev/null +++ b/sound/soc/msm/msm-dai-fe.c @@ -0,0 +1,210 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include + +static struct snd_soc_dai_ops msm_fe_dai_ops = {}; + +static struct snd_soc_dai_driver msm_fe_dais[] = { + { + .playback = { + .stream_name = "Multimedia1 Playback", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "Multimedia1 Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "MultiMedia1", + }, + { + .playback = { + .stream_name = "Multimedia2 Playback", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "Multimedia2 Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "MultiMedia2", + }, + { + .playback = { + .stream_name = "Voice Playback", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "Voice Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "CS-VOICE", + }, + { + .playback = { + .stream_name = "VoIP Playback", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "VoIP Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "VoIP", + }, + { + .playback = { + .stream_name = "MultiMedia3 Playback", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "MultiMedia3", + }, + /* FE DAIs created for hostless operation purpose */ + { + .playback = { + .stream_name = "SLIMBUS0 Hostless Playback", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "SLIMBUS0 Hostless Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "SLIMBUS0_HOSTLESS", + }, + { + .playback = { + .stream_name = "INT_FM Hostless Playback", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .capture = { + .stream_name = "INT_FM Hostless Capture", + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_fe_dai_ops, + .name = "INT_FM_HOSTLESS", + }, +}; + +static __devinit int msm_fe_dai_dev_probe(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "%s: dev name %s\n", __func__, + dev_name(&pdev->dev)); + return snd_soc_register_dais(&pdev->dev, msm_fe_dais, + ARRAY_SIZE(msm_fe_dais)); +} + +static __devexit int msm_fe_dai_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver msm_fe_dai_driver = { + .probe = msm_fe_dai_dev_probe, + .remove = msm_fe_dai_dev_remove, + .driver = { + .name = "msm-dai-fe", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_fe_dai_init(void) +{ + return platform_driver_register(&msm_fe_dai_driver); +} +module_init(msm_fe_dai_init); + +static void __exit msm_fe_dai_exit(void) +{ + platform_driver_unregister(&msm_fe_dai_driver); +} +module_exit(msm_fe_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Frontend DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-dai-q6.c b/sound/soc/msm/msm-dai-q6.c new file mode 100644 index 00000000000..9cdd1d6a2e4 --- /dev/null +++ b/sound/soc/msm/msm-dai-q6.c @@ -0,0 +1,555 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + STATUS_PORT_STARTED, /* track if AFE port has started */ + STATUS_MAX +}; + +struct msm_dai_q6_dai_data { + DECLARE_BITMAP(status_mask, STATUS_MAX); + u32 rate; + u32 channels; + union afe_port_config port_config; +}; + +static int msm_dai_q6_cdc_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->channels = params_channels(params); + switch (dai_data->channels) { + case 2: + dai_data->port_config.mi2s.channel = MSM_AFE_STEREO; + break; + case 1: + dai_data->port_config.mi2s.channel = MSM_AFE_MONO; + break; + default: + return -EINVAL; + break; + } + dai_data->rate = params_rate(params); + + dev_dbg(dai->dev, " channel %d sample rate %d entered\n", + dai_data->channels, dai_data->rate); + + /* Q6 only supports 16 as now */ + dai_data->port_config.mi2s.bitwidth = 16; + dai_data->port_config.mi2s.line = 1; + dai_data->port_config.mi2s.ws = 1; /* I2S master mode for now */ + + return 0; +} + +static int msm_dai_q6_hdmi_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dev_dbg(dai->dev, "%s start HDMI port\n", __func__); + + dai_data->channels = params_channels(params); + switch (dai_data->channels) { + case 2: + dai_data->port_config.hdmi.channel_mode = 0; /* Put in macro */ + break; + default: + return -EINVAL; + break; + } + + /* Q6 only supports 16 as now */ + dai_data->port_config.hdmi.bitwidth = 16; + dai_data->port_config.hdmi.data_type = 0; + dai_data->rate = params_rate(params); + + return 0; +} + +static int msm_dai_q6_slim_bus_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + u8 pgd_la, inf_la; + + memset(dai_data->port_config.slimbus.slave_port_mapping, 0, + sizeof(dai_data->port_config.slimbus.slave_port_mapping)); + + dai_data->channels = params_channels(params); + switch (dai_data->channels) { + case 2: + if (dai->id == SLIMBUS_0_RX) { + dai_data->port_config.slimbus.slave_port_mapping[0] = 1; + dai_data->port_config.slimbus.slave_port_mapping[1] = 2; + } else { + dai_data->port_config.slimbus.slave_port_mapping[0] = 7; + dai_data->port_config.slimbus.slave_port_mapping[1] = 8; + } + break; + case 1: + if (dai->id == SLIMBUS_0_RX) + dai_data->port_config.slimbus.slave_port_mapping[0] = 1; + else + dai_data->port_config.slimbus.slave_port_mapping[0] = 7; + break; + default: + return -EINVAL; + break; + } + dai_data->rate = params_rate(params); + tabla_get_logical_addresses(&pgd_la, &inf_la); + + dai_data->port_config.slimbus.slimbus_dev_id = AFE_SLIMBUS_DEVICE_1; + dai_data->port_config.slimbus.slave_dev_pgd_la = pgd_la; + dai_data->port_config.slimbus.slave_dev_intfdev_la = inf_la; + /* Q6 only supports 16 as now */ + dai_data->port_config.slimbus.bit_width = 16; + dai_data->port_config.slimbus.data_format = 0; + dai_data->port_config.slimbus.num_channels = dai_data->channels; + dai_data->port_config.slimbus.reserved = 0; + + dev_dbg(dai->dev, "slimbus_dev_id %hu slave_dev_pgd_la 0x%hx\n" + "slave_dev_intfdev_la 0x%hx bit_width %hu data_format %hu\n" + "num_channel %hu slave_port_mapping[0] %hu\n" + "slave_port_mapping[1] %hu slave_port_mapping[2] %hu\n" + "sample_rate %d\n", + dai_data->port_config.slimbus.slimbus_dev_id, + dai_data->port_config.slimbus.slave_dev_pgd_la, + dai_data->port_config.slimbus.slave_dev_intfdev_la, + dai_data->port_config.slimbus.bit_width, + dai_data->port_config.slimbus.data_format, + dai_data->port_config.slimbus.num_channels, + dai_data->port_config.slimbus.slave_port_mapping[0], + dai_data->port_config.slimbus.slave_port_mapping[1], + dai_data->port_config.slimbus.slave_port_mapping[2], + dai_data->rate); + + return 0; +} + +static int msm_dai_q6_bt_fm_hw_params(struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai, int stream) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + + dai_data->channels = params_channels(params); + dai_data->rate = params_rate(params); + + dev_dbg(dai->dev, "channels %d sample rate %d entered\n", + dai_data->channels, dai_data->rate); + + memset(&dai_data->port_config, 0, sizeof(dai_data->port_config)); + + return 0; +} + +/* Current implementation assumes hw_param is called once + * This may not be the case but what to do when ADM and AFE + * port are already opened and parameter changes + */ +static int msm_dai_q6_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int rc = 0; + + switch (dai->id) { + case PRIMARY_I2S_TX: + case PRIMARY_I2S_RX: + rc = msm_dai_q6_cdc_hw_params(params, dai, substream->stream); + break; + case HDMI_RX: + rc = msm_dai_q6_hdmi_hw_params(params, dai); + break; + + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + rc = msm_dai_q6_slim_bus_hw_params(params, dai, + substream->stream); + break; + case INT_BT_SCO_RX: + case INT_BT_SCO_TX: + case INT_FM_RX: + case INT_FM_TX: + rc = msm_dai_q6_bt_fm_hw_params(params, dai, substream->stream); + break; + default: + dev_err(dai->dev, "invalid AFE port ID\n"); + rc = -EINVAL; + break; + } + + return rc; +} + +static void msm_dai_q6_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc; + + rc = adm_close(dai->id); + + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close ADM COPP\n"); + + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + rc = afe_close(dai->id); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } +}; + +static int msm_dai_q6_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + /* if AFE port is already started, this means + * application wishes to restore hardware to + * fresh state. This logic anticipates prepare is not + * called right after TRIGGER_START before Q6 AFE + * has enough time to respond to port start command. + */ + rc = afe_close(dai->id); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + clear_bit(STATUS_PORT_STARTED, dai_data->status_mask); + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rc = adm_open_mixer(dai->id, 1, dai_data->rate, + dai_data->channels, DEFAULT_COPP_TOPOLOGY); + else + rc = adm_open_mixer(dai->id, 2, dai_data->rate, + dai_data->channels, DEFAULT_COPP_TOPOLOGY); + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to open ADM\n"); + } + + return rc; + +} + +static int msm_dai_q6_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc = 0; + + /* Start/stop port without waiting for Q6 AFE response. Need to have + * native q6 AFE driver propagates AFE response in order to handle + * port start/stop command error properly if error does arise. + */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (!test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + afe_port_start_nowait(dai->id, &dai_data->port_config, + dai_data->rate); + set_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + afe_port_stop_nowait(dai->id); + clear_bit(STATUS_PORT_STARTED, + dai_data->status_mask); + } + break; + + default: + rc = -EINVAL; + } + + return rc; +} + +static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc = 0; + + dai_data = kzalloc(sizeof(struct msm_dai_q6_dai_data), + GFP_KERNEL); + + if (!dai_data) { + dev_err(dai->dev, "DAI-%d: fail to allocate dai data\n", + dai->id); + rc = -ENOMEM; + } else + dev_set_drvdata(dai->dev, dai_data); + + return rc; +} + +static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) +{ + struct msm_dai_q6_dai_data *dai_data; + int rc; + + dai_data = dev_get_drvdata(dai->dev); + + /* If AFE port is still up, close it */ + if (test_bit(STATUS_PORT_STARTED, dai_data->status_mask)) { + rc = afe_close(dai->id); /* can block */ + if (IS_ERR_VALUE(rc)) + dev_err(dai->dev, "fail to close AFE port\n"); + } + kfree(dai_data); + snd_soc_unregister_dai(dai->dev); + + return 0; +} + +static struct snd_soc_dai_ops msm_dai_q6_ops = { + /* + * DSP only handles 16-bit and support only I2S + * master mode for now. leave set_fmt function + * unimplemented for now. + */ + .prepare = msm_dai_q6_prepare, + .trigger = msm_dai_q6_trigger, + .hw_params = msm_dai_q6_hw_params, + .shutdown = msm_dai_q6_shutdown, +}; + +static struct snd_soc_dai_driver msm_dai_q6_i2s_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_i2s_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_hdmi_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_max = 48000, + .rate_min = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_slimbus_tx_dai = { + .capture = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_bt_sco_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_bt_sco_tx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 1, + .rate_max = 16000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_fm_rx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_max = 48000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +static struct snd_soc_dai_driver msm_dai_q6_fm_tx_dai = { + .playback = { + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_max = 48000, + .rate_min = 8000, + }, + .ops = &msm_dai_q6_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, +}; + +/* To do: change to register DAIs as batch */ +static __devinit int msm_dai_q6_dev_probe(struct platform_device *pdev) +{ + int rc = 0; + + dev_dbg(&pdev->dev, "dev name %s\n", dev_name(&pdev->dev)); + + switch (pdev->id) { + case PRIMARY_I2S_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_i2s_rx_dai); + break; + case PRIMARY_I2S_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_i2s_tx_dai); + break; + case HDMI_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_hdmi_rx_dai); + break; + case SLIMBUS_0_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_rx_dai); + break; + case SLIMBUS_0_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_slimbus_tx_dai); + case INT_BT_SCO_RX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_bt_sco_rx_dai); + break; + case INT_BT_SCO_TX: + rc = snd_soc_register_dai(&pdev->dev, + &msm_dai_q6_bt_sco_tx_dai); + break; + case INT_FM_RX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_fm_rx_dai); + break; + case INT_FM_TX: + rc = snd_soc_register_dai(&pdev->dev, &msm_dai_q6_fm_tx_dai); + break; + default: + rc = -ENODEV; + break; + } + return rc; +} + +static __devexit int msm_dai_q6_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver msm_dai_q6_driver = { + .probe = msm_dai_q6_dev_probe, + .remove = msm_dai_q6_dev_remove, + .driver = { + .name = "msm-dai-q6", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_dai_q6_init(void) +{ + return platform_driver_register(&msm_dai_q6_driver); +} +module_init(msm_dai_q6_init); + +static void __exit msm_dai_q6_exit(void) +{ + platform_driver_unregister(&msm_dai_q6_driver); +} +module_exit(msm_dai_q6_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM DSP DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-dai.c b/sound/soc/msm/msm-dai.c new file mode 100644 index 00000000000..6ebee5122df --- /dev/null +++ b/sound/soc/msm/msm-dai.c @@ -0,0 +1,154 @@ +/* sound/soc/msm/msm-dai.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * + * Derived from msm-pcm.c and msm7201.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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_ARCH_MSM_ARM11 +#include "msm-pcm.h" +#else +#include "qsd-pcm.h" +#endif + +static struct snd_soc_dai_driver msm_pcm_codec_dais[] = { +{ + .name = "msm-codec-dai", + .playback = { + .stream_name = "Playback", + .channels_max = USE_CHANNELS_MAX, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .formats = USE_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rates = USE_RATE, + .formats = USE_FORMATS, + }, +}, +}; + +static struct snd_soc_dai_driver msm_pcm_cpu_dais[] = { +{ + .name = "msm-cpu-dai", + .playback = { + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .formats = USE_FORMATS, + }, + .capture = { + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rates = USE_RATE, + .formats = USE_FORMATS, + }, +}, +}; + +static struct snd_soc_codec_driver soc_codec_dev_msm = { + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + +static __devinit int asoc_msm_codec_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_msm, + msm_pcm_codec_dais, ARRAY_SIZE(msm_pcm_codec_dais)); +} + +static int __devexit asoc_msm_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static __devinit int asoc_pcm_cpu_probe(struct platform_device *pdev) +{ + return snd_soc_register_dai(&pdev->dev, msm_pcm_cpu_dais); +} + +static int __devexit asoc_pcm_cpu_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver asoc_codec_dai_driver = { + .probe = asoc_msm_codec_probe, + .remove = __devexit_p(asoc_msm_codec_remove), + .driver = { + .name = "msm-codec-dai", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver asoc_cpu_dai_driver = { + .probe = asoc_pcm_cpu_probe, + .remove = __devexit_p(asoc_pcm_cpu_remove), + .driver = { + .name = "msm-cpu-dai", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_codec_dai_init(void) +{ + return platform_driver_register(&asoc_codec_dai_driver); +} + +static void __exit msm_codec_dai_exit(void) +{ + platform_driver_unregister(&asoc_codec_dai_driver); +} + +static int __init msm_cpu_dai_init(void) +{ + return platform_driver_register(&asoc_cpu_dai_driver); +} + +static void __exit msm_cpu_dai_exit(void) +{ + platform_driver_unregister(&asoc_cpu_dai_driver); +} + +module_init(msm_codec_dai_init); +module_exit(msm_codec_dai_exit); +module_init(msm_cpu_dai_init); +module_exit(msm_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Codec/Cpu Dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-mvs.c b/sound/soc/msm/msm-mvs.c new file mode 100644 index 00000000000..2e7114c94f0 --- /dev/null +++ b/sound/soc/msm/msm-mvs.c @@ -0,0 +1,936 @@ +/* Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_audio_mvs.h" + + +static struct audio_mvs_info_type audio_mvs_info; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MVS_MAX_VOC_PKT_SIZE * MVS_MAX_Q_LEN, + .period_bytes_min = MVS_MAX_VOC_PKT_SIZE, + .period_bytes_max = MVS_MAX_VOC_PKT_SIZE, + .periods_min = MVS_MAX_Q_LEN, + .periods_max = MVS_MAX_Q_LEN, + .fifo_size = 0, +}; + +static void snd_pcm_mvs_timer(unsigned long data) +{ + struct audio_mvs_info_type *audio = &audio_mvs_info; + MM_DBG("%s\n", __func__); + if (audio->playback_start) { + if (audio->ack_dl_count) { + audio->pcm_playback_irq_pos += audio->pcm_count; + audio->ack_dl_count--; + snd_pcm_period_elapsed(audio->playback_substream); + } + } + + if (audio->capture_start) { + if (audio->ack_ul_count) { + audio->pcm_capture_irq_pos += audio->pcm_capture_count; + audio->ack_ul_count--; + snd_pcm_period_elapsed(audio->capture_substream); + } + } + audio->timer.expires += audio->expiry_delta; + add_timer(&audio->timer); +} + +static int audio_mvs_setup_mvs(struct audio_mvs_info_type *audio) +{ + int rc = 0; + struct audio_mvs_enable_msg enable_msg; + MM_DBG("%s\n", __func__); + + /* Enable MVS. */ + + memset(&enable_msg, 0, sizeof(enable_msg)); + audio->rpc_status = RPC_STATUS_FAILURE; + enable_msg.enable_args.client_id = cpu_to_be32(MVS_CLIENT_ID_VOIP); + enable_msg.enable_args.mode = cpu_to_be32(MVS_MODE_LINEAR_PCM); + enable_msg.enable_args.ul_cb_func_id = (int) NULL; + enable_msg.enable_args.dl_cb_func_id = (int) NULL; + enable_msg.enable_args.context = cpu_to_be32(MVS_PKT_CONTEXT_ISR); + + msm_rpc_setup_req(&enable_msg.rpc_hdr, MVS_PROG, + MVS_VERS, MVS_ENABLE_PROC); + + rc = msm_rpc_write(audio->rpc_endpt, + &enable_msg, sizeof(enable_msg)); + + if (rc >= 0) { + MM_DBG("RPC write for enable done\n"); + + rc = wait_event_timeout(audio->wait, + (audio->rpc_status != + RPC_STATUS_FAILURE), 1 * HZ); + + if (rc > 0) { + MM_DBG("Wait event for enable succeeded\n"); + + mutex_lock(&audio->lock); + audio->mvs_mode = MVS_MODE_LINEAR_PCM; + audio->frame_mode = MVS_FRAME_MODE_PCM_DL; + audio->pcm_frame = 0; + mutex_unlock(&audio->lock); + rc = 0; + + } else + MM_ERR("Wait event for enable failed %d\n", rc); + } else + MM_ERR("RPC write for enable failed %d\n", rc); + return rc; +} + +static void audio_mvs_rpc_reply(struct msm_rpc_endpoint *endpoint, + uint32_t xid) +{ + int rc = 0; + struct rpc_reply_hdr reply_hdr; + MM_DBG("%s\n", __func__); + + memset(&reply_hdr, 0, sizeof(reply_hdr)); + reply_hdr.xid = cpu_to_be32(xid); + reply_hdr.type = cpu_to_be32(RPC_TYPE_REPLY); + reply_hdr.reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + reply_hdr.data.acc_hdr.accept_stat = + cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); + reply_hdr.data.acc_hdr.verf_flavor = 0; + reply_hdr.data.acc_hdr.verf_length = 0; + + rc = msm_rpc_write(endpoint, &reply_hdr, sizeof(reply_hdr)); + + if (rc < 0) + MM_ERR("RPC write for response failed %d\n", rc); +} + +static void audio_mvs_process_rpc_request(uint32_t procedure, uint32_t xid, + void *data, uint32_t length, + struct audio_mvs_info_type *audio) +{ + + int rc = 0; + uint32_t index; + MM_DBG("%s\n", __func__); + switch (procedure) { + case MVS_EVENT_CB_TYPE_PROC:{ + struct audio_mvs_cb_func_args *args = data; + uint32_t event_type = be32_to_cpu(args->event); + uint32_t cmd_status = + be32_to_cpu(args-> + event_data.mvs_ev_command_type.cmd_status); + uint32_t mode_status = + be32_to_cpu(args-> + event_data.mvs_ev_mode_type.mode_status); + audio_mvs_rpc_reply(audio->rpc_endpt, xid); + if (be32_to_cpu(args->valid_ptr)) { + if (event_type == AUDIO_MVS_COMMAND) { + if (cmd_status == AUDIO_MVS_CMD_SUCCESS) + audio->rpc_status = RPC_STATUS_SUCCESS; + wake_up(&audio->wait); + } else if (event_type == AUDIO_MVS_MODE) { + if (mode_status != AUDIO_MVS_MODE_NOT_AVAIL) { + audio->rpc_status = + RPC_STATUS_SUCCESS; + } + audio->prepare_ack++; + wake_up(&audio->wait); + wake_up(&audio->prepare_wait); + } else { + /*nothing to do */ + } + } else + MM_ERR("ALSA: CB event pointer not valid\n"); + break; + } + case MVS_PACKET_UL_FN_TYPE_PROC:{ + uint32_t *cb_data = data; + uint32_t pkt_len ; + struct audio_mvs_ul_reply ul_reply; + MM_DBG("MVS_PACKET_UL_FN_TYPE_PROC\n"); + + memset(&ul_reply, 0, sizeof(ul_reply)); + cb_data++; + pkt_len = be32_to_cpu(*cb_data); + cb_data++; + if (audio->capture_enable) { + audio_mvs_info.ack_ul_count++; + mutex_lock(&audio->out_lock); + index = audio->out_write % MVS_MAX_Q_LEN; + memcpy(audio->out[index].voc_pkt, cb_data, + pkt_len); + audio->out[index].len = pkt_len; + audio->out_write++; + mutex_unlock(&audio->out_lock); + } + MM_DBG(" audio->out_read = %d audio->out write = %d\n", + audio->out_read, audio->out_write); + ul_reply.reply_hdr.xid = cpu_to_be32(xid); + ul_reply.reply_hdr.type = cpu_to_be32(RPC_TYPE_REPLY); + ul_reply.reply_hdr.reply_stat = + cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + ul_reply.reply_hdr.data.acc_hdr.accept_stat = + cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); + ul_reply.reply_hdr.data.acc_hdr.verf_flavor = 0; + ul_reply.reply_hdr.data.acc_hdr.verf_length = 0; + ul_reply.valid_pkt_status_ptr = cpu_to_be32(0x00000001); + ul_reply.pkt_status = cpu_to_be32(0x00000000); + rc = msm_rpc_write(audio->rpc_endpt, &ul_reply, + sizeof(ul_reply)); + wake_up(&audio->out_wait); + if (rc < 0) + MM_ERR("RPC write for UL response failed %d\n", + rc); + break; + } + case MVS_PACKET_DL_FN_TYPE_PROC:{ + struct audio_mvs_dl_reply dl_reply; + MM_DBG("MVS_PACKET_DL_FN_TYPE_PROC\n"); + memset(&dl_reply, 0, sizeof(dl_reply)); + dl_reply.reply_hdr.xid = cpu_to_be32(xid); + dl_reply.reply_hdr.type = cpu_to_be32(RPC_TYPE_REPLY); + dl_reply.reply_hdr.reply_stat = + cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED); + dl_reply.reply_hdr.data.acc_hdr.accept_stat = + cpu_to_be32(RPC_ACCEPTSTAT_SUCCESS); + dl_reply.reply_hdr.data.acc_hdr.verf_flavor = 0; + dl_reply.reply_hdr.data.acc_hdr.verf_length = 0; + mutex_lock(&audio->in_lock); + if (audio->in_read < audio->in_write + && audio->dl_play) { + index = audio->in_read % MVS_MAX_Q_LEN; + memcpy(&dl_reply.voc_pkt, + audio->in[index].voc_pkt, + audio->in[index].len); + audio->in_read++; + audio_mvs_info.ack_dl_count++; + dl_reply.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_NORMAL); + wake_up(&audio->in_wait); + } else { + dl_reply.pkt_status = + cpu_to_be32(AUDIO_MVS_PKT_SLOW); + } + mutex_unlock(&audio->in_lock); + MM_DBG(" audio->in_read = %d audio->in write = %d\n", + audio->in_read, audio->in_write); + dl_reply.valid_frame_info_ptr = cpu_to_be32(0x00000001); + dl_reply.frame_mode = cpu_to_be32(audio->frame_mode); + dl_reply.frame_mode_again = + cpu_to_be32(audio->frame_mode); + dl_reply.frame_info_hdr.frame_mode = + cpu_to_be32(audio->frame_mode); + dl_reply.frame_info_hdr.mvs_mode = + cpu_to_be32(audio->mvs_mode); + dl_reply.frame_info_hdr.buf_free_cnt = 0; + dl_reply.pcm_frame = cpu_to_be32(audio->pcm_frame); + dl_reply.pcm_mode = cpu_to_be32(audio->pcm_mode); + dl_reply.valid_pkt_status_ptr = cpu_to_be32(0x00000001); + rc = msm_rpc_write(audio->rpc_endpt, &dl_reply, + sizeof(dl_reply)); + if (rc < 0) + MM_ERR("RPC write for DL response failed %d\n", + rc); + break; + } + default: + MM_ERR("Unknown CB type %d\n", procedure); + } +} + +static int audio_mvs_thread(void *data) +{ + struct audio_mvs_info_type *audio = &audio_mvs_info; + struct rpc_request_hdr *rpc_hdr = NULL; + struct rpc_reply_hdr *rpc_reply = NULL; + uint32_t reply_status = 0; + uint32_t rpc_type; + int rpc_hdr_len; + MM_DBG("%s\n", __func__); + + while (!kthread_should_stop()) { + rpc_hdr_len = + msm_rpc_read(audio->rpc_endpt, (void **)&rpc_hdr, -1, -1); + if (rpc_hdr_len < 0) { + MM_ERR("RPC read failed %d\n", rpc_hdr_len); + break; + } else if (rpc_hdr_len < RPC_COMMON_HDR_SZ) + continue; + else { + rpc_type = be32_to_cpu(rpc_hdr->type); + if (rpc_type == RPC_TYPE_REPLY) { + if (rpc_hdr_len < RPC_REPLY_HDR_SZ) + continue; + rpc_reply = (void *)rpc_hdr; + reply_status = be32_to_cpu(rpc_reply-> + reply_stat); + if (reply_status != RPCMSG_REPLYSTAT_ACCEPTED) { + /* If the command is not accepted, + * there will be no response callback. + * Wake the caller and report error. */ + audio->rpc_status = RPC_STATUS_REJECT; + wake_up(&audio->wait); + MM_ERR("RPC reply status denied\n"); + } + } else if (rpc_type == RPC_TYPE_REQUEST) { + if (rpc_hdr_len < RPC_REQUEST_HDR_SZ) + continue; + MM_DBG("ALSA: kthread call procedure\n"); + audio_mvs_process_rpc_request( + be32_to_cpu(rpc_hdr->procedure), + be32_to_cpu(rpc_hdr->xid), + (void *)(rpc_hdr + 1), + (rpc_hdr_len - sizeof(*rpc_hdr)), + audio); + } else + MM_ERR("Unexpected RPC type %d\n", rpc_type); + } + kfree(rpc_hdr); + rpc_hdr = NULL; + } + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + + struct audio_mvs_info_type *audio = &audio_mvs_info; + MM_DBG("%s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_start = 1; + else + audio->capture_start = 1; + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_start = 0; + else + audio->capture_start = 0; + break; + default: + break; + } + return 0; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + + MM_DBG("%s\n", __func__); + mutex_lock(&audio->lock); + if (audio->state < AUDIO_MVS_OPENED) { + audio->rpc_endpt = + msm_rpc_connect_compatible(MVS_PROG, + MVS_VERS, + MSM_RPC_UNINTERRUPTIBLE); + audio->state = AUDIO_MVS_OPENED; + } + + if (IS_ERR(audio->rpc_endpt)) { + MM_ERR("ALSA MVS RPC connect failed with version 0x%x\n", + MVS_VERS); + ret = PTR_ERR(audio->rpc_endpt); + audio->rpc_endpt = NULL; + goto err; + } else { + MM_DBG("ALSA MVS RPC connect succeeded\n"); + if (audio->playback_substream == NULL || + audio->capture_substream == NULL) { + if (substream->stream == + SNDRV_PCM_STREAM_PLAYBACK) { + audio->playback_substream = + substream; + runtime->hw = msm_pcm_hardware; + } else if (substream->stream == + SNDRV_PCM_STREAM_CAPTURE) { + audio->capture_substream = + substream; + runtime->hw = msm_pcm_hardware; + } + } else { + ret = -EPERM; + goto err; + } + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + MM_ERR("snd_pcm_hw_constraint_integer failed\n"); + if (!audio->instance) { + msm_rpc_close(audio->rpc_endpt); + audio->rpc_endpt = NULL; + } + goto err; + } + audio->instance++; + } +err: + mutex_unlock(&audio->lock); + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int rc = 0; + int count = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + uint32_t index; + MM_DBG("%s\n", __func__); + if (audio->dl_play == 1) { + rc = wait_event_interruptible_timeout(audio->in_wait, + (audio->in_write - audio->in_read <= 3), + 100 * HZ); + if (!rc) { + MM_ERR("MVS: write time out\n"); + return -ETIMEDOUT; + } else if (rc < 0) { + MM_ERR("MVS: write was interrupted\n"); + return -ERESTARTSYS; + } + } + mutex_lock(&audio->in_lock); + if (audio->state == AUDIO_MVS_ENABLED) { + index = audio->in_write % MVS_MAX_Q_LEN; + count = frames_to_bytes(runtime, frames); + if (count <= MVS_MAX_VOC_PKT_SIZE) { + rc = copy_from_user(audio->in[index].voc_pkt, buf, + count); + } else + rc = -ENOMEM; + if (!rc) { + audio->in[index].len = count; + audio->in_write++; + rc = count; + if (audio->in_write >= 3) + audio->dl_play = 1; + } else { + MM_ERR("Copy from user returned %d\n", rc); + rc = -EFAULT; + } + + } else { + MM_ERR("Write performed in invalid state %d\n", + audio->state); + rc = -EINVAL; + } + mutex_unlock(&audio->in_lock); + return rc; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, + void __user *buf, snd_pcm_uframes_t frames) +{ + int rc = 0; + int count = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + uint32_t index = 0; + + MM_DBG("%s\n", __func__); + + /* Ensure the driver has been enabled. */ + if (audio->state != AUDIO_MVS_ENABLED) { + MM_ERR("Read performed in invalid state %d\n", audio->state); + return -EPERM; + } + rc = wait_event_interruptible_timeout(audio->out_wait, + (audio->out_read < audio->out_write || + audio->state == AUDIO_MVS_CLOSING || + audio->state == AUDIO_MVS_CLOSED), + 100 * HZ); + if (!rc) { + MM_ERR("MVS: No UL data available\n"); + return -ETIMEDOUT; + } else if (rc < 0) { + MM_ERR("MVS: Read was interrupted\n"); + return -ERESTARTSYS; + } + + mutex_lock(&audio->out_lock); + if (audio->state == AUDIO_MVS_CLOSING + || audio->state == AUDIO_MVS_CLOSED) { + rc = -EBUSY; + } else { + count = frames_to_bytes(runtime, frames); + index = audio->out_read % MVS_MAX_Q_LEN; + if (audio->out[index].len <= count) { + rc = copy_to_user(buf, + audio->out[index].voc_pkt, + audio->out[index].len); + if (rc == 0) { + rc = audio->out[index].len; + audio->out_read++; + } else { + MM_ERR("Copy to user %d\n", rc); + rc = -EFAULT; + } + } else + rc = -ENOMEM; + } + mutex_unlock(&audio->out_lock); + return rc; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + MM_DBG("%s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct audio_mvs_info_type *audio = &audio_mvs_info; + struct audio_mvs_release_msg release_msg; + MM_DBG("%s\n", __func__); + memset(&release_msg, 0, sizeof(release_msg)); + mutex_lock(&audio->lock); + + audio->instance--; + wake_up(&audio->out_wait); + + if (!audio->instance) { + if (audio->state == AUDIO_MVS_ENABLED) { + audio->state = AUDIO_MVS_CLOSING; + /* Release MVS. */ + release_msg.client_id = cpu_to_be32(MVS_CLIENT_ID_VOIP); + msm_rpc_setup_req(&release_msg.rpc_hdr, audio->rpc_prog, + audio->rpc_ver, + MVS_RELEASE_PROC); + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, &release_msg, + sizeof(release_msg)); + if (rc >= 0) { + MM_DBG("RPC write for release done\n"); + rc = wait_event_timeout(audio->wait, + (audio->rpc_status != + RPC_STATUS_FAILURE), 1 * HZ); + if (rc != 0) { + MM_DBG + ("Wait event for release succeeded\n"); + rc = 0; + kthread_stop(audio->task); + audio->prepare_ack = 0; + audio->task = NULL; + del_timer_sync(&audio->timer); + } else { + MM_ERR + ("Wait event for release failed %d\n", + rc); + } + } else { + MM_ERR("RPC write for release failed %d\n", rc); + } + } + audio->state = AUDIO_MVS_CLOSED; + msm_rpc_close(audio->rpc_endpt); + audio->rpc_endpt = NULL; + } + + mutex_unlock(&audio->lock); + + wake_unlock(&audio->suspend_lock); + wake_unlock(&audio->idle_lock); + /* Release the IO buffers. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mutex_lock(&audio->in_lock); + audio->in_write = 0; + audio->in_read = 0; + audio->playback_enable = 0; + audio->dl_play = 0; + audio->ack_dl_count = 0; + memset(audio->in[0].voc_pkt, 0, + MVS_MAX_VOC_PKT_SIZE * MVS_MAX_Q_LEN); + audio->in->len = 0; + audio->playback_substream = NULL; + mutex_unlock(&audio->in_lock); + } else { + mutex_lock(&audio->out_lock); + audio->out_write = 0; + audio->out_read = 0; + audio->capture_enable = 0; + audio->ack_ul_count = 0; + memset(audio->out[0].voc_pkt, 0, + MVS_MAX_VOC_PKT_SIZE * MVS_MAX_Q_LEN); + audio->out->len = 0; + audio->capture_substream = NULL; + mutex_unlock(&audio->out_lock); + } + return rc; +} + +static int msm_mvs_pcm_setup(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct audio_mvs_acquire_msg acquire_msg; + struct audio_mvs_info_type *audio = &audio_mvs_info; + memset(&acquire_msg, 0, sizeof(acquire_msg)); + + /*Create an Kthread */ + MM_DBG("ALSA MVS thread creating\n"); + if (!IS_ERR(audio->rpc_endpt)) { + audio->task = + kthread_run(audio_mvs_thread, audio, + "audio_alsa_mvs_thread"); + if (!IS_ERR(audio->task)) { + MM_DBG("ALSA MVS thread create succeeded\n"); + audio->rpc_prog = MVS_PROG; + audio->rpc_ver = MVS_VERS; + /* Acquire MVS. */ + acquire_msg.acquire_args.client_id = + cpu_to_be32(MVS_CLIENT_ID_VOIP); + acquire_msg.acquire_args.cb_func_id = + cpu_to_be32(MVS_CB_FUNC_ID); + msm_rpc_setup_req(&acquire_msg.rpc_hdr, + audio->rpc_prog, + audio->rpc_ver, + MVS_ACQUIRE_PROC); + audio->rpc_status = RPC_STATUS_FAILURE; + rc = msm_rpc_write(audio->rpc_endpt, + &acquire_msg, sizeof(acquire_msg)); + if (rc >= 0) { + MM_DBG("RPC write for acquire done\n"); + + rc = wait_event_timeout(audio->wait, + (audio->rpc_status != + RPC_STATUS_FAILURE), + 1 * HZ); + if (rc != 0) { + audio->state = + AUDIO_MVS_ACQUIRE; + rc = 0; + MM_DBG + ("MVS driver in acquire state\n"); + } else { + MM_ERR + ("acquire Wait event failed %d\n", + rc); + rc = -EBUSY; + } + } else { + MM_ERR("RPC write for acquire failed %d\n", + rc); + rc = -EBUSY; + } + } else { + MM_ERR("ALSA MVS thread create failed\n"); + rc = PTR_ERR(audio->task); + audio->task = NULL; + msm_rpc_close(audio->rpc_endpt); + audio->rpc_endpt = NULL; + } + } else { + MM_ERR("RPC connect is not setup with version 0x%x\n", + MVS_VERS); + rc = PTR_ERR(audio->rpc_endpt); + audio->rpc_endpt = NULL; + } + /*mvs mode setup */ + if (audio->state == AUDIO_MVS_ACQUIRE) + rc = audio_mvs_setup_mvs(audio); + else + rc = -EBUSY; + return rc; +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct audio_mvs_info_type *prtd = &audio_mvs_info; + MM_DBG("%s\n", __func__); + prtd->pcm_playback_irq_pos = 0; + prtd->pcm_playback_buf_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->playback_enable = 1; + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct audio_mvs_info_type *prtd = &audio_mvs_info; + prtd->pcm_capture_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_capture_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_capture_irq_pos = 0; + prtd->pcm_capture_buf_pos = 0; + prtd->capture_enable = 1; + return 0; +} + + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *prtd = &audio_mvs_info; + unsigned long expiry = 0; + MM_DBG("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + + mutex_lock(&prtd->prepare_lock); + if (prtd->state == AUDIO_MVS_ENABLED) + goto enabled; + else if (prtd->state == AUDIO_MVS_PREPARING) + goto prepairing; + else if (prtd->state == AUDIO_MVS_OPENED) { + prtd->state = AUDIO_MVS_PREPARING; + rc = msm_mvs_pcm_setup(substream); + } + if (!rc) { + expiry = ((unsigned long)((prtd->pcm_count * 1000) + /(runtime->rate * runtime->channels * 2))); + expiry -= (expiry % 10); + prtd->timer.expires = jiffies + (msecs_to_jiffies(expiry)); + prtd->expiry_delta = (msecs_to_jiffies(expiry)); + if (prtd->expiry_delta <= 2) + prtd->expiry_delta = 1; + setup_timer(&prtd->timer, snd_pcm_mvs_timer, + (unsigned long)prtd); + prtd->ack_ul_count = 0; + prtd->ack_dl_count = 0; + add_timer(&prtd->timer); + + } else { + MM_ERR("ALSA MVS setup is not done"); + rc = -EPERM; + prtd->state = AUDIO_MVS_OPENED; + goto err; + } + +prepairing: + rc = wait_event_interruptible(prtd->prepare_wait, + (prtd->prepare_ack == 2)); + if (rc < 0) { + MM_ERR("Wait event for prepare faild rc %d", rc); + rc = -EINTR; + prtd->state = AUDIO_MVS_OPENED; + goto err; + } else + MM_DBG("Wait event for prepare succeeded\n"); + + prtd->state = AUDIO_MVS_ENABLED; +enabled: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rc = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rc = msm_pcm_capture_prepare(substream); +err: + mutex_unlock(&prtd->prepare_lock); + return rc; +} + +int msm_mvs_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + MM_DBG("%s\n", __func__); + if (substream->pcm->device & 1) { + runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED; + runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED; + } + return 0; +} + +static snd_pcm_uframes_t +msm_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + + if (audio->pcm_playback_irq_pos >= audio->pcm_size) + audio->pcm_playback_irq_pos = 0; + return bytes_to_frames(runtime, (audio->pcm_playback_irq_pos)); +} + +static snd_pcm_uframes_t +msm_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_mvs_info_type *audio = &audio_mvs_info; + + if (audio->pcm_capture_irq_pos >= audio->pcm_capture_size) + audio->pcm_capture_irq_pos = 0; + return bytes_to_frames(runtime, (audio->pcm_capture_irq_pos)); +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t ret = 0; + MM_DBG("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_pointer(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_pointer(substream); + return ret; +} + +static struct snd_pcm_ops msm_mvs_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_mvs_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + +}; + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int i, ret, offset = 0; + struct snd_pcm *pcm = rtd->pcm; + + audio_mvs_info.mem_chunk = kmalloc( + 2 * MVS_MAX_VOC_PKT_SIZE * MVS_MAX_Q_LEN, GFP_KERNEL); + if (audio_mvs_info.mem_chunk != NULL) { + audio_mvs_info.in_read = 0; + audio_mvs_info.in_write = 0; + audio_mvs_info.out_read = 0; + audio_mvs_info.out_write = 0; + for (i = 0; i < MVS_MAX_Q_LEN; i++) { + audio_mvs_info.in[i].voc_pkt = + audio_mvs_info.mem_chunk + offset; + offset = offset + MVS_MAX_VOC_PKT_SIZE; + } + for (i = 0; i < MVS_MAX_Q_LEN; i++) { + audio_mvs_info.out[i].voc_pkt = + audio_mvs_info.mem_chunk + offset; + offset = offset + MVS_MAX_VOC_PKT_SIZE; + } + audio_mvs_info.playback_substream = NULL; + audio_mvs_info.capture_substream = NULL; + } else { + MM_ERR("MSM MVS kmalloc failed\n"); + return -ENODEV; + } + + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_mvs_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_mvs_pcm_ops); + + return 0; +} + +struct snd_soc_platform_driver msm_mvs_soc_platform = { + .ops = &msm_mvs_pcm_ops, + .pcm_new = msm_pcm_new, +}; +EXPORT_SYMBOL(msm_mvs_soc_platform); + +static __devinit int msm_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, + &msm_mvs_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-mvs-audio", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = __devexit_p(msm_pcm_remove), +}; + +static int __init msm_mvs_soc_platform_init(void) +{ + memset(&audio_mvs_info, 0, sizeof(audio_mvs_info)); + mutex_init(&audio_mvs_info.lock); + mutex_init(&audio_mvs_info.prepare_lock); + mutex_init(&audio_mvs_info.in_lock); + mutex_init(&audio_mvs_info.out_lock); + init_waitqueue_head(&audio_mvs_info.wait); + init_waitqueue_head(&audio_mvs_info.prepare_wait); + init_waitqueue_head(&audio_mvs_info.out_wait); + init_waitqueue_head(&audio_mvs_info.in_wait); + wake_lock_init(&audio_mvs_info.suspend_lock, WAKE_LOCK_SUSPEND, + "audio_mvs_suspend"); + wake_lock_init(&audio_mvs_info.idle_lock, WAKE_LOCK_IDLE, + "audio_mvs_idle"); + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_mvs_soc_platform_init); + +static void __exit msm_mvs_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_mvs_soc_platform_exit); + +MODULE_DESCRIPTION("MVS PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-hostless.c b/sound/soc/msm/msm-pcm-hostless.c new file mode 100644 index 00000000000..c61511dc2e8 --- /dev/null +++ b/sound/soc/msm/msm-pcm-hostless.c @@ -0,0 +1,61 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +static struct snd_pcm_ops msm_pcm_hostless_ops = {}; + +static struct snd_soc_platform_driver msm_soc_hostless_platform = { + .ops = &msm_pcm_hostless_ops, +}; + +static __devinit int msm_pcm_hostless_probe(struct platform_device *pdev) +{ + pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_hostless_platform); +} + +static int msm_pcm_hostless_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_hostless_driver = { + .driver = { + .name = "msm-pcm-hostless", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_hostless_probe, + .remove = __devexit_p(msm_pcm_hostless_remove), +}; + +static int __init msm_soc_platform_init(void) +{ + return platform_driver_register(&msm_pcm_hostless_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_hostless_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("Hostless platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-lpa.c b/sound/soc/msm/msm-pcm-lpa.c new file mode 100644 index 00000000000..10546fb58c0 --- /dev/null +++ b/sound/soc/msm/msm-pcm-lpa.c @@ -0,0 +1,464 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6.h" +#include "msm-pcm-routing.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 2 * 1024 * 1024, +/* TODO: Check on the lowest period size we can support */ + .period_bytes_min = 128 * 1024, + .period_bytes_max = 512 * 1024, + .periods_min = 4, + .periods_max = 16, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_aio_write_param param; + struct audio_buffer *buf = prtd->audio_client->port[IN].buf; + int i = 0; + + pr_debug("%s\n", __func__); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: { + uint32_t *ptrmem = (uint32_t *)¶m; + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) + break; + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + + param.paddr = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[0].phys + + (prtd->out_head * prtd->pcm_count); + for (i = 0; i < sizeof(struct audio_aio_write_param)/4; + i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) & (runtime->periods - 1); + break; + } + case ASM_DATA_CMDRSP_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case APR_BASIC_RSP_RESULT: { + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, prtd->pcm_count); + param.paddr = (unsigned long)buf[prtd->out_head].data; + param.len = prtd->pcm_count; + param.msw_ts = 0; + param.lsw_ts = 0; + param.flags = NO_TIMESTAMP; + param.uid = (unsigned long)buf[prtd->out_head].data; + if (q6asm_async_write(prtd->audio_client, + ¶m) < 0) + pr_err("%s:q6asm_async_write failed\n", + __func__); + else + prtd->out_head = + (prtd->out_head + 1) + & (runtime->periods - 1); + } + break; + default: + break; + } + break; + } + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_pcm(prtd->audio_client, runtime->rate, + runtime->channels); + if (ret < 0) + pr_debug("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + prtd->enabled = 1; + prtd->cmd_ack = 0; + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("SNDRV_PCM_TRIGGER_START\n"); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->start, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd; + int ret = 0; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + runtime->hw = msm_pcm_hardware; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_debug("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + ret = q6asm_set_io_mode(prtd->audio_client, ASYNC_IO_MODE); + if (ret < 0) { + pr_err("%s: Set IO mode failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EPERM; + pr_debug("%s: session ID %d\n", __func__, prtd->audio_client->session); + prtd->session_id = prtd->audio_client->session; + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + + return 0; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int ret = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + ret = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (ret < 0) + pr_err("%s: CMD_EOS failed\n", __func__); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + pr_debug("%s\n", __func__); + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + pr_debug("%s\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + return ret; +} + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + pr_debug("%s: pcm_irq_pos = %d\n", __func__, prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + return -EPERM; + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed \ + rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + if (!buf && !buf[0].data) + return -ENOMEM; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static __devinit int msm_pcm_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", + __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-pcm-lpa", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = __devexit_p(msm_pcm_remove), +}; + +static int __init msm_soc_platform_init(void) +{ + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-q6.c b/sound/soc/msm/msm-pcm-q6.c new file mode 100644 index 00000000000..053569332ea --- /dev/null +++ b/sound/soc/msm/msm-pcm-q6.c @@ -0,0 +1,689 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6.h" +#include "msm-pcm-routing.h" + +static struct audio_locks the_locks; + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 4096 * 8, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 8, + .periods_max = 8, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static uint32_t in_frame_info[8][2]; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + uint32_t *ptrmem = (uint32_t *)payload; + int i = 0; + uint32_t idx = 0; + uint32_t size = 0; + + pr_debug("%s\n", __func__); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: { + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) + break; + if (!prtd->mmap_flag) + break; + if (q6asm_is_cpu_buf_avail(IN, + prtd->audio_client, + &size, &idx)) { + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + q6asm_write(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + } + break; + } + case ASM_DATA_CMDRSP_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case ASM_DATA_EVENT_READ_DONE: { + pr_debug("ASM_DATA_EVENT_READ_DONE\n"); + pr_debug("token = 0x%08x\n", token); + for (i = 0; i < 8; i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + in_frame_info[token][0] = payload[2]; + in_frame_info[token][1] = payload[3]; + prtd->pcm_irq_pos += in_frame_info[token][0]; + pr_debug("pcm_irq_pos=%d\n", prtd->pcm_irq_pos); + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + if (atomic_read(&prtd->in_count) <= prtd->periods) + atomic_inc(&prtd->in_count); + wake_up(&the_locks.read_wait); + if (prtd->mmap_flag + && q6asm_is_cpu_buf_avail(OUT, + prtd->audio_client, + &size, &idx)) + q6asm_read(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + if (!prtd->mmap_flag + && !atomic_read(&prtd->out_needed)) + break; + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) + break; + if (prtd->mmap_flag) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + } else { + while (atomic_read(&prtd->out_needed)) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + atomic_dec(&prtd->out_needed); + wake_up(&the_locks.write_wait); + }; + } + break; + default: + break; + } + } + break; + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_pcm(prtd->audio_client, runtime->rate, + runtime->channels); + if (ret < 0) + pr_info("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + + prtd->enabled = 1; + prtd->cmd_ack = 0; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + int i = 0; + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + + if (prtd->enabled) + return 0; + + pr_debug("Samp_rate = %d\n", prtd->samp_rate); + pr_debug("Channel = %d\n", prtd->channel_mode); + ret = q6asm_enc_cfg_blk_pcm(prtd->audio_client, prtd->samp_rate, + prtd->channel_mode); + if (ret < 0) + pr_debug("%s: cmd cfg pcm was block failed", __func__); + + for (i = 0; i < runtime->periods; i++) + q6asm_read(prtd->audio_client); + prtd->periods = runtime->periods; + + prtd->enabled = 1; + + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: Trigger start\n", __func__); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->start, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd; + int ret = 0; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + runtime->hw = msm_pcm_hardware; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_info("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm in open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + + pr_info("%s: session ID %d\n", __func__, prtd->audio_client->session); + + prtd->session_id = prtd->audio_client->session; + msm_pcm_routing_reg_phy_stream(soc_prtd->dai_link->be_id, + prtd->session_id, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->cmd_ack = 1; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_info("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + + return 0; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer = 0; + char *bufptr = NULL; + void *data = NULL; + uint32_t idx = 0; + uint32_t size = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + pr_debug("%s: prtd->out_count = %d\n", + __func__, atomic_read(&prtd->out_count)); + ret = wait_event_timeout(the_locks.write_wait, + (atomic_read(&prtd->out_count)), 5 * HZ); + if (ret < 0) { + pr_err("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + + if (!atomic_read(&prtd->out_count)) { + pr_err("%s: pcm stopped out_count 0\n", __func__); + return 0; + } + + data = q6asm_is_cpu_buf_avail(IN, prtd->audio_client, &size, &idx); + bufptr = data; + if (bufptr) { + pr_debug("%s:fbytes =%d: xfer=%d size=%d\n", + __func__, fbytes, xfer, size); + xfer = fbytes; + if (copy_from_user(bufptr, buf, xfer)) { + ret = -EFAULT; + goto fail; + } + buf += xfer; + fbytes -= xfer; + pr_debug("%s:fbytes = %d: xfer=%d\n", __func__, fbytes, xfer); + if (atomic_read(&prtd->start)) { + pr_debug("%s:writing %d bytes of buffer to dsp\n", + __func__, xfer); + ret = q6asm_write(prtd->audio_client, xfer, + 0, 0, NO_TIMESTAMP); + if (ret < 0) { + ret = -EFAULT; + goto fail; + } + } else + atomic_inc(&prtd->out_needed); + atomic_dec(&prtd->out_count); + } +fail: + return ret; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int ret = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + ret = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (ret < 0) + pr_err("%s: CMD_EOS failed\n", __func__); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_PLAYBACK); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return 0; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer; + char *bufptr; + void *data = NULL; + static uint32_t idx; + static uint32_t size; + uint32_t offset = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + + pr_debug("%s\n", __func__); + fbytes = frames_to_bytes(runtime, frames); + + pr_debug("appl_ptr %d\n", (int)runtime->control->appl_ptr); + pr_debug("hw_ptr %d\n", (int)runtime->status->hw_ptr); + pr_debug("avail_min %d\n", (int)runtime->control->avail_min); + + ret = wait_event_timeout(the_locks.read_wait, + (atomic_read(&prtd->in_count)), 5 * HZ); + if (ret < 0) { + pr_debug("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + if (!atomic_read(&prtd->in_count)) { + pr_debug("%s: pcm stopped in_count 0\n", __func__); + return 0; + } + pr_debug("Checking if valid buffer is available...%08x\n", + (unsigned int) data); + data = q6asm_is_cpu_buf_avail(OUT, prtd->audio_client, &size, &idx); + bufptr = data; + pr_debug("Size = %d\n", size); + pr_debug("fbytes = %d\n", fbytes); + pr_debug("idx = %d\n", idx); + if (bufptr) { + xfer = fbytes; + if (xfer > size) + xfer = size; + offset = in_frame_info[idx][1]; + pr_debug("Offset value = %d\n", offset); + if (copy_to_user(buf, bufptr+offset, xfer)) { + pr_err("Failed to copy buf to user\n"); + ret = -EFAULT; + goto fail; + } + fbytes -= xfer; + size -= xfer; + in_frame_info[idx][1] += xfer; + pr_debug("%s:fbytes = %d: size=%d: xfer=%d\n", + __func__, fbytes, size, xfer); + pr_debug(" Sending next buffer to dsp\n"); + memset(&in_frame_info[idx], 0, + sizeof(uint32_t) * 2); + atomic_dec(&prtd->in_count); + ret = q6asm_read(prtd->audio_client); + if (ret < 0) { + pr_err("q6asm read failed\n"); + ret = -EFAULT; + goto fail; + } + } else + pr_err("No valid buffer\n"); + + pr_debug("Returning from capture_copy... %d\n", ret); +fail: + return ret; +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; + struct msm_audio *prtd = runtime->private_data; + int dir = OUT; + + pr_debug("%s\n", __func__); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + msm_pcm_routing_dereg_phy_stream(soc_prtd->dai_link->be_id, + SNDRV_PCM_STREAM_CAPTURE); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + pr_debug("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + dir = OUT; + + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed \ + rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static __devinit int msm_pcm_probe(struct platform_device *pdev) +{ + pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-pcm-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = __devexit_p(msm_pcm_remove), +}; + +static int __init msm_soc_platform_init(void) +{ + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-q6.h b/sound/soc/msm/msm-pcm-q6.h new file mode 100644 index 00000000000..6a0635b0739 --- /dev/null +++ b/sound/soc/msm/msm-pcm-q6.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009,2011 Code Aurora Forum. 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H +#include +#include + + +/* Support unconventional sample rates 12000, 24000 as well */ +#define USE_RATE \ + (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) + +extern int copy_count; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t eos_wait; + wait_queue_head_t enable_wait; +}; + +struct msm_audio { + struct snd_pcm_substream *substream; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + uint16_t source; /* Encoding source bit mask */ + + struct audio_client *audio_client; + + uint16_t session_id; + + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t dsp_cnt; + + int abort; /* set when error, like sample rate mismatch */ + + int enabled; + int close_ack; + int cmd_ack; + atomic_t start; + atomic_t out_count; + atomic_t in_count; + atomic_t out_needed; + int out_head; + int periods; + int mmap_flag; +}; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm-pcm-routing.c b/sound/soc/msm/msm-pcm-routing.c new file mode 100644 index 00000000000..06f72b71067 --- /dev/null +++ b/sound/soc/msm/msm-pcm-routing.c @@ -0,0 +1,688 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-routing.h" +#include "qdsp6/q6voice.h" + +struct audio_mixer_data { + u32 port_id; /* AFE port ID for Rx, FE DAI ID for TX */ + unsigned long dai_sessions; /* Rx: FE DAIs Tx: BE DAI */ + u32 mixer_type; /* playback or capture */ +}; + +#define INVALID_SESSION -1 + +enum { + AUDIO_MIXER_PRI_I2S_RX = 0, + AUDIO_MIXER_SLIMBUS_0_RX, + AUDIO_MIXER_HDMI_RX, + AUDIO_MIXER_MM_UL1, + AUDIO_MIXER_INT_BT_SCO_RX, + AUDIO_MIXER_INT_FM_RX, + AUDIO_MIXER_MAX, +}; + +enum { + AUDIO_PORT_MIXER_SLIM_0_RX = 0, + AUDIO_PORT_MIXER_MAX, +}; + +/* Tx mixer session is stored based on BE DAI ID + * Need to map to actual AFE port ID since AFE port + * ID can get really large. + * The table convert DAI back to AFE port ID + */ +static int bedai_port_map[MSM_BACKEND_DAI_MAX] = { + PRIMARY_I2S_RX, + PRIMARY_I2S_TX, + SLIMBUS_0_RX, + SLIMBUS_0_TX, + HDMI_RX, + INT_BT_SCO_RX, + INT_BT_SCO_TX, + INT_FM_RX, + INT_FM_TX, +}; + +/* Track ASM playback & capture sessions of DAI */ +static int fe_dai_map[MSM_FRONTEND_DAI_MAX][2] = { + /* MULTIMEDIA1 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA2 */ + {INVALID_SESSION, INVALID_SESSION}, + /* MULTIMEDIA3 */ + {INVALID_SESSION, INVALID_SESSION}, +}; + +static struct audio_mixer_data audio_mixers[AUDIO_MIXER_MAX] = { + /* AUDIO_MIXER_PRI_I2S_RX */ + {PRIMARY_I2S_RX, 0, SNDRV_PCM_STREAM_PLAYBACK}, + /* AUDIO_MIXER_SLIMBUS_0_RX */ + {SLIMBUS_0_RX, 0, SNDRV_PCM_STREAM_PLAYBACK}, + /* AUDIO_MIXER_HDMI_RX */ + {HDMI_RX, 0, SNDRV_PCM_STREAM_PLAYBACK}, + /* AUDIO_MIXER_MM_UL1 */ + {MSM_FRONTEND_DAI_MULTIMEDIA1, 0, SNDRV_PCM_STREAM_CAPTURE}, + /* AUDIO_MIXER_INT_BT_SCO_RX */ + {INT_BT_SCO_RX, 0, SNDRV_PCM_STREAM_PLAYBACK}, + /* AUDIO_MIXER_INT_FM_RX */ + {INT_FM_RX, 0, SNDRV_PCM_STREAM_PLAYBACK}, +}; +static struct voice_mixer_data voice_mixers[VOICE_MIXER_MAX] = { + /* VOICE_MIXER_PRI_I2S_RX */ + {VOICE_PRI_I2S_RX, 0, SNDRV_PCM_STREAM_PLAYBACK}, + /* VOICE_MIXER_SLIMBUS_0_RX */ + {VOICE_SLIMBUS_0_RX, 0, SNDRV_PCM_STREAM_PLAYBACK}, + /* VOICE_MIXER_PRI_I2S_TX */ + {VOICE_PRI_I2S_TX, 0, SNDRV_PCM_STREAM_CAPTURE}, + /* VOICE_MIXER_SLIMBUS_0_TX */ + {VOICE_SLIMBUS_0_TX, 0, SNDRV_PCM_STREAM_CAPTURE}, + /* VOICE_MIXER_INT_BT_SCO_RX */ + {VOICE_INT_BT_SCO_RX, 0, SNDRV_PCM_STREAM_PLAYBACK}, + /* VOICE_MIXER_INT_BT_SCO_TX */ + {VOICE_INT_BT_SCO_TX, 0, SNDRV_PCM_STREAM_CAPTURE} +}; + +/* Reuse audio_mixer_data struct but ignore mixer type field + * unless there is use case for RX -> TX + */ +static struct audio_mixer_data audio_port_mixers[AUDIO_PORT_MIXER_MAX] = { + /* AUDIO_PORT_MIXER_SLIM_0_RX */ + {SLIMBUS_0_RX, 0, SNDRV_PCM_STREAM_PLAYBACK}, +}; + +void msm_pcm_routing_reg_phy_stream(int fedai_id, int dspst_id, int stream_type) +{ + int i, be_id; + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + fe_dai_map[fedai_id][0] = dspst_id; + for (i = 0; i < AUDIO_MIXER_MAX; i++) { + if ((audio_mixers[i].mixer_type == stream_type) && + (test_bit(fedai_id, &audio_mixers[i].dai_sessions))) + /* To do: multiple devices case */ + adm_route_session(audio_mixers[i].port_id, + dspst_id, 1); + } + } else { + fe_dai_map[fedai_id][1] = dspst_id; + for (i = 0; i < AUDIO_MIXER_MAX; i++) { + if ((audio_mixers[i].mixer_type == stream_type) && + (fedai_id == audio_mixers[i].port_id)) { + /* To-do: Handle mixing of inputs */ + be_id = find_next_bit( + &audio_mixers[i].dai_sessions, + MSM_BACKEND_DAI_MAX, 0); + if (be_id < MSM_BACKEND_DAI_MAX) + adm_route_session(bedai_port_map[be_id], + dspst_id, 1); + else + pr_err("%s: no routing\n", __func__); + } + } + } +} + +void msm_pcm_routing_dereg_phy_stream(int fedai_id, int stream_type) +{ + int i, be_id; + + if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < AUDIO_MIXER_MAX; i++) { + if ((audio_mixers[i].mixer_type == stream_type) && + (test_bit(fedai_id, &audio_mixers[i].dai_sessions))) { + /* To do: multiple devices case */ + adm_route_session(audio_mixers[i].port_id, + fe_dai_map[fedai_id][0], 0); + } + } + fe_dai_map[fedai_id][0] = INVALID_SESSION; + } else { + for (i = 0; i < AUDIO_MIXER_MAX; i++) { + if ((audio_mixers[i].mixer_type == stream_type) && + (fedai_id == audio_mixers[i].port_id)) { + /* To-do: Handle mixing of inputs */ + be_id = find_next_bit( + &audio_mixers[i].dai_sessions, + MSM_BACKEND_DAI_MAX, 0); + if (be_id < MSM_BACKEND_DAI_MAX) + adm_route_session(bedai_port_map[be_id], + fe_dai_map[fedai_id][1], 0); + else + pr_err("%s: no routing\n", __func__); + } + } + fe_dai_map[fedai_id][1] = INVALID_SESSION; + } +} + +static void msm_pcm_routing_process_audio(u16 reg, u16 val, int set) +{ + + pr_debug("%s: reg %x val %x set %x\n", __func__, reg, val, set); + + if (set) + set_bit(val, &audio_mixers[reg].dai_sessions); + else + clear_bit(val, &audio_mixers[reg].dai_sessions); + + if (audio_mixers[reg].mixer_type == SNDRV_PCM_STREAM_PLAYBACK) { + if (fe_dai_map[val][0] != INVALID_SESSION) + adm_route_session(audio_mixers[reg].port_id, + fe_dai_map[val][0], set); + } else { + int fe_id = audio_mixers[reg].port_id; + if (fe_dai_map[fe_id][1] != INVALID_SESSION) + adm_route_session(bedai_port_map[val], + fe_dai_map[fe_id][1], set); + } +} + +static int msm_routing_get_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (test_bit(mc->shift, &audio_mixers[mc->reg].dai_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + + if (ucontrol->value.integer.value[0]) { + msm_pcm_routing_process_audio(mc->reg, mc->shift, 1); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + msm_pcm_routing_process_audio(mc->reg, mc->shift, 0); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + + return 1; +} + +static void msm_pcm_routing_process_voice(u16 reg, u16 val, int set) +{ + + u32 port_map_id; + + pr_debug("%s: reg %x val %x set %x\n", __func__, reg, val, set); + + port_map_id = voice_mixers[reg].port_id; + + if (set) + set_bit(val, &voice_mixers[reg].dai_sessions); + else + clear_bit(val, &voice_mixers[reg].dai_sessions); + + if (voice_mixers[reg].mixer_type == SNDRV_PCM_STREAM_PLAYBACK) { + voc_set_route_flag(RX_PATH, set); + if (set) { + voc_set_rxtx_port(bedai_port_map[port_map_id], DEV_RX); + + if (voc_get_route_flag(RX_PATH) && + voc_get_route_flag(TX_PATH)) + voc_enable_cvp(); + } else { + voc_disable_cvp(); + } + } else { + voc_set_route_flag(TX_PATH, set); + if (set) { + voc_set_rxtx_port(bedai_port_map[port_map_id], DEV_TX); + + if (voc_get_route_flag(RX_PATH) && + voc_get_route_flag(TX_PATH)) + voc_enable_cvp(); + } else { + voc_disable_cvp(); + } + } +} + +static int msm_routing_get_voice_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (test_bit(mc->shift, &voice_mixers[mc->reg].dai_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_voice_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (ucontrol->value.integer.value[0]) { + msm_pcm_routing_process_voice(mc->reg, mc->shift, 1); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 1); + } else { + msm_pcm_routing_process_voice(mc->reg, mc->shift, 0); + snd_soc_dapm_mixer_update_power(widget, kcontrol, 0); + } + + return 1; +} + +static int msm_routing_get_port_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + if (test_bit(mc->shift, &audio_port_mixers[mc->reg].dai_sessions)) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, mc->shift, + ucontrol->value.integer.value[0]); + + return 0; +} + +static int msm_routing_put_port_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + pr_debug("%s: reg %x shift %x val %ld\n", __func__, mc->reg, + mc->shift, ucontrol->value.integer.value[0]); + + if (ucontrol->value.integer.value[0]) { + afe_loopback(1, audio_port_mixers[mc->reg].port_id, + bedai_port_map[mc->shift]); + set_bit(mc->shift, + &audio_port_mixers[mc->reg].dai_sessions); + } else { + afe_loopback(0, audio_port_mixers[mc->reg].port_id, + bedai_port_map[mc->shift]); + clear_bit(mc->shift, + &audio_port_mixers[mc->reg].dai_sessions); + } + + return 1; +} + +static const struct snd_kcontrol_new pri_i2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", AUDIO_MIXER_PRI_I2S_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", AUDIO_MIXER_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", AUDIO_MIXER_PRI_I2S_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", AUDIO_MIXER_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", AUDIO_MIXER_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia3", AUDIO_MIXER_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new hdmi_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", AUDIO_MIXER_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", AUDIO_MIXER_HDMI_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new int_bt_sco_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", AUDIO_MIXER_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", AUDIO_MIXER_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new int_fm_rx_mixer_controls[] = { + SOC_SINGLE_EXT("MultiMedia1", AUDIO_MIXER_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("MultiMedia2", AUDIO_MIXER_INT_FM_RX, + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX", AUDIO_MIXER_MM_UL1, + MSM_BACKEND_DAI_PRI_I2S_TX, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", AUDIO_MIXER_MM_UL1, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX", AUDIO_MIXER_MM_UL1, + MSM_BACKEND_DAI_INT_BT_SCO_TX, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), + SOC_SINGLE_EXT("INTERNAL_FM_TX", AUDIO_MIXER_MM_UL1, + MSM_BACKEND_DAI_INT_FM_TX, 1, 0, msm_routing_get_audio_mixer, + msm_routing_put_audio_mixer), +}; + +static const struct snd_kcontrol_new pri_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", VOICE_MIXER_PRI_I2S_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", VOICE_MIXER_PRI_I2S_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new slimbus_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", VOICE_MIXER_SLIMBUS_0_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", VOICE_MIXER_SLIMBUS_0_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new bt_sco_rx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("CSVoice", VOICE_MIXER_INT_BT_SCO_RX, + MSM_FRONTEND_DAI_CS_VOICE, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("Voip", VOICE_MIXER_INT_BT_SCO_RX , + MSM_FRONTEND_DAI_VOIP, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_voice_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_Voice", VOICE_MIXER_PRI_I2S_TX, + MSM_BACKEND_DAI_PRI_I2S_TX, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voice", VOICE_MIXER_SLIMBUS_0_TX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voice", VOICE_MIXER_INT_BT_SCO_TX, + MSM_BACKEND_DAI_INT_BT_SCO_TX, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new tx_voip_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_TX_Voip", VOICE_MIXER_PRI_I2S_TX, + MSM_BACKEND_DAI_PRI_I2S_TX, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("SLIM_0_TX_Voip", VOICE_MIXER_SLIMBUS_0_TX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), + SOC_SINGLE_EXT("INTERNAL_BT_SCO_TX_Voip", VOICE_MIXER_INT_BT_SCO_TX, + MSM_BACKEND_DAI_INT_BT_SCO_TX, 1, 0, msm_routing_get_voice_mixer, + msm_routing_put_voice_mixer), +}; + +static const struct snd_kcontrol_new sbus_0_rx_port_mixer_controls[] = { + SOC_SINGLE_EXT("INTERNAL_FM_TX", AUDIO_PORT_MIXER_SLIM_0_RX, + MSM_BACKEND_DAI_INT_FM_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), + SOC_SINGLE_EXT("SLIM_0_TX", AUDIO_PORT_MIXER_SLIM_0_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, 1, 0, msm_routing_get_port_mixer, + msm_routing_put_port_mixer), +}; + +static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + /* Frontend AIF */ + /* Widget name equals to Front-End DAI name, + * Stream name must contains substring of front-end dai name + */ + SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL2", "MultiMedia2 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL3", "MultiMedia3 Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("VOIP_DL", "VoIP Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL1", "MultiMedia1 Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("CS-VOICE_DL1", "CS-VOICE Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("CS-VOICE_UL1", "CS-VOICE Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("VOIP_UL", "VoIP Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIM0_DL_HL", "SLIMBUS0_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIM0_UL_HL", "SLIMBUS0_HOSTLESS Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("INTFM_DL_HL", "INT_FM_HOSTLESS Playback", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("INTFM_UL_HL", "INT_FM_HOSTLESS Capture", + 0, 0, 0, 0), + /* Backend AIF */ + /* Stream name equals to backend dai link stream name + */ + SND_SOC_DAPM_AIF_OUT("PRI_I2S_RX", "Primary I2S Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_0_RX", "Slimbus Playback", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("HDMI", "HDMI Playback", 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("PRI_I2S_TX", "Primary I2S Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_0_TX", "Slimbus Capture", 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("INT_BT_SCO_RX", "Internal BT-SCO Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INT_BT_SCO_TX", "Internal BT-SCO Capture", + 0, 0, 0, 0), + SND_SOC_DAPM_AIF_OUT("INT_FM_RX", "Internal FM Playback", + 0, 0, 0 , 0), + SND_SOC_DAPM_AIF_IN("INT_FM_TX", "Internal FM Capture", + 0, 0, 0, 0), + + /* Mixer definitions */ + SND_SOC_DAPM_MIXER("PRI_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_i2s_rx_mixer_controls, ARRAY_SIZE(pri_i2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_rx_mixer_controls, ARRAY_SIZE(slimbus_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0, + hdmi_mixer_controls, ARRAY_SIZE(hdmi_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + /* Voice Mixer */ + SND_SOC_DAPM_MIXER("PRI_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, pri_rx_voice_mixer_controls, + ARRAY_SIZE(pri_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIM_0_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + slimbus_rx_voice_mixer_controls, + ARRAY_SIZE(slimbus_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX_Voice Mixer", + SND_SOC_NOPM, 0, 0, + bt_sco_rx_voice_mixer_controls, + ARRAY_SIZE(bt_sco_rx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("Voice_Tx Mixer", + SND_SOC_NOPM, 0, 0, tx_voice_mixer_controls, + ARRAY_SIZE(tx_voice_mixer_controls)), + SND_SOC_DAPM_MIXER("Voip_Tx Mixer", + SND_SOC_NOPM, 0, 0, tx_voip_mixer_controls, + ARRAY_SIZE(tx_voip_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_BT_SCO_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + int_bt_sco_rx_mixer_controls, ARRAY_SIZE(int_bt_sco_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("INTERNAL_FM_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + int_fm_rx_mixer_controls, ARRAY_SIZE(int_fm_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Port Mixer", + SND_SOC_NOPM, 0, 0, sbus_0_rx_port_mixer_controls, + ARRAY_SIZE(sbus_0_rx_port_mixer_controls)), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"PRI_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"PRI_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"PRI_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"PRI_I2S_RX", NULL, "PRI_RX Audio Mixer"}, + + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"SLIMBUS_0_RX Audio Mixer", "MultiMedia3", "MM_DL3"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Audio Mixer"}, + + {"HDMI Mixer", "MultiMedia1", "MM_DL1"}, + {"HDMI Mixer", "MultiMedia2", "MM_DL2"}, + {"HDMI", NULL, "HDMI Mixer"}, + + {"MultiMedia1 Mixer", "PRI_TX", "PRI_I2S_TX"}, + {"MultiMedia1 Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"INTERNAL_BT_SCO_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX Audio Mixer"}, + + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia1", "MM_DL1"}, + {"INTERNAL_FM_RX Audio Mixer", "MultiMedia2", "MM_DL2"}, + {"INT_FM_RX", NULL, "INTERNAL_FM_RX Audio Mixer"}, + + {"MultiMedia1 Mixer", "INTERNAL_BT_SCO_TX", "INT_BT_SCO_TX"}, + {"MultiMedia1 Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"MM_UL1", NULL, "MultiMedia1 Mixer"}, + + {"PRI_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"PRI_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"PRI_I2S_RX", NULL, "PRI_RX_Voice Mixer"}, + + {"SLIM_0_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"SLIM_0_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"SLIMBUS_0_RX", NULL, "SLIM_0_RX_Voice Mixer"}, + + {"INTERNAL_BT_SCO_RX_Voice Mixer", "CSVoice", "CS-VOICE_DL1"}, + {"INTERNAL_BT_SCO_RX_Voice Mixer", "Voip", "VOIP_DL"}, + {"INT_BT_SCO_RX", NULL, "INTERNAL_BT_SCO_RX_Voice Mixer"}, + + {"Voice_Tx Mixer", "PRI_TX_Voice", "PRI_I2S_TX"}, + {"Voice_Tx Mixer", "SLIM_0_TX_Voice", "SLIMBUS_0_TX"}, + {"Voice_Tx Mixer", "INTERNAL_BT_SCO_TX_Voice", "INT_BT_SCO_TX"}, + {"CS-VOICE_UL1", NULL, "Voice_Tx Mixer"}, + {"Voip_Tx Mixer", "PRI_TX_Voip", "PRI_I2S_TX"}, + {"Voip_Tx Mixer", "SLIM_0_TX_Voip", "SLIMBUS_0_TX"}, + {"Voip_Tx Mixer", "INTERNAL_BT_SCO_TX_Voip", "INT_BT_SCO_TX"}, + {"VOIP_UL", NULL, "Voip_Tx Mixer"}, + {"SLIMBUS_0_RX", NULL, "SLIM0_DL_HL"}, + {"SLIM0_UL_HL", NULL, "SLIMBUS_0_TX"}, + {"INT_FM_RX", NULL, "INTFM_DL_HL"}, + {"INTFM_UL_HL", NULL, "INT_FM_TX"}, + {"SLIMBUS_0_RX Port Mixer", "INTERNAL_FM_TX", "INT_FM_TX"}, + {"SLIMBUS_0_RX Port Mixer", "SLIM_0_TX", "SLIMBUS_0_TX"}, + {"SLIMBUS_0_RX", NULL, "SLIMBUS_0_RX Port Mixer"}, +}; + +static struct snd_pcm_ops msm_routing_pcm_ops = {}; + +static unsigned int msm_routing_read(struct snd_soc_platform *platform, + unsigned int reg) +{ + dev_dbg(platform->dev, "reg %x\n", reg); + return 0; +} + +/* Not used but frame seems to require it */ +static int msm_routing_write(struct snd_soc_platform *platform, + unsigned int reg, unsigned int val) +{ + dev_dbg(platform->dev, "reg %x val %x\n", reg, val); + return 0; +} + +/* Not used but frame seems to require it */ +static int msm_routing_probe(struct snd_soc_platform *platform) +{ + snd_soc_dapm_new_controls(&platform->dapm, msm_qdsp6_widgets, + ARRAY_SIZE(msm_qdsp6_widgets)); + snd_soc_dapm_add_routes(&platform->dapm, intercon, + ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(&platform->dapm); + + return 0; +} + +static struct snd_soc_platform_driver msm_soc_routing_platform = { + .ops = &msm_routing_pcm_ops, + .probe = msm_routing_probe, + .read = msm_routing_read, + .write = msm_routing_write, +}; + +static __devinit int msm_routing_pcm_probe(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "dev name %s\n", dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_routing_platform); +} + +static int msm_routing_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_routing_pcm_driver = { + .driver = { + .name = "msm-pcm-routing", + .owner = THIS_MODULE, + }, + .probe = msm_routing_pcm_probe, + .remove = __devexit_p(msm_routing_pcm_remove), +}; + +static int __init msm_soc_routing_platform_init(void) +{ + return platform_driver_register(&msm_routing_pcm_driver); +} +module_init(msm_soc_routing_platform_init); + +static void __exit msm_soc_routing_platform_exit(void) +{ + platform_driver_unregister(&msm_routing_pcm_driver); +} +module_exit(msm_soc_routing_platform_exit); + +MODULE_DESCRIPTION("MSM routing platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-routing.h b/sound/soc/msm/msm-pcm-routing.h new file mode 100644 index 00000000000..c45f95b6c98 --- /dev/null +++ b/sound/soc/msm/msm-pcm-routing.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MSM_PCM_ROUTING_H +#define _MSM_PCM_ROUTING_H +#include + +#define LPASS_BE_PRI_I2S_RX "(Backend) PRIMARY_I2S_RX" +#define LPASS_BE_PRI_I2S_TX "(Backend) PRIMARY_I2S_TX" +#define LPASS_BE_SLIMBUS_0_RX "(Backend) SLIMBUS_0_RX" +#define LPASS_BE_SLIMBUS_0_TX "(Backend) SLIMBUS_0_TX" +#define LPASS_BE_HDMI "(Backend) HDMI" +#define LPASS_BE_INT_BT_SCO_RX "(Backend) INT_BT_SCO_RX" +#define LPASS_BE_INT_BT_SCO_TX "(Backend) INT_BT_SCO_TX" +#define LPASS_BE_INT_FM_RX "(Backend) INT_FM_RX" +#define LPASS_BE_INT_FM_TX "(Backend) INT_FM_TX" + +enum { + MSM_FRONTEND_DAI_MULTIMEDIA1 = 0, + MSM_FRONTEND_DAI_MULTIMEDIA2, + MSM_FRONTEND_DAI_CS_VOICE, + MSM_FRONTEND_DAI_VOIP, + MSM_FRONTEND_DAI_MULTIMEDIA3, + MSM_FRONTEND_DAI_MAX, +}; + +enum { + MSM_BACKEND_DAI_PRI_I2S_RX = 0, + MSM_BACKEND_DAI_PRI_I2S_TX, + MSM_BACKEND_DAI_SLIMBUS_0_RX, + MSM_BACKEND_DAI_SLIMBUS_0_TX, + MSM_BACKEND_DAI_HDMI_RX, + MSM_BACKEND_DAI_INT_BT_SCO_RX, + MSM_BACKEND_DAI_INT_BT_SCO_TX, + MSM_BACKEND_DAI_INT_FM_RX, + MSM_BACKEND_DAI_INT_FM_TX, + MSM_BACKEND_DAI_MAX, +}; + +struct voice_mixer_data { + u32 port_id; + unsigned long dai_sessions; + u32 mixer_type; +}; + +enum { + VOICE_MIXER_PRI_I2S_RX = 0, + VOICE_MIXER_SLIMBUS_0_RX, + VOICE_MIXER_PRI_I2S_TX, + VOICE_MIXER_SLIMBUS_0_TX, + VOICE_MIXER_INT_BT_SCO_RX, + VOICE_MIXER_INT_BT_SCO_TX, + VOICE_MIXER_MAX, +}; + +enum { + VOICE_PRI_I2S_RX = 0, + VOICE_PRI_I2S_TX, + VOICE_SLIMBUS_0_RX, + VOICE_SLIMBUS_0_TX, + VOICE_INT_BT_SCO_RX = 5, + VOICE_INT_BT_SCO_TX, +}; + +/* dai_id: front-end ID, + * dspst_id: DSP audio stream ID + * stream_type: playback or capture + */ +void msm_pcm_routing_reg_phy_stream(int fedai_id, int dspst_id, + int stream_type); +void msm_pcm_routing_dereg_phy_stream(int fedai_id, int stream_type); + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm-pcm-voice.c b/sound/soc/msm/msm-pcm-voice.c new file mode 100644 index 00000000000..1f581562d52 --- /dev/null +++ b/sound/soc/msm/msm-pcm-voice.c @@ -0,0 +1,319 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-voice.h" +#include "qdsp6/q6voice.h" + +static struct msm_voice voice_info; + +static struct snd_pcm_hardware msm_pcm_hardware = { + + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + + .buffer_bytes_max = 4096 * 2, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 2, + .periods_max = 2, + + .fifo_size = 0, +}; + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + + if (!prtd->playback_start) + prtd->playback_start = 1; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + + if (!prtd->capture_start) + prtd->capture_start = 1; + + return 0; +} +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *voice = &voice_info; + + mutex_lock(&voice->lock); + + runtime->hw = msm_pcm_hardware; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + voice->playback_substream = substream; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + voice->capture_substream = substream; + + voice->instance++; + pr_debug(" %s: instance: %d\n", __func__ , voice->instance); + runtime->private_data = voice; + + mutex_unlock(&voice->lock); + + return 0; +} +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + + if (prtd->playback_start) + prtd->playback_start = 0; + + prtd->playback_substream = NULL; + + return 0; +} +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + + if (prtd->capture_start) + prtd->capture_start = 0; + prtd->capture_substream = NULL; + + return 0; +} +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + int ret; + + mutex_lock(&prtd->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + + prtd->instance--; + if (!prtd->playback_start && !prtd->capture_start) { + pr_debug("end voice call\n"); + voc_end_voice_call(); + } + mutex_unlock(&prtd->lock); + + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_voice *prtd = runtime->private_data; + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + + if (prtd->playback_start && prtd->capture_start) + voc_start_voice_call(); + + mutex_unlock(&prtd->lock); + + return ret; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + pr_debug("%s, Voice\n", __func__); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int msm_voice_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* Volume */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 5; + + return 0; +} + +static int msm_voice_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_voice_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + + pr_debug(" dir: %d, volume: %d\n", dir, volume); + + voc_set_rx_vol_index(dir, volume); + return 0; +} + +static int msm_voice_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 2; + + return 0; +} + +static int msm_voice_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_voice_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int mute = ucontrol->value.integer.value[1]; + + pr_debug("%s: dir=%d, mute=%d\n", __func__, dir, mute); + + voc_set_tx_mute(dir, mute); + + return 0; +} + +static struct snd_kcontrol_new msm_voice_controls[] = { + MSM_EXT("VoiceVolume", msm_voice_volume_info, msm_voice_volume_get, \ + msm_voice_volume_put, 0), + MSM_EXT("VoiceMute", msm_voice_mute_info, msm_voice_mute_get, \ + msm_voice_mute_put, 0), +}; + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .prepare = msm_pcm_prepare, +}; + + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static int msm_pcm_voice_probe(struct snd_soc_platform *platform) +{ + snd_soc_add_platform_controls(platform, msm_voice_controls, + ARRAY_SIZE(msm_voice_controls)); + + return 0; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, + .probe = msm_pcm_voice_probe, +}; + +static __devinit int msm_pcm_probe(struct platform_device *pdev) +{ + pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-pcm-voice", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = __devexit_p(msm_pcm_remove), +}; + +static int __init msm_soc_platform_init(void) +{ + memset(&voice_info, 0, sizeof(voice_info)); + mutex_init(&voice_info.lock); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("Voice PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm-voice.h b/sound/soc/msm/msm-pcm-voice.h new file mode 100644 index 00000000000..2c1f5ca3716 --- /dev/null +++ b/sound/soc/msm/msm-pcm-voice.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _MSM_PCM_VOICE_H +#define _MSM_PCM_VOICE_H +#include + + +struct msm_voice { + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + int instance; + + struct mutex lock; + + uint32_t samp_rate; + uint32_t channel_mode; + + int playback_start; + int capture_start; +}; + +#define MSM_EXT(xname, fp_info, fp_get, fp_put, addr) \ + {.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ + } + +#endif /*_MSM_PCM_VOICE_H*/ diff --git a/sound/soc/msm/msm-pcm-voip.c b/sound/soc/msm/msm-pcm-voip.c new file mode 100644 index 00000000000..2f2ab791bd8 --- /dev/null +++ b/sound/soc/msm/msm-pcm-voip.c @@ -0,0 +1,721 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm-q6.h" +#include "msm-pcm-routing.h" +#include "qdsp6/q6voice.h" + +#define VOIP_MAX_Q_LEN 10 +#define VOIP_MAX_VOC_PKT_SIZE 320 + +enum voip_state { + VOIP_STOPPED, + VOIP_STARTED, +}; + +struct voip_frame { + uint32_t len; + uint8_t voc_pkt[VOIP_MAX_VOC_PKT_SIZE]; +}; + +struct voip_buf_node { + struct list_head list; + struct voip_frame frame; +}; + +struct voip_drv_info { + enum voip_state state; + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct list_head in_queue; + struct list_head free_in_queue; + + struct list_head out_queue; + struct list_head free_out_queue; + + wait_queue_head_t out_wait; + + struct mutex lock; + struct mutex in_lock; + struct mutex out_lock; + + spinlock_t dsp_lock; + + + uint8_t capture_start; + uint8_t playback_start; + + uint8_t playback_instance; + uint8_t capture_instance; + + unsigned int play_samp_rate; + unsigned int cap_samp_rate; + + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_playback_irq_pos; /* IRQ position */ + unsigned int pcm_playback_buf_pos; /* position in buffer */ + + unsigned int pcm_capture_size; + unsigned int pcm_capture_count; + unsigned int pcm_capture_irq_pos; /* IRQ position */ + unsigned int pcm_capture_buf_pos; /* position in buffer */ +}; + +static struct voip_drv_info voip_info; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = VOIP_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN, + .period_bytes_min = VOIP_MAX_VOC_PKT_SIZE, + .period_bytes_max = VOIP_MAX_VOC_PKT_SIZE, + .periods_min = VOIP_MAX_Q_LEN, + .periods_max = VOIP_MAX_Q_LEN, + .fifo_size = 0, +}; + + +/* sample rate supported */ +static unsigned int supported_sample_rates[] = {8000, 16000}; + +/* capture path */ +static void voip_process_ul_pkt(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data) +{ + struct voip_buf_node *buf_node = NULL; + struct voip_drv_info *prtd = private_data; + unsigned long dsp_flags; + + if (prtd->capture_substream == NULL) + return; + + /* Copy up-link packet into out_queue. */ + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + + /* discarding UL packets till start is received */ + if (!list_empty(&prtd->free_out_queue) && prtd->capture_start) { + buf_node = list_first_entry(&prtd->free_out_queue, + struct voip_buf_node, list); + list_del(&buf_node->list); + + buf_node->frame.len = pkt_len; + memcpy(&buf_node->frame.voc_pkt[0], voc_pkt, + buf_node->frame.len); + + list_add_tail(&buf_node->list, &prtd->out_queue); + prtd->pcm_capture_irq_pos += prtd->pcm_capture_count; + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); + snd_pcm_period_elapsed(prtd->capture_substream); + } else { + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); + pr_err("UL data dropped\n"); + } + + wake_up(&prtd->out_wait); +} + +/* playback path */ +static void voip_process_dl_pkt(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data) +{ + struct voip_buf_node *buf_node = NULL; + struct voip_drv_info *prtd = private_data; + unsigned long dsp_flags; + + + if (prtd->playback_substream == NULL) + return; + + spin_lock_irqsave(&prtd->dsp_lock, dsp_flags); + + if (!list_empty(&prtd->in_queue) && prtd->playback_start) { + buf_node = list_first_entry(&prtd->in_queue, + struct voip_buf_node, list); + list_del(&buf_node->list); + + *pkt_len = buf_node->frame.len; + + memcpy(voc_pkt, + &buf_node->frame.voc_pkt[0], + buf_node->frame.len); + + list_add_tail(&buf_node->list, &prtd->free_in_queue); + + prtd->pcm_playback_irq_pos += prtd->pcm_count; + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); + snd_pcm_period_elapsed(prtd->playback_substream); + } else { + *pkt_len = 0; + spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags); + pr_err("DL data not available\n"); + } +} + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + prtd->play_samp_rate = runtime->rate; + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_playback_irq_pos = 0; + prtd->pcm_playback_buf_pos = 0; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + int ret = 0; + + prtd->cap_samp_rate = runtime->rate; + prtd->pcm_capture_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_capture_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_capture_irq_pos = 0; + prtd->pcm_capture_buf_pos = 0; + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + pr_debug("%s: Trigger start\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->capture_start = 1; + else + prtd->playback_start = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->playback_start = 0; + else + prtd->capture_start = 0; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = &voip_info; + int ret = 0; + + pr_debug("%s, VoIP\n", __func__); + mutex_lock(&prtd->lock); + + runtime->hw = msm_pcm_hardware; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_list failed\n"); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_debug("snd_pcm_hw_constraint_integer failed\n"); + goto err; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->playback_substream = substream; + prtd->playback_instance++; + } else { + prtd->capture_substream = substream; + prtd->capture_instance++; + } + runtime->private_data = prtd; +err: + mutex_unlock(&prtd->lock); + + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + struct voip_buf_node *buf_node = NULL; + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + int count = frames_to_bytes(runtime, frames); + pr_debug("%s: count = %d\n", __func__, count); + + mutex_lock(&prtd->in_lock); + + if (count == VOIP_MAX_VOC_PKT_SIZE) { + if (!list_empty(&prtd->free_in_queue)) { + buf_node = + list_first_entry(&prtd->free_in_queue, + struct voip_buf_node, list); + list_del(&buf_node->list); + + ret = copy_from_user(&buf_node->frame.voc_pkt, + buf, count); + buf_node->frame.len = count; + list_add_tail(&buf_node->list, &prtd->in_queue); + } else + pr_err("%s: No free DL buffs\n", __func__); + } else { + pr_err("%s: Write count %d is not VOIP_MAX_VOC_PKT_SIZE\n", + __func__, count); + ret = -ENOMEM; + } + + mutex_unlock(&prtd->in_lock); + + return ret; +} +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + int count = 0; + struct voip_buf_node *buf_node = NULL; + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + count = frames_to_bytes(runtime, frames); + + pr_debug("%s: count = %d\n", __func__, count); + + ret = wait_event_interruptible_timeout(prtd->out_wait, + (!list_empty(&prtd->out_queue) || + prtd->state == VOIP_STOPPED), + 1 * HZ); + + if (ret > 0) { + mutex_lock(&prtd->out_lock); + + if (count == VOIP_MAX_VOC_PKT_SIZE) { + buf_node = list_first_entry(&prtd->out_queue, + struct voip_buf_node, list); + list_del(&buf_node->list); + + ret = copy_to_user(buf, + &buf_node->frame.voc_pkt, + buf_node->frame.len); + + if (ret) { + pr_err("%s: Copy to user retuned %d\n", + __func__, ret); + ret = -EFAULT; + } + list_add_tail(&buf_node->list, + &prtd->free_out_queue); + } else { + pr_err("%s: Read count %d < VOIP_MAX_VOC_PKT_SIZE\n", + __func__, count); + ret = -ENOMEM; + } + + mutex_unlock(&prtd->out_lock); + + } else if (ret == 0) { + pr_err("%s: No UL data available\n", __func__); + ret = -ETIMEDOUT; + } else { + pr_err("%s: Read was interrupted\n", __func__); + ret = -ERESTARTSYS; + } + return ret; +} +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct list_head *ptr = NULL; + struct list_head *next = NULL; + struct voip_buf_node *buf_node = NULL; + struct snd_dma_buffer *p_dma_buf, *c_dma_buf; + struct snd_pcm_substream *p_substream, *c_substream; + struct snd_pcm_runtime *runtime; + struct voip_drv_info *prtd; + + if (substream == NULL) { + pr_err("substream is NULL\n"); + return -EINVAL; + } + runtime = substream->runtime; + prtd = runtime->private_data; + + wake_up(&prtd->out_wait); + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prtd->playback_instance--; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + prtd->capture_instance--; + + if (!prtd->playback_instance && !prtd->capture_instance) { + if (prtd->state == VOIP_STARTED) { + prtd->state = VOIP_STOPPED; + voc_end_voice_call(); + voc_set_voc_path_full(0); + voc_register_mvs_cb(NULL, NULL, prtd); + } + /* release all buffer */ + /* release in_queue and free_in_queue */ + pr_debug("release all buffer\n"); + p_substream = prtd->playback_substream; + if (p_substream == NULL) { + pr_debug("p_substream is NULL\n"); + goto capt; + } + p_dma_buf = &p_substream->dma_buffer; + if (p_dma_buf == NULL) { + pr_debug("p_dma_buf is NULL\n"); + goto capt; + } + if (p_dma_buf->area != NULL) { + mutex_lock(&prtd->in_lock); + list_for_each_safe(ptr, next, &prtd->in_queue) { + buf_node = list_entry(ptr, + struct voip_buf_node, list); + list_del(&buf_node->list); + } + list_for_each_safe(ptr, next, &prtd->free_in_queue) { + buf_node = list_entry(ptr, + struct voip_buf_node, list); + list_del(&buf_node->list); + } + dma_free_coherent(p_substream->pcm->card->dev, + runtime->hw.buffer_bytes_max, p_dma_buf->area, + p_dma_buf->addr); + p_dma_buf->area = NULL; + mutex_unlock(&prtd->in_lock); + } + /* release out_queue and free_out_queue */ +capt: c_substream = prtd->capture_substream; + if (c_substream == NULL) { + pr_debug("c_substream is NULL\n"); + goto done; + } + c_dma_buf = &c_substream->dma_buffer; + if (c_substream == NULL) { + pr_debug("c_dma_buf is NULL.\n"); + goto done; + } + if (c_dma_buf->area != NULL) { + mutex_lock(&prtd->out_lock); + list_for_each_safe(ptr, next, &prtd->out_queue) { + buf_node = list_entry(ptr, + struct voip_buf_node, list); + list_del(&buf_node->list); + } + list_for_each_safe(ptr, next, &prtd->free_out_queue) { + buf_node = list_entry(ptr, + struct voip_buf_node, list); + list_del(&buf_node->list); + } + dma_free_coherent(c_substream->pcm->card->dev, + runtime->hw.buffer_bytes_max, c_dma_buf->area, + c_dma_buf->addr); + c_dma_buf->area = NULL; + mutex_unlock(&prtd->out_lock); + } +done: + prtd->capture_substream = NULL; + prtd->playback_substream = NULL; + } + mutex_unlock(&prtd->lock); + + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + mutex_lock(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + + if (prtd->playback_instance && prtd->capture_instance + && (prtd->state != VOIP_STARTED)) { + + voc_set_voc_path_full(1); + + if ((prtd->play_samp_rate == 8000) && + (prtd->cap_samp_rate == 8000)) + voc_config_vocoder(VSS_MEDIA_ID_PCM_NB, + 0, VSS_NETWORK_ID_VOIP_NB); + else if ((prtd->play_samp_rate == 16000) && + (prtd->cap_samp_rate == 16000)) + voc_config_vocoder(VSS_MEDIA_ID_PCM_WB, + 0, VSS_NETWORK_ID_VOIP_WB); + else { + pr_err(" sample rate are not set properly\n"); + goto err; + } + voc_register_mvs_cb(voip_process_ul_pkt, + voip_process_dl_pkt, prtd); + voc_start_voice_call(); + + prtd->state = VOIP_STARTED; + } +err: + mutex_unlock(&prtd->lock); + + return ret; +} + +static snd_pcm_uframes_t +msm_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + if (prtd->pcm_playback_irq_pos >= prtd->pcm_size) + prtd->pcm_playback_irq_pos = 0; + return bytes_to_frames(runtime, (prtd->pcm_playback_irq_pos)); +} + +static snd_pcm_uframes_t +msm_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct voip_drv_info *prtd = runtime->private_data; + + if (prtd->pcm_capture_irq_pos >= prtd->pcm_capture_size) + prtd->pcm_capture_irq_pos = 0; + return bytes_to_frames(runtime, (prtd->pcm_capture_irq_pos)); +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t ret = 0; + pr_debug("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_pointer(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_pointer(substream); + return ret; +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + pr_debug("%s\n", __func__); + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct voip_buf_node *buf_node = NULL; + int i = 0, offset = 0; + + pr_debug("%s: voip\n", __func__); + + mutex_lock(&voip_info.lock); + + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + + dma_buf->area = dma_alloc_coherent(substream->pcm->card->dev, + runtime->hw.buffer_bytes_max, + &dma_buf->addr, GFP_KERNEL); + if (!dma_buf->area) { + pr_err("%s:MSM VOIP dma_alloc failed\n", __func__); + return -ENOMEM; + } + + dma_buf->bytes = runtime->hw.buffer_bytes_max; + memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < VOIP_MAX_Q_LEN; i++) { + buf_node = (void *)dma_buf->area + offset; + + mutex_lock(&voip_info.in_lock); + list_add_tail(&buf_node->list, + &voip_info.free_in_queue); + mutex_unlock(&voip_info.in_lock); + offset = offset + sizeof(struct voip_buf_node); + } + } else { + for (i = 0; i < VOIP_MAX_Q_LEN; i++) { + buf_node = (void *) dma_buf->area + offset; + mutex_lock(&voip_info.out_lock); + list_add_tail(&buf_node->list, + &voip_info.free_out_queue); + mutex_unlock(&voip_info.out_lock); + offset = offset + sizeof(struct voip_buf_node); + } + } + + mutex_unlock(&voip_info.lock); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + pr_debug("msm_asoc_pcm_new\n"); + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +static struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_asoc_pcm_new, +}; + +static __devinit int msm_pcm_probe(struct platform_device *pdev) +{ + pr_info("%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-voip-dsp", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = __devexit_p(msm_pcm_remove), +}; + +static int __init msm_soc_platform_init(void) +{ + memset(&voip_info, 0, sizeof(voip_info)); + + mutex_init(&voip_info.lock); + mutex_init(&voip_info.in_lock); + mutex_init(&voip_info.out_lock); + + spin_lock_init(&voip_info.dsp_lock); + + init_waitqueue_head(&voip_info.out_wait); + + INIT_LIST_HEAD(&voip_info.in_queue); + INIT_LIST_HEAD(&voip_info.free_in_queue); + INIT_LIST_HEAD(&voip_info.out_queue); + INIT_LIST_HEAD(&voip_info.free_out_queue); + + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm-pcm.c b/sound/soc/msm/msm-pcm.c new file mode 100644 index 00000000000..bea528fe9ac --- /dev/null +++ b/sound/soc/msm/msm-pcm.c @@ -0,0 +1,645 @@ +/* sound/soc/msm/msm-pcm.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, Code Aurora Forum. 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm.h" + +#define MAX_DATA_SIZE 496 +#define AUDPP_ALSA_DECODER (-1) + +#define DB_TABLE_INDEX (50) + +#define audio_send_queue_recbs(prtd, cmd, len) \ + msm_adsp_write(prtd->audrec, QDSP_uPAudRecBitStreamQueue, cmd, len) +#define audio_send_queue_rec(prtd, cmd, len) \ + msm_adsp_write(prtd->audrec, QDSP_uPAudRecCmdQueue, cmd, len) + +int intcnt; +static int audio_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len); + +struct audio_frame { + uint16_t count_low; + uint16_t count_high; + uint16_t bytes; + uint16_t unknown; + unsigned char samples[]; +} __attribute__ ((packed)); + +/* Table contains dB to raw value mapping */ +static const unsigned decoder_db_table[] = { + + 31 , /* -50 dB */ + 35 , 39 , 44 , 50 , 56 , + 63 , 70 , 79 , 89 , 99 , + 112 , 125 , 141 , 158 , 177 , + 199 , 223 , 251 , 281 , 316 , + 354 , 398 , 446 , 501 , 562 , + 630 , 707 , 794 , 891 , 999 , + 1122 , 1258 , 1412 , 1584 , 1778 , + 1995 , 2238 , 2511 , 2818 , 3162 , + 3548 , 3981 , 4466 , 5011 , 5623 , + 6309 , 7079 , 7943 , 8912 , 10000 , + 11220 , 12589 , 14125 , 15848 , 17782 , + 19952 , 22387 , 25118 , 28183 , 31622 , + 35481 , 39810 , 44668 , 50118 , 56234 , + 63095 , 70794 , 79432 , 89125 , 100000 , + 112201 , 125892 , 141253 , 158489 , 177827 , + 199526 , 223872 , 251188 , 281838 , 316227 , + 354813 , 398107 , 446683 , 501187 , 562341 , + 630957 , 707945 , 794328 , 891250 , 1000000 , + 1122018 , 1258925 , 1412537 , 1584893 , 1778279 , + 1995262 , 2238721 , 2511886 , 2818382 , 3162277 , + 3548133 /* 51 dB */ + +}; + +static unsigned compute_db_raw(int db) +{ + unsigned reg_val = 0; /* Computed result for correspondent db */ + /* Check if the given db is out of range */ + if (db <= MIN_DB) + return 0; + else if (db > MAX_DB) + db = MAX_DB; /* If db is too high then set to max */ + reg_val = decoder_db_table[DB_TABLE_INDEX+db]; + return reg_val; +} + +int msm_audio_volume_update(unsigned id, + int volume, int pan) +{ + unsigned vol_raw; + + vol_raw = compute_db_raw(volume); + printk(KERN_INFO "volume: %8x vol_raw: %8x \n", volume, vol_raw); + return audpp_set_volume_and_pan(id, vol_raw, pan); +} +EXPORT_SYMBOL(msm_audio_volume_update); + +void alsa_dsp_event(void *data, unsigned id, uint16_t *msg) +{ + struct msm_audio *prtd = data; + struct buffer *frame; + unsigned long flag; + + switch (id) { + case AUDPP_MSG_STATUS_MSG: + break; + case AUDPP_MSG_SPA_BANDS: + break; + case AUDPP_MSG_HOST_PCM_INTF_MSG:{ + unsigned id = msg[2]; + unsigned idx = msg[3] - 1; + if (id != AUDPP_MSG_HOSTPCM_ID_ARM_RX) { + printk(KERN_ERR "bogus id\n"); + break; + } + if (idx > 1) { + printk(KERN_ERR "bogus buffer idx\n"); + break; + } + /* Update with actual sent buffer size */ + if (prtd->out[idx].used != BUF_INVALID_LEN) + prtd->pcm_irq_pos += prtd->out[idx].used; + + if (prtd->pcm_irq_pos > prtd->pcm_size) + prtd->pcm_irq_pos = prtd->pcm_count; + + if (prtd->ops->playback) + prtd->ops->playback(prtd); + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + if (prtd->running) { + prtd->out[idx].used = 0; + frame = prtd->out + prtd->out_tail; + if (frame->used) { + audio_dsp_send_buffer(prtd, + prtd->out_tail, + frame->used); + prtd->out_tail ^= 1; + } else { + prtd->out_needed++; + } + wake_up(&the_locks.write_wait); + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + break; + } + case AUDPP_MSG_PCMDMAMISSED: + pr_info("alsa_dsp_event: PCMDMAMISSED %d\n", msg[0]); + prtd->eos_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + prtd->out_needed = 0; + prtd->running = 1; + audio_dsp_out_enable(prtd, 1); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + prtd->running = 0; + } else { + printk(KERN_ERR "alsa_dsp_event:CFG_MSG=%d\n", msg[0]); + } + break; + case EVENT_MSG_ID: + printk(KERN_INFO"alsa_dsp_event: arm9 event\n"); + break; + default: + printk(KERN_ERR "alsa_dsp_event: UNKNOWN (%d)\n", id); + } +} + +void alsa_audpre_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + uint16_t msg[MAX_DATA_SIZE/2]; + + if (len > MAX_DATA_SIZE) { + printk(KERN_ERR"audpre: event too large(%d bytes)\n", len); + return; + } + getevent(msg, len); + + switch (id) { + case AUDPREPROC_MSG_CMD_CFG_DONE_MSG: + break; + case AUDPREPROC_MSG_ERROR_MSG_ID: + printk(KERN_ERR "audpre: err_index %d\n", msg[0]); + break; + case EVENT_MSG_ID: + printk(KERN_INFO"audpre: arm9 event\n"); + break; + default: + printk(KERN_ERR "audpre: unknown event %d\n", id); + } +} + +void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct msm_audio *prtd = data; + unsigned long flag; + uint16_t msg[MAX_DATA_SIZE/2]; + + if (len > MAX_DATA_SIZE) { + printk(KERN_ERR"audrec: event/msg too large(%d bytes)\n", len); + return; + } + getevent(msg, len); + + switch (id) { + case AUDREC_MSG_CMD_CFG_DONE_MSG: + if (msg[0] & AUDREC_MSG_CFG_DONE_TYPE_0_UPDATE) { + if (msg[0] & AUDREC_MSG_CFG_DONE_TYPE_0_ENA) + audrec_encoder_config(prtd); + else + prtd->running = 0; + } + break; + case AUDREC_MSG_CMD_AREC_PARAM_CFG_DONE_MSG:{ + prtd->running = 1; + break; + } + case AUDREC_MSG_FATAL_ERR_MSG: + printk(KERN_ERR "audrec: ERROR %x\n", msg[0]); + break; + case AUDREC_MSG_PACKET_READY_MSG: + alsa_get_dsp_frames(prtd); + ++intcnt; + if (prtd->channel_mode == 1) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->pcm_irq_pos += prtd->pcm_count; + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } else if ((prtd->channel_mode == 0) && (intcnt % 2 == 0)) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->pcm_irq_pos += prtd->pcm_count; + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } + break; + case EVENT_MSG_ID: + printk(KERN_INFO"audrec: arm9 event\n"); + break; + default: + printk(KERN_ERR "audrec: unknown event %d\n", id); + } +} + +struct msm_adsp_ops aud_pre_adsp_ops = { + .event = alsa_audpre_dsp_event, +}; + +struct msm_adsp_ops aud_rec_adsp_ops = { + .event = audrec_dsp_event, +}; + +int alsa_adsp_configure(struct msm_audio *prtd) +{ + int ret, i; + + if (prtd->dir == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->data = prtd->playback_substream->dma_buffer.area; + prtd->phys = prtd->playback_substream->dma_buffer.addr; + } + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) { + prtd->data = prtd->capture_substream->dma_buffer.area; + prtd->phys = prtd->capture_substream->dma_buffer.addr; + } + if (!prtd->data) { + ret = -ENOMEM; + goto err1; + } + + ret = audmgr_open(&prtd->audmgr); + if (ret) + goto err2; + if (prtd->dir == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->out_buffer_size = PLAYBACK_DMASZ; + prtd->out_sample_rate = 44100; + prtd->out_channel_mode = AUDPP_CMD_PCM_INTF_STEREO_V; + prtd->out_weight = 100; + + prtd->out[0].data = prtd->data + 0; + prtd->out[0].addr = prtd->phys + 0; + prtd->out[0].size = BUFSZ; + prtd->out[1].data = prtd->data + BUFSZ; + prtd->out[1].addr = prtd->phys + BUFSZ; + prtd->out[1].size = BUFSZ; + } + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) { + prtd->samp_rate = RPC_AUD_DEF_SAMPLE_RATE_44100; + prtd->samp_rate_index = AUDREC_CMD_SAMP_RATE_INDX_44100; + prtd->channel_mode = AUDREC_CMD_STEREO_MODE_STEREO; + prtd->buffer_size = STEREO_DATA_SIZE; + prtd->type = AUDREC_CMD_TYPE_0_INDEX_WAV; + prtd->tx_agc_cfg.cmd_id = AUDPREPROC_CMD_CFG_AGC_PARAMS; + prtd->ns_cfg.cmd_id = AUDPREPROC_CMD_CFG_NS_PARAMS; + prtd->iir_cfg.cmd_id = + AUDPREPROC_CMD_CFG_IIR_TUNING_FILTER_PARAMS; + + ret = msm_adsp_get("AUDPREPROCTASK", + &prtd->audpre, &aud_pre_adsp_ops, prtd); + if (ret) + goto err3; + ret = msm_adsp_get("AUDRECTASK", + &prtd->audrec, &aud_rec_adsp_ops, prtd); + if (ret) { + msm_adsp_put(prtd->audpre); + goto err3; + } + prtd->dsp_cnt = 0; + prtd->in_head = 0; + prtd->in_tail = 0; + prtd->in_count = 0; + for (i = 0; i < FRAME_NUM; i++) { + prtd->in[i].size = 0; + prtd->in[i].read = 0; + } + } + + return 0; + +err3: + audmgr_close(&prtd->audmgr); + +err2: + prtd->data = NULL; +err1: + return ret; +} +EXPORT_SYMBOL(alsa_adsp_configure); + +int alsa_audio_configure(struct msm_audio *prtd) +{ + struct audmgr_config cfg; + int rc; + + if (prtd->enabled) + return 0; + + /* refuse to start if we're not ready with first buffer */ + if (!prtd->out[0].used) + return -EIO; + + cfg.tx_rate = 0; + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_48000; + cfg.def_method = RPC_AUD_DEF_METHOD_HOST_PCM; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_MIDI; + rc = audmgr_enable(&prtd->audmgr, &cfg); + if (rc < 0) + return rc; + + if (audpp_enable(AUDPP_ALSA_DECODER, alsa_dsp_event, prtd)) { + printk(KERN_ERR "audio: audpp_enable() failed\n"); + audmgr_disable(&prtd->audmgr); + return -ENODEV; + } + + prtd->enabled = 1; + return 0; +} +EXPORT_SYMBOL(alsa_audio_configure); + +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + int rc = 0; + + mutex_lock(&the_locks.write_lock); + while (count > 0) { + frame = prtd->out + prtd->out_head; + rc = wait_event_interruptible(the_locks.write_wait, + (frame->used == 0) + || (prtd->stopped)); + if (rc < 0) + break; + if (prtd->stopped) { + rc = -EBUSY; + break; + } + xfer = count > frame->size ? frame->size : count; + if (copy_from_user(frame->data, buf, xfer)) { + rc = -EFAULT; + break; + } + frame->used = xfer; + prtd->out_head ^= 1; + count -= xfer; + buf += xfer; + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + frame = prtd->out + prtd->out_tail; + if (frame->used && prtd->out_needed) { + audio_dsp_send_buffer(prtd, prtd->out_tail, + frame->used); + prtd->out_tail ^= 1; + prtd->out_needed--; + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + } + mutex_unlock(&the_locks.write_lock); + if (buf > start) + return buf - start; + return rc; +} +EXPORT_SYMBOL(alsa_send_buffer); + +int alsa_audio_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + mutex_lock(&the_locks.lock); + prtd->enabled = 0; + audio_dsp_out_enable(prtd, 0); + wake_up(&the_locks.write_wait); + audpp_disable(AUDPP_ALSA_DECODER, prtd); + audmgr_disable(&prtd->audmgr); + prtd->out_needed = 0; + mutex_unlock(&the_locks.lock); + } + return 0; +} +EXPORT_SYMBOL(alsa_audio_disable); + +int alsa_audrec_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + mutex_lock(&the_locks.lock); + prtd->enabled = 0; + alsa_rec_dsp_enable(prtd, 0); + wake_up(&the_locks.read_wait); + msm_adsp_disable(prtd->audpre); + msm_adsp_disable(prtd->audrec); + audmgr_disable(&prtd->audmgr); + prtd->out_needed = 0; + prtd->opened = 0; + mutex_unlock(&the_locks.lock); + } + return 0; +} +EXPORT_SYMBOL(alsa_audrec_disable); + +static int audio_dsp_read_buffer(struct msm_audio *prtd, uint32_t read_cnt) +{ + audrec_cmd_packet_ext_ptr cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_PACKET_EXT_PTR; + /* Both WAV and AAC use AUDREC_CMD_TYPE_0 */ + cmd.type = AUDREC_CMD_TYPE_0; + cmd.curr_rec_count_msw = read_cnt >> 16; + cmd.curr_rec_count_lsw = read_cnt; + + return audio_send_queue_recbs(prtd, &cmd, sizeof(cmd)); +} + +int audrec_encoder_config(struct msm_audio *prtd) +{ + audrec_cmd_arec0param_cfg cmd; + uint16_t *data = (void *)prtd->data; + unsigned n; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_AREC0PARAM_CFG; + cmd.ptr_to_extpkt_buffer_msw = prtd->phys >> 16; + cmd.ptr_to_extpkt_buffer_lsw = prtd->phys; + cmd.buf_len = FRAME_NUM; /* Both WAV and AAC use 8 frames */ + cmd.samp_rate_index = prtd->samp_rate_index; + /* 0 for mono, 1 for stereo */ + cmd.stereo_mode = prtd->channel_mode; + cmd.rec_quality = 0x1C00; + + /* prepare buffer pointers: + * Mono: 1024 samples + 4 halfword header + * Stereo: 2048 samples + 4 halfword header + */ + + for (n = 0; n < FRAME_NUM; n++) { + prtd->in[n].data = data + 4; + data += (4 + (prtd->channel_mode ? 2048 : 1024)); + } + + return audio_send_queue_rec(prtd, &cmd, sizeof(cmd)); +} + +int audio_dsp_out_enable(struct msm_audio *prtd, int yes) +{ + audpp_cmd_pcm_intf cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_PCM_INTF_2; + cmd.object_num = AUDPP_CMD_PCM_INTF_OBJECT_NUM; + cmd.config = AUDPP_CMD_PCM_INTF_CONFIG_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + + if (yes) { + cmd.write_buf1LSW = prtd->out[0].addr; + cmd.write_buf1MSW = prtd->out[0].addr >> 16; + cmd.write_buf1_len = 0; + cmd.write_buf2LSW = prtd->out[1].addr; + cmd.write_buf2MSW = prtd->out[1].addr >> 16; + cmd.write_buf2_len = prtd->out[1].used; + cmd.arm_to_rx_flag = AUDPP_CMD_PCM_INTF_ENA_V; + cmd.weight_decoder_to_rx = prtd->out_weight; + cmd.weight_arm_to_rx = 1; + cmd.partition_number_arm_to_dsp = 0; + cmd.sample_rate = prtd->out_sample_rate; + cmd.channel_mode = prtd->out_channel_mode; + } + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag; + void *data; + uint32_t index; + uint32_t size; + int rc = 0; + + mutex_lock(&the_locks.read_lock); + while (count > 0) { + rc = wait_event_interruptible(the_locks.read_wait, + (prtd->in_count > 0) + || prtd->stopped); + if (rc < 0) + break; + + if (prtd->stopped) { + rc = -EBUSY; + break; + } + + index = prtd->in_tail; + data = (uint8_t *) prtd->in[index].data; + size = prtd->in[index].size; + if (count >= size) { + if (copy_to_user(buf, data, size)) { + rc = -EFAULT; + break; + } + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + if (index != prtd->in_tail) { + /* overrun: data is invalid, we need to retry */ + spin_unlock_irqrestore(&the_locks.read_dsp_lock, + flag); + continue; + } + prtd->in[index].size = 0; + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + prtd->in_count--; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + count -= size; + buf += size; + } else { + break; + } + } + mutex_unlock(&the_locks.read_lock); + return rc; +} +EXPORT_SYMBOL(alsa_buffer_read); + +static int audio_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len) +{ + audpp_cmd_pcm_intf_send_buffer cmd; + cmd.cmd_id = AUDPP_CMD_PCM_INTF_2; + cmd.host_pcm_object = AUDPP_CMD_PCM_INTF_OBJECT_NUM; + cmd.config = AUDPP_CMD_PCM_INTF_BUFFER_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + cmd.dsp_to_arm_buf_id = 0; + cmd.arm_to_dsp_buf_id = idx + 1; + cmd.arm_to_dsp_buf_len = len; + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +int alsa_rec_dsp_enable(struct msm_audio *prtd, int enable) +{ + audrec_cmd_cfg cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_CFG; + cmd.type_0 = enable ? AUDREC_CMD_TYPE_0_ENA : AUDREC_CMD_TYPE_0_DIS; + cmd.type_0 |= (AUDREC_CMD_TYPE_0_UPDATE | prtd->type); + cmd.type_1 = 0; + + return audio_send_queue_rec(prtd, &cmd, sizeof(cmd)); +} +EXPORT_SYMBOL(alsa_rec_dsp_enable); + +void alsa_get_dsp_frames(struct msm_audio *prtd) +{ + struct audio_frame *frame; + uint32_t index = 0; + unsigned long flag; + + if (prtd->type == AUDREC_CMD_TYPE_0_INDEX_WAV) { + index = prtd->in_head; + + frame = + (void *)(((char *)prtd->in[index].data) - sizeof(*frame)); + + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->in[index].size = frame->bytes; + + prtd->in_head = (prtd->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (prtd->in_head == prtd->in_tail) + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + else + prtd->in_count++; + + audio_dsp_read_buffer(prtd, prtd->dsp_cnt++); + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + wake_up(&the_locks.read_wait); + } else { + /* TODO AAC not supported yet. */ + } +} +EXPORT_SYMBOL(alsa_get_dsp_frames); diff --git a/sound/soc/msm/msm-pcm.h b/sound/soc/msm/msm-pcm.h new file mode 100644 index 00000000000..e7ddd30737e --- /dev/null +++ b/sound/soc/msm/msm-pcm.h @@ -0,0 +1,200 @@ +/* sound/soc/msm/msm-pcm.h + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2009, Code Aurora Forum. 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H + + +#include +#include +#include +#include +#include +#include + +#include <../arch/arm/mach-msm/qdsp5/adsp.h> +#include <../arch/arm/mach-msm/qdsp5/audmgr.h> + + +#define FRAME_NUM (8) +#define FRAME_SIZE (2052 * 2) +#define MONO_DATA_SIZE (2048) +#define STEREO_DATA_SIZE (MONO_DATA_SIZE * 2) +#define CAPTURE_DMASZ (FRAME_SIZE * FRAME_NUM) + +#define BUFSZ (960 * 5) +#define PLAYBACK_DMASZ (BUFSZ * 2) + +#define MSM_PLAYBACK_DEFAULT_VOLUME 0 /* 0dB */ +#define MSM_PLAYBACK_DEFAULT_PAN 0 + +#define USE_FORMATS SNDRV_PCM_FMTBIT_S16_LE +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +/* Support unconventional sample rates 12000, 24000 as well */ +#define USE_RATE \ + (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) +#define USE_RATE_MIN 8000 +#define USE_RATE_MAX 48000 +#define MAX_BUFFER_PLAYBACK_SIZE \ + (4800*4) +/* 2048 frames (Mono), 1024 frames (Stereo) */ +#define CAPTURE_SIZE 4096 +#define MAX_BUFFER_CAPTURE_SIZE (4096*4) +#define MAX_PERIOD_SIZE BUFSZ +#define USE_PERIODS_MAX 1024 +#define USE_PERIODS_MIN 1 + + +#define MAX_DB (16) +#define MIN_DB (-50) +#define PCMPLAYBACK_DECODERID 5 + +/* 0xFFFFFFFF Indicates not to be used for audio data copy */ +#define BUF_INVALID_LEN 0xFFFFFFFF + +extern int copy_count; +extern int intcnt; + +struct msm_volume { + bool update; + int volume; /* Volume parameter, in dB Scale */ + int pan; +}; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + struct mutex lock; + struct mutex write_lock; + struct mutex read_lock; + spinlock_t read_dsp_lock; + spinlock_t write_dsp_lock; + spinlock_t mixer_lock; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t eos_wait; +}; + +extern struct audio_locks the_locks; + +struct msm_audio_event_callbacks { + /* event is called from interrupt context when a message + * arrives from the DSP. + */ + void (*playback)(void *); + void (*capture)(void *); +}; + + +struct msm_audio { + struct buffer out[2]; + struct buffer_rec in[8]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + atomic_t out_bytes; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_weight; + uint32_t out_buffer_size; + + struct audmgr audmgr; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int pcm_buf_pos; /* position in buffer */ + + struct msm_adsp_module *audpre; + struct msm_adsp_module *audrec; + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* 2048 for mono, 4096 for stereo */ + uint32_t type; /* 0 for PCM ,1 for AAC */ + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + unsigned short samp_rate_index; + + /* audpre settings */ + audpreproc_cmd_cfg_agc_params tx_agc_cfg; + audpreproc_cmd_cfg_ns_params ns_cfg; + /* For different sample rate, the coeff might be different. * + * All the coeff should be passed from user space */ + audpreproc_cmd_cfg_iir_tuning_filter_params iir_cfg; + + struct msm_audio_event_callbacks *ops; + + int dir; + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int eos_ack; +}; + + + +/* platform data */ +extern int audio_dsp_out_enable(struct msm_audio *prtd, int yes); +extern struct snd_soc_platform_driver msm_soc_platform; + +int audrec_encoder_config(struct msm_audio *prtd); +extern void alsa_get_dsp_frames(struct msm_audio *prtd); +extern int alsa_rec_dsp_enable(struct msm_audio *prtd, int enable); +extern int alsa_audrec_disable(struct msm_audio *prtd); +extern int alsa_audio_configure(struct msm_audio *prtd); +extern int alsa_audio_disable(struct msm_audio *prtd); +extern int alsa_adsp_configure(struct msm_audio *prtd); +extern int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos); +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos); +int msm_audio_volume_update(unsigned id, + int volume, int pan); +extern struct audio_locks the_locks; +extern struct msm_volume msm_vol_ctl; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm-voip.c b/sound/soc/msm/msm-voip.c new file mode 100644 index 00000000000..082c840f9b7 --- /dev/null +++ b/sound/soc/msm/msm-voip.c @@ -0,0 +1,610 @@ +/* Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_audio_mvs.h" + + +static struct audio_voip_info_type audio_voip_info; +static void audio_mvs_process_ul_pkt(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data); +static void audio_mvs_process_dl_pkt(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data); + +struct msm_audio_mvs_frame { + uint32_t frame_type; + uint32_t len; + uint8_t voc_pkt[MVS_MAX_VOC_PKT_SIZE]; +}; + +struct audio_mvs_buf_node { + struct list_head list; + struct msm_audio_mvs_frame frame; +}; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN, + .period_bytes_min = MVS_MAX_VOC_PKT_SIZE, + .period_bytes_max = MVS_MAX_VOC_PKT_SIZE, + .periods_min = VOIP_MAX_Q_LEN, + .periods_max = VOIP_MAX_Q_LEN, + .fifo_size = 0, +}; + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + + struct audio_voip_info_type *audio = &audio_voip_info; + pr_debug("%s\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_start = 1; + else + audio->capture_start = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_start = 0; + else + audio->capture_start = 0; + break; + default: + break; + } + return 0; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct audio_voip_info_type *audio = &audio_voip_info; + struct audio_mvs_release_msg release_msg; + + pr_debug("%s\n", __func__); + memset(&release_msg, 0, sizeof(release_msg)); + mutex_lock(&audio->lock); + + audio->instance--; + wake_up(&audio->out_wait); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio->playback_state = AUDIO_MVS_CLOSED; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + audio->capture_state = AUDIO_MVS_CLOSED; + if (!audio->instance) { + /* Release MVS. */ + release_msg.client_id = cpu_to_be32(MVS_CLIENT_ID_VOIP); + /* Derigstering the callbacks with voice driver */ + voice_register_mvs_cb(NULL, NULL, audio); + } else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + voice_register_mvs_cb(audio_mvs_process_ul_pkt, + NULL, audio); + } else { + voice_register_mvs_cb(NULL, audio_mvs_process_dl_pkt, + audio); + } + + mutex_unlock(&audio->lock); + + wake_unlock(&audio->suspend_lock); + wake_unlock(&audio->idle_lock); + /* Release the IO buffers. */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + audio->in_write = 0; + audio->in_read = 0; + memset(audio->in[0].voc_pkt, 0, + MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN); + audio->playback_substream = NULL; + } else { + audio->out_write = 0; + audio->out_read = 0; + memset(audio->out[0].voc_pkt, 0, + MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN); + audio->capture_substream = NULL; + } + return rc; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + + pr_debug("%s\n", __func__); + mutex_lock(&audio->lock); + + if (audio->playback_substream == NULL || + audio->capture_substream == NULL) { + if (substream->stream == + SNDRV_PCM_STREAM_PLAYBACK) { + audio->playback_substream = substream; + runtime->hw = msm_pcm_hardware; + audio_voip_info.in_read = 0; + audio_voip_info.in_write = 0; + if (audio->playback_state < AUDIO_MVS_OPENED) + audio->playback_state = AUDIO_MVS_OPENED; + } else if (substream->stream == + SNDRV_PCM_STREAM_CAPTURE) { + audio->capture_substream = substream; + runtime->hw = msm_pcm_hardware; + audio_voip_info.out_read = 0; + audio_voip_info.out_write = 0; + if (audio->capture_state < AUDIO_MVS_OPENED) + audio->capture_state = AUDIO_MVS_OPENED; + } + } else { + ret = -EPERM; + goto err; + } + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_debug("%s:snd_pcm_hw_constraint_integer failed\n", __func__); + goto err; + } + audio->instance++; + +err: + mutex_unlock(&audio->lock); + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int rc = 0; + int count = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + uint32_t index; + pr_debug("%s\n", __func__); + + rc = wait_event_timeout(audio->in_wait, + (audio->in_write - audio->in_read <= VOIP_MAX_Q_LEN-1), + 1 * HZ); + if (rc < 0) { + pr_debug("%s: write was interrupted\n", __func__); + return -ERESTARTSYS; + } + + if (audio->playback_state == AUDIO_MVS_ENABLED) { + index = audio->in_write % VOIP_MAX_Q_LEN; + count = frames_to_bytes(runtime, frames); + if (count == MVS_MAX_VOC_PKT_SIZE) { + pr_debug("%s:write index = %d\n", __func__, index); + rc = copy_from_user(audio->in[index].voc_pkt, buf, + count); + if (!rc) { + audio->in[index].len = count; + audio->in_write++; + } else { + pr_debug("%s:Copy from user returned %d\n", + __func__, rc); + rc = -EFAULT; + } + } else + rc = -ENOMEM; + + } else { + pr_debug("%s:Write performed in invalid state %d\n", + __func__, audio->playback_state); + rc = -EINVAL; + } + return rc; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, + void __user *buf, snd_pcm_uframes_t frames) +{ + int rc = 0; + int count = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + uint32_t index = 0; + + pr_debug("%s\n", __func__); + + /* Ensure the driver has been enabled. */ + if (audio->capture_state != AUDIO_MVS_ENABLED) { + pr_debug("%s:Read performed in invalid state %d\n", + __func__, audio->capture_state); + return -EPERM; + } + rc = wait_event_timeout(audio->out_wait, + ((audio->out_read < audio->out_write) || + (audio->capture_state == AUDIO_MVS_CLOSING) || + (audio->capture_state == AUDIO_MVS_CLOSED)), + 1 * HZ); + + if (rc < 0) { + pr_debug("%s: Read was interrupted\n", __func__); + return -ERESTARTSYS; + } + + if (audio->capture_state == AUDIO_MVS_CLOSING + || audio->capture_state == AUDIO_MVS_CLOSED) { + pr_debug("%s:EBUSY STATE\n", __func__); + rc = -EBUSY; + } else { + count = frames_to_bytes(runtime, frames); + index = audio->out_read % VOIP_MAX_Q_LEN; + pr_debug("%s:index=%d\n", __func__, index); + if (audio->out[index].len <= count) { + rc = copy_to_user(buf, + audio->out[index].voc_pkt, + audio->out[index].len); + if (rc) { + pr_debug("%s:Copy to user %d\n", + __func__, rc); + rc = -EFAULT; + } else + audio->out_read++; + } else { + pr_debug("%s:returning ENOMEM\n", __func__); + rc = -ENOMEM; + } + } + return rc; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + pr_debug("%s\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +/* Capture path */ +static void audio_mvs_process_ul_pkt(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data) +{ + struct audio_voip_info_type *audio = private_data; + uint32_t index; + static int i; + pr_debug("%s\n", __func__); + + if (audio->capture_substream == NULL) + return; + index = audio->out_write % VOIP_MAX_Q_LEN; + memcpy(audio->out[index].voc_pkt, voc_pkt, pkt_len); + audio->out[index].len = pkt_len; + audio->out_write++; + wake_up(&audio->out_wait); + i++; + if (audio->capture_start) { + audio->pcm_capture_irq_pos += audio->pcm_count; + if (!(i % 2)) + snd_pcm_period_elapsed(audio->capture_substream); + } +} + +/* Playback path */ +static void audio_mvs_process_dl_pkt(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data) +{ + struct audio_voip_info_type *audio = private_data; + uint32_t index; + static int i; + pr_debug("%s\n", __func__); + + if (audio->playback_substream == NULL) + return; + if ((audio->in_write - audio->in_read >= 0) + && (audio->playback_start)) { + index = audio->in_read % VOIP_MAX_Q_LEN; + *pkt_len = audio->pcm_count; + memcpy(voc_pkt, audio->in[index].voc_pkt, *pkt_len); + audio->in_read++; + wake_up(&audio->in_wait); + i++; + audio->pcm_playback_irq_pos += audio->pcm_count; + if (!(i%2)) + snd_pcm_period_elapsed(audio->playback_substream); + pr_debug("%s:read_index=%d\n", __func__, index); + } +} + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int rc = 0; + struct audio_voip_info_type *prtd = &audio_voip_info; + pr_debug("%s\n", __func__); + prtd->pcm_playback_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + pr_debug("%s:prtd->pcm_playback_size:%d\n", + __func__, prtd->pcm_playback_size); + pr_debug("%s:prtd->pcm_count:%d\n", __func__, prtd->pcm_count); + + mutex_lock(&prtd->prepare_lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (prtd->playback_state == AUDIO_MVS_ENABLED) + goto enabled; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (prtd->capture_state == AUDIO_MVS_ENABLED) + goto enabled; + } + + pr_debug("%s:Register cbs with voice driver check audio_mvs_driver\n", + __func__); + if (prtd->instance == 2) { + voice_register_mvs_cb(audio_mvs_process_ul_pkt, + audio_mvs_process_dl_pkt, + prtd); + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + voice_register_mvs_cb(NULL, + audio_mvs_process_dl_pkt, + prtd); + } else { + voice_register_mvs_cb(audio_mvs_process_ul_pkt, + NULL, + prtd); + } + } + +enabled: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->playback_state = AUDIO_MVS_ENABLED; + prtd->pcm_playback_irq_pos = 0; + prtd->pcm_playback_buf_pos = 0; + /* rate and channels are sent to audio driver */ + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + prtd->capture_state = AUDIO_MVS_ENABLED; + prtd->pcm_capture_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_capture_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_capture_irq_pos = 0; + prtd->pcm_capture_buf_pos = 0; + } + mutex_unlock(&prtd->prepare_lock); + return rc; +} + +static snd_pcm_uframes_t +msm_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + + if (audio->pcm_playback_irq_pos >= audio->pcm_playback_size) + audio->pcm_playback_irq_pos = 0; + return bytes_to_frames(runtime, (audio->pcm_playback_irq_pos)); +} + +static snd_pcm_uframes_t +msm_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_voip_info_type *audio = &audio_voip_info; + + if (audio->pcm_capture_irq_pos >= audio->pcm_capture_size) + audio->pcm_capture_irq_pos = 0; + return bytes_to_frames(runtime, (audio->pcm_capture_irq_pos)); +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t ret = 0; + pr_debug("%s\n", __func__); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_pointer(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_pointer(substream); + return ret; +} + +static struct snd_pcm_ops msm_mvs_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + +}; + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int i, ret, offset = 0; + struct snd_pcm_substream *substream = NULL; + struct snd_dma_buffer *dma_buffer = NULL; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_mvs_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_mvs_pcm_ops); + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (!substream) + return -ENOMEM; + + dma_buffer = &substream->dma_buffer; + dma_buffer->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buffer->dev.dev = card->dev; + dma_buffer->private_data = NULL; + dma_buffer->area = dma_alloc_coherent(card->dev, + (MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN), + &dma_buffer->addr, GFP_KERNEL); + if (!dma_buffer->area) { + pr_err("%s:MSM VOIP dma_alloc failed\n", __func__); + return -ENOMEM; + } + dma_buffer->bytes = MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN; + memset(dma_buffer->area, 0, MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN); + audio_voip_info.in_read = 0; + audio_voip_info.in_write = 0; + audio_voip_info.out_read = 0; + audio_voip_info.out_write = 0; + for (i = 0; i < VOIP_MAX_Q_LEN; i++) { + audio_voip_info.in[i].voc_pkt = + dma_buffer->area + offset; + offset = offset + MVS_MAX_VOC_PKT_SIZE; + } + substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (!substream) + return -ENOMEM; + + dma_buffer = &substream->dma_buffer; + dma_buffer->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buffer->dev.dev = card->dev; + dma_buffer->private_data = NULL; + dma_buffer->area = dma_alloc_coherent(card->dev, + (MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN), + &dma_buffer->addr, GFP_KERNEL); + if (!dma_buffer->area) { + pr_err("%s:MSM VOIP dma_alloc failed\n", __func__); + return -ENOMEM; + } + memset(dma_buffer->area, 0, MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN); + dma_buffer->bytes = MVS_MAX_VOC_PKT_SIZE * VOIP_MAX_Q_LEN; + for (i = 0; i < VOIP_MAX_Q_LEN; i++) { + audio_voip_info.out[i].voc_pkt = + dma_buffer->area + offset; + offset = offset + MVS_MAX_VOC_PKT_SIZE; + } + audio_voip_info.playback_substream = NULL; + audio_voip_info.capture_substream = NULL; + + return 0; +} + +static void msm_pcm_free_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +struct snd_soc_platform_driver msm_mvs_soc_platform = { + .ops = &msm_mvs_pcm_ops, + .pcm_new = msm_pcm_new, + .pcm_free = msm_pcm_free_buffers, +}; +EXPORT_SYMBOL(msm_mvs_soc_platform); + +static __devinit int msm_pcm_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_mvs_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-mvs-audio", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = __devexit_p(msm_pcm_remove), +}; + +static int __init msm_mvs_soc_platform_init(void) +{ + memset(&audio_voip_info, 0, sizeof(audio_voip_info)); + mutex_init(&audio_voip_info.lock); + mutex_init(&audio_voip_info.prepare_lock); + init_waitqueue_head(&audio_voip_info.out_wait); + init_waitqueue_head(&audio_voip_info.in_wait); + wake_lock_init(&audio_voip_info.suspend_lock, WAKE_LOCK_SUSPEND, + "audio_mvs_suspend"); + wake_lock_init(&audio_voip_info.idle_lock, WAKE_LOCK_IDLE, + "audio_mvs_idle"); + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_mvs_soc_platform_init); + +static void __exit msm_mvs_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_mvs_soc_platform_exit); + +MODULE_DESCRIPTION("MVS PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7201.c b/sound/soc/msm/msm7201.c new file mode 100644 index 00000000000..061529a04f8 --- /dev/null +++ b/sound/soc/msm/msm7201.c @@ -0,0 +1,329 @@ +/* linux/sound/soc/msm/msm7201.c + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm.h" +#include +#include + +static struct msm_rpc_endpoint *snd_ep; + +struct msm_snd_rpc_ids { + unsigned long prog; + unsigned long vers; + unsigned long rpc_set_snd_device; + int device; +}; + +static struct msm_snd_rpc_ids snd_rpc_ids; + +static struct platform_device *msm_audio_snd_device; + +static int snd_msm_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; /* Volume Param, in dB */ + uinfo->value.integer.min = MIN_DB; + uinfo->value.integer.max = MAX_DB; + return 0; +} + +static int snd_msm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + spin_lock_irq(&the_locks.mixer_lock); + ucontrol->value.integer.value[0] = msm_vol_ctl.volume; + spin_unlock_irq(&the_locks.mixer_lock); + return 0; +} + +static int snd_msm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + int volume; + + volume = ucontrol->value.integer.value[0]; + spin_lock_irq(&the_locks.mixer_lock); + change = (msm_vol_ctl.volume != volume); + if (change) { + msm_vol_ctl.update = 1; + msm_vol_ctl.volume = volume; + } + spin_unlock_irq(&the_locks.mixer_lock); + return change; +} + +static int snd_msm_device_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; /* Device */ + + /* + * The number of devices supported is 26 (0 to 25) + */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 25; + return 0; +} + +static int snd_msm_device_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = (uint32_t)snd_rpc_ids.device; + return 0; +} + +int msm_snd_init_rpc_ids(void) +{ + snd_rpc_ids.prog = 0x30000002; + snd_rpc_ids.vers = 0x00020001; + /* + * The magic number 2 corresponds to the rpc call + * index for snd_set_device + */ + snd_rpc_ids.rpc_set_snd_device = 2; + return 0; +} + +int msm_snd_rpc_connect(void) +{ + if (snd_ep) { + printk(KERN_INFO "%s: snd_ep already connected\n", __func__); + return 0; + } + + /* Initialize rpc ids */ + if (msm_snd_init_rpc_ids()) { + printk(KERN_ERR "%s: snd rpc ids initialization failed\n" + , __func__); + return -ENODATA; + } + + snd_ep = msm_rpc_connect_compatible(snd_rpc_ids.prog, + snd_rpc_ids.vers, 0); + if (IS_ERR(snd_ep)) { + printk(KERN_ERR "%s: failed (compatible VERS = %ld)\n", + __func__, snd_rpc_ids.vers); + snd_ep = NULL; + return -EAGAIN; + } + return 0; +} + +int msm_snd_rpc_close(void) +{ + int rc = 0; + + if (IS_ERR(snd_ep)) { + printk(KERN_ERR "%s: snd handle unavailable, rc = %ld\n", + __func__, PTR_ERR(snd_ep)); + return -EAGAIN; + } + + rc = msm_rpc_close(snd_ep); + snd_ep = NULL; + + if (rc < 0) { + printk(KERN_ERR "%s: close rpc failed! rc = %d\n", + __func__, rc); + return -EAGAIN; + } else + printk(KERN_INFO "rpc close success\n"); + + return rc; +} + +static int snd_msm_device_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct snd_start_req { + struct rpc_request_hdr hdr; + uint32_t rpc_snd_device; + uint32_t snd_mute_ear_mute; + uint32_t snd_mute_mic_mute; + uint32_t callback_ptr; + uint32_t client_data; + } req; + + snd_rpc_ids.device = (int)ucontrol->value.integer.value[0]; + req.hdr.type = 0; + req.hdr.rpc_vers = 2; + + req.rpc_snd_device = cpu_to_be32(snd_rpc_ids.device); + req.snd_mute_ear_mute = cpu_to_be32(1); + req.snd_mute_mic_mute = cpu_to_be32(0); + req.callback_ptr = -1; + req.client_data = cpu_to_be32(0); + + req.hdr.prog = snd_rpc_ids.prog; + req.hdr.vers = snd_rpc_ids.vers; + + rc = msm_rpc_call(snd_ep, snd_rpc_ids.rpc_set_snd_device , + &req, sizeof(req), 5 * HZ); + + if (rc < 0) { + printk(KERN_ERR "%s: snd rpc call failed! rc = %d\n", + __func__, rc); + } else + printk(KERN_INFO "snd device connected \n"); + + return rc; +} + +/* Supported range -50dB to 18dB */ +static const DECLARE_TLV_DB_LINEAR(db_scale_linear, -5000, 1800); + +#define MSM_EXT(xname, xindex, fp_info, fp_get, fp_put, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, .index = xindex, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ +} + +#define MSM_EXT_TLV(xname, xindex, fp_info, fp_get, fp_put, addr, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = (SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE), \ + .name = xname, .index = xindex, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, .tlv.p = tlv_array, \ + .private_value = addr, \ +} + +static struct snd_kcontrol_new snd_msm_controls[] = { + MSM_EXT_TLV("PCM Playback Volume", 0, snd_msm_volume_info, \ + snd_msm_volume_get, snd_msm_volume_put, 0, db_scale_linear), + MSM_EXT("device", 1, snd_msm_device_info, snd_msm_device_get, \ + snd_msm_device_put, 0), +}; + +static int msm_new_mixer(struct snd_soc_codec *codec) +{ + unsigned int idx; + int err; + + pr_err("msm_soc: ALSA MSM Mixer Setting\n"); + strcpy(codec->card->snd_card->mixername, "MSM Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_msm_controls); idx++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_msm_controls[idx], NULL)); + if (err < 0) + return err; + } + return 0; +} + +static int msm_soc_dai_init( + struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_soc_codec *codec = rtd->codec; + + mutex_init(&the_locks.lock); + mutex_init(&the_locks.write_lock); + mutex_init(&the_locks.read_lock); + spin_lock_init(&the_locks.read_dsp_lock); + spin_lock_init(&the_locks.write_dsp_lock); + spin_lock_init(&the_locks.mixer_lock); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + msm_vol_ctl.volume = MSM_PLAYBACK_DEFAULT_VOLUME; + msm_vol_ctl.pan = MSM_PLAYBACK_DEFAULT_PAN; + + ret = msm_new_mixer(codec); + if (ret < 0) { + pr_err("msm_soc: ALSA MSM Mixer Fail\n"); + } + + return ret; +} + +static struct snd_soc_dai_link msm_dai[] = { +{ + .name = "MSM Primary I2S", + .stream_name = "DSP 1", + .cpu_dai_name = "msm-cpu-dai.0", + .platform_name = "msm-dsp-audio.0", + .codec_name = "msm-codec-dai.0", + .codec_dai_name = "msm-codec-dai", + .init = &msm_soc_dai_init, +}, +}; + +static struct snd_soc_card snd_soc_card_msm = { + .name = "msm-audio", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), +}; + +static int __init msm_audio_init(void) +{ + int ret; + + msm_audio_snd_device = platform_device_alloc("soc-audio", -1); + if (!msm_audio_snd_device) + return -ENOMEM; + + platform_set_drvdata(msm_audio_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_audio_snd_device); + if (ret) { + platform_device_put(msm_audio_snd_device); + return ret; + } + + ret = msm_snd_rpc_connect(); + + return ret; +} + +static void __exit msm_audio_exit(void) +{ + msm_snd_rpc_close(); + platform_device_unregister(msm_audio_snd_device); +} + +module_init(msm_audio_init); +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("PCM module"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7k-pcm.c b/sound/soc/msm/msm7k-pcm.c new file mode 100644 index 00000000000..1f36e3b19c0 --- /dev/null +++ b/sound/soc/msm/msm7k-pcm.c @@ -0,0 +1,600 @@ +/* linux/sound/soc/msm/msm7k-pcm.c + * + * Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org. + */ + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm-pcm.h" + +#define SND_DRIVER "snd_msm" +#define MAX_PCM_DEVICES SNDRV_CARDS +#define MAX_PCM_SUBSTREAMS 1 + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +int copy_count; + +struct audio_locks the_locks; +EXPORT_SYMBOL(the_locks); +struct msm_volume msm_vol_ctl; +EXPORT_SYMBOL(msm_vol_ctl); + + +static unsigned convert_dsp_samp_index(unsigned index) +{ + switch (index) { + case 48000: + return AUDREC_CMD_SAMP_RATE_INDX_48000; + case 44100: + return AUDREC_CMD_SAMP_RATE_INDX_44100; + case 32000: + return AUDREC_CMD_SAMP_RATE_INDX_32000; + case 24000: + return AUDREC_CMD_SAMP_RATE_INDX_24000; + case 22050: + return AUDREC_CMD_SAMP_RATE_INDX_22050; + case 16000: + return AUDREC_CMD_SAMP_RATE_INDX_16000; + case 12000: + return AUDREC_CMD_SAMP_RATE_INDX_12000; + case 11025: + return AUDREC_CMD_SAMP_RATE_INDX_11025; + case 8000: + return AUDREC_CMD_SAMP_RATE_INDX_8000; + default: + return AUDREC_CMD_SAMP_RATE_INDX_44100; + } +} + +static unsigned convert_samp_rate(unsigned hz) +{ + switch (hz) { + case 48000: + return RPC_AUD_DEF_SAMPLE_RATE_48000; + case 44100: + return RPC_AUD_DEF_SAMPLE_RATE_44100; + case 32000: + return RPC_AUD_DEF_SAMPLE_RATE_32000; + case 24000: + return RPC_AUD_DEF_SAMPLE_RATE_24000; + case 22050: + return RPC_AUD_DEF_SAMPLE_RATE_22050; + case 16000: + return RPC_AUD_DEF_SAMPLE_RATE_16000; + case 12000: + return RPC_AUD_DEF_SAMPLE_RATE_12000; + case 11025: + return RPC_AUD_DEF_SAMPLE_RATE_11025; + case 8000: + return RPC_AUD_DEF_SAMPLE_RATE_8000; + default: + return RPC_AUD_DEF_SAMPLE_RATE_44100; + } +} + +static struct snd_pcm_hardware msm_pcm_playback_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_PLAYBACK_SIZE, + .period_bytes_min = 64, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_capture_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .buffer_bytes_max = MAX_BUFFER_CAPTURE_SIZE, + .period_bytes_min = CAPTURE_SIZE, + .period_bytes_max = CAPTURE_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void playback_event_handler(void *data) +{ + struct msm_audio *prtd = data; + snd_pcm_period_elapsed(prtd->playback_substream); +} + +static void capture_event_handler(void *data) +{ + struct msm_audio *prtd = data; + snd_pcm_period_elapsed(prtd->capture_substream); +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->out_sample_rate = runtime->rate; + prtd->out_channel_mode = runtime->channels; + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct audmgr_config cfg; + int rc; + + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = convert_samp_rate(runtime->rate); + prtd->samp_rate_index = convert_dsp_samp_index(runtime->rate); + prtd->channel_mode = (runtime->channels - 1); + prtd->buffer_size = prtd->channel_mode ? STEREO_DATA_SIZE : \ + MONO_DATA_SIZE; + + if (prtd->enabled == 1) + return 0; + + prtd->type = AUDREC_CMD_TYPE_0_INDEX_WAV; + + cfg.tx_rate = convert_samp_rate(runtime->rate); + cfg.rx_rate = RPC_AUD_DEF_SAMPLE_RATE_NONE; + cfg.def_method = RPC_AUD_DEF_METHOD_RECORD; + cfg.codec = RPC_AUD_DEF_CODEC_PCM; + cfg.snd_method = RPC_SND_METHOD_MIDI; + + rc = audmgr_enable(&prtd->audmgr, &cfg); + if (rc < 0) + return rc; + + if (msm_adsp_enable(prtd->audpre)) { + audmgr_disable(&prtd->audmgr); + return -ENODEV; + } + if (msm_adsp_enable(prtd->audrec)) { + msm_adsp_disable(prtd->audpre); + audmgr_disable(&prtd->audmgr); + return -ENODEV; + } + prtd->enabled = 1; + alsa_rec_dsp_enable(prtd, 1); + + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t +msm_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos == prtd->pcm_size) + prtd->pcm_irq_pos = 0; + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int rc = 0, rc1 = 0, rc2 = 0; + int fbytes = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + int monofbytes = 0; + char *bufferp = NULL; + + fbytes = frames_to_bytes(runtime, frames); + monofbytes = fbytes / 2; + if (runtime->channels == 2) { + rc = alsa_buffer_read(prtd, buf, fbytes, NULL); + } else { + bufferp = buf; + rc1 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + bufferp = buf + monofbytes ; + rc2 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + rc = rc1 + rc2; + } + prtd->pcm_buf_pos += fbytes; + return rc; +} + +static snd_pcm_uframes_t +msm_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + alsa_audrec_disable(prtd); + audmgr_close(&prtd->audmgr); + msm_adsp_put(prtd->audrec); + msm_adsp_put(prtd->audpre); + kfree(prtd); + + return 0; +} + +struct msm_audio_event_callbacks snd_msm_audio_ops = { + .playback = playback_event_handler, + .capture = capture_event_handler, +}; + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd; + int ret = 0; + + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + ret = -ENOMEM; + return ret; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msm_vol_ctl.update = 1; /* Update Volume, with Cached value */ + runtime->hw = msm_pcm_playback_hardware; + prtd->dir = SNDRV_PCM_STREAM_PLAYBACK; + prtd->playback_substream = substream; + prtd->eos_ack = 0; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = msm_pcm_capture_hardware; + prtd->dir = SNDRV_PCM_STREAM_CAPTURE; + prtd->capture_substream = substream; + } + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + goto out; + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + prtd->ops = &snd_msm_audio_ops; + prtd->out[0].used = BUF_INVALID_LEN; + prtd->out_head = 1; /* point to second buffer on startup */ + runtime->private_data = prtd; + + ret = alsa_adsp_configure(prtd); + if (ret) + goto out; + copy_count = 0; + return 0; + + out: + kfree(prtd); + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int rc = 1; + int fbytes = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + rc = alsa_send_buffer(prtd, buf, fbytes, NULL); + ++copy_count; + prtd->pcm_buf_pos += fbytes; + if (copy_count == 1) { + mutex_lock(&the_locks.lock); + alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + } + if ((prtd->running) && (msm_vol_ctl.update)) { + rc = msm_audio_volume_update(PCMPLAYBACK_DECODERID, + msm_vol_ctl.volume, msm_vol_ctl.pan); + msm_vol_ctl.update = 0; + } + + return rc; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + int rc = 0; + + pr_debug("%s()\n", __func__); + + /* pcm dmamiss message is sent continously + * when decoder is starved so no race + * condition concern + */ + if (prtd->enabled) + rc = wait_event_interruptible(the_locks.eos_wait, + prtd->eos_ack); + + alsa_audio_disable(prtd); + audmgr_close(&prtd->audmgr); + kfree(prtd); + + return 0; +} + + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_pointer(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_pointer(substream); + return ret; +} + +int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + if (substream->pcm->device & 1) { + runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED; + runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED; + } + return 0; + +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, +}; + +static int pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + if (!stream) + size = PLAYBACK_DMASZ; + else + size = CAPTURE_DMASZ; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void msm_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_pcm_ops); + + ret = pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + + ret = pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); + if (ret) + msm_pcm_free_dma_buffers(pcm); + return ret; +} + +struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_pcm_new, + .pcm_free = msm_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL(msm_soc_platform); + +static __devinit int msm_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-dsp-audio", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = __devexit_p(msm_pcm_remove), +}; + +static int __init msm_soc_platform_init(void) +{ + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7kv2-dai.c b/sound/soc/msm/msm7kv2-dai.c new file mode 100644 index 00000000000..e8d51ac9552 --- /dev/null +++ b/sound/soc/msm/msm7kv2-dai.c @@ -0,0 +1,149 @@ +/* sound/soc/msm/msm-dai.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * + * Derived from msm-pcm.c and msm7201.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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm7kv2-pcm.h" + +static struct snd_soc_dai_driver msm_pcm_codec_dais[] = { +{ + .name = "msm-codec-dai", + .playback = { + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; +static struct snd_soc_dai_driver msm_pcm_cpu_dais[] = { +{ + .name = "msm-cpu-dai", + .playback = { + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = USE_CHANNELS_MAX, + .rate_min = USE_RATE_MIN, + .rate_max = USE_RATE_MAX, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static struct snd_soc_codec_driver soc_codec_dev_msm = { + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + +static __devinit int asoc_msm_codec_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_msm, + msm_pcm_codec_dais, ARRAY_SIZE(msm_pcm_codec_dais)); +} + +static int __devexit asoc_msm_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static __devinit int asoc_msm_cpu_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_dai(&pdev->dev, msm_pcm_cpu_dais); +} + +static int __devexit asoc_msm_cpu_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver asoc_msm_codec_driver = { + .probe = asoc_msm_codec_probe, + .remove = __devexit_p(asoc_msm_codec_remove), + .driver = { + .name = "msm-codec-dai", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver asoc_msm_cpu_driver = { + .probe = asoc_msm_cpu_probe, + .remove = __devexit_p(asoc_msm_cpu_remove), + .driver = { + .name = "msm-cpu-dai", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_codec_dai_init(void) +{ + return platform_driver_register(&asoc_msm_codec_driver); +} + +static void __exit msm_codec_dai_exit(void) +{ + platform_driver_unregister(&asoc_msm_codec_driver); +} + +static int __init msm_cpu_dai_init(void) +{ + return platform_driver_register(&asoc_msm_cpu_driver); +} + +static void __exit msm_cpu_dai_exit(void) +{ + platform_driver_unregister(&asoc_msm_cpu_driver); +} + +module_init(msm_codec_dai_init); +module_exit(msm_codec_dai_exit); +module_init(msm_cpu_dai_init); +module_exit(msm_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Codec/Cpu Dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7kv2-dsp.c b/sound/soc/msm/msm7kv2-dsp.c new file mode 100644 index 00000000000..50bf6fb0e85 --- /dev/null +++ b/sound/soc/msm/msm7kv2-dsp.c @@ -0,0 +1,633 @@ +/* Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2010, Code Aurora Forum. 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm7kv2-pcm.h" + +/* Audrec Queue command sent macro's */ +#define audrec_send_bitstreamqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, ((audio->queue_id & 0xFFFF0000) >> 16),\ + cmd, len) + +#define audrec_send_audrecqueue(audio, cmd, len) \ + msm_adsp_write(audio->audrec, (audio->queue_id & 0x0000FFFF),\ + cmd, len) + +static int alsa_dsp_read_buffer(struct msm_audio *audio, + uint32_t read_cnt); +static void alsa_get_dsp_frames(struct msm_audio *prtd); +static int alsa_in_param_config(struct msm_audio *audio); + +static int alsa_in_mem_config(struct msm_audio *audio); +static int alsa_in_enc_config(struct msm_audio *audio, int enable); + +int intcnt; +struct audio_frame { + uint16_t count_low; + uint16_t count_high; + uint16_t bytes; + uint16_t unknown; + unsigned char samples[]; +} __attribute__ ((packed)); + +void alsa_dsp_event(void *data, unsigned id, uint16_t *msg) +{ + struct msm_audio *prtd = data; + struct buffer *frame; + unsigned long flag = 0; + + MM_DBG("\n"); + switch (id) { + case AUDPP_MSG_HOST_PCM_INTF_MSG: { + unsigned id = msg[3]; + unsigned idx = msg[4] - 1; + + MM_DBG("HOST_PCM id %d idx %d\n", id, idx); + if (id != AUDPP_MSG_HOSTPCM_ID_ARM_RX) { + MM_ERR("bogus id\n"); + break; + } + if (idx > 1) { + MM_ERR("bogus buffer idx\n"); + break; + } + + /* Update with actual sent buffer size */ + if (prtd->out[idx].used != BUF_INVALID_LEN) + prtd->pcm_irq_pos += prtd->out[idx].used; + + if (prtd->pcm_irq_pos > prtd->pcm_size) + prtd->pcm_irq_pos = prtd->pcm_count; + + if (prtd->ops->playback) + prtd->ops->playback(prtd); + + if (prtd->mmap_flag) + break; + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + if (prtd->running) { + prtd->out[idx].used = 0; + frame = prtd->out + prtd->out_tail; + if (frame->used) { + alsa_dsp_send_buffer( + prtd, prtd->out_tail, frame->used); + /* Reset eos_ack flag to avoid stale + * PCMDMAMISS been considered + */ + prtd->eos_ack = 0; + prtd->out_tail ^= 1; + } else { + prtd->out_needed++; + } + wake_up(&the_locks.write_wait); + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + break; + } + case AUDPP_MSG_PCMDMAMISSED: + MM_INFO("PCMDMAMISSED %d\n", msg[0]); + prtd->eos_ack++; + MM_DBG("PCMDMAMISSED Count per Buffer %d\n", prtd->eos_ack); + wake_up(&the_locks.eos_wait); + break; + case AUDPP_MSG_CFG_MSG: + if (msg[0] == AUDPP_MSG_ENA_ENA) { + MM_DBG("CFG_MSG ENABLE\n"); + prtd->out_needed = 0; + prtd->running = 1; + audpp_dsp_set_vol_pan(prtd->session_id, &prtd->vol_pan, + POPP); + audpp_route_stream(prtd->session_id, + msm_snddev_route_dec(prtd->session_id)); + audio_dsp_out_enable(prtd, 1); + } else if (msg[0] == AUDPP_MSG_ENA_DIS) { + MM_DBG("CFG_MSG DISABLE\n"); + prtd->running = 0; + } else { + MM_DBG("CFG_MSG %d?\n", msg[0]); + } + break; + default: + MM_DBG("UNKNOWN (%d)\n", id); + } +} + +static void audpreproc_dsp_event(void *data, unsigned id, void *msg) +{ + struct msm_audio *prtd = data; + + switch (id) { + case AUDPREPROC_ERROR_MSG: { + struct audpreproc_err_msg *err_msg = msg; + + MM_ERR("ERROR_MSG: stream id %d err idx %d\n", + err_msg->stream_id, err_msg->aud_preproc_err_idx); + /* Error case */ + break; + } + case AUDPREPROC_CMD_CFG_DONE_MSG: { + MM_DBG("CMD_CFG_DONE_MSG\n"); + break; + } + case AUDPREPROC_CMD_ENC_CFG_DONE_MSG: { + struct audpreproc_cmd_enc_cfg_done_msg *enc_cfg_msg = msg; + + MM_DBG("CMD_ENC_CFG_DONE_MSG: stream id %d enc type \ + 0x%8x\n", enc_cfg_msg->stream_id, + enc_cfg_msg->rec_enc_type); + /* Encoder enable success */ + if (enc_cfg_msg->rec_enc_type & ENCODE_ENABLE) + alsa_in_param_config(prtd); + else { /* Encoder disable success */ + prtd->running = 0; + alsa_in_record_config(prtd, 0); + } + break; + } + case AUDPREPROC_CMD_ENC_PARAM_CFG_DONE_MSG: { + MM_DBG("CMD_ENC_PARAM_CFG_DONE_MSG\n"); + alsa_in_mem_config(prtd); + break; + } + case AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG: { + MM_DBG("AFE_CMD_AUDIO_RECORD_CFG_DONE_MSG\n"); + wake_up(&the_locks.enable_wait); + break; + } + default: + MM_DBG("Unknown Event id %d\n", id); + } +} + +static void audrec_dsp_event(void *data, unsigned id, size_t len, + void (*getevent) (void *ptr, size_t len)) +{ + struct msm_audio *prtd = data; + unsigned long flag = 0; + + switch (id) { + case AUDREC_CMD_MEM_CFG_DONE_MSG: { + MM_DBG("AUDREC_CMD_MEM_CFG_DONE_MSG\n"); + prtd->running = 1; + alsa_in_record_config(prtd, 1); + break; + } + case AUDREC_FATAL_ERR_MSG: { + struct audrec_fatal_err_msg fatal_err_msg; + + getevent(&fatal_err_msg, AUDREC_FATAL_ERR_MSG_LEN); + MM_ERR("FATAL_ERR_MSG: err id %d\n", + fatal_err_msg.audrec_err_id); + /* Error stop the encoder */ + prtd->stopped = 1; + wake_up(&the_locks.read_wait); + break; + } + case AUDREC_UP_PACKET_READY_MSG: { + struct audrec_up_pkt_ready_msg pkt_ready_msg; + MM_DBG("AUDREC_UP_PACKET_READY_MSG\n"); + + getevent(&pkt_ready_msg, AUDREC_UP_PACKET_READY_MSG_LEN); + MM_DBG("UP_PACKET_READY_MSG: write cnt lsw %d \ + write cnt msw %d read cnt lsw %d read cnt msw %d \n",\ + pkt_ready_msg.audrec_packet_write_cnt_lsw, \ + pkt_ready_msg.audrec_packet_write_cnt_msw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_lsw, \ + pkt_ready_msg.audrec_up_prev_read_cnt_msw); + + alsa_get_dsp_frames(prtd); + ++intcnt; + if (prtd->channel_mode == 1) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } else if ((prtd->channel_mode == 0) && (intcnt % 2 == 0)) { + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + if (prtd->ops->capture) + prtd->ops->capture(prtd); + } + break; + } + default: + MM_DBG("Unknown Event id %d\n", id); + } +} + +struct msm_adsp_ops alsa_audrec_adsp_ops = { + .event = audrec_dsp_event, +}; + +int alsa_audio_configure(struct msm_audio *prtd) +{ + if (prtd->enabled) + return 0; + + MM_DBG("\n"); + if (prtd->dir == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->out_weight = 100; + if (audpp_enable(-1, alsa_dsp_event, prtd)) { + MM_ERR("audpp_enable() failed\n"); + return -ENODEV; + } + } + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) { + if (audpreproc_enable(prtd->session_id, + &audpreproc_dsp_event, prtd)) { + MM_ERR("audpreproc_enable failed\n"); + return -ENODEV; + } + + if (msm_adsp_enable(prtd->audrec)) { + MM_ERR("msm_adsp_enable(audrec) enable failed\n"); + audpreproc_disable(prtd->session_id, prtd); + return -ENODEV; + } + alsa_in_enc_config(prtd, 1); + + } + prtd->enabled = 1; + return 0; +} +EXPORT_SYMBOL(alsa_audio_configure); + +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag = 0; + const char __user *start = buf; + struct buffer *frame; + size_t xfer; + int ret = 0; + + MM_DBG("\n"); + mutex_lock(&the_locks.write_lock); + while (count > 0) { + frame = prtd->out + prtd->out_head; + ret = wait_event_interruptible(the_locks.write_wait, + (frame->used == 0) + || (prtd->stopped)); + if (ret < 0) + break; + if (prtd->stopped) { + ret = -EBUSY; + break; + } + xfer = count > frame->size ? frame->size : count; + if (copy_from_user(frame->data, buf, xfer)) { + ret = -EFAULT; + break; + } + frame->used = xfer; + prtd->out_head ^= 1; + count -= xfer; + buf += xfer; + + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + frame = prtd->out + prtd->out_tail; + if (frame->used && prtd->out_needed) { + alsa_dsp_send_buffer(prtd, prtd->out_tail, + frame->used); + /* Reset eos_ack flag to avoid stale + * PCMDMAMISS been considered + */ + prtd->eos_ack = 0; + prtd->out_tail ^= 1; + prtd->out_needed--; + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + } + mutex_unlock(&the_locks.write_lock); + if (buf > start) + return buf - start; + return ret; +} +EXPORT_SYMBOL(alsa_send_buffer); + +int alsa_audio_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + MM_DBG("\n"); + mutex_lock(&the_locks.lock); + prtd->enabled = 0; + audio_dsp_out_enable(prtd, 0); + wake_up(&the_locks.write_wait); + audpp_disable(-1, prtd); + prtd->out_needed = 0; + mutex_unlock(&the_locks.lock); + } + return 0; +} +EXPORT_SYMBOL(alsa_audio_disable); + +int alsa_audrec_disable(struct msm_audio *prtd) +{ + if (prtd->enabled) { + prtd->enabled = 0; + alsa_in_enc_config(prtd, 0); + wake_up(&the_locks.read_wait); + msm_adsp_disable(prtd->audrec); + prtd->out_needed = 0; + audpreproc_disable(prtd->session_id, prtd); + } + return 0; +} +EXPORT_SYMBOL(alsa_audrec_disable); + +static int alsa_in_enc_config(struct msm_audio *prtd, int enable) +{ + struct audpreproc_audrec_cmd_enc_cfg cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AUDREC_CMD_ENC_CFG; + cmd.stream_id = prtd->session_id; + + if (enable) + cmd.audrec_enc_type = prtd->type | ENCODE_ENABLE; + else + cmd.audrec_enc_type &= ~(ENCODE_ENABLE); + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int alsa_in_param_config(struct msm_audio *prtd) +{ + struct audpreproc_audrec_cmd_parm_cfg_wav cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.common.cmd_id = AUDPREPROC_AUDREC_CMD_PARAM_CFG; + cmd.common.stream_id = prtd->session_id; + + cmd.aud_rec_samplerate_idx = prtd->samp_rate; + cmd.aud_rec_stereo_mode = prtd->channel_mode; + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +int alsa_in_record_config(struct msm_audio *prtd, int enable) +{ + struct audpreproc_afe_cmd_audio_record_cfg cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPREPROC_AFE_CMD_AUDIO_RECORD_CFG; + cmd.stream_id = prtd->session_id; + if (enable) + cmd.destination_activity = AUDIO_RECORDING_TURN_ON; + else + cmd.destination_activity = AUDIO_RECORDING_TURN_OFF; + cmd.source_mix_mask = prtd->source; + if (prtd->session_id == 2) { + if ((cmd.source_mix_mask & + INTERNAL_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & AUX_CODEC_TX_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_UL_SOURCE_MIX_MASK) || + (cmd.source_mix_mask & VOICE_DL_SOURCE_MIX_MASK)) { + cmd.pipe_id = SOURCE_PIPE_1; + } + if (cmd.source_mix_mask & + AUDPP_A2DP_PIPE_SOURCE_MIX_MASK) + cmd.pipe_id |= SOURCE_PIPE_0; + } + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + return audpreproc_send_audreccmdqueue(&cmd, sizeof(cmd)); +} + +static int alsa_in_mem_config(struct msm_audio *prtd) +{ + struct audrec_cmd_arecmem_cfg cmd; + uint16_t *data = (void *) prtd->data; + int n; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDREC_CMD_MEM_CFG_CMD; + cmd.audrec_up_pkt_intm_count = 1; + cmd.audrec_ext_pkt_start_addr_msw = prtd->phys >> 16; + cmd.audrec_ext_pkt_start_addr_lsw = prtd->phys; + cmd.audrec_ext_pkt_buf_number = FRAME_NUM; + + /* prepare buffer pointers: + * Mono: 1024 samples + 4 halfword header + * Stereo: 2048 samples + 4 halfword header + */ + for (n = 0; n < FRAME_NUM; n++) { + prtd->in[n].data = data + 4; + data += (4 + (prtd->channel_mode ? 2048 : 1024)); + MM_DBG("0x%8x\n", (int)(prtd->in[n].data - 8)); + } + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audrec_send_audrecqueue(prtd, &cmd, sizeof(cmd)); +} + +int audio_dsp_out_enable(struct msm_audio *prtd, int yes) +{ + struct audpp_cmd_pcm_intf cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = AUDPP_CMD_PCM_INTF; + cmd.stream = AUDPP_CMD_POPP_STREAM; + cmd.stream_id = prtd->session_id; + cmd.config = AUDPP_CMD_PCM_INTF_CONFIG_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + + if (yes) { + cmd.write_buf1LSW = prtd->out[0].addr; + cmd.write_buf1MSW = prtd->out[0].addr >> 16; + cmd.write_buf1_len = prtd->out[0].size; + cmd.write_buf2LSW = prtd->out[1].addr; + cmd.write_buf2MSW = prtd->out[1].addr >> 16; + if (prtd->out[1].used) + cmd.write_buf2_len = prtd->out[1].used; + else + cmd.write_buf2_len = prtd->out[1].size; + cmd.arm_to_rx_flag = AUDPP_CMD_PCM_INTF_ENA_V; + cmd.weight_decoder_to_rx = prtd->out_weight; + cmd.weight_arm_to_rx = 1; + cmd.partition_number_arm_to_dsp = 0; + cmd.sample_rate = prtd->out_sample_rate; + cmd.channel_mode = prtd->out_channel_mode; + } + + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos) +{ + unsigned long flag; + void *data; + uint32_t index; + uint32_t size; + int ret = 0; + + mutex_lock(&the_locks.read_lock); + while (count > 0) { + ret = wait_event_interruptible(the_locks.read_wait, + (prtd->in_count > 0) + || prtd->stopped || + prtd->abort); + + if (ret < 0) + break; + + if (prtd->stopped) { + ret = -EBUSY; + break; + } + + if (prtd->abort) { + MM_DBG(" prtd->abort !\n"); + ret = -EPERM; /* Not permitted due to abort */ + break; + } + + index = prtd->in_tail; + data = (uint8_t *) prtd->in[index].data; + size = prtd->in[index].size; + if (count >= size) { + if (copy_to_user(buf, data, size)) { + ret = -EFAULT; + break; + } + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + if (index != prtd->in_tail) { + /* overrun: data is invalid, we need to retry */ + spin_unlock_irqrestore(&the_locks.read_dsp_lock, + flag); + continue; + } + prtd->in[index].size = 0; + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + prtd->in_count--; + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + count -= size; + buf += size; + } else { + break; + } + } + mutex_unlock(&the_locks.read_lock); + return ret; +} +EXPORT_SYMBOL(alsa_buffer_read); + +int alsa_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len) +{ + struct audpp_cmd_pcm_intf_send_buffer cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + cmd.cmd_id = AUDPP_CMD_PCM_INTF; + cmd.stream = AUDPP_CMD_POPP_STREAM; + cmd.stream_id = prtd->session_id; + cmd.config = AUDPP_CMD_PCM_INTF_BUFFER_CMD_V; + cmd.intf_type = AUDPP_CMD_PCM_INTF_RX_ENA_ARMTODSP_V; + cmd.dsp_to_arm_buf_id = 0; + cmd.arm_to_dsp_buf_id = idx + 1; + cmd.arm_to_dsp_buf_len = len; + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audpp_send_queue2(&cmd, sizeof(cmd)); +} + +static int alsa_dsp_read_buffer(struct msm_audio *audio, uint32_t read_cnt) +{ + struct up_audrec_packet_ext_ptr cmd; + int i; + unsigned short *ptrmem = (unsigned short *)&cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_id = UP_AUDREC_PACKET_EXT_PTR; + cmd.audrec_up_curr_read_count_msw = read_cnt >> 16; + cmd.audrec_up_curr_read_count_lsw = read_cnt; + for (i = 0; i < sizeof(cmd)/2; i++, ++ptrmem) + MM_DBG("cmd[%d]=0x%04x\n", i, *ptrmem); + + return audrec_send_bitstreamqueue(audio, &cmd, sizeof(cmd)); +} + +static void alsa_get_dsp_frames(struct msm_audio *prtd) +{ + struct audio_frame *frame; + uint32_t index = 0; + unsigned long flag; + + if (prtd->type == ENC_TYPE_WAV) { + index = prtd->in_head; + + frame = + (void *)(((char *)prtd->in[index].data) - sizeof(*frame)); + + spin_lock_irqsave(&the_locks.read_dsp_lock, flag); + prtd->in[index].size = frame->bytes; + MM_DBG("frame = %08x\n", (unsigned int) frame); + MM_DBG("prtd->in[index].size = %08x\n", + (unsigned int) prtd->in[index].size); + + prtd->in_head = (prtd->in_head + 1) & (FRAME_NUM - 1); + + /* If overflow, move the tail index foward. */ + if (prtd->in_head == prtd->in_tail) + prtd->in_tail = (prtd->in_tail + 1) & (FRAME_NUM - 1); + else + prtd->in_count++; + + prtd->pcm_irq_pos += frame->bytes; + alsa_dsp_read_buffer(prtd, prtd->dsp_cnt++); + spin_unlock_irqrestore(&the_locks.read_dsp_lock, flag); + + wake_up(&the_locks.read_wait); + } +} diff --git a/sound/soc/msm/msm7kv2-pcm.c b/sound/soc/msm/msm7kv2-pcm.c new file mode 100644 index 00000000000..c64a3ffc449 --- /dev/null +++ b/sound/soc/msm/msm7kv2-pcm.c @@ -0,0 +1,774 @@ +/* Copyright (c) 2008-2010, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm7kv2-pcm.h" +#include +#include + +#define HOSTPCM_STREAM_ID 5 + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +int copy_count; + +static struct snd_pcm_hardware msm_pcm_playback_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = USE_RATE_MIN, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUFFER_PLAYBACK_SIZE, + .period_bytes_min = BUFSZ, + .period_bytes_max = BUFSZ, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware msm_pcm_capture_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .formats = USE_FORMATS, + .rates = USE_RATE, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = MAX_BUFFER_CAPTURE_SIZE, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 4, + .periods_max = 4, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; +static void alsa_out_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct msm_audio *prtd = (struct msm_audio *) private_data; + MM_DBG("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + prtd->source |= (0x1 << evt_payload->routing_id); + if (prtd->running == 1 && prtd->enabled == 1) + audpp_route_stream(prtd->session_id, prtd->source); + break; + case AUDDEV_EVT_DEV_RLS: + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + prtd->source &= ~(0x1 << evt_payload->routing_id); + if (prtd->running == 1 && prtd->enabled == 1) + audpp_route_stream(prtd->session_id, prtd->source); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + prtd->vol_pan.volume = evt_payload->session_vol; + MM_DBG("AUDDEV_EVT_STREAM_VOL_CHG, stream vol %d\n", + prtd->vol_pan.volume); + if (prtd->running) + audpp_set_volume_and_pan(prtd->session_id, + prtd->vol_pan.volume, + 0, POPP); + break; + default: + MM_DBG("Unknown Event\n"); + break; + } +} + +static void alsa_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + struct msm_audio *prtd = (struct msm_audio *) private_data; + MM_DBG("evt_id = 0x%8x\n", evt_id); + + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: { + MM_DBG("AUDDEV_EVT_DEV_RDY\n"); + prtd->source |= (0x1 << evt_payload->routing_id); + + if ((prtd->running == 1) && (prtd->enabled == 1)) + alsa_in_record_config(prtd, 1); + + break; + } + case AUDDEV_EVT_DEV_RLS: { + MM_DBG("AUDDEV_EVT_DEV_RLS\n"); + prtd->source &= ~(0x1 << evt_payload->routing_id); + + if (!prtd->running || !prtd->enabled) + break; + + /* Turn off as per source */ + if (prtd->source) + alsa_in_record_config(prtd, 1); + else + /* Turn off all */ + alsa_in_record_config(prtd, 0); + + break; + } + case AUDDEV_EVT_FREQ_CHG: { + MM_DBG("Encoder Driver got sample rate change event\n"); + MM_DBG("sample rate %d\n", evt_payload->freq_info.sample_rate); + MM_DBG("dev_type %d\n", evt_payload->freq_info.dev_type); + MM_DBG("acdb_dev_id %d\n", evt_payload->freq_info.acdb_dev_id); + if (prtd->running == 1) { + /* Stop Recording sample rate does not match + with device sample rate */ + if (evt_payload->freq_info.sample_rate != + prtd->samp_rate) { + alsa_in_record_config(prtd, 0); + prtd->abort = 1; + wake_up(&the_locks.read_wait); + } + } + break; + } + default: + MM_DBG("Unknown Event\n"); + break; + } +} + +static void msm_pcm_enqueue_data(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + unsigned int period_size; + + MM_DBG("prtd->out_tail =%d mmap_flag=%d\n", + prtd->out_tail, prtd->mmap_flag); + period_size = snd_pcm_lib_period_bytes(substream); + alsa_dsp_send_buffer(prtd, prtd->out_tail, period_size); + prtd->out_tail ^= 1; + ++copy_count; + prtd->period++; + if (unlikely(prtd->period >= runtime->periods)) + prtd->period = 0; + +} + +static void event_handler(void *data) +{ + struct msm_audio *prtd = data; + MM_DBG("\n"); + snd_pcm_period_elapsed(prtd->substream); + if (prtd->mmap_flag) { + if (prtd->dir == SNDRV_PCM_STREAM_CAPTURE) + return; + if (!prtd->stopped) + msm_pcm_enqueue_data(prtd->substream); + else + prtd->out_needed++; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + MM_DBG("\n"); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + if (prtd->enabled) + return 0; + + MM_DBG("\n"); + /* rate and channels are sent to audio driver */ + prtd->out_sample_rate = runtime->rate; + prtd->out_channel_mode = runtime->channels; + prtd->data = prtd->substream->dma_buffer.area; + prtd->phys = prtd->substream->dma_buffer.addr; + prtd->out[0].data = prtd->data + 0; + prtd->out[0].addr = prtd->phys + 0; + prtd->out[0].size = BUFSZ; + prtd->out[1].data = prtd->data + BUFSZ; + prtd->out[1].addr = prtd->phys + BUFSZ; + prtd->out[1].size = BUFSZ; + + if (prtd->enabled | !(prtd->mmap_flag)) + return 0; + + prtd->out[0].used = prtd->pcm_count; + prtd->out[1].used = prtd->pcm_count; + + mutex_lock(&the_locks.lock); + alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + uint32_t freq; + MM_DBG("\n"); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + prtd->pcm_buf_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->type = ENC_TYPE_WAV; + prtd->samp_rate = runtime->rate; + prtd->channel_mode = (runtime->channels - 1); + prtd->buffer_size = prtd->channel_mode ? STEREO_DATA_SIZE : \ + MONO_DATA_SIZE; + + if (prtd->enabled) + return 0; + + freq = prtd->samp_rate; + + prtd->data = prtd->substream->dma_buffer.area; + prtd->phys = prtd->substream->dma_buffer.addr; + MM_DBG("prtd->data =%08x\n", (unsigned int)prtd->data); + MM_DBG("prtd->phys =%08x\n", (unsigned int)prtd->phys); + + mutex_lock(&the_locks.lock); + ret = alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + if (ret) + return ret; + ret = wait_event_interruptible(the_locks.enable_wait, + prtd->running != 0); + MM_DBG("state prtd->running = %d ret = %d\n", prtd->running, ret); + + if (prtd->running == 0) + ret = -ENODEV; + else + ret = 0; + prtd->enabled = 1; + + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + unsigned long flag = 0; + int ret = 0; + + MM_DBG("\n"); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || !prtd->mmap_flag) + break; + if (!prtd->out_needed) { + prtd->stopped = 0; + break; + } + spin_lock_irqsave(&the_locks.write_dsp_lock, flag); + if (prtd->running == 1) { + if (prtd->stopped == 1) { + prtd->stopped = 0; + prtd->period = 0; + if (prtd->pcm_irq_pos == 0) { + prtd->out_tail = 0; + msm_pcm_enqueue_data(prtd->substream); + prtd->out_needed--; + } else { + prtd->out_tail = 1; + msm_pcm_enqueue_data(prtd->substream); + prtd->out_needed--; + } + if (prtd->out_needed) { + prtd->out_tail ^= 1; + msm_pcm_enqueue_data(prtd->substream); + prtd->out_needed--; + } + } + } + spin_unlock_irqrestore(&the_locks.write_dsp_lock, flag); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) + || !prtd->mmap_flag) + break; + prtd->stopped = 1; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +struct msm_audio_event_callbacks snd_msm_audio_ops = { + .playback = event_handler, + .capture = event_handler, +}; + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd; + int ret = 0; + int i = 0; + int session_attrb, sessionid; + + MM_DBG("\n"); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + ret = -ENOMEM; + return ret; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (prtd->opened) { + kfree(prtd); + return -EBUSY; + } + runtime->hw = msm_pcm_playback_hardware; + prtd->dir = SNDRV_PCM_STREAM_PLAYBACK; + prtd->eos_ack = 0; + prtd->session_id = HOSTPCM_STREAM_ID; + prtd->device_events = AUDDEV_EVT_DEV_RDY | + AUDDEV_EVT_STREAM_VOL_CHG | + AUDDEV_EVT_DEV_RLS; + prtd->source = msm_snddev_route_dec(prtd->session_id); + MM_ERR("Register device event listener\n"); + ret = auddev_register_evt_listner(prtd->device_events, + AUDDEV_CLNT_DEC, prtd->session_id, + alsa_out_listener, (void *) prtd); + if (ret) { + MM_ERR("failed to register device event listener\n"); + goto evt_error; + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw = msm_pcm_capture_hardware; + prtd->dir = SNDRV_PCM_STREAM_CAPTURE; + session_attrb = ENC_TYPE_WAV; + sessionid = audpreproc_aenc_alloc(session_attrb, + &prtd->module_name, &prtd->queue_id); + if (sessionid < 0) { + MM_ERR("AUDREC not available\n"); + kfree(prtd); + return -ENODEV; + } + prtd->session_id = sessionid; + MM_DBG("%s\n", prtd->module_name); + ret = msm_adsp_get(prtd->module_name, &prtd->audrec, + &alsa_audrec_adsp_ops, prtd); + if (ret < 0) { + audpreproc_aenc_free(prtd->session_id); + kfree(prtd); + return -ENODEV; + } + + prtd->abort = 0; + prtd->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_FREQ_CHG; + prtd->source = msm_snddev_route_enc(prtd->session_id); + MM_ERR("Register device event listener\n"); + ret = auddev_register_evt_listner(prtd->device_events, + AUDDEV_CLNT_ENC, prtd->session_id, + alsa_in_listener, (void *) prtd); + if (ret) { + MM_ERR("failed to register device event listener\n"); + audpreproc_aenc_free(prtd->session_id); + msm_adsp_put(prtd->audrec); + goto evt_error; + } + } + prtd->substream = substream; + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + MM_ERR("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + MM_ERR("snd_pcm_hw_constraint_integer failed\n"); + + prtd->ops = &snd_msm_audio_ops; + prtd->out[0].used = BUF_INVALID_LEN; + prtd->out[1].used = 0; + prtd->out_head = 1; /* point to second buffer on startup */ + prtd->out_tail = 0; + prtd->dsp_cnt = 0; + prtd->in_head = 0; + prtd->in_tail = 0; + prtd->in_count = 0; + prtd->out_needed = 0; + for (i = 0; i < FRAME_NUM; i++) { + prtd->in[i].size = 0; + prtd->in[i].read = 0; + } + prtd->vol_pan.volume = 0x2000; + prtd->vol_pan.pan = 0x0; + prtd->opened = 1; + runtime->private_data = prtd; + + copy_count = 0; + return 0; +evt_error: + kfree(prtd); + return ret; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + MM_DBG("%d\n", fbytes); + ret = alsa_send_buffer(prtd, buf, fbytes, NULL); + ++copy_count; + prtd->pcm_buf_pos += fbytes; + if (copy_count == 1) { + mutex_lock(&the_locks.lock); + ret = alsa_audio_configure(prtd); + mutex_unlock(&the_locks.lock); + } + return ret; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + int ret = 0; + + MM_DBG("\n"); + if ((!prtd->mmap_flag) && prtd->enabled) { + ret = wait_event_interruptible(the_locks.eos_wait, + (!(prtd->out[0].used) && !(prtd->out[1].used))); + + if (ret < 0) + goto done; + } + + /* PCM DMAMISS message is sent only once in + * hpcm interface. So, wait for buffer complete + * and teos flag. + */ + if (prtd->enabled) + ret = wait_event_interruptible(the_locks.eos_wait, + prtd->eos_ack); + +done: + alsa_audio_disable(prtd); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, prtd->session_id); + kfree(prtd); + + return 0; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0, rc1 = 0, rc2 = 0; + int fbytes = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + int monofbytes = 0; + char *bufferp = NULL; + + if (prtd->abort) + return -EPERM; + + fbytes = frames_to_bytes(runtime, frames); + MM_DBG("%d\n", fbytes); + monofbytes = fbytes / 2; + if (runtime->channels == 2) { + ret = alsa_buffer_read(prtd, buf, fbytes, NULL); + } else { + bufferp = buf; + rc1 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + bufferp = buf + monofbytes ; + rc2 = alsa_buffer_read(prtd, bufferp, monofbytes, NULL); + ret = rc1 + rc2; + } + prtd->pcm_buf_pos += fbytes; + MM_DBG("prtd->pcm_buf_pos =%d, prtd->mmap_flag =%d\n", + prtd->pcm_buf_pos, prtd->mmap_flag); + return ret; +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + + MM_DBG("\n"); + ret = msm_snddev_withdraw_freq(prtd->session_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + MM_DBG("msm_snddev_withdraw_freq\n"); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, prtd->session_id); + prtd->abort = 0; + wake_up(&the_locks.enable_wait); + alsa_audrec_disable(prtd); + audpreproc_aenc_free(prtd->session_id); + msm_adsp_put(prtd->audrec); + kfree(prtd); + return 0; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + MM_DBG("pcm_irq_pos = %d\n", prtd->pcm_irq_pos); + if (prtd->pcm_irq_pos == prtd->pcm_size) + prtd->pcm_irq_pos = 0; + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + prtd->out_head = 0; /* point to First buffer on startup */ + prtd->mmap_flag = 1; + runtime->dma_bytes = snd_pcm_lib_period_bytes(substream)*2; + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} + +int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int pcm_preallocate_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size; + if (!stream) + size = PLAYBACK_DMASZ; + else + size = CAPTURE_DMASZ; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void msm_pcm_free_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 1); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_pcm_ops); + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + ret = pcm_preallocate_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + ret = pcm_preallocate_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + msm_pcm_free_buffers(pcm); + return ret; +} + +struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_pcm_new, + .pcm_free = msm_pcm_free_buffers, +}; +EXPORT_SYMBOL(msm_soc_platform); + +static __devinit int msm_pcm_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .driver = { + .name = "msm-dsp-audio", + .owner = THIS_MODULE, + }, + .probe = msm_pcm_probe, + .remove = __devexit_p(msm_pcm_remove), +}; + +static int __init msm_soc_platform_init(void) +{ + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm7kv2-pcm.h b/sound/soc/msm/msm7kv2-pcm.h new file mode 100644 index 00000000000..fec7cf57027 --- /dev/null +++ b/sound/soc/msm/msm7kv2-pcm.h @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2008-2010, Code Aurora Forum. 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define FRAME_NUM (8) +#define FRAME_SIZE (2052 * 2) +#define MONO_DATA_SIZE (2048) +#define STEREO_DATA_SIZE (MONO_DATA_SIZE * 2) +#define CAPTURE_DMASZ (FRAME_SIZE * FRAME_NUM) + +#define BUFSZ (960 * 5) +#define PLAYBACK_DMASZ (BUFSZ * 2) + +#define MSM_PLAYBACK_DEFAULT_VOLUME 0 /* 0dB */ +#define MSM_PLAYBACK_DEFAULT_PAN 0 + +#define USE_FORMATS SNDRV_PCM_FMTBIT_S16_LE +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +/* Support unconventional sample rates 12000, 24000 as well */ +#define USE_RATE \ + (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) +#define USE_RATE_MIN 8000 +#define USE_RATE_MAX 48000 +#define MAX_BUFFER_PLAYBACK_SIZE \ + PLAYBACK_DMASZ +/* 2048 frames (Mono), 1024 frames (Stereo) */ +#define CAPTURE_SIZE 4096 +#define MAX_BUFFER_CAPTURE_SIZE (4096*4) +#define MAX_PERIOD_SIZE BUFSZ +#define USE_PERIODS_MAX 1024 +#define USE_PERIODS_MIN 1 + + +#define MAX_DB (16) +#define MIN_DB (-50) +#define PCMPLAYBACK_DECODERID 5 + +/* 0xFFFFFFFF Indicates not to be used for audio data copy */ +#define BUF_INVALID_LEN 0xFFFFFFFF +#define EVENT_MSG_ID ((uint16_t)~0) + +#define AUDDEC_DEC_PCM 0 +/* Decoder status received from AUDPPTASK */ +#define AUDPP_DEC_STATUS_SLEEP 0 +#define AUDPP_DEC_STATUS_INIT 1 +#define AUDPP_DEC_STATUS_CFG 2 +#define AUDPP_DEC_STATUS_PLAY 3 + +extern int copy_count; +extern int intcnt; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + struct mutex lock; + struct mutex write_lock; + struct mutex read_lock; + spinlock_t read_dsp_lock; + spinlock_t write_dsp_lock; + spinlock_t mixer_lock; + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t wait; + wait_queue_head_t eos_wait; + wait_queue_head_t enable_wait; +}; + +extern struct audio_locks the_locks; + +struct msm_audio_event_callbacks { + /* event is called from interrupt context when a message + * arrives from the DSP. + */ + void (*playback)(void *); + void (*capture)(void *); +}; + + +struct msm_audio { + struct buffer out[2]; + struct buffer_rec in[8]; + + uint8_t out_head; + uint8_t out_tail; + uint8_t out_needed; /* number of buffers the dsp is waiting for */ + atomic_t out_bytes; + + /* configuration to use on next enable */ + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_weight; + uint32_t out_buffer_size; + + struct snd_pcm_substream *substream; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int pcm_buf_pos; /* position in buffer */ + uint16_t source; /* Encoding source bit mask */ + + struct msm_adsp_module *audpre; + struct msm_adsp_module *audrec; + struct msm_adsp_module *audplay; + enum msm_aud_decoder_state dec_state; /* Represents decoder state */ + + uint16_t session_id; + uint32_t out_bits; /* bits per sample */ + const char *module_name; + unsigned queue_id; + + /* configuration to use on next enable */ + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t buffer_size; /* 2048 for mono, 4096 for stereo */ + uint32_t type; /* 0 for PCM ,1 for AAC */ + uint32_t dsp_cnt; + uint32_t in_head; /* next buffer dsp will write */ + uint32_t in_tail; /* next buffer read() will read */ + uint32_t in_count; /* number of buffers available to read() */ + + unsigned short samp_rate_index; + uint32_t device_events; /* device events interested in */ + int abort; /* set when error, like sample rate mismatch */ + + /* audpre settings */ + /* For different sample rate, the coeff might be different. * + * All the coeff should be passed from user space */ + + struct msm_audio_event_callbacks *ops; + + int dir; + int opened; + int enabled; + int running; + int stopped; /* set when stopped, cleared on flush */ + int eos_ack; + int mmap_flag; + int period; + struct audpp_cmd_cfg_object_params_volume vol_pan; +}; + + + +/* platform data */ +extern int alsa_dsp_send_buffer(struct msm_audio *prtd, + unsigned idx, unsigned len); +extern int audio_dsp_out_enable(struct msm_audio *prtd, int yes); +extern struct snd_soc_platform_driver msm_soc_platform; + +extern int audrec_encoder_config(struct msm_audio *prtd); +extern int alsa_audrec_disable(struct msm_audio *prtd); +extern int alsa_audio_configure(struct msm_audio *prtd); +extern int alsa_audio_disable(struct msm_audio *prtd); +extern int alsa_buffer_read(struct msm_audio *prtd, void __user *buf, + size_t count, loff_t *pos); +ssize_t alsa_send_buffer(struct msm_audio *prtd, const char __user *buf, + size_t count, loff_t *pos); +extern struct msm_adsp_ops alsa_audrec_adsp_ops; +extern int alsa_in_record_config(struct msm_audio *prtd, int enable); +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm7x30.c b/sound/soc/msm/msm7x30.c new file mode 100644 index 00000000000..94e37caa0fc --- /dev/null +++ b/sound/soc/msm/msm7x30.c @@ -0,0 +1,1004 @@ +/* Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * + * All source code in this file is licensed under the following license except + * where indicated. + * + * 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, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm7kv2-pcm.h" +#include +#include +#include +#include + +static struct platform_device *msm_audio_snd_device; +struct audio_locks the_locks; +EXPORT_SYMBOL(the_locks); +struct msm_volume msm_vol_ctl; +EXPORT_SYMBOL(msm_vol_ctl); +static struct snd_kcontrol_new snd_msm_controls[]; + +char snddev_name[AUDIO_DEV_CTL_MAX_DEV][44]; +#define MSM_MAX_VOLUME 0x2000 +#define MSM_VOLUME_STEP ((MSM_MAX_VOLUME+17)/100) /* 17 added to avoid + more deviation */ +#define LOOPBACK_ENABLE 0x1 +#define LOOPBACK_DISABLE 0x0 + +static int device_index; /* Count of Device controls */ +static int simple_control; /* Count of simple controls*/ +static int src_dev; +static int dst_dev; +static int loopback_status; + + +static int msm_scontrol_count_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + return 0; +} + +static int msm_scontrol_count_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = simple_control; + return 0; +} + +static int msm_v_call_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int msm_v_call_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_v_call_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int start = ucontrol->value.integer.value[0]; + if (start) + broadcast_event(AUDDEV_EVT_START_VOICE, DEVICE_IGNORE, + SESSION_IGNORE); + else + broadcast_event(AUDDEV_EVT_END_VOICE, DEVICE_IGNORE, + SESSION_IGNORE); + return 0; +} + +static int msm_v_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 2; + return 0; +} + +static int msm_v_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_v_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int mute = ucontrol->value.integer.value[1]; + return msm_set_voice_mute(dir, mute); +} + +static int msm_v_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* Volume */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int msm_v_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_v_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + + return msm_set_voice_vol(dir, volume); +} + +static int msm_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Volume and 10-base multiply factor*/ + uinfo->value.integer.min = 0; + + /* limit the muliply factor to 4 decimal digit */ + uinfo->value.integer.max = 1000000; + return 0; +} +static int msm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + int session_id = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + int factor = ucontrol->value.integer.value[2]; + u32 session_mask = 0; + + + if (factor > 10000) + return -EINVAL; + + if ((volume < 0) || (volume/factor > 100)) + return -EINVAL; + + volume = (MSM_VOLUME_STEP * volume); + + /* Convert back to original decimal point by removing the 10-base factor + * and discard the fractional portion + */ + + volume = volume/factor; + + if (volume > MSM_MAX_VOLUME) + volume = MSM_MAX_VOLUME; + + /* Only Decoder volume control supported */ + session_mask = (0x1 << (session_id) << (8 * ((int)AUDDEV_CLNT_DEC-1))); + msm_vol_ctl.volume = volume; + MM_DBG("session_id %d, volume %d", session_id, volume); + broadcast_event(AUDDEV_EVT_STREAM_VOL_CHG, DEVICE_IGNORE, + session_mask); + + return ret; +} + +static int msm_voice_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_voice_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + uint32_t rx_dev_id; + uint32_t tx_dev_id; + struct msm_snddev_info *rx_dev_info; + struct msm_snddev_info *tx_dev_info; + int set = ucontrol->value.integer.value[2]; + u32 session_mask; + + if (!set) + return -EPERM; + /* Rx Device Routing */ + rx_dev_id = ucontrol->value.integer.value[0]; + rx_dev_info = audio_dev_ctrl_find_dev(rx_dev_id); + + if (IS_ERR(rx_dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(rx_dev_info); + return rc; + } + + if (!(rx_dev_info->capability & SNDDEV_CAP_RX)) { + MM_ERR("First Dev is supposed to be RX\n"); + return -EFAULT; + } + + MM_DBG("route cfg %d STREAM_VOICE_RX type\n", + rx_dev_id); + + msm_set_voc_route(rx_dev_info, AUDIO_ROUTE_STREAM_VOICE_RX, + rx_dev_id); + + session_mask = 0x1 << (8 * ((int)AUDDEV_CLNT_VOC-1)); + + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, rx_dev_id, session_mask); + + + /* Tx Device Routing */ + tx_dev_id = ucontrol->value.integer.value[1]; + tx_dev_info = audio_dev_ctrl_find_dev(tx_dev_id); + + if (IS_ERR(tx_dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(tx_dev_info); + return rc; + } + + if (!(tx_dev_info->capability & SNDDEV_CAP_TX)) { + MM_ERR("Second Dev is supposed to be Tx\n"); + return -EFAULT; + } + + MM_DBG("route cfg %d %d type\n", + tx_dev_id, AUDIO_ROUTE_STREAM_VOICE_TX); + + msm_set_voc_route(tx_dev_info, AUDIO_ROUTE_STREAM_VOICE_TX, + tx_dev_id); + + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, tx_dev_id, session_mask); + + if (rx_dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, rx_dev_id, session_mask); + + if (tx_dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, tx_dev_id, session_mask); + + return rc; +} + +static int msm_voice_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + /* TODO: query Device list */ + return 0; +} + +static int msm_device_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_device_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + int set = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + struct msm_snddev_info *dst_dev_info; + struct msm_snddev_info *src_dev_info; + int tx_freq = 0; + int rx_freq = 0; + u32 set_freq = 0; + + set = ucontrol->value.integer.value[0]; + route_cfg.dev_id = ucontrol->id.numid - device_index; + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(dev_info); + return rc; + } + MM_INFO("device %s set %d\n", dev_info->name, set); + + if (set) { + if (!dev_info->opened) { + set_freq = dev_info->sample_rate; + if (!msm_device_is_voice(route_cfg.dev_id)) { + msm_get_voc_freq(&tx_freq, &rx_freq); + if (dev_info->capability & SNDDEV_CAP_TX) + set_freq = tx_freq; + + if (set_freq == 0) + set_freq = dev_info->sample_rate; + } else + set_freq = dev_info->sample_rate; + + + MM_ERR("device freq =%d\n", set_freq); + rc = dev_info->dev_ops.set_freq(dev_info, set_freq); + if (rc < 0) { + MM_ERR("device freq failed!\n"); + return rc; + } + dev_info->set_sample_rate = rc; + rc = 0; + rc = dev_info->dev_ops.open(dev_info); + if (rc < 0) { + MM_ERR("Enabling %s failed", dev_info->name); + return rc; + } + dev_info->opened = 1; + broadcast_event(AUDDEV_EVT_DEV_RDY, route_cfg.dev_id, + SESSION_IGNORE); + /* Event to notify client for device info */ + broadcast_event(AUDDEV_EVT_DEVICE_INFO, + route_cfg.dev_id, SESSION_IGNORE); + if ((route_cfg.dev_id == src_dev) || + (route_cfg.dev_id == dst_dev)) { + dst_dev_info = audio_dev_ctrl_find_dev( + dst_dev); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + src_dev_info = audio_dev_ctrl_find_dev( + src_dev); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + if ((dst_dev_info->opened) && + (src_dev_info->opened)) { + pr_debug("%d: Enable afe_loopback\n", + __LINE__); + afe_ext_loopback(LOOPBACK_ENABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + loopback_status = 1; + } + } + } + } else { + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_REL_PENDING, + route_cfg.dev_id, + SESSION_IGNORE); + rc = dev_info->dev_ops.close(dev_info); + if (rc < 0) { + MM_ERR("Snd device failed close!\n"); + return rc; + } else { + dev_info->opened = 0; + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + SESSION_IGNORE); + } + if (loopback_status == 1) { + if ((route_cfg.dev_id == src_dev) || + (route_cfg.dev_id == dst_dev)) { + dst_dev_info = audio_dev_ctrl_find_dev( + dst_dev); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + src_dev_info = audio_dev_ctrl_find_dev( + src_dev); + if (IS_ERR(src_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + pr_debug("%d: Disable afe_loopback\n", + __LINE__); + afe_ext_loopback(LOOPBACK_DISABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + loopback_status = 0; + } + } + } + + } + return rc; +} + +static int msm_device_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + + route_cfg.dev_id = ucontrol->id.numid - device_index; + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(dev_info); + return rc; + } + + ucontrol->value.integer.value[0] = dev_info->copp_id; + ucontrol->value.integer.value[1] = dev_info->capability; + + return 0; +} + +static int msm_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + /* TODO: query Device list */ + return 0; +} + +static int msm_route_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + int enc_freq = 0; + int requested_freq = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + int session_id = ucontrol->value.integer.value[0]; + int set = ucontrol->value.integer.value[2]; + u32 session_mask = 0; + route_cfg.dev_id = ucontrol->value.integer.value[1]; + + if (ucontrol->id.numid == 2) + route_cfg.stream_type = AUDIO_ROUTE_STREAM_PLAYBACK; + else + route_cfg.stream_type = AUDIO_ROUTE_STREAM_REC; + + MM_DBG("route cfg %d %d type for popp %d set value %d\n", + route_cfg.dev_id, route_cfg.stream_type, session_id, set); + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id\n"); + rc = PTR_ERR(dev_info); + return rc; + } + if (route_cfg.stream_type == AUDIO_ROUTE_STREAM_PLAYBACK) { + rc = msm_snddev_set_dec(session_id, dev_info->copp_id, set); + session_mask = + (0x1 << (session_id) << (8 * ((int)AUDDEV_CLNT_DEC-1))); + if (!set) { + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_REL_PENDING, + route_cfg.dev_id, + session_mask); + + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + } + dev_info->sessions &= ~(session_mask); + } else { + dev_info->sessions = dev_info->sessions | session_mask; + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + /* Event to notify client for device info */ + broadcast_event(AUDDEV_EVT_DEVICE_INFO, + route_cfg.dev_id, + session_mask); + } + } + } else { + rc = msm_snddev_set_enc(session_id, dev_info->copp_id, set); + session_mask = + (0x1 << (session_id)) << (8 * ((int)AUDDEV_CLNT_ENC-1)); + if (!set) { + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } else { + dev_info->sessions = dev_info->sessions | session_mask; + enc_freq = msm_snddev_get_enc_freq(session_id); + requested_freq = enc_freq; + if (enc_freq > 0) { + rc = msm_snddev_request_freq(&enc_freq, + session_id, + SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + MM_DBG("sample rate configured %d" + "sample rate requested %d\n", + enc_freq, requested_freq); + if ((rc <= 0) || (enc_freq != requested_freq)) { + MM_DBG("msm_snddev_withdraw_freq\n"); + rc = msm_snddev_withdraw_freq + (session_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + broadcast_event(AUDDEV_EVT_FREQ_CHG, + route_cfg.dev_id, + SESSION_IGNORE); + } + } + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + /* Event to notify client for device info */ + broadcast_event(AUDDEV_EVT_DEVICE_INFO, + route_cfg.dev_id, + session_mask); + } + } + } + + if (rc < 0) { + MM_ERR("device could not be assigned!\n"); + return -EFAULT; + } + + return rc; +} + +static int msm_device_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int msm_device_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + + dev_info = audio_dev_ctrl_find_dev(dev_id); + ucontrol->value.integer.value[0] = dev_info->dev_volume; + + return 0; +} + +static int msm_device_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = -EPERM; + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + + MM_DBG("dev_id = %d, volume = %d\n", dev_id, volume); + + dev_info = audio_dev_ctrl_find_dev(dev_id); + + if (IS_ERR(dev_info)) { + rc = PTR_ERR(dev_info); + MM_ERR("audio_dev_ctrl_find_dev failed. %ld\n", + PTR_ERR(dev_info)); + return rc; + } + + MM_DBG("dev_name = %s dev_id = %d, volume = %d\n", + dev_info->name, dev_id, volume); + + if (dev_info->dev_ops.set_device_volume) + rc = dev_info->dev_ops.set_device_volume(dev_info, volume); + else { + MM_INFO("device %s does not support device volume " + "control.", dev_info->name); + return -EPERM; + } + + return rc; +} + +static int msm_reset_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0; + return 0; +} + +static int msm_reset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_reset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + MM_DBG("Resetting all devices\n"); + return msm_reset_all_device(); +} + + +static int msm_dual_mic_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + /*Max value is decided based on MAX ENC sessions*/ + uinfo->value.integer.max = MAX_AUDREC_SESSIONS - 1; + return 0; +} + +static int msm_dual_mic_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int enc_session_id = ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = + msm_get_dual_mic_config(enc_session_id); + MM_DBG("session id = %d, config = %ld\n", enc_session_id, + ucontrol->value.integer.value[1]); + return 0; +} + +static int msm_dual_mic_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int enc_session_id = ucontrol->value.integer.value[0]; + int dual_mic_config = ucontrol->value.integer.value[1]; + MM_DBG("session id = %d, config = %d\n", enc_session_id, + dual_mic_config); + return msm_set_dual_mic_config(enc_session_id, dual_mic_config); +} + +static int msm_device_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_device_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int msm_device_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dev_id = ucontrol->value.integer.value[0]; + int mute = ucontrol->value.integer.value[1]; + struct msm_snddev_info *dev_info; + int afe_dev_id = 0; + int volume = 0x4000; + + dev_info = audio_dev_ctrl_find_dev(dev_id); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id %d\n", dev_id); + return PTR_ERR(dev_info); + } + + if (dev_info->capability & SNDDEV_CAP_RX) + return -EPERM; + + MM_DBG("Muting device id %d(%s)\n", dev_id, dev_info->name); + + if (dev_info->copp_id == 0) + afe_dev_id = AFE_HW_PATH_CODEC_TX; + if (dev_info->copp_id == 1) + afe_dev_id = AFE_HW_PATH_AUXPCM_TX; + if (dev_info->copp_id == 2) + afe_dev_id = AFE_HW_PATH_MI2S_TX; + if (mute) + volume = 0; + afe_device_volume_ctrl(afe_dev_id, volume); + return 0; +} + +static int msm_loopback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_loopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_loopback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct msm_snddev_info *src_dev_info = NULL; /* TX device */ + struct msm_snddev_info *dst_dev_info = NULL; /* RX device */ + int dst_dev_id = ucontrol->value.integer.value[0]; + int src_dev_id = ucontrol->value.integer.value[1]; + int set = ucontrol->value.integer.value[2]; + + pr_debug("%s: set=%d\n", __func__, set); + + dst_dev_info = audio_dev_ctrl_find_dev(dst_dev_id); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + if (!(dst_dev_info->capability & SNDDEV_CAP_RX)) { + pr_err("Destination device %d is not RX device\n", + dst_dev_id); + return -EFAULT; + } + + src_dev_info = audio_dev_ctrl_find_dev(src_dev_id); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + if (!(src_dev_info->capability & SNDDEV_CAP_TX)) { + pr_err("Source device %d is not TX device\n", src_dev_id); + return -EFAULT; + } + + if (set) { + pr_debug("%s:%d:Enabling AFE_Loopback\n", __func__, __LINE__); + src_dev = src_dev_id; + dst_dev = dst_dev_id; + loopback_status = 1; + if ((dst_dev_info->opened) && (src_dev_info->opened)) + afe_ext_loopback(LOOPBACK_ENABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + } else { + pr_debug("%s:%d:Disabling AFE_Loopback\n", __func__, __LINE__); + src_dev = DEVICE_IGNORE; + dst_dev = DEVICE_IGNORE; + loopback_status = 0; + afe_ext_loopback(LOOPBACK_DISABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + } + return 0; +} + +static struct snd_kcontrol_new snd_dev_controls[AUDIO_DEV_CTL_MAX_DEV]; + +static int snd_dev_ctl_index(int idx) +{ + struct msm_snddev_info *dev_info; + + dev_info = audio_dev_ctrl_find_dev(idx); + if (IS_ERR(dev_info)) { + MM_ERR("pass invalid dev_id\n"); + return PTR_ERR(dev_info); + } + if (sizeof(dev_info->name) <= 44) + sprintf(&snddev_name[idx][0] , "%s", dev_info->name); + + snd_dev_controls[idx].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + snd_dev_controls[idx].access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_dev_controls[idx].name = &snddev_name[idx][0]; + snd_dev_controls[idx].index = idx; + snd_dev_controls[idx].info = msm_device_info; + snd_dev_controls[idx].get = msm_device_get; + snd_dev_controls[idx].put = msm_device_put; + snd_dev_controls[idx].private_value = 0; + return 0; + +} + +#define MSM_EXT(xname, fp_info, fp_get, fp_put, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ +} + +static struct snd_kcontrol_new snd_msm_controls[] = { + MSM_EXT("Count", msm_scontrol_count_info, msm_scontrol_count_get, \ + NULL, 0), + MSM_EXT("Stream", msm_route_info, msm_route_get, \ + msm_route_put, 0), + MSM_EXT("Record", msm_route_info, msm_route_get, \ + msm_route_put, 0), + MSM_EXT("Voice", msm_voice_info, msm_voice_get, \ + msm_voice_put, 0), + MSM_EXT("Volume", msm_volume_info, msm_volume_get, \ + msm_volume_put, 0), + MSM_EXT("VoiceVolume", msm_v_volume_info, msm_v_volume_get, \ + msm_v_volume_put, 0), + MSM_EXT("VoiceMute", msm_v_mute_info, msm_v_mute_get, \ + msm_v_mute_put, 0), + MSM_EXT("Voice Call", msm_v_call_info, msm_v_call_get, \ + msm_v_call_put, 0), + MSM_EXT("Device_Volume", msm_device_volume_info, + msm_device_volume_get, msm_device_volume_put, 0), + MSM_EXT("Reset", msm_reset_info, + msm_reset_get, msm_reset_put, 0), + MSM_EXT("DualMic Switch", msm_dual_mic_info, + msm_dual_mic_get, msm_dual_mic_put, 0), + MSM_EXT("Device_Mute", msm_device_mute_info, + msm_device_mute_get, msm_device_mute_put, 0), + MSM_EXT("Sound Device Loopback", msm_loopback_info, + msm_loopback_get, msm_loopback_put, 0), +}; + +static int msm_new_mixer(struct snd_soc_codec *codec) +{ + unsigned int idx; + int err; + int dev_cnt; + + strcpy(codec->card->snd_card->mixername, "MSM Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_msm_controls); idx++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_msm_controls[idx], NULL)); + if (err < 0) + MM_ERR("ERR adding ctl\n"); + } + dev_cnt = msm_snddev_devcount(); + + for (idx = 0; idx < dev_cnt; idx++) { + if (!snd_dev_ctl_index(idx)) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_dev_controls[idx], NULL)); + if (err < 0) + MM_ERR("ERR adding ctl\n"); + } else + return 0; + } + simple_control = ARRAY_SIZE(snd_msm_controls); + device_index = simple_control + 1; + return 0; +} + +static int msm_soc_dai_init( + struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_soc_codec *codec = rtd->codec; + ret = msm_new_mixer(codec); + if (ret < 0) + MM_ERR("msm_soc: ALSA MSM Mixer Fail\n"); + + mutex_init(&the_locks.lock); + mutex_init(&the_locks.write_lock); + mutex_init(&the_locks.read_lock); + spin_lock_init(&the_locks.read_dsp_lock); + spin_lock_init(&the_locks.write_dsp_lock); + spin_lock_init(&the_locks.mixer_lock); + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + src_dev = DEVICE_IGNORE; + dst_dev = DEVICE_IGNORE; + + return ret; +} + +static struct snd_soc_dai_link msm_dai[] = { +{ + .name = "MSM Primary I2S", + .stream_name = "DSP 1", + .cpu_dai_name = "msm-cpu-dai.0", + .platform_name = "msm-dsp-audio.0", + .codec_name = "msm-codec-dai.0", + .codec_dai_name = "msm-codec-dai", + .init = &msm_soc_dai_init, +}, +#ifdef CONFIG_SND_MVS_SOC +{ + .name = "MSM Primary Voip", + .stream_name = "MVS", + .cpu_dai_name = "mvs-cpu-dai.0", + .platform_name = "msm-mvs-audio.0", + .codec_name = "mvs-codec-dai.0", + .codec_dai_name = "mvs-codec-dai", +}, +#endif +}; + +static struct snd_soc_card snd_soc_card_msm = { + .name = "msm-audio", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), +}; + +static int __init msm_audio_init(void) +{ + int ret; + + msm_audio_snd_device = platform_device_alloc("soc-audio", -1); + if (!msm_audio_snd_device) + return -ENOMEM; + + platform_set_drvdata(msm_audio_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_audio_snd_device); + if (ret) { + platform_device_put(msm_audio_snd_device); + return ret; + } + + return ret; +} + +static void __exit msm_audio_exit(void) +{ + platform_device_unregister(msm_audio_snd_device); +} + +module_init(msm_audio_init); +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("PCM module"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8660-dma.c b/sound/soc/msm/msm8660-dma.c new file mode 100644 index 00000000000..8ab429a9ea2 --- /dev/null +++ b/sound/soc/msm/msm8660-dma.c @@ -0,0 +1,432 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm8660-pcm.h" + +struct dai_baseinfo { + void __iomem *base; +}; + +static struct dai_baseinfo dai_info; + +struct dai_drv { + u8 *buffer; + u32 buffer_phys; + int channels; + irqreturn_t (*callback) (int intrsrc, void *private_data); + void *private_data; + int in_use; + u32 buffer_len; + u32 period_len; + u32 master_mode; +}; + +static struct dai_drv *dai[MAX_CHANNELS]; +static spinlock_t dai_lock; + +static int dai_find_dma_channel(uint32_t intrsrc) +{ + int i, dma_channel = 0; + pr_debug("%s\n", __func__); + + for (i = 0; i <= 27; i += 3) { + if (intrsrc & (1 << i)) { + dma_channel = i / 3; + break; + } + } + return dma_channel; +} + +void register_dma_irq_handler(int dma_ch, + irqreturn_t (*callback) (int intrsrc, void *private_data), + void *private_data) +{ + pr_debug("%s\n", __func__); + dai[dma_ch]->callback = callback; + dai[dma_ch]->private_data = private_data; +} + +void unregister_dma_irq_handler(int dma_ch) +{ + pr_debug("%s\n", __func__); + dai[dma_ch]->callback = NULL; + dai[dma_ch]->private_data = NULL; +} + +static irqreturn_t dai_irq_handler(int irq, void *data) +{ + unsigned long flag; + uint32_t intrsrc; + uint32_t dma_ch = 0; + irqreturn_t ret = IRQ_HANDLED; + + pr_debug("%s\n", __func__); + spin_lock_irqsave(&dai_lock, flag); + intrsrc = readl(dai_info.base + LPAIF_IRQ_STAT(0)); + writel(intrsrc, dai_info.base + LPAIF_IRQ_CLEAR(0)); + while (intrsrc) { + dma_ch = dai_find_dma_channel(intrsrc); + + if (!dai[dma_ch]->callback) + goto handled; + if (!dai[dma_ch]->private_data) + goto handled; + ret = dai[dma_ch]->callback(intrsrc, + dai[dma_ch]->private_data); + intrsrc &= ~(0x7 << (dma_ch * 3)); + } +handled: + spin_unlock_irqrestore(&dai_lock, flag); + return ret; +} + +void dai_print_state(uint32_t dma_ch) +{ + int i = 0; + unsigned long *ptrmem = (unsigned long *)dai_info.base; + + for (i = 0; i < 4; i++, ++ptrmem) + pr_debug("[0x%08x]=0x%08x\n", (unsigned int)ptrmem, + (unsigned int)*ptrmem); + + ptrmem = (unsigned long *)(dai_info.base + + DMA_CH_CTL_BASE + DMA_CH_INDEX(dma_ch)); + for (i = 0; i < 10; i++, ++ptrmem) + pr_debug("[0x%08x]=0x%08x\n", (unsigned int)ptrmem, + (unsigned int) *ptrmem); +} + +static int dai_enable_irq(uint32_t dma_ch) +{ + int ret; + pr_debug("%s\n", __func__); + ret = request_irq(LPASS_SCSS_AUDIO_IF_OUT0_IRQ, dai_irq_handler, + IRQF_TRIGGER_RISING | IRQF_SHARED, "msm-i2s", + (void *) (dma_ch+1)); + if (ret < 0) { + pr_debug("Request Irq Failed err = %d\n", ret); + return ret; + } + return ret; +} + +static void dai_config_dma(uint32_t dma_ch) +{ + pr_debug("%s dma_ch = %u\n", __func__, dma_ch); + + writel(dai[dma_ch]->buffer_phys, + dai_info.base + LPAIF_DMA_BASE(dma_ch)); + writel(((dai[dma_ch]->buffer_len >> 2) - 1), + dai_info.base + LPAIF_DMA_BUFF_LEN(dma_ch)); + writel(((dai[dma_ch]->period_len >> 2) - 1), + dai_info.base + LPAIF_DMA_PER_LEN(dma_ch)); +} + +static void dai_enable_codec(uint32_t dma_ch, int codec) +{ + uint32_t intrVal; + uint32_t i2sctl; + pr_debug("%s\n", __func__); + + intrVal = readl(dai_info.base + LPAIF_IRQ_EN(0)); + intrVal = intrVal | (7 << (dma_ch * 3)); + writel(intrVal, dai_info.base + LPAIF_IRQ_EN(0)); + if (codec == DAI_SPKR) { + writel(0x0813, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + i2sctl = 0x4400; + i2sctl |= (dai[dma_ch]->master_mode ? WS_SRC_INT : WS_SRC_EXT); + writel(i2sctl, dai_info.base + LPAIF_I2S_CTL_OFFSET(DAI_SPKR)); + } else if (codec == DAI_MIC) { + writel(0x81b, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + i2sctl = 0x0110; + i2sctl |= (dai[dma_ch]->master_mode ? WS_SRC_INT : WS_SRC_EXT); + writel(i2sctl, dai_info.base + LPAIF_I2S_CTL_OFFSET(DAI_MIC)); + } +} + +static void dai_disable_codec(uint32_t dma_ch, int codec) +{ + uint32_t intrVal = 0; + uint32_t intrVal1 = 0; + unsigned long flag = 0x0; + + pr_debug("%s\n", __func__); + spin_lock_irqsave(&dai_lock, flag); + + intrVal1 = readl(dai_info.base + LPAIF_I2S_CTL_OFFSET(codec)); + + if (codec == DAI_SPKR) + intrVal1 = intrVal1 & ~(1 << 14); + else if (codec == DAI_MIC) + intrVal1 = intrVal1 & ~(1 << 8); + + writel(intrVal1, dai_info.base + LPAIF_I2S_CTL_OFFSET(codec)); + intrVal = 0x0; + writel(intrVal, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + + spin_unlock_irqrestore(&dai_lock, flag); +} + +int dai_open(uint32_t dma_ch) +{ + + pr_debug("%s\n", __func__); + if (!dai_info.base) { + pr_debug("%s failed as no msm-dai device\n", __func__); + return -ENODEV; + } + if (dma_ch >= MAX_CHANNELS) { + pr_debug("%s over max channesl %d\n", __func__, dma_ch); + return -ENODEV; + } + return 0; +} + +void dai_close(uint32_t dma_ch) +{ + pr_debug("%s\n", __func__); + if ((dma_ch >= 0) && (dma_ch < 5)) + dai_disable_codec(dma_ch, DAI_SPKR); + else + dai_disable_codec(dma_ch, DAI_MIC); + free_irq(LPASS_SCSS_AUDIO_IF_OUT0_IRQ, (void *) (dma_ch + 1)); +} + +void dai_set_master_mode(uint32_t dma_ch, int mode) +{ + if (dma_ch < MAX_CHANNELS) + dai[dma_ch]->master_mode = mode; + else + pr_err("%s: invalid dma channel\n", __func__); +} + +int dai_set_params(uint32_t dma_ch, struct dai_dma_params *params) +{ + pr_debug("%s\n", __func__); + dai[dma_ch]->buffer = params->buffer; + dai[dma_ch]->buffer_phys = params->src_start; + dai[dma_ch]->channels = params->channels; + dai[dma_ch]->buffer_len = params->buffer_size; + dai[dma_ch]->period_len = params->period_size; + dai_config_dma(dma_ch); + return dma_ch; +} + +int dai_start(uint32_t dma_ch) +{ + unsigned long flag = 0x0; + + spin_lock_irqsave(&dai_lock, flag); + dai_enable_irq(dma_ch); + if ((dma_ch >= 0) && (dma_ch < 5)) + dai_enable_codec(dma_ch, DAI_SPKR); + else + dai_enable_codec(dma_ch, DAI_MIC); + spin_unlock_irqrestore(&dai_lock, flag); + dai_print_state(dma_ch); + return 0; +} + +#define HDMI_BURST_INCR4 (1 << 11) +#define HDMI_WPSCNT (1 << 8) +#define HDMI_AUDIO_INTF (5 << 4) +#define HDMI_FIFO_WATER_MARK (7 << 1) +#define HDMI_ENABLE (1) + +int dai_start_hdmi(uint32_t dma_ch) +{ + unsigned long flag = 0x0; + uint32_t val; + + pr_debug("%s dma_ch = %u\n", __func__, dma_ch); + + spin_lock_irqsave(&dai_lock, flag); + + dai_enable_irq(dma_ch); + + if ((dma_ch >= 0) && (dma_ch < 5)) { + + val = readl(dai_info.base + LPAIF_IRQ_EN(0)); + val = val | (7 << (dma_ch * 3)); + writel(val, dai_info.base + LPAIF_IRQ_EN(0)); + + + val = (HDMI_BURST_INCR4 | HDMI_WPSCNT | HDMI_AUDIO_INTF | + HDMI_FIFO_WATER_MARK | HDMI_ENABLE); + + writel(val, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + } + spin_unlock_irqrestore(&dai_lock, flag); + + dai_print_state(dma_ch); + return 0; +} + +void dai_stop_hdmi(uint32_t dma_ch) +{ + unsigned long flag = 0x0; + uint32_t intrVal; + uint32_t int_mask = 0x00000007; + + pr_debug("%s dma_ch %u\n", __func__, dma_ch); + + spin_lock_irqsave(&dai_lock, flag); + + free_irq(LPASS_SCSS_AUDIO_IF_OUT0_IRQ, (void *) (dma_ch + 1)); + + + intrVal = 0x0; + writel(intrVal, dai_info.base + LPAIF_DMA_CTL(dma_ch)); + + intrVal = readl(dai_info.base + LPAIF_IRQ_EN(0)); + + int_mask = ((int_mask) << (dma_ch * 3)); + int_mask = ~int_mask; + + intrVal = intrVal && int_mask; + writel(intrVal, dai_info.base + LPAIF_IRQ_EN(0)); + + spin_unlock_irqrestore(&dai_lock, flag); +} + +int dai_stop(uint32_t dma_ch) +{ + pr_debug("%s\n", __func__); + return 0; +} + + +uint32_t dai_get_dma_pos(uint32_t dma_ch) +{ + + uint32_t addr; + + pr_debug("%s\n", __func__); + addr = readl(dai_info.base + LPAIF_DMA_CURR_ADDR(dma_ch)); + + return addr; +} + +static int __devinit dai_probe(struct platform_device *pdev) +{ + int rc = 0; + int i = 0; + struct resource *src; + src = platform_get_resource_byname(pdev, IORESOURCE_MEM, "msm-dai"); + if (!src) { + rc = -ENODEV; + pr_debug("%s Error rc=%d\n", __func__, rc); + goto error; + } + for (i = 0; i <= MAX_CHANNELS; i++) { + dai[i] = kzalloc(sizeof(struct dai_drv), GFP_KERNEL); + if (!dai[0]) { + pr_debug("Allocation failed for dma_channel = 0\n"); + return -ENODEV; + } + } + dai_info.base = ioremap(src->start, (src->end - src->start) + 1); + pr_debug("%s: msm-dai: 0x%08x\n", __func__, + (unsigned int)dai_info.base); + spin_lock_init(&dai_lock); +error: + return rc; +} + +static int dai_remove(struct platform_device *pdev) +{ + iounmap(dai_info.base); + return 0; +} + +static struct platform_driver dai_driver = { + .probe = dai_probe, + .remove = dai_remove, + .driver = { + .name = "msm-dai", + .owner = THIS_MODULE + }, +}; + +static struct resource msm_lpa_resources[] = { + { + .start = MSM_LPA_PHYS, + .end = MSM_LPA_END, + .flags = IORESOURCE_MEM, + .name = "msm-dai", + }, +}; + +static struct platform_device *codec_device; + +static int msm_dai_dev_register(const char *name) +{ + int ret = 0; + + pr_debug("%s : called\n", __func__); + codec_device = platform_device_alloc(name, -1); + if (codec_device == NULL) { + pr_debug("Failed to allocate %s\n", name); + return -ENODEV; + } + + platform_set_drvdata(codec_device, (void *)&dai_info); + platform_device_add_resources(codec_device, &msm_lpa_resources[0], + ARRAY_SIZE(msm_lpa_resources)); + ret = platform_device_add(codec_device); + if (ret != 0) { + pr_debug("Failed to register %s: %d\n", name, ret); + platform_device_put(codec_device); + } + return ret; +} + +static int __init dai_init(void) +{ + if (msm_dai_dev_register("msm-dai")) { + pr_notice("dai_init: msm-dai Failed"); + return -ENODEV; + } + return platform_driver_register(&dai_driver); +} + +static void __exit dai_exit(void) +{ + platform_driver_unregister(&dai_driver); + platform_device_put(codec_device); +} + +module_init(dai_init); +module_exit(dai_exit); + +MODULE_DESCRIPTION("MSM I2S driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8660-i2s.c b/sound/soc/msm/msm8660-i2s.c new file mode 100644 index 00000000000..9583c52c9a6 --- /dev/null +++ b/sound/soc/msm/msm8660-i2s.c @@ -0,0 +1,139 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int msm_cpu_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + uint32_t dma_ch = dai->id; + int ret = 0; + + pr_debug("%s\n", __func__); + ret = dai_open(dma_ch); + return ret; + +} + +static void msm_cpu_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + uint32_t dma_ch = dai->id; + + pr_debug("%s\n", __func__); + dai_close(dma_ch); +} + +static int msm_cpu_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + pr_debug("%s\n", __func__); + return 0; +} + +static int msm_cpu_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + uint32_t dma_ch = dai->id; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + dai_set_master_mode(dma_ch, 1); /* CPU is master */ + break; + case SND_SOC_DAIFMT_CBM_CFM: + dai_set_master_mode(dma_ch, 0); /* CPU is slave */ + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct snd_soc_dai_ops msm_cpu_dai_ops = { + .startup = msm_cpu_dai_startup, + .shutdown = msm_cpu_dai_shutdown, + .trigger = msm_cpu_dai_trigger, + .set_fmt = msm_cpu_dai_fmt, + +}; + + +#define MSM_DAI_SPEAKER_BUILDER(link_id) \ +{ \ + .name = "msm-speaker-dai-"#link_id, \ + .id = (link_id), \ + .playback = { \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + .channels_min = 1, \ + .channels_max = 2, \ + .rate_max = 96000, \ + .rate_min = 8000, \ + }, \ + .ops = &msm_cpu_dai_ops, \ +} + + +#define MSM_DAI_MIC_BUILDER(link_id) \ +{ \ + .name = "msm-mic-dai-"#link_id, \ + .id = (link_id), \ + .capture = { \ + .rates = SNDRV_PCM_RATE_8000_96000, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE, \ + .rate_min = 8000, \ + .rate_max = 96000, \ + .channels_min = 1, \ + .channels_max = 2, \ + }, \ + .ops = &msm_cpu_dai_ops, \ +} + + +struct snd_soc_dai msm_cpu_dai[] = { + MSM_DAI_SPEAKER_BUILDER(0), + MSM_DAI_SPEAKER_BUILDER(1), + MSM_DAI_SPEAKER_BUILDER(2), + MSM_DAI_SPEAKER_BUILDER(3), + MSM_DAI_SPEAKER_BUILDER(4), + MSM_DAI_MIC_BUILDER(5), + MSM_DAI_MIC_BUILDER(6), + MSM_DAI_MIC_BUILDER(7), +}; +EXPORT_SYMBOL_GPL(msm_cpu_dai); + +static int __init msm_cpu_dai_init(void) +{ + return snd_soc_register_dais(msm_cpu_dai, ARRAY_SIZE(msm_cpu_dai)); +} +module_init(msm_cpu_dai_init); + +static void __exit msm_cpu_dai_exit(void) +{ + snd_soc_unregister_dais(msm_cpu_dai, ARRAY_SIZE(msm_cpu_dai)); +} +module_exit(msm_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM CPU DAI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8660-pcm.c b/sound/soc/msm/msm8660-pcm.c new file mode 100644 index 00000000000..6f6fe4360fc --- /dev/null +++ b/sound/soc/msm/msm8660-pcm.c @@ -0,0 +1,369 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm8660-pcm.h" + +static const struct snd_pcm_hardware msm_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = 32, + .period_bytes_max = DMASZ/4, + .buffer_bytes_max = DMASZ, + .rate_max = 96000, + .rate_min = 8000, + .channels_min = USE_CHANNELS_MIN, + .channels_max = USE_CHANNELS_MAX, + .periods_min = 4, + .periods_max = 512, + .fifo_size = 0, +}; + +struct msm_pcm_data { + spinlock_t lock; + int ch; +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + + pr_debug("%s\n", __func__); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static irqreturn_t msm_pcm_irq(int intrsrc, void *data) +{ + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + int dma_ch = 0; + unsigned int has_xrun, pending; + int ret = IRQ_NONE; + + if (prtd) + dma_ch = prtd->dma_ch; + else + return ret; + + pr_debug("msm8660-pcm: msm_pcm_irq called\n"); + pending = (intrsrc + & (UNDER_CH(dma_ch) | PER_CH(dma_ch) | ERR_CH(dma_ch))); + has_xrun = (pending & UNDER_CH(dma_ch)); + + if (unlikely(has_xrun) && + substream->runtime && + snd_pcm_running(substream)) { + pr_err("xrun\n"); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + ret = IRQ_HANDLED; + pending &= ~UNDER_CH(dma_ch); + } + + + if (pending & PER_CH(dma_ch)) { + ret = IRQ_HANDLED; + if (likely(substream->runtime && + snd_pcm_running(substream))) { + /* end of buffer missed? loop back */ + if (++prtd->period_index >= runtime->periods) + prtd->period_index = 0; + snd_pcm_period_elapsed(substream); + pr_debug("period elapsed\n"); + } + pending &= ~PER_CH(dma_ch); + } + + if (unlikely(pending + & (UNDER_CH(dma_ch) & PER_CH(dma_ch) & ERR_CH(dma_ch)))) { + if (pending & UNDER_CH(dma_ch)) + pr_err("msm8660-pcm: DMA %x Underflow\n", + dma_ch); + if (pending & ERR_CH(dma_ch)) + pr_err("msm8660-pcm: DMA %x Master Error\n", + dma_ch); + + } + return ret; +} + +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + struct dai_dma_params dma_params; + int dma_ch = 0; + + if (prtd) + dma_ch = prtd->dma_ch; + else + return 0; + + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + pr_debug("%s:prtd->pcm_size = %d\n", __func__, prtd->pcm_size); + pr_debug("%s:prtd->pcm_count = %d\n", __func__, prtd->pcm_count); + + if (prtd->enabled) + return 0; + + dma_params.src_start = runtime->dma_addr; + dma_params.buffer = (u8 *)runtime->dma_area; + dma_params.buffer_size = prtd->pcm_size; + dma_params.period_size = prtd->pcm_count; + dma_params.channels = runtime->channels; + + dai_set_params(dma_ch, &dma_params); + register_dma_irq_handler(dma_ch, msm_pcm_irq, (void *)substream); + + prtd->enabled = 1; + return 0; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + int ret = 0; + + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dai_start(prtd->dma_ch); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dai_stop(prtd->dma_ch); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + snd_pcm_uframes_t offset = 0; + + pr_debug("%s: period_index =%d\n", __func__, prtd->period_index); + offset = prtd->period_index * runtime->period_size; + if (offset >= runtime->buffer_size) + offset = 0; + return offset; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct msm_audio *prtd = NULL; + int ret = 0; + + pr_debug("%s\n", __func__); + snd_soc_set_runtime_hwparams(substream, &msm_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + + if (ret < 0) { + pr_err("Error setting hw_constraint\n"); + goto err; + } + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_err("Error snd_pcm_hw_constraint_list failed\n"); + + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + + if (prtd == NULL) { + pr_err("Error allocating prtd\n"); + ret = -ENOMEM; + goto err; + } + prtd->dma_ch = cpu_dai->id; + prtd->enabled = 0; + runtime->dma_bytes = msm_pcm_hardware.buffer_bytes_max; + runtime->private_data = prtd; +err: + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = (struct msm_audio *)runtime->private_data; + int dma_ch = 0; + + if (prtd) + dma_ch = prtd->dma_ch; + else + return 0; + + pr_debug("%s\n", __func__); + unregister_dma_irq_handler(dma_ch); + kfree(runtime->private_data); + return 0; +} + +static int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vms) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + pr_debug("%s\n", __func__); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + pr_debug("%s: snd_msm_audio_hw_params runtime->dma_addr 0x(%x)\n", + __func__, (unsigned int)runtime->dma_addr); + pr_debug("%s: snd_msm_audio_hw_params runtime->dma_area 0x(%x)\n", + __func__, (unsigned int)runtime->dma_area); + pr_debug("%s: snd_msm_audio_hw_params runtime->dma_bytes 0x(%x)\n", + __func__, (unsigned int)runtime->dma_bytes); + + return dma_mmap_coherent(substream->pcm->card->dev, vms, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = msm_pcm_hw_params, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int pcm_preallocate_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = msm_pcm_hardware.buffer_bytes_max; + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void msm_pcm_free_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!stream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_coherent(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} +static u64 msm_pcm_dmamask = DMA_BIT_MASK(32); + +static int msm_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &msm_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (dai->playback.channels_min) { + ret = pcm_preallocate_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + if (dai->capture.channels_min) { + ret = pcm_preallocate_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + return ret; + } + return ret; +} + +struct snd_soc_platform msm8660_soc_platform = { + .name = "msm8660-pcm-audio", + .pcm_ops = &msm_pcm_ops, + .pcm_new = msm_pcm_new, + .pcm_free = msm_pcm_free_buffers, +}; +EXPORT_SYMBOL_GPL(msm8660_soc_platform); + +static int __init msm_soc_platform_init(void) +{ + return snd_soc_register_platform(&msm8660_soc_platform); +} +static void __exit msm_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&msm8660_soc_platform); +} +module_init(msm_soc_platform_init); +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("MSM PCM module"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8660-pcm.h b/sound/soc/msm/msm8660-pcm.h new file mode 100644 index 00000000000..3bec9a7f414 --- /dev/null +++ b/sound/soc/msm/msm8660-pcm.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H + +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +#define NUM_DMAS 9 +#define DMASZ 16384 +#define MAX_CHANNELS 9 + +#define MSM_LPA_PHYS 0x28100000 +#define MSM_LPA_END 0x2810DFFF + + +struct msm_audio { + struct snd_pcm_substream *substream; + + /* data allocated for various buffers */ + char *data; + dma_addr_t phys; + + unsigned int pcm_size; + unsigned int pcm_count; + int enabled; + int period; + int dma_ch; + int period_index; + int start; +}; + +extern struct snd_soc_dai msm_cpu_dai[NUM_DMAS]; +extern struct snd_soc_platform msm8660_soc_platform; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm8660.c b/sound/soc/msm/msm8660.c new file mode 100644 index 00000000000..8469507c8e3 --- /dev/null +++ b/sound/soc/msm/msm8660.c @@ -0,0 +1,342 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm8660-pcm.h" +#include "../codecs/timpani.h" + +#define PM8058_GPIO_BASE NR_MSM_GPIOS +#define PM8901_GPIO_BASE (PM8058_GPIO_BASE + \ + PM8058_GPIOS + PM8058_MPPS) +#define PM8901_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio + PM8901_GPIO_BASE) +#define GPIO_EXPANDER_GPIO_BASE \ + (PM8901_GPIO_BASE + PM8901_MPPS) + +static struct clk *rx_osr_clk; +static struct clk *rx_bit_clk; +static struct clk *tx_osr_clk; +static struct clk *tx_bit_clk; + +static int rx_hw_param_status; +static int tx_hw_param_status; +/* Platform specific logic */ + +static int timpani_rx_route_enable(void) +{ + int ret = 0; + pr_debug("%s\n", __func__); + ret = gpio_request(109, "I2S_Clock"); + if (ret != 0) { + pr_err("%s: I2s clk gpio 109 request" + "failed\n", __func__); + return ret; + } + return ret; +} + +static int timpani_rx_route_disable(void) +{ + int ret = 0; + pr_debug("%s\n", __func__); + gpio_free(109); + return ret; +} + + +#define GPIO_CLASS_D1_EN (GPIO_EXPANDER_GPIO_BASE + 0) +#define PM8901_MPP_3 (2) /* PM8901 MPP starts from 0 */ +static void config_class_d1_gpio(int enable) +{ + int rc; + + if (enable) { + rc = gpio_request(GPIO_CLASS_D1_EN, "CLASSD1_EN"); + if (rc) { + pr_err("%s: spkr pamp gpio %d request" + "failed\n", __func__, GPIO_CLASS_D1_EN); + return; + } + gpio_direction_output(GPIO_CLASS_D1_EN, 1); + gpio_set_value_cansleep(GPIO_CLASS_D1_EN, 1); + } else { + gpio_set_value_cansleep(GPIO_CLASS_D1_EN, 0); + gpio_free(GPIO_CLASS_D1_EN); + } +} + +static void config_class_d0_gpio(int enable) +{ + int rc; + + if (enable) { + rc = pm8901_mpp_config_digital_out(PM8901_MPP_3, + PM8901_MPP_DIG_LEVEL_MSMIO, 1); + + if (rc) { + pr_err("%s: CLASS_D0_EN failed\n", __func__); + return; + } + + rc = gpio_request(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), + "CLASSD0_EN"); + + if (rc) { + pr_err("%s: spkr pamp gpio pm8901 mpp3 request" + "failed\n", __func__); + pm8901_mpp_config_digital_out(PM8901_MPP_3, + PM8901_MPP_DIG_LEVEL_MSMIO, 0); + return; + } + + gpio_direction_output(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), 1); + gpio_set_value_cansleep(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), 1); + + } else { + pm8901_mpp_config_digital_out(PM8901_MPP_3, + PM8901_MPP_DIG_LEVEL_MSMIO, 0); + gpio_set_value_cansleep(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3), 0); + gpio_free(PM8901_GPIO_PM_TO_SYS(PM8901_MPP_3)); + } +} + +static void timpani_poweramp_on(void) +{ + + pr_debug("%s: enable stereo spkr amp\n", __func__); + timpani_rx_route_enable(); + config_class_d0_gpio(1); + config_class_d1_gpio(1); +} + +static void timpani_poweramp_off(void) +{ + + pr_debug("%s: disable stereo spkr amp\n", __func__); + timpani_rx_route_disable(); + config_class_d0_gpio(0); + config_class_d1_gpio(0); +} + +static int msm8660_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int rate = params_rate(params); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (rx_hw_param_status) + return 0; + clk_set_rate(rx_osr_clk, rate * 256); + rx_hw_param_status++; + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (tx_hw_param_status) + return 0; + clk_set_rate(tx_osr_clk, rate * 256); + tx_hw_param_status++; + } + return 0; +} + +static int msm8660_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + rx_osr_clk = clk_get(NULL, "i2s_spkr_osr_clk"); + if (IS_ERR(rx_osr_clk)) { + pr_debug("Failed to get i2s_spkr_osr_clk\n"); + return PTR_ERR(rx_osr_clk); + } + /* Master clock OSR 256 */ + /* Initially set to Lowest sample rate Needed */ + clk_set_rate(rx_osr_clk, 8000 * 256); + ret = clk_enable(rx_osr_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_spkr_osr_clk\n"); + clk_put(rx_osr_clk); + return ret; + } + rx_bit_clk = clk_get(NULL, "i2s_spkr_bit_clk"); + if (IS_ERR(rx_bit_clk)) { + pr_debug("Failed to get i2s_spkr_bit_clk\n"); + clk_disable(rx_osr_clk); + clk_put(rx_osr_clk); + return PTR_ERR(rx_bit_clk); + } + clk_set_rate(rx_bit_clk, 8); + ret = clk_enable(rx_bit_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_spkr_bit_clk\n"); + clk_put(rx_bit_clk); + clk_disable(rx_osr_clk); + clk_put(rx_osr_clk); + return ret; + } + timpani_poweramp_on(); + msleep(30); + /* End of platform specific logic */ + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + tx_osr_clk = clk_get(NULL, "i2s_mic_osr_clk"); + if (IS_ERR(tx_osr_clk)) { + pr_debug("Failed to get i2s_mic_osr_clk\n"); + return PTR_ERR(tx_osr_clk); + } + /* Master clock OSR 256 */ + clk_set_rate(tx_osr_clk, 8000 * 256); + ret = clk_enable(tx_osr_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_mic_osr_clk\n"); + clk_put(tx_osr_clk); + return ret; + } + tx_bit_clk = clk_get(NULL, "i2s_mic_bit_clk"); + if (IS_ERR(tx_bit_clk)) { + pr_debug("Failed to get i2s_mic_bit_clk\n"); + clk_disable(tx_osr_clk); + clk_put(tx_osr_clk); + return PTR_ERR(tx_bit_clk); + } + clk_set_rate(tx_bit_clk, 8); + ret = clk_enable(tx_bit_clk); + if (ret != 0) { + pr_debug("Unable to enable i2s_mic_bit_clk\n"); + clk_put(tx_bit_clk); + clk_disable(tx_osr_clk); + clk_put(tx_osr_clk); + return ret; + } + msm_snddev_enable_dmic_power(); + msleep(30); + } + return ret; +} + +/* + * TODO: rx/tx_hw_param_status should be a counter in the below code + * when driver starts supporting mutisession else setting it to 0 + * will stop audio in all sessions. + */ +static void msm8660_shutdown(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + rx_hw_param_status = 0; + timpani_poweramp_off(); + msleep(30); + if (rx_bit_clk) { + clk_disable(rx_bit_clk); + clk_put(rx_bit_clk); + rx_bit_clk = NULL; + } + if (rx_osr_clk) { + clk_disable(rx_osr_clk); + clk_put(rx_osr_clk); + rx_osr_clk = NULL; + } + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + tx_hw_param_status = 0; + msm_snddev_disable_dmic_power(); + msleep(30); + if (tx_bit_clk) { + clk_disable(tx_bit_clk); + clk_put(tx_bit_clk); + tx_bit_clk = NULL; + } + if (tx_osr_clk) { + clk_disable(tx_osr_clk); + clk_put(tx_osr_clk); + tx_osr_clk = NULL; + } + } +} + +static struct snd_soc_ops machine_ops = { + .startup = msm8660_startup, + .shutdown = msm8660_shutdown, + .hw_params = msm8660_hw_params, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm8660_dai[] = { + { + .name = "Audio Rx", + .stream_name = "Audio Rx", + .cpu_dai = &msm_cpu_dai[0], + .codec_dai = &timpani_codec_dai[0], + .ops = &machine_ops, + }, + { + .name = "Audio Tx", + .stream_name = "Audio Tx", + .cpu_dai = &msm_cpu_dai[5], + .codec_dai = &timpani_codec_dai[1], + .ops = &machine_ops, + } +}; + +struct snd_soc_card snd_soc_card_msm8660 = { + .name = "msm8660-pcm-audio", + .dai_link = msm8660_dai, + .num_links = ARRAY_SIZE(msm8660_dai), + .platform = &msm8660_soc_platform, +}; + +/* msm_audio audio subsystem */ +static struct snd_soc_device msm_snd_devdata = { + .card = &snd_soc_card_msm8660, + .codec_dev = &soc_codec_dev_timpani, +}; + +static struct platform_device *msm_snd_device; + + +static int __init msm_audio_init(void) +{ + int ret; + + msm_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm_snd_device) { + pr_err("Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(msm_snd_device, &msm_snd_devdata); + + msm_snd_devdata.dev = &msm_snd_device->dev; + ret = platform_device_add(msm_snd_device); + if (ret) { + platform_device_put(msm_snd_device); + return ret; + } + + return ret; +} +module_init(msm_audio_init); + +static void __exit msm_audio_exit(void) +{ + platform_device_unregister(msm_snd_device); +} +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MSM8660"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8960.c b/sound/soc/msm/msm8960.c new file mode 100644 index 00000000000..8c0df5c8f5d --- /dev/null +++ b/sound/soc/msm/msm8960.c @@ -0,0 +1,673 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm-pcm-routing.h" +#include <../codecs/wcd9310.h> + +/* 8960 machine driver */ + +#define PM8921_GPIO_BASE NR_GPIO_IRQS +#define PM8921_GPIO_PM_TO_SYS(pm_gpio) (pm_gpio - 1 + PM8921_GPIO_BASE) + +#define MSM_CDC_PAMPL (PM8921_GPIO_PM_TO_SYS(18)) +#define MSM_CDC_PAMPR (PM8921_GPIO_PM_TO_SYS(19)) +#define MSM8960_SPK_ON 1 +#define MSM8960_SPK_OFF 0 + +#define msm8960_SLIM_0_RX_MAX_CHANNELS 2 +#define msm8960_SLIM_0_TX_MAX_CHANNELS 4 + + +static int msm8960_spk_control; +static int msm8960_pamp_on; +static int msm8960_slim_0_rx_ch = 1; +static int msm8960_slim_0_tx_ch = 1; + +struct tabla_mbhc_calibration tabla_cal = { + .bias = TABLA_MICBIAS2, + .tldoh = 100, + .bg_fast_settle = 100, + .mic_current = TABLA_PID_MIC_5_UA, + .mic_pid = 100, + .hph_current = TABLA_PID_MIC_5_UA, + .setup_plug_removal_delay = 1000000, + .shutdown_plug_removal = 100000, +}; + +static struct clk *codec_clk; +static int clk_users; + +static int msm8960_headset_gpios_configured; + +static struct snd_soc_jack hs_jack; + +static void codec_poweramp_on(void) +{ + int ret = 0; + + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + }; + + if (msm8960_pamp_on) + return; + + pr_debug("%s: enable stereo spkr amp\n", __func__); + ret = gpio_request(MSM_CDC_PAMPL, "CDC PAMP1"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + MSM_CDC_PAMPL); + return; + } + ret = pm8xxx_gpio_config(MSM_CDC_PAMPL, ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + MSM_CDC_PAMPL); + else + gpio_direction_output(MSM_CDC_PAMPL, 1); + + ret = gpio_request(MSM_CDC_PAMPR, "CDC PAMPL"); + if (ret) { + pr_err("%s: Error requesting GPIO %d\n", __func__, + MSM_CDC_PAMPR); + gpio_free(MSM_CDC_PAMPL); + return; + } + ret = pm8xxx_gpio_config(MSM_CDC_PAMPR, ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + MSM_CDC_PAMPR); + else + gpio_direction_output(MSM_CDC_PAMPR, 1); + + msm8960_pamp_on = 1; +} +static void codec_poweramp_off(void) +{ + if (!msm8960_pamp_on) + return; + + pr_debug("%s: disable stereo spkr amp\n", __func__); + gpio_direction_output(MSM_CDC_PAMPL, 0); + gpio_free(MSM_CDC_PAMPL); + gpio_direction_output(MSM_CDC_PAMPR, 0); + gpio_free(MSM_CDC_PAMPR); + msm8960_pamp_on = 0; +} +static void msm8960_ext_control(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s: msm8960_spk_control = %d", __func__, msm8960_spk_control); + if (msm8960_spk_control == MSM8960_SPK_ON) + snd_soc_dapm_enable_pin(dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin(dapm, "Ext Spk"); + + snd_soc_dapm_sync(dapm); +} + +static int msm8960_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_spk_control = %d", __func__, msm8960_spk_control); + ucontrol->value.integer.value[0] = msm8960_spk_control; + return 0; +} +static int msm8960_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + pr_debug("%s()\n", __func__); + if (msm8960_spk_control == ucontrol->value.integer.value[0]) + return 0; + + msm8960_spk_control = ucontrol->value.integer.value[0]; + msm8960_ext_control(codec); + return 1; +} +static int msm8960_spkramp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + pr_debug("%s() %x\n", __func__, SND_SOC_DAPM_EVENT_ON(event)); + if (SND_SOC_DAPM_EVENT_ON(event)) + codec_poweramp_on(); + else + codec_poweramp_off(); + return 0; +} + +static const struct snd_soc_dapm_widget msm8960_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", msm8960_spkramp_event), + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Speaker path */ + {"Ext Spk", NULL, "LINEOUT"}, + + /* Microphone path */ + {"AMIC1", NULL, "MIC BIAS1 Internal"}, + {"DMIC1 IN", NULL, "MIC BIAS1 External"}, + {"AMIC2", NULL, "MIC BIAS2 External"}, + {"MIC BIAS1 Internal", NULL, "Handset Mic"}, + {"MIC BIAS1 External", NULL, "Digital Mic1"}, + {"MIC BIAS2 External", NULL, "Headset Mic"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const struct soc_enum msm8960_enum[] = { + SOC_ENUM_SINGLE_EXT(2, spk_function), +}; + +static int msm8960_slim_0_rx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_slim_0_rx_ch = %d", __func__, + msm8960_slim_0_rx_ch); + ucontrol->value.integer.value[0] = msm8960_slim_0_rx_ch; + return 0; +} + +static int msm8960_slim_0_rx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm8960_slim_0_rx_ch = ucontrol->value.integer.value[0]; + + pr_debug("%s: msm8960_slim_0_tx_ch = %d\n", __func__, + msm8960_slim_0_rx_ch); + return 1; +} + +static int msm8960_slim_0_tx_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_debug("%s: msm8960_slim_0_tx_ch = %d", __func__, + msm8960_slim_0_tx_ch); + ucontrol->value.integer.value[0] = msm8960_slim_0_tx_ch; + return 0; +} + +static int msm8960_slim_0_tx_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + msm8960_slim_0_tx_ch = ucontrol->value.integer.value[0]; + + pr_debug("%s: msm8960_slim_0_tx_ch = %d\n", __func__, + msm8960_slim_0_tx_ch); + return 1; +} + + +static const struct snd_kcontrol_new tabla_msm8960_controls[] = { + SOC_ENUM_EXT("Speaker Function", msm8960_enum[0], msm8960_get_spk, + msm8960_set_spk), + SOC_SINGLE_EXT("SLIM_0_RX Channels", 0, 0, + msm8960_SLIM_0_RX_MAX_CHANNELS, 0, + msm8960_slim_0_rx_ch_get, msm8960_slim_0_rx_ch_put), + SOC_SINGLE_EXT("SLIM_0_TX Channels", 0, 0, + msm8960_SLIM_0_TX_MAX_CHANNELS, 0, + msm8960_slim_0_tx_ch_get, msm8960_slim_0_tx_ch_put), +}; + +static int msm8960_audrx_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_debug("%s()\n", __func__); + + err = snd_soc_add_controls(codec, tabla_msm8960_controls, + ARRAY_SIZE(tabla_msm8960_controls)); + if (err < 0) + return err; + + snd_soc_dapm_new_controls(dapm, msm8960_dapm_widgets, + ARRAY_SIZE(msm8960_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Ext Spk"); + + snd_soc_dapm_sync(dapm); + + err = snd_soc_jack_new(codec, "Headset Jack", + SND_JACK_HEADSET, &hs_jack); + if (err) { + pr_err("failed to create new jack\n"); + return err; + } + tabla_hs_detect(codec, &hs_jack, &tabla_cal); + + return 0; +} + +/* + * LPA Needs only RX BE DAI links. + * Hence define seperate BE list for lpa + */ + +static const char *lpa_mm_be[] = { + LPASS_BE_SLIMBUS_0_RX, +}; + +static struct snd_soc_dsp_link lpa_fe_media = { + .supported_be = lpa_mm_be, + .num_be = ARRAY_SIZE(lpa_mm_be), + .fe_playback_channels = 2, + .fe_capture_channels = 1, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, + SND_SOC_DSP_TRIGGER_POST + }, +}; + +static const char *mm_be[] = { + LPASS_BE_SLIMBUS_0_RX, + LPASS_BE_SLIMBUS_0_TX, + LPASS_BE_HDMI, + LPASS_BE_INT_BT_SCO_RX, + LPASS_BE_INT_BT_SCO_TX, + LPASS_BE_INT_FM_RX, + LPASS_BE_INT_FM_TX, +}; + +static struct snd_soc_dsp_link fe_media = { + .supported_be = mm_be, + .num_be = ARRAY_SIZE(mm_be), + .fe_playback_channels = 2, + .fe_capture_channels = 1, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, + SND_SOC_DSP_TRIGGER_POST + }, +}; + +static const char *slimbus0_hl_be[] = { + LPASS_BE_SLIMBUS_0_RX, + LPASS_BE_SLIMBUS_0_TX, +}; + +static struct snd_soc_dsp_link slimbus0_hl_media = { + .supported_be = slimbus0_hl_be, + .num_be = ARRAY_SIZE(slimbus0_hl_be), + .fe_playback_channels = 2, + .fe_capture_channels = 2, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, + SND_SOC_DSP_TRIGGER_POST + }, +}; + +static const char *int_fm_hl_be[] = { + LPASS_BE_INT_FM_RX, + LPASS_BE_INT_FM_TX, +}; + +static struct snd_soc_dsp_link int_fm_hl_media = { + .supported_be = int_fm_hl_be, + .num_be = ARRAY_SIZE(int_fm_hl_be), + .fe_playback_channels = 2, + .fe_capture_channels = 2, + .trigger = { + SND_SOC_DSP_TRIGGER_POST, + SND_SOC_DSP_TRIGGER_POST + }, +}; + +static int msm8960_slim_0_rx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm8960_slim_0_rx_ch; + + return 0; +} + +static int msm8960_slim_0_tx_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + channels->min = channels->max = msm8960_slim_0_tx_ch; + + return 0; +} + +static int msm8960_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + + pr_debug("%s()\n", __func__); + rate->min = rate->max = 48000; + + return 0; +} + +static int msm8960_startup(struct snd_pcm_substream *substream) +{ + if (clk_users++) + return 0; + + codec_clk = clk_get(NULL, "i2s_spkr_osr_clk"); + if (codec_clk) { + clk_set_rate(codec_clk, 12288000); + clk_enable(codec_clk); + } else { + pr_err("%s: Error setting Tabla MCLK\n", __func__); + clk_users--; + return -EINVAL; + } + return 0; +} + +static void msm8960_shutdown(struct snd_pcm_substream *substream) +{ + clk_users--; + if (!clk_users) { + clk_disable(codec_clk); + clk_put(codec_clk); + } +} + +static struct snd_soc_ops msm8960_be_ops = { + .startup = msm8960_startup, + .shutdown = msm8960_shutdown, +}; + +/* Digital audio interface glue - connects codec <---> CPU */ +static struct snd_soc_dai_link msm8960_dai[] = { + /* FrontEnd DAI Links */ + { + .name = "MSM8960 Media1", + .stream_name = "MultiMedia1", + .cpu_dai_name = "MultiMedia1", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .dsp_link = &fe_media, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA1 + }, + { + .name = "MSM8960 Media2", + .stream_name = "MultiMedia2", + .cpu_dai_name = "MultiMedia2", + .platform_name = "msm-pcm-dsp", + .dynamic = 1, + .dsp_link = &fe_media, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA2, + }, + { + .name = "Circuit-Switch Voice", + .stream_name = "CS-Voice", + .cpu_dai_name = "CS-VOICE", + .platform_name = "msm-pcm-voice", + .dynamic = 1, + .dsp_link = &fe_media, + .be_id = MSM_FRONTEND_DAI_CS_VOICE, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + }, + { + .name = "MSM VoIP", + .stream_name = "VoIP", + .cpu_dai_name = "VoIP", + .platform_name = "msm-voip-dsp", + .dynamic = 1, + .dsp_link = &fe_media, + .be_id = MSM_FRONTEND_DAI_VOIP, + }, + { + .name = "MSM8960 LPA", + .stream_name = "LPA", + .cpu_dai_name = "MultiMedia3", + .platform_name = "msm-pcm-lpa", + .dynamic = 1, + .dsp_link = &lpa_fe_media, + .be_id = MSM_FRONTEND_DAI_MULTIMEDIA3, + }, + /* Hostless PMC purpose */ + { + .name = "SLIMBUS_0 Hostless", + .stream_name = "SLIMBUS_0 Hostless", + .cpu_dai_name = "SLIMBUS0_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .dsp_link = &slimbus0_hl_media, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + /* .be_id = do not care */ + }, + { + .name = "INT_FM Hostless", + .stream_name = "INT_FM Hostless", + .cpu_dai_name = "INT_FM_HOSTLESS", + .platform_name = "msm-pcm-hostless", + .dynamic = 1, + .dsp_link = &int_fm_hl_media, + .no_host_mode = SND_SOC_DAI_LINK_NO_HOST, + /* .be_id = do not care */ + }, + /* Backend DAI Links */ + { + .name = LPASS_BE_SLIMBUS_0_RX, + .stream_name = "Slimbus Playback", + .cpu_dai_name = "msm-dai-q6.16384", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_rx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_RX, + .init = &msm8960_audrx_init, + .be_hw_params_fixup = msm8960_slim_0_rx_be_hw_params_fixup, + .ops = &msm8960_be_ops, + }, + { + .name = LPASS_BE_SLIMBUS_0_TX, + .stream_name = "Slimbus Capture", + .cpu_dai_name = "msm-dai-q6.16385", + .platform_name = "msm-pcm-routing", + .codec_name = "tabla_codec", + .codec_dai_name = "tabla_tx1", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_SLIMBUS_0_TX, + .be_hw_params_fixup = msm8960_slim_0_tx_be_hw_params_fixup, + .ops = &msm8960_be_ops, + }, + /* Backend BT/FM DAI Links */ + { + .name = LPASS_BE_INT_BT_SCO_RX, + .stream_name = "Internal BT-SCO Playback", + .cpu_dai_name = "msm-dai-q6.12288", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_RX, + }, + { + .name = LPASS_BE_INT_BT_SCO_TX, + .stream_name = "Internal BT-SCO Capture", + .cpu_dai_name = "msm-dai-q6.12289", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_BT_SCO_TX, + }, + { + .name = LPASS_BE_INT_FM_RX, + .stream_name = "Internal FM Playback", + .cpu_dai_name = "msm-dai-q6.12292", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_RX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + }, + { + .name = LPASS_BE_INT_FM_TX, + .stream_name = "Internal FM Capture", + .cpu_dai_name = "msm-dai-q6.12293", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-tx", + .no_pcm = 1, + .be_id = MSM_BACKEND_DAI_INT_FM_TX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + }, + /* HDMI BACK END DAI Link */ + { + .name = LPASS_BE_HDMI, + .stream_name = "HDMI Playback", + .cpu_dai_name = "msm-dai-q6.8", + .platform_name = "msm-pcm-routing", + .codec_name = "msm-stub-codec.1", + .codec_dai_name = "msm-stub-rx", + .no_pcm = 1, + .no_codec = 1, + .be_id = MSM_BACKEND_DAI_HDMI_RX, + .be_hw_params_fixup = msm8960_be_hw_params_fixup, + }, +}; + +struct snd_soc_card snd_soc_card_msm8960 = { + .name = "msm8960-snd-card", + .dai_link = msm8960_dai, + .num_links = ARRAY_SIZE(msm8960_dai), +}; + +static struct platform_device *msm8960_snd_device; + +static int msm8960_configure_headset_mic_gpios(void) +{ + int ret; + struct pm_gpio param = { + .direction = PM_GPIO_DIR_OUT, + .output_buffer = PM_GPIO_OUT_BUF_CMOS, + .output_value = 1, + .pull = PM_GPIO_PULL_NO, + .vin_sel = PM_GPIO_VIN_S4, + .out_strength = PM_GPIO_STRENGTH_MED, + .function = PM_GPIO_FUNC_NORMAL, + }; + + ret = gpio_request(PM8921_GPIO_PM_TO_SYS(23), "AV_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(23)); + return ret; + } + + ret = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(23), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(23)); + else + gpio_direction_output(PM8921_GPIO_PM_TO_SYS(23), 0); + + ret = gpio_request(PM8921_GPIO_PM_TO_SYS(35), "US_EURO_SWITCH"); + if (ret) { + pr_err("%s: Failed to request gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(35)); + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + return ret; + } + ret = pm8xxx_gpio_config(PM8921_GPIO_PM_TO_SYS(35), ¶m); + if (ret) + pr_err("%s: Failed to configure gpio %d\n", __func__, + PM8921_GPIO_PM_TO_SYS(35)); + else + gpio_direction_output(PM8921_GPIO_PM_TO_SYS(35), 1); + + return 0; +} +static void msm8960_free_headset_mic_gpios(void) +{ + if (msm8960_headset_gpios_configured) { + gpio_free(PM8921_GPIO_PM_TO_SYS(23)); + gpio_free(PM8921_GPIO_PM_TO_SYS(35)); + } +} + +static int __init msm8960_audio_init(void) +{ + int ret; + + msm8960_snd_device = platform_device_alloc("soc-audio", 0); + if (!msm8960_snd_device) { + pr_err("Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(msm8960_snd_device, &snd_soc_card_msm8960); + ret = platform_device_add(msm8960_snd_device); + if (ret) { + platform_device_put(msm8960_snd_device); + return ret; + } + + if (msm8960_configure_headset_mic_gpios()) { + pr_err("%s Fail to configure headset mic gpios\n", __func__); + msm8960_headset_gpios_configured = 0; + } else + msm8960_headset_gpios_configured = 1; + + return ret; + +} +module_init(msm8960_audio_init); + +static void __exit msm8960_audio_exit(void) +{ + msm8960_free_headset_mic_gpios(); + platform_device_unregister(msm8960_snd_device); +} +module_exit(msm8960_audio_exit); + +MODULE_DESCRIPTION("ALSA SoC MSM8960"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8x60-dai.c b/sound/soc/msm/msm8x60-dai.c new file mode 100644 index 00000000000..8130f071443 --- /dev/null +++ b/sound/soc/msm/msm8x60-dai.c @@ -0,0 +1,148 @@ +/* sound/soc/msm/msm-dai.c + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * Derived from msm-pcm.c and msm7201.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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm8x60-pcm.h" + +static struct snd_soc_dai_driver msm_pcm_codec_dais[] = { +{ + .name = "msm-codec-dai", + .playback = { + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = 2, + .rate_min = 8000, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; +static struct snd_soc_dai_driver msm_pcm_cpu_dais[] = { +{ + .name = "msm-cpu-dai", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static struct snd_soc_codec_driver soc_codec_dev_msm = { + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + +static __devinit int asoc_msm_codec_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_msm, + msm_pcm_codec_dais, ARRAY_SIZE(msm_pcm_codec_dais)); +} + +static int __devexit asoc_msm_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static __devinit int asoc_msm_cpu_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_dai(&pdev->dev, msm_pcm_cpu_dais); +} + +static int __devexit asoc_msm_cpu_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver asoc_msm_codec_driver = { + .probe = asoc_msm_codec_probe, + .remove = __devexit_p(asoc_msm_codec_remove), + .driver = { + .name = "msm-codec-dai", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver asoc_msm_cpu_driver = { + .probe = asoc_msm_cpu_probe, + .remove = __devexit_p(asoc_msm_cpu_remove), + .driver = { + .name = "msm-cpu-dai", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_codec_dai_init(void) +{ + return platform_driver_register(&asoc_msm_codec_driver); +} + +static void __exit msm_codec_dai_exit(void) +{ + platform_driver_unregister(&asoc_msm_codec_driver); +} + +static int __init msm_cpu_dai_init(void) +{ + return platform_driver_register(&asoc_msm_cpu_driver); +} + +static void __exit msm_cpu_dai_exit(void) +{ + platform_driver_unregister(&asoc_msm_cpu_driver); +} + +module_init(msm_codec_dai_init); +module_exit(msm_codec_dai_exit); +module_init(msm_cpu_dai_init); +module_exit(msm_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Codec/Cpu Dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8x60-pcm.c b/sound/soc/msm/msm8x60-pcm.c new file mode 100644 index 00000000000..a6f73501ae7 --- /dev/null +++ b/sound/soc/msm/msm8x60-pcm.c @@ -0,0 +1,806 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msm8x60-pcm.h" + +struct snd_msm { + struct snd_card *card; + struct snd_pcm *pcm; +}; + +static struct snd_pcm_hardware msm_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 960 * 10, + .period_bytes_min = 960 * 5, + .period_bytes_max = 960 * 5, + .periods_min = 2, + .periods_max = 2, + .fifo_size = 0, +}; + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +uint32_t in_frame_info[8][2]; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static void alsa_out_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + int ret = 0; + struct msm_audio *prtd = (struct msm_audio *) private_data; + int dev_rate = 48000; + pr_debug("evt_id = 0x%8x\n", evt_id); + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + pr_debug("AUDDEV_EVT_DEV_RDY\n"); + prtd->copp_id = evt_payload->routing_id; + pr_debug("prtd->session_id = %d, copp_id= %d", + prtd->session_id, prtd->copp_id); + if (prtd->copp_id == PCM_RX) + dev_rate = 8000; + + ret = msm_snddev_set_dec(prtd->session_id, prtd->copp_id, 1, + dev_rate, 1); + break; + case AUDDEV_EVT_DEV_RLS: + pr_debug("AUDDEV_EVT_DEV_RLS\n"); + prtd->copp_id = evt_payload->routing_id; + pr_debug("prtd->session_id = %d, copp_id= %d", + prtd->session_id, prtd->copp_id); + if (prtd->copp_id == PCM_RX) + dev_rate = 8000; + + ret = msm_snddev_set_dec(prtd->session_id, prtd->copp_id, 0, + dev_rate, 1); + break; + case AUDDEV_EVT_STREAM_VOL_CHG: + pr_debug("AUDDEV_EVT_STREAM_VOL_CHG\n"); + break; + default: + pr_debug("Unknown Event\n"); + break; + } +} + +static void alsa_in_listener(u32 evt_id, union auddev_evt_data *evt_payload, + void *private_data) +{ + int ret = 0; + struct msm_audio *prtd = (struct msm_audio *) private_data; + int dev_rate = 48000; + pr_debug("evt_id = 0x%8x\n", evt_id); + + switch (evt_id) { + case AUDDEV_EVT_DEV_RDY: + prtd->copp_id = evt_payload->routing_id; + if (prtd->copp_id == PCM_TX) + dev_rate = 8000; + + ret = msm_snddev_set_enc(prtd->session_id, prtd->copp_id, 1, + dev_rate, 1); + break; + case AUDDEV_EVT_DEV_RLS: + prtd->copp_id = evt_payload->routing_id; + if (prtd->copp_id == PCM_TX) + dev_rate = 8000; + + ret = msm_snddev_set_enc(prtd->session_id, prtd->copp_id, 0, + dev_rate, 1); + break; + default: + pr_debug("Unknown Event\n"); + break; + } +} + +static void event_handler(uint32_t opcode, + uint32_t token, uint32_t *payload, void *priv) +{ + struct msm_audio *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + uint32_t *ptrmem = (uint32_t *)payload; + int i = 0; + + pr_debug("%s\n", __func__); + switch (opcode) { + case ASM_DATA_EVENT_WRITE_DONE: { + pr_debug("ASM_DATA_EVENT_WRITE_DONE\n"); + pr_debug("Buffer Consumed = 0x%08x\n", *ptrmem); + prtd->pcm_irq_pos += prtd->pcm_count; + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + atomic_inc(&prtd->out_count); + wake_up(&the_locks.write_wait); + if (!atomic_read(&prtd->start)) + break; + if (!prtd->mmap_flag) + break; + pr_debug("%s:writing %d bytes of buffer to dsp 2\n", + __func__, prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, 0, 0, NO_TIMESTAMP); + break; + } + case ASM_DATA_CMDRSP_EOS: + pr_debug("ASM_DATA_CMDRSP_EOS\n"); + prtd->cmd_ack = 1; + wake_up(&the_locks.eos_wait); + break; + case ASM_DATA_EVENT_READ_DONE: { + pr_debug("ASM_DATA_EVENT_READ_DONE\n"); + pr_debug("token = 0x%08x\n", token); + for (i = 0; i < 8; i++, ++ptrmem) + pr_debug("cmd[%d]=0x%08x\n", i, *ptrmem); + in_frame_info[token][0] = payload[2]; + in_frame_info[token][1] = payload[3]; + prtd->pcm_irq_pos += in_frame_info[token][0]; + pr_debug("pcm_irq_pos=%d\n", prtd->pcm_irq_pos); + if (atomic_read(&prtd->start)) + snd_pcm_period_elapsed(substream); + if (atomic_read(&prtd->in_count) <= prtd->periods) + atomic_inc(&prtd->in_count); + wake_up(&the_locks.read_wait); + if (prtd->mmap_flag) + q6asm_read_nolock(prtd->audio_client); + break; + } + case APR_BASIC_RSP_RESULT: { + if (!prtd->mmap_flag + && !atomic_read(&prtd->out_needed)) + break; + switch (payload[0]) { + case ASM_SESSION_CMD_RUN: + if (substream->stream + != SNDRV_PCM_STREAM_PLAYBACK) + break; + if (prtd->mmap_flag) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + } else { + while (atomic_read(&prtd->out_needed)) { + pr_debug("%s:writing %d bytes" + " of buffer to dsp\n", + __func__, + prtd->pcm_count); + q6asm_write_nolock(prtd->audio_client, + prtd->pcm_count, + 0, 0, NO_TIMESTAMP); + atomic_dec(&prtd->out_needed); + wake_up(&the_locks.write_wait); + }; + } + break; + default: + break; + } + } + break; + default: + pr_debug("Not Supported Event opcode[0x%x]\n", opcode); + break; + } +} + +static int msm_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret; + int dev_rate = 48000; + int i = 0; + + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + if (prtd->enabled) + return 0; + + ret = q6asm_media_format_block_pcm(prtd->audio_client, runtime->rate, + runtime->channels); + if (ret < 0) + pr_debug("%s: CMD Format block failed\n", __func__); + + atomic_set(&prtd->out_count, runtime->periods); + atomic_set(&prtd->in_count, 0); + for (i = 0; i < MAX_COPP; i++) { + pr_debug("prtd->session_id = %d, copp_id= %d", + prtd->session_id, i); + if (session_route.playback_session[substream->number][i] + != DEVICE_IGNORE) { + pr_err("Device active\n"); + if (i == PCM_RX) + dev_rate = 8000; + msm_snddev_set_dec(prtd->session_id, + i, 1, dev_rate, runtime->channels); + } + } + prtd->enabled = 1; + prtd->cmd_ack = 0; + return 0; +} + +static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int ret = 0; + int i = 0; + int dev_rate = 48000; + pr_debug("%s\n", __func__); + prtd->pcm_size = snd_pcm_lib_buffer_bytes(substream); + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + + /* rate and channels are sent to audio driver */ + prtd->samp_rate = runtime->rate; + prtd->channel_mode = runtime->channels; + + if (prtd->enabled) + return 0; + + pr_debug("Samp_rate = %d\n", prtd->samp_rate); + pr_debug("Channel = %d\n", prtd->channel_mode); + ret = q6asm_enc_cfg_blk_pcm(prtd->audio_client, prtd->samp_rate, + prtd->channel_mode); + if (ret < 0) + pr_debug("%s: cmd cfg pcm was block failed", __func__); + + for (i = 0; i < runtime->periods; i++) + q6asm_read_nolock(prtd->audio_client); + prtd->periods = runtime->periods; + for (i = 0; i < MAX_COPP; i++) { + pr_debug("prtd->session_id = %d, copp_id= %d", + prtd->session_id, + session_route.capture_session[prtd->session_id][i]); + if (session_route.capture_session[prtd->session_id][i] + != DEVICE_IGNORE) { + if (i == PCM_RX) + dev_rate = 8000; + msm_snddev_set_enc(prtd->session_id, i, 1, dev_rate, 1); + } + } + prtd->enabled = 1; + + return ret; +} + +static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + pr_debug("%s\n", __func__); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("SNDRV_PCM_TRIGGER_START\n"); + q6asm_run_nowait(prtd->audio_client, 0, 0, 0); + atomic_set(&prtd->start, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("SNDRV_PCM_TRIGGER_STOP\n"); + atomic_set(&prtd->start, 0); + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + break; + prtd->cmd_ack = 0; + q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("SNDRV_PCM_TRIGGER_PAUSE\n"); + q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); + atomic_set(&prtd->start, 0); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int msm_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd; + int ret = 0; + + pr_debug("%s\n", __func__); + prtd = kzalloc(sizeof(struct msm_audio), GFP_KERNEL); + if (prtd == NULL) { + pr_err("Failed to allocate memory for msm_audio\n"); + return -ENOMEM; + } + runtime->hw = msm_pcm_hardware; + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc( + (app_cb)event_handler, prtd); + if (!prtd->audio_client) { + pr_debug("%s: Could not allocate memory\n", __func__); + kfree(prtd); + return -ENOMEM; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm out open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* Capture path */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM); + if (ret < 0) { + pr_err("%s: pcm in open failed\n", __func__); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + return -ENOMEM; + } + } + /* The session id returned by q6asm_open_read above is random and + * hence we cannot use the session id to route from user space. + * This results in need of a hardcoded session id for both playback + * and capture sessions. we can use the subdevice id to identify + * the session and use that for routing. Hence using + * substream->number as the session id for routing purpose. However + * DSP understands the session based on the allocated session id, + * hence using the variable prtd->session_id for all dsp commands. + */ + + prtd->session_id = prtd->audio_client->session; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + prtd->cmd_ack = 1; + prtd->device_events = AUDDEV_EVT_DEV_RDY | + AUDDEV_EVT_STREAM_VOL_CHG | + AUDDEV_EVT_DEV_RLS; + prtd->source = msm_snddev_route_dec(prtd->session_id); + pr_debug("Register device event listener for" + "SNDRV_PCM_STREAM_PLAYBACK session %d\n", + substream->number); + ret = auddev_register_evt_listner(prtd->device_events, + AUDDEV_CLNT_DEC, substream->number, + alsa_out_listener, (void *) prtd); + if (ret) + pr_debug("failed to register device event listener\n"); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + prtd->device_events = AUDDEV_EVT_DEV_RDY | AUDDEV_EVT_DEV_RLS | + AUDDEV_EVT_FREQ_CHG; + prtd->source = msm_snddev_route_enc(prtd->session_id); + pr_debug("Register device event listener for" + "SNDRV_PCM_STREAM_CAPTURE session %d\n", + substream->number); + ret = auddev_register_evt_listner(prtd->device_events, + AUDDEV_CLNT_ENC, substream->number, + alsa_in_listener, (void *) prtd); + if (ret) + pr_debug("failed to register device event listener\n"); + } + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + pr_debug("snd_pcm_hw_constraint_integer failed\n"); + + prtd->dsp_cnt = 0; + runtime->private_data = prtd; + + return 0; +} + +static int msm_pcm_playback_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer = 0; + char *bufptr = NULL; + void *data = NULL; + uint32_t idx = 0; + uint32_t size = 0; + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + fbytes = frames_to_bytes(runtime, frames); + pr_debug("%s: prtd->out_count = %d\n", + __func__, atomic_read(&prtd->out_count)); + ret = wait_event_timeout(the_locks.write_wait, + (atomic_read(&prtd->out_count)), 5 * HZ); + if (ret < 0) { + pr_debug("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + + if (!atomic_read(&prtd->out_count)) { + pr_debug("%s: pcm stopped out_count 0\n", __func__); + return 0; + } + data = q6asm_is_cpu_buf_avail(IN, prtd->audio_client, &size, &idx); + bufptr = data; + if (bufptr) { + pr_debug("%s:fbytes =%d: xfer=%d size=%d\n", + __func__, fbytes, xfer, size); + xfer = fbytes; + if (copy_from_user(bufptr, buf, xfer)) { + ret = -EFAULT; + goto fail; + } + buf += xfer; + fbytes -= xfer; + pr_debug("%s:fbytes = %d: xfer=%d\n", __func__, fbytes, xfer); + if (atomic_read(&prtd->start)) { + pr_debug("%s:writing %d bytes of buffer to dsp\n", + __func__, xfer); + ret = q6asm_write_nolock(prtd->audio_client, xfer, + 0, 0, NO_TIMESTAMP); + if (ret < 0) { + ret = -EFAULT; + goto fail; + } + } else + atomic_inc(&prtd->out_needed); + atomic_dec(&prtd->out_count); + } +fail: + return ret; +} + +static int msm_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int dir = 0; + int ret = 0; + + pr_debug("%s\n", __func__); + + dir = IN; + ret = wait_event_timeout(the_locks.eos_wait, + prtd->cmd_ack, 5 * HZ); + if (ret < 0) + pr_err("%s: CMD_EOS failed\n", __func__); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + + pr_debug("%s\n", __func__); + auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, + substream->number); + pr_debug("%s\n", __func__); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + msm_clear_session_id(prtd->session_id); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_capture_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t hwoff, void __user *buf, + snd_pcm_uframes_t frames) +{ + int ret = 0; + int fbytes = 0; + int xfer; + char *bufptr; + void *data = NULL; + static uint32_t idx; + static uint32_t size; + uint32_t offset = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = substream->runtime->private_data; + + + pr_debug("%s\n", __func__); + fbytes = frames_to_bytes(runtime, frames); + + pr_debug("appl_ptr %d\n", (int)runtime->control->appl_ptr); + pr_debug("hw_ptr %d\n", (int)runtime->status->hw_ptr); + pr_debug("avail_min %d\n", (int)runtime->control->avail_min); + + ret = wait_event_timeout(the_locks.read_wait, + (atomic_read(&prtd->in_count)), 5 * HZ); + if (ret < 0) { + pr_debug("%s: wait_event_timeout failed\n", __func__); + goto fail; + } + if (!atomic_read(&prtd->in_count)) { + pr_debug("%s: pcm stopped in_count 0\n", __func__); + return 0; + } + pr_debug("Checking if valid buffer is available...%08x\n", + (unsigned int) data); + data = q6asm_is_cpu_buf_avail(OUT, prtd->audio_client, &size, &idx); + bufptr = data; + pr_debug("Size = %d\n", size); + pr_debug("fbytes = %d\n", fbytes); + pr_debug("idx = %d\n", idx); + if (bufptr) { + xfer = fbytes; + if (xfer > size) + xfer = size; + offset = in_frame_info[idx][1]; + pr_debug("Offset value = %d\n", offset); + if (copy_to_user(buf, bufptr+offset, xfer)) { + pr_err("Failed to copy buf to user\n"); + ret = -EFAULT; + goto fail; + } + fbytes -= xfer; + size -= xfer; + in_frame_info[idx][1] += xfer; + pr_debug("%s:fbytes = %d: size=%d: xfer=%d\n", + __func__, fbytes, size, xfer); + pr_debug(" Sending next buffer to dsp\n"); + memset(&in_frame_info[idx], 0, + sizeof(uint32_t) * 2); + atomic_dec(&prtd->in_count); + ret = q6asm_read_nolock(prtd->audio_client); + if (ret < 0) { + pr_err("q6asm read failed\n"); + ret = -EFAULT; + goto fail; + } + } else + pr_err("No valid buffer\n"); + + pr_debug("Returning from capture_copy... %d\n", ret); +fail: + return ret; +} + +static int msm_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + int dir = OUT; + + pr_debug("%s\n", __func__); + q6asm_audio_client_buf_free_contiguous(dir, + prtd->audio_client); + auddev_unregister_evt_listner(AUDDEV_CLNT_ENC, + substream->number); + q6asm_cmd(prtd->audio_client, CMD_CLOSE); + msm_clear_session_id(prtd->session_id); + q6asm_audio_client_free(prtd->audio_client); + kfree(prtd); + + return 0; +} + +static int msm_pcm_copy(struct snd_pcm_substream *substream, int a, + snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_copy(substream, a, hwoff, buf, frames); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_copy(substream, a, hwoff, buf, frames); + return ret; +} + +static int msm_pcm_close(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_close(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_close(substream); + return ret; +} +static int msm_pcm_prepare(struct snd_pcm_substream *substream) +{ + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = msm_pcm_playback_prepare(substream); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + ret = msm_pcm_capture_prepare(substream); + return ret; +} + +static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + pr_debug("%s: pcm_irq_pos = %d\n", __func__, prtd->pcm_irq_pos); + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +int msm_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + + pr_debug("%s\n", __func__); + prtd->mmap_flag = 1; + dma_mmap_coherent(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return 0; +} + +int msm_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct msm_audio *prtd = runtime->private_data; + struct snd_dma_buffer *dma_buf = &substream->dma_buffer; + struct audio_buffer *buf; + int dir, ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dir = IN; + else + dir = OUT; + + ret = q6asm_audio_client_buf_alloc_contiguous(dir, + prtd->audio_client, + runtime->hw.period_bytes_min, + runtime->hw.periods_max); + if (ret < 0) { + pr_err("Audio Start: Buffer Allocation failed \ + rc = %d\n", ret); + return -ENOMEM; + } + buf = prtd->audio_client->port[dir].buf; + + pr_debug("%s:buf = %p\n", __func__, buf); + dma_buf->dev.type = SNDRV_DMA_TYPE_DEV; + dma_buf->dev.dev = substream->pcm->card->dev; + dma_buf->private_data = NULL; + dma_buf->area = buf[0].data; + dma_buf->addr = buf[0].phys; + dma_buf->bytes = runtime->hw.buffer_bytes_max; + if (!dma_buf->area) + return -ENOMEM; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static struct snd_pcm_ops msm_pcm_ops = { + .open = msm_pcm_open, + .copy = msm_pcm_copy, + .hw_params = msm_pcm_hw_params, + .close = msm_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = msm_pcm_prepare, + .trigger = msm_pcm_trigger, + .pointer = msm_pcm_pointer, + .mmap = msm_pcm_mmap, +}; + +static int msm_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + int ret = 0; + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, 2); + if (ret) + return ret; + ret = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, 1); + if (ret) + return ret; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &msm_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &msm_pcm_ops); + + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + return ret; +} + +struct snd_soc_platform_driver msm_soc_platform = { + .ops = &msm_pcm_ops, + .pcm_new = msm_pcm_new, +}; +EXPORT_SYMBOL(msm_soc_platform); + +static __devinit int msm_pcm_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_platform(&pdev->dev, + &msm_soc_platform); +} + +static int msm_pcm_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver msm_pcm_driver = { + .probe = msm_pcm_probe, + .remove = __devexit_p(msm_pcm_remove), + .driver = { + .name = "msm-dsp-audio", + .owner = THIS_MODULE, + }, +}; + +static int __init msm_soc_platform_init(void) +{ + return platform_driver_register(&msm_pcm_driver); +} +module_init(msm_soc_platform_init); + +static void __exit msm_soc_platform_exit(void) +{ + platform_driver_unregister(&msm_pcm_driver); +} +module_exit(msm_soc_platform_exit); + +MODULE_DESCRIPTION("PCM module platform driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm8x60-pcm.h b/sound/soc/msm/msm8x60-pcm.h new file mode 100644 index 00000000000..d7c9ad82052 --- /dev/null +++ b/sound/soc/msm/msm8x60-pcm.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2008 HTC Corporation + * Copyright (c) 2010-2011, Code Aurora Forum. 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#ifndef _MSM_PCM_H +#define _MSM_PCM_H +#include +#include + +#define MAX_PLAYBACK_SESSIONS 2 +#define MAX_CAPTURE_SESSIONS 1 +#define MAX_COPP 12 + +extern int copy_count; + +struct buffer { + void *data; + unsigned size; + unsigned used; + unsigned addr; +}; + +struct buffer_rec { + void *data; + unsigned int size; + unsigned int read; + unsigned int addr; +}; + +struct audio_locks { + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + wait_queue_head_t eos_wait; + wait_queue_head_t enable_wait; +}; + +extern struct audio_locks the_locks; + +struct msm_audio { + struct snd_pcm_substream *substream; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + uint16_t source; /* Encoding source bit mask */ + + struct audio_client *audio_client; + + uint16_t session_id; + int copp_id; + + uint32_t samp_rate; + uint32_t channel_mode; + uint32_t dsp_cnt; + + uint32_t device_events; /* device events interested in */ + int abort; /* set when error, like sample rate mismatch */ + + int enabled; + int close_ack; + int cmd_ack; + atomic_t start; + atomic_t out_count; + atomic_t in_count; + atomic_t out_needed; + int periods; + int mmap_flag; +}; + +struct pcm_session { + unsigned short playback_session[MAX_PLAYBACK_SESSIONS][MAX_COPP]; + unsigned short capture_session[MAX_CAPTURE_SESSIONS][MAX_COPP]; +}; + +/* platform data */ +extern struct snd_soc_platform_driver msm_soc_platform; +extern struct pcm_session session_route; + +#endif /*_MSM_PCM_H*/ diff --git a/sound/soc/msm/msm8x60.c b/sound/soc/msm/msm8x60.c new file mode 100644 index 00000000000..59597f9deb7 --- /dev/null +++ b/sound/soc/msm/msm8x60.c @@ -0,0 +1,1108 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOOPBACK_ENABLE 0x1 +#define LOOPBACK_DISABLE 0x0 + +#include "msm8x60-pcm.h" + +static struct platform_device *msm_audio_snd_device; +struct audio_locks the_locks; +EXPORT_SYMBOL(the_locks); +struct msm_volume msm_vol_ctl; +EXPORT_SYMBOL(msm_vol_ctl); +struct pcm_session session_route; +EXPORT_SYMBOL(session_route); +static struct snd_kcontrol_new snd_msm_controls[]; + +char snddev_name[AUDIO_DEV_CTL_MAX_DEV][44]; +#define MSM_MAX_VOLUME 0x2000 +#define MSM_VOLUME_STEP ((MSM_MAX_VOLUME+17)/100) /* 17 added to avoid + more deviation */ +static int device_index; /* Count of Device controls */ +static int simple_control; /* Count of simple controls*/ +static int src_dev; +static int dst_dev; +static int loopback_status; + +static int msm_scontrol_count_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + return 0; +} + +static int msm_scontrol_count_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = simple_control; + return 0; +} + +static int msm_v_call_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int msm_v_call_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_v_call_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int start = ucontrol->value.integer.value[0]; + if (start) + broadcast_event(AUDDEV_EVT_START_VOICE, DEVICE_IGNORE, + SESSION_IGNORE); + else + broadcast_event(AUDDEV_EVT_END_VOICE, DEVICE_IGNORE, + SESSION_IGNORE); + return 0; +} + +static int msm_v_mute_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 2; + return 0; +} + +static int msm_v_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_v_mute_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int mute = ucontrol->value.integer.value[1]; + return msm_set_voice_mute(dir, mute); +} + +static int msm_v_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* Volume */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int msm_v_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_v_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int dir = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + + return msm_set_voice_vol(dir, volume); +} + +static int msm_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; /* Volume */ + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 16383; + return 0; +} +static int msm_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = 0; + int session_id = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + int factor = ucontrol->value.integer.value[2]; + u64 session_mask = 0; + + if (factor > 10000) + return -EINVAL; + + if ((volume < 0) || (volume/factor > 100)) + return -EINVAL; + + volume = (MSM_VOLUME_STEP * volume); + + /* Convert back to original decimal point by removing the 10-base factor + * and discard the fractional portion + */ + + volume = volume/factor; + + if (volume > MSM_MAX_VOLUME) + volume = MSM_MAX_VOLUME; + + /* Only Decoder volume control supported */ + session_mask = (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_DEC-1)); + msm_vol_ctl.volume = volume; + pr_debug("%s:session_id %d, volume %d", __func__, session_id, volume); + broadcast_event(AUDDEV_EVT_STREAM_VOL_CHG, DEVICE_IGNORE, + session_mask); + + return ret; +} + +static int msm_voice_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_voice_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + uint32_t rx_dev_id; + uint32_t tx_dev_id; + struct msm_snddev_info *rx_dev_info; + struct msm_snddev_info *tx_dev_info; + int set = ucontrol->value.integer.value[2]; + u64 session_mask; + + if (!set) + return -EPERM; + /* Rx Device Routing */ + rx_dev_id = ucontrol->value.integer.value[0]; + rx_dev_info = audio_dev_ctrl_find_dev(rx_dev_id); + + if (IS_ERR(rx_dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(rx_dev_info); + return rc; + } + + if (!(rx_dev_info->capability & SNDDEV_CAP_RX)) { + pr_err("%s:First Dev is supposed to be RX\n", __func__); + return -EFAULT; + } + + pr_debug("%s:route cfg %d STREAM_VOICE_RX type\n", + __func__, rx_dev_id); + + msm_set_voc_route(rx_dev_info, AUDIO_ROUTE_STREAM_VOICE_RX, + rx_dev_id); + + session_mask = ((u64)0x1) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_VOC-1)); + + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, rx_dev_id, session_mask); + + + /* Tx Device Routing */ + tx_dev_id = ucontrol->value.integer.value[1]; + tx_dev_info = audio_dev_ctrl_find_dev(tx_dev_id); + + if (IS_ERR(tx_dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(tx_dev_info); + return rc; + } + + if (!(tx_dev_info->capability & SNDDEV_CAP_TX)) { + pr_err("%s:Second Dev is supposed to be Tx\n", __func__); + return -EFAULT; + } + + pr_debug("%s:route cfg %d %d type\n", + __func__, tx_dev_id, AUDIO_ROUTE_STREAM_VOICE_TX); + + msm_set_voc_route(tx_dev_info, AUDIO_ROUTE_STREAM_VOICE_TX, + tx_dev_id); + + broadcast_event(AUDDEV_EVT_DEV_CHG_VOICE, tx_dev_id, session_mask); + + if (rx_dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, rx_dev_id, session_mask); + + if (tx_dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, tx_dev_id, session_mask); + + return rc; +} + +static int msm_voice_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + /* TODO: query Device list */ + return 0; +} + +static int msm_device_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_device_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + int set = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + struct msm_snddev_info *dst_dev_info; + struct msm_snddev_info *src_dev_info; + int tx_freq = 0; + int rx_freq = 0; + u32 set_freq = 0; + + set = ucontrol->value.integer.value[0]; + route_cfg.dev_id = ucontrol->id.numid - device_index; + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dev_info); + return rc; + } + pr_info("%s:device %s set %d\n", __func__, dev_info->name, set); + + if (set) { + if (!dev_info->opened) { + set_freq = dev_info->sample_rate; + if (!msm_device_is_voice(route_cfg.dev_id)) { + msm_get_voc_freq(&tx_freq, &rx_freq); + if (dev_info->capability & SNDDEV_CAP_TX) + set_freq = tx_freq; + + if (set_freq == 0) + set_freq = dev_info->sample_rate; + } else + set_freq = dev_info->sample_rate; + + + pr_err("%s:device freq =%d\n", __func__, set_freq); + rc = dev_info->dev_ops.set_freq(dev_info, set_freq); + if (rc < 0) { + pr_err("%s:device freq failed!\n", __func__); + return rc; + } + dev_info->set_sample_rate = rc; + rc = 0; + rc = dev_info->dev_ops.open(dev_info); + if (rc < 0) { + pr_err("%s:Enabling %s failed\n", + __func__, dev_info->name); + return rc; + } + dev_info->opened = 1; + broadcast_event(AUDDEV_EVT_DEV_RDY, route_cfg.dev_id, + SESSION_IGNORE); + if ((route_cfg.dev_id == src_dev) || + (route_cfg.dev_id == dst_dev)) { + dst_dev_info = audio_dev_ctrl_find_dev( + dst_dev); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + src_dev_info = audio_dev_ctrl_find_dev( + src_dev); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + if ((dst_dev_info->opened) && + (src_dev_info->opened)) { + pr_debug("%d: Enable afe_loopback\n", + __LINE__); + afe_loopback(LOOPBACK_ENABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + loopback_status = 1; + } + } + } + } else { + if (dev_info->opened) { + broadcast_event(AUDDEV_EVT_REL_PENDING, + route_cfg.dev_id, + SESSION_IGNORE); + rc = dev_info->dev_ops.close(dev_info); + if (rc < 0) { + pr_err("%s:Snd device failed close!\n", + __func__); + return rc; + } else { + dev_info->opened = 0; + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + SESSION_IGNORE); + } + if (loopback_status == 1) { + if ((route_cfg.dev_id == src_dev) || + (route_cfg.dev_id == dst_dev)) { + dst_dev_info = audio_dev_ctrl_find_dev( + dst_dev); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + src_dev_info = audio_dev_ctrl_find_dev( + src_dev); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid" + "dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + pr_debug("%d: Disable afe_loopback\n", + __LINE__); + afe_loopback(LOOPBACK_DISABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + loopback_status = 0; + } + } + } + + } + return rc; +} + +static int msm_device_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + + route_cfg.dev_id = ucontrol->id.numid - device_index; + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + + if (IS_ERR(dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dev_info); + return rc; + } + + ucontrol->value.integer.value[0] = dev_info->copp_id; + ucontrol->value.integer.value[1] = dev_info->capability; + + return 0; +} + +static int msm_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; /* Device */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_route_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + /* TODO: query Device list */ + return 0; +} + +static int msm_route_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + int enc_freq = 0; + int requested_freq = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + int session_id = ucontrol->value.integer.value[0]; + int set = ucontrol->value.integer.value[2]; + u64 session_mask = 0; + route_cfg.dev_id = ucontrol->value.integer.value[1]; + + if (ucontrol->id.numid == 2) + route_cfg.stream_type = AUDIO_ROUTE_STREAM_PLAYBACK; + else + route_cfg.stream_type = AUDIO_ROUTE_STREAM_REC; + + pr_debug("%s:route cfg %d %d type for popp %d\n", + __func__, route_cfg.dev_id, route_cfg.stream_type, session_id); + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + + if (IS_ERR(dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dev_info); + return rc; + } + if (route_cfg.stream_type == AUDIO_ROUTE_STREAM_PLAYBACK) { + rc = msm_snddev_set_dec(session_id, dev_info->copp_id, set, + dev_info->sample_rate, dev_info->channel_mode); + session_mask = + (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_DEC-1)); + if (!set) { + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } else { + dev_info->sessions = dev_info->sessions | session_mask; + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + } + } else { + + rc = msm_snddev_set_enc(session_id, dev_info->copp_id, set, + dev_info->sample_rate, dev_info->channel_mode); + session_mask = + (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_ENC-1)); + if (!set) { + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } else { + dev_info->sessions = dev_info->sessions | session_mask; + enc_freq = msm_snddev_get_enc_freq(session_id); + requested_freq = enc_freq; + if (enc_freq > 0) { + rc = msm_snddev_request_freq(&enc_freq, + session_id, + SNDDEV_CAP_TX, + AUDDEV_CLNT_ENC); + pr_debug("%s:sample rate configured %d\ + sample rate requested %d \n", + __func__, enc_freq, requested_freq); + if ((rc <= 0) || (enc_freq != requested_freq)) { + pr_debug("%s:msm_snddev_withdraw_freq\n", + __func__); + rc = msm_snddev_withdraw_freq + (session_id, + SNDDEV_CAP_TX, AUDDEV_CLNT_ENC); + broadcast_event(AUDDEV_EVT_FREQ_CHG, + route_cfg.dev_id, + SESSION_IGNORE); + } + } + if (dev_info->opened) + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + } + } + + if (rc < 0) { + pr_err("%s:device could not be assigned!\n", __func__); + return -EFAULT; + } + + return rc; +} + +static int msm_device_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + return 0; +} + +static int msm_device_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + + dev_info = audio_dev_ctrl_find_dev(dev_id); + ucontrol->value.integer.value[0] = dev_info->dev_volume; + + return 0; +} + +static int msm_device_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = -EPERM; + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + int volume = ucontrol->value.integer.value[1]; + + pr_debug("%s:dev_id = %d, volume = %d\n", __func__, dev_id, volume); + + dev_info = audio_dev_ctrl_find_dev(dev_id); + + if (IS_ERR(dev_info)) { + rc = PTR_ERR(dev_info); + pr_err("%s: audio_dev_ctrl_find_dev failed. %ld \n", + __func__, PTR_ERR(dev_info)); + return rc; + } + + pr_debug("%s:dev_name = %s dev_id = %d, volume = %d\n", + __func__, dev_info->name, dev_id, volume); + + if (dev_info->dev_ops.set_device_volume) + rc = dev_info->dev_ops.set_device_volume(dev_info, volume); + else { + pr_info("%s : device %s does not support device volume " + "control.", __func__, dev_info->name); + return -EPERM; + } + + return rc; +} + +static int msm_reset_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0; + return 0; +} + +static int msm_reset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_reset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + pr_err("%s:Resetting all devices\n", __func__); + return msm_reset_all_device(); +} + +static int msm_anc_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int msm_anc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_anc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = -EPERM; + struct msm_snddev_info *dev_info; + + int dev_id = ucontrol->value.integer.value[0]; + int enable = ucontrol->value.integer.value[1]; + + pr_debug("%s: dev_id = %d, enable = %d\n", __func__, dev_id, enable); + dev_info = audio_dev_ctrl_find_dev(dev_id); + + if (IS_ERR(dev_info)) { + rc = PTR_ERR(dev_info); + pr_err("%s: audio_dev_ctrl_find_dev failed. %ld\n", + __func__, PTR_ERR(dev_info)); + return rc; + } + + if (dev_info->dev_ops.enable_anc) { + rc = dev_info->dev_ops.enable_anc(dev_info, enable); + } else { + pr_info("%s : device %s does not support anc control.", + __func__, dev_info->name); + return -EPERM; + } + + return rc; +} + +static int pcm_route_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + /* + * First parameter is session id ~ subdevice number + * Second parameter is device id. + */ + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int pcm_route_get_rx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int pcm_route_get_tx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return 0; +} + +static int pcm_route_put_rx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int session_id = 0; + int set = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + u64 session_mask = 0; + + /* + * session id is incremented by one and stored as session id 0 + * is being used by dsp currently. whereas user space would use + * subdevice number as session id. + */ + session_id = ucontrol->value.integer.value[0]; + route_cfg.dev_id = ucontrol->value.integer.value[1]; + set = ucontrol->value.integer.value[2]; + + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + pr_err("pass invalid dev_id %d\n", route_cfg.dev_id); + return PTR_ERR(dev_info); + } + if (!(dev_info->capability & SNDDEV_CAP_RX)) + return -EINVAL; + session_mask = + (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_DEC-1)); + if (!set) { + session_route.playback_session[session_id][dev_info->copp_id] + = DEVICE_IGNORE; + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + return 0; + } + pr_debug("%s:Routing playback session %d to %s\n", + __func__, (session_id), + dev_info->name); + session_route.playback_session[session_id][dev_info->copp_id] = + dev_info->copp_id; + if (dev_info->opened) { + dev_info->sessions = dev_info->sessions | session_mask; + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + } else { + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } + return 0; +} + +static int pcm_route_put_tx(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int session_id = 0; + int set = 0; + struct msm_audio_route_config route_cfg; + struct msm_snddev_info *dev_info; + u64 session_mask = 0; + + session_id = ucontrol->value.integer.value[0]; + route_cfg.dev_id = ucontrol->value.integer.value[1]; + set = ucontrol->value.integer.value[2]; + + dev_info = audio_dev_ctrl_find_dev(route_cfg.dev_id); + if (IS_ERR(dev_info)) { + pr_err("pass invalid dev_id %d\n", route_cfg.dev_id); + return PTR_ERR(dev_info); + } + pr_debug("%s:Routing capture session %d to %s\n", __func__, + session_id, + dev_info->name); + if (!(dev_info->capability & SNDDEV_CAP_TX)) + return -EINVAL; + session_mask = + (((u64)0x1) << session_id) << (MAX_BIT_PER_CLIENT * \ + ((int)AUDDEV_CLNT_ENC-1)); + if (!set) { + session_route.capture_session[session_id][dev_info->copp_id] + = DEVICE_IGNORE; + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + return 0; + } + + session_route.capture_session[session_id][dev_info->copp_id] = + dev_info->copp_id; + if (dev_info->opened) { + dev_info->sessions = dev_info->sessions | session_mask; + broadcast_event(AUDDEV_EVT_DEV_RDY, + route_cfg.dev_id, + session_mask); + } else { + broadcast_event(AUDDEV_EVT_DEV_RLS, + route_cfg.dev_id, + session_mask); + dev_info->sessions &= ~(session_mask); + } + return 0; +} + +static int msm_loopback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = msm_snddev_devcount(); + return 0; +} + +static int msm_loopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = 0; + return 0; +} + +static int msm_loopback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int rc = 0; + struct msm_snddev_info *src_dev_info = NULL; /* TX device */ + struct msm_snddev_info *dst_dev_info = NULL; /* RX device */ + int dst_dev_id = ucontrol->value.integer.value[0]; + int src_dev_id = ucontrol->value.integer.value[1]; + int set = ucontrol->value.integer.value[2]; + + pr_debug("%s: dst=%d :src=%d set=%d\n", __func__, + dst_dev_id, src_dev_id, set); + + dst_dev_info = audio_dev_ctrl_find_dev(dst_dev_id); + if (IS_ERR(dst_dev_info)) { + pr_err("dst_dev:%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(dst_dev_info); + return rc; + } + if (!(dst_dev_info->capability & SNDDEV_CAP_RX)) { + pr_err("Destination device %d is not RX device\n", + dst_dev_id); + return -EFAULT; + } + + src_dev_info = audio_dev_ctrl_find_dev(src_dev_id); + if (IS_ERR(src_dev_info)) { + pr_err("src_dev:%s:pass invalid dev_id\n", __func__); + rc = PTR_ERR(src_dev_info); + return rc; + } + if (!(src_dev_info->capability & SNDDEV_CAP_TX)) { + pr_err("Source device %d is not TX device\n", src_dev_id); + return -EFAULT; + } + + if (set) { + pr_debug("%s:%d:Enabling AFE_Loopback\n", __func__, __LINE__); + src_dev = src_dev_id; + dst_dev = dst_dev_id; + loopback_status = 1; + if ((dst_dev_info->opened) && (src_dev_info->opened)) + rc = afe_loopback(LOOPBACK_ENABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + } else { + pr_debug("%s:%d:Disabling AFE_Loopback\n", __func__, __LINE__); + src_dev = DEVICE_IGNORE; + dst_dev = DEVICE_IGNORE; + loopback_status = 0; + rc = afe_loopback(LOOPBACK_DISABLE, + dst_dev_info->copp_id, + src_dev_info->copp_id); + } + return rc; +} + +static struct snd_kcontrol_new snd_dev_controls[AUDIO_DEV_CTL_MAX_DEV]; + +static int snd_dev_ctl_index(int idx) +{ + struct msm_snddev_info *dev_info; + + dev_info = audio_dev_ctrl_find_dev(idx); + if (IS_ERR(dev_info)) { + pr_err("%s:pass invalid dev_id\n", __func__); + return PTR_ERR(dev_info); + } + if (sizeof(dev_info->name) <= 44) + sprintf(&snddev_name[idx][0] , "%s", dev_info->name); + + snd_dev_controls[idx].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + snd_dev_controls[idx].access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + snd_dev_controls[idx].name = &snddev_name[idx][0]; + snd_dev_controls[idx].index = idx; + snd_dev_controls[idx].info = msm_device_info; + snd_dev_controls[idx].get = msm_device_get; + snd_dev_controls[idx].put = msm_device_put; + snd_dev_controls[idx].private_value = 0; + return 0; + +} + +#define MSM_EXT(xname, fp_info, fp_get, fp_put, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .name = xname, \ + .info = fp_info,\ + .get = fp_get, .put = fp_put, \ + .private_value = addr, \ +} + +/* If new controls are to be added which would be constant across the + * different targets, please add to the structure + * snd_msm_controls. Please do not add any controls to the structure + * snd_msm_secondary_controls defined below unless they are msm8x60 + * specific. + */ + +static struct snd_kcontrol_new snd_msm_controls[] = { + MSM_EXT("Count", msm_scontrol_count_info, msm_scontrol_count_get, \ + NULL, 0), + MSM_EXT("Stream", msm_route_info, msm_route_get, \ + msm_route_put, 0), + MSM_EXT("Record", msm_route_info, msm_route_get, \ + msm_route_put, 0), + MSM_EXT("Voice", msm_voice_info, msm_voice_get, \ + msm_voice_put, 0), + MSM_EXT("Volume", msm_volume_info, msm_volume_get, \ + msm_volume_put, 0), + MSM_EXT("VoiceVolume", msm_v_volume_info, msm_v_volume_get, \ + msm_v_volume_put, 0), + MSM_EXT("VoiceMute", msm_v_mute_info, msm_v_mute_get, \ + msm_v_mute_put, 0), + MSM_EXT("Voice Call", msm_v_call_info, msm_v_call_get, \ + msm_v_call_put, 0), + MSM_EXT("Device_Volume", msm_device_volume_info, + msm_device_volume_get, msm_device_volume_put, 0), + MSM_EXT("Reset", msm_reset_info, + msm_reset_get, msm_reset_put, 0), + MSM_EXT("ANC", msm_anc_info, msm_anc_get, msm_anc_put, 0), +}; + +static struct snd_kcontrol_new snd_msm_secondary_controls[] = { + MSM_EXT("PCM Playback Sink", + pcm_route_info, pcm_route_get_rx, pcm_route_put_rx, 0), + MSM_EXT("PCM Capture Source", + pcm_route_info, pcm_route_get_tx, pcm_route_put_tx, 0), + MSM_EXT("Sound Device Loopback", msm_loopback_info, + msm_loopback_get, msm_loopback_put, 0), +}; + +static int msm_new_mixer(struct snd_soc_codec *codec) +{ + unsigned int idx; + int err; + int dev_cnt; + + strcpy(codec->card->snd_card->mixername, "MSM Mixer"); + for (idx = 0; idx < ARRAY_SIZE(snd_msm_controls); idx++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_msm_controls[idx], + NULL)); + if (err < 0) + pr_err("%s:ERR adding ctl\n", __func__); + } + + for (idx = 0; idx < ARRAY_SIZE(snd_msm_secondary_controls); idx++) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_msm_secondary_controls[idx], + NULL)); + if (err < 0) + pr_err("%s:ERR adding secondary ctl\n", __func__); + } + dev_cnt = msm_snddev_devcount(); + + for (idx = 0; idx < dev_cnt; idx++) { + if (!snd_dev_ctl_index(idx)) { + err = snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&snd_dev_controls[idx], + NULL)); + if (err < 0) + pr_err("%s:ERR adding ctl\n", __func__); + } else + return 0; + } + simple_control = ARRAY_SIZE(snd_msm_controls) + + ARRAY_SIZE(snd_msm_secondary_controls); + device_index = simple_control + 1; + return 0; +} + +static int msm_soc_dai_init( + struct snd_soc_pcm_runtime *rtd) +{ + + int ret = 0; + struct snd_soc_codec *codec = rtd->codec; + + init_waitqueue_head(&the_locks.enable_wait); + init_waitqueue_head(&the_locks.eos_wait); + init_waitqueue_head(&the_locks.write_wait); + init_waitqueue_head(&the_locks.read_wait); + memset(&session_route, DEVICE_IGNORE, sizeof(struct pcm_session)); + + ret = msm_new_mixer(codec); + if (ret < 0) + pr_err("%s: ALSA MSM Mixer Fail\n", __func__); + + return ret; +} + +static struct snd_soc_dai_link msm_dai[] = { +{ + .name = "MSM Primary I2S", + .stream_name = "DSP 1", + .cpu_dai_name = "msm-cpu-dai.0", + .platform_name = "msm-dsp-audio.0", + .codec_name = "msm-codec-dai.0", + .codec_dai_name = "msm-codec-dai", + .init = &msm_soc_dai_init, +}, +#ifdef CONFIG_MSM_8x60_VOIP +{ + .name = "MSM Primary Voip", + .stream_name = "MVS", + .cpu_dai_name = "mvs-cpu-dai.0", + .platform_name = "msm-mvs-audio.0", + .codec_name = "mvs-codec-dai.0", + .codec_dai_name = "mvs-codec-dai", +}, +#endif +}; + +static struct snd_soc_card snd_soc_card_msm = { + .name = "msm-audio", + .dai_link = msm_dai, + .num_links = ARRAY_SIZE(msm_dai), +}; + +static int __init msm_audio_init(void) +{ + int ret; + + msm_audio_snd_device = platform_device_alloc("soc-audio", -1); + if (!msm_audio_snd_device) + return -ENOMEM; + + platform_set_drvdata(msm_audio_snd_device, &snd_soc_card_msm); + ret = platform_device_add(msm_audio_snd_device); + if (ret) { + platform_device_put(msm_audio_snd_device); + return ret; + } + + src_dev = DEVICE_IGNORE; + dst_dev = DEVICE_IGNORE; + + return ret; +} + +static void __exit msm_audio_exit(void) +{ + platform_device_unregister(msm_audio_snd_device); +} + +module_init(msm_audio_init); +module_exit(msm_audio_exit); + +MODULE_DESCRIPTION("PCM module"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/msm_audio_mvs.h b/sound/soc/msm/msm_audio_mvs.h new file mode 100644 index 00000000000..728621aebd5 --- /dev/null +++ b/sound/soc/msm/msm_audio_mvs.h @@ -0,0 +1,368 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#ifndef __MSM_AUDIO_MVS_H +#define __MSM_AUDIO_MVS_H +#include +#include +#include +#include +#include + + +#define AUDIO_GET_MVS_CONFIG _IOW(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 0), unsigned) +#define AUDIO_SET_MVS_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 1), unsigned) +#define AUDIO_SET_SCR_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 2), unsigned) +#define AUDIO_SET_DTX_CONFIG _IOR(AUDIO_IOCTL_MAGIC, \ + (AUDIO_MAX_COMMON_IOCTL_NUM + 3), unsigned) +/* MVS modes */ +#define MVS_MODE_LINEAR_PCM 9 + +#define MVS_PROG 0x30000014 +#define MVS_VERS 0x00030001 + +#define MVS_CLIENT_ID_VOIP 0x00000003 /* MVS_CLIENT_VOIP */ + +#define MVS_ACQUIRE_PROC 4 +#define MVS_ENABLE_PROC 5 +#define MVS_RELEASE_PROC 6 +#define MVS_SET_PCM_MODE_PROC 9 + +#define MVS_EVENT_CB_TYPE_PROC 1 +#define MVS_PACKET_UL_FN_TYPE_PROC 2 +#define MVS_PACKET_DL_FN_TYPE_PROC 3 + +#define MVS_CB_FUNC_ID 0xAAAABBBB +#define MVS_UL_CB_FUNC_ID 0xBBBBCCCC +#define MVS_DL_CB_FUNC_ID 0xCCCCDDDD + +/* MVS frame modes */ + +#define MVS_FRAME_MODE_PCM_UL 13 +#define MVS_FRAME_MODE_PCM_DL 14 + +/* MVS context */ +#define MVS_PKT_CONTEXT_ISR 0x00000001 + +/* Max voc packet size */ +#define MVS_MAX_VOC_PKT_SIZE 320 + +#define VOIP_MAX_Q_LEN 20 +#define MVS_MAX_Q_LEN 8 +#define RPC_TYPE_REQUEST 0 +#define RPC_TYPE_REPLY 1 + +#define RPC_STATUS_FAILURE 0 +#define RPC_STATUS_SUCCESS 1 +#define RPC_STATUS_REJECT 1 + + +#define RPC_COMMON_HDR_SZ (sizeof(uint32_t) * 2) +#define RPC_REQUEST_HDR_SZ (sizeof(struct rpc_request_hdr)) +#define RPC_REPLY_HDR_SZ (sizeof(uint32_t) * 3) + + +enum audio_mvs_state_type { AUDIO_MVS_CLOSED, AUDIO_MVS_OPENED, + AUDIO_MVS_PREPARING, AUDIO_MVS_ACQUIRE, AUDIO_MVS_ENABLED, + AUDIO_MVS_CLOSING +}; + +enum audio_mvs_event_type { AUDIO_MVS_COMMAND, AUDIO_MVS_MODE, + AUDIO_MVS_NOTIFY +}; + +enum audio_mvs_cmd_status_type { AUDIO_MVS_CMD_FAILURE, AUDIO_MVS_CMD_BUSY, + AUDIO_MVS_CMD_SUCCESS +}; + +enum audio_mvs_mode_status_type { AUDIO_MVS_MODE_NOT_AVAIL, + AUDIO_MVS_MODE_INIT, AUDIO_MVS_MODE_READY +}; + +enum audio_mvs_pkt_status_type { AUDIO_MVS_PKT_NORMAL, AUDIO_MVS_PKT_FAST, + AUDIO_MVS_PKT_SLOW +}; + +struct rpc_audio_mvs_acquire_args { + uint32_t client_id; + uint32_t cb_func_id; +}; + +struct audio_mvs_acquire_msg { + struct rpc_request_hdr rpc_hdr; + struct rpc_audio_mvs_acquire_args acquire_args; +}; + +struct rpc_audio_mvs_enable_args { + uint32_t client_id; + uint32_t mode; + uint32_t ul_cb_func_id; + uint32_t dl_cb_func_id; + uint32_t context; +}; + +struct audio_mvs_enable_msg { + struct rpc_request_hdr rpc_hdr; + struct rpc_audio_mvs_enable_args enable_args; +}; + +struct audio_mvs_release_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t client_id; +}; + +struct audio_mvs_set_pcm_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t pcm_mode; +}; + +struct audio_mvs_set_pcmwb_mode_msg { + struct rpc_request_hdr rpc_hdr; + uint32_t pcmwb_mode; +}; + +struct audio_mvs_buffer { + uint8_t *voc_pkt; + uint32_t len; +}; + +union audio_mvs_event_data { + struct mvs_ev_command_type { + uint32_t event; + uint32_t client_id; + uint32_t cmd_status; + } mvs_ev_command_type; + + struct mvs_ev_mode_type { + uint32_t event; + uint32_t client_id; + uint32_t mode_status; + uint32_t mode; + } mvs_ev_mode_type; + + struct mvs_ev_notify_type { + uint32_t event; + uint32_t client_id; + uint32_t buf_dir; + uint32_t max_frames; + } mvs_ev_notify_type; +}; + +struct audio_mvs_cb_func_args { + uint32_t cb_func_id; + uint32_t valid_ptr; + uint32_t event; + union audio_mvs_event_data event_data; +}; + +struct audio_mvs_frame_info_hdr { + uint32_t frame_mode; + uint32_t mvs_mode; + uint32_t buf_free_cnt; +}; + +struct audio_mvs_ul_cb_func_args { + uint32_t cb_func_id; + uint32_t pkt_len; + uint32_t voc_pkt[MVS_MAX_VOC_PKT_SIZE / 4]; + + uint32_t valid_ptr; + + uint32_t frame_mode; + uint32_t frame_mode_ignore; + + struct audio_mvs_frame_info_hdr frame_info_hdr; + + uint32_t pcm_frame; + uint32_t pcm_mode; + + uint32_t pkt_len_ignore; +}; + +struct audio_mvs_ul_reply { + struct rpc_reply_hdr reply_hdr; + uint32_t valid_pkt_status_ptr; + uint32_t pkt_status; +}; + +struct audio_mvs_dl_cb_func_args { + uint32_t cb_func_id; + uint32_t valid_ptr; + + uint32_t frame_mode; + uint32_t frame_mode_ignore; + + struct audio_mvs_frame_info_hdr frame_info_hdr; + + uint32_t pcm_frame; + uint32_t pcm_mode; + +}; + +struct audio_mvs_dl_reply { + struct rpc_reply_hdr reply_hdr; + uint32_t voc_pkt[MVS_MAX_VOC_PKT_SIZE / 4]; + uint32_t valid_frame_info_ptr; + + uint32_t frame_mode; + uint32_t frame_mode_again; + + struct audio_mvs_frame_info_hdr frame_info_hdr; + + uint32_t pcm_frame; + uint32_t pcm_mode; + + uint32_t valid_pkt_status_ptr; + uint32_t pkt_status; +}; + +struct audio_mvs_info_type { + enum audio_mvs_state_type state; + uint32_t frame_mode; + uint32_t mvs_mode; + uint32_t buf_free_cnt; + uint32_t pcm_frame; + uint32_t pcm_mode; + uint32_t out_sample_rate; + uint32_t out_channel_mode; + uint32_t out_weight; + uint32_t out_buffer_size; + int dl_play; + struct msm_rpc_endpoint *rpc_endpt; + uint32_t rpc_prog; + uint32_t rpc_ver; + uint32_t rpc_status; + + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_playback_irq_pos; /* IRQ position */ + unsigned int pcm_playback_buf_pos; /* position in buffer */ + + unsigned int pcm_capture_size; + unsigned int pcm_capture_count; + unsigned int pcm_capture_irq_pos; /* IRQ position */ + unsigned int pcm_capture_buf_pos; /* position in buffer */ + + uint32_t samp_rate; + uint32_t channel_mode; + + uint8_t *mem_chunk; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct audio_mvs_buffer in[MVS_MAX_Q_LEN]; + uint32_t in_read; + uint32_t in_write; + + struct audio_mvs_buffer out[MVS_MAX_Q_LEN]; + uint32_t out_read; + uint32_t out_write; + + struct task_struct *task; + + wait_queue_head_t wait; + wait_queue_head_t prepare_wait; + wait_queue_head_t out_wait; + wait_queue_head_t in_wait; + + + struct mutex lock; + struct mutex prepare_lock; + struct mutex in_lock; + struct mutex out_lock; + + struct wake_lock suspend_lock; + struct wake_lock idle_lock; + struct timer_list timer; + unsigned long expiry; + int ack_dl_count; + int ack_ul_count; + int prepare_ack; + int playback_start; + int capture_start; + unsigned long expiry_delta; + int mvs_enable; + int playback_enable; + int capture_enable; + int instance; + +}; + +struct audio_voip_info_type { + enum audio_mvs_state_type state; + enum audio_mvs_state_type playback_state; + enum audio_mvs_state_type capture_state; + + unsigned int pcm_playback_size; + unsigned int pcm_count; + unsigned int pcm_playback_irq_pos; /* IRQ position */ + unsigned int pcm_playback_buf_pos; /* position in buffer */ + + unsigned int pcm_capture_size; + unsigned int pcm_capture_count; + unsigned int pcm_capture_irq_pos; /* IRQ position */ + unsigned int pcm_capture_buf_pos; /* position in buffer */ + + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct audio_mvs_buffer in[VOIP_MAX_Q_LEN]; + uint32_t in_read; + uint32_t in_write; + + struct audio_mvs_buffer out[VOIP_MAX_Q_LEN]; + uint32_t out_read; + uint32_t out_write; + + wait_queue_head_t out_wait; + wait_queue_head_t in_wait; + + struct mutex lock; + struct mutex prepare_lock; + + struct wake_lock suspend_lock; + struct wake_lock idle_lock; + int playback_start; + int capture_start; + int instance; +}; + +enum msm_audio_pcm_frame_type { + MVS_AMR_SPEECH_GOOD, /* Good speech frame */ + MVS_AMR_SPEECH_DEGRADED, /* Speech degraded */ + MVS_AMR_ONSET, /* onset */ + MVS_AMR_SPEECH_BAD, /* Corrupt speech frame (bad CRC) */ + MVS_AMR_SID_FIRST, /* First silence descriptor */ + MVS_AMR_SID_UPDATE, /* Comfort noise frame */ + MVS_AMR_SID_BAD, /* Corrupt SID frame (bad CRC) */ + MVS_AMR_NO_DATA, /* Nothing to transmit */ + MVS_AMR_SPEECH_LOST, /* downlink speech lost */ +}; + +enum msm_audio_dtx_mode_type { MVS_DTX_OFF, MVS_DTX_ON +}; + +struct msm_audio_mvs_config { + uint32_t mvs_mode; + uint32_t bit_rate; +}; + +extern struct snd_soc_dai_driver msm_mvs_dais[2]; +extern struct snd_soc_codec_device soc_codec_dev_msm_mvs; +extern struct snd_soc_platform_driver msm_mvs_soc_platform; +extern struct snd_soc_platform_driver msm_voip_soc_platform; +#endif /* __MSM_AUDIO_MVS_H */ diff --git a/sound/soc/msm/mvs-dai.c b/sound/soc/msm/mvs-dai.c new file mode 100644 index 00000000000..521c5e54be8 --- /dev/null +++ b/sound/soc/msm/mvs-dai.c @@ -0,0 +1,141 @@ +/* Copyright (c) 2010, Code Aurora Forum. 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. + * You should have received a copy of the GNU General Public License + * along with this program; if not, you can find it at http://www.fsf.org. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "msm_audio_mvs.h" + +static struct snd_soc_dai_driver msm_mvs_codec_dais[] = { +{ + .name = "mvs-codec-dai", + .playback = { + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; +static struct snd_soc_dai_driver msm_mvs_cpu_dais[] = { +{ + .name = "mvs-cpu-dai", + .playback = { + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_max = 2, + .rates = (SNDRV_PCM_RATE_8000), + .rate_min = 8000, + .rate_max = 8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static struct snd_soc_codec_driver soc_codec_dev_msm = { + .compress_type = SND_SOC_FLAT_COMPRESSION, +}; + +static __devinit int asoc_mvs_codec_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_msm, + msm_mvs_codec_dais, ARRAY_SIZE(msm_mvs_codec_dais)); +} + +static int __devexit asoc_mvs_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static __devinit int asoc_mvs_cpu_probe(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "%s: dev name %s\n", __func__, dev_name(&pdev->dev)); + return snd_soc_register_dai(&pdev->dev, msm_mvs_cpu_dais); +} + +static int __devexit asoc_mvs_cpu_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver asoc_mvs_codec_driver = { + .probe = asoc_mvs_codec_probe, + .remove = __devexit_p(asoc_mvs_codec_remove), + .driver = { + .name = "mvs-codec-dai", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver asoc_mvs_cpu_driver = { + .probe = asoc_mvs_cpu_probe, + .remove = __devexit_p(asoc_mvs_cpu_remove), + .driver = { + .name = "mvs-cpu-dai", + .owner = THIS_MODULE, + }, +}; + +static int __init mvs_codec_dai_init(void) +{ + return platform_driver_register(&asoc_mvs_codec_driver); +} + +static void __exit mvs_codec_dai_exit(void) +{ + platform_driver_unregister(&asoc_mvs_codec_driver); +} + +static int __init mvs_cpu_dai_init(void) +{ + return platform_driver_register(&asoc_mvs_cpu_driver); +} + +static void __exit mvs_cpu_dai_exit(void) +{ + platform_driver_unregister(&asoc_mvs_cpu_driver); +} + +module_init(mvs_codec_dai_init); +module_exit(mvs_codec_dai_exit); +module_init(mvs_cpu_dai_init); +module_exit(mvs_cpu_dai_exit); + +/* Module information */ +MODULE_DESCRIPTION("MSM Codec/Cpu Dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/msm/qdsp6/Makefile b/sound/soc/msm/qdsp6/Makefile new file mode 100644 index 00000000000..5fd69cc7a2d --- /dev/null +++ b/sound/soc/msm/qdsp6/Makefile @@ -0,0 +1 @@ +obj-y := q6asm.o q6adm.o q6afe.o q6voice.o diff --git a/sound/soc/msm/qdsp6/q6adm.c b/sound/soc/msm/qdsp6/q6adm.c new file mode 100644 index 00000000000..65d1fbc2a3e --- /dev/null +++ b/sound/soc/msm/qdsp6/q6adm.c @@ -0,0 +1,804 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TIMEOUT_MS 1000 +#define AUDIO_RX 0x0 +#define AUDIO_TX 0x1 +#define ASM_MAX_SESSION 0x8 /* To do: define in a header */ +#define RESET_COPP_ID 99 +#define INVALID_COPP_ID 0xFF + +struct adm_ctl { + void *apr; + atomic_t copp_id[AFE_MAX_PORTS]; + atomic_t copp_cnt[AFE_MAX_PORTS]; + atomic_t copp_stat[AFE_MAX_PORTS]; + unsigned long sessions[AFE_MAX_PORTS]; + wait_queue_head_t wait; +}; + +static struct adm_ctl this_adm; + +static int32_t adm_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *payload; + int i, index; + payload = data->payload; + + if (data->opcode == RESET_EVENTS) { + pr_debug("adm_callback: Reset event is received: %d %d apr[%p]\n", + data->reset_event, data->reset_proc, + this_adm.apr); + if (this_adm.apr) { + apr_reset(this_adm.apr); + for (i = 0; i < AFE_MAX_PORTS; i++) { + atomic_set(&this_adm.copp_id[i], + RESET_COPP_ID); + atomic_set(&this_adm.copp_cnt[i], 0); + atomic_set(&this_adm.copp_stat[i], 0); + } + this_adm.apr = NULL; + } + return 0; + } + + pr_debug("%s: code = 0x%x %x %x size = %d\n", __func__, + data->opcode, payload[0], payload[1], + data->payload_size); + + if (data->payload_size) { + index = afe_get_port_index(data->token); + pr_debug("%s: Port ID %d, index %d\n", __func__, + data->token, index); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + pr_debug("APR_BASIC_RSP_RESULT\n"); + switch (payload[0]) { + case ADM_CMD_SET_PARAMS: +#ifdef CONFIG_MSM8X60_RTAC + if (rtac_make_adm_callback(payload, + data->payload_size)) + break; +#endif + case ADM_CMD_COPP_CLOSE: + case ADM_CMD_MEMORY_MAP: + case ADM_CMD_MEMORY_UNMAP: + case ADM_CMD_MEMORY_MAP_REGIONS: + case ADM_CMD_MEMORY_UNMAP_REGIONS: + case ADM_CMD_MATRIX_MAP_ROUTINGS: + pr_debug("ADM_CMD_MATRIX_MAP_ROUTINGS\n"); + atomic_set(&this_adm.copp_stat[index], 1); + wake_up(&this_adm.wait); + break; + default: + pr_err("%s: Unknown Cmd: 0x%x\n", __func__, + payload[0]); + break; + } + return 0; + } + + switch (data->opcode) { + case ADM_CMDRSP_COPP_OPEN: { + struct adm_copp_open_respond *open = data->payload; + if (open->copp_id == INVALID_COPP_ID) { + pr_err("%s: invalid coppid rxed %d\n", + __func__, open->copp_id); + atomic_set(&this_adm.copp_stat[index], 1); + wake_up(&this_adm.wait); + break; + } + atomic_set(&this_adm.copp_id[index], open->copp_id); + atomic_set(&this_adm.copp_stat[index], 1); + pr_debug("%s: coppid rxed=%d\n", __func__, + open->copp_id); + wake_up(&this_adm.wait); + } + break; +#ifdef CONFIG_MSM8X60_RTAC + case ADM_CMDRSP_GET_PARAMS: + pr_debug("ADM_CMDRSP_GET_PARAMS\n"); + rtac_make_adm_callback(payload, + data->payload_size); + break; +#endif + default: + pr_err("%s: Unknown cmd:0x%x\n", __func__, + data->opcode); + break; + } + } + return 0; +} + +void send_cal(int port_id, struct acdb_cal_block *aud_cal) +{ + s32 result; + struct adm_set_params_command adm_params; + int index = afe_get_port_index(port_id); + + pr_debug("%s: Port id %d, index %d\n", __func__, port_id, index); + + if (!aud_cal || aud_cal->cal_size == 0) { + pr_err("%s: No calibration data to send!\n", __func__); + goto done; + } + + adm_params.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(20), APR_PKT_VER); + adm_params.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(adm_params)); + adm_params.hdr.src_svc = APR_SVC_ADM; + adm_params.hdr.src_domain = APR_DOMAIN_APPS; + adm_params.hdr.src_port = port_id; + adm_params.hdr.dest_svc = APR_SVC_ADM; + adm_params.hdr.dest_domain = APR_DOMAIN_ADSP; + adm_params.hdr.dest_port = atomic_read(&this_adm.copp_id[index]); + adm_params.hdr.token = port_id; + adm_params.hdr.opcode = ADM_CMD_SET_PARAMS; + adm_params.payload = aud_cal->cal_paddr; + adm_params.payload_size = aud_cal->cal_size; + + atomic_set(&this_adm.copp_stat[index], 0); + pr_debug("%s: Sending SET_PARAMS payload = 0x%x, size = %d\n", + __func__, adm_params.payload, adm_params.payload_size); + result = apr_send_pkt(this_adm.apr, (uint32_t *)&adm_params); + if (result < 0) { + pr_err("%s: Set params failed port = %d payload = 0x%x\n", + __func__, port_id, aud_cal->cal_paddr); + goto done; + } + /* Wait for the callback */ + result = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!result) + pr_err("%s: Set params timed out port = %d, payload = 0x%x\n", + __func__, port_id, aud_cal->cal_paddr); +done: + return; +} + +void send_adm_cal(int port_id, int path) +{ + s32 acdb_path; + struct acdb_cal_block aud_cal; + + pr_debug("%s\n", __func__); + + /* Maps audio_dev_ctrl path definition to ACDB definition */ + acdb_path = path - 1; + if ((acdb_path >= NUM_AUDPROC_BUFFERS) || + (acdb_path < 0)) { + pr_err("%s: Path is not RX or TX, path = %d\n", + __func__, path); + goto done; + } + + pr_debug("%s: Sending audproc cal\n", __func__); + get_audproc_cal(acdb_path, &aud_cal); + send_cal(port_id, &aud_cal); + + pr_debug("%s: Sending audvol cal\n", __func__); + get_audvol_cal(acdb_path, &aud_cal); + send_cal(port_id, &aud_cal); +done: + return; +} + +/* This function issues routing command of ASM stream + * to ADM mixer associated with a particular AFE port + */ +int adm_cmd_map(int port_id, int session_id) +{ + struct adm_routings_command route; + int ret = 0; + int index = afe_get_port_index(port_id); + + pr_debug("%s: port %x session %x\n", __func__, port_id, session_id); + + if (!atomic_read(&this_adm.copp_cnt[index])) + return 0; + + route.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + route.hdr.pkt_size = sizeof(route); + route.hdr.src_svc = 0; + route.hdr.src_domain = APR_DOMAIN_APPS; + route.hdr.src_port = port_id; + route.hdr.dest_svc = APR_SVC_ADM; + route.hdr.dest_domain = APR_DOMAIN_ADSP; + route.hdr.dest_port = atomic_read(&this_adm.copp_id[index]); + route.hdr.token = port_id; + route.hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS; + route.num_sessions = 1; + route.session[0].id = session_id; + route.session[0].num_copps = 1; + route.session[0].copp_id[0] = + atomic_read(&this_adm.copp_id[index]); + + /* This rule can change */ + if ((port_id & 0x1)) + route.path = AUDIO_TX; + else + route.path = AUDIO_RX; + + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&route); + if (ret < 0) { + pr_err("%s: ADM routing for port %d failed\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: ADM cmd Route failed for port %d\n", + __func__, port_id); + ret = -EINVAL; + } + +fail_cmd: + return ret; +} + +/* This function establish routing of ASM stream to a particular + * ADM mixer that is routed to a particular hardware port + * session id must be in range of 0 ~ 31. + */ +int adm_route_session(int port_id, uint session_id, int set) +{ + int rc = 0; + int index; + + pr_debug("%s: port %x session %x set %x\n", __func__, + port_id, session_id, set); + + index = afe_get_port_index(port_id); + + if (index >= AFE_MAX_PORTS) { + pr_err("%s port idi[%d] out of limit[%d]\n", __func__, + port_id, AFE_MAX_PORTS); + return -ENODEV; + } + + if (set) { + set_bit(session_id, &this_adm.sessions[index]); + rc = adm_cmd_map(port_id, session_id); /* not thread safe */ + } else /* Not sure how to deroute yet */ + clear_bit(session_id, &this_adm.sessions[index]); + + return rc; +} + +/* This function instantiates a mixer in QDSP6 audio path for + * given audio hardware port. Topology should be made part + * of audio calibration + */ +int adm_open_mixer(int port_id, int path, int rate, + int channel_mode, int topology) { + struct adm_copp_open_command open; + int ret = 0; + u32 i; + int index; + + pr_debug("%s: port %d path:%d rate:%d mode:%d\n", __func__, + port_id, path, rate, channel_mode); + + if (afe_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = afe_get_port_index(port_id); + if (this_adm.apr == NULL) { + this_adm.apr = apr_register("ADSP", "ADM", adm_callback, + 0xFFFFFFFF, &this_adm); + if (this_adm.apr == NULL) { + pr_err("%s: Unable to register ADM\n", __func__); + ret = -ENODEV; + return ret; + } + } + + if (atomic_read(&this_adm.copp_cnt[index]) == 0) { + + open.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + open.hdr.pkt_size = sizeof(open); + open.hdr.src_svc = APR_SVC_ADM; + open.hdr.src_domain = APR_DOMAIN_APPS; + open.hdr.src_port = port_id; + open.hdr.dest_svc = APR_SVC_ADM; + open.hdr.dest_domain = APR_DOMAIN_ADSP; + open.hdr.dest_port = port_id; + open.hdr.token = port_id; + open.hdr.opcode = ADM_CMD_COPP_OPEN; + + open.mode = path; + open.endpoint_id1 = port_id; + open.endpoint_id2 = 0xFFFF; + + open.topology_id = get_adm_topology(); + if (open.topology_id == 0) + open.topology_id = topology; + + open.channel_config = channel_mode & 0x00FF; + open.rate = rate; + + pr_debug("%s: channel_config=%d port_id=%d rate=%d\ + topology_id=0x%X\n", __func__, open.channel_config,\ + open.endpoint_id1, open.rate,\ + open.topology_id); + + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&open); + if (ret < 0) { + pr_err("%s:ADM enable for port %d failed\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + /* Wait for the callback with copp id */ + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s ADM open failed for port %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + } + atomic_inc(&this_adm.copp_cnt[index]); + + /* Set up routing for cached session */ + for (i = find_first_bit(&this_adm.sessions[index], ASM_MAX_SESSION); + i < ASM_MAX_SESSION; i = find_next_bit(&this_adm.sessions[index], + ASM_MAX_SESSION, i + 1)) + adm_cmd_map(port_id, i); /* Not thread safe */ + +fail_cmd: + return ret; +} + +int adm_open(int port_id, int path, int rate, int channel_mode, int topology) +{ + struct adm_copp_open_command open; + int ret = 0; + int index; + + pr_debug("%s: port %d path:%d rate:%d mode:%d\n", __func__, + port_id, path, rate, channel_mode); + + if (afe_validate_port(port_id) < 0) { + pr_err("%s port idi[%d] is invalid\n", __func__, port_id); + return -ENODEV; + } + + index = afe_get_port_index(port_id); + pr_debug("%s: Port ID %d, index %d\n", __func__, port_id, index); + + if (this_adm.apr == NULL) { + this_adm.apr = apr_register("ADSP", "ADM", adm_callback, + 0xFFFFFFFF, &this_adm); + if (this_adm.apr == NULL) { + pr_err("%s: Unable to register ADM\n", __func__); + ret = -ENODEV; + return ret; + } +#ifdef CONFIG_MSM8X60_RTAC + rtac_set_adm_handle(this_adm.apr); +#endif + } + + + /* Create a COPP if port id are not enabled */ + if (atomic_read(&this_adm.copp_cnt[index]) == 0) { + + open.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + open.hdr.pkt_size = sizeof(open); + open.hdr.src_svc = APR_SVC_ADM; + open.hdr.src_domain = APR_DOMAIN_APPS; + open.hdr.src_port = port_id; + open.hdr.dest_svc = APR_SVC_ADM; + open.hdr.dest_domain = APR_DOMAIN_ADSP; + open.hdr.dest_port = port_id; + open.hdr.token = port_id; + open.hdr.opcode = ADM_CMD_COPP_OPEN; + + open.mode = path; + open.endpoint_id1 = port_id; + open.endpoint_id2 = 0xFFFF; + + open.topology_id = get_adm_topology(); + if (open.topology_id == 0) + open.topology_id = topology; + + open.channel_config = channel_mode & 0x00FF; + open.rate = rate; + + pr_debug("%s: channel_config=%d port_id=%d rate=%d\ + topology_id=0x%X\n", __func__, open.channel_config,\ + open.endpoint_id1, open.rate,\ + open.topology_id); + + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&open); + if (ret < 0) { + pr_err("%s:ADM enable for port %d failed\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + /* Wait for the callback with copp id */ + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s ADM open failed for port %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + } + atomic_inc(&this_adm.copp_cnt[index]); + return 0; + +fail_cmd: + + return ret; +} + +int adm_matrix_map(int session_id, int path, int num_copps, + unsigned int *port_id, int copp_id) +{ + struct adm_routings_command route; + int ret = 0, i = 0; + /* Assumes port_ids have already been validated during adm_open */ + int index = afe_get_port_index(copp_id); + + pr_debug("%s: session 0x%x path:%d num_copps:%d port_id[0]:%d\n", + __func__, session_id, path, num_copps, port_id[0]); + + route.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + route.hdr.pkt_size = sizeof(route); + route.hdr.src_svc = 0; + route.hdr.src_domain = APR_DOMAIN_APPS; + route.hdr.src_port = copp_id; + route.hdr.dest_svc = APR_SVC_ADM; + route.hdr.dest_domain = APR_DOMAIN_ADSP; + route.hdr.dest_port = atomic_read(&this_adm.copp_id[index]); + route.hdr.token = copp_id; + route.hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS; + route.num_sessions = 1; + route.session[0].id = session_id; + route.session[0].num_copps = num_copps; + + for (i = 0; i < num_copps; i++) { + int tmp; + tmp = afe_get_port_index(port_id[i]); + + pr_debug("%s: port_id[%d]: %d, index: %d\n", __func__, i, + port_id[i], tmp); + + route.session[0].copp_id[i] = + atomic_read(&this_adm.copp_id[tmp]); + } + if (num_copps % 2) + route.session[0].copp_id[i] = 0; + + switch (path) { + case 0x1: + route.path = AUDIO_RX; + break; + case 0x2: + case 0x3: + route.path = AUDIO_TX; + break; + default: + pr_err("%s: Wrong path set[%d]\n", __func__, path); + break; + } + atomic_set(&this_adm.copp_stat[index], 0); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&route); + if (ret < 0) { + pr_err("%s: ADM routing for port %d failed\n", + __func__, port_id[0]); + ret = -EINVAL; + goto fail_cmd; + } + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: ADM cmd Route failed for port %d\n", + __func__, port_id[0]); + ret = -EINVAL; + goto fail_cmd; + } + + for (i = 0; i < num_copps; i++) + send_adm_cal(port_id[i], path); + +#ifdef CONFIG_MSM8X60_RTAC + for (i = 0; i < num_copps; i++) + rtac_add_adm_device(port_id[i], session_id); +#endif + return 0; + +fail_cmd: + + return ret; +} + +int adm_memory_map_regions(uint32_t *buf_add, uint32_t mempool_id, + uint32_t *bufsz, uint32_t bufcnt) +{ + struct adm_cmd_memory_map_regions *mmap_regions = NULL; + struct adm_memory_map_regions *mregions = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + int ret = 0; + int i = 0; + int cmd_size = 0; + + pr_info("%s\n", __func__); + if (this_adm.apr == NULL) { + this_adm.apr = apr_register("ADSP", "ADM", adm_callback, + 0xFFFFFFFF, &this_adm); + if (this_adm.apr == NULL) { + pr_err("%s: Unable to register ADM\n", __func__); + ret = -ENODEV; + return ret; + } +#ifdef CONFIG_MSM8X60_RTAC + rtac_set_adm_handle(this_adm.apr); +#endif + } + + cmd_size = sizeof(struct adm_cmd_memory_map_regions) + + sizeof(struct adm_memory_map_regions) * bufcnt; + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!mmap_region_cmd) { + pr_err("%s: allocate mmap_region_cmd failed\n", __func__); + return -ENOMEM; + } + mmap_regions = (struct adm_cmd_memory_map_regions *)mmap_region_cmd; + mmap_regions->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mmap_regions->hdr.pkt_size = cmd_size; + mmap_regions->hdr.src_port = 0; + mmap_regions->hdr.dest_port = 0; + mmap_regions->hdr.token = 0; + mmap_regions->hdr.opcode = ADM_CMD_MEMORY_MAP_REGIONS; + mmap_regions->mempool_id = mempool_id & 0x00ff; + mmap_regions->nregions = bufcnt & 0x00ff; + pr_debug("%s: map_regions->nregions = %d\n", __func__, + mmap_regions->nregions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct adm_cmd_memory_map_regions)); + mregions = (struct adm_memory_map_regions *)payload; + + for (i = 0; i < bufcnt; i++) { + mregions->phys = buf_add[i]; + mregions->buf_size = bufsz[i]; + ++mregions; + } + + atomic_set(&this_adm.copp_stat[0], 0); + ret = apr_send_pkt(this_adm.apr, (uint32_t *) mmap_region_cmd); + if (ret < 0) { + pr_err("%s: mmap_regions op[0x%x]rc[%d]\n", __func__, + mmap_regions->hdr.opcode, ret); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[0]), 5 * HZ); + if (!ret) { + pr_err("%s: timeout. waited for memory_map\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } +fail_cmd: + kfree(mmap_region_cmd); + return ret; +} + +int adm_memory_unmap_regions(uint32_t *buf_add, uint32_t *bufsz, + uint32_t bufcnt) +{ + struct adm_cmd_memory_unmap_regions *unmap_regions = NULL; + struct adm_memory_unmap_regions *mregions = NULL; + void *unmap_region_cmd = NULL; + void *payload = NULL; + int ret = 0; + int i = 0; + int cmd_size = 0; + + pr_info("%s\n", __func__); + + if (this_adm.apr == NULL) { + pr_err("%s APR handle NULL\n", __func__); + return -EINVAL; + } + + cmd_size = sizeof(struct adm_cmd_memory_unmap_regions) + + sizeof(struct adm_memory_unmap_regions) * bufcnt; + + unmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + if (!unmap_region_cmd) { + pr_err("%s: allocate unmap_region_cmd failed\n", __func__); + return -ENOMEM; + } + unmap_regions = (struct adm_cmd_memory_unmap_regions *) + unmap_region_cmd; + unmap_regions->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + unmap_regions->hdr.pkt_size = cmd_size; + unmap_regions->hdr.src_port = 0; + unmap_regions->hdr.dest_port = 0; + unmap_regions->hdr.token = 0; + unmap_regions->hdr.opcode = ADM_CMD_MEMORY_UNMAP_REGIONS; + unmap_regions->nregions = bufcnt & 0x00ff; + unmap_regions->reserved = 0; + pr_debug("%s: unmap_regions->nregions = %d\n", __func__, + unmap_regions->nregions); + payload = ((u8 *) unmap_region_cmd + + sizeof(struct adm_cmd_memory_unmap_regions)); + mregions = (struct adm_memory_unmap_regions *)payload; + + for (i = 0; i < bufcnt; i++) { + mregions->phys = buf_add[i]; + ++mregions; + } + atomic_set(&this_adm.copp_stat[0], 0); + ret = apr_send_pkt(this_adm.apr, (uint32_t *) unmap_region_cmd); + if (ret < 0) { + pr_err("%s: mmap_regions op[0x%x]rc[%d]\n", __func__, + unmap_regions->hdr.opcode, ret); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[0]), 5 * HZ); + if (!ret) { + pr_err("%s: timeout. waited for memory_unmap\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } +fail_cmd: + kfree(unmap_region_cmd); + return ret; +} + +#ifdef CONFIG_MSM8X60_RTAC +int adm_get_copp_id(int port_id) +{ + pr_debug("%s\n", __func__); + + if (port_id < 0) { + pr_err("%s: invalid port_id = %d\n", __func__, port_id); + return -EINVAL; + } + + return atomic_read(&this_adm.copp_id[port_id]); +} +#endif + +int adm_close(int port_id) +{ + struct apr_hdr close; + + int ret = 0; + int index = afe_get_port_index(port_id); + + pr_info("%s port_id=%d index %d\n", __func__, port_id, index); + + if (!(atomic_read(&this_adm.copp_cnt[index]))) { + pr_err("%s: copp count for port[%d]is 0\n", __func__, port_id); + + goto fail_cmd; + } + atomic_dec(&this_adm.copp_cnt[index]); + if (!(atomic_read(&this_adm.copp_cnt[index]))) { + + close.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + close.pkt_size = sizeof(close); + close.src_svc = APR_SVC_ADM; + close.src_domain = APR_DOMAIN_APPS; + close.src_port = port_id; + close.dest_svc = APR_SVC_ADM; + close.dest_domain = APR_DOMAIN_ADSP; + close.dest_port = atomic_read(&this_adm.copp_id[index]); + close.token = port_id; + close.opcode = ADM_CMD_COPP_CLOSE; + + atomic_set(&this_adm.copp_id[index], RESET_COPP_ID); + atomic_set(&this_adm.copp_stat[index], 0); + + + pr_debug("%s:coppid %d portid=%d index=%d coppcnt=%d\n", + __func__, + atomic_read(&this_adm.copp_id[index]), + port_id, index, + atomic_read(&this_adm.copp_cnt[index])); + + ret = apr_send_pkt(this_adm.apr, (uint32_t *)&close); + if (ret < 0) { + pr_err("%s ADM close failed\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_adm.wait, + atomic_read(&this_adm.copp_stat[index]), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: ADM cmd Route failed for port %d\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + +#ifdef CONFIG_MSM8X60_RTAC + rtac_remove_adm_device(port_id); +#endif + } + +fail_cmd: + return ret; +} + +static int __init adm_init(void) +{ + int i = 0; + init_waitqueue_head(&this_adm.wait); + this_adm.apr = NULL; + + for (i = 0; i < AFE_MAX_PORTS; i++) { + atomic_set(&this_adm.copp_id[i], RESET_COPP_ID); + atomic_set(&this_adm.copp_cnt[i], 0); + atomic_set(&this_adm.copp_stat[i], 0); + } + return 0; +} + +device_initcall(adm_init); diff --git a/sound/soc/msm/qdsp6/q6afe.c b/sound/soc/msm/qdsp6/q6afe.c new file mode 100644 index 00000000000..500ceb57001 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6afe.c @@ -0,0 +1,868 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct afe_ctl { + void *apr; + atomic_t state; + atomic_t status; + wait_queue_head_t wait; + struct task_struct *task; +}; + +static struct afe_ctl this_afe; + +#define TIMEOUT_MS 1000 +#define Q6AFE_MAX_VOLUME 0x3FFF + +#define SIZEOF_CFG_CMD(y) \ + (sizeof(struct apr_hdr) + sizeof(u16) + (sizeof(struct y))) + +static int32_t afe_callback(struct apr_client_data *data, void *priv) +{ + if (data->opcode == RESET_EVENTS) { + pr_debug("q6afe: reset event = %d %d apr[%p]\n", + data->reset_event, data->reset_proc, this_afe.apr); + if (this_afe.apr) { + apr_reset(this_afe.apr); + atomic_set(&this_afe.state, 0); + this_afe.apr = NULL; + } + /* send info to user */ + pr_debug("task_name = %s pid = %d\n", + this_afe.task->comm, this_afe.task->pid); + send_sig(SIGUSR1, this_afe.task, 0); + } + if (data->payload_size) { + uint32_t *payload; + payload = data->payload; + pr_debug("%s: cmd = 0x%x status = 0x%x\n", __func__, + payload[0], payload[1]); + /* payload[1] contains the error status for response */ + if (payload[1] != 0) { + atomic_set(&this_afe.status, -1); + pr_err("%s: cmd = 0x%x returned error = 0x%x\n", + __func__, payload[0], payload[1]); + } + if (data->opcode == APR_BASIC_RSP_RESULT) { + switch (payload[0]) { + case AFE_PORT_AUDIO_IF_CONFIG: + case AFE_PORT_CMD_STOP: + case AFE_PORT_CMD_START: + case AFE_PORT_CMD_LOOPBACK: + case AFE_PORT_CMD_SIDETONE_CTL: + case AFE_PORT_CMD_SET_PARAM: + case AFE_PSEUDOPORT_CMD_START: + case AFE_PSEUDOPORT_CMD_STOP: + atomic_set(&this_afe.state, 0); + wake_up(&this_afe.wait); + break; + default: + pr_err("Unknown cmd 0x%x\n", + payload[0]); + break; + } + } + } + return 0; +} + +int afe_validate_port(u16 port_id) +{ + int ret; + + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + case PCM_RX: + case PCM_TX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + case HDMI_RX: + case RSVD_2: + case RSVD_3: + case DIGI_MIC_TX: + case VOICE_RECORD_RX: + case VOICE_RECORD_TX: + case VOICE_PLAYBACK_TX: + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + case INT_BT_SCO_RX: + case INT_BT_SCO_TX: + case INT_BT_A2DP_RX: + case INT_FM_RX: + case INT_FM_TX: + { + ret = 0; + break; + } + + default: + ret = -EINVAL; + } + + return ret; +} + +int afe_get_port_index(u16 port_id) +{ + switch (port_id) { + case PRIMARY_I2S_RX: return IDX_PRIMARY_I2S_RX; + case PRIMARY_I2S_TX: return IDX_PRIMARY_I2S_TX; + case PCM_RX: return IDX_PCM_RX; + case PCM_TX: return IDX_PCM_TX; + case SECONDARY_I2S_RX: return IDX_SECONDARY_I2S_RX; + case SECONDARY_I2S_TX: return IDX_SECONDARY_I2S_TX; + case MI2S_RX: return IDX_MI2S_RX; + case MI2S_TX: return IDX_MI2S_TX; + case HDMI_RX: return IDX_HDMI_RX; + case RSVD_2: return IDX_RSVD_2; + case RSVD_3: return IDX_RSVD_3; + case DIGI_MIC_TX: return IDX_DIGI_MIC_TX; + case VOICE_RECORD_RX: return IDX_VOICE_RECORD_RX; + case VOICE_RECORD_TX: return IDX_VOICE_RECORD_TX; + case VOICE_PLAYBACK_TX: return IDX_VOICE_PLAYBACK_TX; + case SLIMBUS_0_RX: return IDX_SLIMBUS_0_RX; + case SLIMBUS_0_TX: return IDX_SLIMBUS_0_TX; + case INT_BT_SCO_RX: return IDX_INT_BT_SCO_RX; + case INT_BT_SCO_TX: return IDX_INT_BT_SCO_TX; + case INT_BT_A2DP_RX: return IDX_INT_BT_A2DP_RX; + case INT_FM_RX: return IDX_INT_FM_RX; + case INT_FM_TX: return IDX_INT_FM_TX; + + default: return -EINVAL; + } +} + +int afe_sizeof_cfg_cmd(u16 port_id) +{ + int ret_size; + switch (port_id) { + case PRIMARY_I2S_RX: + case PRIMARY_I2S_TX: + case SECONDARY_I2S_RX: + case SECONDARY_I2S_TX: + case MI2S_RX: + case MI2S_TX: + ret_size = SIZEOF_CFG_CMD(afe_port_mi2s_cfg); + break; + case HDMI_RX: + ret_size = SIZEOF_CFG_CMD(afe_port_hdmi_cfg); + break; + case SLIMBUS_0_RX: + case SLIMBUS_0_TX: + ret_size = SIZEOF_CFG_CMD(afe_port_slimbus_cfg); + break; + case PCM_RX: + case PCM_TX: + default: + ret_size = SIZEOF_CFG_CMD(afe_port_pcm_cfg); + break; + } + return ret_size; +} + +int afe_port_start_nowait(u16 port_id, union afe_port_config *afe_config, + u32 rate) /* This function is no blocking */ +{ + struct afe_port_start_command start; + struct afe_audioif_config_command config; + int ret; + + if (!afe_config) { + pr_err("%s: Error, no configuration data\n", __func__); + ret = -EINVAL; + return ret; + } + + pr_info("%s: %d %d\n", __func__, port_id, rate); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_info("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + config.hdr.pkt_size = afe_sizeof_cfg_cmd(port_id); + config.hdr.src_port = 0; + config.hdr.dest_port = 0; + config.hdr.token = 0; + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + + if (afe_validate_port(port_id) < 0) { + + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + config.port_id = port_id; + config.port = *afe_config; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &config); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PORT_CMD_START; + start.port_id = port_id; + start.gain = 0x2000; + start.sample_rate = rate; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + + if (IS_ERR_VALUE(ret)) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + if (this_afe.task != current) + this_afe.task = current; + + pr_debug("task_name = %s pid = %d\n", + this_afe.task->comm, this_afe.task->pid); + return 0; + +fail_cmd: + return ret; +} + +int afe_open(u16 port_id, union afe_port_config *afe_config, int rate) +{ + struct afe_port_start_command start; + struct afe_audioif_config_command config; + int ret = 0; + + if (!afe_config) { + pr_err("%s: Error, no configuration data\n", __func__); + ret = -EINVAL; + return ret; + } + + pr_info("%s: %d %d\n", __func__, port_id, rate); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_info("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + config.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + config.hdr.pkt_size = afe_sizeof_cfg_cmd(port_id); + config.hdr.src_port = 0; + config.hdr.dest_port = 0; + config.hdr.token = 0; + config.hdr.opcode = AFE_PORT_AUDIO_IF_CONFIG; + + if (afe_validate_port(port_id) < 0) { + + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + config.port_id = port_id; + config.port = *afe_config; + + atomic_set(&this_afe.state, 1); + atomic_set(&this_afe.status, 0); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &config); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + if (atomic_read(&this_afe.status) != 0) { + pr_err("%s: config cmd failed\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PORT_CMD_START; + start.port_id = port_id; + start.gain = 0x2000; + start.sample_rate = rate; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + if (this_afe.task != current) + this_afe.task = current; + + pr_debug("task_name = %s pid = %d\n", + this_afe.task->comm, this_afe.task->pid); + return 0; +fail_cmd: + return ret; +} + +int afe_loopback(u16 enable, u16 rx_port, u16 tx_port) +{ + struct afe_loopback_command lb_cmd; + int ret = 0; + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_info("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + lb_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(20), APR_PKT_VER); + lb_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(lb_cmd) - APR_HDR_SIZE); + lb_cmd.hdr.src_port = 0; + lb_cmd.hdr.dest_port = 0; + lb_cmd.hdr.token = 0; + lb_cmd.hdr.opcode = AFE_PORT_CMD_LOOPBACK; + lb_cmd.tx_port_id = tx_port; + lb_cmd.rx_port_id = rx_port; + lb_cmd.mode = 0xFFFF; + lb_cmd.enable = (enable ? 1 : 0); + atomic_set(&this_afe.state, 1); + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &lb_cmd); + if (ret < 0) { + pr_err("%s: AFE loopback failed\n", __func__); + ret = -EINVAL; + goto done; + } + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + } +done: + return ret; +} + + +int afe_loopback_gain(u16 port_id, u16 volume) +{ + struct afe_port_cmd_set_param set_param; + int ret = 0; + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is not opened\n", __func__); + ret = -EPERM; + goto fail_cmd; + } + + if (afe_validate_port(port_id) < 0) { + + pr_err("%s: Failed : Invalid Port id = %d\n", __func__, + port_id); + ret = -EINVAL; + goto fail_cmd; + } + + /* RX ports numbers are even .TX ports numbers are odd. */ + if (port_id % 2 == 0) { + pr_err("%s: Failed : afe loopback gain only for TX ports." + " port_id %d\n", __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + pr_debug("%s: %d %hX\n", __func__, port_id, volume); + + set_param.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + set_param.hdr.pkt_size = sizeof(set_param); + set_param.hdr.src_port = 0; + set_param.hdr.dest_port = 0; + set_param.hdr.token = 0; + set_param.hdr.opcode = AFE_PORT_CMD_SET_PARAM; + + set_param.port_id = port_id; + set_param.payload_size = sizeof(struct afe_param_payload); + set_param.payload_address = 0; + + set_param.payload.module_id = AFE_MODULE_ID_PORT_INFO; + set_param.payload.param_id = AFE_PARAM_ID_LOOPBACK_GAIN; + set_param.payload.param_size = sizeof(struct afe_param_loopback_gain); + set_param.payload.reserved = 0; + + set_param.payload.param.loopback_gain.gain = volume; + set_param.payload.param.loopback_gain.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &set_param); + if (ret < 0) { + pr_err("%s: AFE param set failed for port %d\n", + __func__, port_id); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + return 0; +fail_cmd: + return ret; +} + +int afe_start_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_start_command start; + + pr_info("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + this_afe.apr = apr_register("ADSP", "AFE", afe_callback, + 0xFFFFFFFF, &this_afe); + pr_info("%s: Register AFE\n", __func__); + if (this_afe.apr == NULL) { + pr_err("%s: Unable to register AFE\n", __func__); + ret = -ENODEV; + return ret; + } + } + + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + start.hdr.pkt_size = sizeof(start); + start.hdr.src_port = 0; + start.hdr.dest_port = 0; + start.hdr.token = 0; + start.hdr.opcode = AFE_PSEUDOPORT_CMD_START; + start.port_id = port_id; + start.timing = 1; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &start); + if (ret < 0) { + pr_err("%s: AFE enable for port %d failed %d\n", + __func__, port_id, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + return ret; + } + + return 0; +} + +int afe_stop_pseudo_port(u16 port_id) +{ + int ret = 0; + struct afe_pseudoport_stop_command stop; + + pr_info("%s: port_id=%d\n", __func__, port_id); + + if (this_afe.apr == NULL) { + pr_err("%s: AFE is already closed\n", __func__); + ret = -EINVAL; + return ret; + } + + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PSEUDOPORT_CMD_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + if (ret < 0) { + pr_err("%s: AFE close failed %d\n", __func__, ret); + ret = -EINVAL; + return ret; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + return ret; + } + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_afelb; +static struct dentry *debugfs_afelb_gain; + +static int afe_debug_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + pr_info("debug intf %s\n", (char *) file->private_data); + return 0; +} + +static int afe_get_parameters(char *buf, long int *param1, int num_of_par) +{ + char *token; + int base, cnt; + + token = strsep(&buf, " "); + + for (cnt = 0; cnt < num_of_par; cnt++) { + if (token != NULL) { + if ((token[1] == 'x') || (token[1] == 'X')) + base = 16; + else + base = 10; + + if (strict_strtoul(token, base, ¶m1[cnt]) != 0) + return -EINVAL; + + token = strsep(&buf, " "); + } else + return -EINVAL; + } + return 0; +} +#define AFE_LOOPBACK_ON (1) +#define AFE_LOOPBACK_OFF (0) +static ssize_t afe_debug_write(struct file *filp, + const char __user *ubuf, size_t cnt, loff_t *ppos) +{ + char *lb_str = filp->private_data; + char lbuf[32]; + int rc; + unsigned long param[5]; + + if (cnt > sizeof(lbuf) - 1) + return -EINVAL; + + rc = copy_from_user(lbuf, ubuf, cnt); + if (rc) + return -EFAULT; + + lbuf[cnt] = '\0'; + + if (!strcmp(lb_str, "afe_loopback")) { + rc = afe_get_parameters(lbuf, param, 3); + if (!rc) { + pr_info("%s %lu %lu %lu\n", lb_str, param[0], param[1], + param[2]); + + if ((param[0] != AFE_LOOPBACK_ON) && (param[0] != + AFE_LOOPBACK_OFF)) { + pr_err("%s: Error, parameter 0 incorrect\n", + __func__); + rc = -EINVAL; + goto afe_error; + } + if ((afe_validate_port(param[1]) < 0) || + (afe_validate_port(param[2])) < 0) { + pr_err("%s: Error, invalid afe port\n", + __func__); + } + if (this_afe.apr == NULL) { + pr_err("%s: Error, AFE not opened\n", __func__); + rc = -EINVAL; + } else { + rc = afe_loopback(param[0], param[1], param[2]); + } + } else { + pr_err("%s: Error, invalid parameters\n", __func__); + rc = -EINVAL; + } + + } else if (!strcmp(lb_str, "afe_loopback_gain")) { + rc = afe_get_parameters(lbuf, param, 2); + if (!rc) { + pr_info("%s %lu %lu\n", lb_str, param[0], param[1]); + + if (afe_validate_port(param[0]) < 0) { + pr_err("%s: Error, invalid afe port\n", + __func__); + rc = -EINVAL; + goto afe_error; + } + + if (param[1] < 0 || param[1] > 100) { + pr_err("%s: Error, volume shoud be 0 to 100" + " percentage param = %lu\n", + __func__, param[1]); + rc = -EINVAL; + goto afe_error; + } + + param[1] = (Q6AFE_MAX_VOLUME * param[1]) / 100; + + if (this_afe.apr == NULL) { + pr_err("%s: Error, AFE not opened\n", __func__); + rc = -EINVAL; + } else { + rc = afe_loopback_gain(param[0], param[1]); + } + } else { + pr_err("%s: Error, invalid parameters\n", __func__); + rc = -EINVAL; + } + } + +afe_error: + if (rc == 0) + rc = cnt; + else + pr_err("%s: rc = %d\n", __func__, rc); + + return rc; +} + +static const struct file_operations afe_debug_fops = { + .open = afe_debug_open, + .write = afe_debug_write +}; +#endif +int afe_sidetone(u16 tx_port_id, u16 rx_port_id, u16 enable, uint16_t gain) +{ + struct afe_port_sidetone_command cmd_sidetone; + int ret = 0; + + pr_info("%s: tx_port_id:%d rx_port_id:%d enable:%d gain:%d\n", __func__, + tx_port_id, rx_port_id, enable, gain); + cmd_sidetone.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + cmd_sidetone.hdr.pkt_size = sizeof(cmd_sidetone); + cmd_sidetone.hdr.src_port = 0; + cmd_sidetone.hdr.dest_port = 0; + cmd_sidetone.hdr.token = 0; + cmd_sidetone.hdr.opcode = AFE_PORT_CMD_SIDETONE_CTL; + cmd_sidetone.tx_port_id = tx_port_id; + cmd_sidetone.rx_port_id = rx_port_id; + cmd_sidetone.gain = gain; + cmd_sidetone.enable = enable; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &cmd_sidetone); + if (ret < 0) { + pr_err("%s: AFE sidetone failed for tx_port:%d rx_port:%d\n", + __func__, tx_port_id, rx_port_id); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (ret < 0) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + return 0; +fail_cmd: + return ret; +} + +int afe_port_stop_nowait(int port_id) +{ + struct afe_port_stop_command stop; + int ret = 0; + + if (this_afe.apr == NULL) { + pr_err("AFE is already closed\n"); + ret = -EINVAL; + goto fail_cmd; + } + pr_info("%s: port_id=%d\n", __func__, port_id); + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PORT_CMD_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + + if (ret == -ENETRESET) { + pr_info("%s: Need to reset, calling APR deregister", __func__); + return apr_deregister(this_afe.apr); + } else if (IS_ERR_VALUE(ret)) { + pr_err("%s: AFE close failed\n", __func__); + ret = -EINVAL; + } + +fail_cmd: + return ret; + +} + +int afe_close(int port_id) +{ + struct afe_port_stop_command stop; + int ret = 0; + + if (this_afe.apr == NULL) { + pr_err("AFE is already closed\n"); + ret = -EINVAL; + goto fail_cmd; + } + pr_info("%s: port_id=%d\n", __func__, port_id); + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + stop.hdr.pkt_size = sizeof(stop); + stop.hdr.src_port = 0; + stop.hdr.dest_port = 0; + stop.hdr.token = 0; + stop.hdr.opcode = AFE_PORT_CMD_STOP; + stop.port_id = port_id; + stop.reserved = 0; + + atomic_set(&this_afe.state, 1); + ret = apr_send_pkt(this_afe.apr, (uint32_t *) &stop); + + if (ret == -ENETRESET) { + pr_info("%s: Need to reset, calling APR deregister", __func__); + return apr_deregister(this_afe.apr); + } + + if (ret < 0) { + pr_err("%s: AFE close failed\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } + + ret = wait_event_timeout(this_afe.wait, + (atomic_read(&this_afe.state) == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + ret = -EINVAL; + goto fail_cmd; + } +fail_cmd: + return ret; +} + +static int __init afe_init(void) +{ + init_waitqueue_head(&this_afe.wait); + atomic_set(&this_afe.state, 0); + atomic_set(&this_afe.status, 0); + this_afe.apr = NULL; +#ifdef CONFIG_DEBUG_FS + debugfs_afelb = debugfs_create_file("afe_loopback", + S_IFREG | S_IWUGO, NULL, (void *) "afe_loopback", + &afe_debug_fops); + + debugfs_afelb_gain = debugfs_create_file("afe_loopback_gain", + S_IFREG | S_IWUGO, NULL, (void *) "afe_loopback_gain", + &afe_debug_fops); + + +#endif + return 0; +} + +static void __exit afe_exit(void) +{ +#ifdef CONFIG_DEBUG_FS + if (debugfs_afelb) + debugfs_remove(debugfs_afelb); + if (debugfs_afelb_gain) + debugfs_remove(debugfs_afelb_gain); +#endif +} + +device_initcall(afe_init); +__exitcall(afe_exit); diff --git a/sound/soc/msm/qdsp6/q6asm.c b/sound/soc/msm/qdsp6/q6asm.c new file mode 100644 index 00000000000..f5f1c630805 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6asm.c @@ -0,0 +1,2600 @@ + +/* + * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRUE 0x01 +#define FALSE 0x00 +#define READDONE_IDX_STATUS 0 +#define READDONE_IDX_BUFFER 1 +#define READDONE_IDX_SIZE 2 +#define READDONE_IDX_OFFSET 3 +#define READDONE_IDX_MSW_TS 4 +#define READDONE_IDX_LSW_TS 5 +#define READDONE_IDX_FLAGS 6 +#define READDONE_IDX_NUMFRAMES 7 +#define READDONE_IDX_ID 8 + +static DEFINE_MUTEX(session_lock); + +/* session id: 0 reserved */ +static struct audio_client *session[SESSION_MAX+1]; +static int32_t q6asm_mmapcallback(struct apr_client_data *data, void *priv); +static int32_t q6asm_callback(struct apr_client_data *data, void *priv); +static void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg); +static void q6asm_add_hdr_async(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg); +static int q6asm_memory_map_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt); +static int q6asm_memory_unmap_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt); + +static void q6asm_reset_buf_state(struct audio_client *ac); + +struct asm_mmap { + atomic_t ref_cnt; + atomic_t cmd_state; + wait_queue_head_t cmd_wait; + void *apr; +}; + +static struct asm_mmap this_mmap; + +static int q6asm_session_alloc(struct audio_client *ac) +{ + int n; + mutex_lock(&session_lock); + for (n = 1; n <= SESSION_MAX; n++) { + if (!session[n]) { + session[n] = ac; + mutex_unlock(&session_lock); + return n; + } + } + mutex_unlock(&session_lock); + return -ENOMEM; +} + +static void q6asm_session_free(struct audio_client *ac) +{ + pr_debug("%s: sessionid[%d]\n", __func__, ac->session); + mutex_lock(&session_lock); + session[ac->session] = 0; + mutex_unlock(&session_lock); + ac->session = 0; + return; +} + +int q6asm_audio_client_buf_free(unsigned int dir, + struct audio_client *ac) +{ + struct audio_port_data *port; + int cnt = 0; + int rc = 0; + pr_debug("%s: Session id %d\n", __func__, ac->session); + mutex_lock(&ac->cmd_lock); + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[dir]; + if (!port->buf) { + mutex_unlock(&ac->cmd_lock); + return 0; + } + cnt = port->max_buf_cnt - 1; + + if (cnt >= 0) { + rc = q6asm_memory_unmap_regions(ac, dir, + port->buf[0].size, + port->max_buf_cnt); + if (rc < 0) + pr_err("%s CMD Memory_unmap_regions failed\n", + __func__); + } + + while (cnt >= 0) { + if (port->buf[cnt].data) { + pr_debug("%s:data[%p]phys[%p][%p] cnt[%d]" + "mem_buffer[%p]\n", + __func__, (void *)port->buf[cnt].data, + (void *)port->buf[cnt].phys, + (void *)&port->buf[cnt].phys, cnt, + (void *)port->buf[cnt].mem_buffer); + if (IS_ERR((void *)port->buf[cnt].mem_buffer)) + pr_err("%s:mem buffer invalid, error =" + "%ld\n", __func__, + PTR_ERR((void *)port->buf[cnt].mem_buffer)); + else { + if (msm_subsystem_unmap_buffer( + port->buf[cnt].mem_buffer) < 0) + pr_err("%s: unmap buffer" + " failed\n", __func__); + } + free_contiguous_memory_by_paddr( + port->buf[cnt].phys); + + port->buf[cnt].data = NULL; + port->buf[cnt].phys = 0; + --(port->max_buf_cnt); + } + --cnt; + } + kfree(port->buf); + port->buf = NULL; + } + mutex_unlock(&ac->cmd_lock); + return 0; +} + +int q6asm_audio_client_buf_free_contiguous(unsigned int dir, + struct audio_client *ac) +{ + struct audio_port_data *port; + int cnt = 0; + int rc = 0; + pr_debug("%s: Session id %d\n", __func__, ac->session); + mutex_lock(&ac->cmd_lock); + port = &ac->port[dir]; + if (!port->buf) { + mutex_unlock(&ac->cmd_lock); + return 0; + } + cnt = port->max_buf_cnt - 1; + + if (cnt >= 0) { + rc = q6asm_memory_unmap(ac, port->buf[0].size, dir); + if (rc < 0) + pr_err("%s CMD Memory_unmap_regions failed\n", + __func__); + } + + if (port->buf[0].data) { + pr_debug("%s:data[%p]phys[%p][%p] cnt[%d]\n", + __func__, + (void *)port->buf[0].data, + (void *)port->buf[0].phys, + (void *)&port->buf[0].phys, cnt); + dma_free_coherent(NULL, + port->buf[0].size * port->max_buf_cnt, + port->buf[0].data, + port->buf[0].phys); + } + while (cnt >= 0) { + port->buf[cnt].data = NULL; + port->buf[cnt].phys = 0; + cnt--; + } + port->max_buf_cnt = 0; + kfree(port->buf); + port->buf = NULL; + mutex_unlock(&ac->cmd_lock); + return 0; +} + +void q6asm_audio_client_free(struct audio_client *ac) +{ + int loopcnt; + struct audio_port_data *port; + if (!ac || !ac->session) + return; + pr_debug("%s: Session id %d\n", __func__, ac->session); + if (ac->io_mode == SYNC_IO_MODE) { + for (loopcnt = 0; loopcnt <= OUT; loopcnt++) { + port = &ac->port[loopcnt]; + if (!port->buf) + continue; + pr_debug("%s:loopcnt = %d\n", __func__, loopcnt); + q6asm_audio_client_buf_free(loopcnt, ac); + } + } + + apr_deregister(ac->apr); + q6asm_session_free(ac); + + pr_debug("%s: APR De-Register\n", __func__); + if (atomic_read(&this_mmap.ref_cnt) <= 0) { + pr_err("%s: APR Common Port Already Closed\n", __func__); + goto done; + } + + atomic_dec(&this_mmap.ref_cnt); + if (atomic_read(&this_mmap.ref_cnt) == 0) { + apr_deregister(this_mmap.apr); + pr_debug("%s:APR De-Register common port\n", __func__); + } +done: + kfree(ac); + return; +} + +int q6asm_set_io_mode(struct audio_client *ac, uint32_t mode) +{ + if (ac == NULL) { + pr_err("%s APR handle NULL\n", __func__); + return -EINVAL; + } + if ((mode == ASYNC_IO_MODE) || (mode == SYNC_IO_MODE)) { + ac->io_mode = mode; + pr_debug("%s:Set Mode to %d\n", __func__, ac->io_mode); + return 0; + } else { + pr_err("%s:Not an valid IO Mode:%d\n", __func__, ac->io_mode); + return -EINVAL; + } +} + +struct audio_client *q6asm_audio_client_alloc(app_cb cb, void *priv) +{ + struct audio_client *ac; + int n; + int lcnt = 0; + + ac = kzalloc(sizeof(struct audio_client), GFP_KERNEL); + if (!ac) + return NULL; + n = q6asm_session_alloc(ac); + if (n <= 0) + goto fail_session; + ac->session = n; + ac->cb = cb; + ac->priv = priv; + ac->io_mode = SYNC_IO_MODE; + ac->apr = apr_register("ADSP", "ASM", \ + (apr_fn)q6asm_callback,\ + ((ac->session) << 8 | 0x0001),\ + ac); + + if (ac->apr == NULL) { + pr_err("%s Registration with APR failed\n", __func__); + goto fail; + } +#ifdef CONFIG_MSM8X60_RTAC + rtac_set_asm_handle(n, ac->apr); +#endif + pr_debug("%s Registering the common port with APR\n", __func__); + if (atomic_read(&this_mmap.ref_cnt) == 0) { + this_mmap.apr = apr_register("ADSP", "ASM", \ + (apr_fn)q6asm_mmapcallback,\ + 0x0FFFFFFFF, &this_mmap); + if (this_mmap.apr == NULL) { + pr_debug("%s Unable to register \ + APR ASM common port \n", __func__); + goto fail; + } + } + + atomic_inc(&this_mmap.ref_cnt); + init_waitqueue_head(&ac->cmd_wait); + init_waitqueue_head(&ac->time_wait); + atomic_set(&ac->time_flag, 1); + mutex_init(&ac->cmd_lock); + for (lcnt = 0; lcnt <= OUT; lcnt++) { + mutex_init(&ac->port[lcnt].lock); + spin_lock_init(&ac->port[lcnt].dsp_lock); + } + atomic_set(&ac->cmd_state, 0); + + pr_debug("%s: session[%d]\n", __func__, ac->session); + + return ac; +fail: + q6asm_audio_client_free(ac); + return NULL; +fail_session: + kfree(ac); + return NULL; +} + +int q6asm_audio_client_buf_alloc(unsigned int dir, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt) +{ + int cnt = 0; + int rc = 0; + struct audio_buffer *buf; + + if (!(ac) || ((dir != IN) && (dir != OUT))) + return -EINVAL; + + pr_debug("%s: session[%d]bufsz[%d]bufcnt[%d]\n", __func__, ac->session, + bufsz, bufcnt); + + if (ac->session <= 0 || ac->session > 8) + goto fail; + + if (ac->io_mode == SYNC_IO_MODE) { + if (ac->port[dir].buf) { + pr_debug("%s: buffer already allocated\n", __func__); + return 0; + } + mutex_lock(&ac->cmd_lock); + buf = kzalloc(((sizeof(struct audio_buffer))*bufcnt), + GFP_KERNEL); + + if (!buf) { + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + ac->port[dir].buf = buf; + + while (cnt < bufcnt) { + if (bufsz > 0) { + if (!buf[cnt].data) { + unsigned int flags = 0; + buf[cnt].phys = + allocate_contiguous_ebi_nomap(bufsz, + SZ_4K); + if (!buf[cnt].phys) { + pr_err("%s:Buf alloc failed " + " size=%d\n", __func__, + bufsz); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + flags = MSM_SUBSYSTEM_MAP_KADDR | + MSM_SUBSYSTEM_MAP_CACHED; + buf[cnt].mem_buffer = + msm_subsystem_map_buffer(buf[cnt].phys, + bufsz, flags, NULL, 0); + if (IS_ERR( + (void *)buf[cnt].mem_buffer)) { + pr_err("%s:map_buffer failed," + "error = %ld\n", + __func__, PTR_ERR((void *)buf[cnt].mem_buffer)); + goto fail; + } + buf[cnt].data = + buf[cnt].mem_buffer->vaddr; + if (!buf[cnt].data) { + pr_err("%s:invalid vaddr," + " iomap failed\n", __func__); + goto fail; + } + buf[cnt].used = 1; + buf[cnt].size = bufsz; + buf[cnt].actual_size = bufsz; + pr_debug("%s data[%p]phys[%p][%p]" + "mem_buffer[%p]\n", + __func__, + (void *)buf[cnt].data, + (void *)buf[cnt].phys, + (void *)&buf[cnt].phys, + (void *)buf[cnt].mem_buffer); + cnt++; + } + } + } + ac->port[dir].max_buf_cnt = cnt; + + mutex_unlock(&ac->cmd_lock); + rc = q6asm_memory_map_regions(ac, dir, bufsz, cnt); + if (rc < 0) { + pr_err("%s:CMD Memory_map_regions failed\n", __func__); + goto fail; + } + } + return 0; +fail: + q6asm_audio_client_buf_free(dir, ac); + return -EINVAL; +} + +int q6asm_audio_client_buf_alloc_contiguous(unsigned int dir, + struct audio_client *ac, + unsigned int bufsz, + unsigned int bufcnt) +{ + int cnt = 0; + int rc = 0; + struct audio_buffer *buf; + + if (!(ac) || ((dir != IN) && (dir != OUT))) + return -EINVAL; + + pr_debug("%s: session[%d]bufsz[%d]bufcnt[%d]\n", + __func__, ac->session, + bufsz, bufcnt); + + if (ac->session <= 0 || ac->session > 8) + goto fail; + + if (ac->port[dir].buf) { + pr_debug("%s: buffer already allocated\n", __func__); + return 0; + } + mutex_lock(&ac->cmd_lock); + buf = kzalloc(((sizeof(struct audio_buffer))*bufcnt), + GFP_KERNEL); + + if (!buf) { + mutex_unlock(&ac->cmd_lock); + goto fail; + } + + ac->port[dir].buf = buf; + + buf[0].data = dma_alloc_coherent(NULL, bufsz * bufcnt, + &buf[0].phys, GFP_KERNEL); + buf[0].used = dir ^ 1; + buf[0].size = bufsz; + buf[0].actual_size = bufsz; + cnt = 1; + while (cnt < bufcnt) { + if (bufsz > 0) { + buf[cnt].data = buf[0].data + (cnt * bufsz); + buf[cnt].phys = buf[0].phys + (cnt * bufsz); + if (!buf[cnt].data) { + pr_err("%s Buf alloc failed\n", + __func__); + mutex_unlock(&ac->cmd_lock); + goto fail; + } + buf[cnt].used = dir ^ 1; + buf[cnt].size = bufsz; + buf[cnt].actual_size = bufsz; + pr_debug("%s data[%p]phys[%p][%p]\n", __func__, + (void *)buf[cnt].data, + (void *)buf[cnt].phys, + (void *)&buf[cnt].phys); + } + cnt++; + } + ac->port[dir].max_buf_cnt = cnt; + mutex_unlock(&ac->cmd_lock); + rc = q6asm_memory_map(ac, buf[0].phys, dir, bufsz, cnt); + if (rc < 0) { + pr_err("%s:CMD Memory_map_regions failed\n", __func__); + goto fail; + } + return 0; +fail: + q6asm_audio_client_buf_free_contiguous(dir, ac); + return -EINVAL; +} + +static int32_t q6asm_mmapcallback(struct apr_client_data *data, void *priv) +{ + uint32_t token; + uint32_t *payload = data->payload; + + if (data->opcode == RESET_EVENTS) { + pr_debug("%s: Reset event is received: %d %d apr[%p]\n", + __func__, + data->reset_event, + data->reset_proc, + this_mmap.apr); + apr_reset(this_mmap.apr); + this_mmap.apr = NULL; + atomic_set(&this_mmap.cmd_state, 0); + return 0; + } + + pr_debug("%s:ptr0[0x%x]ptr1[0x%x]opcode[0x%x]" + "token[0x%x]payload_s[%d] src[%d] dest[%d]\n", __func__, + payload[0], payload[1], data->opcode, data->token, + data->payload_size, data->src_port, data->dest_port); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + token = data->token; + switch (payload[0]) { + case ASM_SESSION_CMD_MEMORY_MAP: + case ASM_SESSION_CMD_MEMORY_UNMAP: + case ASM_SESSION_CMD_MEMORY_MAP_REGIONS: + case ASM_SESSION_CMD_MEMORY_UNMAP_REGIONS: + pr_debug("%s:command[0x%x]success [0x%x]\n", + __func__, payload[0], payload[1]); + if (atomic_read(&this_mmap.cmd_state)) { + atomic_set(&this_mmap.cmd_state, 0); + wake_up(&this_mmap.cmd_wait); + } + break; + default: + pr_debug("%s:command[0x%x] not expecting rsp\n", + __func__, payload[0]); + break; + } + } + return 0; +} + + +static int32_t q6asm_callback(struct apr_client_data *data, void *priv) +{ + int i = 0; + struct audio_client *ac = (struct audio_client *)priv; + uint32_t token; + unsigned long dsp_flags; + uint32_t *payload; + + + if ((ac == NULL) || (data == NULL)) { + pr_err("ac or priv NULL\n"); + return -EINVAL; + } + if (ac->session <= 0 || ac->session > 8) { + pr_err("%s:Session ID is invalid, session = %d\n", __func__, + ac->session); + return -EINVAL; + } + + payload = data->payload; + + if (data->opcode == RESET_EVENTS) { + pr_debug("q6asm_callback: Reset event is received: %d %d apr[%p]\n", + data->reset_event, data->reset_proc, ac->apr); + apr_reset(ac->apr); + return 0; + } + + pr_debug("%s: session[%d]opcode[0x%x] \ + token[0x%x]payload_s[%d] src[%d] dest[%d]\n", __func__, + ac->session, data->opcode, + data->token, data->payload_size, data->src_port, + data->dest_port); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + token = data->token; + switch (payload[0]) { + case ASM_STREAM_CMD_SET_PP_PARAMS: +#ifdef CONFIG_MSM8X60_RTAC + if (rtac_make_asm_callback(ac->session, payload, + data->payload_size)) + break; +#endif + case ASM_SESSION_CMD_PAUSE: + case ASM_DATA_CMD_EOS: + case ASM_STREAM_CMD_CLOSE: + case ASM_STREAM_CMD_FLUSH: + case ASM_SESSION_CMD_RUN: + case ASM_SESSION_CMD_REGISTER_FOR_TX_OVERFLOW_EVENTS: + case ASM_STREAM_CMD_FLUSH_READBUFS: + pr_debug("%s:Payload = [0x%x]\n", __func__, payload[0]); + if (token != ac->session) { + pr_err("%s:Invalid session[%d] rxed expected[%d]", + __func__, token, ac->session); + return -EINVAL; + } + case ASM_STREAM_CMD_OPEN_READ: + case ASM_STREAM_CMD_OPEN_WRITE: + case ASM_STREAM_CMD_OPEN_READWRITE: + case ASM_DATA_CMD_MEDIA_FORMAT_UPDATE: + case ASM_STREAM_CMD_SET_ENCDEC_PARAM: + if (atomic_read(&ac->cmd_state)) { + atomic_set(&ac->cmd_state, 0); + wake_up(&ac->cmd_wait); + } + if (ac->cb) + ac->cb(data->opcode, data->token, + (uint32_t *)data->payload, ac->priv); + break; + default: + pr_debug("%s:command[0x%x] not expecting rsp\n", + __func__, payload[0]); + break; + } + return 0; + } + + switch (data->opcode) { + case ASM_DATA_EVENT_WRITE_DONE:{ + struct audio_port_data *port = &ac->port[IN]; + pr_debug("%s: Rxed opcode[0x%x] status[0x%x] token[%d]", + __func__, payload[0], payload[1], + data->token); + if (ac->io_mode == SYNC_IO_MODE) { + if (port->buf == NULL) { + pr_err("%s: Unexpected Write Done\n", + __func__); + return -EINVAL; + } + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + if (port->buf[data->token].phys != + payload[0]) { + pr_err("Buf expected[%p]rxed[%p]\n",\ + (void *)port->buf[data->token].phys,\ + (void *)payload[0]); + spin_unlock_irqrestore(&port->dsp_lock, + dsp_flags); + return -EINVAL; + } + token = data->token; + port->buf[token].used = 1; + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + for (i = 0; i < port->max_buf_cnt; i++) + pr_debug("%d ", port->buf[i].used); + + } + break; + } +#ifdef CONFIG_MSM8X60_RTAC + case ASM_STREAM_CMDRSP_GET_PP_PARAMS: + rtac_make_asm_callback(ac->session, payload, + data->payload_size); + break; +#endif + case ASM_DATA_EVENT_READ_DONE:{ + + struct audio_port_data *port = &ac->port[OUT]; + + pr_debug("%s:R-D: status=%d buff_add=%x act_size=%d offset=%d\n", + __func__, payload[READDONE_IDX_STATUS], + payload[READDONE_IDX_BUFFER], + payload[READDONE_IDX_SIZE], + payload[READDONE_IDX_OFFSET]); + pr_debug("%s:R-D:msw_ts=%d lsw_ts=%d flags=%d id=%d num=%d\n", + __func__, payload[READDONE_IDX_MSW_TS], + payload[READDONE_IDX_LSW_TS], + payload[READDONE_IDX_FLAGS], + payload[READDONE_IDX_ID], + payload[READDONE_IDX_NUMFRAMES]); + + if (ac->io_mode == SYNC_IO_MODE) { + if (port->buf == NULL) { + pr_err("%s: Unexpected Write Done\n", __func__); + return -EINVAL; + } + spin_lock_irqsave(&port->dsp_lock, dsp_flags); + token = data->token; + port->buf[token].used = 0; + if (port->buf[token].phys != + payload[READDONE_IDX_BUFFER]) { + pr_err("Buf expected[%p]rxed[%p]\n",\ + (void *)port->buf[token].phys,\ + (void *)payload[READDONE_IDX_BUFFER]); + spin_unlock_irqrestore(&port->dsp_lock, + dsp_flags); + break; + } + port->buf[token].actual_size = + payload[READDONE_IDX_SIZE]; + spin_unlock_irqrestore(&port->dsp_lock, dsp_flags); + } + break; + } + case ASM_DATA_EVENT_EOS: + case ASM_DATA_CMDRSP_EOS: + pr_debug("%s:EOS ACK received: rxed opcode[0x%x]\n", + __func__, data->opcode); + break; + case ASM_STREAM_CMDRSP_GET_ENCDEC_PARAM: + break; + case ASM_SESSION_EVENT_TX_OVERFLOW: + pr_err("ASM_SESSION_EVENT_TX_OVERFLOW\n"); + break; + case ASM_SESSION_CMDRSP_GET_SESSION_TIME: + pr_debug("%s: ASM_SESSION_CMDRSP_GET_SESSION_TIME, " + "payload[0] = %d, payload[1] = %d, " + "payload[2] = %d\n", __func__, + payload[0], payload[1], payload[2]); + ac->time_stamp = (uint64_t)(((uint64_t)payload[1] << 32) | + payload[2]); + if (atomic_read(&ac->time_flag)) { + atomic_set(&ac->time_flag, 0); + wake_up(&ac->time_wait); + } + break; + case ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY: + pr_debug("%s: ASM_DATA_EVENT_SR_CM_CHANGE_NOTIFY, " + "payload[0] = %d, payload[1] = %d, " + "payload[2] = %d, payload[3] = %d\n", __func__, + payload[0], payload[1], payload[2], + payload[3]); + break; + } + if (ac->cb) + ac->cb(data->opcode, data->token, + data->payload, ac->priv); + + return 0; +} + +void *q6asm_is_cpu_buf_avail(int dir, struct audio_client *ac, uint32_t *size, + uint32_t *index) +{ + void *data; + unsigned char idx; + struct audio_port_data *port; + + if (!ac || ((dir != IN) && (dir != OUT))) + return NULL; + + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[dir]; + + mutex_lock(&port->lock); + idx = port->cpu_buf; + if (port->buf == NULL) { + pr_debug("%s:Buffer pointer null\n", __func__); + return NULL; + } + /* dir 0: used = 0 means buf in use + dir 1: used = 1 means buf in use */ + if (port->buf[idx].used == dir) { + /* To make it more robust, we could loop and get the + next avail buf, its risky though */ + pr_debug("%s:Next buf idx[0x%x] not available,\ + dir[%d]\n", __func__, idx, dir); + mutex_unlock(&port->lock); + return NULL; + } + *size = port->buf[idx].actual_size; + *index = port->cpu_buf; + data = port->buf[idx].data; + pr_debug("%s:session[%d]index[%d] data[%p]size[%d]\n", + __func__, + ac->session, + port->cpu_buf, + data, *size); + /* By default increase the cpu_buf cnt + user accesses this function,increase cpu + buf(to avoid another api)*/ + port->buf[idx].used = dir; + port->cpu_buf = ((port->cpu_buf + 1) & (port->max_buf_cnt - 1)); + mutex_unlock(&port->lock); + return data; + } + return NULL; +} + +int q6asm_is_dsp_buf_avail(int dir, struct audio_client *ac) +{ + int ret = -1; + struct audio_port_data *port; + uint32_t idx; + + if (!ac || (dir != OUT)) + return ret; + + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[dir]; + + mutex_lock(&port->lock); + idx = port->dsp_buf; + + if (port->buf[idx].used == (dir ^ 1)) { + /* To make it more robust, we could loop and get the + next avail buf, its risky though */ + pr_err("Next buf idx[0x%x] not available, dir[%d]\n", + idx, dir); + mutex_unlock(&port->lock); + return ret; + } + pr_debug("%s: session[%d]dsp_buf=%d cpu_buf=%d\n", __func__, + ac->session, port->dsp_buf, port->cpu_buf); + ret = ((port->dsp_buf != port->cpu_buf) ? 0 : -1); + mutex_unlock(&port->lock); + } + return ret; +} + +static void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg) +{ + pr_debug("%s:session=%d pkt size=%d cmd_flg=%d\n", __func__, pkt_size, + cmd_flg, ac->session); + mutex_lock(&ac->cmd_lock); + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(sizeof(struct apr_hdr)),\ + APR_PKT_VER); + hdr->src_svc = ((struct apr_svc *)ac->apr)->id; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->dest_svc = APR_SVC_ASM; + hdr->dest_domain = APR_DOMAIN_ADSP; + hdr->src_port = ((ac->session << 8) & 0xFF00) | 0x01; + hdr->dest_port = ((ac->session << 8) & 0xFF00) | 0x01; + if (cmd_flg) { + hdr->token = ac->session; + atomic_set(&ac->cmd_state, 1); + } + hdr->pkt_size = pkt_size; + mutex_unlock(&ac->cmd_lock); + return; +} + +static void q6asm_add_mmaphdr(struct apr_hdr *hdr, uint32_t pkt_size, + uint32_t cmd_flg) +{ + pr_debug("%s:pkt size=%d cmd_flg=%d\n", __func__, pkt_size, cmd_flg); + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + hdr->src_port = 0; + hdr->dest_port = 0; + if (cmd_flg) { + hdr->token = 0; + atomic_set(&this_mmap.cmd_state, 1); + } + hdr->pkt_size = pkt_size; + return; +} + +int q6asm_open_read(struct audio_client *ac, + uint32_t format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_read open; + + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("%s:session[%d]", __func__, ac->session); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + open.hdr.opcode = ASM_STREAM_CMD_OPEN_READ; + /* Stream prio : High, provide meta info with encoded frames */ + open.src_endpoint = ASM_END_POINT_DEVICE_MATRIX; + + open.pre_proc_top = get_asm_topology(); + if (open.pre_proc_top == 0) + open.pre_proc_top = DEFAULT_POPP_TOPOLOGY; + + switch (format) { + case FORMAT_LINEAR_PCM: + open.uMode = STREAM_PRIORITY_HIGH; + open.format = LINEAR_PCM; + break; + case FORMAT_MPEG4_AAC: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = MPEG4_AAC; + break; + case FORMAT_V13K: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = V13K_FS; + break; + case FORMAT_EVRC: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = EVRC_FS; + break; + case FORMAT_AMRNB: + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_HIGH; + open.format = AMRNB_FS; + break; + default: + pr_err("Invalid format[%d]\n", format); + goto fail_cmd; + } + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("open failed op[0x%x]rc[%d]\n", \ + open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout. waited for OPEN_WRITE rc[%d]\n", __func__, + rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_open_write(struct audio_client *ac, uint32_t format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_write open; + + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("%s: session[%d] wr_format[0x%x]", __func__, ac->session, + format); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + + open.hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE; + open.uMode = STREAM_PRIORITY_HIGH; + /* source endpoint : matrix */ + open.sink_endpoint = ASM_END_POINT_DEVICE_MATRIX; + open.stream_handle = 0x00; + + open.post_proc_top = get_asm_topology(); + if (open.post_proc_top == 0) + open.post_proc_top = DEFAULT_POPP_TOPOLOGY; + + switch (format) { + case FORMAT_LINEAR_PCM: + open.format = LINEAR_PCM; + break; + case FORMAT_MPEG4_AAC: + open.format = MPEG4_AAC; + break; + case FORMAT_WMA_V9: + open.format = WMA_V9; + break; + case FORMAT_WMA_V10PRO: + open.format = WMA_V10PRO; + break; + default: + pr_err("%s: Invalid format[%d]\n", __func__, format); + goto fail_cmd; + } + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("%s: open failed op[0x%x]rc[%d]\n", \ + __func__, open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout. waited for OPEN_WRITE rc[%d]\n", __func__, + rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_open_read_write(struct audio_client *ac, + uint32_t rd_format, + uint32_t wr_format) +{ + int rc = 0x00; + struct asm_stream_cmd_open_read_write open; + + if ((ac == NULL) || (ac->apr == NULL)) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: session[%d]", __func__, ac->session); + pr_debug("wr_format[0x%x]rd_format[0x%x]", + wr_format, rd_format); + + q6asm_add_hdr(ac, &open.hdr, sizeof(open), TRUE); + open.hdr.opcode = ASM_STREAM_CMD_OPEN_READWRITE; + + open.uMode = BUFFER_META_ENABLE | STREAM_PRIORITY_NORMAL | + SR_CM_NOTIFY_ENABLE; + /* source endpoint : matrix */ + open.post_proc_top = get_asm_topology(); + if (open.post_proc_top == 0) + open.post_proc_top = DEFAULT_POPP_TOPOLOGY; + + switch (wr_format) { + case FORMAT_LINEAR_PCM: + open.write_format = LINEAR_PCM; + break; + case FORMAT_MPEG4_AAC: + open.write_format = MPEG4_AAC; + break; + case FORMAT_WMA_V9: + open.write_format = WMA_V9; + break; + case FORMAT_WMA_V10PRO: + open.write_format = WMA_V10PRO; + break; + default: + pr_err("Invalid format[%d]\n", wr_format); + goto fail_cmd; + } + + switch (rd_format) { + case FORMAT_LINEAR_PCM: + open.read_format = LINEAR_PCM; + break; + case FORMAT_MPEG4_AAC: + open.read_format = MPEG4_AAC; + break; + case FORMAT_V13K: + open.read_format = V13K_FS; + break; + case FORMAT_EVRC: + open.read_format = EVRC_FS; + break; + case FORMAT_AMRNB: + open.read_format = AMRNB_FS; + break; + default: + pr_err("Invalid format[%d]\n", rd_format); + goto fail_cmd; + } + pr_debug("%s:rdformat[0x%x]wrformat[0x%x]\n", __func__, + open.read_format, open.write_format); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &open); + if (rc < 0) { + pr_err("open failed op[0x%x]rc[%d]\n", \ + open.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for OPEN_WRITE rc[%d]\n", rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_run(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + struct asm_stream_cmd_run run; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s session[%d]", __func__, ac->session); + q6asm_add_hdr(ac, &run.hdr, sizeof(run), TRUE); + + run.hdr.opcode = ASM_SESSION_CMD_RUN; + run.flags = flags; + run.msw_ts = msw_ts; + run.lsw_ts = lsw_ts; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &run); + if (rc < 0) { + pr_err("Commmand run failed[%d]", rc); + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for run success rc[%d]", rc); + goto fail_cmd; + } + + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_run_nowait(struct audio_client *ac, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + struct asm_stream_cmd_run run; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("%s:APR handle NULL\n", __func__); + return -EINVAL; + } + pr_debug("session[%d]", ac->session); + q6asm_add_hdr_async(ac, &run.hdr, sizeof(run), TRUE); + + run.hdr.opcode = ASM_SESSION_CMD_RUN; + run.flags = flags; + run.msw_ts = msw_ts; + run.lsw_ts = lsw_ts; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &run); + if (rc < 0) { + pr_err("%s:Commmand run failed[%d]", __func__, rc); + return -EINVAL; + } + return 0; +} + + +int q6asm_enc_cfg_blk_aac(struct audio_client *ac, + uint32_t frames_per_buf, + uint32_t sample_rate, uint32_t channels, + uint32_t bit_rate, uint32_t mode, uint32_t format) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]SR[%d]ch[%d]bitrate[%d]mode[%d]" + "format[%d]", __func__, ac->session, frames_per_buf, + sample_rate, channels, bit_rate, mode, format); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + enc_cfg.enc_blk.frames_per_buf = frames_per_buf; + enc_cfg.enc_blk.format_id = MPEG4_AAC; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_aac_read_cfg); + enc_cfg.enc_blk.cfg.aac.bitrate = bit_rate; + enc_cfg.enc_blk.cfg.aac.enc_mode = mode; + enc_cfg.enc_blk.cfg.aac.format = format; + enc_cfg.enc_blk.cfg.aac.ch_cfg = channels; + enc_cfg.enc_blk.cfg.aac.sample_rate = sample_rate; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + + int rc = 0; + + pr_debug("%s: Session %d, rate = %d, channels = %d\n", __func__, + ac->session, rate, channels); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + enc_cfg.enc_blk.frames_per_buf = 1; + enc_cfg.enc_blk.format_id = LINEAR_PCM; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_pcm_cfg); + enc_cfg.enc_blk.cfg.pcm.ch_cfg = channels; + enc_cfg.enc_blk.cfg.pcm.bits_per_sample = 16; + enc_cfg.enc_blk.cfg.pcm.sample_rate = rate; + enc_cfg.enc_blk.cfg.pcm.is_signed = 1; + enc_cfg.enc_blk.cfg.pcm.interleaved = 1; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd open failed\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout opcode[0x%x] ", enc_cfg.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enable_sbrps(struct audio_client *ac, + uint32_t sbr_ps_enable) +{ + struct asm_stream_cmd_encdec_sbr sbrps; + + int rc = 0; + + pr_debug("%s: Session %d\n", __func__, ac->session); + + q6asm_add_hdr(ac, &sbrps.hdr, sizeof(sbrps), TRUE); + + sbrps.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + sbrps.param_id = ASM_ENABLE_SBR_PS; + sbrps.param_size = sizeof(struct asm_sbr_ps); + sbrps.sbr_ps.enable = sbr_ps_enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &sbrps); + if (rc < 0) { + pr_err("Command opcode[0x%x]paramid[0x%x] failed\n", + ASM_STREAM_CMD_SET_ENCDEC_PARAM, + ASM_ENABLE_SBR_PS); + rc = -EINVAL; + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout opcode[0x%x] ", sbrps.hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_qcelp(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t reduced_rate_level, uint16_t rate_modulation_cmd) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]min_rate[0x%4x]max_rate[0x%4x] \ + reduced_rate_level[0x%4x]rate_modulation_cmd[0x%4x]", __func__, + ac->session, frames_per_buf, min_rate, max_rate, + reduced_rate_level, rate_modulation_cmd); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + + enc_cfg.enc_blk.frames_per_buf = frames_per_buf; + enc_cfg.enc_blk.format_id = V13K_FS; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_qcelp13_read_cfg); + enc_cfg.enc_blk.cfg.qcelp13.min_rate = min_rate; + enc_cfg.enc_blk.cfg.qcelp13.max_rate = max_rate; + enc_cfg.enc_blk.cfg.qcelp13.reduced_rate_level = reduced_rate_level; + enc_cfg.enc_blk.cfg.qcelp13.rate_modulation_cmd = rate_modulation_cmd; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_evrc(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t min_rate, uint16_t max_rate, + uint16_t rate_modulation_cmd) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]min_rate[0x%4x]max_rate[0x%4x] \ + rate_modulation_cmd[0x%4x]", __func__, ac->session, + frames_per_buf, min_rate, max_rate, rate_modulation_cmd); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + + enc_cfg.enc_blk.frames_per_buf = frames_per_buf; + enc_cfg.enc_blk.format_id = EVRC_FS; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_evrc_read_cfg); + enc_cfg.enc_blk.cfg.evrc.min_rate = min_rate; + enc_cfg.enc_blk.cfg.evrc.max_rate = max_rate; + enc_cfg.enc_blk.cfg.evrc.rate_modulation_cmd = rate_modulation_cmd; + enc_cfg.enc_blk.cfg.evrc.reserved = 0; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_enc_cfg_blk_amrnb(struct audio_client *ac, uint32_t frames_per_buf, + uint16_t band_mode, uint16_t dtx_enable) +{ + struct asm_stream_cmd_encdec_cfg_blk enc_cfg; + int rc = 0; + + pr_debug("%s:session[%d]frames[%d]band_mode[0x%4x]dtx_enable[0x%4x]", + __func__, ac->session, frames_per_buf, band_mode, dtx_enable); + + q6asm_add_hdr(ac, &enc_cfg.hdr, sizeof(enc_cfg), TRUE); + + enc_cfg.hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + + enc_cfg.param_id = ASM_ENCDEC_CFG_BLK_ID; + enc_cfg.param_size = sizeof(struct asm_encode_cfg_blk); + + enc_cfg.enc_blk.frames_per_buf = frames_per_buf; + enc_cfg.enc_blk.format_id = AMRNB_FS; + enc_cfg.enc_blk.cfg_size = sizeof(struct asm_amrnb_read_cfg); + enc_cfg.enc_blk.cfg.amrnb.mode = band_mode; + enc_cfg.enc_blk.cfg.amrnb.dtx_mode = dtx_enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &enc_cfg); + if (rc < 0) { + pr_err("Comamnd %d failed\n", ASM_STREAM_CMD_SET_ENCDEC_PARAM); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for FORMAT_UPDATE\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_pcm(struct audio_client *ac, + uint32_t rate, uint32_t channels) +{ + struct asm_stream_media_format_update fmt; + int rc = 0; + + pr_debug("%s:session[%d]rate[%d]ch[%d]\n", __func__, ac->session, rate, + channels); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = LINEAR_PCM; + fmt.cfg_size = sizeof(struct asm_pcm_cfg); + fmt.write_cfg.pcm_cfg.ch_cfg = channels; + fmt.write_cfg.pcm_cfg.bits_per_sample = 16; + fmt.write_cfg.pcm_cfg.sample_rate = rate; + fmt.write_cfg.pcm_cfg.is_signed = 1; + fmt.write_cfg.pcm_cfg.interleaved = 1; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_aac(struct audio_client *ac, + struct asm_aac_cfg *cfg) +{ + struct asm_stream_media_format_update fmt; + int rc = 0; + + pr_debug("%s:session[%d]rate[%d]ch[%d]\n", __func__, ac->session, + cfg->sample_rate, cfg->ch_cfg); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = MPEG4_AAC; + fmt.cfg_size = sizeof(struct asm_aac_cfg); + fmt.write_cfg.aac_cfg.format = cfg->format; + fmt.write_cfg.aac_cfg.aot = cfg->aot; + fmt.write_cfg.aac_cfg.ep_config = cfg->ep_config; + fmt.write_cfg.aac_cfg.section_data_resilience = + cfg->section_data_resilience; + fmt.write_cfg.aac_cfg.scalefactor_data_resilience = + cfg->scalefactor_data_resilience; + fmt.write_cfg.aac_cfg.spectral_data_resilience = + cfg->spectral_data_resilience; + fmt.write_cfg.aac_cfg.ch_cfg = cfg->ch_cfg; + fmt.write_cfg.aac_cfg.sample_rate = cfg->sample_rate; + pr_info("%s:format=%x cfg_size=%d aac-cfg=%x aot=%d ch=%d sr=%d\n", + __func__, fmt.format, fmt.cfg_size, + fmt.write_cfg.aac_cfg.format, + fmt.write_cfg.aac_cfg.aot, + fmt.write_cfg.aac_cfg.ch_cfg, + fmt.write_cfg.aac_cfg.sample_rate); + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_wma(struct audio_client *ac, + void *cfg) +{ + struct asm_stream_media_format_update fmt; + struct asm_wma_cfg *wma_cfg = (struct asm_wma_cfg *)cfg; + int rc = 0; + + pr_debug("session[%d]format_tag[0x%4x] rate[%d] ch[0x%4x] bps[%d],\ + balign[0x%4x], bit_sample[0x%4x], ch_msk[%d], enc_opt[0x%4x]\n", + ac->session, wma_cfg->format_tag, wma_cfg->sample_rate, + wma_cfg->ch_cfg, wma_cfg->avg_bytes_per_sec, + wma_cfg->block_align, wma_cfg->valid_bits_per_sample, + wma_cfg->ch_mask, wma_cfg->encode_opt); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = WMA_V9; + fmt.cfg_size = sizeof(struct asm_wma_cfg); + fmt.write_cfg.wma_cfg.format_tag = wma_cfg->format_tag; + fmt.write_cfg.wma_cfg.ch_cfg = wma_cfg->ch_cfg; + fmt.write_cfg.wma_cfg.sample_rate = wma_cfg->sample_rate; + fmt.write_cfg.wma_cfg.avg_bytes_per_sec = wma_cfg->avg_bytes_per_sec; + fmt.write_cfg.wma_cfg.block_align = wma_cfg->block_align; + fmt.write_cfg.wma_cfg.valid_bits_per_sample = + wma_cfg->valid_bits_per_sample; + fmt.write_cfg.wma_cfg.ch_mask = wma_cfg->ch_mask; + fmt.write_cfg.wma_cfg.encode_opt = wma_cfg->encode_opt; + fmt.write_cfg.wma_cfg.adv_encode_opt = 0; + fmt.write_cfg.wma_cfg.adv_encode_opt2 = 0; + fmt.write_cfg.wma_cfg.drc_peak_ref = 0; + fmt.write_cfg.wma_cfg.drc_peak_target = 0; + fmt.write_cfg.wma_cfg.drc_ave_ref = 0; + fmt.write_cfg.wma_cfg.drc_ave_target = 0; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_media_format_block_wmapro(struct audio_client *ac, + void *cfg) +{ + struct asm_stream_media_format_update fmt; + struct asm_wmapro_cfg *wmapro_cfg = (struct asm_wmapro_cfg *)cfg; + int rc = 0; + + pr_debug("session[%d]format_tag[0x%4x] rate[%d] ch[0x%4x] bps[%d]," + "balign[0x%4x], bit_sample[0x%4x], ch_msk[%d], enc_opt[0x%4x],\ + adv_enc_opt[0x%4x], adv_enc_opt2[0x%8x]\n", + ac->session, wmapro_cfg->format_tag, wmapro_cfg->sample_rate, + wmapro_cfg->ch_cfg, wmapro_cfg->avg_bytes_per_sec, + wmapro_cfg->block_align, wmapro_cfg->valid_bits_per_sample, + wmapro_cfg->ch_mask, wmapro_cfg->encode_opt, + wmapro_cfg->adv_encode_opt, wmapro_cfg->adv_encode_opt2); + + q6asm_add_hdr(ac, &fmt.hdr, sizeof(fmt), TRUE); + + fmt.hdr.opcode = ASM_DATA_CMD_MEDIA_FORMAT_UPDATE; + + fmt.format = WMA_V10PRO; + fmt.cfg_size = sizeof(struct asm_wmapro_cfg); + fmt.write_cfg.wmapro_cfg.format_tag = wmapro_cfg->format_tag; + fmt.write_cfg.wmapro_cfg.ch_cfg = wmapro_cfg->ch_cfg; + fmt.write_cfg.wmapro_cfg.sample_rate = wmapro_cfg->sample_rate; + fmt.write_cfg.wmapro_cfg.avg_bytes_per_sec = + wmapro_cfg->avg_bytes_per_sec; + fmt.write_cfg.wmapro_cfg.block_align = wmapro_cfg->block_align; + fmt.write_cfg.wmapro_cfg.valid_bits_per_sample = + wmapro_cfg->valid_bits_per_sample; + fmt.write_cfg.wmapro_cfg.ch_mask = wmapro_cfg->ch_mask; + fmt.write_cfg.wmapro_cfg.encode_opt = wmapro_cfg->encode_opt; + fmt.write_cfg.wmapro_cfg.adv_encode_opt = wmapro_cfg->adv_encode_opt; + fmt.write_cfg.wmapro_cfg.adv_encode_opt2 = wmapro_cfg->adv_encode_opt2; + fmt.write_cfg.wmapro_cfg.drc_peak_ref = 0; + fmt.write_cfg.wmapro_cfg.drc_peak_target = 0; + fmt.write_cfg.wmapro_cfg.drc_ave_ref = 0; + fmt.write_cfg.wmapro_cfg.drc_ave_target = 0; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &fmt); + if (rc < 0) { + pr_err("%s:Comamnd open failed\n", __func__); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s:timeout. waited for FORMAT_UPDATE\n", __func__); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_memory_map(struct audio_client *ac, uint32_t buf_add, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct asm_stream_cmd_memory_map mem_map; + int rc = 0; + + if (!ac || ac->apr == NULL || this_mmap.apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + mem_map.hdr.opcode = ASM_SESSION_CMD_MEMORY_MAP; + + mem_map.buf_add = buf_add; + mem_map.buf_size = bufsz * bufcnt; + mem_map.mempool_id = 0; /* EBI */ + mem_map.reserved = 0; + + q6asm_add_mmaphdr(&mem_map.hdr, + sizeof(struct asm_stream_cmd_memory_map), TRUE); + + pr_debug("buf add[%x] buf_add_parameter[%x]\n", + mem_map.buf_add, buf_add); + + rc = apr_send_pkt(this_mmap.apr, (uint32_t *) &mem_map); + if (rc < 0) { + pr_err("mem_map op[0x%x]rc[%d]\n", + mem_map.hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), 5 * HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_memory_unmap(struct audio_client *ac, uint32_t buf_add, int dir) +{ + struct asm_stream_cmd_memory_unmap mem_unmap; + int rc = 0; + + if (!ac || ac->apr == NULL || this_mmap.apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + q6asm_add_mmaphdr(&mem_unmap.hdr, + sizeof(struct asm_stream_cmd_memory_unmap), TRUE); + mem_unmap.hdr.opcode = ASM_SESSION_CMD_MEMORY_UNMAP; + mem_unmap.buf_add = buf_add; + + rc = apr_send_pkt(this_mmap.apr, (uint32_t *) &mem_unmap); + if (rc < 0) { + pr_err("mem_unmap op[0x%x]rc[%d]\n", + mem_unmap.hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), 5 * HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + return rc; +} + +int q6asm_set_lrgain(struct audio_client *ac, int left_gain, int right_gain) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_lrchannel_gain_params *lrgain = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_lrchannel_gain_params); + vol_cmd = kzalloc(sz, GFP_KERNEL); + if (vol_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + return rc; + } + cmd = (struct asm_pp_params_command *)vol_cmd; + q6asm_add_hdr_async(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_lrchannel_gain_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = L_R_CHANNEL_GAIN_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_lrchannel_gain_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + lrgain = (struct asm_lrchannel_gain_params *)payload; + + lrgain->left_gain = left_gain; + lrgain->right_gain = right_gain; + rc = apr_send_pkt(ac->apr, (uint32_t *) vol_cmd); + if (rc < 0) { + pr_err("%s: Volume Command failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending volume command to apr\n", + __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(vol_cmd); + return rc; +} + +static int q6asm_memory_map_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct asm_stream_cmd_memory_map_regions *mmap_regions = NULL; + struct asm_memory_map_regions *mregions = NULL; + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *mmap_region_cmd = NULL; + void *payload = NULL; + int rc = 0; + int i = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || this_mmap.apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + cmd_size = sizeof(struct asm_stream_cmd_memory_map_regions) + + sizeof(struct asm_memory_map_regions) * bufcnt; + + mmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + mmap_regions = (struct asm_stream_cmd_memory_map_regions *) + mmap_region_cmd; + q6asm_add_mmaphdr(&mmap_regions->hdr, cmd_size, TRUE); + mmap_regions->hdr.opcode = ASM_SESSION_CMD_MEMORY_MAP_REGIONS; + mmap_regions->mempool_id = 0; + mmap_regions->nregions = bufcnt & 0x00ff; + pr_debug("map_regions->nregions = %d\n", mmap_regions->nregions); + payload = ((u8 *) mmap_region_cmd + + sizeof(struct asm_stream_cmd_memory_map_regions)); + mregions = (struct asm_memory_map_regions *)payload; + + port = &ac->port[dir]; + for (i = 0; i < bufcnt; i++) { + ab = &port->buf[i]; + mregions->phys = ab->phys; + mregions->buf_size = ab->size; + ++mregions; + } + + rc = apr_send_pkt(this_mmap.apr, (uint32_t *) mmap_region_cmd); + if (rc < 0) { + pr_err("mmap_regions op[0x%x]rc[%d]\n", + mmap_regions->hdr.opcode, rc); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for memory_map\n"); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(mmap_region_cmd); + return rc; +} + +static int q6asm_memory_unmap_regions(struct audio_client *ac, int dir, + uint32_t bufsz, uint32_t bufcnt) +{ + struct asm_stream_cmd_memory_unmap_regions *unmap_regions = NULL; + struct asm_memory_unmap_regions *mregions = NULL; + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + void *unmap_region_cmd = NULL; + void *payload = NULL; + int rc = 0; + int i = 0; + int cmd_size = 0; + + if (!ac || ac->apr == NULL || this_mmap.apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: Session[%d]\n", __func__, ac->session); + + cmd_size = sizeof(struct asm_stream_cmd_memory_unmap_regions) + + sizeof(struct asm_memory_unmap_regions) * bufcnt; + + unmap_region_cmd = kzalloc(cmd_size, GFP_KERNEL); + unmap_regions = (struct asm_stream_cmd_memory_unmap_regions *) + unmap_region_cmd; + q6asm_add_mmaphdr(&unmap_regions->hdr, cmd_size, TRUE); + unmap_regions->hdr.opcode = ASM_SESSION_CMD_MEMORY_UNMAP_REGIONS; + unmap_regions->nregions = bufcnt & 0x00ff; + pr_debug("unmap_regions->nregions = %d\n", unmap_regions->nregions); + payload = ((u8 *) unmap_region_cmd + + sizeof(struct asm_stream_cmd_memory_unmap_regions)); + mregions = (struct asm_memory_unmap_regions *)payload; + port = &ac->port[dir]; + for (i = 0; i < bufcnt; i++) { + ab = &port->buf[i]; + mregions->phys = ab->phys; + ++mregions; + } + + rc = apr_send_pkt(this_mmap.apr, (uint32_t *) unmap_region_cmd); + if (rc < 0) { + pr_err("mmap_regions op[0x%x]rc[%d]\n", + unmap_regions->hdr.opcode, rc); + goto fail_cmd; + } + + rc = wait_event_timeout(this_mmap.cmd_wait, + (atomic_read(&this_mmap.cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for memory_unmap\n"); + goto fail_cmd; + } + rc = 0; + +fail_cmd: + kfree(unmap_region_cmd); + return rc; +} + +int q6asm_set_mute(struct audio_client *ac, int muteflag) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_mute_params *mute = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_mute_params); + vol_cmd = kzalloc(sz, GFP_KERNEL); + if (vol_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + return rc; + } + cmd = (struct asm_pp_params_command *)vol_cmd; + q6asm_add_hdr_async(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_mute_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = MUTE_CONFIG_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_mute_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + mute = (struct asm_mute_params *)payload; + + mute->muteflag = muteflag; + rc = apr_send_pkt(ac->apr, (uint32_t *) vol_cmd); + if (rc < 0) { + pr_err("%s: Mute Command failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending mute command to apr\n", + __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(vol_cmd); + return rc; +} + +int q6asm_set_volume(struct audio_client *ac, int volume) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_master_gain_params *mgain = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_master_gain_params); + vol_cmd = kzalloc(sz, GFP_KERNEL); + if (vol_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + return rc; + } + cmd = (struct asm_pp_params_command *)vol_cmd; + q6asm_add_hdr_async(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_master_gain_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = MASTER_GAIN_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_master_gain_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + mgain = (struct asm_master_gain_params *)payload; + + mgain->master_gain = volume; + mgain->padding = 0x00; + rc = apr_send_pkt(ac->apr, (uint32_t *) vol_cmd); + if (rc < 0) { + pr_err("%s: Volume Command failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending volume command to apr\n", + __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(vol_cmd); + return rc; +} + +int q6asm_set_softpause(struct audio_client *ac, + struct asm_softpause_params *pause_param) +{ + void *vol_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_softpause_params *params = NULL; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_softpause_params); + vol_cmd = kzalloc(sz, GFP_KERNEL); + if (vol_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + return rc; + } + cmd = (struct asm_pp_params_command *)vol_cmd; + q6asm_add_hdr_async(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_softpause_params); + cmd->params.module_id = VOLUME_CONTROL_MODULE_ID; + cmd->params.param_id = SOFT_PAUSE_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_softpause_params); + cmd->params.reserved = 0; + + payload = (u8 *)(vol_cmd + sizeof(struct asm_pp_params_command)); + params = (struct asm_softpause_params *)payload; + + params->enable = pause_param->enable; + params->period = pause_param->period; + params->step = pause_param->step; + params->rampingcurve = pause_param->rampingcurve; + pr_debug("%s: soft Pause Command: enable = %d, period = %d," + "step = %d, curve = %d\n", __func__, params->enable, + params->period, params->step, params->rampingcurve); + rc = apr_send_pkt(ac->apr, (uint32_t *) vol_cmd); + if (rc < 0) { + pr_err("%s: Volume Command(soft_pause) failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending volume command(soft_pause)" + "to apr\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(vol_cmd); + return rc; +} + +int q6asm_equalizer(struct audio_client *ac, void *eq) +{ + void *eq_cmd = NULL; + void *payload = NULL; + struct asm_pp_params_command *cmd = NULL; + struct asm_equalizer_params *equalizer = NULL; + struct msm_audio_eq_stream_config *eq_params = NULL; + int i = 0; + int sz = 0; + int rc = 0; + + sz = sizeof(struct asm_pp_params_command) + + + sizeof(struct asm_equalizer_params); + eq_cmd = kzalloc(sz, GFP_KERNEL); + if (eq_cmd == NULL) { + pr_err("%s[%d]: Mem alloc failed\n", __func__, ac->session); + rc = -EINVAL; + goto fail_cmd; + } + eq_params = (struct msm_audio_eq_stream_config *) eq; + cmd = (struct asm_pp_params_command *)eq_cmd; + q6asm_add_hdr(ac, &cmd->hdr, sz, TRUE); + cmd->hdr.opcode = ASM_STREAM_CMD_SET_PP_PARAMS; + cmd->payload = NULL; + cmd->payload_size = sizeof(struct asm_pp_param_data_hdr) + + sizeof(struct asm_equalizer_params); + cmd->params.module_id = EQUALIZER_MODULE_ID; + cmd->params.param_id = EQUALIZER_PARAM_ID; + cmd->params.param_size = sizeof(struct asm_equalizer_params); + cmd->params.reserved = 0; + payload = (u8 *)(eq_cmd + sizeof(struct asm_pp_params_command)); + equalizer = (struct asm_equalizer_params *)payload; + + equalizer->enable = eq_params->enable; + equalizer->num_bands = eq_params->num_bands; + pr_debug("%s: enable:%d numbands:%d\n", __func__, eq_params->enable, + eq_params->num_bands); + for (i = 0; i < eq_params->num_bands; i++) { + equalizer->eq_bands[i].band_idx = + eq_params->eq_bands[i].band_idx; + equalizer->eq_bands[i].filter_type = + eq_params->eq_bands[i].filter_type; + equalizer->eq_bands[i].center_freq_hz = + eq_params->eq_bands[i].center_freq_hz; + equalizer->eq_bands[i].filter_gain = + eq_params->eq_bands[i].filter_gain; + equalizer->eq_bands[i].q_factor = + eq_params->eq_bands[i].q_factor; + pr_debug("%s: filter_type:%u bandnum:%d\n", __func__, + eq_params->eq_bands[i].filter_type, i); + pr_debug("%s: center_freq_hz:%u bandnum:%d\n", __func__, + eq_params->eq_bands[i].center_freq_hz, i); + pr_debug("%s: filter_gain:%d bandnum:%d\n", __func__, + eq_params->eq_bands[i].filter_gain, i); + pr_debug("%s: q_factor:%d bandnum:%d\n", __func__, + eq_params->eq_bands[i].q_factor, i); + } + rc = apr_send_pkt(ac->apr, (uint32_t *) eq_cmd); + if (rc < 0) { + pr_err("%s: Equalizer Command failed\n", __func__); + rc = -EINVAL; + goto fail_cmd; + } + + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in sending equalizer command to apr\n", + __func__); + rc = -EINVAL; + goto fail_cmd; + } + rc = 0; +fail_cmd: + kfree(eq_cmd); + return rc; +} + +int q6asm_read(struct audio_client *ac) +{ + struct asm_stream_cmd_read read; + struct audio_buffer *ab; + int dsp_buf; + struct audio_port_data *port; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[OUT]; + + q6asm_add_hdr(ac, &read.hdr, sizeof(read), FALSE); + + mutex_lock(&port->lock); + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + pr_debug("%s:session[%d]dsp-buf[%d][%p]cpu_buf[%d][%p]\n", + __func__, + ac->session, + dsp_buf, + (void *)port->buf[dsp_buf].data, + port->cpu_buf, + (void *)port->buf[port->cpu_buf].phys); + + read.hdr.opcode = ASM_DATA_CMD_READ; + read.buf_add = ab->phys; + read.buf_size = ab->size; + read.uid = port->dsp_buf; + read.hdr.token = port->dsp_buf; + + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + mutex_unlock(&port->lock); + pr_debug("%s:buf add[0x%x] token[%d] uid[%d]\n", __func__, + read.buf_add, + read.hdr.token, + read.uid); + rc = apr_send_pkt(ac->apr, (uint32_t *) &read); + if (rc < 0) { + pr_err("read op[0x%x]rc[%d]\n", read.hdr.opcode, rc); + goto fail_cmd; + } + return 0; + } +fail_cmd: + return -EINVAL; +} + +int q6asm_read_nolock(struct audio_client *ac) +{ + struct asm_stream_cmd_read read; + struct audio_buffer *ab; + int dsp_buf; + struct audio_port_data *port; + int rc; + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[OUT]; + + q6asm_add_hdr_async(ac, &read.hdr, sizeof(read), FALSE); + + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + pr_debug("%s:session[%d]dsp-buf[%d][%p]cpu_buf[%d][%p]\n", + __func__, + ac->session, + dsp_buf, + (void *)port->buf[dsp_buf].data, + port->cpu_buf, + (void *)port->buf[port->cpu_buf].phys); + + read.hdr.opcode = ASM_DATA_CMD_READ; + read.buf_add = ab->phys; + read.buf_size = ab->size; + read.uid = port->dsp_buf; + read.hdr.token = port->dsp_buf; + + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + pr_debug("%s:buf add[0x%x] token[%d] uid[%d]\n", __func__, + read.buf_add, + read.hdr.token, + read.uid); + rc = apr_send_pkt(ac->apr, (uint32_t *) &read); + if (rc < 0) { + pr_err("read op[0x%x]rc[%d]\n", read.hdr.opcode, rc); + goto fail_cmd; + } + return 0; + } +fail_cmd: + return -EINVAL; +} + + +static void q6asm_add_hdr_async(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, uint32_t cmd_flg) +{ + pr_debug("session=%d pkt size=%d cmd_flg=%d\n", pkt_size, cmd_flg, + ac->session); + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(sizeof(struct apr_hdr)),\ + APR_PKT_VER); + hdr->src_svc = ((struct apr_svc *)ac->apr)->id; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->dest_svc = APR_SVC_ASM; + hdr->dest_domain = APR_DOMAIN_ADSP; + hdr->src_port = ((ac->session << 8) & 0xFF00) | 0x01; + hdr->dest_port = ((ac->session << 8) & 0xFF00) | 0x01; + if (cmd_flg) { + hdr->token = ac->session; + atomic_set(&ac->cmd_state, 1); + } + hdr->pkt_size = pkt_size; + return; +} + +int q6asm_async_write(struct audio_client *ac, + struct audio_aio_write_param *param) +{ + int rc = 0; + struct asm_stream_cmd_write write; + + if (!ac || ac->apr == NULL) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + + q6asm_add_hdr_async(ac, &write.hdr, sizeof(write), FALSE); + + /* Pass physical address as token for AIO scheme */ + write.hdr.token = param->uid; + write.hdr.opcode = ASM_DATA_CMD_WRITE; + write.buf_add = param->paddr; + write.avail_bytes = param->len; + write.uid = param->uid; + write.msw_ts = param->msw_ts; + write.lsw_ts = param->lsw_ts; + /* Use 0xFF00 for disabling timestamps */ + if (param->flags == 0xFF00) + write.uflags = (0x00000000 | (param->flags & 0x800000FF)); + else + write.uflags = (0x80000000 | param->flags); + + pr_debug("%s: session[%d] bufadd[0x%x]len[0x%x]", __func__, ac->session, + write.buf_add, write.avail_bytes); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &write); + if (rc < 0) { + pr_debug("[%s] write op[0x%x]rc[%d]\n", __func__, + write.hdr.opcode, rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_async_read(struct audio_client *ac, + struct audio_aio_read_param *param) +{ + int rc = 0; + struct asm_stream_cmd_read read; + + if (!ac || ac->apr == NULL) { + pr_err("%s: APR handle NULL\n", __func__); + return -EINVAL; + } + + q6asm_add_hdr_async(ac, &read.hdr, sizeof(read), FALSE); + + /* Pass physical address as token for AIO scheme */ + read.hdr.token = param->paddr; + read.hdr.opcode = ASM_DATA_CMD_READ; + read.buf_add = param->paddr; + read.buf_size = param->len; + read.uid = param->uid; + + pr_debug("%s: session[%d] bufadd[0x%x]len[0x%x]", __func__, ac->session, + read.buf_add, read.buf_size); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &read); + if (rc < 0) { + pr_debug("[%s] read op[0x%x]rc[%d]\n", __func__, + read.hdr.opcode, rc); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_write(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags) +{ + int rc = 0; + struct asm_stream_cmd_write write; + struct audio_port_data *port; + struct audio_buffer *ab; + int dsp_buf = 0; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: session[%d] len=%d", __func__, ac->session, len); + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[IN]; + + q6asm_add_hdr(ac, &write.hdr, sizeof(write), + FALSE); + mutex_lock(&port->lock); + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + write.hdr.token = port->dsp_buf; + write.hdr.opcode = ASM_DATA_CMD_WRITE; + write.buf_add = ab->phys; + write.avail_bytes = len; + write.uid = port->dsp_buf; + write.msw_ts = msw_ts; + write.lsw_ts = lsw_ts; + /* Use 0xFF00 for disabling timestamps */ + if (flags == 0xFF00) + write.uflags = (0x00000000 | (flags & 0x800000FF)); + else + write.uflags = (0x80000000 | flags); + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + + pr_debug("%s:ab->phys[0x%x]bufadd[0x%x]token[0x%x]buf_id[0x%x]" + , __func__, + ab->phys, + write.buf_add, + write.hdr.token, + write.uid); + mutex_unlock(&port->lock); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &write); + if (rc < 0) { + pr_err("write op[0x%x]rc[%d]\n", write.hdr.opcode, rc); + goto fail_cmd; + } + pr_debug("%s: WRITE SUCCESS\n", __func__); + return 0; + } +fail_cmd: + return -EINVAL; +} + +int q6asm_write_nolock(struct audio_client *ac, uint32_t len, uint32_t msw_ts, + uint32_t lsw_ts, uint32_t flags) +{ + int rc = 0; + struct asm_stream_cmd_write write; + struct audio_port_data *port; + struct audio_buffer *ab; + int dsp_buf = 0; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s: session[%d] len=%d", __func__, ac->session, len); + if (ac->io_mode == SYNC_IO_MODE) { + port = &ac->port[IN]; + + q6asm_add_hdr_async(ac, &write.hdr, sizeof(write), + FALSE); + + dsp_buf = port->dsp_buf; + ab = &port->buf[dsp_buf]; + + write.hdr.token = port->dsp_buf; + write.hdr.opcode = ASM_DATA_CMD_WRITE; + write.buf_add = ab->phys; + write.avail_bytes = len; + write.uid = port->dsp_buf; + write.msw_ts = msw_ts; + write.lsw_ts = lsw_ts; + /* Use 0xFF00 for disabling timestamps */ + if (flags == 0xFF00) + write.uflags = (0x00000000 | (flags & 0x800000FF)); + else + write.uflags = (0x80000000 | flags); + port->dsp_buf = (port->dsp_buf + 1) & (port->max_buf_cnt - 1); + + pr_debug("%s:ab->phys[0x%x]bufadd[0x%x]token[0x%x]buf_id[0x%x]" + , __func__, + ab->phys, + write.buf_add, + write.hdr.token, + write.uid); + + rc = apr_send_pkt(ac->apr, (uint32_t *) &write); + if (rc < 0) { + pr_err("write op[0x%x]rc[%d]\n", write.hdr.opcode, rc); + goto fail_cmd; + } + pr_debug("%s: WRITE SUCCESS\n", __func__); + return 0; + } +fail_cmd: + return -EINVAL; +} + +uint64_t q6asm_get_session_time(struct audio_client *ac) +{ + struct apr_hdr hdr; + int rc; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + q6asm_add_hdr(ac, &hdr, sizeof(hdr), TRUE); + hdr.opcode = ASM_SESSION_CMD_GET_SESSION_TIME; + atomic_set(&ac->time_flag, 1); + + pr_debug("%s: session[%d]opcode[0x%x]\n", __func__, + ac->session, + hdr.opcode); + rc = apr_send_pkt(ac->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("Commmand 0x%x failed\n", hdr.opcode); + goto fail_cmd; + } + rc = wait_event_timeout(ac->time_wait, + (atomic_read(&ac->time_flag) == 0), 5*HZ); + if (!rc) { + pr_err("%s: timeout in getting session time from DSP\n", + __func__); + goto fail_cmd; + } + return ac->time_stamp; + +fail_cmd: + return -EINVAL; +} + +int q6asm_cmd(struct audio_client *ac, int cmd) +{ + struct apr_hdr hdr; + int rc; + atomic_t *state; + int cnt = 0; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + q6asm_add_hdr(ac, &hdr, sizeof(hdr), TRUE); + switch (cmd) { + case CMD_PAUSE: + pr_debug("%s:CMD_PAUSE\n", __func__); + hdr.opcode = ASM_SESSION_CMD_PAUSE; + state = &ac->cmd_state; + break; + case CMD_FLUSH: + pr_debug("%s:CMD_FLUSH\n", __func__); + hdr.opcode = ASM_STREAM_CMD_FLUSH; + state = &ac->cmd_state; + break; + case CMD_OUT_FLUSH: + pr_debug("%s:CMD_OUT_FLUSH\n", __func__); + hdr.opcode = ASM_STREAM_CMD_FLUSH_READBUFS; + state = &ac->cmd_state; + break; + case CMD_EOS: + pr_debug("%s:CMD_EOS\n", __func__); + hdr.opcode = ASM_DATA_CMD_EOS; + atomic_set(&ac->cmd_state, 0); + state = &ac->cmd_state; + break; + case CMD_CLOSE: + pr_debug("%s:CMD_CLOSE\n", __func__); + hdr.opcode = ASM_STREAM_CMD_CLOSE; + state = &ac->cmd_state; + break; + default: + pr_err("Invalid format[%d]\n", cmd); + goto fail_cmd; + } + pr_debug("%s:session[%d]opcode[0x%x] ", __func__, + ac->session, + hdr.opcode); + rc = apr_send_pkt(ac->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("Commmand 0x%x failed\n", hdr.opcode); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, (atomic_read(state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for response opcode[0x%x]\n", + hdr.opcode); + goto fail_cmd; + } + if (cmd == CMD_FLUSH) + q6asm_reset_buf_state(ac); + if (cmd == CMD_CLOSE) { + /* check if DSP return all buffers */ + if (ac->port[IN].buf) { + for (cnt = 0; cnt < ac->port[IN].max_buf_cnt; + cnt++) { + if (ac->port[IN].buf[cnt].used == IN) { + pr_debug("Write Buf[%d] not returned\n", + cnt); + } + } + } + if (ac->port[OUT].buf) { + for (cnt = 0; cnt < ac->port[OUT].max_buf_cnt; cnt++) { + if (ac->port[OUT].buf[cnt].used == OUT) { + pr_debug("Read Buf[%d] not returned\n", + cnt); + } + } + } + } + return 0; +fail_cmd: + return -EINVAL; +} + +int q6asm_cmd_nowait(struct audio_client *ac, int cmd) +{ + struct apr_hdr hdr; + int rc; + + if (!ac || ac->apr == NULL) { + pr_err("%s:APR handle NULL\n", __func__); + return -EINVAL; + } + q6asm_add_hdr_async(ac, &hdr, sizeof(hdr), TRUE); + switch (cmd) { + case CMD_PAUSE: + pr_debug("%s:CMD_PAUSE\n", __func__); + hdr.opcode = ASM_SESSION_CMD_PAUSE; + break; + case CMD_EOS: + pr_debug("%s:CMD_EOS\n", __func__); + hdr.opcode = ASM_DATA_CMD_EOS; + break; + default: + pr_err("%s:Invalid format[%d]\n", __func__, cmd); + goto fail_cmd; + } + pr_debug("%s:session[%d]opcode[0x%x] ", __func__, + ac->session, + hdr.opcode); + rc = apr_send_pkt(ac->apr, (uint32_t *) &hdr); + if (rc < 0) { + pr_err("%s:Commmand 0x%x failed\n", __func__, hdr.opcode); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +static void q6asm_reset_buf_state(struct audio_client *ac) +{ + int cnt = 0; + int loopcnt = 0; + struct audio_port_data *port = NULL; + + if (ac->io_mode == SYNC_IO_MODE) { + mutex_lock(&ac->cmd_lock); + for (loopcnt = 0; loopcnt <= OUT; loopcnt++) { + port = &ac->port[loopcnt]; + cnt = port->max_buf_cnt - 1; + port->dsp_buf = 0; + port->cpu_buf = 0; + while (cnt >= 0) { + if (!port->buf) + continue; + port->buf[cnt].used = 1; + cnt--; + } + } + mutex_unlock(&ac->cmd_lock); + } +} + +int q6asm_reg_tx_overflow(struct audio_client *ac, uint16_t enable) +{ + struct asm_stream_cmd_reg_tx_overflow_event tx_overflow; + int rc; + + if (!ac || ac->apr == NULL) { + pr_err("APR handle NULL\n"); + return -EINVAL; + } + pr_debug("%s:session[%d]enable[%d]\n", __func__, + ac->session, enable); + q6asm_add_hdr(ac, &tx_overflow.hdr, sizeof(tx_overflow), TRUE); + + tx_overflow.hdr.opcode = \ + ASM_SESSION_CMD_REGISTER_FOR_TX_OVERFLOW_EVENTS; + /* tx overflow event: enable */ + tx_overflow.enable = enable; + + rc = apr_send_pkt(ac->apr, (uint32_t *) &tx_overflow); + if (rc < 0) { + pr_err("tx overflow op[0x%x]rc[%d]\n", \ + tx_overflow.hdr.opcode, rc); + goto fail_cmd; + } + rc = wait_event_timeout(ac->cmd_wait, + (atomic_read(&ac->cmd_state) == 0), 5*HZ); + if (!rc) { + pr_err("timeout. waited for tx overflow\n"); + goto fail_cmd; + } + return 0; +fail_cmd: + return -EINVAL; +} + +#ifdef CONFIG_MSM8X60_RTAC +int q6asm_get_apr_service_id(int session_id) +{ + pr_debug("%s\n", __func__); + + if (session_id < 0) { + pr_err("%s: invalid session_id = %d\n", __func__, session_id); + return -EINVAL; + } + + return ((struct apr_svc *)session[session_id]->apr)->id; +} +#endif + + +static int __init q6asm_init(void) +{ + pr_debug("%s\n", __func__); + init_waitqueue_head(&this_mmap.cmd_wait); + memset(session, 0, sizeof(session)); + return 0; +} + +device_initcall(q6asm_init); diff --git a/sound/soc/msm/qdsp6/q6voice.c b/sound/soc/msm/qdsp6/q6voice.c new file mode 100644 index 00000000000..28d1acf0495 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6voice.c @@ -0,0 +1,2089 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "sound/apr_audio.h" +#include "sound/q6afe.h" +#include "q6voice.h" + +#define TIMEOUT_MS 3000 + +#define CMD_STATUS_SUCCESS 0 +#define CMD_STATUS_FAIL 1 + +#define VOC_PATH_PASSIVE 0 +#define VOC_PATH_FULL 1 + +static struct voice_data voice; + +static int voice_send_enable_vocproc_cmd(struct voice_data *v); +static int voice_send_netid_timing_cmd(struct voice_data *v); +static int voice_send_attach_vocproc_cmd(struct voice_data *v); +static int voice_send_set_device_cmd(struct voice_data *v); +static int voice_send_disable_vocproc_cmd(struct voice_data *v); +static int voice_send_vol_index_cmd(struct voice_data *v); + +static int32_t qdsp_mvm_callback(struct apr_client_data *data, void *priv); +static int32_t qdsp_cvs_callback(struct apr_client_data *data, void *priv); +static int32_t qdsp_cvp_callback(struct apr_client_data *data, void *priv); + +static u16 voice_get_mvm_handle(struct voice_data *v) +{ + u16 mvm_handle = 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + if (v->voc_path == VOC_PATH_PASSIVE) + mvm_handle = v->mvm_passive_handle; + else + mvm_handle = v->mvm_full_handle; + + pr_debug("%s: mvm_handle %d\n", __func__, mvm_handle); + + return mvm_handle; +} + +static void voice_set_mvm_handle(struct voice_data *v, u16 mvm_handle) +{ + pr_debug("%s: mvm_handle %d\n", __func__, mvm_handle); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return; + } + + if (v->voc_path == VOC_PATH_PASSIVE) + v->mvm_passive_handle = mvm_handle; + else + v->mvm_full_handle = mvm_handle; +} + +static u16 voice_get_cvs_handle(struct voice_data *v) +{ + u16 cvs_handle = 0; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + if (v->voc_path == VOC_PATH_PASSIVE) + cvs_handle = v->cvs_passive_handle; + else + cvs_handle = v->cvs_full_handle; + + pr_debug("%s: cvs_handle %d\n", __func__, cvs_handle); + + return cvs_handle; +} + +static void voice_set_cvs_handle(struct voice_data *v, u16 cvs_handle) +{ + pr_debug("%s: cvs_handle %d\n", __func__, cvs_handle); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return; + } + if (v->voc_path == VOC_PATH_PASSIVE) + v->cvs_passive_handle = cvs_handle; + else + v->cvs_full_handle = cvs_handle; +} + +static u16 voice_get_cvp_handle(struct voice_data *v) +{ + u16 cvp_handle = 0; + + if (v->voc_path == VOC_PATH_PASSIVE) + cvp_handle = v->cvp_passive_handle; + else + cvp_handle = v->cvp_full_handle; + + pr_debug("%s: cvp_handle %d\n", __func__, cvp_handle); + + return cvp_handle; +} + +static void voice_set_cvp_handle(struct voice_data *v, u16 cvp_handle) +{ + pr_debug("%s: cvp_handle %d\n", __func__, cvp_handle); + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return; + } + if (v->voc_path == VOC_PATH_PASSIVE) + v->cvp_passive_handle = cvp_handle; + else + v->cvp_full_handle = cvp_handle; +} + +static int voice_apr_register(struct voice_data *v) +{ + void *apr_mvm, *apr_cvs, *apr_cvp; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = v->apr_q6_mvm; + apr_cvs = v->apr_q6_cvs; + apr_cvp = v->apr_q6_cvp; + + pr_debug("into voice_apr_register_callback\n"); + /* register callback to APR */ + if (apr_mvm == NULL) { + pr_debug("start to register MVM callback\n"); + + apr_mvm = apr_register("ADSP", "MVM", + qdsp_mvm_callback, + 0xFFFFFFFF, v); + + if (apr_mvm == NULL) { + pr_err("Unable to register MVM\n"); + goto err; + } + v->apr_q6_mvm = apr_mvm; + } + + if (apr_cvs == NULL) { + pr_debug("start to register CVS callback\n"); + + apr_cvs = apr_register("ADSP", "CVS", + qdsp_cvs_callback, + 0xFFFFFFFF, v); + + if (apr_cvs == NULL) { + pr_err("Unable to register CVS\n"); + goto err; + } + v->apr_q6_cvs = apr_cvs; + } + + if (apr_cvp == NULL) { + pr_debug("start to register CVP callback\n"); + + apr_cvp = apr_register("ADSP", "CVP", + qdsp_cvp_callback, + 0xFFFFFFFF, v); + + if (apr_cvp == NULL) { + pr_err("Unable to register CVP\n"); + goto err; + } + v->apr_q6_cvp = apr_cvp; + } + return 0; + +err: + if (v->apr_q6_cvs != NULL) { + apr_deregister(apr_cvs); + v->apr_q6_cvs = NULL; + } + if (v->apr_q6_mvm != NULL) { + apr_deregister(apr_mvm); + v->apr_q6_mvm = NULL; + } + + return -ENODEV; +} + +static int voice_create_mvm_cvs_session(struct voice_data *v) +{ + int ret = 0; + struct mvm_create_passive_ctl_session_cmd mvm_session_cmd; + struct cvs_create_passive_ctl_session_cmd cvs_session_cmd; + struct mvm_create_full_ctl_session_cmd mvm_full_ctl_cmd; + struct cvs_create_full_ctl_session_cmd cvs_full_ctl_cmd; + struct mvm_attach_stream_cmd attach_stream_cmd; + void *apr_mvm, *apr_cvs, *apr_cvp; + u16 mvm_handle, cvs_handle, cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = v->apr_q6_mvm; + apr_cvs = v->apr_q6_cvs; + apr_cvp = v->apr_q6_cvp; + + if (!apr_mvm || !apr_cvs || !apr_cvp) { + pr_err("%s: apr_mvm or apr_cvs or apr_cvp is NULL\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + cvs_handle = voice_get_cvs_handle(v); + cvp_handle = voice_get_cvp_handle(v); + + pr_debug("%s: mvm_hdl=%d, cvs_hdl=%d\n", __func__, + mvm_handle, cvs_handle); + /* send cmd to create mvm session and wait for response */ + + if (!mvm_handle) { + if (v->voc_path == VOC_PATH_PASSIVE) { + mvm_session_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_session_cmd.hdr.pkt_size = APR_PKT_SIZE( + APR_HDR_SIZE, + sizeof(mvm_session_cmd) - + APR_HDR_SIZE); + pr_debug("send mvm create session pkt size = %d\n", + mvm_session_cmd.hdr.pkt_size); + mvm_session_cmd.hdr.src_port = 0; + mvm_session_cmd.hdr.dest_port = 0; + mvm_session_cmd.hdr.token = 0; + mvm_session_cmd.hdr.opcode = + VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION; + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &mvm_session_cmd); + if (ret < 0) { + pr_err("Error sending MVM_CONTROL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } else { + pr_debug("%s: creating MVM full ctrl\n", __func__); + mvm_full_ctl_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + mvm_full_ctl_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_full_ctl_cmd) - + APR_HDR_SIZE); + mvm_full_ctl_cmd.hdr.src_port = 0; + mvm_full_ctl_cmd.hdr.dest_port = 0; + mvm_full_ctl_cmd.hdr.token = 0; + mvm_full_ctl_cmd.hdr.opcode = + VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION; + strncpy(mvm_full_ctl_cmd.mvm_session.name, + "default voip", 12); + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &mvm_full_ctl_cmd); + if (ret < 0) { + pr_err("Fail in sending MVM_CONTROL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } + /* Get the created MVM handle. */ + mvm_handle = voice_get_mvm_handle(v); + } + /* send cmd to create cvs session */ + if (!cvs_handle) { + if (v->voc_path == VOC_PATH_PASSIVE) { + pr_debug("creating CVS passive session\n"); + + cvs_session_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_session_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_session_cmd) - + APR_HDR_SIZE); + cvs_session_cmd.hdr.src_port = 0; + cvs_session_cmd.hdr.dest_port = 0; + cvs_session_cmd.hdr.token = 0; + cvs_session_cmd.hdr.opcode = + VSS_ISTREAM_CMD_CREATE_PASSIVE_CONTROL_SESSION; + strncpy(cvs_session_cmd.cvs_session.name, + "default modem voice", 19); + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, + (uint32_t *) &cvs_session_cmd); + if (ret < 0) { + pr_err("Fail in sending STREAM_CONTROL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + /* Get the created CVS handle. */ + cvs_handle = voice_get_cvs_handle(v); + + } else { + pr_debug("creating CVS full session\n"); + + cvs_full_ctl_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + + cvs_full_ctl_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_full_ctl_cmd) - + APR_HDR_SIZE); + + cvs_full_ctl_cmd.hdr.src_port = 0; + cvs_full_ctl_cmd.hdr.dest_port = 0; + cvs_full_ctl_cmd.hdr.token = 0; + cvs_full_ctl_cmd.hdr.opcode = + VSS_ISTREAM_CMD_CREATE_FULL_CONTROL_SESSION; + cvs_full_ctl_cmd.cvs_session.direction = 2; + cvs_full_ctl_cmd.cvs_session.enc_media_type = + v->mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.dec_media_type = + v->mvs_info.media_type; + cvs_full_ctl_cmd.cvs_session.network_id = + v->mvs_info.network_type; + strncpy(cvs_full_ctl_cmd.cvs_session.name, + "default q6 voice", 16); + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, + (uint32_t *) &cvs_full_ctl_cmd); + + if (ret < 0) { + pr_err("%s: Err %d sending CREATE_FULL_CTRL\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + /* Get the created CVS handle. */ + cvs_handle = voice_get_cvs_handle(v); + + /* Attach MVM to CVS. */ + pr_debug("Attach MVM to stream\n"); + + attach_stream_cmd.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + attach_stream_cmd.hdr.pkt_size = + APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(attach_stream_cmd) - + APR_HDR_SIZE); + attach_stream_cmd.hdr.src_port = 0; + attach_stream_cmd.hdr.dest_port = mvm_handle; + attach_stream_cmd.hdr.token = 0; + attach_stream_cmd.hdr.opcode = + VSS_IMVM_CMD_ATTACH_STREAM; + attach_stream_cmd.attach_stream.handle = cvs_handle; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, + (uint32_t *) &attach_stream_cmd); + if (ret < 0) { + pr_err("%s: Error %d sending ATTACH_STREAM\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } + } + return 0; + +fail: + return -EINVAL; +} + +static int voice_destroy_mvm_cvs_session(struct voice_data *v) +{ + int ret = 0; + struct mvm_detach_stream_cmd detach_stream; + struct apr_hdr mvm_destroy; + struct apr_hdr cvs_destroy; + void *apr_mvm, *apr_cvs; + u16 mvm_handle, cvs_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = v->apr_q6_mvm; + apr_cvs = v->apr_q6_cvs; + + if (!apr_mvm || !apr_cvs) { + pr_err("%s: apr_mvm or apr_cvs is NULL\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + cvs_handle = voice_get_cvs_handle(v); + + /* MVM, CVS sessions are destroyed only for Full control sessions. */ + if (v->voc_path == VOC_PATH_FULL) { + pr_debug("MVM detach stream\n"); + + /* Detach voice stream. */ + detach_stream.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + detach_stream.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(detach_stream) - APR_HDR_SIZE); + detach_stream.hdr.src_port = 0; + detach_stream.hdr.dest_port = mvm_handle; + detach_stream.hdr.token = 0; + detach_stream.hdr.opcode = VSS_IMVM_CMD_DETACH_STREAM; + detach_stream.detach_stream.handle = cvs_handle; + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &detach_stream); + if (ret < 0) { + pr_err("%s: Error %d sending DETACH_STREAM\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait event timeout\n", __func__); + goto fail; + } + /* Destroy CVS. */ + pr_debug("CVS destroy session\n"); + + cvs_destroy.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_destroy.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_destroy) - APR_HDR_SIZE); + cvs_destroy.src_port = 0; + cvs_destroy.dest_port = cvs_handle; + cvs_destroy.token = 0; + cvs_destroy.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_destroy); + if (ret < 0) { + pr_err("%s: Error %d sending CVS DESTROY\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait event timeout\n", __func__); + + goto fail; + } + cvs_handle = 0; + voice_set_cvs_handle(v, cvs_handle); + + /* Destroy MVM. */ + pr_debug("MVM destroy session\n"); + + mvm_destroy.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_destroy.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_destroy) - APR_HDR_SIZE); + mvm_destroy.src_port = 0; + mvm_destroy.dest_port = mvm_handle; + mvm_destroy.token = 0; + mvm_destroy.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_destroy); + if (ret < 0) { + pr_err("%s: Error %d sending MVM DESTROY\n", + __func__, ret); + + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait event timeout\n", __func__); + + goto fail; + } + mvm_handle = 0; + voice_set_mvm_handle(v, mvm_handle); + } + return 0; +fail: + return -EINVAL; +} + +static int voice_send_tty_mode_cmd(struct voice_data *v) +{ + int tty_mode = 0; + int ret = 0; + struct mvm_set_tty_mode_cmd mvm_tty_mode_cmd; + void *apr_mvm; + u16 mvm_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = v->apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + if (tty_mode) { + /* send tty mode cmd to mvm */ + mvm_tty_mode_cmd.hdr.hdr_field = APR_HDR_FIELD( + APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_tty_mode_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_tty_mode_cmd) - + APR_HDR_SIZE); + pr_debug("pkt size = %d\n", mvm_tty_mode_cmd.hdr.pkt_size); + mvm_tty_mode_cmd.hdr.src_port = 0; + mvm_tty_mode_cmd.hdr.dest_port = mvm_handle; + mvm_tty_mode_cmd.hdr.token = 0; + mvm_tty_mode_cmd.hdr.opcode = VSS_ISTREAM_CMD_SET_TTY_MODE; + mvm_tty_mode_cmd.tty_mode.mode = tty_mode; + pr_debug("tty mode =%d\n", mvm_tty_mode_cmd.tty_mode.mode); + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_tty_mode_cmd); + if (ret < 0) { + pr_err("Fail: sending VSS_ISTREAM_CMD_SET_TTY_MODE\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + } + return 0; +fail: + return -EINVAL; +} + +static int voice_config_cvs_vocoder(struct voice_data *v) +{ + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + /* Set media type. */ + struct cvs_set_media_type_cmd cvs_set_media_cmd; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = v->apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + + cvs_handle = voice_get_cvs_handle(v); + + cvs_set_media_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_media_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_media_cmd) - APR_HDR_SIZE); + cvs_set_media_cmd.hdr.src_port = 0; + cvs_set_media_cmd.hdr.dest_port = cvs_handle; + cvs_set_media_cmd.hdr.token = 0; + cvs_set_media_cmd.hdr.opcode = VSS_ISTREAM_CMD_SET_MEDIA_TYPE; + cvs_set_media_cmd.media_type.tx_media_id = v->mvs_info.media_type; + cvs_set_media_cmd.media_type.rx_media_id = v->mvs_info.media_type; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_media_cmd); + if (ret < 0) { + pr_err("%s: Error %d sending SET_MEDIA_TYPE\n", + __func__, ret); + + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + /* Set encoder properties. */ + switch (v->mvs_info.media_type) { + case VSS_MEDIA_ID_EVRC_MODEM: { + struct cvs_set_cdma_enc_minmax_rate_cmd cvs_set_cdma_rate; + + pr_debug("Setting EVRC min-max rate\n"); + + cvs_set_cdma_rate.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_cdma_rate.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_cdma_rate) - APR_HDR_SIZE); + cvs_set_cdma_rate.hdr.src_port = 0; + cvs_set_cdma_rate.hdr.dest_port = cvs_handle; + cvs_set_cdma_rate.hdr.token = 0; + cvs_set_cdma_rate.hdr.opcode = + VSS_ISTREAM_CMD_CDMA_SET_ENC_MINMAX_RATE; + cvs_set_cdma_rate.cdma_rate.min_rate = v->mvs_info.rate; + cvs_set_cdma_rate.cdma_rate.max_rate = v->mvs_info.rate; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_cdma_rate); + if (ret < 0) { + pr_err("%s: Error %d sending SET_EVRC_MINMAX_RATE\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + + goto fail; + } + break; + } + case VSS_MEDIA_ID_AMR_NB_MODEM: { + struct cvs_set_amr_enc_rate_cmd cvs_set_amr_rate; + struct cvs_set_enc_dtx_mode_cmd cvs_set_dtx; + + pr_debug("Setting AMR rate\n"); + + cvs_set_amr_rate.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_amr_rate.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_amr_rate) - APR_HDR_SIZE); + cvs_set_amr_rate.hdr.src_port = 0; + cvs_set_amr_rate.hdr.dest_port = cvs_handle; + cvs_set_amr_rate.hdr.token = 0; + cvs_set_amr_rate.hdr.opcode = + VSS_ISTREAM_CMD_VOC_AMR_SET_ENC_RATE; + cvs_set_amr_rate.amr_rate.mode = v->mvs_info.rate; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_amr_rate); + if (ret < 0) { + pr_err("%s: Error %d sending SET_AMR_RATE\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + /* Disable DTX */ + pr_debug("Disabling DTX\n"); + + cvs_set_dtx.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_dtx.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_dtx) - APR_HDR_SIZE); + cvs_set_dtx.hdr.src_port = 0; + cvs_set_dtx.hdr.dest_port = cvs_handle; + cvs_set_dtx.hdr.token = 0; + cvs_set_dtx.hdr.opcode = VSS_ISTREAM_CMD_SET_ENC_DTX_MODE; + cvs_set_dtx.dtx_mode.enable = 0; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_dtx); + if (ret < 0) { + pr_err("%s: Error %d sending SET_DTX\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + break; + } + case VSS_MEDIA_ID_AMR_WB_MODEM: { + struct cvs_set_amrwb_enc_rate_cmd cvs_set_amrwb_rate; + struct cvs_set_enc_dtx_mode_cmd cvs_set_dtx; + + pr_debug("Setting AMR WB rate\n"); + + cvs_set_amrwb_rate.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_amrwb_rate.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_amrwb_rate) - + APR_HDR_SIZE); + cvs_set_amrwb_rate.hdr.src_port = 0; + cvs_set_amrwb_rate.hdr.dest_port = cvs_handle; + cvs_set_amrwb_rate.hdr.token = 0; + cvs_set_amrwb_rate.hdr.opcode = + VSS_ISTREAM_CMD_VOC_AMRWB_SET_ENC_RATE; + cvs_set_amrwb_rate.amrwb_rate.mode = v->mvs_info.rate; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_amrwb_rate); + if (ret < 0) { + pr_err("%s: Error %d sending SET_AMRWB_RATE\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + /* Disable DTX */ + pr_debug("Disabling DTX\n"); + + cvs_set_dtx.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_dtx.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_dtx) - APR_HDR_SIZE); + cvs_set_dtx.hdr.src_port = 0; + cvs_set_dtx.hdr.dest_port = cvs_handle; + cvs_set_dtx.hdr.token = 0; + cvs_set_dtx.hdr.opcode = VSS_ISTREAM_CMD_SET_ENC_DTX_MODE; + cvs_set_dtx.dtx_mode.enable = 0; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_dtx); + if (ret < 0) { + pr_err("%s: Error %d sending SET_DTX\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + break; + } + case VSS_MEDIA_ID_G729: + case VSS_MEDIA_ID_G711_ALAW: + case VSS_MEDIA_ID_G711_MULAW: { + struct cvs_set_enc_dtx_mode_cmd cvs_set_dtx; + /* Disable DTX */ + pr_debug("Disabling DTX\n"); + + cvs_set_dtx.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_set_dtx.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_set_dtx) - APR_HDR_SIZE); + cvs_set_dtx.hdr.src_port = 0; + cvs_set_dtx.hdr.dest_port = cvs_handle; + cvs_set_dtx.hdr.token = 0; + cvs_set_dtx.hdr.opcode = VSS_ISTREAM_CMD_SET_ENC_DTX_MODE; + cvs_set_dtx.dtx_mode.enable = 0; + + v->cvs_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_set_dtx); + if (ret < 0) { + pr_err("%s: Error %d sending SET_DTX\n", + __func__, ret); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + break; + } + default: + /* Do nothing. */ + break; + } + return 0; + +fail: + return -EINVAL; +} + +static int voice_send_start_voice_cmd(struct voice_data *v) +{ + struct apr_hdr mvm_start_voice_cmd; + int ret = 0; + void *apr_mvm; + u16 mvm_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = v->apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + mvm_start_voice_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_start_voice_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_start_voice_cmd) - APR_HDR_SIZE); + pr_debug("send mvm_start_voice_cmd pkt size = %d\n", + mvm_start_voice_cmd.pkt_size); + mvm_start_voice_cmd.src_port = 0; + mvm_start_voice_cmd.dest_port = mvm_handle; + mvm_start_voice_cmd.token = 0; + mvm_start_voice_cmd.opcode = VSS_IMVM_CMD_START_VOICE; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_start_voice_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_START_VOICE\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + return 0; +fail: + return -EINVAL; +} + +static int voice_send_disable_vocproc_cmd(struct voice_data *v) +{ + struct apr_hdr cvp_disable_cmd; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = v->apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr regist failed\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* disable vocproc and wait for respose */ + cvp_disable_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_disable_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_disable_cmd) - APR_HDR_SIZE); + pr_debug("cvp_disable_cmd pkt size = %d, cvp_handle=%d\n", + cvp_disable_cmd.pkt_size, cvp_handle); + cvp_disable_cmd.src_port = 0; + cvp_disable_cmd.dest_port = cvp_handle; + cvp_disable_cmd.token = 0; + cvp_disable_cmd.opcode = VSS_IVOCPROC_CMD_DISABLE; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_disable_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IVOCPROC_CMD_DISABLE\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_set_device_cmd(struct voice_data *v) +{ + struct cvp_set_device_cmd cvp_setdev_cmd; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = v->apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* set device and wait for response */ + cvp_setdev_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_setdev_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_setdev_cmd) - APR_HDR_SIZE); + pr_debug(" send create cvp setdev, pkt size = %d\n", + cvp_setdev_cmd.hdr.pkt_size); + cvp_setdev_cmd.hdr.src_port = 0; + cvp_setdev_cmd.hdr.dest_port = cvp_handle; + cvp_setdev_cmd.hdr.token = 0; + cvp_setdev_cmd.hdr.opcode = VSS_IVOCPROC_CMD_SET_DEVICE; + + /* Use default topology if invalid value in ACDB */ + cvp_setdev_cmd.cvp_set_device.tx_topology_id = + get_voice_tx_topology(); + if (cvp_setdev_cmd.cvp_set_device.tx_topology_id == 0) + cvp_setdev_cmd.cvp_set_device.tx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + + cvp_setdev_cmd.cvp_set_device.rx_topology_id = + get_voice_rx_topology(); + if (cvp_setdev_cmd.cvp_set_device.rx_topology_id == 0) + cvp_setdev_cmd.cvp_set_device.rx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + cvp_setdev_cmd.cvp_set_device.tx_port_id = v->dev_tx.port_id; + cvp_setdev_cmd.cvp_set_device.rx_port_id = v->dev_rx.port_id; + pr_debug("topology=%d , tx_port_id=%d, rx_port_id=%d\n", + cvp_setdev_cmd.cvp_set_device.tx_topology_id, + cvp_setdev_cmd.cvp_set_device.tx_port_id, + cvp_setdev_cmd.cvp_set_device.rx_port_id); + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_setdev_cmd); + if (ret < 0) { + pr_err("Fail in sending VOCPROC_FULL_CONTROL_SESSION\n"); + goto fail; + } + pr_debug("wait for cvp create session event\n"); + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_stop_voice_cmd(struct voice_data *v) +{ + struct apr_hdr mvm_stop_voice_cmd; + int ret = 0; + void *apr_mvm; + u16 mvm_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = v->apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + mvm_stop_voice_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_stop_voice_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_stop_voice_cmd) - APR_HDR_SIZE); + pr_debug("send mvm_stop_voice_cmd pkt size = %d\n", + mvm_stop_voice_cmd.pkt_size); + mvm_stop_voice_cmd.src_port = 0; + mvm_stop_voice_cmd.dest_port = mvm_handle; + mvm_stop_voice_cmd.token = 0; + mvm_stop_voice_cmd.opcode = VSS_IMVM_CMD_STOP_VOICE; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_stop_voice_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IMVM_CMD_STOP_VOICE\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_setup_vocproc(struct voice_data *v) +{ + struct cvp_create_full_ctl_session_cmd cvp_session_cmd; + int ret = 0; + void *apr_cvp; + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = v->apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + + /* create cvp session and wait for response */ + cvp_session_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_session_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_session_cmd) - APR_HDR_SIZE); + pr_debug(" send create cvp session, pkt size = %d\n", + cvp_session_cmd.hdr.pkt_size); + cvp_session_cmd.hdr.src_port = 0; + cvp_session_cmd.hdr.dest_port = 0; + cvp_session_cmd.hdr.token = 0; + cvp_session_cmd.hdr.opcode = + VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION; + + /* Use default topology if invalid value in ACDB */ + cvp_session_cmd.cvp_session.tx_topology_id = + get_voice_tx_topology(); + if (cvp_session_cmd.cvp_session.tx_topology_id == 0) + cvp_session_cmd.cvp_session.tx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + + cvp_session_cmd.cvp_session.rx_topology_id = + get_voice_rx_topology(); + if (cvp_session_cmd.cvp_session.rx_topology_id == 0) + cvp_session_cmd.cvp_session.rx_topology_id = + VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + + cvp_session_cmd.cvp_session.direction = 2; /*tx and rx*/ + cvp_session_cmd.cvp_session.network_id = VSS_NETWORK_ID_DEFAULT; + cvp_session_cmd.cvp_session.tx_port_id = v->dev_tx.port_id; + cvp_session_cmd.cvp_session.rx_port_id = v->dev_rx.port_id; + + pr_debug("topology=%d net_id=%d, dir=%d tx_port_id=%d, rx_port_id=%d\n", + cvp_session_cmd.cvp_session.tx_topology_id, + cvp_session_cmd.cvp_session.network_id, + cvp_session_cmd.cvp_session.direction, + cvp_session_cmd.cvp_session.tx_port_id, + cvp_session_cmd.cvp_session.rx_port_id); + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_session_cmd); + if (ret < 0) { + pr_err("Fail in sending VOCPROC_FULL_CONTROL_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* enable vocproc */ + ret = voice_send_enable_vocproc_cmd(v); + if (ret < 0) + goto fail; + + /* attach vocproc */ + ret = voice_send_attach_vocproc_cmd(v); + if (ret < 0) + goto fail; + + /* send tty mode if tty device is used */ + voice_send_tty_mode_cmd(v); + + if (v->voc_path == VOC_PATH_FULL) + voice_send_netid_timing_cmd(v); + + return 0; + +fail: + return -EINVAL; +} + +static int voice_send_enable_vocproc_cmd(struct voice_data *v) +{ + int ret = 0; + struct apr_hdr cvp_enable_cmd; + void *apr_cvp; + u16 cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = v->apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* enable vocproc and wait for respose */ + cvp_enable_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_enable_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_enable_cmd) - APR_HDR_SIZE); + pr_debug("cvp_enable_cmd pkt size = %d, cvp_handle=%d\n", + cvp_enable_cmd.pkt_size, cvp_handle); + cvp_enable_cmd.src_port = 0; + cvp_enable_cmd.dest_port = cvp_handle; + cvp_enable_cmd.token = 0; + cvp_enable_cmd.opcode = VSS_IVOCPROC_CMD_ENABLE; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_enable_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_IVOCPROC_CMD_ENABLE\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_netid_timing_cmd(struct voice_data *v) +{ + int ret = 0; + void *apr_mvm; + u16 mvm_handle; + struct mvm_set_network_cmd mvm_set_network; + struct mvm_set_voice_timing_cmd mvm_set_voice_timing; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = v->apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + + ret = voice_config_cvs_vocoder(v); + if (ret < 0) { + pr_err("%s: Error %d configuring CVS voc", + __func__, ret); + goto fail; + } + /* Set network ID. */ + pr_debug("Setting network ID\n"); + + mvm_set_network.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_set_network.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_set_network) - APR_HDR_SIZE); + mvm_set_network.hdr.src_port = 0; + mvm_set_network.hdr.dest_port = mvm_handle; + mvm_set_network.hdr.token = 0; + mvm_set_network.hdr.opcode = VSS_ICOMMON_CMD_SET_NETWORK; + mvm_set_network.network.network_id = v->mvs_info.network_type; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_set_network); + if (ret < 0) { + pr_err("%s: Error %d sending SET_NETWORK\n", __func__, ret); + goto fail; + } + + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* Set voice timing. */ + pr_debug("Setting voice timing\n"); + + mvm_set_voice_timing.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_set_voice_timing.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_set_voice_timing) - + APR_HDR_SIZE); + mvm_set_voice_timing.hdr.src_port = 0; + mvm_set_voice_timing.hdr.dest_port = mvm_handle; + mvm_set_voice_timing.hdr.token = 0; + mvm_set_voice_timing.hdr.opcode = VSS_ICOMMON_CMD_SET_VOICE_TIMING; + mvm_set_voice_timing.timing.mode = 0; + mvm_set_voice_timing.timing.enc_offset = 8000; + mvm_set_voice_timing.timing.dec_req_offset = 3300; + mvm_set_voice_timing.timing.dec_offset = 8300; + + v->mvm_state = CMD_STATUS_FAIL; + + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_set_voice_timing); + if (ret < 0) { + pr_err("%s: Error %d sending SET_TIMING\n", __func__, ret); + goto fail; + } + + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_attach_vocproc_cmd(struct voice_data *v) +{ + int ret = 0; + struct mvm_attach_vocproc_cmd mvm_a_vocproc_cmd; + void *apr_mvm; + u16 mvm_handle, cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = v->apr_q6_mvm; + + if (!apr_mvm) { + pr_err("%s: apr_mvm is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + cvp_handle = voice_get_cvp_handle(v); + + /* attach vocproc and wait for response */ + mvm_a_vocproc_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_a_vocproc_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_a_vocproc_cmd) - APR_HDR_SIZE); + pr_debug("send mvm_a_vocproc_cmd pkt size = %d\n", + mvm_a_vocproc_cmd.hdr.pkt_size); + mvm_a_vocproc_cmd.hdr.src_port = 0; + mvm_a_vocproc_cmd.hdr.dest_port = mvm_handle; + mvm_a_vocproc_cmd.hdr.token = 0; + mvm_a_vocproc_cmd.hdr.opcode = VSS_ISTREAM_CMD_ATTACH_VOCPROC; + mvm_a_vocproc_cmd.mvm_attach_cvp_handle.handle = cvp_handle; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_a_vocproc_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_ISTREAM_CMD_ATTACH_VOCPROC\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + return 0; +fail: + return -EINVAL; +} + +static int voice_destroy_vocproc(struct voice_data *v) +{ + struct mvm_detach_vocproc_cmd mvm_d_vocproc_cmd; + struct apr_hdr cvp_destroy_session_cmd; + int ret = 0; + void *apr_mvm, *apr_cvp; + u16 mvm_handle, cvp_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_mvm = v->apr_q6_mvm; + apr_cvp = v->apr_q6_cvp; + + if (!apr_mvm || !apr_cvp) { + pr_err("%s: apr_mvm or apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + mvm_handle = voice_get_mvm_handle(v); + cvp_handle = voice_get_cvp_handle(v); + + /* send stop voice cmd */ + voice_send_stop_voice_cmd(v); + + /* detach VOCPROC and wait for response from mvm */ + mvm_d_vocproc_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + mvm_d_vocproc_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(mvm_d_vocproc_cmd) - APR_HDR_SIZE); + pr_debug("mvm_d_vocproc_cmd pkt size = %d\n", + mvm_d_vocproc_cmd.hdr.pkt_size); + mvm_d_vocproc_cmd.hdr.src_port = 0; + mvm_d_vocproc_cmd.hdr.dest_port = mvm_handle; + mvm_d_vocproc_cmd.hdr.token = 0; + mvm_d_vocproc_cmd.hdr.opcode = VSS_ISTREAM_CMD_DETACH_VOCPROC; + mvm_d_vocproc_cmd.mvm_detach_cvp_handle.handle = cvp_handle; + + v->mvm_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_mvm, (uint32_t *) &mvm_d_vocproc_cmd); + if (ret < 0) { + pr_err("Fail in sending VSS_ISTREAM_CMD_DETACH_VOCPROC\n"); + goto fail; + } + ret = wait_event_timeout(v->mvm_wait, + (v->mvm_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + /* destrop cvp session */ + cvp_destroy_session_cmd.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_destroy_session_cmd.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_destroy_session_cmd) - APR_HDR_SIZE); + pr_debug("cvp_destroy_session_cmd pkt size = %d\n", + cvp_destroy_session_cmd.pkt_size); + cvp_destroy_session_cmd.src_port = 0; + cvp_destroy_session_cmd.dest_port = cvp_handle; + cvp_destroy_session_cmd.token = 0; + cvp_destroy_session_cmd.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_destroy_session_cmd); + if (ret < 0) { + pr_err("Fail in sending APRV2_IBASIC_CMD_DESTROY_SESSION\n"); + goto fail; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + goto fail; + } + + cvp_handle = 0; + voice_set_cvp_handle(v, cvp_handle); + + return 0; + +fail: + return -EINVAL; +} + +static int voice_send_mute_cmd(struct voice_data *v) +{ + struct cvs_set_mute_cmd cvs_mute_cmd; + int ret = 0; + void *apr_cvs; + u16 cvs_handle; + + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvs = v->apr_q6_cvs; + + if (!apr_cvs) { + pr_err("%s: apr_cvs is NULL.\n", __func__); + return -EINVAL; + } + cvs_handle = voice_get_cvs_handle(v); + + /* send mute/unmute to cvs */ + cvs_mute_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvs_mute_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvs_mute_cmd) - APR_HDR_SIZE); + cvs_mute_cmd.hdr.src_port = 0; + cvs_mute_cmd.hdr.dest_port = cvs_handle; + cvs_mute_cmd.hdr.token = 0; + cvs_mute_cmd.hdr.opcode = VSS_ISTREAM_CMD_SET_MUTE; + cvs_mute_cmd.cvs_set_mute.direction = 0; /*tx*/ + cvs_mute_cmd.cvs_set_mute.mute_flag = v->dev_tx.mute; + + pr_info(" mute value =%d\n", cvs_mute_cmd.cvs_set_mute.mute_flag); + v->cvs_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvs, (uint32_t *) &cvs_mute_cmd); + if (ret < 0) { + pr_err("Fail: send STREAM SET MUTE\n"); + goto fail; + } + ret = wait_event_timeout(v->cvs_wait, + (v->cvs_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) + pr_err("%s: wait_event timeout\n", __func__); + + return 0; +fail: + return -EINVAL; +} + +static int voice_send_vol_index_cmd(struct voice_data *v) +{ + struct cvp_set_rx_volume_index_cmd cvp_vol_cmd; + int ret = 0; + void *apr_cvp; + u16 cvp_handle; + if (v == NULL) { + pr_err("%s: v is NULL\n", __func__); + return -EINVAL; + } + apr_cvp = v->apr_q6_cvp; + + if (!apr_cvp) { + pr_err("%s: apr_cvp is NULL.\n", __func__); + return -EINVAL; + } + cvp_handle = voice_get_cvp_handle(v); + + /* send volume index to cvp */ + cvp_vol_cmd.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + cvp_vol_cmd.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(cvp_vol_cmd) - APR_HDR_SIZE); + cvp_vol_cmd.hdr.src_port = 0; + cvp_vol_cmd.hdr.dest_port = cvp_handle; + cvp_vol_cmd.hdr.token = 0; + cvp_vol_cmd.hdr.opcode = VSS_IVOCPROC_CMD_SET_RX_VOLUME_INDEX; + cvp_vol_cmd.cvp_set_vol_idx.vol_index = v->dev_rx.volume; + v->cvp_state = CMD_STATUS_FAIL; + ret = apr_send_pkt(apr_cvp, (uint32_t *) &cvp_vol_cmd); + if (ret < 0) { + pr_err("Fail in sending RX VOL INDEX\n"); + return -EINVAL; + } + ret = wait_event_timeout(v->cvp_wait, + (v->cvp_state == CMD_STATUS_SUCCESS), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + pr_err("%s: wait_event timeout\n", __func__); + return -EINVAL; + } + return 0; +} + +int voc_disable_cvp(void) +{ + struct voice_data *v = &voice; + int ret = 0; + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN) { + afe_sidetone(v->dev_tx.port_id, v->dev_rx.port_id, 0, 0); + /* send cmd to dsp to disable vocproc */ + ret = voice_send_disable_vocproc_cmd(v); + if (ret < 0) { + pr_err("%s: disable vocproc failed\n", __func__); + goto fail; + } + v->voc_state = VOC_CHANGE; + } + +fail: mutex_unlock(&v->lock); + + return ret; +} + +int voc_enable_cvp(void) +{ + struct voice_data *v = &voice; + int ret = 0; + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_CHANGE) { + ret = voice_send_set_device_cmd(v); + if (ret < 0) { + pr_err("%s: set device failed\n", __func__); + goto fail; + } + ret = voice_send_enable_vocproc_cmd(v); + if (ret < 0) { + pr_err("enable vocproc failed\n"); + goto fail; + } + ret = afe_sidetone(v->dev_tx.port_id, v->dev_rx.port_id, + 1, v->sidetone_gain); + + if (ret < 0) + pr_err("AFE command sidetone failed\n"); + + v->voc_state = VOC_RUN; + } + +fail: + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_tx_mute(uint32_t dir, uint32_t mute) +{ + struct voice_data *v = &voice; + int ret = 0; + + mutex_lock(&v->lock); + + v->dev_tx.mute = mute; + + if (v->voc_state == VOC_RUN) + ret = voice_send_mute_cmd(v); + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_set_rx_vol_index(uint32_t dir, uint32_t vol_idx) +{ + struct voice_data *v = &voice; + int ret = 0; + + mutex_lock(&v->lock); + + v->dev_rx.volume = vol_idx; + + if (v->voc_state == VOC_RUN) + ret = voice_send_vol_index_cmd(v); + + mutex_unlock(&v->lock); + + return ret; +} + +void voc_set_rxtx_port(uint32_t port_id, uint32_t dev_type) +{ + struct voice_data *v = &voice; + + pr_debug(" port_id=%d, type=%d\n", port_id, dev_type); + + mutex_lock(&v->lock); + + if (dev_type == DEV_RX) + v->dev_rx.port_id = port_id; + else + v->dev_tx.port_id = port_id; + + mutex_unlock(&v->lock); + + return; +} + +void voc_set_route_flag(uint8_t path_dir, uint8_t set) +{ + struct voice_data *v = &voice; + + pr_debug("path_dir=%d, set=%d\n", path_dir, set); + + mutex_lock(&v->lock); + + if (path_dir == RX_PATH) + v->voc_route_state.rx_route_flag = set; + else + v->voc_route_state.tx_route_flag = set; + + mutex_unlock(&v->lock); + + return; +} + +uint8_t voc_get_route_flag(uint8_t path_dir) +{ + struct voice_data *v = &voice; + int ret = 0; + + mutex_lock(&v->lock); + + if (path_dir == RX_PATH) + ret = v->voc_route_state.rx_route_flag; + else + ret = v->voc_route_state.tx_route_flag; + + mutex_unlock(&v->lock); + + return ret; +} + +int voc_end_voice_call(void) +{ + struct voice_data *v = &voice; + int ret = 0; + + mutex_lock(&v->lock); + + if (v->voc_state == VOC_RUN) { + ret = voice_destroy_vocproc(v); + if (ret < 0) + pr_err("%s: destroy voice failed\n", __func__); + voice_destroy_mvm_cvs_session(v); + + v->voc_state = VOC_RELEASE; + } + mutex_unlock(&v->lock); + return ret; +} + +int voc_start_voice_call(void) +{ + struct voice_data *v = &voice; + int ret = 0; + + mutex_lock(&v->lock); + + if ((v->voc_state == VOC_INIT) || + (v->voc_state == VOC_RELEASE)) { + ret = voice_apr_register(v); + if (ret < 0) { + pr_err("%s: apr register failed\n", __func__); + goto fail; + } + ret = voice_create_mvm_cvs_session(v); + if (ret < 0) { + pr_err("create mvm and cvs failed\n"); + goto fail; + } + ret = voice_setup_vocproc(v); + if (ret < 0) { + pr_err("setup voice failed\n"); + goto fail; + } + ret = voice_send_start_voice_cmd(v); + if (ret < 0) { + pr_err("start voice failed\n"); + goto fail; + } + ret = afe_sidetone(v->dev_tx.port_id, + v->dev_rx.port_id, 1, v->sidetone_gain); + if (ret < 0) + pr_err("AFE command sidetone failed\n"); + + v->voc_state = VOC_RUN; + } + +fail: mutex_unlock(&v->lock); + return ret; +} + +int voc_set_voc_path_full(uint32_t set) +{ + int rc = 0; + + pr_debug("set voc path: %d\n", set); + + mutex_lock(&voice.lock); + + if (voice.voc_state == VOC_INIT || voice.voc_state == VOC_RELEASE) { + if (set) + voice.voc_path = VOC_PATH_FULL; + else + voice.voc_path = VOC_PATH_PASSIVE; + } else { + pr_err("%s: Invalid voc path set to %d, in state %d\n", + __func__, set, voice.voc_state); + rc = -EPERM; + } + + mutex_unlock(&voice.lock); + + return rc; +} +EXPORT_SYMBOL(voc_set_voc_path_full); + +void voc_register_mvs_cb(ul_cb_fn ul_cb, + dl_cb_fn dl_cb, + void *private_data) +{ + voice.mvs_info.ul_cb = ul_cb; + voice.mvs_info.dl_cb = dl_cb; + voice.mvs_info.private_data = private_data; +} + +void voc_config_vocoder(uint32_t media_type, + uint32_t rate, + uint32_t network_type) +{ + voice.mvs_info.media_type = media_type; + voice.mvs_info.rate = rate; + voice.mvs_info.network_type = network_type; +} + +static int32_t qdsp_mvm_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr; + struct voice_data *v; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + return -EINVAL; + } + v = priv; + + pr_debug("%s: Payload Length = %d, opcode=%x\n", __func__, + data->payload_size, data->opcode); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (data->payload_size) { + ptr = data->payload; + + pr_info("%x %x\n", ptr[0], ptr[1]); + /* ping mvm service ACK */ + switch (ptr[0]) { + case VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION: + case VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION: + /* Passive session is used for CS call + * Full session is used for VoIP call. */ + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + if (!ptr[1]) { + pr_debug("%s: MVM handle is %d\n", + __func__, data->src_port); + voice_set_mvm_handle(v, data->src_port); + } else + pr_err("got NACK for sending \ + MVM create session \n"); + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + break; + case VSS_IMVM_CMD_START_VOICE: + case VSS_ISTREAM_CMD_ATTACH_VOCPROC: + case VSS_IMVM_CMD_STOP_VOICE: + case VSS_ISTREAM_CMD_DETACH_VOCPROC: + case VSS_ISTREAM_CMD_SET_TTY_MODE: + case APRV2_IBASIC_CMD_DESTROY_SESSION: + case VSS_IMVM_CMD_ATTACH_STREAM: + case VSS_IMVM_CMD_DETACH_STREAM: + case VSS_ICOMMON_CMD_SET_NETWORK: + case VSS_ICOMMON_CMD_SET_VOICE_TIMING: + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + v->mvm_state = CMD_STATUS_SUCCESS; + wake_up(&v->mvm_wait); + break; + default: + pr_debug("%s: not match cmd = 0x%x\n", + __func__, ptr[0]); + break; + } + } + } + + return 0; +} + +static int32_t qdsp_cvs_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr; + struct voice_data *v; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + return -EINVAL; + } + v = priv; + + pr_debug("%s: Payload Length = %d, opcode=%x\n", __func__, + data->payload_size, data->opcode); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (data->payload_size) { + ptr = data->payload; + + pr_info("%x %x\n", ptr[0], ptr[1]); + /*response from CVS */ + switch (ptr[0]) { + case VSS_ISTREAM_CMD_CREATE_PASSIVE_CONTROL_SESSION: + case VSS_ISTREAM_CMD_CREATE_FULL_CONTROL_SESSION: + if (!ptr[1]) { + pr_debug("%s: CVS handle is %d\n", + __func__, data->src_port); + voice_set_cvs_handle(v, data->src_port); + } else + pr_err("got NACK for sending \ + CVS create session \n"); + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + break; + case VSS_ISTREAM_CMD_SET_MUTE: + case VSS_ISTREAM_CMD_SET_MEDIA_TYPE: + case VSS_ISTREAM_CMD_VOC_AMR_SET_ENC_RATE: + case VSS_ISTREAM_CMD_VOC_AMRWB_SET_ENC_RATE: + case VSS_ISTREAM_CMD_SET_ENC_DTX_MODE: + case VSS_ISTREAM_CMD_CDMA_SET_ENC_MINMAX_RATE: + case APRV2_IBASIC_CMD_DESTROY_SESSION: + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + v->cvs_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvs_wait); + break; + default: + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + break; + } + } + } else if (data->opcode == VSS_ISTREAM_EVT_SEND_ENC_BUFFER) { + uint32_t *voc_pkt = data->payload; + uint32_t pkt_len = data->payload_size; + + if (voc_pkt != NULL && v->mvs_info.ul_cb != NULL) { + pr_debug("%s: Media type is 0x%x\n", + __func__, voc_pkt[0]); + + /* Remove media ID from payload. */ + voc_pkt++; + pkt_len = pkt_len - 4; + + v->mvs_info.ul_cb((uint8_t *)voc_pkt, + pkt_len, + v->mvs_info.private_data); + } else + pr_err("%s: voc_pkt is 0x%x ul_cb is 0x%x\n", + __func__, (unsigned int)voc_pkt, + (unsigned int) v->mvs_info.ul_cb); + } else if (data->opcode == VSS_ISTREAM_EVT_REQUEST_DEC_BUFFER) { + struct cvs_send_dec_buf_cmd send_dec_buf; + int ret = 0; + uint32_t pkt_len = 0; + + if (v->mvs_info.dl_cb != NULL) { + send_dec_buf.dec_buf.media_id = v->mvs_info.media_type; + + v->mvs_info.dl_cb( + (uint8_t *)&send_dec_buf.dec_buf.packet_data, + &pkt_len, + v->mvs_info.private_data); + + send_dec_buf.hdr.hdr_field = + APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + send_dec_buf.hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + sizeof(send_dec_buf.dec_buf.media_id) + pkt_len); + send_dec_buf.hdr.src_port = 0; + send_dec_buf.hdr.dest_port = voice_get_cvs_handle(v); + send_dec_buf.hdr.token = 0; + send_dec_buf.hdr.opcode = + VSS_ISTREAM_EVT_SEND_DEC_BUFFER; + + ret = apr_send_pkt(v->apr_q6_cvs, + (uint32_t *) &send_dec_buf); + if (ret < 0) { + pr_err("%s: Error %d sending DEC_BUF\n", + __func__, ret); + goto fail; + } + } else + pr_debug("%s: dl_cb is NULL\n", __func__); + } else if (data->opcode == VSS_ISTREAM_EVT_SEND_DEC_BUFFER) + pr_debug("Send dec buf resp\n"); + + else + pr_debug("Unknown opcode 0x%x\n", data->opcode); + +fail: + return 0; +} + +static int32_t qdsp_cvp_callback(struct apr_client_data *data, void *priv) +{ + uint32_t *ptr; + struct voice_data *v; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + return -EINVAL; + } + v = priv; + + pr_debug("%s: Payload Length = %d, opcode=%x\n", __func__, + data->payload_size, data->opcode); + + if (data->opcode == APR_BASIC_RSP_RESULT) { + if (data->payload_size) { + ptr = data->payload; + + pr_info("%x %x\n", ptr[0], ptr[1]); + + switch (ptr[0]) { + case VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION: + /*response from CVP */ + pr_debug("%s: cmd = 0x%x\n", __func__, ptr[0]); + if (!ptr[1]) { + voice_set_cvp_handle(v, data->src_port); + pr_debug("cvphdl=%d\n", data->src_port); + } else + pr_err("got NACK from CVP create \ + session response\n"); + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + break; + case VSS_IVOCPROC_CMD_SET_DEVICE: + case VSS_IVOCPROC_CMD_SET_RX_VOLUME_INDEX: + case VSS_IVOCPROC_CMD_ENABLE: + case VSS_IVOCPROC_CMD_DISABLE: + case APRV2_IBASIC_CMD_DESTROY_SESSION: + v->cvp_state = CMD_STATUS_SUCCESS; + wake_up(&v->cvp_wait); + break; + default: + pr_debug("%s: not match cmd = 0x%x\n", + __func__, ptr[0]); + break; + } + } + } + return 0; +} + + +static int __init voice_init(void) +{ + int rc = 0; + struct voice_data *v = &voice; + + /* set default value */ + v->default_mute_val = 1; /* default is mute */ + v->default_vol_val = 0; + v->default_sample_val = 8000; + v->sidetone_gain = 0x512; + + /* initialize dev_rx and dev_tx */ + memset(&v->dev_tx, 0, sizeof(struct device_data)); + memset(&v->dev_rx, 0, sizeof(struct device_data)); + v->dev_rx.volume = v->default_vol_val; + v->dev_tx.mute = v->default_mute_val; + + v->dev_tx.port_id = 1; + v->dev_rx.port_id = 0; + + v->voc_state = VOC_INIT; + v->voc_path = VOC_PATH_PASSIVE; + init_waitqueue_head(&v->mvm_wait); + init_waitqueue_head(&v->cvs_wait); + init_waitqueue_head(&v->cvp_wait); + + mutex_init(&v->lock); + + v->mvm_full_handle = 0; + v->mvm_passive_handle = 0; + v->cvs_full_handle = 0; + v->cvs_passive_handle = 0; + v->cvp_full_handle = 0; + v->cvp_passive_handle = 0; + + v->apr_q6_mvm = NULL; + v->apr_q6_cvs = NULL; + v->apr_q6_cvp = NULL; + + /* Initialize MVS info. */ + memset(&v->mvs_info, 0, sizeof(v->mvs_info)); + v->mvs_info.network_type = VSS_NETWORK_ID_DEFAULT; + + return rc; +} + +device_initcall(voice_init); diff --git a/sound/soc/msm/qdsp6/q6voice.h b/sound/soc/msm/qdsp6/q6voice.h new file mode 100644 index 00000000000..f9e1bc49b69 --- /dev/null +++ b/sound/soc/msm/qdsp6/q6voice.h @@ -0,0 +1,686 @@ +/* Copyright (c) 2011, Code Aurora Forum. 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 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __QDSP6VOICE_H__ +#define __QDSP6VOICE_H__ + +#include + +#define MAX_VOC_PKT_SIZE 322 + +struct voice_header { + uint32_t id; + uint32_t data_len; +}; + +struct voice_init { + struct voice_header hdr; + void *cb_handle; +}; + +/* Device information payload structure */ + +struct device_data { + uint32_t volume; /* in index */ + uint32_t mute; + uint32_t sample; + uint32_t enabled; + uint32_t dev_id; + uint32_t port_id; +}; + +struct voice_dev_route_state { + u16 rx_route_flag; + u16 tx_route_flag; +}; + +enum { + VOC_INIT = 0, + VOC_RUN, + VOC_CHANGE, + VOC_RELEASE, +}; + +/* TO MVM commands */ +#define VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION 0x000110FF +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IMVM_CMD_CREATE_FULL_CONTROL_SESSION 0x000110FE +/* Create a new full control MVM session. */ + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IMVM_CMD_ATTACH_STREAM 0x0001123C +/* Attach a stream to the MVM. */ + +#define VSS_IMVM_CMD_DETACH_STREAM 0x0001123D +/* Detach a stream from the MVM. */ + +#define VSS_IMVM_CMD_START_VOICE 0x00011190 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IMVM_CMD_STOP_VOICE 0x00011192 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ISTREAM_CMD_ATTACH_VOCPROC 0x000110F8 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ISTREAM_CMD_DETACH_VOCPROC 0x000110F9 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + + +#define VSS_ISTREAM_CMD_SET_TTY_MODE 0x00011196 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ICOMMON_CMD_SET_NETWORK 0x0001119C +/* Set the network type. */ + +#define VSS_ICOMMON_CMD_SET_VOICE_TIMING 0x000111E0 +/* Set the voice timing parameters. */ + +struct vss_istream_cmd_set_tty_mode_t { + uint32_t mode; + /**< + * TTY mode. + * + * 0 : TTY disabled + * 1 : HCO + * 2 : VCO + * 3 : FULL + */ +} __packed; + +struct vss_istream_cmd_attach_vocproc_t { + uint16_t handle; + /**< Handle of vocproc being attached. */ +} __packed; + +struct vss_istream_cmd_detach_vocproc_t { + uint16_t handle; + /**< Handle of vocproc being detached. */ +} __packed; + +struct vss_imvm_cmd_attach_stream_t { + uint16_t handle; + /* The stream handle to attach. */ +} __packed; + +struct vss_imvm_cmd_detach_stream_t { + uint16_t handle; + /* The stream handle to detach. */ +} __packed; + +struct vss_icommon_cmd_set_network_t { + uint32_t network_id; + /* Network ID. (Refer to VSS_NETWORK_ID_XXX). */ +} __packed; + +struct vss_icommon_cmd_set_voice_timing_t { + uint16_t mode; + /* + * The vocoder frame synchronization mode. + * + * 0 : No frame sync. + * 1 : Hard VFR (20ms Vocoder Frame Reference interrupt). + */ + uint16_t enc_offset; + /* + * The offset in microseconds from the VFR to deliver a Tx vocoder + * packet. The offset should be less than 20000us. + */ + uint16_t dec_req_offset; + /* + * The offset in microseconds from the VFR to request for an Rx vocoder + * packet. The offset should be less than 20000us. + */ + uint16_t dec_offset; + /* + * The offset in microseconds from the VFR to indicate the deadline to + * receive an Rx vocoder packet. The offset should be less than 20000us. + * Rx vocoder packets received after this deadline are not guaranteed to + * be processed. + */ +} __packed; + +struct vss_imvm_cmd_create_full_control_session_t { + char name[20]; + /* + * A variable-sized stream name. + * + * The stream name size is the payload size minus the size of the other + * fields. + */ +} __packed; + + +struct mvm_attach_vocproc_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_attach_vocproc_t mvm_attach_cvp_handle; +} __packed; + +struct mvm_detach_vocproc_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_detach_vocproc_t mvm_detach_cvp_handle; +} __packed; + +struct mvm_create_passive_ctl_session_cmd { + struct apr_hdr hdr; +} __packed; + +struct mvm_create_full_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_create_full_control_session_t mvm_session; +} __packed; + +struct mvm_set_tty_mode_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_tty_mode_t tty_mode; +} __packed; + +struct mvm_attach_stream_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_attach_stream_t attach_stream; +} __packed; + +struct mvm_detach_stream_cmd { + struct apr_hdr hdr; + struct vss_imvm_cmd_detach_stream_t detach_stream; +} __packed; + +struct mvm_set_network_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_set_network_t network; +} __packed; + +struct mvm_set_voice_timing_cmd { + struct apr_hdr hdr; + struct vss_icommon_cmd_set_voice_timing_t timing; +} __packed; + +/* TO CVS commands */ +#define VSS_ISTREAM_CMD_CREATE_PASSIVE_CONTROL_SESSION 0x00011140 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_ISTREAM_CMD_CREATE_FULL_CONTROL_SESSION 0x000110F7 +/* Create a new full control stream session. */ + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C + +#define VSS_ISTREAM_CMD_SET_MUTE 0x00011022 + +#define VSS_ISTREAM_CMD_SET_MEDIA_TYPE 0x00011186 +/* Set media type on the stream. */ + +#define VSS_ISTREAM_EVT_SEND_ENC_BUFFER 0x00011015 +/* Event sent by the stream to its client to provide an encoded packet. */ + +#define VSS_ISTREAM_EVT_REQUEST_DEC_BUFFER 0x00011017 +/* Event sent by the stream to its client requesting for a decoder packet. + * The client should respond with a VSS_ISTREAM_EVT_SEND_DEC_BUFFER event. + */ + +#define VSS_ISTREAM_EVT_SEND_DEC_BUFFER 0x00011016 +/* Event sent by the client to the stream in response to a + * VSS_ISTREAM_EVT_REQUEST_DEC_BUFFER event, providing a decoder packet. + */ + +#define VSS_ISTREAM_CMD_VOC_AMR_SET_ENC_RATE 0x0001113E +/* Set AMR encoder rate. */ + +#define VSS_ISTREAM_CMD_VOC_AMRWB_SET_ENC_RATE 0x0001113F +/* Set AMR-WB encoder rate. */ + +#define VSS_ISTREAM_CMD_CDMA_SET_ENC_MINMAX_RATE 0x00011019 +/* Set encoder minimum and maximum rate. */ + +#define VSS_ISTREAM_CMD_SET_ENC_DTX_MODE 0x0001101D +/* Set encoder DTX mode. */ + +struct vss_istream_cmd_create_passive_control_session_t { + char name[20]; + /**< + * A variable-sized stream name. + * + * The stream name size is the payload size minus the size of the other + * fields. + */ +} __packed; + +struct vss_istream_cmd_set_mute_t { + uint16_t direction; + /**< + * 0 : TX only + * 1 : RX only + * 2 : TX and Rx + */ + uint16_t mute_flag; + /**< + * Mute, un-mute. + * + * 0 : Silence disable + * 1 : Silence enable + * 2 : CNG enable. Applicable to TX only. If set on RX behavior + * will be the same as 1 + */ +} __packed; + +struct vss_istream_cmd_create_full_control_session_t { + uint16_t direction; + /* + * Stream direction. + * + * 0 : TX only + * 1 : RX only + * 2 : TX and RX + * 3 : TX and RX loopback + */ + uint32_t enc_media_type; + /* Tx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ + uint32_t dec_media_type; + /* Rx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ + uint32_t network_id; + /* Network ID. (Refer to VSS_NETWORK_ID_XXX). */ + char name[20]; + /* + * A variable-sized stream name. + * + * The stream name size is the payload size minus the size of the other + * fields. + */ +} __packed; + +struct vss_istream_cmd_set_media_type_t { + uint32_t rx_media_id; + /* Set the Rx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ + uint32_t tx_media_id; + /* Set the Tx vocoder type. (Refer to VSS_MEDIA_ID_XXX). */ +} __packed; + +struct vss_istream_evt_send_enc_buffer_t { + uint32_t media_id; + /* Media ID of the packet. */ + uint8_t packet_data[MAX_VOC_PKT_SIZE]; + /* Packet data buffer. */ +} __packed; + +struct vss_istream_evt_send_dec_buffer_t { + uint32_t media_id; + /* Media ID of the packet. */ + uint8_t packet_data[MAX_VOC_PKT_SIZE]; + /* Packet data. */ +} __packed; + +struct vss_istream_cmd_voc_amr_set_enc_rate_t { + uint32_t mode; + /* Set the AMR encoder rate. + * + * 0x00000000 : 4.75 kbps + * 0x00000001 : 5.15 kbps + * 0x00000002 : 5.90 kbps + * 0x00000003 : 6.70 kbps + * 0x00000004 : 7.40 kbps + * 0x00000005 : 7.95 kbps + * 0x00000006 : 10.2 kbps + * 0x00000007 : 12.2 kbps + */ +} __packed; + +struct vss_istream_cmd_voc_amrwb_set_enc_rate_t { + uint32_t mode; + /* Set the AMR-WB encoder rate. + * + * 0x00000000 : 6.60 kbps + * 0x00000001 : 8.85 kbps + * 0x00000002 : 12.65 kbps + * 0x00000003 : 14.25 kbps + * 0x00000004 : 15.85 kbps + * 0x00000005 : 18.25 kbps + * 0x00000006 : 19.85 kbps + * 0x00000007 : 23.05 kbps + * 0x00000008 : 23.85 kbps + */ +} __packed; + +struct vss_istream_cmd_cdma_set_enc_minmax_rate_t { + uint16_t min_rate; + /* Set the lower bound encoder rate. + * + * 0x0000 : Blank frame + * 0x0001 : Eighth rate + * 0x0002 : Quarter rate + * 0x0003 : Half rate + * 0x0004 : Full rate + */ + uint16_t max_rate; + /* Set the upper bound encoder rate. + * + * 0x0000 : Blank frame + * 0x0001 : Eighth rate + * 0x0002 : Quarter rate + * 0x0003 : Half rate + * 0x0004 : Full rate + */ +} __packed; + +struct vss_istream_cmd_set_enc_dtx_mode_t { + uint32_t enable; + /* Toggle DTX on or off. + * + * 0 : Disables DTX + * 1 : Enables DTX + */ +} __packed; + +struct cvs_create_passive_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_create_passive_control_session_t cvs_session; +} __packed; + +struct cvs_create_full_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_create_full_control_session_t cvs_session; +}; + +struct cvs_destroy_session_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvs_set_mute_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_mute_t cvs_set_mute; +} __packed; + +struct cvs_set_media_type_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_media_type_t media_type; +} __packed; + +struct cvs_send_dec_buf_cmd { + struct apr_hdr hdr; + struct vss_istream_evt_send_dec_buffer_t dec_buf; +} __packed; + +struct cvs_set_amr_enc_rate_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_voc_amr_set_enc_rate_t amr_rate; +} __packed; + +struct cvs_set_amrwb_enc_rate_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_voc_amrwb_set_enc_rate_t amrwb_rate; +} __packed; + +struct cvs_set_cdma_enc_minmax_rate_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_cdma_set_enc_minmax_rate_t cdma_rate; +} __packed; + +struct cvs_set_enc_dtx_mode_cmd { + struct apr_hdr hdr; + struct vss_istream_cmd_set_enc_dtx_mode_t dtx_mode; +} __packed; + +/* TO CVP commands */ + +#define VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION 0x000100C3 +/**< Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C + +#define VSS_IVOCPROC_CMD_SET_DEVICE 0x000100C4 + +#define VSS_IVOCPROC_CMD_SET_VP3_DATA 0x000110EB + +#define VSS_IVOCPROC_CMD_SET_RX_VOLUME_INDEX 0x000110EE + +#define VSS_IVOCPROC_CMD_ENABLE 0x000100C6 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IVOCPROC_CMD_DISABLE 0x000110E1 +/**< No payload. Wait for APRV2_IBASIC_RSP_RESULT response. */ + +#define VSS_IVOCPROC_TOPOLOGY_ID_NONE 0x00010F70 +#define VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS 0x00010F71 +#define VSS_IVOCPROC_TOPOLOGY_ID_TX_DM_FLUENCE 0x00010F72 + +#define VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT 0x00010F77 + +/* Newtwork IDs */ +#define VSS_NETWORK_ID_DEFAULT 0x00010037 +#define VSS_NETWORK_ID_VOIP_NB 0x00011240 +#define VSS_NETWORK_ID_VOIP_WB 0x00011241 +#define VSS_NETWORK_ID_VOIP_WV 0x00011242 + +/* Media types */ +#define VSS_MEDIA_ID_EVRC_MODEM 0x00010FC2 +/* 80-VF690-47 CDMA enhanced variable rate vocoder modem format. */ +#define VSS_MEDIA_ID_AMR_NB_MODEM 0x00010FC6 +/* 80-VF690-47 UMTS AMR-NB vocoder modem format. */ +#define VSS_MEDIA_ID_AMR_WB_MODEM 0x00010FC7 +/* 80-VF690-47 UMTS AMR-WB vocoder modem format. */ +#define VSS_MEDIA_ID_PCM_NB 0x00010FCB +#define VSS_MEDIA_ID_PCM_WB 0x00010FCC +/* Linear PCM (16-bit, little-endian). */ +#define VSS_MEDIA_ID_G711_ALAW 0x00010FCD +/* G.711 a-law (contains two 10ms vocoder frames). */ +#define VSS_MEDIA_ID_G711_MULAW 0x00010FCE +/* G.711 mu-law (contains two 10ms vocoder frames). */ +#define VSS_MEDIA_ID_G729 0x00010FD0 +/* G.729AB (contains two 10ms vocoder frames. */ + +#define VOICE_CMD_SET_PARAM 0x00011006 +#define VOICE_CMD_GET_PARAM 0x00011007 +#define VOICE_EVT_GET_PARAM_ACK 0x00011008 + +struct vss_ivocproc_cmd_create_full_control_session_t { + uint16_t direction; + /* + * stream direction. + * 0 : TX only + * 1 : RX only + * 2 : TX and RX + */ + uint32_t tx_port_id; + /* + * TX device port ID which vocproc will connect to. If not supplying a + * port ID set to VSS_IVOCPROC_PORT_ID_NONE. + */ + uint32_t tx_topology_id; + /* + * Tx leg topology ID. If not supplying a topology ID set to + * VSS_IVOCPROC_TOPOLOGY_ID_NONE. + */ + uint32_t rx_port_id; + /* + * RX device port ID which vocproc will connect to. If not supplying a + * port ID set to VSS_IVOCPROC_PORT_ID_NONE. + */ + uint32_t rx_topology_id; + /* + * Rx leg topology ID. If not supplying a topology ID set to + * VSS_IVOCPROC_TOPOLOGY_ID_NONE. + */ + int32_t network_id; + /* + * Network ID. (Refer to VSS_NETWORK_ID_XXX). If not supplying a network + * ID set to VSS_NETWORK_ID_DEFAULT. + */ +} __packed; + +struct vss_ivocproc_cmd_set_volume_index_t { + uint16_t vol_index; + /**< + * Volume index utilized by the vocproc to index into the volume table + * provided in VSS_IVOCPROC_CMD_CACHE_VOLUME_CALIBRATION_TABLE and set + * volume on the VDSP. + */ +} __packed; + +struct vss_ivocproc_cmd_set_device_t { + uint32_t tx_port_id; + /**< + * TX device port ID which vocproc will connect to. + * VSS_IVOCPROC_PORT_ID_NONE means vocproc will not connect to any port. + */ + uint32_t tx_topology_id; + /**< + * TX leg topology ID. + * VSS_IVOCPROC_TOPOLOGY_ID_NONE means vocproc does not contain any + * pre/post-processing blocks and is pass-through. + */ + int32_t rx_port_id; + /**< + * RX device port ID which vocproc will connect to. + * VSS_IVOCPROC_PORT_ID_NONE means vocproc will not connect to any port. + */ + uint32_t rx_topology_id; + /**< + * RX leg topology ID. + * VSS_IVOCPROC_TOPOLOGY_ID_NONE means vocproc does not contain any + * pre/post-processing blocks and is pass-through. + */ +} __packed; + +struct cvp_create_full_ctl_session_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_create_full_control_session_t cvp_session; +} __packed; + +struct cvp_command { + struct apr_hdr hdr; +} __packed; + +struct cvp_set_device_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_set_device_t cvp_set_device; +} __packed; + +struct cvp_set_vp3_data_cmd { + struct apr_hdr hdr; +} __packed; + +struct cvp_set_rx_volume_index_cmd { + struct apr_hdr hdr; + struct vss_ivocproc_cmd_set_volume_index_t cvp_set_vol_idx; +} __packed; + +/* CB for up-link packets. */ +typedef void (*ul_cb_fn)(uint8_t *voc_pkt, + uint32_t pkt_len, + void *private_data); + +/* CB for down-link packets. */ +typedef void (*dl_cb_fn)(uint8_t *voc_pkt, + uint32_t *pkt_len, + void *private_data); + + +struct mvs_driver_info { + uint32_t media_type; + uint32_t rate; + uint32_t network_type; + ul_cb_fn ul_cb; + dl_cb_fn dl_cb; + void *private_data; +}; + +struct incall_rec_info { + uint32_t pending; + uint32_t rec_mode; +}; + +struct incall_music_info { + uint32_t pending; + uint32_t playing; +}; + +struct voice_data { + int voc_state;/*INIT, CHANGE, RELEASE, RUN */ + uint32_t voc_path; + + wait_queue_head_t mvm_wait; + wait_queue_head_t cvs_wait; + wait_queue_head_t cvp_wait; + + uint32_t device_events; + + /* cache the values related to Rx and Tx */ + struct device_data dev_rx; + struct device_data dev_tx; + + /* these default values are for all devices */ + uint32_t default_mute_val; + uint32_t default_vol_val; + uint32_t default_sample_val; + + /* APR to MVM in the Q6 */ + void *apr_q6_mvm; + /* APR to CVS in the Q6 */ + void *apr_q6_cvs; + /* APR to CVP in the Q6 */ + void *apr_q6_cvp; + + u32 mvm_state; + u32 cvs_state; + u32 cvp_state; + + /* Handle to MVM in the Q6 */ + u16 mvm_passive_handle; /* for cs call */ + u16 mvm_full_handle; /* for voip */ + /* Handle to CVS in the Q6 */ + u16 cvs_passive_handle; + u16 cvs_full_handle; + /* Handle to CVP in the Q6 */ + u16 cvp_passive_handle; + u16 cvp_full_handle; + + struct mutex lock; + + struct mvs_driver_info mvs_info; + + uint16_t sidetone_gain; + + struct voice_dev_route_state voc_route_state; +}; + +int voc_set_voc_path_full(uint32_t set); + +void voc_register_mvs_cb(ul_cb_fn ul_cb, + dl_cb_fn dl_cb, + void *private_data); + +void voc_config_vocoder(uint32_t media_type, + uint32_t rate, + uint32_t network_type); + +enum { + DEV_RX = 0, + DEV_TX, +}; + +enum { + RX_PATH = 0, + TX_PATH, +}; + +/* called by alsa driver */ +int voc_start_voice_call(void); +int voc_end_voice_call(void); +void voc_set_rxtx_port(uint32_t dev_port_id, uint32_t dev_type); +int voc_set_rx_vol_index(uint32_t dir, uint32_t voc_idx); +int voc_set_tx_mute(uint32_t dir, uint32_t mute); +int voc_disable_cvp(void); +int voc_enable_cvp(void); +void voc_set_route_flag(uint8_t path_dir, uint8_t set); +uint8_t voc_get_route_flag(uint8_t path_dir); + +#endif diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c index 9465588b02f..f16f02b15bf 100644 --- a/sound/soc/samsung/dma.c +++ b/sound/soc/samsung/dma.c @@ -10,8 +10,7 @@ * * 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. + * Free Software Foundation; only version 2 of the License. */ #include @@ -497,5 +496,5 @@ module_exit(samsung_asoc_exit); MODULE_AUTHOR("Ben Dooks, "); MODULE_DESCRIPTION("Samsung ASoC DMA Driver"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:samsung-audio"); diff --git a/sound/soc/sh/siu_pcm.c b/sound/soc/sh/siu_pcm.c index f8f681690a7..c0046451ada 100644 --- a/sound/soc/sh/siu_pcm.c +++ b/sound/soc/sh/siu_pcm.c @@ -6,8 +6,7 @@ * * 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. + * the Free Software Foundation; only version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 1dc6de60a06..87569ed75f9 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2,12 +2,12 @@ * soc-dapm.c -- ALSA SoC Dynamic Audio Power Management * * Copyright 2005 Wolfson Microelectronics PLC. + * * Author: Liam Girdwood * * 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. + * Free Software Foundation; only version 2 of the License. * * Features: * o Changes power status of internal codec blocks depending on the @@ -1545,7 +1545,6 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) dapm->dev_power = 1; break; case SND_SOC_DAPM_STREAM_STOP: -#warning need re-work if (dapm->codec) dapm->dev_power = !!dapm->codec->active; else @@ -3073,4 +3072,4 @@ void snd_soc_dapm_shutdown(struct snd_soc_card *card) /* Module information */ MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/soc-dsp.c b/sound/soc/soc-dsp.c index 9323b57152e..77eda327fed 100644 --- a/sound/soc/soc-dsp.c +++ b/sound/soc/soc-dsp.c @@ -5,10 +5,9 @@ * * Author: Liam Girdwood * - * 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 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. * */ @@ -1511,4 +1510,4 @@ int soc_dsp_debugfs_add(struct snd_soc_pcm_runtime *rtd) /* Module information */ MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("ALSA SoC DSP Core"); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2");